Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add gcom publish command #191

Merged
merged 8 commits into from
Oct 2, 2023
Merged
Show file tree
Hide file tree
Changes from 7 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 18 additions & 0 deletions cmd/flags.go
Original file line number Diff line number Diff line change
Expand Up @@ -324,6 +324,24 @@ var DefaultFlags = []cli.Flag{
},
}

var GCOMFlags = []cli.Flag{
&cli.StringFlag{
Name: "url",
Usage: "URL used in requests to grafana.com",
Value: "https://grafana.com",
},
&cli.StringFlag{
Name: "api-key",
Usage: "API Key used in requests to grafana.com",
Required: true,
},
&cli.StringFlag{
Name: "download-url",
Usage: "URL used to download packages from grafana.com",
Required: true,
},
}

// JoinFlags combines several slices of flags into one slice of flags.
func JoinFlags(f ...[]cli.Flag) []cli.Flag {
flags := []cli.Flag{}
Expand Down
11 changes: 11 additions & 0 deletions cmd/gcom.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package main

import (
"github.com/urfave/cli/v2"
)

var GCOMCommand = &cli.Command{
Name: "gcom",
Description: "Executes requests to grafana.com",
Subcommands: []*cli.Command{GCOMPublishCommand},
}
18 changes: 18 additions & 0 deletions cmd/gcom_publish.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
package main

import (
"github.com/grafana/grafana-build/pipelines"
"github.com/urfave/cli/v2"
)

var GCOMPublishCommand = &cli.Command{
Name: "publish",
Action: PipelineActionWithPackageInput(pipelines.PublishGCOM),
Description: "Publishes a grafana.tar.gz (ideally one built using the 'package' command) to grafana.com (--destination will be the download path)",
Flags: JoinFlagsWithDefault(
GCOMFlags,
PackageInputFlags,
PublishFlags,
ConcurrencyFlags,
),
}
1 change: 1 addition & 0 deletions cmd/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ var app = &cli.App{
ProImageCommand,
StorybookCommand,
NPMCommand,
GCOMCommand,
},
}

Expand Down
57 changes: 57 additions & 0 deletions containers/gcom_publish.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
package containers

import (
"context"
"encoding/json"
"fmt"

"dagger.io/dagger"
)

type GCOMVersionPayload struct {
Version string `json:"version"` // "10.0.3"
ReleaseDate string `json:"releaseDate"` // "2023-07-26T08:20:16.628278891Z"
Stable bool `json:"stable"` // true
Beta bool `json:"beta"` // false
Nightly bool `json:"nightly"` // false
WhatsNewURL string `json:"whatsNewUrl"` // "https://grafana.com/docs/grafana/next/whatsnew/whats-new-in-v10-0/"
ReleaseNotesURL string `json:"releaseNotesUrl"` // "https://grafana.com/docs/grafana/next/release-notes/"
}

type GCOMPackagePayload struct {
OS string `json:"os"` // "deb"
URL string `json:"url"` // "https://dl.grafana.com/oss/release/grafana_10.0.3_arm64.deb"
Sha256 string `json:"sha256"` // "78a718816dd556198cfa3007dd594aaf1d80886decae8c4bd0f615bd3f118279\n"
Arch string `json:"arch"` // "arm64"
}

// PublishGCOM publishes a package to grafana.com.
func PublishGCOM(ctx context.Context, d *dagger.Client, versionPayload *GCOMVersionPayload, packagePayload *GCOMPackagePayload, opts *GCOMOpts) error {
versionApiUrl := fmt.Sprintf("%s/api/grafana/versions", opts.URL)
packagesApiUrl := fmt.Sprintf("%s/api/grafana/versions/%s/packages", opts.URL, versionPayload.Version)

jsonVersionPayload, err := json.Marshal(versionPayload)
if err != nil {
return err
}

jsonPackagePayload, err := json.Marshal(packagePayload)
if err != nil {
return err
}

apiKeySecret := d.SetSecret("gcom-api-key", opts.ApiKey)

_, err = d.Container().From("alpine").
WithSecretVariable("GCOM_API_KEY", apiKeySecret).
WithExec([]string{"apk", "add", "curl"}).
guicaulada marked this conversation as resolved.
Show resolved Hide resolved
WithExec([]string{"/bin/sh", "-c", fmt.Sprintf(`curl -H "Content-Type: application/json" -H "Authorization: Bearer $GCOM_API_KEY" -d '%s' %s`, string(jsonVersionPayload), versionApiUrl)}).
WithExec([]string{"/bin/sh", "-c", fmt.Sprintf(`curl -H "Content-Type: application/json" -H "Authorization: Bearer $GCOM_API_KEY" -d '%s' %s`, string(jsonPackagePayload), packagesApiUrl)}).
Sync(ctx)

if err != nil {
return err
}

return nil
}
18 changes: 18 additions & 0 deletions containers/opts_gcom.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
package containers

