[node operator] Add GCP client

Signed-off-by: Malte Poll <mp@edgeless.systems>
This commit is contained in:
Malte Poll 2022-07-05 14:39:17 +02:00 committed by Malte Poll
parent 0618a000a7
commit 717570d00a
23 changed files with 2102 additions and 21 deletions

View File

@ -3,11 +3,13 @@ module github.com/edgelesssys/constellation/operators/constellation-node-operato
go 1.17
require (
github.com/googleapis/gax-go/v2 v2.4.0
github.com/medik8s/node-maintenance-operator v0.13.0
github.com/onsi/ginkgo v1.16.5
github.com/onsi/gomega v1.18.1
github.com/stretchr/testify v1.7.5
go.etcd.io/etcd/api/v3 v3.5.4
google.golang.org/genproto v0.0.0-20220624142145-8cd45d7dbd1f
k8s.io/api v0.24.0
k8s.io/apimachinery v0.24.0
k8s.io/client-go v0.24.0
@ -21,14 +23,15 @@ require (
github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e // indirect
github.com/coreos/go-systemd/v22 v22.3.2 // indirect
github.com/coreos/pkg v0.0.0-20180928190104-399ea9e2e55f // indirect
github.com/googleapis/enterprise-certificate-proxy v0.1.0 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
go.etcd.io/etcd/client/pkg/v3 v3.5.4 // indirect
google.golang.org/genproto v0.0.0-20220107163113-42d7afdf6368 // indirect
google.golang.org/grpc v1.40.0 // indirect
go.opencensus.io v0.23.0 // indirect
google.golang.org/grpc v1.47.0 // indirect
)
require (
cloud.google.com/go v0.81.0 // indirect
cloud.google.com/go/compute v1.7.0
github.com/Azure/go-autorest v14.2.0+incompatible // indirect
github.com/Azure/go-autorest/autorest v0.11.18 // indirect
github.com/Azure/go-autorest/autorest/adal v0.9.13 // indirect
@ -53,9 +56,9 @@ require (
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect
github.com/golang/protobuf v1.5.2 // indirect
github.com/google/gnostic v0.5.7-v3refs // indirect
github.com/google/go-cmp v0.5.5 // indirect
github.com/google/go-cmp v0.5.8 // indirect
github.com/google/gofuzz v1.1.0 // indirect
github.com/google/uuid v1.1.2 // indirect
github.com/google/uuid v1.3.0 // indirect
github.com/imdario/mergo v0.3.12 // indirect
github.com/josharian/intern v1.0.0 // indirect
github.com/json-iterator/go v1.1.12 // indirect
@ -74,18 +77,19 @@ require (
go.etcd.io/etcd v3.3.27+incompatible
go.etcd.io/etcd/client/v3 v3.5.4
go.uber.org/atomic v1.7.0 // indirect
go.uber.org/multierr v1.6.0 // indirect
go.uber.org/multierr v1.6.0
go.uber.org/zap v1.19.1 // indirect
golang.org/x/crypto v0.0.0-20220214200702-86341886e292 // indirect
golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd // indirect
golang.org/x/oauth2 v0.0.0-20211104180415-d3ed0bb246c8 // indirect
golang.org/x/sys v0.0.0-20220209214540-3681064d5158 // indirect
golang.org/x/net v0.0.0-20220624214902-1bab6f366d9e // indirect
golang.org/x/oauth2 v0.0.0-20220622183110-fd043fe589d2 // indirect
golang.org/x/sys v0.0.0-20220624220833-87e55d714810 // indirect
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211 // indirect
golang.org/x/text v0.3.7 // indirect
golang.org/x/time v0.0.0-20220210224613-90d013bbcef8 // indirect
gomodules.xyz/jsonpatch/v2 v2.2.0 // indirect
google.golang.org/api v0.86.0
google.golang.org/appengine v1.6.7 // indirect
google.golang.org/protobuf v1.27.1 // indirect
google.golang.org/protobuf v1.28.0
gopkg.in/inf.v0 v0.9.1 // indirect
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 // indirect
gopkg.in/yaml.v2 v2.4.0 // indirect

View File

@ -17,17 +17,35 @@ cloud.google.com/go v0.72.0/go.mod h1:M+5Vjvlc2wnp6tjzE102Dw08nGShTscUx2nZMufOKP
cloud.google.com/go v0.74.0/go.mod h1:VV1xSbzvo+9QJOxLDaJfTjx5e+MePCpCWwvftOeQmWk=
cloud.google.com/go v0.78.0/go.mod h1:QjdrLG0uq+YwhjoVOLsS1t7TW8fs36kLs4XO5R5ECHg=
cloud.google.com/go v0.79.0/go.mod h1:3bzgcEeQlzbuEAYu4mrWhKqWjmpprinYgKJLgKHnbb8=
cloud.google.com/go v0.81.0 h1:at8Tk2zUz63cLPR0JPWm5vp77pEZmzxEQBEfRKn1VV8=
cloud.google.com/go v0.81.0/go.mod h1:mk/AM35KwGk/Nm2YSeZbxXdrNK3KZOYHmLkOqC2V6E0=
cloud.google.com/go v0.83.0/go.mod h1:Z7MJUsANfY0pYPdw0lbnivPx4/vhy/e2FEkSkF7vAVY=
cloud.google.com/go v0.84.0/go.mod h1:RazrYuxIK6Kb7YrzzhPoLmCVzl7Sup4NrbKPg8KHSUM=
cloud.google.com/go v0.87.0/go.mod h1:TpDYlFy7vuLzZMMZ+B6iRiELaY7z/gJPaqbMx6mlWcY=
cloud.google.com/go v0.90.0/go.mod h1:kRX0mNRHe0e2rC6oNakvwQqzyDmg57xJ+SZU1eT2aDQ=
cloud.google.com/go v0.93.3/go.mod h1:8utlLll2EF5XMAV15woO4lSbWQlk8rer9aLOfLh7+YI=
cloud.google.com/go v0.94.1/go.mod h1:qAlAugsXlC+JWO+Bke5vCtc9ONxjQT3drlTTnAplMW4=
cloud.google.com/go v0.97.0/go.mod h1:GF7l59pYBVlXQIBLx3a761cZ41F9bBH3JUlihCt2Udc=
cloud.google.com/go v0.99.0/go.mod h1:w0Xx2nLzqWJPuozYQX+hFfCSI8WioryfRDzkoI/Y2ZA=
cloud.google.com/go v0.100.2/go.mod h1:4Xra9TjzAeYHrl5+oeLlzbM2k3mjVhZh4UqTZ//w99A=
cloud.google.com/go v0.102.0 h1:DAq3r8y4mDgyB/ZPJ9v/5VJNqjgJAxTn6ZYLlUywOu8=
cloud.google.com/go v0.102.0/go.mod h1:oWcCzKlqJ5zgHQt9YsaeTY9KzIvjyy0ArmiBUgpQ+nc=
cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o=
cloud.google.com/go/bigquery v1.3.0/go.mod h1:PjpwJnslEMmckchkHFfq+HTD2DmtT67aNFKH1/VBDHE=
cloud.google.com/go/bigquery v1.4.0/go.mod h1:S8dzgnTigyfTmLBfrtrhyYhwRxG72rYxvftPBK2Dvzc=
cloud.google.com/go/bigquery v1.5.0/go.mod h1:snEHRnqQbz117VIFhE8bmtwIDY80NLUZUMb4Nv6dBIg=
cloud.google.com/go/bigquery v1.7.0/go.mod h1://okPTzCYNXSlb24MZs83e2Do+h+VXtc4gLoIoXIAPc=
cloud.google.com/go/bigquery v1.8.0/go.mod h1:J5hqkt3O0uAFnINi6JXValWIb1v0goeZM77hZzJN/fQ=
cloud.google.com/go/compute v0.1.0/go.mod h1:GAesmwr110a34z04OlxYkATPBEfVhkymfTBXtfbBFow=
cloud.google.com/go/compute v1.3.0/go.mod h1:cCZiE1NHEtai4wiufUhW8I8S1JKkAnhnQJWM7YD99wM=
cloud.google.com/go/compute v1.5.0/go.mod h1:9SMHyhJlzhlkJqrPAc839t2BZFTSk6Jdj6mkzQJeu0M=
cloud.google.com/go/compute v1.6.0/go.mod h1:T29tfhtVbq1wvAPo0E3+7vhgmkOYeXjhFvz/FMzPu0s=
cloud.google.com/go/compute v1.6.1/go.mod h1:g85FgpzFvNULZ+S8AYq87axRKuf2Kh7deLqV/jJ3thU=
cloud.google.com/go/compute v1.7.0 h1:v/k9Eueb8aAJ0vZuxKMrgm6kPhCLZU9HxFU+AFDs9Uk=
cloud.google.com/go/compute v1.7.0/go.mod h1:435lt8av5oL9P3fv1OEzSbSUe+ybHXGMPQHHZWZxy9U=
cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE=
cloud.google.com/go/datastore v1.1.0/go.mod h1:umbIZjpQpHh4hmRpGhH4tLFup+FVzqBi1b3c64qFpCk=
cloud.google.com/go/firestore v1.1.0/go.mod h1:ulACoGHTpvq5r8rxGJ4ddJZBZqakUQqClKRT5SZwBmk=
cloud.google.com/go/iam v0.3.0/go.mod h1:XzJPvDayI+9zsASAFO68Hk07u3z+f+JrT2xXNdp4bnY=
cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I=
cloud.google.com/go/pubsub v1.1.0/go.mod h1:EwwdRX2sKPjnvnqCa270oGRyludottCI76h+R3AArQw=
cloud.google.com/go/pubsub v1.2.0/go.mod h1:jhfEVHT8odbXTkndysNHCcx0awwzvfOlguIAii9o8iA=
@ -37,6 +55,7 @@ cloud.google.com/go/storage v1.5.0/go.mod h1:tpKbwo567HUNpVclU5sGELwQWBDZ8gh0Zeo
cloud.google.com/go/storage v1.6.0/go.mod h1:N7U0C8pVQ/+NIKOBQyamJIeKQKkZ+mxpohlUTyfDhBk=
cloud.google.com/go/storage v1.8.0/go.mod h1:Wv1Oy7z6Yz3DshWRJFhqM/UCfaWIRTdp0RXyy7KQOVs=
cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9ullr3+Kg0=
cloud.google.com/go/storage v1.22.1/go.mod h1:S8N1cAStu7BOeFfE8KAQzmyyLkK8p/vmRq6kuBTW58Y=
dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU=
github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1/go.mod h1:xomTg63KZ2rFqZQzSB4Vz2SUXa1BpHTVz9L5PTmPC4E=
github.com/Azure/go-autorest v14.2.0+incompatible h1:V5VMDjClD3GiElqLWO7mz2MxNAK/vTfRHdAubSIPRgs=
@ -102,7 +121,12 @@ github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDk
github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc=
github.com/cncf/udpa/go v0.0.0-20200629203442-efcf912fb354/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk=
github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk=
github.com/cncf/udpa/go v0.0.0-20210930031921-04548b0d99d4/go.mod h1:6pvJx4me5XPnfI9Z40ddWsdw2W/uZgQLFXToKeRcDiI=
github.com/cncf/xds/go v0.0.0-20210312221358-fbca930ec8ed/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs=
github.com/cncf/xds/go v0.0.0-20210805033703-aa0b78936158/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs=
github.com/cncf/xds/go v0.0.0-20210922020428-25de7278fc84/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs=
github.com/cncf/xds/go v0.0.0-20211001041855-01bcc9b48dfe/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs=
github.com/cncf/xds/go v0.0.0-20211011173535-cb28da3451f1/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs=
github.com/cockroachdb/datadriven v0.0.0-20200714090401-bf6692d28da5/go.mod h1:h6jFvWxBdQXxjopDMZyH2UVceIRfR84bdzbkoKrsWNo=
github.com/cockroachdb/errors v1.2.4/go.mod h1:rQD95gz6FARkaKkQXUksEje/d9a6wBJoCr5oaCLELYA=
github.com/cockroachdb/logtags v0.0.0-20190617123548-eb05cc24525f/go.mod h1:i/u985jwjWRlyHXQbwatDASoW0RMlZ/3i9yJHE2xLkI=
@ -147,6 +171,8 @@ github.com/envoyproxy/go-control-plane v0.9.7/go.mod h1:cwu0lG7PUMfa9snN8LXBig5y
github.com/envoyproxy/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk=
github.com/envoyproxy/go-control-plane v0.9.9-0.20210217033140-668b12f5399d/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk=
github.com/envoyproxy/go-control-plane v0.9.9-0.20210512163311-63b5d3c536b0/go.mod h1:hliV/p42l8fGbc6Y9bQ70uLwIvmJyVE5k4iMKlh8wCQ=
github.com/envoyproxy/go-control-plane v0.9.10-0.20210907150352-cf90f659a021/go.mod h1:AFq3mo9L8Lqqiid3OhADV3RfLJnjiw63cSpi+fDTRC0=
github.com/envoyproxy/go-control-plane v0.10.2-0.20220325020618-49ff273808a1/go.mod h1:KJwIaB5Mv44NWtYuAOFCVOjcI94vtpEz2JU/D2v6IjE=
github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
github.com/evanphx/json-patch v0.5.2/go.mod h1:ZWS5hhDbVDyob71nXKNL0+PWn6ToqBHMikGIFbs31qQ=
github.com/evanphx/json-patch v4.11.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk=
@ -218,6 +244,7 @@ github.com/golang/mock v1.4.1/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt
github.com/golang/mock v1.4.3/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw=
github.com/golang/mock v1.4.4/go.mod h1:l3mdAwkq5BuhzHwde/uurv3sEJeZMXNpwsxVWU71h+4=
github.com/golang/mock v1.5.0/go.mod h1:CWnOUgYIOo4TcNZ0wHX3YZCqsaM1I1Jvs6v3mP3KVu8=
github.com/golang/mock v1.6.0/go.mod h1:p6yTPP+5HYm5mzsMV8JkE6ZKdX+/wYM6Hr+LicevLPs=
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
@ -236,6 +263,7 @@ github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaS
github.com/golang/protobuf v1.5.1/go.mod h1:DopwsBzvsk0Fs44TXzsVbJyPhcCPeIwnvohx4u74HPM=
github.com/golang/protobuf v1.5.2 h1:ROPKBNFfQgOUMifHyP+KYbvpjbdoFNs+aK7DXlji0Tw=
github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
github.com/golang/snappy v0.0.3/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
github.com/golangplus/testing v0.0.0-20180327235837-af21d9c3145e/go.mod h1:0AA//k/eakGydO4jKRoRL2j92ZKSzTgj9tclaCrvXHk=
github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
@ -254,14 +282,18 @@ github.com/google/go-cmp v0.5.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/
github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.3/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.5 h1:Khx7svrCpmxxtHBq5j2mp/xVjsi8hQMfNLvJFAlrGgU=
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.7/go.mod h1:n+brtR0CgQNWTVd5ZUFpTBC8YFBDLK/h/bpaJ8/DtOE=
github.com/google/go-cmp v0.5.8 h1:e6P7q2lk1O+qJJb4BtCQXlK8vWEO8V1ZeuEdJNOqZyg=
github.com/google/go-cmp v0.5.8/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
github.com/google/gofuzz v1.1.0 h1:Hsa8mG0dQ46ij8Sl2AYJDUv1oA9/d6Vk+3LG99Oe02g=
github.com/google/gofuzz v1.1.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs=
github.com/google/martian/v3 v3.0.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0=
github.com/google/martian/v3 v3.1.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0=
github.com/google/martian/v3 v3.2.1/go.mod h1:oBOf6HBosgwRXnUGWUB05QECsc6uvmMiJ3+6W4l/CUk=
github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
github.com/google/pprof v0.0.0-20191218002539-d4f498aebedc/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
@ -274,14 +306,29 @@ github.com/google/pprof v0.0.0-20201203190320-1bf35d6f28c2/go.mod h1:kpwsk12EmLe
github.com/google/pprof v0.0.0-20210122040257-d980be63207e/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
github.com/google/pprof v0.0.0-20210226084205-cbba55b83ad5/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
github.com/google/pprof v0.0.0-20210407192527-94a9f03dee38/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
github.com/google/pprof v0.0.0-20210601050228-01bbb1931b22/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
github.com/google/pprof v0.0.0-20210609004039-a478d1d731e9/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
github.com/google/pprof v0.0.0-20210720184732-4bb14d4b1be1/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI=
github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510/go.mod h1:pupxD2MaaD3pAXIBCelhxNneeOaAeabZDe5s4K6zSpQ=
github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/google/uuid v1.1.2 h1:EVhdT+1Kseyi1/pUmXKaFxYsDNy9RQYkMWRH68J/W7Y=
github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I=
github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/googleapis/enterprise-certificate-proxy v0.0.0-20220520183353-fd19c99a87aa/go.mod h1:17drOmN3MwGY7t0e+Ei9b45FFGA3fBs3x36SsCg1hq8=
github.com/googleapis/enterprise-certificate-proxy v0.1.0 h1:zO8WHNx/MYiAKJ3d5spxZXZE6KHmIQGQcAzwUzV7qQw=
github.com/googleapis/enterprise-certificate-proxy v0.1.0/go.mod h1:17drOmN3MwGY7t0e+Ei9b45FFGA3fBs3x36SsCg1hq8=
github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg=
github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk=
github.com/googleapis/gax-go/v2 v2.1.0/go.mod h1:Q3nei7sK6ybPYH7twZdmQpAd1MKb7pfu6SK+H1/DsU0=
github.com/googleapis/gax-go/v2 v2.1.1/go.mod h1:hddJymUZASv3XPyGkUpKj8pPO47Rmb0eJc8R6ouapiM=
github.com/googleapis/gax-go/v2 v2.2.0/go.mod h1:as02EH8zWkzwUoLbBaFeQ+arQaj/OthfcblKl4IGNaM=
github.com/googleapis/gax-go/v2 v2.3.0/go.mod h1:b8LNqSzNabLiUpXKkY7HAR5jr6bIT99EXz9pXxye9YM=
github.com/googleapis/gax-go/v2 v2.4.0 h1:dS9eYAjhrE2RjmzYw2XAPvcXfmcQLtFEQWn0CR82awk=
github.com/googleapis/gax-go/v2 v2.4.0/go.mod h1:XOTVJ59hdnfJLIP/dh8n5CGryZR2LxK9wbMD5+iXC6c=
github.com/googleapis/gnostic v0.5.1/go.mod h1:6U4PtQXGIEt/Z3h5MAT7FNofLnw9vXk2cUuW7uA/OeU=
github.com/googleapis/gnostic v0.5.1/go.mod h1:6U4PtQXGIEt/Z3h5MAT7FNofLnw9vXk2cUuW7uA/OeU=
github.com/googleapis/go-type-adapters v1.0.0/go.mod h1:zHW75FOG2aur7gAO2B+MLby+cLsWGBF62rFAi7WjWO4=
github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
github.com/gorilla/mux v1.8.0/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So=
github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
@ -552,6 +599,7 @@ go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
go.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
go.opencensus.io v0.22.5/go.mod h1:5pWMHQbX5EPX2/62yrJeAkowc+lfs/XD7Uxpq3pI6kk=
go.opencensus.io v0.23.0 h1:gqCw0LfLxScz8irSi8exQc7fyQ0fKQU/qnC/X8+V/1M=
go.opencensus.io v0.23.0/go.mod h1:XItmlyltB5F7CS4xOC1DcqMoFqwtC6OG2xF7mCv7P7E=
go.opentelemetry.io/contrib v0.20.0/go.mod h1:G/EtFaa6qaN7+LxqfIAT3GiZa7Wv5DTBUzl5H4LY0Kc=
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.20.0/go.mod h1:oVGt1LRbBOBq1A5BQLlUg9UaU/54aiHw8cgjV3aWZ/E=
@ -673,12 +721,22 @@ golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v
golang.org/x/net v0.0.0-20210316092652-d523dce5a7f4/go.mod h1:RBQZq4jEuRlivfhVLdyRGr576XBO4/greRjx4P4O3yc=
golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM=
golang.org/x/net v0.0.0-20210428140749-89ef3d95e781/go.mod h1:OJAsFXCWl8Ukc7SiCT/9KSuxbyM7479/AVlXFRxuMCk=
golang.org/x/net v0.0.0-20210503060351-7fd8e65b6420/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.0.0-20210525063256-abc453219eb5/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.0.0-20210525063256-abc453219eb5/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.0.0-20210825183410-e898025ed96a/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.0.0-20210825183410-e898025ed96a/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.0.0-20211015210444-4f30a5c0130f/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd h1:O7DYs+zxREGLKzKoMQrtrEacpb0ZVXA5rIwylE2Xchk=
golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
golang.org/x/net v0.0.0-20220225172249-27dd8689420f/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
golang.org/x/net v0.0.0-20220325170049-de3da57026de/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
golang.org/x/net v0.0.0-20220412020605-290c469a71a5/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
golang.org/x/net v0.0.0-20220425223048-2871e0cb64e4/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
golang.org/x/net v0.0.0-20220607020251-c690dde0001d/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
golang.org/x/net v0.0.0-20220624214902-1bab6f366d9e h1:TsQ7F31D3bUCLeqPT0u+yjp1guoArKaNKmCr22PYgTQ=
golang.org/x/net v0.0.0-20220624214902-1bab6f366d9e/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
@ -692,8 +750,17 @@ golang.org/x/oauth2 v0.0.0-20210220000619-9bb904979d93/go.mod h1:KelEdhl1UZF7XfJ
golang.org/x/oauth2 v0.0.0-20210313182246-cd4f82c27b84/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
golang.org/x/oauth2 v0.0.0-20210402161424-2e8d93401602/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
golang.org/x/oauth2 v0.0.0-20210514164344-f6687ab2804c/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
golang.org/x/oauth2 v0.0.0-20211104180415-d3ed0bb246c8 h1:RerP+noqYHUQ8CMRcPlC2nvTa4dcBIjegkuWdcUDuqg=
golang.org/x/oauth2 v0.0.0-20210628180205-a41e5a781914/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
golang.org/x/oauth2 v0.0.0-20210805134026-6f1e6394065a/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
golang.org/x/oauth2 v0.0.0-20210819190943-2bc19b11175f/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
golang.org/x/oauth2 v0.0.0-20211104180415-d3ed0bb246c8/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
golang.org/x/oauth2 v0.0.0-20211104180415-d3ed0bb246c8/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
golang.org/x/oauth2 v0.0.0-20220223155221-ee480838109b/go.mod h1:DAh4E804XQdzx2j+YRIaUnCqCV2RuMz24cGBJ5QYIrc=
golang.org/x/oauth2 v0.0.0-20220309155454-6242fa91716a/go.mod h1:DAh4E804XQdzx2j+YRIaUnCqCV2RuMz24cGBJ5QYIrc=
golang.org/x/oauth2 v0.0.0-20220411215720-9780585627b5/go.mod h1:DAh4E804XQdzx2j+YRIaUnCqCV2RuMz24cGBJ5QYIrc=
golang.org/x/oauth2 v0.0.0-20220608161450-d0670ef3b1eb/go.mod h1:jaDAt6Dkxork7LmZnYtzbRWj0W47D86a3TGe0YHBvmE=
golang.org/x/oauth2 v0.0.0-20220622183110-fd043fe589d2 h1:+jnHzr9VPj32ykQVai5DNahi9+NSp7yYuCsl5eAQtL0=
golang.org/x/oauth2 v0.0.0-20220622183110-fd043fe589d2/go.mod h1:jaDAt6Dkxork7LmZnYtzbRWj0W47D86a3TGe0YHBvmE=
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
@ -705,6 +772,7 @@ golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJ
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20220601150217-0de741cfad7f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
@ -763,16 +831,36 @@ golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7w
golang.org/x/sys v0.0.0-20210403161142-5e06dd20ab57/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210514084401-e8d321eab015/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210603081109-ebe580a85c40/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210603125802-9665404d3644/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210616094352-59db8d763f22/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210806184541-e5e7981a1069/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210823070655-63515b42dcdf/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210831042530-f4d43177bf5e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210831042530-f4d43177bf5e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210908233432-aa78b53d3365/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20211019181941-9d821ace8654/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20211124211545-fe61309f8881/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20211210111614-af8b64212486/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220114195835-da31bd327af9/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220209214540-3681064d5158 h1:rm+CHSpPEEW2IsXUib1ThaHIjuBVZjxNgSKmBLFfD4c=
golang.org/x/sys v0.0.0-20220128215802-99c3d69c2c27/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220209214540-3681064d5158/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220209214540-3681064d5158/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220227234510-4e6760a101f9/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220328115105-d36c6a25d886/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220412211240-33da011f77ad/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220502124256-b6088ccd6cba/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220503163025-988cb79eb6c6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220610221304-9f5ed59c137d/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220624220833-87e55d714810 h1:rHZQSjJdAI4Xf5Qzeh2bBc5YJIkPFVM6oDtMFYmgws0=
golang.org/x/sys v0.0.0-20220624220833-87e55d714810/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211 h1:JGgROgKl9N8DuW20oFS5gxc+lE67/N3FcwmBPMe7ArY=
@ -849,14 +937,19 @@ golang.org/x/tools v0.0.0-20201224043029-2b0845dc783e/go.mod h1:emZCQorbCU4vsT4f
golang.org/x/tools v0.0.0-20210105154028-b0ab187a4818/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0=
golang.org/x/tools v0.1.1/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
golang.org/x/tools v0.1.2/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
golang.org/x/tools v0.1.3/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
golang.org/x/tools v0.1.4/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
golang.org/x/tools v0.1.10-0.20220218145154-897bd77cd717/go.mod h1:Uh6Zz+xoGYZom868N8YTex3t7RhtHDBrE8Gzo9bV56E=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE=
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20220411194840-2f41105eb62f/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20220517211312-f3a8303e98df/go.mod h1:K8+ghG5WaK9qNqU5K3HdILfMLy1f3aNYFI/wnl100a8=
golang.org/x/xerrors v0.0.0-20220609144429-65e65417b02f/go.mod h1:K8+ghG5WaK9qNqU5K3HdILfMLy1f3aNYFI/wnl100a8=
gomodules.xyz/jsonpatch/v2 v2.2.0 h1:4pT439QV83L+G9FkcCriY6EkpcK6r6bK+A5FBUMI7qY=
gomodules.xyz/jsonpatch/v2 v2.2.0/go.mod h1:WXp+iVDkoLQqPudfQ9GBlwB2eZ5DKOnjQZCYdOS8GPY=
google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE=
@ -881,6 +974,26 @@ google.golang.org/api v0.40.0/go.mod h1:fYKFpnQN0DsDSKRVRcQSDQNtqWPfM9i+zNPxepjR
google.golang.org/api v0.41.0/go.mod h1:RkxM5lITDfTzmyKFPt+wGrCJbVfniCr2ool8kTBzRTU=
google.golang.org/api v0.43.0/go.mod h1:nQsDGjRXMo4lvh5hP0TKqF244gqhGcr/YSIykhUk/94=
google.golang.org/api v0.44.0/go.mod h1:EBOGZqzyhtvMDoxwS97ctnh0zUmYY6CxqXsc1AvkYD8=
google.golang.org/api v0.47.0/go.mod h1:Wbvgpq1HddcWVtzsVLyfLp8lDg6AA241LmgIL59tHXo=
google.golang.org/api v0.48.0/go.mod h1:71Pr1vy+TAZRPkPs/xlCf5SsU8WjuAWv1Pfjbtukyy4=
google.golang.org/api v0.50.0/go.mod h1:4bNT5pAuq5ji4SRZm+5QIkjny9JAyVD/3gaSihNefaw=
google.golang.org/api v0.51.0/go.mod h1:t4HdrdoNgyN5cbEfm7Lum0lcLDLiise1F8qDKX00sOU=
google.golang.org/api v0.54.0/go.mod h1:7C4bFFOvVDGXjfDTAsgGwDgAxRDeQ4X8NvUedIt6z3k=
google.golang.org/api v0.55.0/go.mod h1:38yMfeP1kfjsl8isn0tliTjIb1rJXcQi4UXlbqivdVE=
google.golang.org/api v0.56.0/go.mod h1:38yMfeP1kfjsl8isn0tliTjIb1rJXcQi4UXlbqivdVE=
google.golang.org/api v0.57.0/go.mod h1:dVPlbZyBo2/OjBpmvNdpn2GRm6rPy75jyU7bmhdrMgI=
google.golang.org/api v0.61.0/go.mod h1:xQRti5UdCmoCEqFxcz93fTl338AVqDgyaDRuOZ3hg9I=
google.golang.org/api v0.63.0/go.mod h1:gs4ij2ffTRXwuzzgJl/56BdwJaA194ijkfn++9tDuPo=
google.golang.org/api v0.67.0/go.mod h1:ShHKP8E60yPsKNw/w8w+VYaj9H6buA5UqDp8dhbQZ6g=
google.golang.org/api v0.70.0/go.mod h1:Bs4ZM2HGifEvXwd50TtW70ovgJffJYw2oRCOFU/SkfA=
google.golang.org/api v0.71.0/go.mod h1:4PyU6e6JogV1f9eA4voyrTY2batOLdgZ5qZ5HOCc4j8=
google.golang.org/api v0.74.0/go.mod h1:ZpfMZOVRMywNyvJFeqL9HRWBgAuRfSjJFpe9QtRRyDs=
google.golang.org/api v0.75.0/go.mod h1:pU9QmyHLnzlpar1Mjt4IbapUCy8J+6HD6GeELN69ljA=
google.golang.org/api v0.78.0/go.mod h1:1Sg78yoMLOhlQTeF+ARBoytAcH1NNyyl390YMy6rKmw=
google.golang.org/api v0.80.0/go.mod h1:xY3nI94gbvBrE0J6NHXhxOmW97HG7Khjkku6AFB3Hyg=
google.golang.org/api v0.84.0/go.mod h1:NTsGnUFJMYROtiquksZHBWtHfeMC7iYthki7Eq3pa8o=
google.golang.org/api v0.86.0 h1:ZAnyOHQFIuWso1BodVfSaRyffD74T9ERGFa3k1fNk/U=
google.golang.org/api v0.86.0/go.mod h1:+Sem1dnrKlrXMR/X0bPnMWyluQe4RsNoYfmNLhOIkzw=
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
@ -931,11 +1044,49 @@ google.golang.org/genproto v0.0.0-20210222152913-aa3ee6e6a81c/go.mod h1:FWY/as6D
google.golang.org/genproto v0.0.0-20210303154014-9728d6b83eeb/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
google.golang.org/genproto v0.0.0-20210310155132-4ce2db91004e/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
google.golang.org/genproto v0.0.0-20210319143718-93e7006c17a6/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
google.golang.org/genproto v0.0.0-20210329143202-679c6ae281ee/go.mod h1:9lPAdzaEmUacj36I+k7YKbEc5CXzPIeORRgDAUOu28A=
google.golang.org/genproto v0.0.0-20210402141018-6c239bbf2bb1/go.mod h1:9lPAdzaEmUacj36I+k7YKbEc5CXzPIeORRgDAUOu28A=
google.golang.org/genproto v0.0.0-20210513213006-bf773b8c8384/go.mod h1:P3QM42oQyzQSnHPnZ/vqoCdDmzH28fzWByN9asMeM8A=
google.golang.org/genproto v0.0.0-20210602131652-f16073e35f0c/go.mod h1:UODoCrxHCcBojKKwX1terBiRUaqAsFqJiF615XL43r0=
google.golang.org/genproto v0.0.0-20210604141403-392c879c8b08/go.mod h1:UODoCrxHCcBojKKwX1terBiRUaqAsFqJiF615XL43r0=
google.golang.org/genproto v0.0.0-20210608205507-b6d2f5bf0d7d/go.mod h1:UODoCrxHCcBojKKwX1terBiRUaqAsFqJiF615XL43r0=
google.golang.org/genproto v0.0.0-20210624195500-8bfb893ecb84/go.mod h1:SzzZ/N+nwJDaO1kznhnlzqS8ocJICar6hYhVyhi++24=
google.golang.org/genproto v0.0.0-20210713002101-d411969a0d9a/go.mod h1:AxrInvYm1dci+enl5hChSFPOmmUF1+uAa/UsgNRWd7k=
google.golang.org/genproto v0.0.0-20210716133855-ce7ef5c701ea/go.mod h1:AxrInvYm1dci+enl5hChSFPOmmUF1+uAa/UsgNRWd7k=
google.golang.org/genproto v0.0.0-20210728212813-7823e685a01f/go.mod h1:ob2IJxKrgPT52GcgX759i1sleT07tiKowYBGbczaW48=
google.golang.org/genproto v0.0.0-20210805201207-89edb61ffb67/go.mod h1:ob2IJxKrgPT52GcgX759i1sleT07tiKowYBGbczaW48=
google.golang.org/genproto v0.0.0-20210813162853-db860fec028c/go.mod h1:cFeNkxwySK631ADgubI+/XFU/xp8FD5KIVV4rj8UC5w=
google.golang.org/genproto v0.0.0-20210821163610-241b8fcbd6c8/go.mod h1:eFjDcFEctNawg4eG61bRv87N7iHBWyVhJu7u1kqDUXY=
google.golang.org/genproto v0.0.0-20210828152312-66f60bf46e71/go.mod h1:eFjDcFEctNawg4eG61bRv87N7iHBWyVhJu7u1kqDUXY=
google.golang.org/genproto v0.0.0-20210831024726-fe130286e0e2/go.mod h1:eFjDcFEctNawg4eG61bRv87N7iHBWyVhJu7u1kqDUXY=
google.golang.org/genproto v0.0.0-20220107163113-42d7afdf6368 h1:Et6SkiuvnBn+SgrSYXs/BrUpGB4mbdwt4R3vaPIlicA=
google.golang.org/genproto v0.0.0-20210831024726-fe130286e0e2/go.mod h1:eFjDcFEctNawg4eG61bRv87N7iHBWyVhJu7u1kqDUXY=
google.golang.org/genproto v0.0.0-20210903162649-d08c68adba83/go.mod h1:eFjDcFEctNawg4eG61bRv87N7iHBWyVhJu7u1kqDUXY=
google.golang.org/genproto v0.0.0-20210909211513-a8c4777a87af/go.mod h1:eFjDcFEctNawg4eG61bRv87N7iHBWyVhJu7u1kqDUXY=
google.golang.org/genproto v0.0.0-20210924002016-3dee208752a0/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc=
google.golang.org/genproto v0.0.0-20211118181313-81c1377c94b1/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc=
google.golang.org/genproto v0.0.0-20211206160659-862468c7d6e0/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc=
google.golang.org/genproto v0.0.0-20211208223120-3a66f561d7aa/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc=
google.golang.org/genproto v0.0.0-20211221195035-429b39de9b1c/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc=
google.golang.org/genproto v0.0.0-20220107163113-42d7afdf6368/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc=
google.golang.org/genproto v0.0.0-20220126215142-9970aeb2e350/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc=
google.golang.org/genproto v0.0.0-20220207164111-0872dc986b00/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc=
google.golang.org/genproto v0.0.0-20220218161850-94dd64e39d7c/go.mod h1:kGP+zUP2Ddo0ayMi4YuN7C3WZyJvGLZRh8Z5wnAqvEI=
google.golang.org/genproto v0.0.0-20220222213610-43724f9ea8cf/go.mod h1:kGP+zUP2Ddo0ayMi4YuN7C3WZyJvGLZRh8Z5wnAqvEI=
google.golang.org/genproto v0.0.0-20220304144024-325a89244dc8/go.mod h1:kGP+zUP2Ddo0ayMi4YuN7C3WZyJvGLZRh8Z5wnAqvEI=
google.golang.org/genproto v0.0.0-20220310185008-1973136f34c6/go.mod h1:kGP+zUP2Ddo0ayMi4YuN7C3WZyJvGLZRh8Z5wnAqvEI=
google.golang.org/genproto v0.0.0-20220324131243-acbaeb5b85eb/go.mod h1:hAL49I2IFola2sVEjAn7MEwsja0xp51I0tlGAf9hz4E=
google.golang.org/genproto v0.0.0-20220407144326-9054f6ed7bac/go.mod h1:8w6bsBMX6yCPbAVTeqQHvzxW0EIFigd5lZyahWgyfDo=
google.golang.org/genproto v0.0.0-20220413183235-5e96e2839df9/go.mod h1:8w6bsBMX6yCPbAVTeqQHvzxW0EIFigd5lZyahWgyfDo=
google.golang.org/genproto v0.0.0-20220414192740-2d67ff6cf2b4/go.mod h1:8w6bsBMX6yCPbAVTeqQHvzxW0EIFigd5lZyahWgyfDo=
google.golang.org/genproto v0.0.0-20220421151946-72621c1f0bd3/go.mod h1:8w6bsBMX6yCPbAVTeqQHvzxW0EIFigd5lZyahWgyfDo=
google.golang.org/genproto v0.0.0-20220429170224-98d788798c3e/go.mod h1:8w6bsBMX6yCPbAVTeqQHvzxW0EIFigd5lZyahWgyfDo=
google.golang.org/genproto v0.0.0-20220505152158-f39f71e6c8f3/go.mod h1:RAyBrSAP7Fh3Nc84ghnVLDPuV51xc9agzmm4Ph6i0Q4=
google.golang.org/genproto v0.0.0-20220518221133-4f43b3371335/go.mod h1:RAyBrSAP7Fh3Nc84ghnVLDPuV51xc9agzmm4Ph6i0Q4=
google.golang.org/genproto v0.0.0-20220523171625-347a074981d8/go.mod h1:RAyBrSAP7Fh3Nc84ghnVLDPuV51xc9agzmm4Ph6i0Q4=
google.golang.org/genproto v0.0.0-20220608133413-ed9918b62aac/go.mod h1:KEWEmljWE5zPzLBa/oHl6DaEt9LmfH6WtH1OHIvleBA=
google.golang.org/genproto v0.0.0-20220616135557-88e70c0c3a90/go.mod h1:KEWEmljWE5zPzLBa/oHl6DaEt9LmfH6WtH1OHIvleBA=
google.golang.org/genproto v0.0.0-20220624142145-8cd45d7dbd1f h1:hJ/Y5SqPXbarffmAsApliUlcvMU+wScNGfyop4bZm8o=
google.golang.org/genproto v0.0.0-20220624142145-8cd45d7dbd1f/go.mod h1:KEWEmljWE5zPzLBa/oHl6DaEt9LmfH6WtH1OHIvleBA=
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38=
google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM=
@ -956,9 +1107,19 @@ google.golang.org/grpc v1.35.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAG
google.golang.org/grpc v1.36.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU=
google.golang.org/grpc v1.36.1/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU=
google.golang.org/grpc v1.37.0/go.mod h1:NREThFqKR1f3iQ6oBuvc5LadQuXVGo9rkm5ZGrQdJfM=
google.golang.org/grpc v1.37.1/go.mod h1:NREThFqKR1f3iQ6oBuvc5LadQuXVGo9rkm5ZGrQdJfM=
google.golang.org/grpc v1.38.0/go.mod h1:NREThFqKR1f3iQ6oBuvc5LadQuXVGo9rkm5ZGrQdJfM=
google.golang.org/grpc v1.40.0 h1:AGJ0Ih4mHjSeibYkFGh1dD9KJ/eOtZ93I6hoHhukQ5Q=
google.golang.org/grpc v1.39.0/go.mod h1:PImNr+rS9TWYb2O4/emRugxiyHZ5JyHW5F+RPnDzfrE=
google.golang.org/grpc v1.39.1/go.mod h1:PImNr+rS9TWYb2O4/emRugxiyHZ5JyHW5F+RPnDzfrE=
google.golang.org/grpc v1.40.0/go.mod h1:ogyxbiOoUXAkP+4+xa6PZSE9DZgIHtSpzjDTB9KAK34=
google.golang.org/grpc v1.40.1/go.mod h1:ogyxbiOoUXAkP+4+xa6PZSE9DZgIHtSpzjDTB9KAK34=
google.golang.org/grpc v1.44.0/go.mod h1:k+4IHHFw41K8+bbowsex27ge2rCb65oeWqe4jJ590SU=
google.golang.org/grpc v1.45.0/go.mod h1:lN7owxKUQEqMfSyQikvvk5tf/6zMPsrK+ONuO11+0rQ=
google.golang.org/grpc v1.46.0/go.mod h1:vN9eftEi1UMyUsIF80+uQXhHjbXYbm0uXoFCACuMGWk=
google.golang.org/grpc v1.46.2/go.mod h1:vN9eftEi1UMyUsIF80+uQXhHjbXYbm0uXoFCACuMGWk=
google.golang.org/grpc v1.47.0 h1:9n77onPX5F3qfFCqjy9dhn8PbNQsIKeVU04J9G7umt8=
google.golang.org/grpc v1.47.0/go.mod h1:vN9eftEi1UMyUsIF80+uQXhHjbXYbm0uXoFCACuMGWk=
google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.1.0/go.mod h1:6Kw0yEErY5E/yWrBtf03jp27GLLJujG4z/JK95pnjjw=
google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=
@ -971,8 +1132,9 @@ google.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGj
google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c=
google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
google.golang.org/protobuf v1.27.1 h1:SnqbnDw1V7RiZcXPx5MEeqPv2s79L9i7BJUlG/+RurQ=
google.golang.org/protobuf v1.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
google.golang.org/protobuf v1.28.0 h1:w43yiav+6bVFTBQFZX0r7ipe9JQ1QsbMgHwbBziscLw=
google.golang.org/protobuf v1.28.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=

View File

@ -0,0 +1,53 @@
package client
import (
"context"
"github.com/googleapis/gax-go/v2"
computepb "google.golang.org/genproto/googleapis/cloud/compute/v1"
)
type instanceAPI interface {
Close() error
Get(ctx context.Context, req *computepb.GetInstanceRequest,
opts ...gax.CallOption) (*computepb.Instance, error)
}
type instanceTemplateAPI interface {
Close() error
Get(ctx context.Context, req *computepb.GetInstanceTemplateRequest,
opts ...gax.CallOption) (*computepb.InstanceTemplate, error)
Delete(ctx context.Context, req *computepb.DeleteInstanceTemplateRequest,
opts ...gax.CallOption) (Operation, error)
Insert(ctx context.Context, req *computepb.InsertInstanceTemplateRequest,
opts ...gax.CallOption) (Operation, error)
}
type instanceGroupManagersAPI interface {
Close() error
Get(ctx context.Context, req *computepb.GetInstanceGroupManagerRequest,
opts ...gax.CallOption) (*computepb.InstanceGroupManager, error)
SetInstanceTemplate(ctx context.Context, req *computepb.SetInstanceTemplateInstanceGroupManagerRequest,
opts ...gax.CallOption) (Operation, error)
CreateInstances(ctx context.Context, req *computepb.CreateInstancesInstanceGroupManagerRequest,
opts ...gax.CallOption) (Operation, error)
DeleteInstances(ctx context.Context, req *computepb.DeleteInstancesInstanceGroupManagerRequest,
opts ...gax.CallOption) (Operation, error)
}
type diskAPI interface {
Close() error
Get(ctx context.Context, req *computepb.GetDiskRequest,
opts ...gax.CallOption) (*computepb.Disk, error)
}
type Operation interface {
Proto() *computepb.Operation
Done() bool
Wait(ctx context.Context, opts ...gax.CallOption) error
}
type prng interface {
// Intn returns, as an int, a non-negative pseudo-random number in the half-open interval [0,n). It panics if n <= 0.
Intn(n int) int
}

View File

@ -0,0 +1,83 @@
package client
import (
"context"
"math/rand"
"time"
compute "cloud.google.com/go/compute/apiv1"
"go.uber.org/multierr"
)
// Client is a client for the Google Compute Engine.
type Client struct {
instanceAPI
instanceTemplateAPI
instanceGroupManagersAPI
diskAPI
// prng is a pseudo-random number generator seeded with time. Not used for security.
prng
}
// New creates a new client for the Google Compute Engine.
func New(ctx context.Context) (*Client, error) {
var closers []closer
insAPI, err := compute.NewInstancesRESTClient(ctx)
if err != nil {
return nil, err
}
closers = append(closers, insAPI)
templAPI, err := compute.NewInstanceTemplatesRESTClient(ctx)
if err != nil {
_ = closeAll(closers)
return nil, err
}
closers = append(closers, templAPI)
groupAPI, err := compute.NewInstanceGroupManagersRESTClient(ctx)
if err != nil {
_ = closeAll(closers)
return nil, err
}
closers = append(closers, groupAPI)
diskAPI, err := compute.NewDisksRESTClient(ctx)
if err != nil {
_ = closeAll(closers)
return nil, err
}
return &Client{
instanceAPI: insAPI,
instanceTemplateAPI: &instanceTemplateClient{templAPI},
instanceGroupManagersAPI: &instanceGroupManagersClient{groupAPI},
diskAPI: diskAPI,
prng: rand.New(rand.NewSource(int64(time.Now().Nanosecond()))),
}, nil
}
// Close closes the client's connection.
func (c *Client) Close() error {
closers := []closer{
c.instanceAPI,
c.instanceTemplateAPI,
c.instanceGroupManagersAPI,
c.diskAPI,
}
return closeAll(closers)
}
type closer interface {
Close() error
}
// closeAll closes all closers, even if an error occurs.
//
// Errors are collected and a composed error is returned.
func closeAll(closers []closer) error {
// Since this function is intended to be deferred, it will always call all
// close operations, even if a previous operation failed.
var errs error
for _, closer := range closers {
errs = multierr.Append(errs, closer.Close())
}
return errs
}

View File

@ -0,0 +1,143 @@
package client
import (
"context"
"github.com/googleapis/gax-go/v2"
computepb "google.golang.org/genproto/googleapis/cloud/compute/v1"
"google.golang.org/protobuf/proto"
)
type stubInstanceAPI struct {
instance *computepb.Instance
getErr error
}
func (a stubInstanceAPI) Close() error {
return nil
}
func (a stubInstanceAPI) Get(ctx context.Context, req *computepb.GetInstanceRequest,
opts ...gax.CallOption,
) (*computepb.Instance, error) {
return a.instance, a.getErr
}
type stubInstanceTemplateAPI struct {
template *computepb.InstanceTemplate
getErr error
deleteErr error
insertErr error
}
func (a stubInstanceTemplateAPI) Close() error {
return nil
}
func (a stubInstanceTemplateAPI) Get(ctx context.Context, req *computepb.GetInstanceTemplateRequest,
opts ...gax.CallOption,
) (*computepb.InstanceTemplate, error) {
return a.template, a.getErr
}
func (a stubInstanceTemplateAPI) Delete(ctx context.Context, req *computepb.DeleteInstanceTemplateRequest,
opts ...gax.CallOption,
) (Operation, error) {
return &stubOperation{
&computepb.Operation{
Name: proto.String("name"),
},
}, a.deleteErr
}
func (a stubInstanceTemplateAPI) Insert(ctx context.Context, req *computepb.InsertInstanceTemplateRequest,
opts ...gax.CallOption,
) (Operation, error) {
return &stubOperation{
&computepb.Operation{
Name: proto.String("name"),
},
}, a.insertErr
}
type stubInstanceGroupManagersAPI struct {
instanceGroupManager *computepb.InstanceGroupManager
getErr error
setInstanceTemplateErr error
createInstancesErr error
deleteInstancesErr error
}
func (a stubInstanceGroupManagersAPI) Close() error {
return nil
}
func (a stubInstanceGroupManagersAPI) Get(ctx context.Context, req *computepb.GetInstanceGroupManagerRequest,
opts ...gax.CallOption,
) (*computepb.InstanceGroupManager, error) {
return a.instanceGroupManager, a.getErr
}
func (a stubInstanceGroupManagersAPI) SetInstanceTemplate(ctx context.Context, req *computepb.SetInstanceTemplateInstanceGroupManagerRequest,
opts ...gax.CallOption,
) (Operation, error) {
return &stubOperation{
&computepb.Operation{
Name: proto.String("name"),
},
}, a.setInstanceTemplateErr
}
func (a stubInstanceGroupManagersAPI) CreateInstances(ctx context.Context, req *computepb.CreateInstancesInstanceGroupManagerRequest,
opts ...gax.CallOption,
) (Operation, error) {
return &stubOperation{
&computepb.Operation{
Name: proto.String("name"),
},
}, a.createInstancesErr
}
func (a stubInstanceGroupManagersAPI) DeleteInstances(ctx context.Context, req *computepb.DeleteInstancesInstanceGroupManagerRequest,
opts ...gax.CallOption,
) (Operation, error) {
if a.deleteInstancesErr != nil {
return nil, a.deleteInstancesErr
}
return &stubOperation{
&computepb.Operation{
Name: proto.String("name"),
},
}, nil
}
type stubDiskAPI struct {
disk *computepb.Disk
getErr error
}
func (a stubDiskAPI) Close() error {
return nil
}
func (a stubDiskAPI) Get(ctx context.Context, req *computepb.GetDiskRequest,
opts ...gax.CallOption,
) (*computepb.Disk, error) {
return a.disk, a.getErr
}
type stubOperation struct {
*computepb.Operation
}
func (o *stubOperation) Proto() *computepb.Operation {
return o.Operation
}
func (o *stubOperation) Done() bool {
return true
}
func (o *stubOperation) Wait(ctx context.Context, opts ...gax.CallOption) error {
return nil
}

View File

@ -0,0 +1,36 @@
package client
import (
"fmt"
"regexp"
"google.golang.org/genproto/googleapis/cloud/compute/v1"
)
var (
diskSourceRegex = regexp.MustCompile(`^https://www.googleapis.com/compute/v1/projects/([^/]+)/zones/([^/]+)/disks/([^/]+)$`)
computeAPIBase = regexp.MustCompile(`^https://www.googleapis.com/compute/v1/(.+)$`)
)
// diskSourceToDiskReq converts a disk source URI to a disk request.
func diskSourceToDiskReq(diskSource string) (*compute.GetDiskRequest, error) {
matches := diskSourceRegex.FindStringSubmatch(diskSource)
if len(matches) != 4 {
return nil, fmt.Errorf("error splitting diskSource: %v", diskSource)
}
return &compute.GetDiskRequest{
Disk: matches[3],
Project: matches[1],
Zone: matches[2],
}, nil
}
// uriNormalize normalizes a compute API URI by removing the optional URI prefix.
// for normalization, the prefix 'https://www.googleapis.com/compute/v1/' is removed.
func uriNormalize(imageURI string) string {
matches := computeAPIBase.FindStringSubmatch(imageURI)
if len(matches) != 2 {
return imageURI
}
return matches[1]
}

View File

@ -0,0 +1,73 @@
package client
import (
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"google.golang.org/genproto/googleapis/cloud/compute/v1"
)
func TestDiskSourceToDiskReq(t *testing.T) {
testCases := map[string]struct {
diskSource string
wantRequest *compute.GetDiskRequest
wantErr bool
}{
"valid request": {
diskSource: "https://www.googleapis.com/compute/v1/projects/project/zones/zone/disks/disk",
wantRequest: &compute.GetDiskRequest{
Disk: "disk",
Project: "project",
Zone: "zone",
},
},
"invalid host": {
diskSource: "https://hostname/compute/v1/projects/project/zones/zone/disks/disk",
wantErr: true,
},
"invalid scheme": {
diskSource: "invalid://www.googleapis.com/compute/v1/projects/project/zones/zone/disks/disk",
wantErr: true,
},
}
for name, tc := range testCases {
t.Run(name, func(t *testing.T) {
assert := assert.New(t)
require := require.New(t)
request, err := diskSourceToDiskReq(tc.diskSource)
if tc.wantErr {
assert.Error(err)
return
}
require.NoError(err)
assert.Equal(tc.wantRequest, request)
})
}
}
func TestURINormalize(t *testing.T) {
testCases := map[string]struct {
imageURI string
wantNormalized string
}{
"URI with scheme and host": {
imageURI: "https://www.googleapis.com/compute/v1/projects/project/global/images/image",
wantNormalized: "projects/project/global/images/image",
},
"normalized": {
imageURI: "projects/project/global/images/image",
wantNormalized: "projects/project/global/images/image",
},
}
for name, tc := range testCases {
t.Run(name, func(t *testing.T) {
assert := assert.New(t)
normalized := uriNormalize(tc.imageURI)
assert.Equal(tc.wantNormalized, normalized)
})
}
}

View File

@ -0,0 +1,61 @@
package client
import (
"context"
compute "cloud.google.com/go/compute/apiv1"
"github.com/googleapis/gax-go/v2"
computepb "google.golang.org/genproto/googleapis/cloud/compute/v1"
)
type instanceTemplateClient struct {
*compute.InstanceTemplatesClient
}
func (c *instanceTemplateClient) Close() error {
return c.InstanceTemplatesClient.Close()
}
func (c *instanceTemplateClient) Delete(ctx context.Context, req *computepb.DeleteInstanceTemplateRequest,
opts ...gax.CallOption,
) (Operation, error) {
return c.InstanceTemplatesClient.Delete(ctx, req, opts...)
}
func (c *instanceTemplateClient) Insert(ctx context.Context, req *computepb.InsertInstanceTemplateRequest,
opts ...gax.CallOption,
) (Operation, error) {
return c.InstanceTemplatesClient.Insert(ctx, req, opts...)
}
type instanceGroupManagersClient struct {
*compute.InstanceGroupManagersClient
}
func (c *instanceGroupManagersClient) Close() error {
return c.InstanceGroupManagersClient.Close()
}
func (c *instanceGroupManagersClient) Get(ctx context.Context, req *computepb.GetInstanceGroupManagerRequest,
opts ...gax.CallOption,
) (*computepb.InstanceGroupManager, error) {
return c.InstanceGroupManagersClient.Get(ctx, req, opts...)
}
func (c *instanceGroupManagersClient) SetInstanceTemplate(ctx context.Context, req *computepb.SetInstanceTemplateInstanceGroupManagerRequest,
opts ...gax.CallOption,
) (Operation, error) {
return c.InstanceGroupManagersClient.SetInstanceTemplate(ctx, req, opts...)
}
func (c *instanceGroupManagersClient) CreateInstances(ctx context.Context, req *computepb.CreateInstancesInstanceGroupManagerRequest,
opts ...gax.CallOption,
) (Operation, error) {
return c.InstanceGroupManagersClient.CreateInstances(ctx, req, opts...)
}
func (c *instanceGroupManagersClient) DeleteInstances(ctx context.Context, req *computepb.DeleteInstancesInstanceGroupManagerRequest,
opts ...gax.CallOption,
) (Operation, error) {
return c.InstanceGroupManagersClient.DeleteInstances(ctx, req, opts...)
}

View File

@ -0,0 +1,29 @@
package client
import (
"fmt"
"regexp"
)
var instanceGroupIDRegex = regexp.MustCompile(`^projects/([^/]+)/zones/([^/]+)/instanceGroupManagers/([^/]+)$`)
// splitInstanceGroupID splits an instance group ID into core components.
func splitInstanceGroupID(instanceGroupID string) (project, zone, instanceGroup string, err error) {
matches := instanceGroupIDRegex.FindStringSubmatch(instanceGroupID)
if len(matches) != 4 {
return "", "", "", fmt.Errorf("error splitting instanceGroupID: %v", instanceGroupID)
}
return matches[1], matches[2], matches[3], nil
}
// generateInstanceName generates a random instance name.
func generateInstanceName(baseInstanceName string, random prng) (string, error) {
letters := []byte("abcdefghijklmnopqrstuvwxyz0123456789")
const uidLen = 4
uid := make([]byte, 0, uidLen)
for i := 0; i < uidLen; i++ {
n := random.Intn(len(letters))
uid = append(uid, letters[n])
}
return baseInstanceName + "-" + string(uid), nil
}

View File

@ -0,0 +1,76 @@
package client
import (
"math/rand"
"regexp"
"testing"
"time"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func TestSplitInstanceGroupID(t *testing.T) {
testCases := map[string]struct {
instanceGroupID string
wantProject string
wantZone string
wantInstanceGroup string
wantErr bool
}{
"valid request": {
instanceGroupID: "projects/project/zones/zone/instanceGroupManagers/instanceGroup",
wantProject: "project",
wantZone: "zone",
wantInstanceGroup: "instanceGroup",
},
"wrong format": {
instanceGroupID: "wrong-format",
wantErr: true,
},
}
for name, tc := range testCases {
t.Run(name, func(t *testing.T) {
assert := assert.New(t)
require := require.New(t)
gotProject, gotZone, gotInstanceGroup, err := splitInstanceGroupID(tc.instanceGroupID)
if tc.wantErr {
assert.Error(err)
return
}
require.NoError(err)
assert.Equal(tc.wantProject, gotProject)
assert.Equal(tc.wantZone, gotZone)
assert.Equal(tc.wantInstanceGroup, gotInstanceGroup)
})
}
}
func TestGenerateInstanceName(t *testing.T) {
assert := assert.New(t)
require := require.New(t)
baseInstanceName := "base"
gotInstanceName, err := generateInstanceName(baseInstanceName, &stubRng{result: 0})
require.NoError(err)
assert.Equal("base-aaaa", gotInstanceName)
}
func TestGenerateInstanceNameRandomTest(t *testing.T) {
assert := assert.New(t)
require := require.New(t)
instanceNameRegexp := regexp.MustCompile(`^base-[0-9a-z]{4}$`)
baseInstanceName := "base"
random := rand.New(rand.NewSource(int64(time.Now().Nanosecond())))
gotInstanceName, err := generateInstanceName(baseInstanceName, random)
require.NoError(err)
assert.Regexp(instanceNameRegexp, gotInstanceName)
}
type stubRng struct {
result int
}
func (r *stubRng) Intn(n int) int {
return r.result
}

View File

@ -0,0 +1,47 @@
package client
import (
"fmt"
"math"
"regexp"
"strconv"
)
var (
numberedNameRegex = regexp.MustCompile(`^(.+)-(\d+)$`)
instanceTemplateIDRegex = regexp.MustCompile(`projects/([^/]+)/global/instanceTemplates/([^/]+)`)
)
// generateInstanceTemplateName generates a unique name for an instance template by incrementing a counter.
// The name is in the format <prefix>-<counter>.
func generateInstanceTemplateName(last string) (string, error) {
if len(last) > 0 && last[len(last)-1] == '-' {
return last + "1", nil
}
matches := numberedNameRegex.FindStringSubmatch(last)
if len(matches) != 3 {
return last + "-1", nil
}
n, err := strconv.Atoi(matches[2])
if err != nil {
return "", err
}
if n < 1 || n == math.MaxInt {
return "", fmt.Errorf("invalid counter: %v", n)
}
return matches[1] + "-" + strconv.Itoa(n+1), nil
}
// splitInstanceTemplateID splits an instance template ID into its project and name components.
func splitInstanceTemplateID(instanceTemplateID string) (project, templateName string, err error) {
matches := instanceTemplateIDRegex.FindStringSubmatch(instanceTemplateID)
if len(matches) != 3 {
return "", "", fmt.Errorf("error splitting instanceTemplateID: %v", instanceTemplateID)
}
return matches[1], matches[2], nil
}
// joinInstanceTemplateURI joins a project and template name into an instance template URI.
func joinInstanceTemplateURI(project, templateName string) string {
return fmt.Sprintf("https://www.googleapis.com/compute/v1/projects/%v/global/instanceTemplates/%v", project, templateName)
}

View File

@ -0,0 +1,99 @@
package client
import (
"fmt"
"math"
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func TestGenerateInstanceTemplateName(t *testing.T) {
testCases := map[string]struct {
last string
wantNext string
wantErr bool
}{
"no numbering yet": {
last: "prefix",
wantNext: "prefix-1",
},
"ends in -": {
last: "prefix-",
wantNext: "prefix-1",
},
"has number": {
last: "prefix-1",
wantNext: "prefix-2",
},
"last number too small": {
last: "prefix-0",
wantErr: true,
},
"last number would overflow": {
last: fmt.Sprintf("prefix-%d", math.MaxInt),
wantErr: true,
},
"integer out of range": {
last: "prefix-999999999999999999999999999999999999999999",
wantErr: true,
},
}
for name, tc := range testCases {
t.Run(name, func(t *testing.T) {
assert := assert.New(t)
require := require.New(t)
gotNext, err := generateInstanceTemplateName(tc.last)
if tc.wantErr {
assert.Error(err)
return
}
require.NoError(err)
assert.Equal(tc.wantNext, gotNext)
})
}
}
func TestSplitInstanceTemplateID(t *testing.T) {
testCases := map[string]struct {
instanceTemplateID string
wantProject string
wantTemplateName string
wantErr bool
}{
"valid request": {
instanceTemplateID: "projects/project/global/instanceTemplates/template",
wantProject: "project",
wantTemplateName: "template",
},
"wrong format": {
instanceTemplateID: "wrong-format",
wantErr: true,
},
}
for name, tc := range testCases {
t.Run(name, func(t *testing.T) {
assert := assert.New(t)
require := require.New(t)
gotProject, gotTemplateName, err := splitInstanceTemplateID(tc.instanceTemplateID)
if tc.wantErr {
assert.Error(err)
return
}
require.NoError(err)
assert.Equal(tc.wantProject, gotProject)
assert.Equal(tc.wantTemplateName, gotTemplateName)
})
}
}
func TestJoinInstanceTemplateID(t *testing.T) {
assert := assert.New(t)
project := "project"
templateName := "template"
wantInstanceTemplateURI := "https://www.googleapis.com/compute/v1/projects/project/global/instanceTemplates/template"
gotInstancetemplateURI := joinInstanceTemplateURI(project, templateName)
assert.Equal(wantInstanceTemplateURI, gotInstancetemplateURI)
}

View File

@ -0,0 +1,21 @@
package client
import (
computepb "google.golang.org/genproto/googleapis/cloud/compute/v1"
)
// getMetadataByKey returns the value of the metadata key in the given metadata.
func getMetadataByKey(metadata *computepb.Metadata, key string) string {
if metadata == nil {
return ""
}
for _, item := range metadata.Items {
if item.Key == nil || item.Value == nil {
continue
}
if *item.Key == key {
return *item.Value
}
}
return ""
}

View File

@ -0,0 +1,57 @@
package client
import (
"testing"
"github.com/stretchr/testify/assert"
"google.golang.org/genproto/googleapis/cloud/compute/v1"
"google.golang.org/protobuf/proto"
)
func TestGetMetadataByKey(t *testing.T) {
testCases := map[string]struct {
metadata *compute.Metadata
key string
wantValue string
}{
"metadata has key": {
metadata: &compute.Metadata{
Items: []*compute.Items{
{Key: proto.String("key"), Value: proto.String("value")},
},
},
key: "key",
wantValue: "value",
},
"metadata does not have key": {
metadata: &compute.Metadata{
Items: []*compute.Items{
{Key: proto.String("otherkey"), Value: proto.String("value")},
},
},
key: "key",
wantValue: "",
},
"metadata contains invalid item": {
metadata: &compute.Metadata{
Items: []*compute.Items{
{},
{Key: proto.String("key"), Value: proto.String("value")},
},
},
key: "key",
wantValue: "value",
},
"metadata is nil": {
key: "key",
wantValue: "",
},
}
for name, tc := range testCases {
t.Run(name, func(t *testing.T) {
assert := assert.New(t)
assert.Equal(tc.wantValue, getMetadataByKey(tc.metadata, tc.key))
})
}
}

View File

@ -0,0 +1,134 @@
package client
import (
"context"
"fmt"
computepb "google.golang.org/genproto/googleapis/cloud/compute/v1"
"google.golang.org/protobuf/proto"
)
// GetNodeImage returns the image name of the node.
func (c *Client) GetNodeImage(ctx context.Context, providerID string) (string, error) {
project, zone, instanceName, err := splitProviderID(providerID)
if err != nil {
return "", err
}
instance, err := c.instanceAPI.Get(ctx, &computepb.GetInstanceRequest{
Instance: instanceName,
Project: project,
Zone: zone,
})
if err != nil {
return "", err
}
// first disk is always the boot disk
if len(instance.Disks) < 1 {
return "", fmt.Errorf("instance %v has no disks", instanceName)
}
if instance.Disks[0] == nil || instance.Disks[0].Source == nil {
return "", fmt.Errorf("instance %q has invalid disk", instanceName)
}
diskReq, err := diskSourceToDiskReq(*instance.Disks[0].Source)
if err != nil {
return "", err
}
disk, err := c.diskAPI.Get(ctx, diskReq)
if err != nil {
return "", err
}
if disk.SourceImage == nil {
return "", fmt.Errorf("disk %q has no source image", diskReq.Disk)
}
return uriNormalize(*disk.SourceImage), nil
}
// GetScalingGroupID returns the scaling group ID of the node.
func (c *Client) GetScalingGroupID(ctx context.Context, providerID string) (string, error) {
project, zone, instanceName, err := splitProviderID(providerID)
if err != nil {
return "", err
}
instance, err := c.instanceAPI.Get(ctx, &computepb.GetInstanceRequest{
Instance: instanceName,
Project: project,
Zone: zone,
})
if err != nil {
return "", fmt.Errorf("getting instance %q: %w", instanceName, err)
}
scalingGroupID := getMetadataByKey(instance.Metadata, "created-by")
if scalingGroupID == "" {
return "", fmt.Errorf("instance %q has no created-by metadata", instanceName)
}
return scalingGroupID, nil
}
// CreateNode creates a node in the specified scaling group.
func (c *Client) CreateNode(ctx context.Context, scalingGroupID string) (nodeName, providerID string, err error) {
project, zone, instanceGroupName, err := splitInstanceGroupID(scalingGroupID)
if err != nil {
return "", "", err
}
instanceGroupManager, err := c.instanceGroupManagersAPI.Get(ctx, &computepb.GetInstanceGroupManagerRequest{
InstanceGroupManager: instanceGroupName,
Project: project,
Zone: zone,
})
if err != nil {
return "", "", err
}
if instanceGroupManager.BaseInstanceName == nil {
return "", "", fmt.Errorf("instance group manager %q has no base instance name", instanceGroupName)
}
instanceName, err := generateInstanceName(*instanceGroupManager.BaseInstanceName, c.prng)
if err != nil {
return "", "", err
}
op, err := c.instanceGroupManagersAPI.CreateInstances(ctx, &computepb.CreateInstancesInstanceGroupManagerRequest{
InstanceGroupManager: instanceGroupName,
Project: project,
Zone: zone,
InstanceGroupManagersCreateInstancesRequestResource: &computepb.InstanceGroupManagersCreateInstancesRequest{
Instances: []*computepb.PerInstanceConfig{
{Name: proto.String(instanceName)},
},
},
})
if err != nil {
return "", "", err
}
if err := op.Wait(ctx); err != nil {
return "", "", err
}
return instanceName, joinProviderID(project, zone, instanceName), nil
}
// DeleteNode deletes a node specified by its provider ID.
func (c *Client) DeleteNode(ctx context.Context, providerID string) error {
_, zone, instanceName, err := splitProviderID(providerID)
if err != nil {
return err
}
scalingGroupID, err := c.GetScalingGroupID(ctx, providerID)
if err != nil {
return err
}
instanceGroupProject, instanceGroupZone, instanceGroupName, err := splitInstanceGroupID(scalingGroupID)
if err != nil {
return err
}
instanceID := joinInstanceID(zone, instanceName)
op, err := c.instanceGroupManagersAPI.DeleteInstances(ctx, &computepb.DeleteInstancesInstanceGroupManagerRequest{
InstanceGroupManager: instanceGroupName,
Project: instanceGroupProject,
Zone: instanceGroupZone,
InstanceGroupManagersDeleteInstancesRequestResource: &computepb.InstanceGroupManagersDeleteInstancesRequest{
Instances: []string{instanceID},
},
})
if err != nil {
return fmt.Errorf("deleting instance %q from instance group manager %q: %w", instanceID, scalingGroupID, err)
}
return op.Wait(ctx)
}

View File

@ -0,0 +1,292 @@
package client
import (
"context"
"errors"
"math/rand"
"testing"
"time"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
computepb "google.golang.org/genproto/googleapis/cloud/compute/v1"
"google.golang.org/protobuf/proto"
)
func TestGetNodeImage(t *testing.T) {
testCases := map[string]struct {
providerID string
attachedDisks []*computepb.AttachedDisk
disk *computepb.Disk
getInstanceErr error
getDiskErr error
wantImage string
wantErr bool
}{
"boot disk is found": {
providerID: "gce://project/zone/instance-name",
attachedDisks: []*computepb.AttachedDisk{
{
Source: proto.String("https://www.googleapis.com/compute/v1/projects/project/zones/zone/disks/disk"),
},
},
disk: &computepb.Disk{
SourceImage: proto.String("https://www.googleapis.com/compute/v1/projects/project/global/images/image"),
},
wantImage: "projects/project/global/images/image",
},
"splitting providerID fails": {
providerID: "invalid",
wantErr: true,
},
"get instance fails": {
providerID: "gce://project/zone/instance-name",
getInstanceErr: errors.New("get instance error"),
wantErr: true,
},
"instance has no disks": {
providerID: "gce://project/zone/instance-name",
wantErr: true,
},
"attached disk is invalid": {
providerID: "gce://project/zone/instance-name",
attachedDisks: []*computepb.AttachedDisk{{}},
wantErr: true,
},
"boot disk reference is invalid": {
providerID: "gce://project/zone/instance-name",
attachedDisks: []*computepb.AttachedDisk{{
Source: proto.String("invalid"),
}},
wantErr: true,
},
"get disk fails": {
providerID: "gce://project/zone/instance-name",
attachedDisks: []*computepb.AttachedDisk{{
Source: proto.String("https://www.googleapis.com/compute/v1/projects/project/zones/zone/disks/disk"),
}},
getDiskErr: errors.New("get disk error"),
wantErr: true,
},
"disk has no source image": {
providerID: "gce://project/zone/instance-name",
attachedDisks: []*computepb.AttachedDisk{{
Source: proto.String("https://www.googleapis.com/compute/v1/projects/project/zones/zone/disks/disk"),
}},
disk: &computepb.Disk{},
wantErr: true,
},
}
for name, tc := range testCases {
t.Run(name, func(t *testing.T) {
assert := assert.New(t)
require := require.New(t)
client := Client{
instanceAPI: &stubInstanceAPI{
getErr: tc.getInstanceErr,
instance: &computepb.Instance{
Disks: tc.attachedDisks,
},
},
diskAPI: &stubDiskAPI{
getErr: tc.getDiskErr,
disk: tc.disk,
},
}
gotImage, err := client.GetNodeImage(context.Background(), tc.providerID)
if tc.wantErr {
assert.Error(err)
return
}
require.NoError(err)
assert.Equal(tc.wantImage, gotImage)
})
}
}
func TestGetScalingGroupID(t *testing.T) {
testCases := map[string]struct {
providerID string
createdBy string
getInstanceErr error
wantScalingGroupID string
wantErr bool
}{
"scaling group is found": {
providerID: "gce://project/zone/instance-name",
createdBy: "projects/project/zones/zone/instanceGroups/instance-group",
wantScalingGroupID: "projects/project/zones/zone/instanceGroups/instance-group",
},
"splitting providerID fails": {
providerID: "invalid",
wantErr: true,
},
"get instance fails": {
providerID: "gce://project/zone/instance-name",
getInstanceErr: errors.New("get instance error"),
wantErr: true,
},
"instance has no created-by": {
providerID: "gce://project/zone/instance-name",
wantErr: true,
},
}
for name, tc := range testCases {
t.Run(name, func(t *testing.T) {
assert := assert.New(t)
require := require.New(t)
instance := computepb.Instance{}
if tc.createdBy != "" {
instance.Metadata = &computepb.Metadata{
Items: []*computepb.Items{
{
Key: proto.String("created-by"),
Value: proto.String(tc.createdBy),
},
},
}
}
client := Client{
instanceAPI: &stubInstanceAPI{
getErr: tc.getInstanceErr,
instance: &instance,
},
}
gotScalingGroupID, err := client.GetScalingGroupID(context.Background(), tc.providerID)
if tc.wantErr {
assert.Error(err)
return
}
require.NoError(err)
assert.Equal(tc.wantScalingGroupID, gotScalingGroupID)
})
}
}
func TestCreateNode(t *testing.T) {
testCases := map[string]struct {
scalingGroupID string
baseInstanceName *string
getInstanceGroupManagerErr error
createInstanceErr error
wantErr bool
}{
"scaling group is found": {
scalingGroupID: "projects/project/zones/zone/instanceGroupManagers/instance-group",
baseInstanceName: proto.String("base-name"),
},
"splitting scalingGroupID fails": {
scalingGroupID: "invalid",
wantErr: true,
},
"get instance group manager fails": {
scalingGroupID: "projects/project/zones/zone/instanceGroupManagers/instance-group",
getInstanceGroupManagerErr: errors.New("get instance group manager error"),
wantErr: true,
},
"instance group manager has no base instance name": {
scalingGroupID: "projects/project/zones/zone/instanceGroupManagers/instance-group",
wantErr: true,
},
"create instance fails": {
scalingGroupID: "projects/project/zones/zone/instanceGroupManagers/instance-group",
baseInstanceName: proto.String("base-name"),
createInstanceErr: errors.New("create instance error"),
wantErr: true,
},
}
for name, tc := range testCases {
t.Run(name, func(t *testing.T) {
assert := assert.New(t)
require := require.New(t)
client := Client{
instanceGroupManagersAPI: &stubInstanceGroupManagersAPI{
getErr: tc.getInstanceGroupManagerErr,
createInstancesErr: tc.createInstanceErr,
instanceGroupManager: &computepb.InstanceGroupManager{
BaseInstanceName: tc.baseInstanceName,
},
},
prng: rand.New(rand.NewSource(int64(time.Now().Nanosecond()))),
}
instanceName, providerID, err := client.CreateNode(context.Background(), tc.scalingGroupID)
if tc.wantErr {
assert.Error(err)
return
}
require.NoError(err)
assert.Contains(instanceName, "base-name")
assert.Contains(providerID, "base-name")
})
}
}
func TestDeleteNode(t *testing.T) {
testCases := map[string]struct {
providerID string
scalingGroupID string
getInstanceErr error
deleteInstanceErr error
wantErr bool
}{
"node is deleted": {
providerID: "gce://project/zone/instance-name",
scalingGroupID: "projects/project/zones/zone/instanceGroupManagers/instance-group",
},
"splitting providerID fails": {
providerID: "invalid",
wantErr: true,
},
"get instance fails": {
providerID: "gce://project/zone/instance-name",
getInstanceErr: errors.New("get instance error"),
wantErr: true,
},
"splitting scalingGroupID fails": {
providerID: "gce://project/zone/instance-name",
scalingGroupID: "invalid",
wantErr: true,
},
"delete instance fails": {
providerID: "gce://project/zone/instance-name",
scalingGroupID: "projects/project/zones/zone/instanceGroupManagers/instance-group",
deleteInstanceErr: errors.New("delete instance error"),
wantErr: true,
},
}
for name, tc := range testCases {
t.Run(name, func(t *testing.T) {
assert := assert.New(t)
require := require.New(t)
client := Client{
instanceGroupManagersAPI: &stubInstanceGroupManagersAPI{
deleteInstancesErr: tc.deleteInstanceErr,
},
instanceAPI: &stubInstanceAPI{
getErr: tc.getInstanceErr,
instance: &computepb.Instance{
Metadata: &computepb.Metadata{
Items: []*computepb.Items{
{Key: proto.String("created-by"), Value: &tc.scalingGroupID},
},
},
},
},
}
err := client.DeleteNode(context.Background(), tc.providerID)
if tc.wantErr {
assert.Error(err)
return
}
require.NoError(err)
})
}
}

View File

@ -0,0 +1,59 @@
package client
import (
"context"
"errors"
"net/http"
"github.com/edgelesssys/constellation/operators/constellation-node-operator/api/v1alpha1"
updatev1alpha1 "github.com/edgelesssys/constellation/operators/constellation-node-operator/api/v1alpha1"
"google.golang.org/api/googleapi"
computepb "google.golang.org/genproto/googleapis/cloud/compute/v1"
)
// GetNodeState returns the state of the node.
func (c *Client) GetNodeState(ctx context.Context, providerID string) (updatev1alpha1.CSPNodeState, error) {
project, zone, instanceName, err := splitProviderID(providerID)
if err != nil {
return "", err
}
instance, err := c.instanceAPI.Get(ctx, &computepb.GetInstanceRequest{
Instance: instanceName,
Project: project,
Zone: zone,
})
if err != nil {
var apiErr *googleapi.Error
if errors.As(err, &apiErr) {
if apiErr.Code == http.StatusNotFound {
return v1alpha1.NodeStateTerminated, nil
}
}
return "", err
}
if instance.Status == nil {
return v1alpha1.NodeStateUnknown, nil
}
// reference: https://cloud.google.com/compute/docs/instances/instance-life-cycle
switch *instance.Status {
case computepb.Instance_PROVISIONING.String():
fallthrough
case computepb.Instance_STAGING.String():
return v1alpha1.NodeStateCreating, nil
case computepb.Instance_RUNNING.String():
return v1alpha1.NodeStateReady, nil
case computepb.Instance_STOPPING.String():
fallthrough
case computepb.Instance_SUSPENDING.String():
fallthrough
case computepb.Instance_SUSPENDED.String():
fallthrough
case computepb.Instance_REPAIRING.String():
fallthrough
case computepb.Instance_TERMINATED.String(): // this is stopped in GCP terms
return v1alpha1.NodeStateStopped, nil
}
return v1alpha1.NodeStateUnknown, nil
}

View File

@ -0,0 +1,114 @@
package client
import (
"context"
"errors"
"net/http"
"testing"
updatev1alpha1 "github.com/edgelesssys/constellation/operators/constellation-node-operator/api/v1alpha1"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"google.golang.org/api/googleapi"
computepb "google.golang.org/genproto/googleapis/cloud/compute/v1"
"google.golang.org/protobuf/proto"
)
func TestGetNodeState(t *testing.T) {
testCases := map[string]struct {
providerID string
getInstanceErr error
instanceStatus *string
wantNodeState updatev1alpha1.CSPNodeState
wantErr bool
}{
"node is deleted and API returns 404": {
providerID: "gce://project/zone/instance-name",
getInstanceErr: &googleapi.Error{
Code: http.StatusNotFound,
},
wantNodeState: updatev1alpha1.NodeStateTerminated,
},
"splitting providerID fails": {
providerID: "invalid",
wantErr: true,
},
"node is deleted and API returns other error": {
providerID: "gce://project/zone/instance-name",
getInstanceErr: errors.New("get instance error"),
wantErr: true,
},
"instance has no status": {
providerID: "gce://project/zone/instance-name",
wantNodeState: updatev1alpha1.NodeStateUnknown,
},
"instance is provisioning": {
providerID: "gce://project/zone/instance-name",
instanceStatus: proto.String("PROVISIONING"),
wantNodeState: updatev1alpha1.NodeStateCreating,
},
"instance is staging": {
providerID: "gce://project/zone/instance-name",
instanceStatus: proto.String("STAGING"),
wantNodeState: updatev1alpha1.NodeStateCreating,
},
"instance is running": {
providerID: "gce://project/zone/instance-name",
instanceStatus: proto.String("RUNNING"),
wantNodeState: updatev1alpha1.NodeStateReady,
},
"instance is stopping": {
providerID: "gce://project/zone/instance-name",
instanceStatus: proto.String("STOPPING"),
wantNodeState: updatev1alpha1.NodeStateStopped,
},
"instance is suspending": {
providerID: "gce://project/zone/instance-name",
instanceStatus: proto.String("SUSPENDING"),
wantNodeState: updatev1alpha1.NodeStateStopped,
},
"instance is suspended": {
providerID: "gce://project/zone/instance-name",
instanceStatus: proto.String("SUSPENDED"),
wantNodeState: updatev1alpha1.NodeStateStopped,
},
"instance is repairing": {
providerID: "gce://project/zone/instance-name",
instanceStatus: proto.String("REPAIRING"),
wantNodeState: updatev1alpha1.NodeStateStopped,
},
"instance terminated": {
providerID: "gce://project/zone/instance-name",
instanceStatus: proto.String("TERMINATED"),
wantNodeState: updatev1alpha1.NodeStateStopped,
},
"instance state unknown": {
providerID: "gce://project/zone/instance-name",
instanceStatus: proto.String("unknown"),
wantNodeState: updatev1alpha1.NodeStateUnknown,
},
}
for name, tc := range testCases {
t.Run(name, func(t *testing.T) {
assert := assert.New(t)
require := require.New(t)
client := Client{
instanceAPI: &stubInstanceAPI{
getErr: tc.getInstanceErr,
instance: &computepb.Instance{
Status: tc.instanceStatus,
},
},
}
nodeState, err := client.GetNodeState(context.Background(), tc.providerID)
if tc.wantErr {
assert.Error(err)
return
}
require.NoError(err)
assert.Equal(tc.wantNodeState, nodeState)
})
}
}

View File

@ -0,0 +1,30 @@
package client
import (
"fmt"
"regexp"
)
var providerIDRegex = regexp.MustCompile(`^gce://([^/]+)/([^/]+)/([^/]+)$`)
// splitProviderID splits a provider's id into core components.
// A providerID is build after the schema 'gce://<project-id>/<zone>/<instance-name>'
func splitProviderID(providerID string) (project, zone, instance string, err error) {
matches := providerIDRegex.FindStringSubmatch(providerID)
if len(matches) != 4 {
return "", "", "", fmt.Errorf("splitting providerID: %q. matches: %v", providerID, matches)
}
return matches[1], matches[2], matches[3], nil
}
// joinProviderID builds a k8s provider ID for GCP instances.
// A providerID is build after the schema 'gce://<project-id>/<zone>/<instance-name>'
func joinProviderID(project, zone, instanceName string) string {
return fmt.Sprintf("gce://%v/%v/%v", project, zone, instanceName)
}
// joinInstanceID builds a gcp instance ID from the zone and instance name.
func joinInstanceID(zone, instanceName string) string {
return fmt.Sprintf("zones/%v/instances/%v", zone, instanceName)
}

View File

@ -0,0 +1,99 @@
package client
import (
"testing"
"github.com/stretchr/testify/assert"
)
func TestSplitProviderID(t *testing.T) {
testCases := map[string]struct {
providerID string
wantProjectID string
wantZone string
wantInstance string
wantErr bool
}{
"simple id": {
providerID: "gce://someProject/someZone/someInstance",
wantProjectID: "someProject",
wantZone: "someZone",
wantInstance: "someInstance",
},
"incomplete id": {
providerID: "gce://someProject/someZone",
wantErr: true,
},
"wrong provider": {
providerID: "azure://someProject/someZone/someInstance",
wantErr: true,
},
}
for name, tc := range testCases {
t.Run(name, func(t *testing.T) {
assert := assert.New(t)
projectID, zone, instance, err := splitProviderID(tc.providerID)
if tc.wantErr {
assert.Error(err)
return
}
assert.NoError(err)
assert.Equal(tc.wantProjectID, projectID)
assert.Equal(tc.wantZone, zone)
assert.Equal(tc.wantInstance, instance)
})
}
}
func TestJoinProviderID(t *testing.T) {
testCases := map[string]struct {
projectID string
zone string
instance string
wantProviderID string
}{
"simple id": {
projectID: "someProject",
zone: "someZone",
instance: "someInstance",
wantProviderID: "gce://someProject/someZone/someInstance",
},
}
for name, tc := range testCases {
t.Run(name, func(t *testing.T) {
assert := assert.New(t)
providerID := joinProviderID(tc.projectID, tc.zone, tc.instance)
assert.Equal(tc.wantProviderID, providerID)
})
}
}
func TestJoinnstanceID(t *testing.T) {
testCases := map[string]struct {
zone string
instanceName string
wantInstanceID string
}{
"simple id": {
zone: "someZone",
instanceName: "someInstance",
wantInstanceID: "zones/someZone/instances/someInstance",
},
}
for name, tc := range testCases {
t.Run(name, func(t *testing.T) {
assert := assert.New(t)
providerID := joinInstanceID(tc.zone, tc.instanceName)
assert.Equal(tc.wantInstanceID, providerID)
})
}
}

View File

@ -0,0 +1,118 @@
package client
import (
"context"
"errors"
"fmt"
computepb "google.golang.org/genproto/googleapis/cloud/compute/v1"
)
// GetScalingGroupImage returns the image URI of the scaling group.
func (c *Client) GetScalingGroupImage(ctx context.Context, scalingGroupID string) (string, error) {
instanceTemplate, err := c.getScalingGroupTemplate(ctx, scalingGroupID)
if err != nil {
return "", err
}
return instanceTemplateSourceImage(instanceTemplate)
}
// SetScalingGroupImage sets the image URI of the scaling group.
func (c *Client) SetScalingGroupImage(ctx context.Context, scalingGroupID, imageURI string) error {
project, zone, instanceGroupName, err := splitInstanceGroupID(scalingGroupID)
if err != nil {
return err
}
// get current template
instanceTemplate, err := c.getScalingGroupTemplate(ctx, scalingGroupID)
if err != nil {
return err
}
// check if template already uses the same image
oldImageURI, err := instanceTemplateSourceImage(instanceTemplate)
if err != nil {
return err
}
if oldImageURI == imageURI {
return nil
}
// clone template with desired image
if instanceTemplate.Name == nil {
return fmt.Errorf("instance template of scaling group %q has no name", scalingGroupID)
}
instanceTemplate.Properties.Disks[0].InitializeParams.SourceImage = &imageURI
newTemplateName, err := generateInstanceTemplateName(*instanceTemplate.Name)
if err != nil {
return err
}
instanceTemplate.Name = &newTemplateName
op, err := c.instanceTemplateAPI.Insert(ctx, &computepb.InsertInstanceTemplateRequest{
Project: project,
InstanceTemplateResource: instanceTemplate,
})
if err != nil {
return fmt.Errorf("cloning instance template: %w", err)
}
if err := op.Wait(ctx); err != nil {
return fmt.Errorf("waiting for cloned instance template: %w", err)
}
newTemplateURI := joinInstanceTemplateURI(project, newTemplateName)
// update instance group manager to use new template
op, err = c.instanceGroupManagersAPI.SetInstanceTemplate(ctx, &computepb.SetInstanceTemplateInstanceGroupManagerRequest{
InstanceGroupManager: instanceGroupName,
Project: project,
Zone: zone,
InstanceGroupManagersSetInstanceTemplateRequestResource: &computepb.InstanceGroupManagersSetInstanceTemplateRequest{
InstanceTemplate: &newTemplateURI,
},
})
if err != nil {
return fmt.Errorf("setting instance template: %w", err)
}
if err := op.Wait(ctx); err != nil {
return fmt.Errorf("waiting for setting instance template: %w", err)
}
return nil
}
func (c *Client) getScalingGroupTemplate(ctx context.Context, scalingGroupID string) (*computepb.InstanceTemplate, error) {
project, zone, instanceGroupName, err := splitInstanceGroupID(scalingGroupID)
if err != nil {
return nil, err
}
instanceGroupManager, err := c.instanceGroupManagersAPI.Get(ctx, &computepb.GetInstanceGroupManagerRequest{
InstanceGroupManager: instanceGroupName,
Project: project,
Zone: zone,
})
if err != nil {
return nil, fmt.Errorf("getting instance group manager %q: %w", instanceGroupName, err)
}
if instanceGroupManager.InstanceTemplate == nil {
return nil, fmt.Errorf("instance group manager %q has no instance template", instanceGroupName)
}
instanceTemplateProject, instanceTemplateName, err := splitInstanceTemplateID(uriNormalize(*instanceGroupManager.InstanceTemplate))
if err != nil {
return nil, fmt.Errorf("splitting instance template name: %w", err)
}
instanceTemplate, err := c.instanceTemplateAPI.Get(ctx, &computepb.GetInstanceTemplateRequest{
InstanceTemplate: instanceTemplateName,
Project: instanceTemplateProject,
})
if err != nil {
return nil, fmt.Errorf("getting instance template %q: %w", instanceTemplateName, err)
}
return instanceTemplate, nil
}
func instanceTemplateSourceImage(instanceTemplate *computepb.InstanceTemplate) (string, error) {
if instanceTemplate.Properties == nil ||
len(instanceTemplate.Properties.Disks) == 0 ||
instanceTemplate.Properties.Disks[0].InitializeParams == nil ||
instanceTemplate.Properties.Disks[0].InitializeParams.SourceImage == nil {
return "", errors.New("instance template has no source image")
}
return uriNormalize(*instanceTemplate.Properties.Disks[0].InitializeParams.SourceImage), nil
}

View File

@ -0,0 +1,284 @@
package client
import (
"context"
"errors"
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
computepb "google.golang.org/genproto/googleapis/cloud/compute/v1"
"google.golang.org/protobuf/proto"
)
func TestGetScalingGroupImage(t *testing.T) {
testCases := map[string]struct {
scalingGroupID string
instanceGroupManagerTemplateID *string
instanceTemplate *computepb.InstanceTemplate
getInstanceGroupManagerErr error
getInstanceTemplateErr error
wantImage string
wantErr bool
}{
"getting image works": {
scalingGroupID: "projects/project/zones/zone/instanceGroupManagers/instance-group",
instanceGroupManagerTemplateID: proto.String("projects/project/global/instanceTemplates/instance-template"),
instanceTemplate: &computepb.InstanceTemplate{
Properties: &computepb.InstanceProperties{
Disks: []*computepb.AttachedDisk{
{
InitializeParams: &computepb.AttachedDiskInitializeParams{
SourceImage: proto.String("https://www.googleapis.com/compute/v1/projects/project/global/images/image"),
},
},
},
},
},
wantImage: "projects/project/global/images/image",
},
"splitting scalingGroupID fails": {
scalingGroupID: "invalid",
wantErr: true,
},
"get instance fails": {
scalingGroupID: "projects/project/zones/zone/instanceGroupManagers/instance-group",
getInstanceGroupManagerErr: errors.New("get instance error"),
wantErr: true,
},
"instance group manager has no template": {
scalingGroupID: "projects/project/zones/zone/instanceGroupManagers/instance-group",
wantErr: true,
},
"instance group manager template id is invalid": {
scalingGroupID: "projects/project/zones/zone/instanceGroupManagers/instance-group",
instanceGroupManagerTemplateID: proto.String("invalid"),
wantErr: true,
},
"get instance template fails": {
scalingGroupID: "projects/project/zones/zone/instanceGroupManagers/instance-group",
instanceGroupManagerTemplateID: proto.String("projects/project/global/instanceTemplates/instance-template"),
getInstanceTemplateErr: errors.New("get instance template error"),
wantErr: true,
},
"instance template has no disks": {
scalingGroupID: "projects/project/zones/zone/instanceGroupManagers/instance-group",
instanceGroupManagerTemplateID: proto.String("projects/project/global/instanceTemplates/instance-template"),
instanceTemplate: &computepb.InstanceTemplate{
Properties: &computepb.InstanceProperties{},
},
wantErr: true,
},
}
for name, tc := range testCases {
t.Run(name, func(t *testing.T) {
assert := assert.New(t)
require := require.New(t)
client := Client{
instanceGroupManagersAPI: &stubInstanceGroupManagersAPI{
getErr: tc.getInstanceGroupManagerErr,
instanceGroupManager: &computepb.InstanceGroupManager{
InstanceTemplate: tc.instanceGroupManagerTemplateID,
},
},
instanceTemplateAPI: &stubInstanceTemplateAPI{
getErr: tc.getInstanceTemplateErr,
template: tc.instanceTemplate,
},
}
gotImage, err := client.GetScalingGroupImage(context.Background(), tc.scalingGroupID)
if tc.wantErr {
assert.Error(err)
return
}
require.NoError(err)
assert.Equal(tc.wantImage, gotImage)
})
}
}
func TestSetScalingGroupImage(t *testing.T) {
testCases := map[string]struct {
scalingGroupID string
imageURI string
instanceGroupManagerTemplateID *string
instanceTemplate *computepb.InstanceTemplate
getInstanceGroupManagerErr error
getInstanceTemplateErr error
setInstanceTemplateErr error
insertInstanceTemplateErr error
wantErr bool
}{
"setting image works": {
scalingGroupID: "projects/project/zones/zone/instanceGroupManagers/instance-group",
imageURI: "projects/project/global/images/image-2",
instanceGroupManagerTemplateID: proto.String("projects/project/global/instanceTemplates/instance-template"),
instanceTemplate: &computepb.InstanceTemplate{
Name: proto.String("instance-template"),
Properties: &computepb.InstanceProperties{
Disks: []*computepb.AttachedDisk{
{
InitializeParams: &computepb.AttachedDiskInitializeParams{
SourceImage: proto.String("https://www.googleapis.com/compute/v1/projects/project/global/images/image-1"),
},
},
},
},
},
},
"same image already in use": {
scalingGroupID: "projects/project/zones/zone/instanceGroupManagers/instance-group",
imageURI: "projects/project/global/images/image",
instanceGroupManagerTemplateID: proto.String("projects/project/global/instanceTemplates/instance-template"),
instanceTemplate: &computepb.InstanceTemplate{
Name: proto.String("instance-template"),
Properties: &computepb.InstanceProperties{
Disks: []*computepb.AttachedDisk{
{
InitializeParams: &computepb.AttachedDiskInitializeParams{
SourceImage: proto.String("https://www.googleapis.com/compute/v1/projects/project/global/images/image"),
},
},
},
},
},
// will not be triggered
insertInstanceTemplateErr: errors.New("insert instance template error"),
},
"splitting scalingGroupID fails": {
scalingGroupID: "invalid",
wantErr: true,
},
"get instance fails": {
scalingGroupID: "projects/project/zones/zone/instanceGroupManagers/instance-group",
getInstanceGroupManagerErr: errors.New("get instance error"),
wantErr: true,
},
"instance group manager has no template": {
scalingGroupID: "projects/project/zones/zone/instanceGroupManagers/instance-group",
wantErr: true,
},
"instance group manager template id is invalid": {
scalingGroupID: "projects/project/zones/zone/instanceGroupManagers/instance-group",
instanceGroupManagerTemplateID: proto.String("invalid"),
wantErr: true,
},
"get instance template fails": {
scalingGroupID: "projects/project/zones/zone/instanceGroupManagers/instance-group",
instanceGroupManagerTemplateID: proto.String("projects/project/global/instanceTemplates/instance-template"),
getInstanceTemplateErr: errors.New("get instance template error"),
wantErr: true,
},
"instance template has no disks": {
scalingGroupID: "projects/project/zones/zone/instanceGroupManagers/instance-group",
instanceGroupManagerTemplateID: proto.String("projects/project/global/instanceTemplates/instance-template"),
instanceTemplate: &computepb.InstanceTemplate{
Properties: &computepb.InstanceProperties{},
},
wantErr: true,
},
"instance template has no name": {
scalingGroupID: "projects/project/zones/zone/instanceGroupManagers/instance-group",
imageURI: "projects/project/global/images/image-2",
instanceGroupManagerTemplateID: proto.String("projects/project/global/instanceTemplates/instance-template"),
instanceTemplate: &computepb.InstanceTemplate{
Properties: &computepb.InstanceProperties{
Disks: []*computepb.AttachedDisk{
{
InitializeParams: &computepb.AttachedDiskInitializeParams{
SourceImage: proto.String("https://www.googleapis.com/compute/v1/projects/project/global/images/image-1"),
},
},
},
},
},
wantErr: true,
},
"instance template name generation fails": {
scalingGroupID: "projects/project/zones/zone/instanceGroupManagers/instance-group",
imageURI: "projects/project/global/images/image-2",
instanceGroupManagerTemplateID: proto.String("projects/project/global/instanceTemplates/instance-template"),
instanceTemplate: &computepb.InstanceTemplate{
Name: proto.String("instance-template-999999999999999999999"),
Properties: &computepb.InstanceProperties{
Disks: []*computepb.AttachedDisk{
{
InitializeParams: &computepb.AttachedDiskInitializeParams{
SourceImage: proto.String("https://www.googleapis.com/compute/v1/projects/project/global/images/image-1"),
},
},
},
},
},
wantErr: true,
},
"instance template insert fails": {
scalingGroupID: "projects/project/zones/zone/instanceGroupManagers/instance-group",
imageURI: "projects/project/global/images/image-2",
instanceGroupManagerTemplateID: proto.String("projects/project/global/instanceTemplates/instance-template"),
instanceTemplate: &computepb.InstanceTemplate{
Name: proto.String("instance-template"),
Properties: &computepb.InstanceProperties{
Disks: []*computepb.AttachedDisk{
{
InitializeParams: &computepb.AttachedDiskInitializeParams{
SourceImage: proto.String("https://www.googleapis.com/compute/v1/projects/project/global/images/image-1"),
},
},
},
},
},
insertInstanceTemplateErr: errors.New("insert instance template error"),
wantErr: true,
},
"setting instance template fails": {
scalingGroupID: "projects/project/zones/zone/instanceGroupManagers/instance-group",
imageURI: "projects/project/global/images/image-2",
instanceGroupManagerTemplateID: proto.String("projects/project/global/instanceTemplates/instance-template"),
instanceTemplate: &computepb.InstanceTemplate{
Name: proto.String("instance-template"),
Properties: &computepb.InstanceProperties{
Disks: []*computepb.AttachedDisk{
{
InitializeParams: &computepb.AttachedDiskInitializeParams{
SourceImage: proto.String("https://www.googleapis.com/compute/v1/projects/project/global/images/image-1"),
},
},
},
},
},
setInstanceTemplateErr: errors.New("setting instance template error"),
wantErr: true,
},
}
for name, tc := range testCases {
t.Run(name, func(t *testing.T) {
assert := assert.New(t)
require := require.New(t)
client := Client{
instanceGroupManagersAPI: &stubInstanceGroupManagersAPI{
getErr: tc.getInstanceGroupManagerErr,
setInstanceTemplateErr: tc.setInstanceTemplateErr,
instanceGroupManager: &computepb.InstanceGroupManager{
InstanceTemplate: tc.instanceGroupManagerTemplateID,
},
},
instanceTemplateAPI: &stubInstanceTemplateAPI{
getErr: tc.getInstanceTemplateErr,
insertErr: tc.insertInstanceTemplateErr,
template: tc.instanceTemplate,
},
}
err := client.SetScalingGroupImage(context.Background(), tc.scalingGroupID, tc.imageURI)
if tc.wantErr {
assert.Error(err)
return
}
require.NoError(err)
})
}
}

View File

@ -19,6 +19,8 @@ import (
"sigs.k8s.io/controller-runtime/pkg/healthz"
"sigs.k8s.io/controller-runtime/pkg/log/zap"
gcpclient "github.com/edgelesssys/constellation/operators/constellation-node-operator/internal/gcp/client"
updatev1alpha1 "github.com/edgelesssys/constellation/operators/constellation-node-operator/api/v1alpha1"
"github.com/edgelesssys/constellation/operators/constellation-node-operator/controllers"
"github.com/edgelesssys/constellation/operators/constellation-node-operator/internal/etcd"
@ -58,11 +60,16 @@ func main() {
ctrl.SetLogger(zap.New(zap.UseFlagOptions(&opts)))
var cspClient cspAPI
var clientErr error
switch strings.ToLower(csp) {
case "azure":
panic("Azure is not supported yet")
case "gcp":
panic("GCP is not supported yet")
cspClient, clientErr = gcpclient.New(context.Background())
if clientErr != nil {
setupLog.Error(clientErr, "unable to create GCP client")
os.Exit(1)
}
default:
setupLog.Info("Unknown CSP", "csp", csp)
os.Exit(1)