Merged master, fixed conflict
This commit is contained in:
commit
32b067af0c
|
|
@ -1,6 +1,20 @@
|
|||
# This file is autogenerated, do not edit; changes may be undone by the next 'dep ensure'.
|
||||
|
||||
|
||||
[[projects]]
|
||||
name = "cloud.google.com/go"
|
||||
packages = [
|
||||
"compute/metadata",
|
||||
"iam",
|
||||
"internal",
|
||||
"internal/optional",
|
||||
"internal/trace",
|
||||
"internal/version",
|
||||
"storage"
|
||||
]
|
||||
revision = "4b98a6370e36d7a85192e7bad08a4ebd82eac2a8"
|
||||
version = "v0.20.0"
|
||||
|
||||
[[projects]]
|
||||
name = "github.com/BurntSushi/toml"
|
||||
packages = ["."]
|
||||
|
|
@ -227,10 +241,23 @@
|
|||
|
||||
[[projects]]
|
||||
name = "github.com/golang/protobuf"
|
||||
packages = ["proto"]
|
||||
packages = [
|
||||
"proto",
|
||||
"protoc-gen-go/descriptor",
|
||||
"ptypes",
|
||||
"ptypes/any",
|
||||
"ptypes/duration",
|
||||
"ptypes/timestamp"
|
||||
]
|
||||
revision = "925541529c1fa6821df4e44ce2723319eb2be768"
|
||||
version = "v1.0.0"
|
||||
|
||||
[[projects]]
|
||||
name = "github.com/googleapis/gax-go"
|
||||
packages = ["."]
|
||||
revision = "317e0006254c44a0ac427cc52a0e083ff0b9622f"
|
||||
version = "v2.0.0"
|
||||
|
||||
[[projects]]
|
||||
name = "github.com/gorilla/context"
|
||||
packages = ["."]
|
||||
|
|
@ -412,6 +439,24 @@
|
|||
revision = "38ec4ddb06dedbea0a895c4848b248eb38af221b"
|
||||
version = "v0.10.2"
|
||||
|
||||
[[projects]]
|
||||
name = "go.opencensus.io"
|
||||
packages = [
|
||||
"exporter/stackdriver/propagation",
|
||||
"internal",
|
||||
"internal/tagencoding",
|
||||
"plugin/ochttp",
|
||||
"plugin/ochttp/propagation/b3",
|
||||
"stats",
|
||||
"stats/internal",
|
||||
"stats/view",
|
||||
"tag",
|
||||
"trace",
|
||||
"trace/propagation"
|
||||
]
|
||||
revision = "6e3f034057826b530038d93267906ec3c012183f"
|
||||
version = "v0.6.0"
|
||||
|
||||
[[projects]]
|
||||
branch = "master"
|
||||
name = "golang.org/x/crypto"
|
||||
|
|
@ -436,11 +481,25 @@
|
|||
"http2",
|
||||
"http2/hpack",
|
||||
"idna",
|
||||
"internal/timeseries",
|
||||
"lex/httplex",
|
||||
"proxy"
|
||||
"proxy",
|
||||
"trace"
|
||||
]
|
||||
revision = "07e8617a6db2368fa55d4616f371ee1b1403c817"
|
||||
|
||||
[[projects]]
|
||||
branch = "master"
|
||||
name = "golang.org/x/oauth2"
|
||||
packages = [
|
||||
".",
|
||||
"google",
|
||||
"internal",
|
||||
"jws",
|
||||
"jwt"
|
||||
]
|
||||
revision = "fdc9e635145ae97e6c2cb777c48305600cf515cb"
|
||||
|
||||
[[projects]]
|
||||
branch = "master"
|
||||
name = "golang.org/x/sys"
|
||||
|
|
@ -471,6 +530,80 @@
|
|||
revision = "f21a4dfb5e38f5895301dc265a8def02365cc3d0"
|
||||
version = "v0.3.0"
|
||||
|
||||
[[projects]]
|
||||
branch = "master"
|
||||
name = "google.golang.org/api"
|
||||
packages = [
|
||||
"gensupport",
|
||||
"googleapi",
|
||||
"googleapi/internal/uritemplates",
|
||||
"googleapi/transport",
|
||||
"internal",
|
||||
"iterator",
|
||||
"option",
|
||||
"storage/v1",
|
||||
"transport/http"
|
||||
]
|
||||
revision = "e4126357c891acdef6dcd7805daa4c6533be6544"
|
||||
|
||||
[[projects]]
|
||||
name = "google.golang.org/appengine"
|
||||
packages = [
|
||||
".",
|
||||
"internal",
|
||||
"internal/app_identity",
|
||||
"internal/base",
|
||||
"internal/datastore",
|
||||
"internal/log",
|
||||
"internal/modules",
|
||||
"internal/remote_api",
|
||||
"internal/urlfetch",
|
||||
"urlfetch"
|
||||
]
|
||||
revision = "150dc57a1b433e64154302bdc40b6bb8aefa313a"
|
||||
version = "v1.0.0"
|
||||
|
||||
[[projects]]
|
||||
branch = "master"
|
||||
name = "google.golang.org/genproto"
|
||||
packages = [
|
||||
"googleapis/api/annotations",
|
||||
"googleapis/iam/v1",
|
||||
"googleapis/rpc/code",
|
||||
"googleapis/rpc/status"
|
||||
]
|
||||
revision = "ab0870e398d5dd054b868c0db1481ab029b9a9f2"
|
||||
|
||||
[[projects]]
|
||||
name = "google.golang.org/grpc"
|
||||
packages = [
|
||||
".",
|
||||
"balancer",
|
||||
"balancer/base",
|
||||
"balancer/roundrobin",
|
||||
"codes",
|
||||
"connectivity",
|
||||
"credentials",
|
||||
"encoding",
|
||||
"encoding/proto",
|
||||
"grpclb/grpc_lb_v1/messages",
|
||||
"grpclog",
|
||||
"internal",
|
||||
"keepalive",
|
||||
"metadata",
|
||||
"naming",
|
||||
"peer",
|
||||
"resolver",
|
||||
"resolver/dns",
|
||||
"resolver/passthrough",
|
||||
"stats",
|
||||
"status",
|
||||
"tap",
|
||||
"transport"
|
||||
]
|
||||
revision = "8e4536a86ab602859c20df5ebfd0bd4228d08655"
|
||||
version = "v1.10.0"
|
||||
|
||||
[[projects]]
|
||||
name = "gopkg.in/cheggaaa/pb.v1"
|
||||
packages = ["."]
|
||||
|
|
@ -492,6 +625,6 @@
|
|||
[solve-meta]
|
||||
analyzer-name = "dep"
|
||||
analyzer-version = 1
|
||||
inputs-digest = "061d7ecaab8da6e0a543987bf870b813d2f8c88481c6cdffec33374150b66c98"
|
||||
inputs-digest = "46a5e132ee4e288f54fbe425376c80ce19d01bce195ad56d0ff74602ace9402f"
|
||||
solver-name = "gps-cdcl"
|
||||
solver-version = 1
|
||||
|
|
|
|||
2
Makefile
2
Makefile
|
|
@ -35,7 +35,7 @@ EXECUTOR_PACKAGE = $(REPOPATH)/executor
|
|||
KBUILD_PACKAGE = $(REPOPATH)/kbuild
|
||||
|
||||
out/executor: $(GO_FILES)
|
||||
GOOS=$* GOARCH=$(GOARCH) CGO_ENABLED=0 go build -ldflags $(GO_LDFLAGS) -tags $(GO_BUILD_TAGS) -o $@ $(EXECUTOR_PACKAGE)
|
||||
GOARCH=$(GOARCH) GOOS=linux CGO_ENABLED=0 go build -ldflags $(GO_LDFLAGS) -tags $(GO_BUILD_TAGS) -o $@ $(EXECUTOR_PACKAGE)
|
||||
|
||||
|
||||
out/kbuild: $(GO_FILES)
|
||||
|
|
|
|||
|
|
@ -21,3 +21,4 @@ ADD files/docker-credential-gcr /usr/local/bin/
|
|||
ADD files/config.json /root/.docker/
|
||||
ENV HOME /root
|
||||
ENV PATH /usr/local/bin
|
||||
ENTRYPOINT ["/kbuild/executor"]
|
||||
|
|
|
|||
|
|
@ -23,22 +23,26 @@ import (
|
|||
"github.com/GoogleCloudPlatform/k8s-container-builder/pkg/image"
|
||||
"github.com/GoogleCloudPlatform/k8s-container-builder/pkg/snapshot"
|
||||
"github.com/GoogleCloudPlatform/k8s-container-builder/pkg/util"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/sirupsen/logrus"
|
||||
"github.com/spf13/cobra"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path/filepath"
|
||||
)
|
||||
|
||||
var (
|
||||
dockerfilePath string
|
||||
destination string
|
||||
srcContext string
|
||||
bucket string
|
||||
logLevel string
|
||||
)
|
||||
|
||||
func init() {
|
||||
RootCmd.PersistentFlags().StringVarP(&dockerfilePath, "dockerfile", "f", "/workspace/Dockerfile", "Path to the dockerfile to be built.")
|
||||
RootCmd.PersistentFlags().StringVarP(&dockerfilePath, "dockerfile", "f", "Dockerfile", "Path to the dockerfile to be built.")
|
||||
RootCmd.PersistentFlags().StringVarP(&srcContext, "context", "c", "", "Path to the dockerfile build context.")
|
||||
RootCmd.PersistentFlags().StringVarP(&bucket, "bucket", "b", "", "Name of the GCS bucket from which to access build context as tarball.")
|
||||
RootCmd.PersistentFlags().StringVarP(&destination, "destination", "d", "", "Registry the final image should be pushed to (ex: gcr.io/test/example:latest)")
|
||||
RootCmd.PersistentFlags().StringVarP(&logLevel, "verbosity", "v", constants.DefaultLogLevel, "Log level (debug, info, warn, error, fatal, panic")
|
||||
}
|
||||
|
|
@ -49,6 +53,10 @@ var RootCmd = &cobra.Command{
|
|||
return util.SetLogLevel(logLevel)
|
||||
},
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
if err := resolveSourceContext(); err != nil {
|
||||
logrus.Error(err)
|
||||
os.Exit(1)
|
||||
}
|
||||
if err := execute(); err != nil {
|
||||
logrus.Error(err)
|
||||
os.Exit(1)
|
||||
|
|
@ -56,6 +64,33 @@ var RootCmd = &cobra.Command{
|
|||
},
|
||||
}
|
||||
|
||||
// resolveSourceContext unpacks the source context if it is a tar in a GCS bucket
|
||||
// it resets srcContext to be the path to the unpacked build context within the image
|
||||
func resolveSourceContext() error {
|
||||
if srcContext == "" && bucket == "" {
|
||||
return errors.New("please specify a path to the build context with the --context flag or a GCS bucket with the --bucket flag")
|
||||
}
|
||||
if srcContext != "" && bucket != "" {
|
||||
return errors.New("please specify either --bucket or --context as the desired build context")
|
||||
}
|
||||
if srcContext != "" {
|
||||
return nil
|
||||
}
|
||||
logrus.Infof("Using GCS bucket %s as source context", bucket)
|
||||
buildContextPath := constants.BuildContextDir
|
||||
if err := util.UnpackTarFromGCSBucket(bucket, buildContextPath); err != nil {
|
||||
return err
|
||||
}
|
||||
logrus.Debugf("Unpacked tar from %s to path %s", bucket, buildContextPath)
|
||||
srcContext = buildContextPath
|
||||
// If path to dockerfile doesn't exist, assume it is in the unpacked tar
|
||||
if !util.FilepathExists(dockerfilePath) {
|
||||
logrus.Debugf("Expecting dockerfile to be located at %s within the tar build context", dockerfilePath)
|
||||
dockerfilePath = filepath.Join(srcContext, dockerfilePath)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func execute() error {
|
||||
// Parse dockerfile and unpack base image to root
|
||||
d, err := ioutil.ReadFile(dockerfilePath)
|
||||
|
|
@ -123,5 +158,23 @@ func execute() error {
|
|||
}
|
||||
}
|
||||
// Push the image
|
||||
if err := setDefaultEnv(); err != nil {
|
||||
return err
|
||||
}
|
||||
return image.PushImage(sourceImage, destination)
|
||||
}
|
||||
|
||||
// setDefaultEnv sets default values for HOME and PATH so that
|
||||
// config.json and docker-credential-gcr can be accessed
|
||||
func setDefaultEnv() error {
|
||||
defaultEnvs := map[string]string{
|
||||
"HOME": "/root",
|
||||
"PATH": "/usr/local/bin/",
|
||||
}
|
||||
for key, val := range defaultEnvs {
|
||||
if err := os.Setenv(key, val); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,19 @@
|
|||
# Copyright 2018 Google, Inc. All rights reserved.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
FROM gcr.io/google-appengine/debian9
|
||||
RUN useradd testuser
|
||||
RUN groupadd testgroup
|
||||
USER testuser:testgroup
|
||||
RUN echo "hey" > /tmp/foo
|
||||
|
|
@ -0,0 +1,12 @@
|
|||
[
|
||||
{
|
||||
"Image1": "gcr.io/kbuild-test/docker-test-bucket-buildcontext:latest",
|
||||
"Image2": "gcr.io/kbuild-test/kbuild-test-bucket-buildcontext:latest",
|
||||
"DiffType": "File",
|
||||
"Diff": {
|
||||
"Adds": null,
|
||||
"Dels": null,
|
||||
"Mods": null
|
||||
}
|
||||
}
|
||||
]
|
||||
|
|
@ -0,0 +1,15 @@
|
|||
schemaVersion: '2.0.0'
|
||||
commandTests:
|
||||
- name: 'whoami'
|
||||
command: 'whoami'
|
||||
expectedOutput: ['testuser']
|
||||
excludedOutput: ['root']
|
||||
- name: 'file owner'
|
||||
command: 'ls'
|
||||
args: ['-l', '/tmp/foo']
|
||||
expectedOutput: ['.*testuser.*', '.*testgroup.*']
|
||||
excludedOutput: ['.*root.*']
|
||||
fileContentTests:
|
||||
- name: "/tmp/foo"
|
||||
path: "/tmp/foo"
|
||||
expectedContent: ["hey"]
|
||||
|
|
@ -22,53 +22,84 @@ import (
|
|||
"gopkg.in/yaml.v2"
|
||||
)
|
||||
|
||||
const (
|
||||
executorImage = "executor-image"
|
||||
dockerImage = "gcr.io/cloud-builders/docker"
|
||||
ubuntuImage = "ubuntu"
|
||||
testRepo = "gcr.io/kbuild-test/"
|
||||
dockerPrefix = "docker-"
|
||||
kbuildPrefix = "kbuild-"
|
||||
daemonPrefix = "daemon://"
|
||||
containerDiffOutputFile = "container-diff.json"
|
||||
kbuildTestBucket = "kbuild-test-bucket"
|
||||
buildcontextPath = "/workspace/integration_tests"
|
||||
dockerfilesPath = "/workspace/integration_tests/dockerfiles"
|
||||
)
|
||||
|
||||
var fileTests = []struct {
|
||||
description string
|
||||
dockerfilePath string
|
||||
configPath string
|
||||
context string
|
||||
repo string
|
||||
description string
|
||||
dockerfilePath string
|
||||
configPath string
|
||||
dockerContext string
|
||||
kbuildContext string
|
||||
kbuildContextBucket bool
|
||||
repo string
|
||||
}{
|
||||
{
|
||||
description: "test extract filesystem",
|
||||
dockerfilePath: "/workspace/integration_tests/dockerfiles/Dockerfile_test_extract_fs",
|
||||
configPath: "/workspace/integration_tests/dockerfiles/config_test_extract_fs.json",
|
||||
context: "integration_tests/dockerfiles/",
|
||||
dockerContext: dockerfilesPath,
|
||||
kbuildContext: dockerfilesPath,
|
||||
repo: "extract-filesystem",
|
||||
},
|
||||
{
|
||||
description: "test run",
|
||||
dockerfilePath: "/workspace/integration_tests/dockerfiles/Dockerfile_test_run",
|
||||
configPath: "/workspace/integration_tests/dockerfiles/config_test_run.json",
|
||||
context: "integration_tests/dockerfiles/",
|
||||
dockerContext: dockerfilesPath,
|
||||
kbuildContext: dockerfilesPath,
|
||||
repo: "test-run",
|
||||
},
|
||||
{
|
||||
description: "test run no files changed",
|
||||
dockerfilePath: "/workspace/integration_tests/dockerfiles/Dockerfile_test_run_2",
|
||||
configPath: "/workspace/integration_tests/dockerfiles/config_test_run_2.json",
|
||||
context: "integration_tests/dockerfiles/",
|
||||
dockerContext: dockerfilesPath,
|
||||
kbuildContext: dockerfilesPath,
|
||||
repo: "test-run-2",
|
||||
},
|
||||
{
|
||||
description: "test copy",
|
||||
dockerfilePath: "/workspace/integration_tests/dockerfiles/Dockerfile_test_copy",
|
||||
configPath: "/workspace/integration_tests/dockerfiles/config_test_copy.json",
|
||||
context: "/workspace/integration_tests/",
|
||||
dockerContext: buildcontextPath,
|
||||
kbuildContext: buildcontextPath,
|
||||
repo: "test-copy",
|
||||
},
|
||||
{
|
||||
description: "test bucket build context",
|
||||
dockerfilePath: "/workspace/integration_tests/dockerfiles/Dockerfile_test_copy",
|
||||
configPath: "/workspace/integration_tests/dockerfiles/config_test_bucket_buildcontext.json",
|
||||
dockerContext: buildcontextPath,
|
||||
kbuildContext: kbuildTestBucket,
|
||||
kbuildContextBucket: true,
|
||||
repo: "test-bucket-buildcontext",
|
||||
},
|
||||
{
|
||||
description: "test workdir",
|
||||
dockerfilePath: "/workspace/integration_tests/dockerfiles/Dockerfile_test_workdir",
|
||||
configPath: "/workspace/integration_tests/dockerfiles/config_test_workdir.json",
|
||||
context: "/workspace/integration_tests/",
|
||||
dockerContext: buildcontextPath,
|
||||
kbuildContext: buildcontextPath,
|
||||
repo: "test-workdir",
|
||||
},
|
||||
{
|
||||
description: "test add",
|
||||
dockerfilePath: "/workspace/integration_tests/dockerfiles/Dockerfile_test_add",
|
||||
configPath: "/workspace/integration_tests/dockerfiles/config_test_add.json",
|
||||
context: "/workspace/integration_tests/",
|
||||
dockerContext: buildcontextPath,
|
||||
kbuildContext: buildcontextPath,
|
||||
repo: "test-add",
|
||||
},
|
||||
}
|
||||
|
|
@ -78,22 +109,33 @@ var structureTests = []struct {
|
|||
dockerfilePath string
|
||||
structureTestYamlPath string
|
||||
dockerBuildContext string
|
||||
kbuildContext string
|
||||
repo string
|
||||
}{
|
||||
{
|
||||
description: "test env",
|
||||
dockerfilePath: "/workspace/integration_tests/dockerfiles/Dockerfile_test_env",
|
||||
repo: "test-env",
|
||||
dockerBuildContext: "/workspace/integration_tests/dockerfiles/",
|
||||
dockerBuildContext: dockerfilesPath,
|
||||
kbuildContext: dockerfilesPath,
|
||||
structureTestYamlPath: "/workspace/integration_tests/dockerfiles/test_env.yaml",
|
||||
},
|
||||
{
|
||||
description: "test metadata",
|
||||
dockerfilePath: "/workspace/integration_tests/dockerfiles/Dockerfile_test_metadata",
|
||||
repo: "test-metadata",
|
||||
dockerBuildContext: "/workspace/integration_tests/dockerfiles/",
|
||||
dockerBuildContext: dockerfilesPath,
|
||||
kbuildContext: dockerfilesPath,
|
||||
structureTestYamlPath: "/workspace/integration_tests/dockerfiles/test_metadata.yaml",
|
||||
},
|
||||
{
|
||||
description: "test user command",
|
||||
dockerfilePath: "/workspace/integration_tests/dockerfiles/Dockerfile_test_user_run",
|
||||
repo: "test-user",
|
||||
dockerBuildContext: dockerfilesPath,
|
||||
kbuildContext: dockerfilesPath,
|
||||
structureTestYamlPath: "/workspace/integration_tests/dockerfiles/test_user.yaml",
|
||||
},
|
||||
}
|
||||
|
||||
type step struct {
|
||||
|
|
@ -106,16 +148,6 @@ type testyaml struct {
|
|||
Steps []step
|
||||
}
|
||||
|
||||
var executorImage = "executor-image"
|
||||
var executorCommand = "/kbuild/executor"
|
||||
var dockerImage = "gcr.io/cloud-builders/docker"
|
||||
var ubuntuImage = "ubuntu"
|
||||
var testRepo = "gcr.io/kbuild-test/"
|
||||
var dockerPrefix = "docker-"
|
||||
var kbuildPrefix = "kbuild-"
|
||||
var daemonPrefix = "daemon://"
|
||||
var containerDiffOutputFile = "container-diff.json"
|
||||
|
||||
func main() {
|
||||
|
||||
// First, copy container-diff in
|
||||
|
|
@ -135,27 +167,41 @@ func main() {
|
|||
Name: ubuntuImage,
|
||||
Args: []string{"chmod", "+x", "container-structure-test"},
|
||||
}
|
||||
|
||||
GCSBucketTarBuildContext := step{
|
||||
Name: ubuntuImage,
|
||||
Args: []string{"tar", "-C", "/workspace/integration_tests/", "-zcvf", "/workspace/context.tar.gz", "."},
|
||||
}
|
||||
uploadTarBuildContext := step{
|
||||
Name: "gcr.io/cloud-builders/gsutil",
|
||||
Args: []string{"cp", "/workspace/context.tar.gz", "gs://kbuild-test-bucket/"},
|
||||
}
|
||||
|
||||
// Build executor image
|
||||
buildExecutorImage := step{
|
||||
Name: dockerImage,
|
||||
Args: []string{"build", "-t", executorImage, "-f", "deploy/Dockerfile", "."},
|
||||
}
|
||||
y := testyaml{
|
||||
Steps: []step{containerDiffStep, containerDiffPermissions, structureTestsStep, structureTestPermissions, buildExecutorImage},
|
||||
Steps: []step{containerDiffStep, containerDiffPermissions, structureTestsStep, structureTestPermissions, GCSBucketTarBuildContext, uploadTarBuildContext, buildExecutorImage},
|
||||
}
|
||||
for _, test := range fileTests {
|
||||
// First, build the image with docker
|
||||
dockerImageTag := testRepo + dockerPrefix + test.repo
|
||||
dockerBuild := step{
|
||||
Name: dockerImage,
|
||||
Args: []string{"build", "-t", dockerImageTag, "-f", test.dockerfilePath, test.context},
|
||||
Args: []string{"build", "-t", dockerImageTag, "-f", test.dockerfilePath, test.dockerContext},
|
||||
}
|
||||
|
||||
// Then, buld the image with kbuild
|
||||
kbuildImage := testRepo + kbuildPrefix + test.repo
|
||||
contextFlag := "--context"
|
||||
if test.kbuildContextBucket {
|
||||
contextFlag = "--bucket"
|
||||
}
|
||||
kbuild := step{
|
||||
Name: executorImage,
|
||||
Args: []string{executorCommand, "--destination", kbuildImage, "--dockerfile", test.dockerfilePath, "--context", test.context},
|
||||
Args: []string{"--destination", kbuildImage, "--dockerfile", test.dockerfilePath, contextFlag, test.kbuildContext},
|
||||
}
|
||||
|
||||
// Pull the kbuild image
|
||||
|
|
@ -199,7 +245,7 @@ func main() {
|
|||
kbuildImage := testRepo + kbuildPrefix + test.repo
|
||||
kbuild := step{
|
||||
Name: executorImage,
|
||||
Args: []string{executorCommand, "--destination", kbuildImage, "--dockerfile", test.dockerfilePath},
|
||||
Args: []string{"--destination", kbuildImage, "--dockerfile", test.dockerfilePath, "--context", test.kbuildContext},
|
||||
}
|
||||
// Pull the kbuild image
|
||||
pullKbuildImage := step{
|
||||
|
|
|
|||
|
|
@ -54,6 +54,8 @@ func GetCommand(cmd instructions.Command, buildcontext string) (DockerCommand, e
|
|||
return &EntrypointCommand{cmd: c}, nil
|
||||
case *instructions.LabelCommand:
|
||||
return &LabelCommand{cmd: c}, nil
|
||||
case *instructions.UserCommand:
|
||||
return &UserCommand{cmd: c}, nil
|
||||
}
|
||||
return nil, errors.Errorf("%s is not a supported command", cmd.Name())
|
||||
}
|
||||
|
|
|
|||
|
|
@ -30,6 +30,7 @@ type ExposeCommand struct {
|
|||
}
|
||||
|
||||
func (r *ExposeCommand) ExecuteCommand(config *manifest.Schema2Config) error {
|
||||
logrus.Info("cmd: EXPOSE")
|
||||
// Grab the currently exposed ports
|
||||
existingPorts := config.ExposedPorts
|
||||
// Add any new ones in
|
||||
|
|
|
|||
|
|
@ -29,6 +29,7 @@ type LabelCommand struct {
|
|||
}
|
||||
|
||||
func (r *LabelCommand) ExecuteCommand(config *manifest.Schema2Config) error {
|
||||
logrus.Info("cmd: LABEL")
|
||||
return updateLabels(r.cmd.Labels, config)
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -22,7 +22,9 @@ import (
|
|||
"github.com/sirupsen/logrus"
|
||||
"os"
|
||||
"os/exec"
|
||||
"strconv"
|
||||
"strings"
|
||||
"syscall"
|
||||
)
|
||||
|
||||
type RunCommand struct {
|
||||
|
|
@ -46,6 +48,26 @@ func (r *RunCommand) ExecuteCommand(config *manifest.Schema2Config) error {
|
|||
cmd := exec.Command(newCommand[0], newCommand[1:]...)
|
||||
cmd.Dir = config.WorkingDir
|
||||
cmd.Stdout = os.Stdout
|
||||
// If specified, run the command as a specific user
|
||||
if config.User != "" {
|
||||
userAndGroup := strings.Split(config.User, ":")
|
||||
// uid and gid need to be uint32
|
||||
uid64, err := strconv.ParseUint(userAndGroup[0], 10, 32)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
uid := uint32(uid64)
|
||||
var gid uint32
|
||||
if len(userAndGroup) > 1 {
|
||||
gid64, err := strconv.ParseUint(userAndGroup[1], 10, 32)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
gid = uint32(gid64)
|
||||
}
|
||||
cmd.SysProcAttr = &syscall.SysProcAttr{}
|
||||
cmd.SysProcAttr.Credential = &syscall.Credential{Uid: uid, Gid: gid}
|
||||
}
|
||||
return cmd.Run()
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,95 @@
|
|||
/*
|
||||
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 commands
|
||||
|
||||
import (
|
||||
"github.com/GoogleCloudPlatform/k8s-container-builder/pkg/util"
|
||||
"github.com/containers/image/manifest"
|
||||
"github.com/docker/docker/builder/dockerfile/instructions"
|
||||
"github.com/sirupsen/logrus"
|
||||
"os/user"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type UserCommand struct {
|
||||
cmd *instructions.UserCommand
|
||||
}
|
||||
|
||||
func (r *UserCommand) ExecuteCommand(config *manifest.Schema2Config) error {
|
||||
logrus.Info("cmd: USER")
|
||||
u := r.cmd.User
|
||||
userAndGroup := strings.Split(u, ":")
|
||||
userStr, err := util.ResolveEnvironmentReplacement(userAndGroup[0], config.Env, false)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
var groupStr string
|
||||
if len(userAndGroup) > 1 {
|
||||
groupStr, err = util.ResolveEnvironmentReplacement(userAndGroup[1], config.Env, false)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
// Lookup by username
|
||||
userObj, err := user.Lookup(userStr)
|
||||
if err != nil {
|
||||
if _, ok := err.(user.UnknownUserError); ok {
|
||||
// Lookup by id
|
||||
userObj, err = user.LookupId(userStr)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
// Same dance with groups
|
||||
var group *user.Group
|
||||
if groupStr != "" {
|
||||
group, err = user.LookupGroup(groupStr)
|
||||
if err != nil {
|
||||
if _, ok := err.(user.UnknownGroupError); ok {
|
||||
group, err = user.LookupGroupId(groupStr)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
uid := userObj.Uid
|
||||
if group != nil {
|
||||
uid = uid + ":" + group.Gid
|
||||
}
|
||||
|
||||
logrus.Infof("Setting user to %s", uid)
|
||||
config.User = uid
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r *UserCommand) FilesToSnapshot() []string {
|
||||
return []string{}
|
||||
}
|
||||
|
||||
func (r *UserCommand) CreatedBy() string {
|
||||
s := []string{r.cmd.Name(), r.cmd.User}
|
||||
return strings.Join(s, " ")
|
||||
}
|
||||
|
|
@ -0,0 +1,98 @@
|
|||
/*
|
||||
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 commands
|
||||
|
||||
import (
|
||||
"github.com/GoogleCloudPlatform/k8s-container-builder/testutil"
|
||||
"github.com/containers/image/manifest"
|
||||
"github.com/docker/docker/builder/dockerfile/instructions"
|
||||
"testing"
|
||||
)
|
||||
|
||||
var userTests = []struct {
|
||||
user string
|
||||
expectedUid string
|
||||
shouldError bool
|
||||
}{
|
||||
{
|
||||
user: "root",
|
||||
expectedUid: "0",
|
||||
shouldError: false,
|
||||
},
|
||||
{
|
||||
user: "0",
|
||||
expectedUid: "0",
|
||||
shouldError: false,
|
||||
},
|
||||
{
|
||||
user: "fakeUser",
|
||||
expectedUid: "",
|
||||
shouldError: true,
|
||||
},
|
||||
{
|
||||
user: "root:root",
|
||||
expectedUid: "0:0",
|
||||
shouldError: false,
|
||||
},
|
||||
{
|
||||
user: "0:root",
|
||||
expectedUid: "0:0",
|
||||
shouldError: false,
|
||||
},
|
||||
{
|
||||
user: "root:0",
|
||||
expectedUid: "0:0",
|
||||
shouldError: false,
|
||||
},
|
||||
{
|
||||
user: "0:0",
|
||||
expectedUid: "0:0",
|
||||
shouldError: false,
|
||||
},
|
||||
{
|
||||
user: "root:fakeGroup",
|
||||
expectedUid: "",
|
||||
shouldError: true,
|
||||
},
|
||||
{
|
||||
user: "$envuser",
|
||||
expectedUid: "0",
|
||||
shouldError: false,
|
||||
},
|
||||
{
|
||||
user: "root:$envgroup",
|
||||
expectedUid: "0:0",
|
||||
shouldError: false,
|
||||
},
|
||||
}
|
||||
|
||||
func TestUpdateUser(t *testing.T) {
|
||||
for _, test := range userTests {
|
||||
cfg := &manifest.Schema2Config{
|
||||
Env: []string{
|
||||
"envuser=root",
|
||||
"envgroup=root",
|
||||
},
|
||||
}
|
||||
cmd := UserCommand{
|
||||
&instructions.UserCommand{
|
||||
User: test.user,
|
||||
},
|
||||
}
|
||||
err := cmd.ExecuteCommand(cfg)
|
||||
testutil.CheckErrorAndDeepEqual(t, test.shouldError, err, test.expectedUid, cfg.User)
|
||||
}
|
||||
}
|
||||
|
|
@ -26,7 +26,17 @@ const (
|
|||
// WorkspaceDir is the path to the workspace directory
|
||||
WorkspaceDir = "/workspace"
|
||||
|
||||
//KbuildDir is the path to the kbuild directory
|
||||
KbuildDir = "/kbuild"
|
||||
|
||||
WhitelistPath = "/proc/self/mountinfo"
|
||||
|
||||
Author = "kbuild"
|
||||
|
||||
// ContextTar is the default name of the tar uploaded to GCS buckets
|
||||
ContextTar = "context.tar.gz"
|
||||
|
||||
// BuildContextDir is the directory a build context will be unpacked into,
|
||||
// for example, a tarball from a GCS bucket will be unpacked here
|
||||
BuildContextDir = "/kbuild/buildcontext/"
|
||||
)
|
||||
|
|
|
|||
|
|
@ -0,0 +1,65 @@
|
|||
/*
|
||||
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 util
|
||||
|
||||
import (
|
||||
"cloud.google.com/go/storage"
|
||||
"github.com/GoogleCloudPlatform/k8s-container-builder/pkg/constants"
|
||||
"github.com/sirupsen/logrus"
|
||||
"golang.org/x/net/context"
|
||||
"os"
|
||||
"path/filepath"
|
||||
)
|
||||
|
||||
// UnpackTarFromGCSBucket unpacks the kbuild.tar file in the given bucket to the given directory
|
||||
func UnpackTarFromGCSBucket(bucketName, directory string) error {
|
||||
// Get the tar from the bucket
|
||||
tarPath, err := getTarFromBucket(bucketName, directory)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
logrus.Debug("Unpacking source context tar...")
|
||||
if err := UnpackCompressedTar(tarPath, directory); err != nil {
|
||||
return err
|
||||
}
|
||||
// Remove the tar so it doesn't interfere with subsequent commands
|
||||
logrus.Debugf("Deleting %s", tarPath)
|
||||
return os.Remove(tarPath)
|
||||
}
|
||||
|
||||
// getTarFromBucket gets kbuild.tar from the GCS bucket and saves it to the filesystem
|
||||
// It returns the path to the tar file
|
||||
func getTarFromBucket(bucketName, directory string) (string, error) {
|
||||
ctx := context.Background()
|
||||
client, err := storage.NewClient(ctx)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
bucket := client.Bucket(bucketName)
|
||||
// Get the tarfile kbuild.tar from the GCS bucket, and save it to a tar object
|
||||
reader, err := bucket.Object(constants.ContextTar).NewReader(ctx)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
defer reader.Close()
|
||||
tarPath := filepath.Join(directory, constants.ContextTar)
|
||||
if err := CreateFile(tarPath, reader, 0600); err != nil {
|
||||
return "", err
|
||||
}
|
||||
logrus.Debugf("Copied tarball %s from GCS bucket %s to %s", constants.ContextTar, bucketName, tarPath)
|
||||
return tarPath, nil
|
||||
}
|
||||
|
|
@ -93,7 +93,7 @@ func checkHardlink(p string, i os.FileInfo) (bool, string) {
|
|||
return hardlink, linkDst
|
||||
}
|
||||
|
||||
//UnpackLocalTarArchive unpacks the tar archive at path to the directory dest
|
||||
// UnpackLocalTarArchive unpacks the tar archive at path to the directory dest
|
||||
// Returns true if the path was acutally unpacked
|
||||
func UnpackLocalTarArchive(path, dest string) error {
|
||||
// First, we need to check if the path is a local tar archive
|
||||
|
|
@ -104,12 +104,7 @@ func UnpackLocalTarArchive(path, dest string) error {
|
|||
}
|
||||
defer file.Close()
|
||||
if compressionLevel == archive.Gzip {
|
||||
gzr, err := gzip.NewReader(file)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer gzr.Close()
|
||||
return pkgutil.UnTar(gzr, dest, nil)
|
||||
return UnpackCompressedTar(path, dest)
|
||||
} else if compressionLevel == archive.Bzip2 {
|
||||
bzr := bzip2.NewReader(file)
|
||||
return pkgutil.UnTar(bzr, dest, nil)
|
||||
|
|
@ -172,3 +167,18 @@ func fileIsUncompressedTar(src string) bool {
|
|||
return true
|
||||
}
|
||||
}
|
||||
|
||||
// UnpackCompressedTar unpacks the compressed tar at path to dir
|
||||
func UnpackCompressedTar(path, dir string) error {
|
||||
file, err := os.Open(path)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer file.Close()
|
||||
gzr, err := gzip.NewReader(file)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer gzr.Close()
|
||||
return pkgutil.UnTar(gzr, dir, nil)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,35 @@
|
|||
# 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.
|
||||
|
||||
#!/bin/bash
|
||||
set -e
|
||||
|
||||
if [ $# -ne 2 ];
|
||||
then echo "Usage: run_in_docker.sh <context directory> <image tag>"
|
||||
fi
|
||||
|
||||
|
||||
context=$1
|
||||
tag=$2
|
||||
|
||||
if [[ ! -e $HOME/.config/gcloud/application_default_credentials.json ]]; then
|
||||
echo "Application Default Credentials do not exist. Run [gcloud auth application-default login] to configure them"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
docker run \
|
||||
-v $HOME/.config/gcloud:/root/.config/gcloud \
|
||||
-v ${context}:/workspace \
|
||||
gcr.io/kbuild-project/executor:latest \
|
||||
/kbuild/executor -d ${tag}
|
||||
|
|
@ -0,0 +1,15 @@
|
|||
# This is the official list of cloud authors for copyright purposes.
|
||||
# This file is distinct from the CONTRIBUTORS files.
|
||||
# See the latter for an explanation.
|
||||
|
||||
# Names should be added to this file as:
|
||||
# Name or Organization <email address>
|
||||
# The email address is not required for organizations.
|
||||
|
||||
Filippo Valsorda <hi@filippo.io>
|
||||
Google Inc.
|
||||
Ingo Oeser <nightlyone@googlemail.com>
|
||||
Palm Stone Games, Inc.
|
||||
Paweł Knap <pawelknap88@gmail.com>
|
||||
Péter Szilágyi <peterke@gmail.com>
|
||||
Tyler Treat <ttreat31@gmail.com>
|
||||
|
|
@ -0,0 +1,39 @@
|
|||
# People who have agreed to one of the CLAs and can contribute patches.
|
||||
# The AUTHORS file lists the copyright holders; this file
|
||||
# lists people. For example, Google employees are listed here
|
||||
# but not in AUTHORS, because Google holds the copyright.
|
||||
#
|
||||
# https://developers.google.com/open-source/cla/individual
|
||||
# https://developers.google.com/open-source/cla/corporate
|
||||
#
|
||||
# Names should be added to this file as:
|
||||
# Name <email address>
|
||||
|
||||
# Keep the list alphabetically sorted.
|
||||
|
||||
Alexis Hunt <lexer@google.com>
|
||||
Andreas Litt <andreas.litt@gmail.com>
|
||||
Andrew Gerrand <adg@golang.org>
|
||||
Brad Fitzpatrick <bradfitz@golang.org>
|
||||
Burcu Dogan <jbd@google.com>
|
||||
Dave Day <djd@golang.org>
|
||||
David Sansome <me@davidsansome.com>
|
||||
David Symonds <dsymonds@golang.org>
|
||||
Filippo Valsorda <hi@filippo.io>
|
||||
Glenn Lewis <gmlewis@google.com>
|
||||
Ingo Oeser <nightlyone@googlemail.com>
|
||||
Johan Euphrosine <proppy@google.com>
|
||||
Jonathan Amsterdam <jba@google.com>
|
||||
Kunpei Sakai <namusyaka@gmail.com>
|
||||
Luna Duclos <luna.duclos@palmstonegames.com>
|
||||
Magnus Hiie <magnus.hiie@gmail.com>
|
||||
Mario Castro <mariocaster@gmail.com>
|
||||
Michael McGreevy <mcgreevy@golang.org>
|
||||
Omar Jarjur <ojarjur@google.com>
|
||||
Paweł Knap <pawelknap88@gmail.com>
|
||||
Péter Szilágyi <peterke@gmail.com>
|
||||
Sarah Adams <shadams@google.com>
|
||||
Thanatat Tamtan <acoshift@gmail.com>
|
||||
Toby Burress <kurin@google.com>
|
||||
Tuo Shan <shantuo@google.com>
|
||||
Tyler Treat <ttreat31@gmail.com>
|
||||
|
|
@ -0,0 +1,202 @@
|
|||
|
||||
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.
|
||||
|
|
@ -0,0 +1,437 @@
|
|||
// Copyright 2014 Google Inc. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
// Package 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 (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"net"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"os"
|
||||
"runtime"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"golang.org/x/net/context"
|
||||
"golang.org/x/net/context/ctxhttp"
|
||||
)
|
||||
|
||||
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 (
|
||||
metaClient = &http.Client{
|
||||
Transport: &http.Transport{
|
||||
Dial: (&net.Dialer{
|
||||
Timeout: 2 * time.Second,
|
||||
KeepAlive: 30 * time.Second,
|
||||
}).Dial,
|
||||
ResponseHeaderTimeout: 2 * time.Second,
|
||||
},
|
||||
}
|
||||
subscribeClient = &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))
|
||||
}
|
||||
|
||||
// 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 Get(suffix string) (string, error) {
|
||||
val, _, err := getETag(metaClient, suffix)
|
||||
return val, err
|
||||
}
|
||||
|
||||
// getETag returns a value from the metadata service as well as the associated
|
||||
// ETag using the provided client. This func is otherwise equivalent to Get.
|
||||
func getETag(client *http.Client, 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
|
||||
}
|
||||
url := "http://" + host + "/computeMetadata/v1/" + suffix
|
||||
req, _ := http.NewRequest("GET", url, nil)
|
||||
req.Header.Set("Metadata-Flavor", "Google")
|
||||
req.Header.Set("User-Agent", userAgent)
|
||||
res, err := client.Do(req)
|
||||
if err != nil {
|
||||
return "", "", err
|
||||
}
|
||||
defer res.Body.Close()
|
||||
if res.StatusCode == http.StatusNotFound {
|
||||
return "", "", NotDefinedError(suffix)
|
||||
}
|
||||
if res.StatusCode != 200 {
|
||||
return "", "", fmt.Errorf("status code %d trying to fetch %s", res.StatusCode, url)
|
||||
}
|
||||
all, err := ioutil.ReadAll(res.Body)
|
||||
if err != nil {
|
||||
return "", "", err
|
||||
}
|
||||
return string(all), res.Header.Get("Etag"), nil
|
||||
}
|
||||
|
||||
func getTrimmed(suffix string) (s string, err error) {
|
||||
s, err = Get(suffix)
|
||||
s = strings.TrimSpace(s)
|
||||
return
|
||||
}
|
||||
|
||||
func (c *cachedValue) get() (v string, err error) {
|
||||
defer c.mu.Unlock()
|
||||
c.mu.Lock()
|
||||
if c.v != "" {
|
||||
return c.v, nil
|
||||
}
|
||||
if c.trim {
|
||||
v, err = getTrimmed(c.k)
|
||||
} else {
|
||||
v, err = 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/GoogleCloudPlatform/google-cloud-go/issues/194
|
||||
go func() {
|
||||
req, _ := http.NewRequest("GET", "http://"+metadataIP, nil)
|
||||
req.Header.Set("User-Agent", userAgent)
|
||||
res, err := ctxhttp.Do(ctx, metaClient, req)
|
||||
if err != nil {
|
||||
resc <- false
|
||||
return
|
||||
}
|
||||
defer res.Body.Close()
|
||||
resc <- res.Header.Get("Metadata-Flavor") == "Google"
|
||||
}()
|
||||
|
||||
go func() {
|
||||
addrs, err := net.LookupHost("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 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 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 := getETag(subscribeClient, 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 := getETag(subscribeClient, 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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ProjectID returns the current instance's project ID string.
|
||||
func ProjectID() (string, error) { return projID.get() }
|
||||
|
||||
// NumericProjectID returns the current instance's numeric project ID.
|
||||
func NumericProjectID() (string, error) { return projNum.get() }
|
||||
|
||||
// InternalIP returns the instance's primary internal IP address.
|
||||
func InternalIP() (string, error) {
|
||||
return getTrimmed("instance/network-interfaces/0/ip")
|
||||
}
|
||||
|
||||
// ExternalIP returns the instance's primary external (public) IP address.
|
||||
func ExternalIP() (string, error) {
|
||||
return 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 Hostname() (string, error) {
|
||||
return getTrimmed("instance/hostname")
|
||||
}
|
||||
|
||||
// InstanceTags returns the list of user-defined instance tags,
|
||||
// assigned when initially creating a GCE instance.
|
||||
func InstanceTags() ([]string, error) {
|
||||
var s []string
|
||||
j, err := 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
|
||||
}
|
||||
|
||||
// InstanceID returns the current VM's numeric instance ID.
|
||||
func InstanceID() (string, error) {
|
||||
return instID.get()
|
||||
}
|
||||
|
||||
// InstanceName returns the current VM's instance ID string.
|
||||
func InstanceName() (string, error) {
|
||||
host, err := Hostname()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return strings.Split(host, ".")[0], nil
|
||||
}
|
||||
|
||||
// Zone returns the current VM's zone, such as "us-central1-b".
|
||||
func Zone() (string, error) {
|
||||
zone, err := 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 InstanceAttributes() ([]string, error) { return 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 ProjectAttributes() ([]string, error) { return lines("project/attributes/") }
|
||||
|
||||
func lines(suffix string) ([]string, error) {
|
||||
j, err := 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
|
||||
}
|
||||
|
||||
// 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 InstanceAttributeValue(attr string) (string, error) {
|
||||
return 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 ProjectAttributeValue(attr string) (string, error) {
|
||||
return 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 Scopes(serviceAccount string) ([]string, error) {
|
||||
if serviceAccount == "" {
|
||||
serviceAccount = "default"
|
||||
}
|
||||
return lines("instance/service-accounts/" + serviceAccount + "/scopes")
|
||||
}
|
||||
|
||||
func strsContains(ss []string, s string) bool {
|
||||
for _, v := range ss {
|
||||
if v == s {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
|
@ -0,0 +1,284 @@
|
|||
// Copyright 2016 Google Inc. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
// Package 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 (
|
||||
"time"
|
||||
|
||||
gax "github.com/googleapis/gax-go"
|
||||
"golang.org/x/net/context"
|
||||
pb "google.golang.org/genproto/googleapis/iam/v1"
|
||||
"google.golang.org/grpc"
|
||||
"google.golang.org/grpc/codes"
|
||||
)
|
||||
|
||||
// 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)
|
||||
}
|
||||
|
||||
// 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) {
|
||||
var proto *pb.Policy
|
||||
err := gax.Invoke(ctx, func(ctx context.Context, _ gax.CallSettings) error {
|
||||
var err error
|
||||
proto, err = g.c.GetIamPolicy(ctx, &pb.GetIamPolicyRequest{Resource: resource})
|
||||
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 {
|
||||
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
|
||||
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
|
||||
}
|
||||
|
||||
// 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.ClientConn, resource string) *Handle {
|
||||
return InternalNewHandleClient(&grpcClient{c: pb.NewIAMPolicyClient(conn)}, 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,
|
||||
}
|
||||
}
|
||||
|
||||
// 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
|
||||
}
|
||||
|
|
@ -0,0 +1,54 @@
|
|||
// Copyright 2017 Google Inc. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package 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...))
|
||||
}
|
||||
|
|
@ -0,0 +1,108 @@
|
|||
// Copyright 2016 Google Inc. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
// Package 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))
|
||||
}
|
||||
|
|
@ -0,0 +1,55 @@
|
|||
// Copyright 2016 Google Inc. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package internal
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
gax "github.com/googleapis/gax-go"
|
||||
|
||||
"golang.org/x/net/context"
|
||||
)
|
||||
|
||||
// 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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,83 @@
|
|||
// Copyright 2018 Google Inc. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
// +build go1.8
|
||||
|
||||
package trace
|
||||
|
||||
import (
|
||||
"go.opencensus.io/trace"
|
||||
"golang.org/x/net/context"
|
||||
"google.golang.org/api/googleapi"
|
||||
"google.golang.org/genproto/googleapis/rpc/code"
|
||||
"google.golang.org/grpc/status"
|
||||
)
|
||||
|
||||
func StartSpan(ctx context.Context, name string) context.Context {
|
||||
ctx, _ = trace.StartSpan(ctx, name)
|
||||
return ctx
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,30 @@
|
|||
// Copyright 2018 Google Inc. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
// +build !go1.8
|
||||
|
||||
package trace
|
||||
|
||||
import (
|
||||
"golang.org/x/net/context"
|
||||
)
|
||||
|
||||
// OpenCensus only supports go 1.8 and higher.
|
||||
|
||||
func StartSpan(ctx context.Context, _ string) context.Context {
|
||||
return ctx
|
||||
}
|
||||
|
||||
func EndSpan(context.Context, error) {
|
||||
}
|
||||
|
|
@ -0,0 +1,71 @@
|
|||
// Copyright 2016 Google Inc. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
//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 = "20180226"
|
||||
|
||||
// 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.IndexRune("0123456789.", r) < 0
|
||||
}
|
||||
|
|
@ -0,0 +1,245 @@
|
|||
// Copyright 2014 Google Inc. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package storage
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"reflect"
|
||||
|
||||
"cloud.google.com/go/internal/trace"
|
||||
"golang.org/x/net/context"
|
||||
"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
|
||||
Role ACLRole
|
||||
}
|
||||
|
||||
// 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 permission level 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) (_ []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(req, ctx)
|
||||
acls, err = req.Do()
|
||||
return err
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return toACLRules(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(req, ctx)
|
||||
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(req, ctx)
|
||||
acls, err = req.Do()
|
||||
return err
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
r := make([]ACLRule, len(acls.Items))
|
||||
for i, v := range acls.Items {
|
||||
r[i].Entity = ACLEntity(v.Entity)
|
||||
r[i].Role = ACLRole(v.Role)
|
||||
}
|
||||
return r, 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(req, ctx)
|
||||
_, err := req.Do()
|
||||
return err
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (a *ACLHandle) bucketDelete(ctx context.Context, entity ACLEntity) error {
|
||||
err := runWithRetry(ctx, func() error {
|
||||
req := a.c.raw.BucketAccessControls.Delete(a.bucket, string(entity))
|
||||
a.configureCall(req, ctx)
|
||||
return req.Do()
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
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(req, ctx)
|
||||
acls, err = req.Do()
|
||||
return err
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return toACLRules(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(req, ctx)
|
||||
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(req, ctx)
|
||||
return req.Do()
|
||||
})
|
||||
}
|
||||
|
||||
func (a *ACLHandle) configureCall(call interface {
|
||||
Header() http.Header
|
||||
}, ctx context.Context) {
|
||||
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 toACLRules(items []*raw.ObjectAccessControl) []ACLRule {
|
||||
r := make([]ACLRule, 0, len(items))
|
||||
for _, item := range items {
|
||||
r = append(r, ACLRule{Entity: ACLEntity(item.Entity), Role: ACLRole(item.Role)})
|
||||
}
|
||||
return r
|
||||
}
|
||||
|
|
@ -0,0 +1,944 @@
|
|||
// Copyright 2014 Google Inc. LiveAndArchived Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package storage
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
"reflect"
|
||||
"time"
|
||||
|
||||
"cloud.google.com/go/internal/optional"
|
||||
"cloud.google.com/go/internal/trace"
|
||||
"golang.org/x/net/context"
|
||||
"google.golang.org/api/googleapi"
|
||||
"google.golang.org/api/iterator"
|
||||
raw "google.golang.org/api/storage/v1"
|
||||
)
|
||||
|
||||
// BucketHandle provides operations on a Google Cloud Storage bucket.
|
||||
// Use Client.Bucket to get a handle.
|
||||
type BucketHandle struct {
|
||||
c *Client
|
||||
name string
|
||||
acl ACLHandle
|
||||
defaultObjectACL ACLHandle
|
||||
conds *BucketConditions
|
||||
userProject string // project for Requester Pays buckets
|
||||
}
|
||||
|
||||
// Bucket returns a BucketHandle, which provides operations on the named bucket.
|
||||
// This call does not perform any network operations.
|
||||
//
|
||||
// The supplied name must contain only lowercase letters, numbers, dashes,
|
||||
// underscores, and dots. The full specification for valid bucket names can be
|
||||
// found at:
|
||||
// https://cloud.google.com/storage/docs/bucket-naming
|
||||
func (c *Client) Bucket(name string) *BucketHandle {
|
||||
return &BucketHandle{
|
||||
c: c,
|
||||
name: name,
|
||||
acl: ACLHandle{
|
||||
c: c,
|
||||
bucket: name,
|
||||
},
|
||||
defaultObjectACL: ACLHandle{
|
||||
c: c,
|
||||
bucket: name,
|
||||
isDefault: true,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// Create creates the Bucket in the project.
|
||||
// If attrs is nil the API defaults will be used.
|
||||
func (b *BucketHandle) Create(ctx context.Context, projectID string, attrs *BucketAttrs) (err error) {
|
||||
ctx = trace.StartSpan(ctx, "cloud.google.com/go/storage.Bucket.Create")
|
||||
defer func() { trace.EndSpan(ctx, err) }()
|
||||
|
||||
var bkt *raw.Bucket
|
||||
if attrs != nil {
|
||||
bkt = attrs.toRawBucket()
|
||||
} else {
|
||||
bkt = &raw.Bucket{}
|
||||
}
|
||||
bkt.Name = b.name
|
||||
// If there is lifecycle information but no location, explicitly set
|
||||
// the location. This is a GCS quirk/bug.
|
||||
if bkt.Location == "" && bkt.Lifecycle != nil {
|
||||
bkt.Location = "US"
|
||||
}
|
||||
req := b.c.raw.Buckets.Insert(projectID, bkt)
|
||||
setClientHeader(req.Header())
|
||||
return runWithRetry(ctx, func() error { _, err := req.Context(ctx).Do(); return err })
|
||||
}
|
||||
|
||||
// Delete deletes the Bucket.
|
||||
func (b *BucketHandle) Delete(ctx context.Context) (err error) {
|
||||
ctx = trace.StartSpan(ctx, "cloud.google.com/go/storage.Bucket.Delete")
|
||||
defer func() { trace.EndSpan(ctx, err) }()
|
||||
|
||||
req, err := b.newDeleteCall()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return runWithRetry(ctx, func() error { return req.Context(ctx).Do() })
|
||||
}
|
||||
|
||||
func (b *BucketHandle) newDeleteCall() (*raw.BucketsDeleteCall, error) {
|
||||
req := b.c.raw.Buckets.Delete(b.name)
|
||||
setClientHeader(req.Header())
|
||||
if err := applyBucketConds("BucketHandle.Delete", b.conds, req); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if b.userProject != "" {
|
||||
req.UserProject(b.userProject)
|
||||
}
|
||||
return req, nil
|
||||
}
|
||||
|
||||
// ACL returns an ACLHandle, which provides access to the bucket's access control list.
|
||||
// This controls who can list, create or overwrite the objects in a bucket.
|
||||
// This call does not perform any network operations.
|
||||
func (b *BucketHandle) ACL() *ACLHandle {
|
||||
return &b.acl
|
||||
}
|
||||
|
||||
// DefaultObjectACL returns an ACLHandle, which provides access to the bucket's default object ACLs.
|
||||
// These ACLs are applied to newly created objects in this bucket that do not have a defined ACL.
|
||||
// This call does not perform any network operations.
|
||||
func (b *BucketHandle) DefaultObjectACL() *ACLHandle {
|
||||
return &b.defaultObjectACL
|
||||
}
|
||||
|
||||
// Object returns an ObjectHandle, which provides operations on the named object.
|
||||
// This call does not perform any network operations.
|
||||
//
|
||||
// name must consist entirely of valid UTF-8-encoded runes. The full specification
|
||||
// for valid object names can be found at:
|
||||
// https://cloud.google.com/storage/docs/bucket-naming
|
||||
func (b *BucketHandle) Object(name string) *ObjectHandle {
|
||||
return &ObjectHandle{
|
||||
c: b.c,
|
||||
bucket: b.name,
|
||||
object: name,
|
||||
acl: ACLHandle{
|
||||
c: b.c,
|
||||
bucket: b.name,
|
||||
object: name,
|
||||
userProject: b.userProject,
|
||||
},
|
||||
gen: -1,
|
||||
userProject: b.userProject,
|
||||
}
|
||||
}
|
||||
|
||||
// Attrs returns the metadata for the bucket.
|
||||
func (b *BucketHandle) Attrs(ctx context.Context) (_ *BucketAttrs, err error) {
|
||||
ctx = trace.StartSpan(ctx, "cloud.google.com/go/storage.Bucket.Attrs")
|
||||
defer func() { trace.EndSpan(ctx, err) }()
|
||||
|
||||
req, err := b.newGetCall()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var resp *raw.Bucket
|
||||
err = runWithRetry(ctx, func() error {
|
||||
resp, err = req.Context(ctx).Do()
|
||||
return err
|
||||
})
|
||||
if e, ok := err.(*googleapi.Error); ok && e.Code == http.StatusNotFound {
|
||||
return nil, ErrBucketNotExist
|
||||
}
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return newBucket(resp)
|
||||
}
|
||||
|
||||
func (b *BucketHandle) newGetCall() (*raw.BucketsGetCall, error) {
|
||||
req := b.c.raw.Buckets.Get(b.name).Projection("full")
|
||||
setClientHeader(req.Header())
|
||||
if err := applyBucketConds("BucketHandle.Attrs", b.conds, req); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if b.userProject != "" {
|
||||
req.UserProject(b.userProject)
|
||||
}
|
||||
return req, nil
|
||||
}
|
||||
|
||||
func (b *BucketHandle) Update(ctx context.Context, uattrs BucketAttrsToUpdate) (_ *BucketAttrs, err error) {
|
||||
ctx = trace.StartSpan(ctx, "cloud.google.com/go/storage.Bucket.Create")
|
||||
defer func() { trace.EndSpan(ctx, err) }()
|
||||
|
||||
req, err := b.newPatchCall(&uattrs)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// TODO(jba): retry iff metagen is set?
|
||||
rb, err := req.Context(ctx).Do()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return newBucket(rb)
|
||||
}
|
||||
|
||||
func (b *BucketHandle) newPatchCall(uattrs *BucketAttrsToUpdate) (*raw.BucketsPatchCall, error) {
|
||||
rb := uattrs.toRawBucket()
|
||||
req := b.c.raw.Buckets.Patch(b.name, rb).Projection("full")
|
||||
setClientHeader(req.Header())
|
||||
if err := applyBucketConds("BucketHandle.Update", b.conds, req); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if b.userProject != "" {
|
||||
req.UserProject(b.userProject)
|
||||
}
|
||||
return req, nil
|
||||
}
|
||||
|
||||
// BucketAttrs represents the metadata for a Google Cloud Storage bucket.
|
||||
// Read-only fields are ignored by BucketHandle.Create.
|
||||
type BucketAttrs struct {
|
||||
// Name is the name of the bucket.
|
||||
// This field is read-only.
|
||||
Name string
|
||||
|
||||
// ACL is the list of access control rules on the bucket.
|
||||
ACL []ACLRule
|
||||
|
||||
// DefaultObjectACL is the list of access controls to
|
||||
// apply to new objects when no object ACL is provided.
|
||||
DefaultObjectACL []ACLRule
|
||||
|
||||
// Location is the location of the bucket. It defaults to "US".
|
||||
Location string
|
||||
|
||||
// MetaGeneration is the metadata generation of the bucket.
|
||||
// This field is read-only.
|
||||
MetaGeneration int64
|
||||
|
||||
// StorageClass is the default storage class of the bucket. This defines
|
||||
// how objects in the bucket are stored and determines the SLA
|
||||
// and the cost of storage. Typical values are "MULTI_REGIONAL",
|
||||
// "REGIONAL", "NEARLINE", "COLDLINE", "STANDARD" and
|
||||
// "DURABLE_REDUCED_AVAILABILITY". Defaults to "STANDARD", which
|
||||
// is equivalent to "MULTI_REGIONAL" or "REGIONAL" depending on
|
||||
// the bucket's location settings.
|
||||
StorageClass string
|
||||
|
||||
// Created is the creation time of the bucket.
|
||||
// This field is read-only.
|
||||
Created time.Time
|
||||
|
||||
// VersioningEnabled reports whether this bucket has versioning enabled.
|
||||
VersioningEnabled bool
|
||||
|
||||
// Labels are the bucket's labels.
|
||||
Labels map[string]string
|
||||
|
||||
// RequesterPays reports whether the bucket is a Requester Pays bucket.
|
||||
// Clients performing operations on Requester Pays buckets must provide
|
||||
// a user project (see BucketHandle.UserProject), which will be billed
|
||||
// for the operations.
|
||||
RequesterPays bool
|
||||
|
||||
// Lifecycle is the lifecycle configuration for objects in the bucket.
|
||||
Lifecycle Lifecycle
|
||||
|
||||
// Retention policy enforces a minimum retention time for all objects
|
||||
// contained in the bucket. A RetentionPolicy of nil implies the bucket
|
||||
// has no minimum data retention.
|
||||
//
|
||||
// This feature is in private alpha release. It is not currently available to
|
||||
// most customers. It might be changed in backwards-incompatible ways and is not
|
||||
// subject to any SLA or deprecation policy.
|
||||
RetentionPolicy *RetentionPolicy
|
||||
|
||||
// The bucket's Cross-Origin Resource Sharing (CORS) configuration.
|
||||
CORS []CORS
|
||||
}
|
||||
|
||||
// Lifecycle is the lifecycle configuration for objects in the bucket.
|
||||
type Lifecycle struct {
|
||||
Rules []LifecycleRule
|
||||
}
|
||||
|
||||
// Retention policy enforces a minimum retention time for all objects
|
||||
// contained in the bucket.
|
||||
//
|
||||
// Any attempt to overwrite or delete objects younger than the retention
|
||||
// period will result in an error. An unlocked retention policy can be
|
||||
// modified or removed from the bucket via the Update method. A
|
||||
// locked retention policy cannot be removed or shortened in duration
|
||||
// for the lifetime of the bucket.
|
||||
//
|
||||
// This feature is in private alpha release. It is not currently available to
|
||||
// most customers. It might be changed in backwards-incompatible ways and is not
|
||||
// subject to any SLA or deprecation policy.
|
||||
type RetentionPolicy struct {
|
||||
// RetentionPeriod specifies the duration that objects need to be
|
||||
// retained. Retention duration must be greater than zero and less than
|
||||
// 100 years. Note that enforcement of retention periods less than a day
|
||||
// is not guaranteed. Such periods should only be used for testing
|
||||
// purposes.
|
||||
RetentionPeriod time.Duration
|
||||
|
||||
// EffectiveTime is the time from which the policy was enforced and
|
||||
// effective. This field is read-only.
|
||||
EffectiveTime time.Time
|
||||
}
|
||||
|
||||
const (
|
||||
// RFC3339 date with only the date segment, used for CreatedBefore in LifecycleRule.
|
||||
rfc3339Date = "2006-01-02"
|
||||
|
||||
// DeleteAction is a lifecycle action that deletes a live and/or archived
|
||||
// objects. Takes precendence over SetStorageClass actions.
|
||||
DeleteAction = "Delete"
|
||||
|
||||
// SetStorageClassAction changes the storage class of live and/or archived
|
||||
// objects.
|
||||
SetStorageClassAction = "SetStorageClass"
|
||||
)
|
||||
|
||||
// LifecycleRule is a lifecycle configuration rule.
|
||||
//
|
||||
// When all the configured conditions are met by an object in the bucket, the
|
||||
// configured action will automatically be taken on that object.
|
||||
type LifecycleRule struct {
|
||||
// Action is the action to take when all of the associated conditions are
|
||||
// met.
|
||||
Action LifecycleAction
|
||||
|
||||
// Condition is the set of conditions that must be met for the associated
|
||||
// action to be taken.
|
||||
Condition LifecycleCondition
|
||||
}
|
||||
|
||||
// LifecycleAction is a lifecycle configuration action.
|
||||
type LifecycleAction struct {
|
||||
// Type is the type of action to take on matching objects.
|
||||
//
|
||||
// Acceptable values are "Delete" to delete matching objects and
|
||||
// "SetStorageClass" to set the storage class defined in StorageClass on
|
||||
// matching objects.
|
||||
Type string
|
||||
|
||||
// StorageClass is the storage class to set on matching objects if the Action
|
||||
// is "SetStorageClass".
|
||||
StorageClass string
|
||||
}
|
||||
|
||||
// Liveness specifies whether the object is live or not.
|
||||
type Liveness int
|
||||
|
||||
const (
|
||||
// LiveAndArchived includes both live and archived objects.
|
||||
LiveAndArchived Liveness = iota
|
||||
// Live specifies that the object is still live.
|
||||
Live
|
||||
// Archived specifies that the object is archived.
|
||||
Archived
|
||||
)
|
||||
|
||||
// LifecycleCondition is a set of conditions used to match objects and take an
|
||||
// action automatically.
|
||||
//
|
||||
// All configured conditions must be met for the associated action to be taken.
|
||||
type LifecycleCondition struct {
|
||||
// AgeInDays is the age of the object in days.
|
||||
AgeInDays int64
|
||||
|
||||
// CreatedBefore is the time the object was created.
|
||||
//
|
||||
// This condition is satisfied when an object is created before midnight of
|
||||
// the specified date in UTC.
|
||||
CreatedBefore time.Time
|
||||
|
||||
// Liveness specifies the object's liveness. Relevant only for versioned objects
|
||||
Liveness Liveness
|
||||
|
||||
// MatchesStorageClasses is the condition matching the object's storage
|
||||
// class.
|
||||
//
|
||||
// Values include "MULTI_REGIONAL", "REGIONAL", "NEARLINE", "COLDLINE",
|
||||
// "STANDARD", and "DURABLE_REDUCED_AVAILABILITY".
|
||||
MatchesStorageClasses []string
|
||||
|
||||
// NumNewerVersions is the condition matching objects with a number of newer versions.
|
||||
//
|
||||
// If the value is N, this condition is satisfied when there are at least N
|
||||
// versions (including the live version) newer than this version of the
|
||||
// object.
|
||||
NumNewerVersions int64
|
||||
}
|
||||
|
||||
func newBucket(b *raw.Bucket) (*BucketAttrs, error) {
|
||||
if b == nil {
|
||||
return nil, nil
|
||||
}
|
||||
rp, err := toRetentionPolicy(b.RetentionPolicy)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
bucket := &BucketAttrs{
|
||||
Name: b.Name,
|
||||
Location: b.Location,
|
||||
MetaGeneration: b.Metageneration,
|
||||
StorageClass: b.StorageClass,
|
||||
Created: convertTime(b.TimeCreated),
|
||||
VersioningEnabled: b.Versioning != nil && b.Versioning.Enabled,
|
||||
Labels: b.Labels,
|
||||
RequesterPays: b.Billing != nil && b.Billing.RequesterPays,
|
||||
Lifecycle: toLifecycle(b.Lifecycle),
|
||||
RetentionPolicy: rp,
|
||||
CORS: toCORS(b.Cors),
|
||||
}
|
||||
acl := make([]ACLRule, len(b.Acl))
|
||||
for i, rule := range b.Acl {
|
||||
acl[i] = ACLRule{
|
||||
Entity: ACLEntity(rule.Entity),
|
||||
Role: ACLRole(rule.Role),
|
||||
}
|
||||
}
|
||||
bucket.ACL = acl
|
||||
objACL := make([]ACLRule, len(b.DefaultObjectAcl))
|
||||
for i, rule := range b.DefaultObjectAcl {
|
||||
objACL[i] = ACLRule{
|
||||
Entity: ACLEntity(rule.Entity),
|
||||
Role: ACLRole(rule.Role),
|
||||
}
|
||||
}
|
||||
bucket.DefaultObjectACL = objACL
|
||||
return bucket, nil
|
||||
}
|
||||
|
||||
// toRawBucket copies the editable attribute from b to the raw library's Bucket type.
|
||||
func (b *BucketAttrs) toRawBucket() *raw.Bucket {
|
||||
var acl []*raw.BucketAccessControl
|
||||
if len(b.ACL) > 0 {
|
||||
acl = make([]*raw.BucketAccessControl, len(b.ACL))
|
||||
for i, rule := range b.ACL {
|
||||
acl[i] = &raw.BucketAccessControl{
|
||||
Entity: string(rule.Entity),
|
||||
Role: string(rule.Role),
|
||||
}
|
||||
}
|
||||
}
|
||||
dACL := toRawObjectACL(b.DefaultObjectACL)
|
||||
// Copy label map.
|
||||
var labels map[string]string
|
||||
if len(b.Labels) > 0 {
|
||||
labels = make(map[string]string, len(b.Labels))
|
||||
for k, v := range b.Labels {
|
||||
labels[k] = v
|
||||
}
|
||||
}
|
||||
// Ignore VersioningEnabled if it is false. This is OK because
|
||||
// we only call this method when creating a bucket, and by default
|
||||
// new buckets have versioning off.
|
||||
var v *raw.BucketVersioning
|
||||
if b.VersioningEnabled {
|
||||
v = &raw.BucketVersioning{Enabled: true}
|
||||
}
|
||||
var bb *raw.BucketBilling
|
||||
if b.RequesterPays {
|
||||
bb = &raw.BucketBilling{RequesterPays: true}
|
||||
}
|
||||
return &raw.Bucket{
|
||||
Name: b.Name,
|
||||
DefaultObjectAcl: dACL,
|
||||
Location: b.Location,
|
||||
StorageClass: b.StorageClass,
|
||||
Acl: acl,
|
||||
Versioning: v,
|
||||
Labels: labels,
|
||||
Billing: bb,
|
||||
Lifecycle: toRawLifecycle(b.Lifecycle),
|
||||
RetentionPolicy: b.RetentionPolicy.toRawRetentionPolicy(),
|
||||
Cors: toRawCORS(b.CORS),
|
||||
}
|
||||
}
|
||||
|
||||
// The bucket's Cross-Origin Resource Sharing (CORS) configuration.
|
||||
type CORS struct {
|
||||
// MaxAge is the value to return in the Access-Control-Max-Age
|
||||
// header used in preflight responses.
|
||||
MaxAge time.Duration
|
||||
|
||||
// Methods is the list of HTTP methods on which to include CORS response
|
||||
// headers, (GET, OPTIONS, POST, etc) Note: "*" is permitted in the list
|
||||
// of methods, and means "any method".
|
||||
Methods []string
|
||||
|
||||
// Origins is the list of Origins eligible to receive CORS response
|
||||
// headers. Note: "*" is permitted in the list of origins, and means
|
||||
// "any Origin".
|
||||
Origins []string
|
||||
|
||||
// ResponseHeaders is the list of HTTP headers other than the simple
|
||||
// response headers to give permission for the user-agent to share
|
||||
// across domains.
|
||||
ResponseHeaders []string
|
||||
}
|
||||
|
||||
type BucketAttrsToUpdate struct {
|
||||
// VersioningEnabled, if set, updates whether the bucket uses versioning.
|
||||
VersioningEnabled optional.Bool
|
||||
|
||||
// RequesterPays, if set, updates whether the bucket is a Requester Pays bucket.
|
||||
RequesterPays optional.Bool
|
||||
|
||||
// RetentionPolicy, if set, updates the retention policy of the bucket. Using
|
||||
// RetentionPolicy.RetentionPeriod = 0 will delete the existing policy.
|
||||
//
|
||||
// This feature is in private alpha release. It is not currently available to
|
||||
// most customers. It might be changed in backwards-incompatible ways and is not
|
||||
// subject to any SLA or deprecation policy.
|
||||
RetentionPolicy *RetentionPolicy
|
||||
|
||||
// CORS, if set, replaces the CORS configuration with a new configuration.
|
||||
// When an empty slice is provided, all CORS policies are removed; when nil
|
||||
// is provided, the value is ignored in the update.
|
||||
CORS []CORS
|
||||
|
||||
setLabels map[string]string
|
||||
deleteLabels map[string]bool
|
||||
}
|
||||
|
||||
// SetLabel causes a label to be added or modified when ua is used
|
||||
// in a call to Bucket.Update.
|
||||
func (ua *BucketAttrsToUpdate) SetLabel(name, value string) {
|
||||
if ua.setLabels == nil {
|
||||
ua.setLabels = map[string]string{}
|
||||
}
|
||||
ua.setLabels[name] = value
|
||||
}
|
||||
|
||||
// DeleteLabel causes a label to be deleted when ua is used in a
|
||||
// call to Bucket.Update.
|
||||
func (ua *BucketAttrsToUpdate) DeleteLabel(name string) {
|
||||
if ua.deleteLabels == nil {
|
||||
ua.deleteLabels = map[string]bool{}
|
||||
}
|
||||
ua.deleteLabels[name] = true
|
||||
}
|
||||
|
||||
func (ua *BucketAttrsToUpdate) toRawBucket() *raw.Bucket {
|
||||
rb := &raw.Bucket{}
|
||||
if ua.CORS != nil {
|
||||
rb.Cors = toRawCORS(ua.CORS)
|
||||
rb.ForceSendFields = append(rb.ForceSendFields, "Cors")
|
||||
}
|
||||
if ua.RetentionPolicy != nil {
|
||||
if ua.RetentionPolicy.RetentionPeriod == 0 {
|
||||
rb.NullFields = append(rb.NullFields, "RetentionPolicy")
|
||||
rb.RetentionPolicy = nil
|
||||
} else {
|
||||
rb.RetentionPolicy = ua.RetentionPolicy.toRawRetentionPolicy()
|
||||
}
|
||||
}
|
||||
if ua.VersioningEnabled != nil {
|
||||
rb.Versioning = &raw.BucketVersioning{
|
||||
Enabled: optional.ToBool(ua.VersioningEnabled),
|
||||
ForceSendFields: []string{"Enabled"},
|
||||
}
|
||||
}
|
||||
if ua.RequesterPays != nil {
|
||||
rb.Billing = &raw.BucketBilling{
|
||||
RequesterPays: optional.ToBool(ua.RequesterPays),
|
||||
ForceSendFields: []string{"RequesterPays"},
|
||||
}
|
||||
}
|
||||
if ua.setLabels != nil || ua.deleteLabels != nil {
|
||||
rb.Labels = map[string]string{}
|
||||
for k, v := range ua.setLabels {
|
||||
rb.Labels[k] = v
|
||||
}
|
||||
if len(rb.Labels) == 0 && len(ua.deleteLabels) > 0 {
|
||||
rb.ForceSendFields = append(rb.ForceSendFields, "Labels")
|
||||
}
|
||||
for l := range ua.deleteLabels {
|
||||
rb.NullFields = append(rb.NullFields, "Labels."+l)
|
||||
}
|
||||
}
|
||||
return rb
|
||||
}
|
||||
|
||||
// If returns a new BucketHandle that applies a set of preconditions.
|
||||
// Preconditions already set on the BucketHandle are ignored.
|
||||
// Operations on the new handle will only occur if the preconditions are
|
||||
// satisfied. The only valid preconditions for buckets are MetagenerationMatch
|
||||
// and MetagenerationNotMatch.
|
||||
func (b *BucketHandle) If(conds BucketConditions) *BucketHandle {
|
||||
b2 := *b
|
||||
b2.conds = &conds
|
||||
return &b2
|
||||
}
|
||||
|
||||
// BucketConditions constrain bucket methods to act on specific metagenerations.
|
||||
//
|
||||
// The zero value is an empty set of constraints.
|
||||
type BucketConditions struct {
|
||||
// MetagenerationMatch specifies that the bucket must have the given
|
||||
// metageneration for the operation to occur.
|
||||
// If MetagenerationMatch is zero, it has no effect.
|
||||
MetagenerationMatch int64
|
||||
|
||||
// MetagenerationNotMatch specifies that the bucket must not have the given
|
||||
// metageneration for the operation to occur.
|
||||
// If MetagenerationNotMatch is zero, it has no effect.
|
||||
MetagenerationNotMatch int64
|
||||
}
|
||||
|
||||
func (c *BucketConditions) validate(method string) error {
|
||||
if *c == (BucketConditions{}) {
|
||||
return fmt.Errorf("storage: %s: empty conditions", method)
|
||||
}
|
||||
if c.MetagenerationMatch != 0 && c.MetagenerationNotMatch != 0 {
|
||||
return fmt.Errorf("storage: %s: multiple conditions specified for metageneration", method)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// UserProject returns a new BucketHandle that passes the project ID as the user
|
||||
// project for all subsequent calls. Calls with a user project will be billed to that
|
||||
// project rather than to the bucket's owning project.
|
||||
//
|
||||
// A user project is required for all operations on Requester Pays buckets.
|
||||
func (b *BucketHandle) UserProject(projectID string) *BucketHandle {
|
||||
b2 := *b
|
||||
b2.userProject = projectID
|
||||
b2.acl.userProject = projectID
|
||||
b2.defaultObjectACL.userProject = projectID
|
||||
return &b2
|
||||
}
|
||||
|
||||
// LockRetentionPolicy locks a bucket's retention policy until a previously-configured
|
||||
// RetentionPeriod past the EffectiveTime. Note that if RetentionPeriod is set to less
|
||||
// than a day, the retention policy is treated as a development configuration and locking
|
||||
// will have no effect. The BucketHandle must have a metageneration condition that
|
||||
// matches the bucket's metageneration. See BucketHandle.If.
|
||||
//
|
||||
// This feature is in private alpha release. It is not currently available to
|
||||
// most customers. It might be changed in backwards-incompatible ways and is not
|
||||
// subject to any SLA or deprecation policy.
|
||||
func (b *BucketHandle) LockRetentionPolicy(ctx context.Context) error {
|
||||
var metageneration int64
|
||||
if b.conds != nil {
|
||||
metageneration = b.conds.MetagenerationMatch
|
||||
}
|
||||
req := b.c.raw.Buckets.LockRetentionPolicy(b.name, metageneration)
|
||||
_, err := req.Context(ctx).Do()
|
||||
return err
|
||||
}
|
||||
|
||||
// applyBucketConds modifies the provided call using the conditions in conds.
|
||||
// call is something that quacks like a *raw.WhateverCall.
|
||||
func applyBucketConds(method string, conds *BucketConditions, call interface{}) error {
|
||||
if conds == nil {
|
||||
return nil
|
||||
}
|
||||
if err := conds.validate(method); err != nil {
|
||||
return err
|
||||
}
|
||||
cval := reflect.ValueOf(call)
|
||||
switch {
|
||||
case conds.MetagenerationMatch != 0:
|
||||
if !setConditionField(cval, "IfMetagenerationMatch", conds.MetagenerationMatch) {
|
||||
return fmt.Errorf("storage: %s: ifMetagenerationMatch not supported", method)
|
||||
}
|
||||
case conds.MetagenerationNotMatch != 0:
|
||||
if !setConditionField(cval, "IfMetagenerationNotMatch", conds.MetagenerationNotMatch) {
|
||||
return fmt.Errorf("storage: %s: ifMetagenerationNotMatch not supported", method)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (rp *RetentionPolicy) toRawRetentionPolicy() *raw.BucketRetentionPolicy {
|
||||
if rp == nil {
|
||||
return nil
|
||||
}
|
||||
return &raw.BucketRetentionPolicy{
|
||||
RetentionPeriod: int64(rp.RetentionPeriod / time.Second),
|
||||
}
|
||||
}
|
||||
|
||||
func toRetentionPolicy(rp *raw.BucketRetentionPolicy) (*RetentionPolicy, error) {
|
||||
if rp == nil {
|
||||
return nil, nil
|
||||
}
|
||||
t, err := time.Parse(time.RFC3339, rp.EffectiveTime)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &RetentionPolicy{
|
||||
RetentionPeriod: time.Duration(rp.RetentionPeriod) * time.Second,
|
||||
EffectiveTime: t,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func toRawCORS(c []CORS) []*raw.BucketCors {
|
||||
var out []*raw.BucketCors
|
||||
for _, v := range c {
|
||||
out = append(out, &raw.BucketCors{
|
||||
MaxAgeSeconds: int64(v.MaxAge / time.Second),
|
||||
Method: v.Methods,
|
||||
Origin: v.Origins,
|
||||
ResponseHeader: v.ResponseHeaders,
|
||||
})
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
func toCORS(rc []*raw.BucketCors) []CORS {
|
||||
var out []CORS
|
||||
for _, v := range rc {
|
||||
out = append(out, CORS{
|
||||
MaxAge: time.Duration(v.MaxAgeSeconds) * time.Second,
|
||||
Methods: v.Method,
|
||||
Origins: v.Origin,
|
||||
ResponseHeaders: v.ResponseHeader,
|
||||
})
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
func toRawLifecycle(l Lifecycle) *raw.BucketLifecycle {
|
||||
var rl raw.BucketLifecycle
|
||||
if len(l.Rules) == 0 {
|
||||
return nil
|
||||
}
|
||||
for _, r := range l.Rules {
|
||||
rr := &raw.BucketLifecycleRule{
|
||||
Action: &raw.BucketLifecycleRuleAction{
|
||||
Type: r.Action.Type,
|
||||
StorageClass: r.Action.StorageClass,
|
||||
},
|
||||
Condition: &raw.BucketLifecycleRuleCondition{
|
||||
Age: r.Condition.AgeInDays,
|
||||
MatchesStorageClass: r.Condition.MatchesStorageClasses,
|
||||
NumNewerVersions: r.Condition.NumNewerVersions,
|
||||
},
|
||||
}
|
||||
|
||||
switch r.Condition.Liveness {
|
||||
case LiveAndArchived:
|
||||
rr.Condition.IsLive = nil
|
||||
case Live:
|
||||
rr.Condition.IsLive = googleapi.Bool(true)
|
||||
case Archived:
|
||||
rr.Condition.IsLive = googleapi.Bool(false)
|
||||
}
|
||||
|
||||
if !r.Condition.CreatedBefore.IsZero() {
|
||||
rr.Condition.CreatedBefore = r.Condition.CreatedBefore.Format(rfc3339Date)
|
||||
}
|
||||
rl.Rule = append(rl.Rule, rr)
|
||||
}
|
||||
return &rl
|
||||
}
|
||||
|
||||
func toLifecycle(rl *raw.BucketLifecycle) Lifecycle {
|
||||
var l Lifecycle
|
||||
if rl == nil {
|
||||
return l
|
||||
}
|
||||
for _, rr := range rl.Rule {
|
||||
r := LifecycleRule{
|
||||
Action: LifecycleAction{
|
||||
Type: rr.Action.Type,
|
||||
StorageClass: rr.Action.StorageClass,
|
||||
},
|
||||
Condition: LifecycleCondition{
|
||||
AgeInDays: rr.Condition.Age,
|
||||
MatchesStorageClasses: rr.Condition.MatchesStorageClass,
|
||||
NumNewerVersions: rr.Condition.NumNewerVersions,
|
||||
},
|
||||
}
|
||||
|
||||
switch {
|
||||
case rr.Condition.IsLive == nil:
|
||||
r.Condition.Liveness = LiveAndArchived
|
||||
case *rr.Condition.IsLive == true:
|
||||
r.Condition.Liveness = Live
|
||||
case *rr.Condition.IsLive == false:
|
||||
r.Condition.Liveness = Archived
|
||||
}
|
||||
|
||||
if rr.Condition.CreatedBefore != "" {
|
||||
r.Condition.CreatedBefore, _ = time.Parse(rfc3339Date, rr.Condition.CreatedBefore)
|
||||
}
|
||||
l.Rules = append(l.Rules, r)
|
||||
}
|
||||
return l
|
||||
}
|
||||
|
||||
// Objects returns an iterator over the objects in the bucket that match the Query q.
|
||||
// If q is nil, no filtering is done.
|
||||
func (b *BucketHandle) Objects(ctx context.Context, q *Query) *ObjectIterator {
|
||||
it := &ObjectIterator{
|
||||
ctx: ctx,
|
||||
bucket: b,
|
||||
}
|
||||
it.pageInfo, it.nextFunc = iterator.NewPageInfo(
|
||||
it.fetch,
|
||||
func() int { return len(it.items) },
|
||||
func() interface{} { b := it.items; it.items = nil; return b })
|
||||
if q != nil {
|
||||
it.query = *q
|
||||
}
|
||||
return it
|
||||
}
|
||||
|
||||
// An ObjectIterator is an iterator over ObjectAttrs.
|
||||
type ObjectIterator struct {
|
||||
ctx context.Context
|
||||
bucket *BucketHandle
|
||||
query Query
|
||||
pageInfo *iterator.PageInfo
|
||||
nextFunc func() error
|
||||
items []*ObjectAttrs
|
||||
}
|
||||
|
||||
// PageInfo supports pagination. See the google.golang.org/api/iterator package for details.
|
||||
func (it *ObjectIterator) 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 iterator.Done, all subsequent
|
||||
// calls will return iterator.Done.
|
||||
//
|
||||
// If Query.Delimiter is non-empty, some of the ObjectAttrs returned by Next will
|
||||
// have a non-empty Prefix field, and a zero value for all other fields. These
|
||||
// represent prefixes.
|
||||
func (it *ObjectIterator) Next() (*ObjectAttrs, error) {
|
||||
if err := it.nextFunc(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
item := it.items[0]
|
||||
it.items = it.items[1:]
|
||||
return item, nil
|
||||
}
|
||||
|
||||
func (it *ObjectIterator) fetch(pageSize int, pageToken string) (string, error) {
|
||||
req := it.bucket.c.raw.Objects.List(it.bucket.name)
|
||||
setClientHeader(req.Header())
|
||||
req.Projection("full")
|
||||
req.Delimiter(it.query.Delimiter)
|
||||
req.Prefix(it.query.Prefix)
|
||||
req.Versions(it.query.Versions)
|
||||
req.PageToken(pageToken)
|
||||
if it.bucket.userProject != "" {
|
||||
req.UserProject(it.bucket.userProject)
|
||||
}
|
||||
if pageSize > 0 {
|
||||
req.MaxResults(int64(pageSize))
|
||||
}
|
||||
var resp *raw.Objects
|
||||
var err error
|
||||
err = runWithRetry(it.ctx, func() error {
|
||||
resp, err = req.Context(it.ctx).Do()
|
||||
return err
|
||||
})
|
||||
if err != nil {
|
||||
if e, ok := err.(*googleapi.Error); ok && e.Code == http.StatusNotFound {
|
||||
err = ErrBucketNotExist
|
||||
}
|
||||
return "", err
|
||||
}
|
||||
for _, item := range resp.Items {
|
||||
it.items = append(it.items, newObject(item))
|
||||
}
|
||||
for _, prefix := range resp.Prefixes {
|
||||
it.items = append(it.items, &ObjectAttrs{Prefix: prefix})
|
||||
}
|
||||
return resp.NextPageToken, nil
|
||||
}
|
||||
|
||||
// TODO(jbd): Add storage.buckets.update.
|
||||
|
||||
// Buckets returns an iterator over the buckets in the project. You may
|
||||
// optionally set the iterator's Prefix field to restrict the list to buckets
|
||||
// whose names begin with the prefix. By default, all buckets in the project
|
||||
// are returned.
|
||||
func (c *Client) Buckets(ctx context.Context, projectID string) *BucketIterator {
|
||||
it := &BucketIterator{
|
||||
ctx: ctx,
|
||||
client: c,
|
||||
projectID: projectID,
|
||||
}
|
||||
it.pageInfo, it.nextFunc = iterator.NewPageInfo(
|
||||
it.fetch,
|
||||
func() int { return len(it.buckets) },
|
||||
func() interface{} { b := it.buckets; it.buckets = nil; return b })
|
||||
return it
|
||||
}
|
||||
|
||||
// A BucketIterator is an iterator over BucketAttrs.
|
||||
type BucketIterator struct {
|
||||
// Prefix restricts the iterator to buckets whose names begin with it.
|
||||
Prefix string
|
||||
|
||||
ctx context.Context
|
||||
client *Client
|
||||
projectID string
|
||||
buckets []*BucketAttrs
|
||||
pageInfo *iterator.PageInfo
|
||||
nextFunc func() error
|
||||
}
|
||||
|
||||
// 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.
|
||||
func (it *BucketIterator) Next() (*BucketAttrs, error) {
|
||||
if err := it.nextFunc(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
b := it.buckets[0]
|
||||
it.buckets = it.buckets[1:]
|
||||
return b, nil
|
||||
}
|
||||
|
||||
// PageInfo supports pagination. See the google.golang.org/api/iterator package for details.
|
||||
func (it *BucketIterator) PageInfo() *iterator.PageInfo { return it.pageInfo }
|
||||
|
||||
func (it *BucketIterator) fetch(pageSize int, pageToken string) (_ string, err error) {
|
||||
req := it.client.raw.Buckets.List(it.projectID)
|
||||
setClientHeader(req.Header())
|
||||
req.Projection("full")
|
||||
req.Prefix(it.Prefix)
|
||||
req.PageToken(pageToken)
|
||||
if pageSize > 0 {
|
||||
req.MaxResults(int64(pageSize))
|
||||
}
|
||||
var resp *raw.Buckets
|
||||
err = runWithRetry(it.ctx, func() error {
|
||||
resp, err = req.Context(it.ctx).Do()
|
||||
return err
|
||||
})
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
for _, item := range resp.Items {
|
||||
b, err := newBucket(item)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
it.buckets = append(it.buckets, b)
|
||||
}
|
||||
return resp.NextPageToken, nil
|
||||
}
|
||||
|
|
@ -0,0 +1,207 @@
|
|||
// Copyright 2016 Google Inc. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package storage
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
|
||||
"cloud.google.com/go/internal/trace"
|
||||
"golang.org/x/net/context"
|
||||
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)
|
||||
|
||||
dst, src *ObjectHandle
|
||||
}
|
||||
|
||||
// Run performs the copy.
|
||||
func (c *Copier) Run(ctx context.Context) (_ *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
|
||||
}
|
||||
// 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 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
|
||||
|
||||
dst *ObjectHandle
|
||||
srcs []*ObjectHandle
|
||||
}
|
||||
|
||||
// Run performs the compose operation.
|
||||
func (c *Composer) Run(ctx context.Context) (_ *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)
|
||||
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 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
|
||||
}
|
||||
|
|
@ -0,0 +1,167 @@
|
|||
// Copyright 2016 Google Inc. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
/*
|
||||
Package storage 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.
|
||||
|
||||
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.
|
||||
|
||||
|
||||
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 intial 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)
|
||||
|
||||
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)
|
||||
|
||||
Authentication
|
||||
|
||||
See examples of authorization and authentication at
|
||||
https://godoc.org/cloud.google.com/go#pkg-examples.
|
||||
*/
|
||||
package storage // import "cloud.google.com/go/storage"
|
||||
|
|
@ -0,0 +1,30 @@
|
|||
// Copyright 2017 Google Inc. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
// +build go1.10
|
||||
|
||||
package storage
|
||||
|
||||
import "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)
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,26 @@
|
|||
// Copyright 2017 Google Inc. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
// +build go1.7
|
||||
|
||||
package storage
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
func withContext(r *http.Request, ctx context.Context) *http.Request {
|
||||
return r.WithContext(ctx)
|
||||
}
|
||||
|
|
@ -0,0 +1,129 @@
|
|||
// Copyright 2017 Google Inc. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package storage
|
||||
|
||||
import (
|
||||
"cloud.google.com/go/iam"
|
||||
"cloud.google.com/go/internal/trace"
|
||||
"golang.org/x/net/context"
|
||||
raw "google.golang.org/api/storage/v1"
|
||||
iampb "google.golang.org/genproto/googleapis/iam/v1"
|
||||
)
|
||||
|
||||
// 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) (_ *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)
|
||||
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) (_ []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),
|
||||
}
|
||||
}
|
||||
|
||||
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,
|
||||
})
|
||||
}
|
||||
return rbs
|
||||
}
|
||||
|
||||
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,
|
||||
})
|
||||
}
|
||||
return ibs
|
||||
}
|
||||
|
|
@ -0,0 +1,36 @@
|
|||
// Copyright 2014 Google Inc. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package storage
|
||||
|
||||
import (
|
||||
"cloud.google.com/go/internal"
|
||||
gax "github.com/googleapis/gax-go"
|
||||
"golang.org/x/net/context"
|
||||
)
|
||||
|
||||
// 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
|
||||
})
|
||||
}
|
||||
|
|
@ -0,0 +1,40 @@
|
|||
// Copyright 2017 Google Inc. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
// +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")
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,26 @@
|
|||
// Copyright 2017 Google Inc. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
// +build !go1.7
|
||||
|
||||
package storage
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
)
|
||||
|
||||
func withContext(r *http.Request, _ interface{}) *http.Request {
|
||||
// In Go 1.6 and below, ignore the context.
|
||||
return r
|
||||
}
|
||||
|
|
@ -0,0 +1,188 @@
|
|||
// Copyright 2017 Google Inc. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package storage
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"regexp"
|
||||
|
||||
"cloud.google.com/go/internal/trace"
|
||||
"golang.org/x/net/context"
|
||||
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) (_ *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) (_ 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()
|
||||
}
|
||||
|
|
@ -0,0 +1,245 @@
|
|||
// Copyright 2016 Google Inc. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package storage
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"hash/crc32"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"cloud.google.com/go/internal/trace"
|
||||
"golang.org/x/net/context"
|
||||
"google.golang.org/api/googleapi"
|
||||
)
|
||||
|
||||
var crc32cTable = crc32.MakeTable(crc32.Castagnoli)
|
||||
|
||||
// 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.
|
||||
func (o *ObjectHandle) NewRangeReader(ctx context.Context, offset, length int64) (_ *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 {
|
||||
return nil, fmt.Errorf("storage: invalid offset %d < 0", offset)
|
||||
}
|
||||
if o.conds != nil {
|
||||
if err := o.conds.validate("NewRangeReader"); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
u := &url.URL{
|
||||
Scheme: "https",
|
||||
Host: "storage.googleapis.com",
|
||||
Path: fmt.Sprintf("/%s/%s", o.bucket, o.object),
|
||||
RawQuery: conditionsQuery(o.gen, o.conds),
|
||||
}
|
||||
verb := "GET"
|
||||
if length == 0 {
|
||||
verb = "HEAD"
|
||||
}
|
||||
req, err := http.NewRequest(verb, u.String(), nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
req = withContext(req, ctx)
|
||||
if length < 0 && offset > 0 {
|
||||
req.Header.Set("Range", fmt.Sprintf("bytes=%d-", offset))
|
||||
} else if length > 0 {
|
||||
req.Header.Set("Range", fmt.Sprintf("bytes=%d-%d", offset, offset+length-1))
|
||||
}
|
||||
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
|
||||
}
|
||||
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),
|
||||
}
|
||||
}
|
||||
if offset > 0 && length != 0 && res.StatusCode != http.StatusPartialContent {
|
||||
res.Body.Close()
|
||||
return errors.New("storage: partial request not satisfied")
|
||||
}
|
||||
return nil
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var size int64 // total size of object, even if a range was requested.
|
||||
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)
|
||||
}
|
||||
} else {
|
||||
size = res.ContentLength
|
||||
}
|
||||
|
||||
remain := res.ContentLength
|
||||
body := res.Body
|
||||
if length == 0 {
|
||||
remain = 0
|
||||
body.Close()
|
||||
body = emptyBody
|
||||
}
|
||||
var (
|
||||
checkCRC bool
|
||||
crc uint32
|
||||
)
|
||||
// Even if there is a CRC header, we can't compute the hash on partial data.
|
||||
if remain == size {
|
||||
crc, checkCRC = parseCRC32c(res)
|
||||
}
|
||||
return &Reader{
|
||||
body: body,
|
||||
size: size,
|
||||
remain: remain,
|
||||
contentType: res.Header.Get("Content-Type"),
|
||||
contentEncoding: res.Header.Get("Content-Encoding"),
|
||||
cacheControl: res.Header.Get("Cache-Control"),
|
||||
wantCRC: crc,
|
||||
checkCRC: checkCRC,
|
||||
}, nil
|
||||
}
|
||||
|
||||
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 {
|
||||
body io.ReadCloser
|
||||
remain, size int64
|
||||
contentType string
|
||||
contentEncoding string
|
||||
cacheControl string
|
||||
checkCRC bool // should we check the CRC?
|
||||
wantCRC uint32 // the CRC32c value the server sent in the header
|
||||
gotCRC uint32 // running crc
|
||||
checkedCRC bool // did we check the CRC? (For tests.)
|
||||
}
|
||||
|
||||
// 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.body.Read(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 r.remain == 0 { // Only check if we have Content-Length.
|
||||
r.checkedCRC = true
|
||||
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
|
||||
}
|
||||
|
||||
// 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.
|
||||
func (r *Reader) Size() int64 {
|
||||
return r.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.
|
||||
func (r *Reader) ContentType() string {
|
||||
return r.contentType
|
||||
}
|
||||
|
||||
// ContentEncoding returns the content encoding of the object.
|
||||
func (r *Reader) ContentEncoding() string {
|
||||
return r.contentEncoding
|
||||
}
|
||||
|
||||
// CacheControl returns the cache control of the object.
|
||||
func (r *Reader) CacheControl() string {
|
||||
return r.cacheControl
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
|
|
@ -0,0 +1,218 @@
|
|||
// Copyright 2014 Google Inc. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package storage
|
||||
|
||||
import (
|
||||
"encoding/base64"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"sync"
|
||||
"unicode/utf8"
|
||||
|
||||
"golang.org/x/net/context"
|
||||
"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. If zero, chunking will be disabled and
|
||||
// the object will be uploaded in a single request.
|
||||
//
|
||||
// ChunkSize will default to a reasonable value. If you perform many concurrent
|
||||
// writes of small objects, you may wish set ChunkSize to a value that matches
|
||||
// your objects' sizes to avoid consuming large amounts of memory.
|
||||
//
|
||||
// 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)
|
||||
}
|
||||
pr, pw := io.Pipe()
|
||||
w.pw = pw
|
||||
w.opened = true
|
||||
|
||||
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)
|
||||
}
|
||||
call := w.o.c.raw.Objects.Insert(w.o.bucket, rawObj).
|
||||
Media(pr, mediaOpts...).
|
||||
Projection("full").
|
||||
Context(w.ctx)
|
||||
if w.ProgressFunc != nil {
|
||||
call.ProgressUpdater(func(n, _ int64) { w.ProgressFunc(n) })
|
||||
}
|
||||
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())
|
||||
// If the chunk size is zero, then no chunking is done on the Reader,
|
||||
// which means we cannot retry: the first call will read the data, and if
|
||||
// it fails, there is no way to re-read.
|
||||
if w.ChunkSize == 0 {
|
||||
resp, err = call.Do()
|
||||
} else {
|
||||
// We will only retry here if the initial POST, which obtains a URI for
|
||||
// the resumable upload, fails with a retryable error. The upload itself
|
||||
// has its own retry logic.
|
||||
err = runWithRetry(w.ctx, func() error {
|
||||
var err2 error
|
||||
resp, err2 = call.Do()
|
||||
return err2
|
||||
})
|
||||
}
|
||||
}
|
||||
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.
|
||||
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
|
||||
}
|
||||
}
|
||||
return w.pw.Write(p)
|
||||
}
|
||||
|
||||
// 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
|
||||
}
|
||||
}
|
||||
if err := w.pw.Close(); err != nil {
|
||||
return err
|
||||
}
|
||||
<-w.donec
|
||||
w.mu.Lock()
|
||||
defer w.mu.Unlock()
|
||||
return w.err
|
||||
}
|
||||
|
||||
// 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
|
||||
}
|
||||
2215
vendor/github.com/golang/protobuf/protoc-gen-go/descriptor/descriptor.pb.go
generated
vendored
Normal file
2215
vendor/github.com/golang/protobuf/protoc-gen-go/descriptor/descriptor.pb.go
generated
vendored
Normal file
File diff suppressed because it is too large
Load Diff
|
|
@ -0,0 +1,139 @@
|
|||
// Go support for Protocol Buffers - Google's data interchange format
|
||||
//
|
||||
// Copyright 2016 The Go Authors. All rights reserved.
|
||||
// https://github.com/golang/protobuf
|
||||
//
|
||||
// Redistribution and use in source and binary forms, with or without
|
||||
// modification, are permitted provided that the following conditions are
|
||||
// met:
|
||||
//
|
||||
// * Redistributions of source code must retain the above copyright
|
||||
// notice, this list of conditions and the following disclaimer.
|
||||
// * Redistributions in binary form must reproduce the above
|
||||
// copyright notice, this list of conditions and the following disclaimer
|
||||
// in the documentation and/or other materials provided with the
|
||||
// distribution.
|
||||
// * Neither the name of Google Inc. nor the names of its
|
||||
// contributors may be used to endorse or promote products derived from
|
||||
// this software without specific prior written permission.
|
||||
//
|
||||
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
package ptypes
|
||||
|
||||
// This file implements functions to marshal proto.Message to/from
|
||||
// google.protobuf.Any message.
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"reflect"
|
||||
"strings"
|
||||
|
||||
"github.com/golang/protobuf/proto"
|
||||
"github.com/golang/protobuf/ptypes/any"
|
||||
)
|
||||
|
||||
const googleApis = "type.googleapis.com/"
|
||||
|
||||
// AnyMessageName returns the name of the message contained in a google.protobuf.Any message.
|
||||
//
|
||||
// Note that regular type assertions should be done using the Is
|
||||
// function. AnyMessageName is provided for less common use cases like filtering a
|
||||
// sequence of Any messages based on a set of allowed message type names.
|
||||
func AnyMessageName(any *any.Any) (string, error) {
|
||||
if any == nil {
|
||||
return "", fmt.Errorf("message is nil")
|
||||
}
|
||||
slash := strings.LastIndex(any.TypeUrl, "/")
|
||||
if slash < 0 {
|
||||
return "", fmt.Errorf("message type url %q is invalid", any.TypeUrl)
|
||||
}
|
||||
return any.TypeUrl[slash+1:], nil
|
||||
}
|
||||
|
||||
// MarshalAny takes the protocol buffer and encodes it into google.protobuf.Any.
|
||||
func MarshalAny(pb proto.Message) (*any.Any, error) {
|
||||
value, err := proto.Marshal(pb)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &any.Any{TypeUrl: googleApis + proto.MessageName(pb), Value: value}, nil
|
||||
}
|
||||
|
||||
// DynamicAny is a value that can be passed to UnmarshalAny to automatically
|
||||
// allocate a proto.Message for the type specified in a google.protobuf.Any
|
||||
// message. The allocated message is stored in the embedded proto.Message.
|
||||
//
|
||||
// Example:
|
||||
//
|
||||
// var x ptypes.DynamicAny
|
||||
// if err := ptypes.UnmarshalAny(a, &x); err != nil { ... }
|
||||
// fmt.Printf("unmarshaled message: %v", x.Message)
|
||||
type DynamicAny struct {
|
||||
proto.Message
|
||||
}
|
||||
|
||||
// Empty returns a new proto.Message of the type specified in a
|
||||
// google.protobuf.Any message. It returns an error if corresponding message
|
||||
// type isn't linked in.
|
||||
func Empty(any *any.Any) (proto.Message, error) {
|
||||
aname, err := AnyMessageName(any)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
t := proto.MessageType(aname)
|
||||
if t == nil {
|
||||
return nil, fmt.Errorf("any: message type %q isn't linked in", aname)
|
||||
}
|
||||
return reflect.New(t.Elem()).Interface().(proto.Message), nil
|
||||
}
|
||||
|
||||
// UnmarshalAny parses the protocol buffer representation in a google.protobuf.Any
|
||||
// message and places the decoded result in pb. It returns an error if type of
|
||||
// contents of Any message does not match type of pb message.
|
||||
//
|
||||
// pb can be a proto.Message, or a *DynamicAny.
|
||||
func UnmarshalAny(any *any.Any, pb proto.Message) error {
|
||||
if d, ok := pb.(*DynamicAny); ok {
|
||||
if d.Message == nil {
|
||||
var err error
|
||||
d.Message, err = Empty(any)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return UnmarshalAny(any, d.Message)
|
||||
}
|
||||
|
||||
aname, err := AnyMessageName(any)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
mname := proto.MessageName(pb)
|
||||
if aname != mname {
|
||||
return fmt.Errorf("mismatched message type: got %q want %q", aname, mname)
|
||||
}
|
||||
return proto.Unmarshal(any.Value, pb)
|
||||
}
|
||||
|
||||
// Is returns true if any value contains a given message type.
|
||||
func Is(any *any.Any, pb proto.Message) bool {
|
||||
aname, err := AnyMessageName(any)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
|
||||
return aname == proto.MessageName(pb)
|
||||
}
|
||||
|
|
@ -0,0 +1,178 @@
|
|||
// Code generated by protoc-gen-go. DO NOT EDIT.
|
||||
// source: google/protobuf/any.proto
|
||||
|
||||
/*
|
||||
Package any is a generated protocol buffer package.
|
||||
|
||||
It is generated from these files:
|
||||
google/protobuf/any.proto
|
||||
|
||||
It has these top-level messages:
|
||||
Any
|
||||
*/
|
||||
package any
|
||||
|
||||
import proto "github.com/golang/protobuf/proto"
|
||||
import fmt "fmt"
|
||||
import math "math"
|
||||
|
||||
// Reference imports to suppress errors if they are not otherwise used.
|
||||
var _ = proto.Marshal
|
||||
var _ = fmt.Errorf
|
||||
var _ = math.Inf
|
||||
|
||||
// This is a compile-time assertion to ensure that this generated file
|
||||
// is compatible with the proto package it is being compiled against.
|
||||
// A compilation error at this line likely means your copy of the
|
||||
// proto package needs to be updated.
|
||||
const _ = proto.ProtoPackageIsVersion2 // please upgrade the proto package
|
||||
|
||||
// `Any` contains an arbitrary serialized protocol buffer message along with a
|
||||
// URL that describes the type of the serialized message.
|
||||
//
|
||||
// Protobuf library provides support to pack/unpack Any values in the form
|
||||
// of utility functions or additional generated methods of the Any type.
|
||||
//
|
||||
// Example 1: Pack and unpack a message in C++.
|
||||
//
|
||||
// Foo foo = ...;
|
||||
// Any any;
|
||||
// any.PackFrom(foo);
|
||||
// ...
|
||||
// if (any.UnpackTo(&foo)) {
|
||||
// ...
|
||||
// }
|
||||
//
|
||||
// Example 2: Pack and unpack a message in Java.
|
||||
//
|
||||
// Foo foo = ...;
|
||||
// Any any = Any.pack(foo);
|
||||
// ...
|
||||
// if (any.is(Foo.class)) {
|
||||
// foo = any.unpack(Foo.class);
|
||||
// }
|
||||
//
|
||||
// Example 3: Pack and unpack a message in Python.
|
||||
//
|
||||
// foo = Foo(...)
|
||||
// any = Any()
|
||||
// any.Pack(foo)
|
||||
// ...
|
||||
// if any.Is(Foo.DESCRIPTOR):
|
||||
// any.Unpack(foo)
|
||||
// ...
|
||||
//
|
||||
// Example 4: Pack and unpack a message in Go
|
||||
//
|
||||
// foo := &pb.Foo{...}
|
||||
// any, err := ptypes.MarshalAny(foo)
|
||||
// ...
|
||||
// foo := &pb.Foo{}
|
||||
// if err := ptypes.UnmarshalAny(any, foo); err != nil {
|
||||
// ...
|
||||
// }
|
||||
//
|
||||
// The pack methods provided by protobuf library will by default use
|
||||
// 'type.googleapis.com/full.type.name' as the type URL and the unpack
|
||||
// methods only use the fully qualified type name after the last '/'
|
||||
// in the type URL, for example "foo.bar.com/x/y.z" will yield type
|
||||
// name "y.z".
|
||||
//
|
||||
//
|
||||
// JSON
|
||||
// ====
|
||||
// The JSON representation of an `Any` value uses the regular
|
||||
// representation of the deserialized, embedded message, with an
|
||||
// additional field `@type` which contains the type URL. Example:
|
||||
//
|
||||
// package google.profile;
|
||||
// message Person {
|
||||
// string first_name = 1;
|
||||
// string last_name = 2;
|
||||
// }
|
||||
//
|
||||
// {
|
||||
// "@type": "type.googleapis.com/google.profile.Person",
|
||||
// "firstName": <string>,
|
||||
// "lastName": <string>
|
||||
// }
|
||||
//
|
||||
// If the embedded message type is well-known and has a custom JSON
|
||||
// representation, that representation will be embedded adding a field
|
||||
// `value` which holds the custom JSON in addition to the `@type`
|
||||
// field. Example (for message [google.protobuf.Duration][]):
|
||||
//
|
||||
// {
|
||||
// "@type": "type.googleapis.com/google.protobuf.Duration",
|
||||
// "value": "1.212s"
|
||||
// }
|
||||
//
|
||||
type Any struct {
|
||||
// A URL/resource name whose content describes the type of the
|
||||
// serialized protocol buffer message.
|
||||
//
|
||||
// For URLs which use the scheme `http`, `https`, or no scheme, the
|
||||
// following restrictions and interpretations apply:
|
||||
//
|
||||
// * If no scheme is provided, `https` is assumed.
|
||||
// * The last segment of the URL's path must represent the fully
|
||||
// qualified name of the type (as in `path/google.protobuf.Duration`).
|
||||
// The name should be in a canonical form (e.g., leading "." is
|
||||
// not accepted).
|
||||
// * An HTTP GET on the URL must yield a [google.protobuf.Type][]
|
||||
// value in binary format, or produce an error.
|
||||
// * Applications are allowed to cache lookup results based on the
|
||||
// URL, or have them precompiled into a binary to avoid any
|
||||
// lookup. Therefore, binary compatibility needs to be preserved
|
||||
// on changes to types. (Use versioned type names to manage
|
||||
// breaking changes.)
|
||||
//
|
||||
// Schemes other than `http`, `https` (or the empty scheme) might be
|
||||
// used with implementation specific semantics.
|
||||
//
|
||||
TypeUrl string `protobuf:"bytes,1,opt,name=type_url,json=typeUrl" json:"type_url,omitempty"`
|
||||
// Must be a valid serialized protocol buffer of the above specified type.
|
||||
Value []byte `protobuf:"bytes,2,opt,name=value,proto3" json:"value,omitempty"`
|
||||
}
|
||||
|
||||
func (m *Any) Reset() { *m = Any{} }
|
||||
func (m *Any) String() string { return proto.CompactTextString(m) }
|
||||
func (*Any) ProtoMessage() {}
|
||||
func (*Any) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{0} }
|
||||
func (*Any) XXX_WellKnownType() string { return "Any" }
|
||||
|
||||
func (m *Any) GetTypeUrl() string {
|
||||
if m != nil {
|
||||
return m.TypeUrl
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (m *Any) GetValue() []byte {
|
||||
if m != nil {
|
||||
return m.Value
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func init() {
|
||||
proto.RegisterType((*Any)(nil), "google.protobuf.Any")
|
||||
}
|
||||
|
||||
func init() { proto.RegisterFile("google/protobuf/any.proto", fileDescriptor0) }
|
||||
|
||||
var fileDescriptor0 = []byte{
|
||||
// 185 bytes of a gzipped FileDescriptorProto
|
||||
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xe2, 0x92, 0x4c, 0xcf, 0xcf, 0x4f,
|
||||
0xcf, 0x49, 0xd5, 0x2f, 0x28, 0xca, 0x2f, 0xc9, 0x4f, 0x2a, 0x4d, 0xd3, 0x4f, 0xcc, 0xab, 0xd4,
|
||||
0x03, 0x73, 0x84, 0xf8, 0x21, 0x52, 0x7a, 0x30, 0x29, 0x25, 0x33, 0x2e, 0x66, 0xc7, 0xbc, 0x4a,
|
||||
0x21, 0x49, 0x2e, 0x8e, 0x92, 0xca, 0x82, 0xd4, 0xf8, 0xd2, 0xa2, 0x1c, 0x09, 0x46, 0x05, 0x46,
|
||||
0x0d, 0xce, 0x20, 0x76, 0x10, 0x3f, 0xb4, 0x28, 0x47, 0x48, 0x84, 0x8b, 0xb5, 0x2c, 0x31, 0xa7,
|
||||
0x34, 0x55, 0x82, 0x49, 0x81, 0x51, 0x83, 0x27, 0x08, 0xc2, 0x71, 0xca, 0xe7, 0x12, 0x4e, 0xce,
|
||||
0xcf, 0xd5, 0x43, 0x33, 0xce, 0x89, 0xc3, 0x31, 0xaf, 0x32, 0x00, 0xc4, 0x09, 0x60, 0x8c, 0x52,
|
||||
0x4d, 0xcf, 0x2c, 0xc9, 0x28, 0x4d, 0xd2, 0x4b, 0xce, 0xcf, 0xd5, 0x4f, 0xcf, 0xcf, 0x49, 0xcc,
|
||||
0x4b, 0x47, 0xb8, 0xa8, 0x00, 0x64, 0x7a, 0x31, 0xc8, 0x61, 0x8b, 0x98, 0x98, 0xdd, 0x03, 0x9c,
|
||||
0x56, 0x31, 0xc9, 0xb9, 0x43, 0x8c, 0x0a, 0x80, 0x2a, 0xd1, 0x0b, 0x4f, 0xcd, 0xc9, 0xf1, 0xce,
|
||||
0xcb, 0x2f, 0xcf, 0x0b, 0x01, 0x29, 0x4d, 0x62, 0x03, 0xeb, 0x35, 0x06, 0x04, 0x00, 0x00, 0xff,
|
||||
0xff, 0x13, 0xf8, 0xe8, 0x42, 0xdd, 0x00, 0x00, 0x00,
|
||||
}
|
||||
|
|
@ -0,0 +1,35 @@
|
|||
// Go support for Protocol Buffers - Google's data interchange format
|
||||
//
|
||||
// Copyright 2016 The Go Authors. All rights reserved.
|
||||
// https://github.com/golang/protobuf
|
||||
//
|
||||
// Redistribution and use in source and binary forms, with or without
|
||||
// modification, are permitted provided that the following conditions are
|
||||
// met:
|
||||
//
|
||||
// * Redistributions of source code must retain the above copyright
|
||||
// notice, this list of conditions and the following disclaimer.
|
||||
// * Redistributions in binary form must reproduce the above
|
||||
// copyright notice, this list of conditions and the following disclaimer
|
||||
// in the documentation and/or other materials provided with the
|
||||
// distribution.
|
||||
// * Neither the name of Google Inc. nor the names of its
|
||||
// contributors may be used to endorse or promote products derived from
|
||||
// this software without specific prior written permission.
|
||||
//
|
||||
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
/*
|
||||
Package ptypes contains code for interacting with well-known types.
|
||||
*/
|
||||
package ptypes
|
||||
|
|
@ -0,0 +1,102 @@
|
|||
// Go support for Protocol Buffers - Google's data interchange format
|
||||
//
|
||||
// Copyright 2016 The Go Authors. All rights reserved.
|
||||
// https://github.com/golang/protobuf
|
||||
//
|
||||
// Redistribution and use in source and binary forms, with or without
|
||||
// modification, are permitted provided that the following conditions are
|
||||
// met:
|
||||
//
|
||||
// * Redistributions of source code must retain the above copyright
|
||||
// notice, this list of conditions and the following disclaimer.
|
||||
// * Redistributions in binary form must reproduce the above
|
||||
// copyright notice, this list of conditions and the following disclaimer
|
||||
// in the documentation and/or other materials provided with the
|
||||
// distribution.
|
||||
// * Neither the name of Google Inc. nor the names of its
|
||||
// contributors may be used to endorse or promote products derived from
|
||||
// this software without specific prior written permission.
|
||||
//
|
||||
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
package ptypes
|
||||
|
||||
// This file implements conversions between google.protobuf.Duration
|
||||
// and time.Duration.
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
durpb "github.com/golang/protobuf/ptypes/duration"
|
||||
)
|
||||
|
||||
const (
|
||||
// Range of a durpb.Duration in seconds, as specified in
|
||||
// google/protobuf/duration.proto. This is about 10,000 years in seconds.
|
||||
maxSeconds = int64(10000 * 365.25 * 24 * 60 * 60)
|
||||
minSeconds = -maxSeconds
|
||||
)
|
||||
|
||||
// validateDuration determines whether the durpb.Duration is valid according to the
|
||||
// definition in google/protobuf/duration.proto. A valid durpb.Duration
|
||||
// may still be too large to fit into a time.Duration (the range of durpb.Duration
|
||||
// is about 10,000 years, and the range of time.Duration is about 290).
|
||||
func validateDuration(d *durpb.Duration) error {
|
||||
if d == nil {
|
||||
return errors.New("duration: nil Duration")
|
||||
}
|
||||
if d.Seconds < minSeconds || d.Seconds > maxSeconds {
|
||||
return fmt.Errorf("duration: %v: seconds out of range", d)
|
||||
}
|
||||
if d.Nanos <= -1e9 || d.Nanos >= 1e9 {
|
||||
return fmt.Errorf("duration: %v: nanos out of range", d)
|
||||
}
|
||||
// Seconds and Nanos must have the same sign, unless d.Nanos is zero.
|
||||
if (d.Seconds < 0 && d.Nanos > 0) || (d.Seconds > 0 && d.Nanos < 0) {
|
||||
return fmt.Errorf("duration: %v: seconds and nanos have different signs", d)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Duration converts a durpb.Duration to a time.Duration. Duration
|
||||
// returns an error if the durpb.Duration is invalid or is too large to be
|
||||
// represented in a time.Duration.
|
||||
func Duration(p *durpb.Duration) (time.Duration, error) {
|
||||
if err := validateDuration(p); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
d := time.Duration(p.Seconds) * time.Second
|
||||
if int64(d/time.Second) != p.Seconds {
|
||||
return 0, fmt.Errorf("duration: %v is out of range for time.Duration", p)
|
||||
}
|
||||
if p.Nanos != 0 {
|
||||
d += time.Duration(p.Nanos)
|
||||
if (d < 0) != (p.Nanos < 0) {
|
||||
return 0, fmt.Errorf("duration: %v is out of range for time.Duration", p)
|
||||
}
|
||||
}
|
||||
return d, nil
|
||||
}
|
||||
|
||||
// DurationProto converts a time.Duration to a durpb.Duration.
|
||||
func DurationProto(d time.Duration) *durpb.Duration {
|
||||
nanos := d.Nanoseconds()
|
||||
secs := nanos / 1e9
|
||||
nanos -= secs * 1e9
|
||||
return &durpb.Duration{
|
||||
Seconds: secs,
|
||||
Nanos: int32(nanos),
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,144 @@
|
|||
// Code generated by protoc-gen-go. DO NOT EDIT.
|
||||
// source: google/protobuf/duration.proto
|
||||
|
||||
/*
|
||||
Package duration is a generated protocol buffer package.
|
||||
|
||||
It is generated from these files:
|
||||
google/protobuf/duration.proto
|
||||
|
||||
It has these top-level messages:
|
||||
Duration
|
||||
*/
|
||||
package duration
|
||||
|
||||
import proto "github.com/golang/protobuf/proto"
|
||||
import fmt "fmt"
|
||||
import math "math"
|
||||
|
||||
// Reference imports to suppress errors if they are not otherwise used.
|
||||
var _ = proto.Marshal
|
||||
var _ = fmt.Errorf
|
||||
var _ = math.Inf
|
||||
|
||||
// This is a compile-time assertion to ensure that this generated file
|
||||
// is compatible with the proto package it is being compiled against.
|
||||
// A compilation error at this line likely means your copy of the
|
||||
// proto package needs to be updated.
|
||||
const _ = proto.ProtoPackageIsVersion2 // please upgrade the proto package
|
||||
|
||||
// A Duration represents a signed, fixed-length span of time represented
|
||||
// as a count of seconds and fractions of seconds at nanosecond
|
||||
// resolution. It is independent of any calendar and concepts like "day"
|
||||
// or "month". It is related to Timestamp in that the difference between
|
||||
// two Timestamp values is a Duration and it can be added or subtracted
|
||||
// from a Timestamp. Range is approximately +-10,000 years.
|
||||
//
|
||||
// # Examples
|
||||
//
|
||||
// Example 1: Compute Duration from two Timestamps in pseudo code.
|
||||
//
|
||||
// Timestamp start = ...;
|
||||
// Timestamp end = ...;
|
||||
// Duration duration = ...;
|
||||
//
|
||||
// duration.seconds = end.seconds - start.seconds;
|
||||
// duration.nanos = end.nanos - start.nanos;
|
||||
//
|
||||
// if (duration.seconds < 0 && duration.nanos > 0) {
|
||||
// duration.seconds += 1;
|
||||
// duration.nanos -= 1000000000;
|
||||
// } else if (durations.seconds > 0 && duration.nanos < 0) {
|
||||
// duration.seconds -= 1;
|
||||
// duration.nanos += 1000000000;
|
||||
// }
|
||||
//
|
||||
// Example 2: Compute Timestamp from Timestamp + Duration in pseudo code.
|
||||
//
|
||||
// Timestamp start = ...;
|
||||
// Duration duration = ...;
|
||||
// Timestamp end = ...;
|
||||
//
|
||||
// end.seconds = start.seconds + duration.seconds;
|
||||
// end.nanos = start.nanos + duration.nanos;
|
||||
//
|
||||
// if (end.nanos < 0) {
|
||||
// end.seconds -= 1;
|
||||
// end.nanos += 1000000000;
|
||||
// } else if (end.nanos >= 1000000000) {
|
||||
// end.seconds += 1;
|
||||
// end.nanos -= 1000000000;
|
||||
// }
|
||||
//
|
||||
// Example 3: Compute Duration from datetime.timedelta in Python.
|
||||
//
|
||||
// td = datetime.timedelta(days=3, minutes=10)
|
||||
// duration = Duration()
|
||||
// duration.FromTimedelta(td)
|
||||
//
|
||||
// # JSON Mapping
|
||||
//
|
||||
// In JSON format, the Duration type is encoded as a string rather than an
|
||||
// object, where the string ends in the suffix "s" (indicating seconds) and
|
||||
// is preceded by the number of seconds, with nanoseconds expressed as
|
||||
// fractional seconds. For example, 3 seconds with 0 nanoseconds should be
|
||||
// encoded in JSON format as "3s", while 3 seconds and 1 nanosecond should
|
||||
// be expressed in JSON format as "3.000000001s", and 3 seconds and 1
|
||||
// microsecond should be expressed in JSON format as "3.000001s".
|
||||
//
|
||||
//
|
||||
type Duration struct {
|
||||
// Signed seconds of the span of time. Must be from -315,576,000,000
|
||||
// to +315,576,000,000 inclusive. Note: these bounds are computed from:
|
||||
// 60 sec/min * 60 min/hr * 24 hr/day * 365.25 days/year * 10000 years
|
||||
Seconds int64 `protobuf:"varint,1,opt,name=seconds" json:"seconds,omitempty"`
|
||||
// Signed fractions of a second at nanosecond resolution of the span
|
||||
// of time. Durations less than one second are represented with a 0
|
||||
// `seconds` field and a positive or negative `nanos` field. For durations
|
||||
// of one second or more, a non-zero value for the `nanos` field must be
|
||||
// of the same sign as the `seconds` field. Must be from -999,999,999
|
||||
// to +999,999,999 inclusive.
|
||||
Nanos int32 `protobuf:"varint,2,opt,name=nanos" json:"nanos,omitempty"`
|
||||
}
|
||||
|
||||
func (m *Duration) Reset() { *m = Duration{} }
|
||||
func (m *Duration) String() string { return proto.CompactTextString(m) }
|
||||
func (*Duration) ProtoMessage() {}
|
||||
func (*Duration) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{0} }
|
||||
func (*Duration) XXX_WellKnownType() string { return "Duration" }
|
||||
|
||||
func (m *Duration) GetSeconds() int64 {
|
||||
if m != nil {
|
||||
return m.Seconds
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
func (m *Duration) GetNanos() int32 {
|
||||
if m != nil {
|
||||
return m.Nanos
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
func init() {
|
||||
proto.RegisterType((*Duration)(nil), "google.protobuf.Duration")
|
||||
}
|
||||
|
||||
func init() { proto.RegisterFile("google/protobuf/duration.proto", fileDescriptor0) }
|
||||
|
||||
var fileDescriptor0 = []byte{
|
||||
// 190 bytes of a gzipped FileDescriptorProto
|
||||
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xe2, 0x92, 0x4b, 0xcf, 0xcf, 0x4f,
|
||||
0xcf, 0x49, 0xd5, 0x2f, 0x28, 0xca, 0x2f, 0xc9, 0x4f, 0x2a, 0x4d, 0xd3, 0x4f, 0x29, 0x2d, 0x4a,
|
||||
0x2c, 0xc9, 0xcc, 0xcf, 0xd3, 0x03, 0x8b, 0x08, 0xf1, 0x43, 0xe4, 0xf5, 0x60, 0xf2, 0x4a, 0x56,
|
||||
0x5c, 0x1c, 0x2e, 0x50, 0x25, 0x42, 0x12, 0x5c, 0xec, 0xc5, 0xa9, 0xc9, 0xf9, 0x79, 0x29, 0xc5,
|
||||
0x12, 0x8c, 0x0a, 0x8c, 0x1a, 0xcc, 0x41, 0x30, 0xae, 0x90, 0x08, 0x17, 0x6b, 0x5e, 0x62, 0x5e,
|
||||
0x7e, 0xb1, 0x04, 0x93, 0x02, 0xa3, 0x06, 0x6b, 0x10, 0x84, 0xe3, 0x54, 0xc3, 0x25, 0x9c, 0x9c,
|
||||
0x9f, 0xab, 0x87, 0x66, 0xa4, 0x13, 0x2f, 0xcc, 0xc0, 0x00, 0x90, 0x48, 0x00, 0x63, 0x94, 0x56,
|
||||
0x7a, 0x66, 0x49, 0x46, 0x69, 0x92, 0x5e, 0x72, 0x7e, 0xae, 0x7e, 0x7a, 0x7e, 0x4e, 0x62, 0x5e,
|
||||
0x3a, 0xc2, 0x7d, 0x05, 0x25, 0x95, 0x05, 0xa9, 0xc5, 0x70, 0x67, 0xfe, 0x60, 0x64, 0x5c, 0xc4,
|
||||
0xc4, 0xec, 0x1e, 0xe0, 0xb4, 0x8a, 0x49, 0xce, 0x1d, 0x62, 0x6e, 0x00, 0x54, 0xa9, 0x5e, 0x78,
|
||||
0x6a, 0x4e, 0x8e, 0x77, 0x5e, 0x7e, 0x79, 0x5e, 0x08, 0x48, 0x4b, 0x12, 0x1b, 0xd8, 0x0c, 0x63,
|
||||
0x40, 0x00, 0x00, 0x00, 0xff, 0xff, 0xdc, 0x84, 0x30, 0xff, 0xf3, 0x00, 0x00, 0x00,
|
||||
}
|
||||
|
|
@ -0,0 +1,134 @@
|
|||
// Go support for Protocol Buffers - Google's data interchange format
|
||||
//
|
||||
// Copyright 2016 The Go Authors. All rights reserved.
|
||||
// https://github.com/golang/protobuf
|
||||
//
|
||||
// Redistribution and use in source and binary forms, with or without
|
||||
// modification, are permitted provided that the following conditions are
|
||||
// met:
|
||||
//
|
||||
// * Redistributions of source code must retain the above copyright
|
||||
// notice, this list of conditions and the following disclaimer.
|
||||
// * Redistributions in binary form must reproduce the above
|
||||
// copyright notice, this list of conditions and the following disclaimer
|
||||
// in the documentation and/or other materials provided with the
|
||||
// distribution.
|
||||
// * Neither the name of Google Inc. nor the names of its
|
||||
// contributors may be used to endorse or promote products derived from
|
||||
// this software without specific prior written permission.
|
||||
//
|
||||
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
package ptypes
|
||||
|
||||
// This file implements operations on google.protobuf.Timestamp.
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
tspb "github.com/golang/protobuf/ptypes/timestamp"
|
||||
)
|
||||
|
||||
const (
|
||||
// Seconds field of the earliest valid Timestamp.
|
||||
// This is time.Date(1, 1, 1, 0, 0, 0, 0, time.UTC).Unix().
|
||||
minValidSeconds = -62135596800
|
||||
// Seconds field just after the latest valid Timestamp.
|
||||
// This is time.Date(10000, 1, 1, 0, 0, 0, 0, time.UTC).Unix().
|
||||
maxValidSeconds = 253402300800
|
||||
)
|
||||
|
||||
// validateTimestamp determines whether a Timestamp is valid.
|
||||
// A valid timestamp represents a time in the range
|
||||
// [0001-01-01, 10000-01-01) and has a Nanos field
|
||||
// in the range [0, 1e9).
|
||||
//
|
||||
// If the Timestamp is valid, validateTimestamp returns nil.
|
||||
// Otherwise, it returns an error that describes
|
||||
// the problem.
|
||||
//
|
||||
// Every valid Timestamp can be represented by a time.Time, but the converse is not true.
|
||||
func validateTimestamp(ts *tspb.Timestamp) error {
|
||||
if ts == nil {
|
||||
return errors.New("timestamp: nil Timestamp")
|
||||
}
|
||||
if ts.Seconds < minValidSeconds {
|
||||
return fmt.Errorf("timestamp: %v before 0001-01-01", ts)
|
||||
}
|
||||
if ts.Seconds >= maxValidSeconds {
|
||||
return fmt.Errorf("timestamp: %v after 10000-01-01", ts)
|
||||
}
|
||||
if ts.Nanos < 0 || ts.Nanos >= 1e9 {
|
||||
return fmt.Errorf("timestamp: %v: nanos not in range [0, 1e9)", ts)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Timestamp converts a google.protobuf.Timestamp proto to a time.Time.
|
||||
// It returns an error if the argument is invalid.
|
||||
//
|
||||
// Unlike most Go functions, if Timestamp returns an error, the first return value
|
||||
// is not the zero time.Time. Instead, it is the value obtained from the
|
||||
// time.Unix function when passed the contents of the Timestamp, in the UTC
|
||||
// locale. This may or may not be a meaningful time; many invalid Timestamps
|
||||
// do map to valid time.Times.
|
||||
//
|
||||
// A nil Timestamp returns an error. The first return value in that case is
|
||||
// undefined.
|
||||
func Timestamp(ts *tspb.Timestamp) (time.Time, error) {
|
||||
// Don't return the zero value on error, because corresponds to a valid
|
||||
// timestamp. Instead return whatever time.Unix gives us.
|
||||
var t time.Time
|
||||
if ts == nil {
|
||||
t = time.Unix(0, 0).UTC() // treat nil like the empty Timestamp
|
||||
} else {
|
||||
t = time.Unix(ts.Seconds, int64(ts.Nanos)).UTC()
|
||||
}
|
||||
return t, validateTimestamp(ts)
|
||||
}
|
||||
|
||||
// TimestampNow returns a google.protobuf.Timestamp for the current time.
|
||||
func TimestampNow() *tspb.Timestamp {
|
||||
ts, err := TimestampProto(time.Now())
|
||||
if err != nil {
|
||||
panic("ptypes: time.Now() out of Timestamp range")
|
||||
}
|
||||
return ts
|
||||
}
|
||||
|
||||
// TimestampProto converts the time.Time to a google.protobuf.Timestamp proto.
|
||||
// It returns an error if the resulting Timestamp is invalid.
|
||||
func TimestampProto(t time.Time) (*tspb.Timestamp, error) {
|
||||
seconds := t.Unix()
|
||||
nanos := int32(t.Sub(time.Unix(seconds, 0)))
|
||||
ts := &tspb.Timestamp{
|
||||
Seconds: seconds,
|
||||
Nanos: nanos,
|
||||
}
|
||||
if err := validateTimestamp(ts); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return ts, nil
|
||||
}
|
||||
|
||||
// TimestampString returns the RFC 3339 string for valid Timestamps. For invalid
|
||||
// Timestamps, it returns an error message in parentheses.
|
||||
func TimestampString(ts *tspb.Timestamp) string {
|
||||
t, err := Timestamp(ts)
|
||||
if err != nil {
|
||||
return fmt.Sprintf("(%v)", err)
|
||||
}
|
||||
return t.Format(time.RFC3339Nano)
|
||||
}
|
||||
160
vendor/github.com/golang/protobuf/ptypes/timestamp/timestamp.pb.go
generated
vendored
Normal file
160
vendor/github.com/golang/protobuf/ptypes/timestamp/timestamp.pb.go
generated
vendored
Normal file
|
|
@ -0,0 +1,160 @@
|
|||
// Code generated by protoc-gen-go. DO NOT EDIT.
|
||||
// source: google/protobuf/timestamp.proto
|
||||
|
||||
/*
|
||||
Package timestamp is a generated protocol buffer package.
|
||||
|
||||
It is generated from these files:
|
||||
google/protobuf/timestamp.proto
|
||||
|
||||
It has these top-level messages:
|
||||
Timestamp
|
||||
*/
|
||||
package timestamp
|
||||
|
||||
import proto "github.com/golang/protobuf/proto"
|
||||
import fmt "fmt"
|
||||
import math "math"
|
||||
|
||||
// Reference imports to suppress errors if they are not otherwise used.
|
||||
var _ = proto.Marshal
|
||||
var _ = fmt.Errorf
|
||||
var _ = math.Inf
|
||||
|
||||
// This is a compile-time assertion to ensure that this generated file
|
||||
// is compatible with the proto package it is being compiled against.
|
||||
// A compilation error at this line likely means your copy of the
|
||||
// proto package needs to be updated.
|
||||
const _ = proto.ProtoPackageIsVersion2 // please upgrade the proto package
|
||||
|
||||
// A Timestamp represents a point in time independent of any time zone
|
||||
// or calendar, represented as seconds and fractions of seconds at
|
||||
// nanosecond resolution in UTC Epoch time. It is encoded using the
|
||||
// Proleptic Gregorian Calendar which extends the Gregorian calendar
|
||||
// backwards to year one. It is encoded assuming all minutes are 60
|
||||
// seconds long, i.e. leap seconds are "smeared" so that no leap second
|
||||
// table is needed for interpretation. Range is from
|
||||
// 0001-01-01T00:00:00Z to 9999-12-31T23:59:59.999999999Z.
|
||||
// By restricting to that range, we ensure that we can convert to
|
||||
// and from RFC 3339 date strings.
|
||||
// See [https://www.ietf.org/rfc/rfc3339.txt](https://www.ietf.org/rfc/rfc3339.txt).
|
||||
//
|
||||
// # Examples
|
||||
//
|
||||
// Example 1: Compute Timestamp from POSIX `time()`.
|
||||
//
|
||||
// Timestamp timestamp;
|
||||
// timestamp.set_seconds(time(NULL));
|
||||
// timestamp.set_nanos(0);
|
||||
//
|
||||
// Example 2: Compute Timestamp from POSIX `gettimeofday()`.
|
||||
//
|
||||
// struct timeval tv;
|
||||
// gettimeofday(&tv, NULL);
|
||||
//
|
||||
// Timestamp timestamp;
|
||||
// timestamp.set_seconds(tv.tv_sec);
|
||||
// timestamp.set_nanos(tv.tv_usec * 1000);
|
||||
//
|
||||
// Example 3: Compute Timestamp from Win32 `GetSystemTimeAsFileTime()`.
|
||||
//
|
||||
// FILETIME ft;
|
||||
// GetSystemTimeAsFileTime(&ft);
|
||||
// UINT64 ticks = (((UINT64)ft.dwHighDateTime) << 32) | ft.dwLowDateTime;
|
||||
//
|
||||
// // A Windows tick is 100 nanoseconds. Windows epoch 1601-01-01T00:00:00Z
|
||||
// // is 11644473600 seconds before Unix epoch 1970-01-01T00:00:00Z.
|
||||
// Timestamp timestamp;
|
||||
// timestamp.set_seconds((INT64) ((ticks / 10000000) - 11644473600LL));
|
||||
// timestamp.set_nanos((INT32) ((ticks % 10000000) * 100));
|
||||
//
|
||||
// Example 4: Compute Timestamp from Java `System.currentTimeMillis()`.
|
||||
//
|
||||
// long millis = System.currentTimeMillis();
|
||||
//
|
||||
// Timestamp timestamp = Timestamp.newBuilder().setSeconds(millis / 1000)
|
||||
// .setNanos((int) ((millis % 1000) * 1000000)).build();
|
||||
//
|
||||
//
|
||||
// Example 5: Compute Timestamp from current time in Python.
|
||||
//
|
||||
// timestamp = Timestamp()
|
||||
// timestamp.GetCurrentTime()
|
||||
//
|
||||
// # JSON Mapping
|
||||
//
|
||||
// In JSON format, the Timestamp type is encoded as a string in the
|
||||
// [RFC 3339](https://www.ietf.org/rfc/rfc3339.txt) format. That is, the
|
||||
// format is "{year}-{month}-{day}T{hour}:{min}:{sec}[.{frac_sec}]Z"
|
||||
// where {year} is always expressed using four digits while {month}, {day},
|
||||
// {hour}, {min}, and {sec} are zero-padded to two digits each. The fractional
|
||||
// seconds, which can go up to 9 digits (i.e. up to 1 nanosecond resolution),
|
||||
// are optional. The "Z" suffix indicates the timezone ("UTC"); the timezone
|
||||
// is required, though only UTC (as indicated by "Z") is presently supported.
|
||||
//
|
||||
// For example, "2017-01-15T01:30:15.01Z" encodes 15.01 seconds past
|
||||
// 01:30 UTC on January 15, 2017.
|
||||
//
|
||||
// In JavaScript, one can convert a Date object to this format using the
|
||||
// standard [toISOString()](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date/toISOString]
|
||||
// method. In Python, a standard `datetime.datetime` object can be converted
|
||||
// to this format using [`strftime`](https://docs.python.org/2/library/time.html#time.strftime)
|
||||
// with the time format spec '%Y-%m-%dT%H:%M:%S.%fZ'. Likewise, in Java, one
|
||||
// can use the Joda Time's [`ISODateTimeFormat.dateTime()`](
|
||||
// http://joda-time.sourceforge.net/apidocs/org/joda/time/format/ISODateTimeFormat.html#dateTime())
|
||||
// to obtain a formatter capable of generating timestamps in this format.
|
||||
//
|
||||
//
|
||||
type Timestamp struct {
|
||||
// Represents seconds of UTC time since Unix epoch
|
||||
// 1970-01-01T00:00:00Z. Must be from 0001-01-01T00:00:00Z to
|
||||
// 9999-12-31T23:59:59Z inclusive.
|
||||
Seconds int64 `protobuf:"varint,1,opt,name=seconds" json:"seconds,omitempty"`
|
||||
// Non-negative fractions of a second at nanosecond resolution. Negative
|
||||
// second values with fractions must still have non-negative nanos values
|
||||
// that count forward in time. Must be from 0 to 999,999,999
|
||||
// inclusive.
|
||||
Nanos int32 `protobuf:"varint,2,opt,name=nanos" json:"nanos,omitempty"`
|
||||
}
|
||||
|
||||
func (m *Timestamp) Reset() { *m = Timestamp{} }
|
||||
func (m *Timestamp) String() string { return proto.CompactTextString(m) }
|
||||
func (*Timestamp) ProtoMessage() {}
|
||||
func (*Timestamp) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{0} }
|
||||
func (*Timestamp) XXX_WellKnownType() string { return "Timestamp" }
|
||||
|
||||
func (m *Timestamp) GetSeconds() int64 {
|
||||
if m != nil {
|
||||
return m.Seconds
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
func (m *Timestamp) GetNanos() int32 {
|
||||
if m != nil {
|
||||
return m.Nanos
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
func init() {
|
||||
proto.RegisterType((*Timestamp)(nil), "google.protobuf.Timestamp")
|
||||
}
|
||||
|
||||
func init() { proto.RegisterFile("google/protobuf/timestamp.proto", fileDescriptor0) }
|
||||
|
||||
var fileDescriptor0 = []byte{
|
||||
// 191 bytes of a gzipped FileDescriptorProto
|
||||
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xe2, 0x92, 0x4f, 0xcf, 0xcf, 0x4f,
|
||||
0xcf, 0x49, 0xd5, 0x2f, 0x28, 0xca, 0x2f, 0xc9, 0x4f, 0x2a, 0x4d, 0xd3, 0x2f, 0xc9, 0xcc, 0x4d,
|
||||
0x2d, 0x2e, 0x49, 0xcc, 0x2d, 0xd0, 0x03, 0x0b, 0x09, 0xf1, 0x43, 0x14, 0xe8, 0xc1, 0x14, 0x28,
|
||||
0x59, 0x73, 0x71, 0x86, 0xc0, 0xd4, 0x08, 0x49, 0x70, 0xb1, 0x17, 0xa7, 0x26, 0xe7, 0xe7, 0xa5,
|
||||
0x14, 0x4b, 0x30, 0x2a, 0x30, 0x6a, 0x30, 0x07, 0xc1, 0xb8, 0x42, 0x22, 0x5c, 0xac, 0x79, 0x89,
|
||||
0x79, 0xf9, 0xc5, 0x12, 0x4c, 0x0a, 0x8c, 0x1a, 0xac, 0x41, 0x10, 0x8e, 0x53, 0x1d, 0x97, 0x70,
|
||||
0x72, 0x7e, 0xae, 0x1e, 0x9a, 0x99, 0x4e, 0x7c, 0x70, 0x13, 0x03, 0x40, 0x42, 0x01, 0x8c, 0x51,
|
||||
0xda, 0xe9, 0x99, 0x25, 0x19, 0xa5, 0x49, 0x7a, 0xc9, 0xf9, 0xb9, 0xfa, 0xe9, 0xf9, 0x39, 0x89,
|
||||
0x79, 0xe9, 0x08, 0x27, 0x16, 0x94, 0x54, 0x16, 0xa4, 0x16, 0x23, 0x5c, 0xfa, 0x83, 0x91, 0x71,
|
||||
0x11, 0x13, 0xb3, 0x7b, 0x80, 0xd3, 0x2a, 0x26, 0x39, 0x77, 0x88, 0xc9, 0x01, 0x50, 0xb5, 0x7a,
|
||||
0xe1, 0xa9, 0x39, 0x39, 0xde, 0x79, 0xf9, 0xe5, 0x79, 0x21, 0x20, 0x3d, 0x49, 0x6c, 0x60, 0x43,
|
||||
0x8c, 0x01, 0x01, 0x00, 0x00, 0xff, 0xff, 0xbc, 0x77, 0x4a, 0x07, 0xf7, 0x00, 0x00, 0x00,
|
||||
}
|
||||
|
|
@ -0,0 +1,27 @@
|
|||
Copyright 2016, Google Inc.
|
||||
All rights reserved.
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions are
|
||||
met:
|
||||
|
||||
* Redistributions of source code must retain the above copyright
|
||||
notice, this list of conditions and the following disclaimer.
|
||||
* Redistributions in binary form must reproduce the above
|
||||
copyright notice, this list of conditions and the following disclaimer
|
||||
in the documentation and/or other materials provided with the
|
||||
distribution.
|
||||
* Neither the name of Google Inc. nor the names of its
|
||||
contributors may be used to endorse or promote products derived from
|
||||
this software without specific prior written permission.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
|
@ -0,0 +1,157 @@
|
|||
// Copyright 2016, Google Inc.
|
||||
// All rights reserved.
|
||||
//
|
||||
// Redistribution and use in source and binary forms, with or without
|
||||
// modification, are permitted provided that the following conditions are
|
||||
// met:
|
||||
//
|
||||
// * Redistributions of source code must retain the above copyright
|
||||
// notice, this list of conditions and the following disclaimer.
|
||||
// * Redistributions in binary form must reproduce the above
|
||||
// copyright notice, this list of conditions and the following disclaimer
|
||||
// in the documentation and/or other materials provided with the
|
||||
// distribution.
|
||||
// * Neither the name of Google Inc. nor the names of its
|
||||
// contributors may be used to endorse or promote products derived from
|
||||
// this software without specific prior written permission.
|
||||
//
|
||||
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
package gax
|
||||
|
||||
import (
|
||||
"math/rand"
|
||||
"time"
|
||||
|
||||
"google.golang.org/grpc"
|
||||
"google.golang.org/grpc/codes"
|
||||
"google.golang.org/grpc/status"
|
||||
)
|
||||
|
||||
// CallOption is an option used by Invoke to control behaviors of RPC calls.
|
||||
// CallOption works by modifying relevant fields of CallSettings.
|
||||
type CallOption interface {
|
||||
// Resolve applies the option by modifying cs.
|
||||
Resolve(cs *CallSettings)
|
||||
}
|
||||
|
||||
// Retryer is used by Invoke to determine retry behavior.
|
||||
type Retryer interface {
|
||||
// Retry reports whether a request should be retriedand how long to pause before retrying
|
||||
// if the previous attempt returned with err. Invoke never calls Retry with nil error.
|
||||
Retry(err error) (pause time.Duration, shouldRetry bool)
|
||||
}
|
||||
|
||||
type retryerOption func() Retryer
|
||||
|
||||
func (o retryerOption) Resolve(s *CallSettings) {
|
||||
s.Retry = o
|
||||
}
|
||||
|
||||
// WithRetry sets CallSettings.Retry to fn.
|
||||
func WithRetry(fn func() Retryer) CallOption {
|
||||
return retryerOption(fn)
|
||||
}
|
||||
|
||||
// OnCodes returns a Retryer that retries if and only if
|
||||
// the previous attempt returns a GRPC error whose error code is stored in cc.
|
||||
// Pause times between retries are specified by bo.
|
||||
//
|
||||
// bo is only used for its parameters; each Retryer has its own copy.
|
||||
func OnCodes(cc []codes.Code, bo Backoff) Retryer {
|
||||
return &boRetryer{
|
||||
backoff: bo,
|
||||
codes: append([]codes.Code(nil), cc...),
|
||||
}
|
||||
}
|
||||
|
||||
type boRetryer struct {
|
||||
backoff Backoff
|
||||
codes []codes.Code
|
||||
}
|
||||
|
||||
func (r *boRetryer) Retry(err error) (time.Duration, bool) {
|
||||
st, ok := status.FromError(err)
|
||||
if !ok {
|
||||
return 0, false
|
||||
}
|
||||
c := st.Code()
|
||||
for _, rc := range r.codes {
|
||||
if c == rc {
|
||||
return r.backoff.Pause(), true
|
||||
}
|
||||
}
|
||||
return 0, false
|
||||
}
|
||||
|
||||
// Backoff implements exponential backoff.
|
||||
// The wait time between retries is a random value between 0 and the "retry envelope".
|
||||
// The envelope starts at Initial and increases by the factor of Multiplier every retry,
|
||||
// but is capped at Max.
|
||||
type Backoff struct {
|
||||
// Initial is the initial value of the retry envelope, defaults to 1 second.
|
||||
Initial time.Duration
|
||||
|
||||
// Max is the maximum value of the retry envelope, defaults to 30 seconds.
|
||||
Max time.Duration
|
||||
|
||||
// Multiplier is the factor by which the retry envelope increases.
|
||||
// It should be greater than 1 and defaults to 2.
|
||||
Multiplier float64
|
||||
|
||||
// cur is the current retry envelope
|
||||
cur time.Duration
|
||||
}
|
||||
|
||||
func (bo *Backoff) Pause() time.Duration {
|
||||
if bo.Initial == 0 {
|
||||
bo.Initial = time.Second
|
||||
}
|
||||
if bo.cur == 0 {
|
||||
bo.cur = bo.Initial
|
||||
}
|
||||
if bo.Max == 0 {
|
||||
bo.Max = 30 * time.Second
|
||||
}
|
||||
if bo.Multiplier < 1 {
|
||||
bo.Multiplier = 2
|
||||
}
|
||||
// Select a duration between zero and the current max. It might seem counterintuitive to
|
||||
// have so much jitter, but https://www.awsarchitectureblog.com/2015/03/backoff.html
|
||||
// argues that that is the best strategy.
|
||||
d := time.Duration(rand.Int63n(int64(bo.cur)))
|
||||
bo.cur = time.Duration(float64(bo.cur) * bo.Multiplier)
|
||||
if bo.cur > bo.Max {
|
||||
bo.cur = bo.Max
|
||||
}
|
||||
return d
|
||||
}
|
||||
|
||||
type grpcOpt []grpc.CallOption
|
||||
|
||||
func (o grpcOpt) Resolve(s *CallSettings) {
|
||||
s.GRPC = o
|
||||
}
|
||||
|
||||
func WithGRPCOptions(opt ...grpc.CallOption) CallOption {
|
||||
return grpcOpt(append([]grpc.CallOption(nil), opt...))
|
||||
}
|
||||
|
||||
type CallSettings struct {
|
||||
// Retry returns a Retryer to be used to control retry logic of a method call.
|
||||
// If Retry is nil or the returned Retryer is nil, the call will not be retried.
|
||||
Retry func() Retryer
|
||||
|
||||
// CallOptions to be forwarded to GRPC.
|
||||
GRPC []grpc.CallOption
|
||||
}
|
||||
|
|
@ -0,0 +1,40 @@
|
|||
// Copyright 2016, Google Inc.
|
||||
// All rights reserved.
|
||||
//
|
||||
// Redistribution and use in source and binary forms, with or without
|
||||
// modification, are permitted provided that the following conditions are
|
||||
// met:
|
||||
//
|
||||
// * Redistributions of source code must retain the above copyright
|
||||
// notice, this list of conditions and the following disclaimer.
|
||||
// * Redistributions in binary form must reproduce the above
|
||||
// copyright notice, this list of conditions and the following disclaimer
|
||||
// in the documentation and/or other materials provided with the
|
||||
// distribution.
|
||||
// * Neither the name of Google Inc. nor the names of its
|
||||
// contributors may be used to endorse or promote products derived from
|
||||
// this software without specific prior written permission.
|
||||
//
|
||||
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
// Package gax contains a set of modules which aid the development of APIs
|
||||
// for clients and servers based on gRPC and Google API conventions.
|
||||
//
|
||||
// Application code will rarely need to use this library directly.
|
||||
// However, code generated automatically from API definition files can use it
|
||||
// to simplify code generation and to provide more convenient and idiomatic API surfaces.
|
||||
//
|
||||
// This project is currently experimental and not supported.
|
||||
package gax
|
||||
|
||||
const Version = "0.1.0"
|
||||
|
|
@ -0,0 +1,24 @@
|
|||
package gax
|
||||
|
||||
import "bytes"
|
||||
|
||||
// XGoogHeader is for use by the Google Cloud Libraries only.
|
||||
//
|
||||
// XGoogHeader formats key-value pairs.
|
||||
// The resulting string is suitable for x-goog-api-client header.
|
||||
func XGoogHeader(keyval ...string) string {
|
||||
if len(keyval) == 0 {
|
||||
return ""
|
||||
}
|
||||
if len(keyval)%2 != 0 {
|
||||
panic("gax.Header: odd argument count")
|
||||
}
|
||||
var buf bytes.Buffer
|
||||
for i := 0; i < len(keyval); i += 2 {
|
||||
buf.WriteByte(' ')
|
||||
buf.WriteString(keyval[i])
|
||||
buf.WriteByte('/')
|
||||
buf.WriteString(keyval[i+1])
|
||||
}
|
||||
return buf.String()[1:]
|
||||
}
|
||||
|
|
@ -0,0 +1,90 @@
|
|||
// Copyright 2016, Google Inc.
|
||||
// All rights reserved.
|
||||
//
|
||||
// Redistribution and use in source and binary forms, with or without
|
||||
// modification, are permitted provided that the following conditions are
|
||||
// met:
|
||||
//
|
||||
// * Redistributions of source code must retain the above copyright
|
||||
// notice, this list of conditions and the following disclaimer.
|
||||
// * Redistributions in binary form must reproduce the above
|
||||
// copyright notice, this list of conditions and the following disclaimer
|
||||
// in the documentation and/or other materials provided with the
|
||||
// distribution.
|
||||
// * Neither the name of Google Inc. nor the names of its
|
||||
// contributors may be used to endorse or promote products derived from
|
||||
// this software without specific prior written permission.
|
||||
//
|
||||
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
package gax
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"golang.org/x/net/context"
|
||||
)
|
||||
|
||||
// A user defined call stub.
|
||||
type APICall func(context.Context, CallSettings) error
|
||||
|
||||
// Invoke calls the given APICall,
|
||||
// performing retries as specified by opts, if any.
|
||||
func Invoke(ctx context.Context, call APICall, opts ...CallOption) error {
|
||||
var settings CallSettings
|
||||
for _, opt := range opts {
|
||||
opt.Resolve(&settings)
|
||||
}
|
||||
return invoke(ctx, call, settings, Sleep)
|
||||
}
|
||||
|
||||
// Sleep is similar to time.Sleep, but it can be interrupted by ctx.Done() closing.
|
||||
// If interrupted, Sleep returns ctx.Err().
|
||||
func Sleep(ctx context.Context, d time.Duration) error {
|
||||
t := time.NewTimer(d)
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
t.Stop()
|
||||
return ctx.Err()
|
||||
case <-t.C:
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
type sleeper func(ctx context.Context, d time.Duration) error
|
||||
|
||||
// invoke implements Invoke, taking an additional sleeper argument for testing.
|
||||
func invoke(ctx context.Context, call APICall, settings CallSettings, sp sleeper) error {
|
||||
var retryer Retryer
|
||||
for {
|
||||
err := call(ctx, settings)
|
||||
if err == nil {
|
||||
return nil
|
||||
}
|
||||
if settings.Retry == nil {
|
||||
return err
|
||||
}
|
||||
if retryer == nil {
|
||||
if r := settings.Retry(); r != nil {
|
||||
retryer = r
|
||||
} else {
|
||||
return err
|
||||
}
|
||||
}
|
||||
if d, ok := retryer.Retry(err); !ok {
|
||||
return err
|
||||
} else if err = sp(ctx, d); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1 @@
|
|||
Google Inc.
|
||||
|
|
@ -0,0 +1,202 @@
|
|||
|
||||
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.
|
||||
|
|
@ -0,0 +1,94 @@
|
|||
// Copyright 2018, OpenCensus Authors
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
// Package propagation implement X-Cloud-Trace-Context header propagation used
|
||||
// by Google Cloud products.
|
||||
package propagation // import "go.opencensus.io/exporter/stackdriver/propagation"
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
"encoding/hex"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"go.opencensus.io/trace"
|
||||
"go.opencensus.io/trace/propagation"
|
||||
)
|
||||
|
||||
const (
|
||||
httpHeaderMaxSize = 200
|
||||
httpHeader = `X-Cloud-Trace-Context`
|
||||
)
|
||||
|
||||
var _ propagation.HTTPFormat = (*HTTPFormat)(nil)
|
||||
|
||||
// HTTPFormat implements propagation.HTTPFormat to propagate
|
||||
// traces in HTTP headers for Google Cloud Platform and Stackdriver Trace.
|
||||
type HTTPFormat struct{}
|
||||
|
||||
// SpanContextFromRequest extracts a Stackdriver Trace span context from incoming requests.
|
||||
func (f *HTTPFormat) SpanContextFromRequest(req *http.Request) (sc trace.SpanContext, ok bool) {
|
||||
h := req.Header.Get(httpHeader)
|
||||
// See https://cloud.google.com/trace/docs/faq for the header HTTPFormat.
|
||||
// Return if the header is empty or missing, or if the header is unreasonably
|
||||
// large, to avoid making unnecessary copies of a large string.
|
||||
if h == "" || len(h) > httpHeaderMaxSize {
|
||||
return trace.SpanContext{}, false
|
||||
}
|
||||
|
||||
// Parse the trace id field.
|
||||
slash := strings.Index(h, `/`)
|
||||
if slash == -1 {
|
||||
return trace.SpanContext{}, false
|
||||
}
|
||||
tid, h := h[:slash], h[slash+1:]
|
||||
|
||||
buf, err := hex.DecodeString(tid)
|
||||
if err != nil {
|
||||
return trace.SpanContext{}, false
|
||||
}
|
||||
copy(sc.TraceID[:], buf)
|
||||
|
||||
// Parse the span id field.
|
||||
spanstr := h
|
||||
semicolon := strings.Index(h, `;`)
|
||||
if semicolon != -1 {
|
||||
spanstr, h = h[:semicolon], h[semicolon+1:]
|
||||
}
|
||||
sid, err := strconv.ParseUint(spanstr, 10, 64)
|
||||
if err != nil {
|
||||
return trace.SpanContext{}, false
|
||||
}
|
||||
binary.BigEndian.PutUint64(sc.SpanID[:], sid)
|
||||
|
||||
// Parse the options field, options field is optional.
|
||||
if !strings.HasPrefix(h, "o=") {
|
||||
return sc, true
|
||||
}
|
||||
o, err := strconv.ParseUint(h[2:], 10, 64)
|
||||
if err != nil {
|
||||
return trace.SpanContext{}, false
|
||||
}
|
||||
sc.TraceOptions = trace.TraceOptions(o)
|
||||
return sc, true
|
||||
}
|
||||
|
||||
// SpanContextToRequest modifies the given request to include a Stackdriver Trace header.
|
||||
func (f *HTTPFormat) SpanContextToRequest(sc trace.SpanContext, req *http.Request) {
|
||||
sid := binary.BigEndian.Uint64(sc.SpanID[:])
|
||||
header := fmt.Sprintf("%s/%d;o=%d", hex.EncodeToString(sc.TraceID[:]), sid, int64(sc.TraceOptions))
|
||||
req.Header.Set(httpHeader, header)
|
||||
}
|
||||
|
|
@ -0,0 +1,32 @@
|
|||
// Copyright 2017, OpenCensus Authors
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package internal
|
||||
|
||||
import "time"
|
||||
|
||||
// UserAgent is the user agent to be added to the outgoing
|
||||
// requests from the exporters.
|
||||
const UserAgent = "opencensus-go-v0.4.0"
|
||||
|
||||
// MonotonicEndTime returns the end time at present
|
||||
// but offset from start, monotonically.
|
||||
//
|
||||
// The monotonic clock is used in subtractions hence
|
||||
// the duration since start added back to start gives
|
||||
// end as a monotonic time.
|
||||
// See https://golang.org/pkg/time/#hdr-Monotonic_Clocks
|
||||
func MonotonicEndTime(start time.Time) time.Time {
|
||||
return start.Add(time.Now().Sub(start))
|
||||
}
|
||||
|
|
@ -0,0 +1,50 @@
|
|||
// Copyright 2017, OpenCensus Authors
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package internal
|
||||
|
||||
import (
|
||||
"strings"
|
||||
"unicode"
|
||||
)
|
||||
|
||||
const labelKeySizeLimit = 100
|
||||
|
||||
// Sanitize returns a string that is trunacated to 100 characters if it's too
|
||||
// long, and replaces non-alphanumeric characters to underscores.
|
||||
func Sanitize(s string) string {
|
||||
if len(s) == 0 {
|
||||
return s
|
||||
}
|
||||
if len(s) > labelKeySizeLimit {
|
||||
s = s[:labelKeySizeLimit]
|
||||
}
|
||||
s = strings.Map(sanitizeRune, s)
|
||||
if unicode.IsDigit(rune(s[0])) {
|
||||
s = "key_" + s
|
||||
}
|
||||
if s[0] == '_' {
|
||||
s = "key" + s
|
||||
}
|
||||
return s
|
||||
}
|
||||
|
||||
// converts anything that is not a letter or digit to an underscore
|
||||
func sanitizeRune(r rune) rune {
|
||||
if unicode.IsLetter(r) || unicode.IsDigit(r) {
|
||||
return r
|
||||
}
|
||||
// Everything else turns into an underscore
|
||||
return '_'
|
||||
}
|
||||
|
|
@ -0,0 +1,72 @@
|
|||
// Copyright 2017, OpenCensus Authors
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
//
|
||||
|
||||
// Package tagencoding contains the tag encoding
|
||||
// used interally by the stats collector.
|
||||
package tagencoding
|
||||
|
||||
type Values struct {
|
||||
Buffer []byte
|
||||
WriteIndex int
|
||||
ReadIndex int
|
||||
}
|
||||
|
||||
func (vb *Values) growIfRequired(expected int) {
|
||||
if len(vb.Buffer)-vb.WriteIndex < expected {
|
||||
tmp := make([]byte, 2*(len(vb.Buffer)+1)+expected)
|
||||
copy(tmp, vb.Buffer)
|
||||
vb.Buffer = tmp
|
||||
}
|
||||
}
|
||||
|
||||
func (vb *Values) WriteValue(v []byte) {
|
||||
length := len(v) & 0xff
|
||||
vb.growIfRequired(1 + length)
|
||||
|
||||
// writing length of v
|
||||
vb.Buffer[vb.WriteIndex] = byte(length)
|
||||
vb.WriteIndex++
|
||||
|
||||
if length == 0 {
|
||||
// No value was encoded for this key
|
||||
return
|
||||
}
|
||||
|
||||
// writing v
|
||||
copy(vb.Buffer[vb.WriteIndex:], v[:length])
|
||||
vb.WriteIndex += length
|
||||
}
|
||||
|
||||
// ReadValue is the helper method to read the values when decoding valuesBytes to a map[Key][]byte.
|
||||
func (vb *Values) ReadValue() []byte {
|
||||
// read length of v
|
||||
length := int(vb.Buffer[vb.ReadIndex])
|
||||
vb.ReadIndex++
|
||||
if length == 0 {
|
||||
// No value was encoded for this key
|
||||
return nil
|
||||
}
|
||||
|
||||
// read value of v
|
||||
v := make([]byte, length)
|
||||
endIdx := vb.ReadIndex + length
|
||||
copy(v, vb.Buffer[vb.ReadIndex:endIdx])
|
||||
vb.ReadIndex = endIdx
|
||||
return v
|
||||
}
|
||||
|
||||
func (vb *Values) Bytes() []byte {
|
||||
return vb.Buffer[:vb.WriteIndex]
|
||||
}
|
||||
|
|
@ -0,0 +1,52 @@
|
|||
// Copyright 2017, OpenCensus Authors
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package internal
|
||||
|
||||
import (
|
||||
"time"
|
||||
)
|
||||
|
||||
// Trace allows internal access to some trace functionality.
|
||||
// TODO(#412): remove this
|
||||
var Trace interface{}
|
||||
|
||||
var LocalSpanStoreEnabled bool
|
||||
|
||||
// BucketConfiguration stores the number of samples to store for span buckets
|
||||
// for successful and failed spans for a particular span name.
|
||||
type BucketConfiguration struct {
|
||||
Name string
|
||||
MaxRequestsSucceeded int
|
||||
MaxRequestsErrors int
|
||||
}
|
||||
|
||||
// PerMethodSummary is a summary of the spans stored for a single span name.
|
||||
type PerMethodSummary struct {
|
||||
Active int
|
||||
LatencyBuckets []LatencyBucketSummary
|
||||
ErrorBuckets []ErrorBucketSummary
|
||||
}
|
||||
|
||||
// LatencyBucketSummary is a summary of a latency bucket.
|
||||
type LatencyBucketSummary struct {
|
||||
MinLatency, MaxLatency time.Duration
|
||||
Size int
|
||||
}
|
||||
|
||||
// ErrorBucketSummary is a summary of an error bucket.
|
||||
type ErrorBucketSummary struct {
|
||||
ErrorCode int32
|
||||
Size int
|
||||
}
|
||||
|
|
@ -0,0 +1,84 @@
|
|||
// Copyright 2018, OpenCensus Authors
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package ochttp
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"go.opencensus.io/trace"
|
||||
"go.opencensus.io/trace/propagation"
|
||||
)
|
||||
|
||||
// Transport is an http.RoundTripper that instruments all outgoing requests with
|
||||
// stats and tracing. The zero value is intended to be a useful default, but for
|
||||
// now it's recommended that you explicitly set Propagation.
|
||||
type Transport struct {
|
||||
// Base may be set to wrap another http.RoundTripper that does the actual
|
||||
// requests. By default http.DefaultTransport is used.
|
||||
//
|
||||
// If base HTTP roundtripper implements CancelRequest,
|
||||
// the returned round tripper will be cancelable.
|
||||
Base http.RoundTripper
|
||||
|
||||
// Propagation defines how traces are propagated. If unspecified, a default
|
||||
// (currently B3 format) will be used.
|
||||
Propagation propagation.HTTPFormat
|
||||
|
||||
// StartOptions are applied to the span started by this Transport around each
|
||||
// request.
|
||||
//
|
||||
// StartOptions.SpanKind will always be set to trace.SpanKindClient
|
||||
// for spans started by this transport.
|
||||
StartOptions trace.StartOptions
|
||||
|
||||
// TODO: Implement tag propagation for HTTP.
|
||||
}
|
||||
|
||||
// RoundTrip implements http.RoundTripper, delegating to Base and recording stats and traces for the request.
|
||||
func (t *Transport) RoundTrip(req *http.Request) (*http.Response, error) {
|
||||
rt := t.base()
|
||||
// TODO: remove excessive nesting of http.RoundTrippers here.
|
||||
format := t.Propagation
|
||||
if format == nil {
|
||||
format = defaultFormat
|
||||
}
|
||||
rt = &traceTransport{
|
||||
base: rt,
|
||||
format: format,
|
||||
startOptions: trace.StartOptions{
|
||||
Sampler: t.StartOptions.Sampler,
|
||||
SpanKind: trace.SpanKindClient,
|
||||
},
|
||||
}
|
||||
rt = statsTransport{base: rt}
|
||||
return rt.RoundTrip(req)
|
||||
}
|
||||
|
||||
func (t *Transport) base() http.RoundTripper {
|
||||
if t.Base != nil {
|
||||
return t.Base
|
||||
}
|
||||
return http.DefaultTransport
|
||||
}
|
||||
|
||||
// CancelRequest cancels an in-flight request by closing its connection.
|
||||
func (t *Transport) CancelRequest(req *http.Request) {
|
||||
type canceler interface {
|
||||
CancelRequest(*http.Request)
|
||||
}
|
||||
if cr, ok := t.base().(canceler); ok {
|
||||
cr.CancelRequest(req)
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,125 @@
|
|||
// Copyright 2018, OpenCensus Authors
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package ochttp
|
||||
|
||||
import (
|
||||
"context"
|
||||
"io"
|
||||
"net/http"
|
||||
"strconv"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"go.opencensus.io/stats"
|
||||
"go.opencensus.io/tag"
|
||||
)
|
||||
|
||||
// statsTransport is an http.RoundTripper that collects stats for the outgoing requests.
|
||||
type statsTransport struct {
|
||||
base http.RoundTripper
|
||||
}
|
||||
|
||||
// RoundTrip implements http.RoundTripper, delegating to Base and recording stats for the request.
|
||||
func (t statsTransport) RoundTrip(req *http.Request) (*http.Response, error) {
|
||||
ctx, _ := tag.New(req.Context(),
|
||||
tag.Upsert(Host, req.URL.Host),
|
||||
tag.Upsert(Path, req.URL.Path),
|
||||
tag.Upsert(Method, req.Method))
|
||||
req = req.WithContext(ctx)
|
||||
track := &tracker{
|
||||
start: time.Now(),
|
||||
ctx: ctx,
|
||||
}
|
||||
if req.Body == nil {
|
||||
// TODO: Handle cases where ContentLength is not set.
|
||||
track.reqSize = -1
|
||||
} else if req.ContentLength > 0 {
|
||||
track.reqSize = req.ContentLength
|
||||
}
|
||||
stats.Record(ctx, ClientRequestCount.M(1))
|
||||
|
||||
// Perform request.
|
||||
resp, err := t.base.RoundTrip(req)
|
||||
|
||||
if err != nil {
|
||||
track.statusCode = http.StatusInternalServerError
|
||||
track.end()
|
||||
} else {
|
||||
track.statusCode = resp.StatusCode
|
||||
if resp.Body == nil {
|
||||
track.end()
|
||||
} else {
|
||||
track.body = resp.Body
|
||||
resp.Body = track
|
||||
}
|
||||
}
|
||||
return resp, err
|
||||
}
|
||||
|
||||
// CancelRequest cancels an in-flight request by closing its connection.
|
||||
func (t statsTransport) CancelRequest(req *http.Request) {
|
||||
type canceler interface {
|
||||
CancelRequest(*http.Request)
|
||||
}
|
||||
if cr, ok := t.base.(canceler); ok {
|
||||
cr.CancelRequest(req)
|
||||
}
|
||||
}
|
||||
|
||||
type tracker struct {
|
||||
ctx context.Context
|
||||
respSize int64
|
||||
reqSize int64
|
||||
start time.Time
|
||||
body io.ReadCloser
|
||||
statusCode int
|
||||
endOnce sync.Once
|
||||
}
|
||||
|
||||
var _ io.ReadCloser = (*tracker)(nil)
|
||||
|
||||
func (t *tracker) end() {
|
||||
t.endOnce.Do(func() {
|
||||
m := []stats.Measurement{
|
||||
ClientLatency.M(float64(time.Since(t.start)) / float64(time.Millisecond)),
|
||||
ClientResponseBytes.M(t.respSize),
|
||||
}
|
||||
if t.reqSize >= 0 {
|
||||
m = append(m, ClientRequestBytes.M(t.reqSize))
|
||||
}
|
||||
ctx, _ := tag.New(t.ctx, tag.Upsert(StatusCode, strconv.Itoa(t.statusCode)))
|
||||
stats.Record(ctx, m...)
|
||||
})
|
||||
}
|
||||
|
||||
func (t *tracker) Read(b []byte) (int, error) {
|
||||
n, err := t.body.Read(b)
|
||||
switch err {
|
||||
case nil:
|
||||
t.respSize += int64(n)
|
||||
return n, nil
|
||||
case io.EOF:
|
||||
t.end()
|
||||
}
|
||||
return n, err
|
||||
}
|
||||
|
||||
func (t *tracker) Close() error {
|
||||
// Invoking endSpan on Close will help catch the cases
|
||||
// in which a read returned a non-nil error, we set the
|
||||
// span status but didn't end the span.
|
||||
t.end()
|
||||
return t.body.Close()
|
||||
}
|
||||
|
|
@ -0,0 +1,19 @@
|
|||
// Copyright 2018, OpenCensus Authors
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
// Package ochttp provides OpenCensus instrumentation for net/http package.
|
||||
//
|
||||
// For server instrumentation, see Handler. For client-side instrumentation,
|
||||
// see Transport.
|
||||
package ochttp // import "go.opencensus.io/plugin/ochttp"
|
||||
|
|
@ -0,0 +1,119 @@
|
|||
// Copyright 2018, OpenCensus Authors
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
// Package b3 contains a propagation.HTTPFormat implementation
|
||||
// for B3 propagation. See https://github.com/openzipkin/b3-propagation
|
||||
// for more details.
|
||||
package b3
|
||||
|
||||
import (
|
||||
"encoding/hex"
|
||||
"net/http"
|
||||
|
||||
"go.opencensus.io/trace"
|
||||
"go.opencensus.io/trace/propagation"
|
||||
)
|
||||
|
||||
const (
|
||||
traceIDHeader = "X-B3-TraceId"
|
||||
spanIDHeader = "X-B3-SpanId"
|
||||
sampledHeader = "X-B3-Sampled"
|
||||
)
|
||||
|
||||
// HTTPFormat implements propagation.HTTPFormat to propagate
|
||||
// traces in HTTP headers in B3 propagation format.
|
||||
// HTTPFormat skips the X-B3-ParentId and X-B3-Flags headers
|
||||
// because there are additional fields not represented in the
|
||||
// OpenCensus span context. Spans created from the incoming
|
||||
// header will be the direct children of the client-side span.
|
||||
// Similarly, reciever of the outgoing spans should use client-side
|
||||
// span created by OpenCensus as the parent.
|
||||
type HTTPFormat struct{}
|
||||
|
||||
var _ propagation.HTTPFormat = (*HTTPFormat)(nil)
|
||||
|
||||
// SpanContextFromRequest extracts a B3 span context from incoming requests.
|
||||
func (f *HTTPFormat) SpanContextFromRequest(req *http.Request) (sc trace.SpanContext, ok bool) {
|
||||
tid, ok := parseTraceID(req.Header.Get(traceIDHeader))
|
||||
if !ok {
|
||||
return trace.SpanContext{}, false
|
||||
}
|
||||
sid, ok := parseSpanID(req.Header.Get(spanIDHeader))
|
||||
if !ok {
|
||||
return trace.SpanContext{}, false
|
||||
}
|
||||
sampled, _ := parseSampled(req.Header.Get(sampledHeader))
|
||||
return trace.SpanContext{
|
||||
TraceID: tid,
|
||||
SpanID: sid,
|
||||
TraceOptions: sampled,
|
||||
}, true
|
||||
}
|
||||
|
||||
func parseTraceID(tid string) (trace.TraceID, bool) {
|
||||
if tid == "" {
|
||||
return trace.TraceID{}, false
|
||||
}
|
||||
b, err := hex.DecodeString(tid)
|
||||
if err != nil {
|
||||
return trace.TraceID{}, false
|
||||
}
|
||||
var traceID trace.TraceID
|
||||
if len(b) <= 8 {
|
||||
// The lower 64-bits.
|
||||
start := 8 + (8 - len(b))
|
||||
copy(traceID[start:], b)
|
||||
} else {
|
||||
start := 16 - len(b)
|
||||
copy(traceID[start:], b)
|
||||
}
|
||||
|
||||
return traceID, true
|
||||
}
|
||||
|
||||
func parseSpanID(sid string) (spanID trace.SpanID, ok bool) {
|
||||
if sid == "" {
|
||||
return trace.SpanID{}, false
|
||||
}
|
||||
b, err := hex.DecodeString(sid)
|
||||
if err != nil {
|
||||
return trace.SpanID{}, false
|
||||
}
|
||||
start := (8 - len(b))
|
||||
copy(spanID[start:], b)
|
||||
return spanID, true
|
||||
}
|
||||
|
||||
func parseSampled(sampled string) (trace.TraceOptions, bool) {
|
||||
switch sampled {
|
||||
case "true", "1":
|
||||
return trace.TraceOptions(1), true
|
||||
default:
|
||||
return trace.TraceOptions(0), false
|
||||
}
|
||||
}
|
||||
|
||||
// SpanContextToRequest modifies the given request to include B3 headers.
|
||||
func (f *HTTPFormat) SpanContextToRequest(sc trace.SpanContext, req *http.Request) {
|
||||
req.Header.Set(traceIDHeader, hex.EncodeToString(sc.TraceID[:]))
|
||||
req.Header.Set(spanIDHeader, hex.EncodeToString(sc.SpanID[:]))
|
||||
|
||||
var sampled string
|
||||
if sc.IsSampled() {
|
||||
sampled = "1"
|
||||
} else {
|
||||
sampled = "0"
|
||||
}
|
||||
req.Header.Set(sampledHeader, sampled)
|
||||
}
|
||||
|
|
@ -0,0 +1,180 @@
|
|||
// Copyright 2018, OpenCensus Authors
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package ochttp
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net/http"
|
||||
"strconv"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"go.opencensus.io/stats"
|
||||
"go.opencensus.io/tag"
|
||||
"go.opencensus.io/trace"
|
||||
"go.opencensus.io/trace/propagation"
|
||||
)
|
||||
|
||||
// Handler is a http.Handler that is aware of the incoming request's span.
|
||||
//
|
||||
// The extracted span can be accessed from the incoming request's
|
||||
// context.
|
||||
//
|
||||
// span := trace.FromContext(r.Context())
|
||||
//
|
||||
// The server span will be automatically ended at the end of ServeHTTP.
|
||||
//
|
||||
// Incoming propagation mechanism is determined by the given HTTP propagators.
|
||||
type Handler struct {
|
||||
// Propagation defines how traces are propagated. If unspecified,
|
||||
// B3 propagation will be used.
|
||||
Propagation propagation.HTTPFormat
|
||||
|
||||
// Handler is the handler used to handle the incoming request.
|
||||
Handler http.Handler
|
||||
|
||||
// StartOptions are applied to the span started by this Handler around each
|
||||
// request.
|
||||
//
|
||||
// StartOptions.SpanKind will always be set to trace.SpanKindServer
|
||||
// for spans started by this transport.
|
||||
StartOptions trace.StartOptions
|
||||
|
||||
// IsPublicEndpoint should be set to true for publicly accessible HTTP(S)
|
||||
// servers. If true, any trace metadata set on the incoming request will
|
||||
// be added as a linked trace instead of being added as a parent of the
|
||||
// current trace.
|
||||
IsPublicEndpoint bool
|
||||
}
|
||||
|
||||
func (h *Handler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||
var traceEnd, statsEnd func()
|
||||
r, traceEnd = h.startTrace(w, r)
|
||||
defer traceEnd()
|
||||
w, statsEnd = h.startStats(w, r)
|
||||
defer statsEnd()
|
||||
|
||||
handler := h.Handler
|
||||
if handler == nil {
|
||||
handler = http.DefaultServeMux
|
||||
}
|
||||
handler.ServeHTTP(w, r)
|
||||
}
|
||||
|
||||
func (h *Handler) startTrace(w http.ResponseWriter, r *http.Request) (*http.Request, func()) {
|
||||
opts := trace.StartOptions{
|
||||
Sampler: h.StartOptions.Sampler,
|
||||
SpanKind: trace.SpanKindServer,
|
||||
}
|
||||
|
||||
name := spanNameFromURL(r.URL)
|
||||
ctx := r.Context()
|
||||
var span *trace.Span
|
||||
sc, ok := h.extractSpanContext(r)
|
||||
if ok && !h.IsPublicEndpoint {
|
||||
span = trace.NewSpanWithRemoteParent(name, sc, opts)
|
||||
ctx = trace.WithSpan(ctx, span)
|
||||
} else {
|
||||
span = trace.NewSpan(name, nil, opts)
|
||||
if ok {
|
||||
span.AddLink(trace.Link{
|
||||
TraceID: sc.TraceID,
|
||||
SpanID: sc.SpanID,
|
||||
Type: trace.LinkTypeChild,
|
||||
Attributes: nil,
|
||||
})
|
||||
}
|
||||
}
|
||||
ctx = trace.WithSpan(ctx, span)
|
||||
span.AddAttributes(requestAttrs(r)...)
|
||||
return r.WithContext(trace.WithSpan(r.Context(), span)), span.End
|
||||
}
|
||||
|
||||
func (h *Handler) extractSpanContext(r *http.Request) (trace.SpanContext, bool) {
|
||||
if h.Propagation == nil {
|
||||
return defaultFormat.SpanContextFromRequest(r)
|
||||
}
|
||||
return h.Propagation.SpanContextFromRequest(r)
|
||||
}
|
||||
|
||||
func (h *Handler) startStats(w http.ResponseWriter, r *http.Request) (http.ResponseWriter, func()) {
|
||||
ctx, _ := tag.New(r.Context(),
|
||||
tag.Upsert(Host, r.URL.Host),
|
||||
tag.Upsert(Path, r.URL.Path),
|
||||
tag.Upsert(Method, r.Method))
|
||||
track := &trackingResponseWriter{
|
||||
start: time.Now(),
|
||||
ctx: ctx,
|
||||
writer: w,
|
||||
}
|
||||
if r.Body == nil {
|
||||
// TODO: Handle cases where ContentLength is not set.
|
||||
track.reqSize = -1
|
||||
} else if r.ContentLength > 0 {
|
||||
track.reqSize = r.ContentLength
|
||||
}
|
||||
stats.Record(ctx, ServerRequestCount.M(1))
|
||||
return track, track.end
|
||||
}
|
||||
|
||||
type trackingResponseWriter struct {
|
||||
ctx context.Context
|
||||
reqSize int64
|
||||
respSize int64
|
||||
start time.Time
|
||||
statusCode int
|
||||
endOnce sync.Once
|
||||
writer http.ResponseWriter
|
||||
}
|
||||
|
||||
var _ http.ResponseWriter = (*trackingResponseWriter)(nil)
|
||||
|
||||
func (t *trackingResponseWriter) end() {
|
||||
t.endOnce.Do(func() {
|
||||
if t.statusCode == 0 {
|
||||
t.statusCode = 200
|
||||
}
|
||||
m := []stats.Measurement{
|
||||
ServerLatency.M(float64(time.Since(t.start)) / float64(time.Millisecond)),
|
||||
ServerResponseBytes.M(t.respSize),
|
||||
}
|
||||
if t.reqSize >= 0 {
|
||||
m = append(m, ServerRequestBytes.M(t.reqSize))
|
||||
}
|
||||
ctx, _ := tag.New(t.ctx, tag.Upsert(StatusCode, strconv.Itoa(t.statusCode)))
|
||||
stats.Record(ctx, m...)
|
||||
})
|
||||
}
|
||||
|
||||
func (t *trackingResponseWriter) Header() http.Header {
|
||||
return t.writer.Header()
|
||||
}
|
||||
|
||||
func (t *trackingResponseWriter) Write(data []byte) (int, error) {
|
||||
n, err := t.writer.Write(data)
|
||||
t.respSize += int64(n)
|
||||
return n, err
|
||||
}
|
||||
|
||||
func (t *trackingResponseWriter) WriteHeader(statusCode int) {
|
||||
t.writer.WriteHeader(statusCode)
|
||||
t.statusCode = statusCode
|
||||
}
|
||||
|
||||
func (t *trackingResponseWriter) Flush() {
|
||||
if flusher, ok := t.writer.(http.Flusher); ok {
|
||||
flusher.Flush()
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,173 @@
|
|||
// Copyright 2018, OpenCensus Authors
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package ochttp
|
||||
|
||||
import (
|
||||
"go.opencensus.io/stats"
|
||||
"go.opencensus.io/stats/view"
|
||||
"go.opencensus.io/tag"
|
||||
)
|
||||
|
||||
// The following client HTTP measures are supported for use in custom views.
|
||||
var (
|
||||
ClientRequestCount, _ = stats.Int64("opencensus.io/http/client/request_count", "Number of HTTP requests started", stats.UnitNone)
|
||||
ClientRequestBytes, _ = stats.Int64("opencensus.io/http/client/request_bytes", "HTTP request body size if set as ContentLength (uncompressed)", stats.UnitBytes)
|
||||
ClientResponseBytes, _ = stats.Int64("opencensus.io/http/client/response_bytes", "HTTP response body size (uncompressed)", stats.UnitBytes)
|
||||
ClientLatency, _ = stats.Float64("opencensus.io/http/client/latency", "End-to-end latency", stats.UnitMilliseconds)
|
||||
)
|
||||
|
||||
// The following server HTTP measures are supported for use in custom views:
|
||||
var (
|
||||
ServerRequestCount, _ = stats.Int64("opencensus.io/http/server/request_count", "Number of HTTP requests started", stats.UnitNone)
|
||||
ServerRequestBytes, _ = stats.Int64("opencensus.io/http/server/request_bytes", "HTTP request body size if set as ContentLength (uncompressed)", stats.UnitBytes)
|
||||
ServerResponseBytes, _ = stats.Int64("opencensus.io/http/server/response_bytes", "HTTP response body size (uncompressed)", stats.UnitBytes)
|
||||
ServerLatency, _ = stats.Float64("opencensus.io/http/server/latency", "End-to-end latency", stats.UnitMilliseconds)
|
||||
)
|
||||
|
||||
// The following tags are applied to stats recorded by this package. Host, Path
|
||||
// and Method are applied to all measures. StatusCode is not applied to
|
||||
// ClientRequestCount or ServerRequestCount, since it is recorded before the status is known.
|
||||
var (
|
||||
// Host is the value of the HTTP Host header.
|
||||
Host, _ = tag.NewKey("http.host")
|
||||
|
||||
// StatusCode is the numeric HTTP response status code,
|
||||
// or "error" if a transport error occurred and no status code was read.
|
||||
StatusCode, _ = tag.NewKey("http.status")
|
||||
|
||||
// Path is the URL path (not including query string) in the request.
|
||||
Path, _ = tag.NewKey("http.path")
|
||||
|
||||
// Method is the HTTP method of the request, capitalized (GET, POST, etc.).
|
||||
Method, _ = tag.NewKey("http.method")
|
||||
)
|
||||
|
||||
// Default distributions used by views in this package.
|
||||
var (
|
||||
DefaultSizeDistribution = view.Distribution(0, 1024, 2048, 4096, 16384, 65536, 262144, 1048576, 4194304, 16777216, 67108864, 268435456, 1073741824, 4294967296)
|
||||
DefaultLatencyDistribution = view.Distribution(0, 1, 2, 3, 4, 5, 6, 8, 10, 13, 16, 20, 25, 30, 40, 50, 65, 80, 100, 130, 160, 200, 250, 300, 400, 500, 650, 800, 1000, 2000, 5000, 10000, 20000, 50000, 100000)
|
||||
)
|
||||
|
||||
// Package ochttp provides some convenience views.
|
||||
// You need to subscribe to the views for data to actually be collected.
|
||||
var (
|
||||
ClientRequestCountView = &view.View{
|
||||
Name: "opencensus.io/http/client/request_count",
|
||||
Description: "Count of HTTP requests started",
|
||||
Measure: ClientRequestCount,
|
||||
Aggregation: view.Count(),
|
||||
}
|
||||
|
||||
ClientRequestBytesView = &view.View{
|
||||
Name: "opencensus.io/http/client/request_bytes",
|
||||
Description: "Size distribution of HTTP request body",
|
||||
Measure: ClientRequestBytes,
|
||||
Aggregation: DefaultSizeDistribution,
|
||||
}
|
||||
|
||||
ClientResponseBytesView = &view.View{
|
||||
Name: "opencensus.io/http/client/response_bytes",
|
||||
Description: "Size distribution of HTTP response body",
|
||||
Measure: ClientResponseBytes,
|
||||
Aggregation: DefaultSizeDistribution,
|
||||
}
|
||||
|
||||
ClientLatencyView = &view.View{
|
||||
Name: "opencensus.io/http/client/latency",
|
||||
Description: "Latency distribution of HTTP requests",
|
||||
Measure: ClientLatency,
|
||||
Aggregation: DefaultLatencyDistribution,
|
||||
}
|
||||
|
||||
ClientRequestCountByMethod = &view.View{
|
||||
Name: "opencensus.io/http/client/request_count_by_method",
|
||||
Description: "Client request count by HTTP method",
|
||||
TagKeys: []tag.Key{Method},
|
||||
Measure: ClientRequestCount,
|
||||
Aggregation: view.Count(),
|
||||
}
|
||||
|
||||
ClientResponseCountByStatusCode = &view.View{
|
||||
Name: "opencensus.io/http/client/response_count_by_status_code",
|
||||
Description: "Client response count by status code",
|
||||
TagKeys: []tag.Key{StatusCode},
|
||||
Measure: ClientLatency,
|
||||
Aggregation: view.Count(),
|
||||
}
|
||||
|
||||
ServerRequestCountView = &view.View{
|
||||
Name: "opencensus.io/http/server/request_count",
|
||||
Description: "Count of HTTP requests started",
|
||||
Measure: ServerRequestCount,
|
||||
Aggregation: view.Count(),
|
||||
}
|
||||
|
||||
ServerRequestBytesView = &view.View{
|
||||
Name: "opencensus.io/http/server/request_bytes",
|
||||
Description: "Size distribution of HTTP request body",
|
||||
Measure: ServerRequestBytes,
|
||||
Aggregation: DefaultSizeDistribution,
|
||||
}
|
||||
|
||||
ServerResponseBytesView = &view.View{
|
||||
Name: "opencensus.io/http/server/response_bytes",
|
||||
Description: "Size distribution of HTTP response body",
|
||||
Measure: ServerResponseBytes,
|
||||
Aggregation: DefaultSizeDistribution,
|
||||
}
|
||||
|
||||
ServerLatencyView = &view.View{
|
||||
Name: "opencensus.io/http/server/latency",
|
||||
Description: "Latency distribution of HTTP requests",
|
||||
Measure: ServerLatency,
|
||||
Aggregation: DefaultLatencyDistribution,
|
||||
}
|
||||
|
||||
ServerRequestCountByMethod = &view.View{
|
||||
Name: "opencensus.io/http/server/request_count_by_method",
|
||||
Description: "Server request count by HTTP method",
|
||||
TagKeys: []tag.Key{Method},
|
||||
Measure: ServerRequestCount,
|
||||
Aggregation: view.Count(),
|
||||
}
|
||||
|
||||
ServerResponseCountByStatusCode = &view.View{
|
||||
Name: "opencensus.io/http/server/response_count_by_status_code",
|
||||
Description: "Server response count by status code",
|
||||
TagKeys: []tag.Key{StatusCode},
|
||||
Measure: ServerLatency,
|
||||
Aggregation: view.Count(),
|
||||
}
|
||||
)
|
||||
|
||||
// DefaultClientViews are the default client views provided by this package.
|
||||
var DefaultClientViews = []*view.View{
|
||||
ClientRequestCountView,
|
||||
ClientRequestBytesView,
|
||||
ClientResponseBytesView,
|
||||
ClientLatencyView,
|
||||
ClientRequestCountByMethod,
|
||||
ClientResponseCountByStatusCode,
|
||||
}
|
||||
|
||||
// DefaultServerViews are the default server views provided by this package.
|
||||
var DefaultServerViews = []*view.View{
|
||||
ServerRequestCountView,
|
||||
ServerRequestBytesView,
|
||||
ServerResponseBytesView,
|
||||
ServerLatencyView,
|
||||
ServerRequestCountByMethod,
|
||||
ServerResponseCountByStatusCode,
|
||||
}
|
||||
|
|
@ -0,0 +1,215 @@
|
|||
// Copyright 2018, OpenCensus Authors
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package ochttp
|
||||
|
||||
import (
|
||||
"io"
|
||||
"net/http"
|
||||
"net/url"
|
||||
|
||||
"go.opencensus.io/plugin/ochttp/propagation/b3"
|
||||
"go.opencensus.io/trace"
|
||||
"go.opencensus.io/trace/propagation"
|
||||
)
|
||||
|
||||
// TODO(jbd): Add godoc examples.
|
||||
|
||||
var defaultFormat propagation.HTTPFormat = &b3.HTTPFormat{}
|
||||
|
||||
// Attributes recorded on the span for the requests.
|
||||
// Only trace exporters will need them.
|
||||
const (
|
||||
HostAttribute = "http.host"
|
||||
MethodAttribute = "http.method"
|
||||
PathAttribute = "http.path"
|
||||
UserAgentAttribute = "http.user_agent"
|
||||
StatusCodeAttribute = "http.status_code"
|
||||
)
|
||||
|
||||
type traceTransport struct {
|
||||
base http.RoundTripper
|
||||
startOptions trace.StartOptions
|
||||
format propagation.HTTPFormat
|
||||
}
|
||||
|
||||
// TODO(jbd): Add message events for request and response size.
|
||||
|
||||
// RoundTrip creates a trace.Span and inserts it into the outgoing request's headers.
|
||||
// The created span can follow a parent span, if a parent is presented in
|
||||
// the request's context.
|
||||
func (t *traceTransport) RoundTrip(req *http.Request) (*http.Response, error) {
|
||||
name := spanNameFromURL(req.URL)
|
||||
// TODO(jbd): Discuss whether we want to prefix
|
||||
// outgoing requests with Sent.
|
||||
parent := trace.FromContext(req.Context())
|
||||
span := trace.NewSpan(name, parent, t.startOptions)
|
||||
req = req.WithContext(trace.WithSpan(req.Context(), span))
|
||||
|
||||
if t.format != nil {
|
||||
t.format.SpanContextToRequest(span.SpanContext(), req)
|
||||
}
|
||||
|
||||
span.AddAttributes(requestAttrs(req)...)
|
||||
resp, err := t.base.RoundTrip(req)
|
||||
if err != nil {
|
||||
span.SetStatus(trace.Status{Code: 2, Message: err.Error()})
|
||||
span.End()
|
||||
return resp, err
|
||||
}
|
||||
|
||||
span.AddAttributes(responseAttrs(resp)...)
|
||||
span.SetStatus(status(resp.StatusCode))
|
||||
|
||||
// span.End() will be invoked after
|
||||
// a read from resp.Body returns io.EOF or when
|
||||
// resp.Body.Close() is invoked.
|
||||
resp.Body = &bodyTracker{rc: resp.Body, span: span}
|
||||
return resp, err
|
||||
}
|
||||
|
||||
// bodyTracker wraps a response.Body and invokes
|
||||
// trace.EndSpan on encountering io.EOF on reading
|
||||
// the body of the original response.
|
||||
type bodyTracker struct {
|
||||
rc io.ReadCloser
|
||||
span *trace.Span
|
||||
}
|
||||
|
||||
var _ io.ReadCloser = (*bodyTracker)(nil)
|
||||
|
||||
func (bt *bodyTracker) Read(b []byte) (int, error) {
|
||||
n, err := bt.rc.Read(b)
|
||||
|
||||
switch err {
|
||||
case nil:
|
||||
return n, nil
|
||||
case io.EOF:
|
||||
bt.span.End()
|
||||
default:
|
||||
// For all other errors, set the span status
|
||||
bt.span.SetStatus(trace.Status{
|
||||
// Code 2 is the error code for Internal server error.
|
||||
Code: 2,
|
||||
Message: err.Error(),
|
||||
})
|
||||
}
|
||||
return n, err
|
||||
}
|
||||
|
||||
func (bt *bodyTracker) Close() error {
|
||||
// Invoking endSpan on Close will help catch the cases
|
||||
// in which a read returned a non-nil error, we set the
|
||||
// span status but didn't end the span.
|
||||
bt.span.End()
|
||||
return bt.rc.Close()
|
||||
}
|
||||
|
||||
// CancelRequest cancels an in-flight request by closing its connection.
|
||||
func (t *traceTransport) CancelRequest(req *http.Request) {
|
||||
type canceler interface {
|
||||
CancelRequest(*http.Request)
|
||||
}
|
||||
if cr, ok := t.base.(canceler); ok {
|
||||
cr.CancelRequest(req)
|
||||
}
|
||||
}
|
||||
|
||||
func spanNameFromURL(u *url.URL) string {
|
||||
return u.Path
|
||||
}
|
||||
|
||||
func requestAttrs(r *http.Request) []trace.Attribute {
|
||||
return []trace.Attribute{
|
||||
trace.StringAttribute(PathAttribute, r.URL.Path),
|
||||
trace.StringAttribute(HostAttribute, r.URL.Host),
|
||||
trace.StringAttribute(MethodAttribute, r.Method),
|
||||
trace.StringAttribute(UserAgentAttribute, r.UserAgent()),
|
||||
}
|
||||
}
|
||||
|
||||
func responseAttrs(resp *http.Response) []trace.Attribute {
|
||||
return []trace.Attribute{
|
||||
trace.Int64Attribute(StatusCodeAttribute, int64(resp.StatusCode)),
|
||||
}
|
||||
}
|
||||
|
||||
func status(statusCode int) trace.Status {
|
||||
var code int32
|
||||
if statusCode < 200 || statusCode >= 400 {
|
||||
code = codeUnknown
|
||||
}
|
||||
switch statusCode {
|
||||
case 499:
|
||||
code = codeCancelled
|
||||
case http.StatusBadRequest:
|
||||
code = codeInvalidArgument
|
||||
case http.StatusGatewayTimeout:
|
||||
code = codeDeadlineExceeded
|
||||
case http.StatusNotFound:
|
||||
code = codeNotFound
|
||||
case http.StatusForbidden:
|
||||
code = codePermissionDenied
|
||||
case http.StatusUnauthorized: // 401 is actually unauthenticated.
|
||||
code = codeUnathenticated
|
||||
case http.StatusTooManyRequests:
|
||||
code = codeResourceExhausted
|
||||
case http.StatusNotImplemented:
|
||||
code = codeUnimplemented
|
||||
case http.StatusServiceUnavailable:
|
||||
code = codeUnavailable
|
||||
}
|
||||
return trace.Status{Code: code, Message: codeToStr[code]}
|
||||
}
|
||||
|
||||
// TODO(jbd): Provide status codes from trace package.
|
||||
const (
|
||||
codeOK = 0
|
||||
codeCancelled = 1
|
||||
codeUnknown = 2
|
||||
codeInvalidArgument = 3
|
||||
codeDeadlineExceeded = 4
|
||||
codeNotFound = 5
|
||||
codeAlreadyExists = 6
|
||||
codePermissionDenied = 7
|
||||
codeResourceExhausted = 8
|
||||
codeFailedPrecondition = 9
|
||||
codeAborted = 10
|
||||
codeOutOfRange = 11
|
||||
codeUnimplemented = 12
|
||||
codeInternal = 13
|
||||
codeUnavailable = 14
|
||||
codeDataLoss = 15
|
||||
codeUnathenticated = 16
|
||||
)
|
||||
|
||||
var codeToStr = map[int32]string{
|
||||
codeOK: `"OK"`,
|
||||
codeCancelled: `"CANCELLED"`,
|
||||
codeUnknown: `"UNKNOWN"`,
|
||||
codeInvalidArgument: `"INVALID_ARGUMENT"`,
|
||||
codeDeadlineExceeded: `"DEADLINE_EXCEEDED"`,
|
||||
codeNotFound: `"NOT_FOUND"`,
|
||||
codeAlreadyExists: `"ALREADY_EXISTS"`,
|
||||
codePermissionDenied: `"PERMISSION_DENIED"`,
|
||||
codeResourceExhausted: `"RESOURCE_EXHAUSTED"`,
|
||||
codeFailedPrecondition: `"FAILED_PRECONDITION"`,
|
||||
codeAborted: `"ABORTED"`,
|
||||
codeOutOfRange: `"OUT_OF_RANGE"`,
|
||||
codeUnimplemented: `"UNIMPLEMENTED"`,
|
||||
codeInternal: `"INTERNAL"`,
|
||||
codeUnavailable: `"UNAVAILABLE"`,
|
||||
codeDataLoss: `"DATA_LOSS"`,
|
||||
codeUnathenticated: `"UNAUTHENTICATED"`,
|
||||
}
|
||||
|
|
@ -0,0 +1,55 @@
|
|||
// Copyright 2017, OpenCensus Authors
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
//
|
||||
|
||||
/*
|
||||
Package stats contains support for OpenCensus stats recording.
|
||||
|
||||
OpenCensus allows users to create typed measures, record measurements,
|
||||
aggregate the collected data, and export the aggregated data.
|
||||
|
||||
Measures
|
||||
|
||||
A measure represents a type of metric to be tracked and recorded.
|
||||
For example, latency, request Mb/s, and response Mb/s are measures
|
||||
to collect from a server.
|
||||
|
||||
Each measure needs to be registered before being used. Measure
|
||||
constructors such as Int64 and Float64 automatically
|
||||
register the measure by the given name. Each registered measure needs
|
||||
to be unique by name. Measures also have a description and a unit.
|
||||
|
||||
Libraries can define and export measures for their end users to
|
||||
create views and collect instrumentation data.
|
||||
|
||||
Recording measurements
|
||||
|
||||
Measurement is a data point to be collected for a measure. For example,
|
||||
for a latency (ms) measure, 100 is a measurement that represents a 100ms
|
||||
latency event. Users collect data points on the existing measures with
|
||||
the current context. Tags from the current context are recorded with the
|
||||
measurements if they are any.
|
||||
|
||||
Recorded measurements are dropped immediately if user is not aggregating
|
||||
them via views. Users don't necessarily need to conditionally enable/disable
|
||||
recording to reduce cost. Recording of measurements is cheap.
|
||||
|
||||
Libraries can always record measurements, and end-users can later decide
|
||||
on which measurements they want to collect by registering views. This allows
|
||||
libraries to turn on the instrumentation by default.
|
||||
*/
|
||||
package stats // import "go.opencensus.io/stats"
|
||||
|
||||
// TODO(acetechnologist): Add a link to the language independent OpenCensus
|
||||
// spec when it is available.
|
||||
|
|
@ -0,0 +1,25 @@
|
|||
// Copyright 2018, OpenCensus Authors
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package internal
|
||||
|
||||
import (
|
||||
"go.opencensus.io/tag"
|
||||
)
|
||||
|
||||
// DefaultRecorder will be called for each Record call.
|
||||
var DefaultRecorder func(*tag.Map, interface{})
|
||||
|
||||
// SubscriptionReporter reports when a view subscribed with a measure.
|
||||
var SubscriptionReporter func(measure string)
|
||||
|
|
@ -0,0 +1,28 @@
|
|||
// Copyright 2018, OpenCensus Authors
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package internal
|
||||
|
||||
const (
|
||||
MaxNameLength = 255
|
||||
)
|
||||
|
||||
func IsPrintable(str string) bool {
|
||||
for _, r := range str {
|
||||
if !(r >= ' ' && r <= '~') {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
|
@ -0,0 +1,132 @@
|
|||
// Copyright 2017, OpenCensus Authors
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
//
|
||||
|
||||
package stats
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
|
||||
"go.opencensus.io/stats/internal"
|
||||
)
|
||||
|
||||
// Measure represents a type of metric to be tracked and recorded.
|
||||
// For example, latency, request Mb/s, and response Mb/s are measures
|
||||
// to collect from a server.
|
||||
//
|
||||
// Each measure needs to be registered before being used.
|
||||
// Measure constructors such as Int64 and
|
||||
// Float64 automatically registers the measure
|
||||
// by the given name.
|
||||
// Each registered measure needs to be unique by name.
|
||||
// Measures also have a description and a unit.
|
||||
type Measure interface {
|
||||
Name() string
|
||||
Description() string
|
||||
Unit() string
|
||||
|
||||
subscribe()
|
||||
subscribed() bool
|
||||
}
|
||||
|
||||
type measure struct {
|
||||
subs int32 // access atomically
|
||||
|
||||
name string
|
||||
description string
|
||||
unit string
|
||||
}
|
||||
|
||||
func (m *measure) subscribe() {
|
||||
atomic.StoreInt32(&m.subs, 1)
|
||||
}
|
||||
|
||||
func (m *measure) subscribed() bool {
|
||||
return atomic.LoadInt32(&m.subs) == 1
|
||||
}
|
||||
|
||||
// Name returns the name of the measure.
|
||||
func (m *measure) Name() string {
|
||||
return m.name
|
||||
}
|
||||
|
||||
// Description returns the description of the measure.
|
||||
func (m *measure) Description() string {
|
||||
return m.description
|
||||
}
|
||||
|
||||
// Unit returns the unit of the measure.
|
||||
func (m *measure) Unit() string {
|
||||
return m.unit
|
||||
}
|
||||
|
||||
var (
|
||||
mu sync.RWMutex
|
||||
measures = make(map[string]Measure)
|
||||
)
|
||||
|
||||
var (
|
||||
errDuplicate = errors.New("duplicate measure name")
|
||||
errMeasureNameTooLong = fmt.Errorf("measure name cannot be longer than %v", internal.MaxNameLength)
|
||||
)
|
||||
|
||||
// FindMeasure finds the Measure instance, if any, associated with the given name.
|
||||
func FindMeasure(name string) Measure {
|
||||
mu.RLock()
|
||||
m := measures[name]
|
||||
mu.RUnlock()
|
||||
return m
|
||||
}
|
||||
|
||||
func register(m Measure) (Measure, error) {
|
||||
key := m.Name()
|
||||
mu.Lock()
|
||||
defer mu.Unlock()
|
||||
if stored, ok := measures[key]; ok {
|
||||
return stored, errDuplicate
|
||||
}
|
||||
measures[key] = m
|
||||
return m, nil
|
||||
}
|
||||
|
||||
// Measurement is the numeric value measured when recording stats. Each measure
|
||||
// provides methods to create measurements of their kind. For example, Int64Measure
|
||||
// provides M to convert an int64 into a measurement.
|
||||
type Measurement struct {
|
||||
v float64
|
||||
m Measure
|
||||
}
|
||||
|
||||
// Value returns the value of the Measurement as a float64.
|
||||
func (m Measurement) Value() float64 {
|
||||
return m.v
|
||||
}
|
||||
|
||||
// Measure returns the Measure from which this Measurement was created.
|
||||
func (m Measurement) Measure() Measure {
|
||||
return m.m
|
||||
}
|
||||
|
||||
func checkName(name string) error {
|
||||
if len(name) > internal.MaxNameLength {
|
||||
return errMeasureNameTooLong
|
||||
}
|
||||
if !internal.IsPrintable(name) {
|
||||
return errors.New("measure name needs to be an ASCII string")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
|
@ -0,0 +1,57 @@
|
|||
// Copyright 2017, OpenCensus Authors
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
//
|
||||
|
||||
package stats
|
||||
|
||||
// Float64Measure is a measure of type float64.
|
||||
type Float64Measure struct {
|
||||
measure
|
||||
}
|
||||
|
||||
func (m *Float64Measure) subscribe() {
|
||||
m.measure.subscribe()
|
||||
}
|
||||
|
||||
func (m *Float64Measure) subscribed() bool {
|
||||
return m.measure.subscribed()
|
||||
}
|
||||
|
||||
// M creates a new float64 measurement.
|
||||
// Use Record to record measurements.
|
||||
func (m *Float64Measure) M(v float64) Measurement {
|
||||
if !m.subscribed() {
|
||||
return Measurement{}
|
||||
}
|
||||
return Measurement{m: m, v: v}
|
||||
}
|
||||
|
||||
// Float64 creates a new measure of type Float64Measure. It returns
|
||||
// an error if a measure with the same name already exists.
|
||||
func Float64(name, description, unit string) (*Float64Measure, error) {
|
||||
if err := checkName(name); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
m := &Float64Measure{
|
||||
measure: measure{
|
||||
name: name,
|
||||
description: description,
|
||||
unit: unit,
|
||||
},
|
||||
}
|
||||
if _, err := register(m); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return m, nil
|
||||
}
|
||||
|
|
@ -0,0 +1,57 @@
|
|||
// Copyright 2017, OpenCensus Authors
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
//
|
||||
|
||||
package stats
|
||||
|
||||
// Int64Measure is a measure of type int64.
|
||||
type Int64Measure struct {
|
||||
measure
|
||||
}
|
||||
|
||||
func (m *Int64Measure) subscribe() {
|
||||
m.measure.subscribe()
|
||||
}
|
||||
|
||||
func (m *Int64Measure) subscribed() bool {
|
||||
return m.measure.subscribed()
|
||||
}
|
||||
|
||||
// M creates a new int64 measurement.
|
||||
// Use Record to record measurements.
|
||||
func (m *Int64Measure) M(v int64) Measurement {
|
||||
if !m.subscribed() {
|
||||
return Measurement{}
|
||||
}
|
||||
return Measurement{m: m, v: float64(v)}
|
||||
}
|
||||
|
||||
// Int64 creates a new measure of type Int64Measure. It returns an
|
||||
// error if a measure with the same name already exists.
|
||||
func Int64(name, description, unit string) (*Int64Measure, error) {
|
||||
if err := checkName(name); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
m := &Int64Measure{
|
||||
measure: measure{
|
||||
name: name,
|
||||
description: description,
|
||||
unit: unit,
|
||||
},
|
||||
}
|
||||
if _, err := register(m); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return m, nil
|
||||
}
|
||||
|
|
@ -0,0 +1,52 @@
|
|||
// Copyright 2018, OpenCensus Authors
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
//
|
||||
|
||||
package stats
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"go.opencensus.io/stats/internal"
|
||||
"go.opencensus.io/tag"
|
||||
)
|
||||
|
||||
func init() {
|
||||
internal.SubscriptionReporter = func(measure string) {
|
||||
mu.Lock()
|
||||
measures[measure].subscribe()
|
||||
mu.Unlock()
|
||||
}
|
||||
}
|
||||
|
||||
// Record records one or multiple measurements with the same tags at once.
|
||||
// If there are any tags in the context, measurements will be tagged with them.
|
||||
func Record(ctx context.Context, ms ...Measurement) {
|
||||
if len(ms) == 0 {
|
||||
return
|
||||
}
|
||||
var record bool
|
||||
for _, m := range ms {
|
||||
if (m != Measurement{}) {
|
||||
record = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if !record {
|
||||
return
|
||||
}
|
||||
if internal.DefaultRecorder != nil {
|
||||
internal.DefaultRecorder(tag.FromContext(ctx), ms)
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,24 @@
|
|||
// Copyright 2018, OpenCensus Authors
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
//
|
||||
|
||||
package stats
|
||||
|
||||
// Units are encoded according to the case-sensitive abbreviations from the
|
||||
// Unified Code for Units of Measure: http://unitsofmeasure.org/ucum.html
|
||||
const (
|
||||
UnitNone = "1"
|
||||
UnitBytes = "By"
|
||||
UnitMilliseconds = "ms"
|
||||
)
|
||||
|
|
@ -0,0 +1,112 @@
|
|||
// Copyright 2017, OpenCensus Authors
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
//
|
||||
|
||||
package view
|
||||
|
||||
//go:generate stringer -type AggType
|
||||
|
||||
// AggType represents the type of aggregation function used on a View.
|
||||
type AggType int
|
||||
|
||||
const (
|
||||
AggTypeNone AggType = iota // no aggregation; reserved for future use.
|
||||
AggTypeCount // the count aggregation, see Count.
|
||||
AggTypeSum // the sum aggregation, see Sum.
|
||||
AggTypeMean // the mean aggregation, see Mean.
|
||||
AggTypeDistribution // the distribution aggregation, see Distribution.
|
||||
)
|
||||
|
||||
// Aggregation represents a data aggregation method. Use one of the functions:
|
||||
// Count, Sum, Mean, or Distribution to construct an Aggregation.
|
||||
type Aggregation struct {
|
||||
Type AggType // Type is the AggType of this Aggregation.
|
||||
Buckets []float64 // Buckets are the bucket endpoints if this Aggregation represents a distribution, see Distribution.
|
||||
|
||||
newData func() AggregationData
|
||||
}
|
||||
|
||||
var (
|
||||
aggCount = &Aggregation{
|
||||
Type: AggTypeCount,
|
||||
newData: func() AggregationData {
|
||||
return newCountData(0)
|
||||
},
|
||||
}
|
||||
aggSum = &Aggregation{
|
||||
Type: AggTypeSum,
|
||||
newData: func() AggregationData {
|
||||
return newSumData(0)
|
||||
},
|
||||
}
|
||||
aggMean = &Aggregation{
|
||||
Type: AggTypeMean,
|
||||
newData: func() AggregationData {
|
||||
return newMeanData(0, 0)
|
||||
},
|
||||
}
|
||||
)
|
||||
|
||||
// Count indicates that data collected and aggregated
|
||||
// with this method will be turned into a count value.
|
||||
// For example, total number of accepted requests can be
|
||||
// aggregated by using Count.
|
||||
func Count() *Aggregation {
|
||||
return aggCount
|
||||
}
|
||||
|
||||
// Sum indicates that data collected and aggregated
|
||||
// with this method will be summed up.
|
||||
// For example, accumulated request bytes can be aggregated by using
|
||||
// Sum.
|
||||
func Sum() *Aggregation {
|
||||
return aggSum
|
||||
}
|
||||
|
||||
// Mean indicates that collect and aggregate data and maintain
|
||||
// the mean value.
|
||||
// For example, average latency in milliseconds can be aggregated by using
|
||||
// Mean, although in most cases it is preferable to use a Distribution.
|
||||
func Mean() *Aggregation {
|
||||
return aggMean
|
||||
}
|
||||
|
||||
// Distribution indicates that the desired aggregation is
|
||||
// a histogram distribution.
|
||||
//
|
||||
// An distribution aggregation may contain a histogram of the values in the
|
||||
// population. The bucket boundaries for that histogram are described
|
||||
// by the bounds. This defines len(bounds)+1 buckets.
|
||||
//
|
||||
// If len(bounds) >= 2 then the boundaries for bucket index i are:
|
||||
//
|
||||
// [-infinity, bounds[i]) for i = 0
|
||||
// [bounds[i-1], bounds[i]) for 0 < i < length
|
||||
// [bounds[i-1], +infinity) for i = length
|
||||
//
|
||||
// If len(bounds) is 0 then there is no histogram associated with the
|
||||
// distribution. There will be a single bucket with boundaries
|
||||
// (-infinity, +infinity).
|
||||
//
|
||||
// If len(bounds) is 1 then there is no finite buckets, and that single
|
||||
// element is the common boundary of the overflow and underflow buckets.
|
||||
func Distribution(bounds ...float64) *Aggregation {
|
||||
return &Aggregation{
|
||||
Type: AggTypeDistribution,
|
||||
Buckets: bounds,
|
||||
newData: func() AggregationData {
|
||||
return newDistributionData(bounds)
|
||||
},
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,230 @@
|
|||
// Copyright 2017, OpenCensus Authors
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
//
|
||||
|
||||
package view
|
||||
|
||||
import (
|
||||
"math"
|
||||
)
|
||||
|
||||
// AggregationData represents an aggregated value from a collection.
|
||||
// They are reported on the view data during exporting.
|
||||
// Mosts users won't directly access aggregration data.
|
||||
type AggregationData interface {
|
||||
isAggregationData() bool
|
||||
addSample(v float64)
|
||||
clone() AggregationData
|
||||
equal(other AggregationData) bool
|
||||
}
|
||||
|
||||
const epsilon = 1e-9
|
||||
|
||||
// CountData is the aggregated data for the Count aggregation.
|
||||
// A count aggregation processes data and counts the recordings.
|
||||
//
|
||||
// Most users won't directly access count data.
|
||||
type CountData int64
|
||||
|
||||
func newCountData(v int64) *CountData {
|
||||
tmp := CountData(v)
|
||||
return &tmp
|
||||
}
|
||||
|
||||
func (a *CountData) isAggregationData() bool { return true }
|
||||
|
||||
func (a *CountData) addSample(_ float64) {
|
||||
*a = *a + 1
|
||||
}
|
||||
|
||||
func (a *CountData) clone() AggregationData {
|
||||
return newCountData(int64(*a))
|
||||
}
|
||||
|
||||
func (a *CountData) equal(other AggregationData) bool {
|
||||
a2, ok := other.(*CountData)
|
||||
if !ok {
|
||||
return false
|
||||
}
|
||||
|
||||
return int64(*a) == int64(*a2)
|
||||
}
|
||||
|
||||
// SumData is the aggregated data for the Sum aggregation.
|
||||
// A sum aggregation processes data and sums up the recordings.
|
||||
//
|
||||
// Most users won't directly access sum data.
|
||||
type SumData float64
|
||||
|
||||
func newSumData(v float64) *SumData {
|
||||
tmp := SumData(v)
|
||||
return &tmp
|
||||
}
|
||||
|
||||
func (a *SumData) isAggregationData() bool { return true }
|
||||
|
||||
func (a *SumData) addSample(f float64) {
|
||||
*a += SumData(f)
|
||||
}
|
||||
|
||||
func (a *SumData) clone() AggregationData {
|
||||
return newSumData(float64(*a))
|
||||
}
|
||||
|
||||
func (a *SumData) equal(other AggregationData) bool {
|
||||
a2, ok := other.(*SumData)
|
||||
if !ok {
|
||||
return false
|
||||
}
|
||||
return math.Pow(float64(*a)-float64(*a2), 2) < epsilon
|
||||
}
|
||||
|
||||
// MeanData is the aggregated data for the Mean aggregation.
|
||||
// A mean aggregation processes data and maintains the mean value.
|
||||
//
|
||||
// Most users won't directly access mean data.
|
||||
type MeanData struct {
|
||||
Count int64 // number of data points aggregated
|
||||
Mean float64 // mean of all data points
|
||||
}
|
||||
|
||||
func newMeanData(mean float64, count int64) *MeanData {
|
||||
return &MeanData{
|
||||
Mean: mean,
|
||||
Count: count,
|
||||
}
|
||||
}
|
||||
|
||||
// Sum returns the sum of all samples collected.
|
||||
func (a *MeanData) Sum() float64 { return a.Mean * float64(a.Count) }
|
||||
|
||||
func (a *MeanData) isAggregationData() bool { return true }
|
||||
|
||||
func (a *MeanData) addSample(f float64) {
|
||||
a.Count++
|
||||
if a.Count == 1 {
|
||||
a.Mean = f
|
||||
return
|
||||
}
|
||||
a.Mean = a.Mean + (f-a.Mean)/float64(a.Count)
|
||||
}
|
||||
|
||||
func (a *MeanData) clone() AggregationData {
|
||||
return newMeanData(a.Mean, a.Count)
|
||||
}
|
||||
|
||||
func (a *MeanData) equal(other AggregationData) bool {
|
||||
a2, ok := other.(*MeanData)
|
||||
if !ok {
|
||||
return false
|
||||
}
|
||||
return a.Count == a2.Count && math.Pow(a.Mean-a2.Mean, 2) < epsilon
|
||||
}
|
||||
|
||||
// DistributionData is the aggregated data for the
|
||||
// Distribution aggregation.
|
||||
//
|
||||
// Most users won't directly access distribution data.
|
||||
type DistributionData struct {
|
||||
Count int64 // number of data points aggregated
|
||||
Min float64 // minimum value in the distribution
|
||||
Max float64 // max value in the distribution
|
||||
Mean float64 // mean of the distribution
|
||||
SumOfSquaredDev float64 // sum of the squared deviation from the mean
|
||||
CountPerBucket []int64 // number of occurrences per bucket
|
||||
bounds []float64 // histogram distribution of the values
|
||||
}
|
||||
|
||||
func newDistributionData(bounds []float64) *DistributionData {
|
||||
return &DistributionData{
|
||||
CountPerBucket: make([]int64, len(bounds)+1),
|
||||
bounds: bounds,
|
||||
Min: math.MaxFloat64,
|
||||
Max: math.SmallestNonzeroFloat64,
|
||||
}
|
||||
}
|
||||
|
||||
// Sum returns the sum of all samples collected.
|
||||
func (a *DistributionData) Sum() float64 { return a.Mean * float64(a.Count) }
|
||||
|
||||
func (a *DistributionData) variance() float64 {
|
||||
if a.Count <= 1 {
|
||||
return 0
|
||||
}
|
||||
return a.SumOfSquaredDev / float64(a.Count-1)
|
||||
}
|
||||
|
||||
func (a *DistributionData) isAggregationData() bool { return true }
|
||||
|
||||
func (a *DistributionData) addSample(f float64) {
|
||||
if f < a.Min {
|
||||
a.Min = f
|
||||
}
|
||||
if f > a.Max {
|
||||
a.Max = f
|
||||
}
|
||||
a.Count++
|
||||
a.incrementBucketCount(f)
|
||||
|
||||
if a.Count == 1 {
|
||||
a.Mean = f
|
||||
return
|
||||
}
|
||||
|
||||
oldMean := a.Mean
|
||||
a.Mean = a.Mean + (f-a.Mean)/float64(a.Count)
|
||||
a.SumOfSquaredDev = a.SumOfSquaredDev + (f-oldMean)*(f-a.Mean)
|
||||
}
|
||||
|
||||
func (a *DistributionData) incrementBucketCount(f float64) {
|
||||
if len(a.bounds) == 0 {
|
||||
a.CountPerBucket[0]++
|
||||
return
|
||||
}
|
||||
|
||||
for i, b := range a.bounds {
|
||||
if f < b {
|
||||
a.CountPerBucket[i]++
|
||||
return
|
||||
}
|
||||
}
|
||||
a.CountPerBucket[len(a.bounds)]++
|
||||
}
|
||||
|
||||
func (a *DistributionData) clone() AggregationData {
|
||||
counts := make([]int64, len(a.CountPerBucket))
|
||||
copy(counts, a.CountPerBucket)
|
||||
c := *a
|
||||
c.CountPerBucket = counts
|
||||
return &c
|
||||
}
|
||||
|
||||
func (a *DistributionData) equal(other AggregationData) bool {
|
||||
a2, ok := other.(*DistributionData)
|
||||
if !ok {
|
||||
return false
|
||||
}
|
||||
if a2 == nil {
|
||||
return false
|
||||
}
|
||||
if len(a.CountPerBucket) != len(a2.CountPerBucket) {
|
||||
return false
|
||||
}
|
||||
for i := range a.CountPerBucket {
|
||||
if a.CountPerBucket[i] != a2.CountPerBucket[i] {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return a.Count == a2.Count && a.Min == a2.Min && a.Max == a2.Max && math.Pow(a.Mean-a2.Mean, 2) < epsilon && math.Pow(a.variance()-a2.variance(), 2) < epsilon
|
||||
}
|
||||
|
|
@ -0,0 +1,16 @@
|
|||
// Code generated by "stringer -type AggType"; DO NOT EDIT.
|
||||
|
||||
package view
|
||||
|
||||
import "strconv"
|
||||
|
||||
const _AggType_name = "AggTypeNoneAggTypeCountAggTypeSumAggTypeMeanAggTypeDistribution"
|
||||
|
||||
var _AggType_index = [...]uint8{0, 11, 23, 33, 44, 63}
|
||||
|
||||
func (i AggType) String() string {
|
||||
if i < 0 || i >= AggType(len(_AggType_index)-1) {
|
||||
return "AggType(" + strconv.FormatInt(int64(i), 10) + ")"
|
||||
}
|
||||
return _AggType_name[_AggType_index[i]:_AggType_index[i+1]]
|
||||
}
|
||||
|
|
@ -0,0 +1,84 @@
|
|||
// Copyright 2017, OpenCensus Authors
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
//
|
||||
|
||||
package view
|
||||
|
||||
import (
|
||||
"sort"
|
||||
|
||||
"go.opencensus.io/internal/tagencoding"
|
||||
"go.opencensus.io/tag"
|
||||
)
|
||||
|
||||
type collector struct {
|
||||
// signatures holds the aggregations values for each unique tag signature
|
||||
// (values for all keys) to its aggregator.
|
||||
signatures map[string]AggregationData
|
||||
// Aggregation is the description of the aggregation to perform for this
|
||||
// view.
|
||||
a *Aggregation
|
||||
}
|
||||
|
||||
func (c *collector) addSample(s string, v float64) {
|
||||
aggregator, ok := c.signatures[s]
|
||||
if !ok {
|
||||
aggregator = c.a.newData()
|
||||
c.signatures[s] = aggregator
|
||||
}
|
||||
aggregator.addSample(v)
|
||||
}
|
||||
|
||||
func (c *collector) collectedRows(keys []tag.Key) []*Row {
|
||||
var rows []*Row
|
||||
for sig, aggregator := range c.signatures {
|
||||
tags := decodeTags([]byte(sig), keys)
|
||||
row := &Row{tags, aggregator}
|
||||
rows = append(rows, row)
|
||||
}
|
||||
return rows
|
||||
}
|
||||
|
||||
func (c *collector) clearRows() {
|
||||
c.signatures = make(map[string]AggregationData)
|
||||
}
|
||||
|
||||
// encodeWithKeys encodes the map by using values
|
||||
// only associated with the keys provided.
|
||||
func encodeWithKeys(m *tag.Map, keys []tag.Key) []byte {
|
||||
vb := &tagencoding.Values{
|
||||
Buffer: make([]byte, len(keys)),
|
||||
}
|
||||
for _, k := range keys {
|
||||
v, _ := m.Value(k)
|
||||
vb.WriteValue([]byte(v))
|
||||
}
|
||||
return vb.Bytes()
|
||||
}
|
||||
|
||||
// decodeTags decodes tags from the buffer and
|
||||
// orders them by the keys.
|
||||
func decodeTags(buf []byte, keys []tag.Key) []tag.Tag {
|
||||
vb := &tagencoding.Values{Buffer: buf}
|
||||
var tags []tag.Tag
|
||||
for _, k := range keys {
|
||||
v := vb.ReadValue()
|
||||
if v != nil {
|
||||
tags = append(tags, tag.Tag{Key: k, Value: string(v)})
|
||||
}
|
||||
}
|
||||
vb.ReadIndex = 0
|
||||
sort.Slice(tags, func(i, j int) bool { return tags[i].Key.Name() < tags[j].Key.Name() })
|
||||
return tags
|
||||
}
|
||||
|
|
@ -0,0 +1,47 @@
|
|||
// Copyright 2017, OpenCensus Authors
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
//
|
||||
|
||||
/*
|
||||
Package view contains support for collecting and exposing aggregates over stats.
|
||||
|
||||
In order to collect measurements, views need to be defined and registered.
|
||||
A view allows recorded measurements to be filtered and aggregated over a time window.
|
||||
|
||||
All recorded measurements can be filtered by a list of tags.
|
||||
|
||||
OpenCensus provides several aggregation methods: count, distribution, sum and mean.
|
||||
Count aggregation only counts the number of measurement points. Distribution
|
||||
aggregation provides statistical summary of the aggregated data. Sum distribution
|
||||
sums up the measurement points. Mean provides the mean of the recorded measurements.
|
||||
Aggregations can either happen cumulatively or over an interval.
|
||||
|
||||
Users can dynamically create and delete views.
|
||||
|
||||
Libraries can export their own views and claim the view names
|
||||
by registering them themselves.
|
||||
|
||||
Exporting
|
||||
|
||||
Collected and aggregated data can be exported to a metric collection
|
||||
backend by registering its exporter.
|
||||
|
||||
Multiple exporters can be registered to upload the data to various
|
||||
different backends. Users need to unregister the exporters once they
|
||||
no longer are needed.
|
||||
*/
|
||||
package view // import "go.opencensus.io/stats/view"
|
||||
|
||||
// TODO(acetechnologist): Add a link to the language independent OpenCensus
|
||||
// spec when it is available.
|
||||
|
|
@ -0,0 +1,53 @@
|
|||
// Copyright 2017, OpenCensus Authors
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package view
|
||||
|
||||
import "sync"
|
||||
|
||||
var (
|
||||
exportersMu sync.RWMutex // guards exporters
|
||||
exporters = make(map[Exporter]struct{})
|
||||
)
|
||||
|
||||
// Exporter exports the collected records as view data.
|
||||
//
|
||||
// The ExportView method should return quickly; if an
|
||||
// Exporter takes a significant amount of time to
|
||||
// process a Data, that work should be done on another goroutine.
|
||||
//
|
||||
// The Data should not be modified.
|
||||
type Exporter interface {
|
||||
ExportView(viewData *Data)
|
||||
}
|
||||
|
||||
// RegisterExporter registers an exporter.
|
||||
// Collected data will be reported via all the
|
||||
// registered exporters. Once you no longer
|
||||
// want data to be exported, invoke UnregisterExporter
|
||||
// with the previously registered exporter.
|
||||
func RegisterExporter(e Exporter) {
|
||||
exportersMu.Lock()
|
||||
defer exportersMu.Unlock()
|
||||
|
||||
exporters[e] = struct{}{}
|
||||
}
|
||||
|
||||
// UnregisterExporter unregisters an exporter.
|
||||
func UnregisterExporter(e Exporter) {
|
||||
exportersMu.Lock()
|
||||
defer exportersMu.Unlock()
|
||||
|
||||
delete(exporters, e)
|
||||
}
|
||||
|
|
@ -0,0 +1,197 @@
|
|||
// Copyright 2017, OpenCensus Authors
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
//
|
||||
|
||||
package view
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"reflect"
|
||||
"sort"
|
||||
"sync/atomic"
|
||||
"time"
|
||||
|
||||
"go.opencensus.io/stats"
|
||||
"go.opencensus.io/stats/internal"
|
||||
"go.opencensus.io/tag"
|
||||
)
|
||||
|
||||
// View allows users to aggregate the recorded stats.Measurements.
|
||||
// Views need to be passed to the Subscribe function to be before data will be
|
||||
// collected and sent to Exporters.
|
||||
type View struct {
|
||||
Name string // Name of View. Must be unique. If unset, will default to the name of the Measure.
|
||||
Description string // Description is a human-readable description for this view.
|
||||
|
||||
// TagKeys are the tag keys describing the grouping of this view.
|
||||
// A single Row will be produced for each combination of associated tag values.
|
||||
TagKeys []tag.Key
|
||||
|
||||
// Measure is a stats.Measure to aggregate in this view.
|
||||
Measure stats.Measure
|
||||
|
||||
// Aggregation is the aggregation function tp apply to the set of Measurements.
|
||||
Aggregation *Aggregation
|
||||
}
|
||||
|
||||
// Deprecated: Use &View{}.
|
||||
func New(name, description string, keys []tag.Key, measure stats.Measure, agg *Aggregation) (*View, error) {
|
||||
if measure == nil {
|
||||
panic("measure may not be nil")
|
||||
}
|
||||
return &View{
|
||||
Name: name,
|
||||
Description: description,
|
||||
TagKeys: keys,
|
||||
Measure: measure,
|
||||
Aggregation: agg,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// WithName returns a copy of the View with a new name. This is useful for
|
||||
// renaming views to cope with limitations placed on metric names by various
|
||||
// backends.
|
||||
func (v *View) WithName(name string) *View {
|
||||
vNew := *v
|
||||
vNew.Name = name
|
||||
return &vNew
|
||||
}
|
||||
|
||||
// same compares two views and returns true if they represent the same aggregation.
|
||||
func (v *View) same(other *View) bool {
|
||||
if v == other {
|
||||
return true
|
||||
}
|
||||
if v == nil {
|
||||
return false
|
||||
}
|
||||
return reflect.DeepEqual(v.Aggregation, other.Aggregation) &&
|
||||
v.Measure.Name() == other.Measure.Name()
|
||||
}
|
||||
|
||||
// canonicalized returns a validated View canonicalized by setting explicit
|
||||
// defaults for Name and Description and sorting the TagKeys
|
||||
func (v *View) canonicalize() error {
|
||||
if v.Measure == nil {
|
||||
return fmt.Errorf("cannot subscribe view %q: measure not set", v.Name)
|
||||
}
|
||||
if v.Aggregation == nil {
|
||||
return fmt.Errorf("cannot subscribe view %q: aggregation not set", v.Name)
|
||||
}
|
||||
if v.Name == "" {
|
||||
v.Name = v.Measure.Name()
|
||||
}
|
||||
if v.Description == "" {
|
||||
v.Description = v.Measure.Description()
|
||||
}
|
||||
if err := checkViewName(v.Name); err != nil {
|
||||
return err
|
||||
}
|
||||
sort.Slice(v.TagKeys, func(i, j int) bool {
|
||||
return v.TagKeys[i].Name() < v.TagKeys[j].Name()
|
||||
})
|
||||
return nil
|
||||
}
|
||||
|
||||
// viewInternal is the internal representation of a View.
|
||||
type viewInternal struct {
|
||||
view *View // view is the canonicalized View definition associated with this view.
|
||||
subscribed uint32 // 1 if someone is subscribed and data need to be exported, use atomic to access
|
||||
collector *collector
|
||||
}
|
||||
|
||||
func newViewInternal(v *View) (*viewInternal, error) {
|
||||
return &viewInternal{
|
||||
view: v,
|
||||
collector: &collector{make(map[string]AggregationData), v.Aggregation},
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (v *viewInternal) subscribe() {
|
||||
atomic.StoreUint32(&v.subscribed, 1)
|
||||
}
|
||||
|
||||
func (v *viewInternal) unsubscribe() {
|
||||
atomic.StoreUint32(&v.subscribed, 0)
|
||||
}
|
||||
|
||||
// isSubscribed returns true if the view is exporting
|
||||
// data by subscription.
|
||||
func (v *viewInternal) isSubscribed() bool {
|
||||
return atomic.LoadUint32(&v.subscribed) == 1
|
||||
}
|
||||
|
||||
func (v *viewInternal) clearRows() {
|
||||
v.collector.clearRows()
|
||||
}
|
||||
|
||||
func (v *viewInternal) collectedRows() []*Row {
|
||||
return v.collector.collectedRows(v.view.TagKeys)
|
||||
}
|
||||
|
||||
func (v *viewInternal) addSample(m *tag.Map, val float64) {
|
||||
if !v.isSubscribed() {
|
||||
return
|
||||
}
|
||||
sig := string(encodeWithKeys(m, v.view.TagKeys))
|
||||
v.collector.addSample(sig, val)
|
||||
}
|
||||
|
||||
// A Data is a set of rows about usage of the single measure associated
|
||||
// with the given view. Each row is specific to a unique set of tags.
|
||||
type Data struct {
|
||||
View *View
|
||||
Start, End time.Time
|
||||
Rows []*Row
|
||||
}
|
||||
|
||||
// Row is the collected value for a specific set of key value pairs a.k.a tags.
|
||||
type Row struct {
|
||||
Tags []tag.Tag
|
||||
Data AggregationData
|
||||
}
|
||||
|
||||
func (r *Row) String() string {
|
||||
var buffer bytes.Buffer
|
||||
buffer.WriteString("{ ")
|
||||
buffer.WriteString("{ ")
|
||||
for _, t := range r.Tags {
|
||||
buffer.WriteString(fmt.Sprintf("{%v %v}", t.Key.Name(), t.Value))
|
||||
}
|
||||
buffer.WriteString(" }")
|
||||
buffer.WriteString(fmt.Sprintf("%v", r.Data))
|
||||
buffer.WriteString(" }")
|
||||
return buffer.String()
|
||||
}
|
||||
|
||||
// same returns true if both Rows are equal. Tags are expected to be ordered
|
||||
// by the key name. Even both rows have the same tags but the tags appear in
|
||||
// different orders it will return false.
|
||||
func (r *Row) Equal(other *Row) bool {
|
||||
if r == other {
|
||||
return true
|
||||
}
|
||||
return reflect.DeepEqual(r.Tags, other.Tags) && r.Data.equal(other.Data)
|
||||
}
|
||||
|
||||
func checkViewName(name string) error {
|
||||
if len(name) > internal.MaxNameLength {
|
||||
return fmt.Errorf("view name cannot be larger than %v", internal.MaxNameLength)
|
||||
}
|
||||
if !internal.IsPrintable(name) {
|
||||
return fmt.Errorf("view name needs to be an ASCII string")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
|
@ -0,0 +1,257 @@
|
|||
// Copyright 2017, OpenCensus Authors
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
//
|
||||
|
||||
package view
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"go.opencensus.io/stats"
|
||||
"go.opencensus.io/stats/internal"
|
||||
"go.opencensus.io/tag"
|
||||
)
|
||||
|
||||
func init() {
|
||||
defaultWorker = newWorker()
|
||||
go defaultWorker.start()
|
||||
internal.DefaultRecorder = record
|
||||
}
|
||||
|
||||
type measureRef struct {
|
||||
measure string
|
||||
views map[*viewInternal]struct{}
|
||||
}
|
||||
|
||||
type worker struct {
|
||||
measures map[string]*measureRef
|
||||
views map[string]*viewInternal
|
||||
startTimes map[*viewInternal]time.Time
|
||||
|
||||
timer *time.Ticker
|
||||
c chan command
|
||||
quit, done chan bool
|
||||
}
|
||||
|
||||
var defaultWorker *worker
|
||||
|
||||
var defaultReportingDuration = 10 * time.Second
|
||||
|
||||
// Find returns a subscribed view associated with this name.
|
||||
// If no subscribed view is found, nil is returned.
|
||||
func Find(name string) (v *View) {
|
||||
req := &getViewByNameReq{
|
||||
name: name,
|
||||
c: make(chan *getViewByNameResp),
|
||||
}
|
||||
defaultWorker.c <- req
|
||||
resp := <-req.c
|
||||
return resp.v
|
||||
}
|
||||
|
||||
// Deprecated: Registering is a no-op. Use the Subscribe function.
|
||||
func Register(_ *View) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Deprecated: Unregistering is a no-op, see: Unsubscribe.
|
||||
func Unregister(_ *View) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Deprecated: Use the Subscribe function.
|
||||
func (v *View) Subscribe() error {
|
||||
return Subscribe(v)
|
||||
}
|
||||
|
||||
// Subscribe begins collecting data for the given views.
|
||||
// Once a view is subscribed, it reports data to the registered exporters.
|
||||
func Subscribe(views ...*View) error {
|
||||
for _, v := range views {
|
||||
if err := v.canonicalize(); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
req := &subscribeToViewReq{
|
||||
views: views,
|
||||
err: make(chan error),
|
||||
}
|
||||
defaultWorker.c <- req
|
||||
return <-req.err
|
||||
}
|
||||
|
||||
// Unsubscribe the given views. Data will not longer be exported for these views
|
||||
// after Unsubscribe returns.
|
||||
// It is not necessary to unsubscribe from views you expect to collect for the
|
||||
// duration of your program execution.
|
||||
func Unsubscribe(views ...*View) {
|
||||
names := make([]string, len(views))
|
||||
for i := range views {
|
||||
names[i] = views[i].Name
|
||||
}
|
||||
req := &unsubscribeFromViewReq{
|
||||
views: names,
|
||||
done: make(chan struct{}),
|
||||
}
|
||||
defaultWorker.c <- req
|
||||
<-req.done
|
||||
}
|
||||
|
||||
// Deprecated: Use the Unsubscribe function instead.
|
||||
func (v *View) Unsubscribe() error {
|
||||
if v == nil {
|
||||
return nil
|
||||
}
|
||||
Unsubscribe(v)
|
||||
return nil
|
||||
}
|
||||
|
||||
func RetrieveData(viewName string) ([]*Row, error) {
|
||||
req := &retrieveDataReq{
|
||||
now: time.Now(),
|
||||
v: viewName,
|
||||
c: make(chan *retrieveDataResp),
|
||||
}
|
||||
defaultWorker.c <- req
|
||||
resp := <-req.c
|
||||
return resp.rows, resp.err
|
||||
}
|
||||
|
||||
func record(tags *tag.Map, ms interface{}) {
|
||||
req := &recordReq{
|
||||
tm: tags,
|
||||
ms: ms.([]stats.Measurement),
|
||||
}
|
||||
defaultWorker.c <- req
|
||||
}
|
||||
|
||||
// SetReportingPeriod sets the interval between reporting aggregated views in
|
||||
// the program. If duration is less than or
|
||||
// equal to zero, it enables the default behavior.
|
||||
func SetReportingPeriod(d time.Duration) {
|
||||
// TODO(acetechnologist): ensure that the duration d is more than a certain
|
||||
// value. e.g. 1s
|
||||
req := &setReportingPeriodReq{
|
||||
d: d,
|
||||
c: make(chan bool),
|
||||
}
|
||||
defaultWorker.c <- req
|
||||
<-req.c // don't return until the timer is set to the new duration.
|
||||
}
|
||||
|
||||
func newWorker() *worker {
|
||||
return &worker{
|
||||
measures: make(map[string]*measureRef),
|
||||
views: make(map[string]*viewInternal),
|
||||
startTimes: make(map[*viewInternal]time.Time),
|
||||
timer: time.NewTicker(defaultReportingDuration),
|
||||
c: make(chan command, 1024),
|
||||
quit: make(chan bool),
|
||||
done: make(chan bool),
|
||||
}
|
||||
}
|
||||
|
||||
func (w *worker) start() {
|
||||
for {
|
||||
select {
|
||||
case cmd := <-w.c:
|
||||
if cmd != nil {
|
||||
cmd.handleCommand(w)
|
||||
}
|
||||
case <-w.timer.C:
|
||||
w.reportUsage(time.Now())
|
||||
case <-w.quit:
|
||||
w.timer.Stop()
|
||||
close(w.c)
|
||||
w.done <- true
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (w *worker) stop() {
|
||||
w.quit <- true
|
||||
<-w.done
|
||||
}
|
||||
|
||||
func (w *worker) getMeasureRef(name string) *measureRef {
|
||||
if mr, ok := w.measures[name]; ok {
|
||||
return mr
|
||||
}
|
||||
mr := &measureRef{
|
||||
measure: name,
|
||||
views: make(map[*viewInternal]struct{}),
|
||||
}
|
||||
w.measures[name] = mr
|
||||
return mr
|
||||
}
|
||||
|
||||
func (w *worker) tryRegisterView(v *View) (*viewInternal, error) {
|
||||
vi, err := newViewInternal(v)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if x, ok := w.views[vi.view.Name]; ok {
|
||||
if !x.view.same(vi.view) {
|
||||
return nil, fmt.Errorf("cannot subscribe view %q; a different view with the same name is already subscribed", v.Name)
|
||||
}
|
||||
|
||||
// the view is already registered so there is nothing to do and the
|
||||
// command is considered successful.
|
||||
return x, nil
|
||||
}
|
||||
w.views[vi.view.Name] = vi
|
||||
ref := w.getMeasureRef(vi.view.Measure.Name())
|
||||
ref.views[vi] = struct{}{}
|
||||
return vi, nil
|
||||
}
|
||||
|
||||
func (w *worker) reportUsage(now time.Time) {
|
||||
for _, v := range w.views {
|
||||
if !v.isSubscribed() {
|
||||
continue
|
||||
}
|
||||
rows := v.collectedRows()
|
||||
_, ok := w.startTimes[v]
|
||||
if !ok {
|
||||
w.startTimes[v] = now
|
||||
}
|
||||
// Make sure collector is never going
|
||||
// to mutate the exported data.
|
||||
rows = deepCopyRowData(rows)
|
||||
viewData := &Data{
|
||||
View: v.view,
|
||||
Start: w.startTimes[v],
|
||||
End: time.Now(),
|
||||
Rows: rows,
|
||||
}
|
||||
exportersMu.Lock()
|
||||
for e := range exporters {
|
||||
e.ExportView(viewData)
|
||||
}
|
||||
exportersMu.Unlock()
|
||||
}
|
||||
}
|
||||
|
||||
func deepCopyRowData(rows []*Row) []*Row {
|
||||
newRows := make([]*Row, 0, len(rows))
|
||||
for _, r := range rows {
|
||||
newRows = append(newRows, &Row{
|
||||
Data: r.Data.clone(),
|
||||
Tags: r.Tags,
|
||||
})
|
||||
}
|
||||
return newRows
|
||||
}
|
||||
|
|
@ -0,0 +1,165 @@
|
|||
// Copyright 2017, OpenCensus Authors
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
//
|
||||
|
||||
package view
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"go.opencensus.io/stats"
|
||||
"go.opencensus.io/stats/internal"
|
||||
"go.opencensus.io/tag"
|
||||
)
|
||||
|
||||
type command interface {
|
||||
handleCommand(w *worker)
|
||||
}
|
||||
|
||||
// getViewByNameReq is the command to get a view given its name.
|
||||
type getViewByNameReq struct {
|
||||
name string
|
||||
c chan *getViewByNameResp
|
||||
}
|
||||
|
||||
type getViewByNameResp struct {
|
||||
v *View
|
||||
}
|
||||
|
||||
func (cmd *getViewByNameReq) handleCommand(w *worker) {
|
||||
cmd.c <- &getViewByNameResp{w.views[cmd.name].view}
|
||||
}
|
||||
|
||||
// subscribeToViewReq is the command to subscribe to a view.
|
||||
type subscribeToViewReq struct {
|
||||
views []*View
|
||||
err chan error
|
||||
}
|
||||
|
||||
func (cmd *subscribeToViewReq) handleCommand(w *worker) {
|
||||
var errstr []string
|
||||
for _, view := range cmd.views {
|
||||
vi, err := w.tryRegisterView(view)
|
||||
if err != nil {
|
||||
errstr = append(errstr, fmt.Sprintf("%s: %v", view.Name, err))
|
||||
continue
|
||||
}
|
||||
internal.SubscriptionReporter(view.Measure.Name())
|
||||
vi.subscribe()
|
||||
}
|
||||
if len(errstr) > 0 {
|
||||
cmd.err <- errors.New(strings.Join(errstr, "\n"))
|
||||
} else {
|
||||
cmd.err <- nil
|
||||
}
|
||||
}
|
||||
|
||||
// unsubscribeFromViewReq is the command to unsubscribe to a view. Has no
|
||||
// impact on the data collection for client that are pulling data from the
|
||||
// library.
|
||||
type unsubscribeFromViewReq struct {
|
||||
views []string
|
||||
done chan struct{}
|
||||
}
|
||||
|
||||
func (cmd *unsubscribeFromViewReq) handleCommand(w *worker) {
|
||||
for _, name := range cmd.views {
|
||||
vi, ok := w.views[name]
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
|
||||
vi.unsubscribe()
|
||||
if !vi.isSubscribed() {
|
||||
// this was the last subscription and view is not collecting anymore.
|
||||
// The collected data can be cleared.
|
||||
vi.clearRows()
|
||||
}
|
||||
}
|
||||
cmd.done <- struct{}{}
|
||||
}
|
||||
|
||||
// retrieveDataReq is the command to retrieve data for a view.
|
||||
type retrieveDataReq struct {
|
||||
now time.Time
|
||||
v string
|
||||
c chan *retrieveDataResp
|
||||
}
|
||||
|
||||
type retrieveDataResp struct {
|
||||
rows []*Row
|
||||
err error
|
||||
}
|
||||
|
||||
func (cmd *retrieveDataReq) handleCommand(w *worker) {
|
||||
vi, ok := w.views[cmd.v]
|
||||
if !ok {
|
||||
cmd.c <- &retrieveDataResp{
|
||||
nil,
|
||||
fmt.Errorf("cannot retrieve data; view %q is not registered", cmd.v),
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
if !vi.isSubscribed() {
|
||||
cmd.c <- &retrieveDataResp{
|
||||
nil,
|
||||
fmt.Errorf("cannot retrieve data; view %q has no subscriptions or collection is not forcibly started", cmd.v),
|
||||
}
|
||||
return
|
||||
}
|
||||
cmd.c <- &retrieveDataResp{
|
||||
vi.collectedRows(),
|
||||
nil,
|
||||
}
|
||||
}
|
||||
|
||||
// recordReq is the command to record data related to multiple measures
|
||||
// at once.
|
||||
type recordReq struct {
|
||||
tm *tag.Map
|
||||
ms []stats.Measurement
|
||||
}
|
||||
|
||||
func (cmd *recordReq) handleCommand(w *worker) {
|
||||
for _, m := range cmd.ms {
|
||||
if (m == stats.Measurement{}) { // not subscribed
|
||||
continue
|
||||
}
|
||||
ref := w.getMeasureRef(m.Measure().Name())
|
||||
for v := range ref.views {
|
||||
v.addSample(cmd.tm, m.Value())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// setReportingPeriodReq is the command to modify the duration between
|
||||
// reporting the collected data to the subscribed clients.
|
||||
type setReportingPeriodReq struct {
|
||||
d time.Duration
|
||||
c chan bool
|
||||
}
|
||||
|
||||
func (cmd *setReportingPeriodReq) handleCommand(w *worker) {
|
||||
w.timer.Stop()
|
||||
if cmd.d <= 0 {
|
||||
w.timer = time.NewTicker(defaultReportingDuration)
|
||||
} else {
|
||||
w.timer = time.NewTicker(cmd.d)
|
||||
}
|
||||
cmd.c <- true
|
||||
}
|
||||
|
|
@ -0,0 +1,41 @@
|
|||
// Copyright 2017, OpenCensus Authors
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
//
|
||||
|
||||
package tag
|
||||
|
||||
import "context"
|
||||
|
||||
// FromContext returns the tag map stored in the context.
|
||||
func FromContext(ctx context.Context) *Map {
|
||||
// The returned tag map shouldn't be mutated.
|
||||
ts := ctx.Value(mapCtxKey)
|
||||
if ts == nil {
|
||||
return nil
|
||||
}
|
||||
return ts.(*Map)
|
||||
}
|
||||
|
||||
// NewContext creates a new context with the given tag map.
|
||||
// To propagate a tag map to downstream methods and downstream RPCs, add a tag map
|
||||
// to the current context. NewContext will return a copy of the current context,
|
||||
// and put the tag map into the returned one.
|
||||
// If there is already a tag map in the current context, it will be replaced with m.
|
||||
func NewContext(ctx context.Context, m *Map) context.Context {
|
||||
return context.WithValue(ctx, mapCtxKey, m)
|
||||
}
|
||||
|
||||
type ctxKey struct{}
|
||||
|
||||
var mapCtxKey = ctxKey{}
|
||||
|
|
@ -0,0 +1,26 @@
|
|||
// Copyright 2017, OpenCensus Authors
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
//
|
||||
|
||||
/*
|
||||
Package tag contains OpenCensus tags.
|
||||
|
||||
Tags are key-value pairs. Tags provide additional cardinality to
|
||||
the OpenCensus instrumentation data.
|
||||
|
||||
Tags can be propagated on the wire and in the same
|
||||
process via context.Context. Encode and Decode should be
|
||||
used to represent tags into their binary propagation form.
|
||||
*/
|
||||
package tag // import "go.opencensus.io/tag"
|
||||
|
|
@ -0,0 +1,35 @@
|
|||
// Copyright 2017, OpenCensus Authors
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
//
|
||||
|
||||
package tag
|
||||
|
||||
// Key represents a tag key.
|
||||
type Key struct {
|
||||
name string
|
||||
}
|
||||
|
||||
// NewKey creates or retrieves a string key identified by name.
|
||||
// Calling NewKey consequently with the same name returns the same key.
|
||||
func NewKey(name string) (Key, error) {
|
||||
if !checkKeyName(name) {
|
||||
return Key{}, errInvalidKeyName
|
||||
}
|
||||
return Key{name: name}, nil
|
||||
}
|
||||
|
||||
// Name returns the name of the key.
|
||||
func (k Key) Name() string {
|
||||
return k.name
|
||||
}
|
||||
|
|
@ -0,0 +1,197 @@
|
|||
// Copyright 2017, OpenCensus Authors
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
//
|
||||
|
||||
package tag
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"fmt"
|
||||
"sort"
|
||||
)
|
||||
|
||||
// Tag is a key value pair that can be propagated on wire.
|
||||
type Tag struct {
|
||||
Key Key
|
||||
Value string
|
||||
}
|
||||
|
||||
// Map is a map of tags. Use NewMap to build tag maps.
|
||||
type Map struct {
|
||||
m map[Key]string
|
||||
}
|
||||
|
||||
// Value returns the value for the key if a value
|
||||
// for the key exists.
|
||||
func (m *Map) Value(k Key) (string, bool) {
|
||||
if m == nil {
|
||||
return "", false
|
||||
}
|
||||
v, ok := m.m[k]
|
||||
return v, ok
|
||||
}
|
||||
|
||||
func (m *Map) String() string {
|
||||
if m == nil {
|
||||
return "nil"
|
||||
}
|
||||
var keys []Key
|
||||
for k := range m.m {
|
||||
keys = append(keys, k)
|
||||
}
|
||||
sort.Slice(keys, func(i, j int) bool { return keys[i].Name() < keys[j].Name() })
|
||||
|
||||
var buffer bytes.Buffer
|
||||
buffer.WriteString("{ ")
|
||||
for _, k := range keys {
|
||||
buffer.WriteString(fmt.Sprintf("{%v %v}", k.name, m.m[k]))
|
||||
}
|
||||
buffer.WriteString(" }")
|
||||
return buffer.String()
|
||||
}
|
||||
|
||||
func (m *Map) insert(k Key, v string) {
|
||||
if _, ok := m.m[k]; ok {
|
||||
return
|
||||
}
|
||||
m.m[k] = v
|
||||
}
|
||||
|
||||
func (m *Map) update(k Key, v string) {
|
||||
if _, ok := m.m[k]; ok {
|
||||
m.m[k] = v
|
||||
}
|
||||
}
|
||||
|
||||
func (m *Map) upsert(k Key, v string) {
|
||||
m.m[k] = v
|
||||
}
|
||||
|
||||
func (m *Map) delete(k Key) {
|
||||
delete(m.m, k)
|
||||
}
|
||||
|
||||
func newMap(sizeHint int) *Map {
|
||||
return &Map{m: make(map[Key]string, sizeHint)}
|
||||
}
|
||||
|
||||
// Mutator modifies a tag map.
|
||||
type Mutator interface {
|
||||
Mutate(t *Map) (*Map, error)
|
||||
}
|
||||
|
||||
// Insert returns a mutator that inserts a
|
||||
// value associated with k. If k already exists in the tag map,
|
||||
// mutator doesn't update the value.
|
||||
func Insert(k Key, v string) Mutator {
|
||||
return &mutator{
|
||||
fn: func(m *Map) (*Map, error) {
|
||||
if !checkValue(v) {
|
||||
return nil, errInvalidValue
|
||||
}
|
||||
m.insert(k, v)
|
||||
return m, nil
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// Update returns a mutator that updates the
|
||||
// value of the tag associated with k with v. If k doesn't
|
||||
// exists in the tag map, the mutator doesn't insert the value.
|
||||
func Update(k Key, v string) Mutator {
|
||||
return &mutator{
|
||||
fn: func(m *Map) (*Map, error) {
|
||||
if !checkValue(v) {
|
||||
return nil, errInvalidValue
|
||||
}
|
||||
m.update(k, v)
|
||||
return m, nil
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// Upsert returns a mutator that upserts the
|
||||
// value of the tag associated with k with v. It inserts the
|
||||
// value if k doesn't exist already. It mutates the value
|
||||
// if k already exists.
|
||||
func Upsert(k Key, v string) Mutator {
|
||||
return &mutator{
|
||||
fn: func(m *Map) (*Map, error) {
|
||||
if !checkValue(v) {
|
||||
return nil, errInvalidValue
|
||||
}
|
||||
m.upsert(k, v)
|
||||
return m, nil
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// Delete returns a mutator that deletes
|
||||
// the value associated with k.
|
||||
func Delete(k Key) Mutator {
|
||||
return &mutator{
|
||||
fn: func(m *Map) (*Map, error) {
|
||||
m.delete(k)
|
||||
return m, nil
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// New returns a new context that contains a tag map
|
||||
// originated from the incoming context and modified
|
||||
// with the provided mutators.
|
||||
func New(ctx context.Context, mutator ...Mutator) (context.Context, error) {
|
||||
m := newMap(0)
|
||||
orig := FromContext(ctx)
|
||||
if orig != nil {
|
||||
for k, v := range orig.m {
|
||||
if !checkKeyName(k.Name()) {
|
||||
return ctx, fmt.Errorf("key:%q: %v", k, errInvalidKeyName)
|
||||
}
|
||||
if !checkValue(v) {
|
||||
return ctx, fmt.Errorf("key:%q value:%q: %v", k.Name(), v, errInvalidValue)
|
||||
}
|
||||
m.insert(k, v)
|
||||
}
|
||||
}
|
||||
var err error
|
||||
for _, mod := range mutator {
|
||||
m, err = mod.Mutate(m)
|
||||
if err != nil {
|
||||
return ctx, err
|
||||
}
|
||||
}
|
||||
return NewContext(ctx, m), nil
|
||||
}
|
||||
|
||||
// Do is similar to pprof.Do: a convenience for installing the tags
|
||||
// from the context as Go profiler labels. This allows you to
|
||||
// correlated runtime profiling with stats.
|
||||
//
|
||||
// It converts the key/values from the given map to Go profiler labels
|
||||
// and calls pprof.Do.
|
||||
//
|
||||
// Do is going to do nothing if your Go version is below 1.9.
|
||||
func Do(ctx context.Context, f func(ctx context.Context)) {
|
||||
do(ctx, f)
|
||||
}
|
||||
|
||||
type mutator struct {
|
||||
fn func(t *Map) (*Map, error)
|
||||
}
|
||||
|
||||
func (m *mutator) Mutate(t *Map) (*Map, error) {
|
||||
return m.fn(t)
|
||||
}
|
||||
|
|
@ -0,0 +1,221 @@
|
|||
// Copyright 2017, OpenCensus Authors
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
//
|
||||
|
||||
package tag
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
"fmt"
|
||||
)
|
||||
|
||||
// KeyType defines the types of keys allowed. Currently only keyTypeString is
|
||||
// supported.
|
||||
type keyType byte
|
||||
|
||||
const (
|
||||
keyTypeString keyType = iota
|
||||
keyTypeInt64
|
||||
keyTypeTrue
|
||||
keyTypeFalse
|
||||
|
||||
tagsVersionID = byte(0)
|
||||
)
|
||||
|
||||
type encoderGRPC struct {
|
||||
buf []byte
|
||||
writeIdx, readIdx int
|
||||
}
|
||||
|
||||
// writeKeyString writes the fieldID '0' followed by the key string and value
|
||||
// string.
|
||||
func (eg *encoderGRPC) writeTagString(k, v string) {
|
||||
eg.writeByte(byte(keyTypeString))
|
||||
eg.writeStringWithVarintLen(k)
|
||||
eg.writeStringWithVarintLen(v)
|
||||
}
|
||||
|
||||
func (eg *encoderGRPC) writeTagUint64(k string, i uint64) {
|
||||
eg.writeByte(byte(keyTypeInt64))
|
||||
eg.writeStringWithVarintLen(k)
|
||||
eg.writeUint64(i)
|
||||
}
|
||||
|
||||
func (eg *encoderGRPC) writeTagTrue(k string) {
|
||||
eg.writeByte(byte(keyTypeTrue))
|
||||
eg.writeStringWithVarintLen(k)
|
||||
}
|
||||
|
||||
func (eg *encoderGRPC) writeTagFalse(k string) {
|
||||
eg.writeByte(byte(keyTypeFalse))
|
||||
eg.writeStringWithVarintLen(k)
|
||||
}
|
||||
|
||||
func (eg *encoderGRPC) writeBytesWithVarintLen(bytes []byte) {
|
||||
length := len(bytes)
|
||||
|
||||
eg.growIfRequired(binary.MaxVarintLen64 + length)
|
||||
eg.writeIdx += binary.PutUvarint(eg.buf[eg.writeIdx:], uint64(length))
|
||||
copy(eg.buf[eg.writeIdx:], bytes)
|
||||
eg.writeIdx += length
|
||||
}
|
||||
|
||||
func (eg *encoderGRPC) writeStringWithVarintLen(s string) {
|
||||
length := len(s)
|
||||
|
||||
eg.growIfRequired(binary.MaxVarintLen64 + length)
|
||||
eg.writeIdx += binary.PutUvarint(eg.buf[eg.writeIdx:], uint64(length))
|
||||
copy(eg.buf[eg.writeIdx:], s)
|
||||
eg.writeIdx += length
|
||||
}
|
||||
|
||||
func (eg *encoderGRPC) writeByte(v byte) {
|
||||
eg.growIfRequired(1)
|
||||
eg.buf[eg.writeIdx] = v
|
||||
eg.writeIdx++
|
||||
}
|
||||
|
||||
func (eg *encoderGRPC) writeUint32(i uint32) {
|
||||
eg.growIfRequired(4)
|
||||
binary.LittleEndian.PutUint32(eg.buf[eg.writeIdx:], i)
|
||||
eg.writeIdx += 4
|
||||
}
|
||||
|
||||
func (eg *encoderGRPC) writeUint64(i uint64) {
|
||||
eg.growIfRequired(8)
|
||||
binary.LittleEndian.PutUint64(eg.buf[eg.writeIdx:], i)
|
||||
eg.writeIdx += 8
|
||||
}
|
||||
|
||||
func (eg *encoderGRPC) readByte() byte {
|
||||
b := eg.buf[eg.readIdx]
|
||||
eg.readIdx++
|
||||
return b
|
||||
}
|
||||
|
||||
func (eg *encoderGRPC) readUint32() uint32 {
|
||||
i := binary.LittleEndian.Uint32(eg.buf[eg.readIdx:])
|
||||
eg.readIdx += 4
|
||||
return i
|
||||
}
|
||||
|
||||
func (eg *encoderGRPC) readUint64() uint64 {
|
||||
i := binary.LittleEndian.Uint64(eg.buf[eg.readIdx:])
|
||||
eg.readIdx += 8
|
||||
return i
|
||||
}
|
||||
|
||||
func (eg *encoderGRPC) readBytesWithVarintLen() ([]byte, error) {
|
||||
if eg.readEnded() {
|
||||
return nil, fmt.Errorf("unexpected end while readBytesWithVarintLen '%x' starting at idx '%v'", eg.buf, eg.readIdx)
|
||||
}
|
||||
length, valueStart := binary.Uvarint(eg.buf[eg.readIdx:])
|
||||
if valueStart <= 0 {
|
||||
return nil, fmt.Errorf("unexpected end while readBytesWithVarintLen '%x' starting at idx '%v'", eg.buf, eg.readIdx)
|
||||
}
|
||||
|
||||
valueStart += eg.readIdx
|
||||
valueEnd := valueStart + int(length)
|
||||
if valueEnd > len(eg.buf) {
|
||||
return nil, fmt.Errorf("malformed encoding: length:%v, upper:%v, maxLength:%v", length, valueEnd, len(eg.buf))
|
||||
}
|
||||
|
||||
eg.readIdx = valueEnd
|
||||
return eg.buf[valueStart:valueEnd], nil
|
||||
}
|
||||
|
||||
func (eg *encoderGRPC) readStringWithVarintLen() (string, error) {
|
||||
bytes, err := eg.readBytesWithVarintLen()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return string(bytes), nil
|
||||
}
|
||||
|
||||
func (eg *encoderGRPC) growIfRequired(expected int) {
|
||||
if len(eg.buf)-eg.writeIdx < expected {
|
||||
tmp := make([]byte, 2*(len(eg.buf)+1)+expected)
|
||||
copy(tmp, eg.buf)
|
||||
eg.buf = tmp
|
||||
}
|
||||
}
|
||||
|
||||
func (eg *encoderGRPC) readEnded() bool {
|
||||
return eg.readIdx >= len(eg.buf)
|
||||
}
|
||||
|
||||
func (eg *encoderGRPC) bytes() []byte {
|
||||
return eg.buf[:eg.writeIdx]
|
||||
}
|
||||
|
||||
// Encode encodes the tag map into a []byte. It is useful to propagate
|
||||
// the tag maps on wire in binary format.
|
||||
func Encode(m *Map) []byte {
|
||||
eg := &encoderGRPC{
|
||||
buf: make([]byte, len(m.m)),
|
||||
}
|
||||
eg.writeByte(byte(tagsVersionID))
|
||||
for k, v := range m.m {
|
||||
eg.writeByte(byte(keyTypeString))
|
||||
eg.writeStringWithVarintLen(k.name)
|
||||
eg.writeBytesWithVarintLen([]byte(v))
|
||||
}
|
||||
return eg.bytes()
|
||||
}
|
||||
|
||||
// Decode decodes the given []byte into a tag map.
|
||||
func Decode(bytes []byte) (*Map, error) {
|
||||
ts := newMap(0)
|
||||
|
||||
eg := &encoderGRPC{
|
||||
buf: bytes,
|
||||
}
|
||||
if len(eg.buf) == 0 {
|
||||
return ts, nil
|
||||
}
|
||||
|
||||
version := eg.readByte()
|
||||
if version > tagsVersionID {
|
||||
return nil, fmt.Errorf("cannot decode: unsupported version: %q; supports only up to: %q", version, tagsVersionID)
|
||||
}
|
||||
|
||||
for !eg.readEnded() {
|
||||
typ := keyType(eg.readByte())
|
||||
|
||||
if typ != keyTypeString {
|
||||
return nil, fmt.Errorf("cannot decode: invalid key type: %q", typ)
|
||||
}
|
||||
|
||||
k, err := eg.readBytesWithVarintLen()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
v, err := eg.readBytesWithVarintLen()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
key, err := NewKey(string(k))
|
||||
if err != nil {
|
||||
return nil, err // no partial failures
|
||||
}
|
||||
val := string(v)
|
||||
if !checkValue(val) {
|
||||
return nil, errInvalidValue // no partial failures
|
||||
}
|
||||
ts.upsert(key, val)
|
||||
}
|
||||
return ts, nil
|
||||
}
|
||||
|
|
@ -0,0 +1,31 @@
|
|||
// Copyright 2018, OpenCensus Authors
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
// +build go1.9
|
||||
|
||||
package tag
|
||||
|
||||
import (
|
||||
"context"
|
||||
"runtime/pprof"
|
||||
)
|
||||
|
||||
func do(ctx context.Context, f func(ctx context.Context)) {
|
||||
m := FromContext(ctx)
|
||||
keyvals := make([]string, 0, 2*len(m.m))
|
||||
for k, v := range m.m {
|
||||
keyvals = append(keyvals, k.Name(), v)
|
||||
}
|
||||
pprof.Do(ctx, pprof.Labels(keyvals...), f)
|
||||
}
|
||||
|
|
@ -0,0 +1,23 @@
|
|||
// Copyright 2018, OpenCensus Authors
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
// +build !go1.9
|
||||
|
||||
package tag
|
||||
|
||||
import "context"
|
||||
|
||||
func do(ctx context.Context, f func(ctx context.Context)) {
|
||||
f(ctx)
|
||||
}
|
||||
|
|
@ -0,0 +1,56 @@
|
|||
// Copyright 2017, OpenCensus Authors
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package tag
|
||||
|
||||
import "errors"
|
||||
|
||||
const (
|
||||
maxKeyLength = 255
|
||||
|
||||
// valid are restricted to US-ASCII subset (range 0x20 (' ') to 0x7e ('~')).
|
||||
validKeyValueMin = 32
|
||||
validKeyValueMax = 126
|
||||
)
|
||||
|
||||
var (
|
||||
errInvalidKeyName = errors.New("invalid key name: only ASCII characters accepted; max length must be 255 characters")
|
||||
errInvalidValue = errors.New("invalid value: only ASCII characters accepted; max length must be 255 characters")
|
||||
)
|
||||
|
||||
func checkKeyName(name string) bool {
|
||||
if len(name) == 0 {
|
||||
return false
|
||||
}
|
||||
if len(name) > maxKeyLength {
|
||||
return false
|
||||
}
|
||||
return isASCII(name)
|
||||
}
|
||||
|
||||
func isASCII(s string) bool {
|
||||
for _, c := range s {
|
||||
if (c < validKeyValueMin) || (c > validKeyValueMax) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
func checkValue(v string) bool {
|
||||
if len(v) > maxKeyLength {
|
||||
return false
|
||||
}
|
||||
return isASCII(v)
|
||||
}
|
||||
|
|
@ -0,0 +1,113 @@
|
|||
// Copyright 2017, OpenCensus Authors
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package trace
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"time"
|
||||
)
|
||||
|
||||
type (
|
||||
// TraceID is a 16-byte identifier for a set of spans.
|
||||
TraceID [16]byte
|
||||
// SpanID is an 8-byte identifier for a single span.
|
||||
SpanID [8]byte
|
||||
)
|
||||
|
||||
func (t TraceID) String() string {
|
||||
return fmt.Sprintf("%02x", t[:])
|
||||
}
|
||||
|
||||
func (s SpanID) String() string {
|
||||
return fmt.Sprintf("%02x", s[:])
|
||||
}
|
||||
|
||||
// Annotation represents a text annotation with a set of attributes and a timestamp.
|
||||
type Annotation struct {
|
||||
Time time.Time
|
||||
Message string
|
||||
Attributes map[string]interface{}
|
||||
}
|
||||
|
||||
// Attribute represents a key-value pair on a span, link or annotation.
|
||||
// Construct with one of: BoolAttribute, Int64Attribute, or StringAttribute.
|
||||
type Attribute struct {
|
||||
key string
|
||||
value interface{}
|
||||
}
|
||||
|
||||
// BoolAttribute returns a bool-valued attribute.
|
||||
func BoolAttribute(key string, value bool) Attribute {
|
||||
return Attribute{key: key, value: value}
|
||||
}
|
||||
|
||||
// Int64Attribute returns an int64-valued attribute.
|
||||
func Int64Attribute(key string, value int64) Attribute {
|
||||
return Attribute{key: key, value: value}
|
||||
}
|
||||
|
||||
// StringAttribute returns a string-valued attribute.
|
||||
func StringAttribute(key string, value string) Attribute {
|
||||
return Attribute{key: key, value: value}
|
||||
}
|
||||
|
||||
// LinkType specifies the relationship between the span that had the link
|
||||
// added, and the linked span.
|
||||
type LinkType int32
|
||||
|
||||
// LinkType values.
|
||||
const (
|
||||
LinkTypeUnspecified LinkType = iota // The relationship of the two spans is unknown.
|
||||
LinkTypeChild // The current span is a child of the linked span.
|
||||
LinkTypeParent // The current span is the parent of the linked span.
|
||||
)
|
||||
|
||||
// Link represents a reference from one span to another span.
|
||||
type Link struct {
|
||||
TraceID TraceID
|
||||
SpanID SpanID
|
||||
Type LinkType
|
||||
// Attributes is a set of attributes on the link.
|
||||
Attributes map[string]interface{}
|
||||
}
|
||||
|
||||
// MessageEventType specifies the type of message event.
|
||||
type MessageEventType int32
|
||||
|
||||
// MessageEventType values.
|
||||
const (
|
||||
MessageEventTypeUnspecified MessageEventType = iota // Unknown event type.
|
||||
MessageEventTypeSent // Indicates a sent RPC message.
|
||||
MessageEventTypeRecv // Indicates a received RPC message.
|
||||
)
|
||||
|
||||
// MessageEvent represents an event describing a message sent or received on the network.
|
||||
type MessageEvent struct {
|
||||
Time time.Time
|
||||
EventType MessageEventType
|
||||
MessageID int64
|
||||
UncompressedByteSize int64
|
||||
CompressedByteSize int64
|
||||
}
|
||||
|
||||
// Status is the status of a Span.
|
||||
type Status struct {
|
||||
// Code is a status code. Zero indicates success.
|
||||
//
|
||||
// If Code will be propagated to Google APIs, it ideally should be a value from
|
||||
// https://github.com/googleapis/googleapis/blob/master/google/rpc/code.proto .
|
||||
Code int32
|
||||
Message string
|
||||
}
|
||||
|
|
@ -0,0 +1,55 @@
|
|||
// Copyright 2017, OpenCensus Authors
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
/*
|
||||
Package trace contains types for representing trace information, and
|
||||
functions for global configuration of tracing.
|
||||
|
||||
The following assumes a basic familiarity with OpenCensus concepts.
|
||||
See http://opencensus.io.
|
||||
|
||||
|
||||
Enabling Tracing for a Program
|
||||
|
||||
To use OpenCensus tracing, register at least one Exporter. You can use
|
||||
one of the provided exporters or write your own.
|
||||
|
||||
trace.RegisterExporter(anExporter)
|
||||
|
||||
By default, traces will be sampled relatively rarely. To change the sampling
|
||||
frequency for your entire program, call SetDefaultSampler. Use a ProbabilitySampler
|
||||
to sample a subset of traces, or use AlwaysSample to collect a trace on every run:
|
||||
|
||||
trace.SetDefaultSampler(trace.AlwaysSample())
|
||||
|
||||
|
||||
Adding Spans to a Trace
|
||||
|
||||
A trace consists of a tree of spans. In Go, the current span is carried in a
|
||||
context.Context.
|
||||
|
||||
It is common to want to capture all the activity of a function call in a span. For
|
||||
this to work, the function must take a context.Context as a parameter. Add these two
|
||||
lines to the top of the function:
|
||||
|
||||
ctx, span := trace.StartSpan(ctx, "your choice of name")
|
||||
defer span.End()
|
||||
|
||||
StartSpan will create a new top-level span if the context
|
||||
doesn't contain another span, otherwise it will create a child span.
|
||||
|
||||
As a suggestion, use the fully-qualified function name as the span name, e.g.
|
||||
"github.com/me/mypackage.Run".
|
||||
*/
|
||||
package trace // import "go.opencensus.io/trace"
|
||||
|
|
@ -0,0 +1,74 @@
|
|||
// Copyright 2017, OpenCensus Authors
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package trace
|
||||
|
||||
import (
|
||||
"sync"
|
||||
"time"
|
||||
)
|
||||
|
||||
// Exporter is a type for functions that receive sampled trace spans.
|
||||
//
|
||||
// The ExportSpan method should be safe for concurrent use and should return
|
||||
// quickly; if an Exporter takes a significant amount of time to process a
|
||||
// SpanData, that work should be done on another goroutine.
|
||||
//
|
||||
// The SpanData should not be modified, but a pointer to it can be kept.
|
||||
type Exporter interface {
|
||||
ExportSpan(s *SpanData)
|
||||
}
|
||||
|
||||
var (
|
||||
exportersMu sync.Mutex
|
||||
exporters map[Exporter]struct{}
|
||||
)
|
||||
|
||||
// RegisterExporter adds to the list of Exporters that will receive sampled
|
||||
// trace spans.
|
||||
func RegisterExporter(e Exporter) {
|
||||
exportersMu.Lock()
|
||||
if exporters == nil {
|
||||
exporters = make(map[Exporter]struct{})
|
||||
}
|
||||
exporters[e] = struct{}{}
|
||||
exportersMu.Unlock()
|
||||
}
|
||||
|
||||
// UnregisterExporter removes from the list of Exporters the Exporter that was
|
||||
// registered with the given name.
|
||||
func UnregisterExporter(e Exporter) {
|
||||
exportersMu.Lock()
|
||||
delete(exporters, e)
|
||||
exportersMu.Unlock()
|
||||
}
|
||||
|
||||
// SpanData contains all the information collected by a Span.
|
||||
type SpanData struct {
|
||||
SpanContext
|
||||
ParentSpanID SpanID
|
||||
SpanKind int
|
||||
Name string
|
||||
StartTime time.Time
|
||||
// The wall clock time of EndTime will be adjusted to always be offset
|
||||
// from StartTime by the duration of the span.
|
||||
EndTime time.Time
|
||||
// The values of Attributes each have type string, bool, or int64.
|
||||
Attributes map[string]interface{}
|
||||
Annotations []Annotation
|
||||
MessageEvents []MessageEvent
|
||||
Status
|
||||
Links []Link
|
||||
HasRemoteParent bool
|
||||
}
|
||||
|
|
@ -0,0 +1,108 @@
|
|||
// Copyright 2017, OpenCensus Authors
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
// Package propagation implements the binary trace context format.
|
||||
package propagation
|
||||
|
||||
// TODO: link to external spec document.
|
||||
|
||||
// BinaryFormat format:
|
||||
//
|
||||
// Binary value: <version_id><version_format>
|
||||
// version_id: 1 byte representing the version id.
|
||||
//
|
||||
// For version_id = 0:
|
||||
//
|
||||
// version_format: <field><field>
|
||||
// field_format: <field_id><field_format>
|
||||
//
|
||||
// Fields:
|
||||
//
|
||||
// TraceId: (field_id = 0, len = 16, default = "0000000000000000") - 16-byte array representing the trace_id.
|
||||
// SpanId: (field_id = 1, len = 8, default = "00000000") - 8-byte array representing the span_id.
|
||||
// TraceOptions: (field_id = 2, len = 1, default = "0") - 1-byte array representing the trace_options.
|
||||
//
|
||||
// Fields MUST be encoded using the field id order (smaller to higher).
|
||||
//
|
||||
// Valid value example:
|
||||
//
|
||||
// {0, 0, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 1, 97,
|
||||
// 98, 99, 100, 101, 102, 103, 104, 2, 1}
|
||||
//
|
||||
// version_id = 0;
|
||||
// trace_id = {64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79}
|
||||
// span_id = {97, 98, 99, 100, 101, 102, 103, 104};
|
||||
// trace_options = {1};
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"go.opencensus.io/trace"
|
||||
)
|
||||
|
||||
// Binary returns the binary format representation of a SpanContext.
|
||||
//
|
||||
// If sc is the zero value, Binary returns nil.
|
||||
func Binary(sc trace.SpanContext) []byte {
|
||||
if sc == (trace.SpanContext{}) {
|
||||
return nil
|
||||
}
|
||||
var b [29]byte
|
||||
copy(b[2:18], sc.TraceID[:])
|
||||
b[18] = 1
|
||||
copy(b[19:27], sc.SpanID[:])
|
||||
b[27] = 2
|
||||
b[28] = uint8(sc.TraceOptions)
|
||||
return b[:]
|
||||
}
|
||||
|
||||
// FromBinary returns the SpanContext represented by b.
|
||||
//
|
||||
// If b has an unsupported version ID or contains no TraceID, FromBinary
|
||||
// returns with ok==false.
|
||||
func FromBinary(b []byte) (sc trace.SpanContext, ok bool) {
|
||||
if len(b) == 0 || b[0] != 0 {
|
||||
return trace.SpanContext{}, false
|
||||
}
|
||||
b = b[1:]
|
||||
if len(b) >= 17 && b[0] == 0 {
|
||||
copy(sc.TraceID[:], b[1:17])
|
||||
b = b[17:]
|
||||
} else {
|
||||
return trace.SpanContext{}, false
|
||||
}
|
||||
if len(b) >= 9 && b[0] == 1 {
|
||||
copy(sc.SpanID[:], b[1:9])
|
||||
b = b[9:]
|
||||
}
|
||||
if len(b) >= 2 && b[0] == 2 {
|
||||
sc.TraceOptions = trace.TraceOptions(b[1])
|
||||
}
|
||||
return sc, true
|
||||
}
|
||||
|
||||
// HTTPFormat implementations propagate span contexts
|
||||
// in HTTP requests.
|
||||
//
|
||||
// SpanContextFromRequest extracts a span context from incoming
|
||||
// requests.
|
||||
//
|
||||
// SpanContextToRequest modifies the given request to include the given
|
||||
// span context.
|
||||
type HTTPFormat interface {
|
||||
SpanContextFromRequest(req *http.Request) (sc trace.SpanContext, ok bool)
|
||||
SpanContextToRequest(sc trace.SpanContext, req *http.Request)
|
||||
}
|
||||
|
||||
// TODO(jbd): Find a more representative but short name for HTTPFormat.
|
||||
|
|
@ -0,0 +1,90 @@
|
|||
// Copyright 2017, OpenCensus Authors
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package trace
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
)
|
||||
|
||||
const defaultSamplingProbability = 1e-4
|
||||
|
||||
func init() {
|
||||
defaultSampler = ProbabilitySampler(defaultSamplingProbability)
|
||||
}
|
||||
|
||||
func newDefaultSampler() Sampler {
|
||||
return ProbabilitySampler(defaultSamplingProbability)
|
||||
}
|
||||
|
||||
// SetDefaultSampler sets the default sampler used when creating new spans.
|
||||
func SetDefaultSampler(sampler Sampler) {
|
||||
if sampler == nil {
|
||||
sampler = newDefaultSampler()
|
||||
}
|
||||
mu.Lock()
|
||||
defaultSampler = sampler
|
||||
mu.Unlock()
|
||||
}
|
||||
|
||||
// Sampler decides whether a trace should be sampled and exported.
|
||||
type Sampler func(SamplingParameters) SamplingDecision
|
||||
|
||||
// SamplingParameters contains the values passed to a Sampler.
|
||||
type SamplingParameters struct {
|
||||
ParentContext SpanContext
|
||||
TraceID TraceID
|
||||
SpanID SpanID
|
||||
Name string
|
||||
HasRemoteParent bool
|
||||
}
|
||||
|
||||
// SamplingDecision is the value returned by a Sampler.
|
||||
type SamplingDecision struct {
|
||||
Sample bool
|
||||
}
|
||||
|
||||
// ProbabilitySampler returns a Sampler that samples a given fraction of traces.
|
||||
//
|
||||
// It also samples spans whose parents are sampled.
|
||||
func ProbabilitySampler(fraction float64) Sampler {
|
||||
if !(fraction >= 0) {
|
||||
fraction = 0
|
||||
} else if fraction >= 1 {
|
||||
return AlwaysSample()
|
||||
}
|
||||
|
||||
traceIDUpperBound := uint64(fraction * (1 << 63))
|
||||
return Sampler(func(p SamplingParameters) SamplingDecision {
|
||||
if p.ParentContext.IsSampled() {
|
||||
return SamplingDecision{Sample: true}
|
||||
}
|
||||
x := binary.BigEndian.Uint64(p.TraceID[0:8]) >> 1
|
||||
return SamplingDecision{Sample: x < traceIDUpperBound}
|
||||
})
|
||||
}
|
||||
|
||||
// AlwaysSample returns a Sampler that samples every trace.
|
||||
func AlwaysSample() Sampler {
|
||||
return func(p SamplingParameters) SamplingDecision {
|
||||
return SamplingDecision{Sample: true}
|
||||
}
|
||||
}
|
||||
|
||||
// NeverSample returns a Sampler that samples no traces.
|
||||
func NeverSample() Sampler {
|
||||
return func(p SamplingParameters) SamplingDecision {
|
||||
return SamplingDecision{Sample: false}
|
||||
}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue