build/mage/docker/release.go (140 lines of code) (raw):

// Licensed to Elasticsearch B.V. under one or more contributor // license agreements. See the NOTICE file distributed with // this work for additional information regarding copyright // ownership. Elasticsearch B.V. licenses this file to you 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 docker import ( "bytes" "fmt" "os" "strings" "text/template" "time" semver "github.com/Masterminds/semver/v3" "github.com/magefile/mage/mg" "github.com/magefile/mage/sh" "github.com/elastic/harp/build/artifact" "github.com/elastic/harp/build/mage/git" exec "golang.org/x/sys/execabs" ) var dockerReleaseTemplate = strings.TrimSpace(` # syntax=docker/dockerfile:experimental # Arguments ARG BUILD_DATE={{.BuildDate}} ARG VERSION={{.Version}} ARG VCS_REF={{.VcsRef}} # Builder arguments ARG TOOLS_IMAGE={{.ToolImageName}} ARG RELEASE={{.Release}} FROM $TOOLS_IMAGE as compiler ARG RELEASE={{.Release}} # Back to project root WORKDIR $GOPATH/src/workspace # Get dependencies COPY go.mod . RUN go mod download # Copy project go module COPY --chown=golang:golang . . {{ if .Cmd.HasModule }} # Go to cmd WORKDIR $GOPATH/src/workspace/{{ .Cmd.Module }} {{ else }} # Stay at root path WORKDIR $GOPATH/src/workspace {{ end }} # Clean existing binaries RUN set -eux; \ rm -f bin/* # Update vendor RUN set -eux; \ go mod vendor # Set the target release version ENV RELEASE=$RELEASE # Build final target RUN set -eux; \ mage release ## ------------------------------------------------------------------------------------------------- # Assemble all binary files FROM alpine:latest AS assembler WORKDIR /app {{ if .Cmd.HasModule }} COPY --from=compiler /go/src/workspace/{{.Cmd.Module}}/bin/* /app/ {{ else }} COPY --from=compiler /go/src/workspace/bin/* /app/ {{ end }} ## ------------------------------------------------------------------------------------------------- # hadolint ignore=DL3007 FROM alpine:latest # Arguments ARG BUILD_DATE={{.BuildDate}} ARG VERSION={{.Version}} ARG VCS_REF={{.VcsRef}} ARG RELEASE={{.Release}} # Metadata LABEL \ org.opencontainers.image.created=$BUILD_DATE \ org.opencontainers.image.title="{{.Name}}" \ org.opencontainers.image.description="{{.Cmd.Description}}" \ org.opencontainers.image.url="https://{{.Cmd.Package}}" \ org.opencontainers.image.source="https://{{.Cmd.Package}}.git" \ org.opencontainers.image.revision=$VCS_REF \ org.opencontainers.image.vendor="Elastic" \ org.opencontainers.image.version=$RELEASE \ org.opencontainers.image.licences="ASL2" WORKDIR /app COPY --from=assembler /app/* /app/ `) // Release uses docker pipeline to generate all artifacts. func Release(cmd *artifact.Command) func() error { return func() error { mg.Deps(git.CollectInfo) // Docker image name toolImageName := toolImage if os.Getenv("TOOL_IMAGE_NAME") != "" { toolImageName = os.Getenv("TOOL_IMAGE_NAME") } // Extract release release := os.Getenv("RELEASE") relVer, err := semver.StrictNewVersion(release) if err != nil { return fmt.Errorf("invalid semver syntax for release: %w", err) } buf, err := merge(dockerReleaseTemplate, map[string]interface{}{ "Name": "Harp CLI Artifacts", "ToolImageName": toolImageName, "BuildDate": time.Now().Format(time.RFC3339), "Version": git.Tag, "VcsRef": git.Revision, "Cmd": cmd, "Release": release, }) if err != nil { return err } // Check if we want to generate dockerfile output if os.Getenv("DOCKERFILE_ONLY") != "" { return os.WriteFile("Dockerfile.release", buf.Bytes(), 0o600) } // Prepare command //nolint:gosec // Expected behavior c := exec.Command("docker", "build", "-t", fmt.Sprintf("elastic/%s:artifacts-%s", cmd.Kebab(), relVer.String()), "-f", "-", ".", ) // Prepare environment c.Env = os.Environ() c.Env = append(c.Env, "DOCKER_BUILDKIT=1") c.Stderr = os.Stderr c.Stdout = os.Stdout c.Stdin = buf // Pass Dockerfile as stdin // Execute err = c.Run() if err != nil { return fmt.Errorf("unable to run command: %w", err) } // Check execution error if !sh.CmdRan(err) { return fmt.Errorf("running '%s' failed with exit code %d", c.String(), sh.ExitStatus(err)) } // No error return nil } } // ----------------------------------------------------------------------------- func merge(t string, m interface{}) (*bytes.Buffer, error) { // Compile template dockerFileTmpl, err := template.New("Dockerfile").Parse(t) if err != nil { return nil, fmt.Errorf("unable to compile dockerfile template: %w", err) } // Merge data var buf bytes.Buffer if errTmpl := dockerFileTmpl.Execute(&buf, m); errTmpl != nil { return nil, errTmpl } // Return buffer without error return &buf, nil }