└── deploypipeline-master └── deploypipeline ├── .gitignore ├── errors.go ├── README.md ├── go.mod ├── go.sum └── deploypipeline.go /deploypipeline-master/deploypipeline/.gitignore: -------------------------------------------------------------------------------- 1 | .idea -------------------------------------------------------------------------------- /deploypipeline-master/deploypipeline/errors.go: -------------------------------------------------------------------------------- 1 | package deploypipeline 2 | 3 | import "errors" 4 | 5 | var ( 6 | // os errors 7 | ErrInvalid = errors.New("invalid argument") 8 | ErrPermission = errors.New("permission denied") 9 | ErrExist = errors.New("file already exists") 10 | ErrNotExist = errors.New("file does not exist") 11 | ErrClosed = errors.New("file already closed") 12 | 13 | // pipeline errors 14 | ErrCreateNamespace = errors.New("Error creating namespace") 15 | ) 16 | 17 | func errInvalid() error { return ErrInvalid } 18 | func errPermission() error { return ErrPermission } 19 | func errExist() error { return ErrExist } 20 | func errNotExist() error { return ErrNotExist } 21 | func errClosed() error { return ErrClosed } 22 | 23 | func errCreateNamespace() error { return ErrCreateNamespace } 24 | -------------------------------------------------------------------------------- /deploypipeline-master/deploypipeline/README.md: -------------------------------------------------------------------------------- 1 | # Dome Deploy Pipeline 2 | 3 | ## Update `deploypipeline` Instructions 4 | 5 | 1. Create new deploypipeline release/tag with format `vX.X.X` 6 | 2. `export GOPRIVATE=git.domenetwork.io` 7 | 3. add to `.gitconfig`: 8 | 9 | ``` 10 | [url "ssh://git@git.domenetwork.io/"] 11 | insteadOf = https://git.domenetwork.io/ 12 | ``` 13 | 14 | 4. `go get -u git.domenetwork.io/dome/deploypipeline` 15 | 5. `go mod tidy` 16 | 6. `go mod vendor` 17 | 7. `commit all changes, open and merge PR` 18 | 19 | ## Sample client implementation 20 | 21 | ``` 22 | package main 23 | 24 | import ( 25 | "fmt" 26 | "io/ioutil" 27 | "log" 28 | "path/filepath" 29 | "runtime" 30 | 31 | "git.domenetwork.io/dome/deploypipeline" 32 | ) 33 | 34 | func main() { 35 | // get the file containing ssh private key 36 | var ( 37 | _, b, _, _ = runtime.Caller(0) 38 | basepath = filepath.Dir(b) 39 | ) 40 | 41 | fname := filepath.Join(basepath, "id_rsa") 42 | 43 | // load the private key file into a buffer 44 | secretData, err := ioutil.ReadFile(fname) 45 | if err != nil { 46 | log.Fatal(err) 47 | } 48 | 49 | // create customer config object for deploy pipeline 50 | customerConfig := deploypipeline.CustomerData{ 51 | AppName: "ocean-api", 52 | CustomerName: "Dome", 53 | CustomerProjectName: "Master Blaster", 54 | CustomerServiceName: "Build Pipeline", 55 | DockerImageName: "jspruancedome/ocean-api:dome", 56 | Namespace: "test-pipeline", 57 | SSHPrivateKey: secretData, 58 | } 59 | 60 | // initialize deploy pipeline 61 | deployClusterPath := "kubeconfig.yaml" 62 | clientset, config, initStatus, initErr := deploypipeline.Init(deployClusterPath) 63 | if initErr != nil { 64 | fmt.Println(initErr) 65 | } 66 | 67 | fmt.Println(clientset) 68 | fmt.Println(config) 69 | fmt.Println(initStatus.Message) 70 | 71 | deployPipelineStatus, deployPipelineErr := deploypipeline.CreateDeployment(customerConfig, clientset) 72 | if deployPipelineErr != nil { 73 | fmt.Println(deployPipelineErr) 74 | } 75 | 76 | fmt.Println(deployPipelineStatus) 77 | 78 | // scale deployment down to 0 ReplicaSets 79 | scaleDownResult, scaleDownErr := deploypipeline.ScaleDeployment(customerConfig, clientset, 0) 80 | if scaleDownErr != nil { 81 | fmt.Println(scaleDownErr) 82 | } 83 | fmt.Println(scaleDownResult) 84 | 85 | // scale deployment up to 2 ReplicaSets 86 | scaleUpResult, scaleUpErr := deploypipeline.ScaleDeployment(customerConfig, clientset, 2) 87 | if scaleUpErr != nil { 88 | fmt.Println(scaleUpErr) 89 | } 90 | fmt.Println(scaleUpResult) 91 | 92 | // kubectl -n test-pipeline describe deployment ocean-api 93 | } 94 | ``` -------------------------------------------------------------------------------- /deploypipeline-master/deploypipeline/go.mod: -------------------------------------------------------------------------------- 1 | module git.domenetwork.io/dome/deploypipeline 2 | 3 | go 1.19 4 | 5 | require ( 6 | google.golang.org/api v0.124.0 7 | istio.io/api v0.0.0-20230525160342-5ee20e270db3 8 | istio.io/client-go v1.17.1 9 | k8s.io/api v0.27.2 10 | k8s.io/apimachinery v0.27.2 11 | k8s.io/client-go v0.27.2 12 | ) 13 | 14 | require ( 15 | cloud.google.com/go/compute v1.19.3 // indirect 16 | cloud.google.com/go/compute/metadata v0.2.3 // indirect 17 | github.com/davecgh/go-spew v1.1.1 // indirect 18 | github.com/emicklei/go-restful/v3 v3.9.0 // indirect 19 | github.com/go-logr/logr v1.2.3 // indirect 20 | github.com/go-openapi/jsonpointer v0.19.6 // indirect 21 | github.com/go-openapi/jsonreference v0.20.1 // indirect 22 | github.com/go-openapi/swag v0.22.3 // indirect 23 | github.com/gogo/protobuf v1.3.2 // indirect 24 | github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect 25 | github.com/golang/protobuf v1.5.3 // indirect 26 | github.com/google/gnostic v0.5.7-v3refs // indirect 27 | github.com/google/go-cmp v0.5.9 // indirect 28 | github.com/google/gofuzz v1.1.0 // indirect 29 | github.com/google/s2a-go v0.1.4 // indirect 30 | github.com/google/uuid v1.3.0 // indirect 31 | github.com/googleapis/enterprise-certificate-proxy v0.2.3 // indirect 32 | github.com/googleapis/gax-go/v2 v2.9.1 // indirect 33 | github.com/imdario/mergo v0.3.6 // indirect 34 | github.com/josharian/intern v1.0.0 // indirect 35 | github.com/json-iterator/go v1.1.12 // indirect 36 | github.com/mailru/easyjson v0.7.7 // indirect 37 | github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect 38 | github.com/modern-go/reflect2 v1.0.2 // indirect 39 | github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect 40 | github.com/spf13/pflag v1.0.5 // indirect 41 | go.opencensus.io v0.24.0 // indirect 42 | golang.org/x/crypto v0.9.0 // indirect 43 | golang.org/x/net v0.10.0 // indirect 44 | golang.org/x/oauth2 v0.8.0 // indirect 45 | golang.org/x/sys v0.8.0 // indirect 46 | golang.org/x/term v0.8.0 // indirect 47 | golang.org/x/text v0.9.0 // indirect 48 | golang.org/x/time v0.0.0-20220210224613-90d013bbcef8 // indirect 49 | google.golang.org/appengine v1.6.7 // indirect 50 | google.golang.org/genproto v0.0.0-20230526015343-6ee61e4f9d5f // indirect 51 | google.golang.org/genproto/googleapis/api v0.0.0-20230526015343-6ee61e4f9d5f // indirect 52 | google.golang.org/genproto/googleapis/rpc v0.0.0-20230526015343-6ee61e4f9d5f // indirect 53 | google.golang.org/grpc v1.55.0 // indirect 54 | google.golang.org/protobuf v1.30.0 // indirect 55 | gopkg.in/inf.v0 v0.9.1 // indirect 56 | gopkg.in/yaml.v2 v2.4.0 // indirect 57 | gopkg.in/yaml.v3 v3.0.1 // indirect 58 | k8s.io/klog/v2 v2.90.1 // indirect 59 | k8s.io/kube-openapi v0.0.0-20230501164219-8b0f38b5fd1f // indirect 60 | k8s.io/utils v0.0.0-20230209194617-a36077c30491 // indirect 61 | sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd // indirect 62 | sigs.k8s.io/structured-merge-diff/v4 v4.2.3 // indirect 63 | sigs.k8s.io/yaml v1.3.0 // indirect 64 | ) 65 | -------------------------------------------------------------------------------- /deploypipeline-master/deploypipeline/go.sum: -------------------------------------------------------------------------------- 1 | cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= 2 | cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= 3 | cloud.google.com/go/compute v1.19.3 h1:DcTwsFgGev/wV5+q8o2fzgcHOaac+DKGC91ZlvpsQds= 4 | cloud.google.com/go/compute v1.19.3/go.mod h1:qxvISKp/gYnXkSAD1ppcSOveRAmzxicEv/JlizULFrI= 5 | cloud.google.com/go/compute/metadata v0.2.3 h1:mg4jlk7mCAj6xXp9UJ4fjI9VUI5rubuGBW5aJ7UnBMY= 6 | cloud.google.com/go/compute/metadata v0.2.3/go.mod h1:VAV5nSsACxMJvgaAuX6Pk2AawlZn8kiOGuCv6gTkwuA= 7 | github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= 8 | github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY= 9 | github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= 10 | github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= 11 | github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= 12 | github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= 13 | github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= 14 | github.com/cncf/udpa/go v0.0.0-20210930031921-04548b0d99d4/go.mod h1:6pvJx4me5XPnfI9Z40ddWsdw2W/uZgQLFXToKeRcDiI= 15 | github.com/cncf/xds/go v0.0.0-20210805033703-aa0b78936158/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= 16 | github.com/cncf/xds/go v0.0.0-20210922020428-25de7278fc84/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= 17 | github.com/cncf/xds/go v0.0.0-20211011173535-cb28da3451f1/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= 18 | github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= 19 | github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 20 | github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= 21 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 22 | github.com/docopt/docopt-go v0.0.0-20180111231733-ee0de3bc6815/go.mod h1:WwZ+bS3ebgob9U8Nd0kOddGdZWjyMGR8Wziv+TBNwSE= 23 | github.com/emicklei/go-restful/v3 v3.9.0 h1:XwGDlfxEnQZzuopoqxwSEllNcCOM9DhhFyhFIIGKwxE= 24 | github.com/emicklei/go-restful/v3 v3.9.0/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc= 25 | github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= 26 | github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= 27 | github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= 28 | github.com/envoyproxy/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk= 29 | github.com/envoyproxy/go-control-plane v0.9.10-0.20210907150352-cf90f659a021/go.mod h1:AFq3mo9L8Lqqiid3OhADV3RfLJnjiw63cSpi+fDTRC0= 30 | github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= 31 | github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= 32 | github.com/go-logr/logr v1.2.0/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= 33 | github.com/go-logr/logr v1.2.3 h1:2DntVwHkVopvECVRSlL5PSo9eG+cAkDCuckLubN+rq0= 34 | github.com/go-logr/logr v1.2.3/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= 35 | github.com/go-openapi/jsonpointer v0.19.6 h1:eCs3fxoIi3Wh6vtgmLTOjdhSpiqphQ+DaPn38N2ZdrE= 36 | github.com/go-openapi/jsonpointer v0.19.6/go.mod h1:osyAmYz/mB/C3I+WsTTSgw1ONzaLJoLCyoi6/zppojs= 37 | github.com/go-openapi/jsonreference v0.20.1 h1:FBLnyygC4/IZZr893oiomc9XaghoveYTrLC1F86HID8= 38 | github.com/go-openapi/jsonreference v0.20.1/go.mod h1:Bl1zwGIM8/wsvqjsOQLJ/SH+En5Ap4rVB5KVcIDZG2k= 39 | github.com/go-openapi/swag v0.22.3 h1:yMBqmnQ0gyZvEb/+KzuWZOXgllrXT4SADYbvDaXHv/g= 40 | github.com/go-openapi/swag v0.22.3/go.mod h1:UzaqsxGiab7freDnrUUra0MwWfN/q7tE4j+VcZ0yl14= 41 | github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0 h1:p104kn46Q8WdvHunIJ9dAyjPVtrBPhSr3KT2yUst43I= 42 | github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= 43 | github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= 44 | github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= 45 | github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= 46 | github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da h1:oI5xCqsCo564l8iNU+DwB5epxmsaqB+rhGL0m5jtYqE= 47 | github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= 48 | github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= 49 | github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= 50 | github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= 51 | github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= 52 | github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= 53 | github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= 54 | github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= 55 | github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs= 56 | github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w= 57 | github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= 58 | github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8= 59 | github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= 60 | github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= 61 | github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= 62 | github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= 63 | github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg= 64 | github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= 65 | github.com/google/gnostic v0.5.7-v3refs h1:FhTMOKj2VhjpouxvWJAV1TL304uMlb9zcDqkl6cEI54= 66 | github.com/google/gnostic v0.5.7-v3refs/go.mod h1:73MKFl6jIHelAJNaBGFzt3SPtZULs9dYrGFt8OiIsHQ= 67 | github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= 68 | github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= 69 | github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= 70 | github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= 71 | github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= 72 | github.com/google/go-cmp v0.5.3/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= 73 | github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= 74 | github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38= 75 | github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= 76 | github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= 77 | github.com/google/gofuzz v1.1.0 h1:Hsa8mG0dQ46ij8Sl2AYJDUv1oA9/d6Vk+3LG99Oe02g= 78 | github.com/google/gofuzz v1.1.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= 79 | github.com/google/pprof v0.0.0-20210720184732-4bb14d4b1be1 h1:K6RDEckDVWvDI9JAJYCmNdQXq6neHJOYx3V6jnqNEec= 80 | github.com/google/s2a-go v0.1.4 h1:1kZ/sQM3srePvKs3tXAvQzo66XfcReoqFpIpIccE7Oc= 81 | github.com/google/s2a-go v0.1.4/go.mod h1:Ej+mSEMGRnqRzjc7VtF+jdBwYG5fuJfiZ8ELkjEwM0A= 82 | github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= 83 | github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I= 84 | github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= 85 | github.com/googleapis/enterprise-certificate-proxy v0.2.3 h1:yk9/cqRKtT9wXZSsRH9aurXEpJX+U6FLtpYTdC3R06k= 86 | github.com/googleapis/enterprise-certificate-proxy v0.2.3/go.mod h1:AwSRAtLfXpU5Nm3pW+v7rGDHp09LsPtGY9MduiEsR9k= 87 | github.com/googleapis/gax-go/v2 v2.9.1 h1:DpTpJqzZ3NvX9zqjhIuI1oVzYZMvboZe+3LoeEIJjHM= 88 | github.com/googleapis/gax-go/v2 v2.9.1/go.mod h1:4FG3gMrVZlyMp5itSYKMU9z/lBE7+SbnUOvzH2HqbEY= 89 | github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw= 90 | github.com/imdario/mergo v0.3.6 h1:xTNEAn+kxVO7dTZGu0CegyqKZmoWFI0rF8UxjlB2d28= 91 | github.com/imdario/mergo v0.3.6/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA= 92 | github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY= 93 | github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y= 94 | github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= 95 | github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= 96 | github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= 97 | github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= 98 | github.com/kr/pretty v0.2.0/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= 99 | github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= 100 | github.com/kr/pretty v0.3.0 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0= 101 | github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= 102 | github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= 103 | github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= 104 | github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= 105 | github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0= 106 | github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc= 107 | github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= 108 | github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= 109 | github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= 110 | github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M= 111 | github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= 112 | github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA= 113 | github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= 114 | github.com/onsi/ginkgo/v2 v2.9.1 h1:zie5Ly042PD3bsCvsSOPvRnFwyo3rKe64TJlD6nu0mk= 115 | github.com/onsi/gomega v1.27.4 h1:Z2AnStgsdSayCMDiCU42qIz+HLqEPcgiOCXjAU/w+8E= 116 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= 117 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 118 | github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= 119 | github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ= 120 | github.com/rogpeppe/go-internal v1.10.0 h1:TMyTOH3F/DB16zRVcYyreMH6GnZZrwQVAoYjRBZyWFQ= 121 | github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= 122 | github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= 123 | github.com/stoewer/go-strcase v1.2.0/go.mod h1:IBiWB2sKIp3wVVQ3Y035++gc+knqhUQag1KpM8ahLw8= 124 | github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= 125 | github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= 126 | github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= 127 | github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= 128 | github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= 129 | github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= 130 | github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= 131 | github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= 132 | github.com/stretchr/testify v1.8.1 h1:w7B6lhMri9wdJUVmEZPGGhZzrYTPvgJArz7wNPgYKsk= 133 | github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= 134 | github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= 135 | github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= 136 | github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= 137 | go.opencensus.io v0.24.0 h1:y73uSU6J157QMP2kn2r30vwW1A2W2WFwSCGnAVxeaD0= 138 | go.opencensus.io v0.24.0/go.mod h1:vNK8G9p7aAivkbmorf4v+7Hgx+Zs0yY+0fOtgBfjQKo= 139 | go.opentelemetry.io/proto/otlp v0.7.0/go.mod h1:PqfVotwruBrMGOCsRd/89rSnXhoiJIqeYNgFYFoEGnI= 140 | golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= 141 | golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= 142 | golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= 143 | golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= 144 | golang.org/x/crypto v0.0.0-20220314234659-1baeb1ce4c0b/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= 145 | golang.org/x/crypto v0.9.0 h1:LF6fAI+IutBocDJ2OT0Q1g8plpYljMZ4+lty+dsqw3g= 146 | golang.org/x/crypto v0.9.0/go.mod h1:yrmDGqONDYtNj3tH8X9dzUun2m2lzPa9ngI6/RUPGR0= 147 | golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= 148 | golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= 149 | golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= 150 | golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= 151 | golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= 152 | golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= 153 | golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= 154 | golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 155 | golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 156 | golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 157 | golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 158 | golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= 159 | golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= 160 | golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= 161 | golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 162 | golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 163 | golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= 164 | golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= 165 | golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= 166 | golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= 167 | golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= 168 | golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= 169 | golang.org/x/net v0.10.0 h1:X2//UzNDwYmtCLn7To6G58Wr6f5ahEAQgKNzv9Y951M= 170 | golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg= 171 | golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= 172 | golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= 173 | golang.org/x/oauth2 v0.8.0 h1:6dkIjl3j3LtZ/O3sTgZTMsLKSftL/B8Zgq4huOIIUu8= 174 | golang.org/x/oauth2 v0.8.0/go.mod h1:yr7u4HXZRm1R1kBWqr/xKNqewf0plRYoB7sla+BCIXE= 175 | golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 176 | golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 177 | golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 178 | golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 179 | golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 180 | golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 181 | golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 182 | golang.org/x/sync v0.2.0 h1:PUR+T4wwASmuSTYdKjYHI5TD22Wy5ogLU5qZCOLxBrI= 183 | golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 184 | golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 185 | golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 186 | golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 187 | golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 188 | golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 189 | golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 190 | golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 191 | golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 192 | golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 193 | golang.org/x/sys v0.8.0 h1:EBmGv8NaZBZTWvrbjNoL6HVt+IVy3QDQpJs7VRIw3tU= 194 | golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 195 | golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= 196 | golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= 197 | golang.org/x/term v0.8.0 h1:n5xxQn2i3PC0yLAbjTpNT85q/Kgzcr2gIoX9OrJUols= 198 | golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo= 199 | golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= 200 | golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= 201 | golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= 202 | golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= 203 | golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= 204 | golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ= 205 | golang.org/x/text v0.9.0 h1:2sjJmO8cDvYveuX97RDLsxlyUxLl+GHoLxBiRdHllBE= 206 | golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= 207 | golang.org/x/time v0.0.0-20220210224613-90d013bbcef8 h1:vVKdlvoWBphwdxWKrFZEuM0kGgGLxUOYcY4U/2Vjg44= 208 | golang.org/x/time v0.0.0-20220210224613-90d013bbcef8/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= 209 | golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= 210 | golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= 211 | golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= 212 | golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= 213 | golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= 214 | golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= 215 | golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= 216 | golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= 217 | golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= 218 | golang.org/x/tools v0.7.0 h1:W4OVu8VVOaIO0yzWMNdepAulS7YfoS3Zabrm8DOXXU4= 219 | golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 220 | golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 221 | golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 222 | golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 223 | google.golang.org/api v0.124.0 h1:dP6Ef1VgOGqQ8eiv4GiY8RhmeyqzovcXBYPDUYG8Syo= 224 | google.golang.org/api v0.124.0/go.mod h1:xu2HQurE5gi/3t1aFCvhPD781p0a3p11sdunTJ2BlP4= 225 | google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= 226 | google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= 227 | google.golang.org/appengine v1.6.7 h1:FZR1q0exgwxzPzp/aF+VccGrSfxfPpkBqjIIEq3ru6c= 228 | google.golang.org/appengine v1.6.7/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= 229 | google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= 230 | google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= 231 | google.golang.org/genproto v0.0.0-20200513103714-09dca8ec2884/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= 232 | google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo= 233 | google.golang.org/genproto v0.0.0-20201019141844-1ed22bb0c154/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= 234 | google.golang.org/genproto v0.0.0-20230526015343-6ee61e4f9d5f h1:DwRdHa3+SynqBR2tx3LVtzJrGooL9hg1OCAfBdQAk1A= 235 | google.golang.org/genproto v0.0.0-20230526015343-6ee61e4f9d5f/go.mod h1:9ExIQyXL5hZrHzQceCwuSYwZZ5QZBazOcprJ5rgs3lY= 236 | google.golang.org/genproto/googleapis/api v0.0.0-20230526015343-6ee61e4f9d5f h1:dJhNU2ZodW2tHjMhmDOrcRSahqR0wgfOEBs8nSmVx5Y= 237 | google.golang.org/genproto/googleapis/api v0.0.0-20230526015343-6ee61e4f9d5f/go.mod h1:vHYtlOoi6TsQ3Uk2yxR7NI5z8uoV+3pZtR4jmHIkRig= 238 | google.golang.org/genproto/googleapis/rpc v0.0.0-20230526015343-6ee61e4f9d5f h1:QNVuVEP2S7NNxLdNdOq0RiW3c9pW4gIpUUd+GAOjk1Y= 239 | google.golang.org/genproto/googleapis/rpc v0.0.0-20230526015343-6ee61e4f9d5f/go.mod h1:66JfowdXAEgad5O9NnYcsNPLCPZJD++2L9X0PCMODrA= 240 | google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= 241 | google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= 242 | google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY= 243 | google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= 244 | google.golang.org/grpc v1.33.1/go.mod h1:fr5YgcSWrqhRRxogOsw7RzIpsmvOZ6IcH4kBYTpR3n0= 245 | google.golang.org/grpc v1.33.2/go.mod h1:JMHMWHQWaTccqQQlmk3MJZS+GWXOdAesneDmEnv2fbc= 246 | google.golang.org/grpc v1.36.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU= 247 | google.golang.org/grpc v1.45.0/go.mod h1:lN7owxKUQEqMfSyQikvvk5tf/6zMPsrK+ONuO11+0rQ= 248 | google.golang.org/grpc v1.55.0 h1:3Oj82/tFSCeUrRTg/5E/7d/W5A1tj6Ky1ABAuZuv5ag= 249 | google.golang.org/grpc v1.55.0/go.mod h1:iYEXKGkEBhg1PjZQvoYEVPTDkHo1/bjTnfwTeGONTY8= 250 | google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= 251 | google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= 252 | google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= 253 | google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE= 254 | google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo= 255 | google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= 256 | google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= 257 | google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= 258 | google.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGjtUeSXeh4= 259 | google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c= 260 | google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= 261 | google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= 262 | google.golang.org/protobuf v1.30.0 h1:kPPoIgf3TsEvrm0PFe15JQ+570QVxYzEvvHqChK+cng= 263 | google.golang.org/protobuf v1.30.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= 264 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 265 | gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 266 | gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= 267 | gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= 268 | gopkg.in/inf.v0 v0.9.1 h1:73M5CoZyi3ZLMOyDlQh031Cx6N9NDJ2Vvfl76EDAgDc= 269 | gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw= 270 | gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 271 | gopkg.in/yaml.v2 v2.2.3/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 272 | gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 273 | gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= 274 | gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= 275 | gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 276 | gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 277 | gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= 278 | gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 279 | honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= 280 | honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= 281 | istio.io/api v0.0.0-20230525160342-5ee20e270db3 h1:CwpyYuGnYC1tf5x7hVNWNKJ69j2exIoh5NtEvXgteAY= 282 | istio.io/api v0.0.0-20230525160342-5ee20e270db3/go.mod h1:dDMe1TsOtrRoUlBzdxqNolWXpXPQjLfbcXvqPMtQ6eo= 283 | istio.io/client-go v1.17.1 h1:W0kQXYCzIluA/20zLzxeNF7bNMJXXArmGYRt/MIg2io= 284 | istio.io/client-go v1.17.1/go.mod h1:mLTRYYFxHctzUbt8Iclgj+Sueq34+qC2ZEJTn6BxRuE= 285 | k8s.io/api v0.27.2 h1:+H17AJpUMvl+clT+BPnKf0E3ksMAzoBBg7CntpSuADo= 286 | k8s.io/api v0.27.2/go.mod h1:ENmbocXfBT2ADujUXcBhHV55RIT31IIEvkntP6vZKS4= 287 | k8s.io/apimachinery v0.27.2 h1:vBjGaKKieaIreI+oQwELalVG4d8f3YAMNpWLzDXkxeg= 288 | k8s.io/apimachinery v0.27.2/go.mod h1:XNfZ6xklnMCOGGFNqXG7bUrQCoR04dh/E7FprV6pb+E= 289 | k8s.io/client-go v0.27.2 h1:vDLSeuYvCHKeoQRhCXjxXO45nHVv2Ip4Fe0MfioMrhE= 290 | k8s.io/client-go v0.27.2/go.mod h1:tY0gVmUsHrAmjzHX9zs7eCjxcBsf8IiNe7KQ52biTcQ= 291 | k8s.io/klog/v2 v2.90.1 h1:m4bYOKall2MmOiRaR1J+We67Do7vm9KiQVlT96lnHUw= 292 | k8s.io/klog/v2 v2.90.1/go.mod h1:y1WjHnz7Dj687irZUWR/WLkLc5N1YHtjLdmgWjndZn0= 293 | k8s.io/kube-openapi v0.0.0-20230501164219-8b0f38b5fd1f h1:2kWPakN3i/k81b0gvD5C5FJ2kxm1WrQFanWchyKuqGg= 294 | k8s.io/kube-openapi v0.0.0-20230501164219-8b0f38b5fd1f/go.mod h1:byini6yhqGC14c3ebc/QwanvYwhuMWF6yz2F8uwW8eg= 295 | k8s.io/utils v0.0.0-20230209194617-a36077c30491 h1:r0BAOLElQnnFhE/ApUsg3iHdVYYPBjNSSOMowRZxxsY= 296 | k8s.io/utils v0.0.0-20230209194617-a36077c30491/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0= 297 | sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd h1:EDPBXCAspyGV4jQlpZSudPeMmr1bNJefnuqLsRAsHZo= 298 | sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd/go.mod h1:B8JuhiUyNFVKdsE8h686QcCxMaH6HrOAZj4vswFpcB0= 299 | sigs.k8s.io/structured-merge-diff/v4 v4.2.3 h1:PRbqxJClWWYMNV1dhaG4NsibJbArud9kFxnAMREiWFE= 300 | sigs.k8s.io/structured-merge-diff/v4 v4.2.3/go.mod h1:qjx8mGObPmV2aSZepjQjbmb2ihdVs8cGKBraizNC69E= 301 | sigs.k8s.io/yaml v1.3.0 h1:a2VclLzOGrwOHDiV8EfBGhvjHvP46CtW5j6POvhYGGo= 302 | sigs.k8s.io/yaml v1.3.0/go.mod h1:GeOyir5tyXNByN85N/dRIT9es5UQNerPYEKK56eTBm8= 303 | -------------------------------------------------------------------------------- /deploypipeline-master/deploypipeline/deploypipeline.go: -------------------------------------------------------------------------------- 1 | package deploypipeline 2 | 3 | import ( 4 | "context" 5 | "crypto/sha1" 6 | "encoding/hex" 7 | "encoding/json" 8 | "flag" 9 | "fmt" 10 | "io/ioutil" 11 | "net" 12 | "net/url" 13 | "os" 14 | "strconv" 15 | "strings" 16 | "time" 17 | 18 | dns "google.golang.org/api/dns/v2beta1" 19 | "istio.io/api/networking/v1alpha3" 20 | networkingv1alpha3 "istio.io/client-go/pkg/apis/networking/v1alpha3" 21 | versionedclient "istio.io/client-go/pkg/clientset/versioned" 22 | appsv1 "k8s.io/api/apps/v1" 23 | corev1 "k8s.io/api/core/v1" 24 | "k8s.io/apimachinery/pkg/api/resource" 25 | metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 26 | "k8s.io/apimachinery/pkg/labels" 27 | "k8s.io/apimachinery/pkg/util/yaml" 28 | "k8s.io/apimachinery/pkg/watch" 29 | "k8s.io/client-go/kubernetes" 30 | restclient "k8s.io/client-go/rest" 31 | "k8s.io/client-go/tools/clientcmd" 32 | "k8s.io/client-go/util/retry" 33 | ) 34 | 35 | type Status struct { 36 | Message string 37 | } 38 | 39 | type Networking struct { 40 | Port uint `json:"port"` 41 | ExposedPort uint `json:"exposed_port"` 42 | Protocol string `json:"protocol"` 43 | Exposed bool `json:"exposed"` 44 | } 45 | 46 | // Volume Size units are M (megabytes) 47 | type Volume struct { 48 | Name string `json:"name"` 49 | MountPath string `json:"mountPath"` 50 | Size uint `json:"size"` 51 | } 52 | 53 | // MaxCPU units are millicpu 1/1000 of CPU unit 54 | // MaxRAM units are Mi (mebibyte) 55 | // MaxHDD units are in Mi (mebibyte) 56 | // See: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/ 57 | // This is Basic Autoscale on the front end 58 | type VerticalPodScaleLimit struct { 59 | AllocatedCPU uint `json:"allocatedCPU"` 60 | ScaledCPU uint `json:"scaledCPU"` 61 | AllocatedRAM uint `json:"allocatedRAM"` 62 | ScaledRAM uint `json:"scaledRAM"` 63 | AllocatedHDD uint `json:"allocatedHDD"` 64 | } 65 | 66 | // TargetCPU units are millicpu 1/1000 of CPU unit 67 | // TargetRAM units are Mi (mebibyte) 68 | // TargetHDD units are in Mi (mebibyte) 69 | // This is Advanced Autoscale on the front end 70 | type HorizontalPodScaleLimit struct { 71 | Instances uint `json:"instances"` 72 | TargetCPU uint `json:"targetCPU"` 73 | TargetRAM uint `json:"targetRAM"` 74 | TargetHDD uint `json:"targetHDD"` 75 | } 76 | 77 | type CustomerData struct { 78 | AppName string 79 | CustomerName string 80 | CustomerProjectName string 81 | CustomerServiceName string 82 | CustomerOrganizationName string 83 | RegistryUrl string 84 | DockerImageName string 85 | DockerRegistrySecretPath string 86 | ServiceAccountPath string 87 | Namespace string 88 | Networking []Networking 89 | Volumes []Volume 90 | VerticalPodScaleLimit VerticalPodScaleLimit 91 | HorizontalPodScaleLimit HorizontalPodScaleLimit 92 | EnvironmentVariables map[string]string 93 | DeployId string 94 | IstioGatewayIP string 95 | Replicas int32 96 | PublicAccessible bool 97 | } 98 | 99 | func int32Ptr(i int32) *int32 { return &i } 100 | 101 | func GetNamespace(namespace string, clientset *kubernetes.Clientset) (*corev1.Namespace, error) { 102 | return clientset.CoreV1().Namespaces().Get(context.Background(), namespace, metav1.GetOptions{}) 103 | } 104 | 105 | func CreateNamespace(namespace string, clientset *kubernetes.Clientset) (*corev1.Namespace, Status, error) { 106 | status := Status{Message: ""} 107 | 108 | nsName := &corev1.Namespace{ 109 | ObjectMeta: metav1.ObjectMeta{ 110 | Name: namespace, 111 | }, 112 | } 113 | 114 | namespaceRef, err := clientset.CoreV1().Namespaces().Create(context.Background(), nsName, metav1.CreateOptions{}) 115 | if err != nil { 116 | status.Message = "Namespace creation error" 117 | return nil, status, errCreateNamespace() 118 | } 119 | 120 | status.Message = "Namespace creation succeeded" 121 | 122 | return namespaceRef, status, nil 123 | } 124 | 125 | func DeleteNamespace(namespace string, clientset *kubernetes.Clientset) (Status, error) { 126 | status := Status{Message: ""} 127 | 128 | namespaceDeleteErr := clientset.CoreV1().Namespaces().Delete(context.Background(), namespace, metav1.DeleteOptions{}) 129 | if namespaceDeleteErr != nil { 130 | status.Message = "Namespace deletion error" 131 | return status, namespaceDeleteErr 132 | } 133 | 134 | status.Message = "Namespace successfully deleted" 135 | return status, nil 136 | } 137 | 138 | func InitializeSecrets(clientset *kubernetes.Clientset, customer CustomerData) (Status, error) { 139 | status := Status{Message: ""} 140 | 141 | nameBaseLowercase := strings.ToLower(customer.CustomerName + "-" + customer.CustomerProjectName + "-" + customer.AppName) 142 | nameBase := strings.Replace(nameBaseLowercase, " ", "-", -1) 143 | 144 | // create secret for Docker registry auth 145 | createDockerSecretStatus, createDockerSecretErr := CreateDockerRegistrySecret(clientset, customer.Namespace, customer.DockerRegistrySecretPath, customer, nameBase) 146 | 147 | if createDockerSecretErr != nil { 148 | return createDockerSecretStatus, createDockerSecretErr 149 | } 150 | 151 | // create 'build-packs-service' service account 152 | createServiceAccountStatus, createServiceAccountErr := CreateServiceAccount(clientset, customer.Namespace, customer.ServiceAccountPath, customer, nameBase) 153 | if createServiceAccountErr != nil { 154 | return createServiceAccountStatus, createServiceAccountErr 155 | } 156 | 157 | status.Message = "Secrets Initialized Successfully" 158 | 159 | return status, nil 160 | } 161 | 162 | func CreateDeployment(customer CustomerData, clientset *kubernetes.Clientset, istioclientset *versionedclient.Clientset) (Status, <-chan watch.Event, <-chan watch.Event, []string, error) { 163 | status := Status{Message: ""} 164 | namespaceLowercase := strings.ToLower(customer.Namespace) 165 | namespace := strings.Replace(namespaceLowercase, " ", "-", -1) 166 | customerName := customer.CustomerName 167 | customerProjectName := customer.CustomerProjectName 168 | customerServiceName := customer.CustomerServiceName 169 | customerOrgName := customer.CustomerOrganizationName 170 | deployId := customer.DeployId 171 | appName := customer.AppName 172 | registryUrl := customer.RegistryUrl 173 | imageName := customer.DockerImageName 174 | fullImageName := registryUrl + "/" + imageName 175 | nameBaseLowercase := strings.ToLower(customerName + "-" + customerProjectName + "-" + appName) 176 | nameBase := strings.Replace(nameBaseLowercase, " ", "-", -1) 177 | istioGatewayIP := customer.IstioGatewayIP 178 | replicas := customer.Replicas 179 | publicAccessible := customer.PublicAccessible 180 | 181 | reqCPU, parseErr := resource.ParseQuantity(strconv.FormatUint(uint64(customer.VerticalPodScaleLimit.AllocatedCPU), 10) + "m") 182 | reqRAM, parseErr := resource.ParseQuantity(strconv.FormatUint(uint64(customer.VerticalPodScaleLimit.AllocatedRAM), 10) + "Mi") 183 | maxCPU, parseErr := resource.ParseQuantity(strconv.FormatUint(uint64(customer.VerticalPodScaleLimit.ScaledCPU), 10) + "m") 184 | maxRAM, parseErr := resource.ParseQuantity(strconv.FormatUint(uint64(customer.VerticalPodScaleLimit.ScaledRAM), 10) + "Mi") 185 | maxHDD, parseErr := resource.ParseQuantity(strconv.FormatUint(uint64(customer.VerticalPodScaleLimit.AllocatedHDD), 10) + "Mi") 186 | 187 | if parseErr != nil { 188 | status = Status{Message: "error parsing units"} 189 | return status, nil, nil, []string{}, parseErr 190 | } 191 | 192 | var containerPorts []corev1.ContainerPort 193 | var servicePorts []corev1.ServicePort 194 | 195 | addedPorts := make(map[int]int) 196 | 197 | for i, n := range customer.Networking { 198 | validProtocol(n.Protocol) 199 | portPrefix := "tcp-port-" 200 | containerProtocol := "TCP" 201 | if strings.ToLower(n.Protocol) == "http" { 202 | portPrefix = "http-port-" 203 | } 204 | if _, exists := addedPorts[int(n.Port)]; !exists { 205 | servicePorts = append(servicePorts, corev1.ServicePort{ 206 | Name: portPrefix + "service-" + strconv.Itoa(i), 207 | Port: int32(n.Port), 208 | }) 209 | containerPorts = append(containerPorts, corev1.ContainerPort{ 210 | Name: portPrefix + strconv.Itoa(i), 211 | ContainerPort: int32(n.Port), 212 | Protocol: corev1.Protocol(containerProtocol), 213 | }) 214 | addedPorts[int(n.Port)] = 1 215 | } 216 | 217 | // we can add this to ensure there will be url creation/exposed if at least one port is marked as exposed/external 218 | if n.Exposed { 219 | publicAccessible = true 220 | } 221 | } 222 | 223 | var volumes []corev1.Volume 224 | var volumeMounts []corev1.VolumeMount 225 | 226 | for _, cv := range customer.Volumes { 227 | volSize, parseErr := resource.ParseQuantity(strconv.FormatUint(uint64(cv.Size), 10) + "Mi") 228 | 229 | if parseErr != nil { 230 | status = Status{Message: "error parsing units"} 231 | return status, nil, nil, []string{}, parseErr 232 | } 233 | 234 | claimName := "pvc-" + strings.ToLower(strings.Replace(cv.Name, " ", "-", -1)) 235 | pvc := corev1.PersistentVolumeClaim{ 236 | ObjectMeta: metav1.ObjectMeta{ 237 | Name: claimName, 238 | Namespace: namespace, 239 | Labels: map[string]string{ 240 | "domenetwork.io/appName": appName, 241 | "domenetwork.io/customer": customerName, 242 | "domenetwork.io/organization": customerOrgName, 243 | "domenetwork.io/project": customerProjectName, 244 | "domenetwork.io/service": customerServiceName, 245 | }, 246 | }, 247 | 248 | Spec: corev1.PersistentVolumeClaimSpec{ 249 | Resources: corev1.ResourceRequirements{ 250 | Limits: corev1.ResourceList{ 251 | corev1.ResourceStorage: maxHDD, 252 | }, 253 | 254 | Requests: corev1.ResourceList{ 255 | corev1.ResourceStorage: volSize, 256 | }, 257 | }, 258 | 259 | AccessModes: []corev1.PersistentVolumeAccessMode{ 260 | corev1.ReadWriteOnce, 261 | }, 262 | }, 263 | } 264 | 265 | _, pvcErr := clientset.CoreV1().PersistentVolumeClaims(namespace).Create(context.TODO(), &pvc, metav1.CreateOptions{}) 266 | 267 | if pvcErr != nil { 268 | status = Status{Message: "error creating persistent volume claim"} 269 | return status, nil, nil, []string{}, pvcErr 270 | } 271 | 272 | volumes = append(volumes, corev1.Volume{ 273 | Name: strings.ToLower(cv.Name), 274 | VolumeSource: corev1.VolumeSource{ 275 | PersistentVolumeClaim: &corev1.PersistentVolumeClaimVolumeSource{ 276 | ClaimName: claimName, 277 | }, 278 | }, 279 | }) 280 | 281 | volumeMounts = append(volumeMounts, corev1.VolumeMount{ 282 | Name: strings.ToLower(cv.Name), 283 | MountPath: cv.MountPath, 284 | }) 285 | } 286 | 287 | var envVars []corev1.EnvVar 288 | 289 | for k, v := range customer.EnvironmentVariables { 290 | envVars = append(envVars, corev1.EnvVar{ 291 | Name: k, 292 | Value: v, 293 | }) 294 | } 295 | 296 | deploymentsClient := clientset.AppsV1().Deployments(namespace) 297 | 298 | deployment := &appsv1.Deployment{ 299 | ObjectMeta: metav1.ObjectMeta{ 300 | Name: appName, 301 | Namespace: customer.Namespace, 302 | Labels: map[string]string{ 303 | "domenetwork.io/appName": appName, 304 | "domenetwork.io/customer": customerName, 305 | "domenetwork.io/organization": customerOrgName, 306 | "domenetwork.io/project": customerProjectName, 307 | "domenetwork.io/service": customerServiceName, 308 | "domenetwork.io/deployId": deployId, 309 | }, 310 | }, 311 | Spec: appsv1.DeploymentSpec{ 312 | Replicas: int32Ptr(replicas), 313 | Selector: &metav1.LabelSelector{ 314 | MatchLabels: map[string]string{ 315 | "domenetwork.io/appName": appName, 316 | "domenetwork.io/customer": customerName, 317 | "domenetwork.io/organization": customerOrgName, 318 | "domenetwork.io/project": customerProjectName, 319 | "domenetwork.io/service": customerServiceName, 320 | "domenetwork.io/deployId": deployId, 321 | }, 322 | }, 323 | Template: corev1.PodTemplateSpec{ 324 | ObjectMeta: metav1.ObjectMeta{ 325 | Labels: map[string]string{ 326 | "domenetwork.io/appName": appName, 327 | "domenetwork.io/customer": customerName, 328 | "domenetwork.io/organization": customerOrgName, 329 | "domenetwork.io/project": customerProjectName, 330 | "domenetwork.io/service": customerServiceName, 331 | "domenetwork.io/deployId": deployId, 332 | }, 333 | Annotations: map[string]string{ 334 | "deployTime": time.Now().String(), 335 | }, 336 | }, 337 | Spec: corev1.PodSpec{ 338 | Volumes: volumes, 339 | ImagePullSecrets: []corev1.LocalObjectReference{}, 340 | ServiceAccountName: "deploy-service-account-" + nameBase, 341 | Containers: []corev1.Container{ 342 | { 343 | Name: appName, 344 | Image: fullImageName, 345 | ImagePullPolicy: corev1.PullAlways, 346 | Env: envVars, 347 | Resources: corev1.ResourceRequirements{ 348 | Limits: corev1.ResourceList{ 349 | corev1.ResourceCPU: maxCPU, 350 | corev1.ResourceMemory: maxRAM, 351 | }, 352 | 353 | Requests: corev1.ResourceList{ 354 | corev1.ResourceCPU: reqCPU, 355 | corev1.ResourceMemory: reqRAM, 356 | }, 357 | }, 358 | VolumeMounts: volumeMounts, 359 | Ports: containerPorts, 360 | }, 361 | }, 362 | }, 363 | }, 364 | }, 365 | } 366 | 367 | serviceClient := clientset.CoreV1().Services(customer.Namespace) 368 | service := &corev1.Service{ 369 | ObjectMeta: metav1.ObjectMeta{ 370 | Name: appName, 371 | Namespace: customer.Namespace, 372 | Labels: map[string]string{ 373 | "domenetwork.io/appName": appName, 374 | "domenetwork.io/customer": customerName, 375 | "domenetwork.io/organization": customerOrgName, 376 | "domenetwork.io/project": customerProjectName, 377 | "domenetwork.io/service": customerServiceName, 378 | }, 379 | Annotations: map[string]string{ 380 | "deployTime": time.Now().String(), 381 | }, 382 | }, 383 | Spec: corev1.ServiceSpec{ 384 | Ports: servicePorts, 385 | Selector: map[string]string{ 386 | "domenetwork.io/appName": appName, 387 | }, 388 | Type: corev1.ServiceTypeClusterIP, 389 | SessionAffinity: corev1.ServiceAffinityNone, 390 | }, 391 | } 392 | 393 | // Create Deployment 394 | fmt.Println("Creating deployment...") 395 | result, deploymentErr := deploymentsClient.Create(context.TODO(), deployment, metav1.CreateOptions{}) 396 | if deploymentErr != nil { 397 | return status, nil, nil, []string{}, deploymentErr 398 | } 399 | 400 | fmt.Printf("Created deployment %q.\n", result.GetObjectMeta().GetName()) 401 | 402 | // Create Service 403 | fmt.Println("Creating service...") 404 | _, serviceErr := serviceClient.Create(context.TODO(), service, metav1.CreateOptions{}) 405 | if serviceErr != nil { 406 | return status, nil, nil, []string{}, serviceErr 407 | } 408 | 409 | // Expose it 410 | var mainDeployUrl string 411 | if publicAccessible { 412 | // deployment needs to be exposed 413 | createStatus, serviceUrl, err := CreateDeployURL(namespace, customerOrgName, customerProjectName, customerServiceName, istioGatewayIP) 414 | if err != nil { 415 | return createStatus, nil, nil, []string{}, err 416 | } 417 | mainDeployUrl = serviceUrl 418 | 419 | exposeStatus, _, err := ExposeDeployment(namespace, appName, "", customerName, customerOrgName, customerProjectName, customerServiceName, serviceUrl, istioGatewayIP, customer.Networking, istioclientset) 420 | 421 | if err != nil { 422 | return exposeStatus, nil, nil, []string{}, err 423 | } 424 | 425 | } 426 | var deployUrls []string 427 | 428 | for _, n := range customer.Networking { 429 | if n.Exposed { 430 | if n.Protocol == "http" { 431 | deployUrls = append(deployUrls, "https://"+mainDeployUrl+":"+strconv.Itoa(int(n.ExposedPort))) 432 | } else { 433 | deployUrls = append(deployUrls, mainDeployUrl+":"+strconv.Itoa(int(n.ExposedPort))) 434 | } 435 | 436 | } 437 | } 438 | 439 | watchTimeout := int64(300) 440 | watchLabel := map[string]string{ 441 | "domenetwork.io/appName": appName, 442 | } 443 | 444 | deploymentWatchInterface, deploymentWatchChannelErr := deploymentsClient.Watch(context.TODO(), metav1.ListOptions{ 445 | LabelSelector: labels.Set(watchLabel).String(), 446 | TimeoutSeconds: &watchTimeout, 447 | }) 448 | 449 | serviceWatchInterface, serviceWatchChannelErr := serviceClient.Watch(context.TODO(), metav1.ListOptions{ 450 | LabelSelector: labels.Set(watchLabel).String(), 451 | TimeoutSeconds: &watchTimeout, 452 | }) 453 | 454 | if deploymentWatchChannelErr != nil { 455 | return status, nil, nil, []string{}, deploymentWatchChannelErr 456 | } 457 | 458 | if serviceWatchChannelErr != nil { 459 | return status, nil, nil, []string{}, serviceWatchChannelErr 460 | } 461 | 462 | deploymentWatchChannel := deploymentWatchInterface.ResultChan() 463 | serviceWatchChannel := serviceWatchInterface.ResultChan() 464 | 465 | status.Message = "Deployment and virtual service creation succeeded." 466 | return status, deploymentWatchChannel, serviceWatchChannel, deployUrls, nil 467 | } 468 | 469 | func DeleteDeployment(customer CustomerData, clientset *kubernetes.Clientset, istioclientset *versionedclient.Clientset) (Status, <-chan watch.Event, <-chan watch.Event, error) { 470 | status := Status{Message: ""} 471 | 472 | deploymentsClient := clientset.AppsV1().Deployments(customer.Namespace) 473 | serviceClient := clientset.CoreV1().Services(customer.Namespace) 474 | virtualServiceClient := istioclientset.NetworkingV1alpha3().VirtualServices(customer.Namespace) 475 | deletePolicy := metav1.DeletePropagationForeground 476 | 477 | fmt.Println("Deleting deployment...") 478 | if deleteDeploymentErr := deploymentsClient.Delete(context.TODO(), customer.AppName, metav1.DeleteOptions{ 479 | PropagationPolicy: &deletePolicy, 480 | }); deleteDeploymentErr != nil { 481 | return status, nil, nil, deleteDeploymentErr 482 | } 483 | 484 | fmt.Println("Deleting internal service...") 485 | if deleteServiceErr := serviceClient.Delete(context.TODO(), customer.AppName, metav1.DeleteOptions{ 486 | PropagationPolicy: &deletePolicy, 487 | }); deleteServiceErr != nil { 488 | return status, nil, nil, deleteServiceErr 489 | } 490 | 491 | fmt.Println("Deleting istio virtual service...") 492 | if deleteVirtualServiceErr := virtualServiceClient.Delete(context.TODO(), customer.AppName+"-virtualservice", metav1.DeleteOptions{ 493 | PropagationPolicy: &deletePolicy, 494 | }); deleteVirtualServiceErr != nil { 495 | return status, nil, nil, deleteVirtualServiceErr 496 | } 497 | 498 | watchTimeout := int64(300) 499 | watchLabel := map[string]string{ 500 | "domenetwork.io/appName": customer.AppName, 501 | } 502 | 503 | deploymentWatchInterface, deploymentWatchChannelErr := deploymentsClient.Watch(context.TODO(), metav1.ListOptions{ 504 | LabelSelector: labels.Set(watchLabel).String(), 505 | TimeoutSeconds: &watchTimeout, 506 | }) 507 | 508 | serviceWatchInterface, serviceWatchChannelErr := serviceClient.Watch(context.TODO(), metav1.ListOptions{ 509 | LabelSelector: labels.Set(watchLabel).String(), 510 | TimeoutSeconds: &watchTimeout, 511 | }) 512 | 513 | if deploymentWatchChannelErr != nil { 514 | return status, nil, nil, deploymentWatchChannelErr 515 | } 516 | 517 | if serviceWatchChannelErr != nil { 518 | return status, nil, nil, serviceWatchChannelErr 519 | } 520 | 521 | teardownDeploymentWatchChannel := deploymentWatchInterface.ResultChan() 522 | teardownServiceWatchChannel := serviceWatchInterface.ResultChan() 523 | 524 | status.Message = "Deployment and external service deletion succeeded." 525 | return status, teardownDeploymentWatchChannel, teardownServiceWatchChannel, nil 526 | } 527 | 528 | func RedeployDeployment(customer CustomerData, clientset *kubernetes.Clientset) (Status, <-chan watch.Event, error) { 529 | status := Status{Message: ""} 530 | 531 | deploymentsClient := clientset.AppsV1().Deployments(customer.Namespace) 532 | 533 | retryErr := retry.RetryOnConflict(retry.DefaultRetry, func() error { 534 | // Retrieve the latest version of Deployment before attempting update 535 | // RetryOnConflict uses exponential backoff to avoid exhausting the apiserver 536 | result, getErr := deploymentsClient.Get(context.TODO(), customer.AppName, metav1.GetOptions{}) 537 | if getErr != nil { 538 | panic(fmt.Errorf("Failed to get latest version of Deployment: %v", getErr)) 539 | } 540 | 541 | result.Spec.Template.ObjectMeta.SetAnnotations(map[string]string{ 542 | "deployTime": time.Now().String(), 543 | }) 544 | 545 | _, updateErr := deploymentsClient.Update(context.TODO(), result, metav1.UpdateOptions{}) 546 | return updateErr 547 | }) 548 | 549 | if retryErr != nil { 550 | status.Message = "Redeploy failed" 551 | return status, nil, retryErr 552 | } 553 | 554 | watchTimeout := int64(300) 555 | watchLabel := map[string]string{ 556 | "domenetwork.io/appName": customer.AppName, 557 | } 558 | 559 | deploymentWatchInterface, deploymentWatchChannelErr := deploymentsClient.Watch(context.TODO(), metav1.ListOptions{ 560 | LabelSelector: labels.Set(watchLabel).String(), 561 | TimeoutSeconds: &watchTimeout, 562 | }) 563 | 564 | if deploymentWatchChannelErr != nil { 565 | return status, nil, deploymentWatchChannelErr 566 | } 567 | 568 | deploymentWatchChannel := deploymentWatchInterface.ResultChan() 569 | 570 | status.Message = "Successfully redeployed deployment" 571 | return status, deploymentWatchChannel, nil 572 | } 573 | 574 | func UpdateDeployment(customer CustomerData, clientset *kubernetes.Clientset, istioclientset *versionedclient.Clientset) (Status, <-chan watch.Event, <-chan watch.Event, error) { 575 | status := Status{Message: ""} 576 | 577 | deploymentsClient := clientset.AppsV1().Deployments(customer.Namespace) 578 | serviceClient := clientset.CoreV1().Services(customer.Namespace) 579 | virtualServiceClient := istioclientset.NetworkingV1alpha3().VirtualServices(customer.Namespace) 580 | 581 | retryDeploymentErr := retry.RetryOnConflict(retry.DefaultRetry, func() error { 582 | // Retrieve the latest version of Deployment before attempting update 583 | // RetryOnConflict uses exponential backoff to avoid exhausting the apiserver 584 | currentDeployment, getDeploymentErr := deploymentsClient.Get(context.TODO(), customer.AppName, metav1.GetOptions{}) 585 | 586 | if getDeploymentErr != nil { 587 | status = Status{Message: "failed to get latest version of Deployment - update deployment"} 588 | return getDeploymentErr 589 | } 590 | 591 | var envVars []corev1.EnvVar 592 | for k, v := range customer.EnvironmentVariables { 593 | envVars = append(envVars, corev1.EnvVar{ 594 | Name: k, 595 | Value: v, 596 | }) 597 | } 598 | 599 | var containerPorts []corev1.ContainerPort 600 | addedPorts := make(map[int]int) 601 | 602 | for i, n := range customer.Networking { 603 | validProtocol(n.Protocol) 604 | containerProtocol := "TCP" 605 | if _, exists := addedPorts[int(n.Port)]; !exists { 606 | containerPorts = append(containerPorts, corev1.ContainerPort{ 607 | Name: "tcp-port-" + strconv.Itoa(i), 608 | ContainerPort: int32(n.Port), 609 | Protocol: corev1.Protocol(containerProtocol), 610 | }) 611 | addedPorts[int(n.Port)] = 1 612 | } 613 | } 614 | 615 | var volumes []corev1.Volume 616 | var volumeMounts []corev1.VolumeMount 617 | 618 | currentPVCs, err := clientset.CoreV1().PersistentVolumeClaims(customer.Namespace).List(context.TODO(), metav1.ListOptions{}) 619 | 620 | if err != nil { 621 | status = Status{Message: "error fetching volumes"} 622 | return err 623 | } 624 | 625 | for index, cv := range customer.Volumes { 626 | claimName := "pvc-" + strings.ToLower(strings.Replace(cv.Name, " ", "-", -1)) 627 | volSize, _ := resource.ParseQuantity(strconv.FormatUint(uint64(cv.Size), 10) + "Mi") 628 | maxHDD, _ := resource.ParseQuantity(strconv.FormatUint(uint64(customer.VerticalPodScaleLimit.AllocatedHDD), 10) + "Mi") 629 | storageSize := resource.NewQuantity(int64(cv.Size*1024*1024), resource.BinarySI) 630 | currentPVCSize := len(currentPVCs.Items) 631 | 632 | // if the volume already exist then compare their current sizes to make sure it has not been updated ? 633 | if currentPVCSize > 0 && index < currentPVCSize { 634 | val := currentPVCs.Items[index].Spec.Resources.Requests.Storage().ToDec() 635 | // If the size has been upgraded, update the claim and append to the list of volumes 636 | if claimName == currentPVCs.Items[index].Name && storageSize.Cmp(resource.MustParse(val.String())) == 1 { 637 | pvc, err := clientset.CoreV1().PersistentVolumeClaims(customer.Namespace).Get(context.TODO(), claimName, metav1.GetOptions{}) 638 | 639 | if err != nil { 640 | status = Status{Message: "failed to fetch the existing persistent volume claim"} 641 | return err 642 | } 643 | 644 | pvc.Spec.Resources.Requests = corev1.ResourceList{ 645 | corev1.ResourceStorage: volSize, 646 | } 647 | 648 | _, pvcErr := clientset.CoreV1().PersistentVolumeClaims(customer.Namespace).Update(context.TODO(), pvc, metav1.UpdateOptions{}) 649 | 650 | if pvcErr != nil { 651 | status = Status{Message: "error updating persistent volume claim"} 652 | return pvcErr 653 | } 654 | 655 | volumes = append(volumes, corev1.Volume{ 656 | Name: strings.ToLower(cv.Name), 657 | VolumeSource: corev1.VolumeSource{ 658 | PersistentVolumeClaim: &corev1.PersistentVolumeClaimVolumeSource{ 659 | ClaimName: claimName, 660 | }, 661 | }, 662 | }) 663 | 664 | volumeMounts = append(volumeMounts, corev1.VolumeMount{ 665 | Name: strings.ToLower(cv.Name), 666 | MountPath: cv.MountPath, 667 | }) 668 | } else { 669 | // Just append to the volume without updating 670 | volumes = append(volumes, corev1.Volume{ 671 | Name: strings.ToLower(cv.Name), 672 | VolumeSource: corev1.VolumeSource{ 673 | PersistentVolumeClaim: &corev1.PersistentVolumeClaimVolumeSource{ 674 | ClaimName: claimName, 675 | }, 676 | }, 677 | }) 678 | 679 | volumeMounts = append(volumeMounts, corev1.VolumeMount{ 680 | Name: strings.ToLower(cv.Name), 681 | MountPath: cv.MountPath, 682 | }) 683 | } 684 | } else { 685 | // if its a new claim then create it 686 | pvc := corev1.PersistentVolumeClaim{ 687 | ObjectMeta: metav1.ObjectMeta{ 688 | Name: claimName, 689 | Namespace: customer.Namespace, 690 | Labels: map[string]string{ 691 | "domenetwork.io/appName": customer.AppName, 692 | "domenetwork.io/customer": customer.CustomerName, 693 | "domenetwork.io/organization": customer.CustomerOrganizationName, 694 | "domenetwork.io/project": customer.CustomerProjectName, 695 | "domenetwork.io/service": customer.CustomerServiceName, 696 | }, 697 | }, 698 | 699 | Spec: corev1.PersistentVolumeClaimSpec{ 700 | Resources: corev1.ResourceRequirements{ 701 | Limits: corev1.ResourceList{ 702 | corev1.ResourceStorage: maxHDD, 703 | }, 704 | 705 | Requests: corev1.ResourceList{ 706 | corev1.ResourceStorage: volSize, 707 | }, 708 | }, 709 | 710 | AccessModes: []corev1.PersistentVolumeAccessMode{ 711 | corev1.ReadWriteOnce, 712 | }, 713 | }, 714 | } 715 | 716 | _, pvcErr := clientset.CoreV1().PersistentVolumeClaims(customer.Namespace).Create(context.TODO(), &pvc, metav1.CreateOptions{}) 717 | 718 | if pvcErr != nil { 719 | status = Status{Message: "error creating persistent volume claim"} 720 | return pvcErr 721 | } 722 | 723 | volumes = append(volumes, corev1.Volume{ 724 | Name: strings.ToLower(cv.Name), 725 | VolumeSource: corev1.VolumeSource{ 726 | PersistentVolumeClaim: &corev1.PersistentVolumeClaimVolumeSource{ 727 | ClaimName: claimName, 728 | }, 729 | }, 730 | }) 731 | 732 | volumeMounts = append(volumeMounts, corev1.VolumeMount{ 733 | Name: strings.ToLower(cv.Name), 734 | MountPath: cv.MountPath, 735 | }) 736 | } 737 | } 738 | 739 | podSpec := currentDeployment.Spec.Template.Spec 740 | podSpec.Volumes = volumes 741 | 742 | // set the updated podSpec back on the current deployment 743 | currentDeployment.Spec.Template.Spec = podSpec 744 | 745 | containers := currentDeployment.Spec.Template.Spec.Containers 746 | for i := range containers { 747 | currentDeployment.Spec.Template.Spec.Containers[i].Env = envVars 748 | currentDeployment.Spec.Template.Spec.Containers[i].Ports = containerPorts 749 | currentDeployment.Spec.Template.Spec.Containers[i].VolumeMounts = volumeMounts 750 | } 751 | 752 | currentDeployment.Spec.Template.ObjectMeta.SetAnnotations(map[string]string{ 753 | "deployTime": time.Now().String(), 754 | }) 755 | 756 | _, deploymentUpdateErr := deploymentsClient.Update(context.TODO(), currentDeployment, metav1.UpdateOptions{}) 757 | 758 | return deploymentUpdateErr 759 | }) 760 | 761 | if retryDeploymentErr != nil { 762 | status.Message = "Update Deployment failed" 763 | return status, nil, nil, retryDeploymentErr 764 | } 765 | 766 | retryServiceErr := retry.RetryOnConflict(retry.DefaultRetry, func() error { 767 | // Retrieve the latest version of Service before attempting update 768 | // RetryOnConflict uses exponential backoff to avoid exhausting the apiserver 769 | currentService, getServiceErr := serviceClient.Get(context.TODO(), customer.AppName, metav1.GetOptions{}) 770 | 771 | if getServiceErr != nil { 772 | status = Status{Message: "failed to get the latest version of the service - update deployment"} 773 | return getServiceErr 774 | } 775 | 776 | var servicePorts []corev1.ServicePort 777 | addedPorts := make(map[int]int) 778 | 779 | for i, n := range customer.Networking { 780 | if _, exists := addedPorts[int(n.Port)]; !exists { 781 | portPrefix := "tcp-port-" 782 | if strings.ToLower(n.Protocol) == "http" { 783 | portPrefix = "http-port-" 784 | } 785 | servicePorts = append(servicePorts, corev1.ServicePort{ 786 | Name: portPrefix + "service-" + strconv.Itoa(i), 787 | Port: int32(n.Port), 788 | }) 789 | addedPorts[int(n.Port)] = 1 790 | } 791 | } 792 | 793 | currentService.Spec.Ports = servicePorts 794 | 795 | currentService.ObjectMeta.SetAnnotations(map[string]string{ 796 | "deployTime": time.Now().String(), 797 | }) 798 | 799 | _, serviceUpdateErr := serviceClient.Update(context.TODO(), currentService, metav1.UpdateOptions{}) 800 | return serviceUpdateErr 801 | }) 802 | 803 | if retryServiceErr != nil { 804 | status.Message = "Update Service failed" 805 | return status, nil, nil, retryServiceErr 806 | } 807 | 808 | currentVirtualService, getvirtualServiceErr := virtualServiceClient.Get(context.TODO(), customer.AppName+"-virtualservice", metav1.GetOptions{}) 809 | 810 | if getvirtualServiceErr != nil { 811 | status = Status{Message: "failed to get the latest virtual version of the service - update deployment"} 812 | return status, nil, nil, getvirtualServiceErr 813 | } 814 | 815 | for _, n := range customer.Networking { 816 | if n.Exposed { 817 | if strings.ToLower(n.Protocol) == "tcp" { 818 | /* if internalHostName != "" { 819 | destinationHost = internalHostName 820 | } */ 821 | currentVirtualService.Spec.Tcp = append(currentVirtualService.Spec.Tcp, &v1alpha3.TCPRoute{ 822 | Match: []*v1alpha3.L4MatchAttributes{ 823 | { 824 | Port: uint32(n.ExposedPort), 825 | }, 826 | }, 827 | Route: []*v1alpha3.RouteDestination{ 828 | { 829 | Destination: &v1alpha3.Destination{ 830 | Host: customer.AppName, 831 | Port: &v1alpha3.PortSelector{ 832 | Number: uint32(n.Port), 833 | }, 834 | }, 835 | }, 836 | }, 837 | }) 838 | } else { 839 | currentVirtualService.Spec.Http = append(currentVirtualService.Spec.Http, &v1alpha3.HTTPRoute{ 840 | Match: []*v1alpha3.HTTPMatchRequest{ 841 | { 842 | Port: uint32(n.ExposedPort), 843 | Uri: &v1alpha3.StringMatch{ 844 | MatchType: &v1alpha3.StringMatch_Prefix{ 845 | Prefix: "/", 846 | }, 847 | }, 848 | }, 849 | }, 850 | Rewrite: &v1alpha3.HTTPRewrite{ 851 | Uri: "/", 852 | }, 853 | Route: []*v1alpha3.HTTPRouteDestination{ 854 | { 855 | Destination: &v1alpha3.Destination{ 856 | Host: customer.AppName, 857 | Port: &v1alpha3.PortSelector{ 858 | Number: uint32(n.Port), 859 | }, 860 | }, 861 | }, 862 | }, 863 | Headers: &v1alpha3.Headers{ 864 | Response: &v1alpha3.Headers_HeaderOperations{ 865 | Set: map[string]string{"Strict-Transport-Security": "max-age=31536000"}, 866 | Remove: []string{ 867 | "x-envoy-upstream-healthchecked-cluster", 868 | "x-envoy-upstream-service-time", 869 | "Server", 870 | "Server", 871 | }, 872 | }, 873 | }, 874 | CorsPolicy: &v1alpha3.CorsPolicy{ 875 | AllowOrigins: []*v1alpha3.StringMatch{ 876 | { 877 | MatchType: &v1alpha3.StringMatch_Regex{ 878 | Regex: ".*", 879 | }, 880 | }, 881 | }, 882 | AllowMethods: []string{ 883 | "GET", 884 | "POST", 885 | "PUT", 886 | "DELETE", 887 | "PATCH", 888 | "HEAD", 889 | "OPTIONS", 890 | }, 891 | AllowHeaders: []string{ 892 | "*", 893 | }, 894 | }, 895 | }) 896 | } 897 | } 898 | } 899 | 900 | _, vsErr := virtualServiceClient.Update(context.TODO(), currentVirtualService, metav1.UpdateOptions{}) 901 | if vsErr != nil { 902 | status = Status{Message: "error creating an istio virtual service client"} 903 | return status, nil, nil, vsErr 904 | } 905 | 906 | watchTimeout := int64(300) 907 | watchLabel := map[string]string{ 908 | "domenetwork.io/appName": customer.AppName, 909 | } 910 | 911 | deploymentWatchInterface, deploymentWatchChannelErr := deploymentsClient.Watch(context.TODO(), metav1.ListOptions{ 912 | LabelSelector: labels.Set(watchLabel).String(), 913 | TimeoutSeconds: &watchTimeout, 914 | }) 915 | 916 | serviceWatchInterface, serviceWatchChannelErr := serviceClient.Watch(context.TODO(), metav1.ListOptions{ 917 | LabelSelector: labels.Set(watchLabel).String(), 918 | TimeoutSeconds: &watchTimeout, 919 | }) 920 | 921 | if deploymentWatchChannelErr != nil { 922 | return status, nil, nil, deploymentWatchChannelErr 923 | } 924 | 925 | if serviceWatchChannelErr != nil { 926 | return status, nil, nil, serviceWatchChannelErr 927 | } 928 | 929 | deploymentWatchChannel := deploymentWatchInterface.ResultChan() 930 | serviceWatchChannel := serviceWatchInterface.ResultChan() 931 | 932 | status.Message = "Successfully redeployed deployment" 933 | return status, deploymentWatchChannel, serviceWatchChannel, nil 934 | } 935 | 936 | func GetDeployment(namespace string, appName string, clientset *kubernetes.Clientset) (Status, *appsv1.Deployment, error) { 937 | status := Status{Message: ""} 938 | 939 | deploymentsClient := clientset.AppsV1().Deployments(namespace) 940 | result, getErr := deploymentsClient.Get(context.TODO(), appName, metav1.GetOptions{}) 941 | 942 | if getErr != nil { 943 | status.Message = "Get Deployment failed" 944 | return status, nil, getErr 945 | } 946 | 947 | status.Message = "Get Deployment successful" 948 | return status, result, getErr 949 | } 950 | 951 | func ScaleDeployment(customer CustomerData, clientset *kubernetes.Clientset, replicaSets int32) (Status, error) { 952 | status := Status{Message: ""} 953 | 954 | deploymentsClient := clientset.AppsV1().Deployments(customer.Namespace) 955 | 956 | retryErr := retry.RetryOnConflict(retry.DefaultRetry, func() error { 957 | // Retrieve the latest version of Deployment before attempting update 958 | // RetryOnConflict uses exponential backoff to avoid exhausting the apiserver 959 | result, getErr := deploymentsClient.Get(context.TODO(), customer.AppName, metav1.GetOptions{}) 960 | if getErr != nil { 961 | panic(fmt.Errorf("Failed to get latest version of Deployment: %v", getErr)) 962 | } 963 | 964 | result.Spec.Replicas = int32Ptr(replicaSets) // modify replica count 965 | _, updateErr := deploymentsClient.Update(context.TODO(), result, metav1.UpdateOptions{}) 966 | return updateErr 967 | }) 968 | 969 | if retryErr != nil { 970 | status.Message = "Update failed" 971 | return status, retryErr 972 | } 973 | 974 | status.Message = "Successfully scaled deployment" 975 | return status, nil 976 | } 977 | 978 | func CreateDockerRegistrySecret(clientset *kubernetes.Clientset, namespace string, dockerRegistrySecretPath string, customer CustomerData, nameBase string) (Status, error) { 979 | status := Status{Message: ""} 980 | 981 | // load the file into a buffer 982 | secretData, err := ioutil.ReadFile(dockerRegistrySecretPath) 983 | if err != nil { 984 | status.Message = "Create Docker registry secret error: read file" 985 | return status, err 986 | } 987 | 988 | // convert yaml to JSON 989 | secretJSON, yamlToJSONErr := yaml.ToJSON(secretData) 990 | if yamlToJSONErr != nil { 991 | status.Message = "Create Docker registry secret error: convert yaml to JSON" 992 | return status, yamlToJSONErr 993 | } 994 | 995 | var secret *corev1.Secret 996 | 997 | // convert JSON to Secret type 998 | jsonUnmarshalErr := json.Unmarshal(secretJSON, &secret) 999 | if jsonUnmarshalErr != nil { 1000 | status.Message = "Create Docker registry secret error: unmarshal" 1001 | return status, jsonUnmarshalErr 1002 | } 1003 | 1004 | secret.Namespace = namespace 1005 | secret.Name = "docker-user-pass-" + nameBase 1006 | 1007 | // set labels 1008 | secret.Labels["domenetwork.io/organization"] = formatLabel(customer.CustomerOrganizationName) 1009 | secret.Labels["domenetwork.io/project"] = formatLabel(customer.CustomerProjectName) 1010 | secret.Labels["domenetwork.io/service"] = formatLabel(customer.CustomerServiceName) 1011 | secret.Labels["domenetwork.io/appName"] = formatLabel(customer.AppName) 1012 | 1013 | // create secret on cluster 1014 | _, createDockerSecret := clientset.CoreV1().Secrets(namespace).Create(context.TODO(), secret, metav1.CreateOptions{}) 1015 | if createDockerSecret != nil { 1016 | status.Message = "Create Docker registry secret error" 1017 | return status, createDockerSecret 1018 | } 1019 | 1020 | status.Message = "Create Docker registry secret succeeded" 1021 | 1022 | return status, nil 1023 | } 1024 | 1025 | func DeleteDockerRegistrySecret(clientset *kubernetes.Clientset, namespace string, nameBase string) (Status, error) { 1026 | status := Status{Message: ""} 1027 | 1028 | secretName := "docker-user-pass-" + nameBase 1029 | 1030 | // delete secret on cluster 1031 | deleteDockerSecret := clientset.CoreV1().Secrets(namespace).Delete(context.TODO(), secretName, metav1.DeleteOptions{}) 1032 | if deleteDockerSecret != nil { 1033 | status.Message = "Docker registry secret deletion error" 1034 | return status, deleteDockerSecret 1035 | } 1036 | 1037 | status.Message = "Docker registry secret succeesfully deleted" 1038 | 1039 | return status, nil 1040 | } 1041 | 1042 | func CreateServiceAccount(clientset *kubernetes.Clientset, namespace string, serviceAccountPath string, customer CustomerData, nameBase string) (Status, error) { 1043 | serviceAccountData, readYamlError := readYamlFile(serviceAccountPath) 1044 | status := Status{Message: ""} 1045 | 1046 | if readYamlError != nil { 1047 | status.Message = "Create service account error: read yaml error" 1048 | return status, readYamlError 1049 | } 1050 | 1051 | // convert yaml to JSON 1052 | serviceAccountJSON, yamlToJSONErr := yaml.ToJSON(serviceAccountData) 1053 | if yamlToJSONErr != nil { 1054 | status.Message = "Create service account error: yaml to JSON error" 1055 | return status, yamlToJSONErr 1056 | } 1057 | 1058 | var serviceAccount *corev1.ServiceAccount 1059 | 1060 | // convert JSON to Service Account type 1061 | jsonUnmarshalErr := json.Unmarshal(serviceAccountJSON, &serviceAccount) 1062 | if jsonUnmarshalErr != nil { 1063 | status.Message = "Create service account error: unmarshal JSON error" 1064 | return status, jsonUnmarshalErr 1065 | } 1066 | 1067 | serviceAccount.Name = "deploy-service-account-" + nameBase 1068 | 1069 | dockerSecret := corev1.LocalObjectReference{ 1070 | Name: "docker-user-pass-" + nameBase, 1071 | } 1072 | 1073 | // add secrets to service account 1074 | // serviceAccount.Secrets = append(serviceAccount.Secrets, dockerSecret) 1075 | serviceAccount.ImagePullSecrets = append(serviceAccount.ImagePullSecrets, dockerSecret) 1076 | 1077 | // set labels 1078 | serviceAccount.Labels["domenetwork.io/organization"] = formatLabel(customer.CustomerOrganizationName) 1079 | serviceAccount.Labels["domenetwork.io/project"] = formatLabel(customer.CustomerProjectName) 1080 | serviceAccount.Labels["domenetwork.io/service"] = formatLabel(customer.CustomerServiceName) 1081 | serviceAccount.Labels["domenetwork.io/appName"] = formatLabel(customer.AppName) 1082 | 1083 | // create service account on cluster 1084 | _, createServiceAccountErr := clientset.CoreV1().ServiceAccounts(namespace).Create(context.TODO(), serviceAccount, metav1.CreateOptions{}) 1085 | if createServiceAccountErr != nil { 1086 | status.Message = "Create service account error" 1087 | return status, createServiceAccountErr 1088 | } 1089 | 1090 | status.Message = "Create service account succeeded" 1091 | 1092 | return status, nil 1093 | } 1094 | 1095 | func DeleteServiceAccount(clientset *kubernetes.Clientset, namespace string, nameBase string) (Status, error) { 1096 | status := Status{Message: ""} 1097 | 1098 | serviceAccountName := "deploy-service-account-" + nameBase 1099 | 1100 | // delete service account on cluster 1101 | deleteServiceAccountErr := clientset.CoreV1().ServiceAccounts(namespace).Delete(context.TODO(), serviceAccountName, metav1.DeleteOptions{}) 1102 | if deleteServiceAccountErr != nil { 1103 | status.Message = "Service account deletion error" 1104 | return status, deleteServiceAccountErr 1105 | } 1106 | 1107 | status.Message = "Service account deletion succeeded" 1108 | 1109 | return status, nil 1110 | } 1111 | 1112 | func CreateDeployURL(namespace, orgName, projectName, serviceName, istioGatewayIP string) (Status, string, error) { 1113 | status := Status{Message: ""} 1114 | // create a hashed dome.tools domain url 1115 | hashing := sha1.New() 1116 | hashing.Write([]byte(orgName + namespace + projectName + serviceName)) 1117 | urlHash := hex.EncodeToString(hashing.Sum(nil)) 1118 | hostedZone := "dome-tools" 1119 | serviceUrl := urlHash + ".dome.tools." 1120 | dnsRecordType := "A" 1121 | 1122 | // create a dns service to manipulate the google cloud dns records 1123 | ctx := context.Background() 1124 | dnsService, err := dns.NewService(ctx) 1125 | fmt.Printf("Trying to create domain %s\n", serviceUrl) 1126 | if err != nil { 1127 | status = Status{Message: "error creating a dns service"} 1128 | return status, "", err 1129 | } 1130 | if net.ParseIP(istioGatewayIP) == nil { 1131 | if _, err := url.Parse(istioGatewayIP); err != nil { 1132 | status = Status{Message: "error parsing the istio gateway load balancer endpoint"} 1133 | return status, "", err 1134 | } 1135 | dnsRecordType = "CNAME" 1136 | istioGatewayIP = istioGatewayIP + "." 1137 | } 1138 | 1139 | rec := &dns.ResourceRecordSet{ 1140 | Name: serviceUrl, 1141 | Rrdatas: []string{istioGatewayIP}, 1142 | Ttl: int64(60), 1143 | Type: dnsRecordType, 1144 | } 1145 | 1146 | change := &dns.Change{ 1147 | Additions: []*dns.ResourceRecordSet{rec}, 1148 | } 1149 | 1150 | // look for existing records 1151 | list, err := dnsService.ResourceRecordSets.List("dome-engineering", hostedZone).Name(serviceUrl).Do() 1152 | if err != nil { 1153 | status = Status{Message: "error looking up existing records in the dome.tools zone"} 1154 | return status, "", err 1155 | } 1156 | if len(list.Rrsets) > 0 { 1157 | // Attempt to delete the existing records when adding our new one. 1158 | change.Deletions = list.Rrsets 1159 | } 1160 | // create the dns record 1161 | _, err = dnsService.Changes.Create("dome-engineering", hostedZone, change).Do() 1162 | if err != nil { 1163 | status = Status{Message: "error creating a change for a new record in the dome.tools zone"} 1164 | return status, "", err 1165 | } 1166 | status.Message = "Deploy URL dns records succesfully created." 1167 | return status, strings.TrimSuffix(serviceUrl, "."), nil 1168 | } 1169 | 1170 | func ExposeDeployment(namespace, appname, internalHostName, customerName, orgName, projectName, serviceName, serviceURL, istioGatewayIP string, customerNetworking []Networking, istioclientset *versionedclient.Clientset) (Status, <-chan watch.Event, error) { 1171 | status := Status{Message: ""} 1172 | destinationHost := appname 1173 | hosts := []string{serviceURL} 1174 | gateways := []string{"istio-system/main-gateway"} 1175 | counter := 0 1176 | vsSpec := &v1alpha3.VirtualService{ 1177 | Hosts: hosts, 1178 | Gateways: gateways, 1179 | } 1180 | 1181 | for _, n := range customerNetworking { 1182 | if n.Exposed { 1183 | if strings.ToLower(n.Protocol) == "tcp" { 1184 | if internalHostName != "" { 1185 | destinationHost = internalHostName 1186 | } 1187 | vsSpec.Tcp = append(vsSpec.Tcp, &v1alpha3.TCPRoute{ 1188 | Match: []*v1alpha3.L4MatchAttributes{ 1189 | { 1190 | Port: uint32(n.ExposedPort), 1191 | }, 1192 | }, 1193 | Route: []*v1alpha3.RouteDestination{ 1194 | { 1195 | Destination: &v1alpha3.Destination{ 1196 | Host: destinationHost, 1197 | Port: &v1alpha3.PortSelector{ 1198 | Number: uint32(n.Port), 1199 | }, 1200 | }, 1201 | }, 1202 | }, 1203 | }) 1204 | } else { 1205 | vsSpec.Http = append(vsSpec.Http, &v1alpha3.HTTPRoute{ 1206 | Match: []*v1alpha3.HTTPMatchRequest{ 1207 | { 1208 | Port: uint32(n.ExposedPort), 1209 | Uri: &v1alpha3.StringMatch{ 1210 | MatchType: &v1alpha3.StringMatch_Prefix{ 1211 | Prefix: "/", 1212 | }, 1213 | }, 1214 | }, 1215 | }, 1216 | Rewrite: &v1alpha3.HTTPRewrite{ 1217 | Uri: "/", 1218 | }, 1219 | Route: []*v1alpha3.HTTPRouteDestination{ 1220 | { 1221 | Destination: &v1alpha3.Destination{ 1222 | Host: destinationHost, 1223 | Port: &v1alpha3.PortSelector{ 1224 | Number: uint32(n.Port), 1225 | }, 1226 | }, 1227 | }, 1228 | }, 1229 | Headers: &v1alpha3.Headers{ 1230 | Response: &v1alpha3.Headers_HeaderOperations{ 1231 | Set: map[string]string{"Strict-Transport-Security": "max-age=31536000"}, 1232 | Remove: []string{ 1233 | "x-envoy-upstream-healthchecked-cluster", 1234 | "x-envoy-upstream-service-time", 1235 | "Server", 1236 | "Server", 1237 | }, 1238 | }, 1239 | }, 1240 | CorsPolicy: &v1alpha3.CorsPolicy{ 1241 | AllowOrigins: []*v1alpha3.StringMatch{ 1242 | { 1243 | MatchType: &v1alpha3.StringMatch_Regex{ 1244 | Regex: ".*", 1245 | }, 1246 | }, 1247 | }, 1248 | AllowMethods: []string{ 1249 | "GET", 1250 | "POST", 1251 | "PUT", 1252 | "DELETE", 1253 | "PATCH", 1254 | "HEAD", 1255 | "OPTIONS", 1256 | }, 1257 | AllowHeaders: []string{ 1258 | "*", 1259 | }, 1260 | }, 1261 | }) 1262 | } 1263 | counter++ 1264 | } 1265 | } 1266 | if counter == 0 { 1267 | // no ports to expose 1268 | fmt.Printf("No external ports selected - an empty client/dummy channel will be returned to be watched\n") 1269 | } 1270 | 1271 | virtualService := &networkingv1alpha3.VirtualService{ 1272 | ObjectMeta: metav1.ObjectMeta{ 1273 | Name: appname + "-virtualservice", 1274 | Namespace: namespace, 1275 | Labels: map[string]string{ 1276 | "domenetwork.io/appName": appname, 1277 | "domenetwork.io/customer": customerName, 1278 | "domenetwork.io/organization": orgName, 1279 | "domenetwork.io/project": projectName, 1280 | "domenetwork.io/service": serviceName, 1281 | }, 1282 | Annotations: map[string]string{ 1283 | "deployTime": time.Now().String(), 1284 | }, 1285 | }, 1286 | Spec: *vsSpec, 1287 | } 1288 | 1289 | virtualServiceClient := istioclientset.NetworkingV1alpha3().VirtualServices(namespace) 1290 | 1291 | if counter > 0 { 1292 | // Create Istio Virtual Service 1293 | fmt.Println("Creating service...") 1294 | vsResult, vsErr := virtualServiceClient.Create(context.TODO(), virtualService, metav1.CreateOptions{}) 1295 | if vsErr != nil { 1296 | status = Status{Message: "error creating an istio virtual service client"} 1297 | return status, nil, vsErr 1298 | } 1299 | 1300 | fmt.Printf("Created istio virtual service %q.\n", vsResult.GetObjectMeta().GetName()) 1301 | } 1302 | 1303 | watchTimeout := int64(300) 1304 | watchLabel := map[string]string{ 1305 | "domenetwork.io/appName": appname, 1306 | } 1307 | 1308 | virtualServiceWatchInterface, virtualServiceWatchChannelErr := virtualServiceClient.Watch(context.TODO(), metav1.ListOptions{ 1309 | LabelSelector: labels.Set(watchLabel).String(), 1310 | TimeoutSeconds: &watchTimeout, 1311 | }) 1312 | 1313 | if virtualServiceWatchChannelErr != nil { 1314 | return status, nil, virtualServiceWatchChannelErr 1315 | } 1316 | virtualServiceWatchChannel := virtualServiceWatchInterface.ResultChan() 1317 | 1318 | return status, virtualServiceWatchChannel, nil 1319 | 1320 | } 1321 | 1322 | // utility function to remove spaces and lowercase k8 object labels 1323 | func formatLabel(labelInput string) string { 1324 | label := strings.ToLower(strings.Replace(labelInput, " ", "-", -1)) 1325 | return label 1326 | } 1327 | 1328 | func readYamlFile(filepath string) ([]byte, error) { 1329 | // load the file into a buffer 1330 | data, err := ioutil.ReadFile(filepath) 1331 | if err != nil { 1332 | return nil, err 1333 | } 1334 | return data, nil 1335 | } 1336 | 1337 | func validProtocol(protocol string) bool { 1338 | if protocol == string(corev1.ProtocolTCP) || protocol == string(corev1.ProtocolUDP) || protocol == string(corev1.ProtocolSCTP) { 1339 | return true 1340 | } else { 1341 | return protocol == "HTTP" 1342 | } 1343 | } 1344 | 1345 | func Init(kubeConfigPath string) (*kubernetes.Clientset, *restclient.Config, *versionedclient.Clientset, Status, error) { 1346 | // NewFlagSet to avoid flag redefined error 1347 | flag.CommandLine = flag.NewFlagSet(os.Args[0], flag.ExitOnError) 1348 | 1349 | // set Kubernetes config 1350 | var kubeconfig *string 1351 | status := Status{Message: ""} 1352 | 1353 | kubeconfig = flag.String("kubeconfig", kubeConfigPath, "absolute path to the kubeconfig file") 1354 | 1355 | flag.Parse() 1356 | 1357 | config, configErr := clientcmd.BuildConfigFromFlags("", *kubeconfig) 1358 | if configErr != nil { 1359 | status.Message = "Init error occured" 1360 | return nil, nil, nil, status, configErr 1361 | } 1362 | 1363 | // create default Kubernetes clientset 1364 | clientset, clientsetErr := kubernetes.NewForConfig(config) 1365 | if clientsetErr != nil { 1366 | status.Message = "Init error occured for the default k8s cilent" 1367 | return nil, nil, nil, status, errNotExist() 1368 | } 1369 | 1370 | // create the istio clientset 1371 | istioclientset, err := versionedclient.NewForConfig(config) 1372 | if err != nil { 1373 | status.Message = "Init error occured for the istio client" 1374 | return nil, nil, nil, status, errNotExist() 1375 | } 1376 | 1377 | status.Message = "Init succeeded" 1378 | 1379 | return clientset, config, istioclientset, status, nil 1380 | } 1381 | --------------------------------------------------------------------------------