mirror of
https://github.com/grafana/grafana.git
synced 2024-12-28 18:01:40 -06:00
f73cdc5e80
* Move and rename genversions.go and tests * Fix lint - bring back cli.Exit * Move package.json and fix tests * Add necessary env vars for promote event
309 lines
9.8 KiB
Go
309 lines
9.8 KiB
Go
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/grafana/grafana/pkg/build/config"
|
|
"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 := config.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)
|
|
}
|