Skip to content

Commit 8562115

Browse files
committed
feat(pkg-mgr): Add a configuration parameter to allow automatic dependency version downgrades
Signed-off-by: ezgidemirel <[email protected]>
1 parent 5ed9eea commit 8562115

File tree

15 files changed

+137
-41
lines changed

15 files changed

+137
-41
lines changed

.github/workflows/ci.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -225,7 +225,7 @@ jobs:
225225
- base
226226
- ssa-claims
227227
- realtime-compositions
228-
- package-dependency-upgrades
228+
- package-dependency-updates
229229
- package-signature-verification
230230

231231
steps:

cluster/charts/crossplane/README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -90,6 +90,7 @@ and their default values.
9090
| `packageCache.medium` | Set to `Memory` to hold the package cache in a RAM backed file system. Useful for Crossplane development. | `""` |
9191
| `packageCache.pvc` | The name of a PersistentVolumeClaim to use as the package cache. Disables the default package cache `emptyDir` Volume. | `""` |
9292
| `packageCache.sizeLimit` | The size limit for the package cache. If medium is `Memory` the `sizeLimit` can't exceed Node memory. | `"20Mi"` |
93+
| `packageManager.enableAutomaticDependencyDowngrade` | Enable automatic dependency version downgrades. This configuration is only used when `--enable-dependency-version-upgrades` flag is passed. | `false` |
9394
| `podSecurityContextCrossplane` | Add a custom `securityContext` to the Crossplane pod. | `{}` |
9495
| `podSecurityContextRBACManager` | Add a custom `securityContext` to the RBAC Manager pod. | `{}` |
9596
| `priorityClassName` | The PriorityClass name to apply to the Crossplane and RBAC Manager pods. | `""` |

cluster/charts/crossplane/templates/deployment.yaml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -197,6 +197,10 @@ spec:
197197
- name: {{ $key | replace "." "_" }}
198198
value: {{ $value | quote }}
199199
{{- end}}
200+
{{- if .Values.packageManager.enableAutomaticDependencyDowngrade }}
201+
- name: "AUTOMATIC_DEPENDENCY_DOWNGRADE_ENABLED"
202+
value: "true"
203+
{{- end }}
200204
volumeMounts:
201205
- mountPath: /cache
202206
name: package-cache

cluster/charts/crossplane/values.yaml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -103,6 +103,10 @@ rbacManager:
103103
# -- Add `topologySpreadConstraints` to the RBAC Manager pod deployment.
104104
topologySpreadConstraints: []
105105

106+
packageManager:
107+
# -- Enable automatic dependency version downgrades. This configuration is only used when `--enable-dependency-version-upgrades` flag is passed.
108+
enableAutomaticDependencyDowngrade: false
109+
106110
# -- The PriorityClass name to apply to the Crossplane and RBAC Manager pods.
107111
priorityClassName: ""
108112

cmd/crossplane/core/core.go

