Compare commits

...

187 Commits

Author SHA1 Message Date
Buildpacks Robot f7e753e42f
Bump Go Modules (#327)
Bumps Go modules used by the project. See the commit for details on what modules were updated.

Signed-off-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Co-authored-by: buildpack-bot <buildpack-bot@users.noreply.github.com>
2025-06-27 14:47:47 -04:00
Buildpacks Robot 274a25f737
Bump Go from 1.23 to 1.24 (#326)
Bumps Go from 1.23 to 1.24 and update Go modules used by the project. See the commit for details on what modules were updated.

Signed-off-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Co-authored-by: buildpack-bot <buildpack-bot@users.noreply.github.com>
2025-03-31 08:12:41 -04:00
Buildpacks Robot a5d5f00dfc
Bump Go Modules (#324)
Bumps Go modules used by the project. See the commit for details on what modules were updated.

Signed-off-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Co-authored-by: buildpack-bot <buildpack-bot@users.noreply.github.com>
2025-03-18 07:53:29 -04:00
Buildpacks Robot babcbff863
Bump pipeline from 1.41.2 to 1.42.0 (#325)
Bumps pipeline from 1.41.2 to 1.42.0.

Signed-off-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Co-authored-by: buildpack-bot <buildpack-bot@users.noreply.github.com>
2025-03-18 07:53:14 -04:00
Buildpacks Robot 4c50e41bb7
Bump Go Modules (#323)
Bumps Go modules used by the project. See the commit for details on what modules were updated.

Signed-off-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Co-authored-by: buildpack-bot <buildpack-bot@users.noreply.github.com>
2025-03-10 14:33:11 -04:00
Daniel Mikusa dfb149268e
Update MIGRATION-v1-to-v2.md (#318)
Signed-off-by: Daniel Mikusa <dan@mikusa.com>
2025-02-23 21:49:26 -05:00
Buildpacks Robot 9e96b2cdf8
Bump Go Modules (#319)
Bumps Go modules used by the project. See the commit for details on what modules were updated.

Signed-off-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Co-authored-by: buildpack-bot <buildpack-bot@users.noreply.github.com>
2025-02-23 21:46:31 -05:00
Buildpacks Robot 986aa1f129
Bump Go Modules (#315)
Bumps Go modules used by the project. See the commit for details on what modules were updated.

Signed-off-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Co-authored-by: buildpack-bot <buildpack-bot@users.noreply.github.com>
2025-01-09 16:17:11 -05:00
Buildpacks Robot ec253d8a85
Bump Go Modules (#313)
Bumps Go modules used by the project. See the commit for details on what modules were updated.

Signed-off-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Co-authored-by: buildpack-bot <buildpack-bot@users.noreply.github.com>
2024-12-16 09:41:01 -05:00
Buildpacks Robot 0a704de531
Bump Go Modules (#311)
Bumps Go modules used by the project. See the commit for details on what modules were updated.

Signed-off-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Co-authored-by: buildpack-bot <buildpack-bot@users.noreply.github.com>
2024-11-25 14:58:35 -05:00
Buildpacks Robot 4d96611671
Bump Go Modules (#310)
Bumps Go modules used by the project. See the commit for details on what modules were updated.

Signed-off-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Co-authored-by: buildpack-bot <buildpack-bot@users.noreply.github.com>
2024-11-04 08:03:53 -05:00
Buildpacks Robot fc02d58a39
Bump Go from 1.22 to 1.23 (#309)
Bumps Go from 1.22 to 1.23 and update Go modules used by the project. See the commit for details on what modules were updated.

Signed-off-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Co-authored-by: buildpack-bot <buildpack-bot@users.noreply.github.com>
2024-10-25 09:27:02 -04:00
Daniel Mikusa aa64fa6d04
Add a migration guide (#307)
* Updates

- Update go modules
- Update pipeline
- Update tools
- Remove deprecated linters

Signed-off-by: Daniel Mikusa <dan@mikusa.com>

* Add a migration guide

Signed-off-by: Daniel Mikusa <dan@mikusa.com>

---------

Signed-off-by: Daniel Mikusa <dan@mikusa.com>
2024-10-07 08:16:50 -04:00
Daniel Mikusa e2504aad02
Updates (#306)
- Update go modules
- Update pipeline
- Update tools
- Remove deprecated linters

Signed-off-by: Daniel Mikusa <dan@mikusa.com>
2024-10-07 08:16:38 -04:00
Buildpacks Robot 541caaca58
Bump Go Modules (#300)
Bumps Go modules used by the project. See the commit for details on what modules were updated.

Signed-off-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Co-authored-by: buildpack-bot <buildpack-bot@users.noreply.github.com>
Co-authored-by: Daniel Mikusa <dan@mikusa.com>
2024-09-17 07:48:57 -04:00
Buildpacks Robot b1a32e8a04
Bump pipeline from 1.39.0 to 1.40.0 (#301)
Bumps pipeline from 1.39.0 to 1.40.0.

Signed-off-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Co-authored-by: buildpack-bot <buildpack-bot@users.noreply.github.com>
2024-09-17 07:38:40 -04:00
dependabot[bot] b6de1780e4
Bump github.com/CycloneDX/cyclonedx-go from 0.9.0 to 0.9.1 (#303)
Bumps [github.com/CycloneDX/cyclonedx-go](https://github.com/CycloneDX/cyclonedx-go) from 0.9.0 to 0.9.1.
- [Release notes](https://github.com/CycloneDX/cyclonedx-go/releases)
- [Changelog](https://github.com/CycloneDX/cyclonedx-go/blob/master/.goreleaser.yml)
- [Commits](https://github.com/CycloneDX/cyclonedx-go/compare/v0.9.0...v0.9.1)

---
updated-dependencies:
- dependency-name: github.com/CycloneDX/cyclonedx-go
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-09-16 23:02:54 -04:00
Buildpacks Robot 685a6de0df
Bump Go Modules (#298)
Bumps Go modules used by the project. See the commit for details on what modules were updated.

Signed-off-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Co-authored-by: buildpack-bot <buildpack-bot@users.noreply.github.com>
2024-08-07 08:51:49 -04:00
Buildpacks Robot f85e7067d6
Bump Go Modules (#297)
Bumps Go modules used by the project. See the commit for details on what modules were updated.

Signed-off-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Co-authored-by: buildpack-bot <buildpack-bot@users.noreply.github.com>
2024-07-08 08:23:13 -04:00
Daniel Mikusa 42eabc313b
Code cleanup for v2 (#295)
Code cleanup

- Updates tools/go.mod dependency versions
- Updates mocks
- Updates lints and fixes code lints
- Removes Profile & profile.d support
- Move examples to examples subdirectory
- Remove mention of mixins

Signed-off-by: Daniel Mikusa <dan@mikusa.com>
2024-06-23 09:55:33 +01:00
Ralf Pannemans b9fc3fec80
Support Buildpack API 0.10 (#291)
* Add Deprecated comment to all stack related identifiers

Co-authored-by: Ralf Pannemans <ralf.pannemans@sap.com>
Signed-off-by: Nicolas Bender <nicolas.bender@sap.com>

* Add target structs

Co-authored-by: Ralf Pannemans <ralf.pannemans@sap.com>
Signed-off-by: Nicolas Bender <nicolas.bender@sap.com>

* Adapt extension related files

Co-authored-by: Ralf Pannemans <ralf.pannemans@sap.com>
Signed-off-by: Nicolas Bender <nicolas.bender@sap.com>

* add support for generated dockerfiles and extend config

Signed-off-by: Pavel Busko <pavel.busko@sap.com>

---------

Signed-off-by: Nicolas Bender <nicolas.bender@sap.com>
Signed-off-by: Pavel Busko <pavel.busko@sap.com>
Co-authored-by: Nicolas Bender <nicolas.bender@sap.com>
Co-authored-by: Pavel Busko <pavel.busko@sap.com>
2024-06-17 22:28:34 -04:00
Buildpacks Robot b32eb02dfa
Bump Go from 1.20 to 1.22 (#294)
Bumps Go from 1.20 to 1.22 and update Go modules used by the project. See the commit for details on what modules were updated.

Signed-off-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Co-authored-by: buildpack-bot <buildpack-bot@users.noreply.github.com>
2024-06-10 08:57:50 -04:00
Buildpacks Robot 7a3158aeb9
Bump pipeline from 1.37.5 to 1.39.0 (#290)
Bumps pipeline from 1.37.5 to 1.39.0.

Signed-off-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Co-authored-by: buildpack-bot <buildpack-bot@users.noreply.github.com>
Co-authored-by: Daniel Mikusa <dan@mikusa.com>
2024-06-06 13:27:59 -04:00
Buildpacks Robot 2002d252e9
Bump Go Modules (#289)
Bumps Go modules used by the project. See the commit for details on what modules were updated.

Signed-off-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Co-authored-by: buildpack-bot <buildpack-bot@users.noreply.github.com>
2024-06-06 13:26:09 -04:00
Buildpacks Robot ecdde19b9b
Bump Go Modules (#285)
Bumps Go modules used by the project. See the commit for details on what modules were updated.

Signed-off-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Co-authored-by: buildpack-bot <buildpack-bot@users.noreply.github.com>
2024-04-24 22:45:05 -04:00
Ralf Pannemans 29547c451a
Do not enforce presence of CNB_STACK_ID (#283)
Signed-off-by: Johannes Dillmann <j.dillmann@sap.com>
Co-authored-by: Johannes Dillmann <j.dillmann@sap.com>
2024-04-18 09:38:23 -04:00
Buildpacks Robot 46d41fad11
Bump Go Modules (#282)
Bumps Go modules used by the project. See the commit for details on what modules were updated.

Signed-off-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Co-authored-by: buildpack-bot <buildpack-bot@users.noreply.github.com>
2024-04-08 23:19:51 -04:00
Buildpacks Robot 2a303f641c
Bump pipeline from 1.37.5 to 1.37.5 (#281)
Bumps pipeline from 1.37.5 to 1.37.5.

Signed-off-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Co-authored-by: buildpack-bot <buildpack-bot@users.noreply.github.com>
2024-04-05 12:25:18 -04:00
Buildpacks Robot fbd6632792
Bump pipeline from 1.36.6 to 1.37.5 (#280)
Bumps pipeline from 1.36.6 to 1.37.5.

Signed-off-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Co-authored-by: buildpack-bot <buildpack-bot@users.noreply.github.com>
Co-authored-by: Daniel Mikusa <dan@mikusa.com>
2024-04-04 15:34:35 -04:00
dependabot[bot] 089a0f9d04
Bump google.golang.org/protobuf from 1.30.0 to 1.33.0 in /tools (#279)
Bumps google.golang.org/protobuf from 1.30.0 to 1.33.0.

---
updated-dependencies:
- dependency-name: google.golang.org/protobuf
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Daniel Mikusa <dan@mikusa.com>
2024-04-04 14:54:46 -04:00
Buildpacks Robot 3ba867d5de
Bump Go Modules (#278)
Bumps Go modules used by the project. See the commit for details on what modules were updated.

Signed-off-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Co-authored-by: buildpack-bot <buildpack-bot@users.noreply.github.com>
2024-04-04 13:47:42 -04:00
Buildpacks Robot 70e38ccecc
Bump pipeline from 1.36.5 to 1.36.6 (#276)
Bumps pipeline from 1.36.5 to 1.36.6.

Signed-off-by: GitHub <noreply@github.com>
Co-authored-by: buildpack-bot <buildpack-bot@users.noreply.github.com>
2024-02-29 12:07:46 -05:00
Buildpacks Robot 777d800b23
Bump Go Modules (#275)
Bumps Go modules used by the project. See the commit for details on what modules were updated.

Signed-off-by: GitHub <noreply@github.com>
Co-authored-by: buildpack-bot <buildpack-bot@users.noreply.github.com>
2024-02-12 11:56:01 -05:00
Buildpacks Robot 084bd173f5
Bump pipeline from 1.36.2 to 1.36.5 (#274)
Bumps pipeline from 1.36.2 to 1.36.5.

Signed-off-by: GitHub <noreply@github.com>
Co-authored-by: buildpack-bot <buildpack-bot@users.noreply.github.com>
2024-02-06 00:57:31 -05:00
Buildpacks Robot 193b480460
Bump Go Modules (#271)
Bumps Go modules used by the project. See the commit for details on what modules were updated.

Signed-off-by: GitHub <noreply@github.com>
Co-authored-by: buildpack-bot <buildpack-bot@users.noreply.github.com>
Co-authored-by: Daniel Mikusa <dan@mikusa.com>
2024-01-22 09:30:33 -05:00
Buildpacks Robot 4b0cdf2fb8
Bump pipeline from 1.34.0 to 1.36.2 (#273)
Bumps pipeline from 1.34.0 to 1.36.2.

Signed-off-by: GitHub <noreply@github.com>
Co-authored-by: buildpack-bot <buildpack-bot@users.noreply.github.com>
2024-01-22 09:28:01 -05:00
Buildpacks Robot 1d2cd33a8b
Bump Go Modules (#270)
Bumps Go modules used by the project. See the commit for details on what modules were updated.

Signed-off-by: GitHub <noreply@github.com>
Co-authored-by: buildpack-bot <buildpack-bot@users.noreply.github.com>
2023-12-11 10:10:35 -05:00
Philipp Stehle 5e1b803e2d
align toml tag of `working-dir` with spec (#269)
See https://github.com/buildpacks/spec/blob/buildpack/v0.10/buildpack.md#launchtoml-toml

Signed-off-by: Ralf Pannemans <ralf.pannemans@sap.com>
Co-authored-by: Ralf Pannemans <ralf.pannemans@sap.com>
2023-11-28 11:53:34 -05:00
Buildpacks Robot 976fce60fc
Bump Go Modules (#267)
Bumps Go modules used by the project. See the commit for details on what modules were updated.

Signed-off-by: GitHub <noreply@github.com>
Co-authored-by: buildpack-bot <buildpack-bot@users.noreply.github.com>
2023-11-13 09:20:57 -05:00
Buildpacks Robot 7a8d6df0fb
Bump Go Modules (#262)
Bumps Go modules used by the project. See the commit for details on what modules were updated.

Signed-off-by: GitHub <noreply@github.com>
Co-authored-by: buildpack-bot <buildpack-bot@users.noreply.github.com>
2023-10-22 22:26:19 -04:00
Buildpacks Robot 9343310624
Bump pipeline from 1.33.0 to 1.34.0 (#261)
Bumps pipeline from 1.33.0 to 1.34.0.

Signed-off-by: GitHub <noreply@github.com>
Co-authored-by: buildpack-bot <buildpack-bot@users.noreply.github.com>
Co-authored-by: Daniel Mikusa <dan@mikusa.com>
2023-10-22 22:23:57 -04:00
dependabot[bot] 68f1d7dc99
Bump golang.org/x/net from 0.15.0 to 0.17.0 (#263)
Bumps [golang.org/x/net](https://github.com/golang/net) from 0.15.0 to 0.17.0.
- [Commits](https://github.com/golang/net/compare/v0.15.0...v0.17.0)

---
updated-dependencies:
- dependency-name: golang.org/x/net
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-10-22 22:21:50 -04:00
Buildpacks Robot 09148d831e
Bump Go Modules (#258)
Bumps Go modules used by the project. See the commit for details on what modules were updated.

Signed-off-by: GitHub <noreply@github.com>
Co-authored-by: buildpack-bot <buildpack-bot@users.noreply.github.com>
2023-10-03 14:24:54 -04:00
Johannes Dillmann 432f14274b
Convert nested binding credentials to JSON (#256)
Signed-off-by: Johannes Dillmann <j.dillmann@sap.com>
Signed-off-by: Pavel Busko <pavel.busko@sap.com>
Co-authored-by: Pavel Busko <pavel.busko@sap.com>
2023-09-21 13:10:44 -04:00
Buildpacks Robot 59903afb4c
Bump Go Modules (#257)
Bumps Go modules used by the project. See the commit for details on what modules were updated.

Signed-off-by: GitHub <noreply@github.com>
Co-authored-by: buildpack-bot <buildpack-bot@users.noreply.github.com>
2023-09-11 10:09:37 -04:00
Buildpacks Robot d6b2574ae1
Bump Go Modules (#255)
Bumps Go modules used by the project. See the commit for details on what modules were updated.

Signed-off-by: GitHub <noreply@github.com>
Co-authored-by: buildpack-bot <buildpack-bot@users.noreply.github.com>
2023-09-04 10:22:12 -04:00
Buildpacks Robot 3e3992aaa5
Bump Go Modules (#252)
Bumps Go modules used by the project. See the commit for details on what modules were updated.

Signed-off-by: GitHub <noreply@github.com>
Co-authored-by: buildpack-bot <buildpack-bot@users.noreply.github.com>
2023-08-28 08:20:20 -04:00
dependabot[bot] 663213ffbf
Bump github.com/CycloneDX/cyclonedx-go from 0.7.1 to 0.7.2 (#253)
Bumps [github.com/CycloneDX/cyclonedx-go](https://github.com/CycloneDX/cyclonedx-go) from 0.7.1 to 0.7.2.
- [Release notes](https://github.com/CycloneDX/cyclonedx-go/releases)
- [Changelog](https://github.com/CycloneDX/cyclonedx-go/blob/master/.goreleaser.yml)
- [Commits](https://github.com/CycloneDX/cyclonedx-go/compare/v0.7.1...v0.7.2)

---
updated-dependencies:
- dependency-name: github.com/CycloneDX/cyclonedx-go
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-08-23 16:23:08 -04:00
Daniel Mikusa ff0986080e
Change module versions & prep to cut an alpha (#251)
* Change module versions & prep to cut an alpha

Signed-off-by: Daniel Mikusa <dan@mikusa.com>

* Change module version in tools/

Signed-off-by: Daniel Mikusa <dan@mikusa.com>

---------

Signed-off-by: Daniel Mikusa <dan@mikusa.com>
2023-08-07 07:51:15 -04:00
Ozzy Osborne 19d88148ff
Ensure non-nil debugwriter for easier v2v migration (#249)
* ensure non-nil debugwriter for easy v2v migration

Signed-off-by: Ozzy Osborne <bardweller@gmail.com>

* formatting for linter

Signed-off-by: Ozzy Osborne <bardweller@gmail.com>

---------

Signed-off-by: Ozzy Osborne <bardweller@gmail.com>
2023-07-24 15:44:33 -04:00
Buildpacks Robot cf9baef95d
Bump Go Modules (#248)
Bumps Go modules used by the project. See the commit for details on what modules were updated.

Signed-off-by: GitHub <noreply@github.com>
Co-authored-by: buildpack-bot <buildpack-bot@users.noreply.github.com>
2023-07-24 09:00:19 -04:00
Buildpacks Robot 4f8f92a58c
Bump Go Modules (#247)
Bumps Go modules used by the project. See the commit for details on what modules were updated.

Signed-off-by: GitHub <noreply@github.com>
Co-authored-by: buildpack-bot <buildpack-bot@users.noreply.github.com>
2023-07-17 09:15:30 -04:00
Buildpacks Robot e7c2e5da91
Bump pipeline from 1.32.0 to 1.33.0 (#243)
Bumps pipeline from 1.32.0 to 1.33.0.

Signed-off-by: GitHub <noreply@github.com>
Co-authored-by: buildpack-bot <buildpack-bot@users.noreply.github.com>
2023-06-30 09:31:36 -04:00
Ozzy Osborne 5d3d3f6e6d
Add extension support (#234)
* First pass at extension support

Signed-off-by: Ozzy Osborne <bardweller@gmail.com>

* Split main for extensions/buildpacks

Signed-off-by: Ozzy Osborne <bardweller@gmail.com>

* Fixup lint/pipeline test results.

Signed-off-by: Ozzy Osborne <bardweller@gmail.com>

* Remove newlines for linter

Signed-off-by: Ozzy Osborne <bardweller@gmail.com>

* Detect needs to understand extensions too

Signed-off-by: Ozzy Osborne <bardweller@gmail.com>

* Comment cleanup

Signed-off-by: Ozzy Osborne <bardweller@gmail.com>

* fix lint issue

Signed-off-by: Ozzy Osborne <bardweller@gmail.com>

---------

Signed-off-by: Ozzy Osborne <bardweller@gmail.com>
2023-06-27 09:09:37 -04:00
Buildpacks Robot 0ffaf167f2
Bump Go Modules (#241)
Bumps Go modules used by the project. See the commit for details on what modules were updated.

Signed-off-by: GitHub <noreply@github.com>
Co-authored-by: buildpack-bot <buildpack-bot@users.noreply.github.com>
2023-06-19 07:38:55 -04:00
Ralf Pannemans 6dcaa2eeb2
Distinguish provider and type when reading binding from VCAP_SERVICES (#235)
Signed-off-by: Ralf Pannemans <ralf.pannemans@sap.com>
2023-06-14 08:36:42 -04:00
Buildpacks Robot d77e5b49f3
Bump Go Modules (#240)
Bumps Go modules used by the project. See the commit for details on what modules were updated.

Signed-off-by: GitHub <noreply@github.com>
Co-authored-by: buildpack-bot <buildpack-bot@users.noreply.github.com>
2023-06-12 08:29:21 -04:00
dependabot[bot] a8abacf488
Bump github.com/BurntSushi/toml from 1.3.0 to 1.3.1 (#238)
Bumps [github.com/BurntSushi/toml](https://github.com/BurntSushi/toml) from 1.3.0 to 1.3.1.
- [Release notes](https://github.com/BurntSushi/toml/releases)
- [Commits](https://github.com/BurntSushi/toml/compare/v1.3.0...v1.3.1)

---
updated-dependencies:
- dependency-name: github.com/BurntSushi/toml
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-06-06 16:15:07 -04:00
dependabot[bot] ff323e4cbb
Bump github.com/BurntSushi/toml from 1.2.1 to 1.3.0 (#236) 2023-06-05 11:27:14 +00:00
dependabot[bot] 2b39329ef3
Bump github.com/stretchr/testify from 1.8.2 to 1.8.4 (#237) 2023-06-05 07:25:23 -04:00
Buildpacks Robot 0f889eda5e
Bump Go Modules (#231)
Bumps Go modules used by the project. See the commit for details on what modules were updated.

Signed-off-by: GitHub <noreply@github.com>
Co-authored-by: buildpack-bot <buildpack-bot@users.noreply.github.com>
2023-05-15 08:29:26 -04:00
Ralf Pannemans d4c34358cd
Fix unit test: do not rely on the order of the binding (#229)
Fix unit test: do not rely on the order of the bindings extracted from VCAP_SERVICES

Signed-off-by: Ralf Pannemans <ralf.pannemans@sap.com>
2023-05-04 22:10:54 -04:00
Ralf Pannemans 82f0d2fde6
Read bindings from VCAP_SERVICES (#227)
Signed-off-by: Ralf Pannemans <ralf.pannemans@sap.com>
2023-04-27 09:09:41 +01:00
Buildpacks Robot d7fde7baad
Bump pipeline from 1.31.0 to 1.32.0 (#225)
* Bump pipeline from 1.31.0 to 1.32.0

Bumps pipeline from 1.31.0 to 1.32.0.

Signed-off-by: GitHub <noreply@github.com>

* Update lint tools & linter definitions

Signed-off-by: Daniel Mikusa <dan@mikusa.com>

* Fix linter errors

Signed-off-by: Daniel Mikusa <dan@mikusa.com>

* Regenerate mocks with latest version of `mockery`

Signed-off-by: Daniel Mikusa <dan@mikusa.com>

* Remove usage of ioutil/*

Signed-off-by: Daniel Mikusa <dan@mikusa.com>

---------

Signed-off-by: GitHub <noreply@github.com>
Signed-off-by: Daniel Mikusa <dan@mikusa.com>
Co-authored-by: buildpack-bot <buildpack-bot@users.noreply.github.com>
Co-authored-by: Daniel Mikusa <dan@mikusa.com>
2023-04-12 08:52:45 -04:00
Buildpacks Robot f10dc6660e
Bump Go Modules (#224)
Bumps Go modules used by the project. See the commit for details on what modules were updated.

Signed-off-by: GitHub <noreply@github.com>
Co-authored-by: buildpack-bot <buildpack-bot@users.noreply.github.com>
2023-04-11 08:14:57 -04:00
Buildpacks Robot 2313404efa
Bump Go Modules (#223)
Bumps Go modules used by the project. See the commit for details on what modules were updated.

Signed-off-by: GitHub <noreply@github.com>
Co-authored-by: buildpack-bot <buildpack-bot@users.noreply.github.com>
Co-authored-by: Daniel Mikusa <dan@mikusa.com>
2023-04-03 08:16:17 -04:00
dependabot[bot] 53ea2d4112
Bump github.com/CycloneDX/cyclonedx-go from 0.7.0 to 0.7.1 (#222)
Bumps [github.com/CycloneDX/cyclonedx-go](https://github.com/CycloneDX/cyclonedx-go) from 0.7.0 to 0.7.1.
- [Release notes](https://github.com/CycloneDX/cyclonedx-go/releases)
- [Changelog](https://github.com/CycloneDX/cyclonedx-go/blob/master/.goreleaser.yml)
- [Commits](https://github.com/CycloneDX/cyclonedx-go/compare/v0.7.0...v0.7.1)

---
updated-dependencies:
- dependency-name: github.com/CycloneDX/cyclonedx-go
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-04-03 08:11:06 -04:00
Buildpacks Robot d13fcca61b
Bump Go Modules (#221)
Bumps Go modules used by the project. See the commit for details on what modules were updated.

Signed-off-by: GitHub <noreply@github.com>
Co-authored-by: buildpack-bot <buildpack-bot@users.noreply.github.com>
2023-03-27 11:21:14 -04:00
Buildpacks Robot 7ef9b73067
Bump pipeline from 1.30.0 to 1.31.0 (#219)
Bumps pipeline from 1.30.0 to 1.31.0.

Signed-off-by: GitHub <noreply@github.com>
Co-authored-by: buildpack-bot <buildpack-bot@users.noreply.github.com>
2023-03-24 09:40:38 -04:00
Buildpacks Robot 21fa5b9f3e
Bump Go Modules (#218)
Bumps Go modules used by the project. See the commit for details on what modules were updated.

Signed-off-by: GitHub <noreply@github.com>
Co-authored-by: buildpack-bot <buildpack-bot@users.noreply.github.com>
2023-03-21 09:33:58 -04:00
Daniel Mikusa 70be45f510
Bump max Buildpack API compatibility version to 0.9 (#216)
Signed-off-by: Daniel Mikusa <dan@mikusa.com>
2023-03-18 12:36:11 +00:00
Buildpacks Robot bacbd31203
Bump Go Modules (#213)
Bumps Go modules used by the project. See the commit for details on what modules were updated.

Signed-off-by: GitHub <noreply@github.com>
Co-authored-by: buildpack-bot <buildpack-bot@users.noreply.github.com>
2023-03-13 16:16:22 -04:00
Buildpacks Robot bb82ce0702
Bump Go Modules (#212)
Bumps Go modules used by the project. See the commit for details on what modules were updated.

Signed-off-by: GitHub <noreply@github.com>
Co-authored-by: buildpack-bot <buildpack-bot@users.noreply.github.com>
2023-02-28 08:40:27 -05:00
dependabot[bot] e9f2cb8583
Bump github.com/stretchr/testify from 1.8.1 to 1.8.2 (#211)
Bumps [github.com/stretchr/testify](https://github.com/stretchr/testify) from 1.8.1 to 1.8.2.
- [Release notes](https://github.com/stretchr/testify/releases)
- [Commits](https://github.com/stretchr/testify/compare/v1.8.1...v1.8.2)

---
updated-dependencies:
- dependency-name: github.com/stretchr/testify
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-02-28 08:23:59 -05:00
dependabot[bot] 9541f56830
Bump golang.org/x/sys from 0.0.0-20220209214540-3681064d5158 to 0.1.0 in /tools (#209)
Bump golang.org/x/sys in /tools

Bumps [golang.org/x/sys](https://github.com/golang/sys) from 0.0.0-20220209214540-3681064d5158 to 0.1.0.
- [Release notes](https://github.com/golang/sys/releases)
- [Commits](https://github.com/golang/sys/commits/v0.1.0)

---
updated-dependencies:
- dependency-name: golang.org/x/sys
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Daniel Mikusa <dan@mikusa.com>
2023-02-25 12:26:08 -05:00
Buildpacks Robot a3eccb730e
Bump pipeline from 1.29.0 to 1.30.0 (#201)
Bumps pipeline from 1.29.0 to 1.30.0.

Signed-off-by: GitHub <noreply@github.com>
Co-authored-by: buildpack-bot <buildpack-bot@users.noreply.github.com>
Co-authored-by: Daniel Mikusa <dan@mikusa.com>
2023-02-25 12:24:26 -05:00
Buildpacks Robot 438a986000
Bump Go Modules (#206)
Bumps Go modules used by the project. See the commit for details on what modules were updated.

Signed-off-by: GitHub <noreply@github.com>
Co-authored-by: buildpack-bot <buildpack-bot@users.noreply.github.com>
2023-02-25 12:21:54 -05:00
Buildpacks Robot 9b489461d0
Bump Go Modules (#202)
Bumps Go modules used by the project. See the commit for details on what modules were updated.

Signed-off-by: GitHub <noreply@github.com>
Co-authored-by: buildpack-bot <buildpack-bot@users.noreply.github.com>
Co-authored-by: Daniel Mikusa <dan@mikusa.com>
2023-02-15 08:47:01 -05:00
dependabot[bot] 3175e42c71
Bump github.com/prometheus/client_golang from 1.7.1 to 1.11.1 in /tools (#203)
Bumps [github.com/prometheus/client_golang](https://github.com/prometheus/client_golang) from 1.7.1 to 1.11.1.
- [Release notes](https://github.com/prometheus/client_golang/releases)
- [Changelog](https://github.com/prometheus/client_golang/blob/main/CHANGELOG.md)
- [Commits](https://github.com/prometheus/client_golang/compare/v1.7.1...v1.11.1)

---
updated-dependencies:
- dependency-name: github.com/prometheus/client_golang
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-02-15 08:41:50 -05:00
Buildpacks Robot 8c162d5042
Bump Go Modules (#200)
Bumps Go modules used by the project. See the commit for details on what modules were updated.

Signed-off-by: GitHub <noreply@github.com>
Co-authored-by: buildpack-bot <buildpack-bot@users.noreply.github.com>
2023-01-30 08:29:18 -05:00
Buildpacks Robot 5f52f3ecc8
Bump Go Modules (#198)
Bumps Go modules used by the project. See the commit for details on what modules were updated.

Signed-off-by: GitHub <noreply@github.com>

Signed-off-by: GitHub <noreply@github.com>
Co-authored-by: buildpack-bot <buildpack-bot@users.noreply.github.com>
2023-01-23 08:24:51 -05:00
Buildpacks Robot 7185622fa0
Bump Go Modules (#196)
Bumps Go modules used by the project. See the commit for details on what modules were updated.

Signed-off-by: GitHub <noreply@github.com>

Signed-off-by: GitHub <noreply@github.com>
Co-authored-by: buildpack-bot <buildpack-bot@users.noreply.github.com>
2023-01-10 08:26:10 -05:00
dependabot[bot] 3308e77997
Bump github.com/onsi/gomega from 1.24.1 to 1.24.2 (#195)
Bumps [github.com/onsi/gomega](https://github.com/onsi/gomega) from 1.24.1 to 1.24.2.
- [Release notes](https://github.com/onsi/gomega/releases)
- [Changelog](https://github.com/onsi/gomega/blob/master/CHANGELOG.md)
- [Commits](https://github.com/onsi/gomega/compare/v1.24.1...v1.24.2)

---
updated-dependencies:
- dependency-name: github.com/onsi/gomega
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-12-15 14:06:20 -05:00
Daniel Mikusa 78810648dd
Move formatters out of main package (#146)
Co-authored-by: Daniel Mikusa <dmikusa@vmware.com>
Co-authored-by: Sambhav Kothari <skothari44@bloomberg.net>
2022-12-15 14:00:37 +05:30
Daniel Mikusa 48a6ad033c
Extract method for generating Config (#151)
* Extract method for generating Config

This PR extracts the code to create a Config object from a list of applied Options. The method also allows us to enforce a global set of default options.

Signed-off-by: Daniel Mikusa <dmikusa@vmware.com>

* Switch to passinig Logger through the context

It was mentioned that changing the DetectFunc and BuildFunc creates an undesirable amount of breakage in existing buildpack. Instead of passing the logger that way, this PR now passes the logger through the DetectContext and BuildContext which is already passed through to DetectFunc and BuildFunc.

Signed-off-by: Daniel Mikusa <dmikusa@vmware.com>

* NewConfigWithOptions -> NewConfig

Signed-off-by: Sambhav Kothari <skothari44@bloomberg.net>

Signed-off-by: Daniel Mikusa <dmikusa@vmware.com>
Signed-off-by: Sambhav Kothari <skothari44@bloomberg.net>
Co-authored-by: Daniel Mikusa <dmikusa@vmware.com>
Co-authored-by: Sambhav Kothari <skothari44@bloomberg.net>
2022-12-15 13:12:44 +05:30
Buildpacks Robot cc26ad5bcc
Bump Go Modules (#194)
Co-authored-by: buildpack-bot <buildpack-bot@users.noreply.github.com>
2022-12-15 13:12:23 +05:30
Daniel Mikusa 7fcb08c9fd
Finish removing shell-specific logic (RFC #0093) (#148)
This PR submits the second half of RFC #0093, which is removing the Direct field from the Process struct and also switching the Command field from a string to a list of strings.

This is a breaking change as it removes a field from the struct and changes an existing field, but given this change is only going into v2+ that seems to be the correct path.

Resolves #70.

Signed-off-by: Daniel Mikusa <dmikusa@vmware.com>

Signed-off-by: Daniel Mikusa <dmikusa@vmware.com>
Co-authored-by: Daniel Mikusa <dmikusa@vmware.com>
Co-authored-by: Sambhav Kothari <skothari44@bloomberg.net>
2022-12-05 11:29:07 -05:00
Buildpacks Robot b2aa734851
Bump Go Modules (#193)
Bumps Go modules used by the project. See the commit for details on what modules were updated.

Signed-off-by: GitHub <noreply@github.com>

Signed-off-by: GitHub <noreply@github.com>
Co-authored-by: buildpack-bot <buildpack-bot@users.noreply.github.com>
2022-12-05 11:24:27 -05:00
Buildpacks Robot daf9de1766
Bump pipeline from 1.28.0 to 1.29.0 (#192)
Bumps pipeline from 1.28.0 to 1.29.0.

Signed-off-by: GitHub <noreply@github.com>

Signed-off-by: GitHub <noreply@github.com>
Co-authored-by: buildpack-bot <buildpack-bot@users.noreply.github.com>
2022-11-21 10:15:29 +00:00
Buildpacks Robot 87b531972f
Bump Go Modules (#190)
Bumps Go modules used by the project. See the commit for details on what modules were updated.

Signed-off-by: GitHub <noreply@github.com>

Signed-off-by: GitHub <noreply@github.com>
Co-authored-by: buildpack-bot <buildpack-bot@users.noreply.github.com>
2022-11-14 09:28:04 +00:00
dependabot[bot] 734dbf9b26
Bump github.com/BurntSushi/toml from 1.2.0 to 1.2.1 (#185)
Bumps [github.com/BurntSushi/toml](https://github.com/BurntSushi/toml) from 1.2.0 to 1.2.1.
- [Release notes](https://github.com/BurntSushi/toml/releases)
- [Commits](https://github.com/BurntSushi/toml/compare/v1.2.0...v1.2.1)

---
updated-dependencies:
- dependency-name: github.com/BurntSushi/toml
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-10-28 11:40:36 -04:00
dependabot[bot] 39445a8884
Bump github.com/stretchr/testify from 1.8.0 to 1.8.1 (#186)
Bumps [github.com/stretchr/testify](https://github.com/stretchr/testify) from 1.8.0 to 1.8.1.
- [Release notes](https://github.com/stretchr/testify/releases)
- [Commits](https://github.com/stretchr/testify/compare/v1.8.0...v1.8.1)

---
updated-dependencies:
- dependency-name: github.com/stretchr/testify
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Daniel Mikusa <dan@mikusa.com>
2022-10-28 10:43:18 -04:00
Buildpacks Robot 5234f9c837
Bump pipeline from 1.27.0 to 1.28.0 (#187)
Bumps pipeline from 1.27.0 to 1.28.0.

Signed-off-by: GitHub <noreply@github.com>

Signed-off-by: GitHub <noreply@github.com>
Co-authored-by: buildpack-bot <buildpack-bot@users.noreply.github.com>
Co-authored-by: Daniel Mikusa <dan@mikusa.com>
2022-10-28 10:22:41 -04:00
dependabot[bot] f7f9919675
Bump github.com/onsi/gomega from 1.21.1 to 1.23.0 (#188)
Bumps [github.com/onsi/gomega](https://github.com/onsi/gomega) from 1.21.1 to 1.23.0.
- [Release notes](https://github.com/onsi/gomega/releases)
- [Changelog](https://github.com/onsi/gomega/blob/master/CHANGELOG.md)
- [Commits](https://github.com/onsi/gomega/compare/v1.21.1...v1.23.0)

---
updated-dependencies:
- dependency-name: github.com/onsi/gomega
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-10-28 10:21:02 -04:00
Buildpacks Robot 8542bee9d0
Bump Go from 1.18 to 1.18 (#180)
Bumps Go from 1.18 to 1.18.

Signed-off-by: GitHub <noreply@github.com>

Signed-off-by: GitHub <noreply@github.com>
Co-authored-by: buildpack-bot <buildpack-bot@users.noreply.github.com>
2022-10-10 04:11:08 +01:00
Buildpacks Robot 8347fd9e5b
Bump Go from 1.18 to 1.18 (#178)
Bumps Go from 1.18 to 1.18.

Signed-off-by: GitHub <noreply@github.com>

Signed-off-by: GitHub <noreply@github.com>
Co-authored-by: buildpack-bot <buildpack-bot@users.noreply.github.com>
2022-09-26 06:47:56 -04:00
Buildpacks Robot 05f5c35c95
Bump Go from 1.18 to 1.18 (#177)
Bumps Go from 1.18 to 1.18.

Signed-off-by: GitHub <noreply@github.com>

Signed-off-by: GitHub <noreply@github.com>
Co-authored-by: buildpack-bot <buildpack-bot@users.noreply.github.com>
2022-09-12 07:51:51 -04:00
dependabot[bot] 1f740aba88
Bump github.com/onsi/gomega from 1.20.1 to 1.20.2 (#176)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-09-02 10:27:08 +01:00
Buildpacks Robot 8553694890
Bump pipeline from 1.26.0 to 1.27.0 (#175)
Bumps pipeline from 1.26.0 to 1.27.0.

Signed-off-by: GitHub <noreply@github.com>

Signed-off-by: GitHub <noreply@github.com>
Co-authored-by: buildpack-bot <buildpack-bot@users.noreply.github.com>
2022-08-30 06:54:05 +01:00
Buildpacks Robot 646bf6b465
Bump Go from 1.18 to 1.18 (#174)
Bumps Go from 1.18 to 1.18.

Signed-off-by: GitHub <noreply@github.com>

Signed-off-by: GitHub <noreply@github.com>
Co-authored-by: buildpack-bot <buildpack-bot@users.noreply.github.com>
2022-08-29 08:08:46 +01:00
Aidan Delaney 6f71a8467d
Provide example on using `libcnb.Detector` interface (#164) 2022-08-26 15:22:57 +01:00
Buildpacks Robot b7d3d515ef
Bump pipeline from 1.25.2 to 1.26.0 (#173)
Bumps pipeline from 1.25.2 to 1.26.0.

Signed-off-by: GitHub <noreply@github.com>

Signed-off-by: GitHub <noreply@github.com>
Co-authored-by: buildpack-bot <buildpack-bot@users.noreply.github.com>
2022-08-25 08:23:56 -04:00
Buildpacks Robot 2696858ab0
Bump pipeline from 1.23.0 to 1.25.2 (#172)
Bumps pipeline from 1.23.0 to 1.25.2.

Signed-off-by: GitHub <noreply@github.com>

Signed-off-by: GitHub <noreply@github.com>
Co-authored-by: buildpack-bot <buildpack-bot@users.noreply.github.com>
2022-08-22 09:28:25 -04:00
Daniel Mikusa 4892e3b4c5
Bump pipeline from 1.23.0 to 1.25.2 (#171)
Bumps pipeline from `1.23.0` to `1.25.2`.

Signed-off-by: Daniel Mikusa <dmikusa@vmware.com>

Signed-off-by: Daniel Mikusa <dmikusa@vmware.com>
2022-08-19 15:45:17 -04:00
Buildpacks Robot 4b96ab802c
Bump Go from 1.17 to 1.18 (#169)
Co-authored-by: buildpack-bot <buildpack-bot@users.noreply.github.com>
2022-08-15 09:41:16 +01:00
Buildpacks Robot 98647956ab
Bump pipeline from 1.22.0 to 1.23.0 (#165)
Bumps pipeline from 1.22.0 to 1.23.0.

Signed-off-by: GitHub <noreply@github.com>

Signed-off-by: GitHub <noreply@github.com>
Co-authored-by: buildpack-bot <buildpack-bot@users.noreply.github.com>
2022-08-11 07:53:34 +00:00
Daniel Mikusa 272cbe263f
Use curl to install richgo (#166)
This PR changes the workflow to use 'curl' to fetch and install richgo instead of 'go get'. We could switch to 'go install', but 'curl' is faster than fetching & building.

This is what the pipeline-builder project has been doing to install richgo for quite a while now.

Signed-off-by: Daniel Mikusa <dmikusa@vmware.com>

Signed-off-by: Daniel Mikusa <dmikusa@vmware.com>
2022-08-11 08:51:45 +01:00
dependabot[bot] 8c92bce2dc
Bump github.com/onsi/gomega from 1.19.0 to 1.20.0 (#162)
Bumps [github.com/onsi/gomega](https://github.com/onsi/gomega) from 1.19.0 to 1.20.0.
- [Release notes](https://github.com/onsi/gomega/releases)
- [Changelog](https://github.com/onsi/gomega/blob/master/CHANGELOG.md)
- [Commits](https://github.com/onsi/gomega/compare/v1.19.0...v1.20.0)

---
updated-dependencies:
- dependency-name: github.com/onsi/gomega
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-08-10 13:47:22 -04:00
dependabot[bot] 46bbd4601e
Bump github.com/BurntSushi/toml from 1.1.0 to 1.2.0 (#163)
Bumps [github.com/BurntSushi/toml](https://github.com/BurntSushi/toml) from 1.1.0 to 1.2.0.
- [Release notes](https://github.com/BurntSushi/toml/releases)
- [Commits](https://github.com/BurntSushi/toml/compare/v1.1.0...v1.2.0)

---
updated-dependencies:
- dependency-name: github.com/BurntSushi/toml
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-08-10 13:44:19 -04:00
Daniel Mikusa a9404c31cb
Ensure directories are created when process-specific env variables are used (#160)
* Ensure directories are created when process-specific env variables are used
* Add comment to explain the need for extra calls to makedirs

Signed-off-by: Daniel Mikusa <dmikusa@vmware.com>
2022-07-14 21:44:47 -04:00
dependabot[bot] 2b53a1d8a9
Bump github.com/stretchr/testify from 1.7.4 to 1.8.0 (#155)
Bumps [github.com/stretchr/testify](https://github.com/stretchr/testify) from 1.7.4 to 1.8.0.
- [Release notes](https://github.com/stretchr/testify/releases)
- [Commits](https://github.com/stretchr/testify/compare/v1.7.4...v1.8.0)

---
updated-dependencies:
- dependency-name: github.com/stretchr/testify
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Daniel Mikusa <dmikusa@vmware.com>
2022-07-14 10:09:07 -04:00
Buildpacks Robot a54cf8ab45
Bump pipeline from 1.21.4 to 1.22.0 (#157)
Bumps pipeline from 1.21.4 to 1.22.0.

Signed-off-by: GitHub <noreply@github.com>

Co-authored-by: buildpack-bot <buildpack-bot@users.noreply.github.com>
2022-07-14 10:07:02 -04:00
Buildpacks Robot a81dbdf586
Bump pipeline from 1.21.3 to 1.21.4 (#156)
Bumps pipeline from 1.21.3 to 1.21.4.

Signed-off-by: GitHub <noreply@github.com>

Co-authored-by: buildpack-bot <buildpack-bot@users.noreply.github.com>
2022-07-11 09:08:20 +01:00
dependabot[bot] 3285cf1a1b
Bump github.com/stretchr/testify from 1.7.2 to 1.7.4 (#153)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-06-22 12:10:55 -04:00
dependabot[bot] f2d102431a
Bump github.com/stretchr/testify from 1.7.1 to 1.7.2 (#150)
Bumps [github.com/stretchr/testify](https://github.com/stretchr/testify) from 1.7.1 to 1.7.2.
- [Release notes](https://github.com/stretchr/testify/releases)
- [Commits](https://github.com/stretchr/testify/compare/v1.7.1...v1.7.2)

---
updated-dependencies:
- dependency-name: github.com/stretchr/testify
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-06-07 08:38:30 +01:00
Daniel Mikusa 9f998abc47
Remove support for older buildpack APIs (#149)
This PR bumps the minimal supported Buildpack API to 0.8. It removes support for older versions (code, test cases, etc...).

Signed-off-by: Daniel Mikusa <dmikusa@vmware.com>
2022-05-31 21:55:47 +01:00
Daniel Mikusa 5143e3dcfa
Modifies bindings API to improve consistency (#147)
* Modifies bindings API to improve consistency

- Removes `NewBindingsForLaunch`
- Renames `NewBindingsForBuild` to `NewBindings`
- `NewBindings` is the recommended entrypoint for loading bindings across build/detect/launch
- `NewBindingsFromPath` is being left public for historical reasons and to provide a bit of extra flexiblity
- build.go and detect.go were switch to use `NewBindings`
- Uses `os.ReadDir` instead of `filepath.Glob`, the latter will swallow I/O errors which can make it hard to debug binding issues

Signed-off-by: Daniel Mikusa <dmikusa@vmware.com>

* Add consts for more env variable names & doc strings

Signed-off-by: Daniel Mikusa <dmikusa@vmware.com>
2022-05-30 09:57:51 +01:00
Daniel Mikusa 6882b47e95
Removes TODOs around CNB_BUILDPACK_DIR (#145)
The lifecycle has had CNB_BUILDPACK_DIR support since 0.9.0. It seems safe to remove this fallback code & just fail if the env variable is not present.

Signed-off-by: Daniel Mikusa <dmikusa@vmware.com>

Co-authored-by: Sambhav Kothari <skothari44@bloomberg.net>
2022-05-27 20:45:29 +00:00
Buildpacks Robot 6ea59bbedc
Bump pipeline from 1.21.2 to 1.21.3 (#144)
Bumps pipeline from 1.21.2 to 1.21.3.

Signed-off-by: GitHub <noreply@github.com>

Co-authored-by: buildpack-bot <buildpack-bot@users.noreply.github.com>
2022-05-27 19:19:38 +01:00
Forest Eckhardt 3354bfcda5
Adds layer.Reset function (#141) 2022-05-24 20:55:00 +01:00
Daniel Mikusa 5e36043456
Forward-port v1 changes to v2 (#142) 2022-05-24 19:39:30 +01:00
Forest Eckhardt 5bb9c7b3d8
Removes usage of ioutil (#139)
Signed-off-by: Ubuntu <ubuntu@serrano.c.cf-buildpacks.internal>
2022-05-19 08:24:49 +02:00
Daniel Mikusa 5114b77d7f
Make a configurable Logger interface (#124)
* Make a configurable Logger interface

Presently, we have `log.Logger` which is a concrete implementation of a very basic logger. We use this in libcnb to log information. The buildpack calls only debug related methods, with one exception and that is a single call to an info method for logging a warning.

This PR proposes the following changes:

1. We have a Logger interface that defines debug methods only (more on this later). This can be implemented to control how libcnb logs.
2. We provide a basic implementation of Logger called PlainLogger that writes to stdout if `$BP_LOG_LEVEL=debug` or `$BP_DEBUG=true`. This is in the log module, so it can be bypassed if not needed.
3. We enable a Logger to be passed in through config options to the main/build/detect functions. Thus a buildpack author can provide their own implementation & control formatting.
4. We set a style rule that internally libcnb only writes debug level messages. This removes the need for Info/Warn level methods on the interface.
5. We set a style rule that internally libcnb does not write warnings. Any warning is an error. This is because the warnings that have been used are not messages to end-users, but messages to buildpack authors. Thus making it an error should force buildpack authors to deal with the problem. This should reduce user confusion as they won't see warning messages which they can do nothing about.

Signed-off-by: Daniel Mikusa <dmikusa@vmware.com>

* Removes DebugWriter

- Removes the DebugWriter method from the interface and implementation
- Removes the DebugWriter tests

Signed-off-by: Daniel Mikusa <dmikusa@vmware.com>

* Add default logger implementation

Signed-off-by: Daniel Mikusa <dmikusa@vmware.com>
2022-04-11 08:40:52 +01:00
Sambhav Kothari 71efa8132a
Merge pull request #122 from dmikusa-pivotal/v2-bindings 2022-03-07 14:29:03 +00:00
Sambhav Kothari e6b42e9dc7
Merge pull request #121 from dmikusa-pivotal/v2-pipeline 2022-03-07 14:28:45 +00:00
Daniel Mikusa 31c63bc860
Removes CNB Bindings Support
The CNB binding support has been deprecated for a while in favor of the Kubernetes binding spec. This may be contentious, but I think it seems like a good time to remove support for this API. This PR removes the support & tests.

Signed-off-by: Daniel Mikusa <dmikusa@vmware.com>
2022-03-06 17:15:24 -05:00
Daniel Mikusa 87f95b2b80
Bump the pipeline to the latest, 1.19.0
Signed-off-by: Daniel Mikusa <dmikusa@vmware.com>
2022-03-06 17:03:58 -05:00
Sambhav Kothari 66af9c5c22
Merge pull request #120 from dmikusa-pivotal/v2-sbom
Catch up to main with v2 SBOM Changes
2022-03-06 17:18:15 +00:00
Daniel Mikusa 9f40fa2bec
Make tests pass with buildFunc
Commit 45c858b545 has a test that mocks the builder object. That was removed on the v2 branch. This PR adjusts the tests to pass using the buildFunc instead.

Signed-off-by: Daniel Mikusa <dmikusa@vmware.com>
2022-03-05 14:55:38 -05:00
Sambhav Kothari a9cb6b2dd6
Only validate for API versions 0.5 and 0.6
libcnb is only supported for versions 0.5+ and we check for it a few lines above. Removing some unnecessary checks

Signed-off-by: Sambhav Kothari <skothari44@bloomberg.net>
2022-03-05 14:54:27 -05:00
Daniel Mikusa 1dfbc993cc
Validation should only occur under API 0.7
- Adds API checks before validating SBOM format, should only happen with API 0.7+
- Adds a test to confirm validation does not run if the API is less than 0.7.

Resolves #107

Signed-off-by: Daniel Mikusa <dmikusa@vmware.com>
2022-03-05 14:54:18 -05:00
Daniel Mikusa 3e35e4513b
Make tests pass with buildFunc
Commit 60e9ca7aff has tests which mock the builder object. That was removed on the v2 branch. This PR adjusts the tests to pass using the buildFunc instead.

Signed-off-by: Daniel Mikusa <dmikusa@vmware.com>
2022-03-05 14:53:02 -05:00
Daniel Mikusa f1b4378a23
Updates libcnb to support buildpacks API 0.7
This updates logic checks the buildpack has an acceptable buildpack API. Previously, it supported 0.5 and 0.6. It now has 0.5, 0.6 and 0.7.

Signed-off-by: Daniel Mikusa <dmikusa@vmware.com>
2022-03-05 14:50:02 -05:00
Sambhav Kothari 60e9ca7aff
Make validate SBOM private
Signed-off-by: Sambhav Kothari <skothari44@bloomberg.net>
2022-03-05 14:42:06 -05:00
Daniel Mikusa d61ffa8660
Additional updates for SBOM Support
- Adds SBOMFormats (maps to `sbom-formats` in buildpack.toml) to the BuildpackInfo struct. This makes the information accessible to buildpacks.
- Modifies build such that it only writes the old-style build and launch BOM information if the buildpack API is less than 0.7. If it's 0.7+, it should not write the old style format as that can conflict with the new SBOM format and cause the lifecycle to fail. Omits a warning message if this occurs.
- Adds validation of the new SBOM files that are written by a buildpack. We check that the extension matches up with a valid MIME type that is listed in buildpack.toml's `sbom-formats` field. If it does not match up, then it fails. This should not generally happen with published buildpacks. This check can be helpful while authoring buildpacks, to ensure everything is correctly setup.

Signed-off-by: Daniel Mikusa <dmikusa@vmware.com>
Co-authored-by: Sambhav Kothari <sambhavs.email@gmail.com>
Signed-off-by: Daniel Mikusa <dmikusa@vmware.com>
2022-03-05 14:41:39 -05:00
Sambhav Kothari 8a82a0ef1a
Merge pull request #119 from dmikusa-pivotal/v2-go17
Upgrade to go 1.17
2022-03-05 19:38:00 +00:00
Daniel Mikusa 34e343460c
Upgrade to go 1.17
Upgrades to Go 1.17. Also, bumps BurntSushi/toml and onsi/gomega to latest

Signed-off-by: Daniel Mikusa <dmikusa@vmware.com>
2022-03-05 14:13:47 -05:00
Sambhav Kothari 4d5ea0c017
Merge pull request #101 from ForestEckhardt/extract-structs 2021-12-10 18:38:40 +00:00
Forest Eckhardt a244d9ffc0 Removes application.go file now that struct is gone
Signed-off-by: Forest Eckhardt <feckhardt@pivotal.io>
2021-12-10 17:17:14 +00:00
Sambhav Kothari 587cf4b267
Merge pull request #102 from ForestEckhardt/remove-application-struct
Removes the Application struct
2021-12-10 17:02:01 +00:00
Forest Eckhardt aaff56ad46 Renames WorkingDir field to ApplicationPath
Signed-off-by: Forest Eckhardt <feckhardt@pivotal.io>
2021-12-10 16:48:47 +00:00
Sambhav Kothari 4d4003ef78
Merge pull request #103 from ForestEckhardt/patch-1 2021-12-10 14:40:34 +00:00
Forest Eckhardt 3b38a59476 Remove generate command for removed code
Removes the `mockery` generate command for the removed `Detector` interface

Signed-off-by: Forest Eckhardt <feckhardt@pivotal.io>
2021-12-09 23:50:28 +00:00
Forest Eckhardt 63cd2b0f30 Removes the Application struct
- Currently the Application struct is just a wrapper for the WorkingDir
that is specified by the lifecycle. This adds an unneed layer of
abstracation and strays away from the terminology that is used in the
buildpacks spec. Instead the WorkingDir is just a string fields on both
the Build and Detect contexts that conveys the same information.

Signed-off-by: Forest Eckhardt <feckhardt@pivotal.io>
2021-12-09 22:50:11 +00:00
Forest Eckhardt da9b109ec8 Extracts all structs within the `application.go`
- Extracts all structs that are not the application struct into thier
own files in order to make them more discoverable and make it more
obvious where there structs are located within the codebase.

Signed-off-by: Forest Eckhardt <feckhardt@pivotal.io>
2021-12-09 22:38:19 +00:00
Sambhav Kothari 5beb81e48b
Merge pull request #96 from dmikusa-pivotal/sbom-v2
Adds a convenience method for getting the build, launch and layer BOM file paths
2021-11-18 22:43:34 +00:00
Daniel Mikusa 11abe2b4f0
Adds a convenience method for getting the build, launch and layer BOM file paths
This PR includes:

- convenience methods on the Layers and Layer object for fetching the buildpacks API 0.7 SBOM path
- removes deprecated pre-buildpack API 0.7 BOM functionality

Signed-off-by: Daniel Mikusa <dmikusa@vmware.com>
2021-11-18 09:20:14 -05:00
Sambhav Kothari 1031c7329e
Merge pull request #89 from dmikusa-pivotal/log-rename
Renames poet -> log
2021-11-05 22:37:31 +00:00
Daniel Mikusa 48c2123e6c
Renames poet -> log
- Renames the poet module to log
- Renames NewLogger to just New, so that you have less redundancy. i.e. log.NewLogger becomes log.New
- Renames NewLoggerWithOptions to just NewWithOptions, so that you have less redundancy. i.e. log.NewLoggerWithOptions becomes log.NewWithOptions
- Renames LogLevel to just Level, so that you have less redundancy. i.e. log.LogLevel becomes log.Level

Resolves #81

Signed-off-by: Daniel Mikusa <dmikusa@vmware.com>
2021-11-05 14:57:26 -04:00
Sambhav Kothari f7b68c6ccf
Merge pull request #87 from dmikusa-pivotal/version-2.0 2021-11-05 18:55:48 +00:00
Josh Ghiloni 9c5c713940
Make substring match more explicit in buildpack toml test
Signed-off-by: Josh Ghiloni <jghiloni@vmware.com>
Signed-off-by: Daniel Mikusa <dmikusa@vmware.com>
2021-11-05 13:57:44 -04:00
Josh Ghiloni 481034cb02
Omit Path field from serialization altogether
Signed-off-by: Josh Ghiloni <jghiloni@vmware.com>
2021-11-05 13:54:10 -04:00
Josh Ghiloni 73483d77e8
Add struct tag to Buildpack.Path field
Most Go applications use github.com/burntsushi/toml as their toml
parsing library. When struct tags are omitted, the library will do
case-insensitive deserialization, so a property called "path" will
successfully unmarshal into the Path struct field. However, if you
marshal a Buildpack instance to toml, the "Path = " field appears
regardless of whether it is set or not, and technically violates the
buildpack toml spec. This PR adds a struct tag to not only marshal the
field to the "path" property in the resultant toml, but also adds the
"omitempty" modifier to ignore the field altogether when not set.

Signed-off-by: Josh Ghiloni <jghiloni@vmware.com>
2021-11-05 13:54:10 -04:00
buildpack-bot 33398bde5c
Bump pipeline from 1.12.0 to 1.12.1
Bumps pipeline from 1.12.0 to 1.12.1.

Signed-off-by: GitHub <noreply@github.com>
2021-11-05 13:54:05 -04:00
Sambhav Kothari 3cebafc8e4 Merge remote-tracking branch 'origin/main' into rebased-version2.0
Signed-off-by: Sambhav Kothari <skothari44@bloomberg.net>
2021-10-31 21:10:50 +00:00
Sambhav Kothari 1d06c7f6e3
Merge pull request #82 from ryanmoran/use-funcs
Replace Builder and Detector with functions
2021-10-30 12:49:24 +01:00
Ryan Moran c3a7795dbb Replace Builder and Detector with functions
Signed-off-by: Ryan Moran <ryan.moran@gmail.com>
2021-10-27 11:23:17 -07:00
Terence Lee aed8887609
Merge pull request #74 from ryanmoran/layers
Removes LayerContributor concept
2021-10-27 02:07:03 -05:00
Terence Lee 00601e4588
Merge pull request #80 from buildpacks/update/pipeline
Bump pipeline from 1.10.5 to 1.12.0
2021-10-27 02:04:47 -05:00
buildpack-bot 31c9b286f3 Bump pipeline from 1.10.5 to 1.12.0
Bumps pipeline from 1.10.5 to 1.12.0.

Signed-off-by: GitHub <noreply@github.com>
2021-10-27 05:09:48 +00:00
Terence Lee e90023ce44
Merge pull request #78 from buildpacks/execd
Add execd helper
2021-10-08 08:13:50 -07:00
Sambhav Kothari 9b80891fe5
Merge branch 'main' into execd 2021-10-06 07:57:16 +01:00
Terence Lee da8314616c
Merge pull request #79 from pivotal-david-osullivan/binding_bug
Fixed bug which created bindings from hidden files/dirs
2021-10-05 15:36:34 -07:00
Sambhav Kothari 5e05852376 Rename executor to execd
Signed-off-by: Sambhav Kothari <skothari44@bloomberg.net>
2021-10-05 18:30:07 +01:00
Sambhav Kothari d29fc724c0
Merge branch 'main' into binding_bug 2021-10-04 15:52:35 +01:00
David O'Sullivan 0496d9ff6e
Fixed bug which created bindings from hidden files/dirs
Signed-off-by: David O'Sullivan <davidos@vmware.com>
2021-10-04 13:30:07 +01:00
Sambhav Kothari c36f48d861 Add execd helper
Signed-off-by: Sambhav Kothari <skothari44@bloomberg.net>
2021-10-02 14:59:29 +01:00
Terence Lee 8496ef4039
Merge pull request #77 from dmikusa-pivotal/build-bom
Include BOM in test for emptiness
2021-09-24 09:06:43 -05:00
Emily Casey 45321cb1ac
Merge branch 'main' into build-bom 2021-09-24 09:53:24 -04:00
Emily Casey 0a77d8caf7
Merge pull request #69 from pivotal-david-osullivan/common_logging
Adds support for buildpack log level environment variable BP_LOG_LEVEL
2021-09-24 09:52:43 -04:00
Daniel Mikusa a1b0928708
Include BOM in test for emptiness
The `BuildTOML.isEmpty()` method is used in one place, build.go, to check if it should write out build.toml. In the present state, if Unmet is empty but you have BOM entries then the build.toml and BOM entries are never written out. This change will look at both BOM & Unmet to determine emptiness. Thus it will write build.toml if BOM or Unmet has values.

Signed-off-by: Daniel Mikusa <dmikusa@vmware.com>
2021-09-24 09:06:20 -04:00
Ryan Moran 4454619860 Removes LayerContributor concept
Signed-off-by: Ryan Moran <ryan.moran@gmail.com>
2021-09-22 13:47:17 -07:00
Sambhav Kothari 7eb31bf136
Merge branch 'main' into common_logging 2021-09-08 21:54:43 +01:00
Emily Casey d893d2a8de
Merge pull request #71 from buildpacks/dependabot/go_modules/github.com/onsi/gomega-1.16.0
Bump github.com/onsi/gomega from 1.14.0 to 1.16.0
2021-09-08 15:09:47 -04:00
Emily Casey 42462272ec
Merge branch 'main' into dependabot/go_modules/github.com/onsi/gomega-1.16.0 2021-09-08 12:56:33 -04:00
Emily Casey 81cf3b415a
Merge pull request #72 from buildpacks/update/pipeline
Bump pipeline from 1.10.2 to 1.10.5
2021-09-08 12:56:18 -04:00
Sambhav Kothari 16b7007d6f
Merge branch 'main' into common_logging 2021-09-07 18:21:25 +01:00
dependabot[bot] 03d8910db4
Bump github.com/onsi/gomega from 1.14.0 to 1.16.0
Bumps [github.com/onsi/gomega](https://github.com/onsi/gomega) from 1.14.0 to 1.16.0.
- [Release notes](https://github.com/onsi/gomega/releases)
- [Changelog](https://github.com/onsi/gomega/blob/master/CHANGELOG.md)
- [Commits](https://github.com/onsi/gomega/compare/v1.14.0...v1.16.0)

---
updated-dependencies:
- dependency-name: github.com/onsi/gomega
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2021-09-07 17:21:23 +00:00
Terence Lee 26e4e63625
Merge branch 'main' into update/pipeline 2021-09-07 12:20:11 -05:00
Emily Casey 0f1f628f40
Merge pull request #67 from buildpacks/dependabot/go_modules/github.com/BurntSushi/toml-0.4.1
Bump github.com/BurntSushi/toml from 0.3.1 to 0.4.1
2021-09-07 13:19:49 -04:00
buildpack-bot 4ae60411a2 Bump pipeline from 1.10.2 to 1.10.5
Bumps pipeline from 1.10.2 to 1.10.5.

Signed-off-by: GitHub <noreply@github.com>
2021-09-06 05:09:21 +00:00
David O'Sullivan 1112c1f8ee
Adds support for buildpack log level environment variable 'BP_LOG_LEVEL' which currently accepts 'INFO' (default) or 'DEBUG'
Signed-off-by: David O'Sullivan <davidos@vmware.com>
2021-08-12 12:05:13 +01:00
dependabot[bot] 7394cb3817
Bump github.com/BurntSushi/toml from 0.3.1 to 0.4.1
Bumps [github.com/BurntSushi/toml](https://github.com/BurntSushi/toml) from 0.3.1 to 0.4.1.
- [Release notes](https://github.com/BurntSushi/toml/releases)
- [Commits](https://github.com/BurntSushi/toml/compare/v0.3.1...v0.4.1)

---
updated-dependencies:
- dependency-name: github.com/BurntSushi/toml
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2021-08-05 19:03:34 +00:00
Emily Casey 3a76485b9c
Merge pull request #65 from buildpacks/dependabot/go_modules/github.com/onsi/gomega-1.14.0
Bump github.com/onsi/gomega from 1.13.0 to 1.14.0
2021-07-12 09:47:06 -04:00
dependabot[bot] 615e972098
Bump github.com/onsi/gomega from 1.13.0 to 1.14.0
Bumps [github.com/onsi/gomega](https://github.com/onsi/gomega) from 1.13.0 to 1.14.0.
- [Release notes](https://github.com/onsi/gomega/releases)
- [Changelog](https://github.com/onsi/gomega/blob/master/CHANGELOG.md)
- [Commits](https://github.com/onsi/gomega/compare/v1.13.0...v1.14.0)

---
updated-dependencies:
- dependency-name: github.com/onsi/gomega
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2021-07-12 13:41:39 +00:00
Emily Casey 6906ce9b95
Merge pull request #66 from buildpacks/update/pipeline
Bump pipeline from 1.10.0 to 1.10.2
2021-07-12 09:40:05 -04:00
buildpack-bot baf6c4c366 Bump pipeline from 1.10.0 to 1.10.2
Bumps pipeline from 1.10.0 to 1.10.2.

Signed-off-by: GitHub <noreply@github.com>
2021-07-12 05:09:05 +00:00
85 changed files with 4931 additions and 2588 deletions

View File

@ -4,6 +4,8 @@ updates:
directory: /
schedule:
interval: daily
ignore:
- dependency-name: github.com/onsi/gomega
labels:
- semver:patch
- type:dependency-upgrade

15
.github/labels.yml vendored
View File

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

View File

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

View File

@ -1 +1 @@
1.10.0
1.42.0

View File

@ -1 +0,0 @@
1.4.0

View File

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

View File

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

54
.github/workflows/pb-tests.yml vendored Normal file
View File

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

72
.github/workflows/pb-update-go.yml vendored Normal file
View File

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

View File

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

View File

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

7
.gitignore vendored
View File

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

45
MIGRATION-v1-to-v2.md Normal file
View File

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

View File

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

View File

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

View File

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

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

View File

@ -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"))
})
})
}

27
build_toml.go Normal file
View File

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

View File

@ -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"`
}

51
buildpack_test.go Normal file
View File

@ -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 = ")))
})
}

View File

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

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

View File

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

View File

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

103
examples/build_test.go Normal file
View File

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

67
examples/detect_test.go Normal file
View File

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

30
examples/generate_test.go Normal file
View File

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

71
exec_d.go Normal file
View File

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

130
exec_d_test.go Normal file
View File

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

59
extension.go Normal file
View File

@ -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"`
}

51
extension_test.go Normal file
View File

@ -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 = ")))
})
}

View File

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

View File

@ -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]"))
})
})
}

261
generate.go Normal file
View 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
}
}
}

458
generate_test.go Normal file
View File

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

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

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -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"))
})
}

View File

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

View File

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

62
internal/execd_writer.go Normal file
View File

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

View File

@ -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"`))
})
}

View File

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

38
internal/formatter.go Normal file
View File

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

View File

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

View File

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

View File

@ -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"`
}

View File

@ -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"`))
})

26
label.go Normal file
View File

@ -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"`
}

33
launch_toml.go Normal file
View File

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

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

View File

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

59
log/formatter.go Normal file
View File

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

View File

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

102
log/logger.go Normal file
View File

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

View File

@ -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())
})
})
}

View File

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

81
log/mocks/logger.go Normal file
View File

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

@ -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...)
}

View File

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

View File

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

View File

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

View File

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

54
mocks/exec_d.go Normal file
View File

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

42
mocks/exec_d_writer.go Normal file
View File

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

View File

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

View File

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

View File

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

View File

@ -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"))
}

View File

@ -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{}))
})
})
})
})
})
}

View File

@ -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...)
}

36
process.go Normal file
View File

@ -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"`
}

24
slice.go Normal file
View File

@ -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"`
}

24
store.go Normal file
View File

@ -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"`
}

68
testdata/vcap_services.json vendored Normal file
View File

@ -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": []
}
]
}

View File

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

File diff suppressed because it is too large Load Diff

View File

@ -1,3 +1,4 @@
//go:build tools
// +build tools
package tools