├── .github ├── pipeline-version ├── CODEOWNERS ├── dependabot.yml ├── workflows │ ├── pb-synchronize-labels.yml │ ├── pb-minimal-labels.yml │ ├── pb-update-draft-release.yml │ ├── pb-update-go.yml │ ├── pb-update-pipeline.yml │ ├── pb-update-tomcat-9.yml │ ├── pb-update-tomcat-11.yml │ ├── pb-update-tomcat-10-1.yml │ ├── pb-update-tomcat-logging-support.yml │ ├── pb-update-tomcat-lifecycle-support.yml │ ├── pb-update-tomcat-access-logging-support.yml │ ├── pb-tests.yml │ └── pb-create-package.yml ├── release-drafter.yml ├── labels.yml └── pipeline-descriptor.yml ├── tomcat ├── testdata │ ├── warfiles │ │ ├── api.war │ │ └── ui.war │ ├── c31f9fd9b9458dd8dda54ce879dc7b08f8de0e638cb0936abcaa2316e7460c1e │ │ └── stub-tomcat.tar.gz │ ├── e0a7e163cc9f1ffd41c8de3942c7c6b505090b7484c2ba9be846334e31c44a2c │ │ └── stub-tomcat-logging-support.jar │ ├── 22e708cfd301430cbcf8d1c2289503d8288d50df519ff4db7cca0ff9fe83c324 │ │ └── stub-external-configuration.tar.gz │ ├── 723126712c0b22a7fe409664adf1fbb78cf3040e313a82c06696f5058e190534 │ │ └── stub-tomcat-lifecycle-support.jar │ ├── d723bfe2ba67dfa92b24e3b6c7b2d0e6a963de7313350e306d470e44e330a5d2 │ │ └── stub-tomcat-access-logging-support.jar │ ├── 060818cbcdc2008563f0f9e2428ecf4a199a5821c5b8b1dcd11a67666c1e2cd6 │ │ └── stub-external-configuration-with-directory.tar.gz │ ├── c31f9fd9b9458dd8dda54ce879dc7b08f8de0e638cb0936abcaa2316e7460c1e.toml │ ├── 22e708cfd301430cbcf8d1c2289503d8288d50df519ff4db7cca0ff9fe83c324.toml │ ├── 060818cbcdc2008563f0f9e2428ecf4a199a5821c5b8b1dcd11a67666c1e2cd6.toml │ ├── e0a7e163cc9f1ffd41c8de3942c7c6b505090b7484c2ba9be846334e31c44a2c.toml │ ├── 723126712c0b22a7fe409664adf1fbb78cf3040e313a82c06696f5058e190534.toml │ └── d723bfe2ba67dfa92b24e3b6c7b2d0e6a963de7313350e306d470e44e330a5d2.toml ├── init_test.go ├── home.go ├── home_test.go ├── detect.go ├── detect_test.go ├── build.go ├── base.go ├── build_test.go └── base_test.go ├── internal └── util │ ├── containsWarFiles.go │ └── replaceInCatalinaProps.go ├── NOTICE ├── .gitignore ├── resources ├── context.xml ├── logging.properties └── server.xml ├── scripts └── build.sh ├── helper ├── init_test.go ├── access_logging_support.go └── access_logging_support_test.go ├── cmd ├── main │ └── main.go └── helper │ └── main.go ├── go.mod ├── go.sum ├── README.md └── LICENSE /.github/pipeline-version: -------------------------------------------------------------------------------- 1 | 1.44.0 2 | -------------------------------------------------------------------------------- /.github/CODEOWNERS: -------------------------------------------------------------------------------- 1 | * @paketo-buildpacks/java-maintainers -------------------------------------------------------------------------------- /tomcat/testdata/warfiles/api.war: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/paketo-buildpacks/apache-tomcat/HEAD/tomcat/testdata/warfiles/api.war -------------------------------------------------------------------------------- /tomcat/testdata/warfiles/ui.war: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/paketo-buildpacks/apache-tomcat/HEAD/tomcat/testdata/warfiles/ui.war -------------------------------------------------------------------------------- /tomcat/testdata/c31f9fd9b9458dd8dda54ce879dc7b08f8de0e638cb0936abcaa2316e7460c1e/stub-tomcat.tar.gz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/paketo-buildpacks/apache-tomcat/HEAD/tomcat/testdata/c31f9fd9b9458dd8dda54ce879dc7b08f8de0e638cb0936abcaa2316e7460c1e/stub-tomcat.tar.gz -------------------------------------------------------------------------------- /tomcat/testdata/e0a7e163cc9f1ffd41c8de3942c7c6b505090b7484c2ba9be846334e31c44a2c/stub-tomcat-logging-support.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/paketo-buildpacks/apache-tomcat/HEAD/tomcat/testdata/e0a7e163cc9f1ffd41c8de3942c7c6b505090b7484c2ba9be846334e31c44a2c/stub-tomcat-logging-support.jar -------------------------------------------------------------------------------- /tomcat/testdata/22e708cfd301430cbcf8d1c2289503d8288d50df519ff4db7cca0ff9fe83c324/stub-external-configuration.tar.gz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/paketo-buildpacks/apache-tomcat/HEAD/tomcat/testdata/22e708cfd301430cbcf8d1c2289503d8288d50df519ff4db7cca0ff9fe83c324/stub-external-configuration.tar.gz -------------------------------------------------------------------------------- /tomcat/testdata/723126712c0b22a7fe409664adf1fbb78cf3040e313a82c06696f5058e190534/stub-tomcat-lifecycle-support.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/paketo-buildpacks/apache-tomcat/HEAD/tomcat/testdata/723126712c0b22a7fe409664adf1fbb78cf3040e313a82c06696f5058e190534/stub-tomcat-lifecycle-support.jar -------------------------------------------------------------------------------- /tomcat/testdata/d723bfe2ba67dfa92b24e3b6c7b2d0e6a963de7313350e306d470e44e330a5d2/stub-tomcat-access-logging-support.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/paketo-buildpacks/apache-tomcat/HEAD/tomcat/testdata/d723bfe2ba67dfa92b24e3b6c7b2d0e6a963de7313350e306d470e44e330a5d2/stub-tomcat-access-logging-support.jar -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | - package-ecosystem: gomod 4 | directory: / 5 | schedule: 6 | interval: daily 7 | ignore: 8 | - dependency-name: github.com/onsi/gomega 9 | labels: 10 | - semver:patch 11 | - type:dependency-upgrade 12 | -------------------------------------------------------------------------------- /tomcat/testdata/060818cbcdc2008563f0f9e2428ecf4a199a5821c5b8b1dcd11a67666c1e2cd6/stub-external-configuration-with-directory.tar.gz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/paketo-buildpacks/apache-tomcat/HEAD/tomcat/testdata/060818cbcdc2008563f0f9e2428ecf4a199a5821c5b8b1dcd11a67666c1e2cd6/stub-external-configuration-with-directory.tar.gz -------------------------------------------------------------------------------- /tomcat/testdata/c31f9fd9b9458dd8dda54ce879dc7b08f8de0e638cb0936abcaa2316e7460c1e.toml: -------------------------------------------------------------------------------- 1 | id = "tomcat" 2 | uri = "https://localhost/stub-tomcat.tar.gz" 3 | sha256 = "c31f9fd9b9458dd8dda54ce879dc7b08f8de0e638cb0936abcaa2316e7460c1e" 4 | purl = "pkg:generic/tomcat@1.1.1" 5 | cpes = [ 6 | "cpe:2.3:a:apache:tomcat:1.1.1:*:*:*:*:*:*:*" 7 | ] -------------------------------------------------------------------------------- /tomcat/testdata/22e708cfd301430cbcf8d1c2289503d8288d50df519ff4db7cca0ff9fe83c324.toml: -------------------------------------------------------------------------------- 1 | id = "tomcat-external-configuration" 2 | uri = "https://localhost/stub-external-configuration.tar.gz" 3 | sha256 = "22e708cfd301430cbcf8d1c2289503d8288d50df519ff4db7cca0ff9fe83c324" 4 | purl = "pkg:generic/tomcat@1.1.1" 5 | cpes = [ 6 | "cpe:2.3:a:apache:tomcat:1.1.1:*:*:*:*:*:*:*" 7 | ] -------------------------------------------------------------------------------- /tomcat/testdata/060818cbcdc2008563f0f9e2428ecf4a199a5821c5b8b1dcd11a67666c1e2cd6.toml: -------------------------------------------------------------------------------- 1 | id = "tomcat-external-configuration" 2 | uri = "https://localhost/stub-external-configuration-with-directory.tar.gz" 3 | sha256 = "060818cbcdc2008563f0f9e2428ecf4a199a5821c5b8b1dcd11a67666c1e2cd6" 4 | purl = "pkg:generic/tomcat@1.1.1" 5 | cpes = [ 6 | "cpe:2.3:a:apache:tomcat:1.1.1:*:*:*:*:*:*:*" 7 | ] -------------------------------------------------------------------------------- /tomcat/testdata/e0a7e163cc9f1ffd41c8de3942c7c6b505090b7484c2ba9be846334e31c44a2c.toml: -------------------------------------------------------------------------------- 1 | id = "tomcat-logging-support" 2 | uri = "https://localhost/stub-tomcat-logging-support.jar" 3 | sha256 = "e0a7e163cc9f1ffd41c8de3942c7c6b505090b7484c2ba9be846334e31c44a2c" 4 | purl = "pkg:generic/tomcat-logging-support@3.3.0" 5 | cpes = [ 6 | "cpe:2.3:a:cloudfoundry:tomcat-logging-support:3.3.0:*:*:*:*:*:*:*" 7 | ] -------------------------------------------------------------------------------- /tomcat/testdata/723126712c0b22a7fe409664adf1fbb78cf3040e313a82c06696f5058e190534.toml: -------------------------------------------------------------------------------- 1 | id = "tomcat-lifecycle-support" 2 | uri = "https://localhost/stub-tomcat-lifecycle-support.jar" 3 | sha256 = "723126712c0b22a7fe409664adf1fbb78cf3040e313a82c06696f5058e190534" 4 | purl = "pkg:generic/tomcat-lifecycle-support@3.3.0" 5 | cpes = [ 6 | "cpe:2.3:a:cloudfoundry:tomcat-lifecycle-support:3.3.0:*:*:*:*:*:*:*" 7 | ] -------------------------------------------------------------------------------- /internal/util/containsWarFiles.go: -------------------------------------------------------------------------------- 1 | package util 2 | 3 | import ( 4 | "os" 5 | "strings" 6 | ) 7 | 8 | func ContainsWarFiles(dir string) (bool, error) { 9 | files, err := os.ReadDir(dir) 10 | if err != nil { 11 | return false, err 12 | } 13 | 14 | for _, file := range files { 15 | if strings.HasSuffix(file.Name(), ".war") { 16 | return true, nil 17 | } 18 | } 19 | return false, nil 20 | } 21 | -------------------------------------------------------------------------------- /tomcat/testdata/d723bfe2ba67dfa92b24e3b6c7b2d0e6a963de7313350e306d470e44e330a5d2.toml: -------------------------------------------------------------------------------- 1 | id = "tomcat-access-logging-support" 2 | uri = "https://localhost/stub-tomcat-access-logging-support.jar" 3 | sha256 = "d723bfe2ba67dfa92b24e3b6c7b2d0e6a963de7313350e306d470e44e330a5d2" 4 | purl = "pkg:generic/tomcat-access-logging-support@3.3.0" 5 | cpes = [ 6 | "cpe:2.3:a:cloudfoundry:tomcat-access-logging-support:3.3.0:*:*:*:*:*:*:*" 7 | ] -------------------------------------------------------------------------------- /NOTICE: -------------------------------------------------------------------------------- 1 | apache-tomcat 2 | 3 | Copyright (c) 2020-Present CloudFoundry.org Foundation, Inc. All Rights Reserved. 4 | 5 | This project is licensed to you under the Apache License, Version 2.0 (the "License"). 6 | You may not use this project except in compliance with the License. 7 | 8 | This project may include a number of subcomponents with separate copyright notices 9 | and license terms. Your use of these subcomponents is subject to the terms and 10 | conditions of the subcomponent's license, as noted in the LICENSE file. 11 | -------------------------------------------------------------------------------- /.github/workflows/pb-synchronize-labels.yml: -------------------------------------------------------------------------------- 1 | name: Synchronize Labels 2 | "on": 3 | push: 4 | branches: 5 | - main 6 | paths: 7 | - .github/labels.yml 8 | jobs: 9 | synchronize: 10 | name: Synchronize Labels 11 | runs-on: 12 | - ubuntu-latest 13 | steps: 14 | - uses: actions/checkout@v4 15 | - uses: micnncim/action-label-syncer@v1 16 | env: 17 | GITHUB_TOKEN: ${{ secrets.PAKETO_BOT_GITHUB_TOKEN }} 18 | -------------------------------------------------------------------------------- /internal/util/replaceInCatalinaProps.go: -------------------------------------------------------------------------------- 1 | package util 2 | 3 | import ( 4 | "os" 5 | "strings" 6 | ) 7 | 8 | func ReplaceInCatalinaProps(fileName string) error { 9 | input, err := os.ReadFile(fileName) 10 | if err != nil { 11 | return err 12 | } 13 | 14 | lines := strings.Split(string(input), "\n") 15 | 16 | for i, line := range lines { 17 | if strings.Contains(line, "common.loader=") { 18 | lines[i] = strings.Replace(lines[i], "common.loader=", "common.loader=${BPI_TOMCAT_ADDITIONAL_COMMON_JARS},", 1) 19 | } 20 | } 21 | output := strings.Join(lines, "\n") 22 | err = os.WriteFile(fileName, []byte(output), 0644) 23 | if err != nil { 24 | return err 25 | } 26 | return nil 27 | } 28 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Copyright 2018-2020 the original author or authors. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # https://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | bin/ 16 | linux/ 17 | dependencies/ 18 | package/ 19 | scratch/ 20 | 21 | -------------------------------------------------------------------------------- /resources/context.xml: -------------------------------------------------------------------------------- 1 | 2 | 17 | 18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /.github/release-drafter.yml: -------------------------------------------------------------------------------- 1 | template: $CHANGES 2 | name-template: $RESOLVED_VERSION 3 | tag-template: v$RESOLVED_VERSION 4 | categories: 5 | - title: ⭐️ Enhancements 6 | labels: 7 | - type:enhancement 8 | - title: "\U0001F41E Bug Fixes" 9 | labels: 10 | - type:bug 11 | - title: "\U0001F4D4 Documentation" 12 | labels: 13 | - type:documentation 14 | - title: ⛏ Dependency Upgrades 15 | labels: 16 | - type:dependency-upgrade 17 | - title: "\U0001F6A7 Tasks" 18 | labels: 19 | - type:task 20 | exclude-labels: 21 | - type:question 22 | version-resolver: 23 | major: 24 | labels: 25 | - semver:major 26 | minor: 27 | labels: 28 | - semver:minor 29 | patch: 30 | labels: 31 | - semver:patch 32 | default: patch 33 | -------------------------------------------------------------------------------- /.github/workflows/pb-minimal-labels.yml: -------------------------------------------------------------------------------- 1 | name: Minimal Labels 2 | "on": 3 | pull_request: 4 | types: 5 | - synchronize 6 | - reopened 7 | - labeled 8 | - unlabeled 9 | jobs: 10 | semver: 11 | name: Minimal Semver Labels 12 | runs-on: 13 | - ubuntu-latest 14 | steps: 15 | - uses: mheap/github-action-required-labels@v5 16 | with: 17 | count: 1 18 | labels: semver:major, semver:minor, semver:patch 19 | mode: exactly 20 | type: 21 | name: Minimal Type Labels 22 | runs-on: 23 | - ubuntu-latest 24 | steps: 25 | - uses: mheap/github-action-required-labels@v5 26 | with: 27 | count: 1 28 | labels: type:bug, type:dependency-upgrade, type:documentation, type:enhancement, type:question, type:task 29 | mode: exactly 30 | -------------------------------------------------------------------------------- /scripts/build.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | set -euo pipefail 3 | 4 | GOMOD=$(head -1 go.mod | awk '{print $2}') 5 | GOOS="linux" GOARCH="amd64" go build -ldflags='-s -w' -o "linux/amd64/bin/helper" "$GOMOD/cmd/helper" 6 | GOOS="linux" GOARCH="arm64" go build -ldflags='-s -w' -o "linux/arm64/bin/helper" "$GOMOD/cmd/helper" 7 | GOOS="linux" GOARCH="amd64" go build -ldflags='-s -w' -o linux/amd64/bin/main "$GOMOD/cmd/main" 8 | GOOS="linux" GOARCH="arm64" go build -ldflags='-s -w' -o linux/arm64/bin/main "$GOMOD/cmd/main" 9 | 10 | if [ "${STRIP:-false}" != "false" ]; then 11 | strip linux/amd64/bin/helper linux/arm64/bin/helper 12 | strip linux/amd64/bin/main linux/arm64/bin/main 13 | fi 14 | 15 | if [ "${COMPRESS:-none}" != "none" ]; then 16 | $COMPRESS linux/amd64/bin/helper linux/arm64/bin/helper 17 | $COMPRESS linux/amd64/bin/main linux/arm64/bin/main 18 | fi 19 | 20 | ln -fs main linux/amd64/bin/build 21 | ln -fs main linux/arm64/bin/build 22 | ln -fs main linux/amd64/bin/detect 23 | ln -fs main linux/arm64/bin/detect -------------------------------------------------------------------------------- /helper/init_test.go: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2018-2020 the original author or authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package helper_test 18 | 19 | import ( 20 | "testing" 21 | 22 | "github.com/sclevine/spec" 23 | "github.com/sclevine/spec/report" 24 | ) 25 | 26 | func TestUnit(t *testing.T) { 27 | suite := spec.New("helper", spec.Report(report.Terminal{})) 28 | suite("AccessLoggingSupport", testAccessLoggingSupport) 29 | suite.Run(t) 30 | } 31 | -------------------------------------------------------------------------------- /.github/workflows/pb-update-draft-release.yml: -------------------------------------------------------------------------------- 1 | name: Update Draft Release 2 | "on": 3 | push: 4 | branches: 5 | - main 6 | jobs: 7 | update: 8 | name: Update Draft Release 9 | runs-on: 10 | - ubuntu-latest 11 | steps: 12 | - id: release-drafter 13 | uses: release-drafter/release-drafter@v5 14 | env: 15 | GITHUB_TOKEN: ${{ secrets.PAKETO_BOT_GITHUB_TOKEN }} 16 | - uses: actions/checkout@v4 17 | - name: Update draft release with buildpack information 18 | uses: docker://ghcr.io/paketo-buildpacks/actions/draft-release:main 19 | with: 20 | github_token: ${{ secrets.PAKETO_BOT_GITHUB_TOKEN }} 21 | release_body: ${{ steps.release-drafter.outputs.body }} 22 | release_id: ${{ steps.release-drafter.outputs.id }} 23 | release_name: ${{ steps.release-drafter.outputs.name }} 24 | release_tag_name: ${{ steps.release-drafter.outputs.tag_name }} 25 | -------------------------------------------------------------------------------- /cmd/main/main.go: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2018-2020 the original author or authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package main 18 | 19 | import ( 20 | "os" 21 | 22 | "github.com/paketo-buildpacks/libpak" 23 | "github.com/paketo-buildpacks/libpak/bard" 24 | 25 | "github.com/paketo-buildpacks/apache-tomcat/v8/tomcat" 26 | ) 27 | 28 | func main() { 29 | logger := bard.NewLogger(os.Stdout) 30 | 31 | libpak.Main( 32 | tomcat.Detect{Logger: logger}, 33 | tomcat.Build{Logger: logger}, 34 | ) 35 | } 36 | -------------------------------------------------------------------------------- /tomcat/init_test.go: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2018-2020 the original author or authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package tomcat_test 18 | 19 | import ( 20 | "testing" 21 | 22 | "github.com/sclevine/spec" 23 | "github.com/sclevine/spec/report" 24 | ) 25 | 26 | func TestUnit(t *testing.T) { 27 | suite := spec.New("tomcat", spec.Report(report.Terminal{})) 28 | suite("Base", testBase) 29 | suite("Build", testBuild) 30 | suite("Detect", testDetect) 31 | suite("Home", testHome) 32 | suite.Run(t) 33 | } 34 | -------------------------------------------------------------------------------- /cmd/helper/main.go: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2018-2020 the original author or authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package main 18 | 19 | import ( 20 | "os" 21 | 22 | "github.com/paketo-buildpacks/libpak/bard" 23 | "github.com/paketo-buildpacks/libpak/sherpa" 24 | 25 | "github.com/paketo-buildpacks/apache-tomcat/v8/helper" 26 | ) 27 | 28 | func main() { 29 | sherpa.Execute(func() error { 30 | return sherpa.Helpers(map[string]sherpa.ExecD{ 31 | "access-logging-support": helper.AccessLoggingSupport{Logger: bard.NewLogger(os.Stdout)}, 32 | }) 33 | }) 34 | } 35 | -------------------------------------------------------------------------------- /resources/logging.properties: -------------------------------------------------------------------------------- 1 | # 2 | # Copyright 2018-2020 the original author or authors. 3 | # 4 | # Licensed under the Apache License, Version 2.0 (the "License"); 5 | # you may not use this file except in compliance with the License. 6 | # You may obtain a copy of the License at 7 | # 8 | # https://www.apache.org/licenses/LICENSE-2.0 9 | # 10 | # Unless required by applicable law or agreed to in writing, software 11 | # distributed under the License is distributed on an "AS IS" BASIS, 12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | # See the License for the specific language governing permissions and 14 | # limitations under the License. 15 | # 16 | 17 | handlers: org.cloudfoundry.tomcat.logging.CloudFoundryConsoleHandler 18 | .handlers: org.cloudfoundry.tomcat.logging.CloudFoundryConsoleHandler 19 | 20 | org.cloudfoundry.tomcat.logging.CloudFoundryConsoleHandler.level: FINE 21 | 22 | org.apache.catalina.core.ContainerBase.[Catalina].[localhost].level: INFO 23 | org.apache.catalina.core.ContainerBase.[Catalina].[localhost].[/manager].level: INFO 24 | org.apache.catalina.core.ContainerBase.[Catalina].[localhost].[/host-manager].level: INFO 25 | -------------------------------------------------------------------------------- /.github/labels.yml: -------------------------------------------------------------------------------- 1 | - name: semver:major 2 | description: A change requiring a major version bump 3 | color: f9d0c4 4 | - name: semver:minor 5 | description: A change requiring a minor version bump 6 | color: f9d0c4 7 | - name: semver:patch 8 | description: A change requiring a patch version bump 9 | color: f9d0c4 10 | - name: type:bug 11 | description: A general bug 12 | color: e3d9fc 13 | - name: type:dependency-upgrade 14 | description: A dependency upgrade 15 | color: e3d9fc 16 | - name: type:documentation 17 | description: A documentation update 18 | color: e3d9fc 19 | - name: type:enhancement 20 | description: A general enhancement 21 | color: e3d9fc 22 | - name: type:question 23 | description: A user question 24 | color: e3d9fc 25 | - name: type:task 26 | description: A general task 27 | color: e3d9fc 28 | - name: type:informational 29 | description: Provides information or notice to the community 30 | color: e3d9fc 31 | - name: type:poll 32 | description: Request for feedback from the community 33 | color: e3d9fc 34 | - name: note:ideal-for-contribution 35 | description: An issue that a contributor can help us with 36 | color: 54f7a8 37 | - name: note:on-hold 38 | description: We can't start working on this issue yet 39 | color: 54f7a8 40 | - name: note:good-first-issue 41 | description: A good first issue to get started with 42 | color: 54f7a8 43 | -------------------------------------------------------------------------------- /helper/access_logging_support.go: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2018-2020 the original author or authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package helper 18 | 19 | import ( 20 | "os" 21 | "strings" 22 | 23 | "github.com/paketo-buildpacks/libpak/bard" 24 | ) 25 | 26 | type AccessLoggingSupport struct { 27 | Logger bard.Logger 28 | } 29 | 30 | func (a AccessLoggingSupport) Execute() (map[string]string, error) { 31 | if _, ok := os.LookupEnv("BPL_TOMCAT_ACCESS_LOGGING_ENABLED"); !ok { 32 | return nil, nil 33 | } 34 | 35 | a.Logger.Info("Tomcat Access Logging Enabled") 36 | 37 | var values []string 38 | if s, ok := os.LookupEnv("JAVA_TOOL_OPTIONS"); ok { 39 | values = append(values, s) 40 | } 41 | 42 | values = append(values, "-Daccess.logging.enabled=true") 43 | 44 | return map[string]string{"JAVA_TOOL_OPTIONS": strings.Join(values, " ")}, nil 45 | } 46 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/paketo-buildpacks/apache-tomcat/v8 2 | 3 | go 1.25 4 | 5 | require ( 6 | github.com/buildpacks/libcnb v1.30.4 7 | github.com/heroku/color v0.0.6 8 | github.com/onsi/gomega v1.38.3 9 | github.com/paketo-buildpacks/libjvm v1.46.0 10 | github.com/paketo-buildpacks/libpak v1.73.0 11 | github.com/sclevine/spec v1.4.0 12 | ) 13 | 14 | require ( 15 | github.com/BurntSushi/toml v1.5.0 // indirect 16 | github.com/Masterminds/semver/v3 v3.4.0 // indirect 17 | github.com/creack/pty v1.1.24 // indirect 18 | github.com/davecgh/go-spew v1.1.1 // indirect 19 | github.com/google/go-cmp v0.7.0 // indirect 20 | github.com/h2non/filetype v1.1.3 // indirect 21 | github.com/imdario/mergo v0.3.16 // indirect 22 | github.com/magiconair/properties v1.8.10 // indirect 23 | github.com/mattn/go-colorable v0.1.14 // indirect 24 | github.com/mattn/go-isatty v0.0.20 // indirect 25 | github.com/mattn/go-shellwords v1.0.12 // indirect 26 | github.com/mitchellh/hashstructure/v2 v2.0.2 // indirect 27 | github.com/pavlo-v-chernykh/keystore-go/v4 v4.5.0 // indirect 28 | github.com/pmezard/go-difflib v1.0.0 // indirect 29 | github.com/stretchr/objx v0.5.3 // indirect 30 | github.com/stretchr/testify v1.11.1 // indirect 31 | github.com/xi2/xz v0.0.0-20171230120015-48954b6210f8 // indirect 32 | go.yaml.in/yaml/v3 v3.0.4 // indirect 33 | golang.org/x/crypto v0.46.0 // indirect 34 | golang.org/x/net v0.48.0 // indirect 35 | golang.org/x/sys v0.39.0 // indirect 36 | golang.org/x/text v0.32.0 // indirect 37 | gopkg.in/yaml.v3 v3.0.1 // indirect 38 | software.sslmate.com/src/go-pkcs12 v0.6.0 // indirect 39 | ) 40 | -------------------------------------------------------------------------------- /resources/server.xml: -------------------------------------------------------------------------------- 1 | 2 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 29 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | -------------------------------------------------------------------------------- /tomcat/home.go: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2018-2020 the original author or authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package tomcat 18 | 19 | import ( 20 | "fmt" 21 | "os" 22 | 23 | "github.com/buildpacks/libcnb" 24 | "github.com/paketo-buildpacks/libpak" 25 | "github.com/paketo-buildpacks/libpak/bard" 26 | "github.com/paketo-buildpacks/libpak/crush" 27 | ) 28 | 29 | type Home struct { 30 | LayerContributor libpak.DependencyLayerContributor 31 | Logger bard.Logger 32 | } 33 | 34 | func NewHome(dependency libpak.BuildpackDependency, cache libpak.DependencyCache) (Home, libcnb.BOMEntry) { 35 | contrib, entry := libpak.NewDependencyLayer(dependency, cache, libcnb.LayerTypes{ 36 | Cache: true, 37 | Launch: true, 38 | }) 39 | return Home{LayerContributor: contrib}, entry 40 | } 41 | 42 | func (h Home) Contribute(layer libcnb.Layer) (libcnb.Layer, error) { 43 | h.LayerContributor.Logger = h.Logger 44 | 45 | return h.LayerContributor.Contribute(layer, func(artifact *os.File) (libcnb.Layer, error) { 46 | h.Logger.Bodyf("Expanding to %s", layer.Path) 47 | if err := crush.Extract(artifact, layer.Path, 1); err != nil { 48 | return libcnb.Layer{}, fmt.Errorf("unable to expand Tomcat\n%w", err) 49 | } 50 | 51 | layer.LaunchEnvironment.Default("CATALINA_HOME", layer.Path) 52 | 53 | return layer, nil 54 | }) 55 | } 56 | 57 | func (h Home) Name() string { 58 | return h.LayerContributor.LayerName() 59 | } 60 | -------------------------------------------------------------------------------- /helper/access_logging_support_test.go: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2018-2020 the original author or authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package helper_test 18 | 19 | import ( 20 | "os" 21 | "testing" 22 | 23 | . "github.com/onsi/gomega" 24 | "github.com/sclevine/spec" 25 | 26 | "github.com/paketo-buildpacks/apache-tomcat/v8/helper" 27 | ) 28 | 29 | func testAccessLoggingSupport(t *testing.T, context spec.G, it spec.S) { 30 | var ( 31 | Expect = NewWithT(t).Expect 32 | 33 | a = helper.AccessLoggingSupport{} 34 | ) 35 | 36 | it("returns if $BPL_ACCESS_LOGGING_ENABLED is not set", func() { 37 | Expect(a.Execute()).To(BeNil()) 38 | }) 39 | 40 | context("$BPL_TOMCAT_ACCESS_LOGGING_ENABLED", func() { 41 | it.Before(func() { 42 | Expect(os.Setenv("BPL_TOMCAT_ACCESS_LOGGING_ENABLED", "")).To(Succeed()) 43 | }) 44 | 45 | it.After(func() { 46 | Expect(os.Unsetenv("BPL_TOMCAT_ACCESS_LOGGING_ENABLED")).To(Succeed()) 47 | }) 48 | 49 | it("contributes configuration", func() { 50 | Expect(a.Execute()).To(Equal(map[string]string{"JAVA_TOOL_OPTIONS": "-Daccess.logging.enabled=true"})) 51 | }) 52 | 53 | context("$JAVA_TOOL_OPTIONS", func() { 54 | it.Before(func() { 55 | Expect(os.Setenv("JAVA_TOOL_OPTIONS", "test-java-tool-options")).To(Succeed()) 56 | }) 57 | 58 | it.After(func() { 59 | Expect(os.Unsetenv("JAVA_TOOL_OPTIONS")).To(Succeed()) 60 | }) 61 | 62 | it("contributes configuration appended to existing $JAVA_TOOL_OPTIONS", func() { 63 | Expect(a.Execute()).To(Equal(map[string]string{ 64 | "JAVA_TOOL_OPTIONS": "test-java-tool-options -Daccess.logging.enabled=true", 65 | })) 66 | }) 67 | }) 68 | }) 69 | 70 | } 71 | -------------------------------------------------------------------------------- /tomcat/home_test.go: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2018-2020 the original author or authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package tomcat_test 18 | 19 | import ( 20 | "os" 21 | "path/filepath" 22 | "testing" 23 | 24 | "github.com/buildpacks/libcnb" 25 | . "github.com/onsi/gomega" 26 | "github.com/paketo-buildpacks/libpak" 27 | "github.com/sclevine/spec" 28 | 29 | "github.com/paketo-buildpacks/apache-tomcat/v8/tomcat" 30 | ) 31 | 32 | func testHome(t *testing.T, context spec.G, it spec.S) { 33 | var ( 34 | Expect = NewWithT(t).Expect 35 | 36 | ctx libcnb.BuildContext 37 | ) 38 | 39 | it.Before(func() { 40 | var err error 41 | 42 | ctx.Layers.Path, err = os.MkdirTemp("", "home-layers") 43 | Expect(err).NotTo(HaveOccurred()) 44 | }) 45 | 46 | it.After(func() { 47 | Expect(os.RemoveAll(ctx.Layers.Path)).To(Succeed()) 48 | }) 49 | 50 | it("contributes catalina home", func() { 51 | dep := libpak.BuildpackDependency{ 52 | ID: "tomcat", 53 | URI: "https://localhost/stub-tomcat.tar.gz", 54 | SHA256: "c31f9fd9b9458dd8dda54ce879dc7b08f8de0e638cb0936abcaa2316e7460c1e", 55 | PURL: "pkg:generic/tomcat@1.1.1", 56 | CPEs: []string{"cpe:2.3:a:apache:tomcat:1.1.1:*:*:*:*:*:*:*"}, 57 | } 58 | dc := libpak.DependencyCache{CachePath: "testdata"} 59 | 60 | h, _ := tomcat.NewHome(dep, dc) 61 | 62 | layer, err := ctx.Layers.Layer("test-layer") 63 | Expect(err).NotTo(HaveOccurred()) 64 | 65 | layer, err = h.Contribute(layer) 66 | Expect(err).NotTo(HaveOccurred()) 67 | 68 | Expect(layer.Launch).To(BeTrue()) 69 | Expect(filepath.Join(layer.Path, "fixture-marker")).To(BeARegularFile()) 70 | Expect(layer.LaunchEnvironment["CATALINA_HOME.default"]).To(Equal(layer.Path)) 71 | }) 72 | } 73 | -------------------------------------------------------------------------------- /.github/pipeline-descriptor.yml: -------------------------------------------------------------------------------- 1 | github: 2 | username: ${{ secrets.JAVA_GITHUB_USERNAME }} 3 | token: ${{ secrets.PAKETO_BOT_GITHUB_TOKEN }} 4 | 5 | codeowners: 6 | - path: "*" 7 | owner: "@paketo-buildpacks/java-maintainers" 8 | 9 | helpers: 10 | "bin/helper": "$GOMOD/cmd/helper" 11 | 12 | package: 13 | repositories: ["docker.io/paketobuildpacks/apache-tomcat"] 14 | register: true 15 | registry_token: ${{ secrets.PAKETO_BOT_GITHUB_TOKEN }} 16 | 17 | docker_credentials: 18 | - registry: docker.io 19 | username: ${{ secrets.PAKETO_BUILDPACKS_DOCKERHUB_USERNAME }} 20 | password: ${{ secrets.PAKETO_BUILDPACKS_DOCKERHUB_PASSWORD }} 21 | 22 | dependencies: 23 | - name: Tomcat 9 24 | id: tomcat 25 | version_pattern: "9\\.[\\d]+\\.[\\d]+" 26 | uses: docker://ghcr.io/paketo-buildpacks/actions/tomcat-dependency:main 27 | with: 28 | uri: https://downloads.apache.org/tomcat/tomcat-9 29 | - name: Tomcat 10.1 30 | id: tomcat 31 | version_pattern: "10\\.1\\.[\\d]+" 32 | uses: docker://ghcr.io/paketo-buildpacks/actions/tomcat-dependency:main 33 | with: 34 | uri: https://downloads.apache.org/tomcat/tomcat-10 35 | version_regex: "(10)\\.(1)\\.([\\d]+)" 36 | - name: Tomcat 11 37 | id: tomcat 38 | version_pattern: "11\\.[\\d]+.[\\d]+" 39 | uses: docker://ghcr.io/paketo-buildpacks/actions/tomcat-dependency:main 40 | with: 41 | uri: https://downloads.apache.org/tomcat/tomcat-11 42 | version_regex: "(11)\\.([\\d]+)\\.([\\d]+)" 43 | - id: tomcat-access-logging-support 44 | uses: docker://ghcr.io/paketo-buildpacks/actions/maven-dependency:main 45 | with: 46 | uri: https://repo1.maven.org/maven2 47 | group_id: org.cloudfoundry 48 | artifact_id: tomcat-access-logging-support 49 | version_regex: "^[\\d]+\\.[\\d]+\\.[\\d]+\\.RELEASE$" 50 | source_classifier: sources 51 | - id: tomcat-lifecycle-support 52 | uses: docker://ghcr.io/paketo-buildpacks/actions/maven-dependency:main 53 | with: 54 | uri: https://repo1.maven.org/maven2 55 | group_id: org.cloudfoundry 56 | artifact_id: tomcat-lifecycle-support 57 | version_regex: "^[\\d]+\\.[\\d]+\\.[\\d]+\\.RELEASE$" 58 | source_classifier: sources 59 | - id: tomcat-logging-support 60 | uses: docker://ghcr.io/paketo-buildpacks/actions/maven-dependency:main 61 | with: 62 | uri: https://repo1.maven.org/maven2 63 | group_id: org.cloudfoundry 64 | artifact_id: tomcat-logging-support 65 | version_regex: "^[\\d]+\\.[\\d]+\\.[\\d]+\\.RELEASE$" 66 | source_classifier: sources 67 | -------------------------------------------------------------------------------- /.github/workflows/pb-update-go.yml: -------------------------------------------------------------------------------- 1 | name: Update Go 2 | "on": 3 | schedule: 4 | - cron: 37 2 * * 1 5 | workflow_dispatch: {} 6 | jobs: 7 | update: 8 | name: Update Go 9 | runs-on: 10 | - ubuntu-latest 11 | steps: 12 | - uses: actions/setup-go@v5 13 | with: 14 | go-version: "1.25" 15 | - uses: actions/checkout@v4 16 | - name: Update Go Version & Modules 17 | id: update-go 18 | run: | 19 | #!/usr/bin/env bash 20 | 21 | set -euo pipefail 22 | 23 | if [ -z "${GO_VERSION:-}" ]; then 24 | echo "No go version set" 25 | exit 1 26 | fi 27 | 28 | OLD_GO_VERSION=$(grep -P '^go \d\.\d+' go.mod | cut -d ' ' -f 2 | cut -d '.' -f 1-2) 29 | 30 | go mod edit -go="$GO_VERSION" 31 | go mod tidy 32 | go get -u -t ./... 33 | go mod tidy 34 | 35 | git add go.mod go.sum 36 | git checkout -- . 37 | 38 | if [ "$OLD_GO_VERSION" == "$GO_VERSION" ]; then 39 | COMMIT_TITLE="Bump Go Modules" 40 | COMMIT_BODY="Bumps Go modules used by the project. See the commit for details on what modules were updated." 41 | COMMIT_SEMVER="semver:patch" 42 | else 43 | COMMIT_TITLE="Bump Go from ${OLD_GO_VERSION} to ${GO_VERSION}" 44 | 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." 45 | COMMIT_SEMVER="semver:minor" 46 | fi 47 | 48 | echo "commit-title=${COMMIT_TITLE}" >> "$GITHUB_OUTPUT" 49 | echo "commit-body=${COMMIT_BODY}" >> "$GITHUB_OUTPUT" 50 | echo "commit-semver=${COMMIT_SEMVER}" >> "$GITHUB_OUTPUT" 51 | env: 52 | GO_VERSION: "1.25" 53 | - uses: peter-evans/create-pull-request@v6 54 | with: 55 | author: ${{ secrets.JAVA_GITHUB_USERNAME }} <${{ secrets.JAVA_GITHUB_USERNAME }}@users.noreply.github.com> 56 | body: |- 57 | ${{ steps.update-go.outputs.commit-body }} 58 | 59 |
60 | Release Notes 61 | ${{ steps.pipeline.outputs.release-notes }} 62 |
63 | branch: update/go 64 | commit-message: |- 65 | ${{ steps.update-go.outputs.commit-title }} 66 | 67 | ${{ steps.update-go.outputs.commit-body }} 68 | delete-branch: true 69 | labels: ${{ steps.update-go.outputs.commit-semver }}, type:task 70 | signoff: true 71 | title: ${{ steps.update-go.outputs.commit-title }} 72 | token: ${{ secrets.PAKETO_BOT_GITHUB_TOKEN }} 73 | -------------------------------------------------------------------------------- /tomcat/detect.go: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2018-2020 the original author or authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package tomcat 18 | 19 | import ( 20 | "fmt" 21 | "os" 22 | "path/filepath" 23 | 24 | "github.com/buildpacks/libcnb" 25 | "github.com/paketo-buildpacks/apache-tomcat/v8/internal/util" 26 | "github.com/paketo-buildpacks/libjvm" 27 | "github.com/paketo-buildpacks/libpak" 28 | "github.com/paketo-buildpacks/libpak/bard" 29 | ) 30 | 31 | const ( 32 | PlanEntryJVMApplication = "jvm-application" 33 | PlanEntryJVMApplicationPackage = "jvm-application-package" 34 | PlanEntryJRE = "jre" 35 | PlanEntrySyft = "syft" 36 | PlanEntryJavaApplicationServer = "java-app-server" 37 | JavaAppServerTomcat = "tomcat" 38 | ) 39 | 40 | type Detect struct { 41 | Logger bard.Logger 42 | } 43 | 44 | func (d Detect) Detect(context libcnb.DetectContext) (libcnb.DetectResult, error) { 45 | cr, err := libpak.NewConfigurationResolver(context.Buildpack, &d.Logger) 46 | if err != nil { 47 | return libcnb.DetectResult{}, fmt.Errorf("unable to create configuration resolver\n%w", err) 48 | } 49 | 50 | appServer, _ := cr.Resolve("BP_JAVA_APP_SERVER") 51 | if appServer != "" && appServer != JavaAppServerTomcat { 52 | d.Logger.Infof("SKIPPED: buildpack does not match requested app server of [%s], buildpack supports [%s]", appServer, JavaAppServerTomcat) 53 | return libcnb.DetectResult{Pass: false}, nil 54 | } 55 | 56 | warFilesExist, _ := util.ContainsWarFiles(context.Application.Path) 57 | if !warFilesExist { 58 | m, err := libjvm.NewManifest(context.Application.Path) 59 | if err != nil { 60 | return libcnb.DetectResult{}, fmt.Errorf("unable to read manifest\n%w", err) 61 | } 62 | 63 | if _, ok := m.Get("Main-Class"); ok { 64 | d.Logger.Info("SKIPPED: Manifest attribute 'Main-Class' was found") 65 | return libcnb.DetectResult{Pass: false}, nil 66 | } 67 | } 68 | 69 | result := libcnb.DetectResult{ 70 | Pass: true, 71 | Plans: []libcnb.BuildPlan{ 72 | { 73 | Provides: []libcnb.BuildPlanProvide{ 74 | {Name: PlanEntryJVMApplication}, 75 | {Name: PlanEntryJavaApplicationServer}, 76 | }, 77 | Requires: []libcnb.BuildPlanRequire{ 78 | {Name: PlanEntrySyft}, 79 | {Name: PlanEntryJRE, Metadata: map[string]interface{}{"launch": true}}, 80 | {Name: PlanEntryJVMApplicationPackage}, 81 | {Name: PlanEntryJVMApplication}, 82 | {Name: PlanEntryJavaApplicationServer}, 83 | }, 84 | }, 85 | }, 86 | } 87 | 88 | file := filepath.Join(context.Application.Path, "WEB-INF") 89 | if _, err := os.Stat(file); err != nil && !os.IsNotExist(err) { 90 | return libcnb.DetectResult{}, fmt.Errorf("unable to stat file %s\n%w", file, err) 91 | } else if os.IsNotExist(err) && !warFilesExist { 92 | d.Logger.Info("PASSED: a WEB-INF directory was not found, this is normal when building from source") 93 | return result, nil 94 | } 95 | 96 | result.Plans[0].Provides = append(result.Plans[0].Provides, libcnb.BuildPlanProvide{Name: PlanEntryJVMApplicationPackage}) 97 | return result, nil 98 | } 99 | -------------------------------------------------------------------------------- /.github/workflows/pb-update-pipeline.yml: -------------------------------------------------------------------------------- 1 | name: Update Pipeline 2 | "on": 3 | push: 4 | branches: 5 | - main 6 | paths: 7 | - .github/pipeline-descriptor.yml 8 | schedule: 9 | - cron: 0 5 * * 1-5 10 | workflow_dispatch: {} 11 | jobs: 12 | update: 13 | name: Update Pipeline 14 | runs-on: 15 | - ubuntu-latest 16 | steps: 17 | - uses: actions/setup-go@v5 18 | with: 19 | go-version: "1.25" 20 | - name: Install octo 21 | run: | 22 | #!/usr/bin/env bash 23 | 24 | set -euo pipefail 25 | 26 | go install -ldflags="-s -w" github.com/paketo-buildpacks/pipeline-builder/cmd/octo@latest 27 | - uses: actions/checkout@v4 28 | - name: Update Pipeline 29 | id: pipeline 30 | run: | 31 | #!/usr/bin/env bash 32 | 33 | set -euo pipefail 34 | 35 | if [[ -f .github/pipeline-version ]]; then 36 | OLD_VERSION=$(cat .github/pipeline-version) 37 | else 38 | OLD_VERSION="0.0.0" 39 | fi 40 | 41 | rm .github/workflows/pb-*.yml || true 42 | octo --descriptor "${DESCRIPTOR}" 43 | 44 | PAYLOAD=$(gh api /repos/paketo-buildpacks/pipeline-builder/releases/latest) 45 | 46 | NEW_VERSION=$(jq -n -r --argjson PAYLOAD "${PAYLOAD}" '$PAYLOAD.name') 47 | echo "${NEW_VERSION}" > .github/pipeline-version 48 | 49 | RELEASE_NOTES=$( 50 | gh api \ 51 | -F text="$(jq -n -r --argjson PAYLOAD "${PAYLOAD}" '$PAYLOAD.body')" \ 52 | -F mode="gfm" \ 53 | -F context="paketo-buildpacks/pipeline-builder" \ 54 | -X POST /markdown 55 | ) 56 | 57 | git add .github/ 58 | git add .gitignore 59 | 60 | if [ -f scripts/build.sh ]; then 61 | git add scripts/build.sh 62 | fi 63 | 64 | git checkout -- . 65 | 66 | echo "old-version=${OLD_VERSION}" >> "$GITHUB_OUTPUT" 67 | echo "new-version=${NEW_VERSION}" >> "$GITHUB_OUTPUT" 68 | 69 | 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 70 | 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 71 | env: 72 | DESCRIPTOR: .github/pipeline-descriptor.yml 73 | GITHUB_TOKEN: ${{ secrets.PAKETO_BOT_GITHUB_TOKEN }} 74 | - uses: peter-evans/create-pull-request@v6 75 | with: 76 | author: ${{ secrets.JAVA_GITHUB_USERNAME }} <${{ secrets.JAVA_GITHUB_USERNAME }}@users.noreply.github.com> 77 | body: |- 78 | Bumps pipeline from `${{ steps.pipeline.outputs.old-version }}` to `${{ steps.pipeline.outputs.new-version }}`. 79 | 80 |
81 | Release Notes 82 | ${{ steps.pipeline.outputs.release-notes }} 83 |
84 | branch: update/pipeline 85 | commit-message: |- 86 | Bump pipeline from ${{ steps.pipeline.outputs.old-version }} to ${{ steps.pipeline.outputs.new-version }} 87 | 88 | Bumps pipeline from ${{ steps.pipeline.outputs.old-version }} to ${{ steps.pipeline.outputs.new-version }}. 89 | delete-branch: true 90 | labels: semver:patch, type:task 91 | signoff: true 92 | title: Bump pipeline from ${{ steps.pipeline.outputs.old-version }} to ${{ steps.pipeline.outputs.new-version }} 93 | token: ${{ secrets.PAKETO_BOT_GITHUB_TOKEN }} 94 | -------------------------------------------------------------------------------- /.github/workflows/pb-update-tomcat-9.yml: -------------------------------------------------------------------------------- 1 | name: Update Tomcat 9 2 | "on": 3 | schedule: 4 | - cron: 0 5 * * 1-5 5 | workflow_dispatch: {} 6 | jobs: 7 | update: 8 | name: Update Buildpack Dependency 9 | runs-on: 10 | - ubuntu-latest 11 | steps: 12 | - uses: actions/setup-go@v5 13 | with: 14 | go-version: "1.25" 15 | - name: Install update-buildpack-dependency 16 | run: | 17 | #!/usr/bin/env bash 18 | 19 | set -euo pipefail 20 | 21 | go install -ldflags="-s -w" github.com/paketo-buildpacks/libpak/cmd/update-buildpack-dependency@latest 22 | - uses: buildpacks/github-actions/setup-tools@v5.9.7 23 | with: 24 | crane-version: 0.20.3 25 | yj-version: 5.1.0 26 | - uses: actions/checkout@v4 27 | - id: dependency 28 | uses: docker://ghcr.io/paketo-buildpacks/actions/tomcat-dependency:main 29 | with: 30 | uri: https://downloads.apache.org/tomcat/tomcat-9 31 | - name: Update Buildpack Dependency 32 | id: buildpack 33 | run: | 34 | #!/usr/bin/env bash 35 | 36 | set -euo pipefail 37 | 38 | VERSION_DEPS=$(yj -tj < buildpack.toml | jq -r ".metadata.dependencies[] | select( .id == env.ID ) | select( .version | test( env.VERSION_PATTERN ) )") 39 | ARCH=${ARCH:-amd64} 40 | OLD_VERSION=$(echo "$VERSION_DEPS" | jq -r 'select( .purl | contains( env.ARCH ) ) | .version') 41 | 42 | if [ -z "$OLD_VERSION" ]; then 43 | ARCH="" # empty means noarch 44 | OLD_VERSION=$(echo "$VERSION_DEPS" | jq -r ".version") 45 | fi 46 | 47 | update-buildpack-dependency \ 48 | --buildpack-toml buildpack.toml \ 49 | --id "${ID}" \ 50 | --arch "${ARCH}" \ 51 | --version-pattern "${VERSION_PATTERN}" \ 52 | --version "${VERSION}" \ 53 | --cpe-pattern "${CPE_PATTERN:-}" \ 54 | --cpe "${CPE:-}" \ 55 | --purl-pattern "${PURL_PATTERN:-}" \ 56 | --purl "${PURL:-}" \ 57 | --uri "${URI}" \ 58 | --sha256 "${SHA256}" \ 59 | --source "${SOURCE_URI}" \ 60 | --source-sha256 "${SOURCE_SHA256}" 61 | 62 | git add buildpack.toml 63 | git checkout -- . 64 | 65 | if [ "$(echo "$OLD_VERSION" | awk -F '.' '{print $1}')" != "$(echo "$VERSION" | awk -F '.' '{print $1}')" ]; then 66 | LABEL="semver:major" 67 | elif [ "$(echo "$OLD_VERSION" | awk -F '.' '{print $2}')" != "$(echo "$VERSION" | awk -F '.' '{print $2}')" ]; then 68 | LABEL="semver:minor" 69 | else 70 | LABEL="semver:patch" 71 | fi 72 | 73 | echo "old-version=${OLD_VERSION}" >> "$GITHUB_OUTPUT" 74 | echo "new-version=${VERSION}" >> "$GITHUB_OUTPUT" 75 | echo "version-label=${LABEL}" >> "$GITHUB_OUTPUT" 76 | env: 77 | ARCH: "" 78 | CPE: ${{ steps.dependency.outputs.cpe }} 79 | CPE_PATTERN: "" 80 | ID: tomcat 81 | PURL: ${{ steps.dependency.outputs.purl }} 82 | PURL_PATTERN: "" 83 | SHA256: ${{ steps.dependency.outputs.sha256 }} 84 | SOURCE_SHA256: ${{ steps.dependency.outputs.source_sha256 }} 85 | SOURCE_URI: ${{ steps.dependency.outputs.source }} 86 | URI: ${{ steps.dependency.outputs.uri }} 87 | VERSION: ${{ steps.dependency.outputs.version }} 88 | VERSION_PATTERN: 9\.[\d]+\.[\d]+ 89 | - uses: peter-evans/create-pull-request@v6 90 | with: 91 | author: ${{ secrets.JAVA_GITHUB_USERNAME }} <${{ secrets.JAVA_GITHUB_USERNAME }}@users.noreply.github.com> 92 | body: Bumps `Tomcat 9` from `${{ steps.buildpack.outputs.old-version }}` to `${{ steps.buildpack.outputs.new-version }}`. 93 | branch: update/buildpack/tomcat-9 94 | commit-message: |- 95 | Bump Tomcat 9 from ${{ steps.buildpack.outputs.old-version }} to ${{ steps.buildpack.outputs.new-version }} 96 | 97 | Bumps Tomcat 9 from ${{ steps.buildpack.outputs.old-version }} to ${{ steps.buildpack.outputs.new-version }}. 98 | delete-branch: true 99 | labels: ${{ steps.buildpack.outputs.version-label }}, type:dependency-upgrade 100 | signoff: true 101 | title: Bump Tomcat 9 from ${{ steps.buildpack.outputs.old-version }} to ${{ steps.buildpack.outputs.new-version }} 102 | token: ${{ secrets.PAKETO_BOT_GITHUB_TOKEN }} 103 | -------------------------------------------------------------------------------- /.github/workflows/pb-update-tomcat-11.yml: -------------------------------------------------------------------------------- 1 | name: Update Tomcat 11 2 | "on": 3 | schedule: 4 | - cron: 0 5 * * 1-5 5 | workflow_dispatch: {} 6 | jobs: 7 | update: 8 | name: Update Buildpack Dependency 9 | runs-on: 10 | - ubuntu-latest 11 | steps: 12 | - uses: actions/setup-go@v5 13 | with: 14 | go-version: "1.25" 15 | - name: Install update-buildpack-dependency 16 | run: | 17 | #!/usr/bin/env bash 18 | 19 | set -euo pipefail 20 | 21 | go install -ldflags="-s -w" github.com/paketo-buildpacks/libpak/cmd/update-buildpack-dependency@latest 22 | - uses: buildpacks/github-actions/setup-tools@v5.9.7 23 | with: 24 | crane-version: 0.20.3 25 | yj-version: 5.1.0 26 | - uses: actions/checkout@v4 27 | - id: dependency 28 | uses: docker://ghcr.io/paketo-buildpacks/actions/tomcat-dependency:main 29 | with: 30 | uri: https://downloads.apache.org/tomcat/tomcat-11 31 | version_regex: (11)\.([\d]+)\.([\d]+) 32 | - name: Update Buildpack Dependency 33 | id: buildpack 34 | run: | 35 | #!/usr/bin/env bash 36 | 37 | set -euo pipefail 38 | 39 | VERSION_DEPS=$(yj -tj < buildpack.toml | jq -r ".metadata.dependencies[] | select( .id == env.ID ) | select( .version | test( env.VERSION_PATTERN ) )") 40 | ARCH=${ARCH:-amd64} 41 | OLD_VERSION=$(echo "$VERSION_DEPS" | jq -r 'select( .purl | contains( env.ARCH ) ) | .version') 42 | 43 | if [ -z "$OLD_VERSION" ]; then 44 | ARCH="" # empty means noarch 45 | OLD_VERSION=$(echo "$VERSION_DEPS" | jq -r ".version") 46 | fi 47 | 48 | update-buildpack-dependency \ 49 | --buildpack-toml buildpack.toml \ 50 | --id "${ID}" \ 51 | --arch "${ARCH}" \ 52 | --version-pattern "${VERSION_PATTERN}" \ 53 | --version "${VERSION}" \ 54 | --cpe-pattern "${CPE_PATTERN:-}" \ 55 | --cpe "${CPE:-}" \ 56 | --purl-pattern "${PURL_PATTERN:-}" \ 57 | --purl "${PURL:-}" \ 58 | --uri "${URI}" \ 59 | --sha256 "${SHA256}" \ 60 | --source "${SOURCE_URI}" \ 61 | --source-sha256 "${SOURCE_SHA256}" 62 | 63 | git add buildpack.toml 64 | git checkout -- . 65 | 66 | if [ "$(echo "$OLD_VERSION" | awk -F '.' '{print $1}')" != "$(echo "$VERSION" | awk -F '.' '{print $1}')" ]; then 67 | LABEL="semver:major" 68 | elif [ "$(echo "$OLD_VERSION" | awk -F '.' '{print $2}')" != "$(echo "$VERSION" | awk -F '.' '{print $2}')" ]; then 69 | LABEL="semver:minor" 70 | else 71 | LABEL="semver:patch" 72 | fi 73 | 74 | echo "old-version=${OLD_VERSION}" >> "$GITHUB_OUTPUT" 75 | echo "new-version=${VERSION}" >> "$GITHUB_OUTPUT" 76 | echo "version-label=${LABEL}" >> "$GITHUB_OUTPUT" 77 | env: 78 | ARCH: "" 79 | CPE: ${{ steps.dependency.outputs.cpe }} 80 | CPE_PATTERN: "" 81 | ID: tomcat 82 | PURL: ${{ steps.dependency.outputs.purl }} 83 | PURL_PATTERN: "" 84 | SHA256: ${{ steps.dependency.outputs.sha256 }} 85 | SOURCE_SHA256: ${{ steps.dependency.outputs.source_sha256 }} 86 | SOURCE_URI: ${{ steps.dependency.outputs.source }} 87 | URI: ${{ steps.dependency.outputs.uri }} 88 | VERSION: ${{ steps.dependency.outputs.version }} 89 | VERSION_PATTERN: 11\.[\d]+.[\d]+ 90 | - uses: peter-evans/create-pull-request@v6 91 | with: 92 | author: ${{ secrets.JAVA_GITHUB_USERNAME }} <${{ secrets.JAVA_GITHUB_USERNAME }}@users.noreply.github.com> 93 | body: Bumps `Tomcat 11` from `${{ steps.buildpack.outputs.old-version }}` to `${{ steps.buildpack.outputs.new-version }}`. 94 | branch: update/buildpack/tomcat-11 95 | commit-message: |- 96 | Bump Tomcat 11 from ${{ steps.buildpack.outputs.old-version }} to ${{ steps.buildpack.outputs.new-version }} 97 | 98 | Bumps Tomcat 11 from ${{ steps.buildpack.outputs.old-version }} to ${{ steps.buildpack.outputs.new-version }}. 99 | delete-branch: true 100 | labels: ${{ steps.buildpack.outputs.version-label }}, type:dependency-upgrade 101 | signoff: true 102 | title: Bump Tomcat 11 from ${{ steps.buildpack.outputs.old-version }} to ${{ steps.buildpack.outputs.new-version }} 103 | token: ${{ secrets.PAKETO_BOT_GITHUB_TOKEN }} 104 | -------------------------------------------------------------------------------- /.github/workflows/pb-update-tomcat-10-1.yml: -------------------------------------------------------------------------------- 1 | name: Update Tomcat 10.1 2 | "on": 3 | schedule: 4 | - cron: 0 5 * * 1-5 5 | workflow_dispatch: {} 6 | jobs: 7 | update: 8 | name: Update Buildpack Dependency 9 | runs-on: 10 | - ubuntu-latest 11 | steps: 12 | - uses: actions/setup-go@v5 13 | with: 14 | go-version: "1.25" 15 | - name: Install update-buildpack-dependency 16 | run: | 17 | #!/usr/bin/env bash 18 | 19 | set -euo pipefail 20 | 21 | go install -ldflags="-s -w" github.com/paketo-buildpacks/libpak/cmd/update-buildpack-dependency@latest 22 | - uses: buildpacks/github-actions/setup-tools@v5.9.7 23 | with: 24 | crane-version: 0.20.3 25 | yj-version: 5.1.0 26 | - uses: actions/checkout@v4 27 | - id: dependency 28 | uses: docker://ghcr.io/paketo-buildpacks/actions/tomcat-dependency:main 29 | with: 30 | uri: https://downloads.apache.org/tomcat/tomcat-10 31 | version_regex: (10)\.(1)\.([\d]+) 32 | - name: Update Buildpack Dependency 33 | id: buildpack 34 | run: | 35 | #!/usr/bin/env bash 36 | 37 | set -euo pipefail 38 | 39 | VERSION_DEPS=$(yj -tj < buildpack.toml | jq -r ".metadata.dependencies[] | select( .id == env.ID ) | select( .version | test( env.VERSION_PATTERN ) )") 40 | ARCH=${ARCH:-amd64} 41 | OLD_VERSION=$(echo "$VERSION_DEPS" | jq -r 'select( .purl | contains( env.ARCH ) ) | .version') 42 | 43 | if [ -z "$OLD_VERSION" ]; then 44 | ARCH="" # empty means noarch 45 | OLD_VERSION=$(echo "$VERSION_DEPS" | jq -r ".version") 46 | fi 47 | 48 | update-buildpack-dependency \ 49 | --buildpack-toml buildpack.toml \ 50 | --id "${ID}" \ 51 | --arch "${ARCH}" \ 52 | --version-pattern "${VERSION_PATTERN}" \ 53 | --version "${VERSION}" \ 54 | --cpe-pattern "${CPE_PATTERN:-}" \ 55 | --cpe "${CPE:-}" \ 56 | --purl-pattern "${PURL_PATTERN:-}" \ 57 | --purl "${PURL:-}" \ 58 | --uri "${URI}" \ 59 | --sha256 "${SHA256}" \ 60 | --source "${SOURCE_URI}" \ 61 | --source-sha256 "${SOURCE_SHA256}" 62 | 63 | git add buildpack.toml 64 | git checkout -- . 65 | 66 | if [ "$(echo "$OLD_VERSION" | awk -F '.' '{print $1}')" != "$(echo "$VERSION" | awk -F '.' '{print $1}')" ]; then 67 | LABEL="semver:major" 68 | elif [ "$(echo "$OLD_VERSION" | awk -F '.' '{print $2}')" != "$(echo "$VERSION" | awk -F '.' '{print $2}')" ]; then 69 | LABEL="semver:minor" 70 | else 71 | LABEL="semver:patch" 72 | fi 73 | 74 | echo "old-version=${OLD_VERSION}" >> "$GITHUB_OUTPUT" 75 | echo "new-version=${VERSION}" >> "$GITHUB_OUTPUT" 76 | echo "version-label=${LABEL}" >> "$GITHUB_OUTPUT" 77 | env: 78 | ARCH: "" 79 | CPE: ${{ steps.dependency.outputs.cpe }} 80 | CPE_PATTERN: "" 81 | ID: tomcat 82 | PURL: ${{ steps.dependency.outputs.purl }} 83 | PURL_PATTERN: "" 84 | SHA256: ${{ steps.dependency.outputs.sha256 }} 85 | SOURCE_SHA256: ${{ steps.dependency.outputs.source_sha256 }} 86 | SOURCE_URI: ${{ steps.dependency.outputs.source }} 87 | URI: ${{ steps.dependency.outputs.uri }} 88 | VERSION: ${{ steps.dependency.outputs.version }} 89 | VERSION_PATTERN: 10\.1\.[\d]+ 90 | - uses: peter-evans/create-pull-request@v6 91 | with: 92 | author: ${{ secrets.JAVA_GITHUB_USERNAME }} <${{ secrets.JAVA_GITHUB_USERNAME }}@users.noreply.github.com> 93 | body: Bumps `Tomcat 10.1` from `${{ steps.buildpack.outputs.old-version }}` to `${{ steps.buildpack.outputs.new-version }}`. 94 | branch: update/buildpack/tomcat-10-1 95 | commit-message: |- 96 | Bump Tomcat 10.1 from ${{ steps.buildpack.outputs.old-version }} to ${{ steps.buildpack.outputs.new-version }} 97 | 98 | Bumps Tomcat 10.1 from ${{ steps.buildpack.outputs.old-version }} to ${{ steps.buildpack.outputs.new-version }}. 99 | delete-branch: true 100 | labels: ${{ steps.buildpack.outputs.version-label }}, type:dependency-upgrade 101 | signoff: true 102 | title: Bump Tomcat 10.1 from ${{ steps.buildpack.outputs.old-version }} to ${{ steps.buildpack.outputs.new-version }} 103 | token: ${{ secrets.PAKETO_BOT_GITHUB_TOKEN }} 104 | -------------------------------------------------------------------------------- /.github/workflows/pb-update-tomcat-logging-support.yml: -------------------------------------------------------------------------------- 1 | name: Update tomcat-logging-support 2 | "on": 3 | schedule: 4 | - cron: 0 5 * * 1-5 5 | workflow_dispatch: {} 6 | jobs: 7 | update: 8 | name: Update Buildpack Dependency 9 | runs-on: 10 | - ubuntu-latest 11 | steps: 12 | - uses: actions/setup-go@v5 13 | with: 14 | go-version: "1.25" 15 | - name: Install update-buildpack-dependency 16 | run: | 17 | #!/usr/bin/env bash 18 | 19 | set -euo pipefail 20 | 21 | go install -ldflags="-s -w" github.com/paketo-buildpacks/libpak/cmd/update-buildpack-dependency@latest 22 | - uses: buildpacks/github-actions/setup-tools@v5.9.7 23 | with: 24 | crane-version: 0.20.3 25 | yj-version: 5.1.0 26 | - uses: actions/checkout@v4 27 | - id: dependency 28 | uses: docker://ghcr.io/paketo-buildpacks/actions/maven-dependency:main 29 | with: 30 | artifact_id: tomcat-logging-support 31 | group_id: org.cloudfoundry 32 | source_classifier: sources 33 | uri: https://repo1.maven.org/maven2 34 | version_regex: ^[\d]+\.[\d]+\.[\d]+\.RELEASE$ 35 | - name: Update Buildpack Dependency 36 | id: buildpack 37 | run: | 38 | #!/usr/bin/env bash 39 | 40 | set -euo pipefail 41 | 42 | VERSION_DEPS=$(yj -tj < buildpack.toml | jq -r ".metadata.dependencies[] | select( .id == env.ID ) | select( .version | test( env.VERSION_PATTERN ) )") 43 | ARCH=${ARCH:-amd64} 44 | OLD_VERSION=$(echo "$VERSION_DEPS" | jq -r 'select( .purl | contains( env.ARCH ) ) | .version') 45 | 46 | if [ -z "$OLD_VERSION" ]; then 47 | ARCH="" # empty means noarch 48 | OLD_VERSION=$(echo "$VERSION_DEPS" | jq -r ".version") 49 | fi 50 | 51 | update-buildpack-dependency \ 52 | --buildpack-toml buildpack.toml \ 53 | --id "${ID}" \ 54 | --arch "${ARCH}" \ 55 | --version-pattern "${VERSION_PATTERN}" \ 56 | --version "${VERSION}" \ 57 | --cpe-pattern "${CPE_PATTERN:-}" \ 58 | --cpe "${CPE:-}" \ 59 | --purl-pattern "${PURL_PATTERN:-}" \ 60 | --purl "${PURL:-}" \ 61 | --uri "${URI}" \ 62 | --sha256 "${SHA256}" \ 63 | --source "${SOURCE_URI}" \ 64 | --source-sha256 "${SOURCE_SHA256}" 65 | 66 | git add buildpack.toml 67 | git checkout -- . 68 | 69 | if [ "$(echo "$OLD_VERSION" | awk -F '.' '{print $1}')" != "$(echo "$VERSION" | awk -F '.' '{print $1}')" ]; then 70 | LABEL="semver:major" 71 | elif [ "$(echo "$OLD_VERSION" | awk -F '.' '{print $2}')" != "$(echo "$VERSION" | awk -F '.' '{print $2}')" ]; then 72 | LABEL="semver:minor" 73 | else 74 | LABEL="semver:patch" 75 | fi 76 | 77 | echo "old-version=${OLD_VERSION}" >> "$GITHUB_OUTPUT" 78 | echo "new-version=${VERSION}" >> "$GITHUB_OUTPUT" 79 | echo "version-label=${LABEL}" >> "$GITHUB_OUTPUT" 80 | env: 81 | ARCH: "" 82 | CPE: ${{ steps.dependency.outputs.cpe }} 83 | CPE_PATTERN: "" 84 | ID: tomcat-logging-support 85 | PURL: ${{ steps.dependency.outputs.purl }} 86 | PURL_PATTERN: "" 87 | SHA256: ${{ steps.dependency.outputs.sha256 }} 88 | SOURCE_SHA256: ${{ steps.dependency.outputs.source_sha256 }} 89 | SOURCE_URI: ${{ steps.dependency.outputs.source }} 90 | URI: ${{ steps.dependency.outputs.uri }} 91 | VERSION: ${{ steps.dependency.outputs.version }} 92 | VERSION_PATTERN: '[\d]+\.[\d]+\.[\d]+' 93 | - uses: peter-evans/create-pull-request@v6 94 | with: 95 | author: ${{ secrets.JAVA_GITHUB_USERNAME }} <${{ secrets.JAVA_GITHUB_USERNAME }}@users.noreply.github.com> 96 | body: Bumps `tomcat-logging-support` from `${{ steps.buildpack.outputs.old-version }}` to `${{ steps.buildpack.outputs.new-version }}`. 97 | branch: update/buildpack/tomcat-logging-support 98 | commit-message: |- 99 | Bump tomcat-logging-support from ${{ steps.buildpack.outputs.old-version }} to ${{ steps.buildpack.outputs.new-version }} 100 | 101 | Bumps tomcat-logging-support from ${{ steps.buildpack.outputs.old-version }} to ${{ steps.buildpack.outputs.new-version }}. 102 | delete-branch: true 103 | labels: ${{ steps.buildpack.outputs.version-label }}, type:dependency-upgrade 104 | signoff: true 105 | title: Bump tomcat-logging-support from ${{ steps.buildpack.outputs.old-version }} to ${{ steps.buildpack.outputs.new-version }} 106 | token: ${{ secrets.PAKETO_BOT_GITHUB_TOKEN }} 107 | -------------------------------------------------------------------------------- /.github/workflows/pb-update-tomcat-lifecycle-support.yml: -------------------------------------------------------------------------------- 1 | name: Update tomcat-lifecycle-support 2 | "on": 3 | schedule: 4 | - cron: 0 5 * * 1-5 5 | workflow_dispatch: {} 6 | jobs: 7 | update: 8 | name: Update Buildpack Dependency 9 | runs-on: 10 | - ubuntu-latest 11 | steps: 12 | - uses: actions/setup-go@v5 13 | with: 14 | go-version: "1.25" 15 | - name: Install update-buildpack-dependency 16 | run: | 17 | #!/usr/bin/env bash 18 | 19 | set -euo pipefail 20 | 21 | go install -ldflags="-s -w" github.com/paketo-buildpacks/libpak/cmd/update-buildpack-dependency@latest 22 | - uses: buildpacks/github-actions/setup-tools@v5.9.7 23 | with: 24 | crane-version: 0.20.3 25 | yj-version: 5.1.0 26 | - uses: actions/checkout@v4 27 | - id: dependency 28 | uses: docker://ghcr.io/paketo-buildpacks/actions/maven-dependency:main 29 | with: 30 | artifact_id: tomcat-lifecycle-support 31 | group_id: org.cloudfoundry 32 | source_classifier: sources 33 | uri: https://repo1.maven.org/maven2 34 | version_regex: ^[\d]+\.[\d]+\.[\d]+\.RELEASE$ 35 | - name: Update Buildpack Dependency 36 | id: buildpack 37 | run: | 38 | #!/usr/bin/env bash 39 | 40 | set -euo pipefail 41 | 42 | VERSION_DEPS=$(yj -tj < buildpack.toml | jq -r ".metadata.dependencies[] | select( .id == env.ID ) | select( .version | test( env.VERSION_PATTERN ) )") 43 | ARCH=${ARCH:-amd64} 44 | OLD_VERSION=$(echo "$VERSION_DEPS" | jq -r 'select( .purl | contains( env.ARCH ) ) | .version') 45 | 46 | if [ -z "$OLD_VERSION" ]; then 47 | ARCH="" # empty means noarch 48 | OLD_VERSION=$(echo "$VERSION_DEPS" | jq -r ".version") 49 | fi 50 | 51 | update-buildpack-dependency \ 52 | --buildpack-toml buildpack.toml \ 53 | --id "${ID}" \ 54 | --arch "${ARCH}" \ 55 | --version-pattern "${VERSION_PATTERN}" \ 56 | --version "${VERSION}" \ 57 | --cpe-pattern "${CPE_PATTERN:-}" \ 58 | --cpe "${CPE:-}" \ 59 | --purl-pattern "${PURL_PATTERN:-}" \ 60 | --purl "${PURL:-}" \ 61 | --uri "${URI}" \ 62 | --sha256 "${SHA256}" \ 63 | --source "${SOURCE_URI}" \ 64 | --source-sha256 "${SOURCE_SHA256}" 65 | 66 | git add buildpack.toml 67 | git checkout -- . 68 | 69 | if [ "$(echo "$OLD_VERSION" | awk -F '.' '{print $1}')" != "$(echo "$VERSION" | awk -F '.' '{print $1}')" ]; then 70 | LABEL="semver:major" 71 | elif [ "$(echo "$OLD_VERSION" | awk -F '.' '{print $2}')" != "$(echo "$VERSION" | awk -F '.' '{print $2}')" ]; then 72 | LABEL="semver:minor" 73 | else 74 | LABEL="semver:patch" 75 | fi 76 | 77 | echo "old-version=${OLD_VERSION}" >> "$GITHUB_OUTPUT" 78 | echo "new-version=${VERSION}" >> "$GITHUB_OUTPUT" 79 | echo "version-label=${LABEL}" >> "$GITHUB_OUTPUT" 80 | env: 81 | ARCH: "" 82 | CPE: ${{ steps.dependency.outputs.cpe }} 83 | CPE_PATTERN: "" 84 | ID: tomcat-lifecycle-support 85 | PURL: ${{ steps.dependency.outputs.purl }} 86 | PURL_PATTERN: "" 87 | SHA256: ${{ steps.dependency.outputs.sha256 }} 88 | SOURCE_SHA256: ${{ steps.dependency.outputs.source_sha256 }} 89 | SOURCE_URI: ${{ steps.dependency.outputs.source }} 90 | URI: ${{ steps.dependency.outputs.uri }} 91 | VERSION: ${{ steps.dependency.outputs.version }} 92 | VERSION_PATTERN: '[\d]+\.[\d]+\.[\d]+' 93 | - uses: peter-evans/create-pull-request@v6 94 | with: 95 | author: ${{ secrets.JAVA_GITHUB_USERNAME }} <${{ secrets.JAVA_GITHUB_USERNAME }}@users.noreply.github.com> 96 | body: Bumps `tomcat-lifecycle-support` from `${{ steps.buildpack.outputs.old-version }}` to `${{ steps.buildpack.outputs.new-version }}`. 97 | branch: update/buildpack/tomcat-lifecycle-support 98 | commit-message: |- 99 | Bump tomcat-lifecycle-support from ${{ steps.buildpack.outputs.old-version }} to ${{ steps.buildpack.outputs.new-version }} 100 | 101 | Bumps tomcat-lifecycle-support from ${{ steps.buildpack.outputs.old-version }} to ${{ steps.buildpack.outputs.new-version }}. 102 | delete-branch: true 103 | labels: ${{ steps.buildpack.outputs.version-label }}, type:dependency-upgrade 104 | signoff: true 105 | title: Bump tomcat-lifecycle-support from ${{ steps.buildpack.outputs.old-version }} to ${{ steps.buildpack.outputs.new-version }} 106 | token: ${{ secrets.PAKETO_BOT_GITHUB_TOKEN }} 107 | -------------------------------------------------------------------------------- /.github/workflows/pb-update-tomcat-access-logging-support.yml: -------------------------------------------------------------------------------- 1 | name: Update tomcat-access-logging-support 2 | "on": 3 | schedule: 4 | - cron: 0 5 * * 1-5 5 | workflow_dispatch: {} 6 | jobs: 7 | update: 8 | name: Update Buildpack Dependency 9 | runs-on: 10 | - ubuntu-latest 11 | steps: 12 | - uses: actions/setup-go@v5 13 | with: 14 | go-version: "1.25" 15 | - name: Install update-buildpack-dependency 16 | run: | 17 | #!/usr/bin/env bash 18 | 19 | set -euo pipefail 20 | 21 | go install -ldflags="-s -w" github.com/paketo-buildpacks/libpak/cmd/update-buildpack-dependency@latest 22 | - uses: buildpacks/github-actions/setup-tools@v5.9.7 23 | with: 24 | crane-version: 0.20.3 25 | yj-version: 5.1.0 26 | - uses: actions/checkout@v4 27 | - id: dependency 28 | uses: docker://ghcr.io/paketo-buildpacks/actions/maven-dependency:main 29 | with: 30 | artifact_id: tomcat-access-logging-support 31 | group_id: org.cloudfoundry 32 | source_classifier: sources 33 | uri: https://repo1.maven.org/maven2 34 | version_regex: ^[\d]+\.[\d]+\.[\d]+\.RELEASE$ 35 | - name: Update Buildpack Dependency 36 | id: buildpack 37 | run: | 38 | #!/usr/bin/env bash 39 | 40 | set -euo pipefail 41 | 42 | VERSION_DEPS=$(yj -tj < buildpack.toml | jq -r ".metadata.dependencies[] | select( .id == env.ID ) | select( .version | test( env.VERSION_PATTERN ) )") 43 | ARCH=${ARCH:-amd64} 44 | OLD_VERSION=$(echo "$VERSION_DEPS" | jq -r 'select( .purl | contains( env.ARCH ) ) | .version') 45 | 46 | if [ -z "$OLD_VERSION" ]; then 47 | ARCH="" # empty means noarch 48 | OLD_VERSION=$(echo "$VERSION_DEPS" | jq -r ".version") 49 | fi 50 | 51 | update-buildpack-dependency \ 52 | --buildpack-toml buildpack.toml \ 53 | --id "${ID}" \ 54 | --arch "${ARCH}" \ 55 | --version-pattern "${VERSION_PATTERN}" \ 56 | --version "${VERSION}" \ 57 | --cpe-pattern "${CPE_PATTERN:-}" \ 58 | --cpe "${CPE:-}" \ 59 | --purl-pattern "${PURL_PATTERN:-}" \ 60 | --purl "${PURL:-}" \ 61 | --uri "${URI}" \ 62 | --sha256 "${SHA256}" \ 63 | --source "${SOURCE_URI}" \ 64 | --source-sha256 "${SOURCE_SHA256}" 65 | 66 | git add buildpack.toml 67 | git checkout -- . 68 | 69 | if [ "$(echo "$OLD_VERSION" | awk -F '.' '{print $1}')" != "$(echo "$VERSION" | awk -F '.' '{print $1}')" ]; then 70 | LABEL="semver:major" 71 | elif [ "$(echo "$OLD_VERSION" | awk -F '.' '{print $2}')" != "$(echo "$VERSION" | awk -F '.' '{print $2}')" ]; then 72 | LABEL="semver:minor" 73 | else 74 | LABEL="semver:patch" 75 | fi 76 | 77 | echo "old-version=${OLD_VERSION}" >> "$GITHUB_OUTPUT" 78 | echo "new-version=${VERSION}" >> "$GITHUB_OUTPUT" 79 | echo "version-label=${LABEL}" >> "$GITHUB_OUTPUT" 80 | env: 81 | ARCH: "" 82 | CPE: ${{ steps.dependency.outputs.cpe }} 83 | CPE_PATTERN: "" 84 | ID: tomcat-access-logging-support 85 | PURL: ${{ steps.dependency.outputs.purl }} 86 | PURL_PATTERN: "" 87 | SHA256: ${{ steps.dependency.outputs.sha256 }} 88 | SOURCE_SHA256: ${{ steps.dependency.outputs.source_sha256 }} 89 | SOURCE_URI: ${{ steps.dependency.outputs.source }} 90 | URI: ${{ steps.dependency.outputs.uri }} 91 | VERSION: ${{ steps.dependency.outputs.version }} 92 | VERSION_PATTERN: '[\d]+\.[\d]+\.[\d]+' 93 | - uses: peter-evans/create-pull-request@v6 94 | with: 95 | author: ${{ secrets.JAVA_GITHUB_USERNAME }} <${{ secrets.JAVA_GITHUB_USERNAME }}@users.noreply.github.com> 96 | body: Bumps `tomcat-access-logging-support` from `${{ steps.buildpack.outputs.old-version }}` to `${{ steps.buildpack.outputs.new-version }}`. 97 | branch: update/buildpack/tomcat-access-logging-support 98 | commit-message: |- 99 | Bump tomcat-access-logging-support from ${{ steps.buildpack.outputs.old-version }} to ${{ steps.buildpack.outputs.new-version }} 100 | 101 | Bumps tomcat-access-logging-support from ${{ steps.buildpack.outputs.old-version }} to ${{ steps.buildpack.outputs.new-version }}. 102 | delete-branch: true 103 | labels: ${{ steps.buildpack.outputs.version-label }}, type:dependency-upgrade 104 | signoff: true 105 | title: Bump tomcat-access-logging-support from ${{ steps.buildpack.outputs.old-version }} to ${{ steps.buildpack.outputs.new-version }} 106 | token: ${{ secrets.PAKETO_BOT_GITHUB_TOKEN }} 107 | -------------------------------------------------------------------------------- /tomcat/detect_test.go: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2018-2020 the original author or authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package tomcat_test 18 | 19 | import ( 20 | "io" 21 | "os" 22 | "path/filepath" 23 | "testing" 24 | 25 | "github.com/buildpacks/libcnb" 26 | . "github.com/onsi/gomega" 27 | "github.com/sclevine/spec" 28 | 29 | "github.com/paketo-buildpacks/apache-tomcat/v8/tomcat" 30 | ) 31 | 32 | func testDetect(t *testing.T, context spec.G, it spec.S) { 33 | var ( 34 | Expect = NewWithT(t).Expect 35 | 36 | ctx libcnb.DetectContext 37 | detect tomcat.Detect 38 | path string 39 | ) 40 | 41 | it.Before(func() { 42 | var err error 43 | path, err = os.MkdirTemp("", "tomcat") 44 | Expect(err).NotTo(HaveOccurred()) 45 | 46 | ctx.Application.Path = path 47 | }) 48 | 49 | it.After(func() { 50 | Expect(os.RemoveAll(path)).To(Succeed()) 51 | }) 52 | 53 | it("fails with Main-Class", func() { 54 | Expect(os.MkdirAll(filepath.Join(path, "META-INF"), 0755)).To(Succeed()) 55 | Expect(os.WriteFile(filepath.Join(path, "META-INF", "MANIFEST.MF"), []byte(`Main-Class: test-main-class`), 0644)).To(Succeed()) 56 | 57 | Expect(detect.Detect(ctx)).To(Equal(libcnb.DetectResult{Pass: false})) 58 | }) 59 | 60 | context("WEB-INF not found", func() { 61 | it("requires jvm-application-artifact", func() { 62 | Expect(detect.Detect(ctx)).To(Equal(libcnb.DetectResult{ 63 | Pass: true, 64 | Plans: []libcnb.BuildPlan{ 65 | { 66 | Provides: []libcnb.BuildPlanProvide{ 67 | {Name: "jvm-application"}, 68 | {Name: "java-app-server"}, 69 | }, 70 | Requires: []libcnb.BuildPlanRequire{ 71 | {Name: "syft"}, 72 | {Name: "jre", Metadata: map[string]interface{}{"launch": true}}, 73 | {Name: "jvm-application-package"}, 74 | {Name: "jvm-application"}, 75 | {Name: "java-app-server"}, 76 | }, 77 | }, 78 | }, 79 | })) 80 | }) 81 | }) 82 | 83 | context("WEB-INF found", func() { 84 | it.Before(func() { 85 | Expect(os.MkdirAll(filepath.Join(path, "WEB-INF"), 0755)).To(Succeed()) 86 | }) 87 | 88 | it("requires and provides jvm-application-artifact", func() { 89 | Expect(detect.Detect(ctx)).To(Equal(libcnb.DetectResult{ 90 | Pass: true, 91 | Plans: []libcnb.BuildPlan{ 92 | { 93 | Provides: []libcnb.BuildPlanProvide{ 94 | {Name: "jvm-application"}, 95 | {Name: "java-app-server"}, 96 | {Name: "jvm-application-package"}, 97 | }, 98 | Requires: []libcnb.BuildPlanRequire{ 99 | {Name: "syft"}, 100 | {Name: "jre", Metadata: map[string]interface{}{"launch": true}}, 101 | {Name: "jvm-application-package"}, 102 | {Name: "jvm-application"}, 103 | {Name: "java-app-server"}, 104 | }, 105 | }, 106 | }, 107 | })) 108 | }) 109 | }) 110 | 111 | context("BP_JAVA_APP_SERVER is set to `tomcat`", func() { 112 | it.Before(func() { 113 | Expect(os.Setenv("BP_JAVA_APP_SERVER", "tomcat")).To(Succeed()) 114 | }) 115 | 116 | it.After(func() { 117 | Expect(os.Unsetenv("BP_JAVA_APP_SERVER")).To(Succeed()) 118 | }) 119 | 120 | it("contributes Tomcat", func() { 121 | Expect(detect.Detect(ctx)).To(Equal(libcnb.DetectResult{ 122 | Pass: true, 123 | Plans: []libcnb.BuildPlan{ 124 | { 125 | Provides: []libcnb.BuildPlanProvide{ 126 | {Name: "jvm-application"}, 127 | {Name: "java-app-server"}, 128 | }, 129 | Requires: []libcnb.BuildPlanRequire{ 130 | {Name: "syft"}, 131 | {Name: "jre", Metadata: map[string]interface{}{"launch": true}}, 132 | {Name: "jvm-application-package"}, 133 | {Name: "jvm-application"}, 134 | {Name: "java-app-server"}, 135 | }, 136 | }, 137 | }, 138 | })) 139 | }) 140 | }) 141 | 142 | context("BP_JAVA_APP_SERVER is set to `foo`", func() { 143 | it.Before(func() { 144 | Expect(os.Setenv("BP_JAVA_APP_SERVER", "foo")).To(Succeed()) 145 | }) 146 | 147 | it.After(func() { 148 | Expect(os.Unsetenv("BP_JAVA_APP_SERVER")).To(Succeed()) 149 | }) 150 | 151 | it("fails", func() { 152 | Expect(detect.Detect(ctx)).To(Equal(libcnb.DetectResult{Pass: false})) 153 | }) 154 | }) 155 | 156 | context("Multiple war files found", func() { 157 | files := []string{"api.war", "ui.war"} 158 | 159 | it.Before(func() { 160 | for _, file := range files { 161 | in, err := os.Open(filepath.Join("testdata", "warfiles", file)) 162 | Expect(err).NotTo(HaveOccurred()) 163 | 164 | out, err := os.OpenFile(filepath.Join(ctx.Application.Path, file), os.O_CREATE|os.O_APPEND|os.O_WRONLY, 0644) 165 | Expect(err).NotTo(HaveOccurred()) 166 | 167 | _, err = io.Copy(out, in) 168 | Expect(err).NotTo(HaveOccurred()) 169 | Expect(in.Close()).To(Succeed()) 170 | Expect(out.Close()).To(Succeed()) 171 | } 172 | Expect(os.Setenv("BP_JAVA_APP_SERVER", "tomcat")).To(Succeed()) 173 | }) 174 | 175 | it.After(func() { 176 | Expect(os.Unsetenv("BP_JAVA_APP_SERVER")).To(Succeed()) 177 | for _, file := range files { 178 | os.Remove(filepath.Join(ctx.Application.Path, file)) 179 | } 180 | }) 181 | 182 | it("contributes Tomcat", func() { 183 | Expect(detect.Detect(ctx)).To(Equal(libcnb.DetectResult{ 184 | Pass: true, 185 | Plans: []libcnb.BuildPlan{ 186 | { 187 | Provides: []libcnb.BuildPlanProvide{ 188 | {Name: "jvm-application"}, 189 | {Name: "java-app-server"}, 190 | {Name: "jvm-application-package"}, 191 | }, 192 | Requires: []libcnb.BuildPlanRequire{ 193 | {Name: "syft"}, 194 | {Name: "jre", Metadata: map[string]interface{}{"launch": true}}, 195 | {Name: "jvm-application-package"}, 196 | {Name: "jvm-application"}, 197 | {Name: "java-app-server"}, 198 | }, 199 | }, 200 | }, 201 | })) 202 | }) 203 | }) 204 | } 205 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | github.com/BurntSushi/toml v1.5.0 h1:W5quZX/G/csjUnuI8SUYlsHs9M38FC7znL0lIO+DvMg= 2 | github.com/BurntSushi/toml v1.5.0/go.mod h1:ukJfTF/6rtPPRCnwkur4qwRxa8vTRFBF0uk2lLoLwho= 3 | github.com/Masterminds/semver/v3 v3.4.0 h1:Zog+i5UMtVoCU8oKka5P7i9q9HgrJeGzI9SA1Xbatp0= 4 | github.com/Masterminds/semver/v3 v3.4.0/go.mod h1:4V+yj/TJE1HU9XfppCwVMZq3I84lprf4nC11bSS5beM= 5 | github.com/buildpacks/libcnb v1.30.4 h1:Jp6cJxYsZQgqix+lpRdSpjHt5bv5yCJqgkw9zWmS6xU= 6 | github.com/buildpacks/libcnb v1.30.4/go.mod h1:vjEDAlK3/Rf67AcmBzphXoqIlbdFgBNUK5d8wjreJbY= 7 | github.com/creack/pty v1.1.24 h1:bJrF4RRfyJnbTJqzRLHzcGaZK1NeM5kTC9jGgovnR1s= 8 | github.com/creack/pty v1.1.24/go.mod h1:08sCNb52WyoAwi2QDyzUCTgcvVFhUzewun7wtTfvcwE= 9 | github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= 10 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 11 | github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI= 12 | github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= 13 | github.com/go-task/slim-sprig/v3 v3.0.0 h1:sUs3vkvUymDpBKi3qH1YSqBQk9+9D/8M2mN1vB6EwHI= 14 | github.com/go-task/slim-sprig/v3 v3.0.0/go.mod h1:W848ghGpv3Qj3dhTPRyJypKRiqCdHZiAzKg9hl15HA8= 15 | github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= 16 | github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= 17 | github.com/google/pprof v0.0.0-20250403155104-27863c87afa6 h1:BHT72Gu3keYf3ZEu2J0b1vyeLSOYI8bm5wbJM/8yDe8= 18 | github.com/google/pprof v0.0.0-20250403155104-27863c87afa6/go.mod h1:boTsfXsheKC2y+lKOCMpSfarhxDeIzfZG1jqGcPl3cA= 19 | github.com/h2non/filetype v1.1.3 h1:FKkx9QbD7HR/zjK1Ia5XiBsq9zdLi5Kf3zGyFTAFkGg= 20 | github.com/h2non/filetype v1.1.3/go.mod h1:319b3zT68BvV+WRj7cwy856M2ehB3HqNOt6sy1HndBY= 21 | github.com/heroku/color v0.0.6 h1:UTFFMrmMLFcL3OweqP1lAdp8i1y/9oHqkeHjQ/b/Ny0= 22 | github.com/heroku/color v0.0.6/go.mod h1:ZBvOcx7cTF2QKOv4LbmoBtNl5uB17qWxGuzZrsi1wLU= 23 | github.com/imdario/mergo v0.3.16 h1:wwQJbIsHYGMUyLSPrEq1CT16AhnhNJQ51+4fdHUnCl4= 24 | github.com/imdario/mergo v0.3.16/go.mod h1:WBLT9ZmE3lPoWsEzCh9LPo3TiwVN+ZKEjmz+hD27ysY= 25 | github.com/jarcoal/httpmock v1.3.1 h1:iUx3whfZWVf3jT01hQTO/Eo5sAYtB2/rqaUuOtpInww= 26 | github.com/jarcoal/httpmock v1.3.1/go.mod h1:3yb8rc4BI7TCBhFY8ng0gjuLKJNquuDNiPaZjnENuYg= 27 | github.com/magiconair/properties v1.8.10 h1:s31yESBquKXCV9a/ScB3ESkOjUYYv+X0rg8SYxI99mE= 28 | github.com/magiconair/properties v1.8.10/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0= 29 | github.com/mattn/go-colorable v0.1.2/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE= 30 | github.com/mattn/go-colorable v0.1.14 h1:9A9LHSqF/7dyVVX6g0U9cwm9pG3kP9gSzcuIPHPsaIE= 31 | github.com/mattn/go-colorable v0.1.14/go.mod h1:6LmQG8QLFO4G5z1gPvYEzlUgJ2wF+stgPZH1UqBm1s8= 32 | github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= 33 | github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= 34 | github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= 35 | github.com/mattn/go-shellwords v1.0.12 h1:M2zGm7EW6UQJvDeQxo4T51eKPurbeFbe8WtebGE2xrk= 36 | github.com/mattn/go-shellwords v1.0.12/go.mod h1:EZzvwXDESEeg03EKmM+RmDnNOPKG4lLtQsUlTZDWQ8Y= 37 | github.com/mitchellh/hashstructure/v2 v2.0.2 h1:vGKWl0YJqUNxE8d+h8f6NJLcCJrgbhC4NcD46KavDd4= 38 | github.com/mitchellh/hashstructure/v2 v2.0.2/go.mod h1:MG3aRVU/N29oo/V/IhBX8GR/zz4kQkprJgF2EVszyDE= 39 | github.com/onsi/ginkgo/v2 v2.25.3 h1:Ty8+Yi/ayDAGtk4XxmmfUy4GabvM+MegeB4cDLRi6nw= 40 | github.com/onsi/ginkgo/v2 v2.25.3/go.mod h1:43uiyQC4Ed2tkOzLsEYm7hnrb7UJTWHYNsuy3bG/snE= 41 | github.com/onsi/gomega v1.38.3 h1:eTX+W6dobAYfFeGC2PV6RwXRu/MyT+cQguijutvkpSM= 42 | github.com/onsi/gomega v1.38.3/go.mod h1:ZCU1pkQcXDO5Sl9/VVEGlDyp+zm0m1cmeG5TOzLgdh4= 43 | github.com/paketo-buildpacks/libjvm v1.46.0 h1:+mEOsK30a1if0T3ZvSs6di/w5cp/j14uD3DyYUusavI= 44 | github.com/paketo-buildpacks/libjvm v1.46.0/go.mod h1:jNQuS8SQfbbHN9kenMpBhpxaE0uCa9ZkKLrN89IU0VY= 45 | github.com/paketo-buildpacks/libpak v1.73.0 h1:OgdkOn4VLIzRo0WcSx1iRmqeLrcMAZbIk7pOOJSyl5Q= 46 | github.com/paketo-buildpacks/libpak v1.73.0/go.mod h1:EY01BAEtNPT1kI+/OTGTAkitNzKiFzCTGAmxapBUPJ4= 47 | github.com/pavlo-v-chernykh/keystore-go/v4 v4.5.0 h1:2nosf3P75OZv2/ZO/9Px5ZgZ5gbKrzA3joN1QMfOGMQ= 48 | github.com/pavlo-v-chernykh/keystore-go/v4 v4.5.0/go.mod h1:lAVhWwbNaveeJmxrxuSTxMgKpF6DjnuVpn6T8WiBwYQ= 49 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= 50 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 51 | github.com/sclevine/spec v1.4.0 h1:z/Q9idDcay5m5irkZ28M7PtQM4aOISzOpj4bUPkDee8= 52 | github.com/sclevine/spec v1.4.0/go.mod h1:LvpgJaFyvQzRvc1kaDs0bulYwzC70PbiYjC4QnFHkOM= 53 | github.com/stretchr/objx v0.5.3 h1:jmXUvGomnU1o3W/V5h2VEradbpJDwGrzugQQvL0POH4= 54 | github.com/stretchr/objx v0.5.3/go.mod h1:rDQraq+vQZU7Fde9LOZLr8Tax6zZvy4kuNKF+QYS+U0= 55 | github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U= 56 | github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= 57 | github.com/xi2/xz v0.0.0-20171230120015-48954b6210f8 h1:nIPpBwaJSVYIxUFsDv3M8ofmx9yWTog9BfvIu0q41lo= 58 | github.com/xi2/xz v0.0.0-20171230120015-48954b6210f8/go.mod h1:HUYIGzjTL3rfEspMxjDjgmT5uz5wzYJKVo23qUhYTos= 59 | go.uber.org/automaxprocs v1.6.0 h1:O3y2/QNTOdbF+e/dpXNNW7Rx2hZ4sTIPyybbxyNqTUs= 60 | go.uber.org/automaxprocs v1.6.0/go.mod h1:ifeIMSnPZuznNm6jmdzmU3/bfk01Fe2fotchwEFJ8r8= 61 | go.yaml.in/yaml/v3 v3.0.4 h1:tfq32ie2Jv2UxXFdLJdh3jXuOzWiL1fo0bu/FbuKpbc= 62 | go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg= 63 | golang.org/x/crypto v0.46.0 h1:cKRW/pmt1pKAfetfu+RCEvjvZkA9RimPbh7bhFjGVBU= 64 | golang.org/x/crypto v0.46.0/go.mod h1:Evb/oLKmMraqjZ2iQTwDwvCtJkczlDuTmdJXoZVzqU0= 65 | golang.org/x/net v0.48.0 h1:zyQRTTrjc33Lhh0fBgT/H3oZq9WuvRR5gPC70xpDiQU= 66 | golang.org/x/net v0.48.0/go.mod h1:+ndRgGjkh8FGtu1w1FGbEC31if4VrNVMuKTgcAAnQRY= 67 | golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 68 | golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 69 | golang.org/x/sys v0.39.0 h1:CvCKL8MeisomCi6qNZ+wbb0DN9E5AATixKsvNtMoMFk= 70 | golang.org/x/sys v0.39.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= 71 | golang.org/x/text v0.32.0 h1:ZD01bjUt1FQ9WJ0ClOL5vxgxOI/sVCNgX1YtKwcY0mU= 72 | golang.org/x/text v0.32.0/go.mod h1:o/rUWzghvpD5TXrTIBuJU77MTaN0ljMWE47kxGJQ7jY= 73 | golang.org/x/tools v0.39.0 h1:ik4ho21kwuQln40uelmciQPp9SipgNDdrafrYA4TmQQ= 74 | golang.org/x/tools v0.39.0/go.mod h1:JnefbkDPyD8UU2kI5fuf8ZX4/yUeh9W877ZeBONxUqQ= 75 | google.golang.org/protobuf v1.36.7 h1:IgrO7UwFQGJdRNXH/sQux4R1Dj1WAKcLElzeeRaXV2A= 76 | google.golang.org/protobuf v1.36.7/go.mod h1:jduwjTPXsFjZGTmRluh+L6NjiWu7pchiJ2/5YcXBHnY= 77 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= 78 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 79 | gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= 80 | gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 81 | software.sslmate.com/src/go-pkcs12 v0.6.0 h1:f3sQittAeF+pao32Vb+mkli+ZyT+VwKaD014qFGq6oU= 82 | software.sslmate.com/src/go-pkcs12 v0.6.0/go.mod h1:Qiz0EyvDRJjjxGyUQa2cCNZn/wMyzrRJ/qcDXOQazLI= 83 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Paketo Buildpack for Apache Tomcat 2 | 3 | ## Buildpack ID: `paketo-buildpacks/apache-tomcat` 4 | ## Registry URLs: `docker.io/paketobuildpacks/apache-tomcat` 5 | 6 | The Paketo Buildpack for Apache Tomcat is a Cloud Native Buildpack that contributes Apache Tomcat and Process Types for WARs. 7 | 8 | ## Behavior 9 | 10 | This buildpack will participate if all of the following conditions are met 11 | 12 | * `$BP_JAVA_APP_SERVER` is `tomcat` or if `$BP_JAVA_APP_SERVER` is unset or empty and this is the first buildpack to provide a Java application server. 13 | * `/WEB-INF` exists 14 | * `Main-Class` is NOT defined in the manifest 15 | 16 | The buildpack will do the following: 17 | 18 | * Requests that a JRE be installed 19 | * Contribute a Tomcat instance to `$CATALINA_HOME` 20 | * Contribute a Tomcat instance to `$CATALINA_BASE` 21 | * Contribute `context.xml`, `logging.properties`, `server.xml`, and `web.xml` to `conf/` 22 | * Contribute [Access Logging Support][als], [Lifecycle Support][lcs], and [Logging Support][lgs] 23 | * Contribute external configuration if available 24 | * Contributes `tomcat`, `task`, and `web` process types 25 | 26 | ### Tiny Stack 27 | 28 | When this buildpack runs on the [Tiny stack](https://paketo.io/docs/concepts/stacks/#tiny), which has no shell, the following notes apply: 29 | * As there is no shell, the `catalina.sh` script cannot be used to start Tomcat 30 | * The Tomcat Buildpack will generate a start command directly. It does not support all the functionality in `catalina.sh`. 31 | * Some configuration options such as `bin/setenv.sh` and setting `CATALINA_*` environment variables, will not be available. 32 | * Tomcat will be run with `umask` set to `0022` instead of the `catalina.sh`provided default of `0027` 33 | 34 | [als]: https://github.com/cloudfoundry/java-buildpack-support/tree/master/tomcat-access-logging-support 35 | [lcs]: https://github.com/cloudfoundry/java-buildpack-support/tree/master/tomcat-lifecycle-support 36 | [lgs]: https://github.com/cloudfoundry/java-buildpack-support/tree/master/tomcat-logging-support 37 | 38 | ## Configuration 39 | | Environment Variable | Description | 40 | | ----------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | 41 | | `$BP_JAVA_APP_SERVER` | The application server to use. It defaults to `` (empty string) which means that order dictates which Java application server is installed. The first Java application server buildpack to run will be picked. | 42 | | `$BP_TOMCAT_CONTEXT_PATH` | The context path to mount the application at. Defaults to empty (`ROOT`). | 43 | | `$BP_TOMCAT_EXT_CONF_SHA256` | The SHA256 hash of the external configuration package | 44 | | `$BP_TOMCAT_ENV_PROPERTY_SOURCE_DISABLED` | When true the buildpack will not configure `org.apache.tomcat.util.digester.EnvironmentPropertySource`. This configuration option is added to support loading configuration from environment variables and referencing them in Tomcat configuration files. | 45 | | `$BP_TOMCAT_EXT_CONF_STRIP` | The number of directory levels to strip from the external configuration package. Defaults to `0`. | 46 | | `$BP_TOMCAT_EXT_CONF_URI` | The download URI of the external configuration package | 47 | | `$BP_TOMCAT_EXT_CONF_VERSION` | The version of the external configuration package | 48 | | `$BP_TOMCAT_VERSION` | Configure a specific Tomcat version. This value must _exactly_ match a version available in the buildpack so typically it would configured to a wildcard such as `9.*`. | 49 | | `BPL_TOMCAT_ACCESS_LOGGING_ENABLED` | Whether access logging should be activated. Defaults to inactive. | 50 | | `BPI_TOMCAT_ADDITIONAL_JARS` | This should only be used in other buildpacks to include a `jar` to the tomcat classpath. Several `jars` must be separated by `:`. | 51 | | `BPI_TOMCAT_ADDITIONAL_COMMON_JARS` | This should be used by other buildpacks to include additional locations to be class loaded by the tomcat common classloader. For example a buildpack might contribute resources in its dedicated layer and add the location with this variable to be classloaded additionally by Tomcat. Both folder paths as well as single `jar` file paths can be specified. | 52 | 53 | ### External Configuration Package 54 | The artifacts that the repository provides must be in TAR format and must follow the Tomcat archive structure: 55 | 56 | ``` 57 | 58 | └── conf 59 | ├── context.xml 60 | ├── server.xml 61 | ├── web.xml 62 | ├── ... 63 | ``` 64 | 65 | ### Environment Property Source 66 | When the Environment Property Source is configured, configuration for Tomcats [configuration files](https://tomcat.apache.org/tomcat-9.0-doc/config/systemprops.html) can be loaded 67 | from environment variables. To use this feature, the name of the environment variable must match the name of the property. 68 | 69 | ## Bindings 70 | The buildpack optionally accepts the following bindings: 71 | 72 | ### Type: `dependency-mapping` 73 | | Key | Value | Description | 74 | | --------------------- | ------- | ------------------------------------------------------------------------------------------------- | 75 | | `` | `` | If needed, the buildpack will fetch the dependency with digest `` from `` | 76 | 77 | ## Providing Additional JARs to Tomcat 78 | 79 | Buildpacks can contribute JARs to the `CLASSPATH` of Tomcat by appending a path to `BPI_TOMCAT_ADDITIONAL_JARS`. 80 | 81 | ```go 82 | func (s) Contribute(layer libcnb.Layer) (libcnb.Layer, error) { 83 | // Copy dependency into the layer 84 | file := filepath.Join(layer.Path, filepath.Base(s.Dependency.URI)) 85 | 86 | layer, err := s.LayerContributor.Contribute(layer, func(artifact *os.File) (libcnb.Layer, error) { 87 | if err := sherpa.CopyFile(artifact, file); err != nil { 88 | return libcnb.Layer{}, fmt.Errorf("unable to copy artifact to %s\n%w", file, err) 89 | } 90 | return layer, nil 91 | }) 92 | 93 | additionalJars := []string{file} 94 | // Add dependency to BPI_TOMCAT_ADDITIONAL_JARS 95 | layer.LaunchEnvironment.Append("BPI_TOMCAT_ADDITIONAL_JARS", ":", strings.Join(additionalJars, ":")) 96 | return layer, nil 97 | } 98 | ``` 99 | 100 | ## License 101 | This buildpack is released under version 2.0 of the [Apache License][a]. 102 | 103 | [a]: http://www.apache.org/licenses/LICENSE-2.0 104 | -------------------------------------------------------------------------------- /tomcat/build.go: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2018-2020 the original author or authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package tomcat 18 | 19 | import ( 20 | "fmt" 21 | "os" 22 | "path" 23 | "path/filepath" 24 | "strings" 25 | "time" 26 | 27 | "github.com/paketo-buildpacks/libpak/effect" 28 | "github.com/paketo-buildpacks/libpak/sbom" 29 | "github.com/paketo-buildpacks/libpak/sherpa" 30 | 31 | "github.com/heroku/color" 32 | 33 | "github.com/buildpacks/libcnb" 34 | "github.com/paketo-buildpacks/apache-tomcat/v8/internal/util" 35 | "github.com/paketo-buildpacks/libjvm" 36 | "github.com/paketo-buildpacks/libpak" 37 | "github.com/paketo-buildpacks/libpak/bard" 38 | ) 39 | 40 | type Build struct { 41 | Logger bard.Logger 42 | SBOMScanner sbom.SBOMScanner 43 | } 44 | 45 | func (b Build) Build(context libcnb.BuildContext) (libcnb.BuildResult, error) { 46 | result := libcnb.NewBuildResult() 47 | 48 | pr := libpak.PlanEntryResolver{Plan: context.Plan} 49 | if _, found, err := pr.Resolve(PlanEntryJavaApplicationServer); err != nil { 50 | return libcnb.BuildResult{}, fmt.Errorf("unable to resolve plan entry\n%w", err) 51 | } else if !found { 52 | return result, nil 53 | } 54 | 55 | warFilesExist, _ := util.ContainsWarFiles(context.Application.Path) 56 | if warFilesExist { 57 | b.Logger.Infof("%s contains war files.", context.Application.Path) 58 | } else { 59 | m, err := libjvm.NewManifest(context.Application.Path) 60 | if err != nil { 61 | return libcnb.BuildResult{}, fmt.Errorf("unable to read manifest\n%w", err) 62 | } 63 | 64 | if _, ok := m.Get("Main-Class"); ok { 65 | for _, entry := range context.Plan.Entries { 66 | result.Unmet = append(result.Unmet, libcnb.UnmetPlanEntry{Name: entry.Name}) 67 | } 68 | return result, nil 69 | } 70 | 71 | file := filepath.Join(context.Application.Path, "WEB-INF") 72 | if _, err := os.Stat(file); err != nil && !os.IsNotExist(err) { 73 | return libcnb.BuildResult{}, fmt.Errorf("unable to stat file %s\n%w", file, err) 74 | } else if os.IsNotExist(err) { 75 | for _, entry := range context.Plan.Entries { 76 | result.Unmet = append(result.Unmet, libcnb.UnmetPlanEntry{Name: entry.Name}) 77 | } 78 | return result, nil 79 | } 80 | } 81 | 82 | b.Logger.Title(context.Buildpack) 83 | 84 | cr, err := libpak.NewConfigurationResolver(context.Buildpack, &b.Logger) 85 | if err != nil { 86 | return libcnb.BuildResult{}, fmt.Errorf("unable to create configuration resolver\n%w", err) 87 | } 88 | 89 | dr, err := libpak.NewDependencyResolver(context) 90 | if err != nil { 91 | return libcnb.BuildResult{}, fmt.Errorf("unable to create dependency resolver\n%w", err) 92 | } 93 | 94 | dc, err := libpak.NewDependencyCache(context) 95 | if err != nil { 96 | return libcnb.BuildResult{}, fmt.Errorf("unable to create dependency cache\n%w", err) 97 | } 98 | dc.Logger = b.Logger 99 | 100 | v, _ := cr.Resolve("BP_TOMCAT_VERSION") 101 | tomcatDep, err := dr.Resolve("tomcat", v) 102 | if err != nil { 103 | return libcnb.BuildResult{}, fmt.Errorf("unable to find dependency\n%w", err) 104 | } 105 | 106 | home, be := NewHome(tomcatDep, dc) 107 | home.Logger = b.Logger 108 | result.Layers = append(result.Layers, home) 109 | result.BOM.Entries = append(result.BOM.Entries, be) 110 | 111 | h, be := libpak.NewHelperLayer(context.Buildpack, "access-logging-support") 112 | h.Logger = b.Logger 113 | result.Layers = append(result.Layers, h) 114 | result.BOM.Entries = append(result.BOM.Entries, be) 115 | 116 | accessLoggingDependency, err := dr.Resolve("tomcat-access-logging-support", "") 117 | if err != nil { 118 | return libcnb.BuildResult{}, fmt.Errorf("unable to find dependency\n%w", err) 119 | } 120 | 121 | lifecycleDependency, err := dr.Resolve("tomcat-lifecycle-support", "") 122 | if err != nil { 123 | return libcnb.BuildResult{}, fmt.Errorf("unable to find dependency\n%w", err) 124 | } 125 | 126 | loggingDependency, err := dr.Resolve("tomcat-logging-support", "") 127 | if err != nil { 128 | return libcnb.BuildResult{}, fmt.Errorf("unable to find dependency\n%w", err) 129 | } 130 | 131 | var externalConfigurationDependency *libpak.BuildpackDependency 132 | if uri, ok := cr.Resolve("BP_TOMCAT_EXT_CONF_URI"); ok { 133 | v, versionExists := cr.Resolve("BP_TOMCAT_EXT_CONF_VERSION") 134 | s, shaExists := cr.Resolve("BP_TOMCAT_EXT_CONF_SHA256") 135 | 136 | if !versionExists && !shaExists { 137 | v = time.Now().Format(time.RFC3339) 138 | b.Logger.Infof(color.YellowString("WARNING: No BP_TOMCAT_EXT_CONF_VERSION or BP_TOMCAT_EXT_CONF_SHA256 provided, so no layer caching will occur.")) 139 | } 140 | 141 | externalConfigurationDependency = &libpak.BuildpackDependency{ 142 | ID: "tomcat-external-configuration", 143 | Name: "Tomcat External Configuration", 144 | Version: v, 145 | URI: uri, 146 | SHA256: s, 147 | Stacks: []string{context.StackID}, 148 | CPEs: nil, 149 | PURL: "", 150 | } 151 | } 152 | 153 | base, bomEntries := NewBase(context.Application.Path, context.Buildpack.Path, cr, b.ContextPath(cr), accessLoggingDependency, externalConfigurationDependency, lifecycleDependency, loggingDependency, dc, warFilesExist) 154 | 155 | base.Logger = b.Logger 156 | result.Layers = append(result.Layers, base) 157 | if bomEntries != nil { 158 | result.BOM.Entries = append(result.BOM.Entries, bomEntries...) 159 | } 160 | 161 | command := "sh" 162 | arguments := []string{filepath.Join(context.Layers.Path, "tomcat", "bin", "catalina.sh"), "run"} 163 | 164 | if libpak.IsTinyStack(context.StackID) { 165 | command, arguments = b.tinyStartCommand( 166 | filepath.Join(context.Layers.Path, "tomcat"), 167 | filepath.Join(context.Layers.Path, "catalina-base"), 168 | loggingDependency) 169 | } 170 | 171 | result.Processes = append(result.Processes, 172 | libcnb.Process{Type: "task", Command: command, Arguments: arguments, Direct: true}, 173 | libcnb.Process{Type: "tomcat", Command: command, Arguments: arguments, Direct: true}, 174 | libcnb.Process{Type: "web", Command: command, Arguments: arguments, Direct: true, Default: true}, 175 | ) 176 | 177 | if b.SBOMScanner == nil { 178 | b.SBOMScanner = sbom.NewSyftCLISBOMScanner(context.Layers, effect.CommandExecutor{}, b.Logger) 179 | } 180 | if err := b.SBOMScanner.ScanLaunch(context.Application.Path, libcnb.SyftJSON, libcnb.CycloneDXJSON); err != nil { 181 | return libcnb.BuildResult{}, fmt.Errorf("unable to create Launch SBoM \n%w", err) 182 | } 183 | 184 | return result, nil 185 | } 186 | 187 | func (b Build) ContextPath(configurationResolver libpak.ConfigurationResolver) string { 188 | cp := "ROOT" 189 | if s, ok := configurationResolver.Resolve("BP_TOMCAT_CONTEXT_PATH"); ok { 190 | cp = s 191 | } 192 | cp = strings.TrimPrefix(cp, "/") 193 | cp = strings.TrimSuffix(cp, "/") 194 | cp = strings.ReplaceAll(cp, "/", "#") 195 | 196 | return cp 197 | } 198 | 199 | func (b Build) tinyStartCommand(homePath, basePath string, loggingDep libpak.BuildpackDependency) (string, []string) { 200 | command := "java" 201 | 202 | arguments := []string{ 203 | fmt.Sprintf("-Djava.util.logging.config.file=%s/conf/logging.properties", basePath), 204 | "-Djava.util.logging.manager=org.apache.juli.ClassLoaderLogManager", 205 | } 206 | 207 | arguments = append(arguments, sherpa.GetEnvWithDefault("JSSE_OPTS", "-Djdk.tls.ephemeralDHKeySize=2048")) 208 | 209 | classpath := []string{ 210 | fmt.Sprintf("%s/bin/%s", basePath, path.Base(loggingDep.URI)), 211 | fmt.Sprintf("%s/bin/bootstrap.jar", homePath), 212 | fmt.Sprintf("%s/bin/tomcat-juli.jar", homePath), 213 | } 214 | arguments = append(arguments, "-classpath", strings.Join(classpath, ":")) 215 | 216 | arguments = append(arguments, 217 | fmt.Sprintf("-Dcatalina.home=%s", homePath), 218 | fmt.Sprintf("-Dcatalina.base=%s", basePath), 219 | "org.apache.catalina.startup.Bootstrap", "start", 220 | ) 221 | 222 | b.Logger.Header(color.YellowString("WARNING: Tomcat will run on the Tiny stack which has no shell. Due to this, some configuration options such as `bin/setenv.sh` and setting `CATALINA_*` environment variables, will not be available")) 223 | 224 | return command, arguments 225 | } 226 | -------------------------------------------------------------------------------- /.github/workflows/pb-tests.yml: -------------------------------------------------------------------------------- 1 | name: Tests 2 | "on": 3 | merge_group: 4 | types: 5 | - checks_requested 6 | branches: 7 | - main 8 | pull_request: {} 9 | push: 10 | branches: 11 | - main 12 | jobs: 13 | create-package: 14 | name: Create Package Test 15 | runs-on: 16 | - ubuntu-latest 17 | steps: 18 | - uses: actions/setup-go@v5 19 | with: 20 | go-version: "1.25" 21 | - name: Install create-package 22 | run: | 23 | #!/usr/bin/env bash 24 | 25 | set -euo pipefail 26 | 27 | go install -ldflags="-s -w" github.com/paketo-buildpacks/libpak/cmd/create-package@latest 28 | - uses: buildpacks/github-actions/setup-pack@v5.9.7 29 | with: 30 | pack-version: 0.39.1 31 | - name: Enable pack Experimental 32 | run: | 33 | #!/usr/bin/env bash 34 | 35 | set -euo pipefail 36 | 37 | echo "Enabling pack experimental features" 38 | pack config experimental true 39 | - uses: actions/checkout@v4 40 | - uses: actions/cache@v4 41 | with: 42 | key: ${{ runner.os }}-go-${{ hashFiles('**/buildpack.toml', '**/package.toml') }} 43 | path: |- 44 | ${{ env.HOME }}/.pack 45 | ${{ env.HOME }}/carton-cache 46 | restore-keys: ${{ runner.os }}-go- 47 | - name: Compute Version 48 | id: version 49 | run: | 50 | #!/usr/bin/env bash 51 | 52 | set -euo pipefail 53 | 54 | if [[ ${GITHUB_REF:-} != "refs/"* ]]; then 55 | echo "GITHUB_REF set to [${GITHUB_REF:-}], but that is unexpected. It should start with 'refs/*'" 56 | exit 255 57 | fi 58 | 59 | if [[ ${GITHUB_REF} =~ refs/tags/v([0-9]+\.[0-9]+\.[0-9]+) ]]; then 60 | VERSION=${BASH_REMATCH[1]} 61 | 62 | MAJOR_VERSION="$(echo "${VERSION}" | awk -F '.' '{print $1 }')" 63 | MINOR_VERSION="$(echo "${VERSION}" | awk -F '.' '{print $1 "." $2 }')" 64 | 65 | echo "version-major=${MAJOR_VERSION}" >> "$GITHUB_OUTPUT" 66 | echo "version-minor=${MINOR_VERSION}" >> "$GITHUB_OUTPUT" 67 | elif [[ ${GITHUB_REF} =~ refs/heads/(.+) ]]; then 68 | VERSION=${BASH_REMATCH[1]} 69 | else 70 | VERSION=$(git rev-parse --short HEAD) 71 | fi 72 | 73 | echo "version=${VERSION}" >> "$GITHUB_OUTPUT" 74 | echo "Selected ${VERSION} from 75 | * ref: ${GITHUB_REF} 76 | * sha: ${GITHUB_SHA} 77 | " 78 | - name: Create Package 79 | run: | 80 | #!/usr/bin/env bash 81 | 82 | set -euo pipefail 83 | 84 | # With Go 1.20, we need to set this so that we produce statically compiled binaries 85 | # 86 | # Starting with Go 1.20, Go will produce binaries that are dynamically linked against libc 87 | # which can cause compatibility issues. The compiler links against libc on the build system 88 | # but that may be newer than on the stacks we support. 89 | export CGO_ENABLED=0 90 | 91 | if [[ "${INCLUDE_DEPENDENCIES}" == "true" ]]; then 92 | create-package \ 93 | --source "${SOURCE_PATH:-.}" \ 94 | --cache-location "${HOME}"/carton-cache \ 95 | --destination "${HOME}"/buildpack \ 96 | --include-dependencies \ 97 | --version "${VERSION}" 98 | else 99 | create-package \ 100 | --source "${SOURCE_PATH:-.}" \ 101 | --destination "${HOME}"/buildpack \ 102 | --version "${VERSION}" 103 | fi 104 | 105 | PACKAGE_FILE="${SOURCE_PATH:-.}/package.toml" 106 | if [ -f "${PACKAGE_FILE}" ]; then 107 | cp "${PACKAGE_FILE}" "${HOME}/buildpack/package.toml" 108 | printf '[buildpack]\nuri = "%s"\n\n[platform]\nos = "%s"\n' "${HOME}/buildpack" "${OS}" >> "${HOME}/buildpack/package.toml" 109 | fi 110 | env: 111 | INCLUDE_DEPENDENCIES: "true" 112 | OS: linux 113 | VERSION: ${{ steps.version.outputs.version }} 114 | - name: Package Buildpack 115 | run: |- 116 | #!/usr/bin/env bash 117 | 118 | set -euo pipefail 119 | 120 | COMPILED_BUILDPACK="${HOME}/buildpack" 121 | 122 | # create-package puts the buildpack here, we need to run from that directory 123 | # for component buildpacks so that pack doesn't need a package.toml 124 | cd "${COMPILED_BUILDPACK}" 125 | CONFIG="" 126 | if [ -f "${COMPILED_BUILDPACK}/package.toml" ]; then 127 | CONFIG="--config ${COMPILED_BUILDPACK}/package.toml --flatten" 128 | fi 129 | 130 | PACKAGE_LIST=($PACKAGES) 131 | # Extract first repo (Docker Hub) as the main to package & register 132 | PACKAGE=${PACKAGE_LIST[0]} 133 | 134 | if [[ "${PUBLISH:-x}" == "true" ]]; then 135 | pack -v buildpack package \ 136 | "${PACKAGE}:${VERSION}" ${CONFIG} \ 137 | --publish 138 | 139 | if [[ -n ${VERSION_MINOR:-} && -n ${VERSION_MAJOR:-} ]]; then 140 | crane tag "${PACKAGE}:${VERSION}" "${VERSION_MINOR}" 141 | crane tag "${PACKAGE}:${VERSION}" "${VERSION_MAJOR}" 142 | fi 143 | crane tag "${PACKAGE}:${VERSION}" latest 144 | echo "digest=$(crane digest "${PACKAGE}:${VERSION}")" >> "$GITHUB_OUTPUT" 145 | 146 | # copy to other repositories specified 147 | for P in "${PACKAGE_LIST[@]}" 148 | do 149 | if [ "$P" != "$PACKAGE" ]; then 150 | crane copy "${PACKAGE}:${VERSION}" "${P}:${VERSION}" 151 | if [[ -n ${VERSION_MINOR:-} && -n ${VERSION_MAJOR:-} ]]; then 152 | crane tag "${P}:${VERSION}" "${VERSION_MINOR}" 153 | crane tag "${P}:${VERSION}" "${VERSION_MAJOR}" 154 | fi 155 | crane tag "${P}:${VERSION}" latest 156 | fi 157 | done 158 | else 159 | if [ -n "$TTL_SH_PUBLISH" ] && [ "$TTL_SH_PUBLISH" = "true" ]; then 160 | TAG="${PACKAGE}-$(mktemp -u XXXXX | awk '{print tolower($0)}'):${VERSION}" 161 | pack -v buildpack package "${TAG}" ${CONFIG} --format "${FORMAT}" --publish 162 | else 163 | TAG="${PACKAGE}:${VERSION}" 164 | pack -v buildpack package "${TAG}" ${CONFIG} --format "${FORMAT}" 165 | fi 166 | 167 | echo "ttl-image-tag=${TAG:-}" >> "$GITHUB_OUTPUT" 168 | fi 169 | env: 170 | FORMAT: image 171 | PACKAGES: test 172 | TTL_SH_PUBLISH: "false" 173 | VERSION: ${{ steps.version.outputs.version }} 174 | unit: 175 | name: Unit Test 176 | runs-on: 177 | - ubuntu-latest 178 | steps: 179 | - uses: actions/checkout@v4 180 | - uses: actions/cache@v4 181 | with: 182 | key: ${{ runner.os }}-go-${{ hashFiles('**/go.sum') }} 183 | path: ${{ env.HOME }}/go/pkg/mod 184 | restore-keys: ${{ runner.os }}-go- 185 | - uses: actions/setup-go@v5 186 | with: 187 | go-version: "1.25" 188 | - name: Install richgo 189 | run: | 190 | #!/usr/bin/env bash 191 | 192 | set -euo pipefail 193 | 194 | echo "Installing richgo ${RICHGO_VERSION}" 195 | 196 | mkdir -p "${HOME}"/bin 197 | echo "${HOME}/bin" >> "${GITHUB_PATH}" 198 | 199 | curl \ 200 | --location \ 201 | --show-error \ 202 | --silent \ 203 | "https://github.com/kyoh86/richgo/releases/download/v${RICHGO_VERSION}/richgo_${RICHGO_VERSION}_linux_amd64.tar.gz" \ 204 | | tar -C "${HOME}"/bin -xz richgo 205 | env: 206 | RICHGO_VERSION: 0.3.10 207 | - name: Run Tests 208 | run: | 209 | #!/usr/bin/env bash 210 | 211 | set -euo pipefail 212 | 213 | richgo test ./... 214 | env: 215 | RICHGO_FORCE_COLOR: "1" 216 | -------------------------------------------------------------------------------- /.github/workflows/pb-create-package.yml: -------------------------------------------------------------------------------- 1 | name: Create Package 2 | "on": 3 | release: 4 | types: 5 | - published 6 | jobs: 7 | create-package: 8 | name: Create Package 9 | runs-on: 10 | - ubuntu-latest 11 | steps: 12 | - name: Docker login docker.io 13 | if: ${{ (github.event_name != 'pull_request' || ! github.event.pull_request.head.repo.fork) && (github.actor != 'dependabot[bot]') }} 14 | uses: docker/login-action@v3 15 | with: 16 | password: ${{ secrets.PAKETO_BUILDPACKS_DOCKERHUB_PASSWORD }} 17 | registry: docker.io 18 | username: ${{ secrets.PAKETO_BUILDPACKS_DOCKERHUB_USERNAME }} 19 | - uses: actions/setup-go@v5 20 | with: 21 | go-version: "1.25" 22 | - name: Install create-package 23 | run: | 24 | #!/usr/bin/env bash 25 | 26 | set -euo pipefail 27 | 28 | go install -ldflags="-s -w" github.com/paketo-buildpacks/libpak/cmd/create-package@latest 29 | - uses: buildpacks/github-actions/setup-tools@v5.9.7 30 | with: 31 | crane-version: 0.20.3 32 | yj-version: 5.1.0 33 | - uses: buildpacks/github-actions/setup-pack@v5.9.7 34 | with: 35 | pack-version: 0.39.1 36 | - name: Enable pack Experimental 37 | run: | 38 | #!/usr/bin/env bash 39 | 40 | set -euo pipefail 41 | 42 | echo "Enabling pack experimental features" 43 | pack config experimental true 44 | - uses: actions/checkout@v4 45 | - if: ${{ false }} 46 | uses: actions/cache@v4 47 | with: 48 | key: ${{ runner.os }}-go-${{ hashFiles('**/buildpack.toml', '**/package.toml') }} 49 | path: |- 50 | ${{ env.HOME }}/.pack 51 | ${{ env.HOME }}/carton-cache 52 | restore-keys: ${{ runner.os }}-go- 53 | - name: Compute Version 54 | id: version 55 | run: | 56 | #!/usr/bin/env bash 57 | 58 | set -euo pipefail 59 | 60 | if [[ ${GITHUB_REF:-} != "refs/"* ]]; then 61 | echo "GITHUB_REF set to [${GITHUB_REF:-}], but that is unexpected. It should start with 'refs/*'" 62 | exit 255 63 | fi 64 | 65 | if [[ ${GITHUB_REF} =~ refs/tags/v([0-9]+\.[0-9]+\.[0-9]+) ]]; then 66 | VERSION=${BASH_REMATCH[1]} 67 | 68 | MAJOR_VERSION="$(echo "${VERSION}" | awk -F '.' '{print $1 }')" 69 | MINOR_VERSION="$(echo "${VERSION}" | awk -F '.' '{print $1 "." $2 }')" 70 | 71 | echo "version-major=${MAJOR_VERSION}" >> "$GITHUB_OUTPUT" 72 | echo "version-minor=${MINOR_VERSION}" >> "$GITHUB_OUTPUT" 73 | elif [[ ${GITHUB_REF} =~ refs/heads/(.+) ]]; then 74 | VERSION=${BASH_REMATCH[1]} 75 | else 76 | VERSION=$(git rev-parse --short HEAD) 77 | fi 78 | 79 | echo "version=${VERSION}" >> "$GITHUB_OUTPUT" 80 | echo "Selected ${VERSION} from 81 | * ref: ${GITHUB_REF} 82 | * sha: ${GITHUB_SHA} 83 | " 84 | - name: Create Package 85 | run: | 86 | #!/usr/bin/env bash 87 | 88 | set -euo pipefail 89 | 90 | # With Go 1.20, we need to set this so that we produce statically compiled binaries 91 | # 92 | # Starting with Go 1.20, Go will produce binaries that are dynamically linked against libc 93 | # which can cause compatibility issues. The compiler links against libc on the build system 94 | # but that may be newer than on the stacks we support. 95 | export CGO_ENABLED=0 96 | 97 | if [[ "${INCLUDE_DEPENDENCIES}" == "true" ]]; then 98 | create-package \ 99 | --source "${SOURCE_PATH:-.}" \ 100 | --cache-location "${HOME}"/carton-cache \ 101 | --destination "${HOME}"/buildpack \ 102 | --include-dependencies \ 103 | --version "${VERSION}" 104 | else 105 | create-package \ 106 | --source "${SOURCE_PATH:-.}" \ 107 | --destination "${HOME}"/buildpack \ 108 | --version "${VERSION}" 109 | fi 110 | 111 | PACKAGE_FILE="${SOURCE_PATH:-.}/package.toml" 112 | if [ -f "${PACKAGE_FILE}" ]; then 113 | cp "${PACKAGE_FILE}" "${HOME}/buildpack/package.toml" 114 | printf '[buildpack]\nuri = "%s"\n\n[platform]\nos = "%s"\n' "${HOME}/buildpack" "${OS}" >> "${HOME}/buildpack/package.toml" 115 | fi 116 | env: 117 | INCLUDE_DEPENDENCIES: "false" 118 | OS: linux 119 | SOURCE_PATH: "" 120 | VERSION: ${{ steps.version.outputs.version }} 121 | - name: Package Buildpack 122 | id: package 123 | run: |- 124 | #!/usr/bin/env bash 125 | 126 | set -euo pipefail 127 | 128 | COMPILED_BUILDPACK="${HOME}/buildpack" 129 | 130 | # create-package puts the buildpack here, we need to run from that directory 131 | # for component buildpacks so that pack doesn't need a package.toml 132 | cd "${COMPILED_BUILDPACK}" 133 | CONFIG="" 134 | if [ -f "${COMPILED_BUILDPACK}/package.toml" ]; then 135 | CONFIG="--config ${COMPILED_BUILDPACK}/package.toml --flatten" 136 | fi 137 | 138 | PACKAGE_LIST=($PACKAGES) 139 | # Extract first repo (Docker Hub) as the main to package & register 140 | PACKAGE=${PACKAGE_LIST[0]} 141 | 142 | if [[ "${PUBLISH:-x}" == "true" ]]; then 143 | pack -v buildpack package \ 144 | "${PACKAGE}:${VERSION}" ${CONFIG} \ 145 | --publish 146 | 147 | if [[ -n ${VERSION_MINOR:-} && -n ${VERSION_MAJOR:-} ]]; then 148 | crane tag "${PACKAGE}:${VERSION}" "${VERSION_MINOR}" 149 | crane tag "${PACKAGE}:${VERSION}" "${VERSION_MAJOR}" 150 | fi 151 | crane tag "${PACKAGE}:${VERSION}" latest 152 | echo "digest=$(crane digest "${PACKAGE}:${VERSION}")" >> "$GITHUB_OUTPUT" 153 | 154 | # copy to other repositories specified 155 | for P in "${PACKAGE_LIST[@]}" 156 | do 157 | if [ "$P" != "$PACKAGE" ]; then 158 | crane copy "${PACKAGE}:${VERSION}" "${P}:${VERSION}" 159 | if [[ -n ${VERSION_MINOR:-} && -n ${VERSION_MAJOR:-} ]]; then 160 | crane tag "${P}:${VERSION}" "${VERSION_MINOR}" 161 | crane tag "${P}:${VERSION}" "${VERSION_MAJOR}" 162 | fi 163 | crane tag "${P}:${VERSION}" latest 164 | fi 165 | done 166 | else 167 | if [ -n "$TTL_SH_PUBLISH" ] && [ "$TTL_SH_PUBLISH" = "true" ]; then 168 | TAG="${PACKAGE}-$(mktemp -u XXXXX | awk '{print tolower($0)}'):${VERSION}" 169 | pack -v buildpack package "${TAG}" ${CONFIG} --format "${FORMAT}" --publish 170 | else 171 | TAG="${PACKAGE}:${VERSION}" 172 | pack -v buildpack package "${TAG}" ${CONFIG} --format "${FORMAT}" 173 | fi 174 | 175 | echo "ttl-image-tag=${TAG:-}" >> "$GITHUB_OUTPUT" 176 | fi 177 | env: 178 | PACKAGES: docker.io/paketobuildpacks/apache-tomcat 179 | PUBLISH: "true" 180 | VERSION: ${{ steps.version.outputs.version }} 181 | VERSION_MAJOR: ${{ steps.version.outputs.version-major }} 182 | VERSION_MINOR: ${{ steps.version.outputs.version-minor }} 183 | - name: Update release with digest 184 | run: | 185 | #!/usr/bin/env bash 186 | 187 | set -euo pipefail 188 | 189 | PAYLOAD=$(cat "${GITHUB_EVENT_PATH}") 190 | 191 | RELEASE_ID=$(jq -n -r --argjson PAYLOAD "${PAYLOAD}" '$PAYLOAD.release.id') 192 | RELEASE_TAG_NAME=$(jq -n -r --argjson PAYLOAD "${PAYLOAD}" '$PAYLOAD.release.tag_name') 193 | RELEASE_NAME=$(jq -n -r --argjson PAYLOAD "${PAYLOAD}" '$PAYLOAD.release.name') 194 | RELEASE_BODY=$(jq -n -r --argjson PAYLOAD "${PAYLOAD}" '$PAYLOAD.release.body') 195 | 196 | gh api \ 197 | --method PATCH \ 198 | "/repos/:owner/:repo/releases/${RELEASE_ID}" \ 199 | --field "tag_name=${RELEASE_TAG_NAME}" \ 200 | --field "name=${RELEASE_NAME}" \ 201 | --field "body=${RELEASE_BODY///\`${DIGEST}\`}" 202 | env: 203 | DIGEST: ${{ steps.package.outputs.digest }} 204 | GITHUB_TOKEN: ${{ secrets.PAKETO_BOT_GITHUB_TOKEN }} 205 | - if: ${{ true }} 206 | uses: docker://ghcr.io/buildpacks/actions/registry/request-add-entry:5.9.7 207 | with: 208 | address: docker.io/paketobuildpacks/apache-tomcat@${{ steps.package.outputs.digest }} 209 | id: paketo-buildpacks/apache-tomcat 210 | token: ${{ secrets.PAKETO_BOT_GITHUB_TOKEN }} 211 | version: ${{ steps.version.outputs.version }} 212 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | 2 | Apache License 3 | Version 2.0, January 2004 4 | https://www.apache.org/licenses/ 5 | 6 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 7 | 8 | 1. Definitions. 9 | 10 | "License" shall mean the terms and conditions for use, reproduction, 11 | and distribution as defined by Sections 1 through 9 of this document. 12 | 13 | "Licensor" shall mean the copyright owner or entity authorized by 14 | the copyright owner that is granting the License. 15 | 16 | "Legal Entity" shall mean the union of the acting entity and all 17 | other entities that control, are controlled by, or are under common 18 | control with that entity. For the purposes of this definition, 19 | "control" means (i) the power, direct or indirect, to cause the 20 | direction or management of such entity, whether by contract or 21 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 22 | outstanding shares, or (iii) beneficial ownership of such entity. 23 | 24 | "You" (or "Your") shall mean an individual or Legal Entity 25 | exercising permissions granted by this License. 26 | 27 | "Source" form shall mean the preferred form for making modifications, 28 | including but not limited to software source code, documentation 29 | source, and configuration files. 30 | 31 | "Object" form shall mean any form resulting from mechanical 32 | transformation or translation of a Source form, including but 33 | not limited to compiled object code, generated documentation, 34 | and conversions to other media types. 35 | 36 | "Work" shall mean the work of authorship, whether in Source or 37 | Object form, made available under the License, as indicated by a 38 | copyright notice that is included in or attached to the work 39 | (an example is provided in the Appendix below). 40 | 41 | "Derivative Works" shall mean any work, whether in Source or Object 42 | form, that is based on (or derived from) the Work and for which the 43 | editorial revisions, annotations, elaborations, or other modifications 44 | represent, as a whole, an original work of authorship. For the purposes 45 | of this License, Derivative Works shall not include works that remain 46 | separable from, or merely link (or bind by name) to the interfaces of, 47 | the Work and Derivative Works thereof. 48 | 49 | "Contribution" shall mean any work of authorship, including 50 | the original version of the Work and any modifications or additions 51 | to that Work or Derivative Works thereof, that is intentionally 52 | submitted to Licensor for inclusion in the Work by the copyright owner 53 | or by an individual or Legal Entity authorized to submit on behalf of 54 | the copyright owner. For the purposes of this definition, "submitted" 55 | means any form of electronic, verbal, or written communication sent 56 | to the Licensor or its representatives, including but not limited to 57 | communication on electronic mailing lists, source code control systems, 58 | and issue tracking systems that are managed by, or on behalf of, the 59 | Licensor for the purpose of discussing and improving the Work, but 60 | excluding communication that is conspicuously marked or otherwise 61 | designated in writing by the copyright owner as "Not a Contribution." 62 | 63 | "Contributor" shall mean Licensor and any individual or Legal Entity 64 | on behalf of whom a Contribution has been received by Licensor and 65 | subsequently incorporated within the Work. 66 | 67 | 2. Grant of Copyright License. Subject to the terms and conditions of 68 | this License, each Contributor hereby grants to You a perpetual, 69 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 70 | copyright license to reproduce, prepare Derivative Works of, 71 | publicly display, publicly perform, sublicense, and distribute the 72 | Work and such Derivative Works in Source or Object form. 73 | 74 | 3. Grant of Patent License. Subject to the terms and conditions of 75 | this License, each Contributor hereby grants to You a perpetual, 76 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 77 | (except as stated in this section) patent license to make, have made, 78 | use, offer to sell, sell, import, and otherwise transfer the Work, 79 | where such license applies only to those patent claims licensable 80 | by such Contributor that are necessarily infringed by their 81 | Contribution(s) alone or by combination of their Contribution(s) 82 | with the Work to which such Contribution(s) was submitted. If You 83 | institute patent litigation against any entity (including a 84 | cross-claim or counterclaim in a lawsuit) alleging that the Work 85 | or a Contribution incorporated within the Work constitutes direct 86 | or contributory patent infringement, then any patent licenses 87 | granted to You under this License for that Work shall terminate 88 | as of the date such litigation is filed. 89 | 90 | 4. Redistribution. You may reproduce and distribute copies of the 91 | Work or Derivative Works thereof in any medium, with or without 92 | modifications, and in Source or Object form, provided that You 93 | meet the following conditions: 94 | 95 | (a) You must give any other recipients of the Work or 96 | Derivative Works a copy of this License; and 97 | 98 | (b) You must cause any modified files to carry prominent notices 99 | stating that You changed the files; and 100 | 101 | (c) You must retain, in the Source form of any Derivative Works 102 | that You distribute, all copyright, patent, trademark, and 103 | attribution notices from the Source form of the Work, 104 | excluding those notices that do not pertain to any part of 105 | the Derivative Works; and 106 | 107 | (d) If the Work includes a "NOTICE" text file as part of its 108 | distribution, then any Derivative Works that You distribute must 109 | include a readable copy of the attribution notices contained 110 | within such NOTICE file, excluding those notices that do not 111 | pertain to any part of the Derivative Works, in at least one 112 | of the following places: within a NOTICE text file distributed 113 | as part of the Derivative Works; within the Source form or 114 | documentation, if provided along with the Derivative Works; or, 115 | within a display generated by the Derivative Works, if and 116 | wherever such third-party notices normally appear. The contents 117 | of the NOTICE file are for informational purposes only and 118 | do not modify the License. You may add Your own attribution 119 | notices within Derivative Works that You distribute, alongside 120 | or as an addendum to the NOTICE text from the Work, provided 121 | that such additional attribution notices cannot be construed 122 | as modifying the License. 123 | 124 | You may add Your own copyright statement to Your modifications and 125 | may provide additional or different license terms and conditions 126 | for use, reproduction, or distribution of Your modifications, or 127 | for any such Derivative Works as a whole, provided Your use, 128 | reproduction, and distribution of the Work otherwise complies with 129 | the conditions stated in this License. 130 | 131 | 5. Submission of Contributions. Unless You explicitly state otherwise, 132 | any Contribution intentionally submitted for inclusion in the Work 133 | by You to the Licensor shall be under the terms and conditions of 134 | this License, without any additional terms or conditions. 135 | Notwithstanding the above, nothing herein shall supersede or modify 136 | the terms of any separate license agreement you may have executed 137 | with Licensor regarding such Contributions. 138 | 139 | 6. Trademarks. This License does not grant permission to use the trade 140 | names, trademarks, service marks, or product names of the Licensor, 141 | except as required for reasonable and customary use in describing the 142 | origin of the Work and reproducing the content of the NOTICE file. 143 | 144 | 7. Disclaimer of Warranty. Unless required by applicable law or 145 | agreed to in writing, Licensor provides the Work (and each 146 | Contributor provides its Contributions) on an "AS IS" BASIS, 147 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 148 | implied, including, without limitation, any warranties or conditions 149 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 150 | PARTICULAR PURPOSE. You are solely responsible for determining the 151 | appropriateness of using or redistributing the Work and assume any 152 | risks associated with Your exercise of permissions under this License. 153 | 154 | 8. Limitation of Liability. In no event and under no legal theory, 155 | whether in tort (including negligence), contract, or otherwise, 156 | unless required by applicable law (such as deliberate and grossly 157 | negligent acts) or agreed to in writing, shall any Contributor be 158 | liable to You for damages, including any direct, indirect, special, 159 | incidental, or consequential damages of any character arising as a 160 | result of this License or out of the use or inability to use the 161 | Work (including but not limited to damages for loss of goodwill, 162 | work stoppage, computer failure or malfunction, or any and all 163 | other commercial damages or losses), even if such Contributor 164 | has been advised of the possibility of such damages. 165 | 166 | 9. Accepting Warranty or Additional Liability. While redistributing 167 | the Work or Derivative Works thereof, You may choose to offer, 168 | and charge a fee for, acceptance of support, warranty, indemnity, 169 | or other liability obligations and/or rights consistent with this 170 | License. However, in accepting such obligations, You may act only 171 | on Your own behalf and on Your sole responsibility, not on behalf 172 | of any other Contributor, and only if You agree to indemnify, 173 | defend, and hold each Contributor harmless for any liability 174 | incurred by, or claims asserted against, such Contributor by reason 175 | of your accepting any such warranty or additional liability. 176 | 177 | END OF TERMS AND CONDITIONS 178 | 179 | APPENDIX: How to apply the Apache License to your work. 180 | 181 | To apply the Apache License to your work, attach the following 182 | boilerplate notice, with the fields enclosed by brackets "[]" 183 | replaced with your own identifying information. (Don't include 184 | the brackets!) The text should be enclosed in the appropriate 185 | comment syntax for the file format. We also recommend that a 186 | file or class name and description of purpose be included on the 187 | same "printed page" as the copyright notice for easier 188 | identification within third-party archives. 189 | 190 | Copyright [yyyy] [name of copyright owner] 191 | 192 | Licensed under the Apache License, Version 2.0 (the "License"); 193 | you may not use this file except in compliance with the License. 194 | You may obtain a copy of the License at 195 | 196 | https://www.apache.org/licenses/LICENSE-2.0 197 | 198 | Unless required by applicable law or agreed to in writing, software 199 | distributed under the License is distributed on an "AS IS" BASIS, 200 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 201 | See the License for the specific language governing permissions and 202 | limitations under the License. 203 | -------------------------------------------------------------------------------- /tomcat/base.go: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2018-2020 the original author or authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package tomcat 18 | 19 | import ( 20 | "errors" 21 | "fmt" 22 | "github.com/paketo-buildpacks/apache-tomcat/v8/internal/util" 23 | "os" 24 | "path/filepath" 25 | "strconv" 26 | "strings" 27 | 28 | "github.com/paketo-buildpacks/libpak/sbom" 29 | 30 | "github.com/buildpacks/libcnb" 31 | "github.com/heroku/color" 32 | "github.com/paketo-buildpacks/libpak" 33 | "github.com/paketo-buildpacks/libpak/bard" 34 | "github.com/paketo-buildpacks/libpak/crush" 35 | "github.com/paketo-buildpacks/libpak/sherpa" 36 | ) 37 | 38 | type Base struct { 39 | AccessLoggingDependency libpak.BuildpackDependency 40 | ApplicationPath string 41 | BuildpackPath string 42 | ConfigurationResolver libpak.ConfigurationResolver 43 | ContextPath string 44 | DependencyCache libpak.DependencyCache 45 | ExternalConfigurationDependency *libpak.BuildpackDependency 46 | LayerContributor libpak.LayerContributor 47 | LifecycleDependency libpak.BuildpackDependency 48 | LoggingDependency libpak.BuildpackDependency 49 | Logger bard.Logger 50 | WarFilesExist bool 51 | } 52 | 53 | func NewBase( 54 | applicationPath string, 55 | buildpackPath string, 56 | configurationResolver libpak.ConfigurationResolver, 57 | contextPath string, 58 | accessLoggingDependency libpak.BuildpackDependency, 59 | externalConfigurationDependency *libpak.BuildpackDependency, 60 | lifecycleDependency libpak.BuildpackDependency, 61 | loggingDependency libpak.BuildpackDependency, 62 | cache libpak.DependencyCache, 63 | warFilesExist bool, 64 | ) (Base, []libcnb.BOMEntry) { 65 | 66 | dependencies := []libpak.BuildpackDependency{accessLoggingDependency, lifecycleDependency, loggingDependency} 67 | if externalConfigurationDependency != nil { 68 | dependencies = append(dependencies, *externalConfigurationDependency) 69 | } 70 | 71 | b := Base{ 72 | AccessLoggingDependency: accessLoggingDependency, 73 | ApplicationPath: applicationPath, 74 | BuildpackPath: buildpackPath, 75 | ConfigurationResolver: configurationResolver, 76 | ContextPath: contextPath, 77 | DependencyCache: cache, 78 | ExternalConfigurationDependency: externalConfigurationDependency, 79 | LayerContributor: libpak.NewLayerContributor("Apache Tomcat Support", map[string]interface{}{ 80 | "context-path": contextPath, 81 | "dependencies": dependencies, 82 | }, libcnb.LayerTypes{ 83 | Launch: true, 84 | }), 85 | LifecycleDependency: lifecycleDependency, 86 | LoggingDependency: loggingDependency, 87 | WarFilesExist: warFilesExist, 88 | } 89 | 90 | var bomEntries []libcnb.BOMEntry 91 | 92 | var entry libcnb.BOMEntry 93 | entry = accessLoggingDependency.AsBOMEntry() 94 | entry.Metadata["layer"] = b.Name() 95 | entry.Launch = true 96 | bomEntries = append(bomEntries, entry) 97 | 98 | entry = lifecycleDependency.AsBOMEntry() 99 | entry.Metadata["layer"] = b.Name() 100 | entry.Launch = true 101 | bomEntries = append(bomEntries, entry) 102 | 103 | entry = loggingDependency.AsBOMEntry() 104 | entry.Metadata["layer"] = b.Name() 105 | entry.Launch = true 106 | bomEntries = append(bomEntries, entry) 107 | 108 | if externalConfigurationDependency != nil { 109 | entry = externalConfigurationDependency.AsBOMEntry() 110 | entry.Metadata["layer"] = b.Name() 111 | entry.Launch = true 112 | bomEntries = append(bomEntries, entry) 113 | } 114 | 115 | return b, bomEntries 116 | } 117 | 118 | func (b Base) Contribute(layer libcnb.Layer) (libcnb.Layer, error) { 119 | b.LayerContributor.Logger = b.Logger 120 | var syftArtifacts []sbom.SyftArtifact 121 | 122 | return b.LayerContributor.Contribute(layer, func() (libcnb.Layer, error) { 123 | 124 | if err := b.ContributeConfiguration(layer); err != nil { 125 | return libcnb.Layer{}, fmt.Errorf("unable to contribute configuration\n%w", err) 126 | } 127 | 128 | if err := b.ContributeAccessLogging(layer); err != nil { 129 | return libcnb.Layer{}, fmt.Errorf("unable to contribute access logging\n%w", err) 130 | } 131 | if syftArtifact, err := b.AccessLoggingDependency.AsSyftArtifact(); err != nil { 132 | return libcnb.Layer{}, fmt.Errorf("unable to get Syft Artifact for dependency: %s, \n%w", b.AccessLoggingDependency.Name, err) 133 | } else { 134 | syftArtifacts = append(syftArtifacts, syftArtifact) 135 | } 136 | 137 | if err := b.ContributeLifecycle(layer); err != nil { 138 | return libcnb.Layer{}, fmt.Errorf("unable to contribute lifecycle\n%w", err) 139 | } 140 | if syftArtifact, err := b.LifecycleDependency.AsSyftArtifact(); err != nil { 141 | return libcnb.Layer{}, fmt.Errorf("unable to get Syft Artifact for dependency: %s, \n%w", b.LifecycleDependency.Name, err) 142 | } else { 143 | syftArtifacts = append(syftArtifacts, syftArtifact) 144 | } 145 | 146 | if err := b.ContributeLogging(layer); err != nil { 147 | return libcnb.Layer{}, fmt.Errorf("unable to contribute logging\n%w", err) 148 | } 149 | if syftArtifact, err := b.LoggingDependency.AsSyftArtifact(); err != nil { 150 | return libcnb.Layer{}, fmt.Errorf("unable to get Syft Artifact for dependency: %s, \n%w", b.LoggingDependency.Name, err) 151 | } else { 152 | syftArtifacts = append(syftArtifacts, syftArtifact) 153 | } 154 | 155 | if b.ExternalConfigurationDependency != nil { 156 | if err := b.ContributeExternalConfiguration(layer); err != nil { 157 | return libcnb.Layer{}, fmt.Errorf("unable to contribute external configuration\n%w", err) 158 | } 159 | if syftArtifact, err := b.ExternalConfigurationDependency.AsSyftArtifact(); err != nil { 160 | return libcnb.Layer{}, fmt.Errorf("unable to get Syft Artifact for dependency: %s, \n%w", b.ExternalConfigurationDependency.Name, err) 161 | } else { 162 | syftArtifacts = append(syftArtifacts, syftArtifact) 163 | } 164 | } 165 | 166 | if err := b.ContributeCatalinaProps(layer); err != nil { 167 | return libcnb.Layer{}, fmt.Errorf("unable to contribute configuration\n%w", err) 168 | } 169 | 170 | file := filepath.Join(layer.Path, "temp") 171 | if err := os.MkdirAll(file, 0700); err != nil { 172 | return libcnb.Layer{}, fmt.Errorf("unable to create directory %s\n%w", file, err) 173 | } 174 | 175 | file = filepath.Join(layer.Path, "webapps") 176 | if b.WarFilesExist { 177 | if err := os.Symlink(b.ApplicationPath, file); err != nil { 178 | return libcnb.Layer{}, fmt.Errorf("unable to create symlink from %s to %s\n%w", b.ApplicationPath, file, err) 179 | } 180 | if err := b.explodeWarFiles(); err != nil { 181 | return libcnb.Layer{}, fmt.Errorf("unable to explode war files in %s\n%w", b.ApplicationPath, err) 182 | } 183 | } else { 184 | if err := os.MkdirAll(file, 0755); err != nil { 185 | return libcnb.Layer{}, fmt.Errorf("unable to create directory %s\n%w", file, err) 186 | } 187 | 188 | file = filepath.Join(layer.Path, "webapps", b.ContextPath) 189 | b.Logger.Headerf("Mounting application at %s", b.ContextPath) 190 | if err := os.Symlink(b.ApplicationPath, file); err != nil { 191 | return libcnb.Layer{}, fmt.Errorf("unable to create symlink from %s to %s\n%w", b.ApplicationPath, file, err) 192 | } 193 | } 194 | 195 | catalinaOpts := "-DBPI_TOMCAT_ADDITIONAL_COMMON_JARS=${BPI_TOMCAT_ADDITIONAL_COMMON_JARS}" 196 | environmentPropertySourceDisabled := b.ConfigurationResolver.ResolveBool("BP_TOMCAT_ENV_PROPERTY_SOURCE_DISABLED") 197 | if !environmentPropertySourceDisabled { 198 | catalinaOpts += " -Dorg.apache.tomcat.util.digester.PROPERTY_SOURCE=org.apache.tomcat.util.digester.EnvironmentPropertySource" 199 | } 200 | layer.LaunchEnvironment.Default("CATALINA_OPTS", catalinaOpts) 201 | 202 | layer.LaunchEnvironment.Default("CATALINA_BASE", layer.Path) 203 | layer.LaunchEnvironment.Default("CATALINA_TMPDIR", "/tmp") 204 | 205 | if err := b.writeDependencySBOM(layer, syftArtifacts); err != nil { 206 | return libcnb.Layer{}, err 207 | } 208 | 209 | return layer, nil 210 | }) 211 | } 212 | 213 | func (b Base) ContributeAccessLogging(layer libcnb.Layer) error { 214 | b.Logger.Header(color.BlueString("%s %s", b.AccessLoggingDependency.Name, b.AccessLoggingDependency.Version)) 215 | 216 | artifact, err := b.DependencyCache.Artifact(b.AccessLoggingDependency) 217 | if err != nil { 218 | return fmt.Errorf("unable to get dependency %s\n%w", b.AccessLoggingDependency.ID, err) 219 | } 220 | defer artifact.Close() 221 | 222 | b.Logger.Bodyf("Copying to %s/lib", layer.Path) 223 | 224 | file := filepath.Join(layer.Path, "lib", filepath.Base(b.AccessLoggingDependency.URI)) 225 | if err := sherpa.CopyFile(artifact, file); err != nil { 226 | return fmt.Errorf("unable to copy %s to %s\n%w", filepath.Base(b.AccessLoggingDependency.URI), file, err) 227 | } 228 | 229 | return nil 230 | } 231 | 232 | func (b Base) ContributeConfiguration(layer libcnb.Layer) error { 233 | file := filepath.Join(layer.Path, "conf") 234 | if err := os.MkdirAll(file, 0755); err != nil { 235 | return fmt.Errorf("unable to create directory %s\n%w", file, err) 236 | } 237 | 238 | b.Logger.Bodyf("Copying context.xml to %s/conf", layer.Path) 239 | file = filepath.Join(b.BuildpackPath, "resources", "context.xml") 240 | in, err := os.Open(file) 241 | if err != nil { 242 | return fmt.Errorf("unable to open %s\n%w", file, err) 243 | } 244 | defer in.Close() 245 | 246 | file = filepath.Join(layer.Path, "conf", "context.xml") 247 | if err := sherpa.CopyFile(in, file); err != nil { 248 | return fmt.Errorf("unable to copy %s to %s\n%w", in.Name(), file, err) 249 | } 250 | 251 | b.Logger.Bodyf("Copying logging.properties to %s/conf", layer.Path) 252 | file = filepath.Join(b.BuildpackPath, "resources", "logging.properties") 253 | in, err = os.Open(file) 254 | if err != nil { 255 | return fmt.Errorf("unable to open %s\n%w", file, err) 256 | } 257 | defer in.Close() 258 | 259 | file = filepath.Join(layer.Path, "conf", "logging.properties") 260 | if err := sherpa.CopyFile(in, file); err != nil { 261 | return fmt.Errorf("unable to copy %s to %s\n%w", in.Name(), file, err) 262 | } 263 | 264 | b.Logger.Bodyf("Copying server.xml to %s/conf", layer.Path) 265 | file = filepath.Join(b.BuildpackPath, "resources", "server.xml") 266 | in, err = os.Open(file) 267 | if err != nil { 268 | return fmt.Errorf("unable to open %s\n%w", file, err) 269 | } 270 | defer in.Close() 271 | 272 | file = filepath.Join(layer.Path, "conf", "server.xml") 273 | if err := sherpa.CopyFile(in, file); err != nil { 274 | return fmt.Errorf("unable to copy %s to %s\n%w", in.Name(), file, err) 275 | } 276 | 277 | b.Logger.Bodyf("Copying web.xml to %s/conf", layer.Path) 278 | file = filepath.Join(b.BuildpackPath, "resources", "web.xml") 279 | in, err = os.Open(file) 280 | if err != nil { 281 | return fmt.Errorf("unable to open %s\n%w", file, err) 282 | } 283 | defer in.Close() 284 | 285 | file = filepath.Join(layer.Path, "conf", "web.xml") 286 | if err := sherpa.CopyFile(in, file); err != nil { 287 | return fmt.Errorf("unable to copy %s to %s\n%w", in.Name(), file, err) 288 | } 289 | 290 | return nil 291 | } 292 | 293 | func (b Base) ContributeExternalConfiguration(layer libcnb.Layer) error { 294 | b.Logger.Header(color.BlueString("%s %s", b.ExternalConfigurationDependency.Name, b.ExternalConfigurationDependency.Version)) 295 | 296 | artifact, err := b.DependencyCache.Artifact(*b.ExternalConfigurationDependency) 297 | if err != nil { 298 | return fmt.Errorf("unable to get dependency %s\n%w", b.ExternalConfigurationDependency.ID, err) 299 | } 300 | defer artifact.Close() 301 | 302 | b.Logger.Bodyf("Expanding to %s", layer.Path) 303 | 304 | c := 0 305 | if s, ok := b.ConfigurationResolver.Resolve("BP_TOMCAT_EXT_CONF_STRIP"); ok { 306 | if c, err = strconv.Atoi(s); err != nil { 307 | return fmt.Errorf("unable to parse %s to integer\n%w", s, err) 308 | } 309 | } 310 | 311 | if err := crush.ExtractTarGz(artifact, layer.Path, c); err != nil { 312 | return fmt.Errorf("unable to expand external configuration\n%w", err) 313 | } 314 | 315 | return nil 316 | } 317 | 318 | func (b Base) ContributeLifecycle(layer libcnb.Layer) error { 319 | b.Logger.Header(color.BlueString("%s %s", b.LifecycleDependency.Name, b.LifecycleDependency.Version)) 320 | 321 | artifact, err := b.DependencyCache.Artifact(b.LifecycleDependency) 322 | if err != nil { 323 | return fmt.Errorf("unable to get dependency %s\n%w", b.LifecycleDependency.ID, err) 324 | } 325 | defer artifact.Close() 326 | 327 | b.Logger.Bodyf("Copying to %s/lib", layer.Path) 328 | 329 | file := filepath.Join(layer.Path, "lib", filepath.Base(b.LifecycleDependency.URI)) 330 | if err := sherpa.CopyFile(artifact, file); err != nil { 331 | return fmt.Errorf("unable to copy %s to %s\n%w", filepath.Base(b.LifecycleDependency.URI), file, err) 332 | } 333 | 334 | return nil 335 | } 336 | 337 | func (b Base) ContributeLogging(layer libcnb.Layer) error { 338 | b.Logger.Header(color.BlueString("%s %s", b.LoggingDependency.Name, b.LoggingDependency.Version)) 339 | 340 | artifact, err := b.DependencyCache.Artifact(b.LoggingDependency) 341 | if err != nil { 342 | return fmt.Errorf("unable to get dependency %s\n%w", b.LoggingDependency.ID, err) 343 | } 344 | defer artifact.Close() 345 | 346 | b.Logger.Bodyf("Copying to %s/bin", layer.Path) 347 | 348 | file := filepath.Join(layer.Path, "bin", filepath.Base(b.LoggingDependency.URI)) 349 | if err := sherpa.CopyFile(artifact, file); err != nil { 350 | return fmt.Errorf("unable to copy %s to %s\n%w", filepath.Base(b.LoggingDependency.URI), file, err) 351 | } 352 | 353 | b.Logger.Bodyf("Writing %s/bin/setenv.sh", layer.Path) 354 | 355 | var s string 356 | additionalJars, ok := os.LookupEnv("BPI_TOMCAT_ADDITIONAL_JARS") 357 | if ok { 358 | b.Logger.Bodyf("found BPI_TOMCAT_ADDITIONAL_JARS %q", additionalJars) 359 | s = fmt.Sprintf(`CLASSPATH="%s:%s"`, file, additionalJars) 360 | } else { 361 | s = fmt.Sprintf(`CLASSPATH="%s"`, file) 362 | } 363 | 364 | file = filepath.Join(layer.Path, "bin", "setenv.sh") 365 | if err = os.WriteFile(file, []byte(s), 0755); err != nil { 366 | return fmt.Errorf("unable to write file %s\n%w", file, err) 367 | } 368 | 369 | return nil 370 | } 371 | 372 | func (b Base) ContributeCatalinaProps(layer libcnb.Layer) error { 373 | b.Logger.Header(color.BlueString("Tomcat catalina.properties with altered common.loader")) 374 | 375 | homeProps := filepath.Join(layer.Path, "..", "tomcat", "conf", "catalina.properties") 376 | baseProps := filepath.Join(layer.Path, "conf", "catalina.properties") 377 | 378 | if _, err := os.Stat(baseProps); errors.Is(err, os.ErrNotExist) { 379 | in, err := os.Open(homeProps) 380 | if err != nil { 381 | b.Logger.Bodyf("Skipping copying of catalina.properties, unable to open %s", homeProps) 382 | return nil 383 | } 384 | defer in.Close() 385 | 386 | b.Logger.Bodyf("Copying catalina.properties to %s/conf", layer.Path) 387 | if err := sherpa.CopyFile(in, baseProps); err != nil { 388 | return fmt.Errorf("unable to copy %s to %s\n%w", in.Name(), baseProps, err) 389 | } 390 | } 391 | 392 | b.Logger.Body("Altering catalina.properties common.loader") 393 | if err := util.ReplaceInCatalinaProps(baseProps); err != nil { 394 | return fmt.Errorf("unable to replace in file %s\n%w", baseProps, err) 395 | } 396 | 397 | return nil 398 | } 399 | 400 | func (b Base) writeDependencySBOM(layer libcnb.Layer, syftArtifacts []sbom.SyftArtifact) error { 401 | 402 | sbomPath := layer.SBOMPath(libcnb.SyftJSON) 403 | dep := sbom.NewSyftDependency(layer.Path, syftArtifacts) 404 | b.Logger.Debugf("Writing Syft SBOM at %s: %+v", sbomPath, dep) 405 | if err := dep.WriteTo(sbomPath); err != nil { 406 | return fmt.Errorf("unable to write SBOM\n%w", err) 407 | } 408 | return nil 409 | } 410 | 411 | func (b Base) explodeWarFiles() error { 412 | warFiles, err := filepath.Glob(filepath.Join(b.ApplicationPath, "*.war")) 413 | if err != nil { 414 | return err 415 | } 416 | 417 | for _, warFilePath := range warFiles { 418 | b.Logger.Debugf("Extracting: %s\n", warFilePath) 419 | 420 | if _, err := os.Stat(warFilePath); err == nil { 421 | in, err := os.Open(warFilePath) 422 | if err != nil { 423 | return fmt.Errorf("An error occurred while extracting %s: %s\n", warFilePath, err) 424 | } 425 | defer in.Close() 426 | 427 | targetDir := strings.TrimSuffix(warFilePath, filepath.Ext(warFilePath)) 428 | if err := os.MkdirAll(targetDir, 0755); err != nil { 429 | return fmt.Errorf("An error occurred while extracting %s: %s\n", warFilePath, err) 430 | } 431 | 432 | if err := crush.Extract(in, targetDir, 0); err != nil { 433 | return fmt.Errorf("An error occurred while extracting %s: %s\n", warFilePath, err) 434 | } 435 | 436 | err = os.Remove(warFilePath) 437 | if err != nil { 438 | return fmt.Errorf("An error occurred while removing the .war file: %s\n", err) 439 | } 440 | } 441 | } 442 | return nil 443 | } 444 | 445 | func (Base) Name() string { 446 | return "catalina-base" 447 | } 448 | -------------------------------------------------------------------------------- /tomcat/build_test.go: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2018-2020 the original author or authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package tomcat_test 18 | 19 | import ( 20 | "os" 21 | "path/filepath" 22 | "testing" 23 | "time" 24 | 25 | "github.com/paketo-buildpacks/libpak/sbom/mocks" 26 | 27 | "github.com/buildpacks/libcnb" 28 | . "github.com/onsi/gomega" 29 | "github.com/paketo-buildpacks/libpak" 30 | "github.com/sclevine/spec" 31 | 32 | "github.com/paketo-buildpacks/apache-tomcat/v8/tomcat" 33 | ) 34 | 35 | func testBuild(t *testing.T, context spec.G, it spec.S) { 36 | var ( 37 | Expect = NewWithT(t).Expect 38 | sbomScanner mocks.SBOMScanner 39 | ctx libcnb.BuildContext 40 | ) 41 | 42 | it.Before(func() { 43 | var err error 44 | ctx.Application.Path, err = os.MkdirTemp("", "tomcat-application") 45 | Expect(err).NotTo(HaveOccurred()) 46 | ctx.Plan = libcnb.BuildpackPlan{Entries: []libcnb.BuildpackPlanEntry{ 47 | {Name: "jvm-application"}, 48 | {Name: "java-app-server"}, 49 | }} 50 | sbomScanner = mocks.SBOMScanner{} 51 | sbomScanner.On("ScanLaunch", ctx.Application.Path, libcnb.SyftJSON, libcnb.CycloneDXJSON).Return(nil) 52 | 53 | t.Setenv("BP_ARCH", "amd64") 54 | }) 55 | 56 | it.After(func() { 57 | Expect(os.RemoveAll(ctx.Application.Path)).To(Succeed()) 58 | }) 59 | 60 | it("does not contribute Tomcat if no WEB-INF", func() { 61 | result, err := tomcat.Build{SBOMScanner: &sbomScanner}.Build(ctx) 62 | Expect(err).NotTo(HaveOccurred()) 63 | 64 | Expect(result.Layers).To(BeEmpty()) 65 | Expect(result.Unmet).To(HaveLen(2)) 66 | Expect(result.Unmet[0].Name).To(Equal("jvm-application")) 67 | Expect(result.Unmet[1].Name).To(Equal("java-app-server")) 68 | }) 69 | 70 | it("does not contribute Tomcat if Main-Class", func() { 71 | Expect(os.MkdirAll(filepath.Join(ctx.Application.Path, "WEB-INF"), 0755)).To(Succeed()) 72 | Expect(os.MkdirAll(filepath.Join(ctx.Application.Path, "META-INF"), 0755)).To(Succeed()) 73 | Expect(os.WriteFile(filepath.Join(ctx.Application.Path, "META-INF", "MANIFEST.MF"), []byte(`Main-Class: test-main-class`), 0644)).To(Succeed()) 74 | 75 | result, err := tomcat.Build{SBOMScanner: &sbomScanner}.Build(ctx) 76 | Expect(err).NotTo(HaveOccurred()) 77 | 78 | Expect(result.Layers).To(BeEmpty()) 79 | Expect(result.Unmet).To(HaveLen(2)) 80 | Expect(result.Unmet[0].Name).To(Equal("jvm-application")) 81 | Expect(result.Unmet[1].Name).To(Equal("java-app-server")) 82 | }) 83 | 84 | it("does not contribute Tomcat if java-app-server missing from buildplan", func() { 85 | Expect(os.MkdirAll(filepath.Join(ctx.Application.Path, "WEB-INF"), 0755)).To(Succeed()) 86 | 87 | ctx.Buildpack.Metadata = map[string]interface{}{ 88 | "dependencies": []map[string]interface{}{ 89 | { 90 | "id": "tomcat", 91 | "version": "1.1.1", 92 | "stacks": []interface{}{"test-stack-id"}, 93 | "purl": "pkg:generic/tomcat@1.1.1", 94 | "cpes": "cpe:2.3:a:apache:tomcat:1.1.1:*:*:*:*:*:*:*", 95 | }, 96 | { 97 | "id": "tomcat-access-logging-support", 98 | "version": "1.1.1", 99 | "stacks": []interface{}{"test-stack-id"}, 100 | "purl": "pkg:generic/tomcat-access-logging-support@1.1.1", 101 | "cpes": "cpe:2.3:a:cloudfoundry:tomcat-access-logging-support:1.1.1:*:*:*:*:*:*:*", 102 | }, 103 | { 104 | "id": "tomcat-lifecycle-support", 105 | "version": "1.1.1", 106 | "stacks": []interface{}{"test-stack-id"}, 107 | "purl": "pkg:generic/tomcat-lifecycle-logging-support@1.1.1", 108 | "cpes": "cpe:2.3:a:cloudfoundry:tomcat-lifecycle-logging-support:1.1.1:*:*:*:*:*:*:*", 109 | }, 110 | { 111 | "id": "tomcat-logging-support", 112 | "version": "1.1.1", 113 | "uri": "https://example.com/releases/tomcat-logging-support-1.1.1.RELEASE.jar", 114 | "stacks": []interface{}{"test-stack-id"}, 115 | "purl": "pkg:generic/tomcat-logging-support@1.1.1", 116 | "cpes": "cpe:2.3:a:cloudfoundry:tomcat-logging-support:1.1.1:*:*:*:*:*:*:*", 117 | }, 118 | }, 119 | } 120 | ctx.StackID = "test-stack-id" 121 | ctx.Plan.Entries = ctx.Plan.Entries[0:1] // remove second plan entry, java-app-server 122 | 123 | result, err := tomcat.Build{SBOMScanner: &sbomScanner}.Build(ctx) 124 | Expect(err).NotTo(HaveOccurred()) 125 | 126 | Expect(result.Layers).To(BeEmpty()) 127 | }) 128 | 129 | it("contributes Tomcat", func() { 130 | Expect(os.MkdirAll(filepath.Join(ctx.Application.Path, "WEB-INF"), 0755)).To(Succeed()) 131 | 132 | ctx.Buildpack.Metadata = map[string]interface{}{ 133 | "dependencies": []map[string]interface{}{ 134 | { 135 | "id": "tomcat", 136 | "version": "1.1.1", 137 | "stacks": []interface{}{"test-stack-id"}, 138 | "purl": "pkg:generic/tomcat@1.1.1", 139 | "cpes": "cpe:2.3:a:apache:tomcat:1.1.1:*:*:*:*:*:*:*", 140 | }, 141 | { 142 | "id": "tomcat-access-logging-support", 143 | "version": "1.1.1", 144 | "stacks": []interface{}{"test-stack-id"}, 145 | "purl": "pkg:generic/tomcat-access-logging-support@1.1.1", 146 | "cpes": "cpe:2.3:a:cloudfoundry:tomcat-access-logging-support:1.1.1:*:*:*:*:*:*:*", 147 | }, 148 | { 149 | "id": "tomcat-lifecycle-support", 150 | "version": "1.1.1", 151 | "stacks": []interface{}{"test-stack-id"}, 152 | "purl": "pkg:generic/tomcat-lifecycle-logging-support@1.1.1", 153 | "cpes": "cpe:2.3:a:cloudfoundry:tomcat-lifecycle-logging-support:1.1.1:*:*:*:*:*:*:*", 154 | }, 155 | { 156 | "id": "tomcat-logging-support", 157 | "version": "1.1.1", 158 | "uri": "https://example.com/releases/tomcat-logging-support-1.1.1.RELEASE.jar", 159 | "stacks": []interface{}{"test-stack-id"}, 160 | "purl": "pkg:generic/tomcat-logging-support@1.1.1", 161 | "cpes": "cpe:2.3:a:cloudfoundry:tomcat-logging-support:1.1.1:*:*:*:*:*:*:*", 162 | }, 163 | }, 164 | } 165 | ctx.StackID = "test-stack-id" 166 | 167 | result, err := tomcat.Build{SBOMScanner: &sbomScanner}.Build(ctx) 168 | Expect(err).NotTo(HaveOccurred()) 169 | 170 | Expect(result.Processes).To(ContainElements( 171 | libcnb.Process{Type: "task", Command: "sh", Arguments: []string{"tomcat/bin/catalina.sh", "run"}, Direct: true}, 172 | libcnb.Process{Type: "tomcat", Command: "sh", Arguments: []string{"tomcat/bin/catalina.sh", "run"}, Direct: true}, 173 | libcnb.Process{Type: "web", Command: "sh", Arguments: []string{"tomcat/bin/catalina.sh", "run"}, Direct: true, Default: true}, 174 | )) 175 | 176 | Expect(result.Layers).To(HaveLen(3)) 177 | Expect(result.Layers[0].Name()).To(Equal("tomcat")) 178 | Expect(result.Layers[1].Name()).To(Equal("helper")) 179 | Expect(result.Layers[1].(libpak.HelperLayerContributor).Names).To(Equal([]string{"access-logging-support"})) 180 | Expect(result.Layers[2].Name()).To(Equal("catalina-base")) 181 | 182 | Expect(result.BOM.Entries).To(HaveLen(5)) 183 | Expect(result.BOM.Entries[0].Name).To(Equal("tomcat")) 184 | Expect(result.BOM.Entries[0].Build).To(BeTrue()) 185 | Expect(result.BOM.Entries[0].Launch).To(BeTrue()) 186 | Expect(result.BOM.Entries[1].Name).To(Equal("helper")) 187 | Expect(result.BOM.Entries[1].Build).To(BeFalse()) 188 | Expect(result.BOM.Entries[1].Launch).To(BeTrue()) 189 | Expect(result.BOM.Entries[2].Name).To(Equal("tomcat-access-logging-support")) 190 | Expect(result.BOM.Entries[2].Build).To(BeFalse()) 191 | Expect(result.BOM.Entries[2].Launch).To(BeTrue()) 192 | Expect(result.BOM.Entries[3].Name).To(Equal("tomcat-lifecycle-support")) 193 | Expect(result.BOM.Entries[3].Build).To(BeFalse()) 194 | Expect(result.BOM.Entries[3].Launch).To(BeTrue()) 195 | Expect(result.BOM.Entries[4].Name).To(Equal("tomcat-logging-support")) 196 | Expect(result.BOM.Entries[4].Build).To(BeFalse()) 197 | Expect(result.BOM.Entries[4].Launch).To(BeTrue()) 198 | 199 | sbomScanner.AssertCalled(t, "ScanLaunch", ctx.Application.Path, libcnb.SyftJSON, libcnb.CycloneDXJSON) 200 | }) 201 | 202 | it("contributes Tomcat on Tiny", func() { 203 | ctx.StackID = libpak.BionicTinyStackID 204 | 205 | Expect(os.MkdirAll(filepath.Join(ctx.Application.Path, "WEB-INF"), 0755)).To(Succeed()) 206 | 207 | ctx.Buildpack.Metadata = map[string]interface{}{ 208 | "dependencies": []map[string]interface{}{ 209 | { 210 | "id": "tomcat", 211 | "version": "1.1.2", 212 | "stacks": []interface{}{libpak.BionicTinyStackID}, 213 | "purl": "pkg:generic/tomcat@1.1.1", 214 | "cpes": []interface{}{"cpe:2.3:a:apache:tomcat:1.1.1:*:*:*:*:*:*:*"}, 215 | }, 216 | { 217 | "id": "tomcat-access-logging-support", 218 | "version": "1.1.1", 219 | "stacks": []interface{}{libpak.BionicTinyStackID}, 220 | "purl": "pkg:generic/tomcat-access-logging-support@3.3.0", 221 | "cpes": []interface{}{"cpe:2.3:a:cloudfoundry:tomcat-access-logging-support:3.3.0:*:*:*:*:*:*:*"}, 222 | }, 223 | { 224 | "id": "tomcat-lifecycle-support", 225 | "version": "1.1.1", 226 | "stacks": []interface{}{libpak.BionicTinyStackID}, 227 | "purl": "pkg:generic/tomcat-lifecycle-logging-support@1.1.1", 228 | "cpes": []interface{}{"cpe:2.3:a:cloudfoundry:tomcat-lifecycle-logging-support:1.1.1:*:*:*:*:*:*:*"}, 229 | }, 230 | { 231 | "id": "tomcat-logging-support", 232 | "version": "1.1.1", 233 | "uri": "https://example.com/releases/tomcat-logging-support-1.1.1.RELEASE.jar", 234 | "stacks": []interface{}{libpak.BionicTinyStackID}, 235 | "purl": "pkg:generic/tomcat-logging-support@1.1.1", 236 | "cpes": []interface{}{"cpe:2.3:a:cloudfoundry:tomcat-logging-support:1.1.1:*:*:*:*:*:*:*"}, 237 | }, 238 | }, 239 | } 240 | 241 | result, err := tomcat.Build{SBOMScanner: &sbomScanner}.Build(ctx) 242 | Expect(err).NotTo(HaveOccurred()) 243 | 244 | for _, procType := range []string{"task", "tomcat", "web"} { 245 | expectedProcess := libcnb.Process{ 246 | Type: procType, 247 | Command: "java", 248 | Arguments: []string{ 249 | "-Djava.util.logging.config.file=catalina-base/conf/logging.properties", 250 | "-Djava.util.logging.manager=org.apache.juli.ClassLoaderLogManager", 251 | "-Djdk.tls.ephemeralDHKeySize=2048", 252 | "-classpath", 253 | "catalina-base/bin/tomcat-logging-support-1.1.1.RELEASE.jar:tomcat/bin/bootstrap.jar:tomcat/bin/tomcat-juli.jar", 254 | "-Dcatalina.home=tomcat", 255 | "-Dcatalina.base=catalina-base", 256 | "org.apache.catalina.startup.Bootstrap", 257 | "start", 258 | }, 259 | Direct: true, 260 | } 261 | if procType == "web" { 262 | expectedProcess.Default = true 263 | } 264 | Expect(result.Processes).To(ContainElement(expectedProcess)) 265 | } 266 | 267 | Expect(result.Layers).To(HaveLen(3)) 268 | Expect(result.Layers[0].Name()).To(Equal("tomcat")) 269 | Expect(result.Layers[1].Name()).To(Equal("helper")) 270 | Expect(result.Layers[1].(libpak.HelperLayerContributor).Names).To(Equal([]string{"access-logging-support"})) 271 | Expect(result.Layers[2].Name()).To(Equal("catalina-base")) 272 | 273 | Expect(result.BOM.Entries).To(HaveLen(5)) 274 | Expect(result.BOM.Entries[0].Name).To(Equal("tomcat")) 275 | Expect(result.BOM.Entries[0].Build).To(BeTrue()) 276 | Expect(result.BOM.Entries[0].Launch).To(BeTrue()) 277 | Expect(result.BOM.Entries[1].Name).To(Equal("helper")) 278 | Expect(result.BOM.Entries[1].Build).To(BeFalse()) 279 | Expect(result.BOM.Entries[1].Launch).To(BeTrue()) 280 | Expect(result.BOM.Entries[2].Name).To(Equal("tomcat-access-logging-support")) 281 | Expect(result.BOM.Entries[2].Build).To(BeFalse()) 282 | Expect(result.BOM.Entries[2].Launch).To(BeTrue()) 283 | Expect(result.BOM.Entries[3].Name).To(Equal("tomcat-lifecycle-support")) 284 | Expect(result.BOM.Entries[3].Build).To(BeFalse()) 285 | Expect(result.BOM.Entries[3].Launch).To(BeTrue()) 286 | Expect(result.BOM.Entries[4].Name).To(Equal("tomcat-logging-support")) 287 | Expect(result.BOM.Entries[4].Build).To(BeFalse()) 288 | Expect(result.BOM.Entries[4].Launch).To(BeTrue()) 289 | 290 | sbomScanner.AssertCalled(t, "ScanLaunch", ctx.Application.Path, libcnb.SyftJSON, libcnb.CycloneDXJSON) 291 | }) 292 | 293 | context("$BP_TOMCAT_VERSION", func() { 294 | it.Before(func() { 295 | Expect(os.Setenv("BP_TOMCAT_VERSION", "1.1.1")).To(Succeed()) 296 | }) 297 | 298 | it.After(func() { 299 | Expect(os.Unsetenv("BP_TOMCAT_VERSION")).To(Succeed()) 300 | }) 301 | 302 | it("selects version based on $BP_TOMCAT_VERSION", func() { 303 | Expect(os.MkdirAll(filepath.Join(ctx.Application.Path, "WEB-INF"), 0755)).To(Succeed()) 304 | 305 | ctx.Buildpack.Metadata = map[string]interface{}{ 306 | "dependencies": []map[string]interface{}{ 307 | { 308 | "id": "tomcat", 309 | "version": "1.1.1", 310 | "stacks": []interface{}{"test-stack-id"}, 311 | }, 312 | { 313 | "id": "tomcat", 314 | "version": "2.2.2", 315 | "stacks": []interface{}{"test-stack-id"}, 316 | }, 317 | { 318 | "id": "tomcat-access-logging-support", 319 | "version": "1.1.1", 320 | "stacks": []interface{}{"test-stack-id"}, 321 | }, 322 | { 323 | "id": "tomcat-lifecycle-support", 324 | "version": "1.1.1", 325 | "stacks": []interface{}{"test-stack-id"}, 326 | }, 327 | { 328 | "id": "tomcat-logging-support", 329 | "version": "1.1.1", 330 | "stacks": []interface{}{"test-stack-id"}, 331 | }, 332 | }, 333 | } 334 | ctx.StackID = "test-stack-id" 335 | 336 | result, err := tomcat.Build{SBOMScanner: &sbomScanner}.Build(ctx) 337 | Expect(err).NotTo(HaveOccurred()) 338 | 339 | Expect(result.Layers[0].(tomcat.Home).LayerContributor.Dependency.Version).To(Equal("1.1.1")) 340 | sbomScanner.AssertCalled(t, "ScanLaunch", ctx.Application.Path, libcnb.SyftJSON, libcnb.CycloneDXJSON) 341 | }) 342 | }) 343 | 344 | context("$BP_TOMCAT_EXT_CONF_URI", func() { 345 | it.Before(func() { 346 | Expect(os.MkdirAll(filepath.Join(ctx.Application.Path, "WEB-INF"), 0755)).To(Succeed()) 347 | 348 | ctx.Buildpack.Metadata = map[string]interface{}{ 349 | "dependencies": []map[string]interface{}{ 350 | { 351 | "id": "tomcat", 352 | "version": "1.1.1", 353 | "stacks": []interface{}{"test-stack-id"}, 354 | }, 355 | { 356 | "id": "tomcat-access-logging-support", 357 | "version": "1.1.1", 358 | "stacks": []interface{}{"test-stack-id"}, 359 | }, 360 | { 361 | "id": "tomcat-lifecycle-support", 362 | "version": "1.1.1", 363 | "stacks": []interface{}{"test-stack-id"}, 364 | }, 365 | { 366 | "id": "tomcat-logging-support", 367 | "version": "1.1.1", 368 | "stacks": []interface{}{"test-stack-id"}, 369 | }, 370 | }, 371 | } 372 | ctx.StackID = "test-stack-id" 373 | 374 | t.Setenv("BP_TOMCAT_EXT_CONF_SHA256", "test-sha256") 375 | t.Setenv("BP_TOMCAT_EXT_CONF_URI", "test-uri") 376 | t.Setenv("BP_TOMCAT_EXT_CONF_VERSION", "test-version") 377 | }) 378 | 379 | it("contributes external configuration when $BP_TOMCAT_EXT_CONF_URI, $BP_TOMCAT_EXT_CONF_VERSION and $BP_TOMCAT_EXT_CONF_SHA256 are set", func() { 380 | result, err := tomcat.Build{SBOMScanner: &sbomScanner}.Build(ctx) 381 | Expect(err).NotTo(HaveOccurred()) 382 | 383 | Expect(result.Layers).To(HaveLen(3)) 384 | Expect(result.Layers[2].(tomcat.Base).ExternalConfigurationDependency).To(Equal(&libpak.BuildpackDependency{ 385 | ID: "tomcat-external-configuration", 386 | Name: "Tomcat External Configuration", 387 | Version: "test-version", 388 | URI: "test-uri", 389 | SHA256: "test-sha256", 390 | Stacks: []string{ctx.StackID}, 391 | })) 392 | sbomScanner.AssertCalled(t, "ScanLaunch", ctx.Application.Path, libcnb.SyftJSON, libcnb.CycloneDXJSON) 393 | }) 394 | 395 | it("uses time as version if neither $BP_TOMCAT_EXT_CONF_VERSION nor $BP_TOMCAT_EXT_CONF_SHA256 is provided", func() { 396 | Expect(os.Unsetenv("BP_TOMCAT_EXT_CONF_SHA256")).To(Succeed()) 397 | Expect(os.Unsetenv("BP_TOMCAT_EXT_CONF_VERSION")).To(Succeed()) 398 | 399 | result, err := tomcat.Build{SBOMScanner: &sbomScanner}.Build(ctx) 400 | Expect(err).NotTo(HaveOccurred()) 401 | 402 | Expect(result.Layers).To(HaveLen(3)) 403 | version := result.Layers[2].(tomcat.Base).ExternalConfigurationDependency.Version 404 | Expect(time.Parse(time.RFC3339, version)).NotTo(BeNil()) 405 | }) 406 | 407 | it("contributes external configuration when $BP_TOMCAT_EXT_CONF_URI and $BP_TOMCAT_EXT_CONF_VERSION are set", func() { 408 | Expect(os.Unsetenv("BP_TOMCAT_EXT_CONF_SHA256")).To(Succeed()) 409 | 410 | result, err := tomcat.Build{SBOMScanner: &sbomScanner}.Build(ctx) 411 | Expect(err).NotTo(HaveOccurred()) 412 | 413 | Expect(result.Layers).To(HaveLen(3)) 414 | Expect(result.Layers[2].(tomcat.Base).ExternalConfigurationDependency).To(Equal(&libpak.BuildpackDependency{ 415 | ID: "tomcat-external-configuration", 416 | Name: "Tomcat External Configuration", 417 | Version: "test-version", 418 | URI: "test-uri", 419 | SHA256: "", 420 | Stacks: []string{ctx.StackID}, 421 | })) 422 | sbomScanner.AssertCalled(t, "ScanLaunch", ctx.Application.Path, libcnb.SyftJSON, libcnb.CycloneDXJSON) 423 | }) 424 | 425 | it("contributes external configuration when $BP_TOMCAT_EXT_CONF_URI and $BP_TOMCAT_EXT_CONF_SHA256 are set", func() { 426 | Expect(os.Unsetenv("BP_TOMCAT_EXT_CONF_VERSION")).To(Succeed()) 427 | 428 | result, err := tomcat.Build{SBOMScanner: &sbomScanner}.Build(ctx) 429 | Expect(err).NotTo(HaveOccurred()) 430 | 431 | Expect(result.Layers).To(HaveLen(3)) 432 | Expect(result.Layers[2].(tomcat.Base).ExternalConfigurationDependency).To(Equal(&libpak.BuildpackDependency{ 433 | ID: "tomcat-external-configuration", 434 | Name: "Tomcat External Configuration", 435 | Version: "", 436 | URI: "test-uri", 437 | SHA256: "test-sha256", 438 | Stacks: []string{ctx.StackID}, 439 | })) 440 | sbomScanner.AssertCalled(t, "ScanLaunch", ctx.Application.Path, libcnb.SyftJSON, libcnb.CycloneDXJSON) 441 | }) 442 | 443 | }) 444 | 445 | it("returns default context path", func() { 446 | Expect(tomcat.Build{SBOMScanner: &sbomScanner}.ContextPath(libpak.ConfigurationResolver{})).To(Equal("ROOT")) 447 | }) 448 | 449 | context("$BP_TOMCAT_CONTEXT_PATH", func() { 450 | it.Before(func() { 451 | Expect(os.Setenv("BP_TOMCAT_CONTEXT_PATH", "/alpha/bravo/")).To(Succeed()) 452 | }) 453 | 454 | it.After(func() { 455 | Expect(os.Unsetenv("BP_TOMCAT_CONTEXT_PATH")).To(Succeed()) 456 | }) 457 | 458 | it("returns transformed context path", func() { 459 | Expect(tomcat.Build{SBOMScanner: &sbomScanner}.ContextPath(libpak.ConfigurationResolver{})).To(Equal("alpha#bravo")) 460 | }) 461 | }) 462 | 463 | it("contributes Tomcat with war files", func() { 464 | Expect(os.WriteFile(filepath.Join(ctx.Application.Path, "test.war"), []byte(`test`), 0644)).To(Succeed()) 465 | 466 | ctx.Buildpack.Metadata = map[string]interface{}{ 467 | "dependencies": []map[string]interface{}{ 468 | { 469 | "id": "tomcat", 470 | "version": "1.1.1", 471 | "stacks": []interface{}{"test-stack-id"}, 472 | "purl": "pkg:generic/tomcat@1.1.1", 473 | "cpes": "cpe:2.3:a:apache:tomcat:1.1.1:*:*:*:*:*:*:*", 474 | }, 475 | { 476 | "id": "tomcat-access-logging-support", 477 | "version": "1.1.1", 478 | "stacks": []interface{}{"test-stack-id"}, 479 | "purl": "pkg:generic/tomcat-access-logging-support@1.1.1", 480 | "cpes": "cpe:2.3:a:cloudfoundry:tomcat-access-logging-support:1.1.1:*:*:*:*:*:*:*", 481 | }, 482 | { 483 | "id": "tomcat-lifecycle-support", 484 | "version": "1.1.1", 485 | "stacks": []interface{}{"test-stack-id"}, 486 | "purl": "pkg:generic/tomcat-lifecycle-logging-support@1.1.1", 487 | "cpes": "cpe:2.3:a:cloudfoundry:tomcat-lifecycle-logging-support:1.1.1:*:*:*:*:*:*:*", 488 | }, 489 | { 490 | "id": "tomcat-logging-support", 491 | "version": "1.1.1", 492 | "uri": "https://example.com/releases/tomcat-logging-support-1.1.1.RELEASE.jar", 493 | "stacks": []interface{}{"test-stack-id"}, 494 | "purl": "pkg:generic/tomcat-logging-support@1.1.1", 495 | "cpes": "cpe:2.3:a:cloudfoundry:tomcat-logging-support:1.1.1:*:*:*:*:*:*:*", 496 | }, 497 | }, 498 | } 499 | ctx.StackID = "test-stack-id" 500 | 501 | result, err := tomcat.Build{SBOMScanner: &sbomScanner}.Build(ctx) 502 | Expect(err).NotTo(HaveOccurred()) 503 | 504 | Expect(result.Processes).To(ContainElements( 505 | libcnb.Process{Type: "task", Command: "sh", Arguments: []string{"tomcat/bin/catalina.sh", "run"}, Direct: true}, 506 | libcnb.Process{Type: "tomcat", Command: "sh", Arguments: []string{"tomcat/bin/catalina.sh", "run"}, Direct: true}, 507 | libcnb.Process{Type: "web", Command: "sh", Arguments: []string{"tomcat/bin/catalina.sh", "run"}, Direct: true, Default: true}, 508 | )) 509 | 510 | Expect(result.Layers).To(HaveLen(3)) 511 | Expect(result.Layers[0].Name()).To(Equal("tomcat")) 512 | Expect(result.Layers[1].Name()).To(Equal("helper")) 513 | Expect(result.Layers[1].(libpak.HelperLayerContributor).Names).To(Equal([]string{"access-logging-support"})) 514 | Expect(result.Layers[2].Name()).To(Equal("catalina-base")) 515 | 516 | Expect(result.BOM.Entries).To(HaveLen(5)) 517 | Expect(result.BOM.Entries[0].Name).To(Equal("tomcat")) 518 | Expect(result.BOM.Entries[0].Build).To(BeTrue()) 519 | Expect(result.BOM.Entries[0].Launch).To(BeTrue()) 520 | Expect(result.BOM.Entries[1].Name).To(Equal("helper")) 521 | Expect(result.BOM.Entries[1].Build).To(BeFalse()) 522 | Expect(result.BOM.Entries[1].Launch).To(BeTrue()) 523 | Expect(result.BOM.Entries[2].Name).To(Equal("tomcat-access-logging-support")) 524 | Expect(result.BOM.Entries[2].Build).To(BeFalse()) 525 | Expect(result.BOM.Entries[2].Launch).To(BeTrue()) 526 | Expect(result.BOM.Entries[3].Name).To(Equal("tomcat-lifecycle-support")) 527 | Expect(result.BOM.Entries[3].Build).To(BeFalse()) 528 | Expect(result.BOM.Entries[3].Launch).To(BeTrue()) 529 | Expect(result.BOM.Entries[4].Name).To(Equal("tomcat-logging-support")) 530 | Expect(result.BOM.Entries[4].Build).To(BeFalse()) 531 | Expect(result.BOM.Entries[4].Launch).To(BeTrue()) 532 | 533 | sbomScanner.AssertCalled(t, "ScanLaunch", ctx.Application.Path, libcnb.SyftJSON, libcnb.CycloneDXJSON) 534 | }) 535 | 536 | } 537 | -------------------------------------------------------------------------------- /tomcat/base_test.go: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2018-2020 the original author or authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package tomcat_test 18 | 19 | import ( 20 | "fmt" 21 | "io" 22 | "os" 23 | "path/filepath" 24 | "strings" 25 | "testing" 26 | 27 | "github.com/buildpacks/libcnb" 28 | . "github.com/onsi/gomega" 29 | "github.com/paketo-buildpacks/libpak" 30 | "github.com/sclevine/spec" 31 | 32 | "github.com/paketo-buildpacks/apache-tomcat/v8/tomcat" 33 | ) 34 | 35 | func testBase(t *testing.T, context spec.G, it spec.S) { 36 | var ( 37 | Expect = NewWithT(t).Expect 38 | 39 | ctx libcnb.BuildContext 40 | ) 41 | 42 | it.Before(func() { 43 | var err error 44 | 45 | ctx.Application.Path, err = os.MkdirTemp("", "base-application") 46 | Expect(err).NotTo(HaveOccurred()) 47 | 48 | ctx.Buildpack.Path, err = os.MkdirTemp("", "base-buildpack") 49 | Expect(err).NotTo(HaveOccurred()) 50 | 51 | ctx.Layers.Path, err = os.MkdirTemp("", "base-layers") 52 | Expect(err).NotTo(HaveOccurred()) 53 | 54 | Expect(os.MkdirAll(filepath.Join(ctx.Layers.Path, "tomcat", "conf"), 0755)).To(Succeed()) 55 | commonLoader := "common.loader=\"${catalina.base}/lib\",\"${catalina.base}/lib/*.jar\",\"${catalina.home}/lib\",\"${catalina.home}/lib/*.jar\"" 56 | Expect(os.WriteFile(filepath.Join(ctx.Layers.Path, "tomcat", "conf", "catalina.properties"), []byte(commonLoader), 0644)). 57 | To(Succeed()) 58 | 59 | Expect(os.MkdirAll(filepath.Join(ctx.Buildpack.Path, "resources"), 0755)).To(Succeed()) 60 | Expect(os.WriteFile(filepath.Join(ctx.Buildpack.Path, "resources", "context.xml"), []byte{}, 0644)). 61 | To(Succeed()) 62 | Expect(os.WriteFile(filepath.Join(ctx.Buildpack.Path, "resources", "logging.properties"), []byte{}, 0644)). 63 | To(Succeed()) 64 | Expect(os.WriteFile(filepath.Join(ctx.Buildpack.Path, "resources", "server.xml"), []byte{}, 0644)). 65 | To(Succeed()) 66 | Expect(os.WriteFile(filepath.Join(ctx.Buildpack.Path, "resources", "web.xml"), []byte{}, 0644)). 67 | To(Succeed()) 68 | }) 69 | 70 | it.After(func() { 71 | Expect(os.RemoveAll(ctx.Application.Path)).To(Succeed()) 72 | Expect(os.RemoveAll(ctx.Buildpack.Path)).To(Succeed()) 73 | Expect(os.RemoveAll(ctx.Layers.Path)).To(Succeed()) 74 | }) 75 | 76 | it("contributes catalina base", func() { 77 | accessLoggingDep := libpak.BuildpackDependency{ 78 | ID: "tomcat-access-logging-support", 79 | URI: "https://localhost/stub-tomcat-access-logging-support.jar", 80 | SHA256: "d723bfe2ba67dfa92b24e3b6c7b2d0e6a963de7313350e306d470e44e330a5d2", 81 | PURL: "pkg:generic/tomcat-access-logging-support@3.3.0", 82 | CPEs: []string{"cpe:2.3:a:cloudfoundry:tomcat-access-logging-support:3.3.0:*:*:*:*:*:*:*"}, 83 | } 84 | lifecycleDep := libpak.BuildpackDependency{ 85 | ID: "tomcat-lifecycle-support", 86 | URI: "https://localhost/stub-tomcat-lifecycle-support.jar", 87 | SHA256: "723126712c0b22a7fe409664adf1fbb78cf3040e313a82c06696f5058e190534", 88 | PURL: "pkg:generic/tomcat-lifecycle-support@3.3.0", 89 | CPEs: []string{"cpe:2.3:a:cloudfoundry:tomcat-lifecycle-support:3.3.0:*:*:*:*:*:*:*"}, 90 | } 91 | loggingDep := libpak.BuildpackDependency{ 92 | ID: "tomcat-logging-support", 93 | URI: "https://localhost/stub-tomcat-logging-support.jar", 94 | SHA256: "e0a7e163cc9f1ffd41c8de3942c7c6b505090b7484c2ba9be846334e31c44a2c", 95 | PURL: "pkg:generic/tomcat-logging-support@3.3.0", 96 | CPEs: []string{"cpe:2.3:a:cloudfoundry:tomcat-logging-support:3.3.0:*:*:*:*:*:*:*"}, 97 | } 98 | 99 | dc := libpak.DependencyCache{CachePath: "testdata"} 100 | 101 | contributor, entries := tomcat.NewBase( 102 | ctx.Application.Path, 103 | ctx.Buildpack.Path, 104 | libpak.ConfigurationResolver{}, 105 | "test-context-path", 106 | accessLoggingDep, 107 | nil, 108 | lifecycleDep, 109 | loggingDep, 110 | dc, 111 | false, 112 | ) 113 | 114 | Expect(entries).To(HaveLen(3)) 115 | Expect(entries[0].Name).To(Equal("tomcat-access-logging-support")) 116 | Expect(entries[0].Build).To(BeFalse()) 117 | Expect(entries[0].Launch).To(BeTrue()) 118 | Expect(entries[1].Name).To(Equal("tomcat-lifecycle-support")) 119 | Expect(entries[1].Build).To(BeFalse()) 120 | Expect(entries[1].Launch).To(BeTrue()) 121 | Expect(entries[2].Name).To(Equal("tomcat-logging-support")) 122 | Expect(entries[2].Build).To(BeFalse()) 123 | Expect(entries[2].Launch).To(BeTrue()) 124 | 125 | layer, err := ctx.Layers.Layer("test-layer") 126 | Expect(err).NotTo(HaveOccurred()) 127 | 128 | layer, err = contributor.Contribute(layer) 129 | Expect(err).NotTo(HaveOccurred()) 130 | 131 | Expect(layer.Launch).To(BeTrue()) 132 | Expect(filepath.Join(layer.Path, "conf", "context.xml")).To(BeARegularFile()) 133 | Expect(filepath.Join(layer.Path, "conf", "logging.properties")).To(BeARegularFile()) 134 | Expect(filepath.Join(layer.Path, "conf", "server.xml")).To(BeARegularFile()) 135 | Expect(filepath.Join(layer.Path, "conf", "web.xml")).To(BeARegularFile()) 136 | Expect(filepath.Join(layer.Path, "conf", "catalina.properties")).To(BeARegularFile()) 137 | Expect(os.ReadFile(filepath.Join(layer.Path, "conf", "catalina.properties"))).To(ContainSubstring("common.loader=${BPI_TOMCAT_ADDITIONAL_COMMON_JARS}")) 138 | Expect(filepath.Join(layer.Path, "lib", "stub-tomcat-access-logging-support.jar")).To(BeARegularFile()) 139 | Expect(filepath.Join(layer.Path, "lib", "stub-tomcat-lifecycle-support.jar")).To(BeARegularFile()) 140 | Expect(filepath.Join(layer.Path, "bin", "stub-tomcat-logging-support.jar")).To(BeARegularFile()) 141 | Expect(os.ReadFile(filepath.Join(layer.Path, "bin", "setenv.sh"))).To(Equal( 142 | []byte(fmt.Sprintf(`CLASSPATH="%s"`, filepath.Join(layer.Path, "bin", "stub-tomcat-logging-support.jar"))))) 143 | Expect(layer.LaunchEnvironment["CATALINA_BASE.default"]).To(Equal(layer.Path)) 144 | Expect(filepath.Join(layer.Path, "temp")).To(BeADirectory()) 145 | 146 | file := filepath.Join(layer.Path, "webapps", "test-context-path") 147 | fi, err := os.Lstat(file) 148 | Expect(err).NotTo(HaveOccurred()) 149 | Expect(fi.Mode() & os.ModeSymlink).To(Equal(os.ModeSymlink)) 150 | Expect(os.Readlink(file)).To(Equal(ctx.Application.Path)) 151 | 152 | Expect(layer.LaunchEnvironment["CATALINA_BASE.default"]).To(Equal(layer.Path)) 153 | Expect(layer.LaunchEnvironment["CATALINA_OPTS.default"]).To(Equal("-DBPI_TOMCAT_ADDITIONAL_COMMON_JARS=${BPI_TOMCAT_ADDITIONAL_COMMON_JARS} -Dorg.apache.tomcat.util.digester.PROPERTY_SOURCE=org.apache.tomcat.util.digester.EnvironmentPropertySource")) 154 | }) 155 | 156 | it("contributes custom configuration", func() { 157 | externalConfigurationDep := libpak.BuildpackDependency{ 158 | ID: "tomcat-external-configuration", 159 | URI: "https://localhost/stub-external-configuration.tar.gz", 160 | SHA256: "22e708cfd301430cbcf8d1c2289503d8288d50df519ff4db7cca0ff9fe83c324", 161 | PURL: "pkg:generic/tomcat@1.1.1", 162 | CPEs: []string{"cpe:2.3:a:apache:tomcat:1.1.1:*:*:*:*:*:*:*"}, 163 | } 164 | accessLoggingDep := libpak.BuildpackDependency{ 165 | ID: "tomcat-access-logging-support", 166 | URI: "https://localhost/stub-tomcat-access-logging-support.jar", 167 | SHA256: "d723bfe2ba67dfa92b24e3b6c7b2d0e6a963de7313350e306d470e44e330a5d2", 168 | PURL: "pkg:generic/tomcat-access-logging-support@3.3.0", 169 | CPEs: []string{"cpe:2.3:a:cloudfoundry:tomcat-access-logging-support:3.3.0:*:*:*:*:*:*:*"}, 170 | } 171 | lifecycleDep := libpak.BuildpackDependency{ 172 | ID: "tomcat-lifecycle-support", 173 | URI: "https://localhost/stub-tomcat-lifecycle-support.jar", 174 | SHA256: "723126712c0b22a7fe409664adf1fbb78cf3040e313a82c06696f5058e190534", 175 | PURL: "pkg:generic/tomcat-lifecycle-support@3.3.0", 176 | CPEs: []string{"cpe:2.3:a:cloudfoundry:tomcat-lifecycle-support:3.3.0:*:*:*:*:*:*:*"}, 177 | } 178 | loggingDep := libpak.BuildpackDependency{ 179 | ID: "tomcat-logging-support", 180 | URI: "https://localhost/stub-tomcat-logging-support.jar", 181 | SHA256: "e0a7e163cc9f1ffd41c8de3942c7c6b505090b7484c2ba9be846334e31c44a2c", 182 | PURL: "pkg:generic/tomcat-logging-support@3.3.0", 183 | CPEs: []string{"cpe:2.3:a:cloudfoundry:tomcat-logging-support:3.3.0:*:*:*:*:*:*:*"}, 184 | } 185 | 186 | dc := libpak.DependencyCache{CachePath: "testdata"} 187 | 188 | contrib, entries := tomcat.NewBase( 189 | ctx.Application.Path, 190 | ctx.Buildpack.Path, 191 | libpak.ConfigurationResolver{}, 192 | "test-context-path", 193 | accessLoggingDep, 194 | &externalConfigurationDep, 195 | lifecycleDep, 196 | loggingDep, 197 | dc, 198 | false, 199 | ) 200 | layer, err := ctx.Layers.Layer("test-layer") 201 | Expect(err).NotTo(HaveOccurred()) 202 | 203 | Expect(entries).To(HaveLen(4)) 204 | Expect(entries[0].Name).To(Equal("tomcat-access-logging-support")) 205 | Expect(entries[0].Build).To(BeFalse()) 206 | Expect(entries[0].Launch).To(BeTrue()) 207 | Expect(entries[1].Name).To(Equal("tomcat-lifecycle-support")) 208 | Expect(entries[1].Build).To(BeFalse()) 209 | Expect(entries[1].Launch).To(BeTrue()) 210 | Expect(entries[2].Name).To(Equal("tomcat-logging-support")) 211 | Expect(entries[2].Build).To(BeFalse()) 212 | Expect(entries[2].Launch).To(BeTrue()) 213 | Expect(entries[3].Name).To(Equal("tomcat-external-configuration")) 214 | Expect(entries[3].Build).To(BeFalse()) 215 | Expect(entries[3].Launch).To(BeTrue()) 216 | 217 | layer, err = contrib.Contribute(layer) 218 | Expect(err).NotTo(HaveOccurred()) 219 | 220 | Expect(filepath.Join(layer.Path, "fixture-marker")).To(BeARegularFile()) 221 | }) 222 | 223 | context("$BP_TOMCAT_EXT_CONF_STRIP", func() { 224 | it.Before(func() { 225 | Expect(os.Setenv("BP_TOMCAT_EXT_CONF_STRIP", "1")).To(Succeed()) 226 | }) 227 | 228 | it.After(func() { 229 | Expect(os.Unsetenv("BP_TOMCAT_EXT_CONF_STRIP")).To(Succeed()) 230 | }) 231 | 232 | it("contributes custom configuration with directory", func() { 233 | externalConfigurationDep := libpak.BuildpackDependency{ 234 | ID: "tomcat-external-configuration", 235 | URI: "https://localhost/stub-external-configuration-with-directory.tar.gz", 236 | SHA256: "060818cbcdc2008563f0f9e2428ecf4a199a5821c5b8b1dcd11a67666c1e2cd6", 237 | PURL: "pkg:generic/tomcat@1.1.1", 238 | CPEs: []string{"cpe:2.3:a:apache:tomcat:1.1.1:*:*:*:*:*:*:*"}, 239 | } 240 | accessLoggingDep := libpak.BuildpackDependency{ 241 | ID: "tomcat-access-logging-support", 242 | URI: "https://localhost/stub-tomcat-access-logging-support.jar", 243 | SHA256: "d723bfe2ba67dfa92b24e3b6c7b2d0e6a963de7313350e306d470e44e330a5d2", 244 | PURL: "pkg:generic/tomcat-access-logging-support@3.3.0", 245 | CPEs: []string{"cpe:2.3:a:cloudfoundry:tomcat-access-logging-support:3.3.0:*:*:*:*:*:*:*"}, 246 | } 247 | lifecycleDep := libpak.BuildpackDependency{ 248 | ID: "tomcat-lifecycle-support", 249 | URI: "https://localhost/stub-tomcat-lifecycle-support.jar", 250 | SHA256: "723126712c0b22a7fe409664adf1fbb78cf3040e313a82c06696f5058e190534", 251 | PURL: "pkg:generic/tomcat-lifecycle-support@3.3.0", 252 | CPEs: []string{"cpe:2.3:a:cloudfoundry:tomcat-lifecycle-support:3.3.0:*:*:*:*:*:*:*"}, 253 | } 254 | loggingDep := libpak.BuildpackDependency{ 255 | ID: "tomcat-logging-support", 256 | URI: "https://localhost/stub-tomcat-logging-support.jar", 257 | SHA256: "e0a7e163cc9f1ffd41c8de3942c7c6b505090b7484c2ba9be846334e31c44a2c", 258 | PURL: "pkg:generic/tomcat-logging-support@3.3.0", 259 | CPEs: []string{"cpe:2.3:a:cloudfoundry:tomcat-logging-support:3.3.0:*:*:*:*:*:*:*"}, 260 | } 261 | 262 | dc := libpak.DependencyCache{CachePath: "testdata"} 263 | 264 | contrib, entries := tomcat.NewBase( 265 | ctx.Application.Path, 266 | ctx.Buildpack.Path, 267 | libpak.ConfigurationResolver{}, 268 | "test-context-path", 269 | accessLoggingDep, 270 | &externalConfigurationDep, 271 | lifecycleDep, 272 | loggingDep, 273 | dc, 274 | false, 275 | ) 276 | Expect(entries).To(HaveLen(4)) 277 | Expect(entries[0].Name).To(Equal("tomcat-access-logging-support")) 278 | Expect(entries[0].Build).To(BeFalse()) 279 | Expect(entries[0].Launch).To(BeTrue()) 280 | Expect(entries[1].Name).To(Equal("tomcat-lifecycle-support")) 281 | Expect(entries[1].Build).To(BeFalse()) 282 | Expect(entries[1].Launch).To(BeTrue()) 283 | Expect(entries[2].Name).To(Equal("tomcat-logging-support")) 284 | Expect(entries[2].Build).To(BeFalse()) 285 | Expect(entries[2].Launch).To(BeTrue()) 286 | Expect(entries[3].Name).To(Equal("tomcat-external-configuration")) 287 | Expect(entries[3].Build).To(BeFalse()) 288 | Expect(entries[3].Launch).To(BeTrue()) 289 | 290 | layer, err := ctx.Layers.Layer("test-layer") 291 | Expect(err).NotTo(HaveOccurred()) 292 | 293 | layer, err = contrib.Contribute(layer) 294 | Expect(err).NotTo(HaveOccurred()) 295 | 296 | Expect(filepath.Join(layer.Path, "fixture-marker")).To(BeARegularFile()) 297 | }) 298 | }) 299 | 300 | context("$BP_TOMCAT_ENV_PROPERTY_SOURCE_DISABLED", func() { 301 | it.Before(func() { 302 | Expect(os.Setenv("BP_TOMCAT_ENV_PROPERTY_SOURCE_DISABLED", "true")).To(Succeed()) 303 | }) 304 | 305 | it.After(func() { 306 | Expect(os.Unsetenv("BP_TOMCAT_ENV_PROPERTY_SOURCE_DISABLED")).To(Succeed()) 307 | }) 308 | 309 | it("environment property source can be disabled", func() { 310 | accessLoggingDep := libpak.BuildpackDependency{ 311 | ID: "tomcat-access-logging-support", 312 | URI: "https://localhost/stub-tomcat-access-logging-support.jar", 313 | SHA256: "d723bfe2ba67dfa92b24e3b6c7b2d0e6a963de7313350e306d470e44e330a5d2", 314 | PURL: "pkg:generic/tomcat-access-logging-support@3.3.0", 315 | CPEs: []string{"cpe:2.3:a:cloudfoundry:tomcat-access-logging-support:3.3.0:*:*:*:*:*:*:*"}, 316 | } 317 | lifecycleDep := libpak.BuildpackDependency{ 318 | ID: "tomcat-lifecycle-support", 319 | URI: "https://localhost/stub-tomcat-lifecycle-support.jar", 320 | SHA256: "723126712c0b22a7fe409664adf1fbb78cf3040e313a82c06696f5058e190534", 321 | PURL: "pkg:generic/tomcat-lifecycle-support@3.3.0", 322 | CPEs: []string{"cpe:2.3:a:cloudfoundry:tomcat-lifecycle-support:3.3.0:*:*:*:*:*:*:*"}, 323 | } 324 | loggingDep := libpak.BuildpackDependency{ 325 | ID: "tomcat-logging-support", 326 | URI: "https://localhost/stub-tomcat-logging-support.jar", 327 | SHA256: "e0a7e163cc9f1ffd41c8de3942c7c6b505090b7484c2ba9be846334e31c44a2c", 328 | PURL: "pkg:generic/tomcat-logging-support@3.3.0", 329 | CPEs: []string{"cpe:2.3:a:cloudfoundry:tomcat-logging-support:3.3.0:*:*:*:*:*:*:*"}, 330 | } 331 | 332 | dc := libpak.DependencyCache{CachePath: "testdata"} 333 | 334 | contributor, entries := tomcat.NewBase( 335 | ctx.Application.Path, 336 | ctx.Buildpack.Path, 337 | libpak.ConfigurationResolver{}, 338 | "test-context-path", 339 | accessLoggingDep, 340 | nil, 341 | lifecycleDep, 342 | loggingDep, 343 | dc, 344 | false, 345 | ) 346 | 347 | Expect(entries).To(HaveLen(3)) 348 | Expect(entries[0].Name).To(Equal("tomcat-access-logging-support")) 349 | Expect(entries[0].Build).To(BeFalse()) 350 | Expect(entries[0].Launch).To(BeTrue()) 351 | Expect(entries[1].Name).To(Equal("tomcat-lifecycle-support")) 352 | Expect(entries[1].Build).To(BeFalse()) 353 | Expect(entries[1].Launch).To(BeTrue()) 354 | Expect(entries[2].Name).To(Equal("tomcat-logging-support")) 355 | Expect(entries[2].Build).To(BeFalse()) 356 | Expect(entries[2].Launch).To(BeTrue()) 357 | 358 | layer, err := ctx.Layers.Layer("test-layer") 359 | Expect(err).NotTo(HaveOccurred()) 360 | 361 | layer, err = contributor.Contribute(layer) 362 | Expect(err).NotTo(HaveOccurred()) 363 | 364 | Expect(layer.Launch).To(BeTrue()) 365 | Expect(filepath.Join(layer.Path, "conf", "context.xml")).To(BeARegularFile()) 366 | Expect(filepath.Join(layer.Path, "conf", "logging.properties")).To(BeARegularFile()) 367 | Expect(filepath.Join(layer.Path, "conf", "server.xml")).To(BeARegularFile()) 368 | Expect(filepath.Join(layer.Path, "conf", "web.xml")).To(BeARegularFile()) 369 | Expect(filepath.Join(layer.Path, "conf", "catalina.properties")).To(BeARegularFile()) 370 | Expect(os.ReadFile(filepath.Join(layer.Path, "conf", "catalina.properties"))).To(ContainSubstring("common.loader=${BPI_TOMCAT_ADDITIONAL_COMMON_JARS}")) 371 | Expect(filepath.Join(layer.Path, "lib", "stub-tomcat-access-logging-support.jar")).To(BeARegularFile()) 372 | Expect(filepath.Join(layer.Path, "lib", "stub-tomcat-lifecycle-support.jar")).To(BeARegularFile()) 373 | Expect(filepath.Join(layer.Path, "bin", "stub-tomcat-logging-support.jar")).To(BeARegularFile()) 374 | Expect(os.ReadFile(filepath.Join(layer.Path, "bin", "setenv.sh"))).To(Equal( 375 | []byte(fmt.Sprintf(`CLASSPATH="%s"`, filepath.Join(layer.Path, "bin", "stub-tomcat-logging-support.jar"))))) 376 | Expect(layer.LaunchEnvironment["CATALINA_BASE.default"]).To(Equal(layer.Path)) 377 | Expect(filepath.Join(layer.Path, "temp")).To(BeADirectory()) 378 | 379 | file := filepath.Join(layer.Path, "webapps", "test-context-path") 380 | fi, err := os.Lstat(file) 381 | Expect(err).NotTo(HaveOccurred()) 382 | Expect(fi.Mode() & os.ModeSymlink).To(Equal(os.ModeSymlink)) 383 | Expect(os.Readlink(file)).To(Equal(ctx.Application.Path)) 384 | 385 | Expect(layer.LaunchEnvironment["CATALINA_BASE.default"]).To(Equal(layer.Path)) 386 | Expect(layer.LaunchEnvironment["CATALINA_OPTS.default"]).To(Equal("-DBPI_TOMCAT_ADDITIONAL_COMMON_JARS=${BPI_TOMCAT_ADDITIONAL_COMMON_JARS}")) 387 | }) 388 | 389 | }) 390 | 391 | context("$BPI_TOMCAT_ADDITIONAL_JARS is set", func() { 392 | it.Before(func() { 393 | t.Setenv("BPI_TOMCAT_ADDITIONAL_JARS", "/layers/test-buildpack/foo/bar.jar") 394 | }) 395 | 396 | it("additional jar is added to classpath", func() { 397 | accessLoggingDep := libpak.BuildpackDependency{ 398 | ID: "tomcat-access-logging-support", 399 | URI: "https://localhost/stub-tomcat-access-logging-support.jar", 400 | SHA256: "d723bfe2ba67dfa92b24e3b6c7b2d0e6a963de7313350e306d470e44e330a5d2", 401 | PURL: "pkg:generic/tomcat-access-logging-support@3.3.0", 402 | CPEs: []string{"cpe:2.3:a:cloudfoundry:tomcat-access-logging-support:3.3.0:*:*:*:*:*:*:*"}, 403 | } 404 | lifecycleDep := libpak.BuildpackDependency{ 405 | ID: "tomcat-lifecycle-support", 406 | URI: "https://localhost/stub-tomcat-lifecycle-support.jar", 407 | SHA256: "723126712c0b22a7fe409664adf1fbb78cf3040e313a82c06696f5058e190534", 408 | PURL: "pkg:generic/tomcat-lifecycle-support@3.3.0", 409 | CPEs: []string{"cpe:2.3:a:cloudfoundry:tomcat-lifecycle-support:3.3.0:*:*:*:*:*:*:*"}, 410 | } 411 | loggingDep := libpak.BuildpackDependency{ 412 | ID: "tomcat-logging-support", 413 | URI: "https://localhost/stub-tomcat-logging-support.jar", 414 | SHA256: "e0a7e163cc9f1ffd41c8de3942c7c6b505090b7484c2ba9be846334e31c44a2c", 415 | PURL: "pkg:generic/tomcat-logging-support@3.3.0", 416 | CPEs: []string{"cpe:2.3:a:cloudfoundry:tomcat-logging-support:3.3.0:*:*:*:*:*:*:*"}, 417 | } 418 | 419 | dc := libpak.DependencyCache{CachePath: "testdata"} 420 | 421 | contributor, entries := tomcat.NewBase( 422 | ctx.Application.Path, 423 | ctx.Buildpack.Path, 424 | libpak.ConfigurationResolver{}, 425 | "test-context-path", 426 | accessLoggingDep, 427 | nil, 428 | lifecycleDep, 429 | loggingDep, 430 | dc, 431 | false, 432 | ) 433 | 434 | Expect(entries).To(HaveLen(3)) 435 | Expect(entries[0].Name).To(Equal("tomcat-access-logging-support")) 436 | Expect(entries[0].Build).To(BeFalse()) 437 | Expect(entries[0].Launch).To(BeTrue()) 438 | Expect(entries[1].Name).To(Equal("tomcat-lifecycle-support")) 439 | Expect(entries[1].Build).To(BeFalse()) 440 | Expect(entries[1].Launch).To(BeTrue()) 441 | Expect(entries[2].Name).To(Equal("tomcat-logging-support")) 442 | Expect(entries[2].Build).To(BeFalse()) 443 | Expect(entries[2].Launch).To(BeTrue()) 444 | 445 | layer, err := ctx.Layers.Layer("test-layer") 446 | Expect(err).NotTo(HaveOccurred()) 447 | 448 | layer, err = contributor.Contribute(layer) 449 | Expect(err).NotTo(HaveOccurred()) 450 | 451 | Expect(os.ReadFile(filepath.Join(layer.Path, "bin", "setenv.sh"))).To(Equal( 452 | []byte(fmt.Sprintf(`CLASSPATH="%s:%s"`, filepath.Join(layer.Path, "bin", "stub-tomcat-logging-support.jar"), "/layers/test-buildpack/foo/bar.jar")))) 453 | }) 454 | 455 | }) 456 | 457 | context("Contribute multiple war files", func() { 458 | files := []string{"api.war", "ui.war"} 459 | it.Before(func() { 460 | for _, file := range files { 461 | in, err := os.Open(filepath.Join("testdata", "warfiles", file)) 462 | Expect(err).NotTo(HaveOccurred()) 463 | 464 | out, err := os.OpenFile(filepath.Join(ctx.Application.Path, file), os.O_CREATE|os.O_APPEND|os.O_WRONLY, 0644) 465 | Expect(err).NotTo(HaveOccurred()) 466 | 467 | _, err = io.Copy(out, in) 468 | Expect(err).NotTo(HaveOccurred()) 469 | Expect(in.Close()).To(Succeed()) 470 | Expect(out.Close()).To(Succeed()) 471 | } 472 | }) 473 | 474 | it.After(func() { 475 | for _, file := range files { 476 | os.Remove(filepath.Join(ctx.Application.Path, file)) 477 | } 478 | }) 479 | 480 | it("Multiple war files have been exploded in application path", func() { 481 | accessLoggingDep := libpak.BuildpackDependency{ 482 | ID: "tomcat-access-logging-support", 483 | URI: "https://localhost/stub-tomcat-access-logging-support.jar", 484 | SHA256: "d723bfe2ba67dfa92b24e3b6c7b2d0e6a963de7313350e306d470e44e330a5d2", 485 | PURL: "pkg:generic/tomcat-access-logging-support@3.3.0", 486 | CPEs: []string{"cpe:2.3:a:cloudfoundry:tomcat-access-logging-support:3.3.0:*:*:*:*:*:*:*"}, 487 | } 488 | lifecycleDep := libpak.BuildpackDependency{ 489 | ID: "tomcat-lifecycle-support", 490 | URI: "https://localhost/stub-tomcat-lifecycle-support.jar", 491 | SHA256: "723126712c0b22a7fe409664adf1fbb78cf3040e313a82c06696f5058e190534", 492 | PURL: "pkg:generic/tomcat-lifecycle-support@3.3.0", 493 | CPEs: []string{"cpe:2.3:a:cloudfoundry:tomcat-lifecycle-support:3.3.0:*:*:*:*:*:*:*"}, 494 | } 495 | loggingDep := libpak.BuildpackDependency{ 496 | ID: "tomcat-logging-support", 497 | URI: "https://localhost/stub-tomcat-logging-support.jar", 498 | SHA256: "e0a7e163cc9f1ffd41c8de3942c7c6b505090b7484c2ba9be846334e31c44a2c", 499 | PURL: "pkg:generic/tomcat-logging-support@3.3.0", 500 | CPEs: []string{"cpe:2.3:a:cloudfoundry:tomcat-logging-support:3.3.0:*:*:*:*:*:*:*"}, 501 | } 502 | 503 | dc := libpak.DependencyCache{CachePath: "testdata"} 504 | 505 | contributor, entries := tomcat.NewBase( 506 | ctx.Application.Path, 507 | ctx.Buildpack.Path, 508 | libpak.ConfigurationResolver{}, 509 | "test-context-path", 510 | accessLoggingDep, 511 | nil, 512 | lifecycleDep, 513 | loggingDep, 514 | dc, 515 | true, 516 | ) 517 | 518 | Expect(entries).To(HaveLen(3)) 519 | Expect(entries[0].Name).To(Equal("tomcat-access-logging-support")) 520 | Expect(entries[0].Build).To(BeFalse()) 521 | Expect(entries[0].Launch).To(BeTrue()) 522 | Expect(entries[1].Name).To(Equal("tomcat-lifecycle-support")) 523 | Expect(entries[1].Build).To(BeFalse()) 524 | Expect(entries[1].Launch).To(BeTrue()) 525 | Expect(entries[2].Name).To(Equal("tomcat-logging-support")) 526 | Expect(entries[2].Build).To(BeFalse()) 527 | Expect(entries[2].Launch).To(BeTrue()) 528 | 529 | layer, err := ctx.Layers.Layer("test-layer") 530 | Expect(err).NotTo(HaveOccurred()) 531 | 532 | layer, err = contributor.Contribute(layer) 533 | Expect(err).NotTo(HaveOccurred()) 534 | 535 | Expect(os.Readlink(filepath.Join(layer.Path, "webapps"))).To(Equal(ctx.Application.Path)) 536 | for _, file := range files { 537 | targetDir := strings.TrimSuffix(file, filepath.Ext(file)) 538 | Expect(filepath.Join(layer.Path, "webapps", targetDir, "META-INF", "MANIFEST.MF")).To(BeARegularFile()) 539 | } 540 | }) 541 | }) 542 | 543 | } 544 | --------------------------------------------------------------------------------