diff --git a/.dockerignore b/.dockerignore index 829c073..65750f3 100644 --- a/.dockerignore +++ b/.dockerignore @@ -8,4 +8,3 @@ acme-backup.json traefik-certs-dumper manifest.json *.Dockerfile -internal/ diff --git a/.gitignore b/.gitignore index 6681f45..9f570b3 100644 --- a/.gitignore +++ b/.gitignore @@ -7,4 +7,4 @@ acme.json acme-backup.json traefik-certs-dumper manifest.json -/*.Dockerfile +/linux-*.Dockerfile diff --git a/.travis.yml b/.travis.yml index 034c809..4de67cb 100644 --- a/.travis.yml +++ b/.travis.yml @@ -19,8 +19,11 @@ notifications: before_install: # Install linters and misspell - - curl -sfL https://install.goreleaser.com/github.com/golangci/golangci-lint.sh | bash -s -- -b $GOPATH/bin v1.16.0 + - curl -sfL https://install.goreleaser.com/github.com/golangci/golangci-lint.sh | bash -s -- -b $GOPATH/bin ${GOLANGCI_LINT_VERSION} - golangci-lint --version + # Install Docker image multi-arch builder + - curl -sfL https://raw.githubusercontent.com/ldez/seihon/master/godownloader.sh | bash -s -- -b "${GOPATH}/bin" ${SEIHON_VERSION} + - seihon --version install: - echo "TRAVIS_GO_VERSION=$TRAVIS_GO_VERSION" diff --git a/Makefile b/Makefile index a0c2603..2afe53d 100644 --- a/Makefile +++ b/Makefile @@ -22,6 +22,5 @@ checks: golangci-lint run publish-images: - go run ./internal/ --version="$(TAG_NAME)" --dry-run=false - go run ./internal/ --version="latest" --dry-run=false - rm -f *.Dockerfile + seihon publish --version="$(TAG_NAME)" --image-name ldez/traefik-certs-dumper --dry-run=false + seihon publish --version="latest" --image-name ldez/traefik-certs-dumper --dry-run=false diff --git a/internal/build-options.json b/internal/build-options.json deleted file mode 100644 index b9170dc..0000000 --- a/internal/build-options.json +++ /dev/null @@ -1,33 +0,0 @@ -{ - "386": { - "os": "linux", - "go_arch": "386" - }, - "amd64": { - "os": "linux", - "go_arch": "amd64" - }, - "arm.v5": { - "os": "linux", - "go_arch": "arm", - "go_arm": "5", - "variant": "v5" - }, - "arm.v6": { - "os": "linux", - "go_arch": "arm", - "go_arm": "6", - "variant": "v6" - }, - "arm.v7": { - "os": "linux", - "go_arch": "arm", - "go_arm": "7", - "variant": "v7" - }, - "arm.v8": { - "os": "linux", - "go_arch": "arm64", - "variant": "v8" - } -} diff --git a/internal/multiarch.go b/internal/multiarch.go deleted file mode 100644 index a40b5b2..0000000 --- a/internal/multiarch.go +++ /dev/null @@ -1,53 +0,0 @@ -package main - -import ( - "flag" - "log" - "os" -) - -type buildOption struct { - OS string `json:"os"` - GoARCH string `json:"go_arch"` - GoARM string `json:"go_arm,omitempty"` - Variant string `json:"variant,omitempty"` -} - -func main() { - log.SetFlags(log.Lshortfile) - - imageName := flag.String("image-name", "ldez/traefik-certs-dumper", "") - version := flag.String("version", "", "") - baseImageName := flag.String("base-image-name", "alpine:3.9", "") - dryRun := flag.Bool("dry-run", true, "") - - flag.Parse() - - require("image-name", imageName) - require("version", version) - require("base-image-name", baseImageName) - - _, travisTag := os.LookupEnv("TRAVIS_TAG") - if !travisTag { - log.Println("Skipping deploy") - os.Exit(0) - } - - targets := []string{"arm.v6", "arm.v7", "arm.v8", "amd64", "386"} - - publisher, err := newPublisher(*imageName, *version, *baseImageName, targets) - if err != nil { - log.Fatal(err) - } - - err = publisher.execute(*dryRun) - if err != nil { - log.Fatal(err) - } -} - -func require(fieldName string, field *string) { - if field == nil || *field == "" { - log.Fatalf("%s is required", fieldName) - } -} diff --git a/internal/publisher.go b/internal/publisher.go deleted file mode 100644 index 37b0605..0000000 --- a/internal/publisher.go +++ /dev/null @@ -1,231 +0,0 @@ -package main - -import ( - "encoding/json" - "fmt" - "html/template" - "io/ioutil" - "log" - "os" - "os/exec" - "strings" - - "github.com/docker/distribution/manifest/manifestlist" -) - -const envDockerExperimental = "DOCKER_CLI_EXPERIMENTAL=enabled" - -// Publisher Publish multi-arch image. -type Publisher struct { - Builds []*exec.Cmd - Push []*exec.Cmd - ManifestAnnotate []*exec.Cmd - ManifestCreate *exec.Cmd - ManifestPush *exec.Cmd -} - -func newPublisher(imageName, version, baseImageName string, targets []string) (Publisher, error) { - manifest, err := getManifest(baseImageName) - if err != nil { - return Publisher{}, err - } - - buildOptions, err := getBuildOptions("./internal/build-options.json") - if err != nil { - return Publisher{}, err - } - - publisher := Publisher{} - - for _, target := range targets { - option, ok := buildOptions[target] - if !ok { - return Publisher{}, fmt.Errorf("unsupported platform: %s", target) - } - - descriptor, err := findManifestDescriptor(option, manifest.Manifests) - if err != nil { - return Publisher{}, err - } - - dockerfile := fmt.Sprintf("%s-%s-%s.Dockerfile", option.OS, option.GoARCH, option.GoARM) - - err = createDockerfile(dockerfile, option, descriptor, baseImageName) - if err != nil { - return Publisher{}, err - } - - dBuild := exec.Command("docker", "build", - "-t", fmt.Sprintf("%s:%s-%s", imageName, version, target), - "-f", dockerfile, - ".") - publisher.Builds = append(publisher.Builds, dBuild) - - dPush := exec.Command("docker", "push", fmt.Sprintf(`%s:%s-%s`, imageName, version, target)) - publisher.Push = append(publisher.Push, dPush) - - ma := []string{ - "manifest", "annotate", - fmt.Sprintf("%s:%s", imageName, version), - fmt.Sprintf("%s:%s-%s", imageName, version, target), - fmt.Sprintf("--os=%s", option.OS), - fmt.Sprintf("--arch=%s", option.GoARCH), - } - if option.Variant != "" { - ma = append(ma, fmt.Sprintf("--variant=%s", option.Variant)) - } - - cmdMA := exec.Command("docker", ma...) - cmdMA.Env = append(cmdMA.Env, envDockerExperimental) - publisher.ManifestAnnotate = append(publisher.ManifestAnnotate, cmdMA) - } - - mc := []string{ - "manifest", "create", "--amend", fmt.Sprintf("%s:%s", imageName, version), - } - for _, target := range targets { - mc = append(mc, fmt.Sprintf("%s:%s-%s", imageName, version, target)) - } - - cmdMC := exec.Command("docker", mc...) - cmdMC.Env = append(cmdMC.Env, envDockerExperimental) - publisher.ManifestCreate = cmdMC - - cmdMP := exec.Command("docker", "manifest", "push", fmt.Sprintf("%s:%s", imageName, version)) - cmdMP.Env = append(cmdMP.Env, envDockerExperimental) - publisher.ManifestPush = cmdMP - - return publisher, nil -} - -func (b Publisher) execute(dryRun bool) error { - for _, cmd := range b.Builds { - if err := execCmd(cmd, dryRun); err != nil { - return fmt.Errorf("failed to build: %v: %v", cmd, err) - } - } - - for _, cmd := range b.Push { - if err := execCmd(cmd, dryRun); err != nil { - return fmt.Errorf("failed to push: %v: %v", cmd, err) - } - } - - if err := execCmd(b.ManifestCreate, dryRun); err != nil { - return fmt.Errorf("failed to create manifest: %v: %v", b.ManifestCreate, err) - } - - for _, cmd := range b.ManifestAnnotate { - if err := execCmd(cmd, dryRun); err != nil { - return fmt.Errorf("failed to annotate manifest: %v: %v", cmd, err) - } - } - - if err := execCmd(b.ManifestPush, dryRun); err != nil { - return fmt.Errorf("failed to push manifest: %v: %v", b.ManifestPush, err) - } - - return nil -} - -func createDockerfile(dockerfile string, option buildOption, descriptor manifestlist.ManifestDescriptor, baseImageName string) error { - base := template.New("tmpl.Dockerfile") - parse, err := base.ParseFiles("./internal/tmpl.Dockerfile") - if err != nil { - return err - } - - data := map[string]interface{}{ - "GoOS": option.OS, - "GoARCH": option.GoARCH, - "GoARM": option.GoARM, - "RuntimeImage": fmt.Sprintf("%s@%s", baseImageName, descriptor.Digest), - } - - file, err := os.Create(dockerfile) - if err != nil { - return err - } - - return parse.Execute(file, data) -} - -func getBuildOptions(source string) (map[string]buildOption, error) { - file, err := os.Open(source) - if err != nil { - return nil, err - } - - buildOptions := make(map[string]buildOption) - - err = json.NewDecoder(file).Decode(&buildOptions) - if err != nil { - return nil, err - } - - return buildOptions, nil -} - -func getManifest(baseImageName string) (*manifestlist.ManifestList, error) { - manifestPath := "./manifest.json" - - if _, errExist := os.Stat(manifestPath); os.IsNotExist(errExist) { - cmd := exec.Command("docker", "manifest", "inspect", baseImageName) - cmd.Env = append(cmd.Env, "DOCKER_CLI_EXPERIMENTAL=enabled") - - output, err := cmd.CombinedOutput() - if err != nil { - return nil, err - } - - err = ioutil.WriteFile(manifestPath, output, 0666) - if err != nil { - return nil, err - } - } else if errExist != nil { - return nil, errExist - } - - bytes, err := ioutil.ReadFile(manifestPath) - if err != nil { - return nil, err - } - - manifest := &manifestlist.ManifestList{} - - err = json.Unmarshal(bytes, manifest) - if err != nil { - return nil, err - } - - return manifest, nil -} - -func findManifestDescriptor(criterion buildOption, descriptors []manifestlist.ManifestDescriptor) (manifestlist.ManifestDescriptor, error) { - for _, descriptor := range descriptors { - if descriptor.Platform.OS == criterion.OS && - descriptor.Platform.Architecture == criterion.GoARCH && - descriptor.Platform.Variant == criterion.Variant { - return descriptor, nil - } - } - return manifestlist.ManifestDescriptor{}, fmt.Errorf("not supported: %v", criterion) -} - -func execCmd(cmd *exec.Cmd, dryRun bool) error { - if dryRun { - fmt.Println(cmd.Path, strings.Join(cmd.Args, " ")) - return nil - } - - output, err := cmd.CombinedOutput() - - if len(output) != 0 { - log.Println(string(output)) - } - - if err != nil { - return err - } - return nil -} diff --git a/internal/tmpl.Dockerfile b/tmpl.Dockerfile similarity index 100% rename from internal/tmpl.Dockerfile rename to tmpl.Dockerfile