diff --git a/cmd/kubectl-k8ssandra/helm/crds.go b/cmd/kubectl-k8ssandra/helm/crds.go index d959c29..ad15575 100644 --- a/cmd/kubectl-k8ssandra/helm/crds.go +++ b/cmd/kubectl-k8ssandra/helm/crds.go @@ -31,6 +31,7 @@ type options struct { chartRepo string repoURL string download bool + chartList []string } func newOptions(streams genericclioptions.IOStreams) *options { @@ -49,13 +50,17 @@ func NewUpgradeCmd(streams genericclioptions.IOStreams) *cobra.Command { Short: "upgrade CRDs from chart to target version", Example: fmt.Sprintf(upgraderExample, "kubectl k8ssandra helm crds"), SilenceUsage: true, - RunE: func(c *cobra.Command, args []string) error { + PreRunE: func(c *cobra.Command, args []string) error { if err := o.Complete(c, args); err != nil { return err } if err := o.Validate(); err != nil { return err } + + return nil + }, + RunE: func(c *cobra.Command, args []string) error { if err := o.Run(); err != nil { log.Error("Error upgrading CustomResourceDefinitions", "error", err) return err @@ -70,16 +75,25 @@ func NewUpgradeCmd(streams genericclioptions.IOStreams) *cobra.Command { fl.StringVar(&o.chartVersion, "chartVersion", "", "chartVersion to upgrade to") fl.StringVar(&o.chartRepo, "chartRepo", "", "optional chart repository name to override the default (k8ssandra)") fl.StringVar(&o.repoURL, "repoURL", "", "optional chart repository url to override the default (helm.k8ssandra.io)") + fl.StringSliceVar(&o.chartList, "charts", []string{}, "optional list of dependency charts to upgrade, default is just the main chart. Use \"_\" to update all the subcharts.") fl.BoolVar(&o.download, "download", false, "only download the chart") o.configFlags.AddFlags(fl) + if err := cmd.MarkFlagRequired("chartName"); err != nil { + panic(err) + } + + if err := cmd.MarkFlagRequired("chartVersion"); err != nil { + panic(err) + } + return cmd } // Complete parses the arguments and necessary flags to options func (c *options) Complete(cmd *cobra.Command, args []string) error { var err error - if c.chartName == "" && c.chartVersion == "" { + if c.chartName == "" || c.chartVersion == "" { return errNotEnoughParameters } @@ -118,7 +132,7 @@ func (c *options) Run() error { ctx := context.Background() - upgrader, err := helmutil.NewUpgrader(kubeClient, c.chartRepo, c.repoURL, c.chartName) + upgrader, err := helmutil.NewUpgrader(kubeClient, c.chartRepo, c.repoURL, c.chartName, c.chartList) if err != nil { return err } diff --git a/cmd/kubectl-k8ssandra/helm/helm_test.go b/cmd/kubectl-k8ssandra/helm/helm_test.go new file mode 100644 index 0000000..fa13ef9 --- /dev/null +++ b/cmd/kubectl-k8ssandra/helm/helm_test.go @@ -0,0 +1,58 @@ +package helm + +import ( + "testing" + + "github.com/spf13/cobra" + "github.com/stretchr/testify/require" + "k8s.io/cli-runtime/pkg/genericiooptions" +) + +func TestSimplestCRDCommand(t *testing.T) { + require := require.New(t) + + cmd := NewUpgradeCmd(genericiooptions.NewTestIOStreamsDiscard()) + cmd.RunE = func(cmd *cobra.Command, args []string) error { + return nil + } + + cmd.Root().SetArgs([]string{"upgrade", "--chartName", "k8ssandra-operator", "--chartVersion", "1.0.0"}) + require.NoError(cmd.Execute()) +} + +func TestMissingParamsCRDCommand(t *testing.T) { + require := require.New(t) + + cmd := NewUpgradeCmd(genericiooptions.NewTestIOStreamsDiscard()) + cmd.RunE = func(cmd *cobra.Command, args []string) error { + return nil + } + + cmd.Root().SetArgs([]string{"upgrade", "--chartName", "k8ssandra-operator"}) + require.Error(cmd.Execute()) +} + +func TestInvalidParamsCRDCommand(t *testing.T) { + require := require.New(t) + + cmd := NewUpgradeCmd(genericiooptions.NewTestIOStreamsDiscard()) + cmd.RunE = func(cmd *cobra.Command, args []string) error { + return nil + } + + cmd.Root().SetArgs([]string{"upgrade", "--chartName", "k8ssandra-operator", "--chartTarget", "1.0.0"}) + require.Error(cmd.Execute()) +} + +func TestAllParamsCRDCommand(t *testing.T) { + require := require.New(t) + + cmd := NewUpgradeCmd(genericiooptions.NewTestIOStreamsDiscard()) + cmd.RunE = func(cmd *cobra.Command, args []string) error { + return nil + } + + cmd.Root().SetArgs([]string{"upgrade", "--chartName", "k8ssandra-operator", "--chartVersion", "1.0.0", + "--chartRepo", "devel", "--repoURL", "https://helm.k8ssandra.io/devel", "--download", "true", "--charts", "cass-operator", "--charts", "k8ssandra-operator,cass-operator"}) + require.NoError(cmd.Execute()) +} diff --git a/pkg/helmutil/crds.go b/pkg/helmutil/crds.go index 9705b27..8f6e937 100644 --- a/pkg/helmutil/crds.go +++ b/pkg/helmutil/crds.go @@ -20,21 +20,27 @@ import ( "sigs.k8s.io/controller-runtime/pkg/client" ) +const ( + AllSubCharts = "_" +) + // Upgrader is a utility to update the CRDs in a helm chart's pre-upgrade hook type Upgrader struct { client client.Client repoName string repoURL string chartName string + subCharts []string } // NewUpgrader returns a new Upgrader client -func NewUpgrader(c client.Client, repoName, repoURL, chartName string) (*Upgrader, error) { +func NewUpgrader(c client.Client, repoName, repoURL, chartName string, subCharts []string) (*Upgrader, error) { return &Upgrader{ client: c, repoName: repoName, repoURL: repoURL, chartName: chartName, + subCharts: subCharts, }, nil } @@ -73,12 +79,11 @@ func (u *Upgrader) Upgrade(ctx context.Context, chartVersion string) ([]unstruct crds := make([]unstructured.Unstructured, 0) // For each dir under the charts subdir, check the "crds/" - paths, _ := findCRDDirs(chartDir) + paths, _ := findCRDDirs(chartDir, u.subCharts) for _, path := range paths { log.Debug("Processing CustomResourceDefinition directory", "path", path) - err = parseChartCRDs(&crds, path) - if err != nil { + if err := parseChartCRDs(&crds, path); err != nil { return nil, err } } @@ -106,18 +111,47 @@ func (u *Upgrader) Upgrade(ctx context.Context, chartVersion string) ([]unstruct return crds, err } -func findCRDDirs(chartDir string) ([]string, error) { +func findCRDDirs(chartDir string, subCharts []string) ([]string, error) { + chartsList := make(map[string]struct{}) + for _, chart := range subCharts { + chartsList[chart] = struct{}{} + } + + chartFilter := func(path string, info os.FileInfo) bool { + if !info.IsDir() || filepath.Base(path) != "crds" { + return false + } + + chartParts := strings.Split(filepath.Clean(path), string(os.PathSeparator)) + chartName := chartParts[len(chartParts)-2] + subChart := false + if len(chartParts) > 3 { + subChart = chartParts[len(chartParts)-3] == "charts" + } + + if !subChart { + return true + } + + if _, found := chartsList[AllSubCharts]; found { + return true + } + + if _, found := chartsList[chartName]; found { + return true + } + + return false + } dirs := make([]string, 0) err := filepath.Walk(chartDir, func(path string, info os.FileInfo, err error) error { if err != nil { return err } - if info.IsDir() { - if strings.HasSuffix(path, "crds") { - dirs = append(dirs, path) - } - return nil + if chartFilter(path, info) { + dirs = append(dirs, path) } + return nil }) return dirs, err diff --git a/pkg/helmutil/crds_test.go b/pkg/helmutil/crds_test.go index b299d61..9519866 100644 --- a/pkg/helmutil/crds_test.go +++ b/pkg/helmutil/crds_test.go @@ -28,7 +28,7 @@ func TestUpgradingCRDs(t *testing.T) { require.NoError(cleanCache("k8ssandra", chartName)) // creating new upgrader - u, err := helmutil.NewUpgrader(kubeClient, helmutil.K8ssandraRepoName, helmutil.StableK8ssandraRepoURL, chartName) + u, err := helmutil.NewUpgrader(kubeClient, helmutil.K8ssandraRepoName, helmutil.StableK8ssandraRepoURL, chartName, []string{}) require.NoError(err) crds, err := u.Upgrade(context.TODO(), "0.42.0") diff --git a/pkg/helmutil/crds_unit_test.go b/pkg/helmutil/crds_unit_test.go index d866dff..a511b4c 100644 --- a/pkg/helmutil/crds_unit_test.go +++ b/pkg/helmutil/crds_unit_test.go @@ -15,7 +15,19 @@ func TestFindCRDDirs(t *testing.T) { require.NoError(os.MkdirAll(chartDir+"/downstream-operator/crds", 0755)) - dirs, err := findCRDDirs(chartDir) + dirs, err := findCRDDirs(chartDir, []string{"downstream-operator"}) + require.NoError(err) + + require.Len(dirs, 1) + require.Equal(chartDir+"/downstream-operator/crds", dirs[0]) + + dirs, err = findCRDDirs(chartDir, []string{""}) + require.NoError(err) + + require.Len(dirs, 1) + require.Equal(chartDir+"/downstream-operator/crds", dirs[0]) + + dirs, err = findCRDDirs(chartDir, nil) require.NoError(err) require.Len(dirs, 1) @@ -23,12 +35,30 @@ func TestFindCRDDirs(t *testing.T) { require.NoError(os.MkdirAll(chartDir+"/downstream-operator/charts/k8ssandra-operator/crds", 0755)) require.NoError(os.MkdirAll(chartDir+"/downstream-operator/charts/k8ssandra-operator/charts/cass-operator/crds", 0755)) + require.NoError(os.MkdirAll(chartDir+"/downstream-operator/charts/third-party-operator/crds", 0755)) + dirs, err = findCRDDirs(chartDir, []string{AllSubCharts}) + require.NoError(err) - dirs, err = findCRDDirs(chartDir) + require.Len(dirs, 4) + + dirs, err = findCRDDirs(chartDir, []string{"k8ssandra-operator", "cass-operator"}) require.NoError(err) require.Len(dirs, 3) require.Contains(dirs, chartDir+"/downstream-operator/crds") require.Contains(dirs, chartDir+"/downstream-operator/charts/k8ssandra-operator/crds") require.Contains(dirs, chartDir+"/downstream-operator/charts/k8ssandra-operator/charts/cass-operator/crds") + + dirs, err = findCRDDirs(chartDir, []string{"k8ssandra-operator"}) + require.NoError(err) + + require.Len(dirs, 2) + require.Contains(dirs, chartDir+"/downstream-operator/crds") + require.Contains(dirs, chartDir+"/downstream-operator/charts/k8ssandra-operator/crds") + + dirs, err = findCRDDirs(chartDir, []string{""}) + require.NoError(err) + + require.Len(dirs, 1) + require.Contains(dirs, chartDir+"/downstream-operator/crds") }