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