Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support for multiple war files #402

Merged
merged 7 commits into from
Jan 31, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 20 additions & 0 deletions internal/util/containsWarFiles.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
package util

import (
"os"
"strings"
)

func ContainsWarFiles(dir string) (bool, error) {
files, err := os.ReadDir(dir)
if err != nil {
return false, err
}

for _, file := range files {
if strings.HasSuffix(file.Name(), ".war") {
return true, nil
}
}
return false, nil
}
61 changes: 54 additions & 7 deletions tomcat/base.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import (
"os"
"path/filepath"
"strconv"
"strings"

"github.com/paketo-buildpacks/libpak/sbom"

Expand All @@ -44,6 +45,7 @@ type Base struct {
LifecycleDependency libpak.BuildpackDependency
LoggingDependency libpak.BuildpackDependency
Logger bard.Logger
WarFilesExist bool
}

func NewBase(
Expand All @@ -56,6 +58,7 @@ func NewBase(
lifecycleDependency libpak.BuildpackDependency,
loggingDependency libpak.BuildpackDependency,
cache libpak.DependencyCache,
warFilesExist bool,
) (Base, []libcnb.BOMEntry) {

dependencies := []libpak.BuildpackDependency{accessLoggingDependency, lifecycleDependency, loggingDependency}
Expand All @@ -79,6 +82,7 @@ func NewBase(
}),
LifecycleDependency: lifecycleDependency,
LoggingDependency: loggingDependency,
WarFilesExist: warFilesExist,
}

var bomEntries []libcnb.BOMEntry
Expand Down Expand Up @@ -163,14 +167,23 @@ func (b Base) Contribute(layer libcnb.Layer) (libcnb.Layer, error) {
}

file = filepath.Join(layer.Path, "webapps")
if err := os.MkdirAll(file, 0755); err != nil {
return libcnb.Layer{}, fmt.Errorf("unable to create directory %s\n%w", file, err)
}
if b.WarFilesExist {
if err := os.Symlink(b.ApplicationPath, file); err != nil {
return libcnb.Layer{}, fmt.Errorf("unable to create symlink from %s to %s\n%w", b.ApplicationPath, file, err)
}
if err := b.explodeWarFiles(); err != nil {
return libcnb.Layer{}, fmt.Errorf("unable to explode war files in %s\n%w", b.ApplicationPath, err)
}
} else {
if err := os.MkdirAll(file, 0755); err != nil {
return libcnb.Layer{}, fmt.Errorf("unable to create directory %s\n%w", file, err)
}

file = filepath.Join(layer.Path, "webapps", b.ContextPath)
b.Logger.Headerf("Mounting application at %s", b.ContextPath)
if err := os.Symlink(b.ApplicationPath, file); err != nil {
return libcnb.Layer{}, fmt.Errorf("unable to create symlink from %s to %s\n%w", b.ApplicationPath, file, err)
file = filepath.Join(layer.Path, "webapps", b.ContextPath)
b.Logger.Headerf("Mounting application at %s", b.ContextPath)
if err := os.Symlink(b.ApplicationPath, file); err != nil {
return libcnb.Layer{}, fmt.Errorf("unable to create symlink from %s to %s\n%w", b.ApplicationPath, file, err)
}
}

environmentPropertySourceDisabled := b.ConfigurationResolver.ResolveBool("BP_TOMCAT_ENV_PROPERTY_SOURCE_DISABLED")
Expand Down Expand Up @@ -359,6 +372,40 @@ func (b Base) writeDependencySBOM(layer libcnb.Layer, syftArtifacts []sbom.SyftA
return nil
}

func (b Base) explodeWarFiles() error {
warFiles, err := filepath.Glob(filepath.Join(b.ApplicationPath, "*.war"))
if err != nil {
return err
}

for _, warFilePath := range warFiles {
b.Logger.Debugf("Extracting: %s\n", warFilePath)

if _, err := os.Stat(warFilePath); err == nil {
in, err := os.Open(warFilePath)
if err != nil {
return fmt.Errorf("An error occurred while extracting %s: %s\n", warFilePath, err)
}
defer in.Close()

targetDir := strings.TrimSuffix(warFilePath, filepath.Ext(warFilePath))
if err := os.MkdirAll(targetDir, 0755); err != nil {
return fmt.Errorf("An error occurred while extracting %s: %s\n", warFilePath, err)
}

if err := crush.Extract(in, targetDir, 0); err != nil {
return fmt.Errorf("An error occurred while extracting %s: %s\n", warFilePath, err)
}

err = os.Remove(warFilePath)
if err != nil {
return fmt.Errorf("An error occurred while removing the .war file: %s\n", err)
}
}
}
return nil
}

func (Base) Name() string {
return "catalina-base"
}
117 changes: 115 additions & 2 deletions tomcat/base_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,10 @@ package tomcat_test

import (
"fmt"
"io"
"os"
"path/filepath"
"strings"
"testing"

"github.com/buildpacks/libcnb"
Expand Down Expand Up @@ -101,6 +103,7 @@ func testBase(t *testing.T, context spec.G, it spec.S) {
lifecycleDep,
loggingDep,
dc,
false,
)

Expect(entries).To(HaveLen(3))
Expand Down Expand Up @@ -175,7 +178,18 @@ func testBase(t *testing.T, context spec.G, it spec.S) {

dc := libpak.DependencyCache{CachePath: "testdata"}

contrib, entries := tomcat.NewBase(ctx.Application.Path, ctx.Buildpack.Path, libpak.ConfigurationResolver{}, "test-context-path", accessLoggingDep, &externalConfigurationDep, lifecycleDep, loggingDep, dc)
contrib, entries := tomcat.NewBase(
ctx.Application.Path,
ctx.Buildpack.Path,
libpak.ConfigurationResolver{},
"test-context-path",
accessLoggingDep,
&externalConfigurationDep,
lifecycleDep,
loggingDep,
dc,
false,
)
layer, err := ctx.Layers.Layer("test-layer")
Expect(err).NotTo(HaveOccurred())

Expand Down Expand Up @@ -240,7 +254,18 @@ func testBase(t *testing.T, context spec.G, it spec.S) {

dc := libpak.DependencyCache{CachePath: "testdata"}

contrib, entries := tomcat.NewBase(ctx.Application.Path, ctx.Buildpack.Path, libpak.ConfigurationResolver{}, "test-context-path", accessLoggingDep, &externalConfigurationDep, lifecycleDep, loggingDep, dc)
contrib, entries := tomcat.NewBase(
ctx.Application.Path,
ctx.Buildpack.Path,
libpak.ConfigurationResolver{},
"test-context-path",
accessLoggingDep,
&externalConfigurationDep,
lifecycleDep,
loggingDep,
dc,
false,
)
Expect(entries).To(HaveLen(4))
Expect(entries[0].Name).To(Equal("tomcat-access-logging-support"))
Expect(entries[0].Build).To(BeFalse())
Expand Down Expand Up @@ -309,6 +334,7 @@ func testBase(t *testing.T, context spec.G, it spec.S) {
lifecycleDep,
loggingDep,
dc,
false,
)

Expect(entries).To(HaveLen(3))
Expand Down Expand Up @@ -393,6 +419,7 @@ func testBase(t *testing.T, context spec.G, it spec.S) {
lifecycleDep,
loggingDep,
dc,
false,
)

Expect(entries).To(HaveLen(3))
Expand All @@ -418,4 +445,90 @@ func testBase(t *testing.T, context spec.G, it spec.S) {

})

context("Contribute multiple war files", func() {
files := []string{"api.war", "ui.war"}
it.Before(func() {
for _, file := range files {
in, err := os.Open(filepath.Join("testdata", "warfiles", file))
Expect(err).NotTo(HaveOccurred())

out, err := os.OpenFile(filepath.Join(ctx.Application.Path, file), os.O_CREATE|os.O_APPEND|os.O_WRONLY, 0644)
Expect(err).NotTo(HaveOccurred())

_, err = io.Copy(out, in)
Expect(err).NotTo(HaveOccurred())
Expect(in.Close()).To(Succeed())
Expect(out.Close()).To(Succeed())
}
})

it.After(func() {
for _, file := range files {
os.Remove(filepath.Join(ctx.Application.Path, file))
}
})

it("Multiple war files have been exploded in application path", func() {
accessLoggingDep := libpak.BuildpackDependency{
ID: "tomcat-access-logging-support",
URI: "https://localhost/stub-tomcat-access-logging-support.jar",
SHA256: "d723bfe2ba67dfa92b24e3b6c7b2d0e6a963de7313350e306d470e44e330a5d2",
PURL: "pkg:generic/[email protected]",
CPEs: []string{"cpe:2.3:a:cloudfoundry:tomcat-access-logging-support:3.3.0:*:*:*:*:*:*:*"},
}
lifecycleDep := libpak.BuildpackDependency{
ID: "tomcat-lifecycle-support",
URI: "https://localhost/stub-tomcat-lifecycle-support.jar",
SHA256: "723126712c0b22a7fe409664adf1fbb78cf3040e313a82c06696f5058e190534",
PURL: "pkg:generic/[email protected]",
CPEs: []string{"cpe:2.3:a:cloudfoundry:tomcat-lifecycle-support:3.3.0:*:*:*:*:*:*:*"},
}
loggingDep := libpak.BuildpackDependency{
ID: "tomcat-logging-support",
URI: "https://localhost/stub-tomcat-logging-support.jar",
SHA256: "e0a7e163cc9f1ffd41c8de3942c7c6b505090b7484c2ba9be846334e31c44a2c",
PURL: "pkg:generic/[email protected]",
CPEs: []string{"cpe:2.3:a:cloudfoundry:tomcat-logging-support:3.3.0:*:*:*:*:*:*:*"},
}

dc := libpak.DependencyCache{CachePath: "testdata"}

contributor, entries := tomcat.NewBase(
ctx.Application.Path,
ctx.Buildpack.Path,
libpak.ConfigurationResolver{},
"test-context-path",
accessLoggingDep,
nil,
lifecycleDep,
loggingDep,
dc,
true,
)

Expect(entries).To(HaveLen(3))
Expect(entries[0].Name).To(Equal("tomcat-access-logging-support"))
Expect(entries[0].Build).To(BeFalse())
Expect(entries[0].Launch).To(BeTrue())
Expect(entries[1].Name).To(Equal("tomcat-lifecycle-support"))
Expect(entries[1].Build).To(BeFalse())
Expect(entries[1].Launch).To(BeTrue())
Expect(entries[2].Name).To(Equal("tomcat-logging-support"))
Expect(entries[2].Build).To(BeFalse())
Expect(entries[2].Launch).To(BeTrue())

layer, err := ctx.Layers.Layer("test-layer")
Expect(err).NotTo(HaveOccurred())

layer, err = contributor.Contribute(layer)
Expect(err).NotTo(HaveOccurred())

Expect(os.Readlink(filepath.Join(layer.Path, "webapps"))).To(Equal(ctx.Application.Path))
for _, file := range files {
targetDir := strings.TrimSuffix(file, filepath.Ext(file))
Expect(filepath.Join(layer.Path, "webapps", targetDir, "META-INF", "MANIFEST.MF")).To(BeARegularFile())
}
})
})

}
40 changes: 23 additions & 17 deletions tomcat/build.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ import (
"github.com/heroku/color"

"github.com/buildpacks/libcnb"
"github.com/paketo-buildpacks/apache-tomcat/v7/internal/util"
"github.com/paketo-buildpacks/libjvm"
"github.com/paketo-buildpacks/libpak"
"github.com/paketo-buildpacks/libpak/bard"
Expand All @@ -51,26 +52,31 @@ func (b Build) Build(context libcnb.BuildContext) (libcnb.BuildResult, error) {
return result, nil
}

m, err := libjvm.NewManifest(context.Application.Path)
if err != nil {
return libcnb.BuildResult{}, fmt.Errorf("unable to read manifest\n%w", err)
}
warFilesExist, _ := util.ContainsWarFiles(context.Application.Path)
if warFilesExist {
b.Logger.Infof("%s contains war files.", context.Application.Path)
} else {
m, err := libjvm.NewManifest(context.Application.Path)
if err != nil {
return libcnb.BuildResult{}, fmt.Errorf("unable to read manifest\n%w", err)
}

if _, ok := m.Get("Main-Class"); ok {
for _, entry := range context.Plan.Entries {
result.Unmet = append(result.Unmet, libcnb.UnmetPlanEntry{Name: entry.Name})
if _, ok := m.Get("Main-Class"); ok {
for _, entry := range context.Plan.Entries {
result.Unmet = append(result.Unmet, libcnb.UnmetPlanEntry{Name: entry.Name})
}
return result, nil
}
return result, nil
}

file := filepath.Join(context.Application.Path, "WEB-INF")
if _, err := os.Stat(file); err != nil && !os.IsNotExist(err) {
return libcnb.BuildResult{}, fmt.Errorf("unable to stat file %s\n%w", file, err)
} else if os.IsNotExist(err) {
for _, entry := range context.Plan.Entries {
result.Unmet = append(result.Unmet, libcnb.UnmetPlanEntry{Name: entry.Name})
file := filepath.Join(context.Application.Path, "WEB-INF")
if _, err := os.Stat(file); err != nil && !os.IsNotExist(err) {
return libcnb.BuildResult{}, fmt.Errorf("unable to stat file %s\n%w", file, err)
} else if os.IsNotExist(err) {
for _, entry := range context.Plan.Entries {
result.Unmet = append(result.Unmet, libcnb.UnmetPlanEntry{Name: entry.Name})
}
return result, nil
}
return result, nil
}

b.Logger.Title(context.Buildpack)
Expand Down Expand Up @@ -144,7 +150,7 @@ func (b Build) Build(context libcnb.BuildContext) (libcnb.BuildResult, error) {
}
}

base, bomEntries := NewBase(context.Application.Path, context.Buildpack.Path, cr, b.ContextPath(cr), accessLoggingDependency, externalConfigurationDependency, lifecycleDependency, loggingDependency, dc)
base, bomEntries := NewBase(context.Application.Path, context.Buildpack.Path, cr, b.ContextPath(cr), accessLoggingDependency, externalConfigurationDependency, lifecycleDependency, loggingDependency, dc, warFilesExist)

base.Logger = b.Logger
result.Layers = append(result.Layers, base)
Expand Down
Loading
Loading