Skip to content
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
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -29,5 +29,6 @@ go.work.sum
/.bin
/bin
/build
/linux/
.idea/
.DS_Store
16 changes: 16 additions & 0 deletions build.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import (
conda "github.com/paketo-buildpacks/python-packagers/pkg/packagers/conda"
pipinstall "github.com/paketo-buildpacks/python-packagers/pkg/packagers/pip"
pipenvinstall "github.com/paketo-buildpacks/python-packagers/pkg/packagers/pipenv"
pixiinstall "github.com/paketo-buildpacks/python-packagers/pkg/packagers/pixi"
poetryinstall "github.com/paketo-buildpacks/python-packagers/pkg/packagers/poetry"
uvinstall "github.com/paketo-buildpacks/python-packagers/pkg/packagers/uv"

Expand Down Expand Up @@ -123,6 +124,21 @@ func Build(
} else {
return packit.BuildResult{}, packit.Fail.WithMessage("missing plan for: %s", entry.Name)
}
case pixiinstall.PixiEnvPlanEntry:
if parameters, ok := buildParameters[pixiinstall.PixiEnvPlanEntry]; ok {
pixiResult, err := pixiinstall.Build(
parameters.(pixiinstall.PixiBuildParameters),
commonBuildParameters,
)(context)

if err != nil {
return packit.BuildResult{}, err
}

layers = append(layers, pixiResult.Layers...)
} else {
return packit.BuildResult{}, packit.Fail.WithMessage("missing plan for: %s", entry.Name)
}
default:
return packit.BuildResult{}, packit.Fail.WithMessage("unknown plan: %s", entry.Name)
}
Expand Down
28 changes: 19 additions & 9 deletions detect.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import (
conda "github.com/paketo-buildpacks/python-packagers/pkg/packagers/conda"
pipinstall "github.com/paketo-buildpacks/python-packagers/pkg/packagers/pip"
pipenvinstall "github.com/paketo-buildpacks/python-packagers/pkg/packagers/pipenv"
pixiinstall "github.com/paketo-buildpacks/python-packagers/pkg/packagers/pixi"
)

// Detect will return a packit.DetectFunc that will be invoked during the
Expand Down Expand Up @@ -49,15 +50,6 @@ func Detect(logger scribe.Emitter) packit.DetectFunc {
logger.Detail("%s", err)
}

logger.Title("Checking for conda")
condaResult, err := conda.Detect()(context)

if err == nil {
return condaResult, nil
} else {
logger.Detail("%s", err)
}

logger.Title("Checking for pipenv")
pipenvResult, err := pipenvinstall.Detect(
pipenvinstall.NewPipfileParser(),
Expand All @@ -70,6 +62,24 @@ func Detect(logger scribe.Emitter) packit.DetectFunc {
logger.Detail("%s", err)
}

logger.Title("Checking for pixi")
pixiResult, err := pixiinstall.Detect()(context)

if err == nil {
return pixiResult, nil
} else {
logger.Detail("%s", err)
}

logger.Title("Checking for conda")
condaResult, err := conda.Detect()(context)

if err == nil {
return condaResult, nil
} else {
logger.Detail("%s", err)
}

return packit.DetectResult{}, packit.Fail.WithMessage("No python packager manager related files found")
}
}
4 changes: 4 additions & 0 deletions integration/packagers/init_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -123,5 +123,9 @@ func TestIntegration(t *testing.T) {
suite("uv Offline", uvTestOffline)
suite("uv Reused", uvTestReused)

// pixi
suite("pixi Default", pixiTestDefault)
suite("pixi Reused", pixiTestLayerReuse)

suite.Run(t)
}
153 changes: 153 additions & 0 deletions integration/packagers/pixi_default_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,153 @@
// SPDX-FileCopyrightText: Copyright (c) 2013-Present CloudFoundry.org Foundation, Inc. All Rights Reserved.
//
// SPDX-License-Identifier: Apache-2.0

package integration_test

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

"github.com/paketo-buildpacks/occam"
"github.com/sclevine/spec"

. "github.com/onsi/gomega"
. "github.com/paketo-buildpacks/occam/matchers"
)

