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/jmoiron/sqlx v1.3.5 | ||||
| 	github.com/matryer/is v1.4.0 | ||||
| 	github.com/parca-dev/parca v0.12.1 | ||||
| 	github.com/urfave/cli v1.22.9 | ||||
| 	go.etcd.io/etcd/api/v3 v3.5.4 | ||||
| 	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/bmatcuk/doublestar v1.1.1 // 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/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/drone-go v1.7.1 // 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/mapstructure v1.4.3 // 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/russross/blackfriday/v2 v2.1.0 // 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/coreos/go-semver v0.3.0 // 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/emirpasic/gods v1.12.0 // 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.4/go.mod h1:sx18RgvW6ABJ4iYUw7Q5x7bgFOAB9B6G7+yO0XBc4zw= | ||||
| 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-20190815185530-f2a389ac0a02/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+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/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.4.0 h1:El9xVISelRB7BuFusrZozjnkIM5YnzCViNKohAFqRJQ= | ||||
| 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.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/go.mod h1:BtxoFyWECRxE4U/7sNtV5W15zMzWCbyJoFRP3s7yZA0= | ||||
| 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 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-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-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-20220328201542-3ee0da9b0b42 h1:Gii5eqf+GmIEwGNKQYQClCayuJCe2/4fZUvF7VG99sU= | ||||
| k8s.io/kubernetes v1.13.0/go.mod h1:ocZa8+6APFNC2tX1DZASIbocyYT5jHzqFVsY5aoB7Jk= | ||||
|  |  | |||
|  | @ -225,7 +225,7 @@ func main() { | |||
| 				{ | ||||
| 					Name:   "github", | ||||
| 					Usage:  "Publish packages to GitHub releases", | ||||
| 					Action: PublishGitHub, | ||||
| 					Action: PublishGithub, | ||||
| 					Flags: []cli.Flag{ | ||||
| 						&dryRunFlag, | ||||
| 						&cli.StringFlag{ | ||||
|  | @ -240,7 +240,7 @@ func main() { | |||
| 						}, | ||||
| 						&cli.StringFlag{ | ||||
| 							Name:  "tag", | ||||
| 							Usage: "Release tag (default from metadata)ß", | ||||
| 							Usage: "Release tag (default from metadata)", | ||||
| 						}, | ||||
| 						&cli.BoolFlag{ | ||||
| 							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`) | ||||
| ) | ||||
| 
 | ||||
| func PublishGitHub(ctx *cli.Context) error { | ||||
| func PublishGithub(ctx *cli.Context) error { | ||||
| 	token := os.Getenv("GH_TOKEN") | ||||
| 	f, err := getFlags(ctx) | ||||
| 	f, err := getPublishGithubFlags(ctx) | ||||
| 	if err != nil { | ||||
| 		return err | ||||
| 	} | ||||
|  | @ -55,7 +55,7 @@ func PublishGitHub(ctx *cli.Context) error { | |||
| 	} | ||||
| 
 | ||||
| 	if f.dryRun { | ||||
| 		return runDryRun(f, token, ctx) | ||||
| 		return runPublishGithubDryRun(f, token, ctx) | ||||
| 	} | ||||
| 
 | ||||
| 	client := newGithubClient(ctx.Context, token) | ||||
|  | @ -99,7 +99,7 @@ func githubRepositoryClient(ctx context.Context, token string) githubRepositoryS | |||
| 	return client.Repositories | ||||
| } | ||||
| 
 | ||||
| func getFlags(ctx *cli.Context) (*publishGithubFlags, error) { | ||||
| func getPublishGithubFlags(ctx *cli.Context) (*publishGithubFlags, error) { | ||||
| 	metadata, err := GenerateMetadata(ctx) | ||||
| 	if err != nil { | ||||
| 		return nil, err | ||||
|  | @ -126,12 +126,12 @@ func getFlags(ctx *cli.Context) (*publishGithubFlags, error) { | |||
| 	}, 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) | ||||
| 	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) | ||||
| 	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 | ||||
| 	} | ||||
| 
 | ||||
|  |  | |||
|  | @ -21,16 +21,16 @@ type githubPublishTestCases struct { | |||
| 	expectedError  error | ||||
| 	errorContains  string | ||||
| 	expectedOutput string | ||||
| 	mockedService  *mockGitHubRepositoryServiceImpl | ||||
| 	mockedService  *mockGithubRepositoryServiceImpl | ||||
| } | ||||
| 
 | ||||
| var mockGitHubRepositoryService = &mockGitHubRepositoryServiceImpl{} | ||||
| var mockGithubRepositoryService = &mockGithubRepositoryServiceImpl{} | ||||
| 
 | ||||
| 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") | ||||
| 	testApp, testPath := setupPublishGithubTests(t) | ||||
| 	mockErrUnauthorized := errors.New("401") | ||||
|  | @ -49,21 +49,21 @@ func TestPublishGitHub(t *testing.T) { | |||
| 			name:          "try to publish with invalid token", | ||||
| 			token:         "invalid", | ||||
| 			args:          []string{"--path", testPath, "--repo", "test/test", "--tag", "v1.0.0"}, | ||||
| 			mockedService: &mockGitHubRepositoryServiceImpl{tagErr: mockErrUnauthorized}, | ||||
| 			mockedService: &mockGithubRepositoryServiceImpl{tagErr: mockErrUnauthorized}, | ||||
| 			expectedError: mockErrUnauthorized, | ||||
| 		}, | ||||
| 		{ | ||||
| 			name:          "try to publish with valid token and nonexisting tag with create disabled", | ||||
| 			token:         "valid", | ||||
| 			args:          []string{"--path", testPath, "--repo", "test/test", "--tag", "v1.0.0"}, | ||||
| 			mockedService: &mockGitHubRepositoryServiceImpl{tagErr: errReleaseNotFound}, | ||||
| 			mockedService: &mockGithubRepositoryServiceImpl{tagErr: errReleaseNotFound}, | ||||
| 			expectedError: errReleaseNotFound, | ||||
| 		}, | ||||
| 		{ | ||||
| 			name:          "try to publish with valid token and nonexisting tag with create enabled", | ||||
| 			token:         "valid", | ||||
| 			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", | ||||
|  | @ -74,21 +74,21 @@ func TestPublishGitHub(t *testing.T) { | |||
| 			name:           "dry run with invalid token", | ||||
| 			token:          "invalid", | ||||
| 			args:           []string{"--dry-run", "--path", testPath, "--repo", "test/test", "--tag", "v1.0.0"}, | ||||
| 			mockedService:  &mockGitHubRepositoryServiceImpl{tagErr: mockErrUnauthorized}, | ||||
| 			expectedOutput: "GitHub communication error", | ||||
| 			mockedService:  &mockGithubRepositoryServiceImpl{tagErr: mockErrUnauthorized}, | ||||
| 			expectedOutput: "Github communication error", | ||||
| 		}, | ||||
| 		{ | ||||
| 			name:           "dry run with valid token and nonexisting tag with create disabled", | ||||
| 			token:          "valid", | ||||
| 			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", | ||||
| 		}, | ||||
| 		{ | ||||
| 			name:           "dry run with valid token and nonexisting tag with create enabled", | ||||
| 			token:          "valid", | ||||
| 			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", | ||||
| 		}, | ||||
| 		{ | ||||
|  | @ -116,9 +116,9 @@ func TestPublishGitHub(t *testing.T) { | |||
| 				t.Setenv("GH_TOKEN", test.token) | ||||
| 			} | ||||
| 			if test.mockedService != nil { | ||||
| 				mockGitHubRepositoryService = test.mockedService | ||||
| 				mockGithubRepositoryService = test.mockedService | ||||
| 			} else { | ||||
| 				mockGitHubRepositoryService = &mockGitHubRepositoryServiceImpl{} | ||||
| 				mockGithubRepositoryService = &mockGithubRepositoryServiceImpl{} | ||||
| 			} | ||||
| 			args := []string{"run"} | ||||
| 			args = append(args, test.args...) | ||||
|  | @ -154,7 +154,7 @@ func setupPublishGithubTests(t *testing.T) (*cli.App, string) { | |||
| 	newGithubClient = mockGithubRepositoryClient | ||||
| 
 | ||||
| 	testApp := cli.NewApp() | ||||
| 	testApp.Action = PublishGitHub | ||||
| 	testApp.Action = PublishGithub | ||||
| 	testApp.Flags = []cli.Flag{ | ||||
| 		&dryRunFlag, | ||||
| 		&cli.StringFlag{ | ||||
|  | @ -165,7 +165,7 @@ func setupPublishGithubTests(t *testing.T) (*cli.App, string) { | |||
| 		&cli.StringFlag{ | ||||
| 			Name:     "repo", | ||||
| 			Required: true, | ||||
| 			Usage:    "GitHub repository", | ||||
| 			Usage:    "Github repository", | ||||
| 		}, | ||||
| 		&cli.StringFlag{ | ||||
| 			Name:  "tag", | ||||
|  | @ -194,13 +194,13 @@ func captureStdout(t *testing.T, fn func() error) (string, error) { | |||
| 	return string(out), err | ||||
| } | ||||
| 
 | ||||
| type mockGitHubRepositoryServiceImpl struct { | ||||
| type mockGithubRepositoryServiceImpl struct { | ||||
| 	tagErr    error | ||||
| 	createErr 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 | ||||
| 	res := &github.Response{Response: &http.Response{}} | ||||
| 	if m.tagErr == nil { | ||||
|  | @ -212,12 +212,12 @@ func (m *mockGitHubRepositoryServiceImpl) GetReleaseByTag(ctx context.Context, o | |||
| 	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) | ||||
| 	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" | ||||
| 	assetUrl := "testurl.com.br" | ||||
| 	return &github.ReleaseAsset{Name: &assetName, BrowserDownloadURL: &assetUrl}, &github.Response{}, m.uploadErr | ||||
|  |  | |||
		Loading…
	
		Reference in New Issue