Skip to content

Commit 535c2cd

Browse files
Add library to interact with operator conditions (#43)
* [operator_conditions] Add library to interact with operator conditions This PR removes the previous operator_conditions package and introduces a `Conditions` interface, using which one can: 1. Get an existing condition from the operator conditions CR in cluster. 2. Create/Update a condition in the operator condition CR. * Add envtest binaries
1 parent 94fc682 commit 535c2cd

File tree

11 files changed

+666
-453
lines changed

11 files changed

+666
-453
lines changed

Makefile

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
SOURCES := $(shell find . -name '*.go' -not -path "*/vendor/*" -not -path "*/.git/*")
22
.DEFAULT_GOAL := build
3+
ENVTEST_ASSETS_DIR = $(shell pwd)/testbin
4+
SHELL = /bin/bash
35

46
build: $(SOURCES) ## Build Test
57
go build -i -ldflags="-s -w" ./...
@@ -16,7 +18,11 @@ fmt: ## Run go fmt
1618
fmtcheck: ## Check go formatting
1719
@gofmt -l $(SOURCES) | grep ".*\.go"; if [ "$$?" = "0" ]; then exit 1; fi
1820

21+
export KUBEBUILDER_ASSETS := $(ENVTEST_ASSETS_DIR)/bin
1922
test: ## Run unit tests
23+
mkdir -p $(ENVTEST_ASSETS_DIR)
24+
test -f $(ENVTEST_ASSETS_DIR)/setup-envtest.sh || curl -sSLo $(ENVTEST_ASSETS_DIR)/setup-envtest.sh https://raw.githubusercontent.com/kubernetes-sigs/controller-runtime/v0.6.3/hack/setup-envtest.sh
25+
. $(ENVTEST_ASSETS_DIR)/setup-envtest.sh; fetch_envtest_tools $(ENVTEST_ASSETS_DIR); setup_envtest_env $(ENVTEST_ASSETS_DIR)
2026
@go test -race -covermode atomic -coverprofile cover.out ./...
2127

2228
vet: ## Run go vet

conditions/conditions.go

Lines changed: 127 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,127 @@
1+
// Copyright 2020 The Operator-SDK Authors
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
package conditions
16+
17+
import (
18+
"context"
19+
"fmt"
20+
"os"
21+
22+
apiv1 "github.com/operator-framework/api/pkg/operators/v1"
23+
"github.com/operator-framework/operator-lib/internal/utils"
24+
"k8s.io/apimachinery/pkg/api/meta"
25+
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
26+
"k8s.io/apimachinery/pkg/types"
27+
"sigs.k8s.io/controller-runtime/pkg/client"
28+
)
29+
30+
var (
31+
// ErrNoOperatorCondition indicates that the operator condition CRD is nil
32+
ErrNoOperatorCondition = fmt.Errorf("operator Condition CRD is nil")
33+
34+
// readNamespace gets the namespacedName of the operator.
35+
readNamespace = utils.GetOperatorNamespace
36+
)
37+
38+
const (
39+
// operatorCondEnvVar is the env variable which
40+
// contains the name of the Condition CR associated to the operator,
41+
// set by OLM.
42+
operatorCondEnvVar = "OPERATOR_CONDITION_NAME"
43+
)
44+
45+
// condition is a Condition that gets and sets a specific
46+
// conditionType in the OperatorCondition CR.
47+
type condition struct {
48+
namespacedName types.NamespacedName
49+
condType apiv1.ConditionType
50+
client client.Client
51+
}
52+
53+
var _ Condition = &condition{}
54+
55+
// NewCondition returns a new Condition interface using the provided client
56+
// for the specified conditionType. The condition will internally fetch the namespacedName
57+
// of the operatorConditionCRD.
58+
func NewCondition(cl client.Client, condType apiv1.ConditionType) (Condition, error) {
59+
objKey, err := GetNamespacedName()
60+
if err != nil {
61+
return nil, err
62+
}
63+
return &condition{
64+
namespacedName: *objKey,
65+
condType: condType,
66+
client: cl,
67+
}, nil
68+
}
69+
70+
// Get implements conditions.Get
71+
func (c *condition) Get(ctx context.Context) (*metav1.Condition, error) {
72+
operatorCond := &apiv1.OperatorCondition{}
73+
err := c.client.Get(ctx, c.namespacedName, operatorCond)
74+
if err != nil {
75+
return nil, ErrNoOperatorCondition
76+
}
77+
con := meta.FindStatusCondition(operatorCond.Status.Conditions, string(c.condType))
78+
79+
if con == nil {
80+
return nil, fmt.Errorf("conditionType %v not found", c.condType)
81+
}
82+
return con, nil
83+
}
84+
85+
// Set implements conditions.Set
86+
func (c *condition) Set(ctx context.Context, status metav1.ConditionStatus, option ...Option) error {
87+
operatorCond := &apiv1.OperatorCondition{}
88+
err := c.client.Get(ctx, c.namespacedName, operatorCond)
89+
if err != nil {
90+
return ErrNoOperatorCondition
91+
}
92+
93+
newCond := &metav1.Condition{
94+
Type: string(c.condType),
95+
Status: status,
96+
}
97+
98+
if len(option) != 0 {
99+
for _, opt := range option {
100+
opt(newCond)
101+
}
102+
}
103+
meta.SetStatusCondition(&operatorCond.Status.Conditions, *newCond)
104+
err = c.client.Status().Update(ctx, operatorCond)
105+
if err != nil {
106+
return err
107+
}
108+
return nil
109+
}
110+
111+
// GetNamespacedName returns the NamespacedName of the CR. It returns an error
112+
// when the name of the CR cannot be found from the environment variable set by
113+
// OLM. Hence, GetNamespacedName() can provide the NamespacedName when the operator
114+
// is running on cluster and is being managed by OLM. If running locally, operator
115+
// writers are encouraged to skip this method or gracefully handle the errors by logging
116+
// a message.
117+
func GetNamespacedName() (*types.NamespacedName, error) {
118+
conditionName := os.Getenv(operatorCondEnvVar)
119+
if conditionName == "" {
120+
return nil, fmt.Errorf("could not determine operator condition name: environment variable %s not set", operatorCondEnvVar)
121+
}
122+
operatorNs, err := readNamespace()
123+
if err != nil {
124+
return nil, fmt.Errorf("could not determine operator namespace: %v", err)
125+
}
126+
return &types.NamespacedName{Name: conditionName, Namespace: operatorNs}, nil
127+
}
Lines changed: 115 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,115 @@
1+
// Copyright 2020 The Operator-SDK Authors
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
package conditions
16+
17+
import (
18+
"fmt"
19+
"os"
20+
"os/exec"
21+
"path/filepath"
22+
"testing"
23+
24+
. "github.com/onsi/ginkgo"
25+
. "github.com/onsi/gomega"
26+
apiv1 "github.com/operator-framework/api/pkg/operators/v1"
27+
"k8s.io/apimachinery/pkg/runtime"
28+
"k8s.io/apimachinery/pkg/util/rand"
29+
"k8s.io/client-go/rest"
30+
"sigs.k8s.io/controller-runtime/pkg/envtest"
31+
"sigs.k8s.io/controller-runtime/pkg/envtest/printer"
32+
logf "sigs.k8s.io/controller-runtime/pkg/log"
33+
"sigs.k8s.io/controller-runtime/pkg/log/zap"
34+
)
35+
36+
func TestSource(t *testing.T) {
37+
RegisterFailHandler(Fail)
38+
RunSpecsWithDefaultAndCustomReporters(t, "Conditions Suite", []Reporter{printer.NewlineReporter{}, printer.NewProwReporter("Conditions Suite")})
39+
}
40+
41+
var testenv *envtest.Environment
42+
var cfg *rest.Config
43+
var sch = runtime.NewScheme()
44+
var err error
45+
var tempDir = fmt.Sprintf("%s_%d", "temp", rand.Int63nRange(0, 1000000))
46+
47+
const (
48+
olmYAMLURL = "https://github.com/operator-framework/operator-lifecycle-manager/releases/download/v0.17.0/olm.yaml"
49+
crdsYAMLURL = "https://github.com/operator-framework/operator-lifecycle-manager/releases/download/v0.17.0/crds.yaml"
50+
51+
// TODO: Remove this once OLM releases operator conditions CRD set
52+
condCRDYAML = "https://raw.githubusercontent.com/dinhxuanvu/operator-lifecycle-manager/create-operatorconditions-for-operator/deploy/chart/crds/0000_50_olm_00-operatorconditions.crd.yaml"
53+
)
54+
55+
var _ = BeforeSuite(func(done Done) {
56+
logf.SetLogger(zap.New(zap.WriteTo(GinkgoWriter), zap.UseDevMode(true)))
57+
58+
err = getOLMManifests()
59+
Expect(err).NotTo(HaveOccurred())
60+
// Add operator apiv1 to scheme
61+
err = apiv1.AddToScheme(sch)
62+
Expect(err).NotTo(HaveOccurred())
63+
64+
testenv = &envtest.Environment{}
65+
testenv.CRDInstallOptions = envtest.CRDInstallOptions{
66+
Paths: []string{tempDir},
67+
}
68+
69+
cfg, err = testenv.Start()
70+
Expect(err).NotTo(HaveOccurred())
71+
72+
close(done)
73+
}, 60)
74+
75+
var _ = AfterSuite(func() {
76+
// remove tmp folder
77+
os.RemoveAll(tempDir)
78+
Expect(err).NotTo(HaveOccurred())
79+
Expect(testenv.Stop()).To(Succeed())
80+
})
81+
82+
func getOLMManifests() error {
83+
// create a directory
84+
cmd := exec.Command("mkdir", tempDir)
85+
err := cmd.Run()
86+
if err != nil {
87+
return err
88+
}
89+
90+
// fetch manifests to install olm
91+
err = getYAML(filepath.Join(tempDir, "olm.yaml"), olmYAMLURL)
92+
if err != nil {
93+
return fmt.Errorf("error fetching olm.yaml %v", err)
94+
}
95+
96+
err = getYAML(filepath.Join(tempDir, "crds.yaml"), crdsYAMLURL)
97+
if err != nil {
98+
return fmt.Errorf("error fetching crds.yaml %v", err)
99+
}
100+
101+
err = getYAML(filepath.Join(tempDir, "operatorconditions.crd.yaml"), condCRDYAML)
102+
if err != nil {
103+
return fmt.Errorf("error fetching operator conditions crd %v", err)
104+
}
105+
return nil
106+
}
107+
108+
func getYAML(file, url string) error {
109+
cmd := exec.Command("curl", "-sSLo", file, url)
110+
err := cmd.Run()
111+
if err != nil {
112+
return err
113+
}
114+
return nil
115+
}

0 commit comments

Comments
 (0)