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