mirror of https://github.com/buildpacks/libcnb.git
Compare commits
187 Commits
Author | SHA1 | Date |
---|---|---|
|
f7e753e42f | |
|
274a25f737 | |
|
a5d5f00dfc | |
|
babcbff863 | |
|
4c50e41bb7 | |
|
dfb149268e | |
|
9e96b2cdf8 | |
|
986aa1f129 | |
|
ec253d8a85 | |
|
0a704de531 | |
|
4d96611671 | |
|
fc02d58a39 | |
|
aa64fa6d04 | |
|
e2504aad02 | |
|
541caaca58 | |
|
b1a32e8a04 | |
|
b6de1780e4 | |
|
685a6de0df | |
|
f85e7067d6 | |
|
42eabc313b | |
|
b9fc3fec80 | |
|
b32eb02dfa | |
|
7a3158aeb9 | |
|
2002d252e9 | |
|
ecdde19b9b | |
|
29547c451a | |
|
46d41fad11 | |
|
2a303f641c | |
|
fbd6632792 | |
|
089a0f9d04 | |
|
3ba867d5de | |
|
70e38ccecc | |
|
777d800b23 | |
|
084bd173f5 | |
|
193b480460 | |
|
4b0cdf2fb8 | |
|
1d2cd33a8b | |
|
5e1b803e2d | |
|
976fce60fc | |
|
7a8d6df0fb | |
|
9343310624 | |
|
68f1d7dc99 | |
|
09148d831e | |
|
432f14274b | |
|
59903afb4c | |
|
d6b2574ae1 | |
|
3e3992aaa5 | |
|
663213ffbf | |
|
ff0986080e | |
|
19d88148ff | |
|
cf9baef95d | |
|
4f8f92a58c | |
|
e7c2e5da91 | |
|
5d3d3f6e6d | |
|
0ffaf167f2 | |
|
6dcaa2eeb2 | |
|
d77e5b49f3 | |
|
a8abacf488 | |
|
ff323e4cbb | |
|
2b39329ef3 | |
|
0f889eda5e | |
|
d4c34358cd | |
|
82f0d2fde6 | |
|
d7fde7baad | |
|
f10dc6660e | |
|
2313404efa | |
|
53ea2d4112 | |
|
d13fcca61b | |
|
7ef9b73067 | |
|
21fa5b9f3e | |
|
70be45f510 | |
|
bacbd31203 | |
|
bb82ce0702 | |
|
e9f2cb8583 | |
|
9541f56830 | |
|
a3eccb730e | |
|
438a986000 | |
|
9b489461d0 | |
|
3175e42c71 | |
|
8c162d5042 | |
|
5f52f3ecc8 | |
|
7185622fa0 | |
|
3308e77997 | |
|
78810648dd | |
|
48a6ad033c | |
|
cc26ad5bcc | |
|
7fcb08c9fd | |
|
b2aa734851 | |
|
daf9de1766 | |
|
87b531972f | |
|
734dbf9b26 | |
|
39445a8884 | |
|
5234f9c837 | |
|
f7f9919675 | |
|
8542bee9d0 | |
|
8347fd9e5b | |
|
05f5c35c95 | |
|
1f740aba88 | |
|
8553694890 | |
|
646bf6b465 | |
|
6f71a8467d | |
|
b7d3d515ef | |
|
2696858ab0 | |
|
4892e3b4c5 | |
|
4b96ab802c | |
|
98647956ab | |
|
272cbe263f | |
|
8c92bce2dc | |
|
46bbd4601e | |
|
a9404c31cb | |
|
2b53a1d8a9 | |
|
a54cf8ab45 | |
|
a81dbdf586 | |
|
3285cf1a1b | |
|
f2d102431a | |
|
9f998abc47 | |
|
5143e3dcfa | |
|
6882b47e95 | |
|
6ea59bbedc | |
|
3354bfcda5 | |
|
5e36043456 | |
|
5bb9c7b3d8 | |
|
5114b77d7f | |
|
71efa8132a | |
|
e6b42e9dc7 | |
|
31c63bc860 | |
|
87f95b2b80 | |
|
66af9c5c22 | |
|
9f40fa2bec | |
|
a9cb6b2dd6 | |
|
1dfbc993cc | |
|
3e35e4513b | |
|
f1b4378a23 | |
|
60e9ca7aff | |
|
d61ffa8660 | |
|
8a82a0ef1a | |
|
34e343460c | |
|
4d5ea0c017 | |
|
a244d9ffc0 | |
|
587cf4b267 | |
|
aaff56ad46 | |
|
4d4003ef78 | |
|
3b38a59476 | |
|
63cd2b0f30 | |
|
da9b109ec8 | |
|
5beb81e48b | |
|
11abe2b4f0 | |
|
1031c7329e | |
|
48c2123e6c | |
|
f7b68c6ccf | |
|
9c5c713940 | |
|
481034cb02 | |
|
73483d77e8 | |
|
33398bde5c | |
|
3cebafc8e4 | |
|
1d06c7f6e3 | |
|
c3a7795dbb | |
|
aed8887609 | |
|
00601e4588 | |
|
31c9b286f3 | |
|
e90023ce44 | |
|
9b80891fe5 | |
|
da8314616c | |
|
5e05852376 | |
|
d29fc724c0 | |
|
0496d9ff6e | |
|
c36f48d861 | |
|
8496ef4039 | |
|
45321cb1ac | |
|
0a77d8caf7 | |
|
a1b0928708 | |
|
4454619860 | |
|
7eb31bf136 | |
|
d893d2a8de | |
|
42462272ec | |
|
81cf3b415a | |
|
16b7007d6f | |
|
03d8910db4 | |
|
26e4e63625 | |
|
0f1f628f40 | |
|
4ae60411a2 | |
|
1112c1f8ee | |
|
7394cb3817 | |
|
3a76485b9c | |
|
615e972098 | |
|
6906ce9b95 | |
|
baf6c4c366 |
|
@ -4,6 +4,8 @@ updates:
|
|||
directory: /
|
||||
schedule:
|
||||
interval: daily
|
||||
ignore:
|
||||
- dependency-name: github.com/onsi/gomega
|
||||
labels:
|
||||
- semver:patch
|
||||
- type:dependency-upgrade
|
||||
|
|
|
@ -25,3 +25,18 @@
|
|||
- name: type:task
|
||||
description: A general task
|
||||
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
|
||||
|
||||
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
|
||||
run: |
|
||||
#!/usr/bin/env bash
|
||||
|
|
|
@ -1 +1 @@
|
|||
1.10.0
|
||||
1.42.0
|
||||
|
|
|
@ -1 +0,0 @@
|
|||
1.4.0
|
|
@ -12,7 +12,7 @@ jobs:
|
|||
runs-on:
|
||||
- ubuntu-latest
|
||||
steps:
|
||||
- uses: mheap/github-action-required-labels@v1
|
||||
- uses: mheap/github-action-required-labels@v5
|
||||
with:
|
||||
count: 1
|
||||
labels: semver:major, semver:minor, semver:patch
|
||||
|
@ -22,7 +22,7 @@ jobs:
|
|||
runs-on:
|
||||
- ubuntu-latest
|
||||
steps:
|
||||
- uses: mheap/github-action-required-labels@v1
|
||||
- uses: mheap/github-action-required-labels@v5
|
||||
with:
|
||||
count: 1
|
||||
labels: type:bug, type:dependency-upgrade, type:documentation, type:enhancement, type:question, type:task
|
|
@ -11,7 +11,7 @@ jobs:
|
|||
runs-on:
|
||||
- ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- uses: actions/checkout@v4
|
||||
- uses: micnncim/action-label-syncer@v1
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.IMPLEMENTATION_GITHUB_TOKEN }}
|
|
@ -0,0 +1,54 @@
|
|||
name: Tests
|
||||
"on":
|
||||
merge_group:
|
||||
types:
|
||||
- checks_requested
|
||||
branches:
|
||||
- main
|
||||
pull_request: {}
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
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.24"
|
||||
- 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"
|
|
@ -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.24"
|
||||
- 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 | cut -d '.' -f 1-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.24"
|
||||
- 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 }}
|
|
@ -14,19 +14,19 @@ jobs:
|
|||
runs-on:
|
||||
- ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/setup-go@v2
|
||||
- uses: actions/setup-go@v5
|
||||
with:
|
||||
go-version: "1.16"
|
||||
go-version: "1.24"
|
||||
- name: Install octo
|
||||
run: |
|
||||
#!/usr/bin/env bash
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
GO111MODULE=on go get -u -ldflags="-s -w" github.com/paketo-buildpacks/pipeline-builder/cmd/octo
|
||||
- uses: actions/checkout@v2
|
||||
- id: pipeline
|
||||
name: Update Pipeline
|
||||
go install -ldflags="-s -w" github.com/paketo-buildpacks/pipeline-builder/cmd/octo@latest
|
||||
- uses: actions/checkout@v4
|
||||
- name: Update Pipeline
|
||||
id: pipeline
|
||||
run: |
|
||||
#!/usr/bin/env bash
|
||||
|
||||
|
@ -38,6 +38,7 @@ jobs:
|
|||
OLD_VERSION="0.0.0"
|
||||
fi
|
||||
|
||||
rm .github/workflows/pb-*.yml || true
|
||||
octo --descriptor "${DESCRIPTOR}"
|
||||
|
||||
PAYLOAD=$(gh api /repos/paketo-buildpacks/pipeline-builder/releases/latest)
|
||||
|
@ -54,15 +55,23 @@ jobs:
|
|||
)
|
||||
|
||||
git add .github/
|
||||
git add .gitignore
|
||||
|
||||
if [ -f scripts/build.sh ]; then
|
||||
git add scripts/build.sh
|
||||
fi
|
||||
|
||||
git checkout -- .
|
||||
|
||||
echo "::set-output name=old-version::${OLD_VERSION}"
|
||||
echo "::set-output name=new-version::${NEW_VERSION}"
|
||||
echo "::set-output name=release-notes::${RELEASE_NOTES//$'\n'/%0A}"
|
||||
echo "old-version=${OLD_VERSION}" >> "$GITHUB_OUTPUT"
|
||||
echo "new-version=${NEW_VERSION}" >> "$GITHUB_OUTPUT"
|
||||
|
||||
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:
|
||||
DESCRIPTOR: .github/pipeline-descriptor.yml
|
||||
GITHUB_TOKEN: ${{ secrets.IMPLEMENTATION_GITHUB_TOKEN }}
|
||||
- uses: peter-evans/create-pull-request@v3
|
||||
- uses: peter-evans/create-pull-request@v6
|
||||
with:
|
||||
author: ${{ secrets.IMPLEMENTATION_GITHUB_USERNAME }} <${{ secrets.IMPLEMENTATION_GITHUB_USERNAME }}@users.noreply.github.com>
|
||||
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"
|
|
@ -11,3 +11,10 @@
|
|||
# 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.
|
||||
|
||||
bin/
|
||||
linux/
|
||||
dependencies/
|
||||
package/
|
||||
scratch/
|
||||
|
||||
|
|
|
@ -0,0 +1,45 @@
|
|||
# Migration Guide
|
||||
|
||||
This guide highlights the major differences between libcnb v1 and v2 with a focus on what you as an author need to change to upgrade your buildpacks from v1 to v2.
|
||||
|
||||
## Buildpack API Support
|
||||
|
||||
With libcnb v1, you get support for buildpack API 0.5 to 0.8. With v2, you get support for 0.8 to 0.10. This should provide a seamless transition as you can continue to use buildpack API 0.8 with
|
||||
|
||||
## Removal of LayerContributor
|
||||
|
||||
The LayerContributor has been removed. Previously, libcnb would take a list of LayerContributors and execute them to retrieve the list of Layers to process. Now it just directly takes the list of Layers to process.
|
||||
|
||||
The path forward is to either update your buildpacks to return layers or to implement your own LayerContributor interface as a means to ease the transition to libcnb v2.
|
||||
|
||||
## Replace Builder and Detector with Functions
|
||||
|
||||
The Builder and Detector interfaces have been removed and replaced with functions, specifically BuildFunc and DetectFunc. They serve the same purpose, but simplify your implementation because you do not need to implement the single method interface, you can only need pass in a function that will be called back.
|
||||
|
||||
The path forward is to remove your Builder and Detector structs and pass the method directly into libcnb.
|
||||
|
||||
## Rename `poet` to `log`
|
||||
|
||||
We have renamed the `poet` module to be called `log`. This is a simple find & replace change in your code. We have also simplified the method names, so instead of needing to say `poet.NewLogger`, you can say `log.New` or `log.NewWithOptions`.
|
||||
|
||||
We have also introduced a new `Logger` interface which can be overridden to control how libcnb logs. A basic implementation has been provided, called `PlainLogger`. If you want to supply a custom implementation, it can be passed in through main/build/detect functions.
|
||||
|
||||
Lastly, libcnb has been modified such that it will only log at the `Debug` level. Anything problems that arise will instead return an error, which you as the buildpack author should handle.
|
||||
|
||||
## Remove Deprecated Pre-Buildpack API 0.7 BOM
|
||||
|
||||
The pre-buildpack API 0.7 BOM API was marked for deprecation in libcnb v1. It has been removed in v2. If you are still using it, you will need to migrate to use the new SBOM functionality provided by the [Buildpacks API](https://github.com/buildpacks/rfcs/blob/main/text/0095-sbom.md).
|
||||
|
||||
## Path to Source Code Changed
|
||||
|
||||
In libcnb v1, `BuildContext.Application.Path` points to the application source code. This was shortened to `BuildContext.ApplicationPath`. The same change was made for `DetectContext.ApplicationPath`.
|
||||
|
||||
## Remove Deprecated CNB Binding Support
|
||||
|
||||
The CNB Binding specification has long been replaced by the Service Binding Specification for Kubernetes. Support for the CNB Binding Support had remained, but it is removed in v2. This is unlikely to impact anyone.
|
||||
|
||||
## Remove Shell-Specific Logic & Overridable Process Arguments
|
||||
|
||||
To comply with [RFC #168](https://github.com/buildpacks/rfcs/pull/168), we remove the `Direct` field from the `Process` struct. We also change `Command` from a `string` to `string[]` on the `Process` struct, to support overridable process arguments. Both of these require at leats API 0.9.
|
||||
|
||||
In conjunction with this, we have also removed Profile & `profile.d` support. These should be replaced with the [Profile Buildpack](https://github.com/buildpacks/profile) and `exec.d`.
|
2
Makefile
2
Makefile
|
@ -1,7 +1,7 @@
|
|||
# Go parameters
|
||||
GOCMD?=go
|
||||
GO_VERSION=$(shell go list -m -f "{{.GoVersion}}")
|
||||
PACKAGE_BASE=github.com/buildpacks/libcnb
|
||||
PACKAGE_BASE=github.com/buildpacks/libcnb/v2
|
||||
|
||||
all: test
|
||||
|
||||
|
|
|
@ -14,6 +14,12 @@
|
|||
go get github.com/buildpacks/libcnb
|
||||
```
|
||||
|
||||
or for the v2 alpha
|
||||
|
||||
```
|
||||
go get github.com/buildpacks/libcnb@v2.0.0-alpha.1
|
||||
```
|
||||
|
||||
#### Docs
|
||||
|
||||
https://pkg.go.dev/github.com/buildpacks/libcnb?tab=doc
|
||||
|
|
119
application.go
119
application.go
|
@ -1,119 +0,0 @@
|
|||
/*
|
||||
* 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
|
||||
|
||||
// Application is the user contributed application to build.
|
||||
type Application struct {
|
||||
|
||||
// Path is the path to the application.
|
||||
Path string
|
||||
}
|
||||
|
||||
// Label represents an image label.
|
||||
type Label struct {
|
||||
|
||||
// Key is the key of the label.
|
||||
Key string `toml:"key"`
|
||||
|
||||
// Value is the value of the label.
|
||||
Value string `toml:"value"`
|
||||
}
|
||||
|
||||
// Process represents metadata about a type of command that can be run.
|
||||
type Process struct {
|
||||
|
||||
// Type is the type of the process.
|
||||
Type string `toml:"type"`
|
||||
|
||||
// Command is the command of the process.
|
||||
Command string `toml:"command"`
|
||||
|
||||
// Arguments are arguments to the command.
|
||||
Arguments []string `toml:"args"`
|
||||
|
||||
// Command is exec'd directly by the os (no profile.d scripts run)
|
||||
Direct bool `toml:"direct,omitempty"`
|
||||
|
||||
// Default can be set to true to indicate that the process
|
||||
// type being defined should be the default process type for the app image.
|
||||
Default bool `toml:"default,omitempty"`
|
||||
}
|
||||
|
||||
// Slice represents metadata about a slice.
|
||||
type Slice struct {
|
||||
|
||||
// Paths are the contents of the slice.
|
||||
Paths []string `toml:"paths"`
|
||||
}
|
||||
|
||||
// LaunchTOML represents the contents of launch.toml.
|
||||
type LaunchTOML struct {
|
||||
|
||||
// Labels is the collection of image labels contributed by the buildpack.
|
||||
Labels []Label `toml:"labels"`
|
||||
|
||||
// Processes is the collection of process types contributed by the buildpack.
|
||||
Processes []Process `toml:"processes"`
|
||||
|
||||
// Slices is the collection of slices contributed by the buildpack.
|
||||
Slices []Slice `toml:"slices"`
|
||||
|
||||
// BOM is a collection of entries for the bill of materials.
|
||||
BOM []BOMEntry `toml:"bom"`
|
||||
}
|
||||
|
||||
func (l LaunchTOML) isEmpty() bool {
|
||||
return len(l.Labels) == 0 && len(l.Processes) == 0 && len(l.Slices) == 0 && len(l.BOM) == 0
|
||||
}
|
||||
|
||||
// BuildTOML represents the contents of build.toml.
|
||||
type BuildTOML struct {
|
||||
// BOM contains the build-time bill of materials.
|
||||
BOM []BOMEntry `toml:"bom"`
|
||||
|
||||
// Unmet is a collection of buildpack plan entries that should be passed through to subsequent providers.
|
||||
Unmet []UnmetPlanEntry
|
||||
}
|
||||
|
||||
func (b BuildTOML) isEmpty() bool {
|
||||
return len(b.Unmet) == 0
|
||||
}
|
||||
|
||||
// BOMEntry contains a bill of materials entry.
|
||||
type BOMEntry struct {
|
||||
// Name represents the name of the entry.
|
||||
Name string `toml:"name"`
|
||||
|
||||
// Metadata is the metadata of the entry. Optional.
|
||||
Metadata map[string]interface{} `toml:"metadata,omitempty"`
|
||||
|
||||
// Launch indicates whether the given entry is included in app image. If launch is true the entry
|
||||
// will be added to the app image Bill of Materials. Launch should be true if the entry describes
|
||||
// the contents of a launch layer or app layer.
|
||||
Launch bool `toml:"-"`
|
||||
|
||||
// Build indicates whether the given entry is available at build time. If build is true the entry
|
||||
// will be added to the build Bill of Materials.
|
||||
Build bool `toml:"-"`
|
||||
}
|
||||
|
||||
// Store represents the contents of store.toml
|
||||
type Store struct {
|
||||
|
||||
// Metadata represents the persistent metadata.
|
||||
Metadata map[string]interface{} `toml:"metadata"`
|
||||
}
|
289
build.go
289
build.go
|
@ -25,16 +25,17 @@ import (
|
|||
"strings"
|
||||
|
||||
"github.com/BurntSushi/toml"
|
||||
"github.com/Masterminds/semver"
|
||||
|
||||
"github.com/buildpacks/libcnb/internal"
|
||||
"github.com/buildpacks/libcnb/poet"
|
||||
"github.com/buildpacks/libcnb/v2/internal"
|
||||
"github.com/buildpacks/libcnb/v2/log"
|
||||
)
|
||||
|
||||
// BuildContext contains the inputs to build.
|
||||
type BuildContext struct {
|
||||
|
||||
// Application is application to build.
|
||||
Application Application
|
||||
// ApplicationPath is the location of the application source code as provided by
|
||||
// the lifecycle.
|
||||
ApplicationPath string
|
||||
|
||||
// Buildpack is metadata about the buildpack, from buildpack.toml.
|
||||
Buildpack Buildpack
|
||||
|
@ -42,6 +43,9 @@ type BuildContext struct {
|
|||
// Layers is the layers available to the buildpack.
|
||||
Layers Layers
|
||||
|
||||
// Logger is the way to write messages to the end user
|
||||
Logger log.Logger
|
||||
|
||||
// PersistentMetadata is metadata that is persisted even across cache cleaning.
|
||||
PersistentMetadata map[string]interface{}
|
||||
|
||||
|
@ -51,20 +55,23 @@ type BuildContext struct {
|
|||
// Platform is the contents of the platform.
|
||||
Platform Platform
|
||||
|
||||
// StackID is the ID of the stack.
|
||||
// Deprecated: StackID is the ID of the stack.
|
||||
StackID string
|
||||
|
||||
// TargetInfo contains info of the target (os, arch, ...).
|
||||
TargetInfo TargetInfo
|
||||
|
||||
// TargetDistro is the target distribution (name, version).
|
||||
TargetDistro TargetDistro
|
||||
}
|
||||
|
||||
// BuildResult contains the results of detection.
|
||||
type BuildResult struct {
|
||||
// BOM contains entries to be appended to the app image Bill of Materials and/or build Bill of Materials.
|
||||
BOM *BOM
|
||||
|
||||
// Labels are the image labels contributed by the buildpack.
|
||||
Labels []Label
|
||||
|
||||
// Layers is the collection of LayerCreators contributed by the buildpack.
|
||||
Layers []LayerContributor
|
||||
Layers []Layer
|
||||
|
||||
// PersistentMetadata is metadata that is persisted even across cache cleaning.
|
||||
PersistentMetadata map[string]interface{}
|
||||
|
@ -80,16 +87,19 @@ type BuildResult struct {
|
|||
Unmet []UnmetPlanEntry
|
||||
}
|
||||
|
||||
// BOM contains all Bill of Materials entries
|
||||
type BOM struct {
|
||||
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.8"
|
||||
|
||||
// MaxSupportedBPVersion indicates the maximum supported version of the Buildpacks API
|
||||
MaxSupportedBPVersion = "0.10"
|
||||
)
|
||||
|
||||
// NewBuildResult creates a new BuildResult instance, initializing empty fields.
|
||||
func NewBuildResult() BuildResult {
|
||||
return BuildResult{
|
||||
PersistentMetadata: make(map[string]interface{}),
|
||||
BOM: &BOM{},
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -100,62 +110,46 @@ func (b BuildResult) String() string {
|
|||
}
|
||||
|
||||
return fmt.Sprintf(
|
||||
"{BOM: %+v, Labels:%+v Layers:%s PersistentMetadata:%+v Processes:%+v Slices:%+v, Unmet:%+v}",
|
||||
b.BOM, b.Labels, l, b.PersistentMetadata, b.PersistentMetadata, b.Slices, b.Unmet,
|
||||
"{Labels:%+v Layers:%s PersistentMetadata:%+v Processes:%+v Slices:%+v, Unmet:%+v}",
|
||||
b.Labels, l, b.PersistentMetadata, b.PersistentMetadata, b.Slices, b.Unmet,
|
||||
)
|
||||
}
|
||||
|
||||
//go:generate mockery -name Builder -case=underscore
|
||||
|
||||
// Builder describes an interface for types that can be used by the Build function.
|
||||
type Builder interface {
|
||||
|
||||
// Build takes a context and returns a result, performing buildpack build behaviors.
|
||||
Build(context BuildContext) (BuildResult, error)
|
||||
}
|
||||
// BuildFunc takes a context and returns a result, performing buildpack build behaviors.
|
||||
type BuildFunc func(context BuildContext) (BuildResult, error)
|
||||
|
||||
// Build is called by the main function of a buildpack, for build.
|
||||
func Build(builder Builder, options ...Option) {
|
||||
config := Config{
|
||||
arguments: os.Args,
|
||||
environmentWriter: internal.EnvironmentWriter{},
|
||||
exitHandler: internal.NewExitHandler(),
|
||||
tomlWriter: internal.TOMLWriter{},
|
||||
}
|
||||
|
||||
for _, option := range options {
|
||||
config = option(config)
|
||||
}
|
||||
|
||||
if len(config.arguments) != 4 {
|
||||
config.exitHandler.Error(fmt.Errorf("expected 3 arguments and received %d", len(config.arguments)-1))
|
||||
return
|
||||
}
|
||||
|
||||
func Build(build BuildFunc, config Config) {
|
||||
var (
|
||||
err error
|
||||
file string
|
||||
ok bool
|
||||
)
|
||||
ctx := BuildContext{}
|
||||
logger := poet.NewLogger(os.Stdout)
|
||||
ctx := BuildContext{Logger: config.logger}
|
||||
|
||||
ctx.Application.Path, err = os.Getwd()
|
||||
ctx.ApplicationPath, err = os.Getwd()
|
||||
if err != nil {
|
||||
config.exitHandler.Error(fmt.Errorf("unable to get working directory\n%w", err))
|
||||
return
|
||||
}
|
||||
if logger.IsDebugEnabled() {
|
||||
logger.Debug(ApplicationPathFormatter(ctx.Application.Path))
|
||||
|
||||
if config.logger.IsDebugEnabled() {
|
||||
if err := config.contentWriter.Write("Application contents", ctx.ApplicationPath); err != nil {
|
||||
config.logger.Debugf("unable to write application contents\n%w", err)
|
||||
}
|
||||
}
|
||||
|
||||
if s, ok := os.LookupEnv("CNB_BUILDPACK_DIR"); ok {
|
||||
if s, ok := os.LookupEnv(EnvBuildpackDirectory); ok {
|
||||
ctx.Buildpack.Path = filepath.Clean(s)
|
||||
} else { // TODO: Remove branch once lifecycle has been updated to support this
|
||||
ctx.Buildpack.Path = filepath.Clean(strings.TrimSuffix(config.arguments[0], filepath.Join("bin", "build")))
|
||||
} else {
|
||||
config.exitHandler.Error(fmt.Errorf("unable to get CNB_BUILDPACK_DIR, not found"))
|
||||
return
|
||||
}
|
||||
if logger.IsDebugEnabled() {
|
||||
logger.Debug(BuildpackPathFormatter(ctx.Buildpack.Path))
|
||||
|
||||
if config.logger.IsDebugEnabled() {
|
||||
if err := config.contentWriter.Write("Buildpack contents", ctx.Buildpack.Path); err != nil {
|
||||
config.logger.Debugf("unable to write buildpack contents\n%w", err)
|
||||
}
|
||||
}
|
||||
|
||||
file = filepath.Join(ctx.Buildpack.Path, "buildpack.toml")
|
||||
|
@ -163,34 +157,64 @@ func Build(builder Builder, options ...Option) {
|
|||
config.exitHandler.Error(fmt.Errorf("unable to decode buildpack %s\n%w", file, err))
|
||||
return
|
||||
}
|
||||
logger.Debugf("Buildpack: %+v", ctx.Buildpack)
|
||||
config.logger.Debugf("Buildpack: %+v", ctx.Buildpack)
|
||||
|
||||
API := strings.TrimSpace(ctx.Buildpack.API)
|
||||
if API != "0.5" && API != "0.6" {
|
||||
config.exitHandler.Error(errors.New("this version of libcnb is only compatible with buildpack APIs 0.5 and 0.6"))
|
||||
API, err := semver.NewVersion(ctx.Buildpack.API)
|
||||
if err != nil {
|
||||
config.exitHandler.Error(errors.New("version cannot be parsed"))
|
||||
return
|
||||
}
|
||||
|
||||
ctx.Layers = Layers{config.arguments[1]}
|
||||
logger.Debugf("Layers: %+v", ctx.Layers)
|
||||
compatVersionCheck, _ := semver.NewConstraint(fmt.Sprintf(">= %s, <= %s", MinSupportedBPVersion, MaxSupportedBPVersion))
|
||||
if !compatVersionCheck.Check(API) {
|
||||
if MinSupportedBPVersion == MaxSupportedBPVersion {
|
||||
config.exitHandler.Error(fmt.Errorf("this version of libcnb is only compatible with buildpack API == %s", MinSupportedBPVersion))
|
||||
return
|
||||
}
|
||||
|
||||
ctx.Platform.Path = config.arguments[2]
|
||||
if logger.IsDebugEnabled() {
|
||||
logger.Debug(PlatformFormatter(ctx.Platform))
|
||||
config.exitHandler.Error(fmt.Errorf("this version of libcnb is only compatible with buildpack APIs >= %s, <= %s", MinSupportedBPVersion, MaxSupportedBPVersion))
|
||||
return
|
||||
}
|
||||
|
||||
if ctx.Platform.Bindings, err = NewBindingsForBuild(ctx.Platform.Path); err != nil {
|
||||
layersDir, ok := os.LookupEnv(EnvLayersDirectory)
|
||||
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(EnvPlatformDirectory)
|
||||
if !ok {
|
||||
config.exitHandler.Error(fmt.Errorf("expected CNB_PLATFORM_DIR to be set"))
|
||||
return
|
||||
}
|
||||
|
||||
buildpackPlanPath, ok := os.LookupEnv(EnvBuildPlanPath)
|
||||
if !ok {
|
||||
config.exitHandler.Error(fmt.Errorf("expected CNB_BP_PLAN_PATH to be set"))
|
||||
return
|
||||
}
|
||||
|
||||
config.logger.Debugf("Layers: %+v", ctx.Layers)
|
||||
|
||||
if config.logger.IsDebugEnabled() {
|
||||
if err := config.contentWriter.Write("Platform contents", ctx.Platform.Path); err != nil {
|
||||
config.logger.Debugf("unable to write platform contents\n%w", err)
|
||||
}
|
||||
}
|
||||
|
||||
if ctx.Platform.Bindings, err = NewBindings(ctx.Platform.Path); err != nil {
|
||||
config.exitHandler.Error(fmt.Errorf("unable to read platform bindings %s\n%w", ctx.Platform.Path, err))
|
||||
return
|
||||
}
|
||||
logger.Debugf("Platform Bindings: %+v", ctx.Platform.Bindings)
|
||||
config.logger.Debugf("Platform Bindings: %+v", ctx.Platform.Bindings)
|
||||
|
||||
file = filepath.Join(ctx.Platform.Path, "env")
|
||||
if ctx.Platform.Environment, err = internal.NewConfigMapFromPath(file); err != nil {
|
||||
config.exitHandler.Error(fmt.Errorf("unable to read platform environment %s\n%w", file, err))
|
||||
return
|
||||
}
|
||||
logger.Debugf("Platform Environment: %s", ctx.Platform.Environment)
|
||||
config.logger.Debugf("Platform Environment: %s", ctx.Platform.Environment)
|
||||
|
||||
var store Store
|
||||
file = filepath.Join(ctx.Layers.Path, "store.toml")
|
||||
|
@ -199,27 +223,39 @@ func Build(builder Builder, options ...Option) {
|
|||
return
|
||||
}
|
||||
ctx.PersistentMetadata = store.Metadata
|
||||
logger.Debugf("Persistent Metadata: %+v", ctx.PersistentMetadata)
|
||||
config.logger.Debugf("Persistent Metadata: %+v", ctx.PersistentMetadata)
|
||||
|
||||
file = config.arguments[3]
|
||||
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", file, err))
|
||||
if _, err = toml.DecodeFile(buildpackPlanPath, &ctx.Plan); err != nil && !os.IsNotExist(err) {
|
||||
config.exitHandler.Error(fmt.Errorf("unable to decode buildpack plan %s\n%w", buildpackPlanPath, err))
|
||||
return
|
||||
}
|
||||
logger.Debugf("Buildpack Plan: %+v", ctx.Plan)
|
||||
config.logger.Debugf("Buildpack Plan: %+v", ctx.Plan)
|
||||
|
||||
if ctx.StackID, ok = os.LookupEnv("CNB_STACK_ID"); !ok {
|
||||
config.exitHandler.Error(fmt.Errorf("CNB_STACK_ID not set"))
|
||||
return
|
||||
if ctx.StackID, ok = os.LookupEnv(EnvStackID); !ok {
|
||||
config.logger.Debug("CNB_STACK_ID not set")
|
||||
} else {
|
||||
config.logger.Debugf("Stack: %s", ctx.StackID)
|
||||
}
|
||||
logger.Debugf("Stack: %s", ctx.StackID)
|
||||
|
||||
result, err := builder.Build(ctx)
|
||||
if API.GreaterThan(semver.MustParse("0.9")) {
|
||||
ctx.TargetInfo = TargetInfo{}
|
||||
ctx.TargetInfo.OS, _ = os.LookupEnv(EnvTargetOS)
|
||||
ctx.TargetInfo.Arch, _ = os.LookupEnv(EnvTargetArch)
|
||||
ctx.TargetInfo.Variant, _ = os.LookupEnv(EnvTargetArchVariant)
|
||||
config.logger.Debugf("System: %+v", ctx.TargetInfo)
|
||||
|
||||
ctx.TargetDistro = TargetDistro{}
|
||||
ctx.TargetDistro.Name, _ = os.LookupEnv(EnvTargetDistroName)
|
||||
ctx.TargetDistro.Version, _ = os.LookupEnv(EnvTargetDistroVersion)
|
||||
config.logger.Debugf("Distro: %+v", ctx.TargetDistro)
|
||||
}
|
||||
|
||||
result, err := build(ctx)
|
||||
if err != nil {
|
||||
config.exitHandler.Error(err)
|
||||
return
|
||||
}
|
||||
logger.Debugf("Result: %+v", result)
|
||||
config.logger.Debugf("Result: %+v", result)
|
||||
|
||||
file = filepath.Join(ctx.Layers.Path, "*.toml")
|
||||
existing, err := filepath.Glob(file)
|
||||
|
@ -229,60 +265,31 @@ func Build(builder Builder, options ...Option) {
|
|||
}
|
||||
var contributed []string
|
||||
|
||||
for _, creator := range result.Layers {
|
||||
name := creator.Name()
|
||||
layer, err := ctx.Layers.Layer(name)
|
||||
if err != nil {
|
||||
config.exitHandler.Error(fmt.Errorf("unable to create layer %s\n%w", name, err))
|
||||
return
|
||||
}
|
||||
|
||||
layer, err = creator.Contribute(layer)
|
||||
if err != nil {
|
||||
config.exitHandler.Error(fmt.Errorf("unable to invoke layer creator\n%w", err))
|
||||
return
|
||||
}
|
||||
|
||||
for _, layer := range result.Layers {
|
||||
file = filepath.Join(layer.Path, "env.build")
|
||||
logger.Debugf("Writing layer env.build: %s <= %+v", file, layer.BuildEnvironment)
|
||||
config.logger.Debugf("Writing layer env.build: %s <= %+v", file, layer.BuildEnvironment)
|
||||
if err = config.environmentWriter.Write(file, layer.BuildEnvironment); err != nil {
|
||||
config.exitHandler.Error(fmt.Errorf("unable to write layer env.build %s\n%w", file, err))
|
||||
return
|
||||
}
|
||||
|
||||
file = filepath.Join(layer.Path, "env.launch")
|
||||
logger.Debugf("Writing layer env.launch: %s <= %+v", file, layer.LaunchEnvironment)
|
||||
config.logger.Debugf("Writing layer env.launch: %s <= %+v", file, layer.LaunchEnvironment)
|
||||
if err = config.environmentWriter.Write(file, layer.LaunchEnvironment); err != nil {
|
||||
config.exitHandler.Error(fmt.Errorf("unable to write layer env.launch %s\n%w", file, err))
|
||||
return
|
||||
}
|
||||
|
||||
file = filepath.Join(layer.Path, "env")
|
||||
logger.Debugf("Writing layer env: %s <= %+v", file, layer.SharedEnvironment)
|
||||
config.logger.Debugf("Writing layer env: %s <= %+v", file, layer.SharedEnvironment)
|
||||
if err = config.environmentWriter.Write(file, layer.SharedEnvironment); err != nil {
|
||||
config.exitHandler.Error(fmt.Errorf("unable to write layer env %s\n%w", file, err))
|
||||
return
|
||||
}
|
||||
|
||||
file = filepath.Join(layer.Path, "profile.d")
|
||||
logger.Debugf("Writing layer profile.d: %s <= %+v", file, layer.Profile)
|
||||
if err = config.environmentWriter.Write(file, layer.Profile); err != nil {
|
||||
config.exitHandler.Error(fmt.Errorf("unable to write layer profile.d %s\n%w", file, err))
|
||||
return
|
||||
}
|
||||
|
||||
file = filepath.Join(ctx.Layers.Path, fmt.Sprintf("%s.toml", layer.Name))
|
||||
logger.Debugf("Writing layer metadata: %s <= %+v", file, layer)
|
||||
var toWrite interface{} = layer
|
||||
if API == "0.5" {
|
||||
toWrite = internal.LayerAPI5{
|
||||
Build: layer.LayerTypes.Build,
|
||||
Cache: layer.LayerTypes.Cache,
|
||||
Launch: layer.LayerTypes.Launch,
|
||||
Metadata: layer.Metadata,
|
||||
}
|
||||
}
|
||||
if err = config.tomlWriter.Write(file, toWrite); err != nil {
|
||||
config.logger.Debugf("Writing layer metadata: %s <= %+v", file, layer)
|
||||
if err = config.tomlWriter.Write(file, layer); err != nil {
|
||||
config.exitHandler.Error(fmt.Errorf("unable to write layer metadata %s\n%w", file, err))
|
||||
return
|
||||
}
|
||||
|
@ -294,7 +301,7 @@ func Build(builder Builder, options ...Option) {
|
|||
continue
|
||||
}
|
||||
|
||||
logger.Debugf("Removing %s", e)
|
||||
config.logger.Debugf("Removing %s", e)
|
||||
|
||||
if err := os.RemoveAll(e); err != nil {
|
||||
config.exitHandler.Error(fmt.Errorf("unable to remove %s\n%w", e, err))
|
||||
|
@ -302,36 +309,20 @@ func Build(builder Builder, options ...Option) {
|
|||
}
|
||||
}
|
||||
|
||||
var launchBOM, buildBOM []BOMEntry
|
||||
if result.BOM != nil {
|
||||
for _, entry := range result.BOM.Entries {
|
||||
if entry.Launch {
|
||||
launchBOM = append(launchBOM, entry)
|
||||
}
|
||||
if entry.Build {
|
||||
buildBOM = append(buildBOM, entry)
|
||||
}
|
||||
}
|
||||
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
|
||||
}
|
||||
|
||||
launch := LaunchTOML{
|
||||
Labels: result.Labels,
|
||||
Processes: result.Processes,
|
||||
Slices: result.Slices,
|
||||
BOM: launchBOM,
|
||||
}
|
||||
|
||||
if !launch.isEmpty() {
|
||||
file = filepath.Join(ctx.Layers.Path, "launch.toml")
|
||||
logger.Debugf("Writing application metadata: %s <= %+v", file, launch)
|
||||
|
||||
if API == "0.5" {
|
||||
for _, process := range launch.Processes {
|
||||
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.")
|
||||
}
|
||||
}
|
||||
}
|
||||
config.logger.Debugf("Writing application metadata: %s <= %+v", file, launch)
|
||||
|
||||
if err = config.tomlWriter.Write(file, launch); err != nil {
|
||||
config.exitHandler.Error(fmt.Errorf("unable to write application metadata %s\n%w", file, err))
|
||||
|
@ -339,15 +330,15 @@ func Build(builder Builder, options ...Option) {
|
|||
}
|
||||
}
|
||||
|
||||
build := BuildTOML{
|
||||
buildTOML := BuildTOML{
|
||||
Unmet: result.Unmet,
|
||||
BOM: buildBOM,
|
||||
}
|
||||
|
||||
if !build.isEmpty() {
|
||||
if !buildTOML.isEmpty() {
|
||||
file = filepath.Join(ctx.Layers.Path, "build.toml")
|
||||
logger.Debugf("Writing build metadata: %s <= %+v", file, build)
|
||||
if err = config.tomlWriter.Write(file, build); err != nil {
|
||||
config.logger.Debugf("Writing build metadata: %s <= %+v", file, build)
|
||||
|
||||
if err = config.tomlWriter.Write(file, buildTOML); err != nil {
|
||||
config.exitHandler.Error(fmt.Errorf("unable to write build metadata %s\n%w", file, err))
|
||||
return
|
||||
}
|
||||
|
@ -358,7 +349,7 @@ func Build(builder Builder, options ...Option) {
|
|||
Metadata: result.PersistentMetadata,
|
||||
}
|
||||
file = filepath.Join(ctx.Layers.Path, "store.toml")
|
||||
logger.Debugf("Writing persistent metadata: %s <= %+v", file, store)
|
||||
config.logger.Debugf("Writing persistent metadata: %s <= %+v", file, store)
|
||||
if err = config.tomlWriter.Write(file, store); err != nil {
|
||||
config.exitHandler.Error(fmt.Errorf("unable to write persistent metadata %s\n%w", file, err))
|
||||
return
|
||||
|
@ -375,3 +366,27 @@ func contains(candidates []string, s string) bool {
|
|||
|
||||
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
|
||||
}
|
||||
|
|
776
build_test.go
776
build_test.go
|
@ -18,8 +18,8 @@ package libcnb_test
|
|||
|
||||
import (
|
||||
"bytes"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"testing"
|
||||
|
@ -29,24 +29,23 @@ import (
|
|||
"github.com/sclevine/spec"
|
||||
"github.com/stretchr/testify/mock"
|
||||
|
||||
"github.com/buildpacks/libcnb"
|
||||
"github.com/buildpacks/libcnb/internal"
|
||||
"github.com/buildpacks/libcnb/mocks"
|
||||
"github.com/buildpacks/libcnb/v2"
|
||||
"github.com/buildpacks/libcnb/v2/log"
|
||||
"github.com/buildpacks/libcnb/v2/mocks"
|
||||
)
|
||||
|
||||
func testBuild(t *testing.T, context spec.G, it spec.S) {
|
||||
var (
|
||||
Expect = NewWithT(t).Expect
|
||||
|
||||
buildFunc libcnb.BuildFunc
|
||||
applicationPath string
|
||||
builder *mocks.Builder
|
||||
buildpackPath string
|
||||
buildpackPlanPath string
|
||||
bpTOMLContents string
|
||||
commandPath string
|
||||
environmentWriter *mocks.EnvironmentWriter
|
||||
exitHandler *mocks.ExitHandler
|
||||
layerContributor *mocks.LayerContributor
|
||||
layersPath string
|
||||
platformPath string
|
||||
tomlWriter *mocks.TOMLWriter
|
||||
|
@ -56,16 +55,17 @@ func testBuild(t *testing.T, context spec.G, it spec.S) {
|
|||
)
|
||||
|
||||
it.Before(func() {
|
||||
var err error
|
||||
buildFunc = func(libcnb.BuildContext) (libcnb.BuildResult, error) {
|
||||
return libcnb.NewBuildResult(), nil
|
||||
}
|
||||
|
||||
applicationPath, err = ioutil.TempDir("", "build-application-path")
|
||||
var err error
|
||||
applicationPath, err = os.MkdirTemp("", "build-application-path")
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
applicationPath, err = filepath.EvalSymlinks(applicationPath)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
builder = &mocks.Builder{}
|
||||
|
||||
buildpackPath, err = ioutil.TempDir("", "build-buildpack-path")
|
||||
buildpackPath, err = os.MkdirTemp("", "build-buildpack-path")
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
Expect(os.Setenv("CNB_BUILDPACK_DIR", buildpackPath)).To(Succeed())
|
||||
|
||||
|
@ -96,7 +96,6 @@ optional = true
|
|||
|
||||
[[stacks]]
|
||||
id = "test-id"
|
||||
mixins = ["test-name"]
|
||||
|
||||
[metadata]
|
||||
test-key = "test-value"
|
||||
|
@ -105,17 +104,17 @@ test-key = "test-value"
|
|||
Expect(err).ToNot(HaveOccurred())
|
||||
|
||||
var b bytes.Buffer
|
||||
err = buildpackTOML.Execute(&b, map[string]string{"APIVersion": "0.6"})
|
||||
err = buildpackTOML.Execute(&b, map[string]string{"APIVersion": "0.8"})
|
||||
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(f.Close()).NotTo(HaveOccurred())
|
||||
buildpackPlanPath = f.Name()
|
||||
|
||||
Expect(ioutil.WriteFile(buildpackPlanPath,
|
||||
Expect(os.WriteFile(buildpackPlanPath,
|
||||
[]byte(`
|
||||
[[entries]]
|
||||
name = "test-name"
|
||||
|
@ -135,12 +134,10 @@ test-key = "test-value"
|
|||
exitHandler = &mocks.ExitHandler{}
|
||||
exitHandler.On("Error", mock.Anything)
|
||||
|
||||
layerContributor = &mocks.LayerContributor{}
|
||||
|
||||
layersPath, err = ioutil.TempDir("", "build-layers-path")
|
||||
layersPath, err = os.MkdirTemp("", "build-layers-path")
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
Expect(ioutil.WriteFile(filepath.Join(layersPath, "store.toml"),
|
||||
Expect(os.WriteFile(filepath.Join(layersPath, "store.toml"),
|
||||
[]byte(`
|
||||
[metadata]
|
||||
test-key = "test-value"
|
||||
|
@ -148,21 +145,30 @@ test-key = "test-value"
|
|||
0600),
|
||||
).To(Succeed())
|
||||
|
||||
platformPath, err = ioutil.TempDir("", "build-platform-path")
|
||||
platformPath, err = os.MkdirTemp("", "build-platform-path")
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
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())
|
||||
|
||||
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())
|
||||
|
||||
tomlWriter = &mocks.TOMLWriter{}
|
||||
tomlWriter.On("Write", mock.Anything, mock.Anything).Return(nil)
|
||||
|
||||
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())
|
||||
|
||||
Expect(os.Setenv("CNB_TARGET_OS", "linux")).To(Succeed())
|
||||
Expect(os.Setenv("CNB_TARGET_ARCH", "arm")).To(Succeed())
|
||||
Expect(os.Setenv("CNB_TARGET_ARCH_VARIANT", "v6")).To(Succeed())
|
||||
Expect(os.Setenv("CNB_TARGET_DISTRO_NAME", "ubuntu")).To(Succeed())
|
||||
Expect(os.Setenv("CNB_TARGET_DISTRO_VERSION", "24.04")).To(Succeed())
|
||||
|
||||
workingDir, err = os.Getwd()
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
@ -173,6 +179,15 @@ test-key = "test-value"
|
|||
Expect(os.Chdir(workingDir)).To(Succeed())
|
||||
Expect(os.Unsetenv("CNB_BUILDPACK_DIR")).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.Unsetenv("CNB_TARGET_OS"))
|
||||
Expect(os.Unsetenv("CNB_TARGET_ARCH"))
|
||||
Expect(os.Unsetenv("CNB_TARGET_ARCH_VARIANT"))
|
||||
Expect(os.Unsetenv("CNB_TARGET_DISTRO_NAME"))
|
||||
Expect(os.Unsetenv("CNB_TARGET_DISTRO_VERSION"))
|
||||
|
||||
Expect(os.RemoveAll(applicationPath)).To(Succeed())
|
||||
Expect(os.RemoveAll(buildpackPath)).To(Succeed())
|
||||
|
@ -181,11 +196,11 @@ test-key = "test-value"
|
|||
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() {
|
||||
Expect(ioutil.WriteFile(filepath.Join(buildpackPath, "buildpack.toml"),
|
||||
Expect(os.WriteFile(filepath.Join(buildpackPath, "buildpack.toml"),
|
||||
[]byte(`
|
||||
api = "0.4"
|
||||
api = "0.7"
|
||||
|
||||
[buildpack]
|
||||
id = "test-id"
|
||||
|
@ -197,150 +212,228 @@ version = "1.1.1"
|
|||
})
|
||||
|
||||
it("fails", func() {
|
||||
libcnb.Build(builder,
|
||||
libcnb.WithArguments([]string{commandPath, layersPath, platformPath, buildpackPlanPath}),
|
||||
libcnb.WithExitHandler(exitHandler),
|
||||
libcnb.Build(buildFunc,
|
||||
libcnb.NewConfig(
|
||||
libcnb.WithArguments([]string{commandPath, layersPath, platformPath, buildpackPlanPath}),
|
||||
libcnb.WithExitHandler(exitHandler),
|
||||
libcnb.WithLogger(log.NewDiscard())),
|
||||
)
|
||||
|
||||
Expect(exitHandler.Calls[0].Arguments.Get(0)).To(MatchError(
|
||||
"this version of libcnb is only compatible with buildpack APIs 0.5 and 0.6",
|
||||
))
|
||||
if libcnb.MinSupportedBPVersion == libcnb.MaxSupportedBPVersion {
|
||||
Expect(exitHandler.Calls[0].Arguments.Get(0)).To(MatchError(
|
||||
fmt.Sprintf("this version of libcnb is only compatible with buildpack API == %s", libcnb.MinSupportedBPVersion)))
|
||||
} else {
|
||||
Expect(exitHandler.Calls[0].Arguments.Get(0)).To(MatchError(
|
||||
fmt.Sprintf("this version of libcnb is only compatible with buildpack APIs >= %s, <= %s", libcnb.MinSupportedBPVersion, libcnb.MaxSupportedBPVersion),
|
||||
))
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
it("encounters the wrong number of arguments", func() {
|
||||
builder.On("Build", mock.Anything).Return(libcnb.NewBuildResult(), nil)
|
||||
context("errors if required env vars are not set", 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)
|
||||
})
|
||||
|
||||
libcnb.Build(builder,
|
||||
libcnb.WithArguments([]string{commandPath}),
|
||||
libcnb.WithExitHandler(exitHandler),
|
||||
)
|
||||
|
||||
Expect(exitHandler.Calls[0].Arguments.Get(0)).To(MatchError("expected 3 arguments and received 0"))
|
||||
it("fails", func() {
|
||||
libcnb.Build(buildFunc,
|
||||
libcnb.NewConfig(
|
||||
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("doesn't receive CNB_STACK_ID", func() {
|
||||
Expect(os.Unsetenv("CNB_STACK_ID")).To(Succeed())
|
||||
builder.On("Build", mock.Anything).Return(libcnb.NewBuildResult(), nil)
|
||||
context("has a build environment", func() {
|
||||
var ctx libcnb.BuildContext
|
||||
|
||||
libcnb.Build(builder,
|
||||
libcnb.WithArguments([]string{commandPath, layersPath, platformPath, buildpackPlanPath}),
|
||||
libcnb.WithExitHandler(exitHandler),
|
||||
)
|
||||
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())
|
||||
|
||||
Expect(exitHandler.Calls[0].Arguments.Get(0)).To(MatchError("CNB_STACK_ID not set"))
|
||||
})
|
||||
buildFunc = func(context libcnb.BuildContext) (libcnb.BuildResult, error) {
|
||||
ctx = context
|
||||
return libcnb.NewBuildResult(), nil
|
||||
}
|
||||
})
|
||||
|
||||
it("creates context", func() {
|
||||
builder.On("Build", mock.Anything).Return(libcnb.NewBuildResult(), nil)
|
||||
it("creates context", func() {
|
||||
libcnb.Build(buildFunc,
|
||||
libcnb.NewConfig(
|
||||
libcnb.WithArguments([]string{commandPath})),
|
||||
)
|
||||
|
||||
libcnb.Build(builder,
|
||||
libcnb.WithArguments([]string{commandPath, layersPath, platformPath, buildpackPlanPath}),
|
||||
)
|
||||
|
||||
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.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"},
|
||||
Expect(ctx.ApplicationPath).To(Equal(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,
|
||||
Stacks: []libcnb.BuildpackStack{
|
||||
{
|
||||
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,
|
||||
}))
|
||||
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",
|
||||
}))
|
||||
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"))
|
||||
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() {
|
||||
context("has a build environment specifying target metadata", func() {
|
||||
var ctx libcnb.BuildContext
|
||||
|
||||
it.Before(func() {
|
||||
Expect(os.WriteFile(filepath.Join(buildpackPath, "buildpack.toml"),
|
||||
[]byte(`
|
||||
api = "0.10"
|
||||
|
||||
[buildpack]
|
||||
id = "test-id"
|
||||
name = "test-name"
|
||||
version = "1.1.1"
|
||||
|
||||
[[targets]]
|
||||
os = "linux"
|
||||
arch = "amd64"
|
||||
|
||||
[[targets.distros]]
|
||||
name = "ubuntu"
|
||||
version = "18.04"
|
||||
|
||||
[[targets.distros]]
|
||||
name = "debian"
|
||||
|
||||
[[targets]]
|
||||
os = "linux"
|
||||
arch = "arm"
|
||||
variant = "v6"
|
||||
`), 0600),
|
||||
).To(Succeed())
|
||||
|
||||
buildFunc = func(context libcnb.BuildContext) (libcnb.BuildResult, error) {
|
||||
ctx = context
|
||||
return libcnb.NewBuildResult(), nil
|
||||
}
|
||||
})
|
||||
|
||||
it("provides target information", func() {
|
||||
libcnb.Build(buildFunc,
|
||||
libcnb.NewConfig(
|
||||
libcnb.WithArguments([]string{commandPath}),
|
||||
libcnb.WithLogger(log.New(os.Stdout)),
|
||||
),
|
||||
)
|
||||
|
||||
Expect(ctx.Buildpack.Targets).To(HaveLen(2))
|
||||
Expect(ctx.Buildpack.Targets[0].OS).To(Equal("linux"))
|
||||
Expect(ctx.Buildpack.Targets[0].Arch).To(Equal("amd64"))
|
||||
Expect(ctx.Buildpack.Targets[0].Distros).To(HaveLen(2))
|
||||
Expect(ctx.Buildpack.Targets[0].Distros[0].Name).To(Equal("ubuntu"))
|
||||
Expect(ctx.Buildpack.Targets[0].Distros[0].Version).To(Equal("18.04"))
|
||||
Expect(ctx.Buildpack.Targets[0].Distros[1].Name).To(Equal("debian"))
|
||||
|
||||
Expect(ctx.Buildpack.Targets[1].Variant).To(Equal("v6"))
|
||||
|
||||
Expect(ctx.TargetInfo.OS).To(Equal("linux"))
|
||||
Expect(ctx.TargetInfo.Arch).To(Equal("arm"))
|
||||
Expect(ctx.TargetInfo.Variant).To(Equal("v6"))
|
||||
Expect(ctx.TargetDistro.Name).To(Equal("ubuntu"))
|
||||
Expect(ctx.TargetDistro.Version).To(Equal("24.04"))
|
||||
})
|
||||
})
|
||||
|
||||
it("fails if CNB_BUILDPACK_DIR is not set", func() {
|
||||
Expect(os.Unsetenv("CNB_BUILDPACK_DIR")).To(Succeed())
|
||||
|
||||
builder.On("Build", mock.Anything).Return(libcnb.NewBuildResult(), nil)
|
||||
|
||||
libcnb.Build(builder,
|
||||
libcnb.WithArguments([]string{filepath.Join(buildpackPath, commandPath), layersPath, platformPath, buildpackPlanPath}),
|
||||
libcnb.Build(buildFunc,
|
||||
libcnb.NewConfig(
|
||||
libcnb.WithArguments([]string{filepath.Join(buildpackPath, commandPath), layersPath, platformPath, buildpackPlanPath}),
|
||||
libcnb.WithExitHandler(exitHandler),
|
||||
libcnb.WithLogger(log.NewDiscard())),
|
||||
)
|
||||
|
||||
ctx := builder.Calls[0].Arguments[0].(libcnb.BuildContext)
|
||||
|
||||
Expect(ctx.Buildpack.Path).To(Equal(buildpackPath))
|
||||
Expect(exitHandler.Calls[0].Arguments.Get(0)).To(MatchError("unable to get CNB_BUILDPACK_DIR, not found"))
|
||||
})
|
||||
|
||||
it("handles error from BuildFunc", func() {
|
||||
builder.On("Build", mock.Anything).Return(libcnb.NewBuildResult(), fmt.Errorf("test-error"))
|
||||
buildFunc = func(libcnb.BuildContext) (libcnb.BuildResult, error) {
|
||||
return libcnb.NewBuildResult(), errors.New("test-error")
|
||||
}
|
||||
|
||||
libcnb.Build(builder,
|
||||
libcnb.WithArguments([]string{commandPath, layersPath, platformPath, buildpackPlanPath}),
|
||||
libcnb.WithExitHandler(exitHandler),
|
||||
libcnb.Build(buildFunc,
|
||||
libcnb.NewConfig(
|
||||
libcnb.WithArguments([]string{commandPath, layersPath, platformPath, buildpackPlanPath}),
|
||||
libcnb.WithExitHandler(exitHandler),
|
||||
libcnb.WithLogger(log.NewDiscard())),
|
||||
)
|
||||
|
||||
Expect(exitHandler.Calls[0].Arguments.Get(0)).To(MatchError("test-error"))
|
||||
})
|
||||
|
||||
it("calls layer contributor", func() {
|
||||
layerContributor.On("Contribute", mock.Anything).Return(libcnb.Layer{}, nil)
|
||||
layerContributor.On("Name").Return("test-name")
|
||||
builder.On("Build", mock.Anything).
|
||||
Return(libcnb.BuildResult{Layers: []libcnb.LayerContributor{layerContributor}}, nil)
|
||||
|
||||
libcnb.Build(builder,
|
||||
libcnb.WithArguments([]string{commandPath, layersPath, platformPath, buildpackPlanPath}),
|
||||
libcnb.WithTOMLWriter(tomlWriter),
|
||||
)
|
||||
|
||||
Expect(layerContributor.Calls).To(HaveLen(2))
|
||||
})
|
||||
|
||||
it("writes env.build", func() {
|
||||
layer := libcnb.Layer{Path: filepath.Join(layersPath, "test-name"), BuildEnvironment: libcnb.Environment{}}
|
||||
layer.BuildEnvironment.Defaultf("test-build", "test-%s", "value")
|
||||
layerContributor.On("Contribute", mock.Anything).Return(layer, nil)
|
||||
layerContributor.On("Name").Return("test-name")
|
||||
result := libcnb.BuildResult{Layers: []libcnb.LayerContributor{layerContributor}}
|
||||
builder.On("Build", mock.Anything).Return(result, nil)
|
||||
buildFunc = func(libcnb.BuildContext) (libcnb.BuildResult, error) {
|
||||
layer := libcnb.Layer{Path: filepath.Join(layersPath, "test-name"), BuildEnvironment: libcnb.Environment{}}
|
||||
layer.BuildEnvironment.Defaultf("test-build", "test-%s", "value")
|
||||
return libcnb.BuildResult{Layers: []libcnb.Layer{layer}}, nil
|
||||
}
|
||||
|
||||
libcnb.Build(builder,
|
||||
libcnb.WithArguments([]string{commandPath, layersPath, platformPath, buildpackPlanPath}),
|
||||
libcnb.WithEnvironmentWriter(environmentWriter),
|
||||
libcnb.Build(buildFunc,
|
||||
libcnb.NewConfig(
|
||||
libcnb.WithArguments([]string{commandPath, layersPath, platformPath, buildpackPlanPath}),
|
||||
libcnb.WithEnvironmentWriter(environmentWriter),
|
||||
libcnb.WithLogger(log.NewDiscard())),
|
||||
)
|
||||
|
||||
Expect(environmentWriter.Calls[0].Arguments[0]).To(Equal(filepath.Join(layersPath, "test-name", "env.build")))
|
||||
|
@ -348,16 +441,17 @@ version = "1.1.1"
|
|||
})
|
||||
|
||||
it("writes env.launch", func() {
|
||||
layer := libcnb.Layer{Path: filepath.Join(layersPath, "test-name"), LaunchEnvironment: libcnb.Environment{}}
|
||||
layer.LaunchEnvironment.Defaultf("test-launch", "test-%s", "value")
|
||||
layerContributor.On("Contribute", mock.Anything).Return(layer, nil)
|
||||
layerContributor.On("Name").Return("test-name")
|
||||
result := libcnb.BuildResult{Layers: []libcnb.LayerContributor{layerContributor}}
|
||||
builder.On("Build", mock.Anything).Return(result, nil)
|
||||
buildFunc = func(libcnb.BuildContext) (libcnb.BuildResult, error) {
|
||||
layer := libcnb.Layer{Path: filepath.Join(layersPath, "test-name"), LaunchEnvironment: libcnb.Environment{}}
|
||||
layer.LaunchEnvironment.Defaultf("test-launch", "test-%s", "value")
|
||||
return libcnb.BuildResult{Layers: []libcnb.Layer{layer}}, nil
|
||||
}
|
||||
|
||||
libcnb.Build(builder,
|
||||
libcnb.WithArguments([]string{commandPath, layersPath, platformPath, buildpackPlanPath}),
|
||||
libcnb.WithEnvironmentWriter(environmentWriter),
|
||||
libcnb.Build(buildFunc,
|
||||
libcnb.NewConfig(
|
||||
libcnb.WithArguments([]string{commandPath, layersPath, platformPath, buildpackPlanPath}),
|
||||
libcnb.WithEnvironmentWriter(environmentWriter),
|
||||
libcnb.WithLogger(log.NewDiscard())),
|
||||
)
|
||||
|
||||
Expect(environmentWriter.Calls[1].Arguments[0]).To(Equal(filepath.Join(layersPath, "test-name", "env.launch")))
|
||||
|
@ -365,96 +459,43 @@ version = "1.1.1"
|
|||
})
|
||||
|
||||
it("writes env", func() {
|
||||
layer := libcnb.Layer{Path: filepath.Join(layersPath, "test-name"), SharedEnvironment: libcnb.Environment{}}
|
||||
layer.SharedEnvironment.Defaultf("test-shared", "test-%s", "value")
|
||||
layerContributor.On("Contribute", mock.Anything).Return(layer, nil)
|
||||
layerContributor.On("Name").Return("test-name")
|
||||
result := libcnb.BuildResult{Layers: []libcnb.LayerContributor{layerContributor}}
|
||||
builder.On("Build", mock.Anything).Return(result, nil)
|
||||
buildFunc = func(libcnb.BuildContext) (libcnb.BuildResult, error) {
|
||||
layer := libcnb.Layer{Path: filepath.Join(layersPath, "test-name"), SharedEnvironment: libcnb.Environment{}}
|
||||
layer.SharedEnvironment.Defaultf("test-shared", "test-%s", "value")
|
||||
return libcnb.BuildResult{Layers: []libcnb.Layer{layer}}, nil
|
||||
}
|
||||
|
||||
libcnb.Build(builder,
|
||||
|
||||
libcnb.WithArguments([]string{commandPath, layersPath, platformPath, buildpackPlanPath}),
|
||||
libcnb.WithEnvironmentWriter(environmentWriter),
|
||||
libcnb.Build(buildFunc,
|
||||
libcnb.NewConfig(
|
||||
libcnb.WithArguments([]string{commandPath, layersPath, platformPath, buildpackPlanPath}),
|
||||
libcnb.WithEnvironmentWriter(environmentWriter),
|
||||
libcnb.WithLogger(log.NewDiscard())),
|
||||
)
|
||||
|
||||
Expect(environmentWriter.Calls[2].Arguments[0]).To(Equal(filepath.Join(layersPath, "test-name", "env")))
|
||||
Expect(environmentWriter.Calls[2].Arguments[1]).To(Equal(map[string]string{"test-shared.default": "test-value"}))
|
||||
})
|
||||
|
||||
it("writes profile.d", func() {
|
||||
layer := libcnb.Layer{Path: filepath.Join(layersPath, "test-name"), Profile: libcnb.Profile{}}
|
||||
layer.Profile.Addf("test-profile", "test-%s", "value")
|
||||
layerContributor.On("Contribute", mock.Anything).Return(layer, nil)
|
||||
layerContributor.On("Name").Return("test-name")
|
||||
result := libcnb.BuildResult{Layers: []libcnb.LayerContributor{layerContributor}}
|
||||
builder.On("Build", mock.Anything).Return(result, nil)
|
||||
|
||||
libcnb.Build(builder,
|
||||
libcnb.WithArguments([]string{commandPath, layersPath, platformPath, buildpackPlanPath}),
|
||||
libcnb.WithEnvironmentWriter(environmentWriter),
|
||||
)
|
||||
|
||||
Expect(environmentWriter.Calls[3].Arguments[0]).To(Equal(filepath.Join(layersPath, "test-name", "profile.d")))
|
||||
Expect(environmentWriter.Calls[3].Arguments[1]).To(Equal(map[string]string{"test-profile": "test-value"}))
|
||||
})
|
||||
|
||||
it("writes 0.5 layer metadata", func() {
|
||||
var b bytes.Buffer
|
||||
err := buildpackTOML.Execute(&b, map[string]string{"APIVersion": "0.5"})
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
|
||||
Expect(ioutil.WriteFile(filepath.Join(buildpackPath, "buildpack.toml"), b.Bytes(), 0600)).To(Succeed())
|
||||
|
||||
layer := libcnb.Layer{
|
||||
Name: "test-name",
|
||||
Path: filepath.Join(layersPath, "test-name"),
|
||||
LayerTypes: libcnb.LayerTypes{
|
||||
Build: true,
|
||||
Cache: true,
|
||||
Launch: true,
|
||||
},
|
||||
Metadata: map[string]interface{}{"test-key": "test-value"},
|
||||
it("writes layer metadata", func() {
|
||||
buildFunc = func(libcnb.BuildContext) (libcnb.BuildResult, error) {
|
||||
layer := libcnb.Layer{
|
||||
Name: "test-name",
|
||||
Path: filepath.Join(layersPath, "test-name"),
|
||||
LayerTypes: libcnb.LayerTypes{
|
||||
Build: true,
|
||||
Cache: true,
|
||||
Launch: true,
|
||||
},
|
||||
Metadata: map[string]interface{}{"test-key": "test-value"},
|
||||
}
|
||||
return libcnb.BuildResult{Layers: []libcnb.Layer{layer}}, nil
|
||||
}
|
||||
layerContributor.On("Contribute", mock.Anything).Return(layer, nil)
|
||||
layerContributor.On("Name").Return("test-name")
|
||||
result := libcnb.BuildResult{Layers: []libcnb.LayerContributor{layerContributor}}
|
||||
builder.On("Build", mock.Anything).Return(result, nil)
|
||||
|
||||
libcnb.Build(builder,
|
||||
libcnb.WithArguments([]string{commandPath, layersPath, platformPath, buildpackPlanPath}),
|
||||
libcnb.WithTOMLWriter(tomlWriter),
|
||||
)
|
||||
|
||||
Expect(tomlWriter.Calls[0].Arguments[0]).To(Equal(filepath.Join(layersPath, "test-name.toml")))
|
||||
|
||||
layer5, ok := tomlWriter.Calls[0].Arguments[1].(internal.LayerAPI5)
|
||||
Expect(ok).To(BeTrue())
|
||||
Expect(layer5.Build).To(BeTrue())
|
||||
Expect(layer5.Cache).To(BeTrue())
|
||||
Expect(layer5.Launch).To(BeTrue())
|
||||
Expect(layer5.Metadata).To(Equal(map[string]interface{}{"test-key": "test-value"}))
|
||||
})
|
||||
|
||||
it("writes 0.6 layer metadata", func() {
|
||||
layer := libcnb.Layer{
|
||||
Name: "test-name",
|
||||
Path: filepath.Join(layersPath, "test-name"),
|
||||
LayerTypes: libcnb.LayerTypes{
|
||||
Build: true,
|
||||
Cache: true,
|
||||
Launch: true,
|
||||
},
|
||||
Metadata: map[string]interface{}{"test-key": "test-value"},
|
||||
}
|
||||
layerContributor.On("Contribute", mock.Anything).Return(layer, nil)
|
||||
layerContributor.On("Name").Return("test-name")
|
||||
result := libcnb.BuildResult{Layers: []libcnb.LayerContributor{layerContributor}}
|
||||
builder.On("Build", mock.Anything).Return(result, nil)
|
||||
|
||||
libcnb.Build(builder,
|
||||
libcnb.WithArguments([]string{commandPath, layersPath, platformPath, buildpackPlanPath}),
|
||||
libcnb.WithTOMLWriter(tomlWriter),
|
||||
libcnb.Build(buildFunc,
|
||||
libcnb.NewConfig(
|
||||
libcnb.WithArguments([]string{commandPath, layersPath, platformPath, buildpackPlanPath}),
|
||||
libcnb.WithTOMLWriter(tomlWriter),
|
||||
libcnb.WithLogger(log.NewDiscard())),
|
||||
)
|
||||
|
||||
Expect(tomlWriter.Calls[0].Arguments[0]).To(Equal(filepath.Join(layersPath, "test-name.toml")))
|
||||
|
@ -467,42 +508,75 @@ version = "1.1.1"
|
|||
Expect(layer.Metadata).To(Equal(map[string]interface{}{"test-key": "test-value"}))
|
||||
})
|
||||
|
||||
it("writes launch.toml", 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,
|
||||
it("writes launch.toml with working-directory setting", 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())
|
||||
|
||||
buildFunc = func(libcnb.BuildContext) (libcnb.BuildResult, error) {
|
||||
return libcnb.BuildResult{
|
||||
Layers: []libcnb.Layer{},
|
||||
Processes: []libcnb.Process{
|
||||
{
|
||||
Type: "test-type",
|
||||
Command: []string{"test-command-in-dir"},
|
||||
Default: true,
|
||||
WorkingDirectory: "/my/directory/",
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "test-build-bom-entry",
|
||||
Metadata: map[string]interface{}{"test-key": "test-value"},
|
||||
},
|
||||
}},
|
||||
Labels: []libcnb.Label{
|
||||
{
|
||||
Key: "test-key",
|
||||
Value: "test-value",
|
||||
},
|
||||
},
|
||||
}, nil
|
||||
}
|
||||
|
||||
libcnb.Build(buildFunc,
|
||||
libcnb.NewConfig(
|
||||
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,
|
||||
Type: "test-type",
|
||||
Command: []string{"test-command-in-dir"},
|
||||
Default: true,
|
||||
WorkingDirectory: "/my/directory/",
|
||||
},
|
||||
},
|
||||
Slices: []libcnb.Slice{
|
||||
{
|
||||
Paths: []string{"test-path"},
|
||||
},
|
||||
},
|
||||
}, nil)
|
||||
}))
|
||||
})
|
||||
|
||||
libcnb.Build(builder,
|
||||
libcnb.WithArguments([]string{commandPath, layersPath, platformPath, buildpackPlanPath}),
|
||||
libcnb.WithTOMLWriter(tomlWriter),
|
||||
it("writes launch.toml", func() {
|
||||
buildFunc = func(libcnb.BuildContext) (libcnb.BuildResult, error) {
|
||||
return libcnb.BuildResult{
|
||||
Labels: []libcnb.Label{
|
||||
{
|
||||
Key: "test-key",
|
||||
Value: "test-value",
|
||||
},
|
||||
},
|
||||
Processes: []libcnb.Process{
|
||||
{
|
||||
Type: "test-type",
|
||||
Command: []string{"test-command"},
|
||||
Default: true,
|
||||
},
|
||||
},
|
||||
Slices: []libcnb.Slice{
|
||||
{
|
||||
Paths: []string{"test-path"},
|
||||
},
|
||||
},
|
||||
}, nil
|
||||
}
|
||||
|
||||
libcnb.Build(buildFunc,
|
||||
libcnb.NewConfig(
|
||||
libcnb.WithArguments([]string{commandPath, layersPath, platformPath, buildpackPlanPath}),
|
||||
libcnb.WithTOMLWriter(tomlWriter),
|
||||
libcnb.WithLogger(log.NewDiscard())),
|
||||
)
|
||||
|
||||
Expect(tomlWriter.Calls[0].Arguments[0]).To(Equal(filepath.Join(layersPath, "launch.toml")))
|
||||
|
@ -516,7 +590,7 @@ version = "1.1.1"
|
|||
Processes: []libcnb.Process{
|
||||
{
|
||||
Type: "test-type",
|
||||
Command: "test-command",
|
||||
Command: []string{"test-command"},
|
||||
Default: true,
|
||||
},
|
||||
},
|
||||
|
@ -525,24 +599,21 @@ version = "1.1.1"
|
|||
Paths: []string{"test-path"},
|
||||
},
|
||||
},
|
||||
BOM: []libcnb.BOMEntry{
|
||||
{
|
||||
Name: "test-launch-bom-entry",
|
||||
Metadata: map[string]interface{}{"test-key": "test-value"},
|
||||
Launch: true,
|
||||
},
|
||||
},
|
||||
}))
|
||||
})
|
||||
|
||||
it("writes persistent metadata", func() {
|
||||
m := map[string]interface{}{"test-key": "test-value"}
|
||||
|
||||
builder.On("Build", mock.Anything).Return(libcnb.BuildResult{PersistentMetadata: m}, nil)
|
||||
buildFunc = func(libcnb.BuildContext) (libcnb.BuildResult, error) {
|
||||
return libcnb.BuildResult{PersistentMetadata: m}, nil
|
||||
}
|
||||
|
||||
libcnb.Build(builder,
|
||||
libcnb.WithArguments([]string{commandPath, layersPath, platformPath, buildpackPlanPath}),
|
||||
libcnb.WithTOMLWriter(tomlWriter),
|
||||
libcnb.Build(buildFunc,
|
||||
libcnb.NewConfig(
|
||||
libcnb.WithArguments([]string{commandPath, layersPath, platformPath, buildpackPlanPath}),
|
||||
libcnb.WithTOMLWriter(tomlWriter),
|
||||
libcnb.WithLogger(log.NewDiscard())),
|
||||
)
|
||||
|
||||
Expect(tomlWriter.Calls[0].Arguments[0]).To(Equal(filepath.Join(layersPath, "store.toml")))
|
||||
|
@ -550,31 +621,32 @@ version = "1.1.1"
|
|||
})
|
||||
|
||||
it("does not write empty files", func() {
|
||||
builder.On("Build", mock.Anything).Return(libcnb.NewBuildResult(), nil)
|
||||
|
||||
libcnb.Build(builder,
|
||||
libcnb.WithArguments([]string{commandPath, layersPath, platformPath, buildpackPlanPath}),
|
||||
libcnb.WithTOMLWriter(tomlWriter),
|
||||
libcnb.Build(buildFunc,
|
||||
libcnb.NewConfig(
|
||||
libcnb.WithArguments([]string{commandPath, layersPath, platformPath, buildpackPlanPath}),
|
||||
libcnb.WithTOMLWriter(tomlWriter),
|
||||
libcnb.WithLogger(log.NewDiscard())),
|
||||
)
|
||||
|
||||
Expect(tomlWriter.Calls).To(HaveLen(0))
|
||||
})
|
||||
|
||||
it("removes stale layers", func() {
|
||||
Expect(ioutil.WriteFile(filepath.Join(layersPath, "alpha.toml"), []byte(""), 0600)).To(Succeed())
|
||||
Expect(ioutil.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, "alpha.toml"), []byte(""), 0600)).To(Succeed())
|
||||
Expect(os.WriteFile(filepath.Join(layersPath, "bravo.toml"), []byte(""), 0600)).To(Succeed())
|
||||
Expect(os.WriteFile(filepath.Join(layersPath, "store.toml"), []byte(""), 0600)).To(Succeed())
|
||||
|
||||
layer := libcnb.Layer{Name: "alpha"}
|
||||
layerContributor.On("Contribute", mock.Anything).Return(layer, nil)
|
||||
layerContributor.On("Name").Return("alpha")
|
||||
|
||||
builder.On("Build", mock.Anything).
|
||||
Return(libcnb.BuildResult{Layers: []libcnb.LayerContributor{layerContributor}}, nil)
|
||||
buildFunc = func(libcnb.BuildContext) (libcnb.BuildResult, error) {
|
||||
return libcnb.BuildResult{Layers: []libcnb.Layer{layer}}, nil
|
||||
}
|
||||
|
||||
libcnb.Build(builder,
|
||||
libcnb.WithArguments([]string{commandPath, layersPath, platformPath, buildpackPlanPath}),
|
||||
libcnb.WithTOMLWriter(tomlWriter),
|
||||
libcnb.Build(buildFunc,
|
||||
libcnb.NewConfig(
|
||||
libcnb.WithArguments([]string{commandPath, layersPath, platformPath, buildpackPlanPath}),
|
||||
libcnb.WithTOMLWriter(tomlWriter),
|
||||
libcnb.WithLogger(log.NewDiscard())),
|
||||
)
|
||||
|
||||
Expect(tomlWriter.Calls).To(HaveLen(1))
|
||||
|
@ -584,40 +656,25 @@ version = "1.1.1"
|
|||
})
|
||||
|
||||
it("writes build.toml", 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,
|
||||
buildFunc = func(libcnb.BuildContext) (libcnb.BuildResult, error) {
|
||||
return libcnb.BuildResult{
|
||||
Unmet: []libcnb.UnmetPlanEntry{
|
||||
{
|
||||
Name: "test-entry",
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "test-launch-bom-entry",
|
||||
Metadata: map[string]interface{}{"test-key": "test-value"},
|
||||
Build: false,
|
||||
},
|
||||
}},
|
||||
Unmet: []libcnb.UnmetPlanEntry{
|
||||
{
|
||||
Name: "test-entry",
|
||||
},
|
||||
},
|
||||
}, nil)
|
||||
}, nil
|
||||
}
|
||||
|
||||
libcnb.Build(builder,
|
||||
libcnb.WithArguments([]string{commandPath, layersPath, platformPath, buildpackPlanPath}),
|
||||
libcnb.WithTOMLWriter(tomlWriter),
|
||||
libcnb.Build(buildFunc,
|
||||
libcnb.NewConfig(
|
||||
libcnb.WithArguments([]string{commandPath, layersPath, platformPath, buildpackPlanPath}),
|
||||
libcnb.WithTOMLWriter(tomlWriter),
|
||||
libcnb.WithLogger(log.NewDiscard())),
|
||||
)
|
||||
|
||||
Expect(tomlWriter.Calls[0].Arguments[0]).To(Equal(filepath.Join(layersPath, "build.toml")))
|
||||
Expect(tomlWriter.Calls[0].Arguments[1]).To(Equal(libcnb.BuildTOML{
|
||||
BOM: []libcnb.BOMEntry{
|
||||
{
|
||||
Name: "test-build-bom-entry",
|
||||
Metadata: map[string]interface{}{"test-key": "test-value"},
|
||||
Build: true,
|
||||
},
|
||||
},
|
||||
Unmet: []libcnb.UnmetPlanEntry{
|
||||
{
|
||||
Name: "test-entry",
|
||||
|
@ -625,4 +682,101 @@ version = "1.1.1"
|
|||
},
|
||||
}))
|
||||
})
|
||||
|
||||
context("Validates SBOM entries", 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"
|
||||
sbom-formats = ["application/vnd.cyclonedx+json"]
|
||||
`),
|
||||
0600),
|
||||
).To(Succeed())
|
||||
|
||||
buildFunc = func(libcnb.BuildContext) (libcnb.BuildResult, error) {
|
||||
return libcnb.BuildResult{}, nil
|
||||
}
|
||||
})
|
||||
|
||||
it("has SBOM files", func() {
|
||||
libcnb.Build(buildFunc,
|
||||
libcnb.NewConfig(
|
||||
libcnb.WithArguments([]string{commandPath, layersPath, platformPath, buildpackPlanPath}),
|
||||
libcnb.WithExitHandler(exitHandler),
|
||||
libcnb.WithLogger(log.NewDiscard())),
|
||||
)
|
||||
|
||||
Expect(exitHandler.Calls).To(BeEmpty())
|
||||
})
|
||||
|
||||
it("has no accepted formats", func() {
|
||||
Expect(os.WriteFile(filepath.Join(buildpackPath, "buildpack.toml"),
|
||||
[]byte(`
|
||||
api = "0.8"
|
||||
|
||||
[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(buildFunc,
|
||||
libcnb.NewConfig(
|
||||
libcnb.WithArguments([]string{commandPath, layersPath, platformPath, buildpackPlanPath}),
|
||||
libcnb.WithExitHandler(exitHandler),
|
||||
libcnb.WithLogger(log.NewDiscard())),
|
||||
)
|
||||
|
||||
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("has no matching formats", func() {
|
||||
Expect(os.WriteFile(filepath.Join(layersPath, "launch.sbom.spdx.json"), []byte{}, 0600)).To(Succeed())
|
||||
|
||||
libcnb.Build(buildFunc,
|
||||
libcnb.NewConfig(
|
||||
libcnb.WithArguments([]string{commandPath, layersPath, platformPath, buildpackPlanPath}),
|
||||
libcnb.WithExitHandler(exitHandler),
|
||||
libcnb.WithLogger(log.NewDiscard())),
|
||||
)
|
||||
|
||||
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(buildFunc,
|
||||
libcnb.NewConfig(
|
||||
libcnb.WithArguments([]string{commandPath, layersPath, platformPath, buildpackPlanPath}),
|
||||
libcnb.WithExitHandler(exitHandler),
|
||||
libcnb.WithLogger(log.NewDiscard())),
|
||||
)
|
||||
|
||||
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(buildFunc,
|
||||
libcnb.NewConfig(
|
||||
libcnb.WithArguments([]string{commandPath, layersPath, platformPath, buildpackPlanPath}),
|
||||
libcnb.WithExitHandler(exitHandler),
|
||||
libcnb.WithLogger(log.NewDiscard())),
|
||||
)
|
||||
|
||||
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"))
|
||||
})
|
||||
})
|
||||
}
|
||||
|
|
|
@ -0,0 +1,27 @@
|
|||
/*
|
||||
* 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
|
||||
|
||||
// BuildTOML represents the contents of build.toml.
|
||||
type BuildTOML struct {
|
||||
// Unmet is a collection of buildpack plan entries that should be passed through to subsequent providers.
|
||||
Unmet []UnmetPlanEntry
|
||||
}
|
||||
|
||||
func (b BuildTOML) isEmpty() bool {
|
||||
return len(b.Unmet) == 0
|
||||
}
|
42
buildpack.go
42
buildpack.go
|
@ -41,6 +41,9 @@ type BuildpackInfo struct {
|
|||
|
||||
// Licenses a list of buildpack 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
|
||||
|
@ -73,13 +76,39 @@ type BuildpackOrder struct {
|
|||
Groups []BuildpackOrderBuildpack `toml:"group"`
|
||||
}
|
||||
|
||||
// BuildpackStack is a stack supported by the buildpack.
|
||||
// Deprecated: BuildpackStack is a stack supported by the buildpack.
|
||||
type BuildpackStack struct {
|
||||
// ID is the id of the stack.
|
||||
ID string `toml:"id"`
|
||||
}
|
||||
|
||||
// Mixins is the collection of mixins associated with the stack.
|
||||
Mixins []string `toml:"mixins"`
|
||||
// TargetDistro is the supported target distro
|
||||
type TargetDistro struct {
|
||||
// Name is the name of the supported distro.
|
||||
Name string `toml:"name"`
|
||||
|
||||
// Version is the version of the supported distro.
|
||||
Version string `toml:"version"`
|
||||
}
|
||||
|
||||
// TargetInfo is the supported target
|
||||
type TargetInfo struct {
|
||||
// OS is the supported os.
|
||||
OS string `toml:"os"`
|
||||
|
||||
// Arch is the supported architecture.
|
||||
Arch string `toml:"arch"`
|
||||
|
||||
// Variant is the supported variant of the architecture.
|
||||
Variant string `toml:"variant"`
|
||||
}
|
||||
|
||||
// Target is a target supported by the buildpack.
|
||||
type Target struct {
|
||||
TargetInfo
|
||||
|
||||
// Distros is the collection of distros associated with the target.
|
||||
Distros []TargetDistro `toml:"distros"`
|
||||
}
|
||||
|
||||
// Buildpack is the contents of the buildpack.toml file.
|
||||
|
@ -91,11 +120,14 @@ type Buildpack struct {
|
|||
Info BuildpackInfo `toml:"buildpack"`
|
||||
|
||||
// Path is the path to the buildpack.
|
||||
Path string
|
||||
Path string `toml:"-"`
|
||||
|
||||
// Stacks is the collection of stacks supported by the buildpack.
|
||||
// Deprecated: Stacks is the collection of stacks supported by the buildpack.
|
||||
Stacks []BuildpackStack `toml:"stacks"`
|
||||
|
||||
// Targets is the collection of targets supported by the buildpack.
|
||||
Targets []Target `toml:"targets"`
|
||||
|
||||
// Metadata is arbitrary metadata attached to the buildpack.
|
||||
Metadata map[string]interface{} `toml:"metadata"`
|
||||
}
|
||||
|
|
|
@ -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/v2"
|
||||
|
||||
. "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.8",
|
||||
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 = ")))
|
||||
})
|
||||
}
|
83
config.go
83
config.go
|
@ -16,7 +16,14 @@
|
|||
|
||||
package libcnb
|
||||
|
||||
//go:generate mockery -name EnvironmentWriter -case=underscore
|
||||
import (
|
||||
"os"
|
||||
|
||||
"github.com/buildpacks/libcnb/v2/internal"
|
||||
"github.com/buildpacks/libcnb/v2/log"
|
||||
)
|
||||
|
||||
//go:generate mockery --name EnvironmentWriter --case=underscore
|
||||
|
||||
// EnvironmentWriter is the interface implemented by a type that wants to serialize a map of environment variables to
|
||||
// the file system.
|
||||
|
@ -27,7 +34,7 @@ type EnvironmentWriter interface {
|
|||
Write(dir string, environment map[string]string) error
|
||||
}
|
||||
|
||||
//go:generate mockery -name ExitHandler -case=underscore
|
||||
//go:generate mockery --name ExitHandler --case=underscore
|
||||
|
||||
// ExitHandler is the interface implemented by a type that wants to handle exit behavior when a buildpack encounters an
|
||||
// error.
|
||||
|
@ -43,7 +50,7 @@ type ExitHandler interface {
|
|||
Pass()
|
||||
}
|
||||
|
||||
//go:generate mockery -name TOMLWriter -case=underscore
|
||||
//go:generate mockery --name TOMLWriter --case=underscore
|
||||
|
||||
// TOMLWriter is the interface implemented by a type that wants to serialize an object to a TOML file.
|
||||
type TOMLWriter interface {
|
||||
|
@ -52,17 +59,55 @@ type TOMLWriter interface {
|
|||
Write(path string, value interface{}) error
|
||||
}
|
||||
|
||||
//go:generate mockery --name ExecDWriter --case=underscore
|
||||
|
||||
// ExecDWriter is the interface implemented by a type that wants to write exec.d output to file descriptor 3.
|
||||
type ExecDWriter interface {
|
||||
|
||||
// Write is called with the map of environment value key value
|
||||
// pairs that will be written out
|
||||
Write(value map[string]string) error
|
||||
}
|
||||
|
||||
// Config is an object that contains configurable properties for execution.
|
||||
type Config struct {
|
||||
arguments []string
|
||||
environmentWriter EnvironmentWriter
|
||||
exitHandler ExitHandler
|
||||
tomlWriter TOMLWriter
|
||||
arguments []string
|
||||
dirContentFormatter log.DirectoryContentFormatter
|
||||
environmentWriter EnvironmentWriter
|
||||
execdWriter ExecDWriter
|
||||
exitHandler ExitHandler
|
||||
logger log.Logger
|
||||
tomlWriter TOMLWriter
|
||||
contentWriter internal.DirectoryContentsWriter
|
||||
extension bool
|
||||
}
|
||||
|
||||
// Option is a function for configuring a Config instance.
|
||||
type Option func(config Config) Config
|
||||
|
||||
// NewConfig will generate a config from the given set of options
|
||||
func NewConfig(options ...Option) Config {
|
||||
config := Config{}
|
||||
|
||||
// apply defaults
|
||||
options = append([]Option{
|
||||
WithArguments(os.Args),
|
||||
WithEnvironmentWriter(internal.EnvironmentWriter{}),
|
||||
WithExitHandler(internal.NewExitHandler()),
|
||||
WithLogger(log.New(os.Stdout)),
|
||||
WithTOMLWriter(internal.TOMLWriter{}),
|
||||
WithDirectoryContentFormatter(internal.NewPlainDirectoryContentFormatter()),
|
||||
}, options...)
|
||||
|
||||
for _, opt := range options {
|
||||
config = opt(config)
|
||||
}
|
||||
|
||||
config.contentWriter = internal.NewDirectoryContentsWriter(config.dirContentFormatter, config.logger.DebugWriter())
|
||||
|
||||
return config
|
||||
}
|
||||
|
||||
// WithArguments creates an Option that sets a collection of arguments.
|
||||
func WithArguments(arguments []string) Option {
|
||||
return func(config Config) Config {
|
||||
|
@ -94,3 +139,27 @@ func WithTOMLWriter(tomlWriter TOMLWriter) Option {
|
|||
return config
|
||||
}
|
||||
}
|
||||
|
||||
// WithExecDWriter creates an Option that sets a ExecDWriter implementation.
|
||||
func WithExecDWriter(execdWriter ExecDWriter) Option {
|
||||
return func(config Config) Config {
|
||||
config.execdWriter = execdWriter
|
||||
return config
|
||||
}
|
||||
}
|
||||
|
||||
// WithLogger creates an Option that sets a ExecDWriter implementation.
|
||||
func WithLogger(logger log.Logger) Option {
|
||||
return func(config Config) Config {
|
||||
config.logger = logger
|
||||
return config
|
||||
}
|
||||
}
|
||||
|
||||
// WithDirectoryContentFormatter creates an Option that sets a ExecDWriter implementation.
|
||||
func WithDirectoryContentFormatter(formatter log.DirectoryContentFormatter) Option {
|
||||
return func(config Config) Config {
|
||||
config.dirContentFormatter = formatter
|
||||
return config
|
||||
}
|
||||
}
|
||||
|
|
181
detect.go
181
detect.go
|
@ -21,23 +21,30 @@ import (
|
|||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"github.com/BurntSushi/toml"
|
||||
"github.com/Masterminds/semver"
|
||||
|
||||
"github.com/buildpacks/libcnb/internal"
|
||||
"github.com/buildpacks/libcnb/poet"
|
||||
"github.com/buildpacks/libcnb/v2/internal"
|
||||
"github.com/buildpacks/libcnb/v2/log"
|
||||
)
|
||||
|
||||
// DetectContext contains the inputs to detection.
|
||||
type DetectContext struct {
|
||||
|
||||
// Application is the application to build.
|
||||
Application Application
|
||||
// ApplicationPath is the location of the application source code as provided by
|
||||
// the lifecycle.
|
||||
ApplicationPath string
|
||||
|
||||
// Buildpack is metadata about the buildpack, from buildpack.toml.
|
||||
// Buildpack is metadata about the buildpack from buildpack.toml (empty when processing an extension)
|
||||
Buildpack Buildpack
|
||||
|
||||
// Extension is metadata about the extension from extension.toml (empty when processing a buildpack)
|
||||
Extension Extension
|
||||
|
||||
// Logger is the way to write messages to the end user
|
||||
Logger log.Logger
|
||||
|
||||
// Platform is the contents of the platform.
|
||||
Platform Platform
|
||||
|
||||
|
@ -55,103 +62,140 @@ type DetectResult struct {
|
|||
Plans []BuildPlan
|
||||
}
|
||||
|
||||
//go:generate mockery -name Detector -case=underscore
|
||||
|
||||
// Detector describes an interface for types that can be used by the Detect function.
|
||||
type Detector interface {
|
||||
|
||||
// Detect takes a context and returns a result, performing buildpack detect behaviors.
|
||||
Detect(context DetectContext) (DetectResult, error)
|
||||
}
|
||||
// DetectFunc takes a context and returns a result, performing buildpack detect behaviors.
|
||||
type DetectFunc func(context DetectContext) (DetectResult, error)
|
||||
|
||||
// Detect is called by the main function of a buildpack, for detection.
|
||||
func Detect(detector Detector, options ...Option) {
|
||||
config := Config{
|
||||
arguments: os.Args,
|
||||
environmentWriter: internal.EnvironmentWriter{},
|
||||
exitHandler: internal.NewExitHandler(),
|
||||
tomlWriter: internal.TOMLWriter{},
|
||||
}
|
||||
|
||||
for _, option := range options {
|
||||
config = option(config)
|
||||
}
|
||||
|
||||
if len(config.arguments) != 3 {
|
||||
config.exitHandler.Error(fmt.Errorf("expected 2 arguments and received %d", len(config.arguments)-1))
|
||||
return
|
||||
}
|
||||
|
||||
func Detect(detect DetectFunc, config Config) {
|
||||
var (
|
||||
err error
|
||||
file string
|
||||
ok bool
|
||||
err error
|
||||
file string
|
||||
ok bool
|
||||
api string
|
||||
path string
|
||||
destination interface{}
|
||||
)
|
||||
ctx := DetectContext{}
|
||||
logger := poet.NewLogger(os.Stdout)
|
||||
ctx := DetectContext{Logger: config.logger}
|
||||
|
||||
ctx.Application.Path, err = os.Getwd()
|
||||
var moduletype = "buildpack"
|
||||
if config.extension {
|
||||
moduletype = "extension"
|
||||
}
|
||||
|
||||
ctx.ApplicationPath, err = os.Getwd()
|
||||
if err != nil {
|
||||
config.exitHandler.Error(fmt.Errorf("unable to get working directory\n%w", err))
|
||||
return
|
||||
}
|
||||
if logger.IsDebugEnabled() {
|
||||
logger.Debug(ApplicationPathFormatter(ctx.Application.Path))
|
||||
|
||||
if config.logger.IsDebugEnabled() {
|
||||
if err := config.contentWriter.Write("Application contents", ctx.ApplicationPath); err != nil {
|
||||
config.logger.Debugf("unable to write application contents\n%w", err)
|
||||
}
|
||||
}
|
||||
|
||||
if s, ok := os.LookupEnv("CNB_BUILDPACK_DIR"); ok {
|
||||
ctx.Buildpack.Path = filepath.Clean(s)
|
||||
} else { // TODO: Remove branch once lifecycle has been updated to support this
|
||||
ctx.Buildpack.Path = filepath.Clean(strings.TrimSuffix(config.arguments[0], filepath.Join("bin", "detect")))
|
||||
}
|
||||
if logger.IsDebugEnabled() {
|
||||
logger.Debug(BuildpackPathFormatter(ctx.Buildpack.Path))
|
||||
if !config.extension {
|
||||
if s, ok := os.LookupEnv(EnvBuildpackDirectory); ok {
|
||||
path = filepath.Clean(s)
|
||||
} else {
|
||||
config.exitHandler.Error(fmt.Errorf("unable to get CNB_BUILDPACK_DIR, not found"))
|
||||
return
|
||||
}
|
||||
ctx.Buildpack.Path = path
|
||||
destination = &ctx.Buildpack
|
||||
file = filepath.Join(ctx.Buildpack.Path, "buildpack.toml")
|
||||
} else {
|
||||
if s, ok := os.LookupEnv(EnvExtensionDirectory); ok {
|
||||
path = filepath.Clean(s)
|
||||
} else {
|
||||
config.exitHandler.Error(fmt.Errorf("unable to get CNB_EXTENSION_DIR, not found"))
|
||||
return
|
||||
}
|
||||
ctx.Extension.Path = path
|
||||
destination = &ctx.Extension
|
||||
file = filepath.Join(ctx.Extension.Path, "extension.toml")
|
||||
}
|
||||
|
||||
file = filepath.Join(ctx.Buildpack.Path, "buildpack.toml")
|
||||
if _, err = toml.DecodeFile(file, &ctx.Buildpack); err != nil && !os.IsNotExist(err) {
|
||||
config.exitHandler.Error(fmt.Errorf("unable to decode buildpack %s\n%w", file, err))
|
||||
if _, err = toml.DecodeFile(file, destination); err != nil && !os.IsNotExist(err) {
|
||||
config.exitHandler.Error(fmt.Errorf("unable to decode %s %s\n%w", moduletype, file, err))
|
||||
return
|
||||
}
|
||||
logger.Debugf("Buildpack: %+v", ctx.Buildpack)
|
||||
config.logger.Debugf("%s: %+v", moduletype, ctx.Buildpack)
|
||||
|
||||
API := strings.TrimSpace(ctx.Buildpack.API)
|
||||
if API != "0.5" && API != "0.6" {
|
||||
config.exitHandler.Error(errors.New("this version of libcnb is only compatible with buildpack API 0.5 and 0.6"))
|
||||
if config.logger.IsDebugEnabled() {
|
||||
if err := config.contentWriter.Write(moduletype+" contents", path); err != nil {
|
||||
config.logger.Debugf("unable to write %s contents\n%w", moduletype, err)
|
||||
}
|
||||
}
|
||||
|
||||
if config.extension {
|
||||
api = ctx.Extension.API
|
||||
} else {
|
||||
api = ctx.Buildpack.API
|
||||
}
|
||||
API, err := semver.NewVersion(api)
|
||||
if err != nil {
|
||||
config.exitHandler.Error(errors.New("version cannot be parsed"))
|
||||
return
|
||||
}
|
||||
|
||||
ctx.Platform.Path = config.arguments[1]
|
||||
if logger.IsDebugEnabled() {
|
||||
logger.Debug(PlatformFormatter(ctx.Platform))
|
||||
compatVersionCheck, _ := semver.NewConstraint(fmt.Sprintf(">= %s, <= %s", MinSupportedBPVersion, MaxSupportedBPVersion))
|
||||
if !compatVersionCheck.Check(API) {
|
||||
if MinSupportedBPVersion == MaxSupportedBPVersion {
|
||||
config.exitHandler.Error(fmt.Errorf("this version of libcnb is only compatible with buildpack API == %s", MinSupportedBPVersion))
|
||||
return
|
||||
}
|
||||
|
||||
config.exitHandler.Error(fmt.Errorf("this version of libcnb is only compatible with buildpack APIs >= %s, <= %s", MinSupportedBPVersion, MaxSupportedBPVersion))
|
||||
return
|
||||
}
|
||||
|
||||
var buildPlanPath string
|
||||
|
||||
ctx.Platform.Path, ok = os.LookupEnv(EnvPlatformDirectory)
|
||||
if !ok {
|
||||
config.exitHandler.Error(fmt.Errorf("expected CNB_PLATFORM_DIR to be set"))
|
||||
return
|
||||
}
|
||||
|
||||
buildPlanPath, ok = os.LookupEnv(EnvDetectPlanPath)
|
||||
if !ok {
|
||||
config.exitHandler.Error(fmt.Errorf("expected CNB_BUILD_PLAN_PATH to be set"))
|
||||
return
|
||||
}
|
||||
|
||||
if config.logger.IsDebugEnabled() {
|
||||
if err := config.contentWriter.Write("Platform contents", ctx.Platform.Path); err != nil {
|
||||
config.logger.Debugf("unable to write platform contents\n%w", err)
|
||||
}
|
||||
}
|
||||
|
||||
file = filepath.Join(ctx.Platform.Path, "bindings")
|
||||
if ctx.Platform.Bindings, err = NewBindingsFromPath(file); err != nil {
|
||||
if ctx.Platform.Bindings, err = NewBindings(ctx.Platform.Path); err != nil {
|
||||
config.exitHandler.Error(fmt.Errorf("unable to read platform bindings %s\n%w", file, err))
|
||||
return
|
||||
}
|
||||
logger.Debugf("Platform Bindings: %+v", ctx.Platform.Bindings)
|
||||
config.logger.Debugf("Platform Bindings: %+v", ctx.Platform.Bindings)
|
||||
|
||||
file = filepath.Join(ctx.Platform.Path, "env")
|
||||
if ctx.Platform.Environment, err = internal.NewConfigMapFromPath(file); err != nil {
|
||||
config.exitHandler.Error(fmt.Errorf("unable to read platform environment %s\n%w", file, err))
|
||||
return
|
||||
}
|
||||
logger.Debugf("Platform Environment: %s", ctx.Platform.Environment)
|
||||
config.logger.Debugf("Platform Environment: %s", ctx.Platform.Environment)
|
||||
|
||||
if ctx.StackID, ok = os.LookupEnv("CNB_STACK_ID"); !ok {
|
||||
config.exitHandler.Error(fmt.Errorf("CNB_STACK_ID not set"))
|
||||
return
|
||||
if ctx.StackID, ok = os.LookupEnv(EnvStackID); !ok {
|
||||
config.logger.Debug("CNB_STACK_ID not set")
|
||||
} else {
|
||||
config.logger.Debugf("Stack: %s", ctx.StackID)
|
||||
}
|
||||
logger.Debugf("Stack: %s", ctx.StackID)
|
||||
|
||||
result, err := detector.Detect(ctx)
|
||||
result, err := detect(ctx)
|
||||
if err != nil {
|
||||
config.exitHandler.Error(err)
|
||||
return
|
||||
}
|
||||
logger.Debugf("Result: %+v", result)
|
||||
config.logger.Debugf("Result: %+v", result)
|
||||
|
||||
if !result.Pass {
|
||||
config.exitHandler.Fail()
|
||||
|
@ -167,10 +211,9 @@ func Detect(detector Detector, options ...Option) {
|
|||
plans.Or = result.Plans[1:]
|
||||
}
|
||||
|
||||
file = config.arguments[2]
|
||||
logger.Debugf("Writing build plans: %s <= %+v", file, plans)
|
||||
if err := config.tomlWriter.Write(file, plans); err != nil {
|
||||
config.exitHandler.Error(fmt.Errorf("unable to write buildplan %s\n%w", file, err))
|
||||
config.logger.Debugf("Writing build plans: %s <= %+v", buildPlanPath, plans)
|
||||
if err := config.tomlWriter.Write(buildPlanPath, plans); err != nil {
|
||||
config.exitHandler.Error(fmt.Errorf("unable to write buildplan %s\n%w", buildPlanPath, err))
|
||||
return
|
||||
}
|
||||
}
|
||||
|
|
334
detect_test.go
334
detect_test.go
|
@ -18,7 +18,6 @@ package libcnb_test
|
|||
|
||||
import (
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"testing"
|
||||
|
@ -27,8 +26,9 @@ import (
|
|||
"github.com/sclevine/spec"
|
||||
"github.com/stretchr/testify/mock"
|
||||
|
||||
"github.com/buildpacks/libcnb"
|
||||
"github.com/buildpacks/libcnb/mocks"
|
||||
"github.com/buildpacks/libcnb/v2"
|
||||
"github.com/buildpacks/libcnb/v2/log"
|
||||
"github.com/buildpacks/libcnb/v2/mocks"
|
||||
)
|
||||
|
||||
func testDetect(t *testing.T, context spec.G, it spec.S) {
|
||||
|
@ -39,7 +39,7 @@ func testDetect(t *testing.T, context spec.G, it spec.S) {
|
|||
buildpackPath string
|
||||
buildPlanPath string
|
||||
commandPath string
|
||||
detector *mocks.Detector
|
||||
detectFunc libcnb.DetectFunc
|
||||
exitHandler *mocks.ExitHandler
|
||||
platformPath string
|
||||
tomlWriter *mocks.TOMLWriter
|
||||
|
@ -50,18 +50,18 @@ func testDetect(t *testing.T, context spec.G, it spec.S) {
|
|||
it.Before(func() {
|
||||
var err error
|
||||
|
||||
applicationPath, err = ioutil.TempDir("", "detect-application-path")
|
||||
applicationPath, err = os.MkdirTemp("", "detect-application-path")
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
applicationPath, err = filepath.EvalSymlinks(applicationPath)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
buildpackPath, err = ioutil.TempDir("", "detect-buildpack-path")
|
||||
buildpackPath, err = os.MkdirTemp("", "detect-buildpack-path")
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
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(`
|
||||
api = "0.6"
|
||||
api = "0.8"
|
||||
|
||||
[buildpack]
|
||||
id = "test-id"
|
||||
|
@ -81,7 +81,6 @@ uri = "https://spdx.org/licenses/Apache-1.1.html"
|
|||
|
||||
[[stacks]]
|
||||
id = "test-id"
|
||||
mixins = ["test-name"]
|
||||
|
||||
[metadata]
|
||||
test-key = "test-value"
|
||||
|
@ -89,35 +88,39 @@ test-key = "test-value"
|
|||
0600),
|
||||
).To(Succeed())
|
||||
|
||||
f, err := ioutil.TempFile("", "detect-buildplan-path")
|
||||
f, err := os.CreateTemp("", "detect-buildplan-path")
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
Expect(f.Close()).NotTo(HaveOccurred())
|
||||
buildPlanPath = f.Name()
|
||||
|
||||
commandPath = filepath.Join("bin", "detect")
|
||||
|
||||
detector = &mocks.Detector{}
|
||||
detectFunc = func(libcnb.DetectContext) (libcnb.DetectResult, error) {
|
||||
return libcnb.DetectResult{}, nil
|
||||
}
|
||||
|
||||
exitHandler = &mocks.ExitHandler{}
|
||||
exitHandler.On("Error", mock.Anything)
|
||||
exitHandler.On("Fail")
|
||||
exitHandler.On("Pass")
|
||||
|
||||
platformPath, err = ioutil.TempDir("", "detect-platform-path")
|
||||
platformPath, err = os.MkdirTemp("", "detect-platform-path")
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
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())
|
||||
|
||||
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())
|
||||
|
||||
tomlWriter = &mocks.TOMLWriter{}
|
||||
tomlWriter.On("Write", mock.Anything, mock.Anything).Return(nil)
|
||||
|
||||
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()
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
@ -128,6 +131,8 @@ test-key = "test-value"
|
|||
Expect(os.Chdir(workingDir)).To(Succeed())
|
||||
Expect(os.Unsetenv("CNB_BUILDPACK_DIR")).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(buildpackPath)).To(Succeed())
|
||||
|
@ -135,11 +140,11 @@ test-key = "test-value"
|
|||
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() {
|
||||
Expect(ioutil.WriteFile(filepath.Join(buildpackPath, "buildpack.toml"),
|
||||
Expect(os.WriteFile(filepath.Join(buildpackPath, "buildpack.toml"),
|
||||
[]byte(`
|
||||
api = "0.4"
|
||||
api = "0.7"
|
||||
|
||||
[buildpack]
|
||||
id = "test-id"
|
||||
|
@ -151,149 +156,184 @@ version = "1.1.1"
|
|||
})
|
||||
|
||||
it("fails", func() {
|
||||
libcnb.Detect(detector,
|
||||
libcnb.WithArguments([]string{commandPath, platformPath, buildPlanPath}),
|
||||
libcnb.WithExitHandler(exitHandler),
|
||||
libcnb.Detect(detectFunc,
|
||||
libcnb.NewConfig(
|
||||
libcnb.WithArguments([]string{commandPath, platformPath, buildPlanPath}),
|
||||
libcnb.WithExitHandler(exitHandler),
|
||||
libcnb.WithLogger(log.NewDiscard())),
|
||||
)
|
||||
|
||||
Expect(exitHandler.Calls[0].Arguments.Get(0)).To(MatchError(
|
||||
"this version of libcnb is only compatible with buildpack API 0.5 and 0.6",
|
||||
))
|
||||
if libcnb.MinSupportedBPVersion == libcnb.MaxSupportedBPVersion {
|
||||
Expect(exitHandler.Calls[0].Arguments.Get(0)).To(MatchError(
|
||||
fmt.Sprintf("this version of libcnb is only compatible with buildpack API == %s", libcnb.MinSupportedBPVersion)))
|
||||
} else {
|
||||
Expect(exitHandler.Calls[0].Arguments.Get(0)).To(MatchError(
|
||||
fmt.Sprintf("this version of libcnb is only compatible with buildpack APIs >= %s, <= %s", libcnb.MinSupportedBPVersion, libcnb.MaxSupportedBPVersion),
|
||||
))
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
it("encounters the wrong number of Arguments", func() {
|
||||
detector.On("Detect", mock.Anything).Return(libcnb.DetectResult{}, nil)
|
||||
context("errors if required env vars are not set", func() {
|
||||
for _, e := range []string{"CNB_PLATFORM_DIR", "CNB_BUILD_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)
|
||||
})
|
||||
|
||||
libcnb.Detect(detector,
|
||||
libcnb.WithArguments([]string{commandPath}),
|
||||
libcnb.WithExitHandler(exitHandler),
|
||||
)
|
||||
|
||||
Expect(exitHandler.Calls[0].Arguments.Get(0)).To(MatchError("expected 2 arguments and received 0"))
|
||||
it("fails", func() {
|
||||
libcnb.Detect(detectFunc,
|
||||
libcnb.NewConfig(
|
||||
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("doesn't receive CNB_STACK_ID", func() {
|
||||
Expect(os.Unsetenv("CNB_STACK_ID")).To(Succeed())
|
||||
detector.On("Detect", mock.Anything).Return(libcnb.DetectResult{}, nil)
|
||||
context("has a detect environment", func() {
|
||||
var ctx libcnb.DetectContext
|
||||
|
||||
libcnb.Detect(detector,
|
||||
libcnb.WithArguments([]string{commandPath, platformPath, buildPlanPath}),
|
||||
libcnb.WithExitHandler(exitHandler),
|
||||
)
|
||||
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())
|
||||
|
||||
Expect(exitHandler.Calls[0].Arguments.Get(0)).To(MatchError("CNB_STACK_ID not set"))
|
||||
})
|
||||
detectFunc = func(context libcnb.DetectContext) (libcnb.DetectResult, error) {
|
||||
ctx = context
|
||||
return libcnb.DetectResult{}, nil
|
||||
}
|
||||
})
|
||||
|
||||
it("creates context", func() {
|
||||
detector.On("Detect", mock.Anything).Return(libcnb.DetectResult{Pass: true}, nil)
|
||||
it("creates context", func() {
|
||||
libcnb.Detect(detectFunc,
|
||||
libcnb.NewConfig(
|
||||
libcnb.WithArguments([]string{commandPath}),
|
||||
libcnb.WithExitHandler(exitHandler)),
|
||||
)
|
||||
|
||||
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"},
|
||||
Expect(ctx.ApplicationPath).To(Equal(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,
|
||||
Stacks: []libcnb.BuildpackStack{
|
||||
{
|
||||
ID: "test-id",
|
||||
Mixins: []string{"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",
|
||||
Path: buildpackPath,
|
||||
}))
|
||||
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"))
|
||||
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("fails if CNB_BUILDPACK_DIR is not set", func() {
|
||||
Expect(os.Unsetenv("CNB_BUILDPACK_DIR")).To(Succeed())
|
||||
|
||||
detector.On("Detect", mock.Anything).Return(libcnb.DetectResult{Pass: true}, nil)
|
||||
|
||||
libcnb.Detect(detector,
|
||||
libcnb.WithArguments([]string{filepath.Join(buildpackPath, commandPath), platformPath, buildPlanPath}),
|
||||
libcnb.WithExitHandler(exitHandler),
|
||||
libcnb.Detect(detectFunc,
|
||||
libcnb.NewConfig(
|
||||
libcnb.WithArguments([]string{filepath.Join(buildpackPath, commandPath), platformPath, buildPlanPath}),
|
||||
libcnb.WithExitHandler(exitHandler),
|
||||
libcnb.WithLogger(log.NewDiscard())),
|
||||
)
|
||||
|
||||
ctx := detector.Calls[0].Arguments[0].(libcnb.DetectContext)
|
||||
|
||||
Expect(ctx.Buildpack.Path).To(Equal(buildpackPath))
|
||||
Expect(exitHandler.Calls[0].Arguments.Get(0)).To(MatchError("unable to get CNB_BUILDPACK_DIR, not found"))
|
||||
})
|
||||
|
||||
it("handles error from DetectFunc", func() {
|
||||
detector.On("Detect", mock.Anything).Return(libcnb.DetectResult{}, fmt.Errorf("test-error"))
|
||||
detectFunc = func(libcnb.DetectContext) (libcnb.DetectResult, error) {
|
||||
return libcnb.DetectResult{}, fmt.Errorf("test-error")
|
||||
}
|
||||
|
||||
libcnb.Detect(detector,
|
||||
libcnb.WithArguments([]string{commandPath, platformPath, buildPlanPath}),
|
||||
libcnb.WithExitHandler(exitHandler),
|
||||
libcnb.Detect(detectFunc,
|
||||
libcnb.NewConfig(
|
||||
libcnb.WithArguments([]string{commandPath, platformPath, buildPlanPath}),
|
||||
libcnb.WithExitHandler(exitHandler),
|
||||
libcnb.WithLogger(log.NewDiscard())),
|
||||
)
|
||||
|
||||
Expect(exitHandler.Calls[0].Arguments.Get(0)).To(MatchError("test-error"))
|
||||
})
|
||||
|
||||
it("does not write empty files", func() {
|
||||
detector.On("Detect", mock.Anything).Return(libcnb.DetectResult{Pass: true}, nil)
|
||||
detectFunc = func(libcnb.DetectContext) (libcnb.DetectResult, error) {
|
||||
return libcnb.DetectResult{Pass: true}, nil
|
||||
}
|
||||
|
||||
libcnb.Detect(detector,
|
||||
libcnb.WithArguments([]string{commandPath, platformPath, buildPlanPath}),
|
||||
libcnb.WithExitHandler(exitHandler),
|
||||
libcnb.WithTOMLWriter(tomlWriter),
|
||||
libcnb.Detect(detectFunc,
|
||||
libcnb.NewConfig(
|
||||
libcnb.WithArguments([]string{commandPath, platformPath, buildPlanPath}),
|
||||
libcnb.WithExitHandler(exitHandler),
|
||||
libcnb.WithTOMLWriter(tomlWriter),
|
||||
libcnb.WithLogger(log.NewDiscard())),
|
||||
)
|
||||
|
||||
Expect(tomlWriter.Calls).To(HaveLen(0))
|
||||
})
|
||||
|
||||
it("writes one build plan", func() {
|
||||
detector.On("Detect", mock.Anything).Return(libcnb.DetectResult{
|
||||
Pass: true,
|
||||
Plans: []libcnb.BuildPlan{
|
||||
{
|
||||
Provides: []libcnb.BuildPlanProvide{
|
||||
{Name: "test-name"},
|
||||
},
|
||||
Requires: []libcnb.BuildPlanRequire{
|
||||
{
|
||||
Name: "test-name",
|
||||
Metadata: map[string]interface{}{"test-key": "test-value"},
|
||||
detectFunc = func(libcnb.DetectContext) (libcnb.DetectResult, error) {
|
||||
return libcnb.DetectResult{
|
||||
Pass: true,
|
||||
Plans: []libcnb.BuildPlan{
|
||||
{
|
||||
Provides: []libcnb.BuildPlanProvide{
|
||||
{Name: "test-name"},
|
||||
},
|
||||
Requires: []libcnb.BuildPlanRequire{
|
||||
{
|
||||
Name: "test-name",
|
||||
Metadata: map[string]interface{}{"test-key": "test-value"},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}, nil)
|
||||
}, nil
|
||||
}
|
||||
|
||||
libcnb.Detect(detector,
|
||||
libcnb.WithArguments([]string{commandPath, platformPath, buildPlanPath}),
|
||||
libcnb.WithExitHandler(exitHandler),
|
||||
libcnb.WithTOMLWriter(tomlWriter),
|
||||
libcnb.Detect(detectFunc,
|
||||
libcnb.NewConfig(
|
||||
libcnb.WithArguments([]string{commandPath, platformPath, buildPlanPath}),
|
||||
libcnb.WithExitHandler(exitHandler),
|
||||
libcnb.WithTOMLWriter(tomlWriter),
|
||||
libcnb.WithLogger(log.NewDiscard())),
|
||||
)
|
||||
|
||||
Expect(tomlWriter.Calls[0].Arguments.Get(0)).To(Equal(buildPlanPath))
|
||||
|
@ -313,38 +353,42 @@ version = "1.1.1"
|
|||
})
|
||||
|
||||
it("writes two build plans", func() {
|
||||
detector.On("Detect", mock.Anything).Return(libcnb.DetectResult{
|
||||
Pass: true,
|
||||
Plans: []libcnb.BuildPlan{
|
||||
{
|
||||
Provides: []libcnb.BuildPlanProvide{
|
||||
{Name: "test-name-1"},
|
||||
detectFunc = func(libcnb.DetectContext) (libcnb.DetectResult, error) {
|
||||
return libcnb.DetectResult{
|
||||
Pass: true,
|
||||
Plans: []libcnb.BuildPlan{
|
||||
{
|
||||
Provides: []libcnb.BuildPlanProvide{
|
||||
{Name: "test-name-1"},
|
||||
},
|
||||
Requires: []libcnb.BuildPlanRequire{
|
||||
{
|
||||
Name: "test-name-1",
|
||||
Metadata: map[string]interface{}{"test-key-1": "test-value-1"},
|
||||
},
|
||||
},
|
||||
},
|
||||
Requires: []libcnb.BuildPlanRequire{
|
||||
{
|
||||
Name: "test-name-1",
|
||||
Metadata: map[string]interface{}{"test-key-1": "test-value-1"},
|
||||
{
|
||||
Provides: []libcnb.BuildPlanProvide{
|
||||
{Name: "test-name-2"},
|
||||
},
|
||||
Requires: []libcnb.BuildPlanRequire{
|
||||
{
|
||||
Name: "test-name-2",
|
||||
Metadata: map[string]interface{}{"test-key-2": "test-value-2"},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
Provides: []libcnb.BuildPlanProvide{
|
||||
{Name: "test-name-2"},
|
||||
},
|
||||
Requires: []libcnb.BuildPlanRequire{
|
||||
{
|
||||
Name: "test-name-2",
|
||||
Metadata: map[string]interface{}{"test-key-2": "test-value-2"},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}, nil)
|
||||
}, nil
|
||||
}
|
||||
|
||||
libcnb.Detect(detector,
|
||||
libcnb.WithArguments([]string{commandPath, platformPath, buildPlanPath}),
|
||||
libcnb.WithExitHandler(exitHandler),
|
||||
libcnb.WithTOMLWriter(tomlWriter),
|
||||
libcnb.Detect(detectFunc,
|
||||
libcnb.NewConfig(
|
||||
libcnb.WithArguments([]string{commandPath, platformPath, buildPlanPath}),
|
||||
libcnb.WithExitHandler(exitHandler),
|
||||
libcnb.WithTOMLWriter(tomlWriter),
|
||||
libcnb.WithLogger(log.NewDiscard())),
|
||||
)
|
||||
|
||||
Expect(tomlWriter.Calls[0].Arguments.Get(0)).To(Equal(buildPlanPath))
|
||||
|
|
|
@ -23,10 +23,10 @@ import (
|
|||
. "github.com/onsi/gomega"
|
||||
"github.com/sclevine/spec"
|
||||
|
||||
"github.com/buildpacks/libcnb"
|
||||
"github.com/buildpacks/libcnb/v2"
|
||||
)
|
||||
|
||||
func testEnvironment(t *testing.T, context spec.G, it spec.S) {
|
||||
func testEnvironment(t *testing.T, _ spec.G, it spec.S) {
|
||||
var (
|
||||
Expect = NewWithT(t).Expect
|
||||
|
||||
|
|
|
@ -0,0 +1,103 @@
|
|||
package examples
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
|
||||
cdx "github.com/CycloneDX/cyclonedx-go"
|
||||
|
||||
"github.com/buildpacks/libcnb/v2"
|
||||
"github.com/buildpacks/libcnb/v2/log"
|
||||
)
|
||||
|
||||
const (
|
||||
DefaultVersion = "0.1"
|
||||
)
|
||||
|
||||
type Builder struct {
|
||||
Logger log.Logger
|
||||
}
|
||||
|
||||
// BuildpackPlan may contain multiple entries for a single buildpack, resolve
|
||||
// into a single entry.
|
||||
func resolve(plan libcnb.BuildpackPlan, name string) libcnb.BuildpackPlanEntry {
|
||||
entry := libcnb.BuildpackPlanEntry{
|
||||
Name: name,
|
||||
Metadata: map[string]interface{}{},
|
||||
}
|
||||
for _, e := range plan.Entries {
|
||||
for k, v := range e.Metadata {
|
||||
entry.Metadata[k] = v
|
||||
}
|
||||
}
|
||||
return entry
|
||||
}
|
||||
|
||||
func populateLayer(layer libcnb.Layer, version string) (libcnb.Layer, error) {
|
||||
exampleFile := filepath.Join(layer.Path, "example.txt")
|
||||
if err := os.WriteFile(exampleFile, []byte(version), 0600); err != nil {
|
||||
return libcnb.Layer{}, fmt.Errorf("unable to write example file: %w", err)
|
||||
}
|
||||
|
||||
layer.SharedEnvironment.Default("EXAMPLE_FILE", exampleFile)
|
||||
|
||||
// Provide an SBOM
|
||||
bom := cdx.NewBOM()
|
||||
bom.Metadata = &cdx.Metadata{
|
||||
Component: &cdx.Component{
|
||||
Type: cdx.ComponentTypeFile,
|
||||
Name: "example",
|
||||
Version: version,
|
||||
},
|
||||
}
|
||||
sbomPath := layer.SBOMPath(libcnb.CycloneDXJSON)
|
||||
sbomFile, err := os.OpenFile(sbomPath, os.O_CREATE|os.O_WRONLY, 0600)
|
||||
if err != nil {
|
||||
return layer, err
|
||||
}
|
||||
defer sbomFile.Close()
|
||||
encoder := cdx.NewBOMEncoder(sbomFile, cdx.BOMFileFormatJSON)
|
||||
if err := encoder.Encode(bom); err != nil {
|
||||
return layer, err
|
||||
}
|
||||
return layer, nil
|
||||
}
|
||||
|
||||
func (b Builder) Build(context libcnb.BuildContext) (libcnb.BuildResult, error) {
|
||||
// Reduce possible multiple buildpack plan entries to a single entry
|
||||
entry := resolve(context.Plan, Provides)
|
||||
result := libcnb.NewBuildResult()
|
||||
|
||||
// Read metadata from the buildpack plan, often contributed by libcnb.Requires
|
||||
// of the Detect phase
|
||||
version := DefaultVersion
|
||||
if v, ok := entry.Metadata["version"].(string); ok {
|
||||
version = v
|
||||
}
|
||||
|
||||
// Create a layer
|
||||
layer, err := context.Layers.Layer("example")
|
||||
if err != nil {
|
||||
return result, err
|
||||
}
|
||||
layer.LayerTypes = libcnb.LayerTypes{
|
||||
Launch: true,
|
||||
Build: true,
|
||||
Cache: true,
|
||||
}
|
||||
|
||||
layer, err = populateLayer(layer, version)
|
||||
if err != nil {
|
||||
return result, nil
|
||||
}
|
||||
|
||||
result.Layers = append(result.Layers, layer)
|
||||
return result, nil
|
||||
}
|
||||
|
||||
func ExampleBuild() {
|
||||
detector := Detector{log.New(os.Stdout)}
|
||||
builder := Builder{log.New(os.Stdout)}
|
||||
libcnb.BuildpackMain(detector.Detect, builder.Build)
|
||||
}
|
|
@ -0,0 +1,67 @@
|
|||
package examples
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"os"
|
||||
"path/filepath"
|
||||
|
||||
"github.com/buildpacks/libcnb/v2"
|
||||
"github.com/buildpacks/libcnb/v2/log"
|
||||
)
|
||||
|
||||
const (
|
||||
Provides = "example"
|
||||
BpExampleVersion = "BP_EXAMPLE_VERSION"
|
||||
)
|
||||
|
||||
type Detector struct {
|
||||
Logger log.Logger
|
||||
}
|
||||
|
||||
func (Detector) Detect(context libcnb.DetectContext) (libcnb.DetectResult, error) {
|
||||
version := "1.0"
|
||||
// Scan the application source folder to see if the example buildpack is
|
||||
// required. If `version.toml` does not exist we return a failed DetectResult
|
||||
// but no runtime error has occurred, so we return an empty error.
|
||||
versionPath := filepath.Join(context.ApplicationPath, "version.toml")
|
||||
if _, err := os.Open(versionPath); errors.Is(err, os.ErrNotExist) {
|
||||
return libcnb.DetectResult{}, nil
|
||||
}
|
||||
// Read the version number from the buildpack definition
|
||||
if exampleVersion, exists := context.Buildpack.Metadata["version"]; exists {
|
||||
version = exampleVersion.(string)
|
||||
}
|
||||
// Accept version number from the environment if the user provides it
|
||||
if exampleVersion, exists := context.Platform.Environment[BpExampleVersion]; exists {
|
||||
version = exampleVersion
|
||||
}
|
||||
metadata := map[string]interface{}{
|
||||
"version": version,
|
||||
}
|
||||
return libcnb.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: Provides},
|
||||
},
|
||||
// It is common for a buildpack to Require itself if the build phase
|
||||
// needs information from the detect phase. Here we pass the version number
|
||||
// as metadata to the build phase.
|
||||
Requires: []libcnb.BuildPlanRequire{
|
||||
{
|
||||
Name: Provides,
|
||||
Metadata: metadata,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}, nil
|
||||
}
|
||||
|
||||
func ExampleDetect() {
|
||||
detector := Detector{log.New(os.Stdout)}
|
||||
libcnb.BuildpackMain(detector.Detect, nil)
|
||||
}
|
|
@ -0,0 +1,30 @@
|
|||
package examples
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
|
||||
"github.com/buildpacks/libcnb/v2"
|
||||
"github.com/buildpacks/libcnb/v2/log"
|
||||
)
|
||||
|
||||
type Generator struct {
|
||||
Logger log.Logger
|
||||
}
|
||||
|
||||
func (Generator) Generate(context libcnb.GenerateContext) (libcnb.GenerateResult, error) {
|
||||
// here you can read the context.ApplicationPath folder
|
||||
// and create run.Dockerfile and build.Dockerfile in the context.OutputPath folder
|
||||
// and read metadata from the context.Extension struct
|
||||
|
||||
// Just to use context to keep compiler happy =)
|
||||
fmt.Println(context.Extension.Info.ID)
|
||||
|
||||
result := libcnb.NewGenerateResult()
|
||||
return result, nil
|
||||
}
|
||||
|
||||
func ExampleGenerate() {
|
||||
generator := Generator{log.New(os.Stdout)}
|
||||
libcnb.ExtensionMain(nil, generator.Generate)
|
||||
}
|
|
@ -0,0 +1,71 @@
|
|||
/*
|
||||
* Copyright 2018-2021 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
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
|
||||
"github.com/buildpacks/libcnb/v2/internal"
|
||||
)
|
||||
|
||||
//go:generate mockery --name ExecD --case=underscore
|
||||
|
||||
// ExecD describes an interface for types that follow the Exec.d specification.
|
||||
// It should return a map of environment variables and their values as output.
|
||||
type ExecD interface {
|
||||
Execute() (map[string]string, error)
|
||||
}
|
||||
|
||||
// RunExecD is called by the main function of a buildpack's execd binary, encompassing multiple execd
|
||||
// executors in one binary.
|
||||
func RunExecD(execDMap map[string]ExecD, options ...Option) {
|
||||
config := Config{
|
||||
arguments: os.Args,
|
||||
execdWriter: internal.NewExecDWriter(),
|
||||
exitHandler: internal.NewExitHandler(),
|
||||
}
|
||||
|
||||
for _, option := range options {
|
||||
config = option(config)
|
||||
}
|
||||
|
||||
if len(config.arguments) == 0 {
|
||||
config.exitHandler.Error(fmt.Errorf("expected command name"))
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
c := filepath.Base(config.arguments[0])
|
||||
e, ok := execDMap[c]
|
||||
if !ok {
|
||||
config.exitHandler.Error(fmt.Errorf("unsupported command %s", c))
|
||||
return
|
||||
}
|
||||
|
||||
r, err := e.Execute()
|
||||
if err != nil {
|
||||
config.exitHandler.Error(err)
|
||||
return
|
||||
}
|
||||
|
||||
if err := config.execdWriter.Write(r); err != nil {
|
||||
config.exitHandler.Error(err)
|
||||
return
|
||||
}
|
||||
}
|
|
@ -0,0 +1,130 @@
|
|||
/*
|
||||
* Copyright 2018-2021 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 (
|
||||
"fmt"
|
||||
"testing"
|
||||
|
||||
. "github.com/onsi/gomega"
|
||||
"github.com/sclevine/spec"
|
||||
"github.com/stretchr/testify/mock"
|
||||
|
||||
"github.com/buildpacks/libcnb/v2"
|
||||
"github.com/buildpacks/libcnb/v2/mocks"
|
||||
)
|
||||
|
||||
func testExecD(t *testing.T, _ spec.G, it spec.S) {
|
||||
var (
|
||||
Expect = NewWithT(t).Expect
|
||||
|
||||
exitHandler *mocks.ExitHandler
|
||||
execdWriter *mocks.ExecDWriter
|
||||
)
|
||||
|
||||
it.Before(func() {
|
||||
execdWriter = &mocks.ExecDWriter{}
|
||||
execdWriter.On("Write", mock.Anything).Return(nil)
|
||||
exitHandler = &mocks.ExitHandler{}
|
||||
exitHandler.On("Error", mock.Anything)
|
||||
exitHandler.On("Pass", mock.Anything)
|
||||
exitHandler.On("Fail", mock.Anything)
|
||||
})
|
||||
|
||||
it("encounters the wrong number of arguments", func() {
|
||||
libcnb.RunExecD(map[string]libcnb.ExecD{},
|
||||
libcnb.WithArguments([]string{}),
|
||||
libcnb.WithExitHandler(exitHandler),
|
||||
)
|
||||
|
||||
Expect(exitHandler.Calls[0].Arguments.Get(0)).To(MatchError("expected command name"))
|
||||
})
|
||||
|
||||
it("encounters an unsupported execd binary name", func() {
|
||||
libcnb.RunExecD(map[string]libcnb.ExecD{},
|
||||
libcnb.WithArguments([]string{"/dne"}),
|
||||
libcnb.WithExitHandler(exitHandler),
|
||||
)
|
||||
|
||||
Expect(exitHandler.Calls[0].Arguments.Get(0)).To(MatchError("unsupported command dne"))
|
||||
})
|
||||
|
||||
it("calls the appropriate execd for a given execd invoker binary", func() {
|
||||
execd1 := &mocks.ExecD{}
|
||||
execd2 := &mocks.ExecD{}
|
||||
execd1.On("Execute", mock.Anything).Return(map[string]string{}, nil)
|
||||
|
||||
libcnb.RunExecD(map[string]libcnb.ExecD{"execd1": execd1, "execd2": execd2},
|
||||
libcnb.WithArguments([]string{"execd1"}),
|
||||
libcnb.WithExitHandler(exitHandler),
|
||||
libcnb.WithExecDWriter(execdWriter),
|
||||
)
|
||||
|
||||
Expect(execd1.Calls).To(HaveLen(1))
|
||||
Expect(execd2.Calls).To(BeEmpty())
|
||||
})
|
||||
|
||||
it("calls exitHandler with the error from the execd", func() {
|
||||
e := &mocks.ExecD{}
|
||||
err := fmt.Errorf("example error")
|
||||
e.On("Execute", mock.Anything).Return(nil, err)
|
||||
|
||||
libcnb.RunExecD(map[string]libcnb.ExecD{"e": e},
|
||||
libcnb.WithArguments([]string{"/bin/e"}),
|
||||
libcnb.WithExitHandler(exitHandler),
|
||||
libcnb.WithExecDWriter(execdWriter),
|
||||
)
|
||||
|
||||
Expect(e.Calls).To(HaveLen(1))
|
||||
Expect(execdWriter.Calls).To(HaveLen(0))
|
||||
Expect(exitHandler.Calls[0].Arguments.Get(0)).To(MatchError(err))
|
||||
})
|
||||
|
||||
it("calls execdWriter.write with the appropriate input", func() {
|
||||
e := &mocks.ExecD{}
|
||||
o := map[string]string{"test": "test"}
|
||||
e.On("Execute", mock.Anything).Return(o, nil)
|
||||
|
||||
libcnb.RunExecD(map[string]libcnb.ExecD{"e": e},
|
||||
libcnb.WithArguments([]string{"/bin/e"}),
|
||||
libcnb.WithExitHandler(exitHandler),
|
||||
libcnb.WithExecDWriter(execdWriter),
|
||||
)
|
||||
|
||||
Expect(e.Calls).To(HaveLen(1))
|
||||
Expect(execdWriter.Calls).To(HaveLen(1))
|
||||
Expect(execdWriter.Calls[0].Method).To(BeIdenticalTo("Write"))
|
||||
Expect(execdWriter.Calls[0].Arguments).To(HaveLen(1))
|
||||
Expect(execdWriter.Calls[0].Arguments[0]).To(Equal(o))
|
||||
})
|
||||
|
||||
it("calls exitHandler with the error from the execd", func() {
|
||||
e := &mocks.ExecD{}
|
||||
err := fmt.Errorf("example error")
|
||||
e.On("Execute", mock.Anything).Return(nil, err)
|
||||
|
||||
libcnb.RunExecD(map[string]libcnb.ExecD{"e": e},
|
||||
libcnb.WithArguments([]string{"/bin/e"}),
|
||||
libcnb.WithExitHandler(exitHandler),
|
||||
libcnb.WithExecDWriter(execdWriter),
|
||||
)
|
||||
|
||||
Expect(e.Calls).To(HaveLen(1))
|
||||
Expect(execdWriter.Calls).To(HaveLen(0))
|
||||
Expect(exitHandler.Calls[0].Arguments.Get(0)).To(MatchError(err))
|
||||
})
|
||||
}
|
|
@ -0,0 +1,59 @@
|
|||
/*
|
||||
* Copyright 2023 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
|
||||
|
||||
// ExtensionInfo is information about the extension.
|
||||
type ExtensionInfo struct {
|
||||
// ID is the ID of the extension.
|
||||
ID string `toml:"id"`
|
||||
|
||||
// Name is the name of the extension.
|
||||
Name string `toml:"name"`
|
||||
|
||||
// Version is the version of the extension.
|
||||
Version string `toml:"version"`
|
||||
|
||||
// Homepage is the homepage of the extension.
|
||||
Homepage string `toml:"homepage"`
|
||||
|
||||
// Description is a string describing the extension.
|
||||
Description string `toml:"description"`
|
||||
|
||||
// Keywords is a list of words that are associated with the extension.
|
||||
Keywords []string `toml:"keywords"`
|
||||
|
||||
// Licenses a list of extension licenses.
|
||||
Licenses []License `toml:"licenses"`
|
||||
}
|
||||
|
||||
// Extension is the contents of the extension.toml file.
|
||||
type Extension struct {
|
||||
// API is the api version expected by the extension.
|
||||
API string `toml:"api"`
|
||||
|
||||
// Info is information about the extension.
|
||||
Info ExtensionInfo `toml:"extension"`
|
||||
|
||||
// Path is the path to the extension.
|
||||
Path string `toml:"-"`
|
||||
|
||||
// Targets is the collection of targets supported by the buildpack.
|
||||
Targets []Target `toml:"targets"`
|
||||
|
||||
// Metadata is arbitrary metadata attached to the extension.
|
||||
Metadata map[string]interface{} `toml:"metadata"`
|
||||
}
|
|
@ -0,0 +1,51 @@
|
|||
/*
|
||||
* Copyright 2023 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/v2"
|
||||
|
||||
. "github.com/onsi/gomega"
|
||||
)
|
||||
|
||||
func testExtensionTOML(t *testing.T, _ spec.G, it spec.S) {
|
||||
var (
|
||||
Expect = NewWithT(t).Expect
|
||||
)
|
||||
|
||||
it("does not serialize the Path field", func() {
|
||||
extn := libcnb.Extension{
|
||||
API: "0.8",
|
||||
Info: libcnb.ExtensionInfo{
|
||||
ID: "test-buildpack/sample",
|
||||
Name: "sample",
|
||||
},
|
||||
Path: "../buildpack",
|
||||
}
|
||||
|
||||
output := &bytes.Buffer{}
|
||||
|
||||
Expect(toml.NewEncoder(output).Encode(extn)).To(Succeed())
|
||||
Expect(output.String()).NotTo(Or(ContainSubstring("Path = "), ContainSubstring("path = ")))
|
||||
})
|
||||
}
|
59
formatter.go
59
formatter.go
|
@ -1,59 +0,0 @@
|
|||
/*
|
||||
* 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
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/buildpacks/libcnb/internal"
|
||||
)
|
||||
|
||||
// ApplicationPathFormatter is the formatter for an ApplicationPath.
|
||||
type ApplicationPathFormatter string
|
||||
|
||||
func (a ApplicationPathFormatter) String() string {
|
||||
contents, err := internal.DirectoryContents{Path: string(a)}.Get()
|
||||
if err != nil {
|
||||
return fmt.Sprintf("Application contents: %s", err)
|
||||
}
|
||||
|
||||
return fmt.Sprintf("Application contents: %s", contents)
|
||||
}
|
||||
|
||||
// BuildpackPathFormatter is the formatter for a BuildpackPath.
|
||||
type BuildpackPathFormatter string
|
||||
|
||||
func (b BuildpackPathFormatter) String() string {
|
||||
contents, err := internal.DirectoryContents{Path: string(b)}.Get()
|
||||
if err != nil {
|
||||
return fmt.Sprintf("Buildpack contents: %s", err)
|
||||
}
|
||||
|
||||
return fmt.Sprintf("Buildpack contents: %s", contents)
|
||||
}
|
||||
|
||||
// PlatformFormatter is the formatter for a Platform.
|
||||
type PlatformFormatter Platform
|
||||
|
||||
func (p PlatformFormatter) String() string {
|
||||
contents, err := internal.DirectoryContents{Path: p.Path}.Get()
|
||||
if err != nil {
|
||||
return fmt.Sprintf("Platform contents: %s", err)
|
||||
}
|
||||
|
||||
return fmt.Sprintf("Platform contents: %s", contents)
|
||||
}
|
|
@ -1,119 +0,0 @@
|
|||
/*
|
||||
* 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 (
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"testing"
|
||||
|
||||
. "github.com/onsi/gomega"
|
||||
"github.com/sclevine/spec"
|
||||
|
||||
"github.com/buildpacks/libcnb"
|
||||
)
|
||||
|
||||
func testFormatter(t *testing.T, context spec.G, it spec.S) {
|
||||
var (
|
||||
Expect = NewWithT(t).Expect
|
||||
)
|
||||
|
||||
context("ApplicationPathFormatter", func() {
|
||||
var (
|
||||
app string
|
||||
)
|
||||
|
||||
it.Before(func() {
|
||||
var err error
|
||||
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() {
|
||||
Expect(libcnb.ApplicationPathFormatter(app).String()).To(Equal("Application contents: [.]"))
|
||||
})
|
||||
|
||||
it("lists directory contents", func() {
|
||||
f, err := os.Create(filepath.Join(app, "test-file"))
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
defer f.Close()
|
||||
|
||||
Expect(libcnb.ApplicationPathFormatter(app).String()).To(Equal("Application contents: [. test-file]"))
|
||||
})
|
||||
})
|
||||
|
||||
context("BuildpackPathFormatter", func() {
|
||||
var (
|
||||
bp string
|
||||
)
|
||||
|
||||
it.Before(func() {
|
||||
var err error
|
||||
bp, err = ioutil.TempDir("", "buildpack-path-formatter")
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
})
|
||||
|
||||
it.After(func() {
|
||||
Expect(os.RemoveAll(bp)).To(Succeed())
|
||||
})
|
||||
|
||||
it("lists empty directory contents", func() {
|
||||
Expect(libcnb.BuildpackPathFormatter(bp).String()).To(Equal("Buildpack contents: [.]"))
|
||||
})
|
||||
|
||||
it("lists directory contents", func() {
|
||||
f, err := os.Create(filepath.Join(bp, "test-file"))
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
defer f.Close()
|
||||
|
||||
Expect(libcnb.BuildpackPathFormatter(bp).String()).To(Equal("Buildpack contents: [. test-file]"))
|
||||
})
|
||||
})
|
||||
|
||||
context("PlatformFormatter", func() {
|
||||
var (
|
||||
plat libcnb.Platform
|
||||
)
|
||||
|
||||
it.Before(func() {
|
||||
var err error
|
||||
plat.Path, err = ioutil.TempDir("", "platform-formatter")
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
})
|
||||
|
||||
it.After(func() {
|
||||
Expect(os.RemoveAll(plat.Path)).To(Succeed())
|
||||
})
|
||||
|
||||
it("lists empty directory contents", func() {
|
||||
Expect(libcnb.PlatformFormatter(plat).String()).To(Equal("Platform contents: [.]"))
|
||||
})
|
||||
|
||||
it("lists directory contents", func() {
|
||||
f, err := os.Create(filepath.Join(plat.Path, "test-file"))
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
defer f.Close()
|
||||
|
||||
Expect(libcnb.PlatformFormatter(plat).String()).To(Equal("Platform contents: [. test-file]"))
|
||||
})
|
||||
})
|
||||
}
|
|
@ -0,0 +1,261 @@
|
|||
/*
|
||||
* Copyright 2023 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
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
|
||||
"github.com/BurntSushi/toml"
|
||||
"github.com/Masterminds/semver"
|
||||
|
||||
"github.com/buildpacks/libcnb/v2/internal"
|
||||
"github.com/buildpacks/libcnb/v2/log"
|
||||
)
|
||||
|
||||
// GenerateContext contains the inputs to generate.
|
||||
type GenerateContext struct {
|
||||
// ApplicationPath is the location of the application source code as provided by
|
||||
// the lifecycle.
|
||||
ApplicationPath string
|
||||
|
||||
// Extension is metadata about the extension, from extension.toml.
|
||||
Extension Extension
|
||||
|
||||
// OutputDirectory is the location Dockerfiles should be written to.
|
||||
OutputDirectory string
|
||||
|
||||
// Logger is the way to write messages to the end user
|
||||
Logger log.Logger
|
||||
|
||||
// Plan is the buildpack plan provided to the buildpack.
|
||||
Plan BuildpackPlan
|
||||
|
||||
// Platform is the contents of the platform.
|
||||
Platform Platform
|
||||
|
||||
// TargetInfo contains info of the target (os, arch, ...).
|
||||
TargetInfo TargetInfo
|
||||
|
||||
// TargetDistro is the target distribution (name, version).
|
||||
TargetDistro TargetDistro
|
||||
|
||||
// Deprecated: StackID is the ID of the stack.
|
||||
StackID string
|
||||
}
|
||||
|
||||
// GenerateResult contains the results of detection.
|
||||
type GenerateResult struct {
|
||||
// Unmet contains buildpack plan entries that were not satisfied by the buildpack and therefore should be
|
||||
// passed to subsequent providers.
|
||||
Unmet []UnmetPlanEntry
|
||||
RunDockerfile []byte
|
||||
BuildDockerfile []byte
|
||||
Config *ExtendConfig
|
||||
}
|
||||
|
||||
// DockerfileArg is a Dockerfile argument
|
||||
type DockerfileArg struct {
|
||||
Name string `toml:"name"`
|
||||
Value string `toml:"value"`
|
||||
}
|
||||
|
||||
// BuildConfig contains additional arguments passed to the generated Dockerfiles
|
||||
type BuildConfig struct {
|
||||
Args []DockerfileArg `toml:"args"`
|
||||
}
|
||||
|
||||
// ExtendConfig contains additional configuration for the Dockerfiles
|
||||
type ExtendConfig struct {
|
||||
Build BuildConfig `toml:"build"`
|
||||
Run BuildConfig `toml:"run"`
|
||||
}
|
||||
|
||||
// NewGenerateResult creates a new BuildResult instance, initializing empty fields.
|
||||
func NewGenerateResult() GenerateResult {
|
||||
return GenerateResult{}
|
||||
}
|
||||
|
||||
func (b GenerateResult) String() string {
|
||||
return fmt.Sprintf(
|
||||
"{Unmet:%+v}",
|
||||
b.Unmet,
|
||||
)
|
||||
}
|
||||
|
||||
// GenerateFunc takes a context and returns a result, performing extension generate behaviors.
|
||||
type GenerateFunc func(context GenerateContext) (GenerateResult, error)
|
||||
|
||||
// Generate is called by the main function of a extension, for generate phase
|
||||
func Generate(generate GenerateFunc, config Config) {
|
||||
var (
|
||||
err error
|
||||
file string
|
||||
ok bool
|
||||
)
|
||||
ctx := GenerateContext{Logger: config.logger}
|
||||
|
||||
ctx.ApplicationPath, err = os.Getwd()
|
||||
if err != nil {
|
||||
config.exitHandler.Error(fmt.Errorf("unable to get working directory\n%w", err))
|
||||
return
|
||||
}
|
||||
|
||||
if config.logger.IsDebugEnabled() {
|
||||
if err := config.contentWriter.Write("Application contents", ctx.ApplicationPath); err != nil {
|
||||
config.logger.Debugf("unable to write application contents\n%w", err)
|
||||
}
|
||||
}
|
||||
|
||||
if s, ok := os.LookupEnv(EnvExtensionDirectory); ok {
|
||||
ctx.Extension.Path = filepath.Clean(s)
|
||||
} else {
|
||||
config.exitHandler.Error(fmt.Errorf("unable to get CNB_EXTENSION_DIR, not found"))
|
||||
return
|
||||
}
|
||||
|
||||
if config.logger.IsDebugEnabled() {
|
||||
if err := config.contentWriter.Write("Extension contents", ctx.Extension.Path); err != nil {
|
||||
config.logger.Debugf("unable to write extension contents\n%w", err)
|
||||
}
|
||||
}
|
||||
|
||||
file = filepath.Join(ctx.Extension.Path, "extension.toml")
|
||||
if _, err = toml.DecodeFile(file, &ctx.Extension); err != nil && !os.IsNotExist(err) {
|
||||
config.exitHandler.Error(fmt.Errorf("unable to decode extension %s\n%w", file, err))
|
||||
return
|
||||
}
|
||||
config.logger.Debugf("Extension: %+v", ctx.Extension)
|
||||
|
||||
API, err := semver.NewVersion(ctx.Extension.API)
|
||||
if err != nil {
|
||||
config.exitHandler.Error(errors.New("version cannot be parsed"))
|
||||
return
|
||||
}
|
||||
|
||||
compatVersionCheck, _ := semver.NewConstraint(fmt.Sprintf(">= %s, <= %s", MinSupportedBPVersion, MaxSupportedBPVersion))
|
||||
if !compatVersionCheck.Check(API) {
|
||||
if MinSupportedBPVersion == MaxSupportedBPVersion {
|
||||
config.exitHandler.Error(fmt.Errorf("this version of libcnb is only compatible with buildpack API == %s", MinSupportedBPVersion))
|
||||
return
|
||||
}
|
||||
|
||||
config.exitHandler.Error(fmt.Errorf("this version of libcnb is only compatible with buildpack APIs >= %s, <= %s", MinSupportedBPVersion, MaxSupportedBPVersion))
|
||||
return
|
||||
}
|
||||
|
||||
outputDir, ok := os.LookupEnv(EnvOutputDirectory)
|
||||
if !ok {
|
||||
config.exitHandler.Error(fmt.Errorf("expected CNB_OUTPUT_DIR to be set"))
|
||||
return
|
||||
}
|
||||
ctx.OutputDirectory = outputDir
|
||||
|
||||
ctx.Platform.Path, ok = os.LookupEnv(EnvPlatformDirectory)
|
||||
if !ok {
|
||||
config.exitHandler.Error(fmt.Errorf("expected CNB_PLATFORM_DIR to be set"))
|
||||
return
|
||||
}
|
||||
|
||||
buildpackPlanPath, ok := os.LookupEnv(EnvBuildPlanPath)
|
||||
if !ok {
|
||||
config.exitHandler.Error(fmt.Errorf("expected CNB_BP_PLAN_PATH to be set"))
|
||||
return
|
||||
}
|
||||
|
||||
if config.logger.IsDebugEnabled() {
|
||||
if err := config.contentWriter.Write("Platform contents", ctx.Platform.Path); err != nil {
|
||||
config.logger.Debugf("unable to write platform contents\n%w", err)
|
||||
}
|
||||
}
|
||||
|
||||
if ctx.Platform.Bindings, err = NewBindings(ctx.Platform.Path); err != nil {
|
||||
config.exitHandler.Error(fmt.Errorf("unable to read platform bindings %s\n%w", ctx.Platform.Path, err))
|
||||
return
|
||||
}
|
||||
config.logger.Debugf("Platform Bindings: %+v", ctx.Platform.Bindings)
|
||||
|
||||
file = filepath.Join(ctx.Platform.Path, "env")
|
||||
if ctx.Platform.Environment, err = internal.NewConfigMapFromPath(file); err != nil {
|
||||
config.exitHandler.Error(fmt.Errorf("unable to read platform environment %s\n%w", file, err))
|
||||
return
|
||||
}
|
||||
config.logger.Debugf("Platform Environment: %s", ctx.Platform.Environment)
|
||||
|
||||
if _, err = toml.DecodeFile(buildpackPlanPath, &ctx.Plan); err != nil && !os.IsNotExist(err) {
|
||||
config.exitHandler.Error(fmt.Errorf("unable to decode buildpack plan %s\n%w", buildpackPlanPath, err))
|
||||
return
|
||||
}
|
||||
config.logger.Debugf("Buildpack Plan: %+v", ctx.Plan)
|
||||
|
||||
if ctx.StackID, ok = os.LookupEnv(EnvStackID); !ok {
|
||||
config.logger.Debug("CNB_STACK_ID not set")
|
||||
} else {
|
||||
config.logger.Debugf("Stack: %s", ctx.StackID)
|
||||
}
|
||||
|
||||
if API.GreaterThan(semver.MustParse("0.9")) {
|
||||
ctx.TargetInfo = TargetInfo{}
|
||||
ctx.TargetInfo.OS, _ = os.LookupEnv(EnvTargetOS)
|
||||
ctx.TargetInfo.Arch, _ = os.LookupEnv(EnvTargetArch)
|
||||
ctx.TargetInfo.Variant, _ = os.LookupEnv(EnvTargetArchVariant)
|
||||
config.logger.Debugf("System: %+v", ctx.TargetInfo)
|
||||
|
||||
ctx.TargetDistro = TargetDistro{}
|
||||
ctx.TargetDistro.Name, _ = os.LookupEnv(EnvTargetDistroName)
|
||||
ctx.TargetDistro.Version, _ = os.LookupEnv(EnvTargetDistroVersion)
|
||||
config.logger.Debugf("Distro: %+v", ctx.TargetDistro)
|
||||
}
|
||||
|
||||
result, err := generate(ctx)
|
||||
if err != nil {
|
||||
config.exitHandler.Error(err)
|
||||
return
|
||||
}
|
||||
config.logger.Debugf("Result: %+v", result)
|
||||
|
||||
if len(result.RunDockerfile) > 0 {
|
||||
//nolint:gosec
|
||||
if err := os.WriteFile(filepath.Join(ctx.OutputDirectory, "run.Dockerfile"), result.RunDockerfile, 0644); err != nil {
|
||||
config.exitHandler.Error(err)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
if len(result.BuildDockerfile) > 0 {
|
||||
//nolint:gosec
|
||||
if err := os.WriteFile(filepath.Join(ctx.OutputDirectory, "build.Dockerfile"), result.BuildDockerfile, 0644); err != nil {
|
||||
config.exitHandler.Error(err)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
if result.Config != nil {
|
||||
configFile, err := os.Create(filepath.Join(ctx.OutputDirectory, "extend-config.toml"))
|
||||
if err != nil {
|
||||
config.exitHandler.Error(err)
|
||||
return
|
||||
}
|
||||
|
||||
if err := toml.NewEncoder(configFile).Encode(result.Config); err != nil {
|
||||
config.exitHandler.Error(err)
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,458 @@
|
|||
/*
|
||||
* 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"
|
||||
"errors"
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"testing"
|
||||
"text/template"
|
||||
|
||||
. "github.com/onsi/gomega"
|
||||
"github.com/sclevine/spec"
|
||||
"github.com/stretchr/testify/mock"
|
||||
|
||||
"github.com/buildpacks/libcnb/v2"
|
||||
"github.com/buildpacks/libcnb/v2/log"
|
||||
"github.com/buildpacks/libcnb/v2/mocks"
|
||||
)
|
||||
|
||||
func testGenerate(t *testing.T, context spec.G, it spec.S) {
|
||||
var (
|
||||
Expect = NewWithT(t).Expect
|
||||
|
||||
generateFunc libcnb.GenerateFunc
|
||||
applicationPath string
|
||||
extensionPath string
|
||||
outputPath string
|
||||
buildpackPlanPath string
|
||||
extnTOMLContents string
|
||||
commandPath string
|
||||
environmentWriter *mocks.EnvironmentWriter
|
||||
exitHandler *mocks.ExitHandler
|
||||
platformPath string
|
||||
tomlWriter *mocks.TOMLWriter
|
||||
extensionTOML *template.Template
|
||||
|
||||
workingDir string
|
||||
)
|
||||
|
||||
it.Before(func() {
|
||||
generateFunc = func(libcnb.GenerateContext) (libcnb.GenerateResult, error) {
|
||||
return libcnb.NewGenerateResult(), nil
|
||||
}
|
||||
|
||||
var err error
|
||||
applicationPath, err = os.MkdirTemp("", "generate-application-path")
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
applicationPath, err = filepath.EvalSymlinks(applicationPath)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
extensionPath, err = os.MkdirTemp("", "generate-extension-path")
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
Expect(os.Setenv("CNB_EXTENSION_DIR", extensionPath)).To(Succeed())
|
||||
|
||||
extnTOMLContents = `
|
||||
api = "{{.APIVersion}}"
|
||||
|
||||
[extension]
|
||||
id = "test-id"
|
||||
name = "test-name"
|
||||
version = "1.1.1"
|
||||
description = "A test extension"
|
||||
keywords = ["test", "extension"]
|
||||
|
||||
[[extension.licenses]]
|
||||
type = "Apache-2.0"
|
||||
uri = "https://spdx.org/licenses/Apache-2.0.html"
|
||||
|
||||
[[extension.licenses]]
|
||||
type = "Apache-1.1"
|
||||
uri = "https://spdx.org/licenses/Apache-1.1.html"
|
||||
|
||||
[metadata]
|
||||
test-key = "test-value"
|
||||
`
|
||||
extensionTOML, err = template.New("extension.toml").Parse(extnTOMLContents)
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
|
||||
var b bytes.Buffer
|
||||
err = extensionTOML.Execute(&b, map[string]string{"APIVersion": "0.8"})
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
|
||||
Expect(os.WriteFile(filepath.Join(extensionPath, "extension.toml"), b.Bytes(), 0600)).To(Succeed())
|
||||
|
||||
f, err := os.CreateTemp("", "generate-buildpackplan-path")
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
Expect(f.Close()).NotTo(HaveOccurred())
|
||||
buildpackPlanPath = f.Name()
|
||||
|
||||
Expect(os.WriteFile(buildpackPlanPath,
|
||||
[]byte(`
|
||||
[[entries]]
|
||||
name = "test-name"
|
||||
version = "test-version"
|
||||
|
||||
[entries.metadata]
|
||||
test-key = "test-value"
|
||||
`),
|
||||
0600),
|
||||
).To(Succeed())
|
||||
|
||||
commandPath = filepath.Join("bin", "generate")
|
||||
|
||||
environmentWriter = &mocks.EnvironmentWriter{}
|
||||
environmentWriter.On("Write", mock.Anything, mock.Anything).Return(nil)
|
||||
|
||||
exitHandler = &mocks.ExitHandler{}
|
||||
exitHandler.On("Error", mock.Anything)
|
||||
|
||||
platformPath, err = os.MkdirTemp("", "generate-platform-path")
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
Expect(os.MkdirAll(filepath.Join(platformPath, "bindings", "alpha"), 0755)).To(Succeed())
|
||||
Expect(os.WriteFile(filepath.Join(platformPath, "bindings", "alpha", "test-secret-key"),
|
||||
[]byte("test-secret-value"), 0600)).To(Succeed())
|
||||
|
||||
Expect(os.MkdirAll(filepath.Join(platformPath, "env"), 0755)).To(Succeed())
|
||||
Expect(os.WriteFile(filepath.Join(platformPath, "env", "TEST_ENV"), []byte("test-value"), 0600)).
|
||||
To(Succeed())
|
||||
|
||||
tomlWriter = &mocks.TOMLWriter{}
|
||||
tomlWriter.On("Write", mock.Anything, mock.Anything).Return(nil)
|
||||
|
||||
outputPath, err = os.MkdirTemp("", "generate-output-path")
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
Expect(os.Setenv("CNB_OUTPUT_DIR", outputPath)).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_BP_PLAN_PATH", buildpackPlanPath)).To(Succeed())
|
||||
|
||||
Expect(os.Setenv("CNB_TARGET_OS", "linux")).To(Succeed())
|
||||
Expect(os.Setenv("CNB_TARGET_ARCH", "arm")).To(Succeed())
|
||||
Expect(os.Setenv("CNB_TARGET_ARCH_VARIANT", "v6")).To(Succeed())
|
||||
Expect(os.Setenv("CNB_TARGET_DISTRO_NAME", "ubuntu")).To(Succeed())
|
||||
Expect(os.Setenv("CNB_TARGET_DISTRO_VERSION", "24.04")).To(Succeed())
|
||||
|
||||
workingDir, err = os.Getwd()
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
Expect(os.Chdir(applicationPath)).To(Succeed())
|
||||
})
|
||||
|
||||
it.After(func() {
|
||||
Expect(os.Chdir(workingDir)).To(Succeed())
|
||||
Expect(os.Unsetenv("CNB_EXTENSION_DIR")).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_OUTPUT_DIR")).To(Succeed())
|
||||
|
||||
Expect(os.Unsetenv("CNB_TARGET_OS"))
|
||||
Expect(os.Unsetenv("CNB_TARGET_ARCH"))
|
||||
Expect(os.Unsetenv("CNB_TARGET_ARCH_VARIANT"))
|
||||
Expect(os.Unsetenv("CNB_TARGET_DISTRO_NAME"))
|
||||
Expect(os.Unsetenv("CNB_TARGET_DISTRO_VERSION"))
|
||||
|
||||
Expect(os.RemoveAll(applicationPath)).To(Succeed())
|
||||
Expect(os.RemoveAll(extensionPath)).To(Succeed())
|
||||
Expect(os.RemoveAll(buildpackPlanPath)).To(Succeed())
|
||||
Expect(os.RemoveAll(outputPath)).To(Succeed())
|
||||
Expect(os.RemoveAll(platformPath)).To(Succeed())
|
||||
})
|
||||
|
||||
context("buildpack API is not within the supported range", func() {
|
||||
it.Before(func() {
|
||||
Expect(os.WriteFile(filepath.Join(extensionPath, "extension.toml"),
|
||||
[]byte(`
|
||||
api = "0.7"
|
||||
|
||||
[extension]
|
||||
id = "test-id"
|
||||
name = "test-name"
|
||||
version = "1.1.1"
|
||||
`),
|
||||
0600),
|
||||
).To(Succeed())
|
||||
})
|
||||
|
||||
it("fails", func() {
|
||||
libcnb.Generate(generateFunc,
|
||||
libcnb.NewConfig(
|
||||
libcnb.WithArguments([]string{commandPath, outputPath, platformPath, buildpackPlanPath}),
|
||||
libcnb.WithExitHandler(exitHandler),
|
||||
libcnb.WithLogger(log.NewDiscard())),
|
||||
)
|
||||
|
||||
if libcnb.MinSupportedBPVersion == libcnb.MaxSupportedBPVersion {
|
||||
Expect(exitHandler.Calls[0].Arguments.Get(0)).To(MatchError(
|
||||
fmt.Sprintf("this version of libcnb is only compatible with buildpack API == %s", libcnb.MinSupportedBPVersion)))
|
||||
} else {
|
||||
Expect(exitHandler.Calls[0].Arguments.Get(0)).To(MatchError(
|
||||
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", func() {
|
||||
for _, e := range []string{"CNB_OUTPUT_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(extensionPath, "extension.toml"),
|
||||
[]byte(`
|
||||
api = "0.8"
|
||||
|
||||
[extension]
|
||||
id = "test-id"
|
||||
name = "test-name"
|
||||
version = "1.1.1"
|
||||
`),
|
||||
0600),
|
||||
).To(Succeed())
|
||||
os.Unsetenv(envVar)
|
||||
})
|
||||
|
||||
it("fails", func() {
|
||||
libcnb.Generate(generateFunc,
|
||||
libcnb.NewConfig(
|
||||
libcnb.WithArguments([]string{commandPath}),
|
||||
libcnb.WithExitHandler(exitHandler)),
|
||||
)
|
||||
Expect(exitHandler.Calls[0].Arguments.Get(0)).To(MatchError(
|
||||
fmt.Sprintf("expected %s to be set", envVar),
|
||||
))
|
||||
})
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
context("has a build environment", func() {
|
||||
var ctx libcnb.GenerateContext
|
||||
|
||||
it.Before(func() {
|
||||
Expect(os.WriteFile(filepath.Join(extensionPath, "extension.toml"),
|
||||
[]byte(`
|
||||
api = "0.8"
|
||||
|
||||
[extension]
|
||||
id = "test-id"
|
||||
name = "test-name"
|
||||
version = "1.1.1"
|
||||
`),
|
||||
0600),
|
||||
).To(Succeed())
|
||||
|
||||
generateFunc = func(context libcnb.GenerateContext) (libcnb.GenerateResult, error) {
|
||||
ctx = context
|
||||
return libcnb.NewGenerateResult(), nil
|
||||
}
|
||||
})
|
||||
|
||||
it("creates context", func() {
|
||||
libcnb.Generate(generateFunc,
|
||||
libcnb.NewConfig(
|
||||
libcnb.WithArguments([]string{commandPath})),
|
||||
)
|
||||
Expect(ctx.ApplicationPath).To(Equal(applicationPath))
|
||||
Expect(ctx.Extension).To(Equal(libcnb.Extension{
|
||||
API: "0.8",
|
||||
Info: libcnb.ExtensionInfo{
|
||||
ID: "test-id",
|
||||
Name: "test-name",
|
||||
Version: "1.1.1",
|
||||
},
|
||||
Path: extensionPath,
|
||||
}))
|
||||
Expect(ctx.OutputDirectory).To(Equal(outputPath))
|
||||
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"))
|
||||
})
|
||||
})
|
||||
|
||||
context("has a build environment specifying target metadata", func() {
|
||||
var ctx libcnb.GenerateContext
|
||||
|
||||
it.Before(func() {
|
||||
Expect(os.WriteFile(filepath.Join(extensionPath, "extension.toml"),
|
||||
[]byte(`
|
||||
api = "0.10"
|
||||
|
||||
[extension]
|
||||
id = "test-id"
|
||||
name = "test-name"
|
||||
version = "1.1.1"
|
||||
|
||||
[[targets]]
|
||||
os = "linux"
|
||||
arch = "amd64"
|
||||
|
||||
[[targets.distros]]
|
||||
name = "ubuntu"
|
||||
version = "18.04"
|
||||
|
||||
[[targets.distros]]
|
||||
name = "debian"
|
||||
|
||||
[[targets]]
|
||||
os = "linux"
|
||||
arch = "arm"
|
||||
variant = "v6"
|
||||
`), 0600),
|
||||
).To(Succeed())
|
||||
|
||||
generateFunc = func(context libcnb.GenerateContext) (libcnb.GenerateResult, error) {
|
||||
ctx = context
|
||||
return libcnb.NewGenerateResult(), nil
|
||||
}
|
||||
})
|
||||
|
||||
it("provides target information", func() {
|
||||
libcnb.Generate(generateFunc,
|
||||
libcnb.NewConfig(
|
||||
libcnb.WithArguments([]string{commandPath}),
|
||||
libcnb.WithLogger(log.New(os.Stdout)),
|
||||
),
|
||||
)
|
||||
|
||||
Expect(ctx.Extension.Targets).To(HaveLen(2))
|
||||
Expect(ctx.Extension.Targets[0].OS).To(Equal("linux"))
|
||||
Expect(ctx.Extension.Targets[0].Arch).To(Equal("amd64"))
|
||||
Expect(ctx.Extension.Targets[0].Distros).To(HaveLen(2))
|
||||
Expect(ctx.Extension.Targets[0].Distros[0].Name).To(Equal("ubuntu"))
|
||||
Expect(ctx.Extension.Targets[0].Distros[0].Version).To(Equal("18.04"))
|
||||
Expect(ctx.Extension.Targets[0].Distros[1].Name).To(Equal("debian"))
|
||||
|
||||
Expect(ctx.Extension.Targets[1].Variant).To(Equal("v6"))
|
||||
|
||||
Expect(ctx.TargetInfo.OS).To(Equal("linux"))
|
||||
Expect(ctx.TargetInfo.Arch).To(Equal("arm"))
|
||||
Expect(ctx.TargetInfo.Variant).To(Equal("v6"))
|
||||
Expect(ctx.TargetDistro.Name).To(Equal("ubuntu"))
|
||||
Expect(ctx.TargetDistro.Version).To(Equal("24.04"))
|
||||
})
|
||||
})
|
||||
|
||||
it("fails if CNB_EXTENSION_DIR is not set", func() {
|
||||
Expect(os.Unsetenv("CNB_EXTENSION_DIR")).To(Succeed())
|
||||
|
||||
libcnb.Generate(generateFunc,
|
||||
libcnb.NewConfig(
|
||||
libcnb.WithArguments([]string{filepath.Join(extensionPath, commandPath), outputPath, platformPath, buildpackPlanPath}),
|
||||
libcnb.WithExitHandler(exitHandler),
|
||||
libcnb.WithLogger(log.NewDiscard())),
|
||||
)
|
||||
|
||||
Expect(exitHandler.Calls[0].Arguments.Get(0)).To(MatchError("unable to get CNB_EXTENSION_DIR, not found"))
|
||||
})
|
||||
|
||||
it("handles error from GenerateFunc", func() {
|
||||
generateFunc = func(libcnb.GenerateContext) (libcnb.GenerateResult, error) {
|
||||
return libcnb.NewGenerateResult(), errors.New("test-error")
|
||||
}
|
||||
|
||||
libcnb.Generate(generateFunc,
|
||||
libcnb.NewConfig(
|
||||
libcnb.WithArguments([]string{commandPath, outputPath, platformPath, buildpackPlanPath}),
|
||||
libcnb.WithExitHandler(exitHandler),
|
||||
libcnb.WithLogger(log.NewDiscard())),
|
||||
)
|
||||
|
||||
Expect(exitHandler.Calls[0].Arguments.Get(0)).To(MatchError("test-error"))
|
||||
})
|
||||
|
||||
it("writes Dockerfiles", func() {
|
||||
generateFunc = func(_ libcnb.GenerateContext) (libcnb.GenerateResult, error) {
|
||||
result := libcnb.NewGenerateResult()
|
||||
result.BuildDockerfile = []byte(`FROM foo:latest`)
|
||||
result.RunDockerfile = []byte(`FROM bar:latest`)
|
||||
return result, nil
|
||||
}
|
||||
|
||||
libcnb.Generate(generateFunc,
|
||||
libcnb.NewConfig(
|
||||
libcnb.WithArguments([]string{commandPath, outputPath, platformPath, buildpackPlanPath}),
|
||||
libcnb.WithTOMLWriter(tomlWriter),
|
||||
libcnb.WithLogger(log.NewDiscard())),
|
||||
)
|
||||
|
||||
Expect(filepath.Join(outputPath, "build.Dockerfile")).To(BeARegularFile())
|
||||
Expect(filepath.Join(outputPath, "run.Dockerfile")).To(BeARegularFile())
|
||||
})
|
||||
|
||||
it("writes extend-config.toml", func() {
|
||||
generateFunc = func(_ libcnb.GenerateContext) (libcnb.GenerateResult, error) {
|
||||
result := libcnb.NewGenerateResult()
|
||||
result.Config = &libcnb.ExtendConfig{
|
||||
Build: libcnb.BuildConfig{
|
||||
Args: []libcnb.DockerfileArg{
|
||||
{
|
||||
Name: "foo",
|
||||
Value: "bar",
|
||||
},
|
||||
},
|
||||
},
|
||||
Run: libcnb.BuildConfig{
|
||||
Args: []libcnb.DockerfileArg{
|
||||
{
|
||||
Name: "bar",
|
||||
Value: "bazz",
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
return result, nil
|
||||
}
|
||||
|
||||
libcnb.Generate(generateFunc,
|
||||
libcnb.NewConfig(
|
||||
libcnb.WithArguments([]string{commandPath, outputPath, platformPath, buildpackPlanPath}),
|
||||
libcnb.WithTOMLWriter(tomlWriter),
|
||||
libcnb.WithLogger(log.NewDiscard())),
|
||||
)
|
||||
|
||||
Expect(filepath.Join(outputPath, "extend-config.toml")).To(BeARegularFile())
|
||||
})
|
||||
}
|
24
go.mod
24
go.mod
|
@ -1,10 +1,24 @@
|
|||
module github.com/buildpacks/libcnb
|
||||
module github.com/buildpacks/libcnb/v2
|
||||
|
||||
go 1.15
|
||||
go 1.24
|
||||
|
||||
require (
|
||||
github.com/BurntSushi/toml v0.3.1
|
||||
github.com/onsi/gomega v1.13.0
|
||||
github.com/BurntSushi/toml v1.5.0
|
||||
github.com/CycloneDX/cyclonedx-go v0.9.2
|
||||
github.com/Masterminds/semver v1.5.0
|
||||
github.com/onsi/gomega v1.37.0
|
||||
github.com/sclevine/spec v1.4.0
|
||||
github.com/stretchr/testify v1.7.0
|
||||
github.com/stretchr/testify v1.10.0
|
||||
)
|
||||
|
||||
require (
|
||||
github.com/davecgh/go-spew v1.1.1 // indirect
|
||||
github.com/google/go-cmp v0.7.0 // indirect
|
||||
github.com/kr/text v0.2.0 // indirect
|
||||
github.com/pmezard/go-difflib v1.0.0 // indirect
|
||||
github.com/stretchr/objx v0.5.2 // indirect
|
||||
golang.org/x/net v0.41.0 // indirect
|
||||
golang.org/x/text v0.26.0 // indirect
|
||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c // indirect
|
||||
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||
)
|
||||
|
|
150
go.sum
150
go.sum
|
@ -1,106 +1,58 @@
|
|||
github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ=
|
||||
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
|
||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/BurntSushi/toml v1.5.0 h1:W5quZX/G/csjUnuI8SUYlsHs9M38FC7znL0lIO+DvMg=
|
||||
github.com/BurntSushi/toml v1.5.0/go.mod h1:ukJfTF/6rtPPRCnwkur4qwRxa8vTRFBF0uk2lLoLwho=
|
||||
github.com/CycloneDX/cyclonedx-go v0.9.2 h1:688QHn2X/5nRezKe2ueIVCt+NRqf7fl3AVQk+vaFcIo=
|
||||
github.com/CycloneDX/cyclonedx-go v0.9.2/go.mod h1:vcK6pKgO1WanCdd61qx4bFnSsDJQ6SbM2ZuMIgq86Jg=
|
||||
github.com/Masterminds/semver v1.5.0 h1:H65muMkzWKEuNDnfl9d70GUjFniHKHRbFPGBuZ3QEww=
|
||||
github.com/Masterminds/semver v1.5.0/go.mod h1:MB6lktGJrhw8PrUyiEoblNEGEQ+RzHPF078ddwwvV3Y=
|
||||
github.com/bradleyjkemp/cupaloy/v2 v2.8.0 h1:any4BmKE+jGIaMpnU8YgH/I2LPiLBufr6oMMlVBbn9M=
|
||||
github.com/bradleyjkemp/cupaloy/v2 v2.8.0/go.mod h1:bm7JXdkRd4BHJk9HpwqAI8BoAY1lps46Enkdqw6aRX0=
|
||||
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
|
||||
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/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
|
||||
github.com/fsnotify/fsnotify v1.4.9 h1:hsms1Qyu0jgnwNXIxa+/V/PDsU6CfLf6CNO8H7IWoS4=
|
||||
github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ=
|
||||
github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0/go.mod h1:fyg7847qk6SyHyPtNmDHnmrv/HOrqktSC+C9fM+CJOE=
|
||||
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||
github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8=
|
||||
github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA=
|
||||
github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs=
|
||||
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.2 h1:HFB2fbVIlhIfCfOW81bZFbiC/RvnpXSdhbF2/DJr134=
|
||||
github.com/onsi/ginkgo v1.16.2/go.mod h1:CObGmKUOKaSC0RjmoAK7tKyn4Azo5P2IWuoMnvwxz1E=
|
||||
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.13.0 h1:7lLHu94wT9Ij0o6EWWclhu0aOh32VxhkwEJvzuWPeak=
|
||||
github.com/onsi/gomega v1.13.0/go.mod h1:lRk9szgn8TxENtWd0Tp4c3wjlRfMTMH27I+3Je41yGY=
|
||||
github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY=
|
||||
github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
|
||||
github.com/go-task/slim-sprig/v3 v3.0.0 h1:sUs3vkvUymDpBKi3qH1YSqBQk9+9D/8M2mN1vB6EwHI=
|
||||
github.com/go-task/slim-sprig/v3 v3.0.0/go.mod h1:W848ghGpv3Qj3dhTPRyJypKRiqCdHZiAzKg9hl15HA8=
|
||||
github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
|
||||
github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
|
||||
github.com/google/pprof v0.0.0-20241210010833-40e02aabc2ad h1:a6HEuzUHeKH6hwfN/ZoQgRgVIWFJljSWa/zetS2WTvg=
|
||||
github.com/google/pprof v0.0.0-20241210010833-40e02aabc2ad/go.mod h1:vavhavw2zAxS5dIdcRluK6cSGGPlZynqzFM8NdvU144=
|
||||
github.com/kr/pretty v0.2.1 h1:Fmg33tUaq4/8ym9TJN1x7sLJnHVwhP33CNkpYV/7rwI=
|
||||
github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
|
||||
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
|
||||
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
|
||||
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
|
||||
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
|
||||
github.com/onsi/ginkgo/v2 v2.23.3 h1:edHxnszytJ4lD9D5Jjc4tiDkPBZ3siDeJJkUZJJVkp0=
|
||||
github.com/onsi/ginkgo/v2 v2.23.3/go.mod h1:zXTP6xIp3U8aVuXN8ENK9IXRaTjFnpVB9mGmaSRvxnM=
|
||||
github.com/onsi/gomega v1.37.0 h1:CdEG8g0S133B4OswTDC/5XPSzE1OeP29QOioj2PID2Y=
|
||||
github.com/onsi/gomega v1.37.0/go.mod h1:8D9+Txp43QWKhM24yyOBEdpkzN8FvJyAwecBgsU4KU0=
|
||||
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/sclevine/spec v1.4.0 h1:z/Q9idDcay5m5irkZ28M7PtQM4aOISzOpj4bUPkDee8=
|
||||
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.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
|
||||
github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY=
|
||||
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||
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=
|
||||
github.com/stretchr/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY=
|
||||
github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA=
|
||||
github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA=
|
||||
github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
|
||||
github.com/terminalstatic/go-xsd-validate v0.1.6 h1:TenYeQ3eY631qNi1/cTmLH/s2slHPRKTTHT+XSHkepo=
|
||||
github.com/terminalstatic/go-xsd-validate v0.1.6/go.mod h1:18lsvYFofBflqCrvo1umpABZ99+GneNTw2kEEc8UPJw=
|
||||
github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f h1:J9EGpcZtP0E/raorCMxlFGSTBrsSlaDGf3jU/qvAE2c=
|
||||
github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f/go.mod h1:N2zxlSyiKSe5eX1tZViRH5QA0qijqEDrYZiPEAiq3wU=
|
||||
github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 h1:EzJWgHovont7NscjpAxXsDA8S8BMYve8Y5+7cuRE7R0=
|
||||
github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415/go.mod h1:GwrjFmJcFw6At/Gs6z4yjiIwzuJ1/+UwLxMQDVQXShQ=
|
||||
github.com/xeipuuv/gojsonschema v1.2.0 h1:LhYJRs+L4fBtjZUfuSZIKGeVu0QRy8e5Xi7D17UxZ74=
|
||||
github.com/xeipuuv/gojsonschema v1.2.0/go.mod h1:anYRn/JVcOK2ZgGU+IjEV4nwlhoK5sQluxsYJ78Id3Y=
|
||||
golang.org/x/net v0.41.0 h1:vBTly1HeNPEn3wtREYfy4GZ/NECgw2Cnl+nK6Nz3uvw=
|
||||
golang.org/x/net v0.41.0/go.mod h1:B/K4NNqkfmg07DQYrbwvSluqCJOOXwUjeb/5lOisjbA=
|
||||
golang.org/x/sys v0.33.0 h1:q3i8TbbEz+JRD9ywIRlyRAQbM0qF7hu24q3teo2hbuw=
|
||||
golang.org/x/sys v0.33.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
|
||||
golang.org/x/text v0.26.0 h1:P42AVeLghgTYr4+xUnTRKDMqpar+PtX7KWuNQL21L8M=
|
||||
golang.org/x/text v0.26.0/go.mod h1:QK15LZJUUQVJxhz7wXgxSy/CJaTFjd0G+YLonydOVQA=
|
||||
golang.org/x/tools v0.33.0 h1:4qz2S3zmRxbGIhDIAgjxvFutSvH5EfnsYrRBj0UI0bc=
|
||||
golang.org/x/tools v0.33.0/go.mod h1:CIJMaWEY88juyUfo7UbgPqbC8rU2OqfAV1h2Qp0oMYI=
|
||||
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/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ=
|
||||
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=
|
||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
|
||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
|
||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
|
|
|
@ -5,9 +5,9 @@ linters:
|
|||
disable-all: true
|
||||
enable:
|
||||
- bodyclose
|
||||
- deadcode
|
||||
- dogsled
|
||||
- exportloopref
|
||||
- errcheck
|
||||
- copyloopvar
|
||||
- gocritic
|
||||
- goimports
|
||||
- gosec
|
||||
|
@ -18,14 +18,16 @@ linters:
|
|||
- nakedret
|
||||
- revive
|
||||
- staticcheck
|
||||
- structcheck
|
||||
- stylecheck
|
||||
- typecheck
|
||||
- unconvert
|
||||
- unused
|
||||
- varcheck
|
||||
- whitespace
|
||||
|
||||
linters-settings:
|
||||
revive:
|
||||
rules:
|
||||
- name: dot-imports
|
||||
disabled: true
|
||||
goimports:
|
||||
local-prefixes: github.com/buildpacks/libcnb
|
||||
local-prefixes: github.com/buildpacks/libcnb/v2
|
||||
|
|
|
@ -27,10 +27,13 @@ func TestUnit(t *testing.T) {
|
|||
suite := spec.New("libcnb", spec.Report(report.Terminal{}))
|
||||
suite("Build", testBuild)
|
||||
suite("Detect", testDetect)
|
||||
suite("Generate", testGenerate)
|
||||
suite("Environment", testEnvironment)
|
||||
suite("Formatter", testFormatter)
|
||||
suite("Layer", testLayer)
|
||||
suite("Main", testMain)
|
||||
suite("Platform", testPlatform)
|
||||
suite("ExecD", testExecD)
|
||||
suite("BuildpackTOML", testBuildpackTOML)
|
||||
suite("ExtensionTOML", testExtensionTOML)
|
||||
suite.Run(t)
|
||||
}
|
||||
|
|
|
@ -18,7 +18,6 @@ package internal
|
|||
|
||||
import (
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
@ -45,7 +44,7 @@ func NewConfigMapFromPath(path string) (ConfigMap, error) {
|
|||
} else if stat.IsDir() {
|
||||
continue
|
||||
}
|
||||
contents, err := ioutil.ReadFile(file)
|
||||
contents, err := os.ReadFile(file)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("unable to read file %s\n%w", file, err)
|
||||
}
|
||||
|
|
|
@ -17,7 +17,6 @@
|
|||
package internal_test
|
||||
|
||||
import (
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"testing"
|
||||
|
@ -25,10 +24,10 @@ import (
|
|||
. "github.com/onsi/gomega"
|
||||
"github.com/sclevine/spec"
|
||||
|
||||
"github.com/buildpacks/libcnb/internal"
|
||||
"github.com/buildpacks/libcnb/v2/internal"
|
||||
)
|
||||
|
||||
func testConfigMap(t *testing.T, context spec.G, it spec.S) {
|
||||
func testConfigMap(t *testing.T, _ spec.G, it spec.S) {
|
||||
var (
|
||||
Expect = NewWithT(t).Expect
|
||||
|
||||
|
@ -37,7 +36,7 @@ func testConfigMap(t *testing.T, context spec.G, it spec.S) {
|
|||
|
||||
it.Before(func() {
|
||||
var err error
|
||||
path, err = ioutil.TempDir("", "config-map")
|
||||
path, err = os.MkdirTemp("", "config-map")
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
})
|
||||
|
||||
|
@ -55,7 +54,7 @@ func testConfigMap(t *testing.T, context spec.G, it spec.S) {
|
|||
})
|
||||
|
||||
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)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
@ -66,7 +65,7 @@ func testConfigMap(t *testing.T, context spec.G, it spec.S) {
|
|||
it("ignores dirs and follows symlinks", func() {
|
||||
// this is necessary to support bindings mounted as k8s config maps & secrets
|
||||
Expect(os.MkdirAll(filepath.Join(path, ".hidden"), 0755)).To(Succeed())
|
||||
Expect(ioutil.WriteFile(
|
||||
Expect(os.WriteFile(
|
||||
filepath.Join(path, ".hidden", "test-key"),
|
||||
[]byte("test-value"),
|
||||
0600,
|
||||
|
@ -82,7 +81,7 @@ func testConfigMap(t *testing.T, context spec.G, it spec.S) {
|
|||
})
|
||||
|
||||
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)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
* Copyright 2018-2020 the original author or authors.
|
||||
* Copyright 2018-2022 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.
|
||||
|
@ -18,36 +18,53 @@ package internal
|
|||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"sort"
|
||||
|
||||
"github.com/buildpacks/libcnb/v2/log"
|
||||
)
|
||||
|
||||
// DirectoryContents is used to generate a collection of the names of all files within a directory.
|
||||
type DirectoryContents struct {
|
||||
Path string
|
||||
// DirectoryContentsWriter is used write the contents of a directory to the given io.Writer
|
||||
type DirectoryContentsWriter struct {
|
||||
format log.DirectoryContentFormatter
|
||||
writer io.Writer
|
||||
}
|
||||
|
||||
// Get returns the names of all files within a directory
|
||||
func (d DirectoryContents) Get() ([]string, error) {
|
||||
var contents []string
|
||||
// NewDirectoryContentsWriter returns a new DirectoryContentsWriter initialized and ready to be used
|
||||
func NewDirectoryContentsWriter(format log.DirectoryContentFormatter, writer io.Writer) DirectoryContentsWriter {
|
||||
return DirectoryContentsWriter{
|
||||
format: format,
|
||||
writer: writer,
|
||||
}
|
||||
}
|
||||
|
||||
if err := filepath.Walk(d.Path, func(path string, info os.FileInfo, err error) error {
|
||||
// Write all the file contents to the writer
|
||||
func (d DirectoryContentsWriter) Write(title, path string) error {
|
||||
d.format.RootPath(path)
|
||||
|
||||
if _, err := d.writer.Write([]byte(d.format.Title(title))); err != nil {
|
||||
return fmt.Errorf("unable to write title\n%w", err)
|
||||
}
|
||||
|
||||
if err := filepath.Walk(path, func(path string, info os.FileInfo, err error) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
rel, err := filepath.Rel(d.Path, path)
|
||||
msg, err := d.format.File(path, info)
|
||||
if err != nil {
|
||||
return fmt.Errorf("unable to calculate relative path %s -> %s\n%w", d.Path, path, err)
|
||||
return fmt.Errorf("unable to format\n%w", err)
|
||||
}
|
||||
|
||||
if _, err := d.writer.Write([]byte(msg)); err != nil {
|
||||
return fmt.Errorf("unable to write\n%w", err)
|
||||
}
|
||||
|
||||
contents = append(contents, rel)
|
||||
return nil
|
||||
}); err != nil {
|
||||
return nil, fmt.Errorf("error walking path %s\n%w", d.Path, err)
|
||||
return fmt.Errorf("error walking path %s\n%w", path, err)
|
||||
}
|
||||
|
||||
sort.Strings(contents)
|
||||
return contents, nil
|
||||
return nil
|
||||
}
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
* Copyright 2018-2020 the original author or authors.
|
||||
* Copyright 2018-2022 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.
|
||||
|
@ -17,7 +17,8 @@
|
|||
package internal_test
|
||||
|
||||
import (
|
||||
"io/ioutil"
|
||||
"bytes"
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"testing"
|
||||
|
@ -25,19 +26,20 @@ import (
|
|||
. "github.com/onsi/gomega"
|
||||
"github.com/sclevine/spec"
|
||||
|
||||
"github.com/buildpacks/libcnb/internal"
|
||||
"github.com/buildpacks/libcnb/v2/internal"
|
||||
)
|
||||
|
||||
func testDirectoryContents(t *testing.T, context spec.G, it spec.S) {
|
||||
func testDirectoryContentsWriter(t *testing.T, context spec.G, it spec.S) {
|
||||
var (
|
||||
Expect = NewWithT(t).Expect
|
||||
|
||||
path string
|
||||
buf bytes.Buffer
|
||||
)
|
||||
|
||||
it.Before(func() {
|
||||
var err error
|
||||
path, err = ioutil.TempDir("", "directory-contents")
|
||||
path, err = os.MkdirTemp("", "directory-contents")
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
})
|
||||
|
||||
|
@ -45,8 +47,32 @@ func testDirectoryContents(t *testing.T, context spec.G, it spec.S) {
|
|||
Expect(os.RemoveAll(path)).To(Succeed())
|
||||
})
|
||||
|
||||
context("directory content formats", func() {
|
||||
fm := internal.NewPlainDirectoryContentFormatter()
|
||||
|
||||
it("formats title", func() {
|
||||
Expect(fm.Title("foo")).To(Equal("foo:\n"))
|
||||
})
|
||||
|
||||
it("formats a file", func() {
|
||||
cwd, err := os.Getwd()
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
|
||||
info, err := os.Stat(cwd)
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
|
||||
fm.RootPath(filepath.Dir(cwd))
|
||||
|
||||
Expect(fm.File(cwd, info)).To(Equal(fmt.Sprintf("%s\n", filepath.Base(cwd))))
|
||||
})
|
||||
})
|
||||
|
||||
it("lists empty directory contents", func() {
|
||||
Expect(internal.DirectoryContents{path}.Get()).To(Equal([]string{"."}))
|
||||
fm := internal.NewPlainDirectoryContentFormatter()
|
||||
dc := internal.NewDirectoryContentsWriter(fm, &buf)
|
||||
|
||||
Expect(dc.Write("title", path)).To(Succeed())
|
||||
Expect(buf.String()).To(Equal("title:\n.\n"))
|
||||
})
|
||||
|
||||
it("lists directory contents", func() {
|
||||
|
@ -54,6 +80,10 @@ func testDirectoryContents(t *testing.T, context spec.G, it spec.S) {
|
|||
Expect(err).NotTo(HaveOccurred())
|
||||
defer f.Close()
|
||||
|
||||
Expect(internal.DirectoryContents{path}.Get()).To(Equal([]string{".", "test-file"}))
|
||||
fm := internal.NewPlainDirectoryContentFormatter()
|
||||
dc := internal.NewDirectoryContentsWriter(fm, &buf)
|
||||
|
||||
Expect(dc.Write("title", path)).To(Succeed())
|
||||
Expect(buf.String()).To(Equal("title:\n.\ntest-file\n"))
|
||||
})
|
||||
}
|
||||
|
|
|
@ -18,7 +18,6 @@ package internal
|
|||
|
||||
import (
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path/filepath"
|
||||
)
|
||||
|
@ -38,8 +37,14 @@ func (w EnvironmentWriter) Write(path string, environment map[string]string) err
|
|||
|
||||
for key, value := range environment {
|
||||
f := filepath.Join(path, key)
|
||||
// #nosec
|
||||
if err := ioutil.WriteFile(f, []byte(value), 0644); err != nil {
|
||||
|
||||
// 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)
|
||||
}
|
||||
|
||||
//nolint:gosec
|
||||
if err := os.WriteFile(f, []byte(value), 0644); err != nil {
|
||||
return fmt.Errorf("unable to write file %s\n%w", f, err)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -17,7 +17,6 @@
|
|||
package internal_test
|
||||
|
||||
import (
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"testing"
|
||||
|
@ -25,10 +24,10 @@ import (
|
|||
. "github.com/onsi/gomega"
|
||||
"github.com/sclevine/spec"
|
||||
|
||||
"github.com/buildpacks/libcnb/internal"
|
||||
"github.com/buildpacks/libcnb/v2/internal"
|
||||
)
|
||||
|
||||
func testEnvironmentWriter(t *testing.T, context spec.G, it spec.S) {
|
||||
func testEnvironmentWriter(t *testing.T, _ spec.G, it spec.S) {
|
||||
var (
|
||||
Expect = NewWithT(t).Expect
|
||||
|
||||
|
@ -38,7 +37,7 @@ func testEnvironmentWriter(t *testing.T, context spec.G, it spec.S) {
|
|||
|
||||
it.Before(func() {
|
||||
var err error
|
||||
path, err = ioutil.TempDir("", "environment-writer")
|
||||
path, err = os.MkdirTemp("", "environment-writer")
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
Expect(os.RemoveAll(path)).To(Succeed())
|
||||
})
|
||||
|
@ -54,15 +53,26 @@ func testEnvironmentWriter(t *testing.T, context spec.G, it spec.S) {
|
|||
})
|
||||
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(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(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() {
|
||||
err := writer.Write(path, map[string]string{})
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
|
|
@ -0,0 +1,62 @@
|
|||
/*
|
||||
* Copyright 2018-2021 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 internal
|
||||
|
||||
import (
|
||||
"io"
|
||||
"os"
|
||||
|
||||
"github.com/BurntSushi/toml"
|
||||
)
|
||||
|
||||
// ExecDWriter is a type used to write TOML files to fd3.
|
||||
type ExecDWriter struct {
|
||||
outputWriter io.Writer
|
||||
}
|
||||
|
||||
// Option is a function for configuring an ExitHandler instance.
|
||||
type ExecDOption func(handler ExecDWriter) ExecDWriter
|
||||
|
||||
// WithExecDOutputWriter creates an Option that configures the writer.
|
||||
func WithExecDOutputWriter(writer io.Writer) ExecDOption {
|
||||
return func(execdWriter ExecDWriter) ExecDWriter {
|
||||
execdWriter.outputWriter = writer
|
||||
return execdWriter
|
||||
}
|
||||
}
|
||||
|
||||
// NewExitHandler creates a new instance that calls os.Exit and writes to os.stderr.
|
||||
func NewExecDWriter(options ...ExecDOption) ExecDWriter {
|
||||
h := ExecDWriter{
|
||||
outputWriter: os.NewFile(3, "/dev/fd/3"),
|
||||
}
|
||||
|
||||
for _, option := range options {
|
||||
h = option(h)
|
||||
}
|
||||
|
||||
return h
|
||||
}
|
||||
|
||||
// Write outputs the value serialized in TOML format to the appropriate writer.
|
||||
func (e ExecDWriter) Write(value map[string]string) error {
|
||||
if value == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
return toml.NewEncoder(e.outputWriter).Encode(value)
|
||||
}
|
|
@ -0,0 +1,55 @@
|
|||
/*
|
||||
* 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 internal_test
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"testing"
|
||||
|
||||
. "github.com/onsi/gomega"
|
||||
"github.com/sclevine/spec"
|
||||
|
||||
"github.com/buildpacks/libcnb/v2/internal"
|
||||
)
|
||||
|
||||
func testExecDWriter(t *testing.T, _ spec.G, it spec.S) {
|
||||
var (
|
||||
Expect = NewWithT(t).Expect
|
||||
|
||||
b *bytes.Buffer
|
||||
writer internal.ExecDWriter
|
||||
)
|
||||
|
||||
it.Before(func() {
|
||||
b = bytes.NewBuffer([]byte{})
|
||||
|
||||
writer = internal.NewExecDWriter(
|
||||
internal.WithExecDOutputWriter(b),
|
||||
)
|
||||
})
|
||||
|
||||
it("writes the correct set of values", func() {
|
||||
env := map[string]string{
|
||||
"test": "test",
|
||||
"test2": "te∆t",
|
||||
}
|
||||
Expect(writer.Write(env)).To(BeNil())
|
||||
Expect(b.String()).To(internal.MatchTOML(`
|
||||
test = "test"
|
||||
test2 = "te∆t"`))
|
||||
})
|
||||
}
|
|
@ -24,10 +24,10 @@ import (
|
|||
. "github.com/onsi/gomega"
|
||||
"github.com/sclevine/spec"
|
||||
|
||||
"github.com/buildpacks/libcnb/internal"
|
||||
"github.com/buildpacks/libcnb/v2/internal"
|
||||
)
|
||||
|
||||
func testExitHandler(t *testing.T, context spec.G, it spec.S) {
|
||||
func testExitHandler(t *testing.T, _ spec.G, it spec.S) {
|
||||
var (
|
||||
Expect = NewWithT(t).Expect
|
||||
|
||||
|
|
|
@ -0,0 +1,38 @@
|
|||
package internal
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
)
|
||||
|
||||
type PlainDirectoryContentFormatter struct {
|
||||
rootPath string
|
||||
}
|
||||
|
||||
// NewPlainDirectoryContentFormatter returns a formatter applies no formatting
|
||||
//
|
||||
// The returned formatter operates as such:
|
||||
//
|
||||
// Title -> returns string followed by `:\n`
|
||||
// File -> returns file name relative to the root followed by `\n`
|
||||
func NewPlainDirectoryContentFormatter() *PlainDirectoryContentFormatter {
|
||||
return &PlainDirectoryContentFormatter{}
|
||||
}
|
||||
|
||||
func (p *PlainDirectoryContentFormatter) File(path string, _ os.FileInfo) (string, error) {
|
||||
rel, err := filepath.Rel(p.rootPath, path)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("unable to calculate relative path %s -> %s\n%w", p.rootPath, path, err)
|
||||
}
|
||||
|
||||
return fmt.Sprintf("%s\n", rel), nil
|
||||
}
|
||||
|
||||
func (p *PlainDirectoryContentFormatter) RootPath(path string) {
|
||||
p.rootPath = path
|
||||
}
|
||||
|
||||
func (p *PlainDirectoryContentFormatter) Title(title string) string {
|
||||
return fmt.Sprintf("%s:\n", title)
|
||||
}
|
|
@ -0,0 +1,67 @@
|
|||
/*
|
||||
* Copyright 2018-2022 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 internal_test
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"testing"
|
||||
|
||||
. "github.com/onsi/gomega"
|
||||
"github.com/sclevine/spec"
|
||||
|
||||
"github.com/buildpacks/libcnb/v2/internal"
|
||||
)
|
||||
|
||||
func testFormatters(t *testing.T, context spec.G, it spec.S) {
|
||||
var (
|
||||
Expect = NewWithT(t).Expect
|
||||
|
||||
path string
|
||||
)
|
||||
|
||||
it.Before(func() {
|
||||
var err error
|
||||
path, err = os.MkdirTemp("", "directory-contents")
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
})
|
||||
|
||||
it.After(func() {
|
||||
Expect(os.RemoveAll(path)).To(Succeed())
|
||||
})
|
||||
|
||||
context("directory content formats", func() {
|
||||
fm := internal.NewPlainDirectoryContentFormatter()
|
||||
|
||||
it("formats title", func() {
|
||||
Expect(fm.Title("foo")).To(Equal("foo:\n"))
|
||||
})
|
||||
|
||||
it("formats a file", func() {
|
||||
cwd, err := os.Getwd()
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
|
||||
info, err := os.Stat(cwd)
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
|
||||
fm.RootPath(filepath.Dir(cwd))
|
||||
|
||||
Expect(fm.File(cwd, info)).To(Equal(fmt.Sprintf("%s\n", filepath.Base(cwd))))
|
||||
})
|
||||
})
|
||||
}
|
|
@ -26,9 +26,11 @@ import (
|
|||
func TestUnit(t *testing.T) {
|
||||
suite := spec.New("libcnb/internal", spec.Report(report.Terminal{}))
|
||||
suite("ConfigMap", testConfigMap)
|
||||
suite("DirectoryContents", testDirectoryContents)
|
||||
suite("DirectoryContents", testDirectoryContentsWriter)
|
||||
suite("EnvironmentWriter", testEnvironmentWriter)
|
||||
suite("ExitHandler", testExitHandler)
|
||||
suite("TOMLWriter", testTOMLWriter)
|
||||
suite("ExecDWriter", testExecDWriter)
|
||||
suite("Formatters", testFormatters)
|
||||
suite.Run(t)
|
||||
}
|
||||
|
|
|
@ -1,18 +0,0 @@
|
|||
package internal
|
||||
|
||||
// LayerAPI5 is used for backwards compatibility with serialization/deserialization of the layer toml
|
||||
type LayerAPI5 struct {
|
||||
// bool's are inlined to instead of using libcnb.LayerTypes to break a circular package reference, internal -> libcnb -> internal -> ...
|
||||
|
||||
// Build indicates that a layer should be used for builds.
|
||||
Build bool `toml:"build"`
|
||||
|
||||
// Cache indicates that a layer should be cached.
|
||||
Cache bool `toml:"cache"`
|
||||
|
||||
// Launch indicates that a layer should be used for launch.
|
||||
Launch bool `toml:"launch"`
|
||||
|
||||
// Metadata is the metadata associated with the layer.
|
||||
Metadata map[string]interface{} `toml:"metadata"`
|
||||
}
|
|
@ -17,7 +17,6 @@
|
|||
package internal_test
|
||||
|
||||
import (
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"testing"
|
||||
|
@ -25,10 +24,10 @@ import (
|
|||
. "github.com/onsi/gomega"
|
||||
"github.com/sclevine/spec"
|
||||
|
||||
"github.com/buildpacks/libcnb/internal"
|
||||
"github.com/buildpacks/libcnb/v2/internal"
|
||||
)
|
||||
|
||||
func testTOMLWriter(t *testing.T, context spec.G, it spec.S) {
|
||||
func testTOMLWriter(t *testing.T, _ spec.G, it spec.S) {
|
||||
var (
|
||||
Expect = NewWithT(t).Expect
|
||||
|
||||
|
@ -39,7 +38,7 @@ func testTOMLWriter(t *testing.T, context spec.G, it spec.S) {
|
|||
|
||||
it.Before(func() {
|
||||
var err error
|
||||
parent, err = ioutil.TempDir("", "toml-writer")
|
||||
parent, err = os.MkdirTemp("", "toml-writer")
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
path = filepath.Join(parent, "text.toml")
|
||||
|
@ -56,7 +55,7 @@ func testTOMLWriter(t *testing.T, context spec.G, it spec.S) {
|
|||
})
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
Expect(ioutil.ReadFile(path)).To(internal.MatchTOML(`
|
||||
Expect(os.ReadFile(path)).To(internal.MatchTOML(`
|
||||
some-field = "some-value"
|
||||
other-field = "other-value"`))
|
||||
})
|
||||
|
|
|
@ -0,0 +1,26 @@
|
|||
/*
|
||||
* 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
|
||||
|
||||
// Label represents an image label.
|
||||
type Label struct {
|
||||
// Key is the key of the label.
|
||||
Key string `toml:"key"`
|
||||
|
||||
// Value is the value of the label.
|
||||
Value string `toml:"value"`
|
||||
}
|
|
@ -0,0 +1,33 @@
|
|||
/*
|
||||
* 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
|
||||
|
||||
// LaunchTOML represents the contents of launch.toml.
|
||||
type LaunchTOML struct {
|
||||
// Labels is the collection of image labels contributed by the buildpack.
|
||||
Labels []Label `toml:"labels"`
|
||||
|
||||
// Processes is the collection of process types contributed by the buildpack.
|
||||
Processes []Process `toml:"processes"`
|
||||
|
||||
// Slices is the collection of slices contributed by the buildpack.
|
||||
Slices []Slice `toml:"slices"`
|
||||
}
|
||||
|
||||
func (l LaunchTOML) isEmpty() bool {
|
||||
return len(l.Labels) == 0 && len(l.Processes) == 0 && len(l.Slices) == 0
|
||||
}
|
131
layer.go
131
layer.go
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
* Copyright 2018-2020 the original author or authors.
|
||||
* Copyright 2018-2024 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.
|
||||
|
@ -22,13 +22,20 @@ import (
|
|||
"path/filepath"
|
||||
|
||||
"github.com/BurntSushi/toml"
|
||||
)
|
||||
|
||||
"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
|
||||
type Exec struct {
|
||||
|
||||
// Path is the path to the exec.d directory.
|
||||
Path string
|
||||
}
|
||||
|
@ -43,34 +50,47 @@ func (e Exec) ProcessFilePath(processType string, name string) string {
|
|||
return filepath.Join(e.Path, processType, name)
|
||||
}
|
||||
|
||||
// Profile is the collection of values to be written into profile.d
|
||||
type Profile map[string]string
|
||||
// BOMFormat indicates the format of the SBOM entry
|
||||
type SBOMFormat int
|
||||
|
||||
// Add formats using the default formats for its operands and adds an entry for a .profile.d file. Spaces are added
|
||||
// between operands when neither is a string.
|
||||
func (p Profile) Add(name string, a ...interface{}) {
|
||||
p[name] = fmt.Sprint(a...)
|
||||
const (
|
||||
CycloneDXJSON SBOMFormat = iota
|
||||
SPDXJSON
|
||||
SyftJSON
|
||||
UnknownFormat
|
||||
)
|
||||
|
||||
func (b SBOMFormat) String() string {
|
||||
return []string{
|
||||
BOMFormatCycloneDXExtension,
|
||||
BOMFormatSPDXExtension,
|
||||
BOMFormatSyftExtension,
|
||||
BOMUnknown}[b]
|
||||
}
|
||||
|
||||
// Addf formats according to a format specifier and adds an entry for a .profile.d file.
|
||||
func (p Profile) Addf(name string, format string, a ...interface{}) {
|
||||
p[name] = fmt.Sprintf(format, a...)
|
||||
func (b SBOMFormat) MediaType() string {
|
||||
return []string{
|
||||
BOMMediaTypeCycloneDX,
|
||||
BOMMediaTypeSPDX,
|
||||
BOMMediaTypeSyft,
|
||||
BOMUnknown}[b]
|
||||
}
|
||||
|
||||
// ProcessAdd formats using the default formats for its operands and adds an entry for a .profile.d file. Spaces are
|
||||
// added between operands when neither is a string.
|
||||
func (p Profile) ProcessAdd(processType string, name string, a ...interface{}) {
|
||||
p.Add(filepath.Join(processType, name), a...)
|
||||
}
|
||||
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
|
||||
}
|
||||
|
||||
// ProcessAddf formats according to a format specifier and adds an entry for a .profile.d file.
|
||||
func (p Profile) ProcessAddf(processType string, name string, format string, a ...interface{}) {
|
||||
p.Addf(filepath.Join(processType, name), format, a...)
|
||||
return UnknownFormat, fmt.Errorf("unable to translate from %s to SBOMFormat", from)
|
||||
}
|
||||
|
||||
// Contribute represents a layer managed by the buildpack.
|
||||
type Layer struct {
|
||||
|
||||
// LayerTypes indicates the type of layer
|
||||
LayerTypes `toml:"types"`
|
||||
|
||||
|
@ -92,13 +112,40 @@ type Layer struct {
|
|||
// SharedEnvironment are the environment variables set at both build and launch times.
|
||||
SharedEnvironment Environment `toml:"-"`
|
||||
|
||||
// Profile is the profile.d scripts set in the layer.
|
||||
Profile Profile `toml:"-"`
|
||||
|
||||
// Exec is the exec.d executables set in the layer.
|
||||
Exec Exec `toml:"-"`
|
||||
}
|
||||
|
||||
func (l Layer) Reset() (Layer, error) {
|
||||
l.LayerTypes = LayerTypes{
|
||||
Build: false,
|
||||
Launch: false,
|
||||
Cache: false,
|
||||
}
|
||||
|
||||
l.SharedEnvironment = Environment{}
|
||||
l.BuildEnvironment = Environment{}
|
||||
l.LaunchEnvironment = Environment{}
|
||||
l.Metadata = nil
|
||||
|
||||
err := os.RemoveAll(l.Path)
|
||||
if err != nil {
|
||||
return Layer{}, fmt.Errorf("error could not remove file: %s", err)
|
||||
}
|
||||
|
||||
err = os.MkdirAll(l.Path, os.ModePerm)
|
||||
if err != nil {
|
||||
return Layer{}, fmt.Errorf("error could not create directory: %s", err)
|
||||
}
|
||||
|
||||
return l, nil
|
||||
}
|
||||
|
||||
// SBOMPath returns the path to the layer specific SBOM File
|
||||
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
|
||||
// Cache types.
|
||||
type LayerTypes struct {
|
||||
|
@ -112,21 +159,8 @@ type LayerTypes struct {
|
|||
Launch bool `toml:"launch"`
|
||||
}
|
||||
|
||||
//go:generate mockery -name LayerContributor -case=underscore
|
||||
|
||||
// LayerContributor is an interface for types that create layers.
|
||||
type LayerContributor interface {
|
||||
|
||||
// Contribute accepts a layer and transforms it, returning a layer.
|
||||
Contribute(layer Layer) (Layer, error)
|
||||
|
||||
// Name is the name of the layer.
|
||||
Name() string
|
||||
}
|
||||
|
||||
// Layers represents the layers part of the specification.
|
||||
type Layers struct {
|
||||
|
||||
// Path is the layers filesystem location.
|
||||
Path string
|
||||
}
|
||||
|
@ -139,7 +173,6 @@ func (l *Layers) Layer(name string) (Layer, error) {
|
|||
BuildEnvironment: Environment{},
|
||||
LaunchEnvironment: Environment{},
|
||||
SharedEnvironment: Environment{},
|
||||
Profile: Profile{},
|
||||
Exec: Exec{Path: filepath.Join(l.Path, name, "exec.d")},
|
||||
}
|
||||
|
||||
|
@ -148,17 +181,15 @@ func (l *Layers) Layer(name string) (Layer, error) {
|
|||
return Layer{}, fmt.Errorf("unable to decode layer metadata %s\n%w", f, err)
|
||||
}
|
||||
|
||||
if !layer.Build && !layer.Cache && !layer.Launch {
|
||||
// if all three are false, that could mean we have a API <= 0.5 TOML file
|
||||
// try parsing the <= 0.5 API format where these were top level attributes
|
||||
buf := internal.LayerAPI5{}
|
||||
if _, err := toml.DecodeFile(f, &buf); err != nil && !os.IsNotExist(err) {
|
||||
return Layer{}, fmt.Errorf("unable to decode layer metadata %s\n%w", f, err)
|
||||
}
|
||||
layer.Build = buf.Build
|
||||
layer.Cache = buf.Cache
|
||||
layer.Launch = buf.Launch
|
||||
}
|
||||
|
||||
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))
|
||||
}
|
||||
|
|
203
layer_test.go
203
layer_test.go
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
* Copyright 2018-2020 the original author or authors.
|
||||
* Copyright 2018-2024 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.
|
||||
|
@ -17,7 +17,6 @@
|
|||
package libcnb_test
|
||||
|
||||
import (
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"testing"
|
||||
|
@ -25,7 +24,7 @@ import (
|
|||
. "github.com/onsi/gomega"
|
||||
"github.com/sclevine/spec"
|
||||
|
||||
"github.com/buildpacks/libcnb"
|
||||
"github.com/buildpacks/libcnb/v2"
|
||||
)
|
||||
|
||||
func testLayer(t *testing.T, context spec.G, it spec.S) {
|
||||
|
@ -53,38 +52,152 @@ func testLayer(t *testing.T, context spec.G, it spec.S) {
|
|||
})
|
||||
})
|
||||
|
||||
context("Profile", func() {
|
||||
var profile libcnb.Profile
|
||||
context("Reset", func() {
|
||||
var layer libcnb.Layer
|
||||
|
||||
it.Before(func() {
|
||||
profile = libcnb.Profile{}
|
||||
layers = libcnb.Layers{Path: t.TempDir()}
|
||||
})
|
||||
|
||||
it("adds content", func() {
|
||||
profile.Add("test-name", "test-value")
|
||||
Expect(profile).To(Equal(libcnb.Profile{"test-name": "test-value"}))
|
||||
context("when there is no previous build", func() {
|
||||
it.Before(func() {
|
||||
layer = libcnb.Layer{
|
||||
Name: "test-name",
|
||||
Path: filepath.Join(layers.Path, "test-name"),
|
||||
LayerTypes: libcnb.LayerTypes{
|
||||
Launch: true,
|
||||
Build: true,
|
||||
Cache: true,
|
||||
},
|
||||
}
|
||||
})
|
||||
|
||||
it("initializes an empty layer", func() {
|
||||
var err error
|
||||
layer, err = layer.Reset()
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
Expect(layer).To(Equal(libcnb.Layer{
|
||||
Name: "test-name",
|
||||
Path: filepath.Join(layers.Path, "test-name"),
|
||||
LayerTypes: libcnb.LayerTypes{
|
||||
Launch: false,
|
||||
Build: false,
|
||||
Cache: false,
|
||||
},
|
||||
SharedEnvironment: libcnb.Environment{},
|
||||
BuildEnvironment: libcnb.Environment{},
|
||||
LaunchEnvironment: libcnb.Environment{},
|
||||
}))
|
||||
|
||||
Expect(filepath.Join(layers.Path, "test-name")).To(BeADirectory())
|
||||
})
|
||||
})
|
||||
|
||||
it("adds formatted content", func() {
|
||||
profile.Addf("test-name", "test-%s", "value")
|
||||
Expect(profile).To(Equal(libcnb.Profile{"test-name": "test-value"}))
|
||||
context("when cache is retrieved from previous build", func() {
|
||||
it.Before(func() {
|
||||
sharedEnvDir := filepath.Join(layers.Path, "test-name", "env")
|
||||
Expect(os.MkdirAll(sharedEnvDir, os.ModePerm)).To(Succeed())
|
||||
|
||||
err := os.WriteFile(filepath.Join(sharedEnvDir, "OVERRIDE_VAR.override"), []byte("override-value"), 0600)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
buildEnvDir := filepath.Join(layers.Path, "test-name", "env.build")
|
||||
Expect(os.MkdirAll(buildEnvDir, os.ModePerm)).To(Succeed())
|
||||
|
||||
err = os.WriteFile(filepath.Join(buildEnvDir, "DEFAULT_VAR.default"), []byte("default-value"), 0600)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
err = os.WriteFile(filepath.Join(buildEnvDir, "INVALID_VAR.invalid"), []byte("invalid-value"), 0600)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
launchEnvDir := filepath.Join(layers.Path, "test-name", "env.launch")
|
||||
Expect(os.MkdirAll(launchEnvDir, os.ModePerm)).To(Succeed())
|
||||
|
||||
err = os.WriteFile(filepath.Join(launchEnvDir, "APPEND_VAR.append"), []byte("append-value"), 0600)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
err = os.WriteFile(filepath.Join(launchEnvDir, "APPEND_VAR.delim"), []byte("!"), 0600)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
layer = libcnb.Layer{
|
||||
Name: "test-name",
|
||||
Path: filepath.Join(layers.Path, "test-name"),
|
||||
LayerTypes: libcnb.LayerTypes{
|
||||
Launch: true,
|
||||
Build: true,
|
||||
Cache: true,
|
||||
},
|
||||
SharedEnvironment: libcnb.Environment{
|
||||
"OVERRIDE_VAR.override": "override-value",
|
||||
},
|
||||
BuildEnvironment: libcnb.Environment{
|
||||
"DEFAULT_VAR.default": "default-value",
|
||||
},
|
||||
LaunchEnvironment: libcnb.Environment{
|
||||
"APPEND_VAR.append": "append-value",
|
||||
"APPEND_VAR.delim": "!",
|
||||
},
|
||||
Metadata: map[string]interface{}{
|
||||
"some-key": "some-value",
|
||||
},
|
||||
}
|
||||
})
|
||||
|
||||
context("when Reset is called on a layer", func() {
|
||||
it("resets all of the layer data and clears the directory", func() {
|
||||
layer, err := layer.Reset()
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
Expect(layer).To(Equal(libcnb.Layer{
|
||||
Name: "test-name",
|
||||
Path: filepath.Join(layers.Path, "test-name"),
|
||||
LayerTypes: libcnb.LayerTypes{
|
||||
Launch: false,
|
||||
Build: false,
|
||||
Cache: false,
|
||||
},
|
||||
SharedEnvironment: libcnb.Environment{},
|
||||
BuildEnvironment: libcnb.Environment{},
|
||||
LaunchEnvironment: libcnb.Environment{},
|
||||
}))
|
||||
|
||||
Expect(filepath.Join(layers.Path, "test-name")).To(BeADirectory())
|
||||
|
||||
files, err := filepath.Glob(filepath.Join(layers.Path, "test-name", "*"))
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
Expect(files).To(BeEmpty())
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
it("adds process-specific content", func() {
|
||||
profile.ProcessAdd("test-process", "test-name", "test-value")
|
||||
Expect(profile).To(Equal(libcnb.Profile{filepath.Join("test-process", "test-name"): "test-value"}))
|
||||
})
|
||||
context("could not remove files in layer", func() {
|
||||
it.Before(func() {
|
||||
Expect(os.Chmod(layers.Path, 0000)).To(Succeed())
|
||||
})
|
||||
|
||||
it("adds process-specific formatted content", func() {
|
||||
profile.ProcessAddf("test-process", "test-name", "test-%s", "value")
|
||||
Expect(profile).To(Equal(libcnb.Profile{filepath.Join("test-process", "test-name"): "test-value"}))
|
||||
it.After(func() {
|
||||
Expect(os.Chmod(layers.Path, 0777)).To(Succeed())
|
||||
})
|
||||
|
||||
it("return an error", func() {
|
||||
layer := libcnb.Layer{
|
||||
Name: "some-layer",
|
||||
Path: filepath.Join(layers.Path, "some-layer"),
|
||||
}
|
||||
|
||||
_, err := layer.Reset()
|
||||
Expect(err).To(MatchError(ContainSubstring("error could not remove file: ")))
|
||||
Expect(err).To(MatchError(ContainSubstring("permission denied")))
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
context("Layers", func() {
|
||||
it.Before(func() {
|
||||
var err error
|
||||
path, err = ioutil.TempDir("", "layers")
|
||||
path, err = os.MkdirTemp("", "layers")
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
layers = libcnb.Layers{Path: path}
|
||||
|
@ -107,32 +220,40 @@ func testLayer(t *testing.T, context spec.G, it spec.S) {
|
|||
Expect(l.BuildEnvironment).To(Equal(libcnb.Environment{}))
|
||||
Expect(l.LaunchEnvironment).To(Equal(libcnb.Environment{}))
|
||||
Expect(l.SharedEnvironment).To(Equal(libcnb.Environment{}))
|
||||
Expect(l.Profile).To(Equal(libcnb.Profile{}))
|
||||
})
|
||||
|
||||
it("reads existing 0.5 metadata", func() {
|
||||
Expect(ioutil.WriteFile(
|
||||
filepath.Join(path, "test-name.toml"),
|
||||
[]byte(`
|
||||
launch = true
|
||||
build = false
|
||||
|
||||
[metadata]
|
||||
test-key = "test-value"
|
||||
`),
|
||||
0600),
|
||||
).To(Succeed())
|
||||
|
||||
it("generates SBOM paths", func() {
|
||||
l, err := layers.Layer("test-name")
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
Expect(l.Metadata).To(Equal(map[string]interface{}{"test-key": "test-value"}))
|
||||
Expect(l.Launch).To(BeTrue())
|
||||
Expect(l.Build).To(BeFalse())
|
||||
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("reads existing 0.6 metadata", func() {
|
||||
Expect(ioutil.WriteFile(
|
||||
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 metadata", func() {
|
||||
Expect(os.WriteFile(
|
||||
filepath.Join(path, "test-name.toml"),
|
||||
[]byte(`
|
||||
[types]
|
||||
|
@ -153,8 +274,8 @@ test-key = "test-value"
|
|||
Expect(l.Build).To(BeFalse())
|
||||
})
|
||||
|
||||
it("reads existing 0.6 metadata with launch, build and cache all false", func() {
|
||||
Expect(ioutil.WriteFile(
|
||||
it("reads existing metadata with launch, build and cache all false", func() {
|
||||
Expect(os.WriteFile(
|
||||
filepath.Join(path, "test-name.toml"),
|
||||
[]byte(`
|
||||
[types]
|
||||
|
|
|
@ -0,0 +1,59 @@
|
|||
/*
|
||||
* Copyright 2018-2022 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 log
|
||||
|
||||
import (
|
||||
"os"
|
||||
)
|
||||
|
||||
//go:generate mockery --name DirectoryContentFormatter --case=underscore
|
||||
|
||||
// DirectoryContentFormatter allows customization of logged directory output
|
||||
//
|
||||
// When libcnb logs the contents of a directory, each item in the directory
|
||||
// is passed through a DirectoryContentFormatter.
|
||||
//
|
||||
// DirectoryContentsWriter implements this workflow:
|
||||
// - call RootPath(string) with the root path that's being walked
|
||||
// - call Title(string) with the given title, the output is logged
|
||||
// - for each file in the directory:
|
||||
// - call File(string, os.FileInfo), the output is logged
|
||||
//
|
||||
// # A default implementation is provided that returns a formatter applies no formatting
|
||||
//
|
||||
// The returned formatter operates as such:
|
||||
//
|
||||
// Title -> returns string followed by `:\n`
|
||||
// File -> returns file name relative to the root followed by `\n`
|
||||
//
|
||||
// A buildpack author could provide their own implementation through
|
||||
// WithDirectoryContentFormatter when calling Detect or Build.
|
||||
//
|
||||
// A custom implementation might log in color or might log additional
|
||||
// information about each file, like permissions. The implementation can
|
||||
// also control line endings to force all of the files to be logged on a
|
||||
// single line, or as multiple lines.
|
||||
type DirectoryContentFormatter interface {
|
||||
// File takes the full path and os.FileInfo and returns a display string
|
||||
File(path string, info os.FileInfo) (string, error)
|
||||
|
||||
// RootPath provides the root path being iterated
|
||||
RootPath(path string)
|
||||
|
||||
// Title provides a plain string title which can be embellished
|
||||
Title(title string) string
|
||||
}
|
|
@ -14,7 +14,7 @@
|
|||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package poet_test
|
||||
package log_test
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
@ -24,7 +24,7 @@ import (
|
|||
)
|
||||
|
||||
func TestUnit(t *testing.T) {
|
||||
suite := spec.New("libcnb/poet", spec.Report(report.Terminal{}))
|
||||
suite("Logger", testLogger)
|
||||
suite := spec.New("libcnb/log", spec.Report(report.Terminal{}))
|
||||
suite("PlainLogger", testLogger)
|
||||
suite.Run(t)
|
||||
}
|
|
@ -0,0 +1,102 @@
|
|||
/*
|
||||
* 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 log
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"strings"
|
||||
)
|
||||
|
||||
//go:generate mockery --name Logger --case=underscore
|
||||
|
||||
// Logger is the interface implement by a type that wishes to write log messages generated by libcnb
|
||||
type Logger interface {
|
||||
// Debug formats using the default formats for its operands
|
||||
Debug(a ...interface{})
|
||||
|
||||
// Debugf formats according to a format specifier
|
||||
Debugf(format string, a ...interface{})
|
||||
|
||||
// DebugWriter returns the configured debug writer
|
||||
DebugWriter() io.Writer
|
||||
|
||||
// IsDebugEnabled indicates whether debug logging is enabled
|
||||
IsDebugEnabled() bool
|
||||
}
|
||||
|
||||
// PlainLogger implements Logger and logs messages to a writer.
|
||||
type PlainLogger struct {
|
||||
debug io.Writer
|
||||
}
|
||||
|
||||
// New creates a new instance of PlainLogger. It configures debug logging if $BP_DEBUG or $BP_LOG_LEVEL are set.
|
||||
func New(debug io.Writer) PlainLogger {
|
||||
if strings.ToLower(os.Getenv("BP_LOG_LEVEL")) == "debug" || os.Getenv("BP_DEBUG") != "" {
|
||||
return PlainLogger{debug: debug}
|
||||
}
|
||||
|
||||
return PlainLogger{}
|
||||
}
|
||||
|
||||
// NewDiscard creates a new instance of PlainLogger that discards all log messages. Useful in testing.
|
||||
func NewDiscard() PlainLogger {
|
||||
return PlainLogger{debug: io.Discard}
|
||||
}
|
||||
|
||||
// Debug formats using the default formats for its operands and writes to the configured debug writer. Spaces are added
|
||||
// between operands when neither is a string.
|
||||
func (l PlainLogger) Debug(a ...interface{}) {
|
||||
if !l.IsDebugEnabled() {
|
||||
return
|
||||
}
|
||||
|
||||
s := fmt.Sprint(a...)
|
||||
|
||||
if !strings.HasSuffix(s, "\n") {
|
||||
s += "\n"
|
||||
}
|
||||
|
||||
_, _ = fmt.Fprint(l.debug, s)
|
||||
}
|
||||
|
||||
// Debugf formats according to a format specifier and writes to the configured debug writer.
|
||||
func (l PlainLogger) Debugf(format string, a ...interface{}) {
|
||||
if !l.IsDebugEnabled() {
|
||||
return
|
||||
}
|
||||
|
||||
if !strings.HasSuffix(format, "\n") {
|
||||
format += "\n"
|
||||
}
|
||||
|
||||
_, _ = fmt.Fprintf(l.debug, format, a...)
|
||||
}
|
||||
|
||||
// DebugWriter returns the configured debug writer.
|
||||
func (l PlainLogger) DebugWriter() io.Writer {
|
||||
if l.IsDebugEnabled() {
|
||||
return l.debug
|
||||
}
|
||||
return io.Discard
|
||||
}
|
||||
|
||||
// IsDebugEnabled indicates whether debug logging is enabled.
|
||||
func (l PlainLogger) IsDebugEnabled() bool {
|
||||
return l.debug != nil
|
||||
}
|
|
@ -14,17 +14,18 @@
|
|||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package poet_test
|
||||
package log_test
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"io"
|
||||
"os"
|
||||
"testing"
|
||||
|
||||
. "github.com/onsi/gomega"
|
||||
"github.com/sclevine/spec"
|
||||
|
||||
"github.com/buildpacks/libcnb/poet"
|
||||
"github.com/buildpacks/libcnb/v2/log"
|
||||
)
|
||||
|
||||
func testLogger(t *testing.T, context spec.G, it spec.S) {
|
||||
|
@ -32,7 +33,7 @@ func testLogger(t *testing.T, context spec.G, it spec.S) {
|
|||
Expect = NewWithT(t).Expect
|
||||
|
||||
b *bytes.Buffer
|
||||
l poet.Logger
|
||||
l log.PlainLogger
|
||||
)
|
||||
|
||||
it.Before(func() {
|
||||
|
@ -41,18 +42,26 @@ func testLogger(t *testing.T, context spec.G, it spec.S) {
|
|||
|
||||
context("without BP_DEBUG", func() {
|
||||
it.Before(func() {
|
||||
l = poet.NewLogger(b)
|
||||
l = log.New(b)
|
||||
})
|
||||
|
||||
it("does not configure debug", func() {
|
||||
Expect(l.IsDebugEnabled()).To(BeFalse())
|
||||
})
|
||||
|
||||
it("does not return nil debug writer", func() {
|
||||
Expect(l.DebugWriter()).To(Not(BeNil()))
|
||||
})
|
||||
|
||||
it("does not return non-discard writer", func() {
|
||||
Expect(l.DebugWriter()).To(Equal(io.Discard))
|
||||
})
|
||||
})
|
||||
|
||||
context("with BP_DEBUG", func() {
|
||||
it.Before(func() {
|
||||
Expect(os.Setenv("BP_DEBUG", "")).To(Succeed())
|
||||
l = poet.NewLogger(b)
|
||||
l = log.New(b)
|
||||
})
|
||||
|
||||
it.After(func() {
|
||||
|
@ -60,55 +69,33 @@ func testLogger(t *testing.T, context spec.G, it spec.S) {
|
|||
})
|
||||
|
||||
it("configures debug", func() {
|
||||
Expect(l.IsDebugEnabled()).To(BeTrue())
|
||||
Expect(l.IsDebugEnabled()).To(BeFalse())
|
||||
})
|
||||
})
|
||||
|
||||
context("with debug disabled", func() {
|
||||
context("with BP_LOG_LEVEL set to DEBUG", func() {
|
||||
it.Before(func() {
|
||||
l = poet.NewLoggerWithOptions(b)
|
||||
Expect(os.Setenv("BP_LOG_LEVEL", "DEBUG")).To(Succeed())
|
||||
l = log.New(b)
|
||||
})
|
||||
|
||||
it("does not write debug log", func() {
|
||||
l.Debug("test-message")
|
||||
Expect(b.String()).To(Equal(""))
|
||||
it.After(func() {
|
||||
Expect(os.Unsetenv("BP_LOG_LEVEL")).To(Succeed())
|
||||
})
|
||||
|
||||
it("does not write debugf log", func() {
|
||||
l.Debugf("test-%s", "message")
|
||||
Expect(b.String()).To(Equal(""))
|
||||
})
|
||||
|
||||
it("does not return debug writer", func() {
|
||||
Expect(l.DebugWriter()).To(BeNil())
|
||||
})
|
||||
|
||||
it("indicates that debug is not enabled", func() {
|
||||
Expect(l.IsDebugEnabled()).To(BeFalse())
|
||||
})
|
||||
|
||||
it("writes info log", func() {
|
||||
l.Info("test-message")
|
||||
Expect(b.String()).To(Equal("test-message\n"))
|
||||
})
|
||||
|
||||
it("writes infof log", func() {
|
||||
l.Infof("test-%s", "message")
|
||||
Expect(b.String()).To(Equal("test-message\n"))
|
||||
})
|
||||
|
||||
it("returns info writer", func() {
|
||||
Expect(l.InfoWriter()).NotTo(BeNil())
|
||||
})
|
||||
|
||||
it("indicates that info is enabled", func() {
|
||||
Expect(l.IsInfoEnabled()).To(BeTrue())
|
||||
it("configures debug", func() {
|
||||
Expect(l.IsDebugEnabled()).To(BeTrue())
|
||||
})
|
||||
})
|
||||
|
||||
context("with debug enabled", func() {
|
||||
it.Before(func() {
|
||||
l = poet.NewLoggerWithOptions(b, poet.WithDebug(b))
|
||||
Expect(os.Setenv("BP_LOG_LEVEL", "DEBUG")).To(Succeed())
|
||||
l = log.New(b)
|
||||
})
|
||||
|
||||
it.After(func() {
|
||||
Expect(os.Unsetenv("BP_LOG_LEVEL")).To(Succeed())
|
||||
})
|
||||
|
||||
it("writes debug log", func() {
|
||||
|
@ -121,30 +108,14 @@ func testLogger(t *testing.T, context spec.G, it spec.S) {
|
|||
Expect(b.String()).To(Equal("test-message\n"))
|
||||
})
|
||||
|
||||
it("returns debug writer", func() {
|
||||
Expect(l.DebugWriter()).NotTo(BeNil())
|
||||
it("writes debug directly", func() {
|
||||
_, err := l.DebugWriter().Write([]byte("test-message\n"))
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
Expect(b.String()).To(Equal("test-message\n"))
|
||||
})
|
||||
|
||||
it("indicates that debug is enabled", func() {
|
||||
Expect(l.IsDebugEnabled()).To(BeTrue())
|
||||
})
|
||||
|
||||
it("writes info log", func() {
|
||||
l.Info("test-message")
|
||||
Expect(b.String()).To(Equal("test-message\n"))
|
||||
})
|
||||
|
||||
it("writes infof log", func() {
|
||||
l.Infof("test-%s", "message")
|
||||
Expect(b.String()).To(Equal("test-message\n"))
|
||||
})
|
||||
|
||||
it("returns info writer", func() {
|
||||
Expect(l.InfoWriter()).NotTo(BeNil())
|
||||
})
|
||||
|
||||
it("indicates that info is enabled", func() {
|
||||
Expect(l.IsInfoEnabled()).To(BeTrue())
|
||||
})
|
||||
})
|
||||
}
|
|
@ -0,0 +1,79 @@
|
|||
// Code generated by mockery v2.43.2. DO NOT EDIT.
|
||||
|
||||
package mocks
|
||||
|
||||
import (
|
||||
fs "io/fs"
|
||||
|
||||
mock "github.com/stretchr/testify/mock"
|
||||
)
|
||||
|
||||
// DirectoryContentFormatter is an autogenerated mock type for the DirectoryContentFormatter type
|
||||
type DirectoryContentFormatter struct {
|
||||
mock.Mock
|
||||
}
|
||||
|
||||
// File provides a mock function with given fields: path, info
|
||||
func (_m *DirectoryContentFormatter) File(path string, info fs.FileInfo) (string, error) {
|
||||
ret := _m.Called(path, info)
|
||||
|
||||
if len(ret) == 0 {
|
||||
panic("no return value specified for File")
|
||||
}
|
||||
|
||||
var r0 string
|
||||
var r1 error
|
||||
if rf, ok := ret.Get(0).(func(string, fs.FileInfo) (string, error)); ok {
|
||||
return rf(path, info)
|
||||
}
|
||||
if rf, ok := ret.Get(0).(func(string, fs.FileInfo) string); ok {
|
||||
r0 = rf(path, info)
|
||||
} else {
|
||||
r0 = ret.Get(0).(string)
|
||||
}
|
||||
|
||||
if rf, ok := ret.Get(1).(func(string, fs.FileInfo) error); ok {
|
||||
r1 = rf(path, info)
|
||||
} else {
|
||||
r1 = ret.Error(1)
|
||||
}
|
||||
|
||||
return r0, r1
|
||||
}
|
||||
|
||||
// RootPath provides a mock function with given fields: path
|
||||
func (_m *DirectoryContentFormatter) RootPath(path string) {
|
||||
_m.Called(path)
|
||||
}
|
||||
|
||||
// Title provides a mock function with given fields: title
|
||||
func (_m *DirectoryContentFormatter) Title(title string) string {
|
||||
ret := _m.Called(title)
|
||||
|
||||
if len(ret) == 0 {
|
||||
panic("no return value specified for Title")
|
||||
}
|
||||
|
||||
var r0 string
|
||||
if rf, ok := ret.Get(0).(func(string) string); ok {
|
||||
r0 = rf(title)
|
||||
} else {
|
||||
r0 = ret.Get(0).(string)
|
||||
}
|
||||
|
||||
return r0
|
||||
}
|
||||
|
||||
// NewDirectoryContentFormatter creates a new instance of DirectoryContentFormatter. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations.
|
||||
// The first argument is typically a *testing.T value.
|
||||
func NewDirectoryContentFormatter(t interface {
|
||||
mock.TestingT
|
||||
Cleanup(func())
|
||||
}) *DirectoryContentFormatter {
|
||||
mock := &DirectoryContentFormatter{}
|
||||
mock.Mock.Test(t)
|
||||
|
||||
t.Cleanup(func() { mock.AssertExpectations(t) })
|
||||
|
||||
return mock
|
||||
}
|
|
@ -0,0 +1,81 @@
|
|||
// Code generated by mockery v2.43.2. DO NOT EDIT.
|
||||
|
||||
package mocks
|
||||
|
||||
import (
|
||||
io "io"
|
||||
|
||||
mock "github.com/stretchr/testify/mock"
|
||||
)
|
||||
|
||||
// Logger is an autogenerated mock type for the Logger type
|
||||
type Logger struct {
|
||||
mock.Mock
|
||||
}
|
||||
|
||||
// Debug provides a mock function with given fields: a
|
||||
func (_m *Logger) Debug(a ...interface{}) {
|
||||
var _ca []interface{}
|
||||
_ca = append(_ca, a...)
|
||||
_m.Called(_ca...)
|
||||
}
|
||||
|
||||
// DebugWriter provides a mock function with given fields:
|
||||
func (_m *Logger) DebugWriter() io.Writer {
|
||||
ret := _m.Called()
|
||||
|
||||
if len(ret) == 0 {
|
||||
panic("no return value specified for DebugWriter")
|
||||
}
|
||||
|
||||
var r0 io.Writer
|
||||
if rf, ok := ret.Get(0).(func() io.Writer); ok {
|
||||
r0 = rf()
|
||||
} else {
|
||||
if ret.Get(0) != nil {
|
||||
r0 = ret.Get(0).(io.Writer)
|
||||
}
|
||||
}
|
||||
|
||||
return r0
|
||||
}
|
||||
|
||||
// Debugf provides a mock function with given fields: format, a
|
||||
func (_m *Logger) Debugf(format string, a ...interface{}) {
|
||||
var _ca []interface{}
|
||||
_ca = append(_ca, format)
|
||||
_ca = append(_ca, a...)
|
||||
_m.Called(_ca...)
|
||||
}
|
||||
|
||||
// IsDebugEnabled provides a mock function with given fields:
|
||||
func (_m *Logger) IsDebugEnabled() bool {
|
||||
ret := _m.Called()
|
||||
|
||||
if len(ret) == 0 {
|
||||
panic("no return value specified for IsDebugEnabled")
|
||||
}
|
||||
|
||||
var r0 bool
|
||||
if rf, ok := ret.Get(0).(func() bool); ok {
|
||||
r0 = rf()
|
||||
} else {
|
||||
r0 = ret.Get(0).(bool)
|
||||
}
|
||||
|
||||
return r0
|
||||
}
|
||||
|
||||
// NewLogger creates a new instance of Logger. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations.
|
||||
// The first argument is typically a *testing.T value.
|
||||
func NewLogger(t interface {
|
||||
mock.TestingT
|
||||
Cleanup(func())
|
||||
}) *Logger {
|
||||
mock := &Logger{}
|
||||
mock.Mock.Test(t)
|
||||
|
||||
t.Cleanup(func() { mock.AssertExpectations(t) })
|
||||
|
||||
return mock
|
||||
}
|
37
main.go
37
main.go
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
* Copyright 2018-2020 the original author or authors.
|
||||
* Copyright 2018-2023 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.
|
||||
|
@ -18,37 +18,38 @@ package libcnb
|
|||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
|
||||
"github.com/buildpacks/libcnb/internal"
|
||||
)
|
||||
|
||||
// Main is called by the main function of a buildpack, encapsulating both detection and build in the same binary.
|
||||
func Main(detector Detector, builder Builder, options ...Option) {
|
||||
config := Config{
|
||||
arguments: os.Args,
|
||||
environmentWriter: internal.EnvironmentWriter{},
|
||||
exitHandler: internal.NewExitHandler(),
|
||||
tomlWriter: internal.TOMLWriter{},
|
||||
}
|
||||
|
||||
for _, option := range options {
|
||||
config = option(config)
|
||||
}
|
||||
func main(detect DetectFunc, build BuildFunc, generate GenerateFunc, options ...Option) {
|
||||
config := NewConfig(options...)
|
||||
|
||||
if len(config.arguments) == 0 {
|
||||
config.exitHandler.Error(fmt.Errorf("expected command name"))
|
||||
return
|
||||
}
|
||||
|
||||
config.extension = build == nil && generate != nil
|
||||
|
||||
switch c := filepath.Base(config.arguments[0]); c {
|
||||
case "build":
|
||||
Build(builder, options...)
|
||||
Build(build, config)
|
||||
case "detect":
|
||||
Detect(detector, options...)
|
||||
Detect(detect, config)
|
||||
case "generate":
|
||||
Generate(generate, config)
|
||||
default:
|
||||
config.exitHandler.Error(fmt.Errorf("unsupported command %s", c))
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// BuildpackMain is called by the main function of a buildpack, encapsulating both detection and build in the same binary.
|
||||
func BuildpackMain(detect DetectFunc, build BuildFunc, options ...Option) {
|
||||
main(detect, build, nil, options...)
|
||||
}
|
||||
|
||||
// ExtensionMain is called by the main function of a extension, encapsulating both detection and generation in the same binary.
|
||||
func ExtensionMain(detect DetectFunc, generate GenerateFunc, options ...Option) {
|
||||
main(detect, nil, generate, options...)
|
||||
}
|
||||
|
|
116
main_test.go
116
main_test.go
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
* Copyright 2018-2020 the original author or authors.
|
||||
* Copyright 2018-2023 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.
|
||||
|
@ -17,7 +17,6 @@
|
|||
package libcnb_test
|
||||
|
||||
import (
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"testing"
|
||||
|
@ -26,22 +25,23 @@ import (
|
|||
"github.com/sclevine/spec"
|
||||
"github.com/stretchr/testify/mock"
|
||||
|
||||
"github.com/buildpacks/libcnb"
|
||||
"github.com/buildpacks/libcnb/mocks"
|
||||
"github.com/buildpacks/libcnb/v2"
|
||||
"github.com/buildpacks/libcnb/v2/log"
|
||||
"github.com/buildpacks/libcnb/v2/mocks"
|
||||
)
|
||||
|
||||
func testMain(t *testing.T, context spec.G, it spec.S) {
|
||||
func testMain(t *testing.T, _ spec.G, it spec.S) {
|
||||
var (
|
||||
Expect = NewWithT(t).Expect
|
||||
|
||||
applicationPath string
|
||||
builder *mocks.Builder
|
||||
buildFunc libcnb.BuildFunc
|
||||
buildpackPath string
|
||||
buildpackPlanPath string
|
||||
buildPlanPath string
|
||||
detector *mocks.Detector
|
||||
detectFunc libcnb.DetectFunc
|
||||
environmentWriter *mocks.EnvironmentWriter
|
||||
exitHandler *mocks.ExitHandler
|
||||
generateFunc libcnb.GenerateFunc
|
||||
layersPath string
|
||||
platformPath string
|
||||
tomlWriter *mocks.TOMLWriter
|
||||
|
@ -52,20 +52,22 @@ func testMain(t *testing.T, context spec.G, it spec.S) {
|
|||
it.Before(func() {
|
||||
var err error
|
||||
|
||||
applicationPath, err = ioutil.TempDir("", "main-application-path")
|
||||
applicationPath, err = os.MkdirTemp("", "main-application-path")
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
applicationPath, err = filepath.EvalSymlinks(applicationPath)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
builder = &mocks.Builder{}
|
||||
buildFunc = func(libcnb.BuildContext) (libcnb.BuildResult, error) {
|
||||
return libcnb.NewBuildResult(), nil
|
||||
}
|
||||
|
||||
buildpackPath, err = ioutil.TempDir("", "main-buildpack-path")
|
||||
buildpackPath, err = os.MkdirTemp("", "main-buildpack-path")
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
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(`
|
||||
api = "0.6"
|
||||
api = "0.8"
|
||||
|
||||
[buildpack]
|
||||
id = "test-id"
|
||||
|
@ -81,7 +83,6 @@ optional = true
|
|||
|
||||
[[stacks]]
|
||||
id = "test-id"
|
||||
mixins = ["test-name"]
|
||||
|
||||
[metadata]
|
||||
test-key = "test-value"
|
||||
|
@ -89,12 +90,12 @@ test-key = "test-value"
|
|||
0600),
|
||||
).To(Succeed())
|
||||
|
||||
f, err := ioutil.TempFile("", "main-buildpackplan-path")
|
||||
f, err := os.CreateTemp("", "main-buildpackplan-path")
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
Expect(f.Close()).NotTo(HaveOccurred())
|
||||
buildpackPlanPath = f.Name()
|
||||
|
||||
Expect(ioutil.WriteFile(buildpackPlanPath,
|
||||
Expect(os.WriteFile(buildpackPlanPath,
|
||||
[]byte(`
|
||||
[[entries]]
|
||||
name = "test-name"
|
||||
|
@ -106,12 +107,13 @@ test-key = "test-value"
|
|||
0600),
|
||||
).To(Succeed())
|
||||
|
||||
f, err = ioutil.TempFile("", "main-buildplan-path")
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
Expect(f.Close()).NotTo(HaveOccurred())
|
||||
buildPlanPath = f.Name()
|
||||
detectFunc = func(libcnb.DetectContext) (libcnb.DetectResult, error) {
|
||||
return libcnb.DetectResult{}, nil
|
||||
}
|
||||
|
||||
detector = &mocks.Detector{}
|
||||
generateFunc = func(libcnb.GenerateContext) (libcnb.GenerateResult, error) {
|
||||
return libcnb.GenerateResult{}, nil
|
||||
}
|
||||
|
||||
environmentWriter = &mocks.EnvironmentWriter{}
|
||||
environmentWriter.On("Write", mock.Anything, mock.Anything).Return(nil)
|
||||
|
@ -121,10 +123,10 @@ test-key = "test-value"
|
|||
exitHandler.On("Pass", mock.Anything)
|
||||
exitHandler.On("Fail", mock.Anything)
|
||||
|
||||
layersPath, err = ioutil.TempDir("", "main-layers-path")
|
||||
layersPath, err = os.MkdirTemp("", "main-layers-path")
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
Expect(ioutil.WriteFile(filepath.Join(layersPath, "store.toml"),
|
||||
Expect(os.WriteFile(filepath.Join(layersPath, "store.toml"),
|
||||
[]byte(`
|
||||
[metadata]
|
||||
test-key = "test-value"
|
||||
|
@ -132,30 +134,34 @@ test-key = "test-value"
|
|||
0600),
|
||||
).To(Succeed())
|
||||
|
||||
platformPath, err = ioutil.TempDir("", "main-platform-path")
|
||||
platformPath, err = os.MkdirTemp("", "main-platform-path")
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
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"),
|
||||
[]byte("test-metadata-value"),
|
||||
0600,
|
||||
)).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"),
|
||||
[]byte("test-secret-value"),
|
||||
0600,
|
||||
)).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())
|
||||
|
||||
tomlWriter = &mocks.TOMLWriter{}
|
||||
tomlWriter.On("Write", mock.Anything, mock.Anything).Return(nil)
|
||||
|
||||
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())
|
||||
Expect(os.Setenv("CNB_BUILD_PLAN_PATH", buildpackPlanPath)).To(Succeed())
|
||||
|
||||
workingDir, err = os.Getwd()
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
@ -166,6 +172,10 @@ test-key = "test-value"
|
|||
Expect(os.Chdir(workingDir)).To(Succeed())
|
||||
Expect(os.Unsetenv("CNB_BUILDPACK_DIR")).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.Unsetenv("CNB_BUILD_PLAN_PATH")).To(Succeed())
|
||||
|
||||
Expect(os.RemoveAll(applicationPath)).To(Succeed())
|
||||
Expect(os.RemoveAll(buildpackPath)).To(Succeed())
|
||||
|
@ -175,55 +185,78 @@ test-key = "test-value"
|
|||
})
|
||||
|
||||
it("encounters the wrong number of arguments", func() {
|
||||
libcnb.Main(detector, builder,
|
||||
libcnb.BuildpackMain(detectFunc, buildFunc,
|
||||
libcnb.WithArguments([]string{}),
|
||||
libcnb.WithExitHandler(exitHandler),
|
||||
libcnb.WithLogger(log.NewDiscard()),
|
||||
)
|
||||
|
||||
Expect(exitHandler.Calls[0].Arguments.Get(0)).To(MatchError("expected command name"))
|
||||
})
|
||||
|
||||
it("calls builder for build command", func() {
|
||||
builder.On("Build", mock.Anything).Return(libcnb.NewBuildResult(), nil)
|
||||
commandPath := filepath.Join("bin", "build")
|
||||
|
||||
libcnb.Main(detector, builder,
|
||||
libcnb.WithArguments([]string{commandPath, layersPath, platformPath, buildpackPlanPath}),
|
||||
libcnb.BuildpackMain(detectFunc, buildFunc,
|
||||
libcnb.WithArguments([]string{commandPath}),
|
||||
libcnb.WithExitHandler(exitHandler),
|
||||
libcnb.WithLogger(log.NewDiscard()),
|
||||
)
|
||||
|
||||
Expect(exitHandler.Calls).To(BeEmpty())
|
||||
})
|
||||
|
||||
it("calls detector for detect command", func() {
|
||||
detector.On("Detect", mock.Anything).Return(libcnb.DetectResult{Pass: true}, nil)
|
||||
detectFunc = func(libcnb.DetectContext) (libcnb.DetectResult, error) {
|
||||
return libcnb.DetectResult{Pass: true}, nil
|
||||
}
|
||||
commandPath := filepath.Join("bin", "detect")
|
||||
|
||||
libcnb.Main(detector, builder,
|
||||
libcnb.WithArguments([]string{commandPath, platformPath, buildPlanPath}),
|
||||
libcnb.BuildpackMain(detectFunc, buildFunc,
|
||||
libcnb.WithArguments([]string{commandPath}),
|
||||
libcnb.WithExitHandler(exitHandler),
|
||||
libcnb.WithLogger(log.NewDiscard()),
|
||||
)
|
||||
})
|
||||
|
||||
it("calls generator for generate command", func() {
|
||||
generateFunc = func(libcnb.GenerateContext) (libcnb.GenerateResult, error) {
|
||||
return libcnb.GenerateResult{}, nil
|
||||
}
|
||||
commandPath := filepath.Join("bin", "generate")
|
||||
|
||||
libcnb.ExtensionMain(nil, generateFunc,
|
||||
libcnb.WithArguments([]string{commandPath}),
|
||||
libcnb.WithExitHandler(exitHandler),
|
||||
libcnb.WithLogger(log.NewDiscard()),
|
||||
)
|
||||
})
|
||||
|
||||
it("calls exitHandler.Pass() on detection pass", func() {
|
||||
detector.On("Detect", mock.Anything).Return(libcnb.DetectResult{Pass: true}, nil)
|
||||
detectFunc = func(libcnb.DetectContext) (libcnb.DetectResult, error) {
|
||||
return libcnb.DetectResult{Pass: true}, nil
|
||||
}
|
||||
commandPath := filepath.Join("bin", "detect")
|
||||
|
||||
libcnb.Main(detector, builder,
|
||||
libcnb.WithArguments([]string{commandPath, platformPath, buildPlanPath}),
|
||||
libcnb.BuildpackMain(detectFunc, buildFunc,
|
||||
libcnb.WithArguments([]string{commandPath}),
|
||||
libcnb.WithExitHandler(exitHandler),
|
||||
libcnb.WithLogger(log.NewDiscard()),
|
||||
)
|
||||
|
||||
Expect(exitHandler.Calls[0].Method).To(BeIdenticalTo("Pass"))
|
||||
})
|
||||
|
||||
it("calls exitHandler.Fail() on detection fail", func() {
|
||||
detector.On("Detect", mock.Anything).Return(libcnb.DetectResult{Pass: false}, nil)
|
||||
detectFunc = func(libcnb.DetectContext) (libcnb.DetectResult, error) {
|
||||
return libcnb.DetectResult{Pass: false}, nil
|
||||
}
|
||||
commandPath := filepath.Join("bin", "detect")
|
||||
|
||||
libcnb.Main(detector, builder,
|
||||
libcnb.WithArguments([]string{commandPath, platformPath, buildPlanPath}),
|
||||
libcnb.BuildpackMain(detectFunc, buildFunc,
|
||||
libcnb.WithArguments([]string{commandPath}),
|
||||
libcnb.WithExitHandler(exitHandler),
|
||||
libcnb.WithLogger(log.NewDiscard()),
|
||||
)
|
||||
|
||||
Expect(exitHandler.Calls[0].Method).To(BeIdenticalTo("Fail"))
|
||||
|
@ -232,9 +265,10 @@ test-key = "test-value"
|
|||
it("encounters an unknown command", func() {
|
||||
commandPath := filepath.Join("bin", "test-command")
|
||||
|
||||
libcnb.Main(detector, builder,
|
||||
libcnb.BuildpackMain(detectFunc, buildFunc,
|
||||
libcnb.WithArguments([]string{commandPath}),
|
||||
libcnb.WithExitHandler(exitHandler),
|
||||
libcnb.WithLogger(log.NewDiscard()),
|
||||
)
|
||||
|
||||
Expect(exitHandler.Calls[0].Arguments.Get(0)).To(MatchError("unsupported command test-command"))
|
||||
|
|
|
@ -1,35 +0,0 @@
|
|||
// Code generated by mockery v1.0.0. DO NOT EDIT.
|
||||
|
||||
package mocks
|
||||
|
||||
import (
|
||||
mock "github.com/stretchr/testify/mock"
|
||||
|
||||
libcnb "github.com/buildpacks/libcnb"
|
||||
)
|
||||
|
||||
// Builder is an autogenerated mock type for the Builder type
|
||||
type Builder struct {
|
||||
mock.Mock
|
||||
}
|
||||
|
||||
// Build provides a mock function with given fields: context
|
||||
func (_m *Builder) Build(context libcnb.BuildContext) (libcnb.BuildResult, error) {
|
||||
ret := _m.Called(context)
|
||||
|
||||
var r0 libcnb.BuildResult
|
||||
if rf, ok := ret.Get(0).(func(libcnb.BuildContext) libcnb.BuildResult); ok {
|
||||
r0 = rf(context)
|
||||
} else {
|
||||
r0 = ret.Get(0).(libcnb.BuildResult)
|
||||
}
|
||||
|
||||
var r1 error
|
||||
if rf, ok := ret.Get(1).(func(libcnb.BuildContext) error); ok {
|
||||
r1 = rf(context)
|
||||
} else {
|
||||
r1 = ret.Error(1)
|
||||
}
|
||||
|
||||
return r0, r1
|
||||
}
|
|
@ -1,35 +0,0 @@
|
|||
// Code generated by mockery v1.0.0. DO NOT EDIT.
|
||||
|
||||
package mocks
|
||||
|
||||
import (
|
||||
mock "github.com/stretchr/testify/mock"
|
||||
|
||||
libcnb "github.com/buildpacks/libcnb"
|
||||
)
|
||||
|
||||
// Detector is an autogenerated mock type for the Detector type
|
||||
type Detector struct {
|
||||
mock.Mock
|
||||
}
|
||||
|
||||
// Detect provides a mock function with given fields: context
|
||||
func (_m *Detector) Detect(context libcnb.DetectContext) (libcnb.DetectResult, error) {
|
||||
ret := _m.Called(context)
|
||||
|
||||
var r0 libcnb.DetectResult
|
||||
if rf, ok := ret.Get(0).(func(libcnb.DetectContext) libcnb.DetectResult); ok {
|
||||
r0 = rf(context)
|
||||
} else {
|
||||
r0 = ret.Get(0).(libcnb.DetectResult)
|
||||
}
|
||||
|
||||
var r1 error
|
||||
if rf, ok := ret.Get(1).(func(libcnb.DetectContext) error); ok {
|
||||
r1 = rf(context)
|
||||
} else {
|
||||
r1 = ret.Error(1)
|
||||
}
|
||||
|
||||
return r0, r1
|
||||
}
|
|
@ -1,4 +1,4 @@
|
|||
// Code generated by mockery v1.0.0. DO NOT EDIT.
|
||||
// Code generated by mockery v2.43.2. DO NOT EDIT.
|
||||
|
||||
package mocks
|
||||
|
||||
|
@ -13,6 +13,10 @@ type EnvironmentWriter struct {
|
|||
func (_m *EnvironmentWriter) Write(dir string, environment map[string]string) error {
|
||||
ret := _m.Called(dir, environment)
|
||||
|
||||
if len(ret) == 0 {
|
||||
panic("no return value specified for Write")
|
||||
}
|
||||
|
||||
var r0 error
|
||||
if rf, ok := ret.Get(0).(func(string, map[string]string) error); ok {
|
||||
r0 = rf(dir, environment)
|
||||
|
@ -22,3 +26,17 @@ func (_m *EnvironmentWriter) Write(dir string, environment map[string]string) er
|
|||
|
||||
return r0
|
||||
}
|
||||
|
||||
// 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.
|
||||
// The first argument is typically a *testing.T value.
|
||||
func NewEnvironmentWriter(t interface {
|
||||
mock.TestingT
|
||||
Cleanup(func())
|
||||
}) *EnvironmentWriter {
|
||||
mock := &EnvironmentWriter{}
|
||||
mock.Mock.Test(t)
|
||||
|
||||
t.Cleanup(func() { mock.AssertExpectations(t) })
|
||||
|
||||
return mock
|
||||
}
|
||||
|
|
|
@ -0,0 +1,54 @@
|
|||
// Code generated by mockery v2.43.2. DO NOT EDIT.
|
||||
|
||||
package mocks
|
||||
|
||||
import mock "github.com/stretchr/testify/mock"
|
||||
|
||||
// ExecD is an autogenerated mock type for the ExecD type
|
||||
type ExecD struct {
|
||||
mock.Mock
|
||||
}
|
||||
|
||||
// Execute provides a mock function with given fields:
|
||||
func (_m *ExecD) Execute() (map[string]string, error) {
|
||||
ret := _m.Called()
|
||||
|
||||
if len(ret) == 0 {
|
||||
panic("no return value specified for Execute")
|
||||
}
|
||||
|
||||
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 {
|
||||
r0 = rf()
|
||||
} else {
|
||||
if ret.Get(0) != nil {
|
||||
r0 = ret.Get(0).(map[string]string)
|
||||
}
|
||||
}
|
||||
|
||||
if rf, ok := ret.Get(1).(func() error); ok {
|
||||
r1 = rf()
|
||||
} else {
|
||||
r1 = ret.Error(1)
|
||||
}
|
||||
|
||||
return r0, r1
|
||||
}
|
||||
|
||||
// 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.
|
||||
// The first argument is typically a *testing.T value.
|
||||
func NewExecD(t interface {
|
||||
mock.TestingT
|
||||
Cleanup(func())
|
||||
}) *ExecD {
|
||||
mock := &ExecD{}
|
||||
mock.Mock.Test(t)
|
||||
|
||||
t.Cleanup(func() { mock.AssertExpectations(t) })
|
||||
|
||||
return mock
|
||||
}
|
|
@ -0,0 +1,42 @@
|
|||
// Code generated by mockery v2.43.2. DO NOT EDIT.
|
||||
|
||||
package mocks
|
||||
|
||||
import mock "github.com/stretchr/testify/mock"
|
||||
|
||||
// ExecDWriter is an autogenerated mock type for the ExecDWriter type
|
||||
type ExecDWriter struct {
|
||||
mock.Mock
|
||||
}
|
||||
|
||||
// Write provides a mock function with given fields: value
|
||||
func (_m *ExecDWriter) Write(value map[string]string) error {
|
||||
ret := _m.Called(value)
|
||||
|
||||
if len(ret) == 0 {
|
||||
panic("no return value specified for Write")
|
||||
}
|
||||
|
||||
var r0 error
|
||||
if rf, ok := ret.Get(0).(func(map[string]string) error); ok {
|
||||
r0 = rf(value)
|
||||
} else {
|
||||
r0 = ret.Error(0)
|
||||
}
|
||||
|
||||
return r0
|
||||
}
|
||||
|
||||
// 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.
|
||||
// The first argument is typically a *testing.T value.
|
||||
func NewExecDWriter(t interface {
|
||||
mock.TestingT
|
||||
Cleanup(func())
|
||||
}) *ExecDWriter {
|
||||
mock := &ExecDWriter{}
|
||||
mock.Mock.Test(t)
|
||||
|
||||
t.Cleanup(func() { mock.AssertExpectations(t) })
|
||||
|
||||
return mock
|
||||
}
|
|
@ -1,4 +1,4 @@
|
|||
// Code generated by mockery v1.0.0. DO NOT EDIT.
|
||||
// Code generated by mockery v2.43.2. DO NOT EDIT.
|
||||
|
||||
package mocks
|
||||
|
||||
|
@ -23,3 +23,17 @@ func (_m *ExitHandler) Fail() {
|
|||
func (_m *ExitHandler) Pass() {
|
||||
_m.Called()
|
||||
}
|
||||
|
||||
// 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.
|
||||
// The first argument is typically a *testing.T value.
|
||||
func NewExitHandler(t interface {
|
||||
mock.TestingT
|
||||
Cleanup(func())
|
||||
}) *ExitHandler {
|
||||
mock := &ExitHandler{}
|
||||
mock.Mock.Test(t)
|
||||
|
||||
t.Cleanup(func() { mock.AssertExpectations(t) })
|
||||
|
||||
return mock
|
||||
}
|
||||
|
|
|
@ -1,49 +0,0 @@
|
|||
// Code generated by mockery v1.0.0. DO NOT EDIT.
|
||||
|
||||
package mocks
|
||||
|
||||
import (
|
||||
mock "github.com/stretchr/testify/mock"
|
||||
|
||||
libcnb "github.com/buildpacks/libcnb"
|
||||
)
|
||||
|
||||
// LayerContributor is an autogenerated mock type for the LayerContributor type
|
||||
type LayerContributor struct {
|
||||
mock.Mock
|
||||
}
|
||||
|
||||
// Contribute provides a mock function with given fields: _a0
|
||||
func (_m *LayerContributor) Contribute(_a0 libcnb.Layer) (libcnb.Layer, error) {
|
||||
ret := _m.Called(_a0)
|
||||
|
||||
var r0 libcnb.Layer
|
||||
if rf, ok := ret.Get(0).(func(libcnb.Layer) libcnb.Layer); ok {
|
||||
r0 = rf(_a0)
|
||||
} else {
|
||||
r0 = ret.Get(0).(libcnb.Layer)
|
||||
}
|
||||
|
||||
var r1 error
|
||||
if rf, ok := ret.Get(1).(func(libcnb.Layer) error); ok {
|
||||
r1 = rf(_a0)
|
||||
} else {
|
||||
r1 = ret.Error(1)
|
||||
}
|
||||
|
||||
return r0, r1
|
||||
}
|
||||
|
||||
// Name provides a mock function with given fields:
|
||||
func (_m *LayerContributor) Name() string {
|
||||
ret := _m.Called()
|
||||
|
||||
var r0 string
|
||||
if rf, ok := ret.Get(0).(func() string); ok {
|
||||
r0 = rf()
|
||||
} else {
|
||||
r0 = ret.Get(0).(string)
|
||||
}
|
||||
|
||||
return r0
|
||||
}
|
|
@ -1,4 +1,4 @@
|
|||
// Code generated by mockery v1.0.0. DO NOT EDIT.
|
||||
// Code generated by mockery v2.43.2. DO NOT EDIT.
|
||||
|
||||
package mocks
|
||||
|
||||
|
@ -13,6 +13,10 @@ type TOMLWriter struct {
|
|||
func (_m *TOMLWriter) Write(path string, value interface{}) error {
|
||||
ret := _m.Called(path, value)
|
||||
|
||||
if len(ret) == 0 {
|
||||
panic("no return value specified for Write")
|
||||
}
|
||||
|
||||
var r0 error
|
||||
if rf, ok := ret.Get(0).(func(string, interface{}) error); ok {
|
||||
r0 = rf(path, value)
|
||||
|
@ -22,3 +26,17 @@ func (_m *TOMLWriter) Write(path string, value interface{}) error {
|
|||
|
||||
return r0
|
||||
}
|
||||
|
||||
// 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.
|
||||
// The first argument is typically a *testing.T value.
|
||||
func NewTOMLWriter(t interface {
|
||||
mock.TestingT
|
||||
Cleanup(func())
|
||||
}) *TOMLWriter {
|
||||
mock := &TOMLWriter{}
|
||||
mock.Mock.Test(t)
|
||||
|
||||
t.Cleanup(func() { mock.AssertExpectations(t) })
|
||||
|
||||
return mock
|
||||
}
|
||||
|
|
192
platform.go
192
platform.go
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
* Copyright 2018-2020 the original author or authors.
|
||||
* Copyright 2018-2023 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.
|
||||
|
@ -17,19 +17,19 @@
|
|||
package libcnb
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io/fs"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"sort"
|
||||
"strings"
|
||||
|
||||
"github.com/buildpacks/libcnb/internal"
|
||||
"github.com/buildpacks/libcnb/v2/internal"
|
||||
)
|
||||
|
||||
const (
|
||||
// BindingKind is the metadata key for a binding's kind.
|
||||
BindingKind = "kind"
|
||||
|
||||
// BindingProvider is the key for a binding's provider.
|
||||
BindingProvider = "provider"
|
||||
|
||||
|
@ -41,11 +41,52 @@ const (
|
|||
// See the Service Binding Specification for Kubernetes for more details - https://k8s-service-bindings.github.io/spec/
|
||||
EnvServiceBindings = "SERVICE_BINDING_ROOT"
|
||||
|
||||
// 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
|
||||
// Deprecated: Use the Service Binding Specification for Kubernetes instead -
|
||||
// https://github.com/buildpacks/rfcs/blob/main/text/0055-deprecate-service-bindings.md.
|
||||
EnvCNBBindings = "CNB_BINDINGS"
|
||||
// EnvBuildpackDirectory is the name of the environment variable that contains the path to the buildpack
|
||||
EnvBuildpackDirectory = "CNB_BUILDPACK_DIR"
|
||||
|
||||
// EnvExtensionDirectory is the name of the environment variable that contains the path to the extension
|
||||
EnvExtensionDirectory = "CNB_EXTENSION_DIR"
|
||||
|
||||
// EnvVcapServices is the name of the environment variable that contains the bindings in cloudfoundry
|
||||
EnvVcapServices = "VCAP_SERVICES"
|
||||
|
||||
// EnvLayersDirectory is the name of the environment variable that contains the root path to all buildpack layers
|
||||
EnvLayersDirectory = "CNB_LAYERS_DIR"
|
||||
|
||||
// EnvOutputDirectory is the name of the environment variable that contains the path to the output directory
|
||||
EnvOutputDirectory = "CNB_OUTPUT_DIR"
|
||||
|
||||
// EnvPlatformDirectory is the name of the environment variable that contains the path to the platform directory
|
||||
EnvPlatformDirectory = "CNB_PLATFORM_DIR"
|
||||
|
||||
// EnvDetectBuildPlanPath is the name of the environment variable that contains the path to the build plan
|
||||
EnvDetectPlanPath = "CNB_BUILD_PLAN_PATH"
|
||||
|
||||
// EnvBuildPlanPath is the name of the environment variable that contains the path to the build plan
|
||||
EnvBuildPlanPath = "CNB_BP_PLAN_PATH"
|
||||
|
||||
// Deprecated: EnvStackID is the name of the environment variable that contains the stack id
|
||||
EnvStackID = "CNB_STACK_ID"
|
||||
|
||||
// EnvTargetOS contains the name of the os
|
||||
EnvTargetOS = "CNB_TARGET_OS"
|
||||
|
||||
// EnvTargetArch contains the architecture
|
||||
EnvTargetArch = "CNB_TARGET_ARCH"
|
||||
|
||||
// EnvTargetOS contains the variant of the architecture
|
||||
EnvTargetArchVariant = "CNB_TARGET_ARCH_VARIANT"
|
||||
|
||||
// EnvTargetDistroName contains the name of the ditro
|
||||
EnvTargetDistroName = "CNB_TARGET_DISTRO_NAME"
|
||||
|
||||
// EnvTargetDistroVersion contains the version of the distro
|
||||
EnvTargetDistroVersion = "CNB_TARGET_DISTRO_VERSION"
|
||||
|
||||
// DefaultPlatformBindingsLocation is the typical location for bindings, which exists under the platform directory
|
||||
//
|
||||
// Not guaranteed to exist, but often does. This should only be used as a fallback if EnvServiceBindings and EnvPlatformDirectory are not set
|
||||
DefaultPlatformBindingsLocation = "/platform/bindings"
|
||||
)
|
||||
|
||||
// Binding is a projection of metadata about an external entity to be bound to.
|
||||
|
@ -77,7 +118,7 @@ func NewBinding(name string, path string, secret map[string]string) Binding {
|
|||
|
||||
for k, v := range secret {
|
||||
switch k {
|
||||
case BindingType, BindingKind: // TODO: Remove as CNB_BINDINGS ages out
|
||||
case BindingType:
|
||||
b.Type = strings.TrimSpace(v)
|
||||
case BindingProvider:
|
||||
b.Provider = strings.TrimSpace(v)
|
||||
|
@ -96,19 +137,6 @@ func NewBindingFromPath(path string) (Binding, error) {
|
|||
return Binding{}, fmt.Errorf("unable to create new config map from %s\n%w", path, err)
|
||||
}
|
||||
|
||||
// TODO: Remove as CNB_BINDINGS ages out
|
||||
for _, d := range []string{"metadata", "secret"} {
|
||||
file := filepath.Join(path, d)
|
||||
cm, err := internal.NewConfigMapFromPath(file)
|
||||
if err != nil {
|
||||
return Binding{}, fmt.Errorf("unable to create new config map from %s\n%w", file, err)
|
||||
}
|
||||
|
||||
for k, v := range cm {
|
||||
secret[k] = v
|
||||
}
|
||||
}
|
||||
|
||||
return NewBinding(filepath.Base(path), path, secret), nil
|
||||
}
|
||||
|
||||
|
@ -129,52 +157,30 @@ func (b Binding) SecretFilePath(name string) (string, bool) {
|
|||
return "", false
|
||||
}
|
||||
|
||||
// TODO: Remove as CNB_BINDINGS ages out
|
||||
for _, d := range []string{"metadata", "secret"} {
|
||||
file := filepath.Join(b.Path, d, name)
|
||||
if _, err := os.Stat(file); err == nil {
|
||||
return file, true
|
||||
}
|
||||
}
|
||||
|
||||
return filepath.Join(b.Path, name), true
|
||||
}
|
||||
|
||||
// Bindings is a collection of bindings keyed by their name.
|
||||
type Bindings []Binding
|
||||
|
||||
// NewBindingsFromEnvironment creates a new bindings from all the bindings at the path defined by $SERVICE_BINDING_ROOT
|
||||
// or $CNB_BINDINGS if it does not exist. If neither is defined, returns an empty collection of Bindings.
|
||||
// Note - This API is deprecated. Please use NewBindingsForLaunch instead.
|
||||
func NewBindingsFromEnvironment() (Bindings, error) {
|
||||
return NewBindingsForLaunch()
|
||||
}
|
||||
|
||||
// NewBindingsForLaunch creates a new bindings from all the bindings at the path defined by $SERVICE_BINDING_ROOT
|
||||
// or $CNB_BINDINGS if it does not exist. If neither is defined, returns an empty collection of Bindings.
|
||||
func NewBindingsForLaunch() (Bindings, error) {
|
||||
if path, ok := os.LookupEnv(EnvServiceBindings); ok {
|
||||
return NewBindingsFromPath(path)
|
||||
}
|
||||
|
||||
// TODO: Remove as CNB_BINDINGS ages out
|
||||
if path, ok := os.LookupEnv(EnvCNBBindings); ok {
|
||||
return NewBindingsFromPath(path)
|
||||
}
|
||||
|
||||
return Bindings{}, nil
|
||||
}
|
||||
|
||||
// NewBindingsFromPath creates a new instance from all the bindings at a given path.
|
||||
func NewBindingsFromPath(path string) (Bindings, error) {
|
||||
files, err := filepath.Glob(filepath.Join(path, "*"))
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("unable to glob %s\n%w", path, err)
|
||||
files, err := os.ReadDir(path)
|
||||
if err != nil && errors.Is(err, fs.ErrNotExist) {
|
||||
return Bindings{}, nil
|
||||
} else if err != nil {
|
||||
return nil, fmt.Errorf("unable to list directory %s\n%w", path, err)
|
||||
}
|
||||
|
||||
bindings := Bindings{}
|
||||
for _, file := range files {
|
||||
binding, err := NewBindingFromPath(file)
|
||||
bindingPath := filepath.Join(path, file.Name())
|
||||
|
||||
if strings.HasPrefix(filepath.Base(bindingPath), ".") {
|
||||
// ignore hidden files
|
||||
continue
|
||||
}
|
||||
binding, err := NewBindingFromPath(bindingPath)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("unable to create new binding from %s\n%w", file, err)
|
||||
}
|
||||
|
@ -185,17 +191,73 @@ func NewBindingsFromPath(path string) (Bindings, error) {
|
|||
return bindings, nil
|
||||
}
|
||||
|
||||
// NewBindingsForBuild creates a new bindings from all the bindings at the path defined by $SERVICE_BINDING_ROOT
|
||||
// or $CNB_BINDINGS if it does not exist. If neither is defined, bindings are read from <platform>/bindings, the default
|
||||
// path defined in the CNB Binding extension specification.
|
||||
func NewBindingsForBuild(platformDir string) (Bindings, error) {
|
||||
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
|
||||
}
|
||||
|
||||
// NewBindings creates a new bindings from all the bindings at the path defined by $SERVICE_BINDING_ROOT.
|
||||
// If that isn't defined, bindings are read from <platform>/bindings.
|
||||
// If that isn't defined, bindings are read from $VCAP_SERVICES.
|
||||
// If that isn't defined, the specified platform path will be used
|
||||
func NewBindings(platformDir string) (Bindings, error) {
|
||||
if path, ok := os.LookupEnv(EnvServiceBindings); ok {
|
||||
return NewBindingsFromPath(path)
|
||||
}
|
||||
// TODO: Remove as CNB_BINDINGS ages out
|
||||
if path, ok := os.LookupEnv(EnvCNBBindings); ok {
|
||||
return NewBindingsFromPath(path)
|
||||
|
||||
if path, ok := os.LookupEnv(EnvPlatformDirectory); ok {
|
||||
return NewBindingsFromPath(filepath.Join(path, "bindings"))
|
||||
}
|
||||
|
||||
if content, ok := os.LookupEnv(EnvVcapServices); ok {
|
||||
return NewBindingsFromVcapServicesEnv(content)
|
||||
}
|
||||
|
||||
return NewBindingsFromPath(filepath.Join(platformDir, "bindings"))
|
||||
}
|
||||
|
||||
|
|
329
platform_test.go
329
platform_test.go
|
@ -18,7 +18,6 @@ package libcnb_test
|
|||
|
||||
import (
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"testing"
|
||||
|
@ -26,7 +25,7 @@ import (
|
|||
. "github.com/onsi/gomega"
|
||||
"github.com/sclevine/spec"
|
||||
|
||||
"github.com/buildpacks/libcnb"
|
||||
"github.com/buildpacks/libcnb/v2"
|
||||
)
|
||||
|
||||
func testPlatform(t *testing.T, context spec.G, it spec.S) {
|
||||
|
@ -38,7 +37,7 @@ func testPlatform(t *testing.T, context spec.G, it spec.S) {
|
|||
|
||||
it.Before(func() {
|
||||
var err error
|
||||
platformPath, err = ioutil.TempDir("", "platform")
|
||||
platformPath, err = os.MkdirTemp("", "platform")
|
||||
path = filepath.Join(platformPath, "bindings")
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
})
|
||||
|
@ -47,164 +46,74 @@ func testPlatform(t *testing.T, context spec.G, it spec.S) {
|
|||
Expect(os.RemoveAll(path)).To(Succeed())
|
||||
})
|
||||
|
||||
context("CNB Bindings", func() {
|
||||
it.Before(func() {
|
||||
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(ioutil.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(ioutil.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(ioutil.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())
|
||||
context("Cloudfoundry VCAP_SERVICES", 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))
|
||||
|
||||
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(ioutil.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(ioutil.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(ioutil.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())
|
||||
})
|
||||
bindings, err := libcnb.NewBindings("")
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
context("Binding", func() {
|
||||
it("creates an empty binding", func() {
|
||||
Expect(libcnb.NewBinding("test-name", "test-path", map[string]string{
|
||||
libcnb.BindingKind: "test-kind",
|
||||
libcnb.BindingProvider: "test-provider",
|
||||
"test-key": "test-value",
|
||||
})).To(Equal(libcnb.Binding{
|
||||
Name: "test-name",
|
||||
Path: "test-path",
|
||||
Type: "test-kind",
|
||||
Provider: "test-provider",
|
||||
Secret: map[string]string{"test-key": "test-value"},
|
||||
}))
|
||||
})
|
||||
|
||||
it("creates a binding from a path", func() {
|
||||
path := filepath.Join(path, "alpha")
|
||||
|
||||
binding, err := libcnb.NewBindingFromPath(path)
|
||||
Expect(binding, err).To(Equal(libcnb.Binding{
|
||||
Name: filepath.Base(path),
|
||||
Path: path,
|
||||
Type: "test-kind",
|
||||
Provider: "test-provider",
|
||||
Expect(bindings).To(ConsistOf(libcnb.Bindings{
|
||||
{
|
||||
Name: "elephantsql-binding-c6c60",
|
||||
Type: "elephantsql-type",
|
||||
Provider: "elephantsql-provider",
|
||||
Secret: map[string]string{
|
||||
"test-metadata-key": "test-metadata-value",
|
||||
"test-metadata-key-trimmed": "test-metadata-value-trimmed",
|
||||
"test-secret-key": "test-secret-value",
|
||||
"test-secret-key-trimmed": "test-secret-value-trimmed",
|
||||
"bool": "true",
|
||||
"int": "1",
|
||||
"uri": "postgres://exampleuser:examplepass@postgres.example.com:5432/exampleuser",
|
||||
},
|
||||
}))
|
||||
|
||||
metadataFilePath, ok := binding.SecretFilePath("test-metadata-key")
|
||||
Expect(ok).To(BeTrue())
|
||||
Expect(metadataFilePath).To(Equal(filepath.Join(path, "metadata", "test-metadata-key")))
|
||||
|
||||
secretFilePath, ok := binding.SecretFilePath("test-secret-key")
|
||||
Expect(ok).To(BeTrue())
|
||||
Expect(secretFilePath).To(Equal(filepath.Join(path, "secret", "test-secret-key")))
|
||||
})
|
||||
|
||||
it("sanitizes secrets", func() {
|
||||
path := filepath.Join(path, "alpha")
|
||||
|
||||
b, err := libcnb.NewBindingFromPath(path)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
Expect(b.String()).To(Equal(fmt.Sprintf("{Name: alpha Path: %s Type: test-kind Provider: test-provider Secret: [test-metadata-key test-metadata-key-trimmed test-secret-key test-secret-key-trimmed]}", path)))
|
||||
})
|
||||
},
|
||||
{
|
||||
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",
|
||||
},
|
||||
},
|
||||
}))
|
||||
})
|
||||
|
||||
context("Bindings", func() {
|
||||
it("creates a bindings from a path", func() {
|
||||
Expect(libcnb.NewBindingsFromPath(path)).To(Equal(libcnb.Bindings{
|
||||
libcnb.Binding{
|
||||
Name: "alpha",
|
||||
Path: filepath.Join(path, "alpha"),
|
||||
Type: "test-kind",
|
||||
Provider: "test-provider",
|
||||
Secret: map[string]string{
|
||||
"test-metadata-key": "test-metadata-value",
|
||||
"test-metadata-key-trimmed": "test-metadata-value-trimmed",
|
||||
"test-secret-key": "test-secret-value",
|
||||
"test-secret-key-trimmed": "test-secret-value-trimmed",
|
||||
},
|
||||
},
|
||||
libcnb.Binding{
|
||||
Name: "bravo",
|
||||
Path: filepath.Join(path, "bravo"),
|
||||
Type: "test-kind",
|
||||
Provider: "test-provider",
|
||||
Secret: map[string]string{
|
||||
"test-metadata-key": "test-metadata-value",
|
||||
"test-metadata-key-trimmed": "test-metadata-value-trimmed",
|
||||
"test-secret-key": "test-secret-value",
|
||||
"test-secret-key-trimmed": "test-secret-value-trimmed",
|
||||
},
|
||||
},
|
||||
}))
|
||||
})
|
||||
it("creates empty bindings from empty VCAP_SERVICES", func() {
|
||||
t.Setenv(libcnb.EnvVcapServices, "{}")
|
||||
|
||||
it("returns empty bindings if environment variable is not set", func() {
|
||||
Expect(libcnb.NewBindingsFromEnvironment()).To(Equal(libcnb.Bindings{}))
|
||||
})
|
||||
bindings, err := libcnb.NewBindings("")
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
context("from environment", func() {
|
||||
it.Before(func() {
|
||||
Expect(os.Setenv(libcnb.EnvCNBBindings, path))
|
||||
})
|
||||
|
||||
it.After(func() {
|
||||
Expect(os.Unsetenv(libcnb.EnvCNBBindings))
|
||||
})
|
||||
|
||||
it("creates bindings from path in $CNB_BINDINGS", func() {
|
||||
Expect(libcnb.NewBindingsFromEnvironment()).To(Equal(libcnb.Bindings{
|
||||
libcnb.Binding{
|
||||
Name: "alpha",
|
||||
Path: filepath.Join(path, "alpha"),
|
||||
Type: "test-kind",
|
||||
Provider: "test-provider",
|
||||
Secret: map[string]string{
|
||||
"test-metadata-key": "test-metadata-value",
|
||||
"test-metadata-key-trimmed": "test-metadata-value-trimmed",
|
||||
"test-secret-key": "test-secret-value",
|
||||
"test-secret-key-trimmed": "test-secret-value-trimmed",
|
||||
},
|
||||
},
|
||||
libcnb.Binding{
|
||||
Name: "bravo",
|
||||
Path: filepath.Join(path, "bravo"),
|
||||
Type: "test-kind",
|
||||
Provider: "test-provider",
|
||||
Secret: map[string]string{
|
||||
"test-metadata-key": "test-metadata-value",
|
||||
"test-metadata-key-trimmed": "test-metadata-value-trimmed",
|
||||
"test-secret-key": "test-secret-value",
|
||||
"test-secret-key-trimmed": "test-secret-value-trimmed",
|
||||
},
|
||||
},
|
||||
}))
|
||||
})
|
||||
})
|
||||
Expect(bindings).To(HaveLen(0))
|
||||
})
|
||||
})
|
||||
|
||||
context("Kubernetes Service Bindings", func() {
|
||||
it.Before(func() {
|
||||
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(ioutil.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", "type"), []byte("test-type"), 0600)).To(Succeed())
|
||||
Expect(os.WriteFile(filepath.Join(path, "alpha", "provider"), []byte("test-provider"), 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(ioutil.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(ioutil.WriteFile(filepath.Join(path, "bravo", "test-secret-key"), []byte("test-secret-value"), 0600)).To(Succeed())
|
||||
Expect(os.WriteFile(filepath.Join(path, "bravo", "type"), []byte("test-type"), 0600)).To(Succeed())
|
||||
Expect(os.WriteFile(filepath.Join(path, "bravo", "provider"), []byte("test-provider"), 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.WriteFile(filepath.Join(path, ".hidden", "metadata", "kind"), []byte("test-kind"), 0600)).To(Succeed())
|
||||
Expect(os.WriteFile(filepath.Join(path, ".hiddenFile"), []byte("test-kind"), 0600)).To(Succeed())
|
||||
})
|
||||
|
||||
context("Binding", func() {
|
||||
|
@ -271,21 +180,46 @@ func testPlatform(t *testing.T, context spec.G, it spec.S) {
|
|||
}))
|
||||
})
|
||||
|
||||
it("returns empty bindings if environment variable is not set", func() {
|
||||
Expect(libcnb.NewBindingsFromEnvironment()).To(Equal(libcnb.Bindings{}))
|
||||
it("creates an empty binding if path does not exist", func() {
|
||||
Expect(libcnb.NewBindingsFromPath("/path/doesnt/exist")).To(Equal(libcnb.Bindings{}))
|
||||
})
|
||||
|
||||
it("returns empty bindings if SERVICE_BINDING_ROOT and CNB_PLATFORM_DIR are not set and /platform/bindings does not exist", func() {
|
||||
Expect(libcnb.NewBindings(libcnb.DefaultPlatformBindingsLocation)).To(Equal(libcnb.Bindings{}))
|
||||
})
|
||||
|
||||
context("from environment", func() {
|
||||
it.Before(func() {
|
||||
Expect(os.Setenv(libcnb.EnvServiceBindings, path))
|
||||
})
|
||||
|
||||
it.After(func() {
|
||||
Expect(os.Unsetenv(libcnb.EnvServiceBindings))
|
||||
Expect(os.Unsetenv("CNB_PLATFORM_DIR"))
|
||||
})
|
||||
|
||||
it("creates bindings from path in SERVICE_BINDING_ROOT", func() {
|
||||
Expect(libcnb.NewBindingsFromEnvironment()).To(Equal(libcnb.Bindings{
|
||||
it("creates bindings from path in SERVICE_BINDING_ROOT if both set", func() {
|
||||
Expect(os.Setenv(libcnb.EnvServiceBindings, path))
|
||||
Expect(os.Setenv("CNB_PLATFORM_DIR", "/does/not/exist"))
|
||||
|
||||
Expect(libcnb.NewBindings(libcnb.DefaultPlatformBindingsLocation)).To(Equal(libcnb.Bindings{
|
||||
libcnb.Binding{
|
||||
Name: "alpha",
|
||||
Path: filepath.Join(path, "alpha"),
|
||||
Type: "test-type",
|
||||
Provider: "test-provider",
|
||||
Secret: map[string]string{"test-secret-key": "test-secret-value"},
|
||||
},
|
||||
libcnb.Binding{
|
||||
Name: "bravo",
|
||||
Path: filepath.Join(path, "bravo"),
|
||||
Type: "test-type",
|
||||
Provider: "test-provider",
|
||||
Secret: map[string]string{"test-secret-key": "test-secret-value"},
|
||||
},
|
||||
}))
|
||||
})
|
||||
|
||||
it("creates bindings from path in SERVICE_BINDING_ROOT if SERVICE_BINDING_ROOT not set", func() {
|
||||
Expect(os.Setenv("CNB_PLATFORM_DIR", filepath.Dir(path)))
|
||||
|
||||
Expect(libcnb.NewBindings(libcnb.DefaultPlatformBindingsLocation)).To(Equal(libcnb.Bindings{
|
||||
libcnb.Binding{
|
||||
Name: "alpha",
|
||||
Path: filepath.Join(path, "alpha"),
|
||||
|
@ -303,95 +237,6 @@ func testPlatform(t *testing.T, context spec.G, it spec.S) {
|
|||
}))
|
||||
})
|
||||
})
|
||||
|
||||
context("from environment or path", func() {
|
||||
context("when SERVICE_BINDING_ROOT is defined but CNB_BINDINGS or the passed path does not exist", func() {
|
||||
it.Before(func() {
|
||||
Expect(os.Setenv(libcnb.EnvServiceBindings, path))
|
||||
Expect(os.Setenv(libcnb.EnvCNBBindings, "does not exist"))
|
||||
})
|
||||
|
||||
it.After(func() {
|
||||
Expect(os.Unsetenv(libcnb.EnvServiceBindings))
|
||||
Expect(os.Unsetenv(libcnb.EnvCNBBindings))
|
||||
})
|
||||
|
||||
it("creates bindings from path in SERVICE_BINDING_ROOT", func() {
|
||||
Expect(libcnb.NewBindingsForBuild("random-path-that-does-not-exist")).To(Equal(libcnb.Bindings{
|
||||
libcnb.Binding{
|
||||
Name: "alpha",
|
||||
Path: filepath.Join(path, "alpha"),
|
||||
Type: "test-type",
|
||||
Provider: "test-provider",
|
||||
Secret: map[string]string{"test-secret-key": "test-secret-value"},
|
||||
},
|
||||
libcnb.Binding{
|
||||
Name: "bravo",
|
||||
Path: filepath.Join(path, "bravo"),
|
||||
Type: "test-type",
|
||||
Provider: "test-provider",
|
||||
Secret: map[string]string{"test-secret-key": "test-secret-value"},
|
||||
},
|
||||
}))
|
||||
})
|
||||
})
|
||||
|
||||
context("when CNB_BINDINGS is defined but the path does not exist", func() {
|
||||
it.Before(func() {
|
||||
Expect(os.Setenv(libcnb.EnvCNBBindings, path))
|
||||
})
|
||||
|
||||
it.After(func() {
|
||||
Expect(os.Unsetenv(libcnb.EnvCNBBindings))
|
||||
})
|
||||
|
||||
it("creates bindings from path in CNB_BINDINGS", func() {
|
||||
Expect(libcnb.NewBindingsForBuild("random-path-that-does-not-exist")).To(Equal(libcnb.Bindings{
|
||||
libcnb.Binding{
|
||||
Name: "alpha",
|
||||
Path: filepath.Join(path, "alpha"),
|
||||
Type: "test-type",
|
||||
Provider: "test-provider",
|
||||
Secret: map[string]string{"test-secret-key": "test-secret-value"},
|
||||
},
|
||||
libcnb.Binding{
|
||||
Name: "bravo",
|
||||
Path: filepath.Join(path, "bravo"),
|
||||
Type: "test-type",
|
||||
Provider: "test-provider",
|
||||
Secret: map[string]string{"test-secret-key": "test-secret-value"},
|
||||
},
|
||||
}))
|
||||
})
|
||||
})
|
||||
|
||||
context("when SERVICE_BINDING_ROOT and CNB_BINDINGS is not defined but the path exists", func() {
|
||||
it("creates bindings from the given path", func() {
|
||||
Expect(libcnb.NewBindingsForBuild(platformPath)).To(Equal(libcnb.Bindings{
|
||||
libcnb.Binding{
|
||||
Name: "alpha",
|
||||
Path: filepath.Join(path, "alpha"),
|
||||
Type: "test-type",
|
||||
Provider: "test-provider",
|
||||
Secret: map[string]string{"test-secret-key": "test-secret-value"},
|
||||
},
|
||||
libcnb.Binding{
|
||||
Name: "bravo",
|
||||
Path: filepath.Join(path, "bravo"),
|
||||
Type: "test-type",
|
||||
Provider: "test-provider",
|
||||
Secret: map[string]string{"test-secret-key": "test-secret-value"},
|
||||
},
|
||||
}))
|
||||
})
|
||||
})
|
||||
|
||||
context("when no valid binding variable is set", func() {
|
||||
it("returns an an empty binding", func() {
|
||||
Expect(libcnb.NewBindingsForBuild("does-not-exist")).To(Equal(libcnb.Bindings{}))
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
}
|
||||
|
|
141
poet/logger.go
141
poet/logger.go
|
@ -1,141 +0,0 @@
|
|||
/*
|
||||
* 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 poet
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// Logger logs messages to a writer.
|
||||
type Logger struct {
|
||||
debug io.Writer
|
||||
info io.Writer
|
||||
}
|
||||
|
||||
// Option is a function that configures a Logger.
|
||||
type Option func(Logger) Logger
|
||||
|
||||
// WithDebug configures the debug Writer.
|
||||
func WithDebug(writer io.Writer) Option {
|
||||
return func(logger Logger) Logger {
|
||||
logger.debug = writer
|
||||
return logger
|
||||
}
|
||||
}
|
||||
|
||||
// NewLoggerWithOptions create a new instance of Logger. It configures the Logger with options.
|
||||
func NewLoggerWithOptions(writer io.Writer, options ...Option) Logger {
|
||||
l := Logger{
|
||||
info: writer,
|
||||
}
|
||||
|
||||
for _, option := range options {
|
||||
l = option(l)
|
||||
}
|
||||
|
||||
return l
|
||||
}
|
||||
|
||||
// NewLogger creates a new instance of Logger. It configures debug logging if $BP_DEBUG is set.
|
||||
func NewLogger(writer io.Writer) Logger {
|
||||
var options []Option
|
||||
|
||||
if _, ok := os.LookupEnv("BP_DEBUG"); ok {
|
||||
options = append(options, WithDebug(writer))
|
||||
}
|
||||
|
||||
return NewLoggerWithOptions(writer, options...)
|
||||
}
|
||||
|
||||
// Debug formats using the default formats for its operands and writes to the configured debug writer. Spaces are added
|
||||
// between operands when neither is a string.
|
||||
func (l Logger) Debug(a ...interface{}) {
|
||||
if !l.IsDebugEnabled() {
|
||||
return
|
||||
}
|
||||
|
||||
l.print(l.debug, a...)
|
||||
}
|
||||
|
||||
// Debugf formats according to a format specifier and writes to the configured debug writer.
|
||||
func (l Logger) Debugf(format string, a ...interface{}) {
|
||||
if !l.IsDebugEnabled() {
|
||||
return
|
||||
}
|
||||
|
||||
l.printf(l.debug, format, a...)
|
||||
}
|
||||
|
||||
// DebugWriter returns the configured debug writer.
|
||||
func (l Logger) DebugWriter() io.Writer {
|
||||
return l.debug
|
||||
}
|
||||
|
||||
// IsDebugEnabled indicates whether debug logging is enabled.
|
||||
func (l Logger) IsDebugEnabled() bool {
|
||||
return l.debug != nil
|
||||
}
|
||||
|
||||
// Info formats using the default formats for its operands and writes to the configured info writer. Spaces are added
|
||||
// between operands when neither is a string.
|
||||
func (l Logger) Info(a ...interface{}) {
|
||||
if !l.IsInfoEnabled() {
|
||||
return
|
||||
}
|
||||
|
||||
l.print(l.info, a...)
|
||||
}
|
||||
|
||||
// Infof formats according to a format specifier and writes to the configured info writer.
|
||||
func (l Logger) Infof(format string, a ...interface{}) {
|
||||
if !l.IsInfoEnabled() {
|
||||
return
|
||||
}
|
||||
|
||||
l.printf(l.info, format, a...)
|
||||
}
|
||||
|
||||
// InfoWriter returns the configured info writer.
|
||||
func (l Logger) InfoWriter() io.Writer {
|
||||
return l.info
|
||||
}
|
||||
|
||||
// IsInfoEnabled indicates whether info logging is enabled.
|
||||
func (l Logger) IsInfoEnabled() bool {
|
||||
return l.info != nil
|
||||
}
|
||||
|
||||
func (Logger) print(writer io.Writer, a ...interface{}) {
|
||||
s := fmt.Sprint(a...)
|
||||
|
||||
if !strings.HasSuffix(s, "\n") {
|
||||
s += "\n"
|
||||
}
|
||||
|
||||
_, _ = fmt.Fprint(writer, s)
|
||||
}
|
||||
|
||||
func (Logger) printf(writer io.Writer, format string, a ...interface{}) {
|
||||
if !strings.HasSuffix(format, "\n") {
|
||||
format += "\n"
|
||||
}
|
||||
|
||||
_, _ = fmt.Fprintf(writer, format, a...)
|
||||
}
|
|
@ -0,0 +1,36 @@
|
|||
/*
|
||||
* 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
|
||||
|
||||
// Process represents metadata about a type of command that can be run.
|
||||
type Process struct {
|
||||
// Type is the type of the process.
|
||||
Type string `toml:"type"`
|
||||
|
||||
// Command is the command of the process.
|
||||
Command []string `toml:"command"`
|
||||
|
||||
// Arguments are arguments to the command.
|
||||
Arguments []string `toml:"args"`
|
||||
|
||||
// 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
|
||||
// type being defined should be the default process type for the app image.
|
||||
Default bool `toml:"default,omitempty"`
|
||||
}
|
|
@ -0,0 +1,24 @@
|
|||
/*
|
||||
* 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
|
||||
|
||||
// Slice represents metadata about a slice.
|
||||
type Slice struct {
|
||||
|
||||
// Paths are the contents of the slice.
|
||||
Paths []string `toml:"paths"`
|
||||
}
|
|
@ -0,0 +1,24 @@
|
|||
/*
|
||||
* 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
|
||||
|
||||
// Store represents the contents of store.toml
|
||||
type Store struct {
|
||||
|
||||
// Metadata represents the persistent metadata.
|
||||
Metadata map[string]interface{} `toml:"metadata"`
|
||||
}
|
|
@ -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": []
|
||||
}
|
||||
]
|
||||
}
|
191
tools/go.mod
191
tools/go.mod
|
@ -1,7 +1,190 @@
|
|||
module github.com/buildpacks/libcnb/tools
|
||||
module github.com/buildpacks/libcnb/tools/v2
|
||||
|
||||
go 1.15
|
||||
go 1.22.1
|
||||
|
||||
require golang.org/x/tools v0.1.4
|
||||
toolchain go1.23.2
|
||||
|
||||
require github.com/golangci/golangci-lint v1.41.1
|
||||
require golang.org/x/tools v0.26.0
|
||||
|
||||
require github.com/golangci/golangci-lint v1.61.0
|
||||
|
||||
require (
|
||||
4d63.com/gocheckcompilerdirectives v1.2.1 // indirect
|
||||
4d63.com/gochecknoglobals v0.2.1 // indirect
|
||||
github.com/4meepo/tagalign v1.3.4 // indirect
|
||||
github.com/Abirdcfly/dupword v0.1.1 // indirect
|
||||
github.com/Antonboom/errname v0.1.13 // indirect
|
||||
github.com/Antonboom/nilnil v0.1.9 // indirect
|
||||
github.com/Antonboom/testifylint v1.4.3 // indirect
|
||||
github.com/BurntSushi/toml v1.4.1-0.20240526193622-a339e1f7089c // indirect
|
||||
github.com/Crocmagnon/fatcontext v0.5.2 // indirect
|
||||
github.com/Djarvur/go-err113 v0.0.0-20210108212216-aea10b59be24 // indirect
|
||||
github.com/GaijinEntertainment/go-exhaustruct/v3 v3.3.0 // indirect
|
||||
github.com/Masterminds/semver/v3 v3.3.0 // indirect
|
||||
github.com/OpenPeeDeeP/depguard/v2 v2.2.0 // indirect
|
||||
github.com/alecthomas/go-check-sumtype v0.1.4 // indirect
|
||||
github.com/alexkohler/nakedret/v2 v2.0.4 // indirect
|
||||
github.com/alexkohler/prealloc v1.0.0 // indirect
|
||||
github.com/alingse/asasalint v0.0.11 // indirect
|
||||
github.com/ashanbrown/forbidigo v1.6.0 // indirect
|
||||
github.com/ashanbrown/makezero v1.1.1 // indirect
|
||||
github.com/beorn7/perks v1.0.1 // indirect
|
||||
github.com/bkielbasa/cyclop v1.2.1 // indirect
|
||||
github.com/blizzy78/varnamelen v0.8.0 // indirect
|
||||
github.com/bombsimon/wsl/v4 v4.4.1 // indirect
|
||||
github.com/breml/bidichk v0.2.7 // indirect
|
||||
github.com/breml/errchkjson v0.3.6 // indirect
|
||||
github.com/butuzov/ireturn v0.3.0 // indirect
|
||||
github.com/butuzov/mirror v1.2.0 // indirect
|
||||
github.com/catenacyber/perfsprint v0.7.1 // indirect
|
||||
github.com/ccojocar/zxcvbn-go v1.0.2 // indirect
|
||||
github.com/cespare/xxhash/v2 v2.1.2 // indirect
|
||||
github.com/charithe/durationcheck v0.0.10 // indirect
|
||||
github.com/chavacava/garif v0.1.0 // indirect
|
||||
github.com/ckaznocha/intrange v0.2.0 // indirect
|
||||
github.com/curioswitch/go-reassign v0.2.0 // indirect
|
||||
github.com/daixiang0/gci v0.13.5 // indirect
|
||||
github.com/davecgh/go-spew v1.1.1 // indirect
|
||||
github.com/denis-tingaikin/go-header v0.5.0 // indirect
|
||||
github.com/ettle/strcase v0.2.0 // indirect
|
||||
github.com/fatih/color v1.17.0 // indirect
|
||||
github.com/fatih/structtag v1.2.0 // indirect
|
||||
github.com/firefart/nonamedreturns v1.0.5 // indirect
|
||||
github.com/fsnotify/fsnotify v1.5.4 // indirect
|
||||
github.com/fzipp/gocyclo v0.6.0 // indirect
|
||||
github.com/ghostiam/protogetter v0.3.6 // indirect
|
||||
github.com/go-critic/go-critic v0.11.4 // indirect
|
||||
github.com/go-toolsmith/astcast v1.1.0 // indirect
|
||||
github.com/go-toolsmith/astcopy v1.1.0 // indirect
|
||||
github.com/go-toolsmith/astequal v1.2.0 // indirect
|
||||
github.com/go-toolsmith/astfmt v1.1.0 // indirect
|
||||
github.com/go-toolsmith/astp v1.1.0 // indirect
|
||||
github.com/go-toolsmith/strparse v1.1.0 // indirect
|
||||
github.com/go-toolsmith/typep v1.1.0 // indirect
|
||||
github.com/go-viper/mapstructure/v2 v2.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.12.1 // indirect
|
||||
github.com/golang/protobuf v1.5.3 // indirect
|
||||
github.com/golangci/dupl v0.0.0-20180902072040-3e9179ac440a // indirect
|
||||
github.com/golangci/gofmt v0.0.0-20240816233607-d8596aa466a9 // indirect
|
||||
github.com/golangci/misspell v0.6.0 // indirect
|
||||
github.com/golangci/modinfo v0.3.4 // indirect
|
||||
github.com/golangci/plugin-module-register v0.1.1 // indirect
|
||||
github.com/golangci/revgrep v0.5.3 // indirect
|
||||
github.com/golangci/unconvert v0.0.0-20240309020433-c5143eacb3ed // indirect
|
||||
github.com/google/go-cmp v0.6.0 // indirect
|
||||
github.com/gordonklaus/ineffassign v0.1.0 // 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/go-version v1.7.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.7.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/jjti/go-spancheck v0.6.2 // indirect
|
||||
github.com/julz/importas v0.1.0 // indirect
|
||||
github.com/karamaru-alpha/copyloopvar v1.1.0 // indirect
|
||||
github.com/kisielk/errcheck v1.7.0 // indirect
|
||||
github.com/kkHAIKE/contextcheck v1.1.5 // indirect
|
||||
github.com/kulti/thelper v0.6.3 // indirect
|
||||
github.com/kunwardeep/paralleltest v1.0.10 // indirect
|
||||
github.com/kyoh86/exportloopref v0.1.11 // indirect
|
||||
github.com/lasiar/canonicalheader v1.1.1 // indirect
|
||||
github.com/ldez/gomoddirectives v0.2.4 // indirect
|
||||
github.com/ldez/tagliatelle v0.5.0 // indirect
|
||||
github.com/leonklingele/grouper v1.1.2 // indirect
|
||||
github.com/lufeee/execinquery v1.2.1 // indirect
|
||||
github.com/macabu/inamedparam v0.1.3 // indirect
|
||||
github.com/magiconair/properties v1.8.6 // 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.20 // indirect
|
||||
github.com/mattn/go-runewidth v0.0.9 // indirect
|
||||
github.com/matttproud/golang_protobuf_extensions v1.0.1 // indirect
|
||||
github.com/mgechev/revive v1.3.9 // indirect
|
||||
github.com/mitchellh/go-homedir v1.1.0 // indirect
|
||||
github.com/mitchellh/mapstructure v1.5.0 // indirect
|
||||
github.com/moricho/tparallel v0.3.2 // indirect
|
||||
github.com/nakabonne/nestif v0.3.1 // indirect
|
||||
github.com/nishanths/exhaustive v0.12.0 // indirect
|
||||
github.com/nishanths/predeclared v0.2.2 // indirect
|
||||
github.com/nunnatsa/ginkgolinter v0.16.2 // 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.2.3 // indirect
|
||||
github.com/pmezard/go-difflib v1.0.0 // indirect
|
||||
github.com/polyfloyd/go-errorlint v1.6.0 // indirect
|
||||
github.com/prometheus/client_golang v1.12.1 // indirect
|
||||
github.com/prometheus/client_model v0.2.0 // indirect
|
||||
github.com/prometheus/common v0.32.1 // indirect
|
||||
github.com/prometheus/procfs v0.7.3 // indirect
|
||||
github.com/quasilyte/go-ruleguard v0.4.3-0.20240823090925-0fe6f58b47b1 // indirect
|
||||
github.com/quasilyte/go-ruleguard/dsl v0.3.22 // indirect
|
||||
github.com/quasilyte/gogrep v0.5.0 // indirect
|
||||
github.com/quasilyte/regex/syntax v0.0.0-20210819130434-b3f0c404a727 // indirect
|
||||
github.com/quasilyte/stdinfo v0.0.0-20220114132959-f7386bf02567 // indirect
|
||||
github.com/ryancurrah/gomodguard v1.3.5 // indirect
|
||||
github.com/ryanrolds/sqlclosecheck v0.5.1 // indirect
|
||||
github.com/sanposhiho/wastedassign/v2 v2.0.7 // indirect
|
||||
github.com/santhosh-tekuri/jsonschema/v5 v5.3.1 // indirect
|
||||
github.com/sashamelentyev/interfacebloat v1.1.0 // indirect
|
||||
github.com/sashamelentyev/usestdlibvars v1.27.0 // indirect
|
||||
github.com/securego/gosec/v2 v2.21.2 // indirect
|
||||
github.com/shazow/go-diff v0.0.0-20160112020656-b6b7b6733b8c // indirect
|
||||
github.com/sirupsen/logrus v1.9.3 // indirect
|
||||
github.com/sivchari/containedctx v1.0.3 // indirect
|
||||
github.com/sivchari/tenv v1.10.0 // indirect
|
||||
github.com/sonatard/noctx v0.0.2 // indirect
|
||||
github.com/sourcegraph/go-diff v0.7.0 // indirect
|
||||
github.com/spf13/afero v1.11.0 // indirect
|
||||
github.com/spf13/cast v1.5.0 // indirect
|
||||
github.com/spf13/cobra v1.8.1 // indirect
|
||||
github.com/spf13/jwalterweatherman v1.1.0 // indirect
|
||||
github.com/spf13/pflag v1.0.5 // indirect
|
||||
github.com/spf13/viper v1.12.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.2 // indirect
|
||||
github.com/stretchr/testify v1.9.0 // indirect
|
||||
github.com/subosito/gotenv v1.4.1 // indirect
|
||||
github.com/tdakkota/asciicheck v0.2.0 // indirect
|
||||
github.com/tetafro/godot v1.4.17 // indirect
|
||||
github.com/timakin/bodyclose v0.0.0-20230421092635-574207250966 // indirect
|
||||
github.com/timonwong/loggercheck v0.9.4 // indirect
|
||||
github.com/tomarrell/wrapcheck/v2 v2.9.0 // indirect
|
||||
github.com/tommy-muehle/go-mnd/v2 v2.5.1 // indirect
|
||||
github.com/ultraware/funlen v0.1.0 // indirect
|
||||
github.com/ultraware/whitespace v0.1.1 // indirect
|
||||
github.com/uudashr/gocognit v1.1.3 // indirect
|
||||
github.com/xen0n/gosmopolitan v1.2.2 // indirect
|
||||
github.com/yagipy/maintidx v1.0.0 // indirect
|
||||
github.com/yeya24/promlinter v0.3.0 // indirect
|
||||
github.com/ykadowak/zerologlint v0.1.5 // indirect
|
||||
gitlab.com/bosi/decorder v0.4.2 // indirect
|
||||
go-simpler.org/musttag v0.12.2 // indirect
|
||||
go-simpler.org/sloglint v0.7.2 // indirect
|
||||
go.uber.org/atomic v1.7.0 // indirect
|
||||
go.uber.org/automaxprocs v1.5.3 // indirect
|
||||
go.uber.org/multierr v1.6.0 // indirect
|
||||
go.uber.org/zap v1.24.0 // indirect
|
||||
golang.org/x/exp v0.0.0-20240904232852-e7e105dedf7e // indirect
|
||||
golang.org/x/exp/typeparams v0.0.0-20240314144324-c7f7c6466f7f // indirect
|
||||
golang.org/x/mod v0.21.0 // indirect
|
||||
golang.org/x/sync v0.8.0 // indirect
|
||||
golang.org/x/sys v0.26.0 // indirect
|
||||
golang.org/x/text v0.18.0 // indirect
|
||||
google.golang.org/protobuf v1.34.2 // 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.5.1 // indirect
|
||||
mvdan.cc/gofumpt v0.7.0 // indirect
|
||||
mvdan.cc/unparam v0.0.0-20240528143540-8a5130ca722f // indirect
|
||||
)
|
||||
|
|
1082
tools/go.sum
1082
tools/go.sum
File diff suppressed because it is too large
Load Diff
|
@ -1,3 +1,4 @@
|
|||
//go:build tools
|
||||
// +build tools
|
||||
|
||||
package tools
|
||||
|
|
Loading…
Reference in New Issue