diff --git a/cmd/terraform-provider-flexkube/flexkube/kubelet.go b/cmd/terraform-provider-flexkube/flexkube/kubelet.go new file mode 100644 index 00000000..4bf58bf8 --- /dev/null +++ b/cmd/terraform-provider-flexkube/flexkube/kubelet.go @@ -0,0 +1,96 @@ +package flexkube + +import ( + "github.com/hashicorp/terraform-plugin-sdk/helper/schema" + + "github.com/flexkube/libflexkube/pkg/kubelet" + "github.com/flexkube/libflexkube/pkg/types" +) + +func kubeletsUnmarshal(i interface{}) []kubelet.Kubelet { + j := i.([]interface{}) + + kubelets := []kubelet.Kubelet{} + + for _, v := range j { + if v == nil { + continue + } + + t := v.(map[string]interface{}) + + k := kubelet.Kubelet{ + Image: t["image"].(string), + BootstrapKubeconfig: t["bootstrap_kubeconfig"].(string), + KubernetesCACertificate: types.Certificate(t["kubernetes_ca_certificate"].(string)), + PrivilegedLabelsKubeconfig: t["privileged_labels_kubeconfig"].(string), + CgroupDriver: t["cgroup_driver"].(string), + NetworkPlugin: t["network_plugin"].(string), + HairpinMode: t["hairpin_mode"].(string), + VolumePluginDir: t["volume_plugin_dir"].(string), + Name: t["name"].(string), + Address: t["address"].(string), + ClusterDNSIPs: stringListUnmarshal(t["cluster_dns_ips"]), + Taints: stringMapUnmarshal(t["taints"]), + Labels: stringMapUnmarshal(t["labels"]), + PrivilegedLabels: stringMapUnmarshal(t["privileged_labels"]), + SystemReserved: stringMapUnmarshal(t["system_reserved"]), + KubeReserved: stringMapUnmarshal(t["kube_reserved"]), + } + + if v, ok := t["host"]; ok && len(v.([]interface{})) == 1 { + k.Host = hostUnmarshal(v.([]interface{})[0]) + } + + kubelets = append(kubelets, k) + } + + return kubelets +} + +func kubeletSchema() *schema.Schema { + return requiredList(false, false, func(computed bool) *schema.Resource { + return &schema.Resource{ + Schema: map[string]*schema.Schema{ + "name": requiredString(false), + "image": optionalString(false), + "host": hostSchema(false), + "address": requiredString(false), + "bootstrap_kubeconfig": optionalString(false), + "kubernetes_ca_certificate": optionalString(false), + "cluster_dns_ips": optionalStringList(false), + "taints": optionalMapPrimitive(false, func(computed bool) *schema.Schema { + return &schema.Schema{ + Type: schema.TypeString, + } + }), + "labels": optionalMapPrimitive(false, func(computed bool) *schema.Schema { + return &schema.Schema{ + Type: schema.TypeString, + } + }), + "privileged_labels": optionalMapPrimitive(false, func(computed bool) *schema.Schema { + return &schema.Schema{ + Type: schema.TypeString, + } + }), + "privileged_labels_kubeconfig": sensitiveString(false), + "cgroup_driver": optionalString(false), + "network_plugin": optionalString(false), + "system_reserved": optionalMapPrimitive(false, func(computed bool) *schema.Schema { + return &schema.Schema{ + Type: schema.TypeString, + } + }), + "kube_reserved": optionalMapPrimitive(false, func(computed bool) *schema.Schema { + return &schema.Schema{ + Type: schema.TypeString, + } + }), + "hairpin_mode": optionalString(false), + "volume_plugin_dir": optionalString(false), + "pod_cidr": optionalString(false), + }, + } + }) +} diff --git a/cmd/terraform-provider-flexkube/flexkube/resource_kubelet_pool.go b/cmd/terraform-provider-flexkube/flexkube/resource_kubelet_pool.go index 808cb9c3..212c4eb1 100644 --- a/cmd/terraform-provider-flexkube/flexkube/resource_kubelet_pool.go +++ b/cmd/terraform-provider-flexkube/flexkube/resource_kubelet_pool.go @@ -4,37 +4,83 @@ import ( "github.com/hashicorp/terraform-plugin-sdk/helper/schema" "github.com/flexkube/libflexkube/pkg/kubelet" + "github.com/flexkube/libflexkube/pkg/types" ) func resourceKubeletPool() *schema.Resource { return &schema.Resource{ - Create: resourceKubeletPoolCreate, - Read: resourceKubeletPoolRead, - Delete: resourceKubeletPoolDelete, - Update: resourceKubeletPoolCreate, - Schema: map[string]*schema.Schema{ - "config": { - Type: schema.TypeString, - Required: true, - }, - "state": { - Type: schema.TypeString, - Computed: true, - }, - }, + Create: resourceCreate(kubeletPoolUnmarshal), + Read: resourceRead(kubeletPoolUnmarshal), + Delete: resourceDelete(kubeletPoolUnmarshal, "kubelet"), + Update: resourceCreate(kubeletPoolUnmarshal), + CustomizeDiff: resourceDiff(kubeletPoolUnmarshal), + Schema: withCommonFields(map[string]*schema.Schema{ + "image": optionalString(false), + "ssh": sshSchema(false), + "bootstrap_kubeconfig": optionalString(false), + "kubernetes_ca_certificate": optionalString(false), + "cluster_dns_ips": optionalStringList(false), + "taints": optionalMapPrimitive(false, func(computed bool) *schema.Schema { + return &schema.Schema{ + Type: schema.TypeString, + } + }), + "labels": optionalMapPrimitive(false, func(computed bool) *schema.Schema { + return &schema.Schema{ + Type: schema.TypeString, + } + }), + "privileged_labels": optionalMapPrimitive(false, func(computed bool) *schema.Schema { + return &schema.Schema{ + Type: schema.TypeString, + } + }), + "privileged_labels_kubeconfig": sensitiveString(false), + "cgroup_driver": optionalString(false), + "network_plugin": optionalString(false), + "system_reserved": optionalMapPrimitive(false, func(computed bool) *schema.Schema { + return &schema.Schema{ + Type: schema.TypeString, + } + }), + "kube_reserved": optionalMapPrimitive(false, func(computed bool) *schema.Schema { + return &schema.Schema{ + Type: schema.TypeString, + } + }), + "hairpin_mode": optionalString(false), + "volume_plugin_dir": optionalString(false), + "kubelet": kubeletSchema(), + }), } } -const resourceKubeletPoolName = "kubelet pool" +func kubeletPoolUnmarshal(d getter, includeState bool) types.ResourceConfig { + c := &kubelet.Pool{ + Image: d.Get("image").(string), + BootstrapKubeconfig: d.Get("bootstrap_kubeconfig").(string), + KubernetesCACertificate: types.Certificate(d.Get("kubernetes_ca_certificate").(string)), + PrivilegedLabelsKubeconfig: d.Get("privileged_labels_kubeconfig").(string), + CgroupDriver: d.Get("cgroup_driver").(string), + NetworkPlugin: d.Get("network_plugin").(string), + HairpinMode: d.Get("hairpin_mode").(string), + VolumePluginDir: d.Get("volume_plugin_dir").(string), + Kubelets: kubeletsUnmarshal(d.Get("kubelet")), + ClusterDNSIPs: stringListUnmarshal(d.Get("cluster_dns_ips")), + Taints: stringMapUnmarshal(d.Get("taints")), + Labels: stringMapUnmarshal(d.Get("labels")), + PrivilegedLabels: stringMapUnmarshal(d.Get("privileged_labels")), + SystemReserved: stringMapUnmarshal(d.Get("system_reserved")), + KubeReserved: stringMapUnmarshal(d.Get("kube_reserved")), + } -func resourceKubeletPoolCreate(d *schema.ResourceData, m interface{}) error { - return createResource(d, &kubelet.Pool{}, resourceKubeletPoolName) -} + if includeState { + c.State = getState(d) + } -func resourceKubeletPoolRead(d *schema.ResourceData, m interface{}) error { - return readResource(d, &kubelet.Pool{}, resourceKubeletPoolName) -} + if d, ok := d.GetOk("ssh"); ok && len(d.([]interface{})) == 1 { + c.SSH = sshUnmarshal(d.([]interface{})[0]) + } -func resourceKubeletPoolDelete(d *schema.ResourceData, m interface{}) error { - return deleteResource(d) + return c } diff --git a/cmd/terraform-provider-flexkube/flexkube/resource_kubelet_pool_test.go b/cmd/terraform-provider-flexkube/flexkube/resource_kubelet_pool_test.go new file mode 100644 index 00000000..27d094dc --- /dev/null +++ b/cmd/terraform-provider-flexkube/flexkube/resource_kubelet_pool_test.go @@ -0,0 +1,334 @@ +package flexkube //nolint:dupl + +import ( + "regexp" + "testing" + + "github.com/hashicorp/terraform-plugin-sdk/helper/resource" + "github.com/hashicorp/terraform-plugin-sdk/terraform" + "github.com/terraform-providers/terraform-provider-tls/tls" +) + +func TestKubeletPoolPlanOnly(t *testing.T) { + config := ` +locals { + controller_ips = ["1.1.1.1"] + controller_names = ["controller01"] + cgroup_driver = "systemd" + network_plugin = "cni" + first_controller_ip = local.controller_ips[0] + api_port = 6443 + node_load_balancer_address = "127.0.0.1" + controller_cidrs = ["10.0.1/0/24"] + + kubeconfig_admin = < 0 ? 1 : 0 ssh_private_key = file(var.ssh_private_key_path) @@ -231,7 +184,7 @@ resource "flexkube_apiloadbalancer_pool" "nodes" { servers = formatlist("%s:%d", local.controller_ips, local.api_port) ssh { - private_key = file(var.ssh_private_key_path) + private_key = local.ssh_private_key port = var.node_ssh_port user = "core" } @@ -256,7 +209,7 @@ resource "flexkube_apiloadbalancer_pool" "bootstrap" { servers = ["${local.bootstrap_api_bind}:${local.api_port}"] ssh { - private_key = file(var.ssh_private_key_path) + private_key = local.ssh_private_key port = var.node_ssh_port user = "core" } @@ -320,7 +273,7 @@ resource "flexkube_controlplane" "bootstrap" { user = "core" address = local.first_controller_ip port = var.node_ssh_port - private_key = file(var.ssh_private_key_path) + private_key = local.ssh_private_key } depends_on = [ @@ -404,7 +357,59 @@ resource "flexkube_helm_release" "calico" { } resource "flexkube_kubelet_pool" "controller" { - config = local.kubelet_pool_config + bootstrap_kubeconfig = local.bootstrap_kubeconfig + cgroup_driver = local.cgroup_driver + network_plugin = local.network_plugin + kubernetes_ca_certificate = module.kubernetes_pki.kubernetes_ca_cert + hairpin_mode = local.network_plugin == "kubenet" ? "promiscuous-bridge" : "hairpin-veth" + volume_plugin_dir = "/var/lib/kubelet/volumeplugins" + cluster_dns_ips = [ + "11.0.0.10" + ] + + system_reserved = { + "cpu" = "100m" + "memory" = "500Mi" + } + + kube_reserved = { + // 100MB for kubelet and 200MB for etcd. + "memory" = "300Mi" + "cpu" = "100m" + } + + privileged_labels = { + "node-role.kubernetes.io/master" = "" + } + + privileged_labels_kubeconfig = local.kubeconfig_admin + + taints = { + "node-role.kubernetes.io/master" = "NoSchedule" + } + + ssh { + user = "core" + port = var.node_ssh_port + private_key = local.ssh_private_key + } + + dynamic "kubelet" { + for_each = local.controller_ips + + content { + name = local.controller_names[kubelet.key] + pod_cidr = local.network_plugin == "kubenet" ? local.controller_cidrs[kubelet.key] : "" + + address = local.controller_ips[kubelet.key] + + host { + ssh { + address = kubelet.value + } + } + } + } depends_on = [ flexkube_apiloadbalancer_pool.nodes, @@ -416,7 +421,49 @@ resource "flexkube_kubelet_pool" "controller" { resource "flexkube_kubelet_pool" "workers" { count = local.deploy_workers - config = local.kubelet_worker_pool_config + bootstrap_kubeconfig = local.bootstrap_kubeconfig + cgroup_driver = local.cgroup_driver + network_plugin = local.network_plugin + kubernetes_ca_certificate = module.kubernetes_pki.kubernetes_ca_cert + hairpin_mode = local.network_plugin == "kubenet" ? "promiscuous-bridge" : "hairpin-veth" + volume_plugin_dir = "/var/lib/kubelet/volumeplugins" + cluster_dns_ips = [ + "11.0.0.10" + ] + + system_reserved = { + "cpu" = "100m" + "memory" = "500Mi" + } + + kube_reserved = { + // 100MB for kubelet and 200MB for etcd. + "memory" = "300Mi" + "cpu" = "100m" + } + + ssh { + user = "core" + port = var.node_ssh_port + private_key = local.ssh_private_key + } + + dynamic "kubelet" { + for_each = local.worker_ips + + content { + name = local.worker_names[kubelet.key] + pod_cidr = local.network_plugin == "kubenet" ? local.worker_cidrs[kubelet.key] : "" + + address = local.worker_ips[kubelet.key] + + host { + ssh { + address = kubelet.value + } + } + } + } depends_on = [ flexkube_apiloadbalancer_pool.nodes, diff --git a/e2e/templates/kubelet_config.yaml.tmpl b/e2e/templates/kubelet_config.yaml.tmpl deleted file mode 100644 index cc195f14..00000000 --- a/e2e/templates/kubelet_config.yaml.tmpl +++ /dev/null @@ -1,65 +0,0 @@ -cgroupDriver: ${cgroup_driver} -networkPlugin: ${network_plugin} -%{ if network_plugin == "kubenet" } -hairpinMode: promiscuous-bridge -%{~ else ~} -hairpinMode: hairpin-veth -%{~ endif } -volumePluginDir: /var/lib/kubelet/volumeplugins -systemReserved: - cpu: 100m - memory: 500Mi -kubeReserved: -%{ for k, v in kube_reserved ~} - ${k}: '${v}' -%{ endfor ~} -kubelets: -%{ for index, ssh_address in ssh_addresses ~} -- address: ${kubelet_addresses[index]} - %{~ if network_plugin == "kubenet" ~} - podCIDR: ${kubelet_pod_cidrs[index]} - %{~ endif ~} - name: ${kubelet_names[index]} - bootstrapKubeconfig: | - ${indent(6, trimspace(bootstrap_kubeconfig))} - host: - ssh: - address: ${ssh_address} -%{ endfor ~} -kubernetesCACertificate: | - ${indent(4, trimspace(kubernetes_ca_certificate))} -clusterDNSIPs: -- 11.0.0.10 - -%{~ if length(labels) > 0 ~} -labels: -%{ for k, v in labels ~} - ${k}: '${v}' -%{ endfor ~} -%{~ endif ~} - -%{~ if length(taints) > 0 ~} -taints: -%{ for k, v in taints ~} - ${k}: '${v}' -%{ endfor ~} -%{~ endif ~} - -%{ if privileged_labels_kubeconfig != "" ~} -privilegedLabelsKubeconfig: | - ${indent(4, trimspace(privileged_labels_kubeconfig))} -%{ endif ~} - -%{ if length(privileged_labels) > 0 } - -privilegedLabels: -%{ for k, v in privileged_labels ~} - ${k}: '${v}' -%{ endfor ~} -%{~ endif ~} - -ssh: - user: "core" - port: ${ssh_port} - privateKey: | - ${indent(8, trimspace(ssh_private_key))} diff --git a/local-testing/files.tf b/local-testing/files.tf index 2f161d55..5d3489a2 100644 --- a/local-testing/files.tf +++ b/local-testing/files.tf @@ -49,11 +49,11 @@ resource "local_file" "apiloadbalancer_state" { } resource "local_file" "kubelet_pool_config" { - sensitive_content = local.kubelet_pool_config + sensitive_content = flexkube_kubelet_pool.controller.config_yaml filename = "./resources/kubelet-pool/config.yaml" } resource "local_file" "kubelet_pool_state" { - sensitive_content = flexkube_kubelet_pool.controller.state + sensitive_content = flexkube_kubelet_pool.controller.state_yaml filename = "./resources/kubelet-pool/state.yaml" } diff --git a/pkg/container/containersstate.go b/pkg/container/containersstate.go index 11e19dac..737b4f8e 100644 --- a/pkg/container/containersstate.go +++ b/pkg/container/containersstate.go @@ -123,7 +123,6 @@ func (s containersState) Export() ContainersState { }, Host: m.host, ConfigFiles: m.configFiles, - Hooks: m.hooks, } if s := m.container.Status(); s.ID != "" && s.Status != "" { diff --git a/pkg/kubelet/kubelet.go b/pkg/kubelet/kubelet.go index 41c449ae..d5e646ab 100644 --- a/pkg/kubelet/kubelet.go +++ b/pkg/kubelet/kubelet.go @@ -15,29 +15,30 @@ import ( "github.com/flexkube/libflexkube/pkg/defaults" "github.com/flexkube/libflexkube/pkg/host" "github.com/flexkube/libflexkube/pkg/kubernetes/client" + "github.com/flexkube/libflexkube/pkg/types" ) // Kubelet represents single kubelet instance. type Kubelet struct { - Address string `json:"address"` - Image string `json:"image"` - Host host.Host `json:"host"` - BootstrapKubeconfig string `json:"bootstrapKubeconfig"` + Address string `json:"address,omitempty"` + Image string `json:"image,omitempty"` + Host host.Host `json:"host,omitempty"` + BootstrapKubeconfig string `json:"bootstrapKubeconfig,omitempty"` // TODO we require CA certificate, so it can be referred in bootstrap-kubeconfig. Maybe we should be responsible for creating // bootstrap-kubeconfig too then? - KubernetesCACertificate string `json:"kubernetesCACertificate"` - ClusterDNSIPs []string `json:"clusterDNSIPs"` - Name string `json:"name"` - Taints map[string]string `json:"taints"` - Labels map[string]string `json:"labels"` - PrivilegedLabels map[string]string `json:"privilegedLabels"` - PrivilegedLabelsKubeconfig string `json:"privilegedLabelsKubeconfig"` - CgroupDriver string `json:"cgroupDriver"` - NetworkPlugin string `json:"networkPlugin"` - SystemReserved map[string]string `json:"systemReserved"` - KubeReserved map[string]string `json:"kubeReserved"` - HairpinMode string `json:"hairpinMode"` - VolumePluginDir string `json:"volumePluginDir"` + KubernetesCACertificate types.Certificate `json:"kubernetesCACertificate,omitempty"` + ClusterDNSIPs []string `json:"clusterDNSIPs,omitempty"` + Name string `json:"name,omitempty"` + Taints map[string]string `json:"taints,omitempty"` + Labels map[string]string `json:"labels,omitempty"` + PrivilegedLabels map[string]string `json:"privilegedLabels,omitempty"` + PrivilegedLabelsKubeconfig string `json:"privilegedLabelsKubeconfig,omitempty"` + CgroupDriver string `json:"cgroupDriver,omitempty"` + NetworkPlugin string `json:"networkPlugin,omitempty"` + SystemReserved map[string]string `json:"systemReserved,omitempty"` + KubeReserved map[string]string `json:"kubeReserved,omitempty"` + HairpinMode string `json:"hairpinMode,omitempty"` + VolumePluginDir string `json:"volumePluginDir,omitempty"` // Depending on the network plugin, this should be optional, but for now it's required. PodCIDR string `json:"podCIDR,omitempty"` @@ -72,6 +73,19 @@ func (k *Kubelet) New() (container.ResourceInstance, error) { func (k *Kubelet) Validate() error { var errors util.ValidateError + b, err := yaml.Marshal(k) + if err != nil { + errors = append(errors, fmt.Errorf("failed to validate: %w", err)) + } + + if err := yaml.Unmarshal(b, &k); err != nil { + errors = append(errors, fmt.Errorf("validation failed: %w", err)) + } + + if k.KubernetesCACertificate == "" { + errors = append(errors, fmt.Errorf("kubernetesCACertificate can't be empty")) + } + if k.BootstrapKubeconfig == "" { errors = append(errors, fmt.Errorf("bootstrapKubeconfig can't be empty")) } @@ -88,6 +102,10 @@ func (k *Kubelet) Validate() error { errors = append(errors, fmt.Errorf("privilegedLabelsKubeconfig specified, but no privilegedLabels requested")) } + if k.Name == "" { + errors = append(errors, fmt.Errorf("name can't be empty")) + } + switch k.NetworkPlugin { case "cni": if k.PodCIDR != "" { @@ -180,7 +198,7 @@ func (k *kubelet) configFiles() (map[string]string, error) { // kubelet.yaml file is a recommended way to configure the kubelet. "/etc/kubernetes/kubelet/kubelet.yaml": config, "/etc/kubernetes/kubelet/bootstrap-kubeconfig": k.config.BootstrapKubeconfig, - "/etc/kubernetes/kubelet/pki/ca.crt": k.config.KubernetesCACertificate, + "/etc/kubernetes/kubelet/pki/ca.crt": string(k.config.KubernetesCACertificate), }, nil } diff --git a/pkg/kubelet/kubelet_test.go b/pkg/kubelet/kubelet_test.go index 1e3b27ee..20f51d50 100644 --- a/pkg/kubelet/kubelet_test.go +++ b/pkg/kubelet/kubelet_test.go @@ -3,15 +3,19 @@ package kubelet import ( "testing" + "github.com/flexkube/libflexkube/internal/utiltest" "github.com/flexkube/libflexkube/pkg/host" "github.com/flexkube/libflexkube/pkg/host/transport/direct" + "github.com/flexkube/libflexkube/pkg/types" ) func TestToHostConfiguredContainer(t *testing.T) { kk := &Kubelet{ - BootstrapKubeconfig: "foo", - NetworkPlugin: "cni", - VolumePluginDir: "/var/lib/kubelet/volumeplugins", + BootstrapKubeconfig: "foo", + Name: "foo", + NetworkPlugin: "cni", + VolumePluginDir: "/var/lib/kubelet/volumeplugins", + KubernetesCACertificate: types.Certificate(utiltest.GenerateX509Certificate(t)), Host: host.Host{ DirectConfig: &direct.Config{}, }, @@ -42,3 +46,67 @@ func TestToHostConfiguredContainer(t *testing.T) { t.Fatalf("should produce valid HostConfiguredContainer, got: %v", err) } } + +// Validate() +func TestKubeletValidate(t *testing.T) { + k := &Kubelet{ + BootstrapKubeconfig: "foo", + Name: "foo", + NetworkPlugin: "cni", + VolumePluginDir: "/foo", + KubernetesCACertificate: types.Certificate(utiltest.GenerateX509Certificate(t)), + Host: host.Host{ + DirectConfig: &direct.Config{}, + }, + } + + if err := k.Validate(); err != nil { + t.Fatalf("validation of kubelet should pass, got: %v", err) + } +} + +func TestKubeletValidateRequireName(t *testing.T) { + k := &Kubelet{ + BootstrapKubeconfig: "foo", + NetworkPlugin: "cni", + VolumePluginDir: "/foo", + Host: host.Host{ + DirectConfig: &direct.Config{}, + }, + } + + if err := k.Validate(); err == nil { + t.Fatalf("validation of kubelet should fail when name is not set") + } +} + +func TestKubeletValidateEmptyCA(t *testing.T) { + k := &Kubelet{ + BootstrapKubeconfig: "foo", + NetworkPlugin: "cni", + VolumePluginDir: "/foo", + Host: host.Host{ + DirectConfig: &direct.Config{}, + }, + } + + if err := k.Validate(); err == nil { + t.Fatalf("validation of kubelet should fail when kubernetes CA certificate is not set") + } +} + +func TestKubeletValidateBadCA(t *testing.T) { + k := &Kubelet{ + BootstrapKubeconfig: "foo", + NetworkPlugin: "cni", + VolumePluginDir: "/foo", + KubernetesCACertificate: "doh", + Host: host.Host{ + DirectConfig: &direct.Config{}, + }, + } + + if err := k.Validate(); err == nil { + t.Fatalf("validation of kubelet should fail when kubernetes CA certificate is not valid") + } +} diff --git a/pkg/kubelet/pool.go b/pkg/kubelet/pool.go index aa0ad1c3..df1e1e9d 100644 --- a/pkg/kubelet/pool.go +++ b/pkg/kubelet/pool.go @@ -18,25 +18,25 @@ import ( // Pool represents group of kubelet instances and their configuration. type Pool struct { // User-configurable fields. - Image string `json:"image"` - SSH *ssh.Config `json:"ssh"` - BootstrapKubeconfig string `json:"bootstrapKubeconfig"` - Kubelets []Kubelet `json:"kubelets"` - KubernetesCACertificate string `json:"kubernetesCACertificate"` - ClusterDNSIPs []string `json:"clusterDNSIPs"` - Taints map[string]string `json:"taints"` - Labels map[string]string `json:"labels"` - PrivilegedLabels map[string]string `json:"privilegedLabels"` - PrivilegedLabelsKubeconfig string `json:"privilegedLabelsKubeconfig"` - CgroupDriver string `json:"cgroupDriver"` - NetworkPlugin string `json:"networkPlugin"` - SystemReserved map[string]string `json:"systemReserved"` - KubeReserved map[string]string `json:"kubeReserved"` - HairpinMode string `json:"hairpinMode"` - VolumePluginDir string `json:"volumePluginDir"` + Image string `json:"image,omitempty"` + SSH *ssh.Config `json:"ssh,omitempty"` + BootstrapKubeconfig string `json:"bootstrapKubeconfig,omitempty"` + Kubelets []Kubelet `json:"kubelets,omitempty"` + KubernetesCACertificate types.Certificate `json:"kubernetesCACertificate,omitempty"` + ClusterDNSIPs []string `json:"clusterDNSIPs,omitempty"` + Taints map[string]string `json:"taints,omitempty"` + Labels map[string]string `json:"labels,omitempty"` + PrivilegedLabels map[string]string `json:"privilegedLabels,omitempty"` + PrivilegedLabelsKubeconfig string `json:"privilegedLabelsKubeconfig,omitempty"` + CgroupDriver string `json:"cgroupDriver,omitempty"` + NetworkPlugin string `json:"networkPlugin,omitempty"` + SystemReserved map[string]string `json:"systemReserved,omitempty"` + KubeReserved map[string]string `json:"kubeReserved,omitempty"` + HairpinMode string `json:"hairpinMode,omitempty"` + VolumePluginDir string `json:"volumePluginDir,omitempty"` // Serializable fields. - State container.ContainersState `json:"state"` + State container.ContainersState `json:"state,omitempty"` } // pool is a validated version of Pool. @@ -48,7 +48,7 @@ type pool struct { func (p *Pool) propagateKubelet(k *Kubelet) { k.Image = util.PickString(k.Image, p.Image) k.BootstrapKubeconfig = util.PickString(k.BootstrapKubeconfig, p.BootstrapKubeconfig) - k.KubernetesCACertificate = util.PickString(k.KubernetesCACertificate, p.KubernetesCACertificate) + k.KubernetesCACertificate = types.Certificate(util.PickString(string(k.KubernetesCACertificate), string(p.KubernetesCACertificate))) k.ClusterDNSIPs = util.PickStringSlice(k.ClusterDNSIPs, p.ClusterDNSIPs) k.Labels = util.PickStringMap(k.Labels, p.Labels) k.PrivilegedLabels = util.PickStringMap(k.PrivilegedLabels, p.PrivilegedLabels) diff --git a/pkg/kubelet/pool_test.go b/pkg/kubelet/pool_test.go index cf5ac0f2..d51d9989 100644 --- a/pkg/kubelet/pool_test.go +++ b/pkg/kubelet/pool_test.go @@ -1,8 +1,13 @@ package kubelet import ( + "bytes" + "strings" "testing" + "text/template" + "github.com/flexkube/libflexkube/internal/util" + "github.com/flexkube/libflexkube/internal/utiltest" "github.com/flexkube/libflexkube/pkg/types" ) @@ -16,11 +21,21 @@ ssh: retryInterval: 1s bootstrapKubeconfig: foo volumePluginDir: /var/lib/kubelet/volumeplugins +kubernetesCACertificate: | + {{.}} kubelets: - networkPlugin: cni + name: foo ` - p, err := FromYaml([]byte(y)) + var buf bytes.Buffer + + tpl := template.Must(template.New("c").Parse(y)) + if err := tpl.Execute(&buf, strings.TrimSpace(util.Indent(utiltest.GenerateX509Certificate(t), " "))); err != nil { + t.Fatalf("Failed to generate config from template: %v", err) + } + + p, err := FromYaml(buf.Bytes()) if err != nil { t.Fatalf("Creating pool from YAML should succeed, got: %v", err) } @@ -40,6 +55,7 @@ ssh: volumePluginDir: /var/lib/kubelet/volumeplugins kubelets: - networkPlugin: cni + name: foo ` if _, err := FromYaml([]byte(y)); err == nil {