diff --git a/.gitignore b/.gitignore index 40d8dd99e8..58d4d81765 100644 --- a/.gitignore +++ b/.gitignore @@ -70,3 +70,6 @@ bazel-out bazel-testlogs # Ignore default apiserver config apiserver.local.config + +# Ignore awesome_bot markdown links check output +ab-results-*.json diff --git a/.travis.yml b/.travis.yml index e01954e735..adf7621625 100644 --- a/.travis.yml +++ b/.travis.yml @@ -2,7 +2,8 @@ language: go go: - 1.8 - 1.9 - - tip +# removed because of govet issue with apimahcinery +# - tip go_import_path: k8s.io/kops diff --git a/Makefile b/Makefile index f70ecf1b95..cc93c91bea 100644 --- a/Makefile +++ b/Makefile @@ -594,3 +594,12 @@ bazel-push-gce-run: bazel-push .PHONY: bazel-push-aws-run bazel-push-aws-run: bazel-push ssh -t ${TARGET} sudo SKIP_PACKAGE_UPDATE=1 /tmp/nodeup --conf=/var/cache/kubernetes-install/kube_env.yaml --v=8 + +.PHONY: check-markdown-links +check-markdown-links: + docker run -t -v $$PWD:/tmp \ + -e LC_ALL=C.UTF-8 \ + -e LANG=en_US.UTF-8 \ + -e LANGUAGE=en_US.UTF-8 \ + rubygem/awesome_bot --allow-dupe --allow-redirect \ + $(shell find $$PWD -name "*.md" -mindepth 1 -printf '%P\n' | grep -v vendor | grep -v _vendor | grep -v Changelog.md) diff --git a/OWNERS b/OWNERS index df6b457c39..f9bbdc6263 100644 --- a/OWNERS +++ b/OWNERS @@ -4,3 +4,4 @@ approvers: - chrislovecnm - zmerlynn - andrewsykim + - geojaz diff --git a/_vendor/github.com/gophercloud/gophercloud b/_vendor/github.com/gophercloud/gophercloud index 2bf16b94fd..c7551a666c 160000 --- a/_vendor/github.com/gophercloud/gophercloud +++ b/_vendor/github.com/gophercloud/gophercloud @@ -1 +1 @@ -Subproject commit 2bf16b94fdd9b01557c4d076e567fe5cbbe5a961 +Subproject commit c7551a666c4fee120cc314dce91ba3d0663a86f3 diff --git a/addons/kube-state-metrics/README.md b/addons/kube-state-metrics/README.md new file mode 100644 index 0000000000..412bb6aa2d --- /dev/null +++ b/addons/kube-state-metrics/README.md @@ -0,0 +1,2 @@ +## Usages +channels apply channel kube-state-metrics --yes diff --git a/addons/kube-state-metrics/addon.yaml b/addons/kube-state-metrics/addon.yaml new file mode 100644 index 0000000000..c9c52b29a0 --- /dev/null +++ b/addons/kube-state-metrics/addon.yaml @@ -0,0 +1,13 @@ +kind: Addons +metadata: + name: kube-state-metrics +spec: + addons: + - version: 1.0.1 + selector: + k8s-addon: kube-state-metrics.addons.k8s.io + manifest: v1.0.1.yaml + - version: v1.1.0-rc.0 + selector: + k8s-addon: kube-state-metrics.addons.k8s.io + manifest: v1.1.0-rc.0.yaml diff --git a/addons/kube-state-metrics/v1.0.1.yaml b/addons/kube-state-metrics/v1.0.1.yaml new file mode 100644 index 0000000000..251450cdce --- /dev/null +++ b/addons/kube-state-metrics/v1.0.1.yaml @@ -0,0 +1,158 @@ +apiVersion: rbac.authorization.k8s.io/v1beta1 +kind: ClusterRoleBinding +metadata: + name: kube-state-metrics +roleRef: + apiGroup: rbac.authorization.k8s.io + kind: ClusterRole + name: kube-state-metrics +subjects: +- kind: ServiceAccount + name: kube-state-metrics + namespace: kube-system +--- +apiVersion: rbac.authorization.k8s.io/v1beta1 +kind: ClusterRole +metadata: + name: kube-state-metrics +rules: +- apiGroups: [""] + resources: + - nodes + - pods + - services + - resourcequotas + - replicationcontrollers + - limitranges + - persistentvolumeclaims + - namespaces + verbs: ["list", "watch"] +- apiGroups: ["extensions"] + resources: + - daemonsets + - deployments + - replicasets + verbs: ["list", "watch"] +- apiGroups: ["apps"] + resources: + - statefulsets + verbs: ["list", "watch"] +- apiGroups: ["batch"] + resources: + - cronjobs + - jobs + verbs: ["list", "watch"] +--- +apiVersion: extensions/v1beta1 +kind: Deployment +metadata: + name: kube-state-metrics + namespace: kube-system +spec: + replicas: 1 + template: + metadata: + labels: + k8s-app: kube-state-metrics + spec: + serviceAccountName: kube-state-metrics + containers: + - name: kube-state-metrics + image: quay.io/coreos/kube-state-metrics:v1.0.1 + ports: + - name: http-metrics + containerPort: 8080 + readinessProbe: + httpGet: + path: /healthz + port: 8080 + initialDelaySeconds: 5 + timeoutSeconds: 5 + resources: + requests: + memory: 100Mi + cpu: 100m + limits: + memory: 200Mi + cpu: 200m + - name: addon-resizer + image: gcr.io/google_containers/addon-resizer:1.0 + resources: + limits: + cpu: 100m + memory: 30Mi + requests: + cpu: 100m + memory: 30Mi + env: + - name: MY_POD_NAME + valueFrom: + fieldRef: + fieldPath: metadata.name + - name: MY_POD_NAMESPACE + valueFrom: + fieldRef: + fieldPath: metadata.namespace + command: + - /pod_nanny + - --container=kube-state-metrics + - --cpu=100m + - --extra-cpu=1m + - --memory=100Mi + - --extra-memory=2Mi + - --threshold=5 + - --deployment=kube-state-metrics +--- +apiVersion: rbac.authorization.k8s.io/v1beta1 +kind: RoleBinding +metadata: + name: kube-state-metrics + namespace: kube-system +roleRef: + apiGroup: rbac.authorization.k8s.io + kind: Role + name: kube-state-metrics-resizer +subjects: +- kind: ServiceAccount + name: kube-state-metrics + namespace: kube-system +--- +apiVersion: rbac.authorization.k8s.io/v1beta1 +kind: Role +metadata: + namespace: kube-system + name: kube-state-metrics-resizer +rules: +- apiGroups: [""] + resources: + - pods + verbs: ["get"] +- apiGroups: ["extensions"] + resources: + - deployments + resourceNames: ["kube-state-metrics"] + verbs: ["get", "update"] +--- +apiVersion: v1 +kind: ServiceAccount +metadata: + name: kube-state-metrics + namespace: kube-system +--- +apiVersion: v1 +kind: Service +metadata: + name: kube-state-metrics + namespace: kube-system + labels: + k8s-app: kube-state-metrics + annotations: + prometheus.io/scrape: 'true' +spec: + ports: + - name: http-metrics + port: 8080 + targetPort: http-metrics + protocol: TCP + selector: + k8s-app: kube-state-metrics diff --git a/addons/kube-state-metrics/v1.1.0-rc.0.yaml b/addons/kube-state-metrics/v1.1.0-rc.0.yaml new file mode 100644 index 0000000000..0cfee04563 --- /dev/null +++ b/addons/kube-state-metrics/v1.1.0-rc.0.yaml @@ -0,0 +1,158 @@ +apiVersion: rbac.authorization.k8s.io/v1beta1 +kind: ClusterRoleBinding +metadata: + name: kube-state-metrics +roleRef: + apiGroup: rbac.authorization.k8s.io + kind: ClusterRole + name: kube-state-metrics +subjects: +- kind: ServiceAccount + name: kube-state-metrics + namespace: kube-system +--- +apiVersion: rbac.authorization.k8s.io/v1beta1 +kind: ClusterRole +metadata: + name: kube-state-metrics +rules: +- apiGroups: [""] + resources: + - nodes + - pods + - services + - resourcequotas + - replicationcontrollers + - limitranges + - persistentvolumeclaims + - namespaces + verbs: ["list", "watch"] +- apiGroups: ["extensions"] + resources: + - daemonsets + - deployments + - replicasets + verbs: ["list", "watch"] +- apiGroups: ["apps"] + resources: + - statefulsets + verbs: ["list", "watch"] +- apiGroups: ["batch"] + resources: + - cronjobs + - jobs + verbs: ["list", "watch"] +--- +apiVersion: extensions/v1beta1 +kind: Deployment +metadata: + name: kube-state-metrics + namespace: kube-system +spec: + replicas: 1 + template: + metadata: + labels: + k8s-app: kube-state-metrics + spec: + serviceAccountName: kube-state-metrics + containers: + - name: kube-state-metrics + image: quay.io/coreos/kube-state-metrics:v1.1.0-rc.0 + ports: + - name: http-metrics + containerPort: 8080 + readinessProbe: + httpGet: + path: /healthz + port: 8080 + initialDelaySeconds: 5 + timeoutSeconds: 5 + resources: + requests: + memory: 100Mi + cpu: 100m + limits: + memory: 200Mi + cpu: 200m + - name: addon-resizer + image: gcr.io/google_containers/addon-resizer:1.0 + resources: + limits: + cpu: 100m + memory: 30Mi + requests: + cpu: 100m + memory: 30Mi + env: + - name: MY_POD_NAME + valueFrom: + fieldRef: + fieldPath: metadata.name + - name: MY_POD_NAMESPACE + valueFrom: + fieldRef: + fieldPath: metadata.namespace + command: + - /pod_nanny + - --container=kube-state-metrics + - --cpu=100m + - --extra-cpu=1m + - --memory=100Mi + - --extra-memory=2Mi + - --threshold=5 + - --deployment=kube-state-metrics +--- +apiVersion: rbac.authorization.k8s.io/v1beta1 +kind: RoleBinding +metadata: + name: kube-state-metrics + namespace: kube-system +roleRef: + apiGroup: rbac.authorization.k8s.io + kind: Role + name: kube-state-metrics-resizer +subjects: +- kind: ServiceAccount + name: kube-state-metrics + namespace: kube-system +--- +apiVersion: rbac.authorization.k8s.io/v1beta1 +kind: Role +metadata: + namespace: kube-system + name: kube-state-metrics-resizer +rules: +- apiGroups: [""] + resources: + - pods + verbs: ["get"] +- apiGroups: ["extensions"] + resources: + - deployments + resourceNames: ["kube-state-metrics"] + verbs: ["get", "update"] +--- +apiVersion: v1 +kind: ServiceAccount +metadata: + name: kube-state-metrics + namespace: kube-system +--- +apiVersion: v1 +kind: Service +metadata: + name: kube-state-metrics + namespace: kube-system + labels: + k8s-app: kube-state-metrics + annotations: + prometheus.io/scrape: 'true' +spec: + ports: + - name: http-metrics + port: 8080 + targetPort: http-metrics + protocol: TCP + selector: + k8s-app: kube-state-metrics diff --git a/addons/logging-elasticsearch/addon.yaml b/addons/logging-elasticsearch/addon.yaml index 69916ae6d3..bfdb7fe14c 100644 --- a/addons/logging-elasticsearch/addon.yaml +++ b/addons/logging-elasticsearch/addon.yaml @@ -7,4 +7,9 @@ spec: selector: k8s-addon: logging-elasticsearch.addons.k8s.io manifest: v1.5.0.yaml - kubernetesVersion: ">=1.5.0" # We use statefulsets + kubernetesVersion: ">=1.5.0 <1.6.0" # We use statefulsets + - version: 1.6.0 + selector: + k8s-addon: logging-elasticsearch.addons.k8s.io + manifest: v1.6.0.yaml + kubernetesVersion: ">=1.6.0" # RBAC v1beta1 is introduced as of v1.6.0 diff --git a/addons/logging-elasticsearch/v1.6.0.yaml b/addons/logging-elasticsearch/v1.6.0.yaml new file mode 100644 index 0000000000..ce7feac150 --- /dev/null +++ b/addons/logging-elasticsearch/v1.6.0.yaml @@ -0,0 +1,280 @@ +apiVersion: v1 +kind: ServiceAccount +metadata: + name: elasticsearch-logging + namespace: kube-system + labels: + k8s-app: elasticsearch-logging + +--- + +kind: ClusterRole +apiVersion: rbac.authorization.k8s.io/v1beta1 +metadata: + name: elasticsearch-logging + labels: + k8s-app: elasticsearch-logging +rules: +- apiGroups: + - "" + resources: + - "services" + - "namespaces" + - "endpoints" + verbs: + - "get" + +--- + +kind: ClusterRoleBinding +apiVersion: rbac.authorization.k8s.io/v1beta1 +metadata: + namespace: kube-system + name: elasticsearch-logging + labels: + k8s-app: elasticsearch-logging +subjects: +- kind: ServiceAccount + name: elasticsearch-logging + namespace: kube-system + apiGroup: "" +roleRef: + kind: ClusterRole + name: elasticsearch-logging + apiGroup: "" + +--- + +apiVersion: v1 +kind: ServiceAccount +metadata: + name: fluentd-es + namespace: kube-system + labels: + k8s-app: fluentd-es + +--- + +kind: ClusterRole +apiVersion: rbac.authorization.k8s.io/v1beta1 +metadata: + name: fluentd-es + labels: + k8s-app: fluentd-es +rules: +- apiGroups: + - "" + resources: + - "namespaces" + - "pods" + verbs: + - "get" + - "watch" + - "list" + +--- + +kind: ClusterRoleBinding +apiVersion: rbac.authorization.k8s.io/v1beta1 +metadata: + name: fluentd-es + labels: + k8s-app: fluentd-es +subjects: +- kind: ServiceAccount + name: fluentd-es + namespace: kube-system + apiGroup: "" +roleRef: + kind: ClusterRole + name: fluentd-es + apiGroup: "" + +--- + +apiVersion: extensions/v1beta1 +kind: DaemonSet +metadata: + name: fluentd-es + namespace: kube-system + labels: + k8s-addon: logging-elasticsearch.addons.k8s.io + k8s-app: fluentd-es + kubernetes.io/cluster-service: "true" + version: v1.22 +spec: + template: + metadata: + labels: + k8s-app: fluentd-es + kubernetes.io/cluster-service: "true" + version: v1.22 + spec: + serviceAccountName: fluentd-es + containers: + - name: fluentd-es + image: gcr.io/google_containers/fluentd-elasticsearch:1.22 + command: + - '/bin/sh' + - '-c' + - '/usr/sbin/td-agent 2>&1 >> /var/log/fluentd.log' + resources: + limits: + memory: 200Mi + requests: + cpu: 100m + memory: 200Mi + volumeMounts: + - name: varlog + mountPath: /var/log + - name: varlibdockercontainers + mountPath: /var/lib/docker/containers + readOnly: true + #nodeSelector: + # alpha.kubernetes.io/fluentd-ds-ready: "true" + terminationGracePeriodSeconds: 30 + volumes: + - name: varlog + hostPath: + path: /var/log + - name: varlibdockercontainers + hostPath: + path: /var/lib/docker/containers + +--- + +apiVersion: v1 +kind: Service +metadata: + name: elasticsearch-logging + namespace: kube-system + labels: + k8s-addon: logging-elasticsearch.addons.k8s.io + k8s-app: elasticsearch-logging + kubernetes.io/cluster-service: "true" + kubernetes.io/name: "Elasticsearch" +spec: + ports: + - port: 9200 + protocol: TCP + targetPort: db + selector: + k8s-app: elasticsearch-logging + +--- + +apiVersion: apps/v1beta1 +kind: StatefulSet +metadata: + name: elasticsearch-logging + namespace: kube-system + labels: + k8s-addon: logging-elasticsearch.addons.k8s.io + k8s-app: elasticsearch-logging + version: v1 + kubernetes.io/cluster-service: "true" +spec: + serviceName: elasticsearch-logging + replicas: 2 + template: + metadata: + labels: + k8s-app: elasticsearch-logging + version: v1 + kubernetes.io/cluster-service: "true" + spec: + serviceAccountName: elasticsearch-logging + containers: + - image: gcr.io/google_containers/elasticsearch:v2.4.1-2 + name: elasticsearch-logging + resources: + # need more cpu upon initialization, therefore burstable class + limits: + cpu: 1000m + requests: + cpu: 100m + ports: + - containerPort: 9200 + name: db + protocol: TCP + - containerPort: 9300 + name: transport + protocol: TCP + volumeMounts: + - name: es-persistent-storage + mountPath: /data + env: + - name: "NAMESPACE" + valueFrom: + fieldRef: + fieldPath: metadata.namespace + volumeClaimTemplates: + - metadata: + name: es-persistent-storage + annotations: + volume.beta.kubernetes.io/storage-class: "default" + spec: + accessModes: [ "ReadWriteOnce" ] + resources: + requests: + storage: 20Gi + +--- + +apiVersion: extensions/v1beta1 +kind: Deployment +metadata: + name: kibana-logging + namespace: kube-system + labels: + k8s-addon: logging-elasticsearch.addons.k8s.io + k8s-app: kibana-logging + kubernetes.io/cluster-service: "true" +spec: + replicas: 1 + selector: + matchLabels: + k8s-app: kibana-logging + template: + metadata: + labels: + k8s-app: kibana-logging + spec: + containers: + - name: kibana-logging + image: gcr.io/google_containers/kibana:v4.6.1-1 + resources: + # keep request = limit to keep this container in guaranteed class + limits: + cpu: 100m + requests: + cpu: 100m + env: + - name: "ELASTICSEARCH_URL" + value: "http://elasticsearch-logging:9200" + - name: "KIBANA_BASE_URL" + value: "/api/v1/proxy/namespaces/kube-system/services/kibana-logging" + ports: + - containerPort: 5601 + name: ui + protocol: TCP + +--- + +apiVersion: v1 +kind: Service +metadata: + name: kibana-logging + namespace: kube-system + labels: + k8s-addon: logging-elasticsearch.addons.k8s.io + k8s-app: kibana-logging + kubernetes.io/cluster-service: "true" + kubernetes.io/name: "Kibana" +spec: + ports: + - port: 5601 + protocol: TCP + targetPort: ui + selector: + k8s-app: kibana-logging \ No newline at end of file diff --git a/channels/cmd/channels/main.go b/channels/cmd/channels/main.go index fdeba11715..9736cb6736 100644 --- a/channels/cmd/channels/main.go +++ b/channels/cmd/channels/main.go @@ -18,8 +18,9 @@ package main import ( "fmt" - "k8s.io/kops/channels/pkg/cmd" "os" + + "k8s.io/kops/channels/pkg/cmd" ) func main() { diff --git a/channels/pkg/channels/addon.go b/channels/pkg/channels/addon.go index 9b64c45ac2..b378668aea 100644 --- a/channels/pkg/channels/addon.go +++ b/channels/pkg/channels/addon.go @@ -18,11 +18,12 @@ package channels import ( "fmt" + "net/url" + "github.com/golang/glog" "k8s.io/apimachinery/pkg/util/validation/field" "k8s.io/client-go/kubernetes" "k8s.io/kops/channels/pkg/api" - "net/url" ) // Addon is a wrapper around a single version of an addon diff --git a/channels/pkg/channels/addons.go b/channels/pkg/channels/addons.go index 0893766128..efe76ecdc5 100644 --- a/channels/pkg/channels/addons.go +++ b/channels/pkg/channels/addons.go @@ -18,13 +18,14 @@ package channels import ( "fmt" + "net/url" + "strings" + "github.com/blang/semver" "github.com/golang/glog" "k8s.io/kops/channels/pkg/api" "k8s.io/kops/upup/pkg/fi/utils" "k8s.io/kops/util/pkg/vfs" - "net/url" - "strings" ) type Addons struct { diff --git a/channels/pkg/channels/addons_test.go b/channels/pkg/channels/addons_test.go index 810d10bf95..6c03ed9b11 100644 --- a/channels/pkg/channels/addons_test.go +++ b/channels/pkg/channels/addons_test.go @@ -17,9 +17,10 @@ limitations under the License. package channels import ( + "testing" + "github.com/blang/semver" "k8s.io/kops/channels/pkg/api" - "testing" ) func Test_Filtering(t *testing.T) { diff --git a/channels/pkg/channels/apply.go b/channels/pkg/channels/apply.go index c65f17f4bc..c4356c6326 100644 --- a/channels/pkg/channels/apply.go +++ b/channels/pkg/channels/apply.go @@ -18,13 +18,14 @@ package channels import ( "fmt" - "github.com/golang/glog" "io/ioutil" - "k8s.io/kops/util/pkg/vfs" "os" "os/exec" "path" "strings" + + "github.com/golang/glog" + "k8s.io/kops/util/pkg/vfs" ) // Apply calls kubectl apply to apply the manifest. diff --git a/channels/pkg/channels/channel_version.go b/channels/pkg/channels/channel_version.go index 5863cb678e..ab6a53e3e6 100644 --- a/channels/pkg/channels/channel_version.go +++ b/channels/pkg/channels/channel_version.go @@ -19,13 +19,14 @@ package channels import ( "encoding/json" "fmt" + "strings" + "github.com/blang/semver" "github.com/golang/glog" "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/types" "k8s.io/client-go/kubernetes" - "strings" ) const AnnotationPrefix = "addons.k8s.io/" diff --git a/channels/pkg/cmd/apply.go b/channels/pkg/cmd/apply.go index dd61a4641a..4d8f83699b 100644 --- a/channels/pkg/cmd/apply.go +++ b/channels/pkg/cmd/apply.go @@ -17,8 +17,9 @@ limitations under the License. package cmd import ( - "github.com/spf13/cobra" "io" + + "github.com/spf13/cobra" ) func NewCmdApply(f Factory, out io.Writer) *cobra.Command { diff --git a/channels/pkg/cmd/apply_channel.go b/channels/pkg/cmd/apply_channel.go index e002dfb197..4dcd4e5987 100644 --- a/channels/pkg/cmd/apply_channel.go +++ b/channels/pkg/cmd/apply_channel.go @@ -18,14 +18,15 @@ package cmd import ( "fmt" - "github.com/blang/semver" - "github.com/spf13/cobra" "io" - "k8s.io/kops/channels/pkg/channels" - "k8s.io/kops/util/pkg/tables" "net/url" "os" "strings" + + "github.com/blang/semver" + "github.com/spf13/cobra" + "k8s.io/kops/channels/pkg/channels" + "k8s.io/kops/util/pkg/tables" ) type ApplyChannelOptions struct { diff --git a/channels/pkg/cmd/get.go b/channels/pkg/cmd/get.go index dd10ee9df0..78b760d584 100644 --- a/channels/pkg/cmd/get.go +++ b/channels/pkg/cmd/get.go @@ -17,8 +17,9 @@ limitations under the License. package cmd import ( - "github.com/spf13/cobra" "io" + + "github.com/spf13/cobra" ) func NewCmdGet(f Factory, out io.Writer) *cobra.Command { diff --git a/channels/pkg/cmd/get_addons.go b/channels/pkg/cmd/get_addons.go index 481220804d..dbb1498be0 100644 --- a/channels/pkg/cmd/get_addons.go +++ b/channels/pkg/cmd/get_addons.go @@ -34,13 +34,14 @@ package cmd import ( "fmt" - "github.com/spf13/cobra" "io" + "os" + + "github.com/spf13/cobra" "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/kops/channels/pkg/channels" "k8s.io/kops/util/pkg/tables" - "os" ) type GetAddonsOptions struct { diff --git a/channels/pkg/cmd/root.go b/channels/pkg/cmd/root.go index c8353ba86f..c100310e3a 100644 --- a/channels/pkg/cmd/root.go +++ b/channels/pkg/cmd/root.go @@ -20,9 +20,10 @@ import ( goflag "flag" "fmt" + "io" + "github.com/spf13/cobra" "github.com/spf13/viper" - "io" ) type CmdRootOptions struct { diff --git a/cloudmock/aws/mockec2/address.go b/cloudmock/aws/mockec2/address.go index 0fe4e981a8..666db705b2 100644 --- a/cloudmock/aws/mockec2/address.go +++ b/cloudmock/aws/mockec2/address.go @@ -19,10 +19,11 @@ package mockec2 import ( "encoding/binary" "fmt" + "net" + "github.com/aws/aws-sdk-go/aws/request" "github.com/aws/aws-sdk-go/service/ec2" "github.com/golang/glog" - "net" ) func (m *MockEC2) AllocateAddressRequest(*ec2.AllocateAddressInput) (*request.Request, *ec2.AllocateAddressOutput) { diff --git a/cloudmock/aws/mockec2/api.go b/cloudmock/aws/mockec2/api.go index 8c21835ccd..9758564cc7 100644 --- a/cloudmock/aws/mockec2/api.go +++ b/cloudmock/aws/mockec2/api.go @@ -33,7 +33,7 @@ type MockEC2 struct { SecurityGroups []*ec2.SecurityGroup subnetNumber int - Subnets []*ec2.Subnet + subnets map[string]*subnetInfo volumeNumber int Volumes []*ec2.Volume diff --git a/cloudmock/aws/mockec2/images.go b/cloudmock/aws/mockec2/images.go index b99d2f42cf..d5a516739b 100644 --- a/cloudmock/aws/mockec2/images.go +++ b/cloudmock/aws/mockec2/images.go @@ -18,11 +18,12 @@ package mockec2 import ( "fmt" + "strings" + "github.com/aws/aws-sdk-go/aws" "github.com/aws/aws-sdk-go/aws/request" "github.com/aws/aws-sdk-go/service/ec2" "github.com/golang/glog" - "strings" ) func (m *MockEC2) DescribeImageAttributeRequest(*ec2.DescribeImageAttributeInput) (*request.Request, *ec2.DescribeImageAttributeOutput) { diff --git a/cloudmock/aws/mockec2/keypairs.go b/cloudmock/aws/mockec2/keypairs.go index 66a7755ecc..e2c22429e3 100644 --- a/cloudmock/aws/mockec2/keypairs.go +++ b/cloudmock/aws/mockec2/keypairs.go @@ -18,6 +18,7 @@ package mockec2 import ( "fmt" + "github.com/aws/aws-sdk-go/aws" "github.com/aws/aws-sdk-go/aws/request" "github.com/aws/aws-sdk-go/service/ec2" diff --git a/cloudmock/aws/mockec2/securitygroups.go b/cloudmock/aws/mockec2/securitygroups.go index fb3dfb6db5..733697932a 100644 --- a/cloudmock/aws/mockec2/securitygroups.go +++ b/cloudmock/aws/mockec2/securitygroups.go @@ -18,10 +18,11 @@ package mockec2 import ( "fmt" + "strings" + "github.com/aws/aws-sdk-go/aws/request" "github.com/aws/aws-sdk-go/service/ec2" "github.com/golang/glog" - "strings" ) func (m *MockEC2) CreateSecurityGroupRequest(*ec2.CreateSecurityGroupInput) (*request.Request, *ec2.CreateSecurityGroupOutput) { diff --git a/cloudmock/aws/mockec2/subnets.go b/cloudmock/aws/mockec2/subnets.go index 735541e774..e978251e0c 100644 --- a/cloudmock/aws/mockec2/subnets.go +++ b/cloudmock/aws/mockec2/subnets.go @@ -18,12 +18,36 @@ package mockec2 import ( "fmt" + "strings" + "github.com/aws/aws-sdk-go/aws/request" "github.com/aws/aws-sdk-go/service/ec2" "github.com/golang/glog" - "strings" ) +type subnetInfo struct { + main ec2.Subnet +} + +func (m *MockEC2) FindSubnet(id string) *ec2.Subnet { + subnet := m.subnets[id] + if subnet == nil { + return nil + } + + copy := subnet.main + copy.Tags = m.getTags(ec2.ResourceTypeSubnet, id) + return © +} + +func (m *MockEC2) SubnetIds() []string { + var ids []string + for id := range m.subnets { + ids = append(ids, id) + } + return ids +} + func (m *MockEC2) CreateSubnetRequest(*ec2.CreateSubnetInput) (*request.Request, *ec2.CreateSubnetOutput) { panic("Not implemented") return nil, nil @@ -40,7 +64,14 @@ func (m *MockEC2) CreateSubnet(request *ec2.CreateSubnetInput) (*ec2.CreateSubne VpcId: request.VpcId, CidrBlock: request.CidrBlock, } - m.Subnets = append(m.Subnets, subnet) + + if m.subnets == nil { + m.subnets = make(map[string]*subnetInfo) + } + m.subnets[*subnet.SubnetId] = &subnetInfo{ + main: *subnet, + } + response := &ec2.CreateSubnetOutput{ Subnet: subnet, } @@ -57,7 +88,7 @@ func (m *MockEC2) DescribeSubnets(request *ec2.DescribeSubnetsInput) (*ec2.Descr var subnets []*ec2.Subnet - for _, subnet := range m.Subnets { + for id, subnet := range m.subnets { allFiltersMatch := true for _, filter := range request.Filters { match := false @@ -65,7 +96,7 @@ func (m *MockEC2) DescribeSubnets(request *ec2.DescribeSubnetsInput) (*ec2.Descr default: if strings.HasPrefix(*filter.Name, "tag:") { - match = m.hasTag(ec2.ResourceTypeSubnet, *subnet.SubnetId, filter) + match = m.hasTag(ec2.ResourceTypeSubnet, *subnet.main.SubnetId, filter) } else { return nil, fmt.Errorf("unknown filter name: %q", *filter.Name) } @@ -81,8 +112,8 @@ func (m *MockEC2) DescribeSubnets(request *ec2.DescribeSubnetsInput) (*ec2.Descr continue } - copy := *subnet - copy.Tags = m.getTags(ec2.ResourceTypeSubnet, *subnet.SubnetId) + copy := subnet.main + copy.Tags = m.getTags(ec2.ResourceTypeSubnet, id) subnets = append(subnets, ©) } diff --git a/cloudmock/aws/mockec2/tags.go b/cloudmock/aws/mockec2/tags.go index 6e0a464f55..d914e19c2b 100644 --- a/cloudmock/aws/mockec2/tags.go +++ b/cloudmock/aws/mockec2/tags.go @@ -18,10 +18,13 @@ package mockec2 import ( "fmt" + "sort" + "strings" + + "github.com/aws/aws-sdk-go/aws" "github.com/aws/aws-sdk-go/aws/request" "github.com/aws/aws-sdk-go/service/ec2" "github.com/golang/glog" - "strings" ) func (m *MockEC2) CreateTagsRequest(*ec2.CreateTagsInput) (*request.Request, *ec2.CreateTagsOutput) { @@ -165,3 +168,14 @@ func (m *MockEC2) DescribeTagsPages(*ec2.DescribeTagsInput, func(*ec2.DescribeTa panic("Not implemented") return nil } + +// SortTags sorts the slice of tags by Key +func SortTags(tags []*ec2.Tag) { + keys := make([]string, len(tags)) + for i := range tags { + if tags[i] != nil { + keys[i] = aws.StringValue(tags[i].Key) + } + } + sort.SliceStable(tags, func(i, j int) bool { return keys[i] < keys[j] }) +} diff --git a/cloudmock/aws/mockec2/volumes.go b/cloudmock/aws/mockec2/volumes.go index af169c2e87..f5e6420b32 100644 --- a/cloudmock/aws/mockec2/volumes.go +++ b/cloudmock/aws/mockec2/volumes.go @@ -18,10 +18,11 @@ package mockec2 import ( "fmt" + "strings" + "github.com/aws/aws-sdk-go/aws/request" "github.com/aws/aws-sdk-go/service/ec2" "github.com/golang/glog" - "strings" ) func (m *MockEC2) DescribeVolumeAttributeRequest(*ec2.DescribeVolumeAttributeInput) (*request.Request, *ec2.DescribeVolumeAttributeOutput) { diff --git a/cloudmock/aws/mockec2/vpcs.go b/cloudmock/aws/mockec2/vpcs.go index a2d4327807..5f71a9f730 100644 --- a/cloudmock/aws/mockec2/vpcs.go +++ b/cloudmock/aws/mockec2/vpcs.go @@ -18,11 +18,12 @@ package mockec2 import ( "fmt" + "strings" + "github.com/aws/aws-sdk-go/aws" "github.com/aws/aws-sdk-go/aws/request" "github.com/aws/aws-sdk-go/service/ec2" "github.com/golang/glog" - "strings" ) type vpcInfo struct { diff --git a/cloudmock/aws/mockroute53/api.go b/cloudmock/aws/mockroute53/api.go index c7c2186dd5..a2a51da7ce 100644 --- a/cloudmock/aws/mockroute53/api.go +++ b/cloudmock/aws/mockroute53/api.go @@ -17,10 +17,11 @@ limitations under the License. package mockroute53 import ( + "strings" + "github.com/aws/aws-sdk-go/aws" "github.com/aws/aws-sdk-go/service/route53" "github.com/aws/aws-sdk-go/service/route53/route53iface" - "strings" ) type zoneInfo struct { diff --git a/cloudmock/aws/mockroute53/records.go b/cloudmock/aws/mockroute53/records.go index 87d6e7a73a..a21f73ad82 100644 --- a/cloudmock/aws/mockroute53/records.go +++ b/cloudmock/aws/mockroute53/records.go @@ -18,6 +18,7 @@ package mockroute53 import ( "fmt" + "github.com/aws/aws-sdk-go/aws" "github.com/aws/aws-sdk-go/aws/request" "github.com/aws/aws-sdk-go/service/route53" diff --git a/cloudmock/aws/mockroute53/zones.go b/cloudmock/aws/mockroute53/zones.go index 408bea86aa..4216e6ff82 100644 --- a/cloudmock/aws/mockroute53/zones.go +++ b/cloudmock/aws/mockroute53/zones.go @@ -18,6 +18,7 @@ package mockroute53 import ( "fmt" + "github.com/aws/aws-sdk-go/aws/request" "github.com/aws/aws-sdk-go/service/route53" "github.com/golang/glog" diff --git a/cmd/kops-server/main.go b/cmd/kops-server/main.go index d79b581465..fa3fe1f42e 100644 --- a/cmd/kops-server/main.go +++ b/cmd/kops-server/main.go @@ -21,11 +21,12 @@ import ( "os" "runtime" + "math/rand" + "time" + "github.com/golang/glog" "k8s.io/apiserver/pkg/util/logs" "k8s.io/kops/pkg/apiserver/cmd/server" - "math/rand" - "time" ) func main() { diff --git a/cmd/kops/BUILD.bazel b/cmd/kops/BUILD.bazel index 4961c6acd3..a31fb81dc6 100644 --- a/cmd/kops/BUILD.bazel +++ b/cmd/kops/BUILD.bazel @@ -91,6 +91,7 @@ go_library( "//util/pkg/vfs:go_default_library", "//vendor/github.com/aws/aws-sdk-go/aws:go_default_library", "//vendor/github.com/blang/semver:go_default_library", + "//vendor/github.com/ghodss/yaml:go_default_library", "//vendor/github.com/golang/glog:go_default_library", "//vendor/github.com/spf13/cobra:go_default_library", "//vendor/github.com/spf13/cobra/doc:go_default_library", diff --git a/cmd/kops/create.go b/cmd/kops/create.go index ef4a9e8a99..9db4254a3f 100644 --- a/cmd/kops/create.go +++ b/cmd/kops/create.go @@ -130,6 +130,7 @@ func RunCreate(f *util.Factory, out io.Writer, c *CreateOptions) error { return fmt.Errorf("error reading file %q: %v", f, err) } + // TODO: this does not support a JSON array sections := bytes.Split(contents, []byte("\n---\n")) for _, section := range sections { defaults := &schema.GroupVersionKind{ diff --git a/cmd/kops/create_cluster.go b/cmd/kops/create_cluster.go index c45bec4b8a..32930a64c0 100644 --- a/cmd/kops/create_cluster.go +++ b/cmd/kops/create_cluster.go @@ -28,6 +28,7 @@ import ( "github.com/golang/glog" "github.com/spf13/cobra" apierrors "k8s.io/apimachinery/pkg/api/errors" + "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/util/sets" "k8s.io/kops" "k8s.io/kops/cmd/kops/util" @@ -124,6 +125,11 @@ type CreateClusterOptions struct { // ConfigBase is the location where we will store the configuration, it defaults to the state store ConfigBase string + + // DryRun mode output a cluster manifest of Output type. + DryRun bool + // Output type during a DryRun + Output string } func (o *CreateClusterOptions) InitDefaults() { @@ -190,6 +196,11 @@ var ( --project my-gce-project \ --image "ubuntu-os-cloud/ubuntu-1604-xenial-v20170202" \ --yes + # Create manifest for a cluster in AWS + kops create cluster --name=kubernetes-cluster.example.com \ + --state=s3://kops-state-1234 --zones=eu-west-1a \ + --node-count=2 --dry-run -oyaml + `)) create_cluster_short = i18n.T("Create a Kubernetes cluster.") @@ -227,7 +238,7 @@ func NewCmdCreateCluster(f *util.Factory, out io.Writer) *cobra.Command { } cmd.Flags().BoolVar(&options.Yes, "yes", options.Yes, "Specify --yes to immediately create the cluster") - cmd.Flags().StringVar(&options.Target, "target", options.Target, "Target - direct, terraform, cloudformation") + cmd.Flags().StringVar(&options.Target, "target", options.Target, fmt.Sprintf("Valid targets: %s, %s, %s. Set this flag to %s if you want kops to generate terraform", cloudup.TargetDirect, cloudup.TargetTerraform, cloudup.TargetDirect, cloudup.TargetTerraform)) cmd.Flags().StringVar(&options.Models, "model", options.Models, "Models to apply (separate multiple models with commas)") // Configuration / state location @@ -297,6 +308,10 @@ func NewCmdCreateCluster(f *util.Factory, out io.Writer) *cobra.Command { cmd.Flags().StringVar(&options.APILoadBalancerType, "api-loadbalancer-type", options.APILoadBalancerType, "Sets the API loadbalancer type to either 'public' or 'internal'") + // DryRun mode that will print YAML or JSON + cmd.Flags().BoolVar(&options.DryRun, "dry-run", options.DryRun, "If true, only print the object that would be sent, without sending it. This flag can be used to create a cluster YAML or JSON manifest.") + cmd.Flags().StringVarP(&options.Output, "output", "o", options.Output, "Ouput format. One of json|yaml. Used with the --dry-run flag.") + if featureflag.SpecOverrideFlag.Enabled() { cmd.Flags().StringSliceVar(&options.Overrides, "override", options.Overrides, "Directly configure values in the spec") } @@ -326,6 +341,11 @@ func RunCreateCluster(f *util.Factory, out io.Writer, c *CreateClusterOptions) e isDryrun = true targetName = cloudup.TargetDryRun } + + if c.DryRun && c.Output == "" { + return fmt.Errorf("unable to execute --dry-run without setting --output") + } + clusterName := c.ClusterName if clusterName == "" { return fmt.Errorf("--name is required") @@ -919,8 +939,10 @@ func RunCreateCluster(f *util.Factory, out io.Writer, c *CreateClusterOptions) e } } + // Use Strict IAM policy and allow AWS ECR by default when creating a new cluster cluster.Spec.IAM = &api.IAMSpec{ - Legacy: false, + AllowContainerRegistry: true, + Legacy: false, } sshPublicKeys := make(map[string][]byte) @@ -985,6 +1007,32 @@ func RunCreateCluster(f *util.Factory, out io.Writer, c *CreateClusterOptions) e return err } + if c.DryRun { + var obj []runtime.Object + obj = append(obj, cluster) + + for _, group := range fullInstanceGroups { + // Cluster name is not populated, and we need it + group.ObjectMeta.Labels = make(map[string]string) + group.ObjectMeta.Labels[api.LabelClusterName] = cluster.ObjectMeta.Name + obj = append(obj, group) + } + switch c.Output { + case OutputYaml: + if err := fullOutputYAML(out, obj...); err != nil { + return fmt.Errorf("error writing cluster yaml to stdout: %v", err) + } + return nil + case OutputJSON: + if err := fullOutputJSON(out, obj...); err != nil { + return fmt.Errorf("error writing cluster json to stdout: %v", err) + } + return nil + default: + return fmt.Errorf("unsupported output type %q", c.Output) + } + } + // Note we perform as much validation as we can, before writing a bad config err = registry.CreateClusterConfig(clientset, cluster, fullInstanceGroups) if err != nil { @@ -1008,6 +1056,7 @@ func RunCreateCluster(f *util.Factory, out io.Writer, c *CreateClusterOptions) e } } + // Can we acutally get to this if?? if targetName != "" { if isDryrun { fmt.Fprintf(out, "Previewing changes that will be made:\n\n") diff --git a/cmd/kops/create_ig.go b/cmd/kops/create_ig.go index abf8c040d7..dbff7591a1 100644 --- a/cmd/kops/create_ig.go +++ b/cmd/kops/create_ig.go @@ -39,6 +39,10 @@ import ( type CreateInstanceGroupOptions struct { Role string Subnets []string + // DryRun mode output an ig manifest of Output type. + DryRun bool + // Output type during a DryRun + Output string } var ( @@ -52,6 +56,10 @@ var ( # Create an instancegroup for the k8s-cluster.example.com cluster. kops create ig --name=k8s-cluster.example.com node-example \ --role node --subnet my-subnet-name + + # Create a YAML manifest for an instancegroup for the k8s-cluster.example.com cluster. + kops create ig --name=k8s-cluster.example.com node-example \ + --role node --subnet my-subnet-name --dry-run -oyaml `)) create_ig_short = i18n.T(`Create an instancegroup.`) @@ -85,6 +93,9 @@ func NewCmdCreateInstanceGroup(f *util.Factory, out io.Writer) *cobra.Command { cmd.Flags().StringVar(&options.Role, "role", options.Role, "Type of instance group to create ("+strings.Join(allRoles, ",")+")") cmd.Flags().StringSliceVar(&options.Subnets, "subnet", options.Subnets, "Subnets in which to create instance group") + // DryRun mode that will print YAML or JSON + cmd.Flags().BoolVar(&options.DryRun, "dry-run", options.DryRun, "If true, only print the object that would be sent, without sending it. This flag can be used to create a cluster YAML or JSON manifest.") + cmd.Flags().StringVarP(&options.Output, "output", "o", options.Output, "Ouput format. One of json|yaml") return cmd } @@ -142,6 +153,32 @@ func RunCreateInstanceGroup(f *util.Factory, cmd *cobra.Command, args []string, return err } + if options.DryRun { + + if options.Output == "" { + return fmt.Errorf("must set output flag; yaml or json") + } + + // Cluster name is not populated, and we need it + ig.ObjectMeta.Labels = make(map[string]string) + ig.ObjectMeta.Labels[api.LabelClusterName] = cluster.ObjectMeta.Name + + switch options.Output { + case OutputYaml: + if err := fullOutputYAML(out, ig); err != nil { + return fmt.Errorf("error writing cluster yaml to stdout: %v", err) + } + return nil + case OutputJSON: + if err := fullOutputJSON(out, ig); err != nil { + return fmt.Errorf("error writing cluster json to stdout: %v", err) + } + return nil + default: + return fmt.Errorf("unsupported output type %q", options.Output) + } + } + var ( edit = editor.NewDefaultEditor(editorEnvs) ) diff --git a/cmd/kops/delete.go b/cmd/kops/delete.go index a9b6f92e5a..c07d20f0d8 100644 --- a/cmd/kops/delete.go +++ b/cmd/kops/delete.go @@ -21,6 +21,7 @@ import ( "io" "bytes" + "github.com/golang/glog" "github.com/spf13/cobra" "k8s.io/apimachinery/pkg/runtime/schema" diff --git a/cmd/kops/get.go b/cmd/kops/get.go index 19371291e2..cb345a17a5 100644 --- a/cmd/kops/get.go +++ b/cmd/kops/get.go @@ -17,7 +17,6 @@ limitations under the License. package main import ( - "encoding/json" "fmt" "io" @@ -153,35 +152,27 @@ func RunGet(context Factory, out io.Writer, options *GetOptions) error { return err } + var obj []runtime.Object + if options.output != OutputTable { + obj = append(obj, cluster) + for _, group := range instancegroups { + obj = append(obj, group) + } + } + switch options.output { case OutputYaml: - - err = clusterOutputYAML(clusters, out) - if err != nil { - return err + if err := fullOutputYAML(out, obj...); err != nil { + return fmt.Errorf("error writing cluster yaml to stdout: %v", err) } - if err := writeYAMLSep(out); err != nil { - return err - } - - err = igOutputYAML(instancegroups, out) - if err != nil { - return err - } + return nil case OutputJSON: - return fmt.Errorf("not implemented") - // TODO this is not outputing valid json. Not sure what cluster and instance groups should look like - /* - err = clusterOutputJson(clusters,out) - if err != nil { - return err - } - err = igOutputJson(instancegroups,out) - if err != nil { - return err - }*/ + if err := fullOutputJSON(out, obj...); err != nil { + return fmt.Errorf("error writing cluster json to stdout: %v", err) + } + return nil case OutputTable: fmt.Fprintf(os.Stdout, "Cluster\n") @@ -235,7 +226,7 @@ func marshalYaml(obj runtime.Object) ([]byte, error) { // obj must be a pointer to a marshalable object func marshalJSON(obj runtime.Object) ([]byte, error) { - j, err := json.MarshalIndent(obj, "", " ") + j, err := kopscodecs.ToVersionedJSON(obj) if err != nil { return nil, fmt.Errorf("error marshaling json: %v", err) } diff --git a/cmd/kops/get_cluster.go b/cmd/kops/get_cluster.go index 4c28a2e5a7..3e23457422 100644 --- a/cmd/kops/get_cluster.go +++ b/cmd/kops/get_cluster.go @@ -18,13 +18,13 @@ package main import ( "fmt" + "io" "os" "strings" - "io" - "github.com/spf13/cobra" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/util/sets" "k8s.io/kops/cmd/kops/util" api "k8s.io/kops/pkg/apis/kops" @@ -133,7 +133,7 @@ func RunGetClusters(context Factory, out io.Writer, options *GetClusterOptions) } if len(clusters) == 0 { - return fmt.Errorf("No clusters found") + return fmt.Errorf("no clusters found") } if options.FullSpec { @@ -146,14 +146,20 @@ func RunGetClusters(context Factory, out io.Writer, options *GetClusterOptions) fmt.Fprint(out, get_cluster_full_warning) } + var obj []runtime.Object + if options.output != OutputTable { + for _, c := range clusters { + obj = append(obj, c) + } + } + switch options.output { case OutputTable: return clusterOutputTable(clusters, out) case OutputYaml: - return clusterOutputYAML(clusters, out) + return fullOutputYAML(out, obj...) case OutputJSON: - return clusterOutputJson(clusters, out) - + return fullOutputJSON(out, obj...) default: return fmt.Errorf("Unknown output format: %q", options.output) } @@ -206,23 +212,47 @@ func clusterOutputTable(clusters []*api.Cluster, out io.Writer) error { return t.Render(clusters, out, "NAME", "CLOUD", "ZONES") } -func clusterOutputJson(clusters []*api.Cluster, out io.Writer) error { - for _, cluster := range clusters { - if err := marshalToWriter(cluster, marshalJSON, out); err != nil { +// fullOutputJson outputs the marshalled JSON of a list of clusters and instance groups. It will handle +// nils for clusters and instanceGroups slices. +func fullOutputJSON(out io.Writer, args ...runtime.Object) error { + argsLen := len(args) + + if argsLen > 1 { + if _, err := fmt.Fprint(out, "["); err != nil { return err } } + + for i, arg := range args { + if i != 0 { + if _, err := fmt.Fprint(out, ","); err != nil { + return err + } + } + if err := marshalToWriter(arg, marshalJSON, out); err != nil { + return err + } + } + + if argsLen > 1 { + if _, err := fmt.Fprint(out, "]"); err != nil { + return err + } + } + return nil } -func clusterOutputYAML(clusters []*api.Cluster, out io.Writer) error { - for i, cluster := range clusters { +// fullOutputJson outputs the marshalled JSON of a list of clusters and instance groups. It will handle +// nils for clusters and instanceGroups slices. +func fullOutputYAML(out io.Writer, args ...runtime.Object) error { + for i, obj := range args { if i != 0 { if err := writeYAMLSep(out); err != nil { return fmt.Errorf("error writing to stdout: %v", err) } } - if err := marshalToWriter(cluster, marshalYaml, out); err != nil { + if err := marshalToWriter(obj, marshalYaml, out); err != nil { return err } } diff --git a/cmd/kops/get_federation.go b/cmd/kops/get_federation.go index e1c2c0b3b1..e8ae576064 100644 --- a/cmd/kops/get_federation.go +++ b/cmd/kops/get_federation.go @@ -24,6 +24,7 @@ import ( "github.com/spf13/cobra" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime" "k8s.io/kops/cmd/kops/util" api "k8s.io/kops/pkg/apis/kops" "k8s.io/kops/util/pkg/tables" @@ -85,10 +86,16 @@ func RunGetFederations(context Factory, out io.Writer, options *GetFederationOpt if len(federations) == 0 { return fmt.Errorf("No federations found") } + + var obj []runtime.Object + if options.output != OutputTable { + for _, c := range federations { + obj = append(obj, c) + } + } + switch options.output { - case OutputTable: - t := &tables.Table{} t.AddColumn("NAME", func(f *api.Federation) string { return f.ObjectMeta.Name @@ -102,25 +109,10 @@ func RunGetFederations(context Factory, out io.Writer, options *GetFederationOpt return t.Render(federations, out, "NAME", "CONTROLLERS", "MEMBERS") case OutputYaml: - for i, f := range federations { - if i != 0 { - _, err = out.Write([]byte("\n\n---\n\n")) - if err != nil { - return fmt.Errorf("error writing to stdout: %v", err) - } - } - if err := marshalToWriter(f, marshalYaml, os.Stdout); err != nil { - return err - } - } + return fullOutputYAML(out, obj...) case OutputJSON: - for _, f := range federations { - if err := marshalToWriter(f, marshalJSON, os.Stdout); err != nil { - return err - } - } + return fullOutputJSON(out, obj...) default: return fmt.Errorf("Unknown output format: %q", options.output) } - return nil } diff --git a/cmd/kops/get_instancegroups.go b/cmd/kops/get_instancegroups.go index 50e7f2760f..6dc6b90c3b 100644 --- a/cmd/kops/get_instancegroups.go +++ b/cmd/kops/get_instancegroups.go @@ -24,6 +24,7 @@ import ( "github.com/spf13/cobra" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime" "k8s.io/kops/cmd/kops/util" api "k8s.io/kops/pkg/apis/kops" "k8s.io/kops/pkg/formatter" @@ -112,14 +113,20 @@ func RunGetInstanceGroups(options *GetInstanceGroupsOptions, args []string) erro return fmt.Errorf("No InstanceGroup objects found") } - switch options.output { + var obj []runtime.Object + if options.output != OutputTable { + for _, c := range instancegroups { + obj = append(obj, c) + } + } + switch options.output { case OutputTable: return igOutputTable(cluster, instancegroups, out) case OutputYaml: - return igOutputYAML(instancegroups, out) + return fullOutputYAML(out, obj...) case OutputJSON: - return igOutputJson(instancegroups, out) + return fullOutputJSON(out, obj...) default: return fmt.Errorf("Unknown output format: %q", options.output) } @@ -176,29 +183,6 @@ func igOutputTable(cluster *api.Cluster, instancegroups []*api.InstanceGroup, ou return t.Render(instancegroups, os.Stdout, "NAME", "ROLE", "MACHINETYPE", "MIN", "MAX", "ZONES") } -func igOutputJson(instanceGroups []*api.InstanceGroup, out io.Writer) error { - for _, ig := range instanceGroups { - if err := marshalToWriter(ig, marshalJSON, out); err != nil { - return err - } - } - return nil -} - -func igOutputYAML(instanceGroups []*api.InstanceGroup, out io.Writer) error { - for i, ig := range instanceGroups { - if i != 0 { - if err := writeYAMLSep(out); err != nil { - return fmt.Errorf("error writing to stdout: %v", err) - } - } - if err := marshalToWriter(ig, marshalYaml, out); err != nil { - return err - } - } - return nil -} - func int32PointerToString(v *int32) string { if v == nil { return "-" diff --git a/cmd/kops/status_discovery.go b/cmd/kops/status_discovery.go index ca70a93efc..ddf09b6761 100644 --- a/cmd/kops/status_discovery.go +++ b/cmd/kops/status_discovery.go @@ -18,6 +18,7 @@ package main import ( "fmt" + "github.com/aws/aws-sdk-go/aws" "k8s.io/kops/pkg/apis/kops" "k8s.io/kops/upup/pkg/fi/cloudup" diff --git a/cmd/kops/toolbox_template.go b/cmd/kops/toolbox_template.go index f3eb2b52a0..58de13f85c 100644 --- a/cmd/kops/toolbox_template.go +++ b/cmd/kops/toolbox_template.go @@ -17,12 +17,13 @@ limitations under the License. package main import ( - "bytes" "fmt" "io" "io/ioutil" "os" + "path" "path/filepath" + "strings" "github.com/ghodss/yaml" "github.com/spf13/cobra" @@ -139,16 +140,14 @@ func runToolBoxTemplate(f *util.Factory, out io.Writer, options *toolboxTemplate if err != nil { return fmt.Errorf("unable to read snippet: %s, error: %s", j, err) } - snippets[j] = string(content) + snippets[path.Base(j)] = string(content) } } - b := new(bytes.Buffer) - - // @step: render each of the template and write to location + // @step: render each of the templates, splitting on the documents r := templater.NewTemplater() - size := len(templates) - 1 - for i, x := range templates { + var documents []string + for _, x := range templates { content, err := ioutil.ReadFile(x) if err != nil { return fmt.Errorf("unable to read template: %s, error: %s", x, err) @@ -158,37 +157,35 @@ func runToolBoxTemplate(f *util.Factory, out io.Writer, options *toolboxTemplate if err != nil { return fmt.Errorf("unable to render template: %s, error: %s", x, err) } - // @check if we have a zero length string and if so, forgo it + // @check if the content is zero ignore it if len(rendered) <= 0 { continue } - // @check if we need to format the yaml - if options.formatYAML { - var data interface{} - if err := utils.YamlUnmarshal([]byte(rendered), &data); err != nil { + if !options.formatYAML { + documents = append(documents, strings.Split(rendered, "---\n")...) + continue + } + + for _, x := range strings.Split(rendered, "---\n") { + var data map[string]interface{} + if err := yaml.Unmarshal([]byte(x), &data); err != nil { return fmt.Errorf("unable to unmarshall content from template: %s, error: %s", x, err) } - // @TODO: i'm not sure how this could happen but best to heck none the less + if len(data) <= 0 { + continue + } formatted, err := yaml.Marshal(&data) if err != nil { return fmt.Errorf("unable to marhshal formated content to yaml: %s", err) } - rendered = string(formatted) - } - if _, err := b.WriteString(rendered); err != nil { - return fmt.Errorf("unable to write template: %s, error: %s", x, err) - } - - // @check if we should need to add document separator - if i < size { - if _, err := b.WriteString("---\n"); err != nil { - return fmt.Errorf("unable to write to template: %s, error: %s", x, err) - } + documents = append(documents, string(formatted)) } } - iowriter := out + // join in harmony all the YAML documents back together + content := strings.Join(documents, "---\n") + iowriter := out // @check if we are writing to a file rather than stdout if options.outputPath != "" { w, err := os.OpenFile(utils.ExpandPath(options.outputPath), os.O_RDWR|os.O_TRUNC|os.O_CREATE, 0660) @@ -199,7 +196,7 @@ func runToolBoxTemplate(f *util.Factory, out io.Writer, options *toolboxTemplate iowriter = w } - if _, err := iowriter.Write(b.Bytes()); err != nil { + if _, err := iowriter.Write([]byte(content)); err != nil { return fmt.Errorf("unable to write template: %s", err) } diff --git a/dns-controller/pkg/dns/zonespec.go b/dns-controller/pkg/dns/zonespec.go index e0ad5dd1b2..435e2f2c67 100644 --- a/dns-controller/pkg/dns/zonespec.go +++ b/dns-controller/pkg/dns/zonespec.go @@ -18,9 +18,10 @@ package dns import ( "fmt" + "strings" + "github.com/golang/glog" "k8s.io/kubernetes/federation/pkg/dnsprovider" - "strings" ) type ZoneSpec struct { diff --git a/docs/cli/kops_create_cluster.md b/docs/cli/kops_create_cluster.md index 7fdfbb704e..f4bb358b7e 100644 --- a/docs/cli/kops_create_cluster.md +++ b/docs/cli/kops_create_cluster.md @@ -56,6 +56,10 @@ kops create cluster --project my-gce-project \ --image "ubuntu-os-cloud/ubuntu-1604-xenial-v20170202" \ --yes + # Create manifest for a cluster in AWS + kops create cluster --name=kubernetes-cluster.example.com \ + --state=s3://kops-state-1234 --zones=eu-west-1a \ + --node-count=2 --dry-run -oyaml ``` ### Options @@ -71,6 +75,7 @@ kops create cluster --cloud-labels string A list of KV pairs used to tag all instance groups in AWS (eg "Owner=John Doe,Team=Some Team"). --dns string DNS hosted zone to use: public|private. Default is 'public'. (default "Public") --dns-zone string DNS hosted zone to use (defaults to longest matching zone) + --dry-run If true, only print the object that would be sent, without sending it. This flag can be used to create a cluster YAML or JSON manifest. --encrypt-etcd-storage Generate key in aws kms and use it for encrypt etcd volumes --image string Image to use for all instances. --kubernetes-version string Version of kubernetes to run (defaults to version in channel) @@ -89,10 +94,11 @@ kops create cluster --node-tenancy string The tenancy of the node group on AWS. Can be either default or dedicated. --node-volume-size int32 Set instance volume size (in GB) for nodes --out string Path to write any local output + -o, --output string Ouput format. One of json|yaml. Used with the --dry-run flag. --project string Project to use (must be set on GCE) --ssh-access stringSlice Restrict SSH access to this CIDR. If not set, access will not be restricted by IP. (default [0.0.0.0/0]) --ssh-public-key string SSH public key to use (default "~/.ssh/id_rsa.pub") - --target string Target - direct, terraform, cloudformation (default "direct") + --target string Valid targets: direct, terraform, direct. Set this flag to terraform if you want kops to generate terraform (default "direct") -t, --topology string Controls network topology for the cluster. public|private. Default is 'public'. (default "public") --vpc string Set to use a shared VPC --yes Specify --yes to immediately create the cluster diff --git a/docs/cli/kops_create_instancegroup.md b/docs/cli/kops_create_instancegroup.md index e6d087b899..e37d9ce273 100644 --- a/docs/cli/kops_create_instancegroup.md +++ b/docs/cli/kops_create_instancegroup.md @@ -20,11 +20,17 @@ kops create instancegroup # Create an instancegroup for the k8s-cluster.example.com cluster. kops create ig --name=k8s-cluster.example.com node-example \ --role node --subnet my-subnet-name + + # Create a YAML manifest for an instancegroup for the k8s-cluster.example.com cluster. + kops create ig --name=k8s-cluster.example.com node-example \ + --role node --subnet my-subnet-name --dry-run -oyaml ``` ### Options ``` + --dry-run If true, only print the object that would be sent, without sending it. This flag can be used to create a cluster YAML or JSON manifest. + -o, --output string Ouput format. One of json|yaml --role string Type of instance group to create (Node,Master,Bastion) (default "Node") --subnet stringSlice Subnets in which to create instance group ``` diff --git a/docs/cluster_spec.md b/docs/cluster_spec.md index cdee5fa345..47fa11d62e 100644 --- a/docs/cluster_spec.md +++ b/docs/cluster_spec.md @@ -140,8 +140,11 @@ spec: auditLogMaxAge: 10 auditLogMaxBackups: 1 auditLogMaxSize: 100 + auditPolicyFile: /srv/kubernetes/audit.conf ``` +Note: you could use the fileAssets feature to push an advanced audit policy file on the master nodes. + #### runtimeConfig Keys and values here are translated into `--runtime-config` values for `kube-apiserver`, separated by commas. diff --git a/docs/cluster_upgrades_and_migrations.md b/docs/cluster_upgrades_and_migrations.md index 1948317843..b8aad86c84 100644 --- a/docs/cluster_upgrades_and_migrations.md +++ b/docs/cluster_upgrades_and_migrations.md @@ -204,7 +204,7 @@ Limitations: 4. Verify the planned changes with `kops update cluster cluster.example.com` 5. Create the cluster with `kops update cluster cluster.example.com --yes` 6. Wait around for the cluster to fully come up and be available. `k get nodes` should return `(master + minions) = 15` available nodes. -7. (Optional) Create the Dashboard with `kubectl create -f https://rawgit.com/kubernetes/dashboard/master/src/deploy/kubernetes-dashboard.yaml` +7. (Optional) Create the Dashboard with `kubectl create -f https://raw.githubusercontent.com/kubernetes/dashboard/master/src/deploy/recommended/kubernetes-dashboard.yaml` 8. Deploy the existing resource configuration to the new cluster. 9. Confirm that pods on the new cluster are able to access remote resources. - For AWS-hosted services, add the generated `nodes.cluster.example.com` security group to the resources that may need it (i.e. ElastiCache, RDS, etc). diff --git a/docs/development/api_updates.md b/docs/development/api_updates.md index 88e894b230..f8ed901a13 100644 --- a/docs/development/api_updates.md +++ b/docs/development/api_updates.md @@ -3,7 +3,7 @@ Kops uses the Kubernetes API machinery. It is well designed, and very powerful, but you have to jump through some hoops to use it. -Recommended reading: [kubernetes API changes doc](https://github.com/kubernetes/kubernetes/blob/master/docs/devel/api_changes.md) +Recommended reading: [kubernetes API changes doc](https://github.com/kubernetes/community/blob/master/contributors/devel/api_changes.md) The kops APIs live in [pkg/apis/kops](https://github.com/kubernetes/kops/tree/master/pkg/apis/kops), both in that directory directly (the unversioned API) and in the versioned subdirectories (`v1alpha1`, `v1alpha2`). diff --git a/examples/kops-api-example/main.go b/examples/kops-api-example/main.go index 7af04ad347..7be0789b75 100644 --- a/examples/kops-api-example/main.go +++ b/examples/kops-api-example/main.go @@ -19,9 +19,10 @@ package main import ( "flag" "fmt" - "k8s.io/kops/util/pkg/vfs" "os" "strings" + + "k8s.io/kops/util/pkg/vfs" ) // registryBase is the base path where state files are kept (the state store) diff --git a/examples/kops-api-example/up.go b/examples/kops-api-example/up.go index 17f5ac170a..517a2626de 100644 --- a/examples/kops-api-example/up.go +++ b/examples/kops-api-example/up.go @@ -19,6 +19,7 @@ package main import ( "fmt" "io/ioutil" + api "k8s.io/kops/pkg/apis/kops" "k8s.io/kops/pkg/client/simple/vfsclientset" "k8s.io/kops/upup/pkg/fi" diff --git a/federation/federation_cluster.go b/federation/federation_cluster.go index 810c20416a..d8c2a50515 100644 --- a/federation/federation_cluster.go +++ b/federation/federation_cluster.go @@ -18,6 +18,7 @@ package federation import ( "fmt" + "github.com/golang/glog" "k8s.io/api/core/v1" k8sapiv1 "k8s.io/api/core/v1" diff --git a/federation/federation_configuration.go b/federation/federation_configuration.go index 8e8b0f3d5e..edaa8c8d6c 100644 --- a/federation/federation_configuration.go +++ b/federation/federation_configuration.go @@ -18,6 +18,7 @@ package federation import ( "fmt" + "github.com/golang/glog" "k8s.io/api/core/v1" apierrors "k8s.io/apimachinery/pkg/api/errors" diff --git a/federation/federation_namespace.go b/federation/federation_namespace.go index 00a5f486c1..71bbdffc47 100644 --- a/federation/federation_namespace.go +++ b/federation/federation_namespace.go @@ -18,6 +18,7 @@ package federation import ( "fmt" + "github.com/golang/glog" "k8s.io/api/core/v1" apierrors "k8s.io/apimachinery/pkg/api/errors" diff --git a/federation/targets/kubernetestarget/kubernetestarget.go b/federation/targets/kubernetestarget/kubernetestarget.go index 19ddb0103c..3a9ccc8368 100644 --- a/federation/targets/kubernetestarget/kubernetestarget.go +++ b/federation/targets/kubernetestarget/kubernetestarget.go @@ -18,6 +18,7 @@ package kubernetestarget import ( "fmt" + "k8s.io/client-go/kubernetes" kopsapi "k8s.io/kops/pkg/apis/kops" "k8s.io/kops/pkg/client/simple" diff --git a/federation/tasks/kubernetesresource.go b/federation/tasks/kubernetesresource.go index da0309bddc..abf4f06398 100644 --- a/federation/tasks/kubernetesresource.go +++ b/federation/tasks/kubernetesresource.go @@ -18,6 +18,7 @@ package tasks import ( "fmt" + "k8s.io/apimachinery/pkg/util/validation/field" "k8s.io/kops/federation/targets/kubernetestarget" "k8s.io/kops/upup/pkg/fi" diff --git a/nodeup/pkg/distros/identify.go b/nodeup/pkg/distros/identify.go index fd9fb245da..b4f4c3f18c 100644 --- a/nodeup/pkg/distros/identify.go +++ b/nodeup/pkg/distros/identify.go @@ -18,11 +18,12 @@ package distros import ( "fmt" - "github.com/golang/glog" "io/ioutil" "os" "path" "strings" + + "github.com/golang/glog" ) // FindDistribution identifies the distribution on which we are running diff --git a/nodeup/pkg/model/docker_test.go b/nodeup/pkg/model/docker_test.go index a9d00c3a87..a6e4d3f0f6 100644 --- a/nodeup/pkg/model/docker_test.go +++ b/nodeup/pkg/model/docker_test.go @@ -17,11 +17,12 @@ limitations under the License. package model import ( + "path" + "testing" + "k8s.io/kops/pkg/apis/kops" "k8s.io/kops/pkg/flagbuilder" "k8s.io/kops/upup/pkg/fi" - "path" - "testing" ) func TestDockerBuilder_Simple(t *testing.T) { diff --git a/nodeup/pkg/model/kubectl.go b/nodeup/pkg/model/kubectl.go index 652284e04b..d9e5e0d93a 100644 --- a/nodeup/pkg/model/kubectl.go +++ b/nodeup/pkg/model/kubectl.go @@ -33,7 +33,7 @@ type KubectlBuilder struct { var _ fi.ModelBuilder = &KubectlBuilder{} -// Build is responsible for mananging the kubectl on the nodes +// Build is responsible for managing the kubectl on the nodes func (b *KubectlBuilder) Build(c *fi.ModelBuilderContext) error { if !b.IsMaster { return nil diff --git a/nodeup/pkg/model/kubelet_test.go b/nodeup/pkg/model/kubelet_test.go index 6551e567cf..b68ffdd408 100644 --- a/nodeup/pkg/model/kubelet_test.go +++ b/nodeup/pkg/model/kubelet_test.go @@ -25,6 +25,7 @@ import ( "testing" "fmt" + "k8s.io/apimachinery/pkg/runtime/schema" "k8s.io/kops/nodeup/pkg/distros" "k8s.io/kops/pkg/apis/kops" diff --git a/nodeup/pkg/model/network.go b/nodeup/pkg/model/network.go index f2fbec31df..cef1578604 100644 --- a/nodeup/pkg/model/network.go +++ b/nodeup/pkg/model/network.go @@ -18,9 +18,10 @@ package model import ( "fmt" + "path/filepath" + "k8s.io/kops/upup/pkg/fi" "k8s.io/kops/upup/pkg/fi/nodeup/nodetasks" - "path/filepath" ) // NetworkBuilder writes CNI assets diff --git a/pkg/apis/kops/channel.go b/pkg/apis/kops/channel.go index e374c792f8..f6e78fc657 100644 --- a/pkg/apis/kops/channel.go +++ b/pkg/apis/kops/channel.go @@ -18,13 +18,14 @@ package kops import ( "fmt" + "net/url" + "github.com/blang/semver" "github.com/golang/glog" "k8s.io/apimachinery/pkg/apis/meta/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/kops/pkg/apis/kops/util" "k8s.io/kops/util/pkg/vfs" - "net/url" ) var DefaultChannelBase = "https://raw.githubusercontent.com/kubernetes/kops/master/channels/" diff --git a/pkg/apis/kops/model/utils.go b/pkg/apis/kops/model/utils.go index 489b11e797..fa688f30f8 100644 --- a/pkg/apis/kops/model/utils.go +++ b/pkg/apis/kops/model/utils.go @@ -18,6 +18,7 @@ package model import ( "fmt" + "k8s.io/apimachinery/pkg/util/sets" "k8s.io/kops/pkg/apis/kops" ) diff --git a/pkg/apis/kops/model/utils_test.go b/pkg/apis/kops/model/utils_test.go index 1b4deb0812..b4526eab48 100644 --- a/pkg/apis/kops/model/utils_test.go +++ b/pkg/apis/kops/model/utils_test.go @@ -17,9 +17,10 @@ limitations under the License. package model import ( - "k8s.io/kops/pkg/apis/kops" "reflect" "testing" + + "k8s.io/kops/pkg/apis/kops" ) // Test_FindSubnet tests FindSubnet diff --git a/pkg/apis/kops/registry/helpers.go b/pkg/apis/kops/registry/helpers.go index 0a000f433e..f297cc7300 100644 --- a/pkg/apis/kops/registry/helpers.go +++ b/pkg/apis/kops/registry/helpers.go @@ -18,6 +18,7 @@ package registry import ( "fmt" + api "k8s.io/kops/pkg/apis/kops" "k8s.io/kops/pkg/client/simple" ) diff --git a/pkg/apis/kops/registry/registry.go b/pkg/apis/kops/registry/registry.go index 939f6589e4..51fede72e9 100644 --- a/pkg/apis/kops/registry/registry.go +++ b/pkg/apis/kops/registry/registry.go @@ -18,6 +18,7 @@ package registry import ( "fmt" + "k8s.io/apimachinery/pkg/util/validation/field" api "k8s.io/kops/pkg/apis/kops" "k8s.io/kops/util/pkg/vfs" diff --git a/pkg/apis/kops/semver_test.go b/pkg/apis/kops/semver_test.go index c38459c959..85e0405f7d 100644 --- a/pkg/apis/kops/semver_test.go +++ b/pkg/apis/kops/semver_test.go @@ -17,8 +17,9 @@ limitations under the License. package kops import ( - "github.com/blang/semver" "testing" + + "github.com/blang/semver" ) // Test_SemverOrdering is a test of semver ordering, but highlights the case that trips everyone one: diff --git a/pkg/apis/kops/util/labels.go b/pkg/apis/kops/util/labels.go index 8f6b70056b..8c8e31620e 100644 --- a/pkg/apis/kops/util/labels.go +++ b/pkg/apis/kops/util/labels.go @@ -17,8 +17,9 @@ limitations under the License. package util import ( - "k8s.io/api/core/v1" "strings" + + "k8s.io/api/core/v1" ) func GetNodeRole(node *v1.Node) string { diff --git a/pkg/apis/kops/v1alpha1/conversion.go b/pkg/apis/kops/v1alpha1/conversion.go index 653e16db27..e5322412f3 100644 --- a/pkg/apis/kops/v1alpha1/conversion.go +++ b/pkg/apis/kops/v1alpha1/conversion.go @@ -18,11 +18,12 @@ package v1alpha1 import ( "fmt" - "k8s.io/apimachinery/pkg/conversion" - "k8s.io/kops/pkg/apis/kops" "reflect" "sort" "strings" + + "k8s.io/apimachinery/pkg/conversion" + "k8s.io/kops/pkg/apis/kops" ) func Convert_v1alpha1_BastionSpec_To_kops_BastionSpec(in *BastionSpec, out *kops.BastionSpec, s conversion.Scope) error { diff --git a/pkg/apis/kops/validation/helpers.go b/pkg/apis/kops/validation/helpers.go index b8fd8a4d04..20da4d726a 100644 --- a/pkg/apis/kops/validation/helpers.go +++ b/pkg/apis/kops/validation/helpers.go @@ -17,9 +17,10 @@ limitations under the License. package validation import ( - "k8s.io/apimachinery/pkg/util/validation/field" "net" "net/url" + + "k8s.io/apimachinery/pkg/util/validation/field" ) // isSubnet checks if child is a subnet of parent diff --git a/pkg/apis/kops/validation/instancegroup.go b/pkg/apis/kops/validation/instancegroup.go index e6e7d6c98f..4234468fb9 100644 --- a/pkg/apis/kops/validation/instancegroup.go +++ b/pkg/apis/kops/validation/instancegroup.go @@ -18,6 +18,7 @@ package validation import ( "fmt" + "k8s.io/apimachinery/pkg/util/validation/field" "k8s.io/kops/pkg/apis/kops" "k8s.io/kops/pkg/apis/kops/util" diff --git a/pkg/apis/kops/validation/instancegroup_test.go b/pkg/apis/kops/validation/instancegroup_test.go index ff4fcff54d..a1d2b97b63 100644 --- a/pkg/apis/kops/validation/instancegroup_test.go +++ b/pkg/apis/kops/validation/instancegroup_test.go @@ -17,10 +17,11 @@ limitations under the License. package validation import ( - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/kops/pkg/apis/kops" "strings" "testing" + + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/kops/pkg/apis/kops" ) func TestDefaultTaintsEnforcedBefore160(t *testing.T) { diff --git a/pkg/apiserver/apiserver.go b/pkg/apiserver/apiserver.go index 21844b7753..b6ed433f6a 100644 --- a/pkg/apiserver/apiserver.go +++ b/pkg/apiserver/apiserver.go @@ -18,6 +18,7 @@ package apiserver import ( "fmt" + "k8s.io/apimachinery/pkg/apimachinery/announced" "k8s.io/apimachinery/pkg/apimachinery/registered" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" diff --git a/pkg/assets/builder_test.go b/pkg/assets/builder_test.go index 796abe0ad1..78575bde5a 100644 --- a/pkg/assets/builder_test.go +++ b/pkg/assets/builder_test.go @@ -17,8 +17,9 @@ limitations under the License. package assets import ( - "k8s.io/kops/pkg/apis/kops" "testing" + + "k8s.io/kops/pkg/apis/kops" ) func TestRemap_File(t *testing.T) { diff --git a/pkg/client/simple/vfsclientset/cluster.go b/pkg/client/simple/vfsclientset/cluster.go index 5080e7a73f..de74b21f5b 100644 --- a/pkg/client/simple/vfsclientset/cluster.go +++ b/pkg/client/simple/vfsclientset/cluster.go @@ -18,6 +18,10 @@ package vfsclientset import ( "fmt" + "os" + "strings" + "time" + "github.com/golang/glog" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/types" @@ -28,9 +32,6 @@ import ( "k8s.io/kops/pkg/apis/kops/v1alpha1" "k8s.io/kops/pkg/apis/kops/validation" "k8s.io/kops/util/pkg/vfs" - "os" - "strings" - "time" ) type ClusterVFS struct { diff --git a/pkg/client/simple/vfsclientset/commonvfs.go b/pkg/client/simple/vfsclientset/commonvfs.go index 92217231ff..5f25bd7847 100644 --- a/pkg/client/simple/vfsclientset/commonvfs.go +++ b/pkg/client/simple/vfsclientset/commonvfs.go @@ -19,6 +19,11 @@ package vfsclientset import ( "bytes" "fmt" + "os" + "reflect" + "sort" + "time" + "github.com/golang/glog" "k8s.io/apimachinery/pkg/api/meta" "k8s.io/apimachinery/pkg/apis/meta/v1" @@ -30,10 +35,6 @@ import ( "k8s.io/kops/pkg/apis/kops/v1alpha2" "k8s.io/kops/pkg/kopscodecs" "k8s.io/kops/util/pkg/vfs" - "os" - "reflect" - "sort" - "time" ) var StoreVersion = v1alpha2.SchemeGroupVersion diff --git a/pkg/client/simple/vfsclientset/federation.go b/pkg/client/simple/vfsclientset/federation.go index 09940c0ac9..afec1babfb 100644 --- a/pkg/client/simple/vfsclientset/federation.go +++ b/pkg/client/simple/vfsclientset/federation.go @@ -18,6 +18,7 @@ package vfsclientset import ( "fmt" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/types" diff --git a/pkg/client/simple/vfsclientset/import_known_versions.go b/pkg/client/simple/vfsclientset/import_known_versions.go index dc7a6f3d73..7faaa70620 100644 --- a/pkg/client/simple/vfsclientset/import_known_versions.go +++ b/pkg/client/simple/vfsclientset/import_known_versions.go @@ -19,6 +19,7 @@ package vfsclientset // These imports are the API groups the client will support. import ( "fmt" + "k8s.io/kops/pkg/kopscodecs" ) diff --git a/pkg/client/simple/vfsclientset/instancegroup.go b/pkg/client/simple/vfsclientset/instancegroup.go index ed8b3a0bd1..0f922e8271 100644 --- a/pkg/client/simple/vfsclientset/instancegroup.go +++ b/pkg/client/simple/vfsclientset/instancegroup.go @@ -18,6 +18,7 @@ package vfsclientset import ( "fmt" + "github.com/golang/glog" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/runtime" diff --git a/pkg/client/simple/vfsclientset/utils.go b/pkg/client/simple/vfsclientset/utils.go index f2e8d3d589..7fbe5dfd9f 100644 --- a/pkg/client/simple/vfsclientset/utils.go +++ b/pkg/client/simple/vfsclientset/utils.go @@ -18,8 +18,9 @@ package vfsclientset import ( "fmt" - "k8s.io/kops/util/pkg/vfs" "os" + + "k8s.io/kops/util/pkg/vfs" ) func listChildNames(vfsPath vfs.Path) ([]string, error) { diff --git a/pkg/diff/diff_test.go b/pkg/diff/diff_test.go index b543a6d651..a1aef105d9 100644 --- a/pkg/diff/diff_test.go +++ b/pkg/diff/diff_test.go @@ -17,8 +17,9 @@ limitations under the License. package diff import ( - "github.com/sergi/go-diff/diffmatchpatch" "testing" + + "github.com/sergi/go-diff/diffmatchpatch" ) func Test_Diff_1(t *testing.T) { diff --git a/pkg/edit/edit.go b/pkg/edit/edit.go index 7429733be6..57d48ff7d2 100644 --- a/pkg/edit/edit.go +++ b/pkg/edit/edit.go @@ -18,6 +18,7 @@ package edit import ( "bytes" + "k8s.io/apimachinery/pkg/runtime" "k8s.io/kops/pkg/diff" "k8s.io/kops/pkg/kopscodecs" diff --git a/pkg/featureflag/featureflag_test.go b/pkg/featureflag/featureflag_test.go index 41f973be27..64e609f890 100644 --- a/pkg/featureflag/featureflag_test.go +++ b/pkg/featureflag/featureflag_test.go @@ -17,9 +17,10 @@ limitations under the License. package featureflag import ( - "github.com/golang/glog" "os" "testing" + + "github.com/golang/glog" ) func TestFlagToFalse(t *testing.T) { diff --git a/pkg/flagbuilder/build_flags.go b/pkg/flagbuilder/build_flags.go index 5d3c43b26b..60d9097cba 100644 --- a/pkg/flagbuilder/build_flags.go +++ b/pkg/flagbuilder/build_flags.go @@ -29,7 +29,7 @@ import ( "github.com/golang/glog" ) -// BuildFlags returns a space seperated list arguments +// BuildFlags returns a space separated list arguments // @deprecated: please use BuildFlagsList func BuildFlags(options interface{}) (string, error) { flags, err := BuildFlagsList(options) diff --git a/pkg/formatter/instancegroup.go b/pkg/formatter/instancegroup.go index 9fa1917eac..5d7116b8e4 100644 --- a/pkg/formatter/instancegroup.go +++ b/pkg/formatter/instancegroup.go @@ -17,10 +17,11 @@ limitations under the License. package formatter import ( + "strings" + "github.com/golang/glog" "k8s.io/kops/pkg/apis/kops" "k8s.io/kops/pkg/apis/kops/model" - "strings" ) // InstanceGroupRenderFunction is a render function for an InstanceGroup @@ -38,7 +39,7 @@ func RenderInstanceGroupZones(cluster *kops.Cluster) InstanceGroupRenderFunction return func(ig *kops.InstanceGroup) string { zones, err := model.FindZonesForInstanceGroup(cluster, ig) if err != nil { - glog.Warningf("error fetch zones for instancegroup: %v", err) + glog.Warningf("error fetching zones for instancegroup: %v", err) return "" } return strings.Join(zones, ",") diff --git a/pkg/formatter/instancegroup_test.go b/pkg/formatter/instancegroup_test.go index f8d7c93e60..b29fe3f018 100644 --- a/pkg/formatter/instancegroup_test.go +++ b/pkg/formatter/instancegroup_test.go @@ -17,8 +17,9 @@ limitations under the License. package formatter import ( - "k8s.io/kops/pkg/apis/kops" "testing" + + "k8s.io/kops/pkg/apis/kops" ) func TestRenderInstanceGroupZones(t *testing.T) { diff --git a/pkg/k8scodecs/codecs.go b/pkg/k8scodecs/codecs.go index 79fa7a39b7..fec2e7b06b 100644 --- a/pkg/k8scodecs/codecs.go +++ b/pkg/k8scodecs/codecs.go @@ -19,6 +19,8 @@ package k8scodecs import ( "bytes" "fmt" + "os" + "github.com/golang/glog" corev1 "k8s.io/api/core/v1" "k8s.io/apimachinery/pkg/apimachinery/announced" @@ -27,7 +29,6 @@ import ( "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/runtime/schema" "k8s.io/apimachinery/pkg/runtime/serializer" - "os" ) var Scheme = runtime.NewScheme() diff --git a/pkg/k8scodecs/codecs_test.go b/pkg/k8scodecs/codecs_test.go index a4bdddc7c7..53d2a17153 100644 --- a/pkg/k8scodecs/codecs_test.go +++ b/pkg/k8scodecs/codecs_test.go @@ -17,14 +17,15 @@ limitations under the License. package k8scodecs import ( + "strings" + "testing" + "time" + "github.com/MakeNowJust/heredoc" "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/runtime" "k8s.io/kops/pkg/diff" - "strings" - "testing" - "time" ) // An arbitrary timestamp for testing diff --git a/pkg/kopscodecs/codecs.go b/pkg/kopscodecs/codecs.go index a04b175069..80274c53ee 100644 --- a/pkg/kopscodecs/codecs.go +++ b/pkg/kopscodecs/codecs.go @@ -19,6 +19,8 @@ package kopscodecs import ( "bytes" "fmt" + "os" + "github.com/golang/glog" "k8s.io/apimachinery/pkg/apimachinery/announced" "k8s.io/apimachinery/pkg/apimachinery/registered" @@ -29,7 +31,6 @@ import ( "k8s.io/kops/pkg/apis/kops" "k8s.io/kops/pkg/apis/kops/install" "k8s.io/kops/pkg/apis/kops/v1alpha2" - "os" ) var Scheme = runtime.NewScheme() @@ -44,12 +45,12 @@ func init() { install.Install(GroupFactoryRegistry, Registry, Scheme) } -func encoder(gv runtime.GroupVersioner) runtime.Encoder { - yaml, ok := runtime.SerializerInfoForMediaType(Codecs.SupportedMediaTypes(), "application/yaml") +func encoder(gv runtime.GroupVersioner, mediaType string) runtime.Encoder { + e, ok := runtime.SerializerInfoForMediaType(Codecs.SupportedMediaTypes(), mediaType) if !ok { - glog.Fatalf("no YAML serializer registered") + glog.Fatalf("no %s serializer registered", mediaType) } - return Codecs.EncoderForVersion(yaml.Serializer, gv) + return Codecs.EncoderForVersion(e.Serializer, gv) } func decoder() runtime.Decoder { @@ -67,7 +68,22 @@ func ToVersionedYaml(obj runtime.Object) ([]byte, error) { // ToVersionedYamlWithVersion encodes the object to YAML, in a specified API version func ToVersionedYamlWithVersion(obj runtime.Object, version runtime.GroupVersioner) ([]byte, error) { var w bytes.Buffer - err := encoder(version).Encode(obj, &w) + err := encoder(version, "application/yaml").Encode(obj, &w) + if err != nil { + return nil, fmt.Errorf("error encoding %T: %v", obj, err) + } + return w.Bytes(), nil +} + +// ToVersionedJSON encodes the object to JSON +func ToVersionedJSON(obj runtime.Object) ([]byte, error) { + return ToVersionedJSONWithVersion(obj, v1alpha2.SchemeGroupVersion) +} + +// ToVersionedJSONWithVersion encodes the object to JSON, in a specified API version +func ToVersionedJSONWithVersion(obj runtime.Object, version runtime.GroupVersioner) ([]byte, error) { + var w bytes.Buffer + err := encoder(version, "application/json").Encode(obj, &w) if err != nil { return nil, fmt.Errorf("error encoding %T: %v", obj, err) } diff --git a/pkg/kopscodecs/codecs_test.go b/pkg/kopscodecs/codecs_test.go index 986677e381..495fa0e686 100644 --- a/pkg/kopscodecs/codecs_test.go +++ b/pkg/kopscodecs/codecs_test.go @@ -17,14 +17,15 @@ limitations under the License. package kopscodecs import ( + "strings" + "testing" + "time" + "github.com/MakeNowJust/heredoc" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/runtime" "k8s.io/kops/pkg/apis/kops/v1alpha2" "k8s.io/kops/pkg/diff" - "strings" - "testing" - "time" ) // An arbitrary timestamp for testing diff --git a/pkg/kubeconfig/create_kubecfg.go b/pkg/kubeconfig/create_kubecfg.go index 9a634695e5..cb18ea1c1c 100644 --- a/pkg/kubeconfig/create_kubecfg.go +++ b/pkg/kubeconfig/create_kubecfg.go @@ -18,11 +18,12 @@ package kubeconfig import ( "fmt" + "sort" + "github.com/golang/glog" "k8s.io/kops/pkg/apis/kops" "k8s.io/kops/pkg/dns" "k8s.io/kops/upup/pkg/fi" - "sort" ) func BuildKubecfg(cluster *kops.Cluster, keyStore fi.Keystore, secretStore fi.SecretStore, status kops.StatusStore) (*KubeconfigBuilder, error) { diff --git a/pkg/kubemanifest/images.go b/pkg/kubemanifest/images.go index 7be8012072..281e33db03 100644 --- a/pkg/kubemanifest/images.go +++ b/pkg/kubemanifest/images.go @@ -18,8 +18,9 @@ package kubemanifest import ( "fmt" - "github.com/golang/glog" "strings" + + "github.com/golang/glog" ) type ImageRemapFunction func(image string) (string, error) diff --git a/pkg/kubemanifest/manifest.go b/pkg/kubemanifest/manifest.go index 5abbf2c506..b5a2a7e1e5 100644 --- a/pkg/kubemanifest/manifest.go +++ b/pkg/kubemanifest/manifest.go @@ -19,6 +19,7 @@ package kubemanifest import ( "bytes" "fmt" + "github.com/ghodss/yaml" "github.com/golang/glog" ) diff --git a/pkg/kubemanifest/visitor.go b/pkg/kubemanifest/visitor.go index b6b7386fa6..94de412461 100644 --- a/pkg/kubemanifest/visitor.go +++ b/pkg/kubemanifest/visitor.go @@ -18,8 +18,9 @@ package kubemanifest import ( "fmt" - "github.com/golang/glog" "strings" + + "github.com/golang/glog" ) type visitorBase struct { diff --git a/pkg/model/awsmodel/autoscalinggroup_test.go b/pkg/model/awsmodel/autoscalinggroup_test.go index 421c2fe829..a983c79a74 100644 --- a/pkg/model/awsmodel/autoscalinggroup_test.go +++ b/pkg/model/awsmodel/autoscalinggroup_test.go @@ -17,11 +17,12 @@ limitations under the License. package awsmodel import ( + "testing" + "k8s.io/kops/pkg/apis/kops" "k8s.io/kops/pkg/model" "k8s.io/kops/upup/pkg/fi" "k8s.io/kops/upup/pkg/fi/cloudup/awstasks" - "testing" ) func buildMinimalCluster() *kops.Cluster { diff --git a/pkg/model/components/kubecontrollermanager_test.go b/pkg/model/components/kubecontrollermanager_test.go index 25ad97f041..8776a4ebd9 100644 --- a/pkg/model/components/kubecontrollermanager_test.go +++ b/pkg/model/components/kubecontrollermanager_test.go @@ -17,11 +17,12 @@ limitations under the License. package components import ( + "testing" + "time" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" api "k8s.io/kops/pkg/apis/kops" "k8s.io/kops/pkg/assets" - "testing" - "time" ) type ClusterParams struct { diff --git a/pkg/model/components/networking.go b/pkg/model/components/networking.go index 9564bc3757..c23b6d3661 100644 --- a/pkg/model/components/networking.go +++ b/pkg/model/components/networking.go @@ -18,6 +18,7 @@ package components import ( "fmt" + "k8s.io/kops/pkg/apis/kops" "k8s.io/kops/upup/pkg/fi" "k8s.io/kops/upup/pkg/fi/loader" diff --git a/pkg/model/context.go b/pkg/model/context.go index ed64a59ef0..ecc3be3cef 100644 --- a/pkg/model/context.go +++ b/pkg/model/context.go @@ -215,9 +215,27 @@ func (m *KopsModelContext) CloudTags(name string, shared bool) map[string]string switch kops.CloudProviderID(m.Cluster.Spec.CloudProvider) { case kops.CloudProviderAWS: - tags[awsup.TagClusterName] = m.Cluster.ObjectMeta.Name - if name != "" { - tags["Name"] = name + if shared { + // If the resource is shared, we don't try to set the Name - we presume that is managed externally + glog.V(4).Infof("Skipping Name tag for shared resource") + } else { + if name != "" { + tags["Name"] = name + } + } + + // Kubernetes 1.6 introduced the shared ownership tag; that replaces TagClusterName + setLegacyTag := true + if m.IsKubernetesGTE("1.6") { + // For the moment, we only skip the legacy tag for shared resources + // (other people may be using it) + if shared { + glog.V(4).Infof("Skipping %q tag for shared resource", awsup.TagClusterName) + setLegacyTag = false + } + } + if setLegacyTag { + tags[awsup.TagClusterName] = m.Cluster.ObjectMeta.Name } if shared { @@ -284,32 +302,26 @@ func (c *KopsModelContext) UseEtcdTLS() bool { } // KubernetesVersion parses the semver version of kubernetes, from the cluster spec -func (c *KopsModelContext) KubernetesVersion() (semver.Version, error) { +func (c *KopsModelContext) KubernetesVersion() semver.Version { // TODO: Remove copy-pasting c.f. https://github.com/kubernetes/kops/blob/master/pkg/model/components/context.go#L32 kubernetesVersion := c.Cluster.Spec.KubernetesVersion if kubernetesVersion == "" { - return semver.Version{}, fmt.Errorf("KubernetesVersion is required") + glog.Fatalf("KubernetesVersion is required") } sv, err := util.ParseKubernetesVersion(kubernetesVersion) if err != nil { - return semver.Version{}, fmt.Errorf("unable to determine kubernetes version from %q", kubernetesVersion) + glog.Fatalf("unable to determine kubernetes version from %q", kubernetesVersion) } - return *sv, nil + return *sv } -// VersionGTE is a simplified semver comparison -func VersionGTE(version semver.Version, major uint64, minor uint64) bool { - if version.Major > major { - return true - } - if version.Major == major && version.Minor >= minor { - return true - } - return false +// IsKubernetesGTE checks if the kubernetes version is at least version, ignoring prereleases / patches +func (c *KopsModelContext) IsKubernetesGTE(version string) bool { + return util.IsKubernetesGTE(version, c.KubernetesVersion()) } func (c *KopsModelContext) WellKnownServiceIP(id int) (net.IP, error) { diff --git a/pkg/model/context_test.go b/pkg/model/context_test.go index 727e27fd1f..e0c5cc969a 100644 --- a/pkg/model/context_test.go +++ b/pkg/model/context_test.go @@ -17,9 +17,10 @@ limitations under the License. package model import ( + "testing" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/kops/pkg/apis/kops" - "testing" ) func Test_GetELBName32(t *testing.T) { diff --git a/pkg/model/dns.go b/pkg/model/dns.go index ff50ad163e..1576de7af0 100644 --- a/pkg/model/dns.go +++ b/pkg/model/dns.go @@ -18,11 +18,12 @@ package model import ( "fmt" + "strings" + "k8s.io/kops/pkg/apis/kops" "k8s.io/kops/pkg/dns" "k8s.io/kops/upup/pkg/fi" "k8s.io/kops/upup/pkg/fi/cloudup/awstasks" - "strings" ) // DNSModelBuilder builds DNS related model objects diff --git a/pkg/model/firewall.go b/pkg/model/firewall.go index 29fd4e1122..01932dce46 100644 --- a/pkg/model/firewall.go +++ b/pkg/model/firewall.go @@ -155,6 +155,10 @@ func (b *FirewallModelBuilder) applyNodeToMasterAllowSpecificPorts(c *fi.ModelBu tcpPorts = append(tcpPorts, 4001) tcpPorts = append(tcpPorts, 9600) } + + if b.Cluster.Spec.Networking.Kuberouter != nil { + protocols = append(protocols, ProtocolIPIP) + } } for _, udpPort := range udpPorts { @@ -226,6 +230,10 @@ func (b *FirewallModelBuilder) applyNodeToMasterBlockSpecificPorts(c *fi.ModelBu protocols = append(protocols, ProtocolIPIP) } + if b.Cluster.Spec.Networking.Kuberouter != nil { + protocols = append(protocols, ProtocolIPIP) + } + for _, r := range udpRanges { c.AddTask(&awstasks.SecurityGroupRule{ Name: s(fmt.Sprintf("node-to-master-udp-%d-%d", r.From, r.To)), diff --git a/pkg/model/gcemodel/api_loadbalancer.go b/pkg/model/gcemodel/api_loadbalancer.go index c295e8fca3..f509f865c0 100644 --- a/pkg/model/gcemodel/api_loadbalancer.go +++ b/pkg/model/gcemodel/api_loadbalancer.go @@ -18,6 +18,7 @@ package gcemodel import ( "fmt" + "k8s.io/kops/pkg/apis/kops" "k8s.io/kops/upup/pkg/fi" "k8s.io/kops/upup/pkg/fi/cloudup/gcetasks" diff --git a/pkg/model/gcemodel/storageacl.go b/pkg/model/gcemodel/storageacl.go index d5ca546d36..4641d0303b 100644 --- a/pkg/model/gcemodel/storageacl.go +++ b/pkg/model/gcemodel/storageacl.go @@ -18,6 +18,7 @@ package gcemodel import ( "fmt" + "k8s.io/kops/upup/pkg/fi" "k8s.io/kops/upup/pkg/fi/cloudup/gce" "k8s.io/kops/upup/pkg/fi/cloudup/gcetasks" diff --git a/pkg/model/names.go b/pkg/model/names.go index 10ed1c11ff..6b90ec3d3b 100644 --- a/pkg/model/names.go +++ b/pkg/model/names.go @@ -18,6 +18,7 @@ package model import ( "fmt" + "github.com/golang/glog" "k8s.io/kops/pkg/apis/kops" "k8s.io/kops/upup/pkg/fi/cloudup/awstasks" diff --git a/pkg/model/network.go b/pkg/model/network.go index 3576017c2a..4076c492d6 100644 --- a/pkg/model/network.go +++ b/pkg/model/network.go @@ -37,11 +37,6 @@ type NetworkModelBuilder struct { var _ fi.ModelBuilder = &NetworkModelBuilder{} func (b *NetworkModelBuilder) Build(c *fi.ModelBuilderContext) error { - kubernetesVersion, err := b.KubernetesVersion() - if err != nil { - return err - } - sharedVPC := b.Cluster.SharedVPC() vpcName := b.ClusterName() @@ -57,10 +52,10 @@ func (b *NetworkModelBuilder) Build(c *fi.ModelBuilderContext) error { Tags: tags, } - if sharedVPC && VersionGTE(kubernetesVersion, 1, 5) { + if sharedVPC && b.IsKubernetesGTE("1.5") { // If we're running k8s 1.5, and we have e.g. --kubelet-preferred-address-types=InternalIP,Hostname,ExternalIP,LegacyHostIP // then we don't need EnableDNSHostnames any more - glog.V(4).Infof("Kubernetes version %q; skipping EnableDNSHostnames requirement on VPC", kubernetesVersion) + glog.V(4).Infof("Kubernetes version %q; skipping EnableDNSHostnames requirement on VPC", b.KubernetesVersion()) } else { // In theory we don't need to enable it for >= 1.5, // but seems safer to stick with existing behaviour @@ -71,6 +66,7 @@ func (b *NetworkModelBuilder) Build(c *fi.ModelBuilderContext) error { if b.Cluster.Spec.NetworkID != "" { t.ID = s(b.Cluster.Spec.NetworkID) } + if b.Cluster.Spec.NetworkCIDR != "" { t.CIDR = s(b.Cluster.Spec.NetworkCIDR) } diff --git a/pkg/model/pki.go b/pkg/model/pki.go index 059824a75d..0c1603df2a 100644 --- a/pkg/model/pki.go +++ b/pkg/model/pki.go @@ -184,6 +184,7 @@ func (b *PKIModelBuilder) Build(c *fi.ModelBuilderContext) error { } { + // Used by e.g. protokube t := &fitasks.Keypair{ Name: fi.String("kops"), Lifecycle: b.Lifecycle, diff --git a/pkg/model/template_resource.go b/pkg/model/template_resource.go index a5f78d1e4e..b25a57a1f2 100644 --- a/pkg/model/template_resource.go +++ b/pkg/model/template_resource.go @@ -20,8 +20,9 @@ import ( "bytes" "fmt" "io" - "k8s.io/kops/upup/pkg/fi" "text/template" + + "k8s.io/kops/upup/pkg/fi" ) type templateResource struct { diff --git a/pkg/pki/certificate.go b/pkg/pki/certificate.go index 45980b3940..70fe981a6e 100644 --- a/pkg/pki/certificate.go +++ b/pkg/pki/certificate.go @@ -25,8 +25,9 @@ import ( "encoding/json" "encoding/pem" "fmt" - "github.com/golang/glog" "io" + + "github.com/golang/glog" ) type Certificate struct { diff --git a/pkg/pki/privatekey.go b/pkg/pki/privatekey.go index e50ad9c271..1ce25ca2ab 100644 --- a/pkg/pki/privatekey.go +++ b/pkg/pki/privatekey.go @@ -26,8 +26,9 @@ import ( "encoding/json" "encoding/pem" "fmt" - "github.com/golang/glog" "io" + + "github.com/golang/glog" ) func ParsePEMPrivateKey(data []byte) (*PrivateKey, error) { diff --git a/pkg/pretty/help.go b/pkg/pretty/help.go index 503c78ddd8..f3d75abe17 100644 --- a/pkg/pretty/help.go +++ b/pkg/pretty/help.go @@ -18,8 +18,9 @@ package pretty import ( "fmt" - "github.com/MakeNowJust/heredoc" "strings" + + "github.com/MakeNowJust/heredoc" ) // Bash markdown-quotes a bash command for insertion into help text. diff --git a/pkg/templates/templates.go b/pkg/templates/templates.go index 0d91714a27..4e97a2867a 100644 --- a/pkg/templates/templates.go +++ b/pkg/templates/templates.go @@ -19,14 +19,15 @@ package templates import ( "bytes" "fmt" - "github.com/golang/glog" "io" - "k8s.io/kops/pkg/apis/kops" - "k8s.io/kops/upup/pkg/fi" - "k8s.io/kops/util/pkg/vfs" "os" "strings" "text/template" + + "github.com/golang/glog" + "k8s.io/kops/pkg/apis/kops" + "k8s.io/kops/upup/pkg/fi" + "k8s.io/kops/util/pkg/vfs" ) type Templates struct { diff --git a/pkg/util/templater/templater.go b/pkg/util/templater/templater.go index 6f131bc746..d10ef6b4d4 100644 --- a/pkg/util/templater/templater.go +++ b/pkg/util/templater/templater.go @@ -19,7 +19,6 @@ package templater import ( "bytes" "fmt" - "path/filepath" "strings" "text/template" @@ -51,11 +50,10 @@ func (r *Templater) Render(content string, context map[string]interface{}, snipp // @step: add the snippits into the mix for filename, snippet := range snippets { - name := filepath.Base(filename) - if name == templateName { - return "", fmt.Errorf("snippet cannot have the same name as the template: %s", name) + if filename == templateName { + return "", fmt.Errorf("snippet cannot have the same name as the template: %s", filename) } - if _, err = tm.New(name).Parse(snippet); err != nil { + if _, err = tm.New(filename).Parse(snippet); err != nil { return rendered, fmt.Errorf("unable to parse snippet: %s, error: %s", filename, err) } } diff --git a/protokube/pkg/gossip/aws/seeds.go b/protokube/pkg/gossip/aws/seeds.go index dee0d73eef..c477df1be4 100644 --- a/protokube/pkg/gossip/aws/seeds.go +++ b/protokube/pkg/gossip/aws/seeds.go @@ -18,6 +18,7 @@ package aws import ( "fmt" + "github.com/aws/aws-sdk-go/aws" "github.com/aws/aws-sdk-go/service/ec2" "github.com/aws/aws-sdk-go/service/ec2/ec2iface" diff --git a/protokube/pkg/gossip/dns/hosts.go b/protokube/pkg/gossip/dns/hosts.go index 93001a5c7b..f70749f689 100644 --- a/protokube/pkg/gossip/dns/hosts.go +++ b/protokube/pkg/gossip/dns/hosts.go @@ -18,12 +18,13 @@ package dns import ( "fmt" - "github.com/golang/glog" "io/ioutil" "os" "path/filepath" "sort" "strings" + + "github.com/golang/glog" ) const GUARD_BEGIN = "# Begin host entries managed by kops - do not edit" diff --git a/protokube/pkg/gossip/dns/provider/zone.go b/protokube/pkg/gossip/dns/provider/zone.go index 5f38fc7607..aa4b711b8a 100644 --- a/protokube/pkg/gossip/dns/provider/zone.go +++ b/protokube/pkg/gossip/dns/provider/zone.go @@ -18,6 +18,7 @@ package provider import ( "fmt" + "k8s.io/kops/protokube/pkg/gossip/dns" "k8s.io/kubernetes/federation/pkg/dnsprovider" "k8s.io/kubernetes/federation/pkg/dnsprovider/rrstype" diff --git a/protokube/pkg/gossip/dns/provider/zones.go b/protokube/pkg/gossip/dns/provider/zones.go index 43ef6c5b2a..0d1459cd38 100644 --- a/protokube/pkg/gossip/dns/provider/zones.go +++ b/protokube/pkg/gossip/dns/provider/zones.go @@ -18,6 +18,7 @@ package provider import ( "fmt" + "k8s.io/kops/protokube/pkg/gossip/dns" "k8s.io/kubernetes/federation/pkg/dnsprovider" ) diff --git a/protokube/pkg/gossip/gce/seeds.go b/protokube/pkg/gossip/gce/seeds.go index 511ff19720..de4f9447e2 100644 --- a/protokube/pkg/gossip/gce/seeds.go +++ b/protokube/pkg/gossip/gce/seeds.go @@ -18,10 +18,11 @@ package gce import ( "fmt" + "strings" + "github.com/golang/glog" compute "google.golang.org/api/compute/v0.beta" "k8s.io/kops/protokube/pkg/gossip" - "strings" ) type SeedProvider struct { diff --git a/protokube/pkg/gossip/mesh/gossip.go b/protokube/pkg/gossip/mesh/gossip.go index dc7eb4637e..614e2a1157 100644 --- a/protokube/pkg/gossip/mesh/gossip.go +++ b/protokube/pkg/gossip/mesh/gossip.go @@ -18,12 +18,13 @@ package mesh import ( "fmt" - "github.com/golang/glog" - "github.com/weaveworks/mesh" - "k8s.io/kops/protokube/pkg/gossip" "net" "strconv" "time" + + "github.com/golang/glog" + "github.com/weaveworks/mesh" + "k8s.io/kops/protokube/pkg/gossip" ) type MeshGossiper struct { diff --git a/protokube/pkg/gossip/mesh/state.go b/protokube/pkg/gossip/mesh/state.go index 48646c663a..b5e50224ee 100644 --- a/protokube/pkg/gossip/mesh/state.go +++ b/protokube/pkg/gossip/mesh/state.go @@ -18,11 +18,12 @@ package mesh import ( "fmt" + "sync" + "time" + "github.com/gogo/protobuf/proto" "github.com/weaveworks/mesh" "k8s.io/kops/protokube/pkg/gossip" - "sync" - "time" ) // state is an implementation of a LWW map diff --git a/protokube/pkg/protokube/kube_dns.go b/protokube/pkg/protokube/kube_dns.go index 3fd40e7f95..6345aa577e 100644 --- a/protokube/pkg/protokube/kube_dns.go +++ b/protokube/pkg/protokube/kube_dns.go @@ -17,9 +17,10 @@ limitations under the License. package protokube import ( + "time" + "github.com/golang/glog" "k8s.io/kops/dns-controller/pkg/dns" - "time" ) const defaultTTL = time.Minute diff --git a/protokube/pkg/protokube/volume_mounter_test.go b/protokube/pkg/protokube/volume_mounter_test.go index d61f9e874a..74923aa808 100644 --- a/protokube/pkg/protokube/volume_mounter_test.go +++ b/protokube/pkg/protokube/volume_mounter_test.go @@ -17,10 +17,11 @@ limitations under the License. package protokube import ( - "k8s.io/kops/protokube/pkg/etcd" "sort" "strings" "testing" + + "k8s.io/kops/protokube/pkg/etcd" ) func getIDs(volumes []*Volume) string { diff --git a/protokube/pkg/protokube/vsphere_volume.go b/protokube/pkg/protokube/vsphere_volume.go index e2379d2317..163d111d8d 100644 --- a/protokube/pkg/protokube/vsphere_volume.go +++ b/protokube/pkg/protokube/vsphere_volume.go @@ -21,14 +21,15 @@ package protokube import ( "errors" "fmt" - "github.com/golang/glog" "io/ioutil" - etcdmanager "k8s.io/kops/protokube/pkg/etcd" - "k8s.io/kops/upup/pkg/fi/cloudup/vsphere" "net" "os/exec" "runtime" "strings" + + "github.com/golang/glog" + etcdmanager "k8s.io/kops/protokube/pkg/etcd" + "k8s.io/kops/upup/pkg/fi/cloudup/vsphere" ) const VolumeMetaDataFile = "/vol-metadata/metadata.json" diff --git a/tests/codecs/componentconfig_test.go b/tests/codecs/componentconfig_test.go index 0de8e2b654..86ccc4fdf2 100644 --- a/tests/codecs/componentconfig_test.go +++ b/tests/codecs/componentconfig_test.go @@ -17,10 +17,11 @@ limitations under the License. package codecs import ( + "testing" + "k8s.io/kops/pkg/apis/kops/v1alpha2" "k8s.io/kops/pkg/diff" "k8s.io/kops/pkg/kopscodecs" - "testing" ) func TestSerializeEmptyCluster(t *testing.T) { diff --git a/tests/integration/channel/integration_test.go b/tests/integration/channel/integration_test.go index 0d7afbb93e..b6a845b35b 100644 --- a/tests/integration/channel/integration_test.go +++ b/tests/integration/channel/integration_test.go @@ -17,11 +17,12 @@ limitations under the License. package main import ( - "github.com/blang/semver" "io/ioutil" - "k8s.io/kops/pkg/apis/kops" "path" "testing" + + "github.com/blang/semver" + "k8s.io/kops/pkg/apis/kops" ) // TestKopsUpgrades tests the version logic for kops versions diff --git a/tests/integration/conversion/integration_test.go b/tests/integration/conversion/integration_test.go index 359a755560..cbd5dde7f0 100644 --- a/tests/integration/conversion/integration_test.go +++ b/tests/integration/conversion/integration_test.go @@ -19,6 +19,10 @@ package main import ( "bytes" "io/ioutil" + "path" + "strings" + "testing" + "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/runtime/schema" "k8s.io/kops/pkg/apis/kops" @@ -26,9 +30,6 @@ import ( "k8s.io/kops/pkg/apis/kops/v1alpha2" "k8s.io/kops/pkg/diff" "k8s.io/kops/pkg/kopscodecs" - "path" - "strings" - "testing" ) // TestConversionMinimal runs the test on a minimum configuration, similar to kops create cluster minimal.example.com --zones us-west-1a diff --git a/tests/integration/create_cluster/complex/expected-v1alpha2.yaml b/tests/integration/create_cluster/complex/expected-v1alpha2.yaml index 450ec96aa5..3b76fac731 100644 --- a/tests/integration/create_cluster/complex/expected-v1alpha2.yaml +++ b/tests/integration/create_cluster/complex/expected-v1alpha2.yaml @@ -21,6 +21,7 @@ spec: name: a name: events iam: + allowContainerRegistry: true legacy: false kubernetesApiAccess: - 0.0.0.0/0 diff --git a/tests/integration/create_cluster/ha/expected-v1alpha1.yaml b/tests/integration/create_cluster/ha/expected-v1alpha1.yaml index fa63513ce1..df16fdc814 100644 --- a/tests/integration/create_cluster/ha/expected-v1alpha1.yaml +++ b/tests/integration/create_cluster/ha/expected-v1alpha1.yaml @@ -31,6 +31,7 @@ spec: zone: us-test-1c name: events iam: + allowContainerRegistry: true legacy: false kubernetesVersion: v1.6.0-alpha.3 masterPublicName: api.ha.example.com diff --git a/tests/integration/create_cluster/ha/expected-v1alpha2.yaml b/tests/integration/create_cluster/ha/expected-v1alpha2.yaml index c9fc929a0f..8d55022cf3 100644 --- a/tests/integration/create_cluster/ha/expected-v1alpha2.yaml +++ b/tests/integration/create_cluster/ha/expected-v1alpha2.yaml @@ -29,6 +29,7 @@ spec: name: c name: events iam: + allowContainerRegistry: true legacy: false kubernetesApiAccess: - 0.0.0.0/0 diff --git a/tests/integration/create_cluster/ha_encrypt/expected-v1alpha1.yaml b/tests/integration/create_cluster/ha_encrypt/expected-v1alpha1.yaml index 7a8284747b..94149e8d91 100644 --- a/tests/integration/create_cluster/ha_encrypt/expected-v1alpha1.yaml +++ b/tests/integration/create_cluster/ha_encrypt/expected-v1alpha1.yaml @@ -37,6 +37,7 @@ spec: zone: us-test-1c name: events iam: + allowContainerRegistry: true legacy: false kubernetesVersion: v1.6.0-alpha.3 masterPublicName: api.ha.example.com diff --git a/tests/integration/create_cluster/ha_encrypt/expected-v1alpha2.yaml b/tests/integration/create_cluster/ha_encrypt/expected-v1alpha2.yaml index 11d1c55139..1c4b89de24 100644 --- a/tests/integration/create_cluster/ha_encrypt/expected-v1alpha2.yaml +++ b/tests/integration/create_cluster/ha_encrypt/expected-v1alpha2.yaml @@ -35,6 +35,7 @@ spec: name: c name: events iam: + allowContainerRegistry: true legacy: false kubernetesApiAccess: - 0.0.0.0/0 diff --git a/tests/integration/create_cluster/ha_gce/expected-v1alpha2.yaml b/tests/integration/create_cluster/ha_gce/expected-v1alpha2.yaml index 208d7f3bcb..573d64e6eb 100644 --- a/tests/integration/create_cluster/ha_gce/expected-v1alpha2.yaml +++ b/tests/integration/create_cluster/ha_gce/expected-v1alpha2.yaml @@ -29,6 +29,7 @@ spec: name: c name: events iam: + allowContainerRegistry: true legacy: false kubernetesApiAccess: - 0.0.0.0/0 diff --git a/tests/integration/create_cluster/ha_shared_zones/expected-v1alpha2.yaml b/tests/integration/create_cluster/ha_shared_zones/expected-v1alpha2.yaml index a909760abe..763028aac8 100644 --- a/tests/integration/create_cluster/ha_shared_zones/expected-v1alpha2.yaml +++ b/tests/integration/create_cluster/ha_shared_zones/expected-v1alpha2.yaml @@ -37,6 +37,7 @@ spec: name: a-3 name: events iam: + allowContainerRegistry: true legacy: false kubernetesApiAccess: - 0.0.0.0/0 diff --git a/tests/integration/create_cluster/minimal/expected-v1alpha1.yaml b/tests/integration/create_cluster/minimal/expected-v1alpha1.yaml index 3f10d74258..05dd7c1fb2 100644 --- a/tests/integration/create_cluster/minimal/expected-v1alpha1.yaml +++ b/tests/integration/create_cluster/minimal/expected-v1alpha1.yaml @@ -23,6 +23,7 @@ spec: zone: us-test-1a name: events iam: + allowContainerRegistry: true legacy: false kubernetesVersion: v1.4.8 masterPublicName: api.minimal.example.com diff --git a/tests/integration/create_cluster/minimal/expected-v1alpha2.yaml b/tests/integration/create_cluster/minimal/expected-v1alpha2.yaml index ec4afdbac0..691cbcc1f5 100644 --- a/tests/integration/create_cluster/minimal/expected-v1alpha2.yaml +++ b/tests/integration/create_cluster/minimal/expected-v1alpha2.yaml @@ -21,6 +21,7 @@ spec: name: a name: events iam: + allowContainerRegistry: true legacy: false kubernetesApiAccess: - 0.0.0.0/0 diff --git a/tests/integration/create_cluster/ngwspecified/expected-v1alpha1.yaml b/tests/integration/create_cluster/ngwspecified/expected-v1alpha1.yaml index 13bc69963a..12589e3da9 100644 --- a/tests/integration/create_cluster/ngwspecified/expected-v1alpha1.yaml +++ b/tests/integration/create_cluster/ngwspecified/expected-v1alpha1.yaml @@ -24,6 +24,7 @@ spec: zone: us-test-1a name: events iam: + allowContainerRegistry: true legacy: false kubernetesVersion: v1.4.8 masterPublicName: api.private.example.com diff --git a/tests/integration/create_cluster/ngwspecified/expected-v1alpha2.yaml b/tests/integration/create_cluster/ngwspecified/expected-v1alpha2.yaml index 88cde251cb..3c5c7b8905 100644 --- a/tests/integration/create_cluster/ngwspecified/expected-v1alpha2.yaml +++ b/tests/integration/create_cluster/ngwspecified/expected-v1alpha2.yaml @@ -22,6 +22,7 @@ spec: name: a name: events iam: + allowContainerRegistry: true legacy: false kubernetesApiAccess: - 0.0.0.0/0 diff --git a/tests/integration/create_cluster/overrides/expected-v1alpha2.yaml b/tests/integration/create_cluster/overrides/expected-v1alpha2.yaml index 7277ae3e6e..8b64ef865f 100644 --- a/tests/integration/create_cluster/overrides/expected-v1alpha2.yaml +++ b/tests/integration/create_cluster/overrides/expected-v1alpha2.yaml @@ -21,6 +21,7 @@ spec: name: a name: events iam: + allowContainerRegistry: true legacy: false kubernetesApiAccess: - 0.0.0.0/0 diff --git a/tests/integration/create_cluster/private/expected-v1alpha1.yaml b/tests/integration/create_cluster/private/expected-v1alpha1.yaml index 7283203284..f6b6d391d8 100644 --- a/tests/integration/create_cluster/private/expected-v1alpha1.yaml +++ b/tests/integration/create_cluster/private/expected-v1alpha1.yaml @@ -28,6 +28,7 @@ spec: zone: us-test-1a name: events iam: + allowContainerRegistry: true legacy: false kubernetesVersion: v1.4.8 masterPublicName: api.private.example.com diff --git a/tests/integration/create_cluster/private/expected-v1alpha2.yaml b/tests/integration/create_cluster/private/expected-v1alpha2.yaml index 8d52011322..bc450d10dc 100644 --- a/tests/integration/create_cluster/private/expected-v1alpha2.yaml +++ b/tests/integration/create_cluster/private/expected-v1alpha2.yaml @@ -26,6 +26,7 @@ spec: name: a name: events iam: + allowContainerRegistry: true legacy: false kubernetesApiAccess: - 0.0.0.0/0 diff --git a/upup/pkg/fi/assettasks/docker_cli.go b/upup/pkg/fi/assettasks/docker_cli.go index 7ffde4fae6..a11fc2c2a4 100644 --- a/upup/pkg/fi/assettasks/docker_cli.go +++ b/upup/pkg/fi/assettasks/docker_cli.go @@ -18,8 +18,9 @@ package assettasks import ( "fmt" - "github.com/golang/glog" "os/exec" + + "github.com/golang/glog" ) // dockerCLI encapsulates access to docker via the CLI diff --git a/upup/pkg/fi/changes.go b/upup/pkg/fi/changes.go index 216dfec189..f53098f490 100644 --- a/upup/pkg/fi/changes.go +++ b/upup/pkg/fi/changes.go @@ -17,8 +17,9 @@ limitations under the License. package fi import ( - "github.com/golang/glog" "reflect" + + "github.com/golang/glog" ) // An important part of our state synchronization is to compare two tasks, to see what has changed diff --git a/upup/pkg/fi/cloudup/awstasks/cloudformation.go b/upup/pkg/fi/cloudup/awstasks/cloudformation.go index 1be549c36c..7b8c4475d1 100644 --- a/upup/pkg/fi/cloudup/awstasks/cloudformation.go +++ b/upup/pkg/fi/cloudup/awstasks/cloudformation.go @@ -17,8 +17,9 @@ limitations under the License. package awstasks import ( - "github.com/aws/aws-sdk-go/aws" "sort" + + "github.com/aws/aws-sdk-go/aws" ) type cloudformationTag struct { diff --git a/upup/pkg/fi/cloudup/awstasks/dhcp_options.go b/upup/pkg/fi/cloudup/awstasks/dhcp_options.go index b33aba6d50..d4fc935a2d 100644 --- a/upup/pkg/fi/cloudup/awstasks/dhcp_options.go +++ b/upup/pkg/fi/cloudup/awstasks/dhcp_options.go @@ -19,6 +19,8 @@ package awstasks import ( "fmt" + "strings" + "github.com/aws/aws-sdk-go/aws" "github.com/aws/aws-sdk-go/service/ec2" "github.com/golang/glog" @@ -26,7 +28,6 @@ import ( "k8s.io/kops/upup/pkg/fi/cloudup/awsup" "k8s.io/kops/upup/pkg/fi/cloudup/cloudformation" "k8s.io/kops/upup/pkg/fi/cloudup/terraform" - "strings" ) //go:generate fitask -type=DHCPOptions diff --git a/upup/pkg/fi/cloudup/awstasks/dnszone.go b/upup/pkg/fi/cloudup/awstasks/dnszone.go index 5e7135d38d..351f359fe8 100644 --- a/upup/pkg/fi/cloudup/awstasks/dnszone.go +++ b/upup/pkg/fi/cloudup/awstasks/dnszone.go @@ -19,6 +19,11 @@ package awstasks import ( "fmt" + "math/rand" + "reflect" + "strconv" + "strings" + "github.com/aws/aws-sdk-go/aws" "github.com/aws/aws-sdk-go/service/route53" "github.com/golang/glog" @@ -26,10 +31,6 @@ import ( "k8s.io/kops/upup/pkg/fi/cloudup/awsup" "k8s.io/kops/upup/pkg/fi/cloudup/cloudformation" "k8s.io/kops/upup/pkg/fi/cloudup/terraform" - "math/rand" - "reflect" - "strconv" - "strings" ) // DNSZone is a zone object in a dns provider diff --git a/upup/pkg/fi/cloudup/awstasks/elastic_ip_test.go b/upup/pkg/fi/cloudup/awstasks/elastic_ip_test.go index d056bd6442..55d5c53d77 100644 --- a/upup/pkg/fi/cloudup/awstasks/elastic_ip_test.go +++ b/upup/pkg/fi/cloudup/awstasks/elastic_ip_test.go @@ -18,15 +18,16 @@ package awstasks import ( "bytes" + "os" + "reflect" + "testing" + "time" + "github.com/aws/aws-sdk-go/service/ec2" "k8s.io/kops/cloudmock/aws/mockec2" "k8s.io/kops/pkg/assets" "k8s.io/kops/upup/pkg/fi" "k8s.io/kops/upup/pkg/fi/cloudup/awsup" - "os" - "reflect" - "testing" - "time" ) const defaultDeadline = 2 * time.Second diff --git a/upup/pkg/fi/cloudup/awstasks/iamrole.go b/upup/pkg/fi/cloudup/awstasks/iamrole.go index 57f2327d9b..aae44301b0 100644 --- a/upup/pkg/fi/cloudup/awstasks/iamrole.go +++ b/upup/pkg/fi/cloudup/awstasks/iamrole.go @@ -21,6 +21,9 @@ import ( "encoding/json" + "net/url" + "reflect" + "github.com/aws/aws-sdk-go/aws" "github.com/aws/aws-sdk-go/aws/awserr" "github.com/aws/aws-sdk-go/service/iam" @@ -30,8 +33,6 @@ import ( "k8s.io/kops/upup/pkg/fi/cloudup/awsup" "k8s.io/kops/upup/pkg/fi/cloudup/cloudformation" "k8s.io/kops/upup/pkg/fi/cloudup/terraform" - "net/url" - "reflect" ) //go:generate fitask -type=IAMRole diff --git a/upup/pkg/fi/cloudup/awstasks/iamrolepolicy.go b/upup/pkg/fi/cloudup/awstasks/iamrolepolicy.go index 3f9f876792..c5772b0d0a 100644 --- a/upup/pkg/fi/cloudup/awstasks/iamrolepolicy.go +++ b/upup/pkg/fi/cloudup/awstasks/iamrolepolicy.go @@ -20,6 +20,8 @@ import ( "fmt" "encoding/json" + "net/url" + "github.com/aws/aws-sdk-go/aws" "github.com/aws/aws-sdk-go/aws/awserr" "github.com/aws/aws-sdk-go/service/iam" @@ -29,7 +31,6 @@ import ( "k8s.io/kops/upup/pkg/fi/cloudup/awsup" "k8s.io/kops/upup/pkg/fi/cloudup/cloudformation" "k8s.io/kops/upup/pkg/fi/cloudup/terraform" - "net/url" ) //go:generate fitask -type=IAMRolePolicy diff --git a/upup/pkg/fi/cloudup/awstasks/instance.go b/upup/pkg/fi/cloudup/awstasks/instance.go index c18ec6d6da..c6c143de30 100644 --- a/upup/pkg/fi/cloudup/awstasks/instance.go +++ b/upup/pkg/fi/cloudup/awstasks/instance.go @@ -20,13 +20,14 @@ import ( "fmt" "encoding/base64" + "strings" + "github.com/aws/aws-sdk-go/aws" "github.com/aws/aws-sdk-go/service/ec2" "github.com/golang/glog" "k8s.io/kops/upup/pkg/fi" "k8s.io/kops/upup/pkg/fi/cloudup/awsup" "k8s.io/kops/upup/pkg/fi/cloudup/terraform" - "strings" ) const MaxUserDataSize = 16384 diff --git a/upup/pkg/fi/cloudup/awstasks/launchconfiguration.go b/upup/pkg/fi/cloudup/awstasks/launchconfiguration.go index 3872cf11aa..2a43407e01 100644 --- a/upup/pkg/fi/cloudup/awstasks/launchconfiguration.go +++ b/upup/pkg/fi/cloudup/awstasks/launchconfiguration.go @@ -19,6 +19,10 @@ package awstasks import ( "encoding/base64" "fmt" + "sort" + "strings" + "time" + "github.com/aws/aws-sdk-go/aws" "github.com/aws/aws-sdk-go/service/autoscaling" "github.com/golang/glog" @@ -27,9 +31,6 @@ import ( "k8s.io/kops/upup/pkg/fi/cloudup/awsup" "k8s.io/kops/upup/pkg/fi/cloudup/cloudformation" "k8s.io/kops/upup/pkg/fi/cloudup/terraform" - "sort" - "strings" - "time" ) //go:generate fitask -type=LaunchConfiguration diff --git a/upup/pkg/fi/cloudup/awstasks/securitygroup_test.go b/upup/pkg/fi/cloudup/awstasks/securitygroup_test.go index 09fe5d2439..98b36062d3 100644 --- a/upup/pkg/fi/cloudup/awstasks/securitygroup_test.go +++ b/upup/pkg/fi/cloudup/awstasks/securitygroup_test.go @@ -17,13 +17,14 @@ limitations under the License. package awstasks import ( + "reflect" + "testing" + "github.com/aws/aws-sdk-go/aws" "github.com/aws/aws-sdk-go/service/ec2" "k8s.io/kops/cloudmock/aws/mockec2" "k8s.io/kops/upup/pkg/fi" "k8s.io/kops/upup/pkg/fi/cloudup/awsup" - "reflect" - "testing" ) func TestParseRemovalRule(t *testing.T) { diff --git a/upup/pkg/fi/cloudup/awstasks/securitygrouprule.go b/upup/pkg/fi/cloudup/awstasks/securitygrouprule.go index 6136b29b9a..c3b7d90986 100644 --- a/upup/pkg/fi/cloudup/awstasks/securitygrouprule.go +++ b/upup/pkg/fi/cloudup/awstasks/securitygrouprule.go @@ -19,6 +19,8 @@ package awstasks import ( "fmt" + "strings" + "github.com/aws/aws-sdk-go/aws" "github.com/aws/aws-sdk-go/service/ec2" "github.com/golang/glog" @@ -27,7 +29,6 @@ import ( "k8s.io/kops/upup/pkg/fi/cloudup/awsup" "k8s.io/kops/upup/pkg/fi/cloudup/cloudformation" "k8s.io/kops/upup/pkg/fi/cloudup/terraform" - "strings" ) //go:generate fitask -type=SecurityGroupRule diff --git a/upup/pkg/fi/cloudup/awstasks/subnet.go b/upup/pkg/fi/cloudup/awstasks/subnet.go index d60212db0f..28f9d7c57f 100644 --- a/upup/pkg/fi/cloudup/awstasks/subnet.go +++ b/upup/pkg/fi/cloudup/awstasks/subnet.go @@ -82,7 +82,8 @@ func (e *Subnet) Find(c *fi.Context) (*Subnet, error) { e.ID = actual.ID // Prevent spurious changes - actual.Lifecycle = e.Lifecycle + actual.Lifecycle = e.Lifecycle // Lifecycle is not materialized in AWS + actual.Name = e.Name // Name is part of Tags return actual, nil } @@ -159,8 +160,6 @@ func (_ *Subnet) RenderAWS(t *awsup.AWSAPITarget, a, e, changes *Subnet) error { if a == nil { return fmt.Errorf("Subnet with id %q not found", fi.StringValue(e.ID)) } - - return nil } if a == nil { @@ -210,6 +209,8 @@ func (_ *Subnet) RenderTerraform(t *terraform.TerraformTarget, a, e, changes *Su shared := fi.BoolValue(e.Shared) if shared { // Not terraform owned / managed + // We won't apply changes, but our validation (kops update) will still warn + // // We probably shouldn't output subnet_ids only in this case - we normally output them by role, // but removing it now might break people. We could always output subnet_ids though, if we // ever get a request for that. @@ -251,6 +252,7 @@ func (_ *Subnet) RenderCloudformation(t *cloudformation.CloudformationTarget, a, shared := fi.BoolValue(e.Shared) if shared { // Not cloudformation owned / managed + // We won't apply changes, but our validation (kops update) will still warn return nil } diff --git a/upup/pkg/fi/cloudup/awstasks/subnet_test.go b/upup/pkg/fi/cloudup/awstasks/subnet_test.go index 3b853a4920..b5fa11710e 100644 --- a/upup/pkg/fi/cloudup/awstasks/subnet_test.go +++ b/upup/pkg/fi/cloudup/awstasks/subnet_test.go @@ -18,8 +18,14 @@ package awstasks import ( "fmt" - "k8s.io/kops/upup/pkg/fi" + "reflect" "testing" + + "github.com/aws/aws-sdk-go/aws" + "github.com/aws/aws-sdk-go/service/ec2" + "k8s.io/kops/cloudmock/aws/mockec2" + "k8s.io/kops/upup/pkg/fi" + "k8s.io/kops/upup/pkg/fi/cloudup/awsup" ) func Test_Subnet_ValidateRequired(t *testing.T) { @@ -56,3 +62,199 @@ func Test_Subnet_CannotChangeSubnet(t *testing.T) { t.Errorf("unexpected error: %v", err) } } + +func TestSubnetCreate(t *testing.T) { + cloud := awsup.BuildMockAWSCloud("us-east-1", "abc") + c := &mockec2.MockEC2{} + cloud.MockEC2 = c + + // We define a function so we can rebuild the tasks, because we modify in-place when running + buildTasks := func() map[string]fi.Task { + vpc1 := &VPC{ + Name: s("vpc1"), + CIDR: s("172.20.0.0/16"), + Tags: map[string]string{"Name": "vpc1"}, + } + subnet1 := &Subnet{ + Name: s("subnet1"), + VPC: vpc1, + CIDR: s("172.20.1.0/24"), + Tags: map[string]string{"Name": "subnet1"}, + } + + return map[string]fi.Task{ + "subnet1": subnet1, + "vpc1": vpc1, + } + } + + { + allTasks := buildTasks() + subnet1 := allTasks["subnet1"].(*Subnet) + + target := &awsup.AWSAPITarget{ + Cloud: cloud, + } + + context, err := fi.NewContext(target, nil, cloud, nil, nil, nil, true, allTasks) + if err != nil { + t.Fatalf("error building context: %v", err) + } + + if err := context.RunTasks(defaultDeadline); err != nil { + t.Fatalf("unexpected error during Run: %v", err) + } + + if fi.StringValue(subnet1.ID) == "" { + t.Fatalf("ID not set after create") + } + + if len(c.SubnetIds()) != 1 { + t.Fatalf("Expected exactly one Subnet; found %v", c.SubnetIds()) + } + + expected := &ec2.Subnet{ + CidrBlock: aws.String("172.20.1.0/24"), + SubnetId: aws.String("subnet-1"), + VpcId: aws.String("vpc-1"), + Tags: buildTags(map[string]string{ + "Name": "subnet1", + }), + } + actual := c.FindSubnet(*subnet1.ID) + if actual == nil { + t.Fatalf("Subnet created but then not found") + } + if !reflect.DeepEqual(actual, expected) { + t.Fatalf("Unexpected Subnet: expected=%v actual=%v", expected, actual) + } + } + + { + allTasks := buildTasks() + checkNoChanges(t, cloud, allTasks) + } +} + +func TestSharedSubnetCreateDoesNotCreateNew(t *testing.T) { + cloud := awsup.BuildMockAWSCloud("us-east-1", "abc") + c := &mockec2.MockEC2{} + cloud.MockEC2 = c + + // Pre-create the vpc / subnet + vpc, err := c.CreateVpc(&ec2.CreateVpcInput{ + CidrBlock: aws.String("172.20.0.0/16"), + }) + if err != nil { + t.Fatalf("error creating test VPC: %v", err) + } + _, err = c.CreateTags(&ec2.CreateTagsInput{ + Resources: []*string{vpc.Vpc.VpcId}, + Tags: []*ec2.Tag{ + { + Key: aws.String("Name"), + Value: aws.String("ExistingVPC"), + }, + }, + }) + if err != nil { + t.Fatalf("error tagging test vpc: %v", err) + } + + subnet, err := c.CreateSubnet(&ec2.CreateSubnetInput{ + VpcId: vpc.Vpc.VpcId, + CidrBlock: aws.String("172.20.1.0/24"), + }) + if err != nil { + t.Fatalf("error creating test subnet: %v", err) + } + + _, err = c.CreateTags(&ec2.CreateTagsInput{ + Resources: []*string{subnet.Subnet.SubnetId}, + Tags: []*ec2.Tag{ + { + Key: aws.String("Name"), + Value: aws.String("ExistingSubnet"), + }, + }, + }) + if err != nil { + t.Fatalf("error tagging test subnet: %v", err) + } + + // We define a function so we can rebuild the tasks, because we modify in-place when running + buildTasks := func() map[string]fi.Task { + vpc1 := &VPC{ + Name: s("vpc1"), + CIDR: s("172.20.0.0/16"), + Tags: map[string]string{"kubernetes.io/cluster/cluster.example.com": "shared"}, + Shared: fi.Bool(true), + ID: vpc.Vpc.VpcId, + } + subnet1 := &Subnet{ + Name: s("subnet1"), + VPC: vpc1, + CIDR: s("172.20.1.0/24"), + Tags: map[string]string{"kubernetes.io/cluster/cluster.example.com": "shared"}, + Shared: fi.Bool(true), + ID: subnet.Subnet.SubnetId, + } + + return map[string]fi.Task{ + "subnet1": subnet1, + "vpc1": vpc1, + } + } + + { + allTasks := buildTasks() + subnet1 := allTasks["subnet1"].(*Subnet) + + target := &awsup.AWSAPITarget{ + Cloud: cloud, + } + + context, err := fi.NewContext(target, nil, cloud, nil, nil, nil, true, allTasks) + if err != nil { + t.Fatalf("error building context: %v", err) + } + + if err := context.RunTasks(defaultDeadline); err != nil { + t.Fatalf("unexpected error during Run: %v", err) + } + + if fi.StringValue(subnet1.ID) == "" { + t.Fatalf("ID not set after create") + } + + if len(c.SubnetIds()) != 1 { + t.Fatalf("Expected exactly one Subnet; found %v", c.SubnetIds()) + } + + actual := c.FindSubnet(*subnet.Subnet.SubnetId) + if actual == nil { + t.Fatalf("Subnet created but then not found") + } + expected := &ec2.Subnet{ + CidrBlock: aws.String("172.20.1.0/24"), + SubnetId: aws.String("subnet-1"), + VpcId: aws.String("vpc-1"), + Tags: buildTags(map[string]string{ + "Name": "ExistingSubnet", + "kubernetes.io/cluster/cluster.example.com": "shared", + }), + } + + mockec2.SortTags(expected.Tags) + mockec2.SortTags(actual.Tags) + + if !reflect.DeepEqual(actual, expected) { + t.Fatalf("Unexpected Subnet: expected=%v actual=%v", expected, actual) + } + } + + { + allTasks := buildTasks() + checkNoChanges(t, cloud, allTasks) + } +} diff --git a/upup/pkg/fi/cloudup/awstasks/vpc.go b/upup/pkg/fi/cloudup/awstasks/vpc.go index bf17eb6b75..441b2faa50 100644 --- a/upup/pkg/fi/cloudup/awstasks/vpc.go +++ b/upup/pkg/fi/cloudup/awstasks/vpc.go @@ -107,6 +107,7 @@ func (e *VPC) Find(c *fi.Context) (*VPC, error) { e.ID = actual.ID } actual.Lifecycle = e.Lifecycle + actual.Name = e.Name // Name is part of Tags return actual, nil } @@ -143,11 +144,10 @@ func (_ *VPC) RenderAWS(t *awsup.AWSAPITarget, a, e, changes *VPC) error { if featureflag.VPCSkipEnableDNSSupport.Enabled() { glog.Warningf("VPC did not have EnableDNSSupport=true, but ignoring because of VPCSkipEnableDNSSupport feature-flag") } else { + // TODO: We could easily just allow kops to fix this... return fmt.Errorf("VPC with id %q was set to be shared, but did not have EnableDNSSupport=true.", fi.StringValue(e.ID)) } } - - return nil } if a == nil { @@ -189,12 +189,7 @@ func (_ *VPC) RenderAWS(t *awsup.AWSAPITarget, a, e, changes *VPC) error { } } - tags := e.Tags - if shared { - // Don't tag shared resources - tags = nil - } - return t.AddAWSTags(*e.ID, tags) + return t.AddAWSTags(*e.ID, e.Tags) } type terraformVPC struct { @@ -212,6 +207,7 @@ func (_ *VPC) RenderTerraform(t *terraform.TerraformTarget, a, e, changes *VPC) shared := fi.BoolValue(e.Shared) if shared { // Not terraform owned / managed + // We won't apply changes, but our validation (kops update) will still warn return nil } @@ -250,6 +246,7 @@ func (_ *VPC) RenderCloudformation(t *cloudformation.CloudformationTarget, a, e, shared := fi.BoolValue(e.Shared) if shared { // Not cloudformation owned / managed + // We won't apply changes, but our validation (kops update) will still warn return nil } diff --git a/upup/pkg/fi/cloudup/awstasks/vpc_test.go b/upup/pkg/fi/cloudup/awstasks/vpc_test.go index 68c867900e..0ab089bc14 100644 --- a/upup/pkg/fi/cloudup/awstasks/vpc_test.go +++ b/upup/pkg/fi/cloudup/awstasks/vpc_test.go @@ -17,13 +17,14 @@ limitations under the License. package awstasks import ( + "reflect" + "testing" + "github.com/aws/aws-sdk-go/aws" "github.com/aws/aws-sdk-go/service/ec2" "k8s.io/kops/cloudmock/aws/mockec2" "k8s.io/kops/upup/pkg/fi" "k8s.io/kops/upup/pkg/fi/cloudup/awsup" - "reflect" - "testing" ) func TestVPCCreate(t *testing.T) { diff --git a/upup/pkg/fi/cloudup/awsup/aws_utils.go b/upup/pkg/fi/cloudup/awsup/aws_utils.go index 82f46574f0..e4474df2f5 100644 --- a/upup/pkg/fi/cloudup/awsup/aws_utils.go +++ b/upup/pkg/fi/cloudup/awsup/aws_utils.go @@ -18,6 +18,8 @@ package awsup import ( "fmt" + "os" + "github.com/aws/aws-sdk-go/aws" "github.com/aws/aws-sdk-go/aws/awserr" "github.com/aws/aws-sdk-go/aws/session" @@ -26,7 +28,6 @@ import ( "github.com/aws/aws-sdk-go/service/elb" "github.com/golang/glog" "k8s.io/kops/pkg/apis/kops" - "os" ) // allRegions is the list of all regions; tests will set the values diff --git a/upup/pkg/fi/cloudup/awsup/aws_utils_test.go b/upup/pkg/fi/cloudup/awsup/aws_utils_test.go index 28b8fa2861..52a8454c2a 100644 --- a/upup/pkg/fi/cloudup/awsup/aws_utils_test.go +++ b/upup/pkg/fi/cloudup/awsup/aws_utils_test.go @@ -17,10 +17,11 @@ limitations under the License. package awsup import ( + "testing" + "github.com/aws/aws-sdk-go/aws" "github.com/aws/aws-sdk-go/service/ec2" "k8s.io/kops/pkg/apis/kops" - "testing" ) func TestValidateRegion(t *testing.T) { diff --git a/upup/pkg/fi/cloudup/awsup/instancegroups.go b/upup/pkg/fi/cloudup/awsup/instancegroups.go index 1af07c0ab7..cb880f667f 100644 --- a/upup/pkg/fi/cloudup/awsup/instancegroups.go +++ b/upup/pkg/fi/cloudup/awsup/instancegroups.go @@ -18,6 +18,7 @@ package awsup import ( "fmt" + "github.com/golang/glog" "k8s.io/kops/pkg/apis/kops" ) diff --git a/upup/pkg/fi/cloudup/awsup/logging_retryer.go b/upup/pkg/fi/cloudup/awsup/logging_retryer.go index 2d12877b55..fa29c53d0f 100644 --- a/upup/pkg/fi/cloudup/awsup/logging_retryer.go +++ b/upup/pkg/fi/cloudup/awsup/logging_retryer.go @@ -18,10 +18,11 @@ package awsup import ( "fmt" + "time" + "github.com/aws/aws-sdk-go/aws/client" "github.com/aws/aws-sdk-go/aws/request" "github.com/golang/glog" - "time" ) // LoggingRetryer adds some logging when we are retrying, so we have some idea what is happening diff --git a/upup/pkg/fi/cloudup/awsup/status.go b/upup/pkg/fi/cloudup/awsup/status.go index a785959a1c..84a620adb6 100644 --- a/upup/pkg/fi/cloudup/awsup/status.go +++ b/upup/pkg/fi/cloudup/awsup/status.go @@ -18,13 +18,14 @@ package awsup import ( "fmt" + "strings" + "github.com/aws/aws-sdk-go/aws" "github.com/aws/aws-sdk-go/service/ec2" "github.com/golang/glog" "k8s.io/kops/pkg/apis/kops" "k8s.io/kops/protokube/pkg/etcd" "k8s.io/kops/upup/pkg/fi" - "strings" ) // FindClusterStatus discovers the status of the cluster, by looking for the tagged etcd volumes diff --git a/upup/pkg/fi/cloudup/cloudformation/target.go b/upup/pkg/fi/cloudup/cloudformation/target.go index 6f664c5d21..502e14819b 100644 --- a/upup/pkg/fi/cloudup/cloudformation/target.go +++ b/upup/pkg/fi/cloudup/cloudformation/target.go @@ -19,13 +19,14 @@ package cloudformation import ( "encoding/json" "fmt" - "github.com/golang/glog" "io/ioutil" - "k8s.io/kops/upup/pkg/fi" "os" "path" "strings" "sync" + + "github.com/golang/glog" + "k8s.io/kops/upup/pkg/fi" ) type CloudformationTarget struct { diff --git a/upup/pkg/fi/cloudup/defaults_test.go b/upup/pkg/fi/cloudup/defaults_test.go index 259ff8f3cc..f65638c74a 100644 --- a/upup/pkg/fi/cloudup/defaults_test.go +++ b/upup/pkg/fi/cloudup/defaults_test.go @@ -17,8 +17,9 @@ limitations under the License. package cloudup import ( - "k8s.io/kops/pkg/apis/kops" "testing" + + "k8s.io/kops/pkg/apis/kops" ) func TestPopulateClusterSpec_Proxy(t *testing.T) { diff --git a/upup/pkg/fi/cloudup/dns.go b/upup/pkg/fi/cloudup/dns.go index 0b180a0338..cbaa222ecc 100644 --- a/upup/pkg/fi/cloudup/dns.go +++ b/upup/pkg/fi/cloudup/dns.go @@ -18,6 +18,10 @@ package cloudup import ( "fmt" + "net" + "os" + "strings" + "github.com/golang/glog" "k8s.io/kops/dns-controller/pkg/dns" "k8s.io/kops/pkg/apis/kops" @@ -27,9 +31,6 @@ import ( "k8s.io/kops/upup/pkg/fi" "k8s.io/kubernetes/federation/pkg/dnsprovider" "k8s.io/kubernetes/federation/pkg/dnsprovider/rrstype" - "net" - "os" - "strings" ) const ( diff --git a/upup/pkg/fi/cloudup/dns_test.go b/upup/pkg/fi/cloudup/dns_test.go index 5fd6d0c8ea..3f20d0002d 100644 --- a/upup/pkg/fi/cloudup/dns_test.go +++ b/upup/pkg/fi/cloudup/dns_test.go @@ -17,10 +17,11 @@ limitations under the License. package cloudup import ( - "k8s.io/kops/pkg/apis/kops" "reflect" "sort" "testing" + + "k8s.io/kops/pkg/apis/kops" ) func TestPrecreateDNSNames(t *testing.T) { diff --git a/upup/pkg/fi/cloudup/dnstasks/dnszone.go b/upup/pkg/fi/cloudup/dnstasks/dnszone.go index aceef3a2a7..af708fd912 100644 --- a/upup/pkg/fi/cloudup/dnstasks/dnszone.go +++ b/upup/pkg/fi/cloudup/dnstasks/dnszone.go @@ -19,10 +19,11 @@ package dnstasks import ( "fmt" + "strings" + "github.com/golang/glog" "k8s.io/kops/upup/pkg/fi" "k8s.io/kubernetes/federation/pkg/dnsprovider" - "strings" ) // DNSZone is a zone object in a dns provider diff --git a/upup/pkg/fi/cloudup/gce/BUILD.bazel b/upup/pkg/fi/cloudup/gce/BUILD.bazel index acbbf94040..ebb7cc2b43 100644 --- a/upup/pkg/fi/cloudup/gce/BUILD.bazel +++ b/upup/pkg/fi/cloudup/gce/BUILD.bazel @@ -26,6 +26,7 @@ go_library( "//vendor/google.golang.org/api/compute/v0.beta:go_default_library", "//vendor/google.golang.org/api/googleapi:go_default_library", "//vendor/google.golang.org/api/iam/v1:go_default_library", + "//vendor/google.golang.org/api/oauth2/v2:go_default_library", "//vendor/google.golang.org/api/storage/v1:go_default_library", "//vendor/k8s.io/api/core/v1:go_default_library", "//vendor/k8s.io/apimachinery/pkg/util/wait:go_default_library", diff --git a/upup/pkg/fi/cloudup/gce/gce_cloud.go b/upup/pkg/fi/cloudup/gce/gce_cloud.go index 13c639e274..9031ec331c 100644 --- a/upup/pkg/fi/cloudup/gce/gce_cloud.go +++ b/upup/pkg/fi/cloudup/gce/gce_cloud.go @@ -18,13 +18,17 @@ package gce import ( "fmt" + "net/http" "strings" + "os" + "github.com/golang/glog" "golang.org/x/net/context" "golang.org/x/oauth2/google" compute "google.golang.org/api/compute/v0.beta" "google.golang.org/api/iam/v1" + oauth2 "google.golang.org/api/oauth2/v2" "google.golang.org/api/storage/v1" "k8s.io/kops/pkg/apis/kops" "k8s.io/kops/upup/pkg/fi" @@ -85,7 +89,12 @@ func NewGCECloud(region string, project string, labels map[string]string) (GCECl ctx := context.Background() - client, err := google.DefaultClient(ctx, compute.ComputeScope) + if os.Getenv("GOOGLE_APPLICATION_CREDENTIALS") != "" { + glog.Infof("Will load GOOGLE_APPLICATION_CREDENTIALS from %s", os.Getenv("GOOGLE_APPLICATION_CREDENTIALS")) + } + + // TODO: should we create different clients with per-service scopes? + client, err := google.DefaultClient(ctx, compute.CloudPlatformScope) if err != nil { return nil, fmt.Errorf("error building google API client: %v", err) } @@ -109,6 +118,17 @@ func NewGCECloud(region string, project string, labels map[string]string) (GCECl gceCloudInstances[region+"::"+project] = c + { + // Attempt to log the current GCE service account in user, for diagnostic purposes + // At least until we get e2e running, we're doing this always + tokenInfo, err := c.getTokenInfo(client) + if err != nil { + glog.Infof("unable to get token info: %v", err) + } else { + glog.Infof("running with GCE credentials: email=%s, scope=%s", tokenInfo.Email, tokenInfo.Scope) + } + } + return c.WithLabels(labels), nil } @@ -291,3 +311,30 @@ func FindInstanceTemplates(c GCECloud, clusterName string) ([]*compute.InstanceT return matches, nil } + +// logTokenInfo returns information about the active credential +func (c *gceCloudImplementation) getTokenInfo(client *http.Client) (*oauth2.Tokeninfo, error) { + tokenSource, err := google.DefaultTokenSource(context.TODO(), compute.CloudPlatformScope) + if err != nil { + return nil, fmt.Errorf("error building token source: %v", err) + } + + token, err := tokenSource.Token() + if err != nil { + return nil, fmt.Errorf("error getting token: %v", err) + } + + // Note: do not log token or any portion of it + + service, err := oauth2.New(client) + if err != nil { + return nil, fmt.Errorf("error creating oauth2 service client: %v", err) + } + + tokenInfo, err := service.Tokeninfo().AccessToken(token.AccessToken).Do() + if err != nil { + return nil, fmt.Errorf("error fetching oauth2 token info: %v", err) + } + + return tokenInfo, nil +} diff --git a/upup/pkg/fi/cloudup/gce/op.go b/upup/pkg/fi/cloudup/gce/op.go index e24378b273..0d29f13699 100644 --- a/upup/pkg/fi/cloudup/gce/op.go +++ b/upup/pkg/fi/cloudup/gce/op.go @@ -21,11 +21,12 @@ package gce import ( "fmt" + "time" + "github.com/golang/glog" compute "google.golang.org/api/compute/v0.beta" "google.golang.org/api/googleapi" "k8s.io/apimachinery/pkg/util/wait" - "time" ) const ( diff --git a/upup/pkg/fi/cloudup/gce/status.go b/upup/pkg/fi/cloudup/gce/status.go index dfc1ea69b1..b8259d6139 100644 --- a/upup/pkg/fi/cloudup/gce/status.go +++ b/upup/pkg/fi/cloudup/gce/status.go @@ -19,12 +19,13 @@ package gce import ( "context" "fmt" + "strings" + "github.com/golang/glog" compute "google.golang.org/api/compute/v0.beta" "k8s.io/kops/pkg/apis/kops" "k8s.io/kops/protokube/pkg/etcd" "k8s.io/kops/upup/pkg/fi" - "strings" ) func (c *gceCloudImplementation) allZones() ([]string, error) { diff --git a/upup/pkg/fi/cloudup/gce/utils.go b/upup/pkg/fi/cloudup/gce/utils.go index 96cc7b2160..291126f494 100644 --- a/upup/pkg/fi/cloudup/gce/utils.go +++ b/upup/pkg/fi/cloudup/gce/utils.go @@ -18,8 +18,9 @@ package gce import ( "fmt" - "google.golang.org/api/googleapi" "strings" + + "google.golang.org/api/googleapi" ) func IsNotFound(err error) bool { diff --git a/upup/pkg/fi/cloudup/gce/wrappers.go b/upup/pkg/fi/cloudup/gce/wrappers.go index ce042c66dd..ac082d2f51 100644 --- a/upup/pkg/fi/cloudup/gce/wrappers.go +++ b/upup/pkg/fi/cloudup/gce/wrappers.go @@ -18,6 +18,7 @@ package gce import ( "fmt" + "github.com/golang/glog" context "golang.org/x/net/context" compute "google.golang.org/api/compute/v0.beta" diff --git a/upup/pkg/fi/cloudup/gcetasks/storagebucketacl.go b/upup/pkg/fi/cloudup/gcetasks/storagebucketacl.go index 1fbeefc626..2021a8659f 100644 --- a/upup/pkg/fi/cloudup/gcetasks/storagebucketacl.go +++ b/upup/pkg/fi/cloudup/gcetasks/storagebucketacl.go @@ -50,13 +50,13 @@ func (e *StorageBucketAcl) Find(c *fi.Context) (*StorageBucketAcl, error) { bucket := fi.StringValue(e.Bucket) entity := fi.StringValue(e.Entity) - glog.V(2).Infof("Checking GCS Object ACL for gs://%s for %s", bucket, entity) + glog.V(2).Infof("Checking GCS bucket ACL for gs://%s for %s", bucket, entity) r, err := cloud.Storage().BucketAccessControls.Get(bucket, entity).Do() if err != nil { if gce.IsNotFound(err) { return nil, nil } - return nil, fmt.Errorf("error checking GCS Object ACL for gs://%s for %s: %v", bucket, entity, err) + return nil, fmt.Errorf("error checking GCS bucket ACL for gs://%s for %s: %v", bucket, entity, err) } actual := &StorageBucketAcl{} @@ -104,7 +104,7 @@ func (_ *StorageBucketAcl) RenderGCE(t *gce.GCEAPITarget, a, e, changes *Storage return fmt.Errorf("error creating GCS bucket ACL for gs://%s for %s as %s: %v", bucket, entity, role, err) } } else { - glog.V(2).Infof("Updating GCS Object ACL for gs://%s for %s as %s", bucket, entity, role) + glog.V(2).Infof("Updating GCS bucket ACL for gs://%s for %s as %s", bucket, entity, role) _, err := t.Cloud.Storage().BucketAccessControls.Update(bucket, entity, acl).Do() if err != nil { diff --git a/upup/pkg/fi/cloudup/gcetasks/storageobjectacl.go b/upup/pkg/fi/cloudup/gcetasks/storageobjectacl.go index e919a3d745..83f859529f 100644 --- a/upup/pkg/fi/cloudup/gcetasks/storageobjectacl.go +++ b/upup/pkg/fi/cloudup/gcetasks/storageobjectacl.go @@ -52,13 +52,13 @@ func (e *StorageObjectAcl) Find(c *fi.Context) (*StorageObjectAcl, error) { object := fi.StringValue(e.Object) entity := fi.StringValue(e.Entity) - glog.V(2).Infof("Checking GCS Object ACL for gs://%s/%s for %s", bucket, object, entity) + glog.V(2).Infof("Checking GCS object ACL for gs://%s/%s for %s", bucket, object, entity) r, err := cloud.Storage().ObjectAccessControls.Get(bucket, object, entity).Do() if err != nil { if gce.IsNotFound(err) { return nil, nil } - return nil, fmt.Errorf("error querying GCS Object ACL for gs://%s/%s for %s: %v", bucket, object, entity, err) + return nil, fmt.Errorf("error querying GCS object ACL for gs://%s/%s for %s: %v", bucket, object, entity, err) } actual := &StorageObjectAcl{} @@ -104,18 +104,18 @@ func (_ *StorageObjectAcl) RenderGCE(t *gce.GCEAPITarget, a, e, changes *Storage } if a == nil { - glog.V(2).Infof("Creating GCS Object ACL for gs://%s/%s for %s as %s", bucket, object, entity, role) + glog.V(2).Infof("Creating GCS object ACL for gs://%s/%s for %s as %s", bucket, object, entity, role) _, err := t.Cloud.Storage().ObjectAccessControls.Insert(bucket, object, acl).Do() if err != nil { - return fmt.Errorf("error creating GCS Object ACL for gs://%s/%s for %s as %s: %v", bucket, object, entity, role, err) + return fmt.Errorf("error creating GCS object ACL for gs://%s/%s for %s as %s: %v", bucket, object, entity, role, err) } } else { - glog.V(2).Infof("Updating GCS Object ACL for gs://%s/%s for %s as %s", bucket, object, entity, role) + glog.V(2).Infof("Updating GCS object ACL for gs://%s/%s for %s as %s", bucket, object, entity, role) _, err := t.Cloud.Storage().ObjectAccessControls.Update(bucket, object, entity, acl).Do() if err != nil { - return fmt.Errorf("error updating GCS Object ACL for gs://%s/%s for %s as %s: %v", bucket, object, entity, role, err) + return fmt.Errorf("error updating GCS object ACL for gs://%s/%s for %s as %s: %v", bucket, object, entity, role, err) } } diff --git a/upup/pkg/fi/cloudup/networking_test.go b/upup/pkg/fi/cloudup/networking_test.go index 2d1d8c5b88..40632496b1 100644 --- a/upup/pkg/fi/cloudup/networking_test.go +++ b/upup/pkg/fi/cloudup/networking_test.go @@ -17,9 +17,10 @@ limitations under the License. package cloudup import ( - api "k8s.io/kops/pkg/apis/kops" "os" "testing" + + api "k8s.io/kops/pkg/apis/kops" ) func Test_FindCNIAssetFromEnvironmentVariable(t *testing.T) { diff --git a/upup/pkg/fi/cloudup/populateinstancegroup_test.go b/upup/pkg/fi/cloudup/populateinstancegroup_test.go index 1efbed99ad..84f3bf74e6 100644 --- a/upup/pkg/fi/cloudup/populateinstancegroup_test.go +++ b/upup/pkg/fi/cloudup/populateinstancegroup_test.go @@ -18,9 +18,10 @@ package cloudup import ( "fmt" - api "k8s.io/kops/pkg/apis/kops" "strings" "testing" + + api "k8s.io/kops/pkg/apis/kops" ) func buildMinimalNodeInstanceGroup(subnets ...string) *api.InstanceGroup { diff --git a/upup/pkg/fi/cloudup/subnets.go b/upup/pkg/fi/cloudup/subnets.go index c2375df4ff..ff22fea8f6 100644 --- a/upup/pkg/fi/cloudup/subnets.go +++ b/upup/pkg/fi/cloudup/subnets.go @@ -19,11 +19,12 @@ package cloudup import ( "encoding/binary" "fmt" + "net" + "sort" + "github.com/golang/glog" "k8s.io/kops/pkg/apis/kops" "k8s.io/kops/upup/pkg/fi" - "net" - "sort" ) // ByZone implements sort.Interface for []*ClusterSubnetSpec based on diff --git a/upup/pkg/fi/cloudup/subnets_test.go b/upup/pkg/fi/cloudup/subnets_test.go index a41b11caa9..c41e1ce081 100644 --- a/upup/pkg/fi/cloudup/subnets_test.go +++ b/upup/pkg/fi/cloudup/subnets_test.go @@ -17,10 +17,11 @@ limitations under the License. package cloudup import ( - "k8s.io/kops/pkg/apis/kops" "net" "reflect" "testing" + + "k8s.io/kops/pkg/apis/kops" ) func Test_Split_Subnet(t *testing.T) { diff --git a/upup/pkg/fi/cloudup/tagbuilder_test.go b/upup/pkg/fi/cloudup/tagbuilder_test.go index 866cdcac71..fc6670f69d 100644 --- a/upup/pkg/fi/cloudup/tagbuilder_test.go +++ b/upup/pkg/fi/cloudup/tagbuilder_test.go @@ -17,9 +17,10 @@ limitations under the License. package cloudup import ( + "testing" + api "k8s.io/kops/pkg/apis/kops" "k8s.io/kops/upup/pkg/fi" - "testing" ) type ClusterParams struct { diff --git a/upup/pkg/fi/cloudup/terraform/hcl_printer.go b/upup/pkg/fi/cloudup/terraform/hcl_printer.go index cb023a26f7..13d4c23a31 100644 --- a/upup/pkg/fi/cloudup/terraform/hcl_printer.go +++ b/upup/pkg/fi/cloudup/terraform/hcl_printer.go @@ -19,11 +19,12 @@ package terraform import ( "bytes" "fmt" + "strings" + "github.com/golang/glog" "github.com/hashicorp/hcl/hcl/ast" hcl_printer "github.com/hashicorp/hcl/hcl/printer" "k8s.io/kops/pkg/featureflag" - "strings" ) const safeChars = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-_" diff --git a/upup/pkg/fi/cloudup/terraform/target.go b/upup/pkg/fi/cloudup/terraform/target.go index 120d923d25..46fae3839f 100644 --- a/upup/pkg/fi/cloudup/terraform/target.go +++ b/upup/pkg/fi/cloudup/terraform/target.go @@ -19,15 +19,16 @@ package terraform import ( "encoding/json" "fmt" - "github.com/golang/glog" - hcl_parser "github.com/hashicorp/hcl/json/parser" "io/ioutil" - "k8s.io/kops/pkg/apis/kops" - "k8s.io/kops/upup/pkg/fi" "os" "path" "strings" "sync" + + "github.com/golang/glog" + hcl_parser "github.com/hashicorp/hcl/json/parser" + "k8s.io/kops/pkg/apis/kops" + "k8s.io/kops/upup/pkg/fi" ) type TerraformTarget struct { diff --git a/upup/pkg/fi/cloudup/utils.go b/upup/pkg/fi/cloudup/utils.go index e0a655695e..4a4a4eaa0b 100644 --- a/upup/pkg/fi/cloudup/utils.go +++ b/upup/pkg/fi/cloudup/utils.go @@ -18,6 +18,8 @@ package cloudup import ( "fmt" + "strings" + "github.com/golang/glog" "k8s.io/kops/pkg/apis/kops" "k8s.io/kops/upup/pkg/fi" @@ -28,7 +30,6 @@ import ( "k8s.io/kops/upup/pkg/fi/cloudup/vsphere" "k8s.io/kubernetes/federation/pkg/dnsprovider" "k8s.io/kubernetes/federation/pkg/dnsprovider/providers/aws/route53" - "strings" ) func BuildCloud(cluster *kops.Cluster) (fi.Cloud, error) { diff --git a/upup/pkg/fi/cloudup/validation_test.go b/upup/pkg/fi/cloudup/validation_test.go index fbf4ac156a..0435c1fc55 100644 --- a/upup/pkg/fi/cloudup/validation_test.go +++ b/upup/pkg/fi/cloudup/validation_test.go @@ -18,13 +18,14 @@ package cloudup import ( "fmt" + "strings" + "testing" + "github.com/golang/glog" "k8s.io/apimachinery/pkg/util/sets" api "k8s.io/kops/pkg/apis/kops" "k8s.io/kops/pkg/apis/kops/validation" "k8s.io/kops/upup/pkg/fi" - "strings" - "testing" ) const MockAWSRegion = "us-mock-1" diff --git a/upup/pkg/fi/cloudup/vsphere/vsphere_utils.go b/upup/pkg/fi/cloudup/vsphere/vsphere_utils.go index 56e17aac24..f419f16f17 100644 --- a/upup/pkg/fi/cloudup/vsphere/vsphere_utils.go +++ b/upup/pkg/fi/cloudup/vsphere/vsphere_utils.go @@ -20,12 +20,13 @@ package vsphere import ( "context" + "path" + "sync" + "github.com/golang/glog" "github.com/vmware/govmomi/object" "github.com/vmware/govmomi/vim25/mo" "github.com/vmware/govmomi/vim25/types" - "path" - "sync" ) var snapshotLock sync.Mutex diff --git a/upup/pkg/fi/context.go b/upup/pkg/fi/context.go index 00b9a6368a..11ea288bf1 100644 --- a/upup/pkg/fi/context.go +++ b/upup/pkg/fi/context.go @@ -19,15 +19,16 @@ package fi import ( "bytes" "fmt" - "github.com/golang/glog" "io/ioutil" - "k8s.io/kops/pkg/apis/kops" - "k8s.io/kops/util/pkg/vfs" - "k8s.io/kubernetes/federation/pkg/dnsprovider" "os" "reflect" "strings" "time" + + "github.com/golang/glog" + "k8s.io/kops/pkg/apis/kops" + "k8s.io/kops/util/pkg/vfs" + "k8s.io/kubernetes/federation/pkg/dnsprovider" ) type Context struct { diff --git a/upup/pkg/fi/default_methods.go b/upup/pkg/fi/default_methods.go index 4e2f517b02..b516c87aa1 100644 --- a/upup/pkg/fi/default_methods.go +++ b/upup/pkg/fi/default_methods.go @@ -18,8 +18,9 @@ package fi import ( "fmt" - "k8s.io/kops/upup/pkg/fi/utils" "reflect" + + "k8s.io/kops/upup/pkg/fi/utils" ) // DefaultDeltaRunMethod implements the standard change-based run procedure: diff --git a/upup/pkg/fi/dryrun_target.go b/upup/pkg/fi/dryrun_target.go index 866b18bede..056e811a98 100644 --- a/upup/pkg/fi/dryrun_target.go +++ b/upup/pkg/fi/dryrun_target.go @@ -24,11 +24,12 @@ import ( "strings" "sync" + "sort" + "github.com/golang/glog" "k8s.io/kops/pkg/assets" "k8s.io/kops/pkg/diff" "k8s.io/kops/upup/pkg/fi/utils" - "sort" ) // DryRunTarget is a special Target that does not execute anything, but instead tracks all changes. diff --git a/upup/pkg/fi/errors.go b/upup/pkg/fi/errors.go index 8bb364f17d..ce5257cc9f 100644 --- a/upup/pkg/fi/errors.go +++ b/upup/pkg/fi/errors.go @@ -18,6 +18,7 @@ package fi import ( "fmt" + "k8s.io/apimachinery/pkg/api/validation" "k8s.io/apimachinery/pkg/util/validation/field" "k8s.io/kops/upup/pkg/fi/utils" diff --git a/upup/pkg/fi/files.go b/upup/pkg/fi/files.go index c0eadefe42..89da265588 100644 --- a/upup/pkg/fi/files.go +++ b/upup/pkg/fi/files.go @@ -18,13 +18,14 @@ package fi import ( "fmt" - "github.com/golang/glog" "io" - "k8s.io/kops/util/pkg/hashing" "os" "path" "strconv" "syscall" + + "github.com/golang/glog" + "k8s.io/kops/util/pkg/hashing" ) func WriteFile(destPath string, contents Resource, fileMode os.FileMode, dirMode os.FileMode) error { diff --git a/upup/pkg/fi/fitasks/managedfile.go b/upup/pkg/fi/fitasks/managedfile.go index 26f3d20e10..a875f9a303 100644 --- a/upup/pkg/fi/fitasks/managedfile.go +++ b/upup/pkg/fi/fitasks/managedfile.go @@ -18,10 +18,11 @@ package fitasks import ( "fmt" + "os" + "k8s.io/apimachinery/pkg/util/validation/field" "k8s.io/kops/pkg/acls" "k8s.io/kops/upup/pkg/fi" - "os" ) //go:generate fitask -type=ManagedFile diff --git a/upup/pkg/fi/fitasks/secret.go b/upup/pkg/fi/fitasks/secret.go index c93dc6ff26..88a69483d2 100644 --- a/upup/pkg/fi/fitasks/secret.go +++ b/upup/pkg/fi/fitasks/secret.go @@ -18,6 +18,7 @@ package fitasks import ( "fmt" + "k8s.io/kops/upup/pkg/fi" ) diff --git a/upup/pkg/fi/http.go b/upup/pkg/fi/http.go index 2b8110e054..b363732292 100644 --- a/upup/pkg/fi/http.go +++ b/upup/pkg/fi/http.go @@ -18,12 +18,13 @@ package fi import ( "fmt" - "github.com/golang/glog" "io" - "k8s.io/kops/util/pkg/hashing" "net/http" "os" "path" + + "github.com/golang/glog" + "k8s.io/kops/util/pkg/hashing" ) func DownloadURL(url string, dest string, hash *hashing.Hash) (*hashing.Hash, error) { diff --git a/upup/pkg/fi/k8sapi/cert_secret.go b/upup/pkg/fi/k8sapi/cert_secret.go index 06eea5cb17..66c2e52c8d 100644 --- a/upup/pkg/fi/k8sapi/cert_secret.go +++ b/upup/pkg/fi/k8sapi/cert_secret.go @@ -18,6 +18,7 @@ package k8sapi import ( "fmt" + "k8s.io/api/core/v1" "k8s.io/kops/pkg/pki" ) diff --git a/upup/pkg/fi/loader/tree_walker.go b/upup/pkg/fi/loader/tree_walker.go index ae545e2d93..0d0d7c1679 100644 --- a/upup/pkg/fi/loader/tree_walker.go +++ b/upup/pkg/fi/loader/tree_walker.go @@ -18,12 +18,13 @@ package loader import ( "fmt" - "github.com/golang/glog" - "k8s.io/apimachinery/pkg/util/sets" - "k8s.io/kops/util/pkg/vfs" "os" "path" "strings" + + "github.com/golang/glog" + "k8s.io/apimachinery/pkg/util/sets" + "k8s.io/kops/util/pkg/vfs" ) type TreeWalker struct { diff --git a/upup/pkg/fi/nodeup/cloudinit/cloud_init_target.go b/upup/pkg/fi/nodeup/cloudinit/cloud_init_target.go index 3e17e2c049..ad5b8bc9cc 100644 --- a/upup/pkg/fi/nodeup/cloudinit/cloud_init_target.go +++ b/upup/pkg/fi/nodeup/cloudinit/cloud_init_target.go @@ -19,13 +19,14 @@ package cloudinit import ( "encoding/base64" "fmt" - "github.com/golang/glog" "io" + "os" + "path" + + "github.com/golang/glog" "k8s.io/apimachinery/pkg/util/sets" "k8s.io/kops/upup/pkg/fi" "k8s.io/kops/upup/pkg/fi/utils" - "os" - "path" ) type CloudInitTarget struct { diff --git a/upup/pkg/fi/nodeup/local/local_target.go b/upup/pkg/fi/nodeup/local/local_target.go index d5d2f11088..e88c0c4bd7 100644 --- a/upup/pkg/fi/nodeup/local/local_target.go +++ b/upup/pkg/fi/nodeup/local/local_target.go @@ -17,9 +17,10 @@ limitations under the License. package local import ( + "os/exec" + "k8s.io/apimachinery/pkg/util/sets" "k8s.io/kops/upup/pkg/fi" - "os/exec" ) type LocalTarget struct { diff --git a/upup/pkg/fi/nodeup/nodetasks/loadimage_test.go b/upup/pkg/fi/nodeup/nodetasks/loadimage_test.go index 2d53e5d531..4439c26608 100644 --- a/upup/pkg/fi/nodeup/nodetasks/loadimage_test.go +++ b/upup/pkg/fi/nodeup/nodetasks/loadimage_test.go @@ -17,9 +17,10 @@ limitations under the License. package nodetasks import ( - "k8s.io/kops/upup/pkg/fi" "reflect" "testing" + + "k8s.io/kops/upup/pkg/fi" ) func TestLoadImageTask_Deps(t *testing.T) { diff --git a/upup/pkg/fi/nodeup/nodetasks/mount_disk.go b/upup/pkg/fi/nodeup/nodetasks/mount_disk.go index 10820ae4da..f6c9aa4dc2 100644 --- a/upup/pkg/fi/nodeup/nodetasks/mount_disk.go +++ b/upup/pkg/fi/nodeup/nodetasks/mount_disk.go @@ -18,15 +18,16 @@ package nodetasks import ( "fmt" + "os" + "path/filepath" + "time" + "github.com/golang/glog" "k8s.io/kops/upup/pkg/fi" "k8s.io/kops/upup/pkg/fi/nodeup/cloudinit" "k8s.io/kops/upup/pkg/fi/nodeup/local" "k8s.io/kops/upup/pkg/fi/utils" "k8s.io/kubernetes/pkg/util/mount" - "os" - "path/filepath" - "time" ) // MountDiskTask is responsible for mounting a device on a mountpoint diff --git a/upup/pkg/fi/nodeup/nodetasks/package.go b/upup/pkg/fi/nodeup/nodetasks/package.go index 060fd8aa1a..569696d01f 100644 --- a/upup/pkg/fi/nodeup/nodetasks/package.go +++ b/upup/pkg/fi/nodeup/nodetasks/package.go @@ -19,18 +19,19 @@ package nodetasks import ( "encoding/json" "fmt" - "github.com/golang/glog" - "k8s.io/kops/upup/pkg/fi" - "k8s.io/kops/upup/pkg/fi/nodeup/cloudinit" - "k8s.io/kops/upup/pkg/fi/nodeup/local" - "k8s.io/kops/upup/pkg/fi/nodeup/tags" - "k8s.io/kops/util/pkg/hashing" "os" "os/exec" "path" "reflect" "strings" "sync" + + "github.com/golang/glog" + "k8s.io/kops/upup/pkg/fi" + "k8s.io/kops/upup/pkg/fi/nodeup/cloudinit" + "k8s.io/kops/upup/pkg/fi/nodeup/local" + "k8s.io/kops/upup/pkg/fi/nodeup/tags" + "k8s.io/kops/util/pkg/hashing" ) type Package struct { diff --git a/upup/pkg/fi/nodeup/nodetasks/service_test.go b/upup/pkg/fi/nodeup/nodetasks/service_test.go index 99de69779f..095f68bdc7 100644 --- a/upup/pkg/fi/nodeup/nodetasks/service_test.go +++ b/upup/pkg/fi/nodeup/nodetasks/service_test.go @@ -17,9 +17,10 @@ limitations under the License. package nodetasks import ( - "k8s.io/kops/upup/pkg/fi" "reflect" "testing" + + "k8s.io/kops/upup/pkg/fi" ) func TestServiceTask_Deps(t *testing.T) { diff --git a/upup/pkg/fi/nodeup/nodetasks/update_packages.go b/upup/pkg/fi/nodeup/nodetasks/update_packages.go index 9d0984944f..40354edc19 100644 --- a/upup/pkg/fi/nodeup/nodetasks/update_packages.go +++ b/upup/pkg/fi/nodeup/nodetasks/update_packages.go @@ -18,14 +18,15 @@ package nodetasks import ( "fmt" + "os" + "os/exec" + "syscall" + "github.com/golang/glog" "k8s.io/kops/upup/pkg/fi" "k8s.io/kops/upup/pkg/fi/nodeup/cloudinit" "k8s.io/kops/upup/pkg/fi/nodeup/local" "k8s.io/kops/upup/pkg/fi/nodeup/tags" - "os" - "os/exec" - "syscall" ) type UpdatePackages struct { diff --git a/upup/pkg/fi/nodeup/nodetasks/user.go b/upup/pkg/fi/nodeup/nodetasks/user.go index 746f5e33c4..625f5add23 100644 --- a/upup/pkg/fi/nodeup/nodetasks/user.go +++ b/upup/pkg/fi/nodeup/nodetasks/user.go @@ -18,13 +18,14 @@ package nodetasks import ( "fmt" + "os/exec" + "strings" + "github.com/golang/glog" "k8s.io/kops/upup/pkg/fi" "k8s.io/kops/upup/pkg/fi/nodeup/cloudinit" "k8s.io/kops/upup/pkg/fi/nodeup/local" "k8s.io/kops/upup/pkg/fi/utils" - "os/exec" - "strings" ) // UserTask is responsible for creating a user, by calling useradd diff --git a/upup/pkg/fi/resources.go b/upup/pkg/fi/resources.go index 716158e79a..6cfb46c840 100644 --- a/upup/pkg/fi/resources.go +++ b/upup/pkg/fi/resources.go @@ -21,8 +21,9 @@ import ( "encoding/json" "fmt" "io" - "k8s.io/kops/util/pkg/vfs" "os" + + "k8s.io/kops/util/pkg/vfs" ) type Resource interface { diff --git a/upup/pkg/fi/secrets.go b/upup/pkg/fi/secrets.go index ea81dd2cf4..5d16144723 100644 --- a/upup/pkg/fi/secrets.go +++ b/upup/pkg/fi/secrets.go @@ -20,8 +20,9 @@ import ( crypto_rand "crypto/rand" "encoding/base64" "fmt" - "k8s.io/kops/util/pkg/vfs" "strings" + + "k8s.io/kops/util/pkg/vfs" ) type SecretStore interface { diff --git a/upup/pkg/fi/secrets/vfs_secretstore.go b/upup/pkg/fi/secrets/vfs_secretstore.go index 14e3ab6dcf..f24979b833 100644 --- a/upup/pkg/fi/secrets/vfs_secretstore.go +++ b/upup/pkg/fi/secrets/vfs_secretstore.go @@ -19,12 +19,13 @@ package secrets import ( "encoding/json" "fmt" + "os" + "github.com/golang/glog" "k8s.io/kops/pkg/acls" "k8s.io/kops/pkg/apis/kops" "k8s.io/kops/upup/pkg/fi" "k8s.io/kops/util/pkg/vfs" - "os" ) type VFSSecretStore struct { diff --git a/upup/pkg/fi/task.go b/upup/pkg/fi/task.go index 9a25514db4..2599f884ad 100644 --- a/upup/pkg/fi/task.go +++ b/upup/pkg/fi/task.go @@ -18,9 +18,10 @@ package fi import ( "fmt" - "github.com/golang/glog" "reflect" "strings" + + "github.com/golang/glog" ) type Task interface { diff --git a/upup/pkg/fi/topological_sort.go b/upup/pkg/fi/topological_sort.go index b02211f9e3..2dca686941 100644 --- a/upup/pkg/fi/topological_sort.go +++ b/upup/pkg/fi/topological_sort.go @@ -19,9 +19,10 @@ package fi import ( "crypto/x509/pkix" "fmt" + "reflect" + "github.com/golang/glog" "k8s.io/kops/upup/pkg/fi/utils" - "reflect" ) type HasDependencies interface { diff --git a/upup/pkg/fi/users.go b/upup/pkg/fi/users.go index c50ff008c3..f02c0e81d7 100644 --- a/upup/pkg/fi/users.go +++ b/upup/pkg/fi/users.go @@ -18,10 +18,11 @@ package fi import ( "fmt" - "github.com/golang/glog" "io/ioutil" "strconv" "strings" + + "github.com/golang/glog" ) // This file parses /etc/passwd and /etc/group to get information about users & groups diff --git a/upup/pkg/fi/utils/reflect.go b/upup/pkg/fi/utils/reflect.go index d0d79e87ec..a753ac00fd 100644 --- a/upup/pkg/fi/utils/reflect.go +++ b/upup/pkg/fi/utils/reflect.go @@ -20,8 +20,9 @@ import ( "encoding/json" "errors" "fmt" - "github.com/golang/glog" "reflect" + + "github.com/golang/glog" ) var SkipReflection = errors.New("skip this value") diff --git a/upup/pkg/kutil/config.go b/upup/pkg/kutil/config.go index 1060a75e7d..7963c3a533 100644 --- a/upup/pkg/kutil/config.go +++ b/upup/pkg/kutil/config.go @@ -18,6 +18,7 @@ package kutil import ( "fmt" + "github.com/golang/glog" restclient "k8s.io/client-go/rest" "k8s.io/client-go/tools/clientcmd" diff --git a/upup/pkg/kutil/kubectl.go b/upup/pkg/kutil/kubectl.go index 73f99d5a37..50314769e3 100644 --- a/upup/pkg/kutil/kubectl.go +++ b/upup/pkg/kutil/kubectl.go @@ -20,13 +20,14 @@ import ( "bytes" "encoding/json" "fmt" - "github.com/golang/glog" "io/ioutil" - "k8s.io/client-go/tools/clientcmd" - "k8s.io/kops/pkg/kubeconfig" "os" "os/exec" "strings" + + "github.com/golang/glog" + "k8s.io/client-go/tools/clientcmd" + "k8s.io/kops/pkg/kubeconfig" ) type Kubectl struct { diff --git a/upup/pkg/kutil/ssh.go b/upup/pkg/kutil/ssh.go index 478f190a95..4faad080dc 100644 --- a/upup/pkg/kutil/ssh.go +++ b/upup/pkg/kutil/ssh.go @@ -18,8 +18,9 @@ package kutil import ( "fmt" - "golang.org/x/crypto/ssh" "io/ioutil" + + "golang.org/x/crypto/ssh" "k8s.io/kops/util/pkg/vfs" ) diff --git a/upup/tools/generators/fitask/generator.go b/upup/tools/generators/fitask/generator.go index 5055f7d81f..cf6e57d51a 100644 --- a/upup/tools/generators/fitask/generator.go +++ b/upup/tools/generators/fitask/generator.go @@ -18,8 +18,9 @@ package main import ( "io" - "k8s.io/kops/upup/tools/generators/pkg/codegen" "text/template" + + "k8s.io/kops/upup/tools/generators/pkg/codegen" ) type FitaskGenerator struct { diff --git a/util/pkg/tables/format.go b/util/pkg/tables/format.go index 99c839acc0..e800c87f19 100644 --- a/util/pkg/tables/format.go +++ b/util/pkg/tables/format.go @@ -19,12 +19,13 @@ package tables import ( "bytes" "fmt" - "github.com/golang/glog" "io" - "k8s.io/kops/upup/pkg/fi" "reflect" "sort" "text/tabwriter" + + "github.com/golang/glog" + "k8s.io/kops/upup/pkg/fi" ) // Table renders tables to stdout diff --git a/util/pkg/ui/user.go b/util/pkg/ui/user.go index 0717142860..623f6a937c 100644 --- a/util/pkg/ui/user.go +++ b/util/pkg/ui/user.go @@ -20,9 +20,10 @@ import ( "bufio" "fmt" "io" - "k8s.io/apimachinery/pkg/util/sets" "os" "strings" + + "k8s.io/apimachinery/pkg/util/sets" ) // ConfirmArgs encapsulates the arguments that can he passed to GetConfirm diff --git a/util/pkg/vfs/BUILD.bazel b/util/pkg/vfs/BUILD.bazel index 8b6db0861c..0e7d1eec05 100644 --- a/util/pkg/vfs/BUILD.bazel +++ b/util/pkg/vfs/BUILD.bazel @@ -12,6 +12,7 @@ go_library( "s3context.go", "s3fs.go", "sshfs.go", + "swiftfs.go", "vfs.go", "vfssync.go", "writeoption.go", @@ -26,7 +27,13 @@ go_library( "//vendor/github.com/aws/aws-sdk-go/aws/session:go_default_library", "//vendor/github.com/aws/aws-sdk-go/service/ec2:go_default_library", "//vendor/github.com/aws/aws-sdk-go/service/s3:go_default_library", + "//vendor/github.com/go-ini/ini:go_default_library", "//vendor/github.com/golang/glog:go_default_library", + "//vendor/github.com/gophercloud/gophercloud:go_default_library", + "//vendor/github.com/gophercloud/gophercloud/openstack:go_default_library", + "//vendor/github.com/gophercloud/gophercloud/openstack/objectstorage/v1/containers:go_default_library", + "//vendor/github.com/gophercloud/gophercloud/openstack/objectstorage/v1/objects:go_default_library", + "//vendor/github.com/gophercloud/gophercloud/pagination:go_default_library", "//vendor/github.com/pkg/sftp:go_default_library", "//vendor/golang.org/x/crypto/ssh:go_default_library", "//vendor/golang.org/x/net/context:go_default_library", diff --git a/util/pkg/vfs/context.go b/util/pkg/vfs/context.go index f0b6025524..62b87a858a 100644 --- a/util/pkg/vfs/context.go +++ b/util/pkg/vfs/context.go @@ -18,18 +18,20 @@ package vfs import ( "fmt" - "github.com/golang/glog" - "golang.org/x/net/context" - "golang.org/x/oauth2/google" - storage "google.golang.org/api/storage/v1" "io/ioutil" - "k8s.io/apimachinery/pkg/util/wait" "net/http" "net/url" "os" "strings" "sync" "time" + + "github.com/golang/glog" + "github.com/gophercloud/gophercloud" + "golang.org/x/net/context" + "golang.org/x/oauth2/google" + storage "google.golang.org/api/storage/v1" + "k8s.io/apimachinery/pkg/util/wait" ) // VFSContext is a 'context' for VFS, that is normally a singleton @@ -42,6 +44,8 @@ type VFSContext struct { mutex sync.Mutex // The google cloud storage client, if initialized gcsClient *storage.Service + // swiftClient is the openstack swift client + swiftClient *gophercloud.ServiceClient } var Context = VFSContext{ @@ -112,6 +116,10 @@ func (c *VFSContext) BuildVfsPath(p string) (Path, error) { return c.buildKubernetesPath(p) } + if strings.HasPrefix(p, "swift://") { + return c.buildOpenstackSwiftPath(p) + } + return nil, fmt.Errorf("unknown / unhandled path type: %q", p) } @@ -309,3 +317,29 @@ func (c *VFSContext) getGCSClient() (*storage.Service, error) { c.gcsClient = gcsClient return gcsClient, nil } + +func (c *VFSContext) buildOpenstackSwiftPath(p string) (*SwiftPath, error) { + u, err := url.Parse(p) + if err != nil { + return nil, fmt.Errorf("invalid openstack cloud storage path: %q", p) + } + + if u.Scheme != "swift" { + return nil, fmt.Errorf("invalid openstack cloud storage path: %q", p) + } + + bucket := strings.TrimSuffix(u.Host, "/") + if bucket == "" { + return nil, fmt.Errorf("invalid swift path: %q", p) + } + + if c.swiftClient == nil { + swiftClient, err := NewSwiftClient() + if err != nil { + return nil, err + } + c.swiftClient = swiftClient + } + + return NewSwiftPath(c.swiftClient, bucket, u.Path) +} diff --git a/util/pkg/vfs/fs.go b/util/pkg/vfs/fs.go index ce71650734..dcb162b324 100644 --- a/util/pkg/vfs/fs.go +++ b/util/pkg/vfs/fs.go @@ -18,13 +18,14 @@ package vfs import ( "fmt" - "github.com/golang/glog" "io" "io/ioutil" - "k8s.io/kops/util/pkg/hashing" "os" "path" "sync" + + "github.com/golang/glog" + "k8s.io/kops/util/pkg/hashing" ) type FSPath struct { diff --git a/util/pkg/vfs/gsfs.go b/util/pkg/vfs/gsfs.go index 8ce42d71ff..c34178ed2b 100644 --- a/util/pkg/vfs/gsfs.go +++ b/util/pkg/vfs/gsfs.go @@ -21,19 +21,20 @@ import ( "encoding/base64" "encoding/hex" "fmt" - "github.com/golang/glog" - "golang.org/x/net/context" - "google.golang.org/api/googleapi" - storage "google.golang.org/api/storage/v1" "io/ioutil" - "k8s.io/apimachinery/pkg/util/wait" - "k8s.io/kops/util/pkg/hashing" "net/http" "os" "path" "strings" "sync" "time" + + "github.com/golang/glog" + "golang.org/x/net/context" + "google.golang.org/api/googleapi" + storage "google.golang.org/api/storage/v1" + "k8s.io/apimachinery/pkg/util/wait" + "k8s.io/kops/util/pkg/hashing" ) // GSPath is a vfs path for Google Cloud Storage diff --git a/util/pkg/vfs/k8sfs.go b/util/pkg/vfs/k8sfs.go index 30637fea79..8154553caf 100644 --- a/util/pkg/vfs/k8sfs.go +++ b/util/pkg/vfs/k8sfs.go @@ -18,9 +18,10 @@ package vfs import ( "fmt" - "k8s.io/kops/util/pkg/hashing" "path" "strings" + + "k8s.io/kops/util/pkg/hashing" ) // KubernetesPath is a path for a VFS backed by the kubernetes API diff --git a/util/pkg/vfs/s3fs.go b/util/pkg/vfs/s3fs.go index 9696674340..5a76f110e1 100644 --- a/util/pkg/vfs/s3fs.go +++ b/util/pkg/vfs/s3fs.go @@ -20,16 +20,17 @@ import ( "bytes" "encoding/hex" "fmt" - "github.com/aws/aws-sdk-go/aws" - "github.com/aws/aws-sdk-go/aws/awserr" - "github.com/aws/aws-sdk-go/service/s3" - "github.com/golang/glog" "io/ioutil" - "k8s.io/kops/util/pkg/hashing" "os" "path" "strings" "sync" + + "github.com/aws/aws-sdk-go/aws" + "github.com/aws/aws-sdk-go/aws/awserr" + "github.com/aws/aws-sdk-go/service/s3" + "github.com/golang/glog" + "k8s.io/kops/util/pkg/hashing" ) type S3Path struct { diff --git a/util/pkg/vfs/sshfs.go b/util/pkg/vfs/sshfs.go index a0b0bf21e5..9b13b5a6b9 100644 --- a/util/pkg/vfs/sshfs.go +++ b/util/pkg/vfs/sshfs.go @@ -19,14 +19,15 @@ package vfs import ( "bytes" "fmt" - "github.com/golang/glog" - "github.com/pkg/sftp" - "golang.org/x/crypto/ssh" "io" "math/rand" "os" "path" "sync" + + "github.com/golang/glog" + "github.com/pkg/sftp" + "golang.org/x/crypto/ssh" ) type SSHPath struct { diff --git a/util/pkg/vfs/swiftfs.go b/util/pkg/vfs/swiftfs.go new file mode 100644 index 0000000000..1c9d5d5b09 --- /dev/null +++ b/util/pkg/vfs/swiftfs.go @@ -0,0 +1,454 @@ +/* +Copyright 2017 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. +*/ + +package vfs + +import ( + "bytes" + "encoding/hex" + "fmt" + "os" + "path" + "path/filepath" + "runtime" + "strings" + "sync" + "time" + + "github.com/go-ini/ini" + "github.com/golang/glog" + "github.com/gophercloud/gophercloud" + "github.com/gophercloud/gophercloud/openstack" + swiftcontainer "github.com/gophercloud/gophercloud/openstack/objectstorage/v1/containers" + swiftobject "github.com/gophercloud/gophercloud/openstack/objectstorage/v1/objects" + "github.com/gophercloud/gophercloud/pagination" + "k8s.io/apimachinery/pkg/util/wait" + "k8s.io/kops/util/pkg/hashing" +) + +func NewSwiftClient() (*gophercloud.ServiceClient, error) { + config := OpenstackConfig{} + + authOption, err := config.GetCredential() + if err != nil { + return nil, err + } + provider, err := openstack.AuthenticatedClient(authOption) + if err != nil { + return nil, fmt.Errorf("error building openstack authenticated client: %v", err) + } + endpointOpt, err := config.GetServiceConfig("Swift") + if err != nil { + return nil, err + } + + client, err := openstack.NewObjectStorageV1(provider, endpointOpt) + if err != nil { + return nil, fmt.Errorf("error building swift client: %v", err) + } + return client, nil +} + +type OpenstackConfig struct { +} + +func (_ OpenstackConfig) filename() (string, error) { + name := os.Getenv("OPENSTACK_CREDENTIAL_FILE") + if name != "" { + glog.V(2).Infof("using openstack config found in $OPENSTACK_CREDENTIAL_FILE: %s", name) + return name, nil + } + + var homeDir string + if runtime.GOOS == "windows" { + homeDir = os.Getenv("USERPROFILE") + } else { + homeDir = os.Getenv("HOME") + } + if homeDir == "" { + return "", fmt.Errorf("can not find home directory") + } + f := filepath.Join(homeDir, ".openstack", "config") + glog.V(2).Infof("using openstack config found in %s", f) + return f, nil +} + +func (oc OpenstackConfig) getSection(name string, items []string) (map[string]string, error) { + filename, err := oc.filename() + if err != nil { + return nil, err + } + config, err := ini.Load(filename) + if err != nil { + return nil, fmt.Errorf("error loading config file: %v", err) + } + section, err := config.GetSection(name) + if err != nil { + return nil, fmt.Errorf("error getting section of %s: %v", name, err) + } + values := make(map[string]string) + for _, item := range items { + values[item] = section.Key(item).String() + } + return values, nil +} + +func (oc OpenstackConfig) GetCredential() (gophercloud.AuthOptions, error) { + opt := gophercloud.AuthOptions{} + name := "Default" + items := []string{"identity", "user", "user_id", "password", "domain_id", "domain_name", "tenant_id", "tenant_name"} + values, err := oc.getSection(name, items) + if err != nil { + return opt, err + } + + for _, c1 := range []string{"identity", "password"} { + if values[c1] == "" { + return opt, fmt.Errorf("missing %s in section of %s", c1, name) + } + } + + checkItems := [][]string{{"user", "user_id"}, {"domain_name", "domain_id"}, {"tenant_name", "tenant_id"}} + for _, c2 := range checkItems { + if values[c2[0]] == "" && values[c2[1]] == "" { + return opt, fmt.Errorf("missing %s and %s in section of %s", c2[0], c2[1], name) + } + } + + opt.IdentityEndpoint = values["identity"] + opt.UserID = values["user_id"] + opt.Username = values["user"] + opt.Password = values["password"] + opt.TenantID = values["tenant_id"] + opt.TenantName = values["tenant_name"] + opt.DomainID = values["domain_id"] + opt.DomainName = values["domain_name"] + opt.AllowReauth = true + + return opt, nil +} + +func (oc OpenstackConfig) GetServiceConfig(name string) (gophercloud.EndpointOpts, error) { + opt := gophercloud.EndpointOpts{} + items := []string{"service_type", "service_name", "region", "availability"} + values, err := oc.getSection(name, items) + if err != nil { + return opt, err + } + + if values["region"] == "" { + return opt, fmt.Errorf("missing region in section of %s", name) + } + + opt.Type = values["service_type"] + opt.Name = values["service_name"] + opt.Region = values["region"] + opt.Availability = gophercloud.Availability(values["availability"]) + + return opt, nil +} + +// SwiftPath is a vfs path for Openstack Cloud Storage. +type SwiftPath struct { + client *gophercloud.ServiceClient + bucket string + key string + hash string +} + +var _ Path = &SwiftPath{} +var _ HasHash = &SwiftPath{} + +// swiftReadBackoff is the backoff strategy for Swift read retries. +var swiftReadBackoff = wait.Backoff{ + Duration: time.Second, + Factor: 1.5, + Jitter: 0.1, + Steps: 4, +} + +// swiftWriteBackoff is the backoff strategy for Swift write retries. +var swiftWriteBackoff = wait.Backoff{ + Duration: time.Second, + Factor: 1.5, + Jitter: 0.1, + Steps: 5, +} + +func NewSwiftPath(client *gophercloud.ServiceClient, bucket string, key string) (*SwiftPath, error) { + bucket = strings.TrimSuffix(bucket, "/") + key = strings.TrimPrefix(key, "/") + + return &SwiftPath{ + client: client, + bucket: bucket, + key: key, + }, nil +} + +func (p *SwiftPath) Path() string { + return "swift://" + p.bucket + "/" + p.key +} + +func (p *SwiftPath) Bucket() string { + return p.bucket +} + +func (p *SwiftPath) String() string { + return p.Path() +} + +func (p *SwiftPath) Remove() error { + done, err := RetryWithBackoff(swiftWriteBackoff, func() (bool, error) { + opt := swiftobject.DeleteOpts{} + _, err := swiftobject.Delete(p.client, p.bucket, p.key, opt).Extract() + if err != nil { + if isSwiftNotFound(err) { + return true, os.ErrNotExist + } + return false, fmt.Errorf("error deleting %s: %v", p, err) + } + + return true, nil + }) + if err != nil { + return err + } else if done { + return nil + } else { + return wait.ErrWaitTimeout + } +} + +func (p *SwiftPath) Join(relativePath ...string) Path { + args := []string{p.key} + args = append(args, relativePath...) + joined := path.Join(args...) + return &SwiftPath{ + client: p.client, + bucket: p.bucket, + key: joined, + } +} + +func (p *SwiftPath) WriteFile(data []byte, acl ACL) error { + done, err := RetryWithBackoff(swiftWriteBackoff, func() (bool, error) { + glog.V(4).Infof("Writing file %q", p) + createOpts := swiftobject.CreateOpts{Content: bytes.NewReader(data)} + _, err := swiftobject.Create(p.client, p.bucket, p.key, createOpts).Extract() + if err != nil { + return false, fmt.Errorf("error writing %s: %v", p, err) + } + + return true, nil + }) + if err != nil { + return err + } else if done { + return nil + } else { + // Shouldn't happen - we always return a non-nil error with false. + return wait.ErrWaitTimeout + } +} + +// To prevent concurrent creates on the same file while maintaining atomicity of writes, +// we take a process-wide lock during the operation. +// Not a great approach, but fine for a single process (with low concurrency). +// TODO: should we enable versioning? +var createFileLockSwift sync.Mutex + +func (p *SwiftPath) CreateFile(data []byte, acl ACL) error { + createFileLockSwift.Lock() + defer createFileLockSwift.Unlock() + + // Check if exists. + _, err := RetryWithBackoff(swiftReadBackoff, func() (bool, error) { + glog.V(4).Infof("Getting file %q", p) + + _, err := swiftobject.Get(p.client, p.bucket, p.key, swiftobject.GetOpts{}).Extract() + if err == nil { + return true, nil + } else if isSwiftNotFound(err) { + return true, os.ErrNotExist + } else { + return false, fmt.Errorf("error getting %s: %v", p, err) + } + }) + if err == nil { + return os.ErrExist + } else if !os.IsNotExist(err) { + return err + } + + err = p.createBucket() + if err != nil { + return err + } + + return p.WriteFile(data, acl) +} + +func (p *SwiftPath) createBucket() error { + done, err := RetryWithBackoff(swiftWriteBackoff, func() (bool, error) { + _, err := swiftcontainer.Get(p.client, p.bucket).Extract() + if err == nil { + return true, nil + } + if isSwiftNotFound(err) { + createOpts := swiftcontainer.CreateOpts{} + _, err = swiftcontainer.Create(p.client, p.bucket, createOpts).Extract() + return err == nil, err + } + return false, err + }) + if err != nil { + return err + } else if done { + return nil + } else { + // Shouldn't happen - we always return a non-nil error with false. + return wait.ErrWaitTimeout + } +} + +// ReadFile implements Path::ReadFile. +func (p *SwiftPath) ReadFile() ([]byte, error) { + var ret []byte + done, err := RetryWithBackoff(swiftReadBackoff, func() (bool, error) { + glog.V(4).Infof("Reading file %q", p) + + opt := swiftobject.DownloadOpts{} + result := swiftobject.Download(p.client, p.bucket, p.key, opt) + data, err := (&result).ExtractContent() + if err == nil { + ret = data + return true, nil + } else if isSwiftNotFound(err) { + return true, os.ErrNotExist + } else { + return false, fmt.Errorf("error reading %s: %v", p, err) + } + }) + if err != nil { + return nil, err + } else if done { + return ret, nil + } else { + return nil, wait.ErrWaitTimeout + } +} + +func (p *SwiftPath) readPath(opt swiftobject.ListOpts) ([]Path, error) { + var ret []Path + done, err := RetryWithBackoff(swiftReadBackoff, func() (bool, error) { + var paths []Path + pager := swiftobject.List(p.client, p.bucket, opt) + err := pager.EachPage(func(page pagination.Page) (bool, error) { + objects, err1 := swiftobject.ExtractInfo(page) + if err1 != nil { + return false, err1 + } + for _, o := range objects { + child := &SwiftPath{ + client: p.client, + bucket: p.bucket, + key: o.Name, + hash: o.Hash, + } + paths = append(paths, child) + } + + return true, nil + }) + if err != nil { + if isSwiftNotFound(err) { + return true, os.ErrNotExist + } + return false, fmt.Errorf("error listing %s: %v", p, err) + } + glog.V(8).Infof("Listed files in %v: %v", p, paths) + ret = paths + return true, nil + }) + if err != nil { + return nil, err + } else if done { + return ret, nil + } else { + return nil, wait.ErrWaitTimeout + } +} + +// ReadDir implements Path::ReadDir. +func (p *SwiftPath) ReadDir() ([]Path, error) { + prefix := p.key + if !strings.HasSuffix(prefix, "/") { + prefix += "/" + } + opt := swiftobject.ListOpts{ + Full: true, + Path: prefix, + } + return p.readPath(opt) +} + +// ReadTree implements Path::ReadTree. +func (p *SwiftPath) ReadTree() ([]Path, error) { + prefix := p.key + if !strings.HasSuffix(prefix, "/") { + prefix += "/" + } + opt := swiftobject.ListOpts{ + Full: true, + Prefix: prefix, + } + return p.readPath(opt) +} + +func (p *SwiftPath) Base() string { + return path.Base(p.key) +} + +func (p *SwiftPath) PreferredHash() (*hashing.Hash, error) { + return p.Hash(hashing.HashAlgorithmMD5) +} + +func (p *SwiftPath) Hash(a hashing.HashAlgorithm) (*hashing.Hash, error) { + if a != hashing.HashAlgorithmMD5 { + return nil, nil + } + + md5 := p.hash + if md5 == "" { + return nil, nil + } + + md5Bytes, err := hex.DecodeString(md5) + if err != nil { + return nil, fmt.Errorf("Etag was not a valid MD5 sum: %q", md5) + } + + return &hashing.Hash{Algorithm: hashing.HashAlgorithmMD5, HashValue: md5Bytes}, nil +} + +func isSwiftNotFound(err error) bool { + if err == nil { + return false + } + _, ok := err.(gophercloud.ErrDefault404) + return ok +} diff --git a/util/pkg/vfs/vfs.go b/util/pkg/vfs/vfs.go index c402ac1af9..19099d5953 100644 --- a/util/pkg/vfs/vfs.go +++ b/util/pkg/vfs/vfs.go @@ -18,9 +18,10 @@ package vfs import ( "fmt" + "strings" + "github.com/golang/glog" "k8s.io/kops/util/pkg/hashing" - "strings" ) // Yet another VFS package @@ -60,7 +61,7 @@ type Path interface { // ReadDir lists the files in a particular Path ReadDir() ([]Path, error) - // ReadTree lists all files in the subtree rooted at the current Path + // ReadTree lists all files (recursively) in the subtree rooted at the current Path ReadTree() ([]Path, error) } @@ -93,7 +94,7 @@ func IsClusterReadable(p Path) bool { } switch p.(type) { - case *S3Path, *GSPath: + case *S3Path, *GSPath, *SwiftPath: return true case *KubernetesPath: diff --git a/util/pkg/vfs/vfssync.go b/util/pkg/vfs/vfssync.go index 51b28f9b56..107a3c92cb 100644 --- a/util/pkg/vfs/vfssync.go +++ b/util/pkg/vfs/vfssync.go @@ -19,9 +19,10 @@ package vfs import ( "bytes" "fmt" + "os" + "github.com/golang/glog" "k8s.io/kops/util/pkg/hashing" - "os" ) // VFSScan scans a source Path for changes files diff --git a/vendor/github.com/gophercloud/gophercloud/.gitignore b/vendor/github.com/gophercloud/gophercloud/.gitignore index ead84456eb..df9048a010 100644 --- a/vendor/github.com/gophercloud/gophercloud/.gitignore +++ b/vendor/github.com/gophercloud/gophercloud/.gitignore @@ -1 +1,2 @@ **/*.swp +.idea diff --git a/vendor/github.com/gophercloud/gophercloud/.travis.yml b/vendor/github.com/gophercloud/gophercloud/.travis.yml index 5d1486901d..59c4194952 100644 --- a/vendor/github.com/gophercloud/gophercloud/.travis.yml +++ b/vendor/github.com/gophercloud/gophercloud/.travis.yml @@ -12,6 +12,8 @@ go: env: global: - secure: "xSQsAG5wlL9emjbCdxzz/hYQsSpJ/bABO1kkbwMSISVcJ3Nk0u4ywF+LS4bgeOnwPfmFvNTOqVDu3RwEvMeWXSI76t1piCPcObutb2faKLVD/hLoAS76gYX+Z8yGWGHrSB7Do5vTPj1ERe2UljdrnsSeOXzoDwFxYRaZLX4bBOB4AyoGvRniil5QXPATiA1tsWX1VMicj8a4F8X+xeESzjt1Q5Iy31e7vkptu71bhvXCaoo5QhYwT+pLR9dN0S1b7Ro0KVvkRefmr1lUOSYd2e74h6Lc34tC1h3uYZCS4h47t7v5cOXvMNxinEj2C51RvbjvZI1RLVdkuAEJD1Iz4+Ote46nXbZ//6XRZMZz/YxQ13l7ux1PFjgEB6HAapmF5Xd8PRsgeTU9LRJxpiTJ3P5QJ3leS1va8qnziM5kYipj/Rn+V8g2ad/rgkRox9LSiR9VYZD2Pe45YCb1mTKSl2aIJnV7nkOqsShY5LNB4JZSg7xIffA+9YVDktw8dJlATjZqt7WvJJ49g6A61mIUV4C15q2JPGKTkZzDiG81NtmS7hFa7k0yaE2ELgYocbcuyUcAahhxntYTC0i23nJmEHVNiZmBO3u7EgpWe4KGVfumU+lt12tIn5b3dZRBBUk3QakKKozSK1QPHGpk/AZGrhu7H6l8to6IICKWtDcyMPQ=" +before_script: +- go vet ./... script: - ./script/coverage - ./script/format diff --git a/vendor/github.com/gophercloud/gophercloud/acceptance/README.md b/vendor/github.com/gophercloud/gophercloud/acceptance/README.md index 2254aa1ecc..ab35695748 100644 --- a/vendor/github.com/gophercloud/gophercloud/acceptance/README.md +++ b/vendor/github.com/gophercloud/gophercloud/acceptance/README.md @@ -10,7 +10,26 @@ to a remote API. > be certain cases where this does not happen; always double-check to make sure > you have no stragglers left behind. -### Step 1. Set environment variables +### Step 1. Creating a Testing Environment + +Running tests on an existing OpenStack cloud can be risky. Malformed tests, +especially ones which require Admin privileges, can cause damage to the +environment. Additionally, you may incur bandwidth and service charges for +the resources used, as mentioned in the note above. + +Therefore, it is usually best to first practice running acceptance tests in +an isolated test environment. Two options to easily create a testing +environment are [DevStack](https://docs.openstack.org/devstack/latest/) +and [PackStack](https://www.rdoproject.org/install/packstack/). + +The following blog posts detail how to create reusable PackStack environments. +These posts were written with Gophercloud in mind: + +* http://terrarum.net/blog/building-openstack-environments.html +* http://terrarum.net/blog/building-openstack-environments-2.html +* http://terrarum.net/blog/building-openstack-environments-3.html + +### Step 2. Set environment variables A lot of tests rely on environment variables for configuration - so you will need to set them before running the suite. If you're testing against pure OpenStack APIs, @@ -43,14 +62,22 @@ to set them manually. |`OS_FLAVOR_ID`|The ID of the flavor you want your server to be based on| |`OS_FLAVOR_ID_RESIZE`|The ID of the flavor you want your server to be resized to| |`OS_POOL_NAME`|The Pool from where to obtain Floating IPs| -|`OS_NETWORK_NAME`|The network to launch instances on| +|`OS_NETWORK_NAME`|The internal/private network to launch instances on| +|`OS_EXTGW_ID`|The external/public network| + +#### Database + +|Name|Description| +|---|---| +|`OS_DB_DATASTORE_TYPE`|The Datastore type to use. Example: `mariadb`| +|`OS_DB_DATASTORE_VERSION`|The Datastore version to use. Example: `mariadb-10`| #### Shared file systems |Name|Description| |---|---| |`OS_SHARE_NETWORK_ID`| The share network ID to use when creating shares| -### 2. Run the test suite +### 3. Run the test suite From the root directory, run: @@ -78,7 +105,7 @@ $ gophercloudtest TestFlavors compute/v2 $ gophercloudtest Test compute/v2 ``` -### 3. Notes +### 4. Notes #### Compute Tests diff --git a/vendor/github.com/gophercloud/gophercloud/acceptance/clients/BUILD.bazel b/vendor/github.com/gophercloud/gophercloud/acceptance/clients/BUILD.bazel index 882ad66c44..573bc66223 100644 --- a/vendor/github.com/gophercloud/gophercloud/acceptance/clients/BUILD.bazel +++ b/vendor/github.com/gophercloud/gophercloud/acceptance/clients/BUILD.bazel @@ -7,5 +7,6 @@ go_library( deps = [ "//vendor/github.com/gophercloud/gophercloud:go_default_library", "//vendor/github.com/gophercloud/gophercloud/openstack:go_default_library", + "//vendor/github.com/gophercloud/gophercloud/openstack/blockstorage/noauth:go_default_library", ], ) diff --git a/vendor/github.com/gophercloud/gophercloud/acceptance/clients/clients.go b/vendor/github.com/gophercloud/gophercloud/acceptance/clients/clients.go index 7bca0a4408..03209e0f21 100644 --- a/vendor/github.com/gophercloud/gophercloud/acceptance/clients/clients.go +++ b/vendor/github.com/gophercloud/gophercloud/acceptance/clients/clients.go @@ -10,6 +10,7 @@ import ( "github.com/gophercloud/gophercloud" "github.com/gophercloud/gophercloud/openstack" + "github.com/gophercloud/gophercloud/openstack/blockstorage/noauth" ) // AcceptanceTestChoices contains image and flavor selections for use by the acceptance tests. @@ -35,6 +36,12 @@ type AcceptanceTestChoices struct { // ShareNetworkID is the Manila Share network ID ShareNetworkID string + + // DBDatastoreType is the datastore type for DB tests. + DBDatastoreType string + + // DBDatastoreTypeID is the datastore type version for DB tests. + DBDatastoreVersion string } // AcceptanceTestChoicesFromEnv populates a ComputeChoices struct from environment variables. @@ -47,6 +54,8 @@ func AcceptanceTestChoicesFromEnv() (*AcceptanceTestChoices, error) { floatingIPPoolName := os.Getenv("OS_POOL_NAME") externalNetworkID := os.Getenv("OS_EXTGW_ID") shareNetworkID := os.Getenv("OS_SHARE_NETWORK_ID") + dbDatastoreType := os.Getenv("OS_DB_DATASTORE_TYPE") + dbDatastoreVersion := os.Getenv("OS_DB_DATASTORE_VERSION") missing := make([]string, 0, 3) if imageID == "" { @@ -95,6 +104,8 @@ func AcceptanceTestChoicesFromEnv() (*AcceptanceTestChoices, error) { NetworkName: networkName, ExternalNetworkID: externalNetworkID, ShareNetworkID: shareNetworkID, + DBDatastoreType: dbDatastoreType, + DBDatastoreVersion: dbDatastoreVersion, }, nil } @@ -136,22 +147,20 @@ func NewBlockStorageV2Client() (*gophercloud.ServiceClient, error) { }) } -// NewSharedFileSystemV2Client returns a *ServiceClient for making calls -// to the OpenStack Shared File System v2 API. An error will be returned -// if authentication or client creation was not possible. -func NewSharedFileSystemV2Client() (*gophercloud.ServiceClient, error) { - ao, err := openstack.AuthOptionsFromEnv() +// NewBlockStorageV2NoAuthClient returns a noauth *ServiceClient for +// making calls to the OpenStack Block Storage v2 API. An error will be +// returned if client creation was not possible. +func NewBlockStorageV2NoAuthClient() (*gophercloud.ServiceClient, error) { + client, err := noauth.NewClient(gophercloud.AuthOptions{ + Username: os.Getenv("OS_USERNAME"), + TenantName: os.Getenv("OS_TENANT_NAME"), + }) if err != nil { return nil, err } - client, err := openstack.AuthenticatedClient(ao) - if err != nil { - return nil, err - } - - return openstack.NewSharedFileSystemV2(client, gophercloud.EndpointOpts{ - Region: os.Getenv("OS_REGION_NAME"), + return noauth.NewBlockStorageV2(client, noauth.EndpointOpts{ + CinderEndpoint: os.Getenv("CINDER_ENDPOINT"), }) } @@ -174,6 +183,25 @@ func NewComputeV2Client() (*gophercloud.ServiceClient, error) { }) } +// NewDBV1Client returns a *ServiceClient for making calls +// to the OpenStack Database v1 API. An error will be returned +// if authentication or client creation was not possible. +func NewDBV1Client() (*gophercloud.ServiceClient, error) { + ao, err := openstack.AuthOptionsFromEnv() + if err != nil { + return nil, err + } + + client, err := openstack.AuthenticatedClient(ao) + if err != nil { + return nil, err + } + + return openstack.NewDBV1(client, gophercloud.EndpointOpts{ + Region: os.Getenv("OS_REGION_NAME"), + }) +} + // NewDNSV2Client returns a *ServiceClient for making calls // to the OpenStack Compute v2 API. An error will be returned // if authentication or client creation was not possible. @@ -341,3 +369,22 @@ func NewObjectStorageV1Client() (*gophercloud.ServiceClient, error) { Region: os.Getenv("OS_REGION_NAME"), }) } + +// NewSharedFileSystemV2Client returns a *ServiceClient for making calls +// to the OpenStack Shared File System v2 API. An error will be returned +// if authentication or client creation was not possible. +func NewSharedFileSystemV2Client() (*gophercloud.ServiceClient, error) { + ao, err := openstack.AuthOptionsFromEnv() + if err != nil { + return nil, err + } + + client, err := openstack.AuthenticatedClient(ao) + if err != nil { + return nil, err + } + + return openstack.NewSharedFileSystemV2(client, gophercloud.EndpointOpts{ + Region: os.Getenv("OS_REGION_NAME"), + }) +} diff --git a/vendor/github.com/gophercloud/gophercloud/acceptance/openstack/blockstorage/noauth/BUILD.bazel b/vendor/github.com/gophercloud/gophercloud/acceptance/openstack/blockstorage/noauth/BUILD.bazel new file mode 100644 index 0000000000..d247577925 --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/acceptance/openstack/blockstorage/noauth/BUILD.bazel @@ -0,0 +1,17 @@ +load("@io_bazel_rules_go//go:def.bzl", "go_library") + +go_library( + name = "go_default_library", + srcs = [ + "blockstorage.go", + "pkg.go", + ], + visibility = ["//visibility:public"], + deps = [ + "//vendor/github.com/gophercloud/gophercloud:go_default_library", + "//vendor/github.com/gophercloud/gophercloud/acceptance/clients:go_default_library", + "//vendor/github.com/gophercloud/gophercloud/acceptance/tools:go_default_library", + "//vendor/github.com/gophercloud/gophercloud/openstack/blockstorage/v2/snapshots:go_default_library", + "//vendor/github.com/gophercloud/gophercloud/openstack/blockstorage/v2/volumes:go_default_library", + ], +) diff --git a/vendor/github.com/gophercloud/gophercloud/acceptance/openstack/blockstorage/noauth/blockstorage.go b/vendor/github.com/gophercloud/gophercloud/acceptance/openstack/blockstorage/noauth/blockstorage.go new file mode 100644 index 0000000000..9e8aeab9dc --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/acceptance/openstack/blockstorage/noauth/blockstorage.go @@ -0,0 +1,142 @@ +// Package noauth contains common functions for creating block storage based +// resources for use in acceptance tests. See the `*_test.go` files for +// example usages. +package noauth + +import ( + "testing" + + "github.com/gophercloud/gophercloud" + "github.com/gophercloud/gophercloud/acceptance/clients" + "github.com/gophercloud/gophercloud/acceptance/tools" + "github.com/gophercloud/gophercloud/openstack/blockstorage/v2/snapshots" + "github.com/gophercloud/gophercloud/openstack/blockstorage/v2/volumes" +) + +// CreateVolume will create a volume with a random name and size of 1GB. An +// error will be returned if the volume was unable to be created. +func CreateVolume(t *testing.T, client *gophercloud.ServiceClient) (*volumes.Volume, error) { + if testing.Short() { + t.Skip("Skipping test that requires volume creation in short mode.") + } + + volumeName := tools.RandomString("ACPTTEST", 16) + t.Logf("Attempting to create volume: %s", volumeName) + + createOpts := volumes.CreateOpts{ + Size: 1, + Name: volumeName, + } + + volume, err := volumes.Create(client, createOpts).Extract() + if err != nil { + return volume, err + } + + err = volumes.WaitForStatus(client, volume.ID, "available", 60) + if err != nil { + return volume, err + } + + return volume, nil +} + +// CreateVolumeFromImage will create a volume from with a random name and size of +// 1GB. An error will be returned if the volume was unable to be created. +func CreateVolumeFromImage(t *testing.T, client *gophercloud.ServiceClient) (*volumes.Volume, error) { + if testing.Short() { + t.Skip("Skipping test that requires volume creation in short mode.") + } + + choices, err := clients.AcceptanceTestChoicesFromEnv() + if err != nil { + t.Fatal(err) + } + + volumeName := tools.RandomString("ACPTTEST", 16) + t.Logf("Attempting to create volume: %s", volumeName) + + createOpts := volumes.CreateOpts{ + Size: 1, + Name: volumeName, + ImageID: choices.ImageID, + } + + volume, err := volumes.Create(client, createOpts).Extract() + if err != nil { + return volume, err + } + + err = volumes.WaitForStatus(client, volume.ID, "available", 60) + if err != nil { + return volume, err + } + + return volume, nil +} + +// DeleteVolume will delete a volume. A fatal error will occur if the volume +// failed to be deleted. This works best when used as a deferred function. +func DeleteVolume(t *testing.T, client *gophercloud.ServiceClient, volume *volumes.Volume) { + err := volumes.Delete(client, volume.ID).ExtractErr() + if err != nil { + t.Fatalf("Unable to delete volume %s: %v", volume.ID, err) + } + + t.Logf("Deleted volume: %s", volume.ID) +} + +// CreateSnapshot will create a snapshot of the specified volume. +// Snapshot will be assigned a random name and description. +func CreateSnapshot(t *testing.T, client *gophercloud.ServiceClient, volume *volumes.Volume) (*snapshots.Snapshot, error) { + if testing.Short() { + t.Skip("Skipping test that requires snapshot creation in short mode.") + } + + snapshotName := tools.RandomString("ACPTTEST", 16) + snapshotDescription := tools.RandomString("ACPTTEST", 16) + t.Logf("Attempting to create snapshot: %s", snapshotName) + + createOpts := snapshots.CreateOpts{ + VolumeID: volume.ID, + Name: snapshotName, + Description: snapshotDescription, + } + + snapshot, err := snapshots.Create(client, createOpts).Extract() + if err != nil { + return snapshot, err + } + + err = snapshots.WaitForStatus(client, snapshot.ID, "available", 60) + if err != nil { + return snapshot, err + } + + return snapshot, nil +} + +// DeleteSnapshot will delete a snapshot. A fatal error will occur if the +// snapshot failed to be deleted. +func DeleteSnapshot(t *testing.T, client *gophercloud.ServiceClient, snapshot *snapshots.Snapshot) { + err := snapshots.Delete(client, snapshot.ID).ExtractErr() + if err != nil { + t.Fatalf("Unable to delete snapshot %s: %+v", snapshot.ID, err) + } + + // Volumes can't be deleted until their snapshots have been, + // so block up to 120 seconds for the snapshot to delete. + err = gophercloud.WaitFor(120, func() (bool, error) { + _, err := snapshots.Get(client, snapshot.ID).Extract() + if err != nil { + return true, nil + } + + return false, nil + }) + if err != nil { + t.Fatalf("Error waiting for snapshot to delete: %v", err) + } + + t.Logf("Deleted snapshot: %s", snapshot.ID) +} diff --git a/vendor/github.com/gophercloud/gophercloud/acceptance/openstack/blockstorage/noauth/pkg.go b/vendor/github.com/gophercloud/gophercloud/acceptance/openstack/blockstorage/noauth/pkg.go new file mode 100644 index 0000000000..5d4f920cbf --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/acceptance/openstack/blockstorage/noauth/pkg.go @@ -0,0 +1,3 @@ +// The noauth package contains acceptance tests for the Openstack Cinder standalone service. + +package noauth diff --git a/vendor/github.com/gophercloud/gophercloud/acceptance/openstack/blockstorage/noauth/snapshots_test.go b/vendor/github.com/gophercloud/gophercloud/acceptance/openstack/blockstorage/noauth/snapshots_test.go new file mode 100644 index 0000000000..f287c3e5f2 --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/acceptance/openstack/blockstorage/noauth/snapshots_test.go @@ -0,0 +1,58 @@ +// +build acceptance blockstorage + +package noauth + +import ( + "testing" + + "github.com/gophercloud/gophercloud/acceptance/clients" + "github.com/gophercloud/gophercloud/acceptance/tools" + "github.com/gophercloud/gophercloud/openstack/blockstorage/v2/snapshots" +) + +func TestSnapshotsList(t *testing.T) { + client, err := clients.NewBlockStorageV2NoAuthClient() + if err != nil { + t.Fatalf("Unable to create a blockstorage client: %v", err) + } + + allPages, err := snapshots.List(client, snapshots.ListOpts{}).AllPages() + if err != nil { + t.Fatalf("Unable to retrieve snapshots: %v", err) + } + + allSnapshots, err := snapshots.ExtractSnapshots(allPages) + if err != nil { + t.Fatalf("Unable to extract snapshots: %v", err) + } + + for _, snapshot := range allSnapshots { + tools.PrintResource(t, snapshot) + } +} + +func TestSnapshotsCreateDelete(t *testing.T) { + client, err := clients.NewBlockStorageV2NoAuthClient() + if err != nil { + t.Fatalf("Unable to create a blockstorage client: %v", err) + } + + volume, err := CreateVolume(t, client) + if err != nil { + t.Fatalf("Unable to create volume: %v", err) + } + defer DeleteVolume(t, client, volume) + + snapshot, err := CreateSnapshot(t, client, volume) + if err != nil { + t.Fatalf("Unable to create snapshot: %v", err) + } + defer DeleteSnapshot(t, client, snapshot) + + newSnapshot, err := snapshots.Get(client, snapshot.ID).Extract() + if err != nil { + t.Errorf("Unable to retrieve snapshot: %v", err) + } + + tools.PrintResource(t, newSnapshot) +} diff --git a/vendor/github.com/gophercloud/gophercloud/acceptance/openstack/blockstorage/noauth/volumes_test.go b/vendor/github.com/gophercloud/gophercloud/acceptance/openstack/blockstorage/noauth/volumes_test.go new file mode 100644 index 0000000000..4e10344cf6 --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/acceptance/openstack/blockstorage/noauth/volumes_test.go @@ -0,0 +1,52 @@ +// +build acceptance blockstorage + +package noauth + +import ( + "testing" + + "github.com/gophercloud/gophercloud/acceptance/clients" + "github.com/gophercloud/gophercloud/acceptance/tools" + "github.com/gophercloud/gophercloud/openstack/blockstorage/v2/volumes" +) + +func TestVolumesList(t *testing.T) { + client, err := clients.NewBlockStorageV2NoAuthClient() + if err != nil { + t.Fatalf("Unable to create a blockstorage client: %v", err) + } + + allPages, err := volumes.List(client, volumes.ListOpts{}).AllPages() + if err != nil { + t.Fatalf("Unable to retrieve volumes: %v", err) + } + + allVolumes, err := volumes.ExtractVolumes(allPages) + if err != nil { + t.Fatalf("Unable to extract volumes: %v", err) + } + + for _, volume := range allVolumes { + tools.PrintResource(t, volume) + } +} + +func TestVolumesCreateDestroy(t *testing.T) { + client, err := clients.NewBlockStorageV2NoAuthClient() + if err != nil { + t.Fatalf("Unable to create blockstorage client: %v", err) + } + + volume, err := CreateVolume(t, client) + if err != nil { + t.Fatalf("Unable to create volume: %v", err) + } + defer DeleteVolume(t, client, volume) + + newVolume, err := volumes.Get(client, volume.ID).Extract() + if err != nil { + t.Errorf("Unable to retrieve volume: %v", err) + } + + tools.PrintResource(t, newVolume) +} diff --git a/vendor/github.com/gophercloud/gophercloud/acceptance/openstack/compute/v2/servers_test.go b/vendor/github.com/gophercloud/gophercloud/acceptance/openstack/compute/v2/servers_test.go index 14516ff137..ada733f52d 100644 --- a/vendor/github.com/gophercloud/gophercloud/acceptance/openstack/compute/v2/servers_test.go +++ b/vendor/github.com/gophercloud/gophercloud/acceptance/openstack/compute/v2/servers_test.go @@ -11,6 +11,7 @@ import ( "github.com/gophercloud/gophercloud/acceptance/tools" "github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/attachinterfaces" "github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/availabilityzones" + "github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/lockunlock" "github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/pauseunpause" "github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/suspendresume" "github.com/gophercloud/gophercloud/openstack/compute/v2/servers" @@ -479,3 +480,39 @@ func TestServersActionSuspend(t *testing.T) { t.Fatal(err) } } + +func TestServersActionLock(t *testing.T) { + t.Parallel() + + client, err := clients.NewComputeV2Client() + if err != nil { + t.Fatalf("Unable to create a compute client: %v", err) + } + + server, err := CreateServer(t, client) + if err != nil { + t.Fatal(err) + } + defer DeleteServer(t, client, server) + + t.Logf("Attempting to Lock server %s", server.ID) + err = lockunlock.Lock(client, server.ID).ExtractErr() + if err != nil { + t.Fatal(err) + } + + err = servers.Delete(client, server.ID).ExtractErr() + if err == nil { + t.Fatalf("Should not have been able to delete the server") + } + + err = lockunlock.Unlock(client, server.ID).ExtractErr() + if err != nil { + t.Fatal(err) + } + + err = WaitForComputeStatus(client, server, "ACTIVE") + if err != nil { + t.Fatal(err) + } +} diff --git a/vendor/github.com/gophercloud/gophercloud/acceptance/openstack/db/v1/BUILD.bazel b/vendor/github.com/gophercloud/gophercloud/acceptance/openstack/db/v1/BUILD.bazel index bff8d97f8d..ce2acc3a6d 100644 --- a/vendor/github.com/gophercloud/gophercloud/acceptance/openstack/db/v1/BUILD.bazel +++ b/vendor/github.com/gophercloud/gophercloud/acceptance/openstack/db/v1/BUILD.bazel @@ -2,6 +2,17 @@ load("@io_bazel_rules_go//go:def.bzl", "go_library") go_library( name = "go_default_library", - srcs = ["pkg.go"], + srcs = [ + "db.go", + "pkg.go", + ], visibility = ["//visibility:public"], + deps = [ + "//vendor/github.com/gophercloud/gophercloud:go_default_library", + "//vendor/github.com/gophercloud/gophercloud/acceptance/clients:go_default_library", + "//vendor/github.com/gophercloud/gophercloud/acceptance/tools:go_default_library", + "//vendor/github.com/gophercloud/gophercloud/openstack/db/v1/databases:go_default_library", + "//vendor/github.com/gophercloud/gophercloud/openstack/db/v1/instances:go_default_library", + "//vendor/github.com/gophercloud/gophercloud/openstack/db/v1/users:go_default_library", + ], ) diff --git a/vendor/github.com/gophercloud/gophercloud/acceptance/openstack/db/v1/common.go b/vendor/github.com/gophercloud/gophercloud/acceptance/openstack/db/v1/common.go deleted file mode 100644 index bbe7ebd6fd..0000000000 --- a/vendor/github.com/gophercloud/gophercloud/acceptance/openstack/db/v1/common.go +++ /dev/null @@ -1,70 +0,0 @@ -// +build acceptance db - -package v1 - -import ( - "os" - "testing" - - "github.com/gophercloud/gophercloud" - "github.com/gophercloud/gophercloud/openstack" - "github.com/gophercloud/gophercloud/openstack/db/v1/instances" - th "github.com/gophercloud/gophercloud/testhelper" -) - -func newClient(t *testing.T) *gophercloud.ServiceClient { - ao, err := openstack.AuthOptionsFromEnv() - th.AssertNoErr(t, err) - - client, err := openstack.AuthenticatedClient(ao) - th.AssertNoErr(t, err) - - c, err := openstack.NewDBV1(client, gophercloud.EndpointOpts{ - Region: os.Getenv("OS_REGION_NAME"), - }) - th.AssertNoErr(t, err) - - return c -} - -type context struct { - test *testing.T - client *gophercloud.ServiceClient - instanceID string - DBIDs []string - users []string -} - -func newContext(t *testing.T) context { - return context{ - test: t, - client: newClient(t), - } -} - -func (c context) Logf(msg string, args ...interface{}) { - if len(args) > 0 { - c.test.Logf(msg, args...) - } else { - c.test.Log(msg) - } -} - -func (c context) AssertNoErr(err error) { - th.AssertNoErr(c.test, err) -} - -func (c context) WaitUntilActive(id string) { - err := gophercloud.WaitFor(60, func() (bool, error) { - inst, err := instances.Get(c.client, id).Extract() - if err != nil { - return false, err - } - if inst.Status == "ACTIVE" { - return true, nil - } - return false, nil - }) - - c.AssertNoErr(err) -} diff --git a/vendor/github.com/gophercloud/gophercloud/acceptance/openstack/db/v1/database_test.go b/vendor/github.com/gophercloud/gophercloud/acceptance/openstack/db/v1/database_test.go deleted file mode 100644 index c52357a6be..0000000000 --- a/vendor/github.com/gophercloud/gophercloud/acceptance/openstack/db/v1/database_test.go +++ /dev/null @@ -1,45 +0,0 @@ -// +build acceptance db - -package v1 - -import ( - db "github.com/gophercloud/gophercloud/openstack/db/v1/databases" - "github.com/gophercloud/gophercloud/pagination" -) - -func (c context) createDBs() { - opts := db.BatchCreateOpts{ - db.CreateOpts{Name: "db1"}, - db.CreateOpts{Name: "db2"}, - db.CreateOpts{Name: "db3"}, - } - - err := db.Create(c.client, c.instanceID, opts).ExtractErr() - c.AssertNoErr(err) - c.Logf("Created three databases on instance %s: db1, db2, db3", c.instanceID) -} - -func (c context) listDBs() { - c.Logf("Listing databases on instance %s", c.instanceID) - - err := db.List(c.client, c.instanceID).EachPage(func(page pagination.Page) (bool, error) { - dbList, err := db.ExtractDBs(page) - c.AssertNoErr(err) - - for _, db := range dbList { - c.Logf("DB: %#v", db) - } - - return true, nil - }) - - c.AssertNoErr(err) -} - -func (c context) deleteDBs() { - for _, id := range []string{"db1", "db2", "db3"} { - err := db.Delete(c.client, c.instanceID, id).ExtractErr() - c.AssertNoErr(err) - c.Logf("Deleted DB %s", id) - } -} diff --git a/vendor/github.com/gophercloud/gophercloud/acceptance/openstack/db/v1/databases_test.go b/vendor/github.com/gophercloud/gophercloud/acceptance/openstack/db/v1/databases_test.go new file mode 100644 index 0000000000..dcbf72f040 --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/acceptance/openstack/db/v1/databases_test.go @@ -0,0 +1,55 @@ +// +build acceptance db + +package v1 + +import ( + "testing" + + "github.com/gophercloud/gophercloud/acceptance/clients" + "github.com/gophercloud/gophercloud/acceptance/tools" + "github.com/gophercloud/gophercloud/openstack/db/v1/databases" +) + +// Because it takes so long to create an instance, +// all tests will be housed in a single function. +func TestDatabases(t *testing.T) { + if testing.Short() { + t.Skip("Skipping in short mode") + } + + client, err := clients.NewDBV1Client() + if err != nil { + t.Fatalf("Unable to create a DB client: %v", err) + } + + // Create and Get an instance. + instance, err := CreateInstance(t, client) + if err != nil { + t.Fatalf("Unable to create instance: %v", err) + } + defer DeleteInstance(t, client, instance.ID) + + // Create a database. + err = CreateDatabase(t, client, instance.ID) + if err != nil { + t.Fatalf("Unable to create database: %v", err) + } + + // List all databases. + allPages, err := databases.List(client, instance.ID).AllPages() + if err != nil { + t.Fatalf("Unable to list databases: %v", err) + } + + allDatabases, err := databases.ExtractDBs(allPages) + if err != nil { + t.Fatalf("Unable to extract databases: %v", err) + } + + for _, db := range allDatabases { + tools.PrintResource(t, db) + } + + defer DeleteDatabase(t, client, instance.ID, allDatabases[0].Name) + +} diff --git a/vendor/github.com/gophercloud/gophercloud/acceptance/openstack/db/v1/db.go b/vendor/github.com/gophercloud/gophercloud/acceptance/openstack/db/v1/db.go new file mode 100644 index 0000000000..f5e637f3d9 --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/acceptance/openstack/db/v1/db.go @@ -0,0 +1,145 @@ +// Package v2 contains common functions for creating db resources for use +// in acceptance tests. See the `*_test.go` files for example usages. +package v1 + +import ( + "fmt" + "testing" + + "github.com/gophercloud/gophercloud" + "github.com/gophercloud/gophercloud/acceptance/clients" + "github.com/gophercloud/gophercloud/acceptance/tools" + "github.com/gophercloud/gophercloud/openstack/db/v1/databases" + "github.com/gophercloud/gophercloud/openstack/db/v1/instances" + "github.com/gophercloud/gophercloud/openstack/db/v1/users" +) + +// CreateDatabase will create a database with a randomly generated name. +// An error will be returned if the database was unable to be created. +func CreateDatabase(t *testing.T, client *gophercloud.ServiceClient, instanceID string) error { + name := tools.RandomString("ACPTTEST", 8) + t.Logf("Attempting to create database: %s", name) + + createOpts := databases.BatchCreateOpts{ + databases.CreateOpts{ + Name: name, + }, + } + + return databases.Create(client, instanceID, createOpts).ExtractErr() +} + +// CreateInstance will create an instance with a randomly generated name. +// The flavor of the instance will be the value of the OS_FLAVOR_ID +// environment variable. The Datastore will be pulled from the +// OS_DATASTORE_TYPE_ID environment variable. +// An error will be returned if the instance was unable to be created. +func CreateInstance(t *testing.T, client *gophercloud.ServiceClient) (*instances.Instance, error) { + if testing.Short() { + t.Skip("Skipping test that requires instance creation in short mode.") + } + + choices, err := clients.AcceptanceTestChoicesFromEnv() + if err != nil { + return nil, err + } + + name := tools.RandomString("ACPTTEST", 8) + t.Logf("Attempting to create instance: %s", name) + + createOpts := instances.CreateOpts{ + FlavorRef: choices.FlavorID, + Size: 1, + Name: name, + Datastore: &instances.DatastoreOpts{ + Type: choices.DBDatastoreType, + Version: choices.DBDatastoreVersion, + }, + } + + instance, err := instances.Create(client, createOpts).Extract() + if err != nil { + return instance, err + } + + if err := WaitForInstanceStatus(client, instance, "ACTIVE"); err != nil { + return instance, err + } + + return instances.Get(client, instance.ID).Extract() +} + +// CreateUser will create a user with a randomly generated name. +// An error will be returned if the user was unable to be created. +func CreateUser(t *testing.T, client *gophercloud.ServiceClient, instanceID string) error { + name := tools.RandomString("ACPTTEST", 8) + password := tools.RandomString("", 8) + t.Logf("Attempting to create user: %s", name) + + createOpts := users.BatchCreateOpts{ + users.CreateOpts{ + Name: name, + Password: password, + }, + } + + return users.Create(client, instanceID, createOpts).ExtractErr() +} + +// DeleteDatabase deletes a database. A fatal error will occur if the database +// failed to delete. This works best when used as a deferred function. +func DeleteDatabase(t *testing.T, client *gophercloud.ServiceClient, instanceID, name string) { + t.Logf("Attempting to delete database: %s", name) + err := databases.Delete(client, instanceID, name).ExtractErr() + if err != nil { + t.Fatalf("Unable to delete database %s: %s", name, err) + } + + t.Logf("Deleted database: %s", name) +} + +// DeleteInstance deletes an instance. A fatal error will occur if the instance +// failed to delete. This works best when used as a deferred function. +func DeleteInstance(t *testing.T, client *gophercloud.ServiceClient, id string) { + t.Logf("Attempting to delete instance: %s", id) + err := instances.Delete(client, id).ExtractErr() + if err != nil { + t.Fatalf("Unable to delete instance %s: %s", id, err) + } + + t.Logf("Deleted instance: %s", id) +} + +// DeleteUser deletes a user. A fatal error will occur if the user +// failed to delete. This works best when used as a deferred function. +func DeleteUser(t *testing.T, client *gophercloud.ServiceClient, instanceID, name string) { + t.Logf("Attempting to delete user: %s", name) + err := users.Delete(client, instanceID, name).ExtractErr() + if err != nil { + t.Fatalf("Unable to delete users %s: %s", name, err) + } + + t.Logf("Deleted users: %s", name) +} + +// WaitForInstanceState will poll an instance's status until it either matches +// the specified status or the status becomes ERROR. +func WaitForInstanceStatus( + client *gophercloud.ServiceClient, instance *instances.Instance, status string) error { + return tools.WaitFor(func() (bool, error) { + latest, err := instances.Get(client, instance.ID).Extract() + if err != nil { + return false, err + } + + if latest.Status == status { + return true, nil + } + + if latest.Status == "ERROR" { + return false, fmt.Errorf("Instance in ERROR state") + } + + return false, nil + }) +} diff --git a/vendor/github.com/gophercloud/gophercloud/acceptance/openstack/db/v1/flavor_test.go b/vendor/github.com/gophercloud/gophercloud/acceptance/openstack/db/v1/flavor_test.go deleted file mode 100644 index 6440cc9278..0000000000 --- a/vendor/github.com/gophercloud/gophercloud/acceptance/openstack/db/v1/flavor_test.go +++ /dev/null @@ -1,31 +0,0 @@ -// +build acceptance db - -package v1 - -import ( - "github.com/gophercloud/gophercloud/openstack/db/v1/flavors" - "github.com/gophercloud/gophercloud/pagination" -) - -func (c context) listFlavors() { - c.Logf("Listing flavors") - - err := flavors.List(c.client).EachPage(func(page pagination.Page) (bool, error) { - flavorList, err := flavors.ExtractFlavors(page) - c.AssertNoErr(err) - - for _, f := range flavorList { - c.Logf("Flavor: ID [%s] Name [%s] RAM [%d]", f.ID, f.Name, f.RAM) - } - - return true, nil - }) - - c.AssertNoErr(err) -} - -func (c context) getFlavor() { - flavor, err := flavors.Get(c.client, "1").Extract() - c.Logf("Getting flavor %s", flavor.ID) - c.AssertNoErr(err) -} diff --git a/vendor/github.com/gophercloud/gophercloud/acceptance/openstack/db/v1/flavors_test.go b/vendor/github.com/gophercloud/gophercloud/acceptance/openstack/db/v1/flavors_test.go new file mode 100644 index 0000000000..73171b9424 --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/acceptance/openstack/db/v1/flavors_test.go @@ -0,0 +1,58 @@ +// +build acceptance db + +package v1 + +import ( + "testing" + + "github.com/gophercloud/gophercloud/acceptance/clients" + "github.com/gophercloud/gophercloud/acceptance/tools" + "github.com/gophercloud/gophercloud/openstack/db/v1/flavors" +) + +func TestFlavorsList(t *testing.T) { + client, err := clients.NewDBV1Client() + if err != nil { + t.Fatalf("Unable to create a DB client: %v", err) + } + + allPages, err := flavors.List(client).AllPages() + if err != nil { + t.Fatalf("Unable to retrieve flavors: %v", err) + } + + allFlavors, err := flavors.ExtractFlavors(allPages) + if err != nil { + t.Fatalf("Unable to extract flavors: %v", err) + } + + for _, flavor := range allFlavors { + tools.PrintResource(t, &flavor) + } +} + +func TestFlavorsGet(t *testing.T) { + client, err := clients.NewDBV1Client() + if err != nil { + t.Fatalf("Unable to create a DB client: %v", err) + } + + allPages, err := flavors.List(client).AllPages() + if err != nil { + t.Fatalf("Unable to retrieve flavors: %v", err) + } + + allFlavors, err := flavors.ExtractFlavors(allPages) + if err != nil { + t.Fatalf("Unable to extract flavors: %v", err) + } + + if len(allFlavors) > 0 { + flavor, err := flavors.Get(client, allFlavors[0].StrID).Extract() + if err != nil { + t.Fatalf("Unable to get flavor: %v", err) + } + + tools.PrintResource(t, flavor) + } +} diff --git a/vendor/github.com/gophercloud/gophercloud/acceptance/openstack/db/v1/instance_test.go b/vendor/github.com/gophercloud/gophercloud/acceptance/openstack/db/v1/instance_test.go deleted file mode 100644 index 75668a2297..0000000000 --- a/vendor/github.com/gophercloud/gophercloud/acceptance/openstack/db/v1/instance_test.go +++ /dev/null @@ -1,138 +0,0 @@ -// +build acceptance db - -package v1 - -import ( - "os" - "testing" - - "github.com/gophercloud/gophercloud/acceptance/tools" - "github.com/gophercloud/gophercloud/openstack/db/v1/instances" - "github.com/gophercloud/gophercloud/pagination" - th "github.com/gophercloud/gophercloud/testhelper" -) - -const envDSType = "DATASTORE_TYPE_ID" - -func TestRunner(t *testing.T) { - c := newContext(t) - - // FLAVOR tests - c.listFlavors() - c.getFlavor() - - // INSTANCE tests - c.createInstance() - c.listInstances() - c.getInstance() - c.isRootEnabled() - c.enableRootUser() - c.isRootEnabled() - c.restartInstance() - //c.resizeInstance() - //c.resizeVol() - - // DATABASE tests - c.createDBs() - c.listDBs() - - // USER tests - c.createUsers() - c.listUsers() - - // TEARDOWN - c.deleteUsers() - c.deleteDBs() - c.deleteInstance() -} - -func (c context) createInstance() { - if os.Getenv(envDSType) == "" { - c.test.Fatalf("%s must be set as an environment var", envDSType) - } - - opts := instances.CreateOpts{ - FlavorRef: "2", - Size: 5, - Name: tools.RandomString("gopher_db", 5), - Datastore: &instances.DatastoreOpts{Type: os.Getenv(envDSType)}, - } - - instance, err := instances.Create(c.client, opts).Extract() - th.AssertNoErr(c.test, err) - - c.Logf("Restarting %s. Waiting...", instance.ID) - c.WaitUntilActive(instance.ID) - c.Logf("Created Instance %s", instance.ID) - - c.instanceID = instance.ID -} - -func (c context) listInstances() { - c.Logf("Listing instances") - - err := instances.List(c.client).EachPage(func(page pagination.Page) (bool, error) { - instanceList, err := instances.ExtractInstances(page) - c.AssertNoErr(err) - - for _, i := range instanceList { - c.Logf("Instance: ID [%s] Name [%s] Status [%s] VolSize [%d] Datastore Type [%s]", - i.ID, i.Name, i.Status, i.Volume.Size, i.Datastore.Type) - } - - return true, nil - }) - - c.AssertNoErr(err) -} - -func (c context) getInstance() { - instance, err := instances.Get(c.client, c.instanceID).Extract() - c.AssertNoErr(err) - c.Logf("Getting instance: %s", instance.ID) -} - -func (c context) deleteInstance() { - err := instances.Delete(c.client, c.instanceID).ExtractErr() - c.AssertNoErr(err) - c.Logf("Deleted instance %s", c.instanceID) -} - -func (c context) enableRootUser() { - _, err := instances.EnableRootUser(c.client, c.instanceID).Extract() - c.AssertNoErr(err) - c.Logf("Enabled root user on %s", c.instanceID) -} - -func (c context) isRootEnabled() { - enabled, err := instances.IsRootEnabled(c.client, c.instanceID) - c.AssertNoErr(err) - c.Logf("Is root enabled? %d", enabled) -} - -func (c context) restartInstance() { - id := c.instanceID - err := instances.Restart(c.client, id).ExtractErr() - c.AssertNoErr(err) - c.Logf("Restarting %s. Waiting...", id) - c.WaitUntilActive(id) - c.Logf("Restarted %s", id) -} - -func (c context) resizeInstance() { - id := c.instanceID - err := instances.Resize(c.client, id, "3").ExtractErr() - c.AssertNoErr(err) - c.Logf("Resizing %s. Waiting...", id) - c.WaitUntilActive(id) - c.Logf("Resized %s with flavorRef %s", id, "2") -} - -func (c context) resizeVol() { - id := c.instanceID - err := instances.ResizeVolume(c.client, id, 4).ExtractErr() - c.AssertNoErr(err) - c.Logf("Resizing volume of %s. Waiting...", id) - c.WaitUntilActive(id) - c.Logf("Resized the volume of %s to %d GB", id, 2) -} diff --git a/vendor/github.com/gophercloud/gophercloud/acceptance/openstack/db/v1/instances_test.go b/vendor/github.com/gophercloud/gophercloud/acceptance/openstack/db/v1/instances_test.go new file mode 100644 index 0000000000..a98047f3e0 --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/acceptance/openstack/db/v1/instances_test.go @@ -0,0 +1,71 @@ +// +build acceptance db + +package v1 + +import ( + "testing" + + "github.com/gophercloud/gophercloud/acceptance/clients" + "github.com/gophercloud/gophercloud/acceptance/tools" + "github.com/gophercloud/gophercloud/openstack/db/v1/instances" +) + +// Because it takes so long to create an instance, +// all tests will be housed in a single function. +func TestInstances(t *testing.T) { + if testing.Short() { + t.Skip("Skipping in short mode") + } + + client, err := clients.NewDBV1Client() + if err != nil { + t.Fatalf("Unable to create a DB client: %v", err) + } + + // Create and Get an instance. + instance, err := CreateInstance(t, client) + if err != nil { + t.Fatalf("Unable to create instance: %v", err) + } + defer DeleteInstance(t, client, instance.ID) + tools.PrintResource(t, &instance) + + // List all instances. + allPages, err := instances.List(client).AllPages() + if err != nil { + t.Fatalf("Unable to list instances: %v", err) + } + + allInstances, err := instances.ExtractInstances(allPages) + if err != nil { + t.Fatalf("Unable to extract instances: %v", err) + } + + for _, instance := range allInstances { + tools.PrintResource(t, instance) + } + + // Enable root user. + _, err = instances.EnableRootUser(client, instance.ID).Extract() + if err != nil { + t.Fatalf("Unable to enable root user: %v", err) + } + + enabled, err := instances.IsRootEnabled(client, instance.ID).Extract() + if err != nil { + t.Fatalf("Unable to check if root user is enabled: %v", err) + } + + t.Logf("Root user is enabled: %t", enabled) + + // Restart + err = instances.Restart(client, instance.ID).ExtractErr() + if err != nil { + t.Fatalf("Unable to restart instance: %v", err) + } + + err = WaitForInstanceStatus(client, instance, "ACTIVE") + if err != nil { + t.Fatalf("Unable to restart instance: %v", err) + } +} diff --git a/vendor/github.com/gophercloud/gophercloud/acceptance/openstack/db/v1/user_test.go b/vendor/github.com/gophercloud/gophercloud/acceptance/openstack/db/v1/user_test.go deleted file mode 100644 index 0f5fcc24b1..0000000000 --- a/vendor/github.com/gophercloud/gophercloud/acceptance/openstack/db/v1/user_test.go +++ /dev/null @@ -1,70 +0,0 @@ -// +build acceptance db - -package v1 - -import ( - "github.com/gophercloud/gophercloud/acceptance/tools" - db "github.com/gophercloud/gophercloud/openstack/db/v1/databases" - u "github.com/gophercloud/gophercloud/openstack/db/v1/users" - "github.com/gophercloud/gophercloud/pagination" -) - -func (c context) createUsers() { - users := []string{ - tools.RandomString("user_", 5), - tools.RandomString("user_", 5), - tools.RandomString("user_", 5), - } - - db1 := db.CreateOpts{Name: "db1"} - db2 := db.CreateOpts{Name: "db2"} - db3 := db.CreateOpts{Name: "db3"} - - opts := u.BatchCreateOpts{ - u.CreateOpts{ - Name: users[0], - Password: tools.RandomString("", 5), - Databases: db.BatchCreateOpts{db1, db2, db3}, - }, - u.CreateOpts{ - Name: users[1], - Password: tools.RandomString("", 5), - Databases: db.BatchCreateOpts{db1, db2}, - }, - u.CreateOpts{ - Name: users[2], - Password: tools.RandomString("", 5), - Databases: db.BatchCreateOpts{db3}, - }, - } - - err := u.Create(c.client, c.instanceID, opts).ExtractErr() - c.AssertNoErr(err) - c.Logf("Created three users on instance %s: %s, %s, %s", c.instanceID, users[0], users[1], users[2]) - c.users = users -} - -func (c context) listUsers() { - c.Logf("Listing databases on instance %s", c.instanceID) - - err := db.List(c.client, c.instanceID).EachPage(func(page pagination.Page) (bool, error) { - dbList, err := db.ExtractDBs(page) - c.AssertNoErr(err) - - for _, db := range dbList { - c.Logf("DB: %#v", db) - } - - return true, nil - }) - - c.AssertNoErr(err) -} - -func (c context) deleteUsers() { - for _, id := range c.DBIDs { - err := db.Delete(c.client, c.instanceID, id).ExtractErr() - c.AssertNoErr(err) - c.Logf("Deleted DB %s", id) - } -} diff --git a/vendor/github.com/gophercloud/gophercloud/acceptance/openstack/db/v1/users_test.go b/vendor/github.com/gophercloud/gophercloud/acceptance/openstack/db/v1/users_test.go new file mode 100644 index 0000000000..4335eabb2f --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/acceptance/openstack/db/v1/users_test.go @@ -0,0 +1,54 @@ +// +build acceptance db + +package v1 + +import ( + "testing" + + "github.com/gophercloud/gophercloud/acceptance/clients" + "github.com/gophercloud/gophercloud/acceptance/tools" + "github.com/gophercloud/gophercloud/openstack/db/v1/users" +) + +// Because it takes so long to create an instance, +// all tests will be housed in a single function. +func TestUsers(t *testing.T) { + if testing.Short() { + t.Skip("Skipping in short mode") + } + + client, err := clients.NewDBV1Client() + if err != nil { + t.Fatalf("Unable to create a DB client: %v", err) + } + + // Create and Get an instance. + instance, err := CreateInstance(t, client) + if err != nil { + t.Fatalf("Unable to create instance: %v", err) + } + defer DeleteInstance(t, client, instance.ID) + + // Create a user. + err = CreateUser(t, client, instance.ID) + if err != nil { + t.Fatalf("Unable to create user: %v", err) + } + + // List all users. + allPages, err := users.List(client, instance.ID).AllPages() + if err != nil { + t.Fatalf("Unable to list users: %v", err) + } + + allUsers, err := users.ExtractUsers(allPages) + if err != nil { + t.Fatalf("Unable to extract users: %v", err) + } + + for _, user := range allUsers { + tools.PrintResource(t, user) + } + + defer DeleteUser(t, client, instance.ID, allUsers[0].Name) +} diff --git a/vendor/github.com/gophercloud/gophercloud/acceptance/openstack/identity/v3/BUILD.bazel b/vendor/github.com/gophercloud/gophercloud/acceptance/openstack/identity/v3/BUILD.bazel index 19126b199b..c21c62796c 100644 --- a/vendor/github.com/gophercloud/gophercloud/acceptance/openstack/identity/v3/BUILD.bazel +++ b/vendor/github.com/gophercloud/gophercloud/acceptance/openstack/identity/v3/BUILD.bazel @@ -10,7 +10,10 @@ go_library( deps = [ "//vendor/github.com/gophercloud/gophercloud:go_default_library", "//vendor/github.com/gophercloud/gophercloud/acceptance/tools:go_default_library", + "//vendor/github.com/gophercloud/gophercloud/openstack/identity/v3/domains:go_default_library", + "//vendor/github.com/gophercloud/gophercloud/openstack/identity/v3/groups:go_default_library", "//vendor/github.com/gophercloud/gophercloud/openstack/identity/v3/projects:go_default_library", + "//vendor/github.com/gophercloud/gophercloud/openstack/identity/v3/roles:go_default_library", "//vendor/github.com/gophercloud/gophercloud/openstack/identity/v3/users:go_default_library", ], ) diff --git a/vendor/github.com/gophercloud/gophercloud/acceptance/openstack/identity/v3/domains_test.go b/vendor/github.com/gophercloud/gophercloud/acceptance/openstack/identity/v3/domains_test.go new file mode 100644 index 0000000000..b340bed4bd --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/acceptance/openstack/identity/v3/domains_test.go @@ -0,0 +1,96 @@ +// +build acceptance + +package v3 + +import ( + "testing" + + "github.com/gophercloud/gophercloud/acceptance/clients" + "github.com/gophercloud/gophercloud/acceptance/tools" + "github.com/gophercloud/gophercloud/openstack/identity/v3/domains" +) + +func TestDomainsList(t *testing.T) { + client, err := clients.NewIdentityV3Client() + if err != nil { + t.Fatalf("Unable to obtain an identity client: %v", err) + } + + var iTrue bool = true + listOpts := domains.ListOpts{ + Enabled: &iTrue, + } + + allPages, err := domains.List(client, listOpts).AllPages() + if err != nil { + t.Fatalf("Unable to list domains: %v", err) + } + + allDomains, err := domains.ExtractDomains(allPages) + if err != nil { + t.Fatalf("Unable to extract domains: %v", err) + } + + for _, domain := range allDomains { + tools.PrintResource(t, domain) + } +} + +func TestDomainsGet(t *testing.T) { + client, err := clients.NewIdentityV3Client() + if err != nil { + t.Fatalf("Unable to obtain an identity client: %v", err) + } + + allPages, err := domains.List(client, nil).AllPages() + if err != nil { + t.Fatalf("Unable to list domains: %v", err) + } + + allDomains, err := domains.ExtractDomains(allPages) + if err != nil { + t.Fatalf("Unable to extract domains: %v", err) + } + + domain := allDomains[0] + p, err := domains.Get(client, domain.ID).Extract() + if err != nil { + t.Fatalf("Unable to get domain: %v", err) + } + + tools.PrintResource(t, p) +} + +func TestDomainsCRUD(t *testing.T) { + client, err := clients.NewIdentityV3Client() + if err != nil { + t.Fatalf("Unable to obtain an identity client: %v", err) + } + + var iTrue bool = true + createOpts := domains.CreateOpts{ + Description: "Testing Domain", + Enabled: &iTrue, + } + + domain, err := CreateDomain(t, client, &createOpts) + if err != nil { + t.Fatalf("Unable to create domain: %v", err) + } + defer DeleteDomain(t, client, domain.ID) + + tools.PrintResource(t, domain) + + var iFalse bool = false + updateOpts := domains.UpdateOpts{ + Description: "Staging Test Domain", + Enabled: &iFalse, + } + + newDomain, err := domains.Update(client, domain.ID, updateOpts).Extract() + if err != nil { + t.Fatalf("Unable to update domain: %v", err) + } + + tools.PrintResource(t, newDomain) +} diff --git a/vendor/github.com/gophercloud/gophercloud/acceptance/openstack/identity/v3/groups_test.go b/vendor/github.com/gophercloud/gophercloud/acceptance/openstack/identity/v3/groups_test.go new file mode 100644 index 0000000000..3e832c2022 --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/acceptance/openstack/identity/v3/groups_test.go @@ -0,0 +1,79 @@ +// +build acceptance + +package v3 + +import ( + "testing" + + "github.com/gophercloud/gophercloud/acceptance/clients" + "github.com/gophercloud/gophercloud/acceptance/tools" + "github.com/gophercloud/gophercloud/openstack/identity/v3/groups" +) + +func TestGroupCRUD(t *testing.T) { + client, err := clients.NewIdentityV3Client() + if err != nil { + t.Fatalf("Unable to obtain an identity client: %v", err) + } + + createOpts := groups.CreateOpts{ + Name: "testgroup", + DomainID: "default", + Extra: map[string]interface{}{ + "email": "testgroup@example.com", + }, + } + + // Create Group in the default domain + group, err := CreateGroup(t, client, &createOpts) + if err != nil { + t.Fatalf("Unable to create group: %v", err) + } + defer DeleteGroup(t, client, group.ID) + + tools.PrintResource(t, group) + tools.PrintResource(t, group.Extra) + + updateOpts := groups.UpdateOpts{ + Description: "Test Users", + Extra: map[string]interface{}{ + "email": "thetestgroup@example.com", + }, + } + + newGroup, err := groups.Update(client, group.ID, updateOpts).Extract() + if err != nil { + t.Fatalf("Unable to update group: %v", err) + } + + tools.PrintResource(t, newGroup) + tools.PrintResource(t, newGroup.Extra) + + listOpts := groups.ListOpts{ + DomainID: "default", + } + + // List all Groups in default domain + allPages, err := groups.List(client, listOpts).AllPages() + if err != nil { + t.Fatalf("Unable to list groups: %v", err) + } + + allGroups, err := groups.ExtractGroups(allPages) + if err != nil { + t.Fatalf("Unable to extract groups: %v", err) + } + + for _, g := range allGroups { + tools.PrintResource(t, g) + tools.PrintResource(t, g.Extra) + } + + // Get the recently created group by ID + p, err := groups.Get(client, group.ID).Extract() + if err != nil { + t.Fatalf("Unable to get group: %v", err) + } + + tools.PrintResource(t, p) +} diff --git a/vendor/github.com/gophercloud/gophercloud/acceptance/openstack/identity/v3/identity.go b/vendor/github.com/gophercloud/gophercloud/acceptance/openstack/identity/v3/identity.go index 4f2f6219d9..2577297811 100644 --- a/vendor/github.com/gophercloud/gophercloud/acceptance/openstack/identity/v3/identity.go +++ b/vendor/github.com/gophercloud/gophercloud/acceptance/openstack/identity/v3/identity.go @@ -5,7 +5,10 @@ import ( "github.com/gophercloud/gophercloud" "github.com/gophercloud/gophercloud/acceptance/tools" + "github.com/gophercloud/gophercloud/openstack/identity/v3/domains" + "github.com/gophercloud/gophercloud/openstack/identity/v3/groups" "github.com/gophercloud/gophercloud/openstack/identity/v3/projects" + "github.com/gophercloud/gophercloud/openstack/identity/v3/roles" "github.com/gophercloud/gophercloud/openstack/identity/v3/users" ) @@ -36,7 +39,7 @@ func CreateProject(t *testing.T, client *gophercloud.ServiceClient, c *projects. return project, nil } -// CreateUser will create a project with a random name. +// CreateUser will create a user with a random name. // It takes an optional createOpts parameter since creating a user // has so many options. An error will be returned if the user was // unable to be created. @@ -63,6 +66,87 @@ func CreateUser(t *testing.T, client *gophercloud.ServiceClient, c *users.Create return user, nil } +// CreateGroup will create a group with a random name. +// It takes an optional createOpts parameter since creating a group +// has so many options. An error will be returned if the group was +// unable to be created. +func CreateGroup(t *testing.T, client *gophercloud.ServiceClient, c *groups.CreateOpts) (*groups.Group, error) { + name := tools.RandomString("ACPTTEST", 8) + t.Logf("Attempting to create group: %s", name) + + var createOpts groups.CreateOpts + if c != nil { + createOpts = *c + } else { + createOpts = groups.CreateOpts{} + } + + createOpts.Name = name + + group, err := groups.Create(client, createOpts).Extract() + if err != nil { + return group, err + } + + t.Logf("Successfully created group %s with ID %s", name, group.ID) + + return group, nil +} + +// CreateDomain will create a domain with a random name. +// It takes an optional createOpts parameter since creating a domain +// has many options. An error will be returned if the domain was +// unable to be created. +func CreateDomain(t *testing.T, client *gophercloud.ServiceClient, c *domains.CreateOpts) (*domains.Domain, error) { + name := tools.RandomString("ACPTTEST", 8) + t.Logf("Attempting to create domain: %s", name) + + var createOpts domains.CreateOpts + if c != nil { + createOpts = *c + } else { + createOpts = domains.CreateOpts{} + } + + createOpts.Name = name + + domain, err := domains.Create(client, createOpts).Extract() + if err != nil { + return domain, err + } + + t.Logf("Successfully created domain %s with ID %s", name, domain.ID) + + return domain, nil +} + +// CreateRole will create a role with a random name. +// It takes an optional createOpts parameter since creating a role +// has so many options. An error will be returned if the role was +// unable to be created. +func CreateRole(t *testing.T, client *gophercloud.ServiceClient, c *roles.CreateOpts) (*roles.Role, error) { + name := tools.RandomString("ACPTTEST", 8) + t.Logf("Attempting to create role: %s", name) + + var createOpts roles.CreateOpts + if c != nil { + createOpts = *c + } else { + createOpts = roles.CreateOpts{} + } + + createOpts.Name = name + + role, err := roles.Create(client, createOpts).Extract() + if err != nil { + return role, err + } + + t.Logf("Successfully created role %s with ID %s", name, role.ID) + + return role, nil +} + // DeleteProject will delete a project by ID. A fatal error will occur if // the project ID failed to be deleted. This works best when using it as // a deferred function. @@ -81,8 +165,82 @@ func DeleteProject(t *testing.T, client *gophercloud.ServiceClient, projectID st func DeleteUser(t *testing.T, client *gophercloud.ServiceClient, userID string) { err := users.Delete(client, userID).ExtractErr() if err != nil { - t.Fatalf("Unable to delete user %s: %v", userID, err) + t.Fatalf("Unable to delete user with ID %s: %v", userID, err) } - t.Logf("Deleted user: %s", userID) + t.Logf("Deleted user with ID: %s", userID) +} + +// DeleteGroup will delete a group by ID. A fatal error will occur if +// the group failed to be deleted. This works best when using it as +// a deferred function. +func DeleteGroup(t *testing.T, client *gophercloud.ServiceClient, groupID string) { + err := groups.Delete(client, groupID).ExtractErr() + if err != nil { + t.Fatalf("Unable to delete group %s: %v", groupID, err) + } + + t.Logf("Deleted group: %s", groupID) +} + +// DeleteDomain will delete a domain by ID. A fatal error will occur if +// the project ID failed to be deleted. This works best when using it as +// a deferred function. +func DeleteDomain(t *testing.T, client *gophercloud.ServiceClient, domainID string) { + err := domains.Delete(client, domainID).ExtractErr() + if err != nil { + t.Fatalf("Unable to delete domain %s: %v", domainID, err) + } + + t.Logf("Deleted domain: %s", domainID) +} + +// DeleteRole will delete a role by ID. A fatal error will occur if +// the role failed to be deleted. This works best when using it as +// a deferred function. +func DeleteRole(t *testing.T, client *gophercloud.ServiceClient, roleID string) { + err := roles.Delete(client, roleID).ExtractErr() + if err != nil { + t.Fatalf("Unable to delete role %s: %v", roleID, err) + } + + t.Logf("Deleted role: %s", roleID) +} + +// UnassignRole will delete a role assigned to a user/group on a project/domain +// A fatal error will occur if it fails to delete the assignment. +// This works best when using it as a deferred function. +func UnassignRole(t *testing.T, client *gophercloud.ServiceClient, roleID string, opts *roles.UnassignOpts) { + err := roles.Unassign(client, roleID, *opts).ExtractErr() + if err != nil { + t.Fatalf("Unable to unassign a role %v on context %+v: %v", roleID, *opts, err) + } + t.Logf("Unassigned the role %v on context %+v", roleID, *opts) +} + +// FindRole finds all roles that the current authenticated client has access +// to and returns the first one found. An error will be returned if the lookup +// was unsuccessful. +func FindRole(t *testing.T, client *gophercloud.ServiceClient) (*roles.Role, error) { + t.Log("Attempting to find a role") + var role *roles.Role + + allPages, err := roles.List(client, nil).AllPages() + if err != nil { + return nil, err + } + + allRoles, err := roles.ExtractRoles(allPages) + if err != nil { + return nil, err + } + + for _, r := range allRoles { + role = &r + break + } + + t.Logf("Successfully found a role %s with ID %s", role.Name, role.ID) + + return role, nil } diff --git a/vendor/github.com/gophercloud/gophercloud/acceptance/openstack/identity/v3/roles_test.go b/vendor/github.com/gophercloud/gophercloud/acceptance/openstack/identity/v3/roles_test.go new file mode 100644 index 0000000000..be8e73f4e3 --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/acceptance/openstack/identity/v3/roles_test.go @@ -0,0 +1,328 @@ +// +build acceptance + +package v3 + +import ( + "testing" + + "github.com/gophercloud/gophercloud" + "github.com/gophercloud/gophercloud/acceptance/clients" + "github.com/gophercloud/gophercloud/acceptance/tools" + "github.com/gophercloud/gophercloud/openstack/identity/v3/domains" + "github.com/gophercloud/gophercloud/openstack/identity/v3/roles" +) + +func TestRolesList(t *testing.T) { + client, err := clients.NewIdentityV3Client() + if err != nil { + t.Fatalf("Unable to obtain an identity client: %v", err) + } + + listOpts := roles.ListOpts{ + DomainID: "default", + } + + allPages, err := roles.List(client, listOpts).AllPages() + if err != nil { + t.Fatalf("Unable to list roles: %v", err) + } + + allRoles, err := roles.ExtractRoles(allPages) + if err != nil { + t.Fatalf("Unable to extract roles: %v", err) + } + + for _, role := range allRoles { + tools.PrintResource(t, role) + } +} + +func TestRolesGet(t *testing.T) { + client, err := clients.NewIdentityV3Client() + if err != nil { + t.Fatalf("Unable to obtain an identity client: %v", err) + } + + role, err := FindRole(t, client) + if err != nil { + t.Fatalf("Unable to find a role: %v", err) + } + + p, err := roles.Get(client, role.ID).Extract() + if err != nil { + t.Fatalf("Unable to get role: %v", err) + } + + tools.PrintResource(t, p) +} + +func TestRoleCRUD(t *testing.T) { + client, err := clients.NewIdentityV3Client() + if err != nil { + t.Fatalf("Unable to obtain an identity client: %v", err) + } + + createOpts := roles.CreateOpts{ + Name: "testrole", + DomainID: "default", + Extra: map[string]interface{}{ + "description": "test role description", + }, + } + + // Create Role in the default domain + role, err := CreateRole(t, client, &createOpts) + if err != nil { + t.Fatalf("Unable to create role: %v", err) + } + defer DeleteRole(t, client, role.ID) + + tools.PrintResource(t, role) + tools.PrintResource(t, role.Extra) + + updateOpts := roles.UpdateOpts{ + Extra: map[string]interface{}{ + "description": "updated test role description", + }, + } + + newRole, err := roles.Update(client, role.ID, updateOpts).Extract() + if err != nil { + t.Fatalf("Unable to update role: %v", err) + } + + tools.PrintResource(t, newRole) + tools.PrintResource(t, newRole.Extra) +} + +func TestRoleAssignToUserOnProject(t *testing.T) { + client, err := clients.NewIdentityV3Client() + if err != nil { + t.Fatalf("Unable to obtain an indentity client: %v", err) + } + + project, err := CreateProject(t, client, nil) + if err != nil { + t.Fatal("Unable to create a project") + } + defer DeleteProject(t, client, project.ID) + + role, err := FindRole(t, client) + if err != nil { + t.Fatalf("Unable to get a role: %v", err) + } + + user, err := CreateUser(t, client, nil) + if err != nil { + t.Fatalf("Unable to create user: %v", err) + } + defer DeleteUser(t, client, user.ID) + + t.Logf("Attempting to assign a role %s to a user %s on a project %s", role.Name, user.Name, project.Name) + err = roles.Assign(client, role.ID, roles.AssignOpts{ + UserID: user.ID, + ProjectID: project.ID, + }).ExtractErr() + if err != nil { + t.Fatalf("Unable to assign a role to a user on a project: %v", err) + } + t.Logf("Successfully assigned a role %s to a user %s on a project %s", role.Name, user.Name, project.Name) + defer UnassignRole(t, client, role.ID, &roles.UnassignOpts{ + UserID: user.ID, + ProjectID: project.ID, + }) + + allPages, err := roles.ListAssignments(client, roles.ListAssignmentsOpts{ + RoleID: role.ID, + ScopeProjectID: project.ID, + UserID: user.ID, + }).AllPages() + if err != nil { + t.Fatalf("Unable to list role assignments: %v", err) + } + + allRoleAssignments, err := roles.ExtractRoleAssignments(allPages) + if err != nil { + t.Fatalf("Unable to extract role assignments: %v", err) + } + + t.Logf("Role assignments of user %s on project %s:", user.Name, project.Name) + for _, roleAssignment := range allRoleAssignments { + tools.PrintResource(t, roleAssignment) + } +} + +func TestRoleAssignToUserOnDomain(t *testing.T) { + client, err := clients.NewIdentityV3Client() + if err != nil { + t.Fatalf("Unable to obtain an indentity client: %v", err) + } + + domain, err := CreateDomain(t, client, &domains.CreateOpts{ + Enabled: gophercloud.Disabled, + }) + if err != nil { + t.Fatal("Unable to create a domain") + } + defer DeleteDomain(t, client, domain.ID) + + role, err := FindRole(t, client) + if err != nil { + t.Fatalf("Unable to get a role: %v", err) + } + + user, err := CreateUser(t, client, nil) + if err != nil { + t.Fatalf("Unable to create user: %v", err) + } + defer DeleteUser(t, client, user.ID) + + t.Logf("Attempting to assign a role %s to a user %s on a domain %s", role.Name, user.Name, domain.Name) + err = roles.Assign(client, role.ID, roles.AssignOpts{ + UserID: user.ID, + DomainID: domain.ID, + }).ExtractErr() + if err != nil { + t.Fatalf("Unable to assign a role to a user on a domain: %v", err) + } + t.Logf("Successfully assigned a role %s to a user %s on a domain %s", role.Name, user.Name, domain.Name) + defer UnassignRole(t, client, role.ID, &roles.UnassignOpts{ + UserID: user.ID, + DomainID: domain.ID, + }) + + allPages, err := roles.ListAssignments(client, roles.ListAssignmentsOpts{ + RoleID: role.ID, + ScopeDomainID: domain.ID, + UserID: user.ID, + }).AllPages() + if err != nil { + t.Fatalf("Unable to list role assignments: %v", err) + } + + allRoleAssignments, err := roles.ExtractRoleAssignments(allPages) + if err != nil { + t.Fatalf("Unable to extract role assignments: %v", err) + } + + t.Logf("Role assignments of user %s on domain %s:", user.Name, domain.Name) + for _, roleAssignment := range allRoleAssignments { + tools.PrintResource(t, roleAssignment) + } +} + +func TestRoleAssignToGroupOnDomain(t *testing.T) { + client, err := clients.NewIdentityV3Client() + if err != nil { + t.Fatalf("Unable to obtain an indentity client: %v", err) + } + + domain, err := CreateDomain(t, client, &domains.CreateOpts{ + Enabled: gophercloud.Disabled, + }) + if err != nil { + t.Fatal("Unable to create a domain") + } + defer DeleteDomain(t, client, domain.ID) + + role, err := FindRole(t, client) + if err != nil { + t.Fatalf("Unable to get a role: %v", err) + } + + group, err := CreateGroup(t, client, nil) + if err != nil { + t.Fatalf("Unable to create group: %v", err) + } + defer DeleteGroup(t, client, group.ID) + + t.Logf("Attempting to assign a role %s to a group %s on a domain %s", role.Name, group.Name, domain.Name) + err = roles.Assign(client, role.ID, roles.AssignOpts{ + GroupID: group.ID, + DomainID: domain.ID, + }).ExtractErr() + if err != nil { + t.Fatalf("Unable to assign a role to a group on a domain: %v", err) + } + t.Logf("Successfully assigned a role %s to a group %s on a domain %s", role.Name, group.Name, domain.Name) + defer UnassignRole(t, client, role.ID, &roles.UnassignOpts{ + GroupID: group.ID, + DomainID: domain.ID, + }) + + allPages, err := roles.ListAssignments(client, roles.ListAssignmentsOpts{ + RoleID: role.ID, + ScopeDomainID: domain.ID, + GroupID: group.ID, + }).AllPages() + if err != nil { + t.Fatalf("Unable to list role assignments: %v", err) + } + + allRoleAssignments, err := roles.ExtractRoleAssignments(allPages) + if err != nil { + t.Fatalf("Unable to extract role assignments: %v", err) + } + + t.Logf("Role assignments of group %s on domain %s:", group.Name, domain.Name) + for _, roleAssignment := range allRoleAssignments { + tools.PrintResource(t, roleAssignment) + } +} + +func TestRoleAssignToGroupOnProject(t *testing.T) { + client, err := clients.NewIdentityV3Client() + if err != nil { + t.Fatalf("Unable to obtain an indentity client: %v", err) + } + + project, err := CreateProject(t, client, nil) + if err != nil { + t.Fatal("Unable to create a project") + } + defer DeleteProject(t, client, project.ID) + + role, err := FindRole(t, client) + if err != nil { + t.Fatalf("Unable to get a role: %v", err) + } + + group, err := CreateGroup(t, client, nil) + if err != nil { + t.Fatalf("Unable to create group: %v", err) + } + defer DeleteGroup(t, client, group.ID) + + t.Logf("Attempting to assign a role %s to a group %s on a project %s", role.Name, group.Name, project.Name) + err = roles.Assign(client, role.ID, roles.AssignOpts{ + GroupID: group.ID, + ProjectID: project.ID, + }).ExtractErr() + if err != nil { + t.Fatalf("Unable to assign a role to a group on a project: %v", err) + } + t.Logf("Successfully assigned a role %s to a group %s on a project %s", role.Name, group.Name, project.Name) + defer UnassignRole(t, client, role.ID, &roles.UnassignOpts{ + GroupID: group.ID, + ProjectID: project.ID, + }) + + allPages, err := roles.ListAssignments(client, roles.ListAssignmentsOpts{ + RoleID: role.ID, + ScopeProjectID: project.ID, + GroupID: group.ID, + }).AllPages() + if err != nil { + t.Fatalf("Unable to list role assignments: %v", err) + } + + allRoleAssignments, err := roles.ExtractRoleAssignments(allPages) + if err != nil { + t.Fatalf("Unable to extract role assignments: %v", err) + } + + t.Logf("Role assignments of group %s on project %s:", group.Name, project.Name) + for _, roleAssignment := range allRoleAssignments { + tools.PrintResource(t, roleAssignment) + } +} diff --git a/vendor/github.com/gophercloud/gophercloud/acceptance/openstack/identity/v3/users_test.go b/vendor/github.com/gophercloud/gophercloud/acceptance/openstack/identity/v3/users_test.go index e87c88e44b..3ba1e87cf5 100644 --- a/vendor/github.com/gophercloud/gophercloud/acceptance/openstack/identity/v3/users_test.go +++ b/vendor/github.com/gophercloud/gophercloud/acceptance/openstack/identity/v3/users_test.go @@ -8,6 +8,7 @@ import ( "github.com/gophercloud/gophercloud/acceptance/clients" "github.com/gophercloud/gophercloud/acceptance/tools" "github.com/gophercloud/gophercloud/openstack/identity/v3/groups" + "github.com/gophercloud/gophercloud/openstack/identity/v3/projects" "github.com/gophercloud/gophercloud/openstack/identity/v3/users" ) @@ -154,3 +155,68 @@ func TestUsersListGroups(t *testing.T) { tools.PrintResource(t, group.Extra) } } + +func TestUsersListProjects(t *testing.T) { + client, err := clients.NewIdentityV3Client() + if err != nil { + t.Fatalf("Unable to obtain an identity client: %v", err) + } + allUserPages, err := users.List(client, nil).AllPages() + if err != nil { + t.Fatalf("Unable to list users: %v", err) + } + + allUsers, err := users.ExtractUsers(allUserPages) + if err != nil { + t.Fatalf("Unable to extract users: %v", err) + } + + user := allUsers[0] + + allProjectPages, err := users.ListProjects(client, user.ID).AllPages() + if err != nil { + t.Fatalf("Unable to list projects: %v", err) + } + + allProjects, err := projects.ExtractProjects(allProjectPages) + if err != nil { + t.Fatalf("Unable to extract projects: %v", err) + } + + for _, project := range allProjects { + tools.PrintResource(t, project) + } +} + +func TestUsersListInGroup(t *testing.T) { + client, err := clients.NewIdentityV3Client() + if err != nil { + t.Fatalf("Unable to obtain an identity client: %v", err) + } + allGroupPages, err := groups.List(client, nil).AllPages() + if err != nil { + t.Fatalf("Unable to list groups: %v", err) + } + + allGroups, err := groups.ExtractGroups(allGroupPages) + if err != nil { + t.Fatalf("Unable to extract groups: %v", err) + } + + group := allGroups[0] + + allUserPages, err := users.ListInGroup(client, group.ID, nil).AllPages() + if err != nil { + t.Fatalf("Unable to list users: %v", err) + } + + allUsers, err := users.ExtractUsers(allUserPages) + if err != nil { + t.Fatalf("Unable to extract users: %v", err) + } + + for _, user := range allUsers { + tools.PrintResource(t, user) + tools.PrintResource(t, user.Extra) + } +} diff --git a/vendor/github.com/gophercloud/gophercloud/acceptance/openstack/networking/v2/extensions/extensions.go b/vendor/github.com/gophercloud/gophercloud/acceptance/openstack/networking/v2/extensions/extensions.go index 29a31e91a5..06068322e1 100644 --- a/vendor/github.com/gophercloud/gophercloud/acceptance/openstack/networking/v2/extensions/extensions.go +++ b/vendor/github.com/gophercloud/gophercloud/acceptance/openstack/networking/v2/extensions/extensions.go @@ -28,8 +28,8 @@ func CreateExternalNetwork(t *testing.T, client *gophercloud.ServiceClient) (*ne } createOpts := external.CreateOptsExt{ - networkCreateOpts, - &isExternal, + CreateOptsBuilder: networkCreateOpts, + External: &isExternal, } network, err := networks.Create(client, createOpts).Extract() diff --git a/vendor/github.com/gophercloud/gophercloud/acceptance/openstack/networking/v2/extensions/layer3/layer3.go b/vendor/github.com/gophercloud/gophercloud/acceptance/openstack/networking/v2/extensions/layer3/layer3.go index 7bc0676d00..3d017be889 100644 --- a/vendor/github.com/gophercloud/gophercloud/acceptance/openstack/networking/v2/extensions/layer3/layer3.go +++ b/vendor/github.com/gophercloud/gophercloud/acceptance/openstack/networking/v2/extensions/layer3/layer3.go @@ -46,8 +46,10 @@ func CreateExternalRouter(t *testing.T, client *gophercloud.ServiceClient) (*rou t.Logf("Attempting to create external router: %s", routerName) adminStateUp := true + enableSNAT := false gatewayInfo := routers.GatewayInfo{ - NetworkID: choices.ExternalNetworkID, + NetworkID: choices.ExternalNetworkID, + EnableSNAT: &enableSNAT, } createOpts := routers.CreateOpts{ diff --git a/vendor/github.com/gophercloud/gophercloud/acceptance/openstack/networking/v2/extensions/layer3/routers_test.go b/vendor/github.com/gophercloud/gophercloud/acceptance/openstack/networking/v2/extensions/layer3/routers_test.go index 06194af223..4be922e9f5 100644 --- a/vendor/github.com/gophercloud/gophercloud/acceptance/openstack/networking/v2/extensions/layer3/routers_test.go +++ b/vendor/github.com/gophercloud/gophercloud/acceptance/openstack/networking/v2/extensions/layer3/routers_test.go @@ -34,7 +34,7 @@ func TestLayer3RouterList(t *testing.T) { } } -func TestLayer3RouterCreateDelete(t *testing.T) { +func TestLayer3ExternalRouterCreateDelete(t *testing.T) { client, err := clients.NewNetworkV2Client() if err != nil { t.Fatalf("Unable to create a network client: %v", err) diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/blockstorage/extensions/volumetenants/BUILD.bazel b/vendor/github.com/gophercloud/gophercloud/openstack/blockstorage/extensions/volumetenants/BUILD.bazel index 8d95a1e686..c4a1d2ee0a 100644 --- a/vendor/github.com/gophercloud/gophercloud/openstack/blockstorage/extensions/volumetenants/BUILD.bazel +++ b/vendor/github.com/gophercloud/gophercloud/openstack/blockstorage/extensions/volumetenants/BUILD.bazel @@ -2,6 +2,9 @@ load("@io_bazel_rules_go//go:def.bzl", "go_library") go_library( name = "go_default_library", - srcs = ["results.go"], + srcs = [ + "doc.go", + "results.go", + ], visibility = ["//visibility:public"], ) diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/blockstorage/extensions/volumetenants/doc.go b/vendor/github.com/gophercloud/gophercloud/openstack/blockstorage/extensions/volumetenants/doc.go new file mode 100644 index 0000000000..2f501649fe --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/openstack/blockstorage/extensions/volumetenants/doc.go @@ -0,0 +1,26 @@ +/* +Package volumetenants provides the ability to extend a volume result with +tenant/project information. Example: + + type VolumeWithTenant struct { + volumes.Volume + volumetenants.VolumeTenantExt + } + + var allVolumes []VolumeWithTenant + + allPages, err := volumes.List(client, nil).AllPages() + if err != nil { + panic("Unable to retrieve volumes: %s", err) + } + + err = volumes.ExtractVolumesInto(allPages, &allVolumes) + if err != nil { + panic("Unable to extract volumes: %s", err) + } + + for _, volume := range allVolumes { + fmt.Println(volume.TenantID) + } +*/ +package volumetenants diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/blockstorage/extensions/volumetenants/results.go b/vendor/github.com/gophercloud/gophercloud/openstack/blockstorage/extensions/volumetenants/results.go index b7d51c72b7..821e523b7f 100644 --- a/vendor/github.com/gophercloud/gophercloud/openstack/blockstorage/extensions/volumetenants/results.go +++ b/vendor/github.com/gophercloud/gophercloud/openstack/blockstorage/extensions/volumetenants/results.go @@ -1,12 +1,7 @@ package volumetenants -// VolumeExt is an extension to the base Volume object -type VolumeExt struct { +// VolumeTenantExt is an extension to the base Volume object +type VolumeTenantExt struct { // TenantID is the id of the project that owns the volume. TenantID string `json:"os-vol-tenant-attr:tenant_id"` } - -// UnmarshalJSON to override default -func (r *VolumeExt) UnmarshalJSON(b []byte) error { - return nil -} diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/blockstorage/noauth/BUILD.bazel b/vendor/github.com/gophercloud/gophercloud/openstack/blockstorage/noauth/BUILD.bazel new file mode 100644 index 0000000000..1fc1b74dae --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/openstack/blockstorage/noauth/BUILD.bazel @@ -0,0 +1,11 @@ +load("@io_bazel_rules_go//go:def.bzl", "go_library") + +go_library( + name = "go_default_library", + srcs = [ + "doc.go", + "requests.go", + ], + visibility = ["//visibility:public"], + deps = ["//vendor/github.com/gophercloud/gophercloud:go_default_library"], +) diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/blockstorage/noauth/doc.go b/vendor/github.com/gophercloud/gophercloud/openstack/blockstorage/noauth/doc.go new file mode 100644 index 0000000000..204c78fee8 --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/openstack/blockstorage/noauth/doc.go @@ -0,0 +1,17 @@ +/* +Package noauth creates a "noauth" *gophercloud.ServiceClient for use in Cinder +environments configured with the noauth authentication middleware. + +Example of Creating a noauth Service Client + + provider, err := noauth.NewClient(gophercloud.AuthOptions{ + Username: os.Getenv("OS_USERNAME"), + TenantName: os.Getenv("OS_TENANT_NAME"), + }) + client, err := noauth.NewBlockStorageV2(provider, noauth.EndpointOpts{ + CinderEndpoint: os.Getenv("CINDER_ENDPOINT"), + }) + + An example of a CinderEndpoint would be: http://example.com:8776/v2, +*/ +package noauth diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/blockstorage/noauth/requests.go b/vendor/github.com/gophercloud/gophercloud/openstack/blockstorage/noauth/requests.go new file mode 100644 index 0000000000..81c084c9b8 --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/openstack/blockstorage/noauth/requests.go @@ -0,0 +1,55 @@ +package noauth + +import ( + "fmt" + "strings" + + "github.com/gophercloud/gophercloud" +) + +// EndpointOpts specifies a "noauth" Cinder Endpoint. +type EndpointOpts struct { + // CinderEndpoint [required] is currently only used with "noauth" Cinder. + // A cinder endpoint with "auth_strategy=noauth" is necessary, for example: + // http://example.com:8776/v2. + CinderEndpoint string +} + +// NewClient prepares an unauthenticated ProviderClient instance. +func NewClient(options gophercloud.AuthOptions) (*gophercloud.ProviderClient, error) { + if options.Username == "" { + options.Username = "admin" + } + if options.TenantName == "" { + options.TenantName = "admin" + } + + client := &gophercloud.ProviderClient{ + TokenID: fmt.Sprintf("%s:%s", options.Username, options.TenantName), + } + + return client, nil +} + +func initClientOpts(client *gophercloud.ProviderClient, eo EndpointOpts) (*gophercloud.ServiceClient, error) { + sc := new(gophercloud.ServiceClient) + if eo.CinderEndpoint == "" { + return nil, fmt.Errorf("CinderEndpoint is required") + } + + token := strings.Split(client.TokenID, ":") + if len(token) != 2 { + return nil, fmt.Errorf("Malformed noauth token") + } + + endpoint := fmt.Sprintf("%s%s", gophercloud.NormalizeURL(eo.CinderEndpoint), token[1]) + sc.Endpoint = gophercloud.NormalizeURL(endpoint) + sc.ProviderClient = client + return sc, nil +} + +// NewBlockStorageV2 creates a ServiceClient that may be used to access a +// "noauth" block storage service. +func NewBlockStorageV2(client *gophercloud.ProviderClient, eo EndpointOpts) (*gophercloud.ServiceClient, error) { + return initClientOpts(client, eo) +} diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/blockstorage/noauth/testing/BUILD.bazel b/vendor/github.com/gophercloud/gophercloud/openstack/blockstorage/noauth/testing/BUILD.bazel new file mode 100644 index 0000000000..d1fd4abb3e --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/openstack/blockstorage/noauth/testing/BUILD.bazel @@ -0,0 +1,21 @@ +load("@io_bazel_rules_go//go:def.bzl", "go_library", "go_test") + +go_library( + name = "go_default_library", + srcs = [ + "doc.go", + "fixtures.go", + ], + visibility = ["//visibility:public"], +) + +go_test( + name = "go_default_test", + srcs = ["requests_test.go"], + library = ":go_default_library", + deps = [ + "//vendor/github.com/gophercloud/gophercloud:go_default_library", + "//vendor/github.com/gophercloud/gophercloud/openstack/blockstorage/noauth:go_default_library", + "//vendor/github.com/gophercloud/gophercloud/testhelper:go_default_library", + ], +) diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/blockstorage/noauth/testing/doc.go b/vendor/github.com/gophercloud/gophercloud/openstack/blockstorage/noauth/testing/doc.go new file mode 100644 index 0000000000..425ab60553 --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/openstack/blockstorage/noauth/testing/doc.go @@ -0,0 +1,2 @@ +// noauth unit tests +package testing diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/blockstorage/noauth/testing/fixtures.go b/vendor/github.com/gophercloud/gophercloud/openstack/blockstorage/noauth/testing/fixtures.go new file mode 100644 index 0000000000..f78bda3c5f --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/openstack/blockstorage/noauth/testing/fixtures.go @@ -0,0 +1,19 @@ +package testing + +// NoAuthResult is the expected result of the noauth Service Client +type NoAuthResult struct { + TokenID string + Endpoint string +} + +var naTestResult = NoAuthResult{ + TokenID: "user:test", + Endpoint: "http://cinder:8776/v2/test/", +} + +var naResult = NoAuthResult{ + TokenID: "admin:admin", + Endpoint: "http://cinder:8776/v2/admin/", +} + +var errorResult = "CinderEndpoint is required" diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/blockstorage/noauth/testing/requests_test.go b/vendor/github.com/gophercloud/gophercloud/openstack/blockstorage/noauth/testing/requests_test.go new file mode 100644 index 0000000000..492b639f33 --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/openstack/blockstorage/noauth/testing/requests_test.go @@ -0,0 +1,38 @@ +package testing + +import ( + "testing" + + "github.com/gophercloud/gophercloud" + "github.com/gophercloud/gophercloud/openstack/blockstorage/noauth" + th "github.com/gophercloud/gophercloud/testhelper" +) + +func TestNoAuth(t *testing.T) { + ao := gophercloud.AuthOptions{ + Username: "user", + TenantName: "test", + } + provider, err := noauth.NewClient(ao) + th.AssertNoErr(t, err) + noauthClient, err := noauth.NewBlockStorageV2(provider, noauth.EndpointOpts{ + CinderEndpoint: "http://cinder:8776/v2", + }) + th.AssertNoErr(t, err) + th.AssertEquals(t, naTestResult.Endpoint, noauthClient.Endpoint) + th.AssertEquals(t, naTestResult.TokenID, noauthClient.TokenID) + + ao2 := gophercloud.AuthOptions{} + provider2, err := noauth.NewClient(ao2) + th.AssertNoErr(t, err) + noauthClient2, err := noauth.NewBlockStorageV2(provider2, noauth.EndpointOpts{ + CinderEndpoint: "http://cinder:8776/v2/", + }) + th.AssertNoErr(t, err) + th.AssertEquals(t, naResult.Endpoint, noauthClient2.Endpoint) + th.AssertEquals(t, naResult.TokenID, noauthClient2.TokenID) + + errTest, err := noauth.NewBlockStorageV2(provider2, noauth.EndpointOpts{}) + _ = errTest + th.AssertEquals(t, errorResult, err.Error()) +} diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/blockstorage/v2/volumes/testing/requests_test.go b/vendor/github.com/gophercloud/gophercloud/openstack/blockstorage/v2/volumes/testing/requests_test.go index 0a18544850..cd7dcf5ecb 100644 --- a/vendor/github.com/gophercloud/gophercloud/openstack/blockstorage/v2/volumes/testing/requests_test.go +++ b/vendor/github.com/gophercloud/gophercloud/openstack/blockstorage/v2/volumes/testing/requests_test.go @@ -102,7 +102,7 @@ func TestListAllWithExtensions(t *testing.T) { type VolumeWithExt struct { volumes.Volume - volumetenants.VolumeExt + volumetenants.VolumeTenantExt } allPages, err := volumes.List(client.ServiceClient(), &volumes.ListOpts{}).AllPages() @@ -244,7 +244,7 @@ func TestGetWithExtensions(t *testing.T) { var s struct { volumes.Volume - volumetenants.VolumeExt + volumetenants.VolumeTenantExt } err := volumes.Get(client.ServiceClient(), "d32019d3-bc6e-4319-9c1d-6722fc136a22").ExtractInto(&s) th.AssertNoErr(t, err) diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/client.go b/vendor/github.com/gophercloud/gophercloud/openstack/client.go index e1c699fcbf..77214caca3 100644 --- a/vendor/github.com/gophercloud/gophercloud/openstack/client.go +++ b/vendor/github.com/gophercloud/gophercloud/openstack/client.go @@ -4,6 +4,8 @@ import ( "fmt" "net/url" "reflect" + "regexp" + "strings" "github.com/gophercloud/gophercloud" tokens2 "github.com/gophercloud/gophercloud/openstack/identity/v2/tokens" @@ -12,8 +14,13 @@ import ( ) const ( - v20 = "v2.0" - v30 = "v3.0" + // v2 represents Keystone v2. + // It should never increase beyond 2.0. + v2 = "v2.0" + + // v3 represents Keystone v3. + // The version can be anything from v3 to v3.x. + v3 = "v3" ) /* @@ -35,24 +42,25 @@ func NewClient(endpoint string) (*gophercloud.ProviderClient, error) { if err != nil { return nil, err } - hadPath := u.Path != "" - u.Path, u.RawQuery, u.Fragment = "", "", "" - base := u.String() + + u.RawQuery, u.Fragment = "", "" + + var base string + versionRe := regexp.MustCompile("v[0-9.]+/?") + if version := versionRe.FindString(u.Path); version != "" { + base = strings.Replace(u.String(), version, "", -1) + } else { + base = u.String() + } endpoint = gophercloud.NormalizeURL(endpoint) base = gophercloud.NormalizeURL(base) - if hadPath { - return &gophercloud.ProviderClient{ - IdentityBase: base, - IdentityEndpoint: endpoint, - }, nil - } - return &gophercloud.ProviderClient{ IdentityBase: base, - IdentityEndpoint: "", + IdentityEndpoint: endpoint, }, nil + } /* @@ -92,8 +100,8 @@ func AuthenticatedClient(options gophercloud.AuthOptions) (*gophercloud.Provider // supported at the provided endpoint. func Authenticate(client *gophercloud.ProviderClient, options gophercloud.AuthOptions) error { versions := []*utils.Version{ - {ID: v20, Priority: 20, Suffix: "/v2.0/"}, - {ID: v30, Priority: 30, Suffix: "/v3/"}, + {ID: v2, Priority: 20, Suffix: "/v2.0/"}, + {ID: v3, Priority: 30, Suffix: "/v3/"}, } chosen, endpoint, err := utils.ChooseVersion(client, versions) @@ -102,9 +110,9 @@ func Authenticate(client *gophercloud.ProviderClient, options gophercloud.AuthOp } switch chosen.ID { - case v20: + case v2: return v2auth(client, endpoint, options, gophercloud.EndpointOpts{}) - case v30: + case v3: return v3auth(client, endpoint, &options, gophercloud.EndpointOpts{}) default: // The switch statement must be out of date from the versions list. @@ -241,6 +249,13 @@ func NewIdentityV3(client *gophercloud.ProviderClient, eo gophercloud.EndpointOp } } + // Ensure endpoint still has a suffix of v3. + // This is because EndpointLocator might have found a versionless + // endpoint and requests will fail unless targeted at /v3. + if !strings.HasSuffix(endpoint, "v3/") { + endpoint = endpoint + "v3/" + } + return &gophercloud.ServiceClient{ ProviderClient: client, Endpoint: endpoint, diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/defsecrules/requests.go b/vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/defsecrules/requests.go index 1888cb94b7..b63a5d5d63 100644 --- a/vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/defsecrules/requests.go +++ b/vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/defsecrules/requests.go @@ -69,7 +69,7 @@ func Get(client *gophercloud.ServiceClient, id string) (r GetResult) { } // Delete will permanently delete a rule the project's default security group. -func Delete(client *gophercloud.ServiceClient, id string) (r gophercloud.ErrResult) { +func Delete(client *gophercloud.ServiceClient, id string) (r DeleteResult) { _, r.Err = client.Delete(resourceURL(client, id), nil) return } diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/defsecrules/results.go b/vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/defsecrules/results.go index c6c484264b..98c18fe560 100644 --- a/vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/defsecrules/results.go +++ b/vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/defsecrules/results.go @@ -65,3 +65,9 @@ func (r commonResult) Extract() (*DefaultRule, error) { err := r.ExtractInto(&s) return &s.DefaultRule, err } + +// DeleteResult is the response from a delete operation. Call its ExtractErr +// method to determine if the request succeeded or failed. +type DeleteResult struct { + gophercloud.ErrResult +} diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/evacuate/BUILD.bazel b/vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/evacuate/BUILD.bazel new file mode 100644 index 0000000000..1f6e1512b5 --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/evacuate/BUILD.bazel @@ -0,0 +1,13 @@ +load("@io_bazel_rules_go//go:def.bzl", "go_library") + +go_library( + name = "go_default_library", + srcs = [ + "doc.go", + "requests.go", + "results.go", + "urls.go", + ], + visibility = ["//visibility:public"], + deps = ["//vendor/github.com/gophercloud/gophercloud:go_default_library"], +) diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/evacuate/doc.go b/vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/evacuate/doc.go new file mode 100644 index 0000000000..faafe7c313 --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/evacuate/doc.go @@ -0,0 +1,13 @@ +/* +Package evacuate provides functionality to evacuates servers that have been +provisioned by the OpenStack Compute service from a failed host to a new host. + +Example to Evacuate a Server from a Host + + serverID := "b16ba811-199d-4ffd-8839-ba96c1185a67" + err := evacuate.Evacuate(computeClient, serverID, evacuate.EvacuateOpts{}).ExtractErr() + if err != nil { + panic(err) + } +*/ +package evacuate diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/evacuate/requests.go b/vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/evacuate/requests.go new file mode 100644 index 0000000000..3ea7af4642 --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/evacuate/requests.go @@ -0,0 +1,41 @@ +package evacuate + +import ( + "github.com/gophercloud/gophercloud" +) + +// EvacuateOptsBuilder allows extensions to add additional parameters to the +// the Evacuate request. +type EvacuateOptsBuilder interface { + ToEvacuateMap() (map[string]interface{}, error) +} + +// EvacuateOpts specifies Evacuate action parameters. +type EvacuateOpts struct { + // The name of the host to which the server is evacuated + Host string `json:"host,omitempty"` + + // Indicates whether server is on shared storage + OnSharedStorage bool `json:"onSharedStorage"` + + // An administrative password to access the evacuated server + AdminPass string `json:"adminPass,omitempty"` +} + +// ToServerGroupCreateMap constructs a request body from CreateOpts. +func (opts EvacuateOpts) ToEvacuateMap() (map[string]interface{}, error) { + return gophercloud.BuildRequestBody(opts, "evacuate") +} + +// Evacuate will Evacuate a failed instance to another host. +func Evacuate(client *gophercloud.ServiceClient, id string, opts EvacuateOptsBuilder) (r EvacuateResult) { + b, err := opts.ToEvacuateMap() + if err != nil { + r.Err = err + return + } + _, r.Err = client.Post(actionURL(client, id), b, &r.Body, &gophercloud.RequestOpts{ + OkCodes: []int{200}, + }) + return +} diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/evacuate/results.go b/vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/evacuate/results.go new file mode 100644 index 0000000000..8342cb43d0 --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/evacuate/results.go @@ -0,0 +1,23 @@ +package evacuate + +import ( + "github.com/gophercloud/gophercloud" +) + +// EvacuateResult is the response from an Evacuate operation. +//Call its ExtractAdminPass method to retrieve the admin password of the instance. +//The admin password will be an empty string if the cloud is not configured to inject admin passwords.. +type EvacuateResult struct { + gophercloud.Result +} + +func (r EvacuateResult) ExtractAdminPass() (string, error) { + var s struct { + AdminPass string `json:"adminPass"` + } + err := r.ExtractInto(&s) + if err != nil && err.Error() == "EOF" { + return "", nil + } + return s.AdminPass, err +} diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/evacuate/testing/BUILD.bazel b/vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/evacuate/testing/BUILD.bazel new file mode 100644 index 0000000000..7dd0c8e1de --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/evacuate/testing/BUILD.bazel @@ -0,0 +1,25 @@ +load("@io_bazel_rules_go//go:def.bzl", "go_library", "go_test") + +go_library( + name = "go_default_library", + srcs = [ + "doc.go", + "fixtures.go", + ], + visibility = ["//visibility:public"], + deps = [ + "//vendor/github.com/gophercloud/gophercloud/testhelper:go_default_library", + "//vendor/github.com/gophercloud/gophercloud/testhelper/client:go_default_library", + ], +) + +go_test( + name = "go_default_test", + srcs = ["requests_test.go"], + library = ":go_default_library", + deps = [ + "//vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/evacuate:go_default_library", + "//vendor/github.com/gophercloud/gophercloud/testhelper:go_default_library", + "//vendor/github.com/gophercloud/gophercloud/testhelper/client:go_default_library", + ], +) diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/evacuate/testing/doc.go b/vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/evacuate/testing/doc.go new file mode 100644 index 0000000000..613ac1d4b6 --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/evacuate/testing/doc.go @@ -0,0 +1,2 @@ +// compute_extensions_evacuate_v2 +package testing diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/evacuate/testing/fixtures.go b/vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/evacuate/testing/fixtures.go new file mode 100644 index 0000000000..e078d1019d --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/evacuate/testing/fixtures.go @@ -0,0 +1,83 @@ +package testing + +import ( + "fmt" + "net/http" + "testing" + + th "github.com/gophercloud/gophercloud/testhelper" + "github.com/gophercloud/gophercloud/testhelper/client" +) + +func mockEvacuateResponse(t *testing.T, id string) { + th.Mux.HandleFunc("/servers/"+id+"/action", func(w http.ResponseWriter, r *http.Request) { + th.TestMethod(t, r, "POST") + th.TestHeader(t, r, "X-Auth-Token", client.TokenID) + th.TestJSONRequest(t, r, ` + { + "evacuate": { + "adminPass": "MySecretPass", + "host": "derp", + "onSharedStorage": false + } + + } + `) + w.WriteHeader(http.StatusOK) + }) +} + +func mockEvacuateResponseWithHost(t *testing.T, id string) { + th.Mux.HandleFunc("/servers/"+id+"/action", func(w http.ResponseWriter, r *http.Request) { + th.TestMethod(t, r, "POST") + th.TestHeader(t, r, "X-Auth-Token", client.TokenID) + th.TestJSONRequest(t, r, ` + { + "evacuate": { + "host": "derp", + "onSharedStorage": false + } + + } + `) + w.WriteHeader(http.StatusOK) + }) +} + +func mockEvacuateResponseWithNoOpts(t *testing.T, id string) { + th.Mux.HandleFunc("/servers/"+id+"/action", func(w http.ResponseWriter, r *http.Request) { + th.TestMethod(t, r, "POST") + th.TestHeader(t, r, "X-Auth-Token", client.TokenID) + th.TestJSONRequest(t, r, ` + { + "evacuate": { + "onSharedStorage": false + } + + } + `) + w.WriteHeader(http.StatusOK) + }) +} + +const EvacuateResponse = ` +{ + "adminPass": "MySecretPass" +} +` + +func mockEvacuateAdminpassResponse(t *testing.T, id string) { + th.Mux.HandleFunc("/servers/"+id+"/action", func(w http.ResponseWriter, r *http.Request) { + th.TestMethod(t, r, "POST") + th.TestHeader(t, r, "X-Auth-Token", client.TokenID) + th.TestJSONRequest(t, r, ` + { + "evacuate": { + "onSharedStorage": false + } + } + `) + w.Header().Add("Content-Type", "application/json") + fmt.Fprintf(w, EvacuateResponse) + }) +} diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/evacuate/testing/requests_test.go b/vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/evacuate/testing/requests_test.go new file mode 100644 index 0000000000..aec03114b8 --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/evacuate/testing/requests_test.go @@ -0,0 +1,60 @@ +package testing + +import ( + "testing" + + "github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/evacuate" + th "github.com/gophercloud/gophercloud/testhelper" + "github.com/gophercloud/gophercloud/testhelper/client" +) + +func TestEvacuate(t *testing.T) { + const serverID = "b16ba811-199d-4ffd-8839-ba96c1185a67" + th.SetupHTTP() + defer th.TeardownHTTP() + + mockEvacuateResponse(t, serverID) + + _, err := evacuate.Evacuate(client.ServiceClient(), serverID, evacuate.EvacuateOpts{ + Host: "derp", + AdminPass: "MySecretPass", + OnSharedStorage: false, + }).ExtractAdminPass() + th.AssertNoErr(t, err) +} + +func TestEvacuateWithHost(t *testing.T) { + const serverID = "b16ba811-199d-4ffd-8839-ba96c1185a67" + th.SetupHTTP() + defer th.TeardownHTTP() + + mockEvacuateResponseWithHost(t, serverID) + + _, err := evacuate.Evacuate(client.ServiceClient(), serverID, evacuate.EvacuateOpts{ + Host: "derp", + }).ExtractAdminPass() + th.AssertNoErr(t, err) +} + +func TestEvacuateWithNoOpts(t *testing.T) { + const serverID = "b16ba811-199d-4ffd-8839-ba96c1185a67" + th.SetupHTTP() + defer th.TeardownHTTP() + + mockEvacuateResponseWithNoOpts(t, serverID) + + _, err := evacuate.Evacuate(client.ServiceClient(), serverID, evacuate.EvacuateOpts{}).ExtractAdminPass() + th.AssertNoErr(t, err) +} + +func TestEvacuateAdminpassResponse(t *testing.T) { + const serverID = "b16ba811-199d-4ffd-8839-ba96c1185a67" + th.SetupHTTP() + defer th.TeardownHTTP() + + mockEvacuateAdminpassResponse(t, serverID) + + actual, err := evacuate.Evacuate(client.ServiceClient(), serverID, evacuate.EvacuateOpts{}).ExtractAdminPass() + th.CheckEquals(t, "MySecretPass", actual) + th.AssertNoErr(t, err) +} diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/evacuate/urls.go b/vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/evacuate/urls.go new file mode 100644 index 0000000000..a8896aea0b --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/evacuate/urls.go @@ -0,0 +1,9 @@ +package evacuate + +import ( + "github.com/gophercloud/gophercloud" +) + +func actionURL(client *gophercloud.ServiceClient, id string) string { + return client.ServiceURL("servers", id, "action") +} diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/lockunlock/BUILD.bazel b/vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/lockunlock/BUILD.bazel new file mode 100644 index 0000000000..418e5617f8 --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/lockunlock/BUILD.bazel @@ -0,0 +1,12 @@ +load("@io_bazel_rules_go//go:def.bzl", "go_library") + +go_library( + name = "go_default_library", + srcs = [ + "doc.go", + "requests.go", + "results.go", + ], + visibility = ["//visibility:public"], + deps = ["//vendor/github.com/gophercloud/gophercloud:go_default_library"], +) diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/lockunlock/doc.go b/vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/lockunlock/doc.go new file mode 100644 index 0000000000..ac51a36f61 --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/lockunlock/doc.go @@ -0,0 +1,19 @@ +/* +Package lockunlock provides functionality to lock and unlock servers that +have been provisioned by the OpenStack Compute service. + +Example to Lock and Unlock a Server + + serverID := "47b6b7b7-568d-40e4-868c-d5c41735532e" + + err := lockunlock.Lock(computeClient, serverID).ExtractErr() + if err != nil { + panic(err) + } + + err = lockunlock.Unlock(computeClient, serverID).ExtractErr() + if err != nil { + panic(err) + } +*/ +package lockunlock diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/lockunlock/requests.go b/vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/lockunlock/requests.go new file mode 100644 index 0000000000..5243d0fba5 --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/lockunlock/requests.go @@ -0,0 +1,19 @@ +package lockunlock + +import "github.com/gophercloud/gophercloud" + +func actionURL(client *gophercloud.ServiceClient, id string) string { + return client.ServiceURL("servers", id, "action") +} + +// Lock is the operation responsible for locking a Compute server. +func Lock(client *gophercloud.ServiceClient, id string) (r LockResult) { + _, r.Err = client.Post(actionURL(client, id), map[string]interface{}{"lock": nil}, nil, nil) + return +} + +// Unlock is the operation responsible for unlocking a Compute server. +func Unlock(client *gophercloud.ServiceClient, id string) (r UnlockResult) { + _, r.Err = client.Post(actionURL(client, id), map[string]interface{}{"unlock": nil}, nil, nil) + return +} diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/lockunlock/results.go b/vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/lockunlock/results.go new file mode 100644 index 0000000000..282bc8a0eb --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/lockunlock/results.go @@ -0,0 +1,16 @@ +package lockunlock + +import ( + "github.com/gophercloud/gophercloud" +) + +// LockResult and UnlockResult are the responses from a Lock and Unlock +// operations respectively. Call their ExtractErr methods to determine if the +// requests suceeded or failed. +type LockResult struct { + gophercloud.ErrResult +} + +type UnlockResult struct { + gophercloud.ErrResult +} diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/lockunlock/testing/BUILD.bazel b/vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/lockunlock/testing/BUILD.bazel new file mode 100644 index 0000000000..c71e504936 --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/lockunlock/testing/BUILD.bazel @@ -0,0 +1,25 @@ +load("@io_bazel_rules_go//go:def.bzl", "go_library", "go_test") + +go_library( + name = "go_default_library", + srcs = [ + "doc.go", + "fixtures.go", + ], + visibility = ["//visibility:public"], + deps = [ + "//vendor/github.com/gophercloud/gophercloud/testhelper:go_default_library", + "//vendor/github.com/gophercloud/gophercloud/testhelper/client:go_default_library", + ], +) + +go_test( + name = "go_default_test", + srcs = ["request_test.go"], + library = ":go_default_library", + deps = [ + "//vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/lockunlock:go_default_library", + "//vendor/github.com/gophercloud/gophercloud/testhelper:go_default_library", + "//vendor/github.com/gophercloud/gophercloud/testhelper/client:go_default_library", + ], +) diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/lockunlock/testing/doc.go b/vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/lockunlock/testing/doc.go new file mode 100644 index 0000000000..59cb9be471 --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/lockunlock/testing/doc.go @@ -0,0 +1,2 @@ +// unlocklock unit tests +package testing diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/lockunlock/testing/fixtures.go b/vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/lockunlock/testing/fixtures.go new file mode 100644 index 0000000000..ec79b75321 --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/lockunlock/testing/fixtures.go @@ -0,0 +1,27 @@ +package testing + +import ( + "net/http" + "testing" + + th "github.com/gophercloud/gophercloud/testhelper" + "github.com/gophercloud/gophercloud/testhelper/client" +) + +func mockStartServerResponse(t *testing.T, id string) { + th.Mux.HandleFunc("/servers/"+id+"/action", func(w http.ResponseWriter, r *http.Request) { + th.TestMethod(t, r, "POST") + th.TestHeader(t, r, "X-Auth-Token", client.TokenID) + th.TestJSONRequest(t, r, `{"lock": null}`) + w.WriteHeader(http.StatusAccepted) + }) +} + +func mockStopServerResponse(t *testing.T, id string) { + th.Mux.HandleFunc("/servers/"+id+"/action", func(w http.ResponseWriter, r *http.Request) { + th.TestMethod(t, r, "POST") + th.TestHeader(t, r, "X-Auth-Token", client.TokenID) + th.TestJSONRequest(t, r, `{"unlock": null}`) + w.WriteHeader(http.StatusAccepted) + }) +} diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/lockunlock/testing/request_test.go b/vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/lockunlock/testing/request_test.go new file mode 100644 index 0000000000..cb2906d27b --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/lockunlock/testing/request_test.go @@ -0,0 +1,31 @@ +package testing + +import ( + "testing" + + "github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/lockunlock" + th "github.com/gophercloud/gophercloud/testhelper" + "github.com/gophercloud/gophercloud/testhelper/client" +) + +const serverID = "{serverId}" + +func TestLock(t *testing.T) { + th.SetupHTTP() + defer th.TeardownHTTP() + + mockStartServerResponse(t, serverID) + + err := lockunlock.Lock(client.ServiceClient(), serverID).ExtractErr() + th.AssertNoErr(t, err) +} + +func TestUnlock(t *testing.T) { + th.SetupHTTP() + defer th.TeardownHTTP() + + mockStopServerResponse(t, serverID) + + err := lockunlock.Unlock(client.ServiceClient(), serverID).ExtractErr() + th.AssertNoErr(t, err) +} diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/migrate/results.go b/vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/migrate/results.go index 62997b7f5d..ebccf56ac3 100644 --- a/vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/migrate/results.go +++ b/vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/migrate/results.go @@ -5,7 +5,7 @@ import ( ) // MigrateResult is the response from a Migrate operation. Call its ExtractErr -// method to determine if the suceeded or failed. +// method to determine if the request suceeded or failed. type MigrateResult struct { gophercloud.ErrResult } diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/pauseunpause/BUILD.bazel b/vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/pauseunpause/BUILD.bazel index 1fc1b74dae..418e5617f8 100644 --- a/vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/pauseunpause/BUILD.bazel +++ b/vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/pauseunpause/BUILD.bazel @@ -5,6 +5,7 @@ go_library( srcs = [ "doc.go", "requests.go", + "results.go", ], visibility = ["//visibility:public"], deps = ["//vendor/github.com/gophercloud/gophercloud:go_default_library"], diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/pauseunpause/requests.go b/vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/pauseunpause/requests.go index a9e02d99e6..aeb880343f 100644 --- a/vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/pauseunpause/requests.go +++ b/vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/pauseunpause/requests.go @@ -7,13 +7,13 @@ func actionURL(client *gophercloud.ServiceClient, id string) string { } // Pause is the operation responsible for pausing a Compute server. -func Pause(client *gophercloud.ServiceClient, id string) (r gophercloud.ErrResult) { +func Pause(client *gophercloud.ServiceClient, id string) (r PauseResult) { _, r.Err = client.Post(actionURL(client, id), map[string]interface{}{"pause": nil}, nil, nil) return } // Unpause is the operation responsible for unpausing a Compute server. -func Unpause(client *gophercloud.ServiceClient, id string) (r gophercloud.ErrResult) { +func Unpause(client *gophercloud.ServiceClient, id string) (r UnpauseResult) { _, r.Err = client.Post(actionURL(client, id), map[string]interface{}{"unpause": nil}, nil, nil) return } diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/pauseunpause/results.go b/vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/pauseunpause/results.go new file mode 100644 index 0000000000..3cb91d981a --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/pauseunpause/results.go @@ -0,0 +1,15 @@ +package pauseunpause + +import "github.com/gophercloud/gophercloud" + +// PauseResult is the response from a Pause operation. Call its ExtractErr +// method to determine if the request succeeded or failed. +type PauseResult struct { + gophercloud.ErrResult +} + +// UnpauseResult is the response from an Unpause operation. Call its ExtractErr +// method to determine if the request succeeded or failed. +type UnpauseResult struct { + gophercloud.ErrResult +} diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/resetstate/BUILD.bazel b/vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/resetstate/BUILD.bazel new file mode 100644 index 0000000000..1f6e1512b5 --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/resetstate/BUILD.bazel @@ -0,0 +1,13 @@ +load("@io_bazel_rules_go//go:def.bzl", "go_library") + +go_library( + name = "go_default_library", + srcs = [ + "doc.go", + "requests.go", + "results.go", + "urls.go", + ], + visibility = ["//visibility:public"], + deps = ["//vendor/github.com/gophercloud/gophercloud:go_default_library"], +) diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/resetstate/doc.go b/vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/resetstate/doc.go new file mode 100644 index 0000000000..00f004b022 --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/resetstate/doc.go @@ -0,0 +1,13 @@ +/* +Package resetstate provides functionality to reset the state of a server that has +been provisioned by the OpenStack Compute service. + +Example to Reset a Server + + serverID := "47b6b7b7-568d-40e4-868c-d5c41735532e" + err := resetstate.ResetState(client, id, resetstate.StateActive).ExtractErr() + if err != nil { + panic(err) + } +*/ +package resetstate diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/resetstate/requests.go b/vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/resetstate/requests.go new file mode 100644 index 0000000000..628e44aa47 --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/resetstate/requests.go @@ -0,0 +1,23 @@ +package resetstate + +import ( + "github.com/gophercloud/gophercloud" +) + +// ServerState refers to the states usable in ResetState Action +type ServerState string + +const ( + // StateActive returns the state of the server as active + StateActive ServerState = "active" + + // StateError returns the state of the server as error + StateError ServerState = "error" +) + +// ResetState will reset the state of a server +func ResetState(client *gophercloud.ServiceClient, id string, state ServerState) (r ResetResult) { + stateMap := map[string]interface{}{"state": state} + _, r.Err = client.Post(actionURL(client, id), map[string]interface{}{"os-resetState": stateMap}, nil, nil) + return +} diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/resetstate/results.go b/vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/resetstate/results.go new file mode 100644 index 0000000000..ddeb3519a1 --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/resetstate/results.go @@ -0,0 +1,11 @@ +package resetstate + +import ( + "github.com/gophercloud/gophercloud" +) + +// ResetResult is the response of a ResetState operation. Call its ExtractErr +// method to determine if the request suceeded or failed. +type ResetResult struct { + gophercloud.ErrResult +} diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/resetstate/testing/BUILD.bazel b/vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/resetstate/testing/BUILD.bazel new file mode 100644 index 0000000000..7d9a4bcf0b --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/resetstate/testing/BUILD.bazel @@ -0,0 +1,25 @@ +load("@io_bazel_rules_go//go:def.bzl", "go_library", "go_test") + +go_library( + name = "go_default_library", + srcs = [ + "doc.go", + "fixtures.go", + ], + visibility = ["//visibility:public"], + deps = [ + "//vendor/github.com/gophercloud/gophercloud/testhelper:go_default_library", + "//vendor/github.com/gophercloud/gophercloud/testhelper/client:go_default_library", + ], +) + +go_test( + name = "go_default_test", + srcs = ["requests_test.go"], + library = ":go_default_library", + deps = [ + "//vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/resetstate:go_default_library", + "//vendor/github.com/gophercloud/gophercloud/testhelper:go_default_library", + "//vendor/github.com/gophercloud/gophercloud/testhelper/client:go_default_library", + ], +) diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/resetstate/testing/doc.go b/vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/resetstate/testing/doc.go new file mode 100644 index 0000000000..7603f836a0 --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/resetstate/testing/doc.go @@ -0,0 +1 @@ +package testing diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/resetstate/testing/fixtures.go b/vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/resetstate/testing/fixtures.go new file mode 100644 index 0000000000..857a8b2127 --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/resetstate/testing/fixtures.go @@ -0,0 +1,19 @@ +package testing + +import ( + "fmt" + "net/http" + "testing" + + th "github.com/gophercloud/gophercloud/testhelper" + "github.com/gophercloud/gophercloud/testhelper/client" +) + +func mockResetStateResponse(t *testing.T, id string, state string) { + th.Mux.HandleFunc("/servers/"+id+"/action", func(w http.ResponseWriter, r *http.Request) { + th.TestMethod(t, r, "POST") + th.TestHeader(t, r, "X-Auth-Token", client.TokenID) + th.TestJSONRequest(t, r, fmt.Sprintf(`{"os-resetState": {"state": "%s"}}`, state)) + w.WriteHeader(http.StatusAccepted) + }) +} diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/resetstate/testing/requests_test.go b/vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/resetstate/testing/requests_test.go new file mode 100644 index 0000000000..491a7ee1fb --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/resetstate/testing/requests_test.go @@ -0,0 +1,21 @@ +package testing + +import ( + "testing" + + "github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/resetstate" + th "github.com/gophercloud/gophercloud/testhelper" + "github.com/gophercloud/gophercloud/testhelper/client" +) + +const serverID = "b16ba811-199d-4ffd-8839-ba96c1185a67" + +func TestResetState(t *testing.T) { + th.SetupHTTP() + defer th.TeardownHTTP() + + mockResetStateResponse(t, serverID, "active") + + err := resetstate.ResetState(client.ServiceClient(), serverID, "active").ExtractErr() + th.AssertNoErr(t, err) +} diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/resetstate/urls.go b/vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/resetstate/urls.go new file mode 100644 index 0000000000..c6da6d9304 --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/resetstate/urls.go @@ -0,0 +1,9 @@ +package resetstate + +import ( + "github.com/gophercloud/gophercloud" +) + +func actionURL(client *gophercloud.ServiceClient, id string) string { + return client.ServiceURL("servers", id, "action") +} diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/secgroups/doc.go b/vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/secgroups/doc.go index 702f32c985..8d3ebf2e5d 100644 --- a/vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/secgroups/doc.go +++ b/vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/secgroups/doc.go @@ -1 +1,112 @@ +/* +Package secgroups provides the ability to manage security groups through the +Nova API. + +This API has been deprecated and will be removed from a future release of the +Nova API service. + +For environments that support this extension, this package can be used +regardless of if either Neutron or nova-network is used as the cloud's network +service. + +Example to List Security Groups + + allPages, err := secroups.List(computeClient).AllPages() + if err != nil { + panic(err) + } + + allSecurityGroups, err := secgroups.ExtractSecurityGroups(allPages) + if err != nil { + panic(err) + } + + for _, sg := range allSecurityGroups { + fmt.Printf("%+v\n", sg) + } + +Example to List Security Groups by Server + + serverID := "aab3ad01-9956-4623-a29b-24afc89a7d36" + + allPages, err := secroups.ListByServer(computeClient, serverID).AllPages() + if err != nil { + panic(err) + } + + allSecurityGroups, err := secgroups.ExtractSecurityGroups(allPages) + if err != nil { + panic(err) + } + + for _, sg := range allSecurityGroups { + fmt.Printf("%+v\n", sg) + } + +Example to Create a Security Group + + createOpts := secgroups.CreateOpts{ + Name: "group_name", + Description: "A Security Group", + } + + sg, err := secgroups.Create(computeClient, createOpts).Extract() + if err != nil { + panic(err) + } + +Example to Create a Security Group Rule + + sgID := "37d94f8a-d136-465c-ae46-144f0d8ef141" + + createOpts := secgroups.CreateRuleOpts{ + ParentGroupID: sgID, + FromPort: 22, + ToPort: 22, + IPProtocol: "tcp", + CIDR: "0.0.0.0/0", + } + + rule, err := secgroups.CreateRule(computeClient, createOpts).Extract() + if err != nil { + panic(err) + } + +Example to Add a Security Group to a Server + + serverID := "aab3ad01-9956-4623-a29b-24afc89a7d36" + sgID := "37d94f8a-d136-465c-ae46-144f0d8ef141" + + err := secgroups.AddServer(computeClient, serverID, sgID).ExtractErr() + if err != nil { + panic(err) + } + +Example to Remove a Security Group from a Server + + serverID := "aab3ad01-9956-4623-a29b-24afc89a7d36" + sgID := "37d94f8a-d136-465c-ae46-144f0d8ef141" + + err := secgroups.RemoveServer(computeClient, serverID, sgID).ExtractErr() + if err != nil { + panic(err) + } + +Example to Delete a Security Group + + + sgID := "37d94f8a-d136-465c-ae46-144f0d8ef141" + err := secgroups.Delete(computeClient, sgID).ExtractErr() + if err != nil { + panic(err) + } + +Example to Delete a Security Group Rule + + ruleID := "6221fe3e-383d-46c9-a3a6-845e66c1e8b4" + err := secgroups.DeleteRule(computeClient, ruleID).ExtractErr() + if err != nil { + panic(err) + } +*/ package secgroups diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/secgroups/requests.go b/vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/secgroups/requests.go index ec8019f18c..bcceaeacdd 100644 --- a/vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/secgroups/requests.go +++ b/vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/secgroups/requests.go @@ -36,12 +36,13 @@ type GroupOpts struct { // CreateOpts is the struct responsible for creating a security group. type CreateOpts GroupOpts -// CreateOptsBuilder builds the create options into a serializable format. +// CreateOptsBuilder allows extensions to add additional parameters to the +// Create request. type CreateOptsBuilder interface { ToSecGroupCreateMap() (map[string]interface{}, error) } -// ToSecGroupCreateMap builds the create options into a serializable format. +// ToSecGroupCreateMap builds a request body from CreateOpts. func (opts CreateOpts) ToSecGroupCreateMap() (map[string]interface{}, error) { return gophercloud.BuildRequestBody(opts, "security_group") } @@ -62,12 +63,13 @@ func Create(client *gophercloud.ServiceClient, opts CreateOptsBuilder) (r Create // UpdateOpts is the struct responsible for updating an existing security group. type UpdateOpts GroupOpts -// UpdateOptsBuilder builds the update options into a serializable format. +// UpdateOptsBuilder allows extensions to add additional parameters to the +// Update request. type UpdateOptsBuilder interface { ToSecGroupUpdateMap() (map[string]interface{}, error) } -// ToSecGroupUpdateMap builds the update options into a serializable format. +// ToSecGroupUpdateMap builds a request body from UpdateOpts. func (opts UpdateOpts) ToSecGroupUpdateMap() (map[string]interface{}, error) { return gophercloud.BuildRequestBody(opts, "security_group") } @@ -93,7 +95,7 @@ func Get(client *gophercloud.ServiceClient, id string) (r GetResult) { } // Delete will permanently delete a security group from the project. -func Delete(client *gophercloud.ServiceClient, id string) (r gophercloud.ErrResult) { +func Delete(client *gophercloud.ServiceClient, id string) (r DeleteResult) { _, r.Err = client.Delete(resourceURL(client, id), nil) return } @@ -101,31 +103,41 @@ func Delete(client *gophercloud.ServiceClient, id string) (r gophercloud.ErrResu // CreateRuleOpts represents the configuration for adding a new rule to an // existing security group. type CreateRuleOpts struct { - // the ID of the group that this rule will be added to. + // ID is the ID of the group that this rule will be added to. ParentGroupID string `json:"parent_group_id" required:"true"` - // the lower bound of the port range that will be opened. + + // FromPort is the lower bound of the port range that will be opened. + // Use -1 to allow all ICMP traffic. FromPort int `json:"from_port"` - // the upper bound of the port range that will be opened. + + // ToPort is the upper bound of the port range that will be opened. + // Use -1 to allow all ICMP traffic. ToPort int `json:"to_port"` - // the protocol type that will be allowed, e.g. TCP. + + // IPProtocol the protocol type that will be allowed, e.g. TCP. IPProtocol string `json:"ip_protocol" required:"true"` - // ONLY required if FromGroupID is blank. This represents the IP range that - // will be the source of network traffic to your security group. Use - // 0.0.0.0/0 to allow all IP addresses. + + // CIDR is the network CIDR to allow traffic from. + // This is ONLY required if FromGroupID is blank. This represents the IP + // range that will be the source of network traffic to your security group. + // Use 0.0.0.0/0 to allow all IP addresses. CIDR string `json:"cidr,omitempty" or:"FromGroupID"` - // ONLY required if CIDR is blank. This value represents the ID of a group - // that forwards traffic to the parent group. So, instead of accepting + + // FromGroupID represents another security group to allow access. + // This is ONLY required if CIDR is blank. This value represents the ID of a + // group that forwards traffic to the parent group. So, instead of accepting // network traffic from an entire IP range, you can instead refine the // inbound source by an existing security group. FromGroupID string `json:"group_id,omitempty" or:"CIDR"` } -// CreateRuleOptsBuilder builds the create rule options into a serializable format. +// CreateRuleOptsBuilder allows extensions to add additional parameters to the +// CreateRule request. type CreateRuleOptsBuilder interface { ToRuleCreateMap() (map[string]interface{}, error) } -// ToRuleCreateMap builds the create rule options into a serializable format. +// ToRuleCreateMap builds a request body from CreateRuleOpts. func (opts CreateRuleOpts) ToRuleCreateMap() (map[string]interface{}, error) { return gophercloud.BuildRequestBody(opts, "security_group_rule") } @@ -146,7 +158,7 @@ func CreateRule(client *gophercloud.ServiceClient, opts CreateRuleOptsBuilder) ( } // DeleteRule will permanently delete a rule from a security group. -func DeleteRule(client *gophercloud.ServiceClient, id string) (r gophercloud.ErrResult) { +func DeleteRule(client *gophercloud.ServiceClient, id string) (r DeleteRuleResult) { _, r.Err = client.Delete(resourceRuleURL(client, id), nil) return } @@ -159,13 +171,13 @@ func actionMap(prefix, groupName string) map[string]map[string]string { // AddServer will associate a server and a security group, enforcing the // rules of the group on the server. -func AddServer(client *gophercloud.ServiceClient, serverID, groupName string) (r gophercloud.ErrResult) { +func AddServer(client *gophercloud.ServiceClient, serverID, groupName string) (r AddServerResult) { _, r.Err = client.Post(serverActionURL(client, serverID), actionMap("add", groupName), &r.Body, nil) return } // RemoveServer will disassociate a server from a security group. -func RemoveServer(client *gophercloud.ServiceClient, serverID, groupName string) (r gophercloud.ErrResult) { +func RemoveServer(client *gophercloud.ServiceClient, serverID, groupName string) (r RemoveServerResult) { _, r.Err = client.Post(serverActionURL(client, serverID), actionMap("remove", groupName), &r.Body, nil) return } diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/secgroups/results.go b/vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/secgroups/results.go index f49338a1da..cf08547e90 100644 --- a/vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/secgroups/results.go +++ b/vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/secgroups/results.go @@ -59,19 +59,19 @@ type Rule struct { // numeric ID. For the sake of consistency, we always cast it to a string. ID string `json:"-"` - // The lower bound of the port range which this security group should open up + // The lower bound of the port range which this security group should open up. FromPort int `json:"from_port"` - // The upper bound of the port range which this security group should open up + // The upper bound of the port range which this security group should open up. ToPort int `json:"to_port"` - // The IP protocol (e.g. TCP) which the security group accepts + // The IP protocol (e.g. TCP) which the security group accepts. IPProtocol string `json:"ip_protocol"` - // The CIDR IP range whose traffic can be received + // The CIDR IP range whose traffic can be received. IPRange IPRange `json:"ip_range"` - // The security group ID to which this rule belongs + // The security group ID to which this rule belongs. ParentGroupID string `json:"parent_group_id"` // Not documented. @@ -126,13 +126,15 @@ type SecurityGroupPage struct { pagination.SinglePageBase } -// IsEmpty determines whether or not a page of Security Groups contains any results. +// IsEmpty determines whether or not a page of Security Groups contains any +// results. func (page SecurityGroupPage) IsEmpty() (bool, error) { users, err := ExtractSecurityGroups(page) return len(users) == 0, err } -// ExtractSecurityGroups returns a slice of SecurityGroups contained in a single page of results. +// ExtractSecurityGroups returns a slice of SecurityGroups contained in a +// single page of results. func ExtractSecurityGroups(r pagination.Page) ([]SecurityGroup, error) { var s struct { SecurityGroups []SecurityGroup `json:"security_groups"` @@ -145,17 +147,20 @@ type commonResult struct { gophercloud.Result } -// CreateResult represents the result of a create operation. +// CreateResult represents the result of a create operation. Call its Extract +// method to interpret the result as a SecurityGroup. type CreateResult struct { commonResult } -// GetResult represents the result of a get operation. +// GetResult represents the result of a get operation. Call its Extract +// method to interpret the result as a SecurityGroup. type GetResult struct { commonResult } -// UpdateResult represents the result of an update operation. +// UpdateResult represents the result of an update operation. Call its Extract +// method to interpret the result as a SecurityGroup. type UpdateResult struct { commonResult } @@ -170,6 +175,7 @@ func (r commonResult) Extract() (*SecurityGroup, error) { } // CreateRuleResult represents the result when adding rules to a security group. +// Call its Extract method to interpret the result as a Rule. type CreateRuleResult struct { gophercloud.Result } @@ -182,3 +188,27 @@ func (r CreateRuleResult) Extract() (*Rule, error) { err := r.ExtractInto(&s) return s.Rule, err } + +// DeleteResult is the response from delete operation. Call its ExtractErr +// method to determine if the request succeeded or failed. +type DeleteResult struct { + gophercloud.ErrResult +} + +// DeleteRuleResult is the response from a DeleteRule operation. Call its +// ExtractErr method to determine if the request succeeded or failed. +type DeleteRuleResult struct { + gophercloud.ErrResult +} + +// AddServerResult is the response from an AddServer operation. Call its +// ExtractErr method to determine if the request succeeded or failed. +type AddServerResult struct { + gophercloud.ErrResult +} + +// RemoveServerResult is the response from a RemoveServer operation. Call its +// ExtractErr method to determine if the request succeeded or failed. +type RemoveServerResult struct { + gophercloud.ErrResult +} diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/servergroups/doc.go b/vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/servergroups/doc.go index 1e5ed568da..814bde37f3 100644 --- a/vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/servergroups/doc.go +++ b/vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/servergroups/doc.go @@ -1,2 +1,40 @@ -// Package servergroups provides the ability to manage server groups +/* +Package servergroups provides the ability to manage server groups. + +Example to List Server Groups + + allpages, err := servergroups.List(computeClient).AllPages() + if err != nil { + panic(err) + } + + allServerGroups, err := servergroups.ExtractServerGroups(allPages) + if err != nil { + panic(err) + } + + for _, sg := range allServerGroups { + fmt.Printf("%#v\n", sg) + } + +Example to Create a Server Group + + createOpts := servergroups.CreateOpts{ + Name: "my_sg", + Policies: []string{"anti-affinity"}, + } + + sg, err := servergroups.Create(computeClient, createOpts).Extract() + if err != nil { + panic(err) + } + +Example to Delete a Server Group + + sgID := "7a6f29ad-e34d-4368-951a-58a08f11cfb7" + err := servergroups.Delete(computeClient, sgID).ExtractErr() + if err != nil { + panic(err) + } +*/ package servergroups diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/servergroups/requests.go b/vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/servergroups/requests.go index ee98837074..1439a5a34c 100644 --- a/vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/servergroups/requests.go +++ b/vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/servergroups/requests.go @@ -5,23 +5,25 @@ import ( "github.com/gophercloud/gophercloud/pagination" ) -// List returns a Pager that allows you to iterate over a collection of ServerGroups. +// List returns a Pager that allows you to iterate over a collection of +// ServerGroups. func List(client *gophercloud.ServiceClient) pagination.Pager { return pagination.NewPager(client, listURL(client), func(r pagination.PageResult) pagination.Page { return ServerGroupPage{pagination.SinglePageBase(r)} }) } -// CreateOptsBuilder describes struct types that can be accepted by the Create call. Notably, the -// CreateOpts struct in this package does. +// CreateOptsBuilder allows extensions to add additional parameters to the +// Create request. type CreateOptsBuilder interface { ToServerGroupCreateMap() (map[string]interface{}, error) } -// CreateOpts specifies a Server Group allocation request +// CreateOpts specifies Server Group creation parameters. type CreateOpts struct { // Name is the name of the server group Name string `json:"name" required:"true"` + // Policies are the server group policies Policies []string `json:"policies" required:"true"` } @@ -31,7 +33,7 @@ func (opts CreateOpts) ToServerGroupCreateMap() (map[string]interface{}, error) return gophercloud.BuildRequestBody(opts, "server_group") } -// Create requests the creation of a new Server Group +// Create requests the creation of a new Server Group. func Create(client *gophercloud.ServiceClient, opts CreateOptsBuilder) (r CreateResult) { b, err := opts.ToServerGroupCreateMap() if err != nil { diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/servergroups/results.go b/vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/servergroups/results.go index ab49b35a44..b9aeef9815 100644 --- a/vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/servergroups/results.go +++ b/vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/servergroups/results.go @@ -5,7 +5,7 @@ import ( "github.com/gophercloud/gophercloud/pagination" ) -// A ServerGroup creates a policy for instance placement in the cloud +// A ServerGroup creates a policy for instance placement in the cloud. type ServerGroup struct { // ID is the unique ID of the Server Group. ID string `json:"id"` @@ -14,17 +14,26 @@ type ServerGroup struct { Name string `json:"name"` // Polices are the group policies. + // + // Normally a single policy is applied: + // + // "affinity" will place all servers within the server group on the + // same compute node. + // + // "anti-affinity" will place servers within the server group on different + // compute nodes. Policies []string `json:"policies"` // Members are the members of the server group. Members []string `json:"members"` - // Metadata includes a list of all user-specified key-value pairs attached to the Server Group. + // Metadata includes a list of all user-specified key-value pairs attached + // to the Server Group. Metadata map[string]interface{} } -// ServerGroupPage stores a single, only page of ServerGroups -// results from a List call. +// ServerGroupPage stores a single page of all ServerGroups results from a +// List call. type ServerGroupPage struct { pagination.SinglePageBase } @@ -59,20 +68,20 @@ func (r ServerGroupResult) Extract() (*ServerGroup, error) { return s.ServerGroup, err } -// CreateResult is the response from a Create operation. Call its Extract method to interpret it -// as a ServerGroup. +// CreateResult is the response from a Create operation. Call its Extract method +// to interpret it as a ServerGroup. type CreateResult struct { ServerGroupResult } -// GetResult is the response from a Get operation. Call its Extract method to interpret it -// as a ServerGroup. +// GetResult is the response from a Get operation. Call its Extract method to +// interpret it as a ServerGroup. type GetResult struct { ServerGroupResult } -// DeleteResult is the response from a Delete operation. Call its Extract method to determine if -// the call succeeded or failed. +// DeleteResult is the response from a Delete operation. Call its ExtractErr +// method to determine if the call succeeded or failed. type DeleteResult struct { gophercloud.ErrResult } diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/startstop/BUILD.bazel b/vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/startstop/BUILD.bazel index 1fc1b74dae..418e5617f8 100644 --- a/vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/startstop/BUILD.bazel +++ b/vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/startstop/BUILD.bazel @@ -5,6 +5,7 @@ go_library( srcs = [ "doc.go", "requests.go", + "results.go", ], visibility = ["//visibility:public"], deps = ["//vendor/github.com/gophercloud/gophercloud:go_default_library"], diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/startstop/requests.go b/vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/startstop/requests.go index 1d8a593b9f..5b4f3f39dd 100644 --- a/vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/startstop/requests.go +++ b/vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/startstop/requests.go @@ -7,13 +7,13 @@ func actionURL(client *gophercloud.ServiceClient, id string) string { } // Start is the operation responsible for starting a Compute server. -func Start(client *gophercloud.ServiceClient, id string) (r gophercloud.ErrResult) { +func Start(client *gophercloud.ServiceClient, id string) (r StartResult) { _, r.Err = client.Post(actionURL(client, id), map[string]interface{}{"os-start": nil}, nil, nil) return } // Stop is the operation responsible for stopping a Compute server. -func Stop(client *gophercloud.ServiceClient, id string) (r gophercloud.ErrResult) { +func Stop(client *gophercloud.ServiceClient, id string) (r StopResult) { _, r.Err = client.Post(actionURL(client, id), map[string]interface{}{"os-stop": nil}, nil, nil) return } diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/startstop/results.go b/vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/startstop/results.go new file mode 100644 index 0000000000..8349689332 --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/startstop/results.go @@ -0,0 +1,15 @@ +package startstop + +import "github.com/gophercloud/gophercloud" + +// StartResult is the response from a Start operation. Call its ExtractErr +// method to determine if the request succeeded or failed. +type StartResult struct { + gophercloud.ErrResult +} + +// StopResult is the response from Stop operation. Call its ExtractErr +// method to determine if the request succeeded or failed. +type StopResult struct { + gophercloud.ErrResult +} diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/suspendresume/BUILD.bazel b/vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/suspendresume/BUILD.bazel index 1fc1b74dae..418e5617f8 100644 --- a/vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/suspendresume/BUILD.bazel +++ b/vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/suspendresume/BUILD.bazel @@ -5,6 +5,7 @@ go_library( srcs = [ "doc.go", "requests.go", + "results.go", ], visibility = ["//visibility:public"], deps = ["//vendor/github.com/gophercloud/gophercloud:go_default_library"], diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/suspendresume/requests.go b/vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/suspendresume/requests.go index d5f03fcdf1..0abbe72121 100644 --- a/vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/suspendresume/requests.go +++ b/vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/suspendresume/requests.go @@ -7,13 +7,13 @@ func actionURL(client *gophercloud.ServiceClient, id string) string { } // Suspend is the operation responsible for suspending a Compute server. -func Suspend(client *gophercloud.ServiceClient, id string) (r gophercloud.ErrResult) { +func Suspend(client *gophercloud.ServiceClient, id string) (r SuspendResult) { _, r.Err = client.Post(actionURL(client, id), map[string]interface{}{"suspend": nil}, nil, nil) return } // Resume is the operation responsible for resuming a Compute server. -func Resume(client *gophercloud.ServiceClient, id string) (r gophercloud.ErrResult) { +func Resume(client *gophercloud.ServiceClient, id string) (r UnsuspendResult) { _, r.Err = client.Post(actionURL(client, id), map[string]interface{}{"resume": nil}, nil, nil) return } diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/suspendresume/results.go b/vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/suspendresume/results.go new file mode 100644 index 0000000000..988d83e4aa --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/suspendresume/results.go @@ -0,0 +1,15 @@ +package suspendresume + +import "github.com/gophercloud/gophercloud" + +// SuspendResult is the response from a Suspend operation. Call its +// ExtractErr method to determine if the request succeeded or failed. +type SuspendResult struct { + gophercloud.ErrResult +} + +// UnsuspendResult is the response from an Unsuspend operation. Call +// its ExtractErr method to determine if the request succeeded or failed. +type UnsuspendResult struct { + gophercloud.ErrResult +} diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/db/v1/instances/requests.go b/vendor/github.com/gophercloud/gophercloud/openstack/db/v1/instances/requests.go index f8afb7364d..bbd9093182 100644 --- a/vendor/github.com/gophercloud/gophercloud/openstack/db/v1/instances/requests.go +++ b/vendor/github.com/gophercloud/gophercloud/openstack/db/v1/instances/requests.go @@ -203,3 +203,17 @@ func ResizeVolume(client *gophercloud.ServiceClient, id string, size int) (r Act _, r.Err = client.Post(actionURL(client, id), &b, nil, nil) return } + +// AttachConfigurationGroup will attach configuration group to the instance +func AttachConfigurationGroup(client *gophercloud.ServiceClient, instanceID string, configID string) (r ConfigurationResult) { + b := map[string]interface{}{"instance": map[string]interface{}{"configuration": configID}} + _, r.Err = client.Put(resourceURL(client, instanceID), &b, nil, &gophercloud.RequestOpts{OkCodes: []int{202}}) + return +} + +// DetachConfigurationGroup will dettach configuration group from the instance +func DetachConfigurationGroup(client *gophercloud.ServiceClient, instanceID string) (r ConfigurationResult) { + b := map[string]interface{}{"instance": map[string]interface{}{}} + _, r.Err = client.Put(resourceURL(client, instanceID), &b, nil, &gophercloud.RequestOpts{OkCodes: []int{202}}) + return +} diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/db/v1/instances/results.go b/vendor/github.com/gophercloud/gophercloud/openstack/db/v1/instances/results.go index 6bfde15030..1a570899e4 100644 --- a/vendor/github.com/gophercloud/gophercloud/openstack/db/v1/instances/results.go +++ b/vendor/github.com/gophercloud/gophercloud/openstack/db/v1/instances/results.go @@ -106,6 +106,11 @@ type DeleteResult struct { gophercloud.ErrResult } +// ConfigurationResult represents the result of a AttachConfigurationGroup/DetachConfigurationGroup operation. +type ConfigurationResult struct { + gophercloud.ErrResult +} + // Extract will extract an Instance from various result structs. func (r commonResult) Extract() (*Instance, error) { var s struct { diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/db/v1/instances/testing/fixtures.go b/vendor/github.com/gophercloud/gophercloud/openstack/db/v1/instances/testing/fixtures.go index 9347ee15be..93643c4fbd 100644 --- a/vendor/github.com/gophercloud/gophercloud/openstack/db/v1/instances/testing/fixtures.go +++ b/vendor/github.com/gophercloud/gophercloud/openstack/db/v1/instances/testing/fixtures.go @@ -87,17 +87,20 @@ var createReq = ` ` var ( - instanceID = "{instanceID}" - rootURL = "/instances" - resURL = rootURL + "/" + instanceID - uRootURL = resURL + "/root" - aURL = resURL + "/action" + instanceID = "{instanceID}" + configGroupID = "00000000-0000-0000-0000-000000000000" + rootURL = "/instances" + resURL = rootURL + "/" + instanceID + uRootURL = resURL + "/root" + aURL = resURL + "/action" ) var ( - restartReq = `{"restart": {}}` - resizeReq = `{"resize": {"flavorRef": "2"}}` - resizeVolReq = `{"resize": {"volume": {"size": 4}}}` + restartReq = `{"restart": {}}` + resizeReq = `{"resize": {"flavorRef": "2"}}` + resizeVolReq = `{"resize": {"volume": {"size": 4}}}` + attachConfigurationGroupReq = `{"instance": {"configuration": "00000000-0000-0000-0000-000000000000"}}` + detachConfigurationGroupReq = `{"instance": {}}` ) var ( @@ -167,3 +170,11 @@ func HandleResize(t *testing.T) { func HandleResizeVol(t *testing.T) { fixture.SetupHandler(t, aURL, "POST", resizeVolReq, "", 202) } + +func HandleAttachConfigurationGroup(t *testing.T) { + fixture.SetupHandler(t, resURL, "PUT", attachConfigurationGroupReq, "", 202) +} + +func HandleDetachConfigurationGroup(t *testing.T) { + fixture.SetupHandler(t, resURL, "PUT", detachConfigurationGroupReq, "", 202) +} diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/db/v1/instances/testing/requests_test.go b/vendor/github.com/gophercloud/gophercloud/openstack/db/v1/instances/testing/requests_test.go index e3c81e34c7..666cdd2e8d 100644 --- a/vendor/github.com/gophercloud/gophercloud/openstack/db/v1/instances/testing/requests_test.go +++ b/vendor/github.com/gophercloud/gophercloud/openstack/db/v1/instances/testing/requests_test.go @@ -132,3 +132,21 @@ func TestResizeVolume(t *testing.T) { res := instances.ResizeVolume(fake.ServiceClient(), instanceID, 4) th.AssertNoErr(t, res.Err) } + +func TestAttachConfigurationGroup(t *testing.T) { + th.SetupHTTP() + defer th.TeardownHTTP() + HandleAttachConfigurationGroup(t) + + res := instances.AttachConfigurationGroup(fake.ServiceClient(), instanceID, configGroupID) + th.AssertNoErr(t, res.Err) +} + +func TestDetachConfigurationGroup(t *testing.T) { + th.SetupHTTP() + defer th.TeardownHTTP() + HandleDetachConfigurationGroup(t) + + res := instances.DetachConfigurationGroup(fake.ServiceClient(), instanceID) + th.AssertNoErr(t, res.Err) +} diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/identity/v3/domains/BUILD.bazel b/vendor/github.com/gophercloud/gophercloud/openstack/identity/v3/domains/BUILD.bazel new file mode 100644 index 0000000000..3032b771b8 --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/openstack/identity/v3/domains/BUILD.bazel @@ -0,0 +1,16 @@ +load("@io_bazel_rules_go//go:def.bzl", "go_library") + +go_library( + name = "go_default_library", + srcs = [ + "doc.go", + "requests.go", + "results.go", + "urls.go", + ], + visibility = ["//visibility:public"], + deps = [ + "//vendor/github.com/gophercloud/gophercloud:go_default_library", + "//vendor/github.com/gophercloud/gophercloud/pagination:go_default_library", + ], +) diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/identity/v3/domains/doc.go b/vendor/github.com/gophercloud/gophercloud/openstack/identity/v3/domains/doc.go new file mode 100644 index 0000000000..720db782f7 --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/openstack/identity/v3/domains/doc.go @@ -0,0 +1,59 @@ +/* +Package domains manages and retrieves Domains in the OpenStack Identity Service. + +Example to List Domains + + var iTrue bool = true + listOpts := domains.ListOpts{ + Enabled: &iTrue, + } + + allPages, err := domains.List(identityClient, listOpts).AllPages() + if err != nil { + panic(err) + } + + allDomains, err := domains.ExtractDomains(allPages) + if err != nil { + panic(err) + } + + for _, domain := range allDomains { + fmt.Printf("%+v\n", domain) + } + +Example to Create a Domain + + createOpts := domains.CreateOpts{ + Name: "domain name", + Description: "Test domain", + } + + domain, err := domains.Create(identityClient, createOpts).Extract() + if err != nil { + panic(err) + } + +Example to Update a Domain + + domainID := "0fe36e73809d46aeae6705c39077b1b3" + + var iFalse bool = false + updateOpts := domains.UpdateOpts{ + Enabled: &iFalse, + } + + domain, err := domains.Update(identityClient, domainID, updateOpts).Extract() + if err != nil { + panic(err) + } + +Example to Delete a Domain + + domainID := "0fe36e73809d46aeae6705c39077b1b3" + err := domains.Delete(identityClient, domainID).ExtractErr() + if err != nil { + panic(err) + } +*/ +package domains diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/identity/v3/domains/requests.go b/vendor/github.com/gophercloud/gophercloud/openstack/identity/v3/domains/requests.go new file mode 100644 index 0000000000..14fbd27eb3 --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/openstack/identity/v3/domains/requests.go @@ -0,0 +1,126 @@ +package domains + +import ( + "github.com/gophercloud/gophercloud" + "github.com/gophercloud/gophercloud/pagination" +) + +// ListOptsBuilder allows extensions to add additional parameters to +// the List request +type ListOptsBuilder interface { + ToDomainListQuery() (string, error) +} + +// ListOpts provides options to filter the List results. +type ListOpts struct { + // Enabled filters the response by enabled domains. + Enabled *bool `q:"enabled"` + + // Name filters the response by domain name. + Name string `q:"name"` +} + +// ToDomainListQuery formats a ListOpts into a query string. +func (opts ListOpts) ToDomainListQuery() (string, error) { + q, err := gophercloud.BuildQueryString(opts) + return q.String(), err +} + +// List enumerates the domains to which the current token has access. +func List(client *gophercloud.ServiceClient, opts ListOptsBuilder) pagination.Pager { + url := listURL(client) + if opts != nil { + query, err := opts.ToDomainListQuery() + if err != nil { + return pagination.Pager{Err: err} + } + url += query + } + return pagination.NewPager(client, url, func(r pagination.PageResult) pagination.Page { + return DomainPage{pagination.LinkedPageBase{PageResult: r}} + }) +} + +// Get retrieves details on a single domain, by ID. +func Get(client *gophercloud.ServiceClient, id string) (r GetResult) { + _, r.Err = client.Get(getURL(client, id), &r.Body, nil) + return +} + +// CreateOptsBuilder allows extensions to add additional parameters to +// the Create request. +type CreateOptsBuilder interface { + ToDomainCreateMap() (map[string]interface{}, error) +} + +// CreateOpts provides options used to create a domain. +type CreateOpts struct { + // Name is the name of the new domain. + Name string `json:"name" required:"true"` + + // Description is a description of the domain. + Description string `json:"description,omitempty"` + + // Enabled sets the domain status to enabled or disabled. + Enabled *bool `json:"enabled,omitempty"` +} + +// ToDomainCreateMap formats a CreateOpts into a create request. +func (opts CreateOpts) ToDomainCreateMap() (map[string]interface{}, error) { + return gophercloud.BuildRequestBody(opts, "domain") +} + +// Create creates a new Domain. +func Create(client *gophercloud.ServiceClient, opts CreateOptsBuilder) (r CreateResult) { + b, err := opts.ToDomainCreateMap() + if err != nil { + r.Err = err + return + } + _, r.Err = client.Post(createURL(client), &b, &r.Body, &gophercloud.RequestOpts{ + OkCodes: []int{201}, + }) + return +} + +// Delete deletes a domain. +func Delete(client *gophercloud.ServiceClient, domainID string) (r DeleteResult) { + _, r.Err = client.Delete(deleteURL(client, domainID), nil) + return +} + +// UpdateOptsBuilder allows extensions to add additional parameters to +// the Update request. +type UpdateOptsBuilder interface { + ToDomainUpdateMap() (map[string]interface{}, error) +} + +// UpdateOpts represents parameters to update a domain. +type UpdateOpts struct { + // Name is the name of the domain. + Name string `json:"name,omitempty"` + + // Description is the description of the domain. + Description string `json:"description,omitempty"` + + // Enabled sets the domain status to enabled or disabled. + Enabled *bool `json:"enabled,omitempty"` +} + +// ToUpdateCreateMap formats a UpdateOpts into an update request. +func (opts UpdateOpts) ToDomainUpdateMap() (map[string]interface{}, error) { + return gophercloud.BuildRequestBody(opts, "domain") +} + +// Update modifies the attributes of a domain. +func Update(client *gophercloud.ServiceClient, id string, opts UpdateOptsBuilder) (r UpdateResult) { + b, err := opts.ToDomainUpdateMap() + if err != nil { + r.Err = err + return + } + _, r.Err = client.Patch(updateURL(client, id), b, &r.Body, &gophercloud.RequestOpts{ + OkCodes: []int{200}, + }) + return +} diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/identity/v3/domains/results.go b/vendor/github.com/gophercloud/gophercloud/openstack/identity/v3/domains/results.go new file mode 100644 index 0000000000..5b8e2662b2 --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/openstack/identity/v3/domains/results.go @@ -0,0 +1,97 @@ +package domains + +import ( + "github.com/gophercloud/gophercloud" + "github.com/gophercloud/gophercloud/pagination" +) + +// A Domain is a collection of projects, users, and roles. +type Domain struct { + // Description is the description of the Domain. + Description string `json:"description"` + + // Enabled is whether or not the domain is enabled. + Enabled bool `json:"enabled"` + + // ID is the unique ID of the domain. + ID string `json:"id"` + + // Links contains referencing links to the domain. + Links map[string]interface{} `json:"links"` + + // Name is the name of the domain. + Name string `json:"name"` +} + +type domainResult struct { + gophercloud.Result +} + +// GetResult is the response from a Get operation. Call its Extract method +// to interpret it as a Domain. +type GetResult struct { + domainResult +} + +// CreateResult is the response from a Create operation. Call its Extract method +// to interpret it as a Domain. +type CreateResult struct { + domainResult +} + +// DeleteResult is the response from a Delete operation. Call its ExtractErr to +// determine if the request succeeded or failed. +type DeleteResult struct { + gophercloud.ErrResult +} + +// UpdateResult is the result of an Update request. Call its Extract method to +// interpret it as a Domain. +type UpdateResult struct { + domainResult +} + +// DomainPage is a single page of Domain results. +type DomainPage struct { + pagination.LinkedPageBase +} + +// IsEmpty determines whether or not a page of Domains contains any results. +func (r DomainPage) IsEmpty() (bool, error) { + domains, err := ExtractDomains(r) + return len(domains) == 0, err +} + +// NextPageURL extracts the "next" link from the links section of the result. +func (r DomainPage) NextPageURL() (string, error) { + var s struct { + Links struct { + Next string `json:"next"` + Previous string `json:"previous"` + } `json:"links"` + } + err := r.ExtractInto(&s) + if err != nil { + return "", err + } + return s.Links.Next, err +} + +// ExtractDomains returns a slice of Domains contained in a single page of +// results. +func ExtractDomains(r pagination.Page) ([]Domain, error) { + var s struct { + Domains []Domain `json:"domains"` + } + err := (r.(DomainPage)).ExtractInto(&s) + return s.Domains, err +} + +// Extract interprets any domainResults as a Domain. +func (r domainResult) Extract() (*Domain, error) { + var s struct { + Domain *Domain `json:"domain"` + } + err := r.ExtractInto(&s) + return s.Domain, err +} diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/identity/v3/domains/testing/BUILD.bazel b/vendor/github.com/gophercloud/gophercloud/openstack/identity/v3/domains/testing/BUILD.bazel new file mode 100644 index 0000000000..ae77470936 --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/openstack/identity/v3/domains/testing/BUILD.bazel @@ -0,0 +1,24 @@ +load("@io_bazel_rules_go//go:def.bzl", "go_library", "go_test") + +go_library( + name = "go_default_library", + srcs = ["fixtures.go"], + visibility = ["//visibility:public"], + deps = [ + "//vendor/github.com/gophercloud/gophercloud/openstack/identity/v3/domains:go_default_library", + "//vendor/github.com/gophercloud/gophercloud/testhelper:go_default_library", + "//vendor/github.com/gophercloud/gophercloud/testhelper/client:go_default_library", + ], +) + +go_test( + name = "go_default_test", + srcs = ["requests_test.go"], + library = ":go_default_library", + deps = [ + "//vendor/github.com/gophercloud/gophercloud/openstack/identity/v3/domains:go_default_library", + "//vendor/github.com/gophercloud/gophercloud/pagination:go_default_library", + "//vendor/github.com/gophercloud/gophercloud/testhelper:go_default_library", + "//vendor/github.com/gophercloud/gophercloud/testhelper/client:go_default_library", + ], +) diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/identity/v3/domains/testing/fixtures.go b/vendor/github.com/gophercloud/gophercloud/openstack/identity/v3/domains/testing/fixtures.go new file mode 100644 index 0000000000..87ac561b5a --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/openstack/identity/v3/domains/testing/fixtures.go @@ -0,0 +1,188 @@ +package testing + +import ( + "fmt" + "net/http" + "testing" + + "github.com/gophercloud/gophercloud/openstack/identity/v3/domains" + th "github.com/gophercloud/gophercloud/testhelper" + "github.com/gophercloud/gophercloud/testhelper/client" +) + +// ListOutput provides a single page of Domain results. +const ListOutput = ` +{ + "links": { + "next": null, + "previous": null, + "self": "http://example.com/identity/v3/domains" + }, + "domains": [ + { + "enabled": true, + "id": "2844b2a08be147a08ef58317d6471f1f", + "links": { + "self": "http://example.com/identity/v3/domains/2844b2a08be147a08ef58317d6471f1f" + }, + "name": "domain one", + "description": "some description" + }, + { + "enabled": true, + "id": "9fe1d3", + "links": { + "self": "https://example.com/identity/v3/domains/9fe1d3" + }, + "name": "domain two" + } + ] +} +` + +// GetOutput provides a Get result. +const GetOutput = ` +{ + "domain": { + "enabled": true, + "id": "9fe1d3", + "links": { + "self": "https://example.com/identity/v3/domains/9fe1d3" + }, + "name": "domain two" + } +} +` + +// CreateRequest provides the input to a Create request. +const CreateRequest = ` +{ + "domain": { + "name": "domain two" + } +} +` + +// UpdateRequest provides the input to as Update request. +const UpdateRequest = ` +{ + "domain": { + "description": "Staging Domain" + } +} +` + +// UpdateOutput provides an update result. +const UpdateOutput = ` +{ + "domain": { + "enabled": true, + "id": "9fe1d3", + "links": { + "self": "https://example.com/identity/v3/domains/9fe1d3" + }, + "name": "domain two", + "description": "Staging Domain" + } +} +` + +// FirstDomain is the first domain in the List request. +var FirstDomain = domains.Domain{ + Enabled: true, + ID: "2844b2a08be147a08ef58317d6471f1f", + Links: map[string]interface{}{ + "self": "http://example.com/identity/v3/domains/2844b2a08be147a08ef58317d6471f1f", + }, + Name: "domain one", + Description: "some description", +} + +// SecondDomain is the second domain in the List request. +var SecondDomain = domains.Domain{ + Enabled: true, + ID: "9fe1d3", + Links: map[string]interface{}{ + "self": "https://example.com/identity/v3/domains/9fe1d3", + }, + Name: "domain two", +} + +// SecondDomainUpdated is how SecondDomain should look after an Update. +var SecondDomainUpdated = domains.Domain{ + Enabled: true, + ID: "9fe1d3", + Links: map[string]interface{}{ + "self": "https://example.com/identity/v3/domains/9fe1d3", + }, + Name: "domain two", + Description: "Staging Domain", +} + +// ExpectedDomainsSlice is the slice of domains expected to be returned from ListOutput. +var ExpectedDomainsSlice = []domains.Domain{FirstDomain, SecondDomain} + +// HandleListDomainsSuccessfully creates an HTTP handler at `/domains` on the +// test handler mux that responds with a list of two domains. +func HandleListDomainsSuccessfully(t *testing.T) { + th.Mux.HandleFunc("/domains", func(w http.ResponseWriter, r *http.Request) { + th.TestMethod(t, r, "GET") + th.TestHeader(t, r, "Accept", "application/json") + th.TestHeader(t, r, "X-Auth-Token", client.TokenID) + + w.Header().Set("Content-Type", "application/json") + w.WriteHeader(http.StatusOK) + fmt.Fprintf(w, ListOutput) + }) +} + +// HandleGetDomainSuccessfully creates an HTTP handler at `/domains` on the +// test handler mux that responds with a single domain. +func HandleGetDomainSuccessfully(t *testing.T) { + th.Mux.HandleFunc("/domains/9fe1d3", func(w http.ResponseWriter, r *http.Request) { + th.TestMethod(t, r, "GET") + th.TestHeader(t, r, "Accept", "application/json") + th.TestHeader(t, r, "X-Auth-Token", client.TokenID) + + w.Header().Set("Content-Type", "application/json") + w.WriteHeader(http.StatusOK) + fmt.Fprintf(w, GetOutput) + }) +} + +// HandleCreateDomainSuccessfully creates an HTTP handler at `/domains` on the +// test handler mux that tests domain creation. +func HandleCreateDomainSuccessfully(t *testing.T) { + th.Mux.HandleFunc("/domains", func(w http.ResponseWriter, r *http.Request) { + th.TestMethod(t, r, "POST") + th.TestHeader(t, r, "X-Auth-Token", client.TokenID) + th.TestJSONRequest(t, r, CreateRequest) + + w.WriteHeader(http.StatusCreated) + fmt.Fprintf(w, GetOutput) + }) +} + +// HandleDeleteDomainSuccessfully creates an HTTP handler at `/domains` on the +// test handler mux that tests domain deletion. +func HandleDeleteDomainSuccessfully(t *testing.T) { + th.Mux.HandleFunc("/domains/9fe1d3", func(w http.ResponseWriter, r *http.Request) { + th.TestMethod(t, r, "DELETE") + th.TestHeader(t, r, "X-Auth-Token", client.TokenID) + + w.WriteHeader(http.StatusNoContent) + }) +} + +// HandleUpdateDomainSuccessfully creates an HTTP handler at `/domains` on the +// test handler mux that tests domain update. +func HandleUpdateDomainSuccessfully(t *testing.T) { + th.Mux.HandleFunc("/domains/9fe1d3", func(w http.ResponseWriter, r *http.Request) { + th.TestMethod(t, r, "PATCH") + th.TestHeader(t, r, "X-Auth-Token", client.TokenID) + th.TestJSONRequest(t, r, UpdateRequest) + + w.WriteHeader(http.StatusOK) + fmt.Fprintf(w, UpdateOutput) + }) +} diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/identity/v3/domains/testing/requests_test.go b/vendor/github.com/gophercloud/gophercloud/openstack/identity/v3/domains/testing/requests_test.go new file mode 100644 index 0000000000..73ac97aa73 --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/openstack/identity/v3/domains/testing/requests_test.go @@ -0,0 +1,89 @@ +package testing + +import ( + "testing" + + "github.com/gophercloud/gophercloud/openstack/identity/v3/domains" + "github.com/gophercloud/gophercloud/pagination" + th "github.com/gophercloud/gophercloud/testhelper" + "github.com/gophercloud/gophercloud/testhelper/client" +) + +func TestListDomains(t *testing.T) { + th.SetupHTTP() + defer th.TeardownHTTP() + HandleListDomainsSuccessfully(t) + + count := 0 + err := domains.List(client.ServiceClient(), nil).EachPage(func(page pagination.Page) (bool, error) { + count++ + + actual, err := domains.ExtractDomains(page) + th.AssertNoErr(t, err) + + th.CheckDeepEquals(t, ExpectedDomainsSlice, actual) + + return true, nil + }) + th.AssertNoErr(t, err) + th.CheckEquals(t, count, 1) +} + +func TestListDomainsAllPages(t *testing.T) { + th.SetupHTTP() + defer th.TeardownHTTP() + HandleListDomainsSuccessfully(t) + + allPages, err := domains.List(client.ServiceClient(), nil).AllPages() + th.AssertNoErr(t, err) + actual, err := domains.ExtractDomains(allPages) + th.AssertNoErr(t, err) + th.CheckDeepEquals(t, ExpectedDomainsSlice, actual) +} + +func TestGetDomain(t *testing.T) { + th.SetupHTTP() + defer th.TeardownHTTP() + HandleGetDomainSuccessfully(t) + + actual, err := domains.Get(client.ServiceClient(), "9fe1d3").Extract() + th.AssertNoErr(t, err) + th.CheckDeepEquals(t, SecondDomain, *actual) +} + +func TestCreateDomain(t *testing.T) { + th.SetupHTTP() + defer th.TeardownHTTP() + HandleCreateDomainSuccessfully(t) + + createOpts := domains.CreateOpts{ + Name: "domain two", + } + + actual, err := domains.Create(client.ServiceClient(), createOpts).Extract() + th.AssertNoErr(t, err) + th.CheckDeepEquals(t, SecondDomain, *actual) +} + +func TestDeleteDomain(t *testing.T) { + th.SetupHTTP() + defer th.TeardownHTTP() + HandleDeleteDomainSuccessfully(t) + + res := domains.Delete(client.ServiceClient(), "9fe1d3") + th.AssertNoErr(t, res.Err) +} + +func TestUpdateDomain(t *testing.T) { + th.SetupHTTP() + defer th.TeardownHTTP() + HandleUpdateDomainSuccessfully(t) + + updateOpts := domains.UpdateOpts{ + Description: "Staging Domain", + } + + actual, err := domains.Update(client.ServiceClient(), "9fe1d3", updateOpts).Extract() + th.AssertNoErr(t, err) + th.CheckDeepEquals(t, SecondDomainUpdated, *actual) +} diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/identity/v3/domains/urls.go b/vendor/github.com/gophercloud/gophercloud/openstack/identity/v3/domains/urls.go new file mode 100644 index 0000000000..b0c21b80be --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/openstack/identity/v3/domains/urls.go @@ -0,0 +1,23 @@ +package domains + +import "github.com/gophercloud/gophercloud" + +func listURL(client *gophercloud.ServiceClient) string { + return client.ServiceURL("domains") +} + +func getURL(client *gophercloud.ServiceClient, domainID string) string { + return client.ServiceURL("domains", domainID) +} + +func createURL(client *gophercloud.ServiceClient) string { + return client.ServiceURL("domains") +} + +func deleteURL(client *gophercloud.ServiceClient, domainID string) string { + return client.ServiceURL("domains", domainID) +} + +func updateURL(client *gophercloud.ServiceClient, domainID string) string { + return client.ServiceURL("domains", domainID) +} diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/identity/v3/groups/BUILD.bazel b/vendor/github.com/gophercloud/gophercloud/openstack/identity/v3/groups/BUILD.bazel index edac73b75c..ee8100be22 100644 --- a/vendor/github.com/gophercloud/gophercloud/openstack/identity/v3/groups/BUILD.bazel +++ b/vendor/github.com/gophercloud/gophercloud/openstack/identity/v3/groups/BUILD.bazel @@ -4,7 +4,9 @@ go_library( name = "go_default_library", srcs = [ "doc.go", + "requests.go", "results.go", + "urls.go", ], visibility = ["//visibility:public"], deps = [ diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/identity/v3/groups/doc.go b/vendor/github.com/gophercloud/gophercloud/openstack/identity/v3/groups/doc.go index dfbf3dec77..696e2a5d8e 100644 --- a/vendor/github.com/gophercloud/gophercloud/openstack/identity/v3/groups/doc.go +++ b/vendor/github.com/gophercloud/gophercloud/openstack/identity/v3/groups/doc.go @@ -1,3 +1,60 @@ -// Package groups retrieves and manages groups in the OpenStack Identity -// Service. +/* +Package groups manages and retrieves Groups in the OpenStack Identity Service. + +Example to List Groups + + listOpts := groups.ListOpts{ + DomainID: "default", + } + + allPages, err := groups.List(identityClient, listOpts).AllPages() + if err != nil { + panic(err) + } + + allGroups, err := groups.ExtractGroups(allPages) + if err != nil { + panic(err) + } + + for _, group := range allGroups { + fmt.Printf("%+v\n", group) + } + +Example to Create a Group + + createOpts := groups.CreateOpts{ + Name: "groupname", + DomainID: "default", + Extra: map[string]interface{}{ + "email": "groupname@example.com", + } + } + + group, err := groups.Create(identityClient, createOpts).Extract() + if err != nil { + panic(err) + } + +Example to Update a Group + + groupID := "0fe36e73809d46aeae6705c39077b1b3" + + updateOpts := groups.UpdateOpts{ + Description: "Updated Description for group", + } + + group, err := groups.Update(identityClient, groupID, updateOpts).Extract() + if err != nil { + panic(err) + } + +Example to Delete a Group + + groupID := "0fe36e73809d46aeae6705c39077b1b3" + err := groups.Delete(identityClient, groupID).ExtractErr() + if err != nil { + panic(err) + } +*/ package groups diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/identity/v3/groups/requests.go b/vendor/github.com/gophercloud/gophercloud/openstack/identity/v3/groups/requests.go new file mode 100644 index 0000000000..b6e74dcf97 --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/openstack/identity/v3/groups/requests.go @@ -0,0 +1,158 @@ +package groups + +import ( + "github.com/gophercloud/gophercloud" + "github.com/gophercloud/gophercloud/pagination" +) + +// ListOptsBuilder allows extensions to add additional parameters to +// the List request +type ListOptsBuilder interface { + ToGroupListQuery() (string, error) +} + +// ListOpts provides options to filter the List results. +type ListOpts struct { + // DomainID filters the response by a domain ID. + DomainID string `q:"domain_id"` + + // Name filters the response by group name. + Name string `q:"name"` +} + +// ToGroupListQuery formats a ListOpts into a query string. +func (opts ListOpts) ToGroupListQuery() (string, error) { + q, err := gophercloud.BuildQueryString(opts) + return q.String(), err +} + +// List enumerates the Groups to which the current token has access. +func List(client *gophercloud.ServiceClient, opts ListOptsBuilder) pagination.Pager { + url := listURL(client) + if opts != nil { + query, err := opts.ToGroupListQuery() + if err != nil { + return pagination.Pager{Err: err} + } + url += query + } + return pagination.NewPager(client, url, func(r pagination.PageResult) pagination.Page { + return GroupPage{pagination.LinkedPageBase{PageResult: r}} + }) +} + +// Get retrieves details on a single group, by ID. +func Get(client *gophercloud.ServiceClient, id string) (r GetResult) { + _, r.Err = client.Get(getURL(client, id), &r.Body, nil) + return +} + +// CreateOptsBuilder allows extensions to add additional parameters to +// the Create request. +type CreateOptsBuilder interface { + ToGroupCreateMap() (map[string]interface{}, error) +} + +// CreateOpts provides options used to create a group. +type CreateOpts struct { + // Name is the name of the new group. + Name string `json:"name" required:"true"` + + // Description is a description of the group. + Description string `json:"description,omitempty"` + + // DomainID is the ID of the domain the group belongs to. + DomainID string `json:"domain_id,omitempty"` + + // Extra is free-form extra key/value pairs to describe the group. + Extra map[string]interface{} `json:"-"` +} + +// ToGroupCreateMap formats a CreateOpts into a create request. +func (opts CreateOpts) ToGroupCreateMap() (map[string]interface{}, error) { + b, err := gophercloud.BuildRequestBody(opts, "group") + if err != nil { + return nil, err + } + + if opts.Extra != nil { + if v, ok := b["group"].(map[string]interface{}); ok { + for key, value := range opts.Extra { + v[key] = value + } + } + } + + return b, nil +} + +// Create creates a new Group. +func Create(client *gophercloud.ServiceClient, opts CreateOptsBuilder) (r CreateResult) { + b, err := opts.ToGroupCreateMap() + if err != nil { + r.Err = err + return + } + _, r.Err = client.Post(createURL(client), &b, &r.Body, &gophercloud.RequestOpts{ + OkCodes: []int{201}, + }) + return +} + +// UpdateOptsBuilder allows extensions to add additional parameters to +// the Update request. +type UpdateOptsBuilder interface { + ToGroupUpdateMap() (map[string]interface{}, error) +} + +// UpdateOpts provides options for updating a group. +type UpdateOpts struct { + // Name is the name of the new group. + Name string `json:"name,omitempty"` + + // Description is a description of the group. + Description string `json:"description,omitempty"` + + // DomainID is the ID of the domain the group belongs to. + DomainID string `json:"domain_id,omitempty"` + + // Extra is free-form extra key/value pairs to describe the group. + Extra map[string]interface{} `json:"-"` +} + +// ToGroupUpdateMap formats a UpdateOpts into an update request. +func (opts UpdateOpts) ToGroupUpdateMap() (map[string]interface{}, error) { + b, err := gophercloud.BuildRequestBody(opts, "group") + if err != nil { + return nil, err + } + + if opts.Extra != nil { + if v, ok := b["group"].(map[string]interface{}); ok { + for key, value := range opts.Extra { + v[key] = value + } + } + } + + return b, nil +} + +// Update updates an existing Group. +func Update(client *gophercloud.ServiceClient, groupID string, opts UpdateOptsBuilder) (r UpdateResult) { + b, err := opts.ToGroupUpdateMap() + if err != nil { + r.Err = err + return + } + _, r.Err = client.Patch(updateURL(client, groupID), &b, &r.Body, &gophercloud.RequestOpts{ + OkCodes: []int{200}, + }) + return +} + +// Delete deletes a group. +func Delete(client *gophercloud.ServiceClient, groupID string) (r DeleteResult) { + _, r.Err = client.Delete(deleteURL(client, groupID), nil) + return +} diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/identity/v3/groups/results.go b/vendor/github.com/gophercloud/gophercloud/openstack/identity/v3/groups/results.go index ad25d6c8c1..ba7d018d17 100644 --- a/vendor/github.com/gophercloud/gophercloud/openstack/identity/v3/groups/results.go +++ b/vendor/github.com/gophercloud/gophercloud/openstack/identity/v3/groups/results.go @@ -63,6 +63,30 @@ type groupResult struct { gophercloud.Result } +// GetResult is the response from a Get operation. Call its Extract method +// to interpret it as a Group. +type GetResult struct { + groupResult +} + +// CreateResult is the response from a Create operation. Call its Extract method +// to interpret it as a Group. +type CreateResult struct { + groupResult +} + +// UpdateResult is the response from an Update operation. Call its Extract +// method to interpret it as a Group. +type UpdateResult struct { + groupResult +} + +// DeleteResult is the response from a Delete operation. Call its ExtractErr to +// determine if the request succeeded or failed. +type DeleteResult struct { + gophercloud.ErrResult +} + // GroupPage is a single page of Group results. type GroupPage struct { pagination.LinkedPageBase diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/identity/v3/groups/testing/BUILD.bazel b/vendor/github.com/gophercloud/gophercloud/openstack/identity/v3/groups/testing/BUILD.bazel new file mode 100644 index 0000000000..79092fb407 --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/openstack/identity/v3/groups/testing/BUILD.bazel @@ -0,0 +1,24 @@ +load("@io_bazel_rules_go//go:def.bzl", "go_library", "go_test") + +go_library( + name = "go_default_library", + srcs = ["fixtures.go"], + visibility = ["//visibility:public"], + deps = [ + "//vendor/github.com/gophercloud/gophercloud/openstack/identity/v3/groups:go_default_library", + "//vendor/github.com/gophercloud/gophercloud/testhelper:go_default_library", + "//vendor/github.com/gophercloud/gophercloud/testhelper/client:go_default_library", + ], +) + +go_test( + name = "go_default_test", + srcs = ["requests_test.go"], + library = ":go_default_library", + deps = [ + "//vendor/github.com/gophercloud/gophercloud/openstack/identity/v3/groups:go_default_library", + "//vendor/github.com/gophercloud/gophercloud/pagination:go_default_library", + "//vendor/github.com/gophercloud/gophercloud/testhelper:go_default_library", + "//vendor/github.com/gophercloud/gophercloud/testhelper/client:go_default_library", + ], +) diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/identity/v3/groups/testing/fixtures.go b/vendor/github.com/gophercloud/gophercloud/openstack/identity/v3/groups/testing/fixtures.go new file mode 100644 index 0000000000..58f3503785 --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/openstack/identity/v3/groups/testing/fixtures.go @@ -0,0 +1,216 @@ +package testing + +import ( + "fmt" + "net/http" + "testing" + + "github.com/gophercloud/gophercloud/openstack/identity/v3/groups" + th "github.com/gophercloud/gophercloud/testhelper" + "github.com/gophercloud/gophercloud/testhelper/client" +) + +// ListOutput provides a single page of Group results. +const ListOutput = ` +{ + "links": { + "next": null, + "previous": null, + "self": "http://example.com/identity/v3/groups" + }, + "groups": [ + { + "domain_id": "default", + "id": "2844b2a08be147a08ef58317d6471f1f", + "description": "group for internal support users", + "links": { + "self": "http://example.com/identity/v3/groups/2844b2a08be147a08ef58317d6471f1f" + }, + "name": "internal support", + "extra": { + "email": "support@localhost" + } + }, + { + "domain_id": "1789d1", + "id": "9fe1d3", + "description": "group for support users", + "links": { + "self": "https://example.com/identity/v3/groups/9fe1d3" + }, + "name": "support", + "extra": { + "email": "support@example.com" + } + } + ] +} +` + +// GetOutput provides a Get result. +const GetOutput = ` +{ + "group": { + "domain_id": "1789d1", + "id": "9fe1d3", + "description": "group for support users", + "links": { + "self": "https://example.com/identity/v3/groups/9fe1d3" + }, + "name": "support", + "extra": { + "email": "support@example.com" + } + } +} +` + +// CreateRequest provides the input to a Create request. +const CreateRequest = ` +{ + "group": { + "domain_id": "1789d1", + "name": "support", + "description": "group for support users", + "email": "support@example.com" + } +} +` + +// UpdateRequest provides the input to as Update request. +const UpdateRequest = ` +{ + "group": { + "description": "L2 Support Team", + "email": "supportteam@example.com" + } +} +` + +// UpdateOutput provides an update result. +const UpdateOutput = ` +{ + "group": { + "domain_id": "1789d1", + "id": "9fe1d3", + "links": { + "self": "https://example.com/identity/v3/groups/9fe1d3" + }, + "name": "support", + "description": "L2 Support Team", + "extra": { + "email": "supportteam@example.com" + } + } +} +` + +// FirstGroup is the first group in the List request. +var FirstGroup = groups.Group{ + DomainID: "default", + ID: "2844b2a08be147a08ef58317d6471f1f", + Links: map[string]interface{}{ + "self": "http://example.com/identity/v3/groups/2844b2a08be147a08ef58317d6471f1f", + }, + Name: "internal support", + Description: "group for internal support users", + Extra: map[string]interface{}{ + "email": "support@localhost", + }, +} + +// SecondGroup is the second group in the List request. +var SecondGroup = groups.Group{ + DomainID: "1789d1", + ID: "9fe1d3", + Links: map[string]interface{}{ + "self": "https://example.com/identity/v3/groups/9fe1d3", + }, + Name: "support", + Description: "group for support users", + Extra: map[string]interface{}{ + "email": "support@example.com", + }, +} + +// SecondGroupUpdated is how SecondGroup should look after an Update. +var SecondGroupUpdated = groups.Group{ + DomainID: "1789d1", + ID: "9fe1d3", + Links: map[string]interface{}{ + "self": "https://example.com/identity/v3/groups/9fe1d3", + }, + Name: "support", + Description: "L2 Support Team", + Extra: map[string]interface{}{ + "email": "supportteam@example.com", + }, +} + +// ExpectedGroupsSlice is the slice of groups expected to be returned from ListOutput. +var ExpectedGroupsSlice = []groups.Group{FirstGroup, SecondGroup} + +// HandleListGroupsSuccessfully creates an HTTP handler at `/groups` on the +// test handler mux that responds with a list of two groups. +func HandleListGroupsSuccessfully(t *testing.T) { + th.Mux.HandleFunc("/groups", func(w http.ResponseWriter, r *http.Request) { + th.TestMethod(t, r, "GET") + th.TestHeader(t, r, "Accept", "application/json") + th.TestHeader(t, r, "X-Auth-Token", client.TokenID) + + w.Header().Set("Content-Type", "application/json") + w.WriteHeader(http.StatusOK) + fmt.Fprintf(w, ListOutput) + }) +} + +// HandleGetGroupSuccessfully creates an HTTP handler at `/groups` on the +// test handler mux that responds with a single group. +func HandleGetGroupSuccessfully(t *testing.T) { + th.Mux.HandleFunc("/groups/9fe1d3", func(w http.ResponseWriter, r *http.Request) { + th.TestMethod(t, r, "GET") + th.TestHeader(t, r, "Accept", "application/json") + th.TestHeader(t, r, "X-Auth-Token", client.TokenID) + + w.Header().Set("Content-Type", "application/json") + w.WriteHeader(http.StatusOK) + fmt.Fprintf(w, GetOutput) + }) +} + +// HandleCreateGroupSuccessfully creates an HTTP handler at `/groups` on the +// test handler mux that tests group creation. +func HandleCreateGroupSuccessfully(t *testing.T) { + th.Mux.HandleFunc("/groups", func(w http.ResponseWriter, r *http.Request) { + th.TestMethod(t, r, "POST") + th.TestHeader(t, r, "X-Auth-Token", client.TokenID) + th.TestJSONRequest(t, r, CreateRequest) + + w.WriteHeader(http.StatusCreated) + fmt.Fprintf(w, GetOutput) + }) +} + +// HandleUpdateGroupSuccessfully creates an HTTP handler at `/groups` on the +// test handler mux that tests group update. +func HandleUpdateGroupSuccessfully(t *testing.T) { + th.Mux.HandleFunc("/groups/9fe1d3", func(w http.ResponseWriter, r *http.Request) { + th.TestMethod(t, r, "PATCH") + th.TestHeader(t, r, "X-Auth-Token", client.TokenID) + th.TestJSONRequest(t, r, UpdateRequest) + + w.WriteHeader(http.StatusOK) + fmt.Fprintf(w, UpdateOutput) + }) +} + +// HandleDeleteGroupSuccessfully creates an HTTP handler at `/groups` on the +// test handler mux that tests group deletion. +func HandleDeleteGroupSuccessfully(t *testing.T) { + th.Mux.HandleFunc("/groups/9fe1d3", func(w http.ResponseWriter, r *http.Request) { + th.TestMethod(t, r, "DELETE") + th.TestHeader(t, r, "X-Auth-Token", client.TokenID) + + w.WriteHeader(http.StatusNoContent) + }) +} diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/identity/v3/groups/testing/requests_test.go b/vendor/github.com/gophercloud/gophercloud/openstack/identity/v3/groups/testing/requests_test.go new file mode 100644 index 0000000000..e35c97214b --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/openstack/identity/v3/groups/testing/requests_test.go @@ -0,0 +1,101 @@ +package testing + +import ( + "testing" + + "github.com/gophercloud/gophercloud/openstack/identity/v3/groups" + "github.com/gophercloud/gophercloud/pagination" + th "github.com/gophercloud/gophercloud/testhelper" + "github.com/gophercloud/gophercloud/testhelper/client" +) + +func TestListGroups(t *testing.T) { + th.SetupHTTP() + defer th.TeardownHTTP() + HandleListGroupsSuccessfully(t) + + count := 0 + err := groups.List(client.ServiceClient(), nil).EachPage(func(page pagination.Page) (bool, error) { + count++ + + actual, err := groups.ExtractGroups(page) + th.AssertNoErr(t, err) + + th.CheckDeepEquals(t, ExpectedGroupsSlice, actual) + + return true, nil + }) + th.AssertNoErr(t, err) + th.CheckEquals(t, count, 1) +} + +func TestListGroupsAllPages(t *testing.T) { + th.SetupHTTP() + defer th.TeardownHTTP() + HandleListGroupsSuccessfully(t) + + allPages, err := groups.List(client.ServiceClient(), nil).AllPages() + th.AssertNoErr(t, err) + actual, err := groups.ExtractGroups(allPages) + th.AssertNoErr(t, err) + th.CheckDeepEquals(t, ExpectedGroupsSlice, actual) + th.AssertEquals(t, ExpectedGroupsSlice[0].Extra["email"], "support@localhost") + th.AssertEquals(t, ExpectedGroupsSlice[1].Extra["email"], "support@example.com") +} + +func TestGetGroup(t *testing.T) { + th.SetupHTTP() + defer th.TeardownHTTP() + HandleGetGroupSuccessfully(t) + + actual, err := groups.Get(client.ServiceClient(), "9fe1d3").Extract() + + th.AssertNoErr(t, err) + th.CheckDeepEquals(t, SecondGroup, *actual) + th.AssertEquals(t, SecondGroup.Extra["email"], "support@example.com") +} + +func TestCreateGroup(t *testing.T) { + th.SetupHTTP() + defer th.TeardownHTTP() + HandleCreateGroupSuccessfully(t) + + createOpts := groups.CreateOpts{ + Name: "support", + DomainID: "1789d1", + Description: "group for support users", + Extra: map[string]interface{}{ + "email": "support@example.com", + }, + } + + actual, err := groups.Create(client.ServiceClient(), createOpts).Extract() + th.AssertNoErr(t, err) + th.CheckDeepEquals(t, SecondGroup, *actual) +} + +func TestUpdateGroup(t *testing.T) { + th.SetupHTTP() + defer th.TeardownHTTP() + HandleUpdateGroupSuccessfully(t) + + updateOpts := groups.UpdateOpts{ + Description: "L2 Support Team", + Extra: map[string]interface{}{ + "email": "supportteam@example.com", + }, + } + + actual, err := groups.Update(client.ServiceClient(), "9fe1d3", updateOpts).Extract() + th.AssertNoErr(t, err) + th.CheckDeepEquals(t, SecondGroupUpdated, *actual) +} + +func TestDeleteGroup(t *testing.T) { + th.SetupHTTP() + defer th.TeardownHTTP() + HandleDeleteGroupSuccessfully(t) + + res := groups.Delete(client.ServiceClient(), "9fe1d3") + th.AssertNoErr(t, res.Err) +} diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/identity/v3/groups/urls.go b/vendor/github.com/gophercloud/gophercloud/openstack/identity/v3/groups/urls.go new file mode 100644 index 0000000000..e7d1e53b27 --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/openstack/identity/v3/groups/urls.go @@ -0,0 +1,23 @@ +package groups + +import "github.com/gophercloud/gophercloud" + +func listURL(client *gophercloud.ServiceClient) string { + return client.ServiceURL("groups") +} + +func getURL(client *gophercloud.ServiceClient, groupID string) string { + return client.ServiceURL("groups", groupID) +} + +func createURL(client *gophercloud.ServiceClient) string { + return client.ServiceURL("groups") +} + +func updateURL(client *gophercloud.ServiceClient, groupID string) string { + return client.ServiceURL("groups", groupID) +} + +func deleteURL(client *gophercloud.ServiceClient, groupID string) string { + return client.ServiceURL("groups", groupID) +} diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/identity/v3/roles/BUILD.bazel b/vendor/github.com/gophercloud/gophercloud/openstack/identity/v3/roles/BUILD.bazel index 3032b771b8..ee8100be22 100644 --- a/vendor/github.com/gophercloud/gophercloud/openstack/identity/v3/roles/BUILD.bazel +++ b/vendor/github.com/gophercloud/gophercloud/openstack/identity/v3/roles/BUILD.bazel @@ -11,6 +11,7 @@ go_library( visibility = ["//visibility:public"], deps = [ "//vendor/github.com/gophercloud/gophercloud:go_default_library", + "//vendor/github.com/gophercloud/gophercloud/internal:go_default_library", "//vendor/github.com/gophercloud/gophercloud/pagination:go_default_library", ], ) diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/identity/v3/roles/doc.go b/vendor/github.com/gophercloud/gophercloud/openstack/identity/v3/roles/doc.go index 59377a4eb1..2886a872d8 100644 --- a/vendor/github.com/gophercloud/gophercloud/openstack/identity/v3/roles/doc.go +++ b/vendor/github.com/gophercloud/gophercloud/openstack/identity/v3/roles/doc.go @@ -2,6 +2,62 @@ Package roles provides information and interaction with the roles API resource for the OpenStack Identity service. +Example to List Roles + + listOpts := roles.ListOpts{ + DomainID: "default", + } + + allPages, err := roles.List(identityClient, listOpts).AllPages() + if err != nil { + panic(err) + } + + allRoles, err := roles.ExtractRoles(allPages) + if err != nil { + panic(err) + } + + for _, role := range allRoles { + fmt.Printf("%+v\n", role) + } + +Example to Create a Role + + createOpts := roles.CreateOpts{ + Name: "read-only-admin", + DomainID: "default", + Extra: map[string]interface{}{ + "description": "this role grants read-only privilege cross tenant", + } + } + + role, err := roles.Create(identityClient, createOpts).Extract() + if err != nil { + panic(err) + } + +Example to Update a Role + + roleID := "0fe36e73809d46aeae6705c39077b1b3" + + updateOpts := roles.UpdateOpts{ + Name: "read only admin", + } + + role, err := roles.Update(identityClient, roleID, updateOpts).Extract() + if err != nil { + panic(err) + } + +Example to Delete a Role + + roleID := "0fe36e73809d46aeae6705c39077b1b3" + err := roles.Delete(identityClient, roleID).ExtractErr() + if err != nil { + panic(err) + } + Example to List Role Assignments listOpts := roles.ListAssignmentsOpts{ @@ -22,5 +78,35 @@ Example to List Role Assignments for _, role := range allRoles { fmt.Printf("%+v\n", role) } + +Example to Assign a Role to a User in a Project + + projectID := "a99e9b4e620e4db09a2dfb6e42a01e66" + userID := "9df1a02f5eb2416a9781e8b0c022d3ae" + roleID := "9fe2ff9ee4384b1894a90878d3e92bab" + + err := roles.Assign(identityClient, roleID, roles.AssignOpts{ + UserID: userID, + ProjectID: projectID, + }).ExtractErr() + + if err != nil { + panic(err) + } + +Example to Unassign a Role From a User in a Project + + projectID := "a99e9b4e620e4db09a2dfb6e42a01e66" + userID := "9df1a02f5eb2416a9781e8b0c022d3ae" + roleID := "9fe2ff9ee4384b1894a90878d3e92bab" + + err := roles.Unassign(identityClient, roleID, roles.UnassignOpts{ + UserID: userID, + ProjectID: projectID, + }).ExtractErr() + + if err != nil { + panic(err) + } */ package roles diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/identity/v3/roles/requests.go b/vendor/github.com/gophercloud/gophercloud/openstack/identity/v3/roles/requests.go index ea8f207129..7908baa0e4 100644 --- a/vendor/github.com/gophercloud/gophercloud/openstack/identity/v3/roles/requests.go +++ b/vendor/github.com/gophercloud/gophercloud/openstack/identity/v3/roles/requests.go @@ -5,6 +5,150 @@ import ( "github.com/gophercloud/gophercloud/pagination" ) +// ListOptsBuilder allows extensions to add additional parameters to +// the List request +type ListOptsBuilder interface { + ToRoleListQuery() (string, error) +} + +// ListOpts provides options to filter the List results. +type ListOpts struct { + // DomainID filters the response by a domain ID. + DomainID string `q:"domain_id"` + + // Name filters the response by role name. + Name string `q:"name"` +} + +// ToRoleListQuery formats a ListOpts into a query string. +func (opts ListOpts) ToRoleListQuery() (string, error) { + q, err := gophercloud.BuildQueryString(opts) + return q.String(), err +} + +// List enumerates the roles to which the current token has access. +func List(client *gophercloud.ServiceClient, opts ListOptsBuilder) pagination.Pager { + url := listURL(client) + if opts != nil { + query, err := opts.ToRoleListQuery() + if err != nil { + return pagination.Pager{Err: err} + } + url += query + } + + return pagination.NewPager(client, url, func(r pagination.PageResult) pagination.Page { + return RolePage{pagination.LinkedPageBase{PageResult: r}} + }) +} + +// Get retrieves details on a single role, by ID. +func Get(client *gophercloud.ServiceClient, id string) (r GetResult) { + _, r.Err = client.Get(getURL(client, id), &r.Body, nil) + return +} + +// CreateOptsBuilder allows extensions to add additional parameters to +// the Create request. +type CreateOptsBuilder interface { + ToRoleCreateMap() (map[string]interface{}, error) +} + +// CreateOpts provides options used to create a role. +type CreateOpts struct { + // Name is the name of the new role. + Name string `json:"name" required:"true"` + + // DomainID is the ID of the domain the role belongs to. + DomainID string `json:"domain_id,omitempty"` + + // Extra is free-form extra key/value pairs to describe the role. + Extra map[string]interface{} `json:"-"` +} + +// ToRoleCreateMap formats a CreateOpts into a create request. +func (opts CreateOpts) ToRoleCreateMap() (map[string]interface{}, error) { + b, err := gophercloud.BuildRequestBody(opts, "role") + if err != nil { + return nil, err + } + + if opts.Extra != nil { + if v, ok := b["role"].(map[string]interface{}); ok { + for key, value := range opts.Extra { + v[key] = value + } + } + } + + return b, nil +} + +// Create creates a new Role. +func Create(client *gophercloud.ServiceClient, opts CreateOptsBuilder) (r CreateResult) { + b, err := opts.ToRoleCreateMap() + if err != nil { + r.Err = err + return + } + _, r.Err = client.Post(createURL(client), &b, &r.Body, &gophercloud.RequestOpts{ + OkCodes: []int{201}, + }) + return +} + +// UpdateOptsBuilder allows extensions to add additional parameters to +// the Update request. +type UpdateOptsBuilder interface { + ToRoleUpdateMap() (map[string]interface{}, error) +} + +// UpdateOpts provides options for updating a role. +type UpdateOpts struct { + // Name is the name of the new role. + Name string `json:"name,omitempty"` + + // Extra is free-form extra key/value pairs to describe the role. + Extra map[string]interface{} `json:"-"` +} + +// ToRoleUpdateMap formats a UpdateOpts into an update request. +func (opts UpdateOpts) ToRoleUpdateMap() (map[string]interface{}, error) { + b, err := gophercloud.BuildRequestBody(opts, "role") + if err != nil { + return nil, err + } + + if opts.Extra != nil { + if v, ok := b["role"].(map[string]interface{}); ok { + for key, value := range opts.Extra { + v[key] = value + } + } + } + + return b, nil +} + +// Update updates an existing Role. +func Update(client *gophercloud.ServiceClient, roleID string, opts UpdateOptsBuilder) (r UpdateResult) { + b, err := opts.ToRoleUpdateMap() + if err != nil { + r.Err = err + return + } + _, r.Err = client.Patch(updateURL(client, roleID), &b, &r.Body, &gophercloud.RequestOpts{ + OkCodes: []int{200}, + }) + return +} + +// Delete deletes a role. +func Delete(client *gophercloud.ServiceClient, roleID string) (r DeleteResult) { + _, r.Err = client.Delete(deleteURL(client, roleID), nil) + return +} + // ListAssignmentsOptsBuilder allows extensions to add additional parameters to // the ListAssignments request. type ListAssignmentsOptsBuilder interface { @@ -56,3 +200,115 @@ func ListAssignments(client *gophercloud.ServiceClient, opts ListAssignmentsOpts return RoleAssignmentPage{pagination.LinkedPageBase{PageResult: r}} }) } + +// AssignOpts provides options to assign a role +type AssignOpts struct { + // UserID is the ID of a user to assign a role + // Note: exactly one of UserID or GroupID must be provided + UserID string `xor:"GroupID"` + + // GroupID is the ID of a group to assign a role + // Note: exactly one of UserID or GroupID must be provided + GroupID string `xor:"UserID"` + + // ProjectID is the ID of a project to assign a role on + // Note: exactly one of ProjectID or DomainID must be provided + ProjectID string `xor:"DomainID"` + + // DomainID is the ID of a domain to assign a role on + // Note: exactly one of ProjectID or DomainID must be provided + DomainID string `xor:"ProjectID"` +} + +// UnassignOpts provides options to unassign a role +type UnassignOpts struct { + // UserID is the ID of a user to unassign a role + // Note: exactly one of UserID or GroupID must be provided + UserID string `xor:"GroupID"` + + // GroupID is the ID of a group to unassign a role + // Note: exactly one of UserID or GroupID must be provided + GroupID string `xor:"UserID"` + + // ProjectID is the ID of a project to unassign a role on + // Note: exactly one of ProjectID or DomainID must be provided + ProjectID string `xor:"DomainID"` + + // DomainID is the ID of a domain to unassign a role on + // Note: exactly one of ProjectID or DomainID must be provided + DomainID string `xor:"ProjectID"` +} + +// Assign is the operation responsible for assigning a role +// to a user/group on a project/domain. +func Assign(client *gophercloud.ServiceClient, roleID string, opts AssignOpts) (r AssignmentResult) { + // Check xor conditions + _, err := gophercloud.BuildRequestBody(opts, "") + if err != nil { + r.Err = err + return + } + + // Get corresponding URL + var targetID string + var targetType string + if opts.ProjectID != "" { + targetID = opts.ProjectID + targetType = "projects" + } else { + targetID = opts.DomainID + targetType = "domains" + } + + var actorID string + var actorType string + if opts.UserID != "" { + actorID = opts.UserID + actorType = "users" + } else { + actorID = opts.GroupID + actorType = "groups" + } + + _, r.Err = client.Put(assignURL(client, targetType, targetID, actorType, actorID, roleID), nil, nil, &gophercloud.RequestOpts{ + OkCodes: []int{204}, + }) + return +} + +// Unassign is the operation responsible for unassigning a role +// from a user/group on a project/domain. +func Unassign(client *gophercloud.ServiceClient, roleID string, opts UnassignOpts) (r UnassignmentResult) { + // Check xor conditions + _, err := gophercloud.BuildRequestBody(opts, "") + if err != nil { + r.Err = err + return + } + + // Get corresponding URL + var targetID string + var targetType string + if opts.ProjectID != "" { + targetID = opts.ProjectID + targetType = "projects" + } else { + targetID = opts.DomainID + targetType = "domains" + } + + var actorID string + var actorType string + if opts.UserID != "" { + actorID = opts.UserID + actorType = "users" + } else { + actorID = opts.GroupID + actorType = "groups" + } + + _, r.Err = client.Delete(assignURL(client, targetType, targetID, actorType, actorID, roleID), &gophercloud.RequestOpts{ + OkCodes: []int{204}, + }) + return +} diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/identity/v3/roles/results.go b/vendor/github.com/gophercloud/gophercloud/openstack/identity/v3/roles/results.go index a7b43f44a4..af8fd9e6a7 100644 --- a/vendor/github.com/gophercloud/gophercloud/openstack/identity/v3/roles/results.go +++ b/vendor/github.com/gophercloud/gophercloud/openstack/identity/v3/roles/results.go @@ -1,17 +1,144 @@ package roles -import "github.com/gophercloud/gophercloud/pagination" +import ( + "encoding/json" + + "github.com/gophercloud/gophercloud" + "github.com/gophercloud/gophercloud/internal" + "github.com/gophercloud/gophercloud/pagination" +) + +// Role grants permissions to a user. +type Role struct { + // DomainID is the domain ID the role belongs to. + DomainID string `json:"domain_id"` + + // ID is the unique ID of the role. + ID string `json:"id"` + + // Links contains referencing links to the role. + Links map[string]interface{} `json:"links"` + + // Name is the role name + Name string `json:"name"` + + // Extra is a collection of miscellaneous key/values. + Extra map[string]interface{} `json:"-"` +} + +func (r *Role) UnmarshalJSON(b []byte) error { + type tmp Role + var s struct { + tmp + Extra map[string]interface{} `json:"extra"` + } + err := json.Unmarshal(b, &s) + if err != nil { + return err + } + *r = Role(s.tmp) + + // Collect other fields and bundle them into Extra + // but only if a field titled "extra" wasn't sent. + if s.Extra != nil { + r.Extra = s.Extra + } else { + var result interface{} + err := json.Unmarshal(b, &result) + if err != nil { + return err + } + if resultMap, ok := result.(map[string]interface{}); ok { + r.Extra = internal.RemainingKeys(Role{}, resultMap) + } + } + + return err +} + +type roleResult struct { + gophercloud.Result +} + +// GetResult is the response from a Get operation. Call its Extract method +// to interpret it as a Role. +type GetResult struct { + roleResult +} + +// CreateResult is the response from a Create operation. Call its Extract method +// to interpret it as a Role +type CreateResult struct { + roleResult +} + +// UpdateResult is the response from an Update operation. Call its Extract +// method to interpret it as a Role. +type UpdateResult struct { + roleResult +} + +// DeleteResult is the response from a Delete operation. Call its ExtractErr to +// determine if the request succeeded or failed. +type DeleteResult struct { + gophercloud.ErrResult +} + +// RolePage is a single page of Role results. +type RolePage struct { + pagination.LinkedPageBase +} + +// IsEmpty determines whether or not a page of Roles contains any results. +func (r RolePage) IsEmpty() (bool, error) { + roles, err := ExtractRoles(r) + return len(roles) == 0, err +} + +// NextPageURL extracts the "next" link from the links section of the result. +func (r RolePage) NextPageURL() (string, error) { + var s struct { + Links struct { + Next string `json:"next"` + Previous string `json:"previous"` + } `json:"links"` + } + err := r.ExtractInto(&s) + if err != nil { + return "", err + } + return s.Links.Next, err +} + +// ExtractProjects returns a slice of Roles contained in a single page of +// results. +func ExtractRoles(r pagination.Page) ([]Role, error) { + var s struct { + Roles []Role `json:"roles"` + } + err := (r.(RolePage)).ExtractInto(&s) + return s.Roles, err +} + +// Extract interprets any roleResults as a Role. +func (r roleResult) Extract() (*Role, error) { + var s struct { + Role *Role `json:"role"` + } + err := r.ExtractInto(&s) + return s.Role, err +} // RoleAssignment is the result of a role assignments query. type RoleAssignment struct { - Role Role `json:"role,omitempty"` - Scope Scope `json:"scope,omitempty"` - User User `json:"user,omitempty"` - Group Group `json:"group,omitempty"` + Role AssignedRole `json:"role,omitempty"` + Scope Scope `json:"scope,omitempty"` + User User `json:"user,omitempty"` + Group Group `json:"group,omitempty"` } -// Role represents a Role in an assignment. -type Role struct { +// AssignedRole represents a Role in an assignment. +type AssignedRole struct { ID string `json:"id,omitempty"` } @@ -73,3 +200,15 @@ func ExtractRoleAssignments(r pagination.Page) ([]RoleAssignment, error) { err := (r.(RoleAssignmentPage)).ExtractInto(&s) return s.RoleAssignments, err } + +// AssignmentResult represents the result of an assign operation. +// Call ExtractErr method to determine if the request succeeded or failed. +type AssignmentResult struct { + gophercloud.ErrResult +} + +// UnassignmentResult represents the result of an unassign operation. +// Call ExtractErr method to determine if the request succeeded or failed. +type UnassignmentResult struct { + gophercloud.ErrResult +} diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/identity/v3/roles/testing/BUILD.bazel b/vendor/github.com/gophercloud/gophercloud/openstack/identity/v3/roles/testing/BUILD.bazel index c7efa54d1a..014174c0c5 100644 --- a/vendor/github.com/gophercloud/gophercloud/openstack/identity/v3/roles/testing/BUILD.bazel +++ b/vendor/github.com/gophercloud/gophercloud/openstack/identity/v3/roles/testing/BUILD.bazel @@ -2,8 +2,16 @@ load("@io_bazel_rules_go//go:def.bzl", "go_library", "go_test") go_library( name = "go_default_library", - srcs = ["doc.go"], + srcs = [ + "doc.go", + "fixtures.go", + ], visibility = ["//visibility:public"], + deps = [ + "//vendor/github.com/gophercloud/gophercloud/openstack/identity/v3/roles:go_default_library", + "//vendor/github.com/gophercloud/gophercloud/testhelper:go_default_library", + "//vendor/github.com/gophercloud/gophercloud/testhelper/client:go_default_library", + ], ) go_test( diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/identity/v3/roles/testing/fixtures.go b/vendor/github.com/gophercloud/gophercloud/openstack/identity/v3/roles/testing/fixtures.go new file mode 100644 index 0000000000..fa73b11ee0 --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/openstack/identity/v3/roles/testing/fixtures.go @@ -0,0 +1,333 @@ +package testing + +import ( + "fmt" + "net/http" + "testing" + + "github.com/gophercloud/gophercloud/openstack/identity/v3/roles" + th "github.com/gophercloud/gophercloud/testhelper" + fake "github.com/gophercloud/gophercloud/testhelper/client" +) + +// ListOutput provides a single page of Role results. +const ListOutput = ` +{ + "links": { + "next": null, + "previous": null, + "self": "http://example.com/identity/v3/roles" + }, + "roles": [ + { + "domain_id": "default", + "id": "2844b2a08be147a08ef58317d6471f1f", + "links": { + "self": "http://example.com/identity/v3/roles/2844b2a08be147a08ef58317d6471f1f" + }, + "name": "admin-read-only" + }, + { + "domain_id": "1789d1", + "id": "9fe1d3", + "links": { + "self": "https://example.com/identity/v3/roles/9fe1d3" + }, + "name": "support", + "extra": { + "description": "read-only support role" + } + } + ] +} +` + +// GetOutput provides a Get result. +const GetOutput = ` +{ + "role": { + "domain_id": "1789d1", + "id": "9fe1d3", + "links": { + "self": "https://example.com/identity/v3/roles/9fe1d3" + }, + "name": "support", + "extra": { + "description": "read-only support role" + } + } +} +` + +// CreateRequest provides the input to a Create request. +const CreateRequest = ` +{ + "role": { + "domain_id": "1789d1", + "name": "support", + "description": "read-only support role" + } +} +` + +// UpdateRequest provides the input to as Update request. +const UpdateRequest = ` +{ + "role": { + "description": "admin read-only support role" + } +} +` + +// UpdateOutput provides an update result. +const UpdateOutput = ` +{ + "role": { + "domain_id": "1789d1", + "id": "9fe1d3", + "links": { + "self": "https://example.com/identity/v3/roles/9fe1d3" + }, + "name": "support", + "extra": { + "description": "admin read-only support role" + } + } +} +` + +const ListAssignmentOutput = ` +{ + "role_assignments": [ + { + "links": { + "assignment": "http://identity:35357/v3/domains/161718/users/313233/roles/123456" + }, + "role": { + "id": "123456" + }, + "scope": { + "domain": { + "id": "161718" + } + }, + "user": { + "id": "313233" + } + }, + { + "links": { + "assignment": "http://identity:35357/v3/projects/456789/groups/101112/roles/123456", + "membership": "http://identity:35357/v3/groups/101112/users/313233" + }, + "role": { + "id": "123456" + }, + "scope": { + "project": { + "id": "456789" + } + }, + "user": { + "id": "313233" + } + } + ], + "links": { + "self": "http://identity:35357/v3/role_assignments?effective", + "previous": null, + "next": null + } +} +` + +// FirstRole is the first role in the List request. +var FirstRole = roles.Role{ + DomainID: "default", + ID: "2844b2a08be147a08ef58317d6471f1f", + Links: map[string]interface{}{ + "self": "http://example.com/identity/v3/roles/2844b2a08be147a08ef58317d6471f1f", + }, + Name: "admin-read-only", + Extra: map[string]interface{}{}, +} + +// SecondRole is the second role in the List request. +var SecondRole = roles.Role{ + DomainID: "1789d1", + ID: "9fe1d3", + Links: map[string]interface{}{ + "self": "https://example.com/identity/v3/roles/9fe1d3", + }, + Name: "support", + Extra: map[string]interface{}{ + "description": "read-only support role", + }, +} + +// SecondRoleUpdated is how SecondRole should look after an Update. +var SecondRoleUpdated = roles.Role{ + DomainID: "1789d1", + ID: "9fe1d3", + Links: map[string]interface{}{ + "self": "https://example.com/identity/v3/roles/9fe1d3", + }, + Name: "support", + Extra: map[string]interface{}{ + "description": "admin read-only support role", + }, +} + +// ExpectedRolesSlice is the slice of roles expected to be returned from ListOutput. +var ExpectedRolesSlice = []roles.Role{FirstRole, SecondRole} + +// HandleListRolesSuccessfully creates an HTTP handler at `/roles` on the +// test handler mux that responds with a list of two roles. +func HandleListRolesSuccessfully(t *testing.T) { + th.Mux.HandleFunc("/roles", func(w http.ResponseWriter, r *http.Request) { + th.TestMethod(t, r, "GET") + th.TestHeader(t, r, "Accept", "application/json") + th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) + + w.Header().Set("Content-Type", "application/json") + w.WriteHeader(http.StatusOK) + fmt.Fprintf(w, ListOutput) + }) +} + +// HandleGetRoleSuccessfully creates an HTTP handler at `/roles` on the +// test handler mux that responds with a single role. +func HandleGetRoleSuccessfully(t *testing.T) { + th.Mux.HandleFunc("/roles/9fe1d3", func(w http.ResponseWriter, r *http.Request) { + th.TestMethod(t, r, "GET") + th.TestHeader(t, r, "Accept", "application/json") + th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) + + w.Header().Set("Content-Type", "application/json") + w.WriteHeader(http.StatusOK) + fmt.Fprintf(w, GetOutput) + }) +} + +// HandleCreateRoleSuccessfully creates an HTTP handler at `/roles` on the +// test handler mux that tests role creation. +func HandleCreateRoleSuccessfully(t *testing.T) { + th.Mux.HandleFunc("/roles", func(w http.ResponseWriter, r *http.Request) { + th.TestMethod(t, r, "POST") + th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) + th.TestJSONRequest(t, r, CreateRequest) + + w.WriteHeader(http.StatusCreated) + fmt.Fprintf(w, GetOutput) + }) +} + +// HandleUpdateRoleSuccessfully creates an HTTP handler at `/roles` on the +// test handler mux that tests role update. +func HandleUpdateRoleSuccessfully(t *testing.T) { + th.Mux.HandleFunc("/roles/9fe1d3", func(w http.ResponseWriter, r *http.Request) { + th.TestMethod(t, r, "PATCH") + th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) + th.TestJSONRequest(t, r, UpdateRequest) + + w.WriteHeader(http.StatusOK) + fmt.Fprintf(w, UpdateOutput) + }) +} + +// HandleDeleteRoleSuccessfully creates an HTTP handler at `/roles` on the +// test handler mux that tests role deletion. +func HandleDeleteRoleSuccessfully(t *testing.T) { + th.Mux.HandleFunc("/roles/9fe1d3", func(w http.ResponseWriter, r *http.Request) { + th.TestMethod(t, r, "DELETE") + th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) + + w.WriteHeader(http.StatusNoContent) + }) +} + +func HandleAssignSuccessfully(t *testing.T) { + th.Mux.HandleFunc("/projects/{project_id}/users/{user_id}/roles/{role_id}", func(w http.ResponseWriter, r *http.Request) { + th.TestMethod(t, r, "PUT") + th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) + w.WriteHeader(http.StatusNoContent) + }) + + th.Mux.HandleFunc("/projects/{project_id}/groups/{group_id}/roles/{role_id}", func(w http.ResponseWriter, r *http.Request) { + th.TestMethod(t, r, "PUT") + th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) + w.WriteHeader(http.StatusNoContent) + }) + + th.Mux.HandleFunc("/domains/{domain_id}/users/{user_id}/roles/{role_id}", func(w http.ResponseWriter, r *http.Request) { + th.TestMethod(t, r, "PUT") + th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) + w.WriteHeader(http.StatusNoContent) + }) + + th.Mux.HandleFunc("/domains/{domain_id}/groups/{group_id}/roles/{role_id}", func(w http.ResponseWriter, r *http.Request) { + th.TestMethod(t, r, "PUT") + th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) + w.WriteHeader(http.StatusNoContent) + }) +} + +func HandleUnassignSuccessfully(t *testing.T) { + th.Mux.HandleFunc("/projects/{project_id}/users/{user_id}/roles/{role_id}", func(w http.ResponseWriter, r *http.Request) { + th.TestMethod(t, r, "DELETE") + th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) + w.WriteHeader(http.StatusNoContent) + }) + + th.Mux.HandleFunc("/projects/{project_id}/groups/{group_id}/roles/{role_id}", func(w http.ResponseWriter, r *http.Request) { + th.TestMethod(t, r, "DELETE") + th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) + w.WriteHeader(http.StatusNoContent) + }) + + th.Mux.HandleFunc("/domains/{domain_id}/users/{user_id}/roles/{role_id}", func(w http.ResponseWriter, r *http.Request) { + th.TestMethod(t, r, "DELETE") + th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) + w.WriteHeader(http.StatusNoContent) + }) + + th.Mux.HandleFunc("/domains/{domain_id}/groups/{group_id}/roles/{role_id}", func(w http.ResponseWriter, r *http.Request) { + th.TestMethod(t, r, "DELETE") + th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) + w.WriteHeader(http.StatusNoContent) + }) +} + +// FirstRoleAssignment is the first role assignment in the List request. +var FirstRoleAssignment = roles.RoleAssignment{ + Role: roles.AssignedRole{ID: "123456"}, + Scope: roles.Scope{Domain: roles.Domain{ID: "161718"}}, + User: roles.User{ID: "313233"}, + Group: roles.Group{}, +} + +// SecondRoleAssignemnt is the second role assignemnt in the List request. +var SecondRoleAssignment = roles.RoleAssignment{ + Role: roles.AssignedRole{ID: "123456"}, + Scope: roles.Scope{Project: roles.Project{ID: "456789"}}, + User: roles.User{ID: "313233"}, + Group: roles.Group{}, +} + +// ExpectedRoleAssignmentsSlice is the slice of role assignments expected to be +// returned from ListAssignmentOutput. +var ExpectedRoleAssignmentsSlice = []roles.RoleAssignment{FirstRoleAssignment, SecondRoleAssignment} + +// HandleListRoleAssignmentsSuccessfully creates an HTTP handler at `/role_assignments` on the +// test handler mux that responds with a list of two role assignments. +func HandleListRoleAssignmentsSuccessfully(t *testing.T) { + th.Mux.HandleFunc("/role_assignments", func(w http.ResponseWriter, r *http.Request) { + th.TestMethod(t, r, "GET") + th.TestHeader(t, r, "Accept", "application/json") + th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) + + w.Header().Set("Content-Type", "application/json") + w.WriteHeader(http.StatusOK) + fmt.Fprintf(w, ListAssignmentOutput) + }) +} diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/identity/v3/roles/testing/requests_test.go b/vendor/github.com/gophercloud/gophercloud/openstack/identity/v3/roles/testing/requests_test.go index dd9b704d8d..c683197bfa 100644 --- a/vendor/github.com/gophercloud/gophercloud/openstack/identity/v3/roles/testing/requests_test.go +++ b/vendor/github.com/gophercloud/gophercloud/openstack/identity/v3/roles/testing/requests_test.go @@ -1,105 +1,176 @@ package testing import ( - "fmt" - "net/http" - "reflect" "testing" "github.com/gophercloud/gophercloud/openstack/identity/v3/roles" "github.com/gophercloud/gophercloud/pagination" - "github.com/gophercloud/gophercloud/testhelper" + th "github.com/gophercloud/gophercloud/testhelper" "github.com/gophercloud/gophercloud/testhelper/client" ) -func TestListSinglePage(t *testing.T) { - testhelper.SetupHTTP() - defer testhelper.TeardownHTTP() +func TestListRoles(t *testing.T) { + th.SetupHTTP() + defer th.TeardownHTTP() + HandleListRolesSuccessfully(t) - testhelper.Mux.HandleFunc("/role_assignments", func(w http.ResponseWriter, r *http.Request) { - testhelper.TestMethod(t, r, "GET") - testhelper.TestHeader(t, r, "X-Auth-Token", client.TokenID) + count := 0 + err := roles.List(client.ServiceClient(), nil).EachPage(func(page pagination.Page) (bool, error) { + count++ - w.Header().Add("Content-Type", "application/json") - fmt.Fprintf(w, ` - { - "role_assignments": [ - { - "links": { - "assignment": "http://identity:35357/v3/domains/161718/users/313233/roles/123456" - }, - "role": { - "id": "123456" - }, - "scope": { - "domain": { - "id": "161718" - } - }, - "user": { - "id": "313233" - } - }, - { - "links": { - "assignment": "http://identity:35357/v3/projects/456789/groups/101112/roles/123456", - "membership": "http://identity:35357/v3/groups/101112/users/313233" - }, - "role": { - "id": "123456" - }, - "scope": { - "project": { - "id": "456789" - } - }, - "user": { - "id": "313233" - } - } - ], - "links": { - "self": "http://identity:35357/v3/role_assignments?effective", - "previous": null, - "next": null - } - } - `) + actual, err := roles.ExtractRoles(page) + th.AssertNoErr(t, err) + + th.CheckDeepEquals(t, ExpectedRolesSlice, actual) + + return true, nil }) + th.AssertNoErr(t, err) + th.CheckEquals(t, count, 1) +} + +func TestListRolesAllPages(t *testing.T) { + th.SetupHTTP() + defer th.TeardownHTTP() + HandleListRolesSuccessfully(t) + + allPages, err := roles.List(client.ServiceClient(), nil).AllPages() + th.AssertNoErr(t, err) + actual, err := roles.ExtractRoles(allPages) + th.AssertNoErr(t, err) + th.CheckDeepEquals(t, ExpectedRolesSlice, actual) + th.AssertEquals(t, ExpectedRolesSlice[1].Extra["description"], "read-only support role") +} + +func TestGetRole(t *testing.T) { + th.SetupHTTP() + defer th.TeardownHTTP() + HandleGetRoleSuccessfully(t) + + actual, err := roles.Get(client.ServiceClient(), "9fe1d3").Extract() + + th.AssertNoErr(t, err) + th.CheckDeepEquals(t, SecondRole, *actual) +} + +func TestCreateRole(t *testing.T) { + th.SetupHTTP() + defer th.TeardownHTTP() + HandleCreateRoleSuccessfully(t) + + createOpts := roles.CreateOpts{ + Name: "support", + DomainID: "1789d1", + Extra: map[string]interface{}{ + "description": "read-only support role", + }, + } + + actual, err := roles.Create(client.ServiceClient(), createOpts).Extract() + th.AssertNoErr(t, err) + th.CheckDeepEquals(t, SecondRole, *actual) +} + +func TestUpdateRole(t *testing.T) { + th.SetupHTTP() + defer th.TeardownHTTP() + HandleUpdateRoleSuccessfully(t) + + updateOpts := roles.UpdateOpts{ + Extra: map[string]interface{}{ + "description": "admin read-only support role", + }, + } + + actual, err := roles.Update(client.ServiceClient(), "9fe1d3", updateOpts).Extract() + th.AssertNoErr(t, err) + th.CheckDeepEquals(t, SecondRoleUpdated, *actual) +} + +func TestDeleteRole(t *testing.T) { + th.SetupHTTP() + defer th.TeardownHTTP() + HandleDeleteRoleSuccessfully(t) + + res := roles.Delete(client.ServiceClient(), "9fe1d3") + th.AssertNoErr(t, res.Err) +} + +func TestListAssignmentsSinglePage(t *testing.T) { + th.SetupHTTP() + defer th.TeardownHTTP() + HandleListRoleAssignmentsSuccessfully(t) count := 0 err := roles.ListAssignments(client.ServiceClient(), roles.ListAssignmentsOpts{}).EachPage(func(page pagination.Page) (bool, error) { count++ actual, err := roles.ExtractRoleAssignments(page) - if err != nil { - return false, err - } + th.AssertNoErr(t, err) - expected := []roles.RoleAssignment{ - { - Role: roles.Role{ID: "123456"}, - Scope: roles.Scope{Domain: roles.Domain{ID: "161718"}}, - User: roles.User{ID: "313233"}, - Group: roles.Group{}, - }, - { - Role: roles.Role{ID: "123456"}, - Scope: roles.Scope{Project: roles.Project{ID: "456789"}}, - User: roles.User{ID: "313233"}, - Group: roles.Group{}, - }, - } - - if !reflect.DeepEqual(expected, actual) { - t.Errorf("Expected %#v, got %#v", expected, actual) - } + th.CheckDeepEquals(t, ExpectedRoleAssignmentsSlice, actual) return true, nil }) - if err != nil { - t.Errorf("Unexpected error while paging: %v", err) - } - if count != 1 { - t.Errorf("Expected 1 page, got %d", count) - } + th.AssertNoErr(t, err) + th.CheckEquals(t, count, 1) +} + +func TestAssign(t *testing.T) { + th.SetupHTTP() + defer th.TeardownHTTP() + HandleAssignSuccessfully(t) + + err := roles.Assign(client.ServiceClient(), "{role_id}", roles.AssignOpts{ + UserID: "{user_id}", + ProjectID: "{project_id}", + }).ExtractErr() + th.AssertNoErr(t, err) + + err = roles.Assign(client.ServiceClient(), "{role_id}", roles.AssignOpts{ + UserID: "{user_id}", + DomainID: "{domain_id}", + }).ExtractErr() + th.AssertNoErr(t, err) + + err = roles.Assign(client.ServiceClient(), "{role_id}", roles.AssignOpts{ + GroupID: "{group_id}", + ProjectID: "{project_id}", + }).ExtractErr() + th.AssertNoErr(t, err) + + err = roles.Assign(client.ServiceClient(), "{role_id}", roles.AssignOpts{ + GroupID: "{group_id}", + DomainID: "{domain_id}", + }).ExtractErr() + th.AssertNoErr(t, err) +} + +func TestUnassign(t *testing.T) { + th.SetupHTTP() + defer th.TeardownHTTP() + HandleUnassignSuccessfully(t) + + err := roles.Unassign(client.ServiceClient(), "{role_id}", roles.UnassignOpts{ + UserID: "{user_id}", + ProjectID: "{project_id}", + }).ExtractErr() + th.AssertNoErr(t, err) + + err = roles.Unassign(client.ServiceClient(), "{role_id}", roles.UnassignOpts{ + UserID: "{user_id}", + DomainID: "{domain_id}", + }).ExtractErr() + th.AssertNoErr(t, err) + + err = roles.Unassign(client.ServiceClient(), "{role_id}", roles.UnassignOpts{ + GroupID: "{group_id}", + ProjectID: "{project_id}", + }).ExtractErr() + th.AssertNoErr(t, err) + + err = roles.Unassign(client.ServiceClient(), "{role_id}", roles.UnassignOpts{ + GroupID: "{group_id}", + DomainID: "{domain_id}", + }).ExtractErr() + th.AssertNoErr(t, err) } diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/identity/v3/roles/urls.go b/vendor/github.com/gophercloud/gophercloud/openstack/identity/v3/roles/urls.go index 8d87b6e7d4..38d592dca6 100644 --- a/vendor/github.com/gophercloud/gophercloud/openstack/identity/v3/roles/urls.go +++ b/vendor/github.com/gophercloud/gophercloud/openstack/identity/v3/roles/urls.go @@ -2,6 +2,34 @@ package roles import "github.com/gophercloud/gophercloud" +const ( + rolePath = "roles" +) + +func listURL(client *gophercloud.ServiceClient) string { + return client.ServiceURL(rolePath) +} + +func getURL(client *gophercloud.ServiceClient, roleID string) string { + return client.ServiceURL(rolePath, roleID) +} + +func createURL(client *gophercloud.ServiceClient) string { + return client.ServiceURL(rolePath) +} + +func updateURL(client *gophercloud.ServiceClient, roleID string) string { + return client.ServiceURL(rolePath, roleID) +} + +func deleteURL(client *gophercloud.ServiceClient, roleID string) string { + return client.ServiceURL(rolePath, roleID) +} + func listAssignmentsURL(client *gophercloud.ServiceClient) string { return client.ServiceURL("role_assignments") } + +func assignURL(client *gophercloud.ServiceClient, targetType, targetID, actorType, actorID, roleID string) string { + return client.ServiceURL(targetType, targetID, actorType, actorID, rolePath, roleID) +} diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/identity/v3/users/BUILD.bazel b/vendor/github.com/gophercloud/gophercloud/openstack/identity/v3/users/BUILD.bazel index 436506336e..2beecddcf9 100644 --- a/vendor/github.com/gophercloud/gophercloud/openstack/identity/v3/users/BUILD.bazel +++ b/vendor/github.com/gophercloud/gophercloud/openstack/identity/v3/users/BUILD.bazel @@ -13,6 +13,7 @@ go_library( "//vendor/github.com/gophercloud/gophercloud:go_default_library", "//vendor/github.com/gophercloud/gophercloud/internal:go_default_library", "//vendor/github.com/gophercloud/gophercloud/openstack/identity/v3/groups:go_default_library", + "//vendor/github.com/gophercloud/gophercloud/openstack/identity/v3/projects:go_default_library", "//vendor/github.com/gophercloud/gophercloud/pagination:go_default_library", ], ) diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/identity/v3/users/doc.go b/vendor/github.com/gophercloud/gophercloud/openstack/identity/v3/users/doc.go index 8a8bf8451b..aa7ec196f5 100644 --- a/vendor/github.com/gophercloud/gophercloud/openstack/identity/v3/users/doc.go +++ b/vendor/github.com/gophercloud/gophercloud/openstack/identity/v3/users/doc.go @@ -71,7 +71,7 @@ Example to List Groups a User Belongs To panic(err) } - allGroups, err := users.ExtractGroups(allPages) + allGroups, err := groups.ExtractGroups(allPages) if err != nil { panic(err) } @@ -79,5 +79,45 @@ Example to List Groups a User Belongs To for _, group := range allGroups { fmt.Printf("%+v\n", group) } + +Example to List Projects a User Belongs To + + userID := "0fe36e73809d46aeae6705c39077b1b3" + + allPages, err := users.ListProjects(identityClient, userID).AllPages() + if err != nil { + panic(err) + } + + allProjects, err := projects.ExtractProjects(allPages) + if err != nil { + panic(err) + } + + for _, project := range allProjects { + fmt.Printf("%+v\n", project) + } + +Example to List Users in a Group + + groupID := "bede500ee1124ae9b0006ff859758b3a" + listOpts := users.ListOpts{ + DomainID: "default", + } + + allPages, err := users.ListInGroup(identityClient, groupID, listOpts).AllPages() + if err != nil { + panic(err) + } + + allUsers, err := users.ExtractUsers(allPages) + if err != nil { + panic(err) + } + + for _, user := range allUsers { + fmt.Printf("%+v\n", user) + } + */ package users diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/identity/v3/users/requests.go b/vendor/github.com/gophercloud/gophercloud/openstack/identity/v3/users/requests.go index d0dc26f64d..779d116fcc 100644 --- a/vendor/github.com/gophercloud/gophercloud/openstack/identity/v3/users/requests.go +++ b/vendor/github.com/gophercloud/gophercloud/openstack/identity/v3/users/requests.go @@ -3,6 +3,7 @@ package users import ( "github.com/gophercloud/gophercloud" "github.com/gophercloud/gophercloud/openstack/identity/v3/groups" + "github.com/gophercloud/gophercloud/openstack/identity/v3/projects" "github.com/gophercloud/gophercloud/pagination" ) @@ -213,6 +214,29 @@ func Delete(client *gophercloud.ServiceClient, userID string) (r DeleteResult) { func ListGroups(client *gophercloud.ServiceClient, userID string) pagination.Pager { url := listGroupsURL(client, userID) return pagination.NewPager(client, url, func(r pagination.PageResult) pagination.Page { - return groups.GroupPage{pagination.LinkedPageBase{PageResult: r}} + return groups.GroupPage{LinkedPageBase: pagination.LinkedPageBase{PageResult: r}} + }) +} + +// ListProjects enumerates groups user belongs to. +func ListProjects(client *gophercloud.ServiceClient, userID string) pagination.Pager { + url := listProjectsURL(client, userID) + return pagination.NewPager(client, url, func(r pagination.PageResult) pagination.Page { + return projects.ProjectPage{LinkedPageBase: pagination.LinkedPageBase{PageResult: r}} + }) +} + +// ListInGroup enumerates users that belong to a group. +func ListInGroup(client *gophercloud.ServiceClient, groupID string, opts ListOptsBuilder) pagination.Pager { + url := listInGroupURL(client, groupID) + if opts != nil { + query, err := opts.ToUserListQuery() + if err != nil { + return pagination.Pager{Err: err} + } + url += query + } + return pagination.NewPager(client, url, func(r pagination.PageResult) pagination.Page { + return UserPage{pagination.LinkedPageBase{PageResult: r}} }) } diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/identity/v3/users/results.go b/vendor/github.com/gophercloud/gophercloud/openstack/identity/v3/users/results.go index 5716edc304..c474e882b9 100644 --- a/vendor/github.com/gophercloud/gophercloud/openstack/identity/v3/users/results.go +++ b/vendor/github.com/gophercloud/gophercloud/openstack/identity/v3/users/results.go @@ -98,8 +98,8 @@ type UpdateResult struct { userResult } -// DeleteResult is the response from a Delete operation. Call its ExtractErr -// method to interpret it as a User. +// DeleteResult is the response from a Delete operation. Call its ExtractErr to +// determine if the request succeeded or failed. type DeleteResult struct { gophercloud.ErrResult } diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/identity/v3/users/testing/BUILD.bazel b/vendor/github.com/gophercloud/gophercloud/openstack/identity/v3/users/testing/BUILD.bazel index d904b6bef1..ec04586335 100644 --- a/vendor/github.com/gophercloud/gophercloud/openstack/identity/v3/users/testing/BUILD.bazel +++ b/vendor/github.com/gophercloud/gophercloud/openstack/identity/v3/users/testing/BUILD.bazel @@ -7,6 +7,7 @@ go_library( deps = [ "//vendor/github.com/gophercloud/gophercloud:go_default_library", "//vendor/github.com/gophercloud/gophercloud/openstack/identity/v3/groups:go_default_library", + "//vendor/github.com/gophercloud/gophercloud/openstack/identity/v3/projects:go_default_library", "//vendor/github.com/gophercloud/gophercloud/openstack/identity/v3/users:go_default_library", "//vendor/github.com/gophercloud/gophercloud/testhelper:go_default_library", "//vendor/github.com/gophercloud/gophercloud/testhelper/client:go_default_library", @@ -19,6 +20,7 @@ go_test( library = ":go_default_library", deps = [ "//vendor/github.com/gophercloud/gophercloud/openstack/identity/v3/groups:go_default_library", + "//vendor/github.com/gophercloud/gophercloud/openstack/identity/v3/projects:go_default_library", "//vendor/github.com/gophercloud/gophercloud/openstack/identity/v3/users:go_default_library", "//vendor/github.com/gophercloud/gophercloud/pagination:go_default_library", "//vendor/github.com/gophercloud/gophercloud/testhelper:go_default_library", diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/identity/v3/users/testing/fixtures.go b/vendor/github.com/gophercloud/gophercloud/openstack/identity/v3/users/testing/fixtures.go index 00a0e3493f..8d8e6df642 100644 --- a/vendor/github.com/gophercloud/gophercloud/openstack/identity/v3/users/testing/fixtures.go +++ b/vendor/github.com/gophercloud/gophercloud/openstack/identity/v3/users/testing/fixtures.go @@ -8,6 +8,7 @@ import ( "github.com/gophercloud/gophercloud" "github.com/gophercloud/gophercloud/openstack/identity/v3/groups" + "github.com/gophercloud/gophercloud/openstack/identity/v3/projects" "github.com/gophercloud/gophercloud/openstack/identity/v3/users" th "github.com/gophercloud/gophercloud/testhelper" "github.com/gophercloud/gophercloud/testhelper/client" @@ -195,6 +196,41 @@ const ListGroupsOutput = ` } ` +// ListProjectsOutput provides a ListProjects result. +const ListProjectsOutput = ` +{ + "links": { + "next": null, + "previous": null, + "self": "http://localhost:5000/identity/v3/users/foobar/projects" + }, + "projects": [ + { + "description": "my first project", + "domain_id": "11111", + "enabled": true, + "id": "abcde", + "links": { + "self": "http://localhost:5000/identity/v3/projects/abcde" + }, + "name": "project 1", + "parent_id": "11111" + }, + { + "description": "my second project", + "domain_id": "22222", + "enabled": true, + "id": "bcdef", + "links": { + "self": "http://localhost:5000/identity/v3/projects/bcdef" + }, + "name": "project 2", + "parent_id": "22222" + } + ] +} +` + // FirstUser is the first user in the List request. var nilTime time.Time var FirstUser = users.User{ @@ -300,6 +336,26 @@ var SecondGroup = groups.Group{ var ExpectedGroupsSlice = []groups.Group{FirstGroup, SecondGroup} +var FirstProject = projects.Project{ + Description: "my first project", + DomainID: "11111", + Enabled: true, + ID: "abcde", + Name: "project 1", + ParentID: "11111", +} + +var SecondProject = projects.Project{ + Description: "my second project", + DomainID: "22222", + Enabled: true, + ID: "bcdef", + Name: "project 2", + ParentID: "22222", +} + +var ExpectedProjectsSlice = []projects.Project{FirstProject, SecondProject} + // HandleListUsersSuccessfully creates an HTTP handler at `/users` on the // test handler mux that responds with a list of two users. func HandleListUsersSuccessfully(t *testing.T) { @@ -379,7 +435,7 @@ func HandleDeleteUserSuccessfully(t *testing.T) { } // HandleListUserGroupsSuccessfully creates an HTTP handler at /users/{userID}/groups -// on the test handler mux that respons wit a list of two groups +// on the test handler mux that respons with a list of two groups func HandleListUserGroupsSuccessfully(t *testing.T) { th.Mux.HandleFunc("/users/9fe1d3/groups", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "GET") @@ -391,3 +447,31 @@ func HandleListUserGroupsSuccessfully(t *testing.T) { fmt.Fprintf(w, ListGroupsOutput) }) } + +// HandleListUserProjectsSuccessfully creates an HTTP handler at /users/{userID}/projects +// on the test handler mux that respons wit a list of two projects +func HandleListUserProjectsSuccessfully(t *testing.T) { + th.Mux.HandleFunc("/users/9fe1d3/projects", func(w http.ResponseWriter, r *http.Request) { + th.TestMethod(t, r, "GET") + th.TestHeader(t, r, "Accept", "application/json") + th.TestHeader(t, r, "X-Auth-Token", client.TokenID) + + w.Header().Set("Content-Type", "application/json") + w.WriteHeader(http.StatusOK) + fmt.Fprintf(w, ListProjectsOutput) + }) +} + +// HandleListInGroupSuccessfully creates an HTTP handler at /groups/{groupID}/users +// on the test handler mux that response with a list of two users +func HandleListInGroupSuccessfully(t *testing.T) { + th.Mux.HandleFunc("/groups/ea167b/users", func(w http.ResponseWriter, r *http.Request) { + th.TestMethod(t, r, "GET") + th.TestHeader(t, r, "Accept", "application/json") + th.TestHeader(t, r, "X-Auth-Token", client.TokenID) + + w.Header().Set("Content-Type", "application/json") + w.WriteHeader(http.StatusOK) + fmt.Fprintf(w, ListOutput) + }) +} diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/identity/v3/users/testing/requests_test.go b/vendor/github.com/gophercloud/gophercloud/openstack/identity/v3/users/testing/requests_test.go index 5ba21f9de6..15314ca61c 100644 --- a/vendor/github.com/gophercloud/gophercloud/openstack/identity/v3/users/testing/requests_test.go +++ b/vendor/github.com/gophercloud/gophercloud/openstack/identity/v3/users/testing/requests_test.go @@ -4,6 +4,7 @@ import ( "testing" "github.com/gophercloud/gophercloud/openstack/identity/v3/groups" + "github.com/gophercloud/gophercloud/openstack/identity/v3/projects" "github.com/gophercloud/gophercloud/openstack/identity/v3/users" "github.com/gophercloud/gophercloud/pagination" th "github.com/gophercloud/gophercloud/testhelper" @@ -146,3 +147,31 @@ func TestListUserGroups(t *testing.T) { th.AssertNoErr(t, err) th.CheckDeepEquals(t, ExpectedGroupsSlice, actual) } + +func TestListUserProjects(t *testing.T) { + th.SetupHTTP() + defer th.TeardownHTTP() + HandleListUserProjectsSuccessfully(t) + allPages, err := users.ListProjects(client.ServiceClient(), "9fe1d3").AllPages() + th.AssertNoErr(t, err) + actual, err := projects.ExtractProjects(allPages) + th.AssertNoErr(t, err) + th.CheckDeepEquals(t, ExpectedProjectsSlice, actual) +} + +func TestListInGroup(t *testing.T) { + th.SetupHTTP() + defer th.TeardownHTTP() + HandleListInGroupSuccessfully(t) + + iTrue := true + listOpts := users.ListOpts{ + Enabled: &iTrue, + } + + allPages, err := users.ListInGroup(client.ServiceClient(), "ea167b", listOpts).AllPages() + th.AssertNoErr(t, err) + actual, err := users.ExtractUsers(allPages) + th.AssertNoErr(t, err) + th.CheckDeepEquals(t, ExpectedUsersSlice, actual) +} diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/identity/v3/users/urls.go b/vendor/github.com/gophercloud/gophercloud/openstack/identity/v3/users/urls.go index 9ac605ae42..1db2831b5e 100644 --- a/vendor/github.com/gophercloud/gophercloud/openstack/identity/v3/users/urls.go +++ b/vendor/github.com/gophercloud/gophercloud/openstack/identity/v3/users/urls.go @@ -25,3 +25,11 @@ func deleteURL(client *gophercloud.ServiceClient, userID string) string { func listGroupsURL(client *gophercloud.ServiceClient, userID string) string { return client.ServiceURL("users", userID, "groups") } + +func listProjectsURL(client *gophercloud.ServiceClient, userID string) string { + return client.ServiceURL("users", userID, "projects") +} + +func listInGroupURL(client *gophercloud.ServiceClient, groupID string) string { + return client.ServiceURL("groups", groupID, "users") +} diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/imageservice/v2/images/requests.go b/vendor/github.com/gophercloud/gophercloud/openstack/imageservice/v2/images/requests.go index 387e791ceb..081262f1ff 100644 --- a/vendor/github.com/gophercloud/gophercloud/openstack/imageservice/v2/images/requests.go +++ b/vendor/github.com/gophercloud/gophercloud/openstack/imageservice/v2/images/requests.go @@ -45,6 +45,12 @@ type ListOpts struct { // SizeMax filters on the size_max image property. SizeMax int64 `q:"size_max"` + // Sort sorts the results using the new style of sorting. See the OpenStack + // Image API reference for the exact syntax. + // + // Sort cannot be used with the classic sort options (sort_key and sort_dir). + Sort string `q:"sort"` + // SortKey will sort the results based on a specified image property. SortKey string `q:"sort_key"` diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/external/testing/results_test.go b/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/external/testing/results_test.go index 48448e3dec..64a508824d 100644 --- a/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/external/testing/results_test.go +++ b/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/external/testing/results_test.go @@ -93,8 +93,8 @@ func TestCreate(t *testing.T) { } externalCreateOpts := external.CreateOptsExt{ - networkCreateOpts, - &iFalse, + CreateOptsBuilder: &networkCreateOpts, + External: &iFalse, } _, err := networks.Create(fake.ServiceClient(), externalCreateOpts).Extract() @@ -129,8 +129,8 @@ func TestUpdate(t *testing.T) { } externalUpdateOpts := external.UpdateOptsExt{ - networkUpdateOpts, - &iFalse, + UpdateOptsBuilder: &networkUpdateOpts, + External: &iFalse, } _, err := networks.Update(fake.ServiceClient(), "4e8e5957-649f-477b-9e5b-f1f75b21c03c", externalUpdateOpts).Extract() diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/layer3/routers/results.go b/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/layer3/routers/results.go index d6dbb350e6..e19c8e74c4 100644 --- a/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/layer3/routers/results.go +++ b/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/layer3/routers/results.go @@ -8,7 +8,16 @@ import ( // GatewayInfo represents the information of an external gateway for any // particular network router. type GatewayInfo struct { - NetworkID string `json:"network_id"` + NetworkID string `json:"network_id"` + EnableSNAT *bool `json:"enable_snat,omitempty"` + ExternalFixedIPs []ExternalFixedIP `json:"external_fixed_ips,omitempty"` +} + +// ExternalFixedIP is the IP address and subnet ID of the external gateway of a +// router. +type ExternalFixedIP struct { + IPAddress string `json:"ip_address"` + SubnetID string `json:"subnet_id"` } // Route is a possible route in a router. diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/layer3/routers/testing/requests_test.go b/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/layer3/routers/testing/requests_test.go index bf7f35e54c..5010de6050 100644 --- a/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/layer3/routers/testing/requests_test.go +++ b/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/layer3/routers/testing/requests_test.go @@ -44,6 +44,21 @@ func TestList(t *testing.T) { "tenant_id": "33a40233088643acb66ff6eb0ebea679", "distributed": false, "id": "a9254bdb-2613-4a13-ac4c-adc581fba50d" + }, + { + "status": "ACTIVE", + "external_gateway_info": { + "network_id": "2b37576e-b050-4891-8b20-e1e37a93942a", + "external_fixed_ips": [ + {"ip_address": "192.0.2.17", "subnet_id": "ab561bc4-1a8e-48f2-9fbd-376fcb1a1def"}, + {"ip_address": "198.51.100.33", "subnet_id": "1d699529-bdfd-43f8-bcaa-bff00c547af2"} + ] + }, + "name": "gateway", + "admin_state_up": true, + "tenant_id": "a3e881e0a6534880c5473d95b9442099", + "distributed": false, + "id": "308a035c-005d-4452-a9fe-6f8f2f0c28d8" } ] } @@ -79,6 +94,21 @@ func TestList(t *testing.T) { ID: "a9254bdb-2613-4a13-ac4c-adc581fba50d", TenantID: "33a40233088643acb66ff6eb0ebea679", }, + { + Status: "ACTIVE", + GatewayInfo: routers.GatewayInfo{ + NetworkID: "2b37576e-b050-4891-8b20-e1e37a93942a", + ExternalFixedIPs: []routers.ExternalFixedIP{ + {IPAddress: "192.0.2.17", SubnetID: "ab561bc4-1a8e-48f2-9fbd-376fcb1a1def"}, + {IPAddress: "198.51.100.33", SubnetID: "1d699529-bdfd-43f8-bcaa-bff00c547af2"}, + }, + }, + AdminStateUp: true, + Distributed: false, + Name: "gateway", + ID: "308a035c-005d-4452-a9fe-6f8f2f0c28d8", + TenantID: "a3e881e0a6534880c5473d95b9442099", + }, } th.CheckDeepEquals(t, expected, actual) @@ -106,6 +136,7 @@ func TestCreate(t *testing.T) { "name": "foo_router", "admin_state_up": false, "external_gateway_info":{ + "enable_snat": false, "network_id":"8ca37218-28ff-41cb-9b10-039601ea7e6b" } } @@ -120,7 +151,11 @@ func TestCreate(t *testing.T) { "router": { "status": "ACTIVE", "external_gateway_info": { - "network_id": "8ca37218-28ff-41cb-9b10-039601ea7e6b" + "network_id": "8ca37218-28ff-41cb-9b10-039601ea7e6b", + "enable_snat": false, + "external_fixed_ips": [ + {"ip_address": "192.0.2.17", "subnet_id": "ab561bc4-1a8e-48f2-9fbd-376fcb1a1def"} + ] }, "name": "foo_router", "admin_state_up": false, @@ -133,8 +168,11 @@ func TestCreate(t *testing.T) { }) asu := false - gwi := routers.GatewayInfo{NetworkID: "8ca37218-28ff-41cb-9b10-039601ea7e6b"} - + enableSNAT := false + gwi := routers.GatewayInfo{ + NetworkID: "8ca37218-28ff-41cb-9b10-039601ea7e6b", + EnableSNAT: &enableSNAT, + } options := routers.CreateOpts{ Name: "foo_router", AdminStateUp: &asu, @@ -143,9 +181,14 @@ func TestCreate(t *testing.T) { r, err := routers.Create(fake.ServiceClient(), options).Extract() th.AssertNoErr(t, err) + gwi.ExternalFixedIPs = []routers.ExternalFixedIP{{ + IPAddress: "192.0.2.17", + SubnetID: "ab561bc4-1a8e-48f2-9fbd-376fcb1a1def", + }} + th.AssertEquals(t, "foo_router", r.Name) th.AssertEquals(t, false, r.AdminStateUp) - th.AssertDeepEquals(t, routers.GatewayInfo{NetworkID: "8ca37218-28ff-41cb-9b10-039601ea7e6b"}, r.GatewayInfo) + th.AssertDeepEquals(t, gwi, r.GatewayInfo) } func TestGet(t *testing.T) { @@ -164,7 +207,10 @@ func TestGet(t *testing.T) { "router": { "status": "ACTIVE", "external_gateway_info": { - "network_id": "85d76829-6415-48ff-9c63-5c5ca8c61ac6" + "network_id": "85d76829-6415-48ff-9c63-5c5ca8c61ac6", + "external_fixed_ips": [ + {"ip_address": "198.51.100.33", "subnet_id": "1d699529-bdfd-43f8-bcaa-bff00c547af2"} + ] }, "routes": [ { @@ -186,7 +232,12 @@ func TestGet(t *testing.T) { th.AssertNoErr(t, err) th.AssertEquals(t, n.Status, "ACTIVE") - th.AssertDeepEquals(t, n.GatewayInfo, routers.GatewayInfo{NetworkID: "85d76829-6415-48ff-9c63-5c5ca8c61ac6"}) + th.AssertDeepEquals(t, n.GatewayInfo, routers.GatewayInfo{ + NetworkID: "85d76829-6415-48ff-9c63-5c5ca8c61ac6", + ExternalFixedIPs: []routers.ExternalFixedIP{ + {IPAddress: "198.51.100.33", SubnetID: "1d699529-bdfd-43f8-bcaa-bff00c547af2"}, + }, + }) th.AssertEquals(t, n.Name, "router1") th.AssertEquals(t, n.AdminStateUp, true) th.AssertEquals(t, n.TenantID, "d6554fe62e2f41efbb6e026fad5c1542") @@ -228,7 +279,10 @@ func TestUpdate(t *testing.T) { "router": { "status": "ACTIVE", "external_gateway_info": { - "network_id": "8ca37218-28ff-41cb-9b10-039601ea7e6b" + "network_id": "8ca37218-28ff-41cb-9b10-039601ea7e6b", + "external_fixed_ips": [ + {"ip_address": "192.0.2.17", "subnet_id": "ab561bc4-1a8e-48f2-9fbd-376fcb1a1def"} + ] }, "name": "new_name", "admin_state_up": true, @@ -253,8 +307,12 @@ func TestUpdate(t *testing.T) { n, err := routers.Update(fake.ServiceClient(), "4e8e5957-649f-477b-9e5b-f1f75b21c03c", options).Extract() th.AssertNoErr(t, err) + gwi.ExternalFixedIPs = []routers.ExternalFixedIP{ + {IPAddress: "192.0.2.17", SubnetID: "ab561bc4-1a8e-48f2-9fbd-376fcb1a1def"}, + } + th.AssertEquals(t, n.Name, "new_name") - th.AssertDeepEquals(t, n.GatewayInfo, routers.GatewayInfo{NetworkID: "8ca37218-28ff-41cb-9b10-039601ea7e6b"}) + th.AssertDeepEquals(t, n.GatewayInfo, gwi) th.AssertDeepEquals(t, n.Routes, []routers.Route{{DestinationCIDR: "40.0.1.0/24", NextHop: "10.1.0.10"}}) } diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/sharedfilesystems/v2/sharetypes/requests.go b/vendor/github.com/gophercloud/gophercloud/openstack/sharedfilesystems/v2/sharetypes/requests.go index adb1216240..c2c2049b9f 100644 --- a/vendor/github.com/gophercloud/gophercloud/openstack/sharedfilesystems/v2/sharetypes/requests.go +++ b/vendor/github.com/gophercloud/gophercloud/openstack/sharedfilesystems/v2/sharetypes/requests.go @@ -113,7 +113,7 @@ type SetExtraSpecsOptsBuilder interface { type SetExtraSpecsOpts struct { // A list of all extra specifications to be added to a ShareType - Specs map[string]interface{} `json:"extra_specs"` + ExtraSpecs map[string]interface{} `json:"extra_specs" required:"true"` } // ToShareTypeSetExtraSpecsMap assembles a request body based on the contents of a diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/sharedfilesystems/v2/sharetypes/testing/requests_test.go b/vendor/github.com/gophercloud/gophercloud/openstack/sharedfilesystems/v2/sharetypes/testing/requests_test.go index 1307391b2e..85a9e5a974 100644 --- a/vendor/github.com/gophercloud/gophercloud/openstack/sharedfilesystems/v2/sharetypes/testing/requests_test.go +++ b/vendor/github.com/gophercloud/gophercloud/openstack/sharedfilesystems/v2/sharetypes/testing/requests_test.go @@ -143,7 +143,7 @@ func TestSetExtraSpecs(t *testing.T) { MockSetExtraSpecsResponse(t) options := &sharetypes.SetExtraSpecsOpts{ - Specs: map[string]interface{}{"my_key": "my_value"}, + ExtraSpecs: map[string]interface{}{"my_key": "my_value"}, } es, err := sharetypes.SetExtraSpecs(client.ServiceClient(), "shareTypeID", options).Extract() diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/testing/client_test.go b/vendor/github.com/gophercloud/gophercloud/openstack/testing/client_test.go index 3fe768fa42..6f94e44e0d 100644 --- a/vendor/github.com/gophercloud/gophercloud/openstack/testing/client_test.go +++ b/vendor/github.com/gophercloud/gophercloud/openstack/testing/client_test.go @@ -289,7 +289,7 @@ func TestIdentityAdminV3Client(t *testing.T) { Availability: gophercloud.AvailabilityAdmin, }) th.AssertNoErr(t, err) - th.CheckEquals(t, "http://localhost:35357/", sc.Endpoint) + th.CheckEquals(t, "http://localhost:35357/v3/", sc.Endpoint) } func testAuthenticatedClientFails(t *testing.T, endpoint string) { diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/utils/choose_version.go b/vendor/github.com/gophercloud/gophercloud/openstack/utils/choose_version.go index c605d08444..27da19f91a 100644 --- a/vendor/github.com/gophercloud/gophercloud/openstack/utils/choose_version.go +++ b/vendor/github.com/gophercloud/gophercloud/openstack/utils/choose_version.go @@ -68,11 +68,6 @@ func ChooseVersion(client *gophercloud.ProviderClient, recognized []*Version) (* return nil, "", err } - byID := make(map[string]*Version) - for _, version := range recognized { - byID[version.ID] = version - } - var highest *Version var endpoint string @@ -84,20 +79,22 @@ func ChooseVersion(client *gophercloud.ProviderClient, recognized []*Version) (* } } - if matching, ok := byID[value.ID]; ok { - // Prefer a version that exactly matches the provided endpoint. - if href == identityEndpoint { - if href == "" { - return nil, "", fmt.Errorf("Endpoint missing in version %s response from %s", value.ID, client.IdentityBase) + for _, version := range recognized { + if strings.Contains(value.ID, version.ID) { + // Prefer a version that exactly matches the provided endpoint. + if href == identityEndpoint { + if href == "" { + return nil, "", fmt.Errorf("Endpoint missing in version %s response from %s", value.ID, client.IdentityBase) + } + return version, href, nil } - return matching, href, nil - } - // Otherwise, find the highest-priority version with a whitelisted status. - if goodStatus[strings.ToLower(value.Status)] { - if highest == nil || matching.Priority > highest.Priority { - highest = matching - endpoint = href + // Otherwise, find the highest-priority version with a whitelisted status. + if goodStatus[strings.ToLower(value.Status)] { + if highest == nil || version.Priority > highest.Priority { + highest = version + endpoint = href + } } } } diff --git a/vendor/github.com/gophercloud/gophercloud/script/acceptancetest_environments/keystonev2-lbaasv1.sh b/vendor/github.com/gophercloud/gophercloud/script/acceptancetest_environments/keystonev2-lbaasv1.sh deleted file mode 100644 index c74db62477..0000000000 --- a/vendor/github.com/gophercloud/gophercloud/script/acceptancetest_environments/keystonev2-lbaasv1.sh +++ /dev/null @@ -1,194 +0,0 @@ -#!/bin/bash -# -# This script is useful for creating a devstack environment to run gophercloud -# acceptance tests on. -# -# This can be considered a "legacy" devstack environment since it uses -# Keystone v2 and LBaaS v1. -# -# To run, simply execute this script within a virtual machine. -# -# The following OpenStack versions are installed: -# * OpenStack Mitaka -# * Keystone v2 -# * Glance v1 and v2 -# * Nova v2 and v2.1 -# * Cinder v1 and v2 -# * Trove v1 -# * Swift v1 -# * Neutron v2 -# * Neutron LBaaS v1.0 -# * Neutron FWaaS v2.0 -# * Manila v2 -# -# Go 1.6 is also installed. - -set -e - -cd -sudo apt-get update -sudo apt-get install -y git make mercurial - -sudo wget -O /usr/local/bin/gimme https://raw.githubusercontent.com/travis-ci/gimme/master/gimme -sudo chmod +x /usr/local/bin/gimme -gimme 1.6 >> .bashrc - -mkdir ~/go -eval "$(/usr/local/bin/gimme 1.6)" -echo 'export GOPATH=$HOME/go' >> .bashrc -export GOPATH=$HOME/go -source .bashrc - -go get golang.org/x/crypto/ssh -go get github.com/gophercloud/gophercloud - -git clone https://git.openstack.org/openstack-dev/devstack -b stable/mitaka -cd devstack -cat >local.conf <> openrc -echo export OS_IMAGE_ID="$_IMAGE_ID" >> openrc -echo export OS_NETWORK_ID=$_NETWORK_ID >> openrc -echo export OS_EXTGW_ID=$_EXTGW_ID >> openrc -echo export OS_POOL_NAME="public" >> openrc -echo export OS_FLAVOR_ID=99 >> openrc -echo export OS_FLAVOR_ID_RESIZE=98 >> openrc - -# Manila share-network needs to be created -_IDTOVALUE="-F id -f value" -_NEUTRON_NET_ID=$(neutron net-list --name private $_IDTOVALUE) -_NEUTRON_IPV4_SUB=$(neutron subnet-list \ - --ip_version 4 \ - --network_id "$_NEUTRON_NET_ID" \ - $_IDTOVALUE) - -manila share-network-create \ - --neutron-net-id "$_NEUTRON_NET_ID" \ - --neutron-subnet-id "$_NEUTRON_IPV4_SUB" \ - --name "acc_share_nw" - -_SHARE_NETWORK=$(manila share-network-list \ - --neutron-net-id "$_NEUTRON_NET_ID" \ - --neutron-subnet-id "$_NEUTRON_IPV4_SUB" \ - --name "acc_share_nw" \ - | awk 'FNR == 4 {print $2}') - -echo export OS_SHARE_NETWORK_ID="$_SHARE_NETWORK" >> openrc -source openrc demo diff --git a/vendor/github.com/gophercloud/gophercloud/script/acceptancetest_environments/keystonev3-lbaasv2.sh b/vendor/github.com/gophercloud/gophercloud/script/acceptancetest_environments/keystonev3-lbaasv2.sh deleted file mode 100644 index 5cc9212ddc..0000000000 --- a/vendor/github.com/gophercloud/gophercloud/script/acceptancetest_environments/keystonev3-lbaasv2.sh +++ /dev/null @@ -1,208 +0,0 @@ -#!/bin/bash -# -# This script is useful for creating a devstack environment to run gophercloud -# acceptance tests on. -# -# To run, simply execute this script within a virtual machine. -# -# The following OpenStack versions are installed: -# * OpenStack Mitaka -# * Keystone v3 -# * Glance v1 and v2 -# * Nova v2 and v2.1 -# * Cinder v1 and v2 -# * Trove v1 -# * Swift v1 -# * Neutron v2 -# * Neutron LBaaS v2.0 -# * Neutron FWaaS v2.0 -# -# Go 1.6 is also installed. - -set -e - -cd -sudo apt-get update -sudo apt-get install -y git make mercurial - -sudo wget -O /usr/local/bin/gimme https://raw.githubusercontent.com/travis-ci/gimme/master/gimme -sudo chmod +x /usr/local/bin/gimme -gimme 1.6 >> .bashrc - -mkdir ~/go -eval "$(/usr/local/bin/gimme 1.6)" -echo 'export GOPATH=$HOME/go' >> .bashrc -export GOPATH=$HOME/go - -export PATH=$PATH:$HOME/terraform:$HOME/go/bin -echo 'export PATH=$PATH:$HOME/terraform:$HOME/go/bin' >> .bashrc -source .bashrc - -go get golang.org/x/crypto/ssh -go get github.com/gophercloud/gophercloud - -git clone https://git.openstack.org/openstack-dev/devstack -b stable/mitaka -cd devstack -cat >local.conf <> openrc <> openrc -echo export OS_IMAGE_ID="$_IMAGE_ID" >> openrc -echo export OS_NETWORK_ID=$_NETWORK_ID >> openrc -echo export OS_EXTGW_ID=$_EXTGW_ID >> openrc -echo export OS_POOL_NAME="public" >> openrc -echo export OS_FLAVOR_ID=99 >> openrc -echo export OS_FLAVOR_ID_RESIZE=98 >> openrc -source openrc demo