Skip to content

Commit b826849

Browse files
authored
feat: support removing the default channel head in deprecatetrunace (#734)
Signed-off-by: Daniel Sover <[email protected]>
1 parent 29e90de commit b826849

File tree

7 files changed

+380
-27
lines changed

7 files changed

+380
-27
lines changed

cmd/opm/index/deprecatetruncate.go

Lines changed: 18 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,9 @@ var deprecateLong = templates.LongDesc(`
2525
Produces the following update graph in quay.io/my/index:v2
2626
1.4.0 -- replaces -> 1.3.0 [deprecated]
2727
28-
Deprecating a bundle that removes the default channel is not allowed. Changing the default channel prior to deprecation is possible by publishing a new bundle to the index.
28+
Deprecating a bundle that removes the default channel is not allowed unless the head(s) of all channels are being deprecated (the package is subsequently removed from the index).
29+
This behavior can be enabled via the allow-package-removal flag.
30+
Changing the default channel prior to deprecation is possible by publishing a new bundle to the index.
2931
`)
3032

3133
func newIndexDeprecateTruncateCmd() *cobra.Command {
@@ -60,6 +62,7 @@ func newIndexDeprecateTruncateCmd() *cobra.Command {
6062
if err := indexCmd.Flags().MarkHidden("debug"); err != nil {
6163
logrus.Panic(err.Error())
6264
}
65+
indexCmd.Flags().Bool("allow-package-removal", false, "removes the entire package if the heads of all channels in the package are deprecated")
6366

6467
return indexCmd
6568
}
@@ -110,6 +113,11 @@ func runIndexDeprecateTruncateCmdFunc(cmd *cobra.Command, args []string) error {
110113
return err
111114
}
112115

116+
allowPackageRemoval, err := cmd.Flags().GetBool("allow-package-removal")
117+
if err != nil {
118+
return err
119+
}
120+
113121
logger := logrus.WithFields(logrus.Fields{"bundles": bundles})
114122

115123
logger.Info("deprecating bundles from the index")
@@ -120,14 +128,15 @@ func runIndexDeprecateTruncateCmdFunc(cmd *cobra.Command, args []string) error {
120128
logger)
121129

122130
request := indexer.DeprecateFromIndexRequest{
123-
Generate: generate,
124-
FromIndex: fromIndex,
125-
BinarySourceImage: binaryImage,
126-
OutDockerfile: outDockerfile,
127-
Tag: tag,
128-
Bundles: bundles,
129-
Permissive: permissive,
130-
SkipTLS: skipTLS,
131+
Generate: generate,
132+
FromIndex: fromIndex,
133+
BinarySourceImage: binaryImage,
134+
OutDockerfile: outDockerfile,
135+
Tag: tag,
136+
Bundles: bundles,
137+
Permissive: permissive,
138+
SkipTLS: skipTLS,
139+
AllowPackageRemoval: allowPackageRemoval,
131140
}
132141

133142
err = indexDeprecator.DeprecateFromIndex(request)

pkg/lib/indexer/indexer.go

Lines changed: 15 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -643,15 +643,16 @@ func generatePackageYaml(dbQuerier pregistry.Query, packageName, downloadPath st
643643

644644
// DeprecateFromIndexRequest defines the parameters to send to the PruneFromIndex API
645645
type DeprecateFromIndexRequest struct {
646-
Generate bool
647-
Permissive bool
648-
BinarySourceImage string
649-
FromIndex string
650-
OutDockerfile string
651-
Bundles []string
652-
Tag string
653-
CaFile string
654-
SkipTLS bool
646+
Generate bool
647+
Permissive bool
648+
BinarySourceImage string
649+
FromIndex string
650+
OutDockerfile string
651+
Bundles []string
652+
Tag string
653+
CaFile string
654+
SkipTLS bool
655+
AllowPackageRemoval bool
655656
}
656657

657658
// DeprecateFromIndex takes a DeprecateFromIndexRequest and deprecates the requested
@@ -668,14 +669,14 @@ func (i ImageIndexer) DeprecateFromIndex(request DeprecateFromIndexRequest) erro
668669
return err
669670
}
670671

671-
// Run opm registry prune on the database
672672
deprecateFromRegistryReq := registry.DeprecateFromRegistryRequest{
673-
Bundles: request.Bundles,
674-
InputDatabase: databasePath,
675-
Permissive: request.Permissive,
673+
Bundles: request.Bundles,
674+
InputDatabase: databasePath,
675+
Permissive: request.Permissive,
676+
AllowPackageRemoval: request.AllowPackageRemoval,
676677
}
677678

678-
// Prune the bundles from the registry
679+
// Deprecate the bundles from the registry
679680
err = i.RegistryDeprecator.DeprecateFromRegistry(deprecateFromRegistryReq)
680681
if err != nil {
681682
return err

pkg/lib/registry/registry.go

Lines changed: 21 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -331,9 +331,10 @@ func (r RegistryUpdater) PruneFromRegistry(request PruneFromRegistryRequest) err
331331
}
332332

333333
type DeprecateFromRegistryRequest struct {
334-
Permissive bool
335-
InputDatabase string
336-
Bundles []string
334+
Permissive bool
335+
InputDatabase string
336+
Bundles []string
337+
AllowPackageRemoval bool
337338
}
338339

339340
func (r RegistryUpdater) DeprecateFromRegistry(request DeprecateFromRegistryRequest) error {
@@ -366,6 +367,23 @@ func (r RegistryUpdater) DeprecateFromRegistry(request DeprecateFromRegistryRequ
366367
}
367368

368369
deprecator := sqlite.NewSQLDeprecatorForBundles(dbLoader, toDeprecate)
370+
371+
// Check for deprecation of head of default channel. If deprecation request includes heads of all other channels,
372+
// then remove the package entirely. Otherwise, deprecate provided bundles. This enables deprecating an entire package.
373+
// By default deprecating the head of default channel is not permitted.
374+
if request.AllowPackageRemoval {
375+
packageDeprecator := sqlite.NewSQLDeprecatorForBundlesAndPackages(deprecator, dbQuerier)
376+
if err := packageDeprecator.MaybeRemovePackages(); err != nil {
377+
r.Logger.Debugf("unable to deprecate package from database: %s", err)
378+
if !request.Permissive {
379+
r.Logger.WithError(err).Error("permissive mode disabled")
380+
return err
381+
}
382+
r.Logger.WithError(err).Warn("permissive mode enabled")
383+
}
384+
}
385+
386+
// Any bundles associated with removed packages are now removed from the list of bundles to deprecate.
369387
if err := deprecator.Deprecate(); err != nil {
370388
r.Logger.Debugf("unable to deprecate bundles from database: %s", err)
371389
if !request.Permissive {

pkg/registry/populator_test.go

Lines changed: 173 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -939,6 +939,179 @@ func TestDeprecateBundle(t *testing.T) {
939939
}
940940
}
941941

942+
func TestDeprecatePackage(t *testing.T) {
943+
type args struct {
944+
bundles []string
945+
}
946+
type pkgChannel map[string][]string
947+
type expected struct {
948+
err error
949+
remainingBundles []string
950+
deprecatedBundles []string
951+
remainingPkgChannels pkgChannel
952+
}
953+
tests := []struct {
954+
description string
955+
args args
956+
expected expected
957+
}{
958+
{
959+
description: "RemoveEntirePackage/BundlesAreAllHeadsOfChannels",
960+
args: args{
961+
bundles: []string{
962+
"quay.io/test/etcd.0.9.2",
963+
"quay.io/test/etcd.0.9.0",
964+
},
965+
},
966+
expected: expected{
967+
err: nil,
968+
remainingBundles: []string{
969+
"quay.io/test/prometheus.0.22.2/preview",
970+
"quay.io/test/prometheus.0.15.0/preview",
971+
"quay.io/test/prometheus.0.15.0/stable",
972+
"quay.io/test/prometheus.0.14.0/preview",
973+
"quay.io/test/prometheus.0.14.0/stable",
974+
},
975+
deprecatedBundles: []string{},
976+
remainingPkgChannels: pkgChannel{
977+
"prometheus": []string{
978+
"preview",
979+
"stable",
980+
},
981+
},
982+
},
983+
},
984+
{
985+
description: "RemoveHeadOfDefaultChannelWithoutAllChannelHeads/Error",
986+
args: args{
987+
bundles: []string{
988+
"quay.io/test/etcd.0.9.2",
989+
},
990+
},
991+
expected: expected{
992+
err: utilerrors.NewAggregate([]error{fmt.Errorf("cannot deprecate default channel head from package without removing all other channel heads in package %s: must deprecate %s, head of channel %s", "etcd", "quay.io/test/etcd.0.9.0", "beta")}),
993+
remainingBundles: []string{
994+
"quay.io/test/etcd.0.9.0/alpha",
995+
"quay.io/test/etcd.0.9.0/beta",
996+
"quay.io/test/etcd.0.9.0/stable",
997+
"quay.io/test/etcd.0.9.2/stable",
998+
"quay.io/test/etcd.0.9.2/alpha",
999+
"quay.io/test/prometheus.0.14.0/preview",
1000+
"quay.io/test/prometheus.0.14.0/stable",
1001+
"quay.io/test/prometheus.0.15.0/preview",
1002+
"quay.io/test/prometheus.0.15.0/stable",
1003+
"quay.io/test/prometheus.0.22.2/preview",
1004+
},
1005+
deprecatedBundles: []string{},
1006+
remainingPkgChannels: pkgChannel{
1007+
"etcd": []string{
1008+
"alpha",
1009+
"beta",
1010+
"stable",
1011+
},
1012+
"prometheus": []string{
1013+
"preview",
1014+
"stable",
1015+
},
1016+
},
1017+
},
1018+
},
1019+
{
1020+
description: "RemoveEntirePackage/AndDeprecateAdditionalBundle",
1021+
args: args{
1022+
bundles: []string{
1023+
"quay.io/test/etcd.0.9.2",
1024+
"quay.io/test/etcd.0.9.0",
1025+
"quay.io/test/prometheus.0.14.0",
1026+
},
1027+
},
1028+
expected: expected{
1029+
err: nil,
1030+
remainingBundles: []string{
1031+
"quay.io/test/prometheus.0.22.2/preview",
1032+
"quay.io/test/prometheus.0.15.0/preview",
1033+
"quay.io/test/prometheus.0.15.0/stable",
1034+
"quay.io/test/prometheus.0.14.0/preview",
1035+
"quay.io/test/prometheus.0.14.0/stable",
1036+
},
1037+
deprecatedBundles: []string{
1038+
"quay.io/test/prometheus.0.14.0/preview",
1039+
"quay.io/test/prometheus.0.14.0/stable",
1040+
},
1041+
remainingPkgChannels: pkgChannel{
1042+
"prometheus": []string{
1043+
"preview",
1044+
"stable",
1045+
},
1046+
},
1047+
},
1048+
},
1049+
}
1050+
1051+
for _, tt := range tests {
1052+
t.Run(tt.description, func(t *testing.T) {
1053+
logrus.SetLevel(logrus.DebugLevel)
1054+
db, cleanup := CreateTestDb(t)
1055+
defer cleanup()
1056+
1057+
querier, err := createAndPopulateDB(db)
1058+
require.NoError(t, err)
1059+
1060+
store, err := sqlite.NewSQLLiteLoader(db)
1061+
require.NoError(t, err)
1062+
1063+
deprecator := sqlite.NewSQLDeprecatorForBundles(store, tt.args.bundles)
1064+
packageDeprecator := sqlite.NewSQLDeprecatorForBundlesAndPackages(deprecator, querier)
1065+
require.Equal(t, tt.expected.err, packageDeprecator.MaybeRemovePackages())
1066+
if len(tt.expected.deprecatedBundles) > 0 {
1067+
require.Equal(t, tt.expected.err, deprecator.Deprecate())
1068+
}
1069+
1070+
// Ensure remaining bundlePaths in db match
1071+
bundles, err := querier.ListBundles(context.Background())
1072+
require.NoError(t, err)
1073+
var bundlePaths []string
1074+
for _, bundle := range bundles {
1075+
bundlePaths = append(bundlePaths, strings.Join([]string{bundle.BundlePath, bundle.ChannelName}, "/"))
1076+
}
1077+
require.ElementsMatch(t, tt.expected.remainingBundles, bundlePaths)
1078+
1079+
// Ensure deprecated bundles match
1080+
var deprecatedBundles []string
1081+
deprecatedProperty, err := json.Marshal(registry.DeprecatedProperty{})
1082+
require.NoError(t, err)
1083+
for _, bundle := range bundles {
1084+
for _, prop := range bundle.Properties {
1085+
if prop.Type == registry.DeprecatedType && prop.Value == string(deprecatedProperty) {
1086+
deprecatedBundles = append(deprecatedBundles, strings.Join([]string{bundle.BundlePath, bundle.ChannelName}, "/"))
1087+
}
1088+
}
1089+
}
1090+
1091+
require.ElementsMatch(t, tt.expected.deprecatedBundles, deprecatedBundles)
1092+
1093+
// Ensure remaining channels match
1094+
packages, err := querier.ListPackages(context.Background())
1095+
require.NoError(t, err)
1096+
1097+
for _, pkg := range packages {
1098+
channelEntries, err := querier.GetChannelEntriesFromPackage(context.Background(), pkg)
1099+
require.NoError(t, err)
1100+
1101+
uniqueChannels := make(map[string]struct{})
1102+
var channels []string
1103+
for _, ch := range channelEntries {
1104+
uniqueChannels[ch.ChannelName] = struct{}{}
1105+
}
1106+
for k := range uniqueChannels {
1107+
channels = append(channels, k)
1108+
}
1109+
require.ElementsMatch(t, tt.expected.remainingPkgChannels[pkg], channels)
1110+
}
1111+
})
1112+
}
1113+
}
1114+
9421115
func TestAddAfterDeprecate(t *testing.T) {
9431116
type args struct {
9441117
existing []string

0 commit comments

Comments
 (0)