From 97ca40e7588519a17e082560e63d09cdfc8c05b1 Mon Sep 17 00:00:00 2001 From: Rashad Sirajudeen Date: Thu, 15 Feb 2024 14:31:39 +0530 Subject: [PATCH 1/3] added RunDetect --- internal/build/container_ops_test.go | 2 +- internal/build/lifecycle_execution.go | 12 ++++++++++++ internal/build/lifecycle_executor.go | 4 +++- 3 files changed, 16 insertions(+), 2 deletions(-) diff --git a/internal/build/container_ops_test.go b/internal/build/container_ops_test.go index fb1b9bd423..463c97367f 100644 --- a/internal/build/container_ops_test.go +++ b/internal/build/container_ops_test.go @@ -467,7 +467,7 @@ drwxrwxrwx 2 123 456 (.*) some-vol h.AssertNil(t, err) defer cleanupContainer(ctx, ctr.ID) - writeOp := build.WriteRunToml(containerPath, []builder.RunImageMetadata{builder.RunImageMetadata{ + writeOp := build.WriteRunToml(containerPath, []builder.RunImageMetadata{{ Image: "image-1", Mirrors: []string{ "mirror-1", diff --git a/internal/build/lifecycle_execution.go b/internal/build/lifecycle_execution.go index 0f5c9a49a4..821eb61533 100644 --- a/internal/build/lifecycle_execution.go +++ b/internal/build/lifecycle_execution.go @@ -295,6 +295,18 @@ func (l *LifecycleExecution) Run(ctx context.Context, phaseFactoryCreator PhaseF return l.Create(ctx, buildCache, launchCache, phaseFactory) } +func (l *LifecycleExecution) RunDetect(ctx context.Context, phaseFactoryCreator PhaseFactoryCreator) error { + phaseFactory := phaseFactoryCreator(l) + + l.logger.Info(style.Step("DETECTING")) + if err := l.Detect(ctx, phaseFactory); err != nil { + return err + } + + return nil + // return l.Create(ctx, buildCache, launchCache, phaseFactory) +} + func (l *LifecycleExecution) Cleanup() error { var reterr error if err := l.docker.VolumeRemove(context.Background(), l.layersVolume, true); err != nil { diff --git a/internal/build/lifecycle_executor.go b/internal/build/lifecycle_executor.go index 996235a204..3714d7d083 100644 --- a/internal/build/lifecycle_executor.go +++ b/internal/build/lifecycle_executor.go @@ -122,7 +122,9 @@ func (l *LifecycleExecutor) Execute(ctx context.Context, opts LifecycleOptions) if !opts.Interactive { defer lifecycleExec.Cleanup() - return lifecycleExec.Run(ctx, NewDefaultPhaseFactory) + // return lifecycleExec.Run(ctx, NewDefaultPhaseFactory) + return lifecycleExec.RunDetect(ctx, NewDefaultPhaseFactory) + } return opts.Termui.Run(func() { From 87b770ff50d115813df65183b8b9274fd38bdce6 Mon Sep 17 00:00:00 2001 From: Rashad Sirajudeen Date: Thu, 15 Feb 2024 17:21:55 +0530 Subject: [PATCH 2/3] structured detec.go in internal/command, must refine --- internal/commands/detect.go | 273 ++++++++++++++++++++++++++++++++++++ 1 file changed, 273 insertions(+) create mode 100644 internal/commands/detect.go diff --git a/internal/commands/detect.go b/internal/commands/detect.go new file mode 100644 index 0000000000..aa4d3670af --- /dev/null +++ b/internal/commands/detect.go @@ -0,0 +1,273 @@ +package commands + +import ( + "path/filepath" + + "github.com/buildpacks/pack/internal/config" + "github.com/buildpacks/pack/internal/style" + "github.com/buildpacks/pack/pkg/cache" + "github.com/buildpacks/pack/pkg/client" + "github.com/buildpacks/pack/pkg/image" + "github.com/buildpacks/pack/pkg/logging" + "github.com/google/go-containerregistry/pkg/name" + "github.com/pkg/errors" + "github.com/spf13/cobra" +) + +type DetectFlags struct { + Publish bool + ClearCache bool + TrustBuilder bool + Interactive bool + Sparse bool + DockerHost string + CacheImage string + Cache cache.CacheOpts + AppPath string + Builder string + Registry string + RunImage string + Policy string + Network string + DescriptorPath string + DefaultProcessType string + LifecycleImage string + Env []string + EnvFiles []string + Buildpacks []string + Extensions []string + Volumes []string + AdditionalTags []string + Workspace string + GID int + UID int + MacAddress string + PreviousImage string + SBOMDestinationDir string + ReportDestinationDir string + DateTime string + PreBuildpacks []string + PostBuildpacks []string +} + +// Run Detect phase of lifecycle against a source code +func Detect(logger logging.Logger, cfg config.Config, packClient PackClient) *cobra.Command { + var flags DetectFlags + + cmd := &cobra.Command{ + Use: "detect", + Args: cobra.ExactArgs(0), + Short: "Run the detect phase of buildpacks against the source code", + Example: "pack detect --path apps/test-app --builder cnbs/sample-builder:bionic", + Long: "Pack Detect uses Cloud Native Buildpacks to run the detect phase of buildpack groups against the source code.\n\n" + + "You can use `--path` to specify a different source code directory, else it defaults to the current directory. Detect requires a `builder`, which can either " + + "be provided directly to build using `--builder`, or can be set using the `set-default-builder` command.", + RunE: logError(logger, func(cmd *cobra.Command, args []string) error { + inputImageName := client.ParseInputImageReference(args[0]) + if err := validateDetectFlags(&flags, cfg, inputImageName, logger); err != nil { + return err + } + + // ?? + inputPreviousImage := client.ParseInputImageReference(flags.PreviousImage) + + 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 + // We only override the builder to the one in the project descriptor + // if it was not explicitly set by the user + 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 + + env, err := parseEnv(flags.EnvFiles, flags.Env) + if err != nil { + return err + } + + // ?? + trustBuilder := isTrustedBuilder(cfg, builder) || flags.TrustBuilder + + 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() + } + + if err := packClient.Build(cmd.Context(), client.BuildOptions{ + AppPath: flags.AppPath, + Builder: builder, + Registry: flags.Registry, + AdditionalMirrors: getMirrors(cfg), + AdditionalTags: flags.AdditionalTags, + RunImage: flags.RunImage, + Env: env, + Image: inputImageName.Name(), + Publish: flags.Publish, + DockerHost: flags.DockerHost, + PullPolicy: pullPolicy, + ClearCache: flags.ClearCache, + TrustBuilder: func(string) bool { + return trustBuilder + }, + Buildpacks: buildpacks, + Extensions: extensions, + ContainerConfig: client.ContainerConfig{ + Network: flags.Network, + Volumes: flags.Volumes, + }, + DefaultProcessType: flags.DefaultProcessType, + ProjectDescriptorBaseDir: filepath.Dir(actualDescriptorPath), + ProjectDescriptor: descriptor, + Cache: flags.Cache, + CacheImage: flags.CacheImage, + Workspace: flags.Workspace, + LifecycleImage: lifecycleImage, + + MacAddress: flags.MacAddress, + PreviousImage: inputPreviousImage.Name(), + Interactive: flags.Interactive, + SBOMDestinationDir: flags.SBOMDestinationDir, + ReportDestinationDir: flags.ReportDestinationDir, + PreBuildpacks: flags.PreBuildpacks, + PostBuildpacks: flags.PostBuildpacks, + LayoutConfig: &client.LayoutConfig{ + Sparse: flags.Sparse, + InputImage: inputImageName, + PreviousInputImage: inputPreviousImage, + LayoutRepoDir: cfg.LayoutRepositoryDir, + }, + }); err != nil { + return errors.Wrap(err, "failed to detect") + } + return nil + }), + } + detectCommandFlags(cmd, &flags, cfg) + AddHelpFlag(cmd, "build") + return cmd +} + +// TODO: ,-, +func validateDetectFlags(flags *DetectFlags, cfg config.Config, inputImageRef client.InputImageReference, logger logging.Logger) error { + if flags.Registry != "" && !cfg.Experimental { + return client.NewExperimentError("Support for buildpack registries is currently experimental.") + } + + if flags.Cache.Launch.Format == cache.CacheImage { + logger.Warn("cache definition: 'launch' cache in format 'image' is not supported.") + } + + if flags.Cache.Build.Format == cache.CacheImage && flags.CacheImage != "" { + return errors.New("'cache' flag with 'image' format cannot be used with 'cache-image' flag.") + } + + if flags.Cache.Build.Format == cache.CacheImage && !flags.Publish { + return errors.New("image cache format requires the 'publish' flag") + } + + if flags.CacheImage != "" && !flags.Publish { + return errors.New("cache-image flag requires the publish flag") + } + + if flags.GID < 0 { + return errors.New("gid flag must be in the range of 0-2147483647") + } + + if flags.UID < 0 { + return errors.New("uid flag must be in the range of 0-2147483647") + } + + if flags.MacAddress != "" && !isValidMacAddress(flags.MacAddress) { + return errors.New("invalid MAC address provided") + } + + if flags.Interactive && !cfg.Experimental { + return client.NewExperimentError("Interactive mode is currently experimental.") + } + + if inputImageRef.Layout() && !cfg.Experimental { + return client.NewExperimentError("Exporting to OCI layout is currently experimental.") + } + + return nil +} + +// TODO ,-, +func detectCommandFlags(cmd *cobra.Command, detectFlags *DetectFlags, cfg config.Config) { + cmd.Flags().StringVarP(&detectFlags.AppPath, "path", "p", "", "Path to app dir or zip-formatted file (defaults to current working directory)") + cmd.Flags().StringSliceVarP(&detectFlags.Buildpacks, "buildpack", "b", nil, "Buildpack to use. One of:\n a buildpack by id and version in the form of '@',\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 '/[:]'"+stringSliceHelp("buildpack")) + cmd.Flags().StringSliceVarP(&detectFlags.Extensions, "extension", "", nil, "Extension to use. One of:\n an extension by id and version in the form of '@',\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 '/[:]'"+stringSliceHelp("extension")) + cmd.Flags().StringVarP(&detectFlags.Builder, "builder", "B", cfg.DefaultBuilder, "Builder image") + cmd.Flags().Var(&detectFlags.Cache, "cache", + `Cache options used to define cache techniques for build process. +- Cache as bind: 'type=;format=bind;source=' +- Cache as image (requires --publish): 'type=;format=image;name=' +- Cache as volume: 'type=;format=volume;[name=]' + - If no name is provided, a random name will be generated. +`) + cmd.Flags().StringVar(&detectFlags.CacheImage, "cache-image", "", `Cache build layers in remote registry. Requires --publish`) + cmd.Flags().BoolVar(&detectFlags.ClearCache, "clear-cache", false, "Clear image's associated cache before building") + cmd.Flags().StringVar(&detectFlags.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(&detectFlags.DescriptorPath, "descriptor", "d", "", "Path to the project descriptor file") + cmd.Flags().StringVarP(&detectFlags.DefaultProcessType, "default-process", "D", "", `Set the default process type. (default "web")`) + cmd.Flags().StringArrayVarP(&detectFlags.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(&detectFlags.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(&detectFlags.Network, "network", "", "Connect detect and build containers to network") + cmd.Flags().StringArrayVar(&detectFlags.PreBuildpacks, "pre-buildpack", []string{}, "Buildpacks to prepend to the groups in the builder's order") + cmd.Flags().StringArrayVar(&detectFlags.PostBuildpacks, "post-buildpack", []string{}, "Buildpacks to append to the groups in the builder's order") + cmd.Flags().BoolVar(&detectFlags.Publish, "publish", false, "Publish the application image directly to the container registry specified in , instead of the daemon. The run image must also reside in the registry.") + cmd.Flags().StringVar(&detectFlags.DockerHost, "docker-host", "", + `Address to docker daemon that will be exposed to the build container. +If not set (or set to empty string) the standard socket location will be used. +Special value 'inherit' may be used in which case DOCKER_HOST environment variable will be used. +This option may set DOCKER_HOST environment variable for the build container if needed. +`) + cmd.Flags().StringVar(&detectFlags.LifecycleImage, "lifecycle-image", cfg.LifecycleImage, `Custom lifecycle image to use for analysis, restore, and export when builder is untrusted.`) + cmd.Flags().StringVar(&detectFlags.Policy, "pull-policy", "", `Pull policy to use. Accepted values are always, never, and if-not-present. (default "always")`) + cmd.Flags().StringVarP(&detectFlags.Registry, "buildpack-registry", "r", cfg.DefaultRegistryName, "Buildpack Registry by name") + cmd.Flags().StringVar(&detectFlags.RunImage, "run-image", "", "Run image (defaults to default stack's run image)") + cmd.Flags().StringSliceVarP(&detectFlags.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(&detectFlags.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(&detectFlags.Volumes, "volume", nil, "Mount host volume into the build container, in the form ':[:]'.\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==\", can be specified more than once, takes a key-value pair consisting of the option name and its value."+stringArrayHelp("volume")) + cmd.Flags().StringVar(&detectFlags.Workspace, "workspace", "", "Location at which to mount the app dir in the build image") + cmd.Flags().IntVar(&detectFlags.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(&detectFlags.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(&detectFlags.MacAddress, "mac-address", "", "MAC address to set for the build container network configuration") + cmd.Flags().StringVar(&detectFlags.PreviousImage, "previous-image", "", "Set previous image to a particular tag reference, digest reference, or (when performing a daemon build) image ID") + cmd.Flags().StringVar(&detectFlags.SBOMDestinationDir, "sbom-output-dir", "", "Path to export SBoM contents.\nOmitting the flag will yield no SBoM content.") + cmd.Flags().StringVar(&detectFlags.ReportDestinationDir, "report-output-dir", "", "Path to export build report.toml.\nOmitting the flag yield no report file.") + cmd.Flags().BoolVar(&detectFlags.Interactive, "interactive", false, "Launch a terminal UI to depict the build process") + cmd.Flags().BoolVar(&detectFlags.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") + } +} From af2e9ba8d222affa49d2bcf2afe3fd34c4066342 Mon Sep 17 00:00:00 2001 From: Rashad Sirajudeen Date: Fri, 16 Feb 2024 15:25:10 +0530 Subject: [PATCH 3/3] MVP of the detect command Signed-off-by: Rashad Sirajudeen --- cmd/cmd.go | 1 + internal/build/lifecycle_executor.go | 19 +- internal/commands/commands.go | 1 + internal/commands/detect.go | 225 ++++-------------- .../commands/testmocks/mock_pack_client.go | 14 ++ internal/fakes/fake_lifecycle.go | 5 + pkg/client/build.go | 1 + pkg/client/detect.go | 172 +++++++++++++ pkg/testmocks/mock_access_checker.go | 48 ++++ tools/go.sum | 14 ++ 10 files changed, 312 insertions(+), 188 deletions(-) create mode 100644 pkg/client/detect.go create mode 100644 pkg/testmocks/mock_access_checker.go diff --git a/cmd/cmd.go b/cmd/cmd.go index 7ece55f1e1..96b7fe065e 100644 --- a/cmd/cmd.go +++ b/cmd/cmd.go @@ -78,6 +78,7 @@ func NewPackCommand(logger ConfigurableLogger) (*cobra.Command, error) { commands.AddHelpFlag(rootCmd, "pack") rootCmd.AddCommand(commands.Build(logger, cfg, packClient)) + rootCmd.AddCommand(commands.Detect(logger, cfg, packClient)) rootCmd.AddCommand(commands.NewBuilderCommand(logger, cfg, packClient)) rootCmd.AddCommand(commands.NewBuildpackCommand(logger, cfg, packClient, buildpackage.NewConfigReader())) rootCmd.AddCommand(commands.NewExtensionCommand(logger, cfg, packClient, buildpackage.NewConfigReader())) diff --git a/internal/build/lifecycle_executor.go b/internal/build/lifecycle_executor.go index 3714d7d083..4bd3a3cf1e 100644 --- a/internal/build/lifecycle_executor.go +++ b/internal/build/lifecycle_executor.go @@ -122,9 +122,7 @@ func (l *LifecycleExecutor) Execute(ctx context.Context, opts LifecycleOptions) if !opts.Interactive { defer lifecycleExec.Cleanup() - // return lifecycleExec.Run(ctx, NewDefaultPhaseFactory) - return lifecycleExec.RunDetect(ctx, NewDefaultPhaseFactory) - + return lifecycleExec.Run(ctx, NewDefaultPhaseFactory) } return opts.Termui.Run(func() { @@ -132,3 +130,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) +} diff --git a/internal/commands/commands.go b/internal/commands/commands.go index 11fa645ca5..5352206e85 100644 --- a/internal/commands/commands.go +++ b/internal/commands/commands.go @@ -26,6 +26,7 @@ type PackClient interface { PackageBuildpack(ctx context.Context, opts client.PackageBuildpackOptions) error PackageExtension(ctx context.Context, opts client.PackageBuildpackOptions) error Build(context.Context, client.BuildOptions) error + Detect(context.Context, client.BuildOptions) error RegisterBuildpack(context.Context, client.RegisterBuildpackOptions) error YankBuildpack(client.YankBuildpackOptions) error InspectBuildpack(client.InspectBuildpackOptions) (*client.BuildpackInfo, error) diff --git a/internal/commands/detect.go b/internal/commands/detect.go index aa4d3670af..90d5f7d362 100644 --- a/internal/commands/detect.go +++ b/internal/commands/detect.go @@ -1,53 +1,31 @@ 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/cache" "github.com/buildpacks/pack/pkg/client" "github.com/buildpacks/pack/pkg/image" "github.com/buildpacks/pack/pkg/logging" - "github.com/google/go-containerregistry/pkg/name" - "github.com/pkg/errors" - "github.com/spf13/cobra" ) type DetectFlags struct { - Publish bool - ClearCache bool - TrustBuilder bool - Interactive bool - Sparse bool - DockerHost string - CacheImage string - Cache cache.CacheOpts - AppPath string - Builder string - Registry string - RunImage string - Policy string - Network string - DescriptorPath string - DefaultProcessType string - LifecycleImage string - Env []string - EnvFiles []string - Buildpacks []string - Extensions []string - Volumes []string - AdditionalTags []string - Workspace string - GID int - UID int - MacAddress string - PreviousImage string - SBOMDestinationDir string - ReportDestinationDir string - DateTime string - PreBuildpacks []string - PostBuildpacks []string + AppPath string + Builder string + Registry string + DescriptorPath string + LifecycleImage string + Volumes []string + PreBuildpacks []string + PostBuildpacks []string + Policy string + Network string + Env []string + EnvFiles []string + Buildpacks []string + Extensions []string } // Run Detect phase of lifecycle against a source code @@ -57,36 +35,17 @@ func Detect(logger logging.Logger, cfg config.Config, packClient PackClient) *co cmd := &cobra.Command{ Use: "detect", Args: cobra.ExactArgs(0), - Short: "Run the detect phase of buildpacks against the source code", + Short: "Run the detect phase of buildpacks against your source code", Example: "pack detect --path apps/test-app --builder cnbs/sample-builder:bionic", - Long: "Pack Detect uses Cloud Native Buildpacks to run the detect phase of buildpack groups against the source code.\n\n" + + Long: "Pack Detect uses Cloud Native Buildpacks to run the detect phase of buildpack groups against the source code.\n" + "You can use `--path` to specify a different source code directory, else it defaults to the current directory. Detect requires a `builder`, which can either " + "be provided directly to build using `--builder`, or can be set using the `set-default-builder` command.", RunE: logError(logger, func(cmd *cobra.Command, args []string) error { - inputImageName := client.ParseInputImageReference(args[0]) - if err := validateDetectFlags(&flags, cfg, inputImageName, logger); err != nil { + if err := validateDetectFlags(&flags, cfg, logger); err != nil { return err } - // ?? - inputPreviousImage := client.ParseInputImageReference(flags.PreviousImage) - - 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 - // We only override the builder to the one in the project descriptor - // if it was not explicitly set by the user - if !cmd.Flags().Changed("builder") && descriptor.Build.Builder != "" { - builder = descriptor.Build.Builder - } if builder == "" { suggestSettingBuilder(logger, packClient) @@ -101,9 +60,6 @@ func Detect(logger logging.Logger, cfg config.Config, packClient PackClient) *co return err } - // ?? - trustBuilder := isTrustedBuilder(cfg, builder) || flags.TrustBuilder - stringPolicy := flags.Policy if stringPolicy == "" { stringPolicy = cfg.PullPolicy @@ -121,49 +77,22 @@ func Detect(logger logging.Logger, cfg config.Config, packClient PackClient) *co lifecycleImage = ref.Name() } - if err := packClient.Build(cmd.Context(), client.BuildOptions{ - AppPath: flags.AppPath, - Builder: builder, - Registry: flags.Registry, - AdditionalMirrors: getMirrors(cfg), - AdditionalTags: flags.AdditionalTags, - RunImage: flags.RunImage, - Env: env, - Image: inputImageName.Name(), - Publish: flags.Publish, - DockerHost: flags.DockerHost, - PullPolicy: pullPolicy, - ClearCache: flags.ClearCache, - TrustBuilder: func(string) bool { - return trustBuilder - }, - Buildpacks: buildpacks, - Extensions: extensions, + if err := packClient.Detect(cmd.Context(), client.BuildOptions{ + AppPath: flags.AppPath, + Builder: builder, + Env: env, + + PullPolicy: pullPolicy, ContainerConfig: client.ContainerConfig{ Network: flags.Network, Volumes: flags.Volumes, }, - DefaultProcessType: flags.DefaultProcessType, - ProjectDescriptorBaseDir: filepath.Dir(actualDescriptorPath), - ProjectDescriptor: descriptor, - Cache: flags.Cache, - CacheImage: flags.CacheImage, - Workspace: flags.Workspace, - LifecycleImage: lifecycleImage, + LifecycleImage: lifecycleImage, + PreBuildpacks: flags.PreBuildpacks, + PostBuildpacks: flags.PostBuildpacks, - MacAddress: flags.MacAddress, - PreviousImage: inputPreviousImage.Name(), - Interactive: flags.Interactive, - SBOMDestinationDir: flags.SBOMDestinationDir, - ReportDestinationDir: flags.ReportDestinationDir, - PreBuildpacks: flags.PreBuildpacks, - PostBuildpacks: flags.PostBuildpacks, - LayoutConfig: &client.LayoutConfig{ - Sparse: flags.Sparse, - InputImage: inputImageName, - PreviousInputImage: inputPreviousImage, - LayoutRepoDir: cfg.LayoutRepositoryDir, - }, + Buildpacks: buildpacks, + Extensions: extensions, }); err != nil { return errors.Wrap(err, "failed to detect") } @@ -171,103 +100,29 @@ func Detect(logger logging.Logger, cfg config.Config, packClient PackClient) *co }), } detectCommandFlags(cmd, &flags, cfg) - AddHelpFlag(cmd, "build") + AddHelpFlag(cmd, "detect") return cmd } -// TODO: ,-, -func validateDetectFlags(flags *DetectFlags, cfg config.Config, inputImageRef client.InputImageReference, logger logging.Logger) error { - if flags.Registry != "" && !cfg.Experimental { - return client.NewExperimentError("Support for buildpack registries is currently experimental.") - } - - if flags.Cache.Launch.Format == cache.CacheImage { - logger.Warn("cache definition: 'launch' cache in format 'image' is not supported.") - } - - if flags.Cache.Build.Format == cache.CacheImage && flags.CacheImage != "" { - return errors.New("'cache' flag with 'image' format cannot be used with 'cache-image' flag.") - } - - if flags.Cache.Build.Format == cache.CacheImage && !flags.Publish { - return errors.New("image cache format requires the 'publish' flag") - } - - if flags.CacheImage != "" && !flags.Publish { - return errors.New("cache-image flag requires the publish flag") - } - - if flags.GID < 0 { - return errors.New("gid flag must be in the range of 0-2147483647") - } - - if flags.UID < 0 { - return errors.New("uid flag must be in the range of 0-2147483647") - } - - if flags.MacAddress != "" && !isValidMacAddress(flags.MacAddress) { - return errors.New("invalid MAC address provided") - } - - if flags.Interactive && !cfg.Experimental { - return client.NewExperimentError("Interactive mode is currently experimental.") - } - - if inputImageRef.Layout() && !cfg.Experimental { - return client.NewExperimentError("Exporting to OCI layout is currently experimental.") - } - +// TODO: Incomplete +func validateDetectFlags(flags *DetectFlags, cfg config.Config, logger logging.Logger) error { + // Have to implement return nil } -// TODO ,-, +// TODO: Incomplete func detectCommandFlags(cmd *cobra.Command, detectFlags *DetectFlags, cfg config.Config) { cmd.Flags().StringVarP(&detectFlags.AppPath, "path", "p", "", "Path to app dir or zip-formatted file (defaults to current working directory)") cmd.Flags().StringSliceVarP(&detectFlags.Buildpacks, "buildpack", "b", nil, "Buildpack to use. One of:\n a buildpack by id and version in the form of '@',\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 '/[:]'"+stringSliceHelp("buildpack")) cmd.Flags().StringSliceVarP(&detectFlags.Extensions, "extension", "", nil, "Extension to use. One of:\n an extension by id and version in the form of '@',\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 '/[:]'"+stringSliceHelp("extension")) cmd.Flags().StringVarP(&detectFlags.Builder, "builder", "B", cfg.DefaultBuilder, "Builder image") - cmd.Flags().Var(&detectFlags.Cache, "cache", - `Cache options used to define cache techniques for build process. -- Cache as bind: 'type=;format=bind;source=' -- Cache as image (requires --publish): 'type=;format=image;name=' -- Cache as volume: 'type=;format=volume;[name=]' - - If no name is provided, a random name will be generated. -`) - cmd.Flags().StringVar(&detectFlags.CacheImage, "cache-image", "", `Cache build layers in remote registry. Requires --publish`) - cmd.Flags().BoolVar(&detectFlags.ClearCache, "clear-cache", false, "Clear image's associated cache before building") - cmd.Flags().StringVar(&detectFlags.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(&detectFlags.DescriptorPath, "descriptor", "d", "", "Path to the project descriptor file") - cmd.Flags().StringVarP(&detectFlags.DefaultProcessType, "default-process", "D", "", `Set the default process type. (default "web")`) cmd.Flags().StringArrayVarP(&detectFlags.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(&detectFlags.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(&detectFlags.Network, "network", "", "Connect detect and build containers to network") - cmd.Flags().StringArrayVar(&detectFlags.PreBuildpacks, "pre-buildpack", []string{}, "Buildpacks to prepend to the groups in the builder's order") - cmd.Flags().StringArrayVar(&detectFlags.PostBuildpacks, "post-buildpack", []string{}, "Buildpacks to append to the groups in the builder's order") - cmd.Flags().BoolVar(&detectFlags.Publish, "publish", false, "Publish the application image directly to the container registry specified in , instead of the daemon. The run image must also reside in the registry.") - cmd.Flags().StringVar(&detectFlags.DockerHost, "docker-host", "", - `Address to docker daemon that will be exposed to the build container. -If not set (or set to empty string) the standard socket location will be used. -Special value 'inherit' may be used in which case DOCKER_HOST environment variable will be used. -This option may set DOCKER_HOST environment variable for the build container if needed. -`) - cmd.Flags().StringVar(&detectFlags.LifecycleImage, "lifecycle-image", cfg.LifecycleImage, `Custom lifecycle image to use for analysis, restore, and export when builder is untrusted.`) cmd.Flags().StringVar(&detectFlags.Policy, "pull-policy", "", `Pull policy to use. Accepted values are always, never, and if-not-present. (default "always")`) - cmd.Flags().StringVarP(&detectFlags.Registry, "buildpack-registry", "r", cfg.DefaultRegistryName, "Buildpack Registry by name") - cmd.Flags().StringVar(&detectFlags.RunImage, "run-image", "", "Run image (defaults to default stack's run image)") - cmd.Flags().StringSliceVarP(&detectFlags.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(&detectFlags.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().StringVarP(&detectFlags.DescriptorPath, "descriptor", "d", "", "Path to the project descriptor file") + cmd.Flags().StringVar(&detectFlags.LifecycleImage, "lifecycle-image", cfg.LifecycleImage, `Custom lifecycle image to use for analysis, restore, and export when builder is untrusted.`) cmd.Flags().StringArrayVar(&detectFlags.Volumes, "volume", nil, "Mount host volume into the build container, in the form ':[:]'.\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==\", can be specified more than once, takes a key-value pair consisting of the option name and its value."+stringArrayHelp("volume")) - cmd.Flags().StringVar(&detectFlags.Workspace, "workspace", "", "Location at which to mount the app dir in the build image") - cmd.Flags().IntVar(&detectFlags.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(&detectFlags.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(&detectFlags.MacAddress, "mac-address", "", "MAC address to set for the build container network configuration") - cmd.Flags().StringVar(&detectFlags.PreviousImage, "previous-image", "", "Set previous image to a particular tag reference, digest reference, or (when performing a daemon build) image ID") - cmd.Flags().StringVar(&detectFlags.SBOMDestinationDir, "sbom-output-dir", "", "Path to export SBoM contents.\nOmitting the flag will yield no SBoM content.") - cmd.Flags().StringVar(&detectFlags.ReportDestinationDir, "report-output-dir", "", "Path to export build report.toml.\nOmitting the flag yield no report file.") - cmd.Flags().BoolVar(&detectFlags.Interactive, "interactive", false, "Launch a terminal UI to depict the build process") - cmd.Flags().BoolVar(&detectFlags.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().StringArrayVar(&detectFlags.PreBuildpacks, "pre-buildpack", []string{}, "Buildpacks to prepend to the groups in the builder's order") + cmd.Flags().StringArrayVar(&detectFlags.PostBuildpacks, "post-buildpack", []string{}, "Buildpacks to append to the groups in the builder's order") } diff --git a/internal/commands/testmocks/mock_pack_client.go b/internal/commands/testmocks/mock_pack_client.go index f49b92def9..7bde53ebe9 100644 --- a/internal/commands/testmocks/mock_pack_client.go +++ b/internal/commands/testmocks/mock_pack_client.go @@ -64,6 +64,20 @@ func (mr *MockPackClientMockRecorder) CreateBuilder(arg0, arg1 interface{}) *gom return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CreateBuilder", reflect.TypeOf((*MockPackClient)(nil).CreateBuilder), arg0, arg1) } +// Detect mocks base method. +func (m *MockPackClient) Detect(arg0 context.Context, arg1 client.BuildOptions) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "Detect", arg0, arg1) + ret0, _ := ret[0].(error) + return ret0 +} + +// Detect indicates an expected call of Detect. +func (mr *MockPackClientMockRecorder) Detect(arg0, arg1 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Detect", reflect.TypeOf((*MockPackClient)(nil).Detect), arg0, arg1) +} + // DownloadSBOM mocks base method. func (m *MockPackClient) DownloadSBOM(arg0 string, arg1 client.DownloadSBOMOptions) error { m.ctrl.T.Helper() diff --git a/internal/fakes/fake_lifecycle.go b/internal/fakes/fake_lifecycle.go index 280c1bf1d8..a431ef69ec 100644 --- a/internal/fakes/fake_lifecycle.go +++ b/internal/fakes/fake_lifecycle.go @@ -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 +} diff --git a/pkg/client/build.go b/pkg/client/build.go index a4ebef86c0..5778279a3e 100644 --- a/pkg/client/build.go +++ b/pkg/client/build.go @@ -72,6 +72,7 @@ type LifecycleExecutor interface { // Execute is responsible for invoking each of these binaries // with the desired configuration. Execute(ctx context.Context, opts build.LifecycleOptions) error + Detect(ctx context.Context, opts build.LifecycleOptions) error } type IsTrustedBuilder func(string) bool diff --git a/pkg/client/detect.go b/pkg/client/detect.go new file mode 100644 index 0000000000..9d5a591c54 --- /dev/null +++ b/pkg/client/detect.go @@ -0,0 +1,172 @@ +package client + +import ( + "context" + "fmt" + "strings" + + "github.com/docker/docker/api/types" + "github.com/pkg/errors" + + "github.com/buildpacks/pack/internal/build" + "github.com/buildpacks/pack/internal/builder" + internalConfig "github.com/buildpacks/pack/internal/config" + "github.com/buildpacks/pack/internal/style" + "github.com/buildpacks/pack/pkg/image" +) + +func (c *Client) Detect(ctx context.Context, opts BuildOptions) error { + appPath, err := c.processAppPath(opts.AppPath) + if err != nil { + return errors.Wrapf(err, "invalid app path '%s'", opts.AppPath) + } + + proxyConfig := c.processProxyConfig(opts.ProxyConfig) + + builderRef, err := c.processBuilderName(opts.Builder) + if err != nil { + return errors.Wrapf(err, "invalid builder '%s'", opts.Builder) + } + + rawBuilderImage, err := c.imageFetcher.Fetch(ctx, builderRef.Name(), image.FetchOptions{Daemon: true, PullPolicy: opts.PullPolicy}) + if err != nil { + return errors.Wrapf(err, "failed to fetch builder image '%s'", builderRef.Name()) + } + + builderOS, err := rawBuilderImage.OS() + if err != nil { + return errors.Wrapf(err, "getting builder OS") + } + + builderArch, err := rawBuilderImage.Architecture() + if err != nil { + return errors.Wrapf(err, "getting builder architecture") + } + + bldr, err := c.getBuilder(rawBuilderImage) + if err != nil { + return errors.Wrapf(err, "invalid builder %s", style.Symbol(opts.Builder)) + } + + fetchedBPs, order, err := c.processBuildpacks(ctx, bldr.Image(), bldr.Buildpacks(), bldr.Order(), bldr.StackID, opts) + if err != nil { + return err + } + + fetchedExs, orderExtensions, err := c.processExtensions(ctx, bldr.Image(), bldr.Extensions(), bldr.OrderExtensions(), bldr.StackID, opts) + if err != nil { + return err + } + + // Ensure the builder's platform APIs are supported + var builderPlatformAPIs builder.APISet + builderPlatformAPIs = append(builderPlatformAPIs, bldr.LifecycleDescriptor().APIs.Platform.Deprecated...) + builderPlatformAPIs = append(builderPlatformAPIs, bldr.LifecycleDescriptor().APIs.Platform.Supported...) + if !supportsPlatformAPI(builderPlatformAPIs) { + c.logger.Debugf("pack %s supports Platform API(s): %s", c.version, strings.Join(build.SupportedPlatformAPIVersions.AsStrings(), ", ")) + c.logger.Debugf("Builder %s supports Platform API(s): %s", style.Symbol(opts.Builder), strings.Join(builderPlatformAPIs.AsStrings(), ", ")) + return errors.Errorf("Builder %s is incompatible with this version of pack", style.Symbol(opts.Builder)) + } + + // Get the platform API version to use + lifecycleVersion := bldr.LifecycleDescriptor().Info.Version + var ( + lifecycleOptsLifecycleImage string + lifecycleAPIs []string + ) + + if supportsLifecycleImage(lifecycleVersion) { + lifecycleImageName := opts.LifecycleImage + if lifecycleImageName == "" { + lifecycleImageName = fmt.Sprintf("%s:%s", internalConfig.DefaultLifecycleImageRepo, lifecycleVersion.String()) + } + + lifecycleImage, err := c.imageFetcher.Fetch( + ctx, + lifecycleImageName, + image.FetchOptions{ + Daemon: true, + PullPolicy: opts.PullPolicy, + Platform: fmt.Sprintf("%s/%s", builderOS, builderArch), + }, + ) + if err != nil { + return fmt.Errorf("fetching lifecycle image: %w", err) + } + + lifecycleOptsLifecycleImage = lifecycleImage.Name() + labels, err := lifecycleImage.Labels() + if err != nil { + return fmt.Errorf("reading labels of lifecycle image: %w", err) + } + + lifecycleAPIs, err = extractSupportedLifecycleApis(labels) + if err != nil { + return fmt.Errorf("reading api versions of lifecycle image: %w", err) + } + } + + usingPlatformAPI, err := build.FindLatestSupported(append( + bldr.LifecycleDescriptor().APIs.Platform.Deprecated, + bldr.LifecycleDescriptor().APIs.Platform.Supported...), + lifecycleAPIs) + if err != nil { + return fmt.Errorf("finding latest supported Platform API: %w", err) + } + + buildEnvs := map[string]string{} + + for k, v := range opts.Env { + buildEnvs[k] = v + } + + ephemeralBuilder, err := c.createEphemeralBuilder(rawBuilderImage, buildEnvs, order, fetchedBPs, orderExtensions, fetchedExs, usingPlatformAPI.LessThan("0.12")) + if err != nil { + return err + } + defer c.docker.ImageRemove(context.Background(), ephemeralBuilder.Name(), types.ImageRemoveOptions{Force: true}) + + if len(bldr.OrderExtensions()) > 0 || len(ephemeralBuilder.OrderExtensions()) > 0 { + if !c.experimental { + return fmt.Errorf("experimental features must be enabled when builder contains image extensions") + } + if builderOS == "windows" { + return fmt.Errorf("builder contains image extensions which are not supported for Windows builds") + } + if !(opts.PullPolicy == image.PullAlways) { + return fmt.Errorf("pull policy must be 'always' when builder contains image extensions") + } + } + + processedVolumes, warnings, err := processVolumes(builderOS, opts.ContainerConfig.Volumes) + if err != nil { + return err + } + for _, warning := range warnings { + c.logger.Warn(warning) + } + + lifecycleOpts := build.LifecycleOptions{ + AppPath: appPath, + Builder: ephemeralBuilder, + BuilderImage: builderRef.Name(), + LifecycleImage: ephemeralBuilder.Name(), + HTTPProxy: proxyConfig.HTTPProxy, + HTTPSProxy: proxyConfig.HTTPSProxy, + NoProxy: proxyConfig.NoProxy, + Network: opts.ContainerConfig.Network, + Volumes: processedVolumes, + Keychain: c.keychain, + } + + if supportsLifecycleImage(lifecycleVersion) { + lifecycleOpts.LifecycleImage = lifecycleOptsLifecycleImage + lifecycleOpts.LifecycleApis = lifecycleAPIs + } + + if err = c.lifecycleExecutor.Detect(ctx, lifecycleOpts); err != nil { + return fmt.Errorf("executing detect: %w", err) + } + // Log the final detected group + return nil +} diff --git a/pkg/testmocks/mock_access_checker.go b/pkg/testmocks/mock_access_checker.go new file mode 100644 index 0000000000..558b85a580 --- /dev/null +++ b/pkg/testmocks/mock_access_checker.go @@ -0,0 +1,48 @@ +// Code generated by MockGen. DO NOT EDIT. +// Source: github.com/buildpacks/pack/pkg/client (interfaces: AccessChecker) + +// Package testmocks is a generated GoMock package. +package testmocks + +import ( + reflect "reflect" + + gomock "github.com/golang/mock/gomock" +) + +// MockAccessChecker is a mock of AccessChecker interface. +type MockAccessChecker struct { + ctrl *gomock.Controller + recorder *MockAccessCheckerMockRecorder +} + +// MockAccessCheckerMockRecorder is the mock recorder for MockAccessChecker. +type MockAccessCheckerMockRecorder struct { + mock *MockAccessChecker +} + +// NewMockAccessChecker creates a new mock instance. +func NewMockAccessChecker(ctrl *gomock.Controller) *MockAccessChecker { + mock := &MockAccessChecker{ctrl: ctrl} + mock.recorder = &MockAccessCheckerMockRecorder{mock} + return mock +} + +// EXPECT returns an object that allows the caller to indicate expected use. +func (m *MockAccessChecker) EXPECT() *MockAccessCheckerMockRecorder { + return m.recorder +} + +// Check mocks base method. +func (m *MockAccessChecker) Check(arg0 string) bool { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "Check", arg0) + ret0, _ := ret[0].(bool) + return ret0 +} + +// Check indicates an expected call of Check. +func (mr *MockAccessCheckerMockRecorder) Check(arg0 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Check", reflect.TypeOf((*MockAccessChecker)(nil).Check), arg0) +} diff --git a/tools/go.sum b/tools/go.sum index 3511f5a3bf..3617a95580 100644 --- a/tools/go.sum +++ b/tools/go.sum @@ -72,6 +72,7 @@ github.com/ashanbrown/forbidigo v1.5.1/go.mod h1:Y8j9jy9ZYAEHXdu723cUlraTqbzjKF1 github.com/ashanbrown/makezero v1.1.1 h1:iCQ87C0V0vSyO+M9E/FZYbu65auqH0lnsOkf5FcB28s= github.com/ashanbrown/makezero v1.1.1/go.mod h1:i1bJLCRSCHOcOa9Y6MyF2FTfMZMFdHvxKHxgO5Z1axI= github.com/benbjohnson/clock v1.1.0 h1:Q92kusRqC1XV2MjkWETPvjJVqKetz1OzxZB7mHJLju8= +github.com/benbjohnson/clock v1.1.0/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA= github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8= github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= @@ -130,6 +131,7 @@ github.com/fatih/structtag v1.2.0/go.mod h1:mBJUNpUnHmRKrKlQQlmCrh5PuhftFbNv8Ys4 github.com/firefart/nonamedreturns v1.0.4 h1:abzI1p7mAEPYuR4A+VLKn4eNDOycjYo2phmY9sfv40Y= github.com/firefart/nonamedreturns v1.0.4/go.mod h1:TDhe/tjI1BXo48CmYbUduTV7BdIga8MAO/xbKdcVsGI= github.com/frankban/quicktest v1.14.3 h1:FJKSZTDHjyhriyC81FLQ0LY93eSai0ZyR/ZIkd3ZUKE= +github.com/frankban/quicktest v1.14.3/go.mod h1:mgiwOwqx65TmIk1wJ6Q7wvnVMocbUorkibMOrVTHZps= github.com/fsnotify/fsnotify v1.5.4 h1:jRbGcIw6P2Meqdwuo0H1p6JVLbL5DHKAKlYndzMwVZI= github.com/fsnotify/fsnotify v1.5.4/go.mod h1:OVB6XrOHzAwXMpEM7uPOzcehqUV2UqJxmVXmkdnm1bU= github.com/fzipp/gocyclo v0.6.0 h1:lsblElZG7d3ALtGMx9fmxeTKZaLLpU8mET09yN4BBLo= @@ -146,6 +148,7 @@ github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9 github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk= github.com/go-logfmt/logfmt v0.5.0/go.mod h1:wCYkCAKZfumFQihp8CzCvQ3paCTfi41vtzG1KdI/P7A= github.com/go-logr/logr v1.2.3 h1:2DntVwHkVopvECVRSlL5PSo9eG+cAkDCuckLubN+rq0= +github.com/go-logr/logr v1.2.3/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= github.com/go-toolsmith/astcast v1.1.0 h1:+JN9xZV1A+Re+95pgnMgDboWNVnIMMQXwfBwLRPgSC8= github.com/go-toolsmith/astcast v1.1.0/go.mod h1:qdcuFWeGGS2xX5bLM/c3U9lewg7+Zu4mr+xPwZIB4ZU= @@ -159,6 +162,7 @@ github.com/go-toolsmith/astfmt v1.1.0/go.mod h1:OrcLlRwu0CuiIBp/8b5PYF9ktGVZUjlN github.com/go-toolsmith/astp v1.1.0 h1:dXPuCl6u2llURjdPLLDxJeZInAeZ0/eZwFJmqZMnpQA= github.com/go-toolsmith/astp v1.1.0/go.mod h1:0T1xFGz9hicKs8Z5MfAqSUitoUYS30pDMsRVIDHs8CA= github.com/go-toolsmith/pkgload v1.2.2 h1:0CtmHq/02QhxcF7E9N5LIFcYFsMR5rdovfqTtRKkgIk= +github.com/go-toolsmith/pkgload v1.2.2/go.mod h1:R2hxLNRKuAsiXCo2i5J6ZQPhnPMOVtU+f0arbFPWCus= github.com/go-toolsmith/strparse v1.0.0/go.mod h1:YI2nUKP9YGZnL/L1/DLFBfixrcjslWct4wyljWhSRy8= github.com/go-toolsmith/strparse v1.1.0 h1:GAioeZUK9TGxnLS+qfdqNbA4z0SSm5zVNtCQiyP2Bvw= github.com/go-toolsmith/strparse v1.1.0/go.mod h1:7ksGy58fsaQkGQlY8WVoBFNyEPMGuJin1rfoPS4lBSQ= @@ -269,6 +273,7 @@ github.com/gostaticanalysis/nilerr v0.1.1 h1:ThE+hJP0fEp4zWLkWHWcRyI2Od0p7DlgYG3 github.com/gostaticanalysis/nilerr v0.1.1/go.mod h1:wZYb6YI5YAxxq0i1+VJbY0s2YONW0HU0GPE3+5PWN4A= github.com/gostaticanalysis/testutil v0.3.1-0.20210208050101-bfb5c8eec0e4/go.mod h1:D+FIZ+7OahH3ePw/izIEeH5I06eKs1IKI4Xr64/Am3M= github.com/gostaticanalysis/testutil v0.4.0 h1:nhdCmubdmDF6VEatUNjgUZBJKWRqugoISdUv3PPQgHY= +github.com/gostaticanalysis/testutil v0.4.0/go.mod h1:bLIoPefWXrRi/ssLFWX1dx7Repi5x3CuviD3dgAZaBU= github.com/hashicorp/errwrap v1.0.0 h1:hLrqtEDnRye3+sgx6z4qVLNuviH3MR5aQ0ykNJa/UYA= github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= github.com/hashicorp/go-multierror v1.1.1 h1:H5DkEtf6CXdFp0N0Em5UCwQpXMWke8IA0+lD48awMYo= @@ -317,9 +322,11 @@ github.com/kr/fs v0.1.0/go.mod h1:FFnZGqtBN9Gxj7eW1uZ42v5BccTP0vu6NEaFoC2HwRg= github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= github.com/kr/pretty v0.3.0 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0= +github.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= +github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/kulti/thelper v0.6.3 h1:ElhKf+AlItIu+xGnI990no4cE2+XaSu1ULymV2Yulxs= github.com/kulti/thelper v0.6.3/go.mod h1:DsqKShOvP40epevkFrvIwkCMNYxMeTNjdWL4dqWHZ6I= github.com/kunwardeep/paralleltest v1.0.6 h1:FCKYMF1OF2+RveWlABsdnmsvJrei5aoyZoaGS+Ugg8g= @@ -375,6 +382,7 @@ github.com/nakabonne/nestif v0.3.1/go.mod h1:9EtoZochLn5iUprVDmDjqGKPofoUEBL8U4N github.com/nbutton23/zxcvbn-go v0.0.0-20210217022336-fa2cb2858354 h1:4kuARK6Y6FxaNu/BnU2OAaLF86eTVhP2hjTB6iMvItA= github.com/nbutton23/zxcvbn-go v0.0.0-20210217022336-fa2cb2858354/go.mod h1:KSVJerMDfblTH7p5MZaTt+8zaT2iEk3AkVb9PQdZuE8= github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e h1:fD57ERR4JtEqsWbfPhv4DMiApHyliiK5xCTNVSPiaAs= +github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno= github.com/nishanths/exhaustive v0.9.5 h1:TzssWan6orBiLYVqewCG8faud9qlFntJE30ACpzmGME= github.com/nishanths/exhaustive v0.9.5/go.mod h1:IbwrGdVMizvDcIxPYGVdQn5BqWJaOwpCvg4RGb8r/TA= github.com/nishanths/predeclared v0.2.2 h1:V2EPdZPliZymNAn79T8RkNApBjMmVKh5XRpLm/w98Vk= @@ -384,7 +392,9 @@ github.com/nunnatsa/ginkgolinter v0.9.0/go.mod h1:FHaMLURXP7qImeH6bvxWJUpyH+2tuq github.com/olekukonko/tablewriter v0.0.5 h1:P2Ga83D34wi1o9J6Wh1mRuqd4mF/x/lgBS7N7AbDhec= github.com/olekukonko/tablewriter v0.0.5/go.mod h1:hPp6KlRPjbx+hW8ykQs1w3UBbZlj6HuIJcUGPhkA7kY= github.com/onsi/ginkgo/v2 v2.8.0 h1:pAM+oBNPrpXRs+E/8spkeGx9QgekbRVyr74EUvRVOUI= +github.com/onsi/ginkgo/v2 v2.8.0/go.mod h1:6JsQiECmxCa3V5st74AL/AmsV482EDdVrGaVW6z3oYU= github.com/onsi/gomega v1.26.0 h1:03cDLK28U6hWvCAns6NeydX3zIm4SF3ci69ulidS32Q= +github.com/onsi/gomega v1.26.0/go.mod h1:r+zV744Re+DiYCIPRlYOTxn0YkOLcAnW8k1xXdMPGhM= github.com/otiai10/copy v1.2.0 h1:HvG945u96iNadPoG2/Ja2+AUJeW5YuFQMixq9yirC+k= github.com/otiai10/copy v1.2.0/go.mod h1:rrF5dJ5F0t/EWSYODDu4j9/vEeYHMkc8jt0zJChqQWw= github.com/otiai10/curr v0.0.0-20150429015615-9b4961190c95/go.mod h1:9qAhocn7zKJG+0mI8eUu6xqkFDYS2kb2saOteoSB3cE= @@ -436,6 +446,7 @@ github.com/quasilyte/stdinfo v0.0.0-20220114132959-f7386bf02567 h1:M8mH9eK4OUR4l github.com/quasilyte/stdinfo v0.0.0-20220114132959-f7386bf02567/go.mod h1:DWNGW8A4Y+GyBgPuaQJuWiy0XYftx4Xm/y5Jqk9I6VQ= github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= github.com/rogpeppe/go-internal v1.9.0 h1:73kH8U+JUqXU8lRuOHeVHaa/SZPifC7BkcraZVejAe8= +github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs= github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= github.com/ryancurrah/gomodguard v1.3.0 h1:q15RT/pd6UggBXVBuLps8BXRvl5GPBcwVA7BJHMLuTw= github.com/ryancurrah/gomodguard v1.3.0/go.mod h1:ggBxb3luypPEzqVtq33ee7YSN35V28XeGnid8dnni50= @@ -547,6 +558,7 @@ go.opencensus.io v0.22.5/go.mod h1:5pWMHQbX5EPX2/62yrJeAkowc+lfs/XD7Uxpq3pI6kk= go.uber.org/atomic v1.7.0 h1:ADUqmZGgLDDfbSL9ZmPxKTybcoEYHgpYfELNoN+7hsw= go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= go.uber.org/goleak v1.1.11 h1:wy28qYRKZgnJTxGxvye5/wgWr1EKjmUDGYox5mGlRlI= +go.uber.org/goleak v1.1.11/go.mod h1:cwTWslyiVhfpKIDGSZEM2HlOvcqm+tG4zioyIeLoqMQ= go.uber.org/multierr v1.6.0 h1:y6IPFStTAIT5Ytl7/XYmHvzXQ7S3g/IeZW9hyZ5thw4= go.uber.org/multierr v1.6.0/go.mod h1:cdWPpRnG4AhwMwsgIHip0KRBQjJy5kYEpYjJxpXp9iU= go.uber.org/zap v1.24.0 h1:FiJd5l1UOLj0wCgbSE0rwwXHzEdAZS6hiiSnxJN/D60= @@ -652,6 +664,7 @@ golang.org/x/net v0.3.0/go.mod h1:MBQ8lrhLObU/6UmLb4fmbmk5OcyYmqtbGd/9yIeKjEE= golang.org/x/net v0.5.0/go.mod h1:DivGGAXEgPSlEBzxGzZI+ZLohi+xUj054jfeKui00ws= golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= golang.org/x/net v0.8.0 h1:Zrh2ngAOFYneWTAIAPethzeaQLuHwhuBkuV6ZiRnUaQ= +golang.org/x/net v0.8.0/go.mod h1:QVkue5JL9kW//ek3r6jTKnTFis1tRmNAW2P1shuFdJc= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= @@ -934,6 +947,7 @@ gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8 gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f h1:BLraFXnmrev5lT+xlilqcH8XK9/i0At2xKjWk4p6zsU= +gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= gopkg.in/ini.v1 v1.67.0 h1:Dgnx+6+nfE+IfzjUEISNeydPJh9AXNNsWbGP9KzCsOA= gopkg.in/ini.v1 v1.67.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=