diff --git a/.github/workflows/ci.yml b/.github/workflows/ci-base.yml similarity index 100% rename from .github/workflows/ci.yml rename to .github/workflows/ci-base.yml diff --git a/.github/workflows/ci-e2e-tests.yml b/.github/workflows/ci-e2e-tests.yml new file mode 100644 index 0000000..7cd8c13 --- /dev/null +++ b/.github/workflows/ci-e2e-tests.yml @@ -0,0 +1,114 @@ +# Our desired pipeline using only a Nix shell environment +name: E2E tests + +permissions: + id-token: write + contents: read + +on: + push: + branches: + - main + tags: + - "v*.*.*" + pull_request: + branches: + - main + +jobs: + e2e_tests: + name: E2E tests + runs-on: ubuntu-22.04 + steps: + - name: Git checkout + uses: actions/checkout@v3 + + - name: Install Nix + uses: cachix/install-nix-action@v27 + # relax sandbox due impure kontrol-service tests during build + with: + extra_nix_config: | + sandbox = relaxed + + - name: Magic cache + uses: DeterminateSystems/magic-nix-cache-action@v7 + + - name: Build and load kontrol service image + run: | + docker load < $(nix build ./#kontrol-service-container --no-link --print-out-paths) + + - name: Start kontrol service and Postgres + run: | + docker version + docker compose -f ci/docker-compose.yml up -d + docker ps + + - name: Wait for docker network to be ready + run: sleep 10s + shell: bash + + - name: Install CLI + run: curl get.kardinal.dev -sL | sh + + - name: Verify kardinal command + run: bash -c 'source ~/.bashrc; if kardinal | grep -q "Kardinal CLI"; then exit 0; else exit 1; fi' + + - name: Retrieve the tenant UUID + id: tenant + run: | + tenant_id=$(kardinal tenant show) + echo "id=${tenant_id}" >> "$GITHUB_OUTPUT" + + - name: Deploy boutique demo manifest + run: | + KARDINAL_CLI_DEV_MODE=TRUE kardinal deploy -k ci/obd-demo.yaml + + - name: Validate cluster resources endpoint + run: | + tenant_id=${{ steps.tenant.outputs.id }} + services=$(curl http://localhost:8080/tenant/${tenant_id}/cluster-resources | jq -r '.services[].metadata.name' | tr " " "\n" | sort -g | tr "\n" " " | xargs) + if [ "${services}" != "cartservice frontend postgres productcatalogservice" ]; then exit 1; fi + deployments=$(curl http://localhost:8080/tenant/${tenant_id}/cluster-resources | jq -r '.deployments[].metadata.name' | tr " " "\n" | sort -g | tr "\n" " " | xargs) + if [ "${deployments}" != "cartservice-prod frontend-prod postgres-prod productcatalogservice-prod" ]; then exit 1; fi + + - name: Validate topology endpoint + run: | + tenant_id=${{ steps.tenant.outputs.id }} + nodes=$(curl http://localhost:8080/tenant/${tenant_id}/topology | jq -r '.nodes[].id' | tr " " "\n" | sort -g | tr "\n" " " | xargs) + if [ "${nodes}" != "cartservice free-currency-api frontend frontend-external postgres productcatalogservice" ]; then exit 1; fi + + - name: Create, validate and delete flow + run: | + KARDINAL_CLI_DEV_MODE=TRUE kardinal flow create frontend kurtosistech/frontend:demo-frontend > kardinal.out + flow_id=$(grep "Flow.*created" kardinal.out | cut -d ' ' -f2 | tr -d "\"") + tenant_id=${{ steps.tenant.outputs.id }} + deployments=$(curl http://localhost:8080/tenant/${tenant_id}/cluster-resources | jq -r '.deployments[].metadata.name' | tr " " "\n" | sort -g | tr "\n" " " | xargs) + if [ "${deployments}" != "cartservice-prod frontend-${flow_id} frontend-prod postgres-prod productcatalogservice-prod" ]; then exit 1; fi + KARDINAL_CLI_DEV_MODE=TRUE kardinal flow ls | grep ${flow_id} + KARDINAL_CLI_DEV_MODE=TRUE kardinal flow delete ${flow_id} + + - name: Create, validate and delete a more complex flow + run: | + KARDINAL_CLI_DEV_MODE=TRUE kardinal flow create frontend kurtosistech/frontend:demo-on-sale -s productcatalogservice=kurtosistech/productcatalogservice:demo-on-sale > kardinal.out + flow_id=$(grep "Flow.*created" kardinal.out | cut -d ' ' -f2 | tr -d "\"") + tenant_id=${{ steps.tenant.outputs.id }} + deployments=$(curl http://localhost:8080/tenant/${tenant_id}/cluster-resources | jq -r '.deployments[].metadata.name' | tr " " "\n" | sort -g | tr "\n" " " | xargs) + if [ "${deployments}" != "cartservice-prod frontend-${flow_id} frontend-prod postgres-prod productcatalogservice-${flow_id} productcatalogservice-prod" ]; then exit 1; fi + KARDINAL_CLI_DEV_MODE=TRUE kardinal flow ls | grep ${flow_id} + KARDINAL_CLI_DEV_MODE=TRUE kardinal flow delete ${flow_id} + + - name: Create template + run: | + KARDINAL_CLI_DEV_MODE=TRUE kardinal template create extra-item-shared --template-yaml ci/template.yaml --description "Extra item and postgres is shared" + KARDINAL_CLI_DEV_MODE=TRUE kardinal template ls | grep "extra-item-shared" + + - name: Create flow with template and delete flow + run: | + KARDINAL_CLI_DEV_MODE=TRUE kardinal flow create frontend kurtosistech/frontend:demo-frontend --template-args ci/template_args.yaml --template extra-item-shared > kardinal.out + flow_id=$(grep "Flow.*created" kardinal.out | cut -d ' ' -f2 | tr -d "\"") + KARDINAL_CLI_DEV_MODE=TRUE kardinal flow ls | grep ${flow_id} + KARDINAL_CLI_DEV_MODE=TRUE kardinal flow delete ${flow_id} + + - name: Delete template + run: | + KARDINAL_CLI_DEV_MODE=TRUE kardinal template delete extra-item-shared diff --git a/ci/docker-compose.yml b/ci/docker-compose.yml new file mode 100644 index 0000000..7a98e41 --- /dev/null +++ b/ci/docker-compose.yml @@ -0,0 +1,38 @@ +networks: + kardinal: + driver: bridge + enable_ipv6: false + +services: + kontrol-service: + image: kontrol-service:latest-amd64 + restart: always + environment: + - DB_HOSTNAME=postgres + - DB_PORT=5432 + - DB_NAME=kardinal + - DB_USERNAME=postgres + - DB_PASSWORD=kurtosis + - DEV_MODE=true + ports: + - '8080:8080' + networks: + - kardinal + depends_on: + - postgres + postgres: + image: postgres:13-alpine + restart: always + environment: + - POSTGRES_USER=postgres + - POSTGRES_PASSWORD=kurtosis + - POSTGRES_DB=kardinal + ports: + - '5432:5432' + networks: + - kardinal + volumes: + - postgres:/var/lib/postgresql/data +volumes: + postgres: + driver: local diff --git a/ci/obd-demo.yaml b/ci/obd-demo.yaml new file mode 100644 index 0000000..ad689f0 --- /dev/null +++ b/ci/obd-demo.yaml @@ -0,0 +1,330 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: cartservice-v1 + labels: + app: cartservice + version: v1 +spec: + selector: + matchLabels: + app: cartservice + version: v1 + template: + metadata: + labels: + app: cartservice + version: v1 + spec: + terminationGracePeriodSeconds: 5 + containers: + - name: server + securityContext: + allowPrivilegeEscalation: false + capabilities: + drop: + - all + privileged: false + readOnlyRootFilesystem: true + image: kurtosistech/cartservice:main + imagePullPolicy: IfNotPresent + ports: + - containerPort: 8090 + readinessProbe: + httpGet: + path: /health + port: 8090 + initialDelaySeconds: 10 + periodSeconds: 10 + timeoutSeconds: 5 + failureThreshold: 3 + successThreshold: 1 + livenessProbe: + httpGet: + path: /health + port: 8090 + initialDelaySeconds: 15 + periodSeconds: 20 + timeoutSeconds: 5 + failureThreshold: 3 + env: + - name: PORT + value: "8090" + - name: DB_USERNAME + value: "postgresuser" + - name: DB_PASSWORD + value: "postgrespass" + - name: DB_HOST + value: "postgres" + - name: DB_PORT + value: "5432" + - name: DB_NAME + value: "cart" +--- +apiVersion: v1 +kind: Service +metadata: + name: cartservice + labels: + app: cartservice + version: v1 + annotations: + kardinal.dev.service/dependencies: "postgres:tcp" +spec: + type: ClusterIP + selector: + app: cartservice + ports: + - name: http + port: 8090 + targetPort: 8090 + protocol: TCP + appProtocol: HTTP +--- +apiVersion: apps/v1 +kind: Deployment +metadata: + name: frontend-v1 + labels: + app: frontend + version: v1 +spec: + selector: + matchLabels: + app: frontend + version: v1 + template: + metadata: + labels: + app: frontend + version: v1 + annotations: + sidecar.istio.io/rewriteAppHTTPProbers: "true" + spec: + containers: + - name: server + image: kurtosistech/frontend:main + imagePullPolicy: IfNotPresent + ports: + - containerPort: 8080 + readinessProbe: + initialDelaySeconds: 10 + httpGet: + path: "/_healthz" + port: 8080 + httpHeaders: + - name: "Cookie" + value: "shop_session-id=x-readiness-probe" + livenessProbe: + initialDelaySeconds: 10 + httpGet: + path: "/_healthz" + port: 8080 + httpHeaders: + - name: "Cookie" + value: "shop_session-id=x-liveness-probe" + env: + - name: ADDRESS + value: ":8080" + - name: FREECURRENCYAPIKEY + value: "fca_live_nFVVF8CvfxqJhzMHB4N2x1NH7ffVVPwZr9hg3iNl" + - name: CARTSERVICEHOST + value: cartservice + - name: PRODUCTCATALOGSERVICEHOST + value: productcatalogservice + +--- +apiVersion: v1 +kind: Service +metadata: + name: frontend + labels: + app: frontend + version: v1 + annotations: + kardinal.dev.service/dependencies: "productcatalogservice:http,cartservice:http" + kardinal.dev.service/plugins: | + - name: https://github.com/kurtosis-tech/free-currency-api-plugin.git + type: external + servicename: free-currency-api + args: + api_key: fca_live_VKZlykCWEiFcpBHnw74pzd4vLi04q1h9JySbVHDF +spec: + type: ClusterIP + selector: + app: frontend + ports: + - name: http + port: 80 + protocol: TCP + appProtocol: HTTP + targetPort: 8080 +--- +apiVersion: v1 +kind: Service +metadata: + name: frontend-external + annotations: + kardinal.dev.service/ingress: "true" + kardinal.dev.service/host: "prod.app.localhost" +spec: + type: LoadBalancer + selector: + app: frontend + ports: + - name: http + port: 80 + targetPort: 8080 + protocol: TCP + appProtocol: HTTP +--- +apiVersion: apps/v1 +kind: Deployment +metadata: + name: postgres-v1 + labels: + app: postgres + version: v1 +spec: + replicas: 1 + selector: + matchLabels: + app: postgres + version: v1 + template: + metadata: + labels: + app: postgres + version: v1 + spec: + containers: + - name: postgres + image: "postgres:14" + imagePullPolicy: IfNotPresent + ports: + - containerPort: 5432 + env: + - name: POSTGRES_DB + value: "cart" + - name: POSTGRES_USER + value: "postgresuser" + - name: POSTGRES_PASSWORD + value: "postgrespass" + volumeMounts: + - mountPath: /var/lib/postgresql/data + name: postgres-data + volumes: + - name: postgres-data + emptyDir: {} + +--- +apiVersion: v1 +kind: Service +metadata: + name: postgres + labels: + app: postgres + version: v1 + annotations: + kardinal.dev.service/stateful: "true" + kardinal.dev.service/plugins: | + - name: github.com/kurtosis-tech/postgres-seed-plugin + args: + seed_script: | + -- create the table + CREATE TABLE IF NOT EXISTS public.items( + id bigserial PRIMARY KEY, + created_at TIMESTAMP WITH TIME ZONE, + updated_at TIMESTAMP WITH TIME ZONE, + deleted_at TIMESTAMP WITH TIME ZONE, + user_id TEXT, + product_id TEXT, + quantity INTEGER + ); + + INSERT INTO public.items (id, created_at, updated_at, deleted_at, user_id, product_id, quantity) + VALUES (1, '2024-08-02 13:02:07.656104 +00:00', '2024-08-02 13:02:07.656104 +00:00', null, '0494c5e0-dde0-48fa-a6d8-f7962f5476bf', '66VCHSJNUP', 1); + + INSERT INTO public.items (id, created_at, updated_at, deleted_at, user_id, product_id, quantity) + VALUES (2, '2024-08-02 13:02:10.891407 +00:00', '2024-08-02 13:02:10.891407 +00:00', null, '0494c5e0-dde0-48fa-a6d8-f7962f5476bf', '2ZYFJ3GM2N', 1); + + -- Set the sequence to the correct value after inserting records + SELECT setval('public.items_id_seq', (SELECT MAX(id) FROM public.items)); + db_name: "cart" + db_user: "postgresuser" + db_password: "postgrespass" + +spec: + type: ClusterIP + ports: + - name: tcp + port: 5432 + targetPort: 5432 + protocol: TCP + selector: + app: postgres + +--- +apiVersion: apps/v1 +kind: Deployment +metadata: + name: productcatalogservice-v1 + labels: + app: productcatalogservice + version: v1 +spec: + selector: + matchLabels: + app: productcatalogservice + version: v1 + template: + metadata: + labels: + app: productcatalogservice + version: v1 + spec: + terminationGracePeriodSeconds: 5 + containers: + - name: server + image: kurtosistech/productcatalogservice:main + imagePullPolicy: IfNotPresent + ports: + - containerPort: 8070 + readinessProbe: + httpGet: + path: /health + port: 8070 + initialDelaySeconds: 10 + periodSeconds: 10 + timeoutSeconds: 5 + failureThreshold: 3 + successThreshold: 1 + livenessProbe: + httpGet: + path: /health + port: 8070 + initialDelaySeconds: 15 + periodSeconds: 20 + timeoutSeconds: 5 + failureThreshold: 3 + env: + - name: PORT + value: "8070" +--- +apiVersion: v1 +kind: Service +metadata: + name: productcatalogservice + labels: + app: productcatalogservice + version: v1 +spec: + type: ClusterIP + selector: + app: productcatalogservice + ports: + - name: http + port: 8070 + targetPort: 8070 + protocol: TCP + appProtocol: HTTP diff --git a/ci/template.yaml b/ci/template.yaml new file mode 100644 index 0000000..24bc9fa --- /dev/null +++ b/ci/template.yaml @@ -0,0 +1,35 @@ +apiVersion: v1 +kind: Service +metadata: + name: postgres + annotations: + kardinal.dev.service/shared: "true" + kardinal.dev.service/plugins: | + - name: github.com/kurtosis-tech/postgres-seed-plugin + args: + seed_script: | + -- create the table + CREATE TABLE IF NOT EXISTS public.items( + id bigserial PRIMARY KEY, + created_at TIMESTAMP WITH TIME ZONE, + updated_at TIMESTAMP WITH TIME ZONE, + deleted_at TIMESTAMP WITH TIME ZONE, + user_id TEXT, + product_id TEXT, + quantity INTEGER + ); + + INSERT INTO public.items (id, created_at, updated_at, deleted_at, user_id, product_id, quantity) + VALUES (1, '2024-08-02 13:02:07.656104 +00:00', '2024-08-02 13:02:07.656104 +00:00', null, '0494c5e0-dde0-48fa-a6d8-f7962f5476bf', '66VCHSJNUP', 1); + + INSERT INTO public.items (id, created_at, updated_at, deleted_at, user_id, product_id, quantity) + VALUES (2, '2024-08-02 13:02:10.891407 +00:00', '2024-08-02 13:02:10.891407 +00:00', null, '0494c5e0-dde0-48fa-a6d8-f7962f5476bf', '2ZYFJ3GM2N', 1); + + INSERT INTO public.items (id, created_at, updated_at, deleted_at, user_id, product_id, quantity) + VALUES (3, '2024-08-02 13:03:10.891407 +00:00', '2024-08-02 13:02:10.891407 +00:00', null, '0494c5e0-dde0-48fa-a6d8-f7962f5476bf', '2ZYFJ3GM2N', ${last_insert_quantity:-1}); + + -- Set the sequence to the correct value after inserting records + SELECT setval('public.items_id_seq', (SELECT MAX(id) FROM public.items)); + db_name: "cart" + db_user: "postgresuser" + db_password: "postgrespass" \ No newline at end of file diff --git a/ci/template_args.yaml b/ci/template_args.yaml new file mode 100644 index 0000000..de2ea28 --- /dev/null +++ b/ci/template_args.yaml @@ -0,0 +1 @@ +last_insert_quantity: 3