diff --git a/.ci/vgot.sh b/.ci/vgot.sh index 9fce1450a4..f1ceaa19ff 100755 --- a/.ci/vgot.sh +++ b/.ci/vgot.sh @@ -8,7 +8,7 @@ cd "$d" go mod init temp >/dev/null 2>&1 for i; do pkg=`echo $i | sed 's/@.*//'` - go get "$i" && + go get -d "$i" && go install "$pkg" && echo installed `go list -f '{{.ImportPath}}@{{.Module.Version}}' "$pkg"` done diff --git a/.github/workflows/base-checks.yaml b/.github/workflows/base-checks.yaml index e2bd6b1db5..be7bc1c4ed 100644 --- a/.github/workflows/base-checks.yaml +++ b/.github/workflows/base-checks.yaml @@ -11,7 +11,7 @@ jobs: runs-on: ubuntu-20.04 steps: - name: Set up Go - uses: actions/setup-go@v2.1.4 + uses: actions/setup-go@v2.2.0 with: go-version: 1.16 diff --git a/.github/workflows/e2e-allinone.yaml b/.github/workflows/e2e-allinone.yaml new file mode 100644 index 0000000000..9672752c43 --- /dev/null +++ b/.github/workflows/e2e-allinone.yaml @@ -0,0 +1,26 @@ +name: All in One E2E tests + +on: + push: + branches: [ master ] + pull_request: + branches: [ master ] + +jobs: + run-e2e-allinone-test-suite: + runs-on: ubuntu-20.04 + strategy: + matrix: + kube-version: + - "1.19" + - "1.20" + - "1.21" + - "1.22" + name: Run all in one E2E tests + steps: + - name: "Check out code into the Go module directory" + uses: actions/checkout@v2 + - uses: ./hack/actions/e2e + with: + testsuite_name: allinone + kube_version: ${{ matrix.kube-version }} diff --git a/.github/workflows/e2e-cassandra.yaml b/.github/workflows/e2e-cassandra.yaml new file mode 100644 index 0000000000..b684551be7 --- /dev/null +++ b/.github/workflows/e2e-cassandra.yaml @@ -0,0 +1,26 @@ +name: Cassandra E2E tests + +on: + push: + branches: [ master ] + pull_request: + branches: [ master ] + +jobs: + run-e2e-cassandra-test-suite: + runs-on: ubuntu-20.04 + strategy: + matrix: + kube-version: + - "1.19" + - "1.20" + - "1.21" + - "1.22" + name: Run Cassandra E2E tests + steps: + - name: "Check out code into the Go module directory" + uses: actions/checkout@v2 + - uses: ./hack/actions/e2e + with: + testsuite_name: cassandra + kube_version: ${{ matrix.kube-version }} diff --git a/.github/workflows/e2e-elasticsearch.yaml b/.github/workflows/e2e-elasticsearch.yaml new file mode 100644 index 0000000000..307ff4958a --- /dev/null +++ b/.github/workflows/e2e-elasticsearch.yaml @@ -0,0 +1,26 @@ +name: Elasticsearch E2E tests + +on: + push: + branches: [ master ] + pull_request: + branches: [ master ] + +jobs: + run-e2e-elasticsearch-test-suite: + runs-on: ubuntu-20.04 + strategy: + matrix: + kube-version: + - "1.19" + - "1.20" + - "1.21" + - "1.22" + name: Run Elasticsearch E2E tests + steps: + - name: "Check out code into the Go module directory" + uses: actions/checkout@v2 + - uses: ./hack/actions/e2e + with: + testsuite_name: elasticsearch + kube_version: ${{ matrix.kube-version }} diff --git a/.github/workflows/e2e-examples.yaml b/.github/workflows/e2e-examples.yaml new file mode 100644 index 0000000000..5efc8ecbee --- /dev/null +++ b/.github/workflows/e2e-examples.yaml @@ -0,0 +1,26 @@ +name: Examples E2E tests + +on: + push: + branches: [ master ] + pull_request: + branches: [ master ] + +jobs: + run-e2e-examples-test-suite: + runs-on: ubuntu-20.04 + strategy: + matrix: + kube-version: + - "1.19" + - "1.20" + - "1.21" + - "1.22" + name: Run examples E2E tests + steps: + - name: "Check out code into the Go module directory" + uses: actions/checkout@v2 + - uses: ./hack/actions/e2e + with: + testsuite_name: examples + kube_version: ${{ matrix.kube-version }} diff --git a/.github/workflows/e2e-generate.yaml b/.github/workflows/e2e-generate.yaml new file mode 100644 index 0000000000..3e75824506 --- /dev/null +++ b/.github/workflows/e2e-generate.yaml @@ -0,0 +1,26 @@ +name: Generate E2E tests + +on: + push: + branches: [ master ] + pull_request: + branches: [ master ] + +jobs: + run-e2e-generate-test-suite: + runs-on: ubuntu-20.04 + strategy: + matrix: + kube-version: + - "1.19" + - "1.20" + - "1.21" + - "1.22" + name: Run generate E2E tests + steps: + - name: "Check out code into the Go module directory" + uses: actions/checkout@v2 + - uses: ./hack/actions/e2e + with: + testsuite_name: generate + kube_version: ${{ matrix.kube-version }} diff --git a/.github/workflows/e2e-istio.yaml b/.github/workflows/e2e-istio.yaml new file mode 100644 index 0000000000..f2f9b6f736 --- /dev/null +++ b/.github/workflows/e2e-istio.yaml @@ -0,0 +1,26 @@ +name: Istio E2E tests + +on: + push: + branches: [ master ] + pull_request: + branches: [ master ] + +jobs: + run-e2e-istio-test-suite: + runs-on: ubuntu-20.04 + strategy: + matrix: + kube-version: + - "1.19" + - "1.20" + - "1.21" + - "1.22" + name: Run Istio E2E tests + steps: + - name: "Check out code into the Go module directory" + uses: actions/checkout@v2 + - uses: ./hack/actions/e2e + with: + testsuite_name: istio + kube_version: ${{ matrix.kube-version }} diff --git a/.github/workflows/e2e-outside-cluster.yaml b/.github/workflows/e2e-outside-cluster.yaml new file mode 100644 index 0000000000..89e342c934 --- /dev/null +++ b/.github/workflows/e2e-outside-cluster.yaml @@ -0,0 +1,26 @@ +name: Outside cluster E2E tests + +on: + push: + branches: [ master ] + pull_request: + branches: [ master ] + +jobs: + run-e2e-outside-cluster-test-suite: + runs-on: ubuntu-20.04 + strategy: + matrix: + kube-version: + - "1.19" + - "1.20" + - "1.21" + - "1.22" + name: Run outside cluster E2E tests + steps: + - name: "Check out code into the Go module directory" + uses: actions/checkout@v2 + - uses: ./hack/actions/e2e + with: + testsuite_name: outside-cluster + kube_version: ${{ matrix.kube-version }} diff --git a/.github/workflows/e2e-sidecar.yaml b/.github/workflows/e2e-sidecar.yaml new file mode 100644 index 0000000000..9384c4a337 --- /dev/null +++ b/.github/workflows/e2e-sidecar.yaml @@ -0,0 +1,26 @@ +name: Sidecar E2E tests + +on: + push: + branches: [ master ] + pull_request: + branches: [ master ] + +jobs: + run-e2e-sidecar-test-suite: + runs-on: ubuntu-20.04 + strategy: + matrix: + kube-version: + - "1.19" + - "1.20" + - "1.21" + - "1.22" + name: Run sidecar E2E tests + steps: + - name: "Check out code into the Go module directory" + uses: actions/checkout@v2 + - uses: ./hack/actions/e2e + with: + testsuite_name: sidecard + kube_version: ${{ matrix.kube-version }} diff --git a/.github/workflows/e2e-smoke.yaml b/.github/workflows/e2e-smoke.yaml new file mode 100644 index 0000000000..3ded742226 --- /dev/null +++ b/.github/workflows/e2e-smoke.yaml @@ -0,0 +1,26 @@ +name: Smoke E2E tests + +on: + push: + branches: [ master ] + pull_request: + branches: [ master ] + +jobs: + run-e2e-smoke-test-suite: + runs-on: ubuntu-20.04 + strategy: + matrix: + kube-version: + - "1.19" + - "1.20" + - "1.21" + - "1.22" + name: Run smoke E2E tests + steps: + - name: "Check out code into the Go module directory" + uses: actions/checkout@v2 + - uses: ./hack/actions/e2e + with: + testsuite_name: smoke + kube_version: ${{ matrix.kube-version }} diff --git a/.github/workflows/e2e-streaming.yaml b/.github/workflows/e2e-streaming.yaml new file mode 100644 index 0000000000..c89a7fc293 --- /dev/null +++ b/.github/workflows/e2e-streaming.yaml @@ -0,0 +1,26 @@ +name: Streaming E2E tests + +on: + push: + branches: [ master ] + pull_request: + branches: [ master ] + +jobs: + run-e2e-streaming-test-suite: + runs-on: ubuntu-20.04 + strategy: + matrix: + kube-version: + - "1.19" + - "1.20" + - "1.21" + - "1.22" + name: Run streaming E2E tests + steps: + - name: "Check out code into the Go module directory" + uses: actions/checkout@v2 + - uses: ./hack/actions/e2e + with: + testsuite_name: streaming + kube_version: ${{ matrix.kube-version }} diff --git a/.github/workflows/e2e-upgrade.yaml b/.github/workflows/e2e-upgrade.yaml new file mode 100644 index 0000000000..125871c8b3 --- /dev/null +++ b/.github/workflows/e2e-upgrade.yaml @@ -0,0 +1,26 @@ +name: Upgrade E2E tests + +on: + push: + branches: [ master ] + pull_request: + branches: [ master ] + +jobs: + run-e2e-upgrade-test-suite: + runs-on: ubuntu-20.04 + strategy: + matrix: + kube-version: + - "1.19" + - "1.20" + - "1.21" + - "1.22" + name: Run upgrade E2E tests + steps: + - name: "Check out code into the Go module directory" + uses: actions/checkout@v2 + - uses: ./hack/actions/e2e + with: + testsuite_name: upgrade + kube_version: ${{ matrix.kube-version }} diff --git a/.github/workflows/e2e.yaml b/.github/workflows/e2e.yaml deleted file mode 100644 index 5963e863b2..0000000000 --- a/.github/workflows/e2e.yaml +++ /dev/null @@ -1,309 +0,0 @@ -name: "End-to-end tests" - -on: - push: - branches: [ master ] - pull_request: - branches: [ master ] - -jobs: - allinone-e2e-tests: - name: All in one E2E tests - runs-on: ubuntu-20.04 - strategy: - matrix: - kube-version: - - "1.19" - - "1.20" - - "1.21" - - "1.22" - steps: - - name: "Set up Go" - uses: actions/setup-go@v2.1.4 - with: - go-version: 1.16 - - name: "Check out code into the Go module directory" - uses: actions/checkout@v2.4.0 - - name: "Install KIND" - run: ./.ci/install-kind.sh - - name: "Install KUTTL" - run: ./.ci/install-kuttl.sh - - name: "install gomplate" - run: ./.ci/install-gomplate.sh - - name: "Install dependencies" - run: make install-tools - - name: "Run E2E test suite" - env: - KUBE_VERSION: ${{ matrix.kube-version }} - run: make run-e2e-tests-allinone - cassandra-e2e-tests: - name: Cassandra E2E tests - runs-on: ubuntu-20.04 - strategy: - matrix: - kube-version: - - "1.19" - - "1.20" - - "1.21" - - "1.22" - steps: - - - name: "Set up Go" - uses: actions/setup-go@v2.1.4 - with: - go-version: 1.16 - - name: "Check out code into the Go module directory" - uses: actions/checkout@v2.4.0 - - name: "Install KUTTL" - run: ./.ci/install-kuttl.sh - - name: "install gomplate" - run: ./.ci/install-gomplate.sh - - name: "Install dependencies" - run: make install-tools - - name: "Run E2E test suite" - env: - KUBE_VERSION: ${{ matrix.kube-version }} - run: make run-e2e-tests-cassandra - elasticsearch-e2e-tests: - name: Elasticsearch E2E tests - runs-on: ubuntu-20.04 - strategy: - matrix: - kube-version: - - "1.19" - - "1.20" - - "1.21" - - "1.22" - steps: - - name: "Set up Go" - uses: actions/setup-go@v2.1.4 - with: - go-version: 1.16 - - name: "Check out code into the Go module directory" - uses: actions/checkout@v2.4.0 - - name: "Install KUTTL" - run: ./.ci/install-kuttl.sh - - name: "install gomplate" - run: ./.ci/install-gomplate.sh - - name: "Install dependencies" - run: make install-tools - - name: "Run E2E test suite" - env: - KUBE_VERSION: ${{ matrix.kube-version }} - run: make run-e2e-tests-elasticsearch - examples-e2e-tests: - name: Examples E2E tests - runs-on: ubuntu-20.04 - strategy: - matrix: - kube-version: - - "1.19" - - "1.20" - - "1.21" - - "1.22" - steps: - - name: "Set up Go" - uses: actions/setup-go@v2.1.4 - with: - go-version: 1.16 - - name: "Check out code into the Go module directory" - uses: actions/checkout@v2.4.0 - - name: "Install KUTTL" - run: ./.ci/install-kuttl.sh - - name: "install gomplate" - run: ./.ci/install-gomplate.sh - - name: "Install dependencies" - run: make install-tools - - name: "Run E2E test suite" - env: - KUBE_VERSION: ${{ matrix.kube-version }} - run: make run-e2e-tests-examples - generate-e2e-tests: - name: Generate E2E tests - runs-on: ubuntu-20.04 - strategy: - matrix: - kube-version: - - "1.19" - - "1.20" - - "1.21" - - "1.22" - steps: - - name: "Set up Go" - uses: actions/setup-go@v2.1.4 - with: - go-version: 1.16 - - name: "Check out code into the Go module directory" - uses: actions/checkout@v2.4.0 - - name: "Install KUTTL" - run: ./.ci/install-kuttl.sh - - name: "install gomplate" - run: ./.ci/install-gomplate.sh - - name: "Install dependencies" - run: make install-tools - - name: "Run E2E test suite" - env: - KUBE_VERSION: ${{ matrix.kube-version }} - run: make run-e2e-tests-generate - istio-e2e-tests: - name: Istio E2E tests - runs-on: ubuntu-20.04 - strategy: - matrix: - kube-version: - - "1.19" - - "1.20" - - "1.21" - - "1.22" - steps: - - name: "Set up Go" - uses: actions/setup-go@v2.1.4 - with: - go-version: 1.16 - - name: "Check out code into the Go module directory" - uses: actions/checkout@v2.4.0 - - name: "Install KUTTL" - run: ./.ci/install-kuttl.sh - - name: "install gomplate" - run: ./.ci/install-gomplate.sh - - name: "Install dependencies" - run: make install-tools - - name: "Run E2E test suite" - env: - KUBE_VERSION: ${{ matrix.kube-version }} - run: make run-e2e-tests-istio - outside-cluster-e2e-tests: - name: Outside cluster E2E tests - runs-on: ubuntu-20.04 - strategy: - matrix: - kube-version: - - "1.19" - - "1.20" - - "1.21" - - "1.22" - steps: - - name: "Set up Go" - uses: actions/setup-go@v2.1.4 - with: - go-version: 1.16 - - name: "Check out code into the Go module directory" - uses: actions/checkout@v2.4.0 - - name: "Install KUTTL" - run: ./.ci/install-kuttl.sh - - name: "install gomplate" - run: ./.ci/install-gomplate.sh - - name: "Install dependencies" - run: make install-tools - - name: "Run E2E test suite" - env: - KUBE_VERSION: ${{ matrix.kube-version }} - run: make run-e2e-tests-outside-cluster - smoke-e2e-sidecard: - name: Sidecar E2E tests - runs-on: ubuntu-20.04 - strategy: - matrix: - kube-version: - - "1.19" - - "1.20" - - "1.21" - - "1.22" - steps: - - name: "Set up Go" - uses: actions/setup-go@v2.1.4 - with: - go-version: 1.16 - - name: "Check out code into the Go module directory" - uses: actions/checkout@v2.4.0 - - name: "Install KUTTL" - run: ./.ci/install-kuttl.sh - - name: "install gomplate" - run: ./.ci/install-gomplate.sh - - name: "Install dependencies" - run: make install-tools - - name: "Run E2E test suite" - env: - KUBE_VERSION: ${{ matrix.kube-version }} - run: make run-e2e-tests-sidecard - smoke-e2e-tests: - name: Smoke E2E tests - runs-on: ubuntu-20.04 - strategy: - matrix: - kube-version: - - "1.19" - - "1.20" - - "1.21" - - "1.22" - steps: - - name: "Set up Go" - uses: actions/setup-go@v2.1.4 - with: - go-version: 1.16 - - name: "Check out code into the Go module directory" - uses: actions/checkout@v2.4.0 - - name: "Install KUTTL" - run: ./.ci/install-kuttl.sh - - name: "install gomplate" - run: ./.ci/install-gomplate.sh - - name: "Install dependencies" - run: make install-tools - - name: "Run E2E test suite" - env: - KUBE_VERSION: ${{ matrix.kube-version }} - run: make run-e2e-tests-smoke - streaming-e2e-tests: - name: Streaming E2E tests - runs-on: ubuntu-20.04 - strategy: - matrix: - kube-version: - - "1.19" - - "1.20" - - "1.21" - - "1.22" - steps: - - name: "Set up Go" - uses: actions/setup-go@v2.1.4 - with: - go-version: 1.16 - - name: "Check out code into the Go module directory" - uses: actions/checkout@v2.4.0 - - name: "Install KUTTL" - run: ./.ci/install-kuttl.sh - - name: "install gomplate" - run: ./.ci/install-gomplate.sh - - name: "Install dependencies" - run: make install-tools - - name: "Run E2E test suite" - env: - KUBE_VERSION: ${{ matrix.kube-version }} - run: make run-e2e-tests-streaming - upgrade-e2e-tests: - name: Upgrade E2E tests - runs-on: ubuntu-20.04 - strategy: - matrix: - kube-version: - - "1.19" - - "1.20" - - "1.21" - - "1.22" - steps: - - name: "Set up Go" - uses: actions/setup-go@v2.1.4 - with: - go-version: 1.16 - - name: "Check out code into the Go module directory" - uses: actions/checkout@v2.4.0 - - name: "Install KUTTL" - run: ./.ci/install-kuttl.sh - - name: "install gomplate" - run: ./.ci/install-gomplate.sh - - name: "Install dependencies" - run: make install-tools - - name: "Run E2E test suite" - env: - KUBE_VERSION: ${{ matrix.kube-version }} - run: make run-e2e-tests-upgrade diff --git a/.github/workflows/release.yaml b/.github/workflows/release.yaml index aa612e9b79..3b18cfcb0d 100644 --- a/.github/workflows/release.yaml +++ b/.github/workflows/release.yaml @@ -10,7 +10,7 @@ jobs: runs-on: ubuntu-20.04 steps: - name: Set up Go - uses: actions/setup-go@v2 + uses: actions/setup-go@v2.2.0 with: go-version: 1.16 diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 841a0b1f5e..0e84f38351 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -31,7 +31,8 @@ NOTE: Make sure to read the documentation to learn the performance switches that Once minikube has finished starting, get the Operator running: ``` -make run +make cert-manager +IMG=docker.io/$USER/jaeger-operator:latest make generate bundle docker push deploy ``` At this point, a Jaeger instance can be installed: diff --git a/Dockerfile b/Dockerfile index dd6e96e2f8..f8d7e1919e 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,7 +1,6 @@ # Build the manager binary FROM --platform=${BUILDPLATFORM:-linux/amd64} golang:1.16 as builder - WORKDIR /workspace # Copy the Go Modules manifests COPY go.mod go.mod @@ -33,22 +32,9 @@ ARG TARGETARCH # Build RUN CGO_ENABLED=0 GOOS=linux GOARCH=${TARGETARCH} GO111MODULE=on go build -ldflags="-X ${VERSION_PKG}.version=${VERSION} -X ${VERSION_PKG}.buildDate=${VERSION_DATE} -X ${VERSION_PKG}.defaultJaeger=${JAEGER_VERSION}" -a -o jaeger-operator main.go -FROM registry.access.redhat.com/ubi8/ubi - -ENV USER_UID=1001 \ - USER_NAME=jaeger-operator - -RUN INSTALL_PKGS="openssl" && \ - yum install -y $INSTALL_PKGS && \ - rpm -V $INSTALL_PKGS && \ - yum clean all && \ - mkdir /tmp/_working_dir && \ - chmod og+w /tmp/_working_dir - +FROM gcr.io/distroless/static:nonroot WORKDIR / COPY --from=builder /workspace/jaeger-operator . -COPY scripts/ scripts/ - -USER ${USER_UID}:${USER_UID} +USER 65532:65532 ENTRYPOINT ["/jaeger-operator"] diff --git a/Makefile b/Makefile index 1f775f7552..c0538d2fcf 100644 --- a/Makefile +++ b/Makefile @@ -30,9 +30,6 @@ STORAGE_NAMESPACE ?= "${shell kubectl get sa default -o jsonpath='{.metadata.nam KAFKA_NAMESPACE ?= "kafka" KAFKA_EXAMPLE ?= "https://raw.githubusercontent.com/strimzi/strimzi-kafka-operator/0.23.0/examples/kafka/kafka-persistent-single.yaml" KAFKA_YAML ?= "https://github.com/strimzi/strimzi-kafka-operator/releases/download/0.23.0/strimzi-cluster-operator-0.23.0.yaml" -ES_OPERATOR_NAMESPACE ?= openshift-logging -ES_OPERATOR_BRANCH ?= release-4.4 -ES_OPERATOR_IMAGE ?= quay.io/openshift/origin-elasticsearch-operator:4.4 # Istio binary path and version ISTIO_VERSION ?= 1.11.2 ISTIO_PATH = ./tests/_build/ @@ -41,10 +38,17 @@ GOPATH ?= "$(HOME)/go" GOROOT ?= "$(shell go env GOROOT)" ECHO ?= @echo $(echo_prefix) SED ?= "sed" +CERTMANAGER_VERSION ?= 1.6.1 PROMETHEUS_OPERATOR_TAG ?= v0.39.0 PROMETHEUS_BUNDLE ?= https://raw.githubusercontent.com/prometheus-operator/prometheus-operator/${PROMETHEUS_OPERATOR_TAG}/bundle.yaml +# 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 LD_FLAGS ?= "-X $(VERSION_PKG).version=$(VERSION) -X $(VERSION_PKG).buildDate=$(VERSION_DATE) -X $(VERSION_PKG).defaultJaeger=$(JAEGER_VERSION)" @@ -140,15 +144,6 @@ unit-tests: envtest @echo Running unit tests... KUBEBUILDER_ASSETS="$(shell $(ENVTEST) use $(ENVTEST_K8S_VERSION) -p path)" go test ${GOTEST_OPTS} ./... -cover -coverprofile=cover.out -ldflags $(LD_FLAGS) -.PHONY: run -run: manifests generate format vet - $(VECHO)rm -rf /tmp/_cert* - $(VECHO)go run -ldflags ${LD_FLAGS} ./main.go start ${CLI_FLAGS} - -.PHONY: run-debug -run-debug: run -run-debug: CLI_FLAGS = --log-level=debug --tracing-enabled=true - .PHONY: set-max-map-count set-max-map-count: # This is not required in OCP 4.1. The node tuning operator configures the property automatically @@ -161,31 +156,22 @@ set-node-os-linux: # Elasticsearch requires labeled nodes. These labels are by default present in OCP 4.2 $(VECHO)kubectl label nodes --all kubernetes.io/os=linux --overwrite -.PHONY: deploy-es-operator -deploy-es-operator: set-node-os-linux set-max-map-count deploy-prometheus-operator -ifeq ($(OLM),true) - $(ECHO) Skipping es-operator deployment, assuming it has been installed via OperatorHub -else - $(VECHO)kubectl create namespace ${ES_OPERATOR_NAMESPACE} 2>&1 | grep -v "already exists" || true - $(VECHO)kubectl apply -f https://raw.githubusercontent.com/openshift/elasticsearch-operator/${ES_OPERATOR_BRANCH}/manifests/01-service-account.yaml -n ${ES_OPERATOR_NAMESPACE} - $(VECHO)kubectl apply -f https://raw.githubusercontent.com/openshift/elasticsearch-operator/${ES_OPERATOR_BRANCH}/manifests/02-role.yaml - $(VECHO)kubectl apply -f https://raw.githubusercontent.com/openshift/elasticsearch-operator/${ES_OPERATOR_BRANCH}/manifests/03-role-bindings.yaml - $(VECHO)kubectl apply -f https://raw.githubusercontent.com/openshift/elasticsearch-operator/${ES_OPERATOR_BRANCH}/manifests/04-crd.yaml -n ${ES_OPERATOR_NAMESPACE} - $(VECHO)kubectl apply -f https://raw.githubusercontent.com/openshift/elasticsearch-operator/${ES_OPERATOR_BRANCH}/manifests/05-deployment.yaml -n ${ES_OPERATOR_NAMESPACE} - $(VECHO)kubectl set image deployment/elasticsearch-operator elasticsearch-operator=${ES_OPERATOR_IMAGE} -n ${ES_OPERATOR_NAMESPACE} -endif +cert-manager: cmctl + # Consider using cmctl to install the cert-manager once install command is not experimental + kubectl apply --validate=false -f https://github.com/jetstack/cert-manager/releases/download/v${CERTMANAGER_VERSION}/cert-manager.yaml + cmctl check api --wait=5m -.PHONY: undeploy-es-operator -undeploy-es-operator: -ifeq ($(OLM),true) - $(ECHO) Skipping es-operator undeployment, as it should have been installed via OperatorHub +cmctl: +ifeq (, $(shell which cmctl)) + @{ \ + curl -L -o /tmp/cmctl.tar.gz https://github.com/jetstack/cert-manager/releases/download/v$(CERTMANAGER_VERSION)/cmctl-`go env GOOS`-`go env GOARCH`.tar.gz ;\ + cd /tmp ;\ + tar xzf cmctl.tar.gz ;\ + mv cmctl $(GOBIN) ;\ + } +CTL=$(GOBIN)/cmctl else - $(VECHO)kubectl delete -f https://raw.githubusercontent.com/openshift/elasticsearch-operator/${ES_OPERATOR_BRANCH}/manifests/05-deployment.yaml -n ${ES_OPERATOR_NAMESPACE} --ignore-not-found=true || true - $(VECHO)kubectl delete -f https://raw.githubusercontent.com/openshift/elasticsearch-operator/${ES_OPERATOR_BRANCH}/manifests/04-crd.yaml -n ${ES_OPERATOR_NAMESPACE} --ignore-not-found=true || true - $(VECHO)kubectl delete -f https://raw.githubusercontent.com/openshift/elasticsearch-operator/${ES_OPERATOR_BRANCH}/manifests/03-role-bindings.yaml --ignore-not-found=true || true - $(VECHO)kubectl delete -f https://raw.githubusercontent.com/openshift/elasticsearch-operator/${ES_OPERATOR_BRANCH}/manifests/02-role.yaml --ignore-not-found=true || true - $(VECHO)kubectl delete -f https://raw.githubusercontent.com/openshift/elasticsearch-operator/${ES_OPERATOR_BRANCH}/manifests/01-service-account.yaml -n ${ES_OPERATOR_NAMESPACE} --ignore-not-found=true || true - $(VECHO)kubectl delete namespace ${ES_OPERATOR_NAMESPACE} --ignore-not-found=true 2>&1 || true +CTL=$(shell which cmctl) endif .PHONY: es diff --git a/PROJECT b/PROJECT index 22ca08cfcf..c281ad8c59 100644 --- a/PROJECT +++ b/PROJECT @@ -16,4 +16,8 @@ resources: kind: Jaeger path: github.com/jaegertracing/jaeger-operator/apis/v1 version: v1 + webhooks: + defaulting: true + validation: true + webhookVersion: v1 version: "3" diff --git a/apis/v1/jaeger_types.go b/apis/v1/jaeger_types.go index 25dd937516..b6dd5d71f2 100644 --- a/apis/v1/jaeger_types.go +++ b/apis/v1/jaeger_types.go @@ -510,8 +510,16 @@ type JaegerStorageSpec struct { GRPCPlugin GRPCPluginSpec `json:"grpcPlugin,omitempty"` } -// ElasticsearchSpec represents the ES configuration options that we pass down to the Elasticsearch operator +// ElasticsearchSpec represents the ES configuration options that we pass down to the OpenShift Elasticsearch operator. type ElasticsearchSpec struct { + // Name of the OpenShift Elasticsearch instance. Defaults to elasticsearch. + // +optional + Name string `json:"name,omitempty"` + + // Whether Elasticsearch should be provisioned or not. + // +optional + DoNotProvision bool `json:"doNotProvision,omitempty"` + // +optional Image string `json:"image,omitempty"` diff --git a/apis/v1/jaeger_webhook.go b/apis/v1/jaeger_webhook.go new file mode 100644 index 0000000000..6f254d4d9c --- /dev/null +++ b/apis/v1/jaeger_webhook.go @@ -0,0 +1,58 @@ +package v1 + +import ( + "k8s.io/apimachinery/pkg/runtime" + ctrl "sigs.k8s.io/controller-runtime" + logf "sigs.k8s.io/controller-runtime/pkg/log" + "sigs.k8s.io/controller-runtime/pkg/webhook" +) + +const ( + defaultElasticsearchName = "elasticsearch" +) + +// log is for logging in this package. +var jaegerlog = logf.Log.WithName("jaeger-resource") + +// SetupWebhookWithManager adds Jaeger webook to the manager. +func (j *Jaeger) SetupWebhookWithManager(mgr ctrl.Manager) error { + return ctrl.NewWebhookManagedBy(mgr). + For(j). + Complete() +} + +//+kubebuilder:webhook:path=/mutate-jaegertracing-io-v1-jaeger,mutating=true,failurePolicy=fail,sideEffects=None,groups=jaegertracing.io,resources=jaegers,verbs=create;update,versions=v1,name=mjaeger.kb.io,admissionReviewVersions={v1,v1beta1} + +var _ webhook.Defaulter = &Jaeger{} + +// Default implements webhook.Defaulter so a webhook will be registered for the type +func (j *Jaeger) Default() { + jaegerlog.Info("default", "name", j.Name) + + if j.Spec.Storage.Elasticsearch.Name == "" { + j.Spec.Storage.Elasticsearch.Name = defaultElasticsearchName + } +} + +// TODO(user): change verbs to "verbs=create;update;delete" if you want to enable deletion validation. +//+kubebuilder:webhook:path=/validate-jaegertracing-io-v1-jaeger,mutating=false,failurePolicy=fail,sideEffects=None,groups=jaegertracing.io,resources=jaegers,verbs=create;update,versions=v1,name=vjaeger.kb.io,admissionReviewVersions={v1,v1beta1} + +var _ webhook.Validator = &Jaeger{} + +// ValidateCreate implements webhook.Validator so a webhook will be registered for the type +func (j *Jaeger) ValidateCreate() error { + jaegerlog.Info("validate create", "name", j.Name) + return j.ValidateUpdate(nil) +} + +// ValidateUpdate implements webhook.Validator so a webhook will be registered for the type +func (j *Jaeger) ValidateUpdate(_ runtime.Object) error { + jaegerlog.Info("validate update", "name", j.Name) + return nil +} + +// ValidateDelete implements webhook.Validator so a webhook will be registered for the type +func (j *Jaeger) ValidateDelete() error { + jaegerlog.Info("validate delete", "name", j.Name) + return nil +} diff --git a/apis/v1/options.go b/apis/v1/options.go index 1631741ea1..a84b6de5ca 100644 --- a/apis/v1/options.go +++ b/apis/v1/options.go @@ -63,7 +63,9 @@ func (o *Options) UnmarshalJSON(b []byte) error { if err := d.Decode(&entries); err != nil { return err } - o.parse(entries) + if err := o.parse(entries); err != nil { + return err + } o.json = &b return nil } @@ -85,30 +87,43 @@ func (o Options) MarshalJSON() ([]byte, error) { return *o.json, nil } -func (o *Options) parse(entries map[string]interface{}) { +func (o *Options) parse(entries map[string]interface{}) error { o.opts = make(map[string]interface{}) + var err error for k, v := range entries { - o.opts = entry(o.opts, k, v) + o.opts, err = entry(o.opts, k, v) + if err != nil { + return err + } } + return nil } -func entry(entries map[string]interface{}, key string, value interface{}) map[string]interface{} { - switch value.(type) { +func entry(entries map[string]interface{}, key string, value interface{}) (map[string]interface{}, error) { + switch val := value.(type) { case map[string]interface{}: - for k, v := range value.(map[string]interface{}) { - entries = entry(entries, fmt.Sprintf("%s.%v", key, k), v) + var err error + for k, v := range val { + entries, err = entry(entries, fmt.Sprintf("%s.%v", key, k), v) + if err != nil { + return nil, err + } } - case []interface{}: - values := make([]string, 0, len(value.([]interface{}))) - for _, v := range value.([]interface{}) { - values = append(values, v.(string)) + case []interface{}: // NOTE: content of the argument list is not returned as []string when decoding json. + values := make([]string, 0, len(val)) + for _, v := range val { + str, ok := v.(string) + if !ok { + return nil, fmt.Errorf("invalid option type, expect: string, got: %T", v) + } + values = append(values, str) } entries[key] = values case interface{}: entries[key] = fmt.Sprintf("%v", value) } - return entries + return entries, nil } // ToArgs converts the options to a value suitable for the Container.Args field diff --git a/apis/v1/options_test.go b/apis/v1/options_test.go index bf3f008f59..0fc260795c 100644 --- a/apis/v1/options_test.go +++ b/apis/v1/options_test.go @@ -76,6 +76,10 @@ func TestUnmarshalToArgs(t *testing.T) { in: `{"a": 5000000000, "b": 15.222, "c":true, "d": "foo"}`, args: []string{"--a=5000000000", "--b=15.222", "--c=true", "--d=foo"}, }, + { + in: `{"a": {"b": {"c": [{"d": "e", "f": {"g": {"h": "i"}}}]}}}`, + err: "invalid option type, expect: string, got: map[string]interface {}", + }, } for _, test := range tests { opts := Options{} diff --git a/apis/v1/zz_generated.deepcopy.go b/apis/v1/zz_generated.deepcopy.go index 675325f604..1f09341bc5 100644 --- a/apis/v1/zz_generated.deepcopy.go +++ b/apis/v1/zz_generated.deepcopy.go @@ -8,7 +8,7 @@ package v1 import ( appsv1 "k8s.io/api/apps/v1" corev1 "k8s.io/api/core/v1" - runtime "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/runtime" ) // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. diff --git a/bundle/manifests/jaeger-operator-metrics-monitor_monitoring.coreos.com_v1_servicemonitor.yaml b/bundle/manifests/jaeger-operator-metrics-monitor_monitoring.coreos.com_v1_servicemonitor.yaml deleted file mode 100644 index fd0f689ada..0000000000 --- a/bundle/manifests/jaeger-operator-metrics-monitor_monitoring.coreos.com_v1_servicemonitor.yaml +++ /dev/null @@ -1,20 +0,0 @@ -apiVersion: monitoring.coreos.com/v1 -kind: ServiceMonitor -metadata: - labels: - control-plane: controller-manager - name: jaeger-operator - name: jaeger-operator-metrics-monitor -spec: - endpoints: - - bearerTokenFile: /var/run/secrets/kubernetes.io/serviceaccount/token - interval: 30s - path: /metrics - scheme: https - scrapeTimeout: 10s - targetPort: 8443 - tlsConfig: - insecureSkipVerify: true - selector: - matchLabels: - name: jaeger-operator diff --git a/bundle/manifests/jaeger-operator-webhook-service_v1_service.yaml b/bundle/manifests/jaeger-operator-webhook-service_v1_service.yaml new file mode 100644 index 0000000000..cdd8108f36 --- /dev/null +++ b/bundle/manifests/jaeger-operator-webhook-service_v1_service.yaml @@ -0,0 +1,16 @@ +apiVersion: v1 +kind: Service +metadata: + creationTimestamp: null + labels: + name: jaeger-operator + name: jaeger-operator-webhook-service +spec: + ports: + - port: 443 + protocol: TCP + targetPort: 9443 + selector: + name: jaeger-operator +status: + loadBalancer: {} diff --git a/bundle/manifests/jaeger-operator.clusterserviceversion.yaml b/bundle/manifests/jaeger-operator.clusterserviceversion.yaml index 0e1bd44296..56e2a57725 100644 --- a/bundle/manifests/jaeger-operator.clusterserviceversion.yaml +++ b/bundle/manifests/jaeger-operator.clusterserviceversion.yaml @@ -409,6 +409,10 @@ spec: initialDelaySeconds: 15 periodSeconds: 20 name: jaeger-operator + ports: + - containerPort: 9443 + name: webhook-server + protocol: TCP readinessProbe: httpGet: path: /readyz @@ -424,10 +428,19 @@ spec: memory: 128Mi securityContext: allowPrivilegeEscalation: false + volumeMounts: + - mountPath: /tmp/k8s-webhook-server/serving-certs + name: cert + readOnly: true securityContext: runAsNonRoot: true serviceAccountName: jaeger-operator terminationGracePeriodSeconds: 10 + volumes: + - name: cert + secret: + defaultMode: 420 + secretName: jaeger-operator-webhook-server-cert permissions: - rules: - apiGroups: @@ -490,3 +503,46 @@ spec: matchLabels: name: jaeger-operator version: 1.30.0 + webhookdefinitions: + - admissionReviewVersions: + - v1 + - v1beta1 + containerPort: 443 + deploymentName: jaeger-operator + failurePolicy: Fail + generateName: mjaeger.kb.io + rules: + - apiGroups: + - jaegertracing.io + apiVersions: + - v1 + operations: + - CREATE + - UPDATE + resources: + - jaegers + sideEffects: None + targetPort: 9443 + type: MutatingAdmissionWebhook + webhookPath: /mutate-jaegertracing-io-v1-jaeger + - admissionReviewVersions: + - v1 + - v1beta1 + containerPort: 443 + deploymentName: jaeger-operator + failurePolicy: Fail + generateName: vjaeger.kb.io + rules: + - apiGroups: + - jaegertracing.io + apiVersions: + - v1 + operations: + - CREATE + - UPDATE + resources: + - jaegers + sideEffects: None + targetPort: 9443 + type: ValidatingAdmissionWebhook + webhookPath: /validate-jaegertracing-io-v1-jaeger diff --git a/bundle/manifests/jaegertracing.io_jaegers.yaml b/bundle/manifests/jaegertracing.io_jaegers.yaml index 805eb91736..6291643f3f 100644 --- a/bundle/manifests/jaegertracing.io_jaegers.yaml +++ b/bundle/manifests/jaegertracing.io_jaegers.yaml @@ -2,6 +2,7 @@ apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: annotations: + cert-manager.io/inject-ca-from: observability/jaeger-operator-serving-cert controller-gen.kubebuilder.io/version: v0.6.1 creationTimestamp: null labels: @@ -9390,8 +9391,12 @@ spec: type: object elasticsearch: properties: + doNotProvision: + type: boolean image: type: string + name: + type: string nodeCount: format: int32 type: integer diff --git a/config/certmanager/certificate.yaml b/config/certmanager/certificate.yaml new file mode 100644 index 0000000000..bd1474e5bd --- /dev/null +++ b/config/certmanager/certificate.yaml @@ -0,0 +1,28 @@ +# The following manifests contain a self-signed issuer CR and a certificate CR. +# More document can be found at https://docs.cert-manager.io +# WARNING: Targets CertManager v1.0. Check https://cert-manager.io/docs/installation/upgrading/ for breaking changes. +apiVersion: cert-manager.io/v1 +kind: Issuer +metadata: + name: selfsigned-issuer + namespace: system +spec: + selfSigned: {} +--- +apiVersion: cert-manager.io/v1 +kind: Certificate +metadata: + name: serving-cert # this name should match the one appeared in kustomizeconfig.yaml + namespace: system +spec: + # $(SERVICE_NAME) and $(SERVICE_NAMESPACE) will be substituted by kustomize + dnsNames: + - $(SERVICE_NAME).$(SERVICE_NAMESPACE).svc + - $(SERVICE_NAME).$(SERVICE_NAMESPACE).svc.cluster.local + issuerRef: + kind: Issuer + name: selfsigned-issuer + secretName: jaeger-operator-webhook-server-cert # this secret will not be prefixed, since it's not managed by kustomize + subject: + organizationalUnits: + - "jaeger-operator" diff --git a/config/certmanager/kustomization.yaml b/config/certmanager/kustomization.yaml new file mode 100644 index 0000000000..bf360aa21f --- /dev/null +++ b/config/certmanager/kustomization.yaml @@ -0,0 +1,7 @@ +resources: +- certificate.yaml + +namePrefix: jaeger-operator- + +configurations: +- kustomizeconfig.yaml diff --git a/config/certmanager/kustomizeconfig.yaml b/config/certmanager/kustomizeconfig.yaml new file mode 100644 index 0000000000..90d7c313ca --- /dev/null +++ b/config/certmanager/kustomizeconfig.yaml @@ -0,0 +1,16 @@ +# This configuration is for teaching kustomize how to update name ref and var substitution +nameReference: +- kind: Issuer + group: cert-manager.io + fieldSpecs: + - kind: Certificate + group: cert-manager.io + path: spec/issuerRef/name + +varReference: +- kind: Certificate + group: cert-manager.io + path: spec/commonName +- kind: Certificate + group: cert-manager.io + path: spec/dnsNames diff --git a/config/crd/bases/jaegertracing.io_jaegers.yaml b/config/crd/bases/jaegertracing.io_jaegers.yaml index 82fe16ce87..efcd02cc29 100644 --- a/config/crd/bases/jaegertracing.io_jaegers.yaml +++ b/config/crd/bases/jaegertracing.io_jaegers.yaml @@ -9390,8 +9390,12 @@ spec: type: object elasticsearch: properties: + doNotProvision: + type: boolean image: type: string + name: + type: string nodeCount: format: int32 type: integer diff --git a/config/crd/kustomization.yaml b/config/crd/kustomization.yaml index dd7ba71ae5..6bb4561216 100644 --- a/config/crd/kustomization.yaml +++ b/config/crd/kustomization.yaml @@ -14,7 +14,7 @@ patchesStrategicMerge: # [CERTMANAGER] To enable cert-manager, uncomment all the sections with [CERTMANAGER] prefix. # patches here are for enabling the CA injection for each CRD -#- patches/cainjection_in_jaegers.yaml +- patches/cainjection_in_jaegers.yaml #- patches/cainjection_in_kafkas.yaml #+kubebuilder:scaffold:crdkustomizecainjectionpatch diff --git a/config/crd/patches/webhook_in_jaegers.yaml b/config/crd/patches/webhook_in_jaegers.yaml index 393ce019e1..4d346fda62 100644 --- a/config/crd/patches/webhook_in_jaegers.yaml +++ b/config/crd/patches/webhook_in_jaegers.yaml @@ -10,7 +10,7 @@ spec: clientConfig: service: namespace: system - name: webhook-service + name: jaeger-operator-webhook-service path: /convert conversionReviewVersions: - v1 diff --git a/config/default/kustomization.yaml b/config/default/kustomization.yaml index 3dfc7a2e6c..6f9870c108 100644 --- a/config/default/kustomization.yaml +++ b/config/default/kustomization.yaml @@ -7,6 +7,10 @@ namespace: observability # Note that it should also match with the prefix (text before '-') of the namespace # field above. +# The prefix is not used here because the manager's deployment name is jaeger-operator +# which means that the manifest would have to contain an empty name which is not allowed. +#namePrefix: jaeger-operator- + # Labels to add to all resources and selectors. commonLabels: name: jaeger-operator @@ -15,11 +19,8 @@ bases: - ../crd - ../rbac - ../manager -# [WEBHOOK] To enable webhook, uncomment all the sections with [WEBHOOK] prefix including the one in -# crd/kustomization.yaml -#- ../webhook -# [CERTMANAGER] To enable cert-manager, uncomment all sections with 'CERTMANAGER'. 'WEBHOOK' components are required. -#- ../certmanager +- ../webhook +- ../certmanager # [PROMETHEUS] To enable prometheus monitor, uncomment all sections with 'PROMETHEUS'. #- ../prometheus @@ -29,45 +30,39 @@ patchesStrategicMerge: # endpoint w/o any authn/z, please comment the following line. - manager_auth_proxy_patch.yaml +- manager_webhook_patch.yaml +- webhookcainjection_patch.yaml + # Mount the controller config file for loading manager configurations # through a ComponentConfig type #- manager_config_patch.yaml -# [WEBHOOK] To enable webhook, uncomment all the sections with [WEBHOOK] prefix including the one in -# crd/kustomization.yaml -#- manager_webhook_patch.yaml - -# [CERTMANAGER] To enable cert-manager, uncomment all sections with 'CERTMANAGER'. -# Uncomment 'CERTMANAGER' sections in crd/kustomization.yaml to enable the CA injection in the admission webhooks. -# 'CERTMANAGER' needs to be enabled to use ca injection -#- webhookcainjection_patch.yaml - # the following config is for teaching kustomize how to do var substitution vars: # [CERTMANAGER] To enable cert-manager, uncomment all sections with 'CERTMANAGER' prefix. -#- name: CERTIFICATE_NAMESPACE # namespace of the certificate CR -# objref: -# kind: Certificate -# group: cert-manager.io -# version: v1 -# name: serving-cert # this name should match the one in certificate.yaml -# fieldref: -# fieldpath: metadata.namespace -#- name: CERTIFICATE_NAME -# objref: -# kind: Certificate -# group: cert-manager.io -# version: v1 -# name: serving-cert # this name should match the one in certificate.yaml -#- name: SERVICE_NAMESPACE # namespace of the service -# objref: -# kind: Service -# version: v1 -# name: webhook-service -# fieldref: -# fieldpath: metadata.namespace -#- name: SERVICE_NAME -# objref: -# kind: Service -# version: v1 -# name: webhook-service +- name: CERTIFICATE_NAMESPACE # namespace of the certificate CR + objref: + kind: Certificate + group: cert-manager.io + version: v1 + name: serving-cert # this name should match the one in certificate.yaml + fieldref: + fieldpath: metadata.namespace +- name: CERTIFICATE_NAME + objref: + kind: Certificate + group: cert-manager.io + version: v1 + name: serving-cert # this name should match the one in certificate.yaml +- name: SERVICE_NAMESPACE # namespace of the service + objref: + kind: Service + version: v1 + name: webhook-service + fieldref: + fieldpath: metadata.namespace +- name: SERVICE_NAME + objref: + kind: Service + version: v1 + name: webhook-service diff --git a/config/default/manager_webhook_patch.yaml b/config/default/manager_webhook_patch.yaml new file mode 100644 index 0000000000..5f6a6e2fee --- /dev/null +++ b/config/default/manager_webhook_patch.yaml @@ -0,0 +1,22 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: jaeger-operator +spec: + template: + spec: + containers: + - name: jaeger-operator + ports: + - containerPort: 9443 + name: webhook-server + protocol: TCP + volumeMounts: + - mountPath: /tmp/k8s-webhook-server/serving-certs + name: cert + readOnly: true + volumes: + - name: cert + secret: + defaultMode: 420 + secretName: jaeger-operator-webhook-server-cert diff --git a/config/default/webhookcainjection_patch.yaml b/config/default/webhookcainjection_patch.yaml new file mode 100644 index 0000000000..02ab515d42 --- /dev/null +++ b/config/default/webhookcainjection_patch.yaml @@ -0,0 +1,15 @@ +# This patch add annotation to admission webhook config and +# the variables $(CERTIFICATE_NAMESPACE) and $(CERTIFICATE_NAME) will be substituted by kustomize. +apiVersion: admissionregistration.k8s.io/v1 +kind: MutatingWebhookConfiguration +metadata: + name: mutating-webhook-configuration + annotations: + cert-manager.io/inject-ca-from: $(CERTIFICATE_NAMESPACE)/$(CERTIFICATE_NAME) +--- +apiVersion: admissionregistration.k8s.io/v1 +kind: ValidatingWebhookConfiguration +metadata: + name: validating-webhook-configuration + annotations: + cert-manager.io/inject-ca-from: $(CERTIFICATE_NAMESPACE)/$(CERTIFICATE_NAME) diff --git a/config/webhook/kustomization.yaml b/config/webhook/kustomization.yaml new file mode 100644 index 0000000000..18b8caa66b --- /dev/null +++ b/config/webhook/kustomization.yaml @@ -0,0 +1,8 @@ +resources: +- manifests.yaml +- service.yaml + +namePrefix: jaeger-operator- + +configurations: +- kustomizeconfig.yaml diff --git a/config/webhook/kustomizeconfig.yaml b/config/webhook/kustomizeconfig.yaml new file mode 100644 index 0000000000..d6796eaea0 --- /dev/null +++ b/config/webhook/kustomizeconfig.yaml @@ -0,0 +1,26 @@ +# the following config is for teaching kustomize where to look at when substituting vars. +# It requires kustomize v2.1.0 or newer to work properly. + +nameReference: +- kind: Service + version: v1 + fieldSpecs: + - kind: MutatingWebhookConfiguration + group: admissionregistration.k8s.io + path: webhooks/clientConfig/service/name + - kind: ValidatingWebhookConfiguration + group: admissionregistration.k8s.io + path: webhooks/clientConfig/service/name + +namespace: +- kind: MutatingWebhookConfiguration + group: admissionregistration.k8s.io + path: webhooks/clientConfig/service/namespace + create: true +- kind: ValidatingWebhookConfiguration + group: admissionregistration.k8s.io + path: webhooks/clientConfig/service/namespace + create: true + +varReference: +- path: metadata/annotations diff --git a/config/webhook/manifests.yaml b/config/webhook/manifests.yaml new file mode 100644 index 0000000000..953eb1b85a --- /dev/null +++ b/config/webhook/manifests.yaml @@ -0,0 +1,58 @@ + +--- +apiVersion: admissionregistration.k8s.io/v1 +kind: MutatingWebhookConfiguration +metadata: + creationTimestamp: null + name: mutating-webhook-configuration +webhooks: +- admissionReviewVersions: + - v1 + - v1beta1 + clientConfig: + service: + name: webhook-service + namespace: system + path: /mutate-jaegertracing-io-v1-jaeger + failurePolicy: Fail + name: mjaeger.kb.io + rules: + - apiGroups: + - jaegertracing.io + apiVersions: + - v1 + operations: + - CREATE + - UPDATE + resources: + - jaegers + sideEffects: None + +--- +apiVersion: admissionregistration.k8s.io/v1 +kind: ValidatingWebhookConfiguration +metadata: + creationTimestamp: null + name: validating-webhook-configuration +webhooks: +- admissionReviewVersions: + - v1 + - v1beta1 + clientConfig: + service: + name: webhook-service + namespace: system + path: /validate-jaegertracing-io-v1-jaeger + failurePolicy: Fail + name: vjaeger.kb.io + rules: + - apiGroups: + - jaegertracing.io + apiVersions: + - v1 + operations: + - CREATE + - UPDATE + resources: + - jaegers + sideEffects: None diff --git a/config/webhook/service.yaml b/config/webhook/service.yaml new file mode 100644 index 0000000000..acd6493f95 --- /dev/null +++ b/config/webhook/service.yaml @@ -0,0 +1,10 @@ +apiVersion: v1 +kind: Service +metadata: + name: webhook-service + namespace: system +spec: + ports: + - port: 443 + protocol: TCP + targetPort: 9443 diff --git a/hack/actions/e2e/action.yaml b/hack/actions/e2e/action.yaml new file mode 100644 index 0000000000..e6bfd0e853 --- /dev/null +++ b/hack/actions/e2e/action.yaml @@ -0,0 +1,44 @@ +# GitHub action to run the E2E tests. +# For this purpose, it would be a better idea to use a reusable workflow. There +# is some documentation about how to use a local reusable workflow: +# https://github.blog/changelog/2022-01-25-github-actions-reusable-workflows-can-be-referenced-locally/ +# But it seems it doesn't work properly: +# https://github.community/t/allow-reusable-workflows-to-be-located-at-arbitrary-locations-and-be-local/212745/7 +# So, the CI uses a local GitHub action as a template to run all the tests. +name: Run E2E tests +description: "Run an E2E test suite" + +inputs: + testsuite_name: + description: "Name of the test suite to run" + required: true + kube_version: + description: "Kubernetes version to use" + required: true + + +runs: + using: "composite" + steps: + - name: "Set up Go" + uses: actions/setup-go@v2.1.4 + with: + go-version: 1.16 + - name: "Install KIND" + run: ./.ci/install-kind.sh + shell: bash + - name: "Install KUTTL" + run: ./.ci/install-kuttl.sh + shell: bash + - name: "Install gomplate" + run: ./.ci/install-gomplate.sh + shell: bash + - name: "Install dependencies" + run: make install-tools + shell: bash + - name: "Run E2E ${{ inputs.testsuite_name }} test suite on ${{ inputs.kube_version }}" + env: + VERBOSE: "true" + KUBE_VERSION: "${{ inputs.kube_version }}" + run: make run-e2e-tests-${{ inputs.testsuite_name }} + shell: bash diff --git a/pkg/cmd/start/bootstrap.go b/pkg/cmd/start/bootstrap.go index 8f7b7bce6b..dc1d704d1e 100644 --- a/pkg/cmd/start/bootstrap.go +++ b/pkg/cmd/start/bootstrap.go @@ -114,6 +114,7 @@ func bootstrap(ctx context.Context) manager.Manager { detectNamespacePermissions(ctx, mgr) performUpgrades(ctx, mgr) setupControllers(ctx, mgr) + setupWebhooks(ctx, mgr) detectOAuthProxyImageStream(ctx, mgr) err = opmetrics.Bootstrap(ctx, namespace, mgr.GetClient()) if err != nil { @@ -356,6 +357,13 @@ func setupControllers(ctx context.Context, mgr manager.Manager) { } } +func setupWebhooks(_ context.Context, mgr manager.Manager) { + if err := (&v1.Jaeger{}).SetupWebhookWithManager(mgr); err != nil { + setupLog.Error(err, "unable to create webhook", "webhook", "Jaeger") + os.Exit(1) + } +} + func getNamespace(ctx context.Context) string { tracer := otel.GetTracerProvider().Tracer(v1.BootstrapTracer) ctx, span := tracer.Start(ctx, "getNamespace") diff --git a/pkg/controller/jaeger/elasticsearch.go b/pkg/controller/jaeger/elasticsearch.go index deddadd97c..4fbb81e090 100644 --- a/pkg/controller/jaeger/elasticsearch.go +++ b/pkg/controller/jaeger/elasticsearch.go @@ -5,16 +5,15 @@ import ( "sync" "time" + esv1 "github.com/openshift/elasticsearch-operator/apis/logging/v1" "github.com/pkg/errors" log "github.com/sirupsen/logrus" "go.opentelemetry.io/otel" - corev1 "k8s.io/api/apps/v1" + appsv1 "k8s.io/api/apps/v1" k8serrors "k8s.io/apimachinery/pkg/api/errors" "k8s.io/apimachinery/pkg/util/wait" "sigs.k8s.io/controller-runtime/pkg/client" - esv1 "github.com/openshift/elasticsearch-operator/apis/logging/v1" - v1 "github.com/jaegertracing/jaeger-operator/apis/v1" "github.com/jaegertracing/jaeger-operator/pkg/inventory" "github.com/jaegertracing/jaeger-operator/pkg/tracing" @@ -51,6 +50,7 @@ func (r *ReconcileJaeger) applyElasticsearches(ctx context.Context, jaeger v1.Ja if err := r.client.Create(ctx, &d); err != nil { return tracing.HandleError(err, span) } + if err := waitForAvailableElastic(ctx, r.client, d); err != nil { return tracing.HandleError(errors.Wrap(err, "elasticsearch cluster didn't get to ready state"), span) } @@ -92,7 +92,7 @@ func waitForAvailableElastic(ctx context.Context, c client.Client, es esv1.Elast seen := false once := &sync.Once{} return wait.PollImmediate(time.Second, 2*time.Minute, func() (done bool, err error) { - depList := corev1.DeploymentList{} + depList := appsv1.DeploymentList{} labels := map[string]string{ "cluster-name": es.Name, "component": "elasticsearch", @@ -131,7 +131,7 @@ func waitForAvailableElastic(ctx context.Context, c client.Client, es esv1.Elast availableDep++ } } - ssList := corev1.StatefulSetList{} + ssList := appsv1.StatefulSetList{} if err = c.List(ctx, &ssList, opts...); err != nil { if k8serrors.IsNotFound(err) { // the object might have not been created yet diff --git a/pkg/controller/jaeger/jaeger_controller.go b/pkg/controller/jaeger/jaeger_controller.go index 8c6d14c7cf..55ac982dab 100644 --- a/pkg/controller/jaeger/jaeger_controller.go +++ b/pkg/controller/jaeger/jaeger_controller.go @@ -21,7 +21,6 @@ import ( "sigs.k8s.io/controller-runtime/pkg/reconcile" v1 "github.com/jaegertracing/jaeger-operator/apis/v1" - "github.com/jaegertracing/jaeger-operator/pkg/storage" "github.com/jaegertracing/jaeger-operator/pkg/strategy" "github.com/jaegertracing/jaeger-operator/pkg/tracing" ) @@ -211,34 +210,7 @@ func (r *ReconcileJaeger) apply(ctx context.Context, jaeger v1.Jaeger, str strat return jaeger, tracing.HandleError(err, span) } - // ES cert handling requires secrets from environment - // therefore running this here and not in the strategy - if storage.ShouldDeployElasticsearch(jaeger.Spec.Storage) { - opts := client.MatchingLabels(map[string]string{ - "app.kubernetes.io/instance": jaeger.Name, - "app.kubernetes.io/managed-by": "jaeger-operator", - }) - secrets := &corev1.SecretList{} - if err := r.rClient.List(ctx, secrets, opts); err != nil { - jaeger.Status.Phase = v1.JaegerPhaseFailed - if err := r.client.Status().Update(ctx, &jaeger); err != nil { - // we let it return the real error later - jaeger.Logger().WithError(err).Error("failed to store the failed status into the current CustomResource after preconditions") - } - return jaeger, tracing.HandleError(err, span) - } - secretsForNamespace := r.getSecretsForNamespace(secrets.Items, jaeger.Namespace) - - es := &storage.ElasticsearchDeployment{Jaeger: &jaeger, CertScript: "./scripts/cert_generation.sh", Secrets: secretsForNamespace} - err = es.CreateCerts() - if err != nil { - es.Jaeger.Logger().WithError(err).Error("failed to create Elasticsearch certificates, Elasticsearch won't be deployed") - return jaeger, err - } - str = str.WithSecrets(append(str.Secrets(), es.ExtractSecrets()...)) - } - - // secrets have to be created before ES - they are mounted to the ES pod + // TODO this can be removed after previously released version is using cert management from EO e.g. in 1.32.0 if err := r.applySecrets(ctx, jaeger, str.Secrets()); err != nil { return jaeger, tracing.HandleError(err, span) } diff --git a/pkg/metrics/instances.go b/pkg/metrics/instances.go index e9c4a223a7..133b02d58a 100644 --- a/pkg/metrics/instances.go +++ b/pkg/metrics/instances.go @@ -12,12 +12,14 @@ import ( "sigs.k8s.io/controller-runtime/pkg/client" v1 "github.com/jaegertracing/jaeger-operator/apis/v1" + "github.com/jaegertracing/jaeger-operator/pkg/storage" ) const metricPrefix = "jaeger_operator_instances" const agentStrategiesMetric = "agent_strategies" const storageMetric = "storage_types" const strategiesMetric = "strategies" +const autoprovisioningMetric = "autoprovisioning" // This structure contains the labels associated with the instances and a counter of the number of instances type instancesView struct { @@ -35,7 +37,10 @@ func (i *instancesView) reset() { } func (i *instancesView) Record(jaeger v1.Jaeger) { - i.Count[i.KeyFn(jaeger)]++ + label := i.KeyFn(jaeger) + if label != "" { + i.Count[label]++ + } } func (i *instancesView) Report(result metric.BatchObserverResult) { @@ -115,6 +120,21 @@ func (i *instancesMetric) Setup(ctx context.Context) error { return err } i.observations = append(i.observations, obs) + + obs, err = newObservation(batch, autoprovisioningMetric, + "Number of instances using autoprovisioning", + "type", + func(jaeger v1.Jaeger) string { + if storage.ShouldInjectElasticsearchConfiguration(jaeger.Spec.Storage) { + return "elasticsearch" + } + return "" + }) + if err != nil { + return err + } + i.observations = append(i.observations, obs) + return nil } diff --git a/pkg/metrics/instances_test.go b/pkg/metrics/instances_test.go index 3451b281b1..865fda59af 100644 --- a/pkg/metrics/instances_test.go +++ b/pkg/metrics/instances_test.go @@ -156,3 +156,79 @@ func TestValueObservedMetrics(t *testing.T) { assertLabelAndValues(t, e.name, meter.MeasurementBatches, e.labels, e.value) } } + +func TestAutoProvisioningESObservedMetric(t *testing.T) { + s := scheme.Scheme + s.AddKnownTypes(v1.GroupVersion, &v1.Jaeger{}, &v1.JaegerList{}) + + nsn := types.NamespacedName{ + Name: "my-jaeger-prod", + Namespace: "test", + } + + esOptionsMap := map[string]interface{}{ + "es.server-urls": "http://localhost:9200", + } + + noAutoProvisioningInstance := v1.Jaeger{ + ObjectMeta: metav1.ObjectMeta{ + Name: nsn.Name, + Namespace: nsn.Namespace, + }, + Spec: v1.JaegerSpec{ + Strategy: "production", + Storage: v1.JaegerStorageSpec{ + Type: v1.JaegerESStorage, + Options: v1.NewOptions(esOptionsMap), + }, + }, + } + + autoprovisioningInstance := v1.Jaeger{ + ObjectMeta: metav1.ObjectMeta{ + Name: nsn.Name, + Namespace: nsn.Namespace, + }, + Spec: v1.JaegerSpec{ + Strategy: "production", + Storage: v1.JaegerStorageSpec{ + Type: v1.JaegerESStorage, + }, + }, + } + + objs := []runtime.Object{ + &autoprovisioningInstance, + } + + cl := fake.NewFakeClientWithScheme(s, objs...) + meter, provider := oteltest.NewMeterProvider() + global.SetMeterProvider(provider) + + instancesObservedValue := newInstancesMetric(cl) + err := instancesObservedValue.Setup(context.Background()) + require.NoError(t, err) + meter.RunAsyncInstruments() + + expectedMetric := newExpectedMetric(autoprovisioningMetric, attribute.String("type", "elasticsearch"), 1) + assertLabelAndValues(t, expectedMetric.name, meter.MeasurementBatches, expectedMetric.labels, expectedMetric.value) + + // Test deleting autoprovisioning + err = cl.Delete(context.Background(), &autoprovisioningInstance) + require.NoError(t, err) + + // Reset measurement batches + meter.MeasurementBatches = []oteltest.Batch{} + meter.RunAsyncInstruments() + + expectedMetric = newExpectedMetric(autoprovisioningMetric, attribute.String("type", "elasticsearch"), 0) + assertLabelAndValues(t, expectedMetric.name, meter.MeasurementBatches, expectedMetric.labels, expectedMetric.value) + + // Create no autoprovisioned instance + err = cl.Delete(context.Background(), &noAutoProvisioningInstance) + meter.MeasurementBatches = []oteltest.Batch{} + meter.RunAsyncInstruments() + expectedMetric = newExpectedMetric(autoprovisioningMetric, attribute.String("type", "elasticsearch"), 0) + assertLabelAndValues(t, expectedMetric.name, meter.MeasurementBatches, expectedMetric.labels, expectedMetric.value) + +} diff --git a/pkg/storage/elasticsearch.go b/pkg/storage/elasticsearch.go index 66cf9af16b..7c35766dc8 100644 --- a/pkg/storage/elasticsearch.go +++ b/pkg/storage/elasticsearch.go @@ -15,16 +15,15 @@ import ( ) const ( - volumeName = "certs" - volumeMountPath = "/certs" - caPath = volumeMountPath + "/ca" - keyPath = volumeMountPath + "/key" - certPath = volumeMountPath + "/cert" - elasticsearchURL = "https://elasticsearch:9200" + volumeName = "certs" + volumeMountPath = "/certs" + caPath = volumeMountPath + "/ca-bundle.crt" + keyPath = volumeMountPath + "/tls.key" + certPath = volumeMountPath + "/tls.crt" ) -// ShouldDeployElasticsearch determines whether a new instance of Elasticsearch should be deployed -func ShouldDeployElasticsearch(s v1.JaegerStorageSpec) bool { +// ShouldInjectElasticsearchConfiguration determines whether a new instance of Elasticsearch should be deployed +func ShouldInjectElasticsearchConfiguration(s v1.JaegerStorageSpec) bool { if s.Type != v1.JaegerESStorage { return false } @@ -40,7 +39,7 @@ type ElasticsearchDeployment struct { } func (ed *ElasticsearchDeployment) injectArguments(container *corev1.Container) { - container.Args = append(container.Args, "--es.server-urls="+elasticsearchURL) + container.Args = append(container.Args, fmt.Sprintf("--es.server-urls=https://%s:9200", ed.Jaeger.Spec.Storage.Elasticsearch.Name)) if util.FindItem("--es.tls=", container.Args) == "" && util.FindItem("--es.tls.enabled=", container.Args) == "" { container.Args = append(container.Args, "--es.tls.enabled=true") } @@ -62,9 +61,7 @@ func (ed *ElasticsearchDeployment) injectArguments(container *corev1.Container) calculateReplicaShards(ed.Jaeger.Spec.Storage.Elasticsearch.RedundancyPolicy, int(ed.Jaeger.Spec.Storage.Elasticsearch.NodeCount)))) } if strings.EqualFold(util.FindItem("--es-archive.enabled", container.Args), "--es-archive.enabled=true") { - container.Args = append(container.Args, - "--es-archive.server-urls="+elasticsearchURL, - ) + container.Args = append(container.Args, fmt.Sprintf("--es-archive.server-urls=https://%s:9200", ed.Jaeger.Spec.Storage.Elasticsearch.Name)) if util.FindItem("--es-archive.tls=", container.Args) == "" && util.FindItem("--es-archive.tls.enabled=", container.Args) == "" { container.Args = append(container.Args, "--es-archive.tls.enabled=true") } @@ -94,7 +91,7 @@ func (ed *ElasticsearchDeployment) InjectStorageConfiguration(p *corev1.PodSpec) Name: volumeName, VolumeSource: corev1.VolumeSource{ Secret: &corev1.SecretVolumeSource{ - SecretName: jaegerSecret.instanceName(ed.Jaeger), + SecretName: jaegerESSecretName(ed.Jaeger.Spec.Storage.Elasticsearch.Name), }, }, }) @@ -115,14 +112,14 @@ func (ed *ElasticsearchDeployment) InjectSecretsConfiguration(p *corev1.PodSpec) Name: volumeName, VolumeSource: corev1.VolumeSource{ Secret: &corev1.SecretVolumeSource{ - SecretName: curatorSecret.instanceName(ed.Jaeger), + SecretName: jaegerESSecretName(ed.Jaeger.Spec.Storage.Elasticsearch.Name), }, }, }) // we assume jaeger containers are first if len(p.Containers) > 0 { // the size of arguments array should be always 2 - p.Containers[0].Args[1] = elasticsearchURL + p.Containers[0].Args[1] = fmt.Sprintf("https://%s:9200", ed.Jaeger.Spec.Storage.Elasticsearch.Name) p.Containers[0].Env = append(p.Containers[0].Env, corev1.EnvVar{Name: "ES_TLS", Value: "true"}, corev1.EnvVar{Name: "ES_TLS_CA", Value: caPath}, @@ -141,6 +138,12 @@ func (ed *ElasticsearchDeployment) InjectSecretsConfiguration(p *corev1.PodSpec) // Elasticsearch returns an ES CR for the deployment func (ed *ElasticsearchDeployment) Elasticsearch() *esv1.Elasticsearch { + if ed.Jaeger.Spec.Storage.Elasticsearch.DoNotProvision { + // Do not provision ES + // The ES instance will be reused from already provisioned one + return nil + } + // this might yield names like: // elasticsearch-cdm-osdke2ee7864afba6854e498f316bd37347f666simpleprod-1 // for the above value to contain at most 63 chars, our uuid has to have at most 42 chars @@ -152,10 +155,10 @@ func (ed *ElasticsearchDeployment) Elasticsearch() *esv1.Elasticsearch { return &esv1.Elasticsearch{ ObjectMeta: metav1.ObjectMeta{ Namespace: ed.Jaeger.Namespace, - Name: esSecret.name, + Name: ed.Jaeger.Spec.Storage.Elasticsearch.Name, Labels: map[string]string{ "app": "jaeger", - "app.kubernetes.io/name": util.Truncate(esSecret.name, 63), + "app.kubernetes.io/name": util.Truncate(ed.Jaeger.Spec.Storage.Elasticsearch.Name, 63), "app.kubernetes.io/instance": util.Truncate(ed.Jaeger.Name, 63), "app.kubernetes.io/component": "elasticsearch", "app.kubernetes.io/part-of": "jaeger", @@ -163,6 +166,12 @@ func (ed *ElasticsearchDeployment) Elasticsearch() *esv1.Elasticsearch { // to manipulate with objects created by ES operator. //"app.kubernetes.io/managed-by": "jaeger-operator", }, + Annotations: map[string]string{ + "logging.openshift.io/elasticsearch-cert-management": "true", + // The value has to match searchguard configuration + // https://github.com/openshift/origin-aggregated-logging/blob/50126fb8e0c602e9c623d6a8599857aaf98f80f8/elasticsearch/sgconfig/roles_mapping.yml#L34 + fmt.Sprintf("logging.openshift.io/elasticsearch-cert.%s", jaegerESSecretName(ed.Jaeger.Spec.Storage.Elasticsearch.Name)): "user.jaeger", + }, OwnerReferences: []metav1.OwnerReference{util.AsOwner(ed.Jaeger)}, }, Spec: esv1.ElasticsearchSpec{ @@ -224,3 +233,7 @@ func calculateReplicaShards(policyType esv1.RedundancyPolicyType, dataNodes int) return 1 } } + +func jaegerESSecretName(elasticsearch string) string { + return fmt.Sprintf("jaeger-%s", elasticsearch) +} diff --git a/pkg/storage/elasticsearch_secrets.go b/pkg/storage/elasticsearch_secrets.go deleted file mode 100644 index 69ca72648e..0000000000 --- a/pkg/storage/elasticsearch_secrets.go +++ /dev/null @@ -1,216 +0,0 @@ -package storage - -import ( - "fmt" - "io/ioutil" - "os" - "os/exec" - "path" - "path/filepath" - - "github.com/pkg/errors" - log "github.com/sirupsen/logrus" - corev1 "k8s.io/api/core/v1" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - - v1 "github.com/jaegertracing/jaeger-operator/apis/v1" - "github.com/jaegertracing/jaeger-operator/pkg/util" -) - -const ( - tmpWorkingDir = "/tmp/_certs" -) - -type secret struct { - name string - keyFileNameMap map[string]string -} - -func (s secret) instanceName(jaeger *v1.Jaeger) string { - // elasticsearch secret is hardcoded in es-operator https://jira.coreos.com/browse/LOG-326 - if s.name == esSecret.name { - return esSecret.name - } - return fmt.Sprintf("%s-%s", jaeger.Name, s.name) -} - -// master secret is used to generate other certs -var masterSecret = secret{ - name: "master-certs", - keyFileNameMap: map[string]string{ - "ca": "ca.crt", - "ca-key": "ca.key", - }, -} - -// es secret is used by Elasticsearch nodes -var esSecret = secret{ - name: string(v1.JaegerESStorage), - keyFileNameMap: map[string]string{ - "elasticsearch.key": "elasticsearch.key", - "elasticsearch.crt": "elasticsearch.crt", - "logging-es.key": "logging-es.key", - "logging-es.crt": "logging-es.crt", - "admin-key": "system.admin.key", - "admin-cert": "system.admin.crt", - "admin-ca": "ca.crt", - }, -} - -// jaeger secret is used by jaeger components to talk to Elasticsearch -var jaegerSecret = secret{ - name: "jaeger-elasticsearch", - keyFileNameMap: map[string]string{ - "ca": "ca.crt", - "key": "user.jaeger.key", - "cert": "user.jaeger.crt", - }, -} - -// curator secret is used for index cleaner and rollover -var curatorSecret = secret{ - name: "curator", - keyFileNameMap: map[string]string{ - "ca": "ca.crt", - "key": "system.logging.curator.key", - "cert": "system.logging.curator.crt", - }, -} - -// ExtractSecrets assembles a set of secrets related to Elasticsearch -func (ed *ElasticsearchDeployment) ExtractSecrets() []corev1.Secret { - return []corev1.Secret{ - createSecret(ed.Jaeger, masterSecret.instanceName(ed.Jaeger), getWorkingDirContents(getWorkingDir(ed.Jaeger), masterSecret.keyFileNameMap)), - createSecret(ed.Jaeger, esSecret.instanceName(ed.Jaeger), getWorkingDirContents(getWorkingDir(ed.Jaeger), esSecret.keyFileNameMap)), - createSecret(ed.Jaeger, jaegerSecret.instanceName(ed.Jaeger), getWorkingDirContents(getWorkingDir(ed.Jaeger), jaegerSecret.keyFileNameMap)), - createSecret(ed.Jaeger, curatorSecret.instanceName(ed.Jaeger), getWorkingDirContents(getWorkingDir(ed.Jaeger), curatorSecret.keyFileNameMap)), - } -} - -// CreateCerts creates certificates for elasticsearch, jaeger and curator -// The cert generation is done by shell script. If the certificates are not present -// on the filesystem the operator injects them from secrets - this allows operator restarts. -// The script also re-generates expired certificates. -func (ed *ElasticsearchDeployment) CreateCerts() error { - err := extractSecretsToFile(ed.Jaeger, ed.Secrets, masterSecret, esSecret, jaegerSecret, curatorSecret) - if err != nil { - return errors.Wrap(err, "failed to extract certificates from secrets to file") - } - return createESCerts(ed.CertScript, ed.Jaeger) -} - -// CleanCerts removes certificates from local filesystem. -// Use this function in tests to clean resources -func (ed *ElasticsearchDeployment) CleanCerts() error { - return os.RemoveAll(getWorkingDir(ed.Jaeger)) -} - -func extractSecretsToFile(jaeger *v1.Jaeger, secrets []corev1.Secret, s ...secret) error { - secretMap := map[string]corev1.Secret{} - for _, sec := range secrets { - secretMap[sec.Name] = sec - } - for _, sec := range s { - if secret, ok := secretMap[sec.instanceName(jaeger)]; ok { - if err := extractSecretToFile(getWorkingDir(jaeger), secret.Data, sec); err != nil { - return errors.Wrap(err, fmt.Sprintf("failed to extract secret %s", secret.Name)) - } - } - } - return nil -} - -func extractSecretToFile(workingDir string, data map[string][]byte, secret secret) error { - for k, v := range secret.keyFileNameMap { - if err := writeToFile(workingDir, v, data[k]); err != nil { - return err - - } - } - return nil -} - -func getWorkingDir(jaeger *v1.Jaeger) string { - return filepath.Clean(fmt.Sprintf("%s/%s/%s/%s", tmpWorkingDir, jaeger.Namespace, jaeger.Name, jaeger.UID)) -} - -// createESCerts runs bash scripts which generates certificates -func createESCerts(script string, jaeger *v1.Jaeger) error { - // #nosec G204: Subprocess launching should be audited - cmd := exec.Command("bash", script) - cmd.Env = append(os.Environ(), - "NAMESPACE="+jaeger.Namespace, - "WORKING_DIR="+getWorkingDir(jaeger), - ) - if out, err := cmd.CombinedOutput(); err != nil { - log.WithFields(log.Fields{ - "script": script, - "out": string(out)}). - Error("Failed to create certificates") - return fmt.Errorf("error running script %s: %v", script, err) - } - return nil -} - -func createSecret(jaeger *v1.Jaeger, secretName string, data map[string][]byte) corev1.Secret { - return corev1.Secret{ - ObjectMeta: metav1.ObjectMeta{ - Name: secretName, - Namespace: jaeger.Namespace, - Labels: util.Labels(secretName, "es-secret", *jaeger), - OwnerReferences: []metav1.OwnerReference{util.AsOwner(jaeger)}, - }, - Type: corev1.SecretTypeOpaque, - Data: data, - } -} - -func getWorkingDirContents(dir string, content map[string]string) map[string][]byte { - c := map[string][]byte{} - for secretKey, certName := range content { - c[secretKey] = getDirFileContents(dir, certName) - } - return c -} - -func getDirFileContents(dir, filePath string) []byte { - return getFileContents(getFilePath(dir, filePath)) -} - -func getFilePath(dir, toFile string) string { - return path.Join(dir, toFile) -} - -func getFileContents(path string) []byte { - if path == "" { - return nil - } - contents, err := ioutil.ReadFile(filepath.Clean(path)) - if err != nil { - return nil - } - return contents -} - -func writeToFile(dir, file string, value []byte) error { - // first check if file exists - we prefer what is on FS to revert users editing secrets - path := getFilePath(dir, file) - if _, err := os.Stat(path); err == nil { - return nil - } - if err := os.MkdirAll(dir, os.ModePerm); err != nil { - return err - } - f, err := os.OpenFile(path, os.O_RDWR|os.O_CREATE, 0600) - if err != nil { - return err - } - defer f.Close() - _, err = f.Write(value) - if err != nil { - // remove the file on failure - it can be correctly created in the next iteration - os.RemoveAll(path) - return err - } - return nil -} diff --git a/pkg/storage/elasticsearch_secrets_test.go b/pkg/storage/elasticsearch_secrets_test.go deleted file mode 100644 index 9f8ba40731..0000000000 --- a/pkg/storage/elasticsearch_secrets_test.go +++ /dev/null @@ -1,179 +0,0 @@ -package storage - -import ( - "fmt" - "io/ioutil" - "os" - "path" - "reflect" - "runtime" - "testing" - - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" - corev1 "k8s.io/api/core/v1" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/apimachinery/pkg/types" - - v1 "github.com/jaegertracing/jaeger-operator/apis/v1" -) - -func TestCreateESSecretsError(t *testing.T) { - j := v1.NewJaeger(types.NamespacedName{Name: t.Name()}) - es := &ElasticsearchDeployment{Jaeger: j, CertScript: "/foo"} - err := es.CleanCerts() - require.NoError(t, err) - defer es.CleanCerts() - err = es.CreateCerts() - assert.EqualError(t, err, "error running script /foo: exit status 127") -} - -func TestCreateESSecrets(t *testing.T) { - j := v1.NewJaeger(types.NamespacedName{Name: t.Name()}) - es := &ElasticsearchDeployment{Jaeger: j, CertScript: "../../scripts/cert_generation.sh"} - err := es.CleanCerts() - require.NoError(t, err) - defer es.CleanCerts() - err = es.CreateCerts() - assert.NoError(t, err) - sec := es.ExtractSecrets() - assert.Equal(t, []string{ - masterSecret.instanceName(j), - esSecret.instanceName(j), - jaegerSecret.instanceName(j), - curatorSecret.instanceName(j)}, - []string{sec[0].Name, sec[1].Name, sec[2].Name, sec[3].Name}) - for _, s := range sec { - if s.Name == jaegerSecret.instanceName(j) { - ca, err := ioutil.ReadFile(fmt.Sprintf("%s/%s/ca.crt", tmpWorkingDir, j.Name)) - assert.NoError(t, err) - key, err := ioutil.ReadFile(fmt.Sprintf("%s/%s/user.jaeger.key", tmpWorkingDir, j.Name)) - assert.NoError(t, err) - cert, err := ioutil.ReadFile(fmt.Sprintf("%s/%s/user.jaeger.crt", tmpWorkingDir, j.Name)) - assert.NoError(t, err) - assert.Equal(t, map[string][]byte{"ca": ca, "key": key, "cert": cert}, s.Data) - } - } -} - -func TestCreateSecret(t *testing.T) { - j := v1.NewJaeger(types.NamespacedName{Name: "foo"}) - j.Namespace = "myproject" - s := createSecret(j, "bar", map[string][]byte{"foo": {}}) - assert.Equal(t, "bar", s.ObjectMeta.Name) - assert.Equal(t, j.Namespace, s.ObjectMeta.Namespace) - assert.Equal(t, j.Name, s.ObjectMeta.OwnerReferences[0].Name) - assert.Equal(t, j.Name, s.ObjectMeta.OwnerReferences[0].Name) - assert.Equal(t, map[string][]byte{"foo": {}}, s.Data) - assert.Equal(t, corev1.SecretTypeOpaque, s.Type) -} - -func TestGetWorkingFileDirContent(t *testing.T) { - dir := "/tmp/_" + t.Name() - defer os.RemoveAll(dir) - err := os.MkdirAll(dir, os.ModePerm) - assert.NoError(t, err) - err = ioutil.WriteFile(dir+"/foobar", []byte("foo"), 0644) - assert.NoError(t, err) - b := getDirFileContents(dir, "foobar") - assert.Equal(t, "foo", string(b)) -} - -func TestGetWorkingFileDirContent_EmptyPath(t *testing.T) { - b := getDirFileContents("", "") - assert.Nil(t, b) -} - -func TestGetWorkingFileDirContent_FileDoesNotExists(t *testing.T) { - b := getDirFileContents("", "jungle") - assert.Nil(t, b) -} - -func TestGetFileContent_EmptyPath(t *testing.T) { - b := getFileContents("") - assert.Nil(t, b) -} - -func TestExtractSecretsToFile(t *testing.T) { - j := v1.NewJaeger(types.NamespacedName{Name: t.Name()}) - caFile := fmt.Sprintf("%s/%s/ca.crt", tmpWorkingDir, j.Name) - defer os.Remove(caFile) - content := "115dasrez" - err := extractSecretsToFile( - j, - []corev1.Secret{{ObjectMeta: metav1.ObjectMeta{Name: j.Name + "-sec"}, Data: map[string][]byte{"ca": []byte(content)}}}, - secret{name: "sec", keyFileNameMap: map[string]string{"ca": "ca.crt"}}, - ) - assert.NoError(t, err) - ca, err := ioutil.ReadFile(caFile) - assert.NoError(t, err) - assert.Equal(t, []byte(content), ca) -} - -func TestExtractSecretsToFile_Err(t *testing.T) { - err := extractSecretToFile("/root", map[string][]byte{"foo": {}}, secret{keyFileNameMap: map[string]string{"foo": "foo"}}) - - // Avoid assertions on OS-dependent error messages which may differ between OSes. - patherr, ok := err.(*os.PathError) - assert.Error(t, patherr) - assert.True(t, ok) -} - -func TestExtractSecretsToFile_FileExists(t *testing.T) { - defer os.RemoveAll(tmpWorkingDir + "/bar/houdy") - content := "115dasrez" - err := os.MkdirAll(tmpWorkingDir+"/bar/houdy", os.ModePerm) - assert.NoError(t, err) - err = ioutil.WriteFile(tmpWorkingDir+"/bar/houdy/ca.crt", []byte(content), os.ModePerm) - assert.NoError(t, err) - - j := v1.NewJaeger(types.NamespacedName{Name: "houdy"}) - j.Namespace = "bar" - err = extractSecretsToFile( - j, - []corev1.Secret{{ObjectMeta: metav1.ObjectMeta{Name: "houdy-sec"}, Data: map[string][]byte{"ca": []byte("should not be there")}}}, - secret{name: "sec", keyFileNameMap: map[string]string{"ca": "ca.crt"}}, - ) - assert.NoError(t, err) - ca, err := ioutil.ReadFile(tmpWorkingDir + "/bar/houdy/ca.crt") - assert.NoError(t, err) - assert.Equal(t, []byte(content), ca) -} - -func TestWriteToWorkingDir(t *testing.T) { - _, testFile, _, _ := runtime.Caller(0) - defer os.RemoveAll(os.TempDir() + "/foo") - tests := []struct { - dir string - file string - wantErrType reflect.Type - }{ - { - dir: "/foo", file: "", wantErrType: reflect.TypeOf((*os.PathError)(nil)), - }, - { - dir: "/root", file: "bla", wantErrType: reflect.TypeOf((*os.PathError)(nil)), - }, - { - // file exists - dir: path.Dir(testFile), file: path.Base(testFile), - }, - { - // write to file - dir: os.TempDir(), file: "foo", - }, - } - for _, test := range tests { - err := writeToFile(test.dir, test.file, []byte("random")) - if test.wantErrType != nil { - // Avoid assertions on OS-dependent error messages which may differ between OSes. - errType := reflect.TypeOf(err) - assert.True(t, test.wantErrType == errType) - } else { - assert.NoError(t, err) - stat, err := os.Stat(fmt.Sprintf("%s/%s", test.dir, test.file)) - assert.NoError(t, err) - assert.Equal(t, test.file, stat.Name()) - } - } -} diff --git a/pkg/storage/elasticsearch_test.go b/pkg/storage/elasticsearch_test.go index 62ab73f12c..86c95958b8 100644 --- a/pkg/storage/elasticsearch_test.go +++ b/pkg/storage/elasticsearch_test.go @@ -1,6 +1,7 @@ package storage import ( + "fmt" "testing" "github.com/stretchr/testify/assert" @@ -24,7 +25,7 @@ func TestShouldDeployElasticsearch(t *testing.T) { {j: v1.JaegerStorageSpec{Type: v1.JaegerESStorage}, expected: true}, } for _, test := range tests { - assert.Equal(t, test.expected, ShouldDeployElasticsearch(test.j)) + assert.Equal(t, test.expected, ShouldInjectElasticsearchConfiguration(test.j)) } } @@ -54,6 +55,7 @@ func TestCreateElasticsearchCR(t *testing.T) { name: "foo", namespace: "myproject", jEsSpec: v1.ElasticsearchSpec{ + Name: "elasticsearch", NodeCount: 2, RedundancyPolicy: esv1.FullRedundancy, Storage: esv1.ElasticsearchStorageSpec{ @@ -78,6 +80,7 @@ func TestCreateElasticsearchCR(t *testing.T) { name: "foo", namespace: "myproject", jEsSpec: v1.ElasticsearchSpec{ + Name: "elasticsearch", NodeCount: 5, RedundancyPolicy: esv1.FullRedundancy, Storage: esv1.ElasticsearchStorageSpec{ @@ -108,6 +111,7 @@ func TestCreateElasticsearchCR(t *testing.T) { name: "foo-ba%r", namespace: "myproje&ct", jEsSpec: v1.ElasticsearchSpec{ + Name: "elasticsearch", NodeCount: 5, RedundancyPolicy: esv1.FullRedundancy, Storage: esv1.ElasticsearchStorageSpec{ @@ -138,6 +142,7 @@ func TestCreateElasticsearchCR(t *testing.T) { name: "tolerations", namespace: "mytolerableproject", jEsSpec: v1.ElasticsearchSpec{ + Name: "elasticsearch", NodeCount: 2, RedundancyPolicy: esv1.FullRedundancy, Tolerations: tolerations, @@ -181,17 +186,21 @@ func TestInject(t *testing.T) { expected *corev1.PodSpec es v1.ElasticsearchSpec }{ - {pod: &corev1.PodSpec{ - Containers: []corev1.Container{{ - Args: []string{"foo"}, - VolumeMounts: []corev1.VolumeMount{{Name: "lol"}}, - }}, - }, + { + es: v1.ElasticsearchSpec{ + Name: "elasticsearch", + }, + pod: &corev1.PodSpec{ + Containers: []corev1.Container{{ + Args: []string{"foo"}, + VolumeMounts: []corev1.VolumeMount{{Name: "lol"}}, + }}, + }, expected: &corev1.PodSpec{ Containers: []corev1.Container{{ Args: []string{ "foo", - "--es.server-urls=" + elasticsearchURL, + "--es.server-urls=https://elasticsearch:9200", "--es.tls.enabled=true", "--es.tls.ca=" + caPath, "--es.tls.cert=" + certPath, @@ -207,21 +216,23 @@ func TestInject(t *testing.T) { }}, Volumes: []corev1.Volume{{Name: "certs", VolumeSource: corev1.VolumeSource{ Secret: &corev1.SecretVolumeSource{ - SecretName: "hoo-jaeger-elasticsearch"}}}, + SecretName: "jaeger-elasticsearch"}}}, }}, }, - {pod: &corev1.PodSpec{ - Containers: []corev1.Container{{ - Args: []string{"--es.num-shards=15", "--es.num-replicas=55", "--es.timeout=99s"}, - }}, - }, + { + es: v1.ElasticsearchSpec{Name: "elasticsearch"}, + pod: &corev1.PodSpec{ + Containers: []corev1.Container{{ + Args: []string{"--es.num-shards=15", "--es.num-replicas=55", "--es.timeout=99s"}, + }}, + }, expected: &corev1.PodSpec{ Containers: []corev1.Container{{ Args: []string{ "--es.num-shards=15", "--es.num-replicas=55", "--es.timeout=99s", - "--es.server-urls=" + elasticsearchURL, + "--es.server-urls=https://elasticsearch:9200", "--es.tls.enabled=true", "--es.tls.ca=" + caPath, "--es.tls.cert=" + certPath, @@ -233,16 +244,20 @@ func TestInject(t *testing.T) { }}, Volumes: []corev1.Volume{{Name: "certs", VolumeSource: corev1.VolumeSource{ Secret: &corev1.SecretVolumeSource{ - SecretName: "hoo-jaeger-elasticsearch"}}}, + SecretName: "jaeger-elasticsearch"}}}, }}, }, { pod: &corev1.PodSpec{Containers: []corev1.Container{{}}}, - es: v1.ElasticsearchSpec{NodeCount: 15, RedundancyPolicy: esv1.FullRedundancy}, + es: v1.ElasticsearchSpec{ + Name: "my-es", + NodeCount: 15, + RedundancyPolicy: esv1.FullRedundancy, + }, expected: &corev1.PodSpec{ Containers: []corev1.Container{{ Args: []string{ - "--es.server-urls=" + elasticsearchURL, + "--es.server-urls=https://my-es:9200", "--es.tls.enabled=true", "--es.tls.ca=" + caPath, "--es.tls.cert=" + certPath, @@ -257,17 +272,27 @@ func TestInject(t *testing.T) { }}, Volumes: []corev1.Volume{{Name: "certs", VolumeSource: corev1.VolumeSource{ Secret: &corev1.SecretVolumeSource{ - SecretName: "hoo-jaeger-elasticsearch"}}}, + SecretName: "jaeger-my-es"}}}, }}, }, { - pod: &corev1.PodSpec{Containers: []corev1.Container{{Args: []string{"--es-archive.enabled=true"}}}}, - es: v1.ElasticsearchSpec{NodeCount: 15, RedundancyPolicy: esv1.FullRedundancy}, + es: v1.ElasticsearchSpec{ + Name: "es-tenant2", + NodeCount: 15, + RedundancyPolicy: esv1.FullRedundancy, + }, + pod: &corev1.PodSpec{ + Containers: []corev1.Container{ + { + Args: []string{"--es-archive.enabled=true"}, + }, + }, + }, expected: &corev1.PodSpec{ Containers: []corev1.Container{{ Args: []string{ "--es-archive.enabled=true", - "--es.server-urls=" + elasticsearchURL, + "--es.server-urls=https://es-tenant2:9200", "--es.tls.enabled=true", "--es.tls.ca=" + caPath, "--es.tls.cert=" + certPath, @@ -275,7 +300,7 @@ func TestInject(t *testing.T) { "--es.timeout=15s", "--es.num-shards=15", "--es.num-replicas=14", - "--es-archive.server-urls=" + elasticsearchURL, + "--es-archive.server-urls=https://es-tenant2:9200", "--es-archive.tls.enabled=true", "--es-archive.tls.ca=" + caPath, "--es-archive.tls.cert=" + certPath, @@ -290,16 +315,18 @@ func TestInject(t *testing.T) { }}, Volumes: []corev1.Volume{{Name: "certs", VolumeSource: corev1.VolumeSource{ Secret: &corev1.SecretVolumeSource{ - SecretName: "hoo-jaeger-elasticsearch"}}}, + SecretName: "jaeger-es-tenant2"}}}, }}, }, } - for _, test := range tests { - es := &ElasticsearchDeployment{Jaeger: v1.NewJaeger(types.NamespacedName{Name: "hoo"})} - es.Jaeger.Spec.Storage.Elasticsearch = test.es - es.InjectStorageConfiguration(test.pod) - assert.Equal(t, test.expected, test.pod) + for i, test := range tests { + t.Run(fmt.Sprintf("%d", i), func(t *testing.T) { + es := &ElasticsearchDeployment{Jaeger: v1.NewJaeger(types.NamespacedName{Name: "hoo"})} + es.Jaeger.Spec.Storage.Elasticsearch = test.es + es.InjectStorageConfiguration(test.pod) + assert.Equal(t, test.expected, test.pod) + }) } } diff --git a/pkg/strategy/controller.go b/pkg/strategy/controller.go index 0be282d99f..a43b8854a7 100644 --- a/pkg/strategy/controller.go +++ b/pkg/strategy/controller.go @@ -18,10 +18,6 @@ import ( "github.com/jaegertracing/jaeger-operator/pkg/storage" ) -const ( - esCertGenerationScript = "./scripts/cert_generation.sh" -) - var ( defaultEsMemory = resource.MustParse("16Gi") defaultEsCPURequest = resource.MustParse("1") @@ -129,6 +125,10 @@ func normalize(ctx context.Context, jaeger *v1.Jaeger) { normalizeElasticsearch(&jaeger.Spec.Storage.Elasticsearch) normalizeRollover(&jaeger.Spec.Storage.EsRollover) normalizeUI(&jaeger.Spec) + + if jaeger.Spec.Storage.Elasticsearch.Name == "" { + jaeger.Spec.Storage.Elasticsearch.Name = "elasticsearch" + } } func distributedStorage(storage v1.JaegerStorageType) bool { @@ -146,7 +146,7 @@ func normalizeSparkDependencies(spec *v1.JaegerStorageSpec) { // auto enable only for supported storages if cronjob.SupportedStorage(spec.Type) && spec.Dependencies.Enabled == nil && - !storage.ShouldDeployElasticsearch(*spec) && + !storage.ShouldInjectElasticsearchConfiguration(*spec) && tlsIsNotEnabled { trueVar := true spec.Dependencies.Enabled = &trueVar diff --git a/pkg/strategy/production.go b/pkg/strategy/production.go index 2c7ae31372..be383dd505 100644 --- a/pkg/strategy/production.go +++ b/pkg/strategy/production.go @@ -123,7 +123,7 @@ func newProductionStrategy(ctx context.Context, jaeger *v1.Jaeger) S { c.dependencies = storage.Dependencies(jaeger) // assembles the pieces for an elasticsearch self-provisioned deployment via the elasticsearch operator - if storage.ShouldDeployElasticsearch(jaeger.Spec.Storage) { + if storage.ShouldInjectElasticsearchConfiguration(jaeger.Spec.Storage) { var jobs []*corev1.PodSpec for i := range c.dependencies { jobs = append(jobs, &c.dependencies[i].Spec.Template.Spec) @@ -159,5 +159,8 @@ func autoProvisionElasticsearch(manifest *S, jaeger *v1.Jaeger, curatorPods []*c for _, pod := range curatorPods { es.InjectSecretsConfiguration(pod) } - manifest.elasticsearches = append(manifest.elasticsearches, *es.Elasticsearch()) + esCR := es.Elasticsearch() + if esCR != nil { + manifest.elasticsearches = append(manifest.elasticsearches, *esCR) + } } diff --git a/pkg/strategy/streaming.go b/pkg/strategy/streaming.go index ff4ce9b496..5251fa386c 100644 --- a/pkg/strategy/streaming.go +++ b/pkg/strategy/streaming.go @@ -143,7 +143,7 @@ func newStreamingStrategy(ctx context.Context, jaeger *v1.Jaeger) S { manifest.dependencies = storage.Dependencies(jaeger) // assembles the pieces for an elasticsearch self-provisioned deployment via the elasticsearch operator - if storage.ShouldDeployElasticsearch(jaeger.Spec.Storage) { + if storage.ShouldInjectElasticsearchConfiguration(jaeger.Spec.Storage) { var jobs []*corev1.PodSpec for i := range manifest.dependencies { jobs = append(jobs, &manifest.dependencies[i].Spec.Template.Spec) diff --git a/pkg/upgrade/upgrade.go b/pkg/upgrade/upgrade.go index d68a86dd82..22f3edab9f 100644 --- a/pkg/upgrade/upgrade.go +++ b/pkg/upgrade/upgrade.go @@ -60,6 +60,11 @@ func ManagedInstances(ctx context.Context, c client.Client, reader client.Reader jaeger, err := ManagedInstance(ctx, c, j, latestVersion) if err != nil { // nothing to do at this level, just go to the next instance + log.WithFields(log.Fields{ + "jaeger": jaeger.Name, + "namespace": jaeger.Namespace, + "latest-jaeger-version": latestVersion, + }).Error("Failed to upgrade", err) continue } if !reflect.DeepEqual(jaeger, j) { diff --git a/pkg/upgrade/v1_31_0.go b/pkg/upgrade/v1_31_0.go new file mode 100644 index 0000000000..a27b309cfb --- /dev/null +++ b/pkg/upgrade/v1_31_0.go @@ -0,0 +1,34 @@ +package upgrade + +import ( + "context" + "fmt" + + esv1 "github.com/openshift/elasticsearch-operator/apis/logging/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "sigs.k8s.io/controller-runtime/pkg/client" + + v1 "github.com/jaegertracing/jaeger-operator/apis/v1" + "github.com/jaegertracing/jaeger-operator/pkg/storage" +) + +func upgrade1_31_0(ctx context.Context, c client.Client, jaeger v1.Jaeger) (v1.Jaeger, error) { + + // Delete ES instance if self-provisioned ES is used. + // The newly created instance will use cert-management from EO operator. + if storage.ShouldInjectElasticsearchConfiguration(jaeger.Spec.Storage) { + es := esv1.Elasticsearch{ + ObjectMeta: metav1.ObjectMeta{ + // The only possible name + Name: "elasticsearch", + Namespace: jaeger.Namespace, + }, + } + err := c.Delete(ctx, &es) + if err != nil { + return jaeger, fmt.Errorf("failed to delete Elasticsearch, deletion is needed for certificate migration to Elasticsearch operator: %v", err) + } + } + + return jaeger, nil +} diff --git a/pkg/upgrade/versions.go b/pkg/upgrade/versions.go index 60603a45f8..a57ad5bbca 100644 --- a/pkg/upgrade/versions.go +++ b/pkg/upgrade/versions.go @@ -18,5 +18,6 @@ var ( "1.20.0": upgrade1_20_0, "1.22.0": upgrade1_22_0, "1.28.0": upgrade1_28_0, + "1.31.0": upgrade1_31_0, } ) diff --git a/scripts/cert_generation.sh b/scripts/cert_generation.sh deleted file mode 100644 index 431095db71..0000000000 --- a/scripts/cert_generation.sh +++ /dev/null @@ -1,272 +0,0 @@ -#! /bin/bash - -# The script is taken from https://github.com/openshift/cluster-logging-operator - -WORKING_DIR=${WORKING_DIR:-/tmp/_working_dir} -NAMESPACE=${NAMESPACE:-openshift-logging} -CA_PATH=${CA_PATH:-$WORKING_DIR/ca.crt} - -REGENERATE_NEEDED=0 - -function init_cert_files() { - - if [ ! -f ${WORKING_DIR}/ca.db ]; then - touch ${WORKING_DIR}/ca.db - fi - - if [ ! -f ${WORKING_DIR}/ca.serial.txt ]; then - echo 00 > ${WORKING_DIR}/ca.serial.txt - fi -} - -function generate_signing_ca() { - if [ ! -f ${WORKING_DIR}/ca.crt ] || [ ! -f ${WORKING_DIR}/ca.key ] || ! openssl x509 -checkend 0 -noout -in ${WORKING_DIR}/ca.crt; then - openssl req -x509 \ - -new \ - -newkey rsa:4096 \ - -keyout ${WORKING_DIR}/ca.key \ - -nodes \ - -days 1825 \ - -out ${WORKING_DIR}/ca.crt \ - -subj "/CN=openshift-cluster-logging-signer" - - REGENERATE_NEEDED=1 - fi -} - -function create_signing_conf() { - cat < "${WORKING_DIR}/signing.conf" -# Simple Signing CA - -# The [default] section contains global constants that can be referred to from -# the entire configuration file. It may also hold settings pertaining to more -# than one openssl command. - -[ default ] -dir = ${WORKING_DIR} # Top dir - -# The next part of the configuration file is used by the openssl req command. -# It defines the CA's key pair, its DN, and the desired extensions for the CA -# certificate. - -[ req ] -default_bits = 4096 # RSA key size -encrypt_key = yes # Protect private key -default_md = sha512 # MD to use -utf8 = yes # Input is UTF-8 -string_mask = utf8only # Emit UTF-8 strings -prompt = no # Don't prompt for DN -distinguished_name = ca_dn # DN section -req_extensions = ca_reqext # Desired extensions - -[ ca_dn ] -0.domainComponent = "io" -1.domainComponent = "openshift" -organizationName = "OpenShift Origin" -organizationalUnitName = "Logging Signing CA" -commonName = "Logging Signing CA" - -[ ca_reqext ] -keyUsage = critical,keyCertSign,cRLSign -basicConstraints = critical,CA:true,pathlen:0 -subjectKeyIdentifier = hash - -# The remainder of the configuration file is used by the openssl ca command. -# The CA section defines the locations of CA assets, as well as the policies -# applying to the CA. - -[ ca ] -default_ca = signing_ca # The default CA section - -[ signing_ca ] -certificate = \$dir/ca.crt # The CA cert -private_key = \$dir/ca.key # CA private key -new_certs_dir = \$dir/ # Certificate archive -serial = \$dir/ca.serial.txt # Serial number file -crlnumber = \$dir/ca.crl.srl # CRL number file -database = \$dir/ca.db # Index file -unique_subject = no # Require unique subject -default_days = 730 # How long to certify for -default_md = sha512 # MD to use -policy = any_pol # Default naming policy -email_in_dn = no # Add email to cert DN -preserve = no # Keep passed DN ordering -name_opt = ca_default # Subject DN display options -cert_opt = ca_default # Certificate display options -copy_extensions = copy # Copy extensions from CSR -x509_extensions = client_ext # Default cert extensions -default_crl_days = 7 # How long before next CRL -crl_extensions = crl_ext # CRL extensions - -# Naming policies control which parts of a DN end up in the certificate and -# under what circumstances certification should be denied. - -[ match_pol ] -domainComponent = match # Must match 'simple.org' -organizationName = match # Must match 'Simple Inc' -organizationalUnitName = optional # Included if present -commonName = supplied # Must be present - -[ any_pol ] -domainComponent = optional -countryName = optional -stateOrProvinceName = optional -localityName = optional -organizationName = optional -organizationalUnitName = optional -commonName = optional -emailAddress = optional - -# Certificate extensions define what types of certificates the CA is able to -# create. - -[ client_ext ] -keyUsage = critical,digitalSignature,keyEncipherment -basicConstraints = CA:false -extendedKeyUsage = clientAuth -subjectKeyIdentifier = hash -authorityKeyIdentifier = keyid - -[ server_ext ] -keyUsage = critical,digitalSignature,keyEncipherment -basicConstraints = CA:false -extendedKeyUsage = serverAuth,clientAuth -subjectKeyIdentifier = hash -authorityKeyIdentifier = keyid - -# CRL extensions exist solely to point to the CA certificate that has issued -# the CRL. - -[ crl_ext ] -authorityKeyIdentifier = keyid -EOF -} - -function sign_cert() { - local component=$1 - - openssl ca \ - -in ${WORKING_DIR}/${component}.csr \ - -notext \ - -out ${WORKING_DIR}/${component}.crt \ - -config ${WORKING_DIR}/signing.conf \ - -extensions v3_req \ - -batch \ - -extensions server_ext -} - -function generate_cert_config() { - local component=$1 - local extensions=${2:-} - - if [ "$extensions" != "" ]; then - cat < "${WORKING_DIR}/${component}.conf" -[ req ] -default_bits = 4096 -prompt = no -encrypt_key = yes -default_md = sha512 -distinguished_name = dn -req_extensions = req_ext -[ dn ] -CN = ${component} -OU = OpenShift -O = Logging -[ req_ext ] -subjectAltName = ${extensions} -EOF - else - cat < "${WORKING_DIR}/${component}.conf" -[ req ] -default_bits = 4096 -prompt = no -encrypt_key = yes -default_md = sha512 -distinguished_name = dn -[ dn ] -CN = ${component} -OU = OpenShift -O = Logging -EOF - fi -} - -function generate_request() { - local component=$1 - - openssl req -new \ - -out ${WORKING_DIR}/${component}.csr \ - -newkey rsa:4096 \ - -keyout ${WORKING_DIR}/${component}.key \ - -config ${WORKING_DIR}/${component}.conf \ - -days 712 \ - -nodes -} - -function generate_certs() { - local component=$1 - local extensions=${2:-} - - # For TRACING-1631 - if we can't find the namespace in the cert it's bad, regenerate everything - if [ $REGENERATE_NEEDED = 0 ] && [ "${component}" == "elasticsearch" ] && [ -f ${WORKING_DIR}/logging-es.crt ] ; then - # Make sure the SAN contains both "DNS:elasticsearch.${NAMESPACE}.svc.cluster.local" AND "DNS:elasticsearch.${NAMESPACE}.svc. - openssl x509 -in ${WORKING_DIR}/logging-es.crt -text | grep "DNS:elasticsearch.${NAMESPACE}.svc.cluster.local" | grep -E -q "DNS:elasticsearch.${NAMESPACE}.svc," - REGENERATE_NEEDED=$? - fi - - if [ $REGENERATE_NEEDED = 1 ] || [ ! -f ${WORKING_DIR}/${component}.crt ] || ! openssl x509 -checkend 0 -noout -in ${WORKING_DIR}/${component}.crt; then - generate_cert_config $component $extensions - generate_request $component - sign_cert $component - fi -} - -function generate_extensions() { - local add_oid=$1 - local add_localhost=$2 - shift - shift - local cert_names=$@ - - extension_names="" - extension_index=1 - local use_comma=0 - - if [ "$add_localhost" == "true" ]; then - extension_names="IP.1:127.0.0.1,IP.2:0:0:0:0:0:0:0:1,DNS.1:localhost" - extension_index=2 - use_comma=1 - fi - - for name in ${cert_names//,/}; do - if [ $use_comma = 1 ]; then - extension_names="${extension_names},DNS.${extension_index}:${name}" - else - extension_names="DNS.${extension_index}:${name}" - use_comma=1 - fi - extension_index=$(( extension_index + 1 )) - done - - if [ "$add_oid" == "true" ]; then - extension_names="${extension_names},RID.1:1.2.3.4.5.5" - fi - - echo "$extension_names" -} - -if [ ! -d $WORKING_DIR ]; then - mkdir -p $WORKING_DIR -fi - -generate_signing_ca -init_cert_files -create_signing_conf - -generate_certs 'system.admin' -generate_certs 'system.logging.curator' -generate_certs 'user.jaeger' - -# TODO: get es SAN DNS, IP values from es service names -generate_certs 'elasticsearch' "$(generate_extensions true true elasticsearch{,-cluster}{,.${NAMESPACE}.svc}{,.cluster.local})" -generate_certs 'logging-es' "$(generate_extensions false true elasticsearch{,.${NAMESPACE}.svc}{,.cluster.local})" diff --git a/tests/templates/kuttl-test.yaml b/tests/templates/kuttl-test.yaml index 4ba74d47c2..a9d45ee1db 100644 --- a/tests/templates/kuttl-test.yaml +++ b/tests/templates/kuttl-test.yaml @@ -3,6 +3,7 @@ kind: TestSuite crdDir: ../../_build/crds/ artifactsDir: ../../_build/artifacts/ commands: + - script: cd ../../.. && make cert-manager - script: kubectl create namespace observability 2>&1 | grep -v "already exists" || true - command: kubectl apply -f ../../_build/manifests/01-jaeger-operator.yaml -n observability - command: kubectl wait --timeout=5m --for=condition=available deployment jaeger-operator -n observability