diff --git a/cmd/machine-config-operator/start.go b/cmd/machine-config-operator/start.go index a045b35239..1cdd528cbe 100644 --- a/cmd/machine-config-operator/start.go +++ b/cmd/machine-config-operator/start.go @@ -76,6 +76,7 @@ func runStartCmd(cmd *cobra.Command, args []string) { ctrlctx.ClientBuilder.ConfigClientOrDie(componentName), ctrlctx.OpenShiftKubeAPIServerKubeNamespacedInformerFactory.Core().V1().ConfigMaps(), ctrlctx.KubeInformerFactory.Core().V1().Nodes(), + ctrlctx.KubeMAOSharedInformer.Core().V1().Secrets(), ) ctrlctx.NamespacedInformerFactory.Start(ctrlctx.Stop) @@ -85,6 +86,7 @@ func runStartCmd(cmd *cobra.Command, args []string) { ctrlctx.ConfigInformerFactory.Start(ctrlctx.Stop) ctrlctx.OpenShiftKubeAPIServerKubeNamespacedInformerFactory.Start(ctrlctx.Stop) ctrlctx.OperatorInformerFactory.Start(ctrlctx.Stop) + ctrlctx.KubeMAOSharedInformer.Start(ctrlctx.Stop) close(ctrlctx.InformersStarted) go controller.Run(2, ctrlctx.Stop) diff --git a/manifests/userdata_secret.yaml b/manifests/userdata_secret.yaml new file mode 100644 index 0000000000..4256a85bfb --- /dev/null +++ b/manifests/userdata_secret.yaml @@ -0,0 +1,9 @@ +apiVersion: v1 +kind: Secret +metadata: + name: {{.Role}}-user-data-managed + namespace: openshift-machine-api +type: Opaque +data: + disableTemplating: "dHJ1ZQo=" + userData: {{.PointerConfig}} diff --git a/pkg/controller/common/controller_context.go b/pkg/controller/common/controller_context.go index 07e12f00aa..7a49d502e5 100644 --- a/pkg/controller/common/controller_context.go +++ b/pkg/controller/common/controller_context.go @@ -45,6 +45,7 @@ type ControllerContext struct { APIExtInformerFactory apiextinformers.SharedInformerFactory ConfigInformerFactory configinformers.SharedInformerFactory OperatorInformerFactory operatorinformers.SharedInformerFactory + KubeMAOSharedInformer informers.SharedInformerFactory AvailableResources map[schema.GroupVersionResource]bool @@ -74,6 +75,8 @@ func CreateControllerContext(cb *clients.Builder, stop <-chan struct{}, targetNa opt.FieldSelector = fields.OneTermEqualSelector("metadata.name", "kube-apiserver-to-kubelet-client-ca").String() }, ) + // this is needed to listen for changes in MAO user data secrets to re-apply the ones we define in the MCO (since we manage them) + kubeMAOSharedInformer := informers.NewFilteredSharedInformerFactory(kubeClient, resyncPeriod()(), "openshift-machine-api", nil) // filter out CRDs that do not have the MCO label assignFilterLabels := func(opts *metav1.ListOptions) { @@ -103,5 +106,6 @@ func CreateControllerContext(cb *clients.Builder, stop <-chan struct{}, targetNa Stop: stop, InformersStarted: make(chan struct{}), ResyncPeriod: resyncPeriod(), + KubeMAOSharedInformer: kubeMAOSharedInformer, } } diff --git a/pkg/controller/common/helpers.go b/pkg/controller/common/helpers.go index dc29d7fb02..3272086b4e 100644 --- a/pkg/controller/common/helpers.go +++ b/pkg/controller/common/helpers.go @@ -4,6 +4,7 @@ import ( "context" "fmt" "io/ioutil" + "net/url" "reflect" "sort" @@ -29,6 +30,7 @@ import ( "github.com/ghodss/yaml" "github.com/golang/glog" "github.com/pkg/errors" + "github.com/vincent-petithory/dataurl" kerr "k8s.io/apimachinery/pkg/api/errors" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/runtime" @@ -131,6 +133,40 @@ func MergeMachineConfigs(configs []*mcfgv1.MachineConfig, osImageURL string) (*m }, nil } +// PointerConfig generates the stub ignition for the machine to boot properly +// NOTE: If you change this, you also need to change the pointer configuration in openshift/installer, see +// https://github.com/openshift/installer/blob/master/pkg/asset/ignition/machine/node.go#L20 +func PointerConfig(ignitionHost string, rootCA []byte) (ign3types.Config, error) { + configSourceURL := &url.URL{ + Scheme: "https", + Host: ignitionHost, + Path: "/config/{{.Role}}", + } + // we do decoding here as curly brackets are escaped to %7B and breaks golang's templates + ignitionHostTmpl, err := url.QueryUnescape(configSourceURL.String()) + if err != nil { + return ign3types.Config{}, err + } + CASource := dataurl.EncodeBytes(rootCA) + return ign3types.Config{ + Ignition: ign3types.Ignition{ + Version: ign3types.MaxVersion.String(), + Config: ign3types.IgnitionConfig{ + Merge: []ign3types.Resource{{ + Source: &ignitionHostTmpl, + }}, + }, + Security: ign3types.Security{ + TLS: ign3types.TLS{ + CertificateAuthorities: []ign3types.Resource{{ + Source: &CASource, + }}, + }, + }, + }, + }, nil +} + // NewIgnConfig returns an empty ignition config with version set as latest version func NewIgnConfig() ign3types.Config { return ign3types.Config{ diff --git a/pkg/operator/assets/bindata.go b/pkg/operator/assets/bindata.go index 38e7572eb3..89c9b1fc9c 100644 --- a/pkg/operator/assets/bindata.go +++ b/pkg/operator/assets/bindata.go @@ -32,6 +32,7 @@ // manifests/on-prem/coredns.yaml // manifests/on-prem/keepalived.conf.tmpl // manifests/on-prem/keepalived.yaml +// manifests/userdata_secret.yaml // manifests/worker.machineconfigpool.yaml package assets @@ -43,6 +44,7 @@ import ( "strings" "time" ) + type asset struct { bytes []byte info os.FileInfo @@ -259,8 +261,8 @@ spec: baseDomain: description: "baseDomain is the base domain of the cluster. All managed DNS records will be sub-domains of this base. - \n For example, given the base domain `+"`"+`openshift.example.com`+"`"+`, - an API server DNS record may be created for `+"`"+`cluster-api.openshift.example.com`+"`"+`. + \n For example, given the base domain ` + "`" + `openshift.example.com` + "`" + `, + an API server DNS record may be created for ` + "`" + `cluster-api.openshift.example.com` + "`" + `. \n Once set, this field cannot be changed." type: string privateZone: @@ -272,9 +274,9 @@ spec: id: description: "id is the identifier that can be used to find the DNS hosted zone. \n on AWS zone can be fetched - using `+"`"+`ID`+"`"+` as id in [1] on Azure zone can be fetched - using `+"`"+`ID`+"`"+` as a pre-determined name in [2], on GCP zone - can be fetched using `+"`"+`ID`+"`"+` as a pre-determined name in + using ` + "`" + `ID` + "`" + ` as id in [1] on Azure zone can be fetched + using ` + "`" + `ID` + "`" + ` as a pre-determined name in [2], on GCP zone + can be fetched using ` + "`" + `ID` + "`" + ` as a pre-determined name in [3]. \n [1]: https://docs.aws.amazon.com/cli/latest/reference/route53/get-hosted-zone.html#options [2]: https://docs.microsoft.com/en-us/cli/azure/network/dns/zone?view=azure-cli-latest#az-network-dns-zone-show [3]: https://cloud.google.com/dns/docs/reference/v1/managedZones/get" @@ -284,7 +286,7 @@ spec: type: string description: "tags can be used to query the DNS hosted zone. \n on AWS, resourcegroupstaggingapi [1] can be - used to fetch a zone using `+"`"+`Tags`+"`"+` as tag-filters, \n + used to fetch a zone using ` + "`" + `Tags` + "`" + ` as tag-filters, \n [1]: https://docs.aws.amazon.com/cli/latest/reference/resourcegroupstaggingapi/get-resources.html#options" type: object type: object @@ -297,9 +299,9 @@ spec: id: description: "id is the identifier that can be used to find the DNS hosted zone. \n on AWS zone can be fetched - using `+"`"+`ID`+"`"+` as id in [1] on Azure zone can be fetched - using `+"`"+`ID`+"`"+` as a pre-determined name in [2], on GCP zone - can be fetched using `+"`"+`ID`+"`"+` as a pre-determined name in + using ` + "`" + `ID` + "`" + ` as id in [1] on Azure zone can be fetched + using ` + "`" + `ID` + "`" + ` as a pre-determined name in [2], on GCP zone + can be fetched using ` + "`" + `ID` + "`" + ` as a pre-determined name in [3]. \n [1]: https://docs.aws.amazon.com/cli/latest/reference/route53/get-hosted-zone.html#options [2]: https://docs.microsoft.com/en-us/cli/azure/network/dns/zone?view=azure-cli-latest#az-network-dns-zone-show [3]: https://cloud.google.com/dns/docs/reference/v1/managedZones/get" @@ -309,7 +311,7 @@ spec: type: string description: "tags can be used to query the DNS hosted zone. \n on AWS, resourcegroupstaggingapi [1] can be - used to fetch a zone using `+"`"+`Tags`+"`"+` as tag-filters, \n + used to fetch a zone using ` + "`" + `Tags` + "`" + ` as tag-filters, \n [1]: https://docs.aws.amazon.com/cli/latest/reference/resourcegroupstaggingapi/get-resources.html#options" type: object type: object @@ -364,8 +366,8 @@ spec: in the spec for various platforms and combining that with the user provided ConfigMap in this field to create a stitched kube cloud config. The controller generates a ConfigMap - `+"`"+`kube-cloud-config`+"`"+` in `+"`"+`openshift-config-managed`+"`"+` namespace - with the kube cloud config is stored in `+"`"+`cloud.conf`+"`"+` key. + ` + "`" + `kube-cloud-config` + "`" + ` in ` + "`" + `openshift-config-managed` + "`" + ` namespace + with the kube cloud config is stored in ` + "`" + `cloud.conf` + "`" + ` key. All the clients are expected to use the generated ConfigMap only." properties: @@ -532,8 +534,8 @@ spec: default: HighlyAvailable description: infrastructureTopology expresses the expectations for infrastructure services that do not run on control plane - nodes, usually indicated by a node selector for a `+"`"+`role`+"`"+` - value other than `+"`"+`master`+"`"+`. The default is 'HighlyAvailable', + nodes, usually indicated by a node selector for a ` + "`" + `role` + "`" + ` + value other than ` + "`" + `master` + "`" + `. The default is 'HighlyAvailable', which represents the behavior operators have in a "normal" cluster. The 'SingleReplica' mode will be used in single-node deployments and the operators should not configure the operand @@ -688,7 +690,7 @@ spec: description: cloudName is the name of the Azure cloud environment which can be used to configure the Azure SDK with the appropriate Azure API endpoints. If - empty, the value is equal to `+"`"+`AzurePublicCloud`+"`"+`. + empty, the value is equal to ` + "`" + `AzurePublicCloud` + "`" + `. enum: - "" - AzurePublicCloud @@ -730,7 +732,7 @@ spec: nodeDNSIP: description: nodeDNSIP is the IP address for the internal DNS used by the nodes. Unlike the one managed by - the DNS operator, `+"`"+`NodeDNSIP`+"`"+` provides name resolution + the DNS operator, ` + "`" + `NodeDNSIP` + "`" + ` provides name resolution for the nodes themselves. There is no DNS-as-a-service for BareMetal deployments. In order to minimize necessary changes to the datacenter DNS, a DNS service @@ -829,7 +831,7 @@ spec: cloudName: description: cloudName is the name of the desired OpenStack cloud in the client configuration file - (`+"`"+`clouds.yaml`+"`"+`). + (` + "`" + `clouds.yaml` + "`" + `). type: string ingressIP: description: ingressIP is an external IP which routes @@ -840,7 +842,7 @@ spec: nodeDNSIP: description: nodeDNSIP is the IP address for the internal DNS used by the nodes. Unlike the one managed by - the DNS operator, `+"`"+`NodeDNSIP`+"`"+` provides name resolution + the DNS operator, ` + "`" + `NodeDNSIP` + "`" + ` provides name resolution for the nodes themselves. There is no DNS-as-a-service for OpenStack deployments. In order to minimize necessary changes to the datacenter DNS, a DNS service @@ -885,7 +887,7 @@ spec: \"None\". Individual components may not support all platforms, and must handle unrecognized platforms as None if they do not support that platform. \n This value will be - synced with to the `+"`"+`status.platform`+"`"+` and `+"`"+`status.platformStatus.type`+"`"+`. + synced with to the ` + "`" + `status.platform` + "`" + ` and ` + "`" + `status.platformStatus.type` + "`" + `. Currently this value cannot be changed once set." enum: - "" @@ -925,7 +927,7 @@ spec: nodeDNSIP: description: nodeDNSIP is the IP address for the internal DNS used by the nodes. Unlike the one managed by - the DNS operator, `+"`"+`NodeDNSIP`+"`"+` provides name resolution + the DNS operator, ` + "`" + `NodeDNSIP` + "`" + ` provides name resolution for the nodes themselves. There is no DNS-as-a-service for vSphere deployments. In order to minimize necessary changes to the datacenter DNS, a DNS service is @@ -1768,7 +1770,7 @@ func manifestsMachineconfigserverClusterrolebindingYaml() (*asset, error) { return a, nil } -var _manifestsMachineconfigserverCsrBootstrapRoleBindingYaml = []byte(`# system-bootstrap-node-bootstrapper lets serviceaccount `+"`"+`openshift-machine-config-operator/node-bootstrapper`+"`"+` tokens and nodes request CSRs. +var _manifestsMachineconfigserverCsrBootstrapRoleBindingYaml = []byte(`# system-bootstrap-node-bootstrapper lets serviceaccount ` + "`" + `openshift-machine-config-operator/node-bootstrapper` + "`" + ` tokens and nodes request CSRs. apiVersion: rbac.authorization.k8s.io/v1 kind: ClusterRoleBinding metadata: @@ -1802,7 +1804,7 @@ var _manifestsMachineconfigserverCsrRenewalRoleBindingYaml = []byte(`# CSRRenewa # certificates. # # This binding should be altered in the future to hold a list of node -# names instead of targeting `+"`"+`system:nodes`+"`"+` so we can revoke invidivual +# names instead of targeting ` + "`" + `system:nodes` + "`" + ` so we can revoke invidivual # node's ability to renew its certs. apiVersion: rbac.authorization.k8s.io/v1 kind: ClusterRoleBinding @@ -2030,35 +2032,35 @@ func manifestsMasterMachineconfigpoolYaml() (*asset, error) { var _manifestsOnPremCorednsCorefileTmpl = []byte(`. { errors health :18080 - forward . {{`+"`"+`{{- range $upstream := .DNSUpstreams}} {{$upstream}}{{- end}}`+"`"+`}} { + forward . {{` + "`" + `{{- range $upstream := .DNSUpstreams}} {{$upstream}}{{- end}}` + "`" + `}} { policy sequential } cache 30 reload - template IN {{`+"`"+`{{ .Cluster.IngressVIPRecordType }}`+"`"+`}} {{ .ControllerConfig.DNS.Spec.BaseDomain }} { + template IN {{` + "`" + `{{ .Cluster.IngressVIPRecordType }}` + "`" + `}} {{ .ControllerConfig.DNS.Spec.BaseDomain }} { match .*.apps.{{ .ControllerConfig.DNS.Spec.BaseDomain }} - answer "{{`+"`"+`{{"{{ .Name }}"}}`+"`"+`}} 60 in {{`+"`"+`{{"{{ .Type }}"}}`+"`"+`}} {{ onPremPlatformIngressIP .ControllerConfig }}" + answer "{{` + "`" + `{{"{{ .Name }}"}}` + "`" + `}} 60 in {{` + "`" + `{{"{{ .Type }}"}}` + "`" + `}} {{ onPremPlatformIngressIP .ControllerConfig }}" fallthrough } - template IN {{`+"`"+`{{ .Cluster.IngressVIPEmptyType }}`+"`"+`}} {{ .ControllerConfig.DNS.Spec.BaseDomain }} { + template IN {{` + "`" + `{{ .Cluster.IngressVIPEmptyType }}` + "`" + `}} {{ .ControllerConfig.DNS.Spec.BaseDomain }} { match .*.apps.{{ .ControllerConfig.DNS.Spec.BaseDomain }} fallthrough } - template IN {{`+"`"+`{{ .Cluster.APIVIPRecordType }}`+"`"+`}} {{ .ControllerConfig.DNS.Spec.BaseDomain }} { + template IN {{` + "`" + `{{ .Cluster.APIVIPRecordType }}` + "`" + `}} {{ .ControllerConfig.DNS.Spec.BaseDomain }} { match api.{{ .ControllerConfig.DNS.Spec.BaseDomain }} - answer "{{`+"`"+`{{"{{ .Name }}"}}`+"`"+`}} 60 in {{`+"`"+`{{"{{ .Type }}"}}`+"`"+`}} {{ onPremPlatformAPIServerInternalIP .ControllerConfig }}" + answer "{{` + "`" + `{{"{{ .Name }}"}}` + "`" + `}} 60 in {{` + "`" + `{{"{{ .Type }}"}}` + "`" + `}} {{ onPremPlatformAPIServerInternalIP .ControllerConfig }}" fallthrough } - template IN {{`+"`"+`{{ .Cluster.APIVIPEmptyType }}`+"`"+`}} {{ .ControllerConfig.DNS.Spec.BaseDomain }} { + template IN {{` + "`" + `{{ .Cluster.APIVIPEmptyType }}` + "`" + `}} {{ .ControllerConfig.DNS.Spec.BaseDomain }} { match api.{{ .ControllerConfig.DNS.Spec.BaseDomain }} fallthrough } - template IN {{`+"`"+`{{ .Cluster.APIVIPRecordType }}`+"`"+`}} {{ .ControllerConfig.DNS.Spec.BaseDomain }} { + template IN {{` + "`" + `{{ .Cluster.APIVIPRecordType }}` + "`" + `}} {{ .ControllerConfig.DNS.Spec.BaseDomain }} { match api-int.{{ .ControllerConfig.DNS.Spec.BaseDomain }} - answer "{{`+"`"+`{{"{{ .Name }}"}}`+"`"+`}} 60 in {{`+"`"+`{{"{{ .Type }}"}}`+"`"+`}} {{ onPremPlatformAPIServerInternalIP .ControllerConfig }}" + answer "{{` + "`" + `{{"{{ .Name }}"}}` + "`" + `}} 60 in {{` + "`" + `{{"{{ .Type }}"}}` + "`" + `}} {{ onPremPlatformAPIServerInternalIP .ControllerConfig }}" fallthrough } - template IN {{`+"`"+`{{ .Cluster.APIVIPEmptyType }}`+"`"+`}} {{ .ControllerConfig.DNS.Spec.BaseDomain }} { + template IN {{` + "`" + `{{ .Cluster.APIVIPEmptyType }}` + "`" + `}} {{ .ControllerConfig.DNS.Spec.BaseDomain }} { match api-int.{{ .ControllerConfig.DNS.Spec.BaseDomain }} fallthrough } @@ -2185,7 +2187,7 @@ var _manifestsOnPremKeepalivedConfTmpl = []byte(`# Configuration template for Ke # For more information, see installer/data/data/bootstrap/baremetal/README.md # in the installer repo. -{{`+"`"+`vrrp_instance {{.Cluster.Name}}_API { +{{` + "`" + `vrrp_instance {{.Cluster.Name}}_API { state BACKUP interface {{.VRRPInterface}} virtual_router_id {{.Cluster.APIVirtualRouterID }} @@ -2206,7 +2208,7 @@ var _manifestsOnPremKeepalivedConfTmpl = []byte(`# Configuration template for Ke virtual_ipaddress { {{ .Cluster.APIVIP }}/{{ .Cluster.VIPNetmask }} } -}`+"`"+`}} +}` + "`" + `}} `) func manifestsOnPremKeepalivedConfTmplBytes() ([]byte, error) { @@ -2387,6 +2389,32 @@ func manifestsOnPremKeepalivedYaml() (*asset, error) { return a, nil } +var _manifestsUserdata_secretYaml = []byte(`apiVersion: v1 +kind: Secret +metadata: + name: {{.Role}}-user-data-managed + namespace: openshift-machine-api +type: Opaque +data: + disableTemplating: "dHJ1ZQo=" + userData: {{.PointerConfig}} +`) + +func manifestsUserdata_secretYamlBytes() ([]byte, error) { + return _manifestsUserdata_secretYaml, nil +} + +func manifestsUserdata_secretYaml() (*asset, error) { + bytes, err := manifestsUserdata_secretYamlBytes() + if err != nil { + return nil, err + } + + info := bindataFileInfo{name: "manifests/userdata_secret.yaml", size: 0, mode: os.FileMode(0), modTime: time.Unix(0, 0)} + a := &asset{bytes: bytes, info: info} + return a, nil +} + var _manifestsWorkerMachineconfigpoolYaml = []byte(`apiVersion: machineconfiguration.openshift.io/v1 kind: MachineConfigPool metadata: @@ -2501,6 +2529,7 @@ var _bindata = map[string]func() (*asset, error){ "manifests/on-prem/coredns.yaml": manifestsOnPremCorednsYaml, "manifests/on-prem/keepalived.conf.tmpl": manifestsOnPremKeepalivedConfTmpl, "manifests/on-prem/keepalived.yaml": manifestsOnPremKeepalivedYaml, + "manifests/userdata_secret.yaml": manifestsUserdata_secretYaml, "manifests/worker.machineconfigpool.yaml": manifestsWorkerMachineconfigpoolYaml, } @@ -2586,6 +2615,7 @@ var _bintree = &bintree{nil, map[string]*bintree{ "keepalived.conf.tmpl": &bintree{manifestsOnPremKeepalivedConfTmpl, map[string]*bintree{}}, "keepalived.yaml": &bintree{manifestsOnPremKeepalivedYaml, map[string]*bintree{}}, }}, + "userdata_secret.yaml": &bintree{manifestsUserdata_secretYaml, map[string]*bintree{}}, "worker.machineconfigpool.yaml": &bintree{manifestsWorkerMachineconfigpoolYaml, map[string]*bintree{}}, }}, }} diff --git a/pkg/operator/bootstrap.go b/pkg/operator/bootstrap.go index 09f5f6a19c..c856fd134b 100644 --- a/pkg/operator/bootstrap.go +++ b/pkg/operator/bootstrap.go @@ -151,7 +151,7 @@ func RenderBootstrap( templatectrl.BaremetalRuntimeCfgKey: imgs.BaremetalRuntimeCfg, } - config := getRenderConfig("", string(filesData[kubeAPIServerServingCA]), spec, &imgs.RenderConfigImages, infra.Status.APIServerInternalURL) + config := getRenderConfig("", string(filesData[kubeAPIServerServingCA]), spec, &imgs.RenderConfigImages, infra.Status.APIServerInternalURL, nil) manifests := []manifest{ { diff --git a/pkg/operator/operator.go b/pkg/operator/operator.go index 6f2cbf0153..4cd6b6262b 100644 --- a/pkg/operator/operator.go +++ b/pkg/operator/operator.go @@ -101,6 +101,7 @@ type Operator struct { oseKubeAPIListerSynced cache.InformerSynced nodeListerSynced cache.InformerSynced dnsListerSynced cache.InformerSynced + maoSecretInformerSynced cache.InformerSynced // queue only ever has one item, but it has nice error handling backoff/retry semantics queue workqueue.RateLimitingInterface @@ -134,6 +135,7 @@ func New( configClient configclientset.Interface, oseKubeAPIInformer coreinformersv1.ConfigMapInformer, nodeInformer coreinformersv1.NodeInformer, + maoSecretInformer coreinformersv1.SecretInformer, ) *Operator { eventBroadcaster := record.NewBroadcaster() eventBroadcaster.StartLogging(glog.Infof) @@ -168,6 +170,7 @@ func New( oseKubeAPIInformer.Informer(), nodeInformer.Informer(), dnsInformer.Informer(), + maoSecretInformer.Informer(), } { i.AddEventHandler(optr.eventHandler()) } @@ -189,6 +192,7 @@ func New( optr.nodeLister = nodeInformer.Lister() optr.nodeListerSynced = nodeInformer.Informer().HasSynced + optr.maoSecretInformerSynced = maoSecretInformer.Informer().HasSynced optr.serviceAccountInformerSynced = serviceAccountInfomer.Informer().HasSynced optr.clusterRoleInformerSynced = clusterRoleInformer.Informer().HasSynced optr.clusterRoleBindingInformerSynced = clusterRoleBindingInformer.Informer().HasSynced @@ -237,6 +241,7 @@ func (optr *Operator) Run(workers int, stopCh <-chan struct{}) { optr.clusterCmListerSynced, optr.serviceAccountInformerSynced, optr.clusterRoleInformerSynced, + optr.maoSecretInformerSynced, optr.clusterRoleBindingInformerSynced, optr.networkListerSynced, optr.proxyListerSynced, diff --git a/pkg/operator/render.go b/pkg/operator/render.go index cc8b33a8d1..1b0c51761b 100644 --- a/pkg/operator/render.go +++ b/pkg/operator/render.go @@ -31,14 +31,32 @@ type renderConfig struct { KubeAPIServerServingCA string Infra configv1.Infrastructure Constants map[string]string + PointerConfig string } -func renderAsset(config *renderConfig, path string) ([]byte, error) { - objBytes, err := manifests.ReadFile(path) +type assetRenderer struct { + Path string + tmpl *template.Template + templateData string +} + +func newAssetRenderer(path string) *assetRenderer { + return &assetRenderer{ + Path: path, + tmpl: template.New(path), + } +} + +func (a *assetRenderer) read() error { + objBytes, err := manifests.ReadFile(a.Path) if err != nil { - return nil, fmt.Errorf("error getting asset %s: %v", path, err) + return fmt.Errorf("error getting asset %s: %v", a.Path, err) } + a.templateData = string(objBytes) + return nil +} +func (a *assetRenderer) addTemplateFuncs() { funcs := sprig.TxtFuncMap() funcs["toYAML"] = toYAML funcs["onPremPlatformAPIServerInternalIP"] = onPremPlatformAPIServerInternalIP @@ -46,13 +64,13 @@ func renderAsset(config *renderConfig, path string) ([]byte, error) { funcs["onPremPlatformShortName"] = onPremPlatformShortName funcs["onPremPlatformKeepalivedEnableUnicast"] = onPremPlatformKeepalivedEnableUnicast - if config.Constants == nil { - config.Constants = constants.ConstantsByName - } + a.tmpl = a.tmpl.Funcs(funcs) +} - tmpl, err := template.New(path).Funcs(funcs).Parse(string(objBytes)) +func (a *assetRenderer) render(config interface{}) ([]byte, error) { + tmpl, err := a.tmpl.Parse(a.templateData) if err != nil { - return nil, fmt.Errorf("failed to parse asset %s: %v", path, err) + return nil, fmt.Errorf("failed to parse asset %s: %v", a.Path, err) } buf := new(bytes.Buffer) @@ -63,6 +81,21 @@ func renderAsset(config *renderConfig, path string) ([]byte, error) { return buf.Bytes(), nil } +func renderAsset(config *renderConfig, path string) ([]byte, error) { + asset := newAssetRenderer(path) + if err := asset.read(); err != nil { + return nil, err + } + + if config.Constants == nil { + config.Constants = constants.ConstantsByName + } + + asset.addTemplateFuncs() + + return asset.render(config) +} + func toYAML(i interface{}) []byte { out, err := yaml.Marshal(i) if err != nil { diff --git a/pkg/operator/sync.go b/pkg/operator/sync.go index 77f03722f7..f2e2d77401 100644 --- a/pkg/operator/sync.go +++ b/pkg/operator/sync.go @@ -8,6 +8,9 @@ import ( "encoding/pem" "fmt" "io/ioutil" + "net" + "net/url" + "strconv" "strings" "time" @@ -29,8 +32,10 @@ import ( "github.com/openshift/machine-config-operator/lib/resourceread" "github.com/openshift/machine-config-operator/manifests" mcfgv1 "github.com/openshift/machine-config-operator/pkg/apis/machineconfiguration.openshift.io/v1" + ctrlcommon "github.com/openshift/machine-config-operator/pkg/controller/common" templatectrl "github.com/openshift/machine-config-operator/pkg/controller/template" daemonconsts "github.com/openshift/machine-config-operator/pkg/daemon/constants" + "github.com/openshift/machine-config-operator/pkg/server" "github.com/openshift/machine-config-operator/pkg/version" ) @@ -274,11 +279,51 @@ func (optr *Operator) syncRenderConfig(_ *renderConfig) error { templatectrl.BaremetalRuntimeCfgKey: imgs.BaremetalRuntimeCfg, } + ignitionHost, err := getIgnitionHost(&infra.Status) + if err != nil { + return err + } + + pointerConfig, err := ctrlcommon.PointerConfig(ignitionHost, rootCA) + if err != nil { + return err + } + pointerConfigData, err := json.Marshal(pointerConfig) + if err != nil { + return err + } + // create renderConfig - optr.renderConfig = getRenderConfig(optr.namespace, string(kubeAPIServerServingCABytes), spec, &imgs.RenderConfigImages, infra.Status.APIServerInternalURL) + optr.renderConfig = getRenderConfig(optr.namespace, string(kubeAPIServerServingCABytes), spec, &imgs.RenderConfigImages, infra.Status.APIServerInternalURL, pointerConfigData) return nil } +func getIgnitionHost(infraStatus *configv1.InfrastructureStatus) (string, error) { + internalURL := infraStatus.APIServerInternalURL + internalURLParsed, err := url.Parse(internalURL) + if err != nil { + return "", err + } + securePortStr := strconv.Itoa(server.SecurePort) + ignitionHost := fmt.Sprintf("%s:%s", internalURLParsed.Hostname(), securePortStr) + if infraStatus.PlatformStatus != nil { + switch infraStatus.PlatformStatus.Type { + case configv1.BareMetalPlatformType: + ignitionHost = net.JoinHostPort(infraStatus.PlatformStatus.BareMetal.APIServerInternalIP, securePortStr) + case configv1.OpenStackPlatformType: + ignitionHost = net.JoinHostPort(infraStatus.PlatformStatus.OpenStack.APIServerInternalIP, securePortStr) + case configv1.OvirtPlatformType: + ignitionHost = net.JoinHostPort(infraStatus.PlatformStatus.Ovirt.APIServerInternalIP, securePortStr) + case configv1.VSpherePlatformType: + if infraStatus.PlatformStatus.VSphere.APIServerInternalIP != "" { + ignitionHost = net.JoinHostPort(infraStatus.PlatformStatus.VSphere.APIServerInternalIP, securePortStr) + } + } + } + + return ignitionHost, nil +} + func (optr *Operator) syncCustomResourceDefinitions() error { crds := []string{ "manifests/controllerconfig.crd.yaml", @@ -322,6 +367,39 @@ func (optr *Operator) syncMachineConfigPools(config *renderConfig) error { } } + userDataTemplatePath := "manifests/userdata_secret.yaml" + pools, err := optr.mcpLister.List(labels.Everything()) + if err != nil { + return err + } + // base64.StdEncoding.EncodeToString + for _, pool := range pools { + pointerConfigAsset := newAssetRenderer("pointer-config") + pointerConfigAsset.templateData = config.PointerConfig + pointerConfigData, err := pointerConfigAsset.render(struct{ Role string }{pool.Name}) + if err != nil { + return err + } + + userDataAsset := newAssetRenderer(userDataTemplatePath) + if err := userDataAsset.read(); err != nil { + return err + } + userDataAsset.addTemplateFuncs() + userdataBytes, err := userDataAsset.render(struct{ Role, PointerConfig string }{ + pool.Name, + base64.StdEncoding.EncodeToString(pointerConfigData), + }) + if err != nil { + return err + } + p := resourceread.ReadSecretV1OrDie(userdataBytes) + _, _, err = resourceapply.ApplySecret(optr.kubeClient.CoreV1(), p) + if err != nil { + return err + } + } + return nil } @@ -840,7 +918,7 @@ func (optr *Operator) getGlobalConfig() (*configv1.Infrastructure, *configv1.Net return infra, network, proxy, dns, nil } -func getRenderConfig(tnamespace, kubeAPIServerServingCA string, ccSpec *mcfgv1.ControllerConfigSpec, imgs *RenderConfigImages, apiServerURL string) *renderConfig { +func getRenderConfig(tnamespace, kubeAPIServerServingCA string, ccSpec *mcfgv1.ControllerConfigSpec, imgs *RenderConfigImages, apiServerURL string, pointerConfigData []byte) *renderConfig { return &renderConfig{ TargetNamespace: tnamespace, Version: version.Raw, @@ -848,6 +926,7 @@ func getRenderConfig(tnamespace, kubeAPIServerServingCA string, ccSpec *mcfgv1.C Images: imgs, APIServerURL: apiServerURL, KubeAPIServerServingCA: kubeAPIServerServingCA, + PointerConfig: string(pointerConfigData), } }