Update vendor

This commit is contained in:
Tobias Furuholm 2018-01-17 13:54:37 +01:00
parent 7a2ae81363
commit 77df37ca67
61 changed files with 15835 additions and 6971 deletions

14
glide.lock generated
View File

@ -1,5 +1,5 @@
hash: b5b9ebebad30becd361736196a015af23b1d9a616a375c7fc13823121fd17226
updated: 2017-06-05T16:11:29.019891941-04:00
hash: d9cd9bf3ab1048a80f5ad90e05a7ea4c7614c30c561199b71147609de600a524
updated: 2018-01-17T13:29:36.176987+01:00
imports:
- name: github.com/beorn7/perks
version: 4c0e84591b9aa9e6dcfdf3e020114cd81f89d5f9
@ -23,9 +23,19 @@ imports:
- jsonpb
- proto
- protoc-gen-go/descriptor
- ptypes
- ptypes/any
- ptypes/duration
- ptypes/empty
- ptypes/struct
- ptypes/timestamp
- name: github.com/grafeas/grafeas
version: 73210e9cadcba64b5b211a0ec64a9f4c2d4841b5
repo: https://github.com/Grafeas/Grafeas.git
vcs: git
subpackages:
- samples/server/go-server/api/server/name
- v1alpha1/proto
- name: github.com/grpc-ecosystem/go-grpc-prometheus
version: 2500245aa6110c562d17020fb31a2c133d737799
- name: github.com/grpc-ecosystem/grpc-gateway

View File

@ -30,3 +30,6 @@ import:
version: ^1.2.15
- package: gopkg.in/yaml.v2
- package: github.com/cockroachdb/cmux
- package: github.com/grafeas/grafeas
vcs: git
repo: https://github.com/Grafeas/Grafeas.git

48
vendor/github.com/grafeas/grafeas/.circleci/config.yml generated vendored Normal file
View File

@ -0,0 +1,48 @@
# Golang CircleCI 2.0 configuration file
#
# Check https://circleci.com/docs/2.0/language-go/ for more details
version: 2
jobs:
build:
docker:
# specify the version
- image: circleci/golang:1.8
# Specify service dependencies here if necessary
# CircleCI maintains a library of pre-built images
# documented at https://circleci.com/docs/2.0/circleci-images/
# - image: circleci/postgres:9.4
#### TEMPLATE_NOTE: go expects specific checkout path representing url
#### expecting it in the form of
#### /go/src/github.com/circleci/go-tool
#### /go/src/bitbucket.org/circleci/go-tool
working_directory: /go/src/github.com/grafeas/grafeas/
steps:
- checkout
- run:
name: Install protoc
command: >
sudo apt-get update
sudo wget
https://github.com/google/protobuf/releases/download/v3.3.0/protoc-3.3.0-linux-x86_64.zip
unzip protoc-3.3.0-linux-x86_64.zip -d .
rm protoc-3.3.0-linux-x86_64.zip
sudo apt-get remove -y wget unzip
sudo apt-get autoremove -y
- run:
name: Make protoc executables available globally
command: |
echo 'export PATH=./bin:$PATH' >> $BASH_ENV
source /home/circleci/.bashrc
- run:
name: Install go dependencies
command: go get -u -v github.com/golang/protobuf/protoc-gen-go github.com/grpc-ecosystem/grpc-gateway/protoc-gen-grpc-gateway
# specify any bash command here prefixed with `run: `
- run: make build; make test

1
vendor/github.com/grafeas/grafeas/.gitignore generated vendored Normal file
View File

@ -0,0 +1 @@
.install.protoc-gen-go

View File

@ -0,0 +1,9 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="GoImports">
<option name="groupStdlibImports" value="true" />
<option name="importSorting" value="GOFMT" />
<option name="moveAllImportsInOneDeclaration" value="true" />
<option name="moveAllStdlibImportsInOneGroup" value="true" />
</component>
</project>

7
vendor/github.com/grafeas/grafeas/AUTHORS generated vendored Normal file
View File

@ -0,0 +1,7 @@
# This is the list of Grafeas authors for copyright purposes.
#
# This does not necessarily list everyone who has contributed code, since in
# some cases, their employer may be the copyright holder. To see the full list
# of contributors, see the revision history in source control.
Google Inc.
JFrog Ltd

30
vendor/github.com/grafeas/grafeas/CONTRIBUTING.md generated vendored Normal file
View File

