Skip to content

Commit b610b77

Browse files
Merge pull request #1098 from bshephar/prototype-icsp
Use OCP registries.conf for disconnected deployments
2 parents 156d0df + 99d5670 commit b610b77

9 files changed

Lines changed: 328 additions & 16 deletions

File tree

apis/go.mod

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -38,11 +38,10 @@ require (
3838
k8s.io/api v0.29.9
3939
k8s.io/apimachinery v0.29.9
4040
k8s.io/client-go v0.29.9
41+
k8s.io/utils v0.0.0-20240711033017-18e509b52bc8
4142
sigs.k8s.io/controller-runtime v0.17.6
4243
)
4344

44-
require k8s.io/utils v0.0.0-20240711033017-18e509b52bc8
45-
4645
require (
4746
github.com/beorn7/perks v1.0.1 // indirect
4847
github.com/cespare/xxhash/v2 v2.2.0 // indirect

config/rbac/role.yaml

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -499,6 +499,14 @@ rules:
499499
- patch
500500
- update
501501
- watch
502+
- apiGroups:
503+
- machineconfiguration.openshift.io
504+
resources:
505+
- machineconfigs
506+
verbs:
507+
- get
508+
- list
509+
- watch
502510
- apiGroups:
503511
- manila.openstack.org
504512
resources:
@@ -647,6 +655,13 @@ rules:
647655
- patch
648656
- update
649657
- watch
658+
- apiGroups:
659+
- operator.openshift.io
660+
resources:
661+
- imagecontentsourcepolicies
662+
verbs:
663+
- get
664+
- list
650665
- apiGroups:
651666
- ovn.openstack.org
652667
resources:

controllers/dataplane/openstackdataplanenodeset_controller.go

Lines changed: 53 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,8 @@ import (
5454
dataplanev1 "github.com/openstack-k8s-operators/openstack-operator/apis/dataplane/v1beta1"
5555
deployment "github.com/openstack-k8s-operators/openstack-operator/pkg/dataplane"
5656
dataplaneutil "github.com/openstack-k8s-operators/openstack-operator/pkg/dataplane/util"
57+
58+
machineconfig "github.com/openshift/api/machineconfiguration/v1"
5759
)
5860

5961
const (
@@ -120,6 +122,10 @@ func (r *OpenStackDataPlaneNodeSetReconciler) GetLogger(ctx context.Context) log
120122
// +kubebuilder:rbac:groups="image.openshift.io",resources=imagetags,verbs=get;list;watch
121123
// +kubebuilder:rbac:groups="image.openshift.io",resources=imagestreamtags,verbs=get;list;watch
122124

125+
// RBAC for ImageContentSourcePolicy and MachineConfig
126+
// +kubebuilder:rbac:groups="operator.openshift.io",resources=imagecontentsourcepolicies,verbs=get;list
127+
// +kubebuilder:rbac:groups="machineconfiguration.openshift.io",resources=machineconfigs,verbs=get;list;watch
128+
123129
// Reconcile is part of the main kubernetes reconciliation loop which aims to
124130
// move the current state of the cluster closer to the desired state.
125131
// TODO(user): Modify the Reconcile function to compare the state specified by
@@ -565,7 +571,8 @@ func checkDeployment(ctx context.Context, helper *helper.Helper,
565571

566572
// SetupWithManager sets up the controller with the Manager.
567573
func (r *OpenStackDataPlaneNodeSetReconciler) SetupWithManager(
568-
ctx context.Context, mgr ctrl.Manager) error {
574+
ctx context.Context, mgr ctrl.Manager,
575+
) error {
569576
// index for ConfigMaps listed on ansibleVarsFrom
570577
if err := mgr.GetFieldIndexer().IndexField(ctx,
571578
&dataplanev1.OpenStackDataPlaneNodeSet{}, "spec.ansibleVarsFrom.ansible.configMaps",
@@ -639,9 +646,54 @@ func (r *OpenStackDataPlaneNodeSetReconciler) SetupWithManager(
639646
builder.WithPredicates(predicate.ResourceVersionChangedPredicate{})).
640647
Watches(&openstackv1.OpenStackVersion{},
641648
handler.EnqueueRequestsFromMapFunc(r.genericWatcherFn)).
649+
Watches(&machineconfig.MachineConfig{},
650+
handler.EnqueueRequestsFromMapFunc(r.machineConfigWatcherFn),
651+
builder.WithPredicates(predicate.ResourceVersionChangedPredicate{})).
642652
Complete(r)
643653
}
644654

655+
// machineConfigWatcherFn - watches for changes to the registries MachineConfig resource and queues
656+
// a reconcile of each NodeSet if the MachineConfig is changed.
657+
func (r *OpenStackDataPlaneNodeSetReconciler) machineConfigWatcherFn(
658+
ctx context.Context, obj client.Object,
659+
) []reconcile.Request {
660+
Log := r.GetLogger(ctx)
661+
nodeSets := &dataplanev1.OpenStackDataPlaneNodeSetList{}
662+
kind := strings.ToLower(obj.GetObjectKind().GroupVersionKind().Kind)
663+
const registryMachineConfigName string = "99-master-generated-registries"
664+
665+
mcNamespacedName := types.NamespacedName{
666+
Name: registryMachineConfigName,
667+
Namespace: "",
668+
}
669+
670+
if err := r.Get(ctx, mcNamespacedName, obj); err != nil {
671+
Log.Error(err, fmt.Sprintf("Unable to retrieve MachingConfig %s", registryMachineConfigName))
672+
return nil
673+
}
674+
675+
listOpts := []client.ListOption{
676+
client.InNamespace(obj.GetNamespace()),
677+
}
678+
if err := r.Client.List(ctx, nodeSets, listOpts...); err != nil {
679+
Log.Error(err, "Unable to retrieve OpenStackDataPlaneNodeSetList")
680+
return nil
681+
}
682+
683+
requests := make([]reconcile.Request, 0, len(nodeSets.Items))
684+
for _, nodeSet := range nodeSets.Items {
685+
requests = append(requests, reconcile.Request{
686+
NamespacedName: types.NamespacedName{
687+
Namespace: obj.GetNamespace(),
688+
Name: nodeSet.Name,
689+
},
690+
})
691+
Log.Info(fmt.Sprintf("reconcile loop for openstackdataplanenodeset %s triggered by %s %s",
692+
nodeSet.Name, kind, obj.GetName()))
693+
}
694+
return requests
695+
}
696+
645697
func (r *OpenStackDataPlaneNodeSetReconciler) secretWatcherFn(
646698
ctx context.Context, obj client.Object,
647699
) []reconcile.Request {

main.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,8 @@ import (
7777
dataplanev1 "github.com/openstack-k8s-operators/openstack-operator/apis/dataplane/v1beta1"
7878

7979
ocp_configv1 "github.com/openshift/api/config/v1"
80+
machineconfig "github.com/openshift/api/machineconfiguration/v1"
81+
ocp_image "github.com/openshift/api/operator/v1alpha1"
8082
clientcontrollers "github.com/openstack-k8s-operators/openstack-operator/controllers/client"
8183
corecontrollers "github.com/openstack-k8s-operators/openstack-operator/controllers/core"
8284
dataplanecontrollers "github.com/openstack-k8s-operators/openstack-operator/controllers/dataplane"
@@ -120,6 +122,8 @@ func init() {
120122
utilruntime.Must(certmgrv1.AddToScheme(scheme))
121123
utilruntime.Must(barbicanv1.AddToScheme(scheme))
122124
utilruntime.Must(ocp_configv1.AddToScheme(scheme))
125+
utilruntime.Must(ocp_image.AddToScheme(scheme))
126+
utilruntime.Must(machineconfig.AddToScheme(scheme))
123127
// +kubebuilder:scaffold:scheme
124128
}
125129

pkg/dataplane/inventory.go

Lines changed: 30 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -38,8 +38,7 @@ import (
3838

3939
// getAnsibleVarsFrom gets ansible vars from ConfigMap/Secret
4040
func getAnsibleVarsFrom(ctx context.Context, helper *helper.Helper, namespace string, ansible *dataplanev1.AnsibleOpts) (map[string]interface{}, error) {
41-
42-
var result = make(map[string]interface{})
41+
result := make(map[string]interface{})
4342
var value interface{}
4443

4544
for _, dataSource := range ansible.AnsibleVarsFrom {
@@ -90,7 +89,8 @@ func GenerateNodeSetInventory(ctx context.Context, helper *helper.Helper,
9089
instance *dataplanev1.OpenStackDataPlaneNodeSet,
9190
allIPSets map[string]infranetworkv1.IPSet, dnsAddresses []string,
9291
containerImages openstackv1.ContainerImages,
93-
netServiceNetMap map[string]string) (string, error) {
92+
netServiceNetMap map[string]string,
93+
) (string, error) {
9494
inventory := ansible.MakeInventory()
9595
nodeSetGroup := inventory.AddGroup(instance.Name)
9696
groupVars, err := getAnsibleVarsFrom(ctx, helper, instance.Namespace, &instance.Spec.NodeTemplate.Ansible)
@@ -111,6 +111,23 @@ func GenerateNodeSetInventory(ctx context.Context, helper *helper.Helper,
111111
// add the NodeSet name variable
112112
nodeSetGroup.Vars["edpm_nodeset_name"] = instance.Name
113113

114+
isDisconnected, err := util.IsDisconnectedOCP(ctx, helper)
115+
if err != nil {
116+
return "", err
117+
}
118+
119+
if isDisconnected {
120+
registryConfig, err := util.GetMCRegistryConf(ctx, helper)
121+
if err != nil {
122+
return "", err
123+
}
124+
helper.GetLogger().Info("disconnected registry was identified via the ImageContentSourcePolicy. Using OCP registry.")
125+
126+
// Use OCP registries.conf for disconnected deployments
127+
nodeSetGroup.Vars["edpm_podman_registries_conf"] = registryConfig
128+
nodeSetGroup.Vars["edpm_podman_disconnected_ocp"] = isDisconnected
129+
}
130+
114131
// add TLS ansible variable
115132
nodeSetGroup.Vars["edpm_tls_certs_enabled"] = instance.Spec.TLSEnabled
116133
if instance.Spec.Tags != nil {
@@ -205,7 +222,8 @@ func GenerateNodeSetInventory(ctx context.Context, helper *helper.Helper,
205222
// populateInventoryFromIPAM populates inventory from IPAM
206223
func populateInventoryFromIPAM(
207224
ipSet *infranetworkv1.IPSet, host ansible.Host,
208-
dnsAddresses []string, hostName string) {
225+
dnsAddresses []string, hostName string,
226+
) {
209227
var dnsSearchDomains []string
210228
for _, res := range ipSet.Status.Reservation {
211229
// Build the vars for ips/routes etc
@@ -243,8 +261,8 @@ func populateInventoryFromIPAM(
243261
// set group ansible vars from NodeTemplate
244262
func resolveGroupAnsibleVars(template *dataplanev1.NodeTemplate, group *ansible.Group,
245263
containerImages openstackv1.ContainerImages,
246-
netServiceNetMap map[string]string) error {
247-
264+
netServiceNetMap map[string]string,
265+
) error {
248266
if template.Ansible.AnsibleUser != "" {
249267
group.Vars["ansible_user"] = template.Ansible.AnsibleUser
250268
}
@@ -320,8 +338,8 @@ func resolveGroupAnsibleVars(template *dataplanev1.NodeTemplate, group *ansible.
320338

321339
// set host ansible vars from NodeSection
322340
func resolveHostAnsibleVars(node *dataplanev1.NodeSection,
323-
host *ansible.Host, netServiceNetMap map[string]string) error {
324-
341+
host *ansible.Host, netServiceNetMap map[string]string,
342+
) error {
325343
if node.Ansible.AnsibleUser != "" {
326344
host.Vars["ansible_user"] = node.Ansible.AnsibleUser
327345
}
@@ -344,13 +362,12 @@ func resolveHostAnsibleVars(node *dataplanev1.NodeSection,
344362
host.Vars["network_servicenet_map"] = serviceNetMap
345363
}
346364
return nil
347-
348365
}
349366

350367
// unmarshal raw strings into an ansible vars dictionary
351368
func unmarshalAnsibleVars(ansibleVars map[string]json.RawMessage,
352-
parsedVars map[string]interface{}) error {
353-
369+
parsedVars map[string]interface{},
370+
) error {
354371
for key, val := range ansibleVars {
355372
var v interface{}
356373
err := yaml.Unmarshal(val, &v)
@@ -363,7 +380,8 @@ func unmarshalAnsibleVars(ansibleVars map[string]json.RawMessage,
363380
}
364381

365382
func buildNetworkVars(networks []infranetworkv1.IPSetNetwork,
366-
netServiceNetMap map[string]string) ([]string, map[string]string) {
383+
netServiceNetMap map[string]string,
384+
) ([]string, map[string]string) {
367385
serviceNetMap := make(map[string]string)
368386
var nets []string
369387
for _, network := range networks {
Lines changed: 131 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,131 @@
1+
package util
2+
3+
import (
4+
"context"
5+
"encoding/base64"
6+
"encoding/json"
7+
"fmt"
8+
"strings"
9+
10+
mc "github.com/openshift/api/machineconfiguration/v1"
11+
ocpimage "github.com/openshift/api/operator/v1alpha1"
12+
"github.com/openstack-k8s-operators/lib-common/modules/common/helper"
13+
"sigs.k8s.io/controller-runtime/pkg/client"
14+
15+
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
16+
"k8s.io/apimachinery/pkg/types"
17+
)
18+
19+
// machineConfigIgnition - holds the relevant parts of the ignition file that we need to create
20+
// the registries.conf on the dataplane nodes.
21+
type machineConfigIgnition struct {
22+
Ignition struct {
23+
Version string `json:"version"`
24+
} `json:"ignition"`
25+
Storage struct {
26+
Files []struct {
27+
Contents struct {
28+
Compression string `json:"compression,omitempty"`
29+
Source string `json:"source"`
30+
} `json:"contents"`
31+
Mode int `json:"mode,omitempty"`
32+
Overwrite bool `json:"overwrite,omitempty"`
33+
Path string `json:"path"`
34+
} `json:"files"`
35+
} `json:"storage"`
36+
}
37+
38+
// IsDisconnectedOCP - Will retrieve a ImageContentSourcePolicyList. If the list is not
39+
// empty, we can infer that the OCP cluster is a disconnected deployment.
40+
func IsDisconnectedOCP(ctx context.Context, helper *helper.Helper) (bool, error) {
41+
icspList := ocpimage.ImageContentSourcePolicyList{}
42+
43+
listOpts := []client.ListOption{}
44+
err := helper.GetClient().List(ctx, &icspList, listOpts...)
45+
if err != nil {
46+
return false, err
47+
}
48+
49+
if len(icspList.Items) != 0 {
50+
return true, nil
51+
}
52+
53+
return false, nil
54+
}
55+
56+
// GetMCRegistryConf - will unmarshal the MachineConfig ignition file the machineConfigIgnition object.
57+
// This is then parsed and the base64 decoded string is returned.
58+
func GetMCRegistryConf(ctx context.Context, helper *helper.Helper) (string, error) {
59+
var registriesConf string
60+
61+
masterMachineConfig, err := getMachineConfig(ctx, helper)
62+
if err != nil {
63+
return registriesConf, err
64+
}
65+
66+
config := machineConfigIgnition{}
67+
registriesConf, err = config.formatRegistriesConfString(&masterMachineConfig)
68+
if err != nil {
69+
return registriesConf, err
70+
}
71+
72+
return registriesConf, nil
73+
}
74+
75+
func (mci *machineConfigIgnition) removePrefixFromB64String() (string, error) {
76+
const b64Prefix string = "data:text/plain;charset=utf-8;base64,"
77+
if strings.HasPrefix(mci.Storage.Files[0].Contents.Source, b64Prefix) {
78+
return mci.Storage.Files[0].Contents.Source[len(b64Prefix):], nil
79+
}
80+
81+
return "", fmt.Errorf("no b64prefix found in MachineConfig")
82+
}
83+
84+
func (mci *machineConfigIgnition) formatRegistriesConfString(machineConfig *mc.MachineConfig) (string, error) {
85+
var (
86+
err error
87+
rawConfigString string
88+
configString []byte
89+
)
90+
91+
err = json.Unmarshal([]byte(machineConfig.Spec.Config.Raw), &mci)
92+
if err != nil {
93+
return "", err
94+
}
95+
96+
rawConfigString, err = mci.removePrefixFromB64String()
97+
if err != nil {
98+
return "", err
99+
}
100+
configString, err = base64.StdEncoding.DecodeString(rawConfigString)
101+
if err != nil {
102+
return "", err
103+
}
104+
105+
return string(configString), nil
106+
}
107+
108+
func masterMachineConfigBuilder(machineConfigRegistries string) mc.MachineConfig {
109+
return mc.MachineConfig{
110+
ObjectMeta: metav1.ObjectMeta{
111+
Name: machineConfigRegistries,
112+
Namespace: "",
113+
},
114+
}
115+
}
116+
117+
func getMachineConfig(ctx context.Context, helper *helper.Helper) (mc.MachineConfig, error) {
118+
const machineConfigRegistries string = "99-master-generated-registries"
119+
120+
masterMachineConfig := masterMachineConfigBuilder(machineConfigRegistries)
121+
122+
err := helper.GetClient().Get(ctx,
123+
types.NamespacedName{
124+
Name: masterMachineConfig.Name, Namespace: masterMachineConfig.Namespace,
125+
}, &masterMachineConfig)
126+
if err != nil {
127+
return masterMachineConfig, err
128+
}
129+
130+
return masterMachineConfig, nil
131+
}

0 commit comments

Comments
 (0)