mirror of https://github.com/buildpacks/libcnb.git
Compare commits
85 Commits
Author | SHA1 | Date |
---|---|---|
|
f521f92a09 | |
|
e20ec30ee2 | |
|
18b4f48b23 | |
|
7f587a123f | |
|
77105e2978 | |
|
d17194071c | |
|
9021dcf494 | |
|
51c0f7c9d8 | |
|
053fa626f2 | |
|
e9faf69a99 | |
|
60df949646 | |
|
8b7f79763b | |
|
09cee453ad | |
|
0f3d43690e | |
|
fb77fe5013 | |
|
539663a25f | |
|
8660a07c64 | |
|
25ba7f615a | |
|
6305526cf8 | |
|
2e5389a593 | |
|
587c59c91d | |
|
5f657b5cfc | |
|
6e3b073b7b | |
|
52e0183e1d | |
|
2f1a4584c6 | |
|
963d4bea64 | |
|
71c0efab66 | |
|
3d9ff459f6 | |
|
abe34281b4 | |
|
8b98521c54 | |
|
e4b66fbc45 | |
|
5c5fb9c447 | |
|
69cca5e154 | |
|
a9e6d2093f | |
|
31fc6e39de | |
|
8ebd909600 | |
|
fdc1649156 | |
|
e2931f6866 | |
|
007e6d2e3e | |
|
0df8dedcfe | |
|
7ed3dc927c | |
|
bc167dd856 | |
|
714a5dc525 | |
|
fdc252a082 | |
|
62037e0b7a | |
|
6a941db0cd | |
|
f496f6e88d | |
|
86ede05ba0 | |
|
d3e6e18255 | |
|
a0a8708c7a | |
|
53c63e4082 | |
|
df6fd7ca71 | |
|
d91d62a519 | |
|
4d89af3641 | |
|
f4abcd0215 | |
|
f04e4e2cf2 | |
|
49c96bbc6c | |
|
2d891dd194 | |
|
896a63b4e3 | |
|
45c858b545 | |
|
c9ad73fb07 | |
|
f869c1d937 | |
|
4a789fd9d2 | |
|
d67b3ffea7 | |
|
128cdb9dbe | |
|
77cb45d022 | |
|
a33b3677ad | |
|
6d86013d6d | |
|
66f3e9f328 | |
|
6aa81e5081 | |
|
c06fd640c3 | |
|
50e1696404 | |
|
9cb9a2095d | |
|
0376b8a285 | |
|
9af239f94c | |
|
70403787d2 | |
|
ced0ee6ed8 | |
|
c564678891 | |
|
5c9b56b7c6 | |
|
8c10662dc7 | |
|
b2b6b4b4c1 | |
|
33e2828ac7 | |
|
a965403652 | |
|
bc6e6ca9a4 | |
|
536a77887b |
|
@ -4,6 +4,8 @@ updates:
|
||||||
directory: /
|
directory: /
|
||||||
schedule:
|
schedule:
|
||||||
interval: daily
|
interval: daily
|
||||||
|
ignore:
|
||||||
|
- dependency-name: github.com/onsi/gomega
|
||||||
labels:
|
labels:
|
||||||
- semver:patch
|
- semver:patch
|
||||||
- type:dependency-upgrade
|
- type:dependency-upgrade
|
||||||
|
|
|
@ -25,3 +25,18 @@
|
||||||
- name: type:task
|
- name: type:task
|
||||||
description: A general task
|
description: A general task
|
||||||
color: e3d9fc
|
color: e3d9fc
|
||||||
|
- name: type:informational
|
||||||
|
description: Provides information or notice to the community
|
||||||
|
color: e3d9fc
|
||||||
|
- name: type:poll
|
||||||
|
description: Request for feedback from the community
|
||||||
|
color: e3d9fc
|
||||||
|
- name: note:ideal-for-contribution
|
||||||
|
description: An issue that a contributor can help us with
|
||||||
|
color: 54f7a8
|
||||||
|
- name: note:on-hold
|
||||||
|
description: We can't start working on this issue yet
|
||||||
|
color: 54f7a8
|
||||||
|
- name: note:good-first-issue
|
||||||
|
description: A good first issue to get started with
|
||||||
|
color: 54f7a8
|
||||||
|
|
|
@ -14,7 +14,19 @@ test:
|
||||||
|
|
||||||
set -euo pipefail
|
set -euo pipefail
|
||||||
|
|
||||||
GO111MODULE=on go get -u -ldflags="-s -w" github.com/kyoh86/richgo
|
echo "Installing richgo ${RICHGO_VERSION}"
|
||||||
|
|
||||||
|
mkdir -p "${HOME}"/bin
|
||||||
|
echo "${HOME}/bin" >> "${GITHUB_PATH}"
|
||||||
|
|
||||||
|
curl \
|
||||||
|
--location \
|
||||||
|
--show-error \
|
||||||
|
--silent \
|
||||||
|
"https://github.com/kyoh86/richgo/releases/download/v${RICHGO_VERSION}/richgo_${RICHGO_VERSION}_linux_amd64.tar.gz" \
|
||||||
|
| tar -C "${HOME}"/bin -xz richgo
|
||||||
|
env:
|
||||||
|
RICHGO_VERSION: 0.3.10
|
||||||
- name: Run Tests
|
- name: Run Tests
|
||||||
run: |
|
run: |
|
||||||
#!/usr/bin/env bash
|
#!/usr/bin/env bash
|
||||||
|
|
|
@ -1 +1 @@
|
||||||
1.12.0
|
1.37.5
|
||||||
|
|
|
@ -1 +0,0 @@
|
||||||
1.4.0
|
|
|
@ -12,7 +12,7 @@ jobs:
|
||||||
runs-on:
|
runs-on:
|
||||||
- ubuntu-latest
|
- ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: mheap/github-action-required-labels@v1
|
- uses: mheap/github-action-required-labels@v5
|
||||||
with:
|
with:
|
||||||
count: 1
|
count: 1
|
||||||
labels: semver:major, semver:minor, semver:patch
|
labels: semver:major, semver:minor, semver:patch
|
||||||
|
@ -22,7 +22,7 @@ jobs:
|
||||||
runs-on:
|
runs-on:
|
||||||
- ubuntu-latest
|
- ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: mheap/github-action-required-labels@v1
|
- uses: mheap/github-action-required-labels@v5
|
||||||
with:
|
with:
|
||||||
count: 1
|
count: 1
|
||||||
labels: type:bug, type:dependency-upgrade, type:documentation, type:enhancement, type:question, type:task
|
labels: type:bug, type:dependency-upgrade, type:documentation, type:enhancement, type:question, type:task
|
|
@ -2,7 +2,7 @@ name: Synchronize Labels
|
||||||
"on":
|
"on":
|
||||||
push:
|
push:
|
||||||
branches:
|
branches:
|
||||||
- main
|
- release-1.x
|
||||||
paths:
|
paths:
|
||||||
- .github/labels.yml
|
- .github/labels.yml
|
||||||
jobs:
|
jobs:
|
||||||
|
@ -11,7 +11,7 @@ jobs:
|
||||||
runs-on:
|
runs-on:
|
||||||
- ubuntu-latest
|
- ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v2
|
- uses: actions/checkout@v4
|
||||||
- uses: micnncim/action-label-syncer@v1
|
- uses: micnncim/action-label-syncer@v1
|
||||||
env:
|
env:
|
||||||
GITHUB_TOKEN: ${{ secrets.IMPLEMENTATION_GITHUB_TOKEN }}
|
GITHUB_TOKEN: ${{ secrets.IMPLEMENTATION_GITHUB_TOKEN }}
|
|
@ -0,0 +1,54 @@
|
||||||
|
name: Tests
|
||||||
|
"on":
|
||||||
|
merge_group:
|
||||||
|
types:
|
||||||
|
- checks_requested
|
||||||
|
branches:
|
||||||
|
- main
|
||||||
|
pull_request: {}
|
||||||
|
push:
|
||||||
|
branches:
|
||||||
|
- release-1.x
|
||||||
|
jobs:
|
||||||
|
unit:
|
||||||
|
name: Unit Test
|
||||||
|
runs-on:
|
||||||
|
- ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
- uses: actions/cache@v4
|
||||||
|
with:
|
||||||
|
key: ${{ runner.os }}-go-${{ hashFiles('**/go.sum') }}
|
||||||
|
path: ${{ env.HOME }}/go/pkg/mod
|
||||||
|
restore-keys: ${{ runner.os }}-go-
|
||||||
|
- uses: actions/setup-go@v5
|
||||||
|
with:
|
||||||
|
go-version: "1.20"
|
||||||
|
- name: Install richgo
|
||||||
|
run: |
|
||||||
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
echo "Installing richgo ${RICHGO_VERSION}"
|
||||||
|
|
||||||
|
mkdir -p "${HOME}"/bin
|
||||||
|
echo "${HOME}/bin" >> "${GITHUB_PATH}"
|
||||||
|
|
||||||
|
curl \
|
||||||
|
--location \
|
||||||
|
--show-error \
|
||||||
|
--silent \
|
||||||
|
"https://github.com/kyoh86/richgo/releases/download/v${RICHGO_VERSION}/richgo_${RICHGO_VERSION}_linux_amd64.tar.gz" \
|
||||||
|
| tar -C "${HOME}"/bin -xz richgo
|
||||||
|
env:
|
||||||
|
RICHGO_VERSION: 0.3.10
|
||||||
|
- name: Run Tests
|
||||||
|
run: |
|
||||||
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
GOCMD=richgo make
|
||||||
|
env:
|
||||||
|
RICHGO_FORCE_COLOR: "1"
|
|
@ -2,7 +2,7 @@ name: Update Draft Release
|
||||||
"on":
|
"on":
|
||||||
push:
|
push:
|
||||||
branches:
|
branches:
|
||||||
- main
|
- release-1.x
|
||||||
jobs:
|
jobs:
|
||||||
update:
|
update:
|
||||||
name: Update Draft Release
|
name: Update Draft Release
|
|
@ -0,0 +1,72 @@
|
||||||
|
name: Update Go
|
||||||
|
"on":
|
||||||
|
schedule:
|
||||||
|
- cron: 17 2 * * 1
|
||||||
|
workflow_dispatch: {}
|
||||||
|
jobs:
|
||||||
|
update:
|
||||||
|
name: Update Go
|
||||||
|
runs-on:
|
||||||
|
- ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- uses: actions/setup-go@v5
|
||||||
|
with:
|
||||||
|
go-version: "1.20"
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
- name: Update Go Version & Modules
|
||||||
|
id: update-go
|
||||||
|
run: |
|
||||||
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
if [ -z "${GO_VERSION:-}" ]; then
|
||||||
|
echo "No go version set"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
OLD_GO_VERSION=$(grep -P '^go \d\.\d+' go.mod | cut -d ' ' -f 2)
|
||||||
|
|
||||||
|
go mod edit -go="$GO_VERSION"
|
||||||
|
go mod tidy
|
||||||
|
go get -u -t ./...
|
||||||
|
go mod tidy
|
||||||
|
|
||||||
|
git add go.mod go.sum
|
||||||
|
git checkout -- .
|
||||||
|
|
||||||
|
if [ "$OLD_GO_VERSION" == "$GO_VERSION" ]; then
|
||||||
|
COMMIT_TITLE="Bump Go Modules"
|
||||||
|
COMMIT_BODY="Bumps Go modules used by the project. See the commit for details on what modules were updated."
|
||||||
|
COMMIT_SEMVER="semver:patch"
|
||||||
|
else
|
||||||
|
COMMIT_TITLE="Bump Go from ${OLD_GO_VERSION} to ${GO_VERSION}"
|
||||||
|
COMMIT_BODY="Bumps Go from ${OLD_GO_VERSION} to ${GO_VERSION} and update Go modules used by the project. See the commit for details on what modules were updated."
|
||||||
|
COMMIT_SEMVER="semver:minor"
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "commit-title=${COMMIT_TITLE}" >> "$GITHUB_OUTPUT"
|
||||||
|
echo "commit-body=${COMMIT_BODY}" >> "$GITHUB_OUTPUT"
|
||||||
|
echo "commit-semver=${COMMIT_SEMVER}" >> "$GITHUB_OUTPUT"
|
||||||
|
env:
|
||||||
|
GO_VERSION: "1.20"
|
||||||
|
- uses: peter-evans/create-pull-request@v6
|
||||||
|
with:
|
||||||
|
author: ${{ secrets.IMPLEMENTATION_GITHUB_USERNAME }} <${{ secrets.IMPLEMENTATION_GITHUB_USERNAME }}@users.noreply.github.com>
|
||||||
|
body: |-
|
||||||
|
${{ steps.update-go.outputs.commit-body }}
|
||||||
|
|
||||||
|
<details>
|
||||||
|
<summary>Release Notes</summary>
|
||||||
|
${{ steps.pipeline.outputs.release-notes }}
|
||||||
|
</details>
|
||||||
|
branch: update/go
|
||||||
|
commit-message: |-
|
||||||
|
${{ steps.update-go.outputs.commit-title }}
|
||||||
|
|
||||||
|
${{ steps.update-go.outputs.commit-body }}
|
||||||
|
delete-branch: true
|
||||||
|
labels: ${{ steps.update-go.outputs.commit-semver }}, type:task
|
||||||
|
signoff: true
|
||||||
|
title: ${{ steps.update-go.outputs.commit-title }}
|
||||||
|
token: ${{ secrets.IMPLEMENTATION_GITHUB_TOKEN }}
|
|
@ -2,7 +2,7 @@ name: Update Pipeline
|
||||||
"on":
|
"on":
|
||||||
push:
|
push:
|
||||||
branches:
|
branches:
|
||||||
- main
|
- release-1.x
|
||||||
paths:
|
paths:
|
||||||
- .github/pipeline-descriptor.yml
|
- .github/pipeline-descriptor.yml
|
||||||
schedule:
|
schedule:
|
||||||
|
@ -14,17 +14,17 @@ jobs:
|
||||||
runs-on:
|
runs-on:
|
||||||
- ubuntu-latest
|
- ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/setup-go@v2
|
- uses: actions/setup-go@v5
|
||||||
with:
|
with:
|
||||||
go-version: "1.16"
|
go-version: "1.20"
|
||||||
- name: Install octo
|
- name: Install octo
|
||||||
run: |
|
run: |
|
||||||
#!/usr/bin/env bash
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
set -euo pipefail
|
set -euo pipefail
|
||||||
|
|
||||||
GO111MODULE=on go get -u -ldflags="-s -w" github.com/paketo-buildpacks/pipeline-builder/cmd/octo
|
go install -ldflags="-s -w" github.com/paketo-buildpacks/pipeline-builder/cmd/octo@latest
|
||||||
- uses: actions/checkout@v2
|
- uses: actions/checkout@v4
|
||||||
- name: Update Pipeline
|
- name: Update Pipeline
|
||||||
id: pipeline
|
id: pipeline
|
||||||
run: |
|
run: |
|
||||||
|
@ -38,6 +38,7 @@ jobs:
|
||||||
OLD_VERSION="0.0.0"
|
OLD_VERSION="0.0.0"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
rm .github/workflows/pb-*.yml || true
|
||||||
octo --descriptor "${DESCRIPTOR}"
|
octo --descriptor "${DESCRIPTOR}"
|
||||||
|
|
||||||
PAYLOAD=$(gh api /repos/paketo-buildpacks/pipeline-builder/releases/latest)
|
PAYLOAD=$(gh api /repos/paketo-buildpacks/pipeline-builder/releases/latest)
|
||||||
|
@ -54,15 +55,23 @@ jobs:
|
||||||
)
|
)
|
||||||
|
|
||||||
git add .github/
|
git add .github/
|
||||||
|
git add .gitignore
|
||||||
|
|
||||||
|
if [ -f scripts/build.sh ]; then
|
||||||
|
git add scripts/build.sh
|
||||||
|
fi
|
||||||
|
|
||||||
git checkout -- .
|
git checkout -- .
|
||||||
|
|
||||||
echo "::set-output name=old-version::${OLD_VERSION}"
|
echo "old-version=${OLD_VERSION}" >> "$GITHUB_OUTPUT"
|
||||||
echo "::set-output name=new-version::${NEW_VERSION}"
|
echo "new-version=${NEW_VERSION}" >> "$GITHUB_OUTPUT"
|
||||||
echo "::set-output name=release-notes::${RELEASE_NOTES//$'\n'/%0A}"
|
|
||||||
|
DELIMITER=$(openssl rand -hex 16) # roughly the same entropy as uuid v4 used in https://github.com/actions/toolkit/blob/b36e70495fbee083eb20f600eafa9091d832577d/packages/core/src/file-command.ts#L28
|
||||||
|
printf "release-notes<<%s\n%s\n%s\n" "${DELIMITER}" "${RELEASE_NOTES}" "${DELIMITER}" >> "${GITHUB_OUTPUT}" # see https://docs.github.com/en/actions/using-workflows/workflow-commands-for-github-actions#multiline-strings
|
||||||
env:
|
env:
|
||||||
DESCRIPTOR: .github/pipeline-descriptor.yml
|
DESCRIPTOR: .github/pipeline-descriptor.yml
|
||||||
GITHUB_TOKEN: ${{ secrets.IMPLEMENTATION_GITHUB_TOKEN }}
|
GITHUB_TOKEN: ${{ secrets.IMPLEMENTATION_GITHUB_TOKEN }}
|
||||||
- uses: peter-evans/create-pull-request@v3
|
- uses: peter-evans/create-pull-request@v6
|
||||||
with:
|
with:
|
||||||
author: ${{ secrets.IMPLEMENTATION_GITHUB_USERNAME }} <${{ secrets.IMPLEMENTATION_GITHUB_USERNAME }}@users.noreply.github.com>
|
author: ${{ secrets.IMPLEMENTATION_GITHUB_USERNAME }} <${{ secrets.IMPLEMENTATION_GITHUB_USERNAME }}@users.noreply.github.com>
|
||||||
body: |-
|
body: |-
|
|
@ -1,37 +0,0 @@
|
||||||
name: Tests
|
|
||||||
"on":
|
|
||||||
pull_request: {}
|
|
||||||
push:
|
|
||||||
branches:
|
|
||||||
- main
|
|
||||||
jobs:
|
|
||||||
unit:
|
|
||||||
name: Unit Test
|
|
||||||
runs-on:
|
|
||||||
- ubuntu-latest
|
|
||||||
steps:
|
|
||||||
- uses: actions/checkout@v2
|
|
||||||
- uses: actions/cache@v2
|
|
||||||
with:
|
|
||||||
key: ${{ runner.os }}-go-${{ hashFiles('**/go.sum') }}
|
|
||||||
path: ${{ env.HOME }}/go/pkg/mod
|
|
||||||
restore-keys: ${{ runner.os }}-go-
|
|
||||||
- uses: actions/setup-go@v2
|
|
||||||
with:
|
|
||||||
go-version: "1.16"
|
|
||||||
- name: Install richgo
|
|
||||||
run: |
|
|
||||||
#!/usr/bin/env bash
|
|
||||||
|
|
||||||
set -euo pipefail
|
|
||||||
|
|
||||||
GO111MODULE=on go get -u -ldflags="-s -w" github.com/kyoh86/richgo
|
|
||||||
- name: Run Tests
|
|
||||||
run: |
|
|
||||||
#!/usr/bin/env bash
|
|
||||||
|
|
||||||
set -euo pipefail
|
|
||||||
|
|
||||||
GOCMD=richgo make
|
|
||||||
env:
|
|
||||||
RICHGO_FORCE_COLOR: "1"
|
|
|
@ -18,6 +18,10 @@ go get github.com/buildpacks/libcnb
|
||||||
|
|
||||||
https://pkg.go.dev/github.com/buildpacks/libcnb?tab=doc
|
https://pkg.go.dev/github.com/buildpacks/libcnb?tab=doc
|
||||||
|
|
||||||
|
#### Tutorial
|
||||||
|
|
||||||
|
[docs/tutorial.md](docs/tutorial.md)
|
||||||
|
|
||||||
## License
|
## License
|
||||||
This library is released under version 2.0 of the [Apache License][a].
|
This library is released under version 2.0 of the [Apache License][a].
|
||||||
|
|
||||||
|
|
|
@ -48,6 +48,9 @@ type Process struct {
|
||||||
// Command is exec'd directly by the os (no profile.d scripts run)
|
// Command is exec'd directly by the os (no profile.d scripts run)
|
||||||
Direct bool `toml:"direct,omitempty"`
|
Direct bool `toml:"direct,omitempty"`
|
||||||
|
|
||||||
|
// WorkingDirectory is a directory to execute the command in, removes the need to use a shell environment to CD into working directory
|
||||||
|
WorkingDirectory string `toml:"working-dir,omitempty"`
|
||||||
|
|
||||||
// Default can be set to true to indicate that the process
|
// Default can be set to true to indicate that the process
|
||||||
// type being defined should be the default process type for the app image.
|
// type being defined should be the default process type for the app image.
|
||||||
Default bool `toml:"default,omitempty"`
|
Default bool `toml:"default,omitempty"`
|
||||||
|
@ -73,6 +76,8 @@ type LaunchTOML struct {
|
||||||
Slices []Slice `toml:"slices"`
|
Slices []Slice `toml:"slices"`
|
||||||
|
|
||||||
// BOM is a collection of entries for the bill of materials.
|
// BOM is a collection of entries for the bill of materials.
|
||||||
|
//
|
||||||
|
// Deprecated: as of Buildpack API 0.7, write to `layer.BOMPath()` instead
|
||||||
BOM []BOMEntry `toml:"bom"`
|
BOM []BOMEntry `toml:"bom"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -83,6 +88,8 @@ func (l LaunchTOML) isEmpty() bool {
|
||||||
// BuildTOML represents the contents of build.toml.
|
// BuildTOML represents the contents of build.toml.
|
||||||
type BuildTOML struct {
|
type BuildTOML struct {
|
||||||
// BOM contains the build-time bill of materials.
|
// BOM contains the build-time bill of materials.
|
||||||
|
//
|
||||||
|
// Deprecated: as of Buildpack API 0.7, write to `layer.BOMPath()` instead
|
||||||
BOM []BOMEntry `toml:"bom"`
|
BOM []BOMEntry `toml:"bom"`
|
||||||
|
|
||||||
// Unmet is a collection of buildpack plan entries that should be passed through to subsequent providers.
|
// Unmet is a collection of buildpack plan entries that should be passed through to subsequent providers.
|
||||||
|
@ -94,6 +101,8 @@ func (b BuildTOML) isEmpty() bool {
|
||||||
}
|
}
|
||||||
|
|
||||||
// BOMEntry contains a bill of materials entry.
|
// BOMEntry contains a bill of materials entry.
|
||||||
|
//
|
||||||
|
// Deprecated: as of Buildpack API 0.7, BOM should use standard formats like CycloneDX going forward
|
||||||
type BOMEntry struct {
|
type BOMEntry struct {
|
||||||
// Name represents the name of the entry.
|
// Name represents the name of the entry.
|
||||||
Name string `toml:"name"`
|
Name string `toml:"name"`
|
||||||
|
|
122
build.go
122
build.go
|
@ -25,6 +25,7 @@ import (
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/BurntSushi/toml"
|
"github.com/BurntSushi/toml"
|
||||||
|
"github.com/Masterminds/semver/v3"
|
||||||
|
|
||||||
"github.com/buildpacks/libcnb/internal"
|
"github.com/buildpacks/libcnb/internal"
|
||||||
"github.com/buildpacks/libcnb/poet"
|
"github.com/buildpacks/libcnb/poet"
|
||||||
|
@ -58,6 +59,8 @@ type BuildContext struct {
|
||||||
// BuildResult contains the results of detection.
|
// BuildResult contains the results of detection.
|
||||||
type BuildResult struct {
|
type BuildResult struct {
|
||||||
// BOM contains entries to be appended to the app image Bill of Materials and/or build Bill of Materials.
|
// BOM contains entries to be appended to the app image Bill of Materials and/or build Bill of Materials.
|
||||||
|
//
|
||||||
|
// Deprecated: as of Buildpack API 0.7, write to `layer.BOMPath()` instead
|
||||||
BOM *BOM
|
BOM *BOM
|
||||||
|
|
||||||
// Labels are the image labels contributed by the buildpack.
|
// Labels are the image labels contributed by the buildpack.
|
||||||
|
@ -81,10 +84,21 @@ type BuildResult struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
// BOM contains all Bill of Materials entries
|
// BOM contains all Bill of Materials entries
|
||||||
|
//
|
||||||
|
// Deprecated: as of Buildpack API 0.7, write to `layer.BOMPath()` instead
|
||||||
type BOM struct {
|
type BOM struct {
|
||||||
Entries []BOMEntry
|
Entries []BOMEntry
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Constants to track minimum and maximum supported Buildpack API versions
|
||||||
|
const (
|
||||||
|
// MinSupportedBPVersion indicates the minium supported version of the Buildpacks API
|
||||||
|
MinSupportedBPVersion = "0.5"
|
||||||
|
|
||||||
|
// MaxSupportedBPVersion indicates the maximum supported version of the Buildpacks API
|
||||||
|
MaxSupportedBPVersion = "0.8"
|
||||||
|
)
|
||||||
|
|
||||||
// NewBuildResult creates a new BuildResult instance, initializing empty fields.
|
// NewBuildResult creates a new BuildResult instance, initializing empty fields.
|
||||||
func NewBuildResult() BuildResult {
|
func NewBuildResult() BuildResult {
|
||||||
return BuildResult{
|
return BuildResult{
|
||||||
|
@ -118,6 +132,7 @@ type Builder interface {
|
||||||
func Build(builder Builder, options ...Option) {
|
func Build(builder Builder, options ...Option) {
|
||||||
config := Config{
|
config := Config{
|
||||||
arguments: os.Args,
|
arguments: os.Args,
|
||||||
|
bomLabel: false,
|
||||||
environmentWriter: internal.EnvironmentWriter{},
|
environmentWriter: internal.EnvironmentWriter{},
|
||||||
exitHandler: internal.NewExitHandler(),
|
exitHandler: internal.NewExitHandler(),
|
||||||
tomlWriter: internal.TOMLWriter{},
|
tomlWriter: internal.TOMLWriter{},
|
||||||
|
@ -127,11 +142,6 @@ func Build(builder Builder, options ...Option) {
|
||||||
config = option(config)
|
config = option(config)
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(config.arguments) != 4 {
|
|
||||||
config.exitHandler.Error(fmt.Errorf("expected 3 arguments and received %d", len(config.arguments)-1))
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
var (
|
var (
|
||||||
err error
|
err error
|
||||||
file string
|
file string
|
||||||
|
@ -165,16 +175,47 @@ func Build(builder Builder, options ...Option) {
|
||||||
}
|
}
|
||||||
logger.Debugf("Buildpack: %+v", ctx.Buildpack)
|
logger.Debugf("Buildpack: %+v", ctx.Buildpack)
|
||||||
|
|
||||||
API := strings.TrimSpace(ctx.Buildpack.API)
|
API, err := semver.NewVersion(ctx.Buildpack.API)
|
||||||
if API != "0.5" && API != "0.6" {
|
if err != nil {
|
||||||
config.exitHandler.Error(errors.New("this version of libcnb is only compatible with buildpack APIs 0.5 and 0.6"))
|
config.exitHandler.Error(errors.New("version cannot be parsed"))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
ctx.Layers = Layers{config.arguments[1]}
|
compatVersionCheck, _ := semver.NewConstraint(fmt.Sprintf(">= %s, <= %s", MinSupportedBPVersion, MaxSupportedBPVersion))
|
||||||
logger.Debugf("Layers: %+v", ctx.Layers)
|
if !compatVersionCheck.Check(API) {
|
||||||
|
config.exitHandler.Error(fmt.Errorf("this version of libcnb is only compatible with buildpack APIs >= %s, <= %s", MinSupportedBPVersion, MaxSupportedBPVersion))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
var buildpackPlanPath string
|
||||||
|
|
||||||
ctx.Platform.Path = config.arguments[2]
|
if API.LessThan(semver.MustParse("0.8")) {
|
||||||
|
if len(config.arguments) != 4 {
|
||||||
|
config.exitHandler.Error(fmt.Errorf("expected 3 arguments and received %d", len(config.arguments)-1))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
ctx.Layers = Layers{config.arguments[1]}
|
||||||
|
ctx.Platform.Path = config.arguments[2]
|
||||||
|
buildpackPlanPath = config.arguments[3]
|
||||||
|
} else {
|
||||||
|
layersDir, ok := os.LookupEnv("CNB_LAYERS_DIR")
|
||||||
|
if !ok {
|
||||||
|
config.exitHandler.Error(fmt.Errorf("expected CNB_LAYERS_DIR to be set"))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
ctx.Layers = Layers{layersDir}
|
||||||
|
ctx.Platform.Path, ok = os.LookupEnv("CNB_PLATFORM_DIR")
|
||||||
|
if !ok {
|
||||||
|
config.exitHandler.Error(fmt.Errorf("expected CNB_PLATFORM_DIR to be set"))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
buildpackPlanPath, ok = os.LookupEnv("CNB_BP_PLAN_PATH")
|
||||||
|
if !ok {
|
||||||
|
config.exitHandler.Error(fmt.Errorf("expected CNB_BP_PLAN_PATH to be set"))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
logger.Debugf("Layers: %+v", ctx.Layers)
|
||||||
if logger.IsDebugEnabled() {
|
if logger.IsDebugEnabled() {
|
||||||
logger.Debug(PlatformFormatter(ctx.Platform))
|
logger.Debug(PlatformFormatter(ctx.Platform))
|
||||||
}
|
}
|
||||||
|
@ -201,18 +242,17 @@ func Build(builder Builder, options ...Option) {
|
||||||
ctx.PersistentMetadata = store.Metadata
|
ctx.PersistentMetadata = store.Metadata
|
||||||
logger.Debugf("Persistent Metadata: %+v", ctx.PersistentMetadata)
|
logger.Debugf("Persistent Metadata: %+v", ctx.PersistentMetadata)
|
||||||
|
|
||||||
file = config.arguments[3]
|
if _, err = toml.DecodeFile(buildpackPlanPath, &ctx.Plan); err != nil && !os.IsNotExist(err) {
|
||||||
if _, err = toml.DecodeFile(file, &ctx.Plan); err != nil && !os.IsNotExist(err) {
|
config.exitHandler.Error(fmt.Errorf("unable to decode buildpack plan %s\n%w", buildpackPlanPath, err))
|
||||||
config.exitHandler.Error(fmt.Errorf("unable to decode buildpack plan %s\n%w", file, err))
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
logger.Debugf("Buildpack Plan: %+v", ctx.Plan)
|
logger.Debugf("Buildpack Plan: %+v", ctx.Plan)
|
||||||
|
|
||||||
if ctx.StackID, ok = os.LookupEnv("CNB_STACK_ID"); !ok {
|
if ctx.StackID, ok = os.LookupEnv("CNB_STACK_ID"); !ok {
|
||||||
config.exitHandler.Error(fmt.Errorf("CNB_STACK_ID not set"))
|
logger.Debug("CNB_STACK_ID not set")
|
||||||
return
|
} else {
|
||||||
|
logger.Debugf("Stack: %s", ctx.StackID)
|
||||||
}
|
}
|
||||||
logger.Debugf("Stack: %s", ctx.StackID)
|
|
||||||
|
|
||||||
result, err := builder.Build(ctx)
|
result, err := builder.Build(ctx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -274,7 +314,7 @@ func Build(builder Builder, options ...Option) {
|
||||||
file = filepath.Join(ctx.Layers.Path, fmt.Sprintf("%s.toml", layer.Name))
|
file = filepath.Join(ctx.Layers.Path, fmt.Sprintf("%s.toml", layer.Name))
|
||||||
logger.Debugf("Writing layer metadata: %s <= %+v", file, layer)
|
logger.Debugf("Writing layer metadata: %s <= %+v", file, layer)
|
||||||
var toWrite interface{} = layer
|
var toWrite interface{} = layer
|
||||||
if API == "0.5" {
|
if API.Equal(semver.MustParse("0.5")) {
|
||||||
toWrite = internal.LayerAPI5{
|
toWrite = internal.LayerAPI5{
|
||||||
Build: layer.LayerTypes.Build,
|
Build: layer.LayerTypes.Build,
|
||||||
Cache: layer.LayerTypes.Cache,
|
Cache: layer.LayerTypes.Cache,
|
||||||
|
@ -302,8 +342,16 @@ func Build(builder Builder, options ...Option) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if API.GreaterThan(semver.MustParse("0.7")) || API.Equal(semver.MustParse("0.7")) {
|
||||||
|
if err := validateSBOMFormats(ctx.Layers.Path, ctx.Buildpack.Info.SBOMFormats); err != nil {
|
||||||
|
config.exitHandler.Error(fmt.Errorf("unable to validate SBOM\n%w", err))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Deprecated: as of Buildpack API 0.7, to be removed in a future version
|
||||||
var launchBOM, buildBOM []BOMEntry
|
var launchBOM, buildBOM []BOMEntry
|
||||||
if result.BOM != nil {
|
if result.BOM != nil && config.bomLabel {
|
||||||
for _, entry := range result.BOM.Entries {
|
for _, entry := range result.BOM.Entries {
|
||||||
if entry.Launch {
|
if entry.Launch {
|
||||||
launchBOM = append(launchBOM, entry)
|
launchBOM = append(launchBOM, entry)
|
||||||
|
@ -325,7 +373,7 @@ func Build(builder Builder, options ...Option) {
|
||||||
file = filepath.Join(ctx.Layers.Path, "launch.toml")
|
file = filepath.Join(ctx.Layers.Path, "launch.toml")
|
||||||
logger.Debugf("Writing application metadata: %s <= %+v", file, launch)
|
logger.Debugf("Writing application metadata: %s <= %+v", file, launch)
|
||||||
|
|
||||||
if API == "0.5" {
|
if API.LessThan(semver.MustParse("0.6")) {
|
||||||
for _, process := range launch.Processes {
|
for _, process := range launch.Processes {
|
||||||
if process.Default {
|
if process.Default {
|
||||||
logger.Info("WARNING: Launch layer is setting default=true, but that is not supported until API version 0.6. This setting will be ignored.")
|
logger.Info("WARNING: Launch layer is setting default=true, but that is not supported until API version 0.6. This setting will be ignored.")
|
||||||
|
@ -333,6 +381,15 @@ func Build(builder Builder, options ...Option) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if API.LessThan(semver.MustParse("0.8")) {
|
||||||
|
for i, process := range launch.Processes {
|
||||||
|
if process.WorkingDirectory != "" {
|
||||||
|
logger.Infof("WARNING: Launch layer is setting working-directory=%s, but that is not supported until API version 0.8. This setting will be ignored.", process.WorkingDirectory)
|
||||||
|
launch.Processes[i].WorkingDirectory = ""
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if err = config.tomlWriter.Write(file, launch); err != nil {
|
if err = config.tomlWriter.Write(file, launch); err != nil {
|
||||||
config.exitHandler.Error(fmt.Errorf("unable to write application metadata %s\n%w", file, err))
|
config.exitHandler.Error(fmt.Errorf("unable to write application metadata %s\n%w", file, err))
|
||||||
return
|
return
|
||||||
|
@ -347,6 +404,7 @@ func Build(builder Builder, options ...Option) {
|
||||||
if !build.isEmpty() {
|
if !build.isEmpty() {
|
||||||
file = filepath.Join(ctx.Layers.Path, "build.toml")
|
file = filepath.Join(ctx.Layers.Path, "build.toml")
|
||||||
logger.Debugf("Writing build metadata: %s <= %+v", file, build)
|
logger.Debugf("Writing build metadata: %s <= %+v", file, build)
|
||||||
|
|
||||||
if err = config.tomlWriter.Write(file, build); err != nil {
|
if err = config.tomlWriter.Write(file, build); err != nil {
|
||||||
config.exitHandler.Error(fmt.Errorf("unable to write build metadata %s\n%w", file, err))
|
config.exitHandler.Error(fmt.Errorf("unable to write build metadata %s\n%w", file, err))
|
||||||
return
|
return
|
||||||
|
@ -375,3 +433,27 @@ func contains(candidates []string, s string) bool {
|
||||||
|
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func validateSBOMFormats(layersPath string, acceptedSBOMFormats []string) error {
|
||||||
|
sbomFiles, err := filepath.Glob(filepath.Join(layersPath, "*.sbom.*"))
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("unable find SBOM files\n%w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, sbomFile := range sbomFiles {
|
||||||
|
parts := strings.Split(filepath.Base(sbomFile), ".")
|
||||||
|
if len(parts) <= 2 {
|
||||||
|
return fmt.Errorf("invalid format %s", filepath.Base(sbomFile))
|
||||||
|
}
|
||||||
|
sbomFormat, err := SBOMFormatFromString(strings.Join(parts[len(parts)-2:], "."))
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("unable to parse SBOM %s\n%w", sbomFormat, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if !contains(acceptedSBOMFormats, sbomFormat.MediaType()) {
|
||||||
|
return fmt.Errorf("unable to find actual SBOM Type %s in list of supported SBOM types %s", sbomFormat.MediaType(), acceptedSBOMFormats)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
550
build_test.go
550
build_test.go
|
@ -19,7 +19,6 @@ package libcnb_test
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io/ioutil"
|
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"testing"
|
"testing"
|
||||||
|
@ -58,15 +57,13 @@ func testBuild(t *testing.T, context spec.G, it spec.S) {
|
||||||
it.Before(func() {
|
it.Before(func() {
|
||||||
var err error
|
var err error
|
||||||
|
|
||||||
applicationPath, err = ioutil.TempDir("", "build-application-path")
|
applicationPath = t.TempDir()
|
||||||
Expect(err).NotTo(HaveOccurred())
|
|
||||||
applicationPath, err = filepath.EvalSymlinks(applicationPath)
|
applicationPath, err = filepath.EvalSymlinks(applicationPath)
|
||||||
Expect(err).NotTo(HaveOccurred())
|
Expect(err).NotTo(HaveOccurred())
|
||||||
|
|
||||||
builder = &mocks.Builder{}
|
builder = &mocks.Builder{}
|
||||||
|
|
||||||
buildpackPath, err = ioutil.TempDir("", "build-buildpack-path")
|
buildpackPath = t.TempDir()
|
||||||
Expect(err).NotTo(HaveOccurred())
|
|
||||||
Expect(os.Setenv("CNB_BUILDPACK_DIR", buildpackPath)).To(Succeed())
|
Expect(os.Setenv("CNB_BUILDPACK_DIR", buildpackPath)).To(Succeed())
|
||||||
|
|
||||||
bpTOMLContents = `
|
bpTOMLContents = `
|
||||||
|
@ -108,14 +105,14 @@ test-key = "test-value"
|
||||||
err = buildpackTOML.Execute(&b, map[string]string{"APIVersion": "0.6"})
|
err = buildpackTOML.Execute(&b, map[string]string{"APIVersion": "0.6"})
|
||||||
Expect(err).ToNot(HaveOccurred())
|
Expect(err).ToNot(HaveOccurred())
|
||||||
|
|
||||||
Expect(ioutil.WriteFile(filepath.Join(buildpackPath, "buildpack.toml"), b.Bytes(), 0600)).To(Succeed())
|
Expect(os.WriteFile(filepath.Join(buildpackPath, "buildpack.toml"), b.Bytes(), 0600)).To(Succeed())
|
||||||
|
|
||||||
f, err := ioutil.TempFile("", "build-buildpackplan-path")
|
f, err := os.CreateTemp("", "build-buildpackplan-path")
|
||||||
Expect(err).NotTo(HaveOccurred())
|
Expect(err).NotTo(HaveOccurred())
|
||||||
Expect(f.Close()).NotTo(HaveOccurred())
|
Expect(f.Close()).NotTo(HaveOccurred())
|
||||||
buildpackPlanPath = f.Name()
|
buildpackPlanPath = f.Name()
|
||||||
|
|
||||||
Expect(ioutil.WriteFile(buildpackPlanPath,
|
Expect(os.WriteFile(buildpackPlanPath,
|
||||||
[]byte(`
|
[]byte(`
|
||||||
[[entries]]
|
[[entries]]
|
||||||
name = "test-name"
|
name = "test-name"
|
||||||
|
@ -137,10 +134,9 @@ test-key = "test-value"
|
||||||
|
|
||||||
layerContributor = &mocks.LayerContributor{}
|
layerContributor = &mocks.LayerContributor{}
|
||||||
|
|
||||||
layersPath, err = ioutil.TempDir("", "build-layers-path")
|
layersPath = t.TempDir()
|
||||||
Expect(err).NotTo(HaveOccurred())
|
|
||||||
|
|
||||||
Expect(ioutil.WriteFile(filepath.Join(layersPath, "store.toml"),
|
Expect(os.WriteFile(filepath.Join(layersPath, "store.toml"),
|
||||||
[]byte(`
|
[]byte(`
|
||||||
[metadata]
|
[metadata]
|
||||||
test-key = "test-value"
|
test-key = "test-value"
|
||||||
|
@ -148,21 +144,23 @@ test-key = "test-value"
|
||||||
0600),
|
0600),
|
||||||
).To(Succeed())
|
).To(Succeed())
|
||||||
|
|
||||||
platformPath, err = ioutil.TempDir("", "build-platform-path")
|
platformPath = t.TempDir()
|
||||||
Expect(err).NotTo(HaveOccurred())
|
|
||||||
|
|
||||||
Expect(os.MkdirAll(filepath.Join(platformPath, "bindings", "alpha"), 0755)).To(Succeed())
|
Expect(os.MkdirAll(filepath.Join(platformPath, "bindings", "alpha"), 0755)).To(Succeed())
|
||||||
Expect(ioutil.WriteFile(filepath.Join(platformPath, "bindings", "alpha", "test-secret-key"),
|
Expect(os.WriteFile(filepath.Join(platformPath, "bindings", "alpha", "test-secret-key"),
|
||||||
[]byte("test-secret-value"), 0600)).To(Succeed())
|
[]byte("test-secret-value"), 0600)).To(Succeed())
|
||||||
|
|
||||||
Expect(os.MkdirAll(filepath.Join(platformPath, "env"), 0755)).To(Succeed())
|
Expect(os.MkdirAll(filepath.Join(platformPath, "env"), 0755)).To(Succeed())
|
||||||
Expect(ioutil.WriteFile(filepath.Join(platformPath, "env", "TEST_ENV"), []byte("test-value"), 0600)).
|
Expect(os.WriteFile(filepath.Join(platformPath, "env", "TEST_ENV"), []byte("test-value"), 0600)).
|
||||||
To(Succeed())
|
To(Succeed())
|
||||||
|
|
||||||
tomlWriter = &mocks.TOMLWriter{}
|
tomlWriter = &mocks.TOMLWriter{}
|
||||||
tomlWriter.On("Write", mock.Anything, mock.Anything).Return(nil)
|
tomlWriter.On("Write", mock.Anything, mock.Anything).Return(nil)
|
||||||
|
|
||||||
Expect(os.Setenv("CNB_STACK_ID", "test-stack-id")).To(Succeed())
|
Expect(os.Setenv("CNB_STACK_ID", "test-stack-id")).To(Succeed())
|
||||||
|
Expect(os.Setenv("CNB_LAYERS_DIR", layersPath)).To(Succeed())
|
||||||
|
Expect(os.Setenv("CNB_PLATFORM_DIR", platformPath)).To(Succeed())
|
||||||
|
Expect(os.Setenv("CNB_BP_PLAN_PATH", buildpackPlanPath)).To(Succeed())
|
||||||
|
|
||||||
workingDir, err = os.Getwd()
|
workingDir, err = os.Getwd()
|
||||||
Expect(err).NotTo(HaveOccurred())
|
Expect(err).NotTo(HaveOccurred())
|
||||||
|
@ -173,6 +171,9 @@ test-key = "test-value"
|
||||||
Expect(os.Chdir(workingDir)).To(Succeed())
|
Expect(os.Chdir(workingDir)).To(Succeed())
|
||||||
Expect(os.Unsetenv("CNB_BUILDPACK_DIR")).To(Succeed())
|
Expect(os.Unsetenv("CNB_BUILDPACK_DIR")).To(Succeed())
|
||||||
Expect(os.Unsetenv("CNB_STACK_ID")).To(Succeed())
|
Expect(os.Unsetenv("CNB_STACK_ID")).To(Succeed())
|
||||||
|
Expect(os.Unsetenv("CNB_PLATFORM_DIR")).To(Succeed())
|
||||||
|
Expect(os.Unsetenv("CNB_BP_PLAN_PATH")).To(Succeed())
|
||||||
|
Expect(os.Unsetenv("CNB_LAYERS_DIR")).To(Succeed())
|
||||||
|
|
||||||
Expect(os.RemoveAll(applicationPath)).To(Succeed())
|
Expect(os.RemoveAll(applicationPath)).To(Succeed())
|
||||||
Expect(os.RemoveAll(buildpackPath)).To(Succeed())
|
Expect(os.RemoveAll(buildpackPath)).To(Succeed())
|
||||||
|
@ -181,9 +182,9 @@ test-key = "test-value"
|
||||||
Expect(os.RemoveAll(platformPath)).To(Succeed())
|
Expect(os.RemoveAll(platformPath)).To(Succeed())
|
||||||
})
|
})
|
||||||
|
|
||||||
context("buildpack API is not 0.5 or 0.6", func() {
|
context("buildpack API is not within the supported range", func() {
|
||||||
it.Before(func() {
|
it.Before(func() {
|
||||||
Expect(ioutil.WriteFile(filepath.Join(buildpackPath, "buildpack.toml"),
|
Expect(os.WriteFile(filepath.Join(buildpackPath, "buildpack.toml"),
|
||||||
[]byte(`
|
[]byte(`
|
||||||
api = "0.4"
|
api = "0.4"
|
||||||
|
|
||||||
|
@ -198,20 +199,56 @@ version = "1.1.1"
|
||||||
|
|
||||||
it("fails", func() {
|
it("fails", func() {
|
||||||
libcnb.Build(builder,
|
libcnb.Build(builder,
|
||||||
|
libcnb.WithBOMLabel(true),
|
||||||
libcnb.WithArguments([]string{commandPath, layersPath, platformPath, buildpackPlanPath}),
|
libcnb.WithArguments([]string{commandPath, layersPath, platformPath, buildpackPlanPath}),
|
||||||
libcnb.WithExitHandler(exitHandler),
|
libcnb.WithExitHandler(exitHandler),
|
||||||
)
|
)
|
||||||
|
|
||||||
Expect(exitHandler.Calls[0].Arguments.Get(0)).To(MatchError(
|
Expect(exitHandler.Calls[0].Arguments.Get(0)).To(MatchError(
|
||||||
"this version of libcnb is only compatible with buildpack APIs 0.5 and 0.6",
|
fmt.Sprintf("this version of libcnb is only compatible with buildpack APIs >= %s, <= %s", libcnb.MinSupportedBPVersion, libcnb.MaxSupportedBPVersion),
|
||||||
))
|
))
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
context("errors if required env vars are not set for buildpack API >=0.8", func() {
|
||||||
|
for _, e := range []string{"CNB_LAYERS_DIR", "CNB_PLATFORM_DIR", "CNB_BP_PLAN_PATH"} {
|
||||||
|
// We need to do this assignment because of the way that spec binds variables
|
||||||
|
envVar := e
|
||||||
|
context(fmt.Sprintf("when %s is unset", envVar), func() {
|
||||||
|
it.Before(func() {
|
||||||
|
Expect(os.WriteFile(filepath.Join(buildpackPath, "buildpack.toml"),
|
||||||
|
[]byte(`
|
||||||
|
api = "0.8"
|
||||||
|
|
||||||
|
[buildpack]
|
||||||
|
id = "test-id"
|
||||||
|
name = "test-name"
|
||||||
|
version = "1.1.1"
|
||||||
|
`),
|
||||||
|
0600),
|
||||||
|
).To(Succeed())
|
||||||
|
os.Unsetenv(envVar)
|
||||||
|
})
|
||||||
|
|
||||||
|
it("fails", func() {
|
||||||
|
libcnb.Build(builder,
|
||||||
|
libcnb.WithBOMLabel(true),
|
||||||
|
libcnb.WithArguments([]string{commandPath}),
|
||||||
|
libcnb.WithExitHandler(exitHandler),
|
||||||
|
)
|
||||||
|
Expect(exitHandler.Calls[0].Arguments.Get(0)).To(MatchError(
|
||||||
|
fmt.Sprintf("expected %s to be set", envVar),
|
||||||
|
))
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
it("encounters the wrong number of arguments", func() {
|
it("encounters the wrong number of arguments", func() {
|
||||||
builder.On("Build", mock.Anything).Return(libcnb.NewBuildResult(), nil)
|
builder.On("Build", mock.Anything).Return(libcnb.NewBuildResult(), nil)
|
||||||
|
|
||||||
libcnb.Build(builder,
|
libcnb.Build(builder,
|
||||||
|
libcnb.WithBOMLabel(true),
|
||||||
libcnb.WithArguments([]string{commandPath}),
|
libcnb.WithArguments([]string{commandPath}),
|
||||||
libcnb.WithExitHandler(exitHandler),
|
libcnb.WithExitHandler(exitHandler),
|
||||||
)
|
)
|
||||||
|
@ -219,76 +256,136 @@ version = "1.1.1"
|
||||||
Expect(exitHandler.Calls[0].Arguments.Get(0)).To(MatchError("expected 3 arguments and received 0"))
|
Expect(exitHandler.Calls[0].Arguments.Get(0)).To(MatchError("expected 3 arguments and received 0"))
|
||||||
})
|
})
|
||||||
|
|
||||||
it("doesn't receive CNB_STACK_ID", func() {
|
context("when BP API >= 0.8", func() {
|
||||||
Expect(os.Unsetenv("CNB_STACK_ID")).To(Succeed())
|
it.Before(func() {
|
||||||
builder.On("Build", mock.Anything).Return(libcnb.NewBuildResult(), nil)
|
Expect(os.WriteFile(filepath.Join(buildpackPath, "buildpack.toml"),
|
||||||
|
[]byte(`
|
||||||
|
api = "0.8"
|
||||||
|
|
||||||
|
[buildpack]
|
||||||
|
id = "test-id"
|
||||||
|
name = "test-name"
|
||||||
|
version = "1.1.1"
|
||||||
|
`),
|
||||||
|
0600),
|
||||||
|
).To(Succeed())
|
||||||
|
})
|
||||||
|
|
||||||
libcnb.Build(builder,
|
it("creates context", func() {
|
||||||
libcnb.WithArguments([]string{commandPath, layersPath, platformPath, buildpackPlanPath}),
|
builder.On("Build", mock.Anything).Return(libcnb.NewBuildResult(), nil)
|
||||||
libcnb.WithExitHandler(exitHandler),
|
|
||||||
)
|
|
||||||
|
|
||||||
Expect(exitHandler.Calls[0].Arguments.Get(0)).To(MatchError("CNB_STACK_ID not set"))
|
libcnb.Build(builder,
|
||||||
|
libcnb.WithBOMLabel(true),
|
||||||
|
libcnb.WithArguments([]string{commandPath}),
|
||||||
|
)
|
||||||
|
|
||||||
|
ctx := builder.Calls[0].Arguments[0].(libcnb.BuildContext)
|
||||||
|
Expect(ctx.Application).To(Equal(libcnb.Application{Path: applicationPath}))
|
||||||
|
Expect(ctx.Buildpack).To(Equal(libcnb.Buildpack{
|
||||||
|
API: "0.8",
|
||||||
|
Info: libcnb.BuildpackInfo{
|
||||||
|
ID: "test-id",
|
||||||
|
Name: "test-name",
|
||||||
|
Version: "1.1.1",
|
||||||
|
},
|
||||||
|
Path: buildpackPath,
|
||||||
|
}))
|
||||||
|
Expect(ctx.Layers).To(Equal(libcnb.Layers{Path: layersPath}))
|
||||||
|
Expect(ctx.PersistentMetadata).To(Equal(map[string]interface{}{"test-key": "test-value"}))
|
||||||
|
Expect(ctx.Plan).To(Equal(libcnb.BuildpackPlan{
|
||||||
|
Entries: []libcnb.BuildpackPlanEntry{
|
||||||
|
{
|
||||||
|
Name: "test-name",
|
||||||
|
Metadata: map[string]interface{}{
|
||||||
|
"test-key": "test-value",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}))
|
||||||
|
Expect(ctx.Platform).To(Equal(libcnb.Platform{
|
||||||
|
Bindings: libcnb.Bindings{
|
||||||
|
libcnb.Binding{
|
||||||
|
Name: "alpha",
|
||||||
|
Path: filepath.Join(platformPath, "bindings", "alpha"),
|
||||||
|
Secret: map[string]string{
|
||||||
|
"test-secret-key": "test-secret-value",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Environment: map[string]string{"TEST_ENV": "test-value"},
|
||||||
|
Path: platformPath,
|
||||||
|
}))
|
||||||
|
Expect(ctx.StackID).To(Equal("test-stack-id"))
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
it("creates context", func() {
|
context("when BP API < 0.8", func() {
|
||||||
builder.On("Build", mock.Anything).Return(libcnb.NewBuildResult(), nil)
|
it.Before(func() {
|
||||||
|
Expect(os.Unsetenv("CNB_PLATFORM_DIR")).To(Succeed())
|
||||||
|
Expect(os.Unsetenv("CNB_BP_PLAN_PATH")).To(Succeed())
|
||||||
|
Expect(os.Unsetenv("CNB_LAYERS_DIR")).To(Succeed())
|
||||||
|
})
|
||||||
|
|
||||||
libcnb.Build(builder,
|
it("creates context", func() {
|
||||||
libcnb.WithArguments([]string{commandPath, layersPath, platformPath, buildpackPlanPath}),
|
builder.On("Build", mock.Anything).Return(libcnb.NewBuildResult(), nil)
|
||||||
)
|
|
||||||
|
|
||||||
ctx := builder.Calls[0].Arguments[0].(libcnb.BuildContext)
|
libcnb.Build(builder,
|
||||||
Expect(ctx.Application).To(Equal(libcnb.Application{Path: applicationPath}))
|
libcnb.WithBOMLabel(true),
|
||||||
Expect(ctx.Buildpack).To(Equal(libcnb.Buildpack{
|
libcnb.WithArguments([]string{commandPath, layersPath, platformPath, buildpackPlanPath}),
|
||||||
API: "0.6",
|
)
|
||||||
Info: libcnb.BuildpackInfo{
|
|
||||||
ID: "test-id",
|
ctx := builder.Calls[0].Arguments[0].(libcnb.BuildContext)
|
||||||
Name: "test-name",
|
Expect(ctx.Application).To(Equal(libcnb.Application{Path: applicationPath}))
|
||||||
Version: "1.1.1",
|
Expect(ctx.Buildpack).To(Equal(libcnb.Buildpack{
|
||||||
ClearEnvironment: true,
|
API: "0.6",
|
||||||
Description: "A test buildpack",
|
Info: libcnb.BuildpackInfo{
|
||||||
Keywords: []string{"test", "buildpack"},
|
ID: "test-id",
|
||||||
Licenses: []libcnb.License{
|
Name: "test-name",
|
||||||
{Type: "Apache-2.0", URI: "https://spdx.org/licenses/Apache-2.0.html"},
|
Version: "1.1.1",
|
||||||
{Type: "Apache-1.1", URI: "https://spdx.org/licenses/Apache-1.1.html"},
|
ClearEnvironment: true,
|
||||||
},
|
Description: "A test buildpack",
|
||||||
},
|
Keywords: []string{"test", "buildpack"},
|
||||||
Path: buildpackPath,
|
Licenses: []libcnb.License{
|
||||||
Stacks: []libcnb.BuildpackStack{
|
{Type: "Apache-2.0", URI: "https://spdx.org/licenses/Apache-2.0.html"},
|
||||||
{
|
{Type: "Apache-1.1", URI: "https://spdx.org/licenses/Apache-1.1.html"},
|
||||||
ID: "test-id",
|
|
||||||
Mixins: []string{"test-name"},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
Metadata: map[string]interface{}{"test-key": "test-value"},
|
|
||||||
}))
|
|
||||||
Expect(ctx.Layers).To(Equal(libcnb.Layers{Path: layersPath}))
|
|
||||||
Expect(ctx.PersistentMetadata).To(Equal(map[string]interface{}{"test-key": "test-value"}))
|
|
||||||
Expect(ctx.Plan).To(Equal(libcnb.BuildpackPlan{
|
|
||||||
Entries: []libcnb.BuildpackPlanEntry{
|
|
||||||
{
|
|
||||||
Name: "test-name",
|
|
||||||
Metadata: map[string]interface{}{
|
|
||||||
"test-key": "test-value",
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
Path: buildpackPath,
|
||||||
}))
|
Stacks: []libcnb.BuildpackStack{
|
||||||
Expect(ctx.Platform).To(Equal(libcnb.Platform{
|
{
|
||||||
Bindings: libcnb.Bindings{
|
ID: "test-id",
|
||||||
libcnb.Binding{
|
Mixins: []string{"test-name"},
|
||||||
Name: "alpha",
|
|
||||||
Path: filepath.Join(platformPath, "bindings", "alpha"),
|
|
||||||
Secret: map[string]string{
|
|
||||||
"test-secret-key": "test-secret-value",
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
Metadata: map[string]interface{}{"test-key": "test-value"},
|
||||||
Environment: map[string]string{"TEST_ENV": "test-value"},
|
}))
|
||||||
Path: platformPath,
|
Expect(ctx.Layers).To(Equal(libcnb.Layers{Path: layersPath}))
|
||||||
}))
|
Expect(ctx.PersistentMetadata).To(Equal(map[string]interface{}{"test-key": "test-value"}))
|
||||||
Expect(ctx.StackID).To(Equal("test-stack-id"))
|
Expect(ctx.Plan).To(Equal(libcnb.BuildpackPlan{
|
||||||
|
Entries: []libcnb.BuildpackPlanEntry{
|
||||||
|
{
|
||||||
|
Name: "test-name",
|
||||||
|
Metadata: map[string]interface{}{
|
||||||
|
"test-key": "test-value",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}))
|
||||||
|
Expect(ctx.Platform).To(Equal(libcnb.Platform{
|
||||||
|
Bindings: libcnb.Bindings{
|
||||||
|
libcnb.Binding{
|
||||||
|
Name: "alpha",
|
||||||
|
Path: filepath.Join(platformPath, "bindings", "alpha"),
|
||||||
|
Secret: map[string]string{
|
||||||
|
"test-secret-key": "test-secret-value",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Environment: map[string]string{"TEST_ENV": "test-value"},
|
||||||
|
Path: platformPath,
|
||||||
|
}))
|
||||||
|
Expect(ctx.StackID).To(Equal("test-stack-id"))
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
it("extracts buildpack path from command path if CNB_BUILDPACK_PATH is not set", func() {
|
it("extracts buildpack path from command path if CNB_BUILDPACK_PATH is not set", func() {
|
||||||
|
@ -297,6 +394,7 @@ version = "1.1.1"
|
||||||
builder.On("Build", mock.Anything).Return(libcnb.NewBuildResult(), nil)
|
builder.On("Build", mock.Anything).Return(libcnb.NewBuildResult(), nil)
|
||||||
|
|
||||||
libcnb.Build(builder,
|
libcnb.Build(builder,
|
||||||
|
libcnb.WithBOMLabel(true),
|
||||||
libcnb.WithArguments([]string{filepath.Join(buildpackPath, commandPath), layersPath, platformPath, buildpackPlanPath}),
|
libcnb.WithArguments([]string{filepath.Join(buildpackPath, commandPath), layersPath, platformPath, buildpackPlanPath}),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -309,6 +407,7 @@ version = "1.1.1"
|
||||||
builder.On("Build", mock.Anything).Return(libcnb.NewBuildResult(), fmt.Errorf("test-error"))
|
builder.On("Build", mock.Anything).Return(libcnb.NewBuildResult(), fmt.Errorf("test-error"))
|
||||||
|
|
||||||
libcnb.Build(builder,
|
libcnb.Build(builder,
|
||||||
|
libcnb.WithBOMLabel(true),
|
||||||
libcnb.WithArguments([]string{commandPath, layersPath, platformPath, buildpackPlanPath}),
|
libcnb.WithArguments([]string{commandPath, layersPath, platformPath, buildpackPlanPath}),
|
||||||
libcnb.WithExitHandler(exitHandler),
|
libcnb.WithExitHandler(exitHandler),
|
||||||
)
|
)
|
||||||
|
@ -323,6 +422,7 @@ version = "1.1.1"
|
||||||
Return(libcnb.BuildResult{Layers: []libcnb.LayerContributor{layerContributor}}, nil)
|
Return(libcnb.BuildResult{Layers: []libcnb.LayerContributor{layerContributor}}, nil)
|
||||||
|
|
||||||
libcnb.Build(builder,
|
libcnb.Build(builder,
|
||||||
|
libcnb.WithBOMLabel(true),
|
||||||
libcnb.WithArguments([]string{commandPath, layersPath, platformPath, buildpackPlanPath}),
|
libcnb.WithArguments([]string{commandPath, layersPath, platformPath, buildpackPlanPath}),
|
||||||
libcnb.WithTOMLWriter(tomlWriter),
|
libcnb.WithTOMLWriter(tomlWriter),
|
||||||
)
|
)
|
||||||
|
@ -339,6 +439,7 @@ version = "1.1.1"
|
||||||
builder.On("Build", mock.Anything).Return(result, nil)
|
builder.On("Build", mock.Anything).Return(result, nil)
|
||||||
|
|
||||||
libcnb.Build(builder,
|
libcnb.Build(builder,
|
||||||
|
libcnb.WithBOMLabel(true),
|
||||||
libcnb.WithArguments([]string{commandPath, layersPath, platformPath, buildpackPlanPath}),
|
libcnb.WithArguments([]string{commandPath, layersPath, platformPath, buildpackPlanPath}),
|
||||||
libcnb.WithEnvironmentWriter(environmentWriter),
|
libcnb.WithEnvironmentWriter(environmentWriter),
|
||||||
)
|
)
|
||||||
|
@ -356,6 +457,7 @@ version = "1.1.1"
|
||||||
builder.On("Build", mock.Anything).Return(result, nil)
|
builder.On("Build", mock.Anything).Return(result, nil)
|
||||||
|
|
||||||
libcnb.Build(builder,
|
libcnb.Build(builder,
|
||||||
|
libcnb.WithBOMLabel(true),
|
||||||
libcnb.WithArguments([]string{commandPath, layersPath, platformPath, buildpackPlanPath}),
|
libcnb.WithArguments([]string{commandPath, layersPath, platformPath, buildpackPlanPath}),
|
||||||
libcnb.WithEnvironmentWriter(environmentWriter),
|
libcnb.WithEnvironmentWriter(environmentWriter),
|
||||||
)
|
)
|
||||||
|
@ -373,6 +475,7 @@ version = "1.1.1"
|
||||||
builder.On("Build", mock.Anything).Return(result, nil)
|
builder.On("Build", mock.Anything).Return(result, nil)
|
||||||
|
|
||||||
libcnb.Build(builder,
|
libcnb.Build(builder,
|
||||||
|
libcnb.WithBOMLabel(true),
|
||||||
|
|
||||||
libcnb.WithArguments([]string{commandPath, layersPath, platformPath, buildpackPlanPath}),
|
libcnb.WithArguments([]string{commandPath, layersPath, platformPath, buildpackPlanPath}),
|
||||||
libcnb.WithEnvironmentWriter(environmentWriter),
|
libcnb.WithEnvironmentWriter(environmentWriter),
|
||||||
|
@ -391,6 +494,7 @@ version = "1.1.1"
|
||||||
builder.On("Build", mock.Anything).Return(result, nil)
|
builder.On("Build", mock.Anything).Return(result, nil)
|
||||||
|
|
||||||
libcnb.Build(builder,
|
libcnb.Build(builder,
|
||||||
|
libcnb.WithBOMLabel(true),
|
||||||
libcnb.WithArguments([]string{commandPath, layersPath, platformPath, buildpackPlanPath}),
|
libcnb.WithArguments([]string{commandPath, layersPath, platformPath, buildpackPlanPath}),
|
||||||
libcnb.WithEnvironmentWriter(environmentWriter),
|
libcnb.WithEnvironmentWriter(environmentWriter),
|
||||||
)
|
)
|
||||||
|
@ -404,7 +508,7 @@ version = "1.1.1"
|
||||||
err := buildpackTOML.Execute(&b, map[string]string{"APIVersion": "0.5"})
|
err := buildpackTOML.Execute(&b, map[string]string{"APIVersion": "0.5"})
|
||||||
Expect(err).ToNot(HaveOccurred())
|
Expect(err).ToNot(HaveOccurred())
|
||||||
|
|
||||||
Expect(ioutil.WriteFile(filepath.Join(buildpackPath, "buildpack.toml"), b.Bytes(), 0600)).To(Succeed())
|
Expect(os.WriteFile(filepath.Join(buildpackPath, "buildpack.toml"), b.Bytes(), 0600)).To(Succeed())
|
||||||
|
|
||||||
layer := libcnb.Layer{
|
layer := libcnb.Layer{
|
||||||
Name: "test-name",
|
Name: "test-name",
|
||||||
|
@ -422,6 +526,7 @@ version = "1.1.1"
|
||||||
builder.On("Build", mock.Anything).Return(result, nil)
|
builder.On("Build", mock.Anything).Return(result, nil)
|
||||||
|
|
||||||
libcnb.Build(builder,
|
libcnb.Build(builder,
|
||||||
|
libcnb.WithBOMLabel(true),
|
||||||
libcnb.WithArguments([]string{commandPath, layersPath, platformPath, buildpackPlanPath}),
|
libcnb.WithArguments([]string{commandPath, layersPath, platformPath, buildpackPlanPath}),
|
||||||
libcnb.WithTOMLWriter(tomlWriter),
|
libcnb.WithTOMLWriter(tomlWriter),
|
||||||
)
|
)
|
||||||
|
@ -453,6 +558,7 @@ version = "1.1.1"
|
||||||
builder.On("Build", mock.Anything).Return(result, nil)
|
builder.On("Build", mock.Anything).Return(result, nil)
|
||||||
|
|
||||||
libcnb.Build(builder,
|
libcnb.Build(builder,
|
||||||
|
libcnb.WithBOMLabel(true),
|
||||||
libcnb.WithArguments([]string{commandPath, layersPath, platformPath, buildpackPlanPath}),
|
libcnb.WithArguments([]string{commandPath, layersPath, platformPath, buildpackPlanPath}),
|
||||||
libcnb.WithTOMLWriter(tomlWriter),
|
libcnb.WithTOMLWriter(tomlWriter),
|
||||||
)
|
)
|
||||||
|
@ -501,6 +607,7 @@ version = "1.1.1"
|
||||||
}, nil)
|
}, nil)
|
||||||
|
|
||||||
libcnb.Build(builder,
|
libcnb.Build(builder,
|
||||||
|
libcnb.WithBOMLabel(true),
|
||||||
libcnb.WithArguments([]string{commandPath, layersPath, platformPath, buildpackPlanPath}),
|
libcnb.WithArguments([]string{commandPath, layersPath, platformPath, buildpackPlanPath}),
|
||||||
libcnb.WithTOMLWriter(tomlWriter),
|
libcnb.WithTOMLWriter(tomlWriter),
|
||||||
)
|
)
|
||||||
|
@ -535,12 +642,79 @@ version = "1.1.1"
|
||||||
}))
|
}))
|
||||||
})
|
})
|
||||||
|
|
||||||
|
it("ignore working-directory setting and writes launch.toml (API<0.8)", func() {
|
||||||
|
builder.On("Build", mock.Anything).Return(libcnb.BuildResult{
|
||||||
|
Processes: []libcnb.Process{
|
||||||
|
{
|
||||||
|
Type: "test-type",
|
||||||
|
Command: "test-command-in-dir",
|
||||||
|
Default: true,
|
||||||
|
WorkingDirectory: "/my/directory/",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}, nil)
|
||||||
|
|
||||||
|
libcnb.Build(builder,
|
||||||
|
libcnb.WithBOMLabel(true),
|
||||||
|
libcnb.WithArguments([]string{commandPath, layersPath, platformPath, buildpackPlanPath}),
|
||||||
|
libcnb.WithTOMLWriter(tomlWriter),
|
||||||
|
)
|
||||||
|
|
||||||
|
Expect(tomlWriter.Calls[0].Arguments[0]).To(Equal(filepath.Join(layersPath, "launch.toml")))
|
||||||
|
Expect(tomlWriter.Calls[0].Arguments[1]).To(Equal(libcnb.LaunchTOML{
|
||||||
|
Processes: []libcnb.Process{
|
||||||
|
{
|
||||||
|
Type: "test-type",
|
||||||
|
Command: "test-command-in-dir",
|
||||||
|
Default: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}))
|
||||||
|
})
|
||||||
|
|
||||||
|
it("writes launch.toml with working-directory setting(API>=0.8)", func() {
|
||||||
|
var b bytes.Buffer
|
||||||
|
err := buildpackTOML.Execute(&b, map[string]string{"APIVersion": "0.8"})
|
||||||
|
Expect(err).ToNot(HaveOccurred())
|
||||||
|
|
||||||
|
Expect(os.WriteFile(filepath.Join(buildpackPath, "buildpack.toml"), b.Bytes(), 0600)).To(Succeed())
|
||||||
|
builder.On("Build", mock.Anything).Return(libcnb.BuildResult{
|
||||||
|
Processes: []libcnb.Process{
|
||||||
|
{
|
||||||
|
Type: "test-type",
|
||||||
|
Command: "test-command-in-dir",
|
||||||
|
Default: true,
|
||||||
|
WorkingDirectory: "/my/directory/",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}, nil)
|
||||||
|
|
||||||
|
libcnb.Build(builder,
|
||||||
|
libcnb.WithBOMLabel(true),
|
||||||
|
libcnb.WithArguments([]string{commandPath, layersPath, platformPath, buildpackPlanPath}),
|
||||||
|
libcnb.WithTOMLWriter(tomlWriter),
|
||||||
|
)
|
||||||
|
|
||||||
|
Expect(tomlWriter.Calls[0].Arguments[0]).To(Equal(filepath.Join(layersPath, "launch.toml")))
|
||||||
|
Expect(tomlWriter.Calls[0].Arguments[1]).To(Equal(libcnb.LaunchTOML{
|
||||||
|
Processes: []libcnb.Process{
|
||||||
|
{
|
||||||
|
Type: "test-type",
|
||||||
|
Command: "test-command-in-dir",
|
||||||
|
Default: true,
|
||||||
|
WorkingDirectory: "/my/directory/",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}))
|
||||||
|
})
|
||||||
|
|
||||||
it("writes persistent metadata", func() {
|
it("writes persistent metadata", func() {
|
||||||
m := map[string]interface{}{"test-key": "test-value"}
|
m := map[string]interface{}{"test-key": "test-value"}
|
||||||
|
|
||||||
builder.On("Build", mock.Anything).Return(libcnb.BuildResult{PersistentMetadata: m}, nil)
|
builder.On("Build", mock.Anything).Return(libcnb.BuildResult{PersistentMetadata: m}, nil)
|
||||||
|
|
||||||
libcnb.Build(builder,
|
libcnb.Build(builder,
|
||||||
|
libcnb.WithBOMLabel(true),
|
||||||
libcnb.WithArguments([]string{commandPath, layersPath, platformPath, buildpackPlanPath}),
|
libcnb.WithArguments([]string{commandPath, layersPath, platformPath, buildpackPlanPath}),
|
||||||
libcnb.WithTOMLWriter(tomlWriter),
|
libcnb.WithTOMLWriter(tomlWriter),
|
||||||
)
|
)
|
||||||
|
@ -553,6 +727,7 @@ version = "1.1.1"
|
||||||
builder.On("Build", mock.Anything).Return(libcnb.NewBuildResult(), nil)
|
builder.On("Build", mock.Anything).Return(libcnb.NewBuildResult(), nil)
|
||||||
|
|
||||||
libcnb.Build(builder,
|
libcnb.Build(builder,
|
||||||
|
libcnb.WithBOMLabel(true),
|
||||||
libcnb.WithArguments([]string{commandPath, layersPath, platformPath, buildpackPlanPath}),
|
libcnb.WithArguments([]string{commandPath, layersPath, platformPath, buildpackPlanPath}),
|
||||||
libcnb.WithTOMLWriter(tomlWriter),
|
libcnb.WithTOMLWriter(tomlWriter),
|
||||||
)
|
)
|
||||||
|
@ -561,9 +736,9 @@ version = "1.1.1"
|
||||||
})
|
})
|
||||||
|
|
||||||
it("removes stale layers", func() {
|
it("removes stale layers", func() {
|
||||||
Expect(ioutil.WriteFile(filepath.Join(layersPath, "alpha.toml"), []byte(""), 0600)).To(Succeed())
|
Expect(os.WriteFile(filepath.Join(layersPath, "alpha.toml"), []byte(""), 0600)).To(Succeed())
|
||||||
Expect(ioutil.WriteFile(filepath.Join(layersPath, "bravo.toml"), []byte(""), 0600)).To(Succeed())
|
Expect(os.WriteFile(filepath.Join(layersPath, "bravo.toml"), []byte(""), 0600)).To(Succeed())
|
||||||
Expect(ioutil.WriteFile(filepath.Join(layersPath, "store.toml"), []byte(""), 0600)).To(Succeed())
|
Expect(os.WriteFile(filepath.Join(layersPath, "store.toml"), []byte(""), 0600)).To(Succeed())
|
||||||
|
|
||||||
layer := libcnb.Layer{Name: "alpha"}
|
layer := libcnb.Layer{Name: "alpha"}
|
||||||
layerContributor.On("Contribute", mock.Anything).Return(layer, nil)
|
layerContributor.On("Contribute", mock.Anything).Return(layer, nil)
|
||||||
|
@ -573,6 +748,7 @@ version = "1.1.1"
|
||||||
Return(libcnb.BuildResult{Layers: []libcnb.LayerContributor{layerContributor}}, nil)
|
Return(libcnb.BuildResult{Layers: []libcnb.LayerContributor{layerContributor}}, nil)
|
||||||
|
|
||||||
libcnb.Build(builder,
|
libcnb.Build(builder,
|
||||||
|
libcnb.WithBOMLabel(true),
|
||||||
libcnb.WithArguments([]string{commandPath, layersPath, platformPath, buildpackPlanPath}),
|
libcnb.WithArguments([]string{commandPath, layersPath, platformPath, buildpackPlanPath}),
|
||||||
libcnb.WithTOMLWriter(tomlWriter),
|
libcnb.WithTOMLWriter(tomlWriter),
|
||||||
)
|
)
|
||||||
|
@ -605,6 +781,7 @@ version = "1.1.1"
|
||||||
}, nil)
|
}, nil)
|
||||||
|
|
||||||
libcnb.Build(builder,
|
libcnb.Build(builder,
|
||||||
|
libcnb.WithBOMLabel(true),
|
||||||
libcnb.WithArguments([]string{commandPath, layersPath, platformPath, buildpackPlanPath}),
|
libcnb.WithArguments([]string{commandPath, layersPath, platformPath, buildpackPlanPath}),
|
||||||
libcnb.WithTOMLWriter(tomlWriter),
|
libcnb.WithTOMLWriter(tomlWriter),
|
||||||
)
|
)
|
||||||
|
@ -625,4 +802,213 @@ version = "1.1.1"
|
||||||
},
|
},
|
||||||
}))
|
}))
|
||||||
})
|
})
|
||||||
|
|
||||||
|
context("Config bomLabel is false", func() {
|
||||||
|
it.Before(func() {
|
||||||
|
var err error
|
||||||
|
|
||||||
|
buildpackTOML, err = template.New("buildpack.toml").Parse(bpTOMLContents)
|
||||||
|
Expect(err).ToNot(HaveOccurred())
|
||||||
|
|
||||||
|
var b bytes.Buffer
|
||||||
|
err = buildpackTOML.Execute(&b, map[string]string{"APIVersion": "0.7"})
|
||||||
|
Expect(err).ToNot(HaveOccurred())
|
||||||
|
|
||||||
|
Expect(os.WriteFile(filepath.Join(buildpackPath, "buildpack.toml"), b.Bytes(), 0600)).To(Succeed())
|
||||||
|
})
|
||||||
|
|
||||||
|
it("writes launch.toml without BOM entries", func() {
|
||||||
|
builder.On("Build", mock.Anything).Return(libcnb.BuildResult{
|
||||||
|
BOM: &libcnb.BOM{Entries: []libcnb.BOMEntry{
|
||||||
|
{
|
||||||
|
Name: "test-launch-bom-entry",
|
||||||
|
Metadata: map[string]interface{}{"test-key": "test-value"},
|
||||||
|
Launch: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Name: "test-build-bom-entry",
|
||||||
|
Metadata: map[string]interface{}{"test-key": "test-value"},
|
||||||
|
},
|
||||||
|
}},
|
||||||
|
Processes: []libcnb.Process{
|
||||||
|
{
|
||||||
|
Type: "test-type",
|
||||||
|
Command: "test-command",
|
||||||
|
Default: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}, nil)
|
||||||
|
|
||||||
|
libcnb.Build(builder,
|
||||||
|
libcnb.WithBOMLabel(false),
|
||||||
|
libcnb.WithArguments([]string{commandPath, layersPath, platformPath, buildpackPlanPath}),
|
||||||
|
libcnb.WithTOMLWriter(tomlWriter),
|
||||||
|
)
|
||||||
|
|
||||||
|
Expect(tomlWriter.Calls[0].Arguments[0]).To(Equal(filepath.Join(layersPath, "launch.toml")))
|
||||||
|
Expect(tomlWriter.Calls[0].Arguments[1]).To(Equal(libcnb.LaunchTOML{
|
||||||
|
Processes: []libcnb.Process{
|
||||||
|
{
|
||||||
|
Type: "test-type",
|
||||||
|
Command: "test-command",
|
||||||
|
Default: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
BOM: nil,
|
||||||
|
}))
|
||||||
|
})
|
||||||
|
|
||||||
|
it("writes build.toml without BOM entries", func() {
|
||||||
|
builder.On("Build", mock.Anything).Return(libcnb.BuildResult{
|
||||||
|
BOM: &libcnb.BOM{Entries: []libcnb.BOMEntry{
|
||||||
|
{
|
||||||
|
Name: "test-build-bom-entry",
|
||||||
|
Metadata: map[string]interface{}{"test-key": "test-value"},
|
||||||
|
Build: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Name: "test-launch-bom-entry",
|
||||||
|
Metadata: map[string]interface{}{"test-key": "test-value"},
|
||||||
|
Build: false,
|
||||||
|
},
|
||||||
|
}},
|
||||||
|
Unmet: []libcnb.UnmetPlanEntry{
|
||||||
|
{
|
||||||
|
Name: "test-entry",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}, nil)
|
||||||
|
|
||||||
|
libcnb.Build(builder,
|
||||||
|
libcnb.WithBOMLabel(false),
|
||||||
|
libcnb.WithArguments([]string{commandPath, layersPath, platformPath, buildpackPlanPath}),
|
||||||
|
libcnb.WithTOMLWriter(tomlWriter),
|
||||||
|
)
|
||||||
|
|
||||||
|
Expect(tomlWriter.Calls[0].Arguments[0]).To(Equal(filepath.Join(layersPath, "build.toml")))
|
||||||
|
Expect(tomlWriter.Calls[0].Arguments[1]).To(Equal(libcnb.BuildTOML{
|
||||||
|
BOM: nil,
|
||||||
|
Unmet: []libcnb.UnmetPlanEntry{
|
||||||
|
{
|
||||||
|
Name: "test-entry",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}))
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
context("Validates SBOM entries", func() {
|
||||||
|
it.Before(func() {
|
||||||
|
Expect(os.WriteFile(filepath.Join(buildpackPath, "buildpack.toml"),
|
||||||
|
[]byte(`
|
||||||
|
api = "0.7"
|
||||||
|
|
||||||
|
[buildpack]
|
||||||
|
id = "test-id"
|
||||||
|
name = "test-name"
|
||||||
|
version = "1.1.1"
|
||||||
|
sbom-formats = ["application/vnd.cyclonedx+json"]
|
||||||
|
`),
|
||||||
|
0600),
|
||||||
|
).To(Succeed())
|
||||||
|
|
||||||
|
builder.On("Build", mock.Anything).Return(libcnb.BuildResult{}, nil)
|
||||||
|
})
|
||||||
|
|
||||||
|
it("has no SBOM files", func() {
|
||||||
|
libcnb.Build(builder,
|
||||||
|
libcnb.WithBOMLabel(true),
|
||||||
|
libcnb.WithArguments([]string{commandPath, layersPath, platformPath, buildpackPlanPath}),
|
||||||
|
libcnb.WithExitHandler(exitHandler),
|
||||||
|
)
|
||||||
|
|
||||||
|
Expect(exitHandler.Calls).To(BeEmpty())
|
||||||
|
})
|
||||||
|
|
||||||
|
it("has no accepted formats", func() {
|
||||||
|
Expect(os.WriteFile(filepath.Join(buildpackPath, "buildpack.toml"),
|
||||||
|
[]byte(`
|
||||||
|
api = "0.7"
|
||||||
|
|
||||||
|
[buildpack]
|
||||||
|
id = "test-id"
|
||||||
|
name = "test-name"
|
||||||
|
version = "1.1.1"
|
||||||
|
sbom-formats = []
|
||||||
|
`),
|
||||||
|
0600),
|
||||||
|
).To(Succeed())
|
||||||
|
|
||||||
|
Expect(os.WriteFile(filepath.Join(layersPath, "launch.sbom.spdx.json"), []byte{}, 0600)).To(Succeed())
|
||||||
|
|
||||||
|
libcnb.Build(builder,
|
||||||
|
libcnb.WithBOMLabel(true),
|
||||||
|
libcnb.WithArguments([]string{commandPath, layersPath, platformPath, buildpackPlanPath}),
|
||||||
|
libcnb.WithExitHandler(exitHandler),
|
||||||
|
)
|
||||||
|
|
||||||
|
Expect(exitHandler.Calls[0].Arguments.Get(0)).To(MatchError("unable to validate SBOM\nunable to find actual SBOM Type application/spdx+json in list of supported SBOM types []"))
|
||||||
|
})
|
||||||
|
|
||||||
|
it("skips if API is not 0.7", func() {
|
||||||
|
Expect(os.WriteFile(filepath.Join(buildpackPath, "buildpack.toml"),
|
||||||
|
[]byte(`
|
||||||
|
api = "0.6"
|
||||||
|
|
||||||
|
[buildpack]
|
||||||
|
id = "test-id"
|
||||||
|
name = "test-name"
|
||||||
|
version = "1.1.1"
|
||||||
|
sbom-formats = []
|
||||||
|
`),
|
||||||
|
0600),
|
||||||
|
).To(Succeed())
|
||||||
|
|
||||||
|
Expect(os.WriteFile(filepath.Join(layersPath, "launch.sbom.spdx.json"), []byte{}, 0600)).To(Succeed())
|
||||||
|
|
||||||
|
libcnb.Build(builder,
|
||||||
|
libcnb.WithBOMLabel(true),
|
||||||
|
libcnb.WithArguments([]string{commandPath, layersPath, platformPath, buildpackPlanPath}),
|
||||||
|
libcnb.WithExitHandler(exitHandler),
|
||||||
|
)
|
||||||
|
|
||||||
|
Expect(exitHandler.Calls).To(BeEmpty())
|
||||||
|
})
|
||||||
|
|
||||||
|
it("has no matching formats", func() {
|
||||||
|
Expect(os.WriteFile(filepath.Join(layersPath, "launch.sbom.spdx.json"), []byte{}, 0600)).To(Succeed())
|
||||||
|
|
||||||
|
libcnb.Build(builder,
|
||||||
|
libcnb.WithBOMLabel(true),
|
||||||
|
libcnb.WithArguments([]string{commandPath, layersPath, platformPath, buildpackPlanPath}),
|
||||||
|
libcnb.WithExitHandler(exitHandler),
|
||||||
|
)
|
||||||
|
|
||||||
|
Expect(exitHandler.Calls[0].Arguments.Get(0)).To(MatchError("unable to validate SBOM\nunable to find actual SBOM Type application/spdx+json in list of supported SBOM types [application/vnd.cyclonedx+json]"))
|
||||||
|
})
|
||||||
|
|
||||||
|
it("has a matching format", func() {
|
||||||
|
Expect(os.WriteFile(filepath.Join(layersPath, "launch.sbom.cdx.json"), []byte{}, 0600)).To(Succeed())
|
||||||
|
Expect(os.WriteFile(filepath.Join(layersPath, "layer.sbom.cdx.json"), []byte{}, 0600)).To(Succeed())
|
||||||
|
libcnb.Build(builder,
|
||||||
|
libcnb.WithBOMLabel(true),
|
||||||
|
libcnb.WithArguments([]string{commandPath, layersPath, platformPath, buildpackPlanPath}),
|
||||||
|
libcnb.WithExitHandler(exitHandler),
|
||||||
|
)
|
||||||
|
|
||||||
|
Expect(exitHandler.Calls).To(BeEmpty())
|
||||||
|
})
|
||||||
|
|
||||||
|
it("has a junk format", func() {
|
||||||
|
Expect(os.WriteFile(filepath.Join(layersPath, "launch.sbom.random.json"), []byte{}, 0600)).To(Succeed())
|
||||||
|
Expect(os.WriteFile(filepath.Join(layersPath, "layer.sbom.cdx.json"), []byte{}, 0600)).To(Succeed())
|
||||||
|
libcnb.Build(builder,
|
||||||
|
libcnb.WithBOMLabel(true),
|
||||||
|
libcnb.WithArguments([]string{commandPath, layersPath, platformPath, buildpackPlanPath}),
|
||||||
|
libcnb.WithExitHandler(exitHandler),
|
||||||
|
)
|
||||||
|
|
||||||
|
Expect(exitHandler.Calls[0].Arguments.Get(0)).To(MatchError("unable to validate SBOM\nunable to parse SBOM unknown\nunable to translate from random.json to SBOMFormat"))
|
||||||
|
})
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
|
@ -41,6 +41,9 @@ type BuildpackInfo struct {
|
||||||
|
|
||||||
// Licenses a list of buildpack licenses.
|
// Licenses a list of buildpack licenses.
|
||||||
Licenses []License `toml:"licenses"`
|
Licenses []License `toml:"licenses"`
|
||||||
|
|
||||||
|
// SBOM is the list of supported SBOM media types
|
||||||
|
SBOMFormats []string `toml:"sbom-formats"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// License contains information about a Software License
|
// License contains information about a Software License
|
||||||
|
@ -91,7 +94,7 @@ type Buildpack struct {
|
||||||
Info BuildpackInfo `toml:"buildpack"`
|
Info BuildpackInfo `toml:"buildpack"`
|
||||||
|
|
||||||
// Path is the path to the buildpack.
|
// Path is the path to the buildpack.
|
||||||
Path string
|
Path string `toml:"-"`
|
||||||
|
|
||||||
// Stacks is the collection of stacks supported by the buildpack.
|
// Stacks is the collection of stacks supported by the buildpack.
|
||||||
Stacks []BuildpackStack `toml:"stacks"`
|
Stacks []BuildpackStack `toml:"stacks"`
|
||||||
|
|
|
@ -0,0 +1,51 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2018-2020 the original author or authors.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* https://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package libcnb_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/BurntSushi/toml"
|
||||||
|
"github.com/sclevine/spec"
|
||||||
|
|
||||||
|
"github.com/buildpacks/libcnb"
|
||||||
|
|
||||||
|
. "github.com/onsi/gomega"
|
||||||
|
)
|
||||||
|
|
||||||
|
func testBuildpackTOML(t *testing.T, _ spec.G, it spec.S) {
|
||||||
|
var (
|
||||||
|
Expect = NewWithT(t).Expect
|
||||||
|
)
|
||||||
|
|
||||||
|
it("does not serialize the Path field", func() {
|
||||||
|
bp := libcnb.Buildpack{
|
||||||
|
API: "0.6",
|
||||||
|
Info: libcnb.BuildpackInfo{
|
||||||
|
ID: "test-buildpack/sample",
|
||||||
|
Name: "sample",
|
||||||
|
},
|
||||||
|
Path: "../buildpack",
|
||||||
|
}
|
||||||
|
|
||||||
|
output := &bytes.Buffer{}
|
||||||
|
|
||||||
|
Expect(toml.NewEncoder(output).Encode(bp)).To(Succeed())
|
||||||
|
Expect(output.String()).NotTo(Or(ContainSubstring("Path = "), ContainSubstring("path = ")))
|
||||||
|
})
|
||||||
|
}
|
10
config.go
10
config.go
|
@ -65,6 +65,7 @@ type ExecDWriter interface {
|
||||||
// Config is an object that contains configurable properties for execution.
|
// Config is an object that contains configurable properties for execution.
|
||||||
type Config struct {
|
type Config struct {
|
||||||
arguments []string
|
arguments []string
|
||||||
|
bomLabel bool
|
||||||
environmentWriter EnvironmentWriter
|
environmentWriter EnvironmentWriter
|
||||||
exitHandler ExitHandler
|
exitHandler ExitHandler
|
||||||
tomlWriter TOMLWriter
|
tomlWriter TOMLWriter
|
||||||
|
@ -113,3 +114,12 @@ func WithExecDWriter(execdWriter ExecDWriter) Option {
|
||||||
return config
|
return config
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// WithBOMLabel creates an Option that enables/disables writing the BOM Label
|
||||||
|
// Deprecated: as of Buildpack API 0.7, to be removed in a future version
|
||||||
|
func WithBOMLabel(bomLabel bool) Option {
|
||||||
|
return func(config Config) Config {
|
||||||
|
config.bomLabel = bomLabel
|
||||||
|
return config
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
59
detect.go
59
detect.go
|
@ -24,6 +24,7 @@ import (
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/BurntSushi/toml"
|
"github.com/BurntSushi/toml"
|
||||||
|
"github.com/Masterminds/semver/v3"
|
||||||
|
|
||||||
"github.com/buildpacks/libcnb/internal"
|
"github.com/buildpacks/libcnb/internal"
|
||||||
"github.com/buildpacks/libcnb/poet"
|
"github.com/buildpacks/libcnb/poet"
|
||||||
|
@ -77,11 +78,6 @@ func Detect(detector Detector, options ...Option) {
|
||||||
config = option(config)
|
config = option(config)
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(config.arguments) != 3 {
|
|
||||||
config.exitHandler.Error(fmt.Errorf("expected 2 arguments and received %d", len(config.arguments)-1))
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
var (
|
var (
|
||||||
err error
|
err error
|
||||||
file string
|
file string
|
||||||
|
@ -115,20 +111,46 @@ func Detect(detector Detector, options ...Option) {
|
||||||
}
|
}
|
||||||
logger.Debugf("Buildpack: %+v", ctx.Buildpack)
|
logger.Debugf("Buildpack: %+v", ctx.Buildpack)
|
||||||
|
|
||||||
API := strings.TrimSpace(ctx.Buildpack.API)
|
API, err := semver.NewVersion(ctx.Buildpack.API)
|
||||||
if API != "0.5" && API != "0.6" {
|
if err != nil {
|
||||||
config.exitHandler.Error(errors.New("this version of libcnb is only compatible with buildpack API 0.5 and 0.6"))
|
config.exitHandler.Error(errors.New("version cannot be parsed"))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
ctx.Platform.Path = config.arguments[1]
|
compatVersionCheck, _ := semver.NewConstraint(fmt.Sprintf(">= %s, <= %s", MinSupportedBPVersion, MaxSupportedBPVersion))
|
||||||
|
if !compatVersionCheck.Check(API) {
|
||||||
|
config.exitHandler.Error(fmt.Errorf("this version of libcnb is only compatible with buildpack APIs >= %s, <= %s", MinSupportedBPVersion, MaxSupportedBPVersion))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
var buildPlanPath string
|
||||||
|
|
||||||
|
if API.LessThan(semver.MustParse("0.8")) {
|
||||||
|
if len(config.arguments) != 3 {
|
||||||
|
config.exitHandler.Error(fmt.Errorf("expected 2 arguments and received %d", len(config.arguments)-1))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
ctx.Platform.Path = config.arguments[1]
|
||||||
|
buildPlanPath = config.arguments[2]
|
||||||
|
} else {
|
||||||
|
ctx.Platform.Path, ok = os.LookupEnv("CNB_PLATFORM_DIR")
|
||||||
|
if !ok {
|
||||||
|
config.exitHandler.Error(fmt.Errorf("expected CNB_PLATFORM_DIR to be set"))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
buildPlanPath, ok = os.LookupEnv("CNB_BUILD_PLAN_PATH")
|
||||||
|
if !ok {
|
||||||
|
config.exitHandler.Error(fmt.Errorf("expected CNB_BUILD_PLAN_PATH to be set"))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if logger.IsDebugEnabled() {
|
if logger.IsDebugEnabled() {
|
||||||
logger.Debug(PlatformFormatter(ctx.Platform))
|
logger.Debug(PlatformFormatter(ctx.Platform))
|
||||||
}
|
}
|
||||||
|
|
||||||
file = filepath.Join(ctx.Platform.Path, "bindings")
|
if ctx.Platform.Bindings, err = NewBindingsForBuild(ctx.Platform.Path); err != nil {
|
||||||
if ctx.Platform.Bindings, err = NewBindingsFromPath(file); err != nil {
|
config.exitHandler.Error(fmt.Errorf("unable to read platform bindings %s\n%w", ctx.Platform.Path, err))
|
||||||
config.exitHandler.Error(fmt.Errorf("unable to read platform bindings %s\n%w", file, err))
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
logger.Debugf("Platform Bindings: %+v", ctx.Platform.Bindings)
|
logger.Debugf("Platform Bindings: %+v", ctx.Platform.Bindings)
|
||||||
|
@ -141,10 +163,10 @@ func Detect(detector Detector, options ...Option) {
|
||||||
logger.Debugf("Platform Environment: %s", ctx.Platform.Environment)
|
logger.Debugf("Platform Environment: %s", ctx.Platform.Environment)
|
||||||
|
|
||||||
if ctx.StackID, ok = os.LookupEnv("CNB_STACK_ID"); !ok {
|
if ctx.StackID, ok = os.LookupEnv("CNB_STACK_ID"); !ok {
|
||||||
config.exitHandler.Error(fmt.Errorf("CNB_STACK_ID not set"))
|
logger.Debug("CNB_STACK_ID not set")
|
||||||
return
|
} else {
|
||||||
|
logger.Debugf("Stack: %s", ctx.StackID)
|
||||||
}
|
}
|
||||||
logger.Debugf("Stack: %s", ctx.StackID)
|
|
||||||
|
|
||||||
result, err := detector.Detect(ctx)
|
result, err := detector.Detect(ctx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -167,10 +189,9 @@ func Detect(detector Detector, options ...Option) {
|
||||||
plans.Or = result.Plans[1:]
|
plans.Or = result.Plans[1:]
|
||||||
}
|
}
|
||||||
|
|
||||||
file = config.arguments[2]
|
logger.Debugf("Writing build plans: %s <= %+v", buildPlanPath, plans)
|
||||||
logger.Debugf("Writing build plans: %s <= %+v", file, plans)
|
if err := config.tomlWriter.Write(buildPlanPath, plans); err != nil {
|
||||||
if err := config.tomlWriter.Write(file, plans); err != nil {
|
config.exitHandler.Error(fmt.Errorf("unable to write buildplan %s\n%w", buildPlanPath, err))
|
||||||
config.exitHandler.Error(fmt.Errorf("unable to write buildplan %s\n%w", file, err))
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
194
detect_test.go
194
detect_test.go
|
@ -18,7 +18,6 @@ package libcnb_test
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"io/ioutil"
|
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"testing"
|
"testing"
|
||||||
|
@ -50,16 +49,15 @@ func testDetect(t *testing.T, context spec.G, it spec.S) {
|
||||||
it.Before(func() {
|
it.Before(func() {
|
||||||
var err error
|
var err error
|
||||||
|
|
||||||
applicationPath, err = ioutil.TempDir("", "detect-application-path")
|
applicationPath = t.TempDir()
|
||||||
Expect(err).NotTo(HaveOccurred())
|
|
||||||
applicationPath, err = filepath.EvalSymlinks(applicationPath)
|
applicationPath, err = filepath.EvalSymlinks(applicationPath)
|
||||||
Expect(err).NotTo(HaveOccurred())
|
Expect(err).NotTo(HaveOccurred())
|
||||||
|
|
||||||
buildpackPath, err = ioutil.TempDir("", "detect-buildpack-path")
|
buildpackPath = t.TempDir()
|
||||||
Expect(err).NotTo(HaveOccurred())
|
Expect(err).NotTo(HaveOccurred())
|
||||||
Expect(os.Setenv("CNB_BUILDPACK_DIR", buildpackPath)).To(Succeed())
|
Expect(os.Setenv("CNB_BUILDPACK_DIR", buildpackPath)).To(Succeed())
|
||||||
|
|
||||||
Expect(ioutil.WriteFile(filepath.Join(buildpackPath, "buildpack.toml"),
|
Expect(os.WriteFile(filepath.Join(buildpackPath, "buildpack.toml"),
|
||||||
[]byte(`
|
[]byte(`
|
||||||
api = "0.6"
|
api = "0.6"
|
||||||
|
|
||||||
|
@ -89,7 +87,7 @@ test-key = "test-value"
|
||||||
0600),
|
0600),
|
||||||
).To(Succeed())
|
).To(Succeed())
|
||||||
|
|
||||||
f, err := ioutil.TempFile("", "detect-buildplan-path")
|
f, err := os.CreateTemp("", "detect-buildplan-path")
|
||||||
Expect(err).NotTo(HaveOccurred())
|
Expect(err).NotTo(HaveOccurred())
|
||||||
Expect(f.Close()).NotTo(HaveOccurred())
|
Expect(f.Close()).NotTo(HaveOccurred())
|
||||||
buildPlanPath = f.Name()
|
buildPlanPath = f.Name()
|
||||||
|
@ -103,21 +101,22 @@ test-key = "test-value"
|
||||||
exitHandler.On("Fail")
|
exitHandler.On("Fail")
|
||||||
exitHandler.On("Pass")
|
exitHandler.On("Pass")
|
||||||
|
|
||||||
platformPath, err = ioutil.TempDir("", "detect-platform-path")
|
platformPath = t.TempDir()
|
||||||
Expect(err).NotTo(HaveOccurred())
|
|
||||||
|
|
||||||
Expect(os.MkdirAll(filepath.Join(platformPath, "bindings", "alpha"), 0755)).To(Succeed())
|
Expect(os.MkdirAll(filepath.Join(platformPath, "bindings", "alpha"), 0755)).To(Succeed())
|
||||||
Expect(ioutil.WriteFile(filepath.Join(platformPath, "bindings", "alpha", "test-secret-key"),
|
Expect(os.WriteFile(filepath.Join(platformPath, "bindings", "alpha", "test-secret-key"),
|
||||||
[]byte("test-secret-value"), 0600)).To(Succeed())
|
[]byte("test-secret-value"), 0600)).To(Succeed())
|
||||||
|
|
||||||
Expect(os.MkdirAll(filepath.Join(platformPath, "env"), 0755)).To(Succeed())
|
Expect(os.MkdirAll(filepath.Join(platformPath, "env"), 0755)).To(Succeed())
|
||||||
Expect(ioutil.WriteFile(filepath.Join(platformPath, "env", "TEST_ENV"), []byte("test-value"), 0600)).
|
Expect(os.WriteFile(filepath.Join(platformPath, "env", "TEST_ENV"), []byte("test-value"), 0600)).
|
||||||
To(Succeed())
|
To(Succeed())
|
||||||
|
|
||||||
tomlWriter = &mocks.TOMLWriter{}
|
tomlWriter = &mocks.TOMLWriter{}
|
||||||
tomlWriter.On("Write", mock.Anything, mock.Anything).Return(nil)
|
tomlWriter.On("Write", mock.Anything, mock.Anything).Return(nil)
|
||||||
|
|
||||||
Expect(os.Setenv("CNB_STACK_ID", "test-stack-id")).To(Succeed())
|
Expect(os.Setenv("CNB_STACK_ID", "test-stack-id")).To(Succeed())
|
||||||
|
Expect(os.Setenv("CNB_PLATFORM_DIR", platformPath)).To(Succeed())
|
||||||
|
Expect(os.Setenv("CNB_BUILD_PLAN_PATH", buildPlanPath)).To(Succeed())
|
||||||
|
|
||||||
workingDir, err = os.Getwd()
|
workingDir, err = os.Getwd()
|
||||||
Expect(err).NotTo(HaveOccurred())
|
Expect(err).NotTo(HaveOccurred())
|
||||||
|
@ -128,6 +127,8 @@ test-key = "test-value"
|
||||||
Expect(os.Chdir(workingDir)).To(Succeed())
|
Expect(os.Chdir(workingDir)).To(Succeed())
|
||||||
Expect(os.Unsetenv("CNB_BUILDPACK_DIR")).To(Succeed())
|
Expect(os.Unsetenv("CNB_BUILDPACK_DIR")).To(Succeed())
|
||||||
Expect(os.Unsetenv("CNB_STACK_ID")).To(Succeed())
|
Expect(os.Unsetenv("CNB_STACK_ID")).To(Succeed())
|
||||||
|
Expect(os.Unsetenv("CNB_PLATFORM_DIR")).To(Succeed())
|
||||||
|
Expect(os.Unsetenv("CNB_BUILD_PLAN_PATH")).To(Succeed())
|
||||||
|
|
||||||
Expect(os.RemoveAll(applicationPath)).To(Succeed())
|
Expect(os.RemoveAll(applicationPath)).To(Succeed())
|
||||||
Expect(os.RemoveAll(buildpackPath)).To(Succeed())
|
Expect(os.RemoveAll(buildpackPath)).To(Succeed())
|
||||||
|
@ -135,9 +136,9 @@ test-key = "test-value"
|
||||||
Expect(os.RemoveAll(platformPath)).To(Succeed())
|
Expect(os.RemoveAll(platformPath)).To(Succeed())
|
||||||
})
|
})
|
||||||
|
|
||||||
context("buildpack API is not 0.5 or 0.6", func() {
|
context("buildpack API is not within the supported range", func() {
|
||||||
it.Before(func() {
|
it.Before(func() {
|
||||||
Expect(ioutil.WriteFile(filepath.Join(buildpackPath, "buildpack.toml"),
|
Expect(os.WriteFile(filepath.Join(buildpackPath, "buildpack.toml"),
|
||||||
[]byte(`
|
[]byte(`
|
||||||
api = "0.4"
|
api = "0.4"
|
||||||
|
|
||||||
|
@ -157,7 +158,7 @@ version = "1.1.1"
|
||||||
)
|
)
|
||||||
|
|
||||||
Expect(exitHandler.Calls[0].Arguments.Get(0)).To(MatchError(
|
Expect(exitHandler.Calls[0].Arguments.Get(0)).To(MatchError(
|
||||||
"this version of libcnb is only compatible with buildpack API 0.5 and 0.6",
|
fmt.Sprintf("this version of libcnb is only compatible with buildpack APIs >= %s, <= %s", libcnb.MinSupportedBPVersion, libcnb.MaxSupportedBPVersion),
|
||||||
))
|
))
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
@ -173,65 +174,130 @@ version = "1.1.1"
|
||||||
Expect(exitHandler.Calls[0].Arguments.Get(0)).To(MatchError("expected 2 arguments and received 0"))
|
Expect(exitHandler.Calls[0].Arguments.Get(0)).To(MatchError("expected 2 arguments and received 0"))
|
||||||
})
|
})
|
||||||
|
|
||||||
it("doesn't receive CNB_STACK_ID", func() {
|
context("errors if required env vars are not set for buildpack API >=0.8", func() {
|
||||||
Expect(os.Unsetenv("CNB_STACK_ID")).To(Succeed())
|
for _, e := range []string{"CNB_PLATFORM_DIR", "CNB_BUILD_PLAN_PATH"} {
|
||||||
detector.On("Detect", mock.Anything).Return(libcnb.DetectResult{}, nil)
|
// We need to do this assignment because of the way that spec binds variables
|
||||||
|
envVar := e
|
||||||
|
context(fmt.Sprintf("when %s is unset", envVar), func() {
|
||||||
|
it.Before(func() {
|
||||||
|
Expect(os.WriteFile(filepath.Join(buildpackPath, "buildpack.toml"),
|
||||||
|
[]byte(`
|
||||||
|
api = "0.8"
|
||||||
|
|
||||||
|
[buildpack]
|
||||||
|
id = "test-id"
|
||||||
|
name = "test-name"
|
||||||
|
version = "1.1.1"
|
||||||
|
`),
|
||||||
|
0600),
|
||||||
|
).To(Succeed())
|
||||||
|
os.Unsetenv(envVar)
|
||||||
|
})
|
||||||
|
|
||||||
libcnb.Detect(detector,
|
it("fails", func() {
|
||||||
libcnb.WithArguments([]string{commandPath, platformPath, buildPlanPath}),
|
libcnb.Detect(detector,
|
||||||
libcnb.WithExitHandler(exitHandler),
|
libcnb.WithArguments([]string{commandPath}),
|
||||||
)
|
libcnb.WithExitHandler(exitHandler),
|
||||||
|
)
|
||||||
Expect(exitHandler.Calls[0].Arguments.Get(0)).To(MatchError("CNB_STACK_ID not set"))
|
Expect(exitHandler.Calls[0].Arguments.Get(0)).To(MatchError(
|
||||||
|
fmt.Sprintf("expected %s to be set", envVar),
|
||||||
|
))
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
it("creates context", func() {
|
context("when BP API >= 0.8", func() {
|
||||||
detector.On("Detect", mock.Anything).Return(libcnb.DetectResult{Pass: true}, nil)
|
it.Before(func() {
|
||||||
|
Expect(os.WriteFile(filepath.Join(buildpackPath, "buildpack.toml"),
|
||||||
|
[]byte(`
|
||||||
|
api = "0.8"
|
||||||
|
|
||||||
|
[buildpack]
|
||||||
|
id = "test-id"
|
||||||
|
name = "test-name"
|
||||||
|
version = "1.1.1"
|
||||||
|
`),
|
||||||
|
0600),
|
||||||
|
).To(Succeed())
|
||||||
|
})
|
||||||
|
|
||||||
libcnb.Detect(detector,
|
it("creates context", func() {
|
||||||
libcnb.WithArguments([]string{commandPath, platformPath, buildPlanPath}),
|
detector.On("Detect", mock.Anything).Return(libcnb.DetectResult{Pass: true}, nil)
|
||||||
libcnb.WithExitHandler(exitHandler),
|
|
||||||
)
|
|
||||||
|
|
||||||
ctx := detector.Calls[0].Arguments[0].(libcnb.DetectContext)
|
libcnb.Detect(detector,
|
||||||
Expect(ctx.Application).To(Equal(libcnb.Application{Path: applicationPath}))
|
libcnb.WithArguments([]string{commandPath}),
|
||||||
Expect(ctx.Buildpack).To(Equal(libcnb.Buildpack{
|
libcnb.WithExitHandler(exitHandler),
|
||||||
API: "0.6",
|
)
|
||||||
Info: libcnb.BuildpackInfo{
|
|
||||||
ID: "test-id",
|
ctx := detector.Calls[0].Arguments[0].(libcnb.DetectContext)
|
||||||
Name: "test-name",
|
Expect(ctx.Application).To(Equal(libcnb.Application{Path: applicationPath}))
|
||||||
Version: "1.1.1",
|
Expect(ctx.Buildpack).To(Equal(libcnb.Buildpack{
|
||||||
ClearEnvironment: true,
|
API: "0.8",
|
||||||
Description: "A test buildpack",
|
Info: libcnb.BuildpackInfo{
|
||||||
Keywords: []string{"test", "buildpack"},
|
ID: "test-id",
|
||||||
Licenses: []libcnb.License{
|
Name: "test-name",
|
||||||
{Type: "Apache-2.0", URI: "https://spdx.org/licenses/Apache-2.0.html"},
|
Version: "1.1.1",
|
||||||
{Type: "Apache-1.1", URI: "https://spdx.org/licenses/Apache-1.1.html"},
|
|
||||||
},
|
},
|
||||||
},
|
Path: buildpackPath,
|
||||||
Path: buildpackPath,
|
}))
|
||||||
Stacks: []libcnb.BuildpackStack{
|
Expect(ctx.Platform).To(Equal(libcnb.Platform{
|
||||||
{
|
Bindings: libcnb.Bindings{
|
||||||
ID: "test-id",
|
libcnb.Binding{
|
||||||
Mixins: []string{"test-name"},
|
Name: "alpha",
|
||||||
},
|
Path: filepath.Join(platformPath, "bindings", "alpha"),
|
||||||
},
|
Secret: map[string]string{
|
||||||
Metadata: map[string]interface{}{"test-key": "test-value"},
|
"test-secret-key": "test-secret-value",
|
||||||
}))
|
},
|
||||||
Expect(ctx.Platform).To(Equal(libcnb.Platform{
|
|
||||||
Bindings: libcnb.Bindings{
|
|
||||||
libcnb.Binding{
|
|
||||||
Name: "alpha",
|
|
||||||
Path: filepath.Join(platformPath, "bindings", "alpha"),
|
|
||||||
Secret: map[string]string{
|
|
||||||
"test-secret-key": "test-secret-value",
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
Environment: map[string]string{"TEST_ENV": "test-value"},
|
||||||
Environment: map[string]string{"TEST_ENV": "test-value"},
|
Path: platformPath,
|
||||||
Path: platformPath,
|
}))
|
||||||
}))
|
Expect(ctx.StackID).To(Equal("test-stack-id"))
|
||||||
Expect(ctx.StackID).To(Equal("test-stack-id"))
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
context("when BP API < 0.8", func() {
|
||||||
|
it.Before(func() {
|
||||||
|
Expect(os.Unsetenv("CNB_PLATFORM_DIR")).To(Succeed())
|
||||||
|
Expect(os.Unsetenv("CNB_BUILD_PLAN_PATH")).To(Succeed())
|
||||||
|
})
|
||||||
|
|
||||||
|
it("creates context", func() {
|
||||||
|
detector.On("Detect", mock.Anything).Return(libcnb.DetectResult{Pass: true}, nil)
|
||||||
|
|
||||||
|
libcnb.Detect(detector,
|
||||||
|
libcnb.WithArguments([]string{commandPath, platformPath, buildPlanPath}),
|
||||||
|
libcnb.WithExitHandler(exitHandler),
|
||||||
|
)
|
||||||
|
|
||||||
|
ctx := detector.Calls[0].Arguments[0].(libcnb.DetectContext)
|
||||||
|
Expect(ctx.Application).To(Equal(libcnb.Application{Path: applicationPath}))
|
||||||
|
Expect(ctx.Buildpack).To(Equal(libcnb.Buildpack{
|
||||||
|
API: "0.6",
|
||||||
|
Info: libcnb.BuildpackInfo{
|
||||||
|
ID: "test-id",
|
||||||
|
Name: "test-name",
|
||||||
|
Version: "1.1.1",
|
||||||
|
ClearEnvironment: true,
|
||||||
|
Description: "A test buildpack",
|
||||||
|
Keywords: []string{"test", "buildpack"},
|
||||||
|
Licenses: []libcnb.License{
|
||||||
|
{Type: "Apache-2.0", URI: "https://spdx.org/licenses/Apache-2.0.html"},
|
||||||
|
{Type: "Apache-1.1", URI: "https://spdx.org/licenses/Apache-1.1.html"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Path: buildpackPath,
|
||||||
|
Stacks: []libcnb.BuildpackStack{
|
||||||
|
{
|
||||||
|
ID: "test-id",
|
||||||
|
Mixins: []string{"test-name"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Metadata: map[string]interface{}{"test-key": "test-value"},
|
||||||
|
}))
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
it("extracts buildpack path from command path if CNB_BUILDPACK_PATH is not set", func() {
|
it("extracts buildpack path from command path if CNB_BUILDPACK_PATH is not set", func() {
|
||||||
|
|
|
@ -0,0 +1,219 @@
|
||||||
|
# Building
|
||||||
|
|
||||||
|
A buildpack is suitably packaged `detect` binary, a `build` binary and a `buildpack.toml`. Here we look at the implementation logic of the build binary for our buildpack. This starts by declaring a `struct` that we call `Builder`. The purpose of our struct is to satisfy `libcnb`s [`Builder`](https://pkg.go.dev/github.com/buildpacks/libcnb?utm_source=gopls#Builder) interface providing a receiver with signature `func Build(context BuildContext) (BuildResult, error)`. An implementation of `Builder` is passed to `libcnb.Main` as the main entry point to the `build` binary.
|
||||||
|
|
||||||
|
In our `Detector` we use a [`poet`](https://pkg.go.dev/github.com/buildpacks/libcnb/poet) logger instance. The logger instance will be used to inform the user of the progress of our `detect` phase.
|
||||||
|
|
||||||
|
```go
|
||||||
|
type Builder struct {
|
||||||
|
Logger poet.Logger
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Implementing Build
|
||||||
|
|
||||||
|
The most simple implementation of `func (context BuildContext) (BuildResult, error)` returns a `BuildResult` containing an ordered list `LayerContributor`s which will each contribute layers to the image.
|
||||||
|
|
||||||
|
```go
|
||||||
|
func (d Builder) Build(context libcnb.BuildContext) (libcnb.BuildResult, error) {
|
||||||
|
return libcnb.NewBuildResult(), nil
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
The trivial passing implementation of `Build` contributes no layers to the result.
|
||||||
|
|
||||||
|
This trivial case ignores the `Plans` field of the `DetectResult` struct. In order to understand the `Plans` field we look at the concept of provides and requires.
|
||||||
|
|
||||||
|
### Builders and Contributors
|
||||||
|
|
||||||
|
It is recommended that a single `Builder` provides each layer using a `Contributor`. In this example we are contributing a single layer containing our extracted archive. Therefore, our `Builder` will orchestrate a single `Contributor`.
|
||||||
|
|
||||||
|
### Accessing `BuildPlan` Metadata
|
||||||
|
|
||||||
|
The `detect` phases passes metadata to the `build` phase in the [`BuildpackPlan`](https://pkg.go.dev/github.com/buildpacks/libcnb#BuildpackPlan) (note: not to be confused with the more general [`BuildPlan`](https://pkg.go.dev/github.com/buildpacks/libcnb#BuildPlan)). Multiple buildpack `Detect` executions can require that the `Build` of our example buildpack executes. As there may be requirements from multiple `detect` binaries, we must merge all the entries in the buildplan that correspond to our "example" buildpack.
|
||||||
|
|
||||||
|
```go
|
||||||
|
func (b Builder) Build(context libcnb.BuildContext) (libcnb.BuildResult, error) {
|
||||||
|
b.Logger.Title(context.Buildpack)
|
||||||
|
// Resolve the "version" that the detect phase adds to the BuildPlan metadata
|
||||||
|
version, err := resolveVersion(context.Plan)
|
||||||
|
if err != nil {
|
||||||
|
return libcnb.NewBuildResult(), err
|
||||||
|
}
|
||||||
|
// If "version" was not supplied, use a default
|
||||||
|
if version == "" {
|
||||||
|
version = "0.0.1"
|
||||||
|
}
|
||||||
|
|
||||||
|
return libcnb.NewBuildResult(), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// resolveVersion takes the buildpack plan and resolves the version metadata
|
||||||
|
// It then returrns the "version" information extracted from the metadata.
|
||||||
|
func resolveVersion(plan libcnb.BuildpackPlan) (string, error) {
|
||||||
|
exampleBuildPlan := libcnb.BuildpackPlanEntry{}
|
||||||
|
|
||||||
|
// Find the buildpack plan entry that releates to our "example" buildpack
|
||||||
|
for _, e := range plan.Entries {
|
||||||
|
if e.Name == "example" {
|
||||||
|
exampleBuildPlan = e
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Extract the "version" metadata if present
|
||||||
|
if version, ok := exampleBuildPlan.Metadata["version"].(string); ok {
|
||||||
|
return version, nil
|
||||||
|
}
|
||||||
|
return "", fmt.Errorf("unable to resolve version from metadata.")
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
In general, multiple buildpacks could `Require` our example buildpack. In that general case there may be multiple `BuildpackPlan` entries referring to our `example` buildpack. A production-level utility function to resolve the build plan can be found in [`libpak`](https://github.com/paketo-buildpacks/libpak/blob/f24422191fc6a2a02178337d96dda1210faaae9f/buildpack_plan.go#L69).
|
||||||
|
|
||||||
|
### Parsing the Buildpack Metadata
|
||||||
|
|
||||||
|
We download the files to extract into a layer from an archive on the Internet. To make this easier to maintain, we embed the download URL as a string template in the `builpack.toml` metadata. Our download URL is of the form `"https://source.fake/releases/v{{ .version }}/example_{{ .version }}_linux_amd64.tar.gz"`. We accept the version parameter as detected in the `detect` phase, and replace the version into our download URL.
|
||||||
|
|
||||||
|
```go
|
||||||
|
func (b Builder) Build(context libcnb.BuildContext) (libcnb.BuildResult, error) {
|
||||||
|
b.Logger.Title(context.Buildpack)
|
||||||
|
version, err := resolveVersion(context.Plan)
|
||||||
|
if err != nil {
|
||||||
|
return libcnb.NewBuildResult(), err
|
||||||
|
}
|
||||||
|
if version == "" {
|
||||||
|
version = "0.0.1"
|
||||||
|
}
|
||||||
|
|
||||||
|
// Use the standard Go text/template to compute the download URL from the
|
||||||
|
// version resolved from the buildpack plan.
|
||||||
|
urlTemplateMetadata, found := context.Buildpack.Metadata["url"]
|
||||||
|
if !found {
|
||||||
|
return libcnb.NewBuildResult(), fmt.Errorf("no url template in buildpack.toml metadata")
|
||||||
|
}
|
||||||
|
urlTemplate, ok := urlTemplateMetadata.(string)
|
||||||
|
if !ok {
|
||||||
|
return libcnb.NewBuildResult(), fmt.Errorf("unable to parse buildpack.toml metadata")
|
||||||
|
}
|
||||||
|
archiveURL, err := template.New("ArchiveVersion").Parse(urlTemplate)
|
||||||
|
if err != nil {
|
||||||
|
return libcnb.NewBuildResult(), fmt.Errorf("unable buildpack.toml url metadata is not a valid template")
|
||||||
|
}
|
||||||
|
var url bytes.Buffer
|
||||||
|
archiveURL.Execute(&url, struct{ version string }{version: version})
|
||||||
|
|
||||||
|
return libcnb.NewBuildResult(), nil
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Now that we know our download URL, we are in a position to contribute a layer containing the extracted files.
|
||||||
|
|
||||||
|
### Contributing a Layer
|
||||||
|
|
||||||
|
To contribute a layer we implement the [`LayerContributor`](https://pkg.go.dev/github.com/buildpacks/libcnb#LayerContributor) interface. Our `Contributor` will download an archive from the provided URL. Again, we borrow a utility function from `libpak` to extract the archive to an output layer. You could use standard Go archive functions to do this, but it's more work.
|
||||||
|
|
||||||
|
```go
|
||||||
|
type Contributor struct {
|
||||||
|
Logger log.Logger
|
||||||
|
Archive string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (Contributor) Name() string {
|
||||||
|
return "example"
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c Contributor) Contribute(layer libcnb.Layer) (libcnb.Layer, error) {
|
||||||
|
// the values for caching layers are documented at
|
||||||
|
// https://buildpacks.io/docs/buildpack-author-guide/caching-strategies/
|
||||||
|
// Here we make the layer available for caching, as a build layer and as a
|
||||||
|
// launch layer on the output image
|
||||||
|
layer.Launch = true
|
||||||
|
layer.Cache = true
|
||||||
|
layer.Build = true
|
||||||
|
|
||||||
|
if err := os.MkdirAll(layer.Exec.Path, 0755); err != nil {
|
||||||
|
return libcnb.Layer{}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fetch the archive from the Internet
|
||||||
|
resp, err := http.Get(c.Archive)
|
||||||
|
if err != nil {
|
||||||
|
return libcnb.Layer{}, fmt.Errorf("unable to download archive\n%w", err)
|
||||||
|
}
|
||||||
|
defer resp.Body.Close()
|
||||||
|
|
||||||
|
// Extract the archive into the output layer
|
||||||
|
if err := crush.Extract(resp.Body, layer.Path, 0); err != nil {
|
||||||
|
return libcnb.Layer{}, fmt.Errorf("unable to expand archive\n%w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return layer, nil
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
We add our contributor to the `libcnb.BuildContext` layers. `libcnb` will ensure each `LayerContributor` is executed in the order that they are appended to the `BuildResult.Layers` list.
|
||||||
|
|
||||||
|
```go
|
||||||
|
func (b Builder) Build(context libcnb.BuildContext) (libcnb.BuildResult, error) {
|
||||||
|
b.Logger.Title(context.Buildpack)
|
||||||
|
|
||||||
|
...
|
||||||
|
|
||||||
|
contributor := Contributor{
|
||||||
|
Logger: b.Logger,
|
||||||
|
Archive: url.String(),
|
||||||
|
}
|
||||||
|
result := libcnb.NewBuildResult()
|
||||||
|
result.Layers = append(result.Layers, contributor)
|
||||||
|
return result, nil
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Contributing a Startup Script and Environmental Variable
|
||||||
|
|
||||||
|
Finally, we assume that our run image contains a `bash` shell. An [`exec.d` style](https://buildpacks.io/docs/reference/spec/migration/buildpack-api-0.4-0.5/#execd) executable is contributed as to run on container startup.
|
||||||
|
|
||||||
|
An environment variable `EXAMPLE` is contributed to the image with the default value of `1.2.3`. It is left as an exercise to the reader to pass the `version` information from `Build` through the instance of `type Contributor struct` to the `EXAMPLE` environmental variable.
|
||||||
|
|
||||||
|
```go
|
||||||
|
const ExampleExecD string = `
|
||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
echo Executed ${EXAMPLE} on Startup
|
||||||
|
`
|
||||||
|
|
||||||
|
func (c Contributor) Contribute(layer libcnb.Layer) (libcnb.Layer, error) {
|
||||||
|
layer.Launch = true
|
||||||
|
layer.Cache = true
|
||||||
|
layer.Build = true
|
||||||
|
|
||||||
|
if err := os.MkdirAll(layer.Exec.Path, 0755); err != nil {
|
||||||
|
return libcnb.Layer{}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// fetch and extract tar.gz
|
||||||
|
resp, err := http.Get(c.Archive)
|
||||||
|
if err != nil {
|
||||||
|
return libcnb.Layer{}, fmt.Errorf("unable to download archive\n%w", err)
|
||||||
|
}
|
||||||
|
defer resp.Body.Close()
|
||||||
|
if err := crush.Extract(resp.Body, layer.Path, 0); err != nil {
|
||||||
|
return libcnb.Layer{}, fmt.Errorf("unable to expand archive\n%w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// We are using the exec.d interface here per - https://github.com/buildpacks/spec/blob/main/buildpack.md#execd
|
||||||
|
// #nosec G306
|
||||||
|
if err := os.WriteFile(layer.Exec.FilePath("example"), []byte(ExampleExecD), 0755); err != nil {
|
||||||
|
return libcnb.Layer{}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set environment variable
|
||||||
|
layer.LaunchEnvironment.Default("EXAMPLE", "1.2.3")
|
||||||
|
|
||||||
|
return layer, nil
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Summary
|
||||||
|
|
||||||
|
We have taken a quick tour of implementing a `build` binary using `libcnb`. We have seen how `libcnb` offers a straightforward `BuildResult` that can be used to return a number of `LayerContributor`s. In turn, each `LayerContributor` can contribute files to the output image, scripts executed on startup and environmental variable on the output image. Our tour of the `libcnb` API included a look at `libcnb.BuildContext` through which we have access to the `BuildPlan` computed after the `detect` phase and buildpack metadata.
|
|
@ -0,0 +1,224 @@
|
||||||
|
# Detection
|
||||||
|
|
||||||
|
A buildpack is suitably packaged `detect` binary, a `build` binary and a `buildpack.toml`. Here we look at the implementation logic of the detect binary for our example buildpack. This starts by declaring a `struct` that we call `Detector`. The purpose of our struct is to satisfy `libcnb`s [`Detector`](https://pkg.go.dev/github.com/buildpacks/libcnb?utm_source=gopls#Detector) interface providing a receiver with signature `func Detect(context DetectContext) (DetectResult, error)`. An implementation of `Detector` is passed to `libcnb.Main` as the main entry point to the `detect` binary.
|
||||||
|
|
||||||
|
In our `Detector` we use a [`poet`](https://pkg.go.dev/github.com/buildpacks/libcnb/poet) logger instance. The logger instance will be used to inform the user of the progress of our `detect` phase.
|
||||||
|
|
||||||
|
```go
|
||||||
|
type Detector struct {
|
||||||
|
Logger poet.Logger
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Implementing Detect
|
||||||
|
|
||||||
|
The most simple implementation of `func (context DetectContext) (DetectResult, error)` returns a `DetectResult` ensuring that the this buildpack does not contribute to the build phase.
|
||||||
|
|
||||||
|
```go
|
||||||
|
func (d Detector) Detect(context DetectContext) (DetectResult, error) {
|
||||||
|
return DetectResult{}, nil
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
The trivial passing implementation of `Detect` ensures that the `build` binary of this buildpack will run during the build phase.
|
||||||
|
|
||||||
|
```go
|
||||||
|
func (d Detector) Detect(context DetectContext) (DetectResult, error) {
|
||||||
|
return DetectResult{Pass: true}, nil
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
These two trivial cases ignore the `Plans` field of the `DetectResult` struct. In order to understand the `Plans` field we look at the concept of provides and requires.
|
||||||
|
|
||||||
|
### Provides and Requires
|
||||||
|
|
||||||
|
Buildpacks may depend on other buildpacks. The dependencies are computed at runtime. Buildpacks can **provide** functionality that other buildpacks use. For example, a buildpack may provide a runtime such as the python runtime or a Java runtime environment; other buildpacks may detect that an application is a python or Java application and require the provided runtime. Buildpacks may also **require** functionality provided by other buildpacks.
|
||||||
|
|
||||||
|
A buildpack can offer multiple provides. However, it is common for a buildpack to offer only a single provide. Each provision is identified by a string. Here we declare `Example` as an identifier.
|
||||||
|
|
||||||
|
```go
|
||||||
|
const (
|
||||||
|
Example = "example"
|
||||||
|
)
|
||||||
|
```
|
||||||
|
|
||||||
|
Extending a trivial passing implementation with `Provides` allows another buildpack to require this buildpack.
|
||||||
|
|
||||||
|
```go
|
||||||
|
func (d Detector) Detect(context DetectContext) (DetectResult, error) {
|
||||||
|
return DetectResult{
|
||||||
|
Pass: true,
|
||||||
|
Plans: []libcnb.BuildPlan{
|
||||||
|
{
|
||||||
|
// Let the system know that if other buildpacks Require "example"
|
||||||
|
// then this buildpack Provides the implementation logic
|
||||||
|
Provides: []libcnb.BuildPlanProvide{
|
||||||
|
{Name: Example},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
The detect phase of another buildpack may require this buildpack. This is commonly used when the detect phase gathers information that is passed as metadata to the build phase. Similarly, it is common for a buildpack to require itself. This will allow the detect phase of a buildpack to pass information to the build phase.
|
||||||
|
|
||||||
|
```go
|
||||||
|
func (d Detector) Detect(context DetectContext) (DetectResult, error) {
|
||||||
|
return DetectResult{
|
||||||
|
Pass: true,
|
||||||
|
Plans: []libcnb.BuildPlan{
|
||||||
|
{
|
||||||
|
// Let the system know that if other buildpacks Require "example"
|
||||||
|
// then this buildpack Provides the implementation logic
|
||||||
|
Provides: []libcnb.BuildPlanProvide{
|
||||||
|
{Name: Example},
|
||||||
|
},
|
||||||
|
Requires: []libcmb.BuildPlanRequire{
|
||||||
|
{Name: Example},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Here we pass the key-value pair `(version, 1.0)` as metadata from the detect phase of the example buildpack to the build phase. Metadata can be arbitrarily nested, but it must be textual as it is serialized to TOML at the end of the detect phase.
|
||||||
|
|
||||||
|
```go
|
||||||
|
func (d Detector) Detect(context DetectContext) (DetectResult, error) {
|
||||||
|
return DetectResult{
|
||||||
|
Pass: true,
|
||||||
|
Plans: []libcnb.BuildPlan{
|
||||||
|
{
|
||||||
|
// Let the system know that if other buildpacks Require "example"
|
||||||
|
// then this buildpack Provides the implementation logic
|
||||||
|
Provides: []libcnb.BuildPlanProvide{
|
||||||
|
{Name: Example},
|
||||||
|
},
|
||||||
|
Requires: []libcmb.BuildPlanRequire{
|
||||||
|
{
|
||||||
|
Name: Example,
|
||||||
|
// Pass arbitrary metadata to the Example build phase
|
||||||
|
Metadata: {
|
||||||
|
"version": "1.0",
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
The `detect` binary is provided with read-only access to the application source code. As an example we can detect the presence of a file called `example.txt`. If this file does not exist, then we can fail at the detect phase.
|
||||||
|
|
||||||
|
```go
|
||||||
|
func (d Detector) Detect(context DetectContext) (DetectResult, error) {
|
||||||
|
const exampleFile := "example.txt"
|
||||||
|
// Construct a path to exampleFile in the Application source project
|
||||||
|
requirementsFile := filepath.Join(context.Application.Path, exampleFile)
|
||||||
|
// Test for the existance of exampleFile
|
||||||
|
if _, err := os.Stat(exampleFile); err != nil && os.IsNotExist(err) {
|
||||||
|
return libcnb.DetectResult{}, err
|
||||||
|
}
|
||||||
|
d.Logger.Debugf("Found %s example file -> %s", exampleFile)
|
||||||
|
...
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
We have defined a `detect` binary that passes if `example.txt` is present in the source application and fails otherwise.
|
||||||
|
|
||||||
|
During the detect phase the buildpack has access to static metadata declared in the `buildpack.toml` file.
|
||||||
|
|
||||||
|
## Accessing buildpack metadata
|
||||||
|
|
||||||
|
It is common for static metadata to be declared in a `buildpack.toml`. This is commonly used to pass URLs to the buildpack that are used to download artefacts. As an example, a `buildpack.toml` may declare the download location for the `example` artefact:
|
||||||
|
|
||||||
|
```toml
|
||||||
|
[metadata]
|
||||||
|
[[metadata.dependencies]]
|
||||||
|
url = "https://source.fake/releases/v{{ .version }}/example_{{ .version }}_linux_amd64.tar.gz"
|
||||||
|
```
|
||||||
|
|
||||||
|
In our running example the detect phase of our buildpack detects the presence of `example.txt`. We may want a buildpack definition to change the name of the file for detection.
|
||||||
|
|
||||||
|
```toml
|
||||||
|
[metadata]
|
||||||
|
[[metadata.dependencies]]
|
||||||
|
example-file = "custom-example.txt"
|
||||||
|
```
|
||||||
|
|
||||||
|
The `Detect(context)` function can access the metadata in the buildpack definition.
|
||||||
|
|
||||||
|
```go
|
||||||
|
func (d Detector) Detect(context DetectContext) (DetectResult, error) {
|
||||||
|
var exampleFile := "example.txt"
|
||||||
|
if configuredExampleFile, ok; context.Buildpack.Metadata["example-file"]; ok {
|
||||||
|
exampleFile = configuredExampleFile.(string)
|
||||||
|
}
|
||||||
|
// Construct a path to exampleFile in the Application source project
|
||||||
|
requirementsFile := filepath.Join(context.Application.Path, exampleFile)
|
||||||
|
// Test for the existance of exampleFile
|
||||||
|
if _, err := os.Stat(exampleFile); err != nil && os.IsNotExist(err) {
|
||||||
|
return libcnb.DetectResult{Pass: true}, err
|
||||||
|
}
|
||||||
|
d.Logger.Debugf("Found %s example file -> %s", exampleFile)
|
||||||
|
...
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
If `example.txt` can be overridden it is common to also provide an environment variable to allow overriding. Here we allow `example.txt` to be overridden using either a static definition in the `buildpack.toml` file or by the presence of a `BP_EXAMPLE_FILE` environment variable.
|
||||||
|
|
||||||
|
```go
|
||||||
|
const (
|
||||||
|
BpExampleFile = "BP_EXAMPLE_FILE"
|
||||||
|
)
|
||||||
|
|
||||||
|
func (d Detector) Detect(context DetectContext) (DetectResult, error) {
|
||||||
|
var exampleFile := "example.txt"
|
||||||
|
if configuredExampleFile, ok; context.Buildpack.Metadata["example-file"]; ok {
|
||||||
|
exampleFile = configuredExampleFile.(string)
|
||||||
|
}
|
||||||
|
if file, ok := context.Platform.Environment(BpExamplefile); ok {
|
||||||
|
exampleFile = file
|
||||||
|
}
|
||||||
|
// Construct a path to exampleFile in the Application source project
|
||||||
|
requirementsFile := filepath.Join(context.Application.Path, exampleFile)
|
||||||
|
// Test for the existance of exampleFile
|
||||||
|
if _, err := os.Stat(exampleFile); err != nil && os.IsNotExist(err) {
|
||||||
|
return libcnb.DetectResult{Pass: true}, err
|
||||||
|
}
|
||||||
|
d.Logger.Debugf("Found %s example file -> %s", exampleFile)
|
||||||
|
...
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Buildpack metadata and environment variables are useful when passing configuration to a buildpack `detect` binary. Passing sensitive data, such as secrets, requires a different approach.
|
||||||
|
|
||||||
|
## Secrets and other bindings
|
||||||
|
|
||||||
|
Secrets are available through the `DetectContext`. Here we look for a binding named "GIT_TOKEN" which we expect to define a secret called "TOKEN". We assume the existence of a function with signature `findBinding([]libcnb.Bindings, string) (libcnb.Binding, error)`.
|
||||||
|
|
||||||
|
```go
|
||||||
|
const (
|
||||||
|
GitTokenBinding = "GIT_TOKEN"
|
||||||
|
GitToken = "TOKEN"
|
||||||
|
)
|
||||||
|
|
||||||
|
func (d Detector) Detect(context DetectContext) (DetectResult, error) {
|
||||||
|
bindings := context.Platform.Bindings
|
||||||
|
gitTokenBinding, err := findBinding(bindings, GitTokenBinding)
|
||||||
|
if err != nil {
|
||||||
|
return DetectResult{}, fmt.errorf("unable to access binding %s", GitTokenBinding)
|
||||||
|
}
|
||||||
|
if gitToken, exists := gitTokenBinding.Secret[GitToken]; !exists {
|
||||||
|
return DetectResult{}, fmt.errorf("unable to find secret %s in binding %s", GitToken, GitTokenBinding)
|
||||||
|
}
|
||||||
|
...
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Summary
|
||||||
|
|
||||||
|
We have taken a quick tour of implementing a `detect` binary using `libcnb`. We have seen how `libcnb` offers a straightforward `DetectResult` that can be used to signal success or failure of the detect phase. In addition the `DetectResult` can offer `Provides` and specify `Requires`. Our tour of the `libcnb` API included a look at `libcnb.DetectContext` through which we have access to environment variables, buildpack metadata and bindings.
|
|
@ -0,0 +1,44 @@
|
||||||
|
# `libcnb` tutorial
|
||||||
|
|
||||||
|
[https://pkg.go.dev/github.com/buildpacks/libcnb?tab=doc](https://pkg.go.dev/github.com/buildpacks/libcnb?tab=doc) provides the reference documentation for `libcnb`. In this tutorial we walk through an example implementation using data structures and functions described in the reference documentation.
|
||||||
|
|
||||||
|
In general, buildpacks are responsible for
|
||||||
|
|
||||||
|
* installing runtimes eg: a `python` interpreter,
|
||||||
|
* installing dependency managers eg: Python's `pip` dependency manager,
|
||||||
|
* resolving application dependencies, and
|
||||||
|
* building/packaging the application code.
|
||||||
|
|
||||||
|
Commonly each of these responsibilities is factored out into a separate buildpacks.
|
||||||
|
|
||||||
|
In this tutorial we take a simplified look at a generic buildpack and implement both the `detect` and `build` binaries for an example buildpack.
|
||||||
|
|
||||||
|
## Scenario
|
||||||
|
|
||||||
|
Our buildpack will have the following properties:
|
||||||
|
|
||||||
|
* if the source project contains `example.txt` or `BP_EXAMPLE_FILE=some-file.txt` then the buildpack `detect` process will succeed.
|
||||||
|
* if `detect` passes, then our `build` binary will contribute a single layer to the output image. The layer will contain the contents of a gzipped archive fetched from an Internet location.
|
||||||
|
|
||||||
|
We first [`detect`](detect.md) whether this buildpack should contribute to the build. If detection passes we then run [`build`](build.md) to extract our example gzipped archive.
|
||||||
|
|
||||||
|
The main application of our buildpack will combine our implementation of `Detector` and `Builder` to provide the `detect` and `build` binaries:
|
||||||
|
|
||||||
|
```go
|
||||||
|
func main() {
|
||||||
|
detector := Detector{Logger: log.New(os.Stdout)}
|
||||||
|
builder := Builder{Logger: log.New(os.Stdout)}
|
||||||
|
libcnb.Main(detector, builder)
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Learning Goals
|
||||||
|
|
||||||
|
At the end of this tutorial you will have
|
||||||
|
|
||||||
|
* examined the separation of `detect` responsibilities from `build` responsibilities,
|
||||||
|
* introspected a source application to satisfy `detect`,
|
||||||
|
* passed metadata between the `detect` and `build` phases using the build plan,
|
||||||
|
* contributed a layer to an output image that contains the contents of an archive,
|
||||||
|
* set a default environment variable `EXAMPLE` on the output image, and
|
||||||
|
* defined an exec.d startup script on the output image.
|
|
@ -26,7 +26,7 @@ import (
|
||||||
"github.com/buildpacks/libcnb"
|
"github.com/buildpacks/libcnb"
|
||||||
)
|
)
|
||||||
|
|
||||||
func testEnvironment(t *testing.T, context spec.G, it spec.S) {
|
func testEnvironment(t *testing.T, _ spec.G, it spec.S) {
|
||||||
var (
|
var (
|
||||||
Expect = NewWithT(t).Expect
|
Expect = NewWithT(t).Expect
|
||||||
|
|
||||||
|
|
|
@ -28,7 +28,7 @@ import (
|
||||||
"github.com/buildpacks/libcnb/mocks"
|
"github.com/buildpacks/libcnb/mocks"
|
||||||
)
|
)
|
||||||
|
|
||||||
func testExecD(t *testing.T, context spec.G, it spec.S) {
|
func testExecD(t *testing.T, _ spec.G, it spec.S) {
|
||||||
var (
|
var (
|
||||||
Expect = NewWithT(t).Expect
|
Expect = NewWithT(t).Expect
|
||||||
|
|
||||||
|
|
|
@ -17,7 +17,6 @@
|
||||||
package libcnb_test
|
package libcnb_test
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"io/ioutil"
|
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"testing"
|
"testing"
|
||||||
|
@ -39,13 +38,7 @@ func testFormatter(t *testing.T, context spec.G, it spec.S) {
|
||||||
)
|
)
|
||||||
|
|
||||||
it.Before(func() {
|
it.Before(func() {
|
||||||
var err error
|
app = t.TempDir()
|
||||||
app, err = ioutil.TempDir("", "application-path-formatter")
|
|
||||||
Expect(err).NotTo(HaveOccurred())
|
|
||||||
})
|
|
||||||
|
|
||||||
it.After(func() {
|
|
||||||
Expect(os.RemoveAll(app)).To(Succeed())
|
|
||||||
})
|
})
|
||||||
|
|
||||||
it("lists empty directory contents", func() {
|
it("lists empty directory contents", func() {
|
||||||
|
@ -67,9 +60,7 @@ func testFormatter(t *testing.T, context spec.G, it spec.S) {
|
||||||
)
|
)
|
||||||
|
|
||||||
it.Before(func() {
|
it.Before(func() {
|
||||||
var err error
|
bp = t.TempDir()
|
||||||
bp, err = ioutil.TempDir("", "buildpack-path-formatter")
|
|
||||||
Expect(err).NotTo(HaveOccurred())
|
|
||||||
})
|
})
|
||||||
|
|
||||||
it.After(func() {
|
it.After(func() {
|
||||||
|
@ -95,9 +86,7 @@ func testFormatter(t *testing.T, context spec.G, it spec.S) {
|
||||||
)
|
)
|
||||||
|
|
||||||
it.Before(func() {
|
it.Before(func() {
|
||||||
var err error
|
plat.Path = t.TempDir()
|
||||||
plat.Path, err = ioutil.TempDir("", "platform-formatter")
|
|
||||||
Expect(err).NotTo(HaveOccurred())
|
|
||||||
})
|
})
|
||||||
|
|
||||||
it.After(func() {
|
it.After(func() {
|
||||||
|
|
19
go.mod
19
go.mod
|
@ -1,10 +1,21 @@
|
||||||
module github.com/buildpacks/libcnb
|
module github.com/buildpacks/libcnb
|
||||||
|
|
||||||
go 1.15
|
go 1.20
|
||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/BurntSushi/toml v0.4.1
|
github.com/BurntSushi/toml v1.3.2
|
||||||
github.com/onsi/gomega v1.16.0
|
github.com/Masterminds/semver/v3 v3.2.1
|
||||||
|
github.com/onsi/gomega v1.33.0
|
||||||
github.com/sclevine/spec v1.4.0
|
github.com/sclevine/spec v1.4.0
|
||||||
github.com/stretchr/testify v1.7.0
|
github.com/stretchr/testify v1.9.0
|
||||||
|
)
|
||||||
|
|
||||||
|
require (
|
||||||
|
github.com/davecgh/go-spew v1.1.1 // indirect
|
||||||
|
github.com/google/go-cmp v0.6.0 // indirect
|
||||||
|
github.com/pmezard/go-difflib v1.0.0 // indirect
|
||||||
|
github.com/stretchr/objx v0.5.2 // indirect
|
||||||
|
golang.org/x/net v0.24.0 // indirect
|
||||||
|
golang.org/x/text v0.14.0 // indirect
|
||||||
|
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||||
)
|
)
|
||||||
|
|
122
go.sum
122
go.sum
|
@ -1,106 +1,32 @@
|
||||||
github.com/BurntSushi/toml v0.4.1 h1:GaI7EiDXDRfa8VshkTj7Fym7ha+y8/XxIgD2okUIjLw=
|
github.com/BurntSushi/toml v1.3.2 h1:o7IhLm0Msx3BaB+n3Ag7L8EVlByGnpq14C4YWiu/gL8=
|
||||||
github.com/BurntSushi/toml v0.4.1/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ=
|
github.com/BurntSushi/toml v1.3.2/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ=
|
||||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
github.com/Masterminds/semver/v3 v3.2.1 h1:RN9w6+7QoMeJVGyfmbcgs28Br8cvmnucEXnY0rYXWg0=
|
||||||
|
github.com/Masterminds/semver/v3 v3.2.1/go.mod h1:qvl/7zhW3nngYb5+80sSMF+FG2BjYrf8m9wsX0PNOMQ=
|
||||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
|
github.com/go-logr/logr v1.4.1 h1:pKouT5E8xu9zeFC39JXRDukb6JFQPXM5p5I91188VAQ=
|
||||||
github.com/fsnotify/fsnotify v1.4.9 h1:hsms1Qyu0jgnwNXIxa+/V/PDsU6CfLf6CNO8H7IWoS4=
|
github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 h1:tfuBGBXKqDEevZMzYi5KSi8KkcZtzBcTgAUUtapy0OI=
|
||||||
github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ=
|
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
|
||||||
github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0/go.mod h1:fyg7847qk6SyHyPtNmDHnmrv/HOrqktSC+C9fM+CJOE=
|
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
||||||
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
github.com/google/pprof v0.0.0-20210407192527-94a9f03dee38 h1:yAJXTCF9TqKcTiHJAE8dj7HMvPfh66eeA2JYW7eFpSE=
|
||||||
github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8=
|
github.com/onsi/ginkgo/v2 v2.17.1 h1:V++EzdbhI4ZV4ev0UTIj0PzhzOcReJFyJaLjtSF55M8=
|
||||||
github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA=
|
github.com/onsi/gomega v1.33.0 h1:snPCflnZrpMsy94p4lXVEkHo12lmPnc3vY5XBbreexE=
|
||||||
github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs=
|
github.com/onsi/gomega v1.33.0/go.mod h1:+925n5YtiFsLzzafLUHzVMBpvvRAzrydIBiSIxjX3wY=
|
||||||
github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w=
|
|
||||||
github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0=
|
|
||||||
github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
|
|
||||||
github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
|
|
||||||
github.com/golang/protobuf v1.5.2 h1:ROPKBNFfQgOUMifHyP+KYbvpjbdoFNs+aK7DXlji0Tw=
|
|
||||||
github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
|
|
||||||
github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
|
|
||||||
github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
|
|
||||||
github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
|
||||||
github.com/google/go-cmp v0.5.5 h1:Khx7svrCpmxxtHBq5j2mp/xVjsi8hQMfNLvJFAlrGgU=
|
|
||||||
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
|
||||||
github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
|
|
||||||
github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A=
|
|
||||||
github.com/nxadm/tail v1.4.8 h1:nPr65rt6Y5JFSKQO7qToXr7pePgD6Gwiw05lkbyAQTE=
|
|
||||||
github.com/nxadm/tail v1.4.8/go.mod h1:+ncqLTQzXmGhMZNUePPaPqPvBxHAIsmXswZKocGu+AU=
|
|
||||||
github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
|
|
||||||
github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk=
|
|
||||||
github.com/onsi/ginkgo v1.16.4 h1:29JGrr5oVBm5ulCWet69zQkzWipVXIol6ygQUe/EzNc=
|
|
||||||
github.com/onsi/ginkgo v1.16.4/go.mod h1:dX+/inL/fNMqNlz0e9LfyB9TswhZpCVdJM/Z6Vvnwo0=
|
|
||||||
github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY=
|
|
||||||
github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo=
|
|
||||||
github.com/onsi/gomega v1.16.0 h1:6gjqkI8iiRHMvdccRJM8rVKjCWk6ZIm6FTm3ddIe4/c=
|
|
||||||
github.com/onsi/gomega v1.16.0/go.mod h1:HnhC7FXeEQY45zxNK3PPoIUhzk/80Xly9PcubAlGdZY=
|
|
||||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||||
github.com/sclevine/spec v1.4.0 h1:z/Q9idDcay5m5irkZ28M7PtQM4aOISzOpj4bUPkDee8=
|
github.com/sclevine/spec v1.4.0 h1:z/Q9idDcay5m5irkZ28M7PtQM4aOISzOpj4bUPkDee8=
|
||||||
github.com/sclevine/spec v1.4.0/go.mod h1:LvpgJaFyvQzRvc1kaDs0bulYwzC70PbiYjC4QnFHkOM=
|
github.com/sclevine/spec v1.4.0/go.mod h1:LvpgJaFyvQzRvc1kaDs0bulYwzC70PbiYjC4QnFHkOM=
|
||||||
github.com/stretchr/objx v0.1.0 h1:4G4v2dO3VZwixGIRoQ5Lfboy6nUhCyYzaqnIAPPhYs4=
|
github.com/stretchr/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY=
|
||||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA=
|
||||||
github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
|
github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
|
||||||
github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY=
|
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
|
||||||
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
golang.org/x/net v0.24.0 h1:1PcaxkF854Fu3+lvBIx5SYn9wRlBzzcnHZSiaFFAb0w=
|
||||||
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
golang.org/x/net v0.24.0/go.mod h1:2Q7sJY5mzlzWjKtYUEXSlBWCdyaioyXzRB2RtU8KVE8=
|
||||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
golang.org/x/sys v0.19.0 h1:q5f1RH2jigJ1MoAWp2KTp3gm5zAGFUTarQZ5U386+4o=
|
||||||
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ=
|
||||||
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
|
||||||
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
golang.org/x/tools v0.17.0 h1:FvmRgNOcs3kOa+T20R1uhfP9F6HgG2mfxDv1vrx1Htc=
|
||||||
golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
|
||||||
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
|
||||||
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
|
||||||
golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
|
|
||||||
golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
|
|
||||||
golang.org/x/net v0.0.0-20210428140749-89ef3d95e781 h1:DzZ89McO9/gWPsQXS/FVKAlG02ZjaQ6AlZRBimEYOd0=
|
|
||||||
golang.org/x/net v0.0.0-20210428140749-89ef3d95e781/go.mod h1:OJAsFXCWl8Ukc7SiCT/9KSuxbyM7479/AVlXFRxuMCk=
|
|
||||||
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
|
||||||
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
|
||||||
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
|
||||||
golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
|
||||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
|
||||||
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
|
||||||
golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
|
||||||
golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
|
||||||
golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
|
||||||
golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
|
||||||
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
|
||||||
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
|
||||||
golang.org/x/sys v0.0.0-20210112080510-489259a85091/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
|
||||||
golang.org/x/sys v0.0.0-20210423082822-04245dca01da h1:b3NXsE2LusjYGGjL5bxEVZZORm/YEFFrWFjR8eFrw/c=
|
|
||||||
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
|
||||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
|
||||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
|
||||||
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
|
||||||
golang.org/x/text v0.3.6 h1:aRYxNxv6iGQlyVaZmk6ZgYEDa+Jg18DxebPSrd6bg1M=
|
|
||||||
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
|
||||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
|
||||||
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
|
||||||
golang.org/x/tools v0.0.0-20201224043029-2b0845dc783e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
|
|
||||||
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
|
||||||
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
|
||||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
|
||||||
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE=
|
|
||||||
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
|
||||||
google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
|
|
||||||
google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
|
|
||||||
google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=
|
|
||||||
google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE=
|
|
||||||
google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo=
|
|
||||||
google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
|
|
||||||
google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
|
|
||||||
google.golang.org/protobuf v1.26.0 h1:bxAC2xTBsZGibn2RTntX0oH50xLsqy1OxA9tTL3p/lk=
|
|
||||||
google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
|
|
||||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
|
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
|
||||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||||
gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys=
|
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||||
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ=
|
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||||
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=
|
|
||||||
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
|
||||||
gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
|
||||||
gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
|
||||||
gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
|
|
||||||
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
|
|
||||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo=
|
|
||||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
|
||||||
|
|
|
@ -5,7 +5,6 @@ linters:
|
||||||
disable-all: true
|
disable-all: true
|
||||||
enable:
|
enable:
|
||||||
- bodyclose
|
- bodyclose
|
||||||
- deadcode
|
|
||||||
- dogsled
|
- dogsled
|
||||||
- exportloopref
|
- exportloopref
|
||||||
- gocritic
|
- gocritic
|
||||||
|
@ -18,12 +17,10 @@ linters:
|
||||||
- nakedret
|
- nakedret
|
||||||
- revive
|
- revive
|
||||||
- staticcheck
|
- staticcheck
|
||||||
- structcheck
|
|
||||||
- stylecheck
|
- stylecheck
|
||||||
- typecheck
|
- typecheck
|
||||||
- unconvert
|
- unconvert
|
||||||
- unused
|
- unused
|
||||||
- varcheck
|
|
||||||
- whitespace
|
- whitespace
|
||||||
|
|
||||||
linters-settings:
|
linters-settings:
|
||||||
|
|
|
@ -33,5 +33,6 @@ func TestUnit(t *testing.T) {
|
||||||
suite("Main", testMain)
|
suite("Main", testMain)
|
||||||
suite("Platform", testPlatform)
|
suite("Platform", testPlatform)
|
||||||
suite("ExecD", testExecD)
|
suite("ExecD", testExecD)
|
||||||
|
suite("BuildpackTOML", testBuildpackTOML)
|
||||||
suite.Run(t)
|
suite.Run(t)
|
||||||
}
|
}
|
||||||
|
|
|
@ -18,7 +18,6 @@ package internal
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"io/ioutil"
|
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"strings"
|
"strings"
|
||||||
|
@ -45,7 +44,7 @@ func NewConfigMapFromPath(path string) (ConfigMap, error) {
|
||||||
} else if stat.IsDir() {
|
} else if stat.IsDir() {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
contents, err := ioutil.ReadFile(file)
|
contents, err := os.ReadFile(file)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("unable to read file %s\n%w", file, err)
|
return nil, fmt.Errorf("unable to read file %s\n%w", file, err)
|
||||||
}
|
}
|
||||||
|
|
|
@ -17,7 +17,6 @@
|
||||||
package internal_test
|
package internal_test
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"io/ioutil"
|
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"testing"
|
"testing"
|
||||||
|
@ -28,7 +27,7 @@ import (
|
||||||
"github.com/buildpacks/libcnb/internal"
|
"github.com/buildpacks/libcnb/internal"
|
||||||
)
|
)
|
||||||
|
|
||||||
func testConfigMap(t *testing.T, context spec.G, it spec.S) {
|
func testConfigMap(t *testing.T, _ spec.G, it spec.S) {
|
||||||
var (
|
var (
|
||||||
Expect = NewWithT(t).Expect
|
Expect = NewWithT(t).Expect
|
||||||
|
|
||||||
|
@ -36,13 +35,7 @@ func testConfigMap(t *testing.T, context spec.G, it spec.S) {
|
||||||
)
|
)
|
||||||
|
|
||||||
it.Before(func() {
|
it.Before(func() {
|
||||||
var err error
|
path = t.TempDir()
|
||||||
path, err = ioutil.TempDir("", "config-map")
|
|
||||||
Expect(err).NotTo(HaveOccurred())
|
|
||||||
})
|
|
||||||
|
|
||||||
it.After(func() {
|
|
||||||
Expect(os.RemoveAll(path)).To(Succeed())
|
|
||||||
})
|
})
|
||||||
|
|
||||||
it("returns an empty ConfigMap when directory does not exist", func() {
|
it("returns an empty ConfigMap when directory does not exist", func() {
|
||||||
|
@ -55,7 +48,7 @@ func testConfigMap(t *testing.T, context spec.G, it spec.S) {
|
||||||
})
|
})
|
||||||
|
|
||||||
it("loads the ConfigMap from a directory", func() {
|
it("loads the ConfigMap from a directory", func() {
|
||||||
Expect(ioutil.WriteFile(filepath.Join(path, "test-key"), []byte("test-value"), 0600)).To(Succeed())
|
Expect(os.WriteFile(filepath.Join(path, "test-key"), []byte("test-value"), 0600)).To(Succeed())
|
||||||
|
|
||||||
cm, err := internal.NewConfigMapFromPath(path)
|
cm, err := internal.NewConfigMapFromPath(path)
|
||||||
Expect(err).NotTo(HaveOccurred())
|
Expect(err).NotTo(HaveOccurred())
|
||||||
|
@ -66,7 +59,7 @@ func testConfigMap(t *testing.T, context spec.G, it spec.S) {
|
||||||
it("ignores dirs and follows symlinks", func() {
|
it("ignores dirs and follows symlinks", func() {
|
||||||
// this is necessary to support bindings mounted as k8s config maps & secrets
|
// this is necessary to support bindings mounted as k8s config maps & secrets
|
||||||
Expect(os.MkdirAll(filepath.Join(path, ".hidden"), 0755)).To(Succeed())
|
Expect(os.MkdirAll(filepath.Join(path, ".hidden"), 0755)).To(Succeed())
|
||||||
Expect(ioutil.WriteFile(
|
Expect(os.WriteFile(
|
||||||
filepath.Join(path, ".hidden", "test-key"),
|
filepath.Join(path, ".hidden", "test-key"),
|
||||||
[]byte("test-value"),
|
[]byte("test-value"),
|
||||||
0600,
|
0600,
|
||||||
|
@ -82,7 +75,7 @@ func testConfigMap(t *testing.T, context spec.G, it spec.S) {
|
||||||
})
|
})
|
||||||
|
|
||||||
it("ignores hidden files", func() {
|
it("ignores hidden files", func() {
|
||||||
Expect(ioutil.WriteFile(filepath.Join(path, ".hidden-key"), []byte("hidden-value"), 0600)).To(Succeed())
|
Expect(os.WriteFile(filepath.Join(path, ".hidden-key"), []byte("hidden-value"), 0600)).To(Succeed())
|
||||||
|
|
||||||
cm, err := internal.NewConfigMapFromPath(path)
|
cm, err := internal.NewConfigMapFromPath(path)
|
||||||
Expect(err).NotTo(HaveOccurred())
|
Expect(err).NotTo(HaveOccurred())
|
||||||
|
|
|
@ -17,7 +17,6 @@
|
||||||
package internal_test
|
package internal_test
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"io/ioutil"
|
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"testing"
|
"testing"
|
||||||
|
@ -28,7 +27,7 @@ import (
|
||||||
"github.com/buildpacks/libcnb/internal"
|
"github.com/buildpacks/libcnb/internal"
|
||||||
)
|
)
|
||||||
|
|
||||||
func testDirectoryContents(t *testing.T, context spec.G, it spec.S) {
|
func testDirectoryContents(t *testing.T, _ spec.G, it spec.S) {
|
||||||
var (
|
var (
|
||||||
Expect = NewWithT(t).Expect
|
Expect = NewWithT(t).Expect
|
||||||
|
|
||||||
|
@ -36,13 +35,7 @@ func testDirectoryContents(t *testing.T, context spec.G, it spec.S) {
|
||||||
)
|
)
|
||||||
|
|
||||||
it.Before(func() {
|
it.Before(func() {
|
||||||
var err error
|
path = t.TempDir()
|
||||||
path, err = ioutil.TempDir("", "directory-contents")
|
|
||||||
Expect(err).NotTo(HaveOccurred())
|
|
||||||
})
|
|
||||||
|
|
||||||
it.After(func() {
|
|
||||||
Expect(os.RemoveAll(path)).To(Succeed())
|
|
||||||
})
|
})
|
||||||
|
|
||||||
it("lists empty directory contents", func() {
|
it("lists empty directory contents", func() {
|
||||||
|
|
|
@ -18,7 +18,6 @@ package internal
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"io/ioutil"
|
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
)
|
)
|
||||||
|
@ -38,8 +37,14 @@ func (w EnvironmentWriter) Write(path string, environment map[string]string) err
|
||||||
|
|
||||||
for key, value := range environment {
|
for key, value := range environment {
|
||||||
f := filepath.Join(path, key)
|
f := filepath.Join(path, key)
|
||||||
|
|
||||||
|
// required to support process-specific environment variables
|
||||||
|
if err := os.MkdirAll(filepath.Dir(f), 0755); err != nil {
|
||||||
|
return fmt.Errorf("unable to mkdir from key %s\n%w", filepath.Dir(f), err)
|
||||||
|
}
|
||||||
|
|
||||||
// #nosec
|
// #nosec
|
||||||
if err := ioutil.WriteFile(f, []byte(value), 0644); err != nil {
|
if err := os.WriteFile(f, []byte(value), 0644); err != nil {
|
||||||
return fmt.Errorf("unable to write file %s\n%w", f, err)
|
return fmt.Errorf("unable to write file %s\n%w", f, err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -17,7 +17,6 @@
|
||||||
package internal_test
|
package internal_test
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"io/ioutil"
|
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"testing"
|
"testing"
|
||||||
|
@ -28,7 +27,7 @@ import (
|
||||||
"github.com/buildpacks/libcnb/internal"
|
"github.com/buildpacks/libcnb/internal"
|
||||||
)
|
)
|
||||||
|
|
||||||
func testEnvironmentWriter(t *testing.T, context spec.G, it spec.S) {
|
func testEnvironmentWriter(t *testing.T, _ spec.G, it spec.S) {
|
||||||
var (
|
var (
|
||||||
Expect = NewWithT(t).Expect
|
Expect = NewWithT(t).Expect
|
||||||
|
|
||||||
|
@ -37,13 +36,7 @@ func testEnvironmentWriter(t *testing.T, context spec.G, it spec.S) {
|
||||||
)
|
)
|
||||||
|
|
||||||
it.Before(func() {
|
it.Before(func() {
|
||||||
var err error
|
path = t.TempDir()
|
||||||
path, err = ioutil.TempDir("", "environment-writer")
|
|
||||||
Expect(err).NotTo(HaveOccurred())
|
|
||||||
Expect(os.RemoveAll(path)).To(Succeed())
|
|
||||||
})
|
|
||||||
|
|
||||||
it.After(func() {
|
|
||||||
Expect(os.RemoveAll(path)).To(Succeed())
|
Expect(os.RemoveAll(path)).To(Succeed())
|
||||||
})
|
})
|
||||||
|
|
||||||
|
@ -54,15 +47,26 @@ func testEnvironmentWriter(t *testing.T, context spec.G, it spec.S) {
|
||||||
})
|
})
|
||||||
Expect(err).NotTo(HaveOccurred())
|
Expect(err).NotTo(HaveOccurred())
|
||||||
|
|
||||||
content, err := ioutil.ReadFile(filepath.Join(path, "some-name"))
|
content, err := os.ReadFile(filepath.Join(path, "some-name"))
|
||||||
Expect(err).NotTo(HaveOccurred())
|
Expect(err).NotTo(HaveOccurred())
|
||||||
Expect(string(content)).To(Equal("some-content"))
|
Expect(string(content)).To(Equal("some-content"))
|
||||||
|
|
||||||
content, err = ioutil.ReadFile(filepath.Join(path, "other-name"))
|
content, err = os.ReadFile(filepath.Join(path, "other-name"))
|
||||||
Expect(err).NotTo(HaveOccurred())
|
Expect(err).NotTo(HaveOccurred())
|
||||||
Expect(string(content)).To(Equal("other-content"))
|
Expect(string(content)).To(Equal("other-content"))
|
||||||
})
|
})
|
||||||
|
|
||||||
|
it("writes the given environment with process specific envs to a directory", func() {
|
||||||
|
err := writer.Write(path, map[string]string{
|
||||||
|
"some-proc/some-name": "some-content",
|
||||||
|
})
|
||||||
|
Expect(err).NotTo(HaveOccurred())
|
||||||
|
|
||||||
|
content, err := os.ReadFile(filepath.Join(path, "some-proc", "some-name"))
|
||||||
|
Expect(err).NotTo(HaveOccurred())
|
||||||
|
Expect(string(content)).To(Equal("some-content"))
|
||||||
|
})
|
||||||
|
|
||||||
it("writes does not create a directory of the env map is empty", func() {
|
it("writes does not create a directory of the env map is empty", func() {
|
||||||
err := writer.Write(path, map[string]string{})
|
err := writer.Write(path, map[string]string{})
|
||||||
Expect(err).NotTo(HaveOccurred())
|
Expect(err).NotTo(HaveOccurred())
|
||||||
|
|
|
@ -26,7 +26,7 @@ import (
|
||||||
"github.com/buildpacks/libcnb/internal"
|
"github.com/buildpacks/libcnb/internal"
|
||||||
)
|
)
|
||||||
|
|
||||||
func testExecDWriter(t *testing.T, context spec.G, it spec.S) {
|
func testExecDWriter(t *testing.T, _ spec.G, it spec.S) {
|
||||||
var (
|
var (
|
||||||
Expect = NewWithT(t).Expect
|
Expect = NewWithT(t).Expect
|
||||||
|
|
||||||
|
|
|
@ -27,7 +27,7 @@ import (
|
||||||
"github.com/buildpacks/libcnb/internal"
|
"github.com/buildpacks/libcnb/internal"
|
||||||
)
|
)
|
||||||
|
|
||||||
func testExitHandler(t *testing.T, context spec.G, it spec.S) {
|
func testExitHandler(t *testing.T, _ spec.G, it spec.S) {
|
||||||
var (
|
var (
|
||||||
Expect = NewWithT(t).Expect
|
Expect = NewWithT(t).Expect
|
||||||
|
|
||||||
|
|
|
@ -17,7 +17,6 @@
|
||||||
package internal_test
|
package internal_test
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"io/ioutil"
|
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"testing"
|
"testing"
|
||||||
|
@ -28,7 +27,7 @@ import (
|
||||||
"github.com/buildpacks/libcnb/internal"
|
"github.com/buildpacks/libcnb/internal"
|
||||||
)
|
)
|
||||||
|
|
||||||
func testTOMLWriter(t *testing.T, context spec.G, it spec.S) {
|
func testTOMLWriter(t *testing.T, _ spec.G, it spec.S) {
|
||||||
var (
|
var (
|
||||||
Expect = NewWithT(t).Expect
|
Expect = NewWithT(t).Expect
|
||||||
|
|
||||||
|
@ -38,17 +37,10 @@ func testTOMLWriter(t *testing.T, context spec.G, it spec.S) {
|
||||||
)
|
)
|
||||||
|
|
||||||
it.Before(func() {
|
it.Before(func() {
|
||||||
var err error
|
parent = t.TempDir()
|
||||||
parent, err = ioutil.TempDir("", "toml-writer")
|
|
||||||
Expect(err).NotTo(HaveOccurred())
|
|
||||||
|
|
||||||
path = filepath.Join(parent, "text.toml")
|
path = filepath.Join(parent, "text.toml")
|
||||||
})
|
})
|
||||||
|
|
||||||
it.After(func() {
|
|
||||||
Expect(os.RemoveAll(parent)).To(Succeed())
|
|
||||||
})
|
|
||||||
|
|
||||||
it("writes the contents of a given object out to a .toml file", func() {
|
it("writes the contents of a given object out to a .toml file", func() {
|
||||||
err := tomlWriter.Write(path, map[string]string{
|
err := tomlWriter.Write(path, map[string]string{
|
||||||
"some-field": "some-value",
|
"some-field": "some-value",
|
||||||
|
@ -56,7 +48,7 @@ func testTOMLWriter(t *testing.T, context spec.G, it spec.S) {
|
||||||
})
|
})
|
||||||
Expect(err).NotTo(HaveOccurred())
|
Expect(err).NotTo(HaveOccurred())
|
||||||
|
|
||||||
Expect(ioutil.ReadFile(path)).To(internal.MatchTOML(`
|
Expect(os.ReadFile(path)).To(internal.MatchTOML(`
|
||||||
some-field = "some-value"
|
some-field = "some-value"
|
||||||
other-field = "other-value"`))
|
other-field = "other-value"`))
|
||||||
})
|
})
|
||||||
|
|
69
layer.go
69
layer.go
|
@ -26,9 +26,18 @@ import (
|
||||||
"github.com/buildpacks/libcnb/internal"
|
"github.com/buildpacks/libcnb/internal"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
BOMFormatCycloneDXExtension = "cdx.json"
|
||||||
|
BOMFormatSPDXExtension = "spdx.json"
|
||||||
|
BOMFormatSyftExtension = "syft.json"
|
||||||
|
BOMMediaTypeCycloneDX = "application/vnd.cyclonedx+json"
|
||||||
|
BOMMediaTypeSPDX = "application/spdx+json"
|
||||||
|
BOMMediaTypeSyft = "application/vnd.syft+json"
|
||||||
|
BOMUnknown = "unknown"
|
||||||
|
)
|
||||||
|
|
||||||
// Exec represents the exec.d layer location
|
// Exec represents the exec.d layer location
|
||||||
type Exec struct {
|
type Exec struct {
|
||||||
|
|
||||||
// Path is the path to the exec.d directory.
|
// Path is the path to the exec.d directory.
|
||||||
Path string
|
Path string
|
||||||
}
|
}
|
||||||
|
@ -68,9 +77,47 @@ func (p Profile) ProcessAddf(processType string, name string, format string, a .
|
||||||
p.Addf(filepath.Join(processType, name), format, a...)
|
p.Addf(filepath.Join(processType, name), format, a...)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Contribute represents a layer managed by the buildpack.
|
// BOMFormat indicates the format of the SBOM entry
|
||||||
type Layer struct {
|
type SBOMFormat int
|
||||||
|
|
||||||
|
const (
|
||||||
|
CycloneDXJSON SBOMFormat = iota
|
||||||
|
SPDXJSON
|
||||||
|
SyftJSON
|
||||||
|
UnknownFormat
|
||||||
|
)
|
||||||
|
|
||||||
|
func (b SBOMFormat) String() string {
|
||||||
|
return []string{
|
||||||
|
BOMFormatCycloneDXExtension,
|
||||||
|
BOMFormatSPDXExtension,
|
||||||
|
BOMFormatSyftExtension,
|
||||||
|
BOMUnknown}[b]
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b SBOMFormat) MediaType() string {
|
||||||
|
return []string{
|
||||||
|
BOMMediaTypeCycloneDX,
|
||||||
|
BOMMediaTypeSPDX,
|
||||||
|
BOMMediaTypeSyft,
|
||||||
|
BOMUnknown}[b]
|
||||||
|
}
|
||||||
|
|
||||||
|
func SBOMFormatFromString(from string) (SBOMFormat, error) {
|
||||||
|
switch from {
|
||||||
|
case CycloneDXJSON.String():
|
||||||
|
return CycloneDXJSON, nil
|
||||||
|
case SPDXJSON.String():
|
||||||
|
return SPDXJSON, nil
|
||||||
|
case SyftJSON.String():
|
||||||
|
return SyftJSON, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return UnknownFormat, fmt.Errorf("unable to translate from %s to SBOMFormat", from)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Layer represents a layer managed by the buildpack.
|
||||||
|
type Layer struct {
|
||||||
// LayerTypes indicates the type of layer
|
// LayerTypes indicates the type of layer
|
||||||
LayerTypes `toml:"types"`
|
LayerTypes `toml:"types"`
|
||||||
|
|
||||||
|
@ -99,6 +146,10 @@ type Layer struct {
|
||||||
Exec Exec `toml:"-"`
|
Exec Exec `toml:"-"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (l Layer) SBOMPath(bt SBOMFormat) string {
|
||||||
|
return filepath.Join(filepath.Dir(l.Path), fmt.Sprintf("%s.sbom.%s", l.Name, bt))
|
||||||
|
}
|
||||||
|
|
||||||
// LayerTypes describes which types apply to a given layer. A layer may have any combination of Launch, Build, and
|
// LayerTypes describes which types apply to a given layer. A layer may have any combination of Launch, Build, and
|
||||||
// Cache types.
|
// Cache types.
|
||||||
type LayerTypes struct {
|
type LayerTypes struct {
|
||||||
|
@ -116,7 +167,6 @@ type LayerTypes struct {
|
||||||
|
|
||||||
// LayerContributor is an interface for types that create layers.
|
// LayerContributor is an interface for types that create layers.
|
||||||
type LayerContributor interface {
|
type LayerContributor interface {
|
||||||
|
|
||||||
// Contribute accepts a layer and transforms it, returning a layer.
|
// Contribute accepts a layer and transforms it, returning a layer.
|
||||||
Contribute(layer Layer) (Layer, error)
|
Contribute(layer Layer) (Layer, error)
|
||||||
|
|
||||||
|
@ -126,7 +176,6 @@ type LayerContributor interface {
|
||||||
|
|
||||||
// Layers represents the layers part of the specification.
|
// Layers represents the layers part of the specification.
|
||||||
type Layers struct {
|
type Layers struct {
|
||||||
|
|
||||||
// Path is the layers filesystem location.
|
// Path is the layers filesystem location.
|
||||||
Path string
|
Path string
|
||||||
}
|
}
|
||||||
|
@ -162,3 +211,13 @@ func (l *Layers) Layer(name string) (Layer, error) {
|
||||||
|
|
||||||
return layer, nil
|
return layer, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// BOMBuildPath returns the full path to the build SBoM file for the buildpack
|
||||||
|
func (l Layers) BuildSBOMPath(bt SBOMFormat) string {
|
||||||
|
return filepath.Join(l.Path, fmt.Sprintf("build.sbom.%s", bt))
|
||||||
|
}
|
||||||
|
|
||||||
|
// BOMLaunchPath returns the full path to the launch SBoM file for the buildpack
|
||||||
|
func (l Layers) LaunchSBOMPath(bt SBOMFormat) string {
|
||||||
|
return filepath.Join(l.Path, fmt.Sprintf("launch.sbom.%s", bt))
|
||||||
|
}
|
||||||
|
|
|
@ -17,7 +17,6 @@
|
||||||
package libcnb_test
|
package libcnb_test
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"io/ioutil"
|
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"testing"
|
"testing"
|
||||||
|
@ -83,10 +82,7 @@ func testLayer(t *testing.T, context spec.G, it spec.S) {
|
||||||
|
|
||||||
context("Layers", func() {
|
context("Layers", func() {
|
||||||
it.Before(func() {
|
it.Before(func() {
|
||||||
var err error
|
path = t.TempDir()
|
||||||
path, err = ioutil.TempDir("", "layers")
|
|
||||||
Expect(err).NotTo(HaveOccurred())
|
|
||||||
|
|
||||||
layers = libcnb.Layers{Path: path}
|
layers = libcnb.Layers{Path: path}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
@ -110,8 +106,38 @@ func testLayer(t *testing.T, context spec.G, it spec.S) {
|
||||||
Expect(l.Profile).To(Equal(libcnb.Profile{}))
|
Expect(l.Profile).To(Equal(libcnb.Profile{}))
|
||||||
})
|
})
|
||||||
|
|
||||||
|
it("generates SBOM paths", func() {
|
||||||
|
l, err := layers.Layer("test-name")
|
||||||
|
Expect(err).NotTo(HaveOccurred())
|
||||||
|
|
||||||
|
Expect(l.Path).To(Equal(filepath.Join(path, "test-name")))
|
||||||
|
Expect(layers.BuildSBOMPath(libcnb.CycloneDXJSON)).To(Equal(filepath.Join(path, "build.sbom.cdx.json")))
|
||||||
|
Expect(layers.BuildSBOMPath(libcnb.SPDXJSON)).To(Equal(filepath.Join(path, "build.sbom.spdx.json")))
|
||||||
|
Expect(layers.BuildSBOMPath(libcnb.SyftJSON)).To(Equal(filepath.Join(path, "build.sbom.syft.json")))
|
||||||
|
Expect(layers.LaunchSBOMPath(libcnb.SyftJSON)).To(Equal(filepath.Join(path, "launch.sbom.syft.json")))
|
||||||
|
Expect(l.SBOMPath(libcnb.SyftJSON)).To(Equal(filepath.Join(path, "test-name.sbom.syft.json")))
|
||||||
|
})
|
||||||
|
|
||||||
|
it("maps from string to SBOM Format", func() {
|
||||||
|
fmt, err := libcnb.SBOMFormatFromString("cdx.json")
|
||||||
|
Expect(err).ToNot(HaveOccurred())
|
||||||
|
Expect(fmt).To(Equal(libcnb.CycloneDXJSON))
|
||||||
|
|
||||||
|
fmt, err = libcnb.SBOMFormatFromString("spdx.json")
|
||||||
|
Expect(err).ToNot(HaveOccurred())
|
||||||
|
Expect(fmt).To(Equal(libcnb.SPDXJSON))
|
||||||
|
|
||||||
|
fmt, err = libcnb.SBOMFormatFromString("syft.json")
|
||||||
|
Expect(err).ToNot(HaveOccurred())
|
||||||
|
Expect(fmt).To(Equal(libcnb.SyftJSON))
|
||||||
|
|
||||||
|
fmt, err = libcnb.SBOMFormatFromString("foobar.json")
|
||||||
|
Expect(err).To(MatchError("unable to translate from foobar.json to SBOMFormat"))
|
||||||
|
Expect(fmt).To(Equal(libcnb.UnknownFormat))
|
||||||
|
})
|
||||||
|
|
||||||
it("reads existing 0.5 metadata", func() {
|
it("reads existing 0.5 metadata", func() {
|
||||||
Expect(ioutil.WriteFile(
|
Expect(os.WriteFile(
|
||||||
filepath.Join(path, "test-name.toml"),
|
filepath.Join(path, "test-name.toml"),
|
||||||
[]byte(`
|
[]byte(`
|
||||||
launch = true
|
launch = true
|
||||||
|
@ -132,7 +158,7 @@ test-key = "test-value"
|
||||||
})
|
})
|
||||||
|
|
||||||
it("reads existing 0.6 metadata", func() {
|
it("reads existing 0.6 metadata", func() {
|
||||||
Expect(ioutil.WriteFile(
|
Expect(os.WriteFile(
|
||||||
filepath.Join(path, "test-name.toml"),
|
filepath.Join(path, "test-name.toml"),
|
||||||
[]byte(`
|
[]byte(`
|
||||||
[types]
|
[types]
|
||||||
|
@ -154,7 +180,7 @@ test-key = "test-value"
|
||||||
})
|
})
|
||||||
|
|
||||||
it("reads existing 0.6 metadata with launch, build and cache all false", func() {
|
it("reads existing 0.6 metadata with launch, build and cache all false", func() {
|
||||||
Expect(ioutil.WriteFile(
|
Expect(os.WriteFile(
|
||||||
filepath.Join(path, "test-name.toml"),
|
filepath.Join(path, "test-name.toml"),
|
||||||
[]byte(`
|
[]byte(`
|
||||||
[types]
|
[types]
|
||||||
|
|
30
main_test.go
30
main_test.go
|
@ -17,7 +17,6 @@
|
||||||
package libcnb_test
|
package libcnb_test
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"io/ioutil"
|
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"testing"
|
"testing"
|
||||||
|
@ -30,7 +29,7 @@ import (
|
||||||
"github.com/buildpacks/libcnb/mocks"
|
"github.com/buildpacks/libcnb/mocks"
|
||||||
)
|
)
|
||||||
|
|
||||||
func testMain(t *testing.T, context spec.G, it spec.S) {
|
func testMain(t *testing.T, _ spec.G, it spec.S) {
|
||||||
var (
|
var (
|
||||||
Expect = NewWithT(t).Expect
|
Expect = NewWithT(t).Expect
|
||||||
|
|
||||||
|
@ -52,18 +51,17 @@ func testMain(t *testing.T, context spec.G, it spec.S) {
|
||||||
it.Before(func() {
|
it.Before(func() {
|
||||||
var err error
|
var err error
|
||||||
|
|
||||||
applicationPath, err = ioutil.TempDir("", "main-application-path")
|
applicationPath = t.TempDir()
|
||||||
Expect(err).NotTo(HaveOccurred())
|
|
||||||
applicationPath, err = filepath.EvalSymlinks(applicationPath)
|
applicationPath, err = filepath.EvalSymlinks(applicationPath)
|
||||||
Expect(err).NotTo(HaveOccurred())
|
Expect(err).NotTo(HaveOccurred())
|
||||||
|
|
||||||
builder = &mocks.Builder{}
|
builder = &mocks.Builder{}
|
||||||
|
|
||||||
buildpackPath, err = ioutil.TempDir("", "main-buildpack-path")
|
buildpackPath = t.TempDir()
|
||||||
Expect(err).NotTo(HaveOccurred())
|
Expect(err).NotTo(HaveOccurred())
|
||||||
Expect(os.Setenv("CNB_BUILDPACK_DIR", buildpackPath)).To(Succeed())
|
Expect(os.Setenv("CNB_BUILDPACK_DIR", buildpackPath)).To(Succeed())
|
||||||
|
|
||||||
Expect(ioutil.WriteFile(filepath.Join(buildpackPath, "buildpack.toml"),
|
Expect(os.WriteFile(filepath.Join(buildpackPath, "buildpack.toml"),
|
||||||
[]byte(`
|
[]byte(`
|
||||||
api = "0.6"
|
api = "0.6"
|
||||||
|
|
||||||
|
@ -89,12 +87,12 @@ test-key = "test-value"
|
||||||
0600),
|
0600),
|
||||||
).To(Succeed())
|
).To(Succeed())
|
||||||
|
|
||||||
f, err := ioutil.TempFile("", "main-buildpackplan-path")
|
f, err := os.CreateTemp("", "main-buildpackplan-path")
|
||||||
Expect(err).NotTo(HaveOccurred())
|
Expect(err).NotTo(HaveOccurred())
|
||||||
Expect(f.Close()).NotTo(HaveOccurred())
|
Expect(f.Close()).NotTo(HaveOccurred())
|
||||||
buildpackPlanPath = f.Name()
|
buildpackPlanPath = f.Name()
|
||||||
|
|
||||||
Expect(ioutil.WriteFile(buildpackPlanPath,
|
Expect(os.WriteFile(buildpackPlanPath,
|
||||||
[]byte(`
|
[]byte(`
|
||||||
[[entries]]
|
[[entries]]
|
||||||
name = "test-name"
|
name = "test-name"
|
||||||
|
@ -106,7 +104,7 @@ test-key = "test-value"
|
||||||
0600),
|
0600),
|
||||||
).To(Succeed())
|
).To(Succeed())
|
||||||
|
|
||||||
f, err = ioutil.TempFile("", "main-buildplan-path")
|
f, err = os.CreateTemp("", "main-buildplan-path")
|
||||||
Expect(err).NotTo(HaveOccurred())
|
Expect(err).NotTo(HaveOccurred())
|
||||||
Expect(f.Close()).NotTo(HaveOccurred())
|
Expect(f.Close()).NotTo(HaveOccurred())
|
||||||
buildPlanPath = f.Name()
|
buildPlanPath = f.Name()
|
||||||
|
@ -121,10 +119,9 @@ test-key = "test-value"
|
||||||
exitHandler.On("Pass", mock.Anything)
|
exitHandler.On("Pass", mock.Anything)
|
||||||
exitHandler.On("Fail", mock.Anything)
|
exitHandler.On("Fail", mock.Anything)
|
||||||
|
|
||||||
layersPath, err = ioutil.TempDir("", "main-layers-path")
|
layersPath = t.TempDir()
|
||||||
Expect(err).NotTo(HaveOccurred())
|
|
||||||
|
|
||||||
Expect(ioutil.WriteFile(filepath.Join(layersPath, "store.toml"),
|
Expect(os.WriteFile(filepath.Join(layersPath, "store.toml"),
|
||||||
[]byte(`
|
[]byte(`
|
||||||
[metadata]
|
[metadata]
|
||||||
test-key = "test-value"
|
test-key = "test-value"
|
||||||
|
@ -132,24 +129,23 @@ test-key = "test-value"
|
||||||
0600),
|
0600),
|
||||||
).To(Succeed())
|
).To(Succeed())
|
||||||
|
|
||||||
platformPath, err = ioutil.TempDir("", "main-platform-path")
|
platformPath = t.TempDir()
|
||||||
Expect(err).NotTo(HaveOccurred())
|
|
||||||
|
|
||||||
Expect(os.MkdirAll(filepath.Join(platformPath, "bindings", "alpha", "metadata"), 0755)).To(Succeed())
|
Expect(os.MkdirAll(filepath.Join(platformPath, "bindings", "alpha", "metadata"), 0755)).To(Succeed())
|
||||||
Expect(ioutil.WriteFile(
|
Expect(os.WriteFile(
|
||||||
filepath.Join(platformPath, "bindings", "alpha", "metadata", "test-metadata-key"),
|
filepath.Join(platformPath, "bindings", "alpha", "metadata", "test-metadata-key"),
|
||||||
[]byte("test-metadata-value"),
|
[]byte("test-metadata-value"),
|
||||||
0600,
|
0600,
|
||||||
)).To(Succeed())
|
)).To(Succeed())
|
||||||
Expect(os.MkdirAll(filepath.Join(platformPath, "bindings", "alpha", "secret"), 0755)).To(Succeed())
|
Expect(os.MkdirAll(filepath.Join(platformPath, "bindings", "alpha", "secret"), 0755)).To(Succeed())
|
||||||
Expect(ioutil.WriteFile(
|
Expect(os.WriteFile(
|
||||||
filepath.Join(platformPath, "bindings", "alpha", "secret", "test-secret-key"),
|
filepath.Join(platformPath, "bindings", "alpha", "secret", "test-secret-key"),
|
||||||
[]byte("test-secret-value"),
|
[]byte("test-secret-value"),
|
||||||
0600,
|
0600,
|
||||||
)).To(Succeed())
|
)).To(Succeed())
|
||||||
|
|
||||||
Expect(os.MkdirAll(filepath.Join(platformPath, "env"), 0755)).To(Succeed())
|
Expect(os.MkdirAll(filepath.Join(platformPath, "env"), 0755)).To(Succeed())
|
||||||
Expect(ioutil.WriteFile(filepath.Join(platformPath, "env", "TEST_ENV"), []byte("test-value"), 0600)).
|
Expect(os.WriteFile(filepath.Join(platformPath, "env", "TEST_ENV"), []byte("test-value"), 0600)).
|
||||||
To(Succeed())
|
To(Succeed())
|
||||||
|
|
||||||
tomlWriter = &mocks.TOMLWriter{}
|
tomlWriter = &mocks.TOMLWriter{}
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
// Code generated by mockery 2.9.4. DO NOT EDIT.
|
// Code generated by mockery v2.24.0. DO NOT EDIT.
|
||||||
|
|
||||||
package mocks
|
package mocks
|
||||||
|
|
||||||
|
@ -18,13 +18,16 @@ func (_m *Builder) Build(context libcnb.BuildContext) (libcnb.BuildResult, error
|
||||||
ret := _m.Called(context)
|
ret := _m.Called(context)
|
||||||
|
|
||||||
var r0 libcnb.BuildResult
|
var r0 libcnb.BuildResult
|
||||||
|
var r1 error
|
||||||
|
if rf, ok := ret.Get(0).(func(libcnb.BuildContext) (libcnb.BuildResult, error)); ok {
|
||||||
|
return rf(context)
|
||||||
|
}
|
||||||
if rf, ok := ret.Get(0).(func(libcnb.BuildContext) libcnb.BuildResult); ok {
|
if rf, ok := ret.Get(0).(func(libcnb.BuildContext) libcnb.BuildResult); ok {
|
||||||
r0 = rf(context)
|
r0 = rf(context)
|
||||||
} else {
|
} else {
|
||||||
r0 = ret.Get(0).(libcnb.BuildResult)
|
r0 = ret.Get(0).(libcnb.BuildResult)
|
||||||
}
|
}
|
||||||
|
|
||||||
var r1 error
|
|
||||||
if rf, ok := ret.Get(1).(func(libcnb.BuildContext) error); ok {
|
if rf, ok := ret.Get(1).(func(libcnb.BuildContext) error); ok {
|
||||||
r1 = rf(context)
|
r1 = rf(context)
|
||||||
} else {
|
} else {
|
||||||
|
@ -33,3 +36,18 @@ func (_m *Builder) Build(context libcnb.BuildContext) (libcnb.BuildResult, error
|
||||||
|
|
||||||
return r0, r1
|
return r0, r1
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type mockConstructorTestingTNewBuilder interface {
|
||||||
|
mock.TestingT
|
||||||
|
Cleanup(func())
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewBuilder creates a new instance of Builder. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations.
|
||||||
|
func NewBuilder(t mockConstructorTestingTNewBuilder) *Builder {
|
||||||
|
mock := &Builder{}
|
||||||
|
mock.Mock.Test(t)
|
||||||
|
|
||||||
|
t.Cleanup(func() { mock.AssertExpectations(t) })
|
||||||
|
|
||||||
|
return mock
|
||||||
|
}
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
// Code generated by mockery 2.9.4. DO NOT EDIT.
|
// Code generated by mockery v2.24.0. DO NOT EDIT.
|
||||||
|
|
||||||
package mocks
|
package mocks
|
||||||
|
|
||||||
|
@ -18,13 +18,16 @@ func (_m *Detector) Detect(context libcnb.DetectContext) (libcnb.DetectResult, e
|
||||||
ret := _m.Called(context)
|
ret := _m.Called(context)
|
||||||
|
|
||||||
var r0 libcnb.DetectResult
|
var r0 libcnb.DetectResult
|
||||||
|
var r1 error
|
||||||
|
if rf, ok := ret.Get(0).(func(libcnb.DetectContext) (libcnb.DetectResult, error)); ok {
|
||||||
|
return rf(context)
|
||||||
|
}
|
||||||
if rf, ok := ret.Get(0).(func(libcnb.DetectContext) libcnb.DetectResult); ok {
|
if rf, ok := ret.Get(0).(func(libcnb.DetectContext) libcnb.DetectResult); ok {
|
||||||
r0 = rf(context)
|
r0 = rf(context)
|
||||||
} else {
|
} else {
|
||||||
r0 = ret.Get(0).(libcnb.DetectResult)
|
r0 = ret.Get(0).(libcnb.DetectResult)
|
||||||
}
|
}
|
||||||
|
|
||||||
var r1 error
|
|
||||||
if rf, ok := ret.Get(1).(func(libcnb.DetectContext) error); ok {
|
if rf, ok := ret.Get(1).(func(libcnb.DetectContext) error); ok {
|
||||||
r1 = rf(context)
|
r1 = rf(context)
|
||||||
} else {
|
} else {
|
||||||
|
@ -33,3 +36,18 @@ func (_m *Detector) Detect(context libcnb.DetectContext) (libcnb.DetectResult, e
|
||||||
|
|
||||||
return r0, r1
|
return r0, r1
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type mockConstructorTestingTNewDetector interface {
|
||||||
|
mock.TestingT
|
||||||
|
Cleanup(func())
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewDetector creates a new instance of Detector. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations.
|
||||||
|
func NewDetector(t mockConstructorTestingTNewDetector) *Detector {
|
||||||
|
mock := &Detector{}
|
||||||
|
mock.Mock.Test(t)
|
||||||
|
|
||||||
|
t.Cleanup(func() { mock.AssertExpectations(t) })
|
||||||
|
|
||||||
|
return mock
|
||||||
|
}
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
// Code generated by mockery 2.9.4. DO NOT EDIT.
|
// Code generated by mockery v2.24.0. DO NOT EDIT.
|
||||||
|
|
||||||
package mocks
|
package mocks
|
||||||
|
|
||||||
|
@ -22,3 +22,18 @@ func (_m *EnvironmentWriter) Write(dir string, environment map[string]string) er
|
||||||
|
|
||||||
return r0
|
return r0
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type mockConstructorTestingTNewEnvironmentWriter interface {
|
||||||
|
mock.TestingT
|
||||||
|
Cleanup(func())
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewEnvironmentWriter creates a new instance of EnvironmentWriter. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations.
|
||||||
|
func NewEnvironmentWriter(t mockConstructorTestingTNewEnvironmentWriter) *EnvironmentWriter {
|
||||||
|
mock := &EnvironmentWriter{}
|
||||||
|
mock.Mock.Test(t)
|
||||||
|
|
||||||
|
t.Cleanup(func() { mock.AssertExpectations(t) })
|
||||||
|
|
||||||
|
return mock
|
||||||
|
}
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
// Code generated by mockery 2.9.4. DO NOT EDIT.
|
// Code generated by mockery v2.24.0. DO NOT EDIT.
|
||||||
|
|
||||||
package mocks
|
package mocks
|
||||||
|
|
||||||
|
@ -14,6 +14,10 @@ func (_m *ExecD) Execute() (map[string]string, error) {
|
||||||
ret := _m.Called()
|
ret := _m.Called()
|
||||||
|
|
||||||
var r0 map[string]string
|
var r0 map[string]string
|
||||||
|
var r1 error
|
||||||
|
if rf, ok := ret.Get(0).(func() (map[string]string, error)); ok {
|
||||||
|
return rf()
|
||||||
|
}
|
||||||
if rf, ok := ret.Get(0).(func() map[string]string); ok {
|
if rf, ok := ret.Get(0).(func() map[string]string); ok {
|
||||||
r0 = rf()
|
r0 = rf()
|
||||||
} else {
|
} else {
|
||||||
|
@ -22,7 +26,6 @@ func (_m *ExecD) Execute() (map[string]string, error) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var r1 error
|
|
||||||
if rf, ok := ret.Get(1).(func() error); ok {
|
if rf, ok := ret.Get(1).(func() error); ok {
|
||||||
r1 = rf()
|
r1 = rf()
|
||||||
} else {
|
} else {
|
||||||
|
@ -31,3 +34,18 @@ func (_m *ExecD) Execute() (map[string]string, error) {
|
||||||
|
|
||||||
return r0, r1
|
return r0, r1
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type mockConstructorTestingTNewExecD interface {
|
||||||
|
mock.TestingT
|
||||||
|
Cleanup(func())
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewExecD creates a new instance of ExecD. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations.
|
||||||
|
func NewExecD(t mockConstructorTestingTNewExecD) *ExecD {
|
||||||
|
mock := &ExecD{}
|
||||||
|
mock.Mock.Test(t)
|
||||||
|
|
||||||
|
t.Cleanup(func() { mock.AssertExpectations(t) })
|
||||||
|
|
||||||
|
return mock
|
||||||
|
}
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
// Code generated by mockery 2.9.4. DO NOT EDIT.
|
// Code generated by mockery v2.24.0. DO NOT EDIT.
|
||||||
|
|
||||||
package mocks
|
package mocks
|
||||||
|
|
||||||
|
@ -22,3 +22,18 @@ func (_m *ExecDWriter) Write(value map[string]string) error {
|
||||||
|
|
||||||
return r0
|
return r0
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type mockConstructorTestingTNewExecDWriter interface {
|
||||||
|
mock.TestingT
|
||||||
|
Cleanup(func())
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewExecDWriter creates a new instance of ExecDWriter. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations.
|
||||||
|
func NewExecDWriter(t mockConstructorTestingTNewExecDWriter) *ExecDWriter {
|
||||||
|
mock := &ExecDWriter{}
|
||||||
|
mock.Mock.Test(t)
|
||||||
|
|
||||||
|
t.Cleanup(func() { mock.AssertExpectations(t) })
|
||||||
|
|
||||||
|
return mock
|
||||||
|
}
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
// Code generated by mockery 2.9.4. DO NOT EDIT.
|
// Code generated by mockery v2.24.0. DO NOT EDIT.
|
||||||
|
|
||||||
package mocks
|
package mocks
|
||||||
|
|
||||||
|
@ -23,3 +23,18 @@ func (_m *ExitHandler) Fail() {
|
||||||
func (_m *ExitHandler) Pass() {
|
func (_m *ExitHandler) Pass() {
|
||||||
_m.Called()
|
_m.Called()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type mockConstructorTestingTNewExitHandler interface {
|
||||||
|
mock.TestingT
|
||||||
|
Cleanup(func())
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewExitHandler creates a new instance of ExitHandler. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations.
|
||||||
|
func NewExitHandler(t mockConstructorTestingTNewExitHandler) *ExitHandler {
|
||||||
|
mock := &ExitHandler{}
|
||||||
|
mock.Mock.Test(t)
|
||||||
|
|
||||||
|
t.Cleanup(func() { mock.AssertExpectations(t) })
|
||||||
|
|
||||||
|
return mock
|
||||||
|
}
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
// Code generated by mockery 2.9.4. DO NOT EDIT.
|
// Code generated by mockery v2.24.0. DO NOT EDIT.
|
||||||
|
|
||||||
package mocks
|
package mocks
|
||||||
|
|
||||||
|
@ -18,13 +18,16 @@ func (_m *LayerContributor) Contribute(layer libcnb.Layer) (libcnb.Layer, error)
|
||||||
ret := _m.Called(layer)
|
ret := _m.Called(layer)
|
||||||
|
|
||||||
var r0 libcnb.Layer
|
var r0 libcnb.Layer
|
||||||
|
var r1 error
|
||||||
|
if rf, ok := ret.Get(0).(func(libcnb.Layer) (libcnb.Layer, error)); ok {
|
||||||
|
return rf(layer)
|
||||||
|
}
|
||||||
if rf, ok := ret.Get(0).(func(libcnb.Layer) libcnb.Layer); ok {
|
if rf, ok := ret.Get(0).(func(libcnb.Layer) libcnb.Layer); ok {
|
||||||
r0 = rf(layer)
|
r0 = rf(layer)
|
||||||
} else {
|
} else {
|
||||||
r0 = ret.Get(0).(libcnb.Layer)
|
r0 = ret.Get(0).(libcnb.Layer)
|
||||||
}
|
}
|
||||||
|
|
||||||
var r1 error
|
|
||||||
if rf, ok := ret.Get(1).(func(libcnb.Layer) error); ok {
|
if rf, ok := ret.Get(1).(func(libcnb.Layer) error); ok {
|
||||||
r1 = rf(layer)
|
r1 = rf(layer)
|
||||||
} else {
|
} else {
|
||||||
|
@ -47,3 +50,18 @@ func (_m *LayerContributor) Name() string {
|
||||||
|
|
||||||
return r0
|
return r0
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type mockConstructorTestingTNewLayerContributor interface {
|
||||||
|
mock.TestingT
|
||||||
|
Cleanup(func())
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewLayerContributor creates a new instance of LayerContributor. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations.
|
||||||
|
func NewLayerContributor(t mockConstructorTestingTNewLayerContributor) *LayerContributor {
|
||||||
|
mock := &LayerContributor{}
|
||||||
|
mock.Mock.Test(t)
|
||||||
|
|
||||||
|
t.Cleanup(func() { mock.AssertExpectations(t) })
|
||||||
|
|
||||||
|
return mock
|
||||||
|
}
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
// Code generated by mockery 2.9.4. DO NOT EDIT.
|
// Code generated by mockery v2.24.0. DO NOT EDIT.
|
||||||
|
|
||||||
package mocks
|
package mocks
|
||||||
|
|
||||||
|
@ -22,3 +22,18 @@ func (_m *TOMLWriter) Write(path string, value interface{}) error {
|
||||||
|
|
||||||
return r0
|
return r0
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type mockConstructorTestingTNewTOMLWriter interface {
|
||||||
|
mock.TestingT
|
||||||
|
Cleanup(func())
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewTOMLWriter creates a new instance of TOMLWriter. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations.
|
||||||
|
func NewTOMLWriter(t mockConstructorTestingTNewTOMLWriter) *TOMLWriter {
|
||||||
|
mock := &TOMLWriter{}
|
||||||
|
mock.Mock.Test(t)
|
||||||
|
|
||||||
|
t.Cleanup(func() { mock.AssertExpectations(t) })
|
||||||
|
|
||||||
|
return mock
|
||||||
|
}
|
||||||
|
|
63
platform.go
63
platform.go
|
@ -17,6 +17,7 @@
|
||||||
package libcnb
|
package libcnb
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
|
@ -41,8 +42,12 @@ const (
|
||||||
// See the Service Binding Specification for Kubernetes for more details - https://k8s-service-bindings.github.io/spec/
|
// See the Service Binding Specification for Kubernetes for more details - https://k8s-service-bindings.github.io/spec/
|
||||||
EnvServiceBindings = "SERVICE_BINDING_ROOT"
|
EnvServiceBindings = "SERVICE_BINDING_ROOT"
|
||||||
|
|
||||||
|
// EnvVcapServices is the name of the environment variable that contains the bindings in cloudfoundry
|
||||||
|
EnvVcapServices = "VCAP_SERVICES"
|
||||||
|
|
||||||
// EnvCNBBindings is the name of the environment variable that contains the path to the CNB bindings directory.
|
// EnvCNBBindings is the name of the environment variable that contains the path to the CNB bindings directory.
|
||||||
// See the CNB bindings extension spec for more details - https://github.com/buildpacks/spec/blob/main/extensions/bindings.md
|
// See the CNB bindings extension spec for more details - https://github.com/buildpacks/spec/blob/main/extensions/bindings.md
|
||||||
|
//
|
||||||
// Deprecated: Use the Service Binding Specification for Kubernetes instead -
|
// Deprecated: Use the Service Binding Specification for Kubernetes instead -
|
||||||
// https://github.com/buildpacks/rfcs/blob/main/text/0055-deprecate-service-bindings.md.
|
// https://github.com/buildpacks/rfcs/blob/main/text/0055-deprecate-service-bindings.md.
|
||||||
EnvCNBBindings = "CNB_BINDINGS"
|
EnvCNBBindings = "CNB_BINDINGS"
|
||||||
|
@ -112,6 +117,56 @@ func NewBindingFromPath(path string) (Binding, error) {
|
||||||
return NewBinding(filepath.Base(path), path, secret), nil
|
return NewBinding(filepath.Base(path), path, secret), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type vcapServicesBinding struct {
|
||||||
|
Name string `json:"name"`
|
||||||
|
Label string `json:"label"`
|
||||||
|
Credentials map[string]interface{} `json:"credentials"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func toJSONString(input interface{}) (string, error) {
|
||||||
|
switch in := input.(type) {
|
||||||
|
case string:
|
||||||
|
return in, nil
|
||||||
|
default:
|
||||||
|
jsonProperty, err := json.Marshal(in)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
return string(jsonProperty), nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewBindingsFromVcapServicesEnv creates a new instance from all the bindings given from the VCAP_SERVICES.
|
||||||
|
func NewBindingsFromVcapServicesEnv(content string) (Bindings, error) {
|
||||||
|
var contentTyped map[string][]vcapServicesBinding
|
||||||
|
|
||||||
|
err := json.Unmarshal([]byte(content), &contentTyped)
|
||||||
|
if err != nil {
|
||||||
|
return Bindings{}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
bindings := Bindings{}
|
||||||
|
for p, bArray := range contentTyped {
|
||||||
|
for _, b := range bArray {
|
||||||
|
secret := map[string]string{}
|
||||||
|
for k, v := range b.Credentials {
|
||||||
|
secret[k], err = toJSONString(v)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
bindings = append(bindings, Binding{
|
||||||
|
Name: b.Name,
|
||||||
|
Type: b.Label,
|
||||||
|
Provider: p,
|
||||||
|
Secret: secret,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return bindings, nil
|
||||||
|
}
|
||||||
|
|
||||||
func (b Binding) String() string {
|
func (b Binding) String() string {
|
||||||
var s []string
|
var s []string
|
||||||
for k := range b.Secret {
|
for k := range b.Secret {
|
||||||
|
@ -162,6 +217,10 @@ func NewBindingsForLaunch() (Bindings, error) {
|
||||||
return NewBindingsFromPath(path)
|
return NewBindingsFromPath(path)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if content, ok := os.LookupEnv(EnvVcapServices); ok {
|
||||||
|
return NewBindingsFromVcapServicesEnv(content)
|
||||||
|
}
|
||||||
|
|
||||||
return Bindings{}, nil
|
return Bindings{}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -200,6 +259,10 @@ func NewBindingsForBuild(platformDir string) (Bindings, error) {
|
||||||
if path, ok := os.LookupEnv(EnvCNBBindings); ok {
|
if path, ok := os.LookupEnv(EnvCNBBindings); ok {
|
||||||
return NewBindingsFromPath(path)
|
return NewBindingsFromPath(path)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if content, ok := os.LookupEnv(EnvVcapServices); ok {
|
||||||
|
return NewBindingsFromVcapServicesEnv(content)
|
||||||
|
}
|
||||||
return NewBindingsFromPath(filepath.Join(platformDir, "bindings"))
|
return NewBindingsFromPath(filepath.Join(platformDir, "bindings"))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
128
platform_test.go
128
platform_test.go
|
@ -18,7 +18,6 @@ package libcnb_test
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"io/ioutil"
|
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"testing"
|
"testing"
|
||||||
|
@ -37,39 +36,112 @@ func testPlatform(t *testing.T, context spec.G, it spec.S) {
|
||||||
)
|
)
|
||||||
|
|
||||||
it.Before(func() {
|
it.Before(func() {
|
||||||
var err error
|
platformPath = t.TempDir()
|
||||||
platformPath, err = ioutil.TempDir("", "platform")
|
|
||||||
path = filepath.Join(platformPath, "bindings")
|
path = filepath.Join(platformPath, "bindings")
|
||||||
Expect(err).NotTo(HaveOccurred())
|
|
||||||
})
|
})
|
||||||
|
|
||||||
it.After(func() {
|
context("Cloudfoundry VCAP_SERVICES", func() {
|
||||||
Expect(os.RemoveAll(path)).To(Succeed())
|
context("Build", func() {
|
||||||
|
it("creates a bindings from VCAP_SERVICES", func() {
|
||||||
|
content, err := os.ReadFile("testdata/vcap_services.json")
|
||||||
|
Expect(err).NotTo(HaveOccurred())
|
||||||
|
t.Setenv(libcnb.EnvVcapServices, string(content))
|
||||||
|
|
||||||
|
bindings, err := libcnb.NewBindingsForBuild("")
|
||||||
|
Expect(err).NotTo(HaveOccurred())
|
||||||
|
|
||||||
|
Expect(bindings).To(ConsistOf(libcnb.Bindings{
|
||||||
|
{
|
||||||
|
Name: "elephantsql-binding-c6c60",
|
||||||
|
Type: "elephantsql-type",
|
||||||
|
Provider: "elephantsql-provider",
|
||||||
|
Secret: map[string]string{
|
||||||
|
"bool": "true",
|
||||||
|
"int": "1",
|
||||||
|
"uri": "postgres://exampleuser:examplepass@postgres.example.com:5432/exampleuser",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Name: "mysendgrid",
|
||||||
|
Type: "sendgrid-type",
|
||||||
|
Provider: "sendgrid-provider",
|
||||||
|
Secret: map[string]string{
|
||||||
|
"username": "QvsXMbJ3rK",
|
||||||
|
"password": "HCHMOYluTv",
|
||||||
|
"hostname": "smtp.example.com",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Name: "postgres",
|
||||||
|
Type: "postgres",
|
||||||
|
Provider: "postgres",
|
||||||
|
Secret: map[string]string{
|
||||||
|
"urls": "{\"example\":\"http://example.com\"}",
|
||||||
|
"username": "foo",
|
||||||
|
"password": "bar",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}))
|
||||||
|
})
|
||||||
|
|
||||||
|
it("creates empty bindings from empty VCAP_SERVICES", func() {
|
||||||
|
t.Setenv(libcnb.EnvVcapServices, "{}")
|
||||||
|
|
||||||
|
bindings, err := libcnb.NewBindingsForBuild("")
|
||||||
|
Expect(err).NotTo(HaveOccurred())
|
||||||
|
|
||||||
|
Expect(bindings).To(HaveLen(0))
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
context("Launch", func() {
|
||||||
|
it("creates a bindings from VCAP_SERVICES", func() {
|
||||||
|
content, err := os.ReadFile("testdata/vcap_services.json")
|
||||||
|
Expect(err).NotTo(HaveOccurred())
|
||||||
|
t.Setenv(libcnb.EnvVcapServices, string(content))
|
||||||
|
|
||||||
|
bindings, err := libcnb.NewBindingsForLaunch()
|
||||||
|
Expect(err).NotTo(HaveOccurred())
|
||||||
|
|
||||||
|
Expect(bindings).To(HaveLen(3))
|
||||||
|
types := []string{bindings[0].Type, bindings[1].Type, bindings[2].Type}
|
||||||
|
Expect(types).To(ContainElements("elephantsql-type", "sendgrid-type", "postgres"))
|
||||||
|
})
|
||||||
|
|
||||||
|
it("creates empty bindings from empty VCAP_SERVICES", func() {
|
||||||
|
t.Setenv(libcnb.EnvVcapServices, "{}")
|
||||||
|
|
||||||
|
bindings, err := libcnb.NewBindingsForLaunch()
|
||||||
|
Expect(err).NotTo(HaveOccurred())
|
||||||
|
|
||||||
|
Expect(bindings).To(HaveLen(0))
|
||||||
|
})
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
context("CNB Bindings", func() {
|
context("CNB Bindings", func() {
|
||||||
it.Before(func() {
|
it.Before(func() {
|
||||||
Expect(os.MkdirAll(filepath.Join(path, "alpha", "metadata"), 0755)).To(Succeed())
|
Expect(os.MkdirAll(filepath.Join(path, "alpha", "metadata"), 0755)).To(Succeed())
|
||||||
Expect(ioutil.WriteFile(filepath.Join(path, "alpha", "metadata", "kind"), []byte("test-kind"), 0600)).To(Succeed())
|
Expect(os.WriteFile(filepath.Join(path, "alpha", "metadata", "kind"), []byte("test-kind"), 0600)).To(Succeed())
|
||||||
Expect(ioutil.WriteFile(filepath.Join(path, "alpha", "metadata", "provider"), []byte("test-provider"), 0600)).To(Succeed())
|
Expect(os.WriteFile(filepath.Join(path, "alpha", "metadata", "provider"), []byte("test-provider"), 0600)).To(Succeed())
|
||||||
Expect(ioutil.WriteFile(filepath.Join(path, "alpha", "metadata", "test-metadata-key"), []byte("test-metadata-value"), 0600)).To(Succeed())
|
Expect(os.WriteFile(filepath.Join(path, "alpha", "metadata", "test-metadata-key"), []byte("test-metadata-value"), 0600)).To(Succeed())
|
||||||
Expect(ioutil.WriteFile(filepath.Join(path, "alpha", "metadata", "test-metadata-key-trimmed"), []byte(" test-metadata-value-trimmed \n"), 0600)).To(Succeed())
|
Expect(os.WriteFile(filepath.Join(path, "alpha", "metadata", "test-metadata-key-trimmed"), []byte(" test-metadata-value-trimmed \n"), 0600)).To(Succeed())
|
||||||
Expect(os.MkdirAll(filepath.Join(path, "alpha", "secret"), 0755)).To(Succeed())
|
Expect(os.MkdirAll(filepath.Join(path, "alpha", "secret"), 0755)).To(Succeed())
|
||||||
Expect(ioutil.WriteFile(filepath.Join(path, "alpha", "secret", "test-secret-key"), []byte("test-secret-value"), 0600)).To(Succeed())
|
Expect(os.WriteFile(filepath.Join(path, "alpha", "secret", "test-secret-key"), []byte("test-secret-value"), 0600)).To(Succeed())
|
||||||
Expect(ioutil.WriteFile(filepath.Join(path, "alpha", "secret", "test-secret-key-trimmed"), []byte(" test-secret-value-trimmed \n"), 0600)).To(Succeed())
|
Expect(os.WriteFile(filepath.Join(path, "alpha", "secret", "test-secret-key-trimmed"), []byte(" test-secret-value-trimmed \n"), 0600)).To(Succeed())
|
||||||
|
|
||||||
Expect(os.MkdirAll(filepath.Join(path, "bravo", "metadata"), 0755)).To(Succeed())
|
Expect(os.MkdirAll(filepath.Join(path, "bravo", "metadata"), 0755)).To(Succeed())
|
||||||
Expect(ioutil.WriteFile(filepath.Join(path, "bravo", "metadata", "kind"), []byte("test-kind"), 0600)).To(Succeed())
|
Expect(os.WriteFile(filepath.Join(path, "bravo", "metadata", "kind"), []byte("test-kind"), 0600)).To(Succeed())
|
||||||
Expect(ioutil.WriteFile(filepath.Join(path, "bravo", "metadata", "provider"), []byte("test-provider"), 0600)).To(Succeed())
|
Expect(os.WriteFile(filepath.Join(path, "bravo", "metadata", "provider"), []byte("test-provider"), 0600)).To(Succeed())
|
||||||
Expect(ioutil.WriteFile(filepath.Join(path, "bravo", "metadata", "test-metadata-key"), []byte("test-metadata-value"), 0600)).To(Succeed())
|
Expect(os.WriteFile(filepath.Join(path, "bravo", "metadata", "test-metadata-key"), []byte("test-metadata-value"), 0600)).To(Succeed())
|
||||||
Expect(ioutil.WriteFile(filepath.Join(path, "bravo", "metadata", "test-metadata-key-trimmed"), []byte(" test-metadata-value-trimmed \n"), 0600)).To(Succeed())
|
Expect(os.WriteFile(filepath.Join(path, "bravo", "metadata", "test-metadata-key-trimmed"), []byte(" test-metadata-value-trimmed \n"), 0600)).To(Succeed())
|
||||||
Expect(os.MkdirAll(filepath.Join(path, "bravo", "secret"), 0755)).To(Succeed())
|
Expect(os.MkdirAll(filepath.Join(path, "bravo", "secret"), 0755)).To(Succeed())
|
||||||
Expect(ioutil.WriteFile(filepath.Join(path, "bravo", "secret", "test-secret-key"), []byte("test-secret-value"), 0600)).To(Succeed())
|
Expect(os.WriteFile(filepath.Join(path, "bravo", "secret", "test-secret-key"), []byte("test-secret-value"), 0600)).To(Succeed())
|
||||||
Expect(ioutil.WriteFile(filepath.Join(path, "bravo", "secret", "test-secret-key-trimmed"), []byte(" test-secret-value-trimmed \n"), 0600)).To(Succeed())
|
Expect(os.WriteFile(filepath.Join(path, "bravo", "secret", "test-secret-key-trimmed"), []byte(" test-secret-value-trimmed \n"), 0600)).To(Succeed())
|
||||||
|
|
||||||
Expect(os.MkdirAll(filepath.Join(path, ".hidden", "metadata"), 0755)).To(Succeed())
|
Expect(os.MkdirAll(filepath.Join(path, ".hidden", "metadata"), 0755)).To(Succeed())
|
||||||
Expect(ioutil.WriteFile(filepath.Join(path, ".hidden", "metadata", "kind"), []byte("test-kind"), 0600)).To(Succeed())
|
Expect(os.WriteFile(filepath.Join(path, ".hidden", "metadata", "kind"), []byte("test-kind"), 0600)).To(Succeed())
|
||||||
Expect(ioutil.WriteFile(filepath.Join(path, ".hiddenFile"), []byte("test-kind"), 0600)).To(Succeed())
|
Expect(os.WriteFile(filepath.Join(path, ".hiddenFile"), []byte("test-kind"), 0600)).To(Succeed())
|
||||||
})
|
})
|
||||||
|
|
||||||
context("Binding", func() {
|
context("Binding", func() {
|
||||||
|
@ -201,18 +273,18 @@ func testPlatform(t *testing.T, context spec.G, it spec.S) {
|
||||||
context("Kubernetes Service Bindings", func() {
|
context("Kubernetes Service Bindings", func() {
|
||||||
it.Before(func() {
|
it.Before(func() {
|
||||||
Expect(os.MkdirAll(filepath.Join(path, "alpha"), 0755)).To(Succeed())
|
Expect(os.MkdirAll(filepath.Join(path, "alpha"), 0755)).To(Succeed())
|
||||||
Expect(ioutil.WriteFile(filepath.Join(path, "alpha", "type"), []byte("test-type"), 0600)).To(Succeed())
|
Expect(os.WriteFile(filepath.Join(path, "alpha", "type"), []byte("test-type"), 0600)).To(Succeed())
|
||||||
Expect(ioutil.WriteFile(filepath.Join(path, "alpha", "provider"), []byte("test-provider"), 0600)).To(Succeed())
|
Expect(os.WriteFile(filepath.Join(path, "alpha", "provider"), []byte("test-provider"), 0600)).To(Succeed())
|
||||||
Expect(ioutil.WriteFile(filepath.Join(path, "alpha", "test-secret-key"), []byte("test-secret-value"), 0600)).To(Succeed())
|
Expect(os.WriteFile(filepath.Join(path, "alpha", "test-secret-key"), []byte("test-secret-value"), 0600)).To(Succeed())
|
||||||
|
|
||||||
Expect(os.MkdirAll(filepath.Join(path, "bravo"), 0755)).To(Succeed())
|
Expect(os.MkdirAll(filepath.Join(path, "bravo"), 0755)).To(Succeed())
|
||||||
Expect(ioutil.WriteFile(filepath.Join(path, "bravo", "type"), []byte("test-type"), 0600)).To(Succeed())
|
Expect(os.WriteFile(filepath.Join(path, "bravo", "type"), []byte("test-type"), 0600)).To(Succeed())
|
||||||
Expect(ioutil.WriteFile(filepath.Join(path, "bravo", "provider"), []byte("test-provider"), 0600)).To(Succeed())
|
Expect(os.WriteFile(filepath.Join(path, "bravo", "provider"), []byte("test-provider"), 0600)).To(Succeed())
|
||||||
Expect(ioutil.WriteFile(filepath.Join(path, "bravo", "test-secret-key"), []byte("test-secret-value"), 0600)).To(Succeed())
|
Expect(os.WriteFile(filepath.Join(path, "bravo", "test-secret-key"), []byte("test-secret-value"), 0600)).To(Succeed())
|
||||||
|
|
||||||
Expect(os.MkdirAll(filepath.Join(path, ".hidden", "metadata"), 0755)).To(Succeed())
|
Expect(os.MkdirAll(filepath.Join(path, ".hidden", "metadata"), 0755)).To(Succeed())
|
||||||
Expect(ioutil.WriteFile(filepath.Join(path, ".hidden", "metadata", "kind"), []byte("test-kind"), 0600)).To(Succeed())
|
Expect(os.WriteFile(filepath.Join(path, ".hidden", "metadata", "kind"), []byte("test-kind"), 0600)).To(Succeed())
|
||||||
Expect(ioutil.WriteFile(filepath.Join(path, ".hiddenFile"), []byte("test-kind"), 0600)).To(Succeed())
|
Expect(os.WriteFile(filepath.Join(path, ".hiddenFile"), []byte("test-kind"), 0600)).To(Succeed())
|
||||||
})
|
})
|
||||||
|
|
||||||
context("Binding", func() {
|
context("Binding", func() {
|
||||||
|
|
|
@ -0,0 +1,68 @@
|
||||||
|
{
|
||||||
|
"elephantsql-provider": [
|
||||||
|
{
|
||||||
|
"name": "elephantsql-binding-c6c60",
|
||||||
|
"binding_guid": "44ceb72f-100b-4f50-87a2-7809c8b42b8d",
|
||||||
|
"binding_name": "elephantsql-binding-c6c60",
|
||||||
|
"instance_guid": "391308e8-8586-4c42-b464-c7831aa2ad22",
|
||||||
|
"instance_name": "elephantsql-c6c60",
|
||||||
|
"label": "elephantsql-type",
|
||||||
|
"tags": [
|
||||||
|
"postgres",
|
||||||
|
"postgresql",
|
||||||
|
"relational"
|
||||||
|
],
|
||||||
|
"plan": "turtle",
|
||||||
|
"credentials": {
|
||||||
|
"uri": "postgres://exampleuser:examplepass@postgres.example.com:5432/exampleuser",
|
||||||
|
"int": 1,
|
||||||
|
"bool": true
|
||||||
|
},
|
||||||
|
"syslog_drain_url": null,
|
||||||
|
"volume_mounts": []
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"sendgrid-provider": [
|
||||||
|
{
|
||||||
|
"name": "mysendgrid",
|
||||||
|
"binding_guid": "6533b1b6-7916-488d-b286-ca33d3fa0081",
|
||||||
|
"binding_name": null,
|
||||||
|
"instance_guid": "8c907d0f-ec0f-44e4-87cf-e23c9ba3925d",
|
||||||
|
"instance_name": "mysendgrid",
|
||||||
|
"label": "sendgrid-type",
|
||||||
|
"tags": [
|
||||||
|
"smtp"
|
||||||
|
],
|
||||||
|
"plan": "free",
|
||||||
|
"credentials": {
|
||||||
|
"hostname": "smtp.example.com",
|
||||||
|
"username": "QvsXMbJ3rK",
|
||||||
|
"password": "HCHMOYluTv"
|
||||||
|
},
|
||||||
|
"syslog_drain_url": null,
|
||||||
|
"volume_mounts": []
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"postgres": [
|
||||||
|
{
|
||||||
|
"name": "postgres",
|
||||||
|
"label": "postgres",
|
||||||
|
"plan": "default",
|
||||||
|
"tags": [
|
||||||
|
"postgres"
|
||||||
|
],
|
||||||
|
"binding_guid": "6533b1b6-7916-488d-b286-ca33d3fa0081",
|
||||||
|
"binding_name": null,
|
||||||
|
"instance_guid": "8c907d0f-ec0f-44e4-87cf-e23c9ba3925d",
|
||||||
|
"credentials": {
|
||||||
|
"username": "foo",
|
||||||
|
"password": "bar",
|
||||||
|
"urls": {
|
||||||
|
"example": "http://example.com"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"syslog_drain_url": null,
|
||||||
|
"volume_mounts": []
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
183
tools/go.mod
183
tools/go.mod
|
@ -1,7 +1,184 @@
|
||||||
module github.com/buildpacks/libcnb/tools
|
module github.com/buildpacks/libcnb/tools
|
||||||
|
|
||||||
go 1.15
|
go 1.20
|
||||||
|
|
||||||
require golang.org/x/tools v0.1.4
|
require golang.org/x/tools v0.8.0
|
||||||
|
|
||||||
require github.com/golangci/golangci-lint v1.41.1
|
require github.com/golangci/golangci-lint v1.52.2
|
||||||
|
|
||||||
|
require (
|
||||||
|
4d63.com/gocheckcompilerdirectives v1.2.1 // indirect
|
||||||
|
4d63.com/gochecknoglobals v0.2.1 // indirect
|
||||||
|
github.com/Abirdcfly/dupword v0.0.11 // indirect
|
||||||
|
github.com/Antonboom/errname v0.1.9 // indirect
|
||||||
|
github.com/Antonboom/nilnil v0.1.4 // indirect
|
||||||
|
github.com/BurntSushi/toml v1.2.1 // indirect
|
||||||
|
github.com/Djarvur/go-err113 v0.0.0-20210108212216-aea10b59be24 // indirect
|
||||||
|
github.com/GaijinEntertainment/go-exhaustruct/v2 v2.3.0 // indirect
|
||||||
|
github.com/Masterminds/semver v1.5.0 // indirect
|
||||||
|
github.com/OpenPeeDeeP/depguard v1.1.1 // indirect
|
||||||
|
github.com/alexkohler/prealloc v1.0.0 // indirect
|
||||||
|
github.com/alingse/asasalint v0.0.11 // indirect
|
||||||
|
github.com/ashanbrown/forbidigo v1.5.1 // indirect
|
||||||
|
github.com/ashanbrown/makezero v1.1.1 // indirect
|
||||||
|
github.com/beorn7/perks v1.0.1 // indirect
|
||||||
|
github.com/bkielbasa/cyclop v1.2.0 // indirect
|
||||||
|
github.com/blizzy78/varnamelen v0.8.0 // indirect
|
||||||
|
github.com/bombsimon/wsl/v3 v3.4.0 // indirect
|
||||||
|
github.com/breml/bidichk v0.2.4 // indirect
|
||||||
|
github.com/breml/errchkjson v0.3.1 // indirect
|
||||||
|
github.com/butuzov/ireturn v0.1.1 // indirect
|
||||||
|
github.com/cespare/xxhash/v2 v2.2.0 // indirect
|
||||||
|
github.com/charithe/durationcheck v0.0.10 // indirect
|
||||||
|
github.com/chavacava/garif v0.0.0-20230227094218-b8c73b2037b8 // indirect
|
||||||
|
github.com/curioswitch/go-reassign v0.2.0 // indirect
|
||||||
|
github.com/daixiang0/gci v0.10.1 // indirect
|
||||||
|
github.com/davecgh/go-spew v1.1.1 // indirect
|
||||||
|
github.com/denis-tingaikin/go-header v0.4.3 // indirect
|
||||||
|
github.com/esimonov/ifshort v1.0.4 // indirect
|
||||||
|
github.com/ettle/strcase v0.1.1 // indirect
|
||||||
|
github.com/fatih/color v1.15.0 // indirect
|
||||||
|
github.com/fatih/structtag v1.2.0 // indirect
|
||||||
|
github.com/firefart/nonamedreturns v1.0.4 // indirect
|
||||||
|
github.com/fsnotify/fsnotify v1.6.0 // indirect
|
||||||
|
github.com/fzipp/gocyclo v0.6.0 // indirect
|
||||||
|
github.com/go-critic/go-critic v0.7.0 // 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.1.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-xmlfmt/xmlfmt v1.1.2 // indirect
|
||||||
|
github.com/gobwas/glob v0.2.3 // indirect
|
||||||
|
github.com/gofrs/flock v0.8.1 // indirect
|
||||||
|
github.com/golang/protobuf v1.5.3 // indirect
|
||||||
|
github.com/golangci/check v0.0.0-20180506172741-cfe4005ccda2 // indirect
|
||||||
|
github.com/golangci/dupl v0.0.0-20180902072040-3e9179ac440a // indirect
|
||||||
|
github.com/golangci/go-misc v0.0.0-20220329215616-d24fe342adfe // indirect
|
||||||
|
github.com/golangci/gofmt v0.0.0-20220901101216-f2edd75033f2 // indirect
|
||||||
|
github.com/golangci/lint-1 v0.0.0-20191013205115-297bf364a8e0 // indirect
|
||||||
|
github.com/golangci/maligned v0.0.0-20180506175553-b1d89398deca // indirect
|
||||||
|
github.com/golangci/misspell v0.4.0 // indirect
|
||||||
|
github.com/golangci/revgrep v0.0.0-20220804021717-745bb2f7c2e6 // indirect
|
||||||
|
github.com/golangci/unconvert v0.0.0-20180507085042-28b1c447d1f4 // indirect
|
||||||
|
github.com/google/go-cmp v0.5.9 // indirect
|
||||||
|
github.com/gordonklaus/ineffassign v0.0.0-20230107090616-13ace0543b28 // indirect
|
||||||
|
github.com/gostaticanalysis/analysisutil v0.7.1 // indirect
|
||||||
|
github.com/gostaticanalysis/comment v1.4.2 // indirect
|
||||||
|
github.com/gostaticanalysis/forcetypeassert v0.1.0 // indirect
|
||||||
|
github.com/gostaticanalysis/nilerr v0.1.1 // indirect
|
||||||
|
github.com/hashicorp/errwrap v1.1.0 // indirect
|
||||||
|
github.com/hashicorp/go-multierror v1.1.1 // indirect
|
||||||
|
github.com/hashicorp/go-version v1.6.0 // 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.5.1 // indirect
|
||||||
|
github.com/jingyugao/rowserrcheck v1.1.1 // indirect
|
||||||
|
github.com/jirfag/go-printf-func-name v0.0.0-20200119135958-7558a9eaa5af // indirect
|
||||||
|
github.com/julz/importas v0.1.0 // indirect
|
||||||
|
github.com/junk1tm/musttag v0.5.0 // indirect
|
||||||
|
github.com/kisielk/errcheck v1.6.3 // indirect
|
||||||
|
github.com/kisielk/gotool v1.0.0 // indirect
|
||||||
|
github.com/kkHAIKE/contextcheck v1.1.4 // indirect
|
||||||
|
github.com/kulti/thelper v0.6.3 // indirect
|
||||||
|
github.com/kunwardeep/paralleltest v1.0.6 // indirect
|
||||||
|
github.com/kyoh86/exportloopref v0.1.11 // indirect
|
||||||
|
github.com/ldez/gomoddirectives v0.2.3 // indirect
|
||||||
|
github.com/ldez/tagliatelle v0.4.0 // indirect
|
||||||
|
github.com/leonklingele/grouper v1.1.1 // indirect
|
||||||
|
github.com/lufeee/execinquery v1.2.1 // indirect
|
||||||
|
github.com/magiconair/properties v1.8.7 // indirect
|
||||||
|
github.com/maratori/testableexamples v1.0.0 // indirect
|
||||||
|
github.com/maratori/testpackage v1.1.1 // indirect
|
||||||
|
github.com/matoous/godox v0.0.0-20230222163458-006bad1f9d26 // indirect
|
||||||
|
github.com/mattn/go-colorable v0.1.13 // indirect
|
||||||
|
github.com/mattn/go-isatty v0.0.18 // indirect
|
||||||
|
github.com/mattn/go-runewidth v0.0.14 // indirect
|
||||||
|
github.com/matttproud/golang_protobuf_extensions v1.0.4 // indirect
|
||||||
|
github.com/mbilski/exhaustivestruct v1.2.0 // indirect
|
||||||
|
github.com/mgechev/revive v1.3.1 // indirect
|
||||||
|
github.com/mitchellh/go-homedir v1.1.0 // indirect
|
||||||
|
github.com/mitchellh/mapstructure v1.5.0 // indirect
|
||||||
|
github.com/moricho/tparallel v0.3.1 // indirect
|
||||||
|
github.com/nakabonne/nestif v0.3.1 // indirect
|
||||||
|
github.com/nbutton23/zxcvbn-go v0.0.0-20210217022336-fa2cb2858354 // indirect
|
||||||
|
github.com/nishanths/exhaustive v0.10.0 // indirect
|
||||||
|
github.com/nishanths/predeclared v0.2.2 // indirect
|
||||||
|
github.com/nunnatsa/ginkgolinter v0.11.0 // indirect
|
||||||
|
github.com/olekukonko/tablewriter v0.0.5 // indirect
|
||||||
|
github.com/pelletier/go-toml v1.9.5 // indirect
|
||||||
|
github.com/pelletier/go-toml/v2 v2.0.7 // indirect
|
||||||
|
github.com/phayes/checkstyle v0.0.0-20170904204023-bfd46e6a821d // indirect
|
||||||
|
github.com/pkg/errors v0.9.1 // indirect
|
||||||
|
github.com/pmezard/go-difflib v1.0.0 // indirect
|
||||||
|
github.com/polyfloyd/go-errorlint v1.4.0 // indirect
|
||||||
|
github.com/prometheus/client_golang v1.14.0 // indirect
|
||||||
|
github.com/prometheus/client_model v0.3.0 // indirect
|
||||||
|
github.com/prometheus/common v0.42.0 // indirect
|
||||||
|
github.com/prometheus/procfs v0.9.0 // indirect
|
||||||
|
github.com/quasilyte/go-ruleguard v0.3.19 // 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/rivo/uniseg v0.4.4 // indirect
|
||||||
|
github.com/ryancurrah/gomodguard v1.3.0 // indirect
|
||||||
|
github.com/ryanrolds/sqlclosecheck v0.4.0 // indirect
|
||||||
|
github.com/sanposhiho/wastedassign/v2 v2.0.7 // indirect
|
||||||
|
github.com/sashamelentyev/interfacebloat v1.1.0 // indirect
|
||||||
|
github.com/sashamelentyev/usestdlibvars v1.23.0 // indirect
|
||||||
|
github.com/securego/gosec/v2 v2.15.0 // indirect
|
||||||
|
github.com/shazow/go-diff v0.0.0-20160112020656-b6b7b6733b8c // indirect
|
||||||
|
github.com/sirupsen/logrus v1.9.0 // indirect
|
||||||
|
github.com/sivchari/containedctx v1.0.3 // indirect
|
||||||
|
github.com/sivchari/nosnakecase v1.7.0 // indirect
|
||||||
|
github.com/sivchari/tenv v1.7.1 // indirect
|
||||||
|
github.com/sonatard/noctx v0.0.2 // indirect
|
||||||
|
github.com/sourcegraph/go-diff v0.7.0 // indirect
|
||||||
|
github.com/spf13/afero v1.9.5 // indirect
|
||||||
|
github.com/spf13/cast v1.5.0 // indirect
|
||||||
|
github.com/spf13/cobra v1.7.0 // indirect
|
||||||
|
github.com/spf13/jwalterweatherman v1.1.0 // indirect
|
||||||
|
github.com/spf13/pflag v1.0.5 // indirect
|
||||||
|
github.com/spf13/viper v1.15.0 // indirect
|
||||||
|
github.com/ssgreg/nlreturn/v2 v2.2.1 // indirect
|
||||||
|
github.com/stbenjam/no-sprintf-host-port v0.1.1 // indirect
|
||||||
|
github.com/stretchr/objx v0.5.0 // indirect
|
||||||
|
github.com/stretchr/testify v1.8.2 // indirect
|
||||||
|
github.com/subosito/gotenv v1.4.2 // indirect
|
||||||
|
github.com/sylvia7788/contextcheck v1.0.9 // indirect
|
||||||
|
github.com/t-yuki/gocover-cobertura v0.0.0-20180217150009-aaee18c8195c // indirect
|
||||||
|
github.com/tdakkota/asciicheck v0.2.0 // indirect
|
||||||
|
github.com/tetafro/godot v1.4.11 // indirect
|
||||||
|
github.com/timakin/bodyclose v0.0.0-20221125081123-e39cf3fc478e // indirect
|
||||||
|
github.com/timonwong/loggercheck v0.9.4 // indirect
|
||||||
|
github.com/tomarrell/wrapcheck/v2 v2.8.1 // indirect
|
||||||
|
github.com/tommy-muehle/go-mnd/v2 v2.5.1 // indirect
|
||||||
|
github.com/ultraware/funlen v0.0.3 // indirect
|
||||||
|
github.com/ultraware/whitespace v0.0.5 // indirect
|
||||||
|
github.com/uudashr/gocognit v1.0.6 // indirect
|
||||||
|
github.com/yagipy/maintidx v1.0.0 // indirect
|
||||||
|
github.com/yeya24/promlinter v0.2.0 // indirect
|
||||||
|
gitlab.com/bosi/decorder v0.2.3 // indirect
|
||||||
|
go.uber.org/atomic v1.10.0 // indirect
|
||||||
|
go.uber.org/multierr v1.11.0 // indirect
|
||||||
|
go.uber.org/zap v1.24.0 // indirect
|
||||||
|
golang.org/x/exp v0.0.0-20230321023759-10a507213a29 // indirect
|
||||||
|
golang.org/x/exp/typeparams v0.0.0-20230321023759-10a507213a29 // indirect
|
||||||
|
golang.org/x/mod v0.10.0 // indirect
|
||||||
|
golang.org/x/sync v0.1.0 // indirect
|
||||||
|
golang.org/x/sys v0.7.0 // indirect
|
||||||
|
golang.org/x/text v0.9.0 // indirect
|
||||||
|
golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2 // indirect
|
||||||
|
google.golang.org/protobuf v1.30.0 // 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.4.3 // indirect
|
||||||
|
mvdan.cc/gofumpt v0.5.0 // indirect
|
||||||
|
mvdan.cc/interfacer v0.0.0-20180901003855-c20040233aed // indirect
|
||||||
|
mvdan.cc/lint v0.0.0-20170908181259-adc824a0674b // indirect
|
||||||
|
mvdan.cc/unparam v0.0.0-20230312165513-e84e2d14e3b8 // indirect
|
||||||
|
)
|
||||||
|
|
1058
tools/go.sum
1058
tools/go.sum
File diff suppressed because it is too large
Load Diff
|
@ -1,3 +1,4 @@
|
||||||
|
//go:build tools
|
||||||
// +build tools
|
// +build tools
|
||||||
|
|
||||||
package tools
|
package tools
|
||||||
|
|
Loading…
Reference in New Issue