Lines changed: 15 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -102,7 +102,8 @@ type startCommand struct {
102102
MaxReconcileRate int `default:"100" help:"The global maximum rate per second at which resources may checked for drift from the desired state."`
103103
MaxConcurrentPackageEstablishers int `default:"10" help:"The the maximum number of goroutines to use for establishing Providers, Configurations and Functions."`
104104

105-
WebhookEnabled bool `default:"true" env:"WEBHOOK_ENABLED" help:"Enable webhook configuration."`
105+
WebhookEnabled bool `default:"true" env:"WEBHOOK_ENABLED" help:"Enable webhook configuration."`
106+
AutomaticDependencyDowngradeEnabled bool `default:"false" env:"AUTOMATIC_DEPENDENCY_DOWNGRADE_ENABLED" help:"Enable automatic dependency version downgrades. This configuration requires the 'EnableDependencyVersionUpgrades' feature flag to be enabled."`
106107

107108
TLSServerSecretName string `env:"TLS_SERVER_SECRET_NAME" help:"The name of the TLS Secret that will store Crossplane's server certificate."`
108109
TLSServerCertsDir string `env:"TLS_SERVER_CERTS_DIR" help:"The path of the folder which will store TLS server certificate of Crossplane."`
@@ -285,6 +286,10 @@ func (c *startCommand) Run(s *runtime.Scheme, log logging.Logger) error { //noli
285286
if c.EnableDependencyVersionUpgrades {
286287
o.Features.Enable(features.EnableAlphaDependencyVersionUpgrades)
287288
log.Info("Alpha feature enabled", "flag", features.EnableAlphaDependencyVersionUpgrades)
289+
290+
if c.AutomaticDependencyDowngradeEnabled {
291+
log.Info("Automatic dependency downgrade is enabled.")
292+
}
288293
}
289294
if c.EnableSignatureVerification {
290295
o.Features.Enable(features.EnableAlphaSignatureVerification)
@@ -392,14 +397,15 @@ func (c *startCommand) Run(s *runtime.Scheme, log logging.Logger) error { //noli
392397
}
393398

394399
po := pkgcontroller.Options{
395-
Options: o,
396-
Cache: xpkg.NewFsPackageCache(c.CacheDir, afero.NewOsFs()),
397-
Namespace: c.Namespace,
398-
ServiceAccount: c.ServiceAccount,
399-
DefaultRegistry: c.Registry,
400-
FetcherOptions: []xpkg.FetcherOpt{xpkg.WithUserAgent(c.UserAgent)},
401-
PackageRuntime: pr,
402-
MaxConcurrentPackageEstablishers: c.MaxConcurrentPackageEstablishers,
400+
Options: o,
401+
Cache: xpkg.NewFsPackageCache(c.CacheDir, afero.NewOsFs()),
402+
Namespace: c.Namespace,
403+
ServiceAccount: c.ServiceAccount,
404+
DefaultRegistry: c.Registry,
405+
FetcherOptions: []xpkg.FetcherOpt{xpkg.WithUserAgent(c.UserAgent)},
406+
PackageRuntime: pr,
407+
MaxConcurrentPackageEstablishers: c.MaxConcurrentPackageEstablishers,
408+
AutomaticDependencyDowngradeEnabled: c.AutomaticDependencyDowngradeEnabled,
403409
}
404410

405411
// We need to set the TUF_ROOT environment variable so that the TUF client

internal/controller/pkg/controller/options.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,4 +49,8 @@ type Options struct {
4949
// MaxConcurrentPackageEstablishers is the maximum number of goroutines to use
5050
// for establishing Providers, Configurations and Functions.
5151
MaxConcurrentPackageEstablishers int
52+
53+
// AutomaticDependencyDowngradeEnabled is a configuration option that
54+
// enables automatic downgrade of dependencies to the highest valid version.
55+
AutomaticDependencyDowngradeEnabled bool
5256
}

internal/controller/pkg/resolver/reconciler.go

Lines changed: 27 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -138,6 +138,13 @@ func WithFeatures(f *feature.Flags) ReconcilerOption {
138138
}
139139
}
140140

141+
// WithDowngradesEnabled sets whether upgrades are enabled or not.
142+
func WithDowngradesEnabled() ReconcilerOption {
143+
return func(r *Reconciler) {
144+
r.downgradesEnabled = true
145+
}
146+
}
147+
141148
// Reconciler reconciles packages.
142149
type Reconciler struct {
143150
client client.Client
@@ -148,6 +155,8 @@ type Reconciler struct {
148155
config xpkg.ConfigStore
149156
registry string
150157
features *feature.Flags
158+
159+
downgradesEnabled bool
151160
}
152161

153162
// Setup adds a controller that reconciles the Lock.
@@ -172,6 +181,9 @@ func Setup(mgr ctrl.Manager, o controller.Options) error {
172181

173182
if o.Features.Enabled(features.EnableAlphaDependencyVersionUpgrades) {
174183
opts = append(opts, WithNewDagFn(internaldag.NewUpgradingMapDag))
184+
if o.AutomaticDependencyDowngradeEnabled {
185+
opts = append(opts, WithDowngradesEnabled())
186+
}
175187
}
176188

177189
return ctrl.NewControllerManagedBy(mgr).
@@ -387,7 +399,7 @@ func (r *Reconciler) Reconcile(ctx context.Context, req reconcile.Request) (reco
387399
return reconcile.Result{}, errors.Errorf(errFmtMissingDependency, depID)
388400
}
389401

390-
newVer, err := r.findDependencyVersionToUpgrade(ctx, ref, installedVersion, n, log)
402+
newVer, err := r.findDependencyVersionToUpdate(ctx, ref, installedVersion, n, log)
391403
if err != nil {
392404
log.Debug(errFindDependencyUpgrade, "error", errors.Wrapf(err, depID, dep.Constraints))
393405
lock.SetConditions(v1beta1.ResolutionFailed(errors.Wrap(err, errFindDependencyUpgrade)))
@@ -467,8 +479,8 @@ func (r *Reconciler) findDependencyVersionToInstall(ctx context.Context, dep *v1
467479
return addVer, nil
468480
}
469481

470-
// FindValidDependencyVersion finds a valid version with version upgrade capability considering parent constraints.
471-
func (r *Reconciler) findDependencyVersionToUpgrade(ctx context.Context, ref name.Reference, insVer string, dep internaldag.Node, log logging.Logger) (string, error) {
482+
// findDependencyVersionToUpdate finds a valid version to update the dependency considering the parent constraints.
483+
func (r *Reconciler) findDependencyVersionToUpdate(ctx context.Context, ref name.Reference, insVer string, dep internaldag.Node, log logging.Logger) (string, error) {
472484
// If there is a digest in the parent constraints, we need to make sure that all other parent constraints are the same.
473485
digest, err := findDigestToUpdate(dep)
474486
if err != nil {
@@ -521,14 +533,10 @@ func (r *Reconciler) findDependencyVersionToUpgrade(ctx context.Context, ref nam
521533

522534
sort.Sort(semver.Collection(availableVersions))
523535
currentVersion := semver.MustParse(insVer)
536+
var targetVersion *semver.Version
524537

525538
// We aim to find the lowest version that satisfies all parent constraints and is greater than the current version.
526539
for _, v := range availableVersions {
527-
// Downgrades are not allowed, so we skip versions that are less than the current version.
528-
if v.LessThan(currentVersion) {
529-
continue
530-
}
531-
532540
valid := true
533541
for _, c := range parentConstraints {
534542
if !c.Check(v) {
@@ -537,9 +545,19 @@ func (r *Reconciler) findDependencyVersionToUpgrade(ctx context.Context, ref nam
537545
}
538546
}
539547

540-
if valid {
548+
// If we're upgrading, we target the first valid version that is greater than the current version.
549+
if (v.GreaterThan(currentVersion) || v.Equal(currentVersion)) && valid {
541550
return v.Original(), nil
542551
}
552+
553+
// If we're downgrading, we target the largest valid version that is less than the current version.
554+
if r.downgradesEnabled && valid {
555+
targetVersion = v
556+
}
557+
}
558+
559+
if targetVersion != nil {
560+
return targetVersion.Original(), nil
543561
}
544562

545563
log.Debug(errFindDependencyUpgrade, "error", errors.Errorf(errFmtNoValidVersion, dep.Identifier(), dep.GetParentConstraints()))

internal/controller/pkg/resolver/reconciler_test.go

Lines changed: 56 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1050,7 +1050,7 @@ func TestReconcilerFindDependencyVersionToUpgrade(t *testing.T) {
10501050
},
10511051
},
10521052
"ErrorNoValidVersionDowngrade": {
1053-
reason: "We should return an error if no valid version exists for dependency and downgrade is not allowed.",
1053+
reason: "We should return an error if no valid version exists for dependency and downgrade is not enabled.",
10541054
args: args{
10551055
mgr: &fake.Manager{Client: test.NewMockClient()},
10561056
insVer: "v1.0.0",
@@ -1074,18 +1074,70 @@ func TestReconcilerFindDependencyVersionToUpgrade(t *testing.T) {
10741074
err: errors.Errorf(errFmtNoValidVersion, "cool-repo/cool-image", "[<=v1.0.0 v0.0.1]"),
10751075
},
10761076
},
1077+
"UpgradeToSmallestValid": {
1078+
reason: "We should be able to find the smallest valid version to update to.",
1079+
args: args{
1080+
mgr: &fake.Manager{Client: test.NewMockClient()},
1081+
insVer: "v2.0.0",
1082+
dep: &v1beta1.Dependency{
1083+
Package: "cool-repo/cool-image",
1084+
ParentConstraints: []string{
1085+
">v2.0.0",
1086+
"<=v3.0.0",
1087+
},
1088+
},
1089+
rec: []ReconcilerOption{
1090+
WithFetcher(&fakexpkg.MockFetcher{
1091+
MockTags: fakexpkg.NewMockTagsFn([]string{"v1.0.0", "v2.0.0", "v2.1.0", "v3.0.0"}, nil),
1092+
}),
1093+
WithConfigStore(&fakexpkg.MockConfigStore{
1094+
MockPullSecretFor: fakexpkg.NewMockConfigStorePullSecretForFn("", "", nil),
1095+
}),
1096+
WithDowngradesEnabled(),
1097+
},
1098+
},
1099+
want: want{
1100+
version: "v2.1.0",
1101+
},
1102+
},
1103+
"DowngradeToLargestValid": {
1104+
reason: "We should return an error if no valid version exists for dependency and downgrade is not allowed.",
1105+
args: args{
1106+
mgr: &fake.Manager{Client: test.NewMockClient()},
1107+
insVer: "v3.0.0",
1108+
dep: &v1beta1.Dependency{
1109+
Package: "cool-repo/cool-image",
1110+
ParentConstraints: []string{
1111+
">=v0.0.1",
1112+
"<v3.0.0",
1113+
},
1114+
},
1115+
rec: []ReconcilerOption{
1116+
WithFetcher(&fakexpkg.MockFetcher{
1117+
MockTags: fakexpkg.NewMockTagsFn([]string{"v0.0.1", "v1.0.0", "v2.0.0", "v3.0.0"}, nil),
1118+
}),
1119+
WithConfigStore(&fakexpkg.MockConfigStore{
1120+
MockPullSecretFor: fakexpkg.NewMockConfigStorePullSecretForFn("", "", nil),
1121+
}),
1122+
WithDowngradesEnabled(),
1123+
},
1124+
},
1125+
want: want{
1126+
version: "v2.0.0",
1127+
},
1128+
},
10771129
}
10781130
for name, tc := range cases {
10791131
t.Run(name, func(t *testing.T) {
10801132
r := NewReconciler(tc.args.mgr, append(tc.args.rec, WithLogger(testLog))...)
10811133
ref, _ := pkgName.ParseReference(tc.args.dep.Identifier())
1082-
got, err := r.findDependencyVersionToUpgrade(context.Background(), ref, tc.args.insVer, tc.args.dep, testLog)
1134+
got, err := r.findDependencyVersionToUpdate(context.Background(), ref, tc.args.insVer, tc.args.dep, testLog)
10831135

10841136
if diff := cmp.Diff(tc.want.err, err, test.EquateErrors()); diff != "" {
1085-
t.Errorf("\n%s\nr.findDependencyVersionToUpgrade(...): -want error, +got error:\n%s", tc.reason, diff)
1137+
t.Errorf("\n%s\nr.findDependencyVersionToUpdate(...): -want error, +got error:\n%s", tc.reason, diff)
10861138
}
10871139
if diff := cmp.Diff(tc.want.version, got, test.EquateErrors()); diff != "" {
1088-
t.Errorf("\n%s\nr.findDependencyVersionToUpgrade(...): -want, +got:\n%s", tc.reason, diff)
1140+
t.Errorf("\n%s\nr.findDependencyVersionToUpdate(...): -want, +got:\n%s", tc.reason, diff)
10891141
}
10901142
})
10911143
}

test/e2e/manifests/pkg/dependency-upgrade/no-downgrade/configuration-nop-revision.yaml renamed to test/e2e/manifests/pkg/dependency-upgrade/downgrade/configuration-nop-revision.yaml

File renamed without changes.

test/e2e/manifests/pkg/dependency-upgrade/no-downgrade/configuration-nop.yaml renamed to test/e2e/manifests/pkg/dependency-upgrade/downgrade/configuration-nop.yaml

File renamed without changes.

0 commit comments

Comments
 (0)