Skip to content

Commit

Permalink
Add a dry-run mode to flux build kustomization
Browse files Browse the repository at this point in the history
If implemented user will be able to use `flux build kustomization`
without any connection to the cluster.

Signed-off-by: Soule BA <soule@weave.works>
  • Loading branch information
souleb committed Nov 16, 2022
1 parent 35ea91c commit ce3688b
Show file tree
Hide file tree
Showing 7 changed files with 88 additions and 24 deletions.
20 changes: 17 additions & 3 deletions cmd/flux/build_kustomization.go
Expand Up @@ -40,25 +40,30 @@ It is possible to specify a Flux kustomization file using --kustomization-file.`
flux build kustomization my-app --path ./path/to/local/manifests
# Build using a local flux kustomization file
flux build kustomization my-app --path ./path/to/local/manifests --kustomization-file ./path/to/local/my-app.yaml`,
flux build kustomization my-app --path ./path/to/local/manifests --kustomization-file ./path/to/local/my-app.yaml
# Build in dry-run mode, i.e. do not connect to the cluster at all
flux build kustomization my-app --path ./path/to/local/manifests --kustomization-file ./path/to/local/my-app.yaml --dry-run`,
ValidArgsFunction: resourceNamesCompletionFunc(kustomizev1.GroupVersion.WithKind(kustomizev1.KustomizationKind)),
RunE: buildKsCmdRun,
}

type buildKsFlags struct {
kustomizationFile string
path string
dryRun bool
}

var buildKsArgs buildKsFlags

func init() {
buildKsCmd.Flags().StringVar(&buildKsArgs.path, "path", "", "Path to the manifests location.")
buildKsCmd.Flags().StringVar(&buildKsArgs.kustomizationFile, "kustomization-file", "", "Path to the Flux Kustomization YAML file.")
buildKsCmd.Flags().BoolVar(&buildKsArgs.dryRun, "dry-run", false, "Dry run mode.")
buildCmd.AddCommand(buildKsCmd)
}

func buildKsCmdRun(cmd *cobra.Command, args []string) error {
func buildKsCmdRun(cmd *cobra.Command, args []string) (err error) {
if len(args) < 1 {
return fmt.Errorf("%s name is required", kustomizationType.humanKind)
}
Expand All @@ -72,13 +77,22 @@ func buildKsCmdRun(cmd *cobra.Command, args []string) error {
return fmt.Errorf("invalid resource path %q", buildKsArgs.path)
}

if buildKsArgs.dryRun && buildKsArgs.kustomizationFile == "" {
return fmt.Errorf("dry-run mode requires a kustomization file")
}

if buildKsArgs.kustomizationFile != "" {
if fs, err := os.Stat(buildKsArgs.kustomizationFile); os.IsNotExist(err) || fs.IsDir() {
return fmt.Errorf("invalid kustomization file %q", buildKsArgs.kustomizationFile)
}
}

builder, err := build.NewBuilder(kubeconfigArgs, kubeclientOptions, name, buildKsArgs.path, build.WithTimeout(rootArgs.timeout), build.WithKustomizationFile(buildKsArgs.kustomizationFile))
builder, err := build.NewBuilder(name, buildKsArgs.path,
build.WithClientConfig(kubeconfigArgs, kubeclientOptions),
build.WithTimeout(rootArgs.timeout),
build.WithKustomizationFile(buildKsArgs.kustomizationFile),
build.WithDryRun(buildKsArgs.dryRun),
)
if err != nil {
return err
}
Expand Down
6 changes: 6 additions & 0 deletions cmd/flux/build_kustomization_test.go
Expand Up @@ -142,6 +142,12 @@ spec:
resultFile: "./testdata/build-kustomization/podinfo-with-var-substitution-result.yaml",
assertFunc: "assertGoldenTemplateFile",
},
{
name: "build deployment and configmap with var substitution in dry-run mode",
args: "build kustomization podinfo --kustomization-file ./testdata/build-kustomization/podinfo.yaml --path ./testdata/build-kustomization/var-substitution --dry-run",
resultFile: "./testdata/build-kustomization/podinfo-with-var-substitution-result.yaml",
assertFunc: "assertGoldenTemplateFile",
},
}

tmpl := map[string]string{
Expand Down
17 changes: 13 additions & 4 deletions cmd/flux/diff_kustomization.go
Expand Up @@ -77,12 +77,21 @@ func diffKsCmdRun(cmd *cobra.Command, args []string) error {
}
}

var builder *build.Builder
var err error
var (
builder *build.Builder
err error
)
if diffKsArgs.progressBar {
builder, err = build.NewBuilder(kubeconfigArgs, kubeclientOptions, name, diffKsArgs.path, build.WithTimeout(rootArgs.timeout), build.WithKustomizationFile(diffKsArgs.kustomizationFile), build.WithProgressBar())
builder, err = build.NewBuilder(name, diffKsArgs.path,
build.WithClientConfig(kubeconfigArgs, kubeclientOptions),
build.WithTimeout(rootArgs.timeout),
build.WithKustomizationFile(diffKsArgs.kustomizationFile),
build.WithProgressBar())
} else {
builder, err = build.NewBuilder(kubeconfigArgs, kubeclientOptions, name, diffKsArgs.path, build.WithTimeout(rootArgs.timeout), build.WithKustomizationFile(diffKsArgs.kustomizationFile))
builder, err = build.NewBuilder(name, diffKsArgs.path,
build.WithClientConfig(kubeconfigArgs, kubeclientOptions),
build.WithTimeout(rootArgs.timeout),
build.WithKustomizationFile(diffKsArgs.kustomizationFile))
}

if err != nil {
Expand Down
2 changes: 1 addition & 1 deletion cmd/flux/diff_kustomization_test.go
Expand Up @@ -97,7 +97,7 @@ func TestDiffKustomization(t *testing.T) {
"fluxns": allocateNamespace("flux-system"),
}

b, _ := build.NewBuilder(kubeconfigArgs, kubeclientOptions, "podinfo", "")
b, _ := build.NewBuilder("podinfo", "", build.WithClientConfig(kubeconfigArgs, kubeclientOptions))

resourceManager, err := b.Manager()
if err != nil {
Expand Down
2 changes: 1 addition & 1 deletion go.mod
Expand Up @@ -2,7 +2,7 @@ module github.com/fluxcd/flux2

go 1.18

replace github.com/fluxcd/pkg/kustomize => github.com/souleb/pkg/kustomize v0.0.0-20221114164450-c3ad651f477b
replace github.com/fluxcd/pkg/kustomize => github.com/souleb/pkg/kustomize v0.0.0-20221115105652-bfcd8988ecc8

require (
github.com/Masterminds/semver/v3 v3.1.1
Expand Down
4 changes: 2 additions & 2 deletions go.sum
Expand Up @@ -592,8 +592,8 @@ github.com/sirupsen/logrus v1.6.0/go.mod h1:7uNnSEd1DgxDLC74fIahvMZmmYsHGZGEOFrf
github.com/sirupsen/logrus v1.7.0/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0=
github.com/sirupsen/logrus v1.9.0 h1:trlNQbNUG3OdDrDil03MCb1H2o9nJ1x4/5LYw7byDE0=
github.com/sirupsen/logrus v1.9.0/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ=
github.com/souleb/pkg/kustomize v0.0.0-20221114164450-c3ad651f477b h1:JlaF96cEGlAxdW7NYi8ON398wR2xtrJkRUAAHFCTh0w=
github.com/souleb/pkg/kustomize v0.0.0-20221114164450-c3ad651f477b/go.mod h1:rXQcYjvqqS+9oCOA2J/w7KTnwNhdwDCeW4mE5zQRjN4=
github.com/souleb/pkg/kustomize v0.0.0-20221115105652-bfcd8988ecc8 h1:8ffW88dnCuwYL3zZRkFfxM4OCM3kfT4a2fzMH4HQDWY=
github.com/souleb/pkg/kustomize v0.0.0-20221115105652-bfcd8988ecc8/go.mod h1:rXQcYjvqqS+9oCOA2J/w7KTnwNhdwDCeW4mE5zQRjN4=
github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA=
github.com/spf13/afero v1.2.2/go.mod h1:9ZxEEn6pIJ8Rxe320qSDBk6AsU0r9pR7Q4OcevTdifk=
github.com/spf13/cobra v1.6.0 h1:42a0n6jwCot1pUmomAp4T7DeMD+20LFv4Q54pxLf2LI=
Expand Down
61 changes: 48 additions & 13 deletions internal/build/build.go
Expand Up @@ -76,17 +76,21 @@ type Builder struct {
kustomization *kustomizev1.Kustomization
timeout time.Duration
spinner *yacspin.Spinner
dryRun bool
}

// BuilderOptionFunc is a function that configures a Builder
type BuilderOptionFunc func(b *Builder) error

// WithKustomizationFile sets the kustomization file
func WithKustomizationFile(file string) BuilderOptionFunc {
return func(b *Builder) error {
b.kustomizationFile = file
return nil
}
}

// WithTimeout sets the timeout for the builder
func WithTimeout(timeout time.Duration) BuilderOptionFunc {
return func(b *Builder) error {
b.timeout = timeout
Expand Down Expand Up @@ -116,24 +120,47 @@ func WithProgressBar() BuilderOptionFunc {
}
}

// NewBuilder returns a new Builder
// to dp : create functional options
func NewBuilder(rcg *genericclioptions.ConfigFlags, clientOpts *runclient.Options, name, resources string, opts ...BuilderOptionFunc) (*Builder, error) {
kubeClient, err := utils.KubeClient(rcg, clientOpts)
if err != nil {
return nil, err
// WithClientConfig sets the client configuration
func WithClientConfig(rcg *genericclioptions.ConfigFlags, clientOpts *runclient.Options) BuilderOptionFunc {
return func(b *Builder) error {
kubeClient, err := utils.KubeClient(rcg, clientOpts)
if err != nil {
return err
}

restMapper, err := rcg.ToRESTMapper()
if err != nil {
return err
}
b.client = kubeClient
b.restMapper = restMapper
b.namespace = *rcg.Namespace
return nil
}
}

restMapper, err := rcg.ToRESTMapper()
if err != nil {
return nil, err
// WithDryRun sets the dry-run flag
func WithDryRun(dryRun bool) BuilderOptionFunc {
return func(b *Builder) error {
b.dryRun = dryRun
return nil
}
}

// NewBuilder returns a new Builder
// It takes a kustomization name and a path to the resources
// It also takes a list of BuilderOptionFunc to configure the builder
// One of the options is WithClientConfig, that must be provided for the builder to work
// with the k8s cluster
// One other option is WithKustomizationFile, that must be provided for the builder to work
// with a local kustomization file. If the kustomization file is not provided, the builder
// will try to retrieve the kustomization object from the k8s cluster.
// WithDryRun sets the dry-run flag, and needs to be provided if the builder is used for
// a dry-run. This flag works in conjunction with WithKustomizationFile, because the
// kustomization object is not retrieved from the k8s cluster when the dry-run flag is set.
func NewBuilder(name, resources string, opts ...BuilderOptionFunc) (*Builder, error) {
b := &Builder{
client: kubeClient,
restMapper: restMapper,
name: name,
namespace: *rcg.Namespace,
resourcesPath: resources,
}

Expand All @@ -147,6 +174,14 @@ func NewBuilder(rcg *genericclioptions.ConfigFlags, clientOpts *runclient.Option
b.timeout = defaultTimeout
}

if b.dryRun && b.kustomizationFile == "" {
return nil, fmt.Errorf("kustomization file is required for dry-run")
}

if !b.dryRun && b.client == nil {
return nil, fmt.Errorf("client is required for live run")
}

return b, nil
}

Expand Down Expand Up @@ -301,7 +336,7 @@ func (b *Builder) do(ctx context.Context, kustomization kustomizev1.Kustomizatio
if err != nil {
return nil, err
}
outRes, err := kustomize.SubstituteVariables(ctx, b.client, unstructured.Unstructured{Object: data}, res, false)
outRes, err := kustomize.SubstituteVariables(ctx, b.client, unstructured.Unstructured{Object: data}, res, b.dryRun)
if err != nil {
return nil, fmt.Errorf("var substitution failed for '%s': %w", res.GetName(), err)
}
Expand Down

0 comments on commit ce3688b

Please sign in to comment.