diff --git a/operators/constellation-node-operator/go.mod b/operators/constellation-node-operator/go.mod index 3b567e58c..5d7c255b3 100644 --- a/operators/constellation-node-operator/go.mod +++ b/operators/constellation-node-operator/go.mod @@ -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 diff --git a/operators/constellation-node-operator/go.sum b/operators/constellation-node-operator/go.sum index 33bd27544..62998e557 100644 --- a/operators/constellation-node-operator/go.sum +++ b/operators/constellation-node-operator/go.sum @@ -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= diff --git a/operators/constellation-node-operator/internal/gcp/client/api.go b/operators/constellation-node-operator/internal/gcp/client/api.go new file mode 100644 index 000000000..e01a7fe00 --- /dev/null +++ b/operators/constellation-node-operator/internal/gcp/client/api.go @@ -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 +} diff --git a/operators/constellation-node-operator/internal/gcp/client/client.go b/operators/constellation-node-operator/internal/gcp/client/client.go new file mode 100644 index 000000000..4b44a19cd --- /dev/null +++ b/operators/constellation-node-operator/internal/gcp/client/client.go @@ -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 +} diff --git a/operators/constellation-node-operator/internal/gcp/client/client_test.go b/operators/constellation-node-operator/internal/gcp/client/client_test.go new file mode 100644 index 000000000..ec5a1b396 --- /dev/null +++ b/operators/constellation-node-operator/internal/gcp/client/client_test.go @@ -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 +} diff --git a/operators/constellation-node-operator/internal/gcp/client/disks.go b/operators/constellation-node-operator/internal/gcp/client/disks.go new file mode 100644 index 000000000..fb2d1c6b8 --- /dev/null +++ b/operators/constellation-node-operator/internal/gcp/client/disks.go @@ -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] +} diff --git a/operators/constellation-node-operator/internal/gcp/client/disks_test.go b/operators/constellation-node-operator/internal/gcp/client/disks_test.go new file mode 100644 index 000000000..342bfe52e --- /dev/null +++ b/operators/constellation-node-operator/internal/gcp/client/disks_test.go @@ -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) + }) + } +} diff --git a/operators/constellation-node-operator/internal/gcp/client/gcpwrappers.go b/operators/constellation-node-operator/internal/gcp/client/gcpwrappers.go new file mode 100644 index 000000000..a72cfcd1c --- /dev/null +++ b/operators/constellation-node-operator/internal/gcp/client/gcpwrappers.go @@ -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...) +} diff --git a/operators/constellation-node-operator/internal/gcp/client/instancegroupmanagers.go b/operators/constellation-node-operator/internal/gcp/client/instancegroupmanagers.go new file mode 100644 index 000000000..9cbc06610 --- /dev/null +++ b/operators/constellation-node-operator/internal/gcp/client/instancegroupmanagers.go @@ -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 +} diff --git a/operators/constellation-node-operator/internal/gcp/client/instancegroupmanagers_test.go b/operators/constellation-node-operator/internal/gcp/client/instancegroupmanagers_test.go new file mode 100644 index 000000000..3b202c401 --- /dev/null +++ b/operators/constellation-node-operator/internal/gcp/client/instancegroupmanagers_test.go @@ -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 +} diff --git a/operators/constellation-node-operator/internal/gcp/client/instancetemplate.go b/operators/constellation-node-operator/internal/gcp/client/instancetemplate.go new file mode 100644 index 000000000..83d502052 --- /dev/null +++ b/operators/constellation-node-operator/internal/gcp/client/instancetemplate.go @@ -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 -. +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) +} diff --git a/operators/constellation-node-operator/internal/gcp/client/instancetemplate_test.go b/operators/constellation-node-operator/internal/gcp/client/instancetemplate_test.go new file mode 100644 index 000000000..f367e198c --- /dev/null +++ b/operators/constellation-node-operator/internal/gcp/client/instancetemplate_test.go @@ -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) +} diff --git a/operators/constellation-node-operator/internal/gcp/client/metadata.go b/operators/constellation-node-operator/internal/gcp/client/metadata.go new file mode 100644 index 000000000..ef876cb3f --- /dev/null +++ b/operators/constellation-node-operator/internal/gcp/client/metadata.go @@ -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 "" +} diff --git a/operators/constellation-node-operator/internal/gcp/client/metadata_test.go b/operators/constellation-node-operator/internal/gcp/client/metadata_test.go new file mode 100644 index 000000000..8acd45804 --- /dev/null +++ b/operators/constellation-node-operator/internal/gcp/client/metadata_test.go @@ -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)) + }) + } +} diff --git a/operators/constellation-node-operator/internal/gcp/client/nodeimage.go b/operators/constellation-node-operator/internal/gcp/client/nodeimage.go new file mode 100644 index 000000000..d995cb071 --- /dev/null +++ b/operators/constellation-node-operator/internal/gcp/client/nodeimage.go @@ -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) +} diff --git a/operators/constellation-node-operator/internal/gcp/client/nodeimage_test.go b/operators/constellation-node-operator/internal/gcp/client/nodeimage_test.go new file mode 100644 index 000000000..1bca6bdd9 --- /dev/null +++ b/operators/constellation-node-operator/internal/gcp/client/nodeimage_test.go @@ -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) + }) + } +} diff --git a/operators/constellation-node-operator/internal/gcp/client/pendingnode.go b/operators/constellation-node-operator/internal/gcp/client/pendingnode.go new file mode 100644 index 000000000..6c5a46397 --- /dev/null +++ b/operators/constellation-node-operator/internal/gcp/client/pendingnode.go @@ -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 +} diff --git a/operators/constellation-node-operator/internal/gcp/client/pendingnode_test.go b/operators/constellation-node-operator/internal/gcp/client/pendingnode_test.go new file mode 100644 index 000000000..1e171628a --- /dev/null +++ b/operators/constellation-node-operator/internal/gcp/client/pendingnode_test.go @@ -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) + }) + } +} diff --git a/operators/constellation-node-operator/internal/gcp/client/providerid.go b/operators/constellation-node-operator/internal/gcp/client/providerid.go new file mode 100644 index 000000000..29b9aceec --- /dev/null +++ b/operators/constellation-node-operator/internal/gcp/client/providerid.go @@ -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:////' +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:////' +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) +} diff --git a/operators/constellation-node-operator/internal/gcp/client/providerid_test.go b/operators/constellation-node-operator/internal/gcp/client/providerid_test.go new file mode 100644 index 000000000..7b1ddbc24 --- /dev/null +++ b/operators/constellation-node-operator/internal/gcp/client/providerid_test.go @@ -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) + }) + } +} diff --git a/operators/constellation-node-operator/internal/gcp/client/scalinggroup.go b/operators/constellation-node-operator/internal/gcp/client/scalinggroup.go new file mode 100644 index 000000000..5ff75bf85 --- /dev/null +++ b/operators/constellation-node-operator/internal/gcp/client/scalinggroup.go @@ -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 +} diff --git a/operators/constellation-node-operator/internal/gcp/client/scalinggroup_test.go b/operators/constellation-node-operator/internal/gcp/client/scalinggroup_test.go new file mode 100644 index 000000000..61c127abb --- /dev/null +++ b/operators/constellation-node-operator/internal/gcp/client/scalinggroup_test.go @@ -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) + }) + } +} diff --git a/operators/constellation-node-operator/main.go b/operators/constellation-node-operator/main.go index 9b3020028..6ea13a1fb 100644 --- a/operators/constellation-node-operator/main.go +++ b/operators/constellation-node-operator/main.go @@ -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)