Skip to content

Commit

Permalink
OCM-7263 | feat: Add the ability to list KubeletConfigs for a cluster
Browse files Browse the repository at this point in the history
OCM-6318 | ci: Build up deprovision step

OCM-6318 | ci: Build up deprovision step
  • Loading branch information
robpblake authored and jameszwang committed May 11, 2024
1 parent 5fd65fa commit 342ab9f
Show file tree
Hide file tree
Showing 12 changed files with 436 additions and 20 deletions.
5 changes: 4 additions & 1 deletion cmd/list/cmd.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ import (
"github.com/openshift/rosa/cmd/list/idp"
"github.com/openshift/rosa/cmd/list/ingress"
"github.com/openshift/rosa/cmd/list/instancetypes"
"github.com/openshift/rosa/cmd/list/kubeletconfig"
"github.com/openshift/rosa/cmd/list/machinepool"
"github.com/openshift/rosa/cmd/list/ocmroles"
"github.com/openshift/rosa/cmd/list/oidcconfig"
Expand Down Expand Up @@ -77,6 +78,8 @@ func init() {
Cmd.AddCommand(rhRegion.Cmd)
Cmd.AddCommand(externalauthprovider.Cmd)
Cmd.AddCommand(breakglasscredential.Cmd)
kubeletconfig := kubeletconfig.NewListKubeletConfigsCommand()
Cmd.AddCommand(kubeletconfig)
flags := Cmd.PersistentFlags()
arguments.AddProfileFlag(flags)
arguments.AddRegionFlag(flags)
Expand All @@ -90,7 +93,7 @@ func init() {
gates.Cmd, idp.Cmd, ingress.Cmd, machinePoolCommand,
operatorroles.Cmd, region.Cmd, rhRegion.Cmd,
service.Cmd, tuningconfigs.Cmd, upgrade.Cmd,
user.Cmd, version.Cmd,
user.Cmd, version.Cmd, kubeletconfig,
}
arguments.MarkRegionDeprecated(Cmd, globallyAvailableCommands)
}
69 changes: 69 additions & 0 deletions cmd/list/kubeletconfig/cmd.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
package kubeletconfig

import (
"context"
"fmt"
"os"
"text/tabwriter"

"github.com/spf13/cobra"

"github.com/openshift/rosa/pkg/kubeletconfig"
"github.com/openshift/rosa/pkg/ocm"
"github.com/openshift/rosa/pkg/output"
"github.com/openshift/rosa/pkg/rosa"
)

const (
use = "kubeletconfigs"
short = "List kubeletconfigs"
long = short
example = ` # List the kubeletconfigs for cluster 'foo'
rosa list kubeletconfig --cluster foo`
alias = "kubelet-configs"
)

func NewListKubeletConfigsCommand() *cobra.Command {
cmd := &cobra.Command{
Use: use,
Short: short,
Long: long,
Example: example,
Aliases: []string{alias},
Args: cobra.NoArgs,
Run: rosa.DefaultRunner(rosa.RuntimeWithOCM(), ListKubeletConfigRunner()),
}

output.AddFlag(cmd)
ocm.AddClusterFlag(cmd)
return cmd
}

func ListKubeletConfigRunner() rosa.CommandRunner {
return func(ctx context.Context, runtime *rosa.Runtime, command *cobra.Command, args []string) error {

cluster, err := runtime.OCMClient.GetCluster(runtime.GetClusterKey(), runtime.Creator)
if err != nil {
return err
}
kubeletConfigs, err := runtime.OCMClient.ListKubeletConfigs(ctx, cluster.ID())
if err != nil {
return err
}

if output.HasFlag() {
output.Print(kubeletConfigs)
} else {
if len(kubeletConfigs) == 0 {
runtime.Reporter.Infof("There are no KubeletConfigs for cluster '%s'.", runtime.ClusterKey)
return nil
}

writer := tabwriter.NewWriter(os.Stdout, 0, 4, 2, ' ', 0)
fmt.Fprint(writer, kubeletconfig.PrintKubeletConfigsForTabularOutput(kubeletConfigs))
return writer.Flush()
}

return nil
}
}
13 changes: 13 additions & 0 deletions cmd/list/kubeletconfig/cmd_suite_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package kubeletconfig

import (
"testing"

. "github.com/onsi/ginkgo/v2"
. "github.com/onsi/gomega"
)

func TestListKubeletConfigs(t *testing.T) {
RegisterFailHandler(Fail)
RunSpecs(t, "List KubeletConfigs Suite")
}
176 changes: 176 additions & 0 deletions cmd/list/kubeletconfig/cmd_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,176 @@
package kubeletconfig

import (
"context"
"fmt"
"net/http"

. "github.com/onsi/ginkgo/v2"
. "github.com/onsi/gomega"
cmv1 "github.com/openshift-online/ocm-sdk-go/clustersmgmt/v1"
. "github.com/openshift-online/ocm-sdk-go/testing"

"github.com/openshift/rosa/pkg/output"
. "github.com/openshift/rosa/pkg/test"
)

var tabularOutput = `ID NAME POD PIDS LIMIT
foo testing 10000
bar testing2 20000
`

var _ = Describe("List KubeletConfig Command", func() {

Context("Create Command", func() {
It("Creates the command correctly", func() {
cmd := NewListKubeletConfigsCommand()
Expect(cmd).NotTo(BeNil())

Expect(cmd.Use).To(Equal(use))
Expect(cmd.Short).To(Equal(short))
Expect(cmd.Long).To(Equal(long))
Expect(cmd.Aliases).To(ContainElements(alias))
Expect(cmd.Args).NotTo(BeNil())
Expect(cmd.Run).NotTo(BeNil())
Expect(cmd.RunE).To(BeNil())

flags := cmd.Flags()
Expect(flags.Lookup("cluster")).NotTo(BeNil())
Expect(flags.Lookup("output")).NotTo(BeNil())
})
})

Context("Command Runner", func() {

var t *TestingRuntime

BeforeEach(func() {
t = NewTestRuntime()
output.SetOutput("")
})

AfterEach(func() {
output.SetOutput("")
})

It("Returns an error if the cluster does not exist", func() {

t.ApiServer.AppendHandlers(RespondWithJSON(http.StatusOK, FormatClusterList(make([]*cmv1.Cluster, 0))))
t.SetCluster("cluster", nil)

runner := ListKubeletConfigRunner()
err := runner(context.Background(), t.RosaRuntime, nil, nil)
Expect(err).To(HaveOccurred())
Expect(err.Error()).To(
Equal("There is no cluster with identifier or name 'cluster'"))
})

It("Returns an error if OCM API fails to list KubeletConfigs", func() {
cluster := MockCluster(func(c *cmv1.ClusterBuilder) {
c.State(cmv1.ClusterStateReady)
})

t.SetCluster(cluster.Name(), cluster)
t.ApiServer.AppendHandlers(RespondWithJSON(http.StatusOK, FormatClusterList([]*cmv1.Cluster{cluster})))
t.ApiServer.RouteToHandler(
"GET",
fmt.Sprintf("/api/clusters_mgmt/v1/clusters/%s/kubelet_configs", cluster.ID()),
RespondWithJSON(http.StatusInternalServerError, "{}"))

runner := ListKubeletConfigRunner()
err := runner(context.Background(), t.RosaRuntime, nil, nil)

Expect(err).NotTo(BeNil())
Expect(err.Error()).To(ContainSubstring("status is 500"))
})

It("Prints message if there are no KubeletConfigs for the cluster", func() {
cluster := MockCluster(func(c *cmv1.ClusterBuilder) {
c.State(cmv1.ClusterStateReady)
})

t.SetCluster(cluster.Name(), cluster)
t.ApiServer.AppendHandlers(RespondWithJSON(http.StatusOK, FormatClusterList([]*cmv1.Cluster{cluster})))
t.ApiServer.AppendHandlers(RespondWithJSON(http.StatusOK, FormatKubeletConfigList([]*cmv1.KubeletConfig{})))

runner := ListKubeletConfigRunner()

t.StdOutReader.Record()
err := runner(context.Background(), t.RosaRuntime, nil, nil)
Expect(err).NotTo(HaveOccurred())

stdOut, _ := t.StdOutReader.Read()
Expect(stdOut).To(Equal("INFO: There are no KubeletConfigs for cluster 'cluster'.\n"))
})

It("Prints empty json if there are no KubeletConfigs for the cluster", func() {
output.SetOutput("json")
cluster := MockCluster(func(c *cmv1.ClusterBuilder) {
c.State(cmv1.ClusterStateReady)
})

t.SetCluster(cluster.Name(), cluster)
t.ApiServer.AppendHandlers(RespondWithJSON(http.StatusOK, FormatClusterList([]*cmv1.Cluster{cluster})))
t.ApiServer.AppendHandlers(RespondWithJSON(http.StatusOK, FormatKubeletConfigList([]*cmv1.KubeletConfig{})))

runner := ListKubeletConfigRunner()

t.StdOutReader.Record()
err := runner(context.Background(), t.RosaRuntime, nil, nil)
Expect(err).NotTo(HaveOccurred())

stdOut, _ := t.StdOutReader.Read()
Expect(stdOut).To(Equal("[]\n"))
})

It("Prints empty yaml if there are no KubeletConfigs for the cluster", func() {
output.SetOutput("yaml")
cluster := MockCluster(func(c *cmv1.ClusterBuilder) {
c.State(cmv1.ClusterStateReady)
})

t.SetCluster(cluster.Name(), cluster)
t.ApiServer.AppendHandlers(RespondWithJSON(http.StatusOK, FormatClusterList([]*cmv1.Cluster{cluster})))
t.ApiServer.AppendHandlers(RespondWithJSON(http.StatusOK, FormatKubeletConfigList([]*cmv1.KubeletConfig{})))

runner := ListKubeletConfigRunner()

t.StdOutReader.Record()
err := runner(context.Background(), t.RosaRuntime, nil, nil)
Expect(err).NotTo(HaveOccurred())

stdOut, _ := t.StdOutReader.Read()
Expect(stdOut).To(Equal("[]\n"))
})

It("Prints tabular list of KubeletConfigs for the cluster", func() {
cluster := MockCluster(func(c *cmv1.ClusterBuilder) {
c.State(cmv1.ClusterStateReady)
})

kubeletConfig := MockKubeletConfig(func(k *cmv1.KubeletConfigBuilder) {
k.Name("testing").ID("foo").PodPidsLimit(10000)
})

kubeletConfig2 := MockKubeletConfig(func(k *cmv1.KubeletConfigBuilder) {
k.Name("testing2").ID("bar").PodPidsLimit(20000)
})

t.SetCluster(cluster.Name(), cluster)
t.ApiServer.AppendHandlers(RespondWithJSON(http.StatusOK, FormatClusterList([]*cmv1.Cluster{cluster})))
t.ApiServer.AppendHandlers(
RespondWithJSON(
http.StatusOK, FormatKubeletConfigList(
[]*cmv1.KubeletConfig{kubeletConfig, kubeletConfig2})))

runner := ListKubeletConfigRunner()

t.StdOutReader.Record()
err := runner(context.Background(), t.RosaRuntime, nil, nil)
Expect(err).NotTo(HaveOccurred())

stdOut, _ := t.StdOutReader.Read()
Expect(stdOut).To(Equal(tabularOutput))
})
})
})
23 changes: 23 additions & 0 deletions pkg/kubeletconfig/output.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
package kubeletconfig

import (
"fmt"

cmv1 "github.com/openshift-online/ocm-sdk-go/clustersmgmt/v1"
)

const emptyName = "-"

func PrintKubeletConfigsForTabularOutput(configs []*cmv1.KubeletConfig) string {
output := "ID\tNAME\tPOD PIDS LIMIT\n"
for _, config := range configs {

name := config.Name()
if name == "" {
name = emptyName
}
output += fmt.Sprintf("%s\t%s\t%d\n", config.ID(), name, config.PodPidsLimit())
}

return output
}
25 changes: 25 additions & 0 deletions pkg/kubeletconfig/output_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
package kubeletconfig

import (
. "github.com/onsi/ginkgo/v2"
. "github.com/onsi/gomega"
cmv1 "github.com/openshift-online/ocm-sdk-go/clustersmgmt/v1"

. "github.com/openshift/rosa/pkg/test"
)

var _ = Describe("KubeletConfig Output", func() {
It("Correctly Prints KubeletConfigList for Tabuluar Output", func() {

kubeletConfig := MockKubeletConfig(func(k *cmv1.KubeletConfigBuilder) {
k.Name("test").PodPidsLimit(10000).ID("foo")
})

kubeletConfig2 := MockKubeletConfig(func(k *cmv1.KubeletConfigBuilder) {
k.Name("").PodPidsLimit(20000).ID("bar")
})

output := PrintKubeletConfigsForTabularOutput([]*cmv1.KubeletConfig{kubeletConfig, kubeletConfig2})
Expect(output).To(Equal("ID\tNAME\tPOD PIDS LIMIT\nfoo\ttest\t10000\nbar\t-\t20000\n"))
})
})
14 changes: 12 additions & 2 deletions pkg/ocm/kubeletconfig.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package ocm

import (
"context"
"net/http"

cmv1 "github.com/openshift-online/ocm-sdk-go/clustersmgmt/v1"
Expand Down Expand Up @@ -53,7 +54,7 @@ func (c *Client) CreateKubeletConfig(clusterID string, args KubeletConfigArgs) (
KubeletConfig().Post().Body(kubeletConfig).Send()

if err != nil {
return nil, err
return nil, handleErr(response.Error(), err)
}

return response.Body(), nil
Expand All @@ -69,8 +70,17 @@ func (c *Client) UpdateKubeletConfig(clusterID string, args KubeletConfigArgs) (
KubeletConfig().Update().Body(kubeletConfig).Send()

if err != nil {
return nil, err
return nil, handleErr(response.Error(), err)
}

return response.Body(), nil
}

func (c *Client) ListKubeletConfigs(ctx context.Context, clusterId string) ([]*cmv1.KubeletConfig, error) {
response, err := c.ocm.ClustersMgmt().V1().Clusters().Cluster(clusterId).KubeletConfigs().List().SendContext(ctx)
if err != nil {
return []*cmv1.KubeletConfig{}, handleErr(response.Error(), err)
}

return response.Items().Slice(), nil
}

0 comments on commit 342ab9f

Please sign in to comment.