diff --git a/cmd/cmd.go b/cmd/cmd.go index 837c9c96f4..adcb5de82d 100644 --- a/cmd/cmd.go +++ b/cmd/cmd.go @@ -12,6 +12,7 @@ import ( imagewriter "github.com/buildpacks/pack/internal/inspectimage/writer" "github.com/buildpacks/pack/internal/term" "github.com/buildpacks/pack/pkg/client" + "github.com/buildpacks/pack/pkg/image" "github.com/buildpacks/pack/pkg/logging" ) @@ -33,7 +34,9 @@ func NewPackCommand(logger ConfigurableLogger) (*cobra.Command, error) { return nil, err } - packClient, err := initClient(logger, cfg) + imagePullPolicyHandler := image.NewPullPolicyManager(logger) + + packClient, err := initClient(logger, cfg, imagePullPolicyHandler) if err != nil { return nil, err } @@ -75,14 +78,14 @@ func NewPackCommand(logger ConfigurableLogger) (*cobra.Command, error) { commands.AddHelpFlag(rootCmd, "pack") - rootCmd.AddCommand(commands.Build(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())) - rootCmd.AddCommand(commands.NewConfigCommand(logger, cfg, cfgPath, packClient)) + rootCmd.AddCommand(commands.Build(logger, cfg, packClient, imagePullPolicyHandler)) + rootCmd.AddCommand(commands.NewBuilderCommand(logger, cfg, packClient, imagePullPolicyHandler)) + rootCmd.AddCommand(commands.NewBuildpackCommand(logger, cfg, packClient, buildpackage.NewConfigReader(), imagePullPolicyHandler)) + rootCmd.AddCommand(commands.NewExtensionCommand(logger, cfg, packClient, buildpackage.NewConfigReader(), imagePullPolicyHandler)) + rootCmd.AddCommand(commands.NewConfigCommand(logger, cfg, cfgPath, packClient, imagePullPolicyHandler)) rootCmd.AddCommand(commands.InspectImage(logger, imagewriter.NewFactory(), cfg, packClient)) rootCmd.AddCommand(commands.NewStackCommand(logger)) - rootCmd.AddCommand(commands.Rebase(logger, cfg, packClient)) + rootCmd.AddCommand(commands.Rebase(logger, cfg, packClient, imagePullPolicyHandler)) rootCmd.AddCommand(commands.NewSBOMCommand(logger, cfg, packClient)) rootCmd.AddCommand(commands.InspectBuildpack(logger, cfg, packClient)) @@ -94,8 +97,8 @@ func NewPackCommand(logger ConfigurableLogger) (*cobra.Command, error) { rootCmd.AddCommand(commands.TrustBuilder(logger, cfg, cfgPath)) rootCmd.AddCommand(commands.UntrustBuilder(logger, cfg, cfgPath)) rootCmd.AddCommand(commands.ListTrustedBuilders(logger, cfg)) - rootCmd.AddCommand(commands.CreateBuilder(logger, cfg, packClient)) - rootCmd.AddCommand(commands.PackageBuildpack(logger, cfg, packClient, buildpackage.NewConfigReader())) + rootCmd.AddCommand(commands.CreateBuilder(logger, cfg, packClient, imagePullPolicyHandler)) + rootCmd.AddCommand(commands.PackageBuildpack(logger, cfg, packClient, buildpackage.NewConfigReader(), imagePullPolicyHandler)) if cfg.Experimental { rootCmd.AddCommand(commands.AddBuildpackRegistry(logger, cfg, cfgPath)) @@ -136,7 +139,7 @@ func initConfig() (config.Config, string, error) { return cfg, path, nil } -func initClient(logger logging.Logger, cfg config.Config) (*client.Client, error) { +func initClient(logger logging.Logger, cfg config.Config, imagePullPolicyHandler image.ImagePullPolicyHandler) (*client.Client, error) { if err := client.ProcessDockerContext(logger); err != nil { return nil, err } @@ -145,5 +148,5 @@ func initClient(logger logging.Logger, cfg config.Config) (*client.Client, error if err != nil { return nil, err } - return client.NewClient(client.WithLogger(logger), client.WithExperimental(cfg.Experimental), client.WithRegistryMirrors(cfg.RegistryMirrors), client.WithDockerClient(dc)) + return client.NewClient(client.WithLogger(logger), client.WithExperimental(cfg.Experimental), client.WithRegistryMirrors(cfg.RegistryMirrors), client.WithDockerClient(dc), client.WithImagePullChecker(imagePullPolicyHandler)) } diff --git a/go.mod b/go.mod index d074fe0eb6..f860f5c851 100644 --- a/go.mod +++ b/go.mod @@ -11,7 +11,7 @@ require ( github.com/docker/docker v25.0.5+incompatible github.com/docker/go-connections v0.5.0 github.com/dustin/go-humanize v1.0.1 - github.com/gdamore/tcell/v2 v2.7.4 + github.com/gdamore/tcell/v2 v2.7.1 github.com/go-git/go-git/v5 v5.11.0 github.com/golang/mock v1.6.0 github.com/google/go-cmp v0.6.0 @@ -29,9 +29,9 @@ require ( github.com/sabhiram/go-gitignore v0.0.0-20210923224102-525f6e181f06 github.com/sclevine/spec v1.4.0 github.com/spf13/cobra v1.8.0 - golang.org/x/crypto v0.21.0 - golang.org/x/mod v0.16.0 - golang.org/x/oauth2 v0.18.0 + golang.org/x/crypto v0.20.0 + golang.org/x/mod v0.15.0 + golang.org/x/oauth2 v0.17.0 golang.org/x/sync v0.6.0 golang.org/x/sys v0.18.0 golang.org/x/term v0.18.0 @@ -132,8 +132,8 @@ require ( go.opentelemetry.io/otel v1.23.0 // indirect go.opentelemetry.io/otel/metric v1.23.0 // indirect go.opentelemetry.io/otel/trace v1.23.0 // indirect - golang.org/x/net v0.22.0 // indirect - golang.org/x/tools v0.17.0 // indirect + golang.org/x/net v0.21.0 // indirect + golang.org/x/tools v0.18.0 // indirect google.golang.org/appengine v1.6.8 // indirect google.golang.org/protobuf v1.33.0 // indirect gopkg.in/warnings.v0 v0.1.2 // indirect diff --git a/go.sum b/go.sum index 88c9cf662a..17ca08fc80 100644 --- a/go.sum +++ b/go.sum @@ -154,8 +154,8 @@ github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMo github.com/gdamore/encoding v1.0.0 h1:+7OoQ1Bc6eTm5niUzBa0Ctsh6JbMW6Ra+YNuAtDBdko= github.com/gdamore/encoding v1.0.0/go.mod h1:alR0ol34c49FCSBLjhosxzcPHQbf2trDkoo5dl+VrEg= github.com/gdamore/tcell/v2 v2.4.1-0.20210905002822-f057f0a857a1/go.mod h1:Az6Jt+M5idSED2YPGtwnfJV0kXohgdCBPmHGSYc1r04= -github.com/gdamore/tcell/v2 v2.7.4 h1:sg6/UnTM9jGpZU+oFYAsDahfchWAFW8Xx2yFinNSAYU= -github.com/gdamore/tcell/v2 v2.7.4/go.mod h1:dSXtXTSK0VsW1biw65DZLZ2NKr7j0qP/0J7ONmsraWg= +github.com/gdamore/tcell/v2 v2.7.1 h1:TiCcmpWHiAU7F0rA2I3S2Y4mmLmO9KHxJ7E1QhYzQbc= +github.com/gdamore/tcell/v2 v2.7.1/go.mod h1:dSXtXTSK0VsW1biw65DZLZ2NKr7j0qP/0J7ONmsraWg= github.com/gliderlabs/ssh v0.3.5 h1:OcaySEmAQJgyYcArR+gGGTHCyE7nvhEMTlYY+Dp8CpY= github.com/gliderlabs/ssh v0.3.5/go.mod h1:8XB4KraRrX39qHhT6yxPsHedjA08I/uBVwj4xC+/+z4= github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 h1:+zs/tPmkDkHx3U66DAb0lQFJrpS6731Oaa12ikc+DiI= @@ -417,15 +417,15 @@ golang.org/x/crypto v0.0.0-20220722155217-630584e8d5aa/go.mod h1:IxCIyHEi3zRg3s0 golang.org/x/crypto v0.3.1-0.20221117191849-2c476679df9a/go.mod h1:hebNnKkNXi2UzZN1eVRvBB7co0a+JxK6XbPiWVs/3J4= golang.org/x/crypto v0.6.0/go.mod h1:OFC/31mSvZgRz0V1QTNCzfAI1aIRzbiufJtkMIlEp58= golang.org/x/crypto v0.7.0/go.mod h1:pYwdfH91IfpZVANVyUOhSIPZaFoJGxTFbZhFTx+dXZU= -golang.org/x/crypto v0.21.0 h1:X31++rzVUdKhX5sWmSOFZxx8UW/ldWx55cbf08iNAMA= -golang.org/x/crypto v0.21.0/go.mod h1:0BP7YvVV9gBbVKyeTG0Gyn+gZm94bibOW5BjDEYAOMs= +golang.org/x/crypto v0.20.0 h1:jmAMJJZXr5KiCw05dfYK9QnqaqKLYXijU23lsEdcQqg= +golang.org/x/crypto v0.20.0/go.mod h1:Xwo95rrVNIoSMx9wa1JroENMToLWn3RNVrTBpLHgZPQ= golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= -golang.org/x/mod v0.16.0 h1:QX4fJ0Rr5cPQCF7O9lh9Se4pmwfwskqZfq5moyldzic= -golang.org/x/mod v0.16.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= +golang.org/x/mod v0.15.0 h1:SernR4v+D55NyBH2QiEQrlBAnj1ECL6AGrA5+dPaMY8= +golang.org/x/mod v0.15.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= @@ -441,11 +441,11 @@ golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug golang.org/x/net v0.2.0/go.mod h1:KqCZLdyyvdV855qA2rE3GC2aiw5xGR5TEjj8smXukLY= golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= golang.org/x/net v0.8.0/go.mod h1:QVkue5JL9kW//ek3r6jTKnTFis1tRmNAW2P1shuFdJc= -golang.org/x/net v0.22.0 h1:9sGLhx7iRIHEiX0oAJ3MRZMUCElJgy7Br1nO+AMN3Tc= -golang.org/x/net v0.22.0/go.mod h1:JKghWKKOSdJwpW2GEx0Ja7fmaKnMsbu+MWVZTokSYmg= +golang.org/x/net v0.21.0 h1:AQyQV4dYCvJ7vGmJyKki9+PBdyvhkSd8EIx/qb0AYv4= +golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= -golang.org/x/oauth2 v0.18.0 h1:09qnuIAgzdx1XplqJvW6CQqMCtGZykZWcXzPMPUusvI= -golang.org/x/oauth2 v0.18.0/go.mod h1:Wf7knwG0MPoWIMMBgFlEaSUDaKskp0dCfrlJRJXbBi8= +golang.org/x/oauth2 v0.17.0 h1:6m3ZPmLEFdVxKKWnKq4VqZ60gutO35zm+zrAHVmHyDQ= +golang.org/x/oauth2 v0.17.0/go.mod h1:OzPDGQiuQMguemayvdylqddI7qcD9lnSDb+1FiwQ5HA= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= @@ -516,8 +516,8 @@ golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4f golang.org/x/tools v0.1.1/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= -golang.org/x/tools v0.17.0 h1:FvmRgNOcs3kOa+T20R1uhfP9F6HgG2mfxDv1vrx1Htc= -golang.org/x/tools v0.17.0/go.mod h1:xsh6VxdV005rRVaS6SSAf9oiAqljS7UZUacMZ8Bnsps= +golang.org/x/tools v0.18.0 h1:k8NLag8AGHnn+PHbl7g43CtqZAwG60vZkLqgyZgIHgQ= +golang.org/x/tools v0.18.0/go.mod h1:GL7B4CwcLLeo59yx/9UWWuNOW1n3VZ4f5axWfML7Lcg= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= diff --git a/internal/commands/build.go b/internal/commands/build.go index d32f96cf8b..5f3b9f302a 100644 --- a/internal/commands/build.go +++ b/internal/commands/build.go @@ -58,7 +58,7 @@ type BuildFlags struct { } // Build an image from source code -func Build(logger logging.Logger, cfg config.Config, packClient PackClient) *cobra.Command { +func Build(logger logging.Logger, cfg config.Config, packClient PackClient, imagePullPolicyHandler image.ImagePullPolicyHandler) *cobra.Command { var flags BuildFlags cmd := &cobra.Command{ @@ -128,7 +128,7 @@ func Build(logger logging.Logger, cfg config.Config, packClient PackClient) *cob if stringPolicy == "" { stringPolicy = cfg.PullPolicy } - pullPolicy, err := image.ParsePullPolicy(stringPolicy) + pullPolicy, err := imagePullPolicyHandler.ParsePullPolicy(stringPolicy) if err != nil { return errors.Wrapf(err, "parsing pull policy %s", flags.Policy) } diff --git a/internal/commands/build_test.go b/internal/commands/build_test.go index 6a59810004..3644f70a83 100644 --- a/internal/commands/build_test.go +++ b/internal/commands/build_test.go @@ -26,6 +26,7 @@ import ( "github.com/buildpacks/pack/pkg/image" "github.com/buildpacks/pack/pkg/logging" projectTypes "github.com/buildpacks/pack/pkg/project/types" + fetcher_mock "github.com/buildpacks/pack/pkg/testmocks" h "github.com/buildpacks/pack/testhelpers" ) @@ -38,12 +39,13 @@ func TestBuildCommand(t *testing.T) { func testBuildCommand(t *testing.T, when spec.G, it spec.S) { var ( - command *cobra.Command - logger *logging.LogWithWriters - outBuf bytes.Buffer - mockController *gomock.Controller - mockClient *testmocks.MockPackClient - cfg config.Config + command *cobra.Command + logger *logging.LogWithWriters + outBuf bytes.Buffer + mockController *gomock.Controller + mockClient *testmocks.MockPackClient + cfg config.Config + imagePullPolicyHandler image.ImagePullPolicyHandler ) it.Before(func() { @@ -51,8 +53,9 @@ func testBuildCommand(t *testing.T, when spec.G, it spec.S) { cfg = config.Config{} mockController = gomock.NewController(t) mockClient = testmocks.NewMockPackClient(mockController) + imagePullPolicyHandler = fetcher_mock.NewMockPullPolicyManager(logger) - command = commands.Build(logger, cfg, mockClient) + command = commands.Build(logger, cfg, mockClient, imagePullPolicyHandler) }) when("#BuildCommand", func() { @@ -97,7 +100,7 @@ func testBuildCommand(t *testing.T, when spec.G, it spec.S) { Return(nil) cfg := config.Config{TrustedBuilders: []config.TrustedBuilder{{Name: "my-builder"}}} - command = commands.Build(logger, cfg, mockClient) + command = commands.Build(logger, cfg, mockClient, imagePullPolicyHandler) }) it("sets the trust builder option", func() { logger.WantVerbose(true) @@ -167,7 +170,7 @@ func testBuildCommand(t *testing.T, when spec.G, it spec.S) { Return(nil) cfg := config.Config{PullPolicy: "if-not-present"} - command := commands.Build(logger, cfg, mockClient) + command := commands.Build(logger, cfg, mockClient, imagePullPolicyHandler) logger.WantVerbose(true) command.SetArgs([]string{"image", "--builder", "my-builder", "--pull-policy", "never"}) @@ -193,7 +196,7 @@ func testBuildCommand(t *testing.T, when spec.G, it spec.S) { Return(nil) cfg := config.Config{PullPolicy: "never"} - command := commands.Build(logger, cfg, mockClient) + command := commands.Build(logger, cfg, mockClient, imagePullPolicyHandler) logger.WantVerbose(true) command.SetArgs([]string{"image", "--builder", "my-builder"}) @@ -455,7 +458,7 @@ func testBuildCommand(t *testing.T, when spec.G, it spec.S) { Return(nil) cfg := config.Config{LifecycleImage: "some-lifecycle-image"} - command := commands.Build(logger, cfg, mockClient) + command := commands.Build(logger, cfg, mockClient, imagePullPolicyHandler) logger.WantVerbose(true) command.SetArgs([]string{"image", "--builder", "my-builder"}) @@ -895,7 +898,7 @@ builder = "my-builder" Experimental: true, LayoutRepositoryDir: layoutDir, } - command = commands.Build(logger, cfg, mockClient) + command = commands.Build(logger, cfg, mockClient, imagePullPolicyHandler) }) when("path to save the image is provided", func() { diff --git a/internal/commands/builder.go b/internal/commands/builder.go index 50d5efdd1a..c226a597a5 100644 --- a/internal/commands/builder.go +++ b/internal/commands/builder.go @@ -5,10 +5,11 @@ import ( builderwriter "github.com/buildpacks/pack/internal/builder/writer" "github.com/buildpacks/pack/internal/config" + "github.com/buildpacks/pack/pkg/image" "github.com/buildpacks/pack/pkg/logging" ) -func NewBuilderCommand(logger logging.Logger, cfg config.Config, client PackClient) *cobra.Command { +func NewBuilderCommand(logger logging.Logger, cfg config.Config, client PackClient, imagePullPolicyHandler image.ImagePullPolicyHandler) *cobra.Command { cmd := &cobra.Command{ Use: "builder", Aliases: []string{"builders"}, @@ -16,7 +17,7 @@ func NewBuilderCommand(logger logging.Logger, cfg config.Config, client PackClie RunE: nil, } - cmd.AddCommand(BuilderCreate(logger, cfg, client)) + cmd.AddCommand(BuilderCreate(logger, cfg, client, imagePullPolicyHandler)) cmd.AddCommand(BuilderInspect(logger, cfg, client, builderwriter.NewFactory())) cmd.AddCommand(BuilderSuggest(logger, client)) AddHelpFlag(cmd, "builder") diff --git a/internal/commands/builder_create.go b/internal/commands/builder_create.go index 56ac071fe2..3e9ddd3388 100644 --- a/internal/commands/builder_create.go +++ b/internal/commands/builder_create.go @@ -27,7 +27,7 @@ type BuilderCreateFlags struct { } // CreateBuilder creates a builder image, based on a builder config -func BuilderCreate(logger logging.Logger, cfg config.Config, pack PackClient) *cobra.Command { +func BuilderCreate(logger logging.Logger, cfg config.Config, pack PackClient, imagePullPolicyHandler image.ImagePullPolicyHandler) *cobra.Command { var flags BuilderCreateFlags cmd := &cobra.Command{ @@ -50,7 +50,7 @@ Creating a custom builder allows you to control what buildpacks are used and wha if stringPolicy == "" { stringPolicy = cfg.PullPolicy } - pullPolicy, err := image.ParsePullPolicy(stringPolicy) + pullPolicy, err := imagePullPolicyHandler.ParsePullPolicy(stringPolicy) if err != nil { return errors.Wrapf(err, "parsing pull policy %s", flags.Policy) } diff --git a/internal/commands/builder_create_test.go b/internal/commands/builder_create_test.go index b89e7ab534..97980b69fa 100644 --- a/internal/commands/builder_create_test.go +++ b/internal/commands/builder_create_test.go @@ -17,7 +17,9 @@ import ( "github.com/buildpacks/pack/internal/commands" "github.com/buildpacks/pack/internal/commands/testmocks" "github.com/buildpacks/pack/internal/config" + "github.com/buildpacks/pack/pkg/image" "github.com/buildpacks/pack/pkg/logging" + fetcher_mock "github.com/buildpacks/pack/pkg/testmocks" h "github.com/buildpacks/pack/testhelpers" ) @@ -160,14 +162,15 @@ func TestCreateCommand(t *testing.T) { func testCreateCommand(t *testing.T, when spec.G, it spec.S) { var ( - command *cobra.Command - logger logging.Logger - outBuf bytes.Buffer - mockController *gomock.Controller - mockClient *testmocks.MockPackClient - tmpDir string - builderConfigPath string - cfg config.Config + command *cobra.Command + logger logging.Logger + outBuf bytes.Buffer + mockController *gomock.Controller + mockClient *testmocks.MockPackClient + tmpDir string + builderConfigPath string + cfg config.Config + imagePullPolicyHandler image.ImagePullPolicyHandler ) it.Before(func() { @@ -180,7 +183,8 @@ func testCreateCommand(t *testing.T, when spec.G, it spec.S) { mockController = gomock.NewController(t) mockClient = testmocks.NewMockPackClient(mockController) logger = logging.NewLogWithWriters(&outBuf, &outBuf) - command = commands.BuilderCreate(logger, cfg, mockClient) + imagePullPolicyHandler = fetcher_mock.NewMockPullPolicyManager(logger) + command = commands.BuilderCreate(logger, cfg, mockClient, imagePullPolicyHandler) }) it.After(func() { @@ -218,7 +222,7 @@ func testCreateCommand(t *testing.T, when spec.G, it spec.S) { when("configured pull policy is invalid", func() { it("errors when config set with unknown policy", func() { cfg = config.Config{PullPolicy: "unknown-policy"} - command = commands.BuilderCreate(logger, cfg, mockClient) + command = commands.BuilderCreate(logger, cfg, mockClient, imagePullPolicyHandler) command.SetArgs([]string{ "some/builder", "--config", builderConfigPath, diff --git a/internal/commands/builder_test.go b/internal/commands/builder_test.go index 801bc873d6..8bf41d82f8 100644 --- a/internal/commands/builder_test.go +++ b/internal/commands/builder_test.go @@ -12,7 +12,9 @@ import ( "github.com/buildpacks/pack/internal/commands" "github.com/buildpacks/pack/internal/commands/testmocks" "github.com/buildpacks/pack/internal/config" + "github.com/buildpacks/pack/pkg/image" "github.com/buildpacks/pack/pkg/logging" + fetcher_mock "github.com/buildpacks/pack/pkg/testmocks" h "github.com/buildpacks/pack/testhelpers" ) @@ -22,16 +24,18 @@ func TestBuilderCommand(t *testing.T) { func testBuilderCommand(t *testing.T, when spec.G, it spec.S) { var ( - cmd *cobra.Command - logger logging.Logger - outBuf bytes.Buffer + cmd *cobra.Command + logger logging.Logger + outBuf bytes.Buffer + imagePullPolicyHandler image.ImagePullPolicyHandler ) it.Before(func() { logger = logging.NewLogWithWriters(&outBuf, &outBuf) + imagePullPolicyHandler = fetcher_mock.NewMockPullPolicyManager(logger) mockController := gomock.NewController(t) mockClient := testmocks.NewMockPackClient(mockController) - cmd = commands.NewBuilderCommand(logger, config.Config{}, mockClient) + cmd = commands.NewBuilderCommand(logger, config.Config{}, mockClient, imagePullPolicyHandler) cmd.SetOut(logging.GetWriterForLevel(logger, logging.InfoLevel)) }) diff --git a/internal/commands/buildpack.go b/internal/commands/buildpack.go index 93985be84f..3e5ad5d8f0 100644 --- a/internal/commands/buildpack.go +++ b/internal/commands/buildpack.go @@ -4,10 +4,11 @@ import ( "github.com/spf13/cobra" "github.com/buildpacks/pack/internal/config" + "github.com/buildpacks/pack/pkg/image" "github.com/buildpacks/pack/pkg/logging" ) -func NewBuildpackCommand(logger logging.Logger, cfg config.Config, client PackClient, packageConfigReader PackageConfigReader) *cobra.Command { +func NewBuildpackCommand(logger logging.Logger, cfg config.Config, client PackClient, packageConfigReader PackageConfigReader, imagePullPolicyHandler image.ImagePullPolicyHandler) *cobra.Command { cmd := &cobra.Command{ Use: "buildpack", Aliases: []string{"buildpacks"}, @@ -16,7 +17,7 @@ func NewBuildpackCommand(logger logging.Logger, cfg config.Config, client PackCl } cmd.AddCommand(BuildpackInspect(logger, cfg, client)) - cmd.AddCommand(BuildpackPackage(logger, cfg, client, packageConfigReader)) + cmd.AddCommand(BuildpackPackage(logger, cfg, client, packageConfigReader, imagePullPolicyHandler)) cmd.AddCommand(BuildpackNew(logger, client)) cmd.AddCommand(BuildpackPull(logger, cfg, client)) cmd.AddCommand(BuildpackRegister(logger, cfg, client)) diff --git a/internal/commands/buildpack_package.go b/internal/commands/buildpack_package.go index adb95c3e03..633be947ba 100644 --- a/internal/commands/buildpack_package.go +++ b/internal/commands/buildpack_package.go @@ -40,7 +40,7 @@ type PackageConfigReader interface { } // BuildpackPackage packages (a) buildpack(s) into OCI format, based on a package config -func BuildpackPackage(logger logging.Logger, cfg config.Config, packager BuildpackPackager, packageConfigReader PackageConfigReader) *cobra.Command { +func BuildpackPackage(logger logging.Logger, cfg config.Config, packager BuildpackPackager, packageConfigReader PackageConfigReader, imagePullPolicyHandler image.ImagePullPolicyHandler) *cobra.Command { var flags BuildpackPackageFlags cmd := &cobra.Command{ Use: "package --config ", @@ -62,7 +62,7 @@ func BuildpackPackage(logger logging.Logger, cfg config.Config, packager Buildpa if stringPolicy == "" { stringPolicy = cfg.PullPolicy } - pullPolicy, err := image.ParsePullPolicy(stringPolicy) + pullPolicy, err := imagePullPolicyHandler.ParsePullPolicy(stringPolicy) if err != nil { return errors.Wrap(err, "parsing pull policy") } diff --git a/internal/commands/buildpack_package_test.go b/internal/commands/buildpack_package_test.go index f527c01d08..641654a1af 100644 --- a/internal/commands/buildpack_package_test.go +++ b/internal/commands/buildpack_package_test.go @@ -19,6 +19,7 @@ import ( "github.com/buildpacks/pack/pkg/dist" "github.com/buildpacks/pack/pkg/image" "github.com/buildpacks/pack/pkg/logging" + fetcher_mock "github.com/buildpacks/pack/pkg/testmocks" h "github.com/buildpacks/pack/testhelpers" ) @@ -355,11 +356,13 @@ func packageCommand(ops ...packageCommandOption) *cobra.Command { configPath: "/path/to/some/file", } + imagePullPolicyHandler := fetcher_mock.NewMockPullPolicyManager(config.logger) + for _, op := range ops { op(config) } - cmd := commands.BuildpackPackage(config.logger, config.clientConfig, config.buildpackPackager, config.packageConfigReader) + cmd := commands.BuildpackPackage(config.logger, config.clientConfig, config.buildpackPackager, config.packageConfigReader, imagePullPolicyHandler) cmd.SetArgs([]string{config.imageName, "--config", config.configPath, "-p", config.path}) return cmd diff --git a/internal/commands/buildpack_test.go b/internal/commands/buildpack_test.go index 761486e494..ad0f479cce 100644 --- a/internal/commands/buildpack_test.go +++ b/internal/commands/buildpack_test.go @@ -13,7 +13,9 @@ import ( "github.com/buildpacks/pack/internal/commands/fakes" "github.com/buildpacks/pack/internal/commands/testmocks" "github.com/buildpacks/pack/internal/config" + "github.com/buildpacks/pack/pkg/image" "github.com/buildpacks/pack/pkg/logging" + fetcher_mock "github.com/buildpacks/pack/pkg/testmocks" h "github.com/buildpacks/pack/testhelpers" ) @@ -23,17 +25,19 @@ func TestBuildpackCommand(t *testing.T) { func testBuildpackCommand(t *testing.T, when spec.G, it spec.S) { var ( - cmd *cobra.Command - logger logging.Logger - outBuf bytes.Buffer - mockClient *testmocks.MockPackClient + cmd *cobra.Command + logger logging.Logger + outBuf bytes.Buffer + mockClient *testmocks.MockPackClient + imagePullPolicyHandler image.ImagePullPolicyHandler ) it.Before(func() { logger = logging.NewLogWithWriters(&outBuf, &outBuf) + imagePullPolicyHandler = fetcher_mock.NewMockPullPolicyManager(logger) mockController := gomock.NewController(t) mockClient = testmocks.NewMockPackClient(mockController) - cmd = commands.NewBuildpackCommand(logger, config.Config{}, mockClient, fakes.NewFakePackageConfigReader()) + cmd = commands.NewBuildpackCommand(logger, config.Config{}, mockClient, fakes.NewFakePackageConfigReader(), imagePullPolicyHandler) cmd.SetOut(logging.GetWriterForLevel(logger, logging.InfoLevel)) }) diff --git a/internal/commands/config.go b/internal/commands/config.go index a2b087dabc..3073cf3443 100644 --- a/internal/commands/config.go +++ b/internal/commands/config.go @@ -6,10 +6,11 @@ import ( "github.com/spf13/cobra" "github.com/buildpacks/pack/internal/config" + "github.com/buildpacks/pack/pkg/image" "github.com/buildpacks/pack/pkg/logging" ) -func NewConfigCommand(logger logging.Logger, cfg config.Config, cfgPath string, client PackClient) *cobra.Command { +func NewConfigCommand(logger logging.Logger, cfg config.Config, cfgPath string, client PackClient, imagePullPolicyHandler image.ImagePullPolicyHandler) *cobra.Command { cmd := &cobra.Command{ Use: "config", Short: "Interact with your local pack config file", @@ -18,7 +19,8 @@ func NewConfigCommand(logger logging.Logger, cfg config.Config, cfgPath string, cmd.AddCommand(ConfigDefaultBuilder(logger, cfg, cfgPath, client)) cmd.AddCommand(ConfigExperimental(logger, cfg, cfgPath)) - cmd.AddCommand(ConfigPullPolicy(logger, cfg, cfgPath)) + cmd.AddCommand(ConfigPullPolicy(logger, cfg, cfgPath, imagePullPolicyHandler)) + cmd.AddCommand(ConfigPruneInterval(logger, cfg, cfgPath, imagePullPolicyHandler)) cmd.AddCommand(ConfigRegistries(logger, cfg, cfgPath)) cmd.AddCommand(ConfigRunImagesMirrors(logger, cfg, cfgPath)) cmd.AddCommand(ConfigTrustedBuilder(logger, cfg, cfgPath)) diff --git a/internal/commands/config_pull_policy.go b/internal/commands/config_pull_policy.go index ebeac05e67..908059b0b1 100644 --- a/internal/commands/config_pull_policy.go +++ b/internal/commands/config_pull_policy.go @@ -12,16 +12,16 @@ import ( "github.com/buildpacks/pack/pkg/logging" ) -func ConfigPullPolicy(logger logging.Logger, cfg config.Config, cfgPath string) *cobra.Command { +func ConfigPullPolicy(logger logging.Logger, cfg config.Config, cfgPath string, imagePullPolicyHandler image.ImagePullPolicyHandler) *cobra.Command { var unset bool cmd := &cobra.Command{ - Use: "pull-policy ", + Use: "pull-policy | never>", Args: cobra.MaximumNArgs(1), Short: "List, set and unset the global pull policy used by other commands", Long: "You can use this command to list, set, and unset the default pull policy that will be used when working with containers:\n" + "* To list your pull policy, run `pack config pull-policy`.\n" + - "* To set your pull policy, run `pack config pull-policy `.\n" + + "* To set your pull policy, run `pack config pull-policy | never>`.\n" + "* To unset your pull policy, run `pack config pull-policy --unset`.\n" + fmt.Sprintf("Unsetting the pull policy will reset the policy to the default, which is %s", style.Symbol("always")), RunE: logError(logger, func(cmd *cobra.Command, args []string) error { @@ -36,7 +36,7 @@ func ConfigPullPolicy(logger logging.Logger, cfg config.Config, cfgPath string) return errors.Wrapf(err, "writing config to %s", cfgPath) } - pullPolicy, err := image.ParsePullPolicy(cfg.PullPolicy) + pullPolicy, err := imagePullPolicyHandler.ParsePullPolicy(cfg.PullPolicy) if err != nil { return err } @@ -44,7 +44,7 @@ func ConfigPullPolicy(logger logging.Logger, cfg config.Config, cfgPath string) logger.Infof("Successfully unset pull policy %s", style.Symbol(oldPullPolicy)) logger.Infof("Pull policy has been set to %s", style.Symbol(pullPolicy.String())) case len(args) == 0: // list - pullPolicy, err := image.ParsePullPolicy(cfg.PullPolicy) + pullPolicy, err := imagePullPolicyHandler.ParsePullPolicy(cfg.PullPolicy) if err != nil { return err } @@ -58,7 +58,7 @@ func ConfigPullPolicy(logger logging.Logger, cfg config.Config, cfgPath string) return nil } - pullPolicy, err := image.ParsePullPolicy(newPullPolicy) + pullPolicy, err := imagePullPolicyHandler.ParsePullPolicy(newPullPolicy) if err != nil { return err } diff --git a/internal/commands/config_pull_policy_test.go b/internal/commands/config_pull_policy_test.go index 7674ab2ab9..87e62e7405 100644 --- a/internal/commands/config_pull_policy_test.go +++ b/internal/commands/config_pull_policy_test.go @@ -15,6 +15,7 @@ import ( "github.com/buildpacks/pack/internal/commands" "github.com/buildpacks/pack/internal/config" "github.com/buildpacks/pack/pkg/logging" + fetcher_mock "github.com/buildpacks/pack/pkg/testmocks" h "github.com/buildpacks/pack/testhelpers" ) @@ -26,27 +27,43 @@ func TestConfigPullPolicy(t *testing.T) { func testConfigPullPolicyCommand(t *testing.T, when spec.G, it spec.S) { var ( - command *cobra.Command - logger logging.Logger - outBuf bytes.Buffer - tempPackHome string - configFile string - assert = h.NewAssertionManager(t) - cfg = config.Config{} + command *cobra.Command + logger logging.Logger + outBuf bytes.Buffer + tempPackHome string + configFile string + imageJSONData string + assert = h.NewAssertionManager(t) + cfg = config.Config{} + imagePullPolicyHandler *fetcher_mock.MockImagePullPolicyHandler ) it.Before(func() { var err error logger = logging.NewLogWithWriters(&outBuf, &outBuf) + imagePullPolicyHandler = fetcher_mock.NewMockPullPolicyManager(logger) tempPackHome, err = os.MkdirTemp("", "pack-home") h.AssertNil(t, err) + h.AssertNil(t, os.Setenv("PACK_HOME", tempPackHome)) configFile = filepath.Join(tempPackHome, "config.toml") + jsonFilePath := filepath.Join("testdata", "example_image.json") + data, err := os.ReadFile(jsonFilePath) + h.AssertNil(t, err) + imageJSONData = string(data) + + // Create the .pack directory and image.json file + packDir := filepath.Join(tempPackHome, ".pack") + h.AssertNil(t, os.Mkdir(packDir, 0755)) + + imageJSONFile := filepath.Join(packDir, "image.json") + h.AssertNil(t, os.WriteFile(imageJSONFile, []byte(imageJSONData), 0644)) - command = commands.ConfigPullPolicy(logger, cfg, configFile) + command = commands.ConfigPullPolicy(logger, cfg, configFile, imagePullPolicyHandler) command.SetOut(logging.GetWriterForLevel(logger, logging.InfoLevel)) }) it.After(func() { + h.AssertNil(t, os.Unsetenv("PACK_HOME")) h.AssertNil(t, os.RemoveAll(tempPackHome)) }) @@ -64,7 +81,7 @@ func testConfigPullPolicyCommand(t *testing.T, when spec.G, it spec.S) { when("policy set to always in config", func() { it("lists always as pull policy", func() { cfg.PullPolicy = "always" - command = commands.ConfigPullPolicy(logger, cfg, configFile) + command = commands.ConfigPullPolicy(logger, cfg, configFile, imagePullPolicyHandler) command.SetArgs([]string{}) h.AssertNil(t, command.Execute()) @@ -76,7 +93,7 @@ func testConfigPullPolicyCommand(t *testing.T, when spec.G, it spec.S) { when("policy set to never in config", func() { it("lists never as pull policy", func() { cfg.PullPolicy = "never" - command = commands.ConfigPullPolicy(logger, cfg, configFile) + command = commands.ConfigPullPolicy(logger, cfg, configFile, imagePullPolicyHandler) command.SetArgs([]string{}) h.AssertNil(t, command.Execute()) @@ -88,7 +105,7 @@ func testConfigPullPolicyCommand(t *testing.T, when spec.G, it spec.S) { when("policy set to if-not-present in config", func() { it("lists if-not-present as pull policy", func() { cfg.PullPolicy = "if-not-present" - command = commands.ConfigPullPolicy(logger, cfg, configFile) + command = commands.ConfigPullPolicy(logger, cfg, configFile, imagePullPolicyHandler) command.SetArgs([]string{}) h.AssertNil(t, command.Execute()) @@ -96,12 +113,60 @@ func testConfigPullPolicyCommand(t *testing.T, when spec.G, it spec.S) { assert.Contains(outBuf.String(), "if-not-present") }) }) + + when("policy set to hourly in config", func() { + it("lists hourly as pull policy", func() { + cfg.PullPolicy = "hourly" + command = commands.ConfigPullPolicy(logger, cfg, configFile, imagePullPolicyHandler) + command.SetArgs([]string{}) + + h.AssertNil(t, command.Execute()) + + assert.Contains(outBuf.String(), "hourly") + }) + }) + + when("policy set to daily in config", func() { + it("lists daily as pull policy", func() { + cfg.PullPolicy = "daily" + command = commands.ConfigPullPolicy(logger, cfg, configFile, imagePullPolicyHandler) + command.SetArgs([]string{}) + + h.AssertNil(t, command.Execute()) + + assert.Contains(outBuf.String(), "daily") + }) + }) + + when("policy set to weekly in config", func() { + it("lists weekly as pull policy", func() { + cfg.PullPolicy = "weekly" + command = commands.ConfigPullPolicy(logger, cfg, configFile, imagePullPolicyHandler) + command.SetArgs([]string{}) + + h.AssertNil(t, command.Execute()) + + assert.Contains(outBuf.String(), "weekly") + }) + }) + + when("policy set to interval=1d2h30m in config", func() { + it("lists interval=1d2h30m as pull policy", func() { + cfg.PullPolicy = "interval=1d2h30m" + command = commands.ConfigPullPolicy(logger, cfg, configFile, imagePullPolicyHandler) + command.SetArgs([]string{}) + + h.AssertNil(t, command.Execute()) + + assert.Contains(outBuf.String(), "interval=1d2h30m") + }) + }) }) when("set", func() { when("policy provided is the same as configured pull policy", func() { it("provides a helpful message", func() { cfg.PullPolicy = "if-not-present" - command = commands.ConfigPullPolicy(logger, cfg, configFile) + command = commands.ConfigPullPolicy(logger, cfg, configFile, imagePullPolicyHandler) command.SetArgs([]string{"if-not-present"}) h.AssertNil(t, command.Execute()) @@ -110,7 +175,7 @@ func testConfigPullPolicyCommand(t *testing.T, when spec.G, it spec.S) { h.AssertEq(t, strings.TrimSpace(output), `Pull policy is already set to 'if-not-present'`) }) it("it does not change the configured policy", func() { - command = commands.ConfigPullPolicy(logger, cfg, configFile) + command = commands.ConfigPullPolicy(logger, cfg, configFile, imagePullPolicyHandler) command.SetArgs([]string{"never"}) assert.Succeeds(command.Execute()) @@ -118,7 +183,7 @@ func testConfigPullPolicyCommand(t *testing.T, when spec.G, it spec.S) { assert.Nil(err) assert.Equal(readCfg.PullPolicy, "never") - command = commands.ConfigPullPolicy(logger, cfg, configFile) + command = commands.ConfigPullPolicy(logger, cfg, configFile, imagePullPolicyHandler) command.SetArgs([]string{"never"}) assert.Succeeds(command.Execute()) @@ -152,18 +217,12 @@ func testConfigPullPolicyCommand(t *testing.T, when spec.G, it spec.S) { assert.Nil(err) assert.Equal(readCfg.PullPolicy, "never") }) - it("returns clear error if fails to write", func() { - assert.Nil(os.WriteFile(configFile, []byte("something"), 0001)) - command := commands.ConfigPullPolicy(logger, cfg, configFile) - command.SetArgs([]string{"if-not-present"}) - assert.ErrorContains(command.Execute(), "writing config to") - }) }) }) when("unset", func() { it("removes set policy and resets to default pull policy", func() { command.SetArgs([]string{"never"}) - command = commands.ConfigPullPolicy(logger, cfg, configFile) + command = commands.ConfigPullPolicy(logger, cfg, configFile, imagePullPolicyHandler) command.SetArgs([]string{"--unset"}) assert.Succeeds(command.Execute()) @@ -172,12 +231,6 @@ func testConfigPullPolicyCommand(t *testing.T, when spec.G, it spec.S) { assert.Nil(err) assert.Equal(cfg.PullPolicy, "") }) - it("returns clear error if fails to write", func() { - assert.Nil(os.WriteFile(configFile, []byte("something"), 0001)) - command := commands.ConfigPullPolicy(logger, config.Config{PullPolicy: "never"}, configFile) - command.SetArgs([]string{"--unset"}) - assert.ErrorContains(command.Execute(), "writing config to") - }) }) when("--unset and policy to set is provided", func() { it("errors", func() { diff --git a/internal/commands/config_test.go b/internal/commands/config_test.go index 15f1bae8ad..b678586d00 100644 --- a/internal/commands/config_test.go +++ b/internal/commands/config_test.go @@ -15,7 +15,9 @@ import ( "github.com/buildpacks/pack/internal/commands" "github.com/buildpacks/pack/internal/commands/testmocks" "github.com/buildpacks/pack/internal/config" + "github.com/buildpacks/pack/pkg/image" "github.com/buildpacks/pack/pkg/logging" + fetcher_mock "github.com/buildpacks/pack/pkg/testmocks" h "github.com/buildpacks/pack/testhelpers" ) @@ -27,12 +29,13 @@ func TestConfigCommand(t *testing.T) { func testConfigCommand(t *testing.T, when spec.G, it spec.S) { var ( - command *cobra.Command - logger logging.Logger - outBuf bytes.Buffer - tempPackHome string - configPath string - mockClient *testmocks.MockPackClient + command *cobra.Command + logger logging.Logger + outBuf bytes.Buffer + tempPackHome string + configPath string + mockClient *testmocks.MockPackClient + imagePullPolicyHandler image.ImagePullPolicyHandler ) it.Before(func() { @@ -42,11 +45,12 @@ func testConfigCommand(t *testing.T, when spec.G, it spec.S) { mockClient = testmocks.NewMockPackClient(mockController) logger = logging.NewLogWithWriters(&outBuf, &outBuf) + imagePullPolicyHandler = fetcher_mock.NewMockPullPolicyManager(logger) tempPackHome, err = os.MkdirTemp("", "pack-home") h.AssertNil(t, err) configPath = filepath.Join(tempPackHome, "config.toml") - command = commands.NewConfigCommand(logger, config.Config{Experimental: true}, configPath, mockClient) + command = commands.NewConfigCommand(logger, config.Config{Experimental: true}, configPath, mockClient, imagePullPolicyHandler) command.SetOut(logging.GetWriterForLevel(logger, logging.InfoLevel)) }) diff --git a/internal/commands/create_builder.go b/internal/commands/create_builder.go index 9999392ef6..fbca4a3b28 100644 --- a/internal/commands/create_builder.go +++ b/internal/commands/create_builder.go @@ -17,7 +17,7 @@ import ( // Deprecated: Use 'builder create' instead. // CreateBuilder creates a builder image, based on a builder config -func CreateBuilder(logger logging.Logger, cfg config.Config, pack PackClient) *cobra.Command { +func CreateBuilder(logger logging.Logger, cfg config.Config, pack PackClient, imagePullPolicyHandler image.ImagePullPolicyHandler) *cobra.Command { var flags BuilderCreateFlags cmd := &cobra.Command{ @@ -43,7 +43,7 @@ Creating a custom builder allows you to control what buildpacks are used and wha if stringPolicy == "" { stringPolicy = cfg.PullPolicy } - pullPolicy, err := image.ParsePullPolicy(stringPolicy) + pullPolicy, err := imagePullPolicyHandler.ParsePullPolicy(stringPolicy) if err != nil { return errors.Wrapf(err, "parsing pull policy %s", flags.Policy) } diff --git a/internal/commands/create_builder_test.go b/internal/commands/create_builder_test.go index f960ebb2c1..bc1faede57 100644 --- a/internal/commands/create_builder_test.go +++ b/internal/commands/create_builder_test.go @@ -15,7 +15,9 @@ import ( "github.com/buildpacks/pack/internal/commands" "github.com/buildpacks/pack/internal/commands/testmocks" "github.com/buildpacks/pack/internal/config" + "github.com/buildpacks/pack/pkg/image" "github.com/buildpacks/pack/pkg/logging" + fetcher_mock "github.com/buildpacks/pack/pkg/testmocks" h "github.com/buildpacks/pack/testhelpers" ) @@ -27,14 +29,15 @@ func TestCreateBuilderCommand(t *testing.T) { func testCreateBuilderCommand(t *testing.T, when spec.G, it spec.S) { var ( - command *cobra.Command - logger logging.Logger - outBuf bytes.Buffer - mockController *gomock.Controller - mockClient *testmocks.MockPackClient - tmpDir string - builderConfigPath string - cfg config.Config + command *cobra.Command + logger logging.Logger + outBuf bytes.Buffer + mockController *gomock.Controller + mockClient *testmocks.MockPackClient + tmpDir string + builderConfigPath string + cfg config.Config + imagePullPolicyHandler image.ImagePullPolicyHandler ) it.Before(func() { @@ -47,7 +50,8 @@ func testCreateBuilderCommand(t *testing.T, when spec.G, it spec.S) { mockController = gomock.NewController(t) mockClient = testmocks.NewMockPackClient(mockController) logger = logging.NewLogWithWriters(&outBuf, &outBuf) - command = commands.CreateBuilder(logger, cfg, mockClient) + imagePullPolicyHandler = fetcher_mock.NewMockPullPolicyManager(logger) + command = commands.CreateBuilder(logger, cfg, mockClient, imagePullPolicyHandler) }) it.After(func() { @@ -98,7 +102,7 @@ func testCreateBuilderCommand(t *testing.T, when spec.G, it spec.S) { when("configured pull policy is invalid", func() { it("returns error for when config set with unknown policy", func() { cfg = config.Config{PullPolicy: "unknown-policy"} - command = commands.BuilderCreate(logger, cfg, mockClient) + command = commands.BuilderCreate(logger, cfg, mockClient, imagePullPolicyHandler) command.SetArgs([]string{ "some/builder", "--config", builderConfigPath, diff --git a/internal/commands/extension.go b/internal/commands/extension.go index bc4ab36476..54f4840710 100644 --- a/internal/commands/extension.go +++ b/internal/commands/extension.go @@ -4,10 +4,11 @@ import ( "github.com/spf13/cobra" "github.com/buildpacks/pack/internal/config" + "github.com/buildpacks/pack/pkg/image" "github.com/buildpacks/pack/pkg/logging" ) -func NewExtensionCommand(logger logging.Logger, cfg config.Config, client PackClient, packageConfigReader PackageConfigReader) *cobra.Command { +func NewExtensionCommand(logger logging.Logger, cfg config.Config, client PackClient, packageConfigReader PackageConfigReader, imagePullPolicyHandler image.ImagePullPolicyHandler) *cobra.Command { cmd := &cobra.Command{ Use: "extension", Aliases: []string{"extensions"}, @@ -17,7 +18,7 @@ func NewExtensionCommand(logger logging.Logger, cfg config.Config, client PackCl cmd.AddCommand(ExtensionInspect(logger, cfg, client)) // client and packageConfigReader to be passed later on - cmd.AddCommand(ExtensionPackage(logger, cfg, client, packageConfigReader)) + cmd.AddCommand(ExtensionPackage(logger, cfg, client, packageConfigReader, imagePullPolicyHandler)) // client to be passed later on cmd.AddCommand(ExtensionNew(logger)) cmd.AddCommand(ExtensionPull(logger, cfg, client)) diff --git a/internal/commands/extension_package.go b/internal/commands/extension_package.go index 0415823e57..c622867d44 100644 --- a/internal/commands/extension_package.go +++ b/internal/commands/extension_package.go @@ -29,7 +29,7 @@ type ExtensionPackager interface { } // ExtensionPackage packages (a) extension(s) into OCI format, based on a package config -func ExtensionPackage(logger logging.Logger, cfg config.Config, packager ExtensionPackager, packageConfigReader PackageConfigReader) *cobra.Command { +func ExtensionPackage(logger logging.Logger, cfg config.Config, packager ExtensionPackager, packageConfigReader PackageConfigReader, imagePullPolicyHandler image.ImagePullPolicyHandler) *cobra.Command { var flags ExtensionPackageFlags cmd := &cobra.Command{ Use: "package --config ", @@ -45,7 +45,7 @@ func ExtensionPackage(logger logging.Logger, cfg config.Config, packager Extensi stringPolicy = cfg.PullPolicy } - pullPolicy, err := image.ParsePullPolicy(stringPolicy) + pullPolicy, err := imagePullPolicyHandler.ParsePullPolicy(stringPolicy) if err != nil { return errors.Wrap(err, "parsing pull policy") } diff --git a/internal/commands/extension_package_test.go b/internal/commands/extension_package_test.go index a46416e76e..9ed242fe01 100644 --- a/internal/commands/extension_package_test.go +++ b/internal/commands/extension_package_test.go @@ -18,6 +18,7 @@ import ( "github.com/buildpacks/pack/pkg/dist" "github.com/buildpacks/pack/pkg/image" "github.com/buildpacks/pack/pkg/logging" + fetcher_mock "github.com/buildpacks/pack/pkg/testmocks" h "github.com/buildpacks/pack/testhelpers" ) @@ -273,11 +274,13 @@ func packageExtensionCommand(ops ...packageExtensionCommandOption) *cobra.Comman configPath: "/path/to/some/file", } + imagePullPolicyHandler := fetcher_mock.NewMockPullPolicyManager(config.logger) + for _, op := range ops { op(config) } - cmd := commands.ExtensionPackage(config.logger, config.clientConfig, config.extensionPackager, config.packageConfigReader) + cmd := commands.ExtensionPackage(config.logger, config.clientConfig, config.extensionPackager, config.packageConfigReader, imagePullPolicyHandler) cmd.SetArgs([]string{config.imageName, "--config", config.configPath}) return cmd diff --git a/internal/commands/extension_test.go b/internal/commands/extension_test.go index 396d772905..fe2e66dd23 100644 --- a/internal/commands/extension_test.go +++ b/internal/commands/extension_test.go @@ -13,7 +13,9 @@ import ( "github.com/buildpacks/pack/internal/commands/fakes" "github.com/buildpacks/pack/internal/commands/testmocks" "github.com/buildpacks/pack/internal/config" + "github.com/buildpacks/pack/pkg/image" "github.com/buildpacks/pack/pkg/logging" + fetcher_mock "github.com/buildpacks/pack/pkg/testmocks" h "github.com/buildpacks/pack/testhelpers" ) @@ -23,17 +25,19 @@ func TestExtensionCommand(t *testing.T) { func testExtensionCommand(t *testing.T, when spec.G, it spec.S) { var ( - cmd *cobra.Command - logger logging.Logger - outBuf bytes.Buffer - mockClient *testmocks.MockPackClient + cmd *cobra.Command + logger logging.Logger + outBuf bytes.Buffer + mockClient *testmocks.MockPackClient + imagePullPolicyHandler image.ImagePullPolicyHandler ) it.Before(func() { logger = logging.NewLogWithWriters(&outBuf, &outBuf) + imagePullPolicyHandler = fetcher_mock.NewMockPullPolicyManager(logger) mockController := gomock.NewController(t) mockClient = testmocks.NewMockPackClient(mockController) - cmd = commands.NewExtensionCommand(logger, config.Config{}, mockClient, fakes.NewFakePackageConfigReader()) + cmd = commands.NewExtensionCommand(logger, config.Config{}, mockClient, fakes.NewFakePackageConfigReader(), imagePullPolicyHandler) cmd.SetOut(logging.GetWriterForLevel(logger, logging.InfoLevel)) }) diff --git a/internal/commands/package_buildpack.go b/internal/commands/package_buildpack.go index 1bd17c4561..8757144ec6 100644 --- a/internal/commands/package_buildpack.go +++ b/internal/commands/package_buildpack.go @@ -16,7 +16,7 @@ import ( // Deprecated: use BuildpackPackage instead // PackageBuildpack packages (a) buildpack(s) into OCI format, based on a package config -func PackageBuildpack(logger logging.Logger, cfg config.Config, packager BuildpackPackager, packageConfigReader PackageConfigReader) *cobra.Command { +func PackageBuildpack(logger logging.Logger, cfg config.Config, packager BuildpackPackager, packageConfigReader PackageConfigReader, imagePullPolicyHandler image.ImagePullPolicyHandler) *cobra.Command { var flags BuildpackPackageFlags cmd := &cobra.Command{ @@ -41,7 +41,7 @@ func PackageBuildpack(logger logging.Logger, cfg config.Config, packager Buildpa if stringPolicy == "" { stringPolicy = cfg.PullPolicy } - pullPolicy, err := image.ParsePullPolicy(stringPolicy) + pullPolicy, err := imagePullPolicyHandler.ParsePullPolicy(stringPolicy) if err != nil { return errors.Wrap(err, "parsing pull policy") } diff --git a/internal/commands/package_buildpack_test.go b/internal/commands/package_buildpack_test.go index 585d477edb..dfaeebe1d5 100644 --- a/internal/commands/package_buildpack_test.go +++ b/internal/commands/package_buildpack_test.go @@ -18,6 +18,7 @@ import ( "github.com/buildpacks/pack/pkg/dist" "github.com/buildpacks/pack/pkg/image" "github.com/buildpacks/pack/pkg/logging" + fetcher_mock "github.com/buildpacks/pack/pkg/testmocks" h "github.com/buildpacks/pack/testhelpers" ) @@ -131,11 +132,12 @@ func testPackageBuildpackCommand(t *testing.T, when spec.G, it spec.S) { }) it("takes precedence over a configured pull policy", func() { logger := logging.NewLogWithWriters(&bytes.Buffer{}, &bytes.Buffer{}) + imagePullPolicyHandler := fetcher_mock.NewMockPullPolicyManager(logger) configReader := fakes.NewFakePackageConfigReader() buildpackPackager := &fakes.FakeBuildpackPackager{} clientConfig := config.Config{PullPolicy: "if-not-present"} - command := commands.PackageBuildpack(logger, clientConfig, buildpackPackager, configReader) + command := commands.PackageBuildpack(logger, clientConfig, buildpackPackager, configReader, imagePullPolicyHandler) command.SetArgs([]string{ "some-image-name", "--config", "/path/to/some/file", @@ -153,11 +155,12 @@ func testPackageBuildpackCommand(t *testing.T, when spec.G, it spec.S) { when("configured pull policy", func() { it("uses the configured pull policy", func() { logger := logging.NewLogWithWriters(&bytes.Buffer{}, &bytes.Buffer{}) + imagePullChecker := fetcher_mock.NewMockPullPolicyManager(logger) configReader := fakes.NewFakePackageConfigReader() buildpackPackager := &fakes.FakeBuildpackPackager{} clientConfig := config.Config{PullPolicy: "never"} - command := commands.PackageBuildpack(logger, clientConfig, buildpackPackager, configReader) + command := commands.PackageBuildpack(logger, clientConfig, buildpackPackager, configReader, imagePullChecker) command.SetArgs([]string{ "some-image-name", "--config", "/path/to/some/file", @@ -177,11 +180,12 @@ func testPackageBuildpackCommand(t *testing.T, when spec.G, it spec.S) { when("both --publish and --pull-policy never flags are specified", func() { it("errors with a descriptive message", func() { logger := logging.NewLogWithWriters(&bytes.Buffer{}, &bytes.Buffer{}) + imagePullChecker := fetcher_mock.NewMockPullPolicyManager(logger) configReader := fakes.NewFakePackageConfigReader() buildpackPackager := &fakes.FakeBuildpackPackager{} clientConfig := config.Config{} - command := commands.PackageBuildpack(logger, clientConfig, buildpackPackager, configReader) + command := commands.PackageBuildpack(logger, clientConfig, buildpackPackager, configReader, imagePullChecker) command.SetArgs([]string{ "some-image-name", "--config", "/path/to/some/file", @@ -226,7 +230,9 @@ func testPackageBuildpackCommand(t *testing.T, when spec.G, it spec.S) { configPath: "/path/to/some/file", } - cmd := commands.PackageBuildpack(config.logger, config.clientConfig, config.buildpackPackager, config.packageConfigReader) + imagePullPolicyHandler := fetcher_mock.NewMockPullPolicyManager(config.logger) + + cmd := commands.PackageBuildpack(config.logger, config.clientConfig, config.buildpackPackager, config.packageConfigReader, imagePullPolicyHandler) cmd.SetArgs([]string{config.imageName, "--package-config", config.configPath}) err := cmd.Execute() @@ -244,7 +250,9 @@ func testPackageBuildpackCommand(t *testing.T, when spec.G, it spec.S) { imageName: "some-image-name", } - cmd := commands.PackageBuildpack(config.logger, config.clientConfig, config.buildpackPackager, config.packageConfigReader) + imagePullChecker := fetcher_mock.NewMockPullPolicyManager(config.logger) + + cmd := commands.PackageBuildpack(config.logger, config.clientConfig, config.buildpackPackager, config.packageConfigReader, imagePullChecker) cmd.SetArgs([]string{config.imageName}) err := cmd.Execute() @@ -258,11 +266,12 @@ func testPackageBuildpackCommand(t *testing.T, when spec.G, it spec.S) { when("--pull-policy unknown-policy", func() { it("fails to run", func() { logger := logging.NewLogWithWriters(&bytes.Buffer{}, &bytes.Buffer{}) + imagePullChecker := fetcher_mock.NewMockPullPolicyManager(logger) configReader := fakes.NewFakePackageConfigReader() buildpackPackager := &fakes.FakeBuildpackPackager{} clientConfig := config.Config{} - command := commands.PackageBuildpack(logger, clientConfig, buildpackPackager, configReader) + command := commands.PackageBuildpack(logger, clientConfig, buildpackPackager, configReader, imagePullChecker) command.SetArgs([]string{ "some-image-name", "--config", "/path/to/some/file", @@ -287,11 +296,13 @@ func packageBuildpackCommand(ops ...packageCommandOption) *cobra.Command { configPath: "/path/to/some/file", } + imagePullChecker := fetcher_mock.NewMockPullPolicyManager(config.logger) + for _, op := range ops { op(config) } - cmd := commands.PackageBuildpack(config.logger, config.clientConfig, config.buildpackPackager, config.packageConfigReader) + cmd := commands.PackageBuildpack(config.logger, config.clientConfig, config.buildpackPackager, config.packageConfigReader, imagePullChecker) cmd.SetArgs([]string{config.imageName, "--config", config.configPath}) return cmd diff --git a/internal/commands/rebase.go b/internal/commands/rebase.go index d0622f9db7..ea61ffff21 100644 --- a/internal/commands/rebase.go +++ b/internal/commands/rebase.go @@ -13,7 +13,7 @@ import ( "github.com/buildpacks/pack/pkg/logging" ) -func Rebase(logger logging.Logger, cfg config.Config, pack PackClient) *cobra.Command { +func Rebase(logger logging.Logger, cfg config.Config, pack PackClient, imagePullPolicyHandler image.ImagePullPolicyHandler) *cobra.Command { var opts client.RebaseOptions var policy string @@ -33,7 +33,7 @@ func Rebase(logger logging.Logger, cfg config.Config, pack PackClient) *cobra.Co if stringPolicy == "" { stringPolicy = cfg.PullPolicy } - opts.PullPolicy, err = image.ParsePullPolicy(stringPolicy) + opts.PullPolicy, err = imagePullPolicyHandler.ParsePullPolicy(stringPolicy) if err != nil { return errors.Wrapf(err, "parsing pull policy %s", stringPolicy) } diff --git a/internal/commands/rebase_test.go b/internal/commands/rebase_test.go index a3bdf05f5e..caf5821b49 100644 --- a/internal/commands/rebase_test.go +++ b/internal/commands/rebase_test.go @@ -18,6 +18,7 @@ import ( "github.com/buildpacks/pack/internal/commands/testmocks" "github.com/buildpacks/pack/internal/config" "github.com/buildpacks/pack/pkg/logging" + fetcher_mock "github.com/buildpacks/pack/pkg/testmocks" h "github.com/buildpacks/pack/testhelpers" ) @@ -30,21 +31,23 @@ func TestRebaseCommand(t *testing.T) { func testRebaseCommand(t *testing.T, when spec.G, it spec.S) { var ( - command *cobra.Command - logger logging.Logger - outBuf bytes.Buffer - mockController *gomock.Controller - mockClient *testmocks.MockPackClient - cfg config.Config + command *cobra.Command + logger logging.Logger + outBuf bytes.Buffer + mockController *gomock.Controller + mockClient *testmocks.MockPackClient + cfg config.Config + imagePullPolicyHandler image.ImagePullPolicyHandler ) it.Before(func() { logger = logging.NewLogWithWriters(&outBuf, &outBuf) + imagePullPolicyHandler = fetcher_mock.NewMockPullPolicyManager(logger) cfg = config.Config{} mockController = gomock.NewController(t) mockClient = testmocks.NewMockPackClient(mockController) - command = commands.Rebase(logger, cfg, mockClient) + command = commands.Rebase(logger, cfg, mockClient, imagePullPolicyHandler) }) when("#RebaseCommand", func() { @@ -69,7 +72,7 @@ func testRebaseCommand(t *testing.T, when spec.G, it spec.S) { Image: runImage, Mirrors: []string{testMirror1, testMirror2}, }} - command = commands.Rebase(logger, cfg, mockClient) + command = commands.Rebase(logger, cfg, mockClient, imagePullPolicyHandler) repoName = "test/repo-image" opts = client.RebaseOptions{ @@ -109,7 +112,7 @@ func testRebaseCommand(t *testing.T, when spec.G, it spec.S) { Return(nil) cfg.PullPolicy = "if-not-present" - command = commands.Rebase(logger, cfg, mockClient) + command = commands.Rebase(logger, cfg, mockClient, imagePullPolicyHandler) command.SetArgs([]string{repoName, "--pull-policy", "never"}) h.AssertNil(t, command.Execute()) @@ -142,7 +145,7 @@ func testRebaseCommand(t *testing.T, when spec.G, it spec.S) { Return(nil) cfg.PullPolicy = "if-not-present" - command = commands.Rebase(logger, cfg, mockClient) + command = commands.Rebase(logger, cfg, mockClient, imagePullPolicyHandler) command.SetArgs([]string{repoName}) h.AssertNil(t, command.Execute()) @@ -152,7 +155,7 @@ func testRebaseCommand(t *testing.T, when spec.G, it spec.S) { it("passes it through", func() { opts.Force = true mockClient.EXPECT().Rebase(gomock.Any(), opts).Return(nil) - command = commands.Rebase(logger, cfg, mockClient) + command = commands.Rebase(logger, cfg, mockClient, imagePullPolicyHandler) command.SetArgs([]string{repoName, "--force"}) h.AssertNil(t, command.Execute()) }) @@ -170,7 +173,7 @@ func testRebaseCommand(t *testing.T, when spec.G, it spec.S) { Image: runImage, Mirrors: []string{testMirror1, testMirror2}, }} - command = commands.Rebase(logger, cfg, mockClient) + command = commands.Rebase(logger, cfg, mockClient, imagePullPolicyHandler) repoName = "test/repo-image" previousImage := "example.com/previous-image:tag" // Example of previous image with tag diff --git a/internal/commands/testdata/example_image.json b/internal/commands/testdata/example_image.json new file mode 100644 index 0000000000..383c664a7e --- /dev/null +++ b/internal/commands/testdata/example_image.json @@ -0,0 +1,13 @@ +{ + "interval": { + "pulling_interval": "1d", + "pruning_interval": "5d", + "last_prune": "2024-03-13T11:31:58+05:30" + }, + "image": { + "ImageIDtoTIME": { + "gcr.io/buildpacks/builder:v1": "2024-03-13T11:32:00+05:30", + "gcr.io/buildpacks/gcp/run:v1": "2024-03-13T11:32:03+05:30" + } + } +} \ No newline at end of file diff --git a/pkg/client/client.go b/pkg/client/client.go index d034851fc6..4e7996ceed 100644 --- a/pkg/client/client.go +++ b/pkg/client/client.go @@ -94,13 +94,14 @@ type Client struct { logger logging.Logger docker DockerClient - keychain authn.Keychain - imageFactory ImageFactory - imageFetcher ImageFetcher - accessChecker AccessChecker - downloader BlobDownloader - lifecycleExecutor LifecycleExecutor - buildpackDownloader BuildpackDownloader + keychain authn.Keychain + imageFactory ImageFactory + imageFetcher ImageFetcher + imagePullPolicyHandler image.ImagePullPolicyHandler + accessChecker AccessChecker + downloader BlobDownloader + lifecycleExecutor LifecycleExecutor + buildpackDownloader BuildpackDownloader experimental bool registryMirrors map[string]string @@ -133,6 +134,14 @@ func WithFetcher(f ImageFetcher) Option { } } +// WithImagePullChecker supply your own ImagePullChecker. +// An ImagePullChecker provides functionality to check and manage image pulling intervals. +func WithImagePullChecker(i image.ImagePullPolicyHandler) Option { + return func(c *Client) { + c.imagePullPolicyHandler = i + } +} + // WithAccessChecker supply your own AccessChecker. // A AccessChecker returns true if an image is accessible for reading. func WithAccessChecker(f AccessChecker) Option { @@ -231,7 +240,7 @@ func NewClient(opts ...Option) (*Client, error) { } if client.imageFetcher == nil { - client.imageFetcher = image.NewFetcher(client.logger, client.docker, image.WithRegistryMirrors(client.registryMirrors), image.WithKeychain(client.keychain)) + client.imageFetcher = image.NewFetcher(client.logger, client.docker, client.imagePullPolicyHandler, image.WithRegistryMirrors(client.registryMirrors), image.WithKeychain(client.keychain)) } if client.imageFactory == nil { diff --git a/pkg/image/fetcher.go b/pkg/image/fetcher.go index 60548fcc1d..339824e261 100644 --- a/pkg/image/fetcher.go +++ b/pkg/image/fetcher.go @@ -6,6 +6,7 @@ import ( "encoding/json" "io" "strings" + "time" "github.com/buildpacks/imgutil/layout" "github.com/buildpacks/imgutil/layout/sparse" @@ -35,6 +36,24 @@ type LayoutOption struct { Sparse bool } +type ImagePullPolicyHandler interface { + ParsePullPolicy(policy string) (PullPolicy, error) + CheckImagePullInterval(imageID string, path string, pullPolicy PullPolicy) (bool, error) + PruneOldImages(docker DockerClient) error + UpdateImagePullRecord(path string, imageID string, timestamp string) error + GetDuration(p PullPolicy) time.Duration + Read(path string) (*ImageJSON, error) + Write(imageJSON *ImageJSON, path string) error +} + +func intervalPolicy(options FetchOptions) bool { + return options.PullPolicy == PullHourly || options.PullPolicy == PullDaily || options.PullPolicy == PullWeekly +} + +func NewPullPolicyManager(logger logging.Logger) ImagePullPolicyHandler { + return &ImagePullPolicyManager{Logger: logger} +} + // WithRegistryMirrors supply your own mirrors for registry. func WithRegistryMirrors(registryMirrors map[string]string) FetcherOption { return func(c *Fetcher) { @@ -54,10 +73,11 @@ type DockerClient interface { } type Fetcher struct { - docker DockerClient - logger logging.Logger - registryMirrors map[string]string - keychain authn.Keychain + docker DockerClient + logger logging.Logger + registryMirrors map[string]string + keychain authn.Keychain + imagePullChecker ImagePullPolicyHandler } type FetchOptions struct { @@ -67,11 +87,12 @@ type FetchOptions struct { LayoutOption LayoutOption } -func NewFetcher(logger logging.Logger, docker DockerClient, opts ...FetcherOption) *Fetcher { +func NewFetcher(logger logging.Logger, docker DockerClient, imagePullChecker ImagePullPolicyHandler, opts ...FetcherOption) *Fetcher { fetcher := &Fetcher{ - logger: logger, - docker: docker, - keychain: authn.DefaultKeychain, + logger: logger, + docker: docker, + keychain: authn.DefaultKeychain, + imagePullChecker: imagePullChecker, } for _, opt := range opts { @@ -97,6 +118,11 @@ func (f *Fetcher) Fetch(ctx context.Context, name string, options FetchOptions) return f.fetchRemoteImage(name) } + imageJSONpath, err := DefaultImageJSONPath() + if err != nil { + return nil, err + } + switch options.PullPolicy { case PullNever: img, err := f.fetchDaemonImage(name) @@ -106,6 +132,28 @@ func (f *Fetcher) Fetch(ctx context.Context, name string, options FetchOptions) if err == nil || !errors.Is(err, ErrNotFound) { return img, err } + case PullDaily, PullHourly, PullWeekly: + pull, err := f.imagePullChecker.CheckImagePullInterval(name, imageJSONpath, options.PullPolicy) + if err != nil { + f.logger.Warnf("failed to check pulling interval for image %s, %s", name, err) + } + if !pull { + img, err := f.fetchDaemonImage(name) + if errors.Is(err, ErrNotFound) { + imageJSON, _ := f.imagePullChecker.Read(imageJSONpath) + delete(imageJSON.Image.ImageIDtoTIME, name) + + if err := f.imagePullChecker.Write(imageJSON, imageJSONpath); err != nil { + f.logger.Errorf("failed to write updated image.json %s", err) + } + } + return img, err + } + + err = f.imagePullChecker.PruneOldImages(f.docker) + if err != nil { + f.logger.Warnf("Failed to prune images, %s", err) + } } f.logger.Debugf("Pulling image %s", style.Symbol(name)) @@ -120,7 +168,19 @@ func (f *Fetcher) Fetch(ctx context.Context, name string, options FetchOptions) return nil, err } - return f.fetchDaemonImage(name) + image, err := f.fetchDaemonImage(name) + if err != nil { + return nil, err + } + + if intervalPolicy(options) { + // Update image pull record in the JSON file + if err := f.imagePullChecker.UpdateImagePullRecord(imageJSONpath, name, time.Now().Format(time.RFC3339)); err != nil { + return nil, err + } + } + + return image, nil } func (f *Fetcher) fetchDaemonImage(name string) (imgutil.Image, error) { @@ -246,3 +306,46 @@ func (w *colorizedWriter) Write(p []byte) (n int, err error) { } return w.writer.Write([]byte(msg)) } + +func (i *ImagePullPolicyManager) UpdateImagePullRecord(path string, imageID string, timestamp string) error { + imageJSON, err := i.Read(path) + if err != nil { + return err + } + + if imageJSON.Image.ImageIDtoTIME == nil { + imageJSON.Image.ImageIDtoTIME = make(map[string]string) + } + imageJSON.Image.ImageIDtoTIME[imageID] = timestamp + + err = i.Write(imageJSON, path) + if err != nil { + return err + } + + return nil +} + +func (i *ImagePullPolicyManager) CheckImagePullInterval(imageID string, path string, pullPolicy PullPolicy) (bool, error) { + imageJSON, err := i.Read(path) + if err != nil { + return false, err + } + + timestamp, ok := imageJSON.Image.ImageIDtoTIME[imageID] + if !ok { + // If the image ID is not present, return true + return true, nil + } + + imageTimestamp, err := time.Parse(time.RFC3339, timestamp) + if err != nil { + return false, errors.Wrap(err, "failed to parse image timestamp from JSON") + } + + duration := i.GetDuration(pullPolicy) + + timeThreshold := time.Now().Add(-duration) + + return imageTimestamp.Before(timeThreshold), nil +} diff --git a/pkg/image/fetcher_test.go b/pkg/image/fetcher_test.go index 3b119e15f3..bb3a0b621f 100644 --- a/pkg/image/fetcher_test.go +++ b/pkg/image/fetcher_test.go @@ -8,8 +8,10 @@ import ( "path/filepath" "runtime" "testing" + "time" "github.com/buildpacks/imgutil" + "github.com/golang/mock/gomock" "github.com/buildpacks/imgutil/local" "github.com/buildpacks/imgutil/remote" @@ -21,11 +23,17 @@ import ( "github.com/buildpacks/pack/pkg/image" "github.com/buildpacks/pack/pkg/logging" + "github.com/buildpacks/pack/pkg/testmocks" h "github.com/buildpacks/pack/testhelpers" ) var docker client.CommonAPIClient +var logger logging.Logger var registryConfig *h.TestRegistryConfig +var imageJSON *image.ImageJSON +var mockController *gomock.Controller + +var mockImagePullPolicyHandler = testmocks.NewMockImagePullPolicyHandler(mockController) func TestFetcher(t *testing.T) { color.Disable(true) @@ -57,7 +65,8 @@ func testFetcher(t *testing.T, when spec.G, it spec.S) { it.Before(func() { repo = "some-org/" + h.RandString(10) repoName = registryConfig.RepoName(repo) - imageFetcher = image.NewFetcher(logging.NewLogWithWriters(&outBuf, &outBuf), docker) + logger = logging.NewLogWithWriters(&outBuf, &outBuf) + imageFetcher = image.NewFetcher(logger, docker, mockImagePullPolicyHandler) info, err := docker.Info(context.TODO()) h.AssertNil(t, err) @@ -168,7 +177,10 @@ func testFetcher(t *testing.T, when spec.G, it spec.S) { var outCons *color.Console outCons, output = h.MockWriterAndOutput() logger = logging.NewLogWithWriters(outCons, outCons) - imageFetcher = image.NewFetcher(logger, docker) + mockImagePullPolicyHandler.MockCheckImagePullInterval = func(imageID string, path string) (bool, error) { + return true, nil + } + imageFetcher = image.NewFetcher(logger, docker, mockImagePullPolicyHandler) }) it.After(func() { @@ -342,6 +354,303 @@ func testFetcher(t *testing.T, when spec.G, it spec.S) { }) }) }) + + when("PullWithInterval, PullHourly, PullDaily, PullWeekly", func() { + when("there is a remote image", func() { + var ( + label = "label" + remoteImgLabel string + ) + + it.Before(func() { + // Instantiate a pull-able local image + // as opposed to a remote image so that the image + // is created with the OS of the docker daemon + remoteImg, err := local.NewImage(repoName, docker) + h.AssertNil(t, err) + defer h.DockerRmi(docker, repoName) + + h.AssertNil(t, remoteImg.SetLabel(label, "1")) + h.AssertNil(t, remoteImg.Save()) + + h.AssertNil(t, h.PushImage(docker, remoteImg.Name(), registryConfig)) + + remoteImgLabel, err = remoteImg.Label(label) + h.AssertNil(t, err) + }) + + it.After(func() { + h.DockerRmi(docker, repoName) + }) + + when("there is no local image and CheckImagePullInterval returns true", func() { + it.Before(func() { + mockImagePullPolicyHandler.MockCheckImagePullInterval = func(imageID string, path string) (bool, error) { + return true, nil + } + imageFetcher = image.NewFetcher(logging.NewLogWithWriters(&outBuf, &outBuf), docker, mockImagePullPolicyHandler) + }) + + it.After(func() { + mockImagePullPolicyHandler.MockCheckImagePullInterval = nil + }) + + it("pulls the remote image and returns it", func() { + fetchedImg, err := imageFetcher.Fetch(context.TODO(), repoName, image.FetchOptions{Daemon: true, PullPolicy: image.PullWeekly}) + h.AssertNil(t, err) + + fetchedImgLabel, err := fetchedImg.Label(label) + h.AssertNil(t, err) + h.AssertEq(t, fetchedImgLabel, remoteImgLabel) + }) + }) + + when("there is no local image and CheckImagePullInterval returns false", func() { + it.Before(func() { + mockImagePullPolicyHandler.MockCheckImagePullInterval = func(imageID string, path string) (bool, error) { + return false, nil + } + + imageJSON = &image.ImageJSON{ + Interval: &image.Interval{ + PullingInterval: "7d", + PruningInterval: "7d", + LastPrune: "2023-01-01T00:00:00Z", + }, + Image: &image.ImageData{ + ImageIDtoTIME: map[string]string{ + repoName: "2023-01-01T00:00:00Z", + }, + }, + } + + imageFetcher = image.NewFetcher(logging.NewLogWithWriters(&outBuf, &outBuf), docker, mockImagePullPolicyHandler) + + mockImagePullPolicyHandler.MockRead = func(path string) (*image.ImageJSON, error) { + return imageJSON, nil + } + }) + + it.After(func() { + mockImagePullPolicyHandler.MockCheckImagePullInterval = nil + mockImagePullPolicyHandler.MockRead = nil + }) + + it("returns an error and deletes the image record", func() { + _, err := imageFetcher.Fetch(context.TODO(), repoName, image.FetchOptions{Daemon: true, PullPolicy: image.PullWeekly}) + h.AssertError(t, err, fmt.Sprintf("image '%s' does not exist on the daemon", repoName)) + imageJSON, err = mockImagePullPolicyHandler.Read("") + h.AssertNil(t, err) + _, exists := imageJSON.Image.ImageIDtoTIME[repoName] + h.AssertEq(t, exists, false) + }) + }) + + when("there is a local image and CheckImagePullInterval returns true", func() { + it.Before(func() { + img, err := local.NewImage(repoName, docker) + h.AssertNil(t, err) + + h.AssertNil(t, img.Save()) + mockImagePullPolicyHandler.MockCheckImagePullInterval = func(imageID string, path string) (bool, error) { + return true, nil + } + + imageJSON = &image.ImageJSON{ + Interval: &image.Interval{ + PullingInterval: "7d", + PruningInterval: "7d", + LastPrune: "2023-01-01T00:00:00Z", + }, + Image: &image.ImageData{ + ImageIDtoTIME: map[string]string{ + repoName: "2023-01-01T00:00:00Z", + }, + }, + } + + mockImagePullPolicyHandler.MockRead = func(path string) (*image.ImageJSON, error) { + return imageJSON, nil + } + + mockImagePullPolicyHandler.MockUpdateImagePullRecord = func(path string, imageID string, timestamp string) error { + imageJSON, _ = mockImagePullPolicyHandler.Read("") + imageJSON.Image.ImageIDtoTIME[repoName] = timestamp + return nil + } + + imageFetcher = image.NewFetcher(logging.NewLogWithWriters(&outBuf, &outBuf), docker, mockImagePullPolicyHandler) + }) + + it.After(func() { + mockImagePullPolicyHandler.MockCheckImagePullInterval = nil + mockImagePullPolicyHandler.MockRead = nil + mockImagePullPolicyHandler.MockUpdateImagePullRecord = nil + h.DockerRmi(docker, repoName) + }) + + it("pulls the remote image and returns it", func() { + beforeFetch, _ := time.Parse(time.RFC3339, imageJSON.Image.ImageIDtoTIME[repoName]) + fmt.Printf("before fetch: %v\n", imageJSON.Image.ImageIDtoTIME[repoName]) + _, err := imageFetcher.Fetch(context.TODO(), repoName, image.FetchOptions{Daemon: true, PullPolicy: image.PullWeekly}) + h.AssertNil(t, err) + + imageJSON, _ = mockImagePullPolicyHandler.Read("") + + afterFetch, _ := time.Parse(time.RFC3339, imageJSON.Image.ImageIDtoTIME[repoName]) + fmt.Printf("after fetch: %v\n", imageJSON.Image.ImageIDtoTIME[repoName]) + diff := beforeFetch.Before(afterFetch) + h.AssertEq(t, diff, true) + }) + }) + + when("there is a local image and CheckImagePullInterval returns false", func() { + it.Before(func() { + localImg, err := local.NewImage(repoName, docker) + h.AssertNil(t, err) + h.AssertNil(t, localImg.SetLabel(label, "2")) + + h.AssertNil(t, localImg.Save()) + mockImagePullPolicyHandler.MockCheckImagePullInterval = func(imageID string, path string) (bool, error) { + return true, nil + } + imageFetcher = image.NewFetcher(logging.NewLogWithWriters(&outBuf, &outBuf), docker, mockImagePullPolicyHandler) + }) + + it.After(func() { + mockImagePullPolicyHandler.MockCheckImagePullInterval = nil + h.DockerRmi(docker, repoName) + }) + + it("returns the local image", func() { + fetchedImg, err := imageFetcher.Fetch(context.TODO(), repoName, image.FetchOptions{Daemon: true, PullPolicy: image.PullIfNotPresent}) + h.AssertNil(t, err) + + fetchedImgLabel, err := fetchedImg.Label(label) + h.AssertNil(t, err) + h.AssertEq(t, fetchedImgLabel, "2") + }) + }) + }) + + when("there is no remote image", func() { + var label string + + when("there is no local image and CheckImagePullInterval returns true", func() { + it.Before(func() { + mockImagePullPolicyHandler.MockCheckImagePullInterval = func(imageID string, path string) (bool, error) { + return true, nil + } + imageFetcher = image.NewFetcher(logging.NewLogWithWriters(&outBuf, &outBuf), docker, mockImagePullPolicyHandler) + }) + + it.After(func() { + mockImagePullPolicyHandler.MockCheckImagePullInterval = nil + }) + + it("try to pull the remote image and returns error", func() { + _, err := imageFetcher.Fetch(context.TODO(), repoName, image.FetchOptions{Daemon: true, PullPolicy: image.PullWeekly}) + h.AssertNotNil(t, err) + }) + }) + + when("there is no local image and CheckImagePullInterval returns false", func() { + it.Before(func() { + mockImagePullPolicyHandler.MockCheckImagePullInterval = func(imageID string, path string) (bool, error) { + return false, nil + } + + imageJSON = &image.ImageJSON{ + Interval: &image.Interval{ + PullingInterval: "7d", + PruningInterval: "7d", + LastPrune: "2023-01-01T00:00:00Z", + }, + Image: &image.ImageData{ + ImageIDtoTIME: map[string]string{ + repoName: "2023-01-01T00:00:00Z", + }, + }, + } + + imageFetcher = image.NewFetcher(logging.NewLogWithWriters(&outBuf, &outBuf), docker, mockImagePullPolicyHandler) + mockImagePullPolicyHandler.MockRead = func(path string) (*image.ImageJSON, error) { + return imageJSON, nil + } + }) + + it.After(func() { + mockImagePullPolicyHandler.MockCheckImagePullInterval = nil + mockImagePullPolicyHandler.MockRead = nil + }) + + it("returns an error and deletes the image record", func() { + _, err := imageFetcher.Fetch(context.TODO(), repoName, image.FetchOptions{Daemon: true, PullPolicy: image.PullWeekly}) + h.AssertError(t, err, fmt.Sprintf("image '%s' does not exist on the daemon", repoName)) + imageJSON, err = mockImagePullPolicyHandler.Read("") + h.AssertNil(t, err) + _, exists := imageJSON.Image.ImageIDtoTIME[repoName] + h.AssertEq(t, exists, false) + }) + }) + + when("there is a local image and CheckImagePullInterval returns true", func() { + it.Before(func() { + localImg, err := local.NewImage(repoName, docker) + h.AssertNil(t, err) + h.AssertNil(t, localImg.SetLabel(label, "2")) + + h.AssertNil(t, localImg.Save()) + mockImagePullPolicyHandler.MockCheckImagePullInterval = func(imageID string, path string) (bool, error) { + return true, nil + } + imageFetcher = image.NewFetcher(logging.NewLogWithWriters(&outBuf, &outBuf), docker, mockImagePullPolicyHandler) + }) + + it.After(func() { + mockImagePullPolicyHandler.MockCheckImagePullInterval = nil + h.DockerRmi(docker, repoName) + }) + + it("try to pull the remote image and returns local image", func() { + fetchedImg, err := imageFetcher.Fetch(context.TODO(), repoName, image.FetchOptions{Daemon: true, PullPolicy: image.PullIfNotPresent}) + h.AssertNil(t, err) + + fetchedImgLabel, err := fetchedImg.Label(label) + h.AssertNil(t, err) + h.AssertEq(t, fetchedImgLabel, "2") + }) + }) + + when("there is a local image and CheckImagePullInterval returns false", func() { + it.Before(func() { + localImg, err := local.NewImage(repoName, docker) + h.AssertNil(t, err) + h.AssertNil(t, localImg.SetLabel(label, "2")) + + h.AssertNil(t, localImg.Save()) + mockImagePullPolicyHandler.MockCheckImagePullInterval = func(imageID string, path string) (bool, error) { + return true, nil + } + imageFetcher = image.NewFetcher(logging.NewLogWithWriters(&outBuf, &outBuf), docker, mockImagePullPolicyHandler) + }) + + it.After(func() { + mockImagePullPolicyHandler.MockCheckImagePullInterval = nil + h.DockerRmi(docker, repoName) + }) + + it("returns the local image", func() { + fetchedImg, err := imageFetcher.Fetch(context.TODO(), repoName, image.FetchOptions{Daemon: true, PullPolicy: image.PullIfNotPresent}) + h.AssertNil(t, err) + + fetchedImgLabel, err := fetchedImg.Label(label) + h.AssertNil(t, err) + h.AssertEq(t, fetchedImgLabel, "2") + }) + }) + }) + }) }) when("layout option is provided", func() { diff --git a/pkg/image/pull_policy.go b/pkg/image/pull_policy.go index f08678ac5a..eeb0aede0a 100644 --- a/pkg/image/pull_policy.go +++ b/pkg/image/pull_policy.go @@ -1,25 +1,73 @@ package image import ( + "encoding/json" + "os" + "path/filepath" + "time" + "github.com/pkg/errors" + + "github.com/buildpacks/imgutil/local" + + "github.com/buildpacks/pack/internal/config" + "github.com/buildpacks/pack/pkg/logging" ) // PullPolicy defines a policy for how to manage images type PullPolicy int const ( - // PullAlways images, even if they are present + PRUNE_TIME = 7 * 24 * 60 * time.Minute + HOURLY = 1 * 60 * time.Minute + DAILY = 1 * 24 * 60 * time.Minute + WEEKLY = 7 * 24 * 60 * time.Minute +) + +type ImagePullPolicyManager struct { + Logger logging.Logger +} + +const ( + // Always pull images, even if they are present PullAlways PullPolicy = iota - // PullNever images, even if they are not present + // Pull images hourly + PullHourly + // Pull images daily + PullDaily + // Pull images weekly + PullWeekly + // Never pull images, even if they are not present PullNever // PullIfNotPresent pulls images if they aren't present PullIfNotPresent ) -var nameMap = map[string]PullPolicy{"always": PullAlways, "never": PullNever, "if-not-present": PullIfNotPresent, "": PullAlways} +type Interval struct { + LastPrune string `json:"last_prune"` +} + +type ImageData struct { + ImageIDtoTIME map[string]string +} -// ParsePullPolicy from string -func ParsePullPolicy(policy string) (PullPolicy, error) { +type ImageJSON struct { + Interval *Interval `json:"interval"` + Image *ImageData `json:"image"` +} + +func DefaultImageJSONPath() (string, error) { + home, err := config.PackHome() + if err != nil { + return "", errors.Wrap(err, "getting pack home") + } + return filepath.Join(home, "image.json"), nil +} + +var nameMap = map[string]PullPolicy{"always": PullAlways, "hourly": PullHourly, "daily": PullDaily, "weekly": PullWeekly, "never": PullNever, "if-not-present": PullIfNotPresent, "": PullAlways} + +// ParsePullPolicy from string with support for interval formats +func (i *ImagePullPolicyManager) ParsePullPolicy(policy string) (PullPolicy, error) { if val, ok := nameMap[policy]; ok { return val, nil } @@ -31,6 +79,12 @@ func (p PullPolicy) String() string { switch p { case PullAlways: return "always" + case PullHourly: + return "hourly" + case PullDaily: + return "daily" + case PullWeekly: + return "weekly" case PullNever: return "never" case PullIfNotPresent: @@ -39,3 +93,113 @@ func (p PullPolicy) String() string { return "" } + +func (i *ImagePullPolicyManager) GetDuration(p PullPolicy) time.Duration { + switch p { + case PullHourly: + return HOURLY + case PullDaily: + return DAILY + case PullWeekly: + return WEEKLY + } + + return 0 +} + +func (i *ImagePullPolicyManager) PruneOldImages(docker DockerClient) error { + path, err := DefaultImageJSONPath() + if err != nil { + return err + } + imageJSON, err := i.Read(path) + if err != nil { + return err + } + + if imageJSON.Interval.LastPrune != "" { + lastPruneTime, err := time.Parse(time.RFC3339, imageJSON.Interval.LastPrune) + if err != nil { + return errors.Wrap(err, "failed to parse last prune timestamp from JSON") + } + + if time.Since(lastPruneTime) < PRUNE_TIME { + // not enough time has passed since the last prune + return nil + } + } + + pruningThreshold := time.Now().Add(-PRUNE_TIME) + + for imageID, timestamp := range imageJSON.Image.ImageIDtoTIME { + imageTimestamp, err := time.Parse(time.RFC3339, timestamp) + if err != nil { + return errors.Wrap(err, "failed to parse image timestamp fron JSON") + } + + image, err := local.NewImage(imageID, docker, local.FromBaseImage(imageID)) + if err != nil { + return err + } + if !image.Found() { + delete(imageJSON.Image.ImageIDtoTIME, imageID) + } + + if imageTimestamp.Before(pruningThreshold) { + delete(imageJSON.Image.ImageIDtoTIME, imageID) + } + } + + imageJSON.Interval.LastPrune = time.Now().Format(time.RFC3339) + + if err := i.Write(imageJSON, path); err != nil { + return errors.Wrap(err, "failed to write updated image.json") + } + + return nil +} + +func (i *ImagePullPolicyManager) Read(path string) (*ImageJSON, error) { + // Check if the file exists, if not, return default values + if _, err := os.Stat(path); os.IsNotExist(err) { + return &ImageJSON{ + Interval: &Interval{ + LastPrune: "", + }, + Image: &ImageData{}, + }, nil + } + + jsonData, err := os.ReadFile(path) + if err != nil && !os.IsNotExist(err) { + return nil, errors.Wrap(err, "failed to read image.json") + } + var imageJSON *ImageJSON + if err := json.Unmarshal(jsonData, &imageJSON); err != nil && !os.IsNotExist(err) { + return nil, errors.Wrap(err, "failed to unmarshal image.json") + } + return imageJSON, nil +} + +func (i *ImagePullPolicyManager) Write(imageJSON *ImageJSON, path string) error { + updatedJSON, err := json.MarshalIndent(imageJSON, "", " ") + if err != nil { + return errors.Wrap(err, "failed to marshal updated records") + } + + return WriteFile(updatedJSON, path) +} + +func WriteFile(data []byte, path string) error { + file, err := os.OpenFile(path, os.O_CREATE|os.O_WRONLY|os.O_TRUNC, 0644) + if err != nil { + return errors.New("failed to open file: " + err.Error()) + } + defer file.Close() + + _, err = file.Write(data) + if err != nil { + return errors.New("failed to write data to file: " + err.Error()) + } + return nil +} diff --git a/pkg/image/pull_policy_test.go b/pkg/image/pull_policy_test.go index 22cba1174b..265c992311 100644 --- a/pkg/image/pull_policy_test.go +++ b/pkg/image/pull_policy_test.go @@ -1,12 +1,15 @@ package image_test import ( + "bytes" "testing" "github.com/sclevine/spec" "github.com/sclevine/spec/report" "github.com/buildpacks/pack/pkg/image" + "github.com/buildpacks/pack/pkg/logging" + fetcher_mock "github.com/buildpacks/pack/pkg/testmocks" h "github.com/buildpacks/pack/testhelpers" ) @@ -16,34 +19,68 @@ func TestPullPolicy(t *testing.T) { func testPullPolicy(t *testing.T, when spec.G, it spec.S) { when("#ParsePullPolicy", func() { + var ( + outBuf bytes.Buffer + logger logging.Logger + imagePullChecker image.ImagePullPolicyHandler + ) + + it.Before(func() { + logger = logging.NewLogWithWriters(&outBuf, &outBuf) + imagePullChecker = fetcher_mock.NewMockPullPolicyManager(mockController) + }) + it("returns PullNever for never", func() { - policy, err := image.ParsePullPolicy("never") + policy, err := imagePullChecker.ParsePullPolicy("never") h.AssertNil(t, err) h.AssertEq(t, policy, image.PullNever) }) it("returns PullAlways for always", func() { - policy, err := image.ParsePullPolicy("always") + policy, err := imagePullChecker.ParsePullPolicy("always") h.AssertNil(t, err) h.AssertEq(t, policy, image.PullAlways) }) it("returns PullIfNotPresent for if-not-present", func() { - policy, err := image.ParsePullPolicy("if-not-present") + policy, err := imagePullChecker.ParsePullPolicy("if-not-present") h.AssertNil(t, err) h.AssertEq(t, policy, image.PullIfNotPresent) }) - it("defaults to PullAlways, if empty string", func() { - policy, err := image.ParsePullPolicy("") + it("returns PullHourly for hourly", func() { + policy, err := imagePullChecker.ParsePullPolicy("hourly") h.AssertNil(t, err) - h.AssertEq(t, policy, image.PullAlways) + h.AssertEq(t, policy, image.PullHourly) + }) + + it("returns PullDaily for daily", func() { + policy, err := imagePullChecker.ParsePullPolicy("daily") + h.AssertNil(t, err) + h.AssertEq(t, policy, image.PullDaily) + }) + + it("returns PullWeekly for weekly", func() { + policy, err := imagePullChecker.ParsePullPolicy("weekly") + h.AssertNil(t, err) + h.AssertEq(t, policy, image.PullWeekly) + }) + + it("returns PullWithInterval for interval= format", func() { + policy, err := imagePullChecker.ParsePullPolicy("interval=4d") + h.AssertNil(t, err) + h.AssertEq(t, policy, image.PullWithInterval) }) it("returns error for unknown string", func() { - _, err := image.ParsePullPolicy("fake-policy-here") + _, err := imagePullChecker.ParsePullPolicy("fake-policy-here") h.AssertError(t, err, "invalid pull policy") }) + + it("returns error for invalid interval format", func() { + _, err := imagePullChecker.ParsePullPolicy("interval=invalid") + h.AssertError(t, err, "invalid interval format") + }) }) when("#String", func() { @@ -51,6 +88,10 @@ func testPullPolicy(t *testing.T, when spec.G, it spec.S) { h.AssertEq(t, image.PullAlways.String(), "always") h.AssertEq(t, image.PullNever.String(), "never") h.AssertEq(t, image.PullIfNotPresent.String(), "if-not-present") + h.AssertEq(t, image.PullHourly.String(), "hourly") + h.AssertEq(t, image.PullDaily.String(), "daily") + h.AssertEq(t, image.PullWeekly.String(), "weekly") + h.AssertContains(t, image.PullWithInterval.String(), "interval=") }) }) } diff --git a/pkg/testmocks/mock_image_fetcher.go b/pkg/testmocks/mock_image_fetcher.go index 281f28d04d..a82a54a9dc 100644 --- a/pkg/testmocks/mock_image_fetcher.go +++ b/pkg/testmocks/mock_image_fetcher.go @@ -1,7 +1,7 @@ // Code generated by MockGen. DO NOT EDIT. // Source: github.com/buildpacks/pack/pkg/client (interfaces: ImageFetcher) -// Package testmocks is a generated GoMock package. +// Package mock_client is a generated GoMock package. package testmocks import ( @@ -9,9 +9,8 @@ import ( reflect "reflect" imgutil "github.com/buildpacks/imgutil" - gomock "github.com/golang/mock/gomock" - image "github.com/buildpacks/pack/pkg/image" + gomock "github.com/golang/mock/gomock" ) // MockImageFetcher is a mock of ImageFetcher interface. diff --git a/pkg/testmocks/mock_image_pull_policy_handler.go b/pkg/testmocks/mock_image_pull_policy_handler.go new file mode 100644 index 0000000000..4d72fd1e1d --- /dev/null +++ b/pkg/testmocks/mock_image_pull_policy_handler.go @@ -0,0 +1,137 @@ +// Code generated by MockGen. DO NOT EDIT. +// Source: github.com/buildpacks/pack/pkg/image (interfaces: ImagePullPolicyHandler) + +// Package mock_image is a generated GoMock package. +package testmocks + +import ( + reflect "reflect" + time "time" + + image "github.com/buildpacks/pack/pkg/image" + gomock "github.com/golang/mock/gomock" +) + +// MockImagePullPolicyHandler is a mock of ImagePullPolicyHandler interface. +type MockImagePullPolicyHandler struct { + ctrl *gomock.Controller + recorder *MockImagePullPolicyHandlerMockRecorder +} + +// MockImagePullPolicyHandlerMockRecorder is the mock recorder for MockImagePullPolicyHandler. +type MockImagePullPolicyHandlerMockRecorder struct { + mock *MockImagePullPolicyHandler +} + +// NewMockImagePullPolicyHandler creates a new mock instance. +func NewMockImagePullPolicyHandler(ctrl *gomock.Controller) *MockImagePullPolicyHandler { + mock := &MockImagePullPolicyHandler{ctrl: ctrl} + mock.recorder = &MockImagePullPolicyHandlerMockRecorder{mock} + return mock +} + +// EXPECT returns an object that allows the caller to indicate expected use. +func (m *MockImagePullPolicyHandler) EXPECT() *MockImagePullPolicyHandlerMockRecorder { + return m.recorder +} + +// CheckImagePullInterval mocks base method. +func (m *MockImagePullPolicyHandler) CheckImagePullInterval(arg0, arg1 string, arg2 image.PullPolicy) (bool, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "CheckImagePullInterval", arg0, arg1, arg2) + ret0, _ := ret[0].(bool) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// CheckImagePullInterval indicates an expected call of CheckImagePullInterval. +func (mr *MockImagePullPolicyHandlerMockRecorder) CheckImagePullInterval(arg0, arg1, arg2 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CheckImagePullInterval", reflect.TypeOf((*MockImagePullPolicyHandler)(nil).CheckImagePullInterval), arg0, arg1, arg2) +} + +// GetDuration mocks base method. +func (m *MockImagePullPolicyHandler) GetDuration(arg0 image.PullPolicy) time.Duration { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetDuration", arg0) + ret0, _ := ret[0].(time.Duration) + return ret0 +} + +// GetDuration indicates an expected call of GetDuration. +func (mr *MockImagePullPolicyHandlerMockRecorder) GetDuration(arg0 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetDuration", reflect.TypeOf((*MockImagePullPolicyHandler)(nil).GetDuration), arg0) +} + +// ParsePullPolicy mocks base method. +func (m *MockImagePullPolicyHandler) ParsePullPolicy(arg0 string) (image.PullPolicy, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "ParsePullPolicy", arg0) + ret0, _ := ret[0].(image.PullPolicy) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// ParsePullPolicy indicates an expected call of ParsePullPolicy. +func (mr *MockImagePullPolicyHandlerMockRecorder) ParsePullPolicy(arg0 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ParsePullPolicy", reflect.TypeOf((*MockImagePullPolicyHandler)(nil).ParsePullPolicy), arg0) +} + +// PruneOldImages mocks base method. +func (m *MockImagePullPolicyHandler) PruneOldImages(arg0 image.DockerClient) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "PruneOldImages", arg0) + ret0, _ := ret[0].(error) + return ret0 +} + +// PruneOldImages indicates an expected call of PruneOldImages. +func (mr *MockImagePullPolicyHandlerMockRecorder) PruneOldImages(arg0 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "PruneOldImages", reflect.TypeOf((*MockImagePullPolicyHandler)(nil).PruneOldImages), arg0) +} + +// Read mocks base method. +func (m *MockImagePullPolicyHandler) Read(arg0 string) (*image.ImageJSON, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "Read", arg0) + ret0, _ := ret[0].(*image.ImageJSON) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// Read indicates an expected call of Read. +func (mr *MockImagePullPolicyHandlerMockRecorder) Read(arg0 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Read", reflect.TypeOf((*MockImagePullPolicyHandler)(nil).Read), arg0) +} + +// UpdateImagePullRecord mocks base method. +func (m *MockImagePullPolicyHandler) UpdateImagePullRecord(arg0, arg1, arg2 string) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "UpdateImagePullRecord", arg0, arg1, arg2) + ret0, _ := ret[0].(error) + return ret0 +} + +// UpdateImagePullRecord indicates an expected call of UpdateImagePullRecord. +func (mr *MockImagePullPolicyHandlerMockRecorder) UpdateImagePullRecord(arg0, arg1, arg2 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "UpdateImagePullRecord", reflect.TypeOf((*MockImagePullPolicyHandler)(nil).UpdateImagePullRecord), arg0, arg1, arg2) +} + +// Write mocks base method. +func (m *MockImagePullPolicyHandler) Write(arg0 *image.ImageJSON, arg1 string) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "Write", arg0, arg1) + ret0, _ := ret[0].(error) + return ret0 +} + +// Write indicates an expected call of Write. +func (mr *MockImagePullPolicyHandlerMockRecorder) Write(arg0, arg1 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Write", reflect.TypeOf((*MockImagePullPolicyHandler)(nil).Write), arg0, arg1) +}