From 59f418e5149737f053d6c11b21eba2b894d5c16a Mon Sep 17 00:00:00 2001 From: Cole Snodgrass Date: Tue, 23 Jul 2024 10:09:19 -0700 Subject: [PATCH] feat: add debug logs for helm (#58) --- internal/cmd/local/helm/helm.go | 68 ++++++++++++++++++++++++++++ internal/cmd/local/local/cmd.go | 49 ++------------------ internal/cmd/local/local/cmd_test.go | 3 +- 3 files changed, 74 insertions(+), 46 deletions(-) create mode 100644 internal/cmd/local/helm/helm.go diff --git a/internal/cmd/local/helm/helm.go b/internal/cmd/local/helm/helm.go new file mode 100644 index 0000000..7522584 --- /dev/null +++ b/internal/cmd/local/helm/helm.go @@ -0,0 +1,68 @@ +package helm + +import ( + "context" + "fmt" + "github.com/airbytehq/abctl/internal/cmd/local/localerr" + helmclient "github.com/mittwald/go-helm-client" + "github.com/pterm/pterm" + "helm.sh/helm/v3/pkg/action" + "helm.sh/helm/v3/pkg/chart" + "helm.sh/helm/v3/pkg/release" + "helm.sh/helm/v3/pkg/repo" + "io" + "k8s.io/client-go/tools/clientcmd" +) + +// Client primarily for testing purposes +type Client interface { + AddOrUpdateChartRepo(entry repo.Entry) error + GetChart(name string, options *action.ChartPathOptions) (*chart.Chart, string, error) + GetRelease(name string) (*release.Release, error) + InstallOrUpgradeChart(ctx context.Context, spec *helmclient.ChartSpec, opts *helmclient.GenericHelmOptions) (*release.Release, error) + UninstallReleaseByName(name string) error +} + +// New returns the default helm client +func New(kubecfg, kubectx, namespace string) (Client, error) { + k8sCfg := clientcmd.NewNonInteractiveDeferredLoadingClientConfig( + &clientcmd.ClientConfigLoadingRules{ExplicitPath: kubecfg}, + &clientcmd.ConfigOverrides{CurrentContext: kubectx}, + ) + + restCfg, err := k8sCfg.ClientConfig() + if err != nil { + return nil, fmt.Errorf("%w: could not create rest config: %w", localerr.ErrKubernetes, err) + } + + logger := helmLogger{} + helm, err := helmclient.NewClientFromRestConf(&helmclient.RestConfClientOptions{ + Options: &helmclient.Options{ + Namespace: namespace, + Output: logger, + DebugLog: logger.Debug, + Debug: true, + }, + RestConfig: restCfg, + }) + if err != nil { + return nil, fmt.Errorf("unable to create helm client: %w", err) + } + + return helm, nil +} + +var _ io.Writer = (*helmLogger)(nil) + +// helmLogger is used by the Client to convert all helm output into debug logs. +type helmLogger struct { +} + +func (d helmLogger) Write(p []byte) (int, error) { + pterm.Debug.Println(fmt.Sprintf("helm: %s", string(p))) + return len(p), nil +} + +func (d helmLogger) Debug(format string, v ...interface{}) { + pterm.Debug.Println(fmt.Sprintf("helm - DEBUG: "+format, v...)) +} diff --git a/internal/cmd/local/local/cmd.go b/internal/cmd/local/local/cmd.go index ae98d7d..3a93b59 100644 --- a/internal/cmd/local/local/cmd.go +++ b/internal/cmd/local/local/cmd.go @@ -4,6 +4,7 @@ import ( "context" "fmt" "github.com/airbytehq/abctl/internal/cmd/local/docker" + "github.com/airbytehq/abctl/internal/cmd/local/helm" "github.com/airbytehq/abctl/internal/cmd/local/migrate" "github.com/airbytehq/abctl/internal/cmd/local/paths" corev1 "k8s.io/api/core/v1" @@ -24,8 +25,6 @@ import ( "github.com/pterm/pterm" "golang.org/x/crypto/bcrypt" "helm.sh/helm/v3/pkg/action" - "helm.sh/helm/v3/pkg/chart" - "helm.sh/helm/v3/pkg/release" "helm.sh/helm/v3/pkg/repo" eventsv1 "k8s.io/api/events/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" @@ -53,15 +52,6 @@ const Port = 8000 // dockerAuthSecretName is the name of the secret which holds the docker authentication information. const dockerAuthSecretName = "docker-auth" -// HelmClient primarily for testing purposes -type HelmClient interface { - AddOrUpdateChartRepo(entry repo.Entry) error - GetChart(string, *action.ChartPathOptions) (*chart.Chart, string, error) - GetRelease(name string) (*release.Release, error) - InstallOrUpgradeChart(ctx context.Context, spec *helmclient.ChartSpec, opts *helmclient.GenericHelmOptions) (*release.Release, error) - UninstallReleaseByName(string) error -} - type HTTPClient interface { Do(req *http.Request) (*http.Response, error) } @@ -74,7 +64,7 @@ type Command struct { provider k8s.Provider cluster k8s.Cluster http HTTPClient - helm HelmClient + helm helm.Client k8s k8s.Client portHTTP int spinner *pterm.SpinnerPrinter @@ -101,7 +91,7 @@ func WithHTTPClient(client HTTPClient) Option { } // WithHelmClient define the helm client for this command. -func WithHelmClient(client HelmClient) Option { +func WithHelmClient(client helm.Client) Option { return func(c *Command) { c.helm = client } @@ -172,7 +162,7 @@ func New(provider k8s.Provider, opts ...Option) (*Command, error) { // set the helm client, if not defined if c.helm == nil { var err error - if c.helm, err = defaultHelm(provider.Kubeconfig, provider.Context); err != nil { + if c.helm, err = helm.New(provider.Kubeconfig, provider.Context, airbyteNamespace); err != nil { return nil, err } } @@ -765,29 +755,6 @@ func defaultK8s(kubecfg, kubectx string) (k8s.Client, error) { return &k8s.DefaultK8sClient{ClientSet: k8sClient}, nil } -// defaultHelm returns the default helm client -func defaultHelm(kubecfg, kubectx string) (HelmClient, error) { - k8sCfg, err := k8sClientConfig(kubecfg, kubectx) - if err != nil { - return nil, fmt.Errorf("%w: %w", localerr.ErrKubernetes, err) - } - - restCfg, err := k8sCfg.ClientConfig() - if err != nil { - return nil, fmt.Errorf("%w: could not create rest config: %w", localerr.ErrKubernetes, err) - } - - helm, err := helmclient.NewClientFromRestConf(&helmclient.RestConfClientOptions{ - Options: &helmclient.Options{Namespace: airbyteNamespace, Output: &noopWriter{}, DebugLog: func(format string, v ...interface{}) {}}, - RestConfig: restCfg, - }) - if err != nil { - return nil, fmt.Errorf("unable to create helm client: %w", err) - } - - return helm, nil -} - // k8sClientConfig returns a k8s client config using the ~/.kube/config file and the k8sContext context. func k8sClientConfig(kubecfg, kubectx string) (clientcmd.ClientConfig, error) { return clientcmd.NewNonInteractiveDeferredLoadingClientConfig( @@ -795,11 +762,3 @@ func k8sClientConfig(kubecfg, kubectx string) (clientcmd.ClientConfig, error) { &clientcmd.ConfigOverrides{CurrentContext: kubectx}, ), nil } - -// noopWriter is used by the helm client to suppress its verbose output -type noopWriter struct { -} - -func (w *noopWriter) Write(p []byte) (int, error) { - return len(p), nil -} diff --git a/internal/cmd/local/local/cmd_test.go b/internal/cmd/local/local/cmd_test.go index 489d337..f83b8a7 100644 --- a/internal/cmd/local/local/cmd_test.go +++ b/internal/cmd/local/local/cmd_test.go @@ -4,6 +4,7 @@ import ( "context" "errors" "fmt" + "github.com/airbytehq/abctl/internal/cmd/local/helm" "github.com/airbytehq/abctl/internal/cmd/local/k8s" "github.com/airbytehq/abctl/internal/telemetry" "github.com/google/go-cmp/cmp" @@ -338,7 +339,7 @@ func TestCommand_Install_InvalidValuesFile(t *testing.T) { // --- // only mocks below here // --- -var _ HelmClient = (*mockHelmClient)(nil) +var _ helm.Client = (*mockHelmClient)(nil) type mockHelmClient struct { addOrUpdateChartRepo func(entry repo.Entry) error