@ -0,0 +1,30 @@
# How to Contribute
We'd love to accept your patches and contributions to this project. There are
just a few small guidelines you need to follow.
## Contributor License Agreement
Contributions to this project must be accompanied by a Contributor License
Agreement. You (or your employer) retain the copyright to your contribution,
this simply gives us permission to use and redistribute your contributions as
part of the project. Head over to <https://cla.developers.google.com/> to see
your current agreements on file or to sign a new one.
You generally only need to submit a CLA once, so if you've already submitted one
(even if it was for a different project), you probably don't need to do it
again.
## Proposals and PRs
If you would like to make a large change, please start with a proposal issue that includes:
* What you would like to achieve
* Why you'd like to make this change
* A design overview
## Code reviews
All submissions, including submissions by project members, require review. We
use GitHub pull requests for this purpose. Consult
[GitHub Help](https://help.github.com/articles/about-pull-requests/) for more
information on using pull requests.

10
vendor/github.com/grafeas/grafeas/Dockerfile generated vendored Normal file
View File

@ -0,0 +1,10 @@
FROM golang:1.9
COPY . /go/src/github.com/grafeas/grafeas/
WORKDIR /go/src/github.com/grafeas/grafeas/samples/server/go-server/api/server/main
RUN CGO_ENABLED=0 go build -o grafeas-server .
FROM alpine:latest
WORKDIR /
COPY --from=0 /go/src/github.com/grafeas/grafeas/samples/server/go-server/api/server/main/grafeas-server /grafeas-server
EXPOSE 8080
CMD ["/grafeas-server"]

201
vendor/github.com/grafeas/grafeas/LICENSE generated vendored Normal file
View File

@ -0,0 +1,201 @@
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
APPENDIX: How to apply the Apache License to your work.
To apply the Apache License to your work, attach the following
boilerplate notice, with the fields enclosed by brackets "[]"
replaced with your own identifying information. (Don't include
the brackets!) The text should be enclosed in the appropriate
comment syntax for the file format. We also recommend that a
file or class name and description of purpose be included on the
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright 2017 The Grafeas Authors
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.

46
vendor/github.com/grafeas/grafeas/Makefile generated vendored Normal file
View File

@ -0,0 +1,46 @@
.PHONY: build fmt test vet clean
SRC = $(shell find . -type f -name '*.go' -not -path "./vendor/*")
CLEAN := *~
default: build
install.tools: .install.protoc-gen-go .install.grpc-gateway
CLEAN += .install.protoc-gen-go .install.grpc-gateway
.install.protoc-gen-go:
go get -u -v github.com/golang/protobuf/protoc-gen-go && touch $@
.install.grpc-gateway:
go get -u -v github.com/grpc-ecosystem/grpc-gateway/protoc-gen-grpc-gateway github.com/grpc-ecosystem/grpc-gateway/protoc-gen-swagger && touch $@
build: vet fmt grafeas_go
go build -v ./...
# http://golang.org/cmd/go/#hdr-Run_gofmt_on_package_sources
fmt:
@gofmt -l -w $(SRC)
test:
@go test -v ./...
vet:
@go tool vet ${SRC}
v1alpha1/proto/grafeas.pb.go: .install.protoc-gen-go .install.grpc-gateway v1alpha1/proto/grafeas.proto
protoc \
-I ./ \
-I vendor/github.com/grpc-ecosystem/grpc-gateway/third_party/googleapis \
-I vendor/github.com/googleapis/googleapis \
--go_out=plugins=grpc:. \
--grpc-gateway_out=logtostderr=true:. \
--swagger_out=logtostderr=true:. \
v1alpha1/proto/grafeas.proto
.PHONY: grafeas_go
grafeas_go: v1alpha1/proto/grafeas.pb.go
clean:
go clean ./...
rm -f $(CLEAN)

216
vendor/github.com/grafeas/grafeas/README.md generated vendored Normal file
View File

@ -0,0 +1,216 @@
# Grafeas: A Component Metadata API
Grafeas defines metadata API spec for computing components (e.g., VM images, container images, jar files, scripts) that can assist with aggregations over your metadata. Grafeas uses two API concepts, a **note** and an **occurrence**. This division allows 3rd party metadata providers to create and manage metadata on behalf of many customers. Additionally, the division also allows implementation of access control settings that allow fine grain access control.
## Running grafeas
To run your own Grafeas instance just follow the [instructions](docs/running_greafeas.md).
## Definition of terms
**Notes**: A note is an item or condition that can be found via an analysis or something that is used multiple times in a process. For example, a CVE could be the result of a vulnerability analysis of a Linux package. In a build process, we would store information about our builder in a note.
A note name should take the format `/projects/<project_id>/notes/<note_id>` where the project_id would typically be different from the project where the occurrence is created and the note_id would be unique per note-project, and informative if possible.
Access to notes should be read-only for users who have access to occurrences referencing them, and editable only by the note owner.
**Occurrences**: An occurrence can be thought of as an instantiation of a note and describes how the note was found in a specific cloud resource or project (e.g., location, specific remediation steps, etc.), or what the results of a specific note were (e.g., the container images that resulted from a build). For example, an occurrence might report that the heartbleed OpenSSL bug (a possible Note) was found in a specific package of a container image, and include information about how to remedy the heartbleed bug based on the customers package.
An occurrence name should take the format `/projects/<project_id>/occurrences/<occurrence_id>` where the project_id would typically be different from the project where the note is created and the occurrence_id would be unique per occurrence-project, and would often be random.
Write access to occurrences should only be granted to users who have access to link a note to the occurrence. Any users can have read access to occurrences.
## Kind Specific Schemas
In order to properly aggregate over metadata stored in Grafeas, each kind of information stored has a strict schema. These schemas allow normalization of data from multiple providers, giving users the ability to see meaningful insights in their components over time. Defined below are the currently supported kinds, and a brief summary of what the notes and occurrences for each of them will contain.
Specifying a kind in our notes and occurrences makes Grafeas extensible. As new metadata types need support, new kinds can be added, each with their own schema.
>TODO:Document the process for adding a new kind to the spec and generating the model, documents, and client libraries to include that kind. #38
|Kind |Note Summary |Occurrence Summary |
|---------------------|-------------------------------------------------------------------------|-------------------------------------------------|
|PACKAGE_VULNERABILITY|CVE or vulnerability description and details including severity, versions|Affected packages/versions in a specific resource|
|BUILD_DETAILS |Builder version and signature |Details of this specific build including inputs and outputs|
|IMAGE_BASIS |Base Image for a container |An image that uses the base image, and layers included on top of base image|
|PACKAGE_MANAGER |Package Descriptions |Filesystem locations of where the package is installed in a specific resource|
|DEPLOYMENT_HISTORY |A resource that can be deployed |Details of each deployment of the resource|
|ATTESTATION |A logical attestation "role" or "authority", used as an anchor for attestations|An attestation by an authority for a specific property and resource|
## Examples
A vulnerability scanning provider would create a note under their project with the following json for CVE-2017-14159
```
{
"name": "projects/security-scanner/notes/CVE-2017-14159",
"shortDescription": "CVE-2017-14159",
"longDescription": "NIST vectors: AV:L/AC:M/Au:N/C:N/I:N",
"relatedUrl": [
{
"url": "https://security-tracker.debian.org/tracker/CVE-2017-14159",
"label": "More Info"
},
{
"url": "http://people.ubuntu.com/~ubuntu-security/cve/CVE-2017-14159",
"label": "More Info"
}
],
"kind": "PACKAGE_VULNERABILITY",
"createTime": "2017-09-05T21:44:52.071982Z",
"updateTime": "2017-09-29T16:16:01.140652Z",
"vulnerabilityType": {
"cvssScore": 1.9,
"severity": "LOW",
"details": [
{
"cpeUri": "cpe:/o:debian:debian_linux:7",
"severityName": "LOW",
"fixedLocation": {
"cpeUri": "cpe:/o:debian:debian_linux:7",
"package": "openldap",
"version": {
"kind": "MAXIMUM"
}
},
"minAffectedVersion": {
"kind": "MINIMUM"
},
"package": "openldap",
"description": "slapd in OpenLDAP 2.4.45 and earlier creates a PID file after dropping privileges to a non-root account, which might allow local users to kill arbitrary processes by leveraging access to this non-root account for PID file modification before a root script executes a \"kill `cat /pathname`\" command, as demonstrated by openldap-initscript."
},
{
"cpeUri": "cpe:/o:debian:debian_linux:unstable",
"severityName": "LOW",
"fixedLocation": {
"cpeUri": "cpe:/o:debian:debian_linux:unstable",
"package": "openldap",
"version": {
"kind": "MAXIMUM"
}
},
"minAffectedVersion": {
"kind": "MINIMUM"
},
"package": "openldap",
"description": "slapd in OpenLDAP 2.4.45 and earlier creates a PID file after dropping privileges to a non-root account, which might allow local users to kill arbitrary processes by leveraging access to this non-root account for PID file modification before a root script executes a \"kill `cat /pathname`\" command, as demonstrated by openldap-initscript."
},
{
"cpeUri": "cpe:/o:debian:debian_linux:9",
"severityName": "LOW",
"fixedLocation": {
"cpeUri": "cpe:/o:debian:debian_linux:9",
"package": "openldap",
"version": {
"kind": "MAXIMUM"
}
},
"minAffectedVersion": {
"kind": "MINIMUM"
},
"package": "openldap",
"description": "slapd in OpenLDAP 2.4.45 and earlier creates a PID file after dropping privileges to a non-root account, which might allow local users to kill arbitrary processes by leveraging access to this non-root account for PID file modification before a root script executes a \"kill `cat /pathname`\" command, as demonstrated by openldap-initscript."
},
{
"cpeUri": "cpe:/o:debian:debian_linux:8",
"severityName": "LOW",
"fixedLocation": {
"cpeUri": "cpe:/o:debian:debian_linux:8",
"package": "openldap",
"version": {
"kind": "MAXIMUM"
}
},
"minAffectedVersion": {
"kind": "MINIMUM"
},
"package": "openldap",
"description": "slapd in OpenLDAP 2.4.45 and earlier creates a PID file after dropping privileges to a non-root account, which might allow local users to kill arbitrary processes by leveraging access to this non-root account for PID file modification before a root script executes a \"kill `cat /pathname`\" command, as demonstrated by openldap-initscript."
},
{
"cpeUri": "cpe:/o:canonical:ubuntu_linux:14.04",
"severityName": "LOW",
"fixedLocation": {
"cpeUri": "cpe:/o:canonical:ubuntu_linux:14.04",
"package": "openldap",
"version": {
"kind": "MAXIMUM"
}
},
"minAffectedVersion": {
"kind": "MINIMUM"
},
"package": "openldap",
"description": "slapd in OpenLDAP 2.4.45 and earlier creates a PID file after dropping privileges to a non-root account, which might allow local users to kill arbitrary processes by leveraging access to this non-root account for PID file modification before a root script executes a \"kill `cat /pathname`\" command, as demonstrated by openldap-initscript."
},
{
"cpeUri": "cpe:/o:canonical:ubuntu_linux:16.04",
"severityName": "LOW",
"fixedLocation": {
"cpeUri": "cpe:/o:canonical:ubuntu_linux:16.04",
"package": "openldap",
"version": {
"kind": "MAXIMUM"
}
},
"minAffectedVersion": {
"kind": "MINIMUM"
},
"package": "openldap",
"description": "slapd in OpenLDAP 2.4.45 and earlier creates a PID file after dropping privileges to a non-root account, which might allow local users to kill arbitrary processes by leveraging access to this non-root account for PID file modification before a root script executes a \"kill `cat /pathname`\" command, as demonstrated by openldap-initscript."
}
]
}
}
```
On scanning and coming across this vulnerability, a security scanning provider would create the following in their customers project:
```
{
"name": "projects/scanning-customer/occurrences/randomId1234",
"resourceUrl": "https://gcr.io/scanning-customer/dockerimage@sha256:hash",
"noteName": "projects/security-scanner/notes/CVE-2017-14159",
"kind": "PACKAGE_VULNERABILITY",
"createTime": "2017-09-29T02:58:23.376798Z",
"updateTime": "2017-09-29T07:35:22.141762Z",
"vulnerabilityDetails": {
"severity": "LOW",
"cvssScore": 1.9,
"packageIssue": [
{
"affectedLocation": {
"cpeUri": "cpe:/o:debian:debian_linux:8",
"package": "openldap",
"version": {
"name": "2.4.40+dfsg",
"revision": "1+deb8u2"
}
},
"fixedLocation": {
"cpeUri": "cpe:/o:debian:debian_linux:8",
"package": "openldap",
"version": {
"kind": "MAXIMUM"
}
},
"severityName": "LOW"
}
]
}
}
```
## Resource Urls
Component resource Urls need to be unique per resource as well as immutable. This will mean that the metadata associated with a resourceUrl will always be associated with exactly one component, and what is pointed at should never change. Content addressable resource urls are preferred. In the case with resources that cannot be immutable, a timestamp should be appended.
The following table provides examples one could use as resource urls for several component types:
Component Type|Identifier |Example|
--------------|--------------------------------------------|-------|
|Debian |deb://dist(optional):arch:name:version |deb://lucid:i386:acl:2.2.49-2|
|Docker |https://Namespace/name@sha256:<Checksum> |https://gcr.io/scanning-customer/dockerimage@sha256:244fd47e07d1004f0aed9c156aa09083c82bf8944eceb67c946ff7430510a77b|
|Generic file |file://sha256:<Checksum>:name |file://sha256:244fd47e07d1004f0aed9c156aa09083c82bf8944eceb67c946ff7430510a77b:foo.jar|
|Maven |gav://group:artifact:version |`gav://ant:ant:1.6.5`|
|NPM |npm://package:version |npm://mocha:2.4.5|
|NuGet |nuget://module:version |nuget://log4net:9.0.1|
|Python |pip://package:version |pip://raven:5.13.0|
|RPM |rpm://dist(optional):arch:name:version |rpm://el6:i386:ImageMagick:6.7.2.7-4|

View File

@ -0,0 +1,240 @@
# Kritis: Deployment Authorization for Kubernetes Applications
## Abstract
Binary Authorization aims to provide full software supply chain security for
cloud based applications. In an initial release we enable customers to secure
their supply chain for Kubernetes applications using an attestation based
enforcement technology.
## Introduction
Organizations increasingly employ short software life cycle (for example
continuous delivery) and highly decoupled systems (for example microservice
architecture). In such environments it becomes difficult to ensure that all
software is released and deployed according to best practices and standards.
This is important because running, say, a wrong version of a binary, through
accident or malice, may result in downtime, loss of user data, financial loss,
or worse damage. Binary Authorization wants to address this major concern of
todays organizations: central control and enforcement of software life cycle
process.
Binary Authorization allows stakeholders to ensure that deployed software
artifacts have been prepared according to organizations standards. It does so
through attestation and enforcement: a deployment is prevented unless the
artifact is conformant to central policy; and to express evidence of
conformance, teams use trusted attestations. A Binary Authorization policy then
states attestation requirements necessary for artifact deployment. Policy thus
codifies an important part of organizations life cycle policy.
### Scope
Kritis is our initiative to provide an open implementation of Binary
Authorization. This paper discusses the general ideas behind Binary
Authorization, and as such is a first part of the Kritis initiative. It further
touches upon some specifics of our existing implementation of Binary
Authorization, which in its first release is focused on Google Container Engine
(GKE).
### Software Life Cycle Within an Organization
Each organization uses a release process tailored to specific needs and
constraints. Binary Authorization does not prescribe any process, it instead
helps codify and enforce the process that makes sense to the organization. To
see how Binary Authorization fits into a release process, consider the following
common pattern. Once a release is cut, artifacts go through the following
stages, successful completion of a stage being the prerequisite for progression
to the next one:
* Build and unit test.
* Deploy into development environment, where users arent affected.
* End to end testing might occur here.
* Deploy into QA environment, where only internal users are affected.
* Deploy into canary environment, where only a fraction of external users are
affected.
* Deploy into production.
When an artifact successfully completes a stage, an attestation on that artifact
is created which asserts success. The policy requires previous stages
attestations in order to allow deployment into next stages environment. In this
way, Binary Authorization policy increases assurance that release process is
followed. The policy may also specify that only recently attested artifacts be
allowed deployment, and so ensure freshness.
#### Third Party Dependencies
Organizations rarely develop software from blank slate. Most of the time there
is some reliance on third party “canned” software, e.g. sidecar container images
from public repositories. Ideally, third party dependencies should be subject to
the same scrutiny as internally developed software, however this is rarely
practical. Compromises are usually made, for example by vetting third party
container images as practically possible, and mandating the use of only tested
and vetted versions of the software. Binary Authorization supports this use
case.
### Example
An online merchant runs their services on Kubernetes. They have two clusters:
`production` to run production approved services, and `experimental` to run
experimental versions of services, but only 1% of the traffic is directed to the
experimental cluster. Production versions of the software must pass a stringent
test, while experimental versions, serving only a fraction of the traffic, are
subject to less strict criteria. Both experimental and production software must
pass a basic suite of tests. This organization wants to reduce the risk of
accidentally deploying experimental software to the production cluster.
Following policy realizes their requirements:
```
{
“cluster_admission_requirements”: {
“cluster_name”: “prod”,
“require_attestations”: [ “tested”, “production-approved” ]
}
“cluster_admission_requirements”: {
“cluster_name”: “experimental”,
“require_attestations”: [ “tested” ]
}
}
```
In this organization, the release process has to be designed to respect this
policy. Production qualification process must create the `production-approved`
attestation on artifacts which have indeed passed the qualification. Continuous
testing system must create the `tested` attestation.
## Binary Authorization Model in Google Container Engine (GKE)
Binary Authorization for GKE (BinAuthz) is available as an Alpha release. We
overview its design, discussing some of the more interesting choices. In a
nutshell, a user sets a BinAuthz policy for a GCP project; this policy specifies
attestations required to deploy a container image into the project or into a
specific cluster (or service account in the future). Attestations are managed
through Grafeas as a dedicated Kind `ATTESTATION`.
### Deployment Environments
Google Cloud Platform (GCP) uses projects and service accounts as security
boundaries. In addition to that, GKE uses clusters as a security boundary (a
Kubernetes cluster can have its own secrets for example). These are some of the
deployment targets that we plan to support as subjects of BinAuthz policy
requirements: project, service account, cluster.
### Policy Management
Key concepts of BinAuthz are Attestation Authority and Policy, realized as REST
resources managed through a REST API. An Attestation Authority is a named entity
which has the power to create attestations. As a REST resource, it encapsulates
the location of its attestations (where to store and retrieve from), as well as
verification criteria (what makes an attestation valid). A Policy then names
Attestation Authorities (whose attestations are) required to deploy an artifact
to some target.
#### Example
This might be an Attestation Authority which represents an organizations secure
build system.
```
{
“name”: “projects/secure-builder/attestationAuthorities/built-securely”
“public_keys”: <list of keys expected to sign this authoritys attestations>
}
```
A policy which requires securely built artifacts to deploy to the `prod` cluster
may then look like this.
```
{
“cluster_admission_requirements”: {
“cluster_name”: “prod”,
“attestation_requirements”: [ “projects/secure-builder/attestationAuthorities/built-securely” ]
}
}
```
### Attestations via Component Metadata API / Grafeas
Attestations are represented as Component Metadata objects. Grafeas is its open
source sister project, and can be used as attestation transport too. An
Attestation Authority names a Note (of Kind `ATTESTATION`) which is used as an
anchor for this authoritys attestations, and optionally specifies public keys
if attestations must be signed. Attestations by this authority are then
represented as Occurrences attached to the authoritys Note.
### Enforcement Module Entry Point
Kubernetes orchestrates the execution of containers, predominantly focusing on
Docker as the container runtime. Pod is the lowest level abstraction of a
running container. Users can create Pods directly, or users can create
Controllers (such as ReplicaSet) which then mediate Pod creation. We chose Pod
admission as the interception point. At Pod admission time, information which BinAuthz
needs is available: artifact identifier (container image URL), deploy target
(project, service account, cluster). And Pod creation is the chokepoint through
which flow all code paths to run a Docker container. Intercepting at Pod
creation has some consequences which we must tolerate. Notably, a user may
create a Controller, which then creates and manages Pods asynchronously from
users original request. Because BinAuthz intercepts Pod creation, but not
Container creation, we don't report a BinAuthz failure at Controller creation
time, but only later, asynchronously from users action. Longer-term we plan to
support intercepting Controller creation too for a better user experience
(clearer errors immediately delivered). Even then we will have to keep the Pod
enforcement, to ensure that community contributed controllers dont accidentally
bypass enforcement. Image policy webhook is a Kubernetes admission control
webhook which allows delegating Pod admission decisions to a web service. We
implement an enforcement service and configure the webhook to point to the
service.
### Artifact Identification via Early Tag Resolution
A Docker container image is identified by the registry where it is stored,
repository within the registry, and either tag or digest. Tags or digests are
usually used for versioning. A digest uniquely and immutably identifies a
container image. A tag, by contrast, may be associated to any digest, and this
association may change over time. We chose to allow only digest based container
images in BinAuthz. That is, when deploying to an environment which is subject
to BinAuthz, a tag based deployment is automatically disallowed as it is
impossible to decide the actual version that will be used once the Pod is
created. Besides giving clearer BinAuthz semantics, we believe that digest based
deployments are better production hygiene and thus were favored strongly by
customers we worked with. As an exception to this rule, our policy language
allows glob-based container image whitelisting, through which even tag based
deployments may be allowed. This is useful for images that dont go through the
same internal vetting process (e.g., various sidecar containers). Longer-term
this should be solved through exchange of attestations with the image providers.
## Conclusion and Next Steps
Binary Authorization enables centralized control over software release cycle.
Stakeholders configure policies to enforce the requirements of the release
process, gaining confidence that software is delivered to customers only if it
meets the organizations requirements. Attestations - trusted metadata
associated to software artifacts - are used to assert that software meets
specified requirements.
### Generalizing to Other Orchestration Systems
We described our first, GKE specific, implementation of BinAuthz, however we
note that the basic principles apply to a variety of orchestration systems. We
plan to support other GCP platforms (such as App Engine and App Engine Flex) in
the future. Furthermore, an open specification of BinAuthz is forthcoming.
### Richer Policies and Attestations
BinAuthz attestation is like a seal stamp: its only meaning is “authority `X`
attests artifact `Y`”. A richer language of statements, such as “authority `X`
attests that artifact `Y` was built from source code `Z`” allows for more
expressive policies and more meaningful control. We plan to extend the data
model and policy language to support such richer statements.
### Toolchain Integration
In its current form, BinAuthz requires custom integration into organizations
workflows. We will reduce the integration cost by working with partners to
support BinAuthz in their products. For example, CI/CD providers are of
particular interest. A CI/CD pipeline may at different stages produce various
attestations for the artifacts which it creates, and BinAuthz policy then
enforce that proper process was followed. Source control system is another
integration point, especially if Binary Authorization is to be based on source
provenance of artifacts. To strengthen such guarantees, a trusted build system
may be needed.

29
vendor/github.com/grafeas/grafeas/code-of-conduct.md generated vendored Normal file
View File

@ -0,0 +1,29 @@
## Grafeas Code of Conduct
At Google, we recognize and celebrate the creativity and collaboration of open source contributors and the diversity of skills, experiences, cultures, and opinions they bring to the projects and communities they participate in.
Every one of Google's open source projects and communities are inclusive environments, based on treating all individuals respectfully, regardless of gender identity and expression, sexual orientation, disabilities, neurodiversity, physical appearance, body size, ethnicity, nationality, race, age, religion, or other protected category.
We value diverse opinions, but we value respectful behavior more.
Respectful behavior includes:
* Being considerate, kind, constructive, and helpful.
* Not engaging in demeaning, discriminatory, harassing, hateful, sexualized, or physically threatening behavior, speech, and imagery.
* Not engaging in unwanted physical contact.
Some Google open source projects may adopt their own specific codes of conduct, which may have additional detailed expectations for participants.
### Resolve Peacefully
We do not believe that all conflict is bad; healthy debate and disagreement often yield positive results. However, it is never okay to be disrespectful or to engage in behavior that violates the projects code of conduct.
If you see someone violating the code of conduct, you are encouraged to address the behavior directly with those involved. Many issues can be resolved quickly and easily, and this gives people more control over the outcome of their dispute. If you are unable to resolve the matter for any reason, or if the behavior is threatening or harassing, report it. We are dedicated to providing an environment where participants feel welcome and safe.
Reports should be directed to wmd@google.com, the Project Steward for Grafeas. It is the Project Stewards duty to receive and address reported violations of the code of conduct. The steward will then work with a committee consisting of representatives from the Open Source Programs Office and the Google Open Source Strategy team.
We will investigate every complaint, but you may not receive a direct response. We will use our discretion in determining when and how to follow up on reported incidents, which may range from not taking action to permanent expulsion from the project and project-sponsored spaces. We will notify the accused of the report and provide them an opportunity to discuss it before any action is taken. In potentially harmful situations, such as ongoing harassment or threats to anyone's safety, we may take action without notice. We will do our utmost to keep the identity of the reporter anonymous.
This enforcement policy has been adopted from [IndieWeb Code of Conduct](https://indieweb.org/code-of-conduct).

22
vendor/github.com/grafeas/grafeas/config.yaml.sample generated vendored Normal file
View File

@ -0,0 +1,22 @@
# Copyright 2017 The Grafeas Authors. All rights reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
grafeas:
server:
# Endpoint address, e.g. localhost:10000
address: localhost:10000
# PKI configuration (optional)
cafile:
keyfile:
certfile:

View File

@ -0,0 +1,159 @@
# Running Grafeas
## Start Grafeas
To start the server go to `samples/server/go-server/api/server/main` and execute
go run main.go
This will start the Grafeas gRPC and REST API:s on `localhost:10000`. To start grafeas with a custom configuration use the `-config` flag (e.g. `-config config.yaml`). The root directory includes a `config.yaml.sample` that can be used as a starting point when creating your own config file.
### Access REST API with curl
Grafeas provides both a REST API and a gRPC API. Here is an example of using the REST API to list projects in Grafeas.
`curl http://localhost:10000/v1alpha1/projects`
### Access gRPC API with a go client
Below is a small example of a go client that connects to grafeas and outputs any notes in `myproject`
```
package main
import (
"context"
"log"
pb "github.com/grafeas/grafeas/v1alpha1/proto"
"google.golang.org/grpc"
)
func main() {
conn, err := grpc.Dial("localhost:10000", grpc.WithInsecure())
defer conn.Close()
client := pb.NewGrafeasClient(conn)
// List notes
resp, err := client.ListNotes(context.Background(),
&pb.ListNotesRequest{
Parent: "projects/myproject",
})
if err != nil {
log.Fatal(err)
}
if len(resp.Notes) != 0 {
log.Println(resp.Notes)
} else {
log.Println("Project does not contain any notes")
}
}
```
## Use Grafeas with self-signed certificate
### Generate CA, keys and certs
_NOTE: The steps described in this section is meant for development environments._
```
# Create CA
openssl genrsa -out ca.key 2048
# make sure to set Common Name to your domain, e.g. localhost (without port)
openssl req -new -x509 -days 365 -key ca.key -out ca.crt
# Create the Client Key and CSR
openssl genrsa -out client.key 2048
openssl req -new -key client.key -out client.csr
# Create self-signed client cert
openssl x509 -req -days 365 -in client.csr -CA ca.crt -CAkey ca.key -set_serial 01 -out client.crt
# Convert Client Key to PKCS
openssl pkcs12 -export -clcerts -in client.crt -inkey client.key -out client.p12
# Convert Client Key to (combined) PEM
openssl pkcs12 -in client.p12 -out client.pem -clcerts
```
This is basically following https://gist.github.com/mtigas/952344 with some tweaks
### Update config
Add the following to your config file
cafile: ca.crt
keyfile: ca.key
certfile: ca.crt
### Access REST API with curl
When using curl with a self signed certificate you need to add `-k/--insecure` and specify the client certificate.
`curl -k --cert path/to/client.pem https://localhost:10000/v1alpha1/projects`
### Access gRPC with a go client
When using a go client to access Grafeas with a self signed certificate you need to specify the client certificate, client key and the CA certificate.
```
package main
import (
"crypto/tls"
"crypto/x509"
"io/ioutil"
"log"
pb "github.com/grafeas/grafeas/v1alpha1/proto"
"google.golang.org/grpc"
"google.golang.org/grpc/credentials"
)
var (
certFile = "/path/to/client.crt"
keyFile = "/path/to/client.key"
caFile = "/path/to/ca.crt"
)
func main() {
// Load client cert
cert, err := tls.LoadX509KeyPair(certFile, keyFile)
if err != nil {
log.Fatal(err)
}
// Load CA cert
caCert, err := ioutil.ReadFile(caFile)
if err != nil {
log.Fatal(err)
}
caCertPool := x509.NewCertPool()
caCertPool.AppendCertsFromPEM(caCert)
// Setup HTTPS client
tlsConfig := &tls.Config{
Certificates: []tls.Certificate{cert},
RootCAs: caCertPool,
}
tlsConfig.BuildNameToCertificate()
creds := credentials.NewTLS(tlsConfig)
conn, err := grpc.Dial("localhost:10000", grpc.WithTransportCredentials(creds))
client := pb.NewGrafeasClient(conn)
// List notes
resp, err := client.ListNotes(context.Background(),
&pb.ListNotesRequest{
Parent: "projects/myproject",
})
if err != nil {
log.Fatal(err)
}
if len(resp.Notes) != 0 {
log.Println(resp.Notes)
} else {
log.Println("Project does not contain any notes")
}
}
```

View File

@ -0,0 +1,21 @@
# Grafeas API Reference Implementation
This is a reference implementation of the [Grafeas API Spec](https://github.com/Grafeas/Grafeas/blob/master/README)
## Overview
This reference implementation comes with the following caveats:
* Storage: map backed in memory server storage
* No ACLs are used in this implementation
* No authorization is in place #28
* Filtering in list methods is not currently supported #29
* Operation names are not currently validated when tied to notes/occurrences #31
### Running the server
To run the server, follow these simple steps:
```
go run main/main.go
```

View File

@ -0,0 +1 @@
application:

View File

@ -0,0 +1,62 @@
// Copyright 2017 The Grafeas Authors. All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package config
import (
"io/ioutil"
"github.com/grafeas/grafeas/samples/server/go-server/api/server/server"
"gopkg.in/yaml.v2"
)
// File is the grafeas config file.
type file struct {
Grafeas *config `yaml:"grafeas"`
}
// Config is the global configuration for an instance of Grafeas.
type config struct {
Server *server.Config `yaml:"server"`
}
// DefaultConfig is a configuration that can be used as a fallback value.
func defaultConfig() *config {
return &config{
&server.Config{
Address: "localhost:10000",
CertFile: "",
KeyFile: "",
CAFile: "",
},
}
}
// Creates a config from a YAML-file. If fileName is an empty
// string a default config will be returned.
func LoadConfig(fileName string) (*config, error) {
if fileName == "" {
return defaultConfig(), nil
}
data, err := ioutil.ReadFile(fileName)
if err != nil {
return nil, err
}
var configFile file
err = yaml.Unmarshal(data, &configFile)
if err != nil {
return nil, err
}
return configFile.Grafeas, nil
}

View File

@ -0,0 +1,36 @@
// Copyright 2017 The Grafeas Authors. All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package main
import (
"flag"
"log"
"github.com/grafeas/grafeas/samples/server/go-server/api/server/config"
"github.com/grafeas/grafeas/samples/server/go-server/api/server/server"
)
var (
configFile = flag.String("config", "", "Path to a config file")
)
func main() {
flag.Parse()
config, err := config.LoadConfig(*configFile)
if err != nil {
log.Fatalf("Failed to load config file")
}
server.Run(config.Server)
}

View File

@ -0,0 +1,218 @@
// Copyright 2017 The Grafeas Authors. All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// package name provides methods for manipulating resource names.
package name
import (
"fmt"
"strings"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/status"
)
// ResourceKind is the type that will be used for all public resource kinds.
type ResourceKind string
const (
// Position of projectID in name string
projectKeywordIndex = 1
// Position of the resourceID in the string
resourceKeywordIndex = 3
projectsKeyword = "projects"
occurrencesKeyword = "occurrences"
notesKeyword = "notes"
operationsKeyword = "operations"
// Note is the ResourceKind associated with notes.
Note = ResourceKind(notesKeyword)
// Occurrence is the ResourceKind associated with occurrences.
Occurrence = ResourceKind(occurrencesKeyword)
// Operation is the ResourceKind associated with operations.
Operation = ResourceKind(operationsKeyword)
// Unknown is the ResourceKind when the kind cannot be determined.
Unknown = ResourceKind("")
// NoCharLimit is used to signal that no resource id length validation is needed
NoCharLimit = -1
)
var (
projectNameFormat = FormatProject("{project_id}")
occurrenceNameFormat = FormatOccurrence("{project_id}", "{occurrence_id}")
operationNameFormat = FormatOperation("{provider_project_id}", "{operation_id}")
noteNameFormat = FormatNote("{provider_project_id}", "{note_id}")
)
func invalidArg(pattern, got string) error {
return status.Error(codes.InvalidArgument, fmt.Sprintf("expected name to be of form %q, input was %v", pattern, got))
}
// FormatProject synthesizes a stringly typed name of the form:
// projects/{project_id}
// See also: ParseProject
func FormatProject(projectID string) string {
return fmt.Sprintf("%v/%v", projectsKeyword, projectID)
}
func OccurrenceName(pID, oID string) string {
return fmt.Sprintf("projects/%v/occurrences/%v", pID, oID)
}
func OperationName(pID, oID string) string {
return fmt.Sprintf("projects/%v/operations/%v", pID, oID)
}
func NoteName(pID, nID string) string {
return fmt.Sprintf("projects/%v/notes/%v", pID, nID)
}
// FormatNoteProjectKW synthesizes a stringly typed name of the form:
// projects/{project_id}/notes/{note_id}
// See also: ParseNote
func FormatNote(projectID, noteID string) string {
return strings.Join([]string{projectsKeyword, projectID, string(Note), noteID}, "/")
}
// FormatOccurrence synthesizes a stringly typed name of the form:
// projects/{project_id}/occurrences/{occurrence_id}
// See also: ParseOccurrence
func FormatOccurrence(projectID, occurrenceID string) string {
return strings.Join([]string{projectsKeyword, projectID, string(Occurrence), occurrenceID}, "/")
}
// FormatOperation synthesizes a stringly typed name of the form:
// providers/{provider_id}/project/{project_id}/operations/{operation_id}
// See also: ParseOperation
func FormatOperation(projectID, operationID string) string {
return strings.Join([]string{projectsKeyword, projectID, operationsKeyword, operationID}, "/")
}
// ParseResourceKindAndResource takes a stringly typed name of the form:
// projects/{project_id}/occurrences/{occurrence_id}
// projects/{project_name}/notes/{note_id}
// or:
// validates form and returns either an error or the ResourceKind
// (either occurrence or note) and project/resource-ids
func ParseResourceKindAndResource(name string) (ResourceKind, string, string, error) {
err := invalidArg(fmt.Sprintf("%q or %q", occurrenceNameFormat, noteNameFormat), name)
params := strings.Split(name, "/")
if len(params) != 4 {
return Unknown, "", "", err
}
switch params[projectKeywordIndex-1] {
case projectsKeyword:
switch params[resourceKeywordIndex-1] {
case string(Occurrence):
return Occurrence, params[projectKeywordIndex], params[resourceKeywordIndex], nil
case string(Note):
return Note, params[projectKeywordIndex], params[resourceKeywordIndex], nil
case string(Operation):
return Operation, params[projectKeywordIndex], params[resourceKeywordIndex], nil
}
return Unknown, "", "", invalidArg(fmt.Sprintf("%q or %q", occurrenceNameFormat, noteNameFormat), name)
}
return Unknown, "", "", err
}
// ParseResourceKindAndProjectFromPath retrieves a projectID and resource kind from a Grafeas URL path
// This method should be used with CreateRequests.
func ParseResourceKindAndProject(parent string) (ResourceKind, string, error) {
err := invalidArg(fmt.Sprintf("%q or %q", occurrenceNameFormat, noteNameFormat), parent)
params := strings.Split(parent, "/")
if len(params) != 3 {
return Unknown, "", err
}
switch params[projectKeywordIndex-1] {
case projectsKeyword:
switch params[resourceKeywordIndex-1] {
case string(Occurrence):
return Occurrence, params[projectKeywordIndex], nil
case string(Note):
return Note, params[projectKeywordIndex], nil
case string(Operation):
return Operation, params[projectKeywordIndex], nil
}
return Unknown, "", invalidArg(fmt.Sprintf("%q, %q, or %q", occurrenceNameFormat,
noteNameFormat, operationNameFormat), parent)
}
return Unknown, "", err
}
// ParseOccurrence takes a stringly typed name of the form:
// projects/{project_id}/occurrences/{occurrence_id}
// validates its form and returns either an error or the project-/occurrence-ids.
func ParseOccurrence(name string) (string, string, error) {
return parseProjectAndEntityID(name, projectsKeyword, occurrencesKeyword, NoCharLimit)
}
// ParseNote takes a stringly typed name of the forms:
// providers/{provider_name}/notes/{note_id}
// providers/{provider_name}/notes/{note_id}
// validates its form and returns either an error or the provider-/note-ids.
func ParseNote(name string) (string, string, error) {
return parseProjectAndEntityID(name, projectsKeyword, notesKeyword, 100)
}
// ParseOperation takes a stringly typed name of the form:
// projects/{project_id}/operations/{operation_id}
// validates its form and returns either an error or the project-/operation-ids
func ParseOperation(name string) (string, string, error) {
return parseProjectAndEntityID(name, projectsKeyword, operationsKeyword, 100)
}
// parseProjectAndEntityID takes resource and project keywords, a max resource id length and a stringly typed name of the form:
// projects/{project_id}/<resourceKeyword>/{entity_id}
// validates its form and returns either an error or the project and resource ids. Only validates maxResourceIDLength if it is greater than 0
func parseProjectAndEntityID(name, projectKeyword, resourceKeyword string, maxResourceIDLength int) (string, string, error) {
format := fmt.Sprintf("%s/{project_id}/%s/{entity_id}", projectKeyword, resourceKeyword)
params := strings.Split(name, "/")
if len(params) != 4 {
return "", "", invalidArg(format, name)
}
if params[projectKeywordIndex-1] != projectKeyword {
return "", "", invalidArg(format, name)
}
if params[resourceKeywordIndex-1] != resourceKeyword {
return "", "", invalidArg(format, name)
}
if params[projectKeywordIndex] == "" || params[resourceKeywordIndex] == "" {
return "", "", invalidArg(format, name)
}
if maxResourceIDLength > 0 && len(params[resourceKeywordIndex]) > maxResourceIDLength {
return "", "", status.Error(codes.InvalidArgument, fmt.Sprintf("resource id must be <= %v characters. Input was %v", maxResourceIDLength, name))
}
return params[projectKeywordIndex], params[resourceKeywordIndex], nil
}
// ParseProject takes a stringly typed name of the form:
// projects/{project_id}
// validates its form and returns either an error or the project-id.
func ParseProject(name string) (string, error) {
params := strings.Split(name, "/")
if len(params) != 2 {
return "", invalidArg(projectNameFormat, name)
}
if params[projectKeywordIndex-1] != projectsKeyword {
return "", invalidArg(projectNameFormat, name)
}
if params[projectKeywordIndex] == "" {
return "", invalidArg(projectNameFormat, name)
}
return params[projectKeywordIndex], nil
}

View File

@ -0,0 +1,255 @@
// Copyright 2017 The Grafeas Authors. All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package name
import (
"strings"
"testing"
)
func TestNameRoundtrips(t *testing.T) {
tests := []struct {
part1 string
part2 string
}{
{"a", "b"},
{"foo", "foo"},
{"blah-foo", "baz-inga"},
}
// Test two-part names
for _, test := range tests {
on := FormatOccurrence(test.part1, test.part2)
if p1, p2, err := ParseOccurrence(on); err != nil {
t.Errorf("ParseOccurrence %v; want (%v, %v), got error %v",
on, test.part1, test.part2, err)
} else if p1 != test.part1 || p2 != test.part2 {
t.Errorf("ParseOccurrence %v; want (%v, %v), got (%v, %v)",
on, test.part1, test.part2, p1, p2)
}
if rt, p1, p2, err := ParseResourceKindAndResource(on); err != nil {
t.Errorf("ParseResourceKindAndResource %v; want (%v, %v, %v), got error %v",
on, Occurrence, test.part1, test.part2, err)
} else if rt != Occurrence || p1 != test.part1 || p2 != test.part2 {
t.Errorf("ParseResourceKindAndResource %v; want (%v, %v, %v), got (%v, %v, %v)",
on, Occurrence, test.part1, test.part2, rt, p1, p2)
}
if rt, p1, p2, err := ParseResourceKindAndResource(on); err != nil {
t.Errorf("ParseResourceKindAndResource %v; want (%v, %v, %v), got error %v",
on, Occurrence, test.part1, test.part2, err)
} else if rt != Occurrence || p1 != test.part1 || p2 != test.part2 {
t.Errorf("ParseResourceKindAndResource %v; want (%v, %v, %v), got (%v, %v, %v)",
on, Occurrence, test.part1, test.part2, rt, p1, p2)
}
nn := FormatNote(test.part1, test.part2)
if p1, p2, err := ParseNote(nn); err != nil {
t.Errorf("ParseNote %v; want (%v, %v), got error %v",
nn, test.part1, test.part2, err)
} else if p1 != test.part1 || p2 != test.part2 {
t.Errorf("ParseNote %v; want (%v, %v), got (%v, %v)",
nn, test.part1, test.part2, p1, p2)
}
if rt, p1, p2, err := ParseResourceKindAndResource(nn); err != nil {
t.Errorf("ParseResourceKindAndResource %v; want (%v, %v, %v), got error %v",
on, Note, test.part1, test.part2, err)
} else if rt != Note || p1 != test.part1 || p2 != test.part2 {
t.Errorf("ParseResourceKindAndResource %v; want (%v, %v, %v), got (%v, %v, %v)",
on, Note, test.part1, test.part2, rt, p1, p2)
}
opn := FormatOperation(test.part1, test.part2)
if p1, p2, err := ParseOperation(opn); err != nil {
t.Errorf("ParseOperation %v; got error %v, want (%v, %v)",
opn, err, test.part1, test.part2)
} else if p1 != test.part1 || p2 != test.part2 {
t.Errorf("ParseOperation %v; got (%v, %v), want (%v, %v)",
opn, p1, p2, test.part1, test.part2)
}
}
// Test one-part names
for _, test := range tests {
fn := FormatProject(test.part1)
if p1, err := ParseProject(fn); err != nil {
t.Errorf("ParseProject %v; want %v, got error %v",
fn, test.part1, err)
} else if p1 != test.part1 {
t.Errorf("ParseProject %v; want %v, got %v",
fn, test.part1, p1)
}
}
}
func TestParseNoteValidation(t *testing.T) {
badNoteNames := []string{
// Bad keyword
"providers/foo/findings/bar",
// Too few parts
"providers/foo/notes",
// Too many parts
"providers/foo/notes/bar/baz",
// Empty part
"providers//notes/bar",
"providers/foo/notes/",
// Too long
"providers/foo/occurrences/" + strings.Repeat("a", 101),
}
for _, test := range badNoteNames {
if p1, p2, err := ParseNote(test); err == nil {
t.Errorf("ParseNote %v; wanted error, got (%v, %v)",
test, p1, p2)
}
}
}
func TestParseOccurrenceValidation(t *testing.T) {
badOccurrenceNames := []string{
// Bad keyword
"providers/foo/occurrences/bar",
// Bad keyword
"projects/foo/results/bar",
// Too few parts
"projects/foo/occurrences",
// Too many parts
"projects/foo/occurrences/bar/baz",
// Empty part
"projects//occurrences/bar",
"projects/foo/occurrences/",
}
for _, test := range badOccurrenceNames {
if p1, p2, err := ParseOccurrence(test); err == nil {
t.Errorf("ParseOccurrence %v; wanted error, got (%v, %v)",
test, p1, p2)
}
}
}
func TestParseResourceKindAndResource(t *testing.T) {
badResourceNames := []string{
"providers/foo/findings/bar",
"providers/foo/occurrences/bar",
"foo/foo/bar/bar",
"projects/foo/results/bar",
"projects/foo/results",
"projects/foo/notes",
"projects/foo",
"providers/foo/results",
"providers/foo/notes",
"providers/foo",
"projects/foo/findings",
"projects/foo",
"projects/foo/occurrences",
}
for _, test := range badResourceNames {
if t1, p, r, err := ParseResourceKindAndResource(test); err == nil {
t.Errorf("ParseResourceTypeAndResource %v; wanted error, got (%v, %v, %v)",
test, t1, p, r)
}
}
}
func TestParseOperations(t *testing.T) {
badResourceNames := []string{
"providers/foo/operations/bar",
"providers/foo/operations/bar",
"foo/foo/bar/bar",
"projects/foo/providers/bar",
"providers/foo/projects/bar",
"projects/foo/providers/bar/operations/baz",
"operations/foo",
"providers/-/projects/-/operations/abc",
"providers//projects//operations/",
"providers/foo/projects/bar/operations/" + strings.Repeat("a", 101),
}
for _, test := range badResourceNames {
if t1, r, err := ParseOperation(test); err == nil {
t.Errorf("ParseOperation %v; got (%v, %v), wanted error",
test, t1, r)
}
}
}
func TestOccurrenceErrorMessage(t *testing.T) {
want := "projects/{project_id}/occurrences/{entity_id}"
if _, _, err := ParseOccurrence("providers/foo/notes/bar"); !strings.Contains(err.Error(), "projects/{project_id}/occurrences/{entity_id}") {
t.Fatalf("bad error msg, got %q want it to contain %q", err, want)
}
}
func TestParseResourceKindAndProjectPath(t *testing.T) {
badResourcePaths := []string{
"providers/foo/operations/bar",
"providers/foo/operations/bar",
"foo/foo/bar/bar",
"projects/foo/providers/bar",
"providers/foo/projects/bar",
"projects/foo/providers/bar/operations/baz",
"projects/foo/operations/bar",
"projects/foo/occurrences/bar",
"projects/foo/notes/bar",
"operations/foo",
"providers/-/projects/-/operations/abc",
"providers//projects//operations/",
"providers/foo/projects/bar/operations/" + strings.Repeat("a", 101),
}
for _, test := range badResourcePaths {
if t1, r, err := ParseResourceKindAndProject(test); err == nil {
t.Errorf("ParseResourceKindAndProject %v; got (%v, %v), wanted error",
test, t1, r)
}
}
goodResourcePaths := []string{
"projects/foo/occurrences",
"projects/foo/operations",
"projects/foo/notes",
}
for _, test := range goodResourcePaths {
if t1, r, err := ParseResourceKindAndProject(test); err != nil {
t.Errorf("ParseResourceKindAndProject %v; got (%v, %v, %v), wanted success",
test, t1, r, err)
} else if r != "foo" {
t.Errorf("ParseResourceKindAndProject %v; got %v, wanted foo", test, t1)
}
}
}
func TestParseProjectValidation(t *testing.T) {
badProjectNames := []string{
// Bad keyword
"providers/foo/",
// Trailing slash
"projects/foo/",
// Last part non-empty
"projects/foo/baz",
// Too many parts
"projects/foo/baz/asdf",
// Empty part
"projects//",
}
for _, test := range badProjectNames {
if p1, err := ParseProject(test); err == nil {
t.Errorf("ParseProject %v; wanted error, got %v",
test, p1)
}
}
}

View File

@ -0,0 +1,207 @@
// Copyright 2017 The Grafeas Authors. All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package server
import (
"context"
"crypto/tls"
"crypto/x509"
"io/ioutil"
"log"
"net"
"net/http"
"strings"
"github.com/cockroachdb/cmux"
"github.com/grafeas/grafeas/samples/server/go-server/api/server/storage"
"github.com/grafeas/grafeas/samples/server/go-server/api/server/v1alpha1"
pb "github.com/grafeas/grafeas/v1alpha1/proto"
"github.com/grpc-ecosystem/grpc-gateway/runtime"
opspb "google.golang.org/genproto/googleapis/longrunning"
"google.golang.org/grpc"
"google.golang.org/grpc/credentials"
)
type Config struct {
Address string `yaml:"address"` // Endpoint address, e.g. localhost:10000
CertFile string `yaml:"certfile"` // A PEM eoncoded certificate file
KeyFile string `yaml:"keyfile"` // A PEM encoded private key file
CAFile string `yaml:"cafile"` // A PEM eoncoded CA's certificate file
}
// Run initializes grpc and grpc gateway api services on the same address
func Run(config *Config) {
l, err := net.Listen("tcp", config.Address)
if err != nil {
log.Fatalln("could not listen to address", config.Address)
}
log.Println("starting grpc server")
var (
apiHandler http.Handler
apiListener net.Listener
srv *http.Server
ctx = context.Background()
httpMux = http.NewServeMux()
tcpMux = cmux.New(l)
)
tlsConfig, err := tlsClientConfig(config.CertFile)
if err != nil {
log.Fatal("Failed to create tls config", err)
}
if tlsConfig != nil {
cert, err := tls.LoadX509KeyPair(config.CertFile, config.KeyFile)
if err != nil {
log.Fatalln("Failed to load certificate files", err)
}
tlsConfig.Certificates = []tls.Certificate{cert}
tlsConfig.NextProtos = []string{"h2"}
apiListener = tls.NewListener(tcpMux.Match(cmux.Any()), tlsConfig)
go func() { handleShutdown(tcpMux.Serve()) }()
grpcServer := newGrpcServer(tlsConfig)
gwmux := newGrpcGatewayServer(ctx, apiListener.Addr().String(), tlsConfig)
httpMux.Handle("/", gwmux)
apiHandler = grpcHandlerFunc(grpcServer, httpMux)
log.Println("grpc server is configured with client certificate authentication")
} else {
grpcL := tcpMux.Match(cmux.HTTP2HeaderField("content-type", "application/grpc"))
apiListener = tcpMux.Match(cmux.Any())
go func() { handleShutdown(tcpMux.Serve()) }()
grpcServer := newGrpcServer(nil)
go func() { handleShutdown(grpcServer.Serve(grpcL)) }()
gwmux := newGrpcGatewayServer(ctx, apiListener.Addr().String(), nil)
httpMux.Handle("/", gwmux)
apiHandler = httpMux
log.Println("grpc server is configured without client certificate authentication")
}
srv = &http.Server{
Handler: apiHandler,
TLSConfig: tlsConfig,
}
// blocking call
handleShutdown(srv.Serve(apiListener))
log.Println("Grpc API stopped")
}
// handleShutdown handles the server shut down error.
func handleShutdown(err error) {
if err != nil {
if opErr, ok := err.(*net.OpError); !ok || (ok && opErr.Op != "accept") {
log.Fatal(err)
}
}
}
func newGrpcServer(tlsConfig *tls.Config) *grpc.Server {
grpcOpts := []grpc.ServerOption{}
if tlsConfig != nil {
grpcOpts = append(grpcOpts, grpc.Creds(credentials.NewTLS(tlsConfig)))
}
grpcServer := grpc.NewServer(grpcOpts...)
g := v1alpha1.Grafeas{S: storage.NewMemStore()}
pb.RegisterGrafeasServer(grpcServer, &g)
pb.RegisterGrafeasProjectsServer(grpcServer, &g)
opspb.RegisterOperationsServer(grpcServer, &g)
return grpcServer
}
func newGrpcGatewayServer(ctx context.Context, listenerAddr string, tlsConfig *tls.Config) http.Handler {
var (
gwTLSConfig *tls.Config
gwOpts []grpc.DialOption
)
if tlsConfig != nil {
gwTLSConfig = tlsConfig.Clone()
gwTLSConfig.InsecureSkipVerify = true
gwOpts = append(gwOpts, grpc.WithTransportCredentials(credentials.NewTLS(gwTLSConfig)))
} else {
gwOpts = append(gwOpts, grpc.WithInsecure())
}
// changes json serializer to include empty fields with default values
jsonOpt := runtime.WithMarshalerOption(runtime.MIMEWildcard, &runtime.JSONPb{EmitDefaults: true})
gwmux := runtime.NewServeMux(jsonOpt)
conn, err := grpc.DialContext(ctx, listenerAddr, gwOpts...)
if err != nil {
log.Fatal("could not initialize grpc gateway connection")
}
err = pb.RegisterGrafeasHandler(ctx, gwmux, conn)
if err != nil {
log.Fatal("could not initialize ancestry grpc gateway")
}
err = pb.RegisterGrafeasProjectsHandler(ctx, gwmux, conn)
if err != nil {
log.Fatal("could not initialize notification grpc gateway")
}
return http.Handler(gwmux)
}
// grpcHandlerFunc returns an http.Handler that delegates to grpcServer on incoming gRPC
// connections or otherHandler otherwise. Copied from cockroachdb.
func grpcHandlerFunc(grpcServer *grpc.Server, otherHandler http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
if r.ProtoMajor == 2 && strings.Contains(r.Header.Get("Content-Type"), "application/grpc") {
grpcServer.ServeHTTP(w, r)
} else {
otherHandler.ServeHTTP(w, r)
}
})
}
// tlsClientConfig initializes a *tls.Config using the given CA. The resulting
// *tls.Config is meant to be used to configure an HTTP server to do client
// certificate authentication.
//
// If no CA is given, a nil *tls.Config is returned; no client certificate will
// be required and verified. In other words, authentication will be disabled.
func tlsClientConfig(caPath string) (*tls.Config, error) {
if caPath == "" {
return nil, nil
}
caCert, err := ioutil.ReadFile(caPath)
if err != nil {
return nil, err
}
caCertPool := x509.NewCertPool()
caCertPool.AppendCertsFromPEM(caCert)
tlsConfig := &tls.Config{
ClientCAs: caCertPool,
ClientAuth: tls.RequireAndVerifyClientCert,
}
return tlsConfig, nil
}

View File

@ -0,0 +1,307 @@
// Copyright 2017 The Grafeas Authors. All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package storage
import (
"fmt"
"strings"
"sync"
"github.com/grafeas/grafeas/samples/server/go-server/api/server/name"
"github.com/grafeas/grafeas/server-go"
pb "github.com/grafeas/grafeas/v1alpha1/proto"
opspb "google.golang.org/genproto/googleapis/longrunning"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/status"
)
// memStore is an in-memory storage solution for Grafeas
type memStore struct {
sync.RWMutex
occurrencesByID map[string]*pb.Occurrence
notesByID map[string]*pb.Note
opsByID map[string]*opspb.Operation
projects map[string]bool
}
// NewMemStore creates a memStore with all maps initialized.
func NewMemStore() server.Storager {
return &memStore{
occurrencesByID: map[string]*pb.Occurrence{},
notesByID: map[string]*pb.Note{},
opsByID: map[string]*opspb.Operation{},
projects: map[string]bool{},
}
}
// CreateProject adds the specified project to the mem store
func (m *memStore) CreateProject(pID string) error {
m.Lock()
defer m.Unlock()
if _, ok := m.projects[pID]; ok {
return status.Error(codes.AlreadyExists, fmt.Sprintf("Project with name %q already exists", pID))
}
m.projects[pID] = true
return nil
}
// DeleteProject deletes the project with the given pID from the mem store
func (m *memStore) DeleteProject(pID string) error {
m.Lock()
defer m.Unlock()
if _, ok := m.projects[pID]; !ok {
return status.Error(codes.NotFound, fmt.Sprintf("Project with name %q does not Exist", pID))
}
delete(m.projects, pID)
return nil
}
// GetProject returns the project with the given pID from the mem store
func (m *memStore) GetProject(pID string) (*pb.Project, error) {
m.RLock()
defer m.RUnlock()
if _, ok := m.projects[pID]; !ok {
return nil, status.Error(codes.NotFound, fmt.Sprintf("Project with name %q does not Exist", pID))
}
return &pb.Project{Name: name.FormatProject(pID)}, nil
}
// ListProjects returns the project id for all projects from the mem store
func (m *memStore) ListProjects(filters string) []*pb.Project {
m.RLock()
defer m.RUnlock()
projects := make([]*pb.Project, len(m.projects))
i := 0
for k := range m.projects {
projects[i] = &pb.Project{Name: name.FormatProject(k)}
i++
}
return projects
}
// CreateOccurrence adds the specified occurrence to the mem store
func (m *memStore) CreateOccurrence(o *pb.Occurrence) error {
m.Lock()
defer m.Unlock()
if _, ok := m.occurrencesByID[o.Name]; ok {
return status.Error(codes.AlreadyExists, fmt.Sprintf("Occurrence with name %q already exists", o.Name))
}
m.occurrencesByID[o.Name] = o
return nil
}
// DeleteOccurrence deletes the occurrence with the given pID and oID from the memStore
func (m *memStore) DeleteOccurrence(pID, oID string) error {
oName := name.OccurrenceName(pID, oID)
m.Lock()
defer m.Unlock()
if _, ok := m.occurrencesByID[oName]; !ok {
return status.Error(codes.NotFound, fmt.Sprintf("Occurrence with oName %q does not Exist", oName))
}
delete(m.occurrencesByID, oName)
return nil
}
// UpdateOccurrence updates the existing occurrence with the given projectID and occurrenceID
func (m *memStore) UpdateOccurrence(pID, oID string, o *pb.Occurrence) error {
oName := name.OccurrenceName(pID, oID)
m.Lock()
defer m.Unlock()
if _, ok := m.occurrencesByID[oName]; !ok {
return status.Error(codes.NotFound, fmt.Sprintf("Occurrence with oName %q does not Exist", oName))
}
m.occurrencesByID[oName] = o
return nil
}
// GetOccurrence returns the occurrence with pID and oID
func (m *memStore) GetOccurrence(pID, oID string) (*pb.Occurrence, error) {
oName := name.OccurrenceName(pID, oID)
m.RLock()
defer m.RUnlock()
o, ok := m.occurrencesByID[oName]
if !ok {
return nil, status.Error(codes.NotFound, fmt.Sprintf("Occurrence with name %q does not Exist", oName))
}
return o, nil
}
// ListOccurrences returns the occurrences for this project ID (pID)
func (m *memStore) ListOccurrences(pID, filters string) []*pb.Occurrence {
os := []*pb.Occurrence{}
m.RLock()
defer m.RUnlock()
for _, o := range m.occurrencesByID {
if strings.HasPrefix(o.Name, fmt.Sprintf("projects/%v", pID)) {
os = append(os, o)
}
}
return os
}
// CreateNote adds the specified note to the mem store
func (m *memStore) CreateNote(n *pb.Note) error {
m.Lock()
defer m.Unlock()
if _, ok := m.notesByID[n.Name]; ok {
return status.Error(codes.AlreadyExists, fmt.Sprintf("Note with name %q already exists", n.Name))
}
m.notesByID[n.Name] = n
return nil
}
// DeleteNote deletes the note with the given pID and nID from the memStore
func (m *memStore) DeleteNote(pID, nID string) error {
nName := name.NoteName(pID, nID)
m.Lock()
defer m.Unlock()
if _, ok := m.notesByID[nName]; !ok {
return status.Error(codes.NotFound, fmt.Sprintf("Note with name %q does not Exist", nName))
}
delete(m.notesByID, nName)
return nil
}
// UpdateNote updates the existing note with the given pID and nID
func (m *memStore) UpdateNote(pID, nID string, n *pb.Note) error {
nName := name.NoteName(pID, nID)
m.Lock()
defer m.Unlock()
if _, ok := m.notesByID[nName]; !ok {
return status.Error(codes.NotFound, fmt.Sprintf("Note with name %q does not Exist", nName))
}
m.notesByID[nName] = n
return nil
}
// GetNote returns the note with pID and nID
func (m *memStore) GetNote(pID, nID string) (*pb.Note, error) {
nName := name.NoteName(pID, nID)
m.RLock()
defer m.RUnlock()
n, ok := m.notesByID[nName]
if !ok {
return nil, status.Error(codes.NotFound, fmt.Sprintf("Note with name %q does not Exist", nName))
}
return n, nil
}
// GetNoteByOccurrence returns the note attached to occurrence with pID and oID
func (m *memStore) GetNoteByOccurrence(pID, oID string) (*pb.Note, error) {
oName := name.OccurrenceName(pID, oID)
m.RLock()
defer m.RUnlock()
o, ok := m.occurrencesByID[oName]
if !ok {
return nil, status.Error(codes.NotFound, fmt.Sprintf("Occurrence with name %q does not Exist", oName))
}
n, ok := m.notesByID[o.NoteName]
if !ok {
return nil, status.Error(codes.NotFound, fmt.Sprintf("Note with name %q does not Exist", o.NoteName))
}
return n, nil
}
// ListNotes returns the notes for for this project (pID)
func (m *memStore) ListNotes(pID, filters string) []*pb.Note {
ns := []*pb.Note{}
m.RLock()
defer m.RUnlock()
for _, n := range m.notesByID {
if strings.HasPrefix(n.Name, fmt.Sprintf("projects/%v", pID)) {
ns = append(ns, n)
}
}
return ns
}
// ListNoteOccurrences returns the occcurrences on the particular note (nID) for this project (pID)
func (m *memStore) ListNoteOccurrences(pID, nID, filters string) ([]*pb.Occurrence, error) {
// TODO: use filters
m.RLock()
defer m.RUnlock()
// Verify that note exists
if _, err := m.GetNote(pID, nID); err != nil {
return nil, err
}
nName := name.FormatNote(pID, nID)
os := []*pb.Occurrence{}
for _, o := range m.occurrencesByID {
if o.NoteName == nName {
os = append(os, o)
}
}
return os, nil
}
// GetOperation returns the operation with pID and oID
func (m *memStore) GetOperation(pID, opID string) (*opspb.Operation, error) {
oName := name.OperationName(pID, opID)
m.RLock()
defer m.RUnlock()
o, ok := m.opsByID[oName]
if !ok {
return nil, status.Error(codes.NotFound, fmt.Sprintf("Operation with name %q does not Exist", oName))
}
return o, nil
}
// CreateOperation adds the specified operation to the mem store
func (m *memStore) CreateOperation(o *opspb.Operation) error {
m.Lock()
defer m.Unlock()
if _, ok := m.opsByID[o.Name]; ok {
return status.Error(codes.AlreadyExists, fmt.Sprintf("Operation with name %q already exists", o.Name))
}
m.opsByID[o.Name] = o
return nil
}
// DeleteOperation deletes the operation with the given pID and oID from the memStore
func (m *memStore) DeleteOperation(pID, opID string) error {
opName := name.OperationName(pID, opID)
m.Lock()
defer m.Unlock()
if _, ok := m.opsByID[opName]; !ok {
return status.Error(codes.NotFound, fmt.Sprintf("Operation with name %q does not Exist", opName))
}
delete(m.occurrencesByID, opName)
return nil
}
// UpdateOperation updates the existing operation with the given pID and nID
func (m *memStore) UpdateOperation(pID, opID string, op *opspb.Operation) error {
opName := name.OperationName(pID, opID)
m.Lock()
defer m.Unlock()
if _, ok := m.opsByID[opName]; !ok {
return status.Error(codes.NotFound, fmt.Sprintf("Operation with name %q does not Exist", opName))
}
m.opsByID[opName] = op
return nil
}
// ListOperations returns the operations for this project (pID)
func (m *memStore) ListOperations(pID, filters string) []*opspb.Operation {
ops := []*opspb.Operation{}
m.RLock()
defer m.RUnlock()
for _, op := range m.opsByID {
if strings.HasPrefix(op.Name, fmt.Sprintf("projects/%v", pID)) {
ops = append(ops, op)
}
}
return ops
}

View File

@ -0,0 +1,568 @@
// Copyright 2017 The Grafeas Authors. All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package storage
import (
"fmt"
"sort"
"github.com/grafeas/grafeas/samples/server/go-server/api/server/name"
"github.com/grafeas/grafeas/samples/server/go-server/api/server/testing"
"reflect"
"strings"
"testing"
pb "github.com/grafeas/grafeas/v1alpha1/proto"
opspb "google.golang.org/genproto/googleapis/longrunning"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/status"
)
func TestCreateProject(t *testing.T) {
s := NewMemStore()
p := "myproject"
if err := s.CreateProject(p); err != nil {
t.Errorf("CreateProject got %v want success", err)
}
// Try to insert the same project twice, expect failure.
if err := s.CreateProject(p); err == nil {
t.Errorf("CreateProject got success, want Error")
} else if s, _ := status.FromError(err); s.Code() != codes.AlreadyExists {
t.Errorf("CreateProject got code %v want %v", s.Code(), codes.AlreadyExists)
}
}
func TestCreateNote(t *testing.T) {
s := NewMemStore()
nPID := "vulnerability-scanner-a"
n := testutil.Note(nPID)
if err := s.CreateNote(n); err != nil {
t.Errorf("CreateNote got %v want success", err)
}
// Try to insert the same note twice, expect failure.
if err := s.CreateNote(n); err == nil {
t.Errorf("CreateNote got success, want Error")
} else if s, _ := status.FromError(err); s.Code() != codes.AlreadyExists {
t.Errorf("CreateNote got code %v want %v", s.Code(), codes.AlreadyExists)
}
}
func TestCreateOccurrence(t *testing.T) {
s := NewMemStore()
nPID := "vulnerability-scanner-a"
n := testutil.Note(nPID)
if err := s.CreateNote(n); err != nil {
t.Fatalf("CreateNote got %v want success", err)
}
oPID := "occurrence-project"
o := testutil.Occurrence(oPID, n.Name)
if err := s.CreateOccurrence(o); err != nil {
t.Errorf("CreateOccurrence got %v want success", err)
}
// Try to insert the same occurrence twice, expect failure.
if err := s.CreateOccurrence(o); err == nil {
t.Errorf("CreateOccurrence got success, want Error")
} else if s, _ := status.FromError(err); s.Code() != codes.AlreadyExists {
t.Errorf("CreateOccurrence got code %v want %v", s.Code(), codes.AlreadyExists)
}
pID, oID, err := name.ParseOccurrence(o.Name)
if err != nil {
t.Fatalf("Error parsing projectID and occurrenceID %v", err)
}
if got, err := s.GetOccurrence(pID, oID); err != nil {
t.Fatalf("GetOccurrence got %v, want success", err)
} else if !reflect.DeepEqual(got, o) {
t.Errorf("GetOccurrence got %v, want %v", got, o)
}
}
func TestCreateOperation(t *testing.T) {
s := NewMemStore()
opPID := "vulnerability-scanner-a"
op := testutil.Operation(opPID)
if err := s.CreateOperation(op); err != nil {
t.Errorf("CreateOperation got %v want success", err)
}
// Try to insert the same note twice, expect failure.
if err := s.CreateOperation(op); err == nil {
t.Errorf("CreateOperation got success, want Error")
} else if s, _ := status.FromError(err); s.Code() != codes.AlreadyExists {
t.Errorf("CreateOperation got code %v want %v", s.Code(), codes.AlreadyExists)
}
}
func TestDeleteProject(t *testing.T) {
s := NewMemStore()
pID := "myproject"
// Delete before the note exists
if err := s.DeleteProject(pID); err == nil {
t.Error("Deleting nonexistant note got success, want error")
}
if err := s.CreateProject(pID); err != nil {
t.Fatalf("CreateProject got %v want success", err)
}
if err := s.DeleteProject(pID); err != nil {
t.Errorf("DeleteProject got %v, want success ", err)
}
}
func TestDeleteOccurrence(t *testing.T) {
s := NewMemStore()
nPID := "vulnerability-scanner-a"
n := testutil.Note(nPID)
if err := s.CreateNote(n); err != nil {
t.Fatalf("CreateNote got %v want success", err)
}
oPID := "occurrence-project"
o := testutil.Occurrence(oPID, n.Name)
// Delete before the occurrence exists
pID, oID, err := name.ParseOccurrence(o.Name)
if err != nil {
t.Fatalf("Error parsing occurrence %v", err)
}
if err := s.DeleteOccurrence(pID, oID); err == nil {
t.Error("Deleting nonexistant occurrence got success, want error")
}
if err := s.CreateOccurrence(o); err != nil {
t.Fatalf("CreateOccurrence got %v want success", err)
}
if err := s.DeleteOccurrence(pID, oID); err != nil {
t.Errorf("DeleteOccurrence got %v, want success ", err)
}
}
func TestUpdateOccurrence(t *testing.T) {
s := NewMemStore()
nPID := "vulnerability-scanner-a"
n := testutil.Note(nPID)
if err := s.CreateNote(n); err != nil {
t.Fatalf("CreateNote got %v want success", err)
}
oPID := "occurrence-project"
o := testutil.Occurrence(oPID, n.Name)
pID, oID, err := name.ParseOccurrence(o.Name)
if err != nil {
t.Fatalf("Error parsing projectID and occurrenceID %v", err)
}
if err := s.UpdateOccurrence(pID, oID, o); err == nil {
t.Fatal("UpdateOccurrence got success want error")
}
if err := s.CreateOccurrence(o); err != nil {
t.Fatalf("CreateOccurrence got %v want success", err)
}
if got, err := s.GetOccurrence(pID, oID); err != nil {
t.Fatalf("GetOccurrence got %v, want success", err)
} else if !reflect.DeepEqual(got, o) {
t.Errorf("GetOccurrence got %v, want %v", got, o)
}
o2 := o
o2.GetVulnerabilityDetails().CvssScore = 1.0
if err := s.UpdateOccurrence(pID, oID, o2); err != nil {
t.Fatalf("UpdateOccurrence got %v want success", err)
}
if got, err := s.GetOccurrence(pID, oID); err != nil {
t.Fatalf("GetOccurrence got %v, want success", err)
} else if !reflect.DeepEqual(got, o2) {
t.Errorf("GetOccurrence got %v, want %v", got, o2)
}
}
func TestDeleteNote(t *testing.T) {
s := NewMemStore()
nPID := "vulnerability-scanner-a"
n := testutil.Note(nPID)
// Delete before the note exists
pID, oID, err := name.ParseNote(n.Name)
if err != nil {
t.Fatalf("Error parsing note %v", err)
}
if err := s.DeleteNote(pID, oID); err == nil {
t.Error("Deleting nonexistant note got success, want error")
}
if err := s.CreateNote(n); err != nil {
t.Fatalf("CreateNote got %v want success", err)
}
if err := s.DeleteNote(pID, oID); err != nil {
t.Errorf("DeleteNote got %v, want success ", err)
}
}
func TestUpdateNote(t *testing.T) {
s := NewMemStore()
nPID := "vulnerability-scanner-a"
n := testutil.Note(nPID)
pID, nID, err := name.ParseNote(n.Name)
if err != nil {
t.Fatalf("Error parsing projectID and noteID %v", err)
}
if err := s.UpdateNote(pID, nID, n); err == nil {
t.Fatal("UpdateNote got success want error")
}
if err := s.CreateNote(n); err != nil {
t.Fatalf("CreateNote got %v want success", err)
}
if got, err := s.GetNote(pID, nID); err != nil {
t.Fatalf("GetNote got %v, want success", err)
} else if !reflect.DeepEqual(got, n) {
t.Errorf("GetNote got %v, want %v", got, n)
}
n2 := n
n2.GetVulnerabilityType().CvssScore = 1.0
if err := s.UpdateNote(pID, nID, n2); err != nil {
t.Fatalf("UpdateNote got %v want success", err)
}
if got, err := s.GetNote(pID, nID); err != nil {
t.Fatalf("GetNote got %v, want success", err)
} else if !reflect.DeepEqual(got, n2) {
t.Errorf("GetNote got %v, want %v", got, n2)
}
}
func TestGetProject(t *testing.T) {
s := NewMemStore()
pID := "myproject"
// Try to get project before it has been created, expect failure.
if _, err := s.GetProject(pID); err == nil {
t.Errorf("GetProject got success, want Error")
} else if s, _ := status.FromError(err); s.Code() != codes.NotFound {
t.Errorf("GetProject got code %v want %v", s.Code(), codes.NotFound)
}
s.CreateProject(pID)
if p, err := s.GetProject(pID); err != nil {
t.Fatalf("GetProject got %v want success", err)
} else if p.Name != name.FormatProject(pID) {
t.Fatalf("Got %s want %s", p.Name, pID)
}
}
func TestGetOccurrence(t *testing.T) {
s := NewMemStore()
nPID := "vulnerability-scanner-a"
n := testutil.Note(nPID)
if err := s.CreateNote(n); err != nil {
t.Fatalf("CreateNote got %v want success", err)
}
oPID := "occurrence-project"
o := testutil.Occurrence(oPID, n.Name)
pID, oID, err := name.ParseOccurrence(o.Name)
if err != nil {
t.Fatalf("Error parsing occurrence %v", err)
}
if _, err := s.GetOccurrence(pID, oID); err == nil {
t.Fatal("GetOccurrence got success, want error")
}
if err := s.CreateOccurrence(o); err != nil {
t.Errorf("CreateOccurrence got %v, want Success", err)
}
if got, err := s.GetOccurrence(pID, oID); err != nil {
t.Fatalf("GetOccurrence got %v, want success", err)
} else if !reflect.DeepEqual(got, o) {
t.Errorf("GetOccurrence got %v, want %v", got, o)
}
}
func TestGetNote(t *testing.T) {
s := NewMemStore()
nPID := "vulnerability-scanner-a"
n := testutil.Note(nPID)
pID, nID, err := name.ParseNote(n.Name)
if err != nil {
t.Fatalf("Error parsing note %v", err)
}
if _, err := s.GetNote(pID, nID); err == nil {
t.Fatal("GetNote got success, want error")
}
if err := s.CreateNote(n); err != nil {
t.Errorf("CreateNote got %v, want Success", err)
}
if got, err := s.GetNote(pID, nID); err != nil {
t.Fatalf("GetNote got %v, want success", err)
} else if !reflect.DeepEqual(got, n) {
t.Errorf("GetNote got %v, want %v", got, n)
}
}
func TestGetNoteByOccurrence(t *testing.T) {
s := NewMemStore()
nPID := "vulnerability-scanner-a"
n := testutil.Note(nPID)
if err := s.CreateNote(n); err != nil {
t.Fatalf("CreateNote got %v want success", err)
}
oPID := "occurrence-project"
o := testutil.Occurrence(oPID, n.Name)
pID, oID, err := name.ParseOccurrence(o.Name)
if err != nil {
t.Fatalf("Error parsing occurrence %v", err)
}
if _, err := s.GetNoteByOccurrence(pID, oID); err == nil {
t.Fatal("GetNoteByOccurrence got success, want error")
}
if err := s.CreateOccurrence(o); err != nil {
t.Errorf("CreateOccurrence got %v, want Success", err)
}
if got, err := s.GetNoteByOccurrence(pID, oID); err != nil {
t.Fatalf("GetNoteByOccurrence got %v, want success", err)
} else if !reflect.DeepEqual(got, n) {
t.Errorf("GetNoteByOccurrence got %v, want %v", got, n)
}
}
func TestGetOperation(t *testing.T) {
s := NewMemStore()
oPID := "vulnerability-scanner-a"
o := testutil.Operation(oPID)
pID, oID, err := name.ParseOperation(o.Name)
if err != nil {
t.Fatalf("Error parsing operation %v", err)
}
if _, err := s.GetOperation(pID, oID); err == nil {
t.Fatal("GetOperation got success, want error")
}
if err := s.CreateOperation(o); err != nil {
t.Errorf("CreateOperation got %v, want Success", err)
}
if got, err := s.GetOperation(pID, oID); err != nil {
t.Fatalf("GetOperation got %v, want success", err)
} else if !reflect.DeepEqual(got, o) {
t.Errorf("GetOperation got %v, want %v", got, o)
}
}
func TestDeleteOperation(t *testing.T) {
s := NewMemStore()
oPID := "vulnerability-scanner-a"
o := testutil.Operation(oPID)
// Delete before the operation exists
pID, oID, err := name.ParseOperation(o.Name)
if err != nil {
t.Fatalf("Error parsing note %v", err)
}
if err := s.DeleteOperation(pID, oID); err == nil {
t.Error("Deleting nonexistant operation got success, want error")
}
if err := s.CreateOperation(o); err != nil {
t.Fatalf("CreateOperation got %v want success", err)
}
if err := s.DeleteOperation(pID, oID); err != nil {
t.Errorf("DeleteOperation got %v, want success ", err)
}
}
func TestUpdateOperation(t *testing.T) {
s := NewMemStore()
oPID := "vulnerability-scanner-a"
o := testutil.Operation(oPID)
pID, oID, err := name.ParseOperation(o.Name)
if err != nil {
t.Fatalf("Error parsing projectID and operationID %v", err)
}
if err := s.UpdateOperation(pID, oID, o); err == nil {
t.Fatal("UpdateOperation got success want error")
}
if err := s.CreateOperation(o); err != nil {
t.Fatalf("CreateOperation got %v want success", err)
}
if got, err := s.GetOperation(pID, oID); err != nil {
t.Fatalf("GetOperation got %v, want success", err)
} else if !reflect.DeepEqual(got, o) {
t.Errorf("GetOperation got %v, want %v", got, o)
}
o2 := o
o2.Done = true
if err := s.UpdateOperation(pID, oID, o2); err != nil {
t.Fatalf("UpdateOperation got %v want success", err)
}
if got, err := s.GetOperation(pID, oID); err != nil {
t.Fatalf("GetOperation got %v, want success", err)
} else if !reflect.DeepEqual(got, o2) {
t.Errorf("GetOperation got %v, want %v", got, o2)
}
}
func TestListProjects(t *testing.T) {
s := NewMemStore()
wantProjectNames := []string{}
for i := 0; i < 20; i++ {
pID := fmt.Sprint("Project", i)
if err := s.CreateProject(pID); err != nil {
t.Fatalf("CreateProject got %v want success", err)
}
wantProjectNames = append(wantProjectNames, name.FormatProject(pID))
}
filter := "filters_are_yet_to_be_implemented"
gotProjects := s.ListProjects(filter)
if len(gotProjects) != 20 {
t.Errorf("ListProjects got %v operations, want 20", len(gotProjects))
}
gotProjectNames := make([]string, len(gotProjects))
for i, project := range gotProjects {
gotProjectNames[i] = project.Name
}
// Sort to handle that wantProjectNames are not guaranteed to be listed in insertion order
sort.Strings(wantProjectNames)
sort.Strings(gotProjectNames)
if !reflect.DeepEqual(gotProjectNames, wantProjectNames) {
t.Errorf("ListProjects got %v want %v", gotProjectNames, wantProjectNames)
}
}
func TestListOperations(t *testing.T) {
s := NewMemStore()
ops := []opspb.Operation{}
findProject := "findThese"
dontFind := "dontFind"
for i := 0; i < 20; i++ {
o := testutil.Operation("")
if i < 5 {
o.Name = name.FormatOperation(findProject, string(i))
} else {
o.Name = name.FormatOperation(dontFind, string(i))
}
if err := s.CreateOperation(o); err != nil {
t.Fatalf("CreateOperation got %v want success", err)
}
ops = append(ops, *o)
}
gotOs := s.ListOperations(findProject, "")
if len(gotOs) != 5 {
t.Errorf("ListOperations got %v operations, want 5", len(gotOs))
}
for _, o := range gotOs {
want := name.FormatProject(findProject)
if !strings.HasPrefix(o.Name, want) {
t.Errorf("ListOperations got %v want prefix %v", o.Name, want)
}
}
}
func TestListNotes(t *testing.T) {
s := NewMemStore()
ns := []*pb.Note{}
findProject := "findThese"
dontFind := "dontFind"
for i := 0; i < 20; i++ {
n := testutil.Note("")
if i < 5 {
n.Name = name.FormatNote(findProject, string(i))
} else {
n.Name = name.FormatNote(dontFind, string(i))
}
if err := s.CreateNote(n); err != nil {
t.Fatalf("CreateNote got %v want success", err)
}
ns = append(ns, n)
}
gotNs := s.ListNotes(findProject, "")
if len(gotNs) != 5 {
t.Errorf("ListNotes got %v operations, want 5", len(gotNs))
}
for _, n := range gotNs {
want := name.FormatProject(findProject)
if !strings.HasPrefix(n.Name, want) {
t.Errorf("ListNotes got %v want %v", n.Name, want)
}
}
}
func TestListOccurrences(t *testing.T) {
s := NewMemStore()
os := []*pb.Occurrence{}
findProject := "findThese"
dontFind := "dontFind"
nPID := "vulnerability-scanner-a"
n := testutil.Note(nPID)
if err := s.CreateNote(n); err != nil {
t.Fatalf("CreateNote got %v want success", err)
}
for i := 0; i < 20; i++ {
oPID := "_"
o := testutil.Occurrence(oPID, n.Name)
if i < 5 {
o.Name = name.FormatOccurrence(findProject, string(i))
} else {
o.Name = name.FormatOccurrence(dontFind, string(i))
}
if err := s.CreateOccurrence(o); err != nil {
t.Fatalf("CreateOccurrence got %v want success", err)
}
os = append(os, o)
}
gotOs := s.ListOccurrences(findProject, "")
if len(gotOs) != 5 {
t.Errorf("ListOccurrences got %v Occurrences, want 5", len(gotOs))
}
for _, o := range gotOs {
want := name.FormatProject(findProject)
if !strings.HasPrefix(o.Name, want) {
t.Errorf("ListOccurrences got %v want %v", o.Name, want)
}
}
}
func TestListNoteOccurrences(t *testing.T) {
s := NewMemStore()
os := []*pb.Occurrence{}
findProject := "findThese"
dontFind := "dontFind"
nPID := "vulnerability-scanner-a"
n := testutil.Note(nPID)
if err := s.CreateNote(n); err != nil {
t.Fatalf("CreateNote got %v want success", err)
}
for i := 0; i < 20; i++ {
oPID := "_"
o := testutil.Occurrence(oPID, n.Name)
if i < 5 {
o.Name = name.FormatOccurrence(findProject, string(i))
} else {
o.Name = name.FormatOccurrence(dontFind, string(i))
}
if err := s.CreateOccurrence(o); err != nil {
t.Fatalf("CreateOccurrence got %v want success", err)
}
os = append(os, o)
}
pID, nID, err := name.ParseNote(n.Name)
if err != nil {
t.Fatalf("Error parsing note name %v", err)
}
gotOs, err := s.ListNoteOccurrences(pID, nID, "")
if err != nil {
t.Fatalf("ListNoteOccurrences got %v want success", err)
}
if len(gotOs) != 20 {
t.Errorf("ListNoteOccurrences got %v Occurrences, want 20", len(gotOs))
}
for _, o := range gotOs {
if o.NoteName != n.Name {
t.Errorf("ListNoteOccurrences got %v want %v", o.Name, o.NoteName)
}
}
}

View File

@ -0,0 +1,182 @@
// Copyright 2017 The Grafeas Authors. All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package testutil
import (
"fmt"
"log"
"github.com/golang/protobuf/proto"
"github.com/golang/protobuf/ptypes"
"github.com/golang/protobuf/ptypes/any"
pb "github.com/grafeas/grafeas/v1alpha1/proto"
opspb "google.golang.org/genproto/googleapis/longrunning"
)
func Occurrence(pID, noteName string) *pb.Occurrence {
return &pb.Occurrence{
Name: fmt.Sprintf("projects/%s/occurrences/134", pID),
ResourceUrl: "gcr.io/foo/bar",
NoteName: noteName,
Kind: pb.Note_PACKAGE_VULNERABILITY,
Details: &pb.Occurrence_VulnerabilityDetails{
VulnerabilityDetails: &pb.VulnerabilityType_VulnerabilityDetails{
Severity: pb.VulnerabilityType_HIGH,
CvssScore: 7.5,
PackageIssue: []*pb.VulnerabilityType_PackageIssue{
&pb.VulnerabilityType_PackageIssue{
SeverityName: "HIGH",
AffectedLocation: &pb.VulnerabilityType_VulnerabilityLocation{
CpeUri: "cpe:/o:debian:debian_linux:8",
Package: "icu",
Version: &pb.VulnerabilityType_Version{
Name: "52.1",
Revision: "8+deb8u3",
},
},
FixedLocation: &pb.VulnerabilityType_VulnerabilityLocation{
CpeUri: "cpe:/o:debian:debian_linux:8",
Package: "icu",
Version: &pb.VulnerabilityType_Version{
Name: "52.1",
Revision: "8+deb8u4",
},
},
},
},
},
},
}
}
func Note(pID string) *pb.Note {
return &pb.Note{
Name: fmt.Sprintf("projects/%s/notes/CVE-1999-0710", pID),
ShortDescription: "CVE-2014-9911",
LongDescription: "NIST vectors: AV:N/AC:L/Au:N/C:P/I:P",
Kind: pb.Note_PACKAGE_VULNERABILITY,
NoteType: &pb.Note_VulnerabilityType{
&pb.VulnerabilityType{
CvssScore: 7.5,
Severity: pb.VulnerabilityType_HIGH,
Details: []*pb.VulnerabilityType_Detail{
&pb.VulnerabilityType_Detail{
CpeUri: "cpe:/o:debian:debian_linux:7",
Package: "icu",
Description: "Stack-based buffer overflow in the ures_getByKeyWithFallback function in " +
"common/uresbund.cpp in International Components for Unicode (ICU) before 54.1 for C/C++ allows " +
"remote attackers to cause a denial of service or possibly have unspecified other impact via a crafted uloc_getDisplayName call.",
MinAffectedVersion: &pb.VulnerabilityType_Version{
Kind: pb.VulnerabilityType_Version_MINIMUM,
},
SeverityName: "HIGH",
FixedLocation: &pb.VulnerabilityType_VulnerabilityLocation{
CpeUri: "cpe:/o:debian:debian_linux:7",
Package: "icu",
Version: &pb.VulnerabilityType_Version{
Name: "4.8.1.1",
Revision: "12+deb7u6",
},
},
},
&pb.VulnerabilityType_Detail{
CpeUri: "cpe:/o:debian:debian_linux:8",
Package: "icu",
Description: "Stack-based buffer overflow in the ures_getByKeyWithFallback function in " +
"common/uresbund.cpp in International Components for Unicode (ICU) before 54.1 for C/C++ allows " +
"remote attackers to cause a denial of service or possibly have unspecified other impact via a crafted uloc_getDisplayName call.",
MinAffectedVersion: &pb.VulnerabilityType_Version{
Kind: pb.VulnerabilityType_Version_MINIMUM,
},
SeverityName: "HIGH",
FixedLocation: &pb.VulnerabilityType_VulnerabilityLocation{
CpeUri: "cpe:/o:debian:debian_linux:8",
Package: "icu",
Version: &pb.VulnerabilityType_Version{
Name: "52.1",
Revision: "8+deb8u4",
},
},
},
&pb.VulnerabilityType_Detail{
CpeUri: "cpe:/o:debian:debian_linux:9",
Package: "icu",
Description: "Stack-based buffer overflow in the ures_getByKeyWithFallback function in " +
"common/uresbund.cpp in International Components for Unicode (ICU) before 54.1 for C/C++ allows " +
"remote attackers to cause a denial of service or possibly have unspecified other impact via a crafted uloc_getDisplayName call.",
MinAffectedVersion: &pb.VulnerabilityType_Version{
Kind: pb.VulnerabilityType_Version_MINIMUM,
},
SeverityName: "HIGH",
FixedLocation: &pb.VulnerabilityType_VulnerabilityLocation{
CpeUri: "cpe:/o:debian:debian_linux:9",
Package: "icu",
Version: &pb.VulnerabilityType_Version{
Name: "55.1",
Revision: "3",
},
},
},
&pb.VulnerabilityType_Detail{
CpeUri: "cpe:/o:canonical:ubuntu_linux:14.04",
Package: "andriod",
Description: "Stack-based buffer overflow in the ures_getByKeyWithFallback function in " +
"common/uresbund.cpp in International Components for Unicode (ICU) before 54.1 for C/C++ allows " +
"remote attackers to cause a denial of service or possibly have unspecified other impact via a crafted uloc_getDisplayName call.",
MinAffectedVersion: &pb.VulnerabilityType_Version{
Kind: pb.VulnerabilityType_Version_MINIMUM,
},
SeverityName: "MEDIUM",
FixedLocation: &pb.VulnerabilityType_VulnerabilityLocation{
CpeUri: "cpe:/o:canonical:ubuntu_linux:14.04",
Package: "andriod",
Version: &pb.VulnerabilityType_Version{
Kind: pb.VulnerabilityType_Version_MAXIMUM,
},
},
},
},
},
},
RelatedUrl: []*pb.Note_RelatedUrl{
&pb.Note_RelatedUrl{
Url: "https://security-tracker.debian.org/tracker/CVE-2014-9911",
Label: "More Info",
},
&pb.Note_RelatedUrl{
Url: "http://people.ubuntu.com/~ubuntu-security/cve/CVE-2014-9911",
Label: "More Info",
},
},
}
}
func Operation(pID string) *opspb.Operation {
md := &pb.OperationMetadata{CreateTime: ptypes.TimestampNow()}
bytes, err := proto.Marshal(md)
if err != nil {
log.Printf("Error parsing bytes: %v", err)
return nil
}
return &opspb.Operation{
Name: fmt.Sprintf("projects/%s/operations/foo", pID),
Metadata: &any.Any{Value: bytes},
Done: false,
}
}

View File

@ -0,0 +1,373 @@
// Copyright 2017 The Grafeas Authors. All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// package v1alpha1 is an implementation of the v1alpha1 version of Grafeas.
package v1alpha1
import (
"fmt"
"log"
"github.com/golang/protobuf/ptypes/empty"
"github.com/grafeas/grafeas/samples/server/go-server/api/server/name"
server "github.com/grafeas/grafeas/server-go"
pb "github.com/grafeas/grafeas/v1alpha1/proto"
"golang.org/x/net/context"
opspb "google.golang.org/genproto/googleapis/longrunning"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/status"
)
// Grafeas is an implementation of the Grafeas API, which should be called by handler methods for verification of logic
// and storage.
type Grafeas struct {
S server.Storager
}
// CreateProject validates that a project is valid and then creates a project in the backing datastore.
func (g *Grafeas) CreateProject(ctx context.Context, req *pb.CreateProjectRequest) (*empty.Empty, error) {
pID, err := name.ParseProject(req.Name)
if err != nil {
log.Printf("Error parsing project name: %v", req.Name)
return nil, status.Error(codes.InvalidArgument, "Invalid Project name")
}
return &empty.Empty{}, g.S.CreateProject(pID)
}
// CreateNote validates that a note is valid and then creates a note in the backing datastore.
func (g *Grafeas) CreateNote(ctx context.Context, req *pb.CreateNoteRequest) (*pb.Note, error) {
n := req.Note
if req == nil {
log.Print("Note must not be empty.")
return nil, status.Error(codes.InvalidArgument, "Note must not be empty")
}
if n.Name == "" {
log.Printf("Note name must not be empty: %v", n.Name)
return nil, status.Error(codes.InvalidArgument, "Note name must not be empty")
}
pID, _, err := name.ParseNote(n.Name)
if err != nil {
log.Printf("Invalid note name: %v", n.Name)
return nil, status.Error(codes.InvalidArgument, "Invalid note name")
}
if _, err = g.S.GetProject(pID); err != nil {
log.Printf("Unable to get project %v, err: %v", pID, err)
return nil, status.Error(codes.NotFound, fmt.Sprintf("Project %v not found", pID))
}
// TODO: Validate that operation exists if it is specified when get methods are implmented
return n, g.S.CreateNote(n)
}
// CreateOccurrence validates that a note is valid and then creates an occurrence in the backing datastore.
func (g *Grafeas) CreateOccurrence(ctx context.Context, req *pb.CreateOccurrenceRequest) (*pb.Occurrence, error) {
o := req.Occurrence
if req == nil {
log.Print("Occurrence must not be empty.")
return nil, status.Error(codes.InvalidArgument, "Occurrence must not be empty")
}
if o.Name == "" {
log.Printf("Invalid occurrence name: %v", o.Name)
return nil, status.Error(codes.InvalidArgument, "Invalid occurrence name")
}
if o.NoteName == "" {
log.Print("No note is associated with this occurrence")
}
pID, _, err := name.ParseOccurrence(o.Name)
if _, err = g.S.GetProject(pID); err != nil {
log.Printf("Unable to get project %v, err: %v", pID, err)
return nil, status.Error(codes.NotFound, fmt.Sprintf("Project %v not found", pID))
}
pID, nID, err := name.ParseNote(o.NoteName)
if err != nil {
log.Printf("Invalid note name: %v", o.Name)
return nil, status.Error(codes.InvalidArgument, "Invalid note name")
}
if n, err := g.S.GetNote(pID, nID); n == nil || err != nil {
log.Printf("Unable to getnote %v, err: %v", n, err)
return nil, status.Error(codes.NotFound, fmt.Sprintf("Note %v not found", o.NoteName))
}
// TODO: Validate that operation exists if it is specified
return o, g.S.CreateOccurrence(o)
}
// CreateOperation validates that a note is valid and then creates an operation note in the backing datastore.
func (g *Grafeas) CreateOperation(ctx context.Context, req *pb.CreateOperationRequest) (*opspb.Operation, error) {
o := req.Operation
if o.Name == "" {
log.Printf("Invalid operation name: %v", o.Name)
return nil, status.Error(codes.InvalidArgument, "Invalid operation name")
}
pID, _, err := name.ParseOperation(o.Name)
if _, err = g.S.GetProject(pID); err != nil {
log.Printf("Unable to get project %v, err: %v", pID, err)
return nil, status.Error(codes.NotFound, fmt.Sprintf("Project %v not found", pID))
}
return o, g.S.CreateOperation(o)
}
// DeleteProject deletes a project from the datastore.
func (g *Grafeas) DeleteProject(ctx context.Context, req *pb.DeleteProjectRequest) (*empty.Empty, error) {
pID, err := name.ParseProject(req.Name)
if err != nil {
log.Printf("Error parsing project name: %v", req.Name)
return nil, status.Error(codes.InvalidArgument, "Invalid Project name")
}
return &empty.Empty{}, g.S.DeleteProject(pID)
}
// DeleteOccurrence deletes an occurrence from the datastore.
func (g *Grafeas) DeleteOccurrence(ctx context.Context, req *pb.DeleteOccurrenceRequest) (*empty.Empty, error) {
pID, oID, err := name.ParseOccurrence(req.Name)
if err != nil {
log.Printf("Error parsing name: %v", req.Name)
return nil, status.Error(codes.InvalidArgument, "Invalid occurrence name")
}
return &empty.Empty{}, g.S.DeleteOccurrence(pID, oID)
}
// DeleteNote deletes a note from the datastore.
func (g *Grafeas) DeleteNote(ctx context.Context, req *pb.DeleteNoteRequest) (*empty.Empty, error) {
pID, nID, err := name.ParseNote(req.Name)
if err != nil {
log.Printf("Error parsing name: %v", req.Name)
return nil, status.Error(codes.InvalidArgument, "Invalid note name")
}
// TODO: Check for occurrences tied to this note, and return an error if there are any before deletion.
return &empty.Empty{}, g.S.DeleteNote(pID, nID)
}
// DeleteOperation deletes an operation from the datastore.
func (g *Grafeas) DeleteOperation(ctx context.Context, req *opspb.DeleteOperationRequest) (*empty.Empty, error) {
pID, oID, err := name.ParseOperation(req.Name)
if err != nil {
log.Printf("Error parsing name: %v", req.Name)
return nil, status.Error(codes.InvalidArgument, "Invalid Operation name")
}
// TODO: Check for occurrences and notes tied to this operation, and return an error if there are any before deletion.
return &empty.Empty{}, g.S.DeleteOperation(pID, oID)
}
// GetProject gets a project from the datastore.
func (g *Grafeas) GetProject(ctx context.Context, req *pb.GetProjectRequest) (*pb.Project, error) {
pID, err := name.ParseProject(req.Name)
if err != nil {
log.Printf("Error parsing project name: %v", req.Name)
return nil, status.Error(codes.InvalidArgument, "Invalid Project name")
}
return g.S.GetProject(pID)
}
// GetNote gets a note from the datastore.
func (g *Grafeas) GetNote(ctx context.Context, req *pb.GetNoteRequest) (*pb.Note, error) {
pID, nID, err := name.ParseNote(req.Name)
if err != nil {
log.Printf("Error parsing name: %v", req.Name)
return nil, status.Error(codes.InvalidArgument, "Invalid Note name")
}
return g.S.GetNote(pID, nID)
}
// GetOccurrence gets a occurrence from the datastore.
func (g *Grafeas) GetOccurrence(ctx context.Context, req *pb.GetOccurrenceRequest) (*pb.Occurrence, error) {
pID, oID, err := name.ParseOccurrence(req.Name)
if err != nil {
log.Printf("Could note parse name %v", req.Name)
return nil, status.Error(codes.InvalidArgument, "Could note parse name")
}
return g.S.GetOccurrence(pID, oID)
}
// GetOperation gets a occurrence from the datastore.
func (g *Grafeas) GetOperation(ctx context.Context, req *opspb.GetOperationRequest) (*opspb.Operation, error) {
pID, oID, err := name.ParseOperation(req.Name)
if err != nil {
log.Printf("Error parsing name: %v", req.Name)
return nil, status.Error(codes.InvalidArgument, "Invalid Operation name")
}
return g.S.GetOperation(pID, oID)
}
// GetOccurrenceNote gets a the note for the provided occurrence from the datastore.
func (g *Grafeas) GetOccurrenceNote(ctx context.Context, req *pb.GetOccurrenceNoteRequest) (*pb.Note, error) {
pID, oID, err := name.ParseOccurrence(req.Name)
if err != nil {
log.Printf("Error parsing name: %v", req.Name)
return nil, status.Error(codes.InvalidArgument, "Invalid occurrence name")
}
o, gErr := g.S.GetOccurrence(pID, oID)
if gErr != nil {
return nil, gErr
}
npID, nID, err := name.ParseNote(o.NoteName)
if err != nil {
log.Printf("Invalid note name: %v", o.Name)
return nil, status.Error(codes.InvalidArgument, fmt.Sprintf("Invalid note name: %v", o.NoteName))
}
return g.S.GetNote(npID, nID)
}
func (g *Grafeas) UpdateNote(ctx context.Context, req *pb.UpdateNoteRequest) (*pb.Note, error) {
pID, nID, err := name.ParseNote(req.Name)
if err != nil {
log.Printf("Error parsing name: %v", req.Name)
return nil, status.Error(codes.InvalidArgument, "Invalid Note name")
}
// get existing note
existing, gErr := g.S.GetNote(pID, nID)
if gErr != nil {
return nil, err
}
// verify that name didnt change
if req.Note.Name != existing.Name {
log.Printf("Cannot change note name: %v", req.Note.Name)
return nil, status.Error(codes.InvalidArgument, fmt.Sprintf("Cannot change note name: %v", req.Note.Name))
}
// update note
if gErr = g.S.UpdateNote(pID, nID, req.Note); err != nil {
log.Printf("Cannot update note : %v", gErr)
return nil, status.Error(codes.InvalidArgument, fmt.Sprintf("Cannot change note name: %v", req.Note.Name))
}
return g.S.GetNote(pID, nID)
}
func (g *Grafeas) UpdateOccurrence(ctx context.Context, req *pb.UpdateOccurrenceRequest) (*pb.Occurrence, error) {
pID, oID, err := name.ParseOccurrence(req.Name)
if err != nil {
log.Printf("Error parsing name: %v", req.Name)
return nil, status.Error(codes.InvalidArgument, "Invalid occurrence name")
}
// get existing Occurrence
existing, gErr := g.S.GetOccurrence(pID, oID)
if gErr != nil {
return nil, gErr
}
// verify that name didnt change
if req.Name != existing.Name {
log.Printf("Cannot change occurrence name: %v", req.Occurrence.Name)
return nil, status.Error(codes.InvalidArgument, fmt.Sprintf("Cannot change occurrence name: %v", req.Occurrence.Name))
}
// verify that if note name changed, it still exists
if req.Occurrence.NoteName != existing.NoteName {
npID, nID, err := name.ParseNote(req.Occurrence.NoteName)
if err != nil {
return nil, err
}
if newN, err := g.S.GetNote(npID, nID); newN == nil || err != nil {
return nil, err
}
}
// update Occurrence
if gErr = g.S.UpdateOccurrence(pID, oID, req.Occurrence); gErr != nil {
log.Printf("Cannot update occurrence : %v", req.Occurrence.Name)
return nil, status.Error(codes.InvalidArgument, fmt.Sprintf("Cannot update Occurrences: %v", err))
}
return g.S.GetOccurrence(pID, oID)
}
func (g *Grafeas) UpdateOperation(ctx context.Context, req *pb.UpdateOperationRequest) (*opspb.Operation, error) {
pID, oID, err := name.ParseOperation(req.Name)
if err != nil {
log.Printf("Error parsing name: %v", req.Name)
return nil, status.Error(codes.InvalidArgument, "Invalid Operation name")
}
// get existing operation
existing, gErr := g.S.GetOperation(pID, oID)
if gErr != nil {
return nil, gErr
}
// verify that operation isn't marked done
if req.Operation.Done != existing.Done && existing.Done {
log.Printf("Trying to update a done operation")
return nil, status.Error(codes.InvalidArgument, fmt.Sprintf("Cannot update operation in status done: %v", req.Name))
}
// verify that name didnt change
if req.Operation.Name != existing.Name {
log.Printf("Cannot change operation name: %v", req.Operation.Name)
return nil, status.Error(codes.InvalidArgument, fmt.Sprintf("Cannot change operation name: %v", req.Name))
}
// update operation
if gErr = g.S.UpdateOperation(pID, oID, req.Operation); gErr != nil {
log.Printf("Cannot update operation : %v", req.Operation.Name)
return nil, status.Error(codes.InvalidArgument, fmt.Sprintf("Cannot update Opreation: %v", req.Operation.Name))
}
return g.S.GetOperation(pID, oID)
}
// ListProjects returns the project id for all projects in the backing datastore.
func (g *Grafeas) ListProjects(ctx context.Context, req *pb.ListProjectsRequest) (*pb.ListProjectsResponse, error) {
// TODO: support filters
ns := g.S.ListProjects(req.Filter)
return &pb.ListProjectsResponse{Projects: ns}, nil
}
func (g *Grafeas) ListOperations(ctx context.Context, req *opspb.ListOperationsRequest) (*opspb.ListOperationsResponse, error) {
pID, err := name.ParseProject(req.Name)
if err != nil {
log.Printf("Error parsing name: %v", req.Name)
return nil, status.Error(codes.InvalidArgument, "Invalid Project name")
}
// TODO: support filters
ops := g.S.ListOperations(pID, req.Filter)
return &opspb.ListOperationsResponse{Operations: ops}, nil
}
func (g *Grafeas) ListNotes(ctx context.Context, req *pb.ListNotesRequest) (*pb.ListNotesResponse, error) {
pID, err := name.ParseProject(req.Parent)
if err != nil {
log.Printf("Error parsing name: %v", req.Parent)
return nil, status.Error(codes.InvalidArgument, "Invalid Project name")
}
// TODO: support filters
ns := g.S.ListNotes(pID, req.Filter)
return &pb.ListNotesResponse{Notes: ns}, nil
}
func (g *Grafeas) ListOccurrences(ctx context.Context, req *pb.ListOccurrencesRequest) (*pb.ListOccurrencesResponse, error) {
pID, err := name.ParseProject(req.Parent)
if err != nil {
log.Printf("Error parsing name: %v", req.Parent)
return nil, err
}
// TODO: support filters - prioritizing resource url
os := g.S.ListOccurrences(pID, req.Filter)
return &pb.ListOccurrencesResponse{Occurrences: os}, nil
}
func (g *Grafeas) ListNoteOccurrences(ctx context.Context, req *pb.ListNoteOccurrencesRequest) (*pb.ListNoteOccurrencesResponse, error) {
pID, nID, err := name.ParseNote(req.Name)
if err != nil {
log.Printf("Invalid note name: %v", req.Name)
return nil, status.Error(codes.InvalidArgument, "Invalid note name")
}
// TODO: support filters - prioritizing resource url
os, gErr := g.S.ListNoteOccurrences(pID, nID, req.Filter)
if gErr != nil {
return nil, gErr
}
return &pb.ListNoteOccurrencesResponse{Occurrences: os}, nil
}
func (g *Grafeas) CancelOperation(context.Context, *opspb.CancelOperationRequest) (*empty.Empty, error) {
return nil, status.Error(codes.Unimplemented, "Currently Unimplemented")
}

View File

@ -0,0 +1,659 @@
// Copyright 2017 The Grafeas Authors. All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package v1alpha1
import (
"fmt"
"reflect"
"testing"
"golang.org/x/net/context"
"github.com/grafeas/grafeas/samples/server/go-server/api/server/name"
"github.com/grafeas/grafeas/samples/server/go-server/api/server/storage"
"github.com/grafeas/grafeas/samples/server/go-server/api/server/testing"
pb "github.com/grafeas/grafeas/v1alpha1/proto"
opspb "google.golang.org/genproto/googleapis/longrunning"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/status"
)
func createProject(t *testing.T, pID string, ctx context.Context, g Grafeas) {
req := pb.CreateProjectRequest{Name: name.FormatProject(pID)}
if _, err := g.CreateProject(ctx, &req); err != nil {
t.Errorf("CreateProject(empty operation): got %v, want success", err)
}
}
func TestCreateProject(t *testing.T) {
ctx := context.Background()
pID := "myproject"
g := Grafeas{storage.NewMemStore()}
req := pb.CreateProjectRequest{Name: name.FormatProject(pID)}
_, err := g.CreateProject(ctx, &req)
if err != nil {
t.Errorf("CreateProject(empty operation): got %v, want success", err)
}
_, err = g.CreateProject(ctx, &req)
if s, _ := status.FromError(err); s.Code() != codes.AlreadyExists {
t.Errorf("CreateProject(empty operation): got %v, want AlreadyExists", err)
}
}
func TestCreateOperation(t *testing.T) {
ctx := context.Background()
g := Grafeas{storage.NewMemStore()}
op := &opspb.Operation{}
req := pb.CreateOperationRequest{Parent: "projects/opp", Operation: op}
if _, err := g.CreateOperation(ctx, &req); err == nil {
t.Error("CreateOperation(empty operation): got success, want error")
} else if s, _ := status.FromError(err); s.Code() != codes.InvalidArgument {
t.Errorf("CreateOperation(empty operation): got %v, want InvalidArgument", err)
}
pID := "vulnerability-scanner-a"
op = testutil.Operation(pID)
parent := name.FormatProject(pID)
req = pb.CreateOperationRequest{Parent: parent, Operation: op}
if _, err := g.CreateOperation(ctx, &req); err == nil {
t.Error("CreateOperation: got success, want error")
} else if s, _ := status.FromError(err); s.Code() != codes.NotFound {
t.Errorf("CreateOperation: got %v, want NotFound)", err)
}
createProject(t, pID, ctx, g)
if _, err := g.CreateOperation(ctx, &req); error(err) != nil {
t.Errorf("CreateOperation(%v) got %#v, want success", op, err)
}
}
func TestCreateOccurrence(t *testing.T) {
ctx := context.Background()
g := Grafeas{storage.NewMemStore()}
pID := "vulnerability-scanner-a"
n := testutil.Note(pID)
parent := name.FormatProject(pID)
createProject(t, pID, ctx, g)
req := &pb.CreateNoteRequest{Parent: parent, Note: n}
if _, err := g.CreateNote(ctx, req); err != nil {
t.Fatalf("CreateNote(%v) got %v, want success", req, err)
}
oReq := &pb.CreateOccurrenceRequest{Parent: parent, Occurrence: &pb.Occurrence{}}
if _, err := g.CreateOccurrence(ctx, oReq); err == nil {
t.Error("CreateOccurrence(empty occ): got success, want error")
} else if s, _ := status.FromError(err); s.Code() != codes.InvalidArgument {
t.Errorf("CreateOccurrence(empty occ): got %v, want InvalidArgument)", err)
}
pID = "occurrence-project"
o := testutil.Occurrence(pID, n.Name)
parent = name.FormatProject(pID)
oReq = &pb.CreateOccurrenceRequest{Parent: parent, Occurrence: o}
// Try to insert an occurrence without first creating its project, expect failure
if _, err := g.CreateOccurrence(ctx, oReq); err == nil {
t.Error("CreateOccurrence: got success, want error")
} else if s, _ := status.FromError(err); s.Code() != codes.NotFound {
t.Errorf("CreateOccurrence: got %v, want NotFound)", err)
}
createProject(t, pID, ctx, g)
if _, err := g.CreateOccurrence(ctx, oReq); err != nil {
t.Errorf("CreateOccurrence(%v) got %v, want success", oReq, err)
}
// Try to insert an occurrence for a note that does not exist.
o.Name = "projects/testproject/occurrences/nonote"
o.NoteName = "projects/scan-provider/notes/notthere"
oReq = &pb.CreateOccurrenceRequest{Parent: "projects/testproject", Occurrence: o}
if _, err := g.CreateOccurrence(ctx, oReq); err == nil {
t.Errorf("CreateOccurrence got success, want Error")
} else if s, _ := status.FromError(err); s.Code() != codes.NotFound {
t.Errorf("CreateOccurrence got code %v want %v", err, codes.NotFound)
}
}
func TestCreateNote(t *testing.T) {
ctx := context.Background()
g := Grafeas{storage.NewMemStore()}
n := &pb.Note{}
req := &pb.CreateNoteRequest{Parent: "projects/foo", Note: n}
// Try to insert an empty note, expect failure
if _, err := g.CreateNote(ctx, req); err == nil {
t.Error("CreateNote(empty note): got success, want error")
} else if s, _ := status.FromError(err); s.Code() != codes.InvalidArgument {
t.Errorf("CreateNote(empty note): got %v, want %v", err, codes.InvalidArgument)
}
// Try to insert an onccurrence without first creating its project, expect failure
pID := "vulnerability-scanner-a"
n = testutil.Note(pID)
parent := name.FormatProject(pID)
req = &pb.CreateNoteRequest{Parent: parent, Note: n}
if _, err := g.CreateNote(ctx, req); err == nil {
t.Error("CreateNote: got success, want error")
} else if s, _ := status.FromError(err); s.Code() != codes.NotFound {
t.Errorf("CreateNote: got %v, want NotFound)", err)
}
createProject(t, pID, ctx, g)
if _, err := g.CreateNote(ctx, req); err != nil {
t.Errorf("CreateNote(%v) got %v, want success", n, err)
}
}
func TestDeleteProject(t *testing.T) {
ctx := context.Background()
g := Grafeas{storage.NewMemStore()}
pID := "myproject"
req := pb.DeleteProjectRequest{Name: name.FormatProject(pID)}
if _, err := g.DeleteProject(ctx, &req); err == nil {
t.Error("DeleteProject: got success, want error")
}
createProject(t, pID, ctx, g)
if _, err := g.DeleteProject(ctx, &req); err != nil {
t.Errorf("CreateProject(empty operation): got %v, want success", err)
}
}
func TestDeleteNote(t *testing.T) {
ctx := context.Background()
g := Grafeas{storage.NewMemStore()}
pID := "vulnerability-scanner-a"
n := testutil.Note(pID)
createProject(t, pID, ctx, g)
req := &pb.DeleteNoteRequest{Name: n.Name}
if _, err := g.DeleteNote(ctx, req); err == nil {
t.Error("DeleteNote that doesn't exist got success, want err")
}
parent := name.FormatProject(pID)
cReq := &pb.CreateNoteRequest{Parent: parent, Note: n}
if _, err := g.CreateNote(ctx, cReq); err != nil {
t.Errorf("CreateNote(%v) got %v, want success", n, err)
}
if _, err := g.DeleteNote(ctx, req); err != nil {
t.Errorf("DeleteNote got %v, want success", err)
}
}
func TestDeleteOccurrence(t *testing.T) {
ctx := context.Background()
g := Grafeas{storage.NewMemStore()}
pID := "vulnerability-scanner-a"
n := testutil.Note(pID)
createProject(t, pID, ctx, g)
parent := name.FormatProject(pID)
cReq := &pb.CreateNoteRequest{Parent: parent, Note: n}
// CreateNote so we can create an occurrence
if _, err := g.CreateNote(ctx, cReq); err != nil {
t.Fatalf("CreateNote(%v) got %v, want success", n, err)
}
pID = "occurrence-project"
o := testutil.Occurrence(pID, n.Name)
createProject(t, pID, ctx, g)
parent = name.FormatProject(pID)
oReq := &pb.CreateOccurrenceRequest{Parent: parent, Occurrence: o}
if _, err := g.CreateOccurrence(ctx, oReq); err != nil {
t.Fatalf("CreateOccurrence(%v) got %v, want success", n, err)
}
dReq := &pb.DeleteOccurrenceRequest{Name: o.Name}
if _, err := g.DeleteOccurrence(ctx, dReq); err != nil {
t.Errorf("DeleteOccurrence got %v, want success", err)
}
}
func TestDeleteOperation(t *testing.T) {
ctx := context.Background()
g := Grafeas{storage.NewMemStore()}
pID := "vulnerability-scanner-a"
o := testutil.Operation(pID)
createProject(t, pID, ctx, g)
req := &opspb.DeleteOperationRequest{Name: o.Name}
if _, err := g.DeleteOperation(ctx, req); err == nil {
t.Error("DeleteOperation that doesn't exist got success, want err")
}
parent := name.FormatProject(pID)
cReq := &pb.CreateOperationRequest{Parent: parent, Operation: o}
if _, err := g.CreateOperation(ctx, cReq); err != nil {
t.Fatalf("CreateOperation(%v) got %v, want success", o, err)
}
if _, err := g.DeleteOperation(ctx, req); err != nil {
t.Errorf("DeleteOperation got %v, want success", err)
}
}
func TestGetProjects(t *testing.T) {
ctx := context.Background()
g := Grafeas{storage.NewMemStore()}
pID := "myproject"
req := pb.GetProjectRequest{Name: name.FormatProject(pID)}
if _, err := g.GetProject(ctx, &req); err == nil {
t.Error("GetProject that doesn't exist got success, want err")
}
createProject(t, pID, ctx, g)
if _, err := g.GetProject(ctx, &req); err != nil {
t.Errorf("GetProject: got %v, want success", err)
}
}
func TestGetNote(t *testing.T) {
ctx := context.Background()
g := Grafeas{storage.NewMemStore()}
pID := "vulnerability-scanner-a"
n := testutil.Note(pID)
createProject(t, pID, ctx, g)
req := &pb.GetNoteRequest{Name: n.Name}
if _, err := g.GetNote(ctx, req); err == nil {
t.Error("GetNote that doesn't exist got success, want err")
}
parent := name.FormatProject(pID)
cReq := &pb.CreateNoteRequest{Parent: parent, Note: n}
if _, err := g.CreateNote(ctx, cReq); err != nil {
t.Fatalf("CreateNote(%v) got %v, want success", n, err)
}
if got, err := g.GetNote(ctx, req); err != nil {
t.Fatalf("GetNote(%v) got %v, want success", n, err)
} else if n.Name != got.Name || !reflect.DeepEqual(n.GetVulnerabilityType(), got.GetVulnerabilityType()) {
t.Errorf("GetNote got %v, want %v", *got, n)
}
}
func TestGetOccurrence(t *testing.T) {
ctx := context.Background()
g := Grafeas{storage.NewMemStore()}
pID := "vulnerability-scanner-a"
n := testutil.Note(pID)
createProject(t, pID, ctx, g)
opID := "occurrence-project"
o := testutil.Occurrence(opID, n.Name)
createProject(t, opID, ctx, g)
req := &pb.GetOccurrenceRequest{Name: o.Name}
if _, err := g.GetOccurrence(ctx, req); err == nil {
t.Error("GetOccurrence that doesn't exist got success, want err")
}
parent := name.FormatProject(pID)
cReq := &pb.CreateNoteRequest{Parent: parent, Note: n}
if _, err := g.CreateNote(ctx, cReq); err != nil {
t.Fatalf("CreateNote(%v) got %v, want success", n, err)
}
oParent := name.FormatProject(opID)
ocReq := &pb.CreateOccurrenceRequest{Parent: oParent, Occurrence: o}
if _, err := g.CreateOccurrence(ctx, ocReq); err != nil {
t.Fatalf("CreateOccurrence(%v) got %v, want success", n, err)
}
if got, err := g.GetOccurrence(ctx, req); err != nil {
t.Fatalf("GetOccurrence(%v) got %v, want success", o, err)
} else if o.Name != got.Name || !reflect.DeepEqual(o.GetVulnerabilityDetails(), got.GetVulnerabilityDetails()) {
t.Errorf("GetOccurrence got %v, want %v", *got, o)
}
}
func TestGetOperation(t *testing.T) {
ctx := context.Background()
g := Grafeas{storage.NewMemStore()}
pID := "vulnerability-scanner-a"
o := testutil.Operation(pID)
createProject(t, pID, ctx, g)
req := &opspb.GetOperationRequest{Name: o.Name}
if _, err := g.GetOperation(ctx, req); err == nil {
t.Error("GetOperation that doesn't exist got success, want err")
}
parent := name.FormatProject(pID)
cReq := &pb.CreateOperationRequest{Parent: parent, Operation: o}
if _, err := g.CreateOperation(ctx, cReq); err != nil {
t.Fatalf("CreateOperation(%v) got %v, want success", o, err)
}
if got, err := g.GetOperation(ctx, req); err != nil {
t.Fatalf("GetOperation(%v) got %v, want success", o, err)
} else if o.Name != got.Name || !reflect.DeepEqual(got, o) {
t.Errorf("GetOperation got %v, want %v", *got, o)
}
}
func TestGetOccurrenceNote(t *testing.T) {
ctx := context.Background()
g := Grafeas{storage.NewMemStore()}
pID := "vulnerability-scanner-a"
n := testutil.Note(pID)
createProject(t, pID, ctx, g)
opID := "occurrence-project"
o := testutil.Occurrence(opID, n.Name)
createProject(t, opID, ctx, g)
req := &pb.GetOccurrenceNoteRequest{Name: o.Name}
if _, err := g.GetOccurrenceNote(ctx, req); err == nil {
t.Error("GetOccurrenceNote that doesn't exist got success, want err")
}
pID, _, err := name.ParseNote(n.Name)
if err != nil {
t.Fatalf("Error parsing occurrence name %v", err)
}
parent := name.FormatProject(pID)
cReq := &pb.CreateNoteRequest{Parent: parent, Note: n}
if _, err := g.CreateNote(ctx, cReq); err != nil {
t.Fatalf("CreateNote(%v) got %v, want success", n, err)
}
parent = name.FormatProject(opID)
coReq := &pb.CreateOccurrenceRequest{Parent: parent, Occurrence: o}
if _, err := g.CreateOccurrence(ctx, coReq); err != nil {
t.Fatalf("CreateOccurrence(%v) got %v, want success", n, err)
}
if got, err := g.GetOccurrenceNote(ctx, req); err != nil {
t.Fatalf("GetOccurrenceNote(%v) got %v, want success", n, err)
} else if n.Name != got.Name || !reflect.DeepEqual(n.GetVulnerabilityType(), got.GetVulnerabilityType()) {
t.Errorf("GetOccurrenceNote got %v, want %v", *got, n)
}
}
func TestUpdateNote(t *testing.T) {
ctx := context.Background()
// Update Note that doesn't exist
updateDesc := "this is a new description"
g := Grafeas{storage.NewMemStore()}
pID := "vulnerability-scanner-a"
n := testutil.Note(pID)
createProject(t, pID, ctx, g)
update := testutil.Note(pID)
update.LongDescription = updateDesc
req := &pb.UpdateNoteRequest{Name: n.Name, Note: n}
if _, err := g.UpdateNote(ctx, req); err != nil {
t.Error("UpdateNote that doesn't exist got success, want err")
}
parent := name.FormatProject(pID)
cReq := &pb.CreateNoteRequest{Parent: parent, Note: n}
// Actually create note
if _, err := g.CreateNote(ctx, cReq); err != nil {
t.Fatalf("CreateNote(%v) got %v, want success", n, err)
}
// Update Note name and fail
update.Name = "New name"
req = &pb.UpdateNoteRequest{Name: n.Name, Note: update}
if _, err := g.UpdateNote(ctx, req); err == nil {
t.Error("UpdateNote that with name change got success, want err")
}
// Update Note and verify that update worked.
update = testutil.Note(pID)
update.LongDescription = updateDesc
req = &pb.UpdateNoteRequest{Name: n.Name, Note: update}
if got, err := g.UpdateNote(ctx, req); err != nil {
t.Errorf("UpdateNote got %v, want success", err)
} else if updateDesc != update.LongDescription {
t.Errorf("UpdateNote got %v, want %v",
got.LongDescription, updateDesc)
}
if got, err := g.GetNote(ctx, &pb.GetNoteRequest{Name: n.Name}); err != nil {
t.Fatalf("GetNote(%v) got %v, want success", n, err)
} else if updateDesc != got.LongDescription {
t.Errorf("GetNote got %v, want %v", got.LongDescription, updateDesc)
}
}
func TestUpdateOccurrence(t *testing.T) {
ctx := context.Background()
// Update occurrence that doesn't exist
g := Grafeas{storage.NewMemStore()}
npID := "vulnerability-scanner-a"
n := testutil.Note(npID)
createProject(t, npID, ctx, g)
nParent := name.FormatProject(npID)
cReq := &pb.CreateNoteRequest{Parent: nParent, Note: n}
if _, err := g.CreateNote(ctx, cReq); err != nil {
t.Fatalf("CreateNote(%v) got %v, want success", n, err)
}
pID := "occurrence-project"
o := testutil.Occurrence(pID, n.Name)
createProject(t, pID, ctx, g)
req := &pb.UpdateOccurrenceRequest{Name: o.Name, Occurrence: o}
if _, err := g.UpdateOccurrence(ctx, req); err == nil {
t.Error("UpdateOccurrence that doesn't exist got success, want err")
}
parent := name.FormatProject(pID)
ocReq := &pb.CreateOccurrenceRequest{Parent: parent, Occurrence: o}
if _, err := g.CreateOccurrence(ctx, ocReq); err != nil {
t.Fatalf("CreateOccurrence(%v) got %v, want success", n, err)
}
// update occurrence name
update := testutil.Occurrence(pID, n.Name)
update.Name = "New name"
req = &pb.UpdateOccurrenceRequest{Name: update.Name, Occurrence: update}
if _, err := g.UpdateOccurrence(ctx, req); err == nil {
t.Error("UpdateOccurrence with name change got success, want err")
}
// update note name to a note that doesn't exist
update = testutil.Occurrence(pID, "projects/p/notes/bar")
req = &pb.UpdateOccurrenceRequest{Name: o.Name, Occurrence: update}
if _, err := g.UpdateOccurrence(ctx, req); err == nil {
t.Error("UpdateOccurrence that with note name that doesn't exist" +
" got success, want err")
}
// update note name to a note that does exist
n = testutil.Note(npID)
newName := fmt.Sprintf("%v-new", n.Name)
n.Name = newName
cReq = &pb.CreateNoteRequest{Parent: nParent, Note: n}
if _, err := g.CreateNote(ctx, cReq); err != nil {
t.Fatalf("CreateNote(%v) got %v, want success", n, err)
}
update = testutil.Occurrence(pID, n.Name)
req = &pb.UpdateOccurrenceRequest{Name: o.Name, Occurrence: update}
if got, err := g.UpdateOccurrence(ctx, req); err != nil {
t.Errorf("UpdateOccurrence got %v, want success", err)
} else if n.Name != got.NoteName {
t.Errorf("UpdateOccurrence got %v, want %v",
got.NoteName, n.Name)
}
gReq := &pb.GetOccurrenceRequest{Name: o.Name}
if got, err := g.GetOccurrence(ctx, gReq); err != nil {
t.Fatalf("GetOccurrence(%v) got %v, want success", n, err)
} else if n.Name != got.NoteName {
t.Errorf("GetOccurrence got %v, want %v",
got.NoteName, n.Name)
}
}
func TestListOccurrences(t *testing.T) {
ctx := context.Background()
g := Grafeas{storage.NewMemStore()}
npID := "vulnerability-scanner-a"
n := testutil.Note(npID)
nParent := name.FormatProject(npID)
cReq := &pb.CreateNoteRequest{Parent: nParent, Note: n}
createProject(t, npID, ctx, g)
if _, err := g.CreateNote(ctx, cReq); err != nil {
t.Fatalf("CreateNote(%v) got %v, want success", n, err)
}
os := []*pb.Occurrence{}
findProject := "findThese"
createProject(t, findProject, ctx, g)
dontFind := "dontFind"
createProject(t, dontFind, ctx, g)
for i := 0; i < 20; i++ {
pID := "_"
o := testutil.Occurrence(pID, n.Name)
if i < 5 {
o.Name = name.FormatOccurrence(findProject, string(i))
} else {
o.Name = name.FormatOccurrence(dontFind, string(i))
}
parent := name.FormatProject(pID)
ocReq := &pb.CreateOccurrenceRequest{Parent: parent, Occurrence: o}
if _, err := g.CreateOccurrence(ctx, ocReq); err != nil {
t.Fatalf("CreateOccurrence got %v want success", err)
}
os = append(os, o)
}
lReq := &pb.ListOccurrencesRequest{Parent: name.FormatProject(findProject)}
resp, lErr := g.ListOccurrences(ctx, lReq)
if lErr != nil {
t.Fatalf("ListOccurrences got %v want success", lErr)
}
if len(resp.Occurrences) != 5 {
t.Errorf("resp.Occurrences got %d, want 5", len(resp.Occurrences))
}
}
func TestListProjects(t *testing.T) {
ctx := context.Background()
g := Grafeas{storage.NewMemStore()}
var projects []string
for i := 0; i < 20; i++ {
pID := fmt.Sprintf("proj%v", i)
req := pb.CreateProjectRequest{Name: name.FormatProject(pID)}
if _, err := g.CreateProject(ctx, &req); err != nil {
t.Errorf("CreateProject: got %v, want success", err)
}
if _, err := g.CreateProject(ctx, &req); err == nil {
t.Errorf("CreateProject: got %v, want InvalidArgument", err)
}
projects = append(projects, name.FormatProject(pID))
}
req := pb.ListProjectsRequest{}
_, err := g.ListProjects(ctx, &req)
if err != nil {
t.Errorf("ListProjects: got %v, want success", err)
}
}
func TestListOperations(t *testing.T) {
ctx := context.Background()
g := Grafeas{storage.NewMemStore()}
findProject := "findThese"
createProject(t, findProject, ctx, g)
dontFind := "dontFind"
createProject(t, dontFind, ctx, g)
for i := 0; i < 20; i++ {
pID := "vulnerability-scanner-a"
o := testutil.Operation(pID)
if i < 5 {
o.Name = name.FormatOperation(findProject, string(i))
} else {
o.Name = name.FormatOperation(dontFind, string(i))
}
parent := name.FormatProject(pID)
cReq := &pb.CreateOperationRequest{Parent: parent, Operation: o}
if _, err := g.CreateOperation(ctx, cReq); err != nil {
t.Fatalf("CreateOperation(%v) got %v, want success", o, err)
}
}
lReq := &opspb.ListOperationsRequest{Name: name.FormatProject(findProject)}
resp, err := g.ListOperations(ctx, lReq)
if err != nil {
t.Fatalf("ListOperations got %v want success", err)
}
if len(resp.Operations) != 5 {
t.Errorf("resp.Operations got %d, want 5", len(resp.Operations))
}
}
func TestListNotes(t *testing.T) {
ctx := context.Background()
g := Grafeas{storage.NewMemStore()}
findProject := "findThese"
createProject(t, findProject, ctx, g)
dontFind := "dontFind"
createProject(t, dontFind, ctx, g)
for i := 0; i < 20; i++ {
npID := "vulnerability-scanner-a"
n := testutil.Note(npID)
if i < 5 {
n.Name = name.FormatNote(findProject, string(i))
} else {
n.Name = name.FormatNote(dontFind, string(i))
}
nParent := name.FormatProject(npID)
cReq := &pb.CreateNoteRequest{Parent: nParent, Note: n}
if _, err := g.CreateNote(ctx, cReq); err != nil {
t.Fatalf("CreateNote(%v) got %v, want success", n, err)
}
}
req := &pb.ListNotesRequest{Parent: name.FormatProject(findProject)}
resp, err := g.ListNotes(ctx, req)
if err != nil {
t.Fatalf("ListNotes got %v want success", err)
}
if len(resp.Notes) != 5 {
t.Errorf("resp.Notes got %d, want 5", len(resp.Notes))
}
}
func TestListNoteOccurrences(t *testing.T) {
ctx := context.Background()
g := Grafeas{storage.NewMemStore()}
npID := "vulnerability-scanner-a"
n := testutil.Note(npID)
createProject(t, npID, ctx, g)
nParent := name.FormatProject(npID)
cReq := &pb.CreateNoteRequest{Parent: nParent, Note: n}
if _, err := g.CreateNote(ctx, cReq); err != nil {
t.Fatalf("CreateNote(%v) got %v, want success", n, err)
}
findProject := "findThese"
createProject(t, findProject, ctx, g)
dontFind := "dontFind"
createProject(t, dontFind, ctx, g)
for i := 0; i < 20; i++ {
pID := "_"
o := testutil.Occurrence(pID, n.Name)
if i < 5 {
o.Name = name.FormatOccurrence(findProject, string(i))
} else {
o.Name = name.FormatOccurrence(dontFind, string(i))
}
parent := name.FormatProject(pID)
ocReq := &pb.CreateOccurrenceRequest{Parent: parent, Occurrence: o}
if _, err := g.CreateOccurrence(ctx, ocReq); err != nil {
t.Fatalf("CreateOccurrence got %v want success", err)
}
}
// Create an occurrence tied to another note, to make sure we don't find it.
otherN := testutil.Note("")
otherN.Name = "projects/np/notes/not-to-find"
npID, _, err := name.ParseNote(otherN.Name)
if err != nil {
t.Fatalf("Error parsing note name %v", err)
}
nParent = name.FormatProject(npID)
createProject(t, npID, ctx, g)
cReq = &pb.CreateNoteRequest{Parent: nParent, Note: otherN}
if _, err := g.CreateNote(ctx, cReq); err != nil {
t.Fatalf("CreateNote got %v want success", err)
}
pID := "occurrence-project"
o := testutil.Occurrence(pID, otherN.Name)
createProject(t, pID, ctx, g)
parent := name.FormatProject(pID)
ocReq := &pb.CreateOccurrenceRequest{Parent: parent, Occurrence: o}
if _, err := g.CreateOccurrence(ctx, ocReq); err != nil {
t.Fatalf("CreateOccurrence got %v want success", err)
}
pID, _, err = name.ParseNote(n.Name)
if err != nil {
t.Fatalf("Error parsing note name %v", err)
}
lReq := &pb.ListNoteOccurrencesRequest{Name: n.Name}
resp, lErr := g.ListNoteOccurrences(ctx, lReq)
if lErr != nil {
t.Fatalf("ListNoteOccurrences got %v want success", err)
}
if len(resp.Occurrences) != 20 {
t.Errorf("resp.Occurrences got %d, want 20", len(resp.Occurrences))
}
}

86
vendor/github.com/grafeas/grafeas/server-go/storage.go generated vendored Normal file
View File

@ -0,0 +1,86 @@
// Copyright 2017 The Grafeas Authors. All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package server
import (
pb "github.com/grafeas/grafeas/v1alpha1/proto"
opspb "google.golang.org/genproto/googleapis/longrunning"
)
// Storager is the interface that a Grafeas storage implementation would provide
type Storager interface {
// CreateProject adds the specified project
CreateProject(pID string) error
// CreateNote adds the specified note
CreateNote(n *pb.Note) error
// CreateOccurrence adds the specified occurrence
CreateOccurrence(o *pb.Occurrence) error
// CreateOperation adds the specified operation
CreateOperation(o *opspb.Operation) error
// DeleteNote deletes the project with the given pID
DeleteProject(pID string) error
// DeleteNote deletes the note with the given pID and nID
DeleteNote(pID, nID string) error
// DeleteOccurrence deletes the occurrence with the given pID and oID
DeleteOccurrence(pID, oID string) error
// DeleteOperation deletes the operation with the given pID and oID
DeleteOperation(pID, opID string) error
// GetProject returns the project with the given pID
GetProject(pID string) (*pb.Project, error)
// GetNote returns the note with project (pID) and note ID (nID)
GetNote(pID, nID string) (*pb.Note, error)
// GetNoteByOccurrence returns the note attached to occurrence with pID and oID
GetNoteByOccurrence(pID, oID string) (*pb.Note, error)
// GetOccurrence returns the occurrence with pID and oID
GetOccurrence(pID, oID string) (*pb.Occurrence, error)
// GetOperation returns the operation with pID and oID
GetOperation(pID, opID string) (*opspb.Operation, error)
// ListProjects returns the project id for all projects
ListProjects(filters string) []*pb.Project
// ListNoteOccurrences returns the occcurrences on the particular note (nID) for this project (pID)
ListNoteOccurrences(pID, nID, filters string) ([]*pb.Occurrence, error)
// ListNotes returns the notes for for this project (pID)
ListNotes(pID, filters string) []*pb.Note
// ListOccurrences returns the occurrences for this project ID (pID)
ListOccurrences(pID, filters string) []*pb.Occurrence
// ListOperations returns the operations for this project (pID)
ListOperations(pID, filters string) []*opspb.Operation
// UpdateNote updates the existing note with the given pID and nID
UpdateNote(pID, nID string, n *pb.Note) error
// UpdateOccurrence updates the existing occurrence with the given projectID and occurrenceID
UpdateOccurrence(pID, oID string, o *pb.Occurrence) error
// UpdateOperation updates the existing operation with the given pID and nID
UpdateOperation(pID, opID string, op *opspb.Operation) error
}

2007
vendor/github.com/grafeas/grafeas/v1alpha1/grafeas.json generated vendored Normal file

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

View File

@ -1,15 +0,0 @@
ISC License
Copyright (c) 2012-2013 Dave Collins <dave@davec.name>
Permission to use, copy, modify, and distribute this software for any
purpose with or without fee is hereby granted, provided that the above
copyright notice and this permission notice appear in all copies.
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.

View File

@ -1,152 +0,0 @@
// Copyright (c) 2015 Dave Collins <dave@davec.name>
//
// Permission to use, copy, modify, and distribute this software for any
// purpose with or without fee is hereby granted, provided that the above
// copyright notice and this permission notice appear in all copies.
//
// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
// WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
// MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
// ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
// WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
// ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
// OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
// NOTE: Due to the following build constraints, this file will only be compiled
// when the code is not running on Google App Engine, compiled by GopherJS, and
// "-tags safe" is not added to the go build command line. The "disableunsafe"
// tag is deprecated and thus should not be used.
// +build !js,!appengine,!safe,!disableunsafe
package spew
import (
"reflect"
"unsafe"
)
const (
// UnsafeDisabled is a build-time constant which specifies whether or
// not access to the unsafe package is available.
UnsafeDisabled = false
// ptrSize is the size of a pointer on the current arch.
ptrSize = unsafe.Sizeof((*byte)(nil))
)
var (
// offsetPtr, offsetScalar, and offsetFlag are the offsets for the
// internal reflect.Value fields. These values are valid before golang
// commit ecccf07e7f9d which changed the format. The are also valid
// after commit 82f48826c6c7 which changed the format again to mirror
// the original format. Code in the init function updates these offsets
// as necessary.
offsetPtr = uintptr(ptrSize)
offsetScalar = uintptr(0)
offsetFlag = uintptr(ptrSize * 2)
// flagKindWidth and flagKindShift indicate various bits that the
// reflect package uses internally to track kind information.
//
// flagRO indicates whether or not the value field of a reflect.Value is
// read-only.
//
// flagIndir indicates whether the value field of a reflect.Value is
// the actual data or a pointer to the data.
//
// These values are valid before golang commit 90a7c3c86944 which
// changed their positions. Code in the init function updates these
// flags as necessary.
flagKindWidth = uintptr(5)
flagKindShift = uintptr(flagKindWidth - 1)
flagRO = uintptr(1 << 0)
flagIndir = uintptr(1 << 1)
)
func init() {
// Older versions of reflect.Value stored small integers directly in the
// ptr field (which is named val in the older versions). Versions
// between commits ecccf07e7f9d and 82f48826c6c7 added a new field named
// scalar for this purpose which unfortunately came before the flag
// field, so the offset of the flag field is different for those
// versions.
//
// This code constructs a new reflect.Value from a known small integer
// and checks if the size of the reflect.Value struct indicates it has
// the scalar field. When it does, the offsets are updated accordingly.
vv := reflect.ValueOf(0xf00)
if unsafe.Sizeof(vv) == (ptrSize * 4) {
offsetScalar = ptrSize * 2
offsetFlag = ptrSize * 3
}
// Commit 90a7c3c86944 changed the flag positions such that the low
// order bits are the kind. This code extracts the kind from the flags
// field and ensures it's the correct type. When it's not, the flag
// order has been changed to the newer format, so the flags are updated
// accordingly.
upf := unsafe.Pointer(uintptr(unsafe.Pointer(&vv)) + offsetFlag)
upfv := *(*uintptr)(upf)
flagKindMask := uintptr((1<<flagKindWidth - 1) << flagKindShift)
if (upfv&flagKindMask)>>flagKindShift != uintptr(reflect.Int) {
flagKindShift = 0
flagRO = 1 << 5
flagIndir = 1 << 6
// Commit adf9b30e5594 modified the flags to separate the
// flagRO flag into two bits which specifies whether or not the
// field is embedded. This causes flagIndir to move over a bit
// and means that flagRO is the combination of either of the
// original flagRO bit and the new bit.
//
// This code detects the change by extracting what used to be
// the indirect bit to ensure it's set. When it's not, the flag
// order has been changed to the newer format, so the flags are
// updated accordingly.
if upfv&flagIndir == 0 {
flagRO = 3 << 5
flagIndir = 1 << 7
}
}
}
// unsafeReflectValue converts the passed reflect.Value into a one that bypasses
// the typical safety restrictions preventing access to unaddressable and
// unexported data. It works by digging the raw pointer to the underlying
// value out of the protected value and generating a new unprotected (unsafe)
// reflect.Value to it.
//
// This allows us to check for implementations of the Stringer and error
// interfaces to be used for pretty printing ordinarily unaddressable and
// inaccessible values such as unexported struct fields.
func unsafeReflectValue(v reflect.Value) (rv reflect.Value) {
indirects := 1
vt := v.Type()
upv := unsafe.Pointer(uintptr(unsafe.Pointer(&v)) + offsetPtr)
rvf := *(*uintptr)(unsafe.Pointer(uintptr(unsafe.Pointer(&v)) + offsetFlag))
if rvf&flagIndir != 0 {
vt = reflect.PtrTo(v.Type())
indirects++
} else if offsetScalar != 0 {
// The value is in the scalar field when it's not one of the
// reference types.
switch vt.Kind() {
case reflect.Uintptr:
case reflect.Chan:
case reflect.Func:
case reflect.Map:
case reflect.Ptr:
case reflect.UnsafePointer:
default:
upv = unsafe.Pointer(uintptr(unsafe.Pointer(&v)) +
offsetScalar)
}
}
pv := reflect.NewAt(vt, upv)
rv = pv
for i := 0; i < indirects; i++ {
rv = rv.Elem()
}
return rv
}

View File

@ -1,38 +0,0 @@
// Copyright (c) 2015 Dave Collins <dave@davec.name>
//
// Permission to use, copy, modify, and distribute this software for any
// purpose with or without fee is hereby granted, provided that the above
// copyright notice and this permission notice appear in all copies.
//
// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
// WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
// MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
// ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
// WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
// ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
// OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
// NOTE: Due to the following build constraints, this file will only be compiled
// when the code is running on Google App Engine, compiled by GopherJS, or
// "-tags safe" is added to the go build command line. The "disableunsafe"
// tag is deprecated and thus should not be used.
// +build js appengine safe disableunsafe
package spew
import "reflect"
const (
// UnsafeDisabled is a build-time constant which specifies whether or
// not access to the unsafe package is available.
UnsafeDisabled = true
)
// unsafeReflectValue typically converts the passed reflect.Value into a one
// that bypasses the typical safety restrictions preventing access to
// unaddressable and unexported data. However, doing this relies on access to
// the unsafe package. This is a stub version which simply returns the passed
// reflect.Value when the unsafe package is not available.
func unsafeReflectValue(v reflect.Value) reflect.Value {
return v
}

View File

@ -1,341 +0,0 @@
/*
* Copyright (c) 2013 Dave Collins <dave@davec.name>
*
* Permission to use, copy, modify, and distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
* copyright notice and this permission notice appear in all copies.
*
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
package spew
import (
"bytes"
"fmt"
"io"
"reflect"
"sort"
"strconv"
)
// Some constants in the form of bytes to avoid string overhead. This mirrors
// the technique used in the fmt package.
var (
panicBytes = []byte("(PANIC=")
plusBytes = []byte("+")
iBytes = []byte("i")
trueBytes = []byte("true")
falseBytes = []byte("false")
interfaceBytes = []byte("(interface {})")
commaNewlineBytes = []byte(",\n")
newlineBytes = []byte("\n")
openBraceBytes = []byte("{")
openBraceNewlineBytes = []byte("{\n")
closeBraceBytes = []byte("}")
asteriskBytes = []byte("*")
colonBytes = []byte(":")
colonSpaceBytes = []byte(": ")
openParenBytes = []byte("(")
closeParenBytes = []byte(")")
spaceBytes = []byte(" ")
pointerChainBytes = []byte("->")
nilAngleBytes = []byte("<nil>")
maxNewlineBytes = []byte("<max depth reached>\n")
maxShortBytes = []byte("<max>")
circularBytes = []byte("<already shown>")
circularShortBytes = []byte("<shown>")
invalidAngleBytes = []byte("<invalid>")
openBracketBytes = []byte("[")
closeBracketBytes = []byte("]")
percentBytes = []byte("%")
precisionBytes = []byte(".")
openAngleBytes = []byte("<")
closeAngleBytes = []byte(">")
openMapBytes = []byte("map[")
closeMapBytes = []byte("]")
lenEqualsBytes = []byte("len=")
capEqualsBytes = []byte("cap=")
)
// hexDigits is used to map a decimal value to a hex digit.
var hexDigits = "0123456789abcdef"
// catchPanic handles any panics that might occur during the handleMethods
// calls.
func catchPanic(w io.Writer, v reflect.Value) {
if err := recover(); err != nil {
w.Write(panicBytes)
fmt.Fprintf(w, "%v", err)
w.Write(closeParenBytes)
}
}
// handleMethods attempts to call the Error and String methods on the underlying
// type the passed reflect.Value represents and outputes the result to Writer w.
//
// It handles panics in any called methods by catching and displaying the error
// as the formatted value.
func handleMethods(cs *ConfigState, w io.Writer, v reflect.Value) (handled bool) {
// We need an interface to check if the type implements the error or
// Stringer interface. However, the reflect package won't give us an
// interface on certain things like unexported struct fields in order
// to enforce visibility rules. We use unsafe, when it's available,
// to bypass these restrictions since this package does not mutate the
// values.
if !v.CanInterface() {
if UnsafeDisabled {
return false
}
v = unsafeReflectValue(v)
}
// Choose whether or not to do error and Stringer interface lookups against
// the base type or a pointer to the base type depending on settings.
// Technically calling one of these methods with a pointer receiver can
// mutate the value, however, types which choose to satisify an error or
// Stringer interface with a pointer receiver should not be mutating their
// state inside these interface methods.
if !cs.DisablePointerMethods && !UnsafeDisabled && !v.CanAddr() {
v = unsafeReflectValue(v)
}
if v.CanAddr() {
v = v.Addr()
}
// Is it an error or Stringer?
switch iface := v.Interface().(type) {
case error:
defer catchPanic(w, v)
if cs.ContinueOnMethod {
w.Write(openParenBytes)
w.Write([]byte(iface.Error()))
w.Write(closeParenBytes)
w.Write(spaceBytes)
return false
}
w.Write([]byte(iface.Error()))
return true
case fmt.Stringer:
defer catchPanic(w, v)
if cs.ContinueOnMethod {
w.Write(openParenBytes)
w.Write([]byte(iface.String()))
w.Write(closeParenBytes)
w.Write(spaceBytes)
return false
}
w.Write([]byte(iface.String()))
return true
}
return false
}
// printBool outputs a boolean value as true or false to Writer w.
func printBool(w io.Writer, val bool) {
if val {
w.Write(trueBytes)
} else {
w.Write(falseBytes)
}
}
// printInt outputs a signed integer value to Writer w.
func printInt(w io.Writer, val int64, base int) {
w.Write([]byte(strconv.FormatInt(val, base)))
}
// printUint outputs an unsigned integer value to Writer w.
func printUint(w io.Writer, val uint64, base int) {
w.Write([]byte(strconv.FormatUint(val, base)))
}
// printFloat outputs a floating point value using the specified precision,
// which is expected to be 32 or 64bit, to Writer w.
func printFloat(w io.Writer, val float64, precision int) {
w.Write([]byte(strconv.FormatFloat(val, 'g', -1, precision)))
}
// printComplex outputs a complex value using the specified float precision
// for the real and imaginary parts to Writer w.
func printComplex(w io.Writer, c complex128, floatPrecision int) {
r := real(c)
w.Write(openParenBytes)
w.Write([]byte(strconv.FormatFloat(r, 'g', -1, floatPrecision)))
i := imag(c)
if i >= 0 {
w.Write(plusBytes)
}
w.Write([]byte(strconv.FormatFloat(i, 'g', -1, floatPrecision)))
w.Write(iBytes)
w.Write(closeParenBytes)
}
// printHexPtr outputs a uintptr formatted as hexidecimal with a leading '0x'
// prefix to Writer w.
func printHexPtr(w io.Writer, p uintptr) {
// Null pointer.
num := uint64(p)
if num == 0 {
w.Write(nilAngleBytes)
return
}
// Max uint64 is 16 bytes in hex + 2 bytes for '0x' prefix
buf := make([]byte, 18)
// It's simpler to construct the hex string right to left.
base := uint64(16)
i := len(buf) - 1
for num >= base {
buf[i] = hexDigits[num%base]
num /= base
i--
}
buf[i] = hexDigits[num]
// Add '0x' prefix.
i--
buf[i] = 'x'
i--
buf[i] = '0'
// Strip unused leading bytes.
buf = buf[i:]
w.Write(buf)
}
// valuesSorter implements sort.Interface to allow a slice of reflect.Value
// elements to be sorted.
type valuesSorter struct {
values []reflect.Value
strings []string // either nil or same len and values
cs *ConfigState
}
// newValuesSorter initializes a valuesSorter instance, which holds a set of
// surrogate keys on which the data should be sorted. It uses flags in
// ConfigState to decide if and how to populate those surrogate keys.
func newValuesSorter(values []reflect.Value, cs *ConfigState) sort.Interface {
vs := &valuesSorter{values: values, cs: cs}
if canSortSimply(vs.values[0].Kind()) {
return vs
}
if !cs.DisableMethods {
vs.strings = make([]string, len(values))
for i := range vs.values {
b := bytes.Buffer{}
if !handleMethods(cs, &b, vs.values[i]) {
vs.strings = nil
break
}
vs.strings[i] = b.String()
}
}
if vs.strings == nil && cs.SpewKeys {
vs.strings = make([]string, len(values))
for i := range vs.values {
vs.strings[i] = Sprintf("%#v", vs.values[i].Interface())
}
}
return vs
}
// canSortSimply tests whether a reflect.Kind is a primitive that can be sorted
// directly, or whether it should be considered for sorting by surrogate keys
// (if the ConfigState allows it).
func canSortSimply(kind reflect.Kind) bool {
// This switch parallels valueSortLess, except for the default case.
switch kind {
case reflect.Bool:
return true
case reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64, reflect.Int:
return true
case reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uint:
return true
case reflect.Float32, reflect.Float64:
return true
case reflect.String:
return true
case reflect.Uintptr:
return true
case reflect.Array:
return true
}
return false
}
// Len returns the number of values in the slice. It is part of the
// sort.Interface implementation.
func (s *valuesSorter) Len() int {
return len(s.values)
}
// Swap swaps the values at the passed indices. It is part of the
// sort.Interface implementation.
func (s *valuesSorter) Swap(i, j int) {
s.values[i], s.values[j] = s.values[j], s.values[i]
if s.strings != nil {
s.strings[i], s.strings[j] = s.strings[j], s.strings[i]
}
}
// valueSortLess returns whether the first value should sort before the second
// value. It is used by valueSorter.Less as part of the sort.Interface
// implementation.
func valueSortLess(a, b reflect.Value) bool {
switch a.Kind() {
case reflect.Bool:
return !a.Bool() && b.Bool()
case reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64, reflect.Int:
return a.Int() < b.Int()
case reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uint:
return a.Uint() < b.Uint()
case reflect.Float32, reflect.Float64:
return a.Float() < b.Float()
case reflect.String:
return a.String() < b.String()
case reflect.Uintptr:
return a.Uint() < b.Uint()
case reflect.Array:
// Compare the contents of both arrays.
l := a.Len()
for i := 0; i < l; i++ {
av := a.Index(i)
bv := b.Index(i)
if av.Interface() == bv.Interface() {
continue
}
return valueSortLess(av, bv)
}
}
return a.String() < b.String()
}
// Less returns whether the value at index i should sort before the
// value at index j. It is part of the sort.Interface implementation.
func (s *valuesSorter) Less(i, j int) bool {
if s.strings == nil {
return valueSortLess(s.values[i], s.values[j])
}
return s.strings[i] < s.strings[j]
}
// sortValues is a sort function that handles both native types and any type that
// can be converted to error or Stringer. Other inputs are sorted according to
// their Value.String() value to ensure display stability.
func sortValues(values []reflect.Value, cs *ConfigState) {
if len(values) == 0 {
return
}
sort.Sort(newValuesSorter(values, cs))
}

View File

@ -1,297 +0,0 @@
/*
* Copyright (c) 2013 Dave Collins <dave@davec.name>
*
* Permission to use, copy, modify, and distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
* copyright notice and this permission notice appear in all copies.
*
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
package spew
import (
"bytes"
"fmt"
"io"
"os"
)
// ConfigState houses the configuration options used by spew to format and
// display values. There is a global instance, Config, that is used to control
// all top-level Formatter and Dump functionality. Each ConfigState instance
// provides methods equivalent to the top-level functions.
//
// The zero value for ConfigState provides no indentation. You would typically
// want to set it to a space or a tab.
//
// Alternatively, you can use NewDefaultConfig to get a ConfigState instance
// with default settings. See the documentation of NewDefaultConfig for default
// values.
type ConfigState struct {
// Indent specifies the string to use for each indentation level. The
// global config instance that all top-level functions use set this to a
// single space by default. If you would like more indentation, you might
// set this to a tab with "\t" or perhaps two spaces with " ".
Indent string
// MaxDepth controls the maximum number of levels to descend into nested
// data structures. The default, 0, means there is no limit.
//
// NOTE: Circular data structures are properly detected, so it is not
// necessary to set this value unless you specifically want to limit deeply
// nested data structures.
MaxDepth int
// DisableMethods specifies whether or not error and Stringer interfaces are
// invoked for types that implement them.
DisableMethods bool
// DisablePointerMethods specifies whether or not to check for and invoke
// error and Stringer interfaces on types which only accept a pointer
// receiver when the current type is not a pointer.
//
// NOTE: This might be an unsafe action since calling one of these methods
// with a pointer receiver could technically mutate the value, however,
// in practice, types which choose to satisify an error or Stringer
// interface with a pointer receiver should not be mutating their state
// inside these interface methods. As a result, this option relies on
// access to the unsafe package, so it will not have any effect when
// running in environments without access to the unsafe package such as
// Google App Engine or with the "safe" build tag specified.
DisablePointerMethods bool
// ContinueOnMethod specifies whether or not recursion should continue once
// a custom error or Stringer interface is invoked. The default, false,
// means it will print the results of invoking the custom error or Stringer
// interface and return immediately instead of continuing to recurse into
// the internals of the data type.
//
// NOTE: This flag does not have any effect if method invocation is disabled
// via the DisableMethods or DisablePointerMethods options.
ContinueOnMethod bool
// SortKeys specifies map keys should be sorted before being printed. Use
// this to have a more deterministic, diffable output. Note that only
// native types (bool, int, uint, floats, uintptr and string) and types
// that support the error or Stringer interfaces (if methods are
// enabled) are supported, with other types sorted according to the
// reflect.Value.String() output which guarantees display stability.
SortKeys bool
// SpewKeys specifies that, as a last resort attempt, map keys should
// be spewed to strings and sorted by those strings. This is only
// considered if SortKeys is true.
SpewKeys bool
}
// Config is the active configuration of the top-level functions.
// The configuration can be changed by modifying the contents of spew.Config.
var Config = ConfigState{Indent: " "}
// Errorf is a wrapper for fmt.Errorf that treats each argument as if it were
// passed with a Formatter interface returned by c.NewFormatter. It returns
// the formatted string as a value that satisfies error. See NewFormatter
// for formatting details.
//
// This function is shorthand for the following syntax:
//
// fmt.Errorf(format, c.NewFormatter(a), c.NewFormatter(b))
func (c *ConfigState) Errorf(format string, a ...interface{}) (err error) {
return fmt.Errorf(format, c.convertArgs(a)...)
}
// Fprint is a wrapper for fmt.Fprint that treats each argument as if it were
// passed with a Formatter interface returned by c.NewFormatter. It returns
// the number of bytes written and any write error encountered. See
// NewFormatter for formatting details.
//
// This function is shorthand for the following syntax:
//
// fmt.Fprint(w, c.NewFormatter(a), c.NewFormatter(b))
func (c *ConfigState) Fprint(w io.Writer, a ...interface{}) (n int, err error) {
return fmt.Fprint(w, c.convertArgs(a)...)
}
// Fprintf is a wrapper for fmt.Fprintf that treats each argument as if it were
// passed with a Formatter interface returned by c.NewFormatter. It returns
// the number of bytes written and any write error encountered. See
// NewFormatter for formatting details.
//
// This function is shorthand for the following syntax:
//
// fmt.Fprintf(w, format, c.NewFormatter(a), c.NewFormatter(b))
func (c *ConfigState) Fprintf(w io.Writer, format string, a ...interface{}) (n int, err error) {
return fmt.Fprintf(w, format, c.convertArgs(a)...)
}
// Fprintln is a wrapper for fmt.Fprintln that treats each argument as if it
// passed with a Formatter interface returned by c.NewFormatter. See
// NewFormatter for formatting details.
//
// This function is shorthand for the following syntax:
//
// fmt.Fprintln(w, c.NewFormatter(a), c.NewFormatter(b))
func (c *ConfigState) Fprintln(w io.Writer, a ...interface{}) (n int, err error) {
return fmt.Fprintln(w, c.convertArgs(a)...)
}
// Print is a wrapper for fmt.Print that treats each argument as if it were
// passed with a Formatter interface returned by c.NewFormatter. It returns
// the number of bytes written and any write error encountered. See
// NewFormatter for formatting details.
//
// This function is shorthand for the following syntax:
//
// fmt.Print(c.NewFormatter(a), c.NewFormatter(b))
func (c *ConfigState) Print(a ...interface{}) (n int, err error) {
return fmt.Print(c.convertArgs(a)...)
}
// Printf is a wrapper for fmt.Printf that treats each argument as if it were
// passed with a Formatter interface returned by c.NewFormatter. It returns
// the number of bytes written and any write error encountered. See
// NewFormatter for formatting details.
//
// This function is shorthand for the following syntax:
//
// fmt.Printf(format, c.NewFormatter(a), c.NewFormatter(b))
func (c *ConfigState) Printf(format string, a ...interface{}) (n int, err error) {
return fmt.Printf(format, c.convertArgs(a)...)
}
// Println is a wrapper for fmt.Println that treats each argument as if it were
// passed with a Formatter interface returned by c.NewFormatter. It returns
// the number of bytes written and any write error encountered. See
// NewFormatter for formatting details.
//
// This function is shorthand for the following syntax:
//
// fmt.Println(c.NewFormatter(a), c.NewFormatter(b))
func (c *ConfigState) Println(a ...interface{}) (n int, err error) {
return fmt.Println(c.convertArgs(a)...)
}
// Sprint is a wrapper for fmt.Sprint that treats each argument as if it were
// passed with a Formatter interface returned by c.NewFormatter. It returns
// the resulting string. See NewFormatter for formatting details.
//
// This function is shorthand for the following syntax:
//
// fmt.Sprint(c.NewFormatter(a), c.NewFormatter(b))
func (c *ConfigState) Sprint(a ...interface{}) string {
return fmt.Sprint(c.convertArgs(a)...)
}
// Sprintf is a wrapper for fmt.Sprintf that treats each argument as if it were
// passed with a Formatter interface returned by c.NewFormatter. It returns
// the resulting string. See NewFormatter for formatting details.
//
// This function is shorthand for the following syntax:
//
// fmt.Sprintf(format, c.NewFormatter(a), c.NewFormatter(b))
func (c *ConfigState) Sprintf(format string, a ...interface{}) string {
return fmt.Sprintf(format, c.convertArgs(a)...)
}
// Sprintln is a wrapper for fmt.Sprintln that treats each argument as if it
// were passed with a Formatter interface returned by c.NewFormatter. It
// returns the resulting string. See NewFormatter for formatting details.
//
// This function is shorthand for the following syntax:
//
// fmt.Sprintln(c.NewFormatter(a), c.NewFormatter(b))
func (c *ConfigState) Sprintln(a ...interface{}) string {
return fmt.Sprintln(c.convertArgs(a)...)
}
/*
NewFormatter returns a custom formatter that satisfies the fmt.Formatter
interface. As a result, it integrates cleanly with standard fmt package
printing functions. The formatter is useful for inline printing of smaller data
types similar to the standard %v format specifier.
The custom formatter only responds to the %v (most compact), %+v (adds pointer
addresses), %#v (adds types), and %#+v (adds types and pointer addresses) verb
combinations. Any other verbs such as %x and %q will be sent to the the
standard fmt package for formatting. In addition, the custom formatter ignores
the width and precision arguments (however they will still work on the format
specifiers not handled by the custom formatter).
Typically this function shouldn't be called directly. It is much easier to make
use of the custom formatter by calling one of the convenience functions such as
c.Printf, c.Println, or c.Printf.
*/
func (c *ConfigState) NewFormatter(v interface{}) fmt.Formatter {
return newFormatter(c, v)
}
// Fdump formats and displays the passed arguments to io.Writer w. It formats
// exactly the same as Dump.
func (c *ConfigState) Fdump(w io.Writer, a ...interface{}) {
fdump(c, w, a...)
}
/*
Dump displays the passed parameters to standard out with newlines, customizable
indentation, and additional debug information such as complete types and all
pointer addresses used to indirect to the final value. It provides the
following features over the built-in printing facilities provided by the fmt
package:
* Pointers are dereferenced and followed
* Circular data structures are detected and handled properly
* Custom Stringer/error interfaces are optionally invoked, including
on unexported types
* Custom types which only implement the Stringer/error interfaces via
a pointer receiver are optionally invoked when passing non-pointer
variables
* Byte arrays and slices are dumped like the hexdump -C command which
includes offsets, byte values in hex, and ASCII output
The configuration options are controlled by modifying the public members
of c. See ConfigState for options documentation.
See Fdump if you would prefer dumping to an arbitrary io.Writer or Sdump to
get the formatted result as a string.
*/
func (c *ConfigState) Dump(a ...interface{}) {
fdump(c, os.Stdout, a...)
}
// Sdump returns a string with the passed arguments formatted exactly the same
// as Dump.
func (c *ConfigState) Sdump(a ...interface{}) string {
var buf bytes.Buffer
fdump(c, &buf, a...)
return buf.String()
}
// convertArgs accepts a slice of arguments and returns a slice of the same
// length with each argument converted to a spew Formatter interface using
// the ConfigState associated with s.
func (c *ConfigState) convertArgs(args []interface{}) (formatters []interface{}) {
formatters = make([]interface{}, len(args))
for index, arg := range args {
formatters[index] = newFormatter(c, arg)
}
return formatters
}
// NewDefaultConfig returns a ConfigState with the following default settings.
//
// Indent: " "
// MaxDepth: 0
// DisableMethods: false
// DisablePointerMethods: false
// ContinueOnMethod: false
// SortKeys: false
func NewDefaultConfig() *ConfigState {
return &ConfigState{Indent: " "}
}

View File

@ -1,202 +0,0 @@
/*
* Copyright (c) 2013 Dave Collins <dave@davec.name>
*
* Permission to use, copy, modify, and distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
* copyright notice and this permission notice appear in all copies.
*
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
/*
Package spew implements a deep pretty printer for Go data structures to aid in
debugging.
A quick overview of the additional features spew provides over the built-in
printing facilities for Go data types are as follows:
* Pointers are dereferenced and followed
* Circular data structures are detected and handled properly
* Custom Stringer/error interfaces are optionally invoked, including
on unexported types
* Custom types which only implement the Stringer/error interfaces via
a pointer receiver are optionally invoked when passing non-pointer
variables
* Byte arrays and slices are dumped like the hexdump -C command which
includes offsets, byte values in hex, and ASCII output (only when using
Dump style)
There are two different approaches spew allows for dumping Go data structures:
* Dump style which prints with newlines, customizable indentation,
and additional debug information such as types and all pointer addresses
used to indirect to the final value
* A custom Formatter interface that integrates cleanly with the standard fmt
package and replaces %v, %+v, %#v, and %#+v to provide inline printing
similar to the default %v while providing the additional functionality
outlined above and passing unsupported format verbs such as %x and %q
along to fmt
Quick Start
This section demonstrates how to quickly get started with spew. See the
sections below for further details on formatting and configuration options.
To dump a variable with full newlines, indentation, type, and pointer
information use Dump, Fdump, or Sdump:
spew.Dump(myVar1, myVar2, ...)
spew.Fdump(someWriter, myVar1, myVar2, ...)
str := spew.Sdump(myVar1, myVar2, ...)
Alternatively, if you would prefer to use format strings with a compacted inline
printing style, use the convenience wrappers Printf, Fprintf, etc with
%v (most compact), %+v (adds pointer addresses), %#v (adds types), or
%#+v (adds types and pointer addresses):
spew.Printf("myVar1: %v -- myVar2: %+v", myVar1, myVar2)
spew.Printf("myVar3: %#v -- myVar4: %#+v", myVar3, myVar4)
spew.Fprintf(someWriter, "myVar1: %v -- myVar2: %+v", myVar1, myVar2)
spew.Fprintf(someWriter, "myVar3: %#v -- myVar4: %#+v", myVar3, myVar4)
Configuration Options
Configuration of spew is handled by fields in the ConfigState type. For
convenience, all of the top-level functions use a global state available
via the spew.Config global.
It is also possible to create a ConfigState instance that provides methods
equivalent to the top-level functions. This allows concurrent configuration
options. See the ConfigState documentation for more details.
The following configuration options are available:
* Indent
String to use for each indentation level for Dump functions.
It is a single space by default. A popular alternative is "\t".
* MaxDepth
Maximum number of levels to descend into nested data structures.
There is no limit by default.
* DisableMethods
Disables invocation of error and Stringer interface methods.
Method invocation is enabled by default.
* DisablePointerMethods
Disables invocation of error and Stringer interface methods on types
which only accept pointer receivers from non-pointer variables.
Pointer method invocation is enabled by default.
* ContinueOnMethod
Enables recursion into types after invoking error and Stringer interface
methods. Recursion after method invocation is disabled by default.
* SortKeys
Specifies map keys should be sorted before being printed. Use
this to have a more deterministic, diffable output. Note that
only native types (bool, int, uint, floats, uintptr and string)
and types which implement error or Stringer interfaces are
supported with other types sorted according to the
reflect.Value.String() output which guarantees display
stability. Natural map order is used by default.
* SpewKeys
Specifies that, as a last resort attempt, map keys should be
spewed to strings and sorted by those strings. This is only
considered if SortKeys is true.
Dump Usage
Simply call spew.Dump with a list of variables you want to dump:
spew.Dump(myVar1, myVar2, ...)
You may also call spew.Fdump if you would prefer to output to an arbitrary
io.Writer. For example, to dump to standard error:
spew.Fdump(os.Stderr, myVar1, myVar2, ...)
A third option is to call spew.Sdump to get the formatted output as a string:
str := spew.Sdump(myVar1, myVar2, ...)
Sample Dump Output
See the Dump example for details on the setup of the types and variables being
shown here.
(main.Foo) {
unexportedField: (*main.Bar)(0xf84002e210)({
flag: (main.Flag) flagTwo,
data: (uintptr) <nil>
}),
ExportedField: (map[interface {}]interface {}) (len=1) {
(string) (len=3) "one": (bool) true
}
}
Byte (and uint8) arrays and slices are displayed uniquely like the hexdump -C
command as shown.
([]uint8) (len=32 cap=32) {
00000000 11 12 13 14 15 16 17 18 19 1a 1b 1c 1d 1e 1f 20 |............... |
00000010 21 22 23 24 25 26 27 28 29 2a 2b 2c 2d 2e 2f 30 |!"#$%&'()*+,-./0|
00000020 31 32 |12|
}
Custom Formatter
Spew provides a custom formatter that implements the fmt.Formatter interface
so that it integrates cleanly with standard fmt package printing functions. The
formatter is useful for inline printing of smaller data types similar to the
standard %v format specifier.
The custom formatter only responds to the %v (most compact), %+v (adds pointer
addresses), %#v (adds types), or %#+v (adds types and pointer addresses) verb
combinations. Any other verbs such as %x and %q will be sent to the the
standard fmt package for formatting. In addition, the custom formatter ignores
the width and precision arguments (however they will still work on the format
specifiers not handled by the custom formatter).
Custom Formatter Usage
The simplest way to make use of the spew custom formatter is to call one of the
convenience functions such as spew.Printf, spew.Println, or spew.Printf. The
functions have syntax you are most likely already familiar with:
spew.Printf("myVar1: %v -- myVar2: %+v", myVar1, myVar2)
spew.Printf("myVar3: %#v -- myVar4: %#+v", myVar3, myVar4)
spew.Println(myVar, myVar2)
spew.Fprintf(os.Stderr, "myVar1: %v -- myVar2: %+v", myVar1, myVar2)
spew.Fprintf(os.Stderr, "myVar3: %#v -- myVar4: %#+v", myVar3, myVar4)
See the Index for the full list convenience functions.
Sample Formatter Output
Double pointer to a uint8:
%v: <**>5
%+v: <**>(0xf8400420d0->0xf8400420c8)5
%#v: (**uint8)5
%#+v: (**uint8)(0xf8400420d0->0xf8400420c8)5
Pointer to circular struct with a uint8 field and a pointer to itself:
%v: <*>{1 <*><shown>}
%+v: <*>(0xf84003e260){ui8:1 c:<*>(0xf84003e260)<shown>}
%#v: (*main.circular){ui8:(uint8)1 c:(*main.circular)<shown>}
%#+v: (*main.circular)(0xf84003e260){ui8:(uint8)1 c:(*main.circular)(0xf84003e260)<shown>}
See the Printf example for details on the setup of variables being shown
here.
Errors
Since it is possible for custom Stringer/error interfaces to panic, spew
detects them and handles them internally by printing the panic information
inline with the output. Since spew is intended to provide deep pretty printing
capabilities on structures, it intentionally does not return any errors.
*/
package spew

View File

@ -1,509 +0,0 @@
/*
* Copyright (c) 2013 Dave Collins <dave@davec.name>
*
* Permission to use, copy, modify, and distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
* copyright notice and this permission notice appear in all copies.
*
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
package spew
import (
"bytes"
"encoding/hex"
"fmt"
"io"
"os"
"reflect"
"regexp"
"strconv"
"strings"
)
var (
// uint8Type is a reflect.Type representing a uint8. It is used to
// convert cgo types to uint8 slices for hexdumping.
uint8Type = reflect.TypeOf(uint8(0))
// cCharRE is a regular expression that matches a cgo char.
// It is used to detect character arrays to hexdump them.
cCharRE = regexp.MustCompile("^.*\\._Ctype_char$")
// cUnsignedCharRE is a regular expression that matches a cgo unsigned
// char. It is used to detect unsigned character arrays to hexdump
// them.
cUnsignedCharRE = regexp.MustCompile("^.*\\._Ctype_unsignedchar$")
// cUint8tCharRE is a regular expression that matches a cgo uint8_t.
// It is used to detect uint8_t arrays to hexdump them.
cUint8tCharRE = regexp.MustCompile("^.*\\._Ctype_uint8_t$")
)
// dumpState contains information about the state of a dump operation.
type dumpState struct {
w io.Writer
depth int
pointers map[uintptr]int
ignoreNextType bool
ignoreNextIndent bool
cs *ConfigState
}
// indent performs indentation according to the depth level and cs.Indent
// option.
func (d *dumpState) indent() {
if d.ignoreNextIndent {
d.ignoreNextIndent = false
return
}
d.w.Write(bytes.Repeat([]byte(d.cs.Indent), d.depth))
}
// unpackValue returns values inside of non-nil interfaces when possible.
// This is useful for data types like structs, arrays, slices, and maps which
// can contain varying types packed inside an interface.
func (d *dumpState) unpackValue(v reflect.Value) reflect.Value {
if v.Kind() == reflect.Interface && !v.IsNil() {
v = v.Elem()
}
return v
}
// dumpPtr handles formatting of pointers by indirecting them as necessary.
func (d *dumpState) dumpPtr(v reflect.Value) {
// Remove pointers at or below the current depth from map used to detect
// circular refs.
for k, depth := range d.pointers {
if depth >= d.depth {
delete(d.pointers, k)
}
}
// Keep list of all dereferenced pointers to show later.
pointerChain := make([]uintptr, 0)
// Figure out how many levels of indirection there are by dereferencing
// pointers and unpacking interfaces down the chain while detecting circular
// references.
nilFound := false
cycleFound := false
indirects := 0
ve := v
for ve.Kind() == reflect.Ptr {
if ve.IsNil() {
nilFound = true
break
}
indirects++
addr := ve.Pointer()
pointerChain = append(pointerChain, addr)
if pd, ok := d.pointers[addr]; ok && pd < d.depth {
cycleFound = true
indirects--
break
}
d.pointers[addr] = d.depth
ve = ve.Elem()
if ve.Kind() == reflect.Interface {
if ve.IsNil() {
nilFound = true
break
}
ve = ve.Elem()
}
}
// Display type information.
d.w.Write(openParenBytes)
d.w.Write(bytes.Repeat(asteriskBytes, indirects))
d.w.Write([]byte(ve.Type().String()))
d.w.Write(closeParenBytes)
// Display pointer information.
if len(pointerChain) > 0 {
d.w.Write(openParenBytes)
for i, addr := range pointerChain {
if i > 0 {
d.w.Write(pointerChainBytes)
}
printHexPtr(d.w, addr)
}
d.w.Write(closeParenBytes)
}
// Display dereferenced value.
d.w.Write(openParenBytes)
switch {
case nilFound == true:
d.w.Write(nilAngleBytes)
case cycleFound == true:
d.w.Write(circularBytes)
default:
d.ignoreNextType = true
d.dump(ve)
}
d.w.Write(closeParenBytes)
}
// dumpSlice handles formatting of arrays and slices. Byte (uint8 under
// reflection) arrays and slices are dumped in hexdump -C fashion.
func (d *dumpState) dumpSlice(v reflect.Value) {
// Determine whether this type should be hex dumped or not. Also,
// for types which should be hexdumped, try to use the underlying data
// first, then fall back to trying to convert them to a uint8 slice.
var buf []uint8
doConvert := false
doHexDump := false
numEntries := v.Len()
if numEntries > 0 {
vt := v.Index(0).Type()
vts := vt.String()
switch {
// C types that need to be converted.
case cCharRE.MatchString(vts):
fallthrough
case cUnsignedCharRE.MatchString(vts):
fallthrough
case cUint8tCharRE.MatchString(vts):
doConvert = true
// Try to use existing uint8 slices and fall back to converting
// and copying if that fails.
case vt.Kind() == reflect.Uint8:
// We need an addressable interface to convert the type
// to a byte slice. However, the reflect package won't
// give us an interface on certain things like
// unexported struct fields in order to enforce
// visibility rules. We use unsafe, when available, to
// bypass these restrictions since this package does not
// mutate the values.
vs := v
if !vs.CanInterface() || !vs.CanAddr() {
vs = unsafeReflectValue(vs)
}
if !UnsafeDisabled {
vs = vs.Slice(0, numEntries)
// Use the existing uint8 slice if it can be
// type asserted.
iface := vs.Interface()
if slice, ok := iface.([]uint8); ok {
buf = slice
doHexDump = true
break
}
}
// The underlying data needs to be converted if it can't
// be type asserted to a uint8 slice.
doConvert = true
}
// Copy and convert the underlying type if needed.
if doConvert && vt.ConvertibleTo(uint8Type) {
// Convert and copy each element into a uint8 byte
// slice.
buf = make([]uint8, numEntries)
for i := 0; i < numEntries; i++ {
vv := v.Index(i)
buf[i] = uint8(vv.Convert(uint8Type).Uint())
}
doHexDump = true
}
}
// Hexdump the entire slice as needed.
if doHexDump {
indent := strings.Repeat(d.cs.Indent, d.depth)
str := indent + hex.Dump(buf)
str = strings.Replace(str, "\n", "\n"+indent, -1)
str = strings.TrimRight(str, d.cs.Indent)
d.w.Write([]byte(str))
return
}
// Recursively call dump for each item.
for i := 0; i < numEntries; i++ {
d.dump(d.unpackValue(v.Index(i)))
if i < (numEntries - 1) {
d.w.Write(commaNewlineBytes)
} else {
d.w.Write(newlineBytes)
}
}
}
// dump is the main workhorse for dumping a value. It uses the passed reflect
// value to figure out what kind of object we are dealing with and formats it
// appropriately. It is a recursive function, however circular data structures
// are detected and handled properly.
func (d *dumpState) dump(v reflect.Value) {
// Handle invalid reflect values immediately.
kind := v.Kind()
if kind == reflect.Invalid {
d.w.Write(invalidAngleBytes)
return
}
// Handle pointers specially.
if kind == reflect.Ptr {
d.indent()
d.dumpPtr(v)
return
}
// Print type information unless already handled elsewhere.
if !d.ignoreNextType {
d.indent()
d.w.Write(openParenBytes)
d.w.Write([]byte(v.Type().String()))
d.w.Write(closeParenBytes)
d.w.Write(spaceBytes)
}
d.ignoreNextType = false
// Display length and capacity if the built-in len and cap functions
// work with the value's kind and the len/cap itself is non-zero.
valueLen, valueCap := 0, 0
switch v.Kind() {
case reflect.Array, reflect.Slice, reflect.Chan:
valueLen, valueCap = v.Len(), v.Cap()
case reflect.Map, reflect.String:
valueLen = v.Len()
}
if valueLen != 0 || valueCap != 0 {
d.w.Write(openParenBytes)
if valueLen != 0 {
d.w.Write(lenEqualsBytes)
printInt(d.w, int64(valueLen), 10)
}
if valueCap != 0 {
if valueLen != 0 {
d.w.Write(spaceBytes)
}
d.w.Write(capEqualsBytes)
printInt(d.w, int64(valueCap), 10)
}
d.w.Write(closeParenBytes)
d.w.Write(spaceBytes)
}
// Call Stringer/error interfaces if they exist and the handle methods flag
// is enabled
if !d.cs.DisableMethods {
if (kind != reflect.Invalid) && (kind != reflect.Interface) {
if handled := handleMethods(d.cs, d.w, v); handled {
return
}
}
}
switch kind {
case reflect.Invalid:
// Do nothing. We should never get here since invalid has already
// been handled above.
case reflect.Bool:
printBool(d.w, v.Bool())
case reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64, reflect.Int:
printInt(d.w, v.Int(), 10)
case reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uint:
printUint(d.w, v.Uint(), 10)
case reflect.Float32:
printFloat(d.w, v.Float(), 32)
case reflect.Float64:
printFloat(d.w, v.Float(), 64)
case reflect.Complex64:
printComplex(d.w, v.Complex(), 32)
case reflect.Complex128:
printComplex(d.w, v.Complex(), 64)
case reflect.Slice:
if v.IsNil() {
d.w.Write(nilAngleBytes)
break
}
fallthrough
case reflect.Array:
d.w.Write(openBraceNewlineBytes)
d.depth++
if (d.cs.MaxDepth != 0) && (d.depth > d.cs.MaxDepth) {
d.indent()
d.w.Write(maxNewlineBytes)
} else {
d.dumpSlice(v)
}
d.depth--
d.indent()
d.w.Write(closeBraceBytes)
case reflect.String:
d.w.Write([]byte(strconv.Quote(v.String())))
case reflect.Interface:
// The only time we should get here is for nil interfaces due to
// unpackValue calls.
if v.IsNil() {
d.w.Write(nilAngleBytes)
}
case reflect.Ptr:
// Do nothing. We should never get here since pointers have already
// been handled above.
case reflect.Map:
// nil maps should be indicated as different than empty maps
if v.IsNil() {
d.w.Write(nilAngleBytes)
break
}
d.w.Write(openBraceNewlineBytes)
d.depth++
if (d.cs.MaxDepth != 0) && (d.depth > d.cs.MaxDepth) {
d.indent()
d.w.Write(maxNewlineBytes)
} else {
numEntries := v.Len()
keys := v.MapKeys()
if d.cs.SortKeys {
sortValues(keys, d.cs)
}
for i, key := range keys {
d.dump(d.unpackValue(key))
d.w.Write(colonSpaceBytes)
d.ignoreNextIndent = true
d.dump(d.unpackValue(v.MapIndex(key)))
if i < (numEntries - 1) {
d.w.Write(commaNewlineBytes)
} else {
d.w.Write(newlineBytes)
}
}
}
d.depth--
d.indent()
d.w.Write(closeBraceBytes)
case reflect.Struct:
d.w.Write(openBraceNewlineBytes)
d.depth++
if (d.cs.MaxDepth != 0) && (d.depth > d.cs.MaxDepth) {
d.indent()
d.w.Write(maxNewlineBytes)
} else {
vt := v.Type()
numFields := v.NumField()
for i := 0; i < numFields; i++ {
d.indent()
vtf := vt.Field(i)
d.w.Write([]byte(vtf.Name))
d.w.Write(colonSpaceBytes)
d.ignoreNextIndent = true
d.dump(d.unpackValue(v.Field(i)))
if i < (numFields - 1) {
d.w.Write(commaNewlineBytes)
} else {
d.w.Write(newlineBytes)
}
}
}
d.depth--
d.indent()
d.w.Write(closeBraceBytes)
case reflect.Uintptr:
printHexPtr(d.w, uintptr(v.Uint()))
case reflect.UnsafePointer, reflect.Chan, reflect.Func:
printHexPtr(d.w, v.Pointer())
// There were not any other types at the time this code was written, but
// fall back to letting the default fmt package handle it in case any new
// types are added.
default:
if v.CanInterface() {
fmt.Fprintf(d.w, "%v", v.Interface())
} else {
fmt.Fprintf(d.w, "%v", v.String())
}
}
}
// fdump is a helper function to consolidate the logic from the various public
// methods which take varying writers and config states.
func fdump(cs *ConfigState, w io.Writer, a ...interface{}) {
for _, arg := range a {
if arg == nil {
w.Write(interfaceBytes)
w.Write(spaceBytes)
w.Write(nilAngleBytes)
w.Write(newlineBytes)
continue
}
d := dumpState{w: w, cs: cs}
d.pointers = make(map[uintptr]int)
d.dump(reflect.ValueOf(arg))
d.w.Write(newlineBytes)
}
}
// Fdump formats and displays the passed arguments to io.Writer w. It formats
// exactly the same as Dump.
func Fdump(w io.Writer, a ...interface{}) {
fdump(&Config, w, a...)
}
// Sdump returns a string with the passed arguments formatted exactly the same
// as Dump.
func Sdump(a ...interface{}) string {
var buf bytes.Buffer
fdump(&Config, &buf, a...)
return buf.String()
}
/*
Dump displays the passed parameters to standard out with newlines, customizable
indentation, and additional debug information such as complete types and all
pointer addresses used to indirect to the final value. It provides the
following features over the built-in printing facilities provided by the fmt
package:
* Pointers are dereferenced and followed
* Circular data structures are detected and handled properly
* Custom Stringer/error interfaces are optionally invoked, including
on unexported types
* Custom types which only implement the Stringer/error interfaces via
a pointer receiver are optionally invoked when passing non-pointer
variables
* Byte arrays and slices are dumped like the hexdump -C command which
includes offsets, byte values in hex, and ASCII output
The configuration options are controlled by an exported package global,
spew.Config. See ConfigState for options documentation.
See Fdump if you would prefer dumping to an arbitrary io.Writer or Sdump to
get the formatted result as a string.
*/
func Dump(a ...interface{}) {
fdump(&Config, os.Stdout, a...)
}

View File

@ -1,419 +0,0 @@
/*
* Copyright (c) 2013 Dave Collins <dave@davec.name>
*
* Permission to use, copy, modify, and distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
* copyright notice and this permission notice appear in all copies.
*
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
package spew
import (
"bytes"
"fmt"
"reflect"
"strconv"
"strings"
)
// supportedFlags is a list of all the character flags supported by fmt package.
const supportedFlags = "0-+# "
// formatState implements the fmt.Formatter interface and contains information
// about the state of a formatting operation. The NewFormatter function can
// be used to get a new Formatter which can be used directly as arguments
// in standard fmt package printing calls.
type formatState struct {
value interface{}
fs fmt.State
depth int
pointers map[uintptr]int
ignoreNextType bool
cs *ConfigState
}
// buildDefaultFormat recreates the original format string without precision
// and width information to pass in to fmt.Sprintf in the case of an
// unrecognized type. Unless new types are added to the language, this
// function won't ever be called.
func (f *formatState) buildDefaultFormat() (format string) {
buf := bytes.NewBuffer(percentBytes)
for _, flag := range supportedFlags {
if f.fs.Flag(int(flag)) {
buf.WriteRune(flag)
}
}
buf.WriteRune('v')
format = buf.String()
return format
}
// constructOrigFormat recreates the original format string including precision
// and width information to pass along to the standard fmt package. This allows
// automatic deferral of all format strings this package doesn't support.
func (f *formatState) constructOrigFormat(verb rune) (format string) {
buf := bytes.NewBuffer(percentBytes)
for _, flag := range supportedFlags {
if f.fs.Flag(int(flag)) {
buf.WriteRune(flag)
}
}
if width, ok := f.fs.Width(); ok {
buf.WriteString(strconv.Itoa(width))
}
if precision, ok := f.fs.Precision(); ok {
buf.Write(precisionBytes)
buf.WriteString(strconv.Itoa(precision))
}
buf.WriteRune(verb)
format = buf.String()
return format
}
// unpackValue returns values inside of non-nil interfaces when possible and
// ensures that types for values which have been unpacked from an interface
// are displayed when the show types flag is also set.
// This is useful for data types like structs, arrays, slices, and maps which
// can contain varying types packed inside an interface.
func (f *formatState) unpackValue(v reflect.Value) reflect.Value {
if v.Kind() == reflect.Interface {
f.ignoreNextType = false
if !v.IsNil() {
v = v.Elem()
}
}
return v
}
// formatPtr handles formatting of pointers by indirecting them as necessary.
func (f *formatState) formatPtr(v reflect.Value) {
// Display nil if top level pointer is nil.
showTypes := f.fs.Flag('#')
if v.IsNil() && (!showTypes || f.ignoreNextType) {
f.fs.Write(nilAngleBytes)
return
}
// Remove pointers at or below the current depth from map used to detect
// circular refs.
for k, depth := range f.pointers {
if depth >= f.depth {
delete(f.pointers, k)
}
}
// Keep list of all dereferenced pointers to possibly show later.
pointerChain := make([]uintptr, 0)
// Figure out how many levels of indirection there are by derferencing
// pointers and unpacking interfaces down the chain while detecting circular
// references.
nilFound := false
cycleFound := false
indirects := 0
ve := v
for ve.Kind() == reflect.Ptr {
if ve.IsNil() {
nilFound = true
break
}
indirects++
addr := ve.Pointer()
pointerChain = append(pointerChain, addr)
if pd, ok := f.pointers[addr]; ok && pd < f.depth {
cycleFound = true
indirects--
break
}
f.pointers[addr] = f.depth
ve = ve.Elem()
if ve.Kind() == reflect.Interface {
if ve.IsNil() {
nilFound = true
break
}
ve = ve.Elem()
}
}
// Display type or indirection level depending on flags.
if showTypes && !f.ignoreNextType {
f.fs.Write(openParenBytes)
f.fs.Write(bytes.Repeat(asteriskBytes, indirects))
f.fs.Write([]byte(ve.Type().String()))
f.fs.Write(closeParenBytes)
} else {
if nilFound || cycleFound {
indirects += strings.Count(ve.Type().String(), "*")
}
f.fs.Write(openAngleBytes)
f.fs.Write([]byte(strings.Repeat("*", indirects)))
f.fs.Write(closeAngleBytes)
}
// Display pointer information depending on flags.
if f.fs.Flag('+') && (len(pointerChain) > 0) {
f.fs.Write(openParenBytes)
for i, addr := range pointerChain {
if i > 0 {
f.fs.Write(pointerChainBytes)
}
printHexPtr(f.fs, addr)
}
f.fs.Write(closeParenBytes)
}
// Display dereferenced value.
switch {
case nilFound == true:
f.fs.Write(nilAngleBytes)
case cycleFound == true:
f.fs.Write(circularShortBytes)
default:
f.ignoreNextType = true
f.format(ve)
}
}
// format is the main workhorse for providing the Formatter interface. It
// uses the passed reflect value to figure out what kind of object we are
// dealing with and formats it appropriately. It is a recursive function,
// however circular data structures are detected and handled properly.
func (f *formatState) format(v reflect.Value) {
// Handle invalid reflect values immediately.
kind := v.Kind()
if kind == reflect.Invalid {
f.fs.Write(invalidAngleBytes)
return
}
// Handle pointers specially.
if kind == reflect.Ptr {
f.formatPtr(v)
return
}
// Print type information unless already handled elsewhere.
if !f.ignoreNextType && f.fs.Flag('#') {
f.fs.Write(openParenBytes)
f.fs.Write([]byte(v.Type().String()))
f.fs.Write(closeParenBytes)
}
f.ignoreNextType = false
// Call Stringer/error interfaces if they exist and the handle methods
// flag is enabled.
if !f.cs.DisableMethods {
if (kind != reflect.Invalid) && (kind != reflect.Interface) {
if handled := handleMethods(f.cs, f.fs, v); handled {
return
}
}
}
switch kind {
case reflect.Invalid:
// Do nothing. We should never get here since invalid has already
// been handled above.
case reflect.Bool:
printBool(f.fs, v.Bool())
case reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64, reflect.Int:
printInt(f.fs, v.Int(), 10)
case reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uint:
printUint(f.fs, v.Uint(), 10)
case reflect.Float32:
printFloat(f.fs, v.Float(), 32)
case reflect.Float64:
printFloat(f.fs, v.Float(), 64)
case reflect.Complex64:
printComplex(f.fs, v.Complex(), 32)
case reflect.Complex128:
printComplex(f.fs, v.Complex(), 64)
case reflect.Slice:
if v.IsNil() {
f.fs.Write(nilAngleBytes)
break
}
fallthrough
case reflect.Array:
f.fs.Write(openBracketBytes)
f.depth++
if (f.cs.MaxDepth != 0) && (f.depth > f.cs.MaxDepth) {
f.fs.Write(maxShortBytes)
} else {
numEntries := v.Len()
for i := 0; i < numEntries; i++ {
if i > 0 {
f.fs.Write(spaceBytes)
}
f.ignoreNextType = true
f.format(f.unpackValue(v.Index(i)))
}
}
f.depth--
f.fs.Write(closeBracketBytes)
case reflect.String:
f.fs.Write([]byte(v.String()))
case reflect.Interface:
// The only time we should get here is for nil interfaces due to
// unpackValue calls.
if v.IsNil() {
f.fs.Write(nilAngleBytes)
}
case reflect.Ptr:
// Do nothing. We should never get here since pointers have already
// been handled above.
case reflect.Map:
// nil maps should be indicated as different than empty maps
if v.IsNil() {
f.fs.Write(nilAngleBytes)
break
}
f.fs.Write(openMapBytes)
f.depth++
if (f.cs.MaxDepth != 0) && (f.depth > f.cs.MaxDepth) {
f.fs.Write(maxShortBytes)
} else {
keys := v.MapKeys()
if f.cs.SortKeys {
sortValues(keys, f.cs)
}
for i, key := range keys {
if i > 0 {
f.fs.Write(spaceBytes)
}
f.ignoreNextType = true
f.format(f.unpackValue(key))
f.fs.Write(colonBytes)
f.ignoreNextType = true
f.format(f.unpackValue(v.MapIndex(key)))
}
}
f.depth--
f.fs.Write(closeMapBytes)
case reflect.Struct:
numFields := v.NumField()
f.fs.Write(openBraceBytes)
f.depth++
if (f.cs.MaxDepth != 0) && (f.depth > f.cs.MaxDepth) {
f.fs.Write(maxShortBytes)
} else {
vt := v.Type()
for i := 0; i < numFields; i++ {
if i > 0 {
f.fs.Write(spaceBytes)
}
vtf := vt.Field(i)
if f.fs.Flag('+') || f.fs.Flag('#') {
f.fs.Write([]byte(vtf.Name))
f.fs.Write(colonBytes)
}
f.format(f.unpackValue(v.Field(i)))
}
}
f.depth--
f.fs.Write(closeBraceBytes)
case reflect.Uintptr:
printHexPtr(f.fs, uintptr(v.Uint()))
case reflect.UnsafePointer, reflect.Chan, reflect.Func:
printHexPtr(f.fs, v.Pointer())
// There were not any other types at the time this code was written, but
// fall back to letting the default fmt package handle it if any get added.
default:
format := f.buildDefaultFormat()
if v.CanInterface() {
fmt.Fprintf(f.fs, format, v.Interface())
} else {
fmt.Fprintf(f.fs, format, v.String())
}
}
}
// Format satisfies the fmt.Formatter interface. See NewFormatter for usage
// details.
func (f *formatState) Format(fs fmt.State, verb rune) {
f.fs = fs
// Use standard formatting for verbs that are not v.
if verb != 'v' {
format := f.constructOrigFormat(verb)
fmt.Fprintf(fs, format, f.value)
return
}
if f.value == nil {
if fs.Flag('#') {
fs.Write(interfaceBytes)
}
fs.Write(nilAngleBytes)
return
}
f.format(reflect.ValueOf(f.value))
}
// newFormatter is a helper function to consolidate the logic from the various
// public methods which take varying config states.
func newFormatter(cs *ConfigState, v interface{}) fmt.Formatter {
fs := &formatState{value: v, cs: cs}
fs.pointers = make(map[uintptr]int)
return fs
}
/*
NewFormatter returns a custom formatter that satisfies the fmt.Formatter
interface. As a result, it integrates cleanly with standard fmt package
printing functions. The formatter is useful for inline printing of smaller data
types similar to the standard %v format specifier.
The custom formatter only responds to the %v (most compact), %+v (adds pointer
addresses), %#v (adds types), or %#+v (adds types and pointer addresses) verb
combinations. Any other verbs such as %x and %q will be sent to the the
standard fmt package for formatting. In addition, the custom formatter ignores
the width and precision arguments (however they will still work on the format
specifiers not handled by the custom formatter).
Typically this function shouldn't be called directly. It is much easier to make
use of the custom formatter by calling one of the convenience functions such as
Printf, Println, or Fprintf.
*/
func NewFormatter(v interface{}) fmt.Formatter {
return newFormatter(&Config, v)
}

View File

@ -1,148 +0,0 @@
/*
* Copyright (c) 2013 Dave Collins <dave@davec.name>
*
* Permission to use, copy, modify, and distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
* copyright notice and this permission notice appear in all copies.
*
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
package spew
import (
"fmt"
"io"
)
// Errorf is a wrapper for fmt.Errorf that treats each argument as if it were
// passed with a default Formatter interface returned by NewFormatter. It
// returns the formatted string as a value that satisfies error. See
// NewFormatter for formatting details.
//
// This function is shorthand for the following syntax:
//
// fmt.Errorf(format, spew.NewFormatter(a), spew.NewFormatter(b))
func Errorf(format string, a ...interface{}) (err error) {
return fmt.Errorf(format, convertArgs(a)...)
}
// Fprint is a wrapper for fmt.Fprint that treats each argument as if it were
// passed with a default Formatter interface returned by NewFormatter. It
// returns the number of bytes written and any write error encountered. See
// NewFormatter for formatting details.
//
// This function is shorthand for the following syntax:
//
// fmt.Fprint(w, spew.NewFormatter(a), spew.NewFormatter(b))
func Fprint(w io.Writer, a ...interface{}) (n int, err error) {
return fmt.Fprint(w, convertArgs(a)...)
}
// Fprintf is a wrapper for fmt.Fprintf that treats each argument as if it were
// passed with a default Formatter interface returned by NewFormatter. It
// returns the number of bytes written and any write error encountered. See
// NewFormatter for formatting details.
//
// This function is shorthand for the following syntax:
//
// fmt.Fprintf(w, format, spew.NewFormatter(a), spew.NewFormatter(b))
func Fprintf(w io.Writer, format string, a ...interface{}) (n int, err error) {
return fmt.Fprintf(w, format, convertArgs(a)...)
}
// Fprintln is a wrapper for fmt.Fprintln that treats each argument as if it
// passed with a default Formatter interface returned by NewFormatter. See
// NewFormatter for formatting details.
//
// This function is shorthand for the following syntax:
//
// fmt.Fprintln(w, spew.NewFormatter(a), spew.NewFormatter(b))
func Fprintln(w io.Writer, a ...interface{}) (n int, err error) {
return fmt.Fprintln(w, convertArgs(a)...)
}
// Print is a wrapper for fmt.Print that treats each argument as if it were
// passed with a default Formatter interface returned by NewFormatter. It
// returns the number of bytes written and any write error encountered. See
// NewFormatter for formatting details.
//
// This function is shorthand for the following syntax:
//
// fmt.Print(spew.NewFormatter(a), spew.NewFormatter(b))
func Print(a ...interface{}) (n int, err error) {
return fmt.Print(convertArgs(a)...)
}
// Printf is a wrapper for fmt.Printf that treats each argument as if it were
// passed with a default Formatter interface returned by NewFormatter. It
// returns the number of bytes written and any write error encountered. See
// NewFormatter for formatting details.
//
// This function is shorthand for the following syntax:
//
// fmt.Printf(format, spew.NewFormatter(a), spew.NewFormatter(b))
func Printf(format string, a ...interface{}) (n int, err error) {
return fmt.Printf(format, convertArgs(a)...)
}
// Println is a wrapper for fmt.Println that treats each argument as if it were
// passed with a default Formatter interface returned by NewFormatter. It
// returns the number of bytes written and any write error encountered. See
// NewFormatter for formatting details.
//
// This function is shorthand for the following syntax:
//
// fmt.Println(spew.NewFormatter(a), spew.NewFormatter(b))
func Println(a ...interface{}) (n int, err error) {
return fmt.Println(convertArgs(a)...)
}
// Sprint is a wrapper for fmt.Sprint that treats each argument as if it were
// passed with a default Formatter interface returned by NewFormatter. It
// returns the resulting string. See NewFormatter for formatting details.
//
// This function is shorthand for the following syntax:
//
// fmt.Sprint(spew.NewFormatter(a), spew.NewFormatter(b))
func Sprint(a ...interface{}) string {
return fmt.Sprint(convertArgs(a)...)
}
// Sprintf is a wrapper for fmt.Sprintf that treats each argument as if it were
// passed with a default Formatter interface returned by NewFormatter. It
// returns the resulting string. See NewFormatter for formatting details.
//
// This function is shorthand for the following syntax:
//
// fmt.Sprintf(format, spew.NewFormatter(a), spew.NewFormatter(b))
func Sprintf(format string, a ...interface{}) string {
return fmt.Sprintf(format, convertArgs(a)...)
}
// Sprintln is a wrapper for fmt.Sprintln that treats each argument as if it
// were passed with a default Formatter interface returned by NewFormatter. It
// returns the resulting string. See NewFormatter for formatting details.
//
// This function is shorthand for the following syntax:
//
// fmt.Sprintln(spew.NewFormatter(a), spew.NewFormatter(b))
func Sprintln(a ...interface{}) string {
return fmt.Sprintln(convertArgs(a)...)
}
// convertArgs accepts a slice of arguments and returns a slice of the same
// length with each argument converted to a default spew Formatter interface.
func convertArgs(args []interface{}) (formatters []interface{}) {
formatters = make([]interface{}, len(args))
for index, arg := range args {
formatters[index] = NewFormatter(arg)
}
return formatters
}

View File

@ -1,27 +0,0 @@
Copyright (c) 2013, Patrick Mezard
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are
met:
Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in the
documentation and/or other materials provided with the distribution.
The names of its contributors may not be used to endorse or promote
products derived from this software without specific prior written
permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

View File

@ -1,758 +0,0 @@
// Package difflib is a partial port of Python difflib module.
//
// It provides tools to compare sequences of strings and generate textual diffs.
//
// The following class and functions have been ported:
//
// - SequenceMatcher
//
// - unified_diff
//
// - context_diff
//
// Getting unified diffs was the main goal of the port. Keep in mind this code
// is mostly suitable to output text differences in a human friendly way, there
// are no guarantees generated diffs are consumable by patch(1).
package difflib
import (
"bufio"
"bytes"
"fmt"
"io"
"strings"
)
func min(a, b int) int {
if a < b {
return a
}
return b
}
func max(a, b int) int {
if a > b {
return a
}
return b
}
func calculateRatio(matches, length int) float64 {
if length > 0 {
return 2.0 * float64(matches) / float64(length)
}
return 1.0
}
type Match struct {
A int
B int
Size int
}
type OpCode struct {
Tag byte
I1 int
I2 int
J1 int
J2 int
}
// SequenceMatcher compares sequence of strings. The basic
// algorithm predates, and is a little fancier than, an algorithm
// published in the late 1980's by Ratcliff and Obershelp under the
// hyperbolic name "gestalt pattern matching". The basic idea is to find
// the longest contiguous matching subsequence that contains no "junk"
// elements (R-O doesn't address junk). The same idea is then applied
// recursively to the pieces of the sequences to the left and to the right
// of the matching subsequence. This does not yield minimal edit
// sequences, but does tend to yield matches that "look right" to people.
//
// SequenceMatcher tries to compute a "human-friendly diff" between two
// sequences. Unlike e.g. UNIX(tm) diff, the fundamental notion is the
// longest *contiguous* & junk-free matching subsequence. That's what
// catches peoples' eyes. The Windows(tm) windiff has another interesting
// notion, pairing up elements that appear uniquely in each sequence.
// That, and the method here, appear to yield more intuitive difference
// reports than does diff. This method appears to be the least vulnerable
// to synching up on blocks of "junk lines", though (like blank lines in
// ordinary text files, or maybe "<P>" lines in HTML files). That may be
// because this is the only method of the 3 that has a *concept* of
// "junk" <wink>.
//
// Timing: Basic R-O is cubic time worst case and quadratic time expected
// case. SequenceMatcher is quadratic time for the worst case and has
// expected-case behavior dependent in a complicated way on how many
// elements the sequences have in common; best case time is linear.
type SequenceMatcher struct {
a []string
b []string
b2j map[string][]int
IsJunk func(string) bool
autoJunk bool
bJunk map[string]struct{}
matchingBlocks []Match
fullBCount map[string]int
bPopular map[string]struct{}
opCodes []OpCode
}
func NewMatcher(a, b []string) *SequenceMatcher {
m := SequenceMatcher{autoJunk: true}
m.SetSeqs(a, b)
return &m
}
func NewMatcherWithJunk(a, b []string, autoJunk bool,
isJunk func(string) bool) *SequenceMatcher {
m := SequenceMatcher{IsJunk: isJunk, autoJunk: autoJunk}
m.SetSeqs(a, b)
return &m
}
// Set two sequences to be compared.
func (m *SequenceMatcher) SetSeqs(a, b []string) {
m.SetSeq1(a)
m.SetSeq2(b)
}
// Set the first sequence to be compared. The second sequence to be compared is
// not changed.
//
// SequenceMatcher computes and caches detailed information about the second
// sequence, so if you want to compare one sequence S against many sequences,
// use .SetSeq2(s) once and call .SetSeq1(x) repeatedly for each of the other
// sequences.
//
// See also SetSeqs() and SetSeq2().
func (m *SequenceMatcher) SetSeq1(a []string) {
if &a == &m.a {
return
}
m.a = a
m.matchingBlocks = nil
m.opCodes = nil
}
// Set the second sequence to be compared. The first sequence to be compared is
// not changed.
func (m *SequenceMatcher) SetSeq2(b []string) {
if &b == &m.b {
return
}
m.b = b
m.matchingBlocks = nil
m.opCodes = nil
m.fullBCount = nil
m.chainB()
}
func (m *SequenceMatcher) chainB() {
// Populate line -> index mapping
b2j := map[string][]int{}
for i, s := range m.b {
indices := b2j[s]
indices = append(indices, i)
b2j[s] = indices
}
// Purge junk elements
m.bJunk = map[string]struct{}{}
if m.IsJunk != nil {
junk := m.bJunk
for s, _ := range b2j {
if m.IsJunk(s) {
junk[s] = struct{}{}
}
}
for s, _ := range junk {
delete(b2j, s)
}
}
// Purge remaining popular elements
popular := map[string]struct{}{}
n := len(m.b)
if m.autoJunk && n >= 200 {
ntest := n/100 + 1
for s, indices := range b2j {
if len(indices) > ntest {
popular[s] = struct{}{}
}
}
for s, _ := range popular {
delete(b2j, s)
}
}
m.bPopular = popular
m.b2j = b2j
}
func (m *SequenceMatcher) isBJunk(s string) bool {
_, ok := m.bJunk[s]
return ok
}
// Find longest matching block in a[alo:ahi] and b[blo:bhi].
//
// If IsJunk is not defined:
//
// Return (i,j,k) such that a[i:i+k] is equal to b[j:j+k], where
// alo <= i <= i+k <= ahi
// blo <= j <= j+k <= bhi
// and for all (i',j',k') meeting those conditions,
// k >= k'
// i <= i'
// and if i == i', j <= j'
//
// In other words, of all maximal matching blocks, return one that
// starts earliest in a, and of all those maximal matching blocks that
// start earliest in a, return the one that starts earliest in b.
//
// If IsJunk is defined, first the longest matching block is
// determined as above, but with the additional restriction that no
// junk element appears in the block. Then that block is extended as
// far as possible by matching (only) junk elements on both sides. So
// the resulting block never matches on junk except as identical junk
// happens to be adjacent to an "interesting" match.
//
// If no blocks match, return (alo, blo, 0).
func (m *SequenceMatcher) findLongestMatch(alo, ahi, blo, bhi int) Match {
// CAUTION: stripping common prefix or suffix would be incorrect.
// E.g.,
// ab
// acab
// Longest matching block is "ab", but if common prefix is
// stripped, it's "a" (tied with "b"). UNIX(tm) diff does so
// strip, so ends up claiming that ab is changed to acab by
// inserting "ca" in the middle. That's minimal but unintuitive:
// "it's obvious" that someone inserted "ac" at the front.
// Windiff ends up at the same place as diff, but by pairing up
// the unique 'b's and then matching the first two 'a's.
besti, bestj, bestsize := alo, blo, 0
// find longest junk-free match
// during an iteration of the loop, j2len[j] = length of longest
// junk-free match ending with a[i-1] and b[j]
j2len := map[int]int{}
for i := alo; i != ahi; i++ {
// look at all instances of a[i] in b; note that because
// b2j has no junk keys, the loop is skipped if a[i] is junk
newj2len := map[int]int{}
for _, j := range m.b2j[m.a[i]] {
// a[i] matches b[j]
if j < blo {
continue
}
if j >= bhi {
break
}
k := j2len[j-1] + 1
newj2len[j] = k
if k > bestsize {
besti, bestj, bestsize = i-k+1, j-k+1, k
}
}
j2len = newj2len
}
// Extend the best by non-junk elements on each end. In particular,
// "popular" non-junk elements aren't in b2j, which greatly speeds
// the inner loop above, but also means "the best" match so far
// doesn't contain any junk *or* popular non-junk elements.
for besti > alo && bestj > blo && !m.isBJunk(m.b[bestj-1]) &&
m.a[besti-1] == m.b[bestj-1] {
besti, bestj, bestsize = besti-1, bestj-1, bestsize+1
}
for besti+bestsize < ahi && bestj+bestsize < bhi &&
!m.isBJunk(m.b[bestj+bestsize]) &&
m.a[besti+bestsize] == m.b[bestj+bestsize] {
bestsize += 1
}
// Now that we have a wholly interesting match (albeit possibly
// empty!), we may as well suck up the matching junk on each
// side of it too. Can't think of a good reason not to, and it
// saves post-processing the (possibly considerable) expense of
// figuring out what to do with it. In the case of an empty
// interesting match, this is clearly the right thing to do,
// because no other kind of match is possible in the regions.
for besti > alo && bestj > blo && m.isBJunk(m.b[bestj-1]) &&
m.a[besti-1] == m.b[bestj-1] {
besti, bestj, bestsize = besti-1, bestj-1, bestsize+1
}
for besti+bestsize < ahi && bestj+bestsize < bhi &&
m.isBJunk(m.b[bestj+bestsize]) &&
m.a[besti+bestsize] == m.b[bestj+bestsize] {
bestsize += 1
}
return Match{A: besti, B: bestj, Size: bestsize}
}
// Return list of triples describing matching subsequences.
//
// Each triple is of the form (i, j, n), and means that
// a[i:i+n] == b[j:j+n]. The triples are monotonically increasing in
// i and in j. It's also guaranteed that if (i, j, n) and (i', j', n') are
// adjacent triples in the list, and the second is not the last triple in the
// list, then i+n != i' or j+n != j'. IOW, adjacent triples never describe
// adjacent equal blocks.
//
// The last triple is a dummy, (len(a), len(b), 0), and is the only
// triple with n==0.
func (m *SequenceMatcher) GetMatchingBlocks() []Match {
if m.matchingBlocks != nil {
return m.matchingBlocks
}
var matchBlocks func(alo, ahi, blo, bhi int, matched []Match) []Match
matchBlocks = func(alo, ahi, blo, bhi int, matched []Match) []Match {
match := m.findLongestMatch(alo, ahi, blo, bhi)
i, j, k := match.A, match.B, match.Size
if match.Size > 0 {
if alo < i && blo < j {
matched = matchBlocks(alo, i, blo, j, matched)
}
matched = append(matched, match)
if i+k < ahi && j+k < bhi {
matched = matchBlocks(i+k, ahi, j+k, bhi, matched)
}
}
return matched
}
matched := matchBlocks(0, len(m.a), 0, len(m.b), nil)
// It's possible that we have adjacent equal blocks in the
// matching_blocks list now.
nonAdjacent := []Match{}
i1, j1, k1 := 0, 0, 0
for _, b := range matched {
// Is this block adjacent to i1, j1, k1?
i2, j2, k2 := b.A, b.B, b.Size
if i1+k1 == i2 && j1+k1 == j2 {
// Yes, so collapse them -- this just increases the length of
// the first block by the length of the second, and the first
// block so lengthened remains the block to compare against.
k1 += k2
} else {
// Not adjacent. Remember the first block (k1==0 means it's
// the dummy we started with), and make the second block the
// new block to compare against.
if k1 > 0 {
nonAdjacent = append(nonAdjacent, Match{i1, j1, k1})
}
i1, j1, k1 = i2, j2, k2
}
}
if k1 > 0 {
nonAdjacent = append(nonAdjacent, Match{i1, j1, k1})
}
nonAdjacent = append(nonAdjacent, Match{len(m.a), len(m.b), 0})
m.matchingBlocks = nonAdjacent
return m.matchingBlocks
}
// Return list of 5-tuples describing how to turn a into b.
//
// Each tuple is of the form (tag, i1, i2, j1, j2). The first tuple
// has i1 == j1 == 0, and remaining tuples have i1 == the i2 from the
// tuple preceding it, and likewise for j1 == the previous j2.
//
// The tags are characters, with these meanings:
//
// 'r' (replace): a[i1:i2] should be replaced by b[j1:j2]
//
// 'd' (delete): a[i1:i2] should be deleted, j1==j2 in this case.
//
// 'i' (insert): b[j1:j2] should be inserted at a[i1:i1], i1==i2 in this case.
//
// 'e' (equal): a[i1:i2] == b[j1:j2]
func (m *SequenceMatcher) GetOpCodes() []OpCode {
if m.opCodes != nil {
return m.opCodes
}
i, j := 0, 0
matching := m.GetMatchingBlocks()
opCodes := make([]OpCode, 0, len(matching))
for _, m := range matching {
// invariant: we've pumped out correct diffs to change
// a[:i] into b[:j], and the next matching block is
// a[ai:ai+size] == b[bj:bj+size]. So we need to pump
// out a diff to change a[i:ai] into b[j:bj], pump out
// the matching block, and move (i,j) beyond the match
ai, bj, size := m.A, m.B, m.Size
tag := byte(0)
if i < ai && j < bj {
tag = 'r'
} else if i < ai {
tag = 'd'
} else if j < bj {
tag = 'i'
}
if tag > 0 {
opCodes = append(opCodes, OpCode{tag, i, ai, j, bj})
}
i, j = ai+size, bj+size
// the list of matching blocks is terminated by a
// sentinel with size 0
if size > 0 {
opCodes = append(opCodes, OpCode{'e', ai, i, bj, j})
}
}
m.opCodes = opCodes
return m.opCodes
}
// Isolate change clusters by eliminating ranges with no changes.
//
// Return a generator of groups with up to n lines of context.
// Each group is in the same format as returned by GetOpCodes().
func (m *SequenceMatcher) GetGroupedOpCodes(n int) [][]OpCode {
if n < 0 {
n = 3
}
codes := m.GetOpCodes()
if len(codes) == 0 {
codes = []OpCode{OpCode{'e', 0, 1, 0, 1}}
}
// Fixup leading and trailing groups if they show no changes.
if codes[0].Tag == 'e' {
c := codes[0]
i1, i2, j1, j2 := c.I1, c.I2, c.J1, c.J2
codes[0] = OpCode{c.Tag, max(i1, i2-n), i2, max(j1, j2-n), j2}
}
if codes[len(codes)-1].Tag == 'e' {
c := codes[len(codes)-1]
i1, i2, j1, j2 := c.I1, c.I2, c.J1, c.J2
codes[len(codes)-1] = OpCode{c.Tag, i1, min(i2, i1+n), j1, min(j2, j1+n)}
}
nn := n + n
groups := [][]OpCode{}
group := []OpCode{}
for _, c := range codes {
i1, i2, j1, j2 := c.I1, c.I2, c.J1, c.J2
// End the current group and start a new one whenever
// there is a large range with no changes.
if c.Tag == 'e' && i2-i1 > nn {
group = append(group, OpCode{c.Tag, i1, min(i2, i1+n),
j1, min(j2, j1+n)})
groups = append(groups, group)
group = []OpCode{}
i1, j1 = max(i1, i2-n), max(j1, j2-n)
}
group = append(group, OpCode{c.Tag, i1, i2, j1, j2})
}
if len(group) > 0 && !(len(group) == 1 && group[0].Tag == 'e') {
groups = append(groups, group)
}
return groups
}
// Return a measure of the sequences' similarity (float in [0,1]).
//
// Where T is the total number of elements in both sequences, and
// M is the number of matches, this is 2.0*M / T.
// Note that this is 1 if the sequences are identical, and 0 if
// they have nothing in common.
//
// .Ratio() is expensive to compute if you haven't already computed
// .GetMatchingBlocks() or .GetOpCodes(), in which case you may
// want to try .QuickRatio() or .RealQuickRation() first to get an
// upper bound.
func (m *SequenceMatcher) Ratio() float64 {
matches := 0
for _, m := range m.GetMatchingBlocks() {
matches += m.Size
}
return calculateRatio(matches, len(m.a)+len(m.b))
}
// Return an upper bound on ratio() relatively quickly.
//
// This isn't defined beyond that it is an upper bound on .Ratio(), and
// is faster to compute.
func (m *SequenceMatcher) QuickRatio() float64 {
// viewing a and b as multisets, set matches to the cardinality
// of their intersection; this counts the number of matches
// without regard to order, so is clearly an upper bound
if m.fullBCount == nil {
m.fullBCount = map[string]int{}
for _, s := range m.b {
m.fullBCount[s] = m.fullBCount[s] + 1
}
}
// avail[x] is the number of times x appears in 'b' less the
// number of times we've seen it in 'a' so far ... kinda
avail := map[string]int{}
matches := 0
for _, s := range m.a {
n, ok := avail[s]
if !ok {
n = m.fullBCount[s]
}
avail[s] = n - 1
if n > 0 {
matches += 1
}
}
return calculateRatio(matches, len(m.a)+len(m.b))
}
// Return an upper bound on ratio() very quickly.
//
// This isn't defined beyond that it is an upper bound on .Ratio(), and
// is faster to compute than either .Ratio() or .QuickRatio().
func (m *SequenceMatcher) RealQuickRatio() float64 {
la, lb := len(m.a), len(m.b)
return calculateRatio(min(la, lb), la+lb)
}
// Convert range to the "ed" format
func formatRangeUnified(start, stop int) string {
// Per the diff spec at http://www.unix.org/single_unix_specification/
beginning := start + 1 // lines start numbering with one
length := stop - start
if length == 1 {
return fmt.Sprintf("%d", beginning)
}
if length == 0 {
beginning -= 1 // empty ranges begin at line just before the range
}
return fmt.Sprintf("%d,%d", beginning, length)
}
// Unified diff parameters
type UnifiedDiff struct {
A []string // First sequence lines
FromFile string // First file name
FromDate string // First file time
B []string // Second sequence lines
ToFile string // Second file name
ToDate string // Second file time
Eol string // Headers end of line, defaults to LF
Context int // Number of context lines
}
// Compare two sequences of lines; generate the delta as a unified diff.
//
// Unified diffs are a compact way of showing line changes and a few
// lines of context. The number of context lines is set by 'n' which
// defaults to three.
//
// By default, the diff control lines (those with ---, +++, or @@) are
// created with a trailing newline. This is helpful so that inputs
// created from file.readlines() result in diffs that are suitable for
// file.writelines() since both the inputs and outputs have trailing
// newlines.
//
// For inputs that do not have trailing newlines, set the lineterm
// argument to "" so that the output will be uniformly newline free.
//
// The unidiff format normally has a header for filenames and modification
// times. Any or all of these may be specified using strings for
// 'fromfile', 'tofile', 'fromfiledate', and 'tofiledate'.
// The modification times are normally expressed in the ISO 8601 format.
func WriteUnifiedDiff(writer io.Writer, diff UnifiedDiff) error {
buf := bufio.NewWriter(writer)
defer buf.Flush()
w := func(format string, args ...interface{}) error {
_, err := buf.WriteString(fmt.Sprintf(format, args...))
return err
}
if len(diff.Eol) == 0 {
diff.Eol = "\n"
}
started := false
m := NewMatcher(diff.A, diff.B)
for _, g := range m.GetGroupedOpCodes(diff.Context) {
if !started {
started = true
fromDate := ""
if len(diff.FromDate) > 0 {
fromDate = "\t" + diff.FromDate
}
toDate := ""
if len(diff.ToDate) > 0 {
toDate = "\t" + diff.ToDate
}
err := w("--- %s%s%s", diff.FromFile, fromDate, diff.Eol)
if err != nil {
return err
}
err = w("+++ %s%s%s", diff.ToFile, toDate, diff.Eol)
if err != nil {
return err
}
}
first, last := g[0], g[len(g)-1]
range1 := formatRangeUnified(first.I1, last.I2)
range2 := formatRangeUnified(first.J1, last.J2)
if err := w("@@ -%s +%s @@%s", range1, range2, diff.Eol); err != nil {
return err
}
for _, c := range g {
i1, i2, j1, j2 := c.I1, c.I2, c.J1, c.J2
if c.Tag == 'e' {
for _, line := range diff.A[i1:i2] {
if err := w(" " + line); err != nil {
return err
}
}
continue
}
if c.Tag == 'r' || c.Tag == 'd' {
for _, line := range diff.A[i1:i2] {
if err := w("-" + line); err != nil {
return err
}
}
}
if c.Tag == 'r' || c.Tag == 'i' {
for _, line := range diff.B[j1:j2] {
if err := w("+" + line); err != nil {
return err
}
}
}
}
}
return nil
}
// Like WriteUnifiedDiff but returns the diff a string.
func GetUnifiedDiffString(diff UnifiedDiff) (string, error) {
w := &bytes.Buffer{}
err := WriteUnifiedDiff(w, diff)
return string(w.Bytes()), err
}
// Convert range to the "ed" format.
func formatRangeContext(start, stop int) string {
// Per the diff spec at http://www.unix.org/single_unix_specification/
beginning := start + 1 // lines start numbering with one
length := stop - start
if length == 0 {
beginning -= 1 // empty ranges begin at line just before the range
}
if length <= 1 {
return fmt.Sprintf("%d", beginning)
}
return fmt.Sprintf("%d,%d", beginning, beginning+length-1)
}
type ContextDiff UnifiedDiff
// Compare two sequences of lines; generate the delta as a context diff.
//
// Context diffs are a compact way of showing line changes and a few
// lines of context. The number of context lines is set by diff.Context
// which defaults to three.
//
// By default, the diff control lines (those with *** or ---) are
// created with a trailing newline.
//
// For inputs that do not have trailing newlines, set the diff.Eol
// argument to "" so that the output will be uniformly newline free.
//
// The context diff format normally has a header for filenames and
// modification times. Any or all of these may be specified using
// strings for diff.FromFile, diff.ToFile, diff.FromDate, diff.ToDate.
// The modification times are normally expressed in the ISO 8601 format.
// If not specified, the strings default to blanks.
func WriteContextDiff(writer io.Writer, diff ContextDiff) error {
buf := bufio.NewWriter(writer)
defer buf.Flush()
var diffErr error
w := func(format string, args ...interface{}) {
_, err := buf.WriteString(fmt.Sprintf(format, args...))
if diffErr == nil && err != nil {
diffErr = err
}
}
if len(diff.Eol) == 0 {
diff.Eol = "\n"
}
prefix := map[byte]string{
'i': "+ ",
'd': "- ",
'r': "! ",
'e': " ",
}
started := false
m := NewMatcher(diff.A, diff.B)
for _, g := range m.GetGroupedOpCodes(diff.Context) {
if !started {
started = true
fromDate := ""
if len(diff.FromDate) > 0 {
fromDate = "\t" + diff.FromDate
}
toDate := ""
if len(diff.ToDate) > 0 {
toDate = "\t" + diff.ToDate
}
w("*** %s%s%s", diff.FromFile, fromDate, diff.Eol)
w("--- %s%s%s", diff.ToFile, toDate, diff.Eol)
}
first, last := g[0], g[len(g)-1]
w("***************" + diff.Eol)
range1 := formatRangeContext(first.I1, last.I2)
w("*** %s ****%s", range1, diff.Eol)
for _, c := range g {
if c.Tag == 'r' || c.Tag == 'd' {
for _, cc := range g {
if cc.Tag == 'i' {
continue
}
for _, line := range diff.A[cc.I1:cc.I2] {
w(prefix[cc.Tag] + line)
}
}
break
}
}
range2 := formatRangeContext(first.J1, last.J2)
w("--- %s ----%s", range2, diff.Eol)
for _, c := range g {
if c.Tag == 'r' || c.Tag == 'i' {
for _, cc := range g {
if cc.Tag == 'd' {
continue
}
for _, line := range diff.B[cc.J1:cc.J2] {
w(prefix[cc.Tag] + line)
}
}
break
}
}
}
return diffErr
}
// Like WriteContextDiff but returns the diff a string.
func GetContextDiffString(diff ContextDiff) (string, error) {
w := &bytes.Buffer{}
err := WriteContextDiff(w, diff)
return string(w.Bytes()), err
}
// Split a string on "\n" while preserving them. The output can be used
// as input for UnifiedDiff and ContextDiff structures.
func SplitLines(s string) []string {
lines := strings.SplitAfter(s, "\n")
lines[len(lines)-1] += "\n"
return lines
}

View File

@ -1,22 +0,0 @@
# Compiled Object files, Static and Dynamic libs (Shared Objects)
*.o
*.a
*.so
# Folders
_obj
_test
# Architecture specific extensions/prefixes
*.[568vq]
[568vq].out
*.cgo1.go
*.cgo2.c
_cgo_defun.c
_cgo_gotypes.go
_cgo_export.*
_testmain.go
*.exe

View File

@ -1,23 +0,0 @@
objx - by Mat Ryer and Tyler Bunnell
The MIT License (MIT)
Copyright (c) 2014 Stretchr, Inc.
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

View File

@ -1,3 +0,0 @@
# objx
* Jump into the [API Documentation](http://godoc.org/github.com/stretchr/objx)

View File

@ -1,179 +0,0 @@
package objx
import (
"fmt"
"regexp"
"strconv"
"strings"
)
// arrayAccesRegexString is the regex used to extract the array number
// from the access path
const arrayAccesRegexString = `^(.+)\[([0-9]+)\]$`
// arrayAccesRegex is the compiled arrayAccesRegexString
var arrayAccesRegex = regexp.MustCompile(arrayAccesRegexString)
// Get gets the value using the specified selector and
// returns it inside a new Obj object.
//
// If it cannot find the value, Get will return a nil
// value inside an instance of Obj.
//
// Get can only operate directly on map[string]interface{} and []interface.
//
// Example
//
// To access the title of the third chapter of the second book, do:
//
// o.Get("books[1].chapters[2].title")
func (m Map) Get(selector string) *Value {
rawObj := access(m, selector, nil, false, false)
return &Value{data: rawObj}
}
// Set sets the value using the specified selector and
// returns the object on which Set was called.
//
// Set can only operate directly on map[string]interface{} and []interface
//
// Example
//
// To set the title of the third chapter of the second book, do:
//
// o.Set("books[1].chapters[2].title","Time to Go")
func (m Map) Set(selector string, value interface{}) Map {
access(m, selector, value, true, false)
return m
}
// access accesses the object using the selector and performs the
// appropriate action.
func access(current, selector, value interface{}, isSet, panics bool) interface{} {
switch selector.(type) {
case int, int8, int16, int32, int64, uint, uint8, uint16, uint32, uint64:
if array, ok := current.([]interface{}); ok {
index := intFromInterface(selector)
if index >= len(array) {
if panics {
panic(fmt.Sprintf("objx: Index %d is out of range. Slice only contains %d items.", index, len(array)))
}
return nil
}
return array[index]
}
return nil
case string:
selStr := selector.(string)
selSegs := strings.SplitN(selStr, PathSeparator, 2)
thisSel := selSegs[0]
index := -1
var err error
// https://github.com/stretchr/objx/issues/12
if strings.Contains(thisSel, "[") {
arrayMatches := arrayAccesRegex.FindStringSubmatch(thisSel)
if len(arrayMatches) > 0 {
// Get the key into the map
thisSel = arrayMatches[1]
// Get the index into the array at the key
index, err = strconv.Atoi(arrayMatches[2])
if err != nil {
// This should never happen. If it does, something has gone
// seriously wrong. Panic.
panic("objx: Array index is not an integer. Must use array[int].")
}
}
}
if curMap, ok := current.(Map); ok {
current = map[string]interface{}(curMap)
}
// get the object in question
switch current.(type) {
case map[string]interface{}:
curMSI := current.(map[string]interface{})
if len(selSegs) <= 1 && isSet {
curMSI[thisSel] = value
return nil
} else {
current = curMSI[thisSel]
}
default:
current = nil
}
if current == nil && panics {
panic(fmt.Sprintf("objx: '%v' invalid on object.", selector))
}
// do we need to access the item of an array?
if index > -1 {
if array, ok := current.([]interface{}); ok {
if index < len(array) {
current = array[index]
} else {
if panics {
panic(fmt.Sprintf("objx: Index %d is out of range. Slice only contains %d items.", index, len(array)))
}
current = nil
}
}
}
if len(selSegs) > 1 {
current = access(current, selSegs[1], value, isSet, panics)
}
}
return current
}
// intFromInterface converts an interface object to the largest
// representation of an unsigned integer using a type switch and
// assertions
func intFromInterface(selector interface{}) int {
var value int
switch selector.(type) {
case int:
value = selector.(int)
case int8:
value = int(selector.(int8))
case int16:
value = int(selector.(int16))
case int32:
value = int(selector.(int32))
case int64:
value = int(selector.(int64))
case uint:
value = int(selector.(uint))
case uint8:
value = int(selector.(uint8))
case uint16:
value = int(selector.(uint16))
case uint32:
value = int(selector.(uint32))
case uint64:
value = int(selector.(uint64))
default:
panic("objx: array access argument is not an integer type (this should never happen)")
}
return value
}

View File

@ -1,14 +0,0 @@
case []{1}:
a := object.([]{1})
if isSet {
a[index] = value.({1})
} else {
if index >= len(a) {
if panics {
panic(fmt.Sprintf("objx: Index %d is out of range because the []{1} only contains %d items.", index, len(a)))
}
return nil
} else {
return a[index]
}
}

View File

@ -1,86 +0,0 @@
<html>
<head>
<title>
Codegen
</title>
<style>
body {
width: 800px;
margin: auto;
}
textarea {
width: 100%;
min-height: 100px;
font-family: Courier;
}
</style>
</head>
<body>
<h2>
Template
</h2>
<p>
Use <code>{x}</code> as a placeholder for each argument.
</p>
<textarea id="template"></textarea>
<h2>
Arguments (comma separated)
</h2>
<p>
One block per line
</p>
<textarea id="args"></textarea>
<h2>
Output
</h2>
<input id="go" type="button" value="Generate code" />
<textarea id="output"></textarea>
<script src="http://ajax.googleapis.com/ajax/libs/jquery/1.10.2/jquery.min.js"></script>
<script>
$(function(){
$("#go").click(function(){
var output = ""
var template = $("#template").val()
var args = $("#args").val()
// collect the args
var argLines = args.split("\n")
for (var line in argLines) {
var argLine = argLines[line];
var thisTemp = template
// get individual args
var args = argLine.split(",")
for (var argI in args) {
var argText = args[argI];
var argPlaceholder = "{" + argI + "}";
while (thisTemp.indexOf(argPlaceholder) > -1) {
thisTemp = thisTemp.replace(argPlaceholder, argText);
}
}
output += thisTemp
}
$("#output").val(output);
});
});
</script>
</body>
</html>

View File

@ -1,286 +0,0 @@
/*
{4} ({1} and []{1})
--------------------------------------------------
*/
// {4} gets the value as a {1}, returns the optionalDefault
// value or a system default object if the value is the wrong type.
func (v *Value) {4}(optionalDefault ...{1}) {1} {
if s, ok := v.data.({1}); ok {
return s
}
if len(optionalDefault) == 1 {
return optionalDefault[0]
}
return {3}
}
// Must{4} gets the value as a {1}.
//
// Panics if the object is not a {1}.
func (v *Value) Must{4}() {1} {
return v.data.({1})
}
// {4}Slice gets the value as a []{1}, returns the optionalDefault
// value or nil if the value is not a []{1}.
func (v *Value) {4}Slice(optionalDefault ...[]{1}) []{1} {
if s, ok := v.data.([]{1}); ok {
return s
}
if len(optionalDefault) == 1 {
return optionalDefault[0]
}
return nil
}
// Must{4}Slice gets the value as a []{1}.
//
// Panics if the object is not a []{1}.
func (v *Value) Must{4}Slice() []{1} {
return v.data.([]{1})
}
// Is{4} gets whether the object contained is a {1} or not.
func (v *Value) Is{4}() bool {
_, ok := v.data.({1})
return ok
}
// Is{4}Slice gets whether the object contained is a []{1} or not.
func (v *Value) Is{4}Slice() bool {
_, ok := v.data.([]{1})
return ok
}
// Each{4} calls the specified callback for each object
// in the []{1}.
//
// Panics if the object is the wrong type.
func (v *Value) Each{4}(callback func(int, {1}) bool) *Value {
for index, val := range v.Must{4}Slice() {
carryon := callback(index, val)
if carryon == false {
break
}
}
return v
}
// Where{4} uses the specified decider function to select items
// from the []{1}. The object contained in the result will contain
// only the selected items.
func (v *Value) Where{4}(decider func(int, {1}) bool) *Value {
var selected []{1}
v.Each{4}(func(index int, val {1}) bool {
shouldSelect := decider(index, val)
if shouldSelect == false {
selected = append(selected, val)
}
return true
})
return &Value{data:selected}
}
// Group{4} uses the specified grouper function to group the items
// keyed by the return of the grouper. The object contained in the
// result will contain a map[string][]{1}.
func (v *Value) Group{4}(grouper func(int, {1}) string) *Value {
groups := make(map[string][]{1})
v.Each{4}(func(index int, val {1}) bool {
group := grouper(index, val)
if _, ok := groups[group]; !ok {
groups[group] = make([]{1}, 0)
}
groups[group] = append(groups[group], val)
return true
})
return &Value{data:groups}
}
// Replace{4} uses the specified function to replace each {1}s
// by iterating each item. The data in the returned result will be a
// []{1} containing the replaced items.
func (v *Value) Replace{4}(replacer func(int, {1}) {1}) *Value {
arr := v.Must{4}Slice()
replaced := make([]{1}, len(arr))
v.Each{4}(func(index int, val {1}) bool {
replaced[index] = replacer(index, val)
return true
})
return &Value{data:replaced}
}
// Collect{4} uses the specified collector function to collect a value
// for each of the {1}s in the slice. The data returned will be a
// []interface{}.
func (v *Value) Collect{4}(collector func(int, {1}) interface{}) *Value {
arr := v.Must{4}Slice()
collected := make([]interface{}, len(arr))
v.Each{4}(func(index int, val {1}) bool {
collected[index] = collector(index, val)
return true
})
return &Value{data:collected}
}
// ************************************************************
// TESTS
// ************************************************************
func Test{4}(t *testing.T) {
val := {1}( {2} )
m := map[string]interface{}{"value": val, "nothing": nil}
assert.Equal(t, val, New(m).Get("value").{4}())
assert.Equal(t, val, New(m).Get("value").Must{4}())
assert.Equal(t, {1}({3}), New(m).Get("nothing").{4}())
assert.Equal(t, val, New(m).Get("nothing").{4}({2}))
assert.Panics(t, func() {
New(m).Get("age").Must{4}()
})
}
func Test{4}Slice(t *testing.T) {
val := {1}( {2} )
m := map[string]interface{}{"value": []{1}{ val }, "nothing": nil}
assert.Equal(t, val, New(m).Get("value").{4}Slice()[0])
assert.Equal(t, val, New(m).Get("value").Must{4}Slice()[0])
assert.Equal(t, []{1}(nil), New(m).Get("nothing").{4}Slice())
assert.Equal(t, val, New(m).Get("nothing").{4}Slice( []{1}{ {1}({2}) } )[0])
assert.Panics(t, func() {
New(m).Get("nothing").Must{4}Slice()
})
}
func TestIs{4}(t *testing.T) {
var v *Value
v = &Value{data: {1}({2})}
assert.True(t, v.Is{4}())
v = &Value{data: []{1}{ {1}({2}) }}
assert.True(t, v.Is{4}Slice())
}
func TestEach{4}(t *testing.T) {
v := &Value{data: []{1}{ {1}({2}), {1}({2}), {1}({2}), {1}({2}), {1}({2}) }}
count := 0
replacedVals := make([]{1}, 0)
assert.Equal(t, v, v.Each{4}(func(i int, val {1}) bool {
count++
replacedVals = append(replacedVals, val)
// abort early
if i == 2 {
return false
}
return true
}))
assert.Equal(t, count, 3)
assert.Equal(t, replacedVals[0], v.Must{4}Slice()[0])
assert.Equal(t, replacedVals[1], v.Must{4}Slice()[1])
assert.Equal(t, replacedVals[2], v.Must{4}Slice()[2])
}
func TestWhere{4}(t *testing.T) {
v := &Value{data: []{1}{ {1}({2}), {1}({2}), {1}({2}), {1}({2}), {1}({2}), {1}({2}) }}
selected := v.Where{4}(func(i int, val {1}) bool {
return i%2==0
}).Must{4}Slice()
assert.Equal(t, 3, len(selected))
}
func TestGroup{4}(t *testing.T) {
v := &Value{data: []{1}{ {1}({2}), {1}({2}), {1}({2}), {1}({2}), {1}({2}), {1}({2}) }}
grouped := v.Group{4}(func(i int, val {1}) string {
return fmt.Sprintf("%v", i%2==0)
}).data.(map[string][]{1})
assert.Equal(t, 2, len(grouped))
assert.Equal(t, 3, len(grouped["true"]))
assert.Equal(t, 3, len(grouped["false"]))
}
func TestReplace{4}(t *testing.T) {
v := &Value{data: []{1}{ {1}({2}), {1}({2}), {1}({2}), {1}({2}), {1}({2}), {1}({2}) }}
rawArr := v.Must{4}Slice()
replaced := v.Replace{4}(func(index int, val {1}) {1} {
if index < len(rawArr)-1 {
return rawArr[index+1]
}
return rawArr[0]
})
replacedArr := replaced.Must{4}Slice()
if assert.Equal(t, 6, len(replacedArr)) {
assert.Equal(t, replacedArr[0], rawArr[1])
assert.Equal(t, replacedArr[1], rawArr[2])
assert.Equal(t, replacedArr[2], rawArr[3])
assert.Equal(t, replacedArr[3], rawArr[4])
assert.Equal(t, replacedArr[4], rawArr[5])
assert.Equal(t, replacedArr[5], rawArr[0])
}
}
func TestCollect{4}(t *testing.T) {
v := &Value{data: []{1}{ {1}({2}), {1}({2}), {1}({2}), {1}({2}), {1}({2}), {1}({2}) }}
collected := v.Collect{4}(func(index int, val {1}) interface{} {
return index
})
collectedArr := collected.MustInterSlice()
if assert.Equal(t, 6, len(collectedArr)) {
assert.Equal(t, collectedArr[0], 0)
assert.Equal(t, collectedArr[1], 1)
assert.Equal(t, collectedArr[2], 2)
assert.Equal(t, collectedArr[3], 3)
assert.Equal(t, collectedArr[4], 4)
assert.Equal(t, collectedArr[5], 5)
}
}

View File

@ -1,20 +0,0 @@
Interface,interface{},"something",nil,Inter
Map,map[string]interface{},map[string]interface{}{"name":"Tyler"},nil,MSI
ObjxMap,(Map),New(1),New(nil),ObjxMap
Bool,bool,true,false,Bool
String,string,"hello","",Str
Int,int,1,0,Int
Int8,int8,1,0,Int8
Int16,int16,1,0,Int16
Int32,int32,1,0,Int32
Int64,int64,1,0,Int64
Uint,uint,1,0,Uint
Uint8,uint8,1,0,Uint8
Uint16,uint16,1,0,Uint16
Uint32,uint32,1,0,Uint32
Uint64,uint64,1,0,Uint64
Uintptr,uintptr,1,0,Uintptr
Float32,float32,1,0,Float32
Float64,float64,1,0,Float64
Complex64,complex64,1,0,Complex64
Complex128,complex128,1,0,Complex128

View File

@ -1,13 +0,0 @@
package objx
const (
// PathSeparator is the character used to separate the elements
// of the keypath.
//
// For example, `location.address.city`
PathSeparator string = "."
// SignatureSeparator is the character that is used to
// separate the Base64 string from the security signature.
SignatureSeparator = "_"
)

View File

@ -1,117 +0,0 @@
package objx
import (
"bytes"
"encoding/base64"
"encoding/json"
"errors"
"fmt"
"net/url"
)
// JSON converts the contained object to a JSON string
// representation
func (m Map) JSON() (string, error) {
result, err := json.Marshal(m)
if err != nil {
err = errors.New("objx: JSON encode failed with: " + err.Error())
}
return string(result), err
}
// MustJSON converts the contained object to a JSON string
// representation and panics if there is an error
func (m Map) MustJSON() string {
result, err := m.JSON()
if err != nil {
panic(err.Error())
}
return result
}
// Base64 converts the contained object to a Base64 string
// representation of the JSON string representation
func (m Map) Base64() (string, error) {
var buf bytes.Buffer
jsonData, err := m.JSON()
if err != nil {
return "", err
}
encoder := base64.NewEncoder(base64.StdEncoding, &buf)
encoder.Write([]byte(jsonData))
encoder.Close()
return buf.String(), nil
}
// MustBase64 converts the contained object to a Base64 string
// representation of the JSON string representation and panics
// if there is an error
func (m Map) MustBase64() string {
result, err := m.Base64()
if err != nil {
panic(err.Error())
}
return result
}
// SignedBase64 converts the contained object to a Base64 string
// representation of the JSON string representation and signs it
// using the provided key.
func (m Map) SignedBase64(key string) (string, error) {
base64, err := m.Base64()
if err != nil {
return "", err
}
sig := HashWithKey(base64, key)
return base64 + SignatureSeparator + sig, nil
}
// MustSignedBase64 converts the contained object to a Base64 string
// representation of the JSON string representation and signs it
// using the provided key and panics if there is an error
func (m Map) MustSignedBase64(key string) string {
result, err := m.SignedBase64(key)
if err != nil {
panic(err.Error())
}
return result
}
/*
URL Query
------------------------------------------------
*/
// URLValues creates a url.Values object from an Obj. This
// function requires that the wrapped object be a map[string]interface{}
func (m Map) URLValues() url.Values {
vals := make(url.Values)
for k, v := range m {
//TODO: can this be done without sprintf?
vals.Set(k, fmt.Sprintf("%v", v))
}
return vals
}
// URLQuery gets an encoded URL query representing the given
// Obj. This function requires that the wrapped object be a
// map[string]interface{}
func (m Map) URLQuery() (string, error) {
return m.URLValues().Encode(), nil
}

View File

@ -1,72 +0,0 @@
// objx - Go package for dealing with maps, slices, JSON and other data.
//
// Overview
//
// Objx provides the `objx.Map` type, which is a `map[string]interface{}` that exposes
// a powerful `Get` method (among others) that allows you to easily and quickly get
// access to data within the map, without having to worry too much about type assertions,
// missing data, default values etc.
//
// Pattern
//
// Objx uses a preditable pattern to make access data from within `map[string]interface{}'s
// easy.
//
// Call one of the `objx.` functions to create your `objx.Map` to get going:
//
// m, err := objx.FromJSON(json)
//
// NOTE: Any methods or functions with the `Must` prefix will panic if something goes wrong,
// the rest will be optimistic and try to figure things out without panicking.
//
// Use `Get` to access the value you're interested in. You can use dot and array
// notation too:
//
// m.Get("places[0].latlng")
//
// Once you have saught the `Value` you're interested in, you can use the `Is*` methods
// to determine its type.
//
// if m.Get("code").IsStr() { /* ... */ }
//
// Or you can just assume the type, and use one of the strong type methods to
// extract the real value:
//
// m.Get("code").Int()
//
// If there's no value there (or if it's the wrong type) then a default value
// will be returned, or you can be explicit about the default value.
//
// Get("code").Int(-1)
//
// If you're dealing with a slice of data as a value, Objx provides many useful
// methods for iterating, manipulating and selecting that data. You can find out more
// by exploring the index below.
//
// Reading data
//
// A simple example of how to use Objx:
//
// // use MustFromJSON to make an objx.Map from some JSON
// m := objx.MustFromJSON(`{"name": "Mat", "age": 30}`)
//
// // get the details
// name := m.Get("name").Str()
// age := m.Get("age").Int()
//
// // get their nickname (or use their name if they
// // don't have one)
// nickname := m.Get("nickname").Str(name)
//
// Ranging
//
// Since `objx.Map` is a `map[string]interface{}` you can treat it as such. For
// example, to `range` the data, do what you would expect:
//
// m := objx.MustFromJSON(json)
// for key, value := range m {
//
// /* ... do your magic ... */
//
// }
package objx

View File

@ -1,222 +0,0 @@
package objx
import (
"encoding/base64"
"encoding/json"
"errors"
"io/ioutil"
"net/url"
"strings"
)
// MSIConvertable is an interface that defines methods for converting your
// custom types to a map[string]interface{} representation.
type MSIConvertable interface {
// MSI gets a map[string]interface{} (msi) representing the
// object.
MSI() map[string]interface{}
}
// Map provides extended functionality for working with
// untyped data, in particular map[string]interface (msi).
type Map map[string]interface{}
// Value returns the internal value instance
func (m Map) Value() *Value {
return &Value{data: m}
}
// Nil represents a nil Map.
var Nil Map = New(nil)
// New creates a new Map containing the map[string]interface{} in the data argument.
// If the data argument is not a map[string]interface, New attempts to call the
// MSI() method on the MSIConvertable interface to create one.
func New(data interface{}) Map {
if _, ok := data.(map[string]interface{}); !ok {
if converter, ok := data.(MSIConvertable); ok {
data = converter.MSI()
} else {
return nil
}
}
return Map(data.(map[string]interface{}))
}
// MSI creates a map[string]interface{} and puts it inside a new Map.
//
// The arguments follow a key, value pattern.
//
// Panics
//
// Panics if any key arugment is non-string or if there are an odd number of arguments.
//
// Example
//
// To easily create Maps:
//
// m := objx.MSI("name", "Mat", "age", 29, "subobj", objx.MSI("active", true))
//
// // creates an Map equivalent to
// m := objx.New(map[string]interface{}{"name": "Mat", "age": 29, "subobj": map[string]interface{}{"active": true}})
func MSI(keyAndValuePairs ...interface{}) Map {
newMap := make(map[string]interface{})
keyAndValuePairsLen := len(keyAndValuePairs)
if keyAndValuePairsLen%2 != 0 {
panic("objx: MSI must have an even number of arguments following the 'key, value' pattern.")
}
for i := 0; i < keyAndValuePairsLen; i = i + 2 {
key := keyAndValuePairs[i]
value := keyAndValuePairs[i+1]
// make sure the key is a string
keyString, keyStringOK := key.(string)
if !keyStringOK {
panic("objx: MSI must follow 'string, interface{}' pattern. " + keyString + " is not a valid key.")
}
newMap[keyString] = value
}
return New(newMap)
}
// ****** Conversion Constructors
// MustFromJSON creates a new Map containing the data specified in the
// jsonString.
//
// Panics if the JSON is invalid.
func MustFromJSON(jsonString string) Map {
o, err := FromJSON(jsonString)
if err != nil {
panic("objx: MustFromJSON failed with error: " + err.Error())
}
return o
}
// FromJSON creates a new Map containing the data specified in the
// jsonString.
//
// Returns an error if the JSON is invalid.
func FromJSON(jsonString string) (Map, error) {
var data interface{}
err := json.Unmarshal([]byte(jsonString), &data)
if err != nil {
return Nil, err
}
return New(data), nil
}
// FromBase64 creates a new Obj containing the data specified
// in the Base64 string.
//
// The string is an encoded JSON string returned by Base64
func FromBase64(base64String string) (Map, error) {
decoder := base64.NewDecoder(base64.StdEncoding, strings.NewReader(base64String))
decoded, err := ioutil.ReadAll(decoder)
if err != nil {
return nil, err
}
return FromJSON(string(decoded))
}
// MustFromBase64 creates a new Obj containing the data specified
// in the Base64 string and panics if there is an error.
//
// The string is an encoded JSON string returned by Base64
func MustFromBase64(base64String string) Map {
result, err := FromBase64(base64String)
if err != nil {
panic("objx: MustFromBase64 failed with error: " + err.Error())
}
return result
}
// FromSignedBase64 creates a new Obj containing the data specified
// in the Base64 string.
//
// The string is an encoded JSON string returned by SignedBase64
func FromSignedBase64(base64String, key string) (Map, error) {
parts := strings.Split(base64String, SignatureSeparator)
if len(parts) != 2 {
return nil, errors.New("objx: Signed base64 string is malformed.")
}
sig := HashWithKey(parts[0], key)
if parts[1] != sig {
return nil, errors.New("objx: Signature for base64 data does not match.")
}
return FromBase64(parts[0])
}
// MustFromSignedBase64 creates a new Obj containing the data specified
// in the Base64 string and panics if there is an error.
//
// The string is an encoded JSON string returned by Base64
func MustFromSignedBase64(base64String, key string) Map {
result, err := FromSignedBase64(base64String, key)
if err != nil {
panic("objx: MustFromSignedBase64 failed with error: " + err.Error())
}
return result
}
// FromURLQuery generates a new Obj by parsing the specified
// query.
//
// For queries with multiple values, the first value is selected.
func FromURLQuery(query string) (Map, error) {
vals, err := url.ParseQuery(query)
if err != nil {
return nil, err
}
m := make(map[string]interface{})
for k, vals := range vals {
m[k] = vals[0]
}
return New(m), nil
}
// MustFromURLQuery generates a new Obj by parsing the specified
// query.
//
// For queries with multiple values, the first value is selected.
//
// Panics if it encounters an error
func MustFromURLQuery(query string) Map {
o, err := FromURLQuery(query)
if err != nil {
panic("objx: MustFromURLQuery failed with error: " + err.Error())
}
return o
}

View File

@ -1,81 +0,0 @@
package objx
// Exclude returns a new Map with the keys in the specified []string
// excluded.
func (d Map) Exclude(exclude []string) Map {
excluded := make(Map)
for k, v := range d {
var shouldInclude bool = true
for _, toExclude := range exclude {
if k == toExclude {
shouldInclude = false
break
}
}
if shouldInclude {
excluded[k] = v
}
}
return excluded
}
// Copy creates a shallow copy of the Obj.
func (m Map) Copy() Map {
copied := make(map[string]interface{})
for k, v := range m {
copied[k] = v
}
return New(copied)
}
// Merge blends the specified map with a copy of this map and returns the result.
//
// Keys that appear in both will be selected from the specified map.
// This method requires that the wrapped object be a map[string]interface{}
func (m Map) Merge(merge Map) Map {
return m.Copy().MergeHere(merge)
}
// Merge blends the specified map with this map and returns the current map.
//
// Keys that appear in both will be selected from the specified map. The original map
// will be modified. This method requires that
// the wrapped object be a map[string]interface{}
func (m Map) MergeHere(merge Map) Map {
for k, v := range merge {
m[k] = v
}
return m
}
// Transform builds a new Obj giving the transformer a chance
// to change the keys and values as it goes. This method requires that
// the wrapped object be a map[string]interface{}
func (m Map) Transform(transformer func(key string, value interface{}) (string, interface{})) Map {
newMap := make(map[string]interface{})
for k, v := range m {
modifiedKey, modifiedVal := transformer(k, v)
newMap[modifiedKey] = modifiedVal
}
return New(newMap)
}
// TransformKeys builds a new map using the specified key mapping.
//
// Unspecified keys will be unaltered.
// This method requires that the wrapped object be a map[string]interface{}
func (m Map) TransformKeys(mapping map[string]string) Map {
return m.Transform(func(key string, value interface{}) (string, interface{}) {
if newKey, ok := mapping[key]; ok {
return newKey, value
}
return key, value
})
}

View File

@ -1,14 +0,0 @@
package objx
import (
"crypto/sha1"
"encoding/hex"
)
// HashWithKey hashes the specified string using the security
// key.
func HashWithKey(data, key string) string {
hash := sha1.New()
hash.Write([]byte(data + ":" + key))
return hex.EncodeToString(hash.Sum(nil))
}

View File

@ -1,17 +0,0 @@
package objx
// Has gets whether there is something at the specified selector
// or not.
//
// If m is nil, Has will always return false.
func (m Map) Has(selector string) bool {
if m == nil {
return false
}
return !m.Get(selector).IsNil()
}
// IsNil gets whether the data is nil or not.
func (v *Value) IsNil() bool {
return v == nil || v.data == nil
}

File diff suppressed because it is too large Load Diff

View File

@ -1,13 +0,0 @@
package objx
// Value provides methods for extracting interface{} data in various
// types.
type Value struct {
// data contains the raw data being managed by this Value
data interface{}
}
// Data returns the raw data contained by this Value
func (v *Value) Data() interface{} {
return v.data
}