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)