diff --git a/go.mod b/go.mod index 94ca6a62e..c21c5f051 100644 --- a/go.mod +++ b/go.mod @@ -13,7 +13,7 @@ require ( github.com/google/gofuzz v0.0.0-20161122191042-44d81051d367 // indirect github.com/googleapis/gnostic v0.2.0 // indirect github.com/gregjones/httpcache v0.0.0-20190203031600-7a902570cb17 // indirect - github.com/imdario/mergo v0.3.7 // indirect + github.com/imdario/mergo v0.3.7 github.com/json-iterator/go v0.0.0-20180612202835-f2b4162afba3 // indirect github.com/koron-go/prefixw v0.0.0-20181013140428-271b207a7572 github.com/kr/fs v0.1.0 // indirect diff --git a/pkg/certificate/ca.go b/pkg/certificate/ca.go index 7bba93baa..e6d597deb 100644 --- a/pkg/certificate/ca.go +++ b/pkg/certificate/ca.go @@ -27,6 +27,8 @@ import ( // DownloadCA grabs CA certs/keys from leader host func DownloadCA(s *state.State) error { + s.Logger.Info("Downloading PKI…") + return s.RunTaskOnLeader(func(s *state.State, _ *kubeoneapi.HostConfig, conn ssh.Connection) error { _, _, err := s.Runner.Run(` mkdir -p ./{{ .WORK_DIR }}/pki/etcd diff --git a/pkg/clientutil/create_or_update.go b/pkg/clientutil/create_or_update.go new file mode 100644 index 000000000..b02c0ab0a --- /dev/null +++ b/pkg/clientutil/create_or_update.go @@ -0,0 +1,58 @@ +/* +Copyright 2019 The KubeOne 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 clientutil + +import ( + "context" + + "github.com/imdario/mergo" + "github.com/pkg/errors" + + k8serrors "k8s.io/apimachinery/pkg/api/errors" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime" + "sigs.k8s.io/controller-runtime/pkg/client" +) + +// CreateOrUpdate makes it easy to "apply" objects to kubernetes API server +func CreateOrUpdate(ctx context.Context, c client.Client, obj runtime.Object) error { + existing := obj.DeepCopyObject() + existingMetaObj, ok := existing.(metav1.Object) + if !ok { + return errors.Errorf("%T does not implement metav1.Object interface", obj) + } + + key := client.ObjectKey{ + Name: existingMetaObj.GetName(), + Namespace: existingMetaObj.GetNamespace(), + } + + err := c.Get(ctx, key, existing) + + switch { + case k8serrors.IsNotFound(err): + return errors.Wrapf(c.Create(ctx, obj), "failed to create %T", obj) + case err != nil: + return errors.Wrapf(err, "failed to get %T object", obj) + } + + if err = mergo.Merge(obj, existing); err != nil { + return errors.Wrap(err, "failed to merge objects") + } + + return errors.Wrapf(c.Update(ctx, obj), "failed to update %T object", obj) +} diff --git a/pkg/credentials/secret.go b/pkg/credentials/secret.go index 5ee6367fc..5d0f2e8b3 100644 --- a/pkg/credentials/secret.go +++ b/pkg/credentials/secret.go @@ -21,13 +21,11 @@ import ( "github.com/pkg/errors" + "github.com/kubermatic/kubeone/pkg/clientutil" "github.com/kubermatic/kubeone/pkg/state" corev1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/apimachinery/pkg/runtime" - dynclient "sigs.k8s.io/controller-runtime/pkg/client" - "sigs.k8s.io/controller-runtime/pkg/controller/controllerutil" ) const ( @@ -37,12 +35,6 @@ const ( SecretNamespace = "kube-system" ) -func simpleCreateOrUpdate(ctx context.Context, client dynclient.Client, obj runtime.Object) error { - okFunc := func(runtime.Object) error { return nil } - _, err := controllerutil.CreateOrUpdate(ctx, client, obj, okFunc) - return err -} - // Ensure creates/updates the credentials secret func Ensure(s *state.State) error { if !s.Cluster.MachineController.Deploy && !s.Cluster.CloudProvider.External { @@ -57,9 +49,8 @@ func Ensure(s *state.State) error { return errors.Wrap(err, "unable to fetch cloud provider credentials") } - bgCtx := context.Background() secret := credentialsSecret(creds) - if err := simpleCreateOrUpdate(bgCtx, s.DynamicClient, secret); err != nil { + if err := clientutil.CreateOrUpdate(context.Background(), s.DynamicClient, secret); err != nil { return errors.Wrap(err, "failed to ensure credentials secret") } @@ -68,10 +59,6 @@ func Ensure(s *state.State) error { func credentialsSecret(credentials map[string]string) *corev1.Secret { return &corev1.Secret{ - TypeMeta: metav1.TypeMeta{ - APIVersion: "v1", - Kind: "Secret", - }, ObjectMeta: metav1.ObjectMeta{ Name: SecretName, Namespace: SecretNamespace, diff --git a/pkg/features/activate.go b/pkg/features/activate.go index 29269847a..d769849f3 100644 --- a/pkg/features/activate.go +++ b/pkg/features/activate.go @@ -27,6 +27,8 @@ import ( // Activate configured features. // Installing CRDs, creating policies and so on func Activate(s *state.State) error { + s.Logger.Info("Activating additional features…") + if err := installKubeSystemPSP(s.Cluster.Features.PodSecurityPolicy, s); err != nil { return errors.Wrap(err, "failed to install PodSecurityPolicy") } diff --git a/pkg/features/psp.go b/pkg/features/psp.go index 0c36c83c5..e033820c4 100644 --- a/pkg/features/psp.go +++ b/pkg/features/psp.go @@ -22,6 +22,7 @@ import ( "github.com/pkg/errors" kubeoneapi "github.com/kubermatic/kubeone/pkg/apis/kubeone" + "github.com/kubermatic/kubeone/pkg/clientutil" "github.com/kubermatic/kubeone/pkg/state" "github.com/kubermatic/kubeone/pkg/templates/kubeadm/kubeadmargs" @@ -30,7 +31,6 @@ import ( rbacv1 "k8s.io/api/rbac/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/runtime" - "sigs.k8s.io/controller-runtime/pkg/controller/controllerutil" ) const ( @@ -52,33 +52,26 @@ func installKubeSystemPSP(psp *kubeoneapi.PodSecurityPolicy, s *state.State) err return nil } - bgContext := context.Background() - okFunc := func(runtime.Object) error { return nil } - - _, err := controllerutil.CreateOrUpdate(bgContext, s.DynamicClient, privilegedPSP(), okFunc) - if err != nil { - return errors.Wrap(err, "failed to ensure PodSecurityPolicy") - } - - _, err = controllerutil.CreateOrUpdate(bgContext, s.DynamicClient, privilegedPSPClusterRole(), okFunc) - if err != nil { - return errors.Wrap(err, "failed to ensure PodSecurityPolicy cluster role") + ctx := context.Background() + k8sobjects := []runtime.Object{ + privilegedPSP(), + privilegedPSPClusterRole(), + privilegedPSPRoleBinding(), } - _, err = controllerutil.CreateOrUpdate(bgContext, s.DynamicClient, privilegedPSPRoleBinding(), okFunc) - if err != nil { - return errors.Wrap(err, "failed to ensure PodSecurityPolicy role binding") + for _, obj := range k8sobjects { + if err := clientutil.CreateOrUpdate(ctx, s.DynamicClient, obj); err != nil { + return errors.Wrap(err, "failed to ensure PodSecurityPolicy role binding") + } } return nil } func privilegedPSP() *policybeta1.PodSecurityPolicy { + t := true + return &policybeta1.PodSecurityPolicy{ - TypeMeta: metav1.TypeMeta{ - APIVersion: "policy/v1beta1", - Kind: "PodSecurityPolicy", - }, ObjectMeta: metav1.ObjectMeta{ Name: "privileged", }, @@ -87,7 +80,7 @@ func privilegedPSP() *policybeta1.PodSecurityPolicy { HostNetwork: true, HostIPC: true, HostPID: true, - AllowPrivilegeEscalation: boolPtr(true), + AllowPrivilegeEscalation: &t, AllowedCapabilities: []corev1.Capability{"*"}, Volumes: []policybeta1.FSType{policybeta1.All}, HostPorts: []policybeta1.HostPortRange{ @@ -111,10 +104,6 @@ func privilegedPSP() *policybeta1.PodSecurityPolicy { func privilegedPSPClusterRole() *rbacv1.ClusterRole { return &rbacv1.ClusterRole{ - TypeMeta: metav1.TypeMeta{ - APIVersion: "rbac.authorization.k8s.io/v1", - Kind: "ClusterRole", - }, ObjectMeta: metav1.ObjectMeta{ Name: "privileged-psp", }, @@ -131,10 +120,6 @@ func privilegedPSPClusterRole() *rbacv1.ClusterRole { func privilegedPSPRoleBinding() *rbacv1.RoleBinding { return &rbacv1.RoleBinding{ - TypeMeta: metav1.TypeMeta{ - APIVersion: "rbac.authorization.k8s.io/v1", - Kind: "RoleBinding", - }, ObjectMeta: metav1.ObjectMeta{ Name: "privileged-psp", Namespace: pspRoleNamespace, @@ -158,7 +143,3 @@ func privilegedPSPRoleBinding() *rbacv1.RoleBinding { }, } } - -func boolPtr(b bool) *bool { - return &b -} diff --git a/pkg/installer/installation/cni.go b/pkg/installer/installation/cni.go index 353ab9fd7..e7d93f886 100644 --- a/pkg/installer/installation/cni.go +++ b/pkg/installer/installation/cni.go @@ -45,3 +45,12 @@ func ensureCNICanal(s *state.State) error { s.Logger.Infoln("Applying canal CNI plugin…") return canal.Deploy(s) } + +func patchCNI(s *state.State) error { + if !s.PatchCNI { + return nil + } + + s.Logger.Info("Patching CNI…") + return ensureCNI(s) +} diff --git a/pkg/installer/installation/coredns.go b/pkg/installer/installation/coredns.go index 0ac251bee..fb7125273 100644 --- a/pkg/installer/installation/coredns.go +++ b/pkg/installer/installation/coredns.go @@ -40,14 +40,14 @@ func patchCoreDNS(s *state.State) error { return errors.New("kubernetes client not initialized") } - bgCtx := context.Background() + ctx := context.Background() dep := &appsv1.Deployment{} key := client.ObjectKey{ Name: "coredns", Namespace: metav1.NamespaceSystem, } - err := s.DynamicClient.Get(bgCtx, key, dep) + err := s.DynamicClient.Get(ctx, key, dep) if err != nil { return errors.Wrap(err, "failed to get coredns deployment") } @@ -60,5 +60,5 @@ func patchCoreDNS(s *state.State) error { }, ) - return errors.Wrap(s.DynamicClient.Update(bgCtx, dep), "failed to update coredns deployment") + return errors.Wrap(s.DynamicClient.Update(ctx, dep), "failed to update coredns deployment") } diff --git a/pkg/installer/installation/install.go b/pkg/installer/installation/install.go index daa735230..8ed570cea 100644 --- a/pkg/installer/installation/install.go +++ b/pkg/installer/installation/install.go @@ -45,10 +45,11 @@ func Install(s *state.State) error { {Fn: saveKubeconfig, ErrMsg: "unable to save kubeconfig to the local machine", Retries: 3}, {Fn: kubeconfig.BuildKubernetesClientset, ErrMsg: "unable to build kubernetes clientset", Retries: 3}, {Fn: features.Activate, ErrMsg: "unable to activate features"}, - {Fn: credentials.Ensure, ErrMsg: "unable to ensure credentials secret"}, - {Fn: externalccm.Ensure, ErrMsg: "failed to install external CCM"}, - {Fn: patchCoreDNS, ErrMsg: "failed to patch CoreDNS", Retries: 3}, {Fn: ensureCNI, ErrMsg: "failed to install cni plugin", Retries: 3}, + {Fn: patchCoreDNS, ErrMsg: "failed to patch CoreDNS", Retries: 3}, + {Fn: credentials.Ensure, ErrMsg: "unable to ensure credentials secret", Retries: 3}, + {Fn: externalccm.Ensure, ErrMsg: "failed to install external CCM", Retries: 3}, + {Fn: patchCNI, ErrMsg: "failed to patch CNI", Retries: 3}, {Fn: machinecontroller.Ensure, ErrMsg: "failed to install machine-controller", Retries: 3}, {Fn: machinecontroller.WaitReady, ErrMsg: "failed to wait for machine-controller", Retries: 3}, {Fn: createWorkerMachines, ErrMsg: "failed to create worker machines", Retries: 3}, diff --git a/pkg/installer/installation/kubeconfig.go b/pkg/installer/installation/kubeconfig.go index a8819a174..668daa223 100644 --- a/pkg/installer/installation/kubeconfig.go +++ b/pkg/installer/installation/kubeconfig.go @@ -47,6 +47,8 @@ sudo chown $(id -u):$(id -g) $HOME/.kube/config } func saveKubeconfig(s *state.State) error { + s.Logger.Info("Downloading kubeconfig…") + kc, err := kubeconfig.Download(s.Cluster) if err != nil { return err diff --git a/pkg/state/context.go b/pkg/state/context.go index b3fde8791..3224a060d 100644 --- a/pkg/state/context.go +++ b/pkg/state/context.go @@ -47,9 +47,10 @@ type State struct { RemoveBinaries bool ForceUpgrade bool UpgradeMachineDeployments bool + PatchCNI bool } -// Clone returns a shallow copy of the context. +// Clone returns a shallow copy of the State. func (s *State) Clone() *State { newState := *s return &newState diff --git a/pkg/templates/canal/canal.go b/pkg/templates/canal/canal.go index 1609abca5..30e9186a4 100644 --- a/pkg/templates/canal/canal.go +++ b/pkg/templates/canal/canal.go @@ -23,11 +23,11 @@ import ( "github.com/pkg/errors" + "github.com/kubermatic/kubeone/pkg/clientutil" "github.com/kubermatic/kubeone/pkg/kubeconfig" "github.com/kubermatic/kubeone/pkg/state" - rbacv1 "k8s.io/api/rbac/v1" - apiextensions "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1beta1" + "k8s.io/apimachinery/pkg/runtime" ) const ( @@ -39,40 +39,42 @@ const ( // automatically populated cniNetworkConfig = ` { - "name": "k8s-pod-network", - "cniVersion": "0.3.0", - "plugins": [ - { - "type": "calico", - "log_level": "info", - "datastore_type": "kubernetes", - "nodename": "__KUBERNETES_NODE_NAME__", - "ipam": { - "type": "host-local", - "subnet": "usePodCidr" - }, - "policy": { - "type": "k8s" - }, - "kubernetes": { - "kubeconfig": "__KUBECONFIG_FILEPATH__" - } - }, - { - "type": "portmap", - "snat": true, - "capabilities": {"portMappings": true} - } - ] + "name": "k8s-pod-network", + "cniVersion": "0.3.0", + "plugins": [ + { + "type": "calico", + "log_level": "info", + "datastore_type": "kubernetes", + "nodename": "__KUBERNETES_NODE_NAME__", + "ipam": { + "type": "host-local", + "subnet": "usePodCidr" + }, + "policy": { + "type": "k8s" + }, + "kubernetes": { + "kubeconfig": "__KUBECONFIG_FILEPATH__" + } + }, + { + "type": "portmap", + "snat": true, + "capabilities": { + "portMappings": true + } + } + ] } ` // Flannel network configuration (mounted into the flannel container) flannelNetworkConfig = ` { - "Network": "{{ .POD_SUBNET }}", - "Backend": { - "Type": "vxlan" - } + "Network": "{{ .POD_SUBNET }}", + "Backend": { + "Type": "vxlan" + } } ` ) @@ -98,41 +100,30 @@ func Deploy(s *state.State) error { return errors.Wrap(err, "failed to render canal config") } - bgCtx := context.Background() - // ConfigMap - cm := configMap() - cm.Data["net-conf.json"] = buf.String() - if err = simpleCreateOrUpdate(bgCtx, s.DynamicClient, cm); err != nil { - return errors.Wrap(err, "failed to ensure canal ConfigMap") + ctx := context.Background() + + k8sobjects := []runtime.Object{ + configMap(buf), + daemonSet(s.PatchCNI), + serviceAccount(), + calicoClusterRole(), + flannelClusterRole(), + calicoClusterRoleBinding(), + flannelClusterRoleBinding(), + canalClusterRoleBinding(), + felixConfigurationCRD(), + bgpConfigurationCRD(), + ipPoolsConfigurationCRD(), + hostEndpointsConfigurationCRD(), + clusterInformationsConfigurationCRD(), + globalNetworkPoliciesConfigurationCRD(), + globalNetworksetsConfigurationCRD(), + networkPoliciesConfigurationCRD(), } - // DaemonSet - ds := daemonSet() - if err = simpleCreateOrUpdate(bgCtx, s.DynamicClient, ds); err != nil { - return errors.Wrap(err, "failed to ensure canal DaemonSet") - } - - // ServiceAccount - sa := serviceAccount() - if err = simpleCreateOrUpdate(bgCtx, s.DynamicClient, sa); err != nil { - return errors.Wrap(err, "failed to ensure canal ServiceAccount") - } - - // CRDs - crdGenerators := []func() *apiextensions.CustomResourceDefinition{ - felixConfigurationCRD, - bgpConfigurationCRD, - ipPoolsConfigurationCRD, - hostEndpointsConfigurationCRD, - clusterInformationsConfigurationCRD, - globalNetworkPoliciesConfigurationCRD, - globalNetworksetsConfigurationCRD, - networkPoliciesConfigurationCRD, - } - - for _, crdGen := range crdGenerators { - if err = simpleCreateOrUpdate(bgCtx, s.DynamicClient, crdGen()); err != nil { - return errors.Wrap(err, "failed to ensure canal CustomResourceDefinition") + for _, obj := range k8sobjects { + if err = clientutil.CreateOrUpdate(ctx, s.DynamicClient, obj); err != nil { + return errors.WithStack(err) } } @@ -142,29 +133,5 @@ func Deploy(s *state.State) error { return errors.Wrap(err, "failed to re-init dynamic client") } - // ClusterRoles - crGenerators := []func() *rbacv1.ClusterRole{ - calicoClusterRole, - flannelClusterRole, - } - - for _, crGen := range crGenerators { - if err := simpleCreateOrUpdate(bgCtx, s.DynamicClient, crGen()); err != nil { - return errors.Wrap(err, "failed to ensure canal ClusterRole") - } - } - - // ClusterRoleBindings - crbGenerators := []func() *rbacv1.ClusterRoleBinding{ - calicoClusterRoleBinding, - flannelClusterRoleBinding, - canalClusterRoleBinding, - } - for _, crbGen := range crbGenerators { - if err := simpleCreateOrUpdate(bgCtx, s.DynamicClient, crbGen()); err != nil { - return errors.Wrap(err, "failed to ensure canal ClusterRoleBinding") - } - } - return nil } diff --git a/pkg/templates/canal/daemonset.go b/pkg/templates/canal/daemonset.go index cfeb81354..b0bc5f5e4 100644 --- a/pkg/templates/canal/daemonset.go +++ b/pkg/templates/canal/daemonset.go @@ -26,11 +26,53 @@ import ( // daemonSet installs the calico/node container, as well as the Calico CNI plugins and network config on each // master and worker node in a Kubernetes cluster -func daemonSet() *appsv1.DaemonSet { +func daemonSet(ifacePatch bool) *appsv1.DaemonSet { maxUnavailable := intstr.FromInt(1) terminationGracePeriodSeconds := int64(0) privileged := true fileOrCreate := corev1.HostPathFileOrCreate + + flannelEnv := []corev1.EnvVar{ + { + Name: "POD_NAME", + ValueFrom: &corev1.EnvVarSource{ + FieldRef: &corev1.ObjectFieldSelector{ + FieldPath: "metadata.name", + }, + }, + }, + { + Name: "POD_NAMESPACE", + ValueFrom: &corev1.EnvVarSource{ + FieldRef: &corev1.ObjectFieldSelector{ + FieldPath: "metadata.namespace", + }, + }, + }, + { + Name: "FLANNELD_IP_MASQ", + ValueFrom: &corev1.EnvVarSource{ + ConfigMapKeyRef: &corev1.ConfigMapKeySelector{ + Key: "masquerade", + LocalObjectReference: corev1.LocalObjectReference{ + Name: "canal-config", + }, + }, + }, + }, + } + + if ifacePatch { + flannelEnv = append(flannelEnv, corev1.EnvVar{ + Name: "FLANNELD_IFACE", + ValueFrom: &corev1.EnvVarSource{ + FieldRef: &corev1.ObjectFieldSelector{ + FieldPath: "status.hostIP", + }, + }, + }) + } + return &appsv1.DaemonSet{ ObjectMeta: metav1.ObjectMeta{ Name: "canal", @@ -270,46 +312,7 @@ func daemonSet() *appsv1.DaemonSet { SecurityContext: &corev1.SecurityContext{ Privileged: &privileged, }, - Env: []corev1.EnvVar{ - { - Name: "POD_NAME", - ValueFrom: &corev1.EnvVarSource{ - FieldRef: &corev1.ObjectFieldSelector{ - FieldPath: "metadata.name", - }, - }, - }, - { - Name: "POD_NAMESPACE", - ValueFrom: &corev1.EnvVarSource{ - FieldRef: &corev1.ObjectFieldSelector{ - FieldPath: "metadata.namespace", - }, - }, - }, - { - Name: "FLANNELD_IFACE", - ValueFrom: &corev1.EnvVarSource{ - ConfigMapKeyRef: &corev1.ConfigMapKeySelector{ - Key: "canal_iface", - LocalObjectReference: corev1.LocalObjectReference{ - Name: "canal-config", - }, - }, - }, - }, - { - Name: "FLANNELD_IP_MASQ", - ValueFrom: &corev1.EnvVarSource{ - ConfigMapKeyRef: &corev1.ConfigMapKeySelector{ - Key: "masquerade", - LocalObjectReference: corev1.LocalObjectReference{ - Name: "canal-config", - }, - }, - }, - }, - }, + Env: flannelEnv, VolumeMounts: []corev1.VolumeMount{ { MountPath: "/run/xtables.lock", diff --git a/pkg/templates/canal/helper.go b/pkg/templates/canal/helper.go deleted file mode 100644 index 8268a3e0e..000000000 --- a/pkg/templates/canal/helper.go +++ /dev/null @@ -1,31 +0,0 @@ -/* -Copyright 2019 The KubeOne 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 canal - -import ( - "context" - - "k8s.io/apimachinery/pkg/runtime" - dynclient "sigs.k8s.io/controller-runtime/pkg/client" - "sigs.k8s.io/controller-runtime/pkg/controller/controllerutil" -) - -func simpleCreateOrUpdate(ctx context.Context, client dynclient.Client, obj runtime.Object) error { - okFunc := func(runtime.Object) error { return nil } - _, err := controllerutil.CreateOrUpdate(ctx, client, obj, okFunc) - return err -} diff --git a/pkg/templates/canal/prerequisites.go b/pkg/templates/canal/prerequisites.go index 9ff7e90ad..1f08b30b1 100644 --- a/pkg/templates/canal/prerequisites.go +++ b/pkg/templates/canal/prerequisites.go @@ -17,13 +17,15 @@ limitations under the License. package canal import ( + "bytes" + corev1 "k8s.io/api/core/v1" rbacv1 "k8s.io/api/rbac/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" ) // configMap creates a ConfigMap used to configure a self-hosted Canal installation -func configMap() *corev1.ConfigMap { +func configMap(netConf bytes.Buffer) *corev1.ConfigMap { return &corev1.ConfigMap{ ObjectMeta: metav1.ObjectMeta{ Name: "canal-config", @@ -44,7 +46,7 @@ func configMap() *corev1.ConfigMap { "cni_network_config": cniNetworkConfig, // Flannel network configuration. Mounted into the flannel container - "net-conf.json": "", + "net-conf.json": netConf.String(), }, } } diff --git a/pkg/templates/externalccm/ccm.go b/pkg/templates/externalccm/ccm.go index 89b7ecd12..d93f74a7d 100644 --- a/pkg/templates/externalccm/ccm.go +++ b/pkg/templates/externalccm/ccm.go @@ -19,6 +19,7 @@ package externalccm import ( "context" "strings" + "time" "github.com/Masterminds/semver" "github.com/pkg/errors" @@ -27,9 +28,14 @@ import ( "github.com/kubermatic/kubeone/pkg/state" appsv1 "k8s.io/api/apps/v1" + corev1 "k8s.io/api/core/v1" "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/util/wait" dynclient "sigs.k8s.io/controller-runtime/pkg/client" - "sigs.k8s.io/controller-runtime/pkg/controller/controllerutil" +) + +const ( + uninitializedTaint = "node.cloudprovider.kubernetes.io/uninitialized" ) // Ensure external CCM deployen if Provider.External @@ -38,25 +44,54 @@ func Ensure(s *state.State) error { return nil } - s.Logger.Info("Ensure external CCM is up to date") + s.Logger.Info("Ensure external CCM is up to date…") + var err error + + s.PatchCNI = true switch s.Cluster.CloudProvider.Name { case kubeoneapi.CloudProviderNameHetzner: - return ensureHetzner(s) + err = ensureHetzner(s) case kubeoneapi.CloudProviderNameDigitalOcean: - return ensureDigitalOcean(s) + err = ensureDigitalOcean(s) case kubeoneapi.CloudProviderNamePacket: - return ensurePacket(s) + err = ensurePacket(s) default: s.Logger.Infof("External CCM for %q not yet supported, skipping", s.Cluster.CloudProvider.Name) return nil } + + if err != nil { + return errors.Wrap(err, "failed to ensure CCM is installed") + } + + err = waitForInitializedNodes(s) + return errors.Wrap(err, "failed waiting for nodes to be initialized by CCM") } -func simpleCreateOrUpdate(ctx context.Context, client dynclient.Client, obj runtime.Object) error { - okFunc := func(runtime.Object) error { return nil } - _, err := controllerutil.CreateOrUpdate(ctx, client, obj, okFunc) - return err +func waitForInitializedNodes(s *state.State) error { + ctx := context.Background() + + s.Logger.Info("Waiting for nodes to initialize by CCM…") + + return wait.Poll(5*time.Second, 60*time.Second, func() (bool, error) { + nodes := corev1.NodeList{} + nodeListOpts := dynclient.ListOptions{} + + if err := s.DynamicClient.List(ctx, &nodeListOpts, &nodes); err != nil { + return false, err + } + + for _, node := range nodes.Items { + for _, taint := range node.Spec.Taints { + if taint.Key == uninitializedTaint && taint.Value == "true" { + return false, nil + } + } + } + + return true, nil + }) } func mutateDeploymentWithVersionCheck(want *semver.Constraints) func(obj runtime.Object) error { diff --git a/pkg/templates/externalccm/digitalocean.go b/pkg/templates/externalccm/digitalocean.go index 4fde77b1e..1bafbfc48 100644 --- a/pkg/templates/externalccm/digitalocean.go +++ b/pkg/templates/externalccm/digitalocean.go @@ -22,6 +22,7 @@ import ( "github.com/Masterminds/semver" "github.com/pkg/errors" + "github.com/kubermatic/kubeone/pkg/clientutil" "github.com/kubermatic/kubeone/pkg/credentials" "github.com/kubermatic/kubeone/pkg/state" @@ -30,6 +31,7 @@ import ( rbacv1 "k8s.io/api/rbac/v1" "k8s.io/apimachinery/pkg/api/resource" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime" "sigs.k8s.io/controller-runtime/pkg/controller/controllerutil" ) @@ -44,21 +46,17 @@ func ensureDigitalOcean(s *state.State) error { return errors.New("kubernetes client not initialized") } - bgctx := context.Background() - - sa := doServiceAccount() - if err := simpleCreateOrUpdate(bgctx, s.DynamicClient, sa); err != nil { - return errors.Wrap(err, "failed to ensure digitalocean CCM ServiceAccount") - } - - cr := doClusterRole() - if err := simpleCreateOrUpdate(bgctx, s.DynamicClient, cr); err != nil { - return errors.Wrap(err, "failed to ensure digitalocean CCM ClusterRole") + ctx := context.Background() + k8sobject := []runtime.Object{ + doServiceAccount(), + doClusterRole(), + doClusterRoleBinding(), } - crb := doClusterRoleBinding() - if err := simpleCreateOrUpdate(bgctx, s.DynamicClient, crb); err != nil { - return errors.Wrap(err, "failed to ensure digitalocean CCM ClusterRoleBinding") + for _, obj := range k8sobject { + if err := clientutil.CreateOrUpdate(ctx, s.DynamicClient, obj); err != nil { + return errors.Wrapf(err, "failed to ensure digitalocean CCM %T", obj) + } } dep := doDeployment() @@ -67,7 +65,7 @@ func ensureDigitalOcean(s *state.State) error { return errors.Wrap(err, "failed to parse digitalocean CCM version constraint") } - _, err = controllerutil.CreateOrUpdate(bgctx, + _, err = controllerutil.CreateOrUpdate(ctx, s.DynamicClient, dep, mutateDeploymentWithVersionCheck(want)) @@ -79,10 +77,6 @@ func ensureDigitalOcean(s *state.State) error { func doServiceAccount() *corev1.ServiceAccount { return &corev1.ServiceAccount{ - TypeMeta: metav1.TypeMeta{ - APIVersion: "v1", - Kind: "ServiceAccount", - }, ObjectMeta: metav1.ObjectMeta{ Name: digitaloceanSAName, Namespace: metav1.NamespaceSystem, @@ -92,10 +86,6 @@ func doServiceAccount() *corev1.ServiceAccount { func doClusterRole() *rbacv1.ClusterRole { return &rbacv1.ClusterRole{ - TypeMeta: metav1.TypeMeta{ - APIVersion: "rbac.authorization.k8s.io/v1", - Kind: "ClusterRole", - }, ObjectMeta: metav1.ObjectMeta{ Name: "system:cloud-controller-manager", Annotations: map[string]string{ @@ -149,10 +139,6 @@ func doClusterRole() *rbacv1.ClusterRole { func doClusterRoleBinding() *rbacv1.ClusterRoleBinding { return &rbacv1.ClusterRoleBinding{ - TypeMeta: metav1.TypeMeta{ - APIVersion: "rbac.authorization.k8s.io/v1", - Kind: "ClusterRoleBinding", - }, ObjectMeta: metav1.ObjectMeta{ Name: "system:cloud-controller-manager", }, @@ -178,10 +164,6 @@ func doDeployment() *appsv1.Deployment { ) return &appsv1.Deployment{ - TypeMeta: metav1.TypeMeta{ - APIVersion: "apps/v1", - Kind: "Deployment", - }, ObjectMeta: metav1.ObjectMeta{ Name: digitaloceanDeploymentName, Namespace: metav1.NamespaceSystem, diff --git a/pkg/templates/externalccm/hetzner.go b/pkg/templates/externalccm/hetzner.go index ffb183625..e1e8366cd 100644 --- a/pkg/templates/externalccm/hetzner.go +++ b/pkg/templates/externalccm/hetzner.go @@ -22,6 +22,7 @@ import ( "github.com/Masterminds/semver" "github.com/pkg/errors" + "github.com/kubermatic/kubeone/pkg/clientutil" "github.com/kubermatic/kubeone/pkg/credentials" "github.com/kubermatic/kubeone/pkg/state" @@ -30,6 +31,7 @@ import ( rbacv1 "k8s.io/api/rbac/v1" "k8s.io/apimachinery/pkg/api/resource" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime" "sigs.k8s.io/controller-runtime/pkg/controller/controllerutil" ) @@ -44,16 +46,16 @@ func ensureHetzner(s *state.State) error { return errors.New("kubernetes client not initialized") } - bgctx := context.Background() - - sa := hetznerServiceAccount() - if err := simpleCreateOrUpdate(bgctx, s.DynamicClient, sa); err != nil { - return errors.Wrap(err, "failed to ensure hetzner CCM ServiceAccount") + ctx := context.Background() + k8sobject := []runtime.Object{ + hetznerServiceAccount(), + hetznerClusterRoleBinding(), } - crb := hetznerClusterRoleBinding() - if err := simpleCreateOrUpdate(bgctx, s.DynamicClient, crb); err != nil { - return errors.Wrap(err, "failed to ensure hetzner CCM ClusterRoleBinding") + for _, obj := range k8sobject { + if err := clientutil.CreateOrUpdate(ctx, s.DynamicClient, obj); err != nil { + return errors.Wrapf(err, "failed to ensure hetzner CCM %T", obj) + } } dep := hetznerDeployment() @@ -62,7 +64,7 @@ func ensureHetzner(s *state.State) error { return errors.Wrap(err, "failed to parse hetzner CCM version constraint") } - _, err = controllerutil.CreateOrUpdate(bgctx, + _, err = controllerutil.CreateOrUpdate(ctx, s.DynamicClient, dep, mutateDeploymentWithVersionCheck(want)) @@ -75,10 +77,6 @@ func ensureHetzner(s *state.State) error { func hetznerServiceAccount() *corev1.ServiceAccount { return &corev1.ServiceAccount{ - TypeMeta: metav1.TypeMeta{ - APIVersion: "v1", - Kind: "ServiceAccount", - }, ObjectMeta: metav1.ObjectMeta{ Name: hetznerSAName, Namespace: metav1.NamespaceSystem, @@ -88,10 +86,6 @@ func hetznerServiceAccount() *corev1.ServiceAccount { func hetznerClusterRoleBinding() *rbacv1.ClusterRoleBinding { return &rbacv1.ClusterRoleBinding{ - TypeMeta: metav1.TypeMeta{ - APIVersion: "rbac.authorization.k8s.io/v1", - Kind: "ClusterRoleBinding", - }, ObjectMeta: metav1.ObjectMeta{ Name: "system:cloud-controller-manager", }, @@ -117,10 +111,6 @@ func hetznerDeployment() *appsv1.Deployment { ) return &appsv1.Deployment{ - TypeMeta: metav1.TypeMeta{ - APIVersion: "apps/v1", - Kind: "Deployment", - }, ObjectMeta: metav1.ObjectMeta{ Name: hetznerDeploymentName, Namespace: metav1.NamespaceSystem, diff --git a/pkg/templates/externalccm/packet.go b/pkg/templates/externalccm/packet.go index 5d381ec7b..9d1ac7c78 100644 --- a/pkg/templates/externalccm/packet.go +++ b/pkg/templates/externalccm/packet.go @@ -22,6 +22,7 @@ import ( "github.com/Masterminds/semver" "github.com/pkg/errors" + "github.com/kubermatic/kubeone/pkg/clientutil" "github.com/kubermatic/kubeone/pkg/credentials" "github.com/kubermatic/kubeone/pkg/state" @@ -30,6 +31,7 @@ import ( rbacv1 "k8s.io/api/rbac/v1" "k8s.io/apimachinery/pkg/api/resource" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime" "sigs.k8s.io/controller-runtime/pkg/controller/controllerutil" ) @@ -44,21 +46,17 @@ func ensurePacket(s *state.State) error { return errors.New("kubernetes client not initialized") } - bgctx := context.Background() - - sa := packetServiceAccount() - if err := simpleCreateOrUpdate(bgctx, s.DynamicClient, sa); err != nil { - return errors.Wrap(err, "failed to ensure packet CCM ServiceAccount") - } - - cr := packetClusterRole() - if err := simpleCreateOrUpdate(bgctx, s.DynamicClient, cr); err != nil { - return errors.Wrap(err, "failed to ensure packet CCM ClusterRole") + ctx := context.Background() + k8sobjects := []runtime.Object{ + packetServiceAccount(), + packetClusterRole(), + packetClusterRoleBinding(), } - crb := packetClusterRoleBinding() - if err := simpleCreateOrUpdate(bgctx, s.DynamicClient, crb); err != nil { - return errors.Wrap(err, "failed to ensure packet CCM ClusterRoleBinding") + for _, obj := range k8sobjects { + if err := clientutil.CreateOrUpdate(ctx, s.DynamicClient, obj); err != nil { + return errors.Wrapf(err, "failed to ensure packet CCM %T", obj) + } } dep := packetDeployment() @@ -67,7 +65,7 @@ func ensurePacket(s *state.State) error { return errors.Wrap(err, "failed to parse packet CCM version constraint") } - _, err = controllerutil.CreateOrUpdate(bgctx, + _, err = controllerutil.CreateOrUpdate(ctx, s.DynamicClient, dep, mutateDeploymentWithVersionCheck(want)) @@ -80,10 +78,6 @@ func ensurePacket(s *state.State) error { func packetServiceAccount() *corev1.ServiceAccount { return &corev1.ServiceAccount{ - TypeMeta: metav1.TypeMeta{ - APIVersion: "v1", - Kind: "ServiceAccount", - }, ObjectMeta: metav1.ObjectMeta{ Name: packetSAName, Namespace: metav1.NamespaceSystem, @@ -93,10 +87,6 @@ func packetServiceAccount() *corev1.ServiceAccount { func packetClusterRole() *rbacv1.ClusterRole { return &rbacv1.ClusterRole{ - TypeMeta: metav1.TypeMeta{ - APIVersion: "rbac.authorization.k8s.io/v1", - Kind: "ClusterRole", - }, ObjectMeta: metav1.ObjectMeta{ Name: "system:cloud-controller-manager", Annotations: map[string]string{ @@ -150,10 +140,6 @@ func packetClusterRole() *rbacv1.ClusterRole { func packetClusterRoleBinding() *rbacv1.ClusterRoleBinding { return &rbacv1.ClusterRoleBinding{ - TypeMeta: metav1.TypeMeta{ - APIVersion: "rbac.authorization.k8s.io/v1", - Kind: "ClusterRoleBinding", - }, ObjectMeta: metav1.ObjectMeta{ Name: "system:cloud-controller-manager", }, @@ -178,10 +164,6 @@ func packetDeployment() *appsv1.Deployment { ) return &appsv1.Deployment{ - TypeMeta: metav1.TypeMeta{ - APIVersion: "apps/v1", - Kind: "Deployment", - }, ObjectMeta: metav1.ObjectMeta{ Name: packetDeploymentName, Namespace: metav1.NamespaceSystem, diff --git a/pkg/templates/machinecontroller/deployment.go b/pkg/templates/machinecontroller/deployment.go index 338b9b6b3..af8896e4b 100644 --- a/pkg/templates/machinecontroller/deployment.go +++ b/pkg/templates/machinecontroller/deployment.go @@ -25,6 +25,7 @@ import ( "github.com/pkg/errors" kubeoneapi "github.com/kubermatic/kubeone/pkg/apis/kubeone" + "github.com/kubermatic/kubeone/pkg/clientutil" "github.com/kubermatic/kubeone/pkg/credentials" "github.com/kubermatic/kubeone/pkg/kubeconfig" "github.com/kubermatic/kubeone/pkg/state" @@ -34,6 +35,7 @@ import ( rbacv1 "k8s.io/api/rbac/v1" apiextensions "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1beta1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/util/intstr" "k8s.io/apimachinery/pkg/util/wait" "k8s.io/kubernetes/pkg/registry/core/service/ipallocator" @@ -54,80 +56,37 @@ func Deploy(s *state.State) error { return errors.New("kubernetes client not initialized") } - bgCtx := context.Background() + ctx := context.Background() - // ServiceAccounts - if err := simpleCreateOrUpdate(bgCtx, s.DynamicClient, machineControllerServiceAccount()); err != nil { - return errors.Wrap(err, "failed to ensure machine-controller service account") - } - - // ClusterRoles - if err := simpleCreateOrUpdate(bgCtx, s.DynamicClient, machineControllerClusterRole()); err != nil { - return errors.Wrap(err, "failed to ensure machine-controller cluster role") - } - - // ClusterRoleBindings - crbGenerators := []func() *rbacv1.ClusterRoleBinding{ - nodeSignerClusterRoleBinding, - machineControllerClusterRoleBinding, - nodeBootstrapperClusterRoleBinding, - } - - for _, crbGen := range crbGenerators { - if err := simpleCreateOrUpdate(bgCtx, s.DynamicClient, crbGen()); err != nil { - return errors.Wrap(err, "failed to ensure machine-controller cluster-role binding") - } - } - - // Roles - roleGenerators := []func() *rbacv1.Role{ - machineControllerKubeSystemRole, - machineControllerKubePublicRole, - machineControllerEndpointReaderRole, - machineControllerClusterInfoReaderRole, - } - - for _, roleGen := range roleGenerators { - if err := simpleCreateOrUpdate(bgCtx, s.DynamicClient, roleGen()); err != nil { - return errors.Wrap(err, "failed to ensure machine-controller role") - } - } - - // RoleBindings - roleBindingsGenerators := []func() *rbacv1.RoleBinding{ - machineControllerKubeSystemRoleBinding, - machineControllerKubePublicRoleBinding, - machineControllerDefaultRoleBinding, - machineControllerClusterInfoRoleBinding, - } - - for _, roleBindingGen := range roleBindingsGenerators { - if err := simpleCreateOrUpdate(bgCtx, s.DynamicClient, roleBindingGen()); err != nil { - return errors.Wrap(err, "failed to ensure machine-controller role binding") - } - } - - // Deployments deployment, err := machineControllerDeployment(s.Cluster) if err != nil { return errors.Wrap(err, "failed to generate machine-controller deployment") } - if err = simpleCreateOrUpdate(bgCtx, s.DynamicClient, deployment); err != nil { - return errors.Wrap(err, "failed to ensure machine-controller deployment") - } - - // CRDs - crdGenerators := []func() *apiextensions.CustomResourceDefinition{ - machineControllerMachineCRD, - machineControllerClusterCRD, - machineControllerMachineSetCRD, - machineControllerMachineDeploymentCRD, - } - - for _, crdGen := range crdGenerators { - if err = simpleCreateOrUpdate(bgCtx, s.DynamicClient, crdGen()); err != nil { - return errors.Wrap(err, "failed to ensure machine-controller CRDs") + k8sobject := []runtime.Object{ + machineControllerServiceAccount(), + machineControllerClusterRole(), + nodeSignerClusterRoleBinding(), + machineControllerClusterRoleBinding(), + nodeBootstrapperClusterRoleBinding(), + machineControllerKubeSystemRole(), + machineControllerKubePublicRole(), + machineControllerEndpointReaderRole(), + machineControllerClusterInfoReaderRole(), + machineControllerKubeSystemRoleBinding(), + machineControllerKubePublicRoleBinding(), + machineControllerDefaultRoleBinding(), + machineControllerClusterInfoRoleBinding(), + machineControllerMachineCRD(), + machineControllerClusterCRD(), + machineControllerMachineSetCRD(), + machineControllerMachineDeploymentCRD(), + deployment, + } + + for _, obj := range k8sobject { + if err = clientutil.CreateOrUpdate(ctx, s.DynamicClient, obj); err != nil { + return errors.Wrapf(err, "failed to ensure machine-controller %T", obj) } } diff --git a/pkg/templates/machinecontroller/helper.go b/pkg/templates/machinecontroller/helper.go index afcb77cbd..f7de7b1be 100644 --- a/pkg/templates/machinecontroller/helper.go +++ b/pkg/templates/machinecontroller/helper.go @@ -26,20 +26,12 @@ import ( corev1 "k8s.io/api/core/v1" errorsutil "k8s.io/apimachinery/pkg/api/errors" - "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/util/wait" "k8s.io/client-go/util/retry" clusterv1alpha1 "sigs.k8s.io/cluster-api/pkg/apis/cluster/v1alpha1" dynclient "sigs.k8s.io/controller-runtime/pkg/client" - "sigs.k8s.io/controller-runtime/pkg/controller/controllerutil" ) -func simpleCreateOrUpdate(ctx context.Context, client dynclient.Client, obj runtime.Object) error { - okFunc := func(runtime.Object) error { return nil } - _, err := controllerutil.CreateOrUpdate(ctx, client, obj, okFunc) - return err -} - // Ensure install/update machine-controller func Ensure(s *state.State) error { if !s.Cluster.MachineController.Deploy { diff --git a/pkg/templates/machinecontroller/machines.go b/pkg/templates/machinecontroller/machines.go index a7dc9c748..5fafd9d77 100644 --- a/pkg/templates/machinecontroller/machines.go +++ b/pkg/templates/machinecontroller/machines.go @@ -24,6 +24,7 @@ import ( "github.com/pkg/errors" kubeoneapi "github.com/kubermatic/kubeone/pkg/apis/kubeone" + "github.com/kubermatic/kubeone/pkg/clientutil" "github.com/kubermatic/kubeone/pkg/state" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" @@ -48,7 +49,7 @@ func DeployMachineDeployments(s *state.State) error { return errors.New("kubernetes dynamic client in not initialized") } - bgCtx := context.Background() + ctx := context.Background() // Apply MachineDeployments for _, workerset := range s.Cluster.Workers { @@ -57,7 +58,7 @@ func DeployMachineDeployments(s *state.State) error { return errors.Wrap(err, "failed to generate MachineDeployment") } - err = simpleCreateOrUpdate(bgCtx, s.DynamicClient, machinedeployment) + err = clientutil.CreateOrUpdate(ctx, s.DynamicClient, machinedeployment) if err != nil { return errors.Wrap(err, "failed to ensure MachineDeployment") } diff --git a/pkg/templates/machinecontroller/webhook.go b/pkg/templates/machinecontroller/webhook.go index 18f65a752..517a5036c 100644 --- a/pkg/templates/machinecontroller/webhook.go +++ b/pkg/templates/machinecontroller/webhook.go @@ -27,12 +27,14 @@ import ( kubeoneapi "github.com/kubermatic/kubeone/pkg/apis/kubeone" "github.com/kubermatic/kubeone/pkg/certificate" + "github.com/kubermatic/kubeone/pkg/clientutil" "github.com/kubermatic/kubeone/pkg/state" admissionregistrationv1beta1 "k8s.io/api/admissionregistration/v1beta1" appsv1 "k8s.io/api/apps/v1" corev1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/util/intstr" "k8s.io/apimachinery/pkg/util/wait" certutil "k8s.io/client-go/util/cert" @@ -60,34 +62,25 @@ func DeployWebhookConfiguration(s *state.State) error { return errors.Wrap(err, "failed to load CA keypair") } - bgCtx := context.Background() - - // Deploy Webhook - err = simpleCreateOrUpdate(bgCtx, s.DynamicClient, webhookDeployment(s.Cluster)) - if err != nil { - return errors.Wrap(err, "failed to ensure machine-controller webhook deployment") - } - - // Deploy Webhook service - err = simpleCreateOrUpdate(bgCtx, s.DynamicClient, service()) - if err != nil { - return errors.Wrap(err, "failed to ensure machine-controller webhook service") - } - - // Deploy serving certificate secret + // Generate serving certificate secret servingCert, err := tlsServingCertificate(caPrivateKey, caCert) if err != nil { return errors.Wrap(err, "failed to generate machine-controller webhook TLS secret") } - err = simpleCreateOrUpdate(bgCtx, s.DynamicClient, servingCert) - if err != nil { - return errors.Wrap(err, "failed to ensure machine-controller webhook secret") + ctx := context.Background() + + k8sobjects := []runtime.Object{ + webhookDeployment(s.Cluster), + service(), + servingCert, + mutatingwebhookConfiguration(caCert), } - err = simpleCreateOrUpdate(bgCtx, s.DynamicClient, mutatingwebhookConfiguration(caCert)) - if err != nil { - return errors.Wrap(err, "failed to ensure machine-controller mutating webhook") + for _, obj := range k8sobjects { + if err := clientutil.CreateOrUpdate(ctx, s.DynamicClient, obj); err != nil { + return errors.Wrapf(err, "failed to ensure machine-controller webhook %T", obj) + } } return nil @@ -130,156 +123,154 @@ func WaitForWebhook(client dynclient.Client) error { // webhookDeployment returns the deployment for the machine-controllers MutatignAdmissionWebhook func webhookDeployment(cluster *kubeoneapi.KubeOneCluster) *appsv1.Deployment { - dep := &appsv1.Deployment{ - TypeMeta: metav1.TypeMeta{ - APIVersion: "apps/v1", - Kind: "Deployment", - }, - } - - dep.Name = "machine-controller-webhook" - dep.Namespace = WebhookNamespace - dep.Labels = map[string]string{ - WebhookAppLabelKey: WebhookAppLabelValue, - } - dep.Spec.Replicas = int32Ptr(1) - dep.Spec.Selector = &metav1.LabelSelector{ - MatchLabels: map[string]string{ - WebhookAppLabelKey: WebhookAppLabelValue, - }, - } - dep.Spec.Strategy.Type = appsv1.RollingUpdateStatefulSetStrategyType - dep.Spec.Strategy.RollingUpdate = &appsv1.RollingUpdateDeployment{ - MaxSurge: &intstr.IntOrString{ - Type: intstr.Int, - IntVal: 1, - }, - MaxUnavailable: &intstr.IntOrString{ - Type: intstr.Int, - IntVal: 0, - }, - } - - // TODO: Why whould we need this? - // dep.Spec.Template.Spec.ImagePullSecrets = []corev1.LocalObjectReference{{Name: resources.ImagePullSecretName}} - - volumes := []corev1.Volume{getServingCertVolume()} - dep.Spec.Template.Spec.Volumes = volumes - dep.Spec.Template.ObjectMeta = metav1.ObjectMeta{ - Labels: map[string]string{ - WebhookAppLabelKey: WebhookAppLabelValue, - }, - } - - dep.Spec.Template.Spec.Tolerations = []corev1.Toleration{ - { - Key: "node-role.kubernetes.io/master", - Operator: corev1.TolerationOpExists, - Effect: corev1.TaintEffectNoSchedule, - }, - { - Key: "node.cloudprovider.kubernetes.io/uninitialized", - Value: "true", - Effect: corev1.TaintEffectNoSchedule, - }, - { - Key: "CriticalAddonsOnly", - Operator: corev1.TolerationOpExists, + var replicas int32 = 1 + + return &appsv1.Deployment{ + ObjectMeta: metav1.ObjectMeta{ + Name: "machine-controller-webhook", + Namespace: WebhookNamespace, + Labels: map[string]string{ + WebhookAppLabelKey: WebhookAppLabelValue, + }, }, - } - - dep.Spec.Template.Spec.Containers = []corev1.Container{ - { - Name: "machine-controller-webhook", - Image: "kubermatic/machine-controller:" + WebhookTag, - ImagePullPolicy: corev1.PullIfNotPresent, - Command: []string{"/usr/local/bin/webhook"}, - Args: []string{ - // "-kubeconfig", "/etc/kubernetes/kubeconfig/kubeconfig", - "-logtostderr", - "-v", "4", - "-listen-address", "0.0.0.0:9876", + Spec: appsv1.DeploymentSpec{ + Replicas: &replicas, + Selector: &metav1.LabelSelector{ + MatchLabels: map[string]string{ + WebhookAppLabelKey: WebhookAppLabelValue, + }, }, - Env: getEnvVarCredentials(cluster), - TerminationMessagePath: corev1.TerminationMessagePathDefault, - TerminationMessagePolicy: corev1.TerminationMessageReadFile, - ReadinessProbe: &corev1.Probe{ - Handler: corev1.Handler{ - HTTPGet: &corev1.HTTPGetAction{ - Path: "/healthz", - Port: intstr.FromInt(9876), - Scheme: corev1.URISchemeHTTPS, + Strategy: appsv1.DeploymentStrategy{ + Type: appsv1.RollingUpdateStatefulSetStrategyType, + RollingUpdate: &appsv1.RollingUpdateDeployment{ + MaxSurge: &intstr.IntOrString{ + Type: intstr.Int, + IntVal: 1, + }, + MaxUnavailable: &intstr.IntOrString{ + Type: intstr.Int, + IntVal: 0, }, }, - FailureThreshold: 3, - PeriodSeconds: 10, - SuccessThreshold: 1, - TimeoutSeconds: 15, }, - LivenessProbe: &corev1.Probe{ - FailureThreshold: 8, - Handler: corev1.Handler{ - HTTPGet: &corev1.HTTPGetAction{ - Path: "/healthz", - Port: intstr.FromInt(9876), - Scheme: corev1.URISchemeHTTPS, + Template: corev1.PodTemplateSpec{ + ObjectMeta: metav1.ObjectMeta{ + Labels: map[string]string{ + WebhookAppLabelKey: WebhookAppLabelValue, }, }, - InitialDelaySeconds: 15, - PeriodSeconds: 10, - SuccessThreshold: 1, - TimeoutSeconds: 15, - }, - VolumeMounts: []corev1.VolumeMount{ - { - Name: "machinecontroller-webhook-serving-cert", - MountPath: "/tmp/cert", - ReadOnly: true, + Spec: corev1.PodSpec{ + Volumes: []corev1.Volume{ + getServingCertVolume(), + }, + Tolerations: []corev1.Toleration{ + { + Key: "node-role.kubernetes.io/master", + Operator: corev1.TolerationOpExists, + Effect: corev1.TaintEffectNoSchedule, + }, + { + Key: "node.cloudprovider.kubernetes.io/uninitialized", + Value: "true", + Effect: corev1.TaintEffectNoSchedule, + }, + { + Key: "CriticalAddonsOnly", + Operator: corev1.TolerationOpExists, + }, + }, + Containers: []corev1.Container{ + { + Name: "machine-controller-webhook", + Image: "kubermatic/machine-controller:" + WebhookTag, + ImagePullPolicy: corev1.PullIfNotPresent, + Command: []string{"/usr/local/bin/webhook"}, + Args: []string{ + "-logtostderr", + "-v", "4", + "-listen-address", "0.0.0.0:9876", + }, + Env: getEnvVarCredentials(cluster), + TerminationMessagePath: corev1.TerminationMessagePathDefault, + TerminationMessagePolicy: corev1.TerminationMessageReadFile, + ReadinessProbe: &corev1.Probe{ + Handler: corev1.Handler{ + HTTPGet: &corev1.HTTPGetAction{ + Path: "/healthz", + Port: intstr.FromInt(9876), + Scheme: corev1.URISchemeHTTPS, + }, + }, + FailureThreshold: 3, + PeriodSeconds: 10, + SuccessThreshold: 1, + TimeoutSeconds: 15, + }, + LivenessProbe: &corev1.Probe{ + FailureThreshold: 8, + Handler: corev1.Handler{ + HTTPGet: &corev1.HTTPGetAction{ + Path: "/healthz", + Port: intstr.FromInt(9876), + Scheme: corev1.URISchemeHTTPS, + }, + }, + InitialDelaySeconds: 15, + PeriodSeconds: 10, + SuccessThreshold: 1, + TimeoutSeconds: 15, + }, + VolumeMounts: []corev1.VolumeMount{ + { + Name: "machinecontroller-webhook-serving-cert", + MountPath: "/tmp/cert", + ReadOnly: true, + }, + }, + }, + }, }, }, }, } - - return dep } // service returns the internal service for the machine-controller webhook func service() *corev1.Service { - se := &corev1.Service{ - TypeMeta: metav1.TypeMeta{ - APIVersion: "v1", - Kind: "Service", + return &corev1.Service{ + ObjectMeta: metav1.ObjectMeta{ + Name: "machine-controller-webhook", + Namespace: WebhookNamespace, + Labels: map[string]string{ + WebhookAppLabelKey: WebhookAppLabelValue, + }, }, - } - - se.Name = "machine-controller-webhook" - se.Namespace = WebhookNamespace - se.Labels = map[string]string{ - WebhookAppLabelKey: WebhookAppLabelValue, - } - se.Spec.Type = corev1.ServiceTypeClusterIP - se.Spec.Selector = map[string]string{ - WebhookAppLabelKey: WebhookAppLabelValue, - } - se.Spec.Ports = []corev1.ServicePort{ - { - Name: "", - Port: 443, - Protocol: corev1.ProtocolTCP, - TargetPort: intstr.FromInt(9876), + Spec: corev1.ServiceSpec{ + Type: corev1.ServiceTypeClusterIP, + Selector: map[string]string{ + WebhookAppLabelKey: WebhookAppLabelValue, + }, + Ports: []corev1.ServicePort{ + { + Name: "", + Port: 443, + Protocol: corev1.ProtocolTCP, + TargetPort: intstr.FromInt(9876), + }, + }, }, } - - return se } func getServingCertVolume() corev1.Volume { + var mode int32 = 0444 + return corev1.Volume{ Name: "machinecontroller-webhook-serving-cert", VolumeSource: corev1.VolumeSource{ Secret: &corev1.SecretVolumeSource{ SecretName: "machinecontroller-webhook-serving-cert", - DefaultMode: int32Ptr(0444), + DefaultMode: &mode, }, }, } @@ -288,17 +279,6 @@ func getServingCertVolume() corev1.Volume { // tlsServingCertificate returns a secret with the machine-controller-webhook tls certificate // func tlsServingCertificate(ca *triple.KeyPair) (*corev1.Secret, error) { func tlsServingCertificate(caKey *rsa.PrivateKey, caCert *x509.Certificate) (*corev1.Secret, error) { - se := &corev1.Secret{ - TypeMeta: metav1.TypeMeta{ - APIVersion: "v1", - Kind: "Secret", - }, - } - - se.Name = "machinecontroller-webhook-serving-cert" - se.Namespace = WebhookNamespace - se.Data = map[string][]byte{} - commonName := fmt.Sprintf("%s.%s.svc.cluster.local.", WebhookName, WebhookNamespace) altdnsNames := []string{ commonName, @@ -323,86 +303,81 @@ func tlsServingCertificate(caKey *rsa.PrivateKey, caCert *x509.Certificate) (*co return nil, errors.Wrap(err, "failed to generate certificate") } - se.Data["cert.pem"] = certutil.EncodeCertPEM(newKPCert) - se.Data["key.pem"] = certutil.EncodePrivateKeyPEM(newKPKey) - // Include the CA for simplicity - se.Data["ca.crt"] = certutil.EncodeCertPEM(caCert) - - return se, nil + return &corev1.Secret{ + ObjectMeta: metav1.ObjectMeta{ + Name: "machinecontroller-webhook-serving-cert", + Namespace: WebhookNamespace, + }, + Data: map[string][]byte{ + "cert.pem": certutil.EncodeCertPEM(newKPCert), + "key.pem": certutil.EncodePrivateKeyPEM(newKPKey), + "ca.crt": certutil.EncodeCertPEM(caCert), + }, + }, nil } // mutatingwebhookConfiguration returns the MutatingwebhookConfiguration for the machine controler func mutatingwebhookConfiguration(caCert *x509.Certificate) *admissionregistrationv1beta1.MutatingWebhookConfiguration { - cfg := &admissionregistrationv1beta1.MutatingWebhookConfiguration{ - TypeMeta: metav1.TypeMeta{ - APIVersion: "admissionregistration.k8s.io/v1beta1", - Kind: "MutatingWebhookConfiguration", + return &admissionregistrationv1beta1.MutatingWebhookConfiguration{ + ObjectMeta: metav1.ObjectMeta{ + Name: "machine-controller.kubermatic.io", + Namespace: WebhookNamespace, }, - } - - cfg.Name = "machine-controller.kubermatic.io" - cfg.Namespace = WebhookNamespace - - cfg.Webhooks = []admissionregistrationv1beta1.Webhook{ - { - Name: "machine-controller.kubermatic.io-machinedeployments", - NamespaceSelector: &metav1.LabelSelector{}, - FailurePolicy: failurePolicyPtr(admissionregistrationv1beta1.Fail), - Rules: []admissionregistrationv1beta1.RuleWithOperations{ - { - Operations: []admissionregistrationv1beta1.OperationType{ - admissionregistrationv1beta1.Create, - admissionregistrationv1beta1.Update, - }, - Rule: admissionregistrationv1beta1.Rule{ - APIGroups: []string{"cluster.k8s.io"}, - APIVersions: []string{"v1alpha1"}, - Resources: []string{"machinedeployments"}, + Webhooks: []admissionregistrationv1beta1.Webhook{ + { + Name: "machine-controller.kubermatic.io-machinedeployments", + NamespaceSelector: &metav1.LabelSelector{}, + FailurePolicy: failurePolicyPtr(admissionregistrationv1beta1.Fail), + Rules: []admissionregistrationv1beta1.RuleWithOperations{ + { + Operations: []admissionregistrationv1beta1.OperationType{ + admissionregistrationv1beta1.Create, + admissionregistrationv1beta1.Update, + }, + Rule: admissionregistrationv1beta1.Rule{ + APIGroups: []string{"cluster.k8s.io"}, + APIVersions: []string{"v1alpha1"}, + Resources: []string{"machinedeployments"}, + }, }, }, - }, - ClientConfig: admissionregistrationv1beta1.WebhookClientConfig{ - Service: &admissionregistrationv1beta1.ServiceReference{ - Name: WebhookName, - Namespace: WebhookNamespace, - Path: strPtr("/machinedeployments"), + ClientConfig: admissionregistrationv1beta1.WebhookClientConfig{ + Service: &admissionregistrationv1beta1.ServiceReference{ + Name: WebhookName, + Namespace: WebhookNamespace, + Path: strPtr("/machinedeployments"), + }, + CABundle: certutil.EncodeCertPEM(caCert), }, - CABundle: certutil.EncodeCertPEM(caCert), }, - }, - { - Name: "machine-controller.kubermatic.io-machines", - NamespaceSelector: &metav1.LabelSelector{}, - FailurePolicy: failurePolicyPtr(admissionregistrationv1beta1.Fail), - Rules: []admissionregistrationv1beta1.RuleWithOperations{ - { - Operations: []admissionregistrationv1beta1.OperationType{ - admissionregistrationv1beta1.Create, - admissionregistrationv1beta1.Update, - }, - Rule: admissionregistrationv1beta1.Rule{ - APIGroups: []string{"cluster.k8s.io"}, - APIVersions: []string{"v1alpha1"}, - Resources: []string{"machines"}, + { + Name: "machine-controller.kubermatic.io-machines", + NamespaceSelector: &metav1.LabelSelector{}, + FailurePolicy: failurePolicyPtr(admissionregistrationv1beta1.Fail), + Rules: []admissionregistrationv1beta1.RuleWithOperations{ + { + Operations: []admissionregistrationv1beta1.OperationType{ + admissionregistrationv1beta1.Create, + admissionregistrationv1beta1.Update, + }, + Rule: admissionregistrationv1beta1.Rule{ + APIGroups: []string{"cluster.k8s.io"}, + APIVersions: []string{"v1alpha1"}, + Resources: []string{"machines"}, + }, }, }, - }, - ClientConfig: admissionregistrationv1beta1.WebhookClientConfig{ - Service: &admissionregistrationv1beta1.ServiceReference{ - Name: WebhookName, - Namespace: WebhookNamespace, - Path: strPtr("/machines"), + ClientConfig: admissionregistrationv1beta1.WebhookClientConfig{ + Service: &admissionregistrationv1beta1.ServiceReference{ + Name: WebhookName, + Namespace: WebhookNamespace, + Path: strPtr("/machines"), + }, + CABundle: certutil.EncodeCertPEM(caCert), }, - CABundle: certutil.EncodeCertPEM(caCert), }, }, } - - return cfg -} - -func int32Ptr(i int32) *int32 { - return &i } func strPtr(a string) *string { diff --git a/pkg/templates/metricsserver/deployment.go b/pkg/templates/metricsserver/deployment.go index b5b159cdb..9dd1e1b72 100644 --- a/pkg/templates/metricsserver/deployment.go +++ b/pkg/templates/metricsserver/deployment.go @@ -21,6 +21,7 @@ import ( "github.com/pkg/errors" + "github.com/kubermatic/kubeone/pkg/clientutil" "github.com/kubermatic/kubeone/pkg/state" appsv1 "k8s.io/api/apps/v1" @@ -38,7 +39,7 @@ func Deploy(s *state.State) error { return errors.New("kubernetes client not initialized") } - objs := []runtime.Object{ + k8sobjects := []runtime.Object{ aggregatedMetricsReaderClusterRole(), authDelegatorClusterRoleBinding(), metricsServerKubeSystemRoleBinding(), @@ -50,9 +51,9 @@ func Deploy(s *state.State) error { metricServerClusterRoleBinding(), } - bgCtx := context.Background() - for _, o := range objs { - if err := simpleCreateOrUpdate(bgCtx, s.DynamicClient, o); err != nil { + ctx := context.Background() + for _, obj := range k8sobjects { + if err := clientutil.CreateOrUpdate(ctx, s.DynamicClient, obj); err != nil { return errors.WithStack(err) } } @@ -62,10 +63,6 @@ func Deploy(s *state.State) error { func aggregatedMetricsReaderClusterRole() *rbacv1.ClusterRole { return &rbacv1.ClusterRole{ - TypeMeta: metav1.TypeMeta{ - APIVersion: "rbac.authorization.k8s.io/v1", - Kind: "ClusterRole", - }, ObjectMeta: metav1.ObjectMeta{ Name: "system:aggregated-metrics-reader", Labels: map[string]string{ @@ -86,10 +83,6 @@ func aggregatedMetricsReaderClusterRole() *rbacv1.ClusterRole { func authDelegatorClusterRoleBinding() *rbacv1.ClusterRoleBinding { return &rbacv1.ClusterRoleBinding{ - TypeMeta: metav1.TypeMeta{ - APIVersion: "rbac.authorization.k8s.io/v1", - Kind: "ClusterRoleBinding", - }, ObjectMeta: metav1.ObjectMeta{ Name: "metrics-server:system:auth-delegator", }, @@ -110,10 +103,6 @@ func authDelegatorClusterRoleBinding() *rbacv1.ClusterRoleBinding { func metricsServerKubeSystemRoleBinding() *rbacv1.RoleBinding { return &rbacv1.RoleBinding{ - TypeMeta: metav1.TypeMeta{ - APIVersion: "rbac.authorization.k8s.io/v1", - Kind: "RoleBinding", - }, ObjectMeta: metav1.ObjectMeta{ Name: "metrics-server-auth-reader", Namespace: metav1.NamespaceSystem, @@ -135,10 +124,6 @@ func metricsServerKubeSystemRoleBinding() *rbacv1.RoleBinding { func metricsServerAPIService() *apiregv1.APIService { return &apiregv1.APIService{ - TypeMeta: metav1.TypeMeta{ - APIVersion: "apiregistration.k8s.io/v1", - Kind: "APIService", - }, ObjectMeta: metav1.ObjectMeta{ Name: "v1beta1.metrics.k8s.io", }, @@ -158,10 +143,6 @@ func metricsServerAPIService() *apiregv1.APIService { func metricsServerServiceAccount() *corev1.ServiceAccount { return &corev1.ServiceAccount{ - TypeMeta: metav1.TypeMeta{ - APIVersion: "v1", - Kind: "ServiceAccount", - }, ObjectMeta: metav1.ObjectMeta{ Name: "metrics-server", Namespace: metav1.NamespaceSystem, @@ -173,10 +154,6 @@ func metricsServerDeployment() *appsv1.Deployment { k8sAppLabels := map[string]string{"k8s-app": "metrics-server"} return &appsv1.Deployment{ - TypeMeta: metav1.TypeMeta{ - APIVersion: "apps/v1", - Kind: "Deployment", - }, ObjectMeta: metav1.ObjectMeta{ Name: "metrics-server", Namespace: metav1.NamespaceSystem, @@ -226,10 +203,6 @@ func metricsServerDeployment() *appsv1.Deployment { func metricsServerService() *corev1.Service { return &corev1.Service{ - TypeMeta: metav1.TypeMeta{ - APIVersion: "v1", - Kind: "Service", - }, ObjectMeta: metav1.ObjectMeta{ Name: "metrics-server", Namespace: metav1.NamespaceSystem, @@ -241,6 +214,7 @@ func metricsServerService() *corev1.Service { Selector: map[string]string{ "k8s-app": "metrics-server", }, + Type: corev1.ServiceTypeClusterIP, Ports: []corev1.ServicePort{ { Port: 443, @@ -254,10 +228,6 @@ func metricsServerService() *corev1.Service { func metricServerClusterRole() *rbacv1.ClusterRole { return &rbacv1.ClusterRole{ - TypeMeta: metav1.TypeMeta{ - APIVersion: "rbac.authorization.k8s.io/v1", - Kind: "ClusterRole", - }, ObjectMeta: metav1.ObjectMeta{ Name: "system:metrics-server", }, @@ -273,10 +243,6 @@ func metricServerClusterRole() *rbacv1.ClusterRole { func metricServerClusterRoleBinding() *rbacv1.ClusterRoleBinding { return &rbacv1.ClusterRoleBinding{ - TypeMeta: metav1.TypeMeta{ - APIVersion: "rbac.authorization.k8s.io/v1", - Kind: "ClusterRoleBinding", - }, ObjectMeta: metav1.ObjectMeta{ Name: "system:metrics-server", }, diff --git a/pkg/templates/metricsserver/helper.go b/pkg/templates/metricsserver/helper.go deleted file mode 100644 index 7a7024c0d..000000000 --- a/pkg/templates/metricsserver/helper.go +++ /dev/null @@ -1,31 +0,0 @@ -/* -Copyright 2019 The KubeOne 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 metricsserver - -import ( - "context" - - "k8s.io/apimachinery/pkg/runtime" - dynclient "sigs.k8s.io/controller-runtime/pkg/client" - "sigs.k8s.io/controller-runtime/pkg/controller/controllerutil" -) - -func simpleCreateOrUpdate(ctx context.Context, client dynclient.Client, obj runtime.Object) error { - okFunc := func(runtime.Object) error { return nil } - _, err := controllerutil.CreateOrUpdate(ctx, client, obj, okFunc) - return err -} diff --git a/pkg/templates/weave/helper.go b/pkg/templates/weave/helper.go deleted file mode 100644 index 254bd0663..000000000 --- a/pkg/templates/weave/helper.go +++ /dev/null @@ -1,31 +0,0 @@ -/* -Copyright 2019 The KubeOne 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 weave - -import ( - "context" - - "k8s.io/apimachinery/pkg/runtime" - dynclient "sigs.k8s.io/controller-runtime/pkg/client" - "sigs.k8s.io/controller-runtime/pkg/controller/controllerutil" -) - -func simpleCreateOrUpdate(ctx context.Context, client dynclient.Client, obj runtime.Object) error { - okFunc := func(runtime.Object) error { return nil } - _, err := controllerutil.CreateOrUpdate(ctx, client, obj, okFunc) - return err -} diff --git a/pkg/templates/weave/weave-net.go b/pkg/templates/weave/weave-net.go index 5dbf43088..9c72c2061 100644 --- a/pkg/templates/weave/weave-net.go +++ b/pkg/templates/weave/weave-net.go @@ -24,6 +24,7 @@ import ( "github.com/pkg/errors" + "github.com/kubermatic/kubeone/pkg/clientutil" "github.com/kubermatic/kubeone/pkg/state" appsv1 "k8s.io/api/apps/v1" @@ -32,6 +33,7 @@ import ( k8serrors "k8s.io/apimachinery/pkg/api/errors" "k8s.io/apimachinery/pkg/api/resource" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/util/intstr" "sigs.k8s.io/controller-runtime/pkg/client" ) @@ -48,26 +50,20 @@ func Deploy(s *state.State) error { return errors.New("kubernetes dynamic client is not initialized") } - bgCtx := context.Background() + ctx := context.Background() - if err := simpleCreateOrUpdate(bgCtx, s.DynamicClient, serviceAccount()); err != nil { - return errors.Wrap(err, "failed to ensure weave ServiceAccount") + k8sobjects := []runtime.Object{ + serviceAccount(), + clusterRole(), + clusterRoleBinding(), + role(), + roleBinding(), } - if err := simpleCreateOrUpdate(bgCtx, s.DynamicClient, clusterRole()); err != nil { - return errors.Wrap(err, "failed to ensure weave ClusterRole") - } - - if err := simpleCreateOrUpdate(bgCtx, s.DynamicClient, clusterRoleBinding()); err != nil { - return errors.Wrap(err, "failed to ensure weave ClusterRoleBinding") - } - - if err := simpleCreateOrUpdate(bgCtx, s.DynamicClient, role()); err != nil { - return errors.Wrap(err, "failed to ensure weave Role") - } - - if err := simpleCreateOrUpdate(bgCtx, s.DynamicClient, roleBinding()); err != nil { - return errors.Wrap(err, "failed to ensure weave RoleBinding") + for _, obj := range k8sobjects { + if err := clientutil.CreateOrUpdate(ctx, s.DynamicClient, obj); err != nil { + return errors.Wrapf(err, "failed to ensure weave %s", obj.GetObjectKind().GroupVersionKind().Kind) + } } if s.Cluster.ClusterNetwork.CNI.Encrypted { @@ -83,14 +79,14 @@ func Deploy(s *state.State) error { } secCopy := sec.DeepCopy() - err = s.DynamicClient.Get(bgCtx, key, secCopy) + err = s.DynamicClient.Get(ctx, key, secCopy) switch { case k8serrors.IsNotFound(err): case err != nil: return errors.Wrap(err, "failed to get weave-net Secret") } - err = s.DynamicClient.Create(bgCtx, sec) + err = s.DynamicClient.Create(ctx, sec) if err != nil { return errors.Wrap(err, "failed to create weave-net Secret") } @@ -102,7 +98,7 @@ func Deploy(s *state.State) error { } ds := daemonSet(s.Cluster.ClusterNetwork.CNI.Encrypted, strings.Join(peers, " "), s.Cluster.ClusterNetwork.PodSubnet) - if err := simpleCreateOrUpdate(bgCtx, s.DynamicClient, ds); err != nil { + if err := clientutil.CreateOrUpdate(ctx, s.DynamicClient, ds); err != nil { return errors.Wrap(err, "failed to ensure weave DaemonSet") }