1
0
mirror of https://github.com/SAP/jenkins-library.git synced 2025-11-06 09:09:19 +02:00

feat(cnbBuild): refactored buildEnvVars input property, added resourceRef to the buildpacks (#3310)

Co-authored-by: Johannes Dillmann <j.dillmann@sap.com>
This commit is contained in:
Pavel Busko
2021-11-29 11:32:32 +01:00
committed by GitHub
parent d9e30418bc
commit ac09e6e4aa
11 changed files with 116 additions and 75 deletions

View File

@@ -222,6 +222,22 @@ func prepareDockerConfig(source string, utils cnbutils.BuildUtils) (string, erro
return source, nil
}
func (c *cnbBuildOptions) mergeEnvVars(vars map[string]interface{}) {
if c.BuildEnvVars == nil {
c.BuildEnvVars = vars
return
}
for k, v := range vars {
_, exists := c.BuildEnvVars[k]
if !exists {
c.BuildEnvVars[k] = v
}
}
}
func runCnbBuild(config *cnbBuildOptions, telemetryData *telemetry.CustomData, utils cnbutils.BuildUtils, commonPipelineEnvironment *cnbBuildCommonPipelineEnvironment, httpClient piperhttp.Sender) error {
var err error
@@ -252,14 +268,12 @@ func runCnbBuild(config *cnbBuildOptions, telemetryData *telemetry.CustomData, u
return errors.Wrapf(err, "failed to parse %s", config.ProjectDescriptor)
}
config.mergeEnvVars(descriptor.EnvVars)
if (config.Buildpacks == nil || len(config.Buildpacks) == 0) && len(descriptor.Buildpacks) > 0 {
config.Buildpacks = descriptor.Buildpacks
}
if (config.BuildEnvVars == nil || len(config.BuildEnvVars) == 0) && len(descriptor.EnvVars) > 0 {
config.BuildEnvVars = descriptor.EnvVars
}
if descriptor.Exclude != nil {
exclude = descriptor.Exclude
}
@@ -410,20 +424,21 @@ func runCnbBuild(config *cnbBuildOptions, telemetryData *telemetry.CustomData, u
log.Entry().Info("skipping certificates update")
}
err = utils.RunExecutable(detectorPath, "-buildpacks", buildpacksPath, "-order", orderPath, "-platform", platformPath)
err = utils.RunExecutable(detectorPath, "-buildpacks", buildpacksPath, "-order", orderPath, "-platform", platformPath, "-no-color")
if err != nil {
log.SetErrorCategory(log.ErrorBuild)
return errors.Wrapf(err, "execution of '%s' failed", detectorPath)
}
err = utils.RunExecutable(builderPath, "-buildpacks", buildpacksPath, "-platform", platformPath)
err = utils.RunExecutable(builderPath, "-buildpacks", buildpacksPath, "-platform", platformPath, "-no-color")
if err != nil {
log.SetErrorCategory(log.ErrorBuild)
return errors.Wrapf(err, "execution of '%s' failed", builderPath)
}
utils.AppendEnv([]string{fmt.Sprintf("CNB_REGISTRY_AUTH=%s", string(cnbRegistryAuth))})
err = utils.RunExecutable(exporterPath, targets...)
exporterArgs := append([]string{"-no-color"}, targets...)
err = utils.RunExecutable(exporterPath, exporterArgs...)
if err != nil {
log.SetErrorCategory(log.ErrorBuild)
return errors.Wrapf(err, "execution of '%s' failed", exporterPath)

View File

@@ -22,7 +22,7 @@ type cnbBuildOptions struct {
ContainerImageTag string `json:"containerImageTag,omitempty"`
ContainerRegistryURL string `json:"containerRegistryUrl,omitempty"`
Buildpacks []string `json:"buildpacks,omitempty"`
BuildEnvVars []string `json:"buildEnvVars,omitempty"`
BuildEnvVars map[string]interface{} `json:"buildEnvVars,omitempty"`
Path string `json:"path,omitempty"`
ProjectDescriptor string `json:"projectDescriptor,omitempty"`
DockerConfigJSON string `json:"dockerConfigJSON,omitempty"`
@@ -157,9 +157,9 @@ func addCnbBuildFlags(cmd *cobra.Command, stepConfig *cnbBuildOptions) {
cmd.Flags().StringVar(&stepConfig.ContainerImageTag, "containerImageTag", os.Getenv("PIPER_containerImageTag"), "Tag of the container which will be built")
cmd.Flags().StringVar(&stepConfig.ContainerRegistryURL, "containerRegistryUrl", os.Getenv("PIPER_containerRegistryUrl"), "Container registry where the image should be pushed to")
cmd.Flags().StringSliceVar(&stepConfig.Buildpacks, "buildpacks", []string{}, "List of custom buildpacks to use in the form of '$HOSTNAME/$REPO[:$TAG]'.")
cmd.Flags().StringSliceVar(&stepConfig.BuildEnvVars, "buildEnvVars", []string{}, "List of custom environment variables used during a build in the form of 'KEY=VALUE'.")
cmd.Flags().StringVar(&stepConfig.Path, "path", os.Getenv("PIPER_path"), "The path should either point to a directory with your sources or an artifact in zip format.\nThis property determines the input to the buildpack.\n")
cmd.Flags().StringVar(&stepConfig.ProjectDescriptor, "projectDescriptor", `project.toml`, "Path to the project.toml file.\nSee [buildpacks.io](https://buildpacks.io/docs/reference/config/project-descriptor/) for the reference.\nParameters passed to the cnbBuild step will take precedence over the parameters set in the project.toml file.\n\nNote: Inline buildpacks (see [specification](https://buildpacks.io/docs/reference/config/project-descriptor/#build-_table-optional_)) are not supported yet.\n")
cmd.Flags().StringVar(&stepConfig.ProjectDescriptor, "projectDescriptor", `project.toml`, "Path to the project.toml file.\nSee [buildpacks.io](https://buildpacks.io/docs/reference/config/project-descriptor/) for the reference.\nParameters passed to the cnbBuild step will take precedence over the parameters set in the project.toml file, except the `env` block.\nEnvironment variables declared in a project descriptor file, will be merged with the `buildEnvVars` property, with the `buildEnvVars` having a precedence.\n\nNote: Inline buildpacks (see [specification](https://buildpacks.io/docs/reference/config/project-descriptor/#build-_table-optional_)) are not supported yet.\n")
cmd.Flags().StringVar(&stepConfig.DockerConfigJSON, "dockerConfigJSON", os.Getenv("PIPER_dockerConfigJSON"), "Path to the file `.docker/config.json` - this is typically provided by your CI/CD system. You can find more details about the Docker credentials in the [Docker documentation](https://docs.docker.com/engine/reference/commandline/login/).")
cmd.Flags().StringSliceVar(&stepConfig.CustomTLSCertificateLinks, "customTlsCertificateLinks", []string{}, "List containing download links of custom TLS certificates. This is required to ensure trusted connections to registries with custom certificates.")
cmd.Flags().StringSliceVar(&stepConfig.AdditionalTags, "additionalTags", []string{}, "List of tags which will be pushed to the registry (additionally to the provided `containerImageTag`), e.g. \"latest\".")
@@ -221,22 +221,26 @@ func cnbBuildMetadata() config.StepData {
Default: os.Getenv("PIPER_containerRegistryUrl"),
},
{
Name: "buildpacks",
ResourceRef: []config.ResourceReference{},
Scope: []string{"PARAMETERS", "STAGES", "STEPS"},
Type: "[]string",
Mandatory: false,
Aliases: []config.Alias{},
Default: []string{},
Name: "buildpacks",
ResourceRef: []config.ResourceReference{
{
Name: "commonPipelineEnvironment",
Param: "container/buildpacks",
},
},
Scope: []string{"PARAMETERS", "STAGES", "STEPS"},
Type: "[]string",
Mandatory: false,
Aliases: []config.Alias{},
Default: []string{},
},
{
Name: "buildEnvVars",
ResourceRef: []config.ResourceReference{},
Scope: []string{"PARAMETERS", "STAGES", "STEPS"},
Type: "[]string",
Type: "map[string]interface{}",
Mandatory: false,
Aliases: []config.Alias{},
Default: []string{},
},
{
Name: "path",

View File

@@ -55,9 +55,9 @@ func TestRunCnbBuild(t *testing.T) {
assert.Equal(t, "/cnb/lifecycle/detector", runner.Calls[0].Exec)
assert.Equal(t, "/cnb/lifecycle/builder", runner.Calls[1].Exec)
assert.Equal(t, "/cnb/lifecycle/exporter", runner.Calls[2].Exec)
assert.Equal(t, []string{"-buildpacks", "/cnb/buildpacks", "-order", "/cnb/order.toml", "-platform", "/tmp/platform"}, runner.Calls[0].Params)
assert.Equal(t, []string{"-buildpacks", "/cnb/buildpacks", "-platform", "/tmp/platform"}, runner.Calls[1].Params)
assert.Equal(t, []string{fmt.Sprintf("%s/%s:%s", registry, config.ContainerImageName, config.ContainerImageTag)}, runner.Calls[2].Params)
assert.Equal(t, []string{"-buildpacks", "/cnb/buildpacks", "-order", "/cnb/order.toml", "-platform", "/tmp/platform", "-no-color"}, runner.Calls[0].Params)
assert.Equal(t, []string{"-buildpacks", "/cnb/buildpacks", "-platform", "/tmp/platform", "-no-color"}, runner.Calls[1].Params)
assert.Equal(t, []string{"-no-color", fmt.Sprintf("%s/%s:%s", registry, config.ContainerImageName, config.ContainerImageTag)}, runner.Calls[2].Params)
assert.Equal(t, commonPipelineEnvironment.container.registryURL, fmt.Sprintf("https://%s", registry))
assert.Equal(t, commonPipelineEnvironment.container.imageNameTag, "my-image:0.0.1")
})
@@ -84,9 +84,9 @@ func TestRunCnbBuild(t *testing.T) {
assert.Equal(t, "/cnb/lifecycle/detector", runner.Calls[0].Exec)
assert.Equal(t, "/cnb/lifecycle/builder", runner.Calls[1].Exec)
assert.Equal(t, "/cnb/lifecycle/exporter", runner.Calls[2].Exec)
assert.Equal(t, []string{"-buildpacks", "/cnb/buildpacks", "-order", "/cnb/order.toml", "-platform", "/tmp/platform"}, runner.Calls[0].Params)
assert.Equal(t, []string{"-buildpacks", "/cnb/buildpacks", "-platform", "/tmp/platform"}, runner.Calls[1].Params)
assert.Equal(t, []string{fmt.Sprintf("%s/%s:%s", registry, config.ContainerImageName, config.ContainerImageTag)}, runner.Calls[2].Params)
assert.Equal(t, []string{"-buildpacks", "/cnb/buildpacks", "-order", "/cnb/order.toml", "-platform", "/tmp/platform", "-no-color"}, runner.Calls[0].Params)
assert.Equal(t, []string{"-buildpacks", "/cnb/buildpacks", "-platform", "/tmp/platform", "-no-color"}, runner.Calls[1].Params)
assert.Equal(t, []string{"-no-color", fmt.Sprintf("%s/%s:%s", registry, config.ContainerImageName, config.ContainerImageTag)}, runner.Calls[2].Params)
assert.Equal(t, commonPipelineEnvironment.container.registryURL, fmt.Sprintf("https://%s", registry))
assert.Equal(t, commonPipelineEnvironment.container.imageNameTag, "my-image:0.0.1")
})
@@ -100,8 +100,10 @@ func TestRunCnbBuild(t *testing.T) {
ContainerRegistryURL: registry,
DockerConfigJSON: "/path/to/test.json",
Buildpacks: []string{"test"},
BuildEnvVars: []string{"FOO=BAR"},
AdditionalTags: []string{"latest"},
BuildEnvVars: map[string]interface{}{
"FOO": "BAR",
},
AdditionalTags: []string{"latest"},
}
utils := newCnbBuildTestsUtils()
@@ -116,9 +118,9 @@ func TestRunCnbBuild(t *testing.T) {
assert.Equal(t, "/cnb/lifecycle/detector", runner.Calls[0].Exec)
assert.Equal(t, "/cnb/lifecycle/builder", runner.Calls[1].Exec)
assert.Equal(t, "/cnb/lifecycle/exporter", runner.Calls[2].Exec)
assert.Equal(t, []string{"-buildpacks", "/tmp/buildpacks", "-order", "/tmp/buildpacks/order.toml", "-platform", "/tmp/platform"}, runner.Calls[0].Params)
assert.Equal(t, []string{"-buildpacks", "/tmp/buildpacks", "-platform", "/tmp/platform"}, runner.Calls[1].Params)
assert.Equal(t, []string{fmt.Sprintf("%s/%s:%s", registry, config.ContainerImageName, config.ContainerImageTag), fmt.Sprintf("%s/%s:latest", registry, config.ContainerImageName)}, runner.Calls[2].Params)
assert.Equal(t, []string{"-buildpacks", "/tmp/buildpacks", "-order", "/tmp/buildpacks/order.toml", "-platform", "/tmp/platform", "-no-color"}, runner.Calls[0].Params)
assert.Equal(t, []string{"-buildpacks", "/tmp/buildpacks", "-platform", "/tmp/platform", "-no-color"}, runner.Calls[1].Params)
assert.Equal(t, []string{"-no-color", fmt.Sprintf("%s/%s:%s", registry, config.ContainerImageName, config.ContainerImageTag), fmt.Sprintf("%s/%s:latest", registry, config.ContainerImageName)}, runner.Calls[2].Params)
})
t.Run("success case (customTlsCertificates)", func(t *testing.T) {
@@ -161,9 +163,9 @@ func TestRunCnbBuild(t *testing.T) {
assert.Equal(t, "/cnb/lifecycle/detector", runner.Calls[0].Exec)
assert.Equal(t, "/cnb/lifecycle/builder", runner.Calls[1].Exec)
assert.Equal(t, "/cnb/lifecycle/exporter", runner.Calls[2].Exec)
assert.Equal(t, []string{"-buildpacks", "/tmp/buildpacks", "-order", "/tmp/buildpacks/order.toml", "-platform", "/tmp/platform"}, runner.Calls[0].Params)
assert.Equal(t, []string{"-buildpacks", "/tmp/buildpacks", "-platform", "/tmp/platform"}, runner.Calls[1].Params)
assert.Equal(t, []string{fmt.Sprintf("%s/%s:%s", registry, config.ContainerImageName, config.ContainerImageTag)}, runner.Calls[2].Params)
assert.Equal(t, []string{"-buildpacks", "/tmp/buildpacks", "-order", "/tmp/buildpacks/order.toml", "-platform", "/tmp/platform", "-no-color"}, runner.Calls[0].Params)
assert.Equal(t, []string{"-buildpacks", "/tmp/buildpacks", "-platform", "/tmp/platform", "-no-color"}, runner.Calls[1].Params)
assert.Equal(t, []string{"-no-color", fmt.Sprintf("%s/%s:%s", registry, config.ContainerImageName, config.ContainerImageTag)}, runner.Calls[2].Params)
})
t.Run("success case (additionalTags)", func(t *testing.T) {
@@ -188,7 +190,7 @@ func TestRunCnbBuild(t *testing.T) {
runner := utils.ExecMockRunner
assert.Equal(t, "/cnb/lifecycle/exporter", runner.Calls[2].Exec)
assert.ElementsMatch(t, []string{fmt.Sprintf("%s/%s:%s", registry, config.ContainerImageName, config.ContainerImageTag), fmt.Sprintf("%s/%s:3", registry, config.ContainerImageName), fmt.Sprintf("%s/%s:3.1", registry, config.ContainerImageName)}, runner.Calls[2].Params)
assert.ElementsMatch(t, []string{"-no-color", fmt.Sprintf("%s/%s:%s", registry, config.ContainerImageName, config.ContainerImageTag), fmt.Sprintf("%s/%s:3", registry, config.ContainerImageName), fmt.Sprintf("%s/%s:3.1", registry, config.ContainerImageName)}, runner.Calls[2].Params)
})
t.Run("error case: Invalid DockerConfigJSON file", func(t *testing.T) {

View File

@@ -39,7 +39,7 @@ cnbBuild(
dockerConfigJsonCredentialsId: 'DOCKER_REGISTRY_CREDS',
containerImageName: 'images/example',
containerImageTag: 'v0.0.1',
containerImageRegistryUrl: 'gcr.io'
containerRegistryUrl: 'gcr.io'
)
```
@@ -52,7 +52,7 @@ cnbBuild(
dockerImage: 'paketobuildpacks/builder:base',
containerImageName: 'images/example',
containerImageTag: 'v0.0.1',
containerImageRegistryUrl: 'gcr.io'
containerRegistryUrl: 'gcr.io'
)
```
@@ -64,7 +64,22 @@ cnbBuild(
dockerConfigJsonCredentialsId: 'DOCKER_REGISTRY_CREDS',
containerImageName: 'images/example',
containerImageTag: 'v0.0.1',
containerImageRegistryUrl: 'gcr.io',
containerRegistryUrl: 'gcr.io',
buildpacks: ['gcr.io/paketo-buildpacks/nodejs', 'paketo-community/build-plan']
)
```
### Example 4: Build environment variables
```groovy
cnbBuild(
script: script,
dockerConfigJsonCredentialsId: 'DOCKER_REGISTRY_CREDS',
containerImageName: 'images/example',
containerImageTag: 'v0.0.1',
containerRegistryUrl: 'gcr.io',
buildEnvVars: [
"FOO": "BAR"
]
)
```

View File

@@ -1,5 +1,7 @@
//go:build integration
// +build integration
// can be execute with go test -tags=integration ./integration/...
// can be executed with go test -tags=integration ./integration/...
package main
@@ -12,10 +14,10 @@ func TestNpmProject(t *testing.T) {
container := givenThisContainer(t, IntegrationTestDockerExecRunnerBundle{
Image: "paketobuildpacks/builder:full",
User: "cnb",
TestDir: []string{"testdata", "TestMtaIntegration", "npm"},
TestDir: []string{"testdata"},
})
container.whenRunningPiperCommand("cnbBuild", "--containerImageName", "not-found", "--containerImageTag", "0.0.1", "--containerRegistryUrl", "test", "--buildEnvVars", "BP_NODE_VERSION=16")
container.whenRunningPiperCommand("cnbBuild", "--customConfig", "TestCnbIntegration/config_env.yml", "--path", "TestMtaIntegration/npm", "--containerImageName", "not-found", "--containerImageTag", "0.0.1", "--containerRegistryUrl", "test")
container.assertHasOutput(t, "running command: /cnb/lifecycle/detector")
container.assertHasOutput(t, "Selected Node Engine version (using BP_NODE_VERSION): 16")
@@ -40,7 +42,7 @@ func TestProjectDescriptor(t *testing.T) {
container.assertHasOutput(t, "srv/hello.js matches include pattern")
container.assertHasOutput(t, "package.json matches include pattern")
container.assertHasOutput(t, "Downloading buildpack")
container.assertHasOutput(t, "Setting custom environment variables: '[BP_NODE_VERSION=16]'")
container.assertHasOutput(t, "Setting custom environment variables: 'map[BP_NODE_VERSION:16]'")
container.assertHasOutput(t, "Selected Node Engine version (using BP_NODE_VERSION): 16")
container.assertHasOutput(t, "Paketo NPM Start Buildpack")
container.assertHasOutput(t, "Saving test/not-found:0.0.1")

View File

@@ -0,0 +1,4 @@
steps:
cnbBuild:
buildEnvVars:
BP_NODE_VERSION: 16

View File

@@ -3,24 +3,17 @@ package cnbutils
import (
"fmt"
"path/filepath"
"strings"
)
func CreateEnvFiles(utils BuildUtils, platformPath string, env []string) error {
func CreateEnvFiles(utils BuildUtils, platformPath string, env map[string]interface{}) error {
envDir := filepath.Join(platformPath, "env")
err := utils.MkdirAll(envDir, 0755)
if err != nil {
return err
}
for _, e := range env {
eSplit := strings.SplitN(e, "=", 2)
if len(eSplit) != 2 {
return fmt.Errorf("invalid environment variable: %s", e)
}
err = utils.FileWrite(filepath.Join(envDir, eSplit[0]), []byte(eSplit[1]), 0644)
for k, v := range env {
err = utils.FileWrite(filepath.Join(envDir, k), []byte(fmt.Sprintf("%v", v)), 0644)
if err != nil {
return err
}

View File

@@ -14,7 +14,13 @@ func TestCreateEnvFiles(t *testing.T) {
FilesMock: &mock.FilesMock{},
}
err := CreateEnvFiles(mockUtils, "/tmp/platform", []string{"FOO=BAR", "BAR=BAZ", "COMPLEX={\"foo\": \"bar=3\"}"})
envVars := map[string]interface{}{
"FOO": "BAR",
"BAR": "BAZ",
"COMPLEX": "{\"foo\": \"bar=3\"}",
}
err := CreateEnvFiles(mockUtils, "/tmp/platform", envVars)
assert.NoError(t, err)
assert.True(t, mockUtils.HasWrittenFile("/tmp/platform/env/FOO"))
@@ -34,16 +40,6 @@ func TestCreateEnvFiles(t *testing.T) {
assert.Equal(t, "{\"foo\": \"bar=3\"}", string(result3))
})
t.Run("raises an error if environment variable is invalid", func(t *testing.T) {
mockUtils := MockUtils{
FilesMock: &mock.FilesMock{},
}
err := CreateEnvFiles(mockUtils, "/tmp/platform", []string{"FOOBAR"})
assert.Error(t, err)
assert.Equal(t, err.Error(), "invalid environment variable: FOOBAR")
})
t.Run("raises an error if unable to write to a file", func(t *testing.T) {
mockUtils := MockUtils{
FilesMock: &mock.FilesMock{
@@ -53,7 +49,7 @@ func TestCreateEnvFiles(t *testing.T) {
},
}
err := CreateEnvFiles(mockUtils, "/tmp/platform", []string{"FOO=BAR"})
err := CreateEnvFiles(mockUtils, "/tmp/platform", map[string]interface{}{"FOO": "BAR"})
assert.Error(t, err)
assert.Equal(t, err.Error(), "unable to create dir")
})

View File

@@ -3,7 +3,6 @@ package project
import (
"errors"
"fmt"
"github.com/SAP/jenkins-library/pkg/cnbutils"
"github.com/SAP/jenkins-library/pkg/cnbutils/registry"
@@ -45,7 +44,7 @@ type projectDescriptor struct {
type Descriptor struct {
Exclude *ignore.GitIgnore
Include *ignore.GitIgnore
EnvVars []string
EnvVars map[string]interface{}
Buildpacks []string
}
@@ -73,7 +72,7 @@ func ParseDescriptor(descriptorPath string, utils cnbutils.BuildUtils, httpClien
}
if rawDescriptor.Build.Env != nil && len(rawDescriptor.Build.Env) > 0 {
descriptor.EnvVars = rawDescriptor.Build.envToStringSlice()
descriptor.EnvVars = rawDescriptor.Build.envToMap()
}
if len(rawDescriptor.Build.Exclude) > 0 && len(rawDescriptor.Build.Include) > 0 {
@@ -91,17 +90,18 @@ func ParseDescriptor(descriptorPath string, utils cnbutils.BuildUtils, httpClien
return descriptor, nil
}
func (b *build) envToStringSlice() []string {
strSlice := []string{}
func (b *build) envToMap() map[string]interface{} {
envMap := map[string]interface{}{}
for _, e := range b.Env {
if len(e.Name) == 0 || len(e.Value) == 0 {
continue
}
strSlice = append(strSlice, fmt.Sprintf("%s=%s", e.Name, e.Value))
envMap[e.Name] = e.Value
}
return strSlice
return envMap
}
func (b *build) searchBuildpacks(httpClient piperhttp.Sender) ([]string, error) {

View File

@@ -62,8 +62,8 @@ id = "paketo-buildpacks/nodejs"
descriptor, err := ParseDescriptor("project.toml", utils, client)
assert.NoError(t, err)
assert.Contains(t, descriptor.EnvVars, "VAR1=VAL1")
assert.Contains(t, descriptor.EnvVars, "VAR2=VAL2")
assert.Equal(t, descriptor.EnvVars["VAR1"], "VAL1")
assert.Equal(t, descriptor.EnvVars["VAR2"], "VAL2")
assert.Contains(t, descriptor.Buildpacks, "index.docker.io/test-java@5.9.1")
assert.Contains(t, descriptor.Buildpacks, "index.docker.io/test-nodejs@1.1.1")

View File

@@ -78,9 +78,18 @@ spec:
- PARAMETERS
- STAGES
- STEPS
resourceRef:
- name: commonPipelineEnvironment
param: container/buildpacks
- name: buildEnvVars
type: "[]string"
description: List of custom environment variables used during a build in the form of 'KEY=VALUE'.
type: "map[string]interface{}"
description: |
Map of custom environment variables used during a build.
Example:
```yaml
buildEnvVars:
foo: bar
```
scope:
- PARAMETERS
- STAGES
@@ -99,7 +108,8 @@ spec:
description: |
Path to the project.toml file.
See [buildpacks.io](https://buildpacks.io/docs/reference/config/project-descriptor/) for the reference.
Parameters passed to the cnbBuild step will take precedence over the parameters set in the project.toml file.
Parameters passed to the cnbBuild step will take precedence over the parameters set in the project.toml file, except the `env` block.
Environment variables declared in a project descriptor file, will be merged with the `buildEnvVars` property, with the `buildEnvVars` having a precedence.
Note: Inline buildpacks (see [specification](https://buildpacks.io/docs/reference/config/project-descriptor/#build-_table-optional_)) are not supported yet.
default: project.toml