OS images: use "ref", "stream" and "version"

Switch azure default region to west us
Update find-image script to work with new API spec
Add version for every os image build
generate measurements: Use new API paths
CLI: config fetch measurements: Use image short versions to fetch measurements
CLI: allows shortnames to specify image in config
Image build pipeline: Change paths to contain "ref" and "stream"
This commit is contained in:
Malte Poll 2022-12-09 11:51:38 +01:00 committed by Malte Poll
parent 4795fe9695
commit 4a8ebfd921
28 changed files with 554 additions and 249 deletions

View File

@ -73,7 +73,7 @@ runs:
yq eval -i \ yq eval -i \
"(.provider | select(. | has(\"azure\")).azure.subscription) = \"${{ inputs.azureSubscription }}\" | "(.provider | select(. | has(\"azure\")).azure.subscription) = \"${{ inputs.azureSubscription }}\" |
(.provider | select(. | has(\"azure\")).azure.tenant) = \"${{ inputs.azureTenant }}\" | (.provider | select(. | has(\"azure\")).azure.tenant) = \"${{ inputs.azureTenant }}\" |
(.provider | select(. | has(\"azure\")).azure.location) = \"North Europe\" | (.provider | select(. | has(\"azure\")).azure.location) = \"West US\" |
(.provider | select(. | has(\"azure\")).azure.userAssignedIdentity) = \"${{ inputs.azureUserAssignedIdentity }}\" | (.provider | select(. | has(\"azure\")).azure.userAssignedIdentity) = \"${{ inputs.azureUserAssignedIdentity }}\" |
(.provider | select(. | has(\"azure\")).azure.resourceGroup) = \"${{ inputs.azureResourceGroup }}\" | (.provider | select(. | has(\"azure\")).azure.resourceGroup) = \"${{ inputs.azureResourceGroup }}\" |
(.provider | select(. | has(\"azure\")).azure.appClientID) = \"${{ inputs.azureClientID }}\" | (.provider | select(. | has(\"azure\")).azure.appClientID) = \"${{ inputs.azureClientID }}\" |
@ -94,7 +94,7 @@ runs:
(.provider | select(. | has(\"aws\")).aws.iamProfileWorkerNodes) = \"e2e_test_worker_node_instance_profile\"" \ (.provider | select(. | has(\"aws\")).aws.iamProfileWorkerNodes) = \"e2e_test_worker_node_instance_profile\"" \
constellation-conf.yaml constellation-conf.yaml
if [ ${{ inputs.kubernetesVersion != '' }} = true ]; then if [[ ${{ inputs.kubernetesVersion != '' }} = true ]]; then
yq eval -i "(.kubernetesVersion) = ${{ inputs.kubernetesVersion }}" constellation-conf.yaml yq eval -i "(.kubernetesVersion) = ${{ inputs.kubernetesVersion }}" constellation-conf.yaml
fi fi
@ -187,13 +187,13 @@ runs:
echo "::group::Wait for nodes" echo "::group::Wait for nodes"
NODES_COUNT=$((${{ inputs.controlNodesCount }} + ${{ inputs.workerNodesCount }})) NODES_COUNT=$((${{ inputs.controlNodesCount }} + ${{ inputs.workerNodesCount }}))
JOINWAIT=0 JOINWAIT=0
until [ "$(kubectl get nodes -o json | jq '.items | length')" == "${NODES_COUNT}" ] || [ $JOINWAIT -gt $JOINTIMEOUT ]; until [[ "$(kubectl get nodes -o json | jq '.items | length')" == "${NODES_COUNT}" ]] || [[ $JOINWAIT -gt $JOINTIMEOUT ]];
do do
echo "$(kubectl get nodes -o json | jq '.items | length')/"${NODES_COUNT}" nodes have joined.. waiting.." echo "$(kubectl get nodes -o json | jq '.items | length')/"${NODES_COUNT}" nodes have joined.. waiting.."
JOINWAIT=$((JOINWAIT+30)) JOINWAIT=$((JOINWAIT+30))
sleep 30 sleep 30
done done
if [ $JOINWAIT -gt $JOINTIMEOUT ]; then if [[ $JOINWAIT -gt $JOINTIMEOUT ]]; then
echo "Timed out waiting for nodes to join" echo "Timed out waiting for nodes to join"
exit 1 exit 1
fi fi

View File

@ -22,18 +22,18 @@ runs:
# Scrap namespaces that contain PVCs # Scrap namespaces that contain PVCs
for namespace in `kubectl get namespace --no-headers=true -o custom-columns=":metadata.name"`; do for namespace in `kubectl get namespace --no-headers=true -o custom-columns=":metadata.name"`; do
if [ `kubectl get pvc -n $namespace --no-headers=true -o custom-columns=":metadata.name" | wc -l` -gt 0 ]; then if [[ `kubectl get pvc -n $namespace --no-headers=true -o custom-columns=":metadata.name" | wc -l` -gt 0 ]]; then
kubectl delete namespace $namespace --wait kubectl delete namespace $namespace --wait
fi fi
done done
until [ "$(kubectl get pv -o json | jq '.items | length')" == "0" ] || [ $ELAPSED -gt $PV_DELETION_TIMEOUT ]; until [[ "$(kubectl get pv -o json | jq '.items | length')" == "0" ]] || [[ $ELAPSED -gt $PV_DELETION_TIMEOUT ]];
do do
echo $(kubectl get pv -o json | jq '.items | length') PV remaining.. echo $(kubectl get pv -o json | jq '.items | length') PV remaining..
sleep 1 sleep 1
ELAPSED=$((ELAPSED+1)) ELAPSED=$((ELAPSED+1))
done done
if [ $ELAPSED -gt $PV_DELETION_TIMEOUT ]; then if [[ $ELAPSED -gt $PV_DELETION_TIMEOUT ]]; then
echo "Timed out waiting for PV deletion.." echo "Timed out waiting for PV deletion.."
exit 1 exit 1
fi fi

View File

@ -2,13 +2,18 @@ name: Find latest OS image
description: Finds the latest OS image of a given type. description: Finds the latest OS image of a given type.
inputs: inputs:
imageType: ref:
description: "Type of image to find. Can be one of [debug, release] or a custom prefix (branch name)." description: 'Branch to search on. Can be "-" for releases or a branch name.'
required: true required: true
default: "main"
stream:
description: 'Type of image to find. Can be one of "stable", "nightly", "debug".'
required: true
default: "debug"
outputs: outputs:
image: image:
description: "The latest image of the given type." description: "The latest image of the given ref and stream."
value: ${{ steps.find-latest-image.outputs.image }} value: ${{ steps.find-latest-image.outputs.image }}
runs: runs:
@ -20,31 +25,13 @@ runs:
role-to-assume: arn:aws:iam::795746500882:role/GithubConstellationImageFinder role-to-assume: arn:aws:iam::795746500882:role/GithubConstellationImageFinder
aws-region: eu-central-1 aws-region: eu-central-1
- name: Set search prefix
id: set-search-prefix
shell: bash
env:
image_type: ${{ inputs.imageType }}
run: |
if [[ "${image_type}" == "debug" ]]; then
echo "prefix=debug-v" >> "${GITHUB_OUTPUT}"
elif [[ "${image_type}" == "release" ]]; then
echo "prefix=v" >> "${GITHUB_OUTPUT}"
else
echo "prefix=${image_type}" >> "${GITHUB_OUTPUT}"
fi
- name: Find latest image - name: Find latest image
id: find-latest-image id: find-latest-image
shell: bash shell: bash
env: env:
bucket: cdn-constellation-backend ref: ${{ inputs.ref }}
prefix: constellation/v1/images/${{ steps.set-search-prefix.outputs.prefix }} stream: ${{ inputs.stream }}
run: | run: |
newest_debug_image_path=$(aws s3api list-objects-v2 \ image=$(./find-image.sh --ref "${ref}" --stream "${stream}")
--output text \
--bucket "${bucket}" \
--prefix "${prefix}" \
--query "reverse(sort_by(Contents, &LastModified))[0].Key")
image=$(basename "${newest_debug_image_path}" .json)
echo "image=${image}" >> "${GITHUB_OUTPUT}" echo "image=${image}" >> "${GITHUB_OUTPUT}"
working-directory: hack/find-image

View File

@ -10,8 +10,14 @@ inputs:
basePath: basePath:
description: "Base path to the image build directory" description: "Base path to the image build directory"
required: true required: true
ref:
description: "Branch of the image to be built (or '-' for releases)"
required: true
stream:
description: "Image stream / type. (Use 'stable' for releases, 'nightly' for regular non-release images and 'debug' for debug builds)"
required: true
imageVersion: imageVersion:
description: "Semantic version including patch e.g. v<major>.<minor>.<patch> (only used for releases)" description: "Semantic version including patch e.g. v<major>.<minor>.<patch> or pseudo version"
required: false required: false
imageType: imageType:
description: "Type of image to build" description: "Type of image to build"
@ -145,15 +151,16 @@ runs:
- name: Configure AWS input variables - name: Configure AWS input variables
id: aws id: aws
if: ${{ inputs.csp == 'aws' }} if: inputs.csp == 'aws'
shell: bash shell: bash
env: env:
basePath: ${{ inputs.basePath }} basePath: ${{ inputs.basePath }}
ref: ${{ inputs.ref }}
stream: ${{ inputs.stream }}
imageVersion: ${{ inputs.imageVersion }} imageVersion: ${{ inputs.imageVersion }}
imageType: ${{ inputs.imageType }} imageType: ${{ inputs.imageType }}
timestamp: ${{ steps.version.outputs.timestamp }} timestamp: ${{ steps.version.outputs.timestamp }}
semver: ${{ steps.version.outputs.semanticVersion }} semver: ${{ steps.version.outputs.semanticVersion }}
branchName: ${{ steps.version.outputs.branchName }}
run: | run: |
echo "region=eu-central-1" >> $GITHUB_OUTPUT echo "region=eu-central-1" >> $GITHUB_OUTPUT
echo "replicationRegions=us-east-2 ap-south-1" >> $GITHUB_OUTPUT echo "replicationRegions=us-east-2 ap-south-1" >> $GITHUB_OUTPUT
@ -162,16 +169,12 @@ runs:
echo "imagePath=${basePath}/mkosi.output.aws/fedora~37/image.raw" >> $GITHUB_OUTPUT echo "imagePath=${basePath}/mkosi.output.aws/fedora~37/image.raw" >> $GITHUB_OUTPUT
echo "jsonOutput=${basePath}/mkosi.output.aws/fedora~37/image-upload.json" >> $GITHUB_OUTPUT echo "jsonOutput=${basePath}/mkosi.output.aws/fedora~37/image-upload.json" >> $GITHUB_OUTPUT
echo "imageFilename=image-$(date +%s).raw" >> $GITHUB_OUTPUT echo "imageFilename=image-$(date +%s).raw" >> $GITHUB_OUTPUT
if [ "${imageType}" = release ] if [[ "${stream}" = "stable" ]]
then then
echo "imageName=constellation-${imageVersion}" >> $GITHUB_OUTPUT echo "imageName=constellation-${imageVersion}" >> $GITHUB_OUTPUT
echo "publish=true" >> $GITHUB_OUTPUT echo "publish=true" >> $GITHUB_OUTPUT
elif [ "${imageType}" = debug ]
then
echo "imageName=constellation-debug-${semver}-${timestamp}" >> $GITHUB_OUTPUT
echo "publish=false" >> $GITHUB_OUTPUT
else else
echo "imageName=constellation-${branchName}-${timestamp}" >> $GITHUB_OUTPUT echo "imageName=constellation-${ref}-${stream}-${semver}-${timestamp}" >> $GITHUB_OUTPUT
echo "publish=false" >> $GITHUB_OUTPUT echo "publish=false" >> $GITHUB_OUTPUT
fi fi
@ -180,10 +183,12 @@ runs:
# image version has to be semantic version in the form <uint>.<uint>.<uint> . uint may not be larger than 2,147,483,647 # image version has to be semantic version in the form <uint>.<uint>.<uint> . uint may not be larger than 2,147,483,647
- name: Configure Azure input variables - name: Configure Azure input variables
id: azure id: azure
if: ${{ inputs.csp == 'azure' }} if: inputs.csp == 'azure'
shell: bash shell: bash
env: env:
basePath: ${{ inputs.basePath }} basePath: ${{ inputs.basePath }}
ref: ${{ inputs.ref }}
stream: ${{ inputs.stream }}
imageVersion: ${{ inputs.imageVersion }} imageVersion: ${{ inputs.imageVersion }}
imageType: ${{ inputs.imageType }} imageType: ${{ inputs.imageType }}
timestamp: ${{ steps.version.outputs.timestamp }} timestamp: ${{ steps.version.outputs.timestamp }}
@ -203,31 +208,31 @@ runs:
echo "jsonOutput=${basePath}/mkosi.output.azure/fedora~37/image-upload${uploadVariant}.json" >> $GITHUB_OUTPUT echo "jsonOutput=${basePath}/mkosi.output.azure/fedora~37/image-upload${uploadVariant}.json" >> $GITHUB_OUTPUT
# TODO: set default security type to "ConfidentialVM" once replication is possible # TODO: set default security type to "ConfidentialVM" once replication is possible
securityType=${{ inputs.uploadVariant }} securityType=${{ inputs.uploadVariant }}
if [ -z "${securityType}" ]; then if [[ -z "${securityType}" ]]; then
securityType=ConfidentialVMSupported securityType=ConfidentialVMSupported
fi fi
echo "securityType=${securityType}" >> $GITHUB_OUTPUT echo "securityType=${securityType}" >> $GITHUB_OUTPUT
echo "diskName=constellation-${pseudover//./-}-${securityType,,}" >> $GITHUB_OUTPUT echo "diskName=constellation-${stream}-${timestamp}-${securityType,,}" >> $GITHUB_OUTPUT
if [ "${imageType}" = release ] if [[ "${stream}" = "release" ]]
then then
echo "imageDefinition=constellation" >> $GITHUB_OUTPUT echo "imageDefinition=constellation" >> $GITHUB_OUTPUT
echo "imageOffer=constellation" >> $GITHUB_OUTPUT echo "imageOffer=constellation" >> $GITHUB_OUTPUT
echo "imageVersion=${imageVersion:1}" >> $GITHUB_OUTPUT echo "imageVersion=${imageVersion:1}" >> $GITHUB_OUTPUT
galleryName=Constellation galleryName=Constellation
elif [ "${imageType}" = debug ] elif [[ "${imageType}" = "debug" ]] && [[ [[ "${ref}" = "-" ] || [[ "${ref}" = "main" ]] ]]
then then
echo "imageDefinition=${semver}" >> $GITHUB_OUTPUT echo "imageDefinition=${semver}" >> $GITHUB_OUTPUT
echo "imageOffer=${semver}" >> $GITHUB_OUTPUT echo "imageOffer=${semver}" >> $GITHUB_OUTPUT
echo "imageVersion=${timestamp:0:4}.${timestamp:4:4}.${timestamp:8}" >> $GITHUB_OUTPUT echo "imageVersion=${timestamp:0:4}.${timestamp:4:4}.${timestamp:8}" >> $GITHUB_OUTPUT
galleryName=Constellation_Debug galleryName=Constellation_Debug
else else
echo "imageDefinition=${branchName}" >> $GITHUB_OUTPUT echo "imageDefinition=${ref}-${stream}" >> $GITHUB_OUTPUT
echo "imageOffer=${branchName}" >> $GITHUB_OUTPUT echo "imageOffer=${ref}-${stream}" >> $GITHUB_OUTPUT
echo "imageVersion=${timestamp:0:4}.${timestamp:4:4}.${timestamp:8}" >> $GITHUB_OUTPUT echo "imageVersion=${timestamp:0:4}.${timestamp:4:4}.${timestamp:8}" >> $GITHUB_OUTPUT
galleryName=Constellation_Testing galleryName=Constellation_Testing
fi fi
# TODO: enable VMGS upload for ConfidentialVM images once replication is possible # TODO: enable VMGS upload for ConfidentialVM images once replication is possible
if [ "${securityType}" == "ConfidentialVMSupported" ]; then if [[ "${securityType}" == "ConfidentialVMSupported" ]]; then
echo "galleryName=${galleryName}_CVM" >> $GITHUB_OUTPUT echo "galleryName=${galleryName}_CVM" >> $GITHUB_OUTPUT
echo "vmgsPath=" >> $GITHUB_OUTPUT echo "vmgsPath=" >> $GITHUB_OUTPUT
else else
@ -239,15 +244,16 @@ runs:
# Must not end or begin with a dash # Must not end or begin with a dash
- name: Configure GCP input variables - name: Configure GCP input variables
id: gcp id: gcp
if: ${{ inputs.csp == 'gcp' }} if: inputs.csp == 'gcp'
shell: bash shell: bash
env: env:
basePath: ${{ inputs.basePath }} basePath: ${{ inputs.basePath }}
ref: ${{ inputs.ref }}
stream: ${{ inputs.stream }}
imageVersion: ${{ inputs.imageVersion }} imageVersion: ${{ inputs.imageVersion }}
imageType: ${{ inputs.imageType }} imageType: ${{ inputs.imageType }}
timestamp: ${{ steps.version.outputs.timestamp }} timestamp: ${{ steps.version.outputs.timestamp }}
semver: ${{ steps.version.outputs.semanticVersion }} semver: ${{ steps.version.outputs.semanticVersion }}
branchName: ${{ steps.version.outputs.branchName }}
run: | run: |
echo "project=constellation-images" >> $GITHUB_OUTPUT echo "project=constellation-images" >> $GITHUB_OUTPUT
echo "bucket=constellation-images" >> $GITHUB_OUTPUT echo "bucket=constellation-images" >> $GITHUB_OUTPUT
@ -255,25 +261,25 @@ runs:
echo "rawImagePath=${basePath}/mkosi.output.gcp/fedora~37/image.raw" >> $GITHUB_OUTPUT echo "rawImagePath=${basePath}/mkosi.output.gcp/fedora~37/image.raw" >> $GITHUB_OUTPUT
echo "imagePath=${basePath}/mkosi.output.gcp/fedora~37/image.tar.gz" >> $GITHUB_OUTPUT echo "imagePath=${basePath}/mkosi.output.gcp/fedora~37/image.tar.gz" >> $GITHUB_OUTPUT
echo "jsonOutput=${basePath}/mkosi.output.gcp/fedora~37/image-upload.json" >> $GITHUB_OUTPUT echo "jsonOutput=${basePath}/mkosi.output.gcp/fedora~37/image-upload.json" >> $GITHUB_OUTPUT
if [ "${imageType}" = release ] if [[ "${stream}" = "stable" ]]
then then
echo "imageName=constellation-${imageVersion//./-}" >> $GITHUB_OUTPUT echo "imageName=constellation-${imageVersion//./-}" >> $GITHUB_OUTPUT
echo "imageFilename=constellation-${imageVersion//./-}.tar.gz" >> $GITHUB_OUTPUT echo "imageFilename=constellation-${imageVersion//./-}.tar.gz" >> $GITHUB_OUTPUT
echo "imageFamily=constellation" >> $GITHUB_OUTPUT echo "imageFamily=constellation" >> $GITHUB_OUTPUT
elif [ "${imageType}" = debug ] elif [[ "${imageType}" = "debug" ]] && [[ [[ "${ref}" = "-" ]] || [[ "${ref}" = "main" ]] ]]
then then
echo "imageName=constellation-${timestamp}" >> $GITHUB_OUTPUT echo "imageName=constellation-${ref}-${stream}-${timestamp}" >> $GITHUB_OUTPUT
echo "imageFilename=constellation-${timestamp}.tar.gz" >> $GITHUB_OUTPUT echo "imageFilename=constellation-${ref}-${stream}-${timestamp}.tar.gz" >> $GITHUB_OUTPUT
echo "imageFamily=constellation-debug-${semver//./-}" >> $GITHUB_OUTPUT echo "imageFamily=constellation-debug-${semver//./-}" >> $GITHUB_OUTPUT
else else
echo "imageName=constellation-${timestamp}" >> $GITHUB_OUTPUT echo "imageName=constellation-${ref}-${stream}-${timestamp}" >> $GITHUB_OUTPUT
echo "imageFilename=constellation-${timestamp}.tar.gz" >> $GITHUB_OUTPUT echo "imageFilename=constellation-${ref}-${stream}-${timestamp}.tar.gz" >> $GITHUB_OUTPUT
echo "imageFamily=constellation-${branchName}" >> $GITHUB_OUTPUT echo "imageFamily=constellation-${ref}" >> $GITHUB_OUTPUT
fi fi
- name: Configure QEMU input variables - name: Configure QEMU input variables
id: qemu id: qemu
if: ${{ inputs.csp == 'qemu' }} if: inputs.csp == 'qemu'
shell: bash shell: bash
env: env:
basePath: ${{ inputs.basePath }} basePath: ${{ inputs.basePath }}

45
.github/actions/shortname/action.yaml vendored Normal file
View File

@ -0,0 +1,45 @@
name: Determine parts for shortname
description: "Determine ref, stream and version for a shortname."
inputs:
shortname:
description: "Shortname of the image to build"
required: true
outputs:
ref:
description: "Branch name that the resource is built from (or '-' for releases)"
value: ${{ steps.extract.outputs.ref }}
stream:
description: "Stream that the resource belongs to"
value: ${{ steps.extract.outputs.stream }}
version:
description: "Resource version"
value: ${{ steps.extract.outputs.version }}
runs:
using: "composite"
steps:
- name: Extract ref, stream and version
shell: bash
id: extract
run: |
ref="-"
stream="stable"
version=""
IFS="/" read -r -a string_array <<< "${{ inputs.shortname }}"
for ((i=0; i<${#string_array[@]}; i++)); do
echo "${string_array[i]}"
if [[ ${string_array[i]} == "ref" ]]; then
ref=${string_array[i+1]}
elif [[ ${string_array[i]} == "stream" ]]; then
stream=${string_array[i+1]}
else
version=${string_array[i]}
fi
done
{
echo "ref=$ref"
echo "stream=$stream"
echo "version=$version"
} | tee "$GITHUB_OUTPUT" "$GITHUB_ENV"

View File

@ -104,48 +104,3 @@ The following secrets need to be defined:
* `AZURE_E2E_CLIENT_SECRET`: The client secret value for the registered app on Azure (which is defined as `appClientID`). * `AZURE_E2E_CLIENT_SECRET`: The client secret value for the registered app on Azure (which is defined as `appClientID`).
For information on how to achieve this, refer to the [First steps](https://docs.edgeless.systems/constellation/getting-started/first-steps) in the documentation for Constellation. For information on how to achieve this, refer to the [First steps](https://docs.edgeless.systems/constellation/getting-started/first-steps) in the documentation for Constellation.
## Image versions
The [build-os-image](../workflows/build-os-image.yml) workflow can be used to trigger an image build.
The workflow can be used to build debug or release images.
A debug image uses [`debugd`](../../debugd/) as its bootstrapper binary, while release images use the actual [`bootstrapper`](../../bootstrapper/)
Workflows for the main branch will always build debug images.
The image will be named and categorized depending on the branch the build is triggered from.
In the following, __Release__ refers to non debug images build from a release branch, e.g. `release/v1.4.0`,
__Debug__ refers to debug images build from either main or a release branch,
and __Branch__ refers to any image build from a branch that is not main or a release branch.
Non debug images built from main follow the __Branch__ image naming scheme.
### GCP
Type | Image Family | Image Name
-|-|-
Release | constellation | constellation-v\<major\>-\<minor\>-\<patch\>
Debug | constellation-debug-v\<major\>-\<minor\>-\<patch\> | constellation-\<commit-timestamp\>
Branch | constellation-\<branch-name\> | constellation-\<commit-timestamp\>
Example:
Type | Image Family | Image Name | List command
-|-|-|-
Release | constellation | constellation-v1-5-0 | `gcloud compute images list --filter="family~'^constellation$'" --sort-by=creationTimestamp --project constellation-images --uri \| sed 's#https://www.googleapis.com/compute/v1/##'`
Debug | constellation-debug-v1-5-0 | constellation-20220912123456 | `gcloud compute images list --filter="family~'constellation-debug-v.+'" --sort-by=creationTimestamp --project constellation-images --uri \| sed 's#https://www.googleapis.com/compute/v1/##'`
Branch | constellation-ref-cli | constellation-20220912123456 | `gcloud compute images list --filter="family~'constellation-$(go run $(git rev-parse --show-toplevel)/hack/pseudo-version/pseudo-version.go -print-branch)'" --sort-by=creationTimestamp --project constellation-images --uri \| sed 's#https://www.googleapis.com/compute/v1/##'`
### Azure
Type | Gallery | Image Definition | Image Version
-|-|-|-
Release | Constellation | constellation | \<major\>.\<minor\>.\<patch\>
Debug | Constellation_Debug | v\<major\>.\<minor\>.\<patch\> | \<commit-timestamp\>
Branch | Constellation_Testing | \<branch-name\> | \<commit-timestamp\>
Example:
Type | Gallery | Image Definition | Image Version | List command | Community list command
-|-|-|-|-|-
Release | Constellation | constellation | 1.5.0 | `az sig image-version list --resource-group constellation-images --gallery-name Constellation_CVM --gallery-image-definition constellation --query "sort_by([], &publishingProfile.publishedDate)[].id" -o table` | `az sig image-version list-community --public-gallery-name ConstellationCVM-b3782fa0-0df7-4f2f-963e-fc7fc42663df --gallery-image-definition constellation --location northeurope`
Debug | Constellation_Debug | v1.5.0 | 2022.0912.123456 | `az sig image-version list --resource-group constellation-images --gallery-name Constellation_Debug_CVM --gallery-image-definition v1.5.0 --query "sort_by([], &publishingProfile.publishedDate)[].id" -o table` | `az sig image-version list-community --public-gallery-name ConstellationCVM-d1905bb0-a66c-497e-a9e6-4410ca7e3701 --gallery-image-definition v1.5.0 --location northeurope`
Branch | Constellation_Testing | ref-cli | 2022.0912.123456 | `az sig image-version list --resource-group constellation-images --gallery-name Constellation_Testing_CVM --gallery-image-definition $(go run $(git rev-parse --show-toplevel)/hack/pseudo-version/pseudo-version.go -print-branch) --query "sort_by([], &publishingProfile.publishedDate)[].id" -o table` | `az sig image-version list-community --public-gallery-name ConstellationCVM-d1905bb0-a66c-497e-a9e6-4410ca7e3701 --gallery-image-definition $(go run $(git rev-parse --show-toplevel)/hack/pseudo-version/pseudo-version.go -print-branch) --location northeurope`

View File

@ -5,11 +5,21 @@ on:
imageVersion: imageVersion:
description: "Semantic version including patch e.g. v<major>.<minor>.<patch> (only used for releases)" description: "Semantic version including patch e.g. v<major>.<minor>.<patch> (only used for releases)"
required: false required: false
debug: isRelease:
description: "Build debug image" description: 'Is this a release? (sets "ref" to special value "-")'
type: boolean type: boolean
default: false
required: false required: false
default: false
stream:
description: "Image stream / type. (Use 'stable' for releases, 'nightly' for regular non-release images and 'debug' for debug builds)"
type: choice
required: true
options:
- "stable"
- "nightly"
- "debug"
# TODO: implement console access enabled image
# - "console"
jobs: jobs:
build-dependencies: build-dependencies:
@ -28,13 +38,13 @@ jobs:
ref: ${{ github.head_ref }} ref: ${{ github.head_ref }}
- name: Build bootstrapper - name: Build bootstrapper
if: ${{ inputs.debug == false }} if: inputs.stream != 'debug'
uses: ./.github/actions/build_bootstrapper uses: ./.github/actions/build_bootstrapper
with: with:
outputPath: ${{ github.workspace }}/build/bootstrapper outputPath: ${{ github.workspace }}/build/bootstrapper
- name: Build debugd - name: Build debugd
if: ${{ inputs.debug == true }} if: inputs.stream == 'debug'
uses: ./.github/actions/build_debugd uses: ./.github/actions/build_debugd
with: with:
outputPath: ${{ github.workspace }}/build/bootstrapper outputPath: ${{ github.workspace }}/build/bootstrapper
@ -65,9 +75,13 @@ jobs:
name: "Determine build settings" name: "Determine build settings"
runs-on: ubuntu-22.04 runs-on: ubuntu-22.04
outputs: outputs:
ref: ${{ steps.ref.outputs.ref }}
imageType: ${{ steps.image-type.outputs.imageType }} imageType: ${{ steps.image-type.outputs.imageType }}
pkiSet: ${{ steps.pki-set.outputs.pkiSet }} pkiSet: ${{ steps.pki-set.outputs.pkiSet }}
imageVersionUid: ${{ steps.image-version-uid.outputs.imageVersionUid }} imageVersion: ${{ steps.image-version.outputs.imageVersion }}
imageName: ${{ steps.image-version.outputs.imageName }}
imageNameShort: ${{ steps.image-version.outputs.imageNameShort }}
imageApiBasePath: ${{ steps.image-version.outputs.imageApiBasePath }}
steps: steps:
- name: Checkout - name: Checkout
@ -79,41 +93,65 @@ jobs:
id: version id: version
uses: ./.github/actions/pseudo_version uses: ./.github/actions/pseudo_version
- name: Determine ref
id: ref
run: |
if [[ "${{ inputs.isRelease }}" = "true" ]]; then
echo "ref=-" >> "$GITHUB_OUTPUT"
else
echo "ref=${{ steps.version.outputs.branchName }}" >> "$GITHUB_OUTPUT"
fi
- name: Validate stream
run: |
if [[ "${{ inputs.isRelease }}" == "true" ]] && [[ "${{ inputs.stream }}" == "nightly" ]]; then
echo "Nightly builds are not allowed for releases"
exit 1
elif [[ "${{ inputs.isRelease }}" != "true" ]] && [[ "${{ inputs.stream }}" == "stable" ]]; then
echo "Stable builds are only allowed for releases"
exit 1
fi
- name: Determine type of image build - name: Determine type of image build
shell: bash shell: bash
id: image-type id: image-type
run: | run: |
if [ "${{ startsWith(github.ref, 'refs/heads/release/') && (inputs.debug == false) }}" = true ] if [[ "${{ inputs.stream }}" == "debug" ]]; then
then
echo "imageType=release" >> "$GITHUB_OUTPUT"
elif [ "${{ ((github.ref == 'refs/heads/main') || startsWith(github.ref, 'refs/heads/release/')) && (inputs.debug == true) }}" = true ]
then
echo "imageType=debug" >> "$GITHUB_OUTPUT" echo "imageType=debug" >> "$GITHUB_OUTPUT"
else else
echo "imageType=branch" >> "$GITHUB_OUTPUT" echo "imageType=default" >> "$GITHUB_OUTPUT"
fi fi
- name: Determine PKI set - name: Determine PKI set
id: pki-set id: pki-set
shell: bash shell: bash
run: | run: |
if [ "${{ steps.image-type.outputs.imageType }}" = "release" ] if [[ "${{ inputs.isRelease }}" != "true" ]] && [[ "${{ inputs.stream }}" == "stable" ]]; then
then echo "pkiSet=pki_prod" >> "$GITHUB_OUTPUT"
echo "pkiSet=pki_prod" >> "$GITHUB_OUTPUT" else
else echo "pkiSet=pki_testing" >> "$GITHUB_OUTPUT"
echo "pkiSet=pki_testing" >> "$GITHUB_OUTPUT" fi
fi
- name: Determine image version uid - name: Determine image version
id: image-version-uid id: image-version
shell: bash shell: bash
env:
REF: ${{ steps.ref.outputs.ref }}
STREAM: ${{ inputs.stream }}
IMAGE_VERSION: ${{ inputs.imageVersion || steps.version.outputs.pseudoVersion }}
run: | run: |
if [ "${{ steps.image-type.outputs.imageType }}" = "release" ]; then {
echo "imageVersionUid=${{ github.event.inputs.imageVersion }}" >> "$GITHUB_OUTPUT" echo "imageVersion=${IMAGE_VERSION}"
elif [ "${{ steps.image-type.outputs.imageType }}" = "debug" ]; then echo "imageName=ref/${REF}/stream/${STREAM}/${IMAGE_VERSION}"
echo "imageVersionUid=debug-${{ steps.version.outputs.pseudoVersion }}" >> "$GITHUB_OUTPUT" echo "imageApiBasePath=constellation/v1/ref/${REF}/stream/${STREAM}/image/${IMAGE_VERSION}"
} >> "$GITHUB_OUTPUT"
if [[ "${REF}" = "-" ]] && [[ "${stream}" = "stable" ]]; then
echo "imageNameShort=${IMAGE_VERSION}" >> "$GITHUB_OUTPUT"
elif [[ "${REF}" = "-" ]]; then
echo "imageNameShort=stream/${STREAM}/${IMAGE_VERSION}" >> "$GITHUB_OUTPUT"
else else
echo "imageVersionUid=${{ steps.version.outputs.branchName }}-${{ steps.version.outputs.pseudoVersion }}" >> "$GITHUB_OUTPUT" echo "imageNameShort=ref/${REF}/stream/${STREAM}/${IMAGE_VERSION}" >> "$GITHUB_OUTPUT"
fi fi
make-os-image: make-os-image:
@ -202,7 +240,7 @@ jobs:
env: env:
BOOTSTRAPPER_BINARY: ${{ github.workspace }}/build/bootstrapper BOOTSTRAPPER_BINARY: ${{ github.workspace }}/build/bootstrapper
DISK_MAPPER_BINARY: ${{ github.workspace }}/build/disk-mapper DISK_MAPPER_BINARY: ${{ github.workspace }}/build/disk-mapper
IMAGE_VERSION: ${{ needs.build-settings.outputs.imageVersionUid }} IMAGE_VERSION: ${{ needs.build-settings.outputs.imageVersion }}
CSP: ${{ matrix.csp }} CSP: ${{ matrix.csp }}
- name: Collect hashes - name: Collect hashes
@ -288,9 +326,11 @@ jobs:
csp: ${{ matrix.csp }} csp: ${{ matrix.csp }}
uploadVariant: ${{ matrix.upload-variant }} uploadVariant: ${{ matrix.upload-variant }}
basePath: ${{ github.workspace }}/image basePath: ${{ github.workspace }}/image
imageVersion: ${{ inputs.imageVersion }} ref: ${{ needs.build-settings.outputs.ref }}
stream: ${{ inputs.stream }}
imageVersion: ${{ needs.build-settings.outputs.imageVersion }}
imageType: ${{ needs.build-settings.outputs.imageType }} imageType: ${{ needs.build-settings.outputs.imageType }}
debug: ${{ inputs.debug }} debug: ${{ needs.build-settings.outputs.imageType == 'debug' }}
- name: Install tools - name: Install tools
shell: bash shell: bash
@ -308,7 +348,7 @@ jobs:
# on AWS, login is required to upload the image as AMI # on AWS, login is required to upload the image as AMI
# on Azure, login is done to download the VMGS from S3 # on Azure, login is done to download the VMGS from S3
# on QEMU, login is done to upload the image to S3 # on QEMU, login is done to upload the image to S3
if: ${{ matrix.csp == 'aws' || matrix.csp == 'azure' || matrix.csp == 'qemu' }} if: matrix.csp == 'aws' || matrix.csp == 'azure' || matrix.csp == 'qemu'
with: with:
role-to-assume: arn:aws:iam::795746500882:role/GitHubConstellationImagePipeline role-to-assume: arn:aws:iam::795746500882:role/GitHubConstellationImagePipeline
aws-region: eu-central-1 aws-region: eu-central-1
@ -321,7 +361,7 @@ jobs:
- name: Login to GCP - name: Login to GCP
uses: ./.github/actions/login_gcp uses: ./.github/actions/login_gcp
if: ${{ matrix.csp == 'gcp' }} if: matrix.csp == 'gcp'
with: with:
gcp_service_account_json: ${{ secrets.GCP_IMAGE_UPLOAD_SERVICE_ACCOUNT }} gcp_service_account_json: ${{ secrets.GCP_IMAGE_UPLOAD_SERVICE_ACCOUNT }}
@ -340,7 +380,7 @@ jobs:
"${PKI_SET}/${AZURE_SECURITY_TYPE}.vmgs" \ "${PKI_SET}/${AZURE_SECURITY_TYPE}.vmgs" \
--no-progress --no-progress
working-directory: ${{ github.workspace }}/image working-directory: ${{ github.workspace }}/image
if: ${{ matrix.csp == 'azure' && !endsWith(env.AZURE_SECURITY_TYPE, 'Supported') }} if: matrix.csp == 'azure' && !endsWith(env.AZURE_SECURITY_TYPE, 'Supported')
env: env:
PKI_SET: ${{ needs.build-settings.outputs.pkiSet }} PKI_SET: ${{ needs.build-settings.outputs.pkiSet }}
AZURE_VMGS_REGION: ${{ steps.vars.outputs.azureVmgsRegion }} AZURE_VMGS_REGION: ${{ steps.vars.outputs.azureVmgsRegion }}
@ -355,7 +395,7 @@ jobs:
echo -e "Uploaded AWS image: \n\n\`\`\`\n$(jq < "${AWS_JSON_OUTPUT}")\n\`\`\`\n" >> "$GITHUB_STEP_SUMMARY" echo -e "Uploaded AWS image: \n\n\`\`\`\n$(jq < "${AWS_JSON_OUTPUT}")\n\`\`\`\n" >> "$GITHUB_STEP_SUMMARY"
echo "::endgroup::" echo "::endgroup::"
working-directory: ${{ github.workspace }}/image working-directory: ${{ github.workspace }}/image
if: ${{ matrix.csp == 'aws' }} if: matrix.csp == 'aws'
env: env:
PKI: ${{ github.workspace }}/image/pki PKI: ${{ github.workspace }}/image/pki
AWS_JSON_OUTPUT: ${{ steps.vars.outputs.awsJsonOutput }} AWS_JSON_OUTPUT: ${{ steps.vars.outputs.awsJsonOutput }}
@ -377,7 +417,7 @@ jobs:
echo -e "Uploaded GCP image: \n\n\`\`\`\n$(jq < "${GCP_JSON_OUTPUT}")\n\`\`\`\n" >> "$GITHUB_STEP_SUMMARY" echo -e "Uploaded GCP image: \n\n\`\`\`\n$(jq < "${GCP_JSON_OUTPUT}")\n\`\`\`\n" >> "$GITHUB_STEP_SUMMARY"
echo "::endgroup::" echo "::endgroup::"
working-directory: ${{ github.workspace }}/image working-directory: ${{ github.workspace }}/image
if: ${{ matrix.csp == 'gcp' }} if: matrix.csp == 'gcp'
env: env:
PKI: ${{ github.workspace }}/image/pki PKI: ${{ github.workspace }}/image/pki
GCP_JSON_OUTPUT: ${{ steps.vars.outputs.gcpJsonOutput }} GCP_JSON_OUTPUT: ${{ steps.vars.outputs.gcpJsonOutput }}
@ -399,7 +439,7 @@ jobs:
echo -e "Uploaded Azure ${AZURE_SECURITY_TYPE} image: \n\n\`\`\`\n$(jq < "${AZURE_JSON_OUTPUT}")\n\`\`\`\n" >> "$GITHUB_STEP_SUMMARY" echo -e "Uploaded Azure ${AZURE_SECURITY_TYPE} image: \n\n\`\`\`\n$(jq < "${AZURE_JSON_OUTPUT}")\n\`\`\`\n" >> "$GITHUB_STEP_SUMMARY"
echo "::endgroup::" echo "::endgroup::"
working-directory: ${{ github.workspace }}/image working-directory: ${{ github.workspace }}/image
if: ${{ matrix.csp == 'azure' }} if: matrix.csp == 'azure'
env: env:
PKI: ${{ github.workspace }}/image/pki PKI: ${{ github.workspace }}/image/pki
AZURE_JSON_OUTPUT: ${{ steps.vars.outputs.azureJsonOutput }} AZURE_JSON_OUTPUT: ${{ steps.vars.outputs.azureJsonOutput }}
@ -421,7 +461,7 @@ jobs:
- name: Upload QEMU image - name: Upload QEMU image
shell: bash shell: bash
if: ${{ matrix.csp == 'qemu' }} if: matrix.csp == 'qemu'
run: | run: |
echo "::group::Upload QEMU image" echo "::group::Upload QEMU image"
upload/upload_qemu.sh upload/upload_qemu.sh
@ -433,7 +473,9 @@ jobs:
QEMU_BUCKET: ${{ steps.vars.outputs.qemuBucket }} QEMU_BUCKET: ${{ steps.vars.outputs.qemuBucket }}
QEMU_BASE_URL: ${{ steps.vars.outputs.qemuBaseUrl }} QEMU_BASE_URL: ${{ steps.vars.outputs.qemuBaseUrl }}
QEMU_IMAGE_PATH: ${{ steps.vars.outputs.qemuImagePath }} QEMU_IMAGE_PATH: ${{ steps.vars.outputs.qemuImagePath }}
IMAGE_VERSION_UID: ${{ needs.build-settings.outputs.imageVersionUid }} REF: ${{needs.build-settings.outputs.ref }}
STREAM: ${{inputs.stream }}
IMAGE_VERSION: ${{needs.build-settings.outputs.imageVersion }}
- name: Upload image lookup table as artifact - name: Upload image lookup table as artifact
uses: actions/upload-artifact@83fd05a356d7e2593de66fc9913b3002723633cb # tag=v3.1.1 uses: actions/upload-artifact@83fd05a356d7e2593de66fc9913b3002723633cb # tag=v3.1.1
@ -501,7 +543,7 @@ jobs:
run: | run: |
aws s3 cp \ aws s3 cp \
"pcrs-${{ matrix.csp }}.json" \ "pcrs-${{ matrix.csp }}.json" \
"s3://cdn-constellation-backend/constellation/v1/measurements/${{ needs.build-settings.outputs.imageVersionUid }}/${{ matrix.csp }}/measurements.image.json" \ "s3://cdn-constellation-backend/${{needs.build-settings.outputs.imageApiBasePath}}/csp/${{ matrix.csp }}/measurements.image.json" \
--no-progress --no-progress
generate-sbom: generate-sbom:
@ -616,7 +658,7 @@ jobs:
for file in ${sboms} ${manifests} ${hashes}; do for file in ${sboms} ${manifests} ${hashes}; do
aws s3 cp \ aws s3 cp \
"${file}" \ "${file}" \
"s3://cdn-constellation-backend/constellation/v1/sbom/${{ needs.build-settings.outputs.imageVersionUid }}/${file}" \ "s3://cdn-constellation-backend/${{needs.build-settings.outputs.imageApiBasePath}}/${file}" \
--no-progress --no-progress
done done
@ -633,15 +675,24 @@ jobs:
with: with:
name: lookup-table name: lookup-table
- name: Login to AWS
uses: aws-actions/configure-aws-credentials@67fbcbb121271f7775d2e7715933280b06314838 # tag=v1.7.0
with:
role-to-assume: arn:aws:iam::795746500882:role/GitHubConstellationImagePipeline
aws-region: eu-central-1
- name: Combine lookup tables for CSPs - name: Combine lookup tables for CSPs
shell: bash shell: bash
run: | run: |
echo '{}' > intermediate.json echo '{}' > intermediate.json
jq '.version = "${{ needs.build-settings.outputs.imageVersionUid }}"' intermediate.json > lookup-table.json jq '.ref = "${{ needs.build-settings.outputs.ref }}"' intermediate.json > lookup-table.json
cp lookup-table.json intermediate.json cp lookup-table.json intermediate.json
jq '.debug = ${{ inputs.debug }}' intermediate.json > lookup-table.json jq '.stream = "${{ inputs.stream }}"' intermediate.json > lookup-table.json
cp lookup-table.json intermediate.json
jq '.version = "${{ needs.build-settings.outputs.imageVersion }}"' intermediate.json > lookup-table.json
cp lookup-table.json intermediate.json cp lookup-table.json intermediate.json
for lut in mkosi.output.*/*/image-upload*.json; do for lut in mkosi.output.*/*/image-upload*.json; do
@ -651,25 +702,38 @@ jobs:
rm -f intermediate.json rm -f intermediate.json
mv lookup-table.json "${{ needs.build-settings.outputs.imageVersionUid }}.json"
- name: Login to AWS
uses: aws-actions/configure-aws-credentials@67fbcbb121271f7775d2e7715933280b06314838 # tag=v1.7.0
with:
role-to-assume: arn:aws:iam::795746500882:role/GitHubConstellationImagePipeline
aws-region: eu-central-1
- name: Upload lookup table to S3 - name: Upload lookup table to S3
shell: bash shell: bash
run: | run: |
aws s3 cp \ aws s3 cp \
"${{ needs.build-settings.outputs.imageVersionUid }}.json" \ "lookup-table.json" \
"s3://cdn-constellation-backend/constellation/v1/images/${{ needs.build-settings.outputs.imageVersionUid }}.json" \ "s3://cdn-constellation-backend/${{ needs.build-settings.outputs.imageApiBasePath }}/info.json" \
--no-progress --no-progress
( (
echo -e "Image version ([Lookup table](https://cdn.confidential.cloud/constellation/v1/images/${{ needs.build-settings.outputs.imageVersionUid }}.json)):" echo -e "Image version ([Lookup table](https://cdn.confidential.cloud/${{ needs.build-settings.outputs.imageApiBasePath }}/info.json)):"
echo echo
echo -e "\`\`\`" echo -e "\`\`\`"
echo "${{ needs.build-settings.outputs.imageVersionUid }}" echo "${{ needs.build-settings.outputs.imageNameShort }}"
echo -e "\`\`\`" echo -e "\`\`\`"
) >> "$GITHUB_STEP_SUMMARY" ) >> "$GITHUB_STEP_SUMMARY"
- name: Checkout
uses: actions/checkout@93ea575cb5d8a053eaa0ac8fa3b40d7e05a33cc8 # v3.1.0
with:
ref: ${{ github.head_ref }}
- name: Setup Go environment
uses: actions/setup-go@d0a58c1c4d2b25278816e339b944508c875f3613 # v3.4.0
with:
go-version: "1.19.4"
cache: true
- name: Update list of available OS image versions
if: needs.build-settings.outputs.ref != '-'
run: |
go run main.go \
--version "${{ needs.build-settings.outputs.imageVersion }}" \
--stream "${{ inputs.stream }}" \
--ref "${{ needs.build-settings.outputs.ref }}" \
--latest
working-directory: hack/add-version

View File

@ -30,7 +30,8 @@ jobs:
id: find-latest-image id: find-latest-image
uses: ./.github/actions/find_latest_image uses: ./.github/actions/find_latest_image
with: with:
imageType: debug ref: main
stream: debug
e2e-daily: e2e-daily:
strategy: strategy:

View File

@ -40,10 +40,10 @@ on:
default: false default: false
required: false required: false
osImage: osImage:
description: "Full name of OS image (CSP independent image version UID)." description: "Full name of OS image (CSP independent image version UID). Leave empty for latest debug image on main."
type: string type: string
default: "main latest" default: ""
required: true required: false
isDebugImage: isDebugImage:
description: "Is OS image a debug image?" description: "Is OS image a debug image?"
type: boolean type: boolean
@ -63,7 +63,7 @@ env:
jobs: jobs:
find-latest-image: find-latest-image:
name: Find latest debug image name: Select image
runs-on: ubuntu-22.04 runs-on: ubuntu-22.04
permissions: permissions:
id-token: write id-token: write
@ -75,7 +75,7 @@ jobs:
id: check-input id: check-input
shell: bash shell: bash
run: | run: |
if [[ "${{ inputs.osImage }}" == "main latest" ]]; then if [[ -z "${{ inputs.osImage }}" ]]; then
echo "Using latest debug image from main." echo "Using latest debug image from main."
else else
echo "Using image '${{ inputs.osImage }}'." echo "Using image '${{ inputs.osImage }}'."
@ -90,9 +90,11 @@ jobs:
- name: Find latest image - name: Find latest image
id: find-latest-image id: find-latest-image
if: steps.check-input.outputs.image == ''
uses: ./.github/actions/find_latest_image uses: ./.github/actions/find_latest_image
with: with:
imageType: debug ref: main
stream: debug
e2e-test-manual: e2e-test-manual:
runs-on: ubuntu-22.04 runs-on: ubuntu-22.04

View File

@ -30,7 +30,8 @@ jobs:
id: find-latest-image id: find-latest-image
uses: ./.github/actions/find_latest_image uses: ./.github/actions/find_latest_image
with: with:
imageType: debug ref: main
stream: debug
e2e-weekly: e2e-weekly:
strategy: strategy:

View File

@ -32,15 +32,25 @@ jobs:
ARM_CLIENT_SECRET: ${{ secrets.AZURE_E2E_CLIENT_SECRET }} ARM_CLIENT_SECRET: ${{ secrets.AZURE_E2E_CLIENT_SECRET }}
ARM_SUBSCRIPTION_ID: ${{ secrets.AZURE_E2E_SUBSCRIPTION_ID }} ARM_SUBSCRIPTION_ID: ${{ secrets.AZURE_E2E_SUBSCRIPTION_ID }}
ARM_TENANT_ID: ${{ secrets.AZURE_E2E_TENANT_ID }} ARM_TENANT_ID: ${{ secrets.AZURE_E2E_TENANT_ID }}
outputs:
ref: ${{ steps.extract.outputs.ref }}
stream: ${{ steps.extract.outputs.stream }}
version: ${{ steps.extract.outputs.version }}
steps: steps:
- name: Check out repository - name: Check out repository
uses: actions/checkout@93ea575cb5d8a053eaa0ac8fa3b40d7e05a33cc8 # tag=v3.1.0 uses: actions/checkout@93ea575cb5d8a053eaa0ac8fa3b40d7e05a33cc8 # tag=v3.1.0
with: with:
ref: ${{ github.head_ref }} ref: ${{ github.head_ref }}
- name: Extract ref, stream and version
id: extract
uses: ./.github/actions/shortname
with:
shortname: ${{ github.event.inputs.osImage }}
- name: Check if image definition from build pipeline exists - name: Check if image definition from build pipeline exists
run: | run: |
wget -O /dev/null https://cdn.confidential.cloud/constellation/v1/images/${{ github.event.inputs.osImage }}.json wget -O /dev/null "https://cdn.confidential.cloud/constellation/v1/ref/${{ steps.extract.outputs.ref }}/stream/${{ steps.extract.outputs.stream }}/image/${{ steps.extract.outputs.version }}/info.json"
shell: bash shell: bash
- name: Setup Go environment - name: Setup Go environment
@ -207,10 +217,15 @@ jobs:
- name: Download expected measurements from build pipeline for image - name: Download expected measurements from build pipeline for image
run: | run: |
path="constellation/v1/ref/${ref}/stream/${stream}/image/${version}/csp/${{ matrix.provider }}/measurements.image.json"
mkdir -p ${{ github.workspace }}/expected-measurements mkdir -p ${{ github.workspace }}/expected-measurements
wget -O ${{ github.workspace }}/expected-measurements/measurements.image.json https://cdn.confidential.cloud/constellation/v1/measurements/${{ github.event.inputs.osImage }}/${{ matrix.provider }}/measurements.image.json wget -O ${{ github.workspace }}/expected-measurements/measurements.image.json "https://cdn.confidential.cloud/${path}"
cat ${{ github.workspace }}/expected-measurements/measurements.image.json cat ${{ github.workspace }}/expected-measurements/measurements.image.json
shell: bash shell: bash
env:
ref: ${{ needs.calculate-measurements-on-csp.outputs.ref }}
stream: ${{ needs.calculate-measurements-on-csp.outputs.stream }}
version: ${{ needs.calculate-measurements-on-csp.outputs.version }}
- name: Check if expected measurements == actual measurements from running cluster - name: Check if expected measurements == actual measurements from running cluster
run: | run: |
@ -304,7 +319,7 @@ jobs:
- name: Upload to S3 - name: Upload to S3
run: | run: |
S3_PATH=s3://cdn-constellation-backend/constellation/v1/measurements/${IMAGE_UID,,}/${{ matrix.provider }} S3_PATH=s3://cdn-constellation-backend/constellation/v1/ref/${ref}/stream/${stream}/image/${version}/csp/${{ matrix.provider }}
aws s3 cp "${{ github.workspace }}/generated-measurements/measurements-${{ matrix.provider }}.json" "${S3_PATH}/measurements.json" aws s3 cp "${{ github.workspace }}/generated-measurements/measurements-${{ matrix.provider }}.json" "${S3_PATH}/measurements.json"
aws s3 cp "${{ github.workspace }}/generated-measurements/measurements-${{ matrix.provider }}.json.sig" "${S3_PATH}/measurements.json.sig" aws s3 cp "${{ github.workspace }}/generated-measurements/measurements-${{ matrix.provider }}.json.sig" "${S3_PATH}/measurements.json.sig"
shell: bash shell: bash
@ -312,3 +327,6 @@ jobs:
IMAGE_UID: ${{ inputs.osImage }} IMAGE_UID: ${{ inputs.osImage }}
PUBLIC_BUCKET_NAME: ${{ secrets.PUBLIC_BUCKET_NAME }} PUBLIC_BUCKET_NAME: ${{ secrets.PUBLIC_BUCKET_NAME }}
CSP: ${{ matrix.provider }} CSP: ${{ matrix.provider }}
ref: ${{ needs.calculate-measurements-on-csp.outputs.ref }}
stream: ${{ needs.calculate-measurements-on-csp.outputs.stream }}
version: ${{ needs.calculate-measurements-on-csp.outputs.version }}

View File

@ -8,6 +8,10 @@ on:
tag: tag:
description: "Semantic version tag of the release (vX.Y.Z)." description: "Semantic version tag of the release (vX.Y.Z)."
required: true required: true
latest:
description: "Whether to update the latest tag."
type: boolean
default: false
jobs: jobs:
update: update:
@ -35,5 +39,10 @@ jobs:
- name: Update OS images - name: Update OS images
run: | run: |
go run main.go --version "${{ github.event.release.tag_name }}${{ github.event.inputs.tag }}" latest=$( [[ "${{ inputs.latest }}" = "true" ]] && echo "--latest")
go run main.go \
--version "${{ github.event.release.tag_name }}${{ github.event.inputs.tag }}" \
--stream stable \
--release \
"${latest}"
working-directory: hack/add-version working-directory: hack/add-version

View File

@ -36,7 +36,7 @@ jobs:
- name: Check if action branch exists - name: Check if action branch exists
run: | run: |
ex="$(git ls-remote --heads origin action/constellation/update-cli-reference)" ex="$(git ls-remote --heads origin action/constellation/update-cli-reference)"
echo "EXISTS=$(if [ -z "$ex" ]; then echo 0; else echo 1; fi)" >> "$GITHUB_ENV" echo "EXISTS=$(if [[ -z "$ex" ]]; then echo 0; else echo 1; fi)" >> "$GITHUB_ENV"
- name: Publish new reference (create new branch) - name: Publish new reference (create new branch)
if: ${{ env.EXISTS == 0 }} if: ${{ env.EXISTS == 0 }}

View File

@ -20,6 +20,7 @@ import (
"github.com/edgelesssys/constellation/v2/internal/config" "github.com/edgelesssys/constellation/v2/internal/config"
"github.com/edgelesssys/constellation/v2/internal/constants" "github.com/edgelesssys/constellation/v2/internal/constants"
"github.com/edgelesssys/constellation/v2/internal/file" "github.com/edgelesssys/constellation/v2/internal/file"
"github.com/edgelesssys/constellation/v2/internal/shortname"
"github.com/edgelesssys/constellation/v2/internal/sigstore" "github.com/edgelesssys/constellation/v2/internal/sigstore"
"github.com/spf13/afero" "github.com/spf13/afero"
"github.com/spf13/cobra" "github.com/spf13/cobra"
@ -163,11 +164,14 @@ func (f *fetchMeasurementsFlags) updateURLs(conf *config.Config) error {
} }
func measurementURL(provider cloudprovider.Provider, image, file string) (*url.URL, error) { func measurementURL(provider cloudprovider.Provider, image, file string) (*url.URL, error) {
ref, stream, version, err := shortname.ToParts(image)
if err != nil {
return nil, fmt.Errorf("parsing image name: %w", err)
}
url, err := url.Parse(constants.CDNRepositoryURL) url, err := url.Parse(constants.CDNRepositoryURL)
if err != nil { if err != nil {
return nil, fmt.Errorf("parsing image version repository URL: %w", err) return nil, fmt.Errorf("parsing image version repository URL: %w", err)
} }
url.Path = path.Join(constants.CDNMeasurementsPath, image, strings.ToLower(provider.String()), file) url.Path = path.Join(constants.CDNAPIPrefix, "ref", ref, "stream", stream, "image", version, "csp", strings.ToLower(provider.String()), file)
return url, nil return url, nil
} }

View File

@ -109,8 +109,8 @@ func TestUpdateURLs(t *testing.T) {
}, },
}, },
flags: &fetchMeasurementsFlags{}, flags: &fetchMeasurementsFlags{},
wantMeasurementsURL: constants.CDNRepositoryURL + "/" + constants.CDNMeasurementsPath + "/someImageVersion/gcp/measurements.json", wantMeasurementsURL: constants.CDNRepositoryURL + "/" + constants.CDNAPIPrefix + "/ref/-/stream/stable/image/someImageVersion/csp/gcp/measurements.json",
wantMeasurementsSigURL: constants.CDNRepositoryURL + "/" + constants.CDNMeasurementsPath + "/someImageVersion/gcp/measurements.json.sig", wantMeasurementsSigURL: constants.CDNRepositoryURL + "/" + constants.CDNAPIPrefix + "/ref/-/stream/stable/image/someImageVersion/csp/gcp/measurements.json.sig",
}, },
"both set by user": { "both set by user": {
conf: &config.Config{}, conf: &config.Config{},
@ -185,14 +185,14 @@ func TestConfigFetchMeasurements(t *testing.T) {
signature := "MEYCIQDRAQNK2NjHJBGrnw3HQAyBsXMCmVCptBdgA6VZ3IlyiAIhAPG42waF1aFZq7dnjP3b2jsMNUtaKYDQQSazW1AX8jgF" signature := "MEYCIQDRAQNK2NjHJBGrnw3HQAyBsXMCmVCptBdgA6VZ3IlyiAIhAPG42waF1aFZq7dnjP3b2jsMNUtaKYDQQSazW1AX8jgF"
client := newTestClient(func(req *http.Request) *http.Response { client := newTestClient(func(req *http.Request) *http.Response {
if req.URL.String() == "https://cdn.confidential.cloud/constellation/v1/measurements/v999.999.999/gcp/measurements.json" { if req.URL.Path == "/constellation/v1/ref/-/stream/stable/image/v999.999.999/csp/gcp/measurements.json" {
return &http.Response{ return &http.Response{
StatusCode: http.StatusOK, StatusCode: http.StatusOK,
Body: io.NopCloser(bytes.NewBufferString(measurements)), Body: io.NopCloser(bytes.NewBufferString(measurements)),
Header: make(http.Header), Header: make(http.Header),
} }
} }
if req.URL.String() == "https://cdn.confidential.cloud/constellation/v1/measurements/v999.999.999/gcp/measurements.json.sig" { if req.URL.Path == "/constellation/v1/ref/-/stream/stable/image/v999.999.999/csp/gcp/measurements.json.sig" {
return &http.Response{ return &http.Response{
StatusCode: http.StatusOK, StatusCode: http.StatusOK,
Body: io.NopCloser(bytes.NewBufferString(signature)), Body: io.NopCloser(bytes.NewBufferString(signature)),

View File

@ -11,8 +11,6 @@ import (
"fmt" "fmt"
"io" "io"
"net/http" "net/http"
"net/url"
"path"
"strings" "strings"
"github.com/edgelesssys/constellation/v2/cli/internal/cloudcmd" "github.com/edgelesssys/constellation/v2/cli/internal/cloudcmd"
@ -175,12 +173,12 @@ func getCompatibleImageMeasurements(ctx context.Context, cmd *cobra.Command, cli
) (map[string]config.UpgradeConfig, error) { ) (map[string]config.UpgradeConfig, error) {
upgrades := make(map[string]config.UpgradeConfig) upgrades := make(map[string]config.UpgradeConfig)
for _, img := range images { for _, img := range images {
measurementsURL, err := url.Parse(constants.CDNRepositoryURL + path.Join("/", constants.CDNMeasurementsPath, img, strings.ToLower(csp.String()), "measurements.json")) measurementsURL, err := measurementURL(csp, img, "measurements.json")
if err != nil { if err != nil {
return nil, err return nil, err
} }
signatureURL, err := url.Parse(constants.CDNRepositoryURL + path.Join("/", constants.CDNMeasurementsPath, img, strings.ToLower(csp.String()), "measurements.json.sig")) signatureURL, err := measurementURL(csp, img, "measurements.json.sig")
if err != nil { if err != nil {
return nil, err return nil, err
} }

View File

@ -22,7 +22,7 @@ With `cdbg` and `yq` installed in your path:
1. Run `constellation config generate` to create a new default configuration 1. Run `constellation config generate` to create a new default configuration
2. Locate the latest debugd images by running `hack/find-image/find-image.sh` 2. Locate the latest debugd images by running `hack/find-image/find-image.sh --ref main --stream debug`
3. Modify the `constellation-conf.yaml` to use an image with the debugd already included and add required firewall rules: 3. Modify the `constellation-conf.yaml` to use an image with the debugd already included and add required firewall rules:
@ -83,7 +83,3 @@ For QEMU, the credentials for Opensearch must be parsed via the info flag as wel
Remember to use single quotes for the password. Remember to use single quotes for the password.
You will also need to increase the memory size of QEMU to 4GB. You will also need to increase the memory size of QEMU to 4GB.
### debugd images
For a full list of image naming conventions and how to retreive them check [image version documentation](/.github/docs/README.md#image-versions)

View File

@ -8,19 +8,55 @@
set -euo pipefail set -euo pipefail
shopt -s inherit_errexit shopt -s inherit_errexit
ref="-"
stream="stable"
POSITIONAL_ARGS=()
while [[ $# -gt 0 ]]; do
case $1 in
-r | --ref)
ref="$2"
shift # past argument
shift # past value
;;
-s | --stream)
stream="$2"
shift # past argument
shift # past value
;;
-*)
echo "Unknown option $1"
exit 1
;;
*)
POSITIONAL_ARGS+=("$1") # save positional arg
shift # past argument
;;
esac
done
set -- "${POSITIONAL_ARGS[@]}" # restore positional parameters
ref=$(echo -n "${ref}" | tr -c '[:alnum:]' '-')
base_url="https://cdn.confidential.cloud" base_url="https://cdn.confidential.cloud"
bucket="cdn-constellation-backend" latest_path="constellation/v1/ref/${ref}/stream/${stream}/versions/latest/image.json"
newest_debug_image_path=$(aws s3api list-objects-v2 \ latest_url="${base_url}/${latest_path}"
--output text \ latest_status=$(curl -s -o /dev/null -w "%{http_code}" "${latest_url}")
--bucket "${bucket}" \ if [[ ${latest_status} != "200" ]]; then
--prefix constellation/v1/images/debug-v \ echo "No image found for ref ${ref} and stream ${stream} (${latest_status})"
--query "reverse(sort_by(Contents, &LastModified))[0].Key") exit 1
fi
latest_version=$(curl -sL "${latest_url}" | jq -r '.version')
image_version_uid=$(basename "${newest_debug_image_path}" .json) shortname=""
url="${base_url}/${newest_debug_image_path}" if [[ ${ref} != "-" ]]; then
echo "Found image version UID:" shortname+="ref/${ref}/"
echo "${image_version_uid}" fi
if [[ ${stream} != "stable" ]]; then
shortname+="stream/${stream}/"
fi
shortname+="${latest_version}"
echo "Containing the following images:" echo "${shortname}"
echo "${url}"
curl -sL "${url}" | jq

View File

@ -245,7 +245,9 @@ upload/upload_azure.sh -g --disk-name "${AZURE_DISK_NAME}" "${AZURE_VMGS_PATH}"
```sh ```sh
# set these variables # set these variables
export IMAGE_VERSION_UID= # e.g. "test123" or "v2.1.0" export REF= # e.g. feat-xyz (branch name encoded with dashes)
export STREAM= # e.g. "nightly", "debug", "stable" (depends on the type of image and if it is a release)
export IMAGE_VERSION= # e.g. v2.1.0" or output of pseudo-version tool
export QEMU_BUCKET=cdn-constellation-backend export QEMU_BUCKET=cdn-constellation-backend
export QEMU_BASE_URL="https://cdn.confidential.cloud" export QEMU_BASE_URL="https://cdn.confidential.cloud"
export QEMU_IMAGE_PATH=${PWD}/mkosi.output.qemu/fedora~37/image.raw export QEMU_IMAGE_PATH=${PWD}/mkosi.output.qemu/fedora~37/image.raw

View File

@ -11,9 +11,10 @@ if [[ -z ${CONFIG_FILE-} ]] && [[ -f ${CONFIG_FILE-} ]]; then
. "${CONFIG_FILE}" . "${CONFIG_FILE}"
fi fi
aws s3 cp "${QEMU_IMAGE_PATH}" "s3://${QEMU_BUCKET}/constellation/v1/raw/${IMAGE_VERSION_UID}/qemu/image.raw" --no-progress path="constellation/v1/ref/${REF}/stream/${STREAM}/image/${IMAGE_VERSION}/csp/qemu/image.raw"
aws s3 cp "${QEMU_IMAGE_PATH}" "s3://${QEMU_BUCKET}/${path}" --no-progress
image_url="${QEMU_BASE_URL}/constellation/v1/raw/${IMAGE_VERSION_UID}/qemu/image.raw" image_url="${QEMU_BASE_URL}/${path}"
json=$(jq -ncS \ json=$(jq -ncS \
--arg image_url "${image_url}" \ --arg image_url "${image_url}" \

View File

@ -45,7 +45,7 @@ type Config struct {
Version string `yaml:"version" validate:"eq=v2"` Version string `yaml:"version" validate:"eq=v2"`
// description: | // description: |
// Machine image used to create Constellation nodes. // Machine image used to create Constellation nodes.
Image string `yaml:"image" validate:"required,safe_image"` Image string `yaml:"image" validate:"required"`
// description: | // description: |
// Size (in GB) of a node's disk to store the non-volatile state. // Size (in GB) of a node's disk to store the non-volatile state.
StateDiskSizeGB int `yaml:"stateDiskSizeGB" validate:"min=0"` StateDiskSizeGB int `yaml:"stateDiskSizeGB" validate:"min=0"`
@ -472,10 +472,6 @@ func (c *Config) Validate() error {
return err return err
} }
if err := validate.RegisterValidation("safe_image", validateImage); err != nil {
return err
}
// register custom validator with label supported_k8s_version to validate version based on available versionConfigs. // register custom validator with label supported_k8s_version to validate version based on available versionConfigs.
if err := validate.RegisterValidation("supported_k8s_version", validateK8sVersion); err != nil { if err := validate.RegisterValidation("supported_k8s_version", validateK8sVersion); err != nil {
return err return err

View File

@ -19,19 +19,6 @@ import (
"github.com/go-playground/validator/v10" "github.com/go-playground/validator/v10"
) )
func validateImage(fl validator.FieldLevel) bool {
image := fl.Field().String()
switch {
case image == "":
return false
case image == "..":
return false
case strings.Contains(image, "/"):
return false
}
return true
}
func validateK8sVersion(fl validator.FieldLevel) bool { func validateK8sVersion(fl validator.FieldLevel) bool {
return versions.IsSupportedK8sVersion(fl.Field().String()) return versions.IsSupportedK8sVersion(fl.Field().String())
} }

View File

@ -170,11 +170,7 @@ const (
// CDNRepositoryURL is the base URL of the Constellation CDN artifact repository. // CDNRepositoryURL is the base URL of the Constellation CDN artifact repository.
CDNRepositoryURL = "https://cdn.confidential.cloud" CDNRepositoryURL = "https://cdn.confidential.cloud"
// CDNImagePath is the default path to image references in the CDN repository. // CDNAPIPrefix is the prefix of the Constellation API.
CDNImagePath = "constellation/v1/images"
// CDNMeasurementsPath is the default path to image measurements in the CDN repository.
CDNMeasurementsPath = "constellation/v1/measurements"
// CDNAPIPrefix is the prefix for the Constellation CDN API.
CDNAPIPrefix = "constellation/v1" CDNAPIPrefix = "constellation/v1"
) )

View File

@ -15,15 +15,55 @@ import (
"net/url" "net/url"
"os" "os"
"path" "path"
"path/filepath"
"strings" "strings"
"github.com/edgelesssys/constellation/v2/internal/cloud/cloudprovider" "github.com/edgelesssys/constellation/v2/internal/cloud/cloudprovider"
"github.com/edgelesssys/constellation/v2/internal/config" "github.com/edgelesssys/constellation/v2/internal/config"
"github.com/edgelesssys/constellation/v2/internal/constants" "github.com/edgelesssys/constellation/v2/internal/constants"
"github.com/edgelesssys/constellation/v2/internal/shortname"
"github.com/spf13/afero" "github.com/spf13/afero"
) )
// imageName is a struct that describes a Constellation OS imageName name.
type imageName struct {
Ref string
Stream string
Version string
}
func newImageName(name string) (*imageName, error) {
ref, stream, version, err := shortname.ToParts(name)
if err != nil {
return nil, err
}
return &imageName{
Ref: ref,
Stream: stream,
Version: version,
}, nil
}
func (i *imageName) infoPath() string {
return path.Join(constants.CDNAPIPrefix, "ref", i.Ref, "stream", i.Stream, "image", i.Version, "info.json")
}
func (i *imageName) shortname() string {
return shortname.FromParts(i.Ref, i.Stream, i.Version)
}
// filename is the override file name for the image info file.
func (i *imageName) filename() string {
name := i.shortname()
// replace all non-alphanumeric characters with an underscore
name = strings.Map(func(r rune) rune {
if r >= 'a' && r <= 'z' || r >= 'A' && r <= 'Z' || r >= '0' && r <= '9' || r == '-' || r == '.' {
return r
}
return '_'
}, name)
return name + ".json"
}
// imageInfo is a lookup table for image references. // imageInfo is a lookup table for image references.
// //
// Example: // Example:
@ -106,14 +146,18 @@ func (f *Fetcher) FetchReference(ctx context.Context, config *config.Config) (st
if err != nil { if err != nil {
return "", err return "", err
} }
return f.fetch(ctx, provider, config.Image, variant) image, err := newImageName(config.Image)
if err != nil {
return "", err
}
return f.fetch(ctx, provider, image, variant)
} }
// fetch fetches the image reference for a given image version uid, CSP and image variant. // fetch fetches the image reference for a given image name, uid, CSP and image variant.
func (f *Fetcher) fetch(ctx context.Context, csp cloudprovider.Provider, version, variant string) (string, error) { func (f *Fetcher) fetch(ctx context.Context, csp cloudprovider.Provider, img *imageName, variant string) (string, error) {
raw, err := getFromFile(f.fs, version) raw, err := getFromFile(f.fs, img)
if err != nil && os.IsNotExist(err) { if err != nil && os.IsNotExist(err) {
raw, err = getFromURL(ctx, f.httpc, version) raw, err = getFromURL(ctx, f.httpc, img)
} }
if err != nil { if err != nil {
return "", fmt.Errorf("fetching image reference: %w", err) return "", fmt.Errorf("fetching image reference: %w", err)
@ -145,19 +189,17 @@ func variant(provider cloudprovider.Provider, config *config.Config) (string, er
} }
} }
func getFromFile(fs *afero.Afero, version string) ([]byte, error) { func getFromFile(fs *afero.Afero, img *imageName) ([]byte, error) {
version = filepath.Base(version) return fs.ReadFile(img.filename())
return fs.ReadFile(version + ".json")
} }
// getFromURL fetches the image lookup table from a URL. // getFromURL fetches the image lookup table from a URL.
func getFromURL(ctx context.Context, client httpc, version string) ([]byte, error) { func getFromURL(ctx context.Context, client httpc, img *imageName) ([]byte, error) {
url, err := url.Parse(constants.CDNRepositoryURL) url, err := url.Parse(constants.CDNRepositoryURL)
if err != nil { if err != nil {
return nil, fmt.Errorf("parsing image version repository URL: %w", err) return nil, fmt.Errorf("parsing image version repository URL: %w", err)
} }
versionFilename := path.Base(version) + ".json" url.Path = img.infoPath()
url.Path = path.Join(constants.CDNImagePath, versionFilename)
req, err := http.NewRequestWithContext(ctx, http.MethodGet, url.String(), http.NoBody) req, err := http.NewRequestWithContext(ctx, http.MethodGet, url.String(), http.NoBody)
if err != nil { if err != nil {
return nil, err return nil, err
@ -171,7 +213,7 @@ func getFromURL(ctx context.Context, client httpc, version string) ([]byte, erro
if resp.StatusCode != http.StatusOK { if resp.StatusCode != http.StatusOK {
switch resp.StatusCode { switch resp.StatusCode {
case http.StatusNotFound: case http.StatusNotFound:
return nil, fmt.Errorf("image %q does not exist", version) return nil, fmt.Errorf("image %q does not exist", img.shortname())
default: default:
return nil, fmt.Errorf("unexpected status code %d", resp.StatusCode) return nil, fmt.Errorf("unexpected status code %d", resp.StatusCode)
} }

View File

@ -160,9 +160,9 @@ func TestVariant(t *testing.T) {
} }
func TestFetchReference(t *testing.T) { func TestFetchReference(t *testing.T) {
imageVersionUID := "someImageVersionUID" img := "ref/abc/stream/nightly/v1.2.3"
client := newTestClient(func(req *http.Request) *http.Response { client := newTestClient(func(req *http.Request) *http.Response {
if strings.HasSuffix(req.URL.String(), "/constellation/v1/images/someImageVersionUID.json") { if strings.HasSuffix(req.URL.String(), "/constellation/v1/ref/abc/stream/nightly/image/v1.2.3/info.json") {
return &http.Response{ return &http.Response{
StatusCode: http.StatusOK, StatusCode: http.StatusOK,
Body: io.NopCloser(bytes.NewBufferString(lut)), Body: io.NopCloser(bytes.NewBufferString(lut)),
@ -183,27 +183,27 @@ func TestFetchReference(t *testing.T) {
wantErr bool wantErr bool
}{ }{
"reference fetched remotely": { "reference fetched remotely": {
config: &config.Config{Image: imageVersionUID, Provider: config.ProviderConfig{ config: &config.Config{Image: img, Provider: config.ProviderConfig{
QEMU: &config.QEMUConfig{}, QEMU: &config.QEMUConfig{},
}}, }},
wantReference: "someReference", wantReference: "someReference",
}, },
"reference fetched locally": { "reference fetched locally": {
config: &config.Config{Image: imageVersionUID, Provider: config.ProviderConfig{ config: &config.Config{Image: img, Provider: config.ProviderConfig{
QEMU: &config.QEMUConfig{}, QEMU: &config.QEMUConfig{},
}}, }},
overrideFile: `{"qemu":{"default":"localOverrideReference"}}`, overrideFile: `{"qemu":{"default":"localOverrideReference"}}`,
wantReference: "localOverrideReference", wantReference: "localOverrideReference",
}, },
"lut is invalid": { "lut is invalid": {
config: &config.Config{Image: imageVersionUID, Provider: config.ProviderConfig{ config: &config.Config{Image: img, Provider: config.ProviderConfig{
QEMU: &config.QEMUConfig{}, QEMU: &config.QEMUConfig{},
}}, }},
overrideFile: `{`, overrideFile: `{`,
wantErr: true, wantErr: true,
}, },
"image version does not exist": { "image version does not exist": {
config: &config.Config{Image: "nonExistingImageVersionUID", Provider: config.ProviderConfig{ config: &config.Config{Image: "nonExistingImageName", Provider: config.ProviderConfig{
QEMU: &config.QEMUConfig{}, QEMU: &config.QEMUConfig{},
}}, }},
wantErr: true, wantErr: true,
@ -221,7 +221,7 @@ func TestFetchReference(t *testing.T) {
fetcher := &Fetcher{ fetcher := &Fetcher{
httpc: client, httpc: client,
fs: newImageVersionStubFs(t, imageVersionUID, tc.overrideFile), fs: newImageVersionStubFs(t, img, tc.overrideFile),
} }
reference, err := fetcher.FetchReference(context.Background(), tc.config) reference, err := fetcher.FetchReference(context.Background(), tc.config)
if tc.wantErr { if tc.wantErr {
@ -256,10 +256,12 @@ func newTestClient(fn roundTripFunc) *http.Client {
} }
} }
func newImageVersionStubFs(t *testing.T, imageVersionUID string, overrideFile string) *afero.Afero { func newImageVersionStubFs(t *testing.T, image string, overrideFile string) *afero.Afero {
fs := afero.NewMemMapFs() fs := afero.NewMemMapFs()
img, err := newImageName(image)
must(t, err)
if overrideFile != "" { if overrideFile != "" {
must(t, afero.WriteFile(fs, imageVersionUID+".json", []byte(overrideFile), os.ModePerm)) must(t, afero.WriteFile(fs, img.filename(), []byte(overrideFile), os.ModePerm))
} }
return &afero.Afero{Fs: fs} return &afero.Afero{Fs: fs}
} }

View File

@ -36,12 +36,12 @@ func NewDownloader() *Downloader {
} }
// Download downloads the raw image from source. // Download downloads the raw image from source.
func (d *Downloader) Download(ctx context.Context, errWriter io.Writer, showBar bool, source, version string) (string, error) { func (d *Downloader) Download(ctx context.Context, errWriter io.Writer, showBar bool, source, imageName string) (string, error) {
url, err := url.Parse(source) url, err := url.Parse(source)
if err != nil { if err != nil {
return "", fmt.Errorf("parsing image source URL: %w", err) return "", fmt.Errorf("parsing image source URL: %w", err)
} }
version = filepath.Base(version) imageName = filepath.Base(imageName)
var partfile, destination string var partfile, destination string
switch url.Scheme { switch url.Scheme {
case "http", "https": case "http", "https":
@ -49,8 +49,8 @@ func (d *Downloader) Download(ctx context.Context, errWriter io.Writer, showBar
if err != nil { if err != nil {
return "", fmt.Errorf("getting current working directory: %w", err) return "", fmt.Errorf("getting current working directory: %w", err)
} }
partfile = filepath.Join(cwd, version+".raw.part") partfile = filepath.Join(cwd, imageName+".raw.part")
destination = filepath.Join(cwd, version+".raw") destination = filepath.Join(cwd, imageName+".raw")
case "file": case "file":
return url.Path, nil return url.Path, nil
default: default:

View File

@ -0,0 +1,55 @@
/*
Copyright (c) Edgeless Systems GmbH
SPDX-License-Identifier: AGPL-3.0-only
*/
package shortname
import (
"fmt"
"strings"
)
// ToParts splits an config shortname into its parts.
// The shortname is expected to be in the format of one of:
// ref/<ref>/stream/<stream>/<version>
// stream/<stream>/<version>
// version/<version>.
func ToParts(shortname string) (string, string, string, error) {
parts := strings.Split(shortname, "/")
ref := "-"
stream := "stable"
var version string
switch len(parts) {
case 1:
version = parts[0]
case 3:
if parts[0] != "stream" {
return "", "", "", fmt.Errorf("invalid shortname: expected \"stream/<stream>/<version>\", got %q", shortname)
}
stream = parts[1]
version = parts[2]
case 5:
if parts[0] != "ref" || parts[2] != "stream" {
return "", "", "", fmt.Errorf("invalid shortname: expected \"ref/<ref>/stream/<stream>/<version>\", got %q", shortname)
}
ref = parts[1]
stream = parts[3]
version = parts[4]
default:
return "", "", "", fmt.Errorf("invalid shortname reference %q", shortname)
}
return ref, stream, version, nil
}
// FromParts joins the parts of a config shortname into a shortname.
func FromParts(ref, stream, version string) string {
switch {
case ref == "-" && stream == "stable":
return version
case ref == "-":
return "stream/" + stream + "/" + version
}
return "ref/" + ref + "/stream/" + stream + "/" + version
}

View File

@ -0,0 +1,102 @@
/*
Copyright (c) Edgeless Systems GmbH
SPDX-License-Identifier: AGPL-3.0-only
*/
package shortname
import (
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"go.uber.org/goleak"
)
func TestMain(m *testing.M) {
goleak.VerifyTestMain(m)
}
func TestToParts(t *testing.T) {
testCases := map[string]struct {
shortname string
wantParts [3]string
wantErr bool
}{
"only version": {
shortname: "v1.2.3",
wantParts: [3]string{"-", "stable", "v1.2.3"},
},
"stream and version": {
shortname: "stream/nightly/v1.2.3",
wantParts: [3]string{"-", "nightly", "v1.2.3"},
},
"full name": {
shortname: "ref/feat-xyz/stream/nightly/v1.2.3",
wantParts: [3]string{"feat-xyz", "nightly", "v1.2.3"},
},
"full name with extra slashes": {
shortname: "ref/feat-xyz//stream/nightly/v1.2.3",
wantErr: true,
},
"invalid three part path": {
shortname: "invalid/invalid/invalid",
wantErr: true,
},
"five part path": {
shortname: "invalid/invalid/invalid/invalid/invalid",
wantErr: true,
},
}
for name, tc := range testCases {
t.Run(name, func(t *testing.T) {
assert := assert.New(t)
require := require.New(t)
ref, stream, version, err := ToParts(tc.shortname)
if tc.wantErr {
assert.Error(err)
return
}
require.NoError(err)
assert.Equal(tc.wantParts, [3]string{ref, stream, version})
})
}
}
func TestFromParts(t *testing.T) {
testCases := map[string]struct {
ref, stream, version string
wantShortname string
}{
"only version": {
ref: "-",
stream: "stable",
version: "v1.2.3",
wantShortname: "v1.2.3",
},
"stream and version": {
ref: "-",
stream: "nightly",
version: "v1.2.3",
wantShortname: "stream/nightly/v1.2.3",
},
"full name": {
ref: "feat-xyz",
stream: "nightly",
version: "v1.2.3",
wantShortname: "ref/feat-xyz/stream/nightly/v1.2.3",
},
}
for name, tc := range testCases {
t.Run(name, func(t *testing.T) {
assert := assert.New(t)
gotShortname := FromParts(tc.ref, tc.stream, tc.version)
assert.Equal(tc.wantShortname, gotShortname)
})
}
}