Skip to content

Refactor previewctl install-context #14241

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

Merged
merged 1 commit into from
Nov 3, 2022
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
154 changes: 65 additions & 89 deletions dev/preview/previewctl/cmd/credentials.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,151 +6,127 @@ package cmd

import (
"context"
"fmt"
"os"
"path/filepath"

"github.com/cockroachdb/errors"
kctx "github.com/gitpod-io/gitpod/previewctl/pkg/k8s/context"
"github.com/gitpod-io/gitpod/previewctl/pkg/k8s/context/gke"
"github.com/gitpod-io/gitpod/previewctl/pkg/k8s/context/harvester"
"github.com/sirupsen/logrus"
"github.com/spf13/cobra"
"k8s.io/client-go/tools/clientcmd"
"k8s.io/client-go/tools/clientcmd/api"
"k8s.io/client-go/util/homedir"

"github.com/gitpod-io/gitpod/previewctl/pkg/gcloud"
kube "github.com/gitpod-io/gitpod/previewctl/pkg/k8s"
)

var (
serviceAccountPath string
kubeConfigSavePath string
DefaultKubeConfigPath = filepath.Join(homedir.HomeDir(), clientcmd.RecommendedHomeDir, clientcmd.RecommendedFileName)
)

const (
coreDevClusterName = "core-dev"
coreDevProjectID = "gitpod-core-dev"
coreDevClusterZone = "europe-west1-b"
coreDevDesiredContextName = "dev"
coreDevClusterName = "core-dev"
coreDevProjectID = "gitpod-core-dev"
coreDevClusterZone = "europe-west1-b"
)

type getCredentialsOpts struct {
gcpClient *gcloud.Config
logger *logrus.Logger
logger *logrus.Logger

getCredentialsMap map[string]func(ctx context.Context) (*api.Config, error)
configMap map[string]*api.Config
serviceAccountPath string
kubeConfigSavePath string
}

