diff --git a/cmd/containerimage/yaml.go b/cmd/containerimage/yaml.go index 1960119..e4ee8a8 100644 --- a/cmd/containerimage/yaml.go +++ b/cmd/containerimage/yaml.go @@ -20,8 +20,8 @@ import ( "github.com/spf13/cobra" + intcmd "github.com/stacklok/frizbee/internal/cmd" "github.com/stacklok/frizbee/pkg/config" - cliutils "github.com/stacklok/frizbee/pkg/utils/cli" ) // CmdYAML represents the yaml sub-command @@ -41,28 +41,14 @@ Example: } // flags - cmd.Flags().StringP("dir", "d", ".", "workflows directory") cmd.Flags().StringP("image-regex", "i", "image", "regex to match container image references") - cliutils.DeclareReplacerFlags(cmd) + intcmd.DeclareYAMLReplacerFlags(cmd) return cmd } func replaceYAML(cmd *cobra.Command, _ []string) error { - dir := cmd.Flag("dir").Value.String() - dryRun, err := cmd.Flags().GetBool("dry-run") - if err != nil { - return fmt.Errorf("failed to get dry-run flag: %w", err) - } - errOnModified, err := cmd.Flags().GetBool("error") - if err != nil { - return fmt.Errorf("failed to get error flag: %w", err) - } - quiet, err := cmd.Flags().GetBool("quiet") - if err != nil { - return fmt.Errorf("failed to get quiet flag: %w", err) - } cfg, err := config.FromContext(cmd.Context()) if err != nil { return fmt.Errorf("failed to get config from context: %w", err) @@ -72,20 +58,10 @@ func replaceYAML(cmd *cobra.Command, _ []string) error { return fmt.Errorf("failed to get image-regex flag: %w", err) } - dir = cliutils.ProcessDirNameForBillyFS(dir) - - ctx := cmd.Context() - - replacer := &yamlReplacer{ - Replacer: cliutils.Replacer{ - Dir: dir, - DryRun: dryRun, - Quiet: quiet, - ErrOnModified: errOnModified, - Cmd: cmd, - }, - imageRegex: ir, + replacer, err := intcmd.NewYAMLReplacer(cmd, intcmd.WithImageRegex(ir)) + if err != nil { + return err } - return replacer.do(ctx, cfg) + return replacer.Do(cmd.Context(), cfg) } diff --git a/cmd/dockercompose/dockercompose.go b/cmd/dockercompose/dockercompose.go new file mode 100644 index 0000000..fd1c0e8 --- /dev/null +++ b/cmd/dockercompose/dockercompose.go @@ -0,0 +1,62 @@ +// Copyright 2023 Stacklok, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// Package dockercompose provides command-line utilities to work with container images. +package dockercompose + +import ( + "fmt" + + "github.com/spf13/cobra" + + intcmd "github.com/stacklok/frizbee/internal/cmd" + "github.com/stacklok/frizbee/pkg/config" +) + +// CmdCompose represents the compose yaml sub-command +func CmdCompose() *cobra.Command { + cmd := &cobra.Command{ + Use: "docker-compose", + Aliases: []string{"dockercompose", "compose"}, + Short: "Replace container image references with checksums in docker-compose YAML files", + Long: `This utility replaces a tag or branch reference in a container image references +with the digest hash of the referenced tag in docker-compose YAML files. + +Example: + + $ frizbee docker-compose --dir . --dry-run --quiet --error +`, + RunE: replaceYAML, + SilenceUsage: true, + } + + // flags + intcmd.DeclareYAMLReplacerFlags(cmd) + + return cmd +} + +func replaceYAML(cmd *cobra.Command, _ []string) error { + cfg, err := config.FromContext(cmd.Context()) + if err != nil { + return fmt.Errorf("failed to get config from context: %w", err) + } + + replacer, err := intcmd.NewYAMLReplacer(cmd) + if err != nil { + return err + } + + return replacer.Do(cmd.Context(), cfg) +} diff --git a/cmd/kubernetes/kubernetes.go b/cmd/kubernetes/kubernetes.go new file mode 100644 index 0000000..542b5df --- /dev/null +++ b/cmd/kubernetes/kubernetes.go @@ -0,0 +1,68 @@ +// Copyright 2023 Stacklok, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// Package kubernetes provides command-line utilities to work with kubernetes manifests. +package kubernetes + +import ( + "fmt" + + "github.com/spf13/cobra" + + intcmd "github.com/stacklok/frizbee/internal/cmd" + "github.com/stacklok/frizbee/pkg/config" +) + +// CmdK8s represents the k8s yaml sub-command +func CmdK8s() *cobra.Command { + cmd := &cobra.Command{ + Use: "kubernetes", + Aliases: []string{"k8s"}, + Short: "Replace container image references with checksums in kubernetes YAML files", + Long: `This utility replaces a tag or branch reference in a container image references +with the digest hash of the referenced tag in YAML files. + +Example: + + $ frizbee kubernetes --dir . --dry-run --quiet --error +`, + RunE: replaceYAML, + SilenceUsage: true, + } + + // flags + cmd.Flags().StringP("image-regex", "i", "image", "regex to match container image references") + + intcmd.DeclareYAMLReplacerFlags(cmd) + + return cmd +} + +func replaceYAML(cmd *cobra.Command, _ []string) error { + cfg, err := config.FromContext(cmd.Context()) + if err != nil { + return fmt.Errorf("failed to get config from context: %w", err) + } + ir, err := cmd.Flags().GetString("image-regex") + if err != nil { + return fmt.Errorf("failed to get image-regex flag: %w", err) + } + + replacer, err := intcmd.NewYAMLReplacer(cmd, intcmd.WithImageRegex(ir)) + if err != nil { + return err + } + + return replacer.Do(cmd.Context(), cfg) +} diff --git a/cmd/root.go b/cmd/root.go index 168e8b9..cfd4efb 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -24,7 +24,9 @@ import ( "github.com/spf13/cobra" "github.com/stacklok/frizbee/cmd/containerimage" + "github.com/stacklok/frizbee/cmd/dockercompose" "github.com/stacklok/frizbee/cmd/ghactions" + "github.com/stacklok/frizbee/cmd/kubernetes" "github.com/stacklok/frizbee/pkg/config" ) @@ -40,6 +42,8 @@ func Execute() { rootCmd.AddCommand(ghactions.CmdGHActions()) rootCmd.AddCommand(containerimage.CmdContainerImage()) + rootCmd.AddCommand(dockercompose.CmdCompose()) + rootCmd.AddCommand(kubernetes.CmdK8s()) if err := rootCmd.ExecuteContext(context.Background()); err != nil { os.Exit(1) diff --git a/internal/cmd/doc.go b/internal/cmd/doc.go new file mode 100644 index 0000000..ec8b8ce --- /dev/null +++ b/internal/cmd/doc.go @@ -0,0 +1,17 @@ +// +// Copyright 2023 Stacklok, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// Package cmd provide common implementations for commands +package cmd diff --git a/cmd/containerimage/yamlreplacer.go b/internal/cmd/yamlreplacer.go similarity index 58% rename from cmd/containerimage/yamlreplacer.go rename to internal/cmd/yamlreplacer.go index 931697d..1bdb0d5 100644 --- a/cmd/containerimage/yamlreplacer.go +++ b/internal/cmd/yamlreplacer.go @@ -13,7 +13,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -package containerimage +package cmd import ( "bytes" @@ -24,6 +24,7 @@ import ( "sync/atomic" "github.com/go-git/go-billy/v5/osfs" + "github.com/spf13/cobra" "golang.org/x/sync/errgroup" "github.com/stacklok/frizbee/pkg/config" @@ -32,12 +33,64 @@ import ( cliutils "github.com/stacklok/frizbee/pkg/utils/cli" ) -type yamlReplacer struct { +// DeclareYAMLReplacerFlags declares the flags for the YAML replacer +func DeclareYAMLReplacerFlags(cli *cobra.Command) { + cli.Flags().StringP("dir", "d", ".", "manifests file or directory") + + cliutils.DeclareReplacerFlags(cli) +} + +// YAMLReplacer replaces container image references in YAML files +type YAMLReplacer struct { cliutils.Replacer - imageRegex string + ImageRegex string +} + +// WithImageRegex sets the image regex +func WithImageRegex(regex string) func(*YAMLReplacer) { + return func(r *YAMLReplacer) { + r.ImageRegex = regex + } +} + +// NewYAMLReplacer creates a new YAMLReplacer from the given +// command-line arguments and options +func NewYAMLReplacer(cli *cobra.Command, opts ...func(*YAMLReplacer)) (*YAMLReplacer, error) { + dir := cli.Flag("dir").Value.String() + dryRun, err := cli.Flags().GetBool("dry-run") + if err != nil { + return nil, fmt.Errorf("failed to get dry-run flag: %w", err) + } + errOnModified, err := cli.Flags().GetBool("error") + if err != nil { + return nil, fmt.Errorf("failed to get error flag: %w", err) + } + quiet, err := cli.Flags().GetBool("quiet") + if err != nil { + return nil, fmt.Errorf("failed to get quiet flag: %w", err) + } + + dir = cliutils.ProcessDirNameForBillyFS(dir) + + r := &YAMLReplacer{ + Replacer: cliutils.Replacer{ + Dir: dir, + DryRun: dryRun, + Quiet: quiet, + ErrOnModified: errOnModified, + Cmd: cli, + }, + ImageRegex: "image", + } + for _, opt := range opts { + opt(r) + } + + return r, nil } -func (r *yamlReplacer) do(ctx context.Context, _ *config.Config) error { +// Do runs the YAMLReplacer +func (r *YAMLReplacer) Do(ctx context.Context, _ *config.Config) error { basedir := filepath.Dir(r.Dir) base := filepath.Base(r.Dir) // NOTE: For some reason using boundfs causes a panic when trying to open a file. @@ -69,7 +122,7 @@ func (r *yamlReplacer) do(ctx context.Context, _ *config.Config) error { r.Logf("Processing %s\n", path) buf := bytes.Buffer{} - m, err := containers.ReplaceReferenceFromYAMLWithCache(ctx, r.imageRegex, f, &buf, cache) + m, err := containers.ReplaceReferenceFromYAMLWithCache(ctx, r.ImageRegex, f, &buf, cache) if err != nil { return fmt.Errorf("failed to process YAML file %s: %w", path, err) }