diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index 8072b2433..35bf99be3 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -1,4 +1,4 @@ -name: CI build +name: CI on: push: @@ -8,19 +8,32 @@ on: - v[0-9]+.[0-9]+ - cryostat-v[0-9]+.[0-9]+ - pull_request: + pull_request_target: + types: + - opened + - reopened + - synchronize + - labeled + - unlabeled branches: - main - v[0-9]+ - v[0-9]+.[0-9]+ - cryostat-v[0-9]+.[0-9]+ +env: + CI_OPERATOR_IMG: quay.io/cryostat/cryostat-operator + CI_BUNDLE_IMG: quay.io/cryostat/cryostat-operator-bundle + CI_SCORECARD_IMG: quay.io/cryostat/cryostat-operator-scorecard + jobs: - build: + controller-test: runs-on: ubuntu-latest - env: - CI_OPERATOR_IMG: quay.io/cryostat/cryostat-operator + if: ${{ github.repository_owner == 'cryostatio' }} steps: + - name: Fail if safe-to-test label NOT applied + if: ${{ !contains(github.event.pull_request.labels.*.name, 'safe-to-test') }} + run: exit 1 - uses: actions/checkout@v2 - uses: actions/setup-go@v2 with: @@ -33,13 +46,100 @@ jobs: key: ${{ runner.os }}-go-${{ hashFiles('**/go.sum') }} restore-keys: | ${{ runner.os }}-go- + - name: Run controller tests + run: make test-envtest + scorecard-test: + runs-on: ubuntu-latest + if: ${{ github.repository_owner == 'cryostatio' }} + env: + SCORECARD_NAMESPACE: cryostat-operator-scorecard + SCORECARD_SA_NAME: cryostat-scorecard + steps: + - name: Fail if safe-to-test label NOT applied + if: ${{ !contains(github.event.pull_request.labels.*.name, 'safe-to-test') }} + run: exit 1 + - uses: actions/checkout@v2 - uses: jpkrohling/setup-operator-sdk@v1.1.0 with: operator-sdk-version: v1.28.0 - - run: make generate manifests manager test-envtest - if: ${{ github.event_name == 'pull_request' }} - - run: make oci-build - if: ${{ github.event_name == 'push' }} + - name: Build scorecard image for test + run: make scorecard-build CUSTOM_SCORECARD_IMG=ghcr.io/${{ github.repository_owner }}/cryostat-operator-scorecard:ci-${{ github.event.number }} + - name: Push scorecard image to ghcr.io for test + id: push-scorecard-to-ghcr + uses: redhat-actions/push-to-registry@v2 + with: + image: cryostat-operator-scorecard + tags: ci-${{ github.event.number }} + registry: ghcr.io/${{ github.repository_owner }} + username: ${{ github.event.pull_request.user.login }} + password: ${{ secrets.GHCR_PR_TOKEN }} + - name: Build bundle image for test + run: make bundle bundle-build BUNDLE_IMG=ghcr.io/${{ github.repository_owner }}/cryostat-operator-bundle:ci-${{ github.event.number }} + - name: Push bundle image to ghcr.io for test + id: push-bundle-to-ghcr + uses: redhat-actions/push-to-registry@v2 + with: + image: cryostat-operator-bundle + tags: ci-${{ github.event.number }} + registry: ghcr.io/${{ github.repository_owner }} + username: ${{ github.event.pull_request.user.login }} + password: ${{ secrets.GHCR_PR_TOKEN }} + - name: Set up Kind cluster + run: | + { + printf -- "kind: Cluster\n" + printf -- "apiVersion: kind.x-k8s.io/v1alpha4\n" + printf -- "nodes:\n" + printf -- "- role: control-plane\n" + printf -- " kubeadmConfigPatches:\n" + printf -- " - |\n" + printf -- " kind: InitConfiguration\n" + printf -- " nodeRegistration:\n" + printf -- " kubeletExtraArgs:\n" + printf -- " node-labels: \"ingress-ready=true\"\n" + } > kind-config.yaml + kind create cluster --config=kind-config.yaml -n ci-${{ github.repository_owner }} + # Enabling Ingress + kubectl apply -f https://raw.githubusercontent.com/kubernetes/ingress-nginx/main/deploy/static/provider/kind/deploy.yaml + kubectl wait --namespace ingress-nginx \ + --for=condition=ready pod \ + --selector=app.kubernetes.io/component=controller \ + --timeout=120s + - name: Install Operator Lifecycle Manager + run: curl -sL https://github.com/operator-framework/operator-lifecycle-manager/releases/download/v0.24.0/install.sh | bash -s v0.24.0 + - name: Install Cert Manager + run: make cert_manager + - name: Set up scorecard environment + run: | + KUSTOMIZE=$PWD/bin/kustomize + make kustomize + + # Create a test namespace + kubectl create namespace $SCORECARD_NAMESPACE + + # Generate rbacs + pushd internal/images/custom-scorecard-tests/rbac/ && $KUSTOMIZE edit set namespace $SCORECARD_NAMESPACE + popd && $KUSTOMIZE build internal/images/custom-scorecard-tests/rbac/ | kubectl apply -f - + + # Define credentials to pull from ghcr.io + kubectl create secret docker-registry ghcr-key --docker-server=https://ghcr.io \ + --docker-username=${{ github.event.pull_request.user.login }} --docker-password=${{ secrets.GHCR_PR_TOKEN }} + kubectl patch sa $SCORECARD_SA_NAME -p '{"imagePullSecrets": [{"name": "ghcr-key"}]}' + + # Deploy the operator + operator-sdk run bundle -n $SCORECARD_NAMESPACE \ + --timeout 20m --pull-secret-name ghcr-key ${{ steps.push-bundle-to-ghcr.outputs.registry-path }} + - name: Run scorecard tests + run: | + operator-sdk scorecard -n $SCORECARD_NAMESPACE -s $SCORECARD_SA_NAME -w 20m --pod-security=restricted ./bundle + - name: Clean up Kind cluster + run: kind delete cluster -n ci-${{ github.repository_owner }} + build-operator: + runs-on: ubuntu-latest + if: ${{ github.event_name == 'push' && github.repository_owner == 'cryostatio' }} + steps: + - name: Build operator image + run: SKIP_TESTS=true make oci-build - name: Tag image id: tag-image run: | @@ -48,11 +148,10 @@ jobs: podman tag \ ${{ env.CI_OPERATOR_IMG }}:$IMG_TAG \ ${{ env.CI_OPERATOR_IMG }}:latest - echo "::set-output name=tags::$IMG_TAG latest" + echo "tags=$IMG_TAG latest" >> $GITHUB_OUTPUT else - echo "::set-output name=tags::$IMG_TAG" - fi - if: ${{ github.event_name == 'push' && github.repository_owner == 'cryostatio' }} + echo "tags=$IMG_TAG" >> $GITHUB_OUTPUT + fi - name: Push to quay.io id: push-to-quay uses: redhat-actions/push-to-registry@v2 @@ -62,7 +161,69 @@ jobs: registry: quay.io/cryostat username: cryostat+bot password: ${{ secrets.REGISTRY_PASSWORD }} - if: ${{ github.event_name == 'push' && github.repository_owner == 'cryostatio' }} - name: Print image URL run: echo "Image pushed to ${{ steps.push-to-quay.outputs.registry-paths }}" - if: ${{ github.event_name == 'push' && github.repository_owner == 'cryostatio' }} + build-bundle: + runs-on: ubuntu-latest + if: ${{ github.event_name == 'push' && github.repository_owner == 'cryostatio' }} + steps: + - uses: actions/checkout@v2 + - name: Build bundle image + run: make bundle-build + - name: Tag image + id: tag-image + run: | + IMG_TAG="$(make --eval='print-img-ver: ; @echo $(IMAGE_VERSION)' print-img-ver)" + if [ "$GITHUB_REF" == "refs/heads/main" ]; then + podman tag \ + ${{ env.CI_BUNDLE_IMG }}:$IMG_TAG \ + ${{ env.CI_BUNDLE_IMG }}:latest + echo "tags=$IMG_TAG latest" >> $GITHUB_OUTPUT + else + echo "tags=$IMG_TAG" >> $GITHUB_OUTPUT + fi + - name: Push to quay.io + id: push-to-quay + uses: redhat-actions/push-to-registry@v2 + with: + image: cryostat-operator-bundle + tags: ${{ steps.tag-image.outputs.tags }} + registry: quay.io/cryostat + username: cryostat+bot + password: ${{ secrets.REGISTRY_PASSWORD }} + - name: Print image URL + run: echo "Image pushed to ${{ steps.push-to-quay.outputs.registry-paths }}" + build-scorecard: + runs-on: ubuntu-latest + if: ${{ github.event_name == 'push' && github.repository_owner == 'cryostatio' }} + steps: + - uses: actions/checkout@v2 + - name: Get scorecard image tag + id: get-image-tag + run: | + SCORECARD_TAG=$(yq '[.stages[0].tests[].image | capture("cryostat-operator-scorecard:(?P[\w.\-_]+)$")][0].tag' bundle/tests/scorecard/config.yaml) + echo "tag=$SCORECARD_TAG" >> $GITHUB_OUTPUT + - name: Check if scorecard image tag already exists + id: check-tag-exists + run: | + EXIST=false + if [ -n $(podman search --list-tags ${CI_SCORECARD_IMG} --format json | jq --arg TAG ${{ steps.get-image-tag.outputs.tag }} '.[0].Tags[] | select( . == $TAG)') ]; then + EXIST=true + fi + echo "exist=$EXIST" >> $GITHUB_OUTPUT + - name: Build scorecard image + run: make scorecard-build CUSTOM_SCORECARD_IMG=${CI_SCORECARD_IMG}:${{ steps.get-image-tag.outputs.tag }} + if: ${{ steps.check-tag-exists.outputs.exist == 'false' }} + - name: Push to quay.io + id: push-to-quay + uses: redhat-actions/push-to-registry@v2 + with: + image: cryostat-operator-scorecard + tags: ${{ steps.get-image-tag.outputs.tag }} + registry: quay.io/cryostat + username: cryostat+bot + password: ${{ secrets.REGISTRY_PASSWORD }} + if: ${{ steps.check-tag-exists.outputs.exist == 'false' }} + - name: Print image URL + run: echo "Image pushed to ${{ steps.push-to-quay.outputs.registry-paths }}" + if: ${{ steps.check-tag-exists.outputs.exist == 'false' }} diff --git a/.mergify.yml b/.mergify.yml index 1444011fb..3f89a7b6a 100644 --- a/.mergify.yml +++ b/.mergify.yml @@ -9,3 +9,10 @@ pull_request_rules: - cryostat-v2.3 assignees: - "{{ author }}" + - name: auto label PRs from reviewers + conditions: + - author=@reviewers + actions: + label: + add: + - safe-to-test diff --git a/internal/test/scorecard/tests.go b/internal/test/scorecard/tests.go index a254b89f9..80ea65160 100644 --- a/internal/test/scorecard/tests.go +++ b/internal/test/scorecard/tests.go @@ -45,12 +45,15 @@ import ( scapiv1alpha3 "github.com/operator-framework/api/pkg/apis/scorecard/v1alpha3" apimanifests "github.com/operator-framework/api/pkg/manifests" + routev1 "github.com/openshift/api/route/v1" appsv1 "k8s.io/api/apps/v1" corev1 "k8s.io/api/core/v1" + v1 "k8s.io/api/networking/v1" kerrors "k8s.io/apimachinery/pkg/api/errors" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/util/wait" + "k8s.io/client-go/discovery" "k8s.io/client-go/kubernetes/scheme" ) @@ -101,7 +104,12 @@ func CryostatCRTest(bundle *apimanifests.Bundle, namespace string, openShiftCert } defer cleanupCryostat(&r, client, namespace) - if openShiftCertManager { + openshift, err := isOpenShift(client.DiscoveryClient) + if err != nil { + return fail(r, fmt.Sprintf("could not determine whether platform is OpenShift: %s", err.Error())) + } + + if openshift && openShiftCertManager { err := installOpenShiftCertManager(&r) if err != nil { return fail(r, fmt.Sprintf("failed to install cert-manager Operator for Red Hat OpenShift: %s", err.Error())) @@ -109,15 +117,7 @@ func CryostatCRTest(bundle *apimanifests.Bundle, namespace string, openShiftCert } // Create a default Cryostat CR - cr := &operatorv1beta1.Cryostat{ - ObjectMeta: metav1.ObjectMeta{ - Name: "cryostat-cr-test", - Namespace: namespace, - }, - Spec: operatorv1beta1.CryostatSpec{ - Minimal: false, - }, - } + cr := newCryostatCR(namespace, !openshift) ctx := context.Background() cr, err = client.OperatorCRDs().Cryostats(namespace).Create(ctx, cr) @@ -288,3 +288,89 @@ func logEvents(r *scapiv1alpha3.TestResult, client *CryostatClientset, namespace } return nil } + +func newCryostatCR(namespace string, withIngress bool) *operatorv1beta1.Cryostat { + cr := &operatorv1beta1.Cryostat{ + ObjectMeta: metav1.ObjectMeta{ + Name: "cryostat-cr-test", + Namespace: namespace, + }, + Spec: operatorv1beta1.CryostatSpec{ + Minimal: false, + EnableCertManager: &[]bool{true}[0], + }, + } + + if withIngress { + pathType := v1.PathTypePrefix + cr.Spec.NetworkOptions = &operatorv1beta1.NetworkConfigurationList{ + CoreConfig: &operatorv1beta1.NetworkConfiguration{ + Annotations: map[string]string{ + "nginx.ingress.kubernetes.io/backend-protocol": "HTTPS", + }, + IngressSpec: &v1.IngressSpec{ + TLS: []v1.IngressTLS{}, + Rules: []v1.IngressRule{ + { + Host: "testing.cryostat", + IngressRuleValue: v1.IngressRuleValue{ + HTTP: &v1.HTTPIngressRuleValue{ + Paths: []v1.HTTPIngressPath{ + { + Path: "/", + PathType: &pathType, + Backend: v1.IngressBackend{ + Service: &v1.IngressServiceBackend{ + Name: "cryostat-sample", + Port: v1.ServiceBackendPort{ + Number: 8181, + }, + }, + }, + }, + }, + }, + }, + }, + }, + }, + }, + GrafanaConfig: &operatorv1beta1.NetworkConfiguration{ + Annotations: map[string]string{ + "nginx.ingress.kubernetes.io/backend-protocol": "HTTPS", + }, + IngressSpec: &v1.IngressSpec{ + TLS: []v1.IngressTLS{}, + Rules: []v1.IngressRule{ + { + Host: "testing.cryostat", + IngressRuleValue: v1.IngressRuleValue{ + HTTP: &v1.HTTPIngressRuleValue{ + Paths: []v1.HTTPIngressPath{ + { + Path: "/", + PathType: &pathType, + Backend: v1.IngressBackend{ + Service: &v1.IngressServiceBackend{ + Name: "cryostat-sample-grafana", + Port: v1.ServiceBackendPort{ + Number: 3000, + }, + }, + }, + }, + }, + }, + }, + }, + }, + }, + }, + } + } + return cr +} + +func isOpenShift(client discovery.DiscoveryInterface) (bool, error) { + return discovery.IsResourceEnabled(client, routev1.GroupVersion.WithResource("routes")) +}