Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Pack detect #2132

Open
wants to merge 4 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,9 @@ benchmarks.test
# Jetbrains Goland
.idea/

# Visual Studio Code
.vscode/

# Build outputs
artifacts/
.DS_Store
Expand Down
1 change: 1 addition & 0 deletions cmd/cmd.go
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,7 @@ func NewPackCommand(logger ConfigurableLogger) (*cobra.Command, error) {

commands.AddHelpFlag(rootCmd, "pack")

rootCmd.AddCommand(commands.ExecuteCommand(logger, cfg, packClient))
rootCmd.AddCommand(commands.Build(logger, cfg, packClient))
rootCmd.AddCommand(commands.NewBuilderCommand(logger, cfg, packClient))
rootCmd.AddCommand(commands.NewBuildpackCommand(logger, cfg, packClient, buildpackage.NewConfigReader()))
Expand Down
30 changes: 28 additions & 2 deletions internal/build/lifecycle_execution.go
Original file line number Diff line number Diff line change
Expand Up @@ -452,6 +452,10 @@ func (l *LifecycleExecution) Detect(ctx context.Context, phaseFactory PhaseFacto
CopyOutToMaybe(filepath.Join(l.mountPaths.layersDir(), "analyzed.toml"), l.tmpDir))),
If(l.hasExtensions(), WithPostContainerRunOperations(
CopyOutToMaybe(filepath.Join(l.mountPaths.layersDir(), "generated"), l.tmpDir))),
If(l.opts.GroupDestinationDir != "", WithPostContainerRunOperations(
EnsureVolumeAccess(l.opts.Builder.UID(), l.opts.Builder.GID(), l.os, l.layersVolume, l.appVolume),
CopyOutTo(l.mountPaths.groupPath(), l.opts.GroupDestinationDir))),

envOp,
)

