mirror of
https://github.com/edgelesssys/constellation.git
synced 2025-04-26 01:59:20 -04:00
ci: call TCB upload step for AWS
This commit is contained in:
parent
257eb5370f
commit
46f563c7ca
30
.github/actions/constellation_destroy/action.yml
vendored
30
.github/actions/constellation_destroy/action.yml
vendored
@ -8,6 +8,15 @@ inputs:
|
|||||||
selfManagedInfra:
|
selfManagedInfra:
|
||||||
description: "Use self-managed infrastructure instead of infrastructure created by the Constellation CLI."
|
description: "Use self-managed infrastructure instead of infrastructure created by the Constellation CLI."
|
||||||
required: true
|
required: true
|
||||||
|
gcpClusterDeleteServiceAccount:
|
||||||
|
description: "Service account with permissions to delete a Constellation cluster on GCP."
|
||||||
|
required: true
|
||||||
|
azureClusterDeleteCredentials:
|
||||||
|
description: "Azure credentials authorized to delete a Constellation cluster."
|
||||||
|
required: true
|
||||||
|
cloudProvider:
|
||||||
|
description: "Either 'aws', 'azure' or 'gcp'."
|
||||||
|
required: true
|
||||||
|
|
||||||
runs:
|
runs:
|
||||||
using: "composite"
|
using: "composite"
|
||||||
@ -41,6 +50,27 @@ runs:
|
|||||||
fi
|
fi
|
||||||
echo "::endgroup::"
|
echo "::endgroup::"
|
||||||
|
|
||||||
|
- name: Login to GCP (Cluster service account)
|
||||||
|
if: inputs.cloudProvider == 'gcp'
|
||||||
|
uses: ./.github/actions/login_gcp
|
||||||
|
with:
|
||||||
|
service_account: ${{ inputs.gcpClusterDeleteServiceAccount }}
|
||||||
|
|
||||||
|
- name: Login to AWS (Cluster role)
|
||||||
|
if: inputs.cloudProvider == 'aws'
|
||||||
|
uses: aws-actions/configure-aws-credentials@010d0da01d0b5a38af31e9c3470dbfdabdecca3a # v4.0.1
|
||||||
|
with:
|
||||||
|
role-to-assume: arn:aws:iam::795746500882:role/GithubActionsE2ECluster
|
||||||
|
aws-region: eu-central-1
|
||||||
|
# extend token expiry to 6 hours to ensure constellation can terminate
|
||||||
|
role-duration-seconds: 21600
|
||||||
|
|
||||||
|
- name: Login to Azure (Cluster service principal)
|
||||||
|
if: inputs.cloudProvider == 'azure'
|
||||||
|
uses: ./.github/actions/login_azure
|
||||||
|
with:
|
||||||
|
azure_credentials: ${{ inputs.azureClusterDeleteCredentials }}
|
||||||
|
|
||||||
- name: Constellation terminate
|
- name: Constellation terminate
|
||||||
if: inputs.selfManagedInfra != 'true'
|
if: inputs.selfManagedInfra != 'true'
|
||||||
shell: bash
|
shell: bash
|
||||||
|
@ -2,6 +2,9 @@ name: E2E Attestationconfig API Test
|
|||||||
description: "Test the attestationconfig CLI is functional."
|
description: "Test the attestationconfig CLI is functional."
|
||||||
|
|
||||||
inputs:
|
inputs:
|
||||||
|
csp:
|
||||||
|
description: "Cloud provider to run tests against"
|
||||||
|
default: "azure"
|
||||||
buildBuddyApiKey:
|
buildBuddyApiKey:
|
||||||
description: "BuildBuddy API key for caching Bazel artifacts"
|
description: "BuildBuddy API key for caching Bazel artifacts"
|
||||||
required: true
|
required: true
|
||||||
@ -33,4 +36,4 @@ runs:
|
|||||||
COSIGN_PRIVATE_KEY: ${{ inputs.cosignPrivateKey }}
|
COSIGN_PRIVATE_KEY: ${{ inputs.cosignPrivateKey }}
|
||||||
COSIGN_PASSWORD: ${{ inputs.cosignPassword }}
|
COSIGN_PASSWORD: ${{ inputs.cosignPassword }}
|
||||||
run: |
|
run: |
|
||||||
bazel run //internal/api/attestationconfigapi/cli:cli_e2e_test
|
bazel run //internal/api/attestationconfigapi/cli:cli_e2e_test -- ${{ inputs.csp }}
|
||||||
|
15
.github/actions/e2e_verify/action.yml
vendored
15
.github/actions/e2e_verify/action.yml
vendored
@ -66,8 +66,8 @@ runs:
|
|||||||
forwarderPID=$!
|
forwarderPID=$!
|
||||||
sleep 5
|
sleep 5
|
||||||
|
|
||||||
if [[ ${{ inputs.cloudProvider }} == "azure" ]]; then
|
if [[ ${{ inputs.cloudProvider }} == "azure" || ${{ inputs.cloudProvider }} == "aws" ]]; then
|
||||||
echo "Extracting Azure TCB versions for API update"
|
echo "Extracting TCB versions for API update"
|
||||||
constellation verify --cluster-id "${clusterID}" --node-endpoint localhost:9090 -o json > "snp-report-${node}.json"
|
constellation verify --cluster-id "${clusterID}" --node-endpoint localhost:9090 -o json > "snp-report-${node}.json"
|
||||||
else
|
else
|
||||||
constellation verify --cluster-id "${clusterID}" --node-endpoint localhost:9090
|
constellation verify --cluster-id "${clusterID}" --node-endpoint localhost:9090
|
||||||
@ -84,14 +84,19 @@ runs:
|
|||||||
aws-region: eu-central-1
|
aws-region: eu-central-1
|
||||||
|
|
||||||
- name: Upload extracted TCBs
|
- name: Upload extracted TCBs
|
||||||
if: github.ref_name == 'main' && inputs.cloudProvider == 'azure'
|
if: github.ref_name == 'main' && (inputs.cloudProvider == 'azure' || inputs.cloudProvider == 'aws')
|
||||||
shell: bash
|
shell: bash
|
||||||
env:
|
env:
|
||||||
COSIGN_PASSWORD: ${{ inputs.cosignPassword }}
|
COSIGN_PASSWORD: ${{ inputs.cosignPassword }}
|
||||||
COSIGN_PRIVATE_KEY: ${{ inputs.cosignPrivateKey }}
|
COSIGN_PRIVATE_KEY: ${{ inputs.cosignPrivateKey }}
|
||||||
run: |
|
run: |
|
||||||
for file in $(ls snp-report-*.json); do
|
reports=(snp-report-*.json)
|
||||||
|
if [ -z ${#reports[@]} ]; then
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
for file in "${reports[@]}"; do
|
||||||
path=$(realpath "${file}")
|
path=$(realpath "${file}")
|
||||||
cat "${path}"
|
cat "${path}"
|
||||||
bazel run //internal/api/attestationconfigapi/cli -- upload azure snp-report "${path}"
|
bazel run //internal/api/attestationconfigapi/cli -- upload ${{ inputs.cloudProvider }} snp-report "${path}"
|
||||||
done
|
done
|
||||||
|
@ -16,6 +16,11 @@ on:
|
|||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
e2e-api:
|
e2e-api:
|
||||||
|
strategy:
|
||||||
|
fail-fast: false
|
||||||
|
max-parallel: 1
|
||||||
|
matrix:
|
||||||
|
csp: ["azure", "aws"]
|
||||||
runs-on: ubuntu-22.04
|
runs-on: ubuntu-22.04
|
||||||
permissions:
|
permissions:
|
||||||
id-token: write
|
id-token: write
|
||||||
@ -35,3 +40,4 @@ jobs:
|
|||||||
buildBuddyApiKey: ${{ secrets.BUILDBUDDY_ORG_API_KEY }}
|
buildBuddyApiKey: ${{ secrets.BUILDBUDDY_ORG_API_KEY }}
|
||||||
cosignPrivateKey: ${{ secrets.COSIGN_DEV_PRIVATE_KEY }}
|
cosignPrivateKey: ${{ secrets.COSIGN_DEV_PRIVATE_KEY }}
|
||||||
cosignPassword: ${{ secrets.COSIGN_DEV_PASSWORD }}
|
cosignPassword: ${{ secrets.COSIGN_DEV_PASSWORD }}
|
||||||
|
csp: ${{ matrix.csp }}
|
||||||
|
3
.github/workflows/e2e-test-daily.yml
vendored
3
.github/workflows/e2e-test-daily.yml
vendored
@ -99,6 +99,9 @@ jobs:
|
|||||||
with:
|
with:
|
||||||
kubeconfig: ${{ steps.e2e_test.outputs.kubeconfig }}
|
kubeconfig: ${{ steps.e2e_test.outputs.kubeconfig }}
|
||||||
selfManagedInfra: "false"
|
selfManagedInfra: "false"
|
||||||
|
cloudProvider: ${{ matrix.provider }}
|
||||||
|
azureClusterDeleteCredentials: ${{ secrets.AZURE_E2E_CLUSTER_CREDENTIALS }}
|
||||||
|
gcpClusterDeleteServiceAccount: "constellation-e2e-cluster@constellation-331613.iam.gserviceaccount.com"
|
||||||
|
|
||||||
- name: Always delete IAM configuration
|
- name: Always delete IAM configuration
|
||||||
if: always()
|
if: always()
|
||||||
|
3
.github/workflows/e2e-test-release.yml
vendored
3
.github/workflows/e2e-test-release.yml
vendored
@ -248,6 +248,9 @@ jobs:
|
|||||||
with:
|
with:
|
||||||
kubeconfig: ${{ steps.e2e_test.outputs.kubeconfig }}
|
kubeconfig: ${{ steps.e2e_test.outputs.kubeconfig }}
|
||||||
selfManagedInfra: ${{ matrix.selfManagedInfra == 'true' }}
|
selfManagedInfra: ${{ matrix.selfManagedInfra == 'true' }}
|
||||||
|
cloudProvider: ${{ matrix.provider }}
|
||||||
|
azureClusterDeleteCredentials: ${{ secrets.AZURE_E2E_CLUSTER_CREDENTIALS }}
|
||||||
|
gcpClusterDeleteServiceAccount: "constellation-e2e-cluster@constellation-331613.iam.gserviceaccount.com"
|
||||||
|
|
||||||
- name: Always delete IAM configuration
|
- name: Always delete IAM configuration
|
||||||
if: always()
|
if: always()
|
||||||
|
3
.github/workflows/e2e-test-weekly.yml
vendored
3
.github/workflows/e2e-test-weekly.yml
vendored
@ -267,6 +267,9 @@ jobs:
|
|||||||
with:
|
with:
|
||||||
kubeconfig: ${{ steps.e2e_test.outputs.kubeconfig }}
|
kubeconfig: ${{ steps.e2e_test.outputs.kubeconfig }}
|
||||||
selfManagedInfra: ${{ matrix.selfManagedInfra == 'true' }}
|
selfManagedInfra: ${{ matrix.selfManagedInfra == 'true' }}
|
||||||
|
cloudProvider: ${{ matrix.provider }}
|
||||||
|
azureClusterDeleteCredentials: ${{ secrets.AZURE_E2E_CLUSTER_CREDENTIALS }}
|
||||||
|
gcpClusterDeleteServiceAccount: "constellation-e2e-cluster@constellation-331613.iam.gserviceaccount.com"
|
||||||
|
|
||||||
- name: Always delete IAM configuration
|
- name: Always delete IAM configuration
|
||||||
if: always()
|
if: always()
|
||||||
|
5
.github/workflows/e2e-test.yml
vendored
5
.github/workflows/e2e-test.yml
vendored
@ -165,7 +165,7 @@ jobs:
|
|||||||
uses: actions/checkout@f43a0e5ff2bd294095638e18286ca9a3d1956744 # v3.6.0
|
uses: actions/checkout@f43a0e5ff2bd294095638e18286ca9a3d1956744 # v3.6.0
|
||||||
with:
|
with:
|
||||||
ref: ${{ inputs.git-ref }}
|
ref: ${{ inputs.git-ref }}
|
||||||
|
|
||||||
- name: Get Latest Image
|
- name: Get Latest Image
|
||||||
id: find-latest-image
|
id: find-latest-image
|
||||||
uses: ./.github/actions/find_latest_image
|
uses: ./.github/actions/find_latest_image
|
||||||
@ -246,6 +246,9 @@ jobs:
|
|||||||
with:
|
with:
|
||||||
kubeconfig: ${{ steps.e2e_test.outputs.kubeconfig }}
|
kubeconfig: ${{ steps.e2e_test.outputs.kubeconfig }}
|
||||||
selfManagedInfra: ${{ inputs.selfManagedInfra }}
|
selfManagedInfra: ${{ inputs.selfManagedInfra }}
|
||||||
|
cloudProvider: ${{ inputs.cloudProvider }}
|
||||||
|
azureClusterDeleteCredentials: ${{ secrets.AZURE_E2E_CLUSTER_CREDENTIALS }}
|
||||||
|
gcpClusterDeleteServiceAccount: "constellation-e2e-cluster@constellation-331613.iam.gserviceaccount.com"
|
||||||
|
|
||||||
- name: Always delete IAM configuration
|
- name: Always delete IAM configuration
|
||||||
if: always()
|
if: always()
|
||||||
|
3
.github/workflows/e2e-upgrade.yml
vendored
3
.github/workflows/e2e-upgrade.yml
vendored
@ -290,6 +290,9 @@ jobs:
|
|||||||
with:
|
with:
|
||||||
kubeconfig: ${{ steps.e2e_test.outputs.kubeconfig }}
|
kubeconfig: ${{ steps.e2e_test.outputs.kubeconfig }}
|
||||||
selfManagedInfra: "false"
|
selfManagedInfra: "false"
|
||||||
|
cloudProvider: ${{ inputs.cloudProvider }}
|
||||||
|
azureClusterDeleteCredentials: ${{ secrets.AZURE_E2E_CLUSTER_CREDENTIALS }}
|
||||||
|
gcpClusterDeleteServiceAccount: "constellation-e2e-cluster@constellation-331613.iam.gserviceaccount.com"
|
||||||
|
|
||||||
- name: Always delete IAM configuration
|
- name: Always delete IAM configuration
|
||||||
if: always()
|
if: always()
|
||||||
|
@ -19,6 +19,22 @@ configapi_cli=$(realpath @@CONFIGAPI_CLI@@)
|
|||||||
stat "${configapi_cli}" >> /dev/null
|
stat "${configapi_cli}" >> /dev/null
|
||||||
configapi_cli="${configapi_cli} --testing"
|
configapi_cli="${configapi_cli} --testing"
|
||||||
###### script body ######
|
###### script body ######
|
||||||
|
function variant() {
|
||||||
|
if [[ $1 == "aws" ]]; then
|
||||||
|
echo "aws-sev-snp"
|
||||||
|
return 0
|
||||||
|
elif [[ $1 == "azure" ]]; then
|
||||||
|
echo "azure-sev-snp"
|
||||||
|
return 0
|
||||||
|
else
|
||||||
|
echo "Unknown CSP: $1"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
csp=$1
|
||||||
|
readonly csp
|
||||||
|
attestationType=$(variant "$csp")
|
||||||
|
|
||||||
readonly region="eu-west-1"
|
readonly region="eu-west-1"
|
||||||
readonly bucket="resource-api-testing"
|
readonly bucket="resource-api-testing"
|
||||||
@ -28,7 +44,7 @@ readonly tmpdir
|
|||||||
registerExitHandler "rm -rf $tmpdir"
|
registerExitHandler "rm -rf $tmpdir"
|
||||||
|
|
||||||
# empty the bucket version state
|
# empty the bucket version state
|
||||||
${configapi_cli} delete recursive azure --region "$region" --bucket "$bucket"
|
${configapi_cli} delete recursive "$csp" --region "$region" --bucket "$bucket"
|
||||||
|
|
||||||
# the high version numbers ensure that it's newer than the current latest value
|
# the high version numbers ensure that it's newer than the current latest value
|
||||||
readonly current_report_path="$tmpdir/currentSnpReport.json"
|
readonly current_report_path="$tmpdir/currentSnpReport.json"
|
||||||
@ -57,7 +73,7 @@ cat << EOF > "$current_report_path"
|
|||||||
}
|
}
|
||||||
EOF
|
EOF
|
||||||
# upload a fake latest version for the fetcher
|
# upload a fake latest version for the fetcher
|
||||||
${configapi_cli} upload azure snp-report "$current_report_path" --force --upload-date "2000-01-01-01-01" --region "$region" --bucket "$bucket"
|
${configapi_cli} upload "$csp" snp-report "$current_report_path" --force --upload-date "2000-01-01-01-01" --region "$region" --bucket "$bucket"
|
||||||
|
|
||||||
# the high version numbers ensure that it's newer than the current latest value
|
# the high version numbers ensure that it's newer than the current latest value
|
||||||
readonly report_path="$tmpdir/snpReport.json"
|
readonly report_path="$tmpdir/snpReport.json"
|
||||||
@ -115,16 +131,17 @@ EOF
|
|||||||
|
|
||||||
# report 3 versions with different dates to fill the reporter cache
|
# report 3 versions with different dates to fill the reporter cache
|
||||||
readonly date_oldest="2023-02-01-03-04"
|
readonly date_oldest="2023-02-01-03-04"
|
||||||
${configapi_cli} upload azure snp-report "$older_report_path" --upload-date "$date_oldest" --region "$region" --bucket "$bucket" --cache-window-size 3
|
${configapi_cli} upload "$csp" snp-report "$older_report_path" --upload-date "$date_oldest" --region "$region" --bucket "$bucket" --cache-window-size 3
|
||||||
readonly date_older="2023-02-02-03-04"
|
readonly date_older="2023-02-02-03-04"
|
||||||
${configapi_cli} upload azure snp-report "$older_report_path" --upload-date "$date_older" --region "$region" --bucket "$bucket" --cache-window-size 3
|
${configapi_cli} upload "$csp" snp-report "$older_report_path" --upload-date "$date_older" --region "$region" --bucket "$bucket" --cache-window-size 3
|
||||||
readonly date="2023-02-03-03-04"
|
readonly date="2023-02-03-03-04"
|
||||||
${configapi_cli} upload azure snp-report "$report_path" --upload-date "$date" --region "$region" --bucket "$bucket" --cache-window-size 3
|
${configapi_cli} upload "$csp" snp-report "$report_path" --upload-date "$date" --region "$region" --bucket "$bucket" --cache-window-size 3
|
||||||
|
|
||||||
# expect that $date_oldest is served as latest version
|
# expect that $date_oldest is served as latest version
|
||||||
baseurl="https://d33dzgxuwsgbpw.cloudfront.net/constellation/v1/attestation/azure-sev-snp"
|
basepath="constellation/v1/attestation/${attestationType}"
|
||||||
if ! curl -fsSL ${baseurl}/${date_oldest}.json > version.json; then
|
baseurl="https://d33dzgxuwsgbpw.cloudfront.net/${basepath}"
|
||||||
echo "Checking for uploaded version file constellation/v1/attestation/azure-sev-snp/${date_oldest}.json: request returned ${?}"
|
if ! curl -fsSL "${baseurl}"/${date_oldest}.json > version.json; then
|
||||||
|
echo "Checking for uploaded version file ${basepath}/${date_oldest}.json: request returned ${?}"
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
# check that version values are equal to expected
|
# check that version values are equal to expected
|
||||||
@ -135,13 +152,13 @@ if ! cmp -s <(echo -n '{"bootloader":255,"tee":255,"snp":255,"microcode":254}')
|
|||||||
echo '{"bootloader":255,"tee":255,"snp":255,"microcode":254}'
|
echo '{"bootloader":255,"tee":255,"snp":255,"microcode":254}'
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
if ! curl -fsSL ${baseurl}/${date_oldest}.json.sig > /dev/null; then
|
if ! curl -fsSL "${baseurl}"/${date_oldest}.json.sig > /dev/null; then
|
||||||
echo "Checking for uploaded version signature file constellation/v1/attestation/azure-sev-snp/${date_oldest}.json.sig: request returned ${?}"
|
echo "Checking for uploaded version signature file ${basepath}/${date_oldest}.json.sig: request returned ${?}"
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
# check list endpoint
|
# check list endpoint
|
||||||
if ! curl -fsSL ${baseurl}/list > list.json; then
|
if ! curl -fsSL "${baseurl}"/list > list.json; then
|
||||||
echo "Checking for uploaded list file constellation/v1/attestation/azure-sev-snp/list: request returned ${?}"
|
echo "Checking for uploaded list file ${basepath}/list: request returned ${?}"
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
# check that version values are equal to expected
|
# check that version values are equal to expected
|
||||||
@ -154,28 +171,28 @@ if ! cmp -s <(echo -n '["2023-02-01-03-04.json","2000-01-01-01-01.json"]') list.
|
|||||||
fi
|
fi
|
||||||
|
|
||||||
# check that the other versions are not uploaded
|
# check that the other versions are not uploaded
|
||||||
http_code=$(curl -sSL -w '%{http_code}\n' -o /dev/null ${baseurl}/${date_older}.json)
|
http_code=$(curl -sSL -w '%{http_code}\n' -o /dev/null "${baseurl}"/${date_older}.json)
|
||||||
if [[ $http_code -ne 404 ]]; then
|
if [[ $http_code -ne 404 ]]; then
|
||||||
echo "Expected HTTP code 404 for: constellation/v1/attestation/azure-sev-snp/${date_older}.json, but got ${http_code}"
|
echo "Expected HTTP code 404 for: ${basepath}/${date_older}.json, but got ${http_code}"
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
http_code=$(curl -sSL -w '%{http_code}\n' -o /dev/null ${baseurl}/${date}.json.sig)
|
http_code=$(curl -sSL -w '%{http_code}\n' -o /dev/null "${baseurl}"/${date}.json.sig)
|
||||||
if [[ $http_code -ne 404 ]]; then
|
if [[ $http_code -ne 404 ]]; then
|
||||||
echo "Expected HTTP code 404 for: constellation/v1/attestation/azure-sev-snp/${date}.json, but got ${http_code}"
|
echo "Expected HTTP code 404 for: ${basepath}/${date}.json, but got ${http_code}"
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
${configapi_cli} delete azure snp-report "$date_oldest" --region "$region" --bucket "$bucket"
|
${configapi_cli} delete "$csp" snp-report "$date_oldest" --region "$region" --bucket "$bucket"
|
||||||
|
|
||||||
# Omit -f to check for 404. We want to check that a file was deleted, therefore we expect the query to fail.
|
# Omit -f to check for 404. We want to check that a file was deleted, therefore we expect the query to fail.
|
||||||
http_code=$(curl -sSL -w '%{http_code}\n' -o /dev/null ${baseurl}/${date_oldest}.json)
|
http_code=$(curl -sSL -w '%{http_code}\n' -o /dev/null "${baseurl}"/${date_oldest}.json)
|
||||||
if [[ $http_code -ne 404 ]]; then
|
if [[ $http_code -ne 404 ]]; then
|
||||||
echo "Expected HTTP code 404 for: constellation/v1/attestation/azure-sev-snp/${date_oldest}.json, but got ${http_code}"
|
echo "Expected HTTP code 404 for: ${basepath}/${date_oldest}.json, but got ${http_code}"
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
# Omit -f to check for 404. We want to check that a file was deleted, therefore we expect the query to fail.
|
# Omit -f to check for 404. We want to check that a file was deleted, therefore we expect the query to fail.
|
||||||
http_code=$(curl -sSL -w '%{http_code}\n' -o /dev/null ${baseurl}/${date_oldest}.json.sig)
|
http_code=$(curl -sSL -w '%{http_code}\n' -o /dev/null "${baseurl}"/${date_oldest}.json.sig)
|
||||||
if [[ $http_code -ne 404 ]]; then
|
if [[ $http_code -ne 404 ]]; then
|
||||||
echo "Expected HTTP code 404 for: constellation/v1/attestation/azure-sev-snp/${date_oldest}.json, but got ${http_code}"
|
echo "Expected HTTP code 404 for: ${basepath}/${date_oldest}.json, but got ${http_code}"
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
Loading…
x
Reference in New Issue
Block a user