diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json index a74abd42..1362fdeb 100644 --- a/.devcontainer/devcontainer.json +++ b/.devcontainer/devcontainer.json @@ -1,13 +1,17 @@ { "name": "leeway", - "build": { - "context": "..", - "dockerfile": "../.gitpod.Dockerfile" + "image": "mcr.microsoft.com/devcontainers/base:ubuntu", + "features": { + "ghcr.io/devcontainers/features/node:1": { + "version": "lts" + }, + "ghcr.io/devcontainers/features/go:1": { + "version": "1.24.0" + }, + "ghcr.io/devcontainers/features/common-utils:2": {}, + "ghcr.io/devcontainers-contrib/features/shfmt:1": { + "version": "3.10.0" + } }, - "runArgs": [ - "--privileged", - "--security-opt=seccomp=unconfined", - "--network=host" - ], - "containerUser": "root" -} \ No newline at end of file + "postStartCommand": "./.devcontainer/install-leeway.sh" +} diff --git a/.devcontainer/install-leeway.sh b/.devcontainer/install-leeway.sh new file mode 100755 index 00000000..802f8b07 --- /dev/null +++ b/.devcontainer/install-leeway.sh @@ -0,0 +1,27 @@ +#!/bin/bash +# This script downloads and installs the latest version of leeway + +set -euo pipefail + +# Get the latest leeway version from GitHub API +LATEST_LEEWAY_VERSION=$(curl -s https://api.github.com/repos/gitpod-io/leeway/releases/latest | jq -r '.tag_name' | sed 's/^v//') + +# Ensure we got a valid version +if [ -z "$LATEST_LEEWAY_VERSION" ]; then + echo "Error: Could not determine latest leeway version" >&2 + exit 1 +fi + +echo "Installing leeway version: $LATEST_LEEWAY_VERSION" + +# Download the latest leeway release +curl -L -o /tmp/leeway.tar.gz "https://github.com/gitpod-io/leeway/releases/download/v${LATEST_LEEWAY_VERSION}/leeway_Linux_x86_64.tar.gz" + +# Extract the tarball +tar -xzf /tmp/leeway.tar.gz -C /tmp + +# Install leeway to /usr/local/bin +sudo install -m 755 /tmp/leeway /usr/local/bin/ + +# Clean up temporary files +rm /tmp/leeway.tar.gz /tmp/leeway diff --git a/.gitpod.Dockerfile b/.gitpod.Dockerfile deleted file mode 100644 index 675d3f97..00000000 --- a/.gitpod.Dockerfile +++ /dev/null @@ -1,52 +0,0 @@ -FROM ubuntu:jammy - -ADD https://raw.githubusercontent.com/gitpod-io/workspace-images/main/base/install-packages /usr/bin/install-packages -RUN chmod +x /usr/bin/install-packages - -RUN install-packages \ - zip unzip \ - jq \ - curl \ - ca-certificates \ - file \ - git \ - node.js - -ENV GO_VERSION=1.24.0 -RUN echo "TARGETPLATFORM=${TARGETPLATFORM:-linux/amd64}" && \ - case "${TARGETPLATFORM:-linux/amd64}" in \ - "linux/arm64") \ - echo "GO_PLATFORM=linux-arm64" > /tmp/go_platform.env \ - ;; \ - *) \ - echo "GO_PLATFORM=linux-amd64" > /tmp/go_platform.env \ - ;; \ - esac - -# Install Go and add it to PATH -RUN . /tmp/go_platform.env && \ - curl -fsSL https://dl.google.com/go/go$GO_VERSION.$GO_PLATFORM.tar.gz | tar -C /usr/local -xzs - -# Set Go environment variables -ENV GOROOT=/usr/local/go -ENV PATH=$GOROOT/bin:$PATH -ENV GOPATH=/root/go -ENV PATH=$GOPATH/bin:$PATH - -# install VS Code Go tools for use with gopls as per https://github.com/golang/vscode-go/blob/master/docs/tools.md -# also https://github.com/golang/vscode-go/blob/27bbf42a1523cadb19fad21e0f9d7c316b625684/src/goTools.ts#L139 -RUN go install -v github.com/uudashr/gopkgs/cmd/gopkgs@v2 \ - && go install -v github.com/ramya-rao-a/go-outline@latest \ - && go install -v github.com/cweill/gotests/gotests@latest \ - && go install -v github.com/fatih/gomodifytags@latest \ - && go install -v github.com/josharian/impl@latest \ - && go install -v github.com/haya14busa/goplay/cmd/goplay@latest \ - && go install -v github.com/go-delve/delve/cmd/dlv@latest \ - && go install -v github.com/golangci/golangci-lint/cmd/golangci-lint@latest \ - && go install -v golang.org/x/tools/gopls@latest \ - && go install -v honnef.co/go/tools/cmd/staticcheck@latest \ - && rm -rf /root/.cache - -ENV SHFMT_VERSION=3.10.0 -RUN curl -sSL -o /usr/local/bin/shfmt "https://github.com/mvdan/sh/releases/download/v${SHFMT_VERSION}/shfmt_v${SHFMT_VERSION}_linux_amd64" && \ - chmod 755 /usr/local/bin/shfmt diff --git a/.gitpod.yml b/.gitpod.yml deleted file mode 100644 index 140dff50..00000000 --- a/.gitpod.yml +++ /dev/null @@ -1,20 +0,0 @@ - -checkoutLocation: leeway -workspaceLocation: leeway - -image: - file: .gitpod.Dockerfile - -# List the ports you want to expose and what to do when they are served. See https://www.gitpod.io/docs/43_config_ports/ -ports: -- port: 3000 - -# List the start up tasks. You can start them in parallel in multiple terminals. See https://www.gitpod.io/docs/44_config_start_tasks/ -tasks: -- init: | - git config core.hooksPath .githooks; - go generate -v ./... -vscode: - extensions: - - golang.go - - eamodio.gitlens diff --git a/cmd/build.go b/cmd/build.go index 367cbdf8..b1195a95 100644 --- a/cmd/build.go +++ b/cmd/build.go @@ -136,13 +136,19 @@ func saveBuildResult(ctx context.Context, loc string, localCache cache.LocalCach } fin, err := os.OpenFile(br, os.O_RDONLY, 0644) if err != nil { - fout.Close() + if closeErr := fout.Close(); closeErr != nil { + log.WithError(closeErr).Warn("failed to close output file") + } log.WithError(err).Fatal("cannot copy build result") } _, err = io.Copy(fout, fin) - fout.Close() - fin.Close() + if err := fout.Close(); err != nil { + log.WithError(err).Warn("failed to close output file") + } + if err := fin.Close(); err != nil { + log.WithError(err).Warn("failed to close input file") + } if err != nil { log.WithError(err).Fatal("cannot copy build result") } @@ -246,7 +252,11 @@ func getBuildOpts(cmd *cobra.Command) ([]leeway.BuildOption, cache.LocalCache) { if err != nil { log.Fatal(err) } - defer f.Close() + defer func() { + if err := f.Close(); err != nil { + log.WithError(err).Warn("failed to close plan file") + } + }() planOutlet = f } diff --git a/cmd/exec.go b/cmd/exec.go index 0efc9c5f..8d26e94c 100644 --- a/cmd/exec.go +++ b/cmd/exec.go @@ -273,7 +273,11 @@ func executeCommandInLocations(rawExecCmd []string, locs []commandExecLocation, return fmt.Errorf("execution failed in %s (%s): %w", loc.Name, loc.Dir, err) } _ = pty.InheritSize(ptmx, os.Stdin) - defer ptmx.Close() + defer func() { + if err := ptmx.Close(); err != nil { + log.WithError(err).Warn("failed to close pty") + } + }() //nolint:errcheck go io.Copy(textio.NewPrefixWriter(os.Stdout, prefix), ptmx) @@ -358,7 +362,9 @@ func (c filesystemExecCache) MarkExecuted(ctx context.Context, loc commandExecLo if err != nil { return err } - f.Close() + if err := f.Close(); err != nil { + log.WithError(err).Warn("failed to close file") + } log.WithField("name", fn).Debug("marked executed") return nil } diff --git a/cmd/experiemental-unmount.go b/cmd/experiemental-unmount.go index fa7f2642..1d984b50 100644 --- a/cmd/experiemental-unmount.go +++ b/cmd/experiemental-unmount.go @@ -80,13 +80,21 @@ var unmountCmd = &cobra.Command{ if err != nil { return err } - defer src.Close() + defer func() { + if err := src.Close(); err != nil { + logrus.WithError(err).Warn("failed to close source file") + } + }() f, err := os.OpenFile(dst, os.O_CREATE|os.O_TRUNC|os.O_WRONLY, info.Mode()) if err != nil { return err } - defer f.Close() + defer func() { + if err := f.Close(); err != nil { + logrus.WithError(err).Warn("failed to close destination file") + } + }() logrus.WithField("dest", dst).Debug("applying change: copying content") _, err = io.Copy(f, src) diff --git a/cmd/fmt.go b/cmd/fmt.go index 895602f9..b00e285a 100644 --- a/cmd/fmt.go +++ b/cmd/fmt.go @@ -48,7 +48,11 @@ func formatBuildYaml(fn string, inPlace, fix bool) error { if err != nil { return err } - defer f.Close() + defer func() { + if err := f.Close(); err != nil { + fmt.Fprintf(os.Stderr, "warning: failed to close file: %v\n", err) + } + }() var out io.Writer = os.Stdout if inPlace { diff --git a/cmd/init.go b/cmd/init.go index f0c2df1e..b4316a0c 100644 --- a/cmd/init.go +++ b/cmd/init.go @@ -55,14 +55,18 @@ var initCmd = &cobra.Command{ err = yaml.Unmarshal(tpl, &pkg) if err != nil { log.WithField("template", string(tpl)).Warn("broken package template") - return fmt.Errorf("This is a leeway bug. Cannot parse package template: %w", err) + return fmt.Errorf("this is a leeway bug, cannot parse package template: %w", err) } f, err := os.OpenFile("BUILD.yaml", os.O_CREATE|os.O_RDWR, 0644) if err != nil { return err } - defer f.Close() + defer func() { + if err := f.Close(); err != nil { + fmt.Fprintf(os.Stderr, "warning: failed to close BUILD.yaml file: %v\n", err) + } + }() var cmp yaml.Node err = yaml.NewDecoder(f).Decode(&cmp) @@ -75,7 +79,7 @@ var initCmd = &cobra.Command{ cmps := cmp.Content[0].Content for i, nde := range cmps { - if !(nde.Value == "packages" && i < len(cmps)-1 && cmps[i+1].Kind == yaml.SequenceNode) { + if nde.Value != "packages" || i >= len(cmps)-1 || cmps[i+1].Kind != yaml.SequenceNode { continue } diff --git a/cmd/provenance-assert.go b/cmd/provenance-assert.go index 0be96f36..ceaf0e38 100644 --- a/cmd/provenance-assert.go +++ b/cmd/provenance-assert.go @@ -95,7 +95,11 @@ var provenanceAssertCmd = &cobra.Command{ if err != nil { log.WithError(err).Fatalf("cannot open attestation bundle %s", bundleFN) } - defer f.Close() + defer func() { + if err := f.Close(); err != nil { + log.WithError(err).Warn("failed to close attestation bundle file") + } + }() err = provutil.DecodeBundle(f, assert) } else { diff --git a/cmd/provenance-export.go b/cmd/provenance-export.go index 52074274..e0cb0f44 100644 --- a/cmd/provenance-export.go +++ b/cmd/provenance-export.go @@ -58,7 +58,11 @@ var provenanceExportCmd = &cobra.Command{ if err != nil { log.WithError(err).Fatal("cannot open attestation bundle") } - defer f.Close() + defer func() { + if err := f.Close(); err != nil { + log.WithError(err).Warn("failed to close attestation bundle file") + } + }() err = provutil.DecodeBundle(f, export) if err != nil { log.WithError(err).Fatal("cannot extract attestation bundle") diff --git a/cmd/root.go b/cmd/root.go index a3e9e9a9..5ac81080 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -111,7 +111,11 @@ func Execute() { log.WithError(err).Fatal("cannot start trace but LEEWAY_TRACE is set") return } - defer f.Close() + defer func() { + if err := f.Close(); err != nil { + log.WithError(err).Warn("failed to close trace file") + } + }() err = trace.Start(f) if err != nil { log.WithError(err).Fatal("cannot start trace but LEEWAY_TRACE is set") diff --git a/pkg/leeway/build.go b/pkg/leeway/build.go index 75eb71ed..8fdc4930 100644 --- a/pkg/leeway/build.go +++ b/pkg/leeway/build.go @@ -138,7 +138,11 @@ func newBuildContext(options buildOptions) (ctx *buildContext, err error) { if err != nil { return nil, xerrors.Errorf("cannot compute hash of myself: %w", err) } - defer self.Close() + defer func() { + if err := self.Close(); err != nil { + log.WithError(err).Warn("failed to close self") + } + }() leewayHash := sha256.New() _, err = io.Copy(leewayHash, self) if err != nil { @@ -974,7 +978,8 @@ func (p *Package) buildYarn(buildctx *buildContext, wd, result string) (bld *pac } var commands = make(map[PackageBuildPhase][][]string) - if cfg.Packaging == YarnOfflineMirror { + switch cfg.Packaging { + case YarnOfflineMirror: err := os.Mkdir(filepath.Join(wd, "_mirror"), 0755) if err != nil { return nil, err @@ -1007,7 +1012,8 @@ func (p *Package) buildYarn(buildctx *buildContext, wd, result string) (bld *pac } tgt := p.BuildLayoutLocation(deppkg) - if cfg.Packaging == YarnOfflineMirror { + switch cfg.Packaging { + case YarnOfflineMirror: fn := fmt.Sprintf("%s.tar.gz", tgt) commands[PackageBuildPhasePrep] = append(commands[PackageBuildPhasePrep], []string{"cp", builtpkg, filepath.Join("_mirror", fn)}) builtpkg = filepath.Join(wd, "_mirror", fn) @@ -1136,7 +1142,8 @@ func (p *Package) buildYarn(buildctx *buildContext, wd, result string) (bld *pac pkgCommands [][]string resultDir string ) - if cfg.Packaging == YarnOfflineMirror { + switch cfg.Packaging { + case YarnOfflineMirror: builtinScripts := map[string]string{ "get_yarn_lock.sh": getYarnLockScript, "install.sh": installScript, @@ -1161,12 +1168,12 @@ func (p *Package) buildYarn(buildctx *buildContext, wd, result string) (bld *pac ), }...) resultDir = "_mirror" - } else if cfg.Packaging == YarnLibrary { + case YarnLibrary: pkgCommands = append(pkgCommands, [][]string{ {"sh", "-c", fmt.Sprintf("yarn generate-lock-entry --resolved file://%s > %s", result, pkgYarnLock)}, {"yarn", "pack", "--filename", result}, }...) - } else if cfg.Packaging == YarnApp { + case YarnApp: err := os.Mkdir(filepath.Join(wd, "_pkg"), 0755) if err != nil { return nil, err @@ -1189,12 +1196,12 @@ func (p *Package) buildYarn(buildctx *buildContext, wd, result string) (bld *pac ), }...) resultDir = "_pkg" - } else if cfg.Packaging == YarnArchive { + case YarnArchive: pkgCommands = append(pkgCommands, BuildTarCommand( WithOutputFile(result), WithCompression(!buildctx.DontCompress), )) - } else { + default: return nil, xerrors.Errorf("unknown Yarn packaging: %s", cfg.Packaging) } res.Commands[PackageBuildPhasePackage] = pkgCommands @@ -1345,7 +1352,7 @@ func (p *Package) buildGo(buildctx *buildContext, wd, result string) (res *packa testCommand = append(testCommand, "-v") } - if buildctx.buildOptions.CoverageOutputPath != "" { + if buildctx.CoverageOutputPath != "" { testCommand = append(testCommand, fmt.Sprintf("-coverprofile=%v", codecovComponentName(p.FullName()))) } else { testCommand = append(testCommand, "-coverprofile=testcoverage.out") @@ -1377,7 +1384,7 @@ func (p *Package) buildGo(buildctx *buildContext, wd, result string) (res *packa ) if !cfg.DontTest && !buildctx.DontTest { commands[PackageBuildPhasePackage] = append(commands[PackageBuildPhasePackage], [][]string{ - {"sh", "-c", fmt.Sprintf(`if [ -f "%v" ]; then cp -f %v %v; fi`, codecovComponentName(p.FullName()), codecovComponentName(p.FullName()), buildctx.buildOptions.CoverageOutputPath)}, + {"sh", "-c", fmt.Sprintf(`if [ -f "%v" ]; then cp -f %v %v; fi`, codecovComponentName(p.FullName()), codecovComponentName(p.FullName()), buildctx.CoverageOutputPath)}, }...) } @@ -1745,13 +1752,21 @@ func extractImageNameFromCache(pkgName, cacheBundleFN string) (imgname string, e if err != nil { return "", err } - defer f.Close() + defer func() { + if err := f.Close(); err != nil { + log.WithError(err).Warn("failed to close file") + } + }() gzin, err := gzip.NewReader(f) if err != nil { return "", err } - defer gzin.Close() + defer func() { + if err := gzin.Close(); err != nil { + log.WithError(err).Warn("failed to close gzip reader") + } + }() tarin := tar.NewReader(gzin) for { diff --git a/pkg/leeway/build_linux.go b/pkg/leeway/build_linux.go index 3adc2f40..a248034f 100644 --- a/pkg/leeway/build_linux.go +++ b/pkg/leeway/build_linux.go @@ -36,7 +36,11 @@ func executeCommandsForPackageSafe(buildctx *buildContext, p *Package, wd string } if !log.IsLevelEnabled(log.DebugLevel) { - defer os.RemoveAll(tmpdir) + defer func() { + if err := os.RemoveAll(tmpdir); err != nil { + log.WithError(err).Warn("failed to remove temporary build directory") + } + }() } log.WithField("tmpdir", tmpdir).WithField("package", p.FullName()).Debug("preparing build runc environment") diff --git a/pkg/leeway/build_test.go b/pkg/leeway/build_test.go index 875dc479..5409024f 100644 --- a/pkg/leeway/build_test.go +++ b/pkg/leeway/build_test.go @@ -92,9 +92,15 @@ func TestBuildDockerDeps(t *testing.T) { if err != nil { t.Fatal(err) } - t.Cleanup(func() { os.RemoveAll(pth) }) + t.Cleanup(func() { + if err := os.RemoveAll(pth); err != nil { + t.Logf("warning: failed to remove temporary directory: %v", err) + } + }) - os.Setenv("PATH", pth+":"+os.Getenv("PATH")) + if err := os.Setenv("PATH", pth+":"+os.Getenv("PATH")); err != nil { + t.Fatalf("failed to set PATH: %v", err) + } log.WithField("path", os.Getenv("PATH")).Debug("modified path to use dummy docker") } testutil.RunDUT() @@ -160,9 +166,15 @@ func TestDockerPostProcessing(t *testing.T) { if err != nil { t.Fatal(err) } - t.Cleanup(func() { os.RemoveAll(pth) }) + t.Cleanup(func() { + if err := os.RemoveAll(pth); err != nil { + t.Logf("warning: failed to remove temporary directory: %v", err) + } + }) - os.Setenv("PATH", pth+":"+os.Getenv("PATH")) + if err := os.Setenv("PATH", pth+":"+os.Getenv("PATH")); err != nil { + t.Fatalf("failed to set PATH: %v", err) + } log.WithField("path", os.Getenv("PATH")).Debug("modified path to use dummy docker") } testutil.RunDUT() diff --git a/pkg/leeway/cache/local/fs_test.go b/pkg/leeway/cache/local/fs_test.go index 67f8b704..b7450876 100644 --- a/pkg/leeway/cache/local/fs_test.go +++ b/pkg/leeway/cache/local/fs_test.go @@ -67,7 +67,9 @@ func TestNewFilesystemCache(t *testing.T) { if test.Expectation.Error == "" { // Cleanup created directory - os.RemoveAll(test.Location) + if err := os.RemoveAll(test.Location); err != nil { + t.Logf("Failed to clean up test directory: %v", err) + } } }) } diff --git a/pkg/leeway/cache/remote/s3.go b/pkg/leeway/cache/remote/s3.go index 09b8a7f2..4151699c 100644 --- a/pkg/leeway/cache/remote/s3.go +++ b/pkg/leeway/cache/remote/s3.go @@ -457,13 +457,19 @@ func (s *S3Storage) GetObject(ctx context.Context, key string, dest string) (int if err != nil { return 0, fmt.Errorf("failed to create destination file: %w", err) } - defer file.Close() + defer func() { + if err := file.Close(); err != nil { + log.WithError(err).Warn("failed to close file") + } + }() // Set up cleanup in case of error var downloadErr error defer func() { if downloadErr != nil { - os.Remove(dest) + if err := os.Remove(dest); err != nil { + log.WithError(err).Warn("failed to remove destination file after download error") + } } }() @@ -504,7 +510,11 @@ func (s *S3Storage) UploadObject(ctx context.Context, key string, src string) er log.WithError(err).WithField("key", key).Warn("failed to open source file for upload") return fmt.Errorf("failed to open source file: %w", err) } - defer file.Close() + defer func() { + if err := file.Close(); err != nil { + log.WithError(err).Warn("failed to close file") + } + }() uploader := manager.NewUploader(s.client, func(u *manager.Uploader) { u.PartSize = defaultS3PartSize diff --git a/pkg/leeway/cache/remote/s3_download_test.go b/pkg/leeway/cache/remote/s3_download_test.go index 734cc216..57c34f37 100644 --- a/pkg/leeway/cache/remote/s3_download_test.go +++ b/pkg/leeway/cache/remote/s3_download_test.go @@ -163,7 +163,9 @@ func TestS3CacheDownload(t *testing.T) { // Clean up the temp directory files, _ := filepath.Glob(filepath.Join(tmpDir, "*")) for _, f := range files { - os.Remove(f) + if err := os.Remove(f); err != nil { + t.Logf("Failed to remove test file %s: %v", f, err) + } } mockStorage := &mockS3Storage{ diff --git a/pkg/leeway/cache/remote/s3_test.go b/pkg/leeway/cache/remote/s3_test.go index 168fcb4c..b32a7a3c 100644 --- a/pkg/leeway/cache/remote/s3_test.go +++ b/pkg/leeway/cache/remote/s3_test.go @@ -188,7 +188,11 @@ func TestS3Cache_Download(t *testing.T) { if err != nil { t.Fatalf("failed to create temp dir: %v", err) } - defer os.RemoveAll(tmpDir) + defer func() { + if err := os.RemoveAll(tmpDir); err != nil { + t.Logf("Failed to remove temp directory: %v", err) + } + }() // Create test directories if err := os.MkdirAll(tmpDir, 0755); err != nil { @@ -275,7 +279,9 @@ func TestS3Cache_Download(t *testing.T) { // Clean up any existing files files, _ := filepath.Glob(filepath.Join(tmpDir, "*")) for _, f := range files { - os.Remove(f) + if err := os.Remove(f); err != nil { + t.Logf("Failed to remove test file %s: %v", f, err) + } } mockClient := &mockS3Client{ @@ -340,7 +346,11 @@ func TestS3Cache_Upload(t *testing.T) { if err != nil { t.Fatalf("failed to create temp dir: %v", err) } - defer os.RemoveAll(tmpDir) + defer func() { + if err := os.RemoveAll(tmpDir); err != nil { + t.Logf("Failed to remove temp directory: %v", err) + } + }() // Create test directories if err := os.MkdirAll(tmpDir, 0755); err != nil { diff --git a/pkg/leeway/compression.go b/pkg/leeway/compression.go index 01929f4a..55137913 100644 --- a/pkg/leeway/compression.go +++ b/pkg/leeway/compression.go @@ -297,7 +297,12 @@ func isCompressedFile(filepath string) (CompressionAlgorithm, error) { if err != nil { return NoCompr, fmt.Errorf("failed to open file for compression detection: %w", err) } - defer file.Close() + defer func() { + if err := file.Close(); err != nil { + // Just log the error since we're in a detection function + fmt.Printf("Warning: failed to close file during compression detection: %v\n", err) + } + }() // Read the first few bytes to check for magic numbers header := make([]byte, 4) diff --git a/pkg/leeway/container_image.go b/pkg/leeway/container_image.go index 1afe5c15..c4989f30 100644 --- a/pkg/leeway/container_image.go +++ b/pkg/leeway/container_image.go @@ -42,7 +42,11 @@ func extractImageWithOCILibsImpl(destDir, imgTag string) error { if err != nil { return fmt.Errorf("failed to create temporary extraction directory: %w", err) } - defer os.RemoveAll(tempExtractDir) // Clean up temp dir after we're done + defer func() { + if err := os.RemoveAll(tempExtractDir); err != nil { + log.WithError(err).Warn("failed to remove temporary extraction directory") + } + }() // Clean up temp dir after we're done // Parse the image reference ref, err := name.ParseReference(imgTag) @@ -89,7 +93,11 @@ func extractImageWithOCILibsImpl(destDir, imgTag string) error { // Extract the filesystem by flattening the layers to the temp directory fs := mutate.Extract(img) - defer fs.Close() + defer func() { + if err := fs.Close(); err != nil { + log.WithError(err).Warn("failed to close filesystem reader") + } + }() // Extract the tar contents to the temporary directory if err := extractTarToDir(fs, tempExtractDir); err != nil { @@ -243,10 +251,14 @@ func extractTarToDir(r io.Reader, destDir string) error { } if _, err := io.Copy(f, tr); err != nil { - f.Close() + if closeErr := f.Close(); closeErr != nil { + log.WithError(closeErr).Warn("failed to close file after copy error") + } return err } - f.Close() + if err := f.Close(); err != nil { + log.WithError(err).Warn("failed to close file after copy") + } case tar.TypeSymlink: // Create containing directory @@ -347,7 +359,9 @@ func organizeContainerContent(sourceDir, contentDir string) error { if err := copyFileOrDirectory(sourceItemPath, targetItemPath); err != nil { return fmt.Errorf("copying %s to %s: %w", sourceItemPath, targetItemPath, err) } - os.RemoveAll(sourceItemPath) + if err := os.RemoveAll(sourceItemPath); err != nil { + log.WithError(err).Warn("failed to remove source item after copy") + } } } } else { @@ -357,7 +371,9 @@ func organizeContainerContent(sourceDir, contentDir string) error { if err := copyFileOrDirectory(sourcePath, targetPath); err != nil { return fmt.Errorf("copying %s to %s: %w", sourcePath, targetPath, err) } - os.Remove(sourcePath) + if err := os.Remove(sourcePath); err != nil { + log.WithError(err).Warn("failed to remove source file after copy") + } } } } @@ -400,14 +416,22 @@ func copyFileOrDirectory(src, dst string) error { if err != nil { return err } - defer source.Close() + defer func() { + if err := source.Close(); err != nil { + log.WithError(err).Warn("failed to close source file") + } + }() // Create destination file destination, err := os.OpenFile(dst, os.O_CREATE|os.O_WRONLY|os.O_TRUNC, sourceInfo.Mode()) if err != nil { return err } - defer destination.Close() + defer func() { + if err := destination.Close(); err != nil { + log.WithError(err).Warn("failed to close destination file") + } + }() _, err = io.Copy(destination, source) return err diff --git a/pkg/leeway/package.go b/pkg/leeway/package.go index 4329cf18..fcf19d6a 100644 --- a/pkg/leeway/package.go +++ b/pkg/leeway/package.go @@ -777,8 +777,8 @@ func (p *Package) FilesystemSafeName() string { } func FilesystemSafeName(fn string) string { - res := strings.Replace(fn, "/", "-", -1) - res = strings.Replace(res, ":", "--", -1) + res := strings.ReplaceAll(fn, "/", "-") + res = strings.ReplaceAll(res, ":", "--") // components in the workspace root would otherwise start with - which breaks a lot of shell commands res = strings.TrimLeft(res, "-") return res @@ -888,18 +888,21 @@ func (p *Package) ContentManifest() ([]string, error) { hash, err := highwayhash.New(key) if err != nil { - file.Close() + if closeErr := file.Close(); closeErr != nil { + log.WithError(closeErr).Warn("failed to close file after hash error") + } return nil, err } _, err = io.Copy(hash, file) if err != nil { - file.Close() + if closeErr := file.Close(); closeErr != nil { + log.WithError(closeErr).Warn("failed to close file after copy error") + } return nil, err } - err = file.Close() - if err != nil { + if err = file.Close(); err != nil { return nil, err } diff --git a/pkg/leeway/provenance.go b/pkg/leeway/provenance.go index cc87443c..e763571e 100644 --- a/pkg/leeway/provenance.go +++ b/pkg/leeway/provenance.go @@ -54,7 +54,11 @@ func writeProvenance(p *Package, buildctx *buildContext, builddir string, subjec if err != nil { return fmt.Errorf("cannot write provenance for %s: %w", p.FullName(), err) } - defer f.Close() + defer func() { + if closeErr := f.Close(); closeErr != nil { + log.WithError(closeErr).Warn("failed to close provenance file") + } + }() bundle := newAttestationBundle(f) err = p.getDependenciesProvenanceBundles(buildctx, bundle) @@ -115,13 +119,21 @@ func AccessAttestationBundleInCachedArchive(fn string, handler func(bundle io.Re if err != nil { return err } - defer f.Close() + defer func() { + if closeErr := f.Close(); closeErr != nil { + log.WithError(closeErr).Warn("failed to close file during hash computation") + } + }() g, err := gzip.NewReader(f) if err != nil { return err } - defer g.Close() + defer func() { + if closeErr := g.Close(); closeErr != nil { + log.WithError(closeErr).Warn("failed to close gzip reader") + } + }() var bundleFound bool a := tar.NewReader(g) @@ -310,7 +322,11 @@ func sha256Hash(fn string) (res string, err error) { return "", xerrors.Errorf("cannot compute hash of %s: %w", fn, err) } - defer f.Close() + defer func() { + if closeErr := f.Close(); closeErr != nil { + log.WithError(closeErr).Warn("failed to close file during hash computation") + } + }() hash := sha256.New() _, err = io.Copy(hash, f) @@ -380,7 +396,9 @@ func (fset fileset) Subjects(base string) ([]in_toto.Subject, error) { if err != nil { return nil, xerrors.Errorf("cannot compute hash of %s: %w", src, err) } - f.Close() + if closeErr := f.Close(); closeErr != nil { + log.WithError(closeErr).Warn("failed to close file during subject hash computation") + } res = append(res, in_toto.Subject{ Name: src, diff --git a/pkg/leeway/reporter.go b/pkg/leeway/reporter.go index 96c552f0..b5eb408e 100644 --- a/pkg/leeway/reporter.go +++ b/pkg/leeway/reporter.go @@ -154,21 +154,26 @@ func (r *ConsoleReporter) BuildStarted(pkg *Package, status map[*Package]Package } format := "%s\t%s\t%s\n" - if status == PackageBuilt { + switch status { + case PackageBuilt: lines[i] = fmt.Sprintf(format, color.Green.Sprint("📦\tcached locally"), pkg.FullName(), color.Gray.Sprintf("(version %s)", version)) - } else if status == PackageInRemoteCache { + case PackageInRemoteCache: lines[i] = fmt.Sprintf(format, color.Green.Sprint("🌎\tcached remotely (ignored)"), pkg.FullName(), color.Gray.Sprintf("(version %s)", version)) - } else if status == PackageDownloaded { + case PackageDownloaded: lines[i] = fmt.Sprintf(format, color.Green.Sprint("📥\tcached remotely (downloaded)"), pkg.FullName(), color.Gray.Sprintf("(version %s)", version)) - } else { + default: lines[i] = fmt.Sprintf(format, color.Yellow.Sprint("🔧\tbuild"), pkg.FullName(), color.Gray.Sprintf("(version %s)", version)) } i++ } sort.Slice(lines, func(i, j int) bool { return lines[i] < lines[j] }) tw := tabwriter.NewWriter(os.Stdout, 0, 2, 2, ' ', 0) - fmt.Fprintln(tw, strings.Join(lines, "")) - tw.Flush() + if _, err := fmt.Fprintln(tw, strings.Join(lines, "")); err != nil { + log.WithError(err).Warn("failed to write to tabwriter") + } + if err := tw.Flush(); err != nil { + log.WithError(err).Warn("failed to flush tabwriter") + } } // BuildFinished is called when the build of a package which was started by the user has finished. @@ -216,7 +221,7 @@ func (r *ConsoleReporter) PackageBuildFinished(pkg *Package, rep *PackageBuildRe r.mu.Unlock() var msg string - if rep.Error != nil { + if rep.Error != nil { msg = color.Sprintf("package build failed while %sing\nReason: %s\n", rep.LastPhase(), rep.Error) } else { var coverage string @@ -468,7 +473,11 @@ func (r *HTMLReporter) Report() { tmpl, _ := template.New("Report").Parse(strings.ReplaceAll(tmplString, "'", "`")) file, _ := os.Create(r.filename) - defer file.Close() + defer func() { + if closeErr := file.Close(); closeErr != nil { + log.WithError(closeErr).Warn("failed to close GITHUB_OUTPUT file") + } + }() err := tmpl.Execute(file, vars) if err != nil { @@ -576,7 +585,9 @@ func (sr *SegmentReporter) BuildFinished(pkg *Package, err error) { addPackageToSegmentEventProps(props, pkg) sr.track("build_finished", props) - sr.client.Close() + if err := sr.client.Close(); err != nil { + log.WithError(err).Warn("failed to close segment client") + } sr.client = nil } @@ -651,11 +662,17 @@ func (sr *GitHubActionReporter) PackageBuildFinished(pkg *Package, rep *PackageB log.WithField("fn", fn).WithError(err).Warn("cannot open GITHUB_OUTPUT file") return } - defer f.Close() + defer func() { + if closeErr := f.Close(); closeErr != nil { + log.WithError(closeErr).Warn("failed to close GITHUB_OUTPUT file") + } + }() var success bool if rep.Error == nil { success = true } - fmt.Fprintf(f, "%s=%v\n", pkg.FilesystemSafeName(), success) + if _, err := fmt.Fprintf(f, "%s=%v\n", pkg.FilesystemSafeName(), success); err != nil { + log.WithError(err).Warn("failed to write to GITHUB_OUTPUT file") + } } diff --git a/pkg/leeway/scripts.go b/pkg/leeway/scripts.go index 3004849e..ea800ea6 100644 --- a/pkg/leeway/scripts.go +++ b/pkg/leeway/scripts.go @@ -74,8 +74,8 @@ func (p *Script) GetDependencies() []*Package { // FilesystemSafeName returns a string that is safe to use in a Unix filesystem as directory or filename func (p *Script) FilesystemSafeName() string { pkgdir := p.FullName() - pkgdir = strings.Replace(pkgdir, "/", "-", -1) - pkgdir = strings.Replace(pkgdir, ":", "--", -1) + pkgdir = strings.ReplaceAll(pkgdir, "/", "-") + pkgdir = strings.ReplaceAll(pkgdir, ":", "--") // components in the workspace root would otherwise start with - which breaks a lot of shell commands pkgdir = strings.TrimLeft(pkgdir, "-") return pkgdir @@ -240,7 +240,11 @@ func executeBashScript(script string, wd string, env []string) error { if err != nil { return err } - defer os.Remove(f.Name()) + defer func() { + if err := os.Remove(f.Name()); err != nil { + log.WithError(err).Warn("failed to remove temporary script file") + } + }() _, err = f.WriteString("#!/bin/bash\n") if err != nil { @@ -250,7 +254,9 @@ func executeBashScript(script string, wd string, env []string) error { if err != nil { return err } - f.Close() + if err := f.Close(); err != nil { + return fmt.Errorf("failed to close temporary script file: %w", err) + } log.WithField("env", env).WithField("wd", wd).Debug("running bash script") diff --git a/pkg/leeway/watcher.go b/pkg/leeway/watcher.go index 0ccf6655..33c68062 100644 --- a/pkg/leeway/watcher.go +++ b/pkg/leeway/watcher.go @@ -93,7 +93,11 @@ func WatchSources(ctx context.Context, pkgs []*Package, debounceDuration time.Du } go func() { - defer watcher.Close() + defer func() { + if err := watcher.Close(); err != nil { + log.WithError(err).Warn("failed to close file watcher") + } + }() for { select { case evt := <-watcher.Events: diff --git a/pkg/leeway/workspace.go b/pkg/leeway/workspace.go index 1d3273dd..d8070ce2 100644 --- a/pkg/leeway/workspace.go +++ b/pkg/leeway/workspace.go @@ -561,7 +561,7 @@ func loadComponent(ctx context.Context, workspace *Workspace, path string, args } // replace build args - var rfc []byte = fc + rfc := fc if len(args) > 0 { rfc = replaceBuildArguments(fc, compargs) } diff --git a/pkg/linker/golang.go b/pkg/linker/golang.go index 1c0aad28..9438ca86 100644 --- a/pkg/linker/golang.go +++ b/pkg/linker/golang.go @@ -65,7 +65,7 @@ func LinkGoWorkspace(workspace *leeway.Workspace) error { continue } use.Syntax.InBlock = true - use.Syntax.Comments.Suffix = []modfile.Comment{{Token: "// leeway", Suffix: true}} + use.Syntax.Suffix = []modfile.Comment{{Token: "// leeway", Suffix: true}} } workFile.SortBlocks() workFile.Cleanup() @@ -253,7 +253,7 @@ func addReplace(gomod *modfile.File, old, new module.Version, direct bool, sourc for _, rep := range gomod.Replace { if rep.Old.Path == old.Path && rep.Old.Version == old.Version { rep.Syntax.InBlock = true - rep.Syntax.Comments.Suffix = []modfile.Comment{{Token: comment, Suffix: true}} + rep.Syntax.Suffix = []modfile.Comment{{Token: comment, Suffix: true}} } } return nil diff --git a/pkg/linker/yarn.go b/pkg/linker/yarn.go index 49688bf8..802a9aa8 100644 --- a/pkg/linker/yarn.go +++ b/pkg/linker/yarn.go @@ -100,7 +100,9 @@ func LinkYarnPackagesWithYarn2(workspace *leeway.Workspace) error { enc.SetEscapeHTML(false) enc.SetIndent("", " ") err = enc.Encode(pkgjson) - fd.Close() + if closeErr := fd.Close(); closeErr != nil { + log.WithError(closeErr).Warn("failed to close package.json file") + } if err != nil { return err } diff --git a/pkg/prettyprint/prettyprint.go b/pkg/prettyprint/prettyprint.go index 0a79dac7..dd3e9531 100644 --- a/pkg/prettyprint/prettyprint.go +++ b/pkg/prettyprint/prettyprint.go @@ -51,7 +51,12 @@ func writeTemplate(out io.Writer, in interface{}, tplc string) error { } w := tabwriter.NewWriter(out, 0, 0, 2, ' ', 0) - defer w.Flush() + defer func() { + if err := w.Flush(); err != nil { + // We can't do much if this fails too, but at least try to report it + _, _ = fmt.Fprintf(out, "warning: failed to flush tabwriter: %v\n", err) + } + }() return tpl.Execute(w, in) } diff --git a/pkg/testutil/testutil.go b/pkg/testutil/testutil.go index 3e96dbc1..eb16b254 100644 --- a/pkg/testutil/testutil.go +++ b/pkg/testutil/testutil.go @@ -164,7 +164,9 @@ func (ft *CommandFixtureTest) Run() { t.Fatalf("cannot load fixture from %s: %v", ft.FixturePath, err) } ft.Fixture, err = LoadFromYAML(fp) - fp.Close() + if closeErr := fp.Close(); closeErr != nil { + t.Logf("warning: failed to close fixture file: %v", closeErr) + } if err != nil { t.Fatalf("cannot load fixture from %s: %v", ft.FixturePath, err) } @@ -176,7 +178,11 @@ func (ft *CommandFixtureTest) Run() { t.Fatalf("cannot materialize fixture: %v", err) } t.Logf("materialized fixture workspace: %s", loc) - t.Cleanup(func() { os.RemoveAll(loc) }) + t.Cleanup(func() { + if err := os.RemoveAll(loc); err != nil { + t.Logf("warning: failed to remove temporary directory: %v", err) + } + }) } env := os.Environ() diff --git a/pkg/testutil/testutil_test.go b/pkg/testutil/testutil_test.go index 5e6c3cfe..3b8034b0 100644 --- a/pkg/testutil/testutil_test.go +++ b/pkg/testutil/testutil_test.go @@ -134,7 +134,11 @@ func TestMaterialise(t *testing.T) { if err != nil { t.Fatal(err) } - t.Cleanup(func() { os.RemoveAll(loc) }) + t.Cleanup(func() { + if err := os.RemoveAll(loc); err != nil { + t.Logf("warning: failed to remove temporary directory: %v", err) + } + }) t.Logf("materialized at %s", loc) for _, f := range test.Expectation { @@ -159,7 +163,9 @@ func TestMaterialise(t *testing.T) { continue } _, err = io.Copy(hash, fp) - fp.Close() + if closeErr := fp.Close(); closeErr != nil { + t.Logf("warning: failed to close file: %v", closeErr) + } if err != nil { t.Errorf("cannot hash %s: %v", fn, err) continue diff --git a/pkg/vet/docker.go b/pkg/vet/docker.go index 45a34d1e..35127f9b 100644 --- a/pkg/vet/docker.go +++ b/pkg/vet/docker.go @@ -25,7 +25,7 @@ func checkDockerCopyFromPackage(pkg *leeway.Package) ([]Finding, error) { if !ok { // this is an error as compared to a finding because the issue most likely is with leeway, // and not a user config error. - return nil, fmt.Errorf("Docker package does not have docker package config") + return nil, fmt.Errorf("docker package does not have docker package config") } var dockerfileFN string @@ -47,7 +47,11 @@ func checkDockerCopyFromPackage(pkg *leeway.Package) ([]Finding, error) { if err != nil { return nil, err } - defer f.Close() + defer func() { + if err := f.Close(); err != nil { + log.WithError(err).Warn("failed to close Dockerfile") + } + }() var findings []Finding scanner := bufio.NewScanner(f) diff --git a/pkg/vet/docker_test.go b/pkg/vet/docker_test.go index 62f606ed..c6079253 100644 --- a/pkg/vet/docker_test.go +++ b/pkg/vet/docker_test.go @@ -59,7 +59,11 @@ ADD from-some-pkg--build/hello.txt hello.txt`, tmpdir, err := os.MkdirTemp("", "leeway-test-*") failOnErr(err) - defer os.RemoveAll(tmpdir) + defer func() { + if err := os.RemoveAll(tmpdir); err != nil { + t.Logf("warning: failed to remove temporary directory: %v", err) + } + }() var pkgdeps string failOnErr(os.WriteFile(filepath.Join(tmpdir, "WORKSPACE.yaml"), []byte("environmentManifest:\n - name: \"docker\"\n command: [\"echo\"]"), 0644)) diff --git a/pkg/vet/generic.go b/pkg/vet/generic.go index a5bb00e2..87c0ee8a 100644 --- a/pkg/vet/generic.go +++ b/pkg/vet/generic.go @@ -17,7 +17,7 @@ func checkArgsReferingToPackage(pkg *leeway.Package) ([]Finding, error) { if !ok { // this is an error as compared to a finding because the issue most likely is with leeway, // and not a user config error. - return nil, fmt.Errorf("Generic package does not have generic package config") + return nil, fmt.Errorf("generic package does not have generic package config") } checkForFindings := func(fs []Finding, segmentIndex int, seg string) (findings []Finding) { diff --git a/pkg/vet/golang.go b/pkg/vet/golang.go index 6c22a4d0..a0ab327c 100644 --- a/pkg/vet/golang.go +++ b/pkg/vet/golang.go @@ -52,7 +52,7 @@ func checkGolangHasGomod(pkg *leeway.Package) ([]Finding, error) { func checkGolangHasBuildFlags(pkg *leeway.Package) ([]Finding, error) { goCfg, ok := pkg.Config.(leeway.GoPkgConfig) if !ok { - return nil, fmt.Errorf("Go package does not have go package config") + return nil, fmt.Errorf("go package does not have go package config") } if len(goCfg.BuildFlags) > 0 { diff --git a/pkg/vet/yarn.go b/pkg/vet/yarn.go index 1e1c1320..7401a5c8 100644 --- a/pkg/vet/yarn.go +++ b/pkg/vet/yarn.go @@ -97,7 +97,11 @@ func (c *checkImplicitTransitiveDependencies) grepInFile(fn string, pat *regexp. if err != nil { return } - defer f.Close() + defer func() { + if err := f.Close(); err != nil { + log.WithError(err).Warn("failed to close file") + } + }() r := bufio.NewReader(f) for {