From d2535ae0dd1ae460a7e693f6f7acec635e6ff0fc Mon Sep 17 00:00:00 2001 From: Peter Baumgartner Date: Mon, 22 Sep 2025 11:29:29 -0600 Subject: [PATCH] Fix apppack.toml artifact archival when using custom APPPACK_TOML path MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit When APPPACK_TOML env var is set to a custom location, the build process now copies the file to the default location (apppack.toml) after the build completes. This ensures the configuration is properly included in build artifacts regardless of its source location. Changes: - Add CopyAppPackTomlToDefault() function to handle the copy operation - Call the function after build completion with non-fatal error handling - Add comprehensive test coverage for the new functionality - Use DefaultAppPackTomlFilename constant for consistency 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude --- builder/build/apppacktoml.go | 2 +- builder/build/build.go | 6 ++ builder/filesystem/filesystem.go | 24 +++++++- builder/filesystem/filesystem_test.go | 89 ++++++++++++++++++++++++++- 4 files changed, 117 insertions(+), 4 deletions(-) diff --git a/builder/build/apppacktoml.go b/builder/build/apppacktoml.go index ebc671a..9e355a2 100644 --- a/builder/build/apppacktoml.go +++ b/builder/build/apppacktoml.go @@ -6,8 +6,8 @@ import ( "os" "strings" - "github.com/apppackio/codebuild-image/builder/filesystem" "github.com/BurntSushi/toml" + "github.com/apppackio/codebuild-image/builder/filesystem" "github.com/rs/zerolog/log" ) diff --git a/builder/build/build.go b/builder/build/build.go index 1e45916..3a7605d 100644 --- a/builder/build/build.go +++ b/builder/build/build.go @@ -9,6 +9,7 @@ import ( "sync" "github.com/apppackio/codebuild-image/builder/containers" + "github.com/apppackio/codebuild-image/builder/filesystem" "github.com/docker/docker/api/types/container" "github.com/google/go-containerregistry/pkg/crane" cp "github.com/otiai10/copy" @@ -121,6 +122,11 @@ func (b *Build) RunBuild() error { if err = cp.Copy(logFile.Name(), "build.log"); err != nil { return err } + // Copy the apppack.toml file to the default location if it was read from a custom location + if err = filesystem.CopyAppPackTomlToDefault(); err != nil { + b.Log().Warn().Err(err).Msg("Failed to copy apppack.toml to default location for artifact archival") + // Don't fail the build if we can't copy the file, just warn + } return b.state.WriteCommitTxt() } diff --git a/builder/filesystem/filesystem.go b/builder/filesystem/filesystem.go index 7960894..97d109f 100644 --- a/builder/filesystem/filesystem.go +++ b/builder/filesystem/filesystem.go @@ -17,7 +17,10 @@ import ( "github.com/spf13/afero" ) -const envFileFilename = "env.json" +const ( + envFileFilename = "env.json" + DefaultAppPackTomlFilename = "apppack.toml" +) type State interface { CreateIfNotExists() error @@ -181,9 +184,26 @@ func (f *FileState) WriteJsonToFile(filename string, v interface{}) error { } func GetAppPackTomlFilename() string { - filename := "apppack.toml" + filename := DefaultAppPackTomlFilename if envFile := os.Getenv("APPPACK_TOML"); envFile != "" { filename = envFile } return filename } + +// CopyAppPackTomlToDefault copies the apppack.toml file from a custom location to the default location +// This is needed for artifact archival when APPPACK_TOML env var is used to specify a custom location +// Returns nil if the file is already at the default location or if copy succeeds +func CopyAppPackTomlToDefault() error { + apppackTomlPath := GetAppPackTomlFilename() + if apppackTomlPath == DefaultAppPackTomlFilename { + // File is already at the default location, nothing to do + return nil + } + + log.Debug().Msgf("Copying %s to %s for artifact archival", apppackTomlPath, DefaultAppPackTomlFilename) + if err := cp.Copy(apppackTomlPath, DefaultAppPackTomlFilename); err != nil { + return fmt.Errorf("failed to copy %s to %s: %w", apppackTomlPath, DefaultAppPackTomlFilename, err) + } + return nil +} diff --git a/builder/filesystem/filesystem_test.go b/builder/filesystem/filesystem_test.go index dfd62c1..25cb4d0 100644 --- a/builder/filesystem/filesystem_test.go +++ b/builder/filesystem/filesystem_test.go @@ -7,6 +7,8 @@ import ( "fmt" "io" "os" + "path/filepath" + "strings" "testing" "github.com/rs/zerolog" @@ -117,7 +119,7 @@ func TestGetFilename(t *testing.T) { // Call GetAppPackTomlFilename and check the default value filename := GetAppPackTomlFilename() - if filename != "apppack.toml" { + if filename != DefaultAppPackTomlFilename { t.Errorf("expected apppack.toml, got %s", filename) } @@ -130,6 +132,91 @@ func TestGetFilename(t *testing.T) { } } +func TestCopyAppPackTomlToDefault(t *testing.T) { + tests := []struct { + name string + envValue string + setupFiles map[string]string + expectCopy bool + expectError bool + errorContains string + }{ + { + name: "no copy needed when using default location", + envValue: "", + expectCopy: false, + }, + { + name: "copies from custom location", + envValue: "config/custom.toml", + setupFiles: map[string]string{ + "config/custom.toml": "[build]\ntest = true\n", + }, + expectCopy: true, + }, + { + name: "error when custom file doesn't exist", + envValue: "nonexistent.toml", + expectCopy: false, + expectError: true, + errorContains: "failed to copy nonexistent.toml", + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + // Set up environment + if tt.envValue != "" { + os.Setenv("APPPACK_TOML", tt.envValue) + defer os.Unsetenv("APPPACK_TOML") + } + + // Create temp directory for test + tempDir := t.TempDir() + originalDir, _ := os.Getwd() + os.Chdir(tempDir) + defer os.Chdir(originalDir) + + // Set up test files + for path, content := range tt.setupFiles { + dir := filepath.Dir(path) + if dir != "." { + os.MkdirAll(dir, 0o755) + } + os.WriteFile(path, []byte(content), 0o644) + } + + // Run the function + err := CopyAppPackTomlToDefault() + + // Check error + if tt.expectError { + if err == nil { + t.Errorf("expected error, got nil") + } else if tt.errorContains != "" && !strings.Contains(err.Error(), tt.errorContains) { + t.Errorf("expected error to contain %q, got %q", tt.errorContains, err.Error()) + } + } else if err != nil { + t.Errorf("unexpected error: %v", err) + } + + // Check if file was copied + if tt.expectCopy { + if _, err := os.Stat(DefaultAppPackTomlFilename); os.IsNotExist(err) { + t.Errorf("expected %s to exist after copy", DefaultAppPackTomlFilename) + } else { + // Verify content matches + expected := tt.setupFiles[tt.envValue] + actual, _ := os.ReadFile(DefaultAppPackTomlFilename) + if string(actual) != expected { + t.Errorf("copied content mismatch: got %q, want %q", actual, expected) + } + } + } + }) + } +} + func dummyTarBuffer() (*io.Reader, error) { var buf bytes.Buffer tw := tar.NewWriter(&buf)