Skip to content

Commit

Permalink
Provide context folder for extensions
Browse files Browse the repository at this point in the history
Co-authored-by: Johannes Dillmann <j.dillmann@sap.com>
  • Loading branch information
pbusko and modulo11 committed Jan 24, 2024
1 parent 3c8876c commit 5428f32
Show file tree
Hide file tree
Showing 9 changed files with 157 additions and 51 deletions.
63 changes: 63 additions & 0 deletions buildpack/buildcontext.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
package buildpack

Check failure on line 1 in buildpack/buildcontext.go

View workflow job for this annotation

GitHub Actions / test-linux-arm64

ST1000: at least one file in a package should have a package comment (stylecheck)

import (
"fmt"
"os"
"path/filepath"

"github.com/buildpacks/lifecycle/log"
)

const (
ContextDirKindBuild = "build"

Check failure on line 12 in buildpack/buildcontext.go

View workflow job for this annotation

GitHub Actions / test-linux-arm64

exported: exported const ContextDirKindBuild should have comment (or a comment on this block) or be unexported (revive)
ContextDirKindRun = "run"
ContextDirKindShared = "shared"

SharedContextDir = "context"
)

var (
BuildImageContextDir = fmt.Sprintf("%s.%s", SharedContextDir, ContextDirKindBuild)

Check failure on line 20 in buildpack/buildcontext.go

View workflow job for this annotation

GitHub Actions / test-linux-arm64

exported: exported var BuildImageContextDir should have comment or be unexported (revive)
RunImageContextDir = fmt.Sprintf("%s.%s", SharedContextDir, ContextDirKindRun)
)

type ContextInfo struct {

Check failure on line 24 in buildpack/buildcontext.go

View workflow job for this annotation

GitHub Actions / test-linux-arm64

exported: exported type ContextInfo should have comment or be unexported (revive)
ExtensionID string
Kind string
Path string
}

