From 39f5cc8b24c9c328eaa43ec96043d8891407af0a Mon Sep 17 00:00:00 2001 From: Gregor Noczinski Date: Thu, 10 Oct 2024 19:55:18 +0200 Subject: [PATCH] More adjustments --- .github/workflows/ci.yml | 128 ++++++++---- .github/workflows/lint.yml | 9 +- .github/workflows/pr-publish.yaml | 90 --------- .github/workflows/pr-update.yml | 52 +++++ .github/workflows/release.yml | 38 ++-- cmd/build/base.go | 21 ++ cmd/build/build-artifact.go | 174 +++-------------- cmd/build/build-binary.go | 40 +--- cmd/build/build-digest.go | 4 - cmd/build/build-image-layer.go | 4 - cmd/build/build-image.go | 137 ++++++++++--- cmd/build/build.go | 177 +++++++++++++++-- cmd/build/contrib/dummy-for-oci-images.yaml | 31 +++ cmd/build/contrib/group | 1 + cmd/build/contrib/passwd | 1 + cmd/build/contrib/shadow | 1 + cmd/build/dependencies-images-files.go | 11 +- cmd/build/os.go | 13 +- cmd/build/repo-actions.go | 16 +- cmd/build/repo-packages.go | 29 +-- cmd/build/repo-prs.go | 204 ++++++++++---------- cmd/build/repo-releases.go | 65 +++++++ cmd/build/repo.go | 18 ++ cmd/build/version.go | 6 +- cmd/build/version_test.go | 31 ++- docs/reference/connection/ssh.md | 2 +- go.mod | 28 +-- go.sum | 63 +++--- pkg/configuration/authorization-htpasswd.go | 2 +- pkg/configuration/configuration.go | 12 +- pkg/service/service.go | 10 + pkg/user/etc-colon-repository_test.go | 9 + 32 files changed, 852 insertions(+), 575 deletions(-) delete mode 100644 .github/workflows/pr-publish.yaml create mode 100644 .github/workflows/pr-update.yml create mode 100644 cmd/build/contrib/dummy-for-oci-images.yaml create mode 100644 cmd/build/contrib/group create mode 100644 cmd/build/contrib/passwd create mode 100644 cmd/build/contrib/shadow diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 9692fa0..41a5673 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -1,28 +1,65 @@ name: Continuous Integration -on: - push: +env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + BIFROEST_VENDOR: "Engity GmbH" + +on: pull_request: types: - opened - reopened + - synchronize + - ready_for_review jobs: - test: - name: Tests - strategy: - matrix: - os: [ ubuntu-latest, windows-latest ] - runs-on: ${{ matrix.os }} + evaluate: + name: Evaluate + runs-on: ubuntu-latest + outputs: + commit: "${{ steps.refs.outputs.commit }}" + version: "${{ steps.refs.outputs.version }}" + ref: "${{ steps.refs.outputs.ref }}" + pr: "${{ steps.refs.outputs.pr }}" + stage-binary: "${{ steps.refs.outputs.stage-binary }}" + stage-archive: "${{ steps.refs.outputs.stage-archive }}" + stage-image: "${{ steps.refs.outputs.stage-image }}" + stage-digest: "${{ steps.refs.outputs.stage-digest }}" + stage-publish: "${{ steps.refs.outputs.stage-publish }}" steps: + - name: Checkout code + uses: actions/checkout@v4 + with: + fetch-depth: 0 + - name: Install Go uses: actions/setup-go@v5 with: + cache: false go-version-file: go.mod check-latest: true - cache-dependency-path: | - go.sum + - name: Cache Go + uses: actions/cache@v4 + with: + path: ~/go/pkg/mod + key: ${{ runner.os }}-go-${{ hashFiles('**/go.sum') }} + restore-keys: | + ${{ runner.os }}-go- + + - name: Execute + id: refs + run: | + go run ./cmd/build evaluate-environment --log.colorMode=always + + test: + name: Tests + needs: [ evaluate ] + strategy: + matrix: + os: [ ubuntu-latest, windows-latest ] + runs-on: ${{ matrix.os }} + steps: - name: Install Ubuntu dependencies if: ${{ matrix.os == 'ubuntu-latest' }} run: sudo apt install libpam0g-dev @@ -32,12 +69,17 @@ jobs: with: fetch-depth: 0 - - name: Cache + - name: Install Go + uses: actions/setup-go@v5 + with: + cache: false + go-version-file: go.mod + check-latest: true + + - name: Cache Go uses: actions/cache@v4 with: - path: | - .cache - ~/go/pkg/mod + path: ~/go/pkg/mod key: ${{ runner.os }}-go-${{ hashFiles('**/go.sum') }} restore-keys: | ${{ runner.os }}-go- @@ -62,50 +104,45 @@ jobs: goveralls "-coverprofile=profile.cov" "-service=github" "-parallel" "-flagname=go-${{ matrix.os }}" package: name: Package - strategy: - matrix: - os: [ ubuntu-latest ] - runs-on: ${{ matrix.os }} + needs: [ evaluate ] + runs-on: "ubuntu-latest" + container: + image: ghcr.io/engity-com/build-images/go steps: - - name: Install Go - uses: actions/setup-go@v5 - with: - go-version-file: go.mod - check-latest: true - cache-dependency-path: | - go.sum - - - name: Install Ubuntu dependencies - if: ${{ matrix.os == 'ubuntu-latest' }} - run: sudo apt install libpam0g-dev - - name: Checkout code uses: actions/checkout@v4 with: fetch-depth: 0 - - name: Cache + - name: Cache Go uses: actions/cache@v4 with: - path: | - .cache - ~/go/pkg/mod + path: ~/go/pkg/mod key: ${{ runner.os }}-go-${{ hashFiles('**/go.sum') }} restore-keys: | ${{ runner.os }}-go- + - name: Cache images dependencies + uses: actions/cache@v4 + with: + path: .cache/dependencies/images + key: images-dependencies + restore-keys: images-dependencies + + - name: Git configure + run: | + git config --global --add safe.directory $(pwd) + - name: Install dependencies run: | go mod download - name: Build run: | - go run ./cmd/build --log.colorMode=always - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - BIFROEST_VENDOR: engity + go run ./cmd/build build --log.colorMode=always - name: Archive package results + if: needs.evaluate.outputs.stage-publish == 'true' uses: actions/upload-artifact@v4 with: retention-days: 1 @@ -115,6 +152,7 @@ jobs: doc: name: Documentation + needs: [ evaluate ] runs-on: ubuntu-latest steps: - name: Checkout code @@ -128,7 +166,8 @@ jobs: with: python-version: 3.x - - uses: actions/cache@v4 + - name: Cache + uses: actions/cache@v4 with: path: ~/.cache/pip key: ${{ hashFiles('docs/requirements.txt') }} @@ -142,7 +181,16 @@ jobs: mkdocs --color build -c - name: Deploy + id: deploy + if: needs.evaluate.outputs.stage-publish == 'true' uses: cloudflare/wrangler-action@v3 with: apiToken: ${{ secrets.CLOUDFLARE_API_TOKEN }} - command: pages deploy --project-name=bifroest-engity-org var/doc + command: pages deploy --branch=${{ needs.evaluate.outputs.version }} --commit-dirty=true --project-name=bifroest-engity-org var/doc + + - name: Report + if: needs.evaluate.outputs.stage-publish == 'true' + env: + DEPLOYMENT_URL: ${{ steps.deploy.outputs.deployment-url }} + run: | + echo "Documentation is available at ${DEPLOYMENT_URL}" >> "$GITHUB_STEP_SUMMARY" diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index b32c310..d56c2a8 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -1,11 +1,11 @@ name: Lint on: - push: - pull_request: types: - opened - reopened + - synchronize + - ready_for_review permissions: contents: read @@ -20,14 +20,15 @@ jobs: steps: - name: Checkout code uses: actions/checkout@v4 + with: + fetch-depth: 0 - name: Install Go uses: actions/setup-go@v5 with: + cache: false go-version-file: go.mod check-latest: true - cache-dependency-path: | - go.sum - name: Install Ubuntu dependencies if: ${{ matrix.os == 'ubuntu-latest' }} diff --git a/.github/workflows/pr-publish.yaml b/.github/workflows/pr-publish.yaml deleted file mode 100644 index c29e945..0000000 --- a/.github/workflows/pr-publish.yaml +++ /dev/null @@ -1,90 +0,0 @@ -name: Pull-Requests Images - -env: - LABEL_NAME: "test publish" - WORKFLOW_CI_FN: "ci.yaml" - GITHUB_TOKEN: ${{ github.token }} - GITHUB_PR_ID: ${{github.event.number}} - GITHUB_EVENT_ACTION: ${{github.event.action}} - -concurrency: - cancel-in-progress: true - group: ${{ github.workflow }}-${{github.event.number}} - -on: - pull_request: - types: - - labeled - - unlabeled - - closed - -jobs: - build: - runs-on: ubuntu-latest - name: Inspect - if: github.event_name == 'pull_request' - permissions: - pull-requests: read - actions: write - packages: write - - steps: - - name: Checkout code - uses: actions/checkout@v4 - with: - fetch-depth: 0 - - - name: Install Go - uses: actions/setup-go@v5 - with: - go-version-file: .build/go.mod - check-latest: true - cache-dependency-path: | - .build/go.sum - - - name: Execute - id: refs - working-directory: .build - run: | - set -ex - - function hasLabel() { - go run ./cmd/build has-pr-label "${GITHUB_PR_ID}" "${LABEL_NAME}" && return 0 || retVal=$? - if [ "$retVal" -eq 1 ]; then - return 1 - fi - exit $retVal - } - - function isOpen() { - go run ./cmd/build is-pr-open "${GITHUB_PR_ID}" && return 0 || retVal=$? - if [ "$retVal" -eq 1 ]; then - return 1 - fi - exit $retVal - } - - function triggerCiWorkflow() { - go run ./cmd/build rerun-pr-workflow "${GITHUB_PR_ID}" "${WORKFLOW_CI_FN}" - } - - function removeImages() { - go run ./cmd/build delete-image-tag "pr-${GITHUB_PR_ID}" - } - - if [ "$GITHUB_EVENT_ACTION" == "labeled" ]; then - if [ "${{github.event.label.name}}" == "${LABEL_NAME}" ] && isOpen; then - triggerCiWorkflow - fi - - elif [ "$GITHUB_EVENT_ACTION" == "unlabeled" ]; then - if [ "${{github.event.label.name}}" == "${LABEL_NAME}" ] && isOpen; then - removeImages - fi - - elif [ "$GITHUB_EVENT_ACTION" == "closed" ]; then - if hasLabel; then - removeImages - fi - - fi diff --git a/.github/workflows/pr-update.yml b/.github/workflows/pr-update.yml new file mode 100644 index 0000000..274cc04 --- /dev/null +++ b/.github/workflows/pr-update.yml @@ -0,0 +1,52 @@ +name: Pull-Requests Updates + +env: + GITHUB_TOKEN: ${{ github.token }} + +concurrency: + cancel-in-progress: true + group: ${{ github.workflow }}-${{github.event.number}} + +on: + pull_request: + types: + - labeled + - unlabeled + - closed + +jobs: + build: + runs-on: ubuntu-latest + name: Inspect + if: github.event_name == 'pull_request' + permissions: + pull-requests: read + actions: write + packages: write + + steps: + - name: Checkout code + uses: actions/checkout@v4 + with: + fetch-depth: 0 + + - name: Install Go + uses: actions/setup-go@v5 + with: + cache: false + go-version-file: go.mod + check-latest: true + + - name: Cache Go + uses: actions/cache@v4 + with: + path: ~/go/pkg/mod + key: ${{ runner.os }}-go-${{ hashFiles('**/go.sum') }} + restore-keys: | + ${{ runner.os }}-go- + + - name: Execute + id: refs + run: | + set -ex + go run ./cmd/build inspect-pr-action --log.colorMode=always "${{github.event.action}}" "${{github.event.label.name}}" diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 9a32847..6985f80 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -1,5 +1,9 @@ name: "Release" +env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + BIFROEST_VENDOR: "Engity GmbH" + on: release: types: [ published ] @@ -14,33 +18,29 @@ jobs: release: name: "Release" runs-on: ubuntu-latest + container: + image: ghcr.io/engity-com/build-images/go steps: - - name: Install Go - uses: actions/setup-go@v5 - with: - go-version-file: go.mod - check-latest: true - cache-dependency-path: | - go.sum - - - name: Install Ubuntu dependencies - run: sudo apt install libpam0g-dev - - name: Checkout code uses: actions/checkout@v4 with: fetch-depth: 0 - - name: Cache + - name: Cache Go uses: actions/cache@v4 with: - path: | - .cache - ~/go/pkg/mod + path: ~/go/pkg/mod key: ${{ runner.os }}-go-${{ hashFiles('**/go.sum') }} restore-keys: | ${{ runner.os }}-go- + - name: Cache images dependencies + uses: actions/cache@v4 + with: + path: .cache/dependencies/images + key: images-dependencies + restore-keys: images-dependencies + - name: Install dependencies run: | go mod download @@ -52,10 +52,7 @@ jobs: - name: Build/Release run: | - go run ./cmd/build --log.colorMode=always - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - BIFROEST_VENDOR: engity + go run ./cmd/build build --log.colorMode=always documentation: name: "Documentation" @@ -76,7 +73,8 @@ jobs: run: | pip install -r docs/requirements.txt - - uses: actions/cache@v4 + - name: Cache + uses: actions/cache@v4 with: path: ~/.cache/pip key: ${{ hashFiles('docs/requirements.txt') }} diff --git a/cmd/build/base.go b/cmd/build/base.go index f77f89b..7bcbeff 100644 --- a/cmd/build/base.go +++ b/cmd/build/base.go @@ -3,6 +3,7 @@ package main import ( "context" "fmt" + "math" "os/user" "runtime" "strconv" @@ -28,6 +29,7 @@ func newBase() *base { result := &base{ waitTimeout: time.Second * 3, actor: currentUserName, + title: "Engity's Bifröst", } result.repo = newRepo(result) result.build = newBuild(result) @@ -44,11 +46,15 @@ type base struct { waitTimeout time.Duration actor string + title string rawCommit string rawRef string rawHeadRef string rawPr uint + optionsOutputFilename string + summaryOutputFilename string + versionP atomic.Pointer[version] commitP atomic.Pointer[string] refP atomic.Pointer[string] @@ -64,6 +70,10 @@ func (this *base) init(ctx context.Context, app *kingpin.Application) { Envar("GITHUB_ACTOR"). PlaceHolder(""). StringVar(&this.actor) + app.Flag("title", ""). + Default(this.title). + PlaceHolder(""). + StringVar(&this.title) app.Flag("commit", ""). Envar("GITHUB_SHA"). PlaceHolder("<sha>"). @@ -79,6 +89,14 @@ func (this *base) init(ctx context.Context, app *kingpin.Application) { app.Flag("pr", ""). PlaceHolder("<prNumber>"). UintVar(&this.rawPr) + app.Flag("optionsOutputFilename", ""). + Envar("GITHUB_OUTPUT"). + PlaceHolder("<filename>"). + StringVar(&this.optionsOutputFilename) + app.Flag("summaryOutputFilename", ""). + Envar("GITHUB_STEP_SUMMARY"). + PlaceHolder("<filename>"). + StringVar(&this.summaryOutputFilename) app.Command("status", ""). Action(func(*kingpin.ParseContext) error { @@ -197,6 +215,9 @@ func (this *base) resolvePr() uint { return 0 } n, _ := strconv.ParseUint(strings.TrimSuffix(v, "/merge"), 10, 64) + if n > uint64(math.MaxUint) { + return 0 // or handle the error appropriately + } return uint(n) } return 0 diff --git a/cmd/build/build-artifact.go b/cmd/build/build-artifact.go index 638f22b..abcfcfc 100644 --- a/cmd/build/build-artifact.go +++ b/cmd/build/build-artifact.go @@ -1,18 +1,17 @@ package main import ( - "archive/tar" "fmt" - "io" "iter" gos "os" + "path" + "path/filepath" "strings" "sync" v1 "github.com/google/go-containerregistry/pkg/v1" "github.com/engity-com/bifroest/pkg/common" - "github.com/engity-com/bifroest/pkg/errors" ) type buildArtifactCloser func() error @@ -36,7 +35,29 @@ func (this *buildArtifact) toLdFlags(o os) string { } func (this *buildArtifact) String() string { - return this.platform.String() + "/" + this.t.String() + ":" + this.filepath + return this.platform.String() + "/" + this.t.String() + ":" + this.name() +} + +func (this *buildArtifact) mediaType() string { + switch this.t { + case buildArtifactTypeDigest: + return "text/plain; charset=utf-8" + case buildArtifactTypeArchive: + switch strings.ToLower(path.Ext(this.name())) { + case ".tgz": + return "application/tar+gzip" + case ".zip": + return "application/zip" + default: + return "application/octet-stream" + } + default: + return "application/octet-stream" + } +} + +func (this *buildArtifact) name() string { + return filepath.Base(this.filepath) } func (this *buildArtifact) Close() (rErr error) { @@ -109,12 +130,6 @@ func (this buildArtifacts) onlyOfType(t buildArtifactType) iter.Seq[*buildArtifa }) } -func (this buildArtifacts) onlyOfEdition(e edition) iter.Seq[*buildArtifact] { - return this.filter(func(candidate *buildArtifact) bool { - return candidate.edition == e - }) -} - func (this buildArtifacts) withoutType(t buildArtifactType) iter.Seq[*buildArtifact] { return this.filter(func(candidate *buildArtifact) bool { return candidate.t != t @@ -179,145 +194,6 @@ func (this *buildArtifact) toLayer(otherItems iter.Seq2[imageArtifactLayerItem, return result.layer, nil } -func (this *buildArtifact) toTarReader(configFilename string) func() (io.ReadCloser, error) { - return func() (io.ReadCloser, error) { - success := false - pr, pw := io.Pipe() - result := &buildArtifactTarReader{owner: this, pr: pr, pw: pw} - defer common.IgnoreErrorIfFalse(&success, result.Close) - - bf, err := this.openFile() - if err != nil { - return nil, err - } - defer common.IgnoreErrorIfFalse(&success, bf.Close) - - bfi, err := bf.Stat() - if err != nil { - return nil, err - } - - cf, err := gos.Open(configFilename) - if err != nil { - return nil, err - } - defer common.IgnoreErrorIfFalse(&success, cf.Close) - - cfi, err := cf.Stat() - if err != nil { - return nil, err - } - - adjustPath := func(in string) string { - // Also at Windows we need to always use /, because of the TAR format. - // The OCI runtime will fix this back to \ at execution. - in = strings.ReplaceAll(in, "\\", "/") - if len(in) > 3 && (in[0] == 'C' || in[0] == 'c') && in[1] == ':' && in[2] == '/' { - in = "Files/" + in[3:] - } - return in - } - - go func() { - tw := tar.NewWriter(pw) - defer common.IgnoreCloseError(tw) - - var format tar.Format - var paxRecords map[string]string - - if this.platform.os == osWindows { - format = tar.FormatPAX - paxRecords = map[string]string{ - "MSWINDOWS.rawsd": windowsUserOwnerAndGroupSID, - } - - if err := tw.WriteHeader(&tar.Header{ - Typeflag: tar.TypeDir, - Name: "Files", - Size: bfi.Size(), - Mode: 0555, - Format: format, - PAXRecords: paxRecords, - ModTime: this.time, - }); err != nil { - _ = pw.CloseWithError(err) - return - } - if err := tw.WriteHeader(&tar.Header{ - Typeflag: tar.TypeDir, - Name: "Hives", - Size: bfi.Size(), - Mode: 0555, - Format: format, - PAXRecords: paxRecords, - ModTime: this.time, - }); err != nil { - _ = pw.CloseWithError(err) - return - } - } - - if err := tw.WriteHeader(&tar.Header{ - Typeflag: tar.TypeReg, - Name: adjustPath(this.platform.os.bifroestBinaryFilePath()), - Size: bfi.Size(), - Mode: 0755, - Format: format, - PAXRecords: paxRecords, - ModTime: this.time, - }); err != nil { - _ = pw.CloseWithError(err) - return - } - - if _, err := io.Copy(tw, bf); err != nil && !errors.Is(err, io.ErrClosedPipe) { - _ = pw.CloseWithError(err) - return - } - - if err := tw.WriteHeader(&tar.Header{ - Typeflag: tar.TypeReg, - Name: adjustPath(this.platform.os.bifroestConfigFilePath()), - Size: cfi.Size(), - Mode: 0644, - Format: format, - PAXRecords: paxRecords, - ModTime: this.time, - }); err != nil { - _ = pw.CloseWithError(err) - return - } - - if _, err := io.Copy(tw, cf); err != nil && !errors.Is(err, io.ErrClosedPipe) { - _ = pw.CloseWithError(err) - return - } - - _ = pw.CloseWithError(nil) - }() - - success = true - return result, nil - - } -} - -type buildArtifactTarReader struct { - owner *buildArtifact - pr *io.PipeReader - pw *io.PipeWriter -} - -func (this *buildArtifactTarReader) Read(p []byte) (n int, err error) { - return this.pr.Read(p) -} - -func (this *buildArtifactTarReader) Close() (rErr error) { - defer common.KeepCloseError(&rErr, this.pr) - defer common.KeepCloseError(&rErr, this.pw) - return nil -} - // userOwnerAndGroupSID is a magic value needed to make the binary executable // in a Windows container. // diff --git a/cmd/build/build-binary.go b/cmd/build/build-binary.go index 4a5a4bc..72185d9 100644 --- a/cmd/build/build-binary.go +++ b/cmd/build/build-binary.go @@ -4,7 +4,6 @@ import ( "context" "fmt" gos "os" - "path/filepath" "strconv" "strings" "time" @@ -49,7 +48,7 @@ func (this *buildBinary) compile(ctx context.Context, p *platform) (*buildArtifa With("stage", buildStageBinary). With("file", a.filepath) - ldFlags := "-s -w " + a.toLdFlags(a.os) + ldFlags := " -s -w " + a.toLdFlags(a.os) start := time.Now() l.Debug("building binary...") @@ -142,40 +141,3 @@ func (this *buildBinary) compile(ctx context.Context, p *platform) (*buildArtifa success = true return a, nil } - -func (this *buildBinary) buildLdFlags(ctx context.Context, _ os, _ arch, e edition, forTesting bool, version string) (string, error) { - fail := func(err error) (string, error) { - return "", err - } - - testPrefix := "" - testSuffix := "" - if forTesting { - testPrefix = "TEST" - testSuffix = "TEST" - } - commit, err := this.build.commit(ctx) - if err != nil { - return fail(err) - } - - return "-s -w" + - fmt.Sprintf(" -X main.edition=%v", e) + - fmt.Sprintf(" -X main.version=%s%s%s", testPrefix, version, testSuffix) + - fmt.Sprintf(" -X main.revision=%s", commit) + - fmt.Sprintf(" -X main.vendor=%s", this.build.vendor) + - fmt.Sprintf(" -X main.buildAt=%s", this.build.timeFormatted()), nil -} - -func (this *buildBinary) outputName(o os, a arch, e edition, forTesting bool, version string) string { - dir := filepath.Join(this.build.dest, version) - _ = gos.MkdirAll(dir, 0755) - - fn := fmt.Sprintf("%s-%v-%v-%v", this.prefix, o, a, e) - if forTesting { - fn += "-test" - } - fn += o.execExt() - - return filepath.Join(dir, fn) -} diff --git a/cmd/build/build-digest.go b/cmd/build/build-digest.go index 0643255..f5d5b55 100644 --- a/cmd/build/build-digest.go +++ b/cmd/build/build-digest.go @@ -17,15 +17,11 @@ import ( func newBuildDigest(b *build) *buildDigest { return &buildDigest{ build: b, - - defaultConfigFile: "contrib/configurations/sshd-dropin-replacement.yaml", } } type buildDigest struct { *build - - defaultConfigFile string } func (this *buildDigest) attach(_ *kingpin.CmdClause) {} diff --git a/cmd/build/build-image-layer.go b/cmd/build/build-image-layer.go index 0ef9304..7608ac7 100644 --- a/cmd/build/build-image-layer.go +++ b/cmd/build/build-image-layer.go @@ -25,10 +25,6 @@ type imageArtifactLayerItem struct { mode gos.FileMode } -func (this imageArtifactLayerItem) open() (*gos.File, error) { - return gos.Open(this.sourceFile) -} - func createImageArtifactLayer(os os, id string, time time.Time, items iter.Seq2[imageArtifactLayerItem, error]) (*buildImageLayer, error) { fail := func(err error) (*buildImageLayer, error) { return nil, fmt.Errorf("cannot create tar layer: %w", err) diff --git a/cmd/build/build-image.go b/cmd/build/build-image.go index ed7f995..ceef5d7 100644 --- a/cmd/build/build-image.go +++ b/cmd/build/build-image.go @@ -19,30 +19,42 @@ import ( "github.com/google/go-containerregistry/pkg/v1/partial" "github.com/google/go-containerregistry/pkg/v1/remote" "github.com/google/go-containerregistry/pkg/v1/types" + "github.com/google/go-github/v65/github" v1 "github.com/opencontainers/image-spec/specs-go/v1" "github.com/engity-com/bifroest/pkg/common" ) +const ( + ImageAnnotationEdition = "org.engity.bifroest.edition" + ImageAnnotationPlatform = "org.engity.bifroest.platform" +) + func newBuildImage(b *build) *buildImage { return &buildImage{ build: b, - defaultConfigFile: "contrib/configurations/sshd-dropin-replacement.yaml", + dummyConfiguration: "cmd/build/contrib/dummy-for-oci-images.yaml", + defaultConfiguration: "contrib/configurations/sshd-dropin-replacement.yaml", } } type buildImage struct { *build - defaultConfigFile string + dummyConfiguration string + defaultConfiguration string } func (this *buildImage) attach(cmd *kingpin.CmdClause) { - cmd.Flag("defaultConfigFile", ""). - Default(this.defaultConfigFile). + cmd.Flag("dummyConfiguration", ""). + Default(this.dummyConfiguration). + PlaceHolder("<file>"). + StringVar(&this.dummyConfiguration) + cmd.Flag("defaultConfiguration", ""). + Default(this.defaultConfiguration). PlaceHolder("<file>"). - StringVar(&this.defaultConfigFile) + StringVar(&this.defaultConfiguration) } func (this *buildImage) create(ctx context.Context, binary *buildArtifact) (_ buildArtifacts, rErr error) { @@ -127,10 +139,7 @@ func (this *buildImage) createPart(ctx context.Context, binary *buildArtifact) ( cfg.OSFeatures = ociPlatform.OSFeatures cfg.Variant = ociPlatform.Variant - cfg.Config.Labels = map[string]string{ - v1.AnnotationDescription: "Test test", - v1.AnnotationSource: this.repo.fullName(), - } + cfg.Config.Labels = make(map[string]string) cfg.Config.Env = binary.os.extendPathWith(binary.platform.os.bifroestBinaryDirPath(), cfg.Config.Env) cfg.Config.Entrypoint = []string{binary.platform.os.bifroestBinaryFilePath()} cfg.Config.Cmd = []string{"run"} @@ -143,16 +152,48 @@ func (this *buildImage) createPart(ctx context.Context, binary *buildArtifact) ( return fail(err) } - img = mutate.Annotations(img, map[string]string{ - v1.AnnotationDescription: "This is a description " + a.platform.String(), - }).(gcv1.Image) + annotations, err := this.createAnnotations(ctx, a.edition, func(v version, rm *github.Repository, m map[string]string) error { + m[ImageAnnotationPlatform] = a.platform.String() + return nil + }) + if err != nil { + return fail(err) + } + + img = mutate.Annotations(img, annotations).(gcv1.Image) - binaryLayer, err := binary.toLayer(common.JoinSeq2[imageArtifactLayerItem, error]( - common.Seq2ErrOf[imageArtifactLayerItem](imageArtifactLayerItem{ - sourceFile: this.defaultConfigFile, + artifacts := []imageArtifactLayerItem{{ + sourceFile: this.defaultConfiguration, + targetFile: binary.platform.os.bifroestConfigFilePath(), + mode: 0644, + }} + + if !a.os.isUnix() || strings.EqualFold(from, "scratch") { + artifacts = []imageArtifactLayerItem{{ + sourceFile: this.dummyConfiguration, targetFile: binary.platform.os.bifroestConfigFilePath(), mode: 0644, - }), + }} + } + + if a.os.isUnix() && strings.EqualFold(from, "scratch") { + artifacts = append(artifacts, imageArtifactLayerItem{ + sourceFile: "cmd/build/contrib/passwd", + targetFile: "/etc/passwd", + mode: 0644, + }, imageArtifactLayerItem{ + sourceFile: "cmd/build/contrib/group", + targetFile: "/etc/group", + mode: 0644, + }, imageArtifactLayerItem{ + sourceFile: "cmd/build/contrib/shadow", + targetFile: "/etc/shadow", + mode: 0600, + }) + } + + binaryLayer, err := binary.toLayer(common.JoinSeq2[imageArtifactLayerItem, error]( + common.Seq2ErrOf[imageArtifactLayerItem](artifacts...), common.Seq2ErrOf[imageArtifactLayerItem](artifactDepItems...), )) if err != nil { @@ -176,12 +217,12 @@ func (this *buildImage) createPart(ctx context.Context, binary *buildArtifact) ( return a, nil } -func (this *buildImage) merge(_ context.Context, as buildArtifacts) (_ buildArtifacts, rErr error) { +func (this *buildImage) merge(ctx context.Context, as buildArtifacts) (_ buildArtifacts, rErr error) { result := slices.Collect(as.withoutType(buildArtifactTypeImage)) success := false for _, e := range allEditionVariants { - a, err := this.createdMerged(e, as) + a, err := this.createdMerged(ctx, e, as) if err != nil { return nil, err } @@ -195,19 +236,67 @@ func (this *buildImage) merge(_ context.Context, as buildArtifacts) (_ buildArti return result, nil } -func (this *buildImage) createdMerged(e edition, as buildArtifacts) (result *buildArtifact, _ error) { +func (this *buildImage) createAnnotations(ctx context.Context, e edition, additional func(version, *github.Repository, map[string]string) error) (map[string]string, error) { + rm, err := this.repo.meta(ctx) + if err != nil { + return nil, err + } + + ver, err := this.version(ctx) + if err != nil { + return nil, err + } + commit, err := this.commit(ctx) + if err != nil { + return nil, err + } + + result := map[string]string{ + v1.AnnotationCreated: this.time().Format(time.RFC3339), + v1.AnnotationURL: rm.GetHTMLURL() + "/pkgs/container/" + this.repo.name.String(), + v1.AnnotationDocumentation: rm.GetHomepage(), + v1.AnnotationSource: rm.GetHTMLURL(), + v1.AnnotationVersion: ver.String(), + v1.AnnotationRevision: commit, + v1.AnnotationVendor: this.vendor, + v1.AnnotationTitle: this.title, + v1.AnnotationDescription: rm.GetDescription(), + ImageAnnotationEdition: e.String(), + } + + if l := rm.GetLicense(); l != nil { + result[v1.AnnotationLicenses] = l.GetSPDXID() + } + + for tag := range ver.tags(e.String()+"-", e.String()) { + result[v1.AnnotationRefName] = tag + break + } + + if additional != nil { + if err := additional(ver, rm, result); err != nil { + return nil, err + } + } + + return result, err +} + +func (this *buildImage) createdMerged(ctx context.Context, e edition, as buildArtifacts) (result *buildArtifact, _ error) { l := log.With("edition", e). With("stage", buildStageImage) start := time.Now() l.Debug("merge images...") + annotations, err := this.createAnnotations(ctx, e, nil) + if err != nil { + return nil, err + } + var manifest gcv1.ImageIndex = empty.Index - mutate.IndexMediaType(manifest, types.DockerManifestList) - manifest = mutate.Annotations(manifest, map[string]string{ - v1.AnnotationSource: this.repo.fullName(), - v1.AnnotationDescription: "This is a description", - }).(gcv1.ImageIndex) + manifest = mutate.IndexMediaType(manifest, types.DockerManifestList) + manifest = mutate.Annotations(manifest, annotations).(gcv1.ImageIndex) var adds []mutate.IndexAddendum var refA *buildArtifact diff --git a/cmd/build/build.go b/cmd/build/build.go index 2a9cf49..f79b620 100644 --- a/cmd/build/build.go +++ b/cmd/build/build.go @@ -8,6 +8,7 @@ import ( "path/filepath" "runtime" "slices" + "strconv" "strings" "sync/atomic" "time" @@ -81,7 +82,6 @@ func (this *build) init(ctx context.Context, app *kingpin.Application) { StringVar(&this.prefix) cmd.Flag("stages", ""). PlaceHolder("<" + strings.Join(allBuildStageVariants.Strings(), "|") + ">[,...]"). - Default(this.rawStages.String()). SetValue(&this.rawStages) cmd.Flag("os", ""). PlaceHolder("<" + strings.Join(allOsVariants.Strings(), "|") + ">[,...]"). @@ -111,6 +111,11 @@ func (this *build) init(ctx context.Context, app *kingpin.Application) { this.digest.attach(cmd) } + attach(app.Command("evaluate-environment", ""). + Action(func(*kingpin.ParseContext) error { + return this.evaluateEnvironment(ctx) + })) + attach(app.Command("build", ""). Action(func(*kingpin.ParseContext) (rErr error) { as, err := this.buildAll(ctx, this.testing) @@ -136,7 +141,102 @@ func (this *build) allPlatforms(forTesting bool) iter.Seq[*platform] { } } +func (this *build) evaluateEnvironment(ctx context.Context) error { + commit, err := this.commit(ctx) + if err != nil { + return err + } + ref, err := this.ref(ctx) + if err != nil { + return err + } + ver, err := this.version(ctx) + if err != nil { + return err + } + pr := this.pr() + stages, err := this.stages(ctx) + if err != nil { + return err + } + + log.With("commit", commit). + With("version", ver). + With("ref", ref). + With("pr", pr). + With("stages", stages). + Info() + prStr := strconv.FormatUint(uint64(pr), 10) + + if fn := this.optionsOutputFilename; fn != "" { + f, err := gos.OpenFile(fn, gos.O_CREATE|gos.O_APPEND|gos.O_WRONLY, 0644) + if err != nil { + return err + } + defer common.IgnoreCloseError(f) + + if _, err = fmt.Fprint(f, ""+ + "commit="+commit+"\n"+ + "version="+ver.String()+"\n"+ + "ref="+ref+"\n"+ + "pr="+prStr+"\n", + ); err != nil { + return err + } + + for _, stage := range allBuildStageVariants { + if _, err = fmt.Fprintf(f, "stage-%v=%v\n", stage, stages.contains(stage)); err != nil { + return err + } + } + + log.With("file", fn). + Info("options output created") + } + + if fn := this.summaryOutputFilename; fn != "" { + f, err := gos.OpenFile(fn, gos.O_CREATE|gos.O_APPEND|gos.O_WRONLY, 0644) + if err != nil { + return err + } + defer common.IgnoreCloseError(f) + + baseUrl := "https://" + this.repo.fullName() + + if _, err = fmt.Fprint(f, "## Build environment\n"+ + "| Name | Value |\n"+ + "| ---- | ----- |\n"+ + "| Commit | [`"+commit+"`]("+baseUrl+"/commit/"+commit+") |\n"+ + "| Version | `"+ver.String()+"` |\n"+ + "| Ref | [`"+ref+"`]("+baseUrl+"/tree/"+ref+") |\n"+ + "| PR | [`"+prStr+"`]("+baseUrl+"/pull/"+prStr+") |\n"+ + "\n", + "## Available stages\n"+ + "| Name | Enabled |\n"+ + "| ---- | ------- |\n", + ); err != nil { + return err + } + + for _, stage := range allBuildStageVariants { + if _, err = fmt.Fprintf(f, "| `%v` | `%v` |\n", stage, stages.contains(stage)); err != nil { + return err + } + } + + log.With("file", fn). + Info("summary output created") + } + + return nil +} + func (this *build) buildAll(ctx context.Context, forTesting bool) (artifacts buildArtifacts, _ error) { + stages, err := this.stages(ctx) + if err != nil { + return nil, err + } + if this.updateCaCerts { if err := this.dependencies.caCerts.generatePem(ctx); err != nil { return nil, err @@ -153,7 +253,7 @@ func (this *build) buildAll(ctx context.Context, forTesting bool) (artifacts bui artifacts = append(artifacts, vs...) } - if this.stages.contains(buildStageImage) { + if stages.contains(buildStageImage) { var err error artifacts, err = this.image.merge(ctx, artifacts) if err != nil { @@ -161,7 +261,7 @@ func (this *build) buildAll(ctx context.Context, forTesting bool) (artifacts bui } } - if this.stages.contains(buildStageDigest) { + if stages.contains(buildStageDigest) { var err error artifacts, err = this.digest.create(ctx, artifacts) if err != nil { @@ -169,8 +269,8 @@ func (this *build) buildAll(ctx context.Context, forTesting bool) (artifacts bui } } - if this.stages.contains(buildStagePublish) { - if err := this.image.publish(ctx, artifacts); err != nil { + if stages.contains(buildStagePublish) { + if err := this.publish(ctx, artifacts); err != nil { return nil, err } } @@ -184,13 +284,18 @@ func (this *build) buildSingle(ctx context.Context, p *platform) (artifacts buil return nil, fmt.Errorf("cannot build %v: %w", *p, err) } + stages, err := this.stages(ctx) + if err != nil { + return nil, err + } + l := log.With("platform", p) success := false common.IgnoreCloseErrorIfFalse(&success, artifacts) var ba *buildArtifact - if this.stages.contains(buildStageBinary) && p.isBinarySupported(this.assumedBuildOs(), this.assumedBuildArch()) { + if stages.contains(buildStageBinary) && p.isBinarySupported(this.assumedBuildOs(), this.assumedBuildArch()) { var err error ba, err = this.binary.compile(ctx, p) if err != nil { @@ -202,7 +307,7 @@ func (this *build) buildSingle(ctx context.Context, p *platform) (artifacts buil l.With("stage", buildStageBinary).Info("build binary skipped") } - if ba != nil && this.stages.contains(buildStageArchive) { + if ba != nil && stages.contains(buildStageArchive) { aa, err := this.archive.create(ctx, ba) if err != nil { return fail(err) @@ -212,7 +317,7 @@ func (this *build) buildSingle(ctx context.Context, p *platform) (artifacts buil l.With("stage", buildStageArchive).Info("build archive skipped") } - if ba != nil && this.stages.contains(buildStageImage) && ba.isImageSupported() { + if ba != nil && stages.contains(buildStageImage) && ba.isImageSupported() { aas, err := this.image.create(ctx, ba) if err != nil { return fail(err) @@ -226,6 +331,48 @@ func (this *build) buildSingle(ctx context.Context, p *platform) (artifacts buil return artifacts, nil } +func (this *build) publish(ctx context.Context, as buildArtifacts) error { + fail := func(err error) error { + return fmt.Errorf("cannot publish: %w", err) + } + + if err := this.image.publish(ctx, as); err != nil { + return fail(err) + } + + release, err := this.repo.releases.findCurrent(ctx) + if err != nil { + return fail(err) + } + + if release == nil { + log.Info("outside of release; publish artifacts skipped") + return nil + } + + l := log.With("release", release) + + start := time.Now() + l.Debug("publish release...") + + for a := range as.filter(func(candidate *buildArtifact) bool { + return candidate.t.canBePublished() + }) { + if _, err := release.uploadAsset(ctx, a.name(), a.mediaType(), "", a.filepath); err != nil { + return fail(err) + } + } + + ll := l.With("duration", time.Since(start).Truncate(time.Millisecond)) + if l.IsDebugEnabled() { + ll.Info("publish release...DONE!") + } else { + ll.Info("release published") + } + + return nil +} + func (this *build) time() time.Time { for { if v := this.timeP.Load(); v != nil { @@ -239,10 +386,6 @@ func (this *build) time() time.Time { } } -func (this *build) timeFormatted() string { - return this.time().Format(time.RFC3339) -} - func (this *build) getBuildContext(ctx context.Context) (*buildContext, error) { for { if v := this.buildContextP.Load(); v != nil { @@ -350,17 +493,21 @@ func (this *build) resolveStages(ctx context.Context) (buildStages, error) { // Assume release... if ver.semver != nil { + log.With("version", ver). + Infof("as this is a release version; stage %v was implicitly enabled", buildStagePublish) return allBuildStageVariants, nil } // Check if the PR is allowed to have images... if v := this.pr(); v > 0 { - pr, err := this.repo.prs.byId(ctx, int(v)) + pr, err := this.repo.prs.byId(ctx, v) if err != nil { return nil, err } // Ok, in this case allow images... - if pr.isOpen() && pr.hasLabel("test publish") { + if pr.isOpen() && pr.hasLabel(this.repo.prs.testPublishLabel) { + log.With("pr", v). + Infof("as this is a PR and it was the label %v; stage %v was implicitly enabled", this.repo.prs.testPublishLabel, buildStagePublish) return allBuildStageVariants, nil } } @@ -396,6 +543,6 @@ func (this buildContext) toLdFlags(testing bool) string { } return "-X main.version=" + testPrefix + this.version.String() + testSuffix + " -X main.revision=" + this.revision + - " -X main.vendor=" + this.vendor + + " -X " + strconv.Quote("main.vendor="+this.vendor) + " -X main.buildAt=" + this.time.Format(time.RFC3339) } diff --git a/cmd/build/contrib/dummy-for-oci-images.yaml b/cmd/build/contrib/dummy-for-oci-images.yaml new file mode 100644 index 0000000..904c605 --- /dev/null +++ b/cmd/build/contrib/dummy-for-oci-images.yaml @@ -0,0 +1,31 @@ +## This configuration should only be used as dummy configuration within OCI/Docker images. +## It will create (if not exists) +## for the regular sshd. + +startMessage: > + Welcome to Engity's Bifröst! + This instance is running a demo configuration that is NOT + intended for production use. Therefore, it is not possible to + log in to this instance until it is configured. + See https://bifroest.engity.org/latest/setup/ for more details. + +ssh: + addresses: [ ":22" ] + banner: |+ + Transcend with Engity's Bifröst + =============================== + + This instance is running a demo configuration that is NOT + intended for production use. Therefore, it is not possible to + log in to this instance until it is configured. + + See https://bifroest.engity.org/latest/setup/ for more details. + +flows: + - name: default + authorization: + type: htpasswd + environment: + type: local + # This property will not be evaluated in Windows. + name: "demo" diff --git a/cmd/build/contrib/group b/cmd/build/contrib/group new file mode 100644 index 0000000..1dbf901 --- /dev/null +++ b/cmd/build/contrib/group @@ -0,0 +1 @@ +root:x:0: diff --git a/cmd/build/contrib/passwd b/cmd/build/contrib/passwd new file mode 100644 index 0000000..6d9b028 --- /dev/null +++ b/cmd/build/contrib/passwd @@ -0,0 +1 @@ +root:x:0:0:root:/:/usr/bin/bifroest diff --git a/cmd/build/contrib/shadow b/cmd/build/contrib/shadow new file mode 100644 index 0000000..cafaee5 --- /dev/null +++ b/cmd/build/contrib/shadow @@ -0,0 +1 @@ +root:*:19683:0:99999:7::: diff --git a/cmd/build/dependencies-images-files.go b/cmd/build/dependencies-images-files.go index 13167e5..5a2f668 100644 --- a/cmd/build/dependencies-images-files.go +++ b/cmd/build/dependencies-images-files.go @@ -3,6 +3,7 @@ package main import ( "archive/tar" "context" + "crypto/sha1" "fmt" "io" gos "os" @@ -15,6 +16,7 @@ import ( "github.com/google/go-containerregistry/pkg/crane" v1 "github.com/google/go-containerregistry/pkg/v1" "github.com/google/go-containerregistry/pkg/v1/types" + "github.com/mr-tron/base58" "github.com/engity-com/bifroest/pkg/common" "github.com/engity-com/bifroest/pkg/sys" @@ -70,7 +72,7 @@ func (this *dependenciesImagesFiles) downloadNetapi32DllFor(ctx context.Context, return nil, nil } - targetFn := filepath.Join(this.cacheDirectory, os.String()+"-"+arch.String(), "netapi32.dll") + targetFn := this.cacheLocationFor(os, arch, "netapi32.dll") err := this.getFileFromImage(ctx, os, arch, this.imageWithNetapi32Dll, "Files/Windows/System32/netapi32.dll", targetFn) if err != nil { @@ -80,6 +82,11 @@ func (this *dependenciesImagesFiles) downloadNetapi32DllFor(ctx context.Context, return []imageFileDependency{{os, arch, targetFn, netapi32DllFilename, 0644}}, nil } +func (this *dependenciesImagesFiles) cacheLocationFor(os os, arch arch, base string) string { + hash := sha1.Sum([]byte(base)) + return filepath.Join(this.cacheDirectory, os.String()+"-"+arch.String(), base58.Encode(hash[:]), "netapi32.dll") +} + func (this *dependenciesImagesFiles) getFileFromImage(ctx context.Context, os os, arch arch, imgName string, sourceFn, targetFn string) (rErr error) { fail := func(err error) error { return fmt.Errorf("cannot download %q from %q (%v/%v): %w", sourceFn, imgName, os, arch, err) @@ -184,7 +191,7 @@ func (this *dependenciesImagesFiles) getFileFromLayer(layer v1.Layer, sourceFn, return failf("cannot read TAR from uncompressed part of layer: %w", err) } if strings.EqualFold(header.Name, sourceFn) { - to, err := gos.OpenFile(targetFn, gos.O_TRUNC|gos.O_CREATE|gos.O_WRONLY, 644) + to, err := gos.OpenFile(targetFn, gos.O_TRUNC|gos.O_CREATE|gos.O_WRONLY, 0644) if err != nil { return failf("cannot create file %q: %w", targetFn, err) } diff --git a/cmd/build/os.go b/cmd/build/os.go index f77ee21..acfaa1d 100644 --- a/cmd/build/os.go +++ b/cmd/build/os.go @@ -31,10 +31,6 @@ func (this os) String() string { return v } -func (this os) ociString() string { - return this.String() -} - func (this *os) Set(plain string) error { v, ok := stringToOs[plain] if !ok { @@ -53,6 +49,15 @@ func (this os) execExt() string { } } +func (this os) isUnix() bool { + switch this { + case osLinux: + return true + default: + return false + } +} + func (this os) bifroestBinaryDirPath() string { switch this { case osWindows: diff --git a/cmd/build/repo-actions.go b/cmd/build/repo-actions.go index 2bf2d64..fa5a6d2 100644 --- a/cmd/build/repo-actions.go +++ b/cmd/build/repo-actions.go @@ -12,13 +12,25 @@ import ( func newRepoActions(r *repo) *repoActions { return &repoActions{ repo: r, + + workflowFilenameCi: "ci.yml", } } -func (this *repoActions) init(_ context.Context, _ *kingpin.Application) {} - type repoActions struct { *repo + + workflowFilenameCi string +} + +func (this *repoActions) init(_ context.Context, app *kingpin.Application) { + app.Flag("workflow-filename-ci", ""). + Default(this.workflowFilenameCi). + StringVar(&this.workflowFilenameCi) +} + +func (this *repoActions) ciWorkflow(ctx context.Context) (*repoWorkflow, error) { + return this.workflowByFilename(ctx, this.workflowFilenameCi) } func (this *repoActions) workflowByFilename(ctx context.Context, fn string) (*repoWorkflow, error) { diff --git a/cmd/build/repo-packages.go b/cmd/build/repo-packages.go index a4a2a72..0cb201d 100644 --- a/cmd/build/repo-packages.go +++ b/cmd/build/repo-packages.go @@ -19,25 +19,11 @@ func newRepoPackages(r *repo) *repoPackages { type repoPackages struct { *repo - - subs []string } -func (this *repoPackages) init(ctx context.Context, app *kingpin.Application) { - app.Flag("package-subs", ""). - PlaceHolder("<tag>[,<tag>...]"). - StringsVar(&this.subs) - - cmdDit := app.Command("delete-image-tag", "") - tags := cmdDit.Arg("tags", ""). - Required(). - Strings() - cmdDit.Action(func(*kingpin.ParseContext) error { - return this.deleteVersionsWithTags(ctx, *tags) - }) -} +func (this *repoPackages) init(_ context.Context, _ *kingpin.Application) {} -func (this *repoPackages) deleteVersionsWithTags(ctx context.Context, tags []string) error { +func (this *repoPackages) deleteVersionsWithTags(ctx context.Context, tags ...string) error { del := func(sub string) error { for candidate, err := range this.versionsWithAtLeastOneTag(ctx, sub, tags) { if err != nil { @@ -55,15 +41,8 @@ func (this *repoPackages) deleteVersionsWithTags(ctx context.Context, tags []str return nil } - if len(this.subs) == 0 { - if err := del(""); err != nil { - return err - } - } - for _, sub := range this.subs { - if err := del(sub); err != nil { - return err - } + if err := del(""); err != nil { + return err } return nil diff --git a/cmd/build/repo-prs.go b/cmd/build/repo-prs.go index 35ef1d5..1dbfb58 100644 --- a/cmd/build/repo-prs.go +++ b/cmd/build/repo-prs.go @@ -3,7 +3,6 @@ package main import ( "context" "fmt" - gos "os" "slices" "strings" "time" @@ -16,127 +15,80 @@ import ( func newRepoPrs(r *repo) *repoPrs { return &repoPrs{ repo: r, + + testPublishLabel: "test_publish", } } +type repoPrs struct { + *repo + + testPublishLabel string +} + +func (this *repoPrs) Validate() error { return nil } + func (this *repoPrs) init(ctx context.Context, app *kingpin.Application) { - var prNumber int - var workflowFn string - var label string + app.Flag("label-test-publish", ""). + Default(this.testPublishLabel). + StringVar(&this.testPublishLabel) - cmdRpw := app.Command("rerun-pr-workflow", "") - cmdRpw.Arg("prNumber", ""). - Required(). - IntVar(&prNumber) - cmdRpw.Arg("workflowFilename", ""). - Required(). - StringVar(&workflowFn) - cmdRpw.Action(func(*kingpin.ParseContext) error { - return this.rerunLatestWorkflowCmd(ctx, prNumber, workflowFn) - }) + var eventAction string + var label string - cmdHpl := app.Command("has-pr-label", "") - cmdHpl.Arg("prNumber", ""). - Required(). - IntVar(&prNumber) - cmdHpl.Arg("label", ""). + cmdIu := app.Command("inspect-pr-action", "") + cmdIu.Arg("eventAction", ""). Required(). + StringVar(&eventAction) + cmdIu.Arg("label", ""). StringVar(&label) - cmdHpl.Action(func(*kingpin.ParseContext) error { - return this.hasLabelCmd(ctx, prNumber, label) - }) - - cmdIpo := app.Command("is-pr-open", "") - cmdIpo.Arg("prNumber", ""). - Required(). - IntVar(&prNumber) - cmdIpo.Action(func(*kingpin.ParseContext) error { - return this.isOpenCmd(ctx, prNumber) + cmdIu.Action(func(*kingpin.ParseContext) error { + return this.inspectAction(ctx, eventAction, label) }) } -func (this *repoPrs) Validate() error { return nil } - -type repoPrs struct { - *repo -} +func (this *repoPrs) inspectAction(ctx context.Context, eventAction string, label string) error { + prNumber := this.pr() + if prNumber == 0 { + return fmt.Errorf("the environment does not contain a pr reference") + } -func (this *repoPrs) rerunLatestWorkflowCmd(ctx context.Context, prNumber int, workflowFn string) error { - v, err := this.byId(ctx, prNumber) + pr, err := this.byId(ctx, prNumber) if err != nil { return err } - l := log.With("pr", prNumber). - With("workflowFn", workflowFn) - - start := time.Now() - for ctx.Err() == nil { - wfr, err := v.latestWorkflowRun(ctx, workflowFn) - if err != nil { - return err - } - lw := l.With("workflowRun", wfr) - if wfr.Status != nil && strings.EqualFold(*wfr.Status, "completed") { - if err := wfr.rerun(ctx); err != nil { - return err - } - lw.With("workflowRunUrl", *wfr.HTMLURL). - With("prUrl", *v.HTMLURL). - Info("rerun of workflow run was successfully triggered") - return nil - } - lw.With("duration", time.Since(start).Truncate(time.Second)). - Info("latest workflow run is still running - continue waiting...") - select { - case <-ctx.Done(): - return ctx.Err() - case <-time.After(this.base.waitTimeout): - } + if (eventAction == "labeled" || eventAction == "unlabeled") && label == "" { + return fmt.Errorf("for labeled and unlabeled actions the label argument is required") } - return ctx.Err() -} - -func (this *repoPrs) hasLabelCmd(ctx context.Context, prNumber int, label string) error { - v, err := this.byId(ctx, prNumber) - if err != nil { - return err - } + if eventAction == "labeled" && label == this.testPublishLabel && pr.isOpen() { + log.With("pr", pr). + With("label", this.testPublishLabel). + Info("PR received label for being allowed to publish; rerun the latest workflow to enable them now...") - l := log.With("pr", prNumber). - With("label", label) - if v.hasLabel(label) { - l.Info("label is present") - gos.Exit(0) - } else { - l.Info("label is absent") - gos.Exit(1) + if err := pr.rerunLatestCiWorkflow(ctx); err != nil { + return err + } } - return nil -} + if (eventAction == "unlabeled" && label == this.testPublishLabel && pr.isOpen()) || + eventAction == "closed" { -func (this *repoPrs) isOpenCmd(ctx context.Context, prNumber int) error { - v, err := this.byId(ctx, prNumber) - if err != nil { - return err - } + log.With("pr", pr). + With("label", this.testPublishLabel). + Info("PR was unlabeled or closed; therefore delete all images that might be related to this PR...") - l := log.With("pr", prNumber) - if v.isOpen() { - l.Info("pr is open") - gos.Exit(0) - } else { - l.Info("pr is closed") - gos.Exit(1) + if err := pr.deleteRelatedArtifacts(ctx); err != nil { + return err + } } return nil } -func (this *repoPrs) byId(ctx context.Context, number int) (*repoPr, error) { - v, _, err := this.client().PullRequests.Get(ctx, this.owner.String(), this.name.String(), number) +func (this *repoPrs) byId(ctx context.Context, number uint) (*repoPr, error) { + v, _, err := this.client().PullRequests.Get(ctx, this.owner.String(), this.name.String(), int(number)) if err != nil { return nil, fmt.Errorf("cannot retrieve pull request %d from %v: %w", number, this.base, err) } @@ -169,10 +121,45 @@ func (this *repoPr) isOpen() bool { return this.State != nil && strings.EqualFold(*this.State, "open") } -func (this *repoPr) latestWorkflowRun(ctx context.Context, workflowFn string) (*repoWorkflowRun, error) { - wf, err := this.parent.actions.workflowByFilename(ctx, workflowFn) +func (this *repoPr) rerunLatestCiWorkflow(ctx context.Context) error { + return this.rerunLatestWorkflow(ctx, this.parent.actions.ciWorkflow) +} + +func (this *repoPr) rerunLatestWorkflow(ctx context.Context, workflowLoader func(context.Context) (*repoWorkflow, error)) error { + l := log.With("pr", this.GetID()) + + start := time.Now() + for ctx.Err() == nil { + wfr, err := this.latestWorkflowRun(ctx, workflowLoader) + if err != nil { + return err + } + lw := l.With("workflowRun", wfr) + if wfr.Status != nil && strings.EqualFold(*wfr.Status, "completed") { + if err := wfr.rerun(ctx); err != nil { + return err + } + lw.With("workflowRunUrl", *wfr.HTMLURL). + With("prUrl", this.GetHTMLURL()). + Info("rerun of workflow run was successfully triggered") + return nil + } + lw.With("duration", time.Since(start).Truncate(time.Second)). + Info("latest workflow run is still running - continue waiting...") + select { + case <-ctx.Done(): + return ctx.Err() + case <-time.After(this.parent.base.waitTimeout): + } + } + + return ctx.Err() +} + +func (this *repoPr) latestWorkflowRun(ctx context.Context, workflowLoader func(context.Context) (*repoWorkflow, error)) (*repoWorkflowRun, error) { + wf, err := workflowLoader(ctx) if err != nil { - return nil, fmt.Errorf("cannot get workflow %s of %v: %w", workflowFn, this, err) + return nil, fmt.Errorf("cannot get workflow of %v: %w", this, err) } for candidate, err := range wf.runs(ctx) { if err != nil { @@ -186,3 +173,26 @@ func (this *repoPr) latestWorkflowRun(ctx context.Context, workflowFn string) (* } return nil, nil } + +func (this *repoPr) deleteRelatedArtifacts(ctx context.Context) error { + fail := func(err error) error { + return fmt.Errorf("cannot delete artifacts for %v: %w", this, err) + } + + do := func(tag string) error { + return this.parent.actions.packages.deleteVersionsWithTags(ctx, tag) + } + + mainTag := fmt.Sprintf("pr-%d", this.GetID()) + + if err := do(mainTag); err != nil { + return fail(err) + } + for _, ed := range allEditionVariants { + if err := do(ed.String() + "-" + mainTag); err != nil { + return fail(err) + } + } + + return nil +} diff --git a/cmd/build/repo-releases.go b/cmd/build/repo-releases.go index 1712f12..9e27205 100644 --- a/cmd/build/repo-releases.go +++ b/cmd/build/repo-releases.go @@ -4,11 +4,17 @@ import ( "context" "fmt" "iter" + "net/http" + gos "os" "strings" + "time" "github.com/Masterminds/semver/v3" "github.com/alecthomas/kingpin" + log "github.com/echocat/slf4g" "github.com/google/go-github/v65/github" + + "github.com/engity-com/bifroest/pkg/common" ) func newRepoReleases(r *repo) *repoReleases { @@ -23,6 +29,21 @@ type repoReleases struct { *repo } +func (this *repoReleases) findCurrent(ctx context.Context) (*repoRelease, error) { + ref, err := this.ref(ctx) + if err != nil { + return nil, err + } + v, resp, err := this.client().Repositories.GetReleaseByTag(ctx, this.owner.String(), this.name.String(), ref) + if resp != nil && resp.StatusCode == http.StatusNotFound { + return nil, nil + } + if err != nil { + return nil, fmt.Errorf("cannot retrieve current release: %w", err) + } + return &repoRelease{v, this}, nil +} + func (this *repoReleases) all(ctx context.Context) iter.Seq2[*repoRelease, error] { return func(yield func(*repoRelease, error) bool) { var opts github.ListOptions @@ -84,3 +105,47 @@ type repoRelease struct { func (this *repoRelease) String() string { return fmt.Sprintf("%s(%d)@%v", *this.Name, *this.ID, this.parent.repo) } + +func (this *repoRelease) uploadAsset(ctx context.Context, name, mediaType, label, fn string) (*repoReleaseAsset, error) { + fail := func(err error) (*repoReleaseAsset, error) { + return nil, fmt.Errorf("cannot upload asset %q: %w", name, err) + } + + f, err := gos.Open(fn) + if err != nil { + return fail(err) + } + defer common.IgnoreCloseError(f) + + l := log.With("release", this). + With("name", name). + With("mediaType", mediaType). + With("label", label) + + start := time.Now() + l.Debug("uploading asset...") + + asset, _, err := this.parent.client().Repositories.UploadReleaseAsset(ctx, this.parent.owner.String(), this.parent.name.String(), this.GetID(), &github.UploadOptions{ + Name: name, + Label: label, + MediaType: mediaType, + }, f) + if err != nil { + return fail(err) + } + + ll := l.With("duration", time.Since(start).Truncate(time.Millisecond)) + if l.IsDebugEnabled() { + ll.Info("uploading asset... DONE!") + } else { + ll.Info("asset uploaded") + } + + return &repoReleaseAsset{asset, this.parent}, nil +} + +type repoReleaseAsset struct { + *github.ReleaseAsset + + parent *repoReleases +} diff --git a/cmd/build/repo.go b/cmd/build/repo.go index 60a7772..92b44fc 100644 --- a/cmd/build/repo.go +++ b/cmd/build/repo.go @@ -76,6 +76,7 @@ type repo struct { releases *repoReleases clientP atomic.Pointer[github.Client] + metaP atomic.Pointer[github.Repository] } func (this *repo) String() string { @@ -119,6 +120,23 @@ func (this *repo) client() *github.Client { } } +func (this *repo) meta(ctx context.Context) (*github.Repository, error) { + for { + v := this.metaP.Load() + if v != nil { + return v, nil + } + v, _, err := this.client().Repositories.Get(ctx, this.owner.String(), this.name.String()) + if err != nil { + return nil, err + } + if this.metaP.CompareAndSwap(nil, v) { + return v, nil + } + runtime.Gosched() + } +} + type owner string func (this owner) String() string { diff --git a/cmd/build/version.go b/cmd/build/version.go index 2f82000..87d619f 100644 --- a/cmd/build/version.go +++ b/cmd/build/version.go @@ -11,9 +11,9 @@ import ( ) var ( - versionPattern = regexp.MustCompile("^\\w[\\w.-]{0,127}$") + versionPattern = regexp.MustCompile(`^\w[\w.-]{0,127}$`) - versionNormalizePattern = regexp.MustCompile("[^\\w]+") + versionNormalizePattern = regexp.MustCompile(`[^\w]+`) ) type version struct { @@ -54,7 +54,7 @@ func (this version) tags(prefix string, rootTag string) iter.Seq[string] { return func(yield func(string) bool) { smv := this.semver if smv == nil { - yield(this.raw) + yield(prefix + this.raw) return } diff --git a/cmd/build/version_test.go b/cmd/build/version_test.go index 72ebbdc..53d2295 100644 --- a/cmd/build/version_test.go +++ b/cmd/build/version_test.go @@ -82,7 +82,7 @@ func TestVersion_evaluateLatest(t *testing.T) { } } -func TestVersion_tags(t *testing.T) { +func TestVersion_tags_semver(t *testing.T) { var instance version require.NoError(t, instance.Set("v2.3.4")) @@ -145,6 +145,35 @@ func TestVersion_tags(t *testing.T) { } } +func TestVersion_tags_other(t *testing.T) { + cases := []struct { + input string + prefix string + root string + outputs []string + }{{ + input: "x123x", + prefix: "p", + root: "r", + outputs: a("px123x"), + }, { + input: "v1.2.3", + prefix: "p", + root: "r", + outputs: a("p1.2.3"), + }} + + for _, c := range cases { + t.Run(c.input+"-"+c.prefix+"-"+c.root, func(t *testing.T) { + var instance version + require.NoError(t, instance.Set(c.input)) + + actual := slices.Collect(instance.tags(c.prefix, c.root)) + assert.Equal(t, c.outputs, actual) + }) + } +} + func a[T any](in ...T) []T { return in } diff --git a/docs/reference/connection/ssh.md b/docs/reference/connection/ssh.md index a6fa195..772d13e 100644 --- a/docs/reference/connection/ssh.md +++ b/docs/reference/connection/ssh.md @@ -25,7 +25,7 @@ How many different authentication methods a client can use before the connection <<property("maxConnections", "uint8", None, default=255)>> The maximum amount of parallel connections on this service. Every additional connection beyond will be rejected. -<<property_with_holder("banner", "String Template", "../templating/index.md#string", "Connection", "../context/connection.md", default='{{ `/etc/ssh/sshd-banner` | file `optional` | default `Transcend with Engity Bifröst\n\n` }}')>> +<<property_with_holder("banner", "String Template", "../templating/index.md#string", "Connection", "../context/connection.md", default='{{ `/etc/ssh/sshd-banner` | file `optional` | default `Transcend with Engity\'s Bifröst\n\n` }}')>> Banner which will be shown when the client connects to the server even before the first validation of authorizations or similar happens. ## Examples diff --git a/go.mod b/go.mod index d417102..cb5a84b 100644 --- a/go.mod +++ b/go.mod @@ -16,12 +16,13 @@ require ( github.com/google/go-containerregistry v0.20.2 github.com/google/go-github/v65 v65.0.0 github.com/google/uuid v1.6.0 + github.com/gwatts/rootcerts v0.0.0-20240701182254-d67b2c3ed211 github.com/kardianos/osext v0.0.0-20190222173326-2bc1f35cddc0 github.com/mattn/go-zglob v0.0.6 github.com/mikesmitty/edkey v0.0.0-20170222072505-3356ea4e686a github.com/mr-tron/base58 v1.2.0 github.com/msteinert/pam/v2 v2.0.0 - github.com/opencontainers/image-spec v1.1.0-rc3 + github.com/opencontainers/image-spec v1.1.0 github.com/openwall/yescrypt-go v1.0.0 github.com/otiai10/copy v1.14.0 github.com/pkg/sftp v1.13.6 @@ -37,19 +38,18 @@ require ( dario.cat/mergo v1.0.1 // indirect github.com/Masterminds/goutils v1.1.1 // indirect github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751 // indirect - github.com/alecthomas/units v0.0.0-20240626203959-61d1e3462e30 // indirect + github.com/alecthomas/units v0.0.0-20240927000941-0f3dac36c52b // indirect github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be // indirect - github.com/containerd/stargz-snapshotter/estargz v0.14.3 // indirect + github.com/containerd/stargz-snapshotter/estargz v0.15.1 // indirect github.com/davecgh/go-spew v1.1.1 // indirect - github.com/docker/cli v27.1.1+incompatible // indirect - github.com/docker/distribution v2.8.2+incompatible // indirect - github.com/docker/docker-credential-helpers v0.7.0 // indirect + github.com/docker/cli v27.3.1+incompatible // indirect + github.com/docker/distribution v2.8.3+incompatible // indirect + github.com/docker/docker-credential-helpers v0.8.2 // indirect github.com/go-jose/go-jose/v4 v4.0.4 // indirect - github.com/go-ole/go-ole v1.2.6 // indirect + github.com/go-ole/go-ole v1.3.0 // indirect github.com/google/go-querystring v1.1.0 // indirect - github.com/gwatts/rootcerts v0.0.0-20240701182254-d67b2c3ed211 // indirect github.com/huandu/xstrings v1.5.0 // indirect - github.com/klauspost/compress v1.16.5 // indirect + github.com/klauspost/compress v1.17.10 // indirect github.com/kr/fs v0.1.0 // indirect github.com/mitchellh/copystructure v1.2.0 // indirect github.com/mitchellh/go-homedir v1.1.0 // indirect @@ -57,14 +57,14 @@ require ( github.com/opencontainers/go-digest v1.0.0 // indirect github.com/pkg/errors v0.9.1 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect - github.com/rogpeppe/go-internal v1.12.0 // indirect + github.com/rogpeppe/go-internal v1.13.1 // indirect github.com/shopspring/decimal v1.4.0 // indirect - github.com/sirupsen/logrus v1.9.1 // indirect + github.com/sirupsen/logrus v1.9.3 // indirect github.com/spf13/cast v1.7.0 // indirect github.com/tklauser/go-sysconf v0.3.14 // indirect - github.com/tklauser/numcpus v0.8.0 // indirect - github.com/vbatts/tar-split v0.11.3 // indirect + github.com/tklauser/numcpus v0.9.0 // indirect + github.com/vbatts/tar-split v0.11.6 // indirect github.com/yusufpapurcu/wmi v1.2.4 // indirect golang.org/x/sync v0.8.0 // indirect - golang.org/x/sys v0.25.0 // indirect + golang.org/x/sys v0.26.0 // indirect ) diff --git a/go.sum b/go.sum index 4e52eba..b1c9ace 100644 --- a/go.sum +++ b/go.sum @@ -1,6 +1,5 @@ dario.cat/mergo v1.0.1 h1:Ra4+bf83h2ztPIQYNP99R6m+Y7KfnARDfID+a+vLl4s= dario.cat/mergo v1.0.1/go.mod h1:uNxQE+84aUszobStD9th8a29P2fMDhsBdgRYvZOxGmk= -github.com/BurntSushi/toml v1.2.1/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ= github.com/GehirnInc/crypt v0.0.0-20230320061759-8cc1b52080c5 h1:IEjq88XO4PuBDcvmjQJcQGg+w+UaafSy8G5Kcb5tBhI= github.com/GehirnInc/crypt v0.0.0-20230320061759-8cc1b52080c5/go.mod h1:exZ0C/1emQJAw5tHOaUDyY1ycttqBAPcxuzf7QbY6ec= github.com/Masterminds/goutils v1.1.1 h1:5nUrii3FMTL5diU80unEVvNevw1nH4+ZV4DSLVJLSYI= @@ -13,26 +12,25 @@ github.com/alecthomas/kingpin v2.2.6+incompatible h1:5svnBTFgJjZvGKyYBtMB0+m5wvr github.com/alecthomas/kingpin v2.2.6+incompatible/go.mod h1:59OFYbFVLKQKq+mqrL6Rw5bR0c3ACQaawgXx0QYndlE= github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751 h1:JYp7IbQjafoB+tBA3gMyHYHrpOtNuDiK/uB5uXxq5wM= github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= -github.com/alecthomas/units v0.0.0-20240626203959-61d1e3462e30 h1:t3eaIm0rUkzbrIewtiFmMK5RXHej2XnoXNhxVsAYUfg= -github.com/alecthomas/units v0.0.0-20240626203959-61d1e3462e30/go.mod h1:fvzegU4vN3H1qMT+8wDmzjAcDONcgo2/SZ/TyfdUOFs= +github.com/alecthomas/units v0.0.0-20240927000941-0f3dac36c52b h1:mimo19zliBX/vSQ6PWWSL9lK8qwHozUj03+zLoEB8O0= +github.com/alecthomas/units v0.0.0-20240927000941-0f3dac36c52b/go.mod h1:fvzegU4vN3H1qMT+8wDmzjAcDONcgo2/SZ/TyfdUOFs= github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be h1:9AeTilPcZAjCFIImctFaOjnTIavg87rW78vTPkQqLI8= github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be/go.mod h1:ySMOLuWl6zY27l47sB3qLNK6tF2fkHG55UZxx8oIVo4= -github.com/containerd/stargz-snapshotter/estargz v0.14.3 h1:OqlDCK3ZVUO6C3B/5FSkDwbkEETK84kQgEeFwDC+62k= -github.com/containerd/stargz-snapshotter/estargz v0.14.3/go.mod h1:KY//uOCIkSuNAHhJogcZtrNHdKrA99/FCCRjE3HD36o= +github.com/containerd/stargz-snapshotter/estargz v0.15.1 h1:eXJjw9RbkLFgioVaTG+G/ZW/0kEe2oEKCdS/ZxIyoCU= +github.com/containerd/stargz-snapshotter/estargz v0.15.1/go.mod h1:gr2RNwukQ/S9Nv33Lt6UC7xEx58C+LHRdoqbEKjz1Kk= github.com/coreos/go-oidc/v3 v3.11.0 h1:Ia3MxdwpSw702YW0xgfmP1GVCMA9aEFWu12XUZ3/OtI= github.com/coreos/go-oidc/v3 v3.11.0/go.mod h1:gE3LgjOgFoHi9a4ce4/tJczr0Ai2/BoDhf0r5lltWI0= -github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= github.com/creack/pty v1.1.23 h1:4M6+isWdcStXEf15G/RbrMPOQj1dZ7HPZCGwE4kOeP0= github.com/creack/pty v1.1.23/go.mod h1:08sCNb52WyoAwi2QDyzUCTgcvVFhUzewun7wtTfvcwE= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/docker/cli v27.1.1+incompatible h1:goaZxOqs4QKxznZjjBWKONQci/MywhtRv2oNn0GkeZE= -github.com/docker/cli v27.1.1+incompatible/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8= -github.com/docker/distribution v2.8.2+incompatible h1:T3de5rq0dB1j30rp0sA2rER+m322EBzniBPB6ZIzuh8= -github.com/docker/distribution v2.8.2+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w= -github.com/docker/docker-credential-helpers v0.7.0 h1:xtCHsjxogADNZcdv1pKUHXryefjlVRqWqIhk/uXJp0A= -github.com/docker/docker-credential-helpers v0.7.0/go.mod h1:rETQfLdHNT3foU5kuNkFR1R1V12OJRRO5lzt2D1b5X0= +github.com/docker/cli v27.3.1+incompatible h1:qEGdFBF3Xu6SCvCYhc7CzaQTlBmqDuzxPDpigSyeKQQ= +github.com/docker/cli v27.3.1+incompatible/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8= +github.com/docker/distribution v2.8.3+incompatible h1:AtKxIZ36LoNK51+Z6RpzLpddBirtxJnzDrHLEKxTAYk= +github.com/docker/distribution v2.8.3+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w= +github.com/docker/docker-credential-helpers v0.8.2 h1:bX3YxiGzFP5sOXWc3bTPEXdEaZSeVMrFgOr3T+zrFAo= +github.com/docker/docker-credential-helpers v0.8.2/go.mod h1:P3ci7E3lwkZg6XiHdRKft1KckHiO9a2rNtyFbZ/ry9M= github.com/echocat/slf4g v1.6.1 h1:nQt0ZivDOsn8dyf3BJaHj1hWTF9MXsoefPUoTbVBI18= github.com/echocat/slf4g v1.6.1/go.mod h1:YvF/d1TcPvT+/xiHStLHPI4xPT1GGeEmPczn2MSljNA= github.com/echocat/slf4g/native v1.6.1 h1:g8Y8abLWwFkSQit4JQUvPO1MqpHUiPFZZINjkvPzY50= @@ -45,8 +43,9 @@ github.com/gliderlabs/ssh v0.3.7 h1:iV3Bqi942d9huXnzEF2Mt+CY9gLu8DNM4Obd+8bODRE= github.com/gliderlabs/ssh v0.3.7/go.mod h1:zpHEXBstFnQYtGnB8k8kQLol82umzn/2/snG7alWVD8= github.com/go-jose/go-jose/v4 v4.0.4 h1:VsjPI33J0SB9vQM6PLmNjoHqMQNGPiZ0rHL7Ni7Q6/E= github.com/go-jose/go-jose/v4 v4.0.4/go.mod h1:NKb5HO1EZccyMpiZNbdUw/14tiXNyUJh188dfnMCAfc= -github.com/go-ole/go-ole v1.2.6 h1:/Fpf6oFPoeFik9ty7siob0G6Ke8QvQEuVcuChpwXzpY= github.com/go-ole/go-ole v1.2.6/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0= +github.com/go-ole/go-ole v1.3.0 h1:Dt6ye7+vXGIKZ7Xtk4s6/xVdGDQynvom7xCFEdWr6uE= +github.com/go-ole/go-ole v1.3.0/go.mod h1:5LS6F96DhAwUc7C+1HLexzMXY1xGRSryjyPPKW6zv78= github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= @@ -64,8 +63,8 @@ github.com/huandu/xstrings v1.5.0 h1:2ag3IFq9ZDANvthTwTiqSSZLjDc+BedvHPAp5tJy2TI github.com/huandu/xstrings v1.5.0/go.mod h1:y5/lhBue+AyNmUVz9RLU9xbLR0o4KIIExikq4ovT0aE= github.com/kardianos/osext v0.0.0-20190222173326-2bc1f35cddc0 h1:iQTw/8FWTuc7uiaSepXwyf3o52HaUYcV+Tu66S3F5GA= github.com/kardianos/osext v0.0.0-20190222173326-2bc1f35cddc0/go.mod h1:1NbS8ALrpOvjt0rHPNLyCIeMtbizbir8U//inJ+zuB8= -github.com/klauspost/compress v1.16.5 h1:IFV2oUNUzZaz+XyusxpLzpzS8Pt5rh0Z16For/djlyI= -github.com/klauspost/compress v1.16.5/go.mod h1:ntbaceVETuRiXiv4DpjP66DpAtAGkEQskQzEyD//IeE= +github.com/klauspost/compress v1.17.10 h1:oXAz+Vh0PMUvJczoi+flxpnBEPxoER1IaAnU/NMPtT0= +github.com/klauspost/compress v1.17.10/go.mod h1:pMDklpSncoRMuLFrf1W9Ss9KT+0rH90U12bZKk7uwG0= github.com/kr/fs v0.1.0 h1:Jskdu9ieNAYnjxsi0LbQp1ulIKZV1LAFgK1tWhpZgl8= github.com/kr/fs v0.1.0/go.mod h1:FFnZGqtBN9Gxj7eW1uZ42v5BccTP0vu6NEaFoC2HwRg= github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= @@ -91,8 +90,8 @@ github.com/msteinert/pam/v2 v2.0.0 h1:jnObb8MT6jvMbmrUQO5J/puTUjxy7Av+55zVJRJsCy github.com/msteinert/pam/v2 v2.0.0/go.mod h1:KT28NNIcDFf3PcBmNI2mIGO4zZJ+9RSs/At2PB3IDVc= github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U= github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM= -github.com/opencontainers/image-spec v1.1.0-rc3 h1:fzg1mXZFj8YdPeNkRXMg+zb88BFV0Ys52cJydRwBkb8= -github.com/opencontainers/image-spec v1.1.0-rc3/go.mod h1:X4pATf0uXsnn3g5aiGIsVnJBR4mxhKzfwmvK/B2NTm8= +github.com/opencontainers/image-spec v1.1.0 h1:8SG7/vwALn54lVB/0yZ/MMwhFrPYtpEHQb2IpWsCzug= +github.com/opencontainers/image-spec v1.1.0/go.mod h1:W4s4sFTMaBeK1BQLXbG4AdM2szdn85PY75RI83NrTrM= github.com/openwall/yescrypt-go v1.0.0 h1:jsGk48zkFvtUjGVOhYPGh+CS595JmTRcKnpggK2AON4= github.com/openwall/yescrypt-go v1.0.0/go.mod h1:e6CWtFizUEOUttaOjeVMiv1lJaJie3mfOtLJ9CCD6sA= github.com/otiai10/copy v1.14.0 h1:dCI/t1iTdYGtkvCuBG2BgR6KZa83PTclw4U5n2wAllU= @@ -105,16 +104,14 @@ github.com/pkg/sftp v1.13.6 h1:JFZT4XbOU7l77xGSpOdW+pwIMqP044IyjXX6FGyEKFo= github.com/pkg/sftp v1.13.6/go.mod h1:tz1ryNURKu77RL+GuCzmoJYxQczL3wLNNpPWagdg4Qk= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= -github.com/rogpeppe/go-internal v1.12.0 h1:exVL4IDcn6na9z1rAb56Vxr+CgyK3nn3O+epU5NdKM8= -github.com/rogpeppe/go-internal v1.12.0/go.mod h1:E+RYuTGaKKdloAfM02xzb0FW3Paa99yedzYV+kq4uf4= -github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= +github.com/rogpeppe/go-internal v1.13.1 h1:KvO1DLK/DRN07sQ1LQKScxyZJuNnedQ5/wKSR38lUII= +github.com/rogpeppe/go-internal v1.13.1/go.mod h1:uMEvuHeurkdAXX61udpOXGD/AzZDWNMNyH2VO9fmH0o= github.com/shirou/gopsutil v3.21.11+incompatible h1:+1+c1VGhc88SSonWP6foOcLhvnKlUeu/erjjvaPEYiI= github.com/shirou/gopsutil v3.21.11+incompatible/go.mod h1:5b4v6he4MtMOwMlS0TUMTu2PcXUg8+E1lC7eC3UO/RA= github.com/shopspring/decimal v1.4.0 h1:bxl37RwXBklmTi0C79JfXCEBD1cqqHt0bbgBAGFp81k= github.com/shopspring/decimal v1.4.0/go.mod h1:gawqmDU56v4yIKSwfBSFip1HdCCXN8/+DMd9qYNcwME= -github.com/sirupsen/logrus v1.9.0/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= -github.com/sirupsen/logrus v1.9.1 h1:Ou41VVR3nMWWmTiEUnj0OlsgOSCUFgsPAOl6jRIcVtQ= -github.com/sirupsen/logrus v1.9.1/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= +github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ= +github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= github.com/spf13/cast v1.7.0 h1:ntdiHjuueXFgm5nzDRdOS4yfT43P5Fnud6DH50rz/7w= github.com/spf13/cast v1.7.0/go.mod h1:ancEpBxwJDODSW/UG4rDrAqiKolqNNh2DX3mk86cAdo= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= @@ -124,7 +121,6 @@ github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/ github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= -github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= @@ -132,19 +128,18 @@ github.com/tg123/go-htpasswd v1.2.2 h1:tmNccDsQ+wYsoRfiONzIhDm5OkVHQzN3w4FOBAlN6 github.com/tg123/go-htpasswd v1.2.2/go.mod h1:FcIrK0J+6zptgVwK1JDlqyajW/1B4PtuJ/FLWl7nx8A= github.com/tklauser/go-sysconf v0.3.14 h1:g5vzr9iPFFz24v2KZXs/pvpvh8/V9Fw6vQK5ZZb78yU= github.com/tklauser/go-sysconf v0.3.14/go.mod h1:1ym4lWMLUOhuBOPGtRcJm7tEGX4SCYNEEEtghGG/8uY= -github.com/tklauser/numcpus v0.8.0 h1:Mx4Wwe/FjZLeQsK/6kt2EOepwwSl7SmJrK5bV/dXYgY= -github.com/tklauser/numcpus v0.8.0/go.mod h1:ZJZlAY+dmR4eut8epnzf0u/VwodKmryxR8txiloSqBE= -github.com/urfave/cli v1.22.12/go.mod h1:sSBEIC79qR6OvcmsD4U3KABeOTxDqQtdDnaFuUN30b8= -github.com/vbatts/tar-split v0.11.3 h1:hLFqsOLQ1SsppQNTMpkpPXClLDfC2A3Zgy9OUU+RVck= -github.com/vbatts/tar-split v0.11.3/go.mod h1:9QlHN18E+fEH7RdG+QAJJcuya3rqT7eXSTY7wGrAokY= +github.com/tklauser/numcpus v0.9.0 h1:lmyCHtANi8aRUgkckBgoDk1nHCux3n2cgkJLXdQGPDo= +github.com/tklauser/numcpus v0.9.0/go.mod h1:SN6Nq1O3VychhC1npsWostA+oW+VOQTxZrS604NSRyI= +github.com/vbatts/tar-split v0.11.6 h1:4SjTW5+PU11n6fZenf2IPoV8/tz3AaYHMWjf23envGs= +github.com/vbatts/tar-split v0.11.6/go.mod h1:dqKNtesIOr2j2Qv3W/cHjnvk9I8+G7oAkFDFN6TCBEI= github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= github.com/yusufpapurcu/wmi v1.2.4 h1:zFUKzehAFReQwLys1b/iSMl+JQGSCSjtVqQn9bBrPo0= github.com/yusufpapurcu/wmi v1.2.4/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQmPyzfmi0= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.1.0/go.mod h1:RecgLatLF4+eUMCP1PoPZQb+cVrJcOPbHkTkbkB9sbw= -golang.org/x/crypto v0.28.0 h1:GBDwsMXVQi34v5CCYUm2jkJvu4cbtru2U4TN2PSyQnw= -golang.org/x/crypto v0.28.0/go.mod h1:rmgy+3RHxRZMyY0jjAJShp2zgEdOqj2AO7U0pYmeQ7U= +golang.org/x/crypto v0.27.0 h1:GXm2NjJrPaiv/h1tb2UH8QfgC/hOf/+z0p6PT8o1w7A= +golang.org/x/crypto v0.27.0/go.mod h1:1Xngt8kV6Dvbssa53Ziq6Eqn0HqbZi5Z6R0ZpwQzt70= golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= @@ -163,15 +158,14 @@ golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220906165534-d0df966e6959/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.26.0 h1:KHjCJyddX0LoSTb3J+vWpupP9p0oznkqVk/IfjymZbo= golang.org/x/sys v0.26.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.1.0/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= -golang.org/x/term v0.25.0 h1:WtHI/ltw4NvSUig5KARz9h521QvRC8RmF/cuYqifU24= -golang.org/x/term v0.25.0/go.mod h1:RPyXicDX+6vLxogjjRxjgD2TKtmAO6NZBsBRfrOLu7M= +golang.org/x/term v0.24.0 h1:Mh5cbb+Zk2hqqXNO7S1iTjEphVL+jb8ZWaqh/g+JWkM= +golang.org/x/term v0.24.0/go.mod h1:lOBK/LVxemqiMij05LGJ0tzNr8xlmwBRJ81PX6wVLH8= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= @@ -184,7 +178,6 @@ golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8T gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= -gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/pkg/configuration/authorization-htpasswd.go b/pkg/configuration/authorization-htpasswd.go index 1c3d59a..29bbb03 100644 --- a/pkg/configuration/authorization-htpasswd.go +++ b/pkg/configuration/authorization-htpasswd.go @@ -46,7 +46,7 @@ func (this *AuthorizationHtpasswd) Trim() error { ) } -func (this *AuthorizationHtpasswd) Validate() error { +func (this *AuthorizationHtpasswd) Validate() (rErr error) { return validate(this, func(v *AuthorizationHtpasswd) (string, validator) { return "file", &v.File }, func(v *AuthorizationHtpasswd) (string, validator) { return "entries", &v.Entries }, diff --git a/pkg/configuration/configuration.go b/pkg/configuration/configuration.go index a2f3ce1..b114432 100644 --- a/pkg/configuration/configuration.go +++ b/pkg/configuration/configuration.go @@ -11,6 +11,10 @@ import ( "github.com/engity-com/bifroest/pkg/sys" ) +var ( + DefaultStartMessage = "" +) + type Configuration struct { Ssh Ssh `yaml:"ssh"` @@ -23,6 +27,8 @@ type Configuration struct { Flows Flows `yaml:"flows"` HouseKeeping HouseKeeping `yaml:"housekeeping"` + + StartMessage string `yaml:"startMessage,omitempty"` } func (this *Configuration) SetDefaults() error { @@ -31,6 +37,7 @@ func (this *Configuration) SetDefaults() error { func(v *Configuration) (string, defaulter) { return "session", &v.Session }, func(v *Configuration) (string, defaulter) { return "flows", &v.Flows }, func(v *Configuration) (string, defaulter) { return "houseKeeping", &v.HouseKeeping }, + fixedDefault("startMessage", func(v *Configuration) *string { return &v.StartMessage }, DefaultStartMessage), ) } @@ -40,6 +47,7 @@ func (this *Configuration) Trim() error { func(v *Configuration) (string, trimmer) { return "session", &v.Session }, func(v *Configuration) (string, trimmer) { return "flows", &v.Flows }, func(v *Configuration) (string, trimmer) { return "houseKeeping", &v.HouseKeeping }, + noopTrim[Configuration]("startMessage"), ) } @@ -50,6 +58,7 @@ func (this *Configuration) Validate() error { func(v *Configuration) (string, validator) { return "flows", &v.Flows }, notEmptySliceValidate("flows", func(v *Configuration) *[]Flow { return (*[]Flow)(&v.Flows) }), func(v *Configuration) (string, validator) { return "houseKeeping", &v.HouseKeeping }, + noopValidate[Configuration]("startMessage"), ) } @@ -111,5 +120,6 @@ func (this Configuration) isEqualTo(other *Configuration) bool { return isEqual(&this.Ssh, &other.Ssh) && isEqual(&this.Session, &other.Session) && isEqual(&this.Flows, &other.Flows) && - isEqual(&this.HouseKeeping, &other.HouseKeeping) + isEqual(&this.HouseKeeping, &other.HouseKeeping) && + this.StartMessage == other.StartMessage } diff --git a/pkg/service/service.go b/pkg/service/service.go index 618c06b..5a4eef1 100644 --- a/pkg/service/service.go +++ b/pkg/service/service.go @@ -5,6 +5,7 @@ import ( "fmt" "io" "net" + "strings" "sync" "sync/atomic" "syscall" @@ -55,6 +56,15 @@ func (this *Service) Run(ctx context.Context) (rErr error) { if ctx == nil { ctx = context.Background() } + + if msg := this.Configuration.StartMessage; msg != "" { + for _, line := range strings.Split(msg, "\n") { + if line = strings.TrimSpace(line); line != "" { + log.Warn(line) + } + } + } + svc, err := this.prepare() if err != nil { return err diff --git a/pkg/user/etc-colon-repository_test.go b/pkg/user/etc-colon-repository_test.go index 611893c..b437f49 100644 --- a/pkg/user/etc-colon-repository_test.go +++ b/pkg/user/etc-colon-repository_test.go @@ -418,6 +418,7 @@ bar:XbarX:20453:10:100:::20818:`, groupFile := dir.file("group").setContent(c.group) shadowFile := dir.file("shadow").setContent(c.shadow) + var asyncError error instance := EtcColonRepository{ PasswdFilename: passwdFile.name(), GroupFilename: groupFile.name(), @@ -426,6 +427,11 @@ bar:XbarX:20453:10:100:::20818:`, AllowBadLine: &c.allowBadLine, OnUnhandledAsyncError: c.onUnhandledAsyncError, } + if instance.OnUnhandledAsyncError == nil { + instance.OnUnhandledAsyncError = func(_ log.Logger, err error, _ string) { + asyncError = err + } + } actualErr := instance.Init(context.Background()) if expectedErr := c.expectedError; expectedErr != "" { @@ -440,6 +446,8 @@ bar:XbarX:20453:10:100:::20818:`, actualErr = instance.Close() require.NoError(t, actualErr) + + require.NoError(t, asyncError) }) } } @@ -1454,6 +1462,7 @@ expired-ts:$y$j9T$as2ASyXW241FbtyMlNNQU1$sy6H9k6uXgaY1DeIKI5zPVsczWLD82k5UeQVuIM require.NoError(t, actualErr) assert.NoError(t, syncError) + time.Sleep(150 * time.Millisecond) }) } }