mirror of https://github.com/grafana/grafana
Merge pull request #13508 from grafana/release_publisher
build: automatically publish releases to grafana.com.pull/13511/head
commit
99f2342fa1
@ -0,0 +1,14 @@ |
|||||||
|
#/bin/bash |
||||||
|
|
||||||
|
# no relation to publish.go |
||||||
|
|
||||||
|
# Right now we hack this in into the publish script. |
||||||
|
# Eventually we might want to keep a list of all previous releases somewhere. |
||||||
|
_releaseNoteUrl="https://community.grafana.com/t/release-notes-v5-3-x/10244" |
||||||
|
_whatsNewUrl="http://docs.grafana.org/guides/whats-new-in-v5-3/" |
||||||
|
|
||||||
|
./release_publisher/release_publisher \ |
||||||
|
--wn ${_whatsNewUrl} \ |
||||||
|
--rn ${_releaseNoteUrl} \ |
||||||
|
--version ${CIRCLE_TAG} \ |
||||||
|
--apikey ${GRAFANA_COM_API_KEY} |
@ -0,0 +1,40 @@ |
|||||||
|
package main |
||||||
|
|
||||||
|
import ( |
||||||
|
"flag" |
||||||
|
"fmt" |
||||||
|
"log" |
||||||
|
"os" |
||||||
|
) |
||||||
|
|
||||||
|
var baseUri string = "https://grafana.com/api" |
||||||
|
|
||||||
|
func main() { |
||||||
|
var version string |
||||||
|
var whatsNewUrl string |
||||||
|
var releaseNotesUrl string |
||||||
|
var dryRun bool |
||||||
|
var apiKey string |
||||||
|
|
||||||
|
flag.StringVar(&version, "version", "", "Grafana version (ex: --version v5.2.0-beta1)") |
||||||
|
flag.StringVar(&whatsNewUrl, "wn", "", "What's new url (ex: --wn http://docs.grafana.org/guides/whats-new-in-v5-2/)") |
||||||
|
flag.StringVar(&releaseNotesUrl, "rn", "", "Grafana version (ex: --rn https://community.grafana.com/t/release-notes-v5-2-x/7894)") |
||||||
|
flag.StringVar(&apiKey, "apikey", "", "Grafana.com API key (ex: --apikey ABCDEF)") |
||||||
|
flag.BoolVar(&dryRun, "dry-run", false, "--dry-run") |
||||||
|
flag.Parse() |
||||||
|
|
||||||
|
if len(os.Args) == 1 { |
||||||
|
fmt.Println("Usage: go run publisher.go main.go --version <v> --wn <what's new url> --rn <release notes url> --apikey <api key> --dry-run false") |
||||||
|
fmt.Println("example: go run publisher.go main.go --version v5.2.0-beta2 --wn http://docs.grafana.org/guides/whats-new-in-v5-2/ --rn https://community.grafana.com/t/release-notes-v5-2-x/7894 --apikey ASDF123 --dry-run true") |
||||||
|
os.Exit(1) |
||||||
|
} |
||||||
|
|
||||||
|
if dryRun { |
||||||
|
log.Println("Dry-run has been enabled.") |
||||||
|
} |
||||||
|
|
||||||
|
p := publisher{apiKey: apiKey} |
||||||
|
if err := p.doRelease(version, whatsNewUrl, releaseNotesUrl, dryRun); err != nil { |
||||||
|
log.Fatalf("error: %v", err) |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,266 @@ |
|||||||
|
package main |
||||||
|
|
||||||
|
import ( |
||||||
|
"bytes" |
||||||
|
"encoding/json" |
||||||
|
"fmt" |
||||||
|
"io/ioutil" |
||||||
|
"log" |
||||||
|
"net/http" |
||||||
|
"strings" |
||||||
|
"time" |
||||||
|
) |
||||||
|
|
||||||
|
type publisher struct { |
||||||
|
apiKey string |
||||||
|
} |
||||||
|
|
||||||
|
func (p *publisher) doRelease(version string, whatsNewUrl string, releaseNotesUrl string, dryRun bool) error { |
||||||
|
currentRelease, err := newRelease(version, whatsNewUrl, releaseNotesUrl, buildArtifactConfigurations, getHttpContents{}) |
||||||
|
if err != nil { |
||||||
|
return err |
||||||
|
} |
||||||
|
|
||||||
|
if dryRun { |
||||||
|
relJson, err := json.Marshal(currentRelease) |
||||||
|
if err != nil { |
||||||
|
return err |
||||||
|
} |
||||||
|
log.Println(string(relJson)) |
||||||
|
|
||||||
|
for _, b := range currentRelease.Builds { |
||||||
|
artifactJson, err := json.Marshal(b) |
||||||
|
if err != nil { |
||||||
|
return err |
||||||
|
} |
||||||
|
log.Println(string(artifactJson)) |
||||||
|
} |
||||||
|
} else { |
||||||
|
if err := p.postRelease(currentRelease); err != nil { |
||||||
|
return err |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
return nil |
||||||
|
} |
||||||
|
|
||||||
|
func (p *publisher) postRelease(r *release) error { |
||||||
|
err := p.postRequest("/grafana/versions", r, fmt.Sprintf("Create Release %s", r.Version)) |
||||||
|
if err != nil { |
||||||
|
return err |
||||||
|
} |
||||||
|
err = p.postRequest("/grafana/versions/"+r.Version, r, fmt.Sprintf("Update Release %s", r.Version)) |
||||||
|
if err != nil { |
||||||
|
return err |
||||||
|
} |
||||||
|
for _, b := range r.Builds { |
||||||
|
err = p.postRequest(fmt.Sprintf("/grafana/versions/%s/packages", r.Version), b, fmt.Sprintf("Create Build %s %s", b.Os, b.Arch)) |
||||||
|
if err != nil { |
||||||
|
return err |
||||||
|
} |
||||||
|
err = p.postRequest(fmt.Sprintf("/grafana/versions/%s/packages/%s/%s", r.Version, b.Arch, b.Os), b, fmt.Sprintf("Update Build %s %s", b.Os, b.Arch)) |
||||||
|
if err != nil { |
||||||
|
return err |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
return nil |
||||||
|
} |
||||||
|
|
||||||
|
const baseArhiveUrl = "https://s3-us-west-2.amazonaws.com/grafana-releases/release/grafana" |
||||||
|
|
||||||
|
type buildArtifact struct { |
||||||
|
os string |
||||||
|
arch string |
||||||
|
urlPostfix string |
||||||
|
} |
||||||
|
|
||||||
|
func (t buildArtifact) getUrl(version string, isBeta bool) string { |
||||||
|
prefix := "-" |
||||||
|
rhelReleaseExtra := "" |
||||||
|
|
||||||
|
if t.os == "deb" { |
||||||
|
prefix = "_" |
||||||
|
} |
||||||
|
|
||||||
|
if !isBeta && t.os == "rhel" { |
||||||
|
rhelReleaseExtra = "-1" |
||||||
|
} |
||||||
|
|
||||||
|
url := strings.Join([]string{baseArhiveUrl, prefix, version, rhelReleaseExtra, t.urlPostfix}, "") |
||||||
|
return url |
||||||
|
} |
||||||
|
|
||||||
|
var buildArtifactConfigurations = []buildArtifact{ |
||||||
|
{ |
||||||
|
os: "deb", |
||||||
|
arch: "arm64", |
||||||
|
urlPostfix: "_arm64.deb", |
||||||
|
}, |
||||||
|
{ |
||||||
|
os: "rhel", |
||||||
|
arch: "arm64", |
||||||
|
urlPostfix: ".aarch64.rpm", |
||||||
|
}, |
||||||
|
{ |
||||||
|
os: "linux", |
||||||
|
arch: "arm64", |
||||||
|
urlPostfix: ".linux-arm64.tar.gz", |
||||||
|
}, |
||||||
|
{ |
||||||
|
os: "deb", |
||||||
|
arch: "armv7", |
||||||
|
urlPostfix: "_armhf.deb", |
||||||
|
}, |
||||||
|
{ |
||||||
|
os: "rhel", |
||||||
|
arch: "armv7", |
||||||
|
urlPostfix: ".armhfp.rpm", |
||||||
|
}, |
||||||
|
{ |
||||||
|
os: "linux", |
||||||
|
arch: "armv7", |
||||||
|
urlPostfix: ".linux-armv7.tar.gz", |
||||||
|
}, |
||||||
|
{ |
||||||
|
os: "darwin", |
||||||
|
arch: "amd64", |
||||||
|
urlPostfix: ".darwin-amd64.tar.gz", |
||||||
|
}, |
||||||
|
{ |
||||||
|
os: "deb", |
||||||
|
arch: "amd64", |
||||||
|
urlPostfix: "_amd64.deb", |
||||||
|
}, |
||||||
|
{ |
||||||
|
os: "rhel", |
||||||
|
arch: "amd64", |
||||||
|
urlPostfix: ".x86_64.rpm", |
||||||
|
}, |
||||||
|
{ |
||||||
|
os: "linux", |
||||||
|
arch: "amd64", |
||||||
|
urlPostfix: ".linux-amd64.tar.gz", |
||||||
|
}, |
||||||
|
{ |
||||||
|
os: "win", |
||||||
|
arch: "amd64", |
||||||
|
urlPostfix: ".windows-amd64.zip", |
||||||
|
}, |
||||||
|
} |
||||||
|
|
||||||
|
func newRelease(rawVersion string, whatsNewUrl string, releaseNotesUrl string, artifactConfigurations []buildArtifact, getter urlGetter) (*release, error) { |
||||||
|
version := rawVersion[1:] |
||||||
|
now := time.Now() |
||||||
|
isBeta := strings.Contains(version, "beta") |
||||||
|
|
||||||
|
builds := []build{} |
||||||
|
for _, ba := range artifactConfigurations { |
||||||
|
sha256, err := getter.getContents(fmt.Sprintf("%s.sha256", ba.getUrl(version, isBeta))) |
||||||
|
if err != nil { |
||||||
|
return nil, err |
||||||
|
} |
||||||
|
builds = append(builds, newBuild(ba, version, isBeta, sha256)) |
||||||
|
} |
||||||
|
|
||||||
|
r := release{ |
||||||
|
Version: version, |
||||||
|
ReleaseDate: time.Date(now.Year(), now.Month(), now.Day(), 0, 0, 0, 0, time.Local), |
||||||
|
Stable: !isBeta, |
||||||
|
Beta: isBeta, |
||||||
|
Nightly: false, |
||||||
|
WhatsNewUrl: whatsNewUrl, |
||||||
|
ReleaseNotesUrl: releaseNotesUrl, |
||||||
|
Builds: builds, |
||||||
|
} |
||||||
|
return &r, nil |
||||||
|
} |
||||||
|
|
||||||
|
func newBuild(ba buildArtifact, version string, isBeta bool, sha256 string) build { |
||||||
|
return build{ |
||||||
|
Os: ba.os, |
||||||
|
Url: ba.getUrl(version, isBeta), |
||||||
|
Sha256: sha256, |
||||||
|
Arch: ba.arch, |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
func (p *publisher) postRequest(url string, obj interface{}, desc string) error { |
||||||
|
jsonBytes, err := json.Marshal(obj) |
||||||
|
if err != nil { |
||||||
|
return err |
||||||
|
} |
||||||
|
req, err := http.NewRequest(http.MethodPost, baseUri+url, bytes.NewReader(jsonBytes)) |
||||||
|
if err != nil { |
||||||
|
return err |
||||||
|
} |
||||||
|
req.Header.Add("Authorization", "Bearer "+p.apiKey) |
||||||
|
req.Header.Add("Content-Type", "application/json") |
||||||
|
|
||||||
|
res, err := http.DefaultClient.Do(req) |
||||||
|
if err != nil { |
||||||
|
return err |
||||||
|
} |
||||||
|
|
||||||
|
if res.StatusCode == http.StatusOK { |
||||||
|
log.Printf("Action: %s \t OK", desc) |
||||||
|
return nil |
||||||
|
} |
||||||
|
|
||||||
|
if res.Body != nil { |
||||||
|
defer res.Body.Close() |
||||||
|
body, err := ioutil.ReadAll(res.Body) |
||||||
|
if err != nil { |
||||||
|
return err |
||||||
|
} |
||||||
|
|
||||||
|
if strings.Contains(string(body), "already exists") || strings.Contains(string(body), "Nothing to update") { |
||||||
|
log.Printf("Action: %s \t Already exists", desc) |
||||||
|
} else { |
||||||
|
log.Printf("Action: %s \t Failed - Status: %v", desc, res.Status) |
||||||
|
log.Printf("Resp: %s", body) |
||||||
|
log.Fatalf("Quiting") |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
return nil |
||||||
|
} |
||||||
|
|
||||||
|
type release 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 []build `json:"-"` |
||||||
|
} |
||||||
|
|
||||||
|
type build struct { |
||||||
|
Os string `json:"os"` |
||||||
|
Url string `json:"url"` |
||||||
|
Sha256 string `json:"sha256"` |
||||||
|
Arch string `json:"arch"` |
||||||
|
} |
||||||
|
|
||||||
|
type urlGetter interface { |
||||||
|
getContents(url string) (string, error) |
||||||
|
} |
||||||
|
|
||||||
|
type getHttpContents struct{} |
||||||
|
|
||||||
|
func (getHttpContents) getContents(url string) (string, error) { |
||||||
|
response, err := http.Get(url) |
||||||
|
if err != nil { |
||||||
|
return "", err |
||||||
|
} |
||||||
|
|
||||||
|
defer response.Body.Close() |
||||||
|
all, err := ioutil.ReadAll(response.Body) |
||||||
|
if err != nil { |
||||||
|
return "", err |
||||||
|
} |
||||||
|
|
||||||
|
return string(all), nil |
||||||
|
} |
@ -0,0 +1,43 @@ |
|||||||
|
package main |
||||||
|
|
||||||
|
import "testing" |
||||||
|
|
||||||
|
func TestNewRelease(t *testing.T) { |
||||||
|
versionIn := "v5.2.0-beta1" |
||||||
|
expectedVersion := "5.2.0-beta1" |
||||||
|
whatsNewUrl := "https://whatsnews.foo/" |
||||||
|
relNotesUrl := "https://relnotes.foo/" |
||||||
|
expectedArch := "amd64" |
||||||
|
expectedOs := "linux" |
||||||
|
buildArtifacts := []buildArtifact{{expectedOs, expectedArch, ".linux-amd64.tar.gz"}} |
||||||
|
|
||||||
|
rel, _ := newRelease(versionIn, whatsNewUrl, relNotesUrl, buildArtifacts, mockHttpGetter{}) |
||||||
|
|
||||||
|
if !rel.Beta || rel.Stable { |
||||||
|
t.Errorf("%s should have been tagged as beta (not stable), but wasn't .", versionIn) |
||||||
|
} |
||||||
|
|
||||||
|
if rel.Version != expectedVersion { |
||||||
|
t.Errorf("Expected version to be %s, but it was %s.", expectedVersion, rel.Version) |
||||||
|
} |
||||||
|
|
||||||
|
expectedBuilds := len(buildArtifacts) |
||||||
|
if len(rel.Builds) != expectedBuilds { |
||||||
|
t.Errorf("Expected %v builds, but got %v.", expectedBuilds, len(rel.Builds)) |
||||||
|
} |
||||||
|
|
||||||
|
build := rel.Builds[0] |
||||||
|
if build.Arch != expectedArch { |
||||||
|
t.Errorf("Expected arch to be %v, but it was %v", expectedArch, build.Arch) |
||||||
|
} |
||||||
|
|
||||||
|
if build.Os != expectedOs { |
||||||
|
t.Errorf("Expected arch to be %v, but it was %v", expectedOs, build.Os) |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
type mockHttpGetter struct{} |
||||||
|
|
||||||
|
func (mockHttpGetter) getContents(url string) (string, error) { |
||||||
|
return url, nil |
||||||
|
} |
Loading…
Reference in new issue