func findContexts(d ExtDescriptor, extOutputDir string, logger log.Logger) ([]ContextInfo, error) {
var contexts []ContextInfo
var sharedIsProvided bool

sharedContextDir := filepath.Join(extOutputDir, SharedContextDir)
if s, err := os.Stat(sharedContextDir); err == nil && s.IsDir() {
sharedIsProvided = true
contexts = append(contexts, ContextInfo{
ExtensionID: d.Extension.ID,
Path: sharedContextDir,
})
} else if err != nil && !os.IsNotExist(err) {
return nil, err
}

for _, dir := range []string{DockerfileKindRun, DockerfileKindBuild} {
contextDir := filepath.Join(extOutputDir, dir)

if s, err := os.Stat(contextDir); err == nil && s.IsDir() {
if sharedIsProvided {
return nil, fmt.Errorf("image-specific context dir is provided together with a shared context")
}

contexts = append(contexts, ContextInfo{
ExtensionID: d.Extension.ID,
Path: contextDir,
})
} else if err != nil && !os.IsNotExist(err) {
return nil, err
}
}

return contexts, nil
}
10 changes: 9 additions & 1 deletion buildpack/generate.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ type GenerateInputs struct {

type GenerateOutputs struct {
Dockerfiles []DockerfileInfo
Contexts []ContextInfo
MetRequires []string
}

Expand Down Expand Up @@ -114,6 +115,7 @@ func readOutputFilesExt(d ExtDescriptor, extOutputDir string, extPlanIn Plan, lo
var err error
var dfInfo DockerfileInfo
var found bool
var contexts []ContextInfo

// set MetRequires
gr.MetRequires = names(extPlanIn.Entries)
Expand All @@ -134,9 +136,15 @@ func readOutputFilesExt(d ExtDescriptor, extOutputDir string, extPlanIn Plan, lo
return GenerateOutputs{}, err
} else if found {
gr.Dockerfiles = append(gr.Dockerfiles, dfInfo)
logger.Debugf("Found '%d' Dockerfiles for processing", len(gr.Dockerfiles))
}

logger.Debugf("Found '%d' Dockerfiles for processing", len(gr.Dockerfiles))
if contexts, err = findContexts(d, extOutputDir, logger); err != nil {
return GenerateOutputs{}, err
} else {

Check failure on line 144 in buildpack/generate.go

View workflow job for this annotation

GitHub Actions / test-linux-arm64

indent-error-flow: if block ends with a return statement, so drop this else and outdent its block (revive)
gr.Contexts = contexts
logger.Debugf("Found '%d' Build Contexts for processing", len(gr.Contexts))
}

return gr, nil
}
Expand Down
1 change: 1 addition & 0 deletions internal/extend/dockerfile.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ type Dockerfile struct {
ExtensionID string
Path string `toml:"path"`
Args []Arg
ContextDir string
}

type Arg struct {
Expand Down
3 changes: 1 addition & 2 deletions launch/testmock/launch_env.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 1 addition & 2 deletions launch/testmock/launch_execd.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

29 changes: 24 additions & 5 deletions phase/extender.go
Original file line number Diff line number Diff line change
Expand Up @@ -262,10 +262,10 @@ func (e *Extender) extend(kind string, baseImage v1.Image, logger log.Logger) (v
return nil, err
}
workingHistory = configFile.History
buildOptions := e.extendOptions()
userID, groupID := userFrom(*configFile)
origUserID := userID
for _, dockerfile := range dockerfiles {
buildOptions := e.extendOptions(dockerfile)
dockerfile.Args = append([]extend.Arg{
{Name: argBuildID, Value: uuid.New().String()},
{Name: argUserID, Value: userID},
Expand Down Expand Up @@ -366,12 +366,12 @@ func (e *Extender) dockerfilesFor(kind string, logger log.Logger) ([]extend.Dock
}

func (e *Extender) dockerfileFor(kind, extID string) (*extend.Dockerfile, error) {
dockerfilePath := filepath.Join(e.GeneratedDir, kind, launch.EscapeID(extID), "Dockerfile")
dockerfilePath := filepath.Join(e.GeneratedDir, launch.EscapeID(extID), fmt.Sprintf("%s.Dockerfile", kind))
if _, err := os.Stat(dockerfilePath); err != nil {
return nil, nil
}

configPath := filepath.Join(e.GeneratedDir, kind, launch.EscapeID(extID), "extend-config.toml")
configPath := filepath.Join(e.GeneratedDir, launch.EscapeID(extID), "extend-config.toml")
var config extend.Config
_, err := toml.DecodeFile(configPath, &config)
if err != nil {
Expand All @@ -387,16 +387,35 @@ func (e *Extender) dockerfileFor(kind, extID string) (*extend.Dockerfile, error)
args = config.Run.Args
}

contextDir, err := e.contextDirFor(kind, extID)
if err != nil {
return nil, err
}

return &extend.Dockerfile{
ExtensionID: extID,
Path: dockerfilePath,
Args: args,
ContextDir: contextDir,
}, nil
}

func (e *Extender) extendOptions() extend.Options {
func (e *Extender) contextDirFor(kind, extID string) (string, error) {
sharedContextDir := filepath.Join(e.GeneratedDir, launch.EscapeID(extID), buildpack.SharedContextDir)
kindContextDir := filepath.Join(e.GeneratedDir, launch.EscapeID(extID), fmt.Sprintf("%s.%s", buildpack.SharedContextDir, kind))

for _, dir := range []string{kindContextDir, sharedContextDir} {
if s, err := os.Stat(dir); err == nil && s.IsDir() {
return dir, nil
}
}

return e.AppDir, nil
}

func (e *Extender) extendOptions(dockerfile extend.Dockerfile) extend.Options {
return extend.Options{
BuildContext: e.AppDir,
BuildContext: dockerfile.ContextDir,
CacheTTL: e.CacheTTL,
IgnorePaths: []string{e.AppDir, e.LayersDir, e.PlatformDir},
}
Expand Down
49 changes: 26 additions & 23 deletions phase/extender_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -199,26 +199,29 @@ func testExtender(t *testing.T, when spec.G, it spec.S) {
when("build base image", func() {
it("applies the Dockerfile with the expected args and opts", func() {
expectedDockerfileA := extend.Dockerfile{
Path: filepath.Join(generatedDir, "build", "A", "Dockerfile"),
Path: filepath.Join(generatedDir, "A", "build.Dockerfile"),
Args: []extend.Arg{{Name: "argA", Value: "valueA"}},
}
h.Mkdir(t, filepath.Join(generatedDir, "build", "A"))
h.Mkfile(t, "some dockerfile content", filepath.Join(generatedDir, "build", "A", "Dockerfile"))
h.Mkdir(t, filepath.Join(generatedDir, "A"))
h.Mkdir(t, filepath.Join(generatedDir, "A", "context.build"))
h.Mkfile(t, "some dockerfile content", filepath.Join(generatedDir, "A", "build.Dockerfile"))
buf := new(bytes.Buffer)
data := extend.Config{Build: extend.BuildConfig{Args: expectedDockerfileA.Args}}
h.AssertNil(t, toml.NewEncoder(buf).Encode(data))
h.Mkfile(t, buf.String(), filepath.Join(generatedDir, "build", "A", "extend-config.toml"))
h.Mkfile(t, buf.String(), filepath.Join(generatedDir, "A", "extend-config.toml"))

expectedDockerfileB := extend.Dockerfile{
Path: filepath.Join(generatedDir, "build", "B", "Dockerfile"),
Args: []extend.Arg{{Name: "argB", Value: "valueB"}},
Path: filepath.Join(generatedDir, "B", "build.Dockerfile"),
Args: []extend.Arg{{Name: "argB", Value: "valueB"}},
ContextDir: "dirB",
}
h.Mkdir(t, filepath.Join(generatedDir, "build", "B"))
h.Mkfile(t, "some dockerfile content", filepath.Join(generatedDir, "build", "B", "Dockerfile"))
h.Mkdir(t, filepath.Join(generatedDir, "B"))
h.Mkdir(t, filepath.Join(generatedDir, "B", "context"))
h.Mkfile(t, "some dockerfile content", filepath.Join(generatedDir, "B", "build.Dockerfile"))
buf = new(bytes.Buffer)
data = extend.Config{Build: extend.BuildConfig{Args: expectedDockerfileB.Args}}
h.AssertNil(t, toml.NewEncoder(buf).Encode(data))
h.Mkfile(t, buf.String(), filepath.Join(generatedDir, "build", "B", "extend-config.toml"))
h.Mkfile(t, buf.String(), filepath.Join(generatedDir, "B", "extend-config.toml"))

fakeDockerfileApplier.EXPECT().ImageFor(extender.ImageRef).Return(someFakeImage, nil)
someFakeImage.ManifestReturns(&v1.Manifest{Layers: []v1.Descriptor{}}, nil)
Expand All @@ -233,7 +236,7 @@ func testExtender(t *testing.T, when spec.G, it spec.S) {
gomock.Any(),
gomock.Any(), // we mutate the provided image so we can't expect the fake image
extend.Options{
BuildContext: "some-app-dir",
BuildContext: filepath.Join(generatedDir, "A", "context.build"),
IgnorePaths: []string{"some-app-dir", "some-layers-dir", "some-platform-dir"},
CacheTTL: 7 * (24 * time.Hour),
},
Expand Down Expand Up @@ -266,7 +269,7 @@ func testExtender(t *testing.T, when spec.G, it spec.S) {
gomock.Any(),
someFakeImage,
extend.Options{
BuildContext: "some-app-dir",
BuildContext: filepath.Join(generatedDir, "B", "context"),
IgnorePaths: []string{"some-app-dir", "some-layers-dir", "some-platform-dir"},
CacheTTL: 7 * (24 * time.Hour),
},
Expand Down Expand Up @@ -339,26 +342,26 @@ func testExtender(t *testing.T, when spec.G, it spec.S) {
when(fmt.Sprintf("first Dockerfile is %s, second Dockerfile is %s", desc(tc.firstDockerfileRebasable), desc(tc.secondDockerfileRebasable)), func() {
it("applies the Dockerfile with the expected args and opts", func() {
expectedDockerfileA := extend.Dockerfile{
Path: filepath.Join(generatedDir, "run", "A", "Dockerfile"),
Path: filepath.Join(generatedDir, "A", "run.Dockerfile"),
Args: []extend.Arg{{Name: "argA", Value: "valueA"}},
}
h.Mkdir(t, filepath.Join(generatedDir, "run", "A"))
h.Mkfile(t, "some dockerfile content", filepath.Join(generatedDir, "run", "A", "Dockerfile"))
h.Mkdir(t, filepath.Join(generatedDir, "A"))
h.Mkfile(t, "some dockerfile content", filepath.Join(generatedDir, "A", "run.Dockerfile"))
buf := new(bytes.Buffer)
data := extend.Config{Run: extend.BuildConfig{Args: expectedDockerfileA.Args}}
h.AssertNil(t, toml.NewEncoder(buf).Encode(data))
h.Mkfile(t, buf.String(), filepath.Join(generatedDir, "run", "A", "extend-config.toml"))
h.Mkfile(t, buf.String(), filepath.Join(generatedDir, "A", "extend-config.toml"))

expectedDockerfileB := extend.Dockerfile{
Path: filepath.Join(generatedDir, "run", "B", "Dockerfile"),
Path: filepath.Join(generatedDir, "B", "run.Dockerfile"),
Args: []extend.Arg{{Name: "argB", Value: "valueB"}},
}
h.Mkdir(t, filepath.Join(generatedDir, "run", "B"))
h.Mkfile(t, "some dockerfile content", filepath.Join(generatedDir, "run", "B", "Dockerfile"))
h.Mkdir(t, filepath.Join(generatedDir, "B"))
h.Mkfile(t, "some dockerfile content", filepath.Join(generatedDir, "B", "run.Dockerfile"))
buf = new(bytes.Buffer)
data = extend.Config{Run: extend.BuildConfig{Args: expectedDockerfileB.Args}}
h.AssertNil(t, toml.NewEncoder(buf).Encode(data))
h.Mkfile(t, buf.String(), filepath.Join(generatedDir, "run", "B", "extend-config.toml"))
h.Mkfile(t, buf.String(), filepath.Join(generatedDir, "B", "extend-config.toml"))

fakeDockerfileApplier.EXPECT().ImageFor(extender.ImageRef).Return(someFakeImage, nil)
someFakeImage.ManifestReturns(&v1.Manifest{Layers: []v1.Descriptor{}}, nil)
Expand Down Expand Up @@ -455,15 +458,15 @@ func testExtender(t *testing.T, when spec.G, it spec.S) {

it("errors if the last extension leaves the user as root", func() {
expectedDockerfileA := extend.Dockerfile{
Path: filepath.Join(generatedDir, "run", "A", "Dockerfile"),
Path: filepath.Join(generatedDir, "A", "run.Dockerfile"),
Args: []extend.Arg{{Name: "argA", Value: "valueA"}},
}
h.Mkdir(t, filepath.Join(generatedDir, "run", "A"))
h.Mkfile(t, "some dockerfile content", filepath.Join(generatedDir, "run", "A", "Dockerfile"))
h.Mkdir(t, filepath.Join(generatedDir, "A"))
h.Mkfile(t, "some dockerfile content", filepath.Join(generatedDir, "A", "run.Dockerfile"))
buf := new(bytes.Buffer)
data := extend.Config{Run: extend.BuildConfig{Args: expectedDockerfileA.Args}}
h.AssertNil(t, toml.NewEncoder(buf).Encode(data))
h.Mkfile(t, buf.String(), filepath.Join(generatedDir, "run", "A", "extend-config.toml"))
h.Mkfile(t, buf.String(), filepath.Join(generatedDir, "A", "extend-config.toml"))

fakeDockerfileApplier.EXPECT().ImageFor(extender.ImageRef).Return(someFakeImage, nil)
firstConfig := &v1.ConfigFile{Config: v1.Config{
Expand Down
24 changes: 22 additions & 2 deletions phase/generator.go
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,7 @@ func (g *Generator) Generate() (GenerateResult, error) {
inputs.OutputDir = extensionOutputParentDir

var dockerfiles []buildpack.DockerfileInfo
var contexts []buildpack.ContextInfo
filteredPlan := g.Plan
for _, ext := range g.Extensions {
g.Logger.Debugf("Running generate for extension %s", ext)
Expand All @@ -103,6 +104,7 @@ func (g *Generator) Generate() (GenerateResult, error) {

// aggregate build results
dockerfiles = append(dockerfiles, result.Dockerfiles...)
contexts = append(contexts, result.Contexts...)
filteredPlan = filteredPlan.Filter(result.MetRequires)

g.Logger.Debugf("Finished running generate for extension %s", ext)
Expand Down Expand Up @@ -134,6 +136,11 @@ func (g *Generator) Generate() (GenerateResult, error) {
return GenerateResult{}, err
}

g.Logger.Debug("Copying Context directories")
if err = g.copyContexts(contexts); err != nil {
return GenerateResult{}, err
}

return GenerateResult{
AnalyzedMD: finalAnalyzedMD,
Plan: filteredPlan,
Expand All @@ -153,8 +160,8 @@ func (g *Generator) getGenerateInputs() buildpack.GenerateInputs {

func (g *Generator) copyDockerfiles(dockerfiles []buildpack.DockerfileInfo) error {
for _, dockerfile := range dockerfiles {
targetDir := filepath.Join(g.GeneratedDir, dockerfile.Kind, launch.EscapeID(dockerfile.ExtensionID))
var targetPath = filepath.Join(targetDir, "Dockerfile")
targetDir := filepath.Join(g.GeneratedDir, launch.EscapeID(dockerfile.ExtensionID))
var targetPath = filepath.Join(targetDir, filepath.Base(dockerfile.Path))
if dockerfile.Kind == buildpack.DockerfileKindRun && dockerfile.Ignore {
targetPath += ".ignore"
}
Expand All @@ -176,6 +183,19 @@ func (g *Generator) copyDockerfiles(dockerfiles []buildpack.DockerfileInfo) erro
return nil
}

func (g *Generator) copyContexts(contexts []buildpack.ContextInfo) error {
for _, contextDir := range contexts {
targetPath := filepath.Join(g.GeneratedDir, launch.EscapeID(contextDir.ExtensionID), filepath.Base(contextDir.Path))

g.Logger.Debugf("Copying %s to %s", contextDir.Path, targetPath)
if err := fsutil.Copy(contextDir.Path, targetPath); err != nil {
return err
}
}

return nil
}

func (g *Generator) runImageFrom(dockerfiles []buildpack.DockerfileInfo) (newBase string, extend bool) {
var ignoreNext bool
for i := len(dockerfiles) - 1; i >= 0; i-- {
Expand Down
Loading

0 comments on commit 5428f32

Please sign in to comment.