From a7ca9e74f5e077f1647764bd643c4b8796dae4ab Mon Sep 17 00:00:00 2001 From: Juan Hernandez Date: Wed, 15 Nov 2023 23:33:36 +0100 Subject: [PATCH] [WIP] Add 'dev.py' Signed-off-by: Juan Hernandez --- .containerignore | 3 + .github/actions/install-tools/action.yaml | 37 ++ .github/workflows/check-pull-request.yaml | 49 +-- .gitignore | 4 +- .vscode/launch.json | 11 + Containerfile | 18 +- Makefile | 339 ---------------- config/manager/kustomization.yaml | 4 +- .../oran-o2ims.clusterserviceversion.yaml | 17 +- dev.py | 67 ++++ .github/workflows/requirements.txt => dev.txt | 7 +- dev/__init__.py | 31 ++ dev/build.py | 88 +++++ dev/ci_job.py | 31 ++ dev/clean.py | 29 ++ dev/command.py | 48 +++ dev/defaults.py | 23 ++ dev/deploy.py | 90 +++++ dev/fmt.py | 26 ++ dev/formatter.py | 32 ++ dev/generate.py | 105 +++++ dev/install.py | 75 ++++ dev/lint.py | 39 ++ dev/push.py | 84 ++++ dev/run.py | 30 ++ dev/setup.py | 367 ++++++++++++++++++ dev/test.py | 33 ++ dev/update.py | 37 ++ dev/versions.py | 23 ++ dev/vet.py | 26 ++ hack/install_test_deps.sh | 25 -- hack/update_deps.sh | 5 - 32 files changed, 1390 insertions(+), 413 deletions(-) create mode 100644 .github/actions/install-tools/action.yaml delete mode 100644 Makefile create mode 100755 dev.py rename .github/workflows/requirements.txt => dev.txt (79%) create mode 100644 dev/__init__.py create mode 100755 dev/build.py create mode 100755 dev/ci_job.py create mode 100755 dev/clean.py create mode 100755 dev/command.py create mode 100755 dev/defaults.py create mode 100755 dev/deploy.py create mode 100755 dev/fmt.py create mode 100755 dev/formatter.py create mode 100755 dev/generate.py create mode 100755 dev/install.py create mode 100755 dev/lint.py create mode 100755 dev/push.py create mode 100755 dev/run.py create mode 100755 dev/setup.py create mode 100755 dev/test.py create mode 100755 dev/update.py create mode 100755 dev/versions.py create mode 100755 dev/vet.py delete mode 100755 hack/install_test_deps.sh delete mode 100755 hack/update_deps.sh diff --git a/.containerignore b/.containerignore index dffc4a9d..52fb7092 100644 --- a/.containerignore +++ b/.containerignore @@ -2,6 +2,9 @@ !/LICENSE.txt !/Makefile !/README.md +!/dev.py +!/dev.txt +!/dev/ !/go.mod !/go.sum !/internal diff --git a/.github/actions/install-tools/action.yaml b/.github/actions/install-tools/action.yaml new file mode 100644 index 00000000..4a112136 --- /dev/null +++ b/.github/actions/install-tools/action.yaml @@ -0,0 +1,37 @@ +# +# Copyright (c) 2023 Red Hat, Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except +# in compliance with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software distributed under the License +# is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express +# or implied. See the License for the specific language governing permissions and limitations under +# the License. +# + +name: Install tools +runs: + using: composite + steps: + - name: Install Go + uses: actions/setup-go@v4 + with: + go-version: '1.21' + + - name: Install Python + uses: actions/setup-python@v2 + with: + python-version: '3.11' + cache: pip + cache-dependency-path: dev.txt + + - name: Install Python modules + shell: bash + run: pip install -r dev.txt + + - name: Install tools + shell: bash + run: ./dev.py setup \ No newline at end of file diff --git a/.github/workflows/check-pull-request.yaml b/.github/workflows/check-pull-request.yaml index 987d312a..17c7c060 100644 --- a/.github/workflows/check-pull-request.yaml +++ b/.github/workflows/check-pull-request.yaml @@ -28,8 +28,11 @@ jobs: - name: Checkout the source uses: actions/checkout@v3 + - name: Install tools + uses: ./.github/actions/install-tools + - name: Build image - run: make image + run: ./dev.py build image unit-tests: name: Unit tests @@ -38,25 +41,14 @@ jobs: - name: Checkout the source uses: actions/checkout@v3 - - name: Install Go - uses: actions/setup-go@v4 - with: - go-version: '1.21' - - - name: Install Go tools - run: | - go install github.com/onsi/ginkgo/v2/ginkgo@$(go list -f '{{.Version}}' -m github.com/onsi/ginkgo/v2) - go install go.uber.org/mock/mockgen@v0.3.0 + - name: Install tools + uses: ./.github/actions/install-tools - - name: Install spectral - run: | - curl -Lo spectral https://github.com/stoplightio/spectral/releases/download/v6.11.0/spectral-linux-x64 - echo 0e151d3dc5729750805428f79a152fa01dd4c203f1d9685ef19f4fd4696fcd5f spectral | sha256sum -c - chmod +x spectral - sudo mv spectral /usr/bin + - name: Install tools + run: ./dev.py setup - name: Run the tests - run: make tests + run: ./dev.py test check-generated-code: name: Check generated code @@ -65,17 +57,11 @@ jobs: - name: Checkout the source uses: actions/checkout@v3 - - name: Install Go - uses: actions/setup-go@v4 - with: - go-version: '1.21' - - - name: Install Go tools - run: | - go install go.uber.org/mock/mockgen@v0.3.0 + - name: Install tools + uses: ./.github/actions/install-tools - name: Generate code - run: make generate + run: ./dev.py generate - name: Check differences run: git diff --exit-code @@ -87,13 +73,8 @@ jobs: - name: Checkout the source uses: actions/checkout@v3 - - name: Install Go - uses: actions/setup-go@v4 - with: - go-version: '1.21' + - name: Install tools + uses: ./.github/actions/install-tools - name: Run the linter - uses: golangci/golangci-lint-action@v3 - with: - version: v1.54.2 - args: --timeout=5m + run: ./dev.py lint diff --git a/.gitignore b/.gitignore index e0931f46..6d451144 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,7 @@ *.log +*.pyc /__debug* +/bin/* /oran-o2ims /vendor -/bin/* +__pycache__ diff --git a/.vscode/launch.json b/.vscode/launch.json index f0043d25..e86279af 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -1,6 +1,17 @@ { "version": "0.2.0", "configurations": [ + { + "name": "setup", + "type": "debugpy", + "request": "launch", + "program": "${workspaceFolder}/dev.py", + "args": [ + "setup", + ], + "console": "integratedTerminal", + "justMyCode": true + }, { "name": "version", "type": "go", diff --git a/Containerfile b/Containerfile index cb73f2c4..901ecfd6 100644 --- a/Containerfile +++ b/Containerfile @@ -14,13 +14,19 @@ FROM registry.access.redhat.com/ubi9/ubi:9.2 AS builder -# Install packages: +# Install OS packages: RUN \ dnf install -y \ - make \ + python3.11 \ + python3.11-pip \ && \ dnf clean all +# In RHEL containers the default is Python 3.9, but we need the version of Python 3.11 that we +# installed as the default, otherwise our build scripts don't work correctly: +RUN \ + ln -sf python3.11 /usr/bin/python3 + # Currently RHEL 9 doesn't provide a Go 1.21 compiler, so we need to install it from the Go # downloads site: RUN \ @@ -39,6 +45,12 @@ WORKDIR \ ENV \ PATH="${PATH}:/usr/local/go/bin" +# Install Python packages: +COPY \ + dev.txt . +RUN \ + pip3.11 install -r dev.txt + # Copy the source: COPY \ --chown=builder:builder \ @@ -51,7 +63,7 @@ RUN \ # Build the binary: RUN \ - make binary + ./dev.py build binary FROM registry.access.redhat.com/ubi9-minimal:9.2 AS runtime diff --git a/Makefile b/Makefile deleted file mode 100644 index fc08bc1b..00000000 --- a/Makefile +++ /dev/null @@ -1,339 +0,0 @@ -# -# Copyright (c) 2023 Red Hat, Inc. -# -# Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except -# in compliance with the License. You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software distributed under the License -# is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express -# or implied. See the License for the specific language governing permissions and limitations under -# the License. -# - -# Details of the image: -image_repo:=quay.io/openshift-kni/oran-o2ims -image_tag:=4.16.0 - -# Additional flags to pass to the `ginkgo` command. -ginkgo_flags:= - -# VERSION defines the project version for the bundle. -# Update this value when you upgrade the version of your project. -# To re-generate a bundle for another specific version without changing the standard setup, you can: -# - use the VERSION as arg of the bundle target (e.g make bundle VERSION=0.0.2) -# - use environment variables to overwrite this value (e.g export VERSION=0.0.2) -VERSION ?= 4.16.0 - -# DEFAULT_CHANNEL defines the default channel used in the bundle. -# Add a new line here if you would like to change its default config. (E.g DEFAULT_CHANNEL = "stable") -# To re-generate a bundle for any other default channel without changing the default setup, you can: -# - use the DEFAULT_CHANNEL as arg of the bundle target (e.g make bundle DEFAULT_CHANNEL=stable) -# - use environment variables to overwrite this value (e.g export DEFAULT_CHANNEL="stable") -ifneq ($(origin DEFAULT_CHANNEL), undefined) -BUNDLE_DEFAULT_CHANNEL := --default-channel=$(DEFAULT_CHANNEL) -endif -BUNDLE_METADATA_OPTS ?= $(BUNDLE_CHANNELS) $(BUNDLE_DEFAULT_CHANNEL) - -# IMAGE_TAG_BASE defines the docker.io namespace and part of the image name for remote images. -# This variable is used to construct full image tags for bundle and catalog images. -# -# For example, running 'make bundle-build bundle-push catalog-build catalog-push' will build and push both -# openshift.io/oran-o2ims-bundle:$VERSION and openshift.io/oran-o2ims-catalog:$VERSION. -IMAGE_TAG_BASE ?= quay.io/imihai/oran-o2ims-operator - -# BUNDLE_IMG defines the image:tag used for the bundle. -# You can use it as an arg. (E.g make bundle-build BUNDLE_IMG=/:) -BUNDLE_IMG ?= $(IMAGE_TAG_BASE)-bundle:$(VERSION) - -# BUNDLE_GEN_FLAGS are the flags passed to the operator-sdk generate bundle command -BUNDLE_GEN_FLAGS ?= -q --overwrite --version $(VERSION) $(BUNDLE_METADATA_OPTS) - -# USE_IMAGE_DIGESTS defines if images are resolved via tags or digests -# You can enable this value if you would like to use SHA Based Digests -# To enable set flag to true -USE_IMAGE_DIGESTS ?= false -ifeq ($(USE_IMAGE_DIGESTS), true) - BUNDLE_GEN_FLAGS += --use-image-digests -endif - -# Set the Operator SDK version to use. By default, what is installed on the system is used. -# This is useful for CI or a project to utilize a specific version of the operator-sdk toolkit. -OPERATOR_SDK_VERSION ?= v1.33.0 - -# Image URL to use all building/pushing image targets -IMG ?= $(IMAGE_TAG_BASE):$(VERSION) - -# ENVTEST_K8S_VERSION refers to the version of kubebuilder assets to be downloaded by envtest binary. -ENVTEST_K8S_VERSION = 1.28.0 - -# Get the currently used golang install path (in GOPATH/bin, unless GOBIN is set) -ifeq (,$(shell go env GOBIN)) -GOBIN=$(shell go env GOPATH)/bin -else -GOBIN=$(shell go env GOBIN) -endif - -# CONTAINER_TOOL defines the container tool to be used for building images. -# Be aware that the target commands are only tested with Docker which is -# scaffolded by default. However, you might want to replace it to use other -# tools. (i.e. podman) -CONTAINER_TOOL ?= docker - -# Setting SHELL to bash allows bash commands to be executed by recipes. -# Options are set to exit when a recipe line exits non-zero or a piped command fails. -SHELL = /usr/bin/env bash -o pipefail -.SHELLFLAGS = -ec - -.PHONY: all -all: build - -#@ General - -# The help target prints out all targets with their descriptions organized -# beneath their categories. The categories are represented by '##@' and the -# target descriptions by '##'. The awk command is responsible for reading the -# entire set of makefiles included in this invocation, looking for lines of the -# file as xyz: ## something, and then pretty-format the target and help. Then, -# if there's a line with ##@ something, that gets pretty-printed as a category. -# More info on the usage of ANSI control characters for terminal formatting: -# https://en.wikipedia.org/wiki/ANSI_escape_code#SGR_parameters -# More info on the awk command: -# http://linuxcommand.org/lc3_adv_awk.php - -.PHONY: help -help: ## Display this help. - @awk 'BEGIN {FS = ":.*##"; printf "\nUsage:\n make \033[36m\033[0m\n"} /^[a-zA-Z_0-9-]+:.*?##/ { printf " \033[36m%-15s\033[0m %s\n", $$1, $$2 } /^##@/ { printf "\n\033[1m%s\033[0m\n", substr($$0, 5) } ' $(MAKEFILE_LIST) - -##@ Development - -.PHONY: manifests -manifests: controller-gen ## Generate WebhookConfiguration, ClusterRole and CustomResourceDefinition objects. - $(CONTROLLER_GEN) rbac:roleName=manager-role crd webhook paths="./..." output:crd:artifacts:config=config/crd/bases - -.PHONY: generate -generate: controller-gen ## Generate code containing DeepCopy, DeepCopyInto, and DeepCopyObject method implementations. - $(CONTROLLER_GEN) object:headerFile="hack/boilerplate.go.txt" paths="./..." - -##@ Build -.PHONY: build -build: manifests generate fmt vet ## Build manager binary. - go build -o bin/manager cmd/main.go - -.PHONY: run -run: manifests generate fmt vet ## Run a controller from your host. - go run ./cmd/main.go - -# If you wish to build the manager image targeting other platforms you can use the --platform flag. -# (i.e. docker build --platform linux/arm64). However, you must enable docker buildKit for it. -# More info: https://docs.docker.com/develop/develop-images/build_enhancements/ -.PHONY: docker-build -docker-build: ## Build docker image with the manager. - $(CONTAINER_TOOL) build -t ${IMG} -f Dockerfile . - -.PHONY: docker-push -docker-push: ## Push docker image with the manager. - $(CONTAINER_TOOL) push ${IMG} - -# PLATFORMS defines the target platforms for the manager image be built to provide support to multiple -# architectures. (i.e. make docker-buildx IMG=myregistry/mypoperator:0.0.1). To use this option you need to: -# - be able to use docker buildx. More info: https://docs.docker.com/build/buildx/ -# - have enabled BuildKit. More info: https://docs.docker.com/develop/develop-images/build_enhancements/ -# - be able to push the image to your registry (i.e. if you do not set a valid value via IMG=> then the export will fail) -# To adequately provide solutions that are compatible with multiple platforms, you should consider using this option. -PLATFORMS ?= linux/arm64,linux/amd64,linux/s390x,linux/ppc64le -.PHONY: docker-buildx -docker-buildx: ## Build and push docker image for the manager for cross-platform support - # copy existing Dockerfile and insert --platform=${BUILDPLATFORM} into Dockerfile.cross, and preserve the original Dockerfile - sed -e '1 s/\(^FROM\)/FROM --platform=\$$\{BUILDPLATFORM\}/; t' -e ' 1,// s//FROM --platform=\$$\{BUILDPLATFORM\}/' Dockerfile > Dockerfile.cross - - $(CONTAINER_TOOL) buildx create --name project-v3-builder - $(CONTAINER_TOOL) buildx use project-v3-builder - - $(CONTAINER_TOOL) buildx build --push --platform=$(PLATFORMS) --tag ${IMG} -f Dockerfile.cross . - - $(CONTAINER_TOOL) buildx rm project-v3-builder - rm Dockerfile.cross - -##@ Deployment - -ifndef ignore-not-found - ignore-not-found = false -endif - -.PHONY: install -install: manifests kustomize ## Install CRDs into the K8s cluster specified in ~/.kube/config. - $(KUSTOMIZE) build config/crd | $(KUBECTL) apply -f - - -.PHONY: uninstall -uninstall: manifests kustomize ## Uninstall CRDs from the K8s cluster specified in ~/.kube/config. Call with ignore-not-found=true to ignore resource not found errors during deletion. - $(KUSTOMIZE) build config/crd | $(KUBECTL) delete --ignore-not-found=$(ignore-not-found) -f - - -.PHONY: deploy -deploy: manifests kustomize ## Deploy controller to the K8s cluster specified in ~/.kube/config. - cd config/manager && $(KUSTOMIZE) edit set image controller=${IMG} - $(KUSTOMIZE) build config/default | $(KUBECTL) apply -f - - -.PHONY: undeploy -undeploy: ## Undeploy controller from the K8s cluster specified in ~/.kube/config. Call with ignore-not-found=true to ignore resource not found errors during deletion. - $(KUSTOMIZE) build config/default | $(KUBECTL) delete --ignore-not-found=$(ignore-not-found) -f - - -##@ Build Dependencies - -## Location to install dependencies to -LOCALBIN ?= $(shell pwd)/bin -$(LOCALBIN): - mkdir -p $(LOCALBIN) - -## Tool Binaries -KUBECTL ?= kubectl -KUSTOMIZE ?= $(LOCALBIN)/kustomize -CONTROLLER_GEN ?= $(LOCALBIN)/controller-gen -ENVTEST ?= $(LOCALBIN)/setup-envtest - -## Tool Versions -KUSTOMIZE_VERSION ?= v5.2.1 -CONTROLLER_TOOLS_VERSION ?= v0.13.0 - -.PHONY: kustomize -kustomize: $(KUSTOMIZE) ## Download kustomize locally if necessary. If wrong version is installed, it will be removed before downloading. -$(KUSTOMIZE): $(LOCALBIN) - @if test -x $(LOCALBIN)/kustomize && ! $(LOCALBIN)/kustomize version | grep -q $(KUSTOMIZE_VERSION); then \ - echo "$(LOCALBIN)/kustomize version is not expected $(KUSTOMIZE_VERSION). Removing it before installing."; \ - rm -rf $(LOCALBIN)/kustomize; \ - fi - test -s $(LOCALBIN)/kustomize || GOBIN=$(LOCALBIN) GO111MODULE=on go install sigs.k8s.io/kustomize/kustomize/v5@$(KUSTOMIZE_VERSION) - -.PHONY: controller-gen -controller-gen: $(CONTROLLER_GEN) ## Download controller-gen locally if necessary. If wrong version is installed, it will be overwritten. -$(CONTROLLER_GEN): $(LOCALBIN) - test -s $(LOCALBIN)/controller-gen && $(LOCALBIN)/controller-gen --version | grep -q $(CONTROLLER_TOOLS_VERSION) || \ - GOBIN=$(LOCALBIN) go install sigs.k8s.io/controller-tools/cmd/controller-gen@$(CONTROLLER_TOOLS_VERSION) - -.PHONY: envtest -envtest: $(ENVTEST) ## Download envtest-setup locally if necessary. -$(ENVTEST): $(LOCALBIN) - test -s $(LOCALBIN)/setup-envtest || GOBIN=$(LOCALBIN) go install sigs.k8s.io/controller-runtime/tools/setup-envtest@latest - -.PHONY: operator-sdk -OPERATOR_SDK ?= $(LOCALBIN)/operator-sdk -operator-sdk: ## Download operator-sdk locally if necessary. -ifeq (,$(wildcard $(OPERATOR_SDK))) -ifeq (, $(shell which operator-sdk 2>/dev/null)) - @{ \ - set -e ;\ - mkdir -p $(dir $(OPERATOR_SDK)) ;\ - OS=$(shell go env GOOS) && ARCH=$(shell go env GOARCH) && \ - curl -sSLo $(OPERATOR_SDK) https://github.com/operator-framework/operator-sdk/releases/download/$(OPERATOR_SDK_VERSION)/operator-sdk_$${OS}_$${ARCH} ;\ - chmod +x $(OPERATOR_SDK) ;\ - } -else -OPERATOR_SDK = $(shell which operator-sdk) -endif -endif - -.PHONY: bundle -bundle: manifests kustomize operator-sdk ## Generate bundle manifests and metadata, then validate generated files. - $(OPERATOR_SDK) generate kustomize manifests --apis-dir api/ -q - cd config/manager && $(KUSTOMIZE) edit set image controller=$(IMG) - $(KUSTOMIZE) build config/manifests | $(OPERATOR_SDK) generate bundle $(BUNDLE_GEN_FLAGS) - $(OPERATOR_SDK) bundle validate ./bundle - -.PHONY: bundle-build -bundle-build: ## Build the bundle image. - $(CONTAINER_TOOL) build -f bundle.Dockerfile -t $(BUNDLE_IMG) . - -.PHONY: bundle-push -bundle-push: ## Push the bundle image. - $(MAKE) docker-push IMG=$(BUNDLE_IMG) - -.PHONY: opm -OPM = ./bin/opm -opm: ## Download opm locally if necessary. -ifeq (,$(wildcard $(OPM))) -ifeq (,$(shell which opm 2>/dev/null)) - @{ \ - set -e ;\ - mkdir -p $(dir $(OPM)) ;\ - OS=$(shell go env GOOS) && ARCH=$(shell go env GOARCH) && \ - curl -sSLo $(OPM) https://github.com/operator-framework/operator-registry/releases/download/v1.23.0/$${OS}-$${ARCH}-opm ;\ - chmod +x $(OPM) ;\ - } -else -OPM = $(shell which opm) -endif -endif - -# A comma-separated list of bundle images (e.g. make catalog-build BUNDLE_IMGS=example.com/operator-bundle:v0.1.0,example.com/operator-bundle:v0.2.0). -# These images MUST exist in a registry and be pull-able. -BUNDLE_IMGS ?= $(BUNDLE_IMG) - -# The image tag given to the resulting catalog image (e.g. make catalog-build CATALOG_IMG=example.com/operator-catalog:v0.2.0). -CATALOG_IMG ?= $(IMAGE_TAG_BASE)-catalog:v$(VERSION) - -# Set CATALOG_BASE_IMG to an existing catalog image tag to add $BUNDLE_IMGS to that image. -ifneq ($(origin CATALOG_BASE_IMG), undefined) -FROM_INDEX_OPT := --from-index $(CATALOG_BASE_IMG) -endif - -# Build a catalog image by adding bundle images to an empty catalog using the operator package manager tool, 'opm'. -# This recipe invokes 'opm' in 'semver' bundle add mode. For more information on add modes, see: -# https://github.com/operator-framework/community-operators/blob/7f1438c/docs/packaging-operator.md#updating-your-existing-operator -.PHONY: catalog-build -catalog-build: opm ## Build a catalog image. - $(OPM) index add --container-tool docker --mode semver --tag $(CATALOG_IMG) --bundles $(BUNDLE_IMGS) $(FROM_INDEX_OPT) - -# Push the catalog image. -.PHONY: catalog-push -catalog-push: ## Push a catalog image. - $(MAKE) docker-push IMG=$(CATALOG_IMG) - -##@ Binary -.PHONY: binary -binary: - go build - -.PHONY: image -image: - podman build -t "$(image_repo):$(image_tag)" -f Containerfile . - -.PHONY: push -push: image - podman push "$(image_repo):$(image_tag)" - -.PHONY: generate -go-generate: - go generate ./... - -.PHONY: test tests -test tests: - @echo "Run ginkgo" - ginkgo run -r $(ginkgo_flags) - -.PHONY: fmt -fmt: - @echo "Run fmt" - gofmt -s -l -w . - -.PHONY: vet -vet: ## Run go vet against code. - go vet ./... - -.PHONY: lint -lint: - @echo "Run lint" - golangci-lint --version - golangci-lint run --verbose --print-resources-usage --modules-download-mode=vendor --timeout=5m0s - -.PHONY: deps-update -deps-update: - @echo "Update dependencies" - hack/update_deps.sh - hack/install_test_deps.sh - -.PHONY: ci-job -ci-job: deps-update lint fmt test - -.PHONY: clean -clean: - rm -rf \ - oran-o2ims \ - $(NULL) diff --git a/config/manager/kustomization.yaml b/config/manager/kustomization.yaml index f5521e11..db22134d 100644 --- a/config/manager/kustomization.yaml +++ b/config/manager/kustomization.yaml @@ -9,5 +9,5 @@ generatorOptions: images: - name: controller - newName: quay.io/imihai/oran-o2ims-operator - newTag: 4.16.0 + newName: quay.io/openshift-kni/oran-o2ims + newTag: latest diff --git a/config/manifests/bases/oran-o2ims.clusterserviceversion.yaml b/config/manifests/bases/oran-o2ims.clusterserviceversion.yaml index 65a77723..6393dc26 100644 --- a/config/manifests/bases/oran-o2ims.clusterserviceversion.yaml +++ b/config/manifests/bases/oran-o2ims.clusterserviceversion.yaml @@ -8,7 +8,22 @@ metadata: namespace: placeholder spec: apiservicedefinitions: {} - customresourcedefinitions: {} + customresourcedefinitions: + owned: + - description: ORANO2IMS is the Schema for the orano2ims API + displayName: ORANO2 IMS + kind: ORANO2IMS + name: orano2ims.oran.openshift.io + statusDescriptors: + - displayName: Conditions + path: deploymentStatus.conditions + - displayName: Deployment Server Status + path: deploymentStatus.deploymentServerStatus + - displayName: Metadata Server Status + path: deploymentStatus.metadataServerStatus + - displayName: Resource Server Status + path: deploymentStatus.resourceServerStatus + version: v1alpha1 description: Deploys the ORAN O2IMS services displayName: ORAN O2IMS Operator icon: diff --git a/dev.py b/dev.py new file mode 100755 index 00000000..2ff31ca2 --- /dev/null +++ b/dev.py @@ -0,0 +1,67 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- + +# +# Copyright (c) 2023 Red Hat Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except +# in compliance with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software distributed under the License +# is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express +# or implied. See the License for the specific language governing permissions and limitations under +# the License. +# + +""" +This script is a development tool for the project. +""" + +import logging + +import click + +import dev + +@click.group() +def cli(): + """ + Development tools. + """ + pass + +# Add the commands: +cli.add_command(dev.build) +cli.add_command(dev.ci_job) +cli.add_command(dev.clean) +cli.add_command(dev.deploy) +cli.add_command(dev.fmt) +cli.add_command(dev.generate) +cli.add_command(dev.install) +cli.add_command(dev.lint) +cli.add_command(dev.push) +cli.add_command(dev.run) +cli.add_command(dev.setup) +cli.add_command(dev.test) +cli.add_command(dev.undeploy) +cli.add_command(dev.uninstall) +cli.add_command(dev.update) +cli.add_command(dev.vet) + +if __name__ == '__main__': + # Configure logging: + formatter = dev.Formatter() + handler = logging.StreamHandler() + handler.setFormatter(formatter) + logging.root.handlers = [handler] + logging.root.level = logging.DEBUG + + # Run the command: + cli() + #try: + # cli() + #except Exception as err: + # logging.error(err) + # sys.exit(1) diff --git a/.github/workflows/requirements.txt b/dev.txt similarity index 79% rename from .github/workflows/requirements.txt rename to dev.txt index 8e125efe..6a221ff1 100644 --- a/.github/workflows/requirements.txt +++ b/dev.txt @@ -1,5 +1,5 @@ # -# Copyright (c) 2023 Red Hat, Inc. +# Copyright (c) 2023 Red Hat Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except # in compliance with the License. You may obtain a copy of the License at @@ -12,6 +12,7 @@ # the License. # -# This file lists the Python dependencies used by the GitHub actions. +# This file contains the list of Python modules required by the `dev.py` script. -requests +click +click-default-group \ No newline at end of file diff --git a/dev/__init__.py b/dev/__init__.py new file mode 100644 index 00000000..73f313aa --- /dev/null +++ b/dev/__init__.py @@ -0,0 +1,31 @@ +# -*- coding: utf-8 -*- + +# +# Copyright (c) 2023 Red Hat Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except +# in compliance with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software distributed under the License +# is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express +# or implied. See the License for the specific language governing permissions and limitations under +# the License. +# + +from .build import * +from .ci_job import * +from .clean import * +from .deploy import * +from .fmt import * +from .formatter import * +from .generate import * +from .install import * +from .lint import * +from .push import * +from .run import * +from .setup import * +from .test import * +from .update import * +from .vet import * \ No newline at end of file diff --git a/dev/build.py b/dev/build.py new file mode 100755 index 00000000..d931b947 --- /dev/null +++ b/dev/build.py @@ -0,0 +1,88 @@ +# -*- coding: utf-8 -*- + +# +# Copyright (c) 2023 Red Hat Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except +# in compliance with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software distributed under the License +# is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express +# or implied. See the License for the specific language governing permissions and limitations under +# the License. +# + +import click +import click_default_group + +from . import command +from . import defaults + +@click.group( + cls=click_default_group.DefaultGroup, + default="binary", + default_if_no_args=True, +) +def build() -> None: + """ + Builds binaries, images, catalogs, etc. + """ + pass + +@build.command() +def binary() -> None: + """ + Builds the binary. + """ + command.run(args=["go", "build"]) + +@build.command() +@click.option( + "--repository", + help="Image repository.", + default=defaults.IMAGE_REPOSITORY, +) +@click.option( + "--tag", + help="Image tag.", + default=defaults.IMAGE_TAG, +) +def image( + repository: str, + tag: str, +) -> None: + """ + Builds the container image. + """ + command.run(args=[ + "podman", "build", + "--tag", f"{repository}:{tag}", + "--file", "Containerfile", + ]) + +@build.command() +@click.option( + "--repository", + help="Image repository.", + default=defaults.IMAGE_REPOSITORY, +) +@click.option( + "--tag", + help="Image tag.", + default=defaults.IMAGE_TAG, +) +def bundle_image( + repository: str, + tag: str, +) -> None: + """ + Builds the bundle image. + """ + command.run(args=[ + "podman", "build", + "--file", "bundle.Dockerfile", + "--tag", f"{repository}:{tag}" + ".", + ]) \ No newline at end of file diff --git a/dev/ci_job.py b/dev/ci_job.py new file mode 100755 index 00000000..496bcf4f --- /dev/null +++ b/dev/ci_job.py @@ -0,0 +1,31 @@ +# -*- coding: utf-8 -*- + +# +# Copyright (c) 2023 Red Hat Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except +# in compliance with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software distributed under the License +# is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express +# or implied. See the License for the specific language governing permissions and limitations under +# the License. +# + +import click + +from .fmt import fmt +from .lint import lint +from .test import test + +@click.command() +@click.pass_context +def ci_job(ctx: click.Context) -> None: + """ + Runs the CI job. + """ + ctx.invoke(fmt) + ctx.invoke(lint) + ctx.invoke(test) \ No newline at end of file diff --git a/dev/clean.py b/dev/clean.py new file mode 100755 index 00000000..586ad12a --- /dev/null +++ b/dev/clean.py @@ -0,0 +1,29 @@ +# -*- coding: utf-8 -*- + +# +# Copyright (c) 2023 Red Hat Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except +# in compliance with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software distributed under the License +# is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express +# or implied. See the License for the specific language governing permissions and limitations under +# the License. +# + +import os + +import click + +@click.command() +def clean() -> None: + """ + Removes temporary build artifacts. + """ + try: + os.remove("oran-o2ims") + except FileNotFoundError: + pass \ No newline at end of file diff --git a/dev/command.py b/dev/command.py new file mode 100755 index 00000000..6fa606de --- /dev/null +++ b/dev/command.py @@ -0,0 +1,48 @@ +# -*- coding: utf-8 -*- + +# +# Copyright (c) 2023 Red Hat Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except +# in compliance with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software distributed under the License +# is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express +# or implied. See the License for the specific language governing permissions and limitations under +# the License. +# + +import logging +import shlex +import subprocess + +def run( + args: list[str], + **kwargs, +) -> None: + """ + Runs the given command. + """ + cmd = ' '.join(map(shlex.quote, args)) + logging.debug(f"Running command '{cmd}'") + result = subprocess.run(args=args, **kwargs) + logging.debug(f"Exit code of '{cmd}' is {result.returncode}") + +def eval( + args: list[str], + **kwargs, +) -> tuple[int, str]: + """ + Runs the given command and returns the exit code and the text that it writes to the + standard output. + """ + cmd = ' '.join(map(shlex.quote, args)) + logging.debug(f"Evaluating command '{cmd}'") + result = subprocess.run(args=args, check=False, capture_output=True, **kwargs) + code = result.returncode + output = result.stdout.decode("utf-8").strip() + logging.debug(f"Exit code of '{cmd}' is {code}") + logging.debug(f"Output of '{cmd}' is '{output}'") + return (code, output) \ No newline at end of file diff --git a/dev/defaults.py b/dev/defaults.py new file mode 100755 index 00000000..333b354a --- /dev/null +++ b/dev/defaults.py @@ -0,0 +1,23 @@ +# -*- coding: utf-8 -*- + +# +# Copyright (c) 2024 Red Hat Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except +# in compliance with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software distributed under the License +# is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express +# or implied. See the License for the specific language governing permissions and limitations under +# the License. +# + +# Imane repository and tag: +IMAGE_REPOSITORY = "quay.io/openshift-kni/oran-o2ims" +IMAGE_TAG = "latest" + +# Catalog bundle image repository and tag: +BUNDLE_IMAGE_REPOSITORY = IMAGE_REPOSITORY + "-bundle" +BUNDLE_IMAGE_TAG = IMAGE_TAG \ No newline at end of file diff --git a/dev/deploy.py b/dev/deploy.py new file mode 100755 index 00000000..fbea574a --- /dev/null +++ b/dev/deploy.py @@ -0,0 +1,90 @@ +# -*- coding: utf-8 -*- + +# +# Copyright (c) 2024 Red Hat Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except +# in compliance with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software distributed under the License +# is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express +# or implied. See the License for the specific language governing permissions and limitations under +# the License. +# + +import os +import tempfile + +import click + +from . import command +from . import defaults + +@click.command() +@click.option( + "--image", + help="Image reference.", + default=f"{defaults.IMAGE_REPOSITORY}:{defaults.IMAGE_TAG}", +) +def deploy( + image: str, +) -> None: + """ + Deploy controller to the K8S cluster specified in ~/.kube/config. + """ + command.run( + cwd=os.path.join("config", "manager"), + args=[ + "kustomize", "edit", "set", "image", + f"controller={image}", + ], + ) + with tempfile.TemporaryFile() as tmp: + command.run( + args=[ + "kustomize", "build", + os.path.join("config", "default"), + ], + stdout=tmp, + ) + tmp.seek(0) + command.run( + args=[ + "kubectl", "apply", + "--filename", "-", + ], + stdin=tmp, + ) + +@click.command() +@click.option( + "--ignore-not-found", + help="Ignore resource not found errors during deletion.", + is_flag=True, + default=False, +) +def undeploy( + ignore_not_found: bool, +) -> None: + """ + Undeploy controller from the K8S cluster specified in ~/.kube/config. + """ + with tempfile.TemporaryFile() as tmp: + command.run( + args=[ + "kustomize", "build", + os.path.join("config", "default"), + ], + stdout=tmp, + ) + tmp.seek(0) + command.run( + args=[ + "kubectl", "delete", + f"--ignore-not-found={str(ignore_not_found).lower()}", + "--filename", "-", + ], + stdin=tmp, + ) \ No newline at end of file diff --git a/dev/fmt.py b/dev/fmt.py new file mode 100755 index 00000000..3fa8bda9 --- /dev/null +++ b/dev/fmt.py @@ -0,0 +1,26 @@ +# -*- coding: utf-8 -*- + +# +# Copyright (c) 2023 Red Hat Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except +# in compliance with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software distributed under the License +# is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express +# or implied. See the License for the specific language governing permissions and limitations under +# the License. +# + +import click + +from . import command + +@click.command() +def fmt() -> None: + """ + Formats Go code using the 'gofmt' tool. + """ + command.run(args=["gofmt", "-s", "-l", "-w", "."]) \ No newline at end of file diff --git a/dev/formatter.py b/dev/formatter.py new file mode 100755 index 00000000..5c8eb842 --- /dev/null +++ b/dev/formatter.py @@ -0,0 +1,32 @@ +# -*- coding: utf-8 -*- + +# +# Copyright (c) 2023 Red Hat Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except +# in compliance with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software distributed under the License +# is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express +# or implied. See the License for the specific language governing permissions and limitations under +# the License. +# + +import logging + +class Formatter: + INFO_PREFIX = "\033[32;1mI:\033[0m " + WARNING_PREFIX = "\033[33;1mW:\033[0m " + ERROR_PREFIX = "\033[31;1mE:\033[0m " + + def format(self, record: logging.LogRecord) -> str: + msg = str(record.msg) % record.args + prefix = __class__.INFO_PREFIX + match record.levelno: + case logging.CRITICAL | logging.ERROR: + prefix = __class__.ERROR_PREFIX + case logging.WARNING: + prefix = __class__.WARNING_PREFIX + return prefix + msg \ No newline at end of file diff --git a/dev/generate.py b/dev/generate.py new file mode 100755 index 00000000..cc762630 --- /dev/null +++ b/dev/generate.py @@ -0,0 +1,105 @@ +# -*- coding: utf-8 -*- + +# +# Copyright (c) 2023 Red Hat Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except +# in compliance with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software distributed under the License +# is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express +# or implied. See the License for the specific language governing permissions and limitations under +# the License. +# + +import os + +import click + +from . import command +from . import defaults + +@click.group(invoke_without_command=True) +@click.pass_context +def generate(ctx: click.Context): + """ + Generate code. + """ + if ctx.invoked_subcommand is not None: + return + ctx.invoke(mocks) + ctx.invoke(manifests) + ctx.invoke(deep_copy) + ctx.invoke(bundle) + +@generate.command() +def mocks() -> None: + """ + Generate mocks. + """ + command.run(args=["go", "generate", "./..."]) + +@generate.command() +def manifests() -> None: + """ + Generate webhook configuration, cluster role and custom resource definitions. + """ + command.run(args=[ + "controller-gen", + "rbac:roleName=manager-role", + "crd", + "webhook", + "paths=./...", + f"output:crd:artifacts:config={os.path.join('config', 'crd', 'bases')}", + ]) + +@generate.command() +def deep_copy() -> None: + """ + Generate deep copy code. + """ + command.run(args=[ + "controller-gen", + f"object:headerFile={os.path.join('hack', 'boilerplate.go.txt')}", + "paths=./...", + ]) + +@generate.command() +@click.option( + "--image", + help="Image reference.", + default=f"{defaults.IMAGE_REPOSITORY}:{defaults.IMAGE_TAG}", +) +def bundle(image: str) -> None: + """ + Generate operator bundle. + """ + # Generate the manifests: + command.run(args=[ + "operator-sdk", "generate", "kustomize", "manifests", + "--quiet", + "--apis-dir", "api", + ]) + + # Kustomize the manifests: + command.run( + cwd=os.path.join("config", "manager"), + args=[ + "kustomize", "edit", "set", "image", + f"controller={image}", + ], + ) + + # Generate the bundle: + command.run(args=[ + "kustomize", "build", + os.path.join("config", "manifests"), + ]) + + # Validate the bundle: + command.run(args=[ + "operator-sdk", "bundle", "validate", + os.path.join(".", "bundle"), + ]) diff --git a/dev/install.py b/dev/install.py new file mode 100755 index 00000000..b4210006 --- /dev/null +++ b/dev/install.py @@ -0,0 +1,75 @@ +# -*- coding: utf-8 -*- + +# +# Copyright (c) 2024 Red Hat Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except +# in compliance with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software distributed under the License +# is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express +# or implied. See the License for the specific language governing permissions and limitations under +# the License. +# + +import os +import tempfile + +import click + +from . import command + +@click.command() +def install() -> None: + """ + Install custom resource definitions into the K8S cluster specified in ~/.kube/config. + """ + with tempfile.TemporaryFile() as tmp: + command.run( + args=[ + "kustomize", "build", + os.path.join("config", "crd"), + ], + stdout=tmp, + ) + tmp.seek(0) + command.run( + args=[ + "kubectl", "apply", + "--filename", "-", + ], + stdin=tmp, + ) + +@click.command() +@click.option( + "--ignore-not-found", + help="Ignore resource not found errors during deletion.", + is_flag=True, + default=False, +) +def uninstall( + ignore_not_found: bool, +) -> None: + """ + Uninstall custom resource definitions from the K8S cluster specified in ~/.kube/config. + """ + with tempfile.TemporaryFile() as tmp: + command.run( + args=[ + "kustomize", "build", + os.path.join("config", "crd"), + ], + stdout=tmp, + ) + tmp.seek(0) + command.run( + args=[ + "kubectl", "delete", + f"--ignore-not-found={str(ignore_not_found).lower()}", + "--filename", "-", + ], + stdin=tmp, + ) \ No newline at end of file diff --git a/dev/lint.py b/dev/lint.py new file mode 100755 index 00000000..6e1cb4a3 --- /dev/null +++ b/dev/lint.py @@ -0,0 +1,39 @@ +# -*- coding: utf-8 -*- + +# +# Copyright (c) 2023 Red Hat Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except +# in compliance with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software distributed under the License +# is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express +# or implied. See the License for the specific language governing permissions and limitations under +# the License. +# + +import click +import logging + +from . import command + +@click.command() +def lint() -> None: + """ + Runs the linter. + """ + logging.debug(f"Running linter") + command.run(args=[ + "golangci-lint", + "--version", + ]) + command.run(args=[ + "golangci-lint", + "run", + "--verbose", + "--print-resources-usage", + "--modules-download-mode=vendor", + "--timeout=5m0s", + ]) \ No newline at end of file diff --git a/dev/push.py b/dev/push.py new file mode 100755 index 00000000..ef9074ba --- /dev/null +++ b/dev/push.py @@ -0,0 +1,84 @@ +# -*- coding: utf-8 -*- + +# +# Copyright (c) 2023 Red Hat Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except +# in compliance with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software distributed under the License +# is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express +# or implied. See the License for the specific language governing permissions and limitations under +# the License. +# + +""" +This script is intended to simplify development tasks. +""" + +import click +import click_default_group + +from . import command +from . import defaults + +@click.group( + cls=click_default_group.DefaultGroup, + default="image", + default_if_no_args=True, +) +def push() -> None: + """ + Pushes build artifacts. + """ + pass + +@push.command() +@click.option( + "--repository", + help="Image repository.", + default=defaults.IMAGE_REPOSITORY, +) +@click.option( + "--tag", + help="Image tag.", + default=defaults.IMAGE_TAG, +) +def image( + repository: str, + tag: str, +) -> None: + """ + Pushes the container image to the image registry. + """ + command.run(args=[ + "podman", + "push", + f"{repository}:{tag}", + ]) + +@push.command() +@click.option( + "--repository", + help="Image repository.", + default=defaults.BUNDLE_IMAGE_REPOSITORY +) +@click.option( + "--tag", + help="Image tag.", + default=defaults.BUNDLE_IMAGE_TAG, +) +def bundle_image( + repository: str, + tag: str, +) -> None: + """ + Pushes the catalog bundle image. + """ + command.run(args=[ + "podman", + "push", + f"{repository}:{tag}", + ]) \ No newline at end of file diff --git a/dev/run.py b/dev/run.py new file mode 100755 index 00000000..78c425da --- /dev/null +++ b/dev/run.py @@ -0,0 +1,30 @@ +# -*- coding: utf-8 -*- + +# +# Copyright (c) 2024 Red Hat Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except +# in compliance with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software distributed under the License +# is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express +# or implied. See the License for the specific language governing permissions and limitations under +# the License. +# + +import os + +import click + +from . import command + +@click.command() +def run() -> None: + """ + Run a controller from your host. + """ + command.run(args=[ + "go", "run", os.path.join(".", "cmd", "main.go") + ]) \ No newline at end of file diff --git a/dev/setup.py b/dev/setup.py new file mode 100755 index 00000000..914680f2 --- /dev/null +++ b/dev/setup.py @@ -0,0 +1,367 @@ +# -*- coding: utf-8 -*- + +# +# Copyright (c) 2023 Red Hat Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except +# in compliance with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software distributed under the License +# is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express +# or implied. See the License for the specific language governing permissions and limitations under +# the License. +# + +import functools +import logging +import os +import pathlib +import re +import shutil +import stat +import tempfile + +import click + +from . import command +from . import versions + +@click.command() +def setup() -> None: + """ + Prepares the development environment. + """ + # Install Go: + install_go() + + # Install other tools: + install_controller_gen() + install_ginkgo() + install_golangci_lint() + install_kustomize() + install_mockgen() + install_operator_sdk() + install_opm() + install_setup_envtest() + install_spectral() + +def install_go() -> None: + """ + Installs the Go compiler. + """ + # First check if it is already installed: + directory = shutil.which("go") + if directory is not None: + logging.info(f"The 'go' tool is already installed at '{directory}'") + return + + # We could install Go with an approach similar to what we do for 'golangci-lint', downloadi + # it from the Go downloads page. But then we would need to find a directory where to install + # it. We don't want to do that for now, so instead we just complain. + raise Exception("The 'go' tool isn't available") + +def install_ginkgo() -> None: + """ + Installs the 'ginkgo' tool. + """ + go_install(tool="github.com/onsi/ginkgo/v2/ginkgo") + +def install_mockgen() -> None: + """ + Installs the 'mockgen' tool. + """ + go_install( + tool="go.uber.org/mock/mockgen", + version=f"v{versions.MOCKGEN}", + ) + +def install_golangci_lint() -> None: + """ + Installs the 'golangci-lint' tool. + """ + # The team that develops this tool doesn't recommend installing it with 'go install', see + # here for details: + # + # https://golangci-lint.run/usage/install/#install-from-source + # + # So instead of that we will donwload the artifact from the GitHub releases page and install + # it manually. + + # First check if it is already installed: + binary = shutil.which("golangci-lint") + if binary is not None: + logging.info(f"Tool 'golangci-lint' is already installed at '{binary}'") + return + + # Find an installation directory: + bin = select_bin_directory() + + # Now download, verify and install it from the GitHub releases page: + digest = "ca21c961a33be3bc15e4292dc40c98c8dcc5463a7b6768a3afc123761630c09c" + platform = go_env("GOOS") + architecture = go_env("GOARCH") + url = ( + "https://github.com" + f"/golangci/golangci-lint/releases/download/v{versions.GOLANGCI_LINT}" + f"/golangci-lint-{versions.GOLANGCI_LINT}-{platform}-{architecture}.tar.gz" + ) + tmp = tempfile.mkdtemp() + try: + tarball_file = os.path.join(tmp, "tarball") + command.run(args=[ + "curl", + "--location", + "--silent", + "--fail", + "--output", tarball_file, + url, + ]) + digest_file = os.path.join(tmp, "digest") + with open(file=digest_file, encoding="utf-8", mode="w") as file: + file.write(f"{digest} {tarball_file}\n") + command.run(args=[ + "sha256sum", + "--check", + digest_file, + ]) + command.run(args=[ + "tar", + "--directory", bin, + "--extract", + "--file", tarball_file, + "--strip-components", "1", + f"golangci-lint-{versions.GOLANGCI_LINT}-{platform}-{architecture}/golangci-lint", + ]) + finally: + shutil.rmtree(tmp) + +def install_opm() -> None: + """ + Installs the 'opm' tool. + """ + # First check if the binary already exists: + binary = shutil.which("opm") + if binary is not None: + logging.info(f"Tool 'opm' is already installed at '{binary}'") + return + + # Find an installation directory: + bin = select_bin_directory() + + # Download and install the binary: + platform = go_env("GOOS") + architecture = go_env("GOARCH") + url = ( + "https://github.com" + f"/operator-framework/operator-registry/releases/download/v{versions.OPM}" + f"/{platform}-{architecture}-opm" + ) + file = os.path.join(bin, "opm") + command.run(args=[ + "curl", + "--location", + "--silent", + "--fail", + "--output", file, + url, + ]) + + # Add the execution permission to the binary: + mode = os.stat(file).st_mode + mode |= stat.S_IXUSR + os.chmod(file, mode) + +def install_operator_sdk() -> None: + """ + Install the operator SDK. + """ + # First check if the binary already exists: + binary = shutil.which("operator-sdk") + if binary is not None: + logging.info(f"Tool 'operator-sdk' is already installed at '{binary}'") + return + + # Find an installation directory: + bin = select_bin_directory() + + # Download and install the binary: + platform = go_env("GOOS") + architecture = go_env("GOARCH") + url = ( + "https://github.com" + f"/operator-framework/operator-sdk/releases/download/v{versions.OPERATOR_SDK}" + f"/operator-sdk_{platform}_{architecture}" + ) + file = os.path.join(bin, "operator-sdk") + command.run(args=[ + "curl", + "--location", + "--silent", + "--fail", + "--output", file, + url, + ]) + + # Add the execution permission to the binary: + mode = os.stat(file).st_mode + mode |= stat.S_IXUSR + os.chmod(file, mode) + +def install_kustomize() -> None: + """ + Install the 'kustomize' tool. + """ + go_install( + tool="sigs.k8s.io/kustomize/kustomize/v5", + version=f"v{versions.KUSTOMIZE}", + ) + +def install_controller_gen() -> None: + """ + Install the 'controller-gen' tool. + """ + go_install( + tool="sigs.k8s.io/controller-tools/cmd/controller-gen", + version=f"v{versions.CONTROLLER_GEN}", + ) + +def install_setup_envtest() -> None: + """Install the 'envtest' tool.""" + go_install( + tool="sigs.k8s.io/controller-runtime/tools/setup-envtest", + version="latest", + ) + +def install_spectral() -> None: + """ + Install spectral. + """ + # First check if the binary already exists: + binary = shutil.which("spectral") + if binary is not None: + logging.info(f"Tool 'spectral' is already installed at '{binary}'") + return + + # Find an installation directory: + bin = select_bin_directory() + + # Download and install the binary: + platform = go_env("GOOS") + architecture = go_env("GOARCH") + if architecture == "amd64": + architecture = "x64" + url = ( + "https://github.com" + f"/stoplightio/spectral/releases/download/v{versions.SPECTRAL}" + f"/spectral-{platform}-{architecture}" + ) + file = os.path.join(bin, "spectral") + command.run(args=[ + "curl", + "--location", + "--silent", + "--fail", + "--output", file, + url, + ]) + + # Add the execution permission to the binary: + mode = os.stat(file).st_mode + mode |= stat.S_IXUSR + os.chmod(file, mode) + +@functools.cache +def go_env(var: str) -> str: + """ + Returns the value of an environment variable as reported by the 'go env' command. For example, + in a Linux platform the value for 'GOOS' will be 'linux'. + """ + code, output = command.eval(args=[ + "go", "env", var, + ]) + if code != 0: + raise Exception(f"Failed to get Go environment variable '{var}'") + return output + +def go_install( + tool: str, + version: str | None = None, +) -> None: + """ + Uses the 'go install' command to install the given tool. + + The tool parameter is the complete Go path of the binary. For example, for the 'ginkgo' tool + the value should be 'github.com/onsi/ginkgo/v2/ginkgo'. + + The version is required when the tool isn't a dependency of the project. For example, the + 'mockgen' command isn't usually a dependency of the project because the mock code that it + generates doesn't depend on the mock generation code itself. In those cases the version + can't be extracted from the 'go.mod' file, so it needs to be provided explicitly. + """ + # Check if the binary already exists. Note that usually the name of the binary will be the + # last segment of the package path, but that last segment can also be a version number like + # `v5`. In that case we need to ignore it and use the previous segment. + segments = tool.split("/") + name = segments[-1] + if re.match(r"^v\d+$", name): + name = segments[-2] + binary = shutil.which(name) + if binary is not None: + logging.info(f"Tool '{name}' is already installed at '{binary}'") + return + + # If the version hasn't been specified we need to extract it from the dependencies. Note that + # we need to try with the complete package path, and then with the parent, so on. That is + # because we don't know what part of the path corresponds the Go module. + if version is None: + for i in range(len(segments)-1, 2, -1): + package = "/".join(segments[0:i]) + code, version = command.eval(args=[ + "go", "list", "-f", "{{.Version}}", "-m", package + ]) + if code == 0: + break + if version is None: + raise Exception(f"Failed to find version for tool '{tool}'") + logging.info(f"Version of tool '{tool}' is '{version}'") + + # Try to install: + command.run(args=[ + "go", "install", f"{tool}@{version}", + ]) + + +def select_bin_directory() -> str: + """ + Tries to find the binaries directory. + """ + # Preapre a set containing the directories from the PATH environment variable: + path = os.getenv("PATH") + path = path.split(os.pathsep) + path = {pathlib.Path(d) for d in path} + + # Use the project specific binaries directory it is in the path: + project = pathlib.Path(__file__).parent.parent + bin = project.parent / ".local" / "bin" + if bin in path: + return str(bin) + + # Use the binaries directory inside the repository if it is in the path: + bin = project / "bin" + if bin in path: + return str(bin) + + # Use the Go binaries directory if it is in the path: + bin = go_env("GOBIN") + if bin in path: + return bin + root = go_env("GOROOT") + root = pathlib.Path(root) + bin = root / "bin" + if bin in path: + return str(bin) + + # If we are here then we failed to find a suitable binaries directory: + raise Exception("Failed to select a suitable binaries directory") \ No newline at end of file diff --git a/dev/test.py b/dev/test.py new file mode 100755 index 00000000..6faf3298 --- /dev/null +++ b/dev/test.py @@ -0,0 +1,33 @@ +# -*- coding: utf-8 -*- + +# +# Copyright (c) 2023 Red Hat Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except +# in compliance with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software distributed under the License +# is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express +# or implied. See the License for the specific language governing permissions and limitations under +# the License. +# + +import os +import shlex + +import click + +from . import command + +@click.command() +def test() -> None: + """ + Runs tests. + """ + ginkgo_args = ["ginkgo", "run", "-r"] + ginkgo_flags = os.getenv("GINKGO_FLAGS") + if ginkgo_flags is not None: + ginkgo_args.extend(shlex.split(ginkgo_flags)) + command.run(args=ginkgo_args) \ No newline at end of file diff --git a/dev/update.py b/dev/update.py new file mode 100755 index 00000000..a7fe16fa --- /dev/null +++ b/dev/update.py @@ -0,0 +1,37 @@ +# -*- coding: utf-8 -*- + +# +# Copyright (c) 2024 Red Hat Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except +# in compliance with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software distributed under the License +# is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express +# or implied. See the License for the specific language governing permissions and limitations under +# the License. +# + +import click + +from . import command + +@click.group(invoke_without_command=True) +@click.pass_context +def update(ctx: click.Context): + """ + Update code. + """ + if ctx.invoked_subcommand is not None: + return + ctx.invoke(dependencies) + +@update.command() +def dependencies() -> None: + """ + Update dependencies. + """ + command.run(args=["go", "mod", "vendor"]) + command.run(args=["go", "mod", "tidy"]) \ No newline at end of file diff --git a/dev/versions.py b/dev/versions.py new file mode 100755 index 00000000..9f4fb336 --- /dev/null +++ b/dev/versions.py @@ -0,0 +1,23 @@ +# -*- coding: utf-8 -*- + +# +# Copyright (c) 2024 Red Hat Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except +# in compliance with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software distributed under the License +# is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express +# or implied. See the License for the specific language governing permissions and limitations under +# the License. +# + +CONTROLLER_GEN = "0.13.0" +GOLANGCI_LINT = "1.55.2" +KUSTOMIZE = "5.2.1" +MOCKGEN = "0.3.0" +OPERATOR_SDK = "1.33.0" +OPM = "1.23.0" +SPECTRAL = "6.11.0" \ No newline at end of file diff --git a/dev/vet.py b/dev/vet.py new file mode 100755 index 00000000..fe045cc4 --- /dev/null +++ b/dev/vet.py @@ -0,0 +1,26 @@ +# -*- coding: utf-8 -*- + +# +# Copyright (c) 2024 Red Hat Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except +# in compliance with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software distributed under the License +# is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express +# or implied. See the License for the specific language governing permissions and limitations under +# the License. +# + +import click + +from . import command + +@click.command() +def vet() -> None: + """ + Runs go vet against code. + """ + command.run(args=["go", "vet", "./..."]) \ No newline at end of file diff --git a/hack/install_test_deps.sh b/hack/install_test_deps.sh deleted file mode 100755 index 4623a5c0..00000000 --- a/hack/install_test_deps.sh +++ /dev/null @@ -1,25 +0,0 @@ -#!/bin/bash - -set -e - -go install github.com/onsi/ginkgo/v2/ginkgo@$(go list -f '{{.Version}}' -m github.com/onsi/ginkgo/v2) -go install go.uber.org/mock/mockgen@v0.3.0 - -if ! [ -x "$(command -v golangci-lint)" ]; then - echo "Downloading golangci-lint" - - curl -Lo tarball https://github.com/golangci/golangci-lint/releases/download/v1.55.2/golangci-lint-1.55.2-linux-amd64.tar.gz - echo ca21c961a33be3bc15e4292dc40c98c8dcc5463a7b6768a3afc123761630c09c tarball | sha256sum -c - tar -C $(go env GOPATH)/bin --strip-components=1 -xf tarball golangci-lint-1.55.2-linux-amd64/golangci-lint - rm tarball -fi - - -if ! [ -x "$(command -v spectral)" ]; then - echo "Downloading spectral" - - curl -Lo spectral https://github.com/stoplightio/spectral/releases/download/v6.11.0/spectral-linux-x64 - echo 0e151d3dc5729750805428f79a152fa01dd4c203f1d9685ef19f4fd4696fcd5f spectral | sha256sum -c - chmod +x spectral - mv spectral $(go env GOPATH)/bin -fi diff --git a/hack/update_deps.sh b/hack/update_deps.sh deleted file mode 100755 index 268c98cc..00000000 --- a/hack/update_deps.sh +++ /dev/null @@ -1,5 +0,0 @@ -#!/bin/bash - -go mod vendor -go mod tidy -