Compare commits

..

No commits in common. "main" and "v0.18.0-rc.1" have entirely different histories.

230 changed files with 4910 additions and 5512 deletions

View File

@ -3,14 +3,9 @@ updates:
- package-ecosystem: gomod
directory: "/"
schedule:
interval: weekly
groups:
# Group all minor/patch go dependencies into a single PR.
go-dependencies:
update-types:
- "minor"
- "patch"
interval: daily
open-pull-requests-limit: 10
- package-ecosystem: "github-actions"
directory: "/"
schedule:
interval: weekly
interval: daily

View File

@ -18,7 +18,7 @@ jobs:
with:
fetch-depth: '0'
- name: Setup go
uses: actions/setup-go@v5
uses: actions/setup-go@v4
with:
check-latest: true
go-version-file: 'go.mod'
@ -33,9 +33,8 @@ jobs:
TEST_COVERAGE: 1
run: make test
- name: Upload coverage to Codecov
uses: codecov/codecov-action@v5
uses: codecov/codecov-action@v3
with:
token: ${{ secrets.CODECOV_TOKEN }}
file: ./out/tests/coverage-unit.txt
flags: unit,os_linux
fail_ci_if_error: true
@ -47,7 +46,7 @@ jobs:
with:
fetch-depth: '0'
- name: Setup go
uses: actions/setup-go@v5
uses: actions/setup-go@v4
with:
check-latest: true
go-version-file: 'go.mod'
@ -55,10 +54,62 @@ jobs:
run: |
make format || true
make test
test-windows:
runs-on: windows-2019
steps:
- name: Set git to use LF and symlinks
run: |
git config --global core.autocrlf false
git config --global core.eol lf
git config --global core.symlinks true
- uses: actions/checkout@v4
with:
fetch-depth: '0'
- name: Setup go
uses: actions/setup-go@v4
with:
check-latest: true
go-version-file: 'go.mod'
- name: Add runner IP to daemon insecure-registries and firewall
shell: powershell
run: |
# Get IP from default gateway interface
$IPAddress=(Get-NetIPAddress -InterfaceAlias ((Get-NetRoute "0.0.0.0/0").InterfaceAlias) -AddressFamily IPv4)[0].IPAddress
# Allow container-to-host registry traffic (from public interface, to the same interface)
New-NetfirewallRule -DisplayName test-registry -LocalAddress $IPAddress -RemoteAddress $IPAddress
# create or update daemon config to allow host as insecure-registry
$config=@{}
if (Test-Path C:\ProgramData\docker\config\daemon.json) {
$config=(Get-Content C:\ProgramData\docker\config\daemon.json | ConvertFrom-json)
}
$config | Add-Member -Force -Name "insecure-registries" -value @("$IPAddress/32") -MemberType NoteProperty
$config | Add-Member -Force -Name "allow-nondistributable-artifacts" -value @("$IPAddress/32") -MemberType NoteProperty
ConvertTo-json $config | Out-File -Encoding ASCII C:\ProgramData\docker\config\daemon.json
Restart-Service docker
# dump docker info for auditing
docker version
docker info
- name: Test
env:
TEST_COVERAGE: 1
run: |
make test
- name: Prepare Codecov
uses: crazy-max/ghaction-chocolatey@v3
with:
args: install codecov -y
- name: Run Codecov
run: |
codecov.exe -f .\out\tests\coverage-unit.txt -v --flag os_windows
build-and-publish:
needs:
- test-linux-amd64
- test-linux-arm64
- test-windows
runs-on: ubuntu-latest
permissions:
id-token: write
@ -67,7 +118,7 @@ jobs:
with:
fetch-depth: 0 # fetch all history for all branches and tags
- name: Setup go
uses: actions/setup-go@v5
uses: actions/setup-go@v4
with:
check-latest: true
go-version-file: 'go.mod'
@ -76,14 +127,14 @@ jobs:
- name: Set version
run: |
echo "LIFECYCLE_VERSION=$(go run tools/version/main.go)" | tee -a $GITHUB_ENV version.txt
- uses: actions/upload-artifact@v4
- uses: actions/upload-artifact@v3
with:
name: version
path: version.txt
- name: Set tag
run: |
echo "LIFECYCLE_IMAGE_TAG=$(git describe --always --abbrev=7)" >> tag.txt
- uses: actions/upload-artifact@v4
- uses: actions/upload-artifact@v3
with:
name: tag
path: tag.txt
@ -92,60 +143,60 @@ jobs:
make clean
make build
make package
- uses: actions/upload-artifact@v4
- uses: actions/upload-artifact@v3
with:
name: lifecycle-linux-x86-64
path: out/lifecycle-v*+linux.x86-64.tgz
- uses: actions/upload-artifact@v4
- uses: actions/upload-artifact@v3
with:
name: lifecycle-linux-x86-64-sha256
path: out/lifecycle-v*+linux.x86-64.tgz.sha256
- uses: actions/upload-artifact@v4
- uses: actions/upload-artifact@v3
with:
name: lifecycle-linux-arm64
path: out/lifecycle-v*+linux.arm64.tgz
- uses: actions/upload-artifact@v4
- uses: actions/upload-artifact@v3
with:
name: lifecycle-linux-arm64-sha256
path: out/lifecycle-v*+linux.arm64.tgz.sha256
- uses: actions/upload-artifact@v4
with:
name: lifecycle-linux-ppc64le
path: out/lifecycle-v*+linux.ppc64le.tgz
- uses: actions/upload-artifact@v4
with:
name: lifecycle-linux-ppc64le-sha256
path: out/lifecycle-v*+linux.ppc64le.tgz.sha256
- uses: actions/upload-artifact@v4
- uses: actions/upload-artifact@v3
with:
name: lifecycle-linux-s390x
path: out/lifecycle-v*+linux.s390x.tgz
- uses: actions/upload-artifact@v4
- uses: actions/upload-artifact@v3
with:
name: lifecycle-linux-s390x-sha256
path: out/lifecycle-v*+linux.s390x.tgz.sha256
- uses: actions/upload-artifact@v3
with:
name: lifecycle-windows-x86-64
path: out/lifecycle-v*+windows.x86-64.tgz
- uses: actions/upload-artifact@v3
with:
name: lifecycle-windows-x86-64-sha256
path: out/lifecycle-v*+windows.x86-64.tgz.sha256
- name: Generate SBOM JSON
uses: CycloneDX/gh-gomod-generate-sbom@v2
with:
args: mod -licenses -json -output lifecycle-v${{ env.LIFECYCLE_VERSION }}-bom.cdx.json
version: ^v1
- uses: actions/upload-artifact@v4
- uses: actions/upload-artifact@v3
with:
name: lifecycle-bom-cdx
path: lifecycle-v*-bom.cdx.json
- name: Calculate SBOM sha
run: |
shasum -a 256 lifecycle-v${{ env.LIFECYCLE_VERSION }}-bom.cdx.json > lifecycle-v${{ env.LIFECYCLE_VERSION }}-bom.cdx.json.sha256
- uses: actions/upload-artifact@v4
- uses: actions/upload-artifact@v3
with:
name: lifecycle-bom-cdx-sha256
path: lifecycle-v*-bom.cdx.json.sha256
- uses: azure/docker-login@v2
- uses: azure/docker-login@v1
if: github.event_name == 'push'
with:
username: ${{ secrets.DOCKER_USERNAME }}
password: ${{ secrets.DOCKER_PASSWORD }}
- uses: actions/download-artifact@v5
- uses: actions/download-artifact@v3
with:
name: tag
- name: Set env
@ -163,17 +214,17 @@ jobs:
LINUX_ARM64_SHA=$(go run ./tools/image/main.go -lifecyclePath ./out/lifecycle-v*+linux.arm64.tgz -tag buildpacksio/lifecycle:${LIFECYCLE_IMAGE_TAG}-linux-arm64 -arch arm64 | awk '{print $NF}')
echo "LINUX_ARM64_SHA: $LINUX_ARM64_SHA"
LINUX_PPC64LE_SHA=$(go run ./tools/image/main.go -lifecyclePath ./out/lifecycle-v*+linux.ppc64le.tgz -tag buildpacksio/lifecycle:${LIFECYCLE_IMAGE_TAG}-linux-ppc64le -arch ppc64le | awk '{print $NF}')
echo "LINUX_PPC64LE_SHA: LINUX_PPC64LE_SHA"
LINUX_S390X_SHA=$(go run ./tools/image/main.go -lifecyclePath ./out/lifecycle-v*+linux.s390x.tgz -tag buildpacksio/lifecycle:${LIFECYCLE_IMAGE_TAG}-linux-s390x -arch s390x | awk '{print $NF}')
echo "LINUX_S390X_SHA: $LINUX_S390X_SHA"
WINDOWS_AMD64_SHA=$(go run ./tools/image/main.go -lifecyclePath ./out/lifecycle-v*+windows.x86-64.tgz -tag buildpacksio/lifecycle:${LIFECYCLE_IMAGE_TAG}-windows -os windows | awk '{print $NF}')
echo "WINDOWS_AMD64_SHA: $WINDOWS_AMD64_SHA"
docker manifest create buildpacksio/lifecycle:${LIFECYCLE_IMAGE_TAG} \
buildpacksio/lifecycle:${LIFECYCLE_IMAGE_TAG}-linux-x86-64@${LINUX_AMD64_SHA} \
buildpacksio/lifecycle:${LIFECYCLE_IMAGE_TAG}-linux-arm64@${LINUX_ARM64_SHA} \
buildpacksio/lifecycle:${LIFECYCLE_IMAGE_TAG}-linux-ppc64le@${LINUX_PPC64LE_SHA} \
buildpacksio/lifecycle:${LIFECYCLE_IMAGE_TAG}-linux-s390x@${LINUX_S390X_SHA}
buildpacksio/lifecycle:${LIFECYCLE_IMAGE_TAG}-linux-s390x@${LINUX_S390X_SHA} \
buildpacksio/lifecycle:${LIFECYCLE_IMAGE_TAG}-windows@${WINDOWS_AMD64_SHA}
MANIFEST_SHA=$(docker manifest push buildpacksio/lifecycle:${LIFECYCLE_IMAGE_TAG})
echo "MANIFEST_SHA: $MANIFEST_SHA"
@ -188,7 +239,7 @@ jobs:
buildpacksio/lifecycle:${LIFECYCLE_IMAGE_TAG}
- name: Scan image
if: github.event_name == 'push'
uses: anchore/scan-action@v6
uses: anchore/scan-action@v3
with:
image: buildpacksio/lifecycle:${{ env.LIFECYCLE_IMAGE_TAG }}
pack-acceptance-linux:
@ -203,20 +254,20 @@ jobs:
ref: 'main'
fetch-depth: 0 # fetch all history for all branches and tags
- name: Setup go
uses: actions/setup-go@v5
uses: actions/setup-go@v4
with:
go-version-file: 'pack/go.mod'
- uses: actions/download-artifact@v5
- uses: actions/download-artifact@v3
with:
name: version
- uses: actions/download-artifact@v5
- uses: actions/download-artifact@v3
with:
name: tag
- name: Set env
run: |
cat version.txt >> $GITHUB_ENV
cat tag.txt >> $GITHUB_ENV
- uses: actions/download-artifact@v5
- uses: actions/download-artifact@v3
with:
name: lifecycle-linux-x86-64
path: pack
@ -227,3 +278,75 @@ jobs:
LIFECYCLE_PATH="../lifecycle-v${{ env.LIFECYCLE_VERSION }}+linux.x86-64.tgz" \
LIFECYCLE_IMAGE="buildpacksio/lifecycle:${{ env.LIFECYCLE_IMAGE_TAG }}" \
make acceptance
pack-acceptance-windows:
if: github.event_name == 'push'
needs: build-and-publish
runs-on: windows-2019
steps:
- name: Set git to use LF and symlinks
run: |
git config --global core.autocrlf false
git config --global core.eol lf
git config --global core.symlinks true
- uses: actions/checkout@v4
with:
repository: 'buildpacks/pack'
path: 'pack'
ref: 'main'
fetch-depth: 0 # fetch all history for all branches and tags
- name: Setup go
uses: actions/setup-go@v4
with:
go-version-file: 'pack/go.mod'
- name: Add runner IP to daemon insecure-registries and firewall
shell: powershell
run: |
# Get IP from default gateway interface
$IPAddress=(Get-NetIPAddress -InterfaceAlias ((Get-NetRoute "0.0.0.0/0").InterfaceAlias) -AddressFamily IPv4)[0].IPAddress
# Allow container-to-host registry traffic (from public interface, to the same interface)
New-NetfirewallRule -DisplayName test-registry -LocalAddress $IPAddress -RemoteAddress $IPAddress
# create or update daemon config to allow host as insecure-registry
$config=@{}
if (Test-Path C:\ProgramData\docker\config\daemon.json) {
$config=(Get-Content C:\ProgramData\docker\config\daemon.json | ConvertFrom-json)
}
$config | Add-Member -Force -Name "insecure-registries" -value @("$IPAddress/32") -MemberType NoteProperty
ConvertTo-json $config | Out-File -Encoding ASCII C:\ProgramData\docker\config\daemon.json
Restart-Service docker
# dump docker info for auditing
docker version
docker info
- name: Modify etc\hosts to include runner IP
shell: powershell
run: |
$IPAddress=(Get-NetIPAddress -InterfaceAlias ((Get-NetRoute "0.0.0.0/0").InterfaceAlias) -AddressFamily IPv4)[0].IPAddress
"# Modified by CNB: https://github.com/buildpacks/ci/tree/main/gh-runners/windows
${IPAddress} host.docker.internal
${IPAddress} gateway.docker.internal
" | Out-File -Filepath C:\Windows\System32\drivers\etc\hosts -Encoding utf8
- uses: actions/download-artifact@v3
with:
name: version
- uses: actions/download-artifact@v3
with:
name: tag
- name: Set env
run: |
cat version.txt >> $env:GITHUB_ENV
cat tag.txt >> $env:GITHUB_ENV
- uses: actions/download-artifact@v3
with:
name: lifecycle-windows-x86-64
path: pack
- name: Run pack acceptance
run: |
cd pack
git checkout $(git describe --abbrev=0 --tags) # check out the latest tag
$env:LIFECYCLE_PATH="..\lifecycle-v${{ env.LIFECYCLE_VERSION }}+windows.x86-64.tgz"
$env:LIFECYCLE_IMAGE="buildpacksio/lifecycle:${{ env.LIFECYCLE_IMAGE_TAG }}"
make acceptance

View File

@ -9,11 +9,9 @@ jobs:
check-release:
runs-on:
- ubuntu-latest
permissions:
issues: write
steps:
- uses: actions/checkout@v4
- uses: actions/setup-go@v5
- uses: actions/setup-go@v4
with:
check-latest: true
go-version-file: 'go.mod'
@ -86,7 +84,7 @@ jobs:
fi
- name: Scan latest release image
id: scan-image
uses: anchore/scan-action@v6
uses: anchore/scan-action@v3
with:
image: buildpacksio/lifecycle:${{ steps.read-versions.outputs.latest-release-version }}
fail-build: true

View File

@ -24,7 +24,7 @@ jobs:
exit 1
fi
echo "LIFECYCLE_VERSION=$version" >> $GITHUB_ENV
- name: Determine download urls for linux-x86-64, linux-arm64, linux-ppc64le, linux-s390x
- name: Determine download urls for linux-x86-64, linux-arm64, linux-s390x, and windows
id: artifact-urls
# FIXME: this script should be updated to work with actions/github-script@v6
uses: actions/github-script@v3
@ -83,10 +83,7 @@ jobs:
throw "no artifacts found"
}
if (urlList.length != 10) {
// found too many artifacts
// list them and throw
console.log(urlList);
throw "there should be exactly 10 artifacts, found " + urlList.length + " artifacts"
throw "there should be exactly 10 artifacts"
}
return urlList.join(",")
})
@ -131,10 +128,9 @@ jobs:
return result.data.tag_name
})
- name: Setup go
uses: actions/setup-go@v5
uses: actions/setup-go@v4
with:
check-latest: true
go-version-file: 'go.mod'
- name: Get go version
id: get-go-version
run: |
@ -191,7 +187,7 @@ jobs:
--draft \
--notes-file ../body.txt \
--prerelease \
--target $GITHUB_REF_NAME \
--target $GITHUB_REF \
--title "lifecycle v${{ env.LIFECYCLE_VERSION }}"
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
@ -203,7 +199,7 @@ jobs:
$(ls | sort | paste -sd " " -) \
--draft \
--notes-file ../body.txt \
--target $GITHUB_REF_NAME \
--target $GITHUB_REF \
--title "lifecycle v${{ env.LIFECYCLE_VERSION }}"
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

View File