func newGetCredentialsCommand(logger *logrus.Logger) *cobra.Command {
var err error
var client *gcloud.Config
ctx := context.Background()
opts := &getCredentialsOpts{
logger: logger,
configMap: map[string]*api.Config{},
logger: logger,
}

cmd := &cobra.Command{
Use: "get-credentials",
Long: `previewctl get-credentials retrieves the kubernetes configs for core-dev and harvester clusters,
merges them with the default config, and outputs them either to stdout or to a file.`,
PersistentPreRunE: func(cmd *cobra.Command, args []string) error {
client, err = gcloud.New(ctx, serviceAccountPath)
merges them with the default config, and saves them to the path in KUBECONFIG or the default path '~/.kube/config'"`,
RunE: func(cmd *cobra.Command, args []string) error {
configs, err := opts.getCredentials(ctx)
if err != nil {
return err
}

opts.gcpClient = client
opts.getCredentialsMap = map[string]func(ctx context.Context) (*api.Config, error){
"dev": opts.getCoreDevKubeConfig,
"harvester": opts.getHarvesterKubeConfig,
}

return nil
},
RunE: func(cmd *cobra.Command, args []string) error {
for _, kc := range []string{coreDevDesiredContextName, "harvester"} {
if ok := hasAccess(logger, kc); !ok {
config, err := opts.getCredentialsMap[kc](ctx)
if err != nil {
return err
}

opts.configMap[kc] = config
}
}

return opts.mergeContexts()
opts.kubeConfigSavePath = getKubeConfigPath()
return kube.OutputContext(opts.kubeConfigSavePath, configs)
},
}

cmd.PersistentFlags().StringVar(&serviceAccountPath, "gcp-service-account", "", "path to the GCP service account to use")
cmd.PersistentFlags().StringVar(&kubeConfigSavePath, "kube-save-path", "", "path to save the generated kubeconfig to")
cmd.PersistentFlags().StringVar(&opts.serviceAccountPath, "gcp-service-account", "", "path to the GCP service account to use")

return cmd
}

func hasAccess(logger *logrus.Logger, contextName string) bool {
config, err := kube.NewFromDefaultConfigWithContext(logger, contextName)
if err != nil {
if errors.Is(err, kube.ErrContextNotExists) {
return false
}

logger.Fatal(err)
}

return config.HasAccess()
}

func (o *getCredentialsOpts) mergeContexts() error {
var err error
configs := make([]*api.Config, 0, len(o.configMap))

for _, config := range o.configMap {
configs = append(configs, config)
}
func (o *getCredentialsOpts) getCredentials(ctx context.Context) (*api.Config, error) {
gkeLoader, err := gke.New(ctx, gke.ConfigLoaderOpts{
Logger: o.logger,
ServiceAccountPath: o.serviceAccountPath,
Name: coreDevClusterName,
ProjectID: coreDevProjectID,
Zone: coreDevClusterZone,
RenamedContextName: gke.DevContextName,
})

finalConfig, err := kube.MergeWithDefaultConfig(configs...)
if err != nil {
return err
return nil, errors.Wrap(err, "failed to instantiate gke loader")
}

if kubeConfigSavePath != "" {
return clientcmd.WriteToFile(*finalConfig, kubeConfigSavePath)
loaderMap := map[string]kctx.Loader{
gke.DevContextName: gkeLoader,
harvester.ContextName: &harvester.ConfigLoader{},
}

bytes, err := clientcmd.Write(*finalConfig)
if err != nil {
return err
}
for _, contextName := range []string{gke.DevContextName, harvester.ContextName} {
loader := loaderMap[contextName]
if kc, err := kube.NewFromDefaultConfigWithContext(o.logger, contextName); err == nil && kc.HasAccess(ctx) {
continue
}

fmt.Println(string(bytes))
kc, err := loader.Load(ctx)
if err != nil {
return nil, err
}

return err
}
configs, err := kube.MergeContextsWithDefault(kc)
if err != nil {
return nil, err
}

func (o *getCredentialsOpts) getCoreDevKubeConfig(ctx context.Context) (*api.Config, error) {
coreDevConfig, err := o.gcpClient.GenerateConfig(ctx, coreDevClusterName, coreDevProjectID, coreDevClusterZone, coreDevDesiredContextName)
if err != nil {
return nil, err
// always save the context at the default path
err = kube.OutputContext(DefaultKubeConfigPath, configs)
if err != nil {
return nil, err
}
}

return coreDevConfig, nil
return kube.MergeContextsWithDefault()
}

func (o *getCredentialsOpts) getHarvesterKubeConfig(ctx context.Context) (*api.Config, error) {
coreDevClientConfig, err := clientcmd.NewNonInteractiveClientConfig(*o.configMap[coreDevDesiredContextName], coreDevDesiredContextName, nil, nil).ClientConfig()
func hasAccess(ctx context.Context, logger *logrus.Logger, contextName string) bool {
config, err := kube.NewFromDefaultConfigWithContext(logger, contextName)
if err != nil {
return nil, err
}
if errors.Is(err, kube.ErrContextNotExists) {
return false
}

kubeConfig, err := kube.NewWithConfig(o.logger, coreDevClientConfig)
if err != nil {
return nil, err
logger.Fatal(err)
}

harvesterConfig, err := kubeConfig.GetHarvesterKubeConfig(ctx)
if err != nil {
return nil, err
return config.HasAccess(ctx)
}

func getKubeConfigPath() string {
if v := os.Getenv("KUBECONFIG"); v != "" {
DefaultKubeConfigPath = v
}

return harvesterConfig, nil
return DefaultKubeConfigPath
}
81 changes: 70 additions & 11 deletions dev/preview/previewctl/cmd/install_context.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,25 +5,56 @@
package cmd

import (
"context"
"fmt"
"io/fs"
"os"
"time"

"github.com/cockroachdb/errors"
"github.com/sirupsen/logrus"
"github.com/spf13/cobra"
"k8s.io/client-go/util/homedir"

kube "github.com/gitpod-io/gitpod/previewctl/pkg/k8s"
"github.com/gitpod-io/gitpod/previewctl/pkg/preview"
)

var (
watch bool
timeout time.Duration
)
type installContextCmdOpts struct {
logger *logrus.Logger

watch bool
timeout time.Duration
kubeConfigSavePath string
sshPrivateKeyPath string

getCredentialsOpts *getCredentialsOpts
}

func installContextCmd(logger *logrus.Logger) *cobra.Command {
func newInstallContextCmd(logger *logrus.Logger) *cobra.Command {
ctx := context.Background()

opts := installContextCmdOpts{
logger: logger,
getCredentialsOpts: &getCredentialsOpts{
logger: logger,
},
}

// Used to ensure that we only install contexts
var lastSuccessfulPreviewEnvironment *preview.Preview = nil

install := func(timeout time.Duration) error {
name, err := preview.GetName(branch)
if err != nil {
return err
}

if hasAccess(ctx, logger, name) {
opts.logger.Debugf("Access to [%s] already configured and connections can be established", name)
return nil
}

p, err := preview.New(branch, logger)

if err != nil {
Expand All @@ -35,19 +66,44 @@ func installContextCmd(logger *logrus.Logger) *cobra.Command {
return nil
}

err = p.InstallContext(true, timeout)
err = p.InstallContext(ctx, preview.InstallCtxOpts{
Wait: opts.watch,
Timeout: opts.timeout,
KubeSavePath: opts.kubeConfigSavePath,
SSHPrivateKeyPath: opts.sshPrivateKeyPath,
})

if err == nil {
lastSuccessfulPreviewEnvironment = p
}

return err
}

cmd := &cobra.Command{
Use: "install-context",
Short: "Installs the kubectl context of a preview environment.",
RunE: func(cmd *cobra.Command, args []string) error {
PreRunE: func(cmd *cobra.Command, args []string) error {
configs, err := opts.getCredentialsOpts.getCredentials(ctx)
if err != nil {
return err
}

opts.kubeConfigSavePath = getKubeConfigPath()

err = kube.OutputContext(opts.kubeConfigSavePath, configs)
if err != nil {
return err
}

if watch {
if _, err = os.Stat(opts.sshPrivateKeyPath); errors.Is(err, fs.ErrNotExist) {
return preview.InstallVMSSHKeys()
}

return nil
},
RunE: func(cmd *cobra.Command, args []string) error {
if opts.watch {
for range time.Tick(15 * time.Second) {
// We're using a short timeout here to handle the scenario where someone switches
// to a branch that doens't have a preview envrionment. In that case the default
Expand All @@ -59,14 +115,17 @@ func installContextCmd(logger *logrus.Logger) *cobra.Command {
}
}
} else {
return install(timeout)
return install(opts.timeout)
}

return nil
},
}

cmd.Flags().BoolVar(&watch, "watch", false, "If watch is enabled, previewctl will keep trying to install the kube-context every 15 seconds.")
cmd.Flags().DurationVarP(&timeout, "timeout", "t", 10*time.Minute, "Timeout before considering the installation failed")
cmd.Flags().BoolVar(&opts.watch, "watch", false, "If watch is enabled, previewctl will keep trying to install the kube-context every 15 seconds.")
cmd.Flags().DurationVarP(&opts.timeout, "timeout", "t", 10*time.Minute, "Timeout before considering the installation failed")
cmd.PersistentFlags().StringVar(&opts.sshPrivateKeyPath, "private-key-path", fmt.Sprintf("%s/.ssh/vm_id_rsa", homedir.HomeDir()), "path to the private key used to authenticate with the VM")
cmd.PersistentFlags().StringVar(&opts.getCredentialsOpts.serviceAccountPath, "gcp-service-account", "", "path to the GCP service account to use")

return cmd
}
4 changes: 3 additions & 1 deletion dev/preview/previewctl/cmd/list_previews.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@
package cmd

import (
"context"

"github.com/sirupsen/logrus"
"github.com/spf13/cobra"

Expand All @@ -26,7 +28,7 @@ func listPreviewsCmd(logger *logrus.Logger) *cobra.Command {
return err
}

err = p.ListAllPreviews()
err = p.ListAllPreviews(context.Background())
if err != nil {
logger.WithFields(logrus.Fields{"err": err}).Fatal("Failed to list previews.")
}
Expand Down
Loading