Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add "cloud" command to odigos cli for login and logout #727

Merged
merged 3 commits into from
Oct 31, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
60 changes: 60 additions & 0 deletions cli/cmd/cloud.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
package cmd

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",
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 cloud 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)
}
8 changes: 8 additions & 0 deletions cli/cmd/install.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
127 changes: 127 additions & 0 deletions cli/cmd/login.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,127 @@
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()
}

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.")
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")
}
79 changes: 79 additions & 0 deletions cli/cmd/logout.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
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)
}
4 changes: 2 additions & 2 deletions cli/cmd/resources/keyvalproxy.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -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,
Expand Down
4 changes: 2 additions & 2 deletions cli/cmd/resources/owntelemetry.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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,
Expand Down