diff --git a/go.mod b/go.mod index 6b0a7aed..9b66e619 100644 --- a/go.mod +++ b/go.mod @@ -5,12 +5,14 @@ go 1.23.4 require ( github.com/Dynatrace/libbuildpack-dynatrace v1.8.0 github.com/cloudfoundry/libbuildpack v0.0.0-20240717165421-f2ae8069fcba - github.com/cloudfoundry/switchblade v0.9.2 + github.com/cloudfoundry/switchblade v0.9.3 github.com/golang/mock v1.6.0 github.com/kardolus/httpmock v0.0.0-20181110092731-53def6cd0f87 github.com/onsi/ginkgo v1.16.5 github.com/onsi/gomega v1.36.2 github.com/sclevine/spec v1.4.0 + go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.32.0 // indirect + go.opentelemetry.io/otel/sdk v1.32.0 // indirect ) require ( @@ -19,6 +21,7 @@ require ( github.com/Microsoft/go-winio v0.6.1 // indirect github.com/blang/semver v3.5.1+incompatible // indirect github.com/cenkalti/backoff/v4 v4.3.0 // indirect + github.com/containerd/log v0.1.0 // indirect github.com/distribution/reference v0.6.0 // indirect github.com/docker/docker v27.5.1+incompatible // indirect github.com/docker/go-connections v0.4.0 // indirect @@ -38,6 +41,7 @@ require ( github.com/paketo-buildpacks/packit v1.3.1 // indirect github.com/paketo-buildpacks/packit/v2 v2.16.0 // indirect github.com/pkg/errors v0.9.1 // indirect + github.com/rogpeppe/go-internal v1.13.1 // indirect github.com/teris-io/shortid v0.0.0-20220617161101-71ec9f2aa569 // indirect github.com/tidwall/gjson v1.18.0 // indirect github.com/tidwall/match v1.1.1 // indirect @@ -45,9 +49,7 @@ require ( github.com/ulikunitz/xz v0.5.12 // indirect go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.57.0 // indirect go.opentelemetry.io/otel v1.32.0 // indirect - go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.32.0 // indirect go.opentelemetry.io/otel/metric v1.32.0 // indirect - go.opentelemetry.io/otel/sdk v1.32.0 // indirect go.opentelemetry.io/otel/trace v1.32.0 // indirect golang.org/x/mod v0.22.0 // indirect golang.org/x/net v0.35.0 // indirect @@ -55,19 +57,14 @@ require ( golang.org/x/sys v0.30.0 // indirect golang.org/x/text v0.22.0 // indirect golang.org/x/tools v0.29.0 // indirect + google.golang.org/genproto/googleapis/api v0.0.0-20241104194629-dd2ea8efbc28 // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20241104194629-dd2ea8efbc28 // indirect gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 // indirect gopkg.in/yaml.v2 v2.4.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect ) -exclude google.golang.org/genproto v0.0.0-20230403163135-c38d8f061ccd - -require ( - github.com/containerd/log v0.1.0 // indirect - github.com/rogpeppe/go-internal v1.13.1 // indirect - google.golang.org/genproto/googleapis/api v0.0.0-20241104194629-dd2ea8efbc28 // indirect - google.golang.org/genproto/googleapis/rpc v0.0.0-20241104194629-dd2ea8efbc28 // indirect -) +exclude google.golang.org/genproto v0.0.0-20230403163135-ca9f95a91c1a replace ( github.com/docker/distribution => github.com/docker/distribution v2.8.2+incompatible diff --git a/go.sum b/go.sum index b951b2e4..9b47fe4b 100644 --- a/go.sum +++ b/go.sum @@ -746,8 +746,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-20240717165421-f2ae8069fcba h1:RdbOeYpXLO3wdrQrKuQFUIl7aLx6cdk95Lh/uFwAA9c= github.com/cloudfoundry/libbuildpack v0.0.0-20240717165421-f2ae8069fcba/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.3 h1:BTtnDoWkClfzvlwWm37mylu0eFCZTwN62MtkskFOB2c= +github.com/cloudfoundry/switchblade v0.9.3/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= @@ -2901,6 +2901,7 @@ google.golang.org/genproto v0.0.0-20230223222841-637eb2293923/go.mod h1:3Dl5ZL0q google.golang.org/genproto v0.0.0-20230303212802-e74f57abe488/go.mod h1:TvhZT5f700eVlTNwND1xoEZQeWTB2RY/65kplwl/bFA= google.golang.org/genproto v0.0.0-20230306155012-7f2fa6fef1f4/go.mod h1:NWraEVixdDnqcqQ30jipen1STv2r/n24Wb7twVTGR4s= google.golang.org/genproto v0.0.0-20230320184635-7606e756e683/go.mod h1:NWraEVixdDnqcqQ30jipen1STv2r/n24Wb7twVTGR4s= +google.golang.org/genproto v0.0.0-20230403163135-c38d8f061ccd/go.mod h1:UUQDJDOlWu4KYeJZffbWgBkS1YFobzKbLVfK69pe0Ak= google.golang.org/genproto/googleapis/api v0.0.0-20241104194629-dd2ea8efbc28 h1:M0KvPgPmDZHPlbRbaNU1APr28TvwvvdUPlSv7PUvy8g= google.golang.org/genproto/googleapis/api v0.0.0-20241104194629-dd2ea8efbc28/go.mod h1:dguCy7UOdZhTvLzDyt15+rOrawrpM4q7DD9dQ1P11P4= google.golang.org/genproto/googleapis/rpc v0.0.0-20241104194629-dd2ea8efbc28 h1:XVhgTWWV3kGQlwJHR3upFWZeTsei6Oks1apkZSeonIE= diff --git a/vendor/github.com/cloudfoundry/switchblade/README.md b/vendor/github.com/cloudfoundry/switchblade/README.md index 1fc0e6e2..f7e4623f 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 30bb0556..53b34f0e 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 3f0a5911..fcce5f09 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 16d44062..9042a283 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 00000000..2c445d7f --- /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/stage.go b/vendor/github.com/cloudfoundry/switchblade/internal/cloudfoundry/stage.go index cee76d1d..dcefd060 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 fc232e8f..20a79238 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 fb75c9c9..5c53a112 100644 --- a/vendor/modules.txt +++ b/vendor/modules.txt @@ -29,7 +29,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.3 ## explicit; go 1.23.0 github.com/cloudfoundry/switchblade github.com/cloudfoundry/switchblade/internal/cloudfoundry