func pixiTestDefault(t *testing.T, context spec.G, it spec.S) {
var (
Expect = NewWithT(t).Expect
Eventually = NewWithT(t).Eventually
pack occam.Pack
docker occam.Docker
)

it.Before(func() {
pack = occam.NewPack()
docker = occam.NewDocker()
})

context("when building a simple app", func() {
var (
image occam.Image
container occam.Container
name string
source string
)

it.Before(func() {
var err error
name, err = occam.RandomName()
Expect(err).NotTo(HaveOccurred())

source, err = occam.Source(filepath.Join("testdata", "pixi", "default_app"))
Expect(err).NotTo(HaveOccurred())
})

it.After(func() {
Expect(docker.Container.Remove.Execute(container.ID)).To(Succeed())
Expect(docker.Image.Remove.Execute(image.ID)).To(Succeed())
Expect(docker.Volume.Remove.Execute(occam.CacheVolumeNames(name))).To(Succeed())
Expect(os.RemoveAll(source)).To(Succeed())
})

it("builds an oci image that has the correct behavior", func() {
var err error

var logs fmt.Stringer
image, logs, err = pack.WithNoColor().Build.
WithPullPolicy("never").
WithBuildpacks(
settings.Buildpacks.PythonInstallers.Online,
settings.Buildpacks.PythonPackagers.Online,
settings.Buildpacks.BuildPlan.Online,
).
Execute(name, source)
Expect(err).NotTo(HaveOccurred(), logs.String())

container, err = docker.Container.Run.
WithEnv(map[string]string{"PORT": "8080"}).
WithPublish("8080").
WithPublishAll().
WithCommand("python app.py").
Execute(image.ID)
Expect(err).NotTo(HaveOccurred())

Eventually(container).Should(Serve(ContainSubstring("Hello, world!")).OnPort(8080))
})

context("validating SBOM", func() {
var (
sbomDir string
)

it.Before(func() {
var err error
sbomDir, err = os.MkdirTemp("", "sbom")
Expect(err).NotTo(HaveOccurred())
Expect(os.Chmod(sbomDir, os.ModePerm)).To(Succeed())

source, err = occam.Source(filepath.Join("testdata", "pixi", "default_app"))
Expect(err).NotTo(HaveOccurred())
})

it.After(func() {
Expect(os.RemoveAll(sbomDir)).To(Succeed())
})

it("writes SBOM files to the layer and label metadata", func() {
var err error
var logs fmt.Stringer

image, logs, err = pack.WithNoColor().Build.
WithPullPolicy("never").
WithBuildpacks(
settings.Buildpacks.PythonInstallers.Online,
settings.Buildpacks.PythonPackagers.Online,
settings.Buildpacks.BuildPlan.Online,
).
WithEnv(map[string]string{
"BP_LOG_LEVEL": "DEBUG",
}).
WithSBOMOutputDir(sbomDir).
Execute(name, source)
Expect(err).ToNot(HaveOccurred(), logs.String)

container, err = docker.Container.Run.
WithCommand("python app.py").
WithEnv(map[string]string{"PORT": "8080"}).
WithPublish("8080").
Execute(image.ID)
Expect(err).ToNot(HaveOccurred())

Eventually(container).Should(BeAvailable())
Eventually(container).Should(Serve(ContainSubstring("Hello, world!")).OnPort(8080))

Expect(logs).To(ContainLines(
fmt.Sprintf(" Generating SBOM for /layers/%s/pixi-env", strings.ReplaceAll(buildpackInfo.Buildpack.ID, "/", "_")),
MatchRegexp(` Completed in \d+(\.?\d+)*`),
))
Expect(logs).To(ContainLines(
" Writing SBOM in the following format(s):",
" application/vnd.cyclonedx+json",
" application/spdx+json",
" application/vnd.syft+json",
))

// check that all required SBOM files are present
Expect(filepath.Join(sbomDir, "sbom", "launch", strings.ReplaceAll(buildpackInfo.Buildpack.ID, "/", "_"), "pixi-env", "sbom.cdx.json")).To(BeARegularFile())
Expect(filepath.Join(sbomDir, "sbom", "launch", strings.ReplaceAll(buildpackInfo.Buildpack.ID, "/", "_"), "pixi-env", "sbom.spdx.json")).To(BeARegularFile())
Expect(filepath.Join(sbomDir, "sbom", "launch", strings.ReplaceAll(buildpackInfo.Buildpack.ID, "/", "_"), "pixi-env", "sbom.syft.json")).To(BeARegularFile())

// check an SBOM file to make sure it has an entry for a dependency from pixi.toml
contents, err := os.ReadFile(filepath.Join(sbomDir, "sbom", "launch", strings.ReplaceAll(buildpackInfo.Buildpack.ID, "/", "_"), "pixi-env", "sbom.cdx.json"))
Expect(err).NotTo(HaveOccurred())
Expect(string(contents)).To(ContainSubstring(`"name": "flask"`))
})
})
})
}
126 changes: 126 additions & 0 deletions integration/packagers/pixi_layer_reuse_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,126 @@
// SPDX-FileCopyrightText: Copyright (c) 2013-Present CloudFoundry.org Foundation, Inc. All Rights Reserved.
//
// SPDX-License-Identifier: Apache-2.0

