Skip to content

Commit cac4f5c

Browse files
committed
[defaults] Take ownership of default sources
- Add a command line parameter (-defaultsDir) to pass the folder where defaults are stored - Update the operator deployment to include the new parameter - Move the default OperatorSources from the manifests folder to the default folder - Update the Dockerfile.rhel to include the default folder - Add code to startup that ensures the defaults OperatorSources are present with default spec after initialization - Add code to the OperatorSource's deleted and succeeded reconciler to ensure the default OperatorSources are present and match the default spec
1 parent 1ccddf0 commit cac4f5c

File tree

15 files changed

+340
-2
lines changed

15 files changed

+340
-2
lines changed

Dockerfile

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ RUN useradd marketplace-operator
99
USER marketplace-operator
1010
COPY --from=builder /go/src/github.com/operator-framework/operator-marketplace/build/_output/bin/marketplace-operator /usr/bin
1111
ADD manifests /manifests
12+
ADD defaults /defaults
1213

1314
LABEL io.k8s.display-name="OpenShift Marketplace Operator" \
1415
io.k8s.description="This is a component of OpenShift Container Platform and manages the OpenShift Marketplace." \

Dockerfile.rhel7

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ RUN useradd marketplace-operator
99
USER marketplace-operator
1010
COPY --from=builder /go/src/github.com/operator-framework/operator-marketplace/build/_output/bin/marketplace-operator /usr/bin
1111
ADD manifests /manifests
12+
ADD defaults /defaults
1213

1314
LABEL io.k8s.display-name="OpenShift Marketplace Operator" \
1415
io.k8s.description="This is a component of OpenShift Container Platform and manages the OpenShift Marketplace." \
@@ -17,4 +18,4 @@ LABEL io.k8s.display-name="OpenShift Marketplace Operator" \
1718
maintainer="AOS Marketplace <aos-marketplace@redhat.com>"
1819

1920
# entrypoint specified in operator.yaml as `marketplace-operator`
20-
CMD ["/usr/bin/marketplace-operator"]
21+
CMD ["/usr/bin/marketplace-operator"]

cmd/manager/main.go

