-
Notifications
You must be signed in to change notification settings - Fork 2
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Co-authored-by: Johannes Dillmann <j.dillmann@sap.com> Co-authored-by: Nicolas Bender <nicolas.bender@sap.com> Co-authored-by: Ralf Pannemans <ralf.pannemans@sap.com Co-authored-by: Pavel Busko <pavel.busko@sap.com>
- Loading branch information
1 parent
34cf9e4
commit 1fef9d9
Showing
38 changed files
with
2,363 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,26 @@ | ||
name: Go | ||
|
||
on: | ||
# pull_request: | ||
# branches: ["main"] | ||
# push: | ||
# branches: ["main"] | ||
workflow_dispatch: {} | ||
|
||
jobs: | ||
# unit: | ||
# runs-on: [self-hosted] | ||
# steps: | ||
# - uses: actions/checkout@v4 | ||
# - uses: actions/setup-go@v5 | ||
# with: | ||
# go-version: '1.22' | ||
# - run: make test | ||
# integration: | ||
# runs-on: [self-hosted] | ||
# steps: | ||
# - uses: actions/checkout@v4 | ||
# - uses: actions/setup-go@v5 | ||
# with: | ||
# go-version: '1.22' | ||
# - run: make integration |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
bin/ |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,15 @@ | ||
build: | ||
GOARCH=amd64 GOOS=linux go build -o bin/ -ldflags "-s -w" code.cloudfoundry.org/cnbapplifecycle/cmd/builder code.cloudfoundry.org/cnbapplifecycle/cmd/launcher | ||
test -f bin/diego-sshd || curl -sL https://storage.googleapis.com/cf-deployment-compiled-releases/diego-2.96.0-ubuntu-jammy-1.80-20240322-160011-008934012.tgz | tar -xzO ./compiled_packages/diego-sshd.tgz | tar -xzO ./diego-sshd > bin/diego-sshd && chmod +x bin/diego-sshd | ||
test -f bin/healthcheck || curl -sL https://storage.googleapis.com/cf-deployment-compiled-releases/diego-2.96.0-ubuntu-jammy-1.80-20240322-160011-008934012.tgz | tar -xzO ./compiled_packages/healthcheck.tgz | tar -xzO ./healthcheck > bin/healthcheck && chmod +x bin/healthcheck | ||
|
||
test: | ||
go test -v -count=1 ./... | ||
|
||
integration: build | ||
INCLUDE_INTEGRATION_TESTS=true go test -v -count=1 ./integration --ginkgo.label-filter integration -ginkgo.v | ||
|
||
package: build | ||
tar czf bin/cnb_app_lifecycle.tgz -C bin builder launcher diego-sshd healthcheck | ||
|
||
.PHONY: build test integration package |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1 +1,53 @@ | ||
# cnbapplifecycle | ||
|
||
Lifecycle that produces Cloud Foundry droplets using Cloud Native Buildpacks. | ||
|
||
## Builder | ||
|
||
| Flag(s) | Type | Description | Default | | ||
| ---------------------- | --------- | ------------------- | ---------------------- | | ||
| `-b`, `--buildpacks` | `strings` | buildpacks to use | | | ||
| `-d`, `--droplet` | `string` | output droplet file | `/tmp/droplet` | | ||
| `-l`, `--layers` | `string` | layers dir | `/home/vcap/layers` | | ||
| `-r`, `--result` | `string` | result file | `/tmp/result.json` | | ||
| `-w`, `--workspaceDir` | `string` | app workspace dir | `/home/vcap/workspace` | | ||
|
||
### Metadata | ||
|
||
Example | ||
|
||
```json | ||
{ | ||
"lifecycle_metadata": { | ||
"buildpacks": [ | ||
{ | ||
"key": "paketo-buildpacks/node-engine", | ||
"name": "paketo-buildpacks/node-engine@3.2.1", | ||
"version": "3.2.1" | ||
}, | ||
{ | ||
"key": "paketo-buildpacks/npm-install", | ||
"name": "paketo-buildpacks/npm-install@1.5.0", | ||
"version": "1.5.0" | ||
}, | ||
{ | ||
"key": "paketo-buildpacks/node-start", | ||
"name": "paketo-buildpacks/node-start@1.1.5", | ||
"version": "1.1.5" | ||
}, | ||
{ | ||
"key": "paketo-buildpacks/npm-start", | ||
"name": "paketo-buildpacks/npm-start@1.0.17", | ||
"version": "1.0.17" | ||
} | ||
] | ||
}, | ||
"process_types": { "web": "sh /home/vcap/workspace/start.sh" }, | ||
"execution_metadata": "", | ||
"lifecycle_type": "cnb" | ||
} | ||
``` | ||
|
||
## Launcher | ||
|
||
Reads `config/metadata.toml` from `CNB_LAYERS_DIR` (default `/home/vcap/layers`) and launches the application using the Cloud Native Buildpacks [launcher](https://github.com/buildpacks/lifecycle). |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,259 @@ | ||
package cli | ||
|
||
import ( | ||
"archive/tar" | ||
"compress/gzip" | ||
"context" | ||
"encoding/json" | ||
"os" | ||
"path/filepath" | ||
|
||
"code.cloudfoundry.org/cnbapplifecycle/pkg/archive" | ||
"code.cloudfoundry.org/cnbapplifecycle/pkg/errors" | ||
"code.cloudfoundry.org/cnbapplifecycle/pkg/log" | ||
"code.cloudfoundry.org/cnbapplifecycle/pkg/staging" | ||
"github.com/spf13/cobra" | ||
|
||
"github.com/buildpacks/pack/pkg/blob" | ||
"github.com/buildpacks/pack/pkg/image" | ||
|
||
"github.com/buildpacks/lifecycle/api" | ||
"github.com/buildpacks/lifecycle/buildpack" | ||
"github.com/buildpacks/lifecycle/cache" | ||
"github.com/buildpacks/lifecycle/cmd" | ||
"github.com/buildpacks/lifecycle/launch" | ||
"github.com/buildpacks/lifecycle/layers" | ||
"github.com/buildpacks/lifecycle/phase" | ||
"github.com/buildpacks/lifecycle/platform" | ||
"github.com/buildpacks/lifecycle/platform/files" | ||
) | ||
|
||
const ( | ||
PlatformAPI = "0.13" | ||
DefaultLayersPath = "/home/vcap/layers" | ||
DefaultWorkspacePath = "/home/vcap/workspace" | ||
) | ||
|
||
var ( | ||
layersDir string | ||
workspaceDir string | ||
cacheDir string | ||
cacheOutputFile string | ||
result string | ||
dropletFile string | ||
buildpacks []string | ||
envVarNames []string | ||
) | ||
|
||
var ( | ||
platformDir = filepath.Join(os.TempDir(), "platform") | ||
buildpacksDir = filepath.Join(os.TempDir(), "buildpacks") | ||
extensionsDir = filepath.Join(os.TempDir(), "extensions") | ||
downloadCacheDir = filepath.Join(os.TempDir(), "download-cache") | ||
) | ||
|
||
func Execute() error { | ||
return builderCmd.Execute() | ||
} | ||
|
||
func init() { | ||
builderCmd.Flags().StringSliceVarP(&buildpacks, "buildpack", "b", nil, "buildpack(s) to use") | ||
builderCmd.Flags().StringVarP(&dropletFile, "droplet", "d", "/tmp/droplet", "output droplet file") | ||
builderCmd.Flags().StringVarP(&result, "result", "r", "/tmp/result.json", "result file") | ||
builderCmd.Flags().StringVarP(&workspaceDir, "workspace-dir", "w", DefaultWorkspacePath, "app workspace dir") | ||
builderCmd.Flags().StringVarP(&layersDir, "layers", "l", DefaultLayersPath, "layers dir") | ||
builderCmd.Flags().StringSliceVarP(&envVarNames, "pass-env-var", "", nil, "environment variable(s) to pass to buildpacks") | ||
builderCmd.Flags().StringVarP(&cacheDir, "cache-dir", "c", "/tmp/cache", "cache dir") | ||
builderCmd.Flags().StringVarP(&cacheOutputFile, "cache-output", "", "/tmp/cache-output.tgz", "cache output") | ||
_ = builderCmd.MarkFlagRequired("buildpack") | ||
} | ||
|
||
var builderCmd = &cobra.Command{ | ||
Use: "builder", | ||
SilenceUsage: true, | ||
RunE: func(cobraCmd *cobra.Command, cmdArgs []string) error { | ||
platformAPI := api.MustParse(PlatformAPI) | ||
inputs := platform.NewLifecycleInputs(platformAPI) | ||
|
||
cmd.DisableColor(inputs.NoColor) | ||
logger := log.NewLogger() | ||
if err := logger.SetLevel(inputs.LogLevel); err != nil { | ||
logger.Errorf("failed to set log level to %q, error: %s\n", inputs.LogLevel, err.Error()) | ||
return errors.ErrGenericBuild | ||
} | ||
|
||
for _, dir := range []string{layersDir, platformDir, buildpacksDir, extensionsDir, downloadCacheDir, cacheDir} { | ||
if err := os.MkdirAll(dir, 0o755); err != nil { | ||
logger.Errorf("failed to create %q, error: %s\n", dir, err.Error()) | ||
return errors.ErrGenericBuild | ||
} | ||
} | ||
|
||
if err := staging.CreateEnvFiles(platformDir, envVarNames); err != nil { | ||
logger.Errorf("failed to write env var files, error: %s\n", err.Error()) | ||
return errors.ErrGenericBuild | ||
} | ||
|
||
orderFile, err := os.Create(filepath.Join(buildpacksDir, "order.toml")) | ||
if err != nil { | ||
logger.Errorf("failed to create 'order.toml', error: %s\n", err.Error()) | ||
return errors.ErrGenericBuild | ||
} | ||
|
||
err = staging.DownloadBuildpacks(buildpacks, buildpacksDir, image.NewFetcher(logger, nil), blob.NewDownloader(logger, downloadCacheDir), orderFile, logger) | ||
if err != nil { | ||
logger.Errorf("failed to download buildpacks, error: %s\n", err.Error()) | ||
return errors.ErrDownloadingBuildpack | ||
} | ||
|
||
dirStore := platform.NewDirStore(buildpacksDir, extensionsDir) | ||
detectorFactory := phase.NewHermeticFactory( | ||
platformAPI, | ||
&cmd.BuildpackAPIVerifier{}, | ||
files.Handler, | ||
dirStore, | ||
) | ||
|
||
detector, err := detectorFactory.NewDetector(platform.LifecycleInputs{ | ||
PlatformAPI: platformAPI, | ||
AppDir: workspaceDir, | ||
BuildpacksDir: buildpacksDir, | ||
LayersDir: layersDir, | ||
OrderPath: orderFile.Name(), | ||
PlatformDir: platformDir, | ||
CacheDir: cacheDir, | ||
UseDaemon: false, | ||
}, logger) | ||
if err != nil { | ||
logger.Errorf("failed creating detector, error: %s\n", err.Error()) | ||
return errors.ErrGenericBuild | ||
} | ||
|
||
logger.Phase("DETECTING") | ||
bGroup, plan, err := detector.Detect() | ||
if err != nil { | ||
logger.Errorf("failed 'detect' phase, error: %s\n", err.Error()) | ||
return errors.ErrDetecting | ||
} | ||
|
||
logger.Phase("RESTORING") | ||
cache, err := cache.NewVolumeCache(cacheDir) | ||
if err != nil { | ||
logger.Errorf("failed to initialise cache, error: %s\n", err.Error()) | ||
return errors.ErrRestoring | ||
} | ||
|
||
restorer := phase.Restorer{ | ||
LayersDir: layersDir, | ||
Logger: logger, | ||
Buildpacks: bGroup.Group, | ||
PlatformAPI: platformAPI, | ||
} | ||
if err := restorer.Restore(cache); err != nil { | ||
logger.Errorf("failed to restore cached layers, error: %s\n", err.Error()) | ||
return errors.ErrRestoring | ||
} | ||
|
||
bldr := phase.Builder{ | ||
AppDir: workspaceDir, | ||
LayersDir: layersDir, | ||
PlatformDir: platformDir, | ||
BuildExecutor: &buildpack.DefaultBuildExecutor{}, | ||
DirStore: dirStore, | ||
Group: bGroup, | ||
Logger: logger, | ||
Out: os.Stdout, | ||
Err: os.Stderr, | ||
Plan: plan, | ||
PlatformAPI: platformAPI, | ||
AnalyzeMD: files.Analyzed{}, | ||
} | ||
|
||
logger.Phase("BUILDING") | ||
buildMeta, err := bldr.Build() | ||
if err != nil { | ||
logger.Errorf("failed 'build' phase, error: %s\n", err.Error()) | ||
return errors.ErrBuilding | ||
} | ||
|
||
if err := files.Handler.WriteBuildMetadata(launch.GetMetadataFilePath(layersDir), buildMeta); err != nil { | ||
logger.Errorf("failed writing build metadata, error: %s\n", err.Error()) | ||
return errors.ErrGenericBuild | ||
} | ||
|
||
artifactsDir, err := os.MkdirTemp("", "lifecycle.exporter.layer") | ||
if err != nil { | ||
logger.Errorf("create temp directory for artifacts, error: %s\n", err.Error()) | ||
return errors.ErrGenericBuild | ||
} | ||
|
||
exporter := phase.Exporter{ | ||
Buildpacks: bGroup.Group, | ||
Logger: logger, | ||
PlatformAPI: platformAPI, | ||
LayerFactory: &layers.Factory{ | ||
ArtifactsDir: artifactsDir, | ||
UID: inputs.UID, | ||
GID: inputs.GID, | ||
Logger: logger, | ||
Ctx: context.Background(), | ||
}, | ||
} | ||
|
||
logger.Phase("EXPORTING") | ||
if err := exporter.Cache(layersDir, cache); err != nil { | ||
logger.Errorf("failed to save cached layers, error: %s\n", err.Error()) | ||
return errors.ErrExporting | ||
} | ||
|
||
cacheOutFile, err := os.OpenFile(cacheOutputFile, os.O_CREATE|os.O_WRONLY|os.O_TRUNC, 0o644) | ||
if err != nil { | ||
logger.Errorf("Failed to open %q, error: %s\n", cacheOutputFile, err.Error()) | ||
return errors.ErrGenericBuild | ||
} | ||
defer cacheOutFile.Close() | ||
|
||
cgw := gzip.NewWriter(cacheOutFile) | ||
defer cgw.Close() | ||
|
||
if err := archive.FromDirectory(cacheDir, tar.NewWriter(cgw)); err != nil { | ||
logger.Errorf("failed to save archive cache folder, error: %s\n", err.Error()) | ||
return errors.ErrExporting | ||
} | ||
|
||
resultData := staging.StagingResultFromMetadata(buildMeta) | ||
resultBytes, err := json.Marshal(resultData) | ||
if err != nil { | ||
logger.Errorf("failed to marshal '/tmp/result.json', error: %s\n", err.Error()) | ||
return errors.ErrGenericBuild | ||
} | ||
|
||
if err := os.WriteFile(result, resultBytes, 0o644); err != nil { | ||
logger.Errorf("failed to write '/tmp/result.json', error: %s\n", err.Error()) | ||
return errors.ErrGenericBuild | ||
} | ||
logger.Infof("result file saved to %q", result) | ||
|
||
dropletOutFile, err := os.OpenFile(dropletFile, os.O_CREATE|os.O_WRONLY|os.O_TRUNC, 0o644) | ||
if err != nil { | ||
logger.Errorf("failed to open %q, error: %s\n", dropletFile, err.Error()) | ||
return errors.ErrGenericBuild | ||
} | ||
defer dropletOutFile.Close() | ||
|
||
dgw := gzip.NewWriter(dropletOutFile) | ||
defer dgw.Close() | ||
|
||
if err := staging.RemoveBuildOnlyLayers(layersDir, bGroup.Group, logger); err != nil { | ||
logger.Errorf("failed to remove build-only layers, error: %s\n", err.Error()) | ||
return errors.ErrExporting | ||
} | ||
if err := archive.FromDirectory(filepath.Dir(workspaceDir), tar.NewWriter(dgw)); err != nil { | ||
logger.Errorf("failed 'export' phase, error: %s\n", err.Error()) | ||
return errors.ErrExporting | ||
} | ||
logger.Infof("droplet archive saved to %q", dropletFile) | ||
|
||
return nil | ||
}, | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,16 @@ | ||
package main | ||
|
||
import ( | ||
"os" | ||
|
||
"code.cloudfoundry.org/cnbapplifecycle/cmd/builder/cli" | ||
"code.cloudfoundry.org/cnbapplifecycle/pkg/errors" | ||
) | ||
|
||
func main() { | ||
err := cli.Execute() | ||
|
||
if err != nil { | ||
os.Exit(errors.ExitCodeFromError(err)) | ||
} | ||
} |
Oops, something went wrong.