argo-cd
281 строка · 7.4 Кб
1package generator2
3import (4"bytes"5"context"6"encoding/base64"7"errors"8"log"9"strings"10"time"11
12v12 "k8s.io/apimachinery/pkg/apis/meta/v1"13
14"github.com/argoproj/argo-cd/v2/util/helm"15
16"gopkg.in/yaml.v2"17
18"k8s.io/client-go/kubernetes/scheme"19
20v1 "k8s.io/api/core/v1"21"k8s.io/client-go/rest"22"k8s.io/client-go/tools/remotecommand"23
24"k8s.io/client-go/kubernetes"25
26"github.com/argoproj/argo-cd/v2/hack/gen-resources/util"27argoappv1 "github.com/argoproj/argo-cd/v2/pkg/apis/application/v1alpha1"28"github.com/argoproj/argo-cd/v2/util/db"29)
30
31const POD_PREFIX = "vcluster"32
33type Cluster struct {34Server string `yaml:"server"`35CertificateAuthorityData string `yaml:"certificate-authority-data,omitempty"`36}
37
38type AuthInfo struct {39ClientCertificateData string `yaml:"client-certificate-data,omitempty"`40ClientKeyData string `yaml:"client-key-data,omitempty"`41}
42
43type NamedCluster struct {44// Name is the nickname for this Cluster45Name string `yaml:"name"`46// Cluster holds the cluster information47Cluster Cluster `yaml:"cluster"`48}
49
50type NamedAuthInfo struct {51// Name is the nickname for this AuthInfo52Name string `yaml:"name"`53// AuthInfo holds the auth information54AuthInfo AuthInfo `yaml:"user"`55}
56
57type Config struct {58Clusters []NamedCluster `yaml:"clusters"`59AuthInfos []NamedAuthInfo `yaml:"users"`60}
61
62type ClusterGenerator struct {63db db.ArgoDB64clientSet *kubernetes.Clientset65config *rest.Config66}
67
68func NewClusterGenerator(db db.ArgoDB, clientSet *kubernetes.Clientset, config *rest.Config) Generator {69return &ClusterGenerator{db, clientSet, config}70}
71
72func (cg *ClusterGenerator) getClusterCredentials(namespace string, releaseSuffix string) ([]byte, []byte, []byte, error) {73cmd := []string{74"sh",75"-c",76"cat /root/.kube/config",77}78
79var stdout, stderr, stdin bytes.Buffer80option := &v1.PodExecOptions{81Command: cmd,82Container: "syncer",83Stdin: true,84Stdout: true,85Stderr: true,86TTY: true,87}88
89req := cg.clientSet.CoreV1().RESTClient().Post().Resource("pods").Name(POD_PREFIX + "-" + releaseSuffix + "-0").90Namespace(namespace).SubResource("exec")91
92req.VersionedParams(93option,94scheme.ParameterCodec,95)96
97exec, err := remotecommand.NewSPDYExecutor(cg.config, "POST", req.URL())98if err != nil {99return nil, nil, nil, err100}101
102err = exec.StreamWithContext(context.Background(), remotecommand.StreamOptions{103Stdin: &stdin,104Stdout: &stdout,105Stderr: &stderr,106})107if err != nil {108return nil, nil, nil, err109}110
111var config Config112
113err = yaml.Unmarshal(stdout.Bytes(), &config)114if err != nil {115return nil, nil, nil, err116}117
118if len(config.Clusters) == 0 {119return nil, nil, nil, errors.New("clusters empty")120}121
122caData, err := base64.StdEncoding.DecodeString(config.Clusters[0].Cluster.CertificateAuthorityData)123if err != nil {124return nil, nil, nil, err125}126
127cert, err := base64.StdEncoding.DecodeString(config.AuthInfos[0].AuthInfo.ClientCertificateData)128if err != nil {129return nil, nil, nil, err130}131
132key, err := base64.StdEncoding.DecodeString(config.AuthInfos[0].AuthInfo.ClientKeyData)133if err != nil {134return nil, nil, nil, err135}136
137return caData, cert, key, nil138}
139
140// TODO: also should provision service for vcluster pod
141func (cg *ClusterGenerator) installVCluster(opts *util.GenerateOpts, namespace string, releaseName string) error {142cmd, err := helm.NewCmd("/tmp", "v3", "")143if err != nil {144return err145}146log.Print("Execute helm install command")147_, err = cmd.Freestyle("upgrade", "--install", releaseName, "vcluster", "--values", opts.ClusterOpts.ValuesFilePath, "--repo", "https://charts.loft.sh", "--namespace", namespace, "--repository-config", "", "--create-namespace", "--wait")148if err != nil {149return err150}151return nil152}
153
154func (cg *ClusterGenerator) getClusterServerUri(namespace string, releaseSuffix string) (string, error) {155pod, err := cg.clientSet.CoreV1().Pods(namespace).Get(context.TODO(), POD_PREFIX+"-"+releaseSuffix+"-0", v12.GetOptions{})156if err != nil {157return "", err158}159// TODO: should be moved to service instead pod160log.Printf("Get service for https://" + pod.Status.PodIP + ":8443")161return "https://" + pod.Status.PodIP + ":8443", nil162}
163
164func (cg *ClusterGenerator) retrieveClusterUri(namespace, releaseSuffix string) (string, error) {165for i := 0; i < 10; i++ {166log.Printf("Attempting to get cluster uri")167uri, err := cg.getClusterServerUri(namespace, releaseSuffix)168if err != nil {169log.Printf("Failed to get cluster uri due to %s", err.Error())170time.Sleep(10 * time.Second)171continue172}173return uri, nil174}175return "", nil176}
177
178func (cg *ClusterGenerator) generate(i int, opts *util.GenerateOpts) error {179log.Printf("Generate cluster #%v of #%v", i, opts.ClusterOpts.Samples)180
181namespace := opts.ClusterOpts.NamespacePrefix + "-" + util.GetRandomString()182
183log.Printf("Namespace is %s", namespace)184
185releaseSuffix := util.GetRandomString()186
187log.Printf("Release suffix is %s", namespace)188
189err := cg.installVCluster(opts, namespace, POD_PREFIX+"-"+releaseSuffix)190if err != nil {191log.Printf("Skip cluster installation due error %v", err.Error())192}193
194log.Print("Get cluster credentials")195caData, cert, key, err := cg.getClusterCredentials(namespace, releaseSuffix)196
197for o := 0; o < 5; o++ {198if err == nil {199break200}201log.Printf("Failed to get cluster credentials %s, retrying...", releaseSuffix)202time.Sleep(10 * time.Second)203caData, cert, key, err = cg.getClusterCredentials(namespace, releaseSuffix)204}205if err != nil {206return err207}208
209log.Print("Get cluster server uri")210
211uri, err := cg.retrieveClusterUri(namespace, releaseSuffix)212if err != nil {213return err214}215
216log.Printf("Cluster server uri is %s", uri)217
218log.Print("Create cluster")219_, err = cg.db.CreateCluster(context.TODO(), &argoappv1.Cluster{220Server: uri,221Name: opts.ClusterOpts.ClusterNamePrefix + "-" + util.GetRandomString(),222Config: argoappv1.ClusterConfig{223TLSClientConfig: argoappv1.TLSClientConfig{224Insecure: false,225ServerName: "kubernetes.default.svc",226CAData: caData,227CertData: cert,228KeyData: key,229},230},231ConnectionState: argoappv1.ConnectionState{},232ServerVersion: "1.18",233Namespaces: []string{opts.ClusterOpts.DestinationNamespace},234Labels: labels,235})236if err != nil {237return err238}239return nil240}
241
242func (cg *ClusterGenerator) Generate(opts *util.GenerateOpts) error {243log.Printf("Excute in parallel with %v", opts.ClusterOpts.Concurrency)244
245wg := util.New(opts.ClusterOpts.Concurrency)246for l := 1; l <= opts.ClusterOpts.Samples; l++ {247wg.Add()248go func(i int) {249defer wg.Done()250err := cg.generate(i, opts)251if err != nil {252log.Printf("Failed to generate cluster #%v due to : %s", i, err.Error())253}254}(l)255}256wg.Wait()257return nil258}
259
260func (cg *ClusterGenerator) Clean(opts *util.GenerateOpts) error {261log.Printf("Clean clusters")262namespaces, err := cg.clientSet.CoreV1().Namespaces().List(context.TODO(), v12.ListOptions{})263if err != nil {264return err265}266
267for _, ns := range namespaces.Items {268if strings.HasPrefix(ns.Name, POD_PREFIX) {269log.Printf("Delete namespace %s", ns.Name)270err = cg.clientSet.CoreV1().Namespaces().Delete(context.TODO(), ns.Name, v12.DeleteOptions{})271if err != nil {272log.Printf("Delete namespace failed due: %s", err.Error())273}274}275}276
277secrets := cg.clientSet.CoreV1().Secrets(opts.Namespace)278return secrets.DeleteCollection(context.TODO(), v12.DeleteOptions{}, v12.ListOptions{279LabelSelector: "app.kubernetes.io/generated-by=argocd-generator",280})281}
282