diff --git a/go.mod b/go.mod index d25334225..18ae37ff3 100644 --- a/go.mod +++ b/go.mod @@ -6,7 +6,7 @@ require ( github.com/Dynatrace/libbuildpack-dynatrace v1.8.0 github.com/Masterminds/semver v1.5.0 github.com/cloudfoundry/libbuildpack v0.0.0-20251203175254-7be530ec9fef - github.com/cloudfoundry/switchblade v0.9.2 + github.com/cloudfoundry/switchblade v0.9.4 github.com/golang/mock v1.6.0 github.com/onsi/ginkgo v1.16.5 github.com/onsi/gomega v1.36.2 diff --git a/go.sum b/go.sum index cac3eed54..d6a180b65 100644 --- a/go.sum +++ b/go.sum @@ -478,8 +478,8 @@ github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDk github.com/cloudflare/circl v1.1.0/go.mod h1:prBCrKB9DV4poKZY1l9zBXg2QJY7mvgRvtMxxK7fi4I= github.com/cloudfoundry/libbuildpack v0.0.0-20251203175254-7be530ec9fef h1:lrggsL5p4dr3bBK/x1xIu3sn/6PGYV71GQIe/mCNfFw= github.com/cloudfoundry/libbuildpack v0.0.0-20251203175254-7be530ec9fef/go.mod h1:kn4FHMwI8bTd9gT92wPGjXHzUvGcj8CkPxG8q3AGBAQ= -github.com/cloudfoundry/switchblade v0.9.2 h1:b2lwxrAblg9uKncNQRKZ09/teuKdZIixcENKgrLQPjo= -github.com/cloudfoundry/switchblade v0.9.2/go.mod h1:hIEQdGAsuNnzlyQfsD5OIORt38weSBar6Wq5/JX6Omo= +github.com/cloudfoundry/switchblade v0.9.4 h1:93O90a/DRRcZ4h50htDh4z7+FMliqy/lQH6IFgVa+mQ= +github.com/cloudfoundry/switchblade v0.9.4/go.mod h1:hIEQdGAsuNnzlyQfsD5OIORt38weSBar6Wq5/JX6Omo= github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= github.com/cncf/udpa/go v0.0.0-20200629203442-efcf912fb354/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= diff --git a/vendor/github.com/cloudfoundry/switchblade/README.md b/vendor/github.com/cloudfoundry/switchblade/README.md index 1fc0e6e2a..f7e4623f9 100644 --- a/vendor/github.com/cloudfoundry/switchblade/README.md +++ b/vendor/github.com/cloudfoundry/switchblade/README.md @@ -175,6 +175,31 @@ deployment, logs, err := platform.Deploy. Execute("my-app", "/path/to/my/app/source") ``` +### Retrieving runtime logs: `RuntimeLogs` + +The `deployment.RuntimeLogs()` method retrieves logs from the running application +after deployment succeeds. This is useful for testing runtime behavior such as +application startup, service connections, and module loading. + +```go +// Deploy an application +deployment, stagingLogs, err := platform.Deploy.Execute("my-app", "/path/to/my/app/source") +Expect(err).NotTo(HaveOccurred()) + +// stagingLogs contains build-time output (buildpack detection, compilation, etc.) +Expect(stagingLogs).To(ContainLines(ContainSubstring("Installing dependencies..."))) + +// Retrieve runtime logs (application startup, service connections, etc.) +runtimeLogs, err := deployment.RuntimeLogs() +Expect(err).NotTo(HaveOccurred()) +Expect(runtimeLogs).To(ContainSubstring("Application started")) +Expect(runtimeLogs).To(ContainSubstring("Connected to Redis")) +``` + +**Note:** The logs returned from `platform.Deploy.Execute()` are **staging logs** +(build-time), while `deployment.RuntimeLogs()` returns **runtime logs** (post-deployment). +Use staging logs to test buildpack behavior, and runtime logs to test application behavior. + ## Other utilities ### Random name generation: `RandomName` diff --git a/vendor/github.com/cloudfoundry/switchblade/cloudfoundry.go b/vendor/github.com/cloudfoundry/switchblade/cloudfoundry.go index 30bb0556b..53b34f0e2 100644 --- a/vendor/github.com/cloudfoundry/switchblade/cloudfoundry.go +++ b/vendor/github.com/cloudfoundry/switchblade/cloudfoundry.go @@ -14,11 +14,11 @@ import ( //go:generate faux --package github.com/cloudfoundry/switchblade/internal/cloudfoundry --interface StagePhase --name CloudFoundryStagePhase --output fakes/cloudfoundry_stage_phase.go //go:generate faux --package github.com/cloudfoundry/switchblade/internal/cloudfoundry --interface TeardownPhase --name CloudFoundryTeardownPhase --output fakes/cloudfoundry_teardown_phase.go -func NewCloudFoundry(initialize cloudfoundry.InitializePhase, deinitialize cloudfoundry.DeinitializePhase, setup cloudfoundry.SetupPhase, stage cloudfoundry.StagePhase, teardown cloudfoundry.TeardownPhase, workspace string) Platform { +func NewCloudFoundry(initialize cloudfoundry.InitializePhase, deinitialize cloudfoundry.DeinitializePhase, setup cloudfoundry.SetupPhase, stage cloudfoundry.StagePhase, teardown cloudfoundry.TeardownPhase, workspace string, cli cloudfoundry.Executable) Platform { return Platform{ initialize: cloudFoundryInitializeProcess{initialize: initialize}, deinitialize: cloudFoundryDeinitializeProcess{deinitialize: deinitialize}, - Deploy: cloudFoundryDeployProcess{setup: setup, stage: stage, workspace: workspace}, + Deploy: cloudFoundryDeployProcess{setup: setup, stage: stage, workspace: workspace, cli: cli}, Delete: cloudFoundryDeleteProcess{teardown: teardown, workspace: workspace}, } } @@ -51,6 +51,7 @@ type cloudFoundryDeployProcess struct { setup cloudfoundry.SetupPhase stage cloudfoundry.StagePhase workspace string + cli cloudfoundry.Executable } func (p cloudFoundryDeployProcess) WithBuildpacks(buildpacks ...string) DeployProcess { @@ -111,6 +112,9 @@ func (p cloudFoundryDeployProcess) Execute(name, source string) (Deployment, fmt Name: name, ExternalURL: externalURL, InternalURL: internalURL, + platform: CloudFoundry, + workspace: home, + cfCLI: p.cli, }, logs, nil } diff --git a/vendor/github.com/cloudfoundry/switchblade/deployment.go b/vendor/github.com/cloudfoundry/switchblade/deployment.go index 3f0a5911c..fcce5f094 100644 --- a/vendor/github.com/cloudfoundry/switchblade/deployment.go +++ b/vendor/github.com/cloudfoundry/switchblade/deployment.go @@ -1,7 +1,77 @@ package switchblade +import ( + "context" + "fmt" + "io" + + "github.com/cloudfoundry/switchblade/internal/cloudfoundry" + "github.com/docker/docker/api/types/container" +) + +//go:generate faux --interface LogsClient --output fakes/logs_client.go +type LogsClient interface { + ContainerLogs(ctx context.Context, container string, options container.LogsOptions) (io.ReadCloser, error) +} + type Deployment struct { Name string ExternalURL string InternalURL string + + // Internal fields for log retrieval + platform string + workspace string + cfCLI cloudfoundry.Executable + dockerCLI LogsClient +} + +// RuntimeLogs retrieves recent logs from the running application. +// These are logs generated after the application has started (post-staging). +// This method abstracts platform-specific log retrieval for both +// CloudFoundry and Docker platforms. +// +// Use this for testing: +// - Application startup messages +// - Service connections +// - Module/extension loading +// - Runtime configuration +// +// For build-time logs (staging, buildpack detection), use the logs +// returned from platform.Deploy.Execute() instead. +func (d Deployment) RuntimeLogs() (string, error) { + switch d.platform { + case CloudFoundry: + return d.logsCloudFoundry() + case Docker: + return d.logsDocker() + default: + return "", fmt.Errorf("unknown platform type: %q", d.platform) + } +} + +func (d Deployment) logsCloudFoundry() (string, error) { + return cloudfoundry.FetchRecentLogs(d.cfCLI, d.workspace, d.Name) +} + +func (d Deployment) logsDocker() (string, error) { + ctx := context.Background() + + options := container.LogsOptions{ + ShowStdout: true, + ShowStderr: true, + } + + reader, err := d.dockerCLI.ContainerLogs(ctx, d.Name, options) + if err != nil { + return "", fmt.Errorf("failed to retrieve container logs: %w", err) + } + defer reader.Close() + + logs, err := io.ReadAll(reader) + if err != nil { + return "", fmt.Errorf("failed to read logs: %w", err) + } + + return string(logs), nil } diff --git a/vendor/github.com/cloudfoundry/switchblade/docker.go b/vendor/github.com/cloudfoundry/switchblade/docker.go index 16d44062a..9042a2836 100644 --- a/vendor/github.com/cloudfoundry/switchblade/docker.go +++ b/vendor/github.com/cloudfoundry/switchblade/docker.go @@ -15,11 +15,11 @@ import ( //go:generate faux --package github.com/cloudfoundry/switchblade/internal/docker --interface StartPhase --name DockerStartPhase --output fakes/docker_start_phase.go //go:generate faux --package github.com/cloudfoundry/switchblade/internal/docker --interface TeardownPhase --name DockerTeardownPhase --output fakes/docker_teardown_phase.go -func NewDocker(initialize docker.InitializePhase, deinitialize docker.DeinitializePhase, setup docker.SetupPhase, stage docker.StagePhase, start docker.StartPhase, teardown docker.TeardownPhase) Platform { +func NewDocker(initialize docker.InitializePhase, deinitialize docker.DeinitializePhase, setup docker.SetupPhase, stage docker.StagePhase, start docker.StartPhase, teardown docker.TeardownPhase, client LogsClient) Platform { return Platform{ initialize: dockerInitializeProcess{initialize: initialize}, deinitialize: dockerDeinitializeProcess{deinitialize: deinitialize}, - Deploy: dockerDeployProcess{setup: setup, stage: stage, start: start}, + Deploy: dockerDeployProcess{setup: setup, stage: stage, start: start, client: client}, Delete: dockerDeleteProcess{teardown: teardown}, } } @@ -49,9 +49,10 @@ func (p dockerDeinitializeProcess) Execute() error { } type dockerDeployProcess struct { - setup docker.SetupPhase - stage docker.StagePhase - start docker.StartPhase + setup docker.SetupPhase + stage docker.StagePhase + start docker.StartPhase + client LogsClient } func (p dockerDeployProcess) WithBuildpacks(buildpacks ...string) DeployProcess { @@ -120,6 +121,8 @@ func (p dockerDeployProcess) Execute(name, path string) (Deployment, fmt.Stringe Name: name, ExternalURL: externalURL, InternalURL: internalURL, + platform: Docker, + dockerCLI: p.client, }, logs, nil } diff --git a/vendor/github.com/cloudfoundry/switchblade/internal/cloudfoundry/logs.go b/vendor/github.com/cloudfoundry/switchblade/internal/cloudfoundry/logs.go new file mode 100644 index 000000000..2c445d7fc --- /dev/null +++ b/vendor/github.com/cloudfoundry/switchblade/internal/cloudfoundry/logs.go @@ -0,0 +1,28 @@ +package cloudfoundry + +import ( + "bytes" + "fmt" + "os" + + "github.com/paketo-buildpacks/packit/v2/pexec" +) + +// FetchRecentLogs retrieves recent application logs using 'cf logs --recent'. +// This is a shared helper used for both staging failures and runtime log retrieval. +func FetchRecentLogs(cli Executable, home, appName string) (string, error) { + env := append(os.Environ(), fmt.Sprintf("CF_HOME=%s", home)) + buffer := bytes.NewBuffer(nil) + + err := cli.Execute(pexec.Execution{ + Args: []string{"logs", appName, "--recent"}, + Stdout: buffer, + Stderr: buffer, + Env: env, + }) + if err != nil { + return "", fmt.Errorf("failed to retrieve logs: %w", err) + } + + return buffer.String(), nil +} diff --git a/vendor/github.com/cloudfoundry/switchblade/internal/cloudfoundry/setup.go b/vendor/github.com/cloudfoundry/switchblade/internal/cloudfoundry/setup.go index dd6ee2a42..8cd9c9b91 100644 --- a/vendor/github.com/cloudfoundry/switchblade/internal/cloudfoundry/setup.go +++ b/vendor/github.com/cloudfoundry/switchblade/internal/cloudfoundry/setup.go @@ -183,7 +183,12 @@ func (s Setup) Run(log io.Writer, home, name, source string) (string, error) { Env: env, }) if err != nil { - return "", fmt.Errorf("failed to create-shared-domain: %w\n\nOutput:\n%s", err, log) + logStr := log.(*bytes.Buffer).String() + if strings.Contains(logStr, "already in use") { + fmt.Fprintf(log, "TCP domain already exists, continuing...\n") + } else { + return "", fmt.Errorf("failed to create-shared-domain: %w\n\nOutput:\n%s", err, log) + } } } diff --git a/vendor/github.com/cloudfoundry/switchblade/internal/cloudfoundry/stage.go b/vendor/github.com/cloudfoundry/switchblade/internal/cloudfoundry/stage.go index cee76d1d9..dcefd0607 100644 --- a/vendor/github.com/cloudfoundry/switchblade/internal/cloudfoundry/stage.go +++ b/vendor/github.com/cloudfoundry/switchblade/internal/cloudfoundry/stage.go @@ -37,17 +37,11 @@ func (s Stage) Run(logs io.Writer, home, name string) (string, error) { if err != nil { // In CF API v3, staging failure logs are not automatically captured in stdout/stderr // We need to fetch them explicitly using 'cf logs --recent' - recentLogs := bytes.NewBuffer(nil) - logErr := s.cli.Execute(pexec.Execution{ - Args: []string{"logs", name, "--recent"}, - Stdout: recentLogs, - Stderr: recentLogs, - Env: env, - }) - if logErr == nil && recentLogs.Len() > 0 { + recentLogs, logErr := FetchRecentLogs(s.cli, home, name) + if logErr == nil && len(recentLogs) > 0 { // Append recent logs to the main logs buffer _, _ = logs.Write([]byte("\n--- Recent Logs (cf logs --recent) ---\n")) - _, _ = logs.Write(recentLogs.Bytes()) + _, _ = logs.Write([]byte(recentLogs)) } return "", fmt.Errorf("failed to start: %w\n\nOutput:\n%s", err, logs) diff --git a/vendor/github.com/cloudfoundry/switchblade/platform.go b/vendor/github.com/cloudfoundry/switchblade/platform.go index fc232e8fd..20a792385 100644 --- a/vendor/github.com/cloudfoundry/switchblade/platform.go +++ b/vendor/github.com/cloudfoundry/switchblade/platform.go @@ -71,9 +71,9 @@ func NewPlatform(platformType, token, stack string) (Platform, error) { stage := cloudfoundry.NewStage(cli) teardown := cloudfoundry.NewTeardown(cli) - return NewCloudFoundry(initialize, deinitialize, setup, stage, teardown, os.TempDir()), nil + return NewCloudFoundry(initialize, deinitialize, setup, stage, teardown, os.TempDir(), cli), nil case Docker: - client, err := client.NewClientWithOpts(client.FromEnv, client.WithAPIVersionNegotiation()) + dockerClient, err := client.NewClientWithOpts(client.FromEnv, client.WithAPIVersionNegotiation()) if err != nil { return Platform{}, err } @@ -86,16 +86,16 @@ func NewPlatform(platformType, token, stack string) (Platform, error) { buildpacksCache := docker.NewBuildpacksCache(filepath.Join(workspace, "buildpacks-cache")) buildpacksRegistry := docker.NewBuildpacksRegistry("https://api.github.com", token) buildpacksManager := docker.NewBuildpacksManager(archiver, buildpacksCache, buildpacksRegistry) - networkManager := docker.NewNetworkManager(client) + networkManager := docker.NewNetworkManager(dockerClient) initialize := docker.NewInitialize(buildpacksRegistry, networkManager) deinitialize := docker.NewDeinitialize(networkManager) - setup := docker.NewSetup(client, lifecycleManager, buildpacksManager, archiver, networkManager, workspace, stack) - stage := docker.NewStage(client, archiver, workspace) - start := docker.NewStart(client, networkManager, workspace, stack) - teardown := docker.NewTeardown(client, workspace) + setup := docker.NewSetup(dockerClient, lifecycleManager, buildpacksManager, archiver, networkManager, workspace, stack) + stage := docker.NewStage(dockerClient, archiver, workspace) + start := docker.NewStart(dockerClient, networkManager, workspace, stack) + teardown := docker.NewTeardown(dockerClient, workspace) - return NewDocker(initialize, deinitialize, setup, stage, start, teardown), nil + return NewDocker(initialize, deinitialize, setup, stage, start, teardown, dockerClient), nil } return Platform{}, fmt.Errorf("unknown platform type: %q", platformType) diff --git a/vendor/modules.txt b/vendor/modules.txt index 80adb9fc8..0c5581988 100644 --- a/vendor/modules.txt +++ b/vendor/modules.txt @@ -30,7 +30,7 @@ github.com/cloudfoundry/libbuildpack/cutlass github.com/cloudfoundry/libbuildpack/cutlass/docker github.com/cloudfoundry/libbuildpack/cutlass/glow github.com/cloudfoundry/libbuildpack/packager -# github.com/cloudfoundry/switchblade v0.9.2 +# github.com/cloudfoundry/switchblade v0.9.4 ## explicit; go 1.23.0 github.com/cloudfoundry/switchblade github.com/cloudfoundry/switchblade/internal/cloudfoundry