@ -13,7 +13,7 @@ jobs:
steps:
- uses: actions/checkout@v4
- name: Setup go
uses: actions/setup-go@v5
uses: actions/setup-go@v4
with:
check-latest: true
go-version-file: 'go.mod'
@ -22,7 +22,7 @@ jobs:
go install github.com/google/go-containerregistry/cmd/crane@latest
- name: Install cosign
uses: sigstore/cosign-installer@v3
- uses: azure/docker-login@v2
- uses: azure/docker-login@v1
with:
username: ${{ secrets.DOCKER_USERNAME }}
password: ${{ secrets.DOCKER_PASSWORD }}
@ -40,14 +40,9 @@ jobs:
echo "LINUX_ARM64_SHA: $LINUX_ARM64_SHA"
echo "LINUX_ARM64_SHA=$LINUX_ARM64_SHA" >> $GITHUB_ENV
LINUX_PPC64LE_SHA=$(cosign verify --certificate-identity-regexp "https://github.com/${{ github.repository_owner }}/lifecycle/.github/workflows/build.yml" --certificate-oidc-issuer https://token.actions.githubusercontent.com buildpacksio/lifecycle:${{ env.LIFECYCLE_IMAGE_TAG }}-linux-ppc64le | jq -r .[0].critical.image.\"docker-manifest-digest\")
echo "LINUX_PPC64LE_SHA: $LINUX_PPC64LE_SHA"
echo "LINUX_PPC64LE_SHA=$LINUX_PPC64LE_SHA" >> $GITHUB_ENV
LINUX_S390X_SHA=$(cosign verify --certificate-identity-regexp "https://github.com/${{ github.repository_owner }}/lifecycle/.github/workflows/build.yml" --certificate-oidc-issuer https://token.actions.githubusercontent.com buildpacksio/lifecycle:${{ env.LIFECYCLE_IMAGE_TAG }}-linux-s390x | jq -r .[0].critical.image.\"docker-manifest-digest\")
echo "LINUX_S390X_SHA: $LINUX_S390X_SHA"
echo "LINUX_S390X_SHA=$LINUX_S390X_SHA" >> $GITHUB_ENV
WINDOWS_AMD64_SHA=$(cosign verify --certificate-identity-regexp "https://github.com/${{ github.repository_owner }}/lifecycle/.github/workflows/build.yml" --certificate-oidc-issuer https://token.actions.githubusercontent.com buildpacksio/lifecycle:${{ env.LIFECYCLE_IMAGE_TAG }}-windows | jq -r .[0].critical.image.\"docker-manifest-digest\")
echo "WINDOWS_AMD64_SHA: $WINDOWS_AMD64_SHA"
echo "WINDOWS_AMD64_SHA=$WINDOWS_AMD64_SHA" >> $GITHUB_ENV
- name: Download SBOM
run: |
gh release download --pattern '*-bom.cdx.json' ${{ github.event.release.tag_name }}
@ -59,14 +54,14 @@ jobs:
crane tag buildpacksio/lifecycle:${{ env.LIFECYCLE_IMAGE_TAG }}-linux-x86-64@${{ env.LINUX_AMD64_SHA }} ${{ env.LIFECYCLE_VERSION }}-linux-x86-64
crane tag buildpacksio/lifecycle:${{ env.LIFECYCLE_IMAGE_TAG }}-linux-arm64@${{ env.LINUX_ARM64_SHA }} ${{ env.LIFECYCLE_VERSION }}-linux-arm64
crane tag buildpacksio/lifecycle:${{ env.LIFECYCLE_IMAGE_TAG }}-linux-ppc64le@${{ env.LINUX_PPC64LE_SHA }} ${{ env.LIFECYCLE_VERSION }}-linux-ppc64le
crane tag buildpacksio/lifecycle:${{ env.LIFECYCLE_IMAGE_TAG }}-linux-s390x@${{ env.LINUX_S390X_SHA }} ${{ env.LIFECYCLE_VERSION }}-linux-s390x
crane tag buildpacksio/lifecycle:${{ env.LIFECYCLE_IMAGE_TAG }}-windows@${{ env.WINDOWS_AMD64_SHA }} ${{ env.LIFECYCLE_VERSION }}-windows
docker manifest create buildpacksio/lifecycle:${{ env.LIFECYCLE_VERSION }} \
buildpacksio/lifecycle:${{ env.LIFECYCLE_VERSION }}-linux-x86-64@${{ env.LINUX_AMD64_SHA }} \
buildpacksio/lifecycle:${{ env.LIFECYCLE_VERSION }}-linux-arm64@${{ env.LINUX_ARM64_SHA }} \
buildpacksio/lifecycle:${{ env.LIFECYCLE_VERSION }}-linux-ppc64le@${{ env.LINUX_PPC64LE_SHA }} \
buildpacksio/lifecycle:${{ env.LIFECYCLE_VERSION }}-linux-s390x@${{ env.LINUX_S390X_SHA }}
buildpacksio/lifecycle:${{ env.LIFECYCLE_VERSION }}-linux-s390x@${{ env.LINUX_S390X_SHA }} \
buildpacksio/lifecycle:${{ env.LIFECYCLE_VERSION }}-windows@${{ env.WINDOWS_AMD64_SHA }}
MANIFEST_SHA=$(docker manifest push buildpacksio/lifecycle:${{ env.LIFECYCLE_VERSION }})
echo "MANIFEST_SHA: $MANIFEST_SHA"
@ -96,14 +91,14 @@ jobs:
crane tag buildpacksio/lifecycle:${{ env.LIFECYCLE_IMAGE_TAG }}-linux-x86-64@${{ env.LINUX_AMD64_SHA }} latest-linux-x86-64
crane tag buildpacksio/lifecycle:${{ env.LIFECYCLE_IMAGE_TAG }}-linux-arm64@${{ env.LINUX_ARM64_SHA }} latest-linux-arm64
crane tag buildpacksio/lifecycle:${{ env.LIFECYCLE_IMAGE_TAG }}-linux-ppc64le@${{ env.LINUX_PPC64LE_SHA }} latest-linux-ppc64le
crane tag buildpacksio/lifecycle:${{ env.LIFECYCLE_IMAGE_TAG }}-linux-s390x@${{ env.LINUX_S390X_SHA }} latest-linux-s390x
crane tag buildpacksio/lifecycle:${{ env.LIFECYCLE_IMAGE_TAG }}-windows@${{ env.WINDOWS_AMD64_SHA }} latest-windows
docker manifest create buildpacksio/lifecycle:latest \
buildpacksio/lifecycle:latest-linux-x86-64@${{ env.LINUX_AMD64_SHA }} \
buildpacksio/lifecycle:latest-linux-arm64@${{ env.LINUX_ARM64_SHA }} \
buildpacksio/lifecycle:latest-linux-ppc64le@${{ env.LINUX_PPC64LE_SHA }} \
buildpacksio/lifecycle:latest-linux-s390x@${{ env.LINUX_S390X_SHA }}
buildpacksio/lifecycle:latest-linux-s390x@${{ env.LINUX_S390X_SHA }} \
buildpacksio/lifecycle:latest-windows@${{ env.WINDOWS_AMD64_SHA }}
MANIFEST_SHA=$(docker manifest push buildpacksio/lifecycle:latest)
echo "MANIFEST_SHA: $MANIFEST_SHA"

View File

@ -14,8 +14,8 @@ jobs:
if: (github.ref == 'refs/heads/main' || github.ref == 'refs/heads/release*')
runs-on: ubuntu-latest
env:
ZVSI_FP_NAME: bp-floating-ci-${{ github.run_id }}
ZVSI_INSTANCE_NAME: bp-zvsi-ci-${{ github.run_id }}
ZVSI_FP_NAME: bp-floating-ci-${{ github.run_id }}
ZVSI_INSTANCE_NAME: bp-zvsi-ci-${{ github.run_id }}
ZVSI_ZONE_NAME: ca-tor-1
ZVSI_PROFILE_NAME: bz2-4x16
@ -23,20 +23,20 @@ jobs:
fail-fast: false
steps:
- uses: actions/checkout@v4
- name: install ibmcli and setup ibm login
- name: install ibmcli and setup ibm login
run: |
curl -fsSL https://clis.cloud.ibm.com/install/linux | sh
ibmcloud login -q --apikey ${{ secrets.IBMCLOUD_API_KEY }} -r ca-tor
ibmcloud plugin install vpc-infrastructure
ibmcloud plugin install vpc-infrastructure
- name: Creation of ZVSI
id: ZVSI
run: |
#creation of zvsi
ibmcloud is instance-create $ZVSI_INSTANCE_NAME ${{ secrets.ZVSI_VPC }} $ZVSI_ZONE_NAME $ZVSI_PROFILE_NAME ${{ secrets.ZVSI_SUBNET }} --image ${{ secrets.ZVSI_IMAGE }} --keys ${{ secrets.ZVSI_KEY }} --resource-group-id ${{ secrets.ZVSI_RG_ID }} --primary-network-interface "{\"name\":\"eth0\",\"allow_ip_spoofing\":false,\"subnet\": {\"name\":\"${{ secrets.ZVSI_SUBNET }}\"},\"security_groups\":[{\"id\":\"${{ secrets.ZVSI_SG }}\"}]}"
ibmcloud is instance-create $ZVSI_INSTANCE_NAME ${{ secrets.ZVSI_VPC }} $ZVSI_ZONE_NAME $ZVSI_PROFILE_NAME ${{ secrets.ZVSI_SUBNET }} --image ${{ secrets.ZVSI_IMAGE }} --keys ${{ secrets.ZVSI_KEY }} --resource-group-id ${{ secrets.ZVSI_RG_ID }} --sgs ${{ secrets.ZVSI_SG }}
#Reserving a floating ip to the ZVSI
ibmcloud is floating-ip-reserve $ZVSI_FP_NAME --zone $ZVSI_ZONE_NAME --resource-group-id ${{ secrets.ZVSI_RG_ID }} --in $ZVSI_INSTANCE_NAME
#Bouding the Floating ip to the ZVSI
ibmcloud is floating-ip-update $ZVSI_FP_NAME --nic eth0 --in $ZVSI_INSTANCE_NAME
ibmcloud is floating-ip-update $ZVSI_FP_NAME --nic primary --in $ZVSI_INSTANCE_NAME
sleep 60
#Saving the Floating IP to login ZVSI
ZVSI_HOST=$(ibmcloud is floating-ip $ZVSI_FP_NAME | awk '/Address/{print $2}')
@ -55,11 +55,11 @@ jobs:
fi
done
- name: Install dependencies and run all tests on s390x ZVSI
uses: appleboy/ssh-action@v1.2.2
uses: appleboy/ssh-action@v0.1.10
env:
GH_REPOSITORY: ${{ github.server_url }}/${{ github.repository }}
GH_REF: ${{ github.ref }}
with:
with:
host: ${{ steps.ZVSI.outputs.IP }}
username: ${{ secrets.ZVSI_SSH_USER }}
key: ${{ secrets.ZVSI_PR_KEY }}
@ -68,8 +68,8 @@ jobs:
script: |
apt-get update -y
apt-get install -y wget curl git make gcc jq docker.io
wget https://go.dev/dl/go1.24.6.linux-s390x.tar.gz
rm -rf /usr/local/go && tar -C /usr/local -xzf go1.24.6.linux-s390x.tar.gz
wget https://go.dev/dl/go1.20.6.linux-s390x.tar.gz
rm -rf /usr/local/go && tar -C /usr/local -xzf go1.20.6.linux-s390x.tar.gz
export PATH=$PATH:/usr/local/go/bin
git clone ${GH_REPOSITORY} lifecycle
cd lifecycle && git checkout ${GH_REF}
@ -84,4 +84,4 @@ jobs:
ibmcloud is instance-delete $ZVSI_INSTANCE_NAME --force
sleep 20
#Release the created FP
ibmcloud is floating-ip-release $ZVSI_FP_NAME --force
ibmcloud is floating-ip-release $ZVSI_FP_NAME --force

View File

@ -1,5 +1,3 @@
ignore:
- vulnerability: CVE-2015-5237 # false positive, see https://github.com/anchore/grype/issues/558
- vulnerability: CVE-2021-22570 # false positive, see https://github.com/anchore/grype/issues/558
- vulnerability: CVE-2024-41110 # non-impactful as we only use docker as a client
- vulnerability: GHSA-v23v-6jw2-98fq # non-impactful as we only use docker as a client

View File

@ -21,7 +21,7 @@
* Windows:
* `choco install cygwin make -y`
* `[Environment]::SetEnvironmentVariable("PATH", "C:\tools\cygwin\bin;$ENV:PATH", "MACHINE")`
### Caveats
* The acceptance tests require the docker daemon to be able to communicate with a local containerized insecure registry. On Docker Desktop 3.3.x, this may result in failures such as: `Expected nil: push response: : Get http://localhost:<port>/v2/: dial tcp [::1]:<port>: connect: connection refused`. To fix these failures, it may be necessary to add the following to the Docker Desktop Engine config:
@ -32,6 +32,17 @@
]
```
* Some of the Windows acceptance tests use license restricted base images. By default, the docker deamon will not publish layers from these images when pushing to a registry which can result in test failures with error messages such as: `Ignoring image "X" because it was corrupt`. To fix these failures you must [enable pushing nondistributable artifacts](https://docs.docker.com/engine/reference/commandline/dockerd/#allow-push-of-nondistributable-artifacts) to the test registry by adding the following to your Docker Desktop Engine config:
* `%programdata%\docker\config\daemon.json`:
```
{
"allow-nondistributable-artifacts": [
"<my-host-ip>/32"
]
}
```
### Testing GitHub actions on forks
The lifecycle release process involves chaining a series of GitHub actions together such that:
@ -46,7 +57,7 @@ For the fork, it is necessary to add the following secrets:
* DOCKER_PASSWORD (if not using ghcr.io)
* DOCKER_USERNAME (if not using ghcr.io)
The tools/test-fork.sh script can be used to update the source code to reflect the state of the fork.
The tools/test-fork.sh script can be used to update the source code to reflect the state of the fork.
It can be invoked like so: `./tools/test-fork.sh <registry repo name>`
## Tasks

View File

@ -7,6 +7,7 @@ This image is maintained by the [Cloud Native Buildpacks project](https://buildp
Supported tags are semver-versioned manifest lists - e.g., `0.12.0` or `0.12.0-rc.1`, pointing to one of the following os/architectures:
* `linux/amd64`
* `linux/arm64`
* `windows/amd64`
# About this image
@ -41,4 +42,4 @@ With [tekton](https://github.com/tektoncd/catalog/tree/main/task/buildpacks-phas
* Provide as param `LIFECYCLE_IMAGE` in taskrun
***
[Source](https://github.com/buildpacks/lifecycle/blob/main/IMAGE.md) for this page
[Source](https://github.com/buildpacks/lifecycle/blob/main/IMAGE.md) for this page

298
Makefile
View File

@ -30,6 +30,7 @@ LDFLAGS+=-X 'github.com/buildpacks/lifecycle/cmd.Version=$(LIFECYCLE_VERSION)'
GOBUILD:=go build $(GOFLAGS) -ldflags "$(LDFLAGS)"
GOTEST=$(GOCMD) test $(GOFLAGS)
BUILD_DIR?=$(PWD)$/out
WINDOWS_COMPILATION_IMAGE?=golang:1.20-windowsservercore-1809
SOURCE_COMPILATION_IMAGE?=lifecycle-img
BUILD_CTR?=lifecycle-ctr
DOCKER_CMD?=make test
@ -38,9 +39,12 @@ GOFILES := $(shell $(GOCMD) run tools$/lister$/main.go)
all: test build package
GOOS_ARCHS = linux/amd64 linux/arm64 linux/ppc64le linux/s390x darwin/amd64 darwin/arm64
build: build-linux-amd64 build-linux-arm64 build-windows-amd64 build-linux-s390x
build: build-linux-amd64 build-linux-arm64 build-linux-ppc64le build-linux-s390x
build-linux-amd64: build-linux-amd64-lifecycle build-linux-amd64-symlinks build-linux-amd64-launcher
build-linux-arm64: build-linux-arm64-lifecycle build-linux-arm64-symlinks build-linux-arm64-launcher
build-windows-amd64: build-windows-amd64-lifecycle build-windows-amd64-symlinks build-windows-amd64-launcher
build-linux-s390x: build-linux-s390x-lifecycle build-linux-s390x-symlinks build-linux-s390x-launcher
build-image-linux-amd64: build-linux-amd64 package-linux-amd64
build-image-linux-amd64: ARCHIVE_PATH=$(BUILD_DIR)/lifecycle-v$(LIFECYCLE_VERSION)+linux.x86-64.tgz
@ -52,60 +56,213 @@ build-image-linux-arm64: ARCHIVE_PATH=$(BUILD_DIR)/lifecycle-v$(LIFECYCLE_VERSIO
build-image-linux-arm64:
$(GOCMD) run ./tools/image/main.go -daemon -lifecyclePath $(ARCHIVE_PATH) -os linux -arch arm64 -tag lifecycle:$(LIFECYCLE_IMAGE_TAG)
build-image-linux-ppc64le: build-linux-ppc64le package-linux-ppc64le
build-image-linux-ppc64le: ARCHIVE_PATH=$(BUILD_DIR)/lifecycle-v$(LIFECYCLE_VERSION)+linux.ppc64le.tgz
build-image-linux-ppc64le:
$(GOCMD) run ./tools/image/main.go -daemon -lifecyclePath $(ARCHIVE_PATH) -os linux -arch ppc64le -tag lifecycle:$(LIFECYCLE_IMAGE_TAG)
build-image-windows-amd64: build-windows-amd64 package-windows-amd64
build-image-windows-amd64: ARCHIVE_PATH=$(BUILD_DIR)/lifecycle-v$(LIFECYCLE_VERSION)+windows.x86-64.tgz
build-image-windows-amd64:
$(GOCMD) run ./tools/image/main.go -daemon -lifecyclePath $(ARCHIVE_PATH) -os windows -arch amd64 -tag lifecycle:$(LIFECYCLE_IMAGE_TAG)
build-image-linux-s390x: build-linux-s390x package-linux-s390x
build-image-linux-s390x: ARCHIVE_PATH=$(BUILD_DIR)/lifecycle-v$(LIFECYCLE_VERSION)+linux.s390x.tgz
build-image-linux-s390x:
$(GOCMD) run ./tools/image/main.go -daemon -lifecyclePath $(ARCHIVE_PATH) -os linux -arch s390x -tag lifecycle:$(LIFECYCLE_IMAGE_TAG)
define build_targets
build-$(1)-$(2): build-$(1)-$(2)-lifecycle build-$(1)-$(2)-symlinks build-$(1)-$(2)-launcher
build-linux-amd64-lifecycle: $(BUILD_DIR)/linux-amd64/lifecycle/lifecycle
build-$(1)-$(2)-lifecycle: $(BUILD_DIR)/$(1)-$(2)/lifecycle/lifecycle
build-linux-arm64-lifecycle: $(BUILD_DIR)/linux-arm64/lifecycle/lifecycle
$$(BUILD_DIR)/$(1)-$(2)/lifecycle/lifecycle: export GOOS:=$(1)
$$(BUILD_DIR)/$(1)-$(2)/lifecycle/lifecycle: export GOARCH:=$(2)
$$(BUILD_DIR)/$(1)-$(2)/lifecycle/lifecycle: OUT_DIR?=$$(BUILD_DIR)/$$(GOOS)-$$(GOARCH)/lifecycle
$$(BUILD_DIR)/$(1)-$(2)/lifecycle/lifecycle: $$(GOFILES)
$$(BUILD_DIR)/$(1)-$(2)/lifecycle/lifecycle:
@echo "> Building lifecycle/lifecycle for $$(GOOS)/$$(GOARCH)..."
mkdir -p $$(OUT_DIR)
$$(GOENV) $$(GOBUILD) -o $$(OUT_DIR)/lifecycle -a ./cmd/lifecycle
build-linux-s390x-lifecycle: $(BUILD_DIR)/linux-s390x/lifecycle/lifecycle
build-$(1)-$(2)-symlinks: export GOOS:=$(1)
build-$(1)-$(2)-symlinks: export GOARCH:=$(2)
build-$(1)-$(2)-symlinks: OUT_DIR?=$$(BUILD_DIR)/$$(GOOS)-$$(GOARCH)/lifecycle
build-$(1)-$(2)-symlinks:
@echo "> Creating phase symlinks for $$(GOOS)/$$(GOARCH)..."
ln -sf lifecycle $$(OUT_DIR)/detector
ln -sf lifecycle $$(OUT_DIR)/analyzer
ln -sf lifecycle $$(OUT_DIR)/restorer
ln -sf lifecycle $$(OUT_DIR)/builder
ln -sf lifecycle $$(OUT_DIR)/exporter
ln -sf lifecycle $$(OUT_DIR)/rebaser
ln -sf lifecycle $$(OUT_DIR)/creator
ln -sf lifecycle $$(OUT_DIR)/extender
$(BUILD_DIR)/linux-amd64/lifecycle/lifecycle: export GOOS:=linux
$(BUILD_DIR)/linux-amd64/lifecycle/lifecycle: export GOARCH:=amd64
$(BUILD_DIR)/linux-amd64/lifecycle/lifecycle: OUT_DIR?=$(BUILD_DIR)/$(GOOS)-$(GOARCH)/lifecycle
$(BUILD_DIR)/linux-amd64/lifecycle/lifecycle: $(GOFILES)
$(BUILD_DIR)/linux-amd64/lifecycle/lifecycle:
@echo "> Building lifecycle/lifecycle for $(GOOS)/$(GOARCH)..."
mkdir -p $(OUT_DIR)
$(GOENV) $(GOBUILD) -o $(OUT_DIR)/lifecycle -a ./cmd/lifecycle
build-$(1)-$(2)-launcher: $$(BUILD_DIR)/$(1)-$(2)/lifecycle/launcher
$(BUILD_DIR)/linux-arm64/lifecycle/lifecycle: export GOOS:=linux
$(BUILD_DIR)/linux-arm64/lifecycle/lifecycle: export GOARCH:=arm64
$(BUILD_DIR)/linux-arm64/lifecycle/lifecycle: OUT_DIR?=$(BUILD_DIR)/$(GOOS)-$(GOARCH)/lifecycle
$(BUILD_DIR)/linux-arm64/lifecycle/lifecycle: $(GOFILES)
$(BUILD_DIR)/linux-arm64/lifecycle/lifecycle:
@echo "> Building lifecycle/lifecycle for $(GOOS)/$(GOARCH)..."
mkdir -p $(OUT_DIR)
$(GOENV) $(GOBUILD) -o $(OUT_DIR)/lifecycle -a ./cmd/lifecycle
$$(BUILD_DIR)/$(1)-$(2)/lifecycle/launcher: export GOOS:=$(1)
$$(BUILD_DIR)/$(1)-$(2)/lifecycle/launcher: export GOARCH:=$(2)
$$(BUILD_DIR)/$(1)-$(2)/lifecycle/launcher: OUT_DIR?=$$(BUILD_DIR)/$$(GOOS)-$$(GOARCH)/lifecycle
$$(BUILD_DIR)/$(1)-$(2)/lifecycle/launcher: $$(GOFILES)
$$(BUILD_DIR)/$(1)-$(2)/lifecycle/launcher:
@echo "> Building lifecycle/launcher for $$(GOOS)/$$(GOARCH)..."
mkdir -p $$(OUT_DIR)
$$(GOENV) $$(GOBUILD) -o $$(OUT_DIR)/launcher -a ./cmd/launcher
test $$$$(du -m $$(OUT_DIR)/launcher|cut -f 1) -le 3
endef
$(BUILD_DIR)/linux-s390x/lifecycle/lifecycle: export GOOS:=linux
$(BUILD_DIR)/linux-s390x/lifecycle/lifecycle: export GOARCH:=s390x
$(BUILD_DIR)/linux-s390x/lifecycle/lifecycle: OUT_DIR?=$(BUILD_DIR)/$(GOOS)-$(GOARCH)/lifecycle
$(BUILD_DIR)/linux-s390x/lifecycle/lifecycle: $(GOFILES)
$(BUILD_DIR)/linux-s390x/lifecycle/lifecycle:
@echo "> Building lifecycle/lifecycle for $(GOOS)/$(GOARCH)..."
mkdir -p $(OUT_DIR)
$(GOENV) $(GOBUILD) -o $(OUT_DIR)/lifecycle -a ./cmd/lifecycle
$(foreach ga,$(GOOS_ARCHS),$(eval $(call build_targets,$(word 1, $(subst /, ,$(ga))),$(word 2, $(subst /, ,$(ga))))))
build-linux-amd64-launcher: $(BUILD_DIR)/linux-amd64/lifecycle/launcher
generate-sbom: run-syft-linux-amd64 run-syft-linux-arm64 run-syft-linux-ppc64le run-syft-linux-s390x
$(BUILD_DIR)/linux-amd64/lifecycle/launcher: export GOOS:=linux
$(BUILD_DIR)/linux-amd64/lifecycle/launcher: export GOARCH:=amd64
$(BUILD_DIR)/linux-amd64/lifecycle/launcher: OUT_DIR?=$(BUILD_DIR)/$(GOOS)-$(GOARCH)/lifecycle
$(BUILD_DIR)/linux-amd64/lifecycle/launcher: $(GOFILES)
$(BUILD_DIR)/linux-amd64/lifecycle/launcher:
@echo "> Building lifecycle/launcher for $(GOOS)/$(GOARCH)..."
mkdir -p $(OUT_DIR)
$(GOENV) $(GOBUILD) -o $(OUT_DIR)/launcher -a ./cmd/launcher
test $$(du -m $(OUT_DIR)/launcher|cut -f 1) -le 3
build-linux-arm64-launcher: $(BUILD_DIR)/linux-arm64/lifecycle/launcher
$(BUILD_DIR)/linux-arm64/lifecycle/launcher: export GOOS:=linux
$(BUILD_DIR)/linux-arm64/lifecycle/launcher: export GOARCH:=arm64
$(BUILD_DIR)/linux-arm64/lifecycle/launcher: OUT_DIR?=$(BUILD_DIR)/$(GOOS)-$(GOARCH)/lifecycle
$(BUILD_DIR)/linux-arm64/lifecycle/launcher: $(GOFILES)
$(BUILD_DIR)/linux-arm64/lifecycle/launcher:
@echo "> Building lifecycle/launcher for $(GOOS)/$(GOARCH)..."
mkdir -p $(OUT_DIR)
$(GOENV) $(GOBUILD) -o $(OUT_DIR)/launcher -a ./cmd/launcher
test $$(du -m $(OUT_DIR)/launcher|cut -f 1) -le 3
build-linux-s390x-launcher: $(BUILD_DIR)/linux-s390x/lifecycle/launcher
$(BUILD_DIR)/linux-s390x/lifecycle/launcher: export GOOS:=linux
$(BUILD_DIR)/linux-s390x/lifecycle/launcher: export GOARCH:=s390x
$(BUILD_DIR)/linux-s390x/lifecycle/launcher: OUT_DIR?=$(BUILD_DIR)/$(GOOS)-$(GOARCH)/lifecycle
$(BUILD_DIR)/linux-s390x/lifecycle/launcher: $(GOFILES)
$(BUILD_DIR)/linux-s390x/lifecycle/launcher:
@echo "> Building lifecycle/launcher for $(GOOS)/$(GOARCH)..."
mkdir -p $(OUT_DIR)
$(GOENV) $(GOBUILD) -o $(OUT_DIR)/launcher -a ./cmd/launcher
test $$(du -m $(OUT_DIR)/launcher|cut -f 1) -le 3
build-linux-amd64-symlinks: export GOOS:=linux
build-linux-amd64-symlinks: export GOARCH:=amd64
build-linux-amd64-symlinks: OUT_DIR?=$(BUILD_DIR)/$(GOOS)-$(GOARCH)/lifecycle
build-linux-amd64-symlinks:
@echo "> Creating phase symlinks for $(GOOS)/$(GOARCH)..."
ln -sf lifecycle $(OUT_DIR)/detector
ln -sf lifecycle $(OUT_DIR)/analyzer
ln -sf lifecycle $(OUT_DIR)/restorer
ln -sf lifecycle $(OUT_DIR)/builder
ln -sf lifecycle $(OUT_DIR)/exporter
ln -sf lifecycle $(OUT_DIR)/rebaser
ln -sf lifecycle $(OUT_DIR)/creator
ln -sf lifecycle $(OUT_DIR)/extender
build-linux-arm64-symlinks: export GOOS:=linux
build-linux-arm64-symlinks: export GOARCH:=arm64
build-linux-arm64-symlinks: OUT_DIR?=$(BUILD_DIR)/$(GOOS)-$(GOARCH)/lifecycle
build-linux-arm64-symlinks:
@echo "> Creating phase symlinks for $(GOOS)/$(GOARCH)..."
ln -sf lifecycle $(OUT_DIR)/detector
ln -sf lifecycle $(OUT_DIR)/analyzer
ln -sf lifecycle $(OUT_DIR)/restorer
ln -sf lifecycle $(OUT_DIR)/builder
ln -sf lifecycle $(OUT_DIR)/exporter
ln -sf lifecycle $(OUT_DIR)/rebaser
ln -sf lifecycle $(OUT_DIR)/creator
ln -sf lifecycle $(OUT_DIR)/extender
build-linux-s390x-symlinks: export GOOS:=linux
build-linux-s390x-symlinks: export GOARCH:=s390x
build-linux-s390x-symlinks: OUT_DIR?=$(BUILD_DIR)/$(GOOS)-$(GOARCH)/lifecycle
build-linux-s390x-symlinks:
@echo "> Creating phase symlinks for $(GOOS)/$(GOARCH)..."
ln -sf lifecycle $(OUT_DIR)/detector
ln -sf lifecycle $(OUT_DIR)/analyzer
ln -sf lifecycle $(OUT_DIR)/restorer
ln -sf lifecycle $(OUT_DIR)/builder
ln -sf lifecycle $(OUT_DIR)/exporter
ln -sf lifecycle $(OUT_DIR)/rebaser
ln -sf lifecycle $(OUT_DIR)/creator
ln -sf lifecycle $(OUT_DIR)/extender
build-windows-amd64-lifecycle: $(BUILD_DIR)/windows-amd64/lifecycle/lifecycle.exe
$(BUILD_DIR)/windows-amd64/lifecycle/lifecycle.exe: export GOOS:=windows
$(BUILD_DIR)/windows-amd64/lifecycle/lifecycle.exe: export GOARCH:=amd64
$(BUILD_DIR)/windows-amd64/lifecycle/lifecycle.exe: OUT_DIR?=$(BUILD_DIR)$/$(GOOS)-$(GOARCH)$/lifecycle
$(BUILD_DIR)/windows-amd64/lifecycle/lifecycle.exe: $(GOFILES)
$(BUILD_DIR)/windows-amd64/lifecycle/lifecycle.exe:
@echo "> Building lifecycle/lifecycle for $(GOOS)/$(GOARCH)..."
$(GOBUILD) -o $(OUT_DIR)$/lifecycle.exe -a .$/cmd$/lifecycle
build-windows-amd64-launcher: $(BUILD_DIR)/windows-amd64/lifecycle/launcher.exe
$(BUILD_DIR)/windows-amd64/lifecycle/launcher.exe: export GOOS:=windows
$(BUILD_DIR)/windows-amd64/lifecycle/launcher.exe: export GOARCH:=amd64
$(BUILD_DIR)/windows-amd64/lifecycle/launcher.exe: OUT_DIR?=$(BUILD_DIR)$/$(GOOS)-$(GOARCH)$/lifecycle
$(BUILD_DIR)/windows-amd64/lifecycle/launcher.exe: $(GOFILES)
$(BUILD_DIR)/windows-amd64/lifecycle/launcher.exe:
@echo "> Building lifecycle/launcher for $(GOOS)/$(GOARCH)..."
$(GOBUILD) -o $(OUT_DIR)$/launcher.exe -a .$/cmd$/launcher
build-windows-amd64-symlinks: export GOOS:=windows
build-windows-amd64-symlinks: export GOARCH:=amd64
build-windows-amd64-symlinks: OUT_DIR?=$(BUILD_DIR)$/$(GOOS)-$(GOARCH)$/lifecycle
build-windows-amd64-symlinks:
@echo "> Creating phase symlinks for Windows..."
ifeq ($(OS),Windows_NT)
call del $(OUT_DIR)$/detector.exe
call del $(OUT_DIR)$/analyzer.exe
call del $(OUT_DIR)$/restorer.exe
call del $(OUT_DIR)$/builder.exe
call del $(OUT_DIR)$/exporter.exe
call del $(OUT_DIR)$/rebaser.exe
call del $(OUT_DIR)$/creator.exe
call mklink $(OUT_DIR)$/detector.exe lifecycle.exe
call mklink $(OUT_DIR)$/analyzer.exe lifecycle.exe
call mklink $(OUT_DIR)$/restorer.exe lifecycle.exe
call mklink $(OUT_DIR)$/builder.exe lifecycle.exe
call mklink $(OUT_DIR)$/exporter.exe lifecycle.exe
call mklink $(OUT_DIR)$/rebaser.exe lifecycle.exe
call mklink $(OUT_DIR)$/creator.exe lifecycle.exe
else
ln -sf lifecycle.exe $(OUT_DIR)$/detector.exe
ln -sf lifecycle.exe $(OUT_DIR)$/analyzer.exe
ln -sf lifecycle.exe $(OUT_DIR)$/restorer.exe
ln -sf lifecycle.exe $(OUT_DIR)$/builder.exe
ln -sf lifecycle.exe $(OUT_DIR)$/exporter.exe
ln -sf lifecycle.exe $(OUT_DIR)$/rebaser.exe
ln -sf lifecycle.exe $(OUT_DIR)$/creator.exe
endif
## DARWIN ARM64/AMD64
include lifecycle.mk
include launcher.mk
build-darwin-arm64: build-darwin-arm64-lifecycle build-darwin-arm64-launcher
build-darwin-arm64-lifecycle:
$(eval GOARCH := arm64)
$(eval TARGET := darwin-arm64)
$(eval OUT_DIR := $(BUILD_DIR)/$(TARGET)/lifecycle)
$(call build_lifecycle)
build-darwin-arm64-launcher:
$(eval GOARCH := arm64)
$(eval TARGET := darwin-arm64)
$(eval OUT_DIR := $(BUILD_DIR)/$(TARGET)/lifecycle)
$(call build_launcher)
build-darwin-amd64: build-darwin-amd64-lifecycle build-darwin-amd64-launcher
build-darwin-amd64-lifecycle:
$(eval GOARCH := amd64)
$(eval TARGET := darwin-amd64)
$(eval OUT_DIR := $(BUILD_DIR)/$(TARGET)/lifecycle)
$(call build_lifecycle)
build-darwin-amd64-launcher:
$(eval GOARCH := amd64)
$(eval TARGET := darwin-amd64)
$(eval OUT_DIR := $(BUILD_DIR)/$(TARGET)/lifecycle)
$(call build_launcher)
generate-sbom: run-syft-windows run-syft-linux-amd64 run-syft-linux-arm64 run-syft-linux-s390x
run-syft-windows: install-syft
run-syft-windows: export GOOS:=windows
run-syft-windows: export GOARCH:=amd64
run-syft-windows:
@echo "> Running syft..."
syft $(BUILD_DIR)/$(GOOS)-$(GOARCH)/lifecycle/lifecycle.exe -o json=$(BUILD_DIR)/$(GOOS)-$(GOARCH)/lifecycle/lifecycle.sbom.syft.json -o spdx-json=$(BUILD_DIR)/$(GOOS)-$(GOARCH)/lifecycle/lifecycle.sbom.spdx.json -o cyclonedx-json=$(BUILD_DIR)/$(GOOS)-$(GOARCH)/lifecycle/lifecycle.sbom.cdx.json
syft $(BUILD_DIR)/$(GOOS)-$(GOARCH)/lifecycle/launcher.exe -o json=$(BUILD_DIR)/$(GOOS)-$(GOARCH)/lifecycle/launcher.sbom.syft.json -o spdx-json=$(BUILD_DIR)/$(GOOS)-$(GOARCH)/lifecycle/launcher.sbom.spdx.json -o cyclonedx-json=$(BUILD_DIR)/$(GOOS)-$(GOARCH)/lifecycle/launcher.sbom.cdx.json
run-syft-linux-amd64: install-syft
run-syft-linux-amd64: export GOOS:=linux
@ -123,14 +280,6 @@ run-syft-linux-arm64:
syft $(BUILD_DIR)/$(GOOS)-$(GOARCH)/lifecycle/lifecycle -o json=$(BUILD_DIR)/$(GOOS)-$(GOARCH)/lifecycle/lifecycle.sbom.syft.json -o spdx-json=$(BUILD_DIR)/$(GOOS)-$(GOARCH)/lifecycle/lifecycle.sbom.spdx.json -o cyclonedx-json=$(BUILD_DIR)/$(GOOS)-$(GOARCH)/lifecycle/lifecycle.sbom.cdx.json
syft $(BUILD_DIR)/$(GOOS)-$(GOARCH)/lifecycle/launcher -o json=$(BUILD_DIR)/$(GOOS)-$(GOARCH)/lifecycle/launcher.sbom.syft.json -o spdx-json=$(BUILD_DIR)/$(GOOS)-$(GOARCH)/lifecycle/launcher.sbom.spdx.json -o cyclonedx-json=$(BUILD_DIR)/$(GOOS)-$(GOARCH)/lifecycle/launcher.sbom.cdx.json
run-syft-linux-ppc64le: install-syft
run-syft-linux-ppc64le: export GOOS:=linux
run-syft-linux-ppc64le: export GOARCH:=ppc64le
run-syft-linux-ppc64le:
@echo "> Running syft..."
syft $(BUILD_DIR)/$(GOOS)-$(GOARCH)/lifecycle/lifecycle -o json=$(BUILD_DIR)/$(GOOS)-$(GOARCH)/lifecycle/lifecycle.sbom.syft.json -o spdx-json=$(BUILD_DIR)/$(GOOS)-$(GOARCH)/lifecycle/lifecycle.sbom.spdx.json -o cyclonedx-json=$(BUILD_DIR)/$(GOOS)-$(GOARCH)/lifecycle/lifecycle.sbom.cdx.json
syft $(BUILD_DIR)/$(GOOS)-$(GOARCH)/lifecycle/launcher -o json=$(BUILD_DIR)/$(GOOS)-$(GOARCH)/lifecycle/launcher.sbom.syft.json -o spdx-json=$(BUILD_DIR)/$(GOOS)-$(GOARCH)/lifecycle/launcher.sbom.spdx.json -o cyclonedx-json=$(BUILD_DIR)/$(GOOS)-$(GOARCH)/lifecycle/launcher.sbom.cdx.json
run-syft-linux-s390x: install-syft
run-syft-linux-s390x: export GOOS:=linux
run-syft-linux-s390x: export GOARCH:=s390x
@ -143,26 +292,21 @@ install-syft:
@echo "> Installing syft..."
curl -sSfL https://raw.githubusercontent.com/anchore/syft/main/install.sh | sh -s -- -b /usr/local/bin
define install-go-tool
@echo "> Installing $(1)..."
$(GOCMD) install $(1)@$(shell $(GOCMD) list -m -f '{{.Version}}' $(2))
endef
install-goimports:
@echo "> Installing goimports..."
$(call install-go-tool,golang.org/x/tools/cmd/goimports,golang.org/x/tools)
$(GOCMD) install golang.org/x/tools/cmd/goimports@v0.1.2
install-yj:
@echo "> Installing yj..."
$(call install-go-tool,github.com/sclevine/yj,github.com/sclevine/yj)
$(GOCMD) install github.com/sclevine/yj@v0.0.0-20210612025309-737bdf40a5d1
install-mockgen:
@echo "> Installing mockgen..."
$(call install-go-tool,github.com/golang/mock/mockgen,github.com/golang/mock)
$(GOCMD) install github.com/golang/mock/mockgen@v1.5.0
install-golangci-lint:
@echo "> Installing golangci-lint..."
$(call install-go-tool,github.com/golangci/golangci-lint/v2/cmd/golangci-lint,github.com/golangci/golangci-lint/v2)
$(GOCMD) install github.com/golangci/golangci-lint/cmd/golangci-lint@v1.51.1
lint: install-golangci-lint
@echo "> Linting code..."
@ -205,7 +349,7 @@ clean:
@echo "> Cleaning workspace..."
rm -rf $(BUILD_DIR)
package: generate-sbom package-linux-amd64 package-linux-arm64 package-linux-ppc64le package-linux-s390x
package: generate-sbom package-linux-amd64 package-linux-arm64 package-windows-amd64 package-linux-s390x
package-linux-amd64: GOOS:=linux
package-linux-amd64: GOARCH:=amd64
@ -225,15 +369,6 @@ package-linux-arm64:
@echo "> Packaging lifecycle for $(GOOS)/$(GOARCH)..."
$(GOCMD) run $(PACKAGER) --inputDir $(INPUT_DIR) -archivePath $(ARCHIVE_PATH) -descriptorPath $(LIFECYCLE_DESCRIPTOR_PATH) -version $(LIFECYCLE_VERSION)
package-linux-ppc64le: GOOS:=linux
package-linux-ppc64le: GOARCH:=ppc64le
package-linux-ppc64le: INPUT_DIR:=$(BUILD_DIR)/$(GOOS)-$(GOARCH)/lifecycle
package-linux-ppc64le: ARCHIVE_PATH=$(BUILD_DIR)/lifecycle-v$(LIFECYCLE_VERSION)+$(GOOS).ppc64le.tgz
package-linux-ppc64le: PACKAGER=./tools/packager/main.go
package-linux-ppc64le:
@echo "> Packaging lifecycle for $(GOOS)/$(GOARCH)..."
$(GOCMD) run $(PACKAGER) --inputDir $(INPUT_DIR) -archivePath $(ARCHIVE_PATH) -descriptorPath $(LIFECYCLE_DESCRIPTOR_PATH) -version $(LIFECYCLE_VERSION)
package-linux-s390x: GOOS:=linux
package-linux-s390x: GOARCH:=s390x
package-linux-s390x: INPUT_DIR:=$(BUILD_DIR)/$(GOOS)-$(GOARCH)/lifecycle
@ -242,3 +377,26 @@ package-linux-s390x: PACKAGER=./tools/packager/main.go
package-linux-s390x:
@echo "> Packaging lifecycle for $(GOOS)/$(GOARCH)..."
$(GOCMD) run $(PACKAGER) --inputDir $(INPUT_DIR) -archivePath $(ARCHIVE_PATH) -descriptorPath $(LIFECYCLE_DESCRIPTOR_PATH) -version $(LIFECYCLE_VERSION)
package-windows-amd64: GOOS:=windows
package-windows-amd64: GOARCH:=amd64
package-windows-amd64: INPUT_DIR:=$(BUILD_DIR)$/$(GOOS)-$(GOARCH)$/lifecycle
package-windows-amd64: ARCHIVE_PATH=$(BUILD_DIR)$/lifecycle-v$(LIFECYCLE_VERSION)+$(GOOS).x86-64.tgz
package-windows-amd64: PACKAGER=.$/tools$/packager$/main.go
package-windows-amd64:
@echo "> Packaging lifecycle for $(GOOS)/$(GOARCH)..."
$(GOCMD) run $(PACKAGER) --inputDir $(INPUT_DIR) -archivePath $(ARCHIVE_PATH) -descriptorPath $(LIFECYCLE_DESCRIPTOR_PATH) -version $(LIFECYCLE_VERSION)
# Ensure workdir is clean and build image from .git
docker-build-source-image-windows: $(GOFILES)
docker-build-source-image-windows:
$(if $(shell git status --short), @echo Uncommitted changes. Refusing to run. && exit 1)
docker build .git -f tools/Dockerfile.windows --tag $(SOURCE_COMPILATION_IMAGE) --build-arg image_tag=$(WINDOWS_COMPILATION_IMAGE) --cache-from=$(SOURCE_COMPILATION_IMAGE) --isolation=process --compress
docker-run-windows: docker-build-source-image-windows
docker-run-windows:
@echo "> Running '$(DOCKER_CMD)' in docker windows..."
@docker volume rm -f lifecycle-out
docker run -v lifecycle-out:c:/lifecycle/out -e LIFECYCLE_VERSION -e PLATFORM_API -e BUILDPACK_API -v gopathcache:c:/gopath -v '\\.\pipe\docker_engine:\\.\pipe\docker_engine' --isolation=process --interactive --tty --rm $(SOURCE_COMPILATION_IMAGE) $(DOCKER_CMD)
docker run -v lifecycle-out:c:/lifecycle/out --rm $(SOURCE_COMPILATION_IMAGE) tar -cf- out | tar -xf-
@docker volume rm -f lifecycle-out

View File

@ -11,8 +11,6 @@ A reference implementation of the [Cloud Native Buildpacks specification](https:
## Supported APIs
| Lifecycle Version | Platform APIs | Buildpack APIs |
|-------------------|--------------------------------------------------------------------------------------------------------------------------------------------------|--------------------------------------------------------------------------------------------------------------------------------|
| 0.20.x | [0.7][p/0.7], [0.8][p/0.8], [0.9][p/0.9], [0.10][p/0.10], [0.11][p/0.11], [0.12][p/0.12], [0.13][p/0.13], [0.14][p/0.14] | [0.7][b/0.7], [0.8][b/0.8], [0.9][b/0.9], [0.10][b/0.10], [0.11][b/0.11] |
| 0.19.x | [0.7][p/0.7], [0.8][p/0.8], [0.9][p/0.9], [0.10][p/0.10], [0.11][p/0.11], [0.12][p/0.12], [0.13][p/0.13] | [0.7][b/0.7], [0.8][b/0.8], [0.9][b/0.9], [0.10][b/0.10], [0.11][b/0.11] |
| 0.18.x | [0.7][p/0.7], [0.8][p/0.8], [0.9][p/0.9], [0.10][p/0.10], [0.11][p/0.11], [0.12][p/0.12] | [0.7][b/0.7], [0.8][b/0.8], [0.9][b/0.9], [0.10][b/0.10] |
| 0.17.x | [0.3][p/0.3], [0.4][p/0.4], [0.5][p/0.5], [0.6][p/0.6], [0.7][p/0.7], [0.8][p/0.8], [0.9][p/0.9], [0.10][p/0.10], [0.11][p/0.11], [0.12][p/0.12] | [0.2][b/0.2], [0.3][b/0.3], [0.4][b/0.4], [0.5][b/0.5], [0.6][b/0.6], [0.7][b/0.7], [0.8][b/0.8], [0.9][b/0.9], [0.10][b/0.10] |
| 0.16.x | [0.3][p/0.3], [0.4][p/0.4], [0.5][p/0.5], [0.6][p/0.6], [0.7][p/0.7], [0.8][p/0.8], [0.9][p/0.9], [0.10][p/0.10], [0.11][p/0.11] | [0.2][b/0.2], [0.3][b/0.3], [0.4][b/0.4], [0.5][b/0.5], [0.6][b/0.6], [0.7][b/0.7], [0.8][b/0.8], [0.9][b/0.9] |
@ -29,7 +27,6 @@ A reference implementation of the [Cloud Native Buildpacks specification](https:
[b/0.8]: https://github.com/buildpacks/spec/tree/buildpack/v0.8/buildpack.md
[b/0.9]: https://github.com/buildpacks/spec/tree/buildpack/v0.9/buildpack.md
[b/0.10]: https://github.com/buildpacks/spec/tree/buildpack/v0.10/buildpack.md
[b/0.11]: https://github.com/buildpacks/spec/tree/buildpack/v0.11/buildpack.md
[p/0.2]: https://github.com/buildpacks/spec/blob/platform/v0.2/platform.md
[p/0.3]: https://github.com/buildpacks/spec/blob/platform/v0.3/platform.md
[p/0.4]: https://github.com/buildpacks/spec/blob/platform/v0.4/platform.md
@ -41,8 +38,6 @@ A reference implementation of the [Cloud Native Buildpacks specification](https:
[p/0.10]: https://github.com/buildpacks/spec/blob/platform/v0.10/platform.md
[p/0.11]: https://github.com/buildpacks/spec/blob/platform/v0.11/platform.md
[p/0.12]: https://github.com/buildpacks/spec/blob/platform/v0.12/platform.md
[p/0.13]: https://github.com/buildpacks/spec/blob/platform/v0.13/platform.md
[p/0.14]: https://github.com/buildpacks/spec/blob/platform/v0.14/platform.md
\* denotes unreleased version

View File

@ -1,73 +1,22 @@
# Release Finalization
## Release Finalization
## Types of releases
#### New minor
* For newly supported Platform or Buildpack API versions, or breaking changes (e.g., API deprecations).
#### Pre-release aka release candidate
* Ideally we should ship a pre-release (waiting a few days for folks to try it out) before we ship a new minor.
* We typically don't ship pre-releases for patches or backports.
#### New patch
* For go version updates, CVE fixes / dependency bumps, bug fixes, etc.
* Review the latest commits on `main` to determine if any are unacceptable for a patch - if there are commits that should be excluded, branch off the latest tag for the current minor and cherry-pick commits over.
#### Backport
* New patch for an old minor. Typically, to help folks out who haven't yet upgraded from [unsupported APIs](https://github.com/buildpacks/rfcs/blob/main/text/0110-deprecate-apis.md).
* For go version updates, CVE fixes / dependency bumps, bug fixes, etc.
* Branch off the latest tag for the desired minor.
## Release Finalization Steps
### Step 1 - Prepare
Determine the type of release ([new minor](#new-minor), [pre-release](#pre-release-aka-release-candidate), [new patch](#new-patch), or [backport](#backport)) and prepare the branch accordingly.
**To prepare the release branch:**
1. Check open PRs for any dependabot updates that should be merged.
1. Create a release branch in the format `release/0.99.0-rc.1` (for pre-releases) or `release/0.99.0` (for final releases).
* New commits to this branch will trigger the `build` workflow and produce a lifecycle image: `buildpacksio/lifecycle:<commit sha>`.
To cut a pre-release:
1. If applicable, ensure the README is updated with the latest supported apis (example PR: https://github.com/buildpacks/lifecycle/pull/550).
* For final releases (not pre-releases), remove the pre-release note (`*`) for the latest apis.
1. Create a release branch in the format `release/0.99.0-rc.1`. New commits to this branch will trigger the `build` workflow and produce a lifecycle image: `buildpacksio/lifecycle:<commit sha>`.
1. When ready to cut the release, manually trigger the `draft-release` workflow: Actions -> draft-release -> Run workflow -> Use workflow from branch: `release/0.99.0-rc.1`. This will create a draft release on GitHub using the artifacts from the `build` workflow run for the latest commit on the release branch.
1. Edit the release notes as necessary.
1. Perform any manual validation of the artifacts.
1. When ready to publish the release, edit the release page and click "Publish release". This will trigger the `post-release` workflow that will re-tag the lifecycle image from `buildpacksio/lifecycle:<commit sha>` to `buildpacksio/lifecycle:0.99.0` but will NOT update the `latest` tag.
**For final releases (not pre-releases):**
To cut a release:
1. Ensure the relevant spec APIs have been released.
1. Ensure the `lifecycle/0.99.0` milestone on the [docs repo](https://github.com/buildpacks/docs/blob/main/RELEASE.md#lump-changes) is complete, such that every new feature in the lifecycle is fully explained in the `release/lifecycle/0.99` branch on the docs repo, and [migration guides](https://github.com/buildpacks/docs/tree/main/content/docs/reference/spec/migration) (if relevant) are included.
### Step 2 - Publish the Release
1. Manually trigger the `draft-release` workflow: Actions -> draft-release -> Run workflow -> Use workflow from branch: `release/<release version>`. This will create a draft release on GitHub using the artifacts from the `build` workflow run for the latest commit on the release branch.
1. Create a release branch in the format `release/0.99.0`. New commits to this branch will trigger the `build` workflow and produce a lifecycle image: `buildpacksio/lifecycle:<commit sha>`.
1. If applicable, ensure the README is updated with the latest supported apis (example PR: https://github.com/buildpacks/lifecycle/pull/550) and remove the pre-release note for the latest apis.
1. When ready to cut the release, manually trigger the `draft-release` workflow: Actions -> draft-release -> Run workflow -> Use workflow from branch: `release/0.99.0`. This will create a draft release on GitHub using the artifacts from the `build` workflow run for the latest commit on the release branch.
1. Edit the release notes as necessary.
1. Perform any manual validation of the artifacts as necessary (usually none).
1. Edit the release page and click "Publish release".
* This will trigger the `post-release` workflow that will re-tag the lifecycle image from `buildpacksio/lifecycle:<commit sha>` to `buildpacksio/lifecycle:<release version>`.
* For final releases ONLY, this will also re-tag the lifecycle image from `buildpacksio/lifecycle:<commit sha>` to `buildpacksio/lifecycle:latest`.
### Step 3 - Follow-up
**For pre-releases:**
* Ask the relevant teams to try out the pre-released artifacts.
**For final releases:**
* Update the `main` branch to remove the pre-release note in [README.md](https://github.com/buildpacks/lifecycle/blob/main/README.md) and/or merge `release/0.99.0` into `main`.
* Ask the learning team to merge the `release/lifecycle/0.99` branch into `main` on the docs repo.
## Go version updates
Go version updates should be released as a [new minor](#new-minor) or [new patch](#new-patch) release.
### New Patch
If the go patch is in [actions/go-versions](https://github.com/actions/go-versions/pulls?q=is%3Apr+is%3Aclosed) then CI should pull it in automatically without any action needed.
We simply need to create the release branch and let the pipeline run.
### New Minor
We typically do this when the existing patch version exceeds 6 - e.g., `1.22.6`. This means we have about 6 months to upgrade before the current minor becomes unsupported due to the introduction of the new n+2 minor.
#### Steps
1. Update go.mod
1. Search for the old `major.minor`, there are a few files that need to be updated (example PR: https://github.com/buildpacks/lifecycle/pull/1405/files)
1. Update the linter to a version that supports the current `major.minor`
1. Fix any lint errors as necessary
1. Perform any manual validation of the artifacts.
1. When ready to publish the release, edit the release page and click "Publish release". This will trigger the `post-release` workflow that will re-tag the lifecycle image from `buildpacksio/lifecycle:<commit sha>` to `buildpacksio/lifecycle:0.99.0` and `buildpacksio/lifecycle:latest`.
1. Once released
- Update the `main` branch to remove the pre-release note in [README.md](https://github.com/buildpacks/lifecycle/blob/main/README.md) and/or merge `release/0.99.0` into `main`.
- Ask the learning team to merge the `release/lifecycle/0.99` branch into `main` on the docs repo.

View File

@ -23,6 +23,7 @@ const (
var (
latestPlatformAPI = api.Platform.Latest().String()
buildDir string
cacheFixtureDir string
)
func TestVersion(t *testing.T) {

View File

@ -5,6 +5,7 @@ import (
"os"
"os/exec"
"path/filepath"
"runtime"
"strings"
"testing"
@ -36,6 +37,7 @@ func TestAnalyzer(t *testing.T) {
analyzeImage = analyzeTest.testImageRef
analyzerPath = analyzeTest.containerBinaryPath
cacheFixtureDir = filepath.Join("testdata", "cache-dir")
analyzeRegAuthConfig = analyzeTest.targetRegistry.authConfig
analyzeRegNetwork = analyzeTest.targetRegistry.network
analyzeDaemonFixtures = analyzeTest.targetDaemon.fixtures
@ -127,6 +129,8 @@ func testAnalyzerFunc(platformAPI string) func(t *testing.T, when spec.G, it spe
when("the provided layers directory isn't writeable", func() {
it("recursively chowns the directory", func() {
h.SkipIf(t, runtime.GOOS == "windows", "Not relevant on Windows")
analyzeFlags := []string{"-run-image", analyzeRegFixtures.ReadOnlyRunImage}
output := h.DockerRun(t,
@ -199,6 +203,8 @@ func testAnalyzerFunc(platformAPI string) func(t *testing.T, when spec.G, it spe
})
it("drops privileges", func() {
h.SkipIf(t, runtime.GOOS == "windows", "Not relevant on Windows")
analyzeArgs := []string{
"-analyzed", "/some-dir/some-analyzed.toml",
"-run-image", analyzeRegFixtures.ReadOnlyRunImage,
@ -444,7 +450,7 @@ func testAnalyzerFunc(platformAPI string) func(t *testing.T, when spec.G, it spe
output, err := cmd.CombinedOutput()
h.AssertNotNil(t, err)
expected := "ensure registry read/write access to " + analyzeRegFixtures.InaccessibleImage
expected := "validating registry write access: ensure registry read/write access to " + analyzeRegFixtures.InaccessibleImage
h.AssertStringContains(t, string(output), expected)
})
})
@ -481,7 +487,7 @@ func testAnalyzerFunc(platformAPI string) func(t *testing.T, when spec.G, it spe
analyzer := assertAnalyzedMetadata(t, filepath.Join(copyDir, "analyzed.toml"))
h.AssertNotNil(t, analyzer.RunImage)
analyzedImagePath := filepath.Join(path.RootDir, "layout-repo", "index.docker.io", "library", "busybox", "latest")
reference := fmt.Sprintf("%s@%s", analyzedImagePath, "sha256:f75f3d1a317fc82c793d567de94fc8df2bece37acd5f2bd364a0d91a0d1f3dab")
reference := fmt.Sprintf("%s@%s", analyzedImagePath, "sha256:834f8848308af7090ed7b2270071d28411afc42078e3ba395b1b0a78e2f8b0e2")
h.AssertEq(t, analyzer.RunImage.Reference, reference)
})
})
@ -515,7 +521,7 @@ func assertAnalyzedMetadata(t *testing.T, path string) *files.Analyzed {
h.AssertNil(t, err)
h.AssertEq(t, len(contents) > 0, true)
analyzedMD, err := files.Handler.ReadAnalyzed(path, cmd.DefaultLogger)
analyzedMD, err := files.ReadAnalyzed(path, cmd.DefaultLogger)
h.AssertNil(t, err)
return &analyzedMD

View File

@ -6,8 +6,10 @@ import (
"os"
"os/exec"
"path/filepath"
"runtime"
"testing"
"github.com/BurntSushi/toml"
"github.com/sclevine/spec"
"github.com/sclevine/spec/report"
@ -24,6 +26,8 @@ var (
)
func TestBuilder(t *testing.T) {
h.SkipIf(t, runtime.GOOS == "windows", "Builder acceptance tests are not yet supported on Windows")
info, err := h.DockerCli(t).Info(context.TODO())
h.AssertNil(t, err)
@ -217,6 +221,7 @@ func testBuilder(t *testing.T, when spec.G, it spec.S) {
h.AssertStringContains(t, md.Buildpacks[0].ID, "hello_world")
h.AssertStringContains(t, md.Buildpacks[0].Version, "0.0.1")
h.AssertStringContains(t, md.Extensions[0].API, "0.10")
h.AssertEq(t, md.Extensions[0].Extension, false) // this shows that `extension = true` is not redundantly printed in group.toml
h.AssertStringContains(t, md.Extensions[0].ID, "hello_world")
h.AssertStringContains(t, md.Extensions[0].Version, "0.0.1")
})
@ -237,7 +242,7 @@ func testBuilder(t *testing.T, when spec.G, it spec.S) {
)
output, err := command.CombinedOutput()
h.AssertNotNil(t, err)
expected := "failed to read group file: open /layers/group.toml: no such file or directory"
expected := "failed to read buildpack group: open /layers/group.toml: no such file or directory"
h.AssertStringContains(t, string(output), expected)
})
})
@ -274,7 +279,7 @@ func testBuilder(t *testing.T, when spec.G, it spec.S) {
)
output, err := command.CombinedOutput()
h.AssertNotNil(t, err)
expected := "failed to read group file: toml: line 1: expected '.' or '=', but got 'a' instead"
expected := "failed to read buildpack group: toml: line 1: expected '.' or '=', but got 'a' instead"
h.AssertStringContains(t, string(output), expected)
})
})
@ -313,7 +318,7 @@ func testBuilder(t *testing.T, when spec.G, it spec.S) {
)
output, err := command.CombinedOutput()
h.AssertNotNil(t, err)
expected := "failed to read plan file: open /layers/plan.toml: no such file or directory"
expected := "failed to parse detect plan: open /layers/plan.toml: no such file or directory"
h.AssertStringContains(t, string(output), expected)
})
})
@ -353,7 +358,7 @@ func testBuilder(t *testing.T, when spec.G, it spec.S) {
)
output, err := command.CombinedOutput()
h.AssertNotNil(t, err)
expected := "failed to read plan file: toml: line 1: expected '.' or '=', but got 'a' instead"
expected := "failed to parse detect plan: toml: line 1: expected '.' or '=', but got 'a' instead"
h.AssertStringContains(t, string(output), expected)
})
})
@ -528,8 +533,9 @@ func getBuilderMetadata(t *testing.T, path string) (string, *files.BuildMetadata
contents, _ := os.ReadFile(path)
h.AssertEq(t, len(contents) > 0, true)
buildMD, err := files.Handler.ReadBuildMetadata(path, api.MustParse(latestPlatformAPI))
var buildMD files.BuildMetadata
_, err := toml.Decode(string(contents), &buildMD)
h.AssertNil(t, err)
return string(contents), buildMD
return string(contents), &buildMD
}

View File

@ -1,4 +1,5 @@
//go:build acceptance
// +build acceptance
package acceptance
@ -6,6 +7,7 @@ import (
"os"
"os/exec"
"path/filepath"
"runtime"
"testing"
"time"
@ -29,6 +31,8 @@ var (
)
func TestCreator(t *testing.T) {
h.SkipIf(t, runtime.GOOS == "windows", "Creator acceptance tests are not yet supported on Windows")
testImageDockerContext := filepath.Join("testdata", "creator")
createTest = NewPhaseTest(t, "creator", testImageDockerContext)
createTest.Start(t)
@ -36,6 +40,7 @@ func TestCreator(t *testing.T) {
createImage = createTest.testImageRef
creatorPath = createTest.containerBinaryPath
cacheFixtureDir = filepath.Join("testdata", "creator", "cache-dir")
createRegAuthConfig = createTest.targetRegistry.authConfig
createRegNetwork = createTest.targetRegistry.network
createDaemonFixtures = createTest.targetDaemon.fixtures
@ -71,29 +76,6 @@ func testCreatorFunc(platformAPI string) func(t *testing.T, when spec.G, it spec
})
})
when("detected order contains extensions", func() {
it("errors", func() {
h.SkipIf(t, api.MustParse(platformAPI).LessThan("0.10"), "")
cmd := exec.Command(
"docker", "run", "--rm",
"--env", "CNB_PLATFORM_API="+platformAPI,
"--env", "CNB_REGISTRY_AUTH="+createRegAuthConfig,
"--network", createRegNetwork,
createImage,
ctrPath(creatorPath),
"-log-level", "debug",
"-order", "/cnb/order-with-extensions.toml",
"-run-image", createRegFixtures.ReadOnlyRunImage,
createRegFixtures.SomeAppImage,
) // #nosec G204
output, err := cmd.CombinedOutput()
h.AssertNotNil(t, err)
expected := "detected order contains extensions which is not supported by the creator"
h.AssertStringContains(t, string(output), expected)
})
})
when("daemon case", func() {
it.After(func() {
h.DockerImageRemove(t, createdImageName)

View File

@ -1,4 +1,5 @@
//go:build acceptance
// +build acceptance
package acceptance
@ -8,13 +9,15 @@ import (
"os"
"os/exec"
"path/filepath"
"runtime"
"testing"
"github.com/BurntSushi/toml"
"github.com/sclevine/spec"
"github.com/sclevine/spec/report"
"github.com/buildpacks/lifecycle/api"
"github.com/buildpacks/lifecycle/cmd"
"github.com/buildpacks/lifecycle/buildpack"
"github.com/buildpacks/lifecycle/platform/files"
h "github.com/buildpacks/lifecycle/testhelpers"
)
@ -28,6 +31,8 @@ var (
)
func TestDetector(t *testing.T) {
h.SkipIf(t, runtime.GOOS == "windows", "Detector acceptance tests are not yet supported on Windows")
info, err := h.DockerCli(t).Info(context.TODO())
h.AssertNil(t, err)
@ -48,375 +53,363 @@ func TestDetector(t *testing.T) {
)
defer h.DockerImageRemove(t, detectImage)
for _, platformAPI := range api.Platform.Supported {
if platformAPI.LessThan("0.12") {
continue
}
spec.Run(t, "acceptance-detector/"+platformAPI.String(), testDetectorFunc(platformAPI.String()), spec.Parallel(), spec.Report(report.Terminal{}))
}
spec.Run(t, "acceptance-detector", testDetector, spec.Parallel(), spec.Report(report.Terminal{}))
}
func testDetectorFunc(platformAPI string) func(t *testing.T, when spec.G, it spec.S) {
return func(t *testing.T, when spec.G, it spec.S) {
when("called with arguments", func() {
it("errors", func() {
command := exec.Command(
"docker",
"run",
"--rm",
"--env", "CNB_PLATFORM_API="+platformAPI,
detectImage,
"some-arg",
)
output, err := command.CombinedOutput()
h.AssertNotNil(t, err)
expected := "failed to parse arguments: received unexpected arguments"
h.AssertStringContains(t, string(output), expected)
})
func testDetector(t *testing.T, when spec.G, it spec.S) {
when("called with arguments", func() {
it("errors", func() {
command := exec.Command(
"docker",
"run",
"--rm",
"--env", "CNB_PLATFORM_API="+latestPlatformAPI,
detectImage,
"some-arg",
)
output, err := command.CombinedOutput()
h.AssertNotNil(t, err)
expected := "failed to parse arguments: received unexpected arguments"
h.AssertStringContains(t, string(output), expected)
})
})
when("running as a root", func() {
it("errors", func() {
command := exec.Command(
"docker",
"run",
"--rm",
"--user",
"root",
"--env", "CNB_PLATFORM_API="+platformAPI,
detectImage,
)
output, err := command.CombinedOutput()
h.AssertNotNil(t, err)
expected := "failed to detect: refusing to run as root"
h.AssertStringContains(t, string(output), expected)
})
when("running as a root", func() {
it("errors", func() {
command := exec.Command(
"docker",
"run",
"--rm",
"--user",
"root",
"--env", "CNB_PLATFORM_API="+latestPlatformAPI,
detectImage,
)
output, err := command.CombinedOutput()
h.AssertNotNil(t, err)
expected := "failed to detect: refusing to run as root"
h.AssertStringContains(t, string(output), expected)
})
})
when("read buildpack order file failed", func() {
it("errors", func() {
// no order.toml file in the default search locations
command := exec.Command(
"docker",
"run",
"--rm",
"--env", "CNB_PLATFORM_API="+platformAPI,
detectImage,
)
output, err := command.CombinedOutput()
h.AssertNotNil(t, err)
expected := "failed to initialize detector: reading order"
h.AssertStringContains(t, string(output), expected)
})
when("read buildpack order file failed", func() {
it("errors", func() {
// no order.toml file in the default search locations
command := exec.Command(
"docker",
"run",
"--rm",
"--env", "CNB_PLATFORM_API="+latestPlatformAPI,
detectImage,
)
output, err := command.CombinedOutput()
h.AssertNotNil(t, err)
expected := "failed to initialize detector: reading order"
h.AssertStringContains(t, string(output), expected)
})
})
when("no buildpack group passed detection", func() {
it("errors and exits with the expected code", func() {
command := exec.Command(
"docker",
"run",
"--rm",
"--env", "CNB_ORDER_PATH=/cnb/orders/fail_detect_order.toml",
"--env", "CNB_PLATFORM_API="+platformAPI,
detectImage,
)
output, err := command.CombinedOutput()
h.AssertNotNil(t, err)
failErr, ok := err.(*exec.ExitError)
if !ok {
t.Fatalf("expected an error of type exec.ExitError")
}
h.AssertEq(t, failErr.ExitCode(), 20) // platform code for failed detect
when("no buildpack group passed detection", func() {
it("errors and exits with the expected code", func() {
command := exec.Command(
"docker",
"run",
"--rm",
"--env", "CNB_ORDER_PATH=/cnb/orders/fail_detect_order.toml",
"--env", "CNB_PLATFORM_API="+latestPlatformAPI,
detectImage,
)
output, err := command.CombinedOutput()
h.AssertNotNil(t, err)
failErr, ok := err.(*exec.ExitError)
if !ok {
t.Fatalf("expected an error of type exec.ExitError")
}
h.AssertEq(t, failErr.ExitCode(), 20) // platform code for failed detect
expected1 := `======== Output: fail_detect_buildpack@some_version ========
expected1 := `======== Output: fail_detect_buildpack@some_version ========
Opted out of detection
======== Results ========
fail: fail_detect_buildpack@some_version`
h.AssertStringContains(t, string(output), expected1)
expected2 := "No buildpack groups passed detection."
h.AssertStringContains(t, string(output), expected2)
})
fail: fail_detect_buildpack@some_version
`
h.AssertStringContains(t, string(output), expected1)
expected2 := "No buildpack groups passed detection."
h.AssertStringContains(t, string(output), expected2)
})
})
when("there is a buildpack group that passes detection", func() {
var copyDir, containerName string
it.Before(func() {
containerName = "test-container-" + h.RandString(10)
var err error
copyDir, err = os.MkdirTemp("", "test-docker-copy-")
h.AssertNil(t, err)
})
when("there is a buildpack group that passes detection", func() {
var copyDir, containerName string
it.Before(func() {
containerName = "test-container-" + h.RandString(10)
var err error
copyDir, err = os.MkdirTemp("", "test-docker-copy-")
h.AssertNil(t, err)
})
it.After(func() {
if h.DockerContainerExists(t, containerName) {
h.Run(t, exec.Command("docker", "rm", containerName))
}
os.RemoveAll(copyDir)
})
it("writes group.toml and plan.toml at the default locations", func() {
output := h.DockerRunAndCopy(t,
containerName,
copyDir,
"/layers",
detectImage,
h.WithFlags("--user", userID,
"--env", "CNB_ORDER_PATH=/cnb/orders/simple_order.toml",
"--env", "CNB_PLATFORM_API="+platformAPI,
),
h.WithArgs(),
)
// check group.toml
foundGroupTOML := filepath.Join(copyDir, "layers", "group.toml")
group, err := files.Handler.ReadGroup(foundGroupTOML)
h.AssertNil(t, err)
h.AssertEq(t, group.Group[0].ID, "simple_buildpack")
h.AssertEq(t, group.Group[0].Version, "simple_buildpack_version")
// check plan.toml
foundPlanTOML := filepath.Join(copyDir, "layers", "plan.toml")
buildPlan, err := files.Handler.ReadPlan(foundPlanTOML)
h.AssertNil(t, err)
h.AssertEq(t, buildPlan.Entries[0].Providers[0].ID, "simple_buildpack")
h.AssertEq(t, buildPlan.Entries[0].Providers[0].Version, "simple_buildpack_version")
h.AssertEq(t, buildPlan.Entries[0].Requires[0].Name, "some_requirement")
h.AssertEq(t, buildPlan.Entries[0].Requires[0].Metadata["some_metadata_key"], "some_metadata_val")
h.AssertEq(t, buildPlan.Entries[0].Requires[0].Metadata["version"], "some_version")
// check output
h.AssertStringContains(t, output, "simple_buildpack simple_buildpack_version")
h.AssertStringDoesNotContain(t, output, "======== Results ========") // log output is info level as detect passed
})
it.After(func() {
if h.DockerContainerExists(t, containerName) {
h.Run(t, exec.Command("docker", "rm", containerName))
}
os.RemoveAll(copyDir)
})
when("environment variables are provided for buildpack and app directories and for the output files", func() {
var copyDir, containerName string
it("writes group.toml and plan.toml at the default locations", func() {
output := h.DockerRunAndCopy(t,
containerName,
copyDir,
"/layers",
detectImage,
h.WithFlags("--user", userID,
"--env", "CNB_ORDER_PATH=/cnb/orders/simple_order.toml",
"--env", "CNB_PLATFORM_API="+latestPlatformAPI,
),
h.WithArgs(),
)
it.Before(func() {
containerName = "test-container-" + h.RandString(10)
var err error
copyDir, err = os.MkdirTemp("", "test-docker-copy-")
h.AssertNil(t, err)
})
// check group.toml
foundGroupTOML := filepath.Join(copyDir, "layers", "group.toml")
var buildpackGroup buildpack.Group
_, err := toml.DecodeFile(foundGroupTOML, &buildpackGroup)
h.AssertNil(t, err)
h.AssertEq(t, buildpackGroup.Group[0].ID, "simple_buildpack")
h.AssertEq(t, buildpackGroup.Group[0].Version, "simple_buildpack_version")
it.After(func() {
if h.DockerContainerExists(t, containerName) {
h.Run(t, exec.Command("docker", "rm", containerName))
}
os.RemoveAll(copyDir)
})
// check plan.toml
tempPlanToml := filepath.Join(copyDir, "layers", "plan.toml")
var buildPlan files.Plan
_, err = toml.DecodeFile(tempPlanToml, &buildPlan)
h.AssertNil(t, err)
h.AssertEq(t, buildPlan.Entries[0].Providers[0].ID, "simple_buildpack")
h.AssertEq(t, buildPlan.Entries[0].Providers[0].Version, "simple_buildpack_version")
h.AssertEq(t, buildPlan.Entries[0].Requires[0].Name, "some_requirement")
h.AssertEq(t, buildPlan.Entries[0].Requires[0].Metadata["some_metadata_key"], "some_metadata_val")
h.AssertEq(t, buildPlan.Entries[0].Requires[0].Metadata["version"], "some_version")
it("writes group.toml and plan.toml in the right locations and with the right names", func() {
// check output
h.AssertStringContains(t, output, "simple_buildpack simple_buildpack_version")
h.AssertStringDoesNotContain(t, output, "======== Results ========") // log output is info level as detect passed
})
})
when("environment variables are provided for buildpack and app directories and for the output files", func() {
var copyDir, containerName string
it.Before(func() {
containerName = "test-container-" + h.RandString(10)
var err error
copyDir, err = os.MkdirTemp("", "test-docker-copy-")
h.AssertNil(t, err)
})
it.After(func() {
if h.DockerContainerExists(t, containerName) {
h.Run(t, exec.Command("docker", "rm", containerName))
}
os.RemoveAll(copyDir)
})
it("writes group.toml and plan.toml in the right locations and with the right names", func() {
h.DockerRunAndCopy(t,
containerName,
copyDir,
"/layers",
detectImage,
h.WithFlags("--user", userID,
"--env", "CNB_ORDER_PATH=/cnb/orders/always_detect_order.toml",
"--env", "CNB_BUILDPACKS_DIR=/cnb/custom_buildpacks",
"--env", "CNB_APP_DIR=/custom_workspace",
"--env", "CNB_GROUP_PATH=./custom_group.toml",
"--env", "CNB_PLAN_PATH=./custom_plan.toml",
"--env", "CNB_PLATFORM_DIR=/custom_platform",
"--env", "CNB_PLATFORM_API="+latestPlatformAPI,
),
h.WithArgs("-log-level=debug"),
)
// check group.toml
foundGroupTOML := filepath.Join(copyDir, "layers", "custom_group.toml")
var buildpackGroup buildpack.Group
_, err := toml.DecodeFile(foundGroupTOML, &buildpackGroup)
h.AssertNil(t, err)
h.AssertEq(t, buildpackGroup.Group[0].ID, "always_detect_buildpack")
h.AssertEq(t, buildpackGroup.Group[0].Version, "always_detect_buildpack_version")
// check plan.toml - should be empty since we're using always_detect_order.toml so there is no "actual plan"
tempPlanToml := filepath.Join(copyDir, "layers", "custom_plan.toml")
planContents, err := os.ReadFile(tempPlanToml)
h.AssertNil(t, err)
h.AssertEq(t, len(planContents) == 0, true)
// check platform directory
logs := h.Run(t, exec.Command("docker", "logs", containerName))
expectedPlatformPath := "platform_path: /custom_platform"
expectedAppDir := "app_dir: /custom_workspace"
h.AssertStringContains(t, logs, expectedPlatformPath)
h.AssertStringContains(t, logs, expectedAppDir)
})
})
when("-order is provided", func() {
var copyDir, containerName, expectedOrderTOMLPath string
it.Before(func() {
containerName = "test-container-" + h.RandString(10)
var err error
copyDir, err = os.MkdirTemp("", "test-docker-copy-")
h.AssertNil(t, err)
simpleOrderTOML := filepath.Join("testdata", "detector", "container", "cnb", "orders", "simple_order.toml")
expectedOrderTOMLPath, err = filepath.Abs(simpleOrderTOML)
h.AssertNil(t, err)
})
it.After(func() {
if h.DockerContainerExists(t, containerName) {
h.Run(t, exec.Command("docker", "rm", containerName))
}
os.RemoveAll(copyDir)
})
when("the order.toml exists", func() {
it("processes the provided order.toml", func() {
h.DockerRunAndCopy(t,
containerName,
copyDir,
"/layers",
detectImage,
h.WithFlags("--user", userID,
"--env", "CNB_ORDER_PATH=/cnb/orders/always_detect_order.toml",
"--env", "CNB_BUILDPACKS_DIR=/cnb/custom_buildpacks",
"--env", "CNB_APP_DIR=/custom_workspace",
"--env", "CNB_GROUP_PATH=./custom_group.toml",
"--env", "CNB_PLAN_PATH=./custom_plan.toml",
"--env", "CNB_PLATFORM_DIR=/custom_platform",
"--env", "CNB_PLATFORM_API="+platformAPI,
"--volume", expectedOrderTOMLPath+":/custom/order.toml",
"--env", "CNB_PLATFORM_API="+latestPlatformAPI,
),
h.WithArgs(
"-log-level=debug",
"-order=/custom/order.toml",
),
h.WithArgs("-log-level=debug"),
)
// check group.toml
foundGroupTOML := filepath.Join(copyDir, "layers", "custom_group.toml")
group, err := files.Handler.ReadGroup(foundGroupTOML)
h.AssertNil(t, err)
h.AssertEq(t, group.Group[0].ID, "always_detect_buildpack")
h.AssertEq(t, group.Group[0].Version, "always_detect_buildpack_version")
// check plan.toml - should be empty since we're using always_detect_order.toml so there is no "actual plan"
tempPlanToml := filepath.Join(copyDir, "layers", "custom_plan.toml")
planContents, err := os.ReadFile(tempPlanToml)
h.AssertNil(t, err)
h.AssertEq(t, len(planContents) == 0, true)
// check platform directory
logs := h.Run(t, exec.Command("docker", "logs", containerName))
expectedPlatformPath := "platform_path: /custom_platform"
expectedAppDir := "app_dir: /custom_workspace"
h.AssertStringContains(t, logs, expectedPlatformPath)
h.AssertStringContains(t, logs, expectedAppDir)
})
})
when("-order is provided", func() {
var copyDir, containerName, expectedOrderTOMLPath string
it.Before(func() {
containerName = "test-container-" + h.RandString(10)
var err error
copyDir, err = os.MkdirTemp("", "test-docker-copy-")
h.AssertNil(t, err)
simpleOrderTOML := filepath.Join("testdata", "detector", "container", "cnb", "orders", "simple_order.toml")
expectedOrderTOMLPath, err = filepath.Abs(simpleOrderTOML)
h.AssertNil(t, err)
})
it.After(func() {
if h.DockerContainerExists(t, containerName) {
h.Run(t, exec.Command("docker", "rm", containerName))
}
os.RemoveAll(copyDir)
})
when("the order.toml exists", func() {
it("processes the provided order.toml", func() {
h.DockerRunAndCopy(t,
containerName,
copyDir,
"/layers",
detectImage,
h.WithFlags("--user", userID,
"--volume", expectedOrderTOMLPath+":/custom/order.toml",
"--env", "CNB_PLATFORM_API="+platformAPI,
),
h.WithArgs(
"-log-level=debug",
"-order=/custom/order.toml",
),
)
// check group.toml
foundGroupTOML := filepath.Join(copyDir, "layers", "group.toml")
group, err := files.Handler.ReadGroup(foundGroupTOML)
h.AssertNil(t, err)
h.AssertEq(t, group.Group[0].ID, "simple_buildpack")
h.AssertEq(t, group.Group[0].Version, "simple_buildpack_version")
})
})
when("the order.toml does not exist", func() {
it("errors", func() {
command := exec.Command("docker", "run",
"--user", userID,
"--rm",
"--env", "CNB_PLATFORM_API="+platformAPI,
detectImage,
"-order=/custom/order.toml")
output, err := command.CombinedOutput()
h.AssertNotNil(t, err)
expected := "failed to initialize detector: reading order: failed to read order file: open /custom/order.toml: no such file or directory"
h.AssertStringContains(t, string(output), expected)
})
})
when("the order.toml contains a buildpack using an unsupported api", func() {
it("errors", func() {
command := exec.Command("docker", "run",
"--user", userID,
"--rm",
"--env", "CNB_PLATFORM_API="+platformAPI,
detectImage,
"-order=/cnb/orders/bad_api.toml")
output, err := command.CombinedOutput()
h.AssertNotNil(t, err)
failErr, ok := err.(*exec.ExitError)
if !ok {
t.Fatalf("expected an error of type exec.ExitError")
}
h.AssertEq(t, failErr.ExitCode(), 12) // platform code for buildpack api error
expected := "buildpack API version '0.1' is incompatible with the lifecycle"
h.AssertStringContains(t, string(output), expected)
})
})
})
when("-order contains extensions", func() {
var containerName, copyDir, orderPath string
it.Before(func() {
containerName = "test-container-" + h.RandString(10)
var err error
copyDir, err = os.MkdirTemp("", "test-docker-copy-")
h.AssertNil(t, err)
orderPath, err = filepath.Abs(filepath.Join("testdata", "detector", "container", "cnb", "orders", "order_with_ext.toml"))
h.AssertNil(t, err)
})
it.After(func() {
if h.DockerContainerExists(t, containerName) {
h.Run(t, exec.Command("docker", "rm", containerName))
}
os.RemoveAll(copyDir)
})
it("processes the provided order.toml", func() {
experimentalMode := "warn"
if api.MustParse(platformAPI).AtLeast("0.13") {
experimentalMode = "error"
}
output := h.DockerRunAndCopy(t,
containerName,
copyDir,
"/layers",
detectImage,
h.WithFlags(
"--user", userID,
"--volume", orderPath+":/layers/order.toml",
"--env", "CNB_PLATFORM_API="+platformAPI,
"--env", "CNB_EXPERIMENTAL_MODE="+experimentalMode,
),
h.WithArgs(
"-analyzed=/layers/analyzed.toml",
"-extensions=/cnb/extensions",
"-generated=/layers/generated",
"-log-level=debug",
"-run=/layers/run.toml", // /cnb/run.toml is the default location of run.toml
),
)
t.Log("runs /bin/detect for buildpacks and extensions")
if api.MustParse(platformAPI).LessThan("0.13") {
h.AssertStringContains(t, output, "Platform requested experimental feature 'Dockerfiles'")
}
h.AssertStringContains(t, output, "FOO=val-from-build-config")
h.AssertStringContains(t, output, "simple_extension: output from /bin/detect")
t.Log("writes group.toml")
foundGroupTOML := filepath.Join(copyDir, "layers", "group.toml")
group, err := files.Handler.ReadGroup(foundGroupTOML)
var buildpackGroup buildpack.Group
_, err := toml.DecodeFile(foundGroupTOML, &buildpackGroup)
h.AssertNil(t, err)
h.AssertEq(t, group.GroupExtensions[0].ID, "simple_extension")
h.AssertEq(t, group.GroupExtensions[0].Version, "simple_extension_version")
h.AssertEq(t, group.Group[0].ID, "buildpack_for_ext")
h.AssertEq(t, group.Group[0].Version, "buildpack_for_ext_version")
h.AssertEq(t, group.Group[0].Extension, false)
t.Log("writes plan.toml")
foundPlanTOML := filepath.Join(copyDir, "layers", "plan.toml")
buildPlan, err := files.Handler.ReadPlan(foundPlanTOML)
h.AssertNil(t, err)
h.AssertEq(t, len(buildPlan.Entries), 0) // this shows that the plan was filtered to remove `requires` provided by extensions
t.Log("runs /bin/generate for extensions")
h.AssertStringContains(t, output, "simple_extension: output from /bin/generate")
var dockerfilePath string
if api.MustParse(platformAPI).LessThan("0.13") {
t.Log("copies the generated Dockerfiles to the output directory")
dockerfilePath = filepath.Join(copyDir, "layers", "generated", "run", "simple_extension", "Dockerfile")
} else {
dockerfilePath = filepath.Join(copyDir, "layers", "generated", "simple_extension", "run.Dockerfile")
}
h.AssertPathExists(t, dockerfilePath)
contents, err := os.ReadFile(dockerfilePath)
h.AssertEq(t, string(contents), "FROM some-run-image-from-extension\n")
t.Log("records the new run image in analyzed.toml")
foundAnalyzedTOML := filepath.Join(copyDir, "layers", "analyzed.toml")
analyzedMD, err := files.Handler.ReadAnalyzed(foundAnalyzedTOML, cmd.DefaultLogger)
h.AssertNil(t, err)
h.AssertEq(t, analyzedMD.RunImage.Image, "some-run-image-from-extension")
h.AssertEq(t, buildpackGroup.Group[0].ID, "simple_buildpack")
h.AssertEq(t, buildpackGroup.Group[0].Version, "simple_buildpack_version")
})
})
}
when("the order.toml does not exist", func() {
it("errors", func() {
command := exec.Command("docker", "run",
"--user", userID,
"--rm",
"--env", "CNB_PLATFORM_API="+latestPlatformAPI,
detectImage,
"-order=/custom/order.toml")
output, err := command.CombinedOutput()
h.AssertNotNil(t, err)
expected := "failed to initialize detector: reading order: failed to read order file: open /custom/order.toml: no such file or directory"
h.AssertStringContains(t, string(output), expected)
})
})
when("the order.toml contains a buildpack using an unsupported api", func() {
it("errors", func() {
command := exec.Command("docker", "run",
"--user", userID,
"--rm",
"--env", "CNB_PLATFORM_API="+latestPlatformAPI,
detectImage,
"-order=/cnb/orders/bad_api.toml")
output, err := command.CombinedOutput()
h.AssertNotNil(t, err)
failErr, ok := err.(*exec.ExitError)
if !ok {
t.Fatalf("expected an error of type exec.ExitError")
}
h.AssertEq(t, failErr.ExitCode(), 12) // platform code for buildpack api error
expected := "buildpack API version '0.1' is incompatible with the lifecycle"
h.AssertStringContains(t, string(output), expected)
})
})
})
when("-order contains extensions", func() {
var containerName, copyDir, orderPath string
it.Before(func() {
containerName = "test-container-" + h.RandString(10)
var err error
copyDir, err = os.MkdirTemp("", "test-docker-copy-")
h.AssertNil(t, err)
orderPath, err = filepath.Abs(filepath.Join("testdata", "detector", "container", "cnb", "orders", "order_with_ext.toml"))
h.AssertNil(t, err)
})
it.After(func() {
if h.DockerContainerExists(t, containerName) {
h.Run(t, exec.Command("docker", "rm", containerName))
}
os.RemoveAll(copyDir)
})
it("processes the provided order.toml", func() {
output := h.DockerRunAndCopy(t,
containerName,
copyDir,
"/layers",
detectImage,
h.WithFlags(
"--user", userID,
"--volume", orderPath+":/layers/order.toml",
"--env", "CNB_PLATFORM_API="+latestPlatformAPI,
"--env", "CNB_EXPERIMENTAL_MODE=warn", // required as the default is `error` if unset
),
h.WithArgs(
"-analyzed=/layers/analyzed.toml",
"-extensions=/cnb/extensions",
"-generated=/layers/generated",
"-log-level=debug",
"-run=/layers/run.toml", // /cnb/run.toml is the default location of run.toml
),
)
t.Log("runs /bin/detect for buildpacks and extensions")
h.AssertStringContains(t, output, "Platform requested experimental feature 'Dockerfiles'")
h.AssertStringContains(t, output, "FOO=val-from-build-config")
h.AssertStringContains(t, output, "simple_extension: output from /bin/detect")
t.Log("writes group.toml")
foundGroupTOML := filepath.Join(copyDir, "layers", "group.toml")
var buildpackGroup buildpack.Group
_, err := toml.DecodeFile(foundGroupTOML, &buildpackGroup)
h.AssertNil(t, err)
h.AssertEq(t, buildpackGroup.GroupExtensions[0].ID, "simple_extension")
h.AssertEq(t, buildpackGroup.GroupExtensions[0].Version, "simple_extension_version")
h.AssertEq(t, buildpackGroup.GroupExtensions[0].Extension, false) // this shows that `extension = true` is not redundantly printed in group.toml
h.AssertEq(t, buildpackGroup.Group[0].ID, "buildpack_for_ext")
h.AssertEq(t, buildpackGroup.Group[0].Version, "buildpack_for_ext_version")
h.AssertEq(t, buildpackGroup.Group[0].Extension, false)
t.Log("writes plan.toml")
foundPlanTOML := filepath.Join(copyDir, "layers", "plan.toml")
var plan files.Plan
_, err = toml.DecodeFile(foundPlanTOML, &plan)
h.AssertNil(t, err)
h.AssertEq(t, len(plan.Entries), 0) // this shows that the plan was filtered to remove `requires` provided by extensions
t.Log("runs /bin/generate for extensions")
h.AssertStringContains(t, output, "simple_extension: output from /bin/generate")
t.Log("copies the generated Dockerfiles to the output directory")
dockerfilePath := filepath.Join(copyDir, "layers", "generated", "run", "simple_extension", "Dockerfile")
h.AssertPathExists(t, dockerfilePath)
contents, err := os.ReadFile(dockerfilePath)
h.AssertEq(t, string(contents), "FROM some-run-image-from-extension\n")
t.Log("records the new run image in analyzed.toml")
foundAnalyzedTOML := filepath.Join(copyDir, "layers", "analyzed.toml")
var analyzed files.Analyzed
_, err = toml.DecodeFile(foundAnalyzedTOML, &analyzed)
h.AssertNil(t, err)
h.AssertEq(t, analyzed.RunImage.Image, "some-run-image-from-extension")
})
})
}

View File

@ -1,17 +1,16 @@
//go:build acceptance
// +build acceptance
package acceptance
import (
"context"
"crypto/sha256"
"encoding/hex"
"encoding/json"
"fmt"
"io"
"os"
"os/exec"
"path/filepath"
"runtime"
"strings"
"testing"
"time"
@ -19,7 +18,6 @@ import (
"github.com/buildpacks/imgutil"
"github.com/google/go-containerregistry/pkg/authn"
"github.com/google/go-containerregistry/pkg/v1/remote"
"github.com/pkg/errors"
"github.com/sclevine/spec"
"github.com/sclevine/spec/report"
@ -27,7 +25,6 @@ import (
"github.com/buildpacks/lifecycle/auth"
"github.com/buildpacks/lifecycle/cache"
"github.com/buildpacks/lifecycle/cmd"
"github.com/buildpacks/lifecycle/internal/fsutil"
"github.com/buildpacks/lifecycle/internal/path"
"github.com/buildpacks/lifecycle/platform/files"
h "github.com/buildpacks/lifecycle/testhelpers"
@ -44,6 +41,8 @@ var (
)
func TestExporter(t *testing.T) {
h.SkipIf(t, runtime.GOOS == "windows", "Exporter acceptance tests are not yet supported on Windows")
testImageDockerContext := filepath.Join("testdata", "exporter")
exportTest = NewPhaseTest(t, "exporter", testImageDockerContext)
@ -52,6 +51,7 @@ func TestExporter(t *testing.T) {
exportImage = exportTest.testImageRef
exporterPath = exportTest.containerBinaryPath
cacheFixtureDir = filepath.Join("testdata", "exporter", "cache-dir")
exportRegAuthConfig = exportTest.targetRegistry.authConfig
exportRegNetwork = exportTest.targetRegistry.network
exportDaemonFixtures = exportTest.targetDaemon.fixtures
@ -64,146 +64,138 @@ func TestExporter(t *testing.T) {
func testExporterFunc(platformAPI string) func(t *testing.T, when spec.G, it spec.S) {
return func(t *testing.T, when spec.G, it spec.S) {
var exportedImageName string
it.After(func() {
_, _, _ = h.RunE(exec.Command("docker", "rmi", exportedImageName)) // #nosec G204
})
when("daemon case", func() {
var exportedImageName string
when("first build", func() {
when("app", func() {
it("is created", func() {
exportFlags := []string{"-daemon", "-log-level", "debug"}
exportArgs := append([]string{ctrPath(exporterPath)}, exportFlags...)
exportedImageName = "some-exported-image-" + h.RandString(10)
exportArgs = append(exportArgs, exportedImageName)
it.After(func() {
_, _, _ = h.RunE(exec.Command("docker", "rmi", exportedImageName)) // #nosec G204
})
output := h.DockerRun(t,
exportImage,
h.WithFlags(append(
dockerSocketMount,
"--env", "CNB_PLATFORM_API="+platformAPI,
)...),
h.WithArgs(exportArgs...),
)
h.AssertStringContains(t, output, "Saving "+exportedImageName)
it("app is created", func() {
exportFlags := []string{"-daemon", "-log-level", "debug"}
exportArgs := append([]string{ctrPath(exporterPath)}, exportFlags...)
exportedImageName = "some-exported-image-" + h.RandString(10)
exportArgs = append(exportArgs, exportedImageName)
if api.MustParse(platformAPI).AtLeast("0.11") {
extensions := []string{"sbom.cdx.json", "sbom.spdx.json", "sbom.syft.json"}
for _, extension := range extensions {
h.AssertStringContains(t, output, fmt.Sprintf("Copying SBOM lifecycle.%s to %s", extension, filepath.Join(path.RootDir, "layers", "sbom", "build", "buildpacksio_lifecycle", extension)))
h.AssertStringContains(t, output, fmt.Sprintf("Copying SBOM launcher.%s to %s", extension, filepath.Join(path.RootDir, "layers", "sbom", "launch", "buildpacksio_lifecycle", "launcher", extension)))
}
} else {
h.AssertStringDoesNotContain(t, output, "Copying SBOM")
}
output := h.DockerRun(t,
exportImage,
h.WithFlags(append(
dockerSocketMount,
"--env", "CNB_PLATFORM_API="+platformAPI,
)...),
h.WithArgs(exportArgs...),
)
h.AssertStringContains(t, output, "Saving "+exportedImageName)
if api.MustParse(platformAPI).AtLeast("0.12") {
expectedHistory := []string{
"Buildpacks Launcher Config",
"Buildpacks Application Launcher",
"Application Layer",
"Software Bill-of-Materials",
"Layer: 'launch-layer', Created by buildpack: cacher_buildpack@cacher_v1",
"", // run image layer
}
assertDaemonImageHasHistory(t, exportedImageName, expectedHistory)
} else {
assertDaemonImageDoesNotHaveHistory(t, exportedImageName)
}
if api.MustParse(platformAPI).AtLeast("0.11") {
extensions := []string{"sbom.cdx.json", "sbom.spdx.json", "sbom.syft.json"}
for _, extension := range extensions {
h.AssertStringContains(t, output, fmt.Sprintf("Copying SBOM lifecycle.%s to %s", extension, filepath.Join(path.RootDir, "layers", "sbom", "build", "buildpacksio_lifecycle", extension)))
h.AssertStringContains(t, output, fmt.Sprintf("Copying SBOM launcher.%s to %s", extension, filepath.Join(path.RootDir, "layers", "sbom", "launch", "buildpacksio_lifecycle", "launcher", extension)))
}
} else {
h.AssertStringDoesNotContain(t, output, "Copying SBOM")
}
if api.MustParse(platformAPI).AtLeast("0.12") {
expectedHistory := []string{
"Buildpacks Launcher Config",
"Buildpacks Application Launcher",
"Application Layer",
"Software Bill-of-Materials",
"Layer: 'corrupted-layer', Created by buildpack: corrupted_buildpack@corrupted_v1",
"Layer: 'launch-layer', Created by buildpack: cacher_buildpack@cacher_v1",
"", // run image layer
}
assertDaemonImageHasHistory(t, exportedImageName, expectedHistory)
} else {
assertDaemonImageDoesNotHaveHistory(t, exportedImageName)
}
assertImageOSAndArchAndCreatedAt(t, exportedImageName, exportTest, imgutil.NormalizedDateTime)
})
when("using extensions", func() {
it.Before(func() {
h.SkipIf(t, api.MustParse(platformAPI).LessThan("0.12"), "")
assertImageOSAndArchAndCreatedAt(t, exportedImageName, exportTest, imgutil.NormalizedDateTime)
})
})
it("app is created from the extended run image", func() {
exportFlags := []string{
"-analyzed", "/layers/run-image-extended-analyzed.toml", // though the run image is a registry image, it also exists in the daemon with the same tag
"-daemon",
"-extended", "/layers/some-extended-dir",
"-log-level", "debug",
"-run", "/cnb/run.toml", // though the run image is a registry image, it also exists in the daemon with the same tag
}
exportArgs := append([]string{ctrPath(exporterPath)}, exportFlags...)
exportedImageName = "some-exported-image-" + h.RandString(10)
exportArgs = append(exportArgs, exportedImageName)
when("using extensions", func() {
it.Before(func() {
h.SkipIf(t, api.MustParse(platformAPI).LessThan("0.12"), "")
})
// get run image top layer
inspect, _, err := h.DockerCli(t).ImageInspectWithRaw(context.TODO(), exportTest.targetRegistry.fixtures.ReadOnlyRunImage)
h.AssertNil(t, err)
layers := inspect.RootFS.Layers
runImageFixtureTopLayerSHA := layers[len(layers)-1]
runImageFixtureSHA := inspect.ID
it("is created from the extended run image", func() {
exportFlags := []string{
"-analyzed", "/layers/run-image-extended-analyzed.toml", // though the run image is a registry image, it also exists in the daemon with the same tag
"-daemon",
"-extended", "/layers/some-extended-dir",
"-log-level", "debug",
"-run", "/cnb/run.toml", // though the run image is a registry image, it also exists in the daemon with the same tag
}
exportArgs := append([]string{ctrPath(exporterPath)}, exportFlags...)
exportedImageName = "some-exported-image-" + h.RandString(10)
exportArgs = append(exportArgs, exportedImageName)
experimentalMode := "warn"
if api.MustParse(platformAPI).AtLeast("0.13") {
experimentalMode = "error"
}
// get run image top layer
inspect, _, err := h.DockerCli(t).ImageInspectWithRaw(context.TODO(), exportTest.targetRegistry.fixtures.ReadOnlyRunImage)
h.AssertNil(t, err)
layers := inspect.RootFS.Layers
runImageFixtureTopLayerSHA := layers[len(layers)-1]
runImageFixtureSHA := inspect.ID
output := h.DockerRun(t,
exportImage,
h.WithFlags(append(
dockerSocketMount,
"--env", "CNB_EXPERIMENTAL_MODE="+experimentalMode,
"--env", "CNB_PLATFORM_API="+platformAPI,
)...),
h.WithArgs(exportArgs...),
)
h.AssertStringContains(t, output, "Saving "+exportedImageName)
output := h.DockerRun(t,
exportImage,
h.WithFlags(append(
dockerSocketMount,
"--env", "CNB_EXPERIMENTAL_MODE=warn",
"--env", "CNB_PLATFORM_API="+platformAPI,
)...),
h.WithArgs(exportArgs...),
)
h.AssertStringContains(t, output, "Saving "+exportedImageName)
assertImageOSAndArchAndCreatedAt(t, exportedImageName, exportTest, imgutil.NormalizedDateTime)
expectedHistory := []string{
"Buildpacks Launcher Config",
"Buildpacks Application Launcher",
"Application Layer",
"Software Bill-of-Materials",
"Layer: 'corrupted-layer', Created by buildpack: corrupted_buildpack@corrupted_v1",
"Layer: 'launch-layer', Created by buildpack: cacher_buildpack@cacher_v1",
"Layer: 'RUN mkdir /some-other-dir && echo some-data > /some-other-dir/some-file && echo some-data > /some-other-file', Created by extension: second-extension",
"Layer: 'RUN mkdir /some-dir && echo some-data > /some-dir/some-file && echo some-data > /some-file', Created by extension: first-extension",
"", // run image layer
}
assertDaemonImageHasHistory(t, exportedImageName, expectedHistory)
t.Log("bases the exported image on the extended run image")
inspect, _, err = h.DockerCli(t).ImageInspectWithRaw(context.TODO(), exportedImageName)
h.AssertNil(t, err)
h.AssertEq(t, inspect.Config.Labels["io.buildpacks.rebasable"], "false") // from testdata/exporter/container/layers/some-extended-dir/run/sha256_<sha>/blobs/sha256/<config>
t.Log("Adds extension layers")
type testCase struct {
expectedDiffID string
layerIndex int
}
testCases := []testCase{
{
expectedDiffID: "sha256:fb54d2566824d6630d94db0b008d9a544a94d3547a424f52e2fd282b648c0601", // from testdata/exporter/container/layers/some-extended-dir/run/sha256_<c72eda1c>/blobs/sha256/65c2873d397056a5cb4169790654d787579b005f18b903082b177d4d9b4aecf5 after un-compressing and zeroing timestamps
layerIndex: 1,
},
{
expectedDiffID: "sha256:1018c7d3584c4f7fa3ef4486d1a6a11b93956b9d8bfe0898a3e0fbd248c984d8", // from testdata/exporter/container/layers/some-extended-dir/run/sha256_<c72eda1c>/blobs/sha256/0fb9b88c9cbe9f11b4c8da645f390df59f5949632985a0bfc2a842ef17b2ad18 after un-compressing and zeroing timestamps
layerIndex: 2,
},
}
for _, tc := range testCases {
h.AssertEq(t, inspect.RootFS.Layers[tc.layerIndex], tc.expectedDiffID)
}
t.Log("sets the layers metadata label according to the new spec")
var lmd files.LayersMetadata
lmdJSON := inspect.Config.Labels["io.buildpacks.lifecycle.metadata"]
h.AssertNil(t, json.Unmarshal([]byte(lmdJSON), &lmd))
h.AssertEq(t, lmd.RunImage.Image, exportTest.targetRegistry.fixtures.ReadOnlyRunImage) // from analyzed.toml
h.AssertEq(t, lmd.RunImage.Mirrors, []string{"mirror1", "mirror2"}) // from run.toml
h.AssertEq(t, lmd.RunImage.TopLayer, runImageFixtureTopLayerSHA)
h.AssertEq(t, lmd.RunImage.Reference, strings.TrimPrefix(runImageFixtureSHA, "sha256:"))
assertImageOSAndArchAndCreatedAt(t, exportedImageName, exportTest, imgutil.NormalizedDateTime)
expectedHistory := []string{
"Buildpacks Launcher Config",
"Buildpacks Application Launcher",
"Application Layer",
"Software Bill-of-Materials",
"Layer: 'launch-layer', Created by buildpack: cacher_buildpack@cacher_v1",
"Layer: 'RUN apt-get update && apt-get install -y tree', Created by extension: tree",
"Layer: 'RUN apt-get update && apt-get install -y curl', Created by extension: curl",
"", // run image layer
}
assertDaemonImageHasHistory(t, exportedImageName, expectedHistory)
t.Log("bases the exported image on the extended run image")
inspect, _, err = h.DockerCli(t).ImageInspectWithRaw(context.TODO(), exportedImageName)
h.AssertNil(t, err)
h.AssertEq(t, inspect.Config.Labels["io.buildpacks.rebasable"], "false") // from testdata/exporter/container/layers/extended/sha256:<sha>/blobs/sha256/<config>
t.Log("Adds extension layers")
diffIDFromExt1 := "sha256:60600f423214c27fd184ebc96ae765bf2b4703c9981fb4205d28dd35e7eec4ae"
diffIDFromExt2 := "sha256:1d811b70500e2e9a5e5b8ca7429ef02e091cdf4657b02e456ec54dd1baea0a66"
var foundFromExt1, foundFromExt2 bool
for _, layer := range inspect.RootFS.Layers {
if layer == diffIDFromExt1 {
foundFromExt1 = true
}
if layer == diffIDFromExt2 {
foundFromExt2 = true
}
}
h.AssertEq(t, foundFromExt1, true)
h.AssertEq(t, foundFromExt2, true)
t.Log("sets the layers metadata label according to the new spec")
var lmd files.LayersMetadata
lmdJSON := inspect.Config.Labels["io.buildpacks.lifecycle.metadata"]
h.AssertNil(t, json.Unmarshal([]byte(lmdJSON), &lmd))
h.AssertEq(t, lmd.RunImage.Image, exportTest.targetRegistry.fixtures.ReadOnlyRunImage) // from analyzed.toml
h.AssertEq(t, lmd.RunImage.Mirrors, []string{"mirror1", "mirror2"}) // from run.toml
h.AssertEq(t, lmd.RunImage.TopLayer, runImageFixtureTopLayerSHA)
h.AssertEq(t, lmd.RunImage.Reference, strings.TrimPrefix(runImageFixtureSHA, "sha256:"))
})
})
})
when("SOURCE_DATE_EPOCH is set", func() {
it("app is created with config CreatedAt set to SOURCE_DATE_EPOCH", func() {
it("Image CreatedAt is set to SOURCE_DATE_EPOCH", func() {
h.SkipIf(t, api.MustParse(platformAPI).LessThan("0.9"), "SOURCE_DATE_EPOCH support added in 0.9")
expectedTime := time.Date(2022, 1, 5, 5, 5, 5, 0, time.UTC)
exportFlags := []string{"-daemon"}
@ -230,93 +222,10 @@ func testExporterFunc(platformAPI string) func(t *testing.T, when spec.G, it spe
})
when("registry case", func() {
var exportedImageName string
it.After(func() {
_, _, _ = h.RunE(exec.Command("docker", "rmi", exportedImageName)) // #nosec G204
})
it("app is created", func() {
var exportFlags []string
exportArgs := append([]string{ctrPath(exporterPath)}, exportFlags...)
exportedImageName = exportTest.RegRepoName("some-exported-image-" + h.RandString(10))
exportArgs = append(exportArgs, exportedImageName)
output := h.DockerRun(t,
exportImage,
h.WithFlags(
"--env", "CNB_PLATFORM_API="+platformAPI,
"--env", "CNB_REGISTRY_AUTH="+exportRegAuthConfig,
"--network", exportRegNetwork,
),
h.WithArgs(exportArgs...),
)
h.AssertStringContains(t, output, "Saving "+exportedImageName)
h.Run(t, exec.Command("docker", "pull", exportedImageName))
assertImageOSAndArchAndCreatedAt(t, exportedImageName, exportTest, imgutil.NormalizedDateTime)
})
when("registry is insecure", func() {
it.Before(func() {
h.SkipIf(t, api.MustParse(platformAPI).LessThan("0.12"), "")
})
it("uses http protocol", func() {
var exportFlags []string
exportArgs := append([]string{ctrPath(exporterPath)}, exportFlags...)
exportedImageName = exportTest.RegRepoName("some-insecure-exported-image-" + h.RandString(10))
exportArgs = append(exportArgs, exportedImageName)
insecureRegistry := "host.docker.internal/bar"
insecureAnalyzed := "/layers/analyzed_insecure.toml"
_, _, err := h.DockerRunWithError(t,
exportImage,
h.WithFlags(
"--env", "CNB_PLATFORM_API="+platformAPI,
"--env", "CNB_INSECURE_REGISTRIES="+insecureRegistry,
"--env", "CNB_ANALYZED_PATH="+insecureAnalyzed,
"--network", exportRegNetwork,
),
h.WithArgs(exportArgs...),
)
h.AssertStringContains(t, err.Error(), "http://host.docker.internal")
})
})
when("SOURCE_DATE_EPOCH is set", func() {
it("app is created with config CreatedAt set to SOURCE_DATE_EPOCH", func() {
h.SkipIf(t, api.MustParse(platformAPI).LessThan("0.9"), "SOURCE_DATE_EPOCH support added in 0.9")
expectedTime := time.Date(2022, 1, 5, 5, 5, 5, 0, time.UTC)
var exportFlags []string
exportArgs := append([]string{ctrPath(exporterPath)}, exportFlags...)
exportedImageName = exportTest.RegRepoName("some-exported-image-" + h.RandString(10))
exportArgs = append(exportArgs, exportedImageName)
output := h.DockerRun(t,
exportImage,
h.WithFlags(
"--env", "CNB_PLATFORM_API="+platformAPI,
"--env", "CNB_REGISTRY_AUTH="+exportRegAuthConfig,
"--env", "SOURCE_DATE_EPOCH="+fmt.Sprintf("%d", expectedTime.Unix()),
"--network", exportRegNetwork,
),
h.WithArgs(exportArgs...),
)
h.AssertStringContains(t, output, "Saving "+exportedImageName)
h.Run(t, exec.Command("docker", "pull", exportedImageName))
assertImageOSAndArchAndCreatedAt(t, exportedImageName, exportTest, expectedTime)
})
})
// FIXME: move this out of the registry block
when("cache", func() {
when("image case", func() {
it("cache is created", func() {
cacheImageName := exportTest.RegRepoName("some-cache-image-" + h.RandString(10))
exportFlags := []string{"-cache-image", cacheImageName}
when("first build", func() {
when("app", func() {
it("is created", func() {
var exportFlags []string
exportArgs := append([]string{ctrPath(exporterPath)}, exportFlags...)
exportedImageName = exportTest.RegRepoName("some-exported-image-" + h.RandString(10))
exportArgs = append(exportArgs, exportedImageName)
@ -331,16 +240,71 @@ func testExporterFunc(platformAPI string) func(t *testing.T, when spec.G, it spe
h.WithArgs(exportArgs...),
)
h.AssertStringContains(t, output, "Saving "+exportedImageName)
// To detect whether the export of cacheImage and exportedImage is successful
h.Run(t, exec.Command("docker", "pull", exportedImageName))
assertImageOSAndArchAndCreatedAt(t, exportedImageName, exportTest, imgutil.NormalizedDateTime)
h.Run(t, exec.Command("docker", "pull", cacheImageName))
})
})
when("app using insecure registry", func() {
it.Before(func() {
h.SkipIf(t, api.MustParse(platformAPI).LessThan("0.12"), "")
})
when("parallel export is enabled", func() {
it("cache is created", func() {
it("does an http request", func() {
var exportFlags []string
exportArgs := append([]string{ctrPath(exporterPath)}, exportFlags...)
exportedImageName = exportTest.RegRepoName("some-insecure-exported-image-" + h.RandString(10))
exportArgs = append(exportArgs, exportedImageName)
insecureRegistry := "host.docker.internal/bar"
insecureAnalyzed := "/layers/analyzed_insecure.toml"
_, _, err := h.DockerRunWithError(t,
exportImage,
h.WithFlags(
"--env", "CNB_PLATFORM_API="+platformAPI,
"--env", "CNB_INSECURE_REGISTRIES="+insecureRegistry,
"--env", "CNB_ANALYZED_PATH="+insecureAnalyzed,
"--network", exportRegNetwork,
),
h.WithArgs(exportArgs...),
)
h.AssertStringContains(t, err.Error(), "http://host.docker.internal")
})
})
when("SOURCE_DATE_EPOCH is set", func() {
it("Image CreatedAt is set to SOURCE_DATE_EPOCH", func() {
h.SkipIf(t, api.MustParse(platformAPI).LessThan("0.9"), "SOURCE_DATE_EPOCH support added in 0.9")
expectedTime := time.Date(2022, 1, 5, 5, 5, 5, 0, time.UTC)
var exportFlags []string
exportArgs := append([]string{ctrPath(exporterPath)}, exportFlags...)
exportedImageName = exportTest.RegRepoName("some-exported-image-" + h.RandString(10))
exportArgs = append(exportArgs, exportedImageName)
output := h.DockerRun(t,
exportImage,
h.WithFlags(
"--env", "CNB_PLATFORM_API="+platformAPI,
"--env", "CNB_REGISTRY_AUTH="+exportRegAuthConfig,
"--env", "SOURCE_DATE_EPOCH="+fmt.Sprintf("%d", expectedTime.Unix()),
"--network", exportRegNetwork,
),
h.WithArgs(exportArgs...),
)
h.AssertStringContains(t, output, "Saving "+exportedImageName)
h.Run(t, exec.Command("docker", "pull", exportedImageName))
assertImageOSAndArchAndCreatedAt(t, exportedImageName, exportTest, expectedTime)
})
})
when("cache", func() {
when("cache image case", func() {
it("is created", func() {
cacheImageName := exportTest.RegRepoName("some-cache-image-" + h.RandString(10))
exportFlags := []string{"-cache-image", cacheImageName, "-parallel"}
exportFlags := []string{"-cache-image", cacheImageName}
exportArgs := append([]string{ctrPath(exporterPath)}, exportFlags...)
exportedImageName = exportTest.RegRepoName("some-exported-image-" + h.RandString(10))
exportArgs = append(exportArgs, exportedImageName)
@ -358,12 +322,9 @@ func testExporterFunc(platformAPI string) func(t *testing.T, when spec.G, it spe
h.Run(t, exec.Command("docker", "pull", exportedImageName))
assertImageOSAndArchAndCreatedAt(t, exportedImageName, exportTest, imgutil.NormalizedDateTime)
h.Run(t, exec.Command("docker", "pull", cacheImageName))
})
})
when("cache is provided but no data was cached", func() {
it("cache is created with an empty layer", func() {
it("is created with empty layer", func() {
cacheImageName := exportTest.RegRepoName("some-empty-cache-image-" + h.RandString(10))
exportFlags := []string{"-cache-image", cacheImageName, "-layers", "/other_layers"}
exportArgs := append([]string{ctrPath(exporterPath)}, exportFlags...)
@ -398,186 +359,98 @@ func testExporterFunc(platformAPI string) func(t *testing.T, when spec.G, it spe
})
})
when("directory case", func() {
when("original cache was corrupted", func() {
var cacheDir string
it.Before(func() {
var err error
cacheDir, err = os.MkdirTemp("", "cache")
h.AssertNil(t, err)
h.AssertNil(t, os.Chmod(cacheDir, 0777)) // Override umask
cacheFixtureDir := filepath.Join("testdata", "exporter", "cache-dir")
h.AssertNil(t, fsutil.Copy(cacheFixtureDir, cacheDir))
// We have to pre-create the tar files so that their digests do not change due to timestamps
// But, ':' in the filepath on Windows is not allowed
h.AssertNil(t, os.Rename(
filepath.Join(cacheDir, "committed", "sha256_258dfa0cc987efebc17559694866ebc91139e7c0e574f60d1d4092f53d7dff59.tar"),
filepath.Join(cacheDir, "committed", "sha256:258dfa0cc987efebc17559694866ebc91139e7c0e574f60d1d4092f53d7dff59.tar"),
))
})
it.After(func() {
_ = os.RemoveAll(cacheDir)
})
it("overwrites the original layer", func() {
exportFlags := []string{
"-cache-dir", "/cache",
"-log-level", "debug",
}
exportArgs := append([]string{ctrPath(exporterPath)}, exportFlags...)
exportedImageName = exportTest.RegRepoName("some-exported-image-" + h.RandString(10))
exportArgs = append(exportArgs, exportedImageName)
output := h.DockerRun(t,
exportImage,
h.WithFlags(
"--env", "CNB_PLATFORM_API="+platformAPI,
"--env", "CNB_REGISTRY_AUTH="+exportRegAuthConfig,
"--network", exportRegNetwork,
"--volume", fmt.Sprintf("%s:/cache", cacheDir),
),
h.WithArgs(exportArgs...),
)
h.AssertStringContains(t, output, "Skipping reuse for layer corrupted_buildpack:corrupted-layer: expected layer contents to have SHA 'sha256:258dfa0cc987efebc17559694866ebc91139e7c0e574f60d1d4092f53d7dff59'; found 'sha256:9e0b77ed599eafdab8611f7eeefef084077f91f02f1da0a3870c7ff20a08bee8'")
h.AssertStringContains(t, output, "Saving "+exportedImageName)
h.Run(t, exec.Command("docker", "pull", exportedImageName))
defer h.Run(t, exec.Command("docker", "image", "rm", exportedImageName))
// Verify the app has the correct sha for the layer
inspect, _, err := h.DockerCli(t).ImageInspectWithRaw(context.TODO(), exportedImageName)
h.AssertNil(t, err)
var lmd files.LayersMetadata
lmdJSON := inspect.Config.Labels["io.buildpacks.lifecycle.metadata"]
h.AssertNil(t, json.Unmarshal([]byte(lmdJSON), &lmd))
h.AssertEq(t, lmd.Buildpacks[2].Layers["corrupted-layer"].SHA, "sha256:258dfa0cc987efebc17559694866ebc91139e7c0e574f60d1d4092f53d7dff59")
// Verify the cache has correct contents now
foundDiffID, err := func() (string, error) {
layerPath := filepath.Join(cacheDir, "committed", "sha256:258dfa0cc987efebc17559694866ebc91139e7c0e574f60d1d4092f53d7dff59.tar")
layerRC, err := os.Open(layerPath)
if err != nil {
return "", err
}
defer func() {
_ = layerRC.Close()
}()
hasher := sha256.New()
if _, err = io.Copy(hasher, layerRC); err != nil {
return "", errors.Wrap(err, "hashing layer")
}
foundDiffID := "sha256:" + hex.EncodeToString(hasher.Sum(make([]byte, 0, hasher.Size())))
return foundDiffID, nil
}()
h.AssertNil(t, err)
h.AssertEq(t, foundDiffID, "sha256:258dfa0cc987efebc17559694866ebc91139e7c0e574f60d1d4092f53d7dff59")
})
when("using extensions", func() {
it.Before(func() {
h.SkipIf(t, api.MustParse(platformAPI).LessThan("0.12"), "")
})
})
})
when("using extensions", func() {
it.Before(func() {
h.SkipIf(t, api.MustParse(platformAPI).LessThan("0.12"), "")
})
it("is created from the extended run image", func() {
exportFlags := []string{
"-analyzed", "/layers/run-image-extended-analyzed.toml",
"-extended", "/layers/some-extended-dir",
"-log-level", "debug",
"-run", "/cnb/run.toml",
}
exportArgs := append([]string{ctrPath(exporterPath)}, exportFlags...)
exportedImageName = exportTest.RegRepoName("some-exported-image-" + h.RandString(10))
exportArgs = append(exportArgs, exportedImageName)
it("app is created from the extended run image", func() {
exportFlags := []string{
"-analyzed", "/layers/run-image-extended-analyzed.toml",
"-extended", "/layers/some-extended-dir",
"-log-level", "debug",
"-run", "/cnb/run.toml",
}
exportArgs := append([]string{ctrPath(exporterPath)}, exportFlags...)
exportedImageName = exportTest.RegRepoName("some-exported-image-" + h.RandString(10))
exportArgs = append(exportArgs, exportedImageName)
// get run image SHA & top layer
ref, imageAuth, err := auth.ReferenceForRepoName(authn.DefaultKeychain, exportTest.targetRegistry.fixtures.ReadOnlyRunImage)
h.AssertNil(t, err)
remoteImage, err := remote.Image(ref, remote.WithAuth(imageAuth))
h.AssertNil(t, err)
layers, err := remoteImage.Layers()
h.AssertNil(t, err)
runImageFixtureTopLayerSHA, err := layers[len(layers)-1].DiffID()
h.AssertNil(t, err)
runImageFixtureSHA, err := remoteImage.Digest()
h.AssertNil(t, err)
experimentalMode := "warn"
if api.MustParse(platformAPI).AtLeast("0.13") {
experimentalMode = "error"
}
output := h.DockerRun(t,
exportImage,
h.WithFlags(
"--env", "CNB_EXPERIMENTAL_MODE="+experimentalMode,
"--env", "CNB_PLATFORM_API="+platformAPI,
"--env", "CNB_REGISTRY_AUTH="+exportRegAuthConfig,
"--network", exportRegNetwork,
),
h.WithArgs(exportArgs...),
)
h.AssertStringContains(t, output, "Saving "+exportedImageName)
h.Run(t, exec.Command("docker", "pull", exportedImageName))
assertImageOSAndArchAndCreatedAt(t, exportedImageName, exportTest, imgutil.NormalizedDateTime)
t.Log("bases the exported image on the extended run image")
ref, imageAuth, err = auth.ReferenceForRepoName(authn.DefaultKeychain, exportedImageName)
h.AssertNil(t, err)
remoteImage, err = remote.Image(ref, remote.WithAuth(imageAuth))
h.AssertNil(t, err)
configFile, err := remoteImage.ConfigFile()
h.AssertNil(t, err)
h.AssertEq(t, configFile.Config.Labels["io.buildpacks.rebasable"], "false") // from testdata/exporter/container/layers/some-extended-dir/run/sha256_<sha>/blobs/sha256/<config>
t.Log("Adds extension layers")
layers, err = remoteImage.Layers()
h.AssertNil(t, err)
type testCase struct {
expectedDigest string
layerIndex int
}
testCases := []testCase{
{
expectedDigest: "sha256:08e7ad5ce17cf5e5f70affe68b341a93de86ee2ba074932c3a05b8770f66d772", // from testdata/exporter/container/layers/some-extended-dir/run/sha256_<c72eda1c>/blobs/sha256/65c2873d397056a5cb4169790654d787579b005f18b903082b177d4d9b4aecf5 after un-compressing, zeroing timestamps, and re-compressing
layerIndex: 1,
},
{
expectedDigest: "sha256:0e74ef444ea437147e3fa0ce2aad371df5380c26b96875ae07b9b67f44cdb2ee", // from testdata/exporter/container/layers/some-extended-dir/run/sha256_<c72eda1c>/blobs/sha256/0fb9b88c9cbe9f11b4c8da645f390df59f5949632985a0bfc2a842ef17b2ad18 after un-compressing, zeroing timestamps, and re-compressing
layerIndex: 2,
},
}
for _, tc := range testCases {
layer := layers[tc.layerIndex]
digest, err := layer.Digest()
// get run image SHA & top layer
ref, imageAuth, err := auth.ReferenceForRepoName(authn.DefaultKeychain, exportTest.targetRegistry.fixtures.ReadOnlyRunImage)
h.AssertNil(t, err)
h.AssertEq(t, digest.String(), tc.expectedDigest)
}
t.Log("sets the layers metadata label according to the new spec")
var lmd files.LayersMetadata
lmdJSON := configFile.Config.Labels["io.buildpacks.lifecycle.metadata"]
h.AssertNil(t, json.Unmarshal([]byte(lmdJSON), &lmd))
h.AssertEq(t, lmd.RunImage.Image, exportTest.targetRegistry.fixtures.ReadOnlyRunImage) // from analyzed.toml
h.AssertEq(t, lmd.RunImage.Mirrors, []string{"mirror1", "mirror2"}) // from run.toml
h.AssertEq(t, lmd.RunImage.TopLayer, runImageFixtureTopLayerSHA.String())
h.AssertEq(t, lmd.RunImage.Reference, fmt.Sprintf("%s@%s", exportTest.targetRegistry.fixtures.ReadOnlyRunImage, runImageFixtureSHA.String()))
remoteImage, err := remote.Image(ref, remote.WithAuth(imageAuth))
h.AssertNil(t, err)
layers, err := remoteImage.Layers()
h.AssertNil(t, err)
runImageFixtureTopLayerSHA, err := layers[len(layers)-1].DiffID()
h.AssertNil(t, err)
runImageFixtureSHA, err := remoteImage.Digest()
h.AssertNil(t, err)
output := h.DockerRun(t,
exportImage,
h.WithFlags(
"--env", "CNB_EXPERIMENTAL_MODE=warn",
"--env", "CNB_PLATFORM_API="+platformAPI,
"--env", "CNB_REGISTRY_AUTH="+exportRegAuthConfig,
"--network", exportRegNetwork,
),
h.WithArgs(exportArgs...),
)
h.AssertStringContains(t, output, "Saving "+exportedImageName)
h.Run(t, exec.Command("docker", "pull", exportedImageName))
assertImageOSAndArchAndCreatedAt(t, exportedImageName, exportTest, imgutil.NormalizedDateTime)
t.Log("bases the exported image on the extended run image")
ref, imageAuth, err = auth.ReferenceForRepoName(authn.DefaultKeychain, exportedImageName)
h.AssertNil(t, err)
remoteImage, err = remote.Image(ref, remote.WithAuth(imageAuth))
h.AssertNil(t, err)
configFile, err := remoteImage.ConfigFile()
h.AssertNil(t, err)
h.AssertEq(t, configFile.Config.Labels["io.buildpacks.rebasable"], "false") // from testdata/exporter/container/layers/extended/sha256:<sha>/blobs/sha256/<config>
t.Log("Adds extension layers")
layers, err = remoteImage.Layers()
h.AssertNil(t, err)
digestFromExt1 := "sha256:0c5f7a6fe14dbd19670f39e7466051cbd40b3a534c0812659740fb03e2137c1a"
digestFromExt2 := "sha256:482346d1e0c7afa2514ec366d2e000e0667d0a6664690aab3c8ad51c81915b91"
var foundFromExt1, foundFromExt2 bool
for _, layer := range layers {
digest, err := layer.Digest()
h.AssertNil(t, err)
if digest.String() == digestFromExt1 {
foundFromExt1 = true
}
if digest.String() == digestFromExt2 {
foundFromExt2 = true
}
}
h.AssertEq(t, foundFromExt1, true)
h.AssertEq(t, foundFromExt2, true)
t.Log("sets the layers metadata label according to the new spec")
var lmd files.LayersMetadata
lmdJSON := configFile.Config.Labels["io.buildpacks.lifecycle.metadata"]
h.AssertNil(t, json.Unmarshal([]byte(lmdJSON), &lmd))
h.AssertEq(t, lmd.RunImage.Image, exportTest.targetRegistry.fixtures.ReadOnlyRunImage) // from analyzed.toml
h.AssertEq(t, lmd.RunImage.Mirrors, []string{"mirror1", "mirror2"}) // from run.toml
h.AssertEq(t, lmd.RunImage.TopLayer, runImageFixtureTopLayerSHA.String())
h.AssertEq(t, lmd.RunImage.Reference, fmt.Sprintf("%s@%s", exportTest.targetRegistry.fixtures.ReadOnlyRunImage, runImageFixtureSHA.String()))
})
})
})
})
when("layout case", func() {
var (
containerName string
err error
layoutDir string
tmpDir string
exportedImageName string
containerName string
err error
layoutDir string
tmpDir string
)
when("experimental mode is enabled", func() {
it.Before(func() {
// create the directory to save all OCI images on disk
// creates the directory to save all the OCI images on disk
tmpDir, err = os.MkdirTemp("", "layout")
h.AssertNil(t, err)
@ -592,31 +465,35 @@ func testExporterFunc(platformAPI string) func(t *testing.T, when spec.G, it spe
os.RemoveAll(tmpDir)
})
when("using a custom layout directory", func() {
it.Before(func() {
exportedImageName = "my-custom-layout-app"
layoutDir = filepath.Join(path.RootDir, "my-layout-dir")
})
when("custom layout directory", func() {
when("first build", func() {
when("app", func() {
it.Before(func() {
exportedImageName = "my-custom-layout-app"
layoutDir = filepath.Join(path.RootDir, "my-layout-dir")
})
it("app is created", func() {
var exportFlags []string
h.SkipIf(t, api.MustParse(platformAPI).LessThan("0.12"), "Platform API < 0.12 does not accept a -layout flag")
exportFlags = append(exportFlags, []string{"-layout", "-layout-dir", layoutDir, "-analyzed", "/layers/layout-analyzed.toml"}...)
exportArgs := append([]string{ctrPath(exporterPath)}, exportFlags...)
exportArgs = append(exportArgs, exportedImageName)
it("is created", func() {
var exportFlags []string
h.SkipIf(t, api.MustParse(platformAPI).LessThan("0.12"), "Platform API < 0.12 does not accept a -layout flag")
exportFlags = append(exportFlags, []string{"-layout", "-layout-dir", layoutDir, "-analyzed", "/layers/layout-analyzed.toml"}...)
exportArgs := append([]string{ctrPath(exporterPath)}, exportFlags...)
exportArgs = append(exportArgs, exportedImageName)
output := h.DockerRunAndCopy(t, containerName, tmpDir, layoutDir, exportImage,
h.WithFlags(
"--env", "CNB_EXPERIMENTAL_MODE=warn",
"--env", "CNB_PLATFORM_API="+platformAPI,
),
h.WithArgs(exportArgs...))
output := h.DockerRunAndCopy(t, containerName, tmpDir, layoutDir, exportImage,
h.WithFlags(
"--env", "CNB_EXPERIMENTAL_MODE=warn",
"--env", "CNB_PLATFORM_API="+platformAPI,
),
h.WithArgs(exportArgs...))
h.AssertStringContains(t, output, "Saving /my-layout-dir/index.docker.io/library/my-custom-layout-app/latest")
h.AssertStringContains(t, output, "Saving /my-layout-dir/index.docker.io/library/my-custom-layout-app/latest")
// assert the image was saved on disk in OCI layout format
index := h.ReadIndexManifest(t, filepath.Join(tmpDir, layoutDir, "index.docker.io", "library", exportedImageName, "latest"))
h.AssertEq(t, len(index.Manifests), 1)
// assert the image was saved on disk in OCI layout format
index := h.ReadIndexManifest(t, filepath.Join(tmpDir, layoutDir, "index.docker.io", "library", exportedImageName, "latest"))
h.AssertEq(t, len(index.Manifests), 1)
})
})
})
})
})

View File

@ -1,4 +1,5 @@
//go:build acceptance
// +build acceptance
package acceptance
@ -7,11 +8,12 @@ import (
"os"
"os/exec"
"path/filepath"
"runtime"
"testing"
"github.com/buildpacks/imgutil/layout/sparse"
"github.com/google/go-containerregistry/pkg/authn"
v1 "github.com/google/go-containerregistry/pkg/v1"
"github.com/google/go-containerregistry/pkg/v1/empty"
"github.com/google/go-containerregistry/pkg/v1/layout"
"github.com/google/go-containerregistry/pkg/v1/remote"
"github.com/sclevine/spec"
@ -19,7 +21,8 @@ import (
"github.com/buildpacks/lifecycle/api"
"github.com/buildpacks/lifecycle/auth"
"github.com/buildpacks/lifecycle/cmd"
"github.com/buildpacks/lifecycle/internal/encoding"
"github.com/buildpacks/lifecycle/internal/selective"
"github.com/buildpacks/lifecycle/platform/files"
h "github.com/buildpacks/lifecycle/testhelpers"
)
@ -44,6 +47,8 @@ const (
)
func TestExtender(t *testing.T) {
h.SkipIf(t, runtime.GOOS == "windows", "Extender is not supported on Windows")
testImageDockerContext := filepath.Join("testdata", "extender")
extendTest = NewPhaseTest(t, "extender", testImageDockerContext)
extendTest.Start(t)
@ -66,13 +71,8 @@ func TestExtender(t *testing.T) {
func testExtenderFunc(platformAPI string) func(t *testing.T, when spec.G, it spec.S) {
return func(t *testing.T, when spec.G, it spec.S) {
var generatedDir = "/layers/generated"
it.Before(func() {
h.SkipIf(t, api.MustParse(platformAPI).LessThan("0.10"), "")
if api.MustParse(platformAPI).AtLeast("0.13") {
generatedDir = "/layers/generated-with-contexts"
}
})
when("kaniko case", func() {
@ -105,10 +105,9 @@ func testExtenderFunc(platformAPI string) func(t *testing.T, when spec.G, it spe
baseCacheDir := filepath.Join(kanikoDir, "cache", "base")
h.AssertNil(t, os.MkdirAll(baseCacheDir, 0755))
// write sparse image
layoutImage, err := sparse.NewImage(filepath.Join(baseCacheDir, baseImageDigest), remoteImage)
layoutPath, err := selective.Write(filepath.Join(baseCacheDir, baseImageDigest), empty.Index)
h.AssertNil(t, err)
h.AssertNil(t, layoutImage.Save())
h.AssertNil(t, layoutPath.AppendImage(remoteImage))
// write image reference in analyzed.toml
analyzedMD := files.Analyzed{
@ -121,7 +120,7 @@ func testExtenderFunc(platformAPI string) func(t *testing.T, when spec.G, it spe
},
}
analyzedPath = h.TempFile(t, "", "analyzed.toml")
h.AssertNil(t, files.Handler.WriteAnalyzed(analyzedPath, &analyzedMD, cmd.DefaultLogger))
h.AssertNil(t, encoding.WriteTOML(analyzedPath, analyzedMD))
})
it.After(func() {
@ -134,7 +133,7 @@ func testExtenderFunc(platformAPI string) func(t *testing.T, when spec.G, it spe
extendArgs := []string{
ctrPath(extenderPath),
"-analyzed", "/layers/analyzed.toml",
"-generated", generatedDir,
"-generated", "/layers/generated",
"-log-level", "debug",
"-gid", "1000",
"-uid", "1234",
@ -187,7 +186,7 @@ func testExtenderFunc(platformAPI string) func(t *testing.T, when spec.G, it spe
ctrPath(extenderPath),
"-analyzed", "/layers/analyzed.toml",
"-extended", "/layers/extended",
"-generated", generatedDir,
"-generated", "/layers/generated",
"-kind", "run",
"-log-level", "debug",
"-gid", "1000",
@ -256,16 +255,10 @@ func assertExpectedImage(t *testing.T, imagePath, platformAPI string) {
h.AssertEq(t, configFile.Config.Labels["io.buildpacks.rebasable"], "false")
layers, err := image.Layers()
h.AssertNil(t, err)
history := configFile.History
h.AssertEq(t, len(history), len(configFile.RootFS.DiffIDs))
if api.MustParse(platformAPI).AtLeast("0.13") {
h.AssertEq(t, len(layers), 7) // base (3), curl (2), tree (2)
h.AssertEq(t, history[3].CreatedBy, "Layer: 'RUN apt-get update && apt-get install -y curl', Created by extension: curl")
h.AssertEq(t, history[4].CreatedBy, "Layer: 'COPY run-file /', Created by extension: curl")
h.AssertEq(t, history[5].CreatedBy, "Layer: 'RUN apt-get update && apt-get install -y tree', Created by extension: tree")
h.AssertEq(t, history[6].CreatedBy, "Layer: 'COPY shared-file /shared-run', Created by extension: tree")
} else {
h.AssertEq(t, len(layers), 5) // base (3), curl (1), tree (1)
h.AssertEq(t, len(layers), 5) // base (3), curl (1), tree (1)
if api.MustParse(platformAPI).AtLeast("0.12") {
history := configFile.History
h.AssertEq(t, len(history), len(configFile.RootFS.DiffIDs))
h.AssertEq(t, history[3].CreatedBy, "Layer: 'RUN apt-get update && apt-get install -y curl', Created by extension: curl")
h.AssertEq(t, history[4].CreatedBy, "Layer: 'RUN apt-get update && apt-get install -y tree', Created by extension: tree")
}

View File

@ -4,6 +4,7 @@ import (
"fmt"
"os/exec"
"path/filepath"
"runtime"
"strings"
"testing"
@ -24,6 +25,9 @@ func TestLauncher(t *testing.T) {
launchTest = NewPhaseTest(t, "launcher", testImageDockerContext, withoutDaemonFixtures, withoutRegistry)
containerBinaryDir := filepath.Join("testdata", "launcher", "linux", "container", "cnb", "lifecycle")
if launchTest.targetDaemon.os == "windows" {
containerBinaryDir = filepath.Join("testdata", "launcher", "windows", "container", "cnb", "lifecycle")
}
withCustomContainerBinaryDir := func(_ *testing.T, phaseTest *PhaseTest) {
phaseTest.containerBinaryDir = containerBinaryDir
}
@ -88,7 +92,11 @@ func testLauncher(t *testing.T, when spec.G, it spec.S) {
"--env=CNB_PLATFORM_API="+latestPlatformAPI,
launchImage, "with user provided args",
)
assertOutput(t, cmd, "Executing web process-type with user provided args")
if runtime.GOOS == "windows" {
assertOutput(t, cmd, `Executing web process-type "with user provided args"`)
} else {
assertOutput(t, cmd, "Executing web process-type with user provided args")
}
})
})
@ -101,6 +109,15 @@ func testLauncher(t *testing.T, when spec.G, it spec.S) {
launchImage, "--",
"env",
)
if runtime.GOOS == "windows" {
cmd = exec.Command( //nolint
"docker", "run", "--rm",
`--entrypoint=launcher`,
"--env=CNB_PLATFORM_API=0.7",
launchImage, "--",
"cmd", "/c", "set",
)
}
assertOutput(t, cmd,
"SOME_VAR=some-bp-val",
@ -143,11 +160,22 @@ func testLauncher(t *testing.T, when spec.G, it spec.S) {
launchImage,
"echo", "$SOME_VAR", "$OTHER_VAR", "$WORKER_VAR",
)
if runtime.GOOS == "windows" {
cmd = exec.Command( //nolint
"docker", "run", "--rm",
"--env=CNB_PLATFORM_API="+latestPlatformAPI,
launchImage,
"echo", "%SOME_VAR%", "%OTHER_VAR%", "%WORKER_VAR%",
)
}
assertOutput(t, cmd, "sourced bp profile\nsourced app profile\nsome-bp-val other-bp-val worker-no-process-val")
})
it("passes through env vars from user, excluding excluded vars", func() {
args := []string{"echo", "$SOME_USER_VAR, $CNB_APP_DIR, $OTHER_VAR"}
if runtime.GOOS == "windows" {
args = []string{"echo", "%SOME_USER_VAR%, %CNB_APP_DIR%, %OTHER_VAR%"}
}
cmd := exec.Command("docker",
append(
[]string{
@ -161,7 +189,13 @@ func testLauncher(t *testing.T, when spec.G, it spec.S) {
args...)...,
) // #nosec G204
assertOutput(t, cmd, "sourced bp profile\nsourced app profile\nsome-user-val, , other-user-val**other-bp-val")
if runtime.GOOS == "windows" {
// windows values with spaces will contain quotes
// empty values on windows preserve variable names instead of interpolating to empty strings
assertOutput(t, cmd, "sourced bp profile\nsourced app profile\n\"some-user-val, %CNB_APP_DIR%, other-user-val**other-bp-val\"")
} else {
assertOutput(t, cmd, "sourced bp profile\nsourced app profile\nsome-user-val, , other-user-val**other-bp-val")
}
})
it("adds buildpack bin dirs to the path", func() {
@ -177,13 +211,23 @@ func testLauncher(t *testing.T, when spec.G, it spec.S) {
when("CMD provided starts with --", func() {
it("launches command directly", func() {
cmd := exec.Command( //nolint
"docker", "run", "--rm",
"--env=CNB_PLATFORM_API="+latestPlatformAPI,
launchImage, "--",
"echo", "something",
)
assertOutput(t, cmd, "something")
if runtime.GOOS == "windows" {
cmd := exec.Command( //nolint
"docker", "run", "--rm",
"--env=CNB_PLATFORM_API="+latestPlatformAPI,
launchImage, "--",
"ping", "/?",
)
assertOutput(t, cmd, "Usage: ping")
} else {
cmd := exec.Command( //nolint
"docker", "run", "--rm",
"--env=CNB_PLATFORM_API="+latestPlatformAPI,
launchImage, "--",
"echo", "something",
)
assertOutput(t, cmd, "something")
}
})
it("sets env vars from layers", func() {
@ -193,6 +237,14 @@ func testLauncher(t *testing.T, when spec.G, it spec.S) {
launchImage, "--",
"env",
)
if runtime.GOOS == "windows" {
cmd = exec.Command( //nolint
"docker", "run", "--rm",
"--env=CNB_PLATFORM_API="+latestPlatformAPI,
launchImage, "--",
"cmd", "/c", "set",
)
}
assertOutput(t, cmd,
"SOME_VAR=some-bp-val",
@ -209,6 +261,16 @@ func testLauncher(t *testing.T, when spec.G, it spec.S) {
launchImage, "--",
"env",
)
if runtime.GOOS == "windows" {
cmd = exec.Command( //nolint
"docker", "run", "--rm",
"--env", "CNB_APP_DIR=/workspace",
"--env=CNB_PLATFORM_API="+latestPlatformAPI,
"--env", "SOME_USER_VAR=some-user-val",
launchImage, "--",
"cmd", "/c", "set",
)
}
output, err := cmd.CombinedOutput()
if err != nil {

View File

@ -2,8 +2,6 @@ package acceptance
import (
"context"
"crypto/sha256"
"encoding/hex"
"encoding/json"
"fmt"
"io"
@ -17,14 +15,12 @@ import (
"testing"
"time"
"github.com/docker/docker/api/types/image"
"github.com/BurntSushi/toml"
ih "github.com/buildpacks/imgutil/testhelpers"
"github.com/google/go-containerregistry/pkg/authn"
"github.com/google/go-containerregistry/pkg/registry"
"github.com/buildpacks/lifecycle/auth"
"github.com/buildpacks/lifecycle/cmd"
"github.com/buildpacks/lifecycle/internal/encoding"
"github.com/buildpacks/lifecycle/platform"
"github.com/buildpacks/lifecycle/platform/files"
@ -130,14 +126,6 @@ func (p *PhaseTest) Start(t *testing.T, phaseOp ...func(*testing.T, *PhaseTest))
}
h.MakeAndCopyLifecycle(t, p.targetDaemon.os, p.targetDaemon.arch, p.containerBinaryDir)
// calculate lifecycle digest
hasher := sha256.New()
f, err := os.Open(filepath.Join(p.containerBinaryDir, "lifecycle"+exe)) //#nosec G304
h.AssertNil(t, err)
_, err = io.Copy(hasher, f)
h.AssertNil(t, err)
t.Logf("Built lifecycle binary with digest: %s", hex.EncodeToString(hasher.Sum(nil)))
copyFakeSboms(t)
h.DockerBuild(
t,
@ -145,14 +133,6 @@ func (p *PhaseTest) Start(t *testing.T, phaseOp ...func(*testing.T, *PhaseTest))
p.testImageDockerContext,
h.WithArgs("-f", filepath.Join(p.testImageDockerContext, dockerfileName)),
)
t.Logf("Using image %s with lifecycle version %s",
p.testImageRef,
h.DockerRun(
t,
p.testImageRef,
h.WithFlags("--env", "CNB_PLATFORM_API="+latestPlatformAPI, "--entrypoint", ctrPath("/cnb/lifecycle/lifecycle"+exe)),
h.WithArgs("-version"),
))
}
func (p *PhaseTest) Stop(t *testing.T) {
@ -428,30 +408,14 @@ func SBOMComponents() []string {
}
func assertImageOSAndArch(t *testing.T, imageName string, phaseTest *PhaseTest) { //nolint - these functions are in fact used, i promise
inspect, err := h.DockerCli(t).ImageInspect(context.TODO(), imageName)
inspect, _, err := h.DockerCli(t).ImageInspectWithRaw(context.TODO(), imageName)
h.AssertNil(t, err)
h.AssertEq(t, inspect.Os, phaseTest.targetDaemon.os)
h.AssertEq(t, inspect.Architecture, phaseTest.targetDaemon.arch)
}
func assertImageOSAndArchAndCreatedAt(t *testing.T, imageName string, phaseTest *PhaseTest, expectedCreatedAt time.Time) { //nolint
inspect, err := h.DockerCli(t).ImageInspect(context.TODO(), imageName)
if err != nil {
list, _ := h.DockerCli(t).ImageList(context.TODO(), image.ListOptions{})
fmt.Println("Error encountered running ImageInspectWithRaw. imageName: ", imageName)
fmt.Println(err)
for _, value := range list {
fmt.Println("Image Name: ", value)
}
if strings.Contains(err.Error(), "No such image") {
t.Log("Image not found, retrying...")
time.Sleep(1 * time.Second)
inspect, err = h.DockerCli(t).ImageInspect(context.TODO(), imageName)
}
}
inspect, _, err := h.DockerCli(t).ImageInspectWithRaw(context.TODO(), imageName)
h.AssertNil(t, err)
h.AssertEq(t, inspect.Os, phaseTest.targetDaemon.os)
h.AssertEq(t, inspect.Architecture, phaseTest.targetDaemon.arch)
@ -463,7 +427,8 @@ func assertRunMetadata(t *testing.T, path string) *files.Run { //nolint
h.AssertNil(t, err)
h.AssertEq(t, len(contents) > 0, true)
runMD, err := files.Handler.ReadRun(path, cmd.DefaultLogger)
var runMD files.Run
_, err = toml.Decode(string(contents), &runMD)
h.AssertNil(t, err)
return &runMD

View File

@ -1,4 +1,5 @@
//go:build acceptance
// +build acceptance
package acceptance

View File

@ -1,4 +1,5 @@
//go:build acceptance
// +build acceptance
package acceptance
@ -6,6 +7,7 @@ import (
"os"
"os/exec"
"path/filepath"
"runtime"
"testing"
"github.com/google/go-containerregistry/pkg/name"
@ -14,7 +16,7 @@ import (
"github.com/buildpacks/lifecycle/api"
"github.com/buildpacks/lifecycle/cmd"
"github.com/buildpacks/lifecycle/platform/files"
"github.com/buildpacks/lifecycle/phase"
h "github.com/buildpacks/lifecycle/testhelpers"
)
@ -31,6 +33,8 @@ var (
)
func TestRestorer(t *testing.T) {
h.SkipIf(t, runtime.GOOS == "windows", "Restorer acceptance tests are not yet supported on Windows")
testImageDockerContext := filepath.Join("testdata", "restorer")
restoreTest = NewPhaseTest(t, "restorer", testImageDockerContext)
restoreTest.Start(t, updateTOMLFixturesWithTestRegistry)
@ -161,7 +165,7 @@ func testRestorerFunc(platformAPI string) func(t *testing.T, when spec.G, it spe
h.AssertPathDoesNotExist(t, uncachedFile)
})
it("does not restore layer data from unused buildpacks", func() {
it("does not restore unused buildpack layer data", func() {
h.DockerRunAndCopy(t,
containerName,
copyDir,
@ -175,21 +179,6 @@ func testRestorerFunc(platformAPI string) func(t *testing.T, when spec.G, it spe
unusedBpLayer := filepath.Join(copyDir, "layers", "unused_buildpack")
h.AssertPathDoesNotExist(t, unusedBpLayer)
})
it("does not restore corrupted layer data", func() {
h.DockerRunAndCopy(t,
containerName,
copyDir,
"/layers",
restoreImage,
h.WithFlags("--env", "CNB_PLATFORM_API="+platformAPI),
h.WithArgs("-cache-dir", "/cache"),
)
// check corrupted layer is not restored
corruptedFile := filepath.Join(copyDir, "layers", "corrupted_buildpack", "corrupted-layer")
h.AssertPathDoesNotExist(t, corruptedFile)
})
})
})
@ -209,7 +198,7 @@ func testRestorerFunc(platformAPI string) func(t *testing.T, when spec.G, it spe
h.WithArgs("-build-image", restoreRegFixtures.SomeCacheImage), // some-cache-image simulates a builder image in a registry
)
t.Log("records builder image digest in analyzed.toml")
analyzedMD, err := files.Handler.ReadAnalyzed(filepath.Join(copyDir, "layers", "analyzed.toml"), cmd.DefaultLogger)
analyzedMD, err := phase.Config.ReadAnalyzed(filepath.Join(copyDir, "layers", "analyzed.toml"), cmd.DefaultLogger)
h.AssertNil(t, err)
h.AssertStringContains(t, analyzedMD.BuildImage.Reference, restoreRegFixtures.SomeCacheImage+"@sha256:")
t.Log("writes builder manifest and config to the kaniko cache")
@ -241,7 +230,7 @@ func testRestorerFunc(platformAPI string) func(t *testing.T, when spec.G, it spe
),
)
t.Log("updates run image reference in analyzed.toml to include digest and target data")
analyzedMD, err := files.Handler.ReadAnalyzed(filepath.Join(copyDir, "layers", "some-extend-true-analyzed.toml"), cmd.DefaultLogger)
analyzedMD, err := phase.Config.ReadAnalyzed(filepath.Join(copyDir, "layers", "some-extend-true-analyzed.toml"), cmd.DefaultLogger)
h.AssertNil(t, err)
h.AssertStringContains(t, analyzedMD.RunImage.Reference, restoreRegFixtures.ReadOnlyRunImage+"@sha256:")
h.AssertEq(t, analyzedMD.RunImage.Image, restoreRegFixtures.ReadOnlyRunImage)
@ -278,7 +267,7 @@ func testRestorerFunc(platformAPI string) func(t *testing.T, when spec.G, it spe
)
if api.MustParse(platformAPI).AtLeast("0.12") {
t.Log("updates run image reference in analyzed.toml to include digest and target data")
analyzedMD, err := files.Handler.ReadAnalyzed(filepath.Join(copyDir, "layers", "some-extend-false-analyzed.toml"), cmd.DefaultLogger)
analyzedMD, err := phase.Config.ReadAnalyzed(filepath.Join(copyDir, "layers", "some-extend-false-analyzed.toml"), cmd.DefaultLogger)
h.AssertNil(t, err)
h.AssertStringContains(t, analyzedMD.RunImage.Reference, restoreRegFixtures.ReadOnlyRunImage+"@sha256:")
h.AssertEq(t, analyzedMD.RunImage.Image, restoreRegFixtures.ReadOnlyRunImage)
@ -291,7 +280,7 @@ func testRestorerFunc(platformAPI string) func(t *testing.T, when spec.G, it spe
h.AssertEq(t, len(fis), 1) // .gitkeep
} else {
t.Log("updates run image reference in analyzed.toml to include digest only")
analyzedMD, err := files.Handler.ReadAnalyzed(filepath.Join(copyDir, "layers", "some-extend-false-analyzed.toml"), cmd.DefaultLogger)
analyzedMD, err := phase.Config.ReadAnalyzed(filepath.Join(copyDir, "layers", "some-extend-false-analyzed.toml"), cmd.DefaultLogger)
h.AssertNil(t, err)
h.AssertStringContains(t, analyzedMD.RunImage.Reference, restoreRegFixtures.ReadOnlyRunImage+"@sha256:")
h.AssertEq(t, analyzedMD.RunImage.Image, restoreRegFixtures.ReadOnlyRunImage)
@ -322,7 +311,7 @@ func testRestorerFunc(platformAPI string) func(t *testing.T, when spec.G, it spe
),
)
t.Log("updates run image reference in analyzed.toml to include digest and target data")
analyzedMD, err := files.Handler.ReadAnalyzed(filepath.Join(copyDir, "layers", "some-extend-false-analyzed.toml"), cmd.DefaultLogger)
analyzedMD, err := phase.Config.ReadAnalyzed(filepath.Join(copyDir, "layers", "some-extend-false-analyzed.toml"), cmd.DefaultLogger)
h.AssertNil(t, err)
h.AssertStringDoesNotContain(t, analyzedMD.RunImage.Reference, "@sha256:") // daemon image ID
h.AssertEq(t, analyzedMD.RunImage.Image, restoreRegFixtures.ReadOnlyRunImage)

View File

@ -0,0 +1,12 @@
FROM mcr.microsoft.com/windows/nanoserver:1809
USER ContainerAdministrator
COPY container /
WORKDIR /layers
ENV CNB_USER_ID=1
ENV CNB_GROUP_ID=1
ENV CNB_PLATFORM_API=${cnb_platform_api}

View File

@ -0,0 +1,14 @@
FROM mcr.microsoft.com/windows/nanoserver:1809
USER ContainerAdministrator
COPY container /
ENTRYPOINT ["/cnb/lifecycle/builder"]
WORKDIR /layers
ENV CNB_USER_ID=1
ENV CNB_GROUP_ID=1
ENV CNB_PLATFORM_API=${cnb_platform_api}

View File

@ -1,8 +0,0 @@
# Buildpack API version
api = "0.9"
# Extension ID and metadata
[extension]
id = "samples/hello-world"
version = "0.0.1"
name = "Hello World Extension"

View File

@ -1,9 +0,0 @@
[[order]]
[[order.group]]
id = "samples/hello-world"
version = "0.0.1"
[[order-extensions]]
[[order-extensions.group]]
id = "samples/hello-world"
version = "0.0.1"

View File

@ -1,4 +1,4 @@
FROM ubuntu:jammy
FROM ubuntu:bionic
ARG cnb_uid=1234
ARG cnb_gid=1000

View File

@ -1,12 +1,6 @@
api = "0.9"
api = "0.10"
[buildpack]
id = "simple_buildpack"
version = "simple_buildpack_version"
name = "Simple Buildpack"
[[stacks]]
id = "io.buildpacks.stacks.bionic"
[[stacks]]
id = "io.buildpacks.stacks.jammy"
id = "simple_buildpack"
version = "simple_buildpack_version"
name = "Simple Buildpack"

View File

@ -1,17 +0,0 @@
{
"buildpacks": [
{
"key": "corrupted_buildpack",
"version": "corrupted_v1",
"layers": {
"corrupted-layer": {
"sha": "sha256:258dfa0cc987efebc17559694866ebc91139e7c0e574f60d1d4092f53d7dff59",
"data": null,
"build": false,
"launch": true,
"cache": true
}
}
}
]
}

View File

@ -1 +1 @@
sha256:2d9c9c638d5c4f0df067eeae7b9c99ad05776a89d19ab863c28850a91e5f2944
sha256:b89860e2f9c62e6b5d66d3ce019e18cdabae30273c25150b7f20a82f7a70e494

View File

@ -1,3 +0,0 @@
[types]
cache = true
launch = true

View File

@ -1 +0,0 @@
digest-not-match-data

View File

@ -7,8 +7,3 @@
id = "cacher_buildpack"
version = "cacher_v1"
api = "0.8"
[[group]]
id = "corrupted_buildpack"
version = "corrupted_v1"
api = "0.8"

View File

@ -0,0 +1,107 @@
{
"architecture": "amd64",
"created": "0001-01-01T00:00:00Z",
"history": [
{
"created": "2023-03-01T03:18:00.301777816Z",
"created_by": "/bin/sh -c #(nop) ARG RELEASE",
"empty_layer": true
},
{
"created": "2023-03-01T03:18:00.360617499Z",
"created_by": "/bin/sh -c #(nop) ARG LAUNCHPAD_BUILD_ARCH",
"empty_layer": true
},
{
"created": "2023-03-01T03:18:00.41927153Z",
"created_by": "/bin/sh -c #(nop) LABEL org.opencontainers.image.ref.name=ubuntu",
"empty_layer": true
},
{
"created": "2023-03-01T03:18:00.485275751Z",
"created_by": "/bin/sh -c #(nop) LABEL org.opencontainers.image.version=18.04",
"empty_layer": true
},
{
"created": "2023-03-01T03:18:02.163454314Z",
"created_by": "/bin/sh -c #(nop) ADD file:66eb2ef5574cdf80bc0cb3af1637407620c1869f58cc7514395e3f5aea45cc3b in / "
},
{
"created": "2023-03-01T03:18:02.508220832Z",
"created_by": "/bin/sh -c #(nop) CMD [\"/bin/bash\"]",
"empty_layer": true
},
{
"created": "2023-03-06T17:34:37.7930108Z",
"created_by": "ARG cnb_uid=1234",
"comment": "buildkit.dockerfile.v0",
"empty_layer": true
},
{
"created": "2023-03-06T17:34:37.7930108Z",
"created_by": "ARG cnb_gid=1000",
"comment": "buildkit.dockerfile.v0",
"empty_layer": true
},
{
"created": "2023-03-06T17:34:37.7930108Z",
"created_by": "ENV CNB_USER_ID=1234",
"comment": "buildkit.dockerfile.v0",
"empty_layer": true
},
{
"created": "2023-03-06T17:34:37.7930108Z",
"created_by": "ENV CNB_GROUP_ID=1000",
"comment": "buildkit.dockerfile.v0",
"empty_layer": true
},
{
"created": "2023-03-06T17:34:37.7930108Z",
"created_by": "COPY ./container/ / # buildkit",
"comment": "buildkit.dockerfile.v0"
},
{
"created": "2023-03-06T17:34:39.0316521Z",
"created_by": "RUN |2 cnb_uid=1234 cnb_gid=1000 /bin/sh -c groupadd cnb --gid ${cnb_gid} && useradd --uid ${cnb_uid} --gid ${cnb_gid} -m -s /bin/bash cnb # buildkit",
"comment": "buildkit.dockerfile.v0"
},
{
"author": "kaniko",
"created": "0001-01-01T00:00:00Z",
"created_by": "Layer: 'RUN apt-get update && apt-get install -y curl', Created by extension: curl"
},
{
"author": "kaniko",
"created": "0001-01-01T00:00:00Z",
"created_by": "Layer: 'RUN apt-get update && apt-get install -y tree', Created by extension: tree"
}
],
"os": "linux",
"rootfs": {
"type": "layers",
"diff_ids": [
"sha256:52c5ca3e9f3bf4c13613fb3269982734b189e1e09563b65b670fc8be0e223e03",
"sha256:a44ae1c29c4868890bf64d0f9627291e9b80d6a80d8938c942c6a43b2f0cfde1",
"sha256:049685392ebd2ca7b262f19e854d6fd3ff60bcb7a96394422b528ea30d04ca67",
"sha256:60600f423214c27fd184ebc96ae765bf2b4703c9981fb4205d28dd35e7eec4ae",
"sha256:1d811b70500e2e9a5e5b8ca7429ef02e091cdf4657b02e456ec54dd1baea0a66"
]
},
"config": {
"Cmd": [
"/bin/bash"
],
"Env": [
"PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin",
"CNB_USER_ID=1234",
"CNB_GROUP_ID=1000",
"CNB_STACK_ID=stack-id-from-ext-tree"
],
"Labels": {
"io.buildpacks.rebasable": "false",
"org.opencontainers.image.ref.name": "ubuntu",
"org.opencontainers.image.version": "18.04"
},
"User": "root"
}
}

View File

@ -1,47 +0,0 @@
{
"architecture": "amd64",
"created": "0001-01-01T00:00:00Z",
"history": [
{
"author": "some-base-image-author",
"created": "2023-03-06T17:34:39.0316521Z",
"created_by": "FROM some-base-image"
},
{
"author": "kaniko",
"created": "0001-01-01T00:00:00Z",
"created_by": "Layer: 'RUN mkdir /some-dir && echo some-data > /some-dir/some-file && echo some-data > /some-file', Created by extension: first-extension"
},
{
"author": "kaniko",
"created": "0001-01-01T00:00:00Z",
"created_by": "Layer: 'RUN mkdir /some-other-dir && echo some-data > /some-other-dir/some-file && echo some-data > /some-other-file', Created by extension: second-extension"
}
],
"os": "linux",
"rootfs": {
"type": "layers",
"diff_ids": [
"sha256:b5bb9d8014a0f9b1d61e21e796d78dccdf1352f23cd32812f4850b878ae4944c",
"sha256:d8dea3a780ba766c08bd11800809652ce5e9eba50b7b94ac09cb7f5e98e07f08",
"sha256:36f3735021a89a605c3da10b9659f0ec69e7c4c72abc802dc32471f1b080fd78"
]
},
"config": {
"Cmd": [
"/bin/bash"
],
"Env": [
"PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin",
"CNB_USER_ID=1234",
"CNB_GROUP_ID=1000",
"CNB_STACK_ID=some-stack-id"
],
"Labels": {
"io.buildpacks.rebasable": "false",
"org.opencontainers.image.ref.name": "ubuntu",
"org.opencontainers.image.version": "18.04"
},
"User": "root"
}
}

View File

@ -1,26 +0,0 @@
{
"schemaVersion": 2,
"mediaType": "application/vnd.docker.distribution.manifest.v2+json",
"config": {
"mediaType": "application/vnd.docker.container.image.v1+json",
"size": 2771,
"digest": "sha256:2dc6ef9f627c01f3f9e4f735c90f0251b5adaf6ad5685c5afb5cf638412fad67"
},
"layers": [
{
"mediaType": "application/vnd.docker.image.rootfs.diff.tar.gzip",
"size": 26711153,
"digest": "sha256:0064b1b97ec0775813740e8cb92821a6d84fd38eee70bafba9c12d9c37534661"
},
{
"mediaType": "application/vnd.docker.image.rootfs.diff.tar.gzip",
"size": 38445484,
"digest": "sha256:65c2873d397056a5cb4169790654d787579b005f18b903082b177d4d9b4aecf5"
},
{
"mediaType": "application/vnd.docker.image.rootfs.diff.tar.gzip",
"size": 146545,
"digest": "sha256:0fb9b88c9cbe9f11b4c8da645f390df59f5949632985a0bfc2a842ef17b2ad18"
}
]
}

View File

@ -0,0 +1,36 @@
{
"schemaVersion": 2,
"mediaType": "application/vnd.docker.distribution.manifest.v2+json",
"config": {
"mediaType": "application/vnd.docker.container.image.v1+json",
"size": 2771,
"digest": "sha256:259d97828cc7af9f2f9c62ce4caf6c9eac9d0898b114734ee259a698858debd1"
},
"layers": [
{
"mediaType": "application/vnd.docker.image.rootfs.diff.tar.gzip",
"size": 26711153,
"digest": "sha256:0064b1b97ec0775813740e8cb92821a6d84fd38eee70bafba9c12d9c37534661"
},
{
"mediaType": "application/vnd.docker.image.rootfs.diff.tar.gzip",
"size": 10083299,
"digest": "sha256:b10e5b54dba2a40f7d836f210ea43d7ae6a908f125374b2f70a5ac538b59e8a6"
},
{
"mediaType": "application/vnd.docker.image.rootfs.diff.tar.gzip",
"size": 4698,
"digest": "sha256:b9f98a18bb36447e4f8a0420bcdb6f8f7a7494036a42a83265daf3e53f593d20"
},
{
"mediaType": "application/vnd.docker.image.rootfs.diff.tar.gzip",
"size": 38445484,
"digest": "sha256:482346d1e0c7afa2514ec366d2e000e0667d0a6664690aab3c8ad51c81915b91"
},
{
"mediaType": "application/vnd.docker.image.rootfs.diff.tar.gzip",
"size": 146545,
"digest": "sha256:0c5f7a6fe14dbd19670f39e7466051cbd40b3a534c0812659740fb03e2137c1a"
}
]
}

View File

@ -4,7 +4,7 @@
{
"mediaType": "application/vnd.docker.distribution.manifest.v2+json",
"size": 1083,
"digest": "sha256:40007d6086160bcdf45770ed12d23f0c594013cf0cd5e65ffc67be8f46e0d9c9"
"digest": "sha256:c72eda1cc5b6c41360b95d73f881eff9d0655e56c693a2bc6cc9312c9e70aa24"
}
]
}

View File

@ -1,9 +0,0 @@
ARG base_image
FROM ${base_image}
USER root
RUN apt-get update && apt-get install -y curl
COPY build-file /
ARG build_id=0
RUN echo ${build_id}

View File

@ -1,15 +0,0 @@
ARG base_image
FROM ${base_image}
USER root
RUN apt-get update && apt-get install -y curl
COPY run-file /
ARG build_id=0
RUN echo ${build_id}
# This should not create a layer as the path should be ignored by kaniko
RUN echo "some-content" > /workspace/some-file
ARG user_id
USER ${user_id}

View File

@ -1,11 +0,0 @@
ARG base_image
FROM ${base_image}
USER root
RUN apt-get update && apt-get install -y tree
COPY shared-file /shared-build
ENV CNB_STACK_ID=stack-id-from-ext-tree
ARG build_id=0
RUN echo ${build_id}

View File

@ -1,17 +0,0 @@
ARG base_image
FROM ${base_image}
USER root
RUN apt-get update && apt-get install -y tree
COPY shared-file /shared-run
ENV CNB_STACK_ID=stack-id-from-ext-tree
ARG build_id=0
RUN echo ${build_id}
# tree is not really rebasable, but we set the label here to test that the label in the extended image is set correctly
LABEL io.buildpacks.rebasable=true
ARG user_id
USER ${user_id}

View File

@ -1,4 +1,4 @@
FROM golang:1.24.6 as builder
FROM golang:1.20 as builder
COPY exec.d/ /go/src/exec.d
RUN GO111MODULE=off go build -o helper ./src/exec.d

View File

@ -0,0 +1,16 @@
FROM golang:1.20-nanoserver-1809
COPY exec.d/ /go/src/exec.d
WORKDIR /go/src
ENV GO111MODULE=off
RUN go build -o helper.exe exec.d
COPY windows/container /
RUN mkdir c:\layers\0.9_buildpack\some_layer\exec.d\exec.d-checker
RUN copy helper.exe c:\layers\0.9_buildpack\some_layer\exec.d\helper.exe
RUN copy helper.exe c:\layers\0.9_buildpack\some_layer\exec.d\exec.d-checker\helper.exe
ENV PATH="c:\cnb\process;c:\cnb\lifecycle;C:\Windows\system32;C:\Windows;"
ENTRYPOINT ["c:\\cnb\\lifecycle\\launcher"]

View File

@ -1,4 +1,5 @@
//go:build unix
//go:build linux || darwin
// +build linux darwin
package main

View File

@ -0,0 +1,10 @@
FROM mcr.microsoft.com/windows/nanoserver:1809
USER ContainerAdministrator
COPY container /
ENV CNB_USER_ID=1
ENV CNB_GROUP_ID=1
ENV CNB_PLATFORM_API=${cnb_platform_api}

View File

@ -8,10 +8,10 @@ ENV CNB_GROUP_ID=${cnb_gid}
COPY ./container/ /
# We have to pre-create the tar files so that their digests do not change due to timestamps
# But, ':' in the filepath on Windows is not allowed
RUN mv /cache/committed/sha256_2d9c9c638d5c4f0df067eeae7b9c99ad05776a89d19ab863c28850a91e5f2944.tar /cache/committed/sha256:2d9c9c638d5c4f0df067eeae7b9c99ad05776a89d19ab863c28850a91e5f2944.tar
RUN mv /cache/committed/sha256_430338f576c11e5236669f9c843599d96afe28784cffcb2d46ddb07beb00df78.tar /cache/committed/sha256:430338f576c11e5236669f9c843599d96afe28784cffcb2d46ddb07beb00df78.tar
# turn /to_cache/<buildpack> directories into cache tarballs
# these are referenced by sha in /cache/committed/io.buildpacks.lifecycle.cache.metadata
RUN tar cvf /cache/committed/sha256:b89860e2f9c62e6b5d66d3ce019e18cdabae30273c25150b7f20a82f7a70e494.tar -C /to_cache/cacher_buildpack layers
RUN tar cvf /cache/committed/sha256:58bafa1e79c8e44151141c95086beb37ca85b69578fc890bce33bb4c6c8e851f.tar -C /to_cache/unused_buildpack layers
ENTRYPOINT ["/cnb/lifecycle/restorer"]

View File

@ -1,43 +1 @@
{
"buildpacks": [
{
"key": "cacher_buildpack",
"version": "cacher_v1",
"layers": {
"cached-layer": {
"sha": "sha256:2d9c9c638d5c4f0df067eeae7b9c99ad05776a89d19ab863c28850a91e5f2944",
"data": null,
"build": false,
"launch": false,
"cache": true
}
}
},
{
"key": "corrupted_buildpack",
"version": "corrupted_v1",
"layers": {
"corrupted-layer": {
"sha": "sha256:b5bb9d8014a0f9b1d61e21e796d78dccdf1352f23cd32812f4850b878ae4944c",
"data": null,
"build": false,
"launch": false,
"cache": true
}
}
},
{
"key": "unused_buildpack",
"version": "v1",
"layers": {
"cached-layer": {
"sha": "sha256:430338f576c11e5236669f9c843599d96afe28784cffcb2d46ddb07beb00df78",
"data": null,
"build": false,
"launch": false,
"cache": true
}
}
}
]
}
{"buildpacks":[{"key":"cacher_buildpack","version":"cacher_v1","layers":{"cached-layer":{"sha":"sha256:b89860e2f9c62e6b5d66d3ce019e18cdabae30273c25150b7f20a82f7a70e494","data":null,"build":false,"launch":false,"cache":true}}},{"key":"unused_buildpack","version":"v1","layers":{"cached-layer":{"sha":"sha256:58bafa1e79c8e44151141c95086beb37ca85b69578fc890bce33bb4c6c8e851f","data":null,"build":false,"launch":false,"cache":true}}}]}

View File

@ -1 +1 @@
sha256:2d9c9c638d5c4f0df067eeae7b9c99ad05776a89d19ab863c28850a91e5f2944
sha256:b89860e2f9c62e6b5d66d3ce019e18cdabae30273c25150b7f20a82f7a70e494

View File

@ -8,11 +8,6 @@
version = "cacher_v1"
api = "0.10"
[[group]]
id = "corrupted_buildpack"
version = "corrupted_v1"
api = "0.11"
[[group-extensions]]
id = "some-extension-id"
version = "v1"

View File

@ -1,4 +1,5 @@
//go:build unix
//go:build linux || darwin
// +build linux darwin
package acceptance

View File

@ -0,0 +1,30 @@
package acceptance
import (
"path"
"path/filepath"
"strings"
)
const (
containerBaseImage = "mcr.microsoft.com/windows/nanoserver:1809"
containerBaseImageFull = "mcr.microsoft.com/windows/nanoserver:1809"
dockerfileName = "Dockerfile.windows"
exe = ".exe"
execDBpDir = "0.9_buildpack"
)
var dockerSocketMount = []string{
"--mount", `type=npipe,source=\\.\pipe\docker_engine,target=\\.\pipe\docker_engine`,
"--user", "ContainerAdministrator",
}
// ctrPath equivalent to path.Join but converts to Windows slashes and drive prefix when needed
func ctrPath(unixPathParts ...string) string {
unixPath := path.Join(unixPathParts...)
windowsPath := filepath.FromSlash(unixPath)
if strings.HasPrefix(windowsPath, `\`) {
return "c:" + windowsPath
}
return windowsPath
}

View File

@ -11,11 +11,11 @@ var (
// Platform is a pair of lists of Platform API versions:
// 1. All supported versions (including deprecated versions)
// 2. The versions that are deprecated
Platform = newApisMustParse([]string{"0.7", "0.8", "0.9", "0.10", "0.11", "0.12", "0.13", "0.14"}, []string{})
Platform = newApisMustParse([]string{"0.7", "0.8", "0.9", "0.10", "0.11", "0.12"}, []string{})
// Buildpack is a pair of lists of Buildpack API versions:
// 1. All supported versions (including deprecated versions)
// 2. The versions that are deprecated
Buildpack = newApisMustParse([]string{"0.7", "0.8", "0.9", "0.10", "0.11"}, []string{})
Buildpack = newApisMustParse([]string{"0.7", "0.8", "0.9", "0.10"}, []string{})
)
type APIs struct {

View File

@ -4,6 +4,7 @@ import (
"archive/tar"
"os"
"path/filepath"
"runtime"
"testing"
"github.com/sclevine/spec"
@ -30,16 +31,31 @@ func testExtract(t *testing.T, when spec.G, it spec.S) {
it.Before(func() {
tr, tmpDir = newFakeTarReader(t)
pathModes = []archive.PathMode{
{"root", os.ModeDir + 0755},
{"root/readonly", os.ModeDir + 0500},
{"root/readonly/readonlysub", os.ModeDir + 0500},
{"root/readonly/readonlysub/somefile", 0444},
{"root/standarddir", os.ModeDir + 0755},
{"root/standarddir/somefile", 0644},
{"root/nonexistdirnotintar", os.ModeDir + os.FileMode(int(os.ModePerm)&^originalUmask)}, //nolint
{"root/symlinkdir", os.ModeSymlink + 0777}, // symlink permissions are not preserved from archive
{"root/symlinkfile", os.ModeSymlink + 0777}, // symlink permissions are not preserved from archive
// Golang for Windows only implements owner permissions
if runtime.GOOS == "windows" {
pathModes = []archive.PathMode{
{`root`, os.ModeDir + 0777},
{`root\readonly`, os.ModeDir + 0555},
{`root\readonly\readonlysub`, os.ModeDir + 0555},
{`root\readonly\readonlysub\somefile`, 0444},
{`root\standarddir`, os.ModeDir + 0777},
{`root\standarddir\somefile`, 0666},
{`root\nonexistdirnotintar`, os.ModeDir + 0777},
{`root\symlinkdir`, os.ModeSymlink + 0666},
{`root\symlinkfile`, os.ModeSymlink + 0666},
}
} else {
pathModes = []archive.PathMode{
{"root", os.ModeDir + 0755},
{"root/readonly", os.ModeDir + 0500},
{"root/readonly/readonlysub", os.ModeDir + 0500},
{"root/readonly/readonlysub/somefile", 0444},
{"root/standarddir", os.ModeDir + 0755},
{"root/standarddir/somefile", 0644},
{"root/nonexistdirnotintar", os.ModeDir + os.FileMode(int(os.ModePerm)&^originalUmask)},
{"root/symlinkdir", os.ModeSymlink + 0777}, // symlink permissions are not preserved from archive
{"root/symlinkfile", os.ModeSymlink + 0777}, // symlink permissions are not preserved from archive
}
}
})
@ -94,7 +110,12 @@ func testExtract(t *testing.T, when spec.G, it spec.S) {
fileInfo, err := os.Stat(filepath.Join(tmpDir, "root"))
h.AssertNil(t, err)
h.AssertEq(t, fileInfo.Mode(), os.ModeDir+0744)
if runtime.GOOS != "windows" {
h.AssertEq(t, fileInfo.Mode(), os.ModeDir+0744)
} else {
// Golang for Windows only implements owner permissions
h.AssertEq(t, fileInfo.Mode(), os.ModeDir+0777)
}
})
})
}

View File

@ -3,6 +3,7 @@ package archive_test
import (
"archive/tar"
"io"
"runtime"
"testing"
"github.com/sclevine/spec"
@ -32,7 +33,11 @@ func testNormalizingTarReader(t *testing.T, when spec.G, it spec.S) {
it("converts path separators", func() {
hdr, err := ntr.Next()
h.AssertNil(t, err)
h.AssertEq(t, hdr.Name, `/some/path`)
if runtime.GOOS == "windows" {
h.AssertEq(t, hdr.Name, `\some\path`)
} else {
h.AssertEq(t, hdr.Name, `/some/path`)
}
})
when("#Strip", func() {
@ -40,7 +45,11 @@ func testNormalizingTarReader(t *testing.T, when spec.G, it spec.S) {
ntr.Strip("/some")
hdr, err := ntr.Next()
h.AssertNil(t, err)
h.AssertEq(t, hdr.Name, `/path`)
if runtime.GOOS == "windows" {
h.AssertEq(t, hdr.Name, `\path`)
} else {
h.AssertEq(t, hdr.Name, `/path`)
}
})
})
@ -49,7 +58,11 @@ func testNormalizingTarReader(t *testing.T, when spec.G, it spec.S) {
ntr.PrependDir("/super-dir")
hdr, err := ntr.Next()
h.AssertNil(t, err)
h.AssertEq(t, hdr.Name, `/super-dir/some/path`)
if runtime.GOOS == "windows" {
h.AssertEq(t, hdr.Name, `\super-dir\some\path`)
} else {
h.AssertEq(t, hdr.Name, `/super-dir/some/path`)
}
})
})
@ -60,7 +73,11 @@ func testNormalizingTarReader(t *testing.T, when spec.G, it spec.S) {
ntr.ExcludePaths([]string{"excluded-dir"})
hdr, err := ntr.Next()
h.AssertNil(t, err)
h.AssertEq(t, hdr.Name, `/some/path`)
if runtime.GOOS == "windows" {
h.AssertEq(t, hdr.Name, `\some\path`)
} else {
h.AssertEq(t, hdr.Name, `/some/path`)
}
})
})
})

View File

@ -1,4 +1,5 @@
//go:build unix
//go:build linux || darwin
// +build linux darwin
package archive

View File

@ -2,6 +2,7 @@ package archive_test
import (
"archive/tar"
"runtime"
"testing"
"time"
@ -37,6 +38,21 @@ func testNormalizingTarWriter(t *testing.T, when spec.G, it spec.S) {
h.AssertEq(t, ftw.getLastHeader().Gname, "")
})
when("windows", func() {
it.Before(func() {
if runtime.GOOS != "windows" {
t.Skip("windows specific test")
}
})
it("converts path separators", func() {
h.AssertNil(t, ntw.WriteHeader(&tar.Header{
Name: `c:\some\file\path`,
}))
h.AssertEq(t, ftw.getLastHeader().Name, "/some/file/path")
})
})
when("#WithUID", func() {
it("sets the uid", func() {
ntw.WithUID(999)

View File

@ -1,10 +0,0 @@
# auth
## Skipping Vendor Specific Keychains
The auth package has configuration available to skip vendor specific keychain implementations. If you are a platform handling credentials yourself, you may want to skip loading these keychains. This can improve performance as the helpers automatically get invoked based on the hosting environment and the registries being interacted with.
Set any of the following to `true` to skip loading the vendor keychain.
`CNB_REGISTRY_AUTH_KEYCHAIN_SKIP_AMAZON` - set to skip Amazon/AWS's ECR credhelper.
`CNB_REGISTRY_AUTH_KEYCHAIN_SKIP_AZURE` - set to skip Microsoft/Azure's ACR credhelper

View File

@ -7,7 +7,6 @@ import (
"io"
"os"
"regexp"
"strings"
ecr "github.com/awslabs/amazon-ecr-credential-helper/ecr-login"
"github.com/chrismellard/docker-credential-acr-env/pkg/credhelper"
@ -19,9 +18,6 @@ import (
const EnvRegistryAuth = "CNB_REGISTRY_AUTH"
// EnvRegistryAuthKeychainSkipFormat is the format string for the environment variable that can be used to skip the keychain for a specific vendor.
const EnvRegistryAuthKeychainSkipFormat = "CNB_REGISTRY_AUTH_KEYCHAIN_SKIP_%s"
var (
amazonKeychain = authn.NewKeychainFromHelper(ecr.NewECRHelper(ecr.WithLogger(io.Discard)))
azureKeychain = authn.NewKeychainFromHelper(credhelper.NewACRCredentialsHelper())
@ -38,27 +34,14 @@ func DefaultKeychain(images ...string) (authn.Keychain, error) {
return nil, err
}
keychains := []authn.Keychain{
return authn.NewMultiKeychain(
envKeychain,
NewResolvedKeychain(authn.DefaultKeychain, images...),
}
if vendorKeychainEnabled("amazon") {
keychains = append(keychains, NewResolvedKeychain(amazonKeychain, images...))
}
if vendorKeychainEnabled("azure") {
keychains = append(keychains, NewResolvedKeychain(azureKeychain, images...))
}
return authn.NewMultiKeychain(
keychains...,
NewResolvedKeychain(amazonKeychain, images...),
NewResolvedKeychain(azureKeychain, images...),
), nil
}
func vendorKeychainEnabled(provider string) bool {
providerUpper := strings.ToUpper(provider)
return os.Getenv(fmt.Sprintf(EnvRegistryAuthKeychainSkipFormat, providerUpper)) != "true"
}
// NewEnvKeychain returns an authn.Keychain that uses the provided environment variable as a source of credentials.
// The value of the environment variable should be a JSON object that maps OCI registry hostnames to Authorization headers.
func NewEnvKeychain(envVar string) (authn.Keychain, error) {

View File

@ -8,7 +8,6 @@ import (
"github.com/BurntSushi/toml"
"github.com/buildpacks/lifecycle/api"
"github.com/buildpacks/lifecycle/internal/encoding"
)
@ -18,7 +17,7 @@ type BpDescriptor struct {
Order Order `toml:"order"`
WithRootDir string `toml:"-"`
Targets []TargetMetadata `toml:"targets"`
Stacks []StackMetadata `toml:"stacks"` // just for backwards compat so we can check if it's the bionic stack, which we translate to a target
Stacks []StackMetadata `tome:"stacks"` // just for backwards compat so we can check if it's the bionic stack, which we translate to a target
}
@ -70,9 +69,7 @@ func ReadBpDescriptor(path string) (*BpDescriptor, error) {
if len(descriptor.Targets) == 0 {
for _, stack := range descriptor.Stacks {
if stack.ID == "io.buildpacks.stacks.bionic" {
if api.MustParse(descriptor.API()).AtLeast("0.10") || len(descriptor.Stacks) == 1 {
descriptor.Targets = append(descriptor.Targets, TargetMetadata{OS: "linux", Arch: "amd64", Distros: []OSDistro{{Name: "ubuntu", Version: "18.04"}}})
}
descriptor.Targets = append(descriptor.Targets, TargetMetadata{OS: "linux", Arch: "amd64", Distros: []OSDistro{{Name: "ubuntu", Version: "18.04"}}})
} else if stack.ID == "*" {
descriptor.Targets = append(descriptor.Targets, TargetMetadata{}) // matches any
}
@ -87,8 +84,11 @@ func ReadBpDescriptor(path string) (*BpDescriptor, error) {
return &BpDescriptor{}, err
}
for i := 0; i < len(binFiles); i++ {
bf := binFiles[len(binFiles)-i-1]
bf := binFiles[len(binFiles)-i-1] // we're iterating backwards b/c os.ReadDir sorts "build.exe" after "build" but we want to preferentially detect windows first.
fname := bf.Name()
if fname == "build.exe" || fname == "build.bat" {
descriptor.Targets = append(descriptor.Targets, TargetMetadata{OS: "windows"})
}
if fname == "build" {
descriptor.Targets = append(descriptor.Targets, TargetMetadata{OS: "linux"})
}

View File

@ -81,140 +81,44 @@ func testBpDescriptor(t *testing.T, when spec.G, it spec.S) {
h.AssertEq(t, descriptor.Targets[0].Distros[0].Version, "V8.4-2L3")
})
when("translating stacks to targets", func() {
when("older buildpacks", func() {
when("there is only bionic", func() {
it("creates a target", func() {
path := filepath.Join("testdata", "buildpack", "by-id", "B", "v1", "buildpack.toml")
descriptor, err := buildpack.ReadBpDescriptor(path)
h.AssertNil(t, err)
// common sanity checks
h.AssertEq(t, descriptor.WithAPI, "0.7")
h.AssertEq(t, descriptor.Buildpack.ID, "B")
h.AssertEq(t, descriptor.Buildpack.Name, "Buildpack B")
h.AssertEq(t, descriptor.Buildpack.Version, "v1")
h.AssertEq(t, descriptor.Buildpack.Homepage, "Buildpack B Homepage")
h.AssertEq(t, descriptor.Buildpack.SBOM, []string{"application/vnd.cyclonedx+json"})
// specific behaviors for this test
h.AssertEq(t, descriptor.Stacks[0].ID, "io.buildpacks.stacks.bionic")
h.AssertEq(t, len(descriptor.Targets), 1)
h.AssertEq(t, descriptor.Targets[0].Arch, "amd64")
h.AssertEq(t, descriptor.Targets[0].OS, "linux")
h.AssertEq(t, descriptor.Targets[0].Distros[0].Name, "ubuntu")
h.AssertEq(t, descriptor.Targets[0].Distros[0].Version, "18.04")
})
})
it("does translate one special stack value into target values for older apis", func() {
path := filepath.Join("testdata", "buildpack", "by-id", "B", "v1", "buildpack.toml")
descriptor, err := buildpack.ReadBpDescriptor(path)
h.AssertNil(t, err)
// common sanity checks
h.AssertEq(t, descriptor.WithAPI, "0.7")
h.AssertEq(t, descriptor.Buildpack.ID, "B")
h.AssertEq(t, descriptor.Buildpack.Name, "Buildpack B")
h.AssertEq(t, descriptor.Buildpack.Version, "v1")
h.AssertEq(t, descriptor.Buildpack.Homepage, "Buildpack B Homepage")
h.AssertEq(t, descriptor.Buildpack.SBOM, []string{"application/vnd.cyclonedx+json"})
// specific behaviors for this test
h.AssertEq(t, descriptor.Stacks[0].ID, "io.buildpacks.stacks.bionic")
h.AssertEq(t, len(descriptor.Targets), 1)
h.AssertEq(t, descriptor.Targets[0].Arch, "amd64")
h.AssertEq(t, descriptor.Targets[0].OS, "linux")
h.AssertEq(t, descriptor.Targets[0].Distros[0].Name, "ubuntu")
h.AssertEq(t, descriptor.Targets[0].Distros[0].Version, "18.04")
})
when("there are multiple stacks", func() {
it("does NOT create a target", func() {
path := filepath.Join("testdata", "buildpack", "by-id", "B", "v1.2", "buildpack.toml")
descriptor, err := buildpack.ReadBpDescriptor(path)
h.AssertNil(t, err)
// common sanity checks
h.AssertEq(t, descriptor.WithAPI, "0.7")
h.AssertEq(t, descriptor.Buildpack.ID, "B")
h.AssertEq(t, descriptor.Buildpack.Name, "Buildpack B")
h.AssertEq(t, descriptor.Buildpack.Version, "v1.2")
h.AssertEq(t, descriptor.Buildpack.Homepage, "Buildpack B Homepage")
h.AssertEq(t, descriptor.Buildpack.SBOM, []string{"application/vnd.cyclonedx+json"})
// specific behaviors for this test
h.AssertEq(t, descriptor.Stacks[0].ID, "io.buildpacks.stacks.bionic")
h.AssertEq(t, len(descriptor.Targets), 0)
})
})
when("there is a wildcard stack", func() {
it("creates a wildcard target", func() {
path := filepath.Join("testdata", "buildpack", "by-id", "B", "v1.star", "buildpack.toml")
descriptor, err := buildpack.ReadBpDescriptor(path)
h.AssertNil(t, err)
// common sanity checks
h.AssertEq(t, descriptor.WithAPI, "0.7")
h.AssertEq(t, descriptor.Buildpack.ID, "B")
h.AssertEq(t, descriptor.Buildpack.Name, "Buildpack B")
h.AssertEq(t, descriptor.Buildpack.Version, "v1.star")
h.AssertEq(t, descriptor.Buildpack.Homepage, "Buildpack B Homepage")
h.AssertEq(t, descriptor.Buildpack.SBOM, []string{"application/vnd.cyclonedx+json"})
// specific behaviors for this test
h.AssertEq(t, descriptor.Stacks[0].ID, "*")
h.AssertEq(t, len(descriptor.Targets), 1)
// a target that is completely empty will always match whatever is the base image target
h.AssertEq(t, descriptor.Targets[0].Arch, "")
h.AssertEq(t, descriptor.Targets[0].OS, "")
h.AssertEq(t, descriptor.Targets[0].ArchVariant, "")
h.AssertEq(t, len(descriptor.Targets[0].Distros), 0)
})
})
})
when("newer buildpacks", func() {
when("there is only bionic", func() {
it("creates a target", func() {
path := filepath.Join("testdata", "buildpack", "by-id", "B", "v2", "buildpack.toml")
descriptor, err := buildpack.ReadBpDescriptor(path)
h.AssertNil(t, err)
// common sanity checks
h.AssertEq(t, descriptor.WithAPI, "0.12")
h.AssertEq(t, descriptor.Buildpack.ID, "B")
h.AssertEq(t, descriptor.Buildpack.Name, "Buildpack B")
h.AssertEq(t, descriptor.Buildpack.Version, "v2")
h.AssertEq(t, descriptor.Buildpack.Homepage, "Buildpack B Homepage")
h.AssertEq(t, descriptor.Buildpack.SBOM, []string{"application/vnd.cyclonedx+json"})
// specific behaviors for this test
h.AssertEq(t, descriptor.Stacks[0].ID, "io.buildpacks.stacks.bionic")
h.AssertEq(t, len(descriptor.Targets), 1)
h.AssertEq(t, descriptor.Targets[0].Arch, "amd64")
h.AssertEq(t, descriptor.Targets[0].OS, "linux")
h.AssertEq(t, descriptor.Targets[0].Distros[0].Name, "ubuntu")
h.AssertEq(t, descriptor.Targets[0].Distros[0].Version, "18.04")
})
})
when("there are multiple stacks", func() {
it("creates a target", func() {
path := filepath.Join("testdata", "buildpack", "by-id", "B", "v2.2", "buildpack.toml")
descriptor, err := buildpack.ReadBpDescriptor(path)
h.AssertNil(t, err)
// common sanity checks
h.AssertEq(t, descriptor.WithAPI, "0.12")
h.AssertEq(t, descriptor.Buildpack.ID, "B")
h.AssertEq(t, descriptor.Buildpack.Name, "Buildpack B")
h.AssertEq(t, descriptor.Buildpack.Version, "v2.2")
h.AssertEq(t, descriptor.Buildpack.Homepage, "Buildpack B Homepage")
h.AssertEq(t, descriptor.Buildpack.SBOM, []string{"application/vnd.cyclonedx+json"})
// specific behaviors for this test
h.AssertEq(t, descriptor.Stacks[0].ID, "io.buildpacks.stacks.bionic")
h.AssertEq(t, len(descriptor.Targets), 1)
h.AssertEq(t, descriptor.Targets[0].Arch, "amd64")
h.AssertEq(t, descriptor.Targets[0].OS, "linux")
h.AssertEq(t, descriptor.Targets[0].Distros[0].Name, "ubuntu")
h.AssertEq(t, descriptor.Targets[0].Distros[0].Version, "18.04")
})
})
when("there is a wildcard stack", func() {
it("creates a wildcard target", func() {
path := filepath.Join("testdata", "buildpack", "by-id", "B", "v2.star", "buildpack.toml")
descriptor, err := buildpack.ReadBpDescriptor(path)
h.AssertNil(t, err)
// common sanity checks
h.AssertEq(t, descriptor.WithAPI, "0.12")
h.AssertEq(t, descriptor.Buildpack.ID, "B")
h.AssertEq(t, descriptor.Buildpack.Name, "Buildpack B")
h.AssertEq(t, descriptor.Buildpack.Version, "v2.star")
h.AssertEq(t, descriptor.Buildpack.Homepage, "Buildpack B Homepage")
h.AssertEq(t, descriptor.Buildpack.SBOM, []string{"application/vnd.cyclonedx+json"})
// specific behaviors for this test
h.AssertEq(t, descriptor.Stacks[0].ID, "*")
h.AssertEq(t, len(descriptor.Targets), 1)
// a target that is completely empty will always match whatever is the base image target
h.AssertEq(t, descriptor.Targets[0].Arch, "")
h.AssertEq(t, descriptor.Targets[0].OS, "")
h.AssertEq(t, descriptor.Targets[0].ArchVariant, "")
h.AssertEq(t, len(descriptor.Targets[0].Distros), 0)
})
})
})
it("translates one special stack value into target values", func() {
path := filepath.Join("testdata", "buildpack", "by-id", "B", "v2", "buildpack.toml")
descriptor, err := buildpack.ReadBpDescriptor(path)
h.AssertNil(t, err)
// common sanity checks
h.AssertEq(t, descriptor.WithAPI, "0.12")
h.AssertEq(t, descriptor.Buildpack.ID, "B")
h.AssertEq(t, descriptor.Buildpack.Name, "Buildpack B")
h.AssertEq(t, descriptor.Buildpack.Version, "v1")
h.AssertEq(t, descriptor.Buildpack.Homepage, "Buildpack B Homepage")
h.AssertEq(t, descriptor.Buildpack.SBOM, []string{"application/vnd.cyclonedx+json"})
// specific behaviors for this test
h.AssertEq(t, descriptor.Stacks[0].ID, "io.buildpacks.stacks.bionic")
h.AssertEq(t, len(descriptor.Targets), 1)
h.AssertEq(t, descriptor.Targets[0].Arch, "amd64")
h.AssertEq(t, descriptor.Targets[0].OS, "linux")
h.AssertEq(t, descriptor.Targets[0].Distros[0].Name, "ubuntu")
h.AssertEq(t, descriptor.Targets[0].Distros[0].Version, "18.04")
})
it("does not translate non-special stack values", func() {
@ -252,5 +156,24 @@ func testBpDescriptor(t *testing.T, when spec.G, it spec.S) {
h.AssertEq(t, descriptor.Targets[0].OS, "linux")
h.AssertEq(t, len(descriptor.Targets[0].Distros), 0)
})
it("detects windows/* if batch files are present and ignores linux", func() {
path := filepath.Join("testdata", "buildpack", "by-id", "A", "v1", "buildpack.toml")
descriptor, err := buildpack.ReadBpDescriptor(path)
h.AssertNil(t, err)
// common sanity assertions
h.AssertEq(t, descriptor.WithAPI, "0.7")
h.AssertEq(t, descriptor.Buildpack.ID, "A")
h.AssertEq(t, descriptor.Buildpack.Name, "Buildpack A")
h.AssertEq(t, descriptor.Buildpack.Version, "v1")
h.AssertEq(t, descriptor.Buildpack.Homepage, "Buildpack A Homepage")
h.AssertEq(t, descriptor.Buildpack.SBOM, []string{"application/vnd.cyclonedx+json"})
// specific behaviors for this test
h.AssertEq(t, len(descriptor.Targets), 2)
h.AssertEq(t, descriptor.Targets[0].Arch, "")
h.AssertEq(t, descriptor.Targets[0].OS, "windows")
h.AssertEq(t, descriptor.Targets[1].Arch, "")
h.AssertEq(t, descriptor.Targets[1].OS, "linux")
})
})
}

View File

@ -14,6 +14,7 @@ import (
"github.com/buildpacks/lifecycle/api"
"github.com/buildpacks/lifecycle/env"
"github.com/buildpacks/lifecycle/internal/encoding"
"github.com/buildpacks/lifecycle/internal/fsutil"
"github.com/buildpacks/lifecycle/launch"
"github.com/buildpacks/lifecycle/layers"
"github.com/buildpacks/lifecycle/log"
@ -34,7 +35,6 @@ type BuildInputs struct {
LayersDir string
PlatformDir string
Env BuildEnv
TargetEnv []string
Out, Err io.Writer
Plan Plan
}
@ -93,7 +93,7 @@ func (e *DefaultBuildExecutor) Build(d BpDescriptor, inputs BuildInputs, logger
}
logger.Debug("Updating environment")
if err := d.setupEnv(bpLayersDir, createdLayers, inputs.Env); err != nil {
if err := d.setupEnv(createdLayers, inputs.Env); err != nil {
return BuildOutputs{}, err
}
@ -132,7 +132,7 @@ func runBuildCmd(d BpDescriptor, bpLayersDir, planPath string, inputs BuildInput
) // #nosec G204
cmd.Dir = inputs.AppDir
cmd.Stdout = inputs.Out
cmd.Stderr = inputs.Out
cmd.Stderr = inputs.Err
var err error
if d.Buildpack.ClearEnv {
@ -151,9 +151,6 @@ func runBuildCmd(d BpDescriptor, bpLayersDir, planPath string, inputs BuildInput
EnvLayersDir+"="+bpLayersDir,
)
}
if api.MustParse(d.API()).AtLeast("0.10") {
cmd.Env = append(cmd.Env, inputs.TargetEnv...)
}
if err = cmd.Run(); err != nil {
return NewError(err, ErrTypeBuildpack)
@ -161,72 +158,68 @@ func runBuildCmd(d BpDescriptor, bpLayersDir, planPath string, inputs BuildInput
return nil
}
func (d BpDescriptor) processLayers(bpLayersDir string, logger log.Logger) (map[string]LayerMetadataFile, error) {
bpLayers := make(map[string]LayerMetadataFile)
if err := eachLayer(bpLayersDir, func(layerPath string) error {
layerFile, err := DecodeLayerMetadataFile(layerPath+".toml", d.WithAPI, logger)
func (d BpDescriptor) processLayers(layersDir string, logger log.Logger) (map[string]LayerMetadataFile, error) {
return eachLayer(layersDir, d.WithAPI, func(path, buildpackAPI string) (LayerMetadataFile, error) {
layerMetadataFile, err := DecodeLayerMetadataFile(path+".toml", buildpackAPI, logger)
if err != nil {
return fmt.Errorf("failed to decode layer metadata file: %w", err)
return LayerMetadataFile{}, err
}
if err = renameLayerDirIfNeeded(layerFile, layerPath); err != nil {
return fmt.Errorf("failed to rename layer directory: %w", err)
if err := renameLayerDirIfNeeded(layerMetadataFile, path); err != nil {
return LayerMetadataFile{}, err
}
bpLayers[layerPath] = layerFile
return nil
}); err != nil {
return nil, fmt.Errorf("failed to process buildpack layer: %w", err)
}
return bpLayers, nil
return layerMetadataFile, nil
})
}
func eachLayer(bpLayersDir string, fn func(layerPath string) error) error {
func eachLayer(bpLayersDir, buildpackAPI string, fn func(path, api string) (LayerMetadataFile, error)) (map[string]LayerMetadataFile, error) {
files, err := os.ReadDir(bpLayersDir)
if os.IsNotExist(err) {
return nil
return map[string]LayerMetadataFile{}, nil
} else if err != nil {
return err
return map[string]LayerMetadataFile{}, err
}
bpLayers := map[string]LayerMetadataFile{}
for _, f := range files {
if f.IsDir() || !strings.HasSuffix(f.Name(), ".toml") {
continue
}
path := filepath.Join(bpLayersDir, strings.TrimSuffix(f.Name(), ".toml"))
if err = fn(path); err != nil {
return err
layerMetadataFile, err := fn(path, buildpackAPI)
if err != nil {
return map[string]LayerMetadataFile{}, err
}
bpLayers[path] = layerMetadataFile
}
return nil
return bpLayers, nil
}
func renameLayerDirIfNeeded(layerMetadataFile LayerMetadataFile, layerDir string) error {
// rename <layers>/<layer> to <layers>/<layer>.ignore if all the types flags are set to false
if !layerMetadataFile.Launch && !layerMetadataFile.Cache && !layerMetadataFile.Build {
if err := os.Rename(layerDir, layerDir+".ignore"); err != nil && !os.IsNotExist(err) {
if err := fsutil.RenameWithWindowsFallback(layerDir, layerDir+".ignore"); err != nil && !os.IsNotExist(err) {
return err
}
}
return nil
}
func (d BpDescriptor) setupEnv(bpLayersDir string, createdLayers map[string]LayerMetadataFile, buildEnv BuildEnv) error {
func (d BpDescriptor) setupEnv(createdLayers map[string]LayerMetadataFile, buildEnv BuildEnv) error {
bpAPI := api.MustParse(d.WithAPI)
return eachLayer(bpLayersDir, func(layerPath string) error {
var err error
layerMetadataFile, ok := createdLayers[layerPath]
if !ok {
return fmt.Errorf("failed to find layer metadata for %s", layerPath)
}
for path, layerMetadataFile := range createdLayers {
if !layerMetadataFile.Build {
return nil
continue
}
if err = buildEnv.AddRootDir(layerPath); err != nil {
if err := buildEnv.AddRootDir(path); err != nil {
return err
}
if err = buildEnv.AddEnvDir(filepath.Join(layerPath, "env"), env.DefaultActionType(bpAPI)); err != nil {
if err := buildEnv.AddEnvDir(filepath.Join(path, "env"), env.DefaultActionType(bpAPI)); err != nil {
return err
}
return buildEnv.AddEnvDir(filepath.Join(layerPath, "env.build"), env.DefaultActionType(bpAPI))
})
if err := buildEnv.AddEnvDir(filepath.Join(path, "env.build"), env.DefaultActionType(bpAPI)); err != nil {
return err
}
}
return nil
}
func (d BpDescriptor) readOutputFilesBp(bpLayersDir, bpPlanPath string, bpPlanIn Plan, bpLayers map[string]LayerMetadataFile, logger log.Logger) (BuildOutputs, error) {

View File

@ -291,9 +291,12 @@ func testBuild(t *testing.T, when spec.G, it spec.S) {
if _, err := executor.Build(descriptor, inputs, logger); err != nil {
t.Fatalf("Unexpected error:\n%s\n", err)
}
if s := cmp.Diff(h.CleanEndings(stdout.String()), "build out: A@v1\nbuild err: A@v1\n"); s != "" {
if s := cmp.Diff(h.CleanEndings(stdout.String()), "build out: A@v1\n"); s != "" {
t.Fatalf("Unexpected stdout:\n%s\n", s)
}
if s := cmp.Diff(h.CleanEndings(stderr.String()), "build err: A@v1\n"); s != "" {
t.Fatalf("Unexpected stderr:\n%s\n", s)
}
})
when("modifying the env fails", func() {
@ -938,7 +941,7 @@ func testBuild(t *testing.T, when spec.G, it spec.S) {
filepath.Join(appDir, "launch-A-v1.toml"),
)
_, err := executor.Build(descriptor, inputs, logger)
h.AssertError(t, err, "toml: line 2 (last key \"processes.command\"): incompatible types: TOML value has type []any; destination has type string")
h.AssertError(t, err, "toml: line 2 (last key \"processes.command\"): incompatible types: TOML value has type []interface {}; destination has type string")
})
})
})

View File

@ -8,7 +8,7 @@ const (
// Descriptor exposes information contained in a buildpack.toml or extension.toml
// that is generic to buildpacks and/or image extensions.
//
//go:generate mockgen -package testmock -destination ../phase/testmock/component_descriptor.go github.com/buildpacks/lifecycle/buildpack Descriptor
//go:generate mockgen -package testmock -destination ../lifecycle/testmock/component_descriptor.go github.com/buildpacks/lifecycle/buildpack Descriptor
type Descriptor interface {
API() string
Homepage() string

View File

@ -30,7 +30,6 @@ type DetectInputs struct {
BuildConfigDir string
PlatformDir string
Env BuildEnv
TargetEnv []string
}
type DetectOutputs struct {
@ -179,9 +178,6 @@ func runDetect(d detectable, inputs DetectInputs, planPath string, envRootDirKey
EnvBuildPlanPath+"="+planPath,
)
}
if api.MustParse(d.API()).AtLeast("0.10") {
cmd.Env = append(cmd.Env, inputs.TargetEnv...)
}
if err := cmd.Run(); err != nil {
if err, ok := err.(*exec.ExitError); ok {

View File

@ -4,6 +4,7 @@ import (
"errors"
"os"
"path/filepath"
"runtime"
"testing"
"github.com/apex/log"
@ -294,6 +295,8 @@ func testDetect(t *testing.T, when spec.G, it spec.S) {
)
it.Before(func() {
h.SkipIf(t, runtime.GOOS == "windows", "Image extensions are not supported for Windows builds")
descriptorPath = filepath.Join("testdata", "extension", "by-id", "A", "v1", "extension.toml")
var err error
descriptor, err = buildpack.ReadExtDescriptor(descriptorPath)

View File

@ -8,7 +8,6 @@ import (
"strings"
"github.com/moby/buildkit/frontend/dockerfile/instructions"
"github.com/moby/buildkit/frontend/dockerfile/linter"
"github.com/moby/buildkit/frontend/dockerfile/parser"
"github.com/buildpacks/lifecycle/log"
@ -75,7 +74,7 @@ func parseDockerfile(dockerfile string) ([]instructions.Stage, []instructions.Ar
if err != nil {
return nil, nil, err
}
stages, metaArgs, err := instructions.Parse(p.AST, &linter.Linter{})
stages, metaArgs, err := instructions.Parse(p.AST)
if err != nil {
return nil, nil, err
}

View File

@ -43,10 +43,14 @@ func (d *ExtDescriptor) inferTargets() error {
if err != nil {
return err
}
var linuxDetected bool
var windowsDetected, linuxDetected bool
for i := 0; i < len(binFiles); i++ { // detect and generate files are optional
bf := binFiles[len(binFiles)-i-1]
bf := binFiles[len(binFiles)-i-1] // we're iterating backwards b/c os.ReadDir sorts "foo.exe" after "foo" but we want to preferentially detect windows first.
fname := bf.Name()
if !windowsDetected && (fname == "detect.exe" || fname == "detect.bat" || fname == "generate.exe" || fname == "generate.bat") {
d.Targets = append(d.Targets, TargetMetadata{OS: "windows"})
windowsDetected = true
}
if !linuxDetected && (fname == "detect" || fname == "generate") {
d.Targets = append(d.Targets, TargetMetadata{OS: "linux"})
linuxDetected = true

View File

@ -39,5 +39,13 @@ func testExtDescriptor(t *testing.T, when spec.G, it spec.S) {
h.AssertEq(t, descriptor.Targets[0].OS, "")
h.AssertEq(t, descriptor.Targets[0].Arch, "")
})
it("slices, it dices, it even does windows", func() {
path := filepath.Join("testdata", "extension", "by-id", "D", "v1", "extension.toml")
descriptor, err := buildpack.ReadExtDescriptor(path)
h.AssertNil(t, err)
h.AssertEq(t, len(descriptor.Targets), 1)
h.AssertEq(t, descriptor.Targets[0].OS, "windows")
h.AssertEq(t, descriptor.Targets[0].Arch, "")
})
})
}

View File

@ -7,7 +7,6 @@ import (
"os/exec"
"path/filepath"
"github.com/buildpacks/lifecycle/api"
"github.com/buildpacks/lifecycle/internal/extend"
"github.com/buildpacks/lifecycle/launch"
"github.com/buildpacks/lifecycle/log"
@ -26,14 +25,12 @@ type GenerateInputs struct {
OutputDir string // a temp directory provided by the lifecycle to capture extensions output
PlatformDir string
Env BuildEnv
TargetEnv []string
Out, Err io.Writer
Plan Plan
}
type GenerateOutputs struct {
Dockerfiles []DockerfileInfo
Contexts []extend.ContextInfo
MetRequires []string
}
@ -88,7 +85,7 @@ func runGenerateCmd(d ExtDescriptor, extOutputDir, planPath string, inputs Gener
) // #nosec G204
cmd.Dir = inputs.AppDir
cmd.Stdout = inputs.Out
cmd.Stderr = inputs.Out
cmd.Stderr = inputs.Err
var err error
if d.Extension.ClearEnv {
@ -105,9 +102,6 @@ func runGenerateCmd(d ExtDescriptor, extOutputDir, planPath string, inputs Gener
EnvOutputDir+"="+extOutputDir,
EnvPlatformDir+"="+inputs.PlatformDir,
)
if api.MustParse(d.API()).AtLeast("0.10") {
cmd.Env = append(cmd.Env, inputs.TargetEnv...)
}
if err := cmd.Run(); err != nil {
return NewError(err, ErrTypeBuildpack)
@ -120,7 +114,6 @@ func readOutputFilesExt(d ExtDescriptor, extOutputDir string, extPlanIn Plan, lo
var err error
var dfInfo DockerfileInfo
var found bool
var contexts []extend.ContextInfo
// set MetRequires
gr.MetRequires = names(extPlanIn.Entries)
@ -135,22 +128,15 @@ func readOutputFilesExt(d ExtDescriptor, extOutputDir string, extPlanIn Plan, lo
return GenerateOutputs{}, err
} else if found {
gr.Dockerfiles = append(gr.Dockerfiles, dfInfo)
logger.Debugf("Found run.Dockerfile for processing")
}
if dfInfo, found, err = findDockerfileFor(d, extOutputDir, DockerfileKindBuild, logger); err != nil {
return GenerateOutputs{}, err
} else if found {
gr.Dockerfiles = append(gr.Dockerfiles, dfInfo)
logger.Debugf("Found build.Dockerfile for processing")
}
if contexts, err = extend.FindContexts(d.Extension.ID, extOutputDir, logger); err != nil {
return GenerateOutputs{}, err
}
gr.Contexts = contexts
logger.Debugf("Found '%d' Build Contexts for processing", len(gr.Contexts))
logger.Debugf("Found '%d' Dockerfiles for processing", len(gr.Dockerfiles))
return gr, nil
}

View File

@ -5,6 +5,7 @@ import (
"errors"
"os"
"path/filepath"
"runtime"
"strings"
"testing"
@ -23,7 +24,9 @@ import (
)
func TestGenerate(t *testing.T) {
spec.Run(t, "unit-generate", testGenerate, spec.Report(report.Terminal{}))
if runtime.GOOS != "windows" {
spec.Run(t, "unit-generate", testGenerate, spec.Report(report.Terminal{}))
}
}
func testGenerate(t *testing.T, when spec.G, it spec.S) {
@ -248,9 +251,12 @@ func testGenerate(t *testing.T, when spec.G, it spec.S) {
if _, err := executor.Generate(descriptor, inputs, logger); err != nil {
t.Fatalf("Unexpected error:\n%s\n", err)
}
if s := cmp.Diff(h.CleanEndings(stdout.String()), "build out: A@v1\nbuild err: A@v1\n"); s != "" {
if s := cmp.Diff(h.CleanEndings(stdout.String()), "build out: A@v1\n"); s != "" {
t.Fatalf("Unexpected stdout:\n%s\n", s)
}
if s := cmp.Diff(h.CleanEndings(stderr.String()), "build err: A@v1\n"); s != "" {
t.Fatalf("Unexpected stderr:\n%s\n", s)
}
})
it("errors when the command fails", func() {

View File

@ -122,7 +122,7 @@ type Layer struct { // FIXME: need to refactor so api and logger won't be part o
}
type layerDir struct {
// identifier takes the form "buildpack-id:layer-name" and is used for
// identifier takes the form "bp-id:bp-version" and is used for
// sorting layers,
// logging information about a layer, and
// creating the temporary tar file that is the basis of the layer.

View File

@ -1,14 +0,0 @@
api = "0.7"
[buildpack]
id = "B"
name = "Buildpack B"
version = "v1.2"
homepage = "Buildpack B Homepage"
sbom-formats = ["application/vnd.cyclonedx+json"]
[[stacks]]
id = "io.buildpacks.stacks.bionic"
[[stacks]]
id = "io.buildpacks.stacks.jammy"

View File

@ -1,11 +0,0 @@
api = "0.7"
[buildpack]
id = "B"
name = "Buildpack B"
version = "v1.star"
homepage = "Buildpack B Homepage"
sbom-formats = ["application/vnd.cyclonedx+json"]
[[stacks]]
id = "*"

View File

@ -1,14 +0,0 @@
api = "0.12"
[buildpack]
id = "B"
name = "Buildpack B"
version = "v2.2"
homepage = "Buildpack B Homepage"
sbom-formats = ["application/vnd.cyclonedx+json"]
[[stacks]]
id = "io.buildpacks.stacks.bionic"
[[stacks]]
id = "io.buildpacks.stacks.jammy"

View File

@ -1,11 +0,0 @@
api = "0.12"
[buildpack]
id = "B"
name = "Buildpack B"
version = "v2.star"
homepage = "Buildpack B Homepage"
sbom-formats = ["application/vnd.cyclonedx+json"]
[[stacks]]
id = "*"

View File

@ -3,7 +3,7 @@ api = "0.12"
[buildpack]
id = "B"
name = "Buildpack B"
version = "v2"
version = "v1"
homepage = "Buildpack B Homepage"
sbom-formats = ["application/vnd.cyclonedx+json"]

View File

@ -4,6 +4,8 @@ import (
"io"
"os"
"path/filepath"
"runtime"
"strings"
"testing"
"github.com/buildpacks/imgutil"
@ -11,8 +13,6 @@ import (
"github.com/sclevine/spec"
"github.com/sclevine/spec/report"
"github.com/buildpacks/lifecycle/cmd"
"github.com/buildpacks/lifecycle/cache"
h "github.com/buildpacks/lifecycle/testhelpers"
)
@ -37,7 +37,7 @@ func testCachingImage(t *testing.T, when spec.G, it spec.S) {
fakeImage = fakes.NewImage("some-image", "", nil)
tmpDir, err = os.MkdirTemp("", "")
h.AssertNil(t, err)
volumeCache, err = cache.NewVolumeCache(tmpDir, cmd.DefaultLogger)
volumeCache, err = cache.NewVolumeCache(tmpDir)
h.AssertNil(t, err)
subject = cache.NewCachingImage(fakeImage, volumeCache)
layerPath, layerSHA, layerData = h.RandomLayer(t, tmpDir)
@ -68,6 +68,9 @@ func testCachingImage(t *testing.T, when spec.G, it spec.S) {
h.AssertNil(t, err)
defer from.Close()
if runtime.GOOS == "windows" {
layerSHA = strings.TrimPrefix(layerSHA, "sha256:")
}
to, err := os.Create(filepath.Join(tmpDir, "committed", layerSHA+".tar"))
h.AssertNil(t, err)
defer to.Close()

22
cache/common.go vendored
View File

@ -5,25 +5,3 @@ import (
)
var errCacheCommitted = errors.New("cache cannot be modified after commit")
// ReadErr is an error type for filesystem read errors.
type ReadErr struct {
msg string
}
// NewReadErr creates a new ReadErr.
func NewReadErr(msg string) ReadErr {
return ReadErr{msg: msg}
}
// Error returns the error message.
func (e ReadErr) Error() string {
return e.msg
}
// IsReadErr checks if an error is a ReadErr.
func IsReadErr(err error) (bool, *ReadErr) {
var e ReadErr
isReadErr := errors.As(err, &e)
return isReadErr, &e
}

39
cache/image_cache.go vendored
View File

@ -99,44 +99,15 @@ func (c *ImageCache) AddLayerFile(tarPath string, diffID string) error {
return c.newImage.AddLayerWithDiffID(tarPath, diffID)
}
// isLayerNotFound checks if the error is a layer not found error
//
// FIXME: we should not have to rely on trapping ErrUnexpectedEOF.
// If a blob is not present in the registry, we should get imgutil.ErrLayerNotFound,
// but we do not and instead get io.ErrUnexpectedEOF
func isLayerNotFound(err error) bool {
var e imgutil.ErrLayerNotFound
return errors.As(err, &e) || errors.Is(err, io.ErrUnexpectedEOF)
}
func (c *ImageCache) ReuseLayer(diffID string) error {
if c.committed {
return errCacheCommitted
}
err := c.newImage.ReuseLayer(diffID)
if err != nil {
// FIXME: this path is not currently executed.
// If a blob is not present in the registry, we should get imgutil.ErrLayerNotFound.
// We should then skip attempting to reuse the layer.
// However, we do not get imgutil.ErrLayerNotFound when the blob is not present.
if isLayerNotFound(err) {
return NewReadErr(fmt.Sprintf("failed to find cache layer with SHA '%s'", diffID))
}
return fmt.Errorf("failed to reuse cache layer with SHA '%s'", diffID)
}
return nil
return c.newImage.ReuseLayer(diffID)
}
// RetrieveLayer retrieves a layer from the cache
func (c *ImageCache) RetrieveLayer(diffID string) (io.ReadCloser, error) {
closer, err := c.origImage.GetLayer(diffID)
if err != nil {
if isLayerNotFound(err) {
return nil, NewReadErr(fmt.Sprintf("failed to find cache layer with SHA '%s'", diffID))
}
return nil, fmt.Errorf("failed to get cache layer with SHA '%s'", diffID)
}
return closer, nil
return c.origImage.GetLayer(diffID)
}
func (c *ImageCache) Commit() error {
@ -158,9 +129,3 @@ func (c *ImageCache) Commit() error {
return nil
}
// VerifyLayer returns an error if the layer contents do not match the provided sha.
func (c *ImageCache) VerifyLayer(_ string) error {
// we assume the registry is verifying digests for us
return nil
}

View File

@ -146,7 +146,7 @@ func testImageCache(t *testing.T, when spec.G, it spec.S) {
when("layer does not exist", func() {
it("returns an error", func() {
_, err := subject.RetrieveLayer("some_nonexistent_sha")
h.AssertError(t, err, "failed to get cache layer with SHA 'some_nonexistent_sha'")
h.AssertError(t, err, "failed to get layer with sha 'some_nonexistent_sha'")
})
})
})
@ -236,7 +236,7 @@ func testImageCache(t *testing.T, when spec.G, it spec.S) {
h.AssertNil(t, subject.AddLayerFile(testLayerTarPath, testLayerSHA))
_, err := subject.RetrieveLayer(testLayerSHA)
h.AssertError(t, err, fmt.Sprintf("failed to get cache layer with SHA '%s'", testLayerSHA))
h.AssertError(t, err, fmt.Sprintf("failed to get layer with sha '%s'", testLayerSHA))
})
})
})

View File

@ -5,11 +5,11 @@ import (
"github.com/pkg/errors"
)
//go:generate mockgen -package testmockcache -destination ../phase/testmock/cache/image_comparer.go github.com/buildpacks/lifecycle/cache ImageComparer
//go:generate mockgen -package testmockcache -destination ../lifecycle/testmock/cache/image_comparer.go github.com/buildpacks/lifecycle/cache ImageComparer
// ImageComparer provides a way to compare images
type ImageComparer interface {
ImagesEq(origImage imgutil.Image, newImage imgutil.Image) (bool, error)
ImagesEq(orig imgutil.Image, new imgutil.Image) (bool, error)
}
// ImageComparerImpl implements the ImageComparer interface

View File

@ -12,7 +12,6 @@ import (
// ImageDeleter defines the methods available to delete and compare cached images
type ImageDeleter interface {
DeleteOrigImageIfDifferentFromNewImage(origImage, newImage imgutil.Image)
DeleteImage(image imgutil.Image)
}
// ImageDeleterImpl is a component to manage cache image deletion
@ -36,13 +35,13 @@ func (c *ImageDeleterImpl) DeleteOrigImageIfDifferentFromNewImage(origImage, new
}
if !same {
c.DeleteImage(origImage)
c.deleteImage(origImage)
}
}
}
// DeleteImage deletes an image
func (c *ImageDeleterImpl) DeleteImage(image imgutil.Image) {
// deleteImage deletes an image
func (c *ImageDeleterImpl) deleteImage(image imgutil.Image) {
if c.deletionEnabled {
if err := image.Delete(); err != nil {
c.logger.Warnf("Unable to delete cache image: %v", err.Error())

72
cache/volume_cache.go vendored
View File

@ -1,17 +1,15 @@
package cache
import (
"crypto/sha256"
"encoding/json"
"fmt"
"io"
"os"
"path/filepath"
"runtime"
"strings"
"github.com/pkg/errors"
"github.com/buildpacks/lifecycle/log"
"github.com/buildpacks/lifecycle/internal/fsutil"
"github.com/buildpacks/lifecycle/platform"
)
@ -22,11 +20,9 @@ type VolumeCache struct {
backupDir string
stagingDir string
committedDir string
logger log.Logger
}
// NewVolumeCache creates a new VolumeCache
func NewVolumeCache(dir string, logger log.Logger) (*VolumeCache, error) {
func NewVolumeCache(dir string) (*VolumeCache, error) {
if _, err := os.Stat(dir); err != nil {
return nil, err
}
@ -36,7 +32,6 @@ func NewVolumeCache(dir string, logger log.Logger) (*VolumeCache, error) {
backupDir: filepath.Join(dir, "committed-backup"),
stagingDir: filepath.Join(dir, "staging"),
committedDir: filepath.Join(dir, "committed"),
logger: logger,
}
if err := c.setupStagingDir(); err != nil {
@ -138,17 +133,7 @@ func (c *VolumeCache) ReuseLayer(diffID string) error {
if c.committed {
return errCacheCommitted
}
committedPath := diffIDPath(c.committedDir, diffID)
stagingPath := diffIDPath(c.stagingDir, diffID)
if _, err := os.Stat(committedPath); err != nil {
if err = handleFileError(err, diffID); errors.Is(err, ReadErr{}) {
return err
}
return fmt.Errorf("failed to re-use cache layer with SHA '%s': %w", diffID, err)
}
if err := os.Link(committedPath, stagingPath); err != nil && !os.IsExist(err) {
if err := os.Link(diffIDPath(c.committedDir, diffID), diffIDPath(c.stagingDir, diffID)); err != nil && !os.IsExist(err) {
return errors.Wrapf(err, "reusing layer (%s)", diffID)
}
return nil
@ -161,10 +146,7 @@ func (c *VolumeCache) RetrieveLayer(diffID string) (io.ReadCloser, error) {
}
file, err := os.Open(path)
if err != nil {
if err = handleFileError(err, diffID); errors.Is(err, ReadErr{}) {
return nil, err
}
return nil, fmt.Errorf("failed to get cache layer with SHA '%s'", diffID)
return nil, errors.Wrapf(err, "opening layer with SHA '%s'", diffID)
}
return file, nil
}
@ -182,8 +164,8 @@ func (c *VolumeCache) HasLayer(diffID string) (bool, error) {
func (c *VolumeCache) RetrieveLayerFile(diffID string) (string, error) {
path := diffIDPath(c.committedDir, diffID)
if _, err := os.Stat(path); err != nil {
if err = handleFileError(err, diffID); errors.Is(err, ReadErr{}) {
return "", err
if os.IsNotExist(err) {
return "", errors.Wrapf(err, "layer with SHA '%s' not found", diffID)
}
return "", errors.Wrapf(err, "retrieving layer with SHA '%s'", diffID)
}
@ -195,13 +177,13 @@ func (c *VolumeCache) Commit() error {
return errCacheCommitted
}
c.committed = true
if err := os.Rename(c.committedDir, c.backupDir); err != nil {
if err := fsutil.RenameWithWindowsFallback(c.committedDir, c.backupDir); err != nil {
return errors.Wrap(err, "backing up cache")
}
defer os.RemoveAll(c.backupDir)
if err1 := os.Rename(c.stagingDir, c.committedDir); err1 != nil {
if err2 := os.Rename(c.backupDir, c.committedDir); err2 != nil {
if err1 := fsutil.RenameWithWindowsFallback(c.stagingDir, c.committedDir); err1 != nil {
if err2 := fsutil.RenameWithWindowsFallback(c.backupDir, c.committedDir); err2 != nil {
return errors.Wrap(err2, "rolling back cache")
}
return errors.Wrap(err1, "committing cache")
@ -211,6 +193,10 @@ func (c *VolumeCache) Commit() error {
}
func diffIDPath(basePath, diffID string) string {
if runtime.GOOS == "windows" {
// Avoid colons in Windows file paths
diffID = strings.TrimPrefix(diffID, "sha256:")
}
return filepath.Join(basePath, diffID+".tar")
}
@ -220,33 +206,3 @@ func (c *VolumeCache) setupStagingDir() error {
}
return os.MkdirAll(c.stagingDir, 0777)
}
// VerifyLayer returns an error if the layer contents do not match the provided sha.
func (c *VolumeCache) VerifyLayer(diffID string) error {
layerRC, err := c.RetrieveLayer(diffID)
if err != nil {
return err
}
defer func() {
_ = layerRC.Close()
}()
hasher := sha256.New()
if _, err := io.Copy(hasher, layerRC); err != nil {
return errors.Wrap(err, "hashing layer")
}
foundDiffID := fmt.Sprintf("sha256:%x", hasher.Sum(nil))
if diffID != foundDiffID {
return NewReadErr(fmt.Sprintf("expected layer contents to have SHA '%s'; found '%s'", diffID, foundDiffID))
}
return err
}
func handleFileError(err error, diffID string) error {
if os.IsNotExist(err) {
return NewReadErr(fmt.Sprintf("failed to find cache layer with SHA '%s'", diffID))
}
if os.IsPermission(err) {
return NewReadErr(fmt.Sprintf("failed to read cache layer with SHA '%s' due to insufficient permissions", diffID))
}
return err
}

View File

@ -10,9 +10,6 @@ import (
"github.com/sclevine/spec"
"github.com/sclevine/spec/report"
"github.com/buildpacks/lifecycle/cmd"
"github.com/buildpacks/lifecycle/log"
"github.com/buildpacks/lifecycle/buildpack"
"github.com/buildpacks/lifecycle/cache"
"github.com/buildpacks/lifecycle/platform"
@ -31,7 +28,6 @@ func testVolumeCache(t *testing.T, when spec.G, it spec.S) {
backupDir string
stagingDir string
committedDir string
testLogger log.Logger
)
it.Before(func() {
@ -46,7 +42,6 @@ func testVolumeCache(t *testing.T, when spec.G, it spec.S) {
backupDir = filepath.Join(volumeDir, "committed-backup")
stagingDir = filepath.Join(volumeDir, "staging")
committedDir = filepath.Join(volumeDir, "committed")
testLogger = cmd.DefaultLogger
})
it.After(func() {
@ -55,7 +50,7 @@ func testVolumeCache(t *testing.T, when spec.G, it spec.S) {
when("#NewVolumeCache", func() {
it("returns an error when the volume path does not exist", func() {
_, err := cache.NewVolumeCache(filepath.Join(tmpDir, "does_not_exist"), testLogger)
_, err := cache.NewVolumeCache(filepath.Join(tmpDir, "does_not_exist"))
if err == nil {
t.Fatal("expected NewVolumeCache to fail because volume path does not exist")
}
@ -71,7 +66,7 @@ func testVolumeCache(t *testing.T, when spec.G, it spec.S) {
it("clears staging", func() {
var err error
subject, err = cache.NewVolumeCache(volumeDir, testLogger)
subject, err = cache.NewVolumeCache(volumeDir)
h.AssertNil(t, err)
_, err = os.Stat(filepath.Join(stagingDir, "some-layer.tar"))
@ -85,7 +80,7 @@ func testVolumeCache(t *testing.T, when spec.G, it spec.S) {
it("creates staging dir", func() {
var err error
subject, err = cache.NewVolumeCache(volumeDir, testLogger)
subject, err = cache.NewVolumeCache(volumeDir)
h.AssertNil(t, err)
_, err = os.Stat(stagingDir)
@ -97,7 +92,7 @@ func testVolumeCache(t *testing.T, when spec.G, it spec.S) {
it("creates committed dir", func() {
var err error
subject, err = cache.NewVolumeCache(volumeDir, testLogger)
subject, err = cache.NewVolumeCache(volumeDir)
h.AssertNil(t, err)
_, err = os.Stat(committedDir)
@ -114,7 +109,7 @@ func testVolumeCache(t *testing.T, when spec.G, it spec.S) {
it("clears the backup dir", func() {
var err error
subject, err = cache.NewVolumeCache(volumeDir, testLogger)
subject, err = cache.NewVolumeCache(volumeDir)
h.AssertNil(t, err)
_, err = os.Stat(filepath.Join(backupDir, "some-layer.tar"))
@ -129,7 +124,7 @@ func testVolumeCache(t *testing.T, when spec.G, it spec.S) {
it.Before(func() {
var err error
subject, err = cache.NewVolumeCache(volumeDir, testLogger)
subject, err = cache.NewVolumeCache(volumeDir)
h.AssertNil(t, err)
})
@ -211,7 +206,7 @@ func testVolumeCache(t *testing.T, when spec.G, it spec.S) {
when("layer does not exist", func() {
it("returns an error", func() {
_, err := subject.RetrieveLayer("some_nonexistent_sha")
h.AssertError(t, err, "failed to find cache layer with SHA 'some_nonexistent_sha'")
h.AssertError(t, err, "layer with SHA 'some_nonexistent_sha' not found")
})
})
})
@ -235,7 +230,7 @@ func testVolumeCache(t *testing.T, when spec.G, it spec.S) {
when("layer does not exist", func() {
it("returns an error", func() {
_, err := subject.RetrieveLayerFile("some_nonexistent_sha")
h.AssertError(t, err, "failed to find cache layer with SHA 'some_nonexistent_sha'")
h.AssertError(t, err, "layer with SHA 'some_nonexistent_sha' not found")
})
})
})
@ -345,7 +340,7 @@ func testVolumeCache(t *testing.T, when spec.G, it spec.S) {
h.AssertNil(t, subject.AddLayerFile(tarPath, "some_sha"))
_, err := subject.RetrieveLayer("some_sha")
h.AssertError(t, err, "failed to find cache layer with SHA 'some_sha'")
h.AssertError(t, err, "layer with SHA 'some_sha' not found")
})
})
@ -420,7 +415,7 @@ func testVolumeCache(t *testing.T, when spec.G, it spec.S) {
h.AssertNil(t, subject.AddLayer(layerReader, layerSha))
_, err := subject.RetrieveLayer(layerSha)
h.AssertError(t, err, fmt.Sprintf("failed to find cache layer with SHA '%s'", layerSha))
h.AssertError(t, err, fmt.Sprintf("layer with SHA '%s' not found", layerSha))
})
})
@ -512,21 +507,6 @@ func testVolumeCache(t *testing.T, when spec.G, it spec.S) {
h.AssertEq(t, string(bytes), "existing data")
})
})
when("the layer does not exist", func() {
it("fails with a read error", func() {
err := subject.ReuseLayer("some_nonexistent_sha")
isReadErr, _ := cache.IsReadErr(err)
h.AssertEq(t, isReadErr, true)
err = subject.Commit()
h.AssertNil(t, err)
_, err = subject.RetrieveLayer("some_sha")
isReadErr, _ = cache.IsReadErr(err)
h.AssertEq(t, isReadErr, true)
})
})
})
when("attempting to commit more than once", func() {

View File

@ -60,6 +60,6 @@ func Exit(err error) {
}
func ExitWithVersion() {
DefaultLogger.Info(buildVersion())
DefaultLogger.Infof(buildVersion())
os.Exit(0)
}

View File

@ -12,27 +12,29 @@ import (
"github.com/buildpacks/lifecycle/image"
"github.com/buildpacks/lifecycle/phase"
"github.com/buildpacks/lifecycle/platform"
"github.com/buildpacks/lifecycle/platform/files"
"github.com/buildpacks/lifecycle/priv"
)
type analyzeCmd struct {
*platform.Platform
docker client.APIClient // construct if necessary before dropping privileges
keychain authn.Keychain // construct if necessary before dropping privileges
docker client.CommonAPIClient // construct if necessary before dropping privileges
keychain authn.Keychain // construct if necessary before dropping privileges
}
// DefineFlags defines the flags that are considered valid and reads their values (if provided).
func (a *analyzeCmd) DefineFlags() {
if a.PlatformAPI.LessThan("0.12") {
cli.FlagStackPath(&a.StackPath)
}
switch {
case a.PlatformAPI.AtLeast("0.13"):
cli.FlagInsecureRegistries(&a.InsecureRegistries)
fallthrough
case a.PlatformAPI.AtLeast("0.12"):
cli.FlagLayoutDir(&a.LayoutDir)
cli.FlagRunPath(&a.RunPath)
cli.FlagUseLayout(&a.UseLayout)
cli.FlagRunPath(&a.RunPath)
fallthrough
case a.PlatformAPI.AtLeast("0.9"):
cli.FlagLaunchCacheDir(&a.LaunchCacheDir)
@ -43,18 +45,12 @@ func (a *analyzeCmd) DefineFlags() {
cli.FlagCacheImage(&a.CacheImageRef)
cli.FlagGID(&a.GID)
cli.FlagLayersDir(&a.LayersDir)
cli.FlagLogLevel(&a.LogLevel)
cli.FlagNoColor(&a.NoColor)
cli.FlagPreviousImage(&a.PreviousImageRef)
cli.FlagRunImage(&a.RunImageRef)
cli.FlagTags(&a.AdditionalTags)
cli.FlagUID(&a.UID)
cli.FlagUseDaemon(&a.UseDaemon)
}
// deprecated
if a.PlatformAPI.LessThan("0.12") {
cli.FlagStackPath(&a.StackPath)
}
}
// Args validates arguments and flags, and fills in default values.
@ -103,7 +99,7 @@ func (a *analyzeCmd) Exec() error {
a.PlatformAPI,
&cmd.BuildpackAPIVerifier{},
NewCacheHandler(a.keychain),
files.Handler,
phase.Config,
image.NewHandler(a.docker, a.keychain, a.LayoutDir, a.UseLayout, a.InsecureRegistries),
image.NewRegistryHandler(a.keychain, a.InsecureRegistries),
)
@ -115,5 +111,5 @@ func (a *analyzeCmd) Exec() error {
if err != nil {
return cmd.FailErrCode(err, a.CodeFor(platform.AnalyzeError), "analyze")
}
return files.Handler.WriteAnalyzed(a.AnalyzedPath, &analyzedMD, cmd.DefaultLogger)
return phase.Config.WriteAnalyzed(a.AnalyzedPath, &analyzedMD, cmd.DefaultLogger)
}

View File

@ -3,9 +3,12 @@ package main
import (
"errors"
"github.com/BurntSushi/toml"
"github.com/buildpacks/lifecycle/buildpack"
"github.com/buildpacks/lifecycle/cmd"
"github.com/buildpacks/lifecycle/cmd/lifecycle/cli"
"github.com/buildpacks/lifecycle/internal/encoding"
"github.com/buildpacks/lifecycle/launch"
"github.com/buildpacks/lifecycle/phase"
"github.com/buildpacks/lifecycle/platform"
@ -31,8 +34,6 @@ func (b *buildCmd) DefineFlags() {
cli.FlagBuildpacksDir(&b.BuildpacksDir)
cli.FlagGroupPath(&b.GroupPath)
cli.FlagLayersDir(&b.LayersDir)
cli.FlagLogLevel(&b.LogLevel)
cli.FlagNoColor(&b.NoColor)
cli.FlagPlanPath(&b.PlanPath)
cli.FlagPlatformDir(&b.PlatformDir)
}
@ -65,7 +66,7 @@ func (b *buildCmd) Exec() error {
if err = verifyBuildpackApis(group); err != nil {
return err
}
amd, err := files.Handler.ReadAnalyzed(b.AnalyzedPath, cmd.DefaultLogger)
amd, err := files.ReadAnalyzed(b.AnalyzedPath, cmd.DefaultLogger)
if err != nil {
return unwrapErrorFailWithMessage(err, "reading analyzed.toml")
}
@ -92,7 +93,10 @@ func (b *buildCmd) build(group buildpack.Group, plan files.Plan, analyzedMD file
if err != nil {
return b.unwrapBuildFail(err)
}
return files.Handler.WriteBuildMetadata(launch.GetMetadataFilePath(b.LayersDir), md)
if err = encoding.WriteTOML(launch.GetMetadataFilePath(b.LayersDir), md); err != nil {
return cmd.FailErr(err, "write build metadata")
}
return nil
}
func (b *buildCmd) unwrapBuildFail(err error) error {
@ -105,13 +109,14 @@ func (b *buildCmd) unwrapBuildFail(err error) error {
}
func (b *buildCmd) readData() (buildpack.Group, files.Plan, error) {
group, err := files.Handler.ReadGroup(b.GroupPath)
group, err := phase.ReadGroup(b.GroupPath)
if err != nil {
return buildpack.Group{}, files.Plan{}, err
return buildpack.Group{}, files.Plan{}, cmd.FailErr(err, "read buildpack group")
}
plan, err := files.Handler.ReadPlan(b.PlanPath)
if err != nil {
return buildpack.Group{}, files.Plan{}, err
var plan files.Plan
if _, err := toml.DecodeFile(b.PlanPath, &plan); err != nil {
return buildpack.Group{}, files.Plan{}, cmd.FailErr(err, "parse detect plan")
}
return group, plan, nil
}

Some files were not shown because too many files have changed in this diff Show More