From 1af5abb45bca664c2d180b65312316eb0899eb2a Mon Sep 17 00:00:00 2001 From: Amir Blum Date: Tue, 31 Oct 2023 15:48:07 +0200 Subject: [PATCH 1/3] feat: cli cloud command --- cli/cmd/cloud.go | 53 +++++++++++++ cli/cmd/login.go | 124 ++++++++++++++++++++++++++++++ cli/cmd/logout.go | 82 ++++++++++++++++++++ cli/cmd/resources/keyvalproxy.go | 4 +- cli/cmd/resources/owntelemetry.go | 4 +- 5 files changed, 263 insertions(+), 4 deletions(-) create mode 100644 cli/cmd/cloud.go create mode 100644 cli/cmd/login.go create mode 100644 cli/cmd/logout.go diff --git a/cli/cmd/cloud.go b/cli/cmd/cloud.go new file mode 100644 index 000000000..1ea4b6b2c --- /dev/null +++ b/cli/cmd/cloud.go @@ -0,0 +1,53 @@ +/* +Copyright © 2023 NAME HERE +*/ +package cmd + +import ( + "fmt" + "os" + + "github.com/keyval-dev/odigos/cli/cmd/resources" + "github.com/keyval-dev/odigos/cli/pkg/kube" + "github.com/spf13/cobra" +) + +// cloudCmd represents the cloud command +var cloudCmd = &cobra.Command{ + Use: "cloud", + Short: "Manage odigos cloud", + Long: `Used to interact with odigos managed service.`, + Run: func(cmd *cobra.Command, args []string) { + client, err := kube.CreateClient(cmd) + if err != nil { + kube.PrintClientErrorAndExit(err) + } + ctx := cmd.Context() + + ns, err := resources.GetOdigosNamespace(client, ctx) + if resources.IsErrNoOdigosNamespaceFound(err) { + fmt.Println("\033[31mERROR\033[0m no odigos installation found in the current cluster. use \"odigos install\" to install odigos in the cluster or check that kubeconfig is pointing to the correct cluster.") + os.Exit(1) + } else if err != nil { + fmt.Printf("\033[31mERROR\033[0m Failed to check if Odigos is already installed: %s\n", err) + os.Exit(1) + } + + isOdigosCloud, err := resources.IsOdigosCloud(ctx, client, ns) + if err != nil { + fmt.Println("Odigos upgrade failed - unable to read the current Odigos cloud configuration.") + os.Exit(1) + } + + if isOdigosCloud { + fmt.Println("Odigos cloud is currently enabled") + } else { + fmt.Println(`Odigos cloud is currently disabled. +To enable odigos cloud run 'odigos cloud login'`) + } + }, +} + +func init() { + rootCmd.AddCommand(cloudCmd) +} diff --git a/cli/cmd/login.go b/cli/cmd/login.go new file mode 100644 index 000000000..1f3c9d8cb --- /dev/null +++ b/cli/cmd/login.go @@ -0,0 +1,124 @@ +/* +Copyright © 2023 NAME HERE +*/ +package cmd + +import ( + "bufio" + "context" + "fmt" + "os" + "strconv" + + "github.com/keyval-dev/odigos/cli/cmd/resources" + "github.com/keyval-dev/odigos/cli/pkg/kube" + "github.com/keyval-dev/odigos/cli/pkg/labels" + "github.com/keyval-dev/odigos/cli/pkg/log" + "github.com/spf13/cobra" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/types" +) + +// This function requires knowledge of which objects should be updated once the odigos cloud secret is updated. +// It is not very generic, but it is the best we can do for now. +func restartPodsAfterCloudLogin(ctx context.Context, client *kube.Client, ns string, configVersion int) error { + + configVersionStr := strconv.Itoa(configVersion) + patch := fmt.Sprintf(`{"spec":{"template":{"metadata":{"annotations":{"%s":"%s"}}}}}`, labels.OdigosSystemConfigLabelKey, configVersionStr) + + _, err := client.AppsV1().Deployments(ns).Patch(ctx, resources.KeyvalProxyDeploymentName, types.StrategicMergePatchType, []byte(patch), metav1.PatchOptions{}) + if err != nil { + return err + } + + _, err = client.AppsV1().Deployments(ns).Patch(ctx, resources.OwnTelemetryCollectorDeploymentName, types.StrategicMergePatchType, []byte(patch), metav1.PatchOptions{}) + if err != nil { + return err + } + + return nil +} + +// both login and update trigger this function. +func updateApiKey(cmd *cobra.Command, args []string) { + client, err := kube.CreateClient(cmd) + if err != nil { + kube.PrintClientErrorAndExit(err) + } + ctx := cmd.Context() + + ns, err := resources.GetOdigosNamespace(client, ctx) + if resources.IsErrNoOdigosNamespaceFound(err) { + fmt.Println("\033[31mERROR\033[0m no odigos installation found in the current cluster. use \"odigos install\" to install odigos in the cluster or check that kubeconfig is pointing to the correct cluster.") + os.Exit(1) + } else if err != nil { + fmt.Printf("\033[31mERROR\033[0m Failed to check if Odigos is already installed: %s\n", err) + os.Exit(1) + } + + config, err := resources.GetCurrentConfig(ctx, client, ns) + if err != nil { + fmt.Println("Odigos cloud login failed - unable to read the current Odigos configuration.") + os.Exit(1) + } + config.Spec.ConfigVersion += 1 + + if odigosCloudApiKeyFlag == "" { + fmt.Println("Enter your odigos cloud api-key. You can find it here: https://app.keyval.io/settings/api-keys") + fmt.Print("api-key: ") + scanner := bufio.NewScanner(os.Stdin) + scanner.Scan() + odigosCloudApiKeyFlag = scanner.Text() + } + + isPrevOdigosCloud, err := resources.IsOdigosCloud(ctx, client, ns) + if err != nil { + fmt.Println("Odigos cloud login failed - unable to read the current Odigos cloud configuration.") + os.Exit(1) + } + + resourceManagers := resources.CreateResourceManagers(client, ns, true, &odigosCloudApiKeyFlag, &config.Spec) + err = resources.ApplyResourceManagers(ctx, client, resourceManagers, "Updating") + if err != nil { + fmt.Println("Odigos cloud login failed - unable to apply Odigos resources.") + os.Exit(1) + } + err = resources.DeleteOldOdigosSystemObjects(ctx, client, ns, config) + if err != nil { + fmt.Println("Odigos cloud login failed - unable to cleanup old Odigos resources.") + os.Exit(1) + } + + if isPrevOdigosCloud { + l := log.Print("Restarting relevant pods ...") + err := restartPodsAfterCloudLogin(ctx, client, ns, config.Spec.ConfigVersion) + if err != nil { + l.Error(err) + } + l.Success() + } +} + +// loginCmd represents the login command +var loginCmd = &cobra.Command{ + Use: "login", + Short: "Login to Odigos cloud", + Long: `Connect this Odigos installation to your odigos cloud account.`, + Run: updateApiKey, +} + +// loginCmd represents the login command +var updateCmd = &cobra.Command{ + Use: "update", + Short: "Update your Odigos Cloud api-key", + Long: `Use this command to update your Odigos Cloud api-key.`, + Run: updateApiKey, +} + +func init() { + cloudCmd.AddCommand(loginCmd) + cloudCmd.AddCommand(updateCmd) + + loginCmd.Flags().StringVarP(&odigosCloudApiKeyFlag, "api-key", "k", "", "api key for odigos cloud") + updateCmd.Flags().StringVarP(&odigosCloudApiKeyFlag, "api-key", "k", "", "api key for odigos cloud") +} diff --git a/cli/cmd/logout.go b/cli/cmd/logout.go new file mode 100644 index 000000000..99b782029 --- /dev/null +++ b/cli/cmd/logout.go @@ -0,0 +1,82 @@ +/* +Copyright © 2023 NAME HERE +*/ +package cmd + +import ( + "fmt" + "os" + + "github.com/keyval-dev/odigos/cli/cmd/resources" + "github.com/keyval-dev/odigos/cli/pkg/confirm" + "github.com/keyval-dev/odigos/cli/pkg/kube" + "github.com/spf13/cobra" +) + +// logoutCmd represents the logout command +var logoutCmd = &cobra.Command{ + Use: "logout", + Short: "Logout from Odigos cloud", + Long: `Disconnect this Odigos installation from your odigos cloud account. + + After running this command, you will no longer be able to control and monitor this Odigos installation from Odigos cloud. + You can run 'odigos ui' to manage your Odigos installation locally. + `, + Run: func(cmd *cobra.Command, args []string) { + client, err := kube.CreateClient(cmd) + if err != nil { + kube.PrintClientErrorAndExit(err) + } + ctx := cmd.Context() + + ns, err := resources.GetOdigosNamespace(client, ctx) + if resources.IsErrNoOdigosNamespaceFound(err) { + fmt.Println("\033[31mERROR\033[0m no odigos installation found in the current cluster. use \"odigos install\" to install odigos in the cluster or check that kubeconfig is pointing to the correct cluster.") + os.Exit(1) + } else if err != nil { + fmt.Printf("\033[31mERROR\033[0m Failed to check if Odigos is already installed: %s\n", err) + os.Exit(1) + } + + isOdigosCloud, err := resources.IsOdigosCloud(ctx, client, ns) + if err != nil { + fmt.Println("Odigos cloud logout failed - unable to read the current Odigos cloud configuration.") + os.Exit(1) + } + if !isOdigosCloud { + fmt.Println("The current odigos installation is not connected to Odigos cloud.") + os.Exit(1) + } + + fmt.Println("About to logout from Odigos cloud. You can still manager your Odigos installation locally with 'odigos ui'.") + confirmed, err := confirm.Ask("Are you sure?") + if err != nil || !confirmed { + fmt.Println("Aborting odigos cloud logout") + return + } + + config, err := resources.GetCurrentConfig(ctx, client, ns) + if err != nil { + fmt.Println("Odigos cloud logout failed - unable to read the current Odigos configuration.") + os.Exit(1) + } + config.Spec.ConfigVersion += 1 + + emptyApiKey := "" + resourceManagers := resources.CreateResourceManagers(client, ns, false, &emptyApiKey, &config.Spec) + err = resources.ApplyResourceManagers(ctx, client, resourceManagers, "Updating") + if err != nil { + fmt.Println("Odigos cloud logout failed - unable to apply Odigos resources.") + os.Exit(1) + } + err = resources.DeleteOldOdigosSystemObjects(ctx, client, ns, config) + if err != nil { + fmt.Println("Odigos cloud logout failed - unable to cleanup old Odigos resources.") + os.Exit(1) + } + }, +} + +func init() { + cloudCmd.AddCommand(logoutCmd) +} diff --git a/cli/cmd/resources/keyvalproxy.go b/cli/cmd/resources/keyvalproxy.go index 82abc3416..de62342d9 100644 --- a/cli/cmd/resources/keyvalproxy.go +++ b/cli/cmd/resources/keyvalproxy.go @@ -24,7 +24,7 @@ const ( keyvalProxyServiceName = "odigos-cloud-k8s" keyvalProxyImage = "keyval/odigos-proxy-k8s" keyvalProxyAppName = "odigos-cloud-proxy" - keyvalProxyDeploymentName = "odigos-cloud-proxy" + KeyvalProxyDeploymentName = "odigos-cloud-proxy" keyvalProxyServiceAccountName = "odigos-cloud-proxy" keyvalProxyRoleName = "odigos-cloud-proxy" keyvalProxyRoleBindingName = "odigos-cloud-proxy" @@ -233,7 +233,7 @@ func NewKeyvalProxyDeployment(version string, ns string, imagePrefix string) *ap APIVersion: "apps/v1", }, ObjectMeta: metav1.ObjectMeta{ - Name: keyvalProxyDeploymentName, + Name: KeyvalProxyDeploymentName, Namespace: ns, Labels: map[string]string{ "app": keyvalProxyAppName, diff --git a/cli/cmd/resources/owntelemetry.go b/cli/cmd/resources/owntelemetry.go index 292bd5795..ac3af1ad1 100644 --- a/cli/cmd/resources/owntelemetry.go +++ b/cli/cmd/resources/owntelemetry.go @@ -24,7 +24,7 @@ const ( ownTelemetryCollectorImage = "otel/opentelemetry-collector:0.86.0" ownTelemetryCollectorAppName = "own-telemetry-collector" ownTelemetryCollectorServiceName = "own-telemetry-collector" - ownTelemetryCollectorDeploymentName = "own-telemetry-collector" + OwnTelemetryCollectorDeploymentName = "own-telemetry-collector" ownTelemetryCollectorContainerName = "own-telemetry-collector" ownTelemetryCollectorConfigDir = "/etc/otelcol" // since we use otel/opentelemetry-collector which expect the image at this path ownTelemetryCollectorConfigConfigFileName = "config.yaml" // since we use otel/opentelemetry-collector which expect the config file to be called this way @@ -140,7 +140,7 @@ func NewOwnTelemetryCollectorDeployment(ns string) *appsv1.Deployment { APIVersion: "apps/v1", }, ObjectMeta: metav1.ObjectMeta{ - Name: ownTelemetryCollectorDeploymentName, + Name: OwnTelemetryCollectorDeploymentName, Namespace: ns, Labels: map[string]string{ "app": ownTelemetryCollectorAppName, From e7086d4a9cc237d6cbf7143eb561eeeebc1b4376 Mon Sep 17 00:00:00 2001 From: Amir Blum Date: Tue, 31 Oct 2023 15:50:35 +0200 Subject: [PATCH 2/3] chore: remove auto copyrights --- cli/cmd/cloud.go | 3 --- cli/cmd/login.go | 3 --- cli/cmd/logout.go | 3 --- 3 files changed, 9 deletions(-) diff --git a/cli/cmd/cloud.go b/cli/cmd/cloud.go index 1ea4b6b2c..c71ac507b 100644 --- a/cli/cmd/cloud.go +++ b/cli/cmd/cloud.go @@ -1,6 +1,3 @@ -/* -Copyright © 2023 NAME HERE -*/ package cmd import ( diff --git a/cli/cmd/login.go b/cli/cmd/login.go index 1f3c9d8cb..598c0454d 100644 --- a/cli/cmd/login.go +++ b/cli/cmd/login.go @@ -1,6 +1,3 @@ -/* -Copyright © 2023 NAME HERE -*/ package cmd import ( diff --git a/cli/cmd/logout.go b/cli/cmd/logout.go index 99b782029..b24b7695a 100644 --- a/cli/cmd/logout.go +++ b/cli/cmd/logout.go @@ -1,6 +1,3 @@ -/* -Copyright © 2023 NAME HERE -*/ package cmd import ( From b64d4a32b2e10171050afcb5d93d190e12b945d4 Mon Sep 17 00:00:00 2001 From: Amir Blum Date: Tue, 31 Oct 2023 16:11:32 +0200 Subject: [PATCH 3/3] fix: verify token is uuid format --- cli/cmd/cloud.go | 12 +++++++++++- cli/cmd/install.go | 8 ++++++++ cli/cmd/login.go | 6 ++++++ 3 files changed, 25 insertions(+), 1 deletion(-) diff --git a/cli/cmd/cloud.go b/cli/cmd/cloud.go index c71ac507b..0076a7750 100644 --- a/cli/cmd/cloud.go +++ b/cli/cmd/cloud.go @@ -4,11 +4,21 @@ import ( "fmt" "os" + "github.com/google/uuid" "github.com/keyval-dev/odigos/cli/cmd/resources" "github.com/keyval-dev/odigos/cli/pkg/kube" "github.com/spf13/cobra" ) +func verifyOdigosCloudApiKey(apikey string) error { + _, err := uuid.Parse(apikey) + if err != nil { + return fmt.Errorf("invalid apikey format. expected uuid format") + } + + return nil +} + // cloudCmd represents the cloud command var cloudCmd = &cobra.Command{ Use: "cloud", @@ -32,7 +42,7 @@ var cloudCmd = &cobra.Command{ isOdigosCloud, err := resources.IsOdigosCloud(ctx, client, ns) if err != nil { - fmt.Println("Odigos upgrade failed - unable to read the current Odigos cloud configuration.") + fmt.Println("Odigos cloud failed - unable to read the current Odigos cloud configuration.") os.Exit(1) } diff --git a/cli/cmd/install.go b/cli/cmd/install.go index 6e046de96..5f634131e 100644 --- a/cli/cmd/install.go +++ b/cli/cmd/install.go @@ -79,6 +79,14 @@ This command will install k8s components that will auto-instrument your applicat // create resource managers specific for install isOdigosCloud := odigosCloudApiKeyFlag != "" + if isOdigosCloud { + err = verifyOdigosCloudApiKey(odigosCloudApiKeyFlag) + if err != nil { + fmt.Println("Odigos install failed - invalid api-key format.") + os.Exit(1) + } + } + resourceManagers := resources.CreateResourceManagers(client, ns, isOdigosCloud, &odigosCloudApiKeyFlag, &config) err = resources.ApplyResourceManagers(ctx, client, resourceManagers, "Creating") if err != nil { diff --git a/cli/cmd/login.go b/cli/cmd/login.go index 598c0454d..134397d25 100644 --- a/cli/cmd/login.go +++ b/cli/cmd/login.go @@ -68,6 +68,12 @@ func updateApiKey(cmd *cobra.Command, args []string) { odigosCloudApiKeyFlag = scanner.Text() } + err = verifyOdigosCloudApiKey(odigosCloudApiKeyFlag) + if err != nil { + fmt.Println("Odigos cloud login failed - invalid api-key format.") + os.Exit(1) + } + isPrevOdigosCloud, err := resources.IsOdigosCloud(ctx, client, ns) if err != nil { fmt.Println("Odigos cloud login failed - unable to read the current Odigos cloud configuration.")