import "github.com/grafana/grafana-build/cliutil"

// GCOMOpts are options used when making requests to grafana.com.
type GCOMOpts struct {
URL string
ApiKey string
DownloadURL string
}

func GCOMOptsFromFlags(c cliutil.CLIContext) *GCOMOpts {
return &GCOMOpts{
URL: c.String("url"),
ApiKey: c.String("api-key"),
DownloadURL: c.String("download-url"),
}
}
7 changes: 1 addition & 6 deletions containers/publish.go
Original file line number Diff line number Diff line change
Expand Up @@ -80,12 +80,7 @@ func PublishFile(ctx context.Context, d *dagger.Client, opts *PublishFileOpts) (
if publishOpts.Checksum {
name := destination + ".sha256"
log.Println("Checksum is enabled, creating checksum", name)
files[name] = d.Container().
From("busybox").
WithEnvVariable("DESTINATION", destination).
WithFile("/src/file", file).
WithExec([]string{"/bin/sh", "-c", "sha256sum /src/file | awk '{print $1}' > /src/file.sha256"}).
File("/src/file.sha256")
files[name] = Sha256(d, file)
}

for dst, f := range files {
Expand Down
16 changes: 16 additions & 0 deletions containers/sha256.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
package containers

import (
"time"

"dagger.io/dagger"
)

// Sha256 returns a dagger.File which contains the sha256 for the provided file.
func Sha256(d *dagger.Client, file *dagger.File) *dagger.File {
return d.Container().From("busybox").
WithEnvVariable("CACHE_DISABLE", time.Now().String()).
WithFile("/src/file", file).
WithExec([]string{"/bin/sh", "-c", "sha256sum /src/file | awk '{print $1}' > /src/file.sha256"}).
File("/src/file.sha256")
}
128 changes: 128 additions & 0 deletions pipelines/gcom_publish.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,128 @@
package pipelines

import (
"context"
"fmt"
"log"
"path/filepath"
"strings"
"time"

"dagger.io/dagger"
"github.com/grafana/grafana-build/containers"
"github.com/grafana/grafana-build/executil"
"golang.org/x/sync/errgroup"
"golang.org/x/sync/semaphore"
)

func VersionPayloadFromFileName(name string) *containers.GCOMVersionPayload {
var (
opts = TarOptsFromFileName(name)
splitVersion = strings.Split(opts.Version, ".")
stable = true
nightly = false
beta = false
)

if strings.Contains(opts.Version, "-") {
stable = false
beta = true
}
if strings.Contains(opts.Version, "nightly") {
beta = false
nightly = true
}

return &containers.GCOMVersionPayload{
Version: opts.Version,
ReleaseDate: time.Now().Format(time.RFC3339Nano),
Stable: stable,
Beta: beta,
Nightly: nightly,
WhatsNewURL: fmt.Sprintf("https://grafana.com/docs/grafana/next/whatsnew/whats-new-in-v%s-%s/", splitVersion[0], splitVersion[1]),
ReleaseNotesURL: "https://grafana.com/docs/grafana/next/release-notes/",
}
}

func PackagePayloadFromFile(ctx context.Context, d *dagger.Client, name string, file *dagger.File, opts *containers.GCOMOpts) (*containers.GCOMPackagePayload, error) {
tarOpts := TarOptsFromFileName(name)
ext := filepath.Ext(name)
os, _ := executil.OSAndArch(tarOpts.Distro)
arch := strings.ReplaceAll(executil.FullArch(tarOpts.Distro), "/", "")

if os == "windows" {
os = "win"
}

if ext == ".deb" {
os = "deb"
}
if ext == ".rpm" {
os = "rhel"
}
if ext == ".exe" {
os = "win-installer"
}

sha256, err := containers.Sha256(d, file).Contents(ctx)
if err != nil {
return nil, err
}

return &containers.GCOMPackagePayload{
OS: os,
URL: fmt.Sprintf("%s/%s", opts.DownloadURL, name),
Sha256: sha256,
Arch: arch,
}, nil
}

func PublishGCOM(ctx context.Context, d *dagger.Client, args PipelineArgs) error {
var (
opts = args.GCOMOpts
wg = &errgroup.Group{}
sm = semaphore.NewWeighted(args.ConcurrencyOpts.Parallel)
)

packages, err := containers.GetPackages(ctx, d, args.PackageInputOpts, args.GCPOpts)
if err != nil {
return err
}

// Extract the package(s)
for i, name := range args.PackageInputOpts.Packages {
wg.Go(PublishGCOMFunc(ctx, sm, d, opts, name, packages[i]))
}
return wg.Wait()
}

func PublishGCOMFunc(ctx context.Context, sm *semaphore.Weighted, d *dagger.Client, opts *containers.GCOMOpts, path string, file *dagger.File) func() error {
return func() error {
name := filepath.Base(path)
log.Printf("[%s] Attempting to publish package", name)
log.Printf("[%s] Acquiring semaphore", name)
if err := sm.Acquire(ctx, 1); err != nil {
return fmt.Errorf("failed to acquire semaphore: %w", err)
}
defer sm.Release(1)
log.Printf("[%s] Acquired semaphore", name)

log.Printf("[%s] Building version payload", name)
versionPayload := VersionPayloadFromFileName(name)

log.Printf("[%s] Building package payload", name)
packagePayload, err := PackagePayloadFromFile(ctx, d, name, file, opts)
if err != nil {
return fmt.Errorf("[%s] error: %w", name, err)
}

log.Printf("[%s] Publishing package", name)
err = containers.PublishGCOM(ctx, d, versionPayload, packagePayload, opts)
if err != nil {
return fmt.Errorf("[%s] error: %w", name, err)
}

log.Printf("[%s] Done publishing package", name)
return nil
}
}
4 changes: 4 additions & 0 deletions pipelines/pipeline_args.go
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,9 @@ type PipelineArgs struct {

// NPMOpts will be populated if NPMFlags are enabled on the current sub-command.
NPMOpts *containers.NPMOpts

// GCOMOpts will be populated if GCOMFlags are enabled on the current sub-command.
GCOMOpts *containers.GCOMOpts
}

// PipelineArgsFromContext populates a pipelines.PipelineArgs from a CLI context.
Expand Down Expand Up @@ -89,6 +92,7 @@ func PipelineArgsFromContext(ctx context.Context, c cliutil.CLIContext) (Pipelin
ConcurrencyOpts: ConcurrencyOptsFromFlags(c),
ProImageOpts: containers.ProImageOptsFromFlags(c),
NPMOpts: containers.NPMOptsFromFlags(c),
GCOMOpts: containers.GCOMOptsFromFlags(c),
}, nil
}

Expand Down
9 changes: 8 additions & 1 deletion scripts/drone_publish_nightly_grafana.sh
Original file line number Diff line number Diff line change
Expand Up @@ -41,4 +41,11 @@ dagger run --silent go run ./cmd cdn \
dagger run --silent go run ./cmd npm publish \
$(find $local_dir | grep tar.gz | grep linux | grep amd64 | grep -v sha256 | grep -v docker | awk '{print "--package=file://"$0}') \
--token=${NPM_TOKEN} \
--tag="nightly"
--tag="nightly" || true

# Publish packages to grafana.com
dagger run --silent go run ./cmd gcom publish \
$(find $local_dir | grep -e .rpm -e .tar.gz -e .exe -e .zip -e .deb | grep -v sha256 | awk '{print "--package=file://"$0}') \
--api-key=${GCOM_API_KEY} \
--url="https://grafana-dev.com" \
--download-url="https://dl.grafana.com/oss/release"
Loading