Compare commits

..

No commits in common. "main" and "v0.20.5" have entirely different histories.

96 changed files with 1763 additions and 1330 deletions

View File

@ -33,7 +33,7 @@ jobs:
TEST_COVERAGE: 1
run: make test
- name: Upload coverage to Codecov
uses: codecov/codecov-action@v5
uses: codecov/codecov-action@v4
with:
token: ${{ secrets.CODECOV_TOKEN }}
file: ./out/tests/coverage-unit.txt
@ -55,10 +55,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@v5
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
@ -124,6 +176,14 @@ jobs:
with:
name: lifecycle-linux-s390x-sha256
path: out/lifecycle-v*+linux.s390x.tgz.sha256
- uses: actions/upload-artifact@v4
with:
name: lifecycle-windows-x86-64
path: out/lifecycle-v*+windows.x86-64.tgz
- uses: actions/upload-artifact@v4
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:
@ -145,7 +205,7 @@ jobs:
with:
username: ${{ secrets.DOCKER_USERNAME }}
password: ${{ secrets.DOCKER_PASSWORD }}
- uses: actions/download-artifact@v5
- uses: actions/download-artifact@v4
with:
name: tag
- name: Set env
@ -169,11 +229,15 @@ jobs:
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 +252,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:
@ -206,17 +270,17 @@ jobs:
uses: actions/setup-go@v5
with:
go-version-file: 'pack/go.mod'
- uses: actions/download-artifact@v5
- uses: actions/download-artifact@v4
with:
name: version
- uses: actions/download-artifact@v5
- uses: actions/download-artifact@v4
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@v4
with:
name: lifecycle-linux-x86-64
path: pack

View File

@ -86,7 +86,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-ppc64le, 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
@ -82,11 +82,8 @@ jobs:
if (urlList.length === 0) {
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"
if (urlList.length != 12) {
throw "there should be exactly 12 artifacts"
}
return urlList.join(",")
})
@ -191,7 +188,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 +200,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

@ -48,6 +48,9 @@ jobs:
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 }}
@ -61,12 +64,14 @@ jobs:
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"
@ -98,12 +103,14 @@ jobs:
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

@ -55,7 +55,7 @@ jobs:
fi
done
- name: Install dependencies and run all tests on s390x ZVSI
uses: appleboy/ssh-action@v1.2.2
uses: appleboy/ssh-action@v1.1.0
env:
GH_REPOSITORY: ${{ github.server_url }}/${{ github.repository }}
GH_REF: ${{ github.ref }}
@ -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.23.2.linux-s390x.tar.gz
rm -rf /usr/local/go && tar -C /usr/local -xzf go1.23.2.linux-s390x.tar.gz
export PATH=$PATH:/usr/local/go/bin
git clone ${GH_REPOSITORY} lifecycle
cd lifecycle && git checkout ${GH_REF}

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

