Skip to content
This repository was archived by the owner on Jun 26, 2024. It is now read-only.

Commit 64fd7e5

Browse files
committed
Add install-tools and local-env rules to Makefiles
If required tool version is found on the host, then it will be linked into the ./bin folder. Otherwise, it will be downloaded. Signed-off-by: Francesco Ilario <[email protected]>
1 parent ba95a74 commit 64fd7e5

File tree

5 files changed

+274
-44
lines changed

5 files changed

+274
-44
lines changed

CONTRIBUTING.md

Lines changed: 46 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -174,36 +174,76 @@ put us into an unreleasable state
174174
* End to end, i.e. acceptance tests
175175
* Unit tests:
176176
* Coverage should remain the same or increase
177-
177+
178+
## Configure your local environment
179+
180+
To compile and execute the Service Binding Operator, you must have on your machine the following dependencies:
181+
182+
* Git
183+
* Go
184+
* Python3
185+
* Make
186+
* Docker
187+
188+
Other dependencies are needed, but you can install them locally using the make rule `install-tools`.
189+
This rule will download into the local `./bin` folder all the missing tools needed to work with this repo.
190+
So, to install the dependencies locally and configure your shell to use them, use the following commands:
191+
192+
```
193+
make install-tools
194+
eval $(make local-env)
195+
```
196+
197+
If not present, the following dependencies will be downloaded:
198+
199+
* minikube
200+
* opm
201+
* mockgen
202+
* kubectl-slice
203+
* yq
204+
* kustomize
205+
* controller-gen
206+
* gen-mocks
207+
* operator-sdk
208+
* kubectl
209+
* helm
210+
178211
## Running Acceptance Tests
179212

