<commit_before>/*
Copyright 2019 The Kubernetes Authors.

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

    http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

package etcdmanager

import (
	"encoding/json"
	"fmt"
	"strings"

	v1 "k8s.io/api/core/v1"
	"k8s.io/apimachinery/pkg/api/resource"
	"k8s.io/klog/v2"
	"k8s.io/kops/pkg/apis/kops"
	"k8s.io/kops/pkg/assets"
	"k8s.io/kops/pkg/dns"
	"k8s.io/kops/pkg/featureflag"
	"k8s.io/kops/pkg/flagbuilder"
	"k8s.io/kops/pkg/k8scodecs"
	"k8s.io/kops/pkg/kubemanifest"
	"k8s.io/kops/pkg/model"
	"k8s.io/kops/pkg/wellknownports"
	"k8s.io/kops/upup/pkg/fi"
	"k8s.io/kops/upup/pkg/fi/cloudup/aliup"
	"k8s.io/kops/upup/pkg/fi/cloudup/awsup"
	"k8s.io/kops/upup/pkg/fi/cloudup/azure"
	"k8s.io/kops/upup/pkg/fi/cloudup/do"
	"k8s.io/kops/upup/pkg/fi/cloudup/gce"
	"k8s.io/kops/upup/pkg/fi/cloudup/openstack"
	"k8s.io/kops/upup/pkg/fi/fitasks"
	"k8s.io/kops/util/pkg/env"
	"k8s.io/kops/util/pkg/exec"
)

// EtcdManagerBuilder builds the manifest for the etcd-manager
type EtcdManagerBuilder struct {
	*model.KopsModelContext
	Lifecycle    fi.Lifecycle
	AssetBuilder *assets.AssetBuilder
}

var _ fi.ModelBuilder = &EtcdManagerBuilder{}

// Build creates the tasks
func (b *EtcdManagerBuilder) Build(c *fi.ModelBuilderContext) error {
	for _, etcdCluster := range b.Cluster.Spec.EtcdClusters {
		name := etcdCluster.Name
		version := etcdCluster.Version

		backupStore := ""
		if etcdCluster.Backups != nil {
			backupStore = etcdCluster.Backups.BackupStore
		}
		if backupStore == "" {
			return fmt.Errorf("backupStore must be set for use with etcd-manager")
		}

		manifest, err := b.buildManifest(etcdCluster)
		if err != nil {
			return err
		}

		manifestYAML, err := k8scodecs.ToVersionedYaml(manifest)
		if err != nil {
			return fmt.Errorf("error marshaling manifest to yaml: %v", err)
		}

		c.AddTask(&fitasks.ManagedFile{
			Contents:  fi.NewBytesResource(manifestYAML),
			Lifecycle: b.Lifecycle,
			Location:  fi.String("manifests/etcd/" + name + ".yaml"),
			Name:      fi.String("manifests-etcdmanager-" + name),
		})

		info := &etcdClusterSpec{
			EtcdVersion: version,
			MemberCount: int32(len(etcdCluster.Members)),
		}

		d, err := json.MarshalIndent(info, "", "  ")
		if err != nil {
			return err
		}

		// Ensure a unique backup location for each etcd cluster
		// if a backupStore is not specified.
		var location string
		if backupStore == "" {
			location = "backups/etcd/" + etcdCluster.Name
		}

		c.AddTask(&fitasks.ManagedFile{
			Contents:  fi.NewBytesResource(d),
			Lifecycle: b.Lifecycle,
			Base:      fi.String(backupStore),
			// TODO: We need this to match the backup base (currently)
			Location: fi.String(location + "/control/etcd-cluster-spec"),
			Name:     fi.String("etcd-cluster-spec-" + name),
		})

		// We create a CA keypair to enable secure communication
		c.AddTask(&fitasks.Keypair{
			Name:      fi.String("etcd-manager-ca-" + etcdCluster.Name),
			Lifecycle: b.Lifecycle,
			Subject:   "cn=etcd-manager-ca-" + etcdCluster.Name,
			Type:      "ca",
		})

		// We create a CA for etcd peers and a separate one for clients
		c.AddTask(&fitasks.Keypair{
			Name:      fi.String("etcd-peers-ca-" + etcdCluster.Name),
			Lifecycle: b.Lifecycle,
			Subject:   "cn=etcd-peers-ca-" + etcdCluster.Name,
			Type:      "ca",
		})

		// Because API server can only have a single client-cert, we need to share a client CA
		if err := c.EnsureTask(&fitasks.Keypair{
			Name:      fi.String("etcd-clients-ca"),
			Lifecycle: b.Lifecycle,
			Subject:   "cn=etcd-clients-ca",
			Type:      "ca",
		}); err != nil {
			return err
		}

		if etcdCluster.Name == "cilium" {
			clientsCaCilium := &fitasks.Keypair{
				Name:      fi.String("etcd-clients-ca-cilium"),
				Lifecycle: b.Lifecycle,
				Subject:   "cn=etcd-clients-ca-cilium",
				Type:      "ca",
			}
			c.AddTask(clientsCaCilium)

			if !b.UseKopsControllerForNodeBootstrap() {
				c.AddTask(&fitasks.Keypair{
					Name:      fi.String("etcd-client-cilium"),
					Lifecycle: b.Lifecycle,
					Subject:   "cn=cilium",
					Type:      "client",
					Signer:    clientsCaCilium,
				})
			}
		}
	}

	return nil
}

type etcdClusterSpec struct {
	MemberCount int32  `json:"memberCount,omitempty"`
	EtcdVersion string `json:"etcdVersion,omitempty"`
}

func (b *EtcdManagerBuilder) buildManifest(etcdCluster kops.EtcdClusterSpec) (*v1.Pod, error) {
	return b.buildPod(etcdCluster)
}

// Until we introduce the bundle, we hard-code the manifest
var defaultManifest = `
apiVersion: v1
kind: Pod
metadata:
  name: etcd-manager
  namespace: kube-system
spec:
  containers:
  - image: k8s.gcr.io/etcdadm/etcd-manager:3.0.20210707
    name: etcd-manager
    resources:
      requests:
        cpu: 100m
        memory: 100Mi
    # TODO: Would be nice to reduce these permissions; needed for volume mounting
    securityContext:
      privileged: true
    volumeMounts:
    # TODO: Would be nice to scope this more tightly, but needed for volume mounting
    - mountPath: /rootfs
      name: rootfs
    - mountPath: /run<commit_msg>Update etcd-manager to 3.0.20211007<commit_after>/*
Copyright 2019 The Kubernetes Authors.

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

    http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

package etcdmanager

import (
	"encoding/json"
	"fmt"
	"strings"

	v1 "k8s.io/api/core/v1"
	"k8s.io/apimachinery/pkg/api/resource"
	"k8s.io/klog/v2"
	"k8s.io/kops/pkg/apis/kops"
	"k8s.io/kops/pkg/assets"
	"k8s.io/kops/pkg/dns"
	"k8s.io/kops/pkg/featureflag"
	"k8s.io/kops/pkg/flagbuilder"
	"k8s.io/kops/pkg/k8scodecs"
	"k8s.io/kops/pkg/kubemanifest"
	"k8s.io/kops/pkg/model"
	"k8s.io/kops/pkg/wellknownports"
	"k8s.io/kops/upup/pkg/fi"
	"k8s.io/kops/upup/pkg/fi/cloudup/aliup"
	"k8s.io/kops/upup/pkg/fi/cloudup/awsup"
	"k8s.io/kops/upup/pkg/fi/cloudup/azure"
	"k8s.io/kops/upup/pkg/fi/cloudup/do"
	"k8s.io/kops/upup/pkg/fi/cloudup/gce"
	"k8s.io/kops/upup/pkg/fi/cloudup/openstack"
	"k8s.io/kops/upup/pkg/fi/fitasks"
	"k8s.io/kops/util/pkg/env"
	"k8s.io/kops/util/pkg/exec"
)

// EtcdManagerBuilder builds the manifest for the etcd-manager
type EtcdManagerBuilder struct {
	*model.KopsModelContext
	Lifecycle    fi.Lifecycle
	AssetBuilder *assets.AssetBuilder
}

var _ fi.ModelBuilder = &EtcdManagerBuilder{}

// Build creates the tasks
func (b *EtcdManagerBuilder) Build(c *fi.ModelBuilderContext) error {
	for _, etcdCluster := range b.Cluster.Spec.EtcdClusters {
		name := etcdCluster.Name
		version := etcdCluster.Version

		backupStore := ""
		if etcdCluster.Backups != nil {
			backupStore = etcdCluster.Backups.BackupStore
		}
		if backupStore == "" {
			return fmt.Errorf("backupStore must be set for use with etcd-manager")
		}

		manifest, err := b.buildManifest(etcdCluster)
		if err != nil {
			return err
		}

		manifestYAML, err := k8scodecs.ToVersionedYaml(manifest)
		if err != nil {
			return fmt.Errorf("error marshaling manifest to yaml: %v", err)
		}

		c.AddTask(&fitasks.ManagedFile{
			Contents:  fi.NewBytesResource(manifestYAML),
			Lifecycle: b.Lifecycle,
			Location:  fi.String("manifests/etcd/" + name + ".yaml"),
			Name:      fi.String("manifests-etcdmanager-" + name),
		})

		info := &etcdClusterSpec{
			EtcdVersion: version,
			MemberCount: int32(len(etcdCluster.Members)),
		}

		d, err := json.MarshalIndent(info, "", "  ")
		if err != nil {
			return err
		}

		// Ensure a unique backup location for each etcd cluster
		// if a backupStore is not specified.
		var location string
		if backupStore == "" {
			location = "backups/etcd/" + etcdCluster.Name
		}

		c.AddTask(&fitasks.ManagedFile{
			Contents:  fi.NewBytesResource(d),
			Lifecycle: b.Lifecycle,
			Base:      fi.String(backupStore),
			// TODO: We need this to match the backup base (currently)
			Location: fi.String(location + "/control/etcd-cluster-spec"),
			Name:     fi.String("etcd-cluster-spec-" + name),
		})

		// We create a CA keypair to enable secure communication
		c.AddTask(&fitasks.Keypair{
			Name:      fi.String("etcd-manager-ca-" + etcdCluster.Name),
			Lifecycle: b.Lifecycle,
			Subject:   "cn=etcd-manager-ca-" + etcdCluster.Name,
			Type:      "ca",
		})

		// We create a CA for etcd peers and a separate one for clients
		c.AddTask(&fitasks.Keypair{
			Name:      fi.String("etcd-peers-ca-" + etcdCluster.Name),
			Lifecycle: b.Lifecycle,
			Subject:   "cn=etcd-peers-ca-" + etcdCluster.Name,
			Type:      "ca",
		})

		// Because API server can only have a single client-cert, we need to share a client CA
		if err := c.EnsureTask(&fitasks.Keypair{
			Name:      fi.String("etcd-clients-ca"),
			Lifecycle: b.Lifecycle,
			Subject:   "cn=etcd-clients-ca",
			Type:      "ca",
		}); err != nil {
			return err
		}

		if etcdCluster.Name == "cilium" {
			clientsCaCilium := &fitasks.Keypair{
				Name:      fi.String("etcd-clients-ca-cilium"),
				Lifecycle: b.Lifecycle,
				Subject:   "cn=etcd-clients-ca-cilium",
				Type:      "ca",
			}
			c.AddTask(clientsCaCilium)

			if !b.UseKopsControllerForNodeBootstrap() {
				c.AddTask(&fitasks.Keypair{
					Name:      fi.String("etcd-client-cilium"),
					Lifecycle: b.Lifecycle,
					Subject:   "cn=cilium",
					Type:      "client",
					Signer:    clientsCaCilium,
				})
			}
		}
	}

	return nil
}

type etcdClusterSpec struct {
	MemberCount int32  `json:"memberCount,omitempty"`
	EtcdVersion string `json:"etcdVersion,omitempty"`
}

func (b *EtcdManagerBuilder) buildManifest(etcdCluster kops.EtcdClusterSpec) (*v1.Pod, error) {
	return b.buildPod(etcdCluster)
}

// Until we introduce the bundle, we hard-code the manifest
var defaultManifest = `
apiVersion: v1
kind: Pod
metadata:
  name: etcd-manager
  namespace: kube-system
spec:
  containers:
  - image: k8s.gcr.io/etcdadm/etcd-manager:3.0.20211007
    name: etcd-manager
    resources:
      requests:
        cpu: 100m
        memory: 100Mi
    # TODO: Would be nice to reduce these permissions; needed for volume mounting
    securityContext:
      privileged: true
    volumeMounts:
    # TODO: Would be nice to scope this more tightly, but needed for volume mounting
    - mountPath: /rootfs
      name: rootfs
    - mountPath: /run
{'fraction_non_alphanumeric': 0.12698268003646307, 'fraction_numerical': 0.011485870556061987, 'mean_word_length': 3.3175914994096813, 'pattern_counts': {'":': 0, '<': 3, '<?xml version=': 0, '>': 3, 'https://': 0, 'lorem ipsum': 0, 'www.': 2, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '7647461', 'n_tokens_mistral': 4461, 'n_tokens_neox': 3994, 'n_words': 902}