Lines changed: 40 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ import (
1414
"github.com/operator-framework/operator-marketplace/pkg/apis"
1515
"github.com/operator-framework/operator-marketplace/pkg/catalogsourceconfig"
1616
"github.com/operator-framework/operator-marketplace/pkg/controller"
17+
"github.com/operator-framework/operator-marketplace/pkg/defaults"
1718
"github.com/operator-framework/operator-marketplace/pkg/operatorsource"
1819
"github.com/operator-framework/operator-marketplace/pkg/proxy"
1920
"github.com/operator-framework/operator-marketplace/pkg/registry"
@@ -22,8 +23,11 @@ import (
2223
"github.com/operator-framework/operator-sdk/pkg/leader"
2324
sdkVersion "github.com/operator-framework/operator-sdk/version"
2425
log "github.com/sirupsen/logrus"
26+
kruntime "k8s.io/apimachinery/pkg/runtime"
2527
"k8s.io/client-go/kubernetes"
2628
_ "k8s.io/client-go/plugin/pkg/client/auth/gcp"
29+
"k8s.io/client-go/rest"
30+
"sigs.k8s.io/controller-runtime/pkg/client"
2731
"sigs.k8s.io/controller-runtime/pkg/client/config"
2832
"sigs.k8s.io/controller-runtime/pkg/manager"
2933
)
@@ -46,9 +50,11 @@ func printVersion() {
4650
func main() {
4751
printVersion()
4852

49-
// Parse the command line arguments for the registry server image
53+
// Parse the command line arguments
5054
flag.StringVar(&registry.ServerImage, "registryServerImage",
5155
registry.DefaultServerImage, "the image to use for creating the operator registry pod")
56+
flag.StringVar(&defaults.Dir, "defaultsDir",
57+
"", "the directory where the default OperatorSources are stored")
5258
flag.Parse()
5359

5460
namespace, err := k8sutil.GetWatchNamespace()
@@ -127,6 +133,19 @@ func main() {
127133
log.Info("Elected leader.")
128134

129135
log.Print("Starting the Cmd.")
136+
137+
// Populate the default OperatorSources tracker
138+
err = defaults.PopulateTracker()
139+
if err != nil {
140+
exit(err)
141+
}
142+
143+
// Handle the defaults
144+
err = ensureDefaults(cfg, mgr.GetScheme())
145+
if err != nil {
146+
exit(err)
147+
}
148+
130149
stopCh := signals.SetupSignalHandler()
131150

132151
// statusReportingDoneCh will be closed after the operator has successfully stopped reporting ClusterOperator status.
@@ -155,3 +174,23 @@ func exit(err error) {
155174
log.Info("The operator exited gracefully, exit code 0")
156175
os.Exit(0)
157176
}
177+
178+
// ensureDefaults ensures that all the default OperatorSources are present on
179+
// the cluster
180+
func ensureDefaults(cfg *rest.Config, scheme *kruntime.Scheme) error {
181+
// The default client serves read requests from the cache which only gets
182+
// initialized after mgr.Start(). So we need to instantiate a new client
183+
// for the defaults handler.
184+
clientForDefaults, err := client.New(cfg, client.Options{Scheme: scheme})
185+
if err != nil {
186+
log.Errorf("Error initializing client for handling defaults - %v", err)
187+
return err
188+
}
189+
190+
err = defaults.New().EnsureAll(clientForDefaults)
191+
if err != nil {
192+
log.Errorf("[defaults] Error ensuring default OperatorSource(s) - %v", err)
193+
}
194+
195+
return err
196+
}
File renamed without changes.
File renamed without changes.
File renamed without changes.

deploy/operator.yaml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@ spec:
4141
- marketplace-operator
4242
args:
4343
- -registryServerImage=quay.io/openshift/origin-operator-registry
44+
- -defaultsDir=/defaults
4445
imagePullPolicy: Always
4546
livenessProbe:
4647
httpGet:
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@ spec:
4141
- marketplace-operator
4242
args:
4343
- -registryServerImage=quay.io/openshift/origin-operator-registry
44+
- -defaultsDir=/defaults
4445
imagePullPolicy: IfNotPresent
4546
livenessProbe:
4647
httpGet:

pkg/defaults/defaults.go

Lines changed: 157 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,157 @@
1+
package defaults
2+
3+
import (
4+
"context"
5+
"fmt"
6+
"io/ioutil"
7+
"os"
8+
"path/filepath"
9+
10+
"github.com/operator-framework/operator-marketplace/pkg/apis/operators/v1"
11+
wrapper "github.com/operator-framework/operator-marketplace/pkg/client"
12+
"github.com/sirupsen/logrus"
13+
"k8s.io/apimachinery/pkg/api/errors"
14+
utilerrors "k8s.io/apimachinery/pkg/util/errors"
15+
"k8s.io/apimachinery/pkg/util/yaml"
16+
)
17+
18+
var (
19+
// Dir is the directory where the default OperatorSource definitions are
20+
// placed on disk. It will be empty if defaults are not required.
21+
Dir string
22+
23+
// defaultsTracker is used to keep an in-memory record of default
24+
// OperatorSources. It is a map of OperatorSource name to the OperatorSource
25+
// definition in the defaults directory.
26+
defaultsTracker = make(map[string]v1.OperatorSource)
27+
)
28+
29+
// Defaults is the interface that can be used to ensure default OperatorSources
30+
// are always present on the cluster.
31+
type Defaults interface {
32+
EnsureAll(client wrapper.Client) error
33+
Ensure(client wrapper.Client, opsrcName string) error
34+
}
35+
36+
type defaults struct {
37+
}
38+
39+
// New returns a the singleton defaults
40+
func New() Defaults {
41+
return &defaults{}
42+
}
43+
44+
// Ensure checks if the given OperatorSource source is one of the
45+
// defaults and if it is, it ensures it is present on the cluster.
46+
func (d *defaults) Ensure(client wrapper.Client, opsrcName string) error {
47+
opsrc, present := defaultsTracker[opsrcName]
48+
if !present {
49+
return nil
50+
}
51+
52+
err := processOpSrc(client, opsrc)
53+
if err != nil {
54+
return err
55+
}
56+
57+
return nil
58+
}
59+
60+
// EnsureAll processes all the default OperatorSources and ensures they are
61+
// present on the cluster.
62+
func (d *defaults) EnsureAll(client wrapper.Client) error {
63+
allErrors := []error{}
64+
for name := range defaultsTracker {
65+
err := d.Ensure(client, name)
66+
if err != nil {
67+
allErrors = append(allErrors, fmt.Errorf("Error handling %s - %v", name, err))
68+
}
69+
}
70+
return utilerrors.NewAggregate(allErrors)
71+
}
72+
73+
// PopulateTracker populates the defaultsTracker on initialization
74+
func PopulateTracker() error {
75+
// Default directory has not been specified
76+
if Dir == "" {
77+
return nil
78+
}
79+
80+
_, err := os.Stat(Dir)
81+
if err != nil {
82+
return err
83+
}
84+
85+
fileInfos, err := ioutil.ReadDir(Dir)
86+
if err != nil {
87+
return err
88+
}
89+
90+
for _, fileInfo := range fileInfos {
91+
fileName := fileInfo.Name()
92+
opsrc, err := getOpSrcDefinition(fileName)
93+
if err != nil {
94+
// Reinitialize the tracker as we hard error on even one failure
95+
defaultsTracker = make(map[string]v1.OperatorSource)
96+
return err
97+
}
98+
defaultsTracker[opsrc.Name] = *opsrc
99+
}
100+
return nil
101+
}
102+
103+
// getOpSrcDefinition returns an OperatorSource definition from the given file
104+
// in the defaults directory. It only supports decoding OperatorSources. Any
105+
// other resource type will result in an error.
106+
func getOpSrcDefinition(fileName string) (*v1.OperatorSource, error) {
107+
file, err := os.Open(filepath.Join(Dir, fileName))
108+
if err != nil {
109+
return nil, err
110+
}
111+
112+
opsrc := &v1.OperatorSource{}
113+
decoder := yaml.NewYAMLOrJSONDecoder(file, 1024)
114+
err = decoder.Decode(opsrc)
115+
if err != nil {
116+
return nil, err
117+
}
118+
return opsrc, nil
119+
}
120+
121+
// processOpSrc will ensure that the given OperatorSource exists on the cluster.
122+
func processOpSrc(client wrapper.Client, defaultOpsrc v1.OperatorSource) error {
123+
// Get OperatorSource on the cluster
124+
opsrcCluster := &v1.OperatorSource{}
125+
err := client.Get(context.TODO(), wrapper.ObjectKey{
126+
Name: defaultOpsrc.Name,
127+
Namespace: defaultOpsrc.Namespace},
128+
opsrcCluster)
129+
if err != nil && !errors.IsNotFound(err) {
130+
return err
131+
}
132+
133+
// Create if not present or is deleted
134+
if errors.IsNotFound(err) || !opsrcCluster.ObjectMeta.DeletionTimestamp.IsZero() {
135+
err = client.Create(context.TODO(), &defaultOpsrc)
136+
if err != nil {
137+
return err
138+
}
139+
logrus.Infof("[defaults] Creating OperatorSource %s", defaultOpsrc.Name)
140+
return nil
141+
}
142+
143+
if defaultOpsrc.Spec.IsEqual(&opsrcCluster.Spec) {
144+
logrus.Infof("[defaults] OperatorSource %s default and on cluster specs are same", defaultOpsrc.Name)
145+
return nil
146+
}
147+
148+
// Update if the spec has changed
149+
opsrcCluster.Spec = defaultOpsrc.Spec
150+
err = client.Update(context.TODO(), opsrcCluster)
151+
if err != nil {
152+
return err
153+
}
154+
logrus.Infof("[defaults] Restoring OperatorSource %s", defaultOpsrc.Name)
155+
156+
return nil
157+
}

0 commit comments

Comments
 (0)