Expand Down Expand Up @@ -567,7 +571,7 @@ func (l *LifecycleExecution) Analyze(ctx context.Context, buildCache, launchCach
platformAPILessThan07 := l.platformAPI.LessThan("0.7")

cacheBindOp := NullOp()
if l.opts.ClearCache {
if l.opts.ClearCache || buildCache == nil {
if platformAPILessThan07 || l.platformAPI.AtLeast("0.9") {
args = prependArg("-skip-layers", args)
}
Expand All @@ -584,7 +588,7 @@ func (l *LifecycleExecution) Analyze(ctx context.Context, buildCache, launchCach
}

launchCacheBindOp := NullOp()
if l.platformAPI.AtLeast("0.9") {
if l.platformAPI.AtLeast("0.9") && launchCache != nil {
if !l.opts.Publish {
args = append([]string{"-launch-cache", l.mountPaths.launchCacheDir()}, args...)
launchCacheBindOp = WithBinds(fmt.Sprintf("%s:%s", launchCache.Name(), l.mountPaths.launchCacheDir()))
Expand Down Expand Up @@ -1000,3 +1004,25 @@ func addTags(flags, additionalTags []string) []string {
}
return flags
}

func (l *LifecycleExecution) RunDetect(ctx context.Context, phaseFactoryCreator PhaseFactoryCreator) error {
phaseFactory := phaseFactoryCreator(l)

var dummyCache Cache

if l.platformAPI.LessThan("0.7") {
return errors.New("Detect needs at least platform API 0.7")
}

l.logger.Info(style.Step("ANALYZING"))
if err := l.Analyze(ctx, dummyCache, dummyCache, phaseFactory); err != nil {
return err
}

l.logger.Info(style.Step("DETECTING"))
if err := l.Detect(ctx, phaseFactory); err != nil {
return err
}

return nil
}
18 changes: 18 additions & 0 deletions internal/build/lifecycle_executor.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import (
"github.com/buildpacks/pack/internal/container"
"github.com/buildpacks/pack/pkg/cache"
"github.com/buildpacks/pack/pkg/dist"
"github.com/buildpacks/pack/pkg/image"
"github.com/buildpacks/pack/pkg/logging"
)

Expand Down Expand Up @@ -101,8 +102,10 @@ type LifecycleOptions struct {
PreviousImage string
ReportDestinationDir string
SBOMDestinationDir string
GroupDestinationDir string
CreationTime *time.Time
Keychain authn.Keychain
FetchOptions image.FetchOptions
rashadism marked this conversation as resolved.
Show resolved Hide resolved
}

func NewLifecycleExecutor(logger logging.Logger, docker DockerClient) *LifecycleExecutor {
Expand Down Expand Up @@ -130,3 +133,18 @@ func (l *LifecycleExecutor) Execute(ctx context.Context, opts LifecycleOptions)
lifecycleExec.Run(ctx, NewDefaultPhaseFactory)
})
}

func (l *LifecycleExecutor) Detect(ctx context.Context, opts LifecycleOptions) error {
tmpDir, err := os.MkdirTemp("", "pack.tmp")
if err != nil {
return err
}

lifecycleExec, err := NewLifecycleExecution(l.logger, l.docker, tmpDir, opts)
if err != nil {
return err
}

defer lifecycleExec.Cleanup()
return lifecycleExec.RunDetect(ctx, NewDefaultPhaseFactory)
}
4 changes: 4 additions & 0 deletions internal/build/mount_paths.go
Original file line number Diff line number Diff line change
Expand Up @@ -73,3 +73,7 @@ func (m mountPaths) launchCacheDir() string {
func (m mountPaths) sbomDir() string {
return m.join(m.volume, "layers", "sbom")
}

func (m mountPaths) groupPath() string {
return m.join(m.layersDir(), "group.toml")
}
48 changes: 27 additions & 21 deletions internal/commands/build.go
Original file line number Diff line number Diff line change
Expand Up @@ -51,11 +51,13 @@ type BuildFlags struct {
GID int
UID int
PreviousImage string
GroupDestinationDir string
SBOMDestinationDir string
ReportDestinationDir string
DateTime string
PreBuildpacks []string
PostBuildpacks []string
DetectOnly bool
}

// Build an image from source code
Expand Down Expand Up @@ -236,18 +238,7 @@ func buildCommandFlags(cmd *cobra.Command, buildFlags *BuildFlags, cfg config.Co
cmd.Flags().StringSliceVarP(&buildFlags.Buildpacks, "buildpack", "b", nil, "Buildpack to use. One of:\n a buildpack by id and version in the form of '<buildpack>@<version>',\n path to a buildpack directory (not supported on Windows),\n path/URL to a buildpack .tar or .tgz file, or\n a packaged buildpack image name in the form of '<hostname>/<repo>[:<tag>]'"+stringSliceHelp("buildpack"))
cmd.Flags().StringSliceVarP(&buildFlags.Extensions, "extension", "", nil, "Extension to use. One of:\n an extension by id and version in the form of '<extension>@<version>',\n path to an extension directory (not supported on Windows),\n path/URL to an extension .tar or .tgz file, or\n a packaged extension image name in the form of '<hostname>/<repo>[:<tag>]'"+stringSliceHelp("extension"))
cmd.Flags().StringVarP(&buildFlags.Builder, "builder", "B", cfg.DefaultBuilder, "Builder image")
cmd.Flags().Var(&buildFlags.Cache, "cache",
`Cache options used to define cache techniques for build process.
- Cache as bind: 'type=<build/launch>;format=bind;source=<path to directory>'
- Cache as image (requires --publish): 'type=<build/launch>;format=image;name=<registry image name>'
- Cache as volume: 'type=<build/launch>;format=volume;[name=<volume name>]'
- If no name is provided, a random name will be generated.
`)
cmd.Flags().StringVar(&buildFlags.CacheImage, "cache-image", "", `Cache build layers in remote registry. Requires --publish`)
cmd.Flags().BoolVar(&buildFlags.ClearCache, "clear-cache", false, "Clear image's associated cache before building")
cmd.Flags().StringVar(&buildFlags.DateTime, "creation-time", "", "Desired create time in the output image config. Accepted values are Unix timestamps (e.g., '1641013200'), or 'now'. Platform API version must be at least 0.9 to use this feature.")
cmd.Flags().StringVarP(&buildFlags.DescriptorPath, "descriptor", "d", "", "Path to the project descriptor file")
cmd.Flags().StringVarP(&buildFlags.DefaultProcessType, "default-process", "D", "", `Set the default process type. (default "web")`)
cmd.Flags().StringArrayVarP(&buildFlags.Env, "env", "e", []string{}, "Build-time environment variable, in the form 'VAR=VALUE' or 'VAR'.\nWhen using latter value-less form, value will be taken from current\n environment at the time this command is executed.\nThis flag may be specified multiple times and will override\n individual values defined by --env-file."+stringArrayHelp("env")+"\nNOTE: These are NOT available at image runtime.")
cmd.Flags().StringArrayVar(&buildFlags.EnvFiles, "env-file", []string{}, "Build-time environment variables file\nOne variable per line, of the form 'VAR=VALUE' or 'VAR'\nWhen using latter value-less form, value will be taken from current\n environment at the time this command is executed\nNOTE: These are NOT available at image runtime.\"")
cmd.Flags().StringVar(&buildFlags.Network, "network", "", "Connect detect and build containers to network")
Expand All @@ -265,20 +256,35 @@ This option may set DOCKER_HOST environment variable for the build container if
cmd.Flags().StringVar(&buildFlags.Policy, "pull-policy", "", `Pull policy to use. Accepted values are always, never, and if-not-present. (default "always")`)
cmd.Flags().StringVarP(&buildFlags.Registry, "buildpack-registry", "r", cfg.DefaultRegistryName, "Buildpack Registry by name")
cmd.Flags().StringVar(&buildFlags.RunImage, "run-image", "", "Run image (defaults to default stack's run image)")
cmd.Flags().StringSliceVarP(&buildFlags.AdditionalTags, "tag", "t", nil, "Additional tags to push the output image to.\nTags should be in the format 'image:tag' or 'repository/image:tag'."+stringSliceHelp("tag"))
cmd.Flags().BoolVar(&buildFlags.TrustBuilder, "trust-builder", false, "Trust the provided builder.\nAll lifecycle phases will be run in a single container.\nFor more on trusted builders, and when to trust or untrust a builder, check out our docs here: https://buildpacks.io/docs/tools/pack/concepts/trusted_builders")
cmd.Flags().StringArrayVar(&buildFlags.Volumes, "volume", nil, "Mount host volume into the build container, in the form '<host path>:<target path>[:<options>]'.\n- 'host path': Name of the volume or absolute directory path to mount.\n- 'target path': The path where the file or directory is available in the container.\n- 'options' (default \"ro\"): An optional comma separated list of mount options.\n - \"ro\", volume contents are read-only.\n - \"rw\", volume contents are readable and writeable.\n - \"volume-opt=<key>=<value>\", can be specified more than once, takes a key-value pair consisting of the option name and its value."+stringArrayHelp("volume"))
cmd.Flags().StringVar(&buildFlags.Workspace, "workspace", "", "Location at which to mount the app dir in the build image")
cmd.Flags().IntVar(&buildFlags.GID, "gid", 0, `Override GID of user's group in the stack's build and run images. The provided value must be a positive number`)
cmd.Flags().IntVar(&buildFlags.UID, "uid", 0, `Override UID of user in the stack's build and run images. The provided value must be a positive number`)
cmd.Flags().StringVar(&buildFlags.PreviousImage, "previous-image", "", "Set previous image to a particular tag reference, digest reference, or (when performing a daemon build) image ID")
cmd.Flags().StringVar(&buildFlags.SBOMDestinationDir, "sbom-output-dir", "", "Path to export SBoM contents.\nOmitting the flag will yield no SBoM content.")
cmd.Flags().StringVar(&buildFlags.ReportDestinationDir, "report-output-dir", "", "Path to export build report.toml.\nOmitting the flag yield no report file.")
cmd.Flags().BoolVar(&buildFlags.Interactive, "interactive", false, "Launch a terminal UI to depict the build process")
cmd.Flags().BoolVar(&buildFlags.Sparse, "sparse", false, "Use this flag to avoid saving on disk the run-image layers when the application image is exported to OCI layout format")
if !cfg.Experimental {
cmd.Flags().MarkHidden("interactive")
cmd.Flags().MarkHidden("sparse")
cmd.Flags().StringVar(&buildFlags.GroupDestinationDir, "detect-output-dir", "", "Path to export group.toml.")

if !buildFlags.DetectOnly {
cmd.Flags().Var(&buildFlags.Cache, "cache",
`Cache options used to define cache techniques for build process.
- Cache as bind: 'type=<build/launch>;format=bind;source=<path to directory>'
- Cache as image (requires --publish): 'type=<build/launch>;format=image;name=<registry image name>'
- Cache as volume: 'type=<build/launch>;format=volume;[name=<volume name>]'
- If no name is provided, a random name will be generated.
`)
cmd.Flags().StringVar(&buildFlags.CacheImage, "cache-image", "", `Cache build layers in remote registry. Requires --publish`)
cmd.Flags().BoolVar(&buildFlags.ClearCache, "clear-cache", false, "Clear image's associated cache before building")
cmd.Flags().StringVar(&buildFlags.DateTime, "creation-time", "", "Desired create time in the output image config. Accepted values are Unix timestamps (e.g., '1641013200'), or 'now'. Platform API version must be at least 0.9 to use this feature.")
cmd.Flags().StringVarP(&buildFlags.DefaultProcessType, "default-process", "D", "", `Set the default process type. (default "web")`)
cmd.Flags().StringSliceVarP(&buildFlags.AdditionalTags, "tag", "t", nil, "Additional tags to push the output image to.\nTags should be in the format 'image:tag' or 'repository/image:tag'."+stringSliceHelp("tag"))
cmd.Flags().BoolVar(&buildFlags.TrustBuilder, "trust-builder", false, "Trust the provided builder.\nAll lifecycle phases will be run in a single container.\nFor more on trusted builders, and when to trust or untrust a builder, check out our docs here: https://buildpacks.io/docs/tools/pack/concepts/trusted_builders")
cmd.Flags().StringVar(&buildFlags.PreviousImage, "previous-image", "", "Set previous image to a particular tag reference, digest reference, or (when performing a daemon build) image ID")
cmd.Flags().StringVar(&buildFlags.SBOMDestinationDir, "sbom-output-dir", "", "Path to export SBoM contents.\nOmitting the flag will yield no SBoM content.")
cmd.Flags().StringVar(&buildFlags.ReportDestinationDir, "report-output-dir", "", "Path to export build report.toml.\nOmitting the flag yield no report file.")
cmd.Flags().BoolVar(&buildFlags.Interactive, "interactive", false, "Launch a terminal UI to depict the build process")
cmd.Flags().BoolVar(&buildFlags.Sparse, "sparse", false, "Use this flag to avoid saving on disk the run-image layers when the application image is exported to OCI layout format")
if !cfg.Experimental {
cmd.Flags().MarkHidden("interactive")
cmd.Flags().MarkHidden("sparse")
}
}
}

Expand Down
1 change: 1 addition & 0 deletions internal/commands/commands.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ type PackClient interface {
NewBuildpack(context.Context, client.NewBuildpackOptions) error
PackageBuildpack(ctx context.Context, opts client.PackageBuildpackOptions) error
PackageExtension(ctx context.Context, opts client.PackageBuildpackOptions) error
Detect(context.Context, client.BuildOptions) error
Build(context.Context, client.BuildOptions) error
RegisterBuildpack(context.Context, client.RegisterBuildpackOptions) error
YankBuildpack(client.YankBuildpackOptions) error
Expand Down
20 changes: 20 additions & 0 deletions internal/commands/execute.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
package commands

import (
"github.com/spf13/cobra"

"github.com/buildpacks/pack/internal/config"
"github.com/buildpacks/pack/pkg/logging"
)

func ExecuteCommand(logger logging.Logger, cfg config.Config, client PackClient) *cobra.Command {
cmd := &cobra.Command{
Use: "execute",
Short: "Executes a specific phase in the buildpacks lifecycle",
RunE: nil,
}

cmd.AddCommand(ExecuteDetect(logger, cfg, client))
AddHelpFlag(cmd, "execute")
return cmd
}
127 changes: 127 additions & 0 deletions internal/commands/execute_detect.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,127 @@
package commands

import (
"path/filepath"

"github.com/google/go-containerregistry/pkg/name"
"github.com/pkg/errors"
"github.com/spf13/cobra"

"github.com/buildpacks/pack/internal/config"
"github.com/buildpacks/pack/internal/style"
"github.com/buildpacks/pack/pkg/client"
"github.com/buildpacks/pack/pkg/image"
"github.com/buildpacks/pack/pkg/logging"
)

// Run up to the detect phase of the CNB lifecycle against a source code directory
func ExecuteDetect(logger logging.Logger, cfg config.Config, packClient PackClient) *cobra.Command {
var flags BuildFlags
flags.DetectOnly = true

cmd := &cobra.Command{
Use: "detect",
Args: cobra.ExactArgs(1),
Short: "Execute detect runs the analyze and detect phases of the Cloud Native Buildpacks lifecycle to determine a group of applicable buildpacks and a build plan.",
Example: "pack execute detect --path apps/test-app --builder cnbs/sample-builder:bionic",
Long: "Execute detect uses Cloud Native Buildpacks to run the detect phase of buildpack groups against the source code.\n",
RunE: logError(logger, func(cmd *cobra.Command, args []string) error {
inputImageName := client.ParseInputImageReference(args[0])
if err := validateBuildFlags(&flags, cfg, inputImageName, logger); err != nil {
return err
}

descriptor, actualDescriptorPath, err := parseProjectToml(flags.AppPath, flags.DescriptorPath, logger)
if err != nil {
return err
}

if actualDescriptorPath != "" {
logger.Debugf("Using project descriptor located at %s", style.Symbol(actualDescriptorPath))
}

builder := flags.Builder

if !cmd.Flags().Changed("builder") && descriptor.Build.Builder != "" {
builder = descriptor.Build.Builder
}

if builder == "" {
suggestSettingBuilder(logger, packClient)
return client.NewSoftError()
}

buildpacks := flags.Buildpacks
extensions := flags.Extensions

groupDestinationDir := flags.GroupDestinationDir

env, err := parseEnv(flags.EnvFiles, flags.Env)
if err != nil {
return err
}

stringPolicy := flags.Policy
if stringPolicy == "" {
stringPolicy = cfg.PullPolicy
}
pullPolicy, err := image.ParsePullPolicy(stringPolicy)
if err != nil {
return errors.Wrapf(err, "parsing pull policy %s", flags.Policy)
}
var lifecycleImage string
if flags.LifecycleImage != "" {
ref, err := name.ParseReference(flags.LifecycleImage)
if err != nil {
return errors.Wrapf(err, "parsing lifecycle image %s", flags.LifecycleImage)
}
lifecycleImage = ref.Name()
}

var gid = -1
if cmd.Flags().Changed("gid") {
gid = flags.GID
}

var uid = -1
if cmd.Flags().Changed("uid") {
uid = flags.UID
}

if err := packClient.Detect(cmd.Context(), client.BuildOptions{
AppPath: flags.AppPath,
Builder: builder,
Registry: flags.Registry,
Env: env,
Image: inputImageName.Name(),
RunImage: flags.RunImage,
Publish: flags.Publish,
DockerHost: flags.DockerHost,
PullPolicy: pullPolicy,
ProjectDescriptorBaseDir: filepath.Dir(actualDescriptorPath),
ProjectDescriptor: descriptor,

ContainerConfig: client.ContainerConfig{
Network: flags.Network,
Volumes: flags.Volumes,
},
LifecycleImage: lifecycleImage,
PreBuildpacks: flags.PreBuildpacks,
PostBuildpacks: flags.PostBuildpacks,
Buildpacks: buildpacks,
Extensions: extensions,
Workspace: flags.Workspace,
GroupID: gid,
UserID: uid,
DetectOnly: true,
GroupDestinationDir: groupDestinationDir,
}); err != nil {
return errors.Wrap(err, "failed to detect")
}
return nil
}),
}
buildCommandFlags(cmd, &flags, cfg)
AddHelpFlag(cmd, "detect")
return cmd
}
14 changes: 14 additions & 0 deletions internal/commands/testmocks/mock_pack_client.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 5 additions & 0 deletions internal/fakes/fake_lifecycle.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,3 +14,8 @@ func (f *FakeLifecycle) Execute(ctx context.Context, opts build.LifecycleOptions
f.Opts = opts
return nil
}

func (f *FakeLifecycle) Detect(ctx context.Context, opts build.LifecycleOptions) error {
f.Opts = opts
return nil
}
Loading
Loading