Skip to content

Commit 4cd2f82

Browse files
authored
Handle channelName (#172)
Adds backend support for channelName Fixes #170 Fixes OPRUN-2917 * Integrate channelName * Add channelName unit tests * Update error messages * Update channel name regex Signed-off-by: Todd Short <[email protected]>
1 parent 5e79476 commit 4cd2f82

File tree

7 files changed

+269
-3
lines changed

7 files changed

+269
-3
lines changed

.gitignore

+2
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,8 @@ install.sh
3030
*.swp
3131
*.swo
3232
*~
33+
\#*\#
34+
.\#*
3335

3436
# TODO dfranz remove this line and the bin folder when tools binaries are moved to their own folder
3537
!bin/.dockerignore

api/v1alpha1/operator_types.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@ type OperatorSpec struct {
3939
Version string `json:"version,omitempty"`
4040

4141
//+kubebuilder:validation:MaxLength:=48
42-
//+kubebuilder:validation:Pattern:=^[a-z0-9]+(-[a-z0-9]+)*$
42+
//+kubebuilder:validation:Pattern:=^[a-z0-9]+([\.-][a-z0-9]+)*$
4343
// Channel constraint defintion
4444
Channel string `json:"channel,omitempty"`
4545
}

config/crd/bases/operators.operatorframework.io_operators.yaml

+1-1
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@ spec:
3838
channel:
3939
description: Channel constraint defintion
4040
maxLength: 48
41-
pattern: ^[a-z0-9]+(-[a-z0-9]+)*$
41+
pattern: ^[a-z0-9]+([\.-][a-z0-9]+)*$
4242
type: string
4343
packageName:
4444
maxLength: 48

controllers/admission_test.go

+45
Original file line numberDiff line numberDiff line change
@@ -72,4 +72,49 @@ var _ = Describe("Operator Spec Validations", func() {
7272
Expect(err.Error()).To(ContainSubstring("spec.version in body should match '^(0|[1-9]\\d*)\\.(0|[1-9]\\d*)\\.(0|[1-9]\\d*)(-(0|[1-9]\\d*|[0-9]*[a-zA-Z-][0-9a-zA-Z-]*)(\\.(0|[1-9]\\d*|[0-9]*[a-zA-Z-][0-9a-zA-Z-]*))*)?(\\+([0-9a-zA-Z-]+(\\.[0-9a-zA-Z-]+)*))?$'"))
7373
}
7474
})
75+
It("should fail if an invalid channel name is given", func() {
76+
invalidChannels := []string{
77+
"spaces spaces",
78+
"Capitalized",
79+
"camelCase",
80+
"many/invalid$characters+in_name",
81+
"-start-with-hyphen",
82+
"end-with-hyphen-",
83+
".start-with-period",
84+
"end-with-period.",
85+
}
86+
for _, invalidChannel := range invalidChannels {
87+
err := cl.Create(ctx, operator(operatorsv1alpha1.OperatorSpec{
88+
PackageName: "package",
89+
Channel: invalidChannel,
90+
}))
91+
Expect(err).To(HaveOccurred(), "expected error for invalid channel '%q'", invalidChannel)
92+
Expect(err.Error()).To(ContainSubstring("spec.channel in body should match '^[a-z0-9]+([\\.-][a-z0-9]+)*$'"))
93+
}
94+
})
95+
It("should pass if a valid channel name is given", func() {
96+
validChannels := []string{
97+
"hyphenated-name",
98+
"dotted.name",
99+
"channel-has-version-1.0.1",
100+
}
101+
for _, validChannel := range validChannels {
102+
op := operator(operatorsv1alpha1.OperatorSpec{
103+
PackageName: "package",
104+
Channel: validChannel,
105+
})
106+
err := cl.Create(ctx, op)
107+
Expect(err).NotTo(HaveOccurred(), "unexpected error creating valid channel '%q': %w", validChannel, err)
108+
err = cl.Delete(ctx, op)
109+
Expect(err).NotTo(HaveOccurred(), "unexpected error deleting valid channel '%q': %w", validChannel, err)
110+
}
111+
})
112+
It("should fail if an invalid channel name length", func() {
113+
err := cl.Create(ctx, operator(operatorsv1alpha1.OperatorSpec{
114+
PackageName: "package",
115+
Channel: "longname01234567890123456789012345678901234567890",
116+
}))
117+
Expect(err).To(HaveOccurred(), "expected error for invalid channel length")
118+
Expect(err.Error()).To(ContainSubstring("spec.channel: Too long: may not be longer than 48"))
119+
})
75120
})

controllers/operator_controller_test.go

