mirror of https://github.com/grafana/grafana.git
				
				
				
			Build: Add command to publish to AWS Marketplace through the pipeline (#59068)
* Remove generic variables from publish github action * Create publish aws cmd to automate aws releases * Add tests to publish aws cmd * Replace fmt with log for prints * Remove unnecessary type assertions * Readd mistakenly removed go package * Replace log with fmt for prints due to conflicts * Update github tests to conform with casing
This commit is contained in:
		
							parent
							
								
									41b3398eb4
								
							
						
					
					
						commit
						414df842b8
					
				
							
								
								
									
										8
									
								
								go.mod
								
								
								
								
							
							
						
						
									
										8
									
								
								go.mod
								
								
								
								
							|  | @ -257,7 +257,6 @@ require ( | ||||||
| 	github.com/grafana/dskit v0.0.0-20211011144203-3a88ec0b675f | 	github.com/grafana/dskit v0.0.0-20211011144203-3a88ec0b675f | ||||||
| 	github.com/jmoiron/sqlx v1.3.5 | 	github.com/jmoiron/sqlx v1.3.5 | ||||||
| 	github.com/matryer/is v1.4.0 | 	github.com/matryer/is v1.4.0 | ||||||
| 	github.com/parca-dev/parca v0.12.1 |  | ||||||
| 	github.com/urfave/cli v1.22.9 | 	github.com/urfave/cli v1.22.9 | ||||||
| 	go.etcd.io/etcd/api/v3 v3.5.4 | 	go.etcd.io/etcd/api/v3 v3.5.4 | ||||||
| 	go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.32.0 | 	go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.32.0 | ||||||
|  | @ -275,8 +274,11 @@ require ( | ||||||
| 	github.com/armon/go-metrics v0.3.10 // indirect | 	github.com/armon/go-metrics v0.3.10 // indirect | ||||||
| 	github.com/bmatcuk/doublestar v1.1.1 // indirect | 	github.com/bmatcuk/doublestar v1.1.1 // indirect | ||||||
| 	github.com/buildkite/yaml v2.1.0+incompatible // indirect | 	github.com/buildkite/yaml v2.1.0+incompatible // indirect | ||||||
|  | 	github.com/containerd/containerd v1.6.8 // indirect | ||||||
| 	github.com/cpuguy83/go-md2man/v2 v2.0.2 // indirect | 	github.com/cpuguy83/go-md2man/v2 v2.0.2 // indirect | ||||||
| 	github.com/davecgh/go-spew v1.1.1 // indirect | 	github.com/davecgh/go-spew v1.1.1 // indirect | ||||||
|  | 	github.com/docker/distribution v2.8.1+incompatible // indirect | ||||||
|  | 	github.com/docker/go-connections v0.4.0 // indirect | ||||||
| 	github.com/drone-runners/drone-runner-docker v1.8.2 // indirect | 	github.com/drone-runners/drone-runner-docker v1.8.2 // indirect | ||||||
| 	github.com/drone/drone-go v1.7.1 // indirect | 	github.com/drone/drone-go v1.7.1 // indirect | ||||||
| 	github.com/drone/envsubst v1.0.3 // indirect | 	github.com/drone/envsubst v1.0.3 // indirect | ||||||
|  | @ -296,6 +298,9 @@ require ( | ||||||
| 	github.com/mitchellh/copystructure v1.2.0 // indirect | 	github.com/mitchellh/copystructure v1.2.0 // indirect | ||||||
| 	github.com/mitchellh/mapstructure v1.4.3 // indirect | 	github.com/mitchellh/mapstructure v1.4.3 // indirect | ||||||
| 	github.com/mitchellh/reflectwalk v1.0.2 // indirect | 	github.com/mitchellh/reflectwalk v1.0.2 // indirect | ||||||
|  | 	github.com/opencontainers/go-digest v1.0.0 // indirect | ||||||
|  | 	github.com/opencontainers/image-spec v1.0.3-0.20211202183452-c5a74bcca799 // indirect | ||||||
|  | 	github.com/parca-dev/parca v0.12.1 // indirect | ||||||
| 	github.com/rivo/uniseg v0.2.0 // indirect | 	github.com/rivo/uniseg v0.2.0 // indirect | ||||||
| 	github.com/russross/blackfriday/v2 v2.1.0 // indirect | 	github.com/russross/blackfriday/v2 v2.1.0 // indirect | ||||||
| 	github.com/segmentio/asm v1.1.4 // indirect | 	github.com/segmentio/asm v1.1.4 // indirect | ||||||
|  | @ -327,6 +332,7 @@ require ( | ||||||
| 	github.com/chromedp/cdproto v0.0.0-20220208224320-6efb837e6bc2 // indirect | 	github.com/chromedp/cdproto v0.0.0-20220208224320-6efb837e6bc2 // indirect | ||||||
| 	github.com/coreos/go-semver v0.3.0 // indirect | 	github.com/coreos/go-semver v0.3.0 // indirect | ||||||
| 	github.com/dgryski/go-metro v0.0.0-20211217172704-adc40b04c140 // indirect | 	github.com/dgryski/go-metro v0.0.0-20211217172704-adc40b04c140 // indirect | ||||||
|  | 	github.com/docker/docker v20.10.21+incompatible | ||||||
| 	github.com/elazarl/goproxy v0.0.0-20220115173737-adb46da277ac // indirect | 	github.com/elazarl/goproxy v0.0.0-20220115173737-adb46da277ac // indirect | ||||||
| 	github.com/emirpasic/gods v1.12.0 // indirect | 	github.com/emirpasic/gods v1.12.0 // indirect | ||||||
| 	github.com/ghodss/yaml v1.0.1-0.20190212211648-25d852aebe32 // indirect | 	github.com/ghodss/yaml v1.0.1-0.20190212211648-25d852aebe32 // indirect | ||||||
|  |  | ||||||
							
								
								
									
										4
									
								
								go.sum
								
								
								
								
							
							
						
						
									
										4
									
								
								go.sum
								
								
								
								
							|  | @ -579,6 +579,7 @@ github.com/containerd/containerd v1.5.0-rc.0/go.mod h1:V/IXoMqNGgBlabz3tHD2TWDoT | ||||||
| github.com/containerd/containerd v1.5.1/go.mod h1:0DOxVqwDy2iZvrZp2JUx/E+hS0UNTVn7dJnIOwtYR4g= | github.com/containerd/containerd v1.5.1/go.mod h1:0DOxVqwDy2iZvrZp2JUx/E+hS0UNTVn7dJnIOwtYR4g= | ||||||
| github.com/containerd/containerd v1.5.4/go.mod h1:sx18RgvW6ABJ4iYUw7Q5x7bgFOAB9B6G7+yO0XBc4zw= | github.com/containerd/containerd v1.5.4/go.mod h1:sx18RgvW6ABJ4iYUw7Q5x7bgFOAB9B6G7+yO0XBc4zw= | ||||||
| github.com/containerd/containerd v1.6.8 h1:h4dOFDwzHmqFEP754PgfgTeVXFnLiRc6kiqC7tplDJs= | github.com/containerd/containerd v1.6.8 h1:h4dOFDwzHmqFEP754PgfgTeVXFnLiRc6kiqC7tplDJs= | ||||||
|  | github.com/containerd/containerd v1.6.8/go.mod h1:By6p5KqPK0/7/CgO/A6t/Gz+CUYUu2zf1hUaaymVXB0= | ||||||
| github.com/containerd/continuity v0.0.0-20190426062206-aaeac12a7ffc/go.mod h1:GL3xCUCBDV3CZiTSEKksMWbLE66hEyuu9qyDOOqM47Y= | github.com/containerd/continuity v0.0.0-20190426062206-aaeac12a7ffc/go.mod h1:GL3xCUCBDV3CZiTSEKksMWbLE66hEyuu9qyDOOqM47Y= | ||||||
| github.com/containerd/continuity v0.0.0-20190815185530-f2a389ac0a02/go.mod h1:GL3xCUCBDV3CZiTSEKksMWbLE66hEyuu9qyDOOqM47Y= | github.com/containerd/continuity v0.0.0-20190815185530-f2a389ac0a02/go.mod h1:GL3xCUCBDV3CZiTSEKksMWbLE66hEyuu9qyDOOqM47Y= | ||||||
| github.com/containerd/continuity v0.0.0-20191127005431-f65d91d395eb/go.mod h1:GL3xCUCBDV3CZiTSEKksMWbLE66hEyuu9qyDOOqM47Y= | github.com/containerd/continuity v0.0.0-20191127005431-f65d91d395eb/go.mod h1:GL3xCUCBDV3CZiTSEKksMWbLE66hEyuu9qyDOOqM47Y= | ||||||
|  | @ -751,6 +752,7 @@ github.com/docker/distribution v2.7.0+incompatible/go.mod h1:J2gT2udsDAN96Uj4Kfc | ||||||
| github.com/docker/distribution v2.7.1-0.20190205005809-0d3efadf0154+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w= | github.com/docker/distribution v2.7.1-0.20190205005809-0d3efadf0154+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w= | ||||||
| github.com/docker/distribution v2.7.1+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w= | github.com/docker/distribution v2.7.1+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w= | ||||||
| github.com/docker/distribution v2.8.1+incompatible h1:Q50tZOPR6T/hjNsyc9g8/syEs6bk8XXApsHjKukMl68= | github.com/docker/distribution v2.8.1+incompatible h1:Q50tZOPR6T/hjNsyc9g8/syEs6bk8XXApsHjKukMl68= | ||||||
|  | github.com/docker/distribution v2.8.1+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w= | ||||||
| github.com/docker/go-connections v0.3.0/go.mod h1:Gbd7IOopHjR8Iph03tsViu4nIes5XhDvyHbTtUxmeec= | github.com/docker/go-connections v0.3.0/go.mod h1:Gbd7IOopHjR8Iph03tsViu4nIes5XhDvyHbTtUxmeec= | ||||||
| github.com/docker/go-connections v0.4.0 h1:El9xVISelRB7BuFusrZozjnkIM5YnzCViNKohAFqRJQ= | github.com/docker/go-connections v0.4.0 h1:El9xVISelRB7BuFusrZozjnkIM5YnzCViNKohAFqRJQ= | ||||||
| github.com/docker/go-connections v0.4.0/go.mod h1:Gbd7IOopHjR8Iph03tsViu4nIes5XhDvyHbTtUxmeec= | github.com/docker/go-connections v0.4.0/go.mod h1:Gbd7IOopHjR8Iph03tsViu4nIes5XhDvyHbTtUxmeec= | ||||||
|  | @ -2011,6 +2013,7 @@ github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3I | ||||||
| github.com/opencontainers/image-spec v1.0.0/go.mod h1:BtxoFyWECRxE4U/7sNtV5W15zMzWCbyJoFRP3s7yZA0= | github.com/opencontainers/image-spec v1.0.0/go.mod h1:BtxoFyWECRxE4U/7sNtV5W15zMzWCbyJoFRP3s7yZA0= | ||||||
| github.com/opencontainers/image-spec v1.0.1/go.mod h1:BtxoFyWECRxE4U/7sNtV5W15zMzWCbyJoFRP3s7yZA0= | github.com/opencontainers/image-spec v1.0.1/go.mod h1:BtxoFyWECRxE4U/7sNtV5W15zMzWCbyJoFRP3s7yZA0= | ||||||
| github.com/opencontainers/image-spec v1.0.3-0.20211202183452-c5a74bcca799 h1:rc3tiVYb5z54aKaDfakKn0dDjIyPpTtszkjuMzyt7ec= | github.com/opencontainers/image-spec v1.0.3-0.20211202183452-c5a74bcca799 h1:rc3tiVYb5z54aKaDfakKn0dDjIyPpTtszkjuMzyt7ec= | ||||||
|  | github.com/opencontainers/image-spec v1.0.3-0.20211202183452-c5a74bcca799/go.mod h1:BtxoFyWECRxE4U/7sNtV5W15zMzWCbyJoFRP3s7yZA0= | ||||||
| github.com/opencontainers/runc v0.0.0-20190115041553-12f6a991201f/go.mod h1:qT5XzbpPznkRYVz/mWwUaVBUv2rmF59PVA73FjuZG0U= | github.com/opencontainers/runc v0.0.0-20190115041553-12f6a991201f/go.mod h1:qT5XzbpPznkRYVz/mWwUaVBUv2rmF59PVA73FjuZG0U= | ||||||
| github.com/opencontainers/runc v0.1.1/go.mod h1:qT5XzbpPznkRYVz/mWwUaVBUv2rmF59PVA73FjuZG0U= | github.com/opencontainers/runc v0.1.1/go.mod h1:qT5XzbpPznkRYVz/mWwUaVBUv2rmF59PVA73FjuZG0U= | ||||||
| github.com/opencontainers/runc v1.0.0-rc8.0.20190926000215-3e425f80a8c9/go.mod h1:qT5XzbpPznkRYVz/mWwUaVBUv2rmF59PVA73FjuZG0U= | github.com/opencontainers/runc v1.0.0-rc8.0.20190926000215-3e425f80a8c9/go.mod h1:qT5XzbpPznkRYVz/mWwUaVBUv2rmF59PVA73FjuZG0U= | ||||||
|  | @ -3483,6 +3486,7 @@ k8s.io/kube-openapi v0.0.0-20200410145947-61e04a5be9a6/go.mod h1:GRQhZsXIAJ1xR0C | ||||||
| k8s.io/kube-openapi v0.0.0-20200805222855-6aeccd4b50c6/go.mod h1:UuqjUnNftUyPE5H64/qeyjQoUZhGpeFDVdxjTeEVN2o= | k8s.io/kube-openapi v0.0.0-20200805222855-6aeccd4b50c6/go.mod h1:UuqjUnNftUyPE5H64/qeyjQoUZhGpeFDVdxjTeEVN2o= | ||||||
| k8s.io/kube-openapi v0.0.0-20201113171705-d219536bb9fd/go.mod h1:WOJ3KddDSol4tAGcJo0Tvi+dK12EcqSLqcWsryKMpfM= | k8s.io/kube-openapi v0.0.0-20201113171705-d219536bb9fd/go.mod h1:WOJ3KddDSol4tAGcJo0Tvi+dK12EcqSLqcWsryKMpfM= | ||||||
| k8s.io/kube-openapi v0.0.0-20210305001622-591a79e4bda7/go.mod h1:wXW5VT87nVfh/iLV8FpR2uDvrFyomxbtb1KivDbvPTE= | k8s.io/kube-openapi v0.0.0-20210305001622-591a79e4bda7/go.mod h1:wXW5VT87nVfh/iLV8FpR2uDvrFyomxbtb1KivDbvPTE= | ||||||
|  | k8s.io/kube-openapi v0.0.0-20210421082810-95288971da7e h1:KLHHjkdQFomZy8+06csTWZ0m1343QqxZhR2LJ1OxCYM= | ||||||
| k8s.io/kube-openapi v0.0.0-20210421082810-95288971da7e/go.mod h1:vHXdDvt9+2spS2Rx9ql3I8tycm3H9FDfdUoIuKCefvw= | k8s.io/kube-openapi v0.0.0-20210421082810-95288971da7e/go.mod h1:vHXdDvt9+2spS2Rx9ql3I8tycm3H9FDfdUoIuKCefvw= | ||||||
| k8s.io/kube-openapi v0.0.0-20220328201542-3ee0da9b0b42 h1:Gii5eqf+GmIEwGNKQYQClCayuJCe2/4fZUvF7VG99sU= | k8s.io/kube-openapi v0.0.0-20220328201542-3ee0da9b0b42 h1:Gii5eqf+GmIEwGNKQYQClCayuJCe2/4fZUvF7VG99sU= | ||||||
| k8s.io/kubernetes v1.13.0/go.mod h1:ocZa8+6APFNC2tX1DZASIbocyYT5jHzqFVsY5aoB7Jk= | k8s.io/kubernetes v1.13.0/go.mod h1:ocZa8+6APFNC2tX1DZASIbocyYT5jHzqFVsY5aoB7Jk= | ||||||
|  |  | ||||||
|  | @ -225,7 +225,7 @@ func main() { | ||||||
| 				{ | 				{ | ||||||
| 					Name:   "github", | 					Name:   "github", | ||||||
| 					Usage:  "Publish packages to GitHub releases", | 					Usage:  "Publish packages to GitHub releases", | ||||||
| 					Action: PublishGitHub, | 					Action: PublishGithub, | ||||||
| 					Flags: []cli.Flag{ | 					Flags: []cli.Flag{ | ||||||
| 						&dryRunFlag, | 						&dryRunFlag, | ||||||
| 						&cli.StringFlag{ | 						&cli.StringFlag{ | ||||||
|  | @ -240,7 +240,7 @@ func main() { | ||||||
| 						}, | 						}, | ||||||
| 						&cli.StringFlag{ | 						&cli.StringFlag{ | ||||||
| 							Name:  "tag", | 							Name:  "tag", | ||||||
| 							Usage: "Release tag (default from metadata)ß", | 							Usage: "Release tag (default from metadata)", | ||||||
| 						}, | 						}, | ||||||
| 						&cli.BoolFlag{ | 						&cli.BoolFlag{ | ||||||
| 							Name:  "create", | 							Name:  "create", | ||||||
|  | @ -248,6 +248,33 @@ func main() { | ||||||
| 						}, | 						}, | ||||||
| 					}, | 					}, | ||||||
| 				}, | 				}, | ||||||
|  | 				{ | ||||||
|  | 					Name:   "aws", | ||||||
|  | 					Usage:  "Publish image to AWS Marketplace releases", | ||||||
|  | 					Action: PublishAwsMarketplace, | ||||||
|  | 					Flags: []cli.Flag{ | ||||||
|  | 						&dryRunFlag, | ||||||
|  | 						&cli.StringFlag{ | ||||||
|  | 							Name:  "version", | ||||||
|  | 							Usage: "Release version (default from metadata)", | ||||||
|  | 						}, | ||||||
|  | 						&cli.StringFlag{ | ||||||
|  | 							Name:     "image", | ||||||
|  | 							Required: true, | ||||||
|  | 							Usage:    "Name of the image to be released", | ||||||
|  | 						}, | ||||||
|  | 						&cli.StringFlag{ | ||||||
|  | 							Name:     "repo", | ||||||
|  | 							Required: true, | ||||||
|  | 							Usage:    "AWS Marketplace ECR repository", | ||||||
|  | 						}, | ||||||
|  | 						&cli.StringFlag{ | ||||||
|  | 							Name:     "product", | ||||||
|  | 							Required: true, | ||||||
|  | 							Usage:    "AWS Marketplace product identifier", | ||||||
|  | 						}, | ||||||
|  | 					}, | ||||||
|  | 				}, | ||||||
| 			}, | 			}, | ||||||
| 		}, | 		}, | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
|  | @ -0,0 +1,307 @@ | ||||||
|  | package main | ||||||
|  | 
 | ||||||
|  | import ( | ||||||
|  | 	"context" | ||||||
|  | 	"encoding/base64" | ||||||
|  | 	"encoding/json" | ||||||
|  | 	"errors" | ||||||
|  | 	"fmt" | ||||||
|  | 	"io" | ||||||
|  | 	"os" | ||||||
|  | 	"strings" | ||||||
|  | 
 | ||||||
|  | 	"github.com/aws/aws-sdk-go/aws" | ||||||
|  | 	"github.com/aws/aws-sdk-go/aws/request" | ||||||
|  | 	"github.com/aws/aws-sdk-go/aws/session" | ||||||
|  | 	"github.com/aws/aws-sdk-go/service/ecr" | ||||||
|  | 	"github.com/aws/aws-sdk-go/service/marketplacecatalog" | ||||||
|  | 	"github.com/docker/docker/api/types" | ||||||
|  | 	"github.com/docker/docker/client" | ||||||
|  | 	"github.com/urfave/cli/v2" | ||||||
|  | ) | ||||||
|  | 
 | ||||||
|  | const ( | ||||||
|  | 	marketplaceChangeSetName  = "Add new version" | ||||||
|  | 	marketplaceCatalogId      = "AWSMarketplace" | ||||||
|  | 	marketplaceRegistryId     = "709825985650" | ||||||
|  | 	marketplaceRegistryRegion = "us-east-1" | ||||||
|  | 	marketplaceRegistryUrl    = "709825985650.dkr.ecr.us-east-1.amazonaws.com" | ||||||
|  | 	marketplaceRequestsUrl    = "https://aws.amazon.com/marketplace/management/requests/" | ||||||
|  | 	releaseNotesTemplateUrl   = "https://grafana.com/docs/grafana/latest/release-notes/release-notes-${TAG}/" | ||||||
|  | 	helmChartsUrl             = "https://grafana.github.io/helm-charts/" | ||||||
|  | 	docsUrl                   = "https://grafana.com/docs/grafana/latest/enterprise/license/" | ||||||
|  | 	imagePlatform             = "linux/amd64" | ||||||
|  | 
 | ||||||
|  | 	publishAwsMarketplaceTestKey publishAwsMarketplaceTestKeyType = "test-client" | ||||||
|  | ) | ||||||
|  | 
 | ||||||
|  | var ( | ||||||
|  | 	errEmptyVersion = errors.New(`failed to retrieve release version from metadata, use "--version" to set it manually`) | ||||||
|  | ) | ||||||
|  | 
 | ||||||
|  | type publishAwsMarketplaceTestKeyType string | ||||||
|  | 
 | ||||||
|  | type publishAwsMarketplaceFlags struct { | ||||||
|  | 	dryRun  bool | ||||||
|  | 	version string | ||||||
|  | 	repo    string | ||||||
|  | 	image   string | ||||||
|  | 	product string | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | type AwsMarketplacePublishingService struct { | ||||||
|  | 	auth   string | ||||||
|  | 	docker AwsMarketplaceDocker | ||||||
|  | 	ecr    AwsMarketplaceRegistry | ||||||
|  | 	mkt    AwsMarketplaceCatalog | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | type AwsMarketplaceDocker interface { | ||||||
|  | 	ImagePull(ctx context.Context, refStr string, options types.ImagePullOptions) (io.ReadCloser, error) | ||||||
|  | 	ImageTag(ctx context.Context, source string, target string) error | ||||||
|  | 	ImagePush(ctx context.Context, image string, options types.ImagePushOptions) (io.ReadCloser, error) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | type AwsMarketplaceRegistry interface { | ||||||
|  | 	GetAuthorizationTokenWithContext(ctx context.Context, input *ecr.GetAuthorizationTokenInput, opts ...request.Option) (*ecr.GetAuthorizationTokenOutput, error) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | type AwsMarketplaceCatalog interface { | ||||||
|  | 	DescribeEntityWithContext(ctx context.Context, input *marketplacecatalog.DescribeEntityInput, opts ...request.Option) (*marketplacecatalog.DescribeEntityOutput, error) | ||||||
|  | 	StartChangeSetWithContext(ctx context.Context, input *marketplacecatalog.StartChangeSetInput, opts ...request.Option) (*marketplacecatalog.StartChangeSetOutput, error) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func PublishAwsMarketplace(ctx *cli.Context) error { | ||||||
|  | 	f, err := getPublishAwsMarketplaceFlags(ctx) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return err | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	if f.version == "" { | ||||||
|  | 		return errEmptyVersion | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	svc, err := getAwsMarketplacePublishingService() | ||||||
|  | 	if err != nil { | ||||||
|  | 		return err | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	if ctx.Context.Value(publishAwsMarketplaceTestKey) != nil { | ||||||
|  | 		svc = ctx.Context.Value(publishAwsMarketplaceTestKey).(*AwsMarketplacePublishingService) | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	fmt.Println("Logging in to AWS Marketplace registry") | ||||||
|  | 	err = svc.Login(ctx.Context) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return err | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	fmt.Printf("Retrieving image '%s:%s' from Docker Hub\n", f.image, f.version) | ||||||
|  | 	err = svc.PullImage(ctx.Context, f.image, f.version) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return err | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	fmt.Printf("Renaming image '%s:%s' to '%s/%s:%s'\n", f.image, f.version, marketplaceRegistryUrl, f.repo, f.version) | ||||||
|  | 	err = svc.TagImage(ctx.Context, f.image, f.repo, f.version) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return err | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	if !f.dryRun { | ||||||
|  | 		fmt.Printf("Pushing image '%s/%s:%s' to the AWS Marketplace ECR\n", marketplaceRegistryUrl, f.repo, f.version) | ||||||
|  | 		err = svc.PushToMarketplace(ctx.Context, f.repo, f.version) | ||||||
|  | 		if err != nil { | ||||||
|  | 			return err | ||||||
|  | 		} | ||||||
|  | 	} else { | ||||||
|  | 		fmt.Printf("Dry-Run: Pushing image '%s/%s:%s' to the AWS Marketplace ECR\n", marketplaceRegistryUrl, f.repo, f.version) | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	fmt.Printf("Retrieving product identifier for product '%s'\n", f.product) | ||||||
|  | 	pid, err := svc.GetProductIdentifier(ctx.Context, f.product) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return err | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	if !f.dryRun { | ||||||
|  | 		fmt.Printf("Releasing to product, you can view the progress of the release on %s\n", marketplaceRequestsUrl) | ||||||
|  | 		return svc.ReleaseToProduct(ctx.Context, pid, f.repo, f.version) | ||||||
|  | 	} else { | ||||||
|  | 		fmt.Printf("Dry-Run: Releasing to product, you can view the progress of the release on %s\n", marketplaceRequestsUrl) | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	return nil | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func getAwsMarketplacePublishingService() (*AwsMarketplacePublishingService, error) { | ||||||
|  | 	cli, err := client.NewClientWithOpts(client.FromEnv, client.WithAPIVersionNegotiation()) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return nil, err | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	mySession := session.Must(session.NewSession()) | ||||||
|  | 	ecr := ecr.New(mySession, aws.NewConfig().WithRegion(marketplaceRegistryRegion)) | ||||||
|  | 	mkt := marketplacecatalog.New(mySession, aws.NewConfig().WithRegion(marketplaceRegistryRegion)) | ||||||
|  | 	return &AwsMarketplacePublishingService{ | ||||||
|  | 		docker: cli, | ||||||
|  | 		ecr:    ecr, | ||||||
|  | 		mkt:    mkt, | ||||||
|  | 	}, nil | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (s *AwsMarketplacePublishingService) Login(ctx context.Context) error { | ||||||
|  | 	out, err := s.ecr.GetAuthorizationTokenWithContext(ctx, &ecr.GetAuthorizationTokenInput{}) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return err | ||||||
|  | 	} | ||||||
|  | 	s.auth = *out.AuthorizationData[0].AuthorizationToken | ||||||
|  | 	authData, err := base64.StdEncoding.DecodeString(s.auth) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return err | ||||||
|  | 	} | ||||||
|  | 	authString := strings.Split(string(authData), ":") | ||||||
|  | 	authData, err = json.Marshal(types.AuthConfig{ | ||||||
|  | 		Username: authString[0], | ||||||
|  | 		Password: authString[1], | ||||||
|  | 	}) | ||||||
|  | 	s.auth = base64.StdEncoding.EncodeToString(authData) | ||||||
|  | 	return err | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (s *AwsMarketplacePublishingService) PullImage(ctx context.Context, image string, version string) error { | ||||||
|  | 	reader, err := s.docker.ImagePull(ctx, fmt.Sprintf("%s:%s", image, version), types.ImagePullOptions{ | ||||||
|  | 		Platform: imagePlatform, | ||||||
|  | 	}) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return err | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	_, err = io.Copy(os.Stdout, reader) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return err | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	err = reader.Close() | ||||||
|  | 	if err != nil { | ||||||
|  | 		return err | ||||||
|  | 	} | ||||||
|  | 	return nil | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (s *AwsMarketplacePublishingService) TagImage(ctx context.Context, image string, repo string, version string) error { | ||||||
|  | 	err := s.docker.ImageTag(ctx, fmt.Sprintf("%s:%s", image, version), fmt.Sprintf("%s/%s:%s", marketplaceRegistryUrl, repo, version)) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return err | ||||||
|  | 	} | ||||||
|  | 	return nil | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (s *AwsMarketplacePublishingService) PushToMarketplace(ctx context.Context, repo string, version string) error { | ||||||
|  | 	reader, err := s.docker.ImagePush(ctx, fmt.Sprintf("%s/%s:%s", marketplaceRegistryUrl, repo, version), types.ImagePushOptions{ | ||||||
|  | 		RegistryAuth: s.auth, | ||||||
|  | 	}) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return err | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	_, err = io.Copy(os.Stdout, reader) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return err | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	err = reader.Close() | ||||||
|  | 	if err != nil { | ||||||
|  | 		return err | ||||||
|  | 	} | ||||||
|  | 	return nil | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (s *AwsMarketplacePublishingService) GetProductIdentifier(ctx context.Context, product string) (string, error) { | ||||||
|  | 	out, err := s.mkt.DescribeEntityWithContext(ctx, &marketplacecatalog.DescribeEntityInput{ | ||||||
|  | 		EntityId: aws.String(product), | ||||||
|  | 		Catalog:  aws.String(marketplaceCatalogId), | ||||||
|  | 	}) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return "", err | ||||||
|  | 	} | ||||||
|  | 	return *out.EntityIdentifier, nil | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (s *AwsMarketplacePublishingService) ReleaseToProduct(ctx context.Context, pid string, repo string, version string) error { | ||||||
|  | 	_, err := s.mkt.StartChangeSetWithContext(ctx, &marketplacecatalog.StartChangeSetInput{ | ||||||
|  | 		Catalog:       aws.String(marketplaceCatalogId), | ||||||
|  | 		ChangeSetName: aws.String(marketplaceChangeSetName), | ||||||
|  | 		ChangeSet: []*marketplacecatalog.Change{ | ||||||
|  | 			buildAwsMarketplaceChangeSet(pid, repo, version), | ||||||
|  | 		}, | ||||||
|  | 	}) | ||||||
|  | 	return err | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func getPublishAwsMarketplaceFlags(ctx *cli.Context) (*publishAwsMarketplaceFlags, error) { | ||||||
|  | 	metadata, err := GenerateMetadata(ctx) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return nil, err | ||||||
|  | 	} | ||||||
|  | 	version := ctx.String("version") | ||||||
|  | 	if version == "" && metadata.GrafanaVersion != "" { | ||||||
|  | 		version = metadata.GrafanaVersion | ||||||
|  | 	} | ||||||
|  | 	image := ctx.String("image") | ||||||
|  | 	repo := ctx.String("repo") | ||||||
|  | 	product := ctx.String("product") | ||||||
|  | 	dryRun := ctx.Bool("dry-run") | ||||||
|  | 	return &publishAwsMarketplaceFlags{ | ||||||
|  | 		dryRun:  dryRun, | ||||||
|  | 		version: version, | ||||||
|  | 		image:   image, | ||||||
|  | 		repo:    repo, | ||||||
|  | 		product: product, | ||||||
|  | 	}, nil | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func buildAwsMarketplaceReleaseNotesUrl(version string) string { | ||||||
|  | 	sanitizedVersion := strings.ReplaceAll(version, ".", "-") | ||||||
|  | 	return strings.ReplaceAll(releaseNotesTemplateUrl, "${TAG}", sanitizedVersion) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func buildAwsMarketplaceChangeSet(entityId string, repo string, version string) *marketplacecatalog.Change { | ||||||
|  | 	return &marketplacecatalog.Change{ | ||||||
|  | 		ChangeType: aws.String("AddDeliveryOptions"), | ||||||
|  | 		Entity: &marketplacecatalog.Entity{ | ||||||
|  | 			Type:       aws.String("ContainerProduct@1.0"), | ||||||
|  | 			Identifier: aws.String(entityId), | ||||||
|  | 		}, | ||||||
|  | 		Details: aws.String(buildAwsMarketplaceVersionDetails(repo, version)), | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func buildAwsMarketplaceVersionDetails(repo string, version string) string { | ||||||
|  | 	releaseNotesUrl := buildAwsMarketplaceReleaseNotesUrl(version) | ||||||
|  | 	return fmt.Sprintf(`{ | ||||||
|  |         "Version": { | ||||||
|  |           "ReleaseNotes": "Release notes are available on the website %s", | ||||||
|  |           "VersionTitle": "v%s" | ||||||
|  |         }, | ||||||
|  |         "DeliveryOptions": [ | ||||||
|  |           { | ||||||
|  |             "Details": { | ||||||
|  |               "EcrDeliveryOptionDetails": { | ||||||
|  |                 "DeploymentResources": [ | ||||||
|  |                   { | ||||||
|  |                     "Name": "Helm Charts", | ||||||
|  |                     "Url": "%s" | ||||||
|  |                   } | ||||||
|  |                 ], | ||||||
|  |                 "CompatibleServices": ["EKS", "ECS", "ECS-Anywhere", "EKS-Anywhere"], | ||||||
|  |                 "ContainerImages": ["%s/%s:%s"], | ||||||
|  |                 "Description": "Grafana Enterprise can be installed using the official Grafana Helm chart repository. The repository is available on Github: %s", | ||||||
|  |                 "UsageInstructions": "You can apply your Grafana Enterprise license to a new or existing Grafana Enterprise deployment by updating a configuration setting or environment variable. Your Grafana instance must be deployed on AWS, or have network access to AWS. For more information, see %s" | ||||||
|  |               } | ||||||
|  |             }, | ||||||
|  |             "DeliveryOptionTitle": "Helm Chart" | ||||||
|  |           } | ||||||
|  |         ] | ||||||
|  |       }`, releaseNotesUrl, version, helmChartsUrl, marketplaceRegistryUrl, repo, version, helmChartsUrl, docsUrl) | ||||||
|  | } | ||||||
|  | @ -0,0 +1,205 @@ | ||||||
|  | package main | ||||||
|  | 
 | ||||||
|  | import ( | ||||||
|  | 	"bytes" | ||||||
|  | 	"context" | ||||||
|  | 	"encoding/base64" | ||||||
|  | 	"errors" | ||||||
|  | 	"io" | ||||||
|  | 	"os" | ||||||
|  | 	"testing" | ||||||
|  | 
 | ||||||
|  | 	"github.com/aws/aws-sdk-go/aws" | ||||||
|  | 	"github.com/aws/aws-sdk-go/aws/credentials" | ||||||
|  | 	"github.com/aws/aws-sdk-go/aws/request" | ||||||
|  | 	"github.com/aws/aws-sdk-go/service/ecr" | ||||||
|  | 	"github.com/aws/aws-sdk-go/service/marketplacecatalog" | ||||||
|  | 	"github.com/docker/docker/api/types" | ||||||
|  | 	"github.com/stretchr/testify/assert" | ||||||
|  | 	"github.com/urfave/cli/v2" | ||||||
|  | ) | ||||||
|  | 
 | ||||||
|  | type awsPublishTestCase struct { | ||||||
|  | 	name           string | ||||||
|  | 	args           []string | ||||||
|  | 	expectedError  error | ||||||
|  | 	errorContains  string | ||||||
|  | 	expectedOutput string | ||||||
|  | 	mockedService  *AwsMarketplacePublishingService | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func TestPublishAwsMarketplace(t *testing.T) { | ||||||
|  | 	t.Setenv("DRONE_BUILD_EVENT", "promote") | ||||||
|  | 	testApp := setupPublishAwsMarketplaceTests(t) | ||||||
|  | 	errShouldNotCallMock := errors.New("shouldn't call") | ||||||
|  | 
 | ||||||
|  | 	testCases := []awsPublishTestCase{ | ||||||
|  | 		{ | ||||||
|  | 			name:          "try to publish without required flags", | ||||||
|  | 			errorContains: `Required flags "image, repo, product" not set`, | ||||||
|  | 		}, | ||||||
|  | 		{ | ||||||
|  | 			name: "try to publish without credentials", | ||||||
|  | 			args: []string{"--image", "test/test", "--repo", "test/test", "--product", "test", "--version", "1.0.0"}, | ||||||
|  | 			mockedService: &AwsMarketplacePublishingService{ | ||||||
|  | 				ecr: &mockAwsMarketplaceRegistry{ | ||||||
|  | 					GetAuthorizationTokenWithContextError: credentials.ErrNoValidProvidersFoundInChain, | ||||||
|  | 				}, | ||||||
|  | 			}, | ||||||
|  | 			expectedError: credentials.ErrNoValidProvidersFoundInChain, | ||||||
|  | 		}, | ||||||
|  | 		{ | ||||||
|  | 			name: "try to publish with valid credentials and nonexisting version", | ||||||
|  | 			args: []string{"--image", "test/test", "--repo", "test/test", "--product", "test", "--version", "1.0.0"}, | ||||||
|  | 			mockedService: &AwsMarketplacePublishingService{ | ||||||
|  | 				ecr:    &mockAwsMarketplaceRegistry{}, | ||||||
|  | 				docker: &mockAwsMarketplaceDocker{}, | ||||||
|  | 				mkt:    &mockAwsMarketplaceCatalog{}, | ||||||
|  | 			}, | ||||||
|  | 			expectedOutput: "Releasing to product", | ||||||
|  | 		}, | ||||||
|  | 		{ | ||||||
|  | 			name: "try to publish with valid credentials and existing version", | ||||||
|  | 			args: []string{"--image", "test/test", "--repo", "test/test", "--product", "test", "--version", "1.0.0"}, | ||||||
|  | 			mockedService: &AwsMarketplacePublishingService{ | ||||||
|  | 				ecr:    &mockAwsMarketplaceRegistry{}, | ||||||
|  | 				docker: &mockAwsMarketplaceDocker{}, | ||||||
|  | 				mkt:    &mockAwsMarketplaceCatalog{}, | ||||||
|  | 			}, | ||||||
|  | 			expectedOutput: "Releasing to product", | ||||||
|  | 		}, | ||||||
|  | 		{ | ||||||
|  | 			name: "dry run with invalid credentials", | ||||||
|  | 			args: []string{"--dry-run", "--image", "test/test", "--repo", "test/test", "--product", "test", "--version", "1.0.0"}, | ||||||
|  | 			mockedService: &AwsMarketplacePublishingService{ | ||||||
|  | 				ecr: &mockAwsMarketplaceRegistry{ | ||||||
|  | 					GetAuthorizationTokenWithContextError: credentials.ErrNoValidProvidersFoundInChain, | ||||||
|  | 				}, | ||||||
|  | 			}, | ||||||
|  | 			expectedError: credentials.ErrNoValidProvidersFoundInChain, | ||||||
|  | 		}, | ||||||
|  | 		{ | ||||||
|  | 			name: "dry run with valid credentials", | ||||||
|  | 			args: []string{"--dry-run", "--image", "test/test", "--repo", "test/test", "--product", "test", "--version", "1.0.0"}, | ||||||
|  | 			mockedService: &AwsMarketplacePublishingService{ | ||||||
|  | 				ecr: &mockAwsMarketplaceRegistry{}, | ||||||
|  | 				docker: &mockAwsMarketplaceDocker{ | ||||||
|  | 					ImagePushError: errShouldNotCallMock, | ||||||
|  | 				}, | ||||||
|  | 				mkt: &mockAwsMarketplaceCatalog{ | ||||||
|  | 					StartChangeSetWithContextError: errShouldNotCallMock, | ||||||
|  | 				}, | ||||||
|  | 			}, | ||||||
|  | 			expectedOutput: "Dry-Run: Releasing to product", | ||||||
|  | 		}, | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	if os.Getenv("DRONE_COMMIT") == "" { | ||||||
|  | 		// this test only works locally due to Drone environment
 | ||||||
|  | 		testCases = append(testCases, | ||||||
|  | 			awsPublishTestCase{ | ||||||
|  | 				name:          "try to publish without version", | ||||||
|  | 				args:          []string{"--image", "test/test", "--repo", "test/test", "--product", "test"}, | ||||||
|  | 				expectedError: errEmptyVersion, | ||||||
|  | 			}, | ||||||
|  | 		) | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	for _, test := range testCases { | ||||||
|  | 		t.Run(test.name, func(t *testing.T) { | ||||||
|  | 			ctx := context.WithValue(context.Background(), publishAwsMarketplaceTestKey, test.mockedService) | ||||||
|  | 			args := []string{"run"} | ||||||
|  | 			args = append(args, test.args...) | ||||||
|  | 			out, err := captureStdout(t, func() error { | ||||||
|  | 				return testApp.RunContext(ctx, args) | ||||||
|  | 			}) | ||||||
|  | 			if test.expectedOutput != "" { | ||||||
|  | 				assert.Contains(t, out, test.expectedOutput) | ||||||
|  | 			} | ||||||
|  | 			if test.expectedError != nil || test.errorContains != "" { | ||||||
|  | 				assert.Error(t, err) | ||||||
|  | 				if test.expectedError != nil { | ||||||
|  | 					assert.ErrorIs(t, err, test.expectedError) | ||||||
|  | 				} | ||||||
|  | 				if test.errorContains != "" { | ||||||
|  | 					assert.ErrorContains(t, err, test.errorContains) | ||||||
|  | 				} | ||||||
|  | 			} else { | ||||||
|  | 				assert.NoError(t, err) | ||||||
|  | 			} | ||||||
|  | 		}) | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func setupPublishAwsMarketplaceTests(t *testing.T) *cli.App { | ||||||
|  | 	t.Helper() | ||||||
|  | 	testApp := cli.NewApp() | ||||||
|  | 	testApp.Action = PublishAwsMarketplace | ||||||
|  | 	testApp.Flags = []cli.Flag{ | ||||||
|  | 		&dryRunFlag, | ||||||
|  | 		&cli.StringFlag{ | ||||||
|  | 			Name:  "version", | ||||||
|  | 			Usage: "Release version (default from metadata)", | ||||||
|  | 		}, | ||||||
|  | 		&cli.StringFlag{ | ||||||
|  | 			Name:     "image", | ||||||
|  | 			Required: true, | ||||||
|  | 			Usage:    "Name of the image to be released", | ||||||
|  | 		}, | ||||||
|  | 		&cli.StringFlag{ | ||||||
|  | 			Name:     "repo", | ||||||
|  | 			Required: true, | ||||||
|  | 			Usage:    "AWS Marketplace ECR repository", | ||||||
|  | 		}, | ||||||
|  | 		&cli.StringFlag{ | ||||||
|  | 			Name:     "product", | ||||||
|  | 			Required: true, | ||||||
|  | 			Usage:    "AWS Marketplace product identifier", | ||||||
|  | 		}, | ||||||
|  | 	} | ||||||
|  | 	return testApp | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | type mockAwsMarketplaceDocker struct { | ||||||
|  | 	ImagePullError error | ||||||
|  | 	ImageTagError  error | ||||||
|  | 	ImagePushError error | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (m *mockAwsMarketplaceDocker) ImagePull(ctx context.Context, refStr string, options types.ImagePullOptions) (io.ReadCloser, error) { | ||||||
|  | 	return io.NopCloser(bytes.NewReader([]byte(""))), m.ImagePullError | ||||||
|  | } | ||||||
|  | func (m *mockAwsMarketplaceDocker) ImageTag(ctx context.Context, source string, target string) error { | ||||||
|  | 	return m.ImageTagError | ||||||
|  | } | ||||||
|  | func (m *mockAwsMarketplaceDocker) ImagePush(ctx context.Context, image string, options types.ImagePushOptions) (io.ReadCloser, error) { | ||||||
|  | 	return io.NopCloser(bytes.NewReader([]byte(""))), m.ImagePushError | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | type mockAwsMarketplaceRegistry struct { | ||||||
|  | 	GetAuthorizationTokenWithContextError error | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (m *mockAwsMarketplaceRegistry) GetAuthorizationTokenWithContext(ctx context.Context, input *ecr.GetAuthorizationTokenInput, opts ...request.Option) (*ecr.GetAuthorizationTokenOutput, error) { | ||||||
|  | 	return &ecr.GetAuthorizationTokenOutput{ | ||||||
|  | 		AuthorizationData: []*ecr.AuthorizationData{ | ||||||
|  | 			{ | ||||||
|  | 				AuthorizationToken: aws.String(base64.StdEncoding.EncodeToString([]byte("username:password"))), | ||||||
|  | 			}, | ||||||
|  | 		}, | ||||||
|  | 	}, m.GetAuthorizationTokenWithContextError | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | type mockAwsMarketplaceCatalog struct { | ||||||
|  | 	DescribeEntityWithContextError error | ||||||
|  | 	StartChangeSetWithContextError error | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (m *mockAwsMarketplaceCatalog) DescribeEntityWithContext(ctx context.Context, input *marketplacecatalog.DescribeEntityInput, opts ...request.Option) (*marketplacecatalog.DescribeEntityOutput, error) { | ||||||
|  | 	return &marketplacecatalog.DescribeEntityOutput{ | ||||||
|  | 		EntityIdentifier: aws.String("productid"), | ||||||
|  | 	}, m.DescribeEntityWithContextError | ||||||
|  | } | ||||||
|  | func (m *mockAwsMarketplaceCatalog) StartChangeSetWithContext(ctx context.Context, input *marketplacecatalog.StartChangeSetInput, opts ...request.Option) (*marketplacecatalog.StartChangeSetOutput, error) { | ||||||
|  | 	return &marketplacecatalog.StartChangeSetOutput{}, m.StartChangeSetWithContextError | ||||||
|  | } | ||||||
|  | @ -39,9 +39,9 @@ var ( | ||||||
| 	errReleaseNotFound = errors.New(`release not found, use "--create" to create the release`) | 	errReleaseNotFound = errors.New(`release not found, use "--create" to create the release`) | ||||||
| ) | ) | ||||||
| 
 | 
 | ||||||
| func PublishGitHub(ctx *cli.Context) error { | func PublishGithub(ctx *cli.Context) error { | ||||||
| 	token := os.Getenv("GH_TOKEN") | 	token := os.Getenv("GH_TOKEN") | ||||||
| 	f, err := getFlags(ctx) | 	f, err := getPublishGithubFlags(ctx) | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		return err | 		return err | ||||||
| 	} | 	} | ||||||
|  | @ -55,7 +55,7 @@ func PublishGitHub(ctx *cli.Context) error { | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	if f.dryRun { | 	if f.dryRun { | ||||||
| 		return runDryRun(f, token, ctx) | 		return runPublishGithubDryRun(f, token, ctx) | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	client := newGithubClient(ctx.Context, token) | 	client := newGithubClient(ctx.Context, token) | ||||||
|  | @ -99,7 +99,7 @@ func githubRepositoryClient(ctx context.Context, token string) githubRepositoryS | ||||||
| 	return client.Repositories | 	return client.Repositories | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func getFlags(ctx *cli.Context) (*publishGithubFlags, error) { | func getPublishGithubFlags(ctx *cli.Context) (*publishGithubFlags, error) { | ||||||
| 	metadata, err := GenerateMetadata(ctx) | 	metadata, err := GenerateMetadata(ctx) | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		return nil, err | 		return nil, err | ||||||
|  | @ -126,12 +126,12 @@ func getFlags(ctx *cli.Context) (*publishGithubFlags, error) { | ||||||
| 	}, nil | 	}, nil | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func runDryRun(f *publishGithubFlags, token string, ctx *cli.Context) error { | func runPublishGithubDryRun(f *publishGithubFlags, token string, ctx *cli.Context) error { | ||||||
| 	client := newGithubClient(ctx.Context, token) | 	client := newGithubClient(ctx.Context, token) | ||||||
| 	fmt.Println("Dry-Run: Retrieving release on repository by tag") | 	fmt.Println("Dry-Run: Retrieving release on repository by tag") | ||||||
| 	release, res, err := client.GetReleaseByTag(ctx.Context, f.repo.owner, f.repo.name, f.tag) | 	release, res, err := client.GetReleaseByTag(ctx.Context, f.repo.owner, f.repo.name, f.tag) | ||||||
| 	if err != nil && res.StatusCode != 404 { | 	if err != nil && res.StatusCode != 404 { | ||||||
| 		fmt.Println("Dry-Run: GitHub communication error:\n", err) | 		fmt.Println("Dry-Run: Github communication error:\n", err) | ||||||
| 		return nil | 		return nil | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -21,16 +21,16 @@ type githubPublishTestCases struct { | ||||||
| 	expectedError  error | 	expectedError  error | ||||||
| 	errorContains  string | 	errorContains  string | ||||||
| 	expectedOutput string | 	expectedOutput string | ||||||
| 	mockedService  *mockGitHubRepositoryServiceImpl | 	mockedService  *mockGithubRepositoryServiceImpl | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| var mockGitHubRepositoryService = &mockGitHubRepositoryServiceImpl{} | var mockGithubRepositoryService = &mockGithubRepositoryServiceImpl{} | ||||||
| 
 | 
 | ||||||
| func mockGithubRepositoryClient(context.Context, string) githubRepositoryService { | func mockGithubRepositoryClient(context.Context, string) githubRepositoryService { | ||||||
| 	return mockGitHubRepositoryService | 	return mockGithubRepositoryService | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func TestPublishGitHub(t *testing.T) { | func TestPublishGithub(t *testing.T) { | ||||||
| 	t.Setenv("DRONE_BUILD_EVENT", "promote") | 	t.Setenv("DRONE_BUILD_EVENT", "promote") | ||||||
| 	testApp, testPath := setupPublishGithubTests(t) | 	testApp, testPath := setupPublishGithubTests(t) | ||||||
| 	mockErrUnauthorized := errors.New("401") | 	mockErrUnauthorized := errors.New("401") | ||||||
|  | @ -49,21 +49,21 @@ func TestPublishGitHub(t *testing.T) { | ||||||
| 			name:          "try to publish with invalid token", | 			name:          "try to publish with invalid token", | ||||||
| 			token:         "invalid", | 			token:         "invalid", | ||||||
| 			args:          []string{"--path", testPath, "--repo", "test/test", "--tag", "v1.0.0"}, | 			args:          []string{"--path", testPath, "--repo", "test/test", "--tag", "v1.0.0"}, | ||||||
| 			mockedService: &mockGitHubRepositoryServiceImpl{tagErr: mockErrUnauthorized}, | 			mockedService: &mockGithubRepositoryServiceImpl{tagErr: mockErrUnauthorized}, | ||||||
| 			expectedError: mockErrUnauthorized, | 			expectedError: mockErrUnauthorized, | ||||||
| 		}, | 		}, | ||||||
| 		{ | 		{ | ||||||
| 			name:          "try to publish with valid token and nonexisting tag with create disabled", | 			name:          "try to publish with valid token and nonexisting tag with create disabled", | ||||||
| 			token:         "valid", | 			token:         "valid", | ||||||
| 			args:          []string{"--path", testPath, "--repo", "test/test", "--tag", "v1.0.0"}, | 			args:          []string{"--path", testPath, "--repo", "test/test", "--tag", "v1.0.0"}, | ||||||
| 			mockedService: &mockGitHubRepositoryServiceImpl{tagErr: errReleaseNotFound}, | 			mockedService: &mockGithubRepositoryServiceImpl{tagErr: errReleaseNotFound}, | ||||||
| 			expectedError: errReleaseNotFound, | 			expectedError: errReleaseNotFound, | ||||||
| 		}, | 		}, | ||||||
| 		{ | 		{ | ||||||
| 			name:          "try to publish with valid token and nonexisting tag with create enabled", | 			name:          "try to publish with valid token and nonexisting tag with create enabled", | ||||||
| 			token:         "valid", | 			token:         "valid", | ||||||
| 			args:          []string{"--path", testPath, "--repo", "test/test", "--tag", "v1.0.0", "--create"}, | 			args:          []string{"--path", testPath, "--repo", "test/test", "--tag", "v1.0.0", "--create"}, | ||||||
| 			mockedService: &mockGitHubRepositoryServiceImpl{tagErr: errReleaseNotFound}, | 			mockedService: &mockGithubRepositoryServiceImpl{tagErr: errReleaseNotFound}, | ||||||
| 		}, | 		}, | ||||||
| 		{ | 		{ | ||||||
| 			name:  "try to publish with valid token and existing tag", | 			name:  "try to publish with valid token and existing tag", | ||||||
|  | @ -74,21 +74,21 @@ func TestPublishGitHub(t *testing.T) { | ||||||
| 			name:           "dry run with invalid token", | 			name:           "dry run with invalid token", | ||||||
| 			token:          "invalid", | 			token:          "invalid", | ||||||
| 			args:           []string{"--dry-run", "--path", testPath, "--repo", "test/test", "--tag", "v1.0.0"}, | 			args:           []string{"--dry-run", "--path", testPath, "--repo", "test/test", "--tag", "v1.0.0"}, | ||||||
| 			mockedService:  &mockGitHubRepositoryServiceImpl{tagErr: mockErrUnauthorized}, | 			mockedService:  &mockGithubRepositoryServiceImpl{tagErr: mockErrUnauthorized}, | ||||||
| 			expectedOutput: "GitHub communication error", | 			expectedOutput: "Github communication error", | ||||||
| 		}, | 		}, | ||||||
| 		{ | 		{ | ||||||
| 			name:           "dry run with valid token and nonexisting tag with create disabled", | 			name:           "dry run with valid token and nonexisting tag with create disabled", | ||||||
| 			token:          "valid", | 			token:          "valid", | ||||||
| 			args:           []string{"--dry-run", "--path", testPath, "--repo", "test/test", "--tag", "v1.0.0"}, | 			args:           []string{"--dry-run", "--path", testPath, "--repo", "test/test", "--tag", "v1.0.0"}, | ||||||
| 			mockedService:  &mockGitHubRepositoryServiceImpl{tagErr: errReleaseNotFound}, | 			mockedService:  &mockGithubRepositoryServiceImpl{tagErr: errReleaseNotFound}, | ||||||
| 			expectedOutput: "Release doesn't exist", | 			expectedOutput: "Release doesn't exist", | ||||||
| 		}, | 		}, | ||||||
| 		{ | 		{ | ||||||
| 			name:           "dry run with valid token and nonexisting tag with create enabled", | 			name:           "dry run with valid token and nonexisting tag with create enabled", | ||||||
| 			token:          "valid", | 			token:          "valid", | ||||||
| 			args:           []string{"--dry-run", "--path", testPath, "--repo", "test/test", "--tag", "v1.0.0", "--create"}, | 			args:           []string{"--dry-run", "--path", testPath, "--repo", "test/test", "--tag", "v1.0.0", "--create"}, | ||||||
| 			mockedService:  &mockGitHubRepositoryServiceImpl{tagErr: errReleaseNotFound}, | 			mockedService:  &mockGithubRepositoryServiceImpl{tagErr: errReleaseNotFound}, | ||||||
| 			expectedOutput: "Would upload asset", | 			expectedOutput: "Would upload asset", | ||||||
| 		}, | 		}, | ||||||
| 		{ | 		{ | ||||||
|  | @ -116,9 +116,9 @@ func TestPublishGitHub(t *testing.T) { | ||||||
| 				t.Setenv("GH_TOKEN", test.token) | 				t.Setenv("GH_TOKEN", test.token) | ||||||
| 			} | 			} | ||||||
| 			if test.mockedService != nil { | 			if test.mockedService != nil { | ||||||
| 				mockGitHubRepositoryService = test.mockedService | 				mockGithubRepositoryService = test.mockedService | ||||||
| 			} else { | 			} else { | ||||||
| 				mockGitHubRepositoryService = &mockGitHubRepositoryServiceImpl{} | 				mockGithubRepositoryService = &mockGithubRepositoryServiceImpl{} | ||||||
| 			} | 			} | ||||||
| 			args := []string{"run"} | 			args := []string{"run"} | ||||||
| 			args = append(args, test.args...) | 			args = append(args, test.args...) | ||||||
|  | @ -154,7 +154,7 @@ func setupPublishGithubTests(t *testing.T) (*cli.App, string) { | ||||||
| 	newGithubClient = mockGithubRepositoryClient | 	newGithubClient = mockGithubRepositoryClient | ||||||
| 
 | 
 | ||||||
| 	testApp := cli.NewApp() | 	testApp := cli.NewApp() | ||||||
| 	testApp.Action = PublishGitHub | 	testApp.Action = PublishGithub | ||||||
| 	testApp.Flags = []cli.Flag{ | 	testApp.Flags = []cli.Flag{ | ||||||
| 		&dryRunFlag, | 		&dryRunFlag, | ||||||
| 		&cli.StringFlag{ | 		&cli.StringFlag{ | ||||||
|  | @ -165,7 +165,7 @@ func setupPublishGithubTests(t *testing.T) (*cli.App, string) { | ||||||
| 		&cli.StringFlag{ | 		&cli.StringFlag{ | ||||||
| 			Name:     "repo", | 			Name:     "repo", | ||||||
| 			Required: true, | 			Required: true, | ||||||
| 			Usage:    "GitHub repository", | 			Usage:    "Github repository", | ||||||
| 		}, | 		}, | ||||||
| 		&cli.StringFlag{ | 		&cli.StringFlag{ | ||||||
| 			Name:  "tag", | 			Name:  "tag", | ||||||
|  | @ -194,13 +194,13 @@ func captureStdout(t *testing.T, fn func() error) (string, error) { | ||||||
| 	return string(out), err | 	return string(out), err | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| type mockGitHubRepositoryServiceImpl struct { | type mockGithubRepositoryServiceImpl struct { | ||||||
| 	tagErr    error | 	tagErr    error | ||||||
| 	createErr error | 	createErr error | ||||||
| 	uploadErr error | 	uploadErr error | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func (m *mockGitHubRepositoryServiceImpl) GetReleaseByTag(ctx context.Context, owner string, repo string, tag string) (*github.RepositoryRelease, *github.Response, error) { | func (m *mockGithubRepositoryServiceImpl) GetReleaseByTag(ctx context.Context, owner string, repo string, tag string) (*github.RepositoryRelease, *github.Response, error) { | ||||||
| 	var release *github.RepositoryRelease | 	var release *github.RepositoryRelease | ||||||
| 	res := &github.Response{Response: &http.Response{}} | 	res := &github.Response{Response: &http.Response{}} | ||||||
| 	if m.tagErr == nil { | 	if m.tagErr == nil { | ||||||
|  | @ -212,12 +212,12 @@ func (m *mockGitHubRepositoryServiceImpl) GetReleaseByTag(ctx context.Context, o | ||||||
| 	return release, res, m.tagErr | 	return release, res, m.tagErr | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func (m *mockGitHubRepositoryServiceImpl) CreateRelease(ctx context.Context, owner string, repo string, release *github.RepositoryRelease) (*github.RepositoryRelease, *github.Response, error) { | func (m *mockGithubRepositoryServiceImpl) CreateRelease(ctx context.Context, owner string, repo string, release *github.RepositoryRelease) (*github.RepositoryRelease, *github.Response, error) { | ||||||
| 	releaseID := int64(1) | 	releaseID := int64(1) | ||||||
| 	return &github.RepositoryRelease{ID: &releaseID}, &github.Response{}, m.createErr | 	return &github.RepositoryRelease{ID: &releaseID}, &github.Response{}, m.createErr | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func (m *mockGitHubRepositoryServiceImpl) UploadReleaseAsset(ctx context.Context, owner string, repo string, id int64, opt *github.UploadOptions, file *os.File) (*github.ReleaseAsset, *github.Response, error) { | func (m *mockGithubRepositoryServiceImpl) UploadReleaseAsset(ctx context.Context, owner string, repo string, id int64, opt *github.UploadOptions, file *os.File) (*github.ReleaseAsset, *github.Response, error) { | ||||||
| 	assetName := "test" | 	assetName := "test" | ||||||
| 	assetUrl := "testurl.com.br" | 	assetUrl := "testurl.com.br" | ||||||
| 	return &github.ReleaseAsset{Name: &assetName, BrowserDownloadURL: &assetUrl}, &github.Response{}, m.uploadErr | 	return &github.ReleaseAsset{Name: &assetName, BrowserDownloadURL: &assetUrl}, &github.Response{}, m.uploadErr | ||||||
|  |  | ||||||
		Loading…
	
		Reference in New Issue