Skip to content

Commit 25ace51

Browse files
openshift-cherrypick-robotFlavio Percoco
andauthored
[cloud_hotfix_releases] MGMT-12435: Add a way to apply patches to core manifests (#4636)
* MGMT-12435: Add a way to apply patches to core manifests Starting in 4.12 it's not possible to completely overwrite existing manifests, the way AI was doing it. Core manifests that need to be modified should be patched instead. This PR adds a logic to upload patches to the manifests api and then apply them. Note that the InfrastructureCR patching was not changed to us this new flow as this PR focuses only in adding this possibility and changing the SchedulableMasters flow. Future PRs will align the rest of the codebase. Signed-off-by: Flavio Percoco <[email protected]> * Defer delete of manifests dir in case of error Signed-off-by: Flavio Percoco <[email protected]> * Address review comments Signed-off-by: Flavio Percoco <[email protected]> * Revert "OCPBUGS-1482: Don't override schedulableMasters unnecessarily (#4414)" This reverts commit 5661d9c. Let's go back to applying the schedulable masters patch when either SchedulableMastersForce or SchedulableMasters are set. Signed-off-by: Flavio Percoco <[email protected]> Co-authored-by: Flavio Percoco <[email protected]>
1 parent c20a194 commit 25ace51

7 files changed

Lines changed: 167 additions & 27 deletions

File tree

internal/common/common.go

Lines changed: 1 addition & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -120,14 +120,7 @@ func IsImportedCluster(cluster *Cluster) bool {
120120
}
121121

122122
func AreMastersSchedulable(cluster *Cluster) bool {
123-
// If the topology forces schedulable masters (i.e. there are no workers),
124-
// SchedulableMastersForcedTrue will be true, but also it will be enabled
125-
// by default in the installer. Since overriding it currently causes a
126-
// failure due to a conflict starting with 4.12, we prefer to avoid it. We
127-
// only need to override the installer when the user has set
128-
// SchedulableMasters explicitly and that is not already the default in the
129-
// installer.
130-
return !swag.BoolValue(cluster.SchedulableMastersForcedTrue) && swag.BoolValue(cluster.SchedulableMasters)
123+
return swag.BoolValue(cluster.SchedulableMastersForcedTrue) || swag.BoolValue(cluster.SchedulableMasters)
131124
}
132125

133126
func GetEffectiveRole(host *models.Host) models.HostRole {

internal/common/common_test.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -253,8 +253,8 @@ var _ = Describe("Test AreMastersSchedulable", func() {
253253
}{
254254
{schedulableMastersForcedTrue: false, schedulableMasters: false, expectedSchedulableMasters: false},
255255
{schedulableMastersForcedTrue: false, schedulableMasters: true, expectedSchedulableMasters: true},
256-
{schedulableMastersForcedTrue: true, schedulableMasters: false, expectedSchedulableMasters: false}, // Handled by installer
257-
{schedulableMastersForcedTrue: true, schedulableMasters: true, expectedSchedulableMasters: false}, // Handled by installer
256+
{schedulableMastersForcedTrue: true, schedulableMasters: false, expectedSchedulableMasters: true},
257+
{schedulableMastersForcedTrue: true, schedulableMasters: true, expectedSchedulableMasters: true},
258258
} {
259259
test := test
260260
It(fmt.Sprintf("schedulableMastersForcedTrue=%v schedulableMasters=%v AreMastersSchedulable? %v", test.schedulableMastersForcedTrue, test.schedulableMasters, test.expectedSchedulableMasters), func() {

internal/ignition/ignition.go

Lines changed: 86 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -298,15 +298,23 @@ func (g *installerGenerator) UploadToS3(ctx context.Context) error {
298298
// Generate generates ignition files and applies modifications.
299299
func (g *installerGenerator) Generate(ctx context.Context, installConfig []byte, platformType models.PlatformType) error {
300300
var icspFile string
301+
var err error
301302
log := logutil.FromContext(ctx, g.log)
302303

304+
defer func() {
305+
if err != nil {
306+
os.Remove(filepath.Join(g.workDir, "manifests"))
307+
os.Remove(filepath.Join(g.workDir, "openshift"))
308+
}
309+
}()
310+
303311
// In case we don't want to override image for extracting installer use release one
304312
if g.installerReleaseImageOverride == "" {
305313
g.installerReleaseImageOverride = g.releaseImage
306314
}
307315

308316
// If ImageContentSources are defined, store in a file for the 'oc' command
309-
icspFile, err := getIcspFileFromInstallConfig(installConfig, log)
317+
icspFile, err = getIcspFileFromInstallConfig(installConfig, log)
310318
if err != nil {
311319
return errors.Wrap(err, "failed to create file with ImageContentSources")
312320
}
@@ -384,13 +392,17 @@ func (g *installerGenerator) Generate(ctx context.Context, installConfig []byte,
384392
log.Infof("adding manifest %s to working dir for cluster %s", manifest, g.cluster.ID)
385393
err = g.downloadManifest(ctx, manifest)
386394
if err != nil {
387-
_ = os.Remove(filepath.Join(g.workDir, "manifests"))
388-
_ = os.Remove(filepath.Join(g.workDir, "openshift"))
389395
log.WithError(err).Errorf("Failed to download manifest %s to working dir for cluster %s", manifest, g.cluster.ID)
390396
return err
391397
}
392398
}
393399

400+
err = g.applyManifestPatches(ctx)
401+
if err != nil {
402+
log.WithError(err).Errorf("failed to apply manifests' patches for cluster '%s'", g.cluster.ID)
403+
return err
404+
}
405+
394406
err = g.applyInfrastructureCRPatch(ctx)
395407
if err != nil {
396408
log.WithError(err).Errorf("failed to patch the infrastructure CR manifest '%s'", common.PlatformTypeValue(g.cluster.Platform.Type))
@@ -468,6 +480,77 @@ func (g *installerGenerator) addBootstrapKubeletIpIfRequired(log logrus.FieldLog
468480
return envVars, nil
469481
}
470482

483+
func (g *installerGenerator) applyManifestPatches(ctx context.Context) error {
484+
log := logutil.FromContext(ctx, g.log)
485+
manifestsOpenShiftDir := filepath.Join(g.workDir, "openshift")
486+
487+
// File path walks the directory in lexical order, which means it's possible to have some control on
488+
// how files are being walked through by using a numeric prefix for the patch extension. For example:
489+
// - cluster-scheduler-02-config.yml.patch_01_set_schedulable_masters
490+
// - cluster-scheduler-02-config.yml.patch_02_something_else
491+
err := filepath.Walk(manifestsOpenShiftDir, func(path string, info os.FileInfo, err error) error {
492+
if err != nil {
493+
return err
494+
}
495+
496+
// We allow files that have the following extension .y(a)ml.patch(_something).
497+
// This allows to pushing multuple patches for the same Manifest.
498+
extension := filepath.Ext(info.Name())
499+
if !strings.HasPrefix(extension, ".patch") {
500+
return nil
501+
}
502+
503+
// This is the path to the patch
504+
manifestPatchPath := filepath.Join(manifestsOpenShiftDir, info.Name())
505+
log.Debugf("Applying the following patch: %s", manifestPatchPath)
506+
manifestPatch, err := os.ReadFile(manifestPatchPath)
507+
if err != nil {
508+
return errors.Wrapf(err, "failed to read manifest patch %s", manifestPatchPath)
509+
}
510+
log.Debugf("read the manifest at %s", manifestPatchPath)
511+
512+
// Let's look for the actual manifest. Code first looks in the `openshift` directory and
513+
// fallsback to the `manifests` directory if no patch was found in the former.
514+
manifestPath := filepath.Join(manifestsOpenShiftDir, strings.TrimSuffix(info.Name(), extension))
515+
if _, err = os.Stat(manifestPath); errors.Is(err, os.ErrNotExist) {
516+
log.Debugf("Manifest %s does not exist. Trying with the openshift dir next")
517+
manifestPath = filepath.Join(g.workDir, "manifests", strings.TrimSuffix(info.Name(), extension))
518+
}
519+
520+
data, err := os.ReadFile(manifestPath)
521+
if err != nil {
522+
return errors.Wrapf(err, "failed to read manifest %s", manifestPath)
523+
}
524+
log.Debugf("read the manifest at %s", manifestPath)
525+
526+
// Let's apply the patch now since both files have been read
527+
data, err = common.ApplyYamlPatch(data, manifestPatch)
528+
if err != nil {
529+
return errors.Wrapf(err, "failed to patch manifest \"%s\"", manifestPath)
530+
}
531+
log.Debugf("applied the yaml patch to the manifest at %s: \n %s", manifestPath, string(data[:]))
532+
533+
err = os.WriteFile(manifestPath, data, 0600)
534+
if err != nil {
535+
return errors.Wrapf(err, "failed to write manifest \"%s\"", manifestPath)
536+
}
537+
538+
log.Debugf("wrote the resulting manifest at %s", manifestPath)
539+
540+
err = os.Remove(manifestPatchPath)
541+
if err != nil {
542+
return errors.Wrapf(err, "failed to remove patch %s", manifestPatchPath)
543+
}
544+
return nil
545+
})
546+
547+
if err != nil {
548+
return errors.Wrapf(err, "failed to apply patches")
549+
}
550+
551+
return nil
552+
}
553+
471554
func (g *installerGenerator) applyInfrastructureCRPatch(ctx context.Context) error {
472555
log := logutil.FromContext(ctx, g.log)
473556

internal/ignition/ignition_test.go

Lines changed: 66 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1698,7 +1698,7 @@ status:
16981698
// the infra name should not have changed
16991699
Expect(status["infrastructureName"].(string)).To(Equal("test-cluster-s9qbn"))
17001700
})
1701-
It("patches the manifest correctly", func() {
1701+
It("patches the infrastructure manifest correctly", func() {
17021702
base := `---
17031703
apiVersion: config.openshift.io/v1
17041704
kind: Infrastructure
@@ -1746,6 +1746,71 @@ status:
17461746
// the infra name should not have changed
17471747
Expect(status["infrastructureName"].(string)).To(Equal("test-cluster-s9qbn"))
17481748
})
1749+
1750+
It("patches core manifests correctly", func() {
1751+
base := `---
1752+
apiVersion: config.openshift.io/v1
1753+
kind: Scheduler
1754+
metadata:
1755+
creationTimestamp: "2022-11-03T06:20:17Z"
1756+
generation: 1
1757+
name: cluster
1758+
resourceVersion: "620"
1759+
uid: b74da926-8664-41a2-8fbf-4217156a63c6
1760+
spec:
1761+
mastersSchedulable: false
1762+
policy:
1763+
name: ""`
1764+
1765+
schedulableMastersManifestPatch := `---
1766+
- op: replace
1767+
path: /spec/mastersSchedulable
1768+
value: true
1769+
`
1770+
schedulerPatchCustomTest := `---
1771+
- op: add
1772+
path: /spec/customTest
1773+
value: true
1774+
`
1775+
1776+
manifestsOpenshiftDir := filepath.Join(workDir, "/openshift")
1777+
Expect(os.Mkdir(manifestsOpenshiftDir, 0755)).To(Succeed())
1778+
1779+
manifestPatchPath := filepath.Join(manifestsOpenshiftDir, "cluster-scheduler-02-config.yml.patch")
1780+
err := os.WriteFile(manifestPatchPath, []byte(schedulableMastersManifestPatch), 0600)
1781+
Expect(err).NotTo(HaveOccurred())
1782+
1783+
manifestPatchCustomTestPath := filepath.Join(manifestsOpenshiftDir, "cluster-scheduler-02-config.yml.patch_custom_test")
1784+
err = os.WriteFile(manifestPatchCustomTestPath, []byte(schedulerPatchCustomTest), 0600)
1785+
Expect(err).NotTo(HaveOccurred())
1786+
1787+
manifestsDir := filepath.Join(workDir, "/manifests")
1788+
Expect(os.Mkdir(manifestsDir, 0755)).To(Succeed())
1789+
1790+
err = os.WriteFile(filepath.Join(manifestsDir, "cluster-scheduler-02-config.yml"), []byte(base), 0600)
1791+
Expect(err).NotTo(HaveOccurred())
1792+
1793+
Expect(generator.applyManifestPatches(ctx)).To(Succeed())
1794+
1795+
content, err := os.ReadFile(filepath.Join(manifestsDir, "cluster-scheduler-02-config.yml"))
1796+
Expect(err).NotTo(HaveOccurred())
1797+
1798+
merged := map[string]interface{}{}
1799+
err = yaml.Unmarshal(content, &merged)
1800+
Expect(err).NotTo(HaveOccurred())
1801+
1802+
status := merged["spec"].(map[interface{}]interface{})
1803+
1804+
// Master should now be schedulable
1805+
Expect(status["mastersSchedulable"].(bool)).To(Equal(true))
1806+
Expect(status["customTest"].(bool)).To(Equal(true))
1807+
1808+
_, err = os.Stat(manifestPatchPath)
1809+
Expect(errors.Is(err, os.ErrNotExist)).To(Equal(true))
1810+
1811+
_, err = os.Stat(manifestPatchCustomTestPath)
1812+
Expect(errors.Is(err, os.ErrNotExist)).To(Equal(true))
1813+
})
17491814
})
17501815

17511816
var _ = Describe("Set kubelet node ip", func() {

internal/manifests/manifests.go

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -91,6 +91,11 @@ func (m *Manifests) CreateClusterManifestInternal(ctx context.Context, params op
9191
if !json.Valid(manifestContent) {
9292
return nil, common.NewApiError(http.StatusBadRequest, errors.New("Manifest content has an illegal JSON format"))
9393
}
94+
} else if strings.HasPrefix(extension, ".patch") && (strings.Contains(fileName, ".yaml.patch") || strings.Contains(fileName, ".yml.patch")) {
95+
var s []map[interface{}]interface{}
96+
if yaml.Unmarshal(manifestContent, &s) != nil {
97+
return nil, common.NewApiError(http.StatusBadRequest, errors.New("Patch content has an invalid YAML format"))
98+
}
9499
} else {
95100
return nil, common.NewApiError(http.StatusBadRequest, errors.New("Unsupported manifest extension. Only json, yaml and yml extensions are supported"))
96101
}

internal/network/manifests_generator.go

Lines changed: 6 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -147,16 +147,10 @@ spec:
147147
WantedBy=multi-user.target
148148
`
149149

150-
const schedulableMastersManifest = `
151-
apiVersion: config.openshift.io/v1
152-
kind: Scheduler
153-
metadata:
154-
name: cluster
155-
spec:
156-
mastersSchedulable: true
157-
policy:
158-
name: ""
159-
status: {}
150+
const schedulableMastersManifestPatch = `---
151+
- op: replace
152+
path: /spec/mastersSchedulable
153+
value: true
160154
`
161155

162156
func createChronyManifestContent(c *common.Cluster, role models.HostRole, log logrus.FieldLogger) ([]byte, error) {
@@ -213,8 +207,8 @@ func (m *ManifestsGenerator) AddChronyManifest(ctx context.Context, log logrus.F
213207
}
214208

215209
func (m *ManifestsGenerator) AddSchedulableMastersManifest(ctx context.Context, log logrus.FieldLogger, cluster *common.Cluster) error {
216-
content := []byte(schedulableMastersManifest)
217-
schedulableMastersManifestFile := "50-schedulable_masters.yaml"
210+
content := []byte(schedulableMastersManifestPatch)
211+
schedulableMastersManifestFile := "cluster-scheduler-02-config.yml.patch_ai_set_masters_schedulable"
218212
err := m.createManifests(ctx, cluster, schedulableMastersManifestFile, content)
219213
if err != nil {
220214
return err

internal/network/manifests_generator_test.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -474,7 +474,7 @@ var _ = Describe("schedulable masters manifest", func() {
474474
})
475475

476476
Context("CreateClusterManifest success", func() {
477-
fileName := "50-schedulable_masters.yaml"
477+
fileName := "cluster-scheduler-02-config.yml.patch_ai_set_masters_schedulable"
478478
It("CreateClusterManifest success", func() {
479479
manifestsApi.EXPECT().CreateClusterManifestInternal(gomock.Any(), gomock.Any()).Return(&models.Manifest{
480480
FileName: fileName,

0 commit comments

Comments
 (0)