+200-1
Original file line numberDiff line numberDiff line change
@@ -85,7 +85,7 @@ var _ = Describe("Operator Controller Test", func() {
8585
var pkgName string
8686
BeforeEach(func() {
8787
By("initializing cluster state")
88-
pkgName = fmt.Sprintf("exists-%s", rand.String(6))
88+
pkgName = "prometheus"
8989
operator = &operatorsv1alpha1.Operator{
9090
ObjectMeta: metav1.ObjectMeta{Name: opKey.Name},
9191
Spec: operatorsv1alpha1.OperatorSpec{
@@ -594,6 +594,205 @@ var _ = Describe("Operator Controller Test", func() {
594594
})
595595

596596
})
597+
When("the operator specifies a channel with version that exist", func() {
598+
var pkgName string
599+
var pkgVer string
600+
var pkgChan string
601+
BeforeEach(func() {
602+
By("initializing cluster state")
603+
pkgName = "prometheus"
604+
pkgVer = "0.47.0"
605+
pkgChan = "beta"
606+
operator = &operatorsv1alpha1.Operator{
607+
ObjectMeta: metav1.ObjectMeta{Name: opKey.Name},
608+
Spec: operatorsv1alpha1.OperatorSpec{
609+
PackageName: pkgName,
610+
Version: pkgVer,
611+
Channel: pkgChan,
612+
},
613+
}
614+
err := cl.Create(ctx, operator)
615+
Expect(err).NotTo(HaveOccurred())
616+
})
617+
It("sets resolution success status", func() {
618+
By("running reconcile")
619+
res, err := reconciler.Reconcile(ctx, ctrl.Request{NamespacedName: opKey})
620+
Expect(res).To(Equal(ctrl.Result{}))
621+
Expect(err).NotTo(HaveOccurred())
622+
623+
By("fetching updated operator after reconcile")
624+
Expect(cl.Get(ctx, opKey, operator)).NotTo(HaveOccurred())
625+
626+
By("checking the expected conditions")
627+
cond := apimeta.FindStatusCondition(operator.Status.Conditions, operatorsv1alpha1.TypeReady)
628+
Expect(cond).NotTo(BeNil())
629+
Expect(cond.Status).To(Equal(metav1.ConditionUnknown))
630+
Expect(cond.Reason).To(Equal(operatorsv1alpha1.ReasonInstallationStatusUnknown))
631+
Expect(cond.Message).To(ContainSubstring("waiting for BundleDeployment"))
632+
633+
By("fetching the bundled deployment")
634+
bd := &rukpakv1alpha1.BundleDeployment{}
635+
Expect(cl.Get(ctx, types.NamespacedName{Name: opKey.Name}, bd)).NotTo(HaveOccurred())
636+
Expect(bd.Spec.ProvisionerClassName).To(Equal("core-rukpak-io-plain"))
637+
Expect(bd.Spec.Template.Spec.ProvisionerClassName).To(Equal("core-rukpak-io-registry"))
638+
Expect(bd.Spec.Template.Spec.Source.Type).To(Equal(rukpakv1alpha1.SourceTypeImage))
639+
Expect(bd.Spec.Template.Spec.Source.Image).NotTo(BeNil())
640+
Expect(bd.Spec.Template.Spec.Source.Image.Ref).To(Equal("quay.io/operatorhubio/prometheus@sha256:5b04c49d8d3eff6a338b56ec90bdf491d501fe301c9cdfb740e5bff6769a21ed"))
641+
})
642+
})
643+
When("the operator specifies a package that exists within a channel but no version specified", func() {
644+
var pkgName string
645+
var pkgVer string
646+
var pkgChan string
647+
BeforeEach(func() {
648+
By("initializing cluster state")
649+
pkgName = "prometheus"
650+
pkgChan = "beta"
651+
operator = &operatorsv1alpha1.Operator{
652+
ObjectMeta: metav1.ObjectMeta{Name: opKey.Name},
653+
Spec: operatorsv1alpha1.OperatorSpec{
654+
PackageName: pkgName,
655+
Version: pkgVer,
656+
Channel: pkgChan,
657+
},
658+
}
659+
err := cl.Create(ctx, operator)
660+
Expect(err).NotTo(HaveOccurred())
661+
})
662+
It("sets resolution success status", func() {
663+
By("running reconcile")
664+
res, err := reconciler.Reconcile(ctx, ctrl.Request{NamespacedName: opKey})
665+
Expect(res).To(Equal(ctrl.Result{}))
666+
Expect(err).NotTo(HaveOccurred())
667+
668+
By("fetching updated operator after reconcile")
669+
Expect(cl.Get(ctx, opKey, operator)).NotTo(HaveOccurred())
670+
671+
By("checking the expected conditions")
672+
cond := apimeta.FindStatusCondition(operator.Status.Conditions, operatorsv1alpha1.TypeReady)
673+
Expect(cond).NotTo(BeNil())
674+
Expect(cond.Status).To(Equal(metav1.ConditionUnknown))
675+
Expect(cond.Reason).To(Equal(operatorsv1alpha1.ReasonInstallationStatusUnknown))
676+
Expect(cond.Message).To(ContainSubstring("waiting for BundleDeployment"))
677+
678+
By("fetching the bundled deployment")
679+
bd := &rukpakv1alpha1.BundleDeployment{}
680+
Expect(cl.Get(ctx, types.NamespacedName{Name: opKey.Name}, bd)).NotTo(HaveOccurred())
681+
Expect(bd.Spec.ProvisionerClassName).To(Equal("core-rukpak-io-plain"))
682+
Expect(bd.Spec.Template.Spec.ProvisionerClassName).To(Equal("core-rukpak-io-registry"))
683+
Expect(bd.Spec.Template.Spec.Source.Type).To(Equal(rukpakv1alpha1.SourceTypeImage))
684+
Expect(bd.Spec.Template.Spec.Source.Image).NotTo(BeNil())
685+
Expect(bd.Spec.Template.Spec.Source.Image.Ref).To(Equal("quay.io/operatorhubio/prometheus@sha256:5b04c49d8d3eff6a338b56ec90bdf491d501fe301c9cdfb740e5bff6769a21ed"))
686+
})
687+
})
688+
When("the operator specifies a package version in a channel that does not exist", func() {
689+
var pkgName string
690+
var pkgVer string
691+
var pkgChan string
692+
BeforeEach(func() {
693+
By("initializing cluster state")
694+
pkgName = "prometheus"
695+
pkgVer = "0.47.0"
696+
pkgChan = "alpha"
697+
operator = &operatorsv1alpha1.Operator{
698+
ObjectMeta: metav1.ObjectMeta{Name: opKey.Name},
699+
Spec: operatorsv1alpha1.OperatorSpec{
700+
PackageName: pkgName,
701+
Version: pkgVer,
702+
Channel: pkgChan,
703+
},
704+
}
705+
err := cl.Create(ctx, operator)
706+
Expect(err).NotTo(HaveOccurred())
707+
})
708+
It("sets resolution failure status", func() {
709+
By("running reconcile")
710+
res, err := reconciler.Reconcile(ctx, ctrl.Request{NamespacedName: opKey})
711+
Expect(res).To(Equal(ctrl.Result{}))
712+
Expect(err).To(MatchError(fmt.Sprintf("package '%s' at version '%s' in channel '%s' not found", pkgName, pkgVer, pkgChan)))
713+
714+
By("fetching updated operator after reconcile")
715+
Expect(cl.Get(ctx, opKey, operator)).NotTo(HaveOccurred())
716+
717+
By("checking the expected conditions")
718+
cond := apimeta.FindStatusCondition(operator.Status.Conditions, operatorsv1alpha1.TypeReady)
719+
Expect(cond).NotTo(BeNil())
720+
Expect(cond.Status).To(Equal(metav1.ConditionFalse))
721+
Expect(cond.Reason).To(Equal(operatorsv1alpha1.ReasonResolutionFailed))
722+
Expect(cond.Message).To(Equal(fmt.Sprintf("package '%s' at version '%s' in channel '%s' not found", pkgName, pkgVer, pkgChan)))
723+
})
724+
})
725+
When("the operator specifies a package in a channel that does not exist", func() {
726+
var pkgName string
727+
var pkgChan string
728+
BeforeEach(func() {
729+
By("initializing cluster state")
730+
pkgName = "prometheus"
731+
pkgChan = "alpha"
732+
operator = &operatorsv1alpha1.Operator{
733+
ObjectMeta: metav1.ObjectMeta{Name: opKey.Name},
734+
Spec: operatorsv1alpha1.OperatorSpec{
735+
PackageName: pkgName,
736+
Channel: pkgChan,
737+
},
738+
}
739+
err := cl.Create(ctx, operator)
740+
Expect(err).NotTo(HaveOccurred())
741+
})
742+
It("sets resolution failure status", func() {
743+
By("running reconcile")
744+
res, err := reconciler.Reconcile(ctx, ctrl.Request{NamespacedName: opKey})
745+
Expect(res).To(Equal(ctrl.Result{}))
746+
Expect(err).To(MatchError(fmt.Sprintf("package '%s' in channel '%s' not found", pkgName, pkgChan)))
747+
748+
By("fetching updated operator after reconcile")
749+
Expect(cl.Get(ctx, opKey, operator)).NotTo(HaveOccurred())
750+
751+
By("checking the expected conditions")
752+
cond := apimeta.FindStatusCondition(operator.Status.Conditions, operatorsv1alpha1.TypeReady)
753+
Expect(cond).NotTo(BeNil())
754+
Expect(cond.Status).To(Equal(metav1.ConditionFalse))
755+
Expect(cond.Reason).To(Equal(operatorsv1alpha1.ReasonResolutionFailed))
756+
Expect(cond.Message).To(Equal(fmt.Sprintf("package '%s' in channel '%s' not found", pkgName, pkgChan)))
757+
})
758+
})
759+
When("the operator specifies a package version that does not exist in the channel", func() {
760+
var pkgName string
761+
var pkgVer string
762+
var pkgChan string
763+
BeforeEach(func() {
764+
By("initializing cluster state")
765+
pkgName = "prometheus"
766+
pkgVer = "0.57.0"
767+
pkgChan = "beta"
768+
operator = &operatorsv1alpha1.Operator{
769+
ObjectMeta: metav1.ObjectMeta{Name: opKey.Name},
770+
Spec: operatorsv1alpha1.OperatorSpec{
771+
PackageName: pkgName,
772+
Version: pkgVer,
773+
Channel: pkgChan,
774+
},
775+
}
776+
err := cl.Create(ctx, operator)
777+
Expect(err).NotTo(HaveOccurred())
778+
})
779+
It("sets resolution failure status", func() {
780+
By("running reconcile")
781+
res, err := reconciler.Reconcile(ctx, ctrl.Request{NamespacedName: opKey})
782+
Expect(res).To(Equal(ctrl.Result{}))
783+
Expect(err).To(MatchError(fmt.Sprintf("package '%s' at version '%s' in channel '%s' not found", pkgName, pkgVer, pkgChan)))
784+
785+
By("fetching updated operator after reconcile")
786+
Expect(cl.Get(ctx, opKey, operator)).NotTo(HaveOccurred())
787+
788+
By("checking the expected conditions")
789+
cond := apimeta.FindStatusCondition(operator.Status.Conditions, operatorsv1alpha1.TypeReady)
790+
Expect(cond).NotTo(BeNil())
791+
Expect(cond.Status).To(Equal(metav1.ConditionFalse))
792+
Expect(cond.Reason).To(Equal(operatorsv1alpha1.ReasonResolutionFailed))
793+
Expect(cond.Message).To(Equal(fmt.Sprintf("package '%s' at version '%s' in channel '%s' not found", pkgName, pkgVer, pkgChan)))
794+
})
795+
})
597796
AfterEach(func() {
598797
verifyInvariants(ctx, operator)
599798

internal/resolution/variable_sources/olm/olm.go

+3
Original file line numberDiff line numberDiff line change
@@ -46,5 +46,8 @@ func (o *OLMVariableSource) requiredPackageFromOperator(operator *operatorsv1alp
4646
if operator.Spec.Version != "" {
4747
opts = append(opts, required_package.InVersionRange(operator.Spec.Version))
4848
}
49+
if operator.Spec.Channel != "" {
50+
opts = append(opts, required_package.InChannel(operator.Spec.Channel))
51+
}
4952
return required_package.NewRequiredPackage(operator.Spec.PackageName, opts...)
5053
}

internal/resolution/variable_sources/required_package/required_package.go

+17
Original file line numberDiff line numberDiff line change
@@ -54,9 +54,20 @@ func InVersionRange(versionRange string) RequiredPackageOption {
5454
}
5555
}
5656

57+
func InChannel(channelName string) RequiredPackageOption {
58+
return func(r *RequiredPackageVariableSource) error {
59+
if channelName != "" {
60+
r.channelName = channelName
61+
r.predicates = append(r.predicates, predicates.InChannel(channelName))
62+
}
63+
return nil
64+
}
65+
}
66+
5767
type RequiredPackageVariableSource struct {
5868
packageName string
5969
versionRange string
70+
channelName string
6071
predicates []input.Predicate
6172
}
6273

@@ -99,8 +110,14 @@ func (r *RequiredPackageVariableSource) notFoundError() error {
99110
// context: we originally wanted to support version ranges and take the highest version that satisfies the range
100111
// during the upstream call on the 2023-04-11 we decided to pin the version instead. But, we'll keep version range
101112
// support under the covers in case we decide to pivot back.
113+
if r.versionRange != "" && r.channelName != "" {
114+
return fmt.Errorf("package '%s' at version '%s' in channel '%s' not found", r.packageName, r.versionRange, r.channelName)
115+
}
102116
if r.versionRange != "" {
103117
return fmt.Errorf("package '%s' at version '%s' not found", r.packageName, r.versionRange)
104118
}
119+
if r.channelName != "" {
120+
return fmt.Errorf("package '%s' in channel '%s' not found", r.packageName, r.channelName)
121+
}
105122
return fmt.Errorf("package '%s' not found", r.packageName)
106123
}

0 commit comments

Comments
 (0)