From b7bdb8ffe7402cd1c4c6132a6a52253a1b6b0df9 Mon Sep 17 00:00:00 2001 From: pasha-codefresh Date: Mon, 24 Jan 2022 19:24:39 +0200 Subject: [PATCH] feat: Application generation strategies and clusters generation (#8263) feat: Application generation strategies and clusters generation (#8263) Signed-off-by: pashavictorovich --- hack/gen-resources/cmd/commands/all.go | 76 ------ .../gen-resources/cmd/commands/application.go | 59 ----- hack/gen-resources/cmd/commands/cmd.go | 132 ++++++++++ hack/gen-resources/cmd/commands/project.go | 60 ----- hack/gen-resources/cmd/commands/repos.go | 61 ----- hack/gen-resources/cmd/commands/root.go | 42 ---- .../gen-resources/examples/gen_resources.yaml | 22 ++ .../generators/application_generator.go | 101 ++++++-- .../generators/cluster_generator.go | 235 ++++++++++++++++++ hack/gen-resources/generators/generator.go | 12 +- .../generators/project_generator.go | 11 +- .../generators/repo_generator.go | 15 +- hack/gen-resources/util/gen_options_parser.go | 60 +++++ hack/gen-resources/{tools => util}/kube.go | 14 +- .../{tools => util}/progress_bar.go | 2 +- hack/gen-resources/util/util.go | 21 ++ util/helm/cmd.go | 4 + 17 files changed, 589 insertions(+), 338 deletions(-) delete mode 100644 hack/gen-resources/cmd/commands/all.go delete mode 100644 hack/gen-resources/cmd/commands/application.go create mode 100644 hack/gen-resources/cmd/commands/cmd.go delete mode 100644 hack/gen-resources/cmd/commands/project.go delete mode 100644 hack/gen-resources/cmd/commands/repos.go delete mode 100644 hack/gen-resources/cmd/commands/root.go create mode 100644 hack/gen-resources/examples/gen_resources.yaml create mode 100644 hack/gen-resources/generators/cluster_generator.go create mode 100644 hack/gen-resources/util/gen_options_parser.go rename hack/gen-resources/{tools => util}/kube.go (82%) rename hack/gen-resources/{tools => util}/progress_bar.go (98%) create mode 100644 hack/gen-resources/util/util.go diff --git a/hack/gen-resources/cmd/commands/all.go b/hack/gen-resources/cmd/commands/all.go deleted file mode 100644 index e356384bd8198..0000000000000 --- a/hack/gen-resources/cmd/commands/all.go +++ /dev/null @@ -1,76 +0,0 @@ -package commands - -import ( - "log" - "os" - - generator "github.com/argoproj/argo-cd/v2/hack/gen-resources/generators" - "github.com/argoproj/argo-cd/v2/hack/gen-resources/tools" - - "github.com/spf13/cobra" -) - -func NewAllResourcesCommand(opts *generator.GenerateOpts) *cobra.Command { - var command = &cobra.Command{ - Use: "all", - Short: "Manage all resources", - Long: "Manage all resources", - Run: func(c *cobra.Command, args []string) { - c.HelpFunc()(c, args) - os.Exit(1) - }, - } - command.AddCommand(NewAllResourcesGenerationCommand(opts)) - command.AddCommand(NewAllResourcesCleanCommand(opts)) - return command -} - -func NewAllResourcesGenerationCommand(opts *generator.GenerateOpts) *cobra.Command { - var command = &cobra.Command{ - Use: "generate", - Short: "Generate all resources", - Long: "Generate all resources", - Run: func(c *cobra.Command, args []string) { - clientSet := tools.ConnectToK8sArgoClientSet() - pg := generator.NewProjectGenerator(clientSet) - ag := generator.NewApplicationGenerator(clientSet) - rg := generator.NewRepoGenerator(tools.ConnectToK8sClientSet()) - err := pg.Generate(opts) - if err != nil { - log.Fatalf("Something went wrong, %v", err.Error()) - } - err = ag.Generate(opts) - if err != nil { - log.Fatalf("Something went wrong, %v", err.Error()) - } - err = rg.Generate(opts) - if err != nil { - log.Fatalf("Something went wrong, %v", err.Error()) - } - }, - } - command.PersistentFlags().IntVar(&opts.Samples, "samples", 1, "Amount of samples") - return command -} - -func NewAllResourcesCleanCommand(opts *generator.GenerateOpts) *cobra.Command { - var command = &cobra.Command{ - Use: "clean", - Short: "Clean all resources", - Long: "Clean all resources", - Run: func(c *cobra.Command, args []string) { - clientSet := tools.ConnectToK8sArgoClientSet() - pg := generator.NewProjectGenerator(clientSet) - ag := generator.NewApplicationGenerator(clientSet) - err := pg.Clean(opts) - if err != nil { - log.Fatalf("Something went wrong, %v", err.Error()) - } - err = ag.Clean(opts) - if err != nil { - log.Fatalf("Something went wrong, %v", err.Error()) - } - }, - } - return command -} diff --git a/hack/gen-resources/cmd/commands/application.go b/hack/gen-resources/cmd/commands/application.go deleted file mode 100644 index 0b1247c0c8ca7..0000000000000 --- a/hack/gen-resources/cmd/commands/application.go +++ /dev/null @@ -1,59 +0,0 @@ -package commands - -import ( - "log" - "os" - - generator "github.com/argoproj/argo-cd/v2/hack/gen-resources/generators" - "github.com/argoproj/argo-cd/v2/hack/gen-resources/tools" - - "github.com/spf13/cobra" -) - -func NewApplicationCommand(opts *generator.GenerateOpts) *cobra.Command { - var command = &cobra.Command{ - Use: "application", - Short: "Manage applications", - Long: "Manage applications", - Run: func(c *cobra.Command, args []string) { - c.HelpFunc()(c, args) - os.Exit(1) - }, - } - command.AddCommand(NewApplicationGenerationCommand(opts)) - command.AddCommand(NewApplicationCleanCommand(opts)) - return command -} - -func NewApplicationGenerationCommand(opts *generator.GenerateOpts) *cobra.Command { - var command = &cobra.Command{ - Use: "generate", - Short: "Generate applications", - Long: "Generate applications", - Run: func(c *cobra.Command, args []string) { - pg := generator.NewApplicationGenerator(tools.ConnectToK8sArgoClientSet()) - err := pg.Generate(opts) - if err != nil { - log.Fatalf("Something went wrong, %v", err.Error()) - } - }, - } - command.PersistentFlags().IntVar(&opts.Samples, "samples", 1, "Amount of samples") - return command -} - -func NewApplicationCleanCommand(opts *generator.GenerateOpts) *cobra.Command { - var command = &cobra.Command{ - Use: "clean", - Short: "Clean applications", - Long: "Clean applications", - Run: func(c *cobra.Command, args []string) { - pg := generator.NewApplicationGenerator(tools.ConnectToK8sArgoClientSet()) - err := pg.Clean(opts) - if err != nil { - log.Fatalf("Something went wrong, %v", err.Error()) - } - }, - } - return command -} diff --git a/hack/gen-resources/cmd/commands/cmd.go b/hack/gen-resources/cmd/commands/cmd.go new file mode 100644 index 0000000000000..7c95703e5dbd5 --- /dev/null +++ b/hack/gen-resources/cmd/commands/cmd.go @@ -0,0 +1,132 @@ +package commands + +import ( + "context" + "log" + + "github.com/spf13/cobra" + + generator "github.com/argoproj/argo-cd/v2/hack/gen-resources/generators" + "github.com/argoproj/argo-cd/v2/util/db" + "github.com/argoproj/argo-cd/v2/util/settings" + + cmdutil "github.com/argoproj/argo-cd/v2/cmd/util" + "github.com/argoproj/argo-cd/v2/hack/gen-resources/util" + "github.com/argoproj/argo-cd/v2/util/cli" +) + +const ( + cliName = "argocd-generator" +) + +func init() { + cobra.OnInitialize(initConfig) +} + +func initConfig() { + cli.SetLogFormat(cmdutil.LogFormat) + cli.SetLogLevel(cmdutil.LogLevel) +} + +// NewCommand returns a new instance of an argocd command +func NewCommand() *cobra.Command { + + var generateOpts util.GenerateOpts + + var command = &cobra.Command{ + Use: cliName, + Short: "Generator for argocd resources", + Run: func(c *cobra.Command, args []string) { + c.HelpFunc()(c, args) + }, + DisableAutoGenTag: true, + } + + command.AddCommand(NewGenerateCommand(&generateOpts)) + command.AddCommand(NewCleanCommand(&generateOpts)) + + command.PersistentFlags().StringVar(&generateOpts.Namespace, "kube-namespace", "argocd", "Name of the namespace on which Argo agent should be installed [$KUBE_NAMESPACE]") + return command +} + +func NewGenerateCommand(opts *util.GenerateOpts) *cobra.Command { + var file string + var command = &cobra.Command{ + Use: "generate [-f file]", + Short: "Generate entities", + Long: "Generate entities", + Run: func(c *cobra.Command, args []string) { + log.Printf("Retrieve configuration from %s", file) + err := util.Parse(opts, file) + if err != nil { + log.Fatalf("Failed to retrieve configuration, %v", err.Error()) + } + argoClientSet := util.ConnectToK8sArgoClientSet() + clientSet := util.ConnectToK8sClientSet() + + settingsMgr := settings.NewSettingsManager(context.TODO(), clientSet, opts.Namespace) + argoDB := db.NewDB(opts.Namespace, settingsMgr, clientSet) + + pg := generator.NewProjectGenerator(argoClientSet) + ag := generator.NewApplicationGenerator(argoClientSet, clientSet, argoDB) + rg := generator.NewRepoGenerator(util.ConnectToK8sClientSet()) + cg := generator.NewClusterGenerator(argoDB, util.ConnectToK8sClientSet(), util.ConnectToK8sConfig()) + + err = pg.Generate(opts) + if err != nil { + log.Fatalf("Failed to generate projects, %v", err.Error()) + } + err = rg.Generate(opts) + if err != nil { + log.Fatalf("Failed to generate repositories, %v", err.Error()) + } + err = cg.Generate(opts) + if err != nil { + log.Fatalf("Failed to generate clusters, %v", err.Error()) + } + err = ag.Generate(opts) + if err != nil { + log.Fatalf("Failed to generate applications, %v", err.Error()) + } + }, + } + command.Flags().StringVarP(&file, "file", "f", "", "") + return command +} + +func NewCleanCommand(opts *util.GenerateOpts) *cobra.Command { + var command = &cobra.Command{ + Use: "clean", + Short: "Clean entities", + Long: "Clean entities", + Run: func(c *cobra.Command, args []string) { + argoClientSet := util.ConnectToK8sArgoClientSet() + clientSet := util.ConnectToK8sClientSet() + settingsMgr := settings.NewSettingsManager(context.TODO(), clientSet, opts.Namespace) + argoDB := db.NewDB(opts.Namespace, settingsMgr, clientSet) + + pg := generator.NewProjectGenerator(argoClientSet) + ag := generator.NewApplicationGenerator(argoClientSet, clientSet, argoDB) + cg := generator.NewClusterGenerator(argoDB, clientSet, util.ConnectToK8sConfig()) + rg := generator.NewRepoGenerator(clientSet) + + err := pg.Clean(opts) + if err != nil { + log.Fatalf("Failed to clean projects, %v", err.Error()) + } + err = ag.Clean(opts) + if err != nil { + log.Fatalf("Failed to clean applications, %v", err.Error()) + } + err = cg.Clean(opts) + if err != nil { + log.Fatalf("Failed to clean clusters, %v", err.Error()) + } + err = rg.Clean(opts) + if err != nil { + log.Fatalf("Failed to clean repositores, %v", err.Error()) + } + }, + } + return command +} diff --git a/hack/gen-resources/cmd/commands/project.go b/hack/gen-resources/cmd/commands/project.go deleted file mode 100644 index 60185d5978034..0000000000000 --- a/hack/gen-resources/cmd/commands/project.go +++ /dev/null @@ -1,60 +0,0 @@ -package commands - -import ( - "log" - "os" - - "github.com/argoproj/argo-cd/v2/hack/gen-resources/tools" - - generator "github.com/argoproj/argo-cd/v2/hack/gen-resources/generators" - - "github.com/spf13/cobra" -) - -func NewProjectCommand(opts *generator.GenerateOpts) *cobra.Command { - var command = &cobra.Command{ - Use: "project", - Short: "Manage projects", - Long: "Manage projects", - Run: func(c *cobra.Command, args []string) { - c.HelpFunc()(c, args) - os.Exit(1) - }, - } - command.AddCommand(NewProjectGenerationCommand(opts)) - command.AddCommand(NewProjectCleanCommand(opts)) - return command -} - -func NewProjectGenerationCommand(opts *generator.GenerateOpts) *cobra.Command { - var command = &cobra.Command{ - Use: "generate", - Short: "Generate projects", - Long: "Generate projects", - Run: func(c *cobra.Command, args []string) { - pg := generator.NewProjectGenerator(tools.ConnectToK8sArgoClientSet()) - err := pg.Generate(opts) - if err != nil { - log.Fatalf("Something went wrong, %v", err.Error()) - } - }, - } - command.PersistentFlags().IntVar(&opts.Samples, "samples", 1, "Amount of samples") - return command -} - -func NewProjectCleanCommand(opts *generator.GenerateOpts) *cobra.Command { - var command = &cobra.Command{ - Use: "clean", - Short: "Clean projects", - Long: "Clean projects", - Run: func(c *cobra.Command, args []string) { - pg := generator.NewProjectGenerator(tools.ConnectToK8sArgoClientSet()) - err := pg.Clean(opts) - if err != nil { - log.Fatalf("Something went wrong, %v", err.Error()) - } - }, - } - return command -} diff --git a/hack/gen-resources/cmd/commands/repos.go b/hack/gen-resources/cmd/commands/repos.go deleted file mode 100644 index 4906aa7f4ad16..0000000000000 --- a/hack/gen-resources/cmd/commands/repos.go +++ /dev/null @@ -1,61 +0,0 @@ -package commands - -import ( - "log" - "os" - - "github.com/argoproj/argo-cd/v2/hack/gen-resources/tools" - - generator "github.com/argoproj/argo-cd/v2/hack/gen-resources/generators" - - "github.com/spf13/cobra" -) - -func NewReposCommand(opts *generator.GenerateOpts) *cobra.Command { - var command = &cobra.Command{ - Use: "repos", - Short: "Manage repos", - Long: "Manage repos", - Run: func(c *cobra.Command, args []string) { - c.HelpFunc()(c, args) - os.Exit(1) - }, - } - command.AddCommand(NewReposGenerationCommand(opts)) - command.AddCommand(NewReposCleanCommand(opts)) - return command -} - -func NewReposGenerationCommand(opts *generator.GenerateOpts) *cobra.Command { - var command = &cobra.Command{ - Use: "generate", - Short: "Generate repos", - Long: "Generate repos", - Run: func(c *cobra.Command, args []string) { - rg := generator.NewRepoGenerator(tools.ConnectToK8sClientSet()) - err := rg.Generate(opts) - if err != nil { - log.Fatalf("Something went wrong, %v", err.Error()) - } - }, - } - command.PersistentFlags().StringVar(&opts.GithubToken, "token", "", "Github token") - command.PersistentFlags().IntVar(&opts.Samples, "samples", 1, "Amount of samples") - return command -} - -func NewReposCleanCommand(opts *generator.GenerateOpts) *cobra.Command { - var command = &cobra.Command{ - Use: "clean", - Short: "Clean repos", - Long: "Clean repos", - Run: func(c *cobra.Command, args []string) { - pg := generator.NewRepoGenerator(tools.ConnectToK8sClientSet()) - err := pg.Clean(opts) - if err != nil { - log.Fatalf("Something went wrong, %v", err.Error()) - } - }, - } - return command -} diff --git a/hack/gen-resources/cmd/commands/root.go b/hack/gen-resources/cmd/commands/root.go deleted file mode 100644 index 06a8ae871a1eb..0000000000000 --- a/hack/gen-resources/cmd/commands/root.go +++ /dev/null @@ -1,42 +0,0 @@ -package commands - -import ( - "github.com/spf13/cobra" - - generator "github.com/argoproj/argo-cd/v2/hack/gen-resources/generators" - - cmdutil "github.com/argoproj/argo-cd/v2/cmd/util" - "github.com/argoproj/argo-cd/v2/util/cli" -) - -const ( - cliName = "argocd-generator" -) - -func init() { - cobra.OnInitialize(initConfig) -} - -func initConfig() { - cli.SetLogFormat(cmdutil.LogFormat) - cli.SetLogLevel(cmdutil.LogLevel) -} - -// NewCommand returns a new instance of an argocd command -func NewCommand() *cobra.Command { - var generateOpts generator.GenerateOpts - var command = &cobra.Command{ - Use: cliName, - Short: "Generator for argocd resources", - Run: func(c *cobra.Command, args []string) { - c.HelpFunc()(c, args) - }, - DisableAutoGenTag: true, - } - command.AddCommand(NewProjectCommand(&generateOpts)) - command.AddCommand(NewApplicationCommand(&generateOpts)) - command.AddCommand(NewAllResourcesCommand(&generateOpts)) - command.AddCommand(NewReposCommand(&generateOpts)) - command.PersistentFlags().StringVar(&generateOpts.Namespace, "kube-namespace", "argocd", "Name of the namespace on which Argo agent should be installed [$KUBE_NAMESPACE]") - return command -} diff --git a/hack/gen-resources/examples/gen_resources.yaml b/hack/gen-resources/examples/gen_resources.yaml new file mode 100644 index 0000000000000..378c02768aebd --- /dev/null +++ b/hack/gen-resources/examples/gen_resources.yaml @@ -0,0 +1,22 @@ +application: + samples: 300 + source: + strategy: Random + destination: + strategy: Random + + +cluster: + samples: 2 + namespacePrefix: vcluster + valuesFilePath: /Users/argocd/.kube/util/values.yaml + destinationNamespace: argocd + clusterNamePrefix: test + +repository: + samples: 100 + +project: + samples: 15 + +namespace: argocd \ No newline at end of file diff --git a/hack/gen-resources/generators/application_generator.go b/hack/gen-resources/generators/application_generator.go index 5ae122db39bdc..7810513faea79 100644 --- a/hack/gen-resources/generators/application_generator.go +++ b/hack/gen-resources/generators/application_generator.go @@ -2,41 +2,103 @@ package generator import ( "context" + "log" + "math/rand" + "time" + + "github.com/argoproj/argo-cd/v2/util/settings" + + "github.com/argoproj/argo-cd/v2/util/db" + + "github.com/argoproj/argo-cd/v2/hack/gen-resources/util" v1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/client-go/kubernetes" + "github.com/argoproj/argo-cd/v2/pkg/apis/application/v1alpha1" appclientset "github.com/argoproj/argo-cd/v2/pkg/client/clientset/versioned" ) type ApplicationGenerator struct { - clientSet *appclientset.Clientset + argoClientSet *appclientset.Clientset + clientSet *kubernetes.Clientset + db db.ArgoDB +} + +func NewApplicationGenerator(argoClientSet *appclientset.Clientset, clientSet *kubernetes.Clientset, db db.ArgoDB) Generator { + return &ApplicationGenerator{argoClientSet, clientSet, db} +} + +func (pg *ApplicationGenerator) buildRandomSource(repositories []*v1alpha1.Repository) (*v1alpha1.ApplicationSource, error) { + rand.Seed(time.Now().Unix()) + repoNumber := rand.Int() % len(repositories) + return &v1alpha1.ApplicationSource{ + RepoURL: repositories[repoNumber].Repo, + Path: "helm-guestbook", + TargetRevision: "master", + }, nil +} + +func (ag *ApplicationGenerator) buildSource(opts *util.GenerateOpts, repositories []*v1alpha1.Repository) (*v1alpha1.ApplicationSource, error) { + switch opts.ApplicationOpts.SourceOpts.Strategy { + case "Random": + return ag.buildRandomSource(repositories) + } + return ag.buildRandomSource(repositories) } -func NewApplicationGenerator(clientSet *appclientset.Clientset) Generator { - return &ApplicationGenerator{clientSet} +func (pg *ApplicationGenerator) buildRandomDestination(opts *util.GenerateOpts, clusters []v1alpha1.Cluster) (*v1alpha1.ApplicationDestination, error) { + rand.Seed(time.Now().Unix()) + clusterNumber := rand.Int() % len(clusters) + return &v1alpha1.ApplicationDestination{ + Namespace: opts.Namespace, + Name: clusters[clusterNumber].Name, + }, nil } -func (pg *ApplicationGenerator) Generate(opts *GenerateOpts) error { - applications := pg.clientSet.ArgoprojV1alpha1().Applications(opts.Namespace) - for i := 0; i < opts.Samples; i++ { - _, err := applications.Create(context.TODO(), &v1alpha1.Application{ +func (ag *ApplicationGenerator) buildDestination(opts *util.GenerateOpts, clusters []v1alpha1.Cluster) (*v1alpha1.ApplicationDestination, error) { + switch opts.ApplicationOpts.DestinationOpts.Strategy { + case "Random": + return ag.buildRandomDestination(opts, clusters) + } + return ag.buildRandomDestination(opts, clusters) +} + +func (pg *ApplicationGenerator) Generate(opts *util.GenerateOpts) error { + settingsMgr := settings.NewSettingsManager(context.TODO(), pg.clientSet, opts.Namespace) + repositories, err := db.NewDB(opts.Namespace, settingsMgr, pg.clientSet).ListRepositories(context.TODO()) + if err != nil { + return err + } + clusters, err := db.NewDB(opts.Namespace, settingsMgr, pg.clientSet).ListClusters(context.TODO()) + if err != nil { + return err + } + applications := pg.argoClientSet.ArgoprojV1alpha1().Applications(opts.Namespace) + for i := 0; i < opts.ApplicationOpts.Samples; i++ { + log.Printf("Generate application #%v", i) + source, err := pg.buildSource(opts, repositories) + if err != nil { + return err + } + log.Printf("Pick source \"%s\"", source) + destination, err := pg.buildDestination(opts, clusters.Items) + if err != nil { + return err + } + log.Printf("Pick destination \"%s\"", destination) + log.Printf("Create application") + _, err = applications.Create(context.TODO(), &v1alpha1.Application{ ObjectMeta: v1.ObjectMeta{ GenerateName: "application-", Namespace: opts.Namespace, Labels: labels, }, Spec: v1alpha1.ApplicationSpec{ - Project: "default", - Destination: v1alpha1.ApplicationDestination{ - Namespace: opts.Namespace, - Name: "in-cluster", - }, - Source: v1alpha1.ApplicationSource{ - RepoURL: "https://github.com/argoproj/argocd-example-apps", - Path: "helm-guestbook", - TargetRevision: "master", - }, + Project: "default", + Destination: *destination, + Source: *source, }, }, v1.CreateOptions{}) if err != nil { @@ -46,8 +108,9 @@ func (pg *ApplicationGenerator) Generate(opts *GenerateOpts) error { return nil } -func (ag *ApplicationGenerator) Clean(opts *GenerateOpts) error { - applications := ag.clientSet.ArgoprojV1alpha1().Applications(opts.Namespace) +func (ag *ApplicationGenerator) Clean(opts *util.GenerateOpts) error { + log.Printf("Clean applications") + applications := ag.argoClientSet.ArgoprojV1alpha1().Applications(opts.Namespace) return applications.DeleteCollection(context.TODO(), v1.DeleteOptions{}, v1.ListOptions{ LabelSelector: "app.kubernetes.io/generated-by=argocd-generator", }) diff --git a/hack/gen-resources/generators/cluster_generator.go b/hack/gen-resources/generators/cluster_generator.go new file mode 100644 index 0000000000000..c1967b80a2294 --- /dev/null +++ b/hack/gen-resources/generators/cluster_generator.go @@ -0,0 +1,235 @@ +package generator + +import ( + "bytes" + "context" + "encoding/base64" + "log" + "strings" + + v12 "k8s.io/apimachinery/pkg/apis/meta/v1" + + "github.com/argoproj/argo-cd/v2/util/helm" + + "gopkg.in/yaml.v2" + + "k8s.io/client-go/kubernetes/scheme" + + v1 "k8s.io/api/core/v1" + "k8s.io/client-go/rest" + "k8s.io/client-go/tools/remotecommand" + + "k8s.io/client-go/kubernetes" + + "github.com/argoproj/argo-cd/v2/hack/gen-resources/util" + argoappv1 "github.com/argoproj/argo-cd/v2/pkg/apis/application/v1alpha1" + "github.com/argoproj/argo-cd/v2/util/db" +) + +const POD_PREFIX = "vcluster" + +type Cluster struct { + Server string `yaml:"server"` + CertificateAuthorityData string `yaml:"certificate-authority-data,omitempty"` +} + +type AuthInfo struct { + ClientCertificateData string `yaml:"client-certificate-data,omitempty"` + ClientKeyData string `yaml:"client-key-data,omitempty"` +} + +type NamedCluster struct { + // Name is the nickname for this Cluster + Name string `yaml:"name"` + // Cluster holds the cluster information + Cluster Cluster `yaml:"cluster"` +} + +type NamedAuthInfo struct { + // Name is the nickname for this AuthInfo + Name string `yaml:"name"` + // AuthInfo holds the auth information + AuthInfo AuthInfo `yaml:"user"` +} + +type Config struct { + Clusters []NamedCluster `yaml:"clusters"` + AuthInfos []NamedAuthInfo `yaml:"users"` +} + +type ClusterGenerator struct { + db db.ArgoDB + clientSet *kubernetes.Clientset + config *rest.Config +} + +func NewClusterGenerator(db db.ArgoDB, clientSet *kubernetes.Clientset, config *rest.Config) Generator { + return &ClusterGenerator{db, clientSet, config} +} + +func (cg *ClusterGenerator) getClusterCredentials(namespace string, releaseSuffix string) ([]byte, []byte, []byte, error) { + cmd := []string{ + "sh", + "-c", + "cat /root/.kube/config", + } + + var stdout, stderr, stdin bytes.Buffer + option := &v1.PodExecOptions{ + Command: cmd, + Container: "syncer", + Stdin: true, + Stdout: true, + Stderr: true, + TTY: true, + } + + req := cg.clientSet.CoreV1().RESTClient().Post().Resource("pods").Name(POD_PREFIX + "-" + releaseSuffix + "-0"). + Namespace(namespace).SubResource("exec") + + req.VersionedParams( + option, + scheme.ParameterCodec, + ) + + exec, err := remotecommand.NewSPDYExecutor(cg.config, "POST", req.URL()) + if err != nil { + return nil, nil, nil, err + } + + err = exec.Stream(remotecommand.StreamOptions{ + Stdin: &stdin, + Stdout: &stdout, + Stderr: &stderr, + }) + if err != nil { + return nil, nil, nil, err + } + + var config Config + + err = yaml.Unmarshal(stdout.Bytes(), &config) + if err != nil { + return nil, nil, nil, err + } + + caData, err := base64.StdEncoding.DecodeString(config.Clusters[0].Cluster.CertificateAuthorityData) + if err != nil { + return nil, nil, nil, err + } + + cert, err := base64.StdEncoding.DecodeString(config.AuthInfos[0].AuthInfo.ClientCertificateData) + if err != nil { + return nil, nil, nil, err + } + + key, err := base64.StdEncoding.DecodeString(config.AuthInfos[0].AuthInfo.ClientKeyData) + if err != nil { + return nil, nil, nil, err + } + + return caData, cert, key, nil +} + +//TODO: also should provision service for vcluster pod +func (cg *ClusterGenerator) installVCluster(opts *util.GenerateOpts, namespace string, releaseName string) error { + cmd, err := helm.NewCmd("/tmp", "v3", "") + if err != nil { + return err + } + log.Print("Execute helm install command") + _, err = cmd.Freestyle("install", releaseName, "vcluster", "--values", opts.ClusterOpts.ValuesFilePath, "--repo", "https://charts.loft.sh", "--namespace", namespace, "--repository-config", "", "--create-namespace", "--wait") + if err != nil { + return err + } + return nil +} + +func (cg *ClusterGenerator) getClusterServerUri(namespace string, releaseSuffix string) (string, error) { + pod, err := cg.clientSet.CoreV1().Pods(namespace).Get(context.TODO(), POD_PREFIX+"-"+releaseSuffix+"-0", v12.GetOptions{}) + if err != nil { + return "", err + } + // TODO: should be moved to service instead pod + return "https://" + pod.Status.PodIP + ":8443", nil +} + +func (cg *ClusterGenerator) Generate(opts *util.GenerateOpts) error { + for i := 0; i < opts.ClusterOpts.Samples; i++ { + log.Printf("Generate cluster #%v", i) + + namespace := opts.ClusterOpts.NamespacePrefix + "-" + util.GetRandomString() + + log.Printf("Namespace is %s", namespace) + + releaseSuffix := util.GetRandomString() + + log.Printf("Release suffix is %s", namespace) + + err := cg.installVCluster(opts, namespace, POD_PREFIX+"-"+releaseSuffix) + if err != nil { + log.Printf("Skip cluster installation due error %v", err.Error()) + continue + } + + log.Print("Get cluster credentials") + caData, cert, key, err := cg.getClusterCredentials(namespace, releaseSuffix) + if err != nil { + return err + } + + log.Print("Get cluster server uri") + uri, err := cg.getClusterServerUri(namespace, releaseSuffix) + if err != nil { + return err + } + + log.Printf("Cluster server uri is %s", uri) + + log.Print("Create cluster") + _, err = cg.db.CreateCluster(context.TODO(), &argoappv1.Cluster{ + Server: uri, + Name: opts.ClusterOpts.ClusterNamePrefix + "-" + util.GetRandomString(), + Config: argoappv1.ClusterConfig{ + TLSClientConfig: argoappv1.TLSClientConfig{ + Insecure: false, + ServerName: "kubernetes.default.svc", + CAData: caData, + CertData: cert, + KeyData: key, + }, + }, + ConnectionState: argoappv1.ConnectionState{}, + ServerVersion: "1.18", + Namespaces: []string{opts.ClusterOpts.DestinationNamespace}, + Labels: labels, + }) + if err != nil { + return err + } + } + return nil +} + +func (cg *ClusterGenerator) Clean(opts *util.GenerateOpts) error { + log.Printf("Clean clusters") + namespaces, err := cg.clientSet.CoreV1().Namespaces().List(context.TODO(), v12.ListOptions{}) + if err != nil { + return err + } + + for _, ns := range namespaces.Items { + if strings.HasPrefix(ns.Name, POD_PREFIX) { + log.Printf("Delete namespace %s", ns.Name) + err = cg.clientSet.CoreV1().Namespaces().Delete(context.TODO(), ns.Name, v12.DeleteOptions{}) + if err != nil { + log.Printf("Delete namespace failed due: %s", err.Error()) + } + } + } + + secrets := cg.clientSet.CoreV1().Secrets(opts.Namespace) + return secrets.DeleteCollection(context.TODO(), v12.DeleteOptions{}, v12.ListOptions{ + LabelSelector: "app.kubernetes.io/generated-by=argocd-generator", + }) +} diff --git a/hack/gen-resources/generators/generator.go b/hack/gen-resources/generators/generator.go index 4e84915c4c241..8d24b755a8e98 100644 --- a/hack/gen-resources/generators/generator.go +++ b/hack/gen-resources/generators/generator.go @@ -1,16 +1,12 @@ package generator +import "github.com/argoproj/argo-cd/v2/hack/gen-resources/util" + var labels = map[string]string{ "app.kubernetes.io/generated-by": "argocd-generator", } -type GenerateOpts struct { - Samples int - GithubToken string - Namespace string -} - type Generator interface { - Generate(opts *GenerateOpts) error - Clean(opts *GenerateOpts) error + Generate(opts *util.GenerateOpts) error + Clean(opts *util.GenerateOpts) error } diff --git a/hack/gen-resources/generators/project_generator.go b/hack/gen-resources/generators/project_generator.go index e17d6d07f12cc..a86dca0d86c22 100644 --- a/hack/gen-resources/generators/project_generator.go +++ b/hack/gen-resources/generators/project_generator.go @@ -2,9 +2,11 @@ package generator import ( "context" + "log" v1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "github.com/argoproj/argo-cd/v2/hack/gen-resources/util" "github.com/argoproj/argo-cd/v2/pkg/apis/application/v1alpha1" appclientset "github.com/argoproj/argo-cd/v2/pkg/client/clientset/versioned" ) @@ -17,9 +19,10 @@ func NewProjectGenerator(clientSet *appclientset.Clientset) Generator { return &ProjectGenerator{clientSet} } -func (pg *ProjectGenerator) Generate(opts *GenerateOpts) error { +func (pg *ProjectGenerator) Generate(opts *util.GenerateOpts) error { projects := pg.clientSet.ArgoprojV1alpha1().AppProjects(opts.Namespace) - for i := 0; i < opts.Samples; i++ { + for i := 0; i < opts.ProjectOpts.Samples; i++ { + log.Printf("Generate project #%v", i) _, err := projects.Create(context.TODO(), &v1alpha1.AppProject{ ObjectMeta: v1.ObjectMeta{ GenerateName: "project-", @@ -31,13 +34,15 @@ func (pg *ProjectGenerator) Generate(opts *GenerateOpts) error { }, }, v1.CreateOptions{}) if err != nil { + log.Printf("Project #%v failed to generate", i) return err } } return nil } -func (pg *ProjectGenerator) Clean(opts *GenerateOpts) error { +func (pg *ProjectGenerator) Clean(opts *util.GenerateOpts) error { + log.Printf("Clean projects") projects := pg.clientSet.ArgoprojV1alpha1().AppProjects(opts.Namespace) return projects.DeleteCollection(context.TODO(), v1.DeleteOptions{}, v1.ListOptions{ LabelSelector: "app.kubernetes.io/generated-by=argocd-generator", diff --git a/hack/gen-resources/generators/repo_generator.go b/hack/gen-resources/generators/repo_generator.go index 414107e456103..9292c8feaae26 100644 --- a/hack/gen-resources/generators/repo_generator.go +++ b/hack/gen-resources/generators/repo_generator.go @@ -9,11 +9,11 @@ import ( "log" "net/http" - "github.com/argoproj/argo-cd/v2/hack/gen-resources/tools" - v1 "k8s.io/api/core/v1" v1meta "k8s.io/apimachinery/pkg/apis/meta/v1" + "github.com/argoproj/argo-cd/v2/hack/gen-resources/util" + "k8s.io/client-go/kubernetes" ) @@ -24,11 +24,11 @@ type Repo struct { type RepoGenerator struct { clientSet *kubernetes.Clientset - bar *tools.Bar + bar *util.Bar } func NewRepoGenerator(clientSet *kubernetes.Clientset) Generator { - return &RepoGenerator{clientSet: clientSet, bar: &tools.Bar{}} + return &RepoGenerator{clientSet: clientSet, bar: &util.Bar{}} } func fetchRepos(token string, page int) ([]Repo, error) { @@ -79,8 +79,8 @@ func FetchRepos(token string, samples int) ([]Repo, error) { return repos, nil } -func (rg *RepoGenerator) Generate(opts *GenerateOpts) error { - repos, err := FetchRepos(opts.GithubToken, opts.Samples) +func (rg *RepoGenerator) Generate(opts *util.GenerateOpts) error { + repos, err := FetchRepos(opts.GithubToken, opts.RepositoryOpts.Samples) if err != nil { return err } @@ -116,7 +116,8 @@ func (rg *RepoGenerator) Generate(opts *GenerateOpts) error { return nil } -func (rg *RepoGenerator) Clean(opts *GenerateOpts) error { +func (rg *RepoGenerator) Clean(opts *util.GenerateOpts) error { + log.Printf("Clean repos") secrets := rg.clientSet.CoreV1().Secrets(opts.Namespace) return secrets.DeleteCollection(context.TODO(), v1meta.DeleteOptions{}, v1meta.ListOptions{ LabelSelector: "app.kubernetes.io/generated-by=argocd-generator", diff --git a/hack/gen-resources/util/gen_options_parser.go b/hack/gen-resources/util/gen_options_parser.go new file mode 100644 index 0000000000000..0a43194733d88 --- /dev/null +++ b/hack/gen-resources/util/gen_options_parser.go @@ -0,0 +1,60 @@ +package util + +import ( + "io/ioutil" + + "gopkg.in/yaml.v2" +) + +type SourceOpts struct { + Strategy string `yaml:"strategy"` +} + +type DestinationOpts struct { + Strategy string `yaml:"strategy"` +} + +type ApplicationOpts struct { + Samples int `yaml:"samples"` + SourceOpts SourceOpts `yaml:"source"` + DestinationOpts DestinationOpts `yaml:"destination"` +} + +type RepositoryOpts struct { + Samples int `yaml:"samples"` +} + +type ProjectOpts struct { + Samples int `yaml:"samples"` +} + +type ClusterOpts struct { + Samples int `yaml:"samples"` + NamespacePrefix string `yaml:"namespacePrefix"` + ValuesFilePath string `yaml:"valuesFilePath"` + DestinationNamespace string `yaml:"destinationNamespace"` + ClusterNamePrefix string `yaml:"clusterNamePrefix"` +} + +type GenerateOpts struct { + ApplicationOpts ApplicationOpts `yaml:"application"` + ClusterOpts ClusterOpts `yaml:"cluster"` + RepositoryOpts RepositoryOpts `yaml:"repository"` + ProjectOpts ProjectOpts `yaml:"project"` + + GithubToken string + Namespace string `yaml:"namespace"` +} + +func Parse(opts *GenerateOpts, file string) error { + fp, err := ioutil.ReadFile(file) + if err != nil { + return err + } + + if e := yaml.Unmarshal(fp, &opts); e != nil { + return e + } + + return nil +} diff --git a/hack/gen-resources/tools/kube.go b/hack/gen-resources/util/kube.go similarity index 82% rename from hack/gen-resources/tools/kube.go rename to hack/gen-resources/util/kube.go index c788cac191676..303057ab54bfa 100644 --- a/hack/gen-resources/tools/kube.go +++ b/hack/gen-resources/util/kube.go @@ -1,4 +1,4 @@ -package tools +package util import ( "log" @@ -6,6 +6,8 @@ import ( "os/user" "path" + "k8s.io/client-go/rest" + "k8s.io/client-go/kubernetes" "k8s.io/client-go/tools/clientcmd" @@ -36,11 +38,19 @@ func getKubeConfigPath() string { func ConnectToK8sArgoClientSet() *appclientset.Clientset { config, err := clientcmd.BuildConfigFromFlags("", getKubeConfigPath()) if err != nil { - log.Panicln("failed to create K8s config") + log.Panicln("failed to create Argocd K8s config") } return appclientset.NewForConfigOrDie(config) } +func ConnectToK8sConfig() *rest.Config { + config, err := clientcmd.BuildConfigFromFlags("", getKubeConfigPath()) + if err != nil { + log.Panicln("failed to create K8s config") + } + return config +} + func ConnectToK8sClientSet() *kubernetes.Clientset { config, err := clientcmd.BuildConfigFromFlags("", getKubeConfigPath()) if err != nil { diff --git a/hack/gen-resources/tools/progress_bar.go b/hack/gen-resources/util/progress_bar.go similarity index 98% rename from hack/gen-resources/tools/progress_bar.go rename to hack/gen-resources/util/progress_bar.go index fc778750f794a..66eda264c77e5 100644 --- a/hack/gen-resources/tools/progress_bar.go +++ b/hack/gen-resources/util/progress_bar.go @@ -1,4 +1,4 @@ -package tools +package util import ( "fmt" diff --git a/hack/gen-resources/util/util.go b/hack/gen-resources/util/util.go new file mode 100644 index 0000000000000..ab55ba88d7d3c --- /dev/null +++ b/hack/gen-resources/util/util.go @@ -0,0 +1,21 @@ +package util + +import ( + "crypto/rand" + "math/big" +) + +var letters = []rune("abcdefghijklmnopqrstuvwxyz123456789") + +func GetRandomString() string { + b := make([]rune, 24) + for i := range b { + b[i] = letters[cryptoRandSecure(int64(len(letters)))] + } + return string(b) +} + +func cryptoRandSecure(max int64) int64 { + nBig, _ := rand.Int(rand.Reader, big.NewInt(max)) + return nBig.Int64() +} diff --git a/util/helm/cmd.go b/util/helm/cmd.go index af77d43edf4ed..471db33bdb80e 100644 --- a/util/helm/cmd.go +++ b/util/helm/cmd.go @@ -338,6 +338,10 @@ func (c *Cmd) template(chartPath string, opts *TemplateOpts) (string, error) { return c.run(args...) } +func (c *Cmd) Freestyle(args ...string) (string, error) { + return c.run(args...) +} + func (c *Cmd) Close() { _ = os.RemoveAll(c.helmHome) }