Commit 390fa616 by 流生

velero plugin for alibabacloud

parents

Too many changes to show.

To preserve performance only 1000 of 1000+ files are displayed.

---
name: Bug report
about: Tell us about a problem you are experiencing
---
**What steps did you take and what happened:**
[A clear and concise description of what the bug is.]
**What did you expect to happen:**
**Anything else you would like to add:**
[Miscellaneous information that will assist in solving the issue.]
**Environment:**
- Velero version: (use `ark version`):
- Kubernetes version: (use `kubectl version`):
- Kubernetes installer & version:
- Cloud provider or hardware configuration:
- OS (e.g. from `/etc/os-release`):
---
name: Feature enhancement request
about: Suggest an idea for this project
---
**Describe the solution you'd like**
[A clear and concise description of what you want to happen.]
**Anything else you would like to add:**
[Miscellaneous information that will assist in solving the issue.]
**Environment:**
- Velero version: (use `ark version`):
- Kubernetes version: (use `kubectl version`):
- Kubernetes installer & version:
- Cloud provider or hardware configuration:
- OS (e.g. from `/etc/os-release`):
.go
_output
.idea
velero-*
language: go
go:
- 1.9.x
script: make all-ci
# Velero Community Code of Conduct
## Contributor Code of Conduct
As contributors and maintainers of this project, and in the interest of fostering
an open and welcoming community, we pledge to respect all people who contribute
through reporting issues, posting feature requests, updating documentation,
submitting pull requests or patches, and other activities.
We are committed to making participation in this project a harassment-free experience for
everyone, regardless of level of experience, gender, gender identity and expression,
sexual orientation, disability, personal appearance, body size, race, ethnicity, age,
religion, or nationality.
Examples of unacceptable behavior by participants include:
* The use of sexualized language or imagery
* Personal attacks
* Trolling or insulting/derogatory comments
* Public or private harassment
* Publishing other's private information, such as physical or electronic addresses,
without explicit permission
* Other unethical or unprofessional conduct.
Project maintainers have the right and responsibility to remove, edit, or reject
comments, commits, code, wiki edits, issues, and other contributions that are not
aligned to this Code of Conduct. By adopting this Code of Conduct, project maintainers
commit themselves to fairly and consistently applying these principles to every aspect
of managing this project. Project maintainers who do not follow or enforce the Code of
Conduct may be permanently removed from the project team.
This code of conduct applies both within project spaces and in public spaces
when an individual is representing the project or its community.
Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by contacting the project maintainer(s).
This Code of Conduct is adapted from the [CNCF Code of Conduct](https://github.com/cncf/foundation/blob/master/code-of-conduct.md) and [Contributor Covenant](http://contributor-covenant.org/version/1/2/0/), version 1.2.0.
# Contributing
## DCO Sign off
All authors to the project retain copyright to their work. However, to ensure
that they are only submitting work that they have rights to, we are requiring
everyone to acknowledge this by signing their work.
Any copyright notices in this repo should specify the authors as "the Heptio Ark project contributors".
To sign your work, just add a line like this at the end of your commit message:
```
Signed-off-by: Joe Beda <joe@heptio.com>
```
This can easily be done with the `--signoff` option to `git commit`.
By doing this you state that you can certify the following (from https://developercertificate.org/):
```
Developer Certificate of Origin
Version 1.1
Copyright (C) 2004, 2006 The Linux Foundation and its contributors.
1 Letterman Drive
Suite D4700
San Francisco, CA, 94129
Everyone is permitted to copy and distribute verbatim copies of this
license document, but changing it is not allowed.
Developer's Certificate of Origin 1.1
By making a contribution to this project, I certify that:
(a) The contribution was created in whole or in part by me and I
have the right to submit it under the open source license
indicated in the file; or
(b) The contribution is based upon previous work that, to the best
of my knowledge, is covered under an appropriate open source
license and I have the right under that license to submit that
work with modifications, whether created in whole or in part
by me, under the same open source license (unless I am
permitted to submit under a different license), as indicated
in the file; or
(c) The contribution was provided directly to me by some other
person who certified (a), (b) or (c) and I have not modified
it.
(d) I understand and agree that this project and the contribution
are public and that a record of the contribution (including all
personal information I submit with it, including my sign-off) is
maintained indefinitely and may be redistributed consistent with
this project or the open source license(s) involved.
```
# Copyright 2017, 2019 the Velero contributors.
#
# 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 debian:stretch-slim
RUN mkdir /plugins
ADD velero-* /plugins/
USER nobody:nobody
ENTRYPOINT ["/bin/bash", "-c", "cp /plugins/* /target/."]
This diff is collapsed. Click to expand it.
# Gopkg.toml example
#
# Refer to https://golang.github.io/dep/docs/Gopkg.toml.html
# for detailed Gopkg.toml documentation.
#
# required = ["github.com/user/thing/cmd/thing"]
# ignored = ["github.com/user/project/pkgX", "bitbucket.org/user/project/pkgA/pkgY"]
#
# [[constraint]]
# name = "github.com/user/project"
# version = "1.0.0"
#
# [[constraint]]
# name = "github.com/user/project2"
# branch = "dev"
# source = "github.com/myfork/project2"
#
# [[override]]
# name = "github.com/x/y"
# version = "2.4.0"
#
# [prune]
# non-go = false
# go-tests = true
# unused-packages = true
[[constraint]]
name = "github.com/heptio/velero"
branch = "master"
[[constraint]]
name = "github.com/pkg/errors"
version = "0.8.1"
[[constraint]]
name = "github.com/sirupsen/logrus"
version = "1.4.0"
[[constraint]]
name = "k8s.io/api"
version = "kubernetes-1.14.0"
[[constraint]]
name = "k8s.io/apimachinery"
version = "kubernetes-1.14.0"
[prune]
go-tests = true
unused-packages = true
non-go = true
[[constraint]]
name = "github.com/stretchr/testify"
version = "~1.2.2"
[[constraint]]
name = "github.com/aliyun/aliyun-oss-go-sdk"
version = "1.9.6"
[[constraint]]
name = "github.com/aliyun/alibaba-cloud-sdk-go"
version = "1.27.7"
\ No newline at end of file
This diff is collapsed. Click to expand it.
# Copyright 2017, 2019 the Velero contributors.
#
# 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.
# The binary to build (just the basename).
BIN ?= $(wildcard velero-*)
# This repo's root import path (under GOPATH).
PKG := github.com/heptio/velero-plugin-example
BUILD_IMAGE ?= golang:1.12-stretch
IMAGE ?= gcr.io/heptio-images/velero-plugin-example
# Which architecture to build - see $(ALL_ARCH) for options.
# if the 'local' rule is being run, detect the ARCH from 'go env'
# if it wasn't specified by the caller.
local : ARCH ?= $(shell go env GOOS)-$(shell go env GOARCH)
ARCH ?= linux-amd64
platform_temp = $(subst -, ,$(ARCH))
GOOS = $(word 1, $(platform_temp))
GOARCH = $(word 2, $(platform_temp))
all: $(addprefix build-, $(BIN))
build-%:
$(MAKE) --no-print-directory BIN=$* build
local: build-dirs
GOOS=$(GOOS) \
GOARCH=$(GOARCH) \
PKG=$(PKG) \
BIN=$(BIN) \
OUTPUT_DIR=$$(pwd)/_output/bin/$(GOOS)/$(GOARCH) \
./hack/build.sh
build: _output/bin/$(GOOS)/$(GOARCH)/$(BIN)
_output/bin/$(GOOS)/$(GOARCH)/$(BIN): build-dirs
@echo "building: $@"
$(MAKE) shell CMD="-c '\
GOOS=$(GOOS) \
GOARCH=$(GOARCH) \
PKG=$(PKG) \
BIN=$(BIN) \
OUTPUT_DIR=/output/$(GOOS)/$(GOARCH) \
./hack/build.sh'"
TTY := $(shell tty -s && echo "-t")
shell: build-dirs
@echo "running docker: $@"
@docker run \
-e GOFLAGS \
-i $(TTY) \
--rm \
-u $$(id -u):$$(id -g) \
-v $$(pwd)/.go/pkg:/go/pkg \
-v $$(pwd)/.go/src:/go/src \
-v $$(pwd)/.go/std:/go/std \
-v $$(pwd):/go/src/$(PKG) \
-v $$(pwd)/.go/std/$(GOOS)_$(GOARCH):/usr/local/go/pkg/$(GOOS)_$(GOARCH)_static \
-v "$$(pwd)/.go/go-build:/.cache/go-build:delegated" \
-e CGO_ENABLED=0 \
-w /go/src/$(PKG) \
$(BUILD_IMAGE) \
go build -installsuffix "static" -i -v -o _output/bin/$(GOOS)/$(GOARCH)/$(BIN) ./$(BIN)
build-dirs:
@mkdir -p _output/bin/$(GOOS)/$(GOARCH)
@mkdir -p .go/src/$(PKG) .go/pkg .go/bin .go/std/$(GOOS)/$(GOARCH) .go/go-build
container: all
cp Dockerfile _output/bin/$(GOOS)/$(GOARCH)/Dockerfile
docker build -t $(IMAGE) -f _output/bin/$(GOOS)/$(GOARCH)/Dockerfile _output/bin/$(GOOS)/$(GOARCH)
all-ci: $(addprefix ci-, $(BIN))
ci-%:
$(MAKE) --no-print-directory BIN=$* ci
ci:
mkdir -p _output
CGO_ENABLED=0 go build -v -o _output/bin/$(GOOS)/$(GOARCH)/$(BIN) ./$(BIN)
clean:
@echo "cleaning"
rm -rf .go _output
## Overview
[![GoReportCard Widget]][GoReportCardResult]
Velero is a utility to back up and restore your Kubernetes resource and persistent volumes.
To do backup/restore on Alibaba Cloud through Velero utility, you need to install and configure velero and velero-plugin for alibabacloud.
## Run velero on AlibabaCloud
To set up Velero on AlibabaCloud, you:
* Download an official release of Velero
* Create your OSS bucket
* Create an RAM user for Velero
* Install the velero and velero-plugin for alibabacloud
## Download Velero
1. Download the [latest official release's](https://github.com/heptio/velero/releases) tarball for your client platform.
_We strongly recommend that you use an [official release](https://github.com/heptio/velero/releases) of
Velero. The tarballs for each release contain the `velero` command-line client. The code in the master branch
of the Velero repository is under active development and is not guaranteed to be stable!_
1. Extract the tarball:
```bash
tar -xvf <RELEASE-TARBALL-NAME>.tar.gz -C /dir/to/extract/to
```
We'll refer to the directory you extracted to as the "Velero directory" in subsequent steps.
2. Move the `velero` binary from the Velero directory to somewhere in your PATH.
## Create OSS bucket
Velero requires an object storage bucket to store backups in, preferrably unique to a single Kubernetes cluster. Create an OSS bucket, replacing placeholders appropriately:
```bash
BUCKET=<YOUR_BUCKET>
REGION=<YOUR_REGION>
ossutil mb oss://$BUCKET \
--storage-class Standard \
--acl=private
```
## Create RAM user
For more information, see [the AlibabaCloud documentation on RAM users guides][14].
1. Create the RAM user:
Follow [the AlibabaCloud documentation on RAM users][22].
> If you'll be using Velero to backup multiple clusters with multiple OSS buckets, it may be desirable to create a unique username per cluster rather than the default `velero`.
2. Attach policies to give `velero` the necessary permissions:
```bash
{
"Version": "1",
"Statement": [
{
"Action": [
"ecs:DescribeSnapshots",
"ecs:CreateSnapshot",
"ecs:DeleteSnapshot",
"ecs:DescribeDisks",
"ecs:CreateDisk",
"ecs:Addtags",
"oss:PutObject",
"oss:GetObject",
"oss:DeleteObject",
"oss:GetBucket"
],
"Resource": [
"*"
],
"Effect": "Allow"
}
]
}
```
3. Create an access key for the user:
Follow [the AlibabaCloud documentation on create AK][24].
4. Create a Velero-specific credentials file (`credentials-velero`) in your `install` directory:
```
ALIBABA_CLOUD_ACCESS_KEY_ID=<ALIBABA_CLOUD_ACCESS_KEY_ID>
ALIBABA_CLOUD_ACCESS_KEY_SECRET=<ALIBABA_CLOUD_ACCESS_KEY_SECRET>
ALIBABA_CLOUD_OSS_ENDPOINT=<ALIBABA_CLOUD_OSS_ENDPOINT>
```
where the access key id and secret are the values get from the step 3 and the oss endpoint is the value oss-$REGION.aliyuncs.com.
## Install velero and velero-plugin for alibabacloud
1. Set some environment variables
```bash
BUCKET=<YOUR_BUCKET>
REGION=<YOUR_REGION>
```
2. Create a namespace velero and a secret cloud-credentials
Run the following command to create a namespace named `velero`
`kubectl create namespace velero`
Run the following command to create a secret named `cloud-credentials`
`kubectl create secret generic cloud-credentials --namespace velero --from-file cloud=install/credentials-velero`
3. Create and run velero and velero-plugin for alibabacloud
Run the following command to create and run velero and velero-plugin for alibabacloud
```
sed -i "s#<BUCKET>#$BUCKET#" install/velero.yaml
sed -i "s#<REGION>#$REGION#" install/velero.yaml
kubectl apply -f install/velero.yaml
```
## Installing the nginx example (optional)
1. nginx example without persistent volumes
Run the following command to create a nginx example without persistent volumes:
`kubectl apply -f examples/base.yaml`
Create a backup:
`velero backup create nginx-backup --include-namespaces nginx-example`
Destroy the nginx example:
`kubectl delete namespaces nginx-example`
Create a restore from nginx-backup:
`velero restore create --from-backup nginx-backup`
2. nginx example with persistent volumes
Run the following command to create a nginx example with persistent volumes:
```
VOLUME_ID=<YOUR_ECS_DISK_ID>
sed -i "s#{PV_NAME}#$VOLUME_ID#" examples/with-pv.yaml`
kubectl apply -f examples/with-pv.yaml
```
Create a backup:
`velero backup create nginx-backup-volume --snapshot-volumes --include-namespaces nginx-example`
Destroy the nginx example:
```
kubectl delete namespaces nginx-example
kubectl delete pv $(kubectl get pv|grep nginx-example/nginx-logs|cut -d ' ' -f1)
```
Create a restore from nginx-backup-volume:
`velero restore create --from-backup nginx-backup-volume --restore-volumes`
[14]: https://www.alibabacloud.com/help/doc-detail/28645.htm
[22]: https://www.alibabacloud.com/help/doc-detail/93720.htm
[23]: https://www.alibabacloud.com/help/doc-detail/50452.htm
[24]: https://www.alibabacloud.com/help/doc-detail/53045.htm
[GoReportCard Widget]: https://goreportcard.com/badge/github.com/AliyunContainerService/velero-plugin
[GoReportCardResult]: https://goreportcard.com/report/github.com/AliyunContainerService/velero-plugin
# Copyright 2017 the Velero contributors.
#
# 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.
---
apiVersion: v1
kind: Namespace
metadata:
name: nginx-example
labels:
app: nginx
---
apiVersion: apps/v1beta1
kind: Deployment
metadata:
name: nginx-deployment
namespace: nginx-example
spec:
replicas: 2
template:
metadata:
labels:
app: nginx
spec:
containers:
- image: nginx:1.7.9
name: nginx
ports:
- containerPort: 80
---
apiVersion: v1
kind: Service
metadata:
labels:
app: nginx
name: my-nginx
namespace: nginx-example
spec:
ports:
- port: 80
targetPort: 80
selector:
app: nginx
type: LoadBalancer
\ No newline at end of file
# Copyright 2017 the Velero contributors.
#
# 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.
---
apiVersion: v1
kind: Namespace
metadata:
name: nginx-example
labels:
app: nginx
---
apiVersion: v1
kind: PersistentVolume
metadata:
annotations:
pv.kubernetes.io/bound-by-controller: "yes"
labels:
alicloud-pvname: <PV_NAME>
name: <PV_NAME>
namespace: nginx-example
spec:
accessModes:
- ReadWriteOnce
capacity:
storage: 20Gi
flexVolume:
driver: alicloud/disk
fsType: ext4
options:
volumeId: <PV_NAME>
storageClassName: disk
---
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
annotations:
pv.kubernetes.io/bind-completed: "yes"
pv.kubernetes.io/bound-by-controller: "yes"
name: nginx-logs
namespace: nginx-example
spec:
accessModes:
- ReadWriteOnce
resources:
requests:
storage: 20Gi
selector:
matchLabels:
alicloud-pvname: <PV_NAME>
storageClassName: disk
volumeName: <PV_NAME>
---
apiVersion: apps/v1beta1
kind: Deployment
metadata:
name: nginx-deployment
namespace: nginx-example
spec:
replicas: 1
template:
metadata:
labels:
app: nginx
annotations:
pre.hook.backup.velero.io/container: fsfreeze
pre.hook.backup.velero.io/command: '["/sbin/fsfreeze", "--freeze", "/var/log/nginx"]'
post.hook.backup.velero.io/container: fsfreeze
post.hook.backup.velero.io/command: '["/sbin/fsfreeze", "--unfreeze", "/var/log/nginx"]'
spec:
volumes:
- name: nginx-logs
persistentVolumeClaim:
claimName: nginx-logs
containers:
- image: nginx:1.7.9
name: nginx
ports:
- containerPort: 80
volumeMounts:
- mountPath: "/var/log/nginx"
name: nginx-logs
readOnly: false
# sync from gcr.io/heptio-images/fsfreeze-pause:latest
- image: registry.cn-hangzhou.aliyuncs.com/acs/fsfreeze-pause:latest
name: fsfreeze
securityContext:
privileged: true
volumeMounts:
- mountPath: "/var/log/nginx"
name: nginx-logs
readOnly: false
---
apiVersion: v1
kind: Service
metadata:
labels:
app: nginx
name: my-nginx
namespace: nginx-example
spec:
ports:
- port: 80
targetPort: 80
selector:
app: nginx
type: LoadBalancer
\ No newline at end of file
#!/bin/bash
# Copyright 2016 The Kubernetes 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.
set -o errexit
set -o nounset
set -o pipefail
if [ -z "${PKG}" ]; then
echo "PKG must be set"
exit 1
fi
if [ -z "${BIN}" ]; then
echo "BIN must be set"
exit 1
fi
if [ -z "${GOOS}" ]; then
echo "GOOS must be set"
exit 1
fi
if [ -z "${GOARCH}" ]; then
echo "GOARCH must be set"
exit 1
fi
export CGO_ENABLED=0
if [[ -z "${OUTPUT_DIR:-}" ]]; then
OUTPUT_DIR=.
fi
OUTPUT=${OUTPUT_DIR}/${BIN}
if [[ "${GOOS}" = "windows" ]]; then
OUTPUT="${OUTPUT}.exe"
fi
go build \
-o ${OUTPUT} \
-installsuffix "static" \
${PKG}/${BIN}
ALIBABA_CLOUD_ACCESS_KEY_ID=<ALIBABA_CLOUD_ACCESS_KEY_ID>
ALIBABA_CLOUD_ACCESS_KEY_SECRET=<ALIBABA_CLOUD_ACCESS_KEY_SECRET>
ALIBABA_CLOUD_OSS_ENDPOINT=<ALIBABA_CLOUD_OSS_ENDPOINT>
---
apiVersion: apiextensions.k8s.io/v1beta1
kind: CustomResourceDefinition
metadata:
labels:
component: velero
name: backups.velero.io
spec:
group: velero.io
names:
kind: Backup
listKind: BackupList
plural: backups
singular: backup
scope: Namespaced
version: v1
versions:
- name: v1
served: true
storage: true
---
apiVersion: apiextensions.k8s.io/v1beta1
kind: CustomResourceDefinition
metadata:
labels:
component: velero
name: backupstoragelocations.velero.io
spec:
group: velero.io
names:
kind: BackupStorageLocation
listKind: BackupStorageLocationList
plural: backupstoragelocations
singular: backupstoragelocation
scope: Namespaced
version: v1
versions:
- name: v1
served: true
storage: true
---
apiVersion: apiextensions.k8s.io/v1beta1
kind: CustomResourceDefinition
metadata:
labels:
component: velero
name: deletebackuprequests.velero.io
spec:
group: velero.io
names:
kind: DeleteBackupRequest
listKind: DeleteBackupRequestList
plural: deletebackuprequests
singular: deletebackuprequest
scope: Namespaced
version: v1
versions:
- name: v1
served: true
storage: true
---
apiVersion: apiextensions.k8s.io/v1beta1
kind: CustomResourceDefinition
metadata:
labels:
component: velero
name: downloadrequests.velero.io
spec:
group: velero.io
names:
kind: DownloadRequest
listKind: DownloadRequestList
plural: downloadrequests
singular: downloadrequest
scope: Namespaced
version: v1
versions:
- name: v1
served: true
storage: true
---
apiVersion: apiextensions.k8s.io/v1beta1
kind: CustomResourceDefinition
metadata:
labels:
component: velero
name: podvolumebackups.velero.io
spec:
group: velero.io
names:
kind: PodVolumeBackup
listKind: PodVolumeBackupList
plural: podvolumebackups
singular: podvolumebackup
scope: Namespaced
version: v1
versions:
- name: v1
served: true
storage: true
---
apiVersion: apiextensions.k8s.io/v1beta1
kind: CustomResourceDefinition
metadata:
labels:
component: velero
name: podvolumerestores.velero.io
spec:
group: velero.io
names:
kind: PodVolumeRestore
listKind: PodVolumeRestoreList
plural: podvolumerestores
singular: podvolumerestore
scope: Namespaced
version: v1
versions:
- name: v1
served: true
storage: true
---
apiVersion: apiextensions.k8s.io/v1beta1
kind: CustomResourceDefinition
metadata:
labels:
component: velero
name: resticrepositories.velero.io
spec:
group: velero.io
names:
kind: ResticRepository
listKind: ResticRepositoryList
plural: resticrepositories
singular: resticrepository
scope: Namespaced
version: v1
versions:
- name: v1
served: true
storage: true
---
apiVersion: apiextensions.k8s.io/v1beta1
kind: CustomResourceDefinition
metadata:
labels:
component: velero
name: restores.velero.io
spec:
group: velero.io
names:
kind: Restore
listKind: RestoreList
plural: restores
singular: restore
scope: Namespaced
version: v1
versions:
- name: v1
served: true
storage: true
---
apiVersion: apiextensions.k8s.io/v1beta1
kind: CustomResourceDefinition
metadata:
labels:
component: velero
name: schedules.velero.io
spec:
group: velero.io
names:
kind: Schedule
listKind: ScheduleList
plural: schedules
singular: schedule
scope: Namespaced
version: v1
versions:
- name: v1
served: true
storage: true
---
apiVersion: apiextensions.k8s.io/v1beta1
kind: CustomResourceDefinition
metadata:
labels:
component: velero
name: serverstatusrequests.velero.io
spec:
group: velero.io
names:
kind: ServerStatusRequest
listKind: ServerStatusRequestList
plural: serverstatusrequests
singular: serverstatusrequest
scope: Namespaced
version: v1
versions:
- name: v1
served: true
storage: true
---
apiVersion: apiextensions.k8s.io/v1beta1
kind: CustomResourceDefinition
metadata:
labels:
component: velero
name: volumesnapshotlocations.velero.io
spec:
group: velero.io
names:
kind: VolumeSnapshotLocation
listKind: VolumeSnapshotLocationList
plural: volumesnapshotlocations
singular: volumesnapshotlocation
scope: Namespaced
version: v1
versions:
- name: v1
served: true
storage: true
---
apiVersion: v1
kind: ServiceAccount
metadata:
namespace: velero
name: velero
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
labels:
component: velero
name: velero
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: ClusterRole
name: cluster-admin
subjects:
- kind: ServiceAccount
name: velero
namespace: velero
---
apiVersion: velero.io/v1
kind: BackupStorageLocation
metadata:
labels:
component: velero
name: default
namespace: velero
spec:
config: {}
objectStorage:
bucket: <BUCKET>
prefix: ""
provider: alibabacloud
---
apiVersion: velero.io/v1
kind: VolumeSnapshotLocation
metadata:
labels:
component: velero
name: default
namespace: velero
spec:
config:
region: <REGION>
provider: alibabacloud
---
apiVersion: extensions/v1beta1
kind: Deployment
metadata:
name: velero
namespace: velero
spec:
replicas: 1
selector:
matchLabels:
deploy: velero
template:
metadata:
annotations:
prometheus.io/path: /metrics
prometheus.io/port: "8085"
prometheus.io/scrape: "true"
labels:
component: velero
deploy: velero
spec:
serviceAccountName: velero
containers:
- name: velero
# sync from gcr.io/heptio-images/velero:latest
image: registry.cn-hangzhou.aliyuncs.com/acs/velero:latest
imagePullPolicy: IfNotPresent
command:
- /velero
args:
- server
- --default-volume-snapshot-locations=alibabacloud:default
env:
- name: VELERO_SCRATCH_DIR
value: /scratch
- name: ALIBABA_CLOUD_CREDENTIALS_FILE
value: /credentials/cloud
volumeMounts:
- mountPath: /plugins
name: plugins
- mountPath: /scratch
name: scratch
- mountPath: /credentials
name: cloud-credentials
initContainers:
- image: registry.cn-hangzhou.aliyuncs.com/acs/velero-plugin-alibabacloud:v1
imagePullPolicy: IfNotPresent
name: velero-plugin-alibabacloud
volumeMounts:
- mountPath: /target
name: plugins
volumes:
- emptyDir: {}
name: plugins
- emptyDir: {}
name: scratch
- name: cloud-credentials
secret:
secretName: cloud-credentials
/*
Copyright 2017, 2019 the Velero contributors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package main
import (
"github.com/AliyunContainerService/velero-plugin/pkg/alibabacloud"
veleroplugin "github.com/heptio/velero/pkg/plugin/framework"
"github.com/sirupsen/logrus"
)
func main() {
veleroplugin.NewServer().
RegisterObjectStore("velero.io/alibabacloud", newAlibabaCloudObjectStore).
RegisterVolumeSnapshotter("velero.io/alibabacloud", newAlibabaCloudVolumeSnapshotter).
Serve()
}
func newAlibabaCloudObjectStore(logger logrus.FieldLogger) (interface{}, error) {
return alibabacloud.NewObjectStore(logger), nil
}
func newAlibabaCloudVolumeSnapshotter(logger logrus.FieldLogger) (interface{}, error) {
return alibabacloud.NewVolumeSnapshotter(logger), nil
}
/*
Copyright 2017, 2019 the Velero contributors.
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 alibabacloud
import (
"io"
"os"
"time"
"github.com/aliyun/aliyun-oss-go-sdk/oss"
"github.com/joho/godotenv"
"github.com/pkg/errors"
"github.com/sirupsen/logrus"
"github.com/heptio/velero/pkg/cloudprovider"
)
const (
regionKey = "region"
)
type bucketGetter interface {
Bucket(bucket string) (ossBucket, error)
}
type ossBucket interface {
IsObjectExist(key string) (bool, error)
ListObjects(options ...oss.Option) (oss.ListObjectsResult, error)
PutObject(objectKey string, reader io.Reader, options ...oss.Option) error
GetObject(key string, options ...oss.Option) (io.ReadCloser, error)
DeleteObject(key string) error
SignURL(objectKey string, method oss.HTTPMethod, expiredInSec int64, options ...oss.Option) (string, error)
}
type ossBucketGetter struct {
client *oss.Client
}
func (getter *ossBucketGetter) Bucket(bucket string) (ossBucket, error) {
return getter.client.Bucket(bucket)
}
// ObjectStore represents an object storage entity
type ObjectStore struct {
log logrus.FieldLogger
client bucketGetter
encryptionKeyID string
privateKey []byte
}
// NewObjectStore init ObjectStore
func NewObjectStore(logger logrus.FieldLogger) *ObjectStore {
return &ObjectStore{log: logger}
}
func (o *ObjectStore) getBucket(bucket string) (ossBucket, error) {
bucketObj, err := o.client.Bucket(bucket)
if err != nil {
o.log.Errorf("failed to get OSS bucket: %v", err)
}
return bucketObj, err
}
// load environment vars from $ALIBABA_CLOUD_CREDENTIALS_FILE, if it exists
func loadEnv() error {
envFile := os.Getenv("ALIBABA_CLOUD_CREDENTIALS_FILE")
if envFile == "" {
return nil
}
if err := godotenv.Overload(envFile); err != nil {
return errors.Wrapf(err, "error loading environment from ALIBABA_CLOUD_CREDENTIALS_FILE (%s)", envFile)
}
return nil
}
// Init init oss client with os envs
func (o *ObjectStore) Init(config map[string]string) error {
if err := cloudprovider.ValidateObjectStoreConfigKeys(config); err != nil {
return err
}
if err := loadEnv(); err != nil {
return err
}
accessKeyID := os.Getenv("ALIBABA_CLOUD_ACCESS_KEY_ID")
accessKeySecret := os.Getenv("ALIBABA_CLOUD_ACCESS_KEY_SECRET")
stsToken := os.Getenv("ALIBABA_CLOUD_ACCESS_STS_TOKEN")
encryptionKeyID := os.Getenv("ALIBABA_CLOUD_ENCRYPTION_KEY_ID")
endpoint := os.Getenv("ALIBABA_CLOUD_OSS_ENDPOINT")
if len(accessKeyID) == 0 {
return errors.Errorf("ALIBABA_CLOUD_ACCESS_KEY_ID environment variable is not set")
}
if len(accessKeySecret) == 0 {
return errors.Errorf("ALIBABA_CLOUD_ACCESS_KEY_SECRET environment variable is not set")
}
if len(endpoint) == 0 {
// Set default endpoint
endpoint = "oss-cn-hangzhou.aliyuncs.com"
}
var client *oss.Client
var err error
if len(stsToken) == 0 {
client, err = oss.New(endpoint, accessKeyID, accessKeySecret)
} else {
client, err = oss.New(endpoint, accessKeyID, accessKeySecret, oss.SecurityToken(stsToken))
}
if err != nil {
return errors.Errorf("failed to create OSS client: %v", err.Error())
}
o.client = &ossBucketGetter{
client,
}
o.encryptionKeyID = encryptionKeyID
return nil
}
// PutObject put objects to oss bucket
func (o *ObjectStore) PutObject(bucket, key string, body io.Reader) error {
bucketObj, err := o.getBucket(bucket)
if err != nil {
return err
}
if o.encryptionKeyID != "" {
err = bucketObj.PutObject(key, body,
oss.ServerSideEncryption("KMS"),
oss.ServerSideEncryptionKeyID(o.encryptionKeyID))
} else {
err = bucketObj.PutObject(key, body)
}
return err
}
// ObjectExists check if object exist in a oss bucket
func (o *ObjectStore) ObjectExists(bucket, key string) (bool, error) {
bucketObj, err := o.getBucket(bucket)
if err != nil {
return false, err
}
return bucketObj.IsObjectExist(key)
}
// GetObject get objects from a bucket
func (o *ObjectStore) GetObject(bucket, key string) (io.ReadCloser, error) {
bucketObj, err := o.getBucket(bucket)
if err != nil {
return nil, err
}
return bucketObj.GetObject(key)
}
// ListCommonPrefixes interface
func (o *ObjectStore) ListCommonPrefixes(bucket, prefix, delimiter string) ([]string, error) {
bucketObj, err := o.getBucket(bucket)
if err != nil {
return nil, err
}
var res []string
marker := oss.Marker("")
for {
lor, err := bucketObj.ListObjects(oss.Prefix(prefix), oss.Delimiter(delimiter), oss.MaxKeys(50), marker)
if err != nil {
o.log.Errorf("failed to list objects: %v", err)
return res, err
}
res = append(res, lor.CommonPrefixes...)
if lor.IsTruncated {
marker = oss.Marker(lor.NextMarker)
} else {
break
}
}
return res, nil
}
// ListObjects list objects of a bucket
func (o *ObjectStore) ListObjects(bucket, prefix string) ([]string, error) {
bucketObj, err := o.getBucket(bucket)
if err != nil {
return nil, err
}
var res []string
marker := oss.Marker("")
for {
lor, err := bucketObj.ListObjects(oss.Prefix(prefix), oss.MaxKeys(50), marker)
if err != nil {
o.log.Errorf("failed to list objects: %v", err)
}
for _, obj := range lor.Objects {
res = append(res, obj.Key)
}
if lor.IsTruncated {
marker = oss.Marker(lor.NextMarker)
} else {
break
}
}
return res, nil
}
// DeleteObject delete objects from oss bucket
func (o *ObjectStore) DeleteObject(bucket, key string) error {
bucketObj, err := o.getBucket(bucket)
if err != nil {
return err
}
return bucketObj.DeleteObject(key)
}
// CreateSignedURL create a signed URL
func (o *ObjectStore) CreateSignedURL(bucket, key string, ttl time.Duration) (string, error) {
bucketObj, err := o.getBucket(bucket)
if err != nil {
return "", err
}
return bucketObj.SignURL(key, oss.HTTPGet, int64(ttl.Seconds()))
}
/*
Copyright 2018, 2019 the Velero contributors.
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 alibabacloud
import (
"io"
"testing"
"github.com/aliyun/aliyun-oss-go-sdk/oss"
"github.com/pkg/errors"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/mock"
)
type mockBucketGetter struct {
mock.Mock
}
func (m *mockBucketGetter) Bucket(bucket string) (ossBucket, error) {
args := m.Called(bucket)
return args.Get(0).(ossBucket), args.Error(1)
}
type mockBucket struct {
mock.Mock
}
func (m *mockBucket) IsObjectExist(key string) (bool, error) {
args := m.Called(key)
return args.Get(0).(bool), args.Error(1)
}
func (m *mockBucket) ListObjects(options ...oss.Option) (oss.ListObjectsResult, error) {
args := m.Called(options)
return args.Get(0).(oss.ListObjectsResult), args.Error(1)
}
func (m *mockBucket) GetObject(key string, options ...oss.Option) (io.ReadCloser, error) {
args := m.Called(key, options)
return args.Get(0).(io.ReadCloser), args.Error(1)
}
func (m *mockBucket) PutObject(key string, reader io.Reader, options ...oss.Option) error {
args := m.Called(key, reader, options)
return args.Error(0)
}
func (m *mockBucket) DeleteObject(key string) error {
args := m.Called(key)
return args.Error(0)
}
func (m *mockBucket) SignURL(objectKey string, method oss.HTTPMethod, expiredInSec int64, options ...oss.Option) (string, error) {
args := m.Called(objectKey, method, expiredInSec, options)
return args.Get(0).(string), args.Error(1)
}
func TestObjectExists(t *testing.T) {
tests := []struct {
name string
errorResponse error
expectedExists bool
expectedError string
}{
{
name: "exists",
errorResponse: nil,
expectedExists: true,
},
{
name: "doesn't exist",
errorResponse: errors.New("not found"),
expectedExists: false,
},
{
name: "error checking for existence",
errorResponse: errors.New("bad"),
expectedExists: false,
expectedError: "bad",
},
}
for _, tc := range tests {
t.Run(tc.name, func(t *testing.T) {
client := new(mockBucketGetter)
defer client.AssertExpectations(t)
o := &ObjectStore{
client: client,
}
bucket := new(mockBucket)
defer bucket.AssertExpectations(t)
client.On("Bucket", "bucket").Return(bucket, nil)
bucket.On("IsObjectExist", "key").Return(tc.expectedExists, tc.errorResponse)
exists, err := o.ObjectExists("bucket", "key")
if tc.expectedError != "" {
assert.EqualError(t, err, tc.expectedError)
return
}
assert.Equal(t, tc.expectedExists, exists)
})
}
}
# Contributor Code of Conduct
As contributors and maintainers of this project,
and in the interest of fostering an open and welcoming community,
we pledge to respect all people who contribute through reporting issues,
posting feature requests, updating documentation,
submitting pull requests or patches, and other activities.
We are committed to making participation in this project
a harassment-free experience for everyone,
regardless of level of experience, gender, gender identity and expression,
sexual orientation, disability, personal appearance,
body size, race, ethnicity, age, religion, or nationality.
Examples of unacceptable behavior by participants include:
* The use of sexualized language or imagery
* Personal attacks
* Trolling or insulting/derogatory comments
* Public or private harassment
* Publishing other's private information,
such as physical or electronic
addresses, without explicit permission
* Other unethical or unprofessional conduct.
Project maintainers have the right and responsibility to remove, edit, or reject
comments, commits, code, wiki edits, issues, and other contributions
that are not aligned to this Code of Conduct.
By adopting this Code of Conduct,
project maintainers commit themselves to fairly and consistently
applying these principles to every aspect of managing this project.
Project maintainers who do not follow or enforce the Code of Conduct
may be permanently removed from the project team.
This code of conduct applies both within project spaces and in public spaces
when an individual is representing the project or its community.
Instances of abusive, harassing, or otherwise unacceptable behavior
may be reported by opening an issue
or contacting one or more of the project maintainers.
This Code of Conduct is adapted from the [Contributor Covenant](http://contributor-covenant.org), version 1.2.0,
available at [http://contributor-covenant.org/version/1/2/0/](http://contributor-covenant.org/version/1/2/0/)
# How to Release this Repo
1. Determine the current release version with `git tag -l`. It should look
something like `vX.Y.Z`. We'll call the current version `$CV` and the new
version `$NV`.
1. On master, run `git log $CV..` to list all the changes since the last
release.
1. Edit `CHANGES.md` to include a summary of the changes.
1. Mail the CL containing the `CHANGES.md` changes. When the CL is approved,
submit it.
1. Without submitting any other CLs:
a. Switch to master.
b. `git pull`
c. Tag the repo with the next version: `git tag $NV`.
d. Push the tag: `git push origin $NV`.
1. Update [the releases page](https://github.com/googleapis/google-cloud-go/releases)
with the new release, copying the contents of the CHANGES.md.
// Copyright 2014 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
/*
Package cloud is the root of the packages used to access Google Cloud
Services. See https://godoc.org/cloud.google.com/go for a full list
of sub-packages.
Client Options
All clients in sub-packages are configurable via client options. These options are
described here: https://godoc.org/google.golang.org/api/option.
Authentication and Authorization
All the clients in sub-packages support authentication via Google Application Default
Credentials (see https://cloud.google.com/docs/authentication/production), or
by providing a JSON key file for a Service Account. See the authentication examples
in this package for details.
Timeouts and Cancellation
By default, all requests in sub-packages will run indefinitely, retrying on transient
errors when correctness allows. To set timeouts or arrange for cancellation, use
contexts. See the examples for details.
Do not attempt to control the initial connection (dialing) of a service by setting a
timeout on the context passed to NewClient. Dialing is non-blocking, so timeouts
would be ineffective and would only interfere with credential refreshing, which uses
the same context.
Connection Pooling
Connection pooling differs in clients based on their transport. Cloud
clients either rely on HTTP or gRPC transports to communicate
with Google Cloud.
Cloud clients that use HTTP (bigquery, compute, storage, and translate) rely on the
underlying HTTP transport to cache connections for later re-use. These are cached to
the default http.MaxIdleConns and http.MaxIdleConnsPerHost settings in
http.DefaultTransport.
For gRPC clients (all others in this repo), connection pooling is configurable. Users
of cloud client libraries may specify option.WithGRPCConnectionPool(n) as a client
option to NewClient calls. This configures the underlying gRPC connections to be
pooled and addressed in a round robin fashion.
Using the Libraries with Docker
Minimal docker images like Alpine lack CA certificates. This causes RPCs to appear to
hang, because gRPC retries indefinitely. See https://github.com/googleapis/google-cloud-go/issues/928
for more information.
Debugging
To see gRPC logs, set the environment variable GRPC_GO_LOG_SEVERITY_LEVEL. See
https://godoc.org/google.golang.org/grpc/grpclog for more information.
For HTTP logging, set the GODEBUG environment variable to "http2debug=1" or "http2debug=2".
*/
package cloud // import "cloud.google.com/go"
// 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 metadata_test
import (
"net/http"
"cloud.google.com/go/compute/metadata"
)
// This example demonstrates how to use your own transport when using this package.
func ExampleNewClient() {
c := metadata.NewClient(&http.Client{Transport: userAgentTransport{
userAgent: "my-user-agent",
base: http.DefaultTransport,
}})
p, err := c.ProjectID()
if err != nil {
// TODO: Handle error.
}
_ = p // TODO: Use p.
}
// userAgentTransport sets the User-Agent header before calling base.
type userAgentTransport struct {
userAgent string
base http.RoundTripper
}
// RoundTrip implements the http.RoundTripper interface.
func (t userAgentTransport) RoundTrip(req *http.Request) (*http.Response, error) {
req.Header.Set("User-Agent", t.userAgent)
return t.base.RoundTrip(req)
}
// Copyright 2016 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package metadata
import (
"bytes"
"io/ioutil"
"net/http"
"os"
"sync"
"testing"
)
func TestOnGCE_Stress(t *testing.T) {
if testing.Short() {
t.Skip("skipping in -short mode")
}
var last bool
for i := 0; i < 100; i++ {
onGCEOnce = sync.Once{}
now := OnGCE()
if i > 0 && now != last {
t.Errorf("%d. changed from %v to %v", i, last, now)
}
last = now
}
t.Logf("OnGCE() = %v", last)
}
func TestOnGCE_Force(t *testing.T) {
onGCEOnce = sync.Once{}
old := os.Getenv(metadataHostEnv)
defer os.Setenv(metadataHostEnv, old)
os.Setenv(metadataHostEnv, "127.0.0.1")
if !OnGCE() {
t.Error("OnGCE() = false; want true")
}
}
func TestOverrideUserAgent(t *testing.T) {
const userAgent = "my-user-agent"
rt := &rrt{}
c := NewClient(&http.Client{Transport: userAgentTransport{userAgent, rt}})
c.Get("foo")
if got, want := rt.gotUserAgent, userAgent; got != want {
t.Errorf("got %q, want %q", got, want)
}
}
type userAgentTransport struct {
userAgent string
base http.RoundTripper
}
func (t userAgentTransport) RoundTrip(req *http.Request) (*http.Response, error) {
req.Header.Set("User-Agent", t.userAgent)
return t.base.RoundTrip(req)
}
type rrt struct {
gotUserAgent string
}
func (r *rrt) RoundTrip(req *http.Request) (*http.Response, error) {
r.gotUserAgent = req.Header.Get("User-Agent")
return &http.Response{Body: ioutil.NopCloser(bytes.NewReader(nil))}, nil
}
// 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 cloud_test
import (
"context"
"time"
"cloud.google.com/go/bigquery"
"cloud.google.com/go/datastore"
"cloud.google.com/go/pubsub"
"golang.org/x/oauth2/google"
"google.golang.org/api/option"
)
// To set a timeout for an RPC, use context.WithTimeout.
func Example_timeout() {
ctx := context.Background()
// Do not set a timeout on the context passed to NewClient: dialing happens
// asynchronously, and the context is used to refresh credentials in the
// background.
client, err := bigquery.NewClient(ctx, "project-id")
if err != nil {
// TODO: handle error.
}
// Time out if it takes more than 10 seconds to create a dataset.
tctx, cancel := context.WithTimeout(ctx, 10*time.Second)
defer cancel() // Always call cancel.
if err := client.Dataset("new-dataset").Create(tctx, nil); err != nil {
// TODO: handle error.
}
}
// To arrange for an RPC to be canceled, use context.WithCancel.
func Example_cancellation() {
ctx := context.Background()
// Do not cancel the context passed to NewClient: dialing happens asynchronously,
// and the context is used to refresh credentials in the background.
client, err := bigquery.NewClient(ctx, "project-id")
if err != nil {
// TODO: handle error.
}
cctx, cancel := context.WithCancel(ctx)
defer cancel() // Always call cancel.
// TODO: Make the cancel function available to whatever might want to cancel the
// call--perhaps a GUI button.
if err := client.Dataset("new-dataset").Create(cctx, nil); err != nil {
// TODO: handle error.
}
}
// Google Application Default Credentials is the recommended way to authorize
// and authenticate clients.
//
// For information on how to create and obtain Application Default Credentials, see
// https://developers.google.com/identity/protocols/application-default-credentials.
func Example_applicationDefaultCredentials() {
client, err := datastore.NewClient(context.Background(), "project-id")
if err != nil {
// TODO: handle error.
}
_ = client // Use the client.
}
// You can use a file with credentials to authenticate and authorize, such as a JSON
// key file associated with a Google service account. Service Account keys can be
// created and downloaded from
// https://console.developers.google.com/permissions/serviceaccounts.
//
// This example uses the Datastore client, but the same steps apply to
// the other client libraries underneath this package.
func Example_credentialsFile() {
client, err := datastore.NewClient(context.Background(),
"project-id", option.WithCredentialsFile("/path/to/service-account-key.json"))
if err != nil {
// TODO: handle error.
}
_ = client // Use the client.
}
// In some cases (for instance, you don't want to store secrets on disk), you can
// create credentials from in-memory JSON and use the WithCredentials option.
//
// The google package in this example is at golang.org/x/oauth2/google.
//
// This example uses the PubSub client, but the same steps apply to
// the other client libraries underneath this package.
func Example_credentialsFromJSON() {
ctx := context.Background()
creds, err := google.CredentialsFromJSON(ctx, []byte("JSON creds"), pubsub.ScopePubSub)
if err != nil {
// TODO: handle error.
}
client, err := pubsub.NewClient(ctx, "project-id", option.WithCredentials(creds))
if err != nil {
// TODO: handle error.
}
_ = client // Use the client.
}
module cloud.google.com/go
require (
github.com/golang/mock v1.2.0
github.com/golang/protobuf v1.2.0
github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c
github.com/google/go-cmp v0.2.0
github.com/google/martian v2.1.0+incompatible
github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57
github.com/googleapis/gax-go/v2 v2.0.4
github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024
go.opencensus.io v0.21.0
golang.org/x/exp v0.0.0-20190121172915-509febef88a4
golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f
golang.org/x/net v0.0.0-20190311183353-d8887717615a
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421
golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6
golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2
golang.org/x/time v0.0.0-20181108054448-85acf8d2951c
golang.org/x/tools v0.0.0-20190312170243-e65039ee4138
google.golang.org/api v0.5.0
google.golang.org/genproto v0.0.0-20190508193815-b515fa19cec8
google.golang.org/grpc v1.19.0
honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a
)
cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ=
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b h1:VKtxabqXZkF25pY9ekfRL6a582T4P37/31XEstQ5p58=
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
github.com/golang/mock v1.2.0 h1:28o5sBqPkBsMGnC6b4MvE2TzSr5/AT4c/1fLqVGIwlk=
github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
github.com/golang/protobuf v1.2.0 h1:P3YflyNX/ehuJFLhxviNdFxQPkGK5cDcApsge1SqnvM=
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c h1:964Od4U6p2jUkFxvCydnIczKteheJEzHRToSGK3Bnlw=
github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
github.com/google/go-cmp v0.2.0 h1:+dTQ8DZQJz0Mb/HjFlkptS1FeQ4cWSnN941F8aEG4SQ=
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
github.com/google/martian v2.1.0+incompatible h1:/CP5g8u/VJHijgedC/Legn3BAbAaWPgecwXBIDzw5no=
github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs=
github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57 h1:eqyIo2HjKhKe/mJzTG8n4VqvLXIOEG+SLdDqX7xGtkY=
github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
github.com/googleapis/gax-go/v2 v2.0.4 h1:hU4mGcQI4DaAYW+IbTun+2qEZVFxK0ySjQLTbS0VQKc=
github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg=
github.com/hashicorp/golang-lru v0.5.0 h1:CL2msUPvZTLb5O648aiLNJw3hnBxN2+1Jq8rCOH9wdo=
github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024 h1:rBMNdlhTLzJjJSDIjNEXX1Pz3Hmwmz91v+zycvx9PJc=
github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU=
go.opencensus.io v0.21.0 h1:mU6zScU4U1YAFPHEHYk+3JC4SY7JxgkqS10ZOSyksNg=
go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/exp v0.0.0-20190121172915-509febef88a4 h1:c2HOrn5iMezYjSlGPncknSEr/8x5LELb/ilJbXi9DEA=
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3 h1:x/bBzNauLQAlE3fLku/xy92Y8QwKX5HZymrMz2IiKFc=
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961 h1:GmgasJE571dBGXS7E282h2rIZj+KvCLV8z5I6QXbKNI=
golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f h1:hX65Cu3JDlGH3uEdK7I99Ii+9kjD6mvnnpfLdEAH0x4=
golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190213061140-3a22650c66bd h1:HuTn7WObtcDo9uEEU7rEqL0jYthdXAmZ6PP+meazmaU=
golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190311183353-d8887717615a h1:oWX7TPOiFAMXLq8o0ikBYfCJVlRHBcsciT5bXOrH628=
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421 h1:Wo7BWFiOk0QRFMLYMqJGFMd9CgUAcGx7V+qEg/h5IBI=
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f h1:Bl/8QSvNqXvPGPGXa2z5xUTmV7VDcZyvRZ+QQXkXTZQ=
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6 h1:bjcUS9ztw9kFmmIxJInhon/0Is3p+EHBKNgquIzo1OI=
golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a h1:1BGLXjeY4akVXGgbC9HugT3Jv3hCI0z56oJR5vAMgBU=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/text v0.3.0 h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2 h1:z99zHgr7hKfrUcX/KsoJk5FJfjTceCKIp96+biqP4To=
golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/time v0.0.0-20181108054448-85acf8d2951c h1:fqgJT0MGcGpPgpWU7VRdRjuArfcOvC4AoJmILihzhDg=
golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190226205152-f727befe758c h1:vamGzbGri8IKo20MQncCuljcQ5uAO6kaCeawQPVblAI=
golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=
golang.org/x/tools v0.0.0-20190312170243-e65039ee4138 h1:H3uGjxCR/6Ds0Mjgyp7LMK81+LvmbvWWEnJhzk1Pi9E=
golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
google.golang.org/api v0.5.0 h1:lj9SyhMzyoa38fgFF0oO2T6pjs5IzkLPKfVtxpyCRMM=
google.golang.org/api v0.5.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE=
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
google.golang.org/appengine v1.4.0 h1:/wp5JvzpHIxhs/dumFmF7BXTf3Z+dd4uXta4kVyO508=
google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19 h1:Lj2SnHtxkRGJDqnGaSjo+CCdIieEnwVazbOXILwQemk=
google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
google.golang.org/genproto v0.0.0-20190508193815-b515fa19cec8 h1:x913Lq/RebkvUmRSdQ8MNb0GZKn+SR1ESfoetcQSeak=
google.golang.org/genproto v0.0.0-20190508193815-b515fa19cec8/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
google.golang.org/grpc v1.19.0 h1:cfg4PD8YEdSFnm7qLV4++93WcmhH2nIUhMjhdCvl3j8=
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a h1:/8zB6iBfHCl1qAnEAWwGPNrUvapuy6CPla1VM0k8hQw=
honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
(delete this for feature requests)
## Client
e.g. PubSub
## Describe Your Environment
e.g. Alpine Docker on GKE
## Expected Behavior
e.g. Messages arrive really fast.
## Actual Behavior
e.g. Messages arrive really slowly.
\ No newline at end of file
// Copyright 2016 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package cloud
import (
"bytes"
"io/ioutil"
"os"
"path/filepath"
"strings"
"testing"
)
var sentinels = []string{
"Copyright",
"Google",
`Licensed under the Apache License, Version 2.0 (the "License");`,
}
func TestLicense(t *testing.T) {
t.Parallel()
err := filepath.Walk(".", func(path string, fi os.FileInfo, err error) error {
if err != nil {
return err
}
if ext := filepath.Ext(path); ext != ".go" && ext != ".proto" {
return nil
}
if strings.HasSuffix(path, ".pb.go") {
// .pb.go files are generated from the proto files.
// .proto files must have license headers.
return nil
}
if path == "bigtable/cmd/cbt/cbtdoc.go" {
// Automatically generated.
return nil
}
if path == "cmd/go-cloud-debug-agent/internal/debug/elf/elf.go" {
// BSD license, which is compatible, is embedded in the file.
return nil
}
src, err := ioutil.ReadFile(path)
if err != nil {
return nil
}
src = src[:300] // Ensure all of the sentinel values are at the top of the file.
// Find license
for _, sentinel := range sentinels {
if !bytes.Contains(src, []byte(sentinel)) {
t.Errorf("%v: license header not present. want %q", path, sentinel)
return nil
}
}
return nil
})
if err != nil {
t.Fatal(err)
}
}
#!/bin/bash
# This script generates all GAPIC clients in this repo.
# One-time setup:
# cd path/to/googleapis # https://github.com/googleapis/googleapis
# virtualenv env
# . env/bin/activate
# pip install googleapis-artman
# deactivate
#
# Regenerate:
# cd path/to/googleapis
# . env/bin/activate
# $GOPATH/src/cloud.google.com/go/regen-gapic.sh
# deactivate
#
# Being in googleapis directory is important;
# that's where we find YAML files and where artman puts the "artman-genfiles" directory.
#
# NOTE: This script does not generate the "raw" gRPC client found in google.golang.org/genproto.
# To do that, use the regen.sh script in the genproto repo instead.
set -ex
APIS=(
google/api/expr/artman_cel.yaml
google/iam/artman_iam_admin.yaml
google/cloud/asset/artman_cloudasset_v1beta1.yaml
google/cloud/asset/artman_cloudasset_v1.yaml
google/iam/credentials/artman_iamcredentials_v1.yaml
google/cloud/bigquery/datatransfer/artman_bigquerydatatransfer.yaml
google/cloud/bigquery/storage/artman_bigquerystorage_v1beta1.yaml
google/cloud/dataproc/artman_dataproc_v1.yaml
google/cloud/dataproc/artman_dataproc_v1beta2.yaml
google/cloud/dialogflow/artman_dialogflow_v2.yaml
google/cloud/iot/artman_cloudiot.yaml
google/cloud/irm/artman_irm_v1alpha2.yaml
google/cloud/kms/artman_cloudkms.yaml
google/cloud/language/artman_language_v1.yaml
google/cloud/language/artman_language_v1beta2.yaml
google/cloud/oslogin/artman_oslogin_v1.yaml
google/cloud/oslogin/artman_oslogin_v1beta.yaml
google/cloud/phishingprotection/artman_phishingprotection_v1beta1.yaml
google/cloud/recaptchaenterprise/artman_recaptchaenterprise_v1beta1.yaml
google/cloud/redis/artman_redis_v1beta1.yaml
google/cloud/redis/artman_redis_v1.yaml
google/cloud/scheduler/artman_cloudscheduler_v1beta1.yaml
google/cloud/scheduler/artman_cloudscheduler_v1.yaml
google/cloud/securitycenter/artman_securitycenter_v1beta1.yaml
google/cloud/securitycenter/artman_securitycenter_v1.yaml
google/cloud/speech/artman_speech_v1.yaml
google/cloud/speech/artman_speech_v1p1beta1.yaml
google/cloud/talent/artman_talent_v4beta1.yaml
google/cloud/tasks/artman_cloudtasks_v2beta2.yaml
google/cloud/tasks/artman_cloudtasks_v2beta3.yaml
google/cloud/tasks/artman_cloudtasks_v2.yaml
google/cloud/texttospeech/artman_texttospeech_v1.yaml
google/cloud/videointelligence/artman_videointelligence_v1.yaml
google/cloud/videointelligence/artman_videointelligence_v1beta1.yaml
google/cloud/videointelligence/artman_videointelligence_v1beta2.yaml
google/cloud/vision/artman_vision_v1.yaml
google/cloud/vision/artman_vision_v1p1beta1.yaml
google/cloud/webrisk/artman_webrisk_v1beta1.yaml
google/devtools/artman_clouddebugger.yaml
google/devtools/clouderrorreporting/artman_errorreporting.yaml
google/devtools/cloudtrace/artman_cloudtrace_v1.yaml
google/devtools/cloudtrace/artman_cloudtrace_v2.yaml
google/devtools/containeranalysis/artman_containeranalysis_v1beta1.yaml
google/firestore/artman_firestore.yaml
google/logging/artman_logging.yaml
google/longrunning/artman_longrunning.yaml
google/monitoring/artman_monitoring.yaml
google/privacy/dlp/artman_dlp_v2.yaml
google/pubsub/artman_pubsub.yaml
google/spanner/admin/database/artman_spanner_admin_database.yaml
google/spanner/admin/instance/artman_spanner_admin_instance.yaml
google/spanner/artman_spanner.yaml
)
for api in "${APIS[@]}"; do
rm -rf artman-genfiles/*
artman --config "$api" generate go_gapic
cp -r artman-genfiles/gapi-*/cloud.google.com/go/* $GOPATH/src/cloud.google.com/go/
done
microgen() {
input=$1
options="${@:2}"
# see https://github.com/googleapis/gapic-generator-go/blob/master/README.md#docker-wrapper for details
docker run \
--mount type=bind,source=$(pwd),destination=/conf,readonly \
--mount type=bind,source=$(pwd)/$input,destination=/in/$input,readonly \
--mount type=bind,source=$GOPATH/src,destination=/out \
--rm \
gcr.io/gapic-images/gapic-generator-go:latest \
$options
}
MICROAPIS=(
# input proto directory | gapic-generator-go flag | gapic-service-config flag
# "google/cloud/language/v1 --go-gapic-package cloud.google.com/go/language/apiv1;language --gapic-service-config google/cloud/language/language_v1.yaml"
)
for api in "${MICROAPIS[@]}"; do
microgen $api
done
pushd $GOPATH/src/cloud.google.com/go/
gofmt -s -d -l -w . && goimports -w .
# NOTE(pongad): `sed -i` doesn't work on Macs, because -i option needs an argument.
# `-i ''` doesn't work on GNU, since the empty string is treated as a file name.
# So we just create the backup and delete it after.
ver=$(date +%Y%m%d)
git ls-files -mo | while read modified; do
dir=${modified%/*.*}
find . -path "*/$dir/doc.go" -exec sed -i.backup -e "s/^const versionClient.*/const versionClient = \"$ver\"/" '{}' +
done
popd
HASMANUAL=(
errorreporting/apiv1beta1
firestore/apiv1beta1
firestore/apiv1
logging/apiv2
longrunning/autogen
pubsub/apiv1
spanner/apiv1
trace/apiv1
)
for dir in "${HASMANUAL[@]}"; do
find "$GOPATH/src/cloud.google.com/go/$dir" -name '*.go' -exec sed -i.backup -e 's/setGoogleClientInfo/SetGoogleClientInfo/g' '{}' '+'
done
find $GOPATH/src/cloud.google.com/go/ -name '*.backup' -delete
// +build tools
// Copyright 2018 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// This package exists to cause `go mod` and `go get` to believe these tools
// are dependencies, even though they are not runtime dependencies of any
// package (these are tools used by our CI builds). This means they will appear
// in our `go.mod` file, but will not be a part of the build. Also, since the
// build target is something non-existent, these should not be included in any
// binaries.
package cloud
import (
_ "github.com/golang/protobuf/protoc-gen-go"
_ "github.com/jstemmer/go-junit-report"
_ "golang.org/x/exp/cmd/apidiff"
_ "golang.org/x/lint/golint"
_ "golang.org/x/tools/cmd/goimports"
_ "honnef.co/go/tools/cmd/staticcheck"
)
language: go
go:
- 1.11.x
go_import_path: contrib.go.opencensus.io/exporter/ocagent
before_script:
- GO_FILES=$(find . -iname '*.go' | grep -v /vendor/) # All the .go files, excluding vendor/ if any
- PKGS=$(go list ./... | grep -v /vendor/) # All the import paths, excluding vendor/ if any
script:
- go build ./... # Ensure dependency updates don't break build
- if [ -n "$(gofmt -s -l $GO_FILES)" ]; then echo "gofmt the following files:"; gofmt -s -l $GO_FILES; exit 1; fi
- go vet ./...
- GO111MODULE=on go test -v -race $PKGS # Run all the tests with the race detector enabled
- GO111MODULE=off go test -v -race $PKGS # Make sure tests still pass when not using Go modules.
- 'if [[ $TRAVIS_GO_VERSION = 1.8* ]]; then ! golint ./... | grep -vE "(_mock|_string|\.pb)\.go:"; fi'
# How to contribute
We'd love to accept your patches and contributions to this project. There are
just a few small guidelines you need to follow.
## Contributor License Agreement
Contributions to this project must be accompanied by a Contributor License
Agreement. You (or your employer) retain the copyright to your contribution,
this simply gives us permission to use and redistribute your contributions as
part of the project. Head over to <https://cla.developers.google.com/> to see
your current agreements on file or to sign a new one.
You generally only need to submit a CLA once, so if you've already submitted one
(even if it was for a different project), you probably don't need to do it
again.
## Code reviews
All submissions, including submissions by project members, require review. We
use GitHub pull requests for this purpose. Consult [GitHub Help] for more
information on using pull requests.
[GitHub Help]: https://help.github.com/articles/about-pull-requests/
# OpenCensus Agent Go Exporter
[![Build Status][travis-image]][travis-url] [![GoDoc][godoc-image]][godoc-url]
This repository contains the Go implementation of the OpenCensus Agent (OC-Agent) Exporter.
OC-Agent is a deamon process running in a VM that can retrieve spans/stats/metrics from
OpenCensus Library, export them to other backends and possibly push configurations back to
Library. See more details on [OC-Agent Readme][OCAgentReadme].
Note: This is an experimental repository and is likely to get backwards-incompatible changes.
Ultimately we may want to move the OC-Agent Go Exporter to [OpenCensus Go core library][OpenCensusGo].
## Installation
```bash
$ go get -u contrib.go.opencensus.io/exporter/ocagent
```
## Usage
```go
import (
"context"
"fmt"
"log"
"time"
"contrib.go.opencensus.io/exporter/ocagent"
"go.opencensus.io/trace"
)
func Example() {
exp, err := ocagent.NewExporter(ocagent.WithInsecure(), ocagent.WithServiceName("your-service-name"))
if err != nil {
log.Fatalf("Failed to create the agent exporter: %v", err)
}
defer exp.Stop()
// Now register it as a trace exporter.
trace.RegisterExporter(exp)
// Then use the OpenCensus tracing library, like we normally would.
ctx, span := trace.StartSpan(context.Background(), "AgentExporter-Example")
defer span.End()
for i := 0; i < 10; i++ {
_, iSpan := trace.StartSpan(ctx, fmt.Sprintf("Sample-%d", i))
<-time.After(6 * time.Millisecond)
iSpan.End()
}
}
```
[OCAgentReadme]: https://github.com/census-instrumentation/opencensus-proto/tree/master/opencensus/proto/agent#opencensus-agent-proto
[OpenCensusGo]: https://github.com/census-instrumentation/opencensus-go
[godoc-image]: https://godoc.org/contrib.go.opencensus.io/exporter/ocagent?status.svg
[godoc-url]: https://godoc.org/contrib.go.opencensus.io/exporter/ocagent
[travis-image]: https://travis-ci.org/census-ecosystem/opencensus-go-exporter-ocagent.svg?branch=master
[travis-url]: https://travis-ci.org/census-ecosystem/opencensus-go-exporter-ocagent
// 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 ocagent
import (
"math/rand"
"time"
)
var randSrc = rand.New(rand.NewSource(time.Now().UnixNano()))
// retries function fn upto n times, if fn returns an error lest it returns nil early.
// It applies exponential backoff in units of (1<<n) + jitter microsends.
func nTriesWithExponentialBackoff(nTries int64, timeBaseUnit time.Duration, fn func() error) (err error) {
for i := int64(0); i < nTries; i++ {
err = fn()
if err == nil {
return nil
}
// Backoff for a time period with a pseudo-random jitter
jitter := time.Duration(randSrc.Float64()*100) * time.Microsecond
ts := jitter + ((1 << uint64(i)) * timeBaseUnit)
<-time.After(ts)
}
return err
}
// 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 ocagent
import (
"math/rand"
"sync/atomic"
"time"
)
const (
sDisconnected int32 = 5 + iota
sConnected
)
func (ae *Exporter) setStateDisconnected() {
atomic.StoreInt32(&ae.connectionState, sDisconnected)
select {
case ae.disconnectedCh <- true:
default:
}
}
func (ae *Exporter) setStateConnected() {
atomic.StoreInt32(&ae.connectionState, sConnected)
}
func (ae *Exporter) connected() bool {
return atomic.LoadInt32(&ae.connectionState) == sConnected
}
const defaultConnReattemptPeriod = 10 * time.Second
func (ae *Exporter) indefiniteBackgroundConnection() error {
defer func() {
ae.backgroundConnectionDoneCh <- true
}()
connReattemptPeriod := ae.reconnectionPeriod
if connReattemptPeriod <= 0 {
connReattemptPeriod = defaultConnReattemptPeriod
}
// No strong seeding required, nano time can
// already help with pseudo uniqueness.
rng := rand.New(rand.NewSource(time.Now().UnixNano() + rand.Int63n(1024)))
// maxJitter: 1 + (70% of the connectionReattemptPeriod)
maxJitter := int64(1 + 0.7*float64(connReattemptPeriod))
for {
// Otherwise these will be the normal scenarios to enable
// reconnections if we trip out.
// 1. If we've stopped, return entirely
// 2. Otherwise block until we are disconnected, and
// then retry connecting
select {
case <-ae.stopCh:
return errStopped
case <-ae.disconnectedCh:
// Normal scenario that we'll wait for
}
if err := ae.connect(); err == nil {
ae.setStateConnected()
} else {
ae.setStateDisconnected()
}
// Apply some jitter to avoid lockstep retrials of other
// agent-exporters. Lockstep retrials could result in an
// innocent DDOS, by clogging the machine's resources and network.
jitter := time.Duration(rng.Int63n(maxJitter))
<-time.After(connReattemptPeriod + jitter)
}
}
func (ae *Exporter) connect() error {
cc, err := ae.dialToAgent()
if err != nil {
return err
}
return ae.enableConnectionStreams(cc)
}
// 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 ocagent_test
import (
"context"
"fmt"
"log"
"time"
"google.golang.org/grpc/credentials"
"contrib.go.opencensus.io/exporter/ocagent"
"go.opencensus.io/trace"
)
func Example_insecure() {
exp, err := ocagent.NewExporter(ocagent.WithInsecure(), ocagent.WithServiceName("engine"))
if err != nil {
log.Fatalf("Failed to create the agent exporter: %v", err)
}
defer exp.Stop()
// Now register it as a trace exporter.
trace.RegisterExporter(exp)
// Then use the OpenCensus tracing library, like we normally would.
ctx, span := trace.StartSpan(context.Background(), "AgentExporter-Example")
defer span.End()
for i := 0; i < 10; i++ {
_, iSpan := trace.StartSpan(ctx, fmt.Sprintf("Sample-%d", i))
<-time.After(6 * time.Millisecond)
iSpan.End()
}
}
func Example_withTLS() {
// Please take at look at https://godoc.org/google.golang.org/grpc/credentials#TransportCredentials
// for ways on how to initialize gRPC TransportCredentials.
creds, err := credentials.NewClientTLSFromFile("my-cert.pem", "")
if err != nil {
log.Fatalf("Failed to create gRPC client TLS credentials: %v", err)
}
exp, err := ocagent.NewExporter(ocagent.WithTLSCredentials(creds), ocagent.WithServiceName("engine"))
if err != nil {
log.Fatalf("Failed to create the agent exporter: %v", err)
}
defer exp.Stop()
// Now register it as a trace exporter.
trace.RegisterExporter(exp)
// Then use the OpenCensus tracing library, like we normally would.
ctx, span := trace.StartSpan(context.Background(), "Securely-Talking-To-Agent-Span")
defer span.End()
for i := 0; i < 10; i++ {
_, iSpan := trace.StartSpan(ctx, fmt.Sprintf("Sample-%d", i))
<-time.After(6 * time.Millisecond)
iSpan.End()
}
}
module contrib.go.opencensus.io/exporter/ocagent
require (
github.com/census-instrumentation/opencensus-proto v0.2.0 // this is to match the version used in census-instrumentation/opencensus-service
github.com/golang/protobuf v1.3.1
github.com/grpc-ecosystem/grpc-gateway v1.8.5 // indirect
go.opencensus.io v0.20.2
google.golang.org/api v0.3.1
google.golang.org/grpc v1.19.1
)
// 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.11
package ocagent_test
import (
"context"
"errors"
"fmt"
"io/ioutil"
"net"
"net/http"
"strconv"
"strings"
"sync"
"testing"
"time"
"google.golang.org/grpc"
"contrib.go.opencensus.io/exporter/ocagent"
agenttracepb "github.com/census-instrumentation/opencensus-proto/gen-go/agent/trace/v1"
"go.opencensus.io/plugin/ochttp"
"go.opencensus.io/trace"
)
// Issue #13. The agent exporter was crashing due to -1 that was being
// passed in as a size, into the bundler when exporting spans under load.
func TestExportsUnderLoad_issue13(t *testing.T) {
// Create the agent's listener.
agentLn, err := net.Listen("tcp", ":0")
if err != nil {
t.Fatalf("Failed to bind to address: %v", err)
}
defer agentLn.Close()
agentPort, err := parsePort(agentLn.Addr())
if err != nil {
t.Fatalf("Could not parse port from agent address: %v", err)
}
agentAddr := fmt.Sprintf("localhost:%d", agentPort)
// Then create the frontend HTTP server's listener which
// will be used to generate tertiary spans that
// the ocagent-exporter uploads to the agent.
frontendLn, err := net.Listen("tcp", ":0")
if err != nil {
t.Fatalf("Failed to create frontend server listener: %v", err)
}
defer frontendLn.Close()
frontendPort, err := parsePort(frontendLn.Addr())
if err != nil {
t.Fatalf("Failed to parse port from frontend server address: %v", err)
}
agentSrv := grpc.NewServer()
agenttracepb.RegisterTraceServiceServer(agentSrv, new(discardAgent))
go func() {
_ = agentSrv.Serve(agentLn)
}()
exporter, err := ocagent.NewExporter(
ocagent.WithInsecure(),
ocagent.WithServiceName("go-app"),
ocagent.WithReconnectionPeriod(50*time.Millisecond),
ocagent.WithAddress(agentAddr))
if err != nil {
t.Fatalf("Failed to create the agent exporter: %v", err)
}
defer exporter.Stop()
trace.RegisterExporter(exporter)
body := "Hello, World!"
frontend := &http.Server{Handler: &ochttp.Handler{
Handler: http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.Write([]byte(body))
}),
}}
go func() {
_ = frontend.Serve(frontendLn)
}()
// Creating our semaphore and load it up.
// with 50 concurrent requests to generate many spans in short burts.
// Using 50 because that's a high number for multiplicity of open file descriptors.
n := 50
reqSemaphore := make(chan bool, n)
// Stock it up with concurrency tokens
for i := 0; i < n; i++ {
reqSemaphore <- true
}
frontendURL := fmt.Sprintf("http://localhost:%d", frontendPort)
var wg sync.WaitGroup
httpClient := &http.Client{Transport: &ochttp.Transport{Base: &http.Transport{MaxConnsPerHost: n}}}
for i := 0; i < 10000; i++ {
// Throttle to ensure we have a max of n concurrent requests
<-reqSemaphore
// Permitted to go on
wg.Add(1)
go func(id int) {
defer func() {
// Now declare that we are complete
reqSemaphore <- true
wg.Done()
}()
req, _ := http.NewRequest("GET", frontendURL, nil)
ctx, span := trace.StartSpan(context.Background(), "TestSpan", trace.WithSampler(trace.AlwaysSample()))
req = req.WithContext(ctx)
res, err := httpClient.Do(req)
span.End()
if err != nil {
t.Errorf("Request #%d: error: %v", id, err)
} else {
blob, err := ioutil.ReadAll(res.Body)
if err != nil {
t.Errorf("Request #%d: ReadAll error: %v", id, err)
}
_ = res.Body.Close()
if g, w := string(blob), body; g != w {
t.Errorf("Request #%d:\nGot: %s\nWant: %s\n", id, g, w)
}
}
}(i)
}
// Wait until every single request has completed.
wg.Wait()
}
type discardAgent struct{}
var _ agenttracepb.TraceServiceServer = (*discardAgent)(nil)
func (da *discardAgent) Config(tscs agenttracepb.TraceService_ConfigServer) error {
return nil
}
func (da *discardAgent) Export(tses agenttracepb.TraceService_ExportServer) error {
for {
req, err := tses.Recv()
if err != nil {
return err
}
if req == nil {
}
}
}
func parsePort(addr net.Addr) (uint16, error) {
addrStr := addr.String()
if i := strings.LastIndex(addrStr, ":"); i < 0 {
return 0, errors.New("no `:` found")
} else {
port, err := strconv.Atoi(addrStr[i+1:])
return uint16(port), err
}
}
// 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 ocagent_test
import (
"fmt"
"net"
"sync"
"testing"
"time"
"google.golang.org/grpc"
commonpb "github.com/census-instrumentation/opencensus-proto/gen-go/agent/common/v1"
agenttracepb "github.com/census-instrumentation/opencensus-proto/gen-go/agent/trace/v1"
tracepb "github.com/census-instrumentation/opencensus-proto/gen-go/trace/v1"
)
func makeMockAgent(t *testing.T) *mockAgent {
return &mockAgent{configsToSend: make(chan *agenttracepb.UpdatedLibraryConfig), t: t, wg: new(sync.WaitGroup)}
}
type mockAgent struct {
t *testing.T
spans []*tracepb.Span
mu sync.Mutex
wg *sync.WaitGroup
traceNodes []*commonpb.Node
receivedConfigs []*agenttracepb.CurrentLibraryConfig
configsToSend chan *agenttracepb.UpdatedLibraryConfig
closeConfigsToSendOnce sync.Once
address string
stopFunc func() error
stopOnce sync.Once
}
var _ agenttracepb.TraceServiceServer = (*mockAgent)(nil)
func (ma *mockAgent) Config(tscs agenttracepb.TraceService_ConfigServer) error {
ma.mu.Lock()
ma.wg.Add(1)
ma.mu.Unlock()
defer func() {
ma.mu.Lock()
ma.wg.Done()
ma.mu.Unlock()
}()
in, err := tscs.Recv()
if err != nil {
return err
}
if in == nil || in.Node == nil {
return fmt.Errorf("the first message must contain the node identifier")
}
ma.receivedConfigs = append(ma.receivedConfigs, in)
// Push down all the configs
for cfg := range ma.configsToSend {
// Push down configs
if err := tscs.Send(cfg); err != nil {
return err
}
// And then get back the config sent back by the client library
back, err := tscs.Recv()
if err != nil {
return err
}
ma.receivedConfigs = append(ma.receivedConfigs, back)
}
// Just for the sake of draining any configs
// that the client-side exporter is still sending.
for {
back, err := tscs.Recv()
if err != nil {
return err
}
ma.receivedConfigs = append(ma.receivedConfigs, back)
}
}
func (ma *mockAgent) Export(tses agenttracepb.TraceService_ExportServer) error {
in, err := tses.Recv()
if err != nil {
return err
}
ma.mu.Lock()
ma.wg.Add(1)
ma.mu.Unlock()
defer func() {
ma.mu.Lock()
ma.wg.Done()
ma.mu.Unlock()
}()
// The first trace message should contain the node identifier.
if in == nil || in.Node == nil {
return fmt.Errorf("the first message must contain the node identifier")
}
ma.traceNodes = append(ma.traceNodes, in.Node)
// Now that we have the node identifier, let's start receiving spans.
for {
req, err := tses.Recv()
if err != nil {
return err
}
ma.mu.Lock()
ma.spans = append(ma.spans, req.Spans...)
ma.traceNodes = append(ma.traceNodes, req.Node)
ma.mu.Unlock()
}
}
func (ma *mockAgent) transitionToReceivingClientConfigs() {
// Since we are done sending all the configs, close the configsChannel
// so that the state can transition to receiving all the client configs.
ma.closeConfigsToSendOnce.Do(func() {
close(ma.configsToSend)
})
}
var errAlreadyStopped = fmt.Errorf("already stopped")
func (ma *mockAgent) stop() error {
var err = errAlreadyStopped
ma.stopOnce.Do(func() {
ma.transitionToReceivingClientConfigs()
if ma.stopFunc != nil {
err = ma.stopFunc()
}
})
// Give it sometime to shutdown.
<-time.After(160 * time.Millisecond)
ma.mu.Lock()
ma.wg.Wait()
ma.mu.Unlock()
return err
}
// runMockAgent is a helper function to create a mockAgent
func runMockAgent(t *testing.T) *mockAgent {
return runMockAgentAtAddr(t, ":0")
}
func runMockAgentAtAddr(t *testing.T, addr string) *mockAgent {
var deferFuncs []func() error
ln, err := net.Listen("tcp", addr)
if err != nil {
t.Fatalf("Failed to get an address: %v", err)
}
deferFuncs = append(deferFuncs, ln.Close)
srv := grpc.NewServer()
ma := makeMockAgent(t)
agenttracepb.RegisterTraceServiceServer(srv, ma)
go func() {
_ = srv.Serve(ln)
}()
deferFunc := func() error {
srv.Stop()
return ln.Close()
}
_, agentPortStr, _ := net.SplitHostPort(ln.Addr().String())
ma.address = "localhost:" + agentPortStr
ma.stopFunc = deferFunc
return ma
}
func (ma *mockAgent) getSpans() []*tracepb.Span {
ma.mu.Lock()
spans := append([]*tracepb.Span{}, ma.spans...)
ma.mu.Unlock()
return spans
}
func (ma *mockAgent) getReceivedConfigs() []*agenttracepb.CurrentLibraryConfig {
ma.mu.Lock()
receivedConfigs := append([]*agenttracepb.CurrentLibraryConfig{}, ma.receivedConfigs...)
ma.mu.Unlock()
return receivedConfigs
}
func (ma *mockAgent) getTraceNodes() []*commonpb.Node {
ma.mu.Lock()
traceNodes := append([]*commonpb.Node{}, ma.traceNodes...)
ma.mu.Unlock()
return traceNodes
}
// 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 ocagent
import (
"os"
commonpb "github.com/census-instrumentation/opencensus-proto/gen-go/agent/common/v1"
"go.opencensus.io"
)
// NodeWithStartTime creates a node using nodeName and derives:
// Hostname from the environment
// Pid from the current process
// StartTimestamp from the start time of this process
// Language and library information.
func NodeWithStartTime(nodeName string) *commonpb.Node {
return &commonpb.Node{
Identifier: &commonpb.ProcessIdentifier{
HostName: os.Getenv("HOSTNAME"),
Pid: uint32(os.Getpid()),
StartTimestamp: timeToTimestamp(startTime),
},
LibraryInfo: &commonpb.LibraryInfo{
Language: commonpb.LibraryInfo_GO_LANG,
ExporterVersion: Version,
CoreLibraryVersion: opencensus.Version(),
},
ServiceInfo: &commonpb.ServiceInfo{
Name: nodeName,
},
Attributes: make(map[string]string),
}
}
// 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 ocagent
import (
"time"
"google.golang.org/grpc/credentials"
)
const (
DefaultAgentPort uint16 = 55678
DefaultAgentHost string = "localhost"
)
type ExporterOption interface {
withExporter(e *Exporter)
}
type insecureGrpcConnection int
var _ ExporterOption = (*insecureGrpcConnection)(nil)
func (igc *insecureGrpcConnection) withExporter(e *Exporter) {
e.canDialInsecure = true
}
// WithInsecure disables client transport security for the exporter's gRPC connection
// just like grpc.WithInsecure() https://godoc.org/google.golang.org/grpc#WithInsecure
// does. Note, by default, client security is required unless WithInsecure is used.
func WithInsecure() ExporterOption { return new(insecureGrpcConnection) }
type addressSetter string
func (as addressSetter) withExporter(e *Exporter) {
e.agentAddress = string(as)
}
var _ ExporterOption = (*addressSetter)(nil)
// WithAddress allows one to set the address that the exporter will
// connect to the agent on. If unset, it will instead try to use
// connect to DefaultAgentHost:DefaultAgentPort
func WithAddress(addr string) ExporterOption {
return addressSetter(addr)
}
type serviceNameSetter string
func (sns serviceNameSetter) withExporter(e *Exporter) {
e.serviceName = string(sns)
}
var _ ExporterOption = (*serviceNameSetter)(nil)
// WithServiceName allows one to set/override the service name
// that the exporter will report to the agent.
func WithServiceName(serviceName string) ExporterOption {
return serviceNameSetter(serviceName)
}
type reconnectionPeriod time.Duration
func (rp reconnectionPeriod) withExporter(e *Exporter) {
e.reconnectionPeriod = time.Duration(rp)
}
func WithReconnectionPeriod(rp time.Duration) ExporterOption {
return reconnectionPeriod(rp)
}
type compressorSetter string
func (c compressorSetter) withExporter(e *Exporter) {
e.compressor = string(c)
}
// UseCompressor will set the compressor for the gRPC client to use when sending requests.
// It is the responsibility of the caller to ensure that the compressor set has been registered
// with google.golang.org/grpc/encoding. This can be done by encoding.RegisterCompressor. Some
// compressors auto-register on import, such as gzip, which can be registered by calling
// `import _ "google.golang.org/grpc/encoding/gzip"`
func UseCompressor(compressorName string) ExporterOption {
return compressorSetter(compressorName)
}
type headerSetter map[string]string
func (h headerSetter) withExporter(e *Exporter) {
e.headers = map[string]string(h)
}
// WithHeaders will send the provided headers when the gRPC stream connection
// is instantiated
func WithHeaders(headers map[string]string) ExporterOption {
return headerSetter(headers)
}
type clientCredentials struct {
credentials.TransportCredentials
}
var _ ExporterOption = (*clientCredentials)(nil)
// WithTLSCredentials allows the connection to use TLS credentials
// when talking to the server. It takes in grpc.TransportCredentials instead
// of say a Certificate file or a tls.Certificate, because the retrieving
// these credentials can be done in many ways e.g. plain file, in code tls.Config
// or by certificate rotation, so it is up to the caller to decide what to use.
func WithTLSCredentials(creds credentials.TransportCredentials) ExporterOption {
return &clientCredentials{TransportCredentials: creds}
}
func (cc *clientCredentials) withExporter(e *Exporter) {
e.clientTransportCredentials = cc.TransportCredentials
}
// 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 ocagent
import (
"math"
"time"
"go.opencensus.io/trace"
"go.opencensus.io/trace/tracestate"
tracepb "github.com/census-instrumentation/opencensus-proto/gen-go/trace/v1"
"github.com/golang/protobuf/ptypes/timestamp"
)
const (
maxAnnotationEventsPerSpan = 32
maxMessageEventsPerSpan = 128
)
func ocSpanToProtoSpan(sd *trace.SpanData) *tracepb.Span {
if sd == nil {
return nil
}
var namePtr *tracepb.TruncatableString
if sd.Name != "" {
namePtr = &tracepb.TruncatableString{Value: sd.Name}
}
return &tracepb.Span{
TraceId: sd.TraceID[:],
SpanId: sd.SpanID[:],
ParentSpanId: sd.ParentSpanID[:],
Status: ocStatusToProtoStatus(sd.Status),
StartTime: timeToTimestamp(sd.StartTime),
EndTime: timeToTimestamp(sd.EndTime),
Links: ocLinksToProtoLinks(sd.Links),
Kind: ocSpanKindToProtoSpanKind(sd.SpanKind),
Name: namePtr,
Attributes: ocAttributesToProtoAttributes(sd.Attributes),
TimeEvents: ocTimeEventsToProtoTimeEvents(sd.Annotations, sd.MessageEvents),
Tracestate: ocTracestateToProtoTracestate(sd.Tracestate),
}
}
var blankStatus trace.Status
func ocStatusToProtoStatus(status trace.Status) *tracepb.Status {
if status == blankStatus {
return nil
}
return &tracepb.Status{
Code: status.Code,
Message: status.Message,
}
}
func ocLinksToProtoLinks(links []trace.Link) *tracepb.Span_Links {
if len(links) == 0 {
return nil
}
sl := make([]*tracepb.Span_Link, 0, len(links))
for _, ocLink := range links {
// This redefinition is necessary to prevent ocLink.*ID[:] copies
// being reused -- in short we need a new ocLink per iteration.
ocLink := ocLink
sl = append(sl, &tracepb.Span_Link{
TraceId: ocLink.TraceID[:],
SpanId: ocLink.SpanID[:],
Type: ocLinkTypeToProtoLinkType(ocLink.Type),
})
}
return &tracepb.Span_Links{
Link: sl,
}
}
func ocLinkTypeToProtoLinkType(oct trace.LinkType) tracepb.Span_Link_Type {
switch oct {
case trace.LinkTypeChild:
return tracepb.Span_Link_CHILD_LINKED_SPAN
case trace.LinkTypeParent:
return tracepb.Span_Link_PARENT_LINKED_SPAN
default:
return tracepb.Span_Link_TYPE_UNSPECIFIED
}
}
func ocAttributesToProtoAttributes(attrs map[string]interface{}) *tracepb.Span_Attributes {
if len(attrs) == 0 {
return nil
}
outMap := make(map[string]*tracepb.AttributeValue)
for k, v := range attrs {
switch v := v.(type) {
case bool:
outMap[k] = &tracepb.AttributeValue{Value: &tracepb.AttributeValue_BoolValue{BoolValue: v}}
case int:
outMap[k] = &tracepb.AttributeValue{Value: &tracepb.AttributeValue_IntValue{IntValue: int64(v)}}
case int64:
outMap[k] = &tracepb.AttributeValue{Value: &tracepb.AttributeValue_IntValue{IntValue: v}}
case string:
outMap[k] = &tracepb.AttributeValue{
Value: &tracepb.AttributeValue_StringValue{
StringValue: &tracepb.TruncatableString{Value: v},
},
}
}
}
return &tracepb.Span_Attributes{
AttributeMap: outMap,
}
}
// This code is mostly copied from
// https://github.com/census-ecosystem/opencensus-go-exporter-stackdriver/blob/master/trace_proto.go#L46
func ocTimeEventsToProtoTimeEvents(as []trace.Annotation, es []trace.MessageEvent) *tracepb.Span_TimeEvents {
if len(as) == 0 && len(es) == 0 {
return nil
}
timeEvents := &tracepb.Span_TimeEvents{}
var annotations, droppedAnnotationsCount int
var messageEvents, droppedMessageEventsCount int
// Transform annotations
for i, a := range as {
if annotations >= maxAnnotationEventsPerSpan {
droppedAnnotationsCount = len(as) - i
break
}
annotations++
timeEvents.TimeEvent = append(timeEvents.TimeEvent,
&tracepb.Span_TimeEvent{
Time: timeToTimestamp(a.Time),
Value: transformAnnotationToTimeEvent(&a),
},
)
}
// Transform message events
for i, e := range es {
if messageEvents >= maxMessageEventsPerSpan {
droppedMessageEventsCount = len(es) - i
break
}
messageEvents++
timeEvents.TimeEvent = append(timeEvents.TimeEvent,
&tracepb.Span_TimeEvent{
Time: timeToTimestamp(e.Time),
Value: transformMessageEventToTimeEvent(&e),
},
)
}
// Process dropped counter
timeEvents.DroppedAnnotationsCount = clip32(droppedAnnotationsCount)
timeEvents.DroppedMessageEventsCount = clip32(droppedMessageEventsCount)
return timeEvents
}
func transformAnnotationToTimeEvent(a *trace.Annotation) *tracepb.Span_TimeEvent_Annotation_ {
return &tracepb.Span_TimeEvent_Annotation_{
Annotation: &tracepb.Span_TimeEvent_Annotation{
Description: &tracepb.TruncatableString{Value: a.Message},
Attributes: ocAttributesToProtoAttributes(a.Attributes),
},
}
}
func transformMessageEventToTimeEvent(e *trace.MessageEvent) *tracepb.Span_TimeEvent_MessageEvent_ {
return &tracepb.Span_TimeEvent_MessageEvent_{
MessageEvent: &tracepb.Span_TimeEvent_MessageEvent{
Type: tracepb.Span_TimeEvent_MessageEvent_Type(e.EventType),
Id: uint64(e.MessageID),
UncompressedSize: uint64(e.UncompressedByteSize),
CompressedSize: uint64(e.CompressedByteSize),
},
}
}
// clip32 clips an int to the range of an int32.
func clip32(x int) int32 {
if x < math.MinInt32 {
return math.MinInt32
}
if x > math.MaxInt32 {
return math.MaxInt32
}
return int32(x)
}
func timeToTimestamp(t time.Time) *timestamp.Timestamp {
nanoTime := t.UnixNano()
return &timestamp.Timestamp{
Seconds: nanoTime / 1e9,
Nanos: int32(nanoTime % 1e9),
}
}
func ocSpanKindToProtoSpanKind(kind int) tracepb.Span_SpanKind {
switch kind {
case trace.SpanKindClient:
return tracepb.Span_CLIENT
case trace.SpanKindServer:
return tracepb.Span_SERVER
default:
return tracepb.Span_SPAN_KIND_UNSPECIFIED
}
}
func ocTracestateToProtoTracestate(ts *tracestate.Tracestate) *tracepb.Span_Tracestate {
if ts == nil {
return nil
}
return &tracepb.Span_Tracestate{
Entries: ocTracestateEntriesToProtoTracestateEntries(ts.Entries()),
}
}
func ocTracestateEntriesToProtoTracestateEntries(entries []tracestate.Entry) []*tracepb.Span_Tracestate_Entry {
protoEntries := make([]*tracepb.Span_Tracestate_Entry, 0, len(entries))
for _, entry := range entries {
protoEntries = append(protoEntries, &tracepb.Span_Tracestate_Entry{
Key: entry.Key,
Value: entry.Value,
})
}
return protoEntries
}
// 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 ocagent_test
import (
"reflect"
"testing"
"time"
"contrib.go.opencensus.io/exporter/ocagent"
"go.opencensus.io/trace"
"go.opencensus.io/trace/tracestate"
tracepb "github.com/census-instrumentation/opencensus-proto/gen-go/trace/v1"
"github.com/golang/protobuf/ptypes/timestamp"
)
func TestOCSpanToProtoSpan_endToEnd(t *testing.T) {
// The goal of this test is to ensure that each
// spanData is transformed and exported correctly!
agent := runMockAgent(t)
defer agent.stop()
serviceName := "spanTranslation"
exp, err := ocagent.NewExporter(ocagent.WithInsecure(),
ocagent.WithAddress(agent.address),
ocagent.WithReconnectionPeriod(50*time.Millisecond),
ocagent.WithServiceName(serviceName))
if err != nil {
t.Fatalf("Failed to create a new agent exporter: %v", err)
}
defer exp.Stop()
// Give the background agent connection sometime to setup.
<-time.After(20 * time.Millisecond)
startTime := time.Now()
endTime := startTime.Add(10 * time.Second)
ocTracestate, err := tracestate.New(new(tracestate.Tracestate), tracestate.Entry{Key: "foo", Value: "bar"},
tracestate.Entry{Key: "a", Value: "b"})
if err != nil || ocTracestate == nil {
t.Fatalf("Failed to create ocTracestate: %v", err)
}
ocSpanData := &trace.SpanData{
SpanContext: trace.SpanContext{
TraceID: trace.TraceID{0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F},
SpanID: trace.SpanID{0xFF, 0xFE, 0xFD, 0xFC, 0xFB, 0xFA, 0xF9, 0xF8},
Tracestate: ocTracestate,
},
SpanKind: trace.SpanKindServer,
ParentSpanID: trace.SpanID{0xEF, 0xEE, 0xED, 0xEC, 0xEB, 0xEA, 0xE9, 0xE8},
Name: "End-To-End Here",
StartTime: startTime,
EndTime: endTime,
Annotations: []trace.Annotation{
{
Time: startTime,
Message: "start",
Attributes: map[string]interface{}{
"timeout_ns": int64(12e9),
"agent": "ocagent",
"cache_hit": true,
"ping_count": int(25), // Should be transformed into int64
},
},
{
Time: endTime,
Message: "end",
Attributes: map[string]interface{}{
"timeout_ns": int64(12e9),
"agent": "ocagent",
"cache_hit": false,
"ping_count": int(25), // Should be transformed into int64
},
},
},
MessageEvents: []trace.MessageEvent{
{Time: startTime, EventType: trace.MessageEventTypeSent, UncompressedByteSize: 1024, CompressedByteSize: 512},
{Time: endTime, EventType: trace.MessageEventTypeRecv, UncompressedByteSize: 1024, CompressedByteSize: 1000},
},
Links: []trace.Link{
{
TraceID: trace.TraceID{0xC0, 0xC1, 0xC2, 0xC3, 0xC4, 0xC5, 0xC6, 0xC7, 0xC8, 0xC9, 0xCA, 0xCB, 0xCC, 0xCD, 0xCE, 0xCF},
SpanID: trace.SpanID{0xB0, 0xB1, 0xB2, 0xB3, 0xB4, 0xB5, 0xB6, 0xB7},
Type: trace.LinkTypeParent,
},
{
TraceID: trace.TraceID{0xE0, 0xE1, 0xE2, 0xE3, 0xE4, 0xE5, 0xE6, 0xE7, 0xE8, 0xE9, 0xEA, 0xEB, 0xEC, 0xED, 0xEE, 0xEF},
SpanID: trace.SpanID{0xD0, 0xD1, 0xD2, 0xD3, 0xD4, 0xD5, 0xD6, 0xD7},
Type: trace.LinkTypeChild,
},
},
Status: trace.Status{
Code: trace.StatusCodeInternal,
Message: "This is not a drill!",
},
HasRemoteParent: true,
Attributes: map[string]interface{}{
"timeout_ns": int64(12e9),
"agent": "ocagent",
"cache_hit": true,
"ping_count": int(25), // Should be transformed into int64
},
}
// TODO: file a bug with opencensus-go because trace.Annotation.Attributes' type is map[string]interface{}
// yet Attribute value and key cannot be easily introspected, so we can't easily test the values.
exp.ExportSpan(ocSpanData)
exp.Flush()
// Also try to export a nil span and it should never make it
exp.ExportSpan(nil)
exp.Flush()
// Give flush sometime to upload its data
<-time.After(40 * time.Millisecond)
exp.Stop()
agent.stop()
spans := agent.getSpans()
if len(spans) == 0 || spans[0] == nil {
t.Fatal("Expected the exported span")
}
wantProtoSpan := &tracepb.Span{
TraceId: []byte{0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F},
SpanId: []byte{0xFF, 0xFE, 0xFD, 0xFC, 0xFB, 0xFA, 0xF9, 0xF8},
ParentSpanId: []byte{0xEF, 0xEE, 0xED, 0xEC, 0xEB, 0xEA, 0xE9, 0xE8},
Name: &tracepb.TruncatableString{Value: "End-To-End Here"},
Kind: tracepb.Span_SERVER,
StartTime: timeToTimestamp(startTime),
EndTime: timeToTimestamp(endTime),
Status: &tracepb.Status{
Code: 13,
Message: "This is not a drill!",
},
TimeEvents: &tracepb.Span_TimeEvents{
TimeEvent: []*tracepb.Span_TimeEvent{
// annotation
{
Time: timeToTimestamp(startTime),
Value: &tracepb.Span_TimeEvent_Annotation_{
Annotation: &tracepb.Span_TimeEvent_Annotation{
Description: &tracepb.TruncatableString{Value: "start"},
Attributes: &tracepb.Span_Attributes{
AttributeMap: map[string]*tracepb.AttributeValue{
"cache_hit": {Value: &tracepb.AttributeValue_BoolValue{BoolValue: true}},
"timeout_ns": {Value: &tracepb.AttributeValue_IntValue{IntValue: 12e9}},
"ping_count": {Value: &tracepb.AttributeValue_IntValue{IntValue: 25}},
"agent": {Value: &tracepb.AttributeValue_StringValue{
StringValue: &tracepb.TruncatableString{Value: "ocagent"},
}},
},
},
},
},
},
{
Time: timeToTimestamp(endTime),
Value: &tracepb.Span_TimeEvent_Annotation_{
Annotation: &tracepb.Span_TimeEvent_Annotation{
Description: &tracepb.TruncatableString{Value: "end"},
Attributes: &tracepb.Span_Attributes{
AttributeMap: map[string]*tracepb.AttributeValue{
"cache_hit": {Value: &tracepb.AttributeValue_BoolValue{BoolValue: false}},
"timeout_ns": {Value: &tracepb.AttributeValue_IntValue{IntValue: 12e9}},
"ping_count": {Value: &tracepb.AttributeValue_IntValue{IntValue: 25}},
"agent": {Value: &tracepb.AttributeValue_StringValue{
StringValue: &tracepb.TruncatableString{Value: "ocagent"},
}},
},
},
},
},
},
// message event
{
Time: timeToTimestamp(startTime),
Value: &tracepb.Span_TimeEvent_MessageEvent_{
MessageEvent: &tracepb.Span_TimeEvent_MessageEvent{
Type: tracepb.Span_TimeEvent_MessageEvent_SENT,
UncompressedSize: 1024,
CompressedSize: 512,
},
},
},
{
Time: timeToTimestamp(endTime),
Value: &tracepb.Span_TimeEvent_MessageEvent_{
MessageEvent: &tracepb.Span_TimeEvent_MessageEvent{
Type: tracepb.Span_TimeEvent_MessageEvent_RECEIVED,
UncompressedSize: 1024,
CompressedSize: 1000,
},
},
},
},
},
Links: &tracepb.Span_Links{
Link: []*tracepb.Span_Link{
{
TraceId: []byte{0xC0, 0xC1, 0xC2, 0xC3, 0xC4, 0xC5, 0xC6, 0xC7, 0xC8, 0xC9, 0xCA, 0xCB, 0xCC, 0xCD, 0xCE, 0xCF},
SpanId: []byte{0xB0, 0xB1, 0xB2, 0xB3, 0xB4, 0xB5, 0xB6, 0xB7},
Type: tracepb.Span_Link_PARENT_LINKED_SPAN,
},
{
TraceId: []byte{0xE0, 0xE1, 0xE2, 0xE3, 0xE4, 0xE5, 0xE6, 0xE7, 0xE8, 0xE9, 0xEA, 0xEB, 0xEC, 0xED, 0xEE, 0xEF},
SpanId: []byte{0xD0, 0xD1, 0xD2, 0xD3, 0xD4, 0xD5, 0xD6, 0xD7},
Type: tracepb.Span_Link_CHILD_LINKED_SPAN,
},
},
},
Tracestate: &tracepb.Span_Tracestate{
Entries: []*tracepb.Span_Tracestate_Entry{
{Key: "foo", Value: "bar"},
{Key: "a", Value: "b"},
},
},
Attributes: &tracepb.Span_Attributes{
AttributeMap: map[string]*tracepb.AttributeValue{
"cache_hit": {Value: &tracepb.AttributeValue_BoolValue{BoolValue: true}},
"timeout_ns": {Value: &tracepb.AttributeValue_IntValue{IntValue: 12e9}},
"ping_count": {Value: &tracepb.AttributeValue_IntValue{IntValue: 25}},
"agent": {Value: &tracepb.AttributeValue_StringValue{
StringValue: &tracepb.TruncatableString{Value: "ocagent"},
}},
},
},
}
if g, w := spans[0], wantProtoSpan; !reflect.DeepEqual(g, w) {
for i := 0; i < len(w.Links.Link); i++ {
t.Logf("#%d:: \nGot: %#v\nWant: %#v\n\n", i, g.Links.Link[i], w.Links.Link[i])
}
t.Fatalf("End-to-end transformed span\n\tGot %+v\n\tWant %+v", g, w)
}
}
func timeToTimestamp(t time.Time) *timestamp.Timestamp {
nanoTime := t.UnixNano()
return &timestamp.Timestamp{
Seconds: nanoTime / 1e9,
Nanos: int32(nanoTime % 1e9),
}
}
// 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 ocagent
import (
"errors"
"time"
"go.opencensus.io/stats"
"go.opencensus.io/stats/view"
"go.opencensus.io/tag"
"github.com/golang/protobuf/ptypes/timestamp"
metricspb "github.com/census-instrumentation/opencensus-proto/gen-go/metrics/v1"
)
var (
errNilMeasure = errors.New("expecting a non-nil stats.Measure")
errNilView = errors.New("expecting a non-nil view.View")
errNilViewData = errors.New("expecting a non-nil view.Data")
)
func viewDataToMetric(vd *view.Data) (*metricspb.Metric, error) {
if vd == nil {
return nil, errNilViewData
}
descriptor, err := viewToMetricDescriptor(vd.View)
if err != nil {
return nil, err
}
timeseries, err := viewDataToTimeseries(vd)
if err != nil {
return nil, err
}
metric := &metricspb.Metric{
MetricDescriptor: descriptor,
Timeseries: timeseries,
}
return metric, nil
}
func viewToMetricDescriptor(v *view.View) (*metricspb.MetricDescriptor, error) {
if v == nil {
return nil, errNilView
}
if v.Measure == nil {
return nil, errNilMeasure
}
desc := &metricspb.MetricDescriptor{
Name: stringOrCall(v.Name, v.Measure.Name),
Description: stringOrCall(v.Description, v.Measure.Description),
Unit: v.Measure.Unit(),
Type: aggregationToMetricDescriptorType(v),
LabelKeys: tagKeysToLabelKeys(v.TagKeys),
}
return desc, nil
}
func stringOrCall(first string, call func() string) string {
if first != "" {
return first
}
return call()
}
type measureType uint
const (
measureUnknown measureType = iota
measureInt64
measureFloat64
)
func measureTypeFromMeasure(m stats.Measure) measureType {
switch m.(type) {
default:
return measureUnknown
case *stats.Float64Measure:
return measureFloat64
case *stats.Int64Measure:
return measureInt64
}
}
func aggregationToMetricDescriptorType(v *view.View) metricspb.MetricDescriptor_Type {
if v == nil || v.Aggregation == nil {
return metricspb.MetricDescriptor_UNSPECIFIED
}
if v.Measure == nil {
return metricspb.MetricDescriptor_UNSPECIFIED
}
switch v.Aggregation.Type {
case view.AggTypeCount:
// Cumulative on int64
return metricspb.MetricDescriptor_CUMULATIVE_INT64
case view.AggTypeDistribution:
// Cumulative types
return metricspb.MetricDescriptor_CUMULATIVE_DISTRIBUTION
case view.AggTypeLastValue:
// Gauge types
switch measureTypeFromMeasure(v.Measure) {
case measureFloat64:
return metricspb.MetricDescriptor_GAUGE_DOUBLE
case measureInt64:
return metricspb.MetricDescriptor_GAUGE_INT64
}
case view.AggTypeSum:
// Cumulative types
switch measureTypeFromMeasure(v.Measure) {
case measureFloat64:
return metricspb.MetricDescriptor_CUMULATIVE_DOUBLE
case measureInt64:
return metricspb.MetricDescriptor_CUMULATIVE_INT64
}
}
// For all other cases, return unspecified.
return metricspb.MetricDescriptor_UNSPECIFIED
}
func tagKeysToLabelKeys(tagKeys []tag.Key) []*metricspb.LabelKey {
labelKeys := make([]*metricspb.LabelKey, 0, len(tagKeys))
for _, tagKey := range tagKeys {
labelKeys = append(labelKeys, &metricspb.LabelKey{
Key: tagKey.Name(),
})
}
return labelKeys
}
func viewDataToTimeseries(vd *view.Data) ([]*metricspb.TimeSeries, error) {
if vd == nil || len(vd.Rows) == 0 {
return nil, nil
}
// Given that view.Data only contains Start, End
// the timestamps for all the row data will be the exact same
// per aggregation. However, the values will differ.
// Each row has its own tags.
startTimestamp := timeToProtoTimestamp(vd.Start)
endTimestamp := timeToProtoTimestamp(vd.End)
mType := measureTypeFromMeasure(vd.View.Measure)
timeseries := make([]*metricspb.TimeSeries, 0, len(vd.Rows))
// It is imperative that the ordering of "LabelValues" matches those
// of the Label keys in the metric descriptor.
for _, row := range vd.Rows {
labelValues := labelValuesFromTags(row.Tags)
point := rowToPoint(vd.View, row, endTimestamp, mType)
timeseries = append(timeseries, &metricspb.TimeSeries{
StartTimestamp: startTimestamp,
LabelValues: labelValues,
Points: []*metricspb.Point{point},
})
}
if len(timeseries) == 0 {
return nil, nil
}
return timeseries, nil
}
func timeToProtoTimestamp(t time.Time) *timestamp.Timestamp {
unixNano := t.UnixNano()
return &timestamp.Timestamp{
Seconds: int64(unixNano / 1e9),
Nanos: int32(unixNano % 1e9),
}
}
func rowToPoint(v *view.View, row *view.Row, endTimestamp *timestamp.Timestamp, mType measureType) *metricspb.Point {
pt := &metricspb.Point{
Timestamp: endTimestamp,
}
switch data := row.Data.(type) {
case *view.CountData:
pt.Value = &metricspb.Point_Int64Value{Int64Value: data.Value}
case *view.DistributionData:
pt.Value = &metricspb.Point_DistributionValue{
DistributionValue: &metricspb.DistributionValue{
Count: data.Count,
Sum: float64(data.Count) * data.Mean, // because Mean := Sum/Count
// TODO: Add Exemplar
Buckets: bucketsToProtoBuckets(data.CountPerBucket),
BucketOptions: &metricspb.DistributionValue_BucketOptions{
Type: &metricspb.DistributionValue_BucketOptions_Explicit_{
Explicit: &metricspb.DistributionValue_BucketOptions_Explicit{
Bounds: v.Aggregation.Buckets,
},
},
},
SumOfSquaredDeviation: data.SumOfSquaredDev,
}}
case *view.LastValueData:
setPointValue(pt, data.Value, mType)
case *view.SumData:
setPointValue(pt, data.Value, mType)
}
return pt
}
// Not returning anything from this function because metricspb.Point.is_Value is an unexported
// interface hence we just have to set its value by pointer.
func setPointValue(pt *metricspb.Point, value float64, mType measureType) {
if mType == measureInt64 {
pt.Value = &metricspb.Point_Int64Value{Int64Value: int64(value)}
} else {
pt.Value = &metricspb.Point_DoubleValue{DoubleValue: value}
}
}
func bucketsToProtoBuckets(countPerBucket []int64) []*metricspb.DistributionValue_Bucket {
distBuckets := make([]*metricspb.DistributionValue_Bucket, len(countPerBucket))
for i := 0; i < len(countPerBucket); i++ {
count := countPerBucket[i]
distBuckets[i] = &metricspb.DistributionValue_Bucket{
Count: count,
}
}
return distBuckets
}
func labelValuesFromTags(tags []tag.Tag) []*metricspb.LabelValue {
if len(tags) == 0 {
return nil
}
labelValues := make([]*metricspb.LabelValue, 0, len(tags))
for _, tag_ := range tags {
labelValues = append(labelValues, &metricspb.LabelValue{
Value: tag_.Value,
// It is imperative that we set the "HasValue" attribute,
// in order to distinguish missing a label from the empty string.
// https://godoc.org/github.com/census-instrumentation/opencensus-proto/gen-go/metrics/v1#LabelValue.HasValue
//
// OpenCensus-Go uses non-pointers for tags as seen by this function's arguments,
// so the best case that we can use to distinguish missing labels/tags from the
// empty string is by checking if the Tag.Key.Name() != "" to indicate that we have
// a value.
HasValue: tag_.Key.Name() != "",
})
}
return labelValues
}
// 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 ocagent
const Version = "0.0.1"
// 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 ocagent
import (
"encoding/json"
"errors"
"net"
"reflect"
"sync"
"testing"
"time"
"github.com/golang/protobuf/ptypes/timestamp"
"google.golang.org/grpc"
"go.opencensus.io/stats"
"go.opencensus.io/stats/view"
agentmetricspb "github.com/census-instrumentation/opencensus-proto/gen-go/agent/metrics/v1"
metricspb "github.com/census-instrumentation/opencensus-proto/gen-go/metrics/v1"
)
type metricsAgent struct {
mu sync.RWMutex
metrics []*agentmetricspb.ExportMetricsServiceRequest
}
func TestExportMetrics_conversionFromViewData(t *testing.T) {
ln, err := net.Listen("tcp", ":0")
if err != nil {
t.Fatalf("Failed to get an available TCP address: %v", err)
}
defer ln.Close()
_, agentPortStr, _ := net.SplitHostPort(ln.Addr().String())
ma := new(metricsAgent)
srv := grpc.NewServer()
agentmetricspb.RegisterMetricsServiceServer(srv, ma)
defer srv.Stop()
go func() {
_ = srv.Serve(ln)
}()
reconnectionPeriod := 2 * time.Millisecond
ocexp, err := NewExporter(
WithInsecure(),
WithAddress(":"+agentPortStr),
WithReconnectionPeriod(reconnectionPeriod),
)
if err != nil {
t.Fatalf("Failed to create the ocagent exporter: %v", err)
}
<-time.After(5 * reconnectionPeriod)
ocexp.Flush()
startTime := time.Date(2018, 11, 25, 15, 38, 18, 997, time.UTC)
endTime := startTime.Add(100 * time.Millisecond)
mLatencyMs := stats.Float64("latency", "The latency for various methods", "ms")
ocexp.ExportView(&view.Data{
Start: startTime,
End: endTime,
View: &view.View{
Name: "ocagent.io/latency",
Description: "The latency of the various methods",
Aggregation: view.Count(),
Measure: mLatencyMs,
},
Rows: []*view.Row{
{
Data: &view.CountData{Value: 4},
},
},
})
for i := 0; i < 5; i++ {
ocexp.Flush()
}
<-time.After(100 * time.Millisecond)
var received []*agentmetricspb.ExportMetricsServiceRequest
ma.forEachRequest(func(req *agentmetricspb.ExportMetricsServiceRequest) {
received = append(received, req)
})
// Now compare them with what we expect
want := []*agentmetricspb.ExportMetricsServiceRequest{
{
Node: NodeWithStartTime(""), // The first message identifying this application.
Metrics: nil,
Resource: resourceProtoFromEnv(),
},
{
Metrics: []*metricspb.Metric{
{
MetricDescriptor: &metricspb.MetricDescriptor{
Name: "ocagent.io/latency",
Description: "The latency of the various methods",
Unit: "ms", // Derived from the measure
Type: metricspb.MetricDescriptor_CUMULATIVE_INT64,
LabelKeys: nil,
},
Timeseries: []*metricspb.TimeSeries{
{
StartTimestamp: &timestamp.Timestamp{
Seconds: 1543160298,
Nanos: 997,
},
LabelValues: nil,
Points: []*metricspb.Point{
{
Timestamp: &timestamp.Timestamp{
Seconds: 1543160298,
Nanos: 100000997,
},
Value: &metricspb.Point_Int64Value{Int64Value: 4},
},
},
},
},
},
},
},
}
if !reflect.DeepEqual(received, want) {
gj, _ := json.MarshalIndent(received, "", " ")
wj, _ := json.MarshalIndent(want, "", " ")
if string(gj) != string(wj) {
t.Errorf("Got:\n%s\nWant:\n%s", gj, wj)
}
}
}
func (ma *metricsAgent) Export(mes agentmetricspb.MetricsService_ExportServer) error {
// Expecting the first message to contain the Node information
firstMetric, err := mes.Recv()
if err != nil {
return err
}
if firstMetric == nil || firstMetric.Node == nil {
return errors.New("Expecting a non-nil Node in the first message")
}
ma.addMetric(firstMetric)
for {
msg, err := mes.Recv()
if err != nil {
return err
}
ma.addMetric(msg)
}
}
func (ma *metricsAgent) addMetric(metric *agentmetricspb.ExportMetricsServiceRequest) {
ma.mu.Lock()
ma.metrics = append(ma.metrics, metric)
ma.mu.Unlock()
}
func (ma *metricsAgent) forEachRequest(fn func(*agentmetricspb.ExportMetricsServiceRequest)) {
ma.mu.RLock()
defer ma.mu.RUnlock()
for _, req := range ma.metrics {
fn(req)
}
}
# The standard Go .gitignore file follows. (Sourced from: github.com/github/gitignore/master/Go.gitignore)
# Compiled Object files, Static and Dynamic libs (Shared Objects)
*.o
*.a
*.so
# Folders
_obj
_test
.DS_Store
.idea/
.vscode/
# Architecture specific extensions/prefixes
*.[568vq]
[568vq].out
*.cgo1.go
*.cgo2.c
_cgo_defun.c
_cgo_gotypes.go
_cgo_export.*
_testmain.go
*.exe
*.test
*.prof
# go-autorest specific
vendor/
autorest/azure/example/example
sudo: false
language: go
go:
- 1.10.x
- 1.11.x
- 1.12.x
env:
global:
- DEP_RELEASE_TAG=v0.5.1 # so the script knows which version to use
- GOSEC_RELEASE_TAG=1.3.0
before_install:
- curl -sSL https://raw.githubusercontent.com/golang/dep/master/install.sh | sh
- curl -sfL https://raw.githubusercontent.com/securego/gosec/master/install.sh | sh -s -- -b $GOPATH/bin $GOSEC_RELEASE_TAG
install:
- dep ensure -v
- go install ./vendor/golang.org/x/lint/golint
script:
- grep -L -r --include *.go --exclude-dir vendor -P "Copyright (\d{4}|\(c\)) Microsoft" ./ | tee /dev/stderr | test -z "$(< /dev/stdin)"
- if [[ $TRAVIS_GO_VERSION == 1.11* ]]; then test -z "$(gofmt -s -l -w ./autorest/. | tee /dev/stderr)"; fi
- test -z "$(golint ./autorest/... | tee /dev/stderr)"
- go vet ./autorest/...
#- test -z "$(gosec ./autorest/... | tee /dev/stderr | grep Error)"
- go build -v ./autorest/...
- go test -race -v ./autorest/...
cache:
directories:
- $GOPATH/pkg/dep
DIR?=./autorest/
default: build
build: fmt
go install $(DIR)
test:
go test $(DIR) || exit 1
vet:
@echo "go vet ."
@go vet $(DIR)... ; if [ $$? -eq 1 ]; then \
echo ""; \
echo "Vet found suspicious constructs. Please check the reported constructs"; \
echo "and fix them if necessary before submitting the code for review."; \
exit 1; \
fi
fmt:
gofmt -w $(DIR)
.PHONY: build test vet fmt
# This file is autogenerated, do not edit; changes may be undone by the next 'dep ensure'.
[[projects]]
digest = "1:6b1426cad7057b717351eacf5b6fe70f053f11aac1ce254bbf2fd72c031719eb"
name = "contrib.go.opencensus.io/exporter/ocagent"
packages = ["."]
pruneopts = "UT"
revision = "dcb33c7f3b7cfe67e8a2cea10207ede1b7c40764"
version = "v0.4.12"
[[projects]]
digest = "1:fdb4ed936abeecb46a8c27dcac83f75c05c87a46d9ec7711411eb785c213fa02"
name = "github.com/census-instrumentation/opencensus-proto"
packages = [
"gen-go/agent/common/v1",
"gen-go/agent/metrics/v1",
"gen-go/agent/trace/v1",
"gen-go/metrics/v1",
"gen-go/resource/v1",
"gen-go/trace/v1",
]
pruneopts = "UT"
revision = "a105b96453fe85139acc07b68de48f2cbdd71249"
version = "v0.2.0"
[[projects]]
digest = "1:ffe9824d294da03b391f44e1ae8281281b4afc1bdaa9588c9097785e3af10cec"
name = "github.com/davecgh/go-spew"
packages = ["spew"]
pruneopts = "UT"
revision = "8991bc29aa16c548c550c7ff78260e27b9ab7c73"
version = "v1.1.1"
[[projects]]
digest = "1:76dc72490af7174349349838f2fe118996381b31ea83243812a97e5a0fd5ed55"
name = "github.com/dgrijalva/jwt-go"
packages = ["."]
pruneopts = "UT"
revision = "06ea1031745cb8b3dab3f6a236daf2b0aa468b7e"
version = "v3.2.0"
[[projects]]
digest = "1:cf0d2e435fd4ce45b789e93ef24b5f08e86be0e9807a16beb3694e2d8c9af965"
name = "github.com/dimchansky/utfbom"
packages = ["."]
pruneopts = "UT"
revision = "d2133a1ce379ef6fa992b0514a77146c60db9d1c"
version = "v1.1.0"
[[projects]]
digest = "1:489a99067cd08971bd9c1ee0055119ba8febc1429f9200ab0bec68d35e8c4833"
name = "github.com/golang/protobuf"
packages = [
"jsonpb",
"proto",
"protoc-gen-go/descriptor",
"protoc-gen-go/generator",
"protoc-gen-go/generator/internal/remap",
"protoc-gen-go/plugin",
"ptypes",
"ptypes/any",
"ptypes/duration",
"ptypes/struct",
"ptypes/timestamp",
"ptypes/wrappers",
]
pruneopts = "UT"
revision = "b5d812f8a3706043e23a9cd5babf2e5423744d30"
version = "v1.3.1"
[[projects]]
digest = "1:4cbbca3db0ff89197d000fb2fa0b90ca4516a7fbd4d8cd9fa4bebf17df484f6d"
name = "github.com/grpc-ecosystem/grpc-gateway"
packages = [
"internal",
"runtime",
"utilities",
]
pruneopts = "UT"
revision = "20f268a412e5b342ebfb1a0eef7c3b7bd6c260ea"
version = "v1.8.5"
[[projects]]
digest = "1:67474f760e9ac3799f740db2c489e6423a4cde45520673ec123ac831ad849cb8"
name = "github.com/hashicorp/golang-lru"
packages = ["simplelru"]
pruneopts = "UT"
revision = "7087cb70de9f7a8bc0a10c375cb0d2280a8edf9c"
version = "v0.5.1"
[[projects]]
digest = "1:5d231480e1c64a726869bc4142d270184c419749d34f167646baa21008eb0a79"
name = "github.com/mitchellh/go-homedir"
packages = ["."]
pruneopts = "UT"
revision = "af06845cf3004701891bf4fdb884bfe4920b3727"
version = "v1.1.0"
[[projects]]
digest = "1:0028cb19b2e4c3112225cd871870f2d9cf49b9b4276531f03438a88e94be86fe"
name = "github.com/pmezard/go-difflib"
packages = ["difflib"]
pruneopts = "UT"
revision = "792786c7400a136282c1664665ae0a8db921c6c2"
version = "v1.0.0"
[[projects]]
digest = "1:5da8ce674952566deae4dbc23d07c85caafc6cfa815b0b3e03e41979cedb8750"
name = "github.com/stretchr/testify"
packages = [
"assert",
"require",
]
pruneopts = "UT"
revision = "ffdc059bfe9ce6a4e144ba849dbedead332c6053"
version = "v1.3.0"
[[projects]]
digest = "1:4c93890bbbb5016505e856cb06b5c5a2ff5b7217584d33f2a9071ebef4b5d473"
name = "go.opencensus.io"
packages = [
".",
"internal",
"internal/tagencoding",
"metric/metricdata",
"metric/metricproducer",
"plugin/ocgrpc",
"plugin/ochttp",
"plugin/ochttp/propagation/b3",
"plugin/ochttp/propagation/tracecontext",
"resource",
"stats",
"stats/internal",
"stats/view",
"tag",
"trace",
"trace/internal",
"trace/propagation",
"trace/tracestate",
]
pruneopts = "UT"
revision = "43463a80402d8447b7fce0d2c58edf1687ff0b58"
version = "v0.19.3"
[[projects]]
branch = "master"
digest = "1:994c4915a59f821705d08ea77b117ec7a3e6a46cc867fd194d887500dac1c3c2"
name = "golang.org/x/crypto"
packages = [
"pkcs12",
"pkcs12/internal/rc2",
]
pruneopts = "UT"
revision = "f416ebab96af27ca70b6e5c23d6a0747530da626"
[[projects]]
branch = "master"
digest = "1:134674d729e1afbae39eeaa6abcf8e5f3106338f83643394ab5205a020efbd9b"
name = "golang.org/x/lint"
packages = [
".",
"golint",
]
pruneopts = "UT"
revision = "959b441ac422379a43da2230f62be024250818b0"
[[projects]]
branch = "master"
digest = "1:d9403fe14e9ea0436e59be99b24c76517720c524d5649c3359224364e70252cd"
name = "golang.org/x/net"
packages = [
"context",
"http/httpguts",
"http2",
"http2/hpack",
"idna",
"internal/timeseries",
"trace",
]
pruneopts = "UT"
revision = "1da14a5a36f220ea3f03470682b737b1dfd5de22"
[[projects]]
branch = "master"
digest = "1:75515eedc0dc2cb0b40372008b616fa2841d831c63eedd403285ff286c593295"
name = "golang.org/x/sync"
packages = ["semaphore"]
pruneopts = "UT"
revision = "56d357773e8497dfd526f0727e187720d1093757"
[[projects]]
branch = "master"
digest = "1:28d862f4f9bf2d1976d1d320481bff661b7565eb876d4df2a981f79e7f40e4cd"
name = "golang.org/x/sys"
packages = ["unix"]
pruneopts = "UT"
revision = "12500544f89f9420afe9529ba8940bf72d294972"
[[projects]]
digest = "1:a2ab62866c75542dd18d2b069fec854577a20211d7c0ea6ae746072a1dccdd18"
name = "golang.org/x/text"
packages = [
"collate",
"collate/build",
"internal/colltab",
"internal/gen",
"internal/tag",
"internal/triegen",
"internal/ucd",
"language",
"secure/bidirule",
"transform",
"unicode/bidi",
"unicode/cldr",
"unicode/norm",
"unicode/rangetable",
]
pruneopts = "UT"
revision = "f21a4dfb5e38f5895301dc265a8def02365cc3d0"
version = "v0.3.0"
[[projects]]
branch = "master"
digest = "1:a45ec3bb7c73e52430410dff3e0a5534ce518f72a8eb4355bc8502c546b91ecc"
name = "golang.org/x/tools"
packages = [
"go/ast/astutil",
"go/gcexportdata",
"go/internal/gcimporter",
"go/types/typeutil",
]
pruneopts = "UT"
revision = "7e40e1726ee0fcf776fcf00f033ef7ed114ca1c4"
[[projects]]
digest = "1:5f003878aabe31d7f6b842d4de32b41c46c214bb629bb485387dbcce1edf5643"
name = "google.golang.org/api"
packages = ["support/bundler"]
pruneopts = "UT"
revision = "0cbcb99a9ea0c8023c794b2693cbe1def82ed4d7"
version = "v0.3.2"
[[projects]]
branch = "master"
digest = "1:5dc13cd8c0b417e12d187938d2ccb2348dda90f6d3d4a3d8402a40677a5ac982"
name = "google.golang.org/genproto"
packages = [
"googleapis/api/httpbody",
"googleapis/rpc/status",
"protobuf/field_mask",
]
pruneopts = "UT"
revision = "d1146b9035b912113a38af3b138eb2af567b2c67"
[[projects]]
digest = "1:31d87f39886fb38a2b6c097ff3b9f985d6960772170d64a68246f7790e955746"
name = "google.golang.org/grpc"
packages = [
".",
"balancer",
"balancer/base",
"balancer/roundrobin",
"binarylog/grpc_binarylog_v1",
"codes",
"connectivity",
"credentials",
"credentials/internal",
"encoding",
"encoding/proto",
"grpclog",
"internal",
"internal/backoff",
"internal/balancerload",
"internal/binarylog",
"internal/channelz",
"internal/envconfig",
"internal/grpcrand",
"internal/grpcsync",
"internal/syscall",
"internal/transport",
"keepalive",
"metadata",
"naming",
"peer",
"resolver",
"resolver/dns",
"resolver/passthrough",
"stats",
"status",
"tap",
]
pruneopts = "UT"
revision = "236199dd5f8031d698fb64091194aecd1c3895b2"
version = "v1.20.0"
[solve-meta]
analyzer-name = "dep"
analyzer-version = 1
input-imports = [
"contrib.go.opencensus.io/exporter/ocagent",
"github.com/dgrijalva/jwt-go",
"github.com/dimchansky/utfbom",
"github.com/mitchellh/go-homedir",
"github.com/stretchr/testify/require",
"go.opencensus.io/plugin/ochttp",
"go.opencensus.io/plugin/ochttp/propagation/tracecontext",
"go.opencensus.io/stats/view",
"go.opencensus.io/trace",
"golang.org/x/crypto/pkcs12",
"golang.org/x/lint/golint",
]
solver-name = "gps-cdcl"
solver-version = 1
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment