diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 76c1dc48..71a87878 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -14,11 +14,17 @@ jobs: build-matrix: strategy: matrix: - os: [ ubuntu-latest, windows-2019, windows-2022 ] + os: + - ubuntu-24.04 + - ubuntu-22.04 + - ubuntu-20.04 + - windows-2025 + - windows-2022 + - windows-2019 name: Build ${{ matrix.os }} runs-on: ${{ matrix.os }} steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - uses: ./ # test our "action.yml" 👀 - name: Smoke Test run: | @@ -37,22 +43,31 @@ jobs: name: Go Test runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - name: Go Test run: | docker build --pull --file Dockerfile.test --tag test . docker run --rm test cat coverage.out > coverage.out - - name: Codecov - uses: codecov/codecov-action@v3 + # TODO find a suitable codecov solution that doesn't require privileged access to the org/repo + #- name: Codecov + # uses: codecov/codecov-action@v3 + # with: + # files: coverage.out + # fail_ci_if_error: true + # verbose: true + # in the meantime, upload coverage to GHA + - run: docker run --rm -i test go tool cover -html /dev/stdin -o /dev/stdout < coverage.out > coverage.html + - uses: actions/upload-artifact@v4 with: - files: coverage.out - fail_ci_if_error: true - verbose: true + name: coverage + path: coverage.* + include-hidden-files: true + if-no-files-found: error dockerfile: name: Test Dockerfile runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - name: Build Dockerfile run: | docker build --pull . @@ -60,7 +75,7 @@ jobs: name: Test Dockerfile.release runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - name: Build Dockerfile.release run: | docker build --pull --file Dockerfile.release . diff --git a/Dockerfile b/Dockerfile index 199b67a1..e98b34d0 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,4 +1,4 @@ -FROM golang:1.20-bullseye AS build +FROM golang:1.21-bookworm AS build SHELL ["bash", "-Eeuo", "pipefail", "-xc"] diff --git a/Dockerfile.release b/Dockerfile.release index 0c183511..bfaa3471 100644 --- a/Dockerfile.release +++ b/Dockerfile.release @@ -1,4 +1,4 @@ -FROM golang:1.20-bullseye +FROM golang:1.21-bookworm SHELL ["bash", "-Eeuo", "pipefail", "-xc"] diff --git a/Dockerfile.test b/Dockerfile.test index da615985..1f923a45 100644 --- a/Dockerfile.test +++ b/Dockerfile.test @@ -1,4 +1,4 @@ -FROM golang:1.20-bullseye +FROM golang:1.21-bookworm SHELL ["bash", "-Eeuo", "pipefail", "-xc"] diff --git a/action.yml b/action.yml index 88ac9685..32332522 100644 --- a/action.yml +++ b/action.yml @@ -8,7 +8,7 @@ description: 'Install the "bashbrew" tool in GITHUB_PATH' runs: using: 'composite' steps: - - uses: actions/setup-go@v3 + - uses: actions/setup-go@v5 with: go-version-file: '${{ github.action_path }}/go.mod' - run: | diff --git a/bashbrew.sh b/bashbrew.sh index 42c8f440..9a5a6e36 100755 --- a/bashbrew.sh +++ b/bashbrew.sh @@ -6,11 +6,11 @@ set -Eeuo pipefail dir="$(readlink -f "$BASH_SOURCE")" dir="$(dirname "$dir")" -: "${CGO_ENABLED:=0}" -export GO111MODULE=on CGO_ENABLED +: "${CGO_ENABLED=0}" "${GOTOOLCHAIN=local}" +export CGO_ENABLED GOTOOLCHAIN ( cd "$dir" - go build -o bin/bashbrew ./cmd/bashbrew > /dev/null + go build -trimpath -o bin/bashbrew ./cmd/bashbrew > /dev/null ) exec "$dir/bin/bashbrew" "$@" diff --git a/cmd/bashbrew/docker.go b/cmd/bashbrew/docker.go index 8dd91552..596d06c4 100644 --- a/cmd/bashbrew/docker.go +++ b/cmd/bashbrew/docker.go @@ -1,7 +1,6 @@ package main import ( - "bufio" "bytes" "crypto/sha256" "encoding/hex" @@ -10,21 +9,14 @@ import ( "os" "os/exec" "path" - "strconv" "strings" + "sync" "github.com/docker-library/bashbrew/manifest" + "github.com/docker-library/bashbrew/pkg/dockerfile" "github.com/urfave/cli" ) -type dockerfileMetadata struct { - StageFroms []string // every image "FROM" instruction value (or the parent stage's FROM value in the case of a named stage) - StageNames []string // the name of any named stage (in order) - StageNameFroms map[string]string // map of stage names to FROM values (or the parent stage's FROM value in the case of a named stage), useful for resolving stage names to FROM values - - Froms []string // every "FROM" or "COPY --from=xxx" value (minus named and/or numbered stages in the case of "--from=") -} - // this returns the "FROM" value for the last stage (which essentially determines the "base" for the final published image) func (r Repo) ArchLastStageFrom(arch string, entry *manifest.Manifest2822Entry) (string, error) { dockerfileMeta, err := r.archDockerfileMetadata(arch, entry) @@ -46,27 +38,25 @@ func (r Repo) ArchDockerFroms(arch string, entry *manifest.Manifest2822Entry) ([ return dockerfileMeta.Froms, nil } -func (r Repo) dockerfileMetadata(entry *manifest.Manifest2822Entry) (*dockerfileMetadata, error) { +func (r Repo) dockerfileMetadata(entry *manifest.Manifest2822Entry) (dockerfile.Metadata, error) { return r.archDockerfileMetadata(arch, entry) } -var dockerfileMetadataCache = map[string]*dockerfileMetadata{} +var ( + dockerfileMetadataCache = map[string]dockerfile.Metadata{} + scratchDockerfileMetadata = sync.OnceValues(func() (dockerfile.Metadata, error) { + return dockerfile.Parse(`FROM scratch`) + }) +) -func (r Repo) archDockerfileMetadata(arch string, entry *manifest.Manifest2822Entry) (*dockerfileMetadata, error) { +func (r Repo) archDockerfileMetadata(arch string, entry *manifest.Manifest2822Entry) (dockerfile.Metadata, error) { if builder := entry.ArchBuilder(arch); builder == "oci-import" { - return &dockerfileMetadata{ - StageFroms: []string{ - "scratch", - }, - Froms: []string{ - "scratch", - }, - }, nil + return scratchDockerfileMetadata() } commit, err := r.fetchGitRepo(arch, entry) if err != nil { - return nil, cli.NewMultiError(fmt.Errorf("failed fetching Git repo for arch %q from entry %q", arch, entry.String()), err) + return dockerfile.Metadata{}, cli.NewMultiError(fmt.Errorf("failed fetching Git repo for arch %q from entry %q", arch, entry.String()), err) } dockerfileFile := path.Join(entry.ArchDirectory(arch), entry.ArchFile(arch)) @@ -79,116 +69,20 @@ func (r Repo) archDockerfileMetadata(arch string, entry *manifest.Manifest2822En return meta, nil } - dockerfile, err := gitShow(commit, dockerfileFile) + df, err := gitShow(commit, dockerfileFile) if err != nil { - return nil, cli.NewMultiError(fmt.Errorf(`failed "git show" for %q from commit %q`, dockerfileFile, commit), err) + return dockerfile.Metadata{}, cli.NewMultiError(fmt.Errorf(`failed "git show" for %q from commit %q`, dockerfileFile, commit), err) } - meta, err := parseDockerfileMetadata(dockerfile) + meta, err := dockerfile.Parse(df) if err != nil { - return nil, cli.NewMultiError(fmt.Errorf(`failed parsing Dockerfile metadata for %q from commit %q`, dockerfileFile, commit), err) + return dockerfile.Metadata{}, cli.NewMultiError(fmt.Errorf(`failed parsing Dockerfile metadata for %q from commit %q`, dockerfileFile, commit), err) } dockerfileMetadataCache[cacheKey] = meta return meta, nil } -func parseDockerfileMetadata(dockerfile string) (*dockerfileMetadata, error) { - meta := &dockerfileMetadata{ - // panic: assignment to entry in nil map - StageNameFroms: map[string]string{}, - // (nil slices work fine) - } - - scanner := bufio.NewScanner(strings.NewReader(dockerfile)) - for scanner.Scan() { - line := strings.TrimSpace(scanner.Text()) - - if line == "" { - // ignore blank lines - continue - } - - if line[0] == '#' { - // TODO handle "escape" parser directive - // TODO handle "syntax" parser directive -- explode appropriately (since custom syntax invalidates our Dockerfile parsing) - // ignore comments - continue - } - - // handle line continuations - // (TODO see note above regarding "escape" parser directive) - for line[len(line)-1] == '\\' && scanner.Scan() { - nextLine := strings.TrimSpace(scanner.Text()) - if nextLine == "" || nextLine[0] == '#' { - // ignore blank lines and comments - continue - } - line = line[0:len(line)-1] + nextLine - } - - fields := strings.Fields(line) - if len(fields) < 1 { - // must be a much more complex empty line?? - continue - } - instruction := strings.ToUpper(fields[0]) - - // TODO balk at ARG / $ in from values - - switch instruction { - case "FROM": - from := fields[1] - - if stageFrom, ok := meta.StageNameFroms[from]; ok { - // if this is a valid stage name, we should resolve it back to the original FROM value of that previous stage (we don't care about inter-stage dependencies for the purposes of either tag dependency calculation or tag building -- just how many there are and what external things they require) - from = stageFrom - } - - // make sure to add ":latest" if it's implied - from = latestizeRepoTag(from) - - meta.StageFroms = append(meta.StageFroms, from) - meta.Froms = append(meta.Froms, from) - - if len(fields) == 4 && strings.ToUpper(fields[2]) == "AS" { - stageName := fields[3] - meta.StageNames = append(meta.StageNames, stageName) - meta.StageNameFroms[stageName] = from - } - case "COPY": - for _, arg := range fields[1:] { - if !strings.HasPrefix(arg, "--") { - // doesn't appear to be a "flag"; time to bail! - break - } - if !strings.HasPrefix(arg, "--from=") { - // ignore any flags we're not interested in - continue - } - from := arg[len("--from="):] - - if stageFrom, ok := meta.StageNameFroms[from]; ok { - // see note above regarding stage names in FROM - from = stageFrom - } else if stageNumber, err := strconv.Atoi(from); err == nil && stageNumber < len(meta.StageFroms) { - // must be a stage number, we should resolve it too - from = meta.StageFroms[stageNumber] - } - - // make sure to add ":latest" if it's implied - from = latestizeRepoTag(from) - - meta.Froms = append(meta.Froms, from) - } - } - } - if err := scanner.Err(); err != nil { - return nil, err - } - return meta, nil -} - func (r Repo) DockerCacheName(entry *manifest.Manifest2822Entry) (string, error) { cacheHash, err := r.dockerCacheHash(entry) if err != nil { diff --git a/cmd/bashbrew/repo.go b/cmd/bashbrew/repo.go index 9005b891..3b063d41 100644 --- a/cmd/bashbrew/repo.go +++ b/cmd/bashbrew/repo.go @@ -6,7 +6,6 @@ import ( "path" "path/filepath" "sort" - "strings" "github.com/docker-library/bashbrew/manifest" ) @@ -39,13 +38,6 @@ func repos(all bool, args ...string) ([]string, error) { return ret, nil } -func latestizeRepoTag(repoTag string) string { - if repoTag != "scratch" && strings.IndexRune(repoTag, ':') < 0 { - return repoTag + ":latest" - } - return repoTag -} - type Repo struct { RepoName string TagName string diff --git a/cmd/bashbrew/version.go b/cmd/bashbrew/version.go index b6a3e22b..1f6b71a1 100644 --- a/cmd/bashbrew/version.go +++ b/cmd/bashbrew/version.go @@ -1,3 +1,3 @@ package main -const version = "v0.1.12" +const version = "v0.1.13" diff --git a/go.mod b/go.mod index c9a993be..bf1205a5 100644 --- a/go.mod +++ b/go.mod @@ -1,6 +1,6 @@ module github.com/docker-library/bashbrew -go 1.20 +go 1.21 require ( github.com/containerd/containerd v1.6.19 diff --git a/go.sum b/go.sum index ba015590..5241db5c 100644 --- a/go.sum +++ b/go.sum @@ -617,6 +617,7 @@ github.com/prometheus/procfs v0.1.3/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4O github.com/prometheus/procfs v0.2.0/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU= github.com/prometheus/procfs v0.6.0/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA= github.com/prometheus/procfs v0.7.3 h1:4jVXhlkAyzOScmCkXBTOLRLTz8EeU+eyjrwB/EPq0VU= +github.com/prometheus/procfs v0.7.3/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA= github.com/prometheus/tsdb v0.7.1/go.mod h1:qhTCs0VvXwvX/y3TZrWD7rabWM+ijKTux40TwIPHuXU= github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg= github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ= diff --git a/manifest/rfc2822.go b/manifest/rfc2822.go index c94cb992..e415f83d 100644 --- a/manifest/rfc2822.go +++ b/manifest/rfc2822.go @@ -16,7 +16,7 @@ import ( ) var ( - GitCommitRegex = regexp.MustCompile(`^[0-9a-f]{1,64}$`) + GitCommitRegex = regexp.MustCompile(`^([0-9a-f]{40}|[0-9a-f]{64})$`) GitFetchRegex = regexp.MustCompile(`^refs/(heads|tags)/[^*?:]+$`) // https://github.com/docker/distribution/blob/v2.7.1/reference/regexp.go#L37 diff --git a/pkg/dockerfile/parse.go b/pkg/dockerfile/parse.go new file mode 100644 index 00000000..715a788a --- /dev/null +++ b/pkg/dockerfile/parse.go @@ -0,0 +1,182 @@ +package dockerfile + +import ( + "bufio" + "io" + "strconv" + "strings" + "unicode" +) + +type Metadata struct { + StageFroms []string // every image "FROM" instruction value (or the parent stage's FROM value in the case of a named stage) + StageNames []string // the name of any named stage (in order) + StageNameFroms map[string]string // map of stage names to FROM values (or the parent stage's FROM value in the case of a named stage), useful for resolving stage names to FROM values + + Froms []string // every "FROM" or "COPY --from=xxx" value (minus named and/or numbered stages in the case of "--from=") +} + +func Parse(dockerfile string) (Metadata, error) { + return ParseReader(strings.NewReader(dockerfile)) +} + +func ParseReader(dockerfile io.Reader) (Metadata, error) { + meta := Metadata{ + // panic: assignment to entry in nil map + StageNameFroms: map[string]string{}, + // (nil slices work fine) + } + + scanner := bufio.NewScanner(dockerfile) + for scanner.Scan() { + line := strings.TrimSpace(scanner.Text()) + + if line == "" { + // ignore straight up blank lines (no complexity) + continue + } + + // (we can't have a comment that ends in a continuation line - that's not continuation, that's part of the comment) + if line[0] == '#' { + // TODO handle "escape" parser directive + // TODO handle "syntax" parser directive -- explode appropriately (since custom syntax invalidates our Dockerfile parsing) + // ignore comments + continue + } + + // handle line continuations + // (TODO see note above regarding "escape" parser directive) + for line[len(line)-1] == '\\' { + if !scanner.Scan() { + line = line[0:len(line)-1] + break + } + // "strings.TrimRightFunc(IsSpace)" because whitespace *after* the escape character is supported and ignored 🙈 + nextLine := strings.TrimRightFunc(scanner.Text(), unicode.IsSpace) + if nextLine == "" { // if it's all space, TrimRight will be TrimSpace 😏 + // ignore "empty continuation" lines (https://github.com/moby/moby/pull/33719) + continue + } + if strings.TrimLeftFunc(nextLine, unicode.IsSpace)[0] == '#' { + // ignore comments inside continuation (https://github.com/moby/moby/issues/29005) + continue + } + line = line[0:len(line)-1] + nextLine + } + + + // TODO *technically* a line like " RUN echo hi " should be parsed as "RUN" "echo hi" (cut off instruction, then the rest of the line with TrimSpace), but for our needs "strings.Fields" is good enough for now + + // line = strings.TrimSpace(line) // (emulated below; "strings.Fields" does essentially the same exact thing so we don't need to do it explicitly here too) + + fields := strings.Fields(line) + + if len(fields) < 1 { + // ignore empty lines + continue + } + + instruction := strings.ToUpper(fields[0]) + + // TODO balk at ARG / $ in from values + + switch instruction { + case "FROM": + from := fields[1] + + if stageFrom, ok := meta.StageNameFroms[from]; ok { + // if this is a valid stage name, we should resolve it back to the original FROM value of that previous stage (we don't care about inter-stage dependencies for the purposes of either tag dependency calculation or tag building -- just how many there are and what external things they require) + from = stageFrom + } + + // make sure to add ":latest" if it's implied + from = latestizeRepoTag(from) + + meta.StageFroms = append(meta.StageFroms, from) + meta.Froms = append(meta.Froms, from) + + if len(fields) == 4 && strings.ToUpper(fields[2]) == "AS" { + stageName := fields[3] + meta.StageNames = append(meta.StageNames, stageName) + meta.StageNameFroms[stageName] = from + } + + case "COPY": + for _, arg := range fields[1:] { + if !strings.HasPrefix(arg, "--") { + // doesn't appear to be a "flag"; time to bail! + break + } + if !strings.HasPrefix(arg, "--from=") { + // ignore any flags we're not interested in + continue + } + from := arg[len("--from="):] + + if stageFrom, ok := meta.StageNameFroms[from]; ok { + // see note above regarding stage names in FROM + from = stageFrom + } else if stageNumber, err := strconv.Atoi(from); err == nil && stageNumber < len(meta.StageFroms) { + // must be a stage number, we should resolve it too + from = meta.StageFroms[stageNumber] + } + + // make sure to add ":latest" if it's implied + from = latestizeRepoTag(from) + + meta.Froms = append(meta.Froms, from) + } + + case "RUN": // TODO combine this and the above COPY-parsing code somehow sanely + for _, arg := range fields[1:] { + if !strings.HasPrefix(arg, "--") { + // doesn't appear to be a "flag"; time to bail! + break + } + if !strings.HasPrefix(arg, "--mount=") { + // ignore any flags we're not interested in + continue + } + csv := arg[len("--mount="):] + // TODO more correct CSV parsing + fields := strings.Split(csv, ",") + var mountType, from string + for _, field := range fields { + if strings.HasPrefix(field, "type=") { + mountType = field[len("type="):] + continue + } + if strings.HasPrefix(field, "from=") { + from = field[len("from="):] + continue + } + } + if mountType != "bind" || from == "" { + // this is probably something we should be worried about, but not something we're interested in parsing + continue + } + + if stageFrom, ok := meta.StageNameFroms[from]; ok { + // see note above regarding stage names in FROM + from = stageFrom + } else if stageNumber, err := strconv.Atoi(from); err == nil && stageNumber < len(meta.StageFroms) { + // must be a stage number, we should resolve it too + from = meta.StageFroms[stageNumber] + } + + // make sure to add ":latest" if it's implied + from = latestizeRepoTag(from) + + meta.Froms = append(meta.Froms, from) + } + } + } + return meta, scanner.Err() +} + +func latestizeRepoTag(repoTag string) string { + if repoTag != "scratch" && strings.IndexRune(repoTag, ':') < 0 { + return repoTag + ":latest" + } + return repoTag +} diff --git a/pkg/dockerfile/parse_test.go b/pkg/dockerfile/parse_test.go new file mode 100644 index 00000000..e4cd31c9 --- /dev/null +++ b/pkg/dockerfile/parse_test.go @@ -0,0 +1,183 @@ +package dockerfile_test + +import ( + "reflect" + "testing" + + "github.com/docker-library/bashbrew/pkg/dockerfile" +) + +func TestParse(t *testing.T) { + for _, td := range []struct { + name string + dockerfile string + metadata dockerfile.Metadata + }{ + { + dockerfile: `FROM scratch`, + metadata: dockerfile.Metadata{ + Froms: []string{"scratch"}, + }, + }, + { + dockerfile: `from bash`, + metadata: dockerfile.Metadata{ + Froms: []string{"bash:latest"}, + }, + }, + { + dockerfile: `fRoM bash:5`, + metadata: dockerfile.Metadata{ + Froms: []string{"bash:5"}, + }, + }, + { + name: "comments+whitespace+continuation", + dockerfile: ` + FROM scratch + + # foo + + # bar + + FROM bash + RUN echo \ + # comment inside continuation + hello \ + world + `, + metadata: dockerfile.Metadata{ + Froms: []string{"scratch", "bash:latest"}, + }, + }, + { + name: "multi-stage", + dockerfile: ` + FROM bash:latest AS foo + FROM busybox:uclibc + # intermediate stage without name + FROM bash:5 AS bar + FROM foo AS foo2 + FROM scratch + COPY --from=foo / / + COPY --from=bar / / + COPY --from=foo2 / / + COPY --chown=1234:5678 /foo /bar + `, + metadata: dockerfile.Metadata{ + StageFroms: []string{"bash:latest", "busybox:uclibc", "bash:5", "bash:latest", "scratch"}, + StageNames: []string{"foo", "bar", "foo2"}, + StageNameFroms: map[string]string{ + "foo": "bash:latest", + "bar": "bash:5", + "foo2": "bash:latest", + }, + Froms: []string{"bash:latest", "busybox:uclibc", "bash:5", "bash:latest", "scratch", "bash:latest", "bash:5", "bash:latest"}, + }, + }, + { + name: "empty continuations", + dockerfile: ` + \ + \ + \ + \ + \ + \ + `, + }, + { + name: "continuation edge cases", + dockerfile: ` + # continuation does not apply to this comment \ + FROM scratch + # but everything below this is part of a single continuation + + FROM\ + + \ + \ + + \ + \ + + # comments inside are fine + # and really yucky empty lines: + + \ + \ + + scratch\ + `, + metadata: dockerfile.Metadata{ + Froms: []string{"scratch", "scratch"}, + }, + }, + { + // TODO is this even something that's supported by classic builder/buildkit? (Tianon *thinks* it was supported once, but maybe he's misremembering and it's never been a thing Dockerfiles, only docker build --target=N ?) + name: "numbered stages", + dockerfile: ` + FROM bash:latest + RUN echo foo > /foo + FROM scratch + COPY --from=0 /foo /foo + FROM scratch + COPY --chown=1234:5678 --from=1 /foo /foo + FROM bash:latest + RUN --mount=type=bind,from=2 cat /foo + `, + metadata: dockerfile.Metadata{ + StageFroms: []string{"bash:latest", "scratch", "scratch", "bash:latest"}, + Froms: []string{"bash:latest", "scratch", "bash:latest", "scratch", "scratch", "bash:latest", "scratch"}, + }, + }, + { + name: "RUN --mount", + dockerfile: ` + FROM scratch + RUN --mount=type=bind,from=busybox:uclibc,target=/tmp ["/tmp/bin/sh","-euxc","echo foo > /foo"] + `, + metadata: dockerfile.Metadata{ + StageFroms: []string{"scratch"}, + Froms: []string{"scratch", "busybox:uclibc"}, + }, + }, + { + name: "RUN --mount=stage", + dockerfile: ` + FROM busybox:uclibc AS bb + RUN --network=none echo hi, a flag that is ignored + RUN --mount=type=tmpfs,dst=/foo touch /foo/bar # this should be ignored + FROM scratch + RUN --mount=type=bind,from=bb,target=/tmp ["/tmp/bin/sh","-euxc","echo foo > /foo"] + `, + metadata: dockerfile.Metadata{ + StageFroms: []string{"busybox:uclibc", "scratch"}, + StageNames: []string{"bb"}, + StageNameFroms: map[string]string{"bb": "busybox:uclibc"}, + Froms: []string{"busybox:uclibc", "scratch", "busybox:uclibc"}, + }, + }, + } { + td := td + // some light normalization + if td.name == "" { + td.name = td.dockerfile + } + if len(td.metadata.Froms) > 0 && len(td.metadata.StageFroms) == 0 { + td.metadata.StageFroms = td.metadata.Froms + } + if td.metadata.StageNameFroms == nil { + td.metadata.StageNameFroms = map[string]string{} + } + t.Run(td.name, func(t *testing.T) { + parsed, err := dockerfile.Parse(td.dockerfile) + if err != nil { + t.Fatal(err) + } + if !reflect.DeepEqual(parsed, td.metadata) { + t.Fatalf("expected:\n%#v\ngot:\n%#v", td.metadata, parsed) + } + }) + } +} diff --git a/scripts/bashbrew-arch-to-goenv.sh b/scripts/bashbrew-arch-to-goenv.sh index b447edbc..3b7e9af5 100755 --- a/scripts/bashbrew-arch-to-goenv.sh +++ b/scripts/bashbrew-arch-to-goenv.sh @@ -1,5 +1,5 @@ -#!/bin/sh -set -eu +#!/usr/bin/env bash +set -Eeuo pipefail # usage: (from within another script) # shell="$(./bashbrew-arch-to-goenv.sh arm32v6)" @@ -10,33 +10,65 @@ bashbrewArch="$1"; shift # "amd64", "arm32v5", "windows-amd64", etc. os="${bashbrewArch%%-*}" [ "$os" != "$bashbrewArch" ] || os='linux' -printf 'export GOOS="%s"\n' "$os" - arch="${bashbrewArch#${os}-}" + +declare -A envs=( + # https://pkg.go.dev/cmd/go#hdr-Build_constraints + # https://gist.github.com/asukakenji/f15ba7e588ac42795f421b48b8aede63 + + [GOOS]="$os" + [GOARCH]="$arch" + + # https://go.dev/wiki/MinimumRequirements#architectures + [GO386]= + [GOAMD64]= + [GOARM64]= + [GOARM]= + [GOMIPS64]= + [GOPPC64]= + [GORISCV64]= +) + case "$arch" in + amd64) + envs[GOAMD64]='v1' + ;; + arm32v*) - printf 'export GOARCH="%s"\n' 'arm' - printf 'export GOARM="%s"\n' "${arch#arm32v}" + envs[GOARCH]='arm' + envs[GOARM]="${arch#arm32v}" # "6", "7", "8", etc ;; arm64v*) - printf 'export GOARCH="%s"\n' 'arm64' - # no GOARM(64) for arm64 (yet?): - # https://github.com/golang/go/blob/be0e0b06ac53d3d02ea83b479790404057b6f19b/src/internal/buildcfg/cfg.go#L86 - # https://github.com/golang/go/issues/60905 - #printf 'export GOARM64="v%s"\n' "${arch#arm64v}" - printf 'unset GOARM\n' + envs[GOARCH]='arm64' + version="${arch#arm64v}" + if [ -z "${version%%[0-9]}" ]; then + # if the version is just a raw number ("8", "9"), we should append ".0" + # https://go-review.googlesource.com/c/go/+/559555/comment/e2049987_1bc3a065/ + # (Go has "v8.0" but no bare "v8") + version+='.0' + fi + envs[GOARM64]="v$version" # "v8.0", "v9.0", etc ;; i386) - printf 'export GOARCH="%s"\n' '386' - printf 'unset GOARM\n' + envs[GOARCH]='386' ;; - # TODO GOAMD64: https://github.com/golang/go/blob/be0e0b06ac53d3d02ea83b479790404057b6f19b/src/internal/buildcfg/cfg.go#L57-L70 (v1 implied) - - *) - printf 'export GOARCH="%s"\n' "$arch" - printf 'unset GOARM\n' - ;; + # TODO GOMIPS64? + # TODO GOPPC64? + # TODO GORISCV64? esac + +exports= +unsets= +for key in "${!envs[@]}"; do + val="${envs[$key]}" + if [ -n "$val" ]; then + exports+=" $(printf '%s=%q' "$key" "$val")" + else + unsets+=" $key" + fi +done +[ -z "$exports" ] || printf 'export%s\n' "$exports" +[ -z "$unsets" ] || printf 'unset%s\n' "$unsets" diff --git a/scripts/bashbrew-host-arch.sh b/scripts/bashbrew-host-arch.sh index a4138c57..bc674b81 100755 --- a/scripts/bashbrew-host-arch.sh +++ b/scripts/bashbrew-host-arch.sh @@ -11,6 +11,8 @@ if command -v apk > /dev/null && tryArch="$(apk --print-arch)"; then arch="$tryArch" elif command -v dpkg > /dev/null && tryArch="$(dpkg --print-architecture)"; then arch="${tryArch##*-}" +elif command -v rpm > /dev/null && tryArch="$(rpm --query --queryformat='%{ARCH}' rpm)"; then + arch="$tryArch" elif command -v uname > /dev/null && tryArch="$(uname -m)"; then echo >&2 "warning: neither of 'dpkg' or 'apk' found, falling back to 'uname'" arch="$tryArch" @@ -28,7 +30,8 @@ case "$arch" in amd64 | x86_64) found 'amd64' ;; arm64 | aarch64) found 'arm64v8' ;; armel) found 'arm32v5' ;; - armv7) found 'arm32v7' ;; + armv6*) found 'arm32v6' ;; + armv7*) found 'arm32v7' ;; i[3456]86 | x86) found 'i386' ;; mips64el) found 'mips64le' ;; # TODO "uname -m" is just "mips64" (which is also "apk --print-arch" on big-endian MIPS) so we ought to disambiguate that somehow ppc64el | ppc64le) found 'ppc64le' ;; diff --git a/scripts/github-actions/example-ci.yml b/scripts/github-actions/example-ci.yml index 61ffb56e..9e33b8bf 100644 --- a/scripts/github-actions/example-ci.yml +++ b/scripts/github-actions/example-ci.yml @@ -26,7 +26,7 @@ jobs: outputs: strategy: ${{ steps.generate-jobs.outputs.strategy }} steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - uses: docker-library/bashbrew@HEAD - id: generate-jobs name: Generate Jobs @@ -44,7 +44,7 @@ jobs: name: ${{ matrix.name }} runs-on: ${{ matrix.os }} steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - name: Prepare Environment run: ${{ matrix.runs.prepare }} - name: Pull Dependencies diff --git a/scripts/github-actions/generate.sh b/scripts/github-actions/generate.sh index 5d240cfd..235bb244 100755 --- a/scripts/github-actions/generate.sh +++ b/scripts/github-actions/generate.sh @@ -21,10 +21,11 @@ if ! command -v bashbrew &> /dev/null; then bashbrew --version > /dev/null fi -mkdir "$tmp/library" -export BASHBREW_LIBRARY="$tmp/library" +mkdir "$tmp/library" # not exporting this as BASHBREW_LIBRARY yet so that "generate-stackbrew-library.sh" gets the externally-set value of BASHBREW_LIBRARY (or unset value) so it can use that to change behavior (see https://github.com/docker-library/buildpack-deps/commit/cc2dc88e04e82cb4c4c2091205d888a5d5b386f3 for an example) + +eval "${GENERATE_STACKBREW_LIBRARY:-./generate-stackbrew-library.sh}" > "$tmp/library/$image" -eval "${GENERATE_STACKBREW_LIBRARY:-./generate-stackbrew-library.sh}" > "$BASHBREW_LIBRARY/$image" +export BASHBREW_LIBRARY="$tmp/library" # if we don't appear to be able to fetch the listed commits, they might live in a PR branch, so we should force them into the Bashbrew cache directly to allow it to do what it needs if ! bashbrew fetch "$image" &> /dev/null; then @@ -60,7 +61,9 @@ for tag in $tags; do { name: .name, os: ( - if (.constraints | contains(["windowsservercore-ltsc2022"])) or (.constraints | contains(["nanoserver-ltsc2022"])) then + if (.constraints | contains(["windowsservercore-ltsc2025"])) or (.constraints | contains(["nanoserver-ltsc2025"])) then + "windows-2025" + elif (.constraints | contains(["windowsservercore-ltsc2022"])) or (.constraints | contains(["nanoserver-ltsc2022"])) then "windows-2022" elif (.constraints | contains(["windowsservercore-1809"])) or (.constraints | contains(["nanoserver-1809"])) then "windows-2019" @@ -198,14 +201,9 @@ strategy="$( "# https://github.com/docker-library/bashbrew/pull/43", if ([ .meta.entries[].builder ] | index("buildkit")) then # https://github.com/docker-library/bashbrew/pull/70#issuecomment-1461033890 (we need to _not_ set BASHBREW_ARCH here) - "if [ -x ~/oi/.bin/bashbrew-buildkit-env-setup.sh ]; then", - "\t# https://github.com/docker-library/official-images/pull/14212", - "\tbuildkitEnvs=\"$(~/oi/.bin/bashbrew-buildkit-env-setup.sh)\"", - "\tjq <<<\"$buildkitEnvs\" -r \(env.envObjectToGitHubEnvFileJQ | @sh) | tee -a \"$GITHUB_ENV\"", - "else", - "\tBASHBREW_BUILDKIT_SYNTAX=\"$(< ~/oi/.bashbrew-buildkit-syntax)\"; export BASHBREW_BUILDKIT_SYNTAX", - "\tprintf \"BASHBREW_BUILDKIT_SYNTAX=%q\\n\" \"$BASHBREW_BUILDKIT_SYNTAX\" >> \"$GITHUB_ENV\"", - "fi", + # https://github.com/docker-library/official-images/pull/14212 + "buildkitEnvs=\"$(~/oi/.bin/bashbrew-buildkit-env-setup.sh)\"", + "jq <<<\"$buildkitEnvs\" -r \(env.envObjectToGitHubEnvFileJQ | @sh) | tee -a \"$GITHUB_ENV\"", empty else empty pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy