name: Constellation create description: Create a new Constellation cluster using latest OS image. inputs: workerNodesCount: description: "Number of worker nodes to spawn." required: true controlNodesCount: description: "Number of control-plane nodes to spawn." required: true cloudProvider: description: "Either 'gcp' or 'azure'." required: true machineType: description: "Machine type of VM to spawn." required: false osImage: description: "OS image to use." required: true isDebugImage: description: "Is OS img a debug img?" required: true kubernetesVersion: description: "Kubernetes version to create the cluster from." required: false artifactNameSuffix: description: "Suffix for artifact naming." required: true keepMeasurements: default: "false" description: "Keep measurements embedded in the CLI." existingConfig: default: "false" description: "Use existing config file." # # GCP specific inputs # gcpProject: description: "The GCP project to deploy Constellation in." required: false gcpInClusterServiceAccountKey: description: "The GCP Service account to use inside the created Constellation cluster." required: false # # Azure specific inputs # azureSubscription: description: "The Azure subscription ID to deploy Constellation in." required: false azureTenant: description: "The Azure tenant ID to deploy Constellation in." required: false azureClientID: description: "The Azure client ID of the application registration created for Constellation." required: false azureClientSecret: description: "The Azure client secret value of the used secret." required: false azureUserAssignedIdentity: description: "The Azure user assigned identity to use for Constellation." required: false azureResourceGroup: description: "The Azure resource group to use for Constellation cluster" required: false outputs: kubeconfig: description: "The kubeconfig for the cluster." value: ${{ steps.constellation-init.outputs.KUBECONFIG }} masterSecret: description: "The master-secret for the cluster." value: ${{ steps.constellation-init.outputs.MASTERSECRET }} osImageUsed: description: "The OS image used in the cluster." value: ${{ steps.setImage.outputs.image }} runs: using: "composite" steps: - name: Constellation config generate shell: bash if: inputs.existingConfig != 'true' run: | if [[ -n "${{ inputs.kubernetesVersion }}" ]]; then constellation config generate ${{ inputs.cloudProvider }} --kubernetes="${{ inputs.kubernetesVersion }}" --debug else constellation config generate ${{ inputs.cloudProvider }} --debug fi yq eval -i "(.name) = \"e2e-test\"" constellation-conf.yaml yq eval -i \ "(.provider | select(. | has(\"azure\")).azure.subscription) = \"${{ inputs.azureSubscription }}\" | (.provider | select(. | has(\"azure\")).azure.tenant) = \"${{ inputs.azureTenant }}\" | (.provider | select(. | has(\"azure\")).azure.location) = \"West US\" | (.provider | select(. | has(\"azure\")).azure.userAssignedIdentity) = \"${{ inputs.azureUserAssignedIdentity }}\" | (.provider | select(. | has(\"azure\")).azure.resourceGroup) = \"${{ inputs.azureResourceGroup }}\" | (.provider | select(. | has(\"azure\")).azure.appClientID) = \"${{ inputs.azureClientID }}\" | (.provider | select(. | has(\"azure\")).azure.clientSecretValue) = \"${{ inputs.azureClientSecret }}\"" \ constellation-conf.yaml yq eval -i \ "(.provider | select(. | has(\"gcp\")).gcp.project) = \"${{ inputs.gcpProject }}\" | (.provider | select(. | has(\"gcp\")).gcp.region) = \"europe-west3\" | (.provider | select(. | has(\"gcp\")).gcp.zone) = \"europe-west3-b\" | (.provider | select(. | has(\"gcp\")).gcp.serviceAccountKeyPath) = \"serviceAccountKey.json\"" \ constellation-conf.yaml yq eval -i \ "(.provider | select(. | has(\"aws\")).aws.region) = \"eu-central-1\" | (.provider | select(. | has(\"aws\")).aws.zone) = \"eu-central-1c\" | (.provider | select(. | has(\"aws\")).aws.iamProfileControlPlane) = \"e2e_test_control_plane_instance_profile\" | (.provider | select(. | has(\"aws\")).aws.iamProfileWorkerNodes) = \"e2e_test_worker_node_instance_profile\"" \ constellation-conf.yaml if [[ -n "${{ inputs.kubernetesVersion }}" ]]; then yq eval -i "(.kubernetesVersion) = \"${{ inputs.kubernetesVersion }}\"" constellation-conf.yaml fi - name: Remove embedded measurements if: inputs.keepMeasurements == 'false' shell: bash run: | if [[ $(yq '.version' constellation-conf.yaml) == "v2" ]] then yq eval -i \ "(.provider | select(. | has(\"aws\")).aws.measurements) = {15:{\"expected\":\"0000000000000000000000000000000000000000000000000000000000000000\",\"warnOnly\":false}}" \ constellation-conf.yaml yq eval -i \ "(.provider | select(. | has(\"azure\")).azure.measurements) = {15:{\"expected\":\"0000000000000000000000000000000000000000000000000000000000000000\",\"warnOnly\":false}}" \ constellation-conf.yaml yq eval -i \ "(.provider | select(. | has(\"gcp\")).gcp.measurements) = {15:{\"expected\":\"0000000000000000000000000000000000000000000000000000000000000000\",\"warnOnly\":false}}"\ constellation-conf.yaml yq eval -i \ "(.provider | select(. | has(\"qemu\")).qemu.measurements) = {15:{\"expected\":\"0000000000000000000000000000000000000000000000000000000000000000\",\"warnOnly\":false}}" \ constellation-conf.yaml else yq eval -i \ "(.attestation | select(. | has(\"awsNitroTPM\")).awsNitroTPM.measurements) = {15:{\"expected\":\"0000000000000000000000000000000000000000000000000000000000000000\",\"warnOnly\":false}}" \ constellation-conf.yaml yq eval -i \ "(.attestation | select(. | has(\"azureSEVSNP\")).azureSEVSNP.measurements) = {15:{\"expected\":\"0000000000000000000000000000000000000000000000000000000000000000\",\"warnOnly\":false}}" \ constellation-conf.yaml yq eval -i \ "(.attestation | select(. | has(\"azureTrustedLaunch\")).azureTrustedLaunch.measurements) = {15:{\"expected\":\"0000000000000000000000000000000000000000000000000000000000000000\",\"warnOnly\":false}}" \ constellation-conf.yaml yq eval -i \ "(.attestation | select(. | has(\"gcpSEVES\")).gcpSEVES.measurements) = {15:{\"expected\":\"0000000000000000000000000000000000000000000000000000000000000000\",\"warnOnly\":false}}"\ constellation-conf.yaml yq eval -i \ "(.attestation | select(. | has(\"qemuVTPM\")).qemuVTPM.measurements) = {15:{\"expected\":\"0000000000000000000000000000000000000000000000000000000000000000\",\"warnOnly\":false}}" \ constellation-conf.yaml fi - name: Set image id: setImage shell: bash env: imageInput: ${{ inputs.osImage }} run: | if [[ -z "${imageInput}" ]]; then echo "No image specified. Using default image from config." image=$(yq eval ".image" constellation-conf.yaml) echo "image=${image}" | tee -a "$GITHUB_OUTPUT" exit 0 fi yq eval -i "(.image) = \"${imageInput}\"" constellation-conf.yaml echo "image=${imageInput}" | tee -a "$GITHUB_OUTPUT" - name: Set instanceType if: inputs.machineType && inputs.machineType != 'default' shell: bash run: | yq eval -i "(.provider | select(. | has(\"azure\")).azure.instanceType) = \"${{ inputs.machineType }}\"" constellation-conf.yaml yq eval -i "(.provider | select(. | has(\"gcp\")).gcp.instanceType) = \"${{ inputs.machineType }}\"" constellation-conf.yaml yq eval -i "(.provider | select(. | has(\"aws\")).aws.instanceType) = \"${{ inputs.machineType }}\"" constellation-conf.yaml - name: Create serviceAccountKey.json if: inputs.cloudProvider == 'gcp' && !inputs.existingConfig # Skip if using existing config. serviceAccountKey.json is already present in that case. shell: bash env: GCP_CLUSTER_SERVICE_ACCOUNT_KEY: ${{ inputs.gcpInClusterServiceAccountKey }} run: | echo "$GCP_CLUSTER_SERVICE_ACCOUNT_KEY" > serviceAccountKey.json - name: Enable debugCluster flag if: inputs.isDebugImage == 'true' shell: bash run: | yq eval -i '(.debugCluster) = true' constellation-conf.yaml # Uses --force flag since the CLI currently does not have a pre-release version and is always on the latest released version. # However, many of our pipelines work on prerelease images. Thus the used images are newer than the CLI's version. # This makes the version validation in the CLI fail. - name: Constellation create shell: bash run: | echo "Creating cluster using config:" cat constellation-conf.yaml sudo sh -c 'echo "127.0.0.1 license.confidential.cloud" >> /etc/hosts' || true output=$(constellation --help) if [[ $output == *"tf-log"* ]]; then TFFLAG="--tf-log=DEBUG" fi constellation create -c ${{ inputs.controlNodesCount }} -w ${{ inputs.workerNodesCount }} -y --force --debug ${TFFLAG:-} - name: Cdbg deploy if: inputs.isDebugImage == 'true' shell: bash run: | echo "::group::cdbg deploy" chmod +x $GITHUB_WORKSPACE/build/cdbg cdbg deploy \ --bootstrapper "${{ github.workspace }}/build/bootstrapper" \ --upgrade-agent "${{ github.workspace }}/build/upgrade-agent" \ --info logcollect=true \ --info logcollect.github.actor="${{ github.triggering_actor }}" \ --info logcollect.github.workflow="${{ github.workflow }}" \ --info logcollect.github.run-id="${{ github.run_id }}" \ --info logcollect.github.run-attempt="${{ github.run_attempt }}" \ --info logcollect.github.ref-name="${{ github.ref_name }}" \ --info logcollect.github.sha="${{ github.sha }}" \ --info logcollect.github.runner-os="${{ runner.os }}" \ --force echo "::endgroup::" - name: Constellation init id: constellation-init shell: bash run: | constellation init --force --debug echo "KUBECONFIG=$(pwd)/constellation-admin.conf" >> $GITHUB_OUTPUT echo "MASTERSECRET=$(pwd)/constellation-mastersecret.json" >> $GITHUB_OUTPUT - name: Wait for nodes to join and become ready shell: bash env: KUBECONFIG: "${{ steps.constellation-init.outputs.KUBECONFIG }}" JOINTIMEOUT: "1200" # 20 minutes timeout for all nodes to join run: | echo "::group::Wait for nodes" NODES_COUNT=$((${{ inputs.controlNodesCount }} + ${{ inputs.workerNodesCount }})) JOINWAIT=0 until [[ "$(kubectl get nodes -o json | jq '.items | length')" == "${NODES_COUNT}" ]] || [[ $JOINWAIT -gt $JOINTIMEOUT ]]; do echo "$(kubectl get nodes -o json | jq '.items | length')/"${NODES_COUNT}" nodes have joined.. waiting.." JOINWAIT=$((JOINWAIT+30)) sleep 30 done if [[ $JOINWAIT -gt $JOINTIMEOUT ]]; then echo "Timed out waiting for nodes to join" exit 1 fi echo "$(kubectl get nodes -o json | jq '.items | length')/"${NODES_COUNT}" nodes have joined" if ! kubectl wait --for=condition=ready --all nodes --timeout=20m; then kubectl get pods -n kube-system kubectl get events -n kube-system echo "::error::kubectl wait timed out before all nodes became ready" echo "::endgroup::" exit 1 fi echo "::endgroup::" - name: Download boot logs if: always() continue-on-error: true shell: bash env: CSP: ${{ inputs.cloudProvider }} run: | echo "::group::Download boot logs" case $CSP in azure) AZURE_RESOURCE_GROUP=$(yq eval ".provider.azure.resourceGroup" constellation-conf.yaml) ./.github/actions/constellation_create/az-logs.sh ${AZURE_RESOURCE_GROUP} ;; gcp) ./.github/actions/constellation_create/gcp-logs.sh ;; aws) ./.github/actions/constellation_create/aws-logs.sh eu-central-1 ;; esac echo "::endgroup::" - name: Upload boot logs if: always() && !env.ACT continue-on-error: true uses: actions/upload-artifact@0b7f8abb1508181956e8e162db84b466c27e18ce # v3.1.2 with: name: serial-logs-${{ inputs.artifactNameSuffix }} path: "*.log"