diff --git a/hack/boilerplate/boilerplate.py b/hack/boilerplate/boilerplate.py index 1c4582bb3..aebf413e6 100755 --- a/hack/boilerplate/boilerplate.py +++ b/hack/boilerplate/boilerplate.py @@ -156,6 +156,8 @@ def file_extension(filename): '.git', 'vendor', 'hack/boilerplate/test', + 'pkg/apis/apiserver/v1', + 'pkg/apis/apiserver/v1alpha1', 'pkg/apis/kubeadm/v1beta1', 'pkg/apis/kubeadm/v1beta2', ] diff --git a/pkg/apis/apiserver/v1/types.go b/pkg/apis/apiserver/v1/types.go new file mode 100644 index 000000000..e139dceb9 --- /dev/null +++ b/pkg/apis/apiserver/v1/types.go @@ -0,0 +1,50 @@ +/* +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 v1 + +import ( + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime" +) + +// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object + +// AdmissionConfiguration provides versioned configuration for admission controllers. +type AdmissionConfiguration struct { + metav1.TypeMeta `json:",inline"` + + // Plugins allows specifying a configuration per admission control plugin. + // +optional + Plugins []AdmissionPluginConfiguration `json:"plugins"` +} + +// AdmissionPluginConfiguration provides the configuration for a single plug-in. +type AdmissionPluginConfiguration struct { + // Name is the name of the admission controller. + // It must match the registered admission plugin name. + Name string `json:"name"` + + // Path is the path to a configuration file that contains the plugin's + // configuration + // +optional + Path string `json:"path"` + + // Configuration is an embedded configuration object to be used as the plugin's + // configuration. If present, it will be used instead of the path to the configuration file. + // +optional + Configuration *runtime.Unknown `json:"configuration"` +} diff --git a/pkg/apis/apiserver/v1/zz_generated.deepcopy.go b/pkg/apis/apiserver/v1/zz_generated.deepcopy.go new file mode 100644 index 000000000..a0e039de0 --- /dev/null +++ b/pkg/apis/apiserver/v1/zz_generated.deepcopy.go @@ -0,0 +1,78 @@ +// +build !ignore_autogenerated + +/* +Copyright 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. +*/ + +// Code generated by deepcopy-gen. DO NOT EDIT. + +package v1 + +import ( + runtime "k8s.io/apimachinery/pkg/runtime" +) + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *AdmissionConfiguration) DeepCopyInto(out *AdmissionConfiguration) { + *out = *in + out.TypeMeta = in.TypeMeta + if in.Plugins != nil { + in, out := &in.Plugins, &out.Plugins + *out = make([]AdmissionPluginConfiguration, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new AdmissionConfiguration. +func (in *AdmissionConfiguration) DeepCopy() *AdmissionConfiguration { + if in == nil { + return nil + } + out := new(AdmissionConfiguration) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *AdmissionConfiguration) DeepCopyObject() runtime.Object { + if c := in.DeepCopy(); c != nil { + return c + } + return nil +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *AdmissionPluginConfiguration) DeepCopyInto(out *AdmissionPluginConfiguration) { + *out = *in + if in.Configuration != nil { + in, out := &in.Configuration, &out.Configuration + *out = new(runtime.Unknown) + (*in).DeepCopyInto(*out) + } + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new AdmissionPluginConfiguration. +func (in *AdmissionPluginConfiguration) DeepCopy() *AdmissionPluginConfiguration { + if in == nil { + return nil + } + out := new(AdmissionPluginConfiguration) + in.DeepCopyInto(out) + return out +} diff --git a/pkg/apis/apiserver/v1alpha1/types.go b/pkg/apis/apiserver/v1alpha1/types.go new file mode 100644 index 000000000..239b8e20e --- /dev/null +++ b/pkg/apis/apiserver/v1alpha1/types.go @@ -0,0 +1,50 @@ +/* +Copyright 2017 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 v1alpha1 + +import ( + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime" +) + +// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object + +// AdmissionConfiguration provides versioned configuration for admission controllers. +type AdmissionConfiguration struct { + metav1.TypeMeta `json:",inline"` + + // Plugins allows specifying a configuration per admission control plugin. + // +optional + Plugins []AdmissionPluginConfiguration `json:"plugins"` +} + +// AdmissionPluginConfiguration provides the configuration for a single plug-in. +type AdmissionPluginConfiguration struct { + // Name is the name of the admission controller. + // It must match the registered admission plugin name. + Name string `json:"name"` + + // Path is the path to a configuration file that contains the plugin's + // configuration + // +optional + Path string `json:"path"` + + // Configuration is an embedded configuration object to be used as the plugin's + // configuration. If present, it will be used instead of the path to the configuration file. + // +optional + Configuration *runtime.Unknown `json:"configuration"` +} diff --git a/pkg/apis/apiserver/v1alpha1/zz_generated.deepcopy.go b/pkg/apis/apiserver/v1alpha1/zz_generated.deepcopy.go new file mode 100644 index 000000000..24151bbd2 --- /dev/null +++ b/pkg/apis/apiserver/v1alpha1/zz_generated.deepcopy.go @@ -0,0 +1,78 @@ +// +build !ignore_autogenerated + +/* +Copyright 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. +*/ + +// Code generated by deepcopy-gen. DO NOT EDIT. + +package v1alpha1 + +import ( + runtime "k8s.io/apimachinery/pkg/runtime" +) + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *AdmissionConfiguration) DeepCopyInto(out *AdmissionConfiguration) { + *out = *in + out.TypeMeta = in.TypeMeta + if in.Plugins != nil { + in, out := &in.Plugins, &out.Plugins + *out = make([]AdmissionPluginConfiguration, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new AdmissionConfiguration. +func (in *AdmissionConfiguration) DeepCopy() *AdmissionConfiguration { + if in == nil { + return nil + } + out := new(AdmissionConfiguration) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *AdmissionConfiguration) DeepCopyObject() runtime.Object { + if c := in.DeepCopy(); c != nil { + return c + } + return nil +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *AdmissionPluginConfiguration) DeepCopyInto(out *AdmissionPluginConfiguration) { + *out = *in + if in.Configuration != nil { + in, out := &in.Configuration, &out.Configuration + *out = new(runtime.Unknown) + (*in).DeepCopyInto(*out) + } + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new AdmissionPluginConfiguration. +func (in *AdmissionPluginConfiguration) DeepCopy() *AdmissionPluginConfiguration { + if in == nil { + return nil + } + out := new(AdmissionPluginConfiguration) + in.DeepCopyInto(out) + return out +} diff --git a/pkg/apis/kubeone/types.go b/pkg/apis/kubeone/types.go index abbbe19b4..e76a25791 100644 --- a/pkg/apis/kubeone/types.go +++ b/pkg/apis/kubeone/types.go @@ -242,6 +242,7 @@ type MachineControllerConfig struct { // Features controls what features will be enabled on the cluster type Features struct { + PodNodeSelector *PodNodeSelector `json:"podNodeSelector"` PodPresets *PodPresets `json:"podPresets"` PodSecurityPolicy *PodSecurityPolicy `json:"podSecurityPolicy"` StaticAuditLog *StaticAuditLog `json:"staticAuditLog"` @@ -257,6 +258,21 @@ type SystemPackages struct { ConfigureRepositories bool `json:"configureRepositories"` } +// PodNodeSelector feature flag +type PodNodeSelector struct { + Enable bool `json:"enable"` + Config PodNodeSelectorConfig `json:"config"` +} + +// PodNodeSelectorConfig config +type PodNodeSelectorConfig struct { + // ConfigFilePath is a path on the local file system to the PodNodeSelector + // configuration file. + // ConfigFilePath is a required field. + // More info: https://kubernetes.io/docs/reference/access-authn-authz/admission-controllers/#podnodeselector + ConfigFilePath string `json:"configFilePath"` +} + // PodPresets feature flag type PodPresets struct { Enable bool `json:"enable"` diff --git a/pkg/apis/kubeone/v1alpha1/types.go b/pkg/apis/kubeone/v1alpha1/types.go index 63f3e02c0..5c0666212 100644 --- a/pkg/apis/kubeone/v1alpha1/types.go +++ b/pkg/apis/kubeone/v1alpha1/types.go @@ -215,6 +215,7 @@ type MachineControllerConfig struct { // Features controls what features will be enabled on the cluster type Features struct { + PodNodeSelector *PodNodeSelector `json:"podNodeSelector"` PodPresets *PodPresets `json:"podPresets"` PodSecurityPolicy *PodSecurityPolicy `json:"podSecurityPolicy"` StaticAuditLog *StaticAuditLog `json:"staticAuditLog"` @@ -230,6 +231,21 @@ type SystemPackages struct { ConfigureRepositories bool `json:"configureRepositories"` } +// PodNodeSelector feature flag +type PodNodeSelector struct { + Enable bool `json:"enable"` + Config PodNodeSelectorConfig `json:"config"` +} + +// PodNodeSelectorConfig config +type PodNodeSelectorConfig struct { + // ConfigFilePath is a path on the local file system to the PodNodeSelector + // configuration file. + // ConfigFilePath is a required field. + // More info: https://kubernetes.io/docs/reference/access-authn-authz/admission-controllers/#podnodeselector + ConfigFilePath string `json:"configFilePath"` +} + // PodPresets feature flag type PodPresets struct { Enable bool `json:"enable"` diff --git a/pkg/apis/kubeone/v1alpha1/zz_generated.conversion.go b/pkg/apis/kubeone/v1alpha1/zz_generated.conversion.go index 52678e1c2..8de23fdfe 100644 --- a/pkg/apis/kubeone/v1alpha1/zz_generated.conversion.go +++ b/pkg/apis/kubeone/v1alpha1/zz_generated.conversion.go @@ -177,6 +177,26 @@ func RegisterConversions(s *runtime.Scheme) error { }); err != nil { return err } + if err := s.AddGeneratedConversionFunc((*PodNodeSelector)(nil), (*kubeone.PodNodeSelector)(nil), func(a, b interface{}, scope conversion.Scope) error { + return Convert_v1alpha1_PodNodeSelector_To_kubeone_PodNodeSelector(a.(*PodNodeSelector), b.(*kubeone.PodNodeSelector), scope) + }); err != nil { + return err + } + if err := s.AddGeneratedConversionFunc((*kubeone.PodNodeSelector)(nil), (*PodNodeSelector)(nil), func(a, b interface{}, scope conversion.Scope) error { + return Convert_kubeone_PodNodeSelector_To_v1alpha1_PodNodeSelector(a.(*kubeone.PodNodeSelector), b.(*PodNodeSelector), scope) + }); err != nil { + return err + } + if err := s.AddGeneratedConversionFunc((*PodNodeSelectorConfig)(nil), (*kubeone.PodNodeSelectorConfig)(nil), func(a, b interface{}, scope conversion.Scope) error { + return Convert_v1alpha1_PodNodeSelectorConfig_To_kubeone_PodNodeSelectorConfig(a.(*PodNodeSelectorConfig), b.(*kubeone.PodNodeSelectorConfig), scope) + }); err != nil { + return err + } + if err := s.AddGeneratedConversionFunc((*kubeone.PodNodeSelectorConfig)(nil), (*PodNodeSelectorConfig)(nil), func(a, b interface{}, scope conversion.Scope) error { + return Convert_kubeone_PodNodeSelectorConfig_To_v1alpha1_PodNodeSelectorConfig(a.(*kubeone.PodNodeSelectorConfig), b.(*PodNodeSelectorConfig), scope) + }); err != nil { + return err + } if err := s.AddGeneratedConversionFunc((*PodPresets)(nil), (*kubeone.PodPresets)(nil), func(a, b interface{}, scope conversion.Scope) error { return Convert_v1alpha1_PodPresets_To_kubeone_PodPresets(a.(*PodPresets), b.(*kubeone.PodPresets), scope) }); err != nil { @@ -470,6 +490,7 @@ func Convert_kubeone_DynamicAuditLog_To_v1alpha1_DynamicAuditLog(in *kubeone.Dyn } func autoConvert_v1alpha1_Features_To_kubeone_Features(in *Features, out *kubeone.Features, s conversion.Scope) error { + out.PodNodeSelector = (*kubeone.PodNodeSelector)(unsafe.Pointer(in.PodNodeSelector)) out.PodPresets = (*kubeone.PodPresets)(unsafe.Pointer(in.PodPresets)) out.PodSecurityPolicy = (*kubeone.PodSecurityPolicy)(unsafe.Pointer(in.PodSecurityPolicy)) out.StaticAuditLog = (*kubeone.StaticAuditLog)(unsafe.Pointer(in.StaticAuditLog)) @@ -485,6 +506,7 @@ func Convert_v1alpha1_Features_To_kubeone_Features(in *Features, out *kubeone.Fe } func autoConvert_kubeone_Features_To_v1alpha1_Features(in *kubeone.Features, out *Features, s conversion.Scope) error { + out.PodNodeSelector = (*PodNodeSelector)(unsafe.Pointer(in.PodNodeSelector)) out.PodPresets = (*PodPresets)(unsafe.Pointer(in.PodPresets)) out.PodSecurityPolicy = (*PodSecurityPolicy)(unsafe.Pointer(in.PodSecurityPolicy)) out.StaticAuditLog = (*StaticAuditLog)(unsafe.Pointer(in.StaticAuditLog)) @@ -708,6 +730,52 @@ func Convert_kubeone_OpenIDConnectConfig_To_v1alpha1_OpenIDConnectConfig(in *kub return autoConvert_kubeone_OpenIDConnectConfig_To_v1alpha1_OpenIDConnectConfig(in, out, s) } +func autoConvert_v1alpha1_PodNodeSelector_To_kubeone_PodNodeSelector(in *PodNodeSelector, out *kubeone.PodNodeSelector, s conversion.Scope) error { + out.Enable = in.Enable + if err := Convert_v1alpha1_PodNodeSelectorConfig_To_kubeone_PodNodeSelectorConfig(&in.Config, &out.Config, s); err != nil { + return err + } + return nil +} + +// Convert_v1alpha1_PodNodeSelector_To_kubeone_PodNodeSelector is an autogenerated conversion function. +func Convert_v1alpha1_PodNodeSelector_To_kubeone_PodNodeSelector(in *PodNodeSelector, out *kubeone.PodNodeSelector, s conversion.Scope) error { + return autoConvert_v1alpha1_PodNodeSelector_To_kubeone_PodNodeSelector(in, out, s) +} + +func autoConvert_kubeone_PodNodeSelector_To_v1alpha1_PodNodeSelector(in *kubeone.PodNodeSelector, out *PodNodeSelector, s conversion.Scope) error { + out.Enable = in.Enable + if err := Convert_kubeone_PodNodeSelectorConfig_To_v1alpha1_PodNodeSelectorConfig(&in.Config, &out.Config, s); err != nil { + return err + } + return nil +} + +// Convert_kubeone_PodNodeSelector_To_v1alpha1_PodNodeSelector is an autogenerated conversion function. +func Convert_kubeone_PodNodeSelector_To_v1alpha1_PodNodeSelector(in *kubeone.PodNodeSelector, out *PodNodeSelector, s conversion.Scope) error { + return autoConvert_kubeone_PodNodeSelector_To_v1alpha1_PodNodeSelector(in, out, s) +} + +func autoConvert_v1alpha1_PodNodeSelectorConfig_To_kubeone_PodNodeSelectorConfig(in *PodNodeSelectorConfig, out *kubeone.PodNodeSelectorConfig, s conversion.Scope) error { + out.ConfigFilePath = in.ConfigFilePath + return nil +} + +// Convert_v1alpha1_PodNodeSelectorConfig_To_kubeone_PodNodeSelectorConfig is an autogenerated conversion function. +func Convert_v1alpha1_PodNodeSelectorConfig_To_kubeone_PodNodeSelectorConfig(in *PodNodeSelectorConfig, out *kubeone.PodNodeSelectorConfig, s conversion.Scope) error { + return autoConvert_v1alpha1_PodNodeSelectorConfig_To_kubeone_PodNodeSelectorConfig(in, out, s) +} + +func autoConvert_kubeone_PodNodeSelectorConfig_To_v1alpha1_PodNodeSelectorConfig(in *kubeone.PodNodeSelectorConfig, out *PodNodeSelectorConfig, s conversion.Scope) error { + out.ConfigFilePath = in.ConfigFilePath + return nil +} + +// Convert_kubeone_PodNodeSelectorConfig_To_v1alpha1_PodNodeSelectorConfig is an autogenerated conversion function. +func Convert_kubeone_PodNodeSelectorConfig_To_v1alpha1_PodNodeSelectorConfig(in *kubeone.PodNodeSelectorConfig, out *PodNodeSelectorConfig, s conversion.Scope) error { + return autoConvert_kubeone_PodNodeSelectorConfig_To_v1alpha1_PodNodeSelectorConfig(in, out, s) +} + func autoConvert_v1alpha1_PodPresets_To_kubeone_PodPresets(in *PodPresets, out *kubeone.PodPresets, s conversion.Scope) error { out.Enable = in.Enable return nil diff --git a/pkg/apis/kubeone/v1alpha1/zz_generated.deepcopy.go b/pkg/apis/kubeone/v1alpha1/zz_generated.deepcopy.go index 999570b1b..40302ecf2 100644 --- a/pkg/apis/kubeone/v1alpha1/zz_generated.deepcopy.go +++ b/pkg/apis/kubeone/v1alpha1/zz_generated.deepcopy.go @@ -152,6 +152,11 @@ func (in *DynamicAuditLog) DeepCopy() *DynamicAuditLog { // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *Features) DeepCopyInto(out *Features) { *out = *in + if in.PodNodeSelector != nil { + in, out := &in.PodNodeSelector, &out.PodNodeSelector + *out = new(PodNodeSelector) + **out = **in + } if in.PodPresets != nil { in, out := &in.PodPresets, &out.PodPresets *out = new(PodPresets) @@ -363,6 +368,39 @@ func (in *OpenIDConnectConfig) DeepCopy() *OpenIDConnectConfig { return out } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *PodNodeSelector) DeepCopyInto(out *PodNodeSelector) { + *out = *in + out.Config = in.Config + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new PodNodeSelector. +func (in *PodNodeSelector) DeepCopy() *PodNodeSelector { + if in == nil { + return nil + } + out := new(PodNodeSelector) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *PodNodeSelectorConfig) DeepCopyInto(out *PodNodeSelectorConfig) { + *out = *in + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new PodNodeSelectorConfig. +func (in *PodNodeSelectorConfig) DeepCopy() *PodNodeSelectorConfig { + if in == nil { + return nil + } + out := new(PodNodeSelectorConfig) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *PodPresets) DeepCopyInto(out *PodPresets) { *out = *in diff --git a/pkg/apis/kubeone/v1beta1/types.go b/pkg/apis/kubeone/v1beta1/types.go index 5795c79e8..0c4a41ce8 100644 --- a/pkg/apis/kubeone/v1beta1/types.go +++ b/pkg/apis/kubeone/v1beta1/types.go @@ -242,6 +242,7 @@ type MachineControllerConfig struct { // Features controls what features will be enabled on the cluster type Features struct { + PodNodeSelector *PodNodeSelector `json:"podNodeSelector"` PodPresets *PodPresets `json:"podPresets"` PodSecurityPolicy *PodSecurityPolicy `json:"podSecurityPolicy"` StaticAuditLog *StaticAuditLog `json:"staticAuditLog"` @@ -257,6 +258,21 @@ type SystemPackages struct { ConfigureRepositories bool `json:"configureRepositories"` } +// PodNodeSelector feature flag +type PodNodeSelector struct { + Enable bool `json:"enable"` + Config PodNodeSelectorConfig `json:"config"` +} + +// PodNodeSelectorConfig config +type PodNodeSelectorConfig struct { + // ConfigFilePath is a path on the local file system to the PodNodeSelector + // configuration file. + // ConfigFilePath is a required field. + // More info: https://kubernetes.io/docs/reference/access-authn-authz/admission-controllers/#podnodeselector + ConfigFilePath string `json:"configFilePath"` +} + // PodPresets feature flag type PodPresets struct { Enable bool `json:"enable"` diff --git a/pkg/apis/kubeone/v1beta1/zz_generated.conversion.go b/pkg/apis/kubeone/v1beta1/zz_generated.conversion.go index 5606aaab1..62dda8fc8 100644 --- a/pkg/apis/kubeone/v1beta1/zz_generated.conversion.go +++ b/pkg/apis/kubeone/v1beta1/zz_generated.conversion.go @@ -297,6 +297,26 @@ func RegisterConversions(s *runtime.Scheme) error { }); err != nil { return err } + if err := s.AddGeneratedConversionFunc((*PodNodeSelector)(nil), (*kubeone.PodNodeSelector)(nil), func(a, b interface{}, scope conversion.Scope) error { + return Convert_v1beta1_PodNodeSelector_To_kubeone_PodNodeSelector(a.(*PodNodeSelector), b.(*kubeone.PodNodeSelector), scope) + }); err != nil { + return err + } + if err := s.AddGeneratedConversionFunc((*kubeone.PodNodeSelector)(nil), (*PodNodeSelector)(nil), func(a, b interface{}, scope conversion.Scope) error { + return Convert_kubeone_PodNodeSelector_To_v1beta1_PodNodeSelector(a.(*kubeone.PodNodeSelector), b.(*PodNodeSelector), scope) + }); err != nil { + return err + } + if err := s.AddGeneratedConversionFunc((*PodNodeSelectorConfig)(nil), (*kubeone.PodNodeSelectorConfig)(nil), func(a, b interface{}, scope conversion.Scope) error { + return Convert_v1beta1_PodNodeSelectorConfig_To_kubeone_PodNodeSelectorConfig(a.(*PodNodeSelectorConfig), b.(*kubeone.PodNodeSelectorConfig), scope) + }); err != nil { + return err + } + if err := s.AddGeneratedConversionFunc((*kubeone.PodNodeSelectorConfig)(nil), (*PodNodeSelectorConfig)(nil), func(a, b interface{}, scope conversion.Scope) error { + return Convert_kubeone_PodNodeSelectorConfig_To_v1beta1_PodNodeSelectorConfig(a.(*kubeone.PodNodeSelectorConfig), b.(*PodNodeSelectorConfig), scope) + }); err != nil { + return err + } if err := s.AddGeneratedConversionFunc((*PodPresets)(nil), (*kubeone.PodPresets)(nil), func(a, b interface{}, scope conversion.Scope) error { return Convert_v1beta1_PodPresets_To_kubeone_PodPresets(a.(*PodPresets), b.(*kubeone.PodPresets), scope) }); err != nil { @@ -735,6 +755,7 @@ func Convert_kubeone_ExternalCNISpec_To_v1beta1_ExternalCNISpec(in *kubeone.Exte } func autoConvert_v1beta1_Features_To_kubeone_Features(in *Features, out *kubeone.Features, s conversion.Scope) error { + out.PodNodeSelector = (*kubeone.PodNodeSelector)(unsafe.Pointer(in.PodNodeSelector)) out.PodPresets = (*kubeone.PodPresets)(unsafe.Pointer(in.PodPresets)) out.PodSecurityPolicy = (*kubeone.PodSecurityPolicy)(unsafe.Pointer(in.PodSecurityPolicy)) out.StaticAuditLog = (*kubeone.StaticAuditLog)(unsafe.Pointer(in.StaticAuditLog)) @@ -750,6 +771,7 @@ func Convert_v1beta1_Features_To_kubeone_Features(in *Features, out *kubeone.Fea } func autoConvert_kubeone_Features_To_v1beta1_Features(in *kubeone.Features, out *Features, s conversion.Scope) error { + out.PodNodeSelector = (*PodNodeSelector)(unsafe.Pointer(in.PodNodeSelector)) out.PodPresets = (*PodPresets)(unsafe.Pointer(in.PodPresets)) out.PodSecurityPolicy = (*PodSecurityPolicy)(unsafe.Pointer(in.PodSecurityPolicy)) out.StaticAuditLog = (*StaticAuditLog)(unsafe.Pointer(in.StaticAuditLog)) @@ -1080,6 +1102,52 @@ func Convert_kubeone_PacketSpec_To_v1beta1_PacketSpec(in *kubeone.PacketSpec, ou return autoConvert_kubeone_PacketSpec_To_v1beta1_PacketSpec(in, out, s) } +func autoConvert_v1beta1_PodNodeSelector_To_kubeone_PodNodeSelector(in *PodNodeSelector, out *kubeone.PodNodeSelector, s conversion.Scope) error { + out.Enable = in.Enable + if err := Convert_v1beta1_PodNodeSelectorConfig_To_kubeone_PodNodeSelectorConfig(&in.Config, &out.Config, s); err != nil { + return err + } + return nil +} + +// Convert_v1beta1_PodNodeSelector_To_kubeone_PodNodeSelector is an autogenerated conversion function. +func Convert_v1beta1_PodNodeSelector_To_kubeone_PodNodeSelector(in *PodNodeSelector, out *kubeone.PodNodeSelector, s conversion.Scope) error { + return autoConvert_v1beta1_PodNodeSelector_To_kubeone_PodNodeSelector(in, out, s) +} + +func autoConvert_kubeone_PodNodeSelector_To_v1beta1_PodNodeSelector(in *kubeone.PodNodeSelector, out *PodNodeSelector, s conversion.Scope) error { + out.Enable = in.Enable + if err := Convert_kubeone_PodNodeSelectorConfig_To_v1beta1_PodNodeSelectorConfig(&in.Config, &out.Config, s); err != nil { + return err + } + return nil +} + +// Convert_kubeone_PodNodeSelector_To_v1beta1_PodNodeSelector is an autogenerated conversion function. +func Convert_kubeone_PodNodeSelector_To_v1beta1_PodNodeSelector(in *kubeone.PodNodeSelector, out *PodNodeSelector, s conversion.Scope) error { + return autoConvert_kubeone_PodNodeSelector_To_v1beta1_PodNodeSelector(in, out, s) +} + +func autoConvert_v1beta1_PodNodeSelectorConfig_To_kubeone_PodNodeSelectorConfig(in *PodNodeSelectorConfig, out *kubeone.PodNodeSelectorConfig, s conversion.Scope) error { + out.ConfigFilePath = in.ConfigFilePath + return nil +} + +// Convert_v1beta1_PodNodeSelectorConfig_To_kubeone_PodNodeSelectorConfig is an autogenerated conversion function. +func Convert_v1beta1_PodNodeSelectorConfig_To_kubeone_PodNodeSelectorConfig(in *PodNodeSelectorConfig, out *kubeone.PodNodeSelectorConfig, s conversion.Scope) error { + return autoConvert_v1beta1_PodNodeSelectorConfig_To_kubeone_PodNodeSelectorConfig(in, out, s) +} + +func autoConvert_kubeone_PodNodeSelectorConfig_To_v1beta1_PodNodeSelectorConfig(in *kubeone.PodNodeSelectorConfig, out *PodNodeSelectorConfig, s conversion.Scope) error { + out.ConfigFilePath = in.ConfigFilePath + return nil +} + +// Convert_kubeone_PodNodeSelectorConfig_To_v1beta1_PodNodeSelectorConfig is an autogenerated conversion function. +func Convert_kubeone_PodNodeSelectorConfig_To_v1beta1_PodNodeSelectorConfig(in *kubeone.PodNodeSelectorConfig, out *PodNodeSelectorConfig, s conversion.Scope) error { + return autoConvert_kubeone_PodNodeSelectorConfig_To_v1beta1_PodNodeSelectorConfig(in, out, s) +} + func autoConvert_v1beta1_PodPresets_To_kubeone_PodPresets(in *PodPresets, out *kubeone.PodPresets, s conversion.Scope) error { out.Enable = in.Enable return nil diff --git a/pkg/apis/kubeone/v1beta1/zz_generated.deepcopy.go b/pkg/apis/kubeone/v1beta1/zz_generated.deepcopy.go index ed28cfac0..289b802bf 100644 --- a/pkg/apis/kubeone/v1beta1/zz_generated.deepcopy.go +++ b/pkg/apis/kubeone/v1beta1/zz_generated.deepcopy.go @@ -337,6 +337,11 @@ func (in *ExternalCNISpec) DeepCopy() *ExternalCNISpec { // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *Features) DeepCopyInto(out *Features) { *out = *in + if in.PodNodeSelector != nil { + in, out := &in.PodNodeSelector, &out.PodNodeSelector + *out = new(PodNodeSelector) + **out = **in + } if in.PodPresets != nil { in, out := &in.PodPresets, &out.PodPresets *out = new(PodPresets) @@ -603,6 +608,39 @@ func (in *PacketSpec) DeepCopy() *PacketSpec { return out } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *PodNodeSelector) DeepCopyInto(out *PodNodeSelector) { + *out = *in + out.Config = in.Config + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new PodNodeSelector. +func (in *PodNodeSelector) DeepCopy() *PodNodeSelector { + if in == nil { + return nil + } + out := new(PodNodeSelector) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *PodNodeSelectorConfig) DeepCopyInto(out *PodNodeSelectorConfig) { + *out = *in + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new PodNodeSelectorConfig. +func (in *PodNodeSelectorConfig) DeepCopy() *PodNodeSelectorConfig { + if in == nil { + return nil + } + out := new(PodNodeSelectorConfig) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *PodPresets) DeepCopyInto(out *PodPresets) { *out = *in diff --git a/pkg/apis/kubeone/validation/validation.go b/pkg/apis/kubeone/validation/validation.go index 2633ed289..ee5df6672 100644 --- a/pkg/apis/kubeone/validation/validation.go +++ b/pkg/apis/kubeone/validation/validation.go @@ -257,6 +257,9 @@ func ValidateDynamicWorkerConfig(workerset []kubeone.DynamicWorkerConfig, fldPat func ValidateFeatures(f kubeone.Features, fldPath *field.Path) field.ErrorList { allErrs := field.ErrorList{} + if f.PodNodeSelector != nil && f.PodNodeSelector.Enable { + allErrs = append(allErrs, ValidatePodNodeSelectorConfig(f.PodNodeSelector.Config, fldPath.Child("podNodeSelector"))...) + } if f.StaticAuditLog != nil && f.StaticAuditLog.Enable { allErrs = append(allErrs, ValidateStaticAuditLogConfig(f.StaticAuditLog.Config, fldPath.Child("staticAuditLog"))...) } @@ -267,7 +270,18 @@ func ValidateFeatures(f kubeone.Features, fldPath *field.Path) field.ErrorList { return allErrs } -// ValidateFeatures validates the StaticAuditLogConfig structure +// ValidatePodNodeSelectorConfig validates the PodNodeSelectorConfig structure +func ValidatePodNodeSelectorConfig(n kubeone.PodNodeSelectorConfig, fldPath *field.Path) field.ErrorList { + allErrs := field.ErrorList{} + + if len(n.ConfigFilePath) == 0 { + allErrs = append(allErrs, field.Required(fldPath.Child("configFilePath"), ".podNodeSelector.config.configFilePath is a required field")) + } + + return allErrs +} + +// ValidateStaticAuditLogConfig validates the StaticAuditLogConfig structure func ValidateStaticAuditLogConfig(s kubeone.StaticAuditLogConfig, fldPath *field.Path) field.ErrorList { allErrs := field.ErrorList{} diff --git a/pkg/apis/kubeone/validation/validation_test.go b/pkg/apis/kubeone/validation/validation_test.go index 91acfcefc..f2621d86f 100644 --- a/pkg/apis/kubeone/validation/validation_test.go +++ b/pkg/apis/kubeone/validation/validation_test.go @@ -978,6 +978,16 @@ func TestValidateFeatures(t *testing.T) { }, expectedError: true, }, + { + name: "invalid podNodeSelector config", + features: kubeone.Features{ + PodNodeSelector: &kubeone.PodNodeSelector{ + Enable: true, + Config: kubeone.PodNodeSelectorConfig{}, + }, + }, + expectedError: true, + }, } for _, tc := range tests { tc := tc @@ -990,6 +1000,36 @@ func TestValidateFeatures(t *testing.T) { } } +func TestValidatePodNodeSelectorConfig(t *testing.T) { + tests := []struct { + name string + podNodeSelectorConfig kubeone.PodNodeSelectorConfig + expectedError bool + }{ + { + name: "valid podNodeSelector config", + podNodeSelectorConfig: kubeone.PodNodeSelectorConfig{ + ConfigFilePath: "./podnodeselector.yaml", + }, + expectedError: false, + }, + { + name: "invalid podNodeSelector config", + podNodeSelectorConfig: kubeone.PodNodeSelectorConfig{}, + expectedError: true, + }, + } + for _, tc := range tests { + tc := tc + t.Run(tc.name, func(t *testing.T) { + errs := ValidatePodNodeSelectorConfig(tc.podNodeSelectorConfig, nil) + if (len(errs) == 0) == tc.expectedError { + t.Errorf("test case failed: expected %v, but got %v", tc.expectedError, (len(errs) != 0)) + } + }) + } +} + func TestValidateStaticAuditLogConfig(t *testing.T) { tests := []struct { name string diff --git a/pkg/apis/kubeone/zz_generated.deepcopy.go b/pkg/apis/kubeone/zz_generated.deepcopy.go index 146201cc7..cac147c7b 100644 --- a/pkg/apis/kubeone/zz_generated.deepcopy.go +++ b/pkg/apis/kubeone/zz_generated.deepcopy.go @@ -337,6 +337,11 @@ func (in *ExternalCNISpec) DeepCopy() *ExternalCNISpec { // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *Features) DeepCopyInto(out *Features) { *out = *in + if in.PodNodeSelector != nil { + in, out := &in.PodNodeSelector, &out.PodNodeSelector + *out = new(PodNodeSelector) + **out = **in + } if in.PodPresets != nil { in, out := &in.PodPresets, &out.PodPresets *out = new(PodPresets) @@ -603,6 +608,39 @@ func (in *PacketSpec) DeepCopy() *PacketSpec { return out } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *PodNodeSelector) DeepCopyInto(out *PodNodeSelector) { + *out = *in + out.Config = in.Config + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new PodNodeSelector. +func (in *PodNodeSelector) DeepCopy() *PodNodeSelector { + if in == nil { + return nil + } + out := new(PodNodeSelector) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *PodNodeSelectorConfig) DeepCopyInto(out *PodNodeSelectorConfig) { + *out = *in + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new PodNodeSelectorConfig. +func (in *PodNodeSelectorConfig) DeepCopy() *PodNodeSelectorConfig { + if in == nil { + return nil + } + out := new(PodNodeSelectorConfig) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *PodPresets) DeepCopyInto(out *PodPresets) { *out = *in diff --git a/pkg/cmd/config.go b/pkg/cmd/config.go index 6f5d70fc7..cd46e52b2 100644 --- a/pkg/cmd/config.go +++ b/pkg/cmd/config.go @@ -67,6 +67,7 @@ type printOpts struct { HTTPSProxy string `longflag:"proxy-https"` NoProxy string `longflag:"proxy-no-proxy"` + EnablePodNodeSelector bool `longflag:"enable-pod-node-selector"` EnablePodSecurityPolicy bool `longflag:"enable-pod-security-policy"` EnablePodPresets bool `longflag:"enable-pod-presets"` EnableStaticAuditLog bool `longflag:"enable-static-audit-log"` @@ -156,6 +157,7 @@ configuration manifest, run the print command with --full flag. cmd.Flags().StringVar(&opts.NoProxy, longFlagName(opts, "NoProxy"), "", "No Proxy to be used for provisioning and Docker") // Features + cmd.Flags().BoolVar(&opts.EnablePodNodeSelector, longFlagName(opts, "EnablePodNodeSelector"), false, "enable PodNodeSelector admission plugin") cmd.Flags().BoolVar(&opts.EnablePodSecurityPolicy, longFlagName(opts, "EnablePodSecurityPolicy"), false, "enable PodSecurityPolicy") cmd.Flags().BoolVar(&opts.EnablePodPresets, longFlagName(opts, "EnablePodPresets"), false, "enable PodPresets") cmd.Flags().BoolVar(&opts.EnableStaticAuditLog, longFlagName(opts, "EnableStaticAuditLog"), false, "enable StaticAuditLog") @@ -318,6 +320,10 @@ func createAndPrintManifest(printOptions *printOpts) error { } // Features + if printOptions.EnablePodNodeSelector { + cfg.Set(yamled.Path{"features", "podNodeSelector", "enable"}, printOptions.EnablePodSecurityPolicy) + cfg.Set(yamled.Path{"features", "podNodeSelector", "config", "configFilePath"}, "") + } if printOptions.EnablePodSecurityPolicy { cfg.Set(yamled.Path{"features", "podSecurityPolicy", "enable"}, printOptions.EnablePodSecurityPolicy) } @@ -477,6 +483,16 @@ cloudProvider: cloudConfig: "{{ .CloudProviderCloudCfg }}" features: + # Enable the PodNodeSelector admission plugin in API server. + # More info: https://kubernetes.io/docs/reference/access-authn-authz/admission-controllers/#podnodeselector + podNodeSelector: + enable: {{ .EnablePodNodeSelector }} + config: + # configFilePath is a path on a local file system to the podNodeSelector + # plugin config, which defines default and allowed node selectors. + # configFilePath is is a required field. + # More info: https://kubernetes.io/docs/reference/access-authn-authz/admission-controllers/#configuration-file-format-1 + configFilePath: "" # Enables PodSecurityPolicy admission plugin in API server, as well as creates # default 'privileged' PodSecurityPolicy, plus RBAC rules to authorize # 'kube-system' namespace pods to 'use' it. diff --git a/pkg/configupload/configuration.go b/pkg/configupload/configuration.go index 28d3cff75..a4a4e9736 100644 --- a/pkg/configupload/configuration.go +++ b/pkg/configupload/configuration.go @@ -49,7 +49,17 @@ func (c *Configuration) AddFile(filename string, content string) { } // AddFilePath saves file contents from a file on filesystem for future references -func (c *Configuration) AddFilePath(filename, filePath string) error { +func (c *Configuration) AddFilePath(filename, filePath, manifestFilePath string) error { + // Normalize the file path. In the case when the relative path is provided, + // the path is relative to the KubeOne configuration file. + if !filepath.IsAbs(filePath) && manifestFilePath != "" { + manifestAbsPath, err := filepath.Abs(filepath.Dir(manifestFilePath)) + if err != nil { + return errors.Wrap(err, "unable to get absolute path to the cluster manifest") + } + filePath = filepath.Join(manifestAbsPath, filePath) + } + b, err := ioutil.ReadFile(filePath) if err != nil { return errors.Wrap(err, "unable to open given file") diff --git a/pkg/features/activate.go b/pkg/features/activate.go index 5158d3e92..294e9f627 100644 --- a/pkg/features/activate.go +++ b/pkg/features/activate.go @@ -24,6 +24,12 @@ import ( "github.com/kubermatic/kubeone/pkg/templates/kubeadm/kubeadmargs" ) +const ( + apiServerAdmissionPluginsFlag = "enable-admission-plugins" + apiServerAdmissionControlConfigFlag = "admission-control-config-file" + apiServerAdmissionControlConfigPath = "/etc/kubernetes/admission/admission-config.yaml" +) + // Activate configured features. // Installing CRDs, creating policies and so on func Activate(s *state.State) error { @@ -37,6 +43,10 @@ func Activate(s *state.State) error { return errors.Wrap(err, "failed to install metrics-server") } + if err := installPodNodeSelector(s.Context, s.DynamicClient, s.Cluster.Features.PodNodeSelector); err != nil { + return errors.Wrap(err, "failed to install podNodeSelector") + } + return nil } @@ -47,5 +57,6 @@ func UpdateKubeadmClusterConfiguration(featuresCfg kubeoneapi.Features, args *ku activateKubeadmStaticAuditLogs(featuresCfg.StaticAuditLog, args) activateKubeadmDynamicAuditLogs(featuresCfg.DynamicAuditLog, args) activateKubeadmOIDC(featuresCfg.OpenIDConnect, args) - activatePodPresets(featuresCfg.PodPresets, args) + activateKubeadmPodPresets(featuresCfg.PodPresets, args) + activateKubeadmPodNodeSelector(featuresCfg.PodNodeSelector, args) } diff --git a/pkg/features/podnodeselector.go b/pkg/features/podnodeselector.go new file mode 100644 index 000000000..d3f2f286d --- /dev/null +++ b/pkg/features/podnodeselector.go @@ -0,0 +1,103 @@ +/* +Copyright 2020 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 features + +import ( + "context" + + kubeoneapi "github.com/kubermatic/kubeone/pkg/apis/kubeone" + "github.com/kubermatic/kubeone/pkg/clientutil" + "github.com/kubermatic/kubeone/pkg/templates/kubeadm/kubeadmargs" + + corev1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + utilerrors "k8s.io/apimachinery/pkg/util/errors" + "sigs.k8s.io/controller-runtime/pkg/client" +) + +const ( + podNodeSelectorAdmissionPlugin = "PodNodeSelector" + nodeSelectorAnnotation = "scheduler.alpha.kubernetes.io/node-selector" +) + +func activateKubeadmPodNodeSelector(feature *kubeoneapi.PodNodeSelector, args *kubeadmargs.Args) { + if feature == nil || !feature.Enable { + return + } + + args.APIServer.AppendMapStringStringExtraArg(apiServerAdmissionPluginsFlag, podNodeSelectorAdmissionPlugin) + args.APIServer.ExtraArgs[apiServerAdmissionControlConfigFlag] = apiServerAdmissionControlConfigPath +} + +// installPodNodeSelector annotates the kube-system namespace and deletes all +// pending pods in the kube-system namespace +func installPodNodeSelector(ctx context.Context, c client.Client, feature *kubeoneapi.PodNodeSelector) error { + if feature == nil || !feature.Enable { + return nil + } + + if err := annotateKubeSystemNamespace(ctx, c); err != nil { + return err + } + if err := deletePendingPods(ctx, c); err != nil { + return err + } + + return nil +} + +// annotateKubeSystemNamespace adds the scheduler.alpha.kubernetes.io/node-selector: "" +// annotation to the kube-system namespace. This ensures that critical pods, such +// as CNI and kube-proxy, can get scheduled on all nodes in the cluster. +func annotateKubeSystemNamespace(ctx context.Context, c client.Client) error { + ns := corev1.Namespace{} + key := client.ObjectKey{ + Name: metav1.NamespaceSystem, + } + + if err := c.Get(ctx, key, &ns); err != nil { + return err + } + if ns.Annotations == nil { + ns.Annotations = map[string]string{} + } + ns.Annotations[nodeSelectorAnnotation] = "" + + return clientutil.CreateOrUpdate(ctx, c, &ns) +} + +// deletePendingPods polls for pending pods in the kube-system namespace +// and deletes them. The annotation has effect only for the newly-created pods, +// so pods created before annotating the namespace might have incorrect node +// selectors. +func deletePendingPods(ctx context.Context, c client.Client) error { + podList := &corev1.PodList{} + if err := c.List(ctx, podList, client.InNamespace(metav1.NamespaceSystem)); err != nil { + return err + } + + errs := []error{} + for i, pod := range podList.Items { + if pod.Status.Phase == corev1.PodPending { + if delErr := c.Delete(ctx, &podList.Items[i]); delErr != nil { + errs = append(errs, delErr) + } + } + } + + return utilerrors.NewAggregate(errs) +} diff --git a/pkg/features/podpresets.go b/pkg/features/podpresets.go index 8acd6ff29..370a8508c 100644 --- a/pkg/features/podpresets.go +++ b/pkg/features/podpresets.go @@ -28,7 +28,7 @@ const ( pluginFlag = "enable-admission-plugins" ) -func activatePodPresets(feature *kubeoneapi.PodPresets, args *kubeadmargs.Args) { +func activateKubeadmPodPresets(feature *kubeoneapi.PodPresets, args *kubeadmargs.Args) { if feature == nil || !feature.Enable { return } diff --git a/pkg/features/psp.go b/pkg/features/psp.go index e033820c4..61d0678b2 100644 --- a/pkg/features/psp.go +++ b/pkg/features/psp.go @@ -34,9 +34,8 @@ import ( ) const ( - pspAdmissionPlugin = "PodSecurityPolicy" - apiServerAdmissionPluginsFlag = "enable-admission-plugins" - pspRoleNamespace = metav1.NamespaceSystem + pspAdmissionPlugin = "PodSecurityPolicy" + pspRoleNamespace = metav1.NamespaceSystem ) func activateKubeadmPSP(feature *kubeoneapi.PodSecurityPolicy, args *kubeadmargs.Args) { diff --git a/pkg/scripts/configs.go b/pkg/scripts/configs.go index 1780d7fcd..842287da0 100644 --- a/pkg/scripts/configs.go +++ b/pkg/scripts/configs.go @@ -36,6 +36,16 @@ if [[ -f "{{ .WORK_DIR }}/cfg/audit-policy.yaml" ]]; then sudo mv {{ .WORK_DIR }}/cfg/audit-policy.yaml /etc/kubernetes/audit/policy.yaml sudo chown root:root /etc/kubernetes/audit/policy.yaml fi +` + + podNodeSelectorConfigTemplate = ` +if [[ -f "{{ .WORK_DIR }}/cfg/podnodeselector.yaml" ]]; then + sudo mkdir -p /etc/kubernetes/admission + sudo mv {{ .WORK_DIR }}/cfg/podnodeselector.yaml /etc/kubernetes/admission/podnodeselector.yaml + sudo mv {{ .WORK_DIR }}/cfg/admission-config.yaml /etc/kubernetes/admission/admission-config.yaml + sudo chown root:root /etc/kubernetes/admission/podnodeselector.yaml + sudo chown root:root /etc/kubernetes/admission/admission-config.yaml +fi ` ) @@ -54,3 +64,9 @@ func SaveAuditPolicyConfig(workdir string) (string, error) { "WORK_DIR": workdir, }) } + +func SavePodNodeSelectorConfig(workdir string) (string, error) { + return Render(podNodeSelectorConfigTemplate, Data{ + "WORK_DIR": workdir, + }) +} diff --git a/pkg/scripts/kubeadm.go b/pkg/scripts/kubeadm.go index 5d82fa8ef..9368df765 100644 --- a/pkg/scripts/kubeadm.go +++ b/pkg/scripts/kubeadm.go @@ -56,6 +56,7 @@ sudo kubeadm {{ .VERBOSE }} \ kubeadmResetScriptTemplate = ` sudo kubeadm {{ .VERBOSE }} reset --force || true sudo rm -f /etc/kubernetes/cloud-config +sudo rm -rf /etc/kubernetes/admission sudo rm -rf /var/lib/etcd/ rm -rf "{{ .WORK_DIR }}" ` diff --git a/pkg/scripts/testdata/TestKubeadmReset-not-verbose.golden b/pkg/scripts/testdata/TestKubeadmReset-not-verbose.golden index c6494687e..a5937cd3d 100644 --- a/pkg/scripts/testdata/TestKubeadmReset-not-verbose.golden +++ b/pkg/scripts/testdata/TestKubeadmReset-not-verbose.golden @@ -3,5 +3,6 @@ export "PATH=$PATH:/sbin:/usr/local/bin:/opt/bin" sudo kubeadm reset --force || true sudo rm -f /etc/kubernetes/cloud-config +sudo rm -rf /etc/kubernetes/admission sudo rm -rf /var/lib/etcd/ rm -rf "test-wd" diff --git a/pkg/scripts/testdata/TestKubeadmReset-verbose.golden b/pkg/scripts/testdata/TestKubeadmReset-verbose.golden index 495ffb23d..b77624845 100644 --- a/pkg/scripts/testdata/TestKubeadmReset-verbose.golden +++ b/pkg/scripts/testdata/TestKubeadmReset-verbose.golden @@ -3,5 +3,6 @@ export "PATH=$PATH:/sbin:/usr/local/bin:/opt/bin" sudo kubeadm --v=6 reset --force || true sudo rm -f /etc/kubernetes/cloud-config +sudo rm -rf /etc/kubernetes/admission sudo rm -rf /var/lib/etcd/ rm -rf "test-wd" diff --git a/pkg/tasks/prerequisites.go b/pkg/tasks/prerequisites.go index 6f775bbfe..37c888970 100644 --- a/pkg/tasks/prerequisites.go +++ b/pkg/tasks/prerequisites.go @@ -23,6 +23,7 @@ import ( "github.com/kubermatic/kubeone/pkg/scripts" "github.com/kubermatic/kubeone/pkg/ssh" "github.com/kubermatic/kubeone/pkg/state" + "github.com/kubermatic/kubeone/pkg/templates/admissionconfig" ) func installPrerequisites(s *state.State) error { @@ -35,11 +36,21 @@ func generateConfigurationFiles(s *state.State) error { s.Configuration.AddFile("cfg/cloud-config", s.Cluster.CloudProvider.CloudConfig) if s.Cluster.Features.StaticAuditLog != nil && s.Cluster.Features.StaticAuditLog.Enable { - err := s.Configuration.AddFilePath("cfg/audit-policy.yaml", s.Cluster.Features.StaticAuditLog.Config.PolicyFilePath) - if err != nil { + if err := s.Configuration.AddFilePath("cfg/audit-policy.yaml", s.Cluster.Features.StaticAuditLog.Config.PolicyFilePath, s.ManifestFilePath); err != nil { return errors.Wrap(err, "unable to add policy file") } } + if s.Cluster.Features.PodNodeSelector != nil && s.Cluster.Features.PodNodeSelector.Enable { + admissionCfg, err := admissionconfig.NewAdmissionConfig(s.Cluster.Versions.Kubernetes, s.Cluster.Features.PodNodeSelector) + if err != nil { + return errors.Wrap(err, "failed to generate admissionconfiguration manifest") + } + s.Configuration.AddFile("cfg/admission-config.yaml", admissionCfg) + + if err := s.Configuration.AddFilePath("cfg/podnodeselector.yaml", s.Cluster.Features.PodNodeSelector.Config.ConfigFilePath, s.ManifestFilePath); err != nil { + return errors.Wrap(err, "failed to add podnodeselector config file") + } + } return nil } @@ -141,6 +152,15 @@ func uploadConfigurationFilesToNode(s *state.State, node *kubeoneapi.HostConfig, if err != nil { return err } + _, _, err = s.Runner.RunRaw(cmd) + if err != nil { + return err + } + + cmd, err = scripts.SavePodNodeSelectorConfig(s.WorkDir) + if err != nil { + return err + } _, _, err = s.Runner.RunRaw(cmd) return err diff --git a/pkg/templates/admissionconfig/admissionconfig.go b/pkg/templates/admissionconfig/admissionconfig.go new file mode 100644 index 000000000..701ba252e --- /dev/null +++ b/pkg/templates/admissionconfig/admissionconfig.go @@ -0,0 +1,90 @@ +/* +Copyright 2020 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 admissionconfig + +import ( + "github.com/Masterminds/semver" + "github.com/pkg/errors" + + apiserverv1 "github.com/kubermatic/kubeone/pkg/apis/apiserver/v1" + apiserverv1alpha1 "github.com/kubermatic/kubeone/pkg/apis/apiserver/v1alpha1" + kubeoneapi "github.com/kubermatic/kubeone/pkg/apis/kubeone" + "github.com/kubermatic/kubeone/pkg/templates" + + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime" +) + +// NewAdmissionConfig generates the AdmissionConfiguration manifest +func NewAdmissionConfig(k8sVersion string, podNodeSelectorFeature *kubeoneapi.PodNodeSelector) (string, error) { + sver, err := semver.NewVersion(k8sVersion) + if err != nil { + return "", errors.Wrap(err, "failed to parse version") + } + c, err := semver.NewConstraint("< 1.17.0") + if err != nil { + return "", errors.Wrap(err, "failed to parse the semver constraint") + } + + var admissionCfg []runtime.Object + switch { + case c.Check(sver): + admissionCfg = admissionConfigV1alpha1(podNodeSelectorFeature) + default: + admissionCfg = admissionConfigV1(podNodeSelectorFeature) + } + + return templates.KubernetesToYAML(admissionCfg) +} + +func admissionConfigV1(podNodeSelectorFeature *kubeoneapi.PodNodeSelector) []runtime.Object { + admissionConfig := &apiserverv1.AdmissionConfiguration{ + TypeMeta: metav1.TypeMeta{ + APIVersion: "apiserver.config.k8s.io/v1", + Kind: "AdmissionConfiguration", + }, + } + + if podNodeSelectorFeature != nil && podNodeSelectorFeature.Enable { + pnsPlugin := apiserverv1.AdmissionPluginConfiguration{ + Name: "PodNodeSelector", + Path: "/etc/kubernetes/admission/podnodeselector.yaml", + } + admissionConfig.Plugins = append(admissionConfig.Plugins, pnsPlugin) + } + + return []runtime.Object{admissionConfig} +} + +func admissionConfigV1alpha1(podNodeSelectorFeature *kubeoneapi.PodNodeSelector) []runtime.Object { + admissionConfig := &apiserverv1alpha1.AdmissionConfiguration{ + TypeMeta: metav1.TypeMeta{ + APIVersion: "apiserver.k8s.io/v1alpha1", + Kind: "AdmissionConfiguration", + }, + } + + if podNodeSelectorFeature != nil && podNodeSelectorFeature.Enable { + pnsPlugin := apiserverv1alpha1.AdmissionPluginConfiguration{ + Name: "PodNodeSelector", + Path: "/etc/kubernetes/admission/podnodeselector.yaml", + } + admissionConfig.Plugins = append(admissionConfig.Plugins, pnsPlugin) + } + + return []runtime.Object{admissionConfig} +} diff --git a/pkg/templates/kubeadm/v1beta1/kubeadm.go b/pkg/templates/kubeadm/v1beta1/kubeadm.go index 283c7af99..613231e96 100644 --- a/pkg/templates/kubeadm/v1beta1/kubeadm.go +++ b/pkg/templates/kubeadm/v1beta1/kubeadm.go @@ -207,6 +207,17 @@ func NewConfig(s *state.State, host kubeoneapi.HostConfig) ([]runtime.Object, er clusterConfig.APIServer.ExtraVolumes = append(clusterConfig.APIServer.ExtraVolumes, logVol) } + if cluster.Features.PodNodeSelector != nil && cluster.Features.PodNodeSelector.Enable { + admissionVol := kubeadmv1beta1.HostPathMount{ + Name: "admission-conf", + HostPath: "/etc/kubernetes/admission", + MountPath: "/etc/kubernetes/admission", + ReadOnly: true, + PathType: corev1.HostPathDirectoryOrCreate, + } + clusterConfig.APIServer.ExtraVolumes = append(clusterConfig.APIServer.ExtraVolumes, admissionVol) + } + args := kubeadmargs.NewFrom(clusterConfig.APIServer.ExtraArgs) features.UpdateKubeadmClusterConfiguration(cluster.Features, args) diff --git a/pkg/templates/kubeadm/v1beta2/kubeadm.go b/pkg/templates/kubeadm/v1beta2/kubeadm.go index 0ed64d13b..236f1391e 100644 --- a/pkg/templates/kubeadm/v1beta2/kubeadm.go +++ b/pkg/templates/kubeadm/v1beta2/kubeadm.go @@ -207,6 +207,17 @@ func NewConfig(s *state.State, host kubeoneapi.HostConfig) ([]runtime.Object, er clusterConfig.APIServer.ExtraVolumes = append(clusterConfig.APIServer.ExtraVolumes, logVol) } + if cluster.Features.PodNodeSelector != nil && cluster.Features.PodNodeSelector.Enable { + admissionVol := kubeadmv1beta2.HostPathMount{ + Name: "admission-conf", + HostPath: "/etc/kubernetes/admission", + MountPath: "/etc/kubernetes/admission", + ReadOnly: true, + PathType: corev1.HostPathDirectoryOrCreate, + } + clusterConfig.APIServer.ExtraVolumes = append(clusterConfig.APIServer.ExtraVolumes, admissionVol) + } + args := kubeadmargs.NewFrom(clusterConfig.APIServer.ExtraArgs) features.UpdateKubeadmClusterConfiguration(cluster.Features, args)