mirror of
https://github.com/gravitational/teleport.git
synced 2026-10-12 02:15:01 +00:00
Remove vendor
- Remove the vendor directory - Update bot to stop accounting for vendor - Update linter config - Remove update-vendor make target
This commit is contained in:
committed by
Zac Bergquist
parent
4e324e74e3
commit
d0eb86191d
@@ -94,9 +94,6 @@ func (b *Bot) parseChanges(ctx context.Context) (bool, bool, error) {
|
||||
}
|
||||
|
||||
func hasDocs(filename string) bool {
|
||||
if strings.HasPrefix(filename, "vendor/") {
|
||||
return false
|
||||
}
|
||||
return strings.HasPrefix(filename, "docs/") ||
|
||||
strings.HasSuffix(filename, ".md") ||
|
||||
strings.HasSuffix(filename, ".mdx") ||
|
||||
|
||||
@@ -62,11 +62,6 @@ func (b *Bot) labels(ctx context.Context) ([]string, error) {
|
||||
}
|
||||
|
||||
for _, file := range files {
|
||||
// Special case: skip vendor until it is removed in #9545.
|
||||
if strings.HasPrefix(file, "vendor/") {
|
||||
continue
|
||||
}
|
||||
|
||||
for k, v := range prefixes {
|
||||
if strings.HasPrefix(file, k) {
|
||||
log.Printf("Label: Found prefix %v, attaching labels: %v.", k, v)
|
||||
|
||||
@@ -32,7 +32,5 @@ output:
|
||||
uniq-by-line: false
|
||||
|
||||
run:
|
||||
skip-dirs:
|
||||
- vendor
|
||||
skip-dirs-use-default: false
|
||||
timeout: 5m
|
||||
|
||||
+1
-1
@@ -19,4 +19,4 @@ If your patch depends on new packages, the dependencies must:
|
||||
|
||||
- be licensed via Apache2 license
|
||||
- be approved by core Teleport contributors ahead of time
|
||||
- a dependency package must be vendored as a [Go module](https://blog.golang.org/using-go-modules)
|
||||
- a dependency package must be a [Go module](https://blog.golang.org/using-go-modules)
|
||||
|
||||
@@ -464,7 +464,7 @@ test: test-sh test-api test-go
|
||||
test-go: ensure-webassets bpf-bytecode roletester rdpclient $(RENDER_TESTS)
|
||||
test-go: FLAGS ?= '-race'
|
||||
test-go: PACKAGES := $(shell go list ./... | grep -v integration)
|
||||
test-go: CHAOS_FOLDERS := $(shell find . -type f -name '*chaos*.go' -not -path '*/vendor/*' | xargs dirname | uniq)
|
||||
test-go: CHAOS_FOLDERS := $(shell find . -type f -name '*chaos*.go' | xargs dirname | uniq)
|
||||
test-go: $(VERSRC)
|
||||
$(CGOFLAG) go test -cover -json -tags "$(PAM_TAG) $(FIPS_TAG) $(BPF_TAG) $(ROLETESTER_TAG) $(RDPCLIENT_TAG)" $(PACKAGES) $(FLAGS) $(ADDFLAGS) \
|
||||
| ${RENDER_TESTS}
|
||||
@@ -560,7 +560,7 @@ lint-api:
|
||||
.PHONY: lint-sh
|
||||
lint-sh: SH_LINT_FLAGS ?=
|
||||
lint-sh:
|
||||
find . -type f -name '*.sh' | grep -v vendor | xargs \
|
||||
find . -type f -name '*.sh' | xargs \
|
||||
shellcheck \
|
||||
--exclude=SC2086 \
|
||||
$(SH_LINT_FLAGS)
|
||||
@@ -620,7 +620,6 @@ ADDLICENSE_ARGS := -c 'Gravitational, Inc' -l apache \
|
||||
-ignore 'e/**' \
|
||||
-ignore 'gitref.go' \
|
||||
-ignore 'lib/web/build/**' \
|
||||
-ignore 'vendor/**' \
|
||||
-ignore 'version.go' \
|
||||
-ignore 'webassets/**' \
|
||||
-ignore 'ignoreme' \
|
||||
@@ -661,7 +660,6 @@ $(VERSRC): Makefile
|
||||
.PHONY: update-api-module-path
|
||||
update-api-module-path:
|
||||
go run build.assets/update_api_module_path/main.go -tags "bpf fips pam roletester desktop_access_rdp"
|
||||
$(MAKE) update-vendor
|
||||
$(MAKE) grpc
|
||||
|
||||
# make tag - prints a tag to use with git for the current version
|
||||
@@ -711,7 +709,7 @@ profile:
|
||||
|
||||
.PHONY: sloccount
|
||||
sloccount:
|
||||
find . -path ./vendor -prune -o -name "*.go" -print0 | xargs -0 wc -l
|
||||
find . -o -name "*.go" -print0 | xargs -0 wc -l
|
||||
|
||||
.PHONY: remove-temp-files
|
||||
remove-temp-files:
|
||||
@@ -942,31 +940,6 @@ init-submodules-e: init-webapps-submodules-e
|
||||
git submodule init e
|
||||
git submodule update
|
||||
|
||||
# Update go.mod and vendor files.
|
||||
.PHONY: update-vendor
|
||||
update-vendor:
|
||||
# update modules in api/
|
||||
cd api && go mod tidy
|
||||
# update modules in root directory
|
||||
go mod tidy
|
||||
go mod vendor
|
||||
$(MAKE) vendor-api
|
||||
|
||||
# When teleport vendors its dependencies, Go also vendors the local api sub module. To get
|
||||
# around this issue, we replace the vendored api package with a symlink to the
|
||||
# local module. The symlink should be in vendor/.../api or vendor/.../api/vX if X >= 2.
|
||||
.PHONY: vendor-api
|
||||
vendor-api: API_VENDOR_PATH := vendor/$(shell head -1 api/go.mod | awk '{print $$2;}')
|
||||
vendor-api:
|
||||
rm -rf vendor/github.com/gravitational/teleport/api
|
||||
mkdir -p $(shell dirname $(API_VENDOR_PATH))
|
||||
# make a relative link to the true api dir (without using `ln -r` for non-linux OS compatibility)
|
||||
if [ -d $(shell dirname $(API_VENDOR_PATH))/../../../../../api/ ]; then \
|
||||
ln -s ../../../../../api $(API_VENDOR_PATH); \
|
||||
else \
|
||||
ln -s ../../../../api $(API_VENDOR_PATH); \
|
||||
fi;
|
||||
|
||||
# update-webassets updates the minified code in the webassets repo using the latest webapps
|
||||
# repo and creates a PR in the teleport repo to update webassets submodule.
|
||||
.PHONY: update-webassets
|
||||
|
||||
@@ -35,7 +35,7 @@
|
||||
|
||||
## Introduction
|
||||
|
||||
Teleport is the easiest, most secure way to access all your infrastructure.
|
||||
Teleport is the easiest, most secure way to access all your infrastructure.
|
||||
Teleport is an identity-aware, multi-protocol access proxy which understands
|
||||
SSH, HTTPS, RDP, Kubernetes API, MySQL, MongoDB and PostgreSQL wire protocols.
|
||||
|
||||
@@ -209,43 +209,27 @@ Latest version:
|
||||
go get github.com/new/dependency
|
||||
```
|
||||
|
||||
Update the source to use this dependency, then run:
|
||||
and update the source to use this dependency.
|
||||
|
||||
```bash
|
||||
make update-vendor
|
||||
```
|
||||
|
||||
Specific version:
|
||||
|
||||
```bash
|
||||
go get github.com/new/dependency@version
|
||||
```
|
||||
|
||||
Update the source to use this dependency, then run:
|
||||
|
||||
```bash
|
||||
make update-vendor
|
||||
```
|
||||
To get a specific version, use `go get github.com/new/dependency@version` instead.
|
||||
|
||||
#### Set dependency to a specific version
|
||||
|
||||
```bash
|
||||
go get github.com/new/dependency@version
|
||||
make update-vendor
|
||||
```
|
||||
|
||||
#### Update dependency to the latest version
|
||||
|
||||
```bash
|
||||
go get -u github.com/new/dependency
|
||||
make update-vendor
|
||||
```
|
||||
|
||||
#### Update all dependencies
|
||||
|
||||
```bash
|
||||
go get -u all
|
||||
make update-vendor
|
||||
```
|
||||
|
||||
#### Debugging dependencies
|
||||
@@ -297,5 +281,5 @@ You can find the latest stable Teleport build on our [Releases](https://gotelepo
|
||||
## Who Built Teleport?
|
||||
|
||||
Teleport was created by [Gravitational Inc](https://goteleport.com). We have
|
||||
built Teleport by borrowing from our previous experiences at Rackspace. [Learn more
|
||||
built Teleport by borrowing from our previous experiences at Rackspace. [Learn more
|
||||
about Teleport and our history](https://goteleport.com/about/).
|
||||
@@ -131,9 +131,7 @@ RUN (git clone https://github.com/gogo/protobuf.git ${GOPATH}/src/github.com/gog
|
||||
git reset --hard ${GOGO_PROTO_TAG} && \
|
||||
make install)
|
||||
|
||||
# Note: protoc reads the proto files from /vendor/.../teleport/api/vX rather than
|
||||
# /api because protoc does not understand go modules, and reads vX as a directory.
|
||||
ENV PROTO_INCLUDE "/usr/local/include":"/go/src/github.com/gravitational/teleport/vendor":"/go/src/github.com/gogo/protobuf/protobuf":"${GOGOPROTO_ROOT}":"${GOGOPROTO_ROOT}/protobuf"
|
||||
ENV PROTO_INCLUDE "/usr/local/include":"/go/src":"/go/src/github.com/gogo/protobuf/protobuf":"${GOGOPROTO_ROOT}":"${GOGOPROTO_ROOT}/protobuf"
|
||||
|
||||
# Install PAM module and policies for testing.
|
||||
COPY pam/ /opt/pam_teleport/
|
||||
|
||||
@@ -59,9 +59,7 @@ RUN (git clone https://github.com/gogo/protobuf.git ${GOPATH}/src/github.com/gog
|
||||
git reset --hard ${GOGO_PROTO_TAG} && \
|
||||
make install)
|
||||
|
||||
# Note: protoc reads the proto files from /vendor/.../teleport/api/vX rather than
|
||||
# /api because protoc does not understand go modules, and reads vX as a directory.
|
||||
ENV PROTO_INCLUDE "/usr/local/include":"/go/src/github.com/gravitational/teleport/vendor":"/go/src/github.com/gogo/protobuf/protobuf":"${GOGOPROTO_ROOT}":"${GOGOPROTO_ROOT}/protobuf"
|
||||
ENV PROTO_INCLUDE "/usr/local/include":"/go/src":"/go/src/github.com/gogo/protobuf/protobuf":"${GOGOPROTO_ROOT}":"${GOGOPROTO_ROOT}/protobuf"
|
||||
|
||||
USER root
|
||||
VOLUME ["/go/src/github.com/gravitational/teleport"]
|
||||
|
||||
+2
-2
@@ -2,9 +2,9 @@
|
||||
|
||||
This checklist is to be run prior to cutting the release branch.
|
||||
|
||||
- [ ] Bump Golang vendor dependencies
|
||||
- [ ] Bump Golang dependencies
|
||||
- [ ] Review forked dependencies for upstream security patches
|
||||
- [ ] Bump Web UI vendor dependencies
|
||||
- [ ] Bump Web UI dependencies
|
||||
- [ ] Make a new docs/VERSION folder
|
||||
- [ ] Update VERSION in Makefile to next dev tag
|
||||
- [ ] Update TELEPORT_VERSION in assets/aws/Makefile
|
||||
|
||||
Generated
-11
@@ -1,11 +0,0 @@
|
||||
# Editors
|
||||
.idea
|
||||
.vscode
|
||||
*.swp
|
||||
|
||||
# Test files
|
||||
*.test
|
||||
coverage.txt
|
||||
|
||||
# Other
|
||||
.DS_Store
|
||||
-1553
File diff suppressed because it is too large
Load Diff
-44
@@ -1,44 +0,0 @@
|
||||
# Contributor Code of Conduct
|
||||
|
||||
As contributors and maintainers of this project,
|
||||
and in the interest of fostering an open and welcoming community,
|
||||
we pledge to respect all people who contribute through reporting issues,
|
||||
posting feature requests, updating documentation,
|
||||
submitting pull requests or patches, and other activities.
|
||||
|
||||
We are committed to making participation in this project
|
||||
a harassment-free experience for everyone,
|
||||
regardless of level of experience, gender, gender identity and expression,
|
||||
sexual orientation, disability, personal appearance,
|
||||
body size, race, ethnicity, age, religion, or nationality.
|
||||
|
||||
Examples of unacceptable behavior by participants include:
|
||||
|
||||
* The use of sexualized language or imagery
|
||||
* Personal attacks
|
||||
* Trolling or insulting/derogatory comments
|
||||
* Public or private harassment
|
||||
* Publishing other's private information,
|
||||
such as physical or electronic
|
||||
addresses, without explicit permission
|
||||
* Other unethical or unprofessional conduct.
|
||||
|
||||
Project maintainers have the right and responsibility to remove, edit, or reject
|
||||
comments, commits, code, wiki edits, issues, and other contributions
|
||||
that are not aligned to this Code of Conduct.
|
||||
By adopting this Code of Conduct,
|
||||
project maintainers commit themselves to fairly and consistently
|
||||
applying these principles to every aspect of managing this project.
|
||||
Project maintainers who do not follow or enforce the Code of Conduct
|
||||
may be permanently removed from the project team.
|
||||
|
||||
This code of conduct applies both within project spaces and in public spaces
|
||||
when an individual is representing the project or its community.
|
||||
|
||||
Instances of abusive, harassing, or otherwise unacceptable behavior
|
||||
may be reported by opening an issue
|
||||
or contacting one or more of the project maintainers.
|
||||
|
||||
This Code of Conduct is adapted from the [Contributor Covenant](http://contributor-covenant.org), version 1.2.0,
|
||||
available at [http://contributor-covenant.org/version/1/2/0/](http://contributor-covenant.org/version/1/2/0/)
|
||||
|
||||
-250
@@ -1,250 +0,0 @@
|
||||
# Contributing
|
||||
|
||||
1. [File an issue](https://github.com/googleapis/google-cloud-go/issues/new/choose).
|
||||
The issue will be used to discuss the bug or feature and should be created
|
||||
before sending a CL.
|
||||
|
||||
1. [Install Go](https://golang.org/dl/).
|
||||
1. Ensure that your `GOBIN` directory (by default `$(go env GOPATH)/bin`)
|
||||
is in your `PATH`.
|
||||
1. Check it's working by running `go version`.
|
||||
* If it doesn't work, check the install location, usually
|
||||
`/usr/local/go`, is on your `PATH`.
|
||||
|
||||
1. Sign one of the
|
||||
[contributor license agreements](#contributor-license-agreements) below.
|
||||
|
||||
1. Clone the repo:
|
||||
`git clone https://github.com/googleapis/google-cloud-go`
|
||||
|
||||
1. Change into the checked out source:
|
||||
`cd google-cloud-go`
|
||||
|
||||
1. Fork the repo.
|
||||
|
||||
1. Set your fork as a remote:
|
||||
`git remote add fork [email protected]:GITHUB_USERNAME/google-cloud-go.git`
|
||||
|
||||
1. Make changes (see [Formatting](#formatting) and [Style](#style)), commit to
|
||||
your fork.
|
||||
|
||||
Commit messages should follow the
|
||||
[Go project style](https://github.com/golang/go/wiki/CommitMessage). For example:
|
||||
```
|
||||
functions: add gophers codelab
|
||||
```
|
||||
|
||||
1. Send a pull request with your changes.
|
||||
|
||||
1. A maintainer will review the pull request and make comments.
|
||||
|
||||
Prefer adding additional commits over amending and force-pushing since it can
|
||||
be difficult to follow code reviews when the commit history changes.
|
||||
|
||||
Commits will be squashed when they're merged.
|
||||
|
||||
## Integration Tests
|
||||
|
||||
In addition to the unit tests, you may run the integration test suite. These
|
||||
directions describe setting up your environment to run integration tests for
|
||||
_all_ packages: note that many of these instructions may be redundant if you
|
||||
intend only to run integration tests on a single package.
|
||||
|
||||
#### GCP Setup
|
||||
|
||||
To run the integrations tests, creation and configuration of two projects in
|
||||
the Google Developers Console is required: one specifically for Firestore
|
||||
integration tests, and another for all other integration tests. We'll refer to
|
||||
these projects as "general project" and "Firestore project".
|
||||
|
||||
After creating each project, you must [create a service account](https://developers.google.com/identity/protocols/OAuth2ServiceAccount#creatinganaccount)
|
||||
for each project. Ensure the project-level **Owner**
|
||||
[IAM role](console.cloud.google.com/iam-admin/iam/project) role is added to
|
||||
each service account. During the creation of the service account, you should
|
||||
download the JSON credential file for use later.
|
||||
|
||||
Next, ensure the following APIs are enabled in the general project:
|
||||
|
||||
- BigQuery API
|
||||
- BigQuery Data Transfer API
|
||||
- Cloud Dataproc API
|
||||
- Cloud Dataproc Control API Private
|
||||
- Cloud Datastore API
|
||||
- Cloud Firestore API
|
||||
- Cloud Key Management Service (KMS) API
|
||||
- Cloud Natural Language API
|
||||
- Cloud OS Login API
|
||||
- Cloud Pub/Sub API
|
||||
- Cloud Resource Manager API
|
||||
- Cloud Spanner API
|
||||
- Cloud Speech API
|
||||
- Cloud Translation API
|
||||
- Cloud Video Intelligence API
|
||||
- Cloud Vision API
|
||||
- Compute Engine API
|
||||
- Compute Engine Instance Group Manager API
|
||||
- Container Registry API
|
||||
- Firebase Rules API
|
||||
- Google Cloud APIs
|
||||
- Google Cloud Deployment Manager V2 API
|
||||
- Google Cloud SQL
|
||||
- Google Cloud Storage
|
||||
- Google Cloud Storage JSON API
|
||||
- Google Compute Engine Instance Group Updater API
|
||||
- Google Compute Engine Instance Groups API
|
||||
- Kubernetes Engine API
|
||||
- Stackdriver Error Reporting API
|
||||
|
||||
Next, create a Datastore database in the general project, and a Firestore
|
||||
database in the Firestore project.
|
||||
|
||||
Finally, in the general project, create an API key for the translate API:
|
||||
|
||||
- Go to GCP Developer Console.
|
||||
- Navigate to APIs & Services > Credentials.
|
||||
- Click Create Credentials > API Key.
|
||||
- Save this key for use in `GCLOUD_TESTS_API_KEY` as described below.
|
||||
|
||||
#### Local Setup
|
||||
|
||||
Once the two projects are created and configured, set the following environment
|
||||
variables:
|
||||
|
||||
- `GCLOUD_TESTS_GOLANG_PROJECT_ID`: Developers Console project's ID (e.g.
|
||||
bamboo-shift-455) for the general project.
|
||||
- `GCLOUD_TESTS_GOLANG_KEY`: The path to the JSON key file of the general
|
||||
project's service account.
|
||||
- `GCLOUD_TESTS_GOLANG_FIRESTORE_PROJECT_ID`: Developers Console project's ID
|
||||
(e.g. doorway-cliff-677) for the Firestore project.
|
||||
- `GCLOUD_TESTS_GOLANG_FIRESTORE_KEY`: The path to the JSON key file of the
|
||||
Firestore project's service account.
|
||||
- `GCLOUD_TESTS_GOLANG_KEYRING`: The full name of the keyring for the tests,
|
||||
in the form
|
||||
"projects/P/locations/L/keyRings/R". The creation of this is described below.
|
||||
- `GCLOUD_TESTS_API_KEY`: API key for using the Translate API.
|
||||
- `GCLOUD_TESTS_GOLANG_ZONE`: Compute Engine zone.
|
||||
|
||||
Install the [gcloud command-line tool][gcloudcli] to your machine and use it to
|
||||
create some resources used in integration tests.
|
||||
|
||||
From the project's root directory:
|
||||
|
||||
``` sh
|
||||
# Sets the default project in your env.
|
||||
$ gcloud config set project $GCLOUD_TESTS_GOLANG_PROJECT_ID
|
||||
|
||||
# Authenticates the gcloud tool with your account.
|
||||
$ gcloud auth login
|
||||
|
||||
# Create the indexes used in the datastore integration tests.
|
||||
$ gcloud datastore indexes create datastore/testdata/index.yaml
|
||||
|
||||
# Creates a Google Cloud storage bucket with the same name as your test project,
|
||||
# and with the Stackdriver Logging service account as owner, for the sink
|
||||
# integration tests in logging.
|
||||
$ gsutil mb gs://$GCLOUD_TESTS_GOLANG_PROJECT_ID
|
||||
$ gsutil acl ch -g [email protected]:O gs://$GCLOUD_TESTS_GOLANG_PROJECT_ID
|
||||
|
||||
# Creates a PubSub topic for integration tests of storage notifications.
|
||||
$ gcloud beta pubsub topics create go-storage-notification-test
|
||||
# Next, go to the Pub/Sub dashboard in GCP console. Authorize the user
|
||||
# "service-<numberic project id>@gs-project-accounts.iam.gserviceaccount.com"
|
||||
# as a publisher to that topic.
|
||||
|
||||
# Creates a Spanner instance for the spanner integration tests.
|
||||
$ gcloud beta spanner instances create go-integration-test --config regional-us-central1 --nodes 10 --description 'Instance for go client test'
|
||||
# NOTE: Spanner instances are priced by the node-hour, so you may want to
|
||||
# delete the instance after testing with 'gcloud beta spanner instances delete'.
|
||||
|
||||
$ export MY_KEYRING=some-keyring-name
|
||||
$ export MY_LOCATION=global
|
||||
# Creates a KMS keyring, in the same location as the default location for your
|
||||
# project's buckets.
|
||||
$ gcloud kms keyrings create $MY_KEYRING --location $MY_LOCATION
|
||||
# Creates two keys in the keyring, named key1 and key2.
|
||||
$ gcloud kms keys create key1 --keyring $MY_KEYRING --location $MY_LOCATION --purpose encryption
|
||||
$ gcloud kms keys create key2 --keyring $MY_KEYRING --location $MY_LOCATION --purpose encryption
|
||||
# Sets the GCLOUD_TESTS_GOLANG_KEYRING environment variable.
|
||||
$ export GCLOUD_TESTS_GOLANG_KEYRING=projects/$GCLOUD_TESTS_GOLANG_PROJECT_ID/locations/$MY_LOCATION/keyRings/$MY_KEYRING
|
||||
# Authorizes Google Cloud Storage to encrypt and decrypt using key1.
|
||||
gsutil kms authorize -p $GCLOUD_TESTS_GOLANG_PROJECT_ID -k $GCLOUD_TESTS_GOLANG_KEYRING/cryptoKeys/key1
|
||||
```
|
||||
|
||||
#### Running
|
||||
|
||||
Once you've done the necessary setup, you can run the integration tests by
|
||||
running:
|
||||
|
||||
``` sh
|
||||
$ go test -v cloud.google.com/go/...
|
||||
```
|
||||
|
||||
#### Replay
|
||||
|
||||
Some packages can record the RPCs during integration tests to a file for
|
||||
subsequent replay. To record, pass the `-record` flag to `go test`. The
|
||||
recording will be saved to the _package_`.replay` file. To replay integration
|
||||
tests from a saved recording, the replay file must be present, the `-short`
|
||||
flag must be passed to `go test`, and the `GCLOUD_TESTS_GOLANG_ENABLE_REPLAY`
|
||||
environment variable must have a non-empty value.
|
||||
|
||||
## Contributor License Agreements
|
||||
|
||||
Before we can accept your pull requests you'll need to sign a Contributor
|
||||
License Agreement (CLA):
|
||||
|
||||
- **If you are an individual writing original source code** and **you own the
|
||||
intellectual property**, then you'll need to sign an [individual CLA][indvcla].
|
||||
- **If you work for a company that wants to allow you to contribute your
|
||||
work**, then you'll need to sign a [corporate CLA][corpcla].
|
||||
|
||||
You can sign these electronically (just scroll to the bottom). After that,
|
||||
we'll be able to accept your pull requests.
|
||||
|
||||
## Contributor Code of Conduct
|
||||
|
||||
As contributors and maintainers of this project,
|
||||
and in the interest of fostering an open and welcoming community,
|
||||
we pledge to respect all people who contribute through reporting issues,
|
||||
posting feature requests, updating documentation,
|
||||
submitting pull requests or patches, and other activities.
|
||||
|
||||
We are committed to making participation in this project
|
||||
a harassment-free experience for everyone,
|
||||
regardless of level of experience, gender, gender identity and expression,
|
||||
sexual orientation, disability, personal appearance,
|
||||
body size, race, ethnicity, age, religion, or nationality.
|
||||
|
||||
Examples of unacceptable behavior by participants include:
|
||||
|
||||
* The use of sexualized language or imagery
|
||||
* Personal attacks
|
||||
* Trolling or insulting/derogatory comments
|
||||
* Public or private harassment
|
||||
* Publishing other's private information,
|
||||
such as physical or electronic
|
||||
addresses, without explicit permission
|
||||
* Other unethical or unprofessional conduct.
|
||||
|
||||
Project maintainers have the right and responsibility to remove, edit, or reject
|
||||
comments, commits, code, wiki edits, issues, and other contributions
|
||||
that are not aligned to this Code of Conduct.
|
||||
By adopting this Code of Conduct,
|
||||
project maintainers commit themselves to fairly and consistently
|
||||
applying these principles to every aspect of managing this project.
|
||||
Project maintainers who do not follow or enforce the Code of Conduct
|
||||
may be permanently removed from the project team.
|
||||
|
||||
This code of conduct applies both within project spaces and in public spaces
|
||||
when an individual is representing the project or its community.
|
||||
|
||||
Instances of abusive, harassing, or otherwise unacceptable behavior
|
||||
may be reported by opening an issue
|
||||
or contacting one or more of the project maintainers.
|
||||
|
||||
This Code of Conduct is adapted from the [Contributor Covenant](http://contributor-covenant.org), version 1.2.0,
|
||||
available at [http://contributor-covenant.org/version/1/2/0/](http://contributor-covenant.org/version/1/2/0/)
|
||||
|
||||
[gcloudcli]: https://developers.google.com/cloud/sdk/gcloud/
|
||||
[indvcla]: https://developers.google.com/open-source/cla/individual
|
||||
[corpcla]: https://developers.google.com/open-source/cla/corporate
|
||||
-202
@@ -1,202 +0,0 @@
|
||||
|
||||
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 [yyyy] [name of copyright owner]
|
||||
|
||||
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.
|
||||
-178
@@ -1,178 +0,0 @@
|
||||
# Google Cloud Client Libraries for Go
|
||||
|
||||
[](https://pkg.go.dev/cloud.google.com/go)
|
||||
|
||||
Go packages for [Google Cloud Platform](https://cloud.google.com) services.
|
||||
|
||||
``` go
|
||||
import "cloud.google.com/go"
|
||||
```
|
||||
|
||||
To install the packages on your system, *do not clone the repo*. Instead:
|
||||
|
||||
1. Change to your project directory:
|
||||
|
||||
```
|
||||
cd /my/cloud/project
|
||||
```
|
||||
1. Get the package you want to use. Some products have their own module, so it's
|
||||
best to `go get` the package(s) you want to use:
|
||||
|
||||
```
|
||||
$ go get cloud.google.com/go/firestore # Replace with the package you want to use.
|
||||
```
|
||||
|
||||
**NOTE:** Some of these packages are under development, and may occasionally
|
||||
make backwards-incompatible changes.
|
||||
|
||||
**NOTE:** Github repo is a mirror of [https://code.googlesource.com/gocloud](https://code.googlesource.com/gocloud).
|
||||
|
||||
## Supported APIs
|
||||
|
||||
Google API | Status | Package
|
||||
------------------------------------------------|--------------|-----------------------------------------------------------
|
||||
[Asset][cloud-asset] | stable | [`cloud.google.com/go/asset/apiv1`](https://pkg.go.dev/cloud.google.com/go/asset/v1beta)
|
||||
[Automl][cloud-automl] | stable | [`cloud.google.com/go/automl/apiv1`](https://pkg.go.dev/cloud.google.com/go/automl/apiv1)
|
||||
[BigQuery][cloud-bigquery] | stable | [`cloud.google.com/go/bigquery`](https://pkg.go.dev/cloud.google.com/go/bigquery)
|
||||
[Bigtable][cloud-bigtable] | stable | [`cloud.google.com/go/bigtable`](https://pkg.go.dev/cloud.google.com/go/bigtable)
|
||||
[Cloudbuild][cloud-build] | stable | [`cloud.google.com/go/cloudbuild/apiv1`](https://pkg.go.dev/cloud.google.com/go/cloudbuild/apiv1)
|
||||
[Cloudtasks][cloud-tasks] | stable | [`cloud.google.com/go/cloudtasks/apiv2`](https://pkg.go.dev/cloud.google.com/go/cloudtasks/apiv2)
|
||||
[Container][cloud-container] | stable | [`cloud.google.com/go/container/apiv1`](https://pkg.go.dev/cloud.google.com/go/container/apiv1)
|
||||
[ContainerAnalysis][cloud-containeranalysis] | beta | [`cloud.google.com/go/containeranalysis/apiv1`](https://pkg.go.dev/cloud.google.com/go/containeranalysis/apiv1)
|
||||
[Dataproc][cloud-dataproc] | stable | [`cloud.google.com/go/dataproc/apiv1`](https://pkg.go.dev/cloud.google.com/go/dataproc/apiv1)
|
||||
[Datastore][cloud-datastore] | stable | [`cloud.google.com/go/datastore`](https://pkg.go.dev/cloud.google.com/go/datastore)
|
||||
[Debugger][cloud-debugger] | stable | [`cloud.google.com/go/debugger/apiv2`](https://pkg.go.dev/cloud.google.com/go/debugger/apiv2)
|
||||
[Dialogflow][cloud-dialogflow] | stable | [`cloud.google.com/go/dialogflow/apiv2`](https://pkg.go.dev/cloud.google.com/go/dialogflow/apiv2)
|
||||
[Data Loss Prevention][cloud-dlp] | stable | [`cloud.google.com/go/dlp/apiv2`](https://pkg.go.dev/cloud.google.com/go/dlp/apiv2)
|
||||
[ErrorReporting][cloud-errors] | alpha | [`cloud.google.com/go/errorreporting`](https://pkg.go.dev/cloud.google.com/go/errorreporting)
|
||||
[Firestore][cloud-firestore] | stable | [`cloud.google.com/go/firestore`](https://pkg.go.dev/cloud.google.com/go/firestore)
|
||||
[IAM][cloud-iam] | stable | [`cloud.google.com/go/iam`](https://pkg.go.dev/cloud.google.com/go/iam)
|
||||
[IoT][cloud-iot] | stable | [`cloud.google.com/go/iot/apiv1`](https://pkg.go.dev/cloud.google.com/go/iot/apiv1)
|
||||
[IRM][cloud-irm] | alpha | [`cloud.google.com/go/irm/apiv1alpha2`](https://pkg.go.dev/cloud.google.com/go/irm/apiv1alpha2)
|
||||
[KMS][cloud-kms] | stable | [`cloud.google.com/go/kms/apiv1`](https://pkg.go.dev/cloud.google.com/go/kms/apiv1)
|
||||
[Natural Language][cloud-natural-language] | stable | [`cloud.google.com/go/language/apiv1`](https://pkg.go.dev/cloud.google.com/go/language/apiv1)
|
||||
[Logging][cloud-logging] | stable | [`cloud.google.com/go/logging`](https://pkg.go.dev/cloud.google.com/go/logging)
|
||||
[Memorystore][cloud-memorystore] | alpha | [`cloud.google.com/go/redis/apiv1`](https://pkg.go.dev/cloud.google.com/go/redis/apiv1)
|
||||
[Monitoring][cloud-monitoring] | stable | [`cloud.google.com/go/monitoring/apiv3`](https://pkg.go.dev/cloud.google.com/go/monitoring/apiv3)
|
||||
[OS Login][cloud-oslogin] | stable | [`cloud.google.com/go/oslogin/apiv1`](https://pkg.go.dev/cloud.google.com/go/oslogin/apiv1)
|
||||
[Pub/Sub][cloud-pubsub] | stable | [`cloud.google.com/go/pubsub`](https://pkg.go.dev/cloud.google.com/go/pubsub)
|
||||
[Phishing Protection][cloud-phishingprotection] | alpha | [`cloud.google.com/go/phishingprotection/apiv1beta1`](https://pkg.go.dev/cloud.google.com/go/phishingprotection/apiv1beta1)
|
||||
[reCAPTCHA Enterprise][cloud-recaptcha] | alpha | [`cloud.google.com/go/recaptchaenterprise/apiv1beta1`](https://pkg.go.dev/cloud.google.com/go/recaptchaenterprise/apiv1beta1)
|
||||
[Recommender][cloud-recommender] | beta | [`cloud.google.com/go/recommender/apiv1beta1`](https://pkg.go.dev/cloud.google.com/go/recommender/apiv1beta1)
|
||||
[Scheduler][cloud-scheduler] | stable | [`cloud.google.com/go/scheduler/apiv1`](https://pkg.go.dev/cloud.google.com/go/scheduler/apiv1)
|
||||
[Securitycenter][cloud-securitycenter] | stable | [`cloud.google.com/go/securitycenter/apiv1`](https://pkg.go.dev/cloud.google.com/go/securitycenter/apiv1)
|
||||
[Spanner][cloud-spanner] | stable | [`cloud.google.com/go/spanner`](https://pkg.go.dev/cloud.google.com/go/spanner)
|
||||
[Speech][cloud-speech] | stable | [`cloud.google.com/go/speech/apiv1`](https://pkg.go.dev/cloud.google.com/go/speech/apiv1)
|
||||
[Storage][cloud-storage] | stable | [`cloud.google.com/go/storage`](https://pkg.go.dev/cloud.google.com/go/storage)
|
||||
[Talent][cloud-talent] | alpha | [`cloud.google.com/go/talent/apiv4beta1`](https://pkg.go.dev/cloud.google.com/go/talent/apiv4beta1)
|
||||
[Text To Speech][cloud-texttospeech] | stable | [`cloud.google.com/go/texttospeech/apiv1`](https://pkg.go.dev/cloud.google.com/go/texttospeech/apiv1)
|
||||
[Trace][cloud-trace] | stable | [`cloud.google.com/go/trace/apiv2`](https://pkg.go.dev/cloud.google.com/go/trace/apiv2)
|
||||
[Translate][cloud-translate] | stable | [`cloud.google.com/go/translate`](https://pkg.go.dev/cloud.google.com/go/translate)
|
||||
[Video Intelligence][cloud-video] | beta | [`cloud.google.com/go/videointelligence/apiv1beta2`](https://pkg.go.dev/cloud.google.com/go/videointelligence/apiv1beta2)
|
||||
[Vision][cloud-vision] | stable | [`cloud.google.com/go/vision/apiv1`](https://pkg.go.dev/cloud.google.com/go/vision/apiv1)
|
||||
[Webrisk][cloud-webrisk] | alpha | [`cloud.google.com/go/webrisk/apiv1beta1`](https://pkg.go.dev/cloud.google.com/go/webrisk/apiv1beta1)
|
||||
|
||||
> **Alpha status**: the API is still being actively developed. As a
|
||||
> result, it might change in backward-incompatible ways and is not recommended
|
||||
> for production use.
|
||||
>
|
||||
> **Beta status**: the API is largely complete, but still has outstanding
|
||||
> features and bugs to be addressed. There may be minor backwards-incompatible
|
||||
> changes where necessary.
|
||||
>
|
||||
> **Stable status**: the API is mature and ready for production use. We will
|
||||
> continue addressing bugs and feature requests.
|
||||
|
||||
Documentation and examples are available at [pkg.go.dev/cloud.google.com/go](https://pkg.go.dev/cloud.google.com/go)
|
||||
|
||||
## Go Versions Supported
|
||||
|
||||
We support the two most recent major versions of Go. If Google App Engine uses
|
||||
an older version, we support that as well.
|
||||
|
||||
## Authorization
|
||||
|
||||
By default, each API will use [Google Application Default Credentials](https://developers.google.com/identity/protocols/application-default-credentials)
|
||||
for authorization credentials used in calling the API endpoints. This will allow your
|
||||
application to run in many environments without requiring explicit configuration.
|
||||
|
||||
[snip]:# (auth)
|
||||
```go
|
||||
client, err := storage.NewClient(ctx)
|
||||
```
|
||||
|
||||
To authorize using a
|
||||
[JSON key file](https://cloud.google.com/iam/docs/managing-service-account-keys),
|
||||
pass
|
||||
[`option.WithCredentialsFile`](https://pkg.go.dev/google.golang.org/api/option#WithCredentialsFile)
|
||||
to the `NewClient` function of the desired package. For example:
|
||||
|
||||
[snip]:# (auth-JSON)
|
||||
```go
|
||||
client, err := storage.NewClient(ctx, option.WithCredentialsFile("path/to/keyfile.json"))
|
||||
```
|
||||
|
||||
You can exert more control over authorization by using the
|
||||
[`golang.org/x/oauth2`](https://pkg.go.dev/golang.org/x/oauth2) package to
|
||||
create an `oauth2.TokenSource`. Then pass
|
||||
[`option.WithTokenSource`](https://pkg.go.dev/google.golang.org/api/option#WithTokenSource)
|
||||
to the `NewClient` function:
|
||||
[snip]:# (auth-ts)
|
||||
```go
|
||||
tokenSource := ...
|
||||
client, err := storage.NewClient(ctx, option.WithTokenSource(tokenSource))
|
||||
```
|
||||
|
||||
## Contributing
|
||||
|
||||
Contributions are welcome. Please, see the
|
||||
[CONTRIBUTING](https://github.com/GoogleCloudPlatform/google-cloud-go/blob/master/CONTRIBUTING.md)
|
||||
document for details.
|
||||
|
||||
Please note that this project is released with a Contributor Code of Conduct.
|
||||
By participating in this project you agree to abide by its terms.
|
||||
See [Contributor Code of Conduct](https://github.com/GoogleCloudPlatform/google-cloud-go/blob/master/CONTRIBUTING.md#contributor-code-of-conduct)
|
||||
for more information.
|
||||
|
||||
[cloud-asset]: https://cloud.google.com/security-command-center/docs/how-to-asset-inventory
|
||||
[cloud-automl]: https://cloud.google.com/automl
|
||||
[cloud-build]: https://cloud.google.com/cloud-build/
|
||||
[cloud-bigquery]: https://cloud.google.com/bigquery/
|
||||
[cloud-bigtable]: https://cloud.google.com/bigtable/
|
||||
[cloud-container]: https://cloud.google.com/containers/
|
||||
[cloud-containeranalysis]: https://cloud.google.com/container-registry/docs/container-analysis
|
||||
[cloud-dataproc]: https://cloud.google.com/dataproc/
|
||||
[cloud-datastore]: https://cloud.google.com/datastore/
|
||||
[cloud-dialogflow]: https://cloud.google.com/dialogflow-enterprise/
|
||||
[cloud-debugger]: https://cloud.google.com/debugger/
|
||||
[cloud-dlp]: https://cloud.google.com/dlp/
|
||||
[cloud-errors]: https://cloud.google.com/error-reporting/
|
||||
[cloud-firestore]: https://cloud.google.com/firestore/
|
||||
[cloud-iam]: https://cloud.google.com/iam/
|
||||
[cloud-iot]: https://cloud.google.com/iot-core/
|
||||
[cloud-irm]: https://cloud.google.com/incident-response/docs/concepts
|
||||
[cloud-kms]: https://cloud.google.com/kms/
|
||||
[cloud-pubsub]: https://cloud.google.com/pubsub/
|
||||
[cloud-storage]: https://cloud.google.com/storage/
|
||||
[cloud-language]: https://cloud.google.com/natural-language
|
||||
[cloud-logging]: https://cloud.google.com/logging/
|
||||
[cloud-natural-language]: https://cloud.google.com/natural-language/
|
||||
[cloud-memorystore]: https://cloud.google.com/memorystore/
|
||||
[cloud-monitoring]: https://cloud.google.com/monitoring/
|
||||
[cloud-oslogin]: https://cloud.google.com/compute/docs/oslogin/rest
|
||||
[cloud-phishingprotection]: https://cloud.google.com/phishing-protection/
|
||||
[cloud-securitycenter]: https://cloud.google.com/security-command-center/
|
||||
[cloud-scheduler]: https://cloud.google.com/scheduler
|
||||
[cloud-spanner]: https://cloud.google.com/spanner/
|
||||
[cloud-speech]: https://cloud.google.com/speech
|
||||
[cloud-talent]: https://cloud.google.com/solutions/talent-solution/
|
||||
[cloud-tasks]: https://cloud.google.com/tasks/
|
||||
[cloud-texttospeech]: https://cloud.google.com/texttospeech/
|
||||
[cloud-talent]: https://cloud.google.com/solutions/talent-solution/
|
||||
[cloud-trace]: https://cloud.google.com/trace/
|
||||
[cloud-translate]: https://cloud.google.com/translate
|
||||
[cloud-recaptcha]: https://cloud.google.com/recaptcha-enterprise/
|
||||
[cloud-recommender]: https://cloud.google.com/recommendations/
|
||||
[cloud-video]: https://cloud.google.com/video-intelligence/
|
||||
[cloud-vision]: https://cloud.google.com/vision
|
||||
[cloud-webrisk]: https://cloud.google.com/web-risk/
|
||||
-128
@@ -1,128 +0,0 @@
|
||||
# Setup from scratch
|
||||
|
||||
1. [Install Go](https://golang.org/dl/).
|
||||
1. Ensure that your `GOBIN` directory (by default `$(go env GOPATH)/bin`)
|
||||
is in your `PATH`.
|
||||
1. Check it's working by running `go version`.
|
||||
* If it doesn't work, check the install location, usually
|
||||
`/usr/local/go`, is on your `PATH`.
|
||||
|
||||
1. Sign one of the
|
||||
[contributor license agreements](#contributor-license-agreements) below.
|
||||
|
||||
1. Clone the repo:
|
||||
`git clone https://github.com/googleapis/google-cloud-go`
|
||||
|
||||
1. Change into the checked out source:
|
||||
`cd google-cloud-go`
|
||||
|
||||
1. Fork the repo and add your fork as a secondary remote (this is necessary in
|
||||
order to create PRs).
|
||||
|
||||
# Which module to release?
|
||||
|
||||
The Go client libraries have several modules. Each module does not strictly
|
||||
correspond to a single library - they correspond to trees of directories. If a
|
||||
file needs to be released, you must release the closest ancestor module.
|
||||
|
||||
To see all modules:
|
||||
|
||||
```
|
||||
$ cat `find . -name go.mod` | grep module
|
||||
module cloud.google.com/go
|
||||
module cloud.google.com/go/bigtable
|
||||
module cloud.google.com/go/firestore
|
||||
module cloud.google.com/go/bigquery
|
||||
module cloud.google.com/go/storage
|
||||
module cloud.google.com/go/datastore
|
||||
module cloud.google.com/go/pubsub
|
||||
module cloud.google.com/go/spanner
|
||||
module cloud.google.com/go/logging
|
||||
```
|
||||
|
||||
The `cloud.google.com/go` is the repository root module. Each other module is
|
||||
a submodule.
|
||||
|
||||
So, if you need to release a change in `bigtable/bttest/inmem.go`, the closest
|
||||
ancestor module is `cloud.google.com/go/bigtable` - so you should release a new
|
||||
version of the `cloud.google.com/go/bigtable` submodule.
|
||||
|
||||
If you need to release a change in `asset/apiv1/asset_client.go`, the closest
|
||||
ancestor module is `cloud.google.com/go` - so you should release a new version
|
||||
of the `cloud.google.com/go` repository root module. Note: releasing
|
||||
`cloud.google.com/go` has no impact on any of the submodules, and vice-versa.
|
||||
They are released entirely independently.
|
||||
|
||||
# Test failures
|
||||
|
||||
If there are any test failures in the Kokoro build, releases are blocked until
|
||||
the failures have been resolved.
|
||||
|
||||
# How to release `cloud.google.com/go`
|
||||
|
||||
1. Check for failures in the
|
||||
[continuous Kokoro build](http://go/google-cloud-go-continuous). If there are any
|
||||
failures in the most recent build, address them before proceeding with the
|
||||
release.
|
||||
1. Navigate to `~/code/gocloud/` and switch to master.
|
||||
1. `git pull`
|
||||
1. Run `git tag -l | grep -v beta | grep -v alpha` to see all existing releases.
|
||||
The current latest tag `$CV` is the largest tag. It should look something
|
||||
like `vX.Y.Z` (note: ignore all `LIB/vX.Y.Z` tags - these are tags for a
|
||||
specific library, not the module root). We'll call the current version `$CV`
|
||||
and the new version `$NV`.
|
||||
1. On master, run `git log $CV...` to list all the changes since the last
|
||||
release. NOTE: You must manually visually parse out changes to submodules [1]
|
||||
(the `git log` is going to show you things in submodules, which are not going
|
||||
to be part of your release).
|
||||
1. Edit `CHANGES.md` to include a summary of the changes.
|
||||
1. `cd internal/version && go generate && cd -`
|
||||
1. Commit the changes, push to your fork, and create a PR.
|
||||
1. Wait for the PR to be reviewed and merged. Once it's merged, and without
|
||||
merging any other PRs in the meantime:
|
||||
a. Switch to master.
|
||||
b. `git pull`
|
||||
c. Tag the repo with the next version: `git tag $NV`.
|
||||
d. Push the tag to origin:
|
||||
`git push origin $NV`
|
||||
2. Update [the releases page](https://github.com/googleapis/google-cloud-go/releases)
|
||||
with the new release, copying the contents of `CHANGES.md`.
|
||||
|
||||
# How to release a submodule
|
||||
|
||||
We have several submodules, including `cloud.google.com/go/logging`,
|
||||
`cloud.google.com/go/datastore`, and so on.
|
||||
|
||||
To release a submodule:
|
||||
|
||||
(these instructions assume we're releasing `cloud.google.com/go/datastore` - adjust accordingly)
|
||||
|
||||
1. Check for failures in the
|
||||
[continuous Kokoro build](http://go/google-cloud-go-continuous). If there are any
|
||||
failures in the most recent build, address them before proceeding with the
|
||||
release. (This applies even if the failures are in a different submodule from the one
|
||||
being released.)
|
||||
1. Navigate to `~/code/gocloud/` and switch to master.
|
||||
1. `git pull`
|
||||
1. Run `git tag -l | grep datastore | grep -v beta | grep -v alpha` to see all
|
||||
existing releases. The current latest tag `$CV` is the largest tag. It
|
||||
should look something like `datastore/vX.Y.Z`. We'll call the current version
|
||||
`$CV` and the new version `$NV`.
|
||||
1. On master, run `git log $CV.. -- datastore/` to list all the changes to the
|
||||
submodule directory since the last release.
|
||||
1. Edit `datastore/CHANGES.md` to include a summary of the changes.
|
||||
1. `cd internal/version && go generate && cd -`
|
||||
1. Commit the changes, push to your fork, and create a PR.
|
||||
1. Wait for the PR to be reviewed and merged. Once it's merged, and without
|
||||
merging any other PRs in the meantime:
|
||||
a. Switch to master.
|
||||
b. `git pull`
|
||||
c. Tag the repo with the next version: `git tag $NV`.
|
||||
d. Push the tag to origin:
|
||||
`git push origin $NV`
|
||||
1. Update [the releases page](https://github.com/googleapis/google-cloud-go/releases)
|
||||
with the new release, copying the contents of `datastore/CHANGES.md`.
|
||||
|
||||
# Appendix
|
||||
|
||||
1: This should get better as submodule tooling matures.
|
||||
-518
@@ -1,518 +0,0 @@
|
||||
// Copyright 2014 Google LLC
|
||||
//
|
||||
// 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 metadata provides access to Google Compute Engine (GCE)
|
||||
// metadata and API service accounts.
|
||||
//
|
||||
// This package is a wrapper around the GCE metadata service,
|
||||
// as documented at https://developers.google.com/compute/docs/metadata.
|
||||
package metadata // import "cloud.google.com/go/compute/metadata"
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"net"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"os"
|
||||
"runtime"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
)
|
||||
|
||||
const (
|
||||
// metadataIP is the documented metadata server IP address.
|
||||
metadataIP = "169.254.169.254"
|
||||
|
||||
// metadataHostEnv is the environment variable specifying the
|
||||
// GCE metadata hostname. If empty, the default value of
|
||||
// metadataIP ("169.254.169.254") is used instead.
|
||||
// This is variable name is not defined by any spec, as far as
|
||||
// I know; it was made up for the Go package.
|
||||
metadataHostEnv = "GCE_METADATA_HOST"
|
||||
|
||||
userAgent = "gcloud-golang/0.1"
|
||||
)
|
||||
|
||||
type cachedValue struct {
|
||||
k string
|
||||
trim bool
|
||||
mu sync.Mutex
|
||||
v string
|
||||
}
|
||||
|
||||
var (
|
||||
projID = &cachedValue{k: "project/project-id", trim: true}
|
||||
projNum = &cachedValue{k: "project/numeric-project-id", trim: true}
|
||||
instID = &cachedValue{k: "instance/id", trim: true}
|
||||
)
|
||||
|
||||
var defaultClient = &Client{hc: &http.Client{
|
||||
Transport: &http.Transport{
|
||||
Dial: (&net.Dialer{
|
||||
Timeout: 2 * time.Second,
|
||||
KeepAlive: 30 * time.Second,
|
||||
}).Dial,
|
||||
},
|
||||
}}
|
||||
|
||||
// NotDefinedError is returned when requested metadata is not defined.
|
||||
//
|
||||
// The underlying string is the suffix after "/computeMetadata/v1/".
|
||||
//
|
||||
// This error is not returned if the value is defined to be the empty
|
||||
// string.
|
||||
type NotDefinedError string
|
||||
|
||||
func (suffix NotDefinedError) Error() string {
|
||||
return fmt.Sprintf("metadata: GCE metadata %q not defined", string(suffix))
|
||||
}
|
||||
|
||||
func (c *cachedValue) get(cl *Client) (v string, err error) {
|
||||
defer c.mu.Unlock()
|
||||
c.mu.Lock()
|
||||
if c.v != "" {
|
||||
return c.v, nil
|
||||
}
|
||||
if c.trim {
|
||||
v, err = cl.getTrimmed(c.k)
|
||||
} else {
|
||||
v, err = cl.Get(c.k)
|
||||
}
|
||||
if err == nil {
|
||||
c.v = v
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
var (
|
||||
onGCEOnce sync.Once
|
||||
onGCE bool
|
||||
)
|
||||
|
||||
// OnGCE reports whether this process is running on Google Compute Engine.
|
||||
func OnGCE() bool {
|
||||
onGCEOnce.Do(initOnGCE)
|
||||
return onGCE
|
||||
}
|
||||
|
||||
func initOnGCE() {
|
||||
onGCE = testOnGCE()
|
||||
}
|
||||
|
||||
func testOnGCE() bool {
|
||||
// The user explicitly said they're on GCE, so trust them.
|
||||
if os.Getenv(metadataHostEnv) != "" {
|
||||
return true
|
||||
}
|
||||
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
defer cancel()
|
||||
|
||||
resc := make(chan bool, 2)
|
||||
|
||||
// Try two strategies in parallel.
|
||||
// See https://github.com/googleapis/google-cloud-go/issues/194
|
||||
go func() {
|
||||
req, _ := http.NewRequest("GET", "http://"+metadataIP, nil)
|
||||
req.Header.Set("User-Agent", userAgent)
|
||||
res, err := defaultClient.hc.Do(req.WithContext(ctx))
|
||||
if err != nil {
|
||||
resc <- false
|
||||
return
|
||||
}
|
||||
defer res.Body.Close()
|
||||
resc <- res.Header.Get("Metadata-Flavor") == "Google"
|
||||
}()
|
||||
|
||||
go func() {
|
||||
addrs, err := net.DefaultResolver.LookupHost(ctx, "metadata.google.internal")
|
||||
if err != nil || len(addrs) == 0 {
|
||||
resc <- false
|
||||
return
|
||||
}
|
||||
resc <- strsContains(addrs, metadataIP)
|
||||
}()
|
||||
|
||||
tryHarder := systemInfoSuggestsGCE()
|
||||
if tryHarder {
|
||||
res := <-resc
|
||||
if res {
|
||||
// The first strategy succeeded, so let's use it.
|
||||
return true
|
||||
}
|
||||
// Wait for either the DNS or metadata server probe to
|
||||
// contradict the other one and say we are running on
|
||||
// GCE. Give it a lot of time to do so, since the system
|
||||
// info already suggests we're running on a GCE BIOS.
|
||||
timer := time.NewTimer(5 * time.Second)
|
||||
defer timer.Stop()
|
||||
select {
|
||||
case res = <-resc:
|
||||
return res
|
||||
case <-timer.C:
|
||||
// Too slow. Who knows what this system is.
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
// There's no hint from the system info that we're running on
|
||||
// GCE, so use the first probe's result as truth, whether it's
|
||||
// true or false. The goal here is to optimize for speed for
|
||||
// users who are NOT running on GCE. We can't assume that
|
||||
// either a DNS lookup or an HTTP request to a blackholed IP
|
||||
// address is fast. Worst case this should return when the
|
||||
// metaClient's Transport.ResponseHeaderTimeout or
|
||||
// Transport.Dial.Timeout fires (in two seconds).
|
||||
return <-resc
|
||||
}
|
||||
|
||||
// systemInfoSuggestsGCE reports whether the local system (without
|
||||
// doing network requests) suggests that we're running on GCE. If this
|
||||
// returns true, testOnGCE tries a bit harder to reach its metadata
|
||||
// server.
|
||||
func systemInfoSuggestsGCE() bool {
|
||||
if runtime.GOOS != "linux" {
|
||||
// We don't have any non-Linux clues available, at least yet.
|
||||
return false
|
||||
}
|
||||
slurp, _ := ioutil.ReadFile("/sys/class/dmi/id/product_name")
|
||||
name := strings.TrimSpace(string(slurp))
|
||||
return name == "Google" || name == "Google Compute Engine"
|
||||
}
|
||||
|
||||
// Subscribe calls Client.Subscribe on the default client.
|
||||
func Subscribe(suffix string, fn func(v string, ok bool) error) error {
|
||||
return defaultClient.Subscribe(suffix, fn)
|
||||
}
|
||||
|
||||
// Get calls Client.Get on the default client.
|
||||
func Get(suffix string) (string, error) { return defaultClient.Get(suffix) }
|
||||
|
||||
// ProjectID returns the current instance's project ID string.
|
||||
func ProjectID() (string, error) { return defaultClient.ProjectID() }
|
||||
|
||||
// NumericProjectID returns the current instance's numeric project ID.
|
||||
func NumericProjectID() (string, error) { return defaultClient.NumericProjectID() }
|
||||
|
||||
// InternalIP returns the instance's primary internal IP address.
|
||||
func InternalIP() (string, error) { return defaultClient.InternalIP() }
|
||||
|
||||
// ExternalIP returns the instance's primary external (public) IP address.
|
||||
func ExternalIP() (string, error) { return defaultClient.ExternalIP() }
|
||||
|
||||
// Email calls Client.Email on the default client.
|
||||
func Email(serviceAccount string) (string, error) { return defaultClient.Email(serviceAccount) }
|
||||
|
||||
// Hostname returns the instance's hostname. This will be of the form
|
||||
// "<instanceID>.c.<projID>.internal".
|
||||
func Hostname() (string, error) { return defaultClient.Hostname() }
|
||||
|
||||
// InstanceTags returns the list of user-defined instance tags,
|
||||
// assigned when initially creating a GCE instance.
|
||||
func InstanceTags() ([]string, error) { return defaultClient.InstanceTags() }
|
||||
|
||||
// InstanceID returns the current VM's numeric instance ID.
|
||||
func InstanceID() (string, error) { return defaultClient.InstanceID() }
|
||||
|
||||
// InstanceName returns the current VM's instance ID string.
|
||||
func InstanceName() (string, error) { return defaultClient.InstanceName() }
|
||||
|
||||
// Zone returns the current VM's zone, such as "us-central1-b".
|
||||
func Zone() (string, error) { return defaultClient.Zone() }
|
||||
|
||||
// InstanceAttributes calls Client.InstanceAttributes on the default client.
|
||||
func InstanceAttributes() ([]string, error) { return defaultClient.InstanceAttributes() }
|
||||
|
||||
// ProjectAttributes calls Client.ProjectAttributes on the default client.
|
||||
func ProjectAttributes() ([]string, error) { return defaultClient.ProjectAttributes() }
|
||||
|
||||
// InstanceAttributeValue calls Client.InstanceAttributeValue on the default client.
|
||||
func InstanceAttributeValue(attr string) (string, error) {
|
||||
return defaultClient.InstanceAttributeValue(attr)
|
||||
}
|
||||
|
||||
// ProjectAttributeValue calls Client.ProjectAttributeValue on the default client.
|
||||
func ProjectAttributeValue(attr string) (string, error) {
|
||||
return defaultClient.ProjectAttributeValue(attr)
|
||||
}
|
||||
|
||||
// Scopes calls Client.Scopes on the default client.
|
||||
func Scopes(serviceAccount string) ([]string, error) { return defaultClient.Scopes(serviceAccount) }
|
||||
|
||||
func strsContains(ss []string, s string) bool {
|
||||
for _, v := range ss {
|
||||
if v == s {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// A Client provides metadata.
|
||||
type Client struct {
|
||||
hc *http.Client
|
||||
}
|
||||
|
||||
// NewClient returns a Client that can be used to fetch metadata.
|
||||
// Returns the client that uses the specified http.Client for HTTP requests.
|
||||
// If nil is specified, returns the default client.
|
||||
func NewClient(c *http.Client) *Client {
|
||||
if c == nil {
|
||||
return defaultClient
|
||||
}
|
||||
|
||||
return &Client{hc: c}
|
||||
}
|
||||
|
||||
// getETag returns a value from the metadata service as well as the associated ETag.
|
||||
// This func is otherwise equivalent to Get.
|
||||
func (c *Client) getETag(suffix string) (value, etag string, err error) {
|
||||
// Using a fixed IP makes it very difficult to spoof the metadata service in
|
||||
// a container, which is an important use-case for local testing of cloud
|
||||
// deployments. To enable spoofing of the metadata service, the environment
|
||||
// variable GCE_METADATA_HOST is first inspected to decide where metadata
|
||||
// requests shall go.
|
||||
host := os.Getenv(metadataHostEnv)
|
||||
if host == "" {
|
||||
// Using 169.254.169.254 instead of "metadata" here because Go
|
||||
// binaries built with the "netgo" tag and without cgo won't
|
||||
// know the search suffix for "metadata" is
|
||||
// ".google.internal", and this IP address is documented as
|
||||
// being stable anyway.
|
||||
host = metadataIP
|
||||
}
|
||||
u := "http://" + host + "/computeMetadata/v1/" + suffix
|
||||
req, err := http.NewRequest("GET", u, nil)
|
||||
if err != nil {
|
||||
return "", "", err
|
||||
}
|
||||
req.Header.Set("Metadata-Flavor", "Google")
|
||||
req.Header.Set("User-Agent", userAgent)
|
||||
res, err := c.hc.Do(req)
|
||||
if err != nil {
|
||||
return "", "", err
|
||||
}
|
||||
defer res.Body.Close()
|
||||
if res.StatusCode == http.StatusNotFound {
|
||||
return "", "", NotDefinedError(suffix)
|
||||
}
|
||||
all, err := ioutil.ReadAll(res.Body)
|
||||
if err != nil {
|
||||
return "", "", err
|
||||
}
|
||||
if res.StatusCode != 200 {
|
||||
return "", "", &Error{Code: res.StatusCode, Message: string(all)}
|
||||
}
|
||||
return string(all), res.Header.Get("Etag"), nil
|
||||
}
|
||||
|
||||
// Get returns a value from the metadata service.
|
||||
// The suffix is appended to "http://${GCE_METADATA_HOST}/computeMetadata/v1/".
|
||||
//
|
||||
// If the GCE_METADATA_HOST environment variable is not defined, a default of
|
||||
// 169.254.169.254 will be used instead.
|
||||
//
|
||||
// If the requested metadata is not defined, the returned error will
|
||||
// be of type NotDefinedError.
|
||||
func (c *Client) Get(suffix string) (string, error) {
|
||||
val, _, err := c.getETag(suffix)
|
||||
return val, err
|
||||
}
|
||||
|
||||
func (c *Client) getTrimmed(suffix string) (s string, err error) {
|
||||
s, err = c.Get(suffix)
|
||||
s = strings.TrimSpace(s)
|
||||
return
|
||||
}
|
||||
|
||||
func (c *Client) lines(suffix string) ([]string, error) {
|
||||
j, err := c.Get(suffix)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
s := strings.Split(strings.TrimSpace(j), "\n")
|
||||
for i := range s {
|
||||
s[i] = strings.TrimSpace(s[i])
|
||||
}
|
||||
return s, nil
|
||||
}
|
||||
|
||||
// ProjectID returns the current instance's project ID string.
|
||||
func (c *Client) ProjectID() (string, error) { return projID.get(c) }
|
||||
|
||||
// NumericProjectID returns the current instance's numeric project ID.
|
||||
func (c *Client) NumericProjectID() (string, error) { return projNum.get(c) }
|
||||
|
||||
// InstanceID returns the current VM's numeric instance ID.
|
||||
func (c *Client) InstanceID() (string, error) { return instID.get(c) }
|
||||
|
||||
// InternalIP returns the instance's primary internal IP address.
|
||||
func (c *Client) InternalIP() (string, error) {
|
||||
return c.getTrimmed("instance/network-interfaces/0/ip")
|
||||
}
|
||||
|
||||
// Email returns the email address associated with the service account.
|
||||
// The account may be empty or the string "default" to use the instance's
|
||||
// main account.
|
||||
func (c *Client) Email(serviceAccount string) (string, error) {
|
||||
if serviceAccount == "" {
|
||||
serviceAccount = "default"
|
||||
}
|
||||
return c.getTrimmed("instance/service-accounts/" + serviceAccount + "/email")
|
||||
}
|
||||
|
||||
// ExternalIP returns the instance's primary external (public) IP address.
|
||||
func (c *Client) ExternalIP() (string, error) {
|
||||
return c.getTrimmed("instance/network-interfaces/0/access-configs/0/external-ip")
|
||||
}
|
||||
|
||||
// Hostname returns the instance's hostname. This will be of the form
|
||||
// "<instanceID>.c.<projID>.internal".
|
||||
func (c *Client) Hostname() (string, error) {
|
||||
return c.getTrimmed("instance/hostname")
|
||||
}
|
||||
|
||||
// InstanceTags returns the list of user-defined instance tags,
|
||||
// assigned when initially creating a GCE instance.
|
||||
func (c *Client) InstanceTags() ([]string, error) {
|
||||
var s []string
|
||||
j, err := c.Get("instance/tags")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := json.NewDecoder(strings.NewReader(j)).Decode(&s); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return s, nil
|
||||
}
|
||||
|
||||
// InstanceName returns the current VM's instance ID string.
|
||||
func (c *Client) InstanceName() (string, error) {
|
||||
return c.getTrimmed("instance/name")
|
||||
}
|
||||
|
||||
// Zone returns the current VM's zone, such as "us-central1-b".
|
||||
func (c *Client) Zone() (string, error) {
|
||||
zone, err := c.getTrimmed("instance/zone")
|
||||
// zone is of the form "projects/<projNum>/zones/<zoneName>".
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return zone[strings.LastIndex(zone, "/")+1:], nil
|
||||
}
|
||||
|
||||
// InstanceAttributes returns the list of user-defined attributes,
|
||||
// assigned when initially creating a GCE VM instance. The value of an
|
||||
// attribute can be obtained with InstanceAttributeValue.
|
||||
func (c *Client) InstanceAttributes() ([]string, error) { return c.lines("instance/attributes/") }
|
||||
|
||||
// ProjectAttributes returns the list of user-defined attributes
|
||||
// applying to the project as a whole, not just this VM. The value of
|
||||
// an attribute can be obtained with ProjectAttributeValue.
|
||||
func (c *Client) ProjectAttributes() ([]string, error) { return c.lines("project/attributes/") }
|
||||
|
||||
// InstanceAttributeValue returns the value of the provided VM
|
||||
// instance attribute.
|
||||
//
|
||||
// If the requested attribute is not defined, the returned error will
|
||||
// be of type NotDefinedError.
|
||||
//
|
||||
// InstanceAttributeValue may return ("", nil) if the attribute was
|
||||
// defined to be the empty string.
|
||||
func (c *Client) InstanceAttributeValue(attr string) (string, error) {
|
||||
return c.Get("instance/attributes/" + attr)
|
||||
}
|
||||
|
||||
// ProjectAttributeValue returns the value of the provided
|
||||
// project attribute.
|
||||
//
|
||||
// If the requested attribute is not defined, the returned error will
|
||||
// be of type NotDefinedError.
|
||||
//
|
||||
// ProjectAttributeValue may return ("", nil) if the attribute was
|
||||
// defined to be the empty string.
|
||||
func (c *Client) ProjectAttributeValue(attr string) (string, error) {
|
||||
return c.Get("project/attributes/" + attr)
|
||||
}
|
||||
|
||||
// Scopes returns the service account scopes for the given account.
|
||||
// The account may be empty or the string "default" to use the instance's
|
||||
// main account.
|
||||
func (c *Client) Scopes(serviceAccount string) ([]string, error) {
|
||||
if serviceAccount == "" {
|
||||
serviceAccount = "default"
|
||||
}
|
||||
return c.lines("instance/service-accounts/" + serviceAccount + "/scopes")
|
||||
}
|
||||
|
||||
// Subscribe subscribes to a value from the metadata service.
|
||||
// The suffix is appended to "http://${GCE_METADATA_HOST}/computeMetadata/v1/".
|
||||
// The suffix may contain query parameters.
|
||||
//
|
||||
// Subscribe calls fn with the latest metadata value indicated by the provided
|
||||
// suffix. If the metadata value is deleted, fn is called with the empty string
|
||||
// and ok false. Subscribe blocks until fn returns a non-nil error or the value
|
||||
// is deleted. Subscribe returns the error value returned from the last call to
|
||||
// fn, which may be nil when ok == false.
|
||||
func (c *Client) Subscribe(suffix string, fn func(v string, ok bool) error) error {
|
||||
const failedSubscribeSleep = time.Second * 5
|
||||
|
||||
// First check to see if the metadata value exists at all.
|
||||
val, lastETag, err := c.getETag(suffix)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := fn(val, true); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
ok := true
|
||||
if strings.ContainsRune(suffix, '?') {
|
||||
suffix += "&wait_for_change=true&last_etag="
|
||||
} else {
|
||||
suffix += "?wait_for_change=true&last_etag="
|
||||
}
|
||||
for {
|
||||
val, etag, err := c.getETag(suffix + url.QueryEscape(lastETag))
|
||||
if err != nil {
|
||||
if _, deleted := err.(NotDefinedError); !deleted {
|
||||
time.Sleep(failedSubscribeSleep)
|
||||
continue // Retry on other errors.
|
||||
}
|
||||
ok = false
|
||||
}
|
||||
lastETag = etag
|
||||
|
||||
if err := fn(val, ok); err != nil || !ok {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Error contains an error response from the server.
|
||||
type Error struct {
|
||||
// Code is the HTTP response status code.
|
||||
Code int
|
||||
// Message is the server response message.
|
||||
Message string
|
||||
}
|
||||
|
||||
func (e *Error) Error() string {
|
||||
return fmt.Sprintf("compute: Received %d `%s`", e.Code, e.Message)
|
||||
}
|
||||
-100
@@ -1,100 +0,0 @@
|
||||
// Copyright 2014 Google LLC
|
||||
//
|
||||
// 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 cloud is the root of the packages used to access Google Cloud
|
||||
Services. See https://godoc.org/cloud.google.com/go for a full list
|
||||
of sub-packages.
|
||||
|
||||
|
||||
Client Options
|
||||
|
||||
All clients in sub-packages are configurable via client options. These options are
|
||||
described here: https://godoc.org/google.golang.org/api/option.
|
||||
|
||||
|
||||
Authentication and Authorization
|
||||
|
||||
All the clients in sub-packages support authentication via Google Application Default
|
||||
Credentials (see https://cloud.google.com/docs/authentication/production), or
|
||||
by providing a JSON key file for a Service Account. See the authentication examples
|
||||
in this package for details.
|
||||
|
||||
|
||||
Timeouts and Cancellation
|
||||
|
||||
By default, all requests in sub-packages will run indefinitely, retrying on transient
|
||||
errors when correctness allows. To set timeouts or arrange for cancellation, use
|
||||
contexts. See the examples for details.
|
||||
|
||||
Do not attempt to control the initial connection (dialing) of a service by setting a
|
||||
timeout on the context passed to NewClient. Dialing is non-blocking, so timeouts
|
||||
would be ineffective and would only interfere with credential refreshing, which uses
|
||||
the same context.
|
||||
|
||||
|
||||
Connection Pooling
|
||||
|
||||
Connection pooling differs in clients based on their transport. Cloud
|
||||
clients either rely on HTTP or gRPC transports to communicate
|
||||
with Google Cloud.
|
||||
|
||||
Cloud clients that use HTTP (bigquery, compute, storage, and translate) rely on the
|
||||
underlying HTTP transport to cache connections for later re-use. These are cached to
|
||||
the default http.MaxIdleConns and http.MaxIdleConnsPerHost settings in
|
||||
http.DefaultTransport.
|
||||
|
||||
For gRPC clients (all others in this repo), connection pooling is configurable. Users
|
||||
of cloud client libraries may specify option.WithGRPCConnectionPool(n) as a client
|
||||
option to NewClient calls. This configures the underlying gRPC connections to be
|
||||
pooled and addressed in a round robin fashion.
|
||||
|
||||
|
||||
Using the Libraries with Docker
|
||||
|
||||
Minimal docker images like Alpine lack CA certificates. This causes RPCs to appear to
|
||||
hang, because gRPC retries indefinitely. See https://github.com/googleapis/google-cloud-go/issues/928
|
||||
for more information.
|
||||
|
||||
|
||||
Debugging
|
||||
|
||||
To see gRPC logs, set the environment variable GRPC_GO_LOG_SEVERITY_LEVEL. See
|
||||
https://godoc.org/google.golang.org/grpc/grpclog for more information.
|
||||
|
||||
For HTTP logging, set the GODEBUG environment variable to "http2debug=1" or "http2debug=2".
|
||||
|
||||
|
||||
Client Stability
|
||||
|
||||
Clients in this repository are considered alpha or beta unless otherwise
|
||||
marked as stable in the README.md. Semver is not used to communicate stability
|
||||
of clients.
|
||||
|
||||
Alpha and beta clients may change or go away without notice.
|
||||
|
||||
Clients marked stable will maintain compatibility with future versions for as
|
||||
long as we can reasonably sustain. Incompatible changes might be made in some
|
||||
situations, including:
|
||||
|
||||
- Security bugs may prompt backwards-incompatible changes.
|
||||
|
||||
- Situations in which components are no longer feasible to maintain without
|
||||
making breaking changes, including removal.
|
||||
|
||||
- Parts of the client surface may be outright unstable and subject to change.
|
||||
These parts of the surface will be labeled with the note, "It is EXPERIMENTAL
|
||||
and subject to change or removal without notice."
|
||||
*/
|
||||
package cloud // import "cloud.google.com/go"
|
||||
-24
@@ -1,24 +0,0 @@
|
||||
# Changes
|
||||
|
||||
## v1.2.0
|
||||
|
||||
- Deprecate v1beta1 client.
|
||||
- Fix serverTimestamp docs.
|
||||
- Add missing operators to query docs.
|
||||
- Make document IDs 20 alpha-numeric characters. Previously, there could be more
|
||||
than 20 non-alphanumeric characters, which broke some users. See
|
||||
https://github.com/googleapis/google-cloud-go/issues/1715.
|
||||
- Various updates to autogenerated clients.
|
||||
|
||||
## v1.1.1
|
||||
|
||||
- Fix bug in CollectionGroup query validation.
|
||||
|
||||
## v1.1.0
|
||||
|
||||
- Add support for `in` and `array-contains-any` query operators.
|
||||
|
||||
## v1.0.0
|
||||
|
||||
This is the first tag to carve out firestore as its own module. See:
|
||||
https://github.com/golang/go/wiki/Modules#is-it-possible-to-add-a-module-to-a-multi-module-repository.
|
||||
-202
@@ -1,202 +0,0 @@
|
||||
|
||||
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 [yyyy] [name of copyright owner]
|
||||
|
||||
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.
|
||||
-13
@@ -1,13 +0,0 @@
|
||||
# Copy textproto files in this directory from the source of truth.
|
||||
|
||||
SRC=$(GOPATH)/src/github.com/GoogleCloudPlatform/google-cloud-common/testing/firestore
|
||||
|
||||
.PHONY: refresh-tests
|
||||
|
||||
refresh-tests:
|
||||
-rm genproto/*.pb.go
|
||||
cp $(SRC)/genproto/*.pb.go genproto
|
||||
-rm testdata/*.textproto
|
||||
cp $(SRC)/testdata/*.textproto testdata
|
||||
openssl dgst -sha1 $(SRC)/testdata/test-suite.binproto > testdata/VERSION
|
||||
|
||||
-100
@@ -1,100 +0,0 @@
|
||||
// Copyright 2020 Google LLC
|
||||
//
|
||||
// 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
|
||||
//
|
||||
// https://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.
|
||||
|
||||
// Code generated by protoc-gen-go_gapic. DO NOT EDIT.
|
||||
|
||||
// Package apiv1 is an auto-generated package for the
|
||||
// Google Cloud Firestore Admin API.
|
||||
//
|
||||
// Accesses the NoSQL document database built for automatic scaling, high
|
||||
// performance, and ease of application development.
|
||||
//
|
||||
// Use of Context
|
||||
//
|
||||
// The ctx passed to NewClient is used for authentication requests and
|
||||
// for creating the underlying connection, but is not used for subsequent calls.
|
||||
// Individual methods on the client use the ctx given to them.
|
||||
//
|
||||
// To close the open connection, use the Close() method.
|
||||
//
|
||||
// For information about setting deadlines, reusing contexts, and more
|
||||
// please visit godoc.org/cloud.google.com/go.
|
||||
package apiv1 // import "cloud.google.com/go/firestore/apiv1/admin"
|
||||
|
||||
import (
|
||||
"context"
|
||||
"runtime"
|
||||
"strings"
|
||||
"unicode"
|
||||
|
||||
"google.golang.org/grpc/metadata"
|
||||
)
|
||||
|
||||
const versionClient = "20200325"
|
||||
|
||||
func insertMetadata(ctx context.Context, mds ...metadata.MD) context.Context {
|
||||
out, _ := metadata.FromOutgoingContext(ctx)
|
||||
out = out.Copy()
|
||||
for _, md := range mds {
|
||||
for k, v := range md {
|
||||
out[k] = append(out[k], v...)
|
||||
}
|
||||
}
|
||||
return metadata.NewOutgoingContext(ctx, out)
|
||||
}
|
||||
|
||||
// DefaultAuthScopes reports the default set of authentication scopes to use with this package.
|
||||
func DefaultAuthScopes() []string {
|
||||
return []string{
|
||||
"https://www.googleapis.com/auth/cloud-platform",
|
||||
"https://www.googleapis.com/auth/datastore",
|
||||
}
|
||||
}
|
||||
|
||||
// versionGo returns the Go runtime version. The returned string
|
||||
// has no whitespace, suitable for reporting in header.
|
||||
func versionGo() string {
|
||||
const develPrefix = "devel +"
|
||||
|
||||
s := runtime.Version()
|
||||
if strings.HasPrefix(s, develPrefix) {
|
||||
s = s[len(develPrefix):]
|
||||
if p := strings.IndexFunc(s, unicode.IsSpace); p >= 0 {
|
||||
s = s[:p]
|
||||
}
|
||||
return s
|
||||
}
|
||||
|
||||
notSemverRune := func(r rune) bool {
|
||||
return !strings.ContainsRune("0123456789.", r)
|
||||
}
|
||||
|
||||
if strings.HasPrefix(s, "go1") {
|
||||
s = s[2:]
|
||||
var prerelease string
|
||||
if p := strings.IndexFunc(s, notSemverRune); p >= 0 {
|
||||
s, prerelease = s[:p], s[p:]
|
||||
}
|
||||
if strings.HasSuffix(s, ".") {
|
||||
s += "0"
|
||||
} else if strings.Count(s, ".") < 2 {
|
||||
s += ".0"
|
||||
}
|
||||
if prerelease != "" {
|
||||
s += "-" + prerelease
|
||||
}
|
||||
return s
|
||||
}
|
||||
return "UNKNOWN"
|
||||
}
|
||||
-803
@@ -1,803 +0,0 @@
|
||||
// Copyright 2020 Google LLC
|
||||
//
|
||||
// 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
|
||||
//
|
||||
// https://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.
|
||||
|
||||
// Code generated by protoc-gen-go_gapic. DO NOT EDIT.
|
||||
|
||||
package apiv1
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"math"
|
||||
"net/url"
|
||||
"time"
|
||||
|
||||
"cloud.google.com/go/longrunning"
|
||||
lroauto "cloud.google.com/go/longrunning/autogen"
|
||||
"github.com/golang/protobuf/proto"
|
||||
gax "github.com/googleapis/gax-go/v2"
|
||||
"google.golang.org/api/iterator"
|
||||
"google.golang.org/api/option"
|
||||
gtransport "google.golang.org/api/transport/grpc"
|
||||
adminpb "google.golang.org/genproto/googleapis/firestore/admin/v1"
|
||||
longrunningpb "google.golang.org/genproto/googleapis/longrunning"
|
||||
"google.golang.org/grpc"
|
||||
"google.golang.org/grpc/codes"
|
||||
"google.golang.org/grpc/metadata"
|
||||
)
|
||||
|
||||
// FirestoreAdminCallOptions contains the retry settings for each method of FirestoreAdminClient.
|
||||
type FirestoreAdminCallOptions struct {
|
||||
CreateIndex []gax.CallOption
|
||||
ListIndexes []gax.CallOption
|
||||
GetIndex []gax.CallOption
|
||||
DeleteIndex []gax.CallOption
|
||||
GetField []gax.CallOption
|
||||
UpdateField []gax.CallOption
|
||||
ListFields []gax.CallOption
|
||||
ExportDocuments []gax.CallOption
|
||||
ImportDocuments []gax.CallOption
|
||||
}
|
||||
|
||||
func defaultFirestoreAdminClientOptions() []option.ClientOption {
|
||||
return []option.ClientOption{
|
||||
option.WithEndpoint("firestore.googleapis.com:443"),
|
||||
option.WithGRPCDialOption(grpc.WithDisableServiceConfig()),
|
||||
option.WithScopes(DefaultAuthScopes()...),
|
||||
option.WithGRPCDialOption(grpc.WithDefaultCallOptions(
|
||||
grpc.MaxCallRecvMsgSize(math.MaxInt32))),
|
||||
}
|
||||
}
|
||||
|
||||
func defaultFirestoreAdminCallOptions() *FirestoreAdminCallOptions {
|
||||
return &FirestoreAdminCallOptions{
|
||||
CreateIndex: []gax.CallOption{},
|
||||
ListIndexes: []gax.CallOption{
|
||||
gax.WithRetry(func() gax.Retryer {
|
||||
return gax.OnCodes([]codes.Code{
|
||||
codes.Unavailable,
|
||||
codes.Internal,
|
||||
codes.DeadlineExceeded,
|
||||
}, gax.Backoff{
|
||||
Initial: 100 * time.Millisecond,
|
||||
Max: 60000 * time.Millisecond,
|
||||
Multiplier: 1.30,
|
||||
})
|
||||
}),
|
||||
},
|
||||
GetIndex: []gax.CallOption{
|
||||
gax.WithRetry(func() gax.Retryer {
|
||||
return gax.OnCodes([]codes.Code{
|
||||
codes.Unavailable,
|
||||
codes.Internal,
|
||||
codes.DeadlineExceeded,
|
||||
}, gax.Backoff{
|
||||
Initial: 100 * time.Millisecond,
|
||||
Max: 60000 * time.Millisecond,
|
||||
Multiplier: 1.30,
|
||||
})
|
||||
}),
|
||||
},
|
||||
DeleteIndex: []gax.CallOption{
|
||||
gax.WithRetry(func() gax.Retryer {
|
||||
return gax.OnCodes([]codes.Code{
|
||||
codes.Unavailable,
|
||||
codes.Internal,
|
||||
codes.DeadlineExceeded,
|
||||
}, gax.Backoff{
|
||||
Initial: 100 * time.Millisecond,
|
||||
Max: 60000 * time.Millisecond,
|
||||
Multiplier: 1.30,
|
||||
})
|
||||
}),
|
||||
},
|
||||
GetField: []gax.CallOption{
|
||||
gax.WithRetry(func() gax.Retryer {
|
||||
return gax.OnCodes([]codes.Code{
|
||||
codes.Unavailable,
|
||||
codes.Internal,
|
||||
codes.DeadlineExceeded,
|
||||
}, gax.Backoff{
|
||||
Initial: 100 * time.Millisecond,
|
||||
Max: 60000 * time.Millisecond,
|
||||
Multiplier: 1.30,
|
||||
})
|
||||
}),
|
||||
},
|
||||
UpdateField: []gax.CallOption{},
|
||||
ListFields: []gax.CallOption{
|
||||
gax.WithRetry(func() gax.Retryer {
|
||||
return gax.OnCodes([]codes.Code{
|
||||
codes.Unavailable,
|
||||
codes.Internal,
|
||||
codes.DeadlineExceeded,
|
||||
}, gax.Backoff{
|
||||
Initial: 100 * time.Millisecond,
|
||||
Max: 60000 * time.Millisecond,
|
||||
Multiplier: 1.30,
|
||||
})
|
||||
}),
|
||||
},
|
||||
ExportDocuments: []gax.CallOption{},
|
||||
ImportDocuments: []gax.CallOption{},
|
||||
}
|
||||
}
|
||||
|
||||
// FirestoreAdminClient is a client for interacting with Google Cloud Firestore Admin API.
|
||||
//
|
||||
// Methods, except Close, may be called concurrently. However, fields must not be modified concurrently with method calls.
|
||||
type FirestoreAdminClient struct {
|
||||
// Connection pool of gRPC connections to the service.
|
||||
connPool gtransport.ConnPool
|
||||
|
||||
// The gRPC API client.
|
||||
firestoreAdminClient adminpb.FirestoreAdminClient
|
||||
|
||||
// LROClient is used internally to handle longrunning operations.
|
||||
// It is exposed so that its CallOptions can be modified if required.
|
||||
// Users should not Close this client.
|
||||
LROClient *lroauto.OperationsClient
|
||||
|
||||
// The call options for this service.
|
||||
CallOptions *FirestoreAdminCallOptions
|
||||
|
||||
// The x-goog-* metadata to be sent with each request.
|
||||
xGoogMetadata metadata.MD
|
||||
}
|
||||
|
||||
// NewFirestoreAdminClient creates a new firestore admin client.
|
||||
//
|
||||
// Operations are created by service FirestoreAdmin, but are accessed via
|
||||
// service google.longrunning.Operations.
|
||||
func NewFirestoreAdminClient(ctx context.Context, opts ...option.ClientOption) (*FirestoreAdminClient, error) {
|
||||
connPool, err := gtransport.DialPool(ctx, append(defaultFirestoreAdminClientOptions(), opts...)...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
c := &FirestoreAdminClient{
|
||||
connPool: connPool,
|
||||
CallOptions: defaultFirestoreAdminCallOptions(),
|
||||
|
||||
firestoreAdminClient: adminpb.NewFirestoreAdminClient(connPool),
|
||||
}
|
||||
c.SetGoogleClientInfo()
|
||||
|
||||
c.LROClient, err = lroauto.NewOperationsClient(ctx, gtransport.WithConnPool(connPool))
|
||||
if err != nil {
|
||||
// This error "should not happen", since we are just reusing old connection pool
|
||||
// and never actually need to dial.
|
||||
// If this does happen, we could leak connp. However, we cannot close conn:
|
||||
// If the user invoked the constructor with option.WithGRPCConn,
|
||||
// we would close a connection that's still in use.
|
||||
// TODO: investigate error conditions.
|
||||
return nil, err
|
||||
}
|
||||
return c, nil
|
||||
}
|
||||
|
||||
// Connection returns a connection to the API service.
|
||||
//
|
||||
// Deprecated.
|
||||
func (c *FirestoreAdminClient) Connection() *grpc.ClientConn {
|
||||
return c.connPool.Conn()
|
||||
}
|
||||
|
||||
// Close closes the connection to the API service. The user should invoke this when
|
||||
// the client is no longer required.
|
||||
func (c *FirestoreAdminClient) Close() error {
|
||||
return c.connPool.Close()
|
||||
}
|
||||
|
||||
// SetGoogleClientInfo sets the name and version of the application in
|
||||
// the `x-goog-api-client` header passed on each request. Intended for
|
||||
// use by Google-written clients.
|
||||
func (c *FirestoreAdminClient) SetGoogleClientInfo(keyval ...string) {
|
||||
kv := append([]string{"gl-go", versionGo()}, keyval...)
|
||||
kv = append(kv, "gapic", versionClient, "gax", gax.Version, "grpc", grpc.Version)
|
||||
c.xGoogMetadata = metadata.Pairs("x-goog-api-client", gax.XGoogHeader(kv...))
|
||||
}
|
||||
|
||||
// CreateIndex creates a composite index. This returns a [google.longrunning.Operation][google.longrunning.Operation]
|
||||
// which may be used to track the status of the creation. The metadata for
|
||||
// the operation will be the type [IndexOperationMetadata][google.firestore.admin.v1.IndexOperationMetadata].
|
||||
func (c *FirestoreAdminClient) CreateIndex(ctx context.Context, req *adminpb.CreateIndexRequest, opts ...gax.CallOption) (*CreateIndexOperation, error) {
|
||||
md := metadata.Pairs("x-goog-request-params", fmt.Sprintf("%s=%v", "parent", url.QueryEscape(req.GetParent())))
|
||||
ctx = insertMetadata(ctx, c.xGoogMetadata, md)
|
||||
opts = append(c.CallOptions.CreateIndex[0:len(c.CallOptions.CreateIndex):len(c.CallOptions.CreateIndex)], opts...)
|
||||
var resp *longrunningpb.Operation
|
||||
err := gax.Invoke(ctx, func(ctx context.Context, settings gax.CallSettings) error {
|
||||
var err error
|
||||
resp, err = c.firestoreAdminClient.CreateIndex(ctx, req, settings.GRPC...)
|
||||
return err
|
||||
}, opts...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &CreateIndexOperation{
|
||||
lro: longrunning.InternalNewOperation(c.LROClient, resp),
|
||||
}, nil
|
||||
}
|
||||
|
||||
// ListIndexes lists composite indexes.
|
||||
func (c *FirestoreAdminClient) ListIndexes(ctx context.Context, req *adminpb.ListIndexesRequest, opts ...gax.CallOption) *IndexIterator {
|
||||
md := metadata.Pairs("x-goog-request-params", fmt.Sprintf("%s=%v", "parent", url.QueryEscape(req.GetParent())))
|
||||
ctx = insertMetadata(ctx, c.xGoogMetadata, md)
|
||||
opts = append(c.CallOptions.ListIndexes[0:len(c.CallOptions.ListIndexes):len(c.CallOptions.ListIndexes)], opts...)
|
||||
it := &IndexIterator{}
|
||||
req = proto.Clone(req).(*adminpb.ListIndexesRequest)
|
||||
it.InternalFetch = func(pageSize int, pageToken string) ([]*adminpb.Index, string, error) {
|
||||
var resp *adminpb.ListIndexesResponse
|
||||
req.PageToken = pageToken
|
||||
if pageSize > math.MaxInt32 {
|
||||
req.PageSize = math.MaxInt32
|
||||
} else {
|
||||
req.PageSize = int32(pageSize)
|
||||
}
|
||||
err := gax.Invoke(ctx, func(ctx context.Context, settings gax.CallSettings) error {
|
||||
var err error
|
||||
resp, err = c.firestoreAdminClient.ListIndexes(ctx, req, settings.GRPC...)
|
||||
return err
|
||||
}, opts...)
|
||||
if err != nil {
|
||||
return nil, "", err
|
||||
}
|
||||
|
||||
it.Response = resp
|
||||
return resp.Indexes, resp.NextPageToken, nil
|
||||
}
|
||||
fetch := func(pageSize int, pageToken string) (string, error) {
|
||||
items, nextPageToken, err := it.InternalFetch(pageSize, pageToken)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
it.items = append(it.items, items...)
|
||||
return nextPageToken, nil
|
||||
}
|
||||
it.pageInfo, it.nextFunc = iterator.NewPageInfo(fetch, it.bufLen, it.takeBuf)
|
||||
it.pageInfo.MaxSize = int(req.PageSize)
|
||||
it.pageInfo.Token = req.PageToken
|
||||
return it
|
||||
}
|
||||
|
||||
// GetIndex gets a composite index.
|
||||
func (c *FirestoreAdminClient) GetIndex(ctx context.Context, req *adminpb.GetIndexRequest, opts ...gax.CallOption) (*adminpb.Index, error) {
|
||||
md := metadata.Pairs("x-goog-request-params", fmt.Sprintf("%s=%v", "name", url.QueryEscape(req.GetName())))
|
||||
ctx = insertMetadata(ctx, c.xGoogMetadata, md)
|
||||
opts = append(c.CallOptions.GetIndex[0:len(c.CallOptions.GetIndex):len(c.CallOptions.GetIndex)], opts...)
|
||||
var resp *adminpb.Index
|
||||
err := gax.Invoke(ctx, func(ctx context.Context, settings gax.CallSettings) error {
|
||||
var err error
|
||||
resp, err = c.firestoreAdminClient.GetIndex(ctx, req, settings.GRPC...)
|
||||
return err
|
||||
}, opts...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return resp, nil
|
||||
}
|
||||
|
||||
// DeleteIndex deletes a composite index.
|
||||
func (c *FirestoreAdminClient) DeleteIndex(ctx context.Context, req *adminpb.DeleteIndexRequest, opts ...gax.CallOption) error {
|
||||
md := metadata.Pairs("x-goog-request-params", fmt.Sprintf("%s=%v", "name", url.QueryEscape(req.GetName())))
|
||||
ctx = insertMetadata(ctx, c.xGoogMetadata, md)
|
||||
opts = append(c.CallOptions.DeleteIndex[0:len(c.CallOptions.DeleteIndex):len(c.CallOptions.DeleteIndex)], opts...)
|
||||
err := gax.Invoke(ctx, func(ctx context.Context, settings gax.CallSettings) error {
|
||||
var err error
|
||||
_, err = c.firestoreAdminClient.DeleteIndex(ctx, req, settings.GRPC...)
|
||||
return err
|
||||
}, opts...)
|
||||
return err
|
||||
}
|
||||
|
||||
// GetField gets the metadata and configuration for a Field.
|
||||
func (c *FirestoreAdminClient) GetField(ctx context.Context, req *adminpb.GetFieldRequest, opts ...gax.CallOption) (*adminpb.Field, error) {
|
||||
md := metadata.Pairs("x-goog-request-params", fmt.Sprintf("%s=%v", "name", url.QueryEscape(req.GetName())))
|
||||
ctx = insertMetadata(ctx, c.xGoogMetadata, md)
|
||||
opts = append(c.CallOptions.GetField[0:len(c.CallOptions.GetField):len(c.CallOptions.GetField)], opts...)
|
||||
var resp *adminpb.Field
|
||||
err := gax.Invoke(ctx, func(ctx context.Context, settings gax.CallSettings) error {
|
||||
var err error
|
||||
resp, err = c.firestoreAdminClient.GetField(ctx, req, settings.GRPC...)
|
||||
return err
|
||||
}, opts...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return resp, nil
|
||||
}
|
||||
|
||||
// UpdateField updates a field configuration. Currently, field updates apply only to
|
||||
// single field index configuration. However, calls to
|
||||
// [FirestoreAdmin.UpdateField][google.firestore.admin.v1.FirestoreAdmin.UpdateField] should provide a field mask to avoid
|
||||
// changing any configuration that the caller isn’t aware of. The field mask
|
||||
// should be specified as: { paths: "index_config" }.
|
||||
//
|
||||
// This call returns a [google.longrunning.Operation][google.longrunning.Operation] which may be used to
|
||||
// track the status of the field update. The metadata for
|
||||
// the operation will be the type [FieldOperationMetadata][google.firestore.admin.v1.FieldOperationMetadata].
|
||||
//
|
||||
// To configure the default field settings for the database, use
|
||||
// the special Field with resource name:
|
||||
// projects/{project_id}/databases/{database_id}/collectionGroups/__default__/fields/*.
|
||||
func (c *FirestoreAdminClient) UpdateField(ctx context.Context, req *adminpb.UpdateFieldRequest, opts ...gax.CallOption) (*UpdateFieldOperation, error) {
|
||||
md := metadata.Pairs("x-goog-request-params", fmt.Sprintf("%s=%v", "field.name", url.QueryEscape(req.GetField().GetName())))
|
||||
ctx = insertMetadata(ctx, c.xGoogMetadata, md)
|
||||
opts = append(c.CallOptions.UpdateField[0:len(c.CallOptions.UpdateField):len(c.CallOptions.UpdateField)], opts...)
|
||||
var resp *longrunningpb.Operation
|
||||
err := gax.Invoke(ctx, func(ctx context.Context, settings gax.CallSettings) error {
|
||||
var err error
|
||||
resp, err = c.firestoreAdminClient.UpdateField(ctx, req, settings.GRPC...)
|
||||
return err
|
||||
}, opts...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &UpdateFieldOperation{
|
||||
lro: longrunning.InternalNewOperation(c.LROClient, resp),
|
||||
}, nil
|
||||
}
|
||||
|
||||
// ListFields lists the field configuration and metadata for this database.
|
||||
//
|
||||
// Currently, [FirestoreAdmin.ListFields][google.firestore.admin.v1.FirestoreAdmin.ListFields] only supports listing fields
|
||||
// that have been explicitly overridden. To issue this query, call
|
||||
// [FirestoreAdmin.ListFields][google.firestore.admin.v1.FirestoreAdmin.ListFields] with the filter set to
|
||||
// indexConfig.usesAncestorConfig:false.
|
||||
func (c *FirestoreAdminClient) ListFields(ctx context.Context, req *adminpb.ListFieldsRequest, opts ...gax.CallOption) *FieldIterator {
|
||||
md := metadata.Pairs("x-goog-request-params", fmt.Sprintf("%s=%v", "parent", url.QueryEscape(req.GetParent())))
|
||||
ctx = insertMetadata(ctx, c.xGoogMetadata, md)
|
||||
opts = append(c.CallOptions.ListFields[0:len(c.CallOptions.ListFields):len(c.CallOptions.ListFields)], opts...)
|
||||
it := &FieldIterator{}
|
||||
req = proto.Clone(req).(*adminpb.ListFieldsRequest)
|
||||
it.InternalFetch = func(pageSize int, pageToken string) ([]*adminpb.Field, string, error) {
|
||||
var resp *adminpb.ListFieldsResponse
|
||||
req.PageToken = pageToken
|
||||
if pageSize > math.MaxInt32 {
|
||||
req.PageSize = math.MaxInt32
|
||||
} else {
|
||||
req.PageSize = int32(pageSize)
|
||||
}
|
||||
err := gax.Invoke(ctx, func(ctx context.Context, settings gax.CallSettings) error {
|
||||
var err error
|
||||
resp, err = c.firestoreAdminClient.ListFields(ctx, req, settings.GRPC...)
|
||||
return err
|
||||
}, opts...)
|
||||
if err != nil {
|
||||
return nil, "", err
|
||||
}
|
||||
|
||||
it.Response = resp
|
||||
return resp.Fields, resp.NextPageToken, nil
|
||||
}
|
||||
fetch := func(pageSize int, pageToken string) (string, error) {
|
||||
items, nextPageToken, err := it.InternalFetch(pageSize, pageToken)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
it.items = append(it.items, items...)
|
||||
return nextPageToken, nil
|
||||
}
|
||||
it.pageInfo, it.nextFunc = iterator.NewPageInfo(fetch, it.bufLen, it.takeBuf)
|
||||
it.pageInfo.MaxSize = int(req.PageSize)
|
||||
it.pageInfo.Token = req.PageToken
|
||||
return it
|
||||
}
|
||||
|
||||
// ExportDocuments exports a copy of all or a subset of documents from Google Cloud Firestore
|
||||
// to another storage system, such as Google Cloud Storage. Recent updates to
|
||||
// documents may not be reflected in the export. The export occurs in the
|
||||
// background and its progress can be monitored and managed via the
|
||||
// Operation resource that is created. The output of an export may only be
|
||||
// used once the associated operation is done. If an export operation is
|
||||
// cancelled before completion it may leave partial data behind in Google
|
||||
// Cloud Storage.
|
||||
func (c *FirestoreAdminClient) ExportDocuments(ctx context.Context, req *adminpb.ExportDocumentsRequest, opts ...gax.CallOption) (*ExportDocumentsOperation, error) {
|
||||
md := metadata.Pairs("x-goog-request-params", fmt.Sprintf("%s=%v", "name", url.QueryEscape(req.GetName())))
|
||||
ctx = insertMetadata(ctx, c.xGoogMetadata, md)
|
||||
opts = append(c.CallOptions.ExportDocuments[0:len(c.CallOptions.ExportDocuments):len(c.CallOptions.ExportDocuments)], opts...)
|
||||
var resp *longrunningpb.Operation
|
||||
err := gax.Invoke(ctx, func(ctx context.Context, settings gax.CallSettings) error {
|
||||
var err error
|
||||
resp, err = c.firestoreAdminClient.ExportDocuments(ctx, req, settings.GRPC...)
|
||||
return err
|
||||
}, opts...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &ExportDocumentsOperation{
|
||||
lro: longrunning.InternalNewOperation(c.LROClient, resp),
|
||||
}, nil
|
||||
}
|
||||
|
||||
// ImportDocuments imports documents into Google Cloud Firestore. Existing documents with the
|
||||
// same name are overwritten. The import occurs in the background and its
|
||||
// progress can be monitored and managed via the Operation resource that is
|
||||
// created. If an ImportDocuments operation is cancelled, it is possible
|
||||
// that a subset of the data has already been imported to Cloud Firestore.
|
||||
func (c *FirestoreAdminClient) ImportDocuments(ctx context.Context, req *adminpb.ImportDocumentsRequest, opts ...gax.CallOption) (*ImportDocumentsOperation, error) {
|
||||
md := metadata.Pairs("x-goog-request-params", fmt.Sprintf("%s=%v", "name", url.QueryEscape(req.GetName())))
|
||||
ctx = insertMetadata(ctx, c.xGoogMetadata, md)
|
||||
opts = append(c.CallOptions.ImportDocuments[0:len(c.CallOptions.ImportDocuments):len(c.CallOptions.ImportDocuments)], opts...)
|
||||
var resp *longrunningpb.Operation
|
||||
err := gax.Invoke(ctx, func(ctx context.Context, settings gax.CallSettings) error {
|
||||
var err error
|
||||
resp, err = c.firestoreAdminClient.ImportDocuments(ctx, req, settings.GRPC...)
|
||||
return err
|
||||
}, opts...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &ImportDocumentsOperation{
|
||||
lro: longrunning.InternalNewOperation(c.LROClient, resp),
|
||||
}, nil
|
||||
}
|
||||
|
||||
// CreateIndexOperation manages a long-running operation from CreateIndex.
|
||||
type CreateIndexOperation struct {
|
||||
lro *longrunning.Operation
|
||||
}
|
||||
|
||||
// CreateIndexOperation returns a new CreateIndexOperation from a given name.
|
||||
// The name must be that of a previously created CreateIndexOperation, possibly from a different process.
|
||||
func (c *FirestoreAdminClient) CreateIndexOperation(name string) *CreateIndexOperation {
|
||||
return &CreateIndexOperation{
|
||||
lro: longrunning.InternalNewOperation(c.LROClient, &longrunningpb.Operation{Name: name}),
|
||||
}
|
||||
}
|
||||
|
||||
// Wait blocks until the long-running operation is completed, returning the response and any errors encountered.
|
||||
//
|
||||
// See documentation of Poll for error-handling information.
|
||||
func (op *CreateIndexOperation) Wait(ctx context.Context, opts ...gax.CallOption) (*adminpb.Index, error) {
|
||||
var resp adminpb.Index
|
||||
if err := op.lro.WaitWithInterval(ctx, &resp, time.Minute, opts...); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &resp, nil
|
||||
}
|
||||
|
||||
// Poll fetches the latest state of the long-running operation.
|
||||
//
|
||||
// Poll also fetches the latest metadata, which can be retrieved by Metadata.
|
||||
//
|
||||
// If Poll fails, the error is returned and op is unmodified. If Poll succeeds and
|
||||
// the operation has completed with failure, the error is returned and op.Done will return true.
|
||||
// If Poll succeeds and the operation has completed successfully,
|
||||
// op.Done will return true, and the response of the operation is returned.
|
||||
// If Poll succeeds and the operation has not completed, the returned response and error are both nil.
|
||||
func (op *CreateIndexOperation) Poll(ctx context.Context, opts ...gax.CallOption) (*adminpb.Index, error) {
|
||||
var resp adminpb.Index
|
||||
if err := op.lro.Poll(ctx, &resp, opts...); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if !op.Done() {
|
||||
return nil, nil
|
||||
}
|
||||
return &resp, nil
|
||||
}
|
||||
|
||||
// Metadata returns metadata associated with the long-running operation.
|
||||
// Metadata itself does not contact the server, but Poll does.
|
||||
// To get the latest metadata, call this method after a successful call to Poll.
|
||||
// If the metadata is not available, the returned metadata and error are both nil.
|
||||
func (op *CreateIndexOperation) Metadata() (*adminpb.IndexOperationMetadata, error) {
|
||||
var meta adminpb.IndexOperationMetadata
|
||||
if err := op.lro.Metadata(&meta); err == longrunning.ErrNoMetadata {
|
||||
return nil, nil
|
||||
} else if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &meta, nil
|
||||
}
|
||||
|
||||
// Done reports whether the long-running operation has completed.
|
||||
func (op *CreateIndexOperation) Done() bool {
|
||||
return op.lro.Done()
|
||||
}
|
||||
|
||||
// Name returns the name of the long-running operation.
|
||||
// The name is assigned by the server and is unique within the service from which the operation is created.
|
||||
func (op *CreateIndexOperation) Name() string {
|
||||
return op.lro.Name()
|
||||
}
|
||||
|
||||
// ExportDocumentsOperation manages a long-running operation from ExportDocuments.
|
||||
type ExportDocumentsOperation struct {
|
||||
lro *longrunning.Operation
|
||||
}
|
||||
|
||||
// ExportDocumentsOperation returns a new ExportDocumentsOperation from a given name.
|
||||
// The name must be that of a previously created ExportDocumentsOperation, possibly from a different process.
|
||||
func (c *FirestoreAdminClient) ExportDocumentsOperation(name string) *ExportDocumentsOperation {
|
||||
return &ExportDocumentsOperation{
|
||||
lro: longrunning.InternalNewOperation(c.LROClient, &longrunningpb.Operation{Name: name}),
|
||||
}
|
||||
}
|
||||
|
||||
// Wait blocks until the long-running operation is completed, returning the response and any errors encountered.
|
||||
//
|
||||
// See documentation of Poll for error-handling information.
|
||||
func (op *ExportDocumentsOperation) Wait(ctx context.Context, opts ...gax.CallOption) (*adminpb.ExportDocumentsResponse, error) {
|
||||
var resp adminpb.ExportDocumentsResponse
|
||||
if err := op.lro.WaitWithInterval(ctx, &resp, time.Minute, opts...); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &resp, nil
|
||||
}
|
||||
|
||||
// Poll fetches the latest state of the long-running operation.
|
||||
//
|
||||
// Poll also fetches the latest metadata, which can be retrieved by Metadata.
|
||||
//
|
||||
// If Poll fails, the error is returned and op is unmodified. If Poll succeeds and
|
||||
// the operation has completed with failure, the error is returned and op.Done will return true.
|
||||
// If Poll succeeds and the operation has completed successfully,
|
||||
// op.Done will return true, and the response of the operation is returned.
|
||||
// If Poll succeeds and the operation has not completed, the returned response and error are both nil.
|
||||
func (op *ExportDocumentsOperation) Poll(ctx context.Context, opts ...gax.CallOption) (*adminpb.ExportDocumentsResponse, error) {
|
||||
var resp adminpb.ExportDocumentsResponse
|
||||
if err := op.lro.Poll(ctx, &resp, opts...); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if !op.Done() {
|
||||
return nil, nil
|
||||
}
|
||||
return &resp, nil
|
||||
}
|
||||
|
||||
// Metadata returns metadata associated with the long-running operation.
|
||||
// Metadata itself does not contact the server, but Poll does.
|
||||
// To get the latest metadata, call this method after a successful call to Poll.
|
||||
// If the metadata is not available, the returned metadata and error are both nil.
|
||||
func (op *ExportDocumentsOperation) Metadata() (*adminpb.ExportDocumentsMetadata, error) {
|
||||
var meta adminpb.ExportDocumentsMetadata
|
||||
if err := op.lro.Metadata(&meta); err == longrunning.ErrNoMetadata {
|
||||
return nil, nil
|
||||
} else if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &meta, nil
|
||||
}
|
||||
|
||||
// Done reports whether the long-running operation has completed.
|
||||
func (op *ExportDocumentsOperation) Done() bool {
|
||||
return op.lro.Done()
|
||||
}
|
||||
|
||||
// Name returns the name of the long-running operation.
|
||||
// The name is assigned by the server and is unique within the service from which the operation is created.
|
||||
func (op *ExportDocumentsOperation) Name() string {
|
||||
return op.lro.Name()
|
||||
}
|
||||
|
||||
// ImportDocumentsOperation manages a long-running operation from ImportDocuments.
|
||||
type ImportDocumentsOperation struct {
|
||||
lro *longrunning.Operation
|
||||
}
|
||||
|
||||
// ImportDocumentsOperation returns a new ImportDocumentsOperation from a given name.
|
||||
// The name must be that of a previously created ImportDocumentsOperation, possibly from a different process.
|
||||
func (c *FirestoreAdminClient) ImportDocumentsOperation(name string) *ImportDocumentsOperation {
|
||||
return &ImportDocumentsOperation{
|
||||
lro: longrunning.InternalNewOperation(c.LROClient, &longrunningpb.Operation{Name: name}),
|
||||
}
|
||||
}
|
||||
|
||||
// Wait blocks until the long-running operation is completed, returning the response and any errors encountered.
|
||||
//
|
||||
// See documentation of Poll for error-handling information.
|
||||
func (op *ImportDocumentsOperation) Wait(ctx context.Context, opts ...gax.CallOption) error {
|
||||
return op.lro.WaitWithInterval(ctx, nil, time.Minute, opts...)
|
||||
}
|
||||
|
||||
// Poll fetches the latest state of the long-running operation.
|
||||
//
|
||||
// Poll also fetches the latest metadata, which can be retrieved by Metadata.
|
||||
//
|
||||
// If Poll fails, the error is returned and op is unmodified. If Poll succeeds and
|
||||
// the operation has completed with failure, the error is returned and op.Done will return true.
|
||||
// If Poll succeeds and the operation has completed successfully,
|
||||
// op.Done will return true, and the response of the operation is returned.
|
||||
// If Poll succeeds and the operation has not completed, the returned response and error are both nil.
|
||||
func (op *ImportDocumentsOperation) Poll(ctx context.Context, opts ...gax.CallOption) error {
|
||||
return op.lro.Poll(ctx, nil, opts...)
|
||||
}
|
||||
|
||||
// Metadata returns metadata associated with the long-running operation.
|
||||
// Metadata itself does not contact the server, but Poll does.
|
||||
// To get the latest metadata, call this method after a successful call to Poll.
|
||||
// If the metadata is not available, the returned metadata and error are both nil.
|
||||
func (op *ImportDocumentsOperation) Metadata() (*adminpb.ImportDocumentsMetadata, error) {
|
||||
var meta adminpb.ImportDocumentsMetadata
|
||||
if err := op.lro.Metadata(&meta); err == longrunning.ErrNoMetadata {
|
||||
return nil, nil
|
||||
} else if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &meta, nil
|
||||
}
|
||||
|
||||
// Done reports whether the long-running operation has completed.
|
||||
func (op *ImportDocumentsOperation) Done() bool {
|
||||
return op.lro.Done()
|
||||
}
|
||||
|
||||
// Name returns the name of the long-running operation.
|
||||
// The name is assigned by the server and is unique within the service from which the operation is created.
|
||||
func (op *ImportDocumentsOperation) Name() string {
|
||||
return op.lro.Name()
|
||||
}
|
||||
|
||||
// UpdateFieldOperation manages a long-running operation from UpdateField.
|
||||
type UpdateFieldOperation struct {
|
||||
lro *longrunning.Operation
|
||||
}
|
||||
|
||||
// UpdateFieldOperation returns a new UpdateFieldOperation from a given name.
|
||||
// The name must be that of a previously created UpdateFieldOperation, possibly from a different process.
|
||||
func (c *FirestoreAdminClient) UpdateFieldOperation(name string) *UpdateFieldOperation {
|
||||
return &UpdateFieldOperation{
|
||||
lro: longrunning.InternalNewOperation(c.LROClient, &longrunningpb.Operation{Name: name}),
|
||||
}
|
||||
}
|
||||
|
||||
// Wait blocks until the long-running operation is completed, returning the response and any errors encountered.
|
||||
//
|
||||
// See documentation of Poll for error-handling information.
|
||||
func (op *UpdateFieldOperation) Wait(ctx context.Context, opts ...gax.CallOption) (*adminpb.Field, error) {
|
||||
var resp adminpb.Field
|
||||
if err := op.lro.WaitWithInterval(ctx, &resp, time.Minute, opts...); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &resp, nil
|
||||
}
|
||||
|
||||
// Poll fetches the latest state of the long-running operation.
|
||||
//
|
||||
// Poll also fetches the latest metadata, which can be retrieved by Metadata.
|
||||
//
|
||||
// If Poll fails, the error is returned and op is unmodified. If Poll succeeds and
|
||||
// the operation has completed with failure, the error is returned and op.Done will return true.
|
||||
// If Poll succeeds and the operation has completed successfully,
|
||||
// op.Done will return true, and the response of the operation is returned.
|
||||
// If Poll succeeds and the operation has not completed, the returned response and error are both nil.
|
||||
func (op *UpdateFieldOperation) Poll(ctx context.Context, opts ...gax.CallOption) (*adminpb.Field, error) {
|
||||
var resp adminpb.Field
|
||||
if err := op.lro.Poll(ctx, &resp, opts...); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if !op.Done() {
|
||||
return nil, nil
|
||||
}
|
||||
return &resp, nil
|
||||
}
|
||||
|
||||
// Metadata returns metadata associated with the long-running operation.
|
||||
// Metadata itself does not contact the server, but Poll does.
|
||||
// To get the latest metadata, call this method after a successful call to Poll.
|
||||
// If the metadata is not available, the returned metadata and error are both nil.
|
||||
func (op *UpdateFieldOperation) Metadata() (*adminpb.FieldOperationMetadata, error) {
|
||||
var meta adminpb.FieldOperationMetadata
|
||||
if err := op.lro.Metadata(&meta); err == longrunning.ErrNoMetadata {
|
||||
return nil, nil
|
||||
} else if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &meta, nil
|
||||
}
|
||||
|
||||
// Done reports whether the long-running operation has completed.
|
||||
func (op *UpdateFieldOperation) Done() bool {
|
||||
return op.lro.Done()
|
||||
}
|
||||
|
||||
// Name returns the name of the long-running operation.
|
||||
// The name is assigned by the server and is unique within the service from which the operation is created.
|
||||
func (op *UpdateFieldOperation) Name() string {
|
||||
return op.lro.Name()
|
||||
}
|
||||
|
||||
// FieldIterator manages a stream of *adminpb.Field.
|
||||
type FieldIterator struct {
|
||||
items []*adminpb.Field
|
||||
pageInfo *iterator.PageInfo
|
||||
nextFunc func() error
|
||||
|
||||
// Response is the raw response for the current page.
|
||||
// It must be cast to the RPC response type.
|
||||
// Calling Next() or InternalFetch() updates this value.
|
||||
Response interface{}
|
||||
|
||||
// InternalFetch is for use by the Google Cloud Libraries only.
|
||||
// It is not part of the stable interface of this package.
|
||||
//
|
||||
// InternalFetch returns results from a single call to the underlying RPC.
|
||||
// The number of results is no greater than pageSize.
|
||||
// If there are no more results, nextPageToken is empty and err is nil.
|
||||
InternalFetch func(pageSize int, pageToken string) (results []*adminpb.Field, nextPageToken string, err error)
|
||||
}
|
||||
|
||||
// PageInfo supports pagination. See the google.golang.org/api/iterator package for details.
|
||||
func (it *FieldIterator) PageInfo() *iterator.PageInfo {
|
||||
return it.pageInfo
|
||||
}
|
||||
|
||||
// Next returns the next result. Its second return value is iterator.Done if there are no more
|
||||
// results. Once Next returns Done, all subsequent calls will return Done.
|
||||
func (it *FieldIterator) Next() (*adminpb.Field, error) {
|
||||
var item *adminpb.Field
|
||||
if err := it.nextFunc(); err != nil {
|
||||
return item, err
|
||||
}
|
||||
item = it.items[0]
|
||||
it.items = it.items[1:]
|
||||
return item, nil
|
||||
}
|
||||
|
||||
func (it *FieldIterator) bufLen() int {
|
||||
return len(it.items)
|
||||
}
|
||||
|
||||
func (it *FieldIterator) takeBuf() interface{} {
|
||||
b := it.items
|
||||
it.items = nil
|
||||
return b
|
||||
}
|
||||
|
||||
// IndexIterator manages a stream of *adminpb.Index.
|
||||
type IndexIterator struct {
|
||||
items []*adminpb.Index
|
||||
pageInfo *iterator.PageInfo
|
||||
nextFunc func() error
|
||||
|
||||
// Response is the raw response for the current page.
|
||||
// It must be cast to the RPC response type.
|
||||
// Calling Next() or InternalFetch() updates this value.
|
||||
Response interface{}
|
||||
|
||||
// InternalFetch is for use by the Google Cloud Libraries only.
|
||||
// It is not part of the stable interface of this package.
|
||||
//
|
||||
// InternalFetch returns results from a single call to the underlying RPC.
|
||||
// The number of results is no greater than pageSize.
|
||||
// If there are no more results, nextPageToken is empty and err is nil.
|
||||
InternalFetch func(pageSize int, pageToken string) (results []*adminpb.Index, nextPageToken string, err error)
|
||||
}
|
||||
|
||||
// PageInfo supports pagination. See the google.golang.org/api/iterator package for details.
|
||||
func (it *IndexIterator) PageInfo() *iterator.PageInfo {
|
||||
return it.pageInfo
|
||||
}
|
||||
|
||||
// Next returns the next result. Its second return value is iterator.Done if there are no more
|
||||
// results. Once Next returns Done, all subsequent calls will return Done.
|
||||
func (it *IndexIterator) Next() (*adminpb.Index, error) {
|
||||
var item *adminpb.Index
|
||||
if err := it.nextFunc(); err != nil {
|
||||
return item, err
|
||||
}
|
||||
item = it.items[0]
|
||||
it.items = it.items[1:]
|
||||
return item, nil
|
||||
}
|
||||
|
||||
func (it *IndexIterator) bufLen() int {
|
||||
return len(it.items)
|
||||
}
|
||||
|
||||
func (it *IndexIterator) takeBuf() interface{} {
|
||||
b := it.items
|
||||
it.items = nil
|
||||
return b
|
||||
}
|
||||
-100
@@ -1,100 +0,0 @@
|
||||
// Copyright 2020 Google LLC
|
||||
//
|
||||
// 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
|
||||
//
|
||||
// https://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.
|
||||
|
||||
// Code generated by protoc-gen-go_gapic. DO NOT EDIT.
|
||||
|
||||
// Package firestore is an auto-generated package for the
|
||||
// Cloud Firestore API.
|
||||
//
|
||||
// Accesses the NoSQL document database built for automatic scaling, high
|
||||
// performance, and ease of application development.
|
||||
//
|
||||
// Use of Context
|
||||
//
|
||||
// The ctx passed to NewClient is used for authentication requests and
|
||||
// for creating the underlying connection, but is not used for subsequent calls.
|
||||
// Individual methods on the client use the ctx given to them.
|
||||
//
|
||||
// To close the open connection, use the Close() method.
|
||||
//
|
||||
// For information about setting deadlines, reusing contexts, and more
|
||||
// please visit godoc.org/cloud.google.com/go.
|
||||
package firestore // import "cloud.google.com/go/firestore/apiv1"
|
||||
|
||||
import (
|
||||
"context"
|
||||
"runtime"
|
||||
"strings"
|
||||
"unicode"
|
||||
|
||||
"google.golang.org/grpc/metadata"
|
||||
)
|
||||
|
||||
const versionClient = "20200325"
|
||||
|
||||
func insertMetadata(ctx context.Context, mds ...metadata.MD) context.Context {
|
||||
out, _ := metadata.FromOutgoingContext(ctx)
|
||||
out = out.Copy()
|
||||
for _, md := range mds {
|
||||
for k, v := range md {
|
||||
out[k] = append(out[k], v...)
|
||||
}
|
||||
}
|
||||
return metadata.NewOutgoingContext(ctx, out)
|
||||
}
|
||||
|
||||
// DefaultAuthScopes reports the default set of authentication scopes to use with this package.
|
||||
func DefaultAuthScopes() []string {
|
||||
return []string{
|
||||
"https://www.googleapis.com/auth/cloud-platform",
|
||||
"https://www.googleapis.com/auth/datastore",
|
||||
}
|
||||
}
|
||||
|
||||
// versionGo returns the Go runtime version. The returned string
|
||||
// has no whitespace, suitable for reporting in header.
|
||||
func versionGo() string {
|
||||
const develPrefix = "devel +"
|
||||
|
||||
s := runtime.Version()
|
||||
if strings.HasPrefix(s, develPrefix) {
|
||||
s = s[len(develPrefix):]
|
||||
if p := strings.IndexFunc(s, unicode.IsSpace); p >= 0 {
|
||||
s = s[:p]
|
||||
}
|
||||
return s
|
||||
}
|
||||
|
||||
notSemverRune := func(r rune) bool {
|
||||
return !strings.ContainsRune("0123456789.", r)
|
||||
}
|
||||
|
||||
if strings.HasPrefix(s, "go1") {
|
||||
s = s[2:]
|
||||
var prerelease string
|
||||
if p := strings.IndexFunc(s, notSemverRune); p >= 0 {
|
||||
s, prerelease = s[:p], s[p:]
|
||||
}
|
||||
if strings.HasSuffix(s, ".") {
|
||||
s += "0"
|
||||
} else if strings.Count(s, ".") < 2 {
|
||||
s += ".0"
|
||||
}
|
||||
if prerelease != "" {
|
||||
s += "-" + prerelease
|
||||
}
|
||||
return s
|
||||
}
|
||||
return "UNKNOWN"
|
||||
}
|
||||
-609
@@ -1,609 +0,0 @@
|
||||
// Copyright 2020 Google LLC
|
||||
//
|
||||
// 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
|
||||
//
|
||||
// https://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.
|
||||
|
||||
// Code generated by protoc-gen-go_gapic. DO NOT EDIT.
|
||||
|
||||
package firestore
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"math"
|
||||
"net/url"
|
||||
"time"
|
||||
|
||||
"github.com/golang/protobuf/proto"
|
||||
gax "github.com/googleapis/gax-go/v2"
|
||||
"google.golang.org/api/iterator"
|
||||
"google.golang.org/api/option"
|
||||
gtransport "google.golang.org/api/transport/grpc"
|
||||
firestorepb "google.golang.org/genproto/googleapis/firestore/v1"
|
||||
"google.golang.org/grpc"
|
||||
"google.golang.org/grpc/codes"
|
||||
"google.golang.org/grpc/metadata"
|
||||
)
|
||||
|
||||
// CallOptions contains the retry settings for each method of Client.
|
||||
type CallOptions struct {
|
||||
GetDocument []gax.CallOption
|
||||
ListDocuments []gax.CallOption
|
||||
UpdateDocument []gax.CallOption
|
||||
DeleteDocument []gax.CallOption
|
||||
BatchGetDocuments []gax.CallOption
|
||||
BeginTransaction []gax.CallOption
|
||||
Commit []gax.CallOption
|
||||
Rollback []gax.CallOption
|
||||
RunQuery []gax.CallOption
|
||||
Write []gax.CallOption
|
||||
Listen []gax.CallOption
|
||||
ListCollectionIds []gax.CallOption
|
||||
CreateDocument []gax.CallOption
|
||||
}
|
||||
|
||||
func defaultClientOptions() []option.ClientOption {
|
||||
return []option.ClientOption{
|
||||
option.WithEndpoint("firestore.googleapis.com:443"),
|
||||
option.WithGRPCDialOption(grpc.WithDisableServiceConfig()),
|
||||
option.WithScopes(DefaultAuthScopes()...),
|
||||
option.WithGRPCDialOption(grpc.WithDefaultCallOptions(
|
||||
grpc.MaxCallRecvMsgSize(math.MaxInt32))),
|
||||
}
|
||||
}
|
||||
|
||||
func defaultCallOptions() *CallOptions {
|
||||
return &CallOptions{
|
||||
GetDocument: []gax.CallOption{
|
||||
gax.WithRetry(func() gax.Retryer {
|
||||
return gax.OnCodes([]codes.Code{
|
||||
codes.Unavailable,
|
||||
codes.Internal,
|
||||
codes.DeadlineExceeded,
|
||||
}, gax.Backoff{
|
||||
Initial: 100 * time.Millisecond,
|
||||
Max: 60000 * time.Millisecond,
|
||||
Multiplier: 1.30,
|
||||
})
|
||||
}),
|
||||
},
|
||||
ListDocuments: []gax.CallOption{
|
||||
gax.WithRetry(func() gax.Retryer {
|
||||
return gax.OnCodes([]codes.Code{
|
||||
codes.Unavailable,
|
||||
codes.Internal,
|
||||
codes.DeadlineExceeded,
|
||||
}, gax.Backoff{
|
||||
Initial: 100 * time.Millisecond,
|
||||
Max: 60000 * time.Millisecond,
|
||||
Multiplier: 1.30,
|
||||
})
|
||||
}),
|
||||
},
|
||||
UpdateDocument: []gax.CallOption{},
|
||||
DeleteDocument: []gax.CallOption{
|
||||
gax.WithRetry(func() gax.Retryer {
|
||||
return gax.OnCodes([]codes.Code{
|
||||
codes.Unavailable,
|
||||
codes.Internal,
|
||||
codes.DeadlineExceeded,
|
||||
}, gax.Backoff{
|
||||
Initial: 100 * time.Millisecond,
|
||||
Max: 60000 * time.Millisecond,
|
||||
Multiplier: 1.30,
|
||||
})
|
||||
}),
|
||||
},
|
||||
BatchGetDocuments: []gax.CallOption{
|
||||
gax.WithRetry(func() gax.Retryer {
|
||||
return gax.OnCodes([]codes.Code{
|
||||
codes.Unavailable,
|
||||
codes.Internal,
|
||||
codes.DeadlineExceeded,
|
||||
}, gax.Backoff{
|
||||
Initial: 100 * time.Millisecond,
|
||||
Max: 60000 * time.Millisecond,
|
||||
Multiplier: 1.30,
|
||||
})
|
||||
}),
|
||||
},
|
||||
BeginTransaction: []gax.CallOption{
|
||||
gax.WithRetry(func() gax.Retryer {
|
||||
return gax.OnCodes([]codes.Code{
|
||||
codes.Unavailable,
|
||||
codes.Internal,
|
||||
codes.DeadlineExceeded,
|
||||
}, gax.Backoff{
|
||||
Initial: 100 * time.Millisecond,
|
||||
Max: 60000 * time.Millisecond,
|
||||
Multiplier: 1.30,
|
||||
})
|
||||
}),
|
||||
},
|
||||
Commit: []gax.CallOption{},
|
||||
Rollback: []gax.CallOption{
|
||||
gax.WithRetry(func() gax.Retryer {
|
||||
return gax.OnCodes([]codes.Code{
|
||||
codes.Unavailable,
|
||||
codes.Internal,
|
||||
codes.DeadlineExceeded,
|
||||
}, gax.Backoff{
|
||||
Initial: 100 * time.Millisecond,
|
||||
Max: 60000 * time.Millisecond,
|
||||
Multiplier: 1.30,
|
||||
})
|
||||
}),
|
||||
},
|
||||
RunQuery: []gax.CallOption{
|
||||
gax.WithRetry(func() gax.Retryer {
|
||||
return gax.OnCodes([]codes.Code{
|
||||
codes.Unavailable,
|
||||
codes.Internal,
|
||||
codes.DeadlineExceeded,
|
||||
}, gax.Backoff{
|
||||
Initial: 100 * time.Millisecond,
|
||||
Max: 60000 * time.Millisecond,
|
||||
Multiplier: 1.30,
|
||||
})
|
||||
}),
|
||||
},
|
||||
Write: []gax.CallOption{},
|
||||
Listen: []gax.CallOption{
|
||||
gax.WithRetry(func() gax.Retryer {
|
||||
return gax.OnCodes([]codes.Code{
|
||||
codes.Unavailable,
|
||||
codes.Internal,
|
||||
codes.DeadlineExceeded,
|
||||
}, gax.Backoff{
|
||||
Initial: 100 * time.Millisecond,
|
||||
Max: 60000 * time.Millisecond,
|
||||
Multiplier: 1.30,
|
||||
})
|
||||
}),
|
||||
},
|
||||
ListCollectionIds: []gax.CallOption{
|
||||
gax.WithRetry(func() gax.Retryer {
|
||||
return gax.OnCodes([]codes.Code{
|
||||
codes.Unavailable,
|
||||
codes.Internal,
|
||||
codes.DeadlineExceeded,
|
||||
}, gax.Backoff{
|
||||
Initial: 100 * time.Millisecond,
|
||||
Max: 60000 * time.Millisecond,
|
||||
Multiplier: 1.30,
|
||||
})
|
||||
}),
|
||||
},
|
||||
CreateDocument: []gax.CallOption{},
|
||||
}
|
||||
}
|
||||
|
||||
// Client is a client for interacting with Cloud Firestore API.
|
||||
//
|
||||
// Methods, except Close, may be called concurrently. However, fields must not be modified concurrently with method calls.
|
||||
type Client struct {
|
||||
// Connection pool of gRPC connections to the service.
|
||||
connPool gtransport.ConnPool
|
||||
|
||||
// The gRPC API client.
|
||||
client firestorepb.FirestoreClient
|
||||
|
||||
// The call options for this service.
|
||||
CallOptions *CallOptions
|
||||
|
||||
// The x-goog-* metadata to be sent with each request.
|
||||
xGoogMetadata metadata.MD
|
||||
}
|
||||
|
||||
// NewClient creates a new firestore client.
|
||||
//
|
||||
// The Cloud Firestore service.
|
||||
//
|
||||
// Cloud Firestore is a fast, fully managed, serverless, cloud-native NoSQL
|
||||
// document database that simplifies storing, syncing, and querying data for
|
||||
// your mobile, web, and IoT apps at global scale. Its client libraries provide
|
||||
// live synchronization and offline support, while its security features and
|
||||
// integrations with Firebase and Google Cloud Platform (GCP) accelerate
|
||||
// building truly serverless apps.
|
||||
func NewClient(ctx context.Context, opts ...option.ClientOption) (*Client, error) {
|
||||
connPool, err := gtransport.DialPool(ctx, append(defaultClientOptions(), opts...)...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
c := &Client{
|
||||
connPool: connPool,
|
||||
CallOptions: defaultCallOptions(),
|
||||
|
||||
client: firestorepb.NewFirestoreClient(connPool),
|
||||
}
|
||||
c.SetGoogleClientInfo()
|
||||
|
||||
return c, nil
|
||||
}
|
||||
|
||||
// Connection returns a connection to the API service.
|
||||
//
|
||||
// Deprecated.
|
||||
func (c *Client) Connection() *grpc.ClientConn {
|
||||
return c.connPool.Conn()
|
||||
}
|
||||
|
||||
// Close closes the connection to the API service. The user should invoke this when
|
||||
// the client is no longer required.
|
||||
func (c *Client) Close() error {
|
||||
return c.connPool.Close()
|
||||
}
|
||||
|
||||
// SetGoogleClientInfo sets the name and version of the application in
|
||||
// the `x-goog-api-client` header passed on each request. Intended for
|
||||
// use by Google-written clients.
|
||||
func (c *Client) SetGoogleClientInfo(keyval ...string) {
|
||||
kv := append([]string{"gl-go", versionGo()}, keyval...)
|
||||
kv = append(kv, "gapic", versionClient, "gax", gax.Version, "grpc", grpc.Version)
|
||||
c.xGoogMetadata = metadata.Pairs("x-goog-api-client", gax.XGoogHeader(kv...))
|
||||
}
|
||||
|
||||
// GetDocument gets a single document.
|
||||
func (c *Client) GetDocument(ctx context.Context, req *firestorepb.GetDocumentRequest, opts ...gax.CallOption) (*firestorepb.Document, error) {
|
||||
md := metadata.Pairs("x-goog-request-params", fmt.Sprintf("%s=%v", "name", url.QueryEscape(req.GetName())))
|
||||
ctx = insertMetadata(ctx, c.xGoogMetadata, md)
|
||||
opts = append(c.CallOptions.GetDocument[0:len(c.CallOptions.GetDocument):len(c.CallOptions.GetDocument)], opts...)
|
||||
var resp *firestorepb.Document
|
||||
err := gax.Invoke(ctx, func(ctx context.Context, settings gax.CallSettings) error {
|
||||
var err error
|
||||
resp, err = c.client.GetDocument(ctx, req, settings.GRPC...)
|
||||
return err
|
||||
}, opts...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return resp, nil
|
||||
}
|
||||
|
||||
// ListDocuments lists documents.
|
||||
func (c *Client) ListDocuments(ctx context.Context, req *firestorepb.ListDocumentsRequest, opts ...gax.CallOption) *DocumentIterator {
|
||||
md := metadata.Pairs("x-goog-request-params", fmt.Sprintf("%s=%v", "parent", url.QueryEscape(req.GetParent())))
|
||||
ctx = insertMetadata(ctx, c.xGoogMetadata, md)
|
||||
opts = append(c.CallOptions.ListDocuments[0:len(c.CallOptions.ListDocuments):len(c.CallOptions.ListDocuments)], opts...)
|
||||
it := &DocumentIterator{}
|
||||
req = proto.Clone(req).(*firestorepb.ListDocumentsRequest)
|
||||
it.InternalFetch = func(pageSize int, pageToken string) ([]*firestorepb.Document, string, error) {
|
||||
var resp *firestorepb.ListDocumentsResponse
|
||||
req.PageToken = pageToken
|
||||
if pageSize > math.MaxInt32 {
|
||||
req.PageSize = math.MaxInt32
|
||||
} else {
|
||||
req.PageSize = int32(pageSize)
|
||||
}
|
||||
err := gax.Invoke(ctx, func(ctx context.Context, settings gax.CallSettings) error {
|
||||
var err error
|
||||
resp, err = c.client.ListDocuments(ctx, req, settings.GRPC...)
|
||||
return err
|
||||
}, opts...)
|
||||
if err != nil {
|
||||
return nil, "", err
|
||||
}
|
||||
|
||||
it.Response = resp
|
||||
return resp.Documents, resp.NextPageToken, nil
|
||||
}
|
||||
fetch := func(pageSize int, pageToken string) (string, error) {
|
||||
items, nextPageToken, err := it.InternalFetch(pageSize, pageToken)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
it.items = append(it.items, items...)
|
||||
return nextPageToken, nil
|
||||
}
|
||||
it.pageInfo, it.nextFunc = iterator.NewPageInfo(fetch, it.bufLen, it.takeBuf)
|
||||
it.pageInfo.MaxSize = int(req.PageSize)
|
||||
it.pageInfo.Token = req.PageToken
|
||||
return it
|
||||
}
|
||||
|
||||
// UpdateDocument updates or inserts a document.
|
||||
func (c *Client) UpdateDocument(ctx context.Context, req *firestorepb.UpdateDocumentRequest, opts ...gax.CallOption) (*firestorepb.Document, error) {
|
||||
md := metadata.Pairs("x-goog-request-params", fmt.Sprintf("%s=%v", "document.name", url.QueryEscape(req.GetDocument().GetName())))
|
||||
ctx = insertMetadata(ctx, c.xGoogMetadata, md)
|
||||
opts = append(c.CallOptions.UpdateDocument[0:len(c.CallOptions.UpdateDocument):len(c.CallOptions.UpdateDocument)], opts...)
|
||||
var resp *firestorepb.Document
|
||||
err := gax.Invoke(ctx, func(ctx context.Context, settings gax.CallSettings) error {
|
||||
var err error
|
||||
resp, err = c.client.UpdateDocument(ctx, req, settings.GRPC...)
|
||||
return err
|
||||
}, opts...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return resp, nil
|
||||
}
|
||||
|
||||
// DeleteDocument deletes a document.
|
||||
func (c *Client) DeleteDocument(ctx context.Context, req *firestorepb.DeleteDocumentRequest, opts ...gax.CallOption) error {
|
||||
md := metadata.Pairs("x-goog-request-params", fmt.Sprintf("%s=%v", "name", url.QueryEscape(req.GetName())))
|
||||
ctx = insertMetadata(ctx, c.xGoogMetadata, md)
|
||||
opts = append(c.CallOptions.DeleteDocument[0:len(c.CallOptions.DeleteDocument):len(c.CallOptions.DeleteDocument)], opts...)
|
||||
err := gax.Invoke(ctx, func(ctx context.Context, settings gax.CallSettings) error {
|
||||
var err error
|
||||
_, err = c.client.DeleteDocument(ctx, req, settings.GRPC...)
|
||||
return err
|
||||
}, opts...)
|
||||
return err
|
||||
}
|
||||
|
||||
// BatchGetDocuments gets multiple documents.
|
||||
//
|
||||
// Documents returned by this method are not guaranteed to be returned in the
|
||||
// same order that they were requested.
|
||||
func (c *Client) BatchGetDocuments(ctx context.Context, req *firestorepb.BatchGetDocumentsRequest, opts ...gax.CallOption) (firestorepb.Firestore_BatchGetDocumentsClient, error) {
|
||||
md := metadata.Pairs("x-goog-request-params", fmt.Sprintf("%s=%v", "database", url.QueryEscape(req.GetDatabase())))
|
||||
ctx = insertMetadata(ctx, c.xGoogMetadata, md)
|
||||
opts = append(c.CallOptions.BatchGetDocuments[0:len(c.CallOptions.BatchGetDocuments):len(c.CallOptions.BatchGetDocuments)], opts...)
|
||||
var resp firestorepb.Firestore_BatchGetDocumentsClient
|
||||
err := gax.Invoke(ctx, func(ctx context.Context, settings gax.CallSettings) error {
|
||||
var err error
|
||||
resp, err = c.client.BatchGetDocuments(ctx, req, settings.GRPC...)
|
||||
return err
|
||||
}, opts...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return resp, nil
|
||||
}
|
||||
|
||||
// BeginTransaction starts a new transaction.
|
||||
func (c *Client) BeginTransaction(ctx context.Context, req *firestorepb.BeginTransactionRequest, opts ...gax.CallOption) (*firestorepb.BeginTransactionResponse, error) {
|
||||
md := metadata.Pairs("x-goog-request-params", fmt.Sprintf("%s=%v", "database", url.QueryEscape(req.GetDatabase())))
|
||||
ctx = insertMetadata(ctx, c.xGoogMetadata, md)
|
||||
opts = append(c.CallOptions.BeginTransaction[0:len(c.CallOptions.BeginTransaction):len(c.CallOptions.BeginTransaction)], opts...)
|
||||
var resp *firestorepb.BeginTransactionResponse
|
||||
err := gax.Invoke(ctx, func(ctx context.Context, settings gax.CallSettings) error {
|
||||
var err error
|
||||
resp, err = c.client.BeginTransaction(ctx, req, settings.GRPC...)
|
||||
return err
|
||||
}, opts...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return resp, nil
|
||||
}
|
||||
|
||||
// Commit commits a transaction, while optionally updating documents.
|
||||
func (c *Client) Commit(ctx context.Context, req *firestorepb.CommitRequest, opts ...gax.CallOption) (*firestorepb.CommitResponse, error) {
|
||||
md := metadata.Pairs("x-goog-request-params", fmt.Sprintf("%s=%v", "database", url.QueryEscape(req.GetDatabase())))
|
||||
ctx = insertMetadata(ctx, c.xGoogMetadata, md)
|
||||
opts = append(c.CallOptions.Commit[0:len(c.CallOptions.Commit):len(c.CallOptions.Commit)], opts...)
|
||||
var resp *firestorepb.CommitResponse
|
||||
err := gax.Invoke(ctx, func(ctx context.Context, settings gax.CallSettings) error {
|
||||
var err error
|
||||
resp, err = c.client.Commit(ctx, req, settings.GRPC...)
|
||||
return err
|
||||
}, opts...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return resp, nil
|
||||
}
|
||||
|
||||
// Rollback rolls back a transaction.
|
||||
func (c *Client) Rollback(ctx context.Context, req *firestorepb.RollbackRequest, opts ...gax.CallOption) error {
|
||||
md := metadata.Pairs("x-goog-request-params", fmt.Sprintf("%s=%v", "database", url.QueryEscape(req.GetDatabase())))
|
||||
ctx = insertMetadata(ctx, c.xGoogMetadata, md)
|
||||
opts = append(c.CallOptions.Rollback[0:len(c.CallOptions.Rollback):len(c.CallOptions.Rollback)], opts...)
|
||||
err := gax.Invoke(ctx, func(ctx context.Context, settings gax.CallSettings) error {
|
||||
var err error
|
||||
_, err = c.client.Rollback(ctx, req, settings.GRPC...)
|
||||
return err
|
||||
}, opts...)
|
||||
return err
|
||||
}
|
||||
|
||||
// RunQuery runs a query.
|
||||
func (c *Client) RunQuery(ctx context.Context, req *firestorepb.RunQueryRequest, opts ...gax.CallOption) (firestorepb.Firestore_RunQueryClient, error) {
|
||||
md := metadata.Pairs("x-goog-request-params", fmt.Sprintf("%s=%v", "parent", url.QueryEscape(req.GetParent())))
|
||||
ctx = insertMetadata(ctx, c.xGoogMetadata, md)
|
||||
opts = append(c.CallOptions.RunQuery[0:len(c.CallOptions.RunQuery):len(c.CallOptions.RunQuery)], opts...)
|
||||
var resp firestorepb.Firestore_RunQueryClient
|
||||
err := gax.Invoke(ctx, func(ctx context.Context, settings gax.CallSettings) error {
|
||||
var err error
|
||||
resp, err = c.client.RunQuery(ctx, req, settings.GRPC...)
|
||||
return err
|
||||
}, opts...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return resp, nil
|
||||
}
|
||||
|
||||
// Write streams batches of document updates and deletes, in order.
|
||||
func (c *Client) Write(ctx context.Context, opts ...gax.CallOption) (firestorepb.Firestore_WriteClient, error) {
|
||||
ctx = insertMetadata(ctx, c.xGoogMetadata)
|
||||
opts = append(c.CallOptions.Write[0:len(c.CallOptions.Write):len(c.CallOptions.Write)], opts...)
|
||||
var resp firestorepb.Firestore_WriteClient
|
||||
err := gax.Invoke(ctx, func(ctx context.Context, settings gax.CallSettings) error {
|
||||
var err error
|
||||
resp, err = c.client.Write(ctx, settings.GRPC...)
|
||||
return err
|
||||
}, opts...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return resp, nil
|
||||
}
|
||||
|
||||
// Listen listens to changes.
|
||||
func (c *Client) Listen(ctx context.Context, opts ...gax.CallOption) (firestorepb.Firestore_ListenClient, error) {
|
||||
ctx = insertMetadata(ctx, c.xGoogMetadata)
|
||||
opts = append(c.CallOptions.Listen[0:len(c.CallOptions.Listen):len(c.CallOptions.Listen)], opts...)
|
||||
var resp firestorepb.Firestore_ListenClient
|
||||
err := gax.Invoke(ctx, func(ctx context.Context, settings gax.CallSettings) error {
|
||||
var err error
|
||||
resp, err = c.client.Listen(ctx, settings.GRPC...)
|
||||
return err
|
||||
}, opts...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return resp, nil
|
||||
}
|
||||
|
||||
// ListCollectionIds lists all the collection IDs underneath a document.
|
||||
func (c *Client) ListCollectionIds(ctx context.Context, req *firestorepb.ListCollectionIdsRequest, opts ...gax.CallOption) *StringIterator {
|
||||
md := metadata.Pairs("x-goog-request-params", fmt.Sprintf("%s=%v", "parent", url.QueryEscape(req.GetParent())))
|
||||
ctx = insertMetadata(ctx, c.xGoogMetadata, md)
|
||||
opts = append(c.CallOptions.ListCollectionIds[0:len(c.CallOptions.ListCollectionIds):len(c.CallOptions.ListCollectionIds)], opts...)
|
||||
it := &StringIterator{}
|
||||
req = proto.Clone(req).(*firestorepb.ListCollectionIdsRequest)
|
||||
it.InternalFetch = func(pageSize int, pageToken string) ([]string, string, error) {
|
||||
var resp *firestorepb.ListCollectionIdsResponse
|
||||
req.PageToken = pageToken
|
||||
if pageSize > math.MaxInt32 {
|
||||
req.PageSize = math.MaxInt32
|
||||
} else {
|
||||
req.PageSize = int32(pageSize)
|
||||
}
|
||||
err := gax.Invoke(ctx, func(ctx context.Context, settings gax.CallSettings) error {
|
||||
var err error
|
||||
resp, err = c.client.ListCollectionIds(ctx, req, settings.GRPC...)
|
||||
return err
|
||||
}, opts...)
|
||||
if err != nil {
|
||||
return nil, "", err
|
||||
}
|
||||
|
||||
it.Response = resp
|
||||
return resp.CollectionIds, resp.NextPageToken, nil
|
||||
}
|
||||
fetch := func(pageSize int, pageToken string) (string, error) {
|
||||
items, nextPageToken, err := it.InternalFetch(pageSize, pageToken)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
it.items = append(it.items, items...)
|
||||
return nextPageToken, nil
|
||||
}
|
||||
it.pageInfo, it.nextFunc = iterator.NewPageInfo(fetch, it.bufLen, it.takeBuf)
|
||||
it.pageInfo.MaxSize = int(req.PageSize)
|
||||
it.pageInfo.Token = req.PageToken
|
||||
return it
|
||||
}
|
||||
|
||||
// CreateDocument creates a new document.
|
||||
func (c *Client) CreateDocument(ctx context.Context, req *firestorepb.CreateDocumentRequest, opts ...gax.CallOption) (*firestorepb.Document, error) {
|
||||
md := metadata.Pairs("x-goog-request-params", fmt.Sprintf("%s=%v", "parent", url.QueryEscape(req.GetParent())))
|
||||
ctx = insertMetadata(ctx, c.xGoogMetadata, md)
|
||||
opts = append(c.CallOptions.CreateDocument[0:len(c.CallOptions.CreateDocument):len(c.CallOptions.CreateDocument)], opts...)
|
||||
var resp *firestorepb.Document
|
||||
err := gax.Invoke(ctx, func(ctx context.Context, settings gax.CallSettings) error {
|
||||
var err error
|
||||
resp, err = c.client.CreateDocument(ctx, req, settings.GRPC...)
|
||||
return err
|
||||
}, opts...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return resp, nil
|
||||
}
|
||||
|
||||
// DocumentIterator manages a stream of *firestorepb.Document.
|
||||
type DocumentIterator struct {
|
||||
items []*firestorepb.Document
|
||||
pageInfo *iterator.PageInfo
|
||||
nextFunc func() error
|
||||
|
||||
// Response is the raw response for the current page.
|
||||
// It must be cast to the RPC response type.
|
||||
// Calling Next() or InternalFetch() updates this value.
|
||||
Response interface{}
|
||||
|
||||
// InternalFetch is for use by the Google Cloud Libraries only.
|
||||
// It is not part of the stable interface of this package.
|
||||
//
|
||||
// InternalFetch returns results from a single call to the underlying RPC.
|
||||
// The number of results is no greater than pageSize.
|
||||
// If there are no more results, nextPageToken is empty and err is nil.
|
||||
InternalFetch func(pageSize int, pageToken string) (results []*firestorepb.Document, nextPageToken string, err error)
|
||||
}
|
||||
|
||||
// PageInfo supports pagination. See the google.golang.org/api/iterator package for details.
|
||||
func (it *DocumentIterator) PageInfo() *iterator.PageInfo {
|
||||
return it.pageInfo
|
||||
}
|
||||
|
||||
// Next returns the next result. Its second return value is iterator.Done if there are no more
|
||||
// results. Once Next returns Done, all subsequent calls will return Done.
|
||||
func (it *DocumentIterator) Next() (*firestorepb.Document, error) {
|
||||
var item *firestorepb.Document
|
||||
if err := it.nextFunc(); err != nil {
|
||||
return item, err
|
||||
}
|
||||
item = it.items[0]
|
||||
it.items = it.items[1:]
|
||||
return item, nil
|
||||
}
|
||||
|
||||
func (it *DocumentIterator) bufLen() int {
|
||||
return len(it.items)
|
||||
}
|
||||
|
||||
func (it *DocumentIterator) takeBuf() interface{} {
|
||||
b := it.items
|
||||
it.items = nil
|
||||
return b
|
||||
}
|
||||
|
||||
// StringIterator manages a stream of string.
|
||||
type StringIterator struct {
|
||||
items []string
|
||||
pageInfo *iterator.PageInfo
|
||||
nextFunc func() error
|
||||
|
||||
// Response is the raw response for the current page.
|
||||
// It must be cast to the RPC response type.
|
||||
// Calling Next() or InternalFetch() updates this value.
|
||||
Response interface{}
|
||||
|
||||
// InternalFetch is for use by the Google Cloud Libraries only.
|
||||
// It is not part of the stable interface of this package.
|
||||
//
|
||||
// InternalFetch returns results from a single call to the underlying RPC.
|
||||
// The number of results is no greater than pageSize.
|
||||
// If there are no more results, nextPageToken is empty and err is nil.
|
||||
InternalFetch func(pageSize int, pageToken string) (results []string, nextPageToken string, err error)
|
||||
}
|
||||
|
||||
// PageInfo supports pagination. See the google.golang.org/api/iterator package for details.
|
||||
func (it *StringIterator) PageInfo() *iterator.PageInfo {
|
||||
return it.pageInfo
|
||||
}
|
||||
|
||||
// Next returns the next result. Its second return value is iterator.Done if there are no more
|
||||
// results. Once Next returns Done, all subsequent calls will return Done.
|
||||
func (it *StringIterator) Next() (string, error) {
|
||||
var item string
|
||||
if err := it.nextFunc(); err != nil {
|
||||
return item, err
|
||||
}
|
||||
item = it.items[0]
|
||||
it.items = it.items[1:]
|
||||
return item, nil
|
||||
}
|
||||
|
||||
func (it *StringIterator) bufLen() int {
|
||||
return len(it.items)
|
||||
}
|
||||
|
||||
func (it *StringIterator) takeBuf() interface{} {
|
||||
b := it.items
|
||||
it.items = nil
|
||||
return b
|
||||
}
|
||||
-346
@@ -1,346 +0,0 @@
|
||||
// Copyright 2017 Google LLC
|
||||
//
|
||||
// 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 firestore
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
vkit "cloud.google.com/go/firestore/apiv1"
|
||||
"cloud.google.com/go/internal/trace"
|
||||
"cloud.google.com/go/internal/version"
|
||||
"github.com/golang/protobuf/ptypes"
|
||||
gax "github.com/googleapis/gax-go/v2"
|
||||
"google.golang.org/api/iterator"
|
||||
"google.golang.org/api/option"
|
||||
"google.golang.org/api/transport"
|
||||
pb "google.golang.org/genproto/googleapis/firestore/v1"
|
||||
"google.golang.org/grpc"
|
||||
"google.golang.org/grpc/codes"
|
||||
"google.golang.org/grpc/metadata"
|
||||
"google.golang.org/grpc/status"
|
||||
)
|
||||
|
||||
// resourcePrefixHeader is the name of the metadata header used to indicate
|
||||
// the resource being operated on.
|
||||
const resourcePrefixHeader = "google-cloud-resource-prefix"
|
||||
|
||||
// DetectProjectID is a sentinel value that instructs NewClient to detect the
|
||||
// project ID. It is given in place of the projectID argument. NewClient will
|
||||
// use the project ID from the given credentials or the default credentials
|
||||
// (https://developers.google.com/accounts/docs/application-default-credentials)
|
||||
// if no credentials were provided. When providing credentials, not all
|
||||
// options will allow NewClient to extract the project ID. Specifically a JWT
|
||||
// does not have the project ID encoded.
|
||||
const DetectProjectID = "*detect-project-id*"
|
||||
|
||||
// A Client provides access to the Firestore service.
|
||||
type Client struct {
|
||||
c *vkit.Client
|
||||
projectID string
|
||||
databaseID string // A client is tied to a single database.
|
||||
}
|
||||
|
||||
// NewClient creates a new Firestore client that uses the given project.
|
||||
func NewClient(ctx context.Context, projectID string, opts ...option.ClientOption) (*Client, error) {
|
||||
var o []option.ClientOption
|
||||
// If this environment variable is defined, configure the client to talk to the emulator.
|
||||
if addr := os.Getenv("FIRESTORE_EMULATOR_HOST"); addr != "" {
|
||||
conn, err := grpc.Dial(addr, grpc.WithInsecure(), grpc.WithPerRPCCredentials(emulatorCreds{}))
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("firestore: dialing address from env var FIRESTORE_EMULATOR_HOST: %s", err)
|
||||
}
|
||||
o = []option.ClientOption{option.WithGRPCConn(conn)}
|
||||
}
|
||||
o = append(o, opts...)
|
||||
|
||||
if projectID == DetectProjectID {
|
||||
creds, err := transport.Creds(ctx, o...)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("fetching creds: %v", err)
|
||||
}
|
||||
if creds.ProjectID == "" {
|
||||
return nil, errors.New("firestore: see the docs on DetectProjectID")
|
||||
}
|
||||
projectID = creds.ProjectID
|
||||
}
|
||||
|
||||
vc, err := vkit.NewClient(ctx, o...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
vc.SetGoogleClientInfo("gccl", version.Repo)
|
||||
c := &Client{
|
||||
c: vc,
|
||||
projectID: projectID,
|
||||
databaseID: "(default)", // always "(default)", for now
|
||||
}
|
||||
return c, nil
|
||||
|
||||
}
|
||||
|
||||
// Close closes any resources held by the client.
|
||||
//
|
||||
// Close need not be called at program exit.
|
||||
func (c *Client) Close() error {
|
||||
return c.c.Close()
|
||||
}
|
||||
|
||||
func (c *Client) path() string {
|
||||
return fmt.Sprintf("projects/%s/databases/%s", c.projectID, c.databaseID)
|
||||
}
|
||||
|
||||
func withResourceHeader(ctx context.Context, resource string) context.Context {
|
||||
md, _ := metadata.FromOutgoingContext(ctx)
|
||||
md = md.Copy()
|
||||
md[resourcePrefixHeader] = []string{resource}
|
||||
return metadata.NewOutgoingContext(ctx, md)
|
||||
}
|
||||
|
||||
// Collection creates a reference to a collection with the given path.
|
||||
// A path is a sequence of IDs separated by slashes.
|
||||
//
|
||||
// Collection returns nil if path contains an even number of IDs or any ID is empty.
|
||||
func (c *Client) Collection(path string) *CollectionRef {
|
||||
coll, _ := c.idsToRef(strings.Split(path, "/"), c.path())
|
||||
return coll
|
||||
}
|
||||
|
||||
// Doc creates a reference to a document with the given path.
|
||||
// A path is a sequence of IDs separated by slashes.
|
||||
//
|
||||
// Doc returns nil if path contains an odd number of IDs or any ID is empty.
|
||||
func (c *Client) Doc(path string) *DocumentRef {
|
||||
_, doc := c.idsToRef(strings.Split(path, "/"), c.path())
|
||||
return doc
|
||||
}
|
||||
|
||||
// CollectionGroup creates a reference to a group of collections that include
|
||||
// the given ID, regardless of parent document.
|
||||
//
|
||||
// For example, consider:
|
||||
// France/Cities/Paris = {population: 100}
|
||||
// Canada/Cities/Montreal = {population: 90}
|
||||
//
|
||||
// CollectionGroup can be used to query across all "Cities" regardless of
|
||||
// its parent "Countries". See ExampleCollectionGroup for a complete example.
|
||||
func (c *Client) CollectionGroup(collectionID string) *CollectionGroupRef {
|
||||
return newCollectionGroupRef(c, c.path(), collectionID)
|
||||
}
|
||||
|
||||
func (c *Client) idsToRef(IDs []string, dbPath string) (*CollectionRef, *DocumentRef) {
|
||||
if len(IDs) == 0 {
|
||||
return nil, nil
|
||||
}
|
||||
for _, id := range IDs {
|
||||
if id == "" {
|
||||
return nil, nil
|
||||
}
|
||||
}
|
||||
coll := newTopLevelCollRef(c, dbPath, IDs[0])
|
||||
i := 1
|
||||
for i < len(IDs) {
|
||||
doc := newDocRef(coll, IDs[i])
|
||||
i++
|
||||
if i == len(IDs) {
|
||||
return nil, doc
|
||||
}
|
||||
coll = newCollRefWithParent(c, doc, IDs[i])
|
||||
i++
|
||||
}
|
||||
return coll, nil
|
||||
}
|
||||
|
||||
// GetAll retrieves multiple documents with a single call. The
|
||||
// DocumentSnapshots are returned in the order of the given DocumentRefs.
|
||||
// The return value will always contain the same number of DocumentSnapshots
|
||||
// as the number of DocumentRefs in the input.
|
||||
//
|
||||
// If the same DocumentRef is specified multiple times in the input, the return
|
||||
// value will contain the same number of DocumentSnapshots referencing the same
|
||||
// document.
|
||||
//
|
||||
// If a document is not present, the corresponding DocumentSnapshot's Exists
|
||||
// method will return false.
|
||||
func (c *Client) GetAll(ctx context.Context, docRefs []*DocumentRef) (_ []*DocumentSnapshot, err error) {
|
||||
ctx = trace.StartSpan(ctx, "cloud.google.com/go/firestore.GetAll")
|
||||
defer func() { trace.EndSpan(ctx, err) }()
|
||||
|
||||
return c.getAll(ctx, docRefs, nil)
|
||||
}
|
||||
|
||||
func (c *Client) getAll(ctx context.Context, docRefs []*DocumentRef, tid []byte) ([]*DocumentSnapshot, error) {
|
||||
var docNames []string
|
||||
docIndices := map[string][]int{} // doc name to positions in docRefs
|
||||
for i, dr := range docRefs {
|
||||
if dr == nil {
|
||||
return nil, errNilDocRef
|
||||
}
|
||||
docNames = append(docNames, dr.Path)
|
||||
docIndices[dr.Path] = append(docIndices[dr.Path], i)
|
||||
}
|
||||
req := &pb.BatchGetDocumentsRequest{
|
||||
Database: c.path(),
|
||||
Documents: docNames,
|
||||
}
|
||||
if tid != nil {
|
||||
req.ConsistencySelector = &pb.BatchGetDocumentsRequest_Transaction{tid}
|
||||
}
|
||||
streamClient, err := c.c.BatchGetDocuments(withResourceHeader(ctx, req.Database), req)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Read and remember all results from the stream.
|
||||
var resps []*pb.BatchGetDocumentsResponse
|
||||
for {
|
||||
resp, err := streamClient.Recv()
|
||||
if err == io.EOF {
|
||||
break
|
||||
}
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
resps = append(resps, resp)
|
||||
}
|
||||
|
||||
// Results may arrive out of order. Put each at the right indices.
|
||||
docs := make([]*DocumentSnapshot, len(docNames))
|
||||
for _, resp := range resps {
|
||||
var (
|
||||
indices []int
|
||||
doc *pb.Document
|
||||
err error
|
||||
)
|
||||
switch r := resp.Result.(type) {
|
||||
case *pb.BatchGetDocumentsResponse_Found:
|
||||
indices = docIndices[r.Found.Name]
|
||||
doc = r.Found
|
||||
case *pb.BatchGetDocumentsResponse_Missing:
|
||||
indices = docIndices[r.Missing]
|
||||
doc = nil
|
||||
default:
|
||||
return nil, errors.New("firestore: unknown BatchGetDocumentsResponse result type")
|
||||
}
|
||||
for _, index := range indices {
|
||||
if docs[index] != nil {
|
||||
return nil, fmt.Errorf("firestore: %q seen twice", docRefs[index].Path)
|
||||
}
|
||||
docs[index], err = newDocumentSnapshot(docRefs[index], doc, c, resp.ReadTime)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
}
|
||||
return docs, nil
|
||||
}
|
||||
|
||||
// Collections returns an iterator over the top-level collections.
|
||||
func (c *Client) Collections(ctx context.Context) *CollectionIterator {
|
||||
it := &CollectionIterator{
|
||||
client: c,
|
||||
it: c.c.ListCollectionIds(
|
||||
withResourceHeader(ctx, c.path()),
|
||||
&pb.ListCollectionIdsRequest{Parent: c.path() + "/documents"}),
|
||||
}
|
||||
it.pageInfo, it.nextFunc = iterator.NewPageInfo(
|
||||
it.fetch,
|
||||
func() int { return len(it.items) },
|
||||
func() interface{} { b := it.items; it.items = nil; return b })
|
||||
return it
|
||||
}
|
||||
|
||||
// Batch returns a WriteBatch.
|
||||
func (c *Client) Batch() *WriteBatch {
|
||||
return &WriteBatch{c: c}
|
||||
}
|
||||
|
||||
// commit calls the Commit RPC outside of a transaction.
|
||||
func (c *Client) commit(ctx context.Context, ws []*pb.Write) ([]*WriteResult, error) {
|
||||
req := &pb.CommitRequest{
|
||||
Database: c.path(),
|
||||
Writes: ws,
|
||||
}
|
||||
res, err := c.c.Commit(withResourceHeader(ctx, req.Database), req)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if len(res.WriteResults) == 0 {
|
||||
return nil, errors.New("firestore: missing WriteResult")
|
||||
}
|
||||
var wrs []*WriteResult
|
||||
for _, pwr := range res.WriteResults {
|
||||
wr, err := writeResultFromProto(pwr)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
wrs = append(wrs, wr)
|
||||
}
|
||||
return wrs, nil
|
||||
}
|
||||
|
||||
func (c *Client) commit1(ctx context.Context, ws []*pb.Write) (*WriteResult, error) {
|
||||
wrs, err := c.commit(ctx, ws)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return wrs[0], nil
|
||||
}
|
||||
|
||||
// A WriteResult is returned by methods that write documents.
|
||||
type WriteResult struct {
|
||||
// The time at which the document was updated, or created if it did not
|
||||
// previously exist. Writes that do not actually change the document do
|
||||
// not change the update time.
|
||||
UpdateTime time.Time
|
||||
}
|
||||
|
||||
func writeResultFromProto(wr *pb.WriteResult) (*WriteResult, error) {
|
||||
t, err := ptypes.Timestamp(wr.UpdateTime)
|
||||
if err != nil {
|
||||
t = time.Time{}
|
||||
// TODO(jba): Follow up if Delete is supposed to return a nil timestamp.
|
||||
}
|
||||
return &WriteResult{UpdateTime: t}, nil
|
||||
}
|
||||
|
||||
func sleep(ctx context.Context, dur time.Duration) error {
|
||||
switch err := gax.Sleep(ctx, dur); err {
|
||||
case context.Canceled:
|
||||
return status.Error(codes.Canceled, "context canceled")
|
||||
case context.DeadlineExceeded:
|
||||
return status.Error(codes.DeadlineExceeded, "context deadline exceeded")
|
||||
default:
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
// emulatorCreds is an instance of grpc.PerRPCCredentials that will configure a
|
||||
// client to act as an admin for the Firestore emulator. It always hardcodes
|
||||
// the "authorization" metadata field to contain "Bearer owner", which the
|
||||
// Firestore emulator accepts as valid admin credentials.
|
||||
type emulatorCreds struct{}
|
||||
|
||||
func (ec emulatorCreds) GetRequestMetadata(ctx context.Context, uri ...string) (map[string]string, error) {
|
||||
return map[string]string{"authorization": "Bearer owner"}, nil
|
||||
}
|
||||
func (ec emulatorCreds) RequireTransportSecurity() bool {
|
||||
return false
|
||||
}
|
||||
-38
@@ -1,38 +0,0 @@
|
||||
// Copyright 2019 Google LLC
|
||||
//
|
||||
// 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 firestore
|
||||
|
||||
// A CollectionGroupRef is a reference to a group of collections sharing the
|
||||
// same ID.
|
||||
type CollectionGroupRef struct {
|
||||
c *Client
|
||||
|
||||
// Use the methods of Query on a CollectionGroupRef to create and run queries.
|
||||
Query
|
||||
}
|
||||
|
||||
func newCollectionGroupRef(c *Client, dbPath, collectionID string) *CollectionGroupRef {
|
||||
return &CollectionGroupRef{
|
||||
c: c,
|
||||
|
||||
Query: Query{
|
||||
c: c,
|
||||
collectionID: collectionID,
|
||||
path: dbPath,
|
||||
parentPath: dbPath + "/documents",
|
||||
allDescendants: true,
|
||||
},
|
||||
}
|
||||
}
|
||||
-138
@@ -1,138 +0,0 @@
|
||||
// Copyright 2017 Google LLC
|
||||
//
|
||||
// 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 firestore
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/rand"
|
||||
"fmt"
|
||||
)
|
||||
|
||||
// A CollectionRef is a reference to Firestore collection.
|
||||
type CollectionRef struct {
|
||||
c *Client
|
||||
|
||||
// The full resource path of the collection's parent. Typically Parent.Path,
|
||||
// or c.path if Parent is nil. May be different if this CollectionRef was
|
||||
// created from a stored reference to a different project/DB. Always
|
||||
// includes /documents - that is, the parent is minimally considered to be
|
||||
// "<db>/documents".
|
||||
//
|
||||
// For example, "projects/P/databases/D/documents/coll-1/doc-1".
|
||||
parentPath string
|
||||
|
||||
// The shorter resource path of the collection. A collection "coll-2" in
|
||||
// document "doc-1" in collection "coll-1" would be: "coll-1/doc-1/coll-2".
|
||||
selfPath string
|
||||
|
||||
// Parent is the document of which this collection is a part. It is
|
||||
// nil for top-level collections.
|
||||
Parent *DocumentRef
|
||||
|
||||
// The full resource path of the collection: "projects/P/databases/D/documents..."
|
||||
Path string
|
||||
|
||||
// ID is the collection identifier.
|
||||
ID string
|
||||
|
||||
// Use the methods of Query on a CollectionRef to create and run queries.
|
||||
Query
|
||||
}
|
||||
|
||||
func newTopLevelCollRef(c *Client, dbPath, id string) *CollectionRef {
|
||||
return &CollectionRef{
|
||||
c: c,
|
||||
ID: id,
|
||||
parentPath: dbPath + "/documents",
|
||||
selfPath: id,
|
||||
Path: dbPath + "/documents/" + id,
|
||||
Query: Query{
|
||||
c: c,
|
||||
collectionID: id,
|
||||
path: dbPath + "/documents/" + id,
|
||||
parentPath: dbPath + "/documents",
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func newCollRefWithParent(c *Client, parent *DocumentRef, id string) *CollectionRef {
|
||||
selfPath := parent.shortPath + "/" + id
|
||||
return &CollectionRef{
|
||||
c: c,
|
||||
Parent: parent,
|
||||
ID: id,
|
||||
parentPath: parent.Path,
|
||||
selfPath: selfPath,
|
||||
Path: parent.Path + "/" + id,
|
||||
Query: Query{
|
||||
c: c,
|
||||
collectionID: id,
|
||||
path: parent.Path + "/" + id,
|
||||
parentPath: parent.Path,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// Doc returns a DocumentRef that refers to the document in the collection with the
|
||||
// given identifier.
|
||||
func (c *CollectionRef) Doc(id string) *DocumentRef {
|
||||
if c == nil {
|
||||
return nil
|
||||
}
|
||||
return newDocRef(c, id)
|
||||
}
|
||||
|
||||
// NewDoc returns a DocumentRef with a uniquely generated ID.
|
||||
//
|
||||
// NewDoc will panic if crypto/rand cannot generate enough bytes to make a new
|
||||
// doc ID.
|
||||
func (c *CollectionRef) NewDoc() *DocumentRef {
|
||||
return c.Doc(uniqueID())
|
||||
}
|
||||
|
||||
// Add generates a DocumentRef with a unique ID. It then creates the document
|
||||
// with the given data, which can be a map[string]interface{}, a struct or a
|
||||
// pointer to a struct.
|
||||
//
|
||||
// Add returns an error in the unlikely event that a document with the same ID
|
||||
// already exists.
|
||||
func (c *CollectionRef) Add(ctx context.Context, data interface{}) (*DocumentRef, *WriteResult, error) {
|
||||
d := c.NewDoc()
|
||||
wr, err := d.Create(ctx, data)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
return d, wr, nil
|
||||
}
|
||||
|
||||
// DocumentRefs returns references to all the documents in the collection, including
|
||||
// missing documents. A missing document is a document that does not exist but has
|
||||
// sub-documents.
|
||||
func (c *CollectionRef) DocumentRefs(ctx context.Context) *DocumentRefIterator {
|
||||
return newDocumentRefIterator(ctx, c, nil)
|
||||
}
|
||||
|
||||
const alphanum = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"
|
||||
|
||||
func uniqueID() string {
|
||||
b := make([]byte, 20)
|
||||
if _, err := rand.Read(b); err != nil {
|
||||
panic(fmt.Sprintf("firestore: crypto/rand.Read error: %v", err))
|
||||
}
|
||||
for i, byt := range b {
|
||||
b[i] = alphanum[int(byt)%len(alphanum)]
|
||||
}
|
||||
return string(b)
|
||||
}
|
||||
-229
@@ -1,229 +0,0 @@
|
||||
// Copyright 2017 Google LLC
|
||||
//
|
||||
// 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.
|
||||
|
||||
// DO NOT EDIT doc.go. Modify internal/doc.template, then run make -C internal.
|
||||
|
||||
/*
|
||||
Package firestore provides a client for reading and writing to a Cloud Firestore
|
||||
database.
|
||||
|
||||
See https://cloud.google.com/firestore/docs for an introduction
|
||||
to Cloud Firestore and additional help on using the Firestore API.
|
||||
|
||||
See https://godoc.org/cloud.google.com/go for authentication, timeouts,
|
||||
connection pooling and similar aspects of this package.
|
||||
|
||||
Note: you can't use both Cloud Firestore and Cloud Datastore in the same
|
||||
project.
|
||||
|
||||
Creating a Client
|
||||
|
||||
To start working with this package, create a client with a project ID:
|
||||
|
||||
ctx := context.Background()
|
||||
client, err := firestore.NewClient(ctx, "projectID")
|
||||
if err != nil {
|
||||
// TODO: Handle error.
|
||||
}
|
||||
|
||||
CollectionRefs and DocumentRefs
|
||||
|
||||
In Firestore, documents are sets of key-value pairs, and collections are groups of
|
||||
documents. A Firestore database consists of a hierarchy of alternating collections
|
||||
and documents, referred to by slash-separated paths like
|
||||
"States/California/Cities/SanFrancisco".
|
||||
|
||||
This client is built around references to collections and documents. CollectionRefs
|
||||
and DocumentRefs are lightweight values that refer to the corresponding database
|
||||
entities. Creating a ref does not involve any network traffic.
|
||||
|
||||
states := client.Collection("States")
|
||||
ny := states.Doc("NewYork")
|
||||
// Or, in a single call:
|
||||
ny = client.Doc("States/NewYork")
|
||||
|
||||
Reading
|
||||
|
||||
Use DocumentRef.Get to read a document. The result is a DocumentSnapshot.
|
||||
Call its Data method to obtain the entire document contents as a map.
|
||||
|
||||
docsnap, err := ny.Get(ctx)
|
||||
if err != nil {
|
||||
// TODO: Handle error.
|
||||
}
|
||||
dataMap := docsnap.Data()
|
||||
fmt.Println(dataMap)
|
||||
|
||||
You can also obtain a single field with DataAt, or extract the data into a struct
|
||||
with DataTo. With the type definition
|
||||
|
||||
type State struct {
|
||||
Capital string `firestore:"capital"`
|
||||
Population float64 `firestore:"pop"` // in millions
|
||||
}
|
||||
|
||||
we can extract the document's data into a value of type State:
|
||||
|
||||
var nyData State
|
||||
if err := docsnap.DataTo(&nyData); err != nil {
|
||||
// TODO: Handle error.
|
||||
}
|
||||
|
||||
Note that this client supports struct tags beginning with "firestore:" that work like
|
||||
the tags of the encoding/json package, letting you rename fields, ignore them, or
|
||||
omit their values when empty.
|
||||
|
||||
To retrieve multiple documents from their references in a single call, use
|
||||
Client.GetAll.
|
||||
|
||||
docsnaps, err := client.GetAll(ctx, []*firestore.DocumentRef{
|
||||
states.Doc("Wisconsin"), states.Doc("Ohio"),
|
||||
})
|
||||
if err != nil {
|
||||
// TODO: Handle error.
|
||||
}
|
||||
for _, ds := range docsnaps {
|
||||
_ = ds // TODO: Use ds.
|
||||
}
|
||||
|
||||
|
||||
Writing
|
||||
|
||||
For writing individual documents, use the methods on DocumentReference.
|
||||
Create creates a new document.
|
||||
|
||||
wr, err := ny.Create(ctx, State{
|
||||
Capital: "Albany",
|
||||
Population: 19.8,
|
||||
})
|
||||
if err != nil {
|
||||
// TODO: Handle error.
|
||||
}
|
||||
fmt.Println(wr)
|
||||
|
||||
The first return value is a WriteResult, which contains the time
|
||||
at which the document was updated.
|
||||
|
||||
Create fails if the document exists. Another method, Set, either replaces an existing
|
||||
document or creates a new one.
|
||||
|
||||
ca := states.Doc("California")
|
||||
_, err = ca.Set(ctx, State{
|
||||
Capital: "Sacramento",
|
||||
Population: 39.14,
|
||||
})
|
||||
|
||||
To update some fields of an existing document, use Update. It takes a list of
|
||||
paths to update and their corresponding values.
|
||||
|
||||
_, err = ca.Update(ctx, []firestore.Update{{Path: "capital", Value: "Sacramento"}})
|
||||
|
||||
Use DocumentRef.Delete to delete a document.
|
||||
|
||||
_, err = ny.Delete(ctx)
|
||||
|
||||
Preconditions
|
||||
|
||||
You can condition Deletes or Updates on when a document was last changed. Specify
|
||||
these preconditions as an option to a Delete or Update method. The check and the
|
||||
write happen atomically with a single RPC.
|
||||
|
||||
docsnap, err = ca.Get(ctx)
|
||||
if err != nil {
|
||||
// TODO: Handle error.
|
||||
}
|
||||
_, err = ca.Update(ctx,
|
||||
[]firestore.Update{{Path: "capital", Value: "Sacramento"}},
|
||||
firestore.LastUpdateTime(docsnap.UpdateTime))
|
||||
|
||||
Here we update a doc only if it hasn't changed since we read it.
|
||||
You could also do this with a transaction.
|
||||
|
||||
To perform multiple writes at once, use a WriteBatch. Its methods chain
|
||||
for convenience.
|
||||
|
||||
WriteBatch.Commit sends the collected writes to the server, where they happen
|
||||
atomically.
|
||||
|
||||
writeResults, err := client.Batch().
|
||||
Create(ny, State{Capital: "Albany"}).
|
||||
Update(ca, []firestore.Update{{Path: "capital", Value: "Sacramento"}}).
|
||||
Delete(client.Doc("States/WestDakota")).
|
||||
Commit(ctx)
|
||||
|
||||
Queries
|
||||
|
||||
You can use SQL to select documents from a collection. Begin with the collection, and
|
||||
build up a query using Select, Where and other methods of Query.
|
||||
|
||||
q := states.Where("pop", ">", 10).OrderBy("pop", firestore.Desc)
|
||||
|
||||
Supported operators include '<', '<=', '>', '>=', '==', 'in', 'array-contains', and
|
||||
'array-contains-any'.
|
||||
|
||||
Call the Query's Documents method to get an iterator, and use it like
|
||||
the other Google Cloud Client iterators.
|
||||
|
||||
iter := q.Documents(ctx)
|
||||
defer iter.Stop()
|
||||
for {
|
||||
doc, err := iter.Next()
|
||||
if err == iterator.Done {
|
||||
break
|
||||
}
|
||||
if err != nil {
|
||||
// TODO: Handle error.
|
||||
}
|
||||
fmt.Println(doc.Data())
|
||||
}
|
||||
|
||||
To get all the documents in a collection, you can use the collection itself
|
||||
as a query.
|
||||
|
||||
iter = client.Collection("States").Documents(ctx)
|
||||
|
||||
Transactions
|
||||
|
||||
Use a transaction to execute reads and writes atomically. All reads must happen
|
||||
before any writes. Transaction creation, commit, rollback and retry are handled for
|
||||
you by the Client.RunTransaction method; just provide a function and use the
|
||||
read and write methods of the Transaction passed to it.
|
||||
|
||||
ny := client.Doc("States/NewYork")
|
||||
err := client.RunTransaction(ctx, func(ctx context.Context, tx *firestore.Transaction) error {
|
||||
doc, err := tx.Get(ny) // tx.Get, NOT ny.Get!
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
pop, err := doc.DataAt("pop")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return tx.Update(ny, []firestore.Update{{Path: "pop", Value: pop.(float64) + 0.2}})
|
||||
})
|
||||
if err != nil {
|
||||
// TODO: Handle error.
|
||||
}
|
||||
|
||||
Google Cloud Firestore Emulator
|
||||
|
||||
This package supports the Cloud Firestore emulator, which is useful for testing and
|
||||
development. Environment variables are used to indicate that Firestore traffic should be
|
||||
directed to the emulator instead of the production Firestore service.
|
||||
|
||||
To install and run the emulator and its environment variables, see the documentation
|
||||
at https://cloud.google.com/sdk/gcloud/reference/beta/emulators/firestore/. Once the
|
||||
emulator is running, set FIRESTORE_EMULATOR_HOST to the API endpoint.
|
||||
*/
|
||||
package firestore
|
||||
-739
@@ -1,739 +0,0 @@
|
||||
// Copyright 2017 Google LLC
|
||||
//
|
||||
// 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 firestore
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"reflect"
|
||||
"sort"
|
||||
|
||||
vkit "cloud.google.com/go/firestore/apiv1"
|
||||
"cloud.google.com/go/internal/trace"
|
||||
"google.golang.org/api/iterator"
|
||||
pb "google.golang.org/genproto/googleapis/firestore/v1"
|
||||
"google.golang.org/grpc/codes"
|
||||
"google.golang.org/grpc/status"
|
||||
)
|
||||
|
||||
var errNilDocRef = errors.New("firestore: nil DocumentRef")
|
||||
|
||||
// A DocumentRef is a reference to a Firestore document.
|
||||
type DocumentRef struct {
|
||||
// The CollectionRef that this document is a part of. Never nil.
|
||||
Parent *CollectionRef
|
||||
|
||||
// The full resource path of the document. A document "doc-1" in collection
|
||||
// "coll-1" would be: "projects/P/databases/D/documents/coll-1/doc-1".
|
||||
Path string
|
||||
|
||||
// The shorter resource path of the document. A document "doc-1" in
|
||||
// collection "coll-1" would be: "coll-1/doc-1".
|
||||
shortPath string
|
||||
|
||||
// The ID of the document: the last component of the resource path.
|
||||
ID string
|
||||
}
|
||||
|
||||
func newDocRef(parent *CollectionRef, id string) *DocumentRef {
|
||||
return &DocumentRef{
|
||||
Parent: parent,
|
||||
ID: id,
|
||||
Path: parent.Path + "/" + id,
|
||||
shortPath: parent.selfPath + "/" + id,
|
||||
}
|
||||
}
|
||||
|
||||
// Collection returns a reference to sub-collection of this document.
|
||||
func (d *DocumentRef) Collection(id string) *CollectionRef {
|
||||
return newCollRefWithParent(d.Parent.c, d, id)
|
||||
}
|
||||
|
||||
// Get retrieves the document. If the document does not exist, Get return a NotFound error, which
|
||||
// can be checked with
|
||||
// status.Code(err) == codes.NotFound
|
||||
// In that case, Get returns a non-nil DocumentSnapshot whose Exists method return false and whose
|
||||
// ReadTime is the time of the failed read operation.
|
||||
func (d *DocumentRef) Get(ctx context.Context) (_ *DocumentSnapshot, err error) {
|
||||
ctx = trace.StartSpan(ctx, "cloud.google.com/go/firestore.DocumentRef.Get")
|
||||
defer func() { trace.EndSpan(ctx, err) }()
|
||||
|
||||
if d == nil {
|
||||
return nil, errNilDocRef
|
||||
}
|
||||
docsnaps, err := d.Parent.c.getAll(ctx, []*DocumentRef{d}, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
ds := docsnaps[0]
|
||||
if !ds.Exists() {
|
||||
return ds, status.Errorf(codes.NotFound, "%q not found", d.Path)
|
||||
}
|
||||
return ds, nil
|
||||
}
|
||||
|
||||
// Create creates the document with the given data.
|
||||
// It returns an error if a document with the same ID already exists.
|
||||
//
|
||||
// The data argument can be a map with string keys, a struct, or a pointer to a
|
||||
// struct. The map keys or exported struct fields become the fields of the firestore
|
||||
// document.
|
||||
// The values of data are converted to Firestore values as follows:
|
||||
//
|
||||
// - bool converts to Bool.
|
||||
// - string converts to String.
|
||||
// - int, int8, int16, int32 and int64 convert to Integer.
|
||||
// - uint8, uint16 and uint32 convert to Integer. uint, uint64 and uintptr are disallowed,
|
||||
// because they may be able to represent values that cannot be represented in an int64,
|
||||
// which is the underlying type of a Integer.
|
||||
// - float32 and float64 convert to Double.
|
||||
// - []byte converts to Bytes.
|
||||
// - time.Time and *ts.Timestamp convert to Timestamp. ts is the package
|
||||
// "github.com/golang/protobuf/ptypes/timestamp".
|
||||
// - *latlng.LatLng converts to GeoPoint. latlng is the package
|
||||
// "google.golang.org/genproto/googleapis/type/latlng". You should always use
|
||||
// a pointer to a LatLng.
|
||||
// - Slices convert to Array.
|
||||
// - *firestore.DocumentRef converts to Reference.
|
||||
// - Maps and structs convert to Map.
|
||||
// - nils of any type convert to Null.
|
||||
//
|
||||
// Pointers and interface{} are also permitted, and their elements processed
|
||||
// recursively.
|
||||
//
|
||||
// Struct fields can have tags like those used by the encoding/json package. Tags
|
||||
// begin with "firestore:" and are followed by "-", meaning "ignore this field," or
|
||||
// an alternative name for the field. Following the name, these comma-separated
|
||||
// options may be provided:
|
||||
//
|
||||
// - omitempty: Do not encode this field if it is empty. A value is empty
|
||||
// if it is a zero value, or an array, slice or map of length zero.
|
||||
// - serverTimestamp: The field must be of type time.Time. serverTimestamp
|
||||
// is a sentinel token that tells Firestore to substitute the server time
|
||||
// into that field. When writing, if the field has the zero value, the
|
||||
// server will populate the stored document with the time that the request
|
||||
// is processed. However, if the field value is non-zero it won't be saved.
|
||||
func (d *DocumentRef) Create(ctx context.Context, data interface{}) (_ *WriteResult, err error) {
|
||||
ctx = trace.StartSpan(ctx, "cloud.google.com/go/firestore.DocumentRef.Create")
|
||||
defer func() { trace.EndSpan(ctx, err) }()
|
||||
|
||||
ws, err := d.newCreateWrites(data)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return d.Parent.c.commit1(ctx, ws)
|
||||
}
|
||||
|
||||
func (d *DocumentRef) newCreateWrites(data interface{}) ([]*pb.Write, error) {
|
||||
if d == nil {
|
||||
return nil, errNilDocRef
|
||||
}
|
||||
doc, transforms, err := toProtoDocument(data)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
doc.Name = d.Path
|
||||
pc, err := exists(false).preconditionProto()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return d.newUpdateWithTransform(doc, nil, pc, transforms, false), nil
|
||||
}
|
||||
|
||||
// Set creates or overwrites the document with the given data. See DocumentRef.Create
|
||||
// for the acceptable values of data. Without options, Set overwrites the document
|
||||
// completely. Specify one of the Merge options to preserve an existing document's
|
||||
// fields. To delete some fields, use a Merge option with firestore.Delete as the
|
||||
// field value.
|
||||
func (d *DocumentRef) Set(ctx context.Context, data interface{}, opts ...SetOption) (_ *WriteResult, err error) {
|
||||
ctx = trace.StartSpan(ctx, "cloud.google.com/go/firestore.DocumentRef.Set")
|
||||
defer func() { trace.EndSpan(ctx, err) }()
|
||||
|
||||
ws, err := d.newSetWrites(data, opts)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return d.Parent.c.commit1(ctx, ws)
|
||||
}
|
||||
|
||||
func (d *DocumentRef) newSetWrites(data interface{}, opts []SetOption) ([]*pb.Write, error) {
|
||||
if d == nil {
|
||||
return nil, errNilDocRef
|
||||
}
|
||||
if data == nil {
|
||||
return nil, errors.New("firestore: nil document contents")
|
||||
}
|
||||
if len(opts) == 0 { // Set without merge
|
||||
doc, serverTimestampPaths, err := toProtoDocument(data)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
doc.Name = d.Path
|
||||
return d.newUpdateWithTransform(doc, nil, nil, serverTimestampPaths, true), nil
|
||||
}
|
||||
// Set with merge.
|
||||
// This is just like Update, except for the existence precondition.
|
||||
// So we turn data into a list of (FieldPath, interface{}) pairs (fpv's), as we do
|
||||
// for Update.
|
||||
fieldPaths, allPaths, err := processSetOptions(opts)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var fpvs []fpv
|
||||
v := reflect.ValueOf(data)
|
||||
if allPaths {
|
||||
// Set with MergeAll. Collect all the leaves of the map.
|
||||
if v.Kind() != reflect.Map {
|
||||
return nil, errors.New("firestore: MergeAll can only be specified with map data")
|
||||
}
|
||||
if v.Len() == 0 {
|
||||
// Special case: MergeAll with an empty map.
|
||||
return d.newUpdateWithTransform(&pb.Document{Name: d.Path}, []FieldPath{}, nil, nil, true), nil
|
||||
}
|
||||
fpvsFromData(v, nil, &fpvs)
|
||||
} else {
|
||||
// Set with merge paths. Collect only the values at the given paths.
|
||||
for _, fp := range fieldPaths {
|
||||
val, err := getAtPath(v, fp)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
fpvs = append(fpvs, fpv{fp, val})
|
||||
}
|
||||
}
|
||||
return d.fpvsToWrites(fpvs, nil)
|
||||
}
|
||||
|
||||
// fpvsFromData converts v into a list of (FieldPath, value) pairs.
|
||||
func fpvsFromData(v reflect.Value, prefix FieldPath, fpvs *[]fpv) {
|
||||
switch v.Kind() {
|
||||
case reflect.Map:
|
||||
for _, k := range v.MapKeys() {
|
||||
fpvsFromData(v.MapIndex(k), prefix.with(k.String()), fpvs)
|
||||
}
|
||||
case reflect.Interface:
|
||||
fpvsFromData(v.Elem(), prefix, fpvs)
|
||||
|
||||
default:
|
||||
var val interface{}
|
||||
if v.IsValid() {
|
||||
val = v.Interface()
|
||||
}
|
||||
*fpvs = append(*fpvs, fpv{prefix, val})
|
||||
}
|
||||
}
|
||||
|
||||
// Delete deletes the document. If the document doesn't exist, it does nothing
|
||||
// and returns no error.
|
||||
func (d *DocumentRef) Delete(ctx context.Context, preconds ...Precondition) (_ *WriteResult, err error) {
|
||||
ctx = trace.StartSpan(ctx, "cloud.google.com/go/firestore.DocumentRef.Delete")
|
||||
defer func() { trace.EndSpan(ctx, err) }()
|
||||
|
||||
ws, err := d.newDeleteWrites(preconds)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return d.Parent.c.commit1(ctx, ws)
|
||||
}
|
||||
|
||||
func (d *DocumentRef) newDeleteWrites(preconds []Precondition) ([]*pb.Write, error) {
|
||||
if d == nil {
|
||||
return nil, errNilDocRef
|
||||
}
|
||||
pc, err := processPreconditionsForDelete(preconds)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return []*pb.Write{{
|
||||
Operation: &pb.Write_Delete{d.Path},
|
||||
CurrentDocument: pc,
|
||||
}}, nil
|
||||
}
|
||||
|
||||
func (d *DocumentRef) newUpdatePathWrites(updates []Update, preconds []Precondition) ([]*pb.Write, error) {
|
||||
if len(updates) == 0 {
|
||||
return nil, errors.New("firestore: no paths to update")
|
||||
}
|
||||
var fpvs []fpv
|
||||
for _, u := range updates {
|
||||
v, err := u.process()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
fpvs = append(fpvs, v)
|
||||
}
|
||||
pc, err := processPreconditionsForUpdate(preconds)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return d.fpvsToWrites(fpvs, pc)
|
||||
}
|
||||
|
||||
func (d *DocumentRef) fpvsToWrites(fpvs []fpv, pc *pb.Precondition) ([]*pb.Write, error) {
|
||||
// Make sure there are no duplications or prefixes among the field paths.
|
||||
var fps []FieldPath
|
||||
for _, fpv := range fpvs {
|
||||
fps = append(fps, fpv.fieldPath)
|
||||
}
|
||||
if err := checkNoDupOrPrefix(fps); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Process each fpv.
|
||||
var updatePaths []FieldPath
|
||||
var transforms []*pb.DocumentTransform_FieldTransform
|
||||
doc := &pb.Document{
|
||||
Name: d.Path,
|
||||
Fields: map[string]*pb.Value{},
|
||||
}
|
||||
for _, fpv := range fpvs {
|
||||
switch fpv.value.(type) {
|
||||
case arrayUnion:
|
||||
au := fpv.value.(arrayUnion)
|
||||
t, err := arrayUnionTransform(au, fpv.fieldPath)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
transforms = append(transforms, t)
|
||||
case arrayRemove:
|
||||
ar := fpv.value.(arrayRemove)
|
||||
t, err := arrayRemoveTransform(ar, fpv.fieldPath)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
transforms = append(transforms, t)
|
||||
case increment:
|
||||
t, err := incrementTransform(fpv.value.(increment), fpv.fieldPath)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
transforms = append(transforms, t)
|
||||
|
||||
default:
|
||||
switch fpv.value {
|
||||
case Delete:
|
||||
// Send the field path without a corresponding value.
|
||||
updatePaths = append(updatePaths, fpv.fieldPath)
|
||||
|
||||
case ServerTimestamp:
|
||||
// Use the path in a transform operation.
|
||||
transforms = append(transforms, serverTimestamp(fpv.fieldPath.toServiceFieldPath()))
|
||||
|
||||
default:
|
||||
updatePaths = append(updatePaths, fpv.fieldPath)
|
||||
// Convert the value to a proto and put it into the document.
|
||||
v := reflect.ValueOf(fpv.value)
|
||||
|
||||
pv, _, err := toProtoValue(v)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
setAtPath(doc.Fields, fpv.fieldPath, pv)
|
||||
// Also accumulate any transforms within the value.
|
||||
ts, err := extractTransforms(v, fpv.fieldPath)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
transforms = append(transforms, ts...)
|
||||
}
|
||||
}
|
||||
}
|
||||
return d.newUpdateWithTransform(doc, updatePaths, pc, transforms, false), nil
|
||||
}
|
||||
|
||||
// newUpdateWithTransform constructs operations for a commit. Most generally, it
|
||||
// returns an update operation followed by a transform.
|
||||
//
|
||||
// If there are no serverTimestampPaths, the transform is omitted.
|
||||
//
|
||||
// If doc.Fields is empty, there are no updatePaths, and there is no precondition,
|
||||
// the update is omitted, unless updateOnEmpty is true.
|
||||
func (d *DocumentRef) newUpdateWithTransform(doc *pb.Document, updatePaths []FieldPath, pc *pb.Precondition, transforms []*pb.DocumentTransform_FieldTransform, updateOnEmpty bool) []*pb.Write {
|
||||
var ws []*pb.Write
|
||||
if updateOnEmpty || len(doc.Fields) > 0 ||
|
||||
len(updatePaths) > 0 || (pc != nil && len(transforms) == 0) {
|
||||
var mask *pb.DocumentMask
|
||||
if updatePaths != nil {
|
||||
sfps := toServiceFieldPaths(updatePaths)
|
||||
sort.Strings(sfps) // TODO(jba): make tests pass without this
|
||||
mask = &pb.DocumentMask{FieldPaths: sfps}
|
||||
}
|
||||
w := &pb.Write{
|
||||
Operation: &pb.Write_Update{doc},
|
||||
UpdateMask: mask,
|
||||
CurrentDocument: pc,
|
||||
}
|
||||
ws = append(ws, w)
|
||||
pc = nil // If the precondition is in the write, we don't need it in the transform.
|
||||
}
|
||||
if len(transforms) > 0 || pc != nil {
|
||||
ws = append(ws, &pb.Write{
|
||||
Operation: &pb.Write_Transform{
|
||||
Transform: &pb.DocumentTransform{
|
||||
Document: d.Path,
|
||||
FieldTransforms: transforms,
|
||||
},
|
||||
},
|
||||
CurrentDocument: pc,
|
||||
})
|
||||
}
|
||||
return ws
|
||||
}
|
||||
|
||||
// arrayUnion is a special type in firestore. It instructs the server to add its
|
||||
// elements to whatever array already exists, or to create an array if no value
|
||||
// exists.
|
||||
type arrayUnion struct {
|
||||
elems []interface{}
|
||||
}
|
||||
|
||||
// ArrayUnion specifies elements to be added to whatever array already exists in
|
||||
// the server, or to create an array if no value exists.
|
||||
//
|
||||
// If a value exists and it's an array, values are appended to it. Any duplicate
|
||||
// value is ignored.
|
||||
// If a value exists and it's not an array, the value is replaced by an array of
|
||||
// the values in the ArrayUnion.
|
||||
// If a value does not exist, an array of the values in the ArrayUnion is created.
|
||||
//
|
||||
// ArrayUnion must be the value of a field directly; it cannot appear in
|
||||
// array or struct values, or in any value that is itself inside an array or
|
||||
// struct.
|
||||
func ArrayUnion(elems ...interface{}) arrayUnion {
|
||||
return arrayUnion{elems: elems}
|
||||
}
|
||||
|
||||
// This helper converts an arrayUnion into a proto object.
|
||||
func arrayUnionTransform(au arrayUnion, fp FieldPath) (*pb.DocumentTransform_FieldTransform, error) {
|
||||
var elems []*pb.Value
|
||||
for _, v := range au.elems {
|
||||
pv, _, err := toProtoValue(reflect.ValueOf(v))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
elems = append(elems, pv)
|
||||
}
|
||||
return &pb.DocumentTransform_FieldTransform{
|
||||
FieldPath: fp.toServiceFieldPath(),
|
||||
TransformType: &pb.DocumentTransform_FieldTransform_AppendMissingElements{
|
||||
AppendMissingElements: &pb.ArrayValue{Values: elems},
|
||||
},
|
||||
}, nil
|
||||
}
|
||||
|
||||
// arrayRemove is a special type in firestore. It instructs the server to remove
|
||||
// the specified values.
|
||||
type arrayRemove struct {
|
||||
elems []interface{}
|
||||
}
|
||||
|
||||
// ArrayRemove specifies elements to be removed from whatever array already
|
||||
// exists in the server.
|
||||
//
|
||||
// If a value exists and it's an array, values are removed from it. All
|
||||
// duplicate values are removed.
|
||||
// If a value exists and it's not an array, the value is replaced by an empty
|
||||
// array.
|
||||
// If a value does not exist, an empty array is created.
|
||||
//
|
||||
// ArrayRemove must be the value of a field directly; it cannot appear in
|
||||
// array or struct values, or in any value that is itself inside an array or
|
||||
// struct.
|
||||
func ArrayRemove(elems ...interface{}) arrayRemove {
|
||||
return arrayRemove{elems: elems}
|
||||
}
|
||||
|
||||
// This helper converts an arrayRemove into a proto object.
|
||||
func arrayRemoveTransform(ar arrayRemove, fp FieldPath) (*pb.DocumentTransform_FieldTransform, error) {
|
||||
var elems []*pb.Value
|
||||
for _, v := range ar.elems {
|
||||
// ServerTimestamp cannot occur in an array, so we ignore transformations here.
|
||||
pv, _, err := toProtoValue(reflect.ValueOf(v))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
elems = append(elems, pv)
|
||||
}
|
||||
return &pb.DocumentTransform_FieldTransform{
|
||||
FieldPath: fp.toServiceFieldPath(),
|
||||
TransformType: &pb.DocumentTransform_FieldTransform_RemoveAllFromArray{
|
||||
RemoveAllFromArray: &pb.ArrayValue{Values: elems},
|
||||
},
|
||||
}, nil
|
||||
}
|
||||
|
||||
type increment struct {
|
||||
n interface{}
|
||||
}
|
||||
|
||||
// Increment returns a special value that can be used with Set, Create, or
|
||||
// Update that tells the server to increment the field's current value
|
||||
// by the given value.
|
||||
//
|
||||
// The supported values are:
|
||||
//
|
||||
// int, int8, int16, int32, int64
|
||||
// uint8, uint16, uint32
|
||||
// float32, float64
|
||||
//
|
||||
// If the field does not yet exist, the transformation will set the field to
|
||||
// the given value.
|
||||
func Increment(n interface{}) increment {
|
||||
return increment{n: n}
|
||||
}
|
||||
|
||||
func incrementTransform(ar increment, fp FieldPath) (*pb.DocumentTransform_FieldTransform, error) {
|
||||
switch ar.n.(type) {
|
||||
case int, int8, int16, int32, int64,
|
||||
uint8, uint16, uint32,
|
||||
float32, float64:
|
||||
default:
|
||||
return nil, fmt.Errorf("unsupported type %T for Increment; supported values include int, int8, int16, int32, int64, uint8, uint16, uint32, float32, float64", ar.n)
|
||||
}
|
||||
|
||||
v, _, err := toProtoValue(reflect.ValueOf(ar.n))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &pb.DocumentTransform_FieldTransform{
|
||||
FieldPath: fp.toServiceFieldPath(),
|
||||
TransformType: &pb.DocumentTransform_FieldTransform_Increment{
|
||||
Increment: v,
|
||||
},
|
||||
}, nil
|
||||
}
|
||||
|
||||
type sentinel int
|
||||
|
||||
const (
|
||||
// Delete is used as a value in a call to Update or Set with merge to indicate
|
||||
// that the corresponding key should be deleted.
|
||||
Delete sentinel = iota
|
||||
|
||||
// ServerTimestamp is used as a value in a call to Update to indicate that the
|
||||
// key's value should be set to the time at which the server processed
|
||||
// the request.
|
||||
//
|
||||
// ServerTimestamp must be the value of a field directly; it cannot appear in
|
||||
// array or struct values, or in any value that is itself inside an array or
|
||||
// struct.
|
||||
ServerTimestamp
|
||||
)
|
||||
|
||||
func (s sentinel) String() string {
|
||||
switch s {
|
||||
case Delete:
|
||||
return "Delete"
|
||||
case ServerTimestamp:
|
||||
return "ServerTimestamp"
|
||||
default:
|
||||
return "<?sentinel?>"
|
||||
}
|
||||
}
|
||||
|
||||
// An Update describes an update to a value referred to by a path.
|
||||
// An Update should have either a non-empty Path or a non-empty FieldPath,
|
||||
// but not both.
|
||||
//
|
||||
// See DocumentRef.Create for acceptable values.
|
||||
// To delete a field, specify firestore.Delete as the value.
|
||||
type Update struct {
|
||||
Path string // Will be split on dots, and must not contain any of "˜*/[]".
|
||||
FieldPath FieldPath
|
||||
Value interface{}
|
||||
}
|
||||
|
||||
// An fpv is a pair of validated FieldPath and value.
|
||||
type fpv struct {
|
||||
fieldPath FieldPath
|
||||
value interface{}
|
||||
}
|
||||
|
||||
func (u *Update) process() (fpv, error) {
|
||||
if (u.Path != "") == (u.FieldPath != nil) {
|
||||
return fpv{}, fmt.Errorf("firestore: update %+v should have exactly one of Path or FieldPath", u)
|
||||
}
|
||||
fp := u.FieldPath
|
||||
var err error
|
||||
if fp == nil {
|
||||
fp, err = parseDotSeparatedString(u.Path)
|
||||
if err != nil {
|
||||
return fpv{}, err
|
||||
}
|
||||
}
|
||||
if err := fp.validate(); err != nil {
|
||||
return fpv{}, err
|
||||
}
|
||||
return fpv{fp, u.Value}, nil
|
||||
}
|
||||
|
||||
// Update updates the document. The values at the given
|
||||
// field paths are replaced, but other fields of the stored document are untouched.
|
||||
func (d *DocumentRef) Update(ctx context.Context, updates []Update, preconds ...Precondition) (_ *WriteResult, err error) {
|
||||
ctx = trace.StartSpan(ctx, "cloud.google.com/go/firestore.DocumentRef.Update")
|
||||
defer func() { trace.EndSpan(ctx, err) }()
|
||||
|
||||
ws, err := d.newUpdatePathWrites(updates, preconds)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return d.Parent.c.commit1(ctx, ws)
|
||||
}
|
||||
|
||||
// Collections returns an iterator over the immediate sub-collections of the document.
|
||||
func (d *DocumentRef) Collections(ctx context.Context) *CollectionIterator {
|
||||
client := d.Parent.c
|
||||
it := &CollectionIterator{
|
||||
client: client,
|
||||
parent: d,
|
||||
it: client.c.ListCollectionIds(
|
||||
withResourceHeader(ctx, client.path()),
|
||||
&pb.ListCollectionIdsRequest{Parent: d.Path}),
|
||||
}
|
||||
it.pageInfo, it.nextFunc = iterator.NewPageInfo(
|
||||
it.fetch,
|
||||
func() int { return len(it.items) },
|
||||
func() interface{} { b := it.items; it.items = nil; return b })
|
||||
return it
|
||||
}
|
||||
|
||||
// CollectionIterator is an iterator over sub-collections of a document.
|
||||
type CollectionIterator struct {
|
||||
client *Client
|
||||
parent *DocumentRef
|
||||
it *vkit.StringIterator
|
||||
pageInfo *iterator.PageInfo
|
||||
nextFunc func() error
|
||||
items []*CollectionRef
|
||||
err error
|
||||
}
|
||||
|
||||
// PageInfo supports pagination. See the google.golang.org/api/iterator package for details.
|
||||
func (it *CollectionIterator) PageInfo() *iterator.PageInfo { return it.pageInfo }
|
||||
|
||||
// Next returns the next result. Its second return value is iterator.Done if there
|
||||
// are no more results. Once Next returns Done, all subsequent calls will return
|
||||
// Done.
|
||||
func (it *CollectionIterator) Next() (*CollectionRef, error) {
|
||||
if err := it.nextFunc(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
item := it.items[0]
|
||||
it.items = it.items[1:]
|
||||
return item, nil
|
||||
}
|
||||
|
||||
func (it *CollectionIterator) fetch(pageSize int, pageToken string) (string, error) {
|
||||
if it.err != nil {
|
||||
return "", it.err
|
||||
}
|
||||
return iterFetch(pageSize, pageToken, it.it.PageInfo(), func() error {
|
||||
id, err := it.it.Next()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
var cr *CollectionRef
|
||||
if it.parent == nil {
|
||||
cr = newTopLevelCollRef(it.client, it.client.path(), id)
|
||||
} else {
|
||||
cr = newCollRefWithParent(it.client, it.parent, id)
|
||||
}
|
||||
it.items = append(it.items, cr)
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
// GetAll returns all the collections remaining from the iterator.
|
||||
func (it *CollectionIterator) GetAll() ([]*CollectionRef, error) {
|
||||
var crs []*CollectionRef
|
||||
for {
|
||||
cr, err := it.Next()
|
||||
if err == iterator.Done {
|
||||
break
|
||||
}
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
crs = append(crs, cr)
|
||||
}
|
||||
return crs, nil
|
||||
}
|
||||
|
||||
// Common fetch code for iterators that are backed by vkit iterators.
|
||||
// TODO(jba): dedup with same function in logging/logadmin.
|
||||
func iterFetch(pageSize int, pageToken string, pi *iterator.PageInfo, next func() error) (string, error) {
|
||||
pi.MaxSize = pageSize
|
||||
pi.Token = pageToken
|
||||
// Get one item, which will fill the buffer.
|
||||
if err := next(); err != nil {
|
||||
return "", err
|
||||
}
|
||||
// Collect the rest of the buffer.
|
||||
for pi.Remaining() > 0 {
|
||||
if err := next(); err != nil {
|
||||
return "", err
|
||||
}
|
||||
}
|
||||
return pi.Token, nil
|
||||
}
|
||||
|
||||
// Snapshots returns an iterator over snapshots of the document. Each time the document
|
||||
// changes or is added or deleted, a new snapshot will be generated.
|
||||
func (d *DocumentRef) Snapshots(ctx context.Context) *DocumentSnapshotIterator {
|
||||
return &DocumentSnapshotIterator{
|
||||
docref: d,
|
||||
ws: newWatchStreamForDocument(ctx, d),
|
||||
}
|
||||
}
|
||||
|
||||
// DocumentSnapshotIterator is an iterator over snapshots of a document.
|
||||
// Call Next on the iterator to get a snapshot of the document each time it changes.
|
||||
// Call Stop on the iterator when done.
|
||||
//
|
||||
// For an example, see DocumentRef.Snapshots.
|
||||
type DocumentSnapshotIterator struct {
|
||||
docref *DocumentRef
|
||||
ws *watchStream
|
||||
}
|
||||
|
||||
// Next blocks until the document changes, then returns the DocumentSnapshot for
|
||||
// the current state of the document. If the document has been deleted, Next
|
||||
// returns a DocumentSnapshot whose Exists method returns false.
|
||||
//
|
||||
// Next never returns iterator.Done unless it is called after Stop.
|
||||
func (it *DocumentSnapshotIterator) Next() (*DocumentSnapshot, error) {
|
||||
btree, _, readTime, err := it.ws.nextSnapshot()
|
||||
if err != nil {
|
||||
if err == io.EOF {
|
||||
err = iterator.Done
|
||||
}
|
||||
// watchStream's error is sticky, so SnapshotIterator does not need to remember it.
|
||||
return nil, err
|
||||
}
|
||||
if btree.Len() == 0 { // document deleted
|
||||
return &DocumentSnapshot{Ref: it.docref, ReadTime: readTime}, nil
|
||||
}
|
||||
snap, _ := btree.At(0)
|
||||
return snap.(*DocumentSnapshot), nil
|
||||
}
|
||||
|
||||
// Stop stops receiving snapshots. You should always call Stop when you are done with
|
||||
// a DocumentSnapshotIterator, to free up resources. It is not safe to call Stop
|
||||
// concurrently with Next.
|
||||
func (it *DocumentSnapshotIterator) Stop() {
|
||||
it.ws.stop()
|
||||
}
|
||||
-328
@@ -1,328 +0,0 @@
|
||||
// Copyright 2017 Google LLC
|
||||
//
|
||||
// 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 firestore
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"reflect"
|
||||
"time"
|
||||
|
||||
"github.com/golang/protobuf/ptypes"
|
||||
tspb "github.com/golang/protobuf/ptypes/timestamp"
|
||||
pb "google.golang.org/genproto/googleapis/firestore/v1"
|
||||
"google.golang.org/grpc/codes"
|
||||
"google.golang.org/grpc/status"
|
||||
)
|
||||
|
||||
// A DocumentSnapshot contains document data and metadata.
|
||||
type DocumentSnapshot struct {
|
||||
// The DocumentRef for this document.
|
||||
Ref *DocumentRef
|
||||
|
||||
// Read-only. The time at which the document was created.
|
||||
// Increases monotonically when a document is deleted then
|
||||
// recreated. It can also be compared to values from other documents and
|
||||
// the read time of a query.
|
||||
CreateTime time.Time
|
||||
|
||||
// Read-only. The time at which the document was last changed. This value
|
||||
// is initially set to CreateTime then increases monotonically with each
|
||||
// change to the document. It can also be compared to values from other
|
||||
// documents and the read time of a query.
|
||||
UpdateTime time.Time
|
||||
|
||||
// Read-only. The time at which the document was read.
|
||||
ReadTime time.Time
|
||||
|
||||
c *Client
|
||||
proto *pb.Document
|
||||
}
|
||||
|
||||
// Exists reports whether the DocumentSnapshot represents an existing document.
|
||||
// Even if Exists returns false, the Ref and ReadTime fields of the DocumentSnapshot
|
||||
// are valid.
|
||||
func (d *DocumentSnapshot) Exists() bool {
|
||||
return d.proto != nil
|
||||
}
|
||||
|
||||
// Data returns the DocumentSnapshot's fields as a map.
|
||||
// It is equivalent to
|
||||
// var m map[string]interface{}
|
||||
// d.DataTo(&m)
|
||||
// except that it returns nil if the document does not exist.
|
||||
func (d *DocumentSnapshot) Data() map[string]interface{} {
|
||||
if !d.Exists() {
|
||||
return nil
|
||||
}
|
||||
m, err := createMapFromValueMap(d.proto.Fields, d.c)
|
||||
// Any error here is a bug in the client.
|
||||
if err != nil {
|
||||
panic(fmt.Sprintf("firestore: %v", err))
|
||||
}
|
||||
return m
|
||||
}
|
||||
|
||||
// DataTo uses the document's fields to populate p, which can be a pointer to a
|
||||
// map[string]interface{} or a pointer to a struct.
|
||||
//
|
||||
// Firestore field values are converted to Go values as follows:
|
||||
// - Null converts to nil.
|
||||
// - Bool converts to bool.
|
||||
// - String converts to string.
|
||||
// - Integer converts int64. When setting a struct field, any signed or unsigned
|
||||
// integer type is permitted except uint, uint64 or uintptr. Overflow is detected
|
||||
// and results in an error.
|
||||
// - Double converts to float64. When setting a struct field, float32 is permitted.
|
||||
// Overflow is detected and results in an error.
|
||||
// - Bytes is converted to []byte.
|
||||
// - Timestamp converts to time.Time.
|
||||
// - GeoPoint converts to *latlng.LatLng, where latlng is the package
|
||||
// "google.golang.org/genproto/googleapis/type/latlng".
|
||||
// - Arrays convert to []interface{}. When setting a struct field, the field
|
||||
// may be a slice or array of any type and is populated recursively.
|
||||
// Slices are resized to the incoming value's size, while arrays that are too
|
||||
// long have excess elements filled with zero values. If the array is too short,
|
||||
// excess incoming values will be dropped.
|
||||
// - Maps convert to map[string]interface{}. When setting a struct field,
|
||||
// maps of key type string and any value type are permitted, and are populated
|
||||
// recursively.
|
||||
// - References are converted to *firestore.DocumentRefs.
|
||||
//
|
||||
// Field names given by struct field tags are observed, as described in
|
||||
// DocumentRef.Create.
|
||||
//
|
||||
// Only the fields actually present in the document are used to populate p. Other fields
|
||||
// of p are left unchanged.
|
||||
//
|
||||
// If the document does not exist, DataTo returns a NotFound error.
|
||||
func (d *DocumentSnapshot) DataTo(p interface{}) error {
|
||||
if !d.Exists() {
|
||||
return status.Errorf(codes.NotFound, "document %s does not exist", d.Ref.Path)
|
||||
}
|
||||
return setFromProtoValue(p, &pb.Value{ValueType: &pb.Value_MapValue{&pb.MapValue{Fields: d.proto.Fields}}}, d.c)
|
||||
}
|
||||
|
||||
// DataAt returns the data value denoted by path.
|
||||
//
|
||||
// The path argument can be a single field or a dot-separated sequence of
|
||||
// fields, and must not contain any of the runes "˜*/[]". Use DataAtPath instead for
|
||||
// such a path.
|
||||
//
|
||||
// See DocumentSnapshot.DataTo for how Firestore values are converted to Go values.
|
||||
//
|
||||
// If the document does not exist, DataAt returns a NotFound error.
|
||||
func (d *DocumentSnapshot) DataAt(path string) (interface{}, error) {
|
||||
if !d.Exists() {
|
||||
return nil, status.Errorf(codes.NotFound, "document %s does not exist", d.Ref.Path)
|
||||
}
|
||||
fp, err := parseDotSeparatedString(path)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return d.DataAtPath(fp)
|
||||
}
|
||||
|
||||
// DataAtPath returns the data value denoted by the FieldPath fp.
|
||||
// If the document does not exist, DataAtPath returns a NotFound error.
|
||||
func (d *DocumentSnapshot) DataAtPath(fp FieldPath) (interface{}, error) {
|
||||
if !d.Exists() {
|
||||
return nil, status.Errorf(codes.NotFound, "document %s does not exist", d.Ref.Path)
|
||||
}
|
||||
v, err := valueAtPath(fp, d.proto.Fields)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return createFromProtoValue(v, d.c)
|
||||
}
|
||||
|
||||
// valueAtPath returns the value of m referred to by fp.
|
||||
func valueAtPath(fp FieldPath, m map[string]*pb.Value) (*pb.Value, error) {
|
||||
for _, k := range fp[:len(fp)-1] {
|
||||
v := m[k]
|
||||
if v == nil {
|
||||
return nil, fmt.Errorf("firestore: no field %q", k)
|
||||
}
|
||||
mv := v.GetMapValue()
|
||||
if mv == nil {
|
||||
return nil, fmt.Errorf("firestore: value for field %q is not a map", k)
|
||||
}
|
||||
m = mv.Fields
|
||||
}
|
||||
k := fp[len(fp)-1]
|
||||
v := m[k]
|
||||
if v == nil {
|
||||
return nil, fmt.Errorf("firestore: no field %q", k)
|
||||
}
|
||||
return v, nil
|
||||
}
|
||||
|
||||
// toProtoDocument converts a Go value to a Document proto.
|
||||
// Valid values are: map[string]T, struct, or pointer to a valid value.
|
||||
// It also returns a list DocumentTransforms.
|
||||
func toProtoDocument(x interface{}) (*pb.Document, []*pb.DocumentTransform_FieldTransform, error) {
|
||||
if x == nil {
|
||||
return nil, nil, errors.New("firestore: nil document contents")
|
||||
}
|
||||
v := reflect.ValueOf(x)
|
||||
pv, _, err := toProtoValue(v)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
var transforms []*pb.DocumentTransform_FieldTransform
|
||||
transforms, err = extractTransforms(v, nil)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
var fields map[string]*pb.Value
|
||||
if pv != nil {
|
||||
m := pv.GetMapValue()
|
||||
if m == nil {
|
||||
return nil, nil, fmt.Errorf("firestore: cannot convert value of type %T into a map", x)
|
||||
}
|
||||
fields = m.Fields
|
||||
}
|
||||
return &pb.Document{Fields: fields}, transforms, nil
|
||||
}
|
||||
|
||||
func extractTransforms(v reflect.Value, prefix FieldPath) ([]*pb.DocumentTransform_FieldTransform, error) {
|
||||
switch v.Kind() {
|
||||
case reflect.Map:
|
||||
return extractTransformsFromMap(v, prefix)
|
||||
case reflect.Struct:
|
||||
return extractTransformsFromStruct(v, prefix)
|
||||
case reflect.Ptr:
|
||||
if v.IsNil() {
|
||||
return nil, nil
|
||||
}
|
||||
return extractTransforms(v.Elem(), prefix)
|
||||
case reflect.Interface:
|
||||
if v.NumMethod() == 0 { // empty interface: recurse on its contents
|
||||
return extractTransforms(v.Elem(), prefix)
|
||||
}
|
||||
return nil, nil
|
||||
default:
|
||||
return nil, nil
|
||||
}
|
||||
}
|
||||
|
||||
func extractTransformsFromMap(v reflect.Value, prefix FieldPath) ([]*pb.DocumentTransform_FieldTransform, error) {
|
||||
var transforms []*pb.DocumentTransform_FieldTransform
|
||||
for _, k := range v.MapKeys() {
|
||||
sk := k.Interface().(string) // assume keys are strings; checked in toProtoValue
|
||||
path := prefix.with(sk)
|
||||
mi := v.MapIndex(k)
|
||||
if mi.Interface() == ServerTimestamp {
|
||||
transforms = append(transforms, serverTimestamp(path.toServiceFieldPath()))
|
||||
} else if au, ok := mi.Interface().(arrayUnion); ok {
|
||||
t, err := arrayUnionTransform(au, path)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
transforms = append(transforms, t)
|
||||
} else if ar, ok := mi.Interface().(arrayRemove); ok {
|
||||
t, err := arrayRemoveTransform(ar, path)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
transforms = append(transforms, t)
|
||||
} else if ar, ok := mi.Interface().(increment); ok {
|
||||
t, err := incrementTransform(ar, path)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
transforms = append(transforms, t)
|
||||
} else {
|
||||
ps, err := extractTransforms(mi, path)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
transforms = append(transforms, ps...)
|
||||
}
|
||||
}
|
||||
return transforms, nil
|
||||
}
|
||||
|
||||
func extractTransformsFromStruct(v reflect.Value, prefix FieldPath) ([]*pb.DocumentTransform_FieldTransform, error) {
|
||||
var transforms []*pb.DocumentTransform_FieldTransform
|
||||
fields, err := fieldCache.Fields(v.Type())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
for _, f := range fields {
|
||||
fv := v.FieldByIndex(f.Index)
|
||||
path := prefix.with(f.Name)
|
||||
opts := f.ParsedTag.(tagOptions)
|
||||
if opts.serverTimestamp {
|
||||
var isZero bool
|
||||
switch f.Type {
|
||||
case typeOfGoTime:
|
||||
isZero = fv.Interface().(time.Time).IsZero()
|
||||
case reflect.PtrTo(typeOfGoTime):
|
||||
isZero = fv.IsNil() || fv.Elem().Interface().(time.Time).IsZero()
|
||||
default:
|
||||
return nil, fmt.Errorf("firestore: field %s of struct %s with serverTimestamp tag must be of type time.Time or *time.Time",
|
||||
f.Name, v.Type())
|
||||
}
|
||||
if isZero {
|
||||
transforms = append(transforms, serverTimestamp(path.toServiceFieldPath()))
|
||||
}
|
||||
} else {
|
||||
ps, err := extractTransforms(fv, path)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
transforms = append(transforms, ps...)
|
||||
}
|
||||
}
|
||||
return transforms, nil
|
||||
}
|
||||
|
||||
func newDocumentSnapshot(ref *DocumentRef, proto *pb.Document, c *Client, readTime *tspb.Timestamp) (*DocumentSnapshot, error) {
|
||||
d := &DocumentSnapshot{
|
||||
Ref: ref,
|
||||
c: c,
|
||||
proto: proto,
|
||||
}
|
||||
if proto != nil {
|
||||
ts, err := ptypes.Timestamp(proto.CreateTime)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
d.CreateTime = ts
|
||||
ts, err = ptypes.Timestamp(proto.UpdateTime)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
d.UpdateTime = ts
|
||||
}
|
||||
if readTime != nil {
|
||||
ts, err := ptypes.Timestamp(readTime)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
d.ReadTime = ts
|
||||
}
|
||||
return d, nil
|
||||
}
|
||||
|
||||
func serverTimestamp(path string) *pb.DocumentTransform_FieldTransform {
|
||||
return &pb.DocumentTransform_FieldTransform{
|
||||
FieldPath: path,
|
||||
TransformType: &pb.DocumentTransform_FieldTransform_SetToServerValue{
|
||||
SetToServerValue: pb.DocumentTransform_FieldTransform_REQUEST_TIME,
|
||||
},
|
||||
}
|
||||
}
|
||||
-259
@@ -1,259 +0,0 @@
|
||||
// Copyright 2017 Google LLC
|
||||
//
|
||||
// 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 firestore
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"errors"
|
||||
"fmt"
|
||||
"reflect"
|
||||
"regexp"
|
||||
"sort"
|
||||
"strings"
|
||||
"sync"
|
||||
|
||||
"cloud.google.com/go/internal/fields"
|
||||
pb "google.golang.org/genproto/googleapis/firestore/v1"
|
||||
)
|
||||
|
||||
// A FieldPath is a non-empty sequence of non-empty fields that reference a value.
|
||||
//
|
||||
// A FieldPath value should only be necessary if one of the field names contains
|
||||
// one of the runes ".˜*/[]". Most methods accept a simpler form of field path
|
||||
// as a string in which the individual fields are separated by dots.
|
||||
// For example,
|
||||
// []string{"a", "b"}
|
||||
// is equivalent to the string form
|
||||
// "a.b"
|
||||
// but
|
||||
// []string{"*"}
|
||||
// has no equivalent string form.
|
||||
type FieldPath []string
|
||||
|
||||
// parseDotSeparatedString constructs a FieldPath from a string that separates
|
||||
// path components with dots. Other than splitting at dots and checking for invalid
|
||||
// characters, it ignores everything else about the string,
|
||||
// including attempts to quote field path compontents. So "a.`b.c`.d" is parsed into
|
||||
// four parts, "a", "`b", "c`" and "d".
|
||||
func parseDotSeparatedString(s string) (FieldPath, error) {
|
||||
const invalidRunes = "~*/[]"
|
||||
if strings.ContainsAny(s, invalidRunes) {
|
||||
return nil, fmt.Errorf("firestore: %q contains an invalid rune (one of %s)", s, invalidRunes)
|
||||
}
|
||||
fp := FieldPath(strings.Split(s, "."))
|
||||
if err := fp.validate(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return fp, nil
|
||||
}
|
||||
|
||||
func (fp1 FieldPath) equal(fp2 FieldPath) bool {
|
||||
if len(fp1) != len(fp2) {
|
||||
return false
|
||||
}
|
||||
for i, c1 := range fp1 {
|
||||
if c1 != fp2[i] {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
func (fp1 FieldPath) prefixOf(fp2 FieldPath) bool {
|
||||
return len(fp1) <= len(fp2) && fp1.equal(fp2[:len(fp1)])
|
||||
}
|
||||
|
||||
// Lexicographic ordering.
|
||||
func (fp1 FieldPath) less(fp2 FieldPath) bool {
|
||||
for i := range fp1 {
|
||||
switch {
|
||||
case i >= len(fp2):
|
||||
return false
|
||||
case fp1[i] < fp2[i]:
|
||||
return true
|
||||
case fp1[i] > fp2[i]:
|
||||
return false
|
||||
}
|
||||
}
|
||||
// fp1 and fp2 are equal up to len(fp1).
|
||||
return len(fp1) < len(fp2)
|
||||
}
|
||||
|
||||
// validate checks the validity of fp and returns an error if it is invalid.
|
||||
func (fp FieldPath) validate() error {
|
||||
if len(fp) == 0 {
|
||||
return errors.New("firestore: empty field path")
|
||||
}
|
||||
for _, c := range fp {
|
||||
if len(c) == 0 {
|
||||
return errors.New("firestore: empty component in field path")
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// with creates a new FieldPath consisting of fp followed by k.
|
||||
func (fp FieldPath) with(k string) FieldPath {
|
||||
r := make(FieldPath, len(fp), len(fp)+1)
|
||||
copy(r, fp)
|
||||
return append(r, k)
|
||||
}
|
||||
|
||||
// checkNoDupOrPrefix checks whether any FieldPath is a prefix of (or equal to)
|
||||
// another.
|
||||
// It modifies the order of FieldPaths in its argument (via sorting).
|
||||
func checkNoDupOrPrefix(fps []FieldPath) error {
|
||||
// Sort fps lexicographically.
|
||||
sort.Sort(byPath(fps))
|
||||
// Check adjacent pairs for prefix.
|
||||
for i := 1; i < len(fps); i++ {
|
||||
if fps[i-1].prefixOf(fps[i]) {
|
||||
return fmt.Errorf("field path %v cannot be used in the same update as %v", fps[i-1], fps[i])
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
type byPath []FieldPath
|
||||
|
||||
func (b byPath) Len() int { return len(b) }
|
||||
func (b byPath) Swap(i, j int) { b[i], b[j] = b[j], b[i] }
|
||||
func (b byPath) Less(i, j int) bool { return b[i].less(b[j]) }
|
||||
|
||||
// setAtPath sets val at the location in m specified by fp, creating sub-maps as
|
||||
// needed. m must not be nil. fp is assumed to be valid.
|
||||
func setAtPath(m map[string]*pb.Value, fp FieldPath, val *pb.Value) {
|
||||
if val == nil {
|
||||
return
|
||||
}
|
||||
if len(fp) == 1 {
|
||||
m[fp[0]] = val
|
||||
} else {
|
||||
v, ok := m[fp[0]]
|
||||
if !ok {
|
||||
v = &pb.Value{ValueType: &pb.Value_MapValue{&pb.MapValue{Fields: map[string]*pb.Value{}}}}
|
||||
m[fp[0]] = v
|
||||
}
|
||||
// The type assertion below cannot fail, because setAtPath is only called
|
||||
// with either an empty map or one filled by setAtPath itself, and the
|
||||
// set of FieldPaths it is called with has been checked to make sure that
|
||||
// no path is the prefix of any other.
|
||||
setAtPath(v.GetMapValue().Fields, fp[1:], val)
|
||||
}
|
||||
}
|
||||
|
||||
// getAtPath gets the value in data referred to by fp. The data argument can
|
||||
// be a map or a struct.
|
||||
// Compare with valueAtPath, which does the same thing for a document.
|
||||
func getAtPath(v reflect.Value, fp FieldPath) (interface{}, error) {
|
||||
var err error
|
||||
for _, k := range fp {
|
||||
v, err = getAtField(v, k)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
return v.Interface(), nil
|
||||
}
|
||||
|
||||
// getAtField returns the equivalent of v[k], if v is a map, or v.k if v is a struct.
|
||||
func getAtField(v reflect.Value, k string) (reflect.Value, error) {
|
||||
switch v.Kind() {
|
||||
case reflect.Map:
|
||||
if r := v.MapIndex(reflect.ValueOf(k)); r.IsValid() {
|
||||
return r, nil
|
||||
}
|
||||
|
||||
case reflect.Struct:
|
||||
fm, err := fieldMap(v.Type())
|
||||
if err != nil {
|
||||
return reflect.Value{}, err
|
||||
}
|
||||
if f, ok := fm[k]; ok {
|
||||
return v.FieldByIndex(f.Index), nil
|
||||
}
|
||||
|
||||
case reflect.Interface:
|
||||
return getAtField(v.Elem(), k)
|
||||
|
||||
case reflect.Ptr:
|
||||
return getAtField(v.Elem(), k)
|
||||
}
|
||||
return reflect.Value{}, fmt.Errorf("firestore: no field %q for value %#v", k, v)
|
||||
}
|
||||
|
||||
// fieldMapCache holds maps from from Firestore field name to struct field,
|
||||
// keyed by struct type.
|
||||
var fieldMapCache sync.Map
|
||||
|
||||
func fieldMap(t reflect.Type) (map[string]fields.Field, error) {
|
||||
x, ok := fieldMapCache.Load(t)
|
||||
if !ok {
|
||||
fieldList, err := fieldCache.Fields(t)
|
||||
if err != nil {
|
||||
x = err
|
||||
} else {
|
||||
m := map[string]fields.Field{}
|
||||
for _, f := range fieldList {
|
||||
m[f.Name] = f
|
||||
}
|
||||
x = m
|
||||
}
|
||||
fieldMapCache.Store(t, x)
|
||||
}
|
||||
if err, ok := x.(error); ok {
|
||||
return nil, err
|
||||
}
|
||||
return x.(map[string]fields.Field), nil
|
||||
}
|
||||
|
||||
// toServiceFieldPath converts fp the form required by the Firestore service.
|
||||
// It assumes fp has been validated.
|
||||
func (fp FieldPath) toServiceFieldPath() string {
|
||||
cs := make([]string, len(fp))
|
||||
for i, c := range fp {
|
||||
cs[i] = toServiceFieldPathComponent(c)
|
||||
}
|
||||
return strings.Join(cs, ".")
|
||||
}
|
||||
|
||||
func toServiceFieldPaths(fps []FieldPath) []string {
|
||||
var sfps []string
|
||||
for _, fp := range fps {
|
||||
sfps = append(sfps, fp.toServiceFieldPath())
|
||||
}
|
||||
return sfps
|
||||
}
|
||||
|
||||
// Google SQL syntax for an unquoted field.
|
||||
var unquotedFieldRegexp = regexp.MustCompile("^[A-Za-z_][A-Za-z_0-9]*$")
|
||||
|
||||
// toServiceFieldPathComponent returns a string that represents key and is a valid
|
||||
// field path component.
|
||||
func toServiceFieldPathComponent(key string) string {
|
||||
if unquotedFieldRegexp.MatchString(key) {
|
||||
return key
|
||||
}
|
||||
var buf bytes.Buffer
|
||||
buf.WriteRune('`')
|
||||
for _, r := range key {
|
||||
if r == '`' || r == '\\' {
|
||||
buf.WriteRune('\\')
|
||||
}
|
||||
buf.WriteRune(r)
|
||||
}
|
||||
buf.WriteRune('`')
|
||||
return buf.String()
|
||||
}
|
||||
-429
@@ -1,429 +0,0 @@
|
||||
// Copyright 2017 Google LLC
|
||||
//
|
||||
// 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 firestore
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"reflect"
|
||||
"strings"
|
||||
|
||||
"github.com/golang/protobuf/ptypes"
|
||||
pb "google.golang.org/genproto/googleapis/firestore/v1"
|
||||
)
|
||||
|
||||
func setFromProtoValue(x interface{}, vproto *pb.Value, c *Client) error {
|
||||
v := reflect.ValueOf(x)
|
||||
if v.Kind() != reflect.Ptr || v.IsNil() {
|
||||
return errors.New("firestore: nil or not a pointer")
|
||||
}
|
||||
return setReflectFromProtoValue(v.Elem(), vproto, c)
|
||||
}
|
||||
|
||||
// setReflectFromProtoValue sets v from a Firestore Value.
|
||||
// v must be a settable value.
|
||||
func setReflectFromProtoValue(v reflect.Value, vproto *pb.Value, c *Client) error {
|
||||
typeErr := func() error {
|
||||
return fmt.Errorf("firestore: cannot set type %s to %s", v.Type(), typeString(vproto))
|
||||
}
|
||||
|
||||
val := vproto.ValueType
|
||||
// A Null value sets anything nullable to nil, and has no effect
|
||||
// on anything else.
|
||||
if _, ok := val.(*pb.Value_NullValue); ok {
|
||||
switch v.Kind() {
|
||||
case reflect.Interface, reflect.Ptr, reflect.Map, reflect.Slice:
|
||||
v.Set(reflect.Zero(v.Type()))
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Handle special types first.
|
||||
switch v.Type() {
|
||||
case typeOfByteSlice:
|
||||
x, ok := val.(*pb.Value_BytesValue)
|
||||
if !ok {
|
||||
return typeErr()
|
||||
}
|
||||
v.SetBytes(x.BytesValue)
|
||||
return nil
|
||||
|
||||
case typeOfGoTime:
|
||||
x, ok := val.(*pb.Value_TimestampValue)
|
||||
if !ok {
|
||||
return typeErr()
|
||||
}
|
||||
t, err := ptypes.Timestamp(x.TimestampValue)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
v.Set(reflect.ValueOf(t))
|
||||
return nil
|
||||
|
||||
case typeOfProtoTimestamp:
|
||||
x, ok := val.(*pb.Value_TimestampValue)
|
||||
if !ok {
|
||||
return typeErr()
|
||||
}
|
||||
v.Set(reflect.ValueOf(x.TimestampValue))
|
||||
return nil
|
||||
|
||||
case typeOfLatLng:
|
||||
x, ok := val.(*pb.Value_GeoPointValue)
|
||||
if !ok {
|
||||
return typeErr()
|
||||
}
|
||||
v.Set(reflect.ValueOf(x.GeoPointValue))
|
||||
return nil
|
||||
|
||||
case typeOfDocumentRef:
|
||||
x, ok := val.(*pb.Value_ReferenceValue)
|
||||
if !ok {
|
||||
return typeErr()
|
||||
}
|
||||
dr, err := pathToDoc(x.ReferenceValue, c)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
v.Set(reflect.ValueOf(dr))
|
||||
return nil
|
||||
}
|
||||
|
||||
switch v.Kind() {
|
||||
case reflect.Bool:
|
||||
x, ok := val.(*pb.Value_BooleanValue)
|
||||
if !ok {
|
||||
return typeErr()
|
||||
}
|
||||
v.SetBool(x.BooleanValue)
|
||||
|
||||
case reflect.String:
|
||||
x, ok := val.(*pb.Value_StringValue)
|
||||
if !ok {
|
||||
return typeErr()
|
||||
}
|
||||
v.SetString(x.StringValue)
|
||||
|
||||
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
|
||||
var i int64
|
||||
switch x := val.(type) {
|
||||
case *pb.Value_IntegerValue:
|
||||
i = x.IntegerValue
|
||||
case *pb.Value_DoubleValue:
|
||||
f := x.DoubleValue
|
||||
i = int64(f)
|
||||
if float64(i) != f {
|
||||
return fmt.Errorf("firestore: float %f does not fit into %s", f, v.Type())
|
||||
}
|
||||
default:
|
||||
return typeErr()
|
||||
}
|
||||
if v.OverflowInt(i) {
|
||||
return overflowErr(v, i)
|
||||
}
|
||||
v.SetInt(i)
|
||||
|
||||
case reflect.Uint8, reflect.Uint16, reflect.Uint32:
|
||||
var u uint64
|
||||
switch x := val.(type) {
|
||||
case *pb.Value_IntegerValue:
|
||||
u = uint64(x.IntegerValue)
|
||||
case *pb.Value_DoubleValue:
|
||||
f := x.DoubleValue
|
||||
u = uint64(f)
|
||||
if float64(u) != f {
|
||||
return fmt.Errorf("firestore: float %f does not fit into %s", f, v.Type())
|
||||
}
|
||||
default:
|
||||
return typeErr()
|
||||
}
|
||||
if v.OverflowUint(u) {
|
||||
return overflowErr(v, u)
|
||||
}
|
||||
v.SetUint(u)
|
||||
|
||||
case reflect.Float32, reflect.Float64:
|
||||
var f float64
|
||||
switch x := val.(type) {
|
||||
case *pb.Value_DoubleValue:
|
||||
f = x.DoubleValue
|
||||
case *pb.Value_IntegerValue:
|
||||
f = float64(x.IntegerValue)
|
||||
if int64(f) != x.IntegerValue {
|
||||
return overflowErr(v, x.IntegerValue)
|
||||
}
|
||||
default:
|
||||
return typeErr()
|
||||
}
|
||||
if v.OverflowFloat(f) {
|
||||
return overflowErr(v, f)
|
||||
}
|
||||
v.SetFloat(f)
|
||||
|
||||
case reflect.Slice:
|
||||
x, ok := val.(*pb.Value_ArrayValue)
|
||||
if !ok {
|
||||
return typeErr()
|
||||
}
|
||||
vals := x.ArrayValue.Values
|
||||
vlen := v.Len()
|
||||
xlen := len(vals)
|
||||
// Make a slice of the right size, avoiding allocation if possible.
|
||||
switch {
|
||||
case vlen < xlen:
|
||||
v.Set(reflect.MakeSlice(v.Type(), xlen, xlen))
|
||||
case vlen > xlen:
|
||||
v.SetLen(xlen)
|
||||
}
|
||||
return populateRepeated(v, vals, xlen, c)
|
||||
|
||||
case reflect.Array:
|
||||
x, ok := val.(*pb.Value_ArrayValue)
|
||||
if !ok {
|
||||
return typeErr()
|
||||
}
|
||||
vals := x.ArrayValue.Values
|
||||
xlen := len(vals)
|
||||
vlen := v.Len()
|
||||
minlen := vlen
|
||||
// Set extra elements to their zero value.
|
||||
if vlen > xlen {
|
||||
z := reflect.Zero(v.Type().Elem())
|
||||
for i := xlen; i < vlen; i++ {
|
||||
v.Index(i).Set(z)
|
||||
}
|
||||
minlen = xlen
|
||||
}
|
||||
return populateRepeated(v, vals, minlen, c)
|
||||
|
||||
case reflect.Map:
|
||||
x, ok := val.(*pb.Value_MapValue)
|
||||
if !ok {
|
||||
return typeErr()
|
||||
}
|
||||
return populateMap(v, x.MapValue.Fields, c)
|
||||
|
||||
case reflect.Ptr:
|
||||
// If the pointer is nil, set it to a zero value.
|
||||
if v.IsNil() {
|
||||
v.Set(reflect.New(v.Type().Elem()))
|
||||
}
|
||||
return setReflectFromProtoValue(v.Elem(), vproto, c)
|
||||
|
||||
case reflect.Struct:
|
||||
x, ok := val.(*pb.Value_MapValue)
|
||||
if !ok {
|
||||
return typeErr()
|
||||
}
|
||||
return populateStruct(v, x.MapValue.Fields, c)
|
||||
|
||||
case reflect.Interface:
|
||||
if v.NumMethod() == 0 { // empty interface
|
||||
// If v holds a pointer, set the pointer.
|
||||
if !v.IsNil() && v.Elem().Kind() == reflect.Ptr {
|
||||
return setReflectFromProtoValue(v.Elem(), vproto, c)
|
||||
}
|
||||
// Otherwise, create a fresh value.
|
||||
x, err := createFromProtoValue(vproto, c)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
v.Set(reflect.ValueOf(x))
|
||||
return nil
|
||||
}
|
||||
// Any other kind of interface is an error.
|
||||
fallthrough
|
||||
|
||||
default:
|
||||
return fmt.Errorf("firestore: cannot set type %s", v.Type())
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// populateRepeated sets the first n elements of vr, which must be a slice or
|
||||
// array, to the corresponding elements of vals.
|
||||
func populateRepeated(vr reflect.Value, vals []*pb.Value, n int, c *Client) error {
|
||||
for i := 0; i < n; i++ {
|
||||
if err := setReflectFromProtoValue(vr.Index(i), vals[i], c); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// populateMap sets the elements of vm, which must be a map, from the
|
||||
// corresponding elements of pm.
|
||||
//
|
||||
// Since a map value is not settable, this function always creates a new
|
||||
// element for each corresponding map key. Existing values of vm are
|
||||
// overwritten. This happens even if the map value is something like a pointer
|
||||
// to a struct, where we could in theory populate the existing struct value
|
||||
// instead of discarding it. This behavior matches encoding/json.
|
||||
func populateMap(vm reflect.Value, pm map[string]*pb.Value, c *Client) error {
|
||||
t := vm.Type()
|
||||
if t.Key().Kind() != reflect.String {
|
||||
return errors.New("firestore: map key type is not string")
|
||||
}
|
||||
if vm.IsNil() {
|
||||
vm.Set(reflect.MakeMap(t))
|
||||
}
|
||||
et := t.Elem()
|
||||
for k, vproto := range pm {
|
||||
el := reflect.New(et).Elem()
|
||||
if err := setReflectFromProtoValue(el, vproto, c); err != nil {
|
||||
return err
|
||||
}
|
||||
vm.SetMapIndex(reflect.ValueOf(k), el)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// createMapFromValueMap creates a fresh map and populates it with pm.
|
||||
func createMapFromValueMap(pm map[string]*pb.Value, c *Client) (map[string]interface{}, error) {
|
||||
m := map[string]interface{}{}
|
||||
for k, pv := range pm {
|
||||
v, err := createFromProtoValue(pv, c)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
m[k] = v
|
||||
}
|
||||
return m, nil
|
||||
}
|
||||
|
||||
// populateStruct sets the fields of vs, which must be a struct, from
|
||||
// the matching elements of pm.
|
||||
func populateStruct(vs reflect.Value, pm map[string]*pb.Value, c *Client) error {
|
||||
fields, err := fieldCache.Fields(vs.Type())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for k, vproto := range pm {
|
||||
f := fields.Match(k)
|
||||
if f == nil {
|
||||
continue
|
||||
}
|
||||
if err := setReflectFromProtoValue(vs.FieldByIndex(f.Index), vproto, c); err != nil {
|
||||
return fmt.Errorf("%s.%s: %v", vs.Type(), f.Name, err)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func createFromProtoValue(vproto *pb.Value, c *Client) (interface{}, error) {
|
||||
switch v := vproto.ValueType.(type) {
|
||||
case *pb.Value_NullValue:
|
||||
return nil, nil
|
||||
case *pb.Value_BooleanValue:
|
||||
return v.BooleanValue, nil
|
||||
case *pb.Value_IntegerValue:
|
||||
return v.IntegerValue, nil
|
||||
case *pb.Value_DoubleValue:
|
||||
return v.DoubleValue, nil
|
||||
case *pb.Value_TimestampValue:
|
||||
return ptypes.Timestamp(v.TimestampValue)
|
||||
case *pb.Value_StringValue:
|
||||
return v.StringValue, nil
|
||||
case *pb.Value_BytesValue:
|
||||
return v.BytesValue, nil
|
||||
case *pb.Value_ReferenceValue:
|
||||
return pathToDoc(v.ReferenceValue, c)
|
||||
case *pb.Value_GeoPointValue:
|
||||
return v.GeoPointValue, nil
|
||||
case *pb.Value_ArrayValue:
|
||||
vals := v.ArrayValue.Values
|
||||
ret := make([]interface{}, len(vals))
|
||||
for i, v := range vals {
|
||||
r, err := createFromProtoValue(v, c)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
ret[i] = r
|
||||
}
|
||||
return ret, nil
|
||||
|
||||
case *pb.Value_MapValue:
|
||||
fields := v.MapValue.Fields
|
||||
ret := make(map[string]interface{}, len(fields))
|
||||
for k, v := range fields {
|
||||
r, err := createFromProtoValue(v, c)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
ret[k] = r
|
||||
}
|
||||
return ret, nil
|
||||
|
||||
default:
|
||||
return nil, fmt.Errorf("firestore: unknown value type %T", v)
|
||||
}
|
||||
}
|
||||
|
||||
// Convert a document path to a DocumentRef.
|
||||
func pathToDoc(docPath string, c *Client) (*DocumentRef, error) {
|
||||
projID, dbID, docIDs, err := parseDocumentPath(docPath)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
parentResourceName := fmt.Sprintf("projects/%s/databases/%s", projID, dbID)
|
||||
_, doc := c.idsToRef(docIDs, parentResourceName)
|
||||
return doc, nil
|
||||
}
|
||||
|
||||
// A document path should be of the form "projects/P/databases/D/documents/coll1/doc1/coll2/doc2/...".
|
||||
func parseDocumentPath(path string) (projectID, databaseID string, docPath []string, err error) {
|
||||
parts := strings.Split(path, "/")
|
||||
if len(parts) < 6 || parts[0] != "projects" || parts[2] != "databases" || parts[4] != "documents" {
|
||||
return "", "", nil, fmt.Errorf("firestore: malformed document path %q", path)
|
||||
}
|
||||
docp := parts[5:]
|
||||
if len(docp)%2 != 0 {
|
||||
return "", "", nil, fmt.Errorf("firestore: path %q refers to collection, not document", path)
|
||||
}
|
||||
return parts[1], parts[3], docp, nil
|
||||
}
|
||||
|
||||
func typeString(vproto *pb.Value) string {
|
||||
switch vproto.ValueType.(type) {
|
||||
case *pb.Value_NullValue:
|
||||
return "null"
|
||||
case *pb.Value_BooleanValue:
|
||||
return "bool"
|
||||
case *pb.Value_IntegerValue:
|
||||
return "int"
|
||||
case *pb.Value_DoubleValue:
|
||||
return "float"
|
||||
case *pb.Value_TimestampValue:
|
||||
return "timestamp"
|
||||
case *pb.Value_StringValue:
|
||||
return "string"
|
||||
case *pb.Value_BytesValue:
|
||||
return "bytes"
|
||||
case *pb.Value_ReferenceValue:
|
||||
return "reference"
|
||||
case *pb.Value_GeoPointValue:
|
||||
return "GeoPoint"
|
||||
case *pb.Value_MapValue:
|
||||
return "map"
|
||||
case *pb.Value_ArrayValue:
|
||||
return "array"
|
||||
default:
|
||||
return "<unknown Value type>"
|
||||
}
|
||||
}
|
||||
|
||||
func overflowErr(v reflect.Value, x interface{}) error {
|
||||
return fmt.Errorf("firestore: value %v overflows type %s", x, v.Type())
|
||||
}
|
||||
-22
@@ -1,22 +0,0 @@
|
||||
// Copyright 2019 Google LLC
|
||||
//
|
||||
// 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.
|
||||
|
||||
// This file, and the cloud.google.com/go import, won't actually become part of
|
||||
// the resultant binary.
|
||||
// +build modhack
|
||||
|
||||
package firestore
|
||||
|
||||
// Necessary for safely adding multi-module repo. See: https://github.com/golang/go/wiki/Modules#is-it-possible-to-add-a-module-to-a-multi-module-repository
|
||||
import _ "cloud.google.com/go"
|
||||
-104
@@ -1,104 +0,0 @@
|
||||
// Copyright 2018 Google LLC
|
||||
//
|
||||
// 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 firestore
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
vkit "cloud.google.com/go/firestore/apiv1"
|
||||
"google.golang.org/api/iterator"
|
||||
pb "google.golang.org/genproto/googleapis/firestore/v1"
|
||||
)
|
||||
|
||||
// DocumentRefIterator is an iterator over DocumentRefs.
|
||||
type DocumentRefIterator struct {
|
||||
client *Client
|
||||
it *vkit.DocumentIterator
|
||||
pageInfo *iterator.PageInfo
|
||||
nextFunc func() error
|
||||
items []*DocumentRef
|
||||
err error
|
||||
}
|
||||
|
||||
func newDocumentRefIterator(ctx context.Context, cr *CollectionRef, tid []byte) *DocumentRefIterator {
|
||||
client := cr.c
|
||||
req := &pb.ListDocumentsRequest{
|
||||
Parent: cr.parentPath,
|
||||
CollectionId: cr.ID,
|
||||
ShowMissing: true,
|
||||
Mask: &pb.DocumentMask{}, // empty mask: we want only the ref
|
||||
}
|
||||
if tid != nil {
|
||||
req.ConsistencySelector = &pb.ListDocumentsRequest_Transaction{tid}
|
||||
}
|
||||
it := &DocumentRefIterator{
|
||||
client: client,
|
||||
it: client.c.ListDocuments(withResourceHeader(ctx, client.path()), req),
|
||||
}
|
||||
it.pageInfo, it.nextFunc = iterator.NewPageInfo(
|
||||
it.fetch,
|
||||
func() int { return len(it.items) },
|
||||
func() interface{} { b := it.items; it.items = nil; return b })
|
||||
return it
|
||||
}
|
||||
|
||||
// PageInfo supports pagination. See the google.golang.org/api/iterator package for details.
|
||||
func (it *DocumentRefIterator) PageInfo() *iterator.PageInfo { return it.pageInfo }
|
||||
|
||||
// Next returns the next result. Its second return value is iterator.Done if there
|
||||
// are no more results. Once Next returns Done, all subsequent calls will return
|
||||
// Done.
|
||||
func (it *DocumentRefIterator) Next() (*DocumentRef, error) {
|
||||
if err := it.nextFunc(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
item := it.items[0]
|
||||
it.items = it.items[1:]
|
||||
return item, nil
|
||||
}
|
||||
|
||||
func (it *DocumentRefIterator) fetch(pageSize int, pageToken string) (string, error) {
|
||||
if it.err != nil {
|
||||
return "", it.err
|
||||
}
|
||||
return iterFetch(pageSize, pageToken, it.it.PageInfo(), func() error {
|
||||
docProto, err := it.it.Next()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
docRef, err := pathToDoc(docProto.Name, it.client)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
it.items = append(it.items, docRef)
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
// GetAll returns all the DocumentRefs remaining from the iterator.
|
||||
func (it *DocumentRefIterator) GetAll() ([]*DocumentRef, error) {
|
||||
var drs []*DocumentRef
|
||||
for {
|
||||
dr, err := it.Next()
|
||||
if err == iterator.Done {
|
||||
break
|
||||
}
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
drs = append(drs, dr)
|
||||
}
|
||||
return drs, nil
|
||||
}
|
||||
-175
@@ -1,175 +0,0 @@
|
||||
// Copyright 2017 Google LLC
|
||||
//
|
||||
// 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 firestore
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"github.com/golang/protobuf/ptypes"
|
||||
pb "google.golang.org/genproto/googleapis/firestore/v1"
|
||||
)
|
||||
|
||||
// A Precondition modifies a Firestore update or delete operation.
|
||||
type Precondition interface {
|
||||
// Returns the corresponding Precondition proto.
|
||||
preconditionProto() (*pb.Precondition, error)
|
||||
}
|
||||
|
||||
// Exists is a Precondition that checks for the existence of a resource before
|
||||
// writing to it. If the check fails, the write does not occur.
|
||||
var Exists Precondition
|
||||
|
||||
func init() {
|
||||
// Initialize here so godoc doesn't show the internal value.
|
||||
Exists = exists(true)
|
||||
}
|
||||
|
||||
type exists bool
|
||||
|
||||
func (e exists) preconditionProto() (*pb.Precondition, error) {
|
||||
return &pb.Precondition{
|
||||
ConditionType: &pb.Precondition_Exists{bool(e)},
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (e exists) String() string {
|
||||
if e {
|
||||
return "Exists"
|
||||
}
|
||||
return "DoesNotExist"
|
||||
}
|
||||
|
||||
// LastUpdateTime returns a Precondition that checks that a resource must exist and
|
||||
// must have last been updated at the given time. If the check fails, the write
|
||||
// does not occur.
|
||||
func LastUpdateTime(t time.Time) Precondition { return lastUpdateTime(t) }
|
||||
|
||||
type lastUpdateTime time.Time
|
||||
|
||||
func (u lastUpdateTime) preconditionProto() (*pb.Precondition, error) {
|
||||
ts, err := ptypes.TimestampProto(time.Time(u))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &pb.Precondition{
|
||||
ConditionType: &pb.Precondition_UpdateTime{ts},
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (u lastUpdateTime) String() string { return fmt.Sprintf("LastUpdateTime(%s)", time.Time(u)) }
|
||||
|
||||
func processPreconditionsForDelete(preconds []Precondition) (*pb.Precondition, error) {
|
||||
// At most one option permitted.
|
||||
switch len(preconds) {
|
||||
case 0:
|
||||
return nil, nil
|
||||
case 1:
|
||||
return preconds[0].preconditionProto()
|
||||
default:
|
||||
return nil, fmt.Errorf("firestore: conflicting preconditions: %+v", preconds)
|
||||
}
|
||||
}
|
||||
|
||||
func processPreconditionsForUpdate(preconds []Precondition) (*pb.Precondition, error) {
|
||||
// At most one option permitted, and it cannot be Exists.
|
||||
switch len(preconds) {
|
||||
case 0:
|
||||
// If the user doesn't provide any options, default to Exists(true).
|
||||
return exists(true).preconditionProto()
|
||||
case 1:
|
||||
if _, ok := preconds[0].(exists); ok {
|
||||
return nil, errors.New("cannot use Exists with Update")
|
||||
}
|
||||
return preconds[0].preconditionProto()
|
||||
default:
|
||||
return nil, fmt.Errorf("firestore: conflicting preconditions: %+v", preconds)
|
||||
}
|
||||
}
|
||||
|
||||
func processPreconditionsForVerify(preconds []Precondition) (*pb.Precondition, error) {
|
||||
// At most one option permitted.
|
||||
switch len(preconds) {
|
||||
case 0:
|
||||
return nil, nil
|
||||
case 1:
|
||||
return preconds[0].preconditionProto()
|
||||
default:
|
||||
return nil, fmt.Errorf("firestore: conflicting preconditions: %+v", preconds)
|
||||
}
|
||||
}
|
||||
|
||||
// A SetOption modifies a Firestore set operation.
|
||||
type SetOption interface {
|
||||
fieldPaths() (fps []FieldPath, all bool, err error)
|
||||
}
|
||||
|
||||
// MergeAll is a SetOption that causes all the field paths given in the data argument
|
||||
// to Set to be overwritten. It is not supported for struct data.
|
||||
var MergeAll SetOption = merge{all: true}
|
||||
|
||||
// Merge returns a SetOption that causes only the given field paths to be
|
||||
// overwritten. Other fields on the existing document will be untouched. It is an
|
||||
// error if a provided field path does not refer to a value in the data passed to
|
||||
// Set.
|
||||
func Merge(fps ...FieldPath) SetOption {
|
||||
for _, fp := range fps {
|
||||
if err := fp.validate(); err != nil {
|
||||
return merge{err: err}
|
||||
}
|
||||
}
|
||||
return merge{paths: fps}
|
||||
}
|
||||
|
||||
type merge struct {
|
||||
all bool
|
||||
paths []FieldPath
|
||||
err error
|
||||
}
|
||||
|
||||
func (m merge) String() string {
|
||||
if m.err != nil {
|
||||
return fmt.Sprintf("<Merge error: %v>", m.err)
|
||||
}
|
||||
if m.all {
|
||||
return "MergeAll"
|
||||
}
|
||||
return fmt.Sprintf("Merge(%+v)", m.paths)
|
||||
}
|
||||
|
||||
func (m merge) fieldPaths() (fps []FieldPath, all bool, err error) {
|
||||
if m.err != nil {
|
||||
return nil, false, m.err
|
||||
}
|
||||
if err := checkNoDupOrPrefix(m.paths); err != nil {
|
||||
return nil, false, err
|
||||
}
|
||||
if m.all {
|
||||
return nil, true, nil
|
||||
}
|
||||
return m.paths, false, nil
|
||||
}
|
||||
|
||||
func processSetOptions(opts []SetOption) (fps []FieldPath, all bool, err error) {
|
||||
switch len(opts) {
|
||||
case 0:
|
||||
return nil, false, nil
|
||||
case 1:
|
||||
return opts[0].fieldPaths()
|
||||
default:
|
||||
return nil, false, fmt.Errorf("conflicting options: %+v", opts)
|
||||
}
|
||||
}
|
||||
-216
@@ -1,216 +0,0 @@
|
||||
// Copyright 2018 Google LLC
|
||||
//
|
||||
// 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 firestore
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"math"
|
||||
"sort"
|
||||
"strings"
|
||||
|
||||
tspb "github.com/golang/protobuf/ptypes/timestamp"
|
||||
pb "google.golang.org/genproto/googleapis/firestore/v1"
|
||||
)
|
||||
|
||||
// Returns a negative number, zero, or a positive number depending on whether a is
|
||||
// less than, equal to, or greater than b according to Firestore's ordering of
|
||||
// values.
|
||||
func compareValues(a, b *pb.Value) int {
|
||||
ta := typeOrder(a)
|
||||
tb := typeOrder(b)
|
||||
if ta != tb {
|
||||
return compareInt64s(int64(ta), int64(tb))
|
||||
}
|
||||
switch a := a.ValueType.(type) {
|
||||
case *pb.Value_NullValue:
|
||||
return 0 // nulls are equal
|
||||
|
||||
case *pb.Value_BooleanValue:
|
||||
av := a.BooleanValue
|
||||
bv := b.GetBooleanValue()
|
||||
switch {
|
||||
case av && !bv:
|
||||
return 1
|
||||
case bv && !av:
|
||||
return -1
|
||||
default:
|
||||
return 0
|
||||
}
|
||||
|
||||
case *pb.Value_IntegerValue:
|
||||
return compareNumbers(float64(a.IntegerValue), toFloat(b))
|
||||
|
||||
case *pb.Value_DoubleValue:
|
||||
return compareNumbers(a.DoubleValue, toFloat(b))
|
||||
|
||||
case *pb.Value_TimestampValue:
|
||||
return compareTimestamps(a.TimestampValue, b.GetTimestampValue())
|
||||
|
||||
case *pb.Value_StringValue:
|
||||
return strings.Compare(a.StringValue, b.GetStringValue())
|
||||
|
||||
case *pb.Value_BytesValue:
|
||||
return bytes.Compare(a.BytesValue, b.GetBytesValue())
|
||||
|
||||
case *pb.Value_ReferenceValue:
|
||||
return compareReferences(a.ReferenceValue, b.GetReferenceValue())
|
||||
|
||||
case *pb.Value_GeoPointValue:
|
||||
ag := a.GeoPointValue
|
||||
bg := b.GetGeoPointValue()
|
||||
if ag.Latitude != bg.Latitude {
|
||||
return compareFloat64s(ag.Latitude, bg.Latitude)
|
||||
}
|
||||
return compareFloat64s(ag.Longitude, bg.Longitude)
|
||||
|
||||
case *pb.Value_ArrayValue:
|
||||
return compareArrays(a.ArrayValue.Values, b.GetArrayValue().Values)
|
||||
|
||||
case *pb.Value_MapValue:
|
||||
return compareMaps(a.MapValue.Fields, b.GetMapValue().Fields)
|
||||
|
||||
default:
|
||||
panic(fmt.Sprintf("bad value type: %v", a))
|
||||
}
|
||||
}
|
||||
|
||||
// Treats NaN as less than any non-NaN.
|
||||
func compareNumbers(a, b float64) int {
|
||||
switch {
|
||||
case math.IsNaN(a):
|
||||
if math.IsNaN(b) {
|
||||
return 0
|
||||
}
|
||||
return -1
|
||||
case math.IsNaN(b):
|
||||
return 1
|
||||
default:
|
||||
return compareFloat64s(a, b)
|
||||
}
|
||||
}
|
||||
|
||||
// Return v as a float64, assuming it's an Integer or Double.
|
||||
func toFloat(v *pb.Value) float64 {
|
||||
if x, ok := v.ValueType.(*pb.Value_IntegerValue); ok {
|
||||
return float64(x.IntegerValue)
|
||||
}
|
||||
return v.GetDoubleValue()
|
||||
}
|
||||
|
||||
func compareTimestamps(a, b *tspb.Timestamp) int {
|
||||
if c := compareInt64s(a.Seconds, b.Seconds); c != 0 {
|
||||
return c
|
||||
}
|
||||
return compareInt64s(int64(a.Nanos), int64(b.Nanos))
|
||||
}
|
||||
|
||||
func compareReferences(a, b string) int {
|
||||
// Compare path components lexicographically.
|
||||
pa := strings.Split(a, "/")
|
||||
pb := strings.Split(b, "/")
|
||||
return compareSequences(len(pa), len(pb), func(i int) int {
|
||||
return strings.Compare(pa[i], pb[i])
|
||||
})
|
||||
}
|
||||
|
||||
func compareArrays(a, b []*pb.Value) int {
|
||||
return compareSequences(len(a), len(b), func(i int) int {
|
||||
return compareValues(a[i], b[i])
|
||||
})
|
||||
}
|
||||
|
||||
func compareMaps(a, b map[string]*pb.Value) int {
|
||||
sortedKeys := func(m map[string]*pb.Value) []string {
|
||||
var ks []string
|
||||
for k := range m {
|
||||
ks = append(ks, k)
|
||||
}
|
||||
sort.Strings(ks)
|
||||
return ks
|
||||
}
|
||||
|
||||
aks := sortedKeys(a)
|
||||
bks := sortedKeys(b)
|
||||
return compareSequences(len(aks), len(bks), func(i int) int {
|
||||
if c := strings.Compare(aks[i], bks[i]); c != 0 {
|
||||
return c
|
||||
}
|
||||
k := aks[i]
|
||||
return compareValues(a[k], b[k])
|
||||
})
|
||||
}
|
||||
|
||||
func compareSequences(len1, len2 int, compare func(int) int) int {
|
||||
for i := 0; i < len1 && i < len2; i++ {
|
||||
if c := compare(i); c != 0 {
|
||||
return c
|
||||
}
|
||||
}
|
||||
return compareInt64s(int64(len1), int64(len2))
|
||||
}
|
||||
|
||||
func compareFloat64s(a, b float64) int {
|
||||
switch {
|
||||
case a < b:
|
||||
return -1
|
||||
case a > b:
|
||||
return 1
|
||||
default:
|
||||
return 0
|
||||
}
|
||||
}
|
||||
|
||||
func compareInt64s(a, b int64) int {
|
||||
switch {
|
||||
case a < b:
|
||||
return -1
|
||||
case a > b:
|
||||
return 1
|
||||
default:
|
||||
return 0
|
||||
}
|
||||
}
|
||||
|
||||
// Return an integer corresponding to the type of value stored in v, such that
|
||||
// comparing the resulting integers gives the Firestore ordering for types.
|
||||
func typeOrder(v *pb.Value) int {
|
||||
switch v.ValueType.(type) {
|
||||
case *pb.Value_NullValue:
|
||||
return 0
|
||||
case *pb.Value_BooleanValue:
|
||||
return 1
|
||||
case *pb.Value_IntegerValue:
|
||||
return 2
|
||||
case *pb.Value_DoubleValue:
|
||||
return 2
|
||||
case *pb.Value_TimestampValue:
|
||||
return 3
|
||||
case *pb.Value_StringValue:
|
||||
return 4
|
||||
case *pb.Value_BytesValue:
|
||||
return 5
|
||||
case *pb.Value_ReferenceValue:
|
||||
return 6
|
||||
case *pb.Value_GeoPointValue:
|
||||
return 7
|
||||
case *pb.Value_ArrayValue:
|
||||
return 8
|
||||
case *pb.Value_MapValue:
|
||||
return 9
|
||||
default:
|
||||
panic(fmt.Sprintf("bad value type: %v", v))
|
||||
}
|
||||
}
|
||||
-793
@@ -1,793 +0,0 @@
|
||||
// Copyright 2017 Google LLC
|
||||
//
|
||||
// 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 firestore
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"math"
|
||||
"reflect"
|
||||
"time"
|
||||
|
||||
"cloud.google.com/go/internal/btree"
|
||||
"github.com/golang/protobuf/ptypes/wrappers"
|
||||
"google.golang.org/api/iterator"
|
||||
pb "google.golang.org/genproto/googleapis/firestore/v1"
|
||||
)
|
||||
|
||||
// Query represents a Firestore query.
|
||||
//
|
||||
// Query values are immutable. Each Query method creates
|
||||
// a new Query; it does not modify the old.
|
||||
type Query struct {
|
||||
c *Client
|
||||
path string // path to query (collection)
|
||||
parentPath string // path of the collection's parent (document)
|
||||
collectionID string
|
||||
selection []FieldPath
|
||||
filters []filter
|
||||
orders []order
|
||||
offset int32
|
||||
limit *wrappers.Int32Value
|
||||
startVals, endVals []interface{}
|
||||
startDoc, endDoc *DocumentSnapshot
|
||||
startBefore, endBefore bool
|
||||
err error
|
||||
|
||||
// allDescendants indicates whether this query is for all collections
|
||||
// that match the ID under the specified parentPath.
|
||||
allDescendants bool
|
||||
}
|
||||
|
||||
// DocumentID is the special field name representing the ID of a document
|
||||
// in queries.
|
||||
const DocumentID = "__name__"
|
||||
|
||||
// Select returns a new Query that specifies the paths
|
||||
// to return from the result documents.
|
||||
// Each path argument can be a single field or a dot-separated sequence of
|
||||
// fields, and must not contain any of the runes "˜*/[]".
|
||||
//
|
||||
// An empty Select call will produce a query that returns only document IDs.
|
||||
func (q Query) Select(paths ...string) Query {
|
||||
var fps []FieldPath
|
||||
for _, s := range paths {
|
||||
fp, err := parseDotSeparatedString(s)
|
||||
if err != nil {
|
||||
q.err = err
|
||||
return q
|
||||
}
|
||||
fps = append(fps, fp)
|
||||
}
|
||||
return q.SelectPaths(fps...)
|
||||
}
|
||||
|
||||
// SelectPaths returns a new Query that specifies the field paths
|
||||
// to return from the result documents.
|
||||
//
|
||||
// An empty SelectPaths call will produce a query that returns only document IDs.
|
||||
func (q Query) SelectPaths(fieldPaths ...FieldPath) Query {
|
||||
if len(fieldPaths) == 0 {
|
||||
q.selection = []FieldPath{{DocumentID}}
|
||||
} else {
|
||||
q.selection = fieldPaths
|
||||
}
|
||||
return q
|
||||
}
|
||||
|
||||
// Where returns a new Query that filters the set of results.
|
||||
// A Query can have multiple filters.
|
||||
// The path argument can be a single field or a dot-separated sequence of
|
||||
// fields, and must not contain any of the runes "˜*/[]".
|
||||
// The op argument must be one of "==", "<", "<=", ">", ">=", "array-contains",
|
||||
// "array-contains-any" or "in".
|
||||
func (q Query) Where(path, op string, value interface{}) Query {
|
||||
fp, err := parseDotSeparatedString(path)
|
||||
if err != nil {
|
||||
q.err = err
|
||||
return q
|
||||
}
|
||||
q.filters = append(append([]filter(nil), q.filters...), filter{fp, op, value})
|
||||
return q
|
||||
}
|
||||
|
||||
// WherePath returns a new Query that filters the set of results.
|
||||
// A Query can have multiple filters.
|
||||
// The op argument must be one of "==", "<", "<=", ">", ">=", "array-contains",
|
||||
// "array-contains-any" or "in".
|
||||
func (q Query) WherePath(fp FieldPath, op string, value interface{}) Query {
|
||||
q.filters = append(append([]filter(nil), q.filters...), filter{fp, op, value})
|
||||
return q
|
||||
}
|
||||
|
||||
// Direction is the sort direction for result ordering.
|
||||
type Direction int32
|
||||
|
||||
const (
|
||||
// Asc sorts results from smallest to largest.
|
||||
Asc Direction = Direction(pb.StructuredQuery_ASCENDING)
|
||||
|
||||
// Desc sorts results from largest to smallest.
|
||||
Desc Direction = Direction(pb.StructuredQuery_DESCENDING)
|
||||
)
|
||||
|
||||
// OrderBy returns a new Query that specifies the order in which results are
|
||||
// returned. A Query can have multiple OrderBy/OrderByPath specifications.
|
||||
// OrderBy appends the specification to the list of existing ones.
|
||||
//
|
||||
// The path argument can be a single field or a dot-separated sequence of
|
||||
// fields, and must not contain any of the runes "˜*/[]".
|
||||
//
|
||||
// To order by document name, use the special field path DocumentID.
|
||||
func (q Query) OrderBy(path string, dir Direction) Query {
|
||||
fp, err := parseDotSeparatedString(path)
|
||||
if err != nil {
|
||||
q.err = err
|
||||
return q
|
||||
}
|
||||
q.orders = append(q.copyOrders(), order{fp, dir})
|
||||
return q
|
||||
}
|
||||
|
||||
// OrderByPath returns a new Query that specifies the order in which results are
|
||||
// returned. A Query can have multiple OrderBy/OrderByPath specifications.
|
||||
// OrderByPath appends the specification to the list of existing ones.
|
||||
func (q Query) OrderByPath(fp FieldPath, dir Direction) Query {
|
||||
q.orders = append(q.copyOrders(), order{fp, dir})
|
||||
return q
|
||||
}
|
||||
|
||||
func (q *Query) copyOrders() []order {
|
||||
return append([]order(nil), q.orders...)
|
||||
}
|
||||
|
||||
// Offset returns a new Query that specifies the number of initial results to skip.
|
||||
// It must not be negative.
|
||||
func (q Query) Offset(n int) Query {
|
||||
q.offset = trunc32(n)
|
||||
return q
|
||||
}
|
||||
|
||||
// Limit returns a new Query that specifies the maximum number of results to return.
|
||||
// It must not be negative.
|
||||
func (q Query) Limit(n int) Query {
|
||||
q.limit = &wrappers.Int32Value{Value: trunc32(n)}
|
||||
return q
|
||||
}
|
||||
|
||||
// StartAt returns a new Query that specifies that results should start at
|
||||
// the document with the given field values.
|
||||
//
|
||||
// StartAt may be called with a single DocumentSnapshot, representing an
|
||||
// existing document within the query. The document must be a direct child of
|
||||
// the location being queried (not a parent document, or document in a
|
||||
// different collection, or a grandchild document, for example).
|
||||
//
|
||||
// Otherwise, StartAt should be called with one field value for each OrderBy clause,
|
||||
// in the order that they appear. For example, in
|
||||
// q.OrderBy("X", Asc).OrderBy("Y", Desc).StartAt(1, 2)
|
||||
// results will begin at the first document where X = 1 and Y = 2.
|
||||
//
|
||||
// If an OrderBy call uses the special DocumentID field path, the corresponding value
|
||||
// should be the document ID relative to the query's collection. For example, to
|
||||
// start at the document "NewYork" in the "States" collection, write
|
||||
//
|
||||
// client.Collection("States").OrderBy(DocumentID, firestore.Asc).StartAt("NewYork")
|
||||
//
|
||||
// Calling StartAt overrides a previous call to StartAt or StartAfter.
|
||||
func (q Query) StartAt(docSnapshotOrFieldValues ...interface{}) Query {
|
||||
q.startBefore = true
|
||||
q.startVals, q.startDoc, q.err = q.processCursorArg("StartAt", docSnapshotOrFieldValues)
|
||||
return q
|
||||
}
|
||||
|
||||
// StartAfter returns a new Query that specifies that results should start just after
|
||||
// the document with the given field values. See Query.StartAt for more information.
|
||||
//
|
||||
// Calling StartAfter overrides a previous call to StartAt or StartAfter.
|
||||
func (q Query) StartAfter(docSnapshotOrFieldValues ...interface{}) Query {
|
||||
q.startBefore = false
|
||||
q.startVals, q.startDoc, q.err = q.processCursorArg("StartAfter", docSnapshotOrFieldValues)
|
||||
return q
|
||||
}
|
||||
|
||||
// EndAt returns a new Query that specifies that results should end at the
|
||||
// document with the given field values. See Query.StartAt for more information.
|
||||
//
|
||||
// Calling EndAt overrides a previous call to EndAt or EndBefore.
|
||||
func (q Query) EndAt(docSnapshotOrFieldValues ...interface{}) Query {
|
||||
q.endBefore = false
|
||||
q.endVals, q.endDoc, q.err = q.processCursorArg("EndAt", docSnapshotOrFieldValues)
|
||||
return q
|
||||
}
|
||||
|
||||
// EndBefore returns a new Query that specifies that results should end just before
|
||||
// the document with the given field values. See Query.StartAt for more information.
|
||||
//
|
||||
// Calling EndBefore overrides a previous call to EndAt or EndBefore.
|
||||
func (q Query) EndBefore(docSnapshotOrFieldValues ...interface{}) Query {
|
||||
q.endBefore = true
|
||||
q.endVals, q.endDoc, q.err = q.processCursorArg("EndBefore", docSnapshotOrFieldValues)
|
||||
return q
|
||||
}
|
||||
|
||||
func (q *Query) processCursorArg(name string, docSnapshotOrFieldValues []interface{}) ([]interface{}, *DocumentSnapshot, error) {
|
||||
for _, e := range docSnapshotOrFieldValues {
|
||||
if ds, ok := e.(*DocumentSnapshot); ok {
|
||||
if len(docSnapshotOrFieldValues) == 1 {
|
||||
return nil, ds, nil
|
||||
}
|
||||
return nil, nil, fmt.Errorf("firestore: a document snapshot must be the only argument to %s", name)
|
||||
}
|
||||
}
|
||||
return docSnapshotOrFieldValues, nil, nil
|
||||
}
|
||||
|
||||
func (q Query) query() *Query { return &q }
|
||||
|
||||
func (q Query) toProto() (*pb.StructuredQuery, error) {
|
||||
if q.err != nil {
|
||||
return nil, q.err
|
||||
}
|
||||
if q.collectionID == "" {
|
||||
return nil, errors.New("firestore: query created without CollectionRef")
|
||||
}
|
||||
if q.startBefore {
|
||||
if len(q.startVals) == 0 && q.startDoc == nil {
|
||||
return nil, errors.New("firestore: StartAt/StartAfter must be called with at least one value")
|
||||
}
|
||||
}
|
||||
if q.endBefore {
|
||||
if len(q.endVals) == 0 && q.endDoc == nil {
|
||||
return nil, errors.New("firestore: EndAt/EndBefore must be called with at least one value")
|
||||
}
|
||||
}
|
||||
p := &pb.StructuredQuery{
|
||||
From: []*pb.StructuredQuery_CollectionSelector{{
|
||||
CollectionId: q.collectionID,
|
||||
AllDescendants: q.allDescendants,
|
||||
}},
|
||||
Offset: q.offset,
|
||||
Limit: q.limit,
|
||||
}
|
||||
if len(q.selection) > 0 {
|
||||
p.Select = &pb.StructuredQuery_Projection{}
|
||||
for _, fp := range q.selection {
|
||||
if err := fp.validate(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
p.Select.Fields = append(p.Select.Fields, fref(fp))
|
||||
}
|
||||
}
|
||||
// If there is only filter, use it directly. Otherwise, construct
|
||||
// a CompositeFilter.
|
||||
if len(q.filters) == 1 {
|
||||
pf, err := q.filters[0].toProto()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
p.Where = pf
|
||||
} else if len(q.filters) > 1 {
|
||||
cf := &pb.StructuredQuery_CompositeFilter{
|
||||
Op: pb.StructuredQuery_CompositeFilter_AND,
|
||||
}
|
||||
p.Where = &pb.StructuredQuery_Filter{
|
||||
FilterType: &pb.StructuredQuery_Filter_CompositeFilter{cf},
|
||||
}
|
||||
for _, f := range q.filters {
|
||||
pf, err := f.toProto()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
cf.Filters = append(cf.Filters, pf)
|
||||
}
|
||||
}
|
||||
orders := q.orders
|
||||
if q.startDoc != nil || q.endDoc != nil {
|
||||
orders = q.adjustOrders()
|
||||
}
|
||||
for _, ord := range orders {
|
||||
po, err := ord.toProto()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
p.OrderBy = append(p.OrderBy, po)
|
||||
}
|
||||
|
||||
cursor, err := q.toCursor(q.startVals, q.startDoc, q.startBefore, orders)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
p.StartAt = cursor
|
||||
cursor, err = q.toCursor(q.endVals, q.endDoc, q.endBefore, orders)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
p.EndAt = cursor
|
||||
return p, nil
|
||||
}
|
||||
|
||||
// If there is a start/end that uses a Document Snapshot, we may need to adjust the OrderBy
|
||||
// clauses that the user provided: we add OrderBy(__name__) if it isn't already present, and
|
||||
// we make sure we don't invalidate the original query by adding an OrderBy for inequality filters.
|
||||
func (q *Query) adjustOrders() []order {
|
||||
// If the user is already ordering by document ID, don't change anything.
|
||||
for _, ord := range q.orders {
|
||||
if ord.isDocumentID() {
|
||||
return q.orders
|
||||
}
|
||||
}
|
||||
// If there are OrderBy clauses, append an OrderBy(DocumentID), using the direction of the last OrderBy clause.
|
||||
if len(q.orders) > 0 {
|
||||
return append(q.copyOrders(), order{
|
||||
fieldPath: FieldPath{DocumentID},
|
||||
dir: q.orders[len(q.orders)-1].dir,
|
||||
})
|
||||
}
|
||||
// If there are no OrderBy clauses but there is an inequality, add an OrderBy clause
|
||||
// for the field of the first inequality.
|
||||
var orders []order
|
||||
for _, f := range q.filters {
|
||||
if f.op != "==" {
|
||||
orders = []order{{fieldPath: f.fieldPath, dir: Asc}}
|
||||
break
|
||||
}
|
||||
}
|
||||
// Add an ascending OrderBy(DocumentID).
|
||||
return append(orders, order{fieldPath: FieldPath{DocumentID}, dir: Asc})
|
||||
}
|
||||
|
||||
func (q *Query) toCursor(fieldValues []interface{}, ds *DocumentSnapshot, before bool, orders []order) (*pb.Cursor, error) {
|
||||
var vals []*pb.Value
|
||||
var err error
|
||||
if ds != nil {
|
||||
vals, err = q.docSnapshotToCursorValues(ds, orders)
|
||||
} else if len(fieldValues) != 0 {
|
||||
vals, err = q.fieldValuesToCursorValues(fieldValues)
|
||||
} else {
|
||||
return nil, nil
|
||||
}
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &pb.Cursor{Values: vals, Before: before}, nil
|
||||
}
|
||||
|
||||
// toPositionValues converts the field values to protos.
|
||||
func (q *Query) fieldValuesToCursorValues(fieldValues []interface{}) ([]*pb.Value, error) {
|
||||
if len(fieldValues) != len(q.orders) {
|
||||
return nil, errors.New("firestore: number of field values in StartAt/StartAfter/EndAt/EndBefore does not match number of OrderBy fields")
|
||||
}
|
||||
vals := make([]*pb.Value, len(fieldValues))
|
||||
var err error
|
||||
for i, ord := range q.orders {
|
||||
fval := fieldValues[i]
|
||||
if ord.isDocumentID() {
|
||||
// TODO(jba): support DocumentRefs as well as strings.
|
||||
// TODO(jba): error if document ref does not belong to the right collection.
|
||||
docID, ok := fval.(string)
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("firestore: expected doc ID for DocumentID field, got %T", fval)
|
||||
}
|
||||
vals[i] = &pb.Value{ValueType: &pb.Value_ReferenceValue{q.path + "/" + docID}}
|
||||
} else {
|
||||
var sawTransform bool
|
||||
vals[i], sawTransform, err = toProtoValue(reflect.ValueOf(fval))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if sawTransform {
|
||||
return nil, errors.New("firestore: transforms disallowed in query value")
|
||||
}
|
||||
}
|
||||
}
|
||||
return vals, nil
|
||||
}
|
||||
|
||||
func (q *Query) docSnapshotToCursorValues(ds *DocumentSnapshot, orders []order) ([]*pb.Value, error) {
|
||||
vals := make([]*pb.Value, len(orders))
|
||||
for i, ord := range orders {
|
||||
if ord.isDocumentID() {
|
||||
dp, qp := ds.Ref.Parent.Path, q.path
|
||||
if !q.allDescendants && dp != qp {
|
||||
return nil, fmt.Errorf("firestore: document snapshot for %s passed to query on %s", dp, qp)
|
||||
}
|
||||
vals[i] = &pb.Value{ValueType: &pb.Value_ReferenceValue{ds.Ref.Path}}
|
||||
} else {
|
||||
val, err := valueAtPath(ord.fieldPath, ds.proto.Fields)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
vals[i] = val
|
||||
}
|
||||
}
|
||||
return vals, nil
|
||||
}
|
||||
|
||||
// Returns a function that compares DocumentSnapshots according to q's ordering.
|
||||
func (q Query) compareFunc() func(d1, d2 *DocumentSnapshot) (int, error) {
|
||||
// Add implicit sorting by name, using the last specified direction.
|
||||
lastDir := Asc
|
||||
if len(q.orders) > 0 {
|
||||
lastDir = q.orders[len(q.orders)-1].dir
|
||||
}
|
||||
orders := append(q.copyOrders(), order{[]string{DocumentID}, lastDir})
|
||||
return func(d1, d2 *DocumentSnapshot) (int, error) {
|
||||
for _, ord := range orders {
|
||||
var cmp int
|
||||
if len(ord.fieldPath) == 1 && ord.fieldPath[0] == DocumentID {
|
||||
cmp = compareReferences(d1.Ref.Path, d2.Ref.Path)
|
||||
} else {
|
||||
v1, err := valueAtPath(ord.fieldPath, d1.proto.Fields)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
v2, err := valueAtPath(ord.fieldPath, d2.proto.Fields)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
cmp = compareValues(v1, v2)
|
||||
}
|
||||
if cmp != 0 {
|
||||
if ord.dir == Desc {
|
||||
cmp = -cmp
|
||||
}
|
||||
return cmp, nil
|
||||
}
|
||||
}
|
||||
return 0, nil
|
||||
}
|
||||
}
|
||||
|
||||
type filter struct {
|
||||
fieldPath FieldPath
|
||||
op string
|
||||
value interface{}
|
||||
}
|
||||
|
||||
func (f filter) toProto() (*pb.StructuredQuery_Filter, error) {
|
||||
if err := f.fieldPath.validate(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if uop, ok := unaryOpFor(f.value); ok {
|
||||
if f.op != "==" {
|
||||
return nil, fmt.Errorf("firestore: must use '==' when comparing %v", f.value)
|
||||
}
|
||||
return &pb.StructuredQuery_Filter{
|
||||
FilterType: &pb.StructuredQuery_Filter_UnaryFilter{
|
||||
UnaryFilter: &pb.StructuredQuery_UnaryFilter{
|
||||
OperandType: &pb.StructuredQuery_UnaryFilter_Field{
|
||||
Field: fref(f.fieldPath),
|
||||
},
|
||||
Op: uop,
|
||||
},
|
||||
},
|
||||
}, nil
|
||||
}
|
||||
var op pb.StructuredQuery_FieldFilter_Operator
|
||||
switch f.op {
|
||||
case "<":
|
||||
op = pb.StructuredQuery_FieldFilter_LESS_THAN
|
||||
case "<=":
|
||||
op = pb.StructuredQuery_FieldFilter_LESS_THAN_OR_EQUAL
|
||||
case ">":
|
||||
op = pb.StructuredQuery_FieldFilter_GREATER_THAN
|
||||
case ">=":
|
||||
op = pb.StructuredQuery_FieldFilter_GREATER_THAN_OR_EQUAL
|
||||
case "==":
|
||||
op = pb.StructuredQuery_FieldFilter_EQUAL
|
||||
case "in":
|
||||
op = pb.StructuredQuery_FieldFilter_IN
|
||||
case "array-contains":
|
||||
op = pb.StructuredQuery_FieldFilter_ARRAY_CONTAINS
|
||||
case "array-contains-any":
|
||||
op = pb.StructuredQuery_FieldFilter_ARRAY_CONTAINS_ANY
|
||||
default:
|
||||
return nil, fmt.Errorf("firestore: invalid operator %q", f.op)
|
||||
}
|
||||
val, sawTransform, err := toProtoValue(reflect.ValueOf(f.value))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if sawTransform {
|
||||
return nil, errors.New("firestore: transforms disallowed in query value")
|
||||
}
|
||||
return &pb.StructuredQuery_Filter{
|
||||
FilterType: &pb.StructuredQuery_Filter_FieldFilter{
|
||||
FieldFilter: &pb.StructuredQuery_FieldFilter{
|
||||
Field: fref(f.fieldPath),
|
||||
Op: op,
|
||||
Value: val,
|
||||
},
|
||||
},
|
||||
}, nil
|
||||
}
|
||||
|
||||
func unaryOpFor(value interface{}) (pb.StructuredQuery_UnaryFilter_Operator, bool) {
|
||||
switch {
|
||||
case value == nil:
|
||||
return pb.StructuredQuery_UnaryFilter_IS_NULL, true
|
||||
case isNaN(value):
|
||||
return pb.StructuredQuery_UnaryFilter_IS_NAN, true
|
||||
default:
|
||||
return pb.StructuredQuery_UnaryFilter_OPERATOR_UNSPECIFIED, false
|
||||
}
|
||||
}
|
||||
|
||||
func isNaN(x interface{}) bool {
|
||||
switch x := x.(type) {
|
||||
case float32:
|
||||
return math.IsNaN(float64(x))
|
||||
case float64:
|
||||
return math.IsNaN(x)
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
type order struct {
|
||||
fieldPath FieldPath
|
||||
dir Direction
|
||||
}
|
||||
|
||||
func (r order) isDocumentID() bool {
|
||||
return len(r.fieldPath) == 1 && r.fieldPath[0] == DocumentID
|
||||
}
|
||||
|
||||
func (r order) toProto() (*pb.StructuredQuery_Order, error) {
|
||||
if err := r.fieldPath.validate(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &pb.StructuredQuery_Order{
|
||||
Field: fref(r.fieldPath),
|
||||
Direction: pb.StructuredQuery_Direction(r.dir),
|
||||
}, nil
|
||||
}
|
||||
|
||||
func fref(fp FieldPath) *pb.StructuredQuery_FieldReference {
|
||||
return &pb.StructuredQuery_FieldReference{FieldPath: fp.toServiceFieldPath()}
|
||||
}
|
||||
|
||||
func trunc32(i int) int32 {
|
||||
if i > math.MaxInt32 {
|
||||
i = math.MaxInt32
|
||||
}
|
||||
return int32(i)
|
||||
}
|
||||
|
||||
// Documents returns an iterator over the query's resulting documents.
|
||||
func (q Query) Documents(ctx context.Context) *DocumentIterator {
|
||||
return &DocumentIterator{
|
||||
iter: newQueryDocumentIterator(withResourceHeader(ctx, q.c.path()), &q, nil),
|
||||
}
|
||||
}
|
||||
|
||||
// DocumentIterator is an iterator over documents returned by a query.
|
||||
type DocumentIterator struct {
|
||||
iter docIterator
|
||||
err error
|
||||
}
|
||||
|
||||
// Unexported interface so we can have two different kinds of DocumentIterator: one
|
||||
// for straight queries, and one for query snapshots. We do it this way instead of
|
||||
// making DocumentIterator an interface because in the client libraries, iterators are
|
||||
// always concrete types, and the fact that this one has two different implementations
|
||||
// is an internal detail.
|
||||
type docIterator interface {
|
||||
next() (*DocumentSnapshot, error)
|
||||
stop()
|
||||
}
|
||||
|
||||
// Next returns the next result. Its second return value is iterator.Done if there
|
||||
// are no more results. Once Next returns Done, all subsequent calls will return
|
||||
// Done.
|
||||
func (it *DocumentIterator) Next() (*DocumentSnapshot, error) {
|
||||
if it.err != nil {
|
||||
return nil, it.err
|
||||
}
|
||||
ds, err := it.iter.next()
|
||||
if err != nil {
|
||||
it.err = err
|
||||
}
|
||||
return ds, err
|
||||
}
|
||||
|
||||
// Stop stops the iterator, freeing its resources.
|
||||
// Always call Stop when you are done with a DocumentIterator.
|
||||
// It is not safe to call Stop concurrently with Next.
|
||||
func (it *DocumentIterator) Stop() {
|
||||
if it.iter != nil { // possible in error cases
|
||||
it.iter.stop()
|
||||
}
|
||||
if it.err == nil {
|
||||
it.err = iterator.Done
|
||||
}
|
||||
}
|
||||
|
||||
// GetAll returns all the documents remaining from the iterator.
|
||||
// It is not necessary to call Stop on the iterator after calling GetAll.
|
||||
func (it *DocumentIterator) GetAll() ([]*DocumentSnapshot, error) {
|
||||
defer it.Stop()
|
||||
var docs []*DocumentSnapshot
|
||||
for {
|
||||
doc, err := it.Next()
|
||||
if err == iterator.Done {
|
||||
break
|
||||
}
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
docs = append(docs, doc)
|
||||
}
|
||||
return docs, nil
|
||||
}
|
||||
|
||||
type queryDocumentIterator struct {
|
||||
ctx context.Context
|
||||
cancel func()
|
||||
q *Query
|
||||
tid []byte // transaction ID, if any
|
||||
streamClient pb.Firestore_RunQueryClient
|
||||
}
|
||||
|
||||
func newQueryDocumentIterator(ctx context.Context, q *Query, tid []byte) *queryDocumentIterator {
|
||||
ctx, cancel := context.WithCancel(ctx)
|
||||
return &queryDocumentIterator{
|
||||
ctx: ctx,
|
||||
cancel: cancel,
|
||||
q: q,
|
||||
tid: tid,
|
||||
}
|
||||
}
|
||||
|
||||
func (it *queryDocumentIterator) next() (*DocumentSnapshot, error) {
|
||||
client := it.q.c
|
||||
if it.streamClient == nil {
|
||||
sq, err := it.q.toProto()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
req := &pb.RunQueryRequest{
|
||||
Parent: it.q.parentPath,
|
||||
QueryType: &pb.RunQueryRequest_StructuredQuery{sq},
|
||||
}
|
||||
if it.tid != nil {
|
||||
req.ConsistencySelector = &pb.RunQueryRequest_Transaction{it.tid}
|
||||
}
|
||||
it.streamClient, err = client.c.RunQuery(it.ctx, req)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
var res *pb.RunQueryResponse
|
||||
var err error
|
||||
for {
|
||||
res, err = it.streamClient.Recv()
|
||||
if err == io.EOF {
|
||||
return nil, iterator.Done
|
||||
}
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if res.Document != nil {
|
||||
break
|
||||
}
|
||||
// No document => partial progress; keep receiving.
|
||||
}
|
||||
docRef, err := pathToDoc(res.Document.Name, client)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
doc, err := newDocumentSnapshot(docRef, res.Document, client, res.ReadTime)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return doc, nil
|
||||
}
|
||||
|
||||
func (it *queryDocumentIterator) stop() {
|
||||
it.cancel()
|
||||
}
|
||||
|
||||
// Snapshots returns an iterator over snapshots of the query. Each time the query
|
||||
// results change, a new snapshot will be generated.
|
||||
func (q Query) Snapshots(ctx context.Context) *QuerySnapshotIterator {
|
||||
ws, err := newWatchStreamForQuery(ctx, q)
|
||||
if err != nil {
|
||||
return &QuerySnapshotIterator{err: err}
|
||||
}
|
||||
return &QuerySnapshotIterator{
|
||||
Query: q,
|
||||
ws: ws,
|
||||
}
|
||||
}
|
||||
|
||||
// QuerySnapshotIterator is an iterator over snapshots of a query.
|
||||
// Call Next on the iterator to get a snapshot of the query's results each time they change.
|
||||
// Call Stop on the iterator when done.
|
||||
//
|
||||
// For an example, see Query.Snapshots.
|
||||
type QuerySnapshotIterator struct {
|
||||
// The Query used to construct this iterator.
|
||||
Query Query
|
||||
|
||||
ws *watchStream
|
||||
err error
|
||||
}
|
||||
|
||||
// Next blocks until the query's results change, then returns a QuerySnapshot for
|
||||
// the current results.
|
||||
//
|
||||
// Next never returns iterator.Done unless it is called after Stop.
|
||||
func (it *QuerySnapshotIterator) Next() (*QuerySnapshot, error) {
|
||||
if it.err != nil {
|
||||
return nil, it.err
|
||||
}
|
||||
btree, changes, readTime, err := it.ws.nextSnapshot()
|
||||
if err != nil {
|
||||
if err == io.EOF {
|
||||
err = iterator.Done
|
||||
}
|
||||
it.err = err
|
||||
return nil, it.err
|
||||
}
|
||||
return &QuerySnapshot{
|
||||
Documents: &DocumentIterator{
|
||||
iter: (*btreeDocumentIterator)(btree.BeforeIndex(0)),
|
||||
},
|
||||
Size: btree.Len(),
|
||||
Changes: changes,
|
||||
ReadTime: readTime,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// Stop stops receiving snapshots. You should always call Stop when you are done with
|
||||
// a QuerySnapshotIterator, to free up resources. It is not safe to call Stop
|
||||
// concurrently with Next.
|
||||
func (it *QuerySnapshotIterator) Stop() {
|
||||
if it.ws != nil {
|
||||
it.ws.stop()
|
||||
}
|
||||
}
|
||||
|
||||
// A QuerySnapshot is a snapshot of query results. It is returned by
|
||||
// QuerySnapshotIterator.Next whenever the results of a query change.
|
||||
type QuerySnapshot struct {
|
||||
// An iterator over the query results.
|
||||
// It is not necessary to call Stop on this iterator.
|
||||
Documents *DocumentIterator
|
||||
|
||||
// The number of results in this snapshot.
|
||||
Size int
|
||||
|
||||
// The changes since the previous snapshot.
|
||||
Changes []DocumentChange
|
||||
|
||||
// The time at which this snapshot was obtained from Firestore.
|
||||
ReadTime time.Time
|
||||
}
|
||||
|
||||
type btreeDocumentIterator btree.Iterator
|
||||
|
||||
func (it *btreeDocumentIterator) next() (*DocumentSnapshot, error) {
|
||||
if !(*btree.Iterator)(it).Next() {
|
||||
return nil, iterator.Done
|
||||
}
|
||||
return it.Key.(*DocumentSnapshot), nil
|
||||
}
|
||||
|
||||
func (*btreeDocumentIterator) stop() {}
|
||||
-307
@@ -1,307 +0,0 @@
|
||||
// Copyright 2017 Google LLC
|
||||
//
|
||||
// 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 firestore
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"reflect"
|
||||
"time"
|
||||
|
||||
"cloud.google.com/go/internal/fields"
|
||||
"github.com/golang/protobuf/ptypes"
|
||||
ts "github.com/golang/protobuf/ptypes/timestamp"
|
||||
pb "google.golang.org/genproto/googleapis/firestore/v1"
|
||||
"google.golang.org/genproto/googleapis/type/latlng"
|
||||
)
|
||||
|
||||
var nullValue = &pb.Value{ValueType: &pb.Value_NullValue{}}
|
||||
|
||||
var (
|
||||
typeOfByteSlice = reflect.TypeOf([]byte{})
|
||||
typeOfGoTime = reflect.TypeOf(time.Time{})
|
||||
typeOfLatLng = reflect.TypeOf((*latlng.LatLng)(nil))
|
||||
typeOfDocumentRef = reflect.TypeOf((*DocumentRef)(nil))
|
||||
typeOfProtoTimestamp = reflect.TypeOf((*ts.Timestamp)(nil))
|
||||
)
|
||||
|
||||
// toProtoValue converts a Go value to a Firestore Value protobuf.
|
||||
// Some corner cases:
|
||||
// - All nils (nil interface, nil slice, nil map, nil pointer) are converted to
|
||||
// a NullValue (not a nil *pb.Value). toProtoValue never returns (nil, false, nil).
|
||||
// It returns (nil, true, nil) if everything in the value is ServerTimestamp.
|
||||
// - An error is returned for uintptr, uint, and uint64, because Firestore uses
|
||||
// an int64 to represent integral values, and those types can't be properly
|
||||
// represented in an int64.
|
||||
// - An error is returned for the special Delete value.
|
||||
//
|
||||
// toProtoValue also reports whether it recursively encountered a transform.
|
||||
func toProtoValue(v reflect.Value) (pbv *pb.Value, sawTransform bool, err error) {
|
||||
if !v.IsValid() {
|
||||
return nullValue, false, nil
|
||||
}
|
||||
vi := v.Interface()
|
||||
if vi == Delete {
|
||||
return nil, false, errors.New("firestore: cannot use Delete in value")
|
||||
}
|
||||
if vi == ServerTimestamp {
|
||||
return nil, false, errors.New("firestore: must use ServerTimestamp as a map value")
|
||||
}
|
||||
switch x := vi.(type) {
|
||||
case []byte:
|
||||
return &pb.Value{ValueType: &pb.Value_BytesValue{x}}, false, nil
|
||||
case time.Time:
|
||||
ts, err := ptypes.TimestampProto(x)
|
||||
if err != nil {
|
||||
return nil, false, err
|
||||
}
|
||||
return &pb.Value{ValueType: &pb.Value_TimestampValue{ts}}, false, nil
|
||||
case *ts.Timestamp:
|
||||
if x == nil {
|
||||
// gRPC doesn't like nil oneofs. Use NullValue.
|
||||
return nullValue, false, nil
|
||||
}
|
||||
return &pb.Value{ValueType: &pb.Value_TimestampValue{x}}, false, nil
|
||||
case *latlng.LatLng:
|
||||
if x == nil {
|
||||
// gRPC doesn't like nil oneofs. Use NullValue.
|
||||
return nullValue, false, nil
|
||||
}
|
||||
return &pb.Value{ValueType: &pb.Value_GeoPointValue{x}}, false, nil
|
||||
case *DocumentRef:
|
||||
if x == nil {
|
||||
// gRPC doesn't like nil oneofs. Use NullValue.
|
||||
return nullValue, false, nil
|
||||
}
|
||||
return &pb.Value{ValueType: &pb.Value_ReferenceValue{x.Path}}, false, nil
|
||||
// Do not add bool, string, int, etc. to this switch; leave them in the
|
||||
// reflect-based switch below. Moving them here would drop support for
|
||||
// types whose underlying types are those primitives.
|
||||
// E.g. Given "type mybool bool", an ordinary type switch on bool will
|
||||
// not catch a mybool, but the reflect.Kind of a mybool is reflect.Bool.
|
||||
}
|
||||
switch v.Kind() {
|
||||
case reflect.Bool:
|
||||
return &pb.Value{ValueType: &pb.Value_BooleanValue{v.Bool()}}, false, nil
|
||||
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
|
||||
return &pb.Value{ValueType: &pb.Value_IntegerValue{v.Int()}}, false, nil
|
||||
case reflect.Uint8, reflect.Uint16, reflect.Uint32:
|
||||
return &pb.Value{ValueType: &pb.Value_IntegerValue{int64(v.Uint())}}, false, nil
|
||||
case reflect.Float32, reflect.Float64:
|
||||
return &pb.Value{ValueType: &pb.Value_DoubleValue{v.Float()}}, false, nil
|
||||
case reflect.String:
|
||||
return &pb.Value{ValueType: &pb.Value_StringValue{v.String()}}, false, nil
|
||||
case reflect.Array:
|
||||
return arrayToProtoValue(v)
|
||||
case reflect.Slice:
|
||||
return sliceToProtoValue(v)
|
||||
case reflect.Map:
|
||||
return mapToProtoValue(v)
|
||||
case reflect.Struct:
|
||||
return structToProtoValue(v)
|
||||
case reflect.Ptr:
|
||||
if v.IsNil() {
|
||||
return nullValue, false, nil
|
||||
}
|
||||
return toProtoValue(v.Elem())
|
||||
case reflect.Interface:
|
||||
if v.NumMethod() == 0 { // empty interface: recurse on its contents
|
||||
return toProtoValue(v.Elem())
|
||||
}
|
||||
fallthrough // any other interface value is an error
|
||||
|
||||
default:
|
||||
return nil, false, fmt.Errorf("firestore: cannot convert type %s to value", v.Type())
|
||||
}
|
||||
}
|
||||
|
||||
// arrayToProtoValue converts a array to a Firestore Value protobuf and reports
|
||||
// whether a transform was encountered.
|
||||
func arrayToProtoValue(v reflect.Value) (*pb.Value, bool, error) {
|
||||
vals := make([]*pb.Value, v.Len())
|
||||
for i := 0; i < v.Len(); i++ {
|
||||
val, sawTransform, err := toProtoValue(v.Index(i))
|
||||
if err != nil {
|
||||
return nil, false, err
|
||||
}
|
||||
if sawTransform {
|
||||
return nil, false, fmt.Errorf("firestore: transforms cannot occur in an array, but saw some in %v", v.Index(i))
|
||||
}
|
||||
vals[i] = val
|
||||
}
|
||||
return &pb.Value{ValueType: &pb.Value_ArrayValue{&pb.ArrayValue{Values: vals}}}, false, nil
|
||||
}
|
||||
|
||||
// sliceToProtoValue converts a slice to a Firestore Value protobuf and reports
|
||||
// whether a transform was encountered.
|
||||
func sliceToProtoValue(v reflect.Value) (*pb.Value, bool, error) {
|
||||
// A nil slice is converted to a null value.
|
||||
if v.IsNil() {
|
||||
return nullValue, false, nil
|
||||
}
|
||||
return arrayToProtoValue(v)
|
||||
}
|
||||
|
||||
// mapToProtoValue converts a map to a Firestore Value protobuf and reports whether
|
||||
// a transform was encountered.
|
||||
func mapToProtoValue(v reflect.Value) (*pb.Value, bool, error) {
|
||||
if v.Type().Key().Kind() != reflect.String {
|
||||
return nil, false, errors.New("firestore: map key type must be string")
|
||||
}
|
||||
// A nil map is converted to a null value.
|
||||
if v.IsNil() {
|
||||
return nullValue, false, nil
|
||||
}
|
||||
m := map[string]*pb.Value{}
|
||||
sawTransform := false
|
||||
for _, k := range v.MapKeys() {
|
||||
mi := v.MapIndex(k)
|
||||
if mi.Interface() == ServerTimestamp {
|
||||
sawTransform = true
|
||||
continue
|
||||
} else if _, ok := mi.Interface().(arrayUnion); ok {
|
||||
sawTransform = true
|
||||
continue
|
||||
} else if _, ok := mi.Interface().(arrayRemove); ok {
|
||||
sawTransform = true
|
||||
continue
|
||||
}
|
||||
val, sst, err := toProtoValue(mi)
|
||||
if err != nil {
|
||||
return nil, false, err
|
||||
}
|
||||
if sst {
|
||||
sawTransform = true
|
||||
}
|
||||
if val == nil { // value was a map with all ServerTimestamp values
|
||||
continue
|
||||
}
|
||||
m[k.String()] = val
|
||||
}
|
||||
var pv *pb.Value
|
||||
if len(m) == 0 && sawTransform {
|
||||
// The entire map consisted of transform values.
|
||||
pv = nil
|
||||
} else {
|
||||
pv = &pb.Value{ValueType: &pb.Value_MapValue{&pb.MapValue{Fields: m}}}
|
||||
}
|
||||
return pv, sawTransform, nil
|
||||
}
|
||||
|
||||
// structToProtoValue converts a struct to a Firestore Value protobuf and reports
|
||||
// whether a transform was encountered.
|
||||
func structToProtoValue(v reflect.Value) (*pb.Value, bool, error) {
|
||||
m := map[string]*pb.Value{}
|
||||
fields, err := fieldCache.Fields(v.Type())
|
||||
if err != nil {
|
||||
return nil, false, err
|
||||
}
|
||||
sawTransform := false
|
||||
if _, ok := v.Interface().(arrayUnion); ok {
|
||||
return nil, false, errors.New("firestore: ArrayUnion may not be used in structs")
|
||||
}
|
||||
if _, ok := v.Interface().(arrayRemove); ok {
|
||||
return nil, false, errors.New("firestore: ArrayRemove may not be used in structs")
|
||||
}
|
||||
|
||||
for _, f := range fields {
|
||||
fv := v.FieldByIndex(f.Index)
|
||||
opts := f.ParsedTag.(tagOptions)
|
||||
if opts.serverTimestamp {
|
||||
// TODO(jba): should we return a non-zero time?
|
||||
sawTransform = true
|
||||
continue
|
||||
}
|
||||
if opts.omitEmpty && isEmptyValue(fv) {
|
||||
continue
|
||||
}
|
||||
val, sst, err := toProtoValue(fv)
|
||||
if err != nil {
|
||||
return nil, false, err
|
||||
}
|
||||
if sst {
|
||||
sawTransform = true
|
||||
}
|
||||
if val == nil { // value was a map with all ServerTimestamp values
|
||||
continue
|
||||
}
|
||||
m[f.Name] = val
|
||||
}
|
||||
var pv *pb.Value
|
||||
if len(m) == 0 && sawTransform {
|
||||
// The entire struct consisted of ServerTimestamp or omitempty values.
|
||||
pv = nil
|
||||
} else {
|
||||
pv = &pb.Value{ValueType: &pb.Value_MapValue{&pb.MapValue{Fields: m}}}
|
||||
}
|
||||
return pv, sawTransform, nil
|
||||
}
|
||||
|
||||
type tagOptions struct {
|
||||
omitEmpty bool // do not marshal value if empty
|
||||
serverTimestamp bool // set time.Time to server timestamp on write
|
||||
}
|
||||
|
||||
// parseTag interprets firestore struct field tags.
|
||||
func parseTag(t reflect.StructTag) (name string, keep bool, other interface{}, err error) {
|
||||
name, keep, opts, err := fields.ParseStandardTag("firestore", t)
|
||||
if err != nil {
|
||||
return "", false, nil, fmt.Errorf("firestore: %v", err)
|
||||
}
|
||||
tagOpts := tagOptions{}
|
||||
for _, opt := range opts {
|
||||
switch opt {
|
||||
case "omitempty":
|
||||
tagOpts.omitEmpty = true
|
||||
case "serverTimestamp":
|
||||
tagOpts.serverTimestamp = true
|
||||
default:
|
||||
return "", false, nil, fmt.Errorf("firestore: unknown tag option: %q", opt)
|
||||
}
|
||||
}
|
||||
return name, keep, tagOpts, nil
|
||||
}
|
||||
|
||||
// isLeafType determines whether or not a type is a 'leaf type'
|
||||
// and should not be recursed into, but considered one field.
|
||||
func isLeafType(t reflect.Type) bool {
|
||||
return t == typeOfGoTime || t == typeOfLatLng || t == typeOfProtoTimestamp
|
||||
}
|
||||
|
||||
var fieldCache = fields.NewCache(parseTag, nil, isLeafType)
|
||||
|
||||
// isEmptyValue is taken from the encoding/json package in the
|
||||
// standard library.
|
||||
// TODO(jba): move to the fields package
|
||||
func isEmptyValue(v reflect.Value) bool {
|
||||
switch v.Kind() {
|
||||
case reflect.Array, reflect.Map, reflect.Slice, reflect.String:
|
||||
return v.Len() == 0
|
||||
case reflect.Bool:
|
||||
return !v.Bool()
|
||||
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
|
||||
return v.Int() == 0
|
||||
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr:
|
||||
return v.Uint() == 0
|
||||
case reflect.Float32, reflect.Float64:
|
||||
return v.Float() == 0
|
||||
case reflect.Interface, reflect.Ptr:
|
||||
return v.IsNil()
|
||||
}
|
||||
if v.Type() == typeOfGoTime {
|
||||
return v.Interface().(time.Time).IsZero()
|
||||
}
|
||||
return false
|
||||
}
|
||||
-280
@@ -1,280 +0,0 @@
|
||||
// Copyright 2017 Google LLC
|
||||
//
|
||||
// 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 firestore
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
|
||||
"cloud.google.com/go/internal/trace"
|
||||
gax "github.com/googleapis/gax-go/v2"
|
||||
pb "google.golang.org/genproto/googleapis/firestore/v1"
|
||||
"google.golang.org/grpc/codes"
|
||||
"google.golang.org/grpc/status"
|
||||
)
|
||||
|
||||
// Transaction represents a Firestore transaction.
|
||||
type Transaction struct {
|
||||
c *Client
|
||||
ctx context.Context
|
||||
id []byte
|
||||
writes []*pb.Write
|
||||
maxAttempts int
|
||||
readOnly bool
|
||||
readAfterWrite bool
|
||||
}
|
||||
|
||||
// A TransactionOption is an option passed to Client.Transaction.
|
||||
type TransactionOption interface {
|
||||
config(t *Transaction)
|
||||
}
|
||||
|
||||
// MaxAttempts is a TransactionOption that configures the maximum number of times to
|
||||
// try a transaction. In defaults to DefaultTransactionMaxAttempts.
|
||||
func MaxAttempts(n int) maxAttempts { return maxAttempts(n) }
|
||||
|
||||
type maxAttempts int
|
||||
|
||||
func (m maxAttempts) config(t *Transaction) { t.maxAttempts = int(m) }
|
||||
|
||||
// DefaultTransactionMaxAttempts is the default number of times to attempt a transaction.
|
||||
const DefaultTransactionMaxAttempts = 5
|
||||
|
||||
// ReadOnly is a TransactionOption that makes the transaction read-only. Read-only
|
||||
// transactions cannot issue write operations, but are more efficient.
|
||||
var ReadOnly = ro{}
|
||||
|
||||
type ro struct{}
|
||||
|
||||
func (ro) config(t *Transaction) { t.readOnly = true }
|
||||
|
||||
var (
|
||||
// Defined here for testing.
|
||||
errReadAfterWrite = errors.New("firestore: read after write in transaction")
|
||||
errWriteReadOnly = errors.New("firestore: write in read-only transaction")
|
||||
errNestedTransaction = errors.New("firestore: nested transaction")
|
||||
)
|
||||
|
||||
type transactionInProgressKey struct{}
|
||||
|
||||
// RunTransaction runs f in a transaction. f should use the transaction it is given
|
||||
// for all Firestore operations. For any operation requiring a context, f should use
|
||||
// the context it is passed, not the first argument to RunTransaction.
|
||||
//
|
||||
// f must not call Commit or Rollback on the provided Transaction.
|
||||
//
|
||||
// If f returns nil, RunTransaction commits the transaction. If the commit fails due
|
||||
// to a conflicting transaction, RunTransaction retries f. It gives up and returns an
|
||||
// error after a number of attempts that can be configured with the MaxAttempts
|
||||
// option. If the commit succeeds, RunTransaction returns a nil error.
|
||||
//
|
||||
// If f returns non-nil, then the transaction will be rolled back and
|
||||
// this method will return the same error. The function f is not retried.
|
||||
//
|
||||
// Note that when f returns, the transaction is not committed. Calling code
|
||||
// must not assume that any of f's changes have been committed until
|
||||
// RunTransaction returns nil.
|
||||
//
|
||||
// Since f may be called more than once, f should usually be idempotent – that is, it
|
||||
// should have the same result when called multiple times.
|
||||
func (c *Client) RunTransaction(ctx context.Context, f func(context.Context, *Transaction) error, opts ...TransactionOption) (err error) {
|
||||
ctx = trace.StartSpan(ctx, "cloud.google.com/go/firestore.Client.RunTransaction")
|
||||
defer func() { trace.EndSpan(ctx, err) }()
|
||||
|
||||
if ctx.Value(transactionInProgressKey{}) != nil {
|
||||
return errNestedTransaction
|
||||
}
|
||||
db := c.path()
|
||||
t := &Transaction{
|
||||
c: c,
|
||||
ctx: withResourceHeader(ctx, db),
|
||||
maxAttempts: DefaultTransactionMaxAttempts,
|
||||
}
|
||||
for _, opt := range opts {
|
||||
opt.config(t)
|
||||
}
|
||||
var txOpts *pb.TransactionOptions
|
||||
if t.readOnly {
|
||||
txOpts = &pb.TransactionOptions{
|
||||
Mode: &pb.TransactionOptions_ReadOnly_{&pb.TransactionOptions_ReadOnly{}},
|
||||
}
|
||||
}
|
||||
var backoff gax.Backoff
|
||||
// TODO(jba): use other than the standard backoff parameters?
|
||||
// TODO(jba): get backoff time from gRPC trailer metadata? See
|
||||
// extractRetryDelay in https://code.googlesource.com/gocloud/+/master/spanner/retry.go.
|
||||
for i := 0; i < t.maxAttempts; i++ {
|
||||
var res *pb.BeginTransactionResponse
|
||||
res, err = t.c.c.BeginTransaction(t.ctx, &pb.BeginTransactionRequest{
|
||||
Database: db,
|
||||
Options: txOpts,
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
t.id = res.Transaction
|
||||
err = f(context.WithValue(ctx, transactionInProgressKey{}, 1), t)
|
||||
// Read after write can only be checked client-side, so we make sure to check
|
||||
// even if the user does not.
|
||||
if err == nil && t.readAfterWrite {
|
||||
err = errReadAfterWrite
|
||||
}
|
||||
if err != nil {
|
||||
t.rollback()
|
||||
// Prefer f's returned error to rollback error.
|
||||
return err
|
||||
}
|
||||
_, err = t.c.c.Commit(t.ctx, &pb.CommitRequest{
|
||||
Database: t.c.path(),
|
||||
Writes: t.writes,
|
||||
Transaction: t.id,
|
||||
})
|
||||
// If a read-write transaction returns Aborted, retry.
|
||||
// On success or other failures, return here.
|
||||
if t.readOnly || status.Code(err) != codes.Aborted {
|
||||
// According to the Firestore team, we should not roll back here
|
||||
// if err != nil. But spanner does.
|
||||
// See https://code.googlesource.com/gocloud/+/master/spanner/transaction.go#740.
|
||||
return err
|
||||
}
|
||||
|
||||
if txOpts == nil {
|
||||
// txOpts can only be nil if is the first retry of a read-write transaction.
|
||||
// (It is only set here and in the body of "if t.readOnly" above.)
|
||||
// Mention the transaction ID in BeginTransaction so the service
|
||||
// knows it is a retry.
|
||||
txOpts = &pb.TransactionOptions{
|
||||
Mode: &pb.TransactionOptions_ReadWrite_{
|
||||
&pb.TransactionOptions_ReadWrite{RetryTransaction: t.id},
|
||||
},
|
||||
}
|
||||
}
|
||||
// Use exponential backoff to avoid contention with other running
|
||||
// transactions.
|
||||
if cerr := sleep(ctx, backoff.Pause()); cerr != nil {
|
||||
err = cerr
|
||||
break
|
||||
}
|
||||
|
||||
// Reset state for the next attempt.
|
||||
t.writes = nil
|
||||
}
|
||||
// If we run out of retries, return the last error we saw (which should
|
||||
// be the Aborted from Commit, or a context error).
|
||||
if err != nil {
|
||||
t.rollback()
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
func (t *Transaction) rollback() {
|
||||
_ = t.c.c.Rollback(t.ctx, &pb.RollbackRequest{
|
||||
Database: t.c.path(),
|
||||
Transaction: t.id,
|
||||
})
|
||||
// Ignore the rollback error.
|
||||
// TODO(jba): Log it?
|
||||
// Note: Rollback is idempotent so it will be retried by the gapic layer.
|
||||
}
|
||||
|
||||
// Get gets the document in the context of the transaction. The transaction holds a
|
||||
// pessimistic lock on the returned document.
|
||||
func (t *Transaction) Get(dr *DocumentRef) (*DocumentSnapshot, error) {
|
||||
docsnaps, err := t.GetAll([]*DocumentRef{dr})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
ds := docsnaps[0]
|
||||
if !ds.Exists() {
|
||||
return ds, status.Errorf(codes.NotFound, "%q not found", dr.Path)
|
||||
}
|
||||
return ds, nil
|
||||
}
|
||||
|
||||
// GetAll retrieves multiple documents with a single call. The DocumentSnapshots are
|
||||
// returned in the order of the given DocumentRefs. If a document is not present, the
|
||||
// corresponding DocumentSnapshot's Exists method will return false. The transaction
|
||||
// holds a pessimistic lock on all of the returned documents.
|
||||
func (t *Transaction) GetAll(drs []*DocumentRef) ([]*DocumentSnapshot, error) {
|
||||
if len(t.writes) > 0 {
|
||||
t.readAfterWrite = true
|
||||
return nil, errReadAfterWrite
|
||||
}
|
||||
return t.c.getAll(t.ctx, drs, t.id)
|
||||
}
|
||||
|
||||
// A Queryer is a Query or a CollectionRef. CollectionRefs act as queries whose
|
||||
// results are all the documents in the collection.
|
||||
type Queryer interface {
|
||||
query() *Query
|
||||
}
|
||||
|
||||
// Documents returns a DocumentIterator based on given Query or CollectionRef. The
|
||||
// results will be in the context of the transaction.
|
||||
func (t *Transaction) Documents(q Queryer) *DocumentIterator {
|
||||
if len(t.writes) > 0 {
|
||||
t.readAfterWrite = true
|
||||
return &DocumentIterator{err: errReadAfterWrite}
|
||||
}
|
||||
return &DocumentIterator{
|
||||
iter: newQueryDocumentIterator(t.ctx, q.query(), t.id),
|
||||
}
|
||||
}
|
||||
|
||||
// DocumentRefs returns references to all the documents in the collection, including
|
||||
// missing documents. A missing document is a document that does not exist but has
|
||||
// sub-documents.
|
||||
func (t *Transaction) DocumentRefs(cr *CollectionRef) *DocumentRefIterator {
|
||||
if len(t.writes) > 0 {
|
||||
t.readAfterWrite = true
|
||||
return &DocumentRefIterator{err: errReadAfterWrite}
|
||||
}
|
||||
return newDocumentRefIterator(t.ctx, cr, t.id)
|
||||
}
|
||||
|
||||
// Create adds a Create operation to the Transaction.
|
||||
// See DocumentRef.Create for details.
|
||||
func (t *Transaction) Create(dr *DocumentRef, data interface{}) error {
|
||||
return t.addWrites(dr.newCreateWrites(data))
|
||||
}
|
||||
|
||||
// Set adds a Set operation to the Transaction.
|
||||
// See DocumentRef.Set for details.
|
||||
func (t *Transaction) Set(dr *DocumentRef, data interface{}, opts ...SetOption) error {
|
||||
return t.addWrites(dr.newSetWrites(data, opts))
|
||||
}
|
||||
|
||||
// Delete adds a Delete operation to the Transaction.
|
||||
// See DocumentRef.Delete for details.
|
||||
func (t *Transaction) Delete(dr *DocumentRef, opts ...Precondition) error {
|
||||
return t.addWrites(dr.newDeleteWrites(opts))
|
||||
}
|
||||
|
||||
// Update adds a new Update operation to the Transaction.
|
||||
// See DocumentRef.Update for details.
|
||||
func (t *Transaction) Update(dr *DocumentRef, data []Update, opts ...Precondition) error {
|
||||
return t.addWrites(dr.newUpdatePathWrites(data, opts))
|
||||
}
|
||||
|
||||
func (t *Transaction) addWrites(ws []*pb.Write, err error) error {
|
||||
if t.readOnly {
|
||||
return errWriteReadOnly
|
||||
}
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
t.writes = append(t.writes, ws...)
|
||||
return nil
|
||||
}
|
||||
-519
@@ -1,519 +0,0 @@
|
||||
// Copyright 2018 Google LLC
|
||||
//
|
||||
// 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 firestore
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"log"
|
||||
"sort"
|
||||
"time"
|
||||
|
||||
"cloud.google.com/go/internal/btree"
|
||||
"github.com/golang/protobuf/ptypes"
|
||||
gax "github.com/googleapis/gax-go/v2"
|
||||
pb "google.golang.org/genproto/googleapis/firestore/v1"
|
||||
"google.golang.org/grpc/codes"
|
||||
"google.golang.org/grpc/status"
|
||||
)
|
||||
|
||||
// LogWatchStreams controls whether watch stream status changes are logged.
|
||||
// This feature is EXPERIMENTAL and may disappear at any time.
|
||||
var LogWatchStreams = false
|
||||
|
||||
// DocumentChangeKind describes the kind of change to a document between
|
||||
// query snapshots.
|
||||
type DocumentChangeKind int
|
||||
|
||||
const (
|
||||
// DocumentAdded indicates that the document was added for the first time.
|
||||
DocumentAdded DocumentChangeKind = iota
|
||||
// DocumentRemoved indicates that the document was removed.
|
||||
DocumentRemoved
|
||||
// DocumentModified indicates that the document was modified.
|
||||
DocumentModified
|
||||
)
|
||||
|
||||
// A DocumentChange describes the change to a document from one query snapshot to the next.
|
||||
type DocumentChange struct {
|
||||
Kind DocumentChangeKind
|
||||
Doc *DocumentSnapshot
|
||||
// The zero-based index of the document in the sequence of query results prior to this change,
|
||||
// or -1 if the document was not present.
|
||||
OldIndex int
|
||||
// The zero-based index of the document in the sequence of query results after this change,
|
||||
// or -1 if the document is no longer present.
|
||||
NewIndex int
|
||||
}
|
||||
|
||||
// Implementation of realtime updates (a.k.a. watch).
|
||||
// This code is closely based on the Node.js implementation,
|
||||
// https://github.com/googleapis/nodejs-firestore/blob/master/src/watch.js.
|
||||
|
||||
// The sole target ID for all streams from this client.
|
||||
// Variable for testing.
|
||||
var watchTargetID int32 = 'g' + 'o'
|
||||
|
||||
var defaultBackoff = gax.Backoff{
|
||||
// Values from https://github.com/googleapis/nodejs-firestore/blob/master/src/backoff.js.
|
||||
Initial: 1 * time.Second,
|
||||
Max: 60 * time.Second,
|
||||
Multiplier: 1.5,
|
||||
}
|
||||
|
||||
// not goroutine-safe
|
||||
type watchStream struct {
|
||||
ctx context.Context
|
||||
c *Client
|
||||
lc pb.Firestore_ListenClient // the gRPC stream
|
||||
target *pb.Target // document or query being watched
|
||||
backoff gax.Backoff // for stream retries
|
||||
err error // sticky permanent error
|
||||
readTime time.Time // time of most recent snapshot
|
||||
current bool // saw CURRENT, but not RESET; precondition for a snapshot
|
||||
hasReturned bool // have we returned a snapshot yet?
|
||||
compare func(a, b *DocumentSnapshot) (int, error) // compare documents according to query
|
||||
|
||||
// An ordered tree where DocumentSnapshots are the keys.
|
||||
docTree *btree.BTree
|
||||
// Map of document name to DocumentSnapshot for the last returned snapshot.
|
||||
docMap map[string]*DocumentSnapshot
|
||||
// Map of document name to DocumentSnapshot for accumulated changes for the current snapshot.
|
||||
// A nil value means the document was removed.
|
||||
changeMap map[string]*DocumentSnapshot
|
||||
}
|
||||
|
||||
func newWatchStreamForDocument(ctx context.Context, dr *DocumentRef) *watchStream {
|
||||
// A single document is always equal to itself.
|
||||
compare := func(_, _ *DocumentSnapshot) (int, error) { return 0, nil }
|
||||
return newWatchStream(ctx, dr.Parent.c, compare, &pb.Target{
|
||||
TargetType: &pb.Target_Documents{
|
||||
Documents: &pb.Target_DocumentsTarget{Documents: []string{dr.Path}},
|
||||
},
|
||||
TargetId: watchTargetID,
|
||||
})
|
||||
}
|
||||
|
||||
func newWatchStreamForQuery(ctx context.Context, q Query) (*watchStream, error) {
|
||||
qp, err := q.toProto()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
target := &pb.Target{
|
||||
TargetType: &pb.Target_Query{
|
||||
Query: &pb.Target_QueryTarget{
|
||||
Parent: q.parentPath,
|
||||
QueryType: &pb.Target_QueryTarget_StructuredQuery{qp},
|
||||
},
|
||||
},
|
||||
TargetId: watchTargetID,
|
||||
}
|
||||
return newWatchStream(ctx, q.c, q.compareFunc(), target), nil
|
||||
}
|
||||
|
||||
const btreeDegree = 4
|
||||
|
||||
func newWatchStream(ctx context.Context, c *Client, compare func(_, _ *DocumentSnapshot) (int, error), target *pb.Target) *watchStream {
|
||||
w := &watchStream{
|
||||
ctx: ctx,
|
||||
c: c,
|
||||
compare: compare,
|
||||
target: target,
|
||||
backoff: defaultBackoff,
|
||||
docMap: map[string]*DocumentSnapshot{},
|
||||
changeMap: map[string]*DocumentSnapshot{},
|
||||
}
|
||||
w.docTree = btree.New(btreeDegree, func(a, b interface{}) bool {
|
||||
return w.less(a.(*DocumentSnapshot), b.(*DocumentSnapshot))
|
||||
})
|
||||
return w
|
||||
}
|
||||
|
||||
func (s *watchStream) less(a, b *DocumentSnapshot) bool {
|
||||
c, err := s.compare(a, b)
|
||||
if err != nil {
|
||||
s.err = err
|
||||
return false
|
||||
}
|
||||
return c < 0
|
||||
}
|
||||
|
||||
// Once nextSnapshot returns an error, it will always return the same error.
|
||||
func (s *watchStream) nextSnapshot() (*btree.BTree, []DocumentChange, time.Time, error) {
|
||||
if s.err != nil {
|
||||
return nil, nil, time.Time{}, s.err
|
||||
}
|
||||
var changes []DocumentChange
|
||||
for {
|
||||
// Process messages until we are in a consistent state.
|
||||
for !s.handleNextMessage() {
|
||||
}
|
||||
if s.err != nil {
|
||||
_ = s.close() // ignore error
|
||||
return nil, nil, time.Time{}, s.err
|
||||
}
|
||||
var newDocTree *btree.BTree
|
||||
newDocTree, changes = s.computeSnapshot(s.docTree, s.docMap, s.changeMap, s.readTime)
|
||||
if s.err != nil {
|
||||
return nil, nil, time.Time{}, s.err
|
||||
}
|
||||
// Only return a snapshot if something has changed, or this is the first snapshot.
|
||||
if !s.hasReturned || newDocTree != s.docTree {
|
||||
s.docTree = newDocTree
|
||||
break
|
||||
}
|
||||
}
|
||||
s.changeMap = map[string]*DocumentSnapshot{}
|
||||
s.hasReturned = true
|
||||
return s.docTree, changes, s.readTime, nil
|
||||
}
|
||||
|
||||
// Read a message from the stream and handle it. Return true when
|
||||
// we're in a consistent state, or there is a permanent error.
|
||||
func (s *watchStream) handleNextMessage() bool {
|
||||
res, err := s.recv()
|
||||
if err != nil {
|
||||
s.err = err
|
||||
// Errors returned by recv are permanent.
|
||||
return true
|
||||
}
|
||||
switch r := res.ResponseType.(type) {
|
||||
case *pb.ListenResponse_TargetChange:
|
||||
return s.handleTargetChange(r.TargetChange)
|
||||
|
||||
case *pb.ListenResponse_DocumentChange:
|
||||
name := r.DocumentChange.Document.Name
|
||||
s.logf("DocumentChange %q", name)
|
||||
if hasWatchTargetID(r.DocumentChange.TargetIds) { // document changed
|
||||
ref, err := pathToDoc(name, s.c)
|
||||
if err == nil {
|
||||
s.changeMap[name], err = newDocumentSnapshot(ref, r.DocumentChange.Document, s.c, nil)
|
||||
}
|
||||
if err != nil {
|
||||
s.err = err
|
||||
return true
|
||||
}
|
||||
} else if hasWatchTargetID(r.DocumentChange.RemovedTargetIds) { // document removed
|
||||
s.changeMap[name] = nil
|
||||
}
|
||||
|
||||
case *pb.ListenResponse_DocumentDelete:
|
||||
s.logf("Delete %q", r.DocumentDelete.Document)
|
||||
s.changeMap[r.DocumentDelete.Document] = nil
|
||||
|
||||
case *pb.ListenResponse_DocumentRemove:
|
||||
s.logf("Remove %q", r.DocumentRemove.Document)
|
||||
s.changeMap[r.DocumentRemove.Document] = nil
|
||||
|
||||
case *pb.ListenResponse_Filter:
|
||||
s.logf("Filter %d", r.Filter.Count)
|
||||
if int(r.Filter.Count) != s.currentSize() {
|
||||
s.resetDocs() // Remove all the current results.
|
||||
// The filter didn't match; close the stream so it will be re-opened on the next
|
||||
// call to nextSnapshot.
|
||||
_ = s.close() // ignore error
|
||||
s.lc = nil
|
||||
}
|
||||
|
||||
default:
|
||||
s.err = fmt.Errorf("unknown response type %T", r)
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// Return true iff in a consistent state, or there is a permanent error.
|
||||
func (s *watchStream) handleTargetChange(tc *pb.TargetChange) bool {
|
||||
switch tc.TargetChangeType {
|
||||
case pb.TargetChange_NO_CHANGE:
|
||||
s.logf("TargetNoChange %d %v", len(tc.TargetIds), tc.ReadTime)
|
||||
if len(tc.TargetIds) == 0 && tc.ReadTime != nil && s.current {
|
||||
// Everything is up-to-date, so we are ready to return a snapshot.
|
||||
rt, err := ptypes.Timestamp(tc.ReadTime)
|
||||
if err != nil {
|
||||
s.err = err
|
||||
return true
|
||||
}
|
||||
s.readTime = rt
|
||||
s.target.ResumeType = &pb.Target_ResumeToken{tc.ResumeToken}
|
||||
return true
|
||||
}
|
||||
|
||||
case pb.TargetChange_ADD:
|
||||
s.logf("TargetAdd")
|
||||
if tc.TargetIds[0] != watchTargetID {
|
||||
s.err = errors.New("unexpected target ID sent by server")
|
||||
return true
|
||||
}
|
||||
|
||||
case pb.TargetChange_REMOVE:
|
||||
s.logf("TargetRemove")
|
||||
// We should never see a remove.
|
||||
if tc.Cause != nil {
|
||||
s.err = status.Error(codes.Code(tc.Cause.Code), tc.Cause.Message)
|
||||
} else {
|
||||
s.err = status.Error(codes.Internal, "firestore: client saw REMOVE")
|
||||
}
|
||||
return true
|
||||
|
||||
// The targets reflect all changes committed before the targets were added
|
||||
// to the stream.
|
||||
case pb.TargetChange_CURRENT:
|
||||
s.logf("TargetCurrent")
|
||||
s.current = true
|
||||
|
||||
// The targets have been reset, and a new initial state for the targets will be
|
||||
// returned in subsequent changes. Whatever changes have happened so far no
|
||||
// longer matter.
|
||||
case pb.TargetChange_RESET:
|
||||
s.logf("TargetReset")
|
||||
s.resetDocs()
|
||||
|
||||
default:
|
||||
s.err = fmt.Errorf("firestore: unknown TargetChange type %s", tc.TargetChangeType)
|
||||
return true
|
||||
}
|
||||
// If we see a resume token and our watch ID is affected, we assume the stream
|
||||
// is now healthy, so we reset our backoff time to the minimum.
|
||||
if tc.ResumeToken != nil && (len(tc.TargetIds) == 0 || hasWatchTargetID(tc.TargetIds)) {
|
||||
s.backoff = defaultBackoff
|
||||
}
|
||||
return false // not in a consistent state, keep receiving
|
||||
}
|
||||
|
||||
func (s *watchStream) resetDocs() {
|
||||
s.target.ResumeType = nil // clear resume token
|
||||
s.current = false
|
||||
s.changeMap = map[string]*DocumentSnapshot{}
|
||||
// Mark each document as deleted. If documents are not deleted, they
|
||||
// will be send again by the server.
|
||||
it := s.docTree.BeforeIndex(0)
|
||||
for it.Next() {
|
||||
s.changeMap[it.Key.(*DocumentSnapshot).Ref.Path] = nil
|
||||
}
|
||||
}
|
||||
|
||||
func (s *watchStream) currentSize() int {
|
||||
_, adds, deletes := extractChanges(s.docMap, s.changeMap)
|
||||
return len(s.docMap) + len(adds) - len(deletes)
|
||||
}
|
||||
|
||||
// Return the changes that have occurred since the last snapshot.
|
||||
func extractChanges(docMap, changeMap map[string]*DocumentSnapshot) (updates, adds []*DocumentSnapshot, deletes []string) {
|
||||
for name, doc := range changeMap {
|
||||
switch {
|
||||
case doc == nil:
|
||||
if _, ok := docMap[name]; ok {
|
||||
deletes = append(deletes, name)
|
||||
}
|
||||
case docMap[name] != nil:
|
||||
updates = append(updates, doc)
|
||||
default:
|
||||
adds = append(adds, doc)
|
||||
}
|
||||
}
|
||||
return updates, adds, deletes
|
||||
}
|
||||
|
||||
// For development only.
|
||||
// TODO(jba): remove.
|
||||
func assert(b bool) {
|
||||
if !b {
|
||||
panic("assertion failed")
|
||||
}
|
||||
}
|
||||
|
||||
// Applies the mutations in changeMap to both the document tree and the
|
||||
// document lookup map. Modifies docMap in place and returns a new docTree.
|
||||
// If there were no changes, returns docTree unmodified.
|
||||
func (s *watchStream) computeSnapshot(docTree *btree.BTree, docMap, changeMap map[string]*DocumentSnapshot, readTime time.Time) (*btree.BTree, []DocumentChange) {
|
||||
var changes []DocumentChange
|
||||
updatedTree := docTree
|
||||
assert(docTree.Len() == len(docMap))
|
||||
updates, adds, deletes := extractChanges(docMap, changeMap)
|
||||
if len(adds) > 0 || len(deletes) > 0 {
|
||||
updatedTree = docTree.Clone()
|
||||
}
|
||||
// Process the sorted changes in the order that is expected by our clients
|
||||
// (removals, additions, and then modifications). We also need to sort the
|
||||
// individual changes to assure that oldIndex/newIndex keep incrementing.
|
||||
deldocs := make([]*DocumentSnapshot, len(deletes))
|
||||
for i, d := range deletes {
|
||||
deldocs[i] = docMap[d]
|
||||
}
|
||||
sort.Sort(byLess{deldocs, s.less})
|
||||
for _, oldDoc := range deldocs {
|
||||
assert(oldDoc != nil)
|
||||
delete(docMap, oldDoc.Ref.Path)
|
||||
_, oldi := updatedTree.GetWithIndex(oldDoc)
|
||||
// TODO(jba): have btree.Delete return old index
|
||||
_, found := updatedTree.Delete(oldDoc)
|
||||
assert(found)
|
||||
changes = append(changes, DocumentChange{
|
||||
Kind: DocumentRemoved,
|
||||
Doc: oldDoc,
|
||||
OldIndex: oldi,
|
||||
NewIndex: -1,
|
||||
})
|
||||
}
|
||||
sort.Sort(byLess{adds, s.less})
|
||||
for _, newDoc := range adds {
|
||||
name := newDoc.Ref.Path
|
||||
assert(docMap[name] == nil)
|
||||
newDoc.ReadTime = readTime
|
||||
docMap[name] = newDoc
|
||||
updatedTree.Set(newDoc, nil)
|
||||
// TODO(jba): change btree so Set returns index as second value.
|
||||
_, newi := updatedTree.GetWithIndex(newDoc)
|
||||
changes = append(changes, DocumentChange{
|
||||
Kind: DocumentAdded,
|
||||
Doc: newDoc,
|
||||
OldIndex: -1,
|
||||
NewIndex: newi,
|
||||
})
|
||||
}
|
||||
sort.Sort(byLess{updates, s.less})
|
||||
for _, newDoc := range updates {
|
||||
name := newDoc.Ref.Path
|
||||
oldDoc := docMap[name]
|
||||
assert(oldDoc != nil)
|
||||
if newDoc.UpdateTime.Equal(oldDoc.UpdateTime) {
|
||||
continue
|
||||
}
|
||||
if updatedTree == docTree {
|
||||
updatedTree = docTree.Clone()
|
||||
}
|
||||
newDoc.ReadTime = readTime
|
||||
docMap[name] = newDoc
|
||||
_, oldi := updatedTree.GetWithIndex(oldDoc)
|
||||
updatedTree.Delete(oldDoc)
|
||||
updatedTree.Set(newDoc, nil)
|
||||
_, newi := updatedTree.GetWithIndex(newDoc)
|
||||
changes = append(changes, DocumentChange{
|
||||
Kind: DocumentModified,
|
||||
Doc: newDoc,
|
||||
OldIndex: oldi,
|
||||
NewIndex: newi,
|
||||
})
|
||||
}
|
||||
assert(updatedTree.Len() == len(docMap))
|
||||
return updatedTree, changes
|
||||
}
|
||||
|
||||
type byLess struct {
|
||||
s []*DocumentSnapshot
|
||||
less func(a, b *DocumentSnapshot) bool
|
||||
}
|
||||
|
||||
func (b byLess) Len() int { return len(b.s) }
|
||||
func (b byLess) Swap(i, j int) { b.s[i], b.s[j] = b.s[j], b.s[i] }
|
||||
func (b byLess) Less(i, j int) bool { return b.less(b.s[i], b.s[j]) }
|
||||
|
||||
func hasWatchTargetID(ids []int32) bool {
|
||||
for _, id := range ids {
|
||||
if id == watchTargetID {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func (s *watchStream) logf(format string, args ...interface{}) {
|
||||
if LogWatchStreams {
|
||||
log.Printf(format, args...)
|
||||
}
|
||||
}
|
||||
|
||||
// Close the stream. From this point on, calls to nextSnapshot will return
|
||||
// io.EOF, or the error from CloseSend.
|
||||
func (s *watchStream) stop() {
|
||||
err := s.close()
|
||||
if s.err != nil { // don't change existing error
|
||||
return
|
||||
}
|
||||
if err != nil {
|
||||
s.err = err
|
||||
}
|
||||
s.err = io.EOF // normal shutdown
|
||||
}
|
||||
|
||||
func (s *watchStream) close() error {
|
||||
if s.lc == nil {
|
||||
return nil
|
||||
}
|
||||
return s.lc.CloseSend()
|
||||
}
|
||||
|
||||
// recv receives the next message from the stream. It also handles opening the stream
|
||||
// initially, and reopening it on non-permanent errors.
|
||||
// recv doesn't have to be goroutine-safe.
|
||||
func (s *watchStream) recv() (*pb.ListenResponse, error) {
|
||||
var err error
|
||||
for {
|
||||
if s.lc == nil {
|
||||
s.lc, err = s.open()
|
||||
if err != nil {
|
||||
// Do not retry if open fails.
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
res, err := s.lc.Recv()
|
||||
if err == nil || isPermanentWatchError(err) {
|
||||
return res, err
|
||||
}
|
||||
// Non-permanent error. Sleep and retry.
|
||||
s.changeMap = map[string]*DocumentSnapshot{} // clear changeMap
|
||||
dur := s.backoff.Pause()
|
||||
// If we're out of quota, wait a long time before retrying.
|
||||
if status.Code(err) == codes.ResourceExhausted {
|
||||
dur = s.backoff.Max
|
||||
}
|
||||
if err := sleep(s.ctx, dur); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
s.lc = nil
|
||||
}
|
||||
}
|
||||
|
||||
func (s *watchStream) open() (pb.Firestore_ListenClient, error) {
|
||||
dbPath := s.c.path()
|
||||
lc, err := s.c.c.Listen(withResourceHeader(s.ctx, dbPath))
|
||||
if err == nil {
|
||||
err = lc.Send(&pb.ListenRequest{
|
||||
Database: dbPath,
|
||||
TargetChange: &pb.ListenRequest_AddTarget{AddTarget: s.target},
|
||||
})
|
||||
}
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return lc, nil
|
||||
}
|
||||
|
||||
func isPermanentWatchError(err error) bool {
|
||||
if err == io.EOF {
|
||||
// Retry on normal end-of-stream.
|
||||
return false
|
||||
}
|
||||
switch status.Code(err) {
|
||||
case codes.Unknown, codes.DeadlineExceeded, codes.ResourceExhausted,
|
||||
codes.Internal, codes.Unavailable, codes.Unauthenticated:
|
||||
return false
|
||||
default:
|
||||
return true
|
||||
}
|
||||
}
|
||||
-85
@@ -1,85 +0,0 @@
|
||||
// Copyright 2017 Google LLC
|
||||
//
|
||||
// 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 firestore
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
|
||||
"cloud.google.com/go/internal/trace"
|
||||
pb "google.golang.org/genproto/googleapis/firestore/v1"
|
||||
)
|
||||
|
||||
// A WriteBatch holds multiple database updates. Build a batch with the Create, Set,
|
||||
// Update and Delete methods, then run it with the Commit method. Errors in Create,
|
||||
// Set, Update or Delete are recorded instead of being returned immediately. The
|
||||
// first such error is returned by Commit.
|
||||
type WriteBatch struct {
|
||||
c *Client
|
||||
err error
|
||||
writes []*pb.Write
|
||||
}
|
||||
|
||||
func (b *WriteBatch) add(ws []*pb.Write, err error) *WriteBatch {
|
||||
if b.err != nil {
|
||||
return b
|
||||
}
|
||||
if err != nil {
|
||||
b.err = err
|
||||
return b
|
||||
}
|
||||
b.writes = append(b.writes, ws...)
|
||||
return b
|
||||
}
|
||||
|
||||
// Create adds a Create operation to the batch.
|
||||
// See DocumentRef.Create for details.
|
||||
func (b *WriteBatch) Create(dr *DocumentRef, data interface{}) *WriteBatch {
|
||||
return b.add(dr.newCreateWrites(data))
|
||||
}
|
||||
|
||||
// Set adds a Set operation to the batch.
|
||||
// See DocumentRef.Set for details.
|
||||
func (b *WriteBatch) Set(dr *DocumentRef, data interface{}, opts ...SetOption) *WriteBatch {
|
||||
return b.add(dr.newSetWrites(data, opts))
|
||||
}
|
||||
|
||||
// Delete adds a Delete operation to the batch.
|
||||
// See DocumentRef.Delete for details.
|
||||
func (b *WriteBatch) Delete(dr *DocumentRef, opts ...Precondition) *WriteBatch {
|
||||
return b.add(dr.newDeleteWrites(opts))
|
||||
}
|
||||
|
||||
// Update adds an Update operation to the batch.
|
||||
// See DocumentRef.Update for details.
|
||||
func (b *WriteBatch) Update(dr *DocumentRef, data []Update, opts ...Precondition) *WriteBatch {
|
||||
return b.add(dr.newUpdatePathWrites(data, opts))
|
||||
}
|
||||
|
||||
// Commit applies all the writes in the batch to the database atomically. Commit
|
||||
// returns an error if there are no writes in the batch, if any errors occurred in
|
||||
// constructing the writes, or if the Commmit operation fails.
|
||||
func (b *WriteBatch) Commit(ctx context.Context) (_ []*WriteResult, err error) {
|
||||
ctx = trace.StartSpan(ctx, "cloud.google.com/go/firestore.WriteBatch.Commit")
|
||||
defer func() { trace.EndSpan(ctx, err) }()
|
||||
|
||||
if b.err != nil {
|
||||
return nil, b.err
|
||||
}
|
||||
if len(b.writes) == 0 {
|
||||
return nil, errors.New("firestore: cannot commit empty WriteBatch")
|
||||
}
|
||||
return b.c.commit(ctx, b.writes)
|
||||
}
|
||||
-105
@@ -1,105 +0,0 @@
|
||||
// Copyright 2020 Google LLC
|
||||
//
|
||||
// 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
|
||||
//
|
||||
// https://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.
|
||||
|
||||
// Code generated by protoc-gen-go_gapic. DO NOT EDIT.
|
||||
|
||||
// Package credentials is an auto-generated package for the
|
||||
// IAM Service Account Credentials API.
|
||||
//
|
||||
// Creates short-lived, limited-privilege credentials for IAM service
|
||||
// accounts.
|
||||
//
|
||||
// Use of Context
|
||||
//
|
||||
// The ctx passed to NewClient is used for authentication requests and
|
||||
// for creating the underlying connection, but is not used for subsequent calls.
|
||||
// Individual methods on the client use the ctx given to them.
|
||||
//
|
||||
// To close the open connection, use the Close() method.
|
||||
//
|
||||
// For information about setting deadlines, reusing contexts, and more
|
||||
// please visit godoc.org/cloud.google.com/go.
|
||||
package credentials // import "cloud.google.com/go/iam/credentials/apiv1"
|
||||
|
||||
import (
|
||||
"context"
|
||||
"runtime"
|
||||
"strings"
|
||||
"unicode"
|
||||
|
||||
"google.golang.org/api/option"
|
||||
"google.golang.org/grpc/metadata"
|
||||
)
|
||||
|
||||
// For more information on implementing a client constructor hook, see
|
||||
// https://github.com/googleapis/google-cloud-go/wiki/Customizing-constructors.
|
||||
type clientHookParams struct{}
|
||||
type clientHook func(context.Context, clientHookParams) ([]option.ClientOption, error)
|
||||
|
||||
const versionClient = "20200629"
|
||||
|
||||
func insertMetadata(ctx context.Context, mds ...metadata.MD) context.Context {
|
||||
out, _ := metadata.FromOutgoingContext(ctx)
|
||||
out = out.Copy()
|
||||
for _, md := range mds {
|
||||
for k, v := range md {
|
||||
out[k] = append(out[k], v...)
|
||||
}
|
||||
}
|
||||
return metadata.NewOutgoingContext(ctx, out)
|
||||
}
|
||||
|
||||
// DefaultAuthScopes reports the default set of authentication scopes to use with this package.
|
||||
func DefaultAuthScopes() []string {
|
||||
return []string{
|
||||
"https://www.googleapis.com/auth/cloud-platform",
|
||||
}
|
||||
}
|
||||
|
||||
// versionGo returns the Go runtime version. The returned string
|
||||
// has no whitespace, suitable for reporting in header.
|
||||
func versionGo() string {
|
||||
const develPrefix = "devel +"
|
||||
|
||||
s := runtime.Version()
|
||||
if strings.HasPrefix(s, develPrefix) {
|
||||
s = s[len(develPrefix):]
|
||||
if p := strings.IndexFunc(s, unicode.IsSpace); p >= 0 {
|
||||
s = s[:p]
|
||||
}
|
||||
return s
|
||||
}
|
||||
|
||||
notSemverRune := func(r rune) bool {
|
||||
return !strings.ContainsRune("0123456789.", r)
|
||||
}
|
||||
|
||||
if strings.HasPrefix(s, "go1") {
|
||||
s = s[2:]
|
||||
var prerelease string
|
||||
if p := strings.IndexFunc(s, notSemverRune); p >= 0 {
|
||||
s, prerelease = s[:p], s[p:]
|
||||
}
|
||||
if strings.HasSuffix(s, ".") {
|
||||
s += "0"
|
||||
} else if strings.Count(s, ".") < 2 {
|
||||
s += ".0"
|
||||
}
|
||||
if prerelease != "" {
|
||||
s += "-" + prerelease
|
||||
}
|
||||
return s
|
||||
}
|
||||
return "UNKNOWN"
|
||||
}
|
||||
-250
@@ -1,250 +0,0 @@
|
||||
// Copyright 2020 Google LLC
|
||||
//
|
||||
// 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
|
||||
//
|
||||
// https://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.
|
||||
|
||||
// Code generated by protoc-gen-go_gapic. DO NOT EDIT.
|
||||
|
||||
package credentials
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"math"
|
||||
"net/url"
|
||||
"time"
|
||||
|
||||
gax "github.com/googleapis/gax-go/v2"
|
||||
"google.golang.org/api/option"
|
||||
gtransport "google.golang.org/api/transport/grpc"
|
||||
credentialspb "google.golang.org/genproto/googleapis/iam/credentials/v1"
|
||||
"google.golang.org/grpc"
|
||||
"google.golang.org/grpc/codes"
|
||||
"google.golang.org/grpc/metadata"
|
||||
)
|
||||
|
||||
var newIamCredentialsClientHook clientHook
|
||||
|
||||
// IamCredentialsCallOptions contains the retry settings for each method of IamCredentialsClient.
|
||||
type IamCredentialsCallOptions struct {
|
||||
GenerateAccessToken []gax.CallOption
|
||||
GenerateIdToken []gax.CallOption
|
||||
SignBlob []gax.CallOption
|
||||
SignJwt []gax.CallOption
|
||||
}
|
||||
|
||||
func defaultIamCredentialsClientOptions() []option.ClientOption {
|
||||
return []option.ClientOption{
|
||||
option.WithEndpoint("iamcredentials.googleapis.com:443"),
|
||||
option.WithGRPCDialOption(grpc.WithDisableServiceConfig()),
|
||||
option.WithScopes(DefaultAuthScopes()...),
|
||||
option.WithGRPCDialOption(grpc.WithDefaultCallOptions(
|
||||
grpc.MaxCallRecvMsgSize(math.MaxInt32))),
|
||||
}
|
||||
}
|
||||
|
||||
func defaultIamCredentialsCallOptions() *IamCredentialsCallOptions {
|
||||
return &IamCredentialsCallOptions{
|
||||
GenerateAccessToken: []gax.CallOption{
|
||||
gax.WithRetry(func() gax.Retryer {
|
||||
return gax.OnCodes([]codes.Code{
|
||||
codes.Unavailable,
|
||||
codes.DeadlineExceeded,
|
||||
}, gax.Backoff{
|
||||
Initial: 100 * time.Millisecond,
|
||||
Max: 60000 * time.Millisecond,
|
||||
Multiplier: 1.30,
|
||||
})
|
||||
}),
|
||||
},
|
||||
GenerateIdToken: []gax.CallOption{
|
||||
gax.WithRetry(func() gax.Retryer {
|
||||
return gax.OnCodes([]codes.Code{
|
||||
codes.Unavailable,
|
||||
codes.DeadlineExceeded,
|
||||
}, gax.Backoff{
|
||||
Initial: 100 * time.Millisecond,
|
||||
Max: 60000 * time.Millisecond,
|
||||
Multiplier: 1.30,
|
||||
})
|
||||
}),
|
||||
},
|
||||
SignBlob: []gax.CallOption{
|
||||
gax.WithRetry(func() gax.Retryer {
|
||||
return gax.OnCodes([]codes.Code{
|
||||
codes.Unavailable,
|
||||
codes.DeadlineExceeded,
|
||||
}, gax.Backoff{
|
||||
Initial: 100 * time.Millisecond,
|
||||
Max: 60000 * time.Millisecond,
|
||||
Multiplier: 1.30,
|
||||
})
|
||||
}),
|
||||
},
|
||||
SignJwt: []gax.CallOption{
|
||||
gax.WithRetry(func() gax.Retryer {
|
||||
return gax.OnCodes([]codes.Code{
|
||||
codes.Unavailable,
|
||||
codes.DeadlineExceeded,
|
||||
}, gax.Backoff{
|
||||
Initial: 100 * time.Millisecond,
|
||||
Max: 60000 * time.Millisecond,
|
||||
Multiplier: 1.30,
|
||||
})
|
||||
}),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// IamCredentialsClient is a client for interacting with IAM Service Account Credentials API.
|
||||
//
|
||||
// Methods, except Close, may be called concurrently. However, fields must not be modified concurrently with method calls.
|
||||
type IamCredentialsClient struct {
|
||||
// Connection pool of gRPC connections to the service.
|
||||
connPool gtransport.ConnPool
|
||||
|
||||
// The gRPC API client.
|
||||
iamCredentialsClient credentialspb.IAMCredentialsClient
|
||||
|
||||
// The call options for this service.
|
||||
CallOptions *IamCredentialsCallOptions
|
||||
|
||||
// The x-goog-* metadata to be sent with each request.
|
||||
xGoogMetadata metadata.MD
|
||||
}
|
||||
|
||||
// NewIamCredentialsClient creates a new iam credentials client.
|
||||
//
|
||||
// A service account is a special type of Google account that belongs to your
|
||||
// application or a virtual machine (VM), instead of to an individual end user.
|
||||
// Your application assumes the identity of the service account to call Google
|
||||
// APIs, so that the users aren’t directly involved.
|
||||
//
|
||||
// Service account credentials are used to temporarily assume the identity
|
||||
// of the service account. Supported credential types include OAuth 2.0 access
|
||||
// tokens, OpenID Connect ID tokens, self-signed JSON Web Tokens (JWTs), and
|
||||
// more.
|
||||
func NewIamCredentialsClient(ctx context.Context, opts ...option.ClientOption) (*IamCredentialsClient, error) {
|
||||
clientOpts := defaultIamCredentialsClientOptions()
|
||||
|
||||
if newIamCredentialsClientHook != nil {
|
||||
hookOpts, err := newIamCredentialsClientHook(ctx, clientHookParams{})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
clientOpts = append(clientOpts, hookOpts...)
|
||||
}
|
||||
|
||||
connPool, err := gtransport.DialPool(ctx, append(clientOpts, opts...)...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
c := &IamCredentialsClient{
|
||||
connPool: connPool,
|
||||
CallOptions: defaultIamCredentialsCallOptions(),
|
||||
|
||||
iamCredentialsClient: credentialspb.NewIAMCredentialsClient(connPool),
|
||||
}
|
||||
c.setGoogleClientInfo()
|
||||
|
||||
return c, nil
|
||||
}
|
||||
|
||||
// Connection returns a connection to the API service.
|
||||
//
|
||||
// Deprecated.
|
||||
func (c *IamCredentialsClient) Connection() *grpc.ClientConn {
|
||||
return c.connPool.Conn()
|
||||
}
|
||||
|
||||
// Close closes the connection to the API service. The user should invoke this when
|
||||
// the client is no longer required.
|
||||
func (c *IamCredentialsClient) Close() error {
|
||||
return c.connPool.Close()
|
||||
}
|
||||
|
||||
// setGoogleClientInfo sets the name and version of the application in
|
||||
// the `x-goog-api-client` header passed on each request. Intended for
|
||||
// use by Google-written clients.
|
||||
func (c *IamCredentialsClient) setGoogleClientInfo(keyval ...string) {
|
||||
kv := append([]string{"gl-go", versionGo()}, keyval...)
|
||||
kv = append(kv, "gapic", versionClient, "gax", gax.Version, "grpc", grpc.Version)
|
||||
c.xGoogMetadata = metadata.Pairs("x-goog-api-client", gax.XGoogHeader(kv...))
|
||||
}
|
||||
|
||||
// GenerateAccessToken generates an OAuth 2.0 access token for a service account.
|
||||
func (c *IamCredentialsClient) GenerateAccessToken(ctx context.Context, req *credentialspb.GenerateAccessTokenRequest, opts ...gax.CallOption) (*credentialspb.GenerateAccessTokenResponse, error) {
|
||||
md := metadata.Pairs("x-goog-request-params", fmt.Sprintf("%s=%v", "name", url.QueryEscape(req.GetName())))
|
||||
ctx = insertMetadata(ctx, c.xGoogMetadata, md)
|
||||
opts = append(c.CallOptions.GenerateAccessToken[0:len(c.CallOptions.GenerateAccessToken):len(c.CallOptions.GenerateAccessToken)], opts...)
|
||||
var resp *credentialspb.GenerateAccessTokenResponse
|
||||
err := gax.Invoke(ctx, func(ctx context.Context, settings gax.CallSettings) error {
|
||||
var err error
|
||||
resp, err = c.iamCredentialsClient.GenerateAccessToken(ctx, req, settings.GRPC...)
|
||||
return err
|
||||
}, opts...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return resp, nil
|
||||
}
|
||||
|
||||
// GenerateIdToken generates an OpenID Connect ID token for a service account.
|
||||
func (c *IamCredentialsClient) GenerateIdToken(ctx context.Context, req *credentialspb.GenerateIdTokenRequest, opts ...gax.CallOption) (*credentialspb.GenerateIdTokenResponse, error) {
|
||||
md := metadata.Pairs("x-goog-request-params", fmt.Sprintf("%s=%v", "name", url.QueryEscape(req.GetName())))
|
||||
ctx = insertMetadata(ctx, c.xGoogMetadata, md)
|
||||
opts = append(c.CallOptions.GenerateIdToken[0:len(c.CallOptions.GenerateIdToken):len(c.CallOptions.GenerateIdToken)], opts...)
|
||||
var resp *credentialspb.GenerateIdTokenResponse
|
||||
err := gax.Invoke(ctx, func(ctx context.Context, settings gax.CallSettings) error {
|
||||
var err error
|
||||
resp, err = c.iamCredentialsClient.GenerateIdToken(ctx, req, settings.GRPC...)
|
||||
return err
|
||||
}, opts...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return resp, nil
|
||||
}
|
||||
|
||||
// SignBlob signs a blob using a service account’s system-managed private key.
|
||||
func (c *IamCredentialsClient) SignBlob(ctx context.Context, req *credentialspb.SignBlobRequest, opts ...gax.CallOption) (*credentialspb.SignBlobResponse, error) {
|
||||
md := metadata.Pairs("x-goog-request-params", fmt.Sprintf("%s=%v", "name", url.QueryEscape(req.GetName())))
|
||||
ctx = insertMetadata(ctx, c.xGoogMetadata, md)
|
||||
opts = append(c.CallOptions.SignBlob[0:len(c.CallOptions.SignBlob):len(c.CallOptions.SignBlob)], opts...)
|
||||
var resp *credentialspb.SignBlobResponse
|
||||
err := gax.Invoke(ctx, func(ctx context.Context, settings gax.CallSettings) error {
|
||||
var err error
|
||||
resp, err = c.iamCredentialsClient.SignBlob(ctx, req, settings.GRPC...)
|
||||
return err
|
||||
}, opts...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return resp, nil
|
||||
}
|
||||
|
||||
// SignJwt signs a JWT using a service account’s system-managed private key.
|
||||
func (c *IamCredentialsClient) SignJwt(ctx context.Context, req *credentialspb.SignJwtRequest, opts ...gax.CallOption) (*credentialspb.SignJwtResponse, error) {
|
||||
md := metadata.Pairs("x-goog-request-params", fmt.Sprintf("%s=%v", "name", url.QueryEscape(req.GetName())))
|
||||
ctx = insertMetadata(ctx, c.xGoogMetadata, md)
|
||||
opts = append(c.CallOptions.SignJwt[0:len(c.CallOptions.SignJwt):len(c.CallOptions.SignJwt)], opts...)
|
||||
var resp *credentialspb.SignJwtResponse
|
||||
err := gax.Invoke(ctx, func(ctx context.Context, settings gax.CallSettings) error {
|
||||
var err error
|
||||
resp, err = c.iamCredentialsClient.SignJwt(ctx, req, settings.GRPC...)
|
||||
return err
|
||||
}, opts...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return resp, nil
|
||||
}
|
||||
-387
@@ -1,387 +0,0 @@
|
||||
// Copyright 2016 Google LLC
|
||||
//
|
||||
// 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 iam supports the resource-specific operations of Google Cloud
|
||||
// IAM (Identity and Access Management) for the Google Cloud Libraries.
|
||||
// See https://cloud.google.com/iam for more about IAM.
|
||||
//
|
||||
// Users of the Google Cloud Libraries will typically not use this package
|
||||
// directly. Instead they will begin with some resource that supports IAM, like
|
||||
// a pubsub topic, and call its IAM method to get a Handle for that resource.
|
||||
package iam
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
gax "github.com/googleapis/gax-go/v2"
|
||||
pb "google.golang.org/genproto/googleapis/iam/v1"
|
||||
"google.golang.org/grpc"
|
||||
"google.golang.org/grpc/codes"
|
||||
"google.golang.org/grpc/metadata"
|
||||
)
|
||||
|
||||
// client abstracts the IAMPolicy API to allow multiple implementations.
|
||||
type client interface {
|
||||
Get(ctx context.Context, resource string) (*pb.Policy, error)
|
||||
Set(ctx context.Context, resource string, p *pb.Policy) error
|
||||
Test(ctx context.Context, resource string, perms []string) ([]string, error)
|
||||
GetWithVersion(ctx context.Context, resource string, requestedPolicyVersion int32) (*pb.Policy, error)
|
||||
}
|
||||
|
||||
// grpcClient implements client for the standard gRPC-based IAMPolicy service.
|
||||
type grpcClient struct {
|
||||
c pb.IAMPolicyClient
|
||||
}
|
||||
|
||||
var withRetry = gax.WithRetry(func() gax.Retryer {
|
||||
return gax.OnCodes([]codes.Code{
|
||||
codes.DeadlineExceeded,
|
||||
codes.Unavailable,
|
||||
}, gax.Backoff{
|
||||
Initial: 100 * time.Millisecond,
|
||||
Max: 60 * time.Second,
|
||||
Multiplier: 1.3,
|
||||
})
|
||||
})
|
||||
|
||||
func (g *grpcClient) Get(ctx context.Context, resource string) (*pb.Policy, error) {
|
||||
return g.GetWithVersion(ctx, resource, 1)
|
||||
}
|
||||
|
||||
func (g *grpcClient) GetWithVersion(ctx context.Context, resource string, requestedPolicyVersion int32) (*pb.Policy, error) {
|
||||
var proto *pb.Policy
|
||||
md := metadata.Pairs("x-goog-request-params", fmt.Sprintf("%s=%v", "resource", resource))
|
||||
ctx = insertMetadata(ctx, md)
|
||||
|
||||
err := gax.Invoke(ctx, func(ctx context.Context, _ gax.CallSettings) error {
|
||||
var err error
|
||||
proto, err = g.c.GetIamPolicy(ctx, &pb.GetIamPolicyRequest{
|
||||
Resource: resource,
|
||||
Options: &pb.GetPolicyOptions{
|
||||
RequestedPolicyVersion: requestedPolicyVersion,
|
||||
},
|
||||
})
|
||||
return err
|
||||
}, withRetry)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return proto, nil
|
||||
}
|
||||
|
||||
func (g *grpcClient) Set(ctx context.Context, resource string, p *pb.Policy) error {
|
||||
md := metadata.Pairs("x-goog-request-params", fmt.Sprintf("%s=%v", "resource", resource))
|
||||
ctx = insertMetadata(ctx, md)
|
||||
|
||||
return gax.Invoke(ctx, func(ctx context.Context, _ gax.CallSettings) error {
|
||||
_, err := g.c.SetIamPolicy(ctx, &pb.SetIamPolicyRequest{
|
||||
Resource: resource,
|
||||
Policy: p,
|
||||
})
|
||||
return err
|
||||
}, withRetry)
|
||||
}
|
||||
|
||||
func (g *grpcClient) Test(ctx context.Context, resource string, perms []string) ([]string, error) {
|
||||
var res *pb.TestIamPermissionsResponse
|
||||
md := metadata.Pairs("x-goog-request-params", fmt.Sprintf("%s=%v", "resource", resource))
|
||||
ctx = insertMetadata(ctx, md)
|
||||
|
||||
err := gax.Invoke(ctx, func(ctx context.Context, _ gax.CallSettings) error {
|
||||
var err error
|
||||
res, err = g.c.TestIamPermissions(ctx, &pb.TestIamPermissionsRequest{
|
||||
Resource: resource,
|
||||
Permissions: perms,
|
||||
})
|
||||
return err
|
||||
}, withRetry)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return res.Permissions, nil
|
||||
}
|
||||
|
||||
// A Handle provides IAM operations for a resource.
|
||||
type Handle struct {
|
||||
c client
|
||||
resource string
|
||||
}
|
||||
|
||||
// A Handle3 provides IAM operations for a resource. It is similar to a Handle, but provides access to newer IAM features (e.g., conditions).
|
||||
type Handle3 struct {
|
||||
c client
|
||||
resource string
|
||||
version int32
|
||||
}
|
||||
|
||||
// InternalNewHandle is for use by the Google Cloud Libraries only.
|
||||
//
|
||||
// InternalNewHandle returns a Handle for resource.
|
||||
// The conn parameter refers to a server that must support the IAMPolicy service.
|
||||
func InternalNewHandle(conn grpc.ClientConnInterface, resource string) *Handle {
|
||||
return InternalNewHandleGRPCClient(pb.NewIAMPolicyClient(conn), resource)
|
||||
}
|
||||
|
||||
// InternalNewHandleGRPCClient is for use by the Google Cloud Libraries only.
|
||||
//
|
||||
// InternalNewHandleClient returns a Handle for resource using the given
|
||||
// grpc service that implements IAM as a mixin
|
||||
func InternalNewHandleGRPCClient(c pb.IAMPolicyClient, resource string) *Handle {
|
||||
return InternalNewHandleClient(&grpcClient{c: c}, resource)
|
||||
}
|
||||
|
||||
// InternalNewHandleClient is for use by the Google Cloud Libraries only.
|
||||
//
|
||||
// InternalNewHandleClient returns a Handle for resource using the given
|
||||
// client implementation.
|
||||
func InternalNewHandleClient(c client, resource string) *Handle {
|
||||
return &Handle{
|
||||
c: c,
|
||||
resource: resource,
|
||||
}
|
||||
}
|
||||
|
||||
// V3 returns a Handle3, which is like Handle except it sets
|
||||
// requestedPolicyVersion to 3 when retrieving a policy and policy.version to 3
|
||||
// when storing a policy.
|
||||
func (h *Handle) V3() *Handle3 {
|
||||
return &Handle3{
|
||||
c: h.c,
|
||||
resource: h.resource,
|
||||
version: 3,
|
||||
}
|
||||
}
|
||||
|
||||
// Policy retrieves the IAM policy for the resource.
|
||||
func (h *Handle) Policy(ctx context.Context) (*Policy, error) {
|
||||
proto, err := h.c.Get(ctx, h.resource)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &Policy{InternalProto: proto}, nil
|
||||
}
|
||||
|
||||
// SetPolicy replaces the resource's current policy with the supplied Policy.
|
||||
//
|
||||
// If policy was created from a prior call to Get, then the modification will
|
||||
// only succeed if the policy has not changed since the Get.
|
||||
func (h *Handle) SetPolicy(ctx context.Context, policy *Policy) error {
|
||||
return h.c.Set(ctx, h.resource, policy.InternalProto)
|
||||
}
|
||||
|
||||
// TestPermissions returns the subset of permissions that the caller has on the resource.
|
||||
func (h *Handle) TestPermissions(ctx context.Context, permissions []string) ([]string, error) {
|
||||
return h.c.Test(ctx, h.resource, permissions)
|
||||
}
|
||||
|
||||
// A RoleName is a name representing a collection of permissions.
|
||||
type RoleName string
|
||||
|
||||
// Common role names.
|
||||
const (
|
||||
Owner RoleName = "roles/owner"
|
||||
Editor RoleName = "roles/editor"
|
||||
Viewer RoleName = "roles/viewer"
|
||||
)
|
||||
|
||||
const (
|
||||
// AllUsers is a special member that denotes all users, even unauthenticated ones.
|
||||
AllUsers = "allUsers"
|
||||
|
||||
// AllAuthenticatedUsers is a special member that denotes all authenticated users.
|
||||
AllAuthenticatedUsers = "allAuthenticatedUsers"
|
||||
)
|
||||
|
||||
// A Policy is a list of Bindings representing roles
|
||||
// granted to members.
|
||||
//
|
||||
// The zero Policy is a valid policy with no bindings.
|
||||
type Policy struct {
|
||||
// TODO(jba): when type aliases are available, put Policy into an internal package
|
||||
// and provide an exported alias here.
|
||||
|
||||
// This field is exported for use by the Google Cloud Libraries only.
|
||||
// It may become unexported in a future release.
|
||||
InternalProto *pb.Policy
|
||||
}
|
||||
|
||||
// Members returns the list of members with the supplied role.
|
||||
// The return value should not be modified. Use Add and Remove
|
||||
// to modify the members of a role.
|
||||
func (p *Policy) Members(r RoleName) []string {
|
||||
b := p.binding(r)
|
||||
if b == nil {
|
||||
return nil
|
||||
}
|
||||
return b.Members
|
||||
}
|
||||
|
||||
// HasRole reports whether member has role r.
|
||||
func (p *Policy) HasRole(member string, r RoleName) bool {
|
||||
return memberIndex(member, p.binding(r)) >= 0
|
||||
}
|
||||
|
||||
// Add adds member member to role r if it is not already present.
|
||||
// A new binding is created if there is no binding for the role.
|
||||
func (p *Policy) Add(member string, r RoleName) {
|
||||
b := p.binding(r)
|
||||
if b == nil {
|
||||
if p.InternalProto == nil {
|
||||
p.InternalProto = &pb.Policy{}
|
||||
}
|
||||
p.InternalProto.Bindings = append(p.InternalProto.Bindings, &pb.Binding{
|
||||
Role: string(r),
|
||||
Members: []string{member},
|
||||
})
|
||||
return
|
||||
}
|
||||
if memberIndex(member, b) < 0 {
|
||||
b.Members = append(b.Members, member)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// Remove removes member from role r if it is present.
|
||||
func (p *Policy) Remove(member string, r RoleName) {
|
||||
bi := p.bindingIndex(r)
|
||||
if bi < 0 {
|
||||
return
|
||||
}
|
||||
bindings := p.InternalProto.Bindings
|
||||
b := bindings[bi]
|
||||
mi := memberIndex(member, b)
|
||||
if mi < 0 {
|
||||
return
|
||||
}
|
||||
// Order doesn't matter for bindings or members, so to remove, move the last item
|
||||
// into the removed spot and shrink the slice.
|
||||
if len(b.Members) == 1 {
|
||||
// Remove binding.
|
||||
last := len(bindings) - 1
|
||||
bindings[bi] = bindings[last]
|
||||
bindings[last] = nil
|
||||
p.InternalProto.Bindings = bindings[:last]
|
||||
return
|
||||
}
|
||||
// Remove member.
|
||||
// TODO(jba): worry about multiple copies of m?
|
||||
last := len(b.Members) - 1
|
||||
b.Members[mi] = b.Members[last]
|
||||
b.Members[last] = ""
|
||||
b.Members = b.Members[:last]
|
||||
}
|
||||
|
||||
// Roles returns the names of all the roles that appear in the Policy.
|
||||
func (p *Policy) Roles() []RoleName {
|
||||
if p.InternalProto == nil {
|
||||
return nil
|
||||
}
|
||||
var rns []RoleName
|
||||
for _, b := range p.InternalProto.Bindings {
|
||||
rns = append(rns, RoleName(b.Role))
|
||||
}
|
||||
return rns
|
||||
}
|
||||
|
||||
// binding returns the Binding for the suppied role, or nil if there isn't one.
|
||||
func (p *Policy) binding(r RoleName) *pb.Binding {
|
||||
i := p.bindingIndex(r)
|
||||
if i < 0 {
|
||||
return nil
|
||||
}
|
||||
return p.InternalProto.Bindings[i]
|
||||
}
|
||||
|
||||
func (p *Policy) bindingIndex(r RoleName) int {
|
||||
if p.InternalProto == nil {
|
||||
return -1
|
||||
}
|
||||
for i, b := range p.InternalProto.Bindings {
|
||||
if b.Role == string(r) {
|
||||
return i
|
||||
}
|
||||
}
|
||||
return -1
|
||||
}
|
||||
|
||||
// memberIndex returns the index of m in b's Members, or -1 if not found.
|
||||
func memberIndex(m string, b *pb.Binding) int {
|
||||
if b == nil {
|
||||
return -1
|
||||
}
|
||||
for i, mm := range b.Members {
|
||||
if mm == m {
|
||||
return i
|
||||
}
|
||||
}
|
||||
return -1
|
||||
}
|
||||
|
||||
// insertMetadata inserts metadata into the given context
|
||||
func insertMetadata(ctx context.Context, mds ...metadata.MD) context.Context {
|
||||
out, _ := metadata.FromOutgoingContext(ctx)
|
||||
out = out.Copy()
|
||||
for _, md := range mds {
|
||||
for k, v := range md {
|
||||
out[k] = append(out[k], v...)
|
||||
}
|
||||
}
|
||||
return metadata.NewOutgoingContext(ctx, out)
|
||||
}
|
||||
|
||||
// A Policy3 is a list of Bindings representing roles granted to members.
|
||||
//
|
||||
// The zero Policy3 is a valid policy with no bindings.
|
||||
//
|
||||
// It is similar to a Policy, except a Policy3 provides direct access to the
|
||||
// list of Bindings.
|
||||
//
|
||||
// The policy version is always set to 3.
|
||||
type Policy3 struct {
|
||||
etag []byte
|
||||
Bindings []*pb.Binding
|
||||
}
|
||||
|
||||
// Policy retrieves the IAM policy for the resource.
|
||||
//
|
||||
// requestedPolicyVersion is always set to 3.
|
||||
func (h *Handle3) Policy(ctx context.Context) (*Policy3, error) {
|
||||
proto, err := h.c.GetWithVersion(ctx, h.resource, h.version)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &Policy3{
|
||||
Bindings: proto.Bindings,
|
||||
etag: proto.Etag,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// SetPolicy replaces the resource's current policy with the supplied Policy.
|
||||
//
|
||||
// If policy was created from a prior call to Get, then the modification will
|
||||
// only succeed if the policy has not changed since the Get.
|
||||
func (h *Handle3) SetPolicy(ctx context.Context, policy *Policy3) error {
|
||||
return h.c.Set(ctx, h.resource, &pb.Policy{
|
||||
Bindings: policy.Bindings,
|
||||
Etag: policy.etag,
|
||||
Version: h.version,
|
||||
})
|
||||
}
|
||||
|
||||
// TestPermissions returns the subset of permissions that the caller has on the resource.
|
||||
func (h *Handle3) TestPermissions(ctx context.Context, permissions []string) ([]string, error) {
|
||||
return h.c.Test(ctx, h.resource, permissions)
|
||||
}
|
||||
-762
@@ -1,762 +0,0 @@
|
||||
{
|
||||
"cloud.google.com/go/asset/apiv1": {
|
||||
"distribution_name": "cloud.google.com/go/asset/apiv1",
|
||||
"description": "Cloud Asset API",
|
||||
"language": "Go",
|
||||
"client_library_type": "generated",
|
||||
"docs_url": "https://pkg.go.dev/cloud.google.com/go/asset/apiv1",
|
||||
"release_level": "ga"
|
||||
},
|
||||
"cloud.google.com/go/asset/apiv1beta1": {
|
||||
"distribution_name": "cloud.google.com/go/asset/apiv1beta1",
|
||||
"description": "Cloud Asset API",
|
||||
"language": "Go",
|
||||
"client_library_type": "generated",
|
||||
"docs_url": "https://pkg.go.dev/cloud.google.com/go/asset/apiv1beta1",
|
||||
"release_level": "beta"
|
||||
},
|
||||
"cloud.google.com/go/asset/apiv1p2beta1": {
|
||||
"distribution_name": "cloud.google.com/go/asset/apiv1p2beta1",
|
||||
"description": "Cloud Asset API",
|
||||
"language": "Go",
|
||||
"client_library_type": "generated",
|
||||
"docs_url": "https://pkg.go.dev/cloud.google.com/go/asset/apiv1p2beta1",
|
||||
"release_level": "beta"
|
||||
},
|
||||
"cloud.google.com/go/asset/apiv1p5beta1": {
|
||||
"distribution_name": "cloud.google.com/go/asset/apiv1p5beta1",
|
||||
"description": "Cloud Asset API",
|
||||
"language": "Go",
|
||||
"client_library_type": "generated",
|
||||
"docs_url": "https://pkg.go.dev/cloud.google.com/go/asset/apiv1p5beta1",
|
||||
"release_level": "beta"
|
||||
},
|
||||
"cloud.google.com/go/automl/apiv1": {
|
||||
"distribution_name": "cloud.google.com/go/automl/apiv1",
|
||||
"description": "Cloud AutoML API",
|
||||
"language": "Go",
|
||||
"client_library_type": "generated",
|
||||
"docs_url": "https://pkg.go.dev/cloud.google.com/go/automl/apiv1",
|
||||
"release_level": "ga"
|
||||
},
|
||||
"cloud.google.com/go/automl/apiv1beta1": {
|
||||
"distribution_name": "cloud.google.com/go/automl/apiv1beta1",
|
||||
"description": "Cloud AutoML API",
|
||||
"language": "Go",
|
||||
"client_library_type": "generated",
|
||||
"docs_url": "https://pkg.go.dev/cloud.google.com/go/automl/apiv1beta1",
|
||||
"release_level": "beta"
|
||||
},
|
||||
"cloud.google.com/go/bigquery": {
|
||||
"distribution_name": "cloud.google.com/go/bigquery",
|
||||
"description": "BigQuery",
|
||||
"language": "Go",
|
||||
"client_library_type": "manual",
|
||||
"docs_url": "https://pkg.go.dev/cloud.google.com/go/bigquery",
|
||||
"release_level": "ga"
|
||||
},
|
||||
"cloud.google.com/go/bigquery/connection/apiv1": {
|
||||
"distribution_name": "cloud.google.com/go/bigquery/connection/apiv1",
|
||||
"description": "BigQuery Connection API",
|
||||
"language": "Go",
|
||||
"client_library_type": "generated",
|
||||
"docs_url": "https://pkg.go.dev/cloud.google.com/go/bigquery/connection/apiv1",
|
||||
"release_level": "ga"
|
||||
},
|
||||
"cloud.google.com/go/bigquery/connection/apiv1beta1": {
|
||||
"distribution_name": "cloud.google.com/go/bigquery/connection/apiv1beta1",
|
||||
"description": "BigQuery Connection API",
|
||||
"language": "Go",
|
||||
"client_library_type": "generated",
|
||||
"docs_url": "https://pkg.go.dev/cloud.google.com/go/bigquery/connection/apiv1beta1",
|
||||
"release_level": "beta"
|
||||
},
|
||||
"cloud.google.com/go/bigquery/datatransfer/apiv1": {
|
||||
"distribution_name": "cloud.google.com/go/bigquery/datatransfer/apiv1",
|
||||
"description": "BigQuery Data Transfer API",
|
||||
"language": "Go",
|
||||
"client_library_type": "generated",
|
||||
"docs_url": "https://pkg.go.dev/cloud.google.com/go/bigquery/datatransfer/apiv1",
|
||||
"release_level": "ga"
|
||||
},
|
||||
"cloud.google.com/go/bigquery/reservation/apiv1": {
|
||||
"distribution_name": "cloud.google.com/go/bigquery/reservation/apiv1",
|
||||
"description": "BigQuery Reservation API",
|
||||
"language": "Go",
|
||||
"client_library_type": "generated",
|
||||
"docs_url": "https://pkg.go.dev/cloud.google.com/go/bigquery/reservation/apiv1",
|
||||
"release_level": "ga"
|
||||
},
|
||||
"cloud.google.com/go/bigquery/reservation/apiv1beta1": {
|
||||
"distribution_name": "cloud.google.com/go/bigquery/reservation/apiv1beta1",
|
||||
"description": "BigQuery Reservation API",
|
||||
"language": "Go",
|
||||
"client_library_type": "generated",
|
||||
"docs_url": "https://pkg.go.dev/cloud.google.com/go/bigquery/reservation/apiv1beta1",
|
||||
"release_level": "beta"
|
||||
},
|
||||
"cloud.google.com/go/bigquery/storage/apiv1": {
|
||||
"distribution_name": "cloud.google.com/go/bigquery/storage/apiv1",
|
||||
"description": "BigQuery Storage API",
|
||||
"language": "Go",
|
||||
"client_library_type": "generated",
|
||||
"docs_url": "https://pkg.go.dev/cloud.google.com/go/bigquery/storage/apiv1",
|
||||
"release_level": "ga"
|
||||
},
|
||||
"cloud.google.com/go/bigquery/storage/apiv1alpha2": {
|
||||
"distribution_name": "cloud.google.com/go/bigquery/storage/apiv1alpha2",
|
||||
"description": "BigQuery Storage API",
|
||||
"language": "Go",
|
||||
"client_library_type": "generated",
|
||||
"docs_url": "https://pkg.go.dev/cloud.google.com/go/bigquery/storage/apiv1alpha2",
|
||||
"release_level": "alpha"
|
||||
},
|
||||
"cloud.google.com/go/bigquery/storage/apiv1beta1": {
|
||||
"distribution_name": "cloud.google.com/go/bigquery/storage/apiv1beta1",
|
||||
"description": "BigQuery Storage API",
|
||||
"language": "Go",
|
||||
"client_library_type": "generated",
|
||||
"docs_url": "https://pkg.go.dev/cloud.google.com/go/bigquery/storage/apiv1beta1",
|
||||
"release_level": "beta"
|
||||
},
|
||||
"cloud.google.com/go/bigquery/storage/apiv1beta2": {
|
||||
"distribution_name": "cloud.google.com/go/bigquery/storage/apiv1beta2",
|
||||
"description": "BigQuery Storage API",
|
||||
"language": "Go",
|
||||
"client_library_type": "generated",
|
||||
"docs_url": "https://pkg.go.dev/cloud.google.com/go/bigquery/storage/apiv1beta2",
|
||||
"release_level": "beta"
|
||||
},
|
||||
"cloud.google.com/go/bigtable": {
|
||||
"distribution_name": "cloud.google.com/go/bigtable",
|
||||
"description": "Cloud BigTable",
|
||||
"language": "Go",
|
||||
"client_library_type": "manual",
|
||||
"docs_url": "https://pkg.go.dev/cloud.google.com/go/bigtable",
|
||||
"release_level": "ga"
|
||||
},
|
||||
"cloud.google.com/go/billing/apiv1": {
|
||||
"distribution_name": "cloud.google.com/go/billing/apiv1",
|
||||
"description": "Cloud Billing API",
|
||||
"language": "Go",
|
||||
"client_library_type": "generated",
|
||||
"docs_url": "https://pkg.go.dev/cloud.google.com/go/billing/apiv1",
|
||||
"release_level": "ga"
|
||||
},
|
||||
"cloud.google.com/go/cloudbuild/apiv1/v2": {
|
||||
"distribution_name": "cloud.google.com/go/cloudbuild/apiv1/v2",
|
||||
"description": "Cloud Build API",
|
||||
"language": "Go",
|
||||
"client_library_type": "generated",
|
||||
"docs_url": "https://pkg.go.dev/cloud.google.com/go/cloudbuild/apiv1/v2",
|
||||
"release_level": "ga"
|
||||
},
|
||||
"cloud.google.com/go/cloudtasks/apiv2": {
|
||||
"distribution_name": "cloud.google.com/go/cloudtasks/apiv2",
|
||||
"description": "Cloud Tasks API",
|
||||
"language": "Go",
|
||||
"client_library_type": "generated",
|
||||
"docs_url": "https://pkg.go.dev/cloud.google.com/go/cloudtasks/apiv2",
|
||||
"release_level": "ga"
|
||||
},
|
||||
"cloud.google.com/go/cloudtasks/apiv2beta2": {
|
||||
"distribution_name": "cloud.google.com/go/cloudtasks/apiv2beta2",
|
||||
"description": "Cloud Tasks API",
|
||||
"language": "Go",
|
||||
"client_library_type": "generated",
|
||||
"docs_url": "https://pkg.go.dev/cloud.google.com/go/cloudtasks/apiv2beta2",
|
||||
"release_level": "beta"
|
||||
},
|
||||
"cloud.google.com/go/cloudtasks/apiv2beta3": {
|
||||
"distribution_name": "cloud.google.com/go/cloudtasks/apiv2beta3",
|
||||
"description": "Cloud Tasks API",
|
||||
"language": "Go",
|
||||
"client_library_type": "generated",
|
||||
"docs_url": "https://pkg.go.dev/cloud.google.com/go/cloudtasks/apiv2beta3",
|
||||
"release_level": "beta"
|
||||
},
|
||||
"cloud.google.com/go/container/apiv1": {
|
||||
"distribution_name": "cloud.google.com/go/container/apiv1",
|
||||
"description": "Kubernetes Engine API",
|
||||
"language": "Go",
|
||||
"client_library_type": "generated",
|
||||
"docs_url": "https://pkg.go.dev/cloud.google.com/go/container/apiv1",
|
||||
"release_level": "ga"
|
||||
},
|
||||
"cloud.google.com/go/containeranalysis/apiv1beta1": {
|
||||
"distribution_name": "cloud.google.com/go/containeranalysis/apiv1beta1",
|
||||
"description": "Container Analysis API",
|
||||
"language": "Go",
|
||||
"client_library_type": "generated",
|
||||
"docs_url": "https://pkg.go.dev/cloud.google.com/go/containeranalysis/apiv1beta1",
|
||||
"release_level": "beta"
|
||||
},
|
||||
"cloud.google.com/go/datacatalog/apiv1": {
|
||||
"distribution_name": "cloud.google.com/go/datacatalog/apiv1",
|
||||
"description": "Google Cloud Data Catalog API",
|
||||
"language": "Go",
|
||||
"client_library_type": "generated",
|
||||
"docs_url": "https://pkg.go.dev/cloud.google.com/go/datacatalog/apiv1",
|
||||
"release_level": "ga"
|
||||
},
|
||||
"cloud.google.com/go/datacatalog/apiv1beta1": {
|
||||
"distribution_name": "cloud.google.com/go/datacatalog/apiv1beta1",
|
||||
"description": "Google Cloud Data Catalog API",
|
||||
"language": "Go",
|
||||
"client_library_type": "generated",
|
||||
"docs_url": "https://pkg.go.dev/cloud.google.com/go/datacatalog/apiv1beta1",
|
||||
"release_level": "beta"
|
||||
},
|
||||
"cloud.google.com/go/dataproc/apiv1": {
|
||||
"distribution_name": "cloud.google.com/go/dataproc/apiv1",
|
||||
"description": "Cloud Dataproc API",
|
||||
"language": "Go",
|
||||
"client_library_type": "generated",
|
||||
"docs_url": "https://pkg.go.dev/cloud.google.com/go/dataproc/apiv1",
|
||||
"release_level": "ga"
|
||||
},
|
||||
"cloud.google.com/go/dataproc/apiv1beta2": {
|
||||
"distribution_name": "cloud.google.com/go/dataproc/apiv1beta2",
|
||||
"description": "Cloud Dataproc API",
|
||||
"language": "Go",
|
||||
"client_library_type": "generated",
|
||||
"docs_url": "https://pkg.go.dev/cloud.google.com/go/dataproc/apiv1beta2",
|
||||
"release_level": "beta"
|
||||
},
|
||||
"cloud.google.com/go/datastore": {
|
||||
"distribution_name": "cloud.google.com/go/datastore",
|
||||
"description": "Cloud Datastore",
|
||||
"language": "Go",
|
||||
"client_library_type": "manual",
|
||||
"docs_url": "https://pkg.go.dev/cloud.google.com/go/datastore",
|
||||
"release_level": "ga"
|
||||
},
|
||||
"cloud.google.com/go/datastore/admin/apiv1": {
|
||||
"distribution_name": "cloud.google.com/go/datastore/admin/apiv1",
|
||||
"description": "Cloud Datastore API",
|
||||
"language": "Go",
|
||||
"client_library_type": "generated",
|
||||
"docs_url": "https://pkg.go.dev/cloud.google.com/go/datastore/admin/apiv1",
|
||||
"release_level": "alpha"
|
||||
},
|
||||
"cloud.google.com/go/debugger/apiv2": {
|
||||
"distribution_name": "cloud.google.com/go/debugger/apiv2",
|
||||
"description": "Stackdriver Debugger API",
|
||||
"language": "Go",
|
||||
"client_library_type": "generated",
|
||||
"docs_url": "https://pkg.go.dev/cloud.google.com/go/debugger/apiv2",
|
||||
"release_level": "ga"
|
||||
},
|
||||
"cloud.google.com/go/dialogflow/apiv2": {
|
||||
"distribution_name": "cloud.google.com/go/dialogflow/apiv2",
|
||||
"description": "Dialogflow API",
|
||||
"language": "Go",
|
||||
"client_library_type": "generated",
|
||||
"docs_url": "https://pkg.go.dev/cloud.google.com/go/dialogflow/apiv2",
|
||||
"release_level": "ga"
|
||||
},
|
||||
"cloud.google.com/go/dlp/apiv2": {
|
||||
"distribution_name": "cloud.google.com/go/dlp/apiv2",
|
||||
"description": "Cloud Data Loss Prevention (DLP) API",
|
||||
"language": "Go",
|
||||
"client_library_type": "generated",
|
||||
"docs_url": "https://pkg.go.dev/cloud.google.com/go/dlp/apiv2",
|
||||
"release_level": "ga"
|
||||
},
|
||||
"cloud.google.com/go/errorreporting": {
|
||||
"distribution_name": "cloud.google.com/go/errorreporting",
|
||||
"description": "Stackdriver Error Reporting API",
|
||||
"language": "Go",
|
||||
"client_library_type": "manual",
|
||||
"docs_url": "https://pkg.go.dev/cloud.google.com/go/errorreporting",
|
||||
"release_level": "beta"
|
||||
},
|
||||
"cloud.google.com/go/errorreporting/apiv1beta1": {
|
||||
"distribution_name": "cloud.google.com/go/errorreporting/apiv1beta1",
|
||||
"description": "Stackdriver Error Reporting API",
|
||||
"language": "Go",
|
||||
"client_library_type": "generated",
|
||||
"docs_url": "https://pkg.go.dev/cloud.google.com/go/errorreporting/apiv1beta1",
|
||||
"release_level": "beta"
|
||||
},
|
||||
"cloud.google.com/go/firestore": {
|
||||
"distribution_name": "cloud.google.com/go/firestore",
|
||||
"description": "Cloud Firestore API",
|
||||
"language": "Go",
|
||||
"client_library_type": "manual",
|
||||
"docs_url": "https://pkg.go.dev/cloud.google.com/go/firestore",
|
||||
"release_level": "ga"
|
||||
},
|
||||
"cloud.google.com/go/firestore/apiv1": {
|
||||
"distribution_name": "cloud.google.com/go/firestore/apiv1",
|
||||
"description": "Cloud Firestore API",
|
||||
"language": "Go",
|
||||
"client_library_type": "generated",
|
||||
"docs_url": "https://pkg.go.dev/cloud.google.com/go/firestore/apiv1",
|
||||
"release_level": "ga"
|
||||
},
|
||||
"cloud.google.com/go/firestore/apiv1/admin": {
|
||||
"distribution_name": "cloud.google.com/go/firestore/apiv1/admin",
|
||||
"description": "Google Cloud Firestore Admin API",
|
||||
"language": "Go",
|
||||
"client_library_type": "generated",
|
||||
"docs_url": "https://pkg.go.dev/cloud.google.com/go/firestore/apiv1/admin",
|
||||
"release_level": "ga"
|
||||
},
|
||||
"cloud.google.com/go/gaming/apiv1beta": {
|
||||
"distribution_name": "cloud.google.com/go/gaming/apiv1beta",
|
||||
"description": "",
|
||||
"language": "Go",
|
||||
"client_library_type": "generated",
|
||||
"docs_url": "https://pkg.go.dev/cloud.google.com/go/gaming/apiv1beta",
|
||||
"release_level": "beta"
|
||||
},
|
||||
"cloud.google.com/go/iam": {
|
||||
"distribution_name": "cloud.google.com/go/iam",
|
||||
"description": "Cloud IAM",
|
||||
"language": "Go",
|
||||
"client_library_type": "manual",
|
||||
"docs_url": "https://pkg.go.dev/cloud.google.com/go/iam",
|
||||
"release_level": "ga"
|
||||
},
|
||||
"cloud.google.com/go/iam/credentials/apiv1": {
|
||||
"distribution_name": "cloud.google.com/go/iam/credentials/apiv1",
|
||||
"description": "IAM Service Account Credentials API",
|
||||
"language": "Go",
|
||||
"client_library_type": "generated",
|
||||
"docs_url": "https://pkg.go.dev/cloud.google.com/go/iam/credentials/apiv1",
|
||||
"release_level": "ga"
|
||||
},
|
||||
"cloud.google.com/go/iot/apiv1": {
|
||||
"distribution_name": "cloud.google.com/go/iot/apiv1",
|
||||
"description": "Cloud IoT API",
|
||||
"language": "Go",
|
||||
"client_library_type": "generated",
|
||||
"docs_url": "https://pkg.go.dev/cloud.google.com/go/iot/apiv1",
|
||||
"release_level": "ga"
|
||||
},
|
||||
"cloud.google.com/go/irm/apiv1alpha2": {
|
||||
"distribution_name": "cloud.google.com/go/irm/apiv1alpha2",
|
||||
"description": "Stackdriver Incident Response \u0026 Management API",
|
||||
"language": "Go",
|
||||
"client_library_type": "generated",
|
||||
"docs_url": "https://pkg.go.dev/cloud.google.com/go/irm/apiv1alpha2",
|
||||
"release_level": "alpha"
|
||||
},
|
||||
"cloud.google.com/go/kms/apiv1": {
|
||||
"distribution_name": "cloud.google.com/go/kms/apiv1",
|
||||
"description": "Cloud Key Management Service (KMS) API",
|
||||
"language": "Go",
|
||||
"client_library_type": "generated",
|
||||
"docs_url": "https://pkg.go.dev/cloud.google.com/go/kms/apiv1",
|
||||
"release_level": "ga"
|
||||
},
|
||||
"cloud.google.com/go/language/apiv1": {
|
||||
"distribution_name": "cloud.google.com/go/language/apiv1",
|
||||
"description": "Cloud Natural Language API",
|
||||
"language": "Go",
|
||||
"client_library_type": "generated",
|
||||
"docs_url": "https://pkg.go.dev/cloud.google.com/go/language/apiv1",
|
||||
"release_level": "ga"
|
||||
},
|
||||
"cloud.google.com/go/language/apiv1beta2": {
|
||||
"distribution_name": "cloud.google.com/go/language/apiv1beta2",
|
||||
"description": "Cloud Natural Language API",
|
||||
"language": "Go",
|
||||
"client_library_type": "generated",
|
||||
"docs_url": "https://pkg.go.dev/cloud.google.com/go/language/apiv1beta2",
|
||||
"release_level": "beta"
|
||||
},
|
||||
"cloud.google.com/go/logging": {
|
||||
"distribution_name": "cloud.google.com/go/logging",
|
||||
"description": "Stackdriver Logging API",
|
||||
"language": "Go",
|
||||
"client_library_type": "manual",
|
||||
"docs_url": "https://pkg.go.dev/cloud.google.com/go/logging",
|
||||
"release_level": "ga"
|
||||
},
|
||||
"cloud.google.com/go/logging/apiv2": {
|
||||
"distribution_name": "cloud.google.com/go/logging/apiv2",
|
||||
"description": "Stackdriver Logging API",
|
||||
"language": "Go",
|
||||
"client_library_type": "generated",
|
||||
"docs_url": "https://pkg.go.dev/cloud.google.com/go/logging/apiv2",
|
||||
"release_level": "ga"
|
||||
},
|
||||
"cloud.google.com/go/longrunning/autogen": {
|
||||
"distribution_name": "cloud.google.com/go/longrunning/autogen",
|
||||
"description": "Long Running Operations API",
|
||||
"language": "Go",
|
||||
"client_library_type": "generated",
|
||||
"docs_url": "https://pkg.go.dev/cloud.google.com/go/longrunning/autogen",
|
||||
"release_level": "alpha"
|
||||
},
|
||||
"cloud.google.com/go/memcache/apiv1beta2": {
|
||||
"distribution_name": "cloud.google.com/go/memcache/apiv1beta2",
|
||||
"description": "Cloud Memorystore for Memcached API",
|
||||
"language": "Go",
|
||||
"client_library_type": "generated",
|
||||
"docs_url": "https://pkg.go.dev/cloud.google.com/go/memcache/apiv1beta2",
|
||||
"release_level": "beta"
|
||||
},
|
||||
"cloud.google.com/go/monitoring/apiv3/v2": {
|
||||
"distribution_name": "cloud.google.com/go/monitoring/apiv3/v2",
|
||||
"description": "Cloud Monitoring API",
|
||||
"language": "Go",
|
||||
"client_library_type": "generated",
|
||||
"docs_url": "https://pkg.go.dev/cloud.google.com/go/monitoring/apiv3/v2",
|
||||
"release_level": "ga"
|
||||
},
|
||||
"cloud.google.com/go/osconfig/agentendpoint/apiv1": {
|
||||
"distribution_name": "cloud.google.com/go/osconfig/agentendpoint/apiv1",
|
||||
"description": "Cloud OS Config API",
|
||||
"language": "Go",
|
||||
"client_library_type": "generated",
|
||||
"docs_url": "https://pkg.go.dev/cloud.google.com/go/osconfig/agentendpoint/apiv1",
|
||||
"release_level": "ga"
|
||||
},
|
||||
"cloud.google.com/go/osconfig/agentendpoint/apiv1beta": {
|
||||
"distribution_name": "cloud.google.com/go/osconfig/agentendpoint/apiv1beta",
|
||||
"description": "Cloud OS Config API",
|
||||
"language": "Go",
|
||||
"client_library_type": "generated",
|
||||
"docs_url": "https://pkg.go.dev/cloud.google.com/go/osconfig/agentendpoint/apiv1beta",
|
||||
"release_level": "beta"
|
||||
},
|
||||
"cloud.google.com/go/osconfig/apiv1": {
|
||||
"distribution_name": "cloud.google.com/go/osconfig/apiv1",
|
||||
"description": "Cloud OS Config API",
|
||||
"language": "Go",
|
||||
"client_library_type": "generated",
|
||||
"docs_url": "https://pkg.go.dev/cloud.google.com/go/osconfig/apiv1",
|
||||
"release_level": "ga"
|
||||
},
|
||||
"cloud.google.com/go/osconfig/apiv1beta": {
|
||||
"distribution_name": "cloud.google.com/go/osconfig/apiv1beta",
|
||||
"description": "Cloud OS Config API",
|
||||
"language": "Go",
|
||||
"client_library_type": "generated",
|
||||
"docs_url": "https://pkg.go.dev/cloud.google.com/go/osconfig/apiv1beta",
|
||||
"release_level": "beta"
|
||||
},
|
||||
"cloud.google.com/go/oslogin/apiv1": {
|
||||
"distribution_name": "cloud.google.com/go/oslogin/apiv1",
|
||||
"description": "Cloud OS Login API",
|
||||
"language": "Go",
|
||||
"client_library_type": "generated",
|
||||
"docs_url": "https://pkg.go.dev/cloud.google.com/go/oslogin/apiv1",
|
||||
"release_level": "ga"
|
||||
},
|
||||
"cloud.google.com/go/oslogin/apiv1beta": {
|
||||
"distribution_name": "cloud.google.com/go/oslogin/apiv1beta",
|
||||
"description": "Cloud OS Login API",
|
||||
"language": "Go",
|
||||
"client_library_type": "generated",
|
||||
"docs_url": "https://pkg.go.dev/cloud.google.com/go/oslogin/apiv1beta",
|
||||
"release_level": "beta"
|
||||
},
|
||||
"cloud.google.com/go/phishingprotection/apiv1beta1": {
|
||||
"distribution_name": "cloud.google.com/go/phishingprotection/apiv1beta1",
|
||||
"description": "Phishing Protection API",
|
||||
"language": "Go",
|
||||
"client_library_type": "generated",
|
||||
"docs_url": "https://pkg.go.dev/cloud.google.com/go/phishingprotection/apiv1beta1",
|
||||
"release_level": "beta"
|
||||
},
|
||||
"cloud.google.com/go/pubsub": {
|
||||
"distribution_name": "cloud.google.com/go/pubsub",
|
||||
"description": "Cloud PubSub",
|
||||
"language": "Go",
|
||||
"client_library_type": "manual",
|
||||
"docs_url": "https://pkg.go.dev/cloud.google.com/go/pubsub",
|
||||
"release_level": "ga"
|
||||
},
|
||||
"cloud.google.com/go/pubsub/apiv1": {
|
||||
"distribution_name": "cloud.google.com/go/pubsub/apiv1",
|
||||
"description": "Cloud Pub/Sub API",
|
||||
"language": "Go",
|
||||
"client_library_type": "generated",
|
||||
"docs_url": "https://pkg.go.dev/cloud.google.com/go/pubsub/apiv1",
|
||||
"release_level": "ga"
|
||||
},
|
||||
"cloud.google.com/go/recaptchaenterprise/apiv1": {
|
||||
"distribution_name": "cloud.google.com/go/recaptchaenterprise/apiv1",
|
||||
"description": "reCAPTCHA Enterprise API",
|
||||
"language": "Go",
|
||||
"client_library_type": "generated",
|
||||
"docs_url": "https://pkg.go.dev/cloud.google.com/go/recaptchaenterprise/apiv1",
|
||||
"release_level": "beta"
|
||||
},
|
||||
"cloud.google.com/go/recaptchaenterprise/apiv1beta1": {
|
||||
"distribution_name": "cloud.google.com/go/recaptchaenterprise/apiv1beta1",
|
||||
"description": "reCAPTCHA Enterprise API",
|
||||
"language": "Go",
|
||||
"client_library_type": "generated",
|
||||
"docs_url": "https://pkg.go.dev/cloud.google.com/go/recaptchaenterprise/apiv1beta1",
|
||||
"release_level": "beta"
|
||||
},
|
||||
"cloud.google.com/go/recommender/apiv1": {
|
||||
"distribution_name": "cloud.google.com/go/recommender/apiv1",
|
||||
"description": "Recommender API",
|
||||
"language": "Go",
|
||||
"client_library_type": "generated",
|
||||
"docs_url": "https://pkg.go.dev/cloud.google.com/go/recommender/apiv1",
|
||||
"release_level": "ga"
|
||||
},
|
||||
"cloud.google.com/go/recommender/apiv1beta1": {
|
||||
"distribution_name": "cloud.google.com/go/recommender/apiv1beta1",
|
||||
"description": "Recommender API",
|
||||
"language": "Go",
|
||||
"client_library_type": "generated",
|
||||
"docs_url": "https://pkg.go.dev/cloud.google.com/go/recommender/apiv1beta1",
|
||||
"release_level": "beta"
|
||||
},
|
||||
"cloud.google.com/go/redis/apiv1": {
|
||||
"distribution_name": "cloud.google.com/go/redis/apiv1",
|
||||
"description": "Google Cloud Memorystore for Redis API",
|
||||
"language": "Go",
|
||||
"client_library_type": "generated",
|
||||
"docs_url": "https://pkg.go.dev/cloud.google.com/go/redis/apiv1",
|
||||
"release_level": "ga"
|
||||
},
|
||||
"cloud.google.com/go/redis/apiv1beta1": {
|
||||
"distribution_name": "cloud.google.com/go/redis/apiv1beta1",
|
||||
"description": "Google Cloud Memorystore for Redis API",
|
||||
"language": "Go",
|
||||
"client_library_type": "generated",
|
||||
"docs_url": "https://pkg.go.dev/cloud.google.com/go/redis/apiv1beta1",
|
||||
"release_level": "beta"
|
||||
},
|
||||
"cloud.google.com/go/rpcreplay": {
|
||||
"distribution_name": "cloud.google.com/go/rpcreplay",
|
||||
"description": "RPC Replay",
|
||||
"language": "Go",
|
||||
"client_library_type": "manual",
|
||||
"docs_url": "https://pkg.go.dev/cloud.google.com/go/rpcreplay",
|
||||
"release_level": "ga"
|
||||
},
|
||||
"cloud.google.com/go/scheduler/apiv1": {
|
||||
"distribution_name": "cloud.google.com/go/scheduler/apiv1",
|
||||
"description": "Cloud Scheduler API",
|
||||
"language": "Go",
|
||||
"client_library_type": "generated",
|
||||
"docs_url": "https://pkg.go.dev/cloud.google.com/go/scheduler/apiv1",
|
||||
"release_level": "ga"
|
||||
},
|
||||
"cloud.google.com/go/scheduler/apiv1beta1": {
|
||||
"distribution_name": "cloud.google.com/go/scheduler/apiv1beta1",
|
||||
"description": "Cloud Scheduler API",
|
||||
"language": "Go",
|
||||
"client_library_type": "generated",
|
||||
"docs_url": "https://pkg.go.dev/cloud.google.com/go/scheduler/apiv1beta1",
|
||||
"release_level": "beta"
|
||||
},
|
||||
"cloud.google.com/go/secretmanager/apiv1": {
|
||||
"distribution_name": "cloud.google.com/go/secretmanager/apiv1",
|
||||
"description": "Secret Manager API",
|
||||
"language": "Go",
|
||||
"client_library_type": "generated",
|
||||
"docs_url": "https://pkg.go.dev/cloud.google.com/go/secretmanager/apiv1",
|
||||
"release_level": "ga"
|
||||
},
|
||||
"cloud.google.com/go/secretmanager/apiv1beta1": {
|
||||
"distribution_name": "cloud.google.com/go/secretmanager/apiv1beta1",
|
||||
"description": "Secret Manager API",
|
||||
"language": "Go",
|
||||
"client_library_type": "generated",
|
||||
"docs_url": "https://pkg.go.dev/cloud.google.com/go/secretmanager/apiv1beta1",
|
||||
"release_level": "beta"
|
||||
},
|
||||
"cloud.google.com/go/securitycenter/apiv1": {
|
||||
"distribution_name": "cloud.google.com/go/securitycenter/apiv1",
|
||||
"description": "Security Command Center API",
|
||||
"language": "Go",
|
||||
"client_library_type": "generated",
|
||||
"docs_url": "https://pkg.go.dev/cloud.google.com/go/securitycenter/apiv1",
|
||||
"release_level": "ga"
|
||||
},
|
||||
"cloud.google.com/go/securitycenter/apiv1beta1": {
|
||||
"distribution_name": "cloud.google.com/go/securitycenter/apiv1beta1",
|
||||
"description": "Cloud Security Command Center API",
|
||||
"language": "Go",
|
||||
"client_library_type": "generated",
|
||||
"docs_url": "https://pkg.go.dev/cloud.google.com/go/securitycenter/apiv1beta1",
|
||||
"release_level": "beta"
|
||||
},
|
||||
"cloud.google.com/go/securitycenter/apiv1p1beta1": {
|
||||
"distribution_name": "cloud.google.com/go/securitycenter/apiv1p1beta1",
|
||||
"description": "Security Command Center API",
|
||||
"language": "Go",
|
||||
"client_library_type": "generated",
|
||||
"docs_url": "https://pkg.go.dev/cloud.google.com/go/securitycenter/apiv1p1beta1",
|
||||
"release_level": "beta"
|
||||
},
|
||||
"cloud.google.com/go/securitycenter/settings/apiv1beta1": {
|
||||
"distribution_name": "cloud.google.com/go/securitycenter/settings/apiv1beta1",
|
||||
"description": "Cloud Security Command Center API",
|
||||
"language": "Go",
|
||||
"client_library_type": "generated",
|
||||
"docs_url": "https://pkg.go.dev/cloud.google.com/go/securitycenter/settings/apiv1beta1",
|
||||
"release_level": "beta"
|
||||
},
|
||||
"cloud.google.com/go/servicedirectory/apiv1beta1": {
|
||||
"distribution_name": "cloud.google.com/go/servicedirectory/apiv1beta1",
|
||||
"description": "Service Directory API",
|
||||
"language": "Go",
|
||||
"client_library_type": "generated",
|
||||
"docs_url": "https://pkg.go.dev/cloud.google.com/go/servicedirectory/apiv1beta1",
|
||||
"release_level": "beta"
|
||||
},
|
||||
"cloud.google.com/go/spanner": {
|
||||
"distribution_name": "cloud.google.com/go/spanner",
|
||||
"description": "Cloud Spanner",
|
||||
"language": "Go",
|
||||
"client_library_type": "manual",
|
||||
"docs_url": "https://pkg.go.dev/cloud.google.com/go/spanner",
|
||||
"release_level": "ga"
|
||||
},
|
||||
"cloud.google.com/go/spanner/admin/database/apiv1": {
|
||||
"distribution_name": "cloud.google.com/go/spanner/admin/database/apiv1",
|
||||
"description": "Cloud Spanner Database Admin API",
|
||||
"language": "Go",
|
||||
"client_library_type": "generated",
|
||||
"docs_url": "https://pkg.go.dev/cloud.google.com/go/spanner/admin/database/apiv1",
|
||||
"release_level": "ga"
|
||||
},
|
||||
"cloud.google.com/go/spanner/admin/instance/apiv1": {
|
||||
"distribution_name": "cloud.google.com/go/spanner/admin/instance/apiv1",
|
||||
"description": "Cloud Spanner Instance Admin API",
|
||||
"language": "Go",
|
||||
"client_library_type": "generated",
|
||||
"docs_url": "https://pkg.go.dev/cloud.google.com/go/spanner/admin/instance/apiv1",
|
||||
"release_level": "ga"
|
||||
},
|
||||
"cloud.google.com/go/spanner/apiv1": {
|
||||
"distribution_name": "cloud.google.com/go/spanner/apiv1",
|
||||
"description": "Cloud Spanner API",
|
||||
"language": "Go",
|
||||
"client_library_type": "generated",
|
||||
"docs_url": "https://pkg.go.dev/cloud.google.com/go/spanner/apiv1",
|
||||
"release_level": "ga"
|
||||
},
|
||||
"cloud.google.com/go/speech/apiv1": {
|
||||
"distribution_name": "cloud.google.com/go/speech/apiv1",
|
||||
"description": "Cloud Speech-to-Text API",
|
||||
"language": "Go",
|
||||
"client_library_type": "generated",
|
||||
"docs_url": "https://pkg.go.dev/cloud.google.com/go/speech/apiv1",
|
||||
"release_level": "ga"
|
||||
},
|
||||
"cloud.google.com/go/speech/apiv1p1beta1": {
|
||||
"distribution_name": "cloud.google.com/go/speech/apiv1p1beta1",
|
||||
"description": "Cloud Speech-to-Text API",
|
||||
"language": "Go",
|
||||
"client_library_type": "generated",
|
||||
"docs_url": "https://pkg.go.dev/cloud.google.com/go/speech/apiv1p1beta1",
|
||||
"release_level": "beta"
|
||||
},
|
||||
"cloud.google.com/go/storage": {
|
||||
"distribution_name": "cloud.google.com/go/storage",
|
||||
"description": "Cloud Storage (GCS)",
|
||||
"language": "Go",
|
||||
"client_library_type": "manual",
|
||||
"docs_url": "https://pkg.go.dev/cloud.google.com/go/storage",
|
||||
"release_level": "ga"
|
||||
},
|
||||
"cloud.google.com/go/talent/apiv4beta1": {
|
||||
"distribution_name": "cloud.google.com/go/talent/apiv4beta1",
|
||||
"description": "Cloud Talent Solution API",
|
||||
"language": "Go",
|
||||
"client_library_type": "generated",
|
||||
"docs_url": "https://pkg.go.dev/cloud.google.com/go/talent/apiv4beta1",
|
||||
"release_level": "beta"
|
||||
},
|
||||
"cloud.google.com/go/texttospeech/apiv1": {
|
||||
"distribution_name": "cloud.google.com/go/texttospeech/apiv1",
|
||||
"description": "Cloud Text-to-Speech API",
|
||||
"language": "Go",
|
||||
"client_library_type": "generated",
|
||||
"docs_url": "https://pkg.go.dev/cloud.google.com/go/texttospeech/apiv1",
|
||||
"release_level": "ga"
|
||||
},
|
||||
"cloud.google.com/go/trace": {
|
||||
"distribution_name": "cloud.google.com/go/trace",
|
||||
"description": "Stackdriver Trace",
|
||||
"language": "Go",
|
||||
"client_library_type": "manual",
|
||||
"docs_url": "https://pkg.go.dev/cloud.google.com/go/trace",
|
||||
"release_level": "ga"
|
||||
},
|
||||
"cloud.google.com/go/trace/apiv1": {
|
||||
"distribution_name": "cloud.google.com/go/trace/apiv1",
|
||||
"description": "Stackdriver Trace API",
|
||||
"language": "Go",
|
||||
"client_library_type": "generated",
|
||||
"docs_url": "https://pkg.go.dev/cloud.google.com/go/trace/apiv1",
|
||||
"release_level": "ga"
|
||||
},
|
||||
"cloud.google.com/go/trace/apiv2": {
|
||||
"distribution_name": "cloud.google.com/go/trace/apiv2",
|
||||
"description": "Stackdriver Trace API",
|
||||
"language": "Go",
|
||||
"client_library_type": "generated",
|
||||
"docs_url": "https://pkg.go.dev/cloud.google.com/go/trace/apiv2",
|
||||
"release_level": "ga"
|
||||
},
|
||||
"cloud.google.com/go/translate/apiv3": {
|
||||
"distribution_name": "cloud.google.com/go/translate/apiv3",
|
||||
"description": "Cloud Translation API",
|
||||
"language": "Go",
|
||||
"client_library_type": "generated",
|
||||
"docs_url": "https://pkg.go.dev/cloud.google.com/go/translate/apiv3",
|
||||
"release_level": "ga"
|
||||
},
|
||||
"cloud.google.com/go/videointelligence/apiv1": {
|
||||
"distribution_name": "cloud.google.com/go/videointelligence/apiv1",
|
||||
"description": "Cloud Video Intelligence API",
|
||||
"language": "Go",
|
||||
"client_library_type": "generated",
|
||||
"docs_url": "https://pkg.go.dev/cloud.google.com/go/videointelligence/apiv1",
|
||||
"release_level": "ga"
|
||||
},
|
||||
"cloud.google.com/go/videointelligence/apiv1beta2": {
|
||||
"distribution_name": "cloud.google.com/go/videointelligence/apiv1beta2",
|
||||
"description": "Google Cloud Video Intelligence API",
|
||||
"language": "Go",
|
||||
"client_library_type": "generated",
|
||||
"docs_url": "https://pkg.go.dev/cloud.google.com/go/videointelligence/apiv1beta2",
|
||||
"release_level": "beta"
|
||||
},
|
||||
"cloud.google.com/go/vision/apiv1": {
|
||||
"distribution_name": "cloud.google.com/go/vision/apiv1",
|
||||
"description": "Cloud Vision API",
|
||||
"language": "Go",
|
||||
"client_library_type": "generated",
|
||||
"docs_url": "https://pkg.go.dev/cloud.google.com/go/vision/apiv1",
|
||||
"release_level": "ga"
|
||||
},
|
||||
"cloud.google.com/go/vision/apiv1p1beta1": {
|
||||
"distribution_name": "cloud.google.com/go/vision/apiv1p1beta1",
|
||||
"description": "Cloud Vision API",
|
||||
"language": "Go",
|
||||
"client_library_type": "generated",
|
||||
"docs_url": "https://pkg.go.dev/cloud.google.com/go/vision/apiv1p1beta1",
|
||||
"release_level": "beta"
|
||||
},
|
||||
"cloud.google.com/go/webrisk/apiv1": {
|
||||
"distribution_name": "cloud.google.com/go/webrisk/apiv1",
|
||||
"description": "Web Risk API",
|
||||
"language": "Go",
|
||||
"client_library_type": "generated",
|
||||
"docs_url": "https://pkg.go.dev/cloud.google.com/go/webrisk/apiv1",
|
||||
"release_level": "ga"
|
||||
},
|
||||
"cloud.google.com/go/webrisk/apiv1beta1": {
|
||||
"distribution_name": "cloud.google.com/go/webrisk/apiv1beta1",
|
||||
"description": "Web Risk API",
|
||||
"language": "Go",
|
||||
"client_library_type": "generated",
|
||||
"docs_url": "https://pkg.go.dev/cloud.google.com/go/webrisk/apiv1beta1",
|
||||
"release_level": "beta"
|
||||
}
|
||||
}
|
||||
-18
@@ -1,18 +0,0 @@
|
||||
# Internal
|
||||
|
||||
This directory contains internal code for cloud.google.com/go packages.
|
||||
|
||||
## .repo-metadata-full.json
|
||||
|
||||
`.repo-metadata-full.json` contains metadata about the packages in this repo. It
|
||||
is generated by `internal/gapicgen/generator`. It's processed by external tools
|
||||
to build lists of all of the packages.
|
||||
|
||||
Don't make breaking changes to the format without consulting with the external
|
||||
tools.
|
||||
|
||||
One day, we may want to create individual `.repo-metadata.json` files next to
|
||||
each package, which is the pattern followed by some other languages. External
|
||||
tools would then talk to pkg.go.dev or some other service to get the overall
|
||||
list of packages and use the `.repo-metadata.json` files to get the additional
|
||||
metadata required. For now, `.repo-metadata-full.json` includes everything.
|
||||
-54
@@ -1,54 +0,0 @@
|
||||
// Copyright 2017 Google LLC
|
||||
//
|
||||
// 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 internal
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"google.golang.org/api/googleapi"
|
||||
"google.golang.org/grpc/status"
|
||||
)
|
||||
|
||||
// Annotate prepends msg to the error message in err, attempting
|
||||
// to preserve other information in err, like an error code.
|
||||
//
|
||||
// Annotate panics if err is nil.
|
||||
//
|
||||
// Annotate knows about these error types:
|
||||
// - "google.golang.org/grpc/status".Status
|
||||
// - "google.golang.org/api/googleapi".Error
|
||||
// If the error is not one of these types, Annotate behaves
|
||||
// like
|
||||
// fmt.Errorf("%s: %v", msg, err)
|
||||
func Annotate(err error, msg string) error {
|
||||
if err == nil {
|
||||
panic("Annotate called with nil")
|
||||
}
|
||||
if s, ok := status.FromError(err); ok {
|
||||
p := s.Proto()
|
||||
p.Message = msg + ": " + p.Message
|
||||
return status.ErrorProto(p)
|
||||
}
|
||||
if g, ok := err.(*googleapi.Error); ok {
|
||||
g.Message = msg + ": " + g.Message
|
||||
return g
|
||||
}
|
||||
return fmt.Errorf("%s: %v", msg, err)
|
||||
}
|
||||
|
||||
// Annotatef uses format and args to format a string, then calls Annotate.
|
||||
func Annotatef(err error, format string, args ...interface{}) error {
|
||||
return Annotate(err, fmt.Sprintf(format, args...))
|
||||
}
|
||||
-11
@@ -1,11 +0,0 @@
|
||||
This package is a fork of github.com/jba/btree at commit
|
||||
d4edd57f39b8425fc2c631047ff4dc6024d82a4f, which itself was a fork of
|
||||
github.com/google/btree at 316fb6d3f031ae8f4d457c6c5186b9e3ded70435.
|
||||
|
||||
This directory makes the following modifications:
|
||||
|
||||
- Updated copyright notice.
|
||||
- removed LICENSE (it is the same as the repo-wide license, Apache 2.0)
|
||||
- Removed examples_test.go and .travis.yml.
|
||||
- Added this file.
|
||||
|
||||
-1013
File diff suppressed because it is too large
Load Diff
-37
@@ -1,37 +0,0 @@
|
||||
// Copyright 2014 Google LLC
|
||||
//
|
||||
// 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 btree
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"strings"
|
||||
)
|
||||
|
||||
func (t *BTree) print(w io.Writer) {
|
||||
t.root.print(w, 0)
|
||||
}
|
||||
|
||||
func (n *node) print(w io.Writer, level int) {
|
||||
indent := strings.Repeat(" ", level)
|
||||
if n == nil {
|
||||
fmt.Fprintf(w, "%s<nil>\n", indent)
|
||||
return
|
||||
}
|
||||
fmt.Fprintf(w, "%s%v\n", indent, n.items)
|
||||
for _, c := range n.children {
|
||||
c.print(w, level+1)
|
||||
}
|
||||
}
|
||||
-480
@@ -1,480 +0,0 @@
|
||||
// Copyright 2016 Google LLC
|
||||
//
|
||||
// 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 fields provides a view of the fields of a struct that follows the Go
|
||||
// rules, amended to consider tags and case insensitivity.
|
||||
//
|
||||
// Usage
|
||||
//
|
||||
// First define a function that interprets tags:
|
||||
//
|
||||
// func parseTag(st reflect.StructTag) (name string, keep bool, other interface{}, err error) { ... }
|
||||
//
|
||||
// The function's return values describe whether to ignore the field
|
||||
// completely or provide an alternate name, as well as other data from the
|
||||
// parse that is stored to avoid re-parsing.
|
||||
//
|
||||
// Then define a function to validate the type:
|
||||
//
|
||||
// func validate(t reflect.Type) error { ... }
|
||||
//
|
||||
// Then, if necessary, define a function to specify leaf types - types
|
||||
// which should be considered one field and not be recursed into:
|
||||
//
|
||||
// func isLeafType(t reflect.Type) bool { ... }
|
||||
//
|
||||
// eg:
|
||||
//
|
||||
// func isLeafType(t reflect.Type) bool {
|
||||
// return t == reflect.TypeOf(time.Time{})
|
||||
// }
|
||||
//
|
||||
// Next, construct a Cache, passing your functions. As its name suggests, a
|
||||
// Cache remembers validation and field information for a type, so subsequent
|
||||
// calls with the same type are very fast.
|
||||
//
|
||||
// cache := fields.NewCache(parseTag, validate, isLeafType)
|
||||
//
|
||||
// To get the fields of a struct type as determined by the above rules, call
|
||||
// the Fields method:
|
||||
//
|
||||
// fields, err := cache.Fields(reflect.TypeOf(MyStruct{}))
|
||||
//
|
||||
// The return value can be treated as a slice of Fields.
|
||||
//
|
||||
// Given a string, such as a key or column name obtained during unmarshalling,
|
||||
// call Match on the list of fields to find a field whose name is the best
|
||||
// match:
|
||||
//
|
||||
// field := fields.Match(name)
|
||||
//
|
||||
// Match looks for an exact match first, then falls back to a case-insensitive
|
||||
// comparison.
|
||||
package fields
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"errors"
|
||||
"reflect"
|
||||
"sort"
|
||||
"strings"
|
||||
"sync"
|
||||
)
|
||||
|
||||
// A Field records information about a struct field.
|
||||
type Field struct {
|
||||
Name string // effective field name
|
||||
NameFromTag bool // did Name come from a tag?
|
||||
Type reflect.Type // field type
|
||||
Index []int // index sequence, for reflect.Value.FieldByIndex
|
||||
ParsedTag interface{} // third return value of the parseTag function
|
||||
|
||||
nameBytes []byte
|
||||
equalFold func(s, t []byte) bool
|
||||
}
|
||||
|
||||
// ParseTagFunc is a function that accepts a struct tag and returns four values: an alternative name for the field
|
||||
// extracted from the tag, a boolean saying whether to keep the field or ignore it, additional data that is stored
|
||||
// with the field information to avoid having to parse the tag again, and an error.
|
||||
type ParseTagFunc func(reflect.StructTag) (name string, keep bool, other interface{}, err error)
|
||||
|
||||
// ValidateFunc is a function that accepts a reflect.Type and returns an error if the struct type is invalid in any
|
||||
// way.
|
||||
type ValidateFunc func(reflect.Type) error
|
||||
|
||||
// LeafTypesFunc is a function that accepts a reflect.Type and returns true if the struct type a leaf, or false if not.
|
||||
// TODO(deklerk): is this description accurate?
|
||||
type LeafTypesFunc func(reflect.Type) bool
|
||||
|
||||
// A Cache records information about the fields of struct types.
|
||||
//
|
||||
// A Cache is safe for use by multiple goroutines.
|
||||
type Cache struct {
|
||||
parseTag ParseTagFunc
|
||||
validate ValidateFunc
|
||||
leafTypes LeafTypesFunc
|
||||
cache sync.Map // from reflect.Type to cacheValue
|
||||
}
|
||||
|
||||
// NewCache constructs a Cache.
|
||||
//
|
||||
// Its first argument should be a function that accepts
|
||||
// a struct tag and returns four values: an alternative name for the field
|
||||
// extracted from the tag, a boolean saying whether to keep the field or ignore
|
||||
// it, additional data that is stored with the field information to avoid
|
||||
// having to parse the tag again, and an error.
|
||||
//
|
||||
// Its second argument should be a function that accepts a reflect.Type and
|
||||
// returns an error if the struct type is invalid in any way. For example, it
|
||||
// may check that all of the struct field tags are valid, or that all fields
|
||||
// are of an appropriate type.
|
||||
func NewCache(parseTag ParseTagFunc, validate ValidateFunc, leafTypes LeafTypesFunc) *Cache {
|
||||
if parseTag == nil {
|
||||
parseTag = func(reflect.StructTag) (string, bool, interface{}, error) {
|
||||
return "", true, nil, nil
|
||||
}
|
||||
}
|
||||
if validate == nil {
|
||||
validate = func(reflect.Type) error {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
if leafTypes == nil {
|
||||
leafTypes = func(reflect.Type) bool {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
return &Cache{
|
||||
parseTag: parseTag,
|
||||
validate: validate,
|
||||
leafTypes: leafTypes,
|
||||
}
|
||||
}
|
||||
|
||||
// A fieldScan represents an item on the fieldByNameFunc scan work list.
|
||||
type fieldScan struct {
|
||||
typ reflect.Type
|
||||
index []int
|
||||
}
|
||||
|
||||
// Fields returns all the exported fields of t, which must be a struct type. It
|
||||
// follows the standard Go rules for embedded fields, modified by the presence
|
||||
// of tags. The result is sorted lexicographically by index.
|
||||
//
|
||||
// These rules apply in the absence of tags:
|
||||
// Anonymous struct fields are treated as if their inner exported fields were
|
||||
// fields in the outer struct (embedding). The result includes all fields that
|
||||
// aren't shadowed by fields at higher level of embedding. If more than one
|
||||
// field with the same name exists at the same level of embedding, it is
|
||||
// excluded. An anonymous field that is not of struct type is treated as having
|
||||
// its type as its name.
|
||||
//
|
||||
// Tags modify these rules as follows:
|
||||
// A field's tag is used as its name.
|
||||
// An anonymous struct field with a name given in its tag is treated as
|
||||
// a field having that name, rather than an embedded struct (the struct's
|
||||
// fields will not be returned).
|
||||
// If more than one field with the same name exists at the same level of embedding,
|
||||
// but exactly one of them is tagged, then the tagged field is reported and the others
|
||||
// are ignored.
|
||||
func (c *Cache) Fields(t reflect.Type) (List, error) {
|
||||
if t.Kind() != reflect.Struct {
|
||||
panic("fields: Fields of non-struct type")
|
||||
}
|
||||
return c.cachedTypeFields(t)
|
||||
}
|
||||
|
||||
// A List is a list of Fields.
|
||||
type List []Field
|
||||
|
||||
// Match returns the field in the list whose name best matches the supplied
|
||||
// name, nor nil if no field does. If there is a field with the exact name, it
|
||||
// is returned. Otherwise the first field (sorted by index) whose name matches
|
||||
// case-insensitively is returned.
|
||||
func (l List) Match(name string) *Field {
|
||||
return l.MatchBytes([]byte(name))
|
||||
}
|
||||
|
||||
// MatchBytes is identical to Match, except that the argument is a byte slice.
|
||||
func (l List) MatchBytes(name []byte) *Field {
|
||||
var f *Field
|
||||
for i := range l {
|
||||
ff := &l[i]
|
||||
if bytes.Equal(ff.nameBytes, name) {
|
||||
return ff
|
||||
}
|
||||
if f == nil && ff.equalFold(ff.nameBytes, name) {
|
||||
f = ff
|
||||
}
|
||||
}
|
||||
return f
|
||||
}
|
||||
|
||||
type cacheValue struct {
|
||||
fields List
|
||||
err error
|
||||
}
|
||||
|
||||
// cachedTypeFields is like typeFields but uses a cache to avoid repeated work.
|
||||
// This code has been copied and modified from
|
||||
// https://go.googlesource.com/go/+/go1.7.3/src/encoding/json/encode.go.
|
||||
func (c *Cache) cachedTypeFields(t reflect.Type) (List, error) {
|
||||
var cv cacheValue
|
||||
x, ok := c.cache.Load(t)
|
||||
if ok {
|
||||
cv = x.(cacheValue)
|
||||
} else {
|
||||
if err := c.validate(t); err != nil {
|
||||
cv = cacheValue{nil, err}
|
||||
} else {
|
||||
f, err := c.typeFields(t)
|
||||
cv = cacheValue{List(f), err}
|
||||
}
|
||||
c.cache.Store(t, cv)
|
||||
}
|
||||
return cv.fields, cv.err
|
||||
}
|
||||
|
||||
func (c *Cache) typeFields(t reflect.Type) ([]Field, error) {
|
||||
fields, err := c.listFields(t)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
sort.Sort(byName(fields))
|
||||
// Delete all fields that are hidden by the Go rules for embedded fields.
|
||||
|
||||
// The fields are sorted in primary order of name, secondary order of field
|
||||
// index length. So the first field with a given name is the dominant one.
|
||||
var out []Field
|
||||
for advance, i := 0, 0; i < len(fields); i += advance {
|
||||
// One iteration per name.
|
||||
// Find the sequence of fields with the name of this first field.
|
||||
fi := fields[i]
|
||||
name := fi.Name
|
||||
for advance = 1; i+advance < len(fields); advance++ {
|
||||
fj := fields[i+advance]
|
||||
if fj.Name != name {
|
||||
break
|
||||
}
|
||||
}
|
||||
// Find the dominant field, if any, out of all fields that have the same name.
|
||||
dominant, ok := dominantField(fields[i : i+advance])
|
||||
if ok {
|
||||
out = append(out, dominant)
|
||||
}
|
||||
}
|
||||
sort.Sort(byIndex(out))
|
||||
return out, nil
|
||||
}
|
||||
|
||||
func (c *Cache) listFields(t reflect.Type) ([]Field, error) {
|
||||
// This uses the same condition that the Go language does: there must be a unique instance
|
||||
// of the match at a given depth level. If there are multiple instances of a match at the
|
||||
// same depth, they annihilate each other and inhibit any possible match at a lower level.
|
||||
// The algorithm is breadth first search, one depth level at a time.
|
||||
|
||||
// The current and next slices are work queues:
|
||||
// current lists the fields to visit on this depth level,
|
||||
// and next lists the fields on the next lower level.
|
||||
current := []fieldScan{}
|
||||
next := []fieldScan{{typ: t}}
|
||||
|
||||
// nextCount records the number of times an embedded type has been
|
||||
// encountered and considered for queueing in the 'next' slice.
|
||||
// We only queue the first one, but we increment the count on each.
|
||||
// If a struct type T can be reached more than once at a given depth level,
|
||||
// then it annihilates itself and need not be considered at all when we
|
||||
// process that next depth level.
|
||||
var nextCount map[reflect.Type]int
|
||||
|
||||
// visited records the structs that have been considered already.
|
||||
// Embedded pointer fields can create cycles in the graph of
|
||||
// reachable embedded types; visited avoids following those cycles.
|
||||
// It also avoids duplicated effort: if we didn't find the field in an
|
||||
// embedded type T at level 2, we won't find it in one at level 4 either.
|
||||
visited := map[reflect.Type]bool{}
|
||||
|
||||
var fields []Field // Fields found.
|
||||
|
||||
for len(next) > 0 {
|
||||
current, next = next, current[:0]
|
||||
count := nextCount
|
||||
nextCount = nil
|
||||
|
||||
// Process all the fields at this depth, now listed in 'current'.
|
||||
// The loop queues embedded fields found in 'next', for processing during the next
|
||||
// iteration. The multiplicity of the 'current' field counts is recorded
|
||||
// in 'count'; the multiplicity of the 'next' field counts is recorded in 'nextCount'.
|
||||
for _, scan := range current {
|
||||
t := scan.typ
|
||||
if visited[t] {
|
||||
// We've looked through this type before, at a higher level.
|
||||
// That higher level would shadow the lower level we're now at,
|
||||
// so this one can't be useful to us. Ignore it.
|
||||
continue
|
||||
}
|
||||
visited[t] = true
|
||||
for i := 0; i < t.NumField(); i++ {
|
||||
f := t.Field(i)
|
||||
|
||||
exported := (f.PkgPath == "")
|
||||
|
||||
// If a named field is unexported, ignore it. An anonymous
|
||||
// unexported field is processed, because it may contain
|
||||
// exported fields, which are visible.
|
||||
if !exported && !f.Anonymous {
|
||||
continue
|
||||
}
|
||||
|
||||
// Examine the tag.
|
||||
tagName, keep, other, err := c.parseTag(f.Tag)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if !keep {
|
||||
continue
|
||||
}
|
||||
if c.leafTypes(f.Type) {
|
||||
fields = append(fields, newField(f, tagName, other, scan.index, i))
|
||||
continue
|
||||
}
|
||||
|
||||
var ntyp reflect.Type
|
||||
if f.Anonymous {
|
||||
// Anonymous field of type T or *T.
|
||||
ntyp = f.Type
|
||||
if ntyp.Kind() == reflect.Ptr {
|
||||
ntyp = ntyp.Elem()
|
||||
}
|
||||
}
|
||||
|
||||
// Record fields with a tag name, non-anonymous fields, or
|
||||
// anonymous non-struct fields.
|
||||
if tagName != "" || ntyp == nil || ntyp.Kind() != reflect.Struct {
|
||||
if !exported {
|
||||
continue
|
||||
}
|
||||
fields = append(fields, newField(f, tagName, other, scan.index, i))
|
||||
if count[t] > 1 {
|
||||
// If there were multiple instances, add a second,
|
||||
// so that the annihilation code will see a duplicate.
|
||||
fields = append(fields, fields[len(fields)-1])
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
// Queue embedded struct fields for processing with next level,
|
||||
// but only if the embedded types haven't already been queued.
|
||||
if nextCount[ntyp] > 0 {
|
||||
nextCount[ntyp] = 2 // exact multiple doesn't matter
|
||||
continue
|
||||
}
|
||||
if nextCount == nil {
|
||||
nextCount = map[reflect.Type]int{}
|
||||
}
|
||||
nextCount[ntyp] = 1
|
||||
if count[t] > 1 {
|
||||
nextCount[ntyp] = 2 // exact multiple doesn't matter
|
||||
}
|
||||
var index []int
|
||||
index = append(index, scan.index...)
|
||||
index = append(index, i)
|
||||
next = append(next, fieldScan{ntyp, index})
|
||||
}
|
||||
}
|
||||
}
|
||||
return fields, nil
|
||||
}
|
||||
|
||||
func newField(f reflect.StructField, tagName string, other interface{}, index []int, i int) Field {
|
||||
name := tagName
|
||||
if name == "" {
|
||||
name = f.Name
|
||||
}
|
||||
sf := Field{
|
||||
Name: name,
|
||||
NameFromTag: tagName != "",
|
||||
Type: f.Type,
|
||||
ParsedTag: other,
|
||||
nameBytes: []byte(name),
|
||||
}
|
||||
sf.equalFold = foldFunc(sf.nameBytes)
|
||||
sf.Index = append(sf.Index, index...)
|
||||
sf.Index = append(sf.Index, i)
|
||||
return sf
|
||||
}
|
||||
|
||||
// byName sorts fields using the following criteria, in order:
|
||||
// 1. name
|
||||
// 2. embedding depth
|
||||
// 3. tag presence (preferring a tagged field)
|
||||
// 4. index sequence.
|
||||
type byName []Field
|
||||
|
||||
func (x byName) Len() int { return len(x) }
|
||||
|
||||
func (x byName) Swap(i, j int) { x[i], x[j] = x[j], x[i] }
|
||||
|
||||
func (x byName) Less(i, j int) bool {
|
||||
if x[i].Name != x[j].Name {
|
||||
return x[i].Name < x[j].Name
|
||||
}
|
||||
if len(x[i].Index) != len(x[j].Index) {
|
||||
return len(x[i].Index) < len(x[j].Index)
|
||||
}
|
||||
if x[i].NameFromTag != x[j].NameFromTag {
|
||||
return x[i].NameFromTag
|
||||
}
|
||||
return byIndex(x).Less(i, j)
|
||||
}
|
||||
|
||||
// byIndex sorts field by index sequence.
|
||||
type byIndex []Field
|
||||
|
||||
func (x byIndex) Len() int { return len(x) }
|
||||
|
||||
func (x byIndex) Swap(i, j int) { x[i], x[j] = x[j], x[i] }
|
||||
|
||||
func (x byIndex) Less(i, j int) bool {
|
||||
xi := x[i].Index
|
||||
xj := x[j].Index
|
||||
ln := len(xi)
|
||||
if l := len(xj); l < ln {
|
||||
ln = l
|
||||
}
|
||||
for k := 0; k < ln; k++ {
|
||||
if xi[k] != xj[k] {
|
||||
return xi[k] < xj[k]
|
||||
}
|
||||
}
|
||||
return len(xi) < len(xj)
|
||||
}
|
||||
|
||||
// dominantField looks through the fields, all of which are known to have the
|
||||
// same name, to find the single field that dominates the others using Go's
|
||||
// embedding rules, modified by the presence of tags. If there are multiple
|
||||
// top-level fields, the boolean will be false: This condition is an error in
|
||||
// Go and we skip all the fields.
|
||||
func dominantField(fs []Field) (Field, bool) {
|
||||
// The fields are sorted in increasing index-length order, then by presence of tag.
|
||||
// That means that the first field is the dominant one. We need only check
|
||||
// for error cases: two fields at top level, either both tagged or neither tagged.
|
||||
if len(fs) > 1 && len(fs[0].Index) == len(fs[1].Index) && fs[0].NameFromTag == fs[1].NameFromTag {
|
||||
return Field{}, false
|
||||
}
|
||||
return fs[0], true
|
||||
}
|
||||
|
||||
// ParseStandardTag extracts the sub-tag named by key, then parses it using the
|
||||
// de facto standard format introduced in encoding/json:
|
||||
// "-" means "ignore this tag". It must occur by itself. (parseStandardTag returns an error
|
||||
// in this case, whereas encoding/json accepts the "-" even if it is not alone.)
|
||||
// "<name>" provides an alternative name for the field
|
||||
// "<name>,opt1,opt2,..." specifies options after the name.
|
||||
// The options are returned as a []string.
|
||||
func ParseStandardTag(key string, t reflect.StructTag) (name string, keep bool, options []string, err error) {
|
||||
s := t.Get(key)
|
||||
parts := strings.Split(s, ",")
|
||||
if parts[0] == "-" {
|
||||
if len(parts) > 1 {
|
||||
return "", false, nil, errors.New(`"-" field tag with options`)
|
||||
}
|
||||
return "", false, nil, nil
|
||||
}
|
||||
if len(parts) > 1 {
|
||||
options = parts[1:]
|
||||
}
|
||||
return parts[0], true, options, nil
|
||||
}
|
||||
-156
@@ -1,156 +0,0 @@
|
||||
// Copyright 2016 Google LLC
|
||||
//
|
||||
// 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 fields
|
||||
|
||||
// This file was copied from https://go.googlesource.com/go/+/go1.7.3/src/encoding/json/fold.go.
|
||||
// Only the license and package were changed.
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"unicode/utf8"
|
||||
)
|
||||
|
||||
const (
|
||||
caseMask = ^byte(0x20) // Mask to ignore case in ASCII.
|
||||
kelvin = '\u212a'
|
||||
smallLongEss = '\u017f'
|
||||
)
|
||||
|
||||
// foldFunc returns one of four different case folding equivalence
|
||||
// functions, from most general (and slow) to fastest:
|
||||
//
|
||||
// 1) bytes.EqualFold, if the key s contains any non-ASCII UTF-8
|
||||
// 2) equalFoldRight, if s contains special folding ASCII ('k', 'K', 's', 'S')
|
||||
// 3) asciiEqualFold, no special, but includes non-letters (including _)
|
||||
// 4) simpleLetterEqualFold, no specials, no non-letters.
|
||||
//
|
||||
// The letters S and K are special because they map to 3 runes, not just 2:
|
||||
// * S maps to s and to U+017F 'ſ' Latin small letter long s
|
||||
// * k maps to K and to U+212A 'K' Kelvin sign
|
||||
// See https://play.golang.org/p/tTxjOc0OGo
|
||||
//
|
||||
// The returned function is specialized for matching against s and
|
||||
// should only be given s. It's not curried for performance reasons.
|
||||
func foldFunc(s []byte) func(s, t []byte) bool {
|
||||
nonLetter := false
|
||||
special := false // special letter
|
||||
for _, b := range s {
|
||||
if b >= utf8.RuneSelf {
|
||||
return bytes.EqualFold
|
||||
}
|
||||
upper := b & caseMask
|
||||
if upper < 'A' || upper > 'Z' {
|
||||
nonLetter = true
|
||||
} else if upper == 'K' || upper == 'S' {
|
||||
// See above for why these letters are special.
|
||||
special = true
|
||||
}
|
||||
}
|
||||
if special {
|
||||
return equalFoldRight
|
||||
}
|
||||
if nonLetter {
|
||||
return asciiEqualFold
|
||||
}
|
||||
return simpleLetterEqualFold
|
||||
}
|
||||
|
||||
// equalFoldRight is a specialization of bytes.EqualFold when s is
|
||||
// known to be all ASCII (including punctuation), but contains an 's',
|
||||
// 'S', 'k', or 'K', requiring a Unicode fold on the bytes in t.
|
||||
// See comments on foldFunc.
|
||||
func equalFoldRight(s, t []byte) bool {
|
||||
for _, sb := range s {
|
||||
if len(t) == 0 {
|
||||
return false
|
||||
}
|
||||
tb := t[0]
|
||||
if tb < utf8.RuneSelf {
|
||||
if sb != tb {
|
||||
sbUpper := sb & caseMask
|
||||
if 'A' <= sbUpper && sbUpper <= 'Z' {
|
||||
if sbUpper != tb&caseMask {
|
||||
return false
|
||||
}
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
}
|
||||
t = t[1:]
|
||||
continue
|
||||
}
|
||||
// sb is ASCII and t is not. t must be either kelvin
|
||||
// sign or long s; sb must be s, S, k, or K.
|
||||
tr, size := utf8.DecodeRune(t)
|
||||
switch sb {
|
||||
case 's', 'S':
|
||||
if tr != smallLongEss {
|
||||
return false
|
||||
}
|
||||
case 'k', 'K':
|
||||
if tr != kelvin {
|
||||
return false
|
||||
}
|
||||
default:
|
||||
return false
|
||||
}
|
||||
t = t[size:]
|
||||
|
||||
}
|
||||
if len(t) > 0 {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
// asciiEqualFold is a specialization of bytes.EqualFold for use when
|
||||
// s is all ASCII (but may contain non-letters) and contains no
|
||||
// special-folding letters.
|
||||
// See comments on foldFunc.
|
||||
func asciiEqualFold(s, t []byte) bool {
|
||||
if len(s) != len(t) {
|
||||
return false
|
||||
}
|
||||
for i, sb := range s {
|
||||
tb := t[i]
|
||||
if sb == tb {
|
||||
continue
|
||||
}
|
||||
if ('a' <= sb && sb <= 'z') || ('A' <= sb && sb <= 'Z') {
|
||||
if sb&caseMask != tb&caseMask {
|
||||
return false
|
||||
}
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
// simpleLetterEqualFold is a specialization of bytes.EqualFold for
|
||||
// use when s is all ASCII letters (no underscores, etc) and also
|
||||
// doesn't contain 'k', 'K', 's', or 'S'.
|
||||
// See comments on foldFunc.
|
||||
func simpleLetterEqualFold(s, t []byte) bool {
|
||||
if len(s) != len(t) {
|
||||
return false
|
||||
}
|
||||
for i, b := range s {
|
||||
if b&caseMask != t[i]&caseMask {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
-108
@@ -1,108 +0,0 @@
|
||||
// Copyright 2016 Google LLC
|
||||
//
|
||||
// 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 optional provides versions of primitive types that can
|
||||
// be nil. These are useful in methods that update some of an API object's
|
||||
// fields.
|
||||
package optional
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
type (
|
||||
// Bool is either a bool or nil.
|
||||
Bool interface{}
|
||||
|
||||
// String is either a string or nil.
|
||||
String interface{}
|
||||
|
||||
// Int is either an int or nil.
|
||||
Int interface{}
|
||||
|
||||
// Uint is either a uint or nil.
|
||||
Uint interface{}
|
||||
|
||||
// Float64 is either a float64 or nil.
|
||||
Float64 interface{}
|
||||
|
||||
// Duration is either a time.Duration or nil.
|
||||
Duration interface{}
|
||||
)
|
||||
|
||||
// ToBool returns its argument as a bool.
|
||||
// It panics if its argument is nil or not a bool.
|
||||
func ToBool(v Bool) bool {
|
||||
x, ok := v.(bool)
|
||||
if !ok {
|
||||
doPanic("Bool", v)
|
||||
}
|
||||
return x
|
||||
}
|
||||
|
||||
// ToString returns its argument as a string.
|
||||
// It panics if its argument is nil or not a string.
|
||||
func ToString(v String) string {
|
||||
x, ok := v.(string)
|
||||
if !ok {
|
||||
doPanic("String", v)
|
||||
}
|
||||
return x
|
||||
}
|
||||
|
||||
// ToInt returns its argument as an int.
|
||||
// It panics if its argument is nil or not an int.
|
||||
func ToInt(v Int) int {
|
||||
x, ok := v.(int)
|
||||
if !ok {
|
||||
doPanic("Int", v)
|
||||
}
|
||||
return x
|
||||
}
|
||||
|
||||
// ToUint returns its argument as a uint.
|
||||
// It panics if its argument is nil or not a uint.
|
||||
func ToUint(v Uint) uint {
|
||||
x, ok := v.(uint)
|
||||
if !ok {
|
||||
doPanic("Uint", v)
|
||||
}
|
||||
return x
|
||||
}
|
||||
|
||||
// ToFloat64 returns its argument as a float64.
|
||||
// It panics if its argument is nil or not a float64.
|
||||
func ToFloat64(v Float64) float64 {
|
||||
x, ok := v.(float64)
|
||||
if !ok {
|
||||
doPanic("Float64", v)
|
||||
}
|
||||
return x
|
||||
}
|
||||
|
||||
// ToDuration returns its argument as a time.Duration.
|
||||
// It panics if its argument is nil or not a time.Duration.
|
||||
func ToDuration(v Duration) time.Duration {
|
||||
x, ok := v.(time.Duration)
|
||||
if !ok {
|
||||
doPanic("Duration", v)
|
||||
}
|
||||
return x
|
||||
}
|
||||
|
||||
func doPanic(capType string, v interface{}) {
|
||||
panic(fmt.Sprintf("optional.%s value should be %s, got %T", capType, strings.ToLower(capType), v))
|
||||
}
|
||||
-54
@@ -1,54 +0,0 @@
|
||||
// Copyright 2016 Google LLC
|
||||
//
|
||||
// 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 internal
|
||||
|
||||
import (
|
||||
"context"
|
||||
"time"
|
||||
|
||||
gax "github.com/googleapis/gax-go/v2"
|
||||
)
|
||||
|
||||
// Retry calls the supplied function f repeatedly according to the provided
|
||||
// backoff parameters. It returns when one of the following occurs:
|
||||
// When f's first return value is true, Retry immediately returns with f's second
|
||||
// return value.
|
||||
// When the provided context is done, Retry returns with an error that
|
||||
// includes both ctx.Error() and the last error returned by f.
|
||||
func Retry(ctx context.Context, bo gax.Backoff, f func() (stop bool, err error)) error {
|
||||
return retry(ctx, bo, f, gax.Sleep)
|
||||
}
|
||||
|
||||
func retry(ctx context.Context, bo gax.Backoff, f func() (stop bool, err error),
|
||||
sleep func(context.Context, time.Duration) error) error {
|
||||
var lastErr error
|
||||
for {
|
||||
stop, err := f()
|
||||
if stop {
|
||||
return err
|
||||
}
|
||||
// Remember the last "real" error from f.
|
||||
if err != nil && err != context.Canceled && err != context.DeadlineExceeded {
|
||||
lastErr = err
|
||||
}
|
||||
p := bo.Pause()
|
||||
if cerr := sleep(ctx, p); cerr != nil {
|
||||
if lastErr != nil {
|
||||
return Annotatef(lastErr, "retry failed with %v; last error", cerr)
|
||||
}
|
||||
return cerr
|
||||
}
|
||||
}
|
||||
}
|
||||
-109
@@ -1,109 +0,0 @@
|
||||
// Copyright 2018 Google LLC
|
||||
//
|
||||
// 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 trace
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"go.opencensus.io/trace"
|
||||
"google.golang.org/api/googleapi"
|
||||
"google.golang.org/genproto/googleapis/rpc/code"
|
||||
"google.golang.org/grpc/status"
|
||||
)
|
||||
|
||||
// StartSpan adds a span to the trace with the given name.
|
||||
func StartSpan(ctx context.Context, name string) context.Context {
|
||||
ctx, _ = trace.StartSpan(ctx, name)
|
||||
return ctx
|
||||
}
|
||||
|
||||
// EndSpan ends a span with the given error.
|
||||
func EndSpan(ctx context.Context, err error) {
|
||||
span := trace.FromContext(ctx)
|
||||
if err != nil {
|
||||
span.SetStatus(toStatus(err))
|
||||
}
|
||||
span.End()
|
||||
}
|
||||
|
||||
// toStatus interrogates an error and converts it to an appropriate
|
||||
// OpenCensus status.
|
||||
func toStatus(err error) trace.Status {
|
||||
if err2, ok := err.(*googleapi.Error); ok {
|
||||
return trace.Status{Code: httpStatusCodeToOCCode(err2.Code), Message: err2.Message}
|
||||
} else if s, ok := status.FromError(err); ok {
|
||||
return trace.Status{Code: int32(s.Code()), Message: s.Message()}
|
||||
} else {
|
||||
return trace.Status{Code: int32(code.Code_UNKNOWN), Message: err.Error()}
|
||||
}
|
||||
}
|
||||
|
||||
// TODO(deklerk): switch to using OpenCensus function when it becomes available.
|
||||
// Reference: https://github.com/googleapis/googleapis/blob/26b634d2724ac5dd30ae0b0cbfb01f07f2e4050e/google/rpc/code.proto
|
||||
func httpStatusCodeToOCCode(httpStatusCode int) int32 {
|
||||
switch httpStatusCode {
|
||||
case 200:
|
||||
return int32(code.Code_OK)
|
||||
case 499:
|
||||
return int32(code.Code_CANCELLED)
|
||||
case 500:
|
||||
return int32(code.Code_UNKNOWN) // Could also be Code_INTERNAL, Code_DATA_LOSS
|
||||
case 400:
|
||||
return int32(code.Code_INVALID_ARGUMENT) // Could also be Code_OUT_OF_RANGE
|
||||
case 504:
|
||||
return int32(code.Code_DEADLINE_EXCEEDED)
|
||||
case 404:
|
||||
return int32(code.Code_NOT_FOUND)
|
||||
case 409:
|
||||
return int32(code.Code_ALREADY_EXISTS) // Could also be Code_ABORTED
|
||||
case 403:
|
||||
return int32(code.Code_PERMISSION_DENIED)
|
||||
case 401:
|
||||
return int32(code.Code_UNAUTHENTICATED)
|
||||
case 429:
|
||||
return int32(code.Code_RESOURCE_EXHAUSTED)
|
||||
case 501:
|
||||
return int32(code.Code_UNIMPLEMENTED)
|
||||
case 503:
|
||||
return int32(code.Code_UNAVAILABLE)
|
||||
default:
|
||||
return int32(code.Code_UNKNOWN)
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: (odeke-em): perhaps just pass around spans due to the cost
|
||||
// incurred from using trace.FromContext(ctx) yet we could avoid
|
||||
// throwing away the work done by ctx, span := trace.StartSpan.
|
||||
func TracePrintf(ctx context.Context, attrMap map[string]interface{}, format string, args ...interface{}) {
|
||||
var attrs []trace.Attribute
|
||||
for k, v := range attrMap {
|
||||
var a trace.Attribute
|
||||
switch v := v.(type) {
|
||||
case string:
|
||||
a = trace.StringAttribute(k, v)
|
||||
case bool:
|
||||
a = trace.BoolAttribute(k, v)
|
||||
case int:
|
||||
a = trace.Int64Attribute(k, int64(v))
|
||||
case int64:
|
||||
a = trace.Int64Attribute(k, v)
|
||||
default:
|
||||
a = trace.StringAttribute(k, fmt.Sprintf("%#v", v))
|
||||
}
|
||||
attrs = append(attrs, a)
|
||||
}
|
||||
trace.FromContext(ctx).Annotatef(attrs, format, args...)
|
||||
}
|
||||
-19
@@ -1,19 +0,0 @@
|
||||
#!/bin/bash
|
||||
# Copyright 2019 Google LLC
|
||||
#
|
||||
# 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.
|
||||
|
||||
today=$(date +%Y%m%d)
|
||||
|
||||
sed -i -r -e 's/const Repo = "([0-9]{8})"/const Repo = "'$today'"/' $GOFILE
|
||||
|
||||
-71
@@ -1,71 +0,0 @@
|
||||
// Copyright 2016 Google LLC
|
||||
//
|
||||
// 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.
|
||||
|
||||
//go:generate ./update_version.sh
|
||||
|
||||
// Package version contains version information for Google Cloud Client
|
||||
// Libraries for Go, as reported in request headers.
|
||||
package version
|
||||
|
||||
import (
|
||||
"runtime"
|
||||
"strings"
|
||||
"unicode"
|
||||
)
|
||||
|
||||
// Repo is the current version of the client libraries in this
|
||||
// repo. It should be a date in YYYYMMDD format.
|
||||
const Repo = "20200618"
|
||||
|
||||
// Go returns the Go runtime version. The returned string
|
||||
// has no whitespace.
|
||||
func Go() string {
|
||||
return goVersion
|
||||
}
|
||||
|
||||
var goVersion = goVer(runtime.Version())
|
||||
|
||||
const develPrefix = "devel +"
|
||||
|
||||
func goVer(s string) string {
|
||||
if strings.HasPrefix(s, develPrefix) {
|
||||
s = s[len(develPrefix):]
|
||||
if p := strings.IndexFunc(s, unicode.IsSpace); p >= 0 {
|
||||
s = s[:p]
|
||||
}
|
||||
return s
|
||||
}
|
||||
|
||||
if strings.HasPrefix(s, "go1") {
|
||||
s = s[2:]
|
||||
var prerelease string
|
||||
if p := strings.IndexFunc(s, notSemverRune); p >= 0 {
|
||||
s, prerelease = s[:p], s[p:]
|
||||
}
|
||||
if strings.HasSuffix(s, ".") {
|
||||
s += "0"
|
||||
} else if strings.Count(s, ".") < 2 {
|
||||
s += ".0"
|
||||
}
|
||||
if prerelease != "" {
|
||||
s += "-" + prerelease
|
||||
}
|
||||
return s
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func notSemverRune(r rune) bool {
|
||||
return !strings.ContainsRune("0123456789.", r)
|
||||
}
|
||||
-102
@@ -1,102 +0,0 @@
|
||||
// Copyright 2020 Google LLC
|
||||
//
|
||||
// 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
|
||||
//
|
||||
// https://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.
|
||||
|
||||
// Code generated by protoc-gen-go_gapic. DO NOT EDIT.
|
||||
|
||||
// Package longrunning is an auto-generated package for the
|
||||
// Long Running Operations API.
|
||||
//
|
||||
// NOTE: This package is in alpha. It is not stable, and is likely to change.
|
||||
//
|
||||
// Use of Context
|
||||
//
|
||||
// The ctx passed to NewClient is used for authentication requests and
|
||||
// for creating the underlying connection, but is not used for subsequent calls.
|
||||
// Individual methods on the client use the ctx given to them.
|
||||
//
|
||||
// To close the open connection, use the Close() method.
|
||||
//
|
||||
// For information about setting deadlines, reusing contexts, and more
|
||||
// please visit godoc.org/cloud.google.com/go.
|
||||
package longrunning // import "cloud.google.com/go/longrunning/autogen"
|
||||
|
||||
import (
|
||||
"context"
|
||||
"runtime"
|
||||
"strings"
|
||||
"unicode"
|
||||
|
||||
"google.golang.org/api/option"
|
||||
"google.golang.org/grpc/metadata"
|
||||
)
|
||||
|
||||
// For more information on implementing a client constructor hook, see
|
||||
// https://github.com/googleapis/google-cloud-go/wiki/Customizing-constructors.
|
||||
type clientHookParams struct{}
|
||||
type clientHook func(context.Context, clientHookParams) ([]option.ClientOption, error)
|
||||
|
||||
const versionClient = "20200629"
|
||||
|
||||
func insertMetadata(ctx context.Context, mds ...metadata.MD) context.Context {
|
||||
out, _ := metadata.FromOutgoingContext(ctx)
|
||||
out = out.Copy()
|
||||
for _, md := range mds {
|
||||
for k, v := range md {
|
||||
out[k] = append(out[k], v...)
|
||||
}
|
||||
}
|
||||
return metadata.NewOutgoingContext(ctx, out)
|
||||
}
|
||||
|
||||
// DefaultAuthScopes reports the default set of authentication scopes to use with this package.
|
||||
func DefaultAuthScopes() []string {
|
||||
return []string{}
|
||||
}
|
||||
|
||||
// versionGo returns the Go runtime version. The returned string
|
||||
// has no whitespace, suitable for reporting in header.
|
||||
func versionGo() string {
|
||||
const develPrefix = "devel +"
|
||||
|
||||
s := runtime.Version()
|
||||
if strings.HasPrefix(s, develPrefix) {
|
||||
s = s[len(develPrefix):]
|
||||
if p := strings.IndexFunc(s, unicode.IsSpace); p >= 0 {
|
||||
s = s[:p]
|
||||
}
|
||||
return s
|
||||
}
|
||||
|
||||
notSemverRune := func(r rune) bool {
|
||||
return !strings.ContainsRune("0123456789.", r)
|
||||
}
|
||||
|
||||
if strings.HasPrefix(s, "go1") {
|
||||
s = s[2:]
|
||||
var prerelease string
|
||||
if p := strings.IndexFunc(s, notSemverRune); p >= 0 {
|
||||
s, prerelease = s[:p], s[p:]
|
||||
}
|
||||
if strings.HasSuffix(s, ".") {
|
||||
s += "0"
|
||||
} else if strings.Count(s, ".") < 2 {
|
||||
s += ".0"
|
||||
}
|
||||
if prerelease != "" {
|
||||
s += "-" + prerelease
|
||||
}
|
||||
return s
|
||||
}
|
||||
return "UNKNOWN"
|
||||
}
|
||||
-30
@@ -1,30 +0,0 @@
|
||||
// Copyright 2020, Google LLC
|
||||
//
|
||||
// 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 longrunning
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"google.golang.org/api/option"
|
||||
"google.golang.org/grpc"
|
||||
)
|
||||
|
||||
// InternalFromConn is for use by the Google Cloud Libraries only.
|
||||
//
|
||||
// Deprecated. Use `NewOperationsClient(ctx, option.WithGRPCConn(conn))` instead.
|
||||
func InternalFromConn(conn *grpc.ClientConn) *OperationsClient {
|
||||
c, _ := NewOperationsClient(context.Background(), option.WithGRPCConn(conn))
|
||||
return c
|
||||
}
|
||||
-24
@@ -1,24 +0,0 @@
|
||||
// Copyright 2020 Google LLC
|
||||
//
|
||||
// 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 longrunning
|
||||
|
||||
// SetGoogleClientInfo sets the name and version of the application in
|
||||
// the `x-goog-api-client` header passed on each request. Also passes any
|
||||
// provided key-value pairs. Intended for use by Google-written clients.
|
||||
//
|
||||
// Internal use only.
|
||||
func (c *OperationsClient) SetGoogleClientInfo(keyval ...string) {
|
||||
c.setGoogleClientInfo(keyval...)
|
||||
}
|
||||
-360
@@ -1,360 +0,0 @@
|
||||
// Copyright 2020 Google LLC
|
||||
//
|
||||
// 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
|
||||
//
|
||||
// https://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.
|
||||
|
||||
// Code generated by protoc-gen-go_gapic. DO NOT EDIT.
|
||||
|
||||
package longrunning
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"math"
|
||||
"net/url"
|
||||
"time"
|
||||
|
||||
"github.com/golang/protobuf/proto"
|
||||
gax "github.com/googleapis/gax-go/v2"
|
||||
"google.golang.org/api/iterator"
|
||||
"google.golang.org/api/option"
|
||||
gtransport "google.golang.org/api/transport/grpc"
|
||||
longrunningpb "google.golang.org/genproto/googleapis/longrunning"
|
||||
"google.golang.org/grpc"
|
||||
"google.golang.org/grpc/codes"
|
||||
"google.golang.org/grpc/metadata"
|
||||
)
|
||||
|
||||
var newOperationsClientHook clientHook
|
||||
|
||||
// OperationsCallOptions contains the retry settings for each method of OperationsClient.
|
||||
type OperationsCallOptions struct {
|
||||
ListOperations []gax.CallOption
|
||||
GetOperation []gax.CallOption
|
||||
DeleteOperation []gax.CallOption
|
||||
CancelOperation []gax.CallOption
|
||||
WaitOperation []gax.CallOption
|
||||
}
|
||||
|
||||
func defaultOperationsClientOptions() []option.ClientOption {
|
||||
return []option.ClientOption{
|
||||
option.WithEndpoint("longrunning.googleapis.com:443"),
|
||||
option.WithGRPCDialOption(grpc.WithDisableServiceConfig()),
|
||||
option.WithScopes(DefaultAuthScopes()...),
|
||||
option.WithGRPCDialOption(grpc.WithDefaultCallOptions(
|
||||
grpc.MaxCallRecvMsgSize(math.MaxInt32))),
|
||||
}
|
||||
}
|
||||
|
||||
func defaultOperationsCallOptions() *OperationsCallOptions {
|
||||
return &OperationsCallOptions{
|
||||
ListOperations: []gax.CallOption{
|
||||
gax.WithRetry(func() gax.Retryer {
|
||||
return gax.OnCodes([]codes.Code{
|
||||
codes.Unavailable,
|
||||
}, gax.Backoff{
|
||||
Initial: 500 * time.Millisecond,
|
||||
Max: 10000 * time.Millisecond,
|
||||
Multiplier: 2.00,
|
||||
})
|
||||
}),
|
||||
},
|
||||
GetOperation: []gax.CallOption{
|
||||
gax.WithRetry(func() gax.Retryer {
|
||||
return gax.OnCodes([]codes.Code{
|
||||
codes.Unavailable,
|
||||
}, gax.Backoff{
|
||||
Initial: 500 * time.Millisecond,
|
||||
Max: 10000 * time.Millisecond,
|
||||
Multiplier: 2.00,
|
||||
})
|
||||
}),
|
||||
},
|
||||
DeleteOperation: []gax.CallOption{
|
||||
gax.WithRetry(func() gax.Retryer {
|
||||
return gax.OnCodes([]codes.Code{
|
||||
codes.Unavailable,
|
||||
}, gax.Backoff{
|
||||
Initial: 500 * time.Millisecond,
|
||||
Max: 10000 * time.Millisecond,
|
||||
Multiplier: 2.00,
|
||||
})
|
||||
}),
|
||||
},
|
||||
CancelOperation: []gax.CallOption{
|
||||
gax.WithRetry(func() gax.Retryer {
|
||||
return gax.OnCodes([]codes.Code{
|
||||
codes.Unavailable,
|
||||
}, gax.Backoff{
|
||||
Initial: 500 * time.Millisecond,
|
||||
Max: 10000 * time.Millisecond,
|
||||
Multiplier: 2.00,
|
||||
})
|
||||
}),
|
||||
},
|
||||
WaitOperation: []gax.CallOption{},
|
||||
}
|
||||
}
|
||||
|
||||
// OperationsClient is a client for interacting with Long Running Operations API.
|
||||
//
|
||||
// Methods, except Close, may be called concurrently. However, fields must not be modified concurrently with method calls.
|
||||
type OperationsClient struct {
|
||||
// Connection pool of gRPC connections to the service.
|
||||
connPool gtransport.ConnPool
|
||||
|
||||
// The gRPC API client.
|
||||
operationsClient longrunningpb.OperationsClient
|
||||
|
||||
// The call options for this service.
|
||||
CallOptions *OperationsCallOptions
|
||||
|
||||
// The x-goog-* metadata to be sent with each request.
|
||||
xGoogMetadata metadata.MD
|
||||
}
|
||||
|
||||
// NewOperationsClient creates a new operations client.
|
||||
//
|
||||
// Manages long-running operations with an API service.
|
||||
//
|
||||
// When an API method normally takes long time to complete, it can be designed
|
||||
// to return Operation to the client, and the client can use this
|
||||
// interface to receive the real response asynchronously by polling the
|
||||
// operation resource, or pass the operation resource to another API (such as
|
||||
// Google Cloud Pub/Sub API) to receive the response. Any API service that
|
||||
// returns long-running operations should implement the Operations interface
|
||||
// so developers can have a consistent client experience.
|
||||
func NewOperationsClient(ctx context.Context, opts ...option.ClientOption) (*OperationsClient, error) {
|
||||
clientOpts := defaultOperationsClientOptions()
|
||||
|
||||
if newOperationsClientHook != nil {
|
||||
hookOpts, err := newOperationsClientHook(ctx, clientHookParams{})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
clientOpts = append(clientOpts, hookOpts...)
|
||||
}
|
||||
|
||||
connPool, err := gtransport.DialPool(ctx, append(clientOpts, opts...)...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
c := &OperationsClient{
|
||||
connPool: connPool,
|
||||
CallOptions: defaultOperationsCallOptions(),
|
||||
|
||||
operationsClient: longrunningpb.NewOperationsClient(connPool),
|
||||
}
|
||||
c.setGoogleClientInfo()
|
||||
|
||||
return c, nil
|
||||
}
|
||||
|
||||
// Connection returns a connection to the API service.
|
||||
//
|
||||
// Deprecated.
|
||||
func (c *OperationsClient) Connection() *grpc.ClientConn {
|
||||
return c.connPool.Conn()
|
||||
}
|
||||
|
||||
// Close closes the connection to the API service. The user should invoke this when
|
||||
// the client is no longer required.
|
||||
func (c *OperationsClient) Close() error {
|
||||
return c.connPool.Close()
|
||||
}
|
||||
|
||||
// setGoogleClientInfo sets the name and version of the application in
|
||||
// the `x-goog-api-client` header passed on each request. Intended for
|
||||
// use by Google-written clients.
|
||||
func (c *OperationsClient) setGoogleClientInfo(keyval ...string) {
|
||||
kv := append([]string{"gl-go", versionGo()}, keyval...)
|
||||
kv = append(kv, "gapic", versionClient, "gax", gax.Version, "grpc", grpc.Version)
|
||||
c.xGoogMetadata = metadata.Pairs("x-goog-api-client", gax.XGoogHeader(kv...))
|
||||
}
|
||||
|
||||
// ListOperations lists operations that match the specified filter in the request. If the
|
||||
// server doesn’t support this method, it returns UNIMPLEMENTED.
|
||||
//
|
||||
// NOTE: the name binding allows API services to override the binding
|
||||
// to use different resource name schemes, such as users/*/operations. To
|
||||
// override the binding, API services can add a binding such as
|
||||
// "/v1/{name=users/*}/operations" to their service configuration.
|
||||
// For backwards compatibility, the default name includes the operations
|
||||
// collection id, however overriding users must ensure the name binding
|
||||
// is the parent resource, without the operations collection id.
|
||||
func (c *OperationsClient) ListOperations(ctx context.Context, req *longrunningpb.ListOperationsRequest, opts ...gax.CallOption) *OperationIterator {
|
||||
md := metadata.Pairs("x-goog-request-params", fmt.Sprintf("%s=%v", "name", url.QueryEscape(req.GetName())))
|
||||
ctx = insertMetadata(ctx, c.xGoogMetadata, md)
|
||||
opts = append(c.CallOptions.ListOperations[0:len(c.CallOptions.ListOperations):len(c.CallOptions.ListOperations)], opts...)
|
||||
it := &OperationIterator{}
|
||||
req = proto.Clone(req).(*longrunningpb.ListOperationsRequest)
|
||||
it.InternalFetch = func(pageSize int, pageToken string) ([]*longrunningpb.Operation, string, error) {
|
||||
var resp *longrunningpb.ListOperationsResponse
|
||||
req.PageToken = pageToken
|
||||
if pageSize > math.MaxInt32 {
|
||||
req.PageSize = math.MaxInt32
|
||||
} else {
|
||||
req.PageSize = int32(pageSize)
|
||||
}
|
||||
err := gax.Invoke(ctx, func(ctx context.Context, settings gax.CallSettings) error {
|
||||
var err error
|
||||
resp, err = c.operationsClient.ListOperations(ctx, req, settings.GRPC...)
|
||||
return err
|
||||
}, opts...)
|
||||
if err != nil {
|
||||
return nil, "", err
|
||||
}
|
||||
|
||||
it.Response = resp
|
||||
return resp.Operations, resp.NextPageToken, nil
|
||||
}
|
||||
fetch := func(pageSize int, pageToken string) (string, error) {
|
||||
items, nextPageToken, err := it.InternalFetch(pageSize, pageToken)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
it.items = append(it.items, items...)
|
||||
return nextPageToken, nil
|
||||
}
|
||||
it.pageInfo, it.nextFunc = iterator.NewPageInfo(fetch, it.bufLen, it.takeBuf)
|
||||
it.pageInfo.MaxSize = int(req.PageSize)
|
||||
it.pageInfo.Token = req.PageToken
|
||||
return it
|
||||
}
|
||||
|
||||
// GetOperation gets the latest state of a long-running operation. Clients can use this
|
||||
// method to poll the operation result at intervals as recommended by the API
|
||||
// service.
|
||||
func (c *OperationsClient) GetOperation(ctx context.Context, req *longrunningpb.GetOperationRequest, opts ...gax.CallOption) (*longrunningpb.Operation, error) {
|
||||
md := metadata.Pairs("x-goog-request-params", fmt.Sprintf("%s=%v", "name", url.QueryEscape(req.GetName())))
|
||||
ctx = insertMetadata(ctx, c.xGoogMetadata, md)
|
||||
opts = append(c.CallOptions.GetOperation[0:len(c.CallOptions.GetOperation):len(c.CallOptions.GetOperation)], opts...)
|
||||
var resp *longrunningpb.Operation
|
||||
err := gax.Invoke(ctx, func(ctx context.Context, settings gax.CallSettings) error {
|
||||
var err error
|
||||
resp, err = c.operationsClient.GetOperation(ctx, req, settings.GRPC...)
|
||||
return err
|
||||
}, opts...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return resp, nil
|
||||
}
|
||||
|
||||
// DeleteOperation deletes a long-running operation. This method indicates that the client is
|
||||
// no longer interested in the operation result. It does not cancel the
|
||||
// operation. If the server doesn’t support this method, it returns
|
||||
// google.rpc.Code.UNIMPLEMENTED.
|
||||
func (c *OperationsClient) DeleteOperation(ctx context.Context, req *longrunningpb.DeleteOperationRequest, opts ...gax.CallOption) error {
|
||||
md := metadata.Pairs("x-goog-request-params", fmt.Sprintf("%s=%v", "name", url.QueryEscape(req.GetName())))
|
||||
ctx = insertMetadata(ctx, c.xGoogMetadata, md)
|
||||
opts = append(c.CallOptions.DeleteOperation[0:len(c.CallOptions.DeleteOperation):len(c.CallOptions.DeleteOperation)], opts...)
|
||||
err := gax.Invoke(ctx, func(ctx context.Context, settings gax.CallSettings) error {
|
||||
var err error
|
||||
_, err = c.operationsClient.DeleteOperation(ctx, req, settings.GRPC...)
|
||||
return err
|
||||
}, opts...)
|
||||
return err
|
||||
}
|
||||
|
||||
// CancelOperation starts asynchronous cancellation on a long-running operation. The server
|
||||
// makes a best effort to cancel the operation, but success is not
|
||||
// guaranteed. If the server doesn’t support this method, it returns
|
||||
// google.rpc.Code.UNIMPLEMENTED. Clients can use
|
||||
// Operations.GetOperation or
|
||||
// other methods to check whether the cancellation succeeded or whether the
|
||||
// operation completed despite cancellation. On successful cancellation,
|
||||
// the operation is not deleted; instead, it becomes an operation with
|
||||
// an Operation.error value with a google.rpc.Status.code of 1,
|
||||
// corresponding to Code.CANCELLED.
|
||||
func (c *OperationsClient) CancelOperation(ctx context.Context, req *longrunningpb.CancelOperationRequest, opts ...gax.CallOption) error {
|
||||
md := metadata.Pairs("x-goog-request-params", fmt.Sprintf("%s=%v", "name", url.QueryEscape(req.GetName())))
|
||||
ctx = insertMetadata(ctx, c.xGoogMetadata, md)
|
||||
opts = append(c.CallOptions.CancelOperation[0:len(c.CallOptions.CancelOperation):len(c.CallOptions.CancelOperation)], opts...)
|
||||
err := gax.Invoke(ctx, func(ctx context.Context, settings gax.CallSettings) error {
|
||||
var err error
|
||||
_, err = c.operationsClient.CancelOperation(ctx, req, settings.GRPC...)
|
||||
return err
|
||||
}, opts...)
|
||||
return err
|
||||
}
|
||||
|
||||
// WaitOperation waits for the specified long-running operation until it is done or reaches
|
||||
// at most a specified timeout, returning the latest state. If the operation
|
||||
// is already done, the latest state is immediately returned. If the timeout
|
||||
// specified is greater than the default HTTP/RPC timeout, the HTTP/RPC
|
||||
// timeout is used. If the server does not support this method, it returns
|
||||
// google.rpc.Code.UNIMPLEMENTED.
|
||||
// Note that this method is on a best-effort basis. It may return the latest
|
||||
// state before the specified timeout (including immediately), meaning even an
|
||||
// immediate response is no guarantee that the operation is done.
|
||||
func (c *OperationsClient) WaitOperation(ctx context.Context, req *longrunningpb.WaitOperationRequest, opts ...gax.CallOption) (*longrunningpb.Operation, error) {
|
||||
ctx = insertMetadata(ctx, c.xGoogMetadata)
|
||||
opts = append(c.CallOptions.WaitOperation[0:len(c.CallOptions.WaitOperation):len(c.CallOptions.WaitOperation)], opts...)
|
||||
var resp *longrunningpb.Operation
|
||||
err := gax.Invoke(ctx, func(ctx context.Context, settings gax.CallSettings) error {
|
||||
var err error
|
||||
resp, err = c.operationsClient.WaitOperation(ctx, req, settings.GRPC...)
|
||||
return err
|
||||
}, opts...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return resp, nil
|
||||
}
|
||||
|
||||
// OperationIterator manages a stream of *longrunningpb.Operation.
|
||||
type OperationIterator struct {
|
||||
items []*longrunningpb.Operation
|
||||
pageInfo *iterator.PageInfo
|
||||
nextFunc func() error
|
||||
|
||||
// Response is the raw response for the current page.
|
||||
// It must be cast to the RPC response type.
|
||||
// Calling Next() or InternalFetch() updates this value.
|
||||
Response interface{}
|
||||
|
||||
// InternalFetch is for use by the Google Cloud Libraries only.
|
||||
// It is not part of the stable interface of this package.
|
||||
//
|
||||
// InternalFetch returns results from a single call to the underlying RPC.
|
||||
// The number of results is no greater than pageSize.
|
||||
// If there are no more results, nextPageToken is empty and err is nil.
|
||||
InternalFetch func(pageSize int, pageToken string) (results []*longrunningpb.Operation, nextPageToken string, err error)
|
||||
}
|
||||
|
||||
// PageInfo supports pagination. See the google.golang.org/api/iterator package for details.
|
||||
func (it *OperationIterator) PageInfo() *iterator.PageInfo {
|
||||
return it.pageInfo
|
||||
}
|
||||
|
||||
// Next returns the next result. Its second return value is iterator.Done if there are no more
|
||||
// results. Once Next returns Done, all subsequent calls will return Done.
|
||||
func (it *OperationIterator) Next() (*longrunningpb.Operation, error) {
|
||||
var item *longrunningpb.Operation
|
||||
if err := it.nextFunc(); err != nil {
|
||||
return item, err
|
||||
}
|
||||
item = it.items[0]
|
||||
it.items = it.items[1:]
|
||||
return item, nil
|
||||
}
|
||||
|
||||
func (it *OperationIterator) bufLen() int {
|
||||
return len(it.items)
|
||||
}
|
||||
|
||||
func (it *OperationIterator) takeBuf() interface{} {
|
||||
b := it.items
|
||||
it.items = nil
|
||||
return b
|
||||
}
|
||||
-179
@@ -1,179 +0,0 @@
|
||||
// Copyright 2016 Google LLC
|
||||
//
|
||||
// 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 longrunning supports Long Running Operations for the Google Cloud Libraries.
|
||||
// See google.golang.org/genproto/googleapis/longrunning for its service definition.
|
||||
//
|
||||
// Users of the Google Cloud Libraries will typically not use this package directly.
|
||||
// Instead they will call functions returning Operations and call their methods.
|
||||
//
|
||||
// This package is still experimental and subject to change.
|
||||
package longrunning // import "cloud.google.com/go/longrunning"
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
autogen "cloud.google.com/go/longrunning/autogen"
|
||||
"github.com/golang/protobuf/proto"
|
||||
"github.com/golang/protobuf/ptypes"
|
||||
gax "github.com/googleapis/gax-go/v2"
|
||||
pb "google.golang.org/genproto/googleapis/longrunning"
|
||||
"google.golang.org/grpc/codes"
|
||||
"google.golang.org/grpc/status"
|
||||
)
|
||||
|
||||
// ErrNoMetadata is the error returned by Metadata if the operation contains no metadata.
|
||||
var ErrNoMetadata = errors.New("operation contains no metadata")
|
||||
|
||||
// Operation represents the result of an API call that may not be ready yet.
|
||||
type Operation struct {
|
||||
c operationsClient
|
||||
proto *pb.Operation
|
||||
}
|
||||
|
||||
type operationsClient interface {
|
||||
GetOperation(context.Context, *pb.GetOperationRequest, ...gax.CallOption) (*pb.Operation, error)
|
||||
CancelOperation(context.Context, *pb.CancelOperationRequest, ...gax.CallOption) error
|
||||
DeleteOperation(context.Context, *pb.DeleteOperationRequest, ...gax.CallOption) error
|
||||
}
|
||||
|
||||
// InternalNewOperation is for use by the google Cloud Libraries only.
|
||||
//
|
||||
// InternalNewOperation returns an long-running operation, abstracting the raw pb.Operation.
|
||||
// The conn parameter refers to a server that proto was received from.
|
||||
func InternalNewOperation(inner *autogen.OperationsClient, proto *pb.Operation) *Operation {
|
||||
return &Operation{
|
||||
c: inner,
|
||||
proto: proto,
|
||||
}
|
||||
}
|
||||
|
||||
// Name returns the name of the long-running operation.
|
||||
// The name is assigned by the server and is unique within the service
|
||||
// from which the operation is created.
|
||||
func (op *Operation) Name() string {
|
||||
return op.proto.Name
|
||||
}
|
||||
|
||||
// Done reports whether the long-running operation has completed.
|
||||
func (op *Operation) Done() bool {
|
||||
return op.proto.Done
|
||||
}
|
||||
|
||||
// Metadata unmarshals op's metadata into meta.
|
||||
// If op does not contain any metadata, Metadata returns ErrNoMetadata and meta is unmodified.
|
||||
func (op *Operation) Metadata(meta proto.Message) error {
|
||||
if m := op.proto.Metadata; m != nil {
|
||||
return ptypes.UnmarshalAny(m, meta)
|
||||
}
|
||||
return ErrNoMetadata
|
||||
}
|
||||
|
||||
// Poll fetches the latest state of a long-running operation.
|
||||
//
|
||||
// If Poll fails, the error is returned and op is unmodified.
|
||||
// If Poll succeeds and the operation has completed with failure,
|
||||
// the error is returned and op.Done will return true.
|
||||
// If Poll succeeds and the operation has completed successfully,
|
||||
// op.Done will return true; if resp != nil, the response of the operation
|
||||
// is stored in resp.
|
||||
func (op *Operation) Poll(ctx context.Context, resp proto.Message, opts ...gax.CallOption) error {
|
||||
if !op.Done() {
|
||||
p, err := op.c.GetOperation(ctx, &pb.GetOperationRequest{Name: op.Name()}, opts...)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
op.proto = p
|
||||
}
|
||||
if !op.Done() {
|
||||
return nil
|
||||
}
|
||||
|
||||
switch r := op.proto.Result.(type) {
|
||||
case *pb.Operation_Error:
|
||||
// TODO(pongad): r.Details may contain further information
|
||||
return status.Errorf(codes.Code(r.Error.Code), "%s", r.Error.Message)
|
||||
case *pb.Operation_Response:
|
||||
if resp == nil {
|
||||
return nil
|
||||
}
|
||||
return ptypes.UnmarshalAny(r.Response, resp)
|
||||
default:
|
||||
return fmt.Errorf("unsupported result type %[1]T: %[1]v", r)
|
||||
}
|
||||
}
|
||||
|
||||
// DefaultWaitInterval is the polling interval used by Operation.Wait.
|
||||
const DefaultWaitInterval = 60 * time.Second
|
||||
|
||||
// Wait is equivalent to WaitWithInterval using DefaultWaitInterval.
|
||||
func (op *Operation) Wait(ctx context.Context, resp proto.Message, opts ...gax.CallOption) error {
|
||||
return op.WaitWithInterval(ctx, resp, DefaultWaitInterval, opts...)
|
||||
}
|
||||
|
||||
// WaitWithInterval blocks until the operation is completed.
|
||||
// If resp != nil, Wait stores the response in resp.
|
||||
// WaitWithInterval polls every interval, except initially
|
||||
// when it polls using exponential backoff.
|
||||
//
|
||||
// See documentation of Poll for error-handling information.
|
||||
func (op *Operation) WaitWithInterval(ctx context.Context, resp proto.Message, interval time.Duration, opts ...gax.CallOption) error {
|
||||
bo := gax.Backoff{
|
||||
Initial: 1 * time.Second,
|
||||
Max: interval,
|
||||
}
|
||||
if bo.Max < bo.Initial {
|
||||
bo.Max = bo.Initial
|
||||
}
|
||||
return op.wait(ctx, resp, &bo, gax.Sleep, opts...)
|
||||
}
|
||||
|
||||
type sleeper func(context.Context, time.Duration) error
|
||||
|
||||
// wait implements Wait, taking exponentialBackoff and sleeper arguments for testing.
|
||||
func (op *Operation) wait(ctx context.Context, resp proto.Message, bo *gax.Backoff, sl sleeper, opts ...gax.CallOption) error {
|
||||
for {
|
||||
if err := op.Poll(ctx, resp, opts...); err != nil {
|
||||
return err
|
||||
}
|
||||
if op.Done() {
|
||||
return nil
|
||||
}
|
||||
if err := sl(ctx, bo.Pause()); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Cancel starts asynchronous cancellation on a long-running operation. The server
|
||||
// makes a best effort to cancel the operation, but success is not
|
||||
// guaranteed. If the server doesn't support this method, it returns
|
||||
// status.Code(err) == codes.Unimplemented. Clients can use
|
||||
// Poll or other methods to check whether the cancellation succeeded or whether the
|
||||
// operation completed despite cancellation. On successful cancellation,
|
||||
// the operation is not deleted; instead, op.Poll returns an error
|
||||
// with code Canceled.
|
||||
func (op *Operation) Cancel(ctx context.Context, opts ...gax.CallOption) error {
|
||||
return op.c.CancelOperation(ctx, &pb.CancelOperationRequest{Name: op.Name()}, opts...)
|
||||
}
|
||||
|
||||
// Delete deletes a long-running operation. This method indicates that the client is
|
||||
// no longer interested in the operation result. It does not cancel the
|
||||
// operation. If the server doesn't support this method, status.Code(err) == codes.Unimplemented.
|
||||
func (op *Operation) Delete(ctx context.Context, opts ...gax.CallOption) error {
|
||||
return op.c.DeleteOperation(ctx, &pb.DeleteOperationRequest{Name: op.Name()}, opts...)
|
||||
}
|
||||
-83
@@ -1,83 +0,0 @@
|
||||
# Changes
|
||||
|
||||
## v1.10.0
|
||||
- Bump dependency on google.golang.org/api to capture changes to retry logic
|
||||
which will make retries on writes more resilient.
|
||||
- Improve documentation for Writer.ChunkSize.
|
||||
- Fix a bug in lifecycle to allow callers to clear lifecycle rules on a bucket.
|
||||
|
||||
## v1.9.0
|
||||
- Add retry for transient network errors on most operations (with the exception
|
||||
of writes).
|
||||
- Bump dependency for google.golang.org/api to capture a change in the default
|
||||
HTTP transport which will improve performance for reads under heavy load.
|
||||
- Add CRC32C checksum validation option to Composer.
|
||||
|
||||
## v1.8.0
|
||||
- Add support for V4 signed post policies.
|
||||
|
||||
## v1.7.0
|
||||
- V4 signed URL support:
|
||||
- Add support for bucket-bound domains and virtual hosted style URLs.
|
||||
- Add support for query parameters in the signature.
|
||||
- Fix text encoding to align with standards.
|
||||
- Add the object name to query parameters for write calls.
|
||||
- Fix retry behavior when reading files with Content-Encoding gzip.
|
||||
- Fix response header in reader.
|
||||
- New code examples:
|
||||
- Error handling for `ObjectHandle` preconditions.
|
||||
- Existence checks for buckets and objects.
|
||||
|
||||
## v1.6.0
|
||||
|
||||
- Updated option handling:
|
||||
- Don't drop custom scopes (#1756)
|
||||
- Don't drop port in provided endpoint (#1737)
|
||||
|
||||
## v1.5.0
|
||||
|
||||
- Honor WithEndpoint client option for reads as well as writes.
|
||||
- Add archive storage class to docs.
|
||||
- Make fixes to storage benchwrapper.
|
||||
|
||||
## v1.4.0
|
||||
|
||||
- When listing objects in a bucket, allow callers to specify which attributes
|
||||
are queried. This allows for performance optimization.
|
||||
|
||||
## v1.3.0
|
||||
|
||||
- Use `storage.googleapis.com/storage/v1` by default for GCS requests
|
||||
instead of `www.googleapis.com/storage/v1`.
|
||||
|
||||
## v1.2.1
|
||||
|
||||
- Fixed a bug where UniformBucketLevelAccess and BucketPolicyOnly were not
|
||||
being sent in all cases.
|
||||
|
||||
## v1.2.0
|
||||
|
||||
- Add support for UniformBucketLevelAccess. This configures access checks
|
||||
to use only bucket-level IAM policies.
|
||||
See: https://godoc.org/cloud.google.com/go/storage#UniformBucketLevelAccess.
|
||||
- Fix userAgent to use correct version.
|
||||
|
||||
## v1.1.2
|
||||
|
||||
- Fix memory leak in BucketIterator and ObjectIterator.
|
||||
|
||||
## v1.1.1
|
||||
|
||||
- Send BucketPolicyOnly even when it's disabled.
|
||||
|
||||
## v1.1.0
|
||||
|
||||
- Performance improvements for ObjectIterator and BucketIterator.
|
||||
- Fix Bucket.ObjectIterator size calculation checks.
|
||||
- Added HMACKeyOptions to all the methods which allows for options such as
|
||||
UserProject to be set per invocation and optionally be used.
|
||||
|
||||
## v1.0.0
|
||||
|
||||
This is the first tag to carve out storage as its own module. See:
|
||||
https://github.com/golang/go/wiki/Modules#is-it-possible-to-add-a-module-to-a-multi-module-repository.
|
||||
-202
@@ -1,202 +0,0 @@
|
||||
|
||||
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 [yyyy] [name of copyright owner]
|
||||
|
||||
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.
|
||||
-32
@@ -1,32 +0,0 @@
|
||||
## Cloud Storage [](https://godoc.org/cloud.google.com/go/storage)
|
||||
|
||||
- [About Cloud Storage](https://cloud.google.com/storage/)
|
||||
- [API documentation](https://cloud.google.com/storage/docs)
|
||||
- [Go client documentation](https://godoc.org/cloud.google.com/go/storage)
|
||||
- [Complete sample programs](https://github.com/GoogleCloudPlatform/golang-samples/tree/master/storage)
|
||||
|
||||
### Example Usage
|
||||
|
||||
First create a `storage.Client` to use throughout your application:
|
||||
|
||||
[snip]:# (storage-1)
|
||||
```go
|
||||
client, err := storage.NewClient(ctx)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
```
|
||||
|
||||
[snip]:# (storage-2)
|
||||
```go
|
||||
// Read the object1 from bucket.
|
||||
rc, err := client.Bucket("bucket").Object("object1").NewReader(ctx)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
defer rc.Close()
|
||||
body, err := ioutil.ReadAll(rc)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
```
|
||||
-335
@@ -1,335 +0,0 @@
|
||||
// Copyright 2014 Google LLC
|
||||
//
|
||||
// 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 (
|
||||
"context"
|
||||
"net/http"
|
||||
"reflect"
|
||||
|
||||
"cloud.google.com/go/internal/trace"
|
||||
"google.golang.org/api/googleapi"
|
||||
raw "google.golang.org/api/storage/v1"
|
||||
)
|
||||
|
||||
// ACLRole is the level of access to grant.
|
||||
type ACLRole string
|
||||
|
||||
const (
|
||||
RoleOwner ACLRole = "OWNER"
|
||||
RoleReader ACLRole = "READER"
|
||||
RoleWriter ACLRole = "WRITER"
|
||||
)
|
||||
|
||||
// ACLEntity refers to a user or group.
|
||||
// They are sometimes referred to as grantees.
|
||||
//
|
||||
// It could be in the form of:
|
||||
// "user-<userId>", "user-<email>", "group-<groupId>", "group-<email>",
|
||||
// "domain-<domain>" and "project-team-<projectId>".
|
||||
//
|
||||
// Or one of the predefined constants: AllUsers, AllAuthenticatedUsers.
|
||||
type ACLEntity string
|
||||
|
||||
const (
|
||||
AllUsers ACLEntity = "allUsers"
|
||||
AllAuthenticatedUsers ACLEntity = "allAuthenticatedUsers"
|
||||
)
|
||||
|
||||
// ACLRule represents a grant for a role to an entity (user, group or team) for a
|
||||
// Google Cloud Storage object or bucket.
|
||||
type ACLRule struct {
|
||||
Entity ACLEntity
|
||||
EntityID string
|
||||
Role ACLRole
|
||||
Domain string
|
||||
Email string
|
||||
ProjectTeam *ProjectTeam
|
||||
}
|
||||
|
||||
// ProjectTeam is the project team associated with the entity, if any.
|
||||
type ProjectTeam struct {
|
||||
ProjectNumber string
|
||||
Team string
|
||||
}
|
||||
|
||||
// ACLHandle provides operations on an access control list for a Google Cloud Storage bucket or object.
|
||||
type ACLHandle struct {
|
||||
c *Client
|
||||
bucket string
|
||||
object string
|
||||
isDefault bool
|
||||
userProject string // for requester-pays buckets
|
||||
}
|
||||
|
||||
// Delete permanently deletes the ACL entry for the given entity.
|
||||
func (a *ACLHandle) Delete(ctx context.Context, entity ACLEntity) (err error) {
|
||||
ctx = trace.StartSpan(ctx, "cloud.google.com/go/storage.ACL.Delete")
|
||||
defer func() { trace.EndSpan(ctx, err) }()
|
||||
|
||||
if a.object != "" {
|
||||
return a.objectDelete(ctx, entity)
|
||||
}
|
||||
if a.isDefault {
|
||||
return a.bucketDefaultDelete(ctx, entity)
|
||||
}
|
||||
return a.bucketDelete(ctx, entity)
|
||||
}
|
||||
|
||||
// Set sets the role for the given entity.
|
||||
func (a *ACLHandle) Set(ctx context.Context, entity ACLEntity, role ACLRole) (err error) {
|
||||
ctx = trace.StartSpan(ctx, "cloud.google.com/go/storage.ACL.Set")
|
||||
defer func() { trace.EndSpan(ctx, err) }()
|
||||
|
||||
if a.object != "" {
|
||||
return a.objectSet(ctx, entity, role, false)
|
||||
}
|
||||
if a.isDefault {
|
||||
return a.objectSet(ctx, entity, role, true)
|
||||
}
|
||||
return a.bucketSet(ctx, entity, role)
|
||||
}
|
||||
|
||||
// List retrieves ACL entries.
|
||||
func (a *ACLHandle) List(ctx context.Context) (rules []ACLRule, err error) {
|
||||
ctx = trace.StartSpan(ctx, "cloud.google.com/go/storage.ACL.List")
|
||||
defer func() { trace.EndSpan(ctx, err) }()
|
||||
|
||||
if a.object != "" {
|
||||
return a.objectList(ctx)
|
||||
}
|
||||
if a.isDefault {
|
||||
return a.bucketDefaultList(ctx)
|
||||
}
|
||||
return a.bucketList(ctx)
|
||||
}
|
||||
|
||||
func (a *ACLHandle) bucketDefaultList(ctx context.Context) ([]ACLRule, error) {
|
||||
var acls *raw.ObjectAccessControls
|
||||
var err error
|
||||
err = runWithRetry(ctx, func() error {
|
||||
req := a.c.raw.DefaultObjectAccessControls.List(a.bucket)
|
||||
a.configureCall(ctx, req)
|
||||
acls, err = req.Do()
|
||||
return err
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return toObjectACLRules(acls.Items), nil
|
||||
}
|
||||
|
||||
func (a *ACLHandle) bucketDefaultDelete(ctx context.Context, entity ACLEntity) error {
|
||||
return runWithRetry(ctx, func() error {
|
||||
req := a.c.raw.DefaultObjectAccessControls.Delete(a.bucket, string(entity))
|
||||
a.configureCall(ctx, req)
|
||||
return req.Do()
|
||||
})
|
||||
}
|
||||
|
||||
func (a *ACLHandle) bucketList(ctx context.Context) ([]ACLRule, error) {
|
||||
var acls *raw.BucketAccessControls
|
||||
var err error
|
||||
err = runWithRetry(ctx, func() error {
|
||||
req := a.c.raw.BucketAccessControls.List(a.bucket)
|
||||
a.configureCall(ctx, req)
|
||||
acls, err = req.Do()
|
||||
return err
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return toBucketACLRules(acls.Items), nil
|
||||
}
|
||||
|
||||
func (a *ACLHandle) bucketSet(ctx context.Context, entity ACLEntity, role ACLRole) error {
|
||||
acl := &raw.BucketAccessControl{
|
||||
Bucket: a.bucket,
|
||||
Entity: string(entity),
|
||||
Role: string(role),
|
||||
}
|
||||
err := runWithRetry(ctx, func() error {
|
||||
req := a.c.raw.BucketAccessControls.Update(a.bucket, string(entity), acl)
|
||||
a.configureCall(ctx, req)
|
||||
_, err := req.Do()
|
||||
return err
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (a *ACLHandle) bucketDelete(ctx context.Context, entity ACLEntity) error {
|
||||
return runWithRetry(ctx, func() error {
|
||||
req := a.c.raw.BucketAccessControls.Delete(a.bucket, string(entity))
|
||||
a.configureCall(ctx, req)
|
||||
return req.Do()
|
||||
})
|
||||
}
|
||||
|
||||
func (a *ACLHandle) objectList(ctx context.Context) ([]ACLRule, error) {
|
||||
var acls *raw.ObjectAccessControls
|
||||
var err error
|
||||
err = runWithRetry(ctx, func() error {
|
||||
req := a.c.raw.ObjectAccessControls.List(a.bucket, a.object)
|
||||
a.configureCall(ctx, req)
|
||||
acls, err = req.Do()
|
||||
return err
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return toObjectACLRules(acls.Items), nil
|
||||
}
|
||||
|
||||
func (a *ACLHandle) objectSet(ctx context.Context, entity ACLEntity, role ACLRole, isBucketDefault bool) error {
|
||||
type setRequest interface {
|
||||
Do(opts ...googleapi.CallOption) (*raw.ObjectAccessControl, error)
|
||||
Header() http.Header
|
||||
}
|
||||
|
||||
acl := &raw.ObjectAccessControl{
|
||||
Bucket: a.bucket,
|
||||
Entity: string(entity),
|
||||
Role: string(role),
|
||||
}
|
||||
var req setRequest
|
||||
if isBucketDefault {
|
||||
req = a.c.raw.DefaultObjectAccessControls.Update(a.bucket, string(entity), acl)
|
||||
} else {
|
||||
req = a.c.raw.ObjectAccessControls.Update(a.bucket, a.object, string(entity), acl)
|
||||
}
|
||||
a.configureCall(ctx, req)
|
||||
return runWithRetry(ctx, func() error {
|
||||
_, err := req.Do()
|
||||
return err
|
||||
})
|
||||
}
|
||||
|
||||
func (a *ACLHandle) objectDelete(ctx context.Context, entity ACLEntity) error {
|
||||
return runWithRetry(ctx, func() error {
|
||||
req := a.c.raw.ObjectAccessControls.Delete(a.bucket, a.object, string(entity))
|
||||
a.configureCall(ctx, req)
|
||||
return req.Do()
|
||||
})
|
||||
}
|
||||
|
||||
func (a *ACLHandle) configureCall(ctx context.Context, call interface{ Header() http.Header }) {
|
||||
vc := reflect.ValueOf(call)
|
||||
vc.MethodByName("Context").Call([]reflect.Value{reflect.ValueOf(ctx)})
|
||||
if a.userProject != "" {
|
||||
vc.MethodByName("UserProject").Call([]reflect.Value{reflect.ValueOf(a.userProject)})
|
||||
}
|
||||
setClientHeader(call.Header())
|
||||
}
|
||||
|
||||
func toObjectACLRules(items []*raw.ObjectAccessControl) []ACLRule {
|
||||
var rs []ACLRule
|
||||
for _, item := range items {
|
||||
rs = append(rs, toObjectACLRule(item))
|
||||
}
|
||||
return rs
|
||||
}
|
||||
|
||||
func toBucketACLRules(items []*raw.BucketAccessControl) []ACLRule {
|
||||
var rs []ACLRule
|
||||
for _, item := range items {
|
||||
rs = append(rs, toBucketACLRule(item))
|
||||
}
|
||||
return rs
|
||||
}
|
||||
|
||||
func toObjectACLRule(a *raw.ObjectAccessControl) ACLRule {
|
||||
return ACLRule{
|
||||
Entity: ACLEntity(a.Entity),
|
||||
EntityID: a.EntityId,
|
||||
Role: ACLRole(a.Role),
|
||||
Domain: a.Domain,
|
||||
Email: a.Email,
|
||||
ProjectTeam: toObjectProjectTeam(a.ProjectTeam),
|
||||
}
|
||||
}
|
||||
|
||||
func toBucketACLRule(a *raw.BucketAccessControl) ACLRule {
|
||||
return ACLRule{
|
||||
Entity: ACLEntity(a.Entity),
|
||||
EntityID: a.EntityId,
|
||||
Role: ACLRole(a.Role),
|
||||
Domain: a.Domain,
|
||||
Email: a.Email,
|
||||
ProjectTeam: toBucketProjectTeam(a.ProjectTeam),
|
||||
}
|
||||
}
|
||||
|
||||
func toRawObjectACL(rules []ACLRule) []*raw.ObjectAccessControl {
|
||||
if len(rules) == 0 {
|
||||
return nil
|
||||
}
|
||||
r := make([]*raw.ObjectAccessControl, 0, len(rules))
|
||||
for _, rule := range rules {
|
||||
r = append(r, rule.toRawObjectAccessControl("")) // bucket name unnecessary
|
||||
}
|
||||
return r
|
||||
}
|
||||
|
||||
func toRawBucketACL(rules []ACLRule) []*raw.BucketAccessControl {
|
||||
if len(rules) == 0 {
|
||||
return nil
|
||||
}
|
||||
r := make([]*raw.BucketAccessControl, 0, len(rules))
|
||||
for _, rule := range rules {
|
||||
r = append(r, rule.toRawBucketAccessControl("")) // bucket name unnecessary
|
||||
}
|
||||
return r
|
||||
}
|
||||
|
||||
func (r ACLRule) toRawBucketAccessControl(bucket string) *raw.BucketAccessControl {
|
||||
return &raw.BucketAccessControl{
|
||||
Bucket: bucket,
|
||||
Entity: string(r.Entity),
|
||||
Role: string(r.Role),
|
||||
// The other fields are not settable.
|
||||
}
|
||||
}
|
||||
|
||||
func (r ACLRule) toRawObjectAccessControl(bucket string) *raw.ObjectAccessControl {
|
||||
return &raw.ObjectAccessControl{
|
||||
Bucket: bucket,
|
||||
Entity: string(r.Entity),
|
||||
Role: string(r.Role),
|
||||
// The other fields are not settable.
|
||||
}
|
||||
}
|
||||
|
||||
func toBucketProjectTeam(p *raw.BucketAccessControlProjectTeam) *ProjectTeam {
|
||||
if p == nil {
|
||||
return nil
|
||||
}
|
||||
return &ProjectTeam{
|
||||
ProjectNumber: p.ProjectNumber,
|
||||
Team: p.Team,
|
||||
}
|
||||
}
|
||||
|
||||
func toObjectProjectTeam(p *raw.ObjectAccessControlProjectTeam) *ProjectTeam {
|
||||
if p == nil {
|
||||
return nil
|
||||
}
|
||||
return &ProjectTeam{
|
||||
ProjectNumber: p.ProjectNumber,
|
||||
Team: p.Team,
|
||||
}
|
||||
}
|
||||
-1265
File diff suppressed because it is too large
Load Diff
-238
@@ -1,238 +0,0 @@
|
||||
// Copyright 2016 Google LLC
|
||||
//
|
||||
// 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 (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
|
||||
"cloud.google.com/go/internal/trace"
|
||||
raw "google.golang.org/api/storage/v1"
|
||||
)
|
||||
|
||||
// CopierFrom creates a Copier that can copy src to dst.
|
||||
// You can immediately call Run on the returned Copier, or
|
||||
// you can configure it first.
|
||||
//
|
||||
// For Requester Pays buckets, the user project of dst is billed, unless it is empty,
|
||||
// in which case the user project of src is billed.
|
||||
func (dst *ObjectHandle) CopierFrom(src *ObjectHandle) *Copier {
|
||||
return &Copier{dst: dst, src: src}
|
||||
}
|
||||
|
||||
// A Copier copies a source object to a destination.
|
||||
type Copier struct {
|
||||
// ObjectAttrs are optional attributes to set on the destination object.
|
||||
// Any attributes must be initialized before any calls on the Copier. Nil
|
||||
// or zero-valued attributes are ignored.
|
||||
ObjectAttrs
|
||||
|
||||
// RewriteToken can be set before calling Run to resume a copy
|
||||
// operation. After Run returns a non-nil error, RewriteToken will
|
||||
// have been updated to contain the value needed to resume the copy.
|
||||
RewriteToken string
|
||||
|
||||
// ProgressFunc can be used to monitor the progress of a multi-RPC copy
|
||||
// operation. If ProgressFunc is not nil and copying requires multiple
|
||||
// calls to the underlying service (see
|
||||
// https://cloud.google.com/storage/docs/json_api/v1/objects/rewrite), then
|
||||
// ProgressFunc will be invoked after each call with the number of bytes of
|
||||
// content copied so far and the total size in bytes of the source object.
|
||||
//
|
||||
// ProgressFunc is intended to make upload progress available to the
|
||||
// application. For example, the implementation of ProgressFunc may update
|
||||
// a progress bar in the application's UI, or log the result of
|
||||
// float64(copiedBytes)/float64(totalBytes).
|
||||
//
|
||||
// ProgressFunc should return quickly without blocking.
|
||||
ProgressFunc func(copiedBytes, totalBytes uint64)
|
||||
|
||||
// The Cloud KMS key, in the form projects/P/locations/L/keyRings/R/cryptoKeys/K,
|
||||
// that will be used to encrypt the object. Overrides the object's KMSKeyName, if
|
||||
// any.
|
||||
//
|
||||
// Providing both a DestinationKMSKeyName and a customer-supplied encryption key
|
||||
// (via ObjectHandle.Key) on the destination object will result in an error when
|
||||
// Run is called.
|
||||
DestinationKMSKeyName string
|
||||
|
||||
dst, src *ObjectHandle
|
||||
}
|
||||
|
||||
// Run performs the copy.
|
||||
func (c *Copier) Run(ctx context.Context) (attrs *ObjectAttrs, err error) {
|
||||
ctx = trace.StartSpan(ctx, "cloud.google.com/go/storage.Copier.Run")
|
||||
defer func() { trace.EndSpan(ctx, err) }()
|
||||
|
||||
if err := c.src.validate(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := c.dst.validate(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if c.DestinationKMSKeyName != "" && c.dst.encryptionKey != nil {
|
||||
return nil, errors.New("storage: cannot use DestinationKMSKeyName with a customer-supplied encryption key")
|
||||
}
|
||||
// Convert destination attributes to raw form, omitting the bucket.
|
||||
// If the bucket is included but name or content-type aren't, the service
|
||||
// returns a 400 with "Required" as the only message. Omitting the bucket
|
||||
// does not cause any problems.
|
||||
rawObject := c.ObjectAttrs.toRawObject("")
|
||||
for {
|
||||
res, err := c.callRewrite(ctx, rawObject)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if c.ProgressFunc != nil {
|
||||
c.ProgressFunc(uint64(res.TotalBytesRewritten), uint64(res.ObjectSize))
|
||||
}
|
||||
if res.Done { // Finished successfully.
|
||||
return newObject(res.Resource), nil
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (c *Copier) callRewrite(ctx context.Context, rawObj *raw.Object) (*raw.RewriteResponse, error) {
|
||||
call := c.dst.c.raw.Objects.Rewrite(c.src.bucket, c.src.object, c.dst.bucket, c.dst.object, rawObj)
|
||||
|
||||
call.Context(ctx).Projection("full")
|
||||
if c.RewriteToken != "" {
|
||||
call.RewriteToken(c.RewriteToken)
|
||||
}
|
||||
if c.DestinationKMSKeyName != "" {
|
||||
call.DestinationKmsKeyName(c.DestinationKMSKeyName)
|
||||
}
|
||||
if c.PredefinedACL != "" {
|
||||
call.DestinationPredefinedAcl(c.PredefinedACL)
|
||||
}
|
||||
if err := applyConds("Copy destination", c.dst.gen, c.dst.conds, call); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if c.dst.userProject != "" {
|
||||
call.UserProject(c.dst.userProject)
|
||||
} else if c.src.userProject != "" {
|
||||
call.UserProject(c.src.userProject)
|
||||
}
|
||||
if err := applySourceConds(c.src.gen, c.src.conds, call); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := setEncryptionHeaders(call.Header(), c.dst.encryptionKey, false); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := setEncryptionHeaders(call.Header(), c.src.encryptionKey, true); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var res *raw.RewriteResponse
|
||||
var err error
|
||||
setClientHeader(call.Header())
|
||||
err = runWithRetry(ctx, func() error { res, err = call.Do(); return err })
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
c.RewriteToken = res.RewriteToken
|
||||
return res, nil
|
||||
}
|
||||
|
||||
// ComposerFrom creates a Composer that can compose srcs into dst.
|
||||
// You can immediately call Run on the returned Composer, or you can
|
||||
// configure it first.
|
||||
//
|
||||
// The encryption key for the destination object will be used to decrypt all
|
||||
// source objects and encrypt the destination object. It is an error
|
||||
// to specify an encryption key for any of the source objects.
|
||||
func (dst *ObjectHandle) ComposerFrom(srcs ...*ObjectHandle) *Composer {
|
||||
return &Composer{dst: dst, srcs: srcs}
|
||||
}
|
||||
|
||||
// A Composer composes source objects into a destination object.
|
||||
//
|
||||
// For Requester Pays buckets, the user project of dst is billed.
|
||||
type Composer struct {
|
||||
// ObjectAttrs are optional attributes to set on the destination object.
|
||||
// Any attributes must be initialized before any calls on the Composer. Nil
|
||||
// or zero-valued attributes are ignored.
|
||||
ObjectAttrs
|
||||
|
||||
// SendCRC specifies whether to transmit a CRC32C field. It should be set
|
||||
// to true in addition to setting the Composer's CRC32C field, because zero
|
||||
// is a valid CRC and normally a zero would not be transmitted.
|
||||
// If a CRC32C is sent, and the data in the destination object does not match
|
||||
// the checksum, the compose will be rejected.
|
||||
SendCRC32C bool
|
||||
|
||||
dst *ObjectHandle
|
||||
srcs []*ObjectHandle
|
||||
}
|
||||
|
||||
// Run performs the compose operation.
|
||||
func (c *Composer) Run(ctx context.Context) (attrs *ObjectAttrs, err error) {
|
||||
ctx = trace.StartSpan(ctx, "cloud.google.com/go/storage.Composer.Run")
|
||||
defer func() { trace.EndSpan(ctx, err) }()
|
||||
|
||||
if err := c.dst.validate(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if len(c.srcs) == 0 {
|
||||
return nil, errors.New("storage: at least one source object must be specified")
|
||||
}
|
||||
|
||||
req := &raw.ComposeRequest{}
|
||||
// Compose requires a non-empty Destination, so we always set it,
|
||||
// even if the caller-provided ObjectAttrs is the zero value.
|
||||
req.Destination = c.ObjectAttrs.toRawObject(c.dst.bucket)
|
||||
if c.SendCRC32C {
|
||||
req.Destination.Crc32c = encodeUint32(c.ObjectAttrs.CRC32C)
|
||||
}
|
||||
for _, src := range c.srcs {
|
||||
if err := src.validate(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if src.bucket != c.dst.bucket {
|
||||
return nil, fmt.Errorf("storage: all source objects must be in bucket %q, found %q", c.dst.bucket, src.bucket)
|
||||
}
|
||||
if src.encryptionKey != nil {
|
||||
return nil, fmt.Errorf("storage: compose source %s.%s must not have encryption key", src.bucket, src.object)
|
||||
}
|
||||
srcObj := &raw.ComposeRequestSourceObjects{
|
||||
Name: src.object,
|
||||
}
|
||||
if err := applyConds("ComposeFrom source", src.gen, src.conds, composeSourceObj{srcObj}); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
req.SourceObjects = append(req.SourceObjects, srcObj)
|
||||
}
|
||||
|
||||
call := c.dst.c.raw.Objects.Compose(c.dst.bucket, c.dst.object, req).Context(ctx)
|
||||
if err := applyConds("ComposeFrom destination", c.dst.gen, c.dst.conds, call); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if c.dst.userProject != "" {
|
||||
call.UserProject(c.dst.userProject)
|
||||
}
|
||||
if c.PredefinedACL != "" {
|
||||
call.DestinationPredefinedAcl(c.PredefinedACL)
|
||||
}
|
||||
if err := setEncryptionHeaders(call.Header(), c.dst.encryptionKey, false); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var obj *raw.Object
|
||||
setClientHeader(call.Header())
|
||||
err = runWithRetry(ctx, func() error { obj, err = call.Do(); return err })
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return newObject(obj), nil
|
||||
}
|
||||
-218
@@ -1,218 +0,0 @@
|
||||
// Copyright 2016 Google LLC
|
||||
//
|
||||
// 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 provides an easy way to work with Google Cloud Storage.
|
||||
Google Cloud Storage stores data in named objects, which are grouped into buckets.
|
||||
|
||||
More information about Google Cloud Storage is available at
|
||||
https://cloud.google.com/storage/docs.
|
||||
|
||||
See https://godoc.org/cloud.google.com/go for authentication, timeouts,
|
||||
connection pooling and similar aspects of this package.
|
||||
|
||||
All of the methods of this package use exponential backoff to retry calls that fail
|
||||
with certain errors, as described in
|
||||
https://cloud.google.com/storage/docs/exponential-backoff. Retrying continues
|
||||
indefinitely unless the controlling context is canceled or the client is closed. See
|
||||
context.WithTimeout and context.WithCancel.
|
||||
|
||||
|
||||
Creating a Client
|
||||
|
||||
To start working with this package, create a client:
|
||||
|
||||
ctx := context.Background()
|
||||
client, err := storage.NewClient(ctx)
|
||||
if err != nil {
|
||||
// TODO: Handle error.
|
||||
}
|
||||
|
||||
The client will use your default application credentials.
|
||||
|
||||
If you only wish to access public data, you can create
|
||||
an unauthenticated client with
|
||||
|
||||
client, err := storage.NewClient(ctx, option.WithoutAuthentication())
|
||||
|
||||
Buckets
|
||||
|
||||
A Google Cloud Storage bucket is a collection of objects. To work with a
|
||||
bucket, make a bucket handle:
|
||||
|
||||
bkt := client.Bucket(bucketName)
|
||||
|
||||
A handle is a reference to a bucket. You can have a handle even if the
|
||||
bucket doesn't exist yet. To create a bucket in Google Cloud Storage,
|
||||
call Create on the handle:
|
||||
|
||||
if err := bkt.Create(ctx, projectID, nil); err != nil {
|
||||
// TODO: Handle error.
|
||||
}
|
||||
|
||||
Note that although buckets are associated with projects, bucket names are
|
||||
global across all projects.
|
||||
|
||||
Each bucket has associated metadata, represented in this package by
|
||||
BucketAttrs. The third argument to BucketHandle.Create allows you to set
|
||||
the initial BucketAttrs of a bucket. To retrieve a bucket's attributes, use
|
||||
Attrs:
|
||||
|
||||
attrs, err := bkt.Attrs(ctx)
|
||||
if err != nil {
|
||||
// TODO: Handle error.
|
||||
}
|
||||
fmt.Printf("bucket %s, created at %s, is located in %s with storage class %s\n",
|
||||
attrs.Name, attrs.Created, attrs.Location, attrs.StorageClass)
|
||||
|
||||
Objects
|
||||
|
||||
An object holds arbitrary data as a sequence of bytes, like a file. You
|
||||
refer to objects using a handle, just as with buckets, but unlike buckets
|
||||
you don't explicitly create an object. Instead, the first time you write
|
||||
to an object it will be created. You can use the standard Go io.Reader
|
||||
and io.Writer interfaces to read and write object data:
|
||||
|
||||
obj := bkt.Object("data")
|
||||
// Write something to obj.
|
||||
// w implements io.Writer.
|
||||
w := obj.NewWriter(ctx)
|
||||
// Write some text to obj. This will either create the object or overwrite whatever is there already.
|
||||
if _, err := fmt.Fprintf(w, "This object contains text.\n"); err != nil {
|
||||
// TODO: Handle error.
|
||||
}
|
||||
// Close, just like writing a file.
|
||||
if err := w.Close(); err != nil {
|
||||
// TODO: Handle error.
|
||||
}
|
||||
|
||||
// Read it back.
|
||||
r, err := obj.NewReader(ctx)
|
||||
if err != nil {
|
||||
// TODO: Handle error.
|
||||
}
|
||||
defer r.Close()
|
||||
if _, err := io.Copy(os.Stdout, r); err != nil {
|
||||
// TODO: Handle error.
|
||||
}
|
||||
// Prints "This object contains text."
|
||||
|
||||
Objects also have attributes, which you can fetch with Attrs:
|
||||
|
||||
objAttrs, err := obj.Attrs(ctx)
|
||||
if err != nil {
|
||||
// TODO: Handle error.
|
||||
}
|
||||
fmt.Printf("object %s has size %d and can be read using %s\n",
|
||||
objAttrs.Name, objAttrs.Size, objAttrs.MediaLink)
|
||||
|
||||
Listing objects
|
||||
|
||||
Listing objects in a bucket is done with the Bucket.Objects method:
|
||||
|
||||
query := &storage.Query{Prefix: ""}
|
||||
|
||||
var names []string
|
||||
it := bkt.Objects(ctx, query)
|
||||
for {
|
||||
attrs, err := it.Next()
|
||||
if err == iterator.Done {
|
||||
break
|
||||
}
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
names = append(names, attrs.Name)
|
||||
}
|
||||
|
||||
If only a subset of object attributes is needed when listing, specifying this
|
||||
subset using Query.SetAttrSelection may speed up the listing process:
|
||||
|
||||
query := &storage.Query{Prefix: ""}
|
||||
query.SetAttrSelection([]string{"Name"})
|
||||
|
||||
// ... as before
|
||||
|
||||
ACLs
|
||||
|
||||
Both objects and buckets have ACLs (Access Control Lists). An ACL is a list of
|
||||
ACLRules, each of which specifies the role of a user, group or project. ACLs
|
||||
are suitable for fine-grained control, but you may prefer using IAM to control
|
||||
access at the project level (see
|
||||
https://cloud.google.com/storage/docs/access-control/iam).
|
||||
|
||||
To list the ACLs of a bucket or object, obtain an ACLHandle and call its List method:
|
||||
|
||||
acls, err := obj.ACL().List(ctx)
|
||||
if err != nil {
|
||||
// TODO: Handle error.
|
||||
}
|
||||
for _, rule := range acls {
|
||||
fmt.Printf("%s has role %s\n", rule.Entity, rule.Role)
|
||||
}
|
||||
|
||||
You can also set and delete ACLs.
|
||||
|
||||
Conditions
|
||||
|
||||
Every object has a generation and a metageneration. The generation changes
|
||||
whenever the content changes, and the metageneration changes whenever the
|
||||
metadata changes. Conditions let you check these values before an operation;
|
||||
the operation only executes if the conditions match. You can use conditions to
|
||||
prevent race conditions in read-modify-write operations.
|
||||
|
||||
For example, say you've read an object's metadata into objAttrs. Now
|
||||
you want to write to that object, but only if its contents haven't changed
|
||||
since you read it. Here is how to express that:
|
||||
|
||||
w = obj.If(storage.Conditions{GenerationMatch: objAttrs.Generation}).NewWriter(ctx)
|
||||
// Proceed with writing as above.
|
||||
|
||||
Signed URLs
|
||||
|
||||
You can obtain a URL that lets anyone read or write an object for a limited time.
|
||||
You don't need to create a client to do this. See the documentation of
|
||||
SignedURL for details.
|
||||
|
||||
url, err := storage.SignedURL(bucketName, "shared-object", opts)
|
||||
if err != nil {
|
||||
// TODO: Handle error.
|
||||
}
|
||||
fmt.Println(url)
|
||||
|
||||
Post Policy V4 Signed Request
|
||||
|
||||
A type of signed request that allows uploads through HTML forms directly to Cloud Storage with
|
||||
temporary permission. Conditions can be applied to restrict how the HTML form is used and exercised
|
||||
by a user.
|
||||
|
||||
For more information, please see https://cloud.google.com/storage/docs/xml-api/post-object as well
|
||||
as the documentation of GenerateSignedPostPolicyV4.
|
||||
|
||||
pv4, err := storage.GenerateSignedPostPolicyV4(bucketName, objectName, opts)
|
||||
if err != nil {
|
||||
// TODO: Handle error.
|
||||
}
|
||||
fmt.Printf("URL: %s\nFields; %v\n", pv4.URL, pv4.Fields)
|
||||
|
||||
Errors
|
||||
|
||||
Errors returned by this client are often of the type [`googleapi.Error`](https://godoc.org/google.golang.org/api/googleapi#Error).
|
||||
These errors can be introspected for more information by type asserting to the richer `googleapi.Error` type. For example:
|
||||
|
||||
if e, ok := err.(*googleapi.Error); ok {
|
||||
if e.Code == 409 { ... }
|
||||
}
|
||||
*/
|
||||
package storage // import "cloud.google.com/go/storage"
|
||||
-48
@@ -1,48 +0,0 @@
|
||||
// Copyright 2017 Google LLC
|
||||
//
|
||||
// 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.
|
||||
|
||||
// +build go1.10
|
||||
|
||||
package storage
|
||||
|
||||
import (
|
||||
"net/url"
|
||||
"strings"
|
||||
|
||||
"google.golang.org/api/googleapi"
|
||||
)
|
||||
|
||||
func shouldRetry(err error) bool {
|
||||
switch e := err.(type) {
|
||||
case *googleapi.Error:
|
||||
// Retry on 429 and 5xx, according to
|
||||
// https://cloud.google.com/storage/docs/exponential-backoff.
|
||||
return e.Code == 429 || (e.Code >= 500 && e.Code < 600)
|
||||
case *url.Error:
|
||||
// Retry socket-level errors ECONNREFUSED and ENETUNREACH (from syscall).
|
||||
// Unfortunately the error type is unexported, so we resort to string
|
||||
// matching.
|
||||
retriable := []string{"connection refused", "connection reset"}
|
||||
for _, s := range retriable {
|
||||
if strings.Contains(e.Error(), s) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
case interface{ Temporary() bool }:
|
||||
return e.Temporary()
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
||||
-22
@@ -1,22 +0,0 @@
|
||||
// Copyright 2019 Google LLC
|
||||
//
|
||||
// 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.
|
||||
|
||||
// This file, and the cloud.google.com/go import, won't actually become part of
|
||||
// the resultant binary.
|
||||
// +build modhack
|
||||
|
||||
package storage
|
||||
|
||||
// Necessary for safely adding multi-module repo. See: https://github.com/golang/go/wiki/Modules#is-it-possible-to-add-a-module-to-a-multi-module-repository
|
||||
import _ "cloud.google.com/go"
|
||||
-441
@@ -1,441 +0,0 @@
|
||||
// Copyright 2019 Google LLC
|
||||
//
|
||||
// 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 (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"google.golang.org/api/iterator"
|
||||
raw "google.golang.org/api/storage/v1"
|
||||
)
|
||||
|
||||
// HMACState is the state of the HMAC key.
|
||||
//
|
||||
// This type is EXPERIMENTAL and subject to change or removal without notice.
|
||||
type HMACState string
|
||||
|
||||
const (
|
||||
// Active is the status for an active key that can be used to sign
|
||||
// requests.
|
||||
Active HMACState = "ACTIVE"
|
||||
|
||||
// Inactive is the status for an inactive key thus requests signed by
|
||||
// this key will be denied.
|
||||
Inactive HMACState = "INACTIVE"
|
||||
|
||||
// Deleted is the status for a key that is deleted.
|
||||
// Once in this state the key cannot key cannot be recovered
|
||||
// and does not count towards key limits. Deleted keys will be cleaned
|
||||
// up later.
|
||||
Deleted HMACState = "DELETED"
|
||||
)
|
||||
|
||||
// HMACKey is the representation of a Google Cloud Storage HMAC key.
|
||||
//
|
||||
// HMAC keys are used to authenticate signed access to objects. To enable HMAC key
|
||||
// authentication, please visit https://cloud.google.com/storage/docs/migrating.
|
||||
//
|
||||
// This type is EXPERIMENTAL and subject to change or removal without notice.
|
||||
type HMACKey struct {
|
||||
// The HMAC's secret key.
|
||||
Secret string
|
||||
|
||||
// AccessID is the ID of the HMAC key.
|
||||
AccessID string
|
||||
|
||||
// Etag is the HTTP/1.1 Entity tag.
|
||||
Etag string
|
||||
|
||||
// ID is the ID of the HMAC key, including the ProjectID and AccessID.
|
||||
ID string
|
||||
|
||||
// ProjectID is the ID of the project that owns the
|
||||
// service account to which the key authenticates.
|
||||
ProjectID string
|
||||
|
||||
// ServiceAccountEmail is the email address
|
||||
// of the key's associated service account.
|
||||
ServiceAccountEmail string
|
||||
|
||||
// CreatedTime is the creation time of the HMAC key.
|
||||
CreatedTime time.Time
|
||||
|
||||
// UpdatedTime is the last modification time of the HMAC key metadata.
|
||||
UpdatedTime time.Time
|
||||
|
||||
// State is the state of the HMAC key.
|
||||
// It can be one of StateActive, StateInactive or StateDeleted.
|
||||
State HMACState
|
||||
}
|
||||
|
||||
// HMACKeyHandle helps provide access and management for HMAC keys.
|
||||
//
|
||||
// This type is EXPERIMENTAL and subject to change or removal without notice.
|
||||
type HMACKeyHandle struct {
|
||||
projectID string
|
||||
accessID string
|
||||
|
||||
raw *raw.ProjectsHmacKeysService
|
||||
}
|
||||
|
||||
// HMACKeyHandle creates a handle that will be used for HMACKey operations.
|
||||
//
|
||||
// This method is EXPERIMENTAL and subject to change or removal without notice.
|
||||
func (c *Client) HMACKeyHandle(projectID, accessID string) *HMACKeyHandle {
|
||||
return &HMACKeyHandle{
|
||||
projectID: projectID,
|
||||
accessID: accessID,
|
||||
raw: raw.NewProjectsHmacKeysService(c.raw),
|
||||
}
|
||||
}
|
||||
|
||||
// Get invokes an RPC to retrieve the HMAC key referenced by the
|
||||
// HMACKeyHandle's accessID.
|
||||
//
|
||||
// Options such as UserProjectForHMACKeys can be used to set the
|
||||
// userProject to be billed against for operations.
|
||||
//
|
||||
// This method is EXPERIMENTAL and subject to change or removal without notice.
|
||||
func (hkh *HMACKeyHandle) Get(ctx context.Context, opts ...HMACKeyOption) (*HMACKey, error) {
|
||||
call := hkh.raw.Get(hkh.projectID, hkh.accessID)
|
||||
|
||||
desc := new(hmacKeyDesc)
|
||||
for _, opt := range opts {
|
||||
opt.withHMACKeyDesc(desc)
|
||||
}
|
||||
if desc.userProjectID != "" {
|
||||
call = call.UserProject(desc.userProjectID)
|
||||
}
|
||||
|
||||
setClientHeader(call.Header())
|
||||
|
||||
var metadata *raw.HmacKeyMetadata
|
||||
var err error
|
||||
err = runWithRetry(ctx, func() error {
|
||||
metadata, err = call.Context(ctx).Do()
|
||||
return err
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
hkPb := &raw.HmacKey{
|
||||
Metadata: metadata,
|
||||
}
|
||||
return pbHmacKeyToHMACKey(hkPb, false)
|
||||
}
|
||||
|
||||
// Delete invokes an RPC to delete the key referenced by accessID, on Google Cloud Storage.
|
||||
// Only inactive HMAC keys can be deleted.
|
||||
// After deletion, a key cannot be used to authenticate requests.
|
||||
//
|
||||
// This method is EXPERIMENTAL and subject to change or removal without notice.
|
||||
func (hkh *HMACKeyHandle) Delete(ctx context.Context, opts ...HMACKeyOption) error {
|
||||
delCall := hkh.raw.Delete(hkh.projectID, hkh.accessID)
|
||||
desc := new(hmacKeyDesc)
|
||||
for _, opt := range opts {
|
||||
opt.withHMACKeyDesc(desc)
|
||||
}
|
||||
if desc.userProjectID != "" {
|
||||
delCall = delCall.UserProject(desc.userProjectID)
|
||||
}
|
||||
setClientHeader(delCall.Header())
|
||||
|
||||
return runWithRetry(ctx, func() error {
|
||||
return delCall.Context(ctx).Do()
|
||||
})
|
||||
}
|
||||
|
||||
func pbHmacKeyToHMACKey(pb *raw.HmacKey, updatedTimeCanBeNil bool) (*HMACKey, error) {
|
||||
pbmd := pb.Metadata
|
||||
if pbmd == nil {
|
||||
return nil, errors.New("field Metadata cannot be nil")
|
||||
}
|
||||
createdTime, err := time.Parse(time.RFC3339, pbmd.TimeCreated)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("field CreatedTime: %v", err)
|
||||
}
|
||||
updatedTime, err := time.Parse(time.RFC3339, pbmd.Updated)
|
||||
if err != nil && !updatedTimeCanBeNil {
|
||||
return nil, fmt.Errorf("field UpdatedTime: %v", err)
|
||||
}
|
||||
|
||||
hmk := &HMACKey{
|
||||
AccessID: pbmd.AccessId,
|
||||
Secret: pb.Secret,
|
||||
Etag: pbmd.Etag,
|
||||
ID: pbmd.Id,
|
||||
State: HMACState(pbmd.State),
|
||||
ProjectID: pbmd.ProjectId,
|
||||
CreatedTime: createdTime,
|
||||
UpdatedTime: updatedTime,
|
||||
|
||||
ServiceAccountEmail: pbmd.ServiceAccountEmail,
|
||||
}
|
||||
|
||||
return hmk, nil
|
||||
}
|
||||
|
||||
// CreateHMACKey invokes an RPC for Google Cloud Storage to create a new HMACKey.
|
||||
//
|
||||
// This method is EXPERIMENTAL and subject to change or removal without notice.
|
||||
func (c *Client) CreateHMACKey(ctx context.Context, projectID, serviceAccountEmail string, opts ...HMACKeyOption) (*HMACKey, error) {
|
||||
if projectID == "" {
|
||||
return nil, errors.New("storage: expecting a non-blank projectID")
|
||||
}
|
||||
if serviceAccountEmail == "" {
|
||||
return nil, errors.New("storage: expecting a non-blank service account email")
|
||||
}
|
||||
|
||||
svc := raw.NewProjectsHmacKeysService(c.raw)
|
||||
call := svc.Create(projectID, serviceAccountEmail)
|
||||
desc := new(hmacKeyDesc)
|
||||
for _, opt := range opts {
|
||||
opt.withHMACKeyDesc(desc)
|
||||
}
|
||||
if desc.userProjectID != "" {
|
||||
call = call.UserProject(desc.userProjectID)
|
||||
}
|
||||
|
||||
setClientHeader(call.Header())
|
||||
|
||||
var hkPb *raw.HmacKey
|
||||
var err error
|
||||
err = runWithRetry(ctx, func() error {
|
||||
hkPb, err = call.Context(ctx).Do()
|
||||
return err
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return pbHmacKeyToHMACKey(hkPb, true)
|
||||
}
|
||||
|
||||
// HMACKeyAttrsToUpdate defines the attributes of an HMACKey that will be updated.
|
||||
//
|
||||
// This type is EXPERIMENTAL and subject to change or removal without notice.
|
||||
type HMACKeyAttrsToUpdate struct {
|
||||
// State is required and must be either StateActive or StateInactive.
|
||||
State HMACState
|
||||
|
||||
// Etag is an optional field and it is the HTTP/1.1 Entity tag.
|
||||
Etag string
|
||||
}
|
||||
|
||||
// Update mutates the HMACKey referred to by accessID.
|
||||
//
|
||||
// This method is EXPERIMENTAL and subject to change or removal without notice.
|
||||
func (h *HMACKeyHandle) Update(ctx context.Context, au HMACKeyAttrsToUpdate, opts ...HMACKeyOption) (*HMACKey, error) {
|
||||
if au.State != Active && au.State != Inactive {
|
||||
return nil, fmt.Errorf("storage: invalid state %q for update, must be either %q or %q", au.State, Active, Inactive)
|
||||
}
|
||||
|
||||
call := h.raw.Update(h.projectID, h.accessID, &raw.HmacKeyMetadata{
|
||||
Etag: au.Etag,
|
||||
State: string(au.State),
|
||||
})
|
||||
|
||||
desc := new(hmacKeyDesc)
|
||||
for _, opt := range opts {
|
||||
opt.withHMACKeyDesc(desc)
|
||||
}
|
||||
if desc.userProjectID != "" {
|
||||
call = call.UserProject(desc.userProjectID)
|
||||
}
|
||||
setClientHeader(call.Header())
|
||||
|
||||
var metadata *raw.HmacKeyMetadata
|
||||
var err error
|
||||
err = runWithRetry(ctx, func() error {
|
||||
metadata, err = call.Context(ctx).Do()
|
||||
return err
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
hkPb := &raw.HmacKey{
|
||||
Metadata: metadata,
|
||||
}
|
||||
return pbHmacKeyToHMACKey(hkPb, false)
|
||||
}
|
||||
|
||||
// An HMACKeysIterator is an iterator over HMACKeys.
|
||||
//
|
||||
// Note: This iterator is not safe for concurrent operations without explicit synchronization.
|
||||
//
|
||||
// This type is EXPERIMENTAL and subject to change or removal without notice.
|
||||
type HMACKeysIterator struct {
|
||||
ctx context.Context
|
||||
raw *raw.ProjectsHmacKeysService
|
||||
projectID string
|
||||
hmacKeys []*HMACKey
|
||||
pageInfo *iterator.PageInfo
|
||||
nextFunc func() error
|
||||
index int
|
||||
desc hmacKeyDesc
|
||||
}
|
||||
|
||||
// ListHMACKeys returns an iterator for listing HMACKeys.
|
||||
//
|
||||
// Note: This iterator is not safe for concurrent operations without explicit synchronization.
|
||||
//
|
||||
// This method is EXPERIMENTAL and subject to change or removal without notice.
|
||||
func (c *Client) ListHMACKeys(ctx context.Context, projectID string, opts ...HMACKeyOption) *HMACKeysIterator {
|
||||
it := &HMACKeysIterator{
|
||||
ctx: ctx,
|
||||
raw: raw.NewProjectsHmacKeysService(c.raw),
|
||||
projectID: projectID,
|
||||
}
|
||||
|
||||
for _, opt := range opts {
|
||||
opt.withHMACKeyDesc(&it.desc)
|
||||
}
|
||||
|
||||
it.pageInfo, it.nextFunc = iterator.NewPageInfo(
|
||||
it.fetch,
|
||||
func() int { return len(it.hmacKeys) - it.index },
|
||||
func() interface{} {
|
||||
prev := it.hmacKeys
|
||||
it.hmacKeys = it.hmacKeys[:0]
|
||||
it.index = 0
|
||||
return prev
|
||||
})
|
||||
return it
|
||||
}
|
||||
|
||||
// Next returns the next result. Its second return value is iterator.Done if
|
||||
// there are no more results. Once Next returns iterator.Done, all subsequent
|
||||
// calls will return iterator.Done.
|
||||
//
|
||||
// Note: This iterator is not safe for concurrent operations without explicit synchronization.
|
||||
//
|
||||
// This method is EXPERIMENTAL and subject to change or removal without notice.
|
||||
func (it *HMACKeysIterator) Next() (*HMACKey, error) {
|
||||
if err := it.nextFunc(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
key := it.hmacKeys[it.index]
|
||||
it.index++
|
||||
|
||||
return key, nil
|
||||
}
|
||||
|
||||
// PageInfo supports pagination. See the google.golang.org/api/iterator package for details.
|
||||
//
|
||||
// Note: This iterator is not safe for concurrent operations without explicit synchronization.
|
||||
//
|
||||
// This method is EXPERIMENTAL and subject to change or removal without notice.
|
||||
func (it *HMACKeysIterator) PageInfo() *iterator.PageInfo { return it.pageInfo }
|
||||
|
||||
func (it *HMACKeysIterator) fetch(pageSize int, pageToken string) (token string, err error) {
|
||||
call := it.raw.List(it.projectID)
|
||||
setClientHeader(call.Header())
|
||||
if pageToken != "" {
|
||||
call = call.PageToken(pageToken)
|
||||
}
|
||||
if it.desc.showDeletedKeys {
|
||||
call = call.ShowDeletedKeys(true)
|
||||
}
|
||||
if it.desc.userProjectID != "" {
|
||||
call = call.UserProject(it.desc.userProjectID)
|
||||
}
|
||||
if it.desc.forServiceAccountEmail != "" {
|
||||
call = call.ServiceAccountEmail(it.desc.forServiceAccountEmail)
|
||||
}
|
||||
if pageSize > 0 {
|
||||
call = call.MaxResults(int64(pageSize))
|
||||
}
|
||||
|
||||
ctx := it.ctx
|
||||
var resp *raw.HmacKeysMetadata
|
||||
err = runWithRetry(it.ctx, func() error {
|
||||
resp, err = call.Context(ctx).Do()
|
||||
return err
|
||||
})
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
for _, metadata := range resp.Items {
|
||||
hkPb := &raw.HmacKey{
|
||||
Metadata: metadata,
|
||||
}
|
||||
hkey, err := pbHmacKeyToHMACKey(hkPb, true)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
it.hmacKeys = append(it.hmacKeys, hkey)
|
||||
}
|
||||
return resp.NextPageToken, nil
|
||||
}
|
||||
|
||||
type hmacKeyDesc struct {
|
||||
forServiceAccountEmail string
|
||||
showDeletedKeys bool
|
||||
userProjectID string
|
||||
}
|
||||
|
||||
// HMACKeyOption configures the behavior of HMACKey related methods and actions.
|
||||
//
|
||||
// This interface is EXPERIMENTAL and subject to change or removal without notice.
|
||||
type HMACKeyOption interface {
|
||||
withHMACKeyDesc(*hmacKeyDesc)
|
||||
}
|
||||
|
||||
type hmacKeyDescFunc func(*hmacKeyDesc)
|
||||
|
||||
func (hkdf hmacKeyDescFunc) withHMACKeyDesc(hkd *hmacKeyDesc) {
|
||||
hkdf(hkd)
|
||||
}
|
||||
|
||||
// ForHMACKeyServiceAccountEmail returns HMAC Keys that are
|
||||
// associated with the email address of a service account in the project.
|
||||
//
|
||||
// Only one service account email can be used as a filter, so if multiple
|
||||
// of these options are applied, the last email to be set will be used.
|
||||
//
|
||||
// This option is EXPERIMENTAL and subject to change or removal without notice.
|
||||
func ForHMACKeyServiceAccountEmail(serviceAccountEmail string) HMACKeyOption {
|
||||
return hmacKeyDescFunc(func(hkd *hmacKeyDesc) {
|
||||
hkd.forServiceAccountEmail = serviceAccountEmail
|
||||
})
|
||||
}
|
||||
|
||||
// ShowDeletedHMACKeys will also list keys whose state is "DELETED".
|
||||
//
|
||||
// This option is EXPERIMENTAL and subject to change or removal without notice.
|
||||
func ShowDeletedHMACKeys() HMACKeyOption {
|
||||
return hmacKeyDescFunc(func(hkd *hmacKeyDesc) {
|
||||
hkd.showDeletedKeys = true
|
||||
})
|
||||
}
|
||||
|
||||
// UserProjectForHMACKeys will bill the request against userProjectID
|
||||
// if userProjectID is non-empty.
|
||||
//
|
||||
// Note: This is a noop right now and only provided for API compatibility.
|
||||
//
|
||||
// This option is EXPERIMENTAL and subject to change or removal without notice.
|
||||
func UserProjectForHMACKeys(userProjectID string) HMACKeyOption {
|
||||
return hmacKeyDescFunc(func(hkd *hmacKeyDesc) {
|
||||
hkd.userProjectID = userProjectID
|
||||
})
|
||||
}
|
||||
-162
@@ -1,162 +0,0 @@
|
||||
// Copyright 2017 Google LLC
|
||||
//
|
||||
// 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 (
|
||||
"context"
|
||||
|
||||
"cloud.google.com/go/iam"
|
||||
"cloud.google.com/go/internal/trace"
|
||||
raw "google.golang.org/api/storage/v1"
|
||||
iampb "google.golang.org/genproto/googleapis/iam/v1"
|
||||
"google.golang.org/genproto/googleapis/type/expr"
|
||||
)
|
||||
|
||||
// IAM provides access to IAM access control for the bucket.
|
||||
func (b *BucketHandle) IAM() *iam.Handle {
|
||||
return iam.InternalNewHandleClient(&iamClient{
|
||||
raw: b.c.raw,
|
||||
userProject: b.userProject,
|
||||
}, b.name)
|
||||
}
|
||||
|
||||
// iamClient implements the iam.client interface.
|
||||
type iamClient struct {
|
||||
raw *raw.Service
|
||||
userProject string
|
||||
}
|
||||
|
||||
func (c *iamClient) Get(ctx context.Context, resource string) (p *iampb.Policy, err error) {
|
||||
return c.GetWithVersion(ctx, resource, 1)
|
||||
}
|
||||
|
||||
func (c *iamClient) GetWithVersion(ctx context.Context, resource string, requestedPolicyVersion int32) (p *iampb.Policy, err error) {
|
||||
ctx = trace.StartSpan(ctx, "cloud.google.com/go/storage.IAM.Get")
|
||||
defer func() { trace.EndSpan(ctx, err) }()
|
||||
|
||||
call := c.raw.Buckets.GetIamPolicy(resource).OptionsRequestedPolicyVersion(int64(requestedPolicyVersion))
|
||||
setClientHeader(call.Header())
|
||||
if c.userProject != "" {
|
||||
call.UserProject(c.userProject)
|
||||
}
|
||||
var rp *raw.Policy
|
||||
err = runWithRetry(ctx, func() error {
|
||||
rp, err = call.Context(ctx).Do()
|
||||
return err
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return iamFromStoragePolicy(rp), nil
|
||||
}
|
||||
|
||||
func (c *iamClient) Set(ctx context.Context, resource string, p *iampb.Policy) (err error) {
|
||||
ctx = trace.StartSpan(ctx, "cloud.google.com/go/storage.IAM.Set")
|
||||
defer func() { trace.EndSpan(ctx, err) }()
|
||||
|
||||
rp := iamToStoragePolicy(p)
|
||||
call := c.raw.Buckets.SetIamPolicy(resource, rp)
|
||||
setClientHeader(call.Header())
|
||||
if c.userProject != "" {
|
||||
call.UserProject(c.userProject)
|
||||
}
|
||||
return runWithRetry(ctx, func() error {
|
||||
_, err := call.Context(ctx).Do()
|
||||
return err
|
||||
})
|
||||
}
|
||||
|
||||
func (c *iamClient) Test(ctx context.Context, resource string, perms []string) (permissions []string, err error) {
|
||||
ctx = trace.StartSpan(ctx, "cloud.google.com/go/storage.IAM.Test")
|
||||
defer func() { trace.EndSpan(ctx, err) }()
|
||||
|
||||
call := c.raw.Buckets.TestIamPermissions(resource, perms)
|
||||
setClientHeader(call.Header())
|
||||
if c.userProject != "" {
|
||||
call.UserProject(c.userProject)
|
||||
}
|
||||
var res *raw.TestIamPermissionsResponse
|
||||
err = runWithRetry(ctx, func() error {
|
||||
res, err = call.Context(ctx).Do()
|
||||
return err
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return res.Permissions, nil
|
||||
}
|
||||
|
||||
func iamToStoragePolicy(ip *iampb.Policy) *raw.Policy {
|
||||
return &raw.Policy{
|
||||
Bindings: iamToStorageBindings(ip.Bindings),
|
||||
Etag: string(ip.Etag),
|
||||
Version: int64(ip.Version),
|
||||
}
|
||||
}
|
||||
|
||||
func iamToStorageBindings(ibs []*iampb.Binding) []*raw.PolicyBindings {
|
||||
var rbs []*raw.PolicyBindings
|
||||
for _, ib := range ibs {
|
||||
rbs = append(rbs, &raw.PolicyBindings{
|
||||
Role: ib.Role,
|
||||
Members: ib.Members,
|
||||
Condition: iamToStorageCondition(ib.Condition),
|
||||
})
|
||||
}
|
||||
return rbs
|
||||
}
|
||||
|
||||
func iamToStorageCondition(exprpb *expr.Expr) *raw.Expr {
|
||||
if exprpb == nil {
|
||||
return nil
|
||||
}
|
||||
return &raw.Expr{
|
||||
Expression: exprpb.Expression,
|
||||
Description: exprpb.Description,
|
||||
Location: exprpb.Location,
|
||||
Title: exprpb.Title,
|
||||
}
|
||||
}
|
||||
|
||||
func iamFromStoragePolicy(rp *raw.Policy) *iampb.Policy {
|
||||
return &iampb.Policy{
|
||||
Bindings: iamFromStorageBindings(rp.Bindings),
|
||||
Etag: []byte(rp.Etag),
|
||||
}
|
||||
}
|
||||
|
||||
func iamFromStorageBindings(rbs []*raw.PolicyBindings) []*iampb.Binding {
|
||||
var ibs []*iampb.Binding
|
||||
for _, rb := range rbs {
|
||||
ibs = append(ibs, &iampb.Binding{
|
||||
Role: rb.Role,
|
||||
Members: rb.Members,
|
||||
Condition: iamFromStorageCondition(rb.Condition),
|
||||
})
|
||||
}
|
||||
return ibs
|
||||
}
|
||||
|
||||
func iamFromStorageCondition(rawexpr *raw.Expr) *expr.Expr {
|
||||
if rawexpr == nil {
|
||||
return nil
|
||||
}
|
||||
return &expr.Expr{
|
||||
Expression: rawexpr.Expression,
|
||||
Description: rawexpr.Description,
|
||||
Location: rawexpr.Location,
|
||||
Title: rawexpr.Title,
|
||||
}
|
||||
}
|
||||
-37
@@ -1,37 +0,0 @@
|
||||
// Copyright 2014 Google LLC
|
||||
//
|
||||
// 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 (
|
||||
"context"
|
||||
|
||||
"cloud.google.com/go/internal"
|
||||
gax "github.com/googleapis/gax-go/v2"
|
||||
)
|
||||
|
||||
// runWithRetry calls the function until it returns nil or a non-retryable error, or
|
||||
// the context is done.
|
||||
func runWithRetry(ctx context.Context, call func() error) error {
|
||||
return internal.Retry(ctx, gax.Backoff{}, func() (stop bool, err error) {
|
||||
err = call()
|
||||
if err == nil {
|
||||
return true, nil
|
||||
}
|
||||
if shouldRetry(err) {
|
||||
return false, nil
|
||||
}
|
||||
return true, err
|
||||
})
|
||||
}
|
||||
-42
@@ -1,42 +0,0 @@
|
||||
// Copyright 2017 Google LLC
|
||||
//
|
||||
// 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.
|
||||
|
||||
// +build !go1.10
|
||||
|
||||
package storage
|
||||
|
||||
import (
|
||||
"net/url"
|
||||
"strings"
|
||||
|
||||
"google.golang.org/api/googleapi"
|
||||
)
|
||||
|
||||
func shouldRetry(err error) bool {
|
||||
switch e := err.(type) {
|
||||
case *googleapi.Error:
|
||||
// Retry on 429 and 5xx, according to
|
||||
// https://cloud.google.com/storage/docs/exponential-backoff.
|
||||
return e.Code == 429 || (e.Code >= 500 && e.Code < 600)
|
||||
case *url.Error:
|
||||
// Retry on REFUSED_STREAM.
|
||||
// Unfortunately the error type is unexported, so we resort to string
|
||||
// matching.
|
||||
return strings.Contains(e.Error(), "REFUSED_STREAM")
|
||||
case interface{ Temporary() bool }:
|
||||
return e.Temporary()
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
||||
-188
@@ -1,188 +0,0 @@
|
||||
// Copyright 2017 Google LLC
|
||||
//
|
||||
// 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 (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"regexp"
|
||||
|
||||
"cloud.google.com/go/internal/trace"
|
||||
raw "google.golang.org/api/storage/v1"
|
||||
)
|
||||
|
||||
// A Notification describes how to send Cloud PubSub messages when certain
|
||||
// events occur in a bucket.
|
||||
type Notification struct {
|
||||
//The ID of the notification.
|
||||
ID string
|
||||
|
||||
// The ID of the topic to which this subscription publishes.
|
||||
TopicID string
|
||||
|
||||
// The ID of the project to which the topic belongs.
|
||||
TopicProjectID string
|
||||
|
||||
// Only send notifications about listed event types. If empty, send notifications
|
||||
// for all event types.
|
||||
// See https://cloud.google.com/storage/docs/pubsub-notifications#events.
|
||||
EventTypes []string
|
||||
|
||||
// If present, only apply this notification configuration to object names that
|
||||
// begin with this prefix.
|
||||
ObjectNamePrefix string
|
||||
|
||||
// An optional list of additional attributes to attach to each Cloud PubSub
|
||||
// message published for this notification subscription.
|
||||
CustomAttributes map[string]string
|
||||
|
||||
// The contents of the message payload.
|
||||
// See https://cloud.google.com/storage/docs/pubsub-notifications#payload.
|
||||
PayloadFormat string
|
||||
}
|
||||
|
||||
// Values for Notification.PayloadFormat.
|
||||
const (
|
||||
// Send no payload with notification messages.
|
||||
NoPayload = "NONE"
|
||||
|
||||
// Send object metadata as JSON with notification messages.
|
||||
JSONPayload = "JSON_API_V1"
|
||||
)
|
||||
|
||||
// Values for Notification.EventTypes.
|
||||
const (
|
||||
// Event that occurs when an object is successfully created.
|
||||
ObjectFinalizeEvent = "OBJECT_FINALIZE"
|
||||
|
||||
// Event that occurs when the metadata of an existing object changes.
|
||||
ObjectMetadataUpdateEvent = "OBJECT_METADATA_UPDATE"
|
||||
|
||||
// Event that occurs when an object is permanently deleted.
|
||||
ObjectDeleteEvent = "OBJECT_DELETE"
|
||||
|
||||
// Event that occurs when the live version of an object becomes an
|
||||
// archived version.
|
||||
ObjectArchiveEvent = "OBJECT_ARCHIVE"
|
||||
)
|
||||
|
||||
func toNotification(rn *raw.Notification) *Notification {
|
||||
n := &Notification{
|
||||
ID: rn.Id,
|
||||
EventTypes: rn.EventTypes,
|
||||
ObjectNamePrefix: rn.ObjectNamePrefix,
|
||||
CustomAttributes: rn.CustomAttributes,
|
||||
PayloadFormat: rn.PayloadFormat,
|
||||
}
|
||||
n.TopicProjectID, n.TopicID = parseNotificationTopic(rn.Topic)
|
||||
return n
|
||||
}
|
||||
|
||||
var topicRE = regexp.MustCompile("^//pubsub.googleapis.com/projects/([^/]+)/topics/([^/]+)")
|
||||
|
||||
// parseNotificationTopic extracts the project and topic IDs from from the full
|
||||
// resource name returned by the service. If the name is malformed, it returns
|
||||
// "?" for both IDs.
|
||||
func parseNotificationTopic(nt string) (projectID, topicID string) {
|
||||
matches := topicRE.FindStringSubmatch(nt)
|
||||
if matches == nil {
|
||||
return "?", "?"
|
||||
}
|
||||
return matches[1], matches[2]
|
||||
}
|
||||
|
||||
func toRawNotification(n *Notification) *raw.Notification {
|
||||
return &raw.Notification{
|
||||
Id: n.ID,
|
||||
Topic: fmt.Sprintf("//pubsub.googleapis.com/projects/%s/topics/%s",
|
||||
n.TopicProjectID, n.TopicID),
|
||||
EventTypes: n.EventTypes,
|
||||
ObjectNamePrefix: n.ObjectNamePrefix,
|
||||
CustomAttributes: n.CustomAttributes,
|
||||
PayloadFormat: string(n.PayloadFormat),
|
||||
}
|
||||
}
|
||||
|
||||
// AddNotification adds a notification to b. You must set n's TopicProjectID, TopicID
|
||||
// and PayloadFormat, and must not set its ID. The other fields are all optional. The
|
||||
// returned Notification's ID can be used to refer to it.
|
||||
func (b *BucketHandle) AddNotification(ctx context.Context, n *Notification) (ret *Notification, err error) {
|
||||
ctx = trace.StartSpan(ctx, "cloud.google.com/go/storage.Bucket.AddNotification")
|
||||
defer func() { trace.EndSpan(ctx, err) }()
|
||||
|
||||
if n.ID != "" {
|
||||
return nil, errors.New("storage: AddNotification: ID must not be set")
|
||||
}
|
||||
if n.TopicProjectID == "" {
|
||||
return nil, errors.New("storage: AddNotification: missing TopicProjectID")
|
||||
}
|
||||
if n.TopicID == "" {
|
||||
return nil, errors.New("storage: AddNotification: missing TopicID")
|
||||
}
|
||||
call := b.c.raw.Notifications.Insert(b.name, toRawNotification(n))
|
||||
setClientHeader(call.Header())
|
||||
if b.userProject != "" {
|
||||
call.UserProject(b.userProject)
|
||||
}
|
||||
rn, err := call.Context(ctx).Do()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return toNotification(rn), nil
|
||||
}
|
||||
|
||||
// Notifications returns all the Notifications configured for this bucket, as a map
|
||||
// indexed by notification ID.
|
||||
func (b *BucketHandle) Notifications(ctx context.Context) (n map[string]*Notification, err error) {
|
||||
ctx = trace.StartSpan(ctx, "cloud.google.com/go/storage.Bucket.Notifications")
|
||||
defer func() { trace.EndSpan(ctx, err) }()
|
||||
|
||||
call := b.c.raw.Notifications.List(b.name)
|
||||
setClientHeader(call.Header())
|
||||
if b.userProject != "" {
|
||||
call.UserProject(b.userProject)
|
||||
}
|
||||
var res *raw.Notifications
|
||||
err = runWithRetry(ctx, func() error {
|
||||
res, err = call.Context(ctx).Do()
|
||||
return err
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return notificationsToMap(res.Items), nil
|
||||
}
|
||||
|
||||
func notificationsToMap(rns []*raw.Notification) map[string]*Notification {
|
||||
m := map[string]*Notification{}
|
||||
for _, rn := range rns {
|
||||
m[rn.Id] = toNotification(rn)
|
||||
}
|
||||
return m
|
||||
}
|
||||
|
||||
// DeleteNotification deletes the notification with the given ID.
|
||||
func (b *BucketHandle) DeleteNotification(ctx context.Context, id string) (err error) {
|
||||
ctx = trace.StartSpan(ctx, "cloud.google.com/go/storage.Bucket.DeleteNotification")
|
||||
defer func() { trace.EndSpan(ctx, err) }()
|
||||
|
||||
call := b.c.raw.Notifications.Delete(b.name, id)
|
||||
setClientHeader(call.Header())
|
||||
if b.userProject != "" {
|
||||
call.UserProject(b.userProject)
|
||||
}
|
||||
return call.Context(ctx).Do()
|
||||
}
|
||||
-377
@@ -1,377 +0,0 @@
|
||||
// Copyright 2020 Google LLC
|
||||
//
|
||||
// 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 (
|
||||
"crypto"
|
||||
"crypto/rand"
|
||||
"crypto/rsa"
|
||||
"crypto/sha256"
|
||||
"encoding/base64"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/url"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
// PostPolicyV4Options are used to construct a signed post policy.
|
||||
// Please see https://cloud.google.com/storage/docs/xml-api/post-object
|
||||
// for reference about the fields.
|
||||
type PostPolicyV4Options struct {
|
||||
// GoogleAccessID represents the authorizer of the signed URL generation.
|
||||
// It is typically the Google service account client email address from
|
||||
// the Google Developers Console in the form of "[email protected]".
|
||||
// Required.
|
||||
GoogleAccessID string
|
||||
|
||||
// PrivateKey is the Google service account private key. It is obtainable
|
||||
// from the Google Developers Console.
|
||||
// At https://console.developers.google.com/project/<your-project-id>/apiui/credential,
|
||||
// create a service account client ID or reuse one of your existing service account
|
||||
// credentials. Click on the "Generate new P12 key" to generate and download
|
||||
// a new private key. Once you download the P12 file, use the following command
|
||||
// to convert it into a PEM file.
|
||||
//
|
||||
// $ openssl pkcs12 -in key.p12 -passin pass:notasecret -out key.pem -nodes
|
||||
//
|
||||
// Provide the contents of the PEM file as a byte slice.
|
||||
// Exactly one of PrivateKey or SignBytes must be non-nil.
|
||||
PrivateKey []byte
|
||||
|
||||
// SignBytes is a function for implementing custom signing. For example, if
|
||||
// your application is running on Google App Engine, you can use
|
||||
// appengine's internal signing function:
|
||||
// ctx := appengine.NewContext(request)
|
||||
// acc, _ := appengine.ServiceAccount(ctx)
|
||||
// url, err := SignedURL("bucket", "object", &SignedURLOptions{
|
||||
// GoogleAccessID: acc,
|
||||
// SignBytes: func(b []byte) ([]byte, error) {
|
||||
// _, signedBytes, err := appengine.SignBytes(ctx, b)
|
||||
// return signedBytes, err
|
||||
// },
|
||||
// // etc.
|
||||
// })
|
||||
//
|
||||
// Exactly one of PrivateKey or SignBytes must be non-nil.
|
||||
SignBytes func(hashBytes []byte) (signature []byte, err error)
|
||||
|
||||
// Expires is the expiration time on the signed URL.
|
||||
// It must be a time in the future.
|
||||
// Required.
|
||||
Expires time.Time
|
||||
|
||||
// Style provides options for the type of URL to use. Options are
|
||||
// PathStyle (default), BucketBoundHostname, and VirtualHostedStyle. See
|
||||
// https://cloud.google.com/storage/docs/request-endpoints for details.
|
||||
// Optional.
|
||||
Style URLStyle
|
||||
|
||||
// Insecure when set indicates that the generated URL's scheme
|
||||
// will use "http" instead of "https" (default).
|
||||
// Optional.
|
||||
Insecure bool
|
||||
|
||||
// Fields specifies the attributes of a PostPolicyV4 request.
|
||||
// When Fields is non-nil, its attributes must match those that will
|
||||
// passed into field Conditions.
|
||||
// Optional.
|
||||
Fields *PolicyV4Fields
|
||||
|
||||
// The conditions that the uploaded file will be expected to conform to.
|
||||
// When used, the failure of an upload to satisfy a condition will result in
|
||||
// a 4XX status code, back with the message describing the problem.
|
||||
// Optional.
|
||||
Conditions []PostPolicyV4Condition
|
||||
}
|
||||
|
||||
// PolicyV4Fields describes the attributes for a PostPolicyV4 request.
|
||||
type PolicyV4Fields struct {
|
||||
// ACL specifies the access control permissions for the object.
|
||||
// Optional.
|
||||
ACL string
|
||||
// CacheControl specifies the caching directives for the object.
|
||||
// Optional.
|
||||
CacheControl string
|
||||
// ContentType specifies the media type of the object.
|
||||
// Optional.
|
||||
ContentType string
|
||||
// ContentDisposition specifies how the file will be served back to requesters.
|
||||
// Optional.
|
||||
ContentDisposition string
|
||||
// ContentEncoding specifies the decompressive transcoding that the object.
|
||||
// This field is complementary to ContentType in that the file could be
|
||||
// compressed but ContentType specifies the file's original media type.
|
||||
// Optional.
|
||||
ContentEncoding string
|
||||
// Metadata specifies custom metadata for the object.
|
||||
// If any key doesn't begin with "x-goog-meta-", an error will be returned.
|
||||
// Optional.
|
||||
Metadata map[string]string
|
||||
// StatusCodeOnSuccess when set, specifies the status code that Cloud Storage
|
||||
// will serve back on successful upload of the object.
|
||||
// Optional.
|
||||
StatusCodeOnSuccess int
|
||||
// RedirectToURLOnSuccess when set, specifies the URL that Cloud Storage
|
||||
// will serve back on successful upload of the object.
|
||||
// Optional.
|
||||
RedirectToURLOnSuccess string
|
||||
}
|
||||
|
||||
// PostPolicyV4 describes the URL and respective form fields for a generated PostPolicyV4 request.
|
||||
type PostPolicyV4 struct {
|
||||
// URL is the generated URL that the file upload will be made to.
|
||||
URL string
|
||||
// Fields specifies the generated key-values that the file uploader
|
||||
// must include in their multipart upload form.
|
||||
Fields map[string]string
|
||||
}
|
||||
|
||||
// PostPolicyV4Condition describes the constraints that the subsequent
|
||||
// object upload's multipart form fields will be expected to conform to.
|
||||
type PostPolicyV4Condition interface {
|
||||
isEmpty() bool
|
||||
json.Marshaler
|
||||
}
|
||||
|
||||
type startsWith struct {
|
||||
key, value string
|
||||
}
|
||||
|
||||
func (sw *startsWith) MarshalJSON() ([]byte, error) {
|
||||
return json.Marshal([]string{"starts-with", sw.key, sw.value})
|
||||
}
|
||||
func (sw *startsWith) isEmpty() bool {
|
||||
return sw.value == ""
|
||||
}
|
||||
|
||||
// ConditionStartsWith checks that an attributes starts with value.
|
||||
// An empty value will cause this condition to be ignored.
|
||||
func ConditionStartsWith(key, value string) PostPolicyV4Condition {
|
||||
return &startsWith{key, value}
|
||||
}
|
||||
|
||||
type contentLengthRangeCondition struct {
|
||||
start, end uint64
|
||||
}
|
||||
|
||||
func (clr *contentLengthRangeCondition) MarshalJSON() ([]byte, error) {
|
||||
return json.Marshal([]interface{}{"content-length-range", clr.start, clr.end})
|
||||
}
|
||||
func (clr *contentLengthRangeCondition) isEmpty() bool {
|
||||
return clr.start == 0 && clr.end == 0
|
||||
}
|
||||
|
||||
type singleValueCondition struct {
|
||||
name, value string
|
||||
}
|
||||
|
||||
func (svc *singleValueCondition) MarshalJSON() ([]byte, error) {
|
||||
return json.Marshal(map[string]string{svc.name: svc.value})
|
||||
}
|
||||
func (svc *singleValueCondition) isEmpty() bool {
|
||||
return svc.value == ""
|
||||
}
|
||||
|
||||
// ConditionContentLengthRange constraints the limits that the
|
||||
// multipart upload's range header will be expected to be within.
|
||||
func ConditionContentLengthRange(start, end uint64) PostPolicyV4Condition {
|
||||
return &contentLengthRangeCondition{start, end}
|
||||
}
|
||||
|
||||
func conditionRedirectToURLOnSuccess(redirectURL string) PostPolicyV4Condition {
|
||||
return &singleValueCondition{"success_action_redirect", redirectURL}
|
||||
}
|
||||
|
||||
func conditionStatusCodeOnSuccess(statusCode int) PostPolicyV4Condition {
|
||||
svc := &singleValueCondition{name: "success_action_status"}
|
||||
if statusCode > 0 {
|
||||
svc.value = fmt.Sprintf("%d", statusCode)
|
||||
}
|
||||
return svc
|
||||
}
|
||||
|
||||
// GenerateSignedPostPolicyV4 generates a PostPolicyV4 value from bucket, object and opts.
|
||||
// The generated URL and fields will then allow an unauthenticated client to perform multipart uploads.
|
||||
func GenerateSignedPostPolicyV4(bucket, object string, opts *PostPolicyV4Options) (*PostPolicyV4, error) {
|
||||
if bucket == "" {
|
||||
return nil, errors.New("storage: bucket must be non-empty")
|
||||
}
|
||||
if object == "" {
|
||||
return nil, errors.New("storage: object must be non-empty")
|
||||
}
|
||||
now := utcNow()
|
||||
if err := validatePostPolicyV4Options(opts, now); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var signingFn func(hashedBytes []byte) ([]byte, error)
|
||||
switch {
|
||||
case opts.SignBytes != nil:
|
||||
signingFn = opts.SignBytes
|
||||
|
||||
case len(opts.PrivateKey) != 0:
|
||||
parsedRSAPrivKey, err := parseKey(opts.PrivateKey)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
signingFn = func(hashedBytes []byte) ([]byte, error) {
|
||||
return rsa.SignPKCS1v15(rand.Reader, parsedRSAPrivKey, crypto.SHA256, hashedBytes)
|
||||
}
|
||||
|
||||
default:
|
||||
return nil, errors.New("storage: exactly one of PrivateKey or SignedBytes must be set")
|
||||
}
|
||||
|
||||
var descFields PolicyV4Fields
|
||||
if opts.Fields != nil {
|
||||
descFields = *opts.Fields
|
||||
}
|
||||
|
||||
if err := validateMetadata(descFields.Metadata); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Build the policy.
|
||||
conds := make([]PostPolicyV4Condition, len(opts.Conditions))
|
||||
copy(conds, opts.Conditions)
|
||||
conds = append(conds,
|
||||
conditionRedirectToURLOnSuccess(descFields.RedirectToURLOnSuccess),
|
||||
conditionStatusCodeOnSuccess(descFields.StatusCodeOnSuccess),
|
||||
&singleValueCondition{"acl", descFields.ACL},
|
||||
&singleValueCondition{"cache-control", descFields.CacheControl},
|
||||
)
|
||||
|
||||
YYYYMMDD := now.Format(yearMonthDay)
|
||||
policyFields := map[string]string{
|
||||
"key": object,
|
||||
"x-goog-date": now.Format(iso8601),
|
||||
"x-goog-credential": opts.GoogleAccessID + "/" + YYYYMMDD + "/auto/storage/goog4_request",
|
||||
"x-goog-algorithm": "GOOG4-RSA-SHA256",
|
||||
"success_action_redirect": descFields.RedirectToURLOnSuccess,
|
||||
"acl": descFields.ACL,
|
||||
}
|
||||
for key, value := range descFields.Metadata {
|
||||
conds = append(conds, &singleValueCondition{key, value})
|
||||
policyFields[key] = value
|
||||
}
|
||||
|
||||
// Following from the order expected by the conformance test cases,
|
||||
// hence manually inserting these fields in a specific order.
|
||||
conds = append(conds,
|
||||
&singleValueCondition{"bucket", bucket},
|
||||
&singleValueCondition{"key", object},
|
||||
&singleValueCondition{"x-goog-date", now.Format(iso8601)},
|
||||
&singleValueCondition{
|
||||
name: "x-goog-credential",
|
||||
value: opts.GoogleAccessID + "/" + YYYYMMDD + "/auto/storage/goog4_request",
|
||||
},
|
||||
&singleValueCondition{"x-goog-algorithm", "GOOG4-RSA-SHA256"},
|
||||
)
|
||||
|
||||
nonEmptyConds := make([]PostPolicyV4Condition, 0, len(opts.Conditions))
|
||||
for _, cond := range conds {
|
||||
if cond == nil || !cond.isEmpty() {
|
||||
nonEmptyConds = append(nonEmptyConds, cond)
|
||||
}
|
||||
}
|
||||
condsAsJSON, err := json.Marshal(map[string]interface{}{
|
||||
"conditions": nonEmptyConds,
|
||||
"expiration": opts.Expires.Format(time.RFC3339),
|
||||
})
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("storage: PostPolicyV4 JSON serialization failed: %v", err)
|
||||
}
|
||||
|
||||
b64Policy := base64.StdEncoding.EncodeToString(condsAsJSON)
|
||||
shaSum := sha256.Sum256([]byte(b64Policy))
|
||||
signature, err := signingFn(shaSum[:])
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
policyFields["policy"] = b64Policy
|
||||
policyFields["x-goog-signature"] = fmt.Sprintf("%x", signature)
|
||||
|
||||
// Construct the URL.
|
||||
scheme := "https"
|
||||
if opts.Insecure {
|
||||
scheme = "http"
|
||||
}
|
||||
path := opts.Style.path(bucket, "") + "/"
|
||||
u := &url.URL{
|
||||
Path: path,
|
||||
RawPath: pathEncodeV4(path),
|
||||
Host: opts.Style.host(bucket),
|
||||
Scheme: scheme,
|
||||
}
|
||||
|
||||
if descFields.StatusCodeOnSuccess > 0 {
|
||||
policyFields["success_action_status"] = fmt.Sprintf("%d", descFields.StatusCodeOnSuccess)
|
||||
}
|
||||
|
||||
// Clear out fields with blanks values.
|
||||
for key, value := range policyFields {
|
||||
if value == "" {
|
||||
delete(policyFields, key)
|
||||
}
|
||||
}
|
||||
pp4 := &PostPolicyV4{
|
||||
Fields: policyFields,
|
||||
URL: u.String(),
|
||||
}
|
||||
return pp4, nil
|
||||
}
|
||||
|
||||
// validatePostPolicyV4Options checks that:
|
||||
// * GoogleAccessID is set
|
||||
// * either but not both PrivateKey and SignBytes are set or nil, but not both
|
||||
// * Expires, the deadline is not in the past
|
||||
// * if Style is not set, it'll use PathStyle
|
||||
func validatePostPolicyV4Options(opts *PostPolicyV4Options, now time.Time) error {
|
||||
if opts == nil || opts.GoogleAccessID == "" {
|
||||
return errors.New("storage: missing required GoogleAccessID")
|
||||
}
|
||||
if privBlank, signBlank := len(opts.PrivateKey) == 0, opts.SignBytes == nil; privBlank == signBlank {
|
||||
return errors.New("storage: exactly one of PrivateKey or SignedBytes must be set")
|
||||
}
|
||||
if opts.Expires.Before(now) {
|
||||
return errors.New("storage: expecting Expires to be in the future")
|
||||
}
|
||||
if opts.Style == nil {
|
||||
opts.Style = PathStyle()
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// validateMetadata ensures that all keys passed in have a prefix of "x-goog-meta-",
|
||||
// otherwise it will return an error.
|
||||
func validateMetadata(hdrs map[string]string) (err error) {
|
||||
if len(hdrs) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
badKeys := make([]string, 0, len(hdrs))
|
||||
for key := range hdrs {
|
||||
if !strings.HasPrefix(key, "x-goog-meta-") {
|
||||
badKeys = append(badKeys, key)
|
||||
}
|
||||
}
|
||||
if len(badKeys) != 0 {
|
||||
err = errors.New("storage: expected metadata to begin with x-goog-meta-, got " + strings.Join(badKeys, ", "))
|
||||
}
|
||||
return
|
||||
}
|
||||
-435
@@ -1,435 +0,0 @@
|
||||
// Copyright 2016 Google LLC
|
||||
//
|
||||
// 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 (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"hash/crc32"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"reflect"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"cloud.google.com/go/internal/trace"
|
||||
"google.golang.org/api/googleapi"
|
||||
)
|
||||
|
||||
var crc32cTable = crc32.MakeTable(crc32.Castagnoli)
|
||||
|
||||
// ReaderObjectAttrs are attributes about the object being read. These are populated
|
||||
// during the New call. This struct only holds a subset of object attributes: to
|
||||
// get the full set of attributes, use ObjectHandle.Attrs.
|
||||
//
|
||||
// Each field is read-only.
|
||||
type ReaderObjectAttrs struct {
|
||||
// Size is the length of the object's content.
|
||||
Size int64
|
||||
|
||||
// StartOffset is the byte offset within the object
|
||||
// from which reading begins.
|
||||
// This value is only non-zero for range requests.
|
||||
StartOffset int64
|
||||
|
||||
// ContentType is the MIME type of the object's content.
|
||||
ContentType string
|
||||
|
||||
// ContentEncoding is the encoding of the object's content.
|
||||
ContentEncoding string
|
||||
|
||||
// CacheControl specifies whether and for how long browser and Internet
|
||||
// caches are allowed to cache your objects.
|
||||
CacheControl string
|
||||
|
||||
// LastModified is the time that the object was last modified.
|
||||
LastModified time.Time
|
||||
|
||||
// Generation is the generation number of the object's content.
|
||||
Generation int64
|
||||
|
||||
// Metageneration is the version of the metadata for this object at
|
||||
// this generation. This field is used for preconditions and for
|
||||
// detecting changes in metadata. A metageneration number is only
|
||||
// meaningful in the context of a particular generation of a
|
||||
// particular object.
|
||||
Metageneration int64
|
||||
}
|
||||
|
||||
// NewReader creates a new Reader to read the contents of the
|
||||
// object.
|
||||
// ErrObjectNotExist will be returned if the object is not found.
|
||||
//
|
||||
// The caller must call Close on the returned Reader when done reading.
|
||||
func (o *ObjectHandle) NewReader(ctx context.Context) (*Reader, error) {
|
||||
return o.NewRangeReader(ctx, 0, -1)
|
||||
}
|
||||
|
||||
// NewRangeReader reads part of an object, reading at most length bytes
|
||||
// starting at the given offset. If length is negative, the object is read
|
||||
// until the end. If offset is negative, the object is read abs(offset) bytes
|
||||
// from the end, and length must also be negative to indicate all remaining
|
||||
// bytes will be read.
|
||||
//
|
||||
// If the object's metadata property "Content-Encoding" is set to "gzip" or satisfies
|
||||
// decompressive transcoding per https://cloud.google.com/storage/docs/transcoding
|
||||
// that file will be served back whole, regardless of the requested range as
|
||||
// Google Cloud Storage dictates.
|
||||
func (o *ObjectHandle) NewRangeReader(ctx context.Context, offset, length int64) (r *Reader, err error) {
|
||||
ctx = trace.StartSpan(ctx, "cloud.google.com/go/storage.Object.NewRangeReader")
|
||||
defer func() { trace.EndSpan(ctx, err) }()
|
||||
|
||||
if err := o.validate(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if offset < 0 && length >= 0 {
|
||||
return nil, fmt.Errorf("storage: invalid offset %d < 0 requires negative length", offset)
|
||||
}
|
||||
if o.conds != nil {
|
||||
if err := o.conds.validate("NewRangeReader"); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
u := &url.URL{
|
||||
Scheme: o.c.scheme,
|
||||
Host: o.c.readHost,
|
||||
Path: fmt.Sprintf("/%s/%s", o.bucket, o.object),
|
||||
}
|
||||
verb := "GET"
|
||||
if length == 0 {
|
||||
verb = "HEAD"
|
||||
}
|
||||
req, err := http.NewRequest(verb, u.String(), nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
req = req.WithContext(ctx)
|
||||
if o.userProject != "" {
|
||||
req.Header.Set("X-Goog-User-Project", o.userProject)
|
||||
}
|
||||
if o.readCompressed {
|
||||
req.Header.Set("Accept-Encoding", "gzip")
|
||||
}
|
||||
if err := setEncryptionHeaders(req.Header, o.encryptionKey, false); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
gen := o.gen
|
||||
|
||||
// Define a function that initiates a Read with offset and length, assuming we
|
||||
// have already read seen bytes.
|
||||
reopen := func(seen int64) (*http.Response, error) {
|
||||
start := offset + seen
|
||||
if length < 0 && start < 0 {
|
||||
req.Header.Set("Range", fmt.Sprintf("bytes=%d", start))
|
||||
} else if length < 0 && start > 0 {
|
||||
req.Header.Set("Range", fmt.Sprintf("bytes=%d-", start))
|
||||
} else if length > 0 {
|
||||
// The end character isn't affected by how many bytes we've seen.
|
||||
req.Header.Set("Range", fmt.Sprintf("bytes=%d-%d", start, offset+length-1))
|
||||
}
|
||||
// We wait to assign conditions here because the generation number can change in between reopen() runs.
|
||||
req.URL.RawQuery = conditionsQuery(gen, o.conds)
|
||||
var res *http.Response
|
||||
err = runWithRetry(ctx, func() error {
|
||||
res, err = o.c.hc.Do(req)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if res.StatusCode == http.StatusNotFound {
|
||||
res.Body.Close()
|
||||
return ErrObjectNotExist
|
||||
}
|
||||
if res.StatusCode < 200 || res.StatusCode > 299 {
|
||||
body, _ := ioutil.ReadAll(res.Body)
|
||||
res.Body.Close()
|
||||
return &googleapi.Error{
|
||||
Code: res.StatusCode,
|
||||
Header: res.Header,
|
||||
Body: string(body),
|
||||
}
|
||||
}
|
||||
|
||||
partialContentNotSatisfied :=
|
||||
!decompressiveTranscoding(res) &&
|
||||
start > 0 && length != 0 &&
|
||||
res.StatusCode != http.StatusPartialContent
|
||||
|
||||
if partialContentNotSatisfied {
|
||||
res.Body.Close()
|
||||
return errors.New("storage: partial request not satisfied")
|
||||
}
|
||||
|
||||
// With "Content-Encoding": "gzip" aka decompressive transcoding, GCS serves
|
||||
// back the whole file regardless of the range count passed in as per:
|
||||
// https://cloud.google.com/storage/docs/transcoding#range,
|
||||
// thus we have to manually move the body forward by seen bytes.
|
||||
if decompressiveTranscoding(res) && seen > 0 {
|
||||
_, _ = io.CopyN(ioutil.Discard, res.Body, seen)
|
||||
}
|
||||
|
||||
// If a generation hasn't been specified, and this is the first response we get, let's record the
|
||||
// generation. In future requests we'll use this generation as a precondition to avoid data races.
|
||||
if gen < 0 && res.Header.Get("X-Goog-Generation") != "" {
|
||||
gen64, err := strconv.ParseInt(res.Header.Get("X-Goog-Generation"), 10, 64)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
gen = gen64
|
||||
}
|
||||
return nil
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return res, nil
|
||||
}
|
||||
|
||||
res, err := reopen(0)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var (
|
||||
size int64 // total size of object, even if a range was requested.
|
||||
checkCRC bool
|
||||
crc uint32
|
||||
startOffset int64 // non-zero if range request.
|
||||
)
|
||||
if res.StatusCode == http.StatusPartialContent {
|
||||
cr := strings.TrimSpace(res.Header.Get("Content-Range"))
|
||||
if !strings.HasPrefix(cr, "bytes ") || !strings.Contains(cr, "/") {
|
||||
return nil, fmt.Errorf("storage: invalid Content-Range %q", cr)
|
||||
}
|
||||
size, err = strconv.ParseInt(cr[strings.LastIndex(cr, "/")+1:], 10, 64)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("storage: invalid Content-Range %q", cr)
|
||||
}
|
||||
|
||||
dashIndex := strings.Index(cr, "-")
|
||||
if dashIndex >= 0 {
|
||||
startOffset, err = strconv.ParseInt(cr[len("bytes="):dashIndex], 10, 64)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("storage: invalid Content-Range %q: %v", cr, err)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
size = res.ContentLength
|
||||
// Check the CRC iff all of the following hold:
|
||||
// - We asked for content (length != 0).
|
||||
// - We got all the content (status != PartialContent).
|
||||
// - The server sent a CRC header.
|
||||
// - The Go http stack did not uncompress the file.
|
||||
// - We were not served compressed data that was uncompressed on download.
|
||||
// The problem with the last two cases is that the CRC will not match -- GCS
|
||||
// computes it on the compressed contents, but we compute it on the
|
||||
// uncompressed contents.
|
||||
if length != 0 && !res.Uncompressed && !uncompressedByServer(res) {
|
||||
crc, checkCRC = parseCRC32c(res)
|
||||
}
|
||||
}
|
||||
|
||||
remain := res.ContentLength
|
||||
body := res.Body
|
||||
if length == 0 {
|
||||
remain = 0
|
||||
body.Close()
|
||||
body = emptyBody
|
||||
}
|
||||
var metaGen int64
|
||||
if res.Header.Get("X-Goog-Metageneration") != "" {
|
||||
metaGen, err = strconv.ParseInt(res.Header.Get("X-Goog-Metageneration"), 10, 64)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
var lm time.Time
|
||||
if res.Header.Get("Last-Modified") != "" {
|
||||
lm, err = http.ParseTime(res.Header.Get("Last-Modified"))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
attrs := ReaderObjectAttrs{
|
||||
Size: size,
|
||||
ContentType: res.Header.Get("Content-Type"),
|
||||
ContentEncoding: res.Header.Get("Content-Encoding"),
|
||||
CacheControl: res.Header.Get("Cache-Control"),
|
||||
LastModified: lm,
|
||||
StartOffset: startOffset,
|
||||
Generation: gen,
|
||||
Metageneration: metaGen,
|
||||
}
|
||||
return &Reader{
|
||||
Attrs: attrs,
|
||||
body: body,
|
||||
size: size,
|
||||
remain: remain,
|
||||
wantCRC: crc,
|
||||
checkCRC: checkCRC,
|
||||
reopen: reopen,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// decompressiveTranscoding returns true if the request was served decompressed
|
||||
// and different than its original storage form. This happens when the "Content-Encoding"
|
||||
// header is "gzip".
|
||||
// See:
|
||||
// * https://cloud.google.com/storage/docs/transcoding#transcoding_and_gzip
|
||||
// * https://github.com/googleapis/google-cloud-go/issues/1800
|
||||
func decompressiveTranscoding(res *http.Response) bool {
|
||||
// Decompressive Transcoding.
|
||||
return res.Header.Get("Content-Encoding") == "gzip" ||
|
||||
res.Header.Get("X-Goog-Stored-Content-Encoding") == "gzip"
|
||||
}
|
||||
|
||||
func uncompressedByServer(res *http.Response) bool {
|
||||
// If the data is stored as gzip but is not encoded as gzip, then it
|
||||
// was uncompressed by the server.
|
||||
return res.Header.Get("X-Goog-Stored-Content-Encoding") == "gzip" &&
|
||||
res.Header.Get("Content-Encoding") != "gzip"
|
||||
}
|
||||
|
||||
func parseCRC32c(res *http.Response) (uint32, bool) {
|
||||
const prefix = "crc32c="
|
||||
for _, spec := range res.Header["X-Goog-Hash"] {
|
||||
if strings.HasPrefix(spec, prefix) {
|
||||
c, err := decodeUint32(spec[len(prefix):])
|
||||
if err == nil {
|
||||
return c, true
|
||||
}
|
||||
}
|
||||
}
|
||||
return 0, false
|
||||
}
|
||||
|
||||
var emptyBody = ioutil.NopCloser(strings.NewReader(""))
|
||||
|
||||
// Reader reads a Cloud Storage object.
|
||||
// It implements io.Reader.
|
||||
//
|
||||
// Typically, a Reader computes the CRC of the downloaded content and compares it to
|
||||
// the stored CRC, returning an error from Read if there is a mismatch. This integrity check
|
||||
// is skipped if transcoding occurs. See https://cloud.google.com/storage/docs/transcoding.
|
||||
type Reader struct {
|
||||
Attrs ReaderObjectAttrs
|
||||
body io.ReadCloser
|
||||
seen, remain, size int64
|
||||
checkCRC bool // should we check the CRC?
|
||||
wantCRC uint32 // the CRC32c value the server sent in the header
|
||||
gotCRC uint32 // running crc
|
||||
reopen func(seen int64) (*http.Response, error)
|
||||
}
|
||||
|
||||
// Close closes the Reader. It must be called when done reading.
|
||||
func (r *Reader) Close() error {
|
||||
return r.body.Close()
|
||||
}
|
||||
|
||||
func (r *Reader) Read(p []byte) (int, error) {
|
||||
n, err := r.readWithRetry(p)
|
||||
if r.remain != -1 {
|
||||
r.remain -= int64(n)
|
||||
}
|
||||
if r.checkCRC {
|
||||
r.gotCRC = crc32.Update(r.gotCRC, crc32cTable, p[:n])
|
||||
// Check CRC here. It would be natural to check it in Close, but
|
||||
// everybody defers Close on the assumption that it doesn't return
|
||||
// anything worth looking at.
|
||||
if err == io.EOF {
|
||||
if r.gotCRC != r.wantCRC {
|
||||
return n, fmt.Errorf("storage: bad CRC on read: got %d, want %d",
|
||||
r.gotCRC, r.wantCRC)
|
||||
}
|
||||
}
|
||||
}
|
||||
return n, err
|
||||
}
|
||||
|
||||
func (r *Reader) readWithRetry(p []byte) (int, error) {
|
||||
n := 0
|
||||
for len(p[n:]) > 0 {
|
||||
m, err := r.body.Read(p[n:])
|
||||
n += m
|
||||
r.seen += int64(m)
|
||||
if !shouldRetryRead(err) {
|
||||
return n, err
|
||||
}
|
||||
// Read failed, but we will try again. Send a ranged read request that takes
|
||||
// into account the number of bytes we've already seen.
|
||||
res, err := r.reopen(r.seen)
|
||||
if err != nil {
|
||||
// reopen already retries
|
||||
return n, err
|
||||
}
|
||||
r.body.Close()
|
||||
r.body = res.Body
|
||||
}
|
||||
return n, nil
|
||||
}
|
||||
|
||||
func shouldRetryRead(err error) bool {
|
||||
if err == nil {
|
||||
return false
|
||||
}
|
||||
return strings.HasSuffix(err.Error(), "INTERNAL_ERROR") && strings.Contains(reflect.TypeOf(err).String(), "http2")
|
||||
}
|
||||
|
||||
// Size returns the size of the object in bytes.
|
||||
// The returned value is always the same and is not affected by
|
||||
// calls to Read or Close.
|
||||
//
|
||||
// Deprecated: use Reader.Attrs.Size.
|
||||
func (r *Reader) Size() int64 {
|
||||
return r.Attrs.Size
|
||||
}
|
||||
|
||||
// Remain returns the number of bytes left to read, or -1 if unknown.
|
||||
func (r *Reader) Remain() int64 {
|
||||
return r.remain
|
||||
}
|
||||
|
||||
// ContentType returns the content type of the object.
|
||||
//
|
||||
// Deprecated: use Reader.Attrs.ContentType.
|
||||
func (r *Reader) ContentType() string {
|
||||
return r.Attrs.ContentType
|
||||
}
|
||||
|
||||
// ContentEncoding returns the content encoding of the object.
|
||||
//
|
||||
// Deprecated: use Reader.Attrs.ContentEncoding.
|
||||
func (r *Reader) ContentEncoding() string {
|
||||
return r.Attrs.ContentEncoding
|
||||
}
|
||||
|
||||
// CacheControl returns the cache control of the object.
|
||||
//
|
||||
// Deprecated: use Reader.Attrs.CacheControl.
|
||||
func (r *Reader) CacheControl() string {
|
||||
return r.Attrs.CacheControl
|
||||
}
|
||||
|
||||
// LastModified returns the value of the Last-Modified header.
|
||||
//
|
||||
// Deprecated: use Reader.Attrs.LastModified.
|
||||
func (r *Reader) LastModified() (time.Time, error) {
|
||||
return r.Attrs.LastModified, nil
|
||||
}
|
||||
-1606
File diff suppressed because it is too large
Load Diff
-30067
File diff suppressed because one or more lines are too long
-268
@@ -1,268 +0,0 @@
|
||||
// Copyright 2014 Google LLC
|
||||
//
|
||||
// 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 (
|
||||
"context"
|
||||
"encoding/base64"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"sync"
|
||||
"unicode/utf8"
|
||||
|
||||
"google.golang.org/api/googleapi"
|
||||
raw "google.golang.org/api/storage/v1"
|
||||
)
|
||||
|
||||
// A Writer writes a Cloud Storage object.
|
||||
type Writer struct {
|
||||
// ObjectAttrs are optional attributes to set on the object. Any attributes
|
||||
// must be initialized before the first Write call. Nil or zero-valued
|
||||
// attributes are ignored.
|
||||
ObjectAttrs
|
||||
|
||||
// SendCRC specifies whether to transmit a CRC32C field. It should be set
|
||||
// to true in addition to setting the Writer's CRC32C field, because zero
|
||||
// is a valid CRC and normally a zero would not be transmitted.
|
||||
// If a CRC32C is sent, and the data written does not match the checksum,
|
||||
// the write will be rejected.
|
||||
SendCRC32C bool
|
||||
|
||||
// ChunkSize controls the maximum number of bytes of the object that the
|
||||
// Writer will attempt to send to the server in a single request. Objects
|
||||
// smaller than the size will be sent in a single request, while larger
|
||||
// objects will be split over multiple requests. The size will be rounded up
|
||||
// to the nearest multiple of 256K.
|
||||
//
|
||||
// ChunkSize will default to a reasonable value. If you perform many
|
||||
// concurrent writes of small objects (under ~8MB), you may wish set ChunkSize
|
||||
// to a value that matches your objects' sizes to avoid consuming large
|
||||
// amounts of memory. See
|
||||
// https://cloud.google.com/storage/docs/json_api/v1/how-tos/upload#size
|
||||
// for more information about performance trade-offs related to ChunkSize.
|
||||
//
|
||||
// If ChunkSize is set to zero, chunking will be disabled and the object will
|
||||
// be uploaded in a single request without the use of a buffer. This will
|
||||
// further reduce memory used during uploads, but will also prevent the writer
|
||||
// from retrying in case of a transient error from the server, since a buffer
|
||||
// is required in order to retry the failed request.
|
||||
//
|
||||
// ChunkSize must be set before the first Write call.
|
||||
ChunkSize int
|
||||
|
||||
// ProgressFunc can be used to monitor the progress of a large write.
|
||||
// operation. If ProgressFunc is not nil and writing requires multiple
|
||||
// calls to the underlying service (see
|
||||
// https://cloud.google.com/storage/docs/json_api/v1/how-tos/resumable-upload),
|
||||
// then ProgressFunc will be invoked after each call with the number of bytes of
|
||||
// content copied so far.
|
||||
//
|
||||
// ProgressFunc should return quickly without blocking.
|
||||
ProgressFunc func(int64)
|
||||
|
||||
ctx context.Context
|
||||
o *ObjectHandle
|
||||
|
||||
opened bool
|
||||
pw *io.PipeWriter
|
||||
|
||||
donec chan struct{} // closed after err and obj are set.
|
||||
obj *ObjectAttrs
|
||||
|
||||
mu sync.Mutex
|
||||
err error
|
||||
}
|
||||
|
||||
func (w *Writer) open() error {
|
||||
attrs := w.ObjectAttrs
|
||||
// Check the developer didn't change the object Name (this is unfortunate, but
|
||||
// we don't want to store an object under the wrong name).
|
||||
if attrs.Name != w.o.object {
|
||||
return fmt.Errorf("storage: Writer.Name %q does not match object name %q", attrs.Name, w.o.object)
|
||||
}
|
||||
if !utf8.ValidString(attrs.Name) {
|
||||
return fmt.Errorf("storage: object name %q is not valid UTF-8", attrs.Name)
|
||||
}
|
||||
if attrs.KMSKeyName != "" && w.o.encryptionKey != nil {
|
||||
return errors.New("storage: cannot use KMSKeyName with a customer-supplied encryption key")
|
||||
}
|
||||
pr, pw := io.Pipe()
|
||||
w.pw = pw
|
||||
w.opened = true
|
||||
|
||||
go w.monitorCancel()
|
||||
|
||||
if w.ChunkSize < 0 {
|
||||
return errors.New("storage: Writer.ChunkSize must be non-negative")
|
||||
}
|
||||
mediaOpts := []googleapi.MediaOption{
|
||||
googleapi.ChunkSize(w.ChunkSize),
|
||||
}
|
||||
if c := attrs.ContentType; c != "" {
|
||||
mediaOpts = append(mediaOpts, googleapi.ContentType(c))
|
||||
}
|
||||
|
||||
go func() {
|
||||
defer close(w.donec)
|
||||
|
||||
rawObj := attrs.toRawObject(w.o.bucket)
|
||||
if w.SendCRC32C {
|
||||
rawObj.Crc32c = encodeUint32(attrs.CRC32C)
|
||||
}
|
||||
if w.MD5 != nil {
|
||||
rawObj.Md5Hash = base64.StdEncoding.EncodeToString(w.MD5)
|
||||
}
|
||||
if w.o.c.envHost != "" {
|
||||
w.o.c.raw.BasePath = fmt.Sprintf("%s://%s", w.o.c.scheme, w.o.c.envHost)
|
||||
}
|
||||
call := w.o.c.raw.Objects.Insert(w.o.bucket, rawObj).
|
||||
Media(pr, mediaOpts...).
|
||||
Projection("full").
|
||||
Context(w.ctx).
|
||||
Name(w.o.object)
|
||||
|
||||
if w.ProgressFunc != nil {
|
||||
call.ProgressUpdater(func(n, _ int64) { w.ProgressFunc(n) })
|
||||
}
|
||||
if attrs.KMSKeyName != "" {
|
||||
call.KmsKeyName(attrs.KMSKeyName)
|
||||
}
|
||||
if attrs.PredefinedACL != "" {
|
||||
call.PredefinedAcl(attrs.PredefinedACL)
|
||||
}
|
||||
if err := setEncryptionHeaders(call.Header(), w.o.encryptionKey, false); err != nil {
|
||||
w.mu.Lock()
|
||||
w.err = err
|
||||
w.mu.Unlock()
|
||||
pr.CloseWithError(err)
|
||||
return
|
||||
}
|
||||
var resp *raw.Object
|
||||
err := applyConds("NewWriter", w.o.gen, w.o.conds, call)
|
||||
if err == nil {
|
||||
if w.o.userProject != "" {
|
||||
call.UserProject(w.o.userProject)
|
||||
}
|
||||
setClientHeader(call.Header())
|
||||
|
||||
// The internals that perform call.Do automatically retry both the initial
|
||||
// call to set up the upload as well as calls to upload individual chunks
|
||||
// for a resumable upload (as long as the chunk size is non-zero). Hence
|
||||
// there is no need to add retries here.
|
||||
resp, err = call.Do()
|
||||
}
|
||||
if err != nil {
|
||||
w.mu.Lock()
|
||||
w.err = err
|
||||
w.mu.Unlock()
|
||||
pr.CloseWithError(err)
|
||||
return
|
||||
}
|
||||
w.obj = newObject(resp)
|
||||
}()
|
||||
return nil
|
||||
}
|
||||
|
||||
// Write appends to w. It implements the io.Writer interface.
|
||||
//
|
||||
// Since writes happen asynchronously, Write may return a nil
|
||||
// error even though the write failed (or will fail). Always
|
||||
// use the error returned from Writer.Close to determine if
|
||||
// the upload was successful.
|
||||
//
|
||||
// Writes will be retried on transient errors from the server, unless
|
||||
// Writer.ChunkSize has been set to zero.
|
||||
func (w *Writer) Write(p []byte) (n int, err error) {
|
||||
w.mu.Lock()
|
||||
werr := w.err
|
||||
w.mu.Unlock()
|
||||
if werr != nil {
|
||||
return 0, werr
|
||||
}
|
||||
if !w.opened {
|
||||
if err := w.open(); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
}
|
||||
n, err = w.pw.Write(p)
|
||||
if err != nil {
|
||||
w.mu.Lock()
|
||||
werr := w.err
|
||||
w.mu.Unlock()
|
||||
// Preserve existing functionality that when context is canceled, Write will return
|
||||
// context.Canceled instead of "io: read/write on closed pipe". This hides the
|
||||
// pipe implementation detail from users and makes Write seem as though it's an RPC.
|
||||
if werr == context.Canceled || werr == context.DeadlineExceeded {
|
||||
return n, werr
|
||||
}
|
||||
}
|
||||
return n, err
|
||||
}
|
||||
|
||||
// Close completes the write operation and flushes any buffered data.
|
||||
// If Close doesn't return an error, metadata about the written object
|
||||
// can be retrieved by calling Attrs.
|
||||
func (w *Writer) Close() error {
|
||||
if !w.opened {
|
||||
if err := w.open(); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
// Closing either the read or write causes the entire pipe to close.
|
||||
if err := w.pw.Close(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
<-w.donec
|
||||
w.mu.Lock()
|
||||
defer w.mu.Unlock()
|
||||
return w.err
|
||||
}
|
||||
|
||||
// monitorCancel is intended to be used as a background goroutine. It monitors the
|
||||
// context, and when it observes that the context has been canceled, it manually
|
||||
// closes things that do not take a context.
|
||||
func (w *Writer) monitorCancel() {
|
||||
select {
|
||||
case <-w.ctx.Done():
|
||||
w.mu.Lock()
|
||||
werr := w.ctx.Err()
|
||||
w.err = werr
|
||||
w.mu.Unlock()
|
||||
|
||||
// Closing either the read or write causes the entire pipe to close.
|
||||
w.CloseWithError(werr)
|
||||
case <-w.donec:
|
||||
}
|
||||
}
|
||||
|
||||
// CloseWithError aborts the write operation with the provided error.
|
||||
// CloseWithError always returns nil.
|
||||
//
|
||||
// Deprecated: cancel the context passed to NewWriter instead.
|
||||
func (w *Writer) CloseWithError(err error) error {
|
||||
if !w.opened {
|
||||
return nil
|
||||
}
|
||||
return w.pw.CloseWithError(err)
|
||||
}
|
||||
|
||||
// Attrs returns metadata about a successfully-written object.
|
||||
// It's only valid to call it after Close returns nil.
|
||||
func (w *Writer) Attrs() *ObjectAttrs {
|
||||
return w.obj
|
||||
}
|
||||
-31
@@ -1,31 +0,0 @@
|
||||
// +build tools
|
||||
|
||||
// Copyright 2018 Google LLC
|
||||
//
|
||||
// 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.
|
||||
|
||||
// This package exists to cause `go mod` and `go get` to believe these tools
|
||||
// are dependencies, even though they are not runtime dependencies of any
|
||||
// package (these are tools used by our CI builds). This means they will appear
|
||||
// in our `go.mod` file, but will not be a part of the build. Also, since the
|
||||
// build target is something non-existent, these should not be included in any
|
||||
// binaries.
|
||||
|
||||
package cloud
|
||||
|
||||
import (
|
||||
_ "github.com/golang/protobuf/protoc-gen-go"
|
||||
_ "github.com/jstemmer/go-junit-report"
|
||||
_ "golang.org/x/lint/golint"
|
||||
_ "golang.org/x/tools/cmd/goimports"
|
||||
)
|
||||
-333
@@ -1,333 +0,0 @@
|
||||
# Release History
|
||||
|
||||
## v0.19.0
|
||||
|
||||
### Breaking Changes
|
||||
* Split content out of `azcore` into various packages. The intent is to separate content based on its usage (common, uncommon, SDK authors).
|
||||
* `azcore` has all core functionality.
|
||||
* `log` contains facilities for configuring in-box logging.
|
||||
* `policy` is used for configuring pipeline options and creating custom pipeline policies.
|
||||
* `runtime` contains various helpers used by SDK authors and generated content.
|
||||
* `streaming` has helpers for streaming IO operations.
|
||||
* `NewTelemetryPolicy()` now requires module and version parameters and the `Value` option has been removed.
|
||||
* As a result, the `Request.Telemetry()` method has been removed.
|
||||
* The telemetry policy now includes the SDK prefix `azsdk-go-` so callers no longer need to provide it.
|
||||
* The `*http.Request` in `runtime.Request` is no longer anonymously embedded. Use the `Raw()` method to access it.
|
||||
* The `UserAgent` and `Version` constants have been made internal, `Module` and `Version` respectively.
|
||||
|
||||
### Bug Fixes
|
||||
* Fixed an issue in the retry policy where the request body could be overwritten after a rewind.
|
||||
|
||||
### Other Changes
|
||||
* Moved modules `armcore` and `to` content into `arm` and `to` packages respectively.
|
||||
* The `Pipeline()` method on `armcore.Connection` has been replaced by `NewPipeline()` in `arm.Connection`. It takes module and version parameters used by the telemetry policy.
|
||||
* Poller logic has been consolidated across ARM and core implementations.
|
||||
* This required some changes to the internal interfaces for core pollers.
|
||||
* The core poller types have been improved, including more logging and test coverage.
|
||||
|
||||
## v0.18.1
|
||||
|
||||
### Features Added
|
||||
* Adds an `ETag` type for comparing etags and handling etags on requests
|
||||
* Simplifies the `requestBodyProgess` and `responseBodyProgress` into a single `progress` object
|
||||
|
||||
### Bugs Fixed
|
||||
* `JoinPaths` will preserve query parameters encoded in the `root` url.
|
||||
|
||||
### Other Changes
|
||||
* Bumps dependency on `internal` module to the latest version (v0.7.0)
|
||||
|
||||
## v0.18.0 (2021-07-29)
|
||||
### Features Added
|
||||
* Replaces methods from Logger type with two package methods for interacting with the logging functionality.
|
||||
* `azcore.SetClassifications` replaces `azcore.Logger().SetClassifications`
|
||||
* `azcore.SetListener` replaces `azcore.Logger().SetListener`
|
||||
|
||||
### Breaking Changes
|
||||
* Removes `Logger` type from `azcore`
|
||||
|
||||
|
||||
## v0.17.0 (2021-07-27)
|
||||
### Features Added
|
||||
* Adding TenantID to TokenRequestOptions (https://github.com/Azure/azure-sdk-for-go/pull/14879)
|
||||
* Adding AuxiliaryTenants to AuthenticationOptions (https://github.com/Azure/azure-sdk-for-go/pull/15123)
|
||||
|
||||
### Breaking Changes
|
||||
* Rename `AnonymousCredential` to `NewAnonymousCredential` (https://github.com/Azure/azure-sdk-for-go/pull/15104)
|
||||
* rename `AuthenticationPolicyOptions` to `AuthenticationOptions` (https://github.com/Azure/azure-sdk-for-go/pull/15103)
|
||||
* Make Header constants private (https://github.com/Azure/azure-sdk-for-go/pull/15038)
|
||||
|
||||
|
||||
## v0.16.2 (2021-05-26)
|
||||
### Features Added
|
||||
* Improved support for byte arrays [#14715](https://github.com/Azure/azure-sdk-for-go/pull/14715)
|
||||
|
||||
|
||||
## v0.16.1 (2021-05-19)
|
||||
### Features Added
|
||||
* Add license.txt to azcore module [#14682](https://github.com/Azure/azure-sdk-for-go/pull/14682)
|
||||
|
||||
|
||||
## v0.16.0 (2021-05-07)
|
||||
### Features Added
|
||||
* Remove extra `*` in UnmarshalAsByteArray() [#14642](https://github.com/Azure/azure-sdk-for-go/pull/14642)
|
||||
|
||||
|
||||
## v0.15.1 (2021-05-06)
|
||||
### Features Added
|
||||
* Cache the original request body on Request [#14634](https://github.com/Azure/azure-sdk-for-go/pull/14634)
|
||||
|
||||
|
||||
## v0.15.0 (2021-05-05)
|
||||
### Features Added
|
||||
* Add support for null map and slice
|
||||
* Export `Response.Payload` method
|
||||
|
||||
### Breaking Changes
|
||||
* remove `Response.UnmarshalError` as it's no longer required
|
||||
|
||||
|
||||
## v0.14.5 (2021-04-23)
|
||||
### Features Added
|
||||
* Add `UnmarshalError()` on `azcore.Response`
|
||||
|
||||
|
||||
## v0.14.4 (2021-04-22)
|
||||
### Features Added
|
||||
* Support for basic LRO polling
|
||||
* Added type `LROPoller` and supporting types for basic polling on long running operations.
|
||||
* rename poller param and added doc comment
|
||||
|
||||
### Bugs Fixed
|
||||
* Fixed content type detection bug in logging.
|
||||
|
||||
|
||||
## v0.14.3 (2021-03-29)
|
||||
### Features Added
|
||||
* Add support for multi-part form data
|
||||
* Added method `WriteMultipartFormData()` to Request.
|
||||
|
||||
|
||||
## v0.14.2 (2021-03-17)
|
||||
### Features Added
|
||||
* Add support for encoding JSON null values
|
||||
* Adds `NullValue()` and `IsNullValue()` functions for setting and detecting sentinel values used for encoding a JSON null.
|
||||
* Documentation fixes
|
||||
|
||||
### Bugs Fixed
|
||||
* Fixed improper error wrapping
|
||||
|
||||
|
||||
## v0.14.1 (2021-02-08)
|
||||
### Features Added
|
||||
* Add `Pager` and `Poller` interfaces to azcore
|
||||
|
||||
|
||||
## v0.14.0 (2021-01-12)
|
||||
### Features Added
|
||||
* Accept zero-value options for default values
|
||||
* Specify zero-value options structs to accept default values.
|
||||
* Remove `DefaultXxxOptions()` methods.
|
||||
* Do not silently change TryTimeout on negative values
|
||||
* make per-try timeout opt-in
|
||||
|
||||
|
||||
## v0.13.4 (2020-11-20)
|
||||
### Features Added
|
||||
* Include telemetry string in User Agent
|
||||
|
||||
|
||||
## v0.13.3 (2020-11-20)
|
||||
### Features Added
|
||||
* Updating response body handling on `azcore.Response`
|
||||
|
||||
|
||||
## v0.13.2 (2020-11-13)
|
||||
### Features Added
|
||||
* Remove implementation of stateless policies as first-class functions.
|
||||
|
||||
|
||||
## v0.13.1 (2020-11-05)
|
||||
### Features Added
|
||||
* Add `Telemetry()` method to `azcore.Request()`
|
||||
|
||||
|
||||
## v0.13.0 (2020-10-14)
|
||||
### Features Added
|
||||
* Rename `log` to `logger` to avoid name collision with the log package.
|
||||
* Documentation improvements
|
||||
* Simplified `DefaultHTTPClientTransport()` implementation
|
||||
|
||||
|
||||
## v0.12.1 (2020-10-13)
|
||||
### Features Added
|
||||
* Update `internal` module dependence to `v0.5.0`
|
||||
|
||||
|
||||
## v0.12.0 (2020-10-08)
|
||||
### Features Added
|
||||
* Removed storage specific content
|
||||
* Removed internal content to prevent API clutter
|
||||
* Refactored various policy options to conform with our options pattern
|
||||
|
||||
|
||||
## v0.11.0 (2020-09-22)
|
||||
### Features Added
|
||||
|
||||
* Removed `LogError` and `LogSlowResponse`.
|
||||
* Renamed `options` in `RequestLogOptions`.
|
||||
* Updated `NewRequestLogPolicy()` to follow standard pattern for options.
|
||||
* Refactored `requestLogPolicy.Do()` per above changes.
|
||||
* Cleaned up/added logging in retry policy.
|
||||
* Export `NewResponseError()`
|
||||
* Fix `RequestLogOptions` comment
|
||||
|
||||
|
||||
## v0.10.1 (2020-09-17)
|
||||
### Features Added
|
||||
* Add default console logger
|
||||
* Default console logger writes to stderr. To enable it, set env var `AZURE_SDK_GO_LOGGING` to the value 'all'.
|
||||
* Added `Logger.Writef()` to reduce the need for `ShouldLog()` checks.
|
||||
* Add `LogLongRunningOperation`
|
||||
|
||||
|
||||
## v0.10.0 (2020-09-10)
|
||||
### Features Added
|
||||
* The `request` and `transport` interfaces have been refactored to align with the patterns in the standard library.
|
||||
* `NewRequest()` now uses `http.NewRequestWithContext()` and performs additional validation, it also requires a context parameter.
|
||||
* The `Policy` and `Transport` interfaces have had their context parameter removed as the context is associated with the underlying `http.Request`.
|
||||
* `Pipeline.Do()` will validate the HTTP request before sending it through the pipeline, avoiding retries on a malformed request.
|
||||
* The `Retrier` interface has been replaced with the `NonRetriableError` interface, and the retry policy updated to test for this.
|
||||
* `Request.SetBody()` now requires a content type parameter for setting the request's MIME type.
|
||||
* moved path concatenation into `JoinPaths()` func
|
||||
|
||||
|
||||
## v0.9.6 (2020-08-18)
|
||||
### Features Added
|
||||
* Improvements to body download policy
|
||||
* Always download the response body for error responses, i.e. HTTP status codes >= 400.
|
||||
* Simplify variable declarations
|
||||
|
||||
|
||||
## v0.9.5 (2020-08-11)
|
||||
### Features Added
|
||||
* Set the Content-Length header in `Request.SetBody`
|
||||
|
||||
|
||||
## v0.9.4 (2020-08-03)
|
||||
### Features Added
|
||||
* Fix cancellation of per try timeout
|
||||
* Per try timeout is used to ensure that an HTTP operation doesn't take too long, e.g. that a GET on some URL doesn't take an inordinant amount of time.
|
||||
* Once the HTTP request returns, the per try timeout should be cancelled, not when the response has been read to completion.
|
||||
* Do not drain response body if there are no more retries
|
||||
* Do not retry non-idempotent operations when body download fails
|
||||
|
||||
|
||||
## v0.9.3 (2020-07-28)
|
||||
### Features Added
|
||||
* Add support for custom HTTP request headers
|
||||
* Inserts an internal policy into the pipeline that can extract HTTP header values from the caller's context, adding them to the request.
|
||||
* Use `azcore.WithHTTPHeader` to add HTTP headers to a context.
|
||||
* Remove method specific to Go 1.14
|
||||
|
||||
|
||||
## v0.9.2 (2020-07-28)
|
||||
### Features Added
|
||||
* Omit read-only content from request payloads
|
||||
* If any field in a payload's object graph contains `azure:"ro"`, make a clone of the object graph, omitting all fields with this annotation.
|
||||
* Verify no fields were dropped
|
||||
* Handle embedded struct types
|
||||
* Added test for cloning by value
|
||||
* Add messages to failures
|
||||
|
||||
|
||||
## v0.9.1 (2020-07-22)
|
||||
### Features Added
|
||||
* Updated dependency on internal module to fix race condition.
|
||||
|
||||
|
||||
## v0.9.0 (2020-07-09)
|
||||
### Features Added
|
||||
* Add `HTTPResponse` interface to be used by callers to access the raw HTTP response from an error in the event of an API call failure.
|
||||
* Updated `sdk/internal` dependency to latest version.
|
||||
* Rename package alias
|
||||
|
||||
|
||||
## v0.8.2 (2020-06-29)
|
||||
### Features Added
|
||||
* Added missing documentation comments
|
||||
|
||||
### Bugs Fixed
|
||||
* Fixed a bug in body download policy.
|
||||
|
||||
|
||||
## v0.8.1 (2020-06-26)
|
||||
### Features Added
|
||||
* Miscellaneous clean-up reported by linters
|
||||
|
||||
|
||||
## v0.8.0 (2020-06-01)
|
||||
### Features Added
|
||||
* Differentiate between standard and URL encoding.
|
||||
|
||||
|
||||
## v0.7.1 (2020-05-27)
|
||||
### Features Added
|
||||
* Add support for for base64 encoding and decoding of payloads.
|
||||
|
||||
|
||||
## v0.7.0 (2020-05-12)
|
||||
### Features Added
|
||||
* Change `RetryAfter()` to a function.
|
||||
|
||||
|
||||
## v0.6.0 (2020-04-29)
|
||||
### Features Added
|
||||
* Updating `RetryAfter` to only return the detaion in the RetryAfter header
|
||||
|
||||
|
||||
## v0.5.0 (2020-03-23)
|
||||
### Features Added
|
||||
* Export `TransportFunc`
|
||||
|
||||
### Breaking Changes
|
||||
* Removed `IterationDone`
|
||||
|
||||
|
||||
## v0.4.1 (2020-02-25)
|
||||
### Features Added
|
||||
* Ensure per-try timeout is properly cancelled
|
||||
* Explicitly call cancel the per-try timeout when the response body has been read/closed by the body download policy.
|
||||
* When the response body is returned to the caller for reading/closing, wrap it in a `responseBodyReader` that will cancel the timeout when the body is closed.
|
||||
* `Logger.Should()` will return false if no listener is set.
|
||||
|
||||
|
||||
## v0.4.0 (2020-02-18)
|
||||
### Features Added
|
||||
* Enable custom `RetryOptions` to be specified per API call
|
||||
* Added `WithRetryOptions()` that adds a custom `RetryOptions` to the provided context, allowing custom settings per API call.
|
||||
* Remove 429 from the list of default HTTP status codes for retry.
|
||||
* Change StatusCodesForRetry to a slice so consumers can append to it.
|
||||
* Added support for retry-after in HTTP-date format.
|
||||
* Cleaned up some comments specific to storage.
|
||||
* Remove `Request.SetQueryParam()`
|
||||
* Renamed `MaxTries` to `MaxRetries`
|
||||
|
||||
## v0.3.0 (2020-01-16)
|
||||
### Features Added
|
||||
* Added `DefaultRetryOptions` to create initialized default options.
|
||||
|
||||
### Breaking Changes
|
||||
* Removed `Response.CheckStatusCode()`
|
||||
|
||||
|
||||
## v0.2.0 (2020-01-15)
|
||||
### Features Added
|
||||
* Add support for marshalling and unmarshalling JSON
|
||||
* Removed `Response.Payload` field
|
||||
* Exit early when unmarsahlling if there is no payload
|
||||
|
||||
|
||||
## v0.1.0 (2020-01-10)
|
||||
### Features Added
|
||||
* Initial release
|
||||
-21
@@ -1,21 +0,0 @@
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2021 Microsoft
|
||||
|
||||
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.
|
||||
-39
@@ -1,39 +0,0 @@
|
||||
# Azure Core Client Module for Go
|
||||
|
||||
[](https://pkg.go.dev/github.com/Azure/azure-sdk-for-go/sdk/azcore)
|
||||
[](https://dev.azure.com/azure-sdk/public/_build/latest?definitionId=1843&branchName=main)
|
||||
[](https://img.shields.io/azure-devops/coverage/azure-sdk/public/1843/main)
|
||||
|
||||
The `azcore` module provides a set of common interfaces and types for Go SDK client modules.
|
||||
These modules follow the [Azure SDK Design Guidelines for Go](https://azure.github.io/azure-sdk/golang_introduction.html).
|
||||
|
||||
## Getting started
|
||||
|
||||
This project uses [Go modules](https://github.com/golang/go/wiki/Modules) for versioning and dependency management.
|
||||
|
||||
Typically, you will not need to explicitly install `azcore` as it will be installed as a client module dependency.
|
||||
To add the latest version to your `go.mod` file, execute the following command.
|
||||
|
||||
```bash
|
||||
go get -u github.com/Azure/azure-sdk-for-go/sdk/azcore
|
||||
```
|
||||
|
||||
General documentation and examples can be found on [pkg.go.dev](https://pkg.go.dev/github.com/Azure/azure-sdk-for-go/sdk/azcore).
|
||||
|
||||
## Contributing
|
||||
This project welcomes contributions and suggestions. Most contributions require
|
||||
you to agree to a Contributor License Agreement (CLA) declaring that you have
|
||||
the right to, and actually do, grant us the rights to use your contribution.
|
||||
For details, visit [https://cla.microsoft.com](https://cla.microsoft.com).
|
||||
|
||||
When you submit a pull request, a CLA-bot will automatically determine whether
|
||||
you need to provide a CLA and decorate the PR appropriately (e.g., label,
|
||||
comment). Simply follow the instructions provided by the bot. You will only
|
||||
need to do this once across all repos using our CLA.
|
||||
|
||||
This project has adopted the
|
||||
[Microsoft Open Source Code of Conduct](https://opensource.microsoft.com/codeofconduct/).
|
||||
For more information, see the
|
||||
[Code of Conduct FAQ](https://opensource.microsoft.com/codeofconduct/faq/)
|
||||
or contact [[email protected]](mailto:[email protected]) with any
|
||||
additional questions or comments.
|
||||
-19
@@ -1,19 +0,0 @@
|
||||
# NOTE: Please refer to https://aka.ms/azsdk/engsys/ci-yaml before editing this file.
|
||||
trigger:
|
||||
paths:
|
||||
include:
|
||||
- sdk/azcore/
|
||||
- eng/
|
||||
|
||||
pr:
|
||||
paths:
|
||||
include:
|
||||
- sdk/azcore/
|
||||
- eng/
|
||||
|
||||
|
||||
stages:
|
||||
- template: ../../eng/pipelines/templates/jobs/archetype-sdk-client.yml
|
||||
parameters:
|
||||
ServiceDirectory: 'azcore'
|
||||
RunTests: true
|
||||
-64
@@ -1,64 +0,0 @@
|
||||
//go:build go1.16
|
||||
// +build go1.16
|
||||
|
||||
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
// Licensed under the MIT License.
|
||||
|
||||
package azcore
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
|
||||
"github.com/Azure/azure-sdk-for-go/sdk/azcore/internal/pollers"
|
||||
)
|
||||
|
||||
// holds sentinel values used to send nulls
|
||||
var nullables map[reflect.Type]interface{} = map[reflect.Type]interface{}{}
|
||||
|
||||
// NullValue is used to send an explicit 'null' within a request.
|
||||
// This is typically used in JSON-MERGE-PATCH operations to delete a value.
|
||||
func NullValue(v interface{}) interface{} {
|
||||
t := reflect.TypeOf(v)
|
||||
if k := t.Kind(); k != reflect.Ptr && k != reflect.Slice && k != reflect.Map {
|
||||
// t is not of pointer type, make it be of pointer type
|
||||
t = reflect.PtrTo(t)
|
||||
}
|
||||
v, found := nullables[t]
|
||||
if !found {
|
||||
var o reflect.Value
|
||||
if k := t.Kind(); k == reflect.Slice || k == reflect.Map {
|
||||
o = reflect.New(t) // *[]type / *map[]
|
||||
o = o.Elem() // []type / map[]
|
||||
} else {
|
||||
o = reflect.New(t.Elem())
|
||||
}
|
||||
v = o.Interface()
|
||||
nullables[t] = v
|
||||
}
|
||||
// return the sentinel object
|
||||
return v
|
||||
}
|
||||
|
||||
// IsNullValue returns true if the field contains a null sentinel value.
|
||||
// This is used by custom marshallers to properly encode a null value.
|
||||
func IsNullValue(v interface{}) bool {
|
||||
// see if our map has a sentinel object for this *T
|
||||
t := reflect.TypeOf(v)
|
||||
if k := t.Kind(); k != reflect.Ptr && k != reflect.Slice && k != reflect.Map {
|
||||
// v isn't a pointer type so it can never be a null
|
||||
return false
|
||||
}
|
||||
if o, found := nullables[t]; found {
|
||||
o1 := reflect.ValueOf(o)
|
||||
v1 := reflect.ValueOf(v)
|
||||
// we found it; return true if v points to the sentinel object.
|
||||
// NOTE: maps and slices can only be compared to nil, else you get
|
||||
// a runtime panic. so we compare addresses instead.
|
||||
return o1.Pointer() == v1.Pointer()
|
||||
}
|
||||
// no sentinel object for this *t
|
||||
return false
|
||||
}
|
||||
|
||||
// Poller encapsulates state and logic for polling on long-running operations.
|
||||
type Poller = pollers.Poller
|
||||
-43
@@ -1,43 +0,0 @@
|
||||
//go:build go1.16
|
||||
// +build go1.16
|
||||
|
||||
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
// Licensed under the MIT License.
|
||||
|
||||
package azcore
|
||||
|
||||
import (
|
||||
"context"
|
||||
"time"
|
||||
|
||||
"github.com/Azure/azure-sdk-for-go/sdk/azcore/policy"
|
||||
"github.com/Azure/azure-sdk-for-go/sdk/azcore/runtime"
|
||||
)
|
||||
|
||||
// Credential represents any credential type.
|
||||
type Credential interface {
|
||||
// AuthenticationPolicy returns a policy that requests the credential and applies it to the HTTP request.
|
||||
NewAuthenticationPolicy(options runtime.AuthenticationOptions) policy.Policy
|
||||
}
|
||||
|
||||
// credentialFunc is a type that implements the Credential interface.
|
||||
// Use this type when implementing a stateless credential as a first-class function.
|
||||
type credentialFunc func(options runtime.AuthenticationOptions) policy.Policy
|
||||
|
||||
// AuthenticationPolicy implements the Credential interface on credentialFunc.
|
||||
func (cf credentialFunc) NewAuthenticationPolicy(options runtime.AuthenticationOptions) policy.Policy {
|
||||
return cf(options)
|
||||
}
|
||||
|
||||
// TokenCredential represents a credential capable of providing an OAuth token.
|
||||
type TokenCredential interface {
|
||||
Credential
|
||||
// GetToken requests an access token for the specified set of scopes.
|
||||
GetToken(ctx context.Context, options policy.TokenRequestOptions) (*AccessToken, error)
|
||||
}
|
||||
|
||||
// AccessToken represents an Azure service bearer access token with expiry information.
|
||||
type AccessToken struct {
|
||||
Token string
|
||||
ExpiresOn time.Time
|
||||
}
|
||||
-168
@@ -1,168 +0,0 @@
|
||||
//go:build go1.16
|
||||
// +build go1.16
|
||||
|
||||
// Copyright 2017 Microsoft Corporation. All rights reserved.
|
||||
// Use of this source code is governed by an MIT
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
/*
|
||||
Package azcore implements an HTTP request/response middleware pipeline.
|
||||
|
||||
The middleware consists of three components.
|
||||
|
||||
- One or more Policy instances.
|
||||
- A Transport instance.
|
||||
- A Pipeline instance that combines the Policy and Transport instances.
|
||||
|
||||
Implementing the Policy Interface
|
||||
|
||||
A Policy can be implemented in two ways; as a first-class function for a stateless Policy, or as
|
||||
a method on a type for a stateful Policy. Note that HTTP requests made via the same pipeline share
|
||||
the same Policy instances, so if a Policy mutates its state it MUST be properly synchronized to
|
||||
avoid race conditions.
|
||||
|
||||
A Policy's Do method is called when an HTTP request wants to be sent over the network. The Do method can
|
||||
perform any operation(s) it desires. For example, it can log the outgoing request, mutate the URL, headers,
|
||||
and/or query parameters, inject a failure, etc. Once the Policy has successfully completed its request
|
||||
work, it must call the Next() method on the *azcore.Request instance in order to pass the request to the
|
||||
next Policy in the chain.
|
||||
|
||||
When an HTTP response comes back, the Policy then gets a chance to process the response/error. The Policy instance
|
||||
can log the response, retry the operation if it failed due to a transient error or timeout, unmarshal the response
|
||||
body, etc. Once the Policy has successfully completed its response work, it must return the *azcore.Response
|
||||
and error instances to its caller.
|
||||
|
||||
Template for implementing a stateless Policy:
|
||||
|
||||
func NewMyStatelessPolicy() Policy {
|
||||
return azcore.PolicyFunc(func(req *azcore.Request) (*azcore.Response, error) {
|
||||
// TODO: mutate/process Request here
|
||||
|
||||
// forward Request to next Policy & get Response/error
|
||||
resp, err := req.Next()
|
||||
|
||||
// TODO: mutate/process Response/error here
|
||||
|
||||
// return Response/error to previous Policy
|
||||
return resp, err
|
||||
})
|
||||
}
|
||||
|
||||
Template for implementing a stateful Policy:
|
||||
|
||||
type MyStatefulPolicy struct {
|
||||
// TODO: add configuration/setting fields here
|
||||
}
|
||||
|
||||
// TODO: add initialization args to NewMyStatefulPolicy()
|
||||
func NewMyStatefulPolicy() Policy {
|
||||
return &MyStatefulPolicy{
|
||||
// TODO: initialize configuration/setting fields here
|
||||
}
|
||||
}
|
||||
|
||||
func (p *MyStatefulPolicy) Do(req *azcore.Request) (resp *azcore.Response, err error) {
|
||||
// TODO: mutate/process Request here
|
||||
|
||||
// forward Request to next Policy & get Response/error
|
||||
resp, err := req.Next()
|
||||
|
||||
// TODO: mutate/process Response/error here
|
||||
|
||||
// return Response/error to previous Policy
|
||||
return resp, err
|
||||
}
|
||||
|
||||
Implementing the Transport Interface
|
||||
|
||||
The Transport interface is responsible for sending the HTTP request and returning the corresponding
|
||||
HTTP response or error. The Transport is invoked by the last Policy in the chain. The default Transport
|
||||
implementation uses a shared http.Client from the standard library.
|
||||
|
||||
The same stateful/stateless rules for Policy implementations apply to Transport implementations.
|
||||
|
||||
Using Policy and Transport Instances Via a Pipeline
|
||||
|
||||
To use the Policy and Transport instances, an application passes them to the NewPipeline function.
|
||||
|
||||
func NewPipeline(transport Transport, policies ...Policy) Pipeline
|
||||
|
||||
The specified Policy instances form a chain and are invoked in the order provided to NewPipeline
|
||||
followed by the Transport.
|
||||
|
||||
Once the Pipeline has been created, create a Request instance and pass it to Pipeline's Do method.
|
||||
|
||||
func NewRequest(ctx context.Context, httpMethod string, endpoint string) (*Request, error)
|
||||
|
||||
func (p Pipeline) Do(req *Request) (*Response, error)
|
||||
|
||||
The Pipeline.Do method sends the specified Request through the chain of Policy and Transport
|
||||
instances. The response/error is then sent through the same chain of Policy instances in reverse
|
||||
order. For example, assuming there are Policy types PolicyA, PolicyB, and PolicyC along with
|
||||
TransportA.
|
||||
|
||||
pipeline := NewPipeline(TransportA, PolicyA, PolicyB, PolicyC)
|
||||
|
||||
The flow of Request and Response looks like the following:
|
||||
|
||||
azcore.Request -> PolicyA -> PolicyB -> PolicyC -> TransportA -----+
|
||||
|
|
||||
HTTP(s) endpoint
|
||||
|
|
||||
caller <--------- PolicyA <- PolicyB <- PolicyC <- azcore.Response-+
|
||||
|
||||
Creating a Request Instance
|
||||
|
||||
The Request instance passed to Pipeline's Do method is a wrapper around an *http.Request. It also
|
||||
contains some internal state and provides various convenience methods. You create a Request instance
|
||||
by calling the NewRequest function:
|
||||
|
||||
func NewRequest(ctx context.Context, httpMethod string, endpoint string) (*Request, error)
|
||||
|
||||
If the Request should contain a body, call the SetBody method.
|
||||
|
||||
func (req *Request) SetBody(body ReadSeekCloser, contentType string) error
|
||||
|
||||
A seekable stream is required so that upon retry, the retry Policy instance can seek the stream
|
||||
back to the beginning before retrying the network request and re-uploading the body.
|
||||
|
||||
Sending an Explicit Null
|
||||
|
||||
Operations like JSON-MERGE-PATCH send a JSON null to indicate a value should be deleted.
|
||||
|
||||
{
|
||||
"delete-me": null
|
||||
}
|
||||
|
||||
This requirement conflicts with the SDK's default marshalling that specifies "omitempty" as
|
||||
a means to resolve the ambiguity between a field to be excluded and its zero-value.
|
||||
|
||||
type Widget struct {
|
||||
Name *string `json:",omitempty"`
|
||||
Count *int `json:",omitempty"`
|
||||
}
|
||||
|
||||
In the above example, Name and Count are defined as pointer-to-type to disambiguate between
|
||||
a missing value (nil) and a zero-value (0) which might have semantic differences.
|
||||
|
||||
In a PATCH operation, any fields left as `nil` are to have their values preserved. When updating
|
||||
a Widget's count, one simply specifies the new value for Count, leaving Name nil.
|
||||
|
||||
To fulfill the requirement for sending a JSON null, the NullValue() function can be used.
|
||||
|
||||
w := Widget{
|
||||
Count: azcore.NullValue(0).(*int),
|
||||
}
|
||||
|
||||
This sends an explict "null" for Count, indicating that any current value for Count should be deleted.
|
||||
|
||||
Processing the Response
|
||||
|
||||
When the HTTP response is received, the underlying *http.Response is wrapped in a Response type.
|
||||
The Response type contains various convenience methods, like testing the HTTP response code and
|
||||
unmarshalling the response body in a particular format.
|
||||
|
||||
The Response is returned through all the Policy instances. Each Policy instance can inspect/mutate the
|
||||
embedded *http.Response.
|
||||
*/
|
||||
package azcore
|
||||
-26
@@ -1,26 +0,0 @@
|
||||
//go:build go1.16
|
||||
// +build go1.16
|
||||
|
||||
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
// Licensed under the MIT License.
|
||||
|
||||
package azcore
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"github.com/Azure/azure-sdk-for-go/sdk/azcore/internal/shared"
|
||||
"github.com/Azure/azure-sdk-for-go/sdk/internal/errorinfo"
|
||||
)
|
||||
|
||||
// HTTPResponse provides access to an HTTP response when available.
|
||||
// Errors returned from failed API calls will implement this interface.
|
||||
// Use errors.As() to access this interface in the error chain.
|
||||
// If there was no HTTP response then this interface will be omitted
|
||||
// from any error in the chain.
|
||||
type HTTPResponse interface {
|
||||
RawResponse() *http.Response
|
||||
}
|
||||
|
||||
var _ HTTPResponse = (*shared.ResponseError)(nil)
|
||||
var _ errorinfo.NonRetriable = (*shared.ResponseError)(nil)
|
||||
-48
@@ -1,48 +0,0 @@
|
||||
//go:build go1.16
|
||||
// +build go1.16
|
||||
|
||||
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
// Licensed under the MIT License.
|
||||
|
||||
package azcore
|
||||
|
||||
import (
|
||||
"strings"
|
||||
)
|
||||
|
||||
// ETag is a property used for optimistic concurrency during updates
|
||||
// ETag is a validator based on https://tools.ietf.org/html/rfc7232#section-2.3.2
|
||||
// An ETag can be empty ("").
|
||||
type ETag string
|
||||
|
||||
// ETagAny is an ETag that represents everything, the value is "*"
|
||||
const ETagAny ETag = "*"
|
||||
|
||||
// Equals does a strong comparison of two ETags. Equals returns true when both
|
||||
// ETags are not weak and the values of the underlying strings are equal.
|
||||
func (e ETag) Equals(other ETag) bool {
|
||||
return !e.IsWeak() && !other.IsWeak() && e == other
|
||||
}
|
||||
|
||||
// WeakEquals does a weak comparison of two ETags. Two ETags are equivalent if their opaque-tags match
|
||||
// character-by-character, regardless of either or both being tagged as "weak".
|
||||
func (e ETag) WeakEquals(other ETag) bool {
|
||||
getStart := func(e1 ETag) int {
|
||||
if e1.IsWeak() {
|
||||
return 2
|
||||
}
|
||||
return 0
|
||||
}
|
||||
aStart := getStart(e)
|
||||
bStart := getStart(other)
|
||||
|
||||
aVal := e[aStart:]
|
||||
bVal := other[bStart:]
|
||||
|
||||
return aVal == bVal
|
||||
}
|
||||
|
||||
// IsWeak specifies whether the ETag is strong or weak.
|
||||
func (e ETag) IsWeak() bool {
|
||||
return len(e) >= 4 && strings.HasPrefix(string(e), "W/\"") && strings.HasSuffix(string(e), "\"")
|
||||
}
|
||||
Generated
Vendored
-93
@@ -1,93 +0,0 @@
|
||||
//go:build go1.16
|
||||
// +build go1.16
|
||||
|
||||
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
// Licensed under the MIT License.
|
||||
|
||||
package pipeline
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/http"
|
||||
|
||||
"golang.org/x/net/http/httpguts"
|
||||
)
|
||||
|
||||
// Policy represents an extensibility point for the Pipeline that can mutate the specified
|
||||
// Request and react to the received Response.
|
||||
type Policy interface {
|
||||
// Do applies the policy to the specified Request. When implementing a Policy, mutate the
|
||||
// request before calling req.Next() to move on to the next policy, and respond to the result
|
||||
// before returning to the caller.
|
||||
Do(req *Request) (*http.Response, error)
|
||||
}
|
||||
|
||||
// Pipeline represents a primitive for sending HTTP requests and receiving responses.
|
||||
// Its behavior can be extended by specifying policies during construction.
|
||||
type Pipeline struct {
|
||||
policies []Policy
|
||||
}
|
||||
|
||||
// Transporter represents an HTTP pipeline transport used to send HTTP requests and receive responses.
|
||||
type Transporter interface {
|
||||
// Do sends the HTTP request and returns the HTTP response or error.
|
||||
Do(req *http.Request) (*http.Response, error)
|
||||
}
|
||||
|
||||
// used to adapt a TransportPolicy to a Policy
|
||||
type transportPolicy struct {
|
||||
trans Transporter
|
||||
}
|
||||
|
||||
func (tp transportPolicy) Do(req *Request) (*http.Response, error) {
|
||||
if tp.trans == nil {
|
||||
return nil, errors.New("missing transporter")
|
||||
}
|
||||
resp, err := tp.trans.Do(req.Raw())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
} else if resp == nil {
|
||||
// there was no response and no error (rare but can happen)
|
||||
// this ensures the retry policy will retry the request
|
||||
return nil, errors.New("received nil response")
|
||||
}
|
||||
return resp, nil
|
||||
}
|
||||
|
||||
// NewPipeline creates a new Pipeline object from the specified Policies.
|
||||
func NewPipeline(transport Transporter, policies ...Policy) Pipeline {
|
||||
// transport policy must always be the last in the slice
|
||||
policies = append(policies, transportPolicy{trans: transport})
|
||||
return Pipeline{
|
||||
policies: policies,
|
||||
}
|
||||
}
|
||||
|
||||
// Do is called for each and every HTTP request. It passes the request through all
|
||||
// the Policy objects (which can transform the Request's URL/query parameters/headers)
|
||||
// and ultimately sends the transformed HTTP request over the network.
|
||||
func (p Pipeline) Do(req *Request) (*http.Response, error) {
|
||||
if req == nil {
|
||||
return nil, errors.New("request cannot be nil")
|
||||
}
|
||||
// check copied from Transport.roundTrip()
|
||||
for k, vv := range req.Raw().Header {
|
||||
if !httpguts.ValidHeaderFieldName(k) {
|
||||
if req.Raw().Body != nil {
|
||||
req.Raw().Body.Close()
|
||||
}
|
||||
return nil, fmt.Errorf("invalid header field name %q", k)
|
||||
}
|
||||
for _, v := range vv {
|
||||
if !httpguts.ValidHeaderFieldValue(v) {
|
||||
if req.Raw().Body != nil {
|
||||
req.Raw().Body.Close()
|
||||
}
|
||||
return nil, fmt.Errorf("invalid header field value %q for key %v", v, k)
|
||||
}
|
||||
}
|
||||
}
|
||||
req.policies = p.policies
|
||||
return req.Next()
|
||||
}
|
||||
-169
@@ -1,169 +0,0 @@
|
||||
//go:build go1.16
|
||||
// +build go1.16
|
||||
|
||||
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
// Licensed under the MIT License.
|
||||
|
||||
package pipeline
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"reflect"
|
||||
"strconv"
|
||||
|
||||
"github.com/Azure/azure-sdk-for-go/sdk/azcore/internal/shared"
|
||||
)
|
||||
|
||||
// PolicyFunc is a type that implements the Policy interface.
|
||||
// Use this type when implementing a stateless policy as a first-class function.
|
||||
type PolicyFunc func(*Request) (*http.Response, error)
|
||||
|
||||
// Do implements the Policy interface on PolicyFunc.
|
||||
func (pf PolicyFunc) Do(req *Request) (*http.Response, error) {
|
||||
return pf(req)
|
||||
}
|
||||
|
||||
// Request is an abstraction over the creation of an HTTP request as it passes through the pipeline.
|
||||
// Don't use this type directly, use NewRequest() instead.
|
||||
type Request struct {
|
||||
req *http.Request
|
||||
body io.ReadSeekCloser
|
||||
policies []Policy
|
||||
values opValues
|
||||
}
|
||||
|
||||
type opValues map[reflect.Type]interface{}
|
||||
|
||||
// Set adds/changes a value
|
||||
func (ov opValues) set(value interface{}) {
|
||||
ov[reflect.TypeOf(value)] = value
|
||||
}
|
||||
|
||||
// Get looks for a value set by SetValue first
|
||||
func (ov opValues) get(value interface{}) bool {
|
||||
v, ok := ov[reflect.ValueOf(value).Elem().Type()]
|
||||
if ok {
|
||||
reflect.ValueOf(value).Elem().Set(reflect.ValueOf(v))
|
||||
}
|
||||
return ok
|
||||
}
|
||||
|
||||
// NewRequest creates a new Request with the specified input.
|
||||
func NewRequest(ctx context.Context, httpMethod string, endpoint string) (*Request, error) {
|
||||
req, err := http.NewRequestWithContext(ctx, httpMethod, endpoint, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if req.URL.Host == "" {
|
||||
return nil, errors.New("no Host in request URL")
|
||||
}
|
||||
if !(req.URL.Scheme == "http" || req.URL.Scheme == "https") {
|
||||
return nil, fmt.Errorf("unsupported protocol scheme %s", req.URL.Scheme)
|
||||
}
|
||||
return &Request{req: req}, nil
|
||||
}
|
||||
|
||||
// Body returns the original body specified when the Request was created.
|
||||
func (req *Request) Body() io.ReadSeekCloser {
|
||||
return req.body
|
||||
}
|
||||
|
||||
// Raw returns the underlying HTTP request.
|
||||
func (req *Request) Raw() *http.Request {
|
||||
return req.req
|
||||
}
|
||||
|
||||
// Next calls the next policy in the pipeline.
|
||||
// If there are no more policies, nil and an error are returned.
|
||||
// This method is intended to be called from pipeline policies.
|
||||
// To send a request through a pipeline call Pipeline.Do().
|
||||
func (req *Request) Next() (*http.Response, error) {
|
||||
if len(req.policies) == 0 {
|
||||
return nil, errors.New("no more policies")
|
||||
}
|
||||
nextPolicy := req.policies[0]
|
||||
nextReq := *req
|
||||
nextReq.policies = nextReq.policies[1:]
|
||||
return nextPolicy.Do(&nextReq)
|
||||
}
|
||||
|
||||
// SetOperationValue adds/changes a mutable key/value associated with a single operation.
|
||||
func (req *Request) SetOperationValue(value interface{}) {
|
||||
if req.values == nil {
|
||||
req.values = opValues{}
|
||||
}
|
||||
req.values.set(value)
|
||||
}
|
||||
|
||||
// OperationValue looks for a value set by SetOperationValue().
|
||||
func (req *Request) OperationValue(value interface{}) bool {
|
||||
if req.values == nil {
|
||||
return false
|
||||
}
|
||||
return req.values.get(value)
|
||||
}
|
||||
|
||||
// SetBody sets the specified ReadSeekCloser as the HTTP request body.
|
||||
func (req *Request) SetBody(body io.ReadSeekCloser, contentType string) error {
|
||||
// Set the body and content length.
|
||||
size, err := body.Seek(0, io.SeekEnd) // Seek to the end to get the stream's size
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if size == 0 {
|
||||
body.Close()
|
||||
return nil
|
||||
}
|
||||
_, err = body.Seek(0, io.SeekStart)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
req.Raw().GetBody = func() (io.ReadCloser, error) {
|
||||
_, err := body.Seek(0, io.SeekStart) // Seek back to the beginning of the stream
|
||||
return body, err
|
||||
}
|
||||
// keep a copy of the original body. this is to handle cases
|
||||
// where req.Body is replaced, e.g. httputil.DumpRequest and friends.
|
||||
req.body = body
|
||||
req.req.Body = body
|
||||
req.req.ContentLength = size
|
||||
req.req.Header.Set(shared.HeaderContentType, contentType)
|
||||
req.req.Header.Set(shared.HeaderContentLength, strconv.FormatInt(size, 10))
|
||||
return nil
|
||||
}
|
||||
|
||||
// SkipBodyDownload will disable automatic downloading of the response body.
|
||||
func (req *Request) SkipBodyDownload() {
|
||||
req.SetOperationValue(shared.BodyDownloadPolicyOpValues{Skip: true})
|
||||
}
|
||||
|
||||
// RewindBody seeks the request's Body stream back to the beginning so it can be resent when retrying an operation.
|
||||
func (req *Request) RewindBody() error {
|
||||
if req.body != nil {
|
||||
// Reset the stream back to the beginning and restore the body
|
||||
_, err := req.body.Seek(0, io.SeekStart)
|
||||
req.req.Body = req.body
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Close closes the request body.
|
||||
func (req *Request) Close() error {
|
||||
if req.body == nil {
|
||||
return nil
|
||||
}
|
||||
return req.body.Close()
|
||||
}
|
||||
|
||||
// Clone returns a deep copy of the request with its context changed to ctx.
|
||||
func (req *Request) Clone(ctx context.Context) *Request {
|
||||
r2 := Request{}
|
||||
r2 = *req
|
||||
r2.req = req.req.Clone(ctx)
|
||||
return &r2
|
||||
}
|
||||
-80
@@ -1,80 +0,0 @@
|
||||
//go:build go1.16
|
||||
// +build go1.16
|
||||
|
||||
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
// Licensed under the MIT License.
|
||||
|
||||
package loc
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/http"
|
||||
|
||||
"github.com/Azure/azure-sdk-for-go/sdk/azcore/internal/pollers"
|
||||
"github.com/Azure/azure-sdk-for-go/sdk/azcore/internal/shared"
|
||||
"github.com/Azure/azure-sdk-for-go/sdk/internal/log"
|
||||
)
|
||||
|
||||
// Kind is the identifier of this type in a resume token.
|
||||
const Kind = "Location"
|
||||
|
||||
// Applicable returns true if the LRO is using Location.
|
||||
func Applicable(resp *http.Response) bool {
|
||||
return resp.Header.Get(shared.HeaderLocation) != ""
|
||||
}
|
||||
|
||||
// Poller is an LRO poller that uses the Location pattern.
|
||||
type Poller struct {
|
||||
Type string `json:"type"`
|
||||
PollURL string `json:"pollURL"`
|
||||
CurState int `json:"state"`
|
||||
}
|
||||
|
||||
// New creates a new Poller from the provided initial response.
|
||||
func New(resp *http.Response, pollerID string) (*Poller, error) {
|
||||
log.Write(log.LongRunningOperation, "Using Location poller.")
|
||||
locURL := resp.Header.Get(shared.HeaderLocation)
|
||||
if locURL == "" {
|
||||
return nil, errors.New("response is missing Location header")
|
||||
}
|
||||
if !pollers.IsValidURL(locURL) {
|
||||
return nil, fmt.Errorf("invalid polling URL %s", locURL)
|
||||
}
|
||||
return &Poller{
|
||||
Type: pollers.MakeID(pollerID, Kind),
|
||||
PollURL: locURL,
|
||||
CurState: resp.StatusCode,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (p *Poller) URL() string {
|
||||
return p.PollURL
|
||||
}
|
||||
|
||||
func (p *Poller) Done() bool {
|
||||
return pollers.IsTerminalState(p.Status())
|
||||
}
|
||||
|
||||
func (p *Poller) Update(resp *http.Response) error {
|
||||
// if the endpoint returned a location header, update cached value
|
||||
if loc := resp.Header.Get(shared.HeaderLocation); loc != "" {
|
||||
p.PollURL = loc
|
||||
}
|
||||
p.CurState = resp.StatusCode
|
||||
return nil
|
||||
}
|
||||
|
||||
func (*Poller) FinalGetURL() string {
|
||||
return ""
|
||||
}
|
||||
|
||||
func (p *Poller) Status() string {
|
||||
if p.CurState == http.StatusAccepted {
|
||||
return pollers.StatusInProgress
|
||||
} else if p.CurState > 199 && p.CurState < 300 {
|
||||
// any 2xx other than a 202 indicates success
|
||||
return pollers.StatusSucceeded
|
||||
}
|
||||
return pollers.StatusFailed
|
||||
}
|
||||
-132
@@ -1,132 +0,0 @@
|
||||
//go:build go1.16
|
||||
// +build go1.16
|
||||
|
||||
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
// Licensed under the MIT License.
|
||||
|
||||
package op
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/http"
|
||||
|
||||
"github.com/Azure/azure-sdk-for-go/sdk/azcore/internal/pollers"
|
||||
"github.com/Azure/azure-sdk-for-go/sdk/azcore/internal/shared"
|
||||
"github.com/Azure/azure-sdk-for-go/sdk/internal/log"
|
||||
)
|
||||
|
||||
// Kind is the identifier of this type in a resume token.
|
||||
const Kind = "Operation-Location"
|
||||
|
||||
// Applicable returns true if the LRO is using Operation-Location.
|
||||
func Applicable(resp *http.Response) bool {
|
||||
return resp.Header.Get(shared.HeaderOperationLocation) != ""
|
||||
}
|
||||
|
||||
// Poller is an LRO poller that uses the Operation-Location pattern.
|
||||
type Poller struct {
|
||||
Type string `json:"type"`
|
||||
PollURL string `json:"pollURL"`
|
||||
LocURL string `json:"locURL"`
|
||||
FinalGET string `json:"finalGET"`
|
||||
CurState string `json:"state"`
|
||||
}
|
||||
|
||||
// New creates a new Poller from the provided initial response.
|
||||
func New(resp *http.Response, pollerID string) (*Poller, error) {
|
||||
log.Write(log.LongRunningOperation, "Using Operation-Location poller.")
|
||||
opURL := resp.Header.Get(shared.HeaderOperationLocation)
|
||||
if opURL == "" {
|
||||
return nil, errors.New("response is missing Operation-Location header")
|
||||
}
|
||||
if !pollers.IsValidURL(opURL) {
|
||||
return nil, fmt.Errorf("invalid Operation-Location URL %s", opURL)
|
||||
}
|
||||
locURL := resp.Header.Get(shared.HeaderLocation)
|
||||
// Location header is optional
|
||||
if locURL != "" && !pollers.IsValidURL(locURL) {
|
||||
return nil, fmt.Errorf("invalid Location URL %s", locURL)
|
||||
}
|
||||
// default initial state to InProgress. if the
|
||||
// service sent us a status then use that instead.
|
||||
curState := pollers.StatusInProgress
|
||||
status, err := getValue(resp, "status")
|
||||
if err != nil && !errors.Is(err, shared.ErrNoBody) {
|
||||
return nil, err
|
||||
}
|
||||
if status != "" {
|
||||
curState = status
|
||||
}
|
||||
// calculate the tentative final GET URL.
|
||||
// can change if we receive a resourceLocation.
|
||||
// it's ok for it to be empty in some cases.
|
||||
finalGET := ""
|
||||
if resp.Request.Method == http.MethodPatch || resp.Request.Method == http.MethodPut {
|
||||
finalGET = resp.Request.URL.String()
|
||||
} else if resp.Request.Method == http.MethodPost && locURL != "" {
|
||||
finalGET = locURL
|
||||
}
|
||||
return &Poller{
|
||||
Type: pollers.MakeID(pollerID, Kind),
|
||||
PollURL: opURL,
|
||||
LocURL: locURL,
|
||||
FinalGET: finalGET,
|
||||
CurState: curState,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (p *Poller) URL() string {
|
||||
return p.PollURL
|
||||
}
|
||||
|
||||
func (p *Poller) Done() bool {
|
||||
return pollers.IsTerminalState(p.Status())
|
||||
}
|
||||
|
||||
func (p *Poller) Update(resp *http.Response) error {
|
||||
status, err := getValue(resp, "status")
|
||||
if err != nil {
|
||||
return err
|
||||
} else if status == "" {
|
||||
return errors.New("the response did not contain a status")
|
||||
}
|
||||
p.CurState = status
|
||||
// if the endpoint returned an operation-location header, update cached value
|
||||
if opLoc := resp.Header.Get(shared.HeaderOperationLocation); opLoc != "" {
|
||||
p.PollURL = opLoc
|
||||
}
|
||||
// check for resourceLocation
|
||||
resLoc, err := getValue(resp, "resourceLocation")
|
||||
if err != nil && !errors.Is(err, shared.ErrNoBody) {
|
||||
return err
|
||||
} else if resLoc != "" {
|
||||
p.FinalGET = resLoc
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (p *Poller) FinalGetURL() string {
|
||||
return p.FinalGET
|
||||
}
|
||||
|
||||
func (p *Poller) Status() string {
|
||||
return p.CurState
|
||||
}
|
||||
|
||||
func getValue(resp *http.Response, val string) (string, error) {
|
||||
jsonBody, err := shared.GetJSON(resp)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
v, ok := jsonBody[val]
|
||||
if !ok {
|
||||
// it might be ok if the field doesn't exist, the caller must make that determination
|
||||
return "", nil
|
||||
}
|
||||
vv, ok := v.(string)
|
||||
if !ok {
|
||||
return "", fmt.Errorf("the %s value %v was not in string format", val, v)
|
||||
}
|
||||
return vv, nil
|
||||
}
|
||||
-213
@@ -1,213 +0,0 @@
|
||||
//go:build go1.16
|
||||
// +build go1.16
|
||||
|
||||
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
// Licensed under the MIT License.
|
||||
|
||||
package pollers
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"reflect"
|
||||
"time"
|
||||
|
||||
"github.com/Azure/azure-sdk-for-go/sdk/azcore/internal/pipeline"
|
||||
"github.com/Azure/azure-sdk-for-go/sdk/azcore/internal/shared"
|
||||
"github.com/Azure/azure-sdk-for-go/sdk/internal/log"
|
||||
)
|
||||
|
||||
// KindFromToken extracts the poller kind from the provided token.
|
||||
// If the pollerID doesn't match what's in the token an error is returned.
|
||||
func KindFromToken(pollerID, token string) (string, error) {
|
||||
// unmarshal into JSON object to determine the poller type
|
||||
obj := map[string]interface{}{}
|
||||
err := json.Unmarshal([]byte(token), &obj)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
t, ok := obj["type"]
|
||||
if !ok {
|
||||
return "", errors.New("missing type field")
|
||||
}
|
||||
tt, ok := t.(string)
|
||||
if !ok {
|
||||
return "", fmt.Errorf("invalid type format %T", t)
|
||||
}
|
||||
ttID, ttKind, err := DecodeID(tt)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
// ensure poller types match
|
||||
if ttID != pollerID {
|
||||
return "", fmt.Errorf("cannot resume from this poller token. expected %s, received %s", pollerID, ttID)
|
||||
}
|
||||
return ttKind, nil
|
||||
}
|
||||
|
||||
// PollerType returns the concrete type of the poller (FOR TESTING PURPOSES).
|
||||
func PollerType(p *Poller) reflect.Type {
|
||||
return reflect.TypeOf(p.lro)
|
||||
}
|
||||
|
||||
// NewPoller creates a Poller from the specified input.
|
||||
func NewPoller(lro Operation, resp *http.Response, pl pipeline.Pipeline, eu func(*http.Response) error) *Poller {
|
||||
return &Poller{lro: lro, pl: pl, eu: eu, resp: resp}
|
||||
}
|
||||
|
||||
// Poller encapsulates state and logic for polling on long-running operations.
|
||||
type Poller struct {
|
||||
lro Operation
|
||||
pl pipeline.Pipeline
|
||||
eu func(*http.Response) error
|
||||
resp *http.Response
|
||||
err error
|
||||
}
|
||||
|
||||
// Done returns true if the LRO has reached a terminal state.
|
||||
func (l *Poller) Done() bool {
|
||||
if l.err != nil {
|
||||
return true
|
||||
}
|
||||
return l.lro.Done()
|
||||
}
|
||||
|
||||
// Poll sends a polling request to the polling endpoint and returns the response or error.
|
||||
func (l *Poller) Poll(ctx context.Context) (*http.Response, error) {
|
||||
if l.Done() {
|
||||
// the LRO has reached a terminal state, don't poll again
|
||||
if l.resp != nil {
|
||||
return l.resp, nil
|
||||
}
|
||||
return nil, l.err
|
||||
}
|
||||
req, err := pipeline.NewRequest(ctx, http.MethodGet, l.lro.URL())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
resp, err := l.pl.Do(req)
|
||||
if err != nil {
|
||||
// don't update the poller for failed requests
|
||||
return nil, err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
if !StatusCodeValid(resp) {
|
||||
// the LRO failed. unmarshall the error and update state
|
||||
l.err = l.eu(resp)
|
||||
l.resp = nil
|
||||
return nil, l.err
|
||||
}
|
||||
if err = l.lro.Update(resp); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
l.resp = resp
|
||||
log.Writef(log.LongRunningOperation, "Status %s", l.lro.Status())
|
||||
if Failed(l.lro.Status()) {
|
||||
l.err = l.eu(resp)
|
||||
l.resp = nil
|
||||
return nil, l.err
|
||||
}
|
||||
return l.resp, nil
|
||||
}
|
||||
|
||||
// ResumeToken returns a token string that can be used to resume a poller that has not yet reached a terminal state.
|
||||
func (l *Poller) ResumeToken() (string, error) {
|
||||
if l.Done() {
|
||||
return "", errors.New("cannot create a ResumeToken from a poller in a terminal state")
|
||||
}
|
||||
b, err := json.Marshal(l.lro)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return string(b), nil
|
||||
}
|
||||
|
||||
// FinalResponse will perform a final GET request and return the final HTTP response for the polling
|
||||
// operation and unmarshall the content of the payload into the respType interface that is provided.
|
||||
func (l *Poller) FinalResponse(ctx context.Context, respType interface{}) (*http.Response, error) {
|
||||
if !l.Done() {
|
||||
return nil, errors.New("cannot return a final response from a poller in a non-terminal state")
|
||||
}
|
||||
// update l.resp with the content from final GET if applicable
|
||||
if u := l.lro.FinalGetURL(); u != "" {
|
||||
log.Write(log.LongRunningOperation, "Performing final GET.")
|
||||
req, err := pipeline.NewRequest(ctx, http.MethodGet, u)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
resp, err := l.pl.Do(req)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if !StatusCodeValid(resp) {
|
||||
return nil, l.eu(resp)
|
||||
}
|
||||
l.resp = resp
|
||||
}
|
||||
// if there's nothing to unmarshall into or no response body just return the final response
|
||||
if respType == nil {
|
||||
return l.resp, nil
|
||||
} else if l.resp.StatusCode == http.StatusNoContent || l.resp.ContentLength == 0 {
|
||||
log.Write(log.LongRunningOperation, "final response specifies a response type but no payload was received")
|
||||
return l.resp, nil
|
||||
}
|
||||
body, err := ioutil.ReadAll(l.resp.Body)
|
||||
l.resp.Body.Close()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err = json.Unmarshal(body, respType); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return l.resp, nil
|
||||
}
|
||||
|
||||
// PollUntilDone will handle the entire span of the polling operation until a terminal state is reached,
|
||||
// then return the final HTTP response for the polling operation and unmarshal the content of the payload
|
||||
// into the respType interface that is provided.
|
||||
// freq - the time to wait between polling intervals if the endpoint doesn't send a Retry-After header.
|
||||
// A good starting value is 30 seconds. Note that some resources might benefit from a different value.
|
||||
func (l *Poller) PollUntilDone(ctx context.Context, freq time.Duration, respType interface{}) (*http.Response, error) {
|
||||
start := time.Now()
|
||||
logPollUntilDoneExit := func(v interface{}) {
|
||||
log.Writef(log.LongRunningOperation, "END PollUntilDone() for %T: %v, total time: %s", l.lro, v, time.Since(start))
|
||||
}
|
||||
log.Writef(log.LongRunningOperation, "BEGIN PollUntilDone() for %T", l.lro)
|
||||
if l.resp != nil {
|
||||
// initial check for a retry-after header existing on the initial response
|
||||
if retryAfter := shared.RetryAfter(l.resp); retryAfter > 0 {
|
||||
log.Writef(log.LongRunningOperation, "initial Retry-After delay for %s", retryAfter.String())
|
||||
if err := shared.Delay(ctx, retryAfter); err != nil {
|
||||
logPollUntilDoneExit(err)
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
}
|
||||
// begin polling the endpoint until a terminal state is reached
|
||||
for {
|
||||
resp, err := l.Poll(ctx)
|
||||
if err != nil {
|
||||
logPollUntilDoneExit(err)
|
||||
return nil, err
|
||||
}
|
||||
if l.Done() {
|
||||
logPollUntilDoneExit(l.lro.Status())
|
||||
return l.FinalResponse(ctx, respType)
|
||||
}
|
||||
d := freq
|
||||
if retryAfter := shared.RetryAfter(resp); retryAfter > 0 {
|
||||
log.Writef(log.LongRunningOperation, "Retry-After delay for %s", retryAfter.String())
|
||||
d = retryAfter
|
||||
} else {
|
||||
log.Writef(log.LongRunningOperation, "delay for %s", d.String())
|
||||
}
|
||||
if err = shared.Delay(ctx, d); err != nil {
|
||||
logPollUntilDoneExit(err)
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
}
|
||||
-99
@@ -1,99 +0,0 @@
|
||||
//go:build go1.16
|
||||
// +build go1.16
|
||||
|
||||
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
// Licensed under the MIT License.
|
||||
|
||||
package pollers
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"strings"
|
||||
|
||||
"github.com/Azure/azure-sdk-for-go/sdk/azcore/internal/shared"
|
||||
)
|
||||
|
||||
const (
|
||||
StatusSucceeded = "Succeeded"
|
||||
StatusCanceled = "Canceled"
|
||||
StatusFailed = "Failed"
|
||||
StatusInProgress = "InProgress"
|
||||
)
|
||||
|
||||
// Operation abstracts the differences between concrete poller types.
|
||||
type Operation interface {
|
||||
Done() bool
|
||||
Update(resp *http.Response) error
|
||||
FinalGetURL() string
|
||||
URL() string
|
||||
Status() string
|
||||
}
|
||||
|
||||
// IsTerminalState returns true if the LRO's state is terminal.
|
||||
func IsTerminalState(s string) bool {
|
||||
return strings.EqualFold(s, StatusSucceeded) || strings.EqualFold(s, StatusFailed) || strings.EqualFold(s, StatusCanceled)
|
||||
}
|
||||
|
||||
// Failed returns true if the LRO's state is terminal failure.
|
||||
func Failed(s string) bool {
|
||||
return strings.EqualFold(s, StatusFailed) || strings.EqualFold(s, StatusCanceled)
|
||||
}
|
||||
|
||||
// returns true if the LRO response contains a valid HTTP status code
|
||||
func StatusCodeValid(resp *http.Response) bool {
|
||||
return shared.HasStatusCode(resp, http.StatusOK, http.StatusAccepted, http.StatusCreated, http.StatusNoContent)
|
||||
}
|
||||
|
||||
// IsValidURL verifies that the URL is valid and absolute.
|
||||
func IsValidURL(s string) bool {
|
||||
u, err := url.Parse(s)
|
||||
return err == nil && u.IsAbs()
|
||||
}
|
||||
|
||||
const idSeparator = ";"
|
||||
|
||||
// MakeID returns the poller ID from the provided values.
|
||||
func MakeID(pollerID string, kind string) string {
|
||||
return fmt.Sprintf("%s%s%s", pollerID, idSeparator, kind)
|
||||
}
|
||||
|
||||
// DecodeID decodes the poller ID, returning [pollerID, kind] or an error.
|
||||
func DecodeID(tk string) (string, string, error) {
|
||||
raw := strings.Split(tk, idSeparator)
|
||||
// strings.Split will include any/all whitespace strings, we want to omit those
|
||||
parts := []string{}
|
||||
for _, r := range raw {
|
||||
if s := strings.TrimSpace(r); s != "" {
|
||||
parts = append(parts, s)
|
||||
}
|
||||
}
|
||||
if len(parts) != 2 {
|
||||
return "", "", fmt.Errorf("invalid token %s", tk)
|
||||
}
|
||||
return parts[0], parts[1], nil
|
||||
}
|
||||
|
||||
// used if the operation synchronously completed
|
||||
type NopPoller struct{}
|
||||
|
||||
func (*NopPoller) URL() string {
|
||||
return ""
|
||||
}
|
||||
|
||||
func (*NopPoller) Done() bool {
|
||||
return true
|
||||
}
|
||||
|
||||
func (*NopPoller) Update(*http.Response) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (*NopPoller) FinalGetURL() string {
|
||||
return ""
|
||||
}
|
||||
|
||||
func (*NopPoller) Status() string {
|
||||
return StatusSucceeded
|
||||
}
|
||||
-34
@@ -1,34 +0,0 @@
|
||||
//go:build go1.16
|
||||
// +build go1.16
|
||||
|
||||
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
// Licensed under the MIT License.
|
||||
|
||||
package shared
|
||||
|
||||
const (
|
||||
ContentTypeAppJSON = "application/json"
|
||||
ContentTypeAppXML = "application/xml"
|
||||
)
|
||||
|
||||
const (
|
||||
HeaderAzureAsync = "Azure-AsyncOperation"
|
||||
HeaderContentLength = "Content-Length"
|
||||
HeaderContentType = "Content-Type"
|
||||
HeaderLocation = "Location"
|
||||
HeaderOperationLocation = "Operation-Location"
|
||||
HeaderRetryAfter = "Retry-After"
|
||||
HeaderUserAgent = "User-Agent"
|
||||
)
|
||||
|
||||
const (
|
||||
DefaultMaxRetries = 3
|
||||
)
|
||||
|
||||
const (
|
||||
// Module is the name of the calling module used in telemetry data.
|
||||
Module = "azcore"
|
||||
|
||||
// Version is the semantic version (see http://semver.org) of this module.
|
||||
Version = "v0.19.0"
|
||||
)
|
||||
-148
@@ -1,148 +0,0 @@
|
||||
//go:build go1.16
|
||||
// +build go1.16
|
||||
|
||||
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
// Licensed under the MIT License.
|
||||
|
||||
package shared
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"strconv"
|
||||
"time"
|
||||
)
|
||||
|
||||
// CtxWithHTTPHeaderKey is used as a context key for adding/retrieving http.Header.
|
||||
type CtxWithHTTPHeaderKey struct{}
|
||||
|
||||
// CtxWithRetryOptionsKey is used as a context key for adding/retrieving RetryOptions.
|
||||
type CtxWithRetryOptionsKey struct{}
|
||||
|
||||
type nopCloser struct {
|
||||
io.ReadSeeker
|
||||
}
|
||||
|
||||
func (n nopCloser) Close() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// NopCloser returns a ReadSeekCloser with a no-op close method wrapping the provided io.ReadSeeker.
|
||||
func NopCloser(rs io.ReadSeeker) io.ReadSeekCloser {
|
||||
return nopCloser{rs}
|
||||
}
|
||||
|
||||
// BodyDownloadPolicyOpValues is the struct containing the per-operation values
|
||||
type BodyDownloadPolicyOpValues struct {
|
||||
Skip bool
|
||||
}
|
||||
|
||||
func NewResponseError(inner error, resp *http.Response) error {
|
||||
return &ResponseError{inner: inner, resp: resp}
|
||||
}
|
||||
|
||||
type ResponseError struct {
|
||||
inner error
|
||||
resp *http.Response
|
||||
}
|
||||
|
||||
// Error implements the error interface for type ResponseError.
|
||||
func (e *ResponseError) Error() string {
|
||||
return e.inner.Error()
|
||||
}
|
||||
|
||||
// Unwrap returns the inner error.
|
||||
func (e *ResponseError) Unwrap() error {
|
||||
return e.inner
|
||||
}
|
||||
|
||||
// RawResponse returns the HTTP response associated with this error.
|
||||
func (e *ResponseError) RawResponse() *http.Response {
|
||||
return e.resp
|
||||
}
|
||||
|
||||
// NonRetriable indicates this error is non-transient.
|
||||
func (e *ResponseError) NonRetriable() {
|
||||
// marker method
|
||||
}
|
||||
|
||||
// Delay waits for the duration to elapse or the context to be cancelled.
|
||||
func Delay(ctx context.Context, delay time.Duration) error {
|
||||
select {
|
||||
case <-time.After(delay):
|
||||
return nil
|
||||
case <-ctx.Done():
|
||||
return ctx.Err()
|
||||
}
|
||||
}
|
||||
|
||||
// ErrNoBody is returned if the response didn't contain a body.
|
||||
var ErrNoBody = errors.New("the response did not contain a body")
|
||||
|
||||
// GetJSON reads the response body into a raw JSON object.
|
||||
// It returns ErrNoBody if there was no content.
|
||||
func GetJSON(resp *http.Response) (map[string]interface{}, error) {
|
||||
body, err := ioutil.ReadAll(resp.Body)
|
||||
defer resp.Body.Close()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if len(body) == 0 {
|
||||
return nil, ErrNoBody
|
||||
}
|
||||
// put the body back so it's available to others
|
||||
resp.Body = ioutil.NopCloser(bytes.NewReader(body))
|
||||
// unmarshall the body to get the value
|
||||
var jsonBody map[string]interface{}
|
||||
if err = json.Unmarshal(body, &jsonBody); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return jsonBody, nil
|
||||
}
|
||||
|
||||
// RetryAfter returns non-zero if the response contains a Retry-After header value.
|
||||
func RetryAfter(resp *http.Response) time.Duration {
|
||||
if resp == nil {
|
||||
return 0
|
||||
}
|
||||
ra := resp.Header.Get(HeaderRetryAfter)
|
||||
if ra == "" {
|
||||
return 0
|
||||
}
|
||||
// retry-after values are expressed in either number of
|
||||
// seconds or an HTTP-date indicating when to try again
|
||||
if retryAfter, _ := strconv.Atoi(ra); retryAfter > 0 {
|
||||
return time.Duration(retryAfter) * time.Second
|
||||
} else if t, err := time.Parse(time.RFC1123, ra); err == nil {
|
||||
return time.Until(t)
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
// HasStatusCode returns true if the Response's status code is one of the specified values.
|
||||
func HasStatusCode(resp *http.Response, statusCodes ...int) bool {
|
||||
if resp == nil {
|
||||
return false
|
||||
}
|
||||
for _, sc := range statusCodes {
|
||||
if resp.StatusCode == sc {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
const defaultScope = "/.default"
|
||||
|
||||
// EndpointToScope converts the provided URL endpoint to its default scope.
|
||||
func EndpointToScope(endpoint string) string {
|
||||
if endpoint[len(endpoint)-1] != '/' {
|
||||
endpoint += "/"
|
||||
}
|
||||
return endpoint + defaultScope
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user