package integration_test

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

"github.com/paketo-buildpacks/occam"
"github.com/sclevine/spec"

. "github.com/onsi/gomega"
. "github.com/paketo-buildpacks/occam/matchers"
)

func pixiTestLayerReuse(t *testing.T, context spec.G, it spec.S) {
var (
Expect = NewWithT(t).Expect
Eventually = NewWithT(t).Eventually
pack occam.Pack
docker occam.Docker
)

it.Before(func() {
pack = occam.NewPack()
docker = occam.NewDocker()
})

context("when rebuilding an app", func() {
var (
firstImage occam.Image
secondImage occam.Image
firstContainer occam.Container
secondContainer occam.Container
name string
source string
imagesMap map[string]interface{}
containerMap map[string]interface{}
)

it.Before(func() {
var err error
name, err = occam.RandomName()
Expect(err).NotTo(HaveOccurred())

imagesMap = map[string]interface{}{}
containerMap = map[string]interface{}{}

source, err = occam.Source(filepath.Join("testdata", "pixi", "default_app"))
Expect(err).NotTo(HaveOccurred())
})

it.After(func() {
for containerID := range containerMap {
Expect(docker.Container.Remove.Execute(containerID)).To(Succeed())
}
for imageID := range imagesMap {
Expect(docker.Image.Remove.Execute(imageID)).To(Succeed())
}

Expect(docker.Volume.Remove.Execute(occam.CacheVolumeNames(name))).To(Succeed())
Expect(os.RemoveAll(source)).To(Succeed())
})

it("reuses the cached packages layer", func() {
var err error

var logs fmt.Stringer
firstImage, logs, err = pack.WithNoColor().Build.
WithPullPolicy("never").
WithBuildpacks(
settings.Buildpacks.PythonInstallers.Online,
settings.Buildpacks.PythonPackagers.Online,
settings.Buildpacks.BuildPlan.Online,
).
Execute(name, source)
Expect(err).NotTo(HaveOccurred(), logs.String())

imagesMap[firstImage.ID] = nil

firstContainer, err = docker.Container.Run.
WithEnv(map[string]string{"PORT": "8080"}).
WithPublish("8080").
WithPublishAll().
WithCommand("python app.py").
Execute(firstImage.ID)
Expect(err).NotTo(HaveOccurred())

containerMap[firstContainer.ID] = nil

Eventually(firstContainer).Should(Serve(ContainSubstring("Hello, world!")).OnPort(8080))

secondImage, logs, err = pack.WithNoColor().Build.
WithPullPolicy("never").
WithBuildpacks(
settings.Buildpacks.PythonInstallers.Online,
settings.Buildpacks.PythonPackagers.Online,
settings.Buildpacks.BuildPlan.Online,
).
Execute(name, source)
Expect(err).NotTo(HaveOccurred(), logs.String())

imagesMap[secondImage.ID] = nil

Expect(secondImage.Buildpacks[1].Key).To(Equal(buildpackInfo.Buildpack.ID))
Expect(secondImage.Buildpacks[1].Layers["pixi-env"].SHA).To(Equal(firstImage.Buildpacks[1].Layers["pixi-env"].SHA))
Expect(secondImage.Buildpacks[1].Layers["pixi-env"].Metadata["lockfile-sha"]).To(Equal(firstImage.Buildpacks[1].Layers["pixi-env"].Metadata["lockfile-sha"]))

secondContainer, err = docker.Container.Run.
WithEnv(map[string]string{"PORT": "8080"}).
WithPublish("8080").
WithPublishAll().
WithCommand("python app.py").
Execute(secondImage.ID)
Expect(err).NotTo(HaveOccurred())

containerMap[secondContainer.ID] = nil

Eventually(secondContainer).Should(Serve(ContainSubstring("Hello, world!")).OnPort(8080))
})
})
}
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
dependencies:
- python=2.7
- click=7.1
- python=3.10
- flask
Loading
Loading