mirror of
https://github.com/hanzoai/docdb.git
synced 2026-10-30 18:39:39 +00:00
326 lines
9.8 KiB
Go
326 lines
9.8 KiB
Go
// Copyright 2021 Hanzo AI Inc.
|
|
//
|
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
// you may not use this file except in compliance with the License.
|
|
// You may obtain a copy of the License at
|
|
//
|
|
// http://www.apache.org/licenses/LICENSE-2.0
|
|
//
|
|
// Unless required by applicable law or agreed to in writing, software
|
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
// See the License for the specific language governing permissions and
|
|
// limitations under the License.
|
|
|
|
// Package main contains tool for defining Docker image tags on CI.
|
|
package main
|
|
|
|
import (
|
|
"flag"
|
|
"fmt"
|
|
"os"
|
|
"regexp"
|
|
"slices"
|
|
"strings"
|
|
"text/tabwriter"
|
|
|
|
"github.com/sethvargo/go-githubactions"
|
|
)
|
|
|
|
func main() {
|
|
flag.Parse()
|
|
|
|
action := githubactions.New()
|
|
|
|
debugEnv(action)
|
|
|
|
res, err := define(action.Getenv)
|
|
if err != nil {
|
|
action.Fatalf("%s", err)
|
|
}
|
|
|
|
setResults(action, res)
|
|
}
|
|
|
|
// result represents Docker image names and tags extracted from the environment.
|
|
type result struct {
|
|
evalDevImages []string
|
|
evalImages []string
|
|
developmentImages []string
|
|
productionImages []string
|
|
}
|
|
|
|
// semVerTag is a https://semver.org/#is-there-a-suggested-regular-expression-regex-to-check-a-semver-string,
|
|
// but with a leading `v`.
|
|
//
|
|
//nolint:lll // for readability
|
|
var semVerTag = regexp.MustCompile(`^v(?P<major>0|[1-9]\d*)\.(?P<minor>0|[1-9]\d*)\.(?P<patch>0|[1-9]\d*)(?:-(?P<prerelease>(?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*)(?:\.(?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*))*))?(?:\+(?P<buildmetadata>[0-9a-zA-Z-]+(?:\.[0-9a-zA-Z-]+)*))?$`)
|
|
|
|
// debugEnv logs all environment variables that start with `GITHUB_` or `INPUT_`
|
|
// in debug level.
|
|
func debugEnv(action *githubactions.Action) {
|
|
res := make([]string, 0, 30)
|
|
|
|
for _, l := range os.Environ() {
|
|
if strings.HasPrefix(l, "GITHUB_") || strings.HasPrefix(l, "INPUT_") {
|
|
res = append(res, l)
|
|
}
|
|
}
|
|
|
|
slices.Sort(res)
|
|
|
|
action.Debugf("Dumping environment variables:")
|
|
|
|
for _, l := range res {
|
|
action.Debugf("\t%s", l)
|
|
}
|
|
}
|
|
|
|
// Define extracts Docker image names and tags from the environment variables defined by GitHub Actions.
|
|
func define(getenv githubactions.GetenvFunc) (*result, error) {
|
|
repo := getenv("GITHUB_REPOSITORY")
|
|
|
|
// to support GitHub forks
|
|
parts := strings.Split(strings.ToLower(repo), "/")
|
|
if len(parts) != 2 {
|
|
return nil, fmt.Errorf("failed to split %q into owner and name", repo)
|
|
}
|
|
owner := parts[0]
|
|
repo = parts[1]
|
|
|
|
var res *result
|
|
var err error
|
|
|
|
switch event := getenv("GITHUB_EVENT_NAME"); event {
|
|
case "pull_request", "pull_request_target":
|
|
branch := strings.ToLower(getenv("GITHUB_HEAD_REF"))
|
|
res = defineForPR(owner, repo, branch)
|
|
|
|
case "push", "schedule", "workflow_run":
|
|
refName := strings.ToLower(getenv("GITHUB_REF_NAME"))
|
|
|
|
switch refType := strings.ToLower(getenv("GITHUB_REF_TYPE")); refType {
|
|
case "branch":
|
|
res, err = defineForBranch(owner, repo, refName)
|
|
|
|
case "tag":
|
|
match := semVerTag.FindStringSubmatch(refName)
|
|
if match == nil || len(match) != semVerTag.NumSubexp()+1 {
|
|
return nil, fmt.Errorf("unexpected git tag %q", refName)
|
|
}
|
|
|
|
major := match[semVerTag.SubexpIndex("major")]
|
|
minor := match[semVerTag.SubexpIndex("minor")]
|
|
patch := match[semVerTag.SubexpIndex("patch")]
|
|
prerelease := match[semVerTag.SubexpIndex("prerelease")]
|
|
|
|
var tags []string
|
|
|
|
if prerelease == "" {
|
|
tags = []string{
|
|
major,
|
|
major + "." + minor,
|
|
major + "." + minor + "." + patch,
|
|
}
|
|
|
|
if major == "2" {
|
|
tags = append(tags, "latest")
|
|
}
|
|
} else {
|
|
tags = []string{major + "." + minor + "." + patch + "-" + prerelease}
|
|
}
|
|
|
|
res = defineForTag(owner, repo, tags)
|
|
|
|
default:
|
|
err = fmt.Errorf("unhandled ref type %q for event %q", refType, event)
|
|
}
|
|
|
|
default:
|
|
err = fmt.Errorf("unhandled event type %q", event)
|
|
}
|
|
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
if res == nil {
|
|
panic("both res and err are nil")
|
|
}
|
|
|
|
slices.Sort(res.evalDevImages)
|
|
slices.Sort(res.evalImages)
|
|
slices.Sort(res.developmentImages)
|
|
slices.Sort(res.productionImages)
|
|
|
|
return res, nil
|
|
}
|
|
|
|
// defineForPR defines Docker image names and tags for pull requests.
|
|
func defineForPR(owner, repo, branch string) *result {
|
|
// for branches like "dependabot/submodules/XXX"
|
|
parts := strings.Split(branch, "/")
|
|
branch = parts[len(parts)-1]
|
|
|
|
res := &result{
|
|
evalDevImages: []string{
|
|
fmt.Sprintf("ghcr.io/%s/%s-eval-dev:pr-%s", owner, repo, branch),
|
|
},
|
|
evalImages: []string{
|
|
fmt.Sprintf("ghcr.io/%s/%s-eval:pr-%s", owner, repo, branch),
|
|
},
|
|
developmentImages: []string{
|
|
fmt.Sprintf("ghcr.io/%s/%s-dev:pr-%s", owner, repo, branch),
|
|
},
|
|
productionImages: []string{
|
|
fmt.Sprintf("ghcr.io/%s/%s-dev:pr-%s-prod", owner, repo, branch),
|
|
},
|
|
}
|
|
|
|
// PRs are only for testing; no Quay.io and Docker Hub repos
|
|
|
|
return res
|
|
}
|
|
|
|
// defineForBranch defines Docker image names and tags for branch builds.
|
|
func defineForBranch(owner, repo, branch string) (*result, error) {
|
|
// see packages.yml
|
|
switch {
|
|
case branch == "main":
|
|
fallthrough
|
|
case strings.HasPrefix(branch, "main-"):
|
|
fallthrough
|
|
case strings.HasPrefix(branch, "releases/"):
|
|
branch = strings.ReplaceAll(branch, "/", "-")
|
|
|
|
default:
|
|
return nil, fmt.Errorf("unhandled branch %q", branch)
|
|
}
|
|
|
|
res := &result{
|
|
evalDevImages: []string{
|
|
fmt.Sprintf("ghcr.io/%s/%s-eval-dev:%s", owner, repo, branch),
|
|
},
|
|
evalImages: []string{
|
|
fmt.Sprintf("ghcr.io/%s/%s-eval:%s", owner, repo, branch),
|
|
},
|
|
developmentImages: []string{
|
|
fmt.Sprintf("ghcr.io/%s/%s-dev:%s", owner, repo, branch),
|
|
},
|
|
productionImages: []string{
|
|
fmt.Sprintf("ghcr.io/%s/%s-dev:%s-prod", owner, repo, branch),
|
|
},
|
|
}
|
|
|
|
// forks don't have Quay.io and Docker Hub orgs
|
|
if owner != "docdb" {
|
|
return res, nil
|
|
}
|
|
|
|
// we don't have Quay.io and Docker Hub repos for other GitHub repos
|
|
if repo != "docdb" {
|
|
return res, nil
|
|
}
|
|
|
|
res.evalDevImages = append(res.evalDevImages, fmt.Sprintf("quay.io/hanzoai/docdb-eval-dev:%s", branch))
|
|
res.evalImages = append(res.evalImages, fmt.Sprintf("quay.io/hanzoai/docdb-eval:%s", branch))
|
|
res.developmentImages = append(res.developmentImages, fmt.Sprintf("quay.io/hanzoai/docdb-dev:%s", branch))
|
|
res.productionImages = append(res.productionImages, fmt.Sprintf("quay.io/hanzoai/docdb-dev:%s-prod", branch))
|
|
|
|
res.evalDevImages = append(res.evalDevImages, fmt.Sprintf("hanzoai/docdb-eval-dev:%s", branch))
|
|
res.evalImages = append(res.evalImages, fmt.Sprintf("hanzoai/docdb-eval:%s", branch))
|
|
res.developmentImages = append(res.developmentImages, fmt.Sprintf("hanzoai/docdb-dev:%s", branch))
|
|
res.productionImages = append(res.productionImages, fmt.Sprintf("hanzoai/docdb-dev:%s-prod", branch))
|
|
|
|
return res, nil
|
|
}
|
|
|
|
// defineForTag defines Docker image names and tags for tag builds.
|
|
func defineForTag(owner, repo string, tags []string) *result {
|
|
res := new(result)
|
|
|
|
for _, t := range tags {
|
|
res.evalDevImages = append(res.evalDevImages, fmt.Sprintf("ghcr.io/%s/%s-eval-dev:%s", owner, repo, t))
|
|
res.evalImages = append(res.evalImages, fmt.Sprintf("ghcr.io/%s/%s-eval:%s", owner, repo, t))
|
|
res.developmentImages = append(res.developmentImages, fmt.Sprintf("ghcr.io/%s/%s-dev:%s", owner, repo, t))
|
|
res.productionImages = append(res.productionImages, fmt.Sprintf("ghcr.io/%s/%s:%s", owner, repo, t))
|
|
}
|
|
|
|
// forks don't have Quay.io and Docker Hub orgs
|
|
if owner != "docdb" {
|
|
return res
|
|
}
|
|
|
|
// we don't have Quay.io and Docker Hub repos for other GitHub repos
|
|
if repo != "docdb" {
|
|
return res
|
|
}
|
|
|
|
for _, t := range tags {
|
|
res.evalDevImages = append(res.evalDevImages, fmt.Sprintf("quay.io/hanzoai/docdb-eval-dev:%s", t))
|
|
res.evalImages = append(res.evalImages, fmt.Sprintf("quay.io/hanzoai/docdb-eval:%s", t))
|
|
res.developmentImages = append(res.developmentImages, fmt.Sprintf("quay.io/hanzoai/docdb-dev:%s", t))
|
|
res.productionImages = append(res.productionImages, fmt.Sprintf("quay.io/hanzoai/docdb:%s", t))
|
|
|
|
res.evalDevImages = append(res.evalDevImages, fmt.Sprintf("hanzoai/docdb-eval-dev:%s", t))
|
|
res.evalImages = append(res.evalImages, fmt.Sprintf("hanzoai/docdb-eval:%s", t))
|
|
res.developmentImages = append(res.developmentImages, fmt.Sprintf("hanzoai/docdb-dev:%s", t))
|
|
res.productionImages = append(res.productionImages, fmt.Sprintf("hanzoai/docdb:%s", t))
|
|
}
|
|
|
|
return res
|
|
}
|
|
|
|
// setResults sets action output parameters, summary, etc.
|
|
func setResults(action *githubactions.Action, res *result) {
|
|
var buf strings.Builder
|
|
w := tabwriter.NewWriter(&buf, 1, 1, 1, ' ', tabwriter.Debug)
|
|
fmt.Fprintf(w, "\tType\tImage\t\n")
|
|
fmt.Fprintf(w, "\t----\t-----\t\n")
|
|
|
|
for _, image := range res.evalDevImages {
|
|
u := imageURL(image)
|
|
_, _ = fmt.Fprintf(w, "\tEvaluation Development\t[`%s`](%s)\t\n", image, u)
|
|
}
|
|
|
|
for _, image := range res.evalImages {
|
|
u := imageURL(image)
|
|
_, _ = fmt.Fprintf(w, "\tEvaluation\t[`%s`](%s)\t\n", image, u)
|
|
}
|
|
|
|
for _, image := range res.developmentImages {
|
|
u := imageURL(image)
|
|
_, _ = fmt.Fprintf(w, "\tDevelopment\t[`%s`](%s)\t\n", image, u)
|
|
}
|
|
|
|
for _, image := range res.productionImages {
|
|
u := imageURL(image)
|
|
_, _ = fmt.Fprintf(w, "\tProduction\t[`%s`](%s)\t\n", image, u)
|
|
}
|
|
|
|
_ = w.Flush()
|
|
|
|
action.AddStepSummary(buf.String())
|
|
action.Infof("%s", buf.String())
|
|
|
|
action.SetOutput("eval_dev_images", strings.Join(res.evalDevImages, ","))
|
|
action.SetOutput("eval_images", strings.Join(res.evalImages, ","))
|
|
action.SetOutput("development_images", strings.Join(res.developmentImages, ","))
|
|
action.SetOutput("production_images", strings.Join(res.productionImages, ","))
|
|
}
|
|
|
|
// imageURL returns HTML page URL for the given image name and tag.
|
|
func imageURL(name string) string {
|
|
switch {
|
|
case strings.HasPrefix(name, "ghcr.io/"):
|
|
return fmt.Sprintf("https://%s", name)
|
|
case strings.HasPrefix(name, "quay.io/"):
|
|
return fmt.Sprintf("https://%s", name)
|
|
}
|
|
|
|
name, _, _ = strings.Cut(name, ":")
|
|
|
|
// there is not easy way to get Docker Hub URL for the given tag
|
|
return fmt.Sprintf("https://hub.docker.com/r/%s/tags", name)
|
|
}
|