mirror of
https://github.com/grafana/grafana.git
synced 2024-12-01 13:09:22 -06:00
7e816d010c
* Update references * Update grabpl to v3.0.38
323 lines
8.5 KiB
Go
323 lines
8.5 KiB
Go
package main
|
|
|
|
import (
|
|
"bytes"
|
|
"encoding/json"
|
|
"fmt"
|
|
"io"
|
|
"log"
|
|
"net/http"
|
|
"net/url"
|
|
"os"
|
|
"path"
|
|
"path/filepath"
|
|
"strings"
|
|
"time"
|
|
|
|
"github.com/urfave/cli/v2"
|
|
|
|
"github.com/grafana/grafana/pkg/build/config"
|
|
"github.com/grafana/grafana/pkg/build/gcloud"
|
|
"github.com/grafana/grafana/pkg/build/gcloud/storage"
|
|
"github.com/grafana/grafana/pkg/build/packaging"
|
|
)
|
|
|
|
const grafanaAPI = "https://grafana.com/api"
|
|
|
|
// GrafanaCom implements the sub-command "grafana-com".
|
|
func GrafanaCom(c *cli.Context) error {
|
|
bucketStr := c.String("src-bucket")
|
|
edition := config.Edition(c.String("edition"))
|
|
|
|
if err := gcloud.ActivateServiceAccount(); err != nil {
|
|
return fmt.Errorf("couldn't activate service account, err: %w", err)
|
|
}
|
|
|
|
metadata, err := config.GenerateMetadata(c)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
releaseMode, err := metadata.GetReleaseMode()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
version := metadata.GrafanaVersion
|
|
if releaseMode.Mode == config.Cronjob {
|
|
gcs, err := storage.New()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
bucket := gcs.Bucket(bucketStr)
|
|
latestMainVersion, err := storage.GetLatestMainBuild(c.Context, bucket, filepath.Join(string(edition), "main"))
|
|
if err != nil {
|
|
return err
|
|
}
|
|
version = latestMainVersion
|
|
}
|
|
|
|
dryRun := c.Bool("dry-run")
|
|
simulateRelease := c.Bool("simulate-release")
|
|
// Test release mode and dryRun imply simulateRelease
|
|
if releaseMode.IsTest || dryRun {
|
|
simulateRelease = true
|
|
}
|
|
|
|
grafanaAPIKey := strings.TrimSpace(os.Getenv("GRAFANA_COM_API_KEY"))
|
|
if grafanaAPIKey == "" {
|
|
return cli.Exit("the environment variable GRAFANA_COM_API_KEY must be set", 1)
|
|
}
|
|
whatsNewURL, releaseNotesURL, err := getReleaseURLs()
|
|
if err != nil {
|
|
return cli.Exit(err.Error(), 1)
|
|
}
|
|
|
|
// TODO: Verify config values
|
|
cfg := packaging.PublishConfig{
|
|
Config: config.Config{
|
|
Version: version,
|
|
},
|
|
Edition: edition,
|
|
ReleaseMode: releaseMode,
|
|
GrafanaAPIKey: grafanaAPIKey,
|
|
WhatsNewURL: whatsNewURL,
|
|
ReleaseNotesURL: releaseNotesURL,
|
|
DryRun: dryRun,
|
|
TTL: c.String("ttl"),
|
|
SimulateRelease: simulateRelease,
|
|
}
|
|
|
|
if err := publishPackages(cfg); err != nil {
|
|
return cli.Exit(err.Error(), 1)
|
|
}
|
|
|
|
log.Println("Successfully published packages to grafana.com!")
|
|
return nil
|
|
}
|
|
|
|
func getReleaseURLs() (string, string, error) {
|
|
type grafanaConf struct {
|
|
WhatsNewURL string `json:"whatsNewUrl"`
|
|
ReleaseNotesURL string `json:"releaseNotesUrl"`
|
|
}
|
|
type packageConf struct {
|
|
Grafana grafanaConf `json:"grafana"`
|
|
}
|
|
|
|
pkgB, err := os.ReadFile("package.json")
|
|
if err != nil {
|
|
return "", "", fmt.Errorf("failed to read package.json: %w", err)
|
|
}
|
|
|
|
var pconf packageConf
|
|
if err := json.Unmarshal(pkgB, &pconf); err != nil {
|
|
return "", "", fmt.Errorf("failed to decode package.json: %w", err)
|
|
}
|
|
if _, err := url.ParseRequestURI(pconf.Grafana.WhatsNewURL); err != nil {
|
|
return "", "", fmt.Errorf("grafana.whatsNewUrl is invalid in package.json: %q", pconf.Grafana.WhatsNewURL)
|
|
}
|
|
if _, err := url.ParseRequestURI(pconf.Grafana.ReleaseNotesURL); err != nil {
|
|
return "", "", fmt.Errorf("grafana.releaseNotesUrl is invalid in package.json: %q",
|
|
pconf.Grafana.ReleaseNotesURL)
|
|
}
|
|
|
|
return pconf.Grafana.WhatsNewURL, pconf.Grafana.ReleaseNotesURL, nil
|
|
}
|
|
|
|
// publishPackages publishes packages to grafana.com.
|
|
func publishPackages(cfg packaging.PublishConfig) error {
|
|
log.Printf("Publishing Grafana packages, version %s, %s edition, %s mode, dryRun: %v, simulating: %v...\n",
|
|
cfg.Version, cfg.Edition, cfg.ReleaseMode.Mode, cfg.DryRun, cfg.SimulateRelease)
|
|
|
|
versionStr := fmt.Sprintf("v%s", cfg.Version)
|
|
log.Printf("Creating release %s at grafana.com...\n", versionStr)
|
|
|
|
var sfx string
|
|
var pth string
|
|
switch cfg.Edition {
|
|
case config.EditionOSS:
|
|
pth = "oss"
|
|
case config.EditionEnterprise:
|
|
pth = "enterprise"
|
|
sfx = packaging.EnterpriseSfx
|
|
default:
|
|
return fmt.Errorf("unrecognized edition %q", cfg.Edition)
|
|
}
|
|
|
|
switch cfg.ReleaseMode.Mode {
|
|
case config.MainMode, config.DownstreamMode, config.CronjobMode:
|
|
pth = path.Join(pth, packaging.MainFolder)
|
|
default:
|
|
pth = path.Join(pth, packaging.ReleaseFolder)
|
|
}
|
|
|
|
product := fmt.Sprintf("grafana%s", sfx)
|
|
pth = path.Join(pth, product)
|
|
baseArchiveURL := fmt.Sprintf("https://dl.grafana.com/%s", pth)
|
|
|
|
var builds []buildRepr
|
|
for _, ba := range packaging.ArtifactConfigs {
|
|
u := ba.GetURL(baseArchiveURL, cfg)
|
|
|
|
sha256, err := getSHA256(u)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
builds = append(builds, buildRepr{
|
|
OS: ba.Os,
|
|
URL: u,
|
|
SHA256: string(sha256),
|
|
Arch: ba.Arch,
|
|
})
|
|
}
|
|
|
|
r := releaseRepr{
|
|
Version: cfg.Version,
|
|
ReleaseDate: time.Now().UTC(),
|
|
Builds: builds,
|
|
Stable: cfg.ReleaseMode.Mode == config.TagMode && !cfg.ReleaseMode.IsPreview && !cfg.ReleaseMode.IsTest,
|
|
Beta: cfg.ReleaseMode.IsPreview,
|
|
Nightly: cfg.ReleaseMode.Mode == config.CronjobMode,
|
|
}
|
|
if cfg.ReleaseMode.Mode == config.TagMode || r.Beta {
|
|
r.WhatsNewURL = cfg.WhatsNewURL
|
|
r.ReleaseNotesURL = cfg.ReleaseNotesURL
|
|
}
|
|
|
|
if err := postRequest(cfg, "versions", r, fmt.Sprintf("create release %s", r.Version)); err != nil {
|
|
return err
|
|
}
|
|
|
|
if err := postRequest(cfg, fmt.Sprintf("versions/%s", cfg.Version), r,
|
|
fmt.Sprintf("update release %s", cfg.Version)); err != nil {
|
|
return err
|
|
}
|
|
|
|
for _, b := range r.Builds {
|
|
if err := postRequest(cfg, fmt.Sprintf("versions/%s/packages", cfg.Version), b,
|
|
fmt.Sprintf("create build %s %s", b.OS, b.Arch)); err != nil {
|
|
return err
|
|
}
|
|
if err := postRequest(cfg, fmt.Sprintf("versions/%s/packages/%s/%s", cfg.Version, b.Arch, b.OS), b,
|
|
fmt.Sprintf("update build %s %s", b.OS, b.Arch)); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func getSHA256(u string) ([]byte, error) {
|
|
shaURL := fmt.Sprintf("%s.sha256", u)
|
|
// nolint:gosec
|
|
resp, err := http.Get(shaURL)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
defer func() {
|
|
if err := resp.Body.Close(); err != nil {
|
|
log.Println("failed to close response body, err: %w", err)
|
|
}
|
|
}()
|
|
if resp.StatusCode < 200 || resp.StatusCode >= 300 {
|
|
return nil, fmt.Errorf("failed downloading %s: %s", u, resp.Status)
|
|
}
|
|
|
|
sha256, err := io.ReadAll(resp.Body)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return sha256, nil
|
|
}
|
|
|
|
func postRequest(cfg packaging.PublishConfig, pth string, obj interface{}, descr string) error {
|
|
var sfx string
|
|
switch cfg.Edition {
|
|
case config.EditionOSS:
|
|
case config.EditionEnterprise:
|
|
sfx = packaging.EnterpriseSfx
|
|
default:
|
|
return fmt.Errorf("unrecognized edition %q", cfg.Edition)
|
|
}
|
|
product := fmt.Sprintf("grafana%s", sfx)
|
|
|
|
jsonB, err := json.Marshal(obj)
|
|
if err != nil {
|
|
return fmt.Errorf("failed to JSON encode release: %w", err)
|
|
}
|
|
|
|
u, err := constructURL(product, pth)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
req, err := http.NewRequest(http.MethodPost, u, bytes.NewReader(jsonB))
|
|
if err != nil {
|
|
return err
|
|
}
|
|
req.Header.Add("Authorization", fmt.Sprintf("Bearer %s", cfg.GrafanaAPIKey))
|
|
req.Header.Add("Content-Type", "application/json")
|
|
|
|
log.Printf("Posting to grafana.com API, %s - JSON: %s\n", u, string(jsonB))
|
|
if cfg.SimulateRelease {
|
|
log.Println("Only simulating request")
|
|
return nil
|
|
}
|
|
|
|
resp, err := http.DefaultClient.Do(req)
|
|
if err != nil {
|
|
return fmt.Errorf("failed posting to %s (%s): %s", u, descr, err)
|
|
}
|
|
defer func() {
|
|
if err := resp.Body.Close(); err != nil {
|
|
log.Println("failed to close response body, err: %w", err)
|
|
}
|
|
}()
|
|
if resp.StatusCode < 200 || resp.StatusCode >= 300 {
|
|
body, err := io.ReadAll(resp.Body)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
if strings.Contains(string(body), "already exists") || strings.Contains(string(body), "Nothing to update") {
|
|
log.Printf("Already exists: %s\n", descr)
|
|
return nil
|
|
}
|
|
|
|
return fmt.Errorf("failed posting to %s (%s): %s", u, descr, resp.Status)
|
|
}
|
|
|
|
log.Printf("Successfully posted to grafana.com API, %s\n", u)
|
|
|
|
return nil
|
|
}
|
|
|
|
func constructURL(product string, pth string) (string, error) {
|
|
productPath := filepath.Clean(filepath.Join("/", product, pth))
|
|
u, err := url.Parse(grafanaAPI)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
u.Path = path.Join(u.Path, productPath)
|
|
return u.String(), err
|
|
}
|
|
|
|
type buildRepr struct {
|
|
OS string `json:"os"`
|
|
URL string `json:"url"`
|
|
SHA256 string `json:"sha256"`
|
|
Arch string `json:"arch"`
|
|
}
|
|
|
|
type releaseRepr struct {
|
|
Version string `json:"version"`
|
|
ReleaseDate time.Time `json:"releaseDate"`
|
|
Stable bool `json:"stable"`
|
|
Beta bool `json:"beta"`
|
|
Nightly bool `json:"nightly"`
|
|
WhatsNewURL string `json:"whatsNewUrl"`
|
|
ReleaseNotesURL string `json:"releaseNotesUrl"`
|
|
Builds []buildRepr `json:"-"`
|
|
}
|