internal/servicedeployer/kubernetes.go (222 lines of code) (raw):

// Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one // or more contributor license agreements. Licensed under the Elastic License; // you may not use this file except in compliance with the Elastic License. package servicedeployer import ( "bytes" "context" _ "embed" "encoding/base64" "fmt" "os" "path/filepath" "strings" "text/template" "github.com/elastic/elastic-package/internal/install" "github.com/elastic/elastic-package/internal/kind" "github.com/elastic/elastic-package/internal/kubectl" "github.com/elastic/elastic-package/internal/logger" "github.com/elastic/elastic-package/internal/profile" "github.com/elastic/elastic-package/internal/stack" ) // KubernetesServiceDeployer is responsible for deploying resources in the Kubernetes cluster. type KubernetesServiceDeployer struct { profile *profile.Profile definitionsDir string stackVersion string policyName string deployIndependentAgent bool runSetup bool runTestsOnly bool runTearDown bool } type KubernetesServiceDeployerOptions struct { Profile *profile.Profile DefinitionsDir string StackVersion string PolicyName string DeployIndependentAgent bool RunSetup bool RunTestsOnly bool RunTearDown bool } type kubernetesDeployedService struct { svcInfo ServiceInfo stackVersion string profile *profile.Profile policyName string deployIndependentAgent bool definitionsDir string } func (s kubernetesDeployedService) TearDown(ctx context.Context) error { if !s.deployIndependentAgent { logger.Debug("Uninstall Elastic Agent Kubernetes") elasticAgentManagedYaml, err := getElasticAgentYAML(s.profile, s.stackVersion, s.policyName) if err != nil { return fmt.Errorf("can't retrieve Kubernetes file for Elastic Agent: %w", err) } err = kubectl.DeleteStdin(ctx, elasticAgentManagedYaml) if err != nil { return fmt.Errorf("can't uninstall Elastic Agent Kubernetes resources (path: %s): %w", s.definitionsDir, err) } } logger.Debugf("Uninstall custom Kubernetes definitions (directory: %s)", s.definitionsDir) definitionPaths, err := findKubernetesDefinitions(s.definitionsDir) if err != nil { return fmt.Errorf("can't find Kubernetes definitions in given directory (path: %s): %w", s.definitionsDir, err) } if len(definitionPaths) == 0 { logger.Debugf("no custom definitions found (directory: %s). Nothing will be uninstalled.", s.definitionsDir) return nil } err = kubectl.Delete(ctx, definitionPaths) if err != nil { return fmt.Errorf("can't uninstall Kubernetes resources (path: %s): %w", s.definitionsDir, err) } return nil } func (s kubernetesDeployedService) Signal(_ context.Context, _ string) error { return ErrNotSupported } func (s kubernetesDeployedService) ExitCode(_ context.Context, _ string) (bool, int, error) { return false, -1, ErrNotSupported } func (s kubernetesDeployedService) Info() ServiceInfo { return s.svcInfo } func (s *kubernetesDeployedService) SetInfo(sc ServiceInfo) error { s.svcInfo = sc return nil } var _ DeployedService = new(kubernetesDeployedService) // NewKubernetesServiceDeployer function creates a new instance of KubernetesServiceDeployer. func NewKubernetesServiceDeployer(opts KubernetesServiceDeployerOptions) (*KubernetesServiceDeployer, error) { return &KubernetesServiceDeployer{ profile: opts.Profile, definitionsDir: opts.DefinitionsDir, stackVersion: opts.StackVersion, policyName: opts.PolicyName, runSetup: opts.RunSetup, runTestsOnly: opts.RunTestsOnly, runTearDown: opts.RunTearDown, deployIndependentAgent: opts.DeployIndependentAgent, }, nil } // SetUp function links the kind container with elastic-package-stack network, installs Elastic-Agent and optionally // custom YAML definitions. func (ksd KubernetesServiceDeployer) SetUp(ctx context.Context, svcInfo ServiceInfo) (DeployedService, error) { err := kind.VerifyContext(ctx) if err != nil { return nil, fmt.Errorf("kind context verification failed: %w", err) } if ksd.runTearDown || ksd.runTestsOnly { logger.Debug("Skip connect kind to Elastic stack network") } else { err = kind.ConnectToElasticStackNetwork(ksd.profile) if err != nil { return nil, fmt.Errorf("can't connect control plane to Elastic stack network: %w", err) } } if ksd.runTearDown || ksd.runTestsOnly || ksd.deployIndependentAgent { logger.Debug("Skip install Elastic Agent in cluster") } else { err = installElasticAgentInCluster(ctx, ksd.profile, ksd.stackVersion, ksd.policyName) if err != nil { return nil, fmt.Errorf("can't install Elastic-Agent in the Kubernetes cluster: %w", err) } } if !ksd.runTearDown { err = ksd.installCustomDefinitions(ctx) if err != nil { return nil, fmt.Errorf("can't install custom definitions in the Kubernetes cluster: %w", err) } } svcInfo.Agent.Independent = true svcInfo.Name = kind.ControlPlaneContainerName svcInfo.Hostname = kind.ControlPlaneContainerName // kind-control-plane is the name of the kind host where Pod is running since we use hostNetwork setting // to deploy Agent Pod. Because of this, hostname inside pod will be equal to the name of the k8s host. svcInfo.Agent.Host.NamePrefix = "kind-control-plane" return &kubernetesDeployedService{ svcInfo: svcInfo, definitionsDir: ksd.definitionsDir, stackVersion: ksd.stackVersion, profile: ksd.profile, deployIndependentAgent: ksd.deployIndependentAgent, policyName: ksd.policyName, }, nil } func (ksd KubernetesServiceDeployer) installCustomDefinitions(ctx context.Context) error { logger.Debugf("install custom Kubernetes definitions (directory: %s)", ksd.definitionsDir) definitionPaths, err := findKubernetesDefinitions(ksd.definitionsDir) if err != nil { return fmt.Errorf("can't find Kubernetes definitions in given path: %s: %w", ksd.definitionsDir, err) } if len(definitionPaths) == 0 { logger.Debugf("no custom definitions found (path: %s). Nothing else will be installed.", ksd.definitionsDir) return nil } err = kubectl.Apply(ctx, definitionPaths) if err != nil { return fmt.Errorf("can't install custom definitions: %w", err) } return nil } var _ ServiceDeployer = new(KubernetesServiceDeployer) func findKubernetesDefinitions(definitionsDir string) ([]string, error) { files, err := filepath.Glob(filepath.Join(definitionsDir, "*.yaml")) if err != nil { return nil, fmt.Errorf("can't read definitions directory (path: %s): %w", definitionsDir, err) } var definitionPaths []string definitionPaths = append(definitionPaths, files...) return definitionPaths, nil } func installElasticAgentInCluster(ctx context.Context, profile *profile.Profile, stackVersion, policyName string) error { logger.Debug("install Elastic Agent in the Kubernetes cluster") elasticAgentManagedYaml, err := getElasticAgentYAML(profile, stackVersion, policyName) if err != nil { return fmt.Errorf("can't retrieve Kubernetes file for Elastic Agent: %w", err) } err = kubectl.ApplyStdin(ctx, elasticAgentManagedYaml) if err != nil { return fmt.Errorf("can't install Elastic-Agent in Kubernetes cluster: %w", err) } return nil } //go:embed _static/elastic-agent-managed.yaml.tmpl var elasticAgentManagedYamlTmpl string func getElasticAgentYAML(profile *profile.Profile, stackVersion, policyName string) ([]byte, error) { logger.Debugf("Prepare YAML definition for Elastic Agent running in stack v%s", stackVersion) appConfig, err := install.Configuration(install.OptionWithStackVersion(stackVersion)) if err != nil { return nil, fmt.Errorf("can't read application configuration: %w", err) } caCert, err := readCACertBase64(profile) if err != nil { return nil, fmt.Errorf("can't read certificate authority file: %w", err) } tmpl := template.Must(template.New("elastic-agent.yml").Parse(elasticAgentManagedYamlTmpl)) var elasticAgentYaml bytes.Buffer err = tmpl.Execute(&elasticAgentYaml, map[string]string{ "fleetURL": "https://fleet-server:8220", "kibanaURL": "https://kibana:5601", "caCertPem": caCert, "elasticAgentImage": appConfig.StackImageRefs().ElasticAgent, "elasticAgentTokenPolicyName": getTokenPolicyName(stackVersion, policyName), }) if err != nil { return nil, fmt.Errorf("can't generate elastic agent manifest: %w", err) } return elasticAgentYaml.Bytes(), nil } func readCACertBase64(profile *profile.Profile) (string, error) { caCertPath, err := stack.FindCACertificate(profile) if err != nil { return "", fmt.Errorf("can't locate CA certificate: %w", err) } d, err := os.ReadFile(caCertPath) if err != nil { return "", err } return base64.StdEncoding.EncodeToString(d), nil } // getTokenPolicyName function returns the policy name for the 8.x Elastic stack. The agent's policy // is predefined in the Kibana configuration file. The logic is not present in older stacks. func getTokenPolicyName(stackVersion, policyName string) string { if policyName == "" { policyName = defaulFleetTokenPolicyName } if strings.HasPrefix(stackVersion, "8.") { return policyName } return "" }