316
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.23-windowsservercore-1809
SOURCE_COMPILATION_IMAGE?=lifecycle-img
BUILD_CTR?=lifecycle-ctr
DOCKER_CMD?=make test
@ -38,9 +39,13 @@ 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-ppc64le 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-ppc64le: build-linux-ppc64le-lifecycle build-linux-ppc64le-symlinks build-linux-ppc64le-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,6 +57,11 @@ 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-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-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:
@ -62,50 +72,240 @@ build-image-linux-s390x: ARCHIVE_PATH=$(BUILD_DIR)/lifecycle-v$(LIFECYCLE_VERSIO
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-ppc64le-lifecycle: $(BUILD_DIR)/linux-ppc64le/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-linux-s390x-lifecycle: $(BUILD_DIR)/linux-s390x/lifecycle/lifecycle
build-$(1)-$(2)-launcher: $$(BUILD_DIR)/$(1)-$(2)/lifecycle/launcher
$(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_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-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
$(foreach ga,$(GOOS_ARCHS),$(eval $(call build_targets,$(word 1, $(subst /, ,$(ga))),$(word 2, $(subst /, ,$(ga))))))
$(BUILD_DIR)/linux-ppc64le/lifecycle/lifecycle: export GOOS:=linux
$(BUILD_DIR)/linux-ppc64le/lifecycle/lifecycle: export GOARCH:=ppc64le
$(BUILD_DIR)/linux-ppc64le/lifecycle/lifecycle: OUT_DIR?=$(BUILD_DIR)/$(GOOS)-$(GOARCH)/lifecycle
$(BUILD_DIR)/linux-ppc64le/lifecycle/lifecycle: $(GOFILES)
$(BUILD_DIR)/linux-ppc64le/lifecycle/lifecycle:
@echo "> Building lifecycle/lifecycle for $(GOOS)/$(GOARCH)..."
mkdir -p $(OUT_DIR)
$(GOENV) $(GOBUILD) -o $(OUT_DIR)/lifecycle -a ./cmd/lifecycle
generate-sbom: run-syft-linux-amd64 run-syft-linux-arm64 run-syft-linux-ppc64le run-syft-linux-s390x
$(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
build-linux-amd64-launcher: $(BUILD_DIR)/linux-amd64/lifecycle/launcher
$(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-ppc64le-launcher: $(BUILD_DIR)/linux-ppc64le/lifecycle/launcher
$(BUILD_DIR)/linux-ppc64le/lifecycle/launcher: export GOOS:=linux
$(BUILD_DIR)/linux-ppc64le/lifecycle/launcher: export GOARCH:=ppc64le
$(BUILD_DIR)/linux-ppc64le/lifecycle/launcher: OUT_DIR?=$(BUILD_DIR)/$(GOOS)-$(GOARCH)/lifecycle
$(BUILD_DIR)/linux-ppc64le/lifecycle/launcher: $(GOFILES)
$(BUILD_DIR)/linux-ppc64le/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-ppc64le-symlinks: export GOOS:=linux
build-linux-ppc64le-symlinks: export GOARCH:=ppc64le
build-linux-ppc64le-symlinks: OUT_DIR?=$(BUILD_DIR)/$(GOOS)-$(GOARCH)/lifecycle
build-linux-ppc64le-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-ppc64le 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
@ -143,26 +343,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.61.0
lint: install-golangci-lint
@echo "> Linting code..."
@ -205,7 +400,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-ppc64le package-linux-s390x
package-linux-amd64: GOOS:=linux
package-linux-amd64: GOARCH:=amd64
@ -242,3 +437,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

@ -5,6 +5,7 @@ import (
"os"
"os/exec"
"path/filepath"
"runtime"
"strings"
"testing"
@ -127,6 +128,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 +202,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,

View File

@ -6,6 +6,7 @@ import (
"os"
"os/exec"
"path/filepath"
"runtime"
"testing"
"github.com/sclevine/spec"
@ -24,6 +25,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)

View File

@ -6,6 +6,7 @@ import (
"os"
"os/exec"
"path/filepath"
"runtime"
"testing"
"time"
@ -29,6 +30,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)

View File

@ -8,6 +8,7 @@ import (
"os"
"os/exec"
"path/filepath"
"runtime"
"testing"
"github.com/sclevine/spec"
@ -28,6 +29,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)

View File

@ -12,6 +12,7 @@ import (
"os"
"os/exec"
"path/filepath"
"runtime"
"strings"
"testing"
"time"
@ -44,6 +45,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)
@ -64,14 +67,13 @@ 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
it.After(func() {
_, _, _ = h.RunE(exec.Command("docker", "rmi", exportedImageName)) // #nosec G204
})
it("app is created", func() {
exportFlags := []string{"-daemon", "-log-level", "debug"}
exportArgs := append([]string{ctrPath(exporterPath)}, exportFlags...)
@ -230,12 +232,6 @@ 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...)
@ -568,11 +564,10 @@ func testExporterFunc(platformAPI string) func(t *testing.T, when spec.G, it spe
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() {

View File

@ -7,6 +7,7 @@ import (
"os"
"os/exec"
"path/filepath"
"runtime"
"testing"
"github.com/buildpacks/imgutil/layout/sparse"
@ -44,6 +45,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)

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

@ -17,8 +17,6 @@ import (
"testing"
"time"
"github.com/docker/docker/api/types/image"
ih "github.com/buildpacks/imgutil/testhelpers"
"github.com/google/go-containerregistry/pkg/authn"
"github.com/google/go-containerregistry/pkg/registry"
@ -428,30 +426,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)

View File

@ -6,6 +6,7 @@ import (
"os"
"os/exec"
"path/filepath"
"runtime"
"testing"
"github.com/google/go-containerregistry/pkg/name"
@ -31,6 +32,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)

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,4 +1,4 @@
FROM golang:1.24.6 as builder
FROM golang:1.23 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.23-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

@ -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

@ -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

@ -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

@ -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

@ -87,8 +87,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

@ -252,5 +252,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"
@ -132,7 +133,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 {
@ -201,7 +202,7 @@ func eachLayer(bpLayersDir string, fn func(layerPath string) error) error {
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
}
}

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() {

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

@ -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

@ -88,7 +88,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 {

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

@ -4,6 +4,8 @@ import (
"io"
"os"
"path/filepath"
"runtime"
"strings"
"testing"
"github.com/buildpacks/imgutil"
@ -68,6 +70,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()

View File

@ -9,7 +9,7 @@ import (
// 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

12
cache/volume_cache.go vendored
View File

@ -7,6 +7,8 @@ import (
"io"
"os"
"path/filepath"
"runtime"
"strings"
"github.com/pkg/errors"
@ -195,13 +197,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 +213,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")
}

View File

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

View File

@ -19,8 +19,8 @@ import (
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).

View File

@ -61,9 +61,6 @@ func Run(c Command, withPhaseName string, asSubcommand bool) {
// We print a warning here, so we should disable color if needed and set the log level before exercising this logic.
for _, arg := range flagSet.Args() {
if len(arg) == 0 {
continue
}
if arg[0:1] == "-" {
cmd.DefaultLogger.Warnf("Warning: unconsumed flag-like positional arg: \n\t%s\n\t This will not be interpreted as a flag.\n\t Did you mean to put this before the first positional argument?", arg)
}

View File

@ -23,8 +23,8 @@ import (
type createCmd 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).

View File

@ -196,7 +196,7 @@ func (r *rebaseCmd) setAppImage() error {
}
// we find the best mirror for the run image as this point
r.RunImageRef, err = platform.BestRunImageMirrorFor(registry, runImage, r.AccessChecker())
r.RunImageRef, err = platform.BestRunImageMirrorFor(registry, runImage, r.LifecycleInputs.AccessChecker())
if err != nil {
return err
}

View File

@ -31,8 +31,8 @@ const kanikoDir = "/kaniko"
type restoreCmd 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).

3
env/build.go vendored
View File

@ -1,6 +1,7 @@
package env
import (
"runtime"
"strings"
)
@ -17,7 +18,7 @@ var BuildEnvIncludelist = []string{
"no_proxy",
}
var ignoreEnvVarCase = false
var ignoreEnvVarCase = runtime.GOOS == "windows"
// NewBuildEnv returns a build-time Env from the given environment.
//

31
env/build_test.go vendored
View File

@ -1,6 +1,7 @@
package env_test
import (
"runtime"
"sort"
"testing"
@ -10,6 +11,7 @@ import (
"github.com/sclevine/spec/report"
"github.com/buildpacks/lifecycle/env"
h "github.com/buildpacks/lifecycle/testhelpers"
)
func TestBuildEnv(t *testing.T) {
@ -62,9 +64,15 @@ func testBuildEnv(t *testing.T, when spec.G, it spec.S) {
"NO_PROXY=some-no-proxy",
"PATH=some-path",
"PKG_CONFIG_PATH=some-pkg-config-path",
"http_proxy=some-http-proxy",
"https_proxy=some-https-proxy",
"no_proxy=some-no-proxy",
}
// Environment variables in Windows are case insensitive, and are added by the lifecycle in uppercase.
if runtime.GOOS != "windows" {
expectedVars = append(
expectedVars,
"http_proxy=some-http-proxy",
"https_proxy=some-https-proxy",
"no_proxy=some-no-proxy",
)
}
if s := cmp.Diff(out, expectedVars); s != "" {
t.Fatalf("Unexpected env\n%s\n", s)
@ -88,5 +96,22 @@ func testBuildEnv(t *testing.T, when spec.G, it spec.S) {
t.Fatalf("Unexpected root dir map\n%s\n", s)
}
})
when("building in Windows", func() {
it.Before(func() {
if runtime.GOOS != "windows" {
t.Skip("This test only applies to Windows builds")
}
})
it("ignores case when initializing", func() {
benv := env.NewBuildEnv([]string{
"Path=some-path",
})
out := benv.List()
h.AssertEq(t, len(out), 1)
h.AssertEq(t, out[0], "PATH=some-path")
})
})
})
}

3
env/env_test.go vendored
View File

@ -4,6 +4,7 @@ import (
"fmt"
"os"
"path/filepath"
"runtime"
"sort"
"strings"
"testing"
@ -41,7 +42,7 @@ func testEnv(t *testing.T, when spec.G, it spec.S) {
"LIBRARY_PATH",
},
},
Vars: env.NewVars(map[string]string{}, false),
Vars: env.NewVars(map[string]string{}, runtime.GOOS == "windows"),
}
})

19
env/launch_test.go vendored
View File

@ -2,6 +2,7 @@ package env_test
import (
"os"
"runtime"
"strings"
"testing"
@ -10,6 +11,7 @@ import (
"github.com/sclevine/spec/report"
"github.com/buildpacks/lifecycle/env"
h "github.com/buildpacks/lifecycle/testhelpers"
)
func TestLaunchEnv(t *testing.T) {
@ -67,5 +69,22 @@ func testLaunchEnv(t *testing.T, when spec.G, it spec.S) {
t.Fatalf("Unexpected root dir map\n%s\n", s)
}
})
when("launching in Windows", func() {
it.Before(func() {
if runtime.GOOS != "windows" {
t.Skip("This test only applies to Windows launches")
}
})
it("ignores case when initializing", func() {
benv := env.NewLaunchEnv([]string{
"Path=some-path",
}, "", "")
out := benv.List()
h.AssertEq(t, len(out), 1)
h.AssertEq(t, out[0], "PATH=some-path")
})
})
})
}

379
go.mod
View File

@ -1,326 +1,139 @@
module github.com/buildpacks/lifecycle
require (
github.com/BurntSushi/toml v1.5.0
github.com/BurntSushi/toml v1.4.0
github.com/GoogleContainerTools/kaniko v1.23.2
github.com/apex/log v1.9.0
github.com/awslabs/amazon-ecr-credential-helper/ecr-login v0.10.1
github.com/buildpacks/imgutil v0.0.0-20250814164739-4b1c8875ba7e
github.com/chainguard-dev/kaniko v1.25.1
github.com/awslabs/amazon-ecr-credential-helper/ecr-login v0.0.0-20240419161514-af205d85bb44
github.com/buildpacks/imgutil v0.0.0-20240605145725-186f89b2d168
github.com/chrismellard/docker-credential-acr-env v0.0.0-20230304212654-82a0ddb27589
github.com/containerd/containerd v1.7.28
github.com/docker/docker v28.3.3+incompatible
github.com/containerd/containerd v1.7.23
github.com/docker/docker v27.0.3+incompatible
github.com/golang/mock v1.6.0
github.com/google/go-cmp v0.7.0
github.com/google/go-containerregistry v0.20.6
github.com/google/go-cmp v0.6.0
github.com/google/go-containerregistry v0.20.2
github.com/google/uuid v1.6.0
github.com/heroku/color v0.0.6
github.com/moby/buildkit v0.23.2
github.com/moby/buildkit v0.14.1
github.com/pkg/errors v0.9.1
github.com/sclevine/spec v1.4.0
golang.org/x/sync v0.16.0
golang.org/x/sys v0.35.0
golang.org/x/sync v0.8.0
golang.org/x/sys v0.26.0
)
require (
4d63.com/gocheckcompilerdirectives v1.3.0 // indirect
4d63.com/gochecknoglobals v0.2.2 // indirect
cloud.google.com/go/compute/metadata v0.7.0 // indirect
github.com/4meepo/tagalign v1.4.2 // indirect
github.com/Abirdcfly/dupword v0.1.3 // indirect
github.com/Antonboom/errname v1.1.0 // indirect
github.com/Antonboom/nilnil v1.1.0 // indirect
github.com/Antonboom/testifylint v1.6.1 // indirect
cloud.google.com/go/compute/metadata v0.3.0 // indirect
github.com/AdaLogics/go-fuzz-headers v0.0.0-20230811130428-ced1acdcaa24 // indirect
github.com/AdamKorcz/go-118-fuzz-build v0.0.0-20230306123547-8075edf89bb0 // indirect
github.com/Azure/azure-sdk-for-go v68.0.0+incompatible // indirect
github.com/Azure/go-ansiterm v0.0.0-20250102033503-faa5f7b0171c // indirect
github.com/Azure/go-ansiterm v0.0.0-20230124172434-306776ec8161 // indirect
github.com/Azure/go-autorest v14.2.0+incompatible // indirect
github.com/Azure/go-autorest/autorest v0.11.30 // indirect
github.com/Azure/go-autorest/autorest/adal v0.9.24 // indirect
github.com/Azure/go-autorest/autorest/azure/auth v0.5.13 // indirect
github.com/Azure/go-autorest/autorest/azure/cli v0.4.7 // indirect
github.com/Azure/go-autorest/autorest/date v0.3.1 // indirect
github.com/Azure/go-autorest/logger v0.2.2 // indirect
github.com/Azure/go-autorest/tracing v0.6.1 // indirect
github.com/Crocmagnon/fatcontext v0.7.2 // indirect
github.com/Djarvur/go-err113 v0.0.0-20210108212216-aea10b59be24 // indirect
github.com/GaijinEntertainment/go-exhaustruct/v3 v3.3.1 // indirect
github.com/Masterminds/semver/v3 v3.3.1 // indirect
github.com/Azure/go-autorest/autorest v0.11.29 // indirect
github.com/Azure/go-autorest/autorest/adal v0.9.23 // indirect
github.com/Azure/go-autorest/autorest/azure/auth v0.5.12 // indirect
github.com/Azure/go-autorest/autorest/azure/cli v0.4.6 // indirect
github.com/Azure/go-autorest/autorest/date v0.3.0 // indirect
github.com/Azure/go-autorest/logger v0.2.1 // indirect
github.com/Azure/go-autorest/tracing v0.6.0 // indirect
github.com/Microsoft/go-winio v0.6.2 // indirect
github.com/Microsoft/hcsshim v0.13.0 // indirect
github.com/OpenPeeDeeP/depguard/v2 v2.2.1 // indirect
github.com/Microsoft/hcsshim v0.11.7 // indirect
github.com/agext/levenshtein v1.2.3 // indirect
github.com/alecthomas/chroma/v2 v2.16.0 // indirect
github.com/alecthomas/go-check-sumtype v0.3.1 // indirect
github.com/alexkohler/nakedret/v2 v2.0.6 // indirect
github.com/alexkohler/prealloc v1.0.0 // indirect
github.com/alingse/asasalint v0.0.11 // indirect
github.com/alingse/nilnesserr v0.2.0 // indirect
github.com/ashanbrown/forbidigo v1.6.0 // indirect
github.com/ashanbrown/makezero v1.2.0 // indirect
github.com/aws/aws-sdk-go-v2 v1.37.0 // indirect
github.com/aws/aws-sdk-go-v2/config v1.30.1 // indirect
github.com/aws/aws-sdk-go-v2/credentials v1.18.1 // indirect
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.18.0 // indirect
github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.0 // indirect
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.0 // indirect
github.com/aws/aws-sdk-go-v2/internal/ini v1.8.3 // indirect
github.com/aws/aws-sdk-go-v2/service/ecr v1.45.1 // indirect
github.com/aws/aws-sdk-go-v2/service/ecrpublic v1.33.2 // indirect
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.13.0 // indirect
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.0 // indirect
github.com/aws/aws-sdk-go-v2/service/sso v1.26.0 // indirect
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.31.0 // indirect
github.com/aws/aws-sdk-go-v2/service/sts v1.35.0 // indirect
github.com/aws/smithy-go v1.22.5 // indirect
github.com/aymanbagabas/go-osc52/v2 v2.0.1 // indirect
github.com/aws/aws-sdk-go-v2 v1.30.1 // indirect
github.com/aws/aws-sdk-go-v2/config v1.27.24 // indirect
github.com/aws/aws-sdk-go-v2/credentials v1.17.24 // indirect
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.9 // indirect
github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.13 // indirect
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.13 // indirect
github.com/aws/aws-sdk-go-v2/internal/ini v1.8.0 // indirect
github.com/aws/aws-sdk-go-v2/service/ecr v1.27.4 // indirect
github.com/aws/aws-sdk-go-v2/service/ecrpublic v1.23.4 // indirect
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.11.3 // indirect
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.11.15 // indirect
github.com/aws/aws-sdk-go-v2/service/sso v1.22.1 // indirect
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.26.2 // indirect
github.com/aws/aws-sdk-go-v2/service/sts v1.30.1 // indirect
github.com/aws/smithy-go v1.20.3 // indirect
github.com/beorn7/perks v1.0.1 // indirect
github.com/bkielbasa/cyclop v1.2.3 // indirect
github.com/blizzy78/varnamelen v0.8.0 // indirect
github.com/bombsimon/wsl/v4 v4.7.0 // indirect
github.com/breml/bidichk v0.3.3 // indirect
github.com/breml/errchkjson v0.4.1 // indirect
github.com/butuzov/ireturn v0.4.0 // indirect
github.com/butuzov/mirror v1.3.0 // indirect
github.com/catenacyber/perfsprint v0.9.1 // indirect
github.com/ccojocar/zxcvbn-go v1.0.2 // indirect
github.com/cespare/xxhash/v2 v2.3.0 // indirect
github.com/charithe/durationcheck v0.0.10 // indirect
github.com/charmbracelet/colorprofile v0.2.3-0.20250311203215-f60798e515dc // indirect
github.com/charmbracelet/lipgloss v1.1.0 // indirect
github.com/charmbracelet/x/ansi v0.8.0 // indirect
github.com/charmbracelet/x/cellbuf v0.0.13-0.20250311204145-2c3ea96c31dd // indirect
github.com/charmbracelet/x/term v0.2.1 // indirect
github.com/chavacava/garif v0.1.0 // indirect
github.com/ckaznocha/intrange v0.3.1 // indirect
github.com/containerd/cgroups/v3 v3.0.5 // indirect
github.com/containerd/containerd/api v1.9.0 // indirect
github.com/containerd/containerd/v2 v2.1.3 // indirect
github.com/containerd/continuity v0.4.5 // indirect
github.com/containerd/errdefs v1.0.0 // indirect
github.com/containerd/errdefs/pkg v0.3.0 // indirect
github.com/cespare/xxhash/v2 v2.2.0 // indirect
github.com/containerd/cgroups v1.1.0 // indirect
github.com/containerd/containerd/api v1.7.19 // indirect
github.com/containerd/continuity v0.4.3 // indirect
github.com/containerd/errdefs v0.3.0 // indirect
github.com/containerd/fifo v1.1.0 // indirect
github.com/containerd/log v0.1.0 // indirect
github.com/containerd/platforms v1.0.0-rc.1 // indirect
github.com/containerd/stargz-snapshotter/estargz v0.16.3 // indirect
github.com/containerd/ttrpc v1.2.7 // indirect
github.com/containerd/typeurl/v2 v2.2.3 // indirect
github.com/curioswitch/go-reassign v0.3.0 // indirect
github.com/daixiang0/gci v0.13.6 // indirect
github.com/dave/dst v0.27.3 // indirect
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect
github.com/denis-tingaikin/go-header v0.5.0 // indirect
github.com/containerd/platforms v0.2.1 // indirect
github.com/containerd/stargz-snapshotter/estargz v0.15.1 // indirect
github.com/containerd/ttrpc v1.2.5 // indirect
github.com/containerd/typeurl/v2 v2.1.1 // indirect
github.com/dimchansky/utfbom v1.1.1 // indirect
github.com/distribution/reference v0.6.0 // indirect
github.com/dlclark/regexp2 v1.11.5 // indirect
github.com/docker/cli v28.2.2+incompatible // indirect
github.com/docker/cli v27.1.1+incompatible // indirect
github.com/docker/distribution v2.8.3+incompatible // indirect
github.com/docker/docker-credential-helpers v0.9.3 // indirect
github.com/docker/docker-credential-helpers v0.8.1 // indirect
github.com/docker/go-connections v0.5.0 // indirect
github.com/docker/go-events v0.0.0-20190806004212-e31b211e4f1c // indirect
github.com/docker/go-metrics v0.0.1 // indirect
github.com/docker/go-units v0.5.0 // indirect
github.com/docker/libtrust v0.0.0-20160708172513-aabc10ec26b7 // indirect
github.com/ePirat/docker-credential-gitlabci v1.0.0 // indirect
github.com/ettle/strcase v0.2.0 // indirect
github.com/fatih/color v1.18.0 // indirect
github.com/fatih/structtag v1.2.0 // indirect
github.com/felixge/httpsnoop v1.0.4 // indirect
github.com/firefart/nonamedreturns v1.0.6 // indirect
github.com/fsnotify/fsnotify v1.9.0 // indirect
github.com/fzipp/gocyclo v0.6.0 // indirect
github.com/ghostiam/protogetter v0.3.15 // indirect
github.com/go-critic/go-critic v0.13.0 // indirect
github.com/go-logr/logr v1.4.3 // indirect
github.com/go-logr/logr v1.4.1 // indirect
github.com/go-logr/stdr v1.2.2 // indirect
github.com/go-toolsmith/astcast v1.1.0 // indirect
github.com/go-toolsmith/astcopy v1.1.0 // indirect
github.com/go-toolsmith/astequal v1.2.0 // indirect
github.com/go-toolsmith/astfmt v1.1.0 // indirect
github.com/go-toolsmith/astp v1.1.0 // indirect
github.com/go-toolsmith/strparse v1.1.0 // indirect
github.com/go-toolsmith/typep v1.1.0 // indirect
github.com/go-viper/mapstructure/v2 v2.4.0 // indirect
github.com/go-xmlfmt/xmlfmt v1.1.3 // indirect
github.com/gobwas/glob v0.2.3 // indirect
github.com/gofrs/flock v0.12.1 // indirect
github.com/gogo/protobuf v1.3.2 // indirect
github.com/golang-jwt/jwt/v4 v4.5.2 // indirect
github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8 // indirect
github.com/golangci/dupl v0.0.0-20250308024227-f665c8d69b32 // indirect
github.com/golangci/go-printf-func-name v0.1.0 // indirect
github.com/golangci/gofmt v0.0.0-20250106114630-d62b90e6713d // indirect
github.com/golangci/golangci-lint/v2 v2.1.2 // indirect
github.com/golangci/golines v0.0.0-20250217134842-442fd0091d95 // indirect
github.com/golangci/misspell v0.6.0 // indirect
github.com/golangci/plugin-module-register v0.1.1 // indirect
github.com/golangci/revgrep v0.8.0 // indirect
github.com/golangci/unconvert v0.0.0-20250410112200-a129a6e6413e // indirect
github.com/gordonklaus/ineffassign v0.1.0 // indirect
github.com/gorilla/mux v1.8.1 // indirect
github.com/gostaticanalysis/analysisutil v0.7.1 // indirect
github.com/gostaticanalysis/comment v1.5.0 // indirect
github.com/gostaticanalysis/forcetypeassert v0.2.0 // indirect
github.com/gostaticanalysis/nilerr v0.1.1 // indirect
github.com/hashicorp/go-immutable-radix/v2 v2.1.0 // indirect
github.com/hashicorp/go-version v1.7.0 // indirect
github.com/hashicorp/golang-lru/v2 v2.0.7 // indirect
github.com/hashicorp/hcl v1.0.0 // indirect
github.com/hexops/gotextdiff v1.0.3 // indirect
github.com/inconshreveable/mousetrap v1.1.0 // indirect
github.com/jgautheron/goconst v1.8.1 // indirect
github.com/jingyugao/rowserrcheck v1.1.1 // indirect
github.com/jjti/go-spancheck v0.6.4 // indirect
github.com/julz/importas v0.2.0 // indirect
github.com/karamaru-alpha/copyloopvar v1.2.1 // indirect
github.com/golang-jwt/jwt/v4 v4.5.0 // indirect
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect
github.com/golang/protobuf v1.5.4 // indirect
github.com/gorilla/mux v1.8.0 // indirect
github.com/hashicorp/go-immutable-radix v1.3.1 // indirect
github.com/hashicorp/go-memdb v1.3.4 // indirect
github.com/hashicorp/golang-lru v1.0.2 // indirect
github.com/jmespath/go-jmespath v0.4.0 // indirect
github.com/karrick/godirwalk v1.17.0 // indirect
github.com/kisielk/errcheck v1.9.0 // indirect
github.com/kkHAIKE/contextcheck v1.1.6 // indirect
github.com/klauspost/compress v1.18.0 // indirect
github.com/kulti/thelper v0.6.3 // indirect
github.com/kunwardeep/paralleltest v1.0.14 // indirect
github.com/lasiar/canonicalheader v1.1.2 // indirect
github.com/ldez/exptostd v0.4.3 // indirect
github.com/ldez/gomoddirectives v0.6.1 // indirect
github.com/ldez/grignotin v0.9.0 // indirect
github.com/ldez/tagliatelle v0.7.1 // indirect
github.com/ldez/usetesting v0.4.3 // indirect
github.com/leonklingele/grouper v1.1.2 // indirect
github.com/lucasb-eyer/go-colorful v1.2.0 // indirect
github.com/macabu/inamedparam v0.2.0 // indirect
github.com/magiconair/properties v1.8.7 // indirect
github.com/manuelarte/funcorder v0.2.1 // indirect
github.com/maratori/testableexamples v1.0.0 // indirect
github.com/maratori/testpackage v1.1.1 // indirect
github.com/matoous/godox v1.1.0 // indirect
github.com/mattn/go-colorable v0.1.14 // indirect
github.com/klauspost/compress v1.17.4 // indirect
github.com/mattn/go-colorable v0.1.13 // indirect
github.com/mattn/go-isatty v0.0.20 // indirect
github.com/mattn/go-runewidth v0.0.16 // indirect
github.com/mgechev/revive v1.9.0 // indirect
github.com/minio/highwayhash v1.0.3 // indirect
github.com/minio/highwayhash v1.0.2 // indirect
github.com/mitchellh/go-homedir v1.1.0 // indirect
github.com/mitchellh/mapstructure v1.5.0 // indirect
github.com/moby/docker-image-spec v1.3.1 // indirect
github.com/moby/go-archive v0.1.0 // indirect
github.com/moby/locker v1.0.1 // indirect
github.com/moby/patternmatcher v0.6.0 // indirect
github.com/moby/sys/atomicwriter v0.1.0 // indirect
github.com/moby/sys/mount v0.3.4 // indirect
github.com/moby/sys/mountinfo v0.7.2 // indirect
github.com/moby/sys/reexec v0.1.0 // indirect
github.com/moby/sys/sequential v0.6.0 // indirect
github.com/moby/sys/signal v0.7.1 // indirect
github.com/moby/sys/symlink v0.3.0 // indirect
github.com/moby/sys/user v0.4.0 // indirect
github.com/moby/swarmkit/v2 v2.0.0-20230911190601-f082dd7a0cee // indirect
github.com/moby/sys/mount v0.3.3 // indirect
github.com/moby/sys/mountinfo v0.7.1 // indirect
github.com/moby/sys/sequential v0.5.0 // indirect
github.com/moby/sys/signal v0.7.0 // indirect
github.com/moby/sys/symlink v0.2.0 // indirect
github.com/moby/sys/user v0.3.0 // indirect
github.com/moby/sys/userns v0.1.0 // indirect
github.com/moby/term v0.5.2 // indirect
github.com/moricho/tparallel v0.3.2 // indirect
github.com/moby/term v0.5.0 // indirect
github.com/morikuni/aec v1.0.0 // indirect
github.com/muesli/termenv v0.16.0 // indirect
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect
github.com/nakabonne/nestif v0.3.1 // indirect
github.com/nishanths/exhaustive v0.12.0 // indirect
github.com/nishanths/predeclared v0.2.2 // indirect
github.com/nunnatsa/ginkgolinter v0.19.1 // indirect
github.com/olekukonko/tablewriter v0.0.5 // indirect
github.com/opencontainers/go-digest v1.0.0 // indirect
github.com/opencontainers/image-spec v1.1.1 // indirect
github.com/opencontainers/runtime-spec v1.2.1 // indirect
github.com/otiai10/copy v1.14.1 // indirect
github.com/otiai10/mint v1.6.3 // indirect
github.com/pelletier/go-toml v1.9.5 // indirect
github.com/pelletier/go-toml/v2 v2.2.4 // indirect
github.com/planetscale/vtprotobuf v0.6.1-0.20240319094008-0393e58bdf10 // indirect
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect
github.com/polyfloyd/go-errorlint v1.8.0 // indirect
github.com/prometheus/client_golang v1.22.0 // indirect
github.com/prometheus/client_model v0.6.2 // indirect
github.com/prometheus/common v0.64.0 // indirect
github.com/prometheus/procfs v0.16.1 // indirect
github.com/quasilyte/go-ruleguard v0.4.4 // indirect
github.com/quasilyte/go-ruleguard/dsl v0.3.22 // indirect
github.com/quasilyte/gogrep v0.5.0 // indirect
github.com/quasilyte/regex/syntax v0.0.0-20210819130434-b3f0c404a727 // indirect
github.com/quasilyte/stdinfo v0.0.0-20220114132959-f7386bf02567 // indirect
github.com/raeperd/recvcheck v0.2.0 // indirect
github.com/rivo/uniseg v0.4.7 // indirect
github.com/rogpeppe/go-internal v1.14.1 // indirect
github.com/ryancurrah/gomodguard v1.4.1 // indirect
github.com/ryanrolds/sqlclosecheck v0.5.1 // indirect
github.com/sagikazarmark/locafero v0.4.0 // indirect
github.com/sagikazarmark/slog-shim v0.1.0 // indirect
github.com/sanposhiho/wastedassign/v2 v2.1.0 // indirect
github.com/santhosh-tekuri/jsonschema/v6 v6.0.1 // indirect
github.com/sashamelentyev/interfacebloat v1.1.0 // indirect
github.com/sashamelentyev/usestdlibvars v1.28.0 // indirect
github.com/sclevine/yj v0.0.0-20210612025309-737bdf40a5d1 // indirect
github.com/securego/gosec/v2 v2.22.3 // indirect
github.com/opencontainers/image-spec v1.1.0 // indirect
github.com/opencontainers/runtime-spec v1.1.0 // indirect
github.com/opencontainers/selinux v1.11.0 // indirect
github.com/otiai10/copy v1.14.0 // indirect
github.com/prometheus/client_golang v1.19.0 // indirect
github.com/prometheus/client_model v0.5.0 // indirect
github.com/prometheus/common v0.48.0 // indirect
github.com/prometheus/procfs v0.12.0 // indirect
github.com/sirupsen/logrus v1.9.3 // indirect
github.com/sivchari/containedctx v1.0.3 // indirect
github.com/sonatard/noctx v0.1.0 // indirect
github.com/sourcegraph/conc v0.3.0 // indirect
github.com/sourcegraph/go-diff v0.7.0 // indirect
github.com/spf13/afero v1.14.0 // indirect
github.com/spf13/cast v1.6.0 // indirect
github.com/spf13/cobra v1.9.1 // indirect
github.com/spf13/pflag v1.0.7 // indirect
github.com/spf13/viper v1.18.2 // indirect
github.com/ssgreg/nlreturn/v2 v2.2.1 // indirect
github.com/stbenjam/no-sprintf-host-port v0.2.0 // indirect
github.com/stretchr/objx v0.5.2 // indirect
github.com/stretchr/testify v1.10.0 // indirect
github.com/subosito/gotenv v1.6.0 // indirect
github.com/tdakkota/asciicheck v0.4.1 // indirect
github.com/tetafro/godot v1.5.0 // indirect
github.com/timakin/bodyclose v0.0.0-20241222091800-1db5c5ca4d67 // indirect
github.com/timonwong/loggercheck v0.11.0 // indirect
github.com/tomarrell/wrapcheck/v2 v2.11.0 // indirect
github.com/tommy-muehle/go-mnd/v2 v2.5.1 // indirect
github.com/tonistiigi/go-csvvalue v0.0.0-20240814133006-030d3b2625d0 // indirect
github.com/ultraware/funlen v0.2.0 // indirect
github.com/ultraware/whitespace v0.2.0 // indirect
github.com/uudashr/gocognit v1.2.0 // indirect
github.com/uudashr/iface v1.3.1 // indirect
github.com/vbatts/tar-split v0.12.1 // indirect
github.com/xen0n/gosmopolitan v1.3.0 // indirect
github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e // indirect
github.com/yagipy/maintidx v1.0.0 // indirect
github.com/yeya24/promlinter v0.3.0 // indirect
github.com/ykadowak/zerologlint v0.1.5 // indirect
gitlab.com/bosi/decorder v0.4.2 // indirect
go-simpler.org/musttag v0.13.0 // indirect
go-simpler.org/sloglint v0.11.0 // indirect
github.com/spf13/afero v1.11.0 // indirect
github.com/vbatts/tar-split v0.11.5 // indirect
go.etcd.io/etcd/raft/v3 v3.5.9 // indirect
go.opencensus.io v0.24.0 // indirect
go.opentelemetry.io/auto/sdk v1.1.0 // indirect
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.61.0 // indirect
go.opentelemetry.io/otel v1.36.0 // indirect
go.opentelemetry.io/otel/metric v1.36.0 // indirect
go.opentelemetry.io/otel/trace v1.36.0 // indirect
go.uber.org/atomic v1.9.0 // indirect
go.uber.org/automaxprocs v1.6.0 // indirect
go.uber.org/multierr v1.11.0 // indirect
go.uber.org/zap v1.24.0 // indirect
golang.org/x/crypto v0.40.0 // indirect
golang.org/x/exp v0.0.0-20250408133849-7e4ce0ab07d0 // indirect
golang.org/x/exp/typeparams v0.0.0-20250210185358-939b2ce775ac // indirect
golang.org/x/mod v0.26.0 // indirect
golang.org/x/oauth2 v0.30.0 // indirect
golang.org/x/text v0.27.0 // indirect
golang.org/x/time v0.12.0 // indirect
golang.org/x/tools v0.34.0 // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20250721164621-a45f3dfb1074 // indirect
google.golang.org/grpc v1.74.2 // indirect
google.golang.org/protobuf v1.36.6 // indirect
gopkg.in/ini.v1 v1.67.0 // indirect
gopkg.in/yaml.v2 v2.4.0 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
honnef.co/go/tools v0.6.1 // indirect
mvdan.cc/gofumpt v0.8.0 // indirect
mvdan.cc/unparam v0.0.0-20250301125049-0df0534333a4 // indirect
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.50.0 // indirect
go.opentelemetry.io/otel v1.25.0 // indirect
go.opentelemetry.io/otel/metric v1.25.0 // indirect
go.opentelemetry.io/otel/trace v1.25.0 // indirect
golang.org/x/crypto v0.24.0 // indirect
golang.org/x/net v0.26.0 // indirect
golang.org/x/oauth2 v0.21.0 // indirect
golang.org/x/text v0.16.0 // indirect
golang.org/x/time v0.5.0 // indirect
google.golang.org/genproto v0.0.0-20240624140628-dc46fd24d27d // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20240624140628-dc46fd24d27d // indirect
google.golang.org/grpc v1.64.1 // indirect
google.golang.org/protobuf v1.34.2 // indirect
)
tool golang.org/x/tools/cmd/goimports
tool github.com/sclevine/yj
tool github.com/golang/mock/mockgen
tool github.com/golangci/golangci-lint/v2/cmd/golangci-lint
go 1.24.6
go 1.23

940
go.sum

File diff suppressed because it is too large Load Diff

View File

@ -1,15 +1,17 @@
version: "2"
run:
timeout: 6m
linters:
default: none
disable-all: true
enable:
- bodyclose
- copyloopvar
- dogsled
- errcheck
- gocritic
- goimports
- gosec
- gosimple
- govet
- ineffassign
- intrange
@ -17,35 +19,27 @@ linters:
- nakedret
- revive
- staticcheck
- stylecheck
- typecheck
- unconvert
- unused
- whitespace
settings:
govet:
enable:
- fieldalignment
exclusions:
generated: lax
rules:
- linters:
- govet
text: pointer bytes could be
paths:
- third_party$
- builtin$
- examples$
linters-settings:
goimports:
local-prefixes: github.com/buildpacks/lifecycle
govet:
enable:
- fieldalignment
issues:
exclude-use-default: false
new-from-rev: 91593cf91797ca0a98ffa31842107a9d916da37b
formatters:
enable:
- goimports
settings:
goimports:
local-prefixes:
- github.com/buildpacks/lifecycle
exclusions:
generated: lax
paths:
- third_party$
- builtin$
- examples$
exclude-rules:
# Ignore this minor optimization.
# See https://github.com/golang/go/issues/44877#issuecomment-794565908
- linters:
- govet
text: "pointer bytes could be"

View File

@ -22,7 +22,7 @@ type Handler interface {
// - WHEN a docker client is provided then it returns a LocalHandler
// - WHEN an auth.Keychain is provided then it returns a RemoteHandler
// - Otherwise nil is returned
func NewHandler(docker client.APIClient, keychain authn.Keychain, layoutDir string, useLayout bool, insecureRegistries []string) Handler {
func NewHandler(docker client.CommonAPIClient, keychain authn.Keychain, layoutDir string, useLayout bool, insecureRegistries []string) Handler {
if layoutDir != "" && useLayout {
return &LayoutHandler{
layoutDir: layoutDir,

View File

@ -23,7 +23,7 @@ func testHandler(t *testing.T, when spec.G, it spec.S) {
var (
mockController *gomock.Controller
mockKeychain *testmockauth.MockKeychain
dockerClient client.APIClient
dockerClient client.CommonAPIClient
)
it.Before(func() {

View File

@ -18,7 +18,7 @@ func TestLocalImageHandler(t *testing.T) {
func testLocalImageHandler(t *testing.T, when spec.G, it spec.S) {
var (
imageHandler image.Handler
dockerClient client.APIClient
dockerClient client.CommonAPIClient
)
when("Local handler", func() {

View File

@ -6,7 +6,7 @@ import (
"path/filepath"
"strings"
"github.com/chainguard-dev/kaniko/pkg/config"
"github.com/GoogleContainerTools/kaniko/pkg/config"
"github.com/containerd/containerd/platforms"
"github.com/google/go-containerregistry/pkg/name"
v1 "github.com/google/go-containerregistry/pkg/v1"

View File

@ -7,11 +7,11 @@ import (
"fmt"
"os"
"github.com/chainguard-dev/kaniko/pkg/config"
"github.com/chainguard-dev/kaniko/pkg/executor"
"github.com/chainguard-dev/kaniko/pkg/image"
"github.com/chainguard-dev/kaniko/pkg/util"
"github.com/chainguard-dev/kaniko/pkg/util/proc"
"github.com/GoogleContainerTools/kaniko/pkg/config"
"github.com/GoogleContainerTools/kaniko/pkg/executor"
"github.com/GoogleContainerTools/kaniko/pkg/image"
"github.com/GoogleContainerTools/kaniko/pkg/util"
"github.com/GoogleContainerTools/kaniko/pkg/util/proc"
v1 "github.com/google/go-containerregistry/pkg/v1"
"github.com/google/go-containerregistry/pkg/v1/mutate"
@ -39,7 +39,7 @@ func (a *DockerfileApplier) Apply(dockerfile extend.Dockerfile, toBaseImage v1.I
opts := createOptions(baseImageRef, dockerfile, withBuildOptions)
// update ignore paths; kaniko does this here:
// https://github.com/chainguard-dev/kaniko/blob/v1.9.2/cmd/executor/cmd/root.go#L124
// https://github.com/GoogleContainerTools/kaniko/blob/v1.9.2/cmd/executor/cmd/root.go#L124
if opts.IgnoreVarRun {
// from kaniko:
// /var/run is a special case. It's common to mount in /var/run/docker.sock
@ -59,7 +59,7 @@ func (a *DockerfileApplier) Apply(dockerfile extend.Dockerfile, toBaseImage v1.I
}
// change to root directory; kaniko does this here:
// https://github.com/chainguard-dev/kaniko/blob/v1.9.2/cmd/executor/cmd/root.go#L160
// https://github.com/GoogleContainerTools/kaniko/blob/v1.9.2/cmd/executor/cmd/root.go#L160
if err = os.Chdir("/"); err != nil {
return nil, err
}

View File

@ -0,0 +1,14 @@
//go:build windows
package kaniko
import (
v1 "github.com/google/go-containerregistry/pkg/v1"
"github.com/buildpacks/lifecycle/internal/extend"
"github.com/buildpacks/lifecycle/log"
)
func (a *DockerfileApplier) Apply(dockerfile extend.Dockerfile, toBaseImage v1.Image, withBuildOptions extend.Options, logger log.Logger) (v1.Image, error) {
return nil, nil
}

View File

@ -1,4 +1,4 @@
//go:build darwin
//go:build windows || darwin
package fsutil_test

View File

@ -4,6 +4,7 @@ import (
"io"
"os"
"path/filepath"
"runtime"
"strings"
)
@ -96,3 +97,20 @@ func copySymlink(src, dst string) error {
}
return os.Symlink(target, dst)
}
func RenameWithWindowsFallback(src, dst string) error {
if err := os.Rename(src, dst); err != nil {
switch {
case runtime.GOOS == "windows":
// On Windows, when using process isolation, we could encounter https://github.com/moby/moby/issues/38256
// which causes renames inside mounted volumes to fail for an unknown reason.
if err = copyDir(src, dst); err != nil {
return err
}
return os.RemoveAll(src)
default:
return err
}
}
return nil
}

View File

@ -70,6 +70,16 @@ func testIO(t *testing.T, when spec.G, it spec.S) {
})
})
when("#RenameWithWindowsFallback", func() {
when("directory does not exist", func() {
it("returns not exist error", func() {
err := fsutil.RenameWithWindowsFallback("some-not-exist-dir", "dest-dir")
h.AssertNotNil(t, err)
h.AssertEq(t, os.IsNotExist(err), true)
})
})
})
when("#FilesWithExtensions", func() {
when("called with directory and extensions", func() {
it("filters the files", func() {

View File

@ -7,6 +7,7 @@ import (
"os"
"path/filepath"
"regexp"
"runtime"
"github.com/buildpacks/imgutil"
"github.com/pkg/errors"
@ -110,7 +111,11 @@ func (r *DefaultSBOMRestorer) RestoreToBuildpackLayers(detectedBps []buildpack.G
func (r *DefaultSBOMRestorer) restoreSBOMFunc(detectedBps []buildpack.GroupElement, bomType string) func(path string, info fs.FileInfo, err error) error {
var bomRegex *regexp.Regexp
bomRegex = regexp.MustCompile(fmt.Sprintf(`%s/(.+)/(.+)/(sbom.+json)`, bomType))
if runtime.GOOS == "windows" {
bomRegex = regexp.MustCompile(fmt.Sprintf(`%s\\(.+)\\(.+)\\(sbom.+json)`, bomType))
} else {
bomRegex = regexp.MustCompile(fmt.Sprintf(`%s/(.+)/(.+)/(sbom.+json)`, bomType))
}
return func(path string, info fs.FileInfo, err error) error {
if info == nil || !info.Mode().IsRegular() {

View File

@ -4,6 +4,7 @@ import (
"fmt"
"os"
"path/filepath"
"runtime"
"testing"
"github.com/sclevine/spec"
@ -25,6 +26,7 @@ func testBash(t *testing.T, when spec.G, it spec.S) {
)
it.Before(func() {
h.SkipIf(t, runtime.GOOS == "windows", "skip bash tests on windows")
var err error
tmpDir, err = os.MkdirTemp("", "shell-test")
h.AssertNil(t, err)

147
launch/cmd_test.go Normal file
View File

@ -0,0 +1,147 @@
package launch_test
import (
"fmt"
"os"
"path/filepath"
"runtime"
"testing"
"github.com/sclevine/spec"
"github.com/sclevine/spec/report"
"github.com/buildpacks/lifecycle/launch"
hl "github.com/buildpacks/lifecycle/launch/testhelpers"
h "github.com/buildpacks/lifecycle/testhelpers"
)
func TestCmd(t *testing.T) {
spec.Run(t, "Cmd", testCmd, spec.Report(report.Terminal{}))
}
func testCmd(t *testing.T, when spec.G, it spec.S) {
var (
shell launch.Shell
tmpDir string
defaultDir string
err error
)
it.Before(func() {
defaultDir, err = os.Getwd()
h.AssertNil(t, err)
h.SkipIf(t, runtime.GOOS != "windows", "skip cmd tests on unix")
tmpDir, err = os.MkdirTemp("", "shell-test")
h.AssertNil(t, err)
shell = &launch.CmdShell{Exec: hl.SyscallExecWithStdout(t, tmpDir)}
})
it.After(func() {
h.AssertNil(t, os.RemoveAll(tmpDir))
})
when("#Launch", func() {
var process launch.ShellProcess
when("is not script", func() {
when("there are profiles", func() {
it.Before(func() {
h.AssertNil(t, err)
process = launch.ShellProcess{
Script: false,
Command: "echo",
Args: []string{"profile env: '!PROFILE_VAR!'"},
Env: []string{
"SOME_VAR=some-val",
},
WorkingDirectory: defaultDir,
}
process.Profiles = []string{
filepath.Join("testdata", "profiles", "print_argv0.bat"),
filepath.Join("testdata", "profiles", "print_env.bat"),
filepath.Join("testdata", "profiles", "set_env.bat"),
}
})
it("runs the profiles from the default directory", func() {
process.Profiles = []string{
filepath.Join("testdata", "profiles", "pwd.bat"),
}
err = shell.Launch(process)
h.AssertNil(t, err)
stdout := rdfile(t, filepath.Join(tmpDir, "stdout"))
h.AssertStringContains(t, stdout, fmt.Sprintf("profile directory: %s\r\n", defaultDir))
})
it("runs the command from the working directory", func() {
process.WorkingDirectory = tmpDir
process.Command = "echo"
process.Args = []string{
"process",
"working",
"directory:",
"&&",
"cd",
}
err = shell.Launch(process)
h.AssertNil(t, err)
stdout := rdfile(t, filepath.Join(tmpDir, "stdout"))
h.AssertStringContains(t, stdout, fmt.Sprintf("process working directory: \r\n%s\r\n", tmpDir))
})
it("sets argv0 for profile scripts to profile script path", func() {
err := shell.Launch(process)
h.AssertNil(t, err)
stdout := rdfile(t, filepath.Join(tmpDir, "stdout"))
if len(stdout) == 0 {
stderr := rdfile(t, filepath.Join(tmpDir, "stderr"))
t.Fatalf("stdout was empty: stderr: %s\n", stderr)
}
h.AssertStringContains(t, stdout, fmt.Sprintf("profile argv0: '%s'", filepath.Join("testdata", "profiles", "print_argv0.bat")))
})
it("sets env for profile scripts", func() {
err := shell.Launch(process)
h.AssertNil(t, err)
stdout := rdfile(t, filepath.Join(tmpDir, "stdout"))
if len(stdout) == 0 {
stderr := rdfile(t, filepath.Join(tmpDir, "stderr"))
t.Fatalf("stdout was empty: stderr: %s\n", stderr)
}
h.AssertStringContains(t, stdout, "SOME_VAR: 'some-val'")
})
it("env vars set in profile scripts are available to the command", func() {
err := shell.Launch(process)
h.AssertNil(t, err)
stdout := rdfile(t, filepath.Join(tmpDir, "stdout"))
if len(stdout) == 0 {
stderr := rdfile(t, filepath.Join(tmpDir, "stderr"))
t.Fatalf("stdout was empty: stderr: %s\n", stderr)
}
h.AssertStringContains(t, stdout, "profile env: 'some-profile-var'")
})
})
it("sets env", func() {
process = launch.ShellProcess{
Script: false,
Command: `echo`,
Args: []string{"SOME_VAR: '%SOME_VAR%'"},
Env: []string{
"SOME_VAR=some-val",
},
WorkingDirectory: defaultDir,
}
err := shell.Launch(process)
h.AssertNil(t, err)
stdout := rdfile(t, filepath.Join(tmpDir, "stdout"))
if len(stdout) == 0 {
stderr := rdfile(t, filepath.Join(tmpDir, "stderr"))
t.Fatalf("stdout was empty: stderr: %s\n", stderr)
}
h.AssertStringContains(t, stdout, "SOME_VAR: 'some-val'")
})
})
})
}

View File

@ -61,8 +61,3 @@ func (e *ExecDRunner) ExecD(path string, env Env) error {
}
return nil
}
func setHandle(cmd *exec.Cmd, f *os.File) error {
cmd.ExtraFiles = []*os.File{f}
return nil
}

View File

@ -5,6 +5,7 @@ import (
"os"
"os/exec"
"path/filepath"
"runtime"
"testing"
"github.com/golang/mock/gomock"
@ -41,7 +42,11 @@ func testExecD(t *testing.T, when spec.G, it spec.S) {
wd, err := os.Getwd()
h.AssertNil(t, err)
path = filepath.Join(tmpDir, "execd")
exe := ""
if runtime.GOOS == "windows" {
exe = ".exe"
}
path = filepath.Join(tmpDir, "execd"+exe)
//#nosec G204
cmd := exec.Command("go", "build",

13
launch/exec_d_unix.go Normal file
View File

@ -0,0 +1,13 @@
//go:build unix
package launch
import (
"os"
"os/exec"
)
func setHandle(cmd *exec.Cmd, f *os.File) error {
cmd.ExtraFiles = []*os.File{f}
return nil
}

25
launch/exec_d_windows.go Normal file
View File

@ -0,0 +1,25 @@
package launch
import (
"fmt"
"os"
"os/exec"
"syscall"
"golang.org/x/sys/windows"
)
const EnvExecDHandle = "CNB_EXEC_D_HANDLE"
func setHandle(cmd *exec.Cmd, f *os.File) error {
handle := f.Fd()
if err := windows.SetHandleInformation(windows.Handle(handle), windows.HANDLE_FLAG_INHERIT, windows.HANDLE_FLAG_INHERIT); err != nil {
return err
}
cmd.SysProcAttr = &syscall.SysProcAttr{
AdditionalInheritedHandles: []syscall.Handle{syscall.Handle(handle)},
}
cmd.Env = append(cmd.Env, fmt.Sprintf("%s=%#x", EnvExecDHandle, handle))
return nil
}

View File

@ -3,6 +3,8 @@ package launch_test
import (
"os"
"path/filepath"
"runtime"
"strings"
"testing"
"github.com/golang/mock/gomock"
@ -122,7 +124,11 @@ func testLauncher(t *testing.T, when spec.G, it spec.S) {
process.Direct = true
// set command to something on the real path so exec.LookPath succeeds
process.Command = launch.NewRawCommand([]string{"sh"})
if runtime.GOOS == "windows" {
process.Command = launch.NewRawCommand([]string{"notepad"})
} else {
process.Command = launch.NewRawCommand([]string{"sh"})
}
mockEnv.EXPECT().Get("PATH").Return("some-path").AnyTimes()
launcher.Setenv = func(k string, v string) error {
@ -148,7 +154,11 @@ func testLauncher(t *testing.T, when spec.G, it spec.S) {
if len(syscallExecArgsColl) != 1 {
t.Fatalf("expected syscall.Exec to be called once: actual %v\n", syscallExecArgsColl)
}
h.AssertMatch(t, syscallExecArgsColl[0].argv0, ".*/bin/sh$")
if runtime.GOOS == "windows" {
h.AssertEq(t, strings.ToLower(syscallExecArgsColl[0].argv0), `c:\windows\system32\notepad.exe`)
} else {
h.AssertMatch(t, syscallExecArgsColl[0].argv0, ".*/bin/sh$")
}
})
it("should set argv to command+args", func() {
@ -156,7 +166,11 @@ func testLauncher(t *testing.T, when spec.G, it spec.S) {
if len(syscallExecArgsColl) != 1 {
t.Fatalf("expected syscall.Exec to be called once: actual %v\n", syscallExecArgsColl)
}
h.AssertEq(t, syscallExecArgsColl[0].argv, []string{"sh", "arg1", "arg2"})
if runtime.GOOS == "windows" {
h.AssertEq(t, syscallExecArgsColl[0].argv, []string{"notepad", "arg1", "arg2"})
} else {
h.AssertEq(t, syscallExecArgsColl[0].argv, []string{"sh", "arg1", "arg2"})
}
})
it("should set envv", func() {
@ -425,10 +439,17 @@ func testLauncher(t *testing.T, when spec.G, it spec.S) {
filepath.Join(tmpDir, "launch", "0.8_buildpack", "layer", "profile.d", "some-process-type"),
)
mkfile(t, "", filepath.Join(tmpDir, "launch", "0.7_buildpack", "layer", "profile.d", "prof1"))
mkfile(t, "", filepath.Join(tmpDir, "launch", "0.7_buildpack", "layer", "profile.d", "some-process-type", "prof1"))
mkfile(t, "", filepath.Join(tmpDir, "launch", "0.8_buildpack", "layer", "profile.d", "prof2"))
mkfile(t, "", filepath.Join(tmpDir, "launch", "0.8_buildpack", "layer", "profile.d", "some-process-type", "prof2"))
if runtime.GOOS == "windows" {
mkfile(t, "", filepath.Join(tmpDir, "launch", "0.7_buildpack", "layer", "profile.d", "prof1.bat"))
mkfile(t, "", filepath.Join(tmpDir, "launch", "0.7_buildpack", "layer", "profile.d", "some-process-type", "prof1.bat"))
mkfile(t, "", filepath.Join(tmpDir, "launch", "0.8_buildpack", "layer", "profile.d", "prof2.bat"))
mkfile(t, "", filepath.Join(tmpDir, "launch", "0.8_buildpack", "layer", "profile.d", "some-process-type", "prof2.bat"))
} else {
mkfile(t, "", filepath.Join(tmpDir, "launch", "0.7_buildpack", "layer", "profile.d", "prof1"))
mkfile(t, "", filepath.Join(tmpDir, "launch", "0.7_buildpack", "layer", "profile.d", "some-process-type", "prof1"))
mkfile(t, "", filepath.Join(tmpDir, "launch", "0.8_buildpack", "layer", "profile.d", "prof2"))
mkfile(t, "", filepath.Join(tmpDir, "launch", "0.8_buildpack", "layer", "profile.d", "some-process-type", "prof2"))
}
mockEnv.EXPECT().AddRootDir(gomock.Any()).AnyTimes()
mockEnv.EXPECT().AddEnvDir(gomock.Any(), gomock.Any()).AnyTimes()
@ -437,10 +458,17 @@ func testLauncher(t *testing.T, when spec.G, it spec.S) {
it("sets the profiles", func() {
h.AssertNil(t, launcher.LaunchProcess("/path/to/launcher", process))
h.AssertEq(t, shell.nCalls, 1)
h.AssertEq(t, shell.process.Profiles, []string{
filepath.Join(tmpDir, "launch", "0.7_buildpack", "layer", "profile.d", "prof1"),
filepath.Join(tmpDir, "launch", "0.8_buildpack", "layer", "profile.d", "prof2"),
})
if runtime.GOOS == "windows" {
h.AssertEq(t, shell.process.Profiles, []string{
filepath.Join(tmpDir, "launch", "0.7_buildpack", "layer", "profile.d", "prof1.bat"),
filepath.Join(tmpDir, "launch", "0.8_buildpack", "layer", "profile.d", "prof2.bat"),
})
} else {
h.AssertEq(t, shell.process.Profiles, []string{
filepath.Join(tmpDir, "launch", "0.7_buildpack", "layer", "profile.d", "prof1"),
filepath.Join(tmpDir, "launch", "0.8_buildpack", "layer", "profile.d", "prof2"),
})
}
})
when("process has a type", func() {
@ -451,12 +479,21 @@ func testLauncher(t *testing.T, when spec.G, it spec.S) {
it("includes type-specifc profiles", func() {
h.AssertNil(t, launcher.LaunchProcess("/path/to/launcher", process))
h.AssertEq(t, shell.nCalls, 1)
h.AssertEq(t, shell.process.Profiles, []string{
filepath.Join(tmpDir, "launch", "0.7_buildpack", "layer", "profile.d", "prof1"),
filepath.Join(tmpDir, "launch", "0.7_buildpack", "layer", "profile.d", "some-process-type", "prof1"),
filepath.Join(tmpDir, "launch", "0.8_buildpack", "layer", "profile.d", "prof2"),
filepath.Join(tmpDir, "launch", "0.8_buildpack", "layer", "profile.d", "some-process-type", "prof2"),
})
if runtime.GOOS == "windows" {
h.AssertEq(t, shell.process.Profiles, []string{
filepath.Join(tmpDir, "launch", "0.7_buildpack", "layer", "profile.d", "prof1.bat"),
filepath.Join(tmpDir, "launch", "0.7_buildpack", "layer", "profile.d", "some-process-type", "prof1.bat"),
filepath.Join(tmpDir, "launch", "0.8_buildpack", "layer", "profile.d", "prof2.bat"),
filepath.Join(tmpDir, "launch", "0.8_buildpack", "layer", "profile.d", "some-process-type", "prof2.bat"),
})
} else {
h.AssertEq(t, shell.process.Profiles, []string{
filepath.Join(tmpDir, "launch", "0.7_buildpack", "layer", "profile.d", "prof1"),
filepath.Join(tmpDir, "launch", "0.7_buildpack", "layer", "profile.d", "some-process-type", "prof1"),
filepath.Join(tmpDir, "launch", "0.8_buildpack", "layer", "profile.d", "prof2"),
filepath.Join(tmpDir, "launch", "0.8_buildpack", "layer", "profile.d", "some-process-type", "prof2"),
})
}
})
})
})
@ -553,13 +590,34 @@ func testLauncher(t *testing.T, when spec.G, it spec.S) {
process.Args = []string{}
})
it("is script", func() {
h.AssertNil(t, launcher.LaunchProcess("/path/to/launcher", process))
h.AssertEq(t, shell.nCalls, 1)
when("linux", func() {
it.Before(func() {
h.SkipIf(t, runtime.GOOS == "windows", "linux test")
})
if !shell.process.Script {
t.Fatalf("expected script process")
}
it("is script", func() {
h.AssertNil(t, launcher.LaunchProcess("/path/to/launcher", process))
h.AssertEq(t, shell.nCalls, 1)
if !shell.process.Script {
t.Fatalf("expected script process")
}
})
})
when("windows", func() {
it.Before(func() {
h.SkipIf(t, runtime.GOOS != "windows", "windows test")
})
it("is not script", func() {
h.AssertNil(t, launcher.LaunchProcess("/path/to/launcher", process))
h.AssertEq(t, shell.nCalls, 1)
if shell.process.Script {
t.Fatalf("did not expect script process")
}
})
})
})
})
@ -581,13 +639,34 @@ func testLauncher(t *testing.T, when spec.G, it spec.S) {
process.Args = []string{}
})
it("is script", func() {
h.AssertNil(t, launcher.LaunchProcess("/path/to/launcher", process))
h.AssertEq(t, shell.nCalls, 1)
when("linux", func() {
it.Before(func() {
h.SkipIf(t, runtime.GOOS == "windows", "linux test")
})
if !shell.process.Script {
t.Fatalf("expected script process")
}
it("is script", func() {
h.AssertNil(t, launcher.LaunchProcess("/path/to/launcher", process))
h.AssertEq(t, shell.nCalls, 1)
if !shell.process.Script {
t.Fatalf("expected script process")
}
})
})
when("windows", func() {
it.Before(func() {
h.SkipIf(t, runtime.GOOS != "windows", "windows test")
})
it("is not script", func() {
h.AssertNil(t, launcher.LaunchProcess("/path/to/launcher", process))
h.AssertEq(t, shell.nCalls, 1)
if shell.process.Script {
t.Fatalf("did not expect script process")
}
})
})
})
})

View File

@ -4,6 +4,7 @@ import (
"fmt"
"os"
"path/filepath"
"runtime"
"strings"
"github.com/pkg/errors"
@ -89,6 +90,10 @@ func (l *Launcher) populateLayerProfiles(procType string, profiles *[]string) di
}
func (l *Launcher) isScript(proc Process) (bool, error) {
if runtime.GOOS == "windows" {
// Windows does not support script commands
return false, nil
}
if len(proc.Args) == 0 {
return true, nil
}

View File

@ -0,0 +1,43 @@
//go:build windows
package testhelpers
import (
"os"
"os/exec"
"path/filepath"
"testing"
"github.com/buildpacks/lifecycle/launch"
)
func SyscallExecWithStdout(t *testing.T, tmpDir string) launch.ExecFunc {
t.Helper()
fstdin, err := os.Create(filepath.Join(tmpDir, "stdin"))
if err != nil {
t.Fatal(err)
}
fstdout, err := os.Create(filepath.Join(tmpDir, "stdout"))
if err != nil {
_ = fstdin.Close()
t.Fatal(err)
}
fstderr, err := os.Create(filepath.Join(tmpDir, "stderr"))
if err != nil {
_ = fstdin.Close()
_ = fstdout.Close()
t.Fatal(err)
}
return func(argv0 string, argv []string, envv []string) error {
defer fstdin.Close()
defer fstdout.Close()
defer fstderr.Close()
c := exec.Command(argv[0], argv[1:]...) // #nosec G204
c.Env = envv
c.Stdin = fstdin
c.Stdout = fstdout
c.Stderr = fstderr
return c.Run()
}
}

6
launcher.mk Normal file
View File

@ -0,0 +1,6 @@
define build_launcher
@echo "> Building launcher for $(TARGET)..."
mkdir -p $(OUT_DIR)
$(GOENV) $(GOBUILD) -o $(OUT_DIR)/launcher -a ./cmd/launcher
test $$(du -m $(OUT_DIR)/launcher | cut -f 1) -le 4
endef

View File

@ -3,13 +3,14 @@ package layers
import (
"archive/tar"
"io"
"runtime"
"github.com/buildpacks/lifecycle/archive"
)
// Extract extracts entries from r to the dest directory
// Contents of r should be an OCI layer.
// If dest is an empty string files with be extracted to `/` on unix filesystems.
// If dest is an empty string files with be extracted to `/` or `c:\` on unix and windows filesystems respectively.
func Extract(r io.Reader, dest string) error {
tr := tarReader(r, dest)
return archive.Extract(tr)
@ -17,6 +18,13 @@ func Extract(r io.Reader, dest string) error {
func tarReader(r io.Reader, dest string) archive.TarReader {
tr := archive.NewNormalizingTarReader(tar.NewReader(r))
if runtime.GOOS == "windows" {
tr.ExcludePaths([]string{"Hives"})
tr.Strip(`Files/`)
if dest == "" {
dest = `c:\`
}
}
if dest == "" {
dest = `/`
}

View File

@ -5,6 +5,7 @@ import (
"fmt"
"io"
"os"
"runtime"
"strings"
"github.com/pkg/errors"
@ -30,7 +31,11 @@ func (f *Factory) LauncherLayer(path string) (layer Layer, err error) {
hdr.Name = launch.LauncherPath
hdr.Uid = 0
hdr.Gid = 0
hdr.Mode = 0755
if runtime.GOOS == "windows" {
hdr.Mode = 0777
} else {
hdr.Mode = 0755
}
return f.writeLayer("buildpacksio/lifecycle:launcher", LauncherLayerName, func(tw *archive.NormalizingTarWriter) error {
for _, dir := range parents {
@ -92,7 +97,11 @@ func validateProcessType(pType string) error {
func rootOwnedDir(path string) *tar.Header {
var modePerm int64
modePerm = 0755
if runtime.GOOS == "windows" {
modePerm = 0777
} else {
modePerm = 0755
}
return &tar.Header{
Typeflag: tar.TypeDir,
Name: path,
@ -102,7 +111,11 @@ func rootOwnedDir(path string) *tar.Header {
func typeSymlink(path string) *tar.Header {
var modePerm int64
modePerm = 0755
if runtime.GOOS == "windows" {
modePerm = 0777
} else {
modePerm = 0755
}
return &tar.Header{
Typeflag: tar.TypeSymlink,
Name: path,

View File

@ -5,6 +5,7 @@ import (
"io"
"os"
"path/filepath"
"runtime"
"testing"
"github.com/apex/log"
@ -53,6 +54,9 @@ func testLauncherLayers(t *testing.T, when spec.G, it spec.S) {
h.AssertNil(t, err)
h.AssertEq(t, configLayer.ID, "buildpacksio/lifecycle:process-types")
var mode int64 = 0755
if runtime.GOOS == "windows" {
mode = 0777
}
assertTarEntries(t, configLayer.TarPath, []*tar.Header{
{
Name: tarPath("/cnb"),
@ -114,6 +118,9 @@ func testLauncherLayers(t *testing.T, when spec.G, it spec.S) {
h.AssertNil(t, err)
h.AssertEq(t, launcherLayer.ID, "buildpacksio/lifecycle:launcher")
var mode int64 = 0755
if runtime.GOOS == "windows" {
mode = 0777
}
assertTarEntries(t, launcherLayer.TarPath, []*tar.Header{
{
Name: tarPath("/cnb"),

View File

@ -2,6 +2,7 @@ package layers_test
import (
"archive/tar"
"io"
"os"
"path"
"path/filepath"
@ -30,3 +31,16 @@ func assertOSSpecificFields(t *testing.T, expected *tar.Header, hdr *tar.Header)
t.Helper()
h.AssertEq(t, hdr.Format, tar.FormatPAX)
}
func assertOSSpecificEntries(t *testing.T, tr *tar.Reader) {
for _, windowsEntry := range []string{"Files", "Hives"} {
header, err := tr.Next()
if err == io.EOF {
t.Fatalf("missing expected archive entry '%s'", windowsEntry)
}
h.AssertEq(t, header.Name, windowsEntry)
if header.Typeflag != tar.TypeDir {
t.Fatalf("expected entry '%s' to have type %q, got %q", windowsEntry, header.Typeflag, tar.TypeDir)
}
}
}

View File

@ -5,6 +5,7 @@ import (
"fmt"
"os"
"path/filepath"
"runtime"
"testing"
"github.com/sclevine/spec"
@ -190,12 +191,21 @@ func testSlices(t *testing.T, when spec.G, it spec.S) {
it.Before(func() {
var err error
sliceLayers, err = factory.SliceLayers(dirToSlice, []layers.Slice{
{Paths: []string{"*.txt", "**/*.txt"}},
{Paths: []string{"other-dir"}},
{Paths: []string{"dir-link/*"}},
{Paths: []string{"../**/dir-to-exclude"}},
})
if runtime.GOOS == "windows" {
sliceLayers, err = factory.SliceLayers(dirToSlice, []layers.Slice{
{Paths: []string{"*.txt", "**\\*.txt"}},
{Paths: []string{"other-dir"}},
{Paths: []string{"dir-link\\*"}},
{Paths: []string{"..\\**\\dir-to-exclude"}},
})
} else {
sliceLayers, err = factory.SliceLayers(dirToSlice, []layers.Slice{
{Paths: []string{"*.txt", "**/*.txt"}},
{Paths: []string{"other-dir"}},
{Paths: []string{"dir-link/*"}},
{Paths: []string{"../**/dir-to-exclude"}},
})
}
h.AssertNil(t, err)
})

View File

@ -1,9 +1,13 @@
package layers_test
import (
"archive/tar"
"io"
"os"
"path/filepath"
"runtime"
"testing"
"time"
v1 "github.com/google/go-containerregistry/pkg/v1"
"github.com/sclevine/spec"
@ -41,6 +45,12 @@ func testTarLayer(t *testing.T, when spec.G, it spec.S) {
h.AssertEq(t, layer.ID, "some-extension-id:some-layer-name")
h.AssertEq(t, layer.TarPath, filepath.Join(factory.ArtifactsDir, "some-extension-id:some-layer-name.tar"))
h.AssertEq(t, layer.History, v1.History{CreatedBy: "some-created-by"})
if runtime.GOOS != "windows" {
// windows reports zeroed timestamps as `1970-01-01 00:00:00 +0000 UTC`
assertTimestamps(t, layer.TarPath, time.Date(1980, time.January, 1, 0, 0, 1, 0, time.UTC))
// normalizing tar writer mutates the filepath separator
h.AssertEq(t, layer.Digest, "sha256:fb54d2566824d6630d94db0b008d9a544a94d3547a424f52e2fd282b648c0601") // from fixture
}
})
})
@ -54,7 +64,34 @@ func testTarLayer(t *testing.T, when spec.G, it spec.S) {
h.AssertEq(t, layer.ID, "some-extension-id:some-layer-name")
h.AssertEq(t, layer.TarPath, filepath.Join(factory.ArtifactsDir, "some-extension-id:some-layer-name.tar"))
h.AssertEq(t, layer.History, v1.History{CreatedBy: "some-created-by"})
if runtime.GOOS != "windows" {
// windows reports zeroed timestamps as `1970-01-01 00:00:00 +0000 UTC`
assertTimestamps(t, layer.TarPath, time.Date(1980, time.January, 1, 0, 0, 1, 0, time.UTC))
// normalizing tar writer mutates the filepath separator
h.AssertEq(t, layer.Digest, "sha256:fb54d2566824d6630d94db0b008d9a544a94d3547a424f52e2fd282b648c0601") // from fixture
}
})
})
})
}
func assertTimestamps(t *testing.T, tarPath string, expected time.Time) {
t.Helper()
f, err := os.Open(tarPath) // #nosec G304
h.AssertNil(t, err)
defer f.Close() // nolint
tr := tar.NewReader(f)
var found int
for {
header, err := tr.Next()
if err == io.EOF {
break
}
h.AssertNil(t, err)
found++
h.AssertEq(t, header.ModTime, expected)
}
h.AssertEq(t, found > 0, true)
}

View File

@ -6,6 +6,9 @@ import (
"fmt"
"io"
"os"
"runtime"
"github.com/buildpacks/imgutil/layer"
"github.com/buildpacks/lifecycle/archive"
)
@ -32,7 +35,12 @@ func (lw *layerWriter) Digest() string {
}
func tarWriter(lw *layerWriter) *archive.NormalizingTarWriter {
tw := archive.NewNormalizingTarWriter(tar.NewWriter(lw))
var tw *archive.NormalizingTarWriter
if runtime.GOOS == "windows" {
tw = archive.NewNormalizingTarWriter(layer.NewWindowsWriter(lw))
} else {
tw = archive.NewNormalizingTarWriter(tar.NewWriter(lw))
}
tw.WithModTime(archive.NormalizedModTime)
return tw
}

13
lifecycle.mk Normal file
View File

@ -0,0 +1,13 @@
define build_lifecycle
@echo "> Building lifecycle for $(TARGET)..."
$(GOENV) $(GOBUILD) -o $(OUT_DIR)/lifecycle -a ./cmd/lifecycle
@echo "> Creating lifecycle symlinks for $(1)..."
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
endef

View File

@ -36,7 +36,7 @@ func (l *DefaultLogger) LogLevel() log.Level {
}
func (l *DefaultLogger) Phase(name string) {
l.Info(phaseStyle("===> %s", name))
l.Infof(phaseStyle("===> %s", name))
}
func (l *DefaultLogger) SetLevel(requested string) error {

View File

@ -65,12 +65,12 @@ func testTimeLog(t *testing.T, when spec.G, it spec.S) {
c1.Log = logger
c1.RecordStart()
h.AssertEq(t, logger.callCount["Debug"], 1)
h.AssertEq(t, c1.StartTime.Equal(nullTime), false)
h.AssertEq(t, c1.StartTime == nullTime, false)
h.AssertEq(t, c1.EndTime, nullTime)
c1.RecordEnd()
h.AssertEq(t, logger.callCount["Debug"], 2)
h.AssertEq(t, c1.EndTime.Equal(nullTime), false)
h.AssertEq(t, c1.EndTime == nullTime, false)
})
it("the convenience functions call the logger", func() {
logger := mockLog{callCount: map[string]int{}}

View File

@ -486,7 +486,7 @@ func testAnalyzer(platformAPI string) func(t *testing.T, when spec.G, it spec.S)
it("populates target metadata from the run image", func() {
h.AssertNil(t, previousImage.SetLabel("io.buildpacks.base.id", "id software"))
h.AssertNil(t, previousImage.SetOS("zindows"))
h.AssertNil(t, previousImage.SetOS("windows"))
h.AssertNil(t, previousImage.SetOSVersion("95"))
h.AssertNil(t, previousImage.SetArchitecture("Pentium"))
h.AssertNil(t, previousImage.SetVariant("MMX"))
@ -501,7 +501,7 @@ func testAnalyzer(platformAPI string) func(t *testing.T, when spec.G, it spec.S)
h.AssertNotNil(t, md.RunImage.TargetMetadata)
h.AssertEq(t, md.RunImage.TargetMetadata.Arch, "Pentium")
h.AssertEq(t, md.RunImage.TargetMetadata.ArchVariant, "MMX")
h.AssertEq(t, md.RunImage.TargetMetadata.OS, "zindows")
h.AssertEq(t, md.RunImage.TargetMetadata.OS, "windows")
h.AssertEq(t, md.RunImage.TargetMetadata.ID, "id software")
h.AssertNotNil(t, md.RunImage.TargetMetadata.Distro)
h.AssertEq(t, md.RunImage.TargetMetadata.Distro.Name, "moobuntu")

View File

@ -744,7 +744,7 @@ func testDetector(t *testing.T, when spec.G, it spec.S) {
Targets: []buildpack.TargetMetadata{
{Arch: "P6", ArchVariant: "Pentium Pro", OS: "Win95",
Distros: []buildpack.OSDistro{
{Name: "Sceens 95", Version: "OSR1"}, {Name: "Sceens 95", Version: "OSR2.5"}}},
{Name: "Windows 95", Version: "OSR1"}, {Name: "Windows 95", Version: "OSR2.5"}}},
{Arch: "ARM64", OS: "MacOS", Distros: []buildpack.OSDistro{{Name: "MacOS", Version: "snow cheetah"}}}},
}
dirStore.EXPECT().LookupBp("A", "v1").Return(bpA1, nil).AnyTimes()
@ -821,7 +821,7 @@ func testDetector(t *testing.T, when spec.G, it spec.S) {
Targets: []buildpack.TargetMetadata{
{Arch: "P6", ArchVariant: "Pentium Pro", OS: "Win95",
Distros: []buildpack.OSDistro{
{Name: "Sceens 95", Version: "OSR1"}, {Name: "Sceens 95", Version: "OSR2.5"}}},
{Name: "Windows 95", Version: "OSR1"}, {Name: "Windows 95", Version: "OSR2.5"}}},
{Arch: "ARM64", OS: "MacOS", Distros: []buildpack.OSDistro{{Name: "MacOS", Version: "snow cheetah"}}}},
}
dirStore.EXPECT().LookupBp("A", "v1").Return(bpA1, nil).AnyTimes()
@ -860,9 +860,9 @@ func testDetector(t *testing.T, when spec.G, it spec.S) {
Targets: []buildpack.TargetMetadata{
{Arch: "P6", ArchVariant: "Pentium Pro", OS: "Win95",
Distros: []buildpack.OSDistro{
{Name: "Sceens 95", Version: "OSR1"}, {Name: "Sceens 95", Version: "OSR2.5"}}},
{Name: "Windows 95", Version: "OSR1"}, {Name: "Windows 95", Version: "OSR2.5"}}},
{Arch: "Pentium M", OS: "Win98",
Distros: []buildpack.OSDistro{{Name: "Screens 2000", Version: "Server"}}},
Distros: []buildpack.OSDistro{{Name: "Windows 2000", Version: "Server"}}},
},
}
dirStore.EXPECT().LookupBp("A", "v1").Return(bpA1, nil).AnyTimes()
@ -874,7 +874,7 @@ func testDetector(t *testing.T, when spec.G, it spec.S) {
h.AssertEq(t, ok, true)
outs := val.(buildpack.DetectOutputs)
h.AssertEq(t, outs.Code, -1)
h.AssertStringContains(t, outs.Err.Error(), `unable to satisfy target os/arch constraints; run image: {"os":"MacOS","arch":"ARM64","distro":{"name":"MacOS","version":"some kind of big cat"}}, buildpack: [{"os":"Win95","arch":"P6","arch-variant":"Pentium Pro","distros":[{"name":"Sceens 95","version":"OSR1"},{"name":"Sceens 95","version":"OSR2.5"}]},{"os":"Win98","arch":"Pentium M","distros":[{"name":"Screens 2000","version":"Server"}]}]`)
h.AssertStringContains(t, outs.Err.Error(), `unable to satisfy target os/arch constraints; run image: {"os":"MacOS","arch":"ARM64","distro":{"name":"MacOS","version":"some kind of big cat"}}, buildpack: [{"os":"Win95","arch":"P6","arch-variant":"Pentium Pro","distros":[{"name":"Windows 95","version":"OSR1"},{"name":"Windows 95","version":"OSR2.5"}]},{"os":"Win98","arch":"Pentium M","distros":[{"name":"Windows 2000","version":"Server"}]}]`)
return []buildpack.GroupElement{}, []files.BuildPlanEntry{}, nil
})

View File

@ -6,6 +6,7 @@ import (
"io"
"os"
"path/filepath"
"runtime"
"strings"
"testing"
@ -707,7 +708,11 @@ version = "4.5.6"
val, err := opts.WorkingImage.Env("PATH")
h.AssertNil(t, err)
h.AssertEq(t, val, `/cnb/process:/cnb/lifecycle:some-path`)
if runtime.GOOS == "windows" {
h.AssertEq(t, val, `c:\cnb\process;c:\cnb\lifecycle;some-path`)
} else {
h.AssertEq(t, val, `/cnb/process:/cnb/lifecycle:some-path`)
}
})
})

View File

@ -5,6 +5,7 @@ import (
"fmt"
"os"
"path/filepath"
"runtime"
"testing"
"time"
@ -30,8 +31,10 @@ import (
)
func TestExtender(t *testing.T) {
spec.Run(t, "unit-new-extender", testExtenderFactory, spec.Report(report.Terminal{}))
spec.Run(t, "unit-extender", testExtender, spec.Sequential(), spec.Report(report.Terminal{}))
if runtime.GOOS != "windows" {
spec.Run(t, "unit-new-extender", testExtenderFactory, spec.Report(report.Terminal{}))
spec.Run(t, "unit-extender", testExtender, spec.Sequential(), spec.Report(report.Terminal{}))
}
}
func testExtenderFactory(t *testing.T, when spec.G, it spec.S) {

View File

@ -163,7 +163,7 @@ func (g *Generator) copyDockerfiles(dockerfiles []buildpack.DockerfileInfo) erro
if g.PlatformAPI.AtLeast("0.13") {
if ignoreDockerfile {
if err := os.Rename(dockerfile.Path, dockerfile.Path+".ignore"); err != nil {
if err := fsutil.RenameWithWindowsFallback(dockerfile.Path, dockerfile.Path+".ignore"); err != nil {
return fmt.Errorf("failed to rename Dockerfile at %s: %w", dockerfile.Path, err)
}
}

View File

@ -5,6 +5,7 @@ import (
"fmt"
"os"
"path/filepath"
"runtime"
"strings"
"testing"
@ -27,8 +28,10 @@ import (
)
func TestGenerator(t *testing.T) {
spec.Run(t, "unit-new-generator", testGeneratorFactory, spec.Report(report.Terminal{}))
spec.Run(t, "unit-generator", testGenerator, spec.Report(report.Terminal{}))
if runtime.GOOS != "windows" {
spec.Run(t, "unit-new-generator", testGeneratorFactory, spec.Report(report.Terminal{}))
spec.Run(t, "unit-generator", testGenerator, spec.Report(report.Terminal{}))
}
}
func testGeneratorFactory(t *testing.T, when spec.G, it spec.S) {

View File

@ -1,50 +0,0 @@
package platform_test
import (
"testing"
"github.com/apex/log"
"github.com/apex/log/handlers/memory"
"github.com/sclevine/spec"
"github.com/sclevine/spec/report"
"github.com/buildpacks/lifecycle/api"
llog "github.com/buildpacks/lifecycle/log"
"github.com/buildpacks/lifecycle/platform"
h "github.com/buildpacks/lifecycle/testhelpers"
)
func TestExportInputs(t *testing.T) {
for _, api := range api.Platform.Supported {
spec.Run(t, "unit-export-inputs/"+api.String(), testResolveExportInputs(api.String()), spec.Parallel(), spec.Report(report.Terminal{}))
}
}
func testResolveExportInputs(platformAPI string) func(t *testing.T, when spec.G, it spec.S) {
return func(t *testing.T, when spec.G, it spec.S) {
var (
inputs *platform.LifecycleInputs
logHandler *memory.Handler
logger llog.Logger
)
it.Before(func() {
inputs = platform.NewLifecycleInputs(api.MustParse(platformAPI))
inputs.OutputImageRef = "some-output-image" // satisfy validation
logHandler = memory.New()
logger = &log.Logger{Handler: logHandler}
inputs.UseDaemon = true // to prevent access checking of run images
})
when("output image ref is empty", func() {
it.Before(func() {
inputs.OutputImageRef = ""
})
it("errors properly", func() {
err := platform.ResolveInputs(platform.Export, inputs, logger)
h.AssertError(t, err, "image argument is required")
})
})
}
}

View File

@ -31,8 +31,8 @@ func ResolveInputs(phase LifecyclePhase, i *LifecycleInputs, logger log.Logger)
switch phase {
case Analyze:
ops = append(ops,
ValidateOutputImageProvided,
FillAnalyzeImages,
ValidateOutputImageProvided,
CheckLaunchCache,
ValidateImageRefs,
ValidateTargetsAreSameRegistry,
@ -42,8 +42,8 @@ func ResolveInputs(phase LifecyclePhase, i *LifecycleInputs, logger log.Logger)
// nop
case Create:
ops = append(ops,
ValidateOutputImageProvided,
FillCreateImages,
ValidateOutputImageProvided,
CheckCache,
CheckLaunchCache,
ValidateImageRefs,
@ -54,8 +54,8 @@ func ResolveInputs(phase LifecyclePhase, i *LifecycleInputs, logger log.Logger)
// nop
case Export:
ops = append(ops,
ValidateOutputImageProvided,
FillExportRunImage,
ValidateOutputImageProvided,
CheckCache,
CheckLaunchCache,
ValidateImageRefs,
@ -65,8 +65,8 @@ func ResolveInputs(phase LifecyclePhase, i *LifecycleInputs, logger log.Logger)
// nop
case Rebase:
ops = append(ops,
ValidateOutputImageProvided,
ValidateRebaseRunImage,
ValidateOutputImageProvided,
ValidateImageRefs,
ValidateTargetsAreSameRegistry,
)

View File

@ -116,7 +116,7 @@ func GetRunImageForExport(inputs LifecycleInputs) (files.RunImageForExport, erro
// GetRunImageFromMetadata extracts the run image from the image metadata
func GetRunImageFromMetadata(inputs LifecycleInputs, md files.LayersMetadata) (files.RunImageForExport, error) {
switch {
case inputs.PlatformAPI.AtLeast("0.12") && md.RunImage.Image != "":
case inputs.PlatformAPI.AtLeast("0.12") && md.RunImage.RunImageForExport.Image != "":
return md.RunImage.RunImageForExport, nil
case md.Stack != nil && md.Stack.RunImage.Image != "":
// for backwards compatibility, we need to fallback to the stack metadata

View File

@ -163,7 +163,7 @@ func testRunImage(t *testing.T, when spec.G, it spec.S) {
})
when("run image set in runImage metadata", func() {
md.RunImage.Image = "run-image-in-metadata"
md.RunImage.RunImageForExport.Image = "run-image-in-metadata"
it("returns the run image from runImage metadata", func() {
result, err := platform.GetRunImageFromMetadata(inputs, md)
@ -190,7 +190,7 @@ func testRunImage(t *testing.T, when spec.G, it spec.S) {
inputs.PlatformAPI = api.MustParse("0.11")
when("run image set in runImage metadata", func() {
md.RunImage.Image = "run-image-in-metadata"
md.RunImage.RunImageForExport.Image = "run-image-in-metadata"
it("fails to return at this platform version", func() {
result, err := platform.GetRunImageFromMetadata(inputs, md)

8
priv/sock_windows.go Normal file
View File

@ -0,0 +1,8 @@
package priv
import "net/url"
// shouldConnectSock is always false on windows
func shouldConnectSock(host *url.URL) bool {
return false
}

View File

@ -20,11 +20,10 @@ import (
var (
dockerCliOnce sync.Once
dockerCliVal dockercli.APIClient
dockerCliVal dockercli.CommonAPIClient
)
// DockerCli returns a new docker client
func DockerCli(t *testing.T) dockercli.APIClient {
func DockerCli(t *testing.T) dockercli.CommonAPIClient {
dockerCliOnce.Do(func() {
var dockerCliErr error
dockerCliVal, dockerCliErr = dockercli.NewClientWithOpts(dockercli.FromEnv, dockercli.WithVersion("1.38"))
@ -124,7 +123,7 @@ func PushImage(dockerCli dockercli.CommonAPIClient, ref string, auth string) err
return nil
}
// SeedDockerVolume only works with Linux daemons
// SeedDockerVolume only works with Linux daemons as Windows only mounts volumes for started containers
func SeedDockerVolume(t *testing.T, srcPath string) string {
volumeName := "test-volume-" + RandString(10)
containerName := "test-volume-helper-" + RandString(10)

View File

@ -0,0 +1,8 @@
package testhelpers
import "testing"
func GetUmask(t *testing.T) int {
// Not implemented on Windows
return 0
}

44
tools/Dockerfile.windows Normal file
View File

@ -0,0 +1,44 @@
ARG image_tag
FROM ${image_tag} as builder
SHELL ["cmd.exe", "/c"]
RUN mkdir c:\tools\bin && \
\
git clone --branch=v20.10.5 --depth=1 https://github.com/docker/cli c:\gopath\src\github.com\docker\cli && \
go get github.com/docker/cli/cmd/docker && \
go build -o c:\tools\bin\docker.exe github.com/docker/cli/cmd/docker && \
\
git clone --branch=v0.3.6 --depth=1 https://github.com/kyoh86/richgo c:\gopath\src\github.com\kyoh86\richgo && \
go get github.com/kyoh86/richgo && \
go build -o c:\tools\bin\richgo.exe github.com/kyoh86/richgo && \
\
curl.exe -o make-installer.exe -L "https://sourceforge.net/projects/gnuwin32/files/make/3.81/make-3.81.exe" && \
make-installer.exe /VERYSILENT /SUPPRESSMSGBOXES /NORESTART /DIR=C:\tools && \
del make-installer.exe && \
\
curl.exe -o c:\tools\bin\jq.exe -L "https://github.com/stedolan/jq/releases/download/jq-1.6/jq-win64.exe" && \
\
setx /M PATH "%PATH%;c:\tools\bin" && \
\
mklink C:\git\usr\bin\bash.exe sh.exe && \
\
git config --global core.autocrlf false && \
git config --global core.eol lf && \
git config --global core.symlinks true && \
\
rmdir /q /s c:\gopath\src
# For reusing dependencies `-v gopathcache:c:/gopath`
ENV GOCMD=richgo
ENV GOPATH=c:\\gopath
ENV GOBIN=c:\\gopath\\bin
VOLUME ["c:/gopath"]
WORKDIR /lifecycle
# Copy git directory for consistent filesystem duplication of source (docker build, cp, volumes are inconsistent with symlinks/hardlinks on Windows)
COPY . /lifecycle/.git
RUN git reset --hard HEAD

View File

@ -18,6 +18,7 @@ import (
"github.com/BurntSushi/toml"
"github.com/buildpacks/imgutil"
"github.com/buildpacks/imgutil/layer"
"github.com/buildpacks/imgutil/local"
"github.com/buildpacks/imgutil/remote"
dockercli "github.com/docker/docker/client"
@ -27,7 +28,8 @@ import (
)
const (
linuxBaseImage = "gcr.io/distroless/static"
linuxBaseImage = "gcr.io/distroless/static"
windowsBaseImage = "mcr.microsoft.com/windows/nanoserver:1809-amd64"
)
// commandline flags
@ -64,6 +66,9 @@ func main() {
}
baseImage := linuxBaseImage
if targetOS == "windows" {
baseImage = windowsBaseImage
}
var img imgutil.Image
if useDaemon {
@ -82,9 +87,6 @@ func main() {
if daemonArch == "x86_64" {
daemonArch = "amd64"
}
if daemonArch == "aarch64" {
daemonArch = "arm64"
}
if daemonArch != targetArch {
log.Fatal("Target architecture and daemon architecture must match")
}
@ -138,6 +140,9 @@ func main() {
return
}
workDir := "/layers"
if targetOS == "windows" {
workDir = `c:\layers`
}
if err := img.SetWorkingDir(workDir); err != nil {
log.Print("Failed to set working directory:", err)
return
@ -272,12 +277,22 @@ func lifecycleLayer() (string, error) {
var ntw *archive.NormalizingTarWriter
var mode int64
ntw = archive.NewNormalizingTarWriter(tar.NewWriter(lf))
mode = 0755
if targetOS == "windows" {
ntw = archive.NewNormalizingTarWriter(layer.NewWindowsWriter(lf))
mode = 0777
} else {
ntw = archive.NewNormalizingTarWriter(tar.NewWriter(lf))
mode = 0755
}
ntw.WithModTime(archive.NormalizedModTime)
ntw.WithUID(0)
ntw.WithGID(0)
if targetOS == "windows" {
ntw.WithUID(1) // gets translated to user permissions in windows writer
ntw.WithGID(1) // gets translated to user permissions in windows writer
} else {
ntw.WithUID(0)
ntw.WithGID(0)
}
if err := ntw.WriteHeader(&tar.Header{
Typeflag: tar.TypeDir,
Name: "/cnb",