Skip to content

Commit

Permalink
create NewKubeClientFromKubeConfig function
Browse files Browse the repository at this point in the history
  • Loading branch information
mfrancisc committed Aug 6, 2024
1 parent d79bd22 commit 887c794
Show file tree
Hide file tree
Showing 5 changed files with 92 additions and 83 deletions.
22 changes: 22 additions & 0 deletions pkg/client/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import (
"github.com/kubesaw/ksctl/pkg/configuration"
clicontext "github.com/kubesaw/ksctl/pkg/context"
"github.com/kubesaw/ksctl/pkg/ioutils"
clientcmdapi "k8s.io/client-go/tools/clientcmd/api"

"github.com/ghodss/yaml"
configv1 "github.com/openshift/api/config/v1"
Expand Down Expand Up @@ -89,6 +90,27 @@ func NewClientFromRestConfig(cfg *rest.Config) (runtimeclient.Client, error) {
return cl, nil
}

// NewKubeClientFromKubeConfig initializes a runtime client starting from a KubeConfig file path.
func NewKubeClientFromKubeConfig(kubeConfigPath string) (cl runtimeclient.Client, err error) {
var kubeConfig *clientcmdapi.Config
var clientConfig *rest.Config

kubeConfig, err = clientcmd.LoadFromFile(kubeConfigPath)
if err != nil {
return
}
clientConfig, err = clientcmd.NewDefaultClientConfig(*kubeConfig, nil).ClientConfig()
if err != nil {
return

Check warning on line 104 in pkg/client/client.go

View check run for this annotation

Codecov / codecov/patch

pkg/client/client.go#L104

Added line #L104 was not covered by tests
}
cl, err = NewClientFromRestConfig(clientConfig)
if err != nil {
return

Check warning on line 108 in pkg/client/client.go

View check run for this annotation

Codecov / codecov/patch

pkg/client/client.go#L108

Added line #L108 was not covered by tests
}

return
}

func newTlsVerifySkippingTransport() http.RoundTripper {
return &http.Transport{
TLSClientConfig: &tls.Config{
Expand Down
31 changes: 29 additions & 2 deletions pkg/client/client_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,10 @@ import (
toolchainv1alpha1 "github.com/codeready-toolchain/api/api/v1alpha1"
"github.com/codeready-toolchain/toolchain-common/pkg/states"
commontest "github.com/codeready-toolchain/toolchain-common/pkg/test"
"github.com/h2non/gock"
"github.com/kubesaw/ksctl/pkg/client"
clicontext "github.com/kubesaw/ksctl/pkg/context"
. "github.com/kubesaw/ksctl/pkg/test"

"github.com/h2non/gock"
routev1 "github.com/openshift/api/route/v1"
olmv1 "github.com/operator-framework/api/pkg/operators/v1"
olmv1alpha1 "github.com/operator-framework/api/pkg/operators/v1alpha1"
Expand Down Expand Up @@ -546,6 +545,34 @@ func TestGetRoute(t *testing.T) {
})
}

func TestNewKubeClientFromKubeConfig(t *testing.T) {
// given
t.Cleanup(gock.OffAll)
gock.New("https://cool-server.com").
Get("api").
Persist().
Reply(200).
BodyString("{}")

t.Run("success", func(j *testing.T) {
// when
cl, err := client.NewKubeClientFromKubeConfig(PersistKubeConfigFile(t, HostKubeConfig()))

// then
require.NoError(t, err)
assert.NotNil(t, cl)
})

t.Run("error", func(j *testing.T) {
// when
cl, err := client.NewKubeClientFromKubeConfig("/invalid/kube/config")

// then
require.Error(t, err)
assert.Nil(t, cl)
})
}

func newSubscription(pkg, channel string) *olmv1alpha1.Subscription {
return &olmv1alpha1.Subscription{
TypeMeta: metav1.TypeMeta{
Expand Down
52 changes: 15 additions & 37 deletions pkg/cmd/adm/install_operator.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,9 +14,6 @@ import (
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/types"
"k8s.io/apimachinery/pkg/util/wait"
"k8s.io/client-go/rest"
"k8s.io/client-go/tools/clientcmd"
clientcmdapi "k8s.io/client-go/tools/clientcmd/api"
runtimeclient "sigs.k8s.io/controller-runtime/pkg/client"

"github.com/spf13/cobra"
Expand All @@ -35,7 +32,11 @@ func NewInstallOperatorCmd() *cobra.Command {
Long: `This command installs the latest stable versions of the kubesaw operator using OLM`,
RunE: func(cmd *cobra.Command, args []string) error {
term := ioutils.NewTerminal(cmd.InOrStdin, cmd.OutOrStdout)
ctx := newExtendedCommandContext(term, client.DefaultNewClientFromRestConfig)
kubeClient, err := client.NewKubeClientFromKubeConfig(commandArgs.kubeConfig)
if err != nil {
return err

Check warning on line 37 in pkg/cmd/adm/install_operator.go

View check run for this annotation

Codecov / codecov/patch

pkg/cmd/adm/install_operator.go#L27-L37

Added lines #L27 - L37 were not covered by tests
}
ctx := clicontext.NewTerminalContext(term, kubeClient)
return installOperator(ctx, commandArgs, args[0], time.Second*60)

Check warning on line 40 in pkg/cmd/adm/install_operator.go

View check run for this annotation

Codecov / codecov/patch

pkg/cmd/adm/install_operator.go#L39-L40

Added lines #L39 - L40 were not covered by tests
},
}
Expand All @@ -47,11 +48,12 @@ func NewInstallOperatorCmd() *cobra.Command {
return cmd

Check warning on line 48 in pkg/cmd/adm/install_operator.go

View check run for this annotation

Codecov / codecov/patch

pkg/cmd/adm/install_operator.go#L44-L48

Added lines #L44 - L48 were not covered by tests
}

func installOperator(ctx *extendedCommandContext, args installArgs, operator string, timeout time.Duration) error {
func installOperator(ctx *clicontext.TerminalContext, args installArgs, operator string, timeout time.Duration) error {
// validate cluster type
if operator != string(configuration.Host) && operator != string(configuration.Member) {
return fmt.Errorf("invalid operator type provided: %s. Valid ones are %s|%s", operator, string(configuration.Host), string(configuration.Member))
}

if !ctx.AskForConfirmation(
ioutils.WithMessagef("install %s in namespace '%s'", operator, args.namespace)) {
return nil
Expand All @@ -60,30 +62,26 @@ func installOperator(ctx *extendedCommandContext, args installArgs, operator str
// install the catalog source
catalogSourceKey := types.NamespacedName{Name: fmt.Sprintf("source-%s-operator", operator), Namespace: args.namespace}
catalogSource := newCatalogSource(catalogSourceKey, operator)
kubeClient, err := newKubeClient(ctx, args.kubeConfig)
if err != nil {
return err
}
if err := kubeClient.Create(ctx, catalogSource); err != nil {
if err := ctx.KubeClient.Create(ctx, catalogSource); err != nil {
return err

Check warning on line 66 in pkg/cmd/adm/install_operator.go

View check run for this annotation

Codecov / codecov/patch

pkg/cmd/adm/install_operator.go#L66

Added line #L66 was not covered by tests
}
if err := waitUntilCatalogSourceIsReady(ctx.CommandContext, kubeClient, catalogSourceKey, timeout); err != nil {
if err := waitUntilCatalogSourceIsReady(ctx, catalogSourceKey, timeout); err != nil {
return err
}
ctx.Printlnf("CatalogSource %s is ready", catalogSourceKey)

// install operator group
operatorGroup := newOperatorGroup(types.NamespacedName{Name: fmt.Sprintf("og-%s-operator", operator), Namespace: args.namespace})
if err := kubeClient.Create(ctx, operatorGroup); err != nil {
if err := ctx.KubeClient.Create(ctx, operatorGroup); err != nil {
return err

Check warning on line 76 in pkg/cmd/adm/install_operator.go

View check run for this annotation

Codecov / codecov/patch

pkg/cmd/adm/install_operator.go#L76

Added line #L76 was not covered by tests
}

// install subscription
subscription := newSubscription(types.NamespacedName{Name: fmt.Sprintf("subscription-%s-operator", operator), Namespace: args.namespace}, fmt.Sprintf("toolchain-%s-operator", operator), catalogSourceKey.Name)
if err := kubeClient.Create(ctx, subscription); err != nil {
if err := ctx.KubeClient.Create(ctx, subscription); err != nil {
return err

Check warning on line 82 in pkg/cmd/adm/install_operator.go

View check run for this annotation

Codecov / codecov/patch

pkg/cmd/adm/install_operator.go#L82

Added line #L82 was not covered by tests
}
if err := waitUntilInstallPlanIsComplete(ctx.CommandContext, kubeClient, args.namespace, timeout); err != nil {
if err := waitUntilInstallPlanIsComplete(ctx, ctx.KubeClient, args.namespace, timeout); err != nil {
return err
}
ctx.Println(fmt.Sprintf("InstallPlans for %s-operator are ready", operator))
Expand Down Expand Up @@ -142,39 +140,19 @@ func newSubscription(name types.NamespacedName, operatorName, catalogSourceName
}
}

func newKubeClient(ctx *extendedCommandContext, kubeConfigPath string) (cl runtimeclient.Client, err error) {
var kubeConfig *clientcmdapi.Config
var clientConfig *rest.Config

kubeConfig, err = clientcmd.LoadFromFile(kubeConfigPath)
if err != nil {
return
}
clientConfig, err = clientcmd.NewDefaultClientConfig(*kubeConfig, nil).ClientConfig()
if err != nil {
return
}
cl, err = ctx.NewClientFromRestConfig(clientConfig)
if err != nil {
return
}

return
}

func waitUntilCatalogSourceIsReady(ctx *clicontext.CommandContext, cl runtimeclient.Client, catalogSourceKey runtimeclient.ObjectKey, waitForReadyTimeout time.Duration) error {
func waitUntilCatalogSourceIsReady(ctx *clicontext.TerminalContext, catalogSourceKey runtimeclient.ObjectKey, waitForReadyTimeout time.Duration) error {
return wait.PollImmediate(2*time.Second, waitForReadyTimeout, func() (bool, error) {
ctx.Printlnf("waiting for CatalogSource %s to become ready", catalogSourceKey)
cs := &olmv1alpha1.CatalogSource{}
if err := cl.Get(ctx, catalogSourceKey, cs); err != nil {
if err := ctx.KubeClient.Get(ctx, catalogSourceKey, cs); err != nil {
return false, err

Check warning on line 148 in pkg/cmd/adm/install_operator.go

View check run for this annotation

Codecov / codecov/patch

pkg/cmd/adm/install_operator.go#L148

Added line #L148 was not covered by tests
}

return cs.Status.GRPCConnectionState != nil && cs.Status.GRPCConnectionState.LastObservedState == "READY", nil
})
}

func waitUntilInstallPlanIsComplete(ctx *clicontext.CommandContext, cl runtimeclient.Client, namespace string, waitForReadyTimeout time.Duration) error {
func waitUntilInstallPlanIsComplete(ctx *clicontext.TerminalContext, cl runtimeclient.Client, namespace string, waitForReadyTimeout time.Duration) error {
return wait.PollImmediate(2*time.Second, waitForReadyTimeout, func() (bool, error) {
ctx.Printlnf("waiting for InstallPlans in namespace %s to complete", namespace)
plans := &olmv1alpha1.InstallPlanList{}
Expand Down
54 changes: 10 additions & 44 deletions pkg/cmd/adm/install_operator_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,11 @@ import (

"github.com/codeready-toolchain/toolchain-common/pkg/test"
"github.com/kubesaw/ksctl/pkg/client"
clicontext "github.com/kubesaw/ksctl/pkg/context"
. "github.com/kubesaw/ksctl/pkg/test"
olmv1alpha1 "github.com/operator-framework/api/pkg/operators/v1alpha1"
"github.com/stretchr/testify/assert"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/client-go/rest"
runtimeclient "sigs.k8s.io/controller-runtime/pkg/client"

"github.com/stretchr/testify/require"
Expand Down Expand Up @@ -47,9 +47,9 @@ func TestInstallOperator(t *testing.T) {
t.Run("install "+operator+" operator is successful", func(t *testing.T) {
// given
fakeClient := test.NewFakeClient(t, &installPlan)
fakeClientFromRestConfig := fakeClientWithCatalogSource(t, fakeClient, "READY")
fakeClientWithCatalogSource(fakeClient, "READY")
term := NewFakeTerminalWithResponse("Y")
ctx := newExtendedCommandContext(term, fakeClientFromRestConfig)
ctx := clicontext.NewTerminalContext(term, fakeClient)

// when
err := installOperator(ctx, installArgs{
Expand Down Expand Up @@ -86,14 +86,8 @@ func TestInstallOperator(t *testing.T) {
t.Run("install "+operator+" operator fails if CatalogSource is not ready", func(t *testing.T) {
// given
fakeClient := test.NewFakeClient(t)
fakeClientFromRestConfig := func(cfg *rest.Config) (runtimeclient.Client, error) {
assert.Contains(t, cfg.Host, "http")
assert.Contains(t, cfg.Host, "://")
assert.Contains(t, cfg.Host, ".com")
return fakeClient, nil
}
term := NewFakeTerminalWithResponse("Y")
ctx := newExtendedCommandContext(term, fakeClientFromRestConfig)
ctx := clicontext.NewTerminalContext(term, fakeClient)

// when
err := installOperator(ctx, installArgs{
Expand All @@ -115,9 +109,9 @@ func TestInstallOperator(t *testing.T) {
// given
// no InstallPlan is pre provisioned
fakeClient := test.NewFakeClient(t)
fakeClientFromRestConfig := fakeClientWithCatalogSource(t, fakeClient, "READY")
fakeClientWithCatalogSource(fakeClient, "READY")
term := NewFakeTerminalWithResponse("Y")
ctx := newExtendedCommandContext(term, fakeClientFromRestConfig)
ctx := clicontext.NewTerminalContext(term, fakeClient)

// when
err := installOperator(ctx, installArgs{
Expand All @@ -137,9 +131,9 @@ func TestInstallOperator(t *testing.T) {
t.Run("fails if operator name is invalid", func(t *testing.T) {
// given
fakeClient := test.NewFakeClient(t)
fakeClientFromRestConfig := fakeClientWithCatalogSource(t, fakeClient, "READY")
fakeClientWithCatalogSource(fakeClient, "READY")
term := NewFakeTerminalWithResponse("Y")
ctx := newExtendedCommandContext(term, fakeClientFromRestConfig)
ctx := clicontext.NewTerminalContext(term, fakeClient)

// when
err := installOperator(ctx, installArgs{},
Expand All @@ -155,7 +149,7 @@ func TestInstallOperator(t *testing.T) {
// given
fakeClient := test.NewFakeClient(t)
term := NewFakeTerminalWithResponse("n")
ctx := newExtendedCommandContext(term, fakeClientFromRestConfig(t, fakeClient))
ctx := clicontext.NewTerminalContext(term, fakeClient)

// when
operator := "host"
Expand All @@ -169,27 +163,9 @@ func TestInstallOperator(t *testing.T) {
assert.Contains(t, term.Output(), fmt.Sprintf("Are you sure that you want to install %s in namespace", operator))
assert.NotContains(t, term.Output(), fmt.Sprintf("InstallPlans for %s are ready", operator))
})

t.Run("doesn't install operator if kubeconfig is invalid", func(t *testing.T) {
// given
fakeClient := test.NewFakeClient(t)
term := NewFakeTerminalWithResponse("Y")
ctx := newExtendedCommandContext(term, fakeClientFromRestConfig(t, fakeClient))

// when
operator := "host"
err := installOperator(ctx, installArgs{kubeConfig: "/some/invalid/path/config"},
operator,
1*time.Second,
)

// then
require.EqualError(t, err, "open /some/invalid/path/config: no such file or directory")
assert.NotContains(t, term.Output(), fmt.Sprintf("InstallPlans for %s are ready", operator))
})
}

func fakeClientWithCatalogSource(t *testing.T, fakeClient *test.FakeClient, catalogSourceState string) func(cfg *rest.Config) (runtimeclient.Client, error) {
func fakeClientWithCatalogSource(fakeClient *test.FakeClient, catalogSourceState string) {
fakeClient.MockCreate = func(ctx context.Context, obj runtimeclient.Object, opts ...runtimeclient.CreateOption) error {
switch objT := obj.(type) {
case *olmv1alpha1.CatalogSource:
Expand All @@ -204,14 +180,4 @@ func fakeClientWithCatalogSource(t *testing.T, fakeClient *test.FakeClient, cata
return fakeClient.Client.Create(ctx, objT)
}
}
return fakeClientFromRestConfig(t, fakeClient)
}

func fakeClientFromRestConfig(t *testing.T, fakeClient *test.FakeClient) func(cfg *rest.Config) (runtimeclient.Client, error) {
return func(cfg *rest.Config) (runtimeclient.Client, error) {
assert.Contains(t, cfg.Host, "http")
assert.Contains(t, cfg.Host, "://")
assert.Contains(t, cfg.Host, ".com")
return fakeClient, nil
}
}
16 changes: 16 additions & 0 deletions pkg/context/command_context.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,3 +26,19 @@ func NewCommandContext(term ioutils.Terminal, newClient NewClientFunc) *CommandC
NewClient: newClient,
}
}

// TerminalContext the context terminal utilities and KubeClient
type TerminalContext struct {
context.Context
ioutils.Terminal
KubeClient runtimeclient.Client
}

// NewTerminalContext returns the context with the terminal utilities and the kubeClient to be used by the CLI command.
func NewTerminalContext(term ioutils.Terminal, client runtimeclient.Client) *TerminalContext {
return &TerminalContext{
Context: context.Background(),
Terminal: term,
KubeClient: client,

Check warning on line 42 in pkg/context/command_context.go

View check run for this annotation

Codecov / codecov/patch

pkg/context/command_context.go#L38-L42

Added lines #L38 - L42 were not covered by tests
}
}

0 comments on commit 887c794

Please sign in to comment.