180-
1. Set KUBECONFIG for both minikube and acceptance tests (it will be generated at minikube's start if it does not exist):
213+
1. Install dependencies
214+
215+
```
216+
make install-tools
217+
eval $(make local-env)
218+
```
219+
220+
2. Set KUBECONFIG for both minikube and acceptance tests (it will be generated at minikube's start if it does not exist):
181221

182222
```
183223
export KUBECONFIG=/tmp/minikubeconfig
184224
```
185225

186-
2. Start minikube:
226+
3. Start minikube:
187227

188228
```
189229
./hack/start-minikube.sh
190230
```
191231

192-
3. Enable olm on minikube:
232+
4. Enable olm on minikube:
193233

194234

195235
```
196236
minikube addons enable olm
197237
```
198238

199-
4. Deploy operator to the minikube cluster
239+
5. Deploy operator to the minikube cluster
200240

201241
```
202242
eval $(minikube docker-env)
203243
make deploy OPERATOR_REPO_REF=$(minikube ip):5000/sbo
204244
```
205245

206-
5. Execute all acceptance tests tagged with `@dev` using `kubectl` CLI:
246+
6. Execute all acceptance tests tagged with `@dev` using `kubectl` CLI:
207247

208248
```
209249
make test-acceptance TEST_ACCEPTANCE_TAGS="@dev" TEST_ACCEPTANCE_START_SBO=remote TEST_ACCEPTANCE_CLI=kubectl

make/acceptance.mk

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -24,11 +24,11 @@ OLM_NAMESPACE ?=
2424
# Testing setup
2525
.PHONY: deploy-test-3rd-party-crds
2626
deploy-test-3rd-party-crds:
27-
$(Q)kubectl --namespace $(TEST_NAMESPACE) apply -f ./test/third-party-crds/
27+
$(Q)$(KUBECTL) --namespace $(TEST_NAMESPACE) apply -f ./test/third-party-crds/
2828

2929
.PHONY: create-test-namespace
3030
create-test-namespace:
31-
$(Q)kubectl get namespace $(TEST_NAMESPACE) || kubectl create namespace $(TEST_NAMESPACE)
31+
$(Q)$(KUBECTL) get namespace $(TEST_NAMESPACE) || $(KUBECTL) create namespace $(TEST_NAMESPACE)
3232

3333
.PHONY: test-setup
3434
test-setup: test-cleanup create-test-namespace deploy-test-3rd-party-crds

make/build.mk

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -23,14 +23,14 @@ manifests: controller-gen
2323

2424
.PHONY: bundle
2525
# Generate bundle manifests and metadata, then validate generated files.
26-
bundle: manifests kustomize yq kubectl-slice push-image
27-
# operator-sdk generate kustomize manifests -q
26+
bundle: manifests kustomize yq $(KUBECTL)-slice push-image
27+
# $(OPERATOR_SDK) generate kustomize manifests -q
2828
cd config/manager && $(KUSTOMIZE) edit set image controller=$(OPERATOR_REPO_REF)@$(OPERATOR_IMAGE_SHA_REF)
29-
$(KUSTOMIZE) build config/manifests | operator-sdk generate bundle -q --overwrite --version $(VERSION) $(BUNDLE_METADATA_OPTS)
29+
$(KUSTOMIZE) build config/manifests | $(OPERATOR_SDK) generate bundle -q --overwrite --version $(VERSION) $(BUNDLE_METADATA_OPTS)
3030
$(YQ) e -i '.metadata.annotations.containerImage="$(OPERATOR_REPO_REF)@$(OPERATOR_IMAGE_SHA_REF)"' bundle/manifests/service-binding-operator.clusterserviceversion.yaml
31-
# this is needed because operator-sdk 1.16 filters out aggregated cluster role and the accompanied binding
31+
# this is needed because $(OPERATOR_SDK) 1.16 filters out aggregated cluster role and the accompanied binding
3232
$(KUSTOMIZE) build config/manifests | $(YQ) e 'select((.kind == "ClusterRole" and .metadata.name == "service-binding-controller-role") or (.kind == "ClusterRoleBinding" and .metadata.name == "service-binding-controller-rolebinding"))' - | $(KUBECTL_SLICE) -o bundle/manifests -t '{{.metadata.name}}_{{.apiVersion | replace "/" "_"}}_{{.kind | lower}}.yaml'
33-
operator-sdk bundle validate ./bundle --select-optional name=operatorhub
33+
$(OPERATOR_SDK) bundle validate ./bundle --select-optional name=operatorhub
3434

3535
.PHONY: registry-login
3636
registry-login:
@@ -54,7 +54,7 @@ bundle-image: bundle
5454
.PHONY: push-bundle-image
5555
push-bundle-image: bundle-image registry-login
5656
$(Q)$(CONTAINER_RUNTIME) push $(OPERATOR_BUNDLE_IMAGE_REF)
57-
$(Q)operator-sdk bundle validate --select-optional name=operatorhub -b $(CONTAINER_RUNTIME) $(OPERATOR_BUNDLE_IMAGE_REF)
57+
$(Q)$(OPERATOR_SDK) bundle validate --select-optional name=operatorhub -b $(CONTAINER_RUNTIME) $(OPERATOR_BUNDLE_IMAGE_REF)
5858

5959
.PHONY: index-image
6060
index-image: opm push-bundle-image

make/common.mk

Lines changed: 214 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -119,55 +119,245 @@ generate: controller-gen
119119
gen-mocks: mockgen
120120
PATH=$(shell pwd)/bin:$(shell printenv PATH) $(GO) generate $(V_FLAG) ./...
121121

122-
# Download controller-gen locally if necessary
123-
CONTROLLER_GEN = $(shell pwd)/bin/controller-gen
124-
controller-gen:
125-
$(call go-install-tool,$(CONTROLLER_GEN),sigs.k8s.io/controller-tools/cmd/[email protected])
126-
127-
# Download kustomize locally if necessary
128-
KUSTOMIZE = $(shell pwd)/bin/kustomize
129-
kustomize:
130-
$(call go-install-tool,$(KUSTOMIZE),sigs.k8s.io/kustomize/kustomize/[email protected])
131-
132122
# go-install-tool will 'go install' any package $2 and install it to $1.
133123
define go-install-tool
134-
@[ -f $(1) ] || { \
124+
[ -f $(1) ] || { \
135125
set -e ;\
136126
TMP_DIR=$$(mktemp -d) ;\
137127
cd $$TMP_DIR ;\
138-
go mod init tmp ;\
139-
echo "Downloading $(2)" ;\
140-
GOBIN=$(PROJECT_DIR)/bin go install $(2) ;\
128+
go mod init tmp 2>/dev/null ;\
129+
GOBIN=$(PROJECT_DIR)/bin go install $(2)@v$(3) ;\
141130
rm -rf $$TMP_DIR ;\
131+
echo "$$(basename $(1))@v$(3) installed" ;\
142132
}
143133
endef
144134

135+
define output-install
136+
echo "$(1)@v$(2) installed"
137+
endef
138+
139+
CONTROLLER_GEN = $(shell pwd)/bin/controller-gen
140+
CONTROLLER_GEN_VERSION ?= 0.7.0
141+
CONTROLLER_GEN_LOCAL_VERSION := $(shell [ -f $(CONTROLLER_GEN) ] && $(CONTROLLER_GEN) --version | cut -d' ' -f 2)
142+
CONTROLLER_GEN_HOST_VERSION := $(shell command -v controller-gen >/dev/null && controller-gen --version | cut -d' ' -f 2)
143+
controller-gen:
144+
ifneq (v$(CONTROLLER_GEN_VERSION), $(CONTROLLER_GEN_LOCAL_VERSION))
145+
@rm -f $(CONTROLLER_GEN)
146+
ifeq (v$(CONTROLLER_GEN_VERSION),$(CONTROLLER_GEN_HOST_VERSION))
147+
@ln -s $$(command -v controller-gen) $(CONTROLLER_GEN)
148+
@echo "controller-gen found at $$(command -v controller-gen)"
149+
else
150+
@$(call go-install-tool,$(CONTROLLER_GEN),sigs.k8s.io/controller-tools/cmd/controller-gen,$(CONTROLLER_GEN_VERSION))
151+
endif
152+
endif
153+
154+
145155
YQ = $(shell pwd)/bin/yq
156+
YQ_VERSION ?= 4.9.8
157+
YQ_LOCAL_VERSION := $(shell [ -f $(YQ) ] && $(YQ) --version | cut -d' ' -f 3)
158+
YQ_HOST_VERSION := $(shell command -v yq >/dev/null && yq --version | cut -d' ' -f 3)
146159
yq:
147-
$(call go-install-tool,$(YQ),github.com/mikefarah/yq/[email protected])
160+
ifneq ($(YQ_VERSION), $(YQ_LOCAL_VERSION))
161+
@rm -f $(YQ)
162+
ifeq ($(YQ_VERSION),$(YQ_HOST_VERSION))
163+
@ln -s $$(command -v yq) $(YQ)
164+
@echo "yq found at $$(command -v yq)"
165+
else
166+
@$(call go-install-tool,$(YQ),github.com/mikefarah/yq/v4,$(YQ_VERSION))
167+
endif
168+
endif
148169

149170
KUBECTL_SLICE = $(shell pwd)/bin/kubectl-slice
171+
KUBECTL_SLICE_VERSION ?= 1.1.0
172+
KUBECTL_SLICE_LOCAL_VERSION := $(shell [ -f $(KUBECTL_SLICE) ] && $(KUBECTL_SLICE) --version | cut -d' ' -f 3)
173+
KUBECTL_SLICE_HOST_VERSION := $(shell command -v kubectl-slice >/dev/null && kubectl-slice --version | cut -d' ' -f 3)
150174
kubectl-slice:
151-
$(call go-install-tool,$(KUBECTL_SLICE),github.com/patrickdappollonio/[email protected])
175+
ifneq ($(KUBECTL_SLICE_VERSION), $(KUBECTL_SLICE_LOCAL_VERSION))
176+
@rm -f $(KUBECTL_SLICE)
177+
ifeq ($(KUBECTL_SLICE_VERSION),$(KUBECTL_SLICE_HOST_VERSION))
178+
@ln -s $$(command -v kubectl-slice) $(KUBECTL_SLICE)
179+
@echo "kubectl-slice found at $$(command -v kubectl-slice)"
180+
else
181+
@{ \
182+
rm -f $(KUBECTL_SLICE) ;\
183+
arch=$$(case "$(ARCH)" in "amd64") echo "x86_64" ;; *) echo "$(ARCH)" ;; esac) ;\
184+
mkdir -p $(KUBECTL_SLICE)-install ;\
185+
curl -sSLo $(KUBECTL_SLICE)-install/kubectl-slice.tar.gz https://github.com/patrickdappollonio/kubectl-slice/releases/download/v$(KUBECTL_SLICE_VERSION)/kubectl-slice_$(KUBECTL_SLICE_VERSION)_$(OS)_$${arch}.tar.gz ;\
186+
tar xvfz $(KUBECTL_SLICE)-install/kubectl-slice.tar.gz -C $(KUBECTL_SLICE)-install/ > /dev/null ;\
187+
mv $(KUBECTL_SLICE)-install/kubectl-slice $(KUBECTL_SLICE) ;\
188+
rm -rf $(KUBECTL_SLICE)-install ;\
189+
$(call output-install,kubectl-slice,$(KUBECTL_SLICE_VERSION)) ;\
190+
}
191+
endif
192+
endif
152193

153194
MOCKGEN = $(shell pwd)/bin/mockgen
195+
MOCKGEN_VERSION ?= 1.6.0
196+
MOCKGEN_LOCAL_VERSION := $(shell [ -f $(MOCKGEN) ] && $(MOCKGEN) --version | cut -d' ' -f 3)
197+
MOCKGEN_HOST_VERSION := $(shell command -v mockgen >/dev/null && mockgen --version | cut -d' ' -f 3)
154198
mockgen:
155-
$(call go-install-tool,$(MOCKGEN),github.com/golang/mock/[email protected])
199+
ifneq (v$(MOCKGEN_VERSION), $(MOCKGEN_LOCAL_VERSION))
200+
@rm -f $(MOCKGEN)
201+
ifeq (v$(MOCKGEN_VERSION),$(MOCKGEN_HOST_VERSION))
202+
@ln -s $$(command -v mockgen) $(MOCKGEN)
203+
@echo "mockgen found at $$(command -v mockgen)"
204+
else
205+
@$(call go-install-tool,$(MOCKGEN),github.com/golang/mock/mockgen,$(MOCKGEN_VERSION))
206+
endif
207+
endif
208+
209+
210+
KUSTOMIZE = $(shell pwd)/bin/kustomize
211+
KUSTOMIZE_VERSION ?= 4.5.4
212+
KUSTOMIZE_LOCAL_VERSION := $(shell [ -f $(KUSTOMIZE) ] && $(KUSTOMIZE) version | cut -d' ' -f 1 | cut -d'/' -f 2)
213+
KUSTOMIZE_HOST_VERSION := $(shell command -v kustomize >/dev/null && kustomize version | cut -d' ' -f 1 | cut -d'/' -f 2)
214+
kustomize:
215+
ifneq (v$(KUSTOMIZE_VERSION), $(KUSTOMIZE_LOCAL_VERSION))
216+
@rm -f $(KUSTOMIZE)
217+
ifeq (v$(KUSTOMIZE_VERSION),$(KUSTOMIZE_HOST_VERSION))
218+
@ln -s $$(command -v kustomize) $(KUSTOMIZE)
219+
@echo "kustomize found at $$(command -v kustomize)"
220+
else
221+
@{ \
222+
set -e ;\
223+
mkdir -p $(dir $(KUSTOMIZE)) ;\
224+
rm -f $(KUSTOMIZE) ; \
225+
mkdir -p $(KUSTOMIZE)-install ;\
226+
curl -sSLo $(KUSTOMIZE)-install/kustomize.tar.gz https://github.com/kubernetes-sigs/kustomize/releases/download/kustomize%2Fv$(KUSTOMIZE_VERSION)/kustomize_v$(KUSTOMIZE_VERSION)_$(OS)_$(ARCH).tar.gz ;\
227+
tar xzvf $(KUSTOMIZE)-install/kustomize.tar.gz -C $(KUSTOMIZE)-install/ >/dev/null ;\
228+
mv $(KUSTOMIZE)-install/kustomize $(KUSTOMIZE) ;\
229+
rm -rf $(KUSTOMIZE)-install ;\
230+
chmod +x $(KUSTOMIZE) ;\
231+
$(call output-install,kustomize,$(KUSTOMIZE_VERSION)) ;\
232+
}
233+
endif
234+
endif
156235

157236
.PHONY: opm
158237
OPM ?= $(shell pwd)/bin/opm
238+
OPM_VERSION ?= 1.22.0
239+
OPM_LOCAL_VERSION := $(shell [ -f $(OPM) ] && $(OPM) version | cut -d'"' -f 2)
240+
OPM_HOST_VERSION := $(shell command -v opm >/dev/null && opm version | cut -d'"' -f 2)
159241
opm:
160-
ifeq (,$(wildcard $(OPM)))
161-
ifeq (,$(shell which opm 2>/dev/null))
242+
ifneq (v$(OPM_VERSION), $(OPM_LOCAL_VERSION))
243+
@rm -f $(OPM)
244+
ifeq (v$(OPM_VERSION),$(OPM_HOST_VERSION))
245+
@ln -s $$(command -v opm) $(OPM)
246+
@echo "opm found at $$(command -v opm)"
247+
else
248+
@{ \
249+
set -e ;\
250+
mkdir -p $(dir $(OPM)) ;\
251+
rm -f $(OPM) ; \
252+
curl -sSLo $(OPM) https://github.com/operator-framework/operator-registry/releases/download/v$(OPM_VERSION)/$(OS)-$(ARCH)-opm ;\
253+
chmod +x $(OPM) ;\
254+
$(call output-install,opm,$(OPM_VERSION)) ;\
255+
}
256+
endif
257+
endif
258+
259+
.PHONY: minikube
260+
MINIKUBE ?= $(shell pwd)/bin/minikube
261+
MINIKUBE_VERSION ?= 1.26.1
262+
MINIKUBE_LOCAL_VERSION = $(shell [ -f $(MINIKUBE) ] && $(MINIKUBE) version --short)
263+
MINIKUBE_HOST_VERSION = $(shell command -v minikube >/dev/null && minikube version --short)
264+
minikube:
265+
ifneq (v$(MINIKUBE_VERSION), $(MINIKUBE_LOCAL_VERSION))
266+
@rm -f $(MINIKUBE)
267+
ifeq (v$(MINIKUBE_VERSION),$(MINIKUBE_HOST_VERSION))
268+
@ln -s $$(command -v minikube) $(MINIKUBE)
269+
@echo "minikube found at $$(command -v minikube)"
270+
else
271+
@{ \
272+
set -e ;\
273+
mkdir -p $(dir $(MINIKUBE)) ;\
274+
rm -f $(MINIKUBE) ; \
275+
curl -sSLo $(MINIKUBE) https://storage.googleapis.com/minikube/releases/v$(MINIKUBE_VERSION)/minikube-$(OS)-$(ARCH) ;\
276+
chmod +x $(MINIKUBE) ;\
277+
$(call output-install,minikube,$(MINIKUBE_VERSION)) ;\
278+
}
279+
endif
280+
endif
281+
282+
.PHONY: operator-sdk
283+
OPERATOR_SDK ?= $(shell pwd)/bin/operator-sdk
284+
OPERATOR_SDK_VERSION ?= 1.24.0
285+
OPERATOR_SDK_LOCAL_VERSION := $(shell [ -f $(OPERATOR_SDK) ] && $(OPERATOR_SDK) version | cut -d'"' -f 2)
286+
OPERATOR_SDK_HOST_VERSION := $(shell command -v operator-sdk >/dev/null && operator-sdk version | cut -d'"' -f 2)
287+
operator-sdk:
288+
ifneq (v$(OPERATOR_SDK_VERSION), $(OPERATOR_SDK_LOCAL_VERSION))
289+
@rm -f $(OPERATOR_SDK)
290+
ifeq (v$(OPERATOR_SDK_VERSION),$(OPERATOR_SDK_HOST_VERSION))
291+
@ln -s $$(command -v operator-sdk) $(OPERATOR_SDK)
292+
@echo "operator-sdk found at $$(command -v operator-sdk)"
293+
else
162294
@{ \
163-
set -e ;\
164-
mkdir -p $(dir $(OPM)) ;\
165-
curl -sSLo $(OPM) https://github.com/operator-framework/operator-registry/releases/download/v1.22.0/$(OS)-$(ARCH)-opm ;\
166-
chmod +x $(OPM) ;\
295+
set -e ;\
296+
mkdir -p $(dir $(OPERATOR_SDK)) ;\
297+
rm -f $(OPERATOR_SDK) ; \
298+
curl -sSLo $(OPERATOR_SDK) https://github.com/operator-framework/operator-sdk/releases/download/v$(OPERATOR_SDK_VERSION)/operator-sdk_$(OS)_$(ARCH) ;\
299+
chmod +x $(OPERATOR_SDK) ;\
300+
$(call output-install,operator-sdk,$(OPERATOR_SDK_VERSION)) ;\
167301
}
302+
endif
303+
endif
304+
305+
.PHONY: kubectl
306+
KUBECTL ?= $(shell pwd)/bin/kubectl
307+
KUBECTL_VERSION ?= 1.25.3
308+
KUBECTL_LOCAL_VERSION := $(shell [ -f $(KUBECTL) ] && $(KUBECTL) version --client --output yaml | grep 'gitVersion:' | tr -d ' ' | cut -d':' -f 2)
309+
KUBECTL_HOST_VERSION := $(shell command -v kubectl >/dev/null && kubectl version --client --output yaml | grep 'gitVersion:' | tr -d ' ' | cut -d':' -f 2)
310+
kubectl:
311+
ifneq (v$(KUBECTL_VERSION), $(KUBECTL_LOCAL_VERSION))
312+
@rm -f $(KUBECTL)
313+
ifeq (v$(KUBECTL_VERSION),$(KUBECTL_HOST_VERSION))
314+
@ln -s $$(command -v kubectl) $(KUBECTL)
315+
@echo "kubectl found at $$(command -v kubectl)"
168316
else
169-
OPM = $(shell which opm)
317+
@{ \
318+
set -e ;\
319+
mkdir -p $(dir $(KUBECTL)) ;\
320+
rm -f $(KUBECTL) ; \
321+
curl -sSLo $(KUBECTL) https://dl.k8s.io/release/v$(KUBECTL_VERSION)/bin/$(OS)/$(ARCH)/kubectl ;\
322+
chmod +x $(KUBECTL) ;\
323+
$(call output-install,kubectl,$(KUBECTL_VERSION)) ;\
324+
}
170325
endif
171326
endif
172327

328+
.PHONY: helm
329+
HELM ?= $(shell pwd)/bin/helm
330+
HELM_VERSION ?= 3.10.1
331+
HELM_LOCAL_VERSION := $(shell [ -f $(HELM) ] && $(HELM) version --short | cut -d'+' -f 1)
332+
HELM_HOST_VERSION := $(shell command -v helm >/dev/null && helm version --short | cut -d'+' -f 1)
333+
helm:
334+
ifneq (v$(HELM_VERSION), $(HELM_LOCAL_VERSION))
335+
@rm -f $(HELM)
336+
ifeq (v$(HELM_VERSION),$(HELM_HOST_VERSION))
337+
@ln -s $$(command -v helm) $(HELM)
338+
@echo "helm found at $$(command -v helm)"
339+
else
340+
@{ \
341+
set -e ;\
342+
mkdir -p $(dir $(HELM)) $(HELM)-install ;\
343+
curl -sSLo $(HELM)-install/helm.tar.gz https://get.helm.sh/helm-v$(HELM_VERSION)-$(OS)-$(ARCH).tar.gz ;\
344+
tar xvfz $(HELM)-install/helm.tar.gz -C $(HELM)-install >/dev/null ;\
345+
rm -f $(HELM) ; \
346+
cp $(HELM)-install/$(OS)-$(ARCH)/helm $(HELM) ;\
347+
rm -r $(HELM)-install ;\
348+
chmod +x $(HELM) ;\
349+
$(call output-install,helm,$(HELM_VERSION)) ;\
350+
}
351+
endif
352+
endif
353+
354+
.PHONY: install-tools
355+
install-tools: controller-gen helm kubectl kubectl-slice kustomize minikube mockgen operator-sdk opm yq
356+
@echo
357+
@echo run '`eval $$(make local-env)`' to configure your shell to use tools in the ./bin folder
358+
359+
.PHONY: local-env
360+
local-env:
361+
@echo export PATH=$(shell pwd)/bin:$$PATH
362+
173363
all: build

0 commit comments

Comments
 (0)