diff --git a/buildpack.toml b/buildpack.toml index 98783073..7f49ca47 100644 --- a/buildpack.toml +++ b/buildpack.toml @@ -11,6 +11,7 @@ api = "0.6" [metadata] include-files = ["buildpack.toml"] +# pipenv [[order]] [[order.group]] @@ -58,6 +59,7 @@ api = "0.6" optional = true version = "4.1.0" +# pip [[order]] [[order.group]] @@ -101,6 +103,7 @@ api = "0.6" optional = true version = "4.1.0" +# conda [[order]] [[order.group]] @@ -140,6 +143,7 @@ api = "0.6" optional = true version = "4.1.0" +# poetry run [[order]] [[order.group]] @@ -182,6 +186,55 @@ api = "0.6" optional = true version = "4.1.0" +# poetry (dep only) +[[order]] + + [[order.group]] + id = "paketo-buildpacks/ca-certificates" + optional = true + version = "3.1.0" + + [[order.group]] + id = "paketo-buildpacks/watchexec" + optional = true + version = "2.3.0" + + [[order.group]] + id = "paketo-buildpacks/cpython" + version = "0.7.6" + + [[order.group]] + id = "paketo-buildpacks/pip" + version = "0.7.0" + + [[order.group]] + id = "paketo-buildpacks/poetry" + version = "0.0.1" + + [[order.group]] + id = "paketo-buildpacks/poetry-install" + version = "0.1.0" + + [[order.group]] + id = "paketo-buildpacks/python-start" + version = "0.9.0" + + [[order.group]] + id = "paketo-buildpacks/procfile" + optional = true + version = "5.1.0" + + [[order.group]] + id = "paketo-buildpacks/environment-variables" + optional = true + version = "4.1.0" + + [[order.group]] + id = "paketo-buildpacks/image-labels" + optional = true + version = "4.1.0" + +# no package manager [[order]] [[order.group]] diff --git a/integration/init_test.go b/integration/init_test.go index 00fd8331..c0891237 100644 --- a/integration/init_test.go +++ b/integration/init_test.go @@ -11,6 +11,7 @@ import ( "github.com/sclevine/spec/report" . "github.com/onsi/gomega" + "github.com/onsi/gomega/format" ) var pythonBuildpack string @@ -18,6 +19,8 @@ var pythonBuildpack string func TestIntegration(t *testing.T) { Expect := NewWithT(t).Expect + format.MaxLength = 0 + bash := pexec.NewExecutable("bash") buffer := bytes.NewBuffer(nil) err := bash.Execute(pexec.Execution{ @@ -36,6 +39,7 @@ func TestIntegration(t *testing.T) { suite("Conda", testConda) suite("Pip", testPip) suite("Pipenv", testPipenv) + suite("PoetryDepOnly", testPoetryDepOnly) suite("PoetryRun", testPoetryRun) suite("NoPackageManager", testNoPackageManager) suite.Run(t) diff --git a/integration/poetry_dep_only_test.go b/integration/poetry_dep_only_test.go new file mode 100644 index 00000000..0cd11467 --- /dev/null +++ b/integration/poetry_dep_only_test.go @@ -0,0 +1,106 @@ +package integration_test + +import ( + "fmt" + "io" + "net/http" + "os" + "path/filepath" + "testing" + + "github.com/paketo-buildpacks/occam" + "github.com/sclevine/spec" + + . "github.com/onsi/gomega" + . "github.com/paketo-buildpacks/occam/matchers" +) + +func testPoetryDepOnly(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 an app with poetry dependency management", 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()) + }) + + 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("creates a working OCI image with a start command", func() { + var err error + source, err = occam.Source(filepath.Join("testdata", "poetry-dep-only")) + Expect(err).NotTo(HaveOccurred()) + + var logs fmt.Stringer + image, logs, err = pack.WithNoColor().WithVerbose().Build. + WithBuildpacks(pythonBuildpack). + WithPullPolicy("never"). + WithEnv(map[string]string{ + "BPE_SOME_VARIABLE": "some-value", + "BP_IMAGE_LABELS": "some-label=some-value", + "BP_LIVE_RELOAD_ENABLED": "true", + }). + Execute(name, source) + Expect(err).NotTo(HaveOccurred(), logs.String()) + + container, err = docker.Container.Run. + WithEnv(map[string]string{"PORT": "8080"}). + WithPublish("8080"). + WithPublishAll(). + Execute(image.ID) + Expect(err).NotTo(HaveOccurred()) + + Eventually(container).Should(BeAvailable()) + + response, err := http.Get(fmt.Sprintf("http://localhost:%s", container.HostPort("8080"))) + Expect(err).NotTo(HaveOccurred()) + defer response.Body.Close() + + Expect(response.StatusCode).To(Equal(http.StatusOK)) + + content, err := io.ReadAll(response.Body) + Expect(err).NotTo(HaveOccurred()) + Expect(string(content)).To(ContainSubstring("Hello, World!")) + + Expect(logs).To(ContainLines(ContainSubstring("CA Certificates Buildpack"))) + Expect(logs).To(ContainLines(ContainSubstring("Watchexec Buildpack"))) + Expect(logs).To(ContainLines(ContainSubstring("CPython Buildpack"))) + Expect(logs).To(ContainLines(ContainSubstring("Pip Buildpack"))) + Expect(logs).To(ContainLines(ContainSubstring("Poetry Buildpack"))) + Expect(logs).To(ContainLines(ContainSubstring("Poetry Install Buildpack"))) + Expect(logs).To(ContainLines(ContainSubstring("Python Start Buildpack"))) + Expect(logs).To(ContainLines(ContainSubstring("Procfile Buildpack"))) + Expect(logs).To(ContainLines(ContainSubstring("Environment Variables Buildpack"))) + Expect(logs).To(ContainLines(ContainSubstring("Image Labels Buildpack"))) + + Expect(image.Buildpacks[8].Key).To(Equal("paketo-buildpacks/environment-variables")) + Expect(image.Buildpacks[8].Layers["environment-variables"].Metadata["variables"]).To(Equal(map[string]interface{}{"SOME_VARIABLE": "some-value"})) + Expect(image.Labels["some-label"]).To(Equal("some-value")) + }) + }) +} diff --git a/integration/testdata/poetry-dep-only/Procfile b/integration/testdata/poetry-dep-only/Procfile new file mode 100644 index 00000000..caf67228 --- /dev/null +++ b/integration/testdata/poetry-dep-only/Procfile @@ -0,0 +1 @@ +web: gunicorn server:app diff --git a/integration/testdata/poetry-dep-only/poetry.lock b/integration/testdata/poetry-dep-only/poetry.lock new file mode 100644 index 00000000..e1e4beb7 --- /dev/null +++ b/integration/testdata/poetry-dep-only/poetry.lock @@ -0,0 +1,165 @@ +[[package]] +name = "click" +version = "8.0.3" +description = "Composable command line interface toolkit" +category = "main" +optional = false +python-versions = ">=3.6" + +[package.dependencies] +colorama = {version = "*", markers = "platform_system == \"Windows\""} +importlib-metadata = {version = "*", markers = "python_version < \"3.8\""} + +[[package]] +name = "colorama" +version = "0.4.4" +description = "Cross-platform colored terminal text." +category = "main" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" + +[[package]] +name = "flask" +version = "0.12.3" +description = "A microframework based on Werkzeug, Jinja2 and good intentions" +category = "main" +optional = false +python-versions = "*" + +[package.dependencies] +click = ">=2.0" +itsdangerous = ">=0.21" +Jinja2 = ">=2.4" +Werkzeug = ">=0.7" + +[[package]] +name = "gunicorn" +version = "19.5.0" +description = "WSGI HTTP Server for UNIX" +category = "main" +optional = false +python-versions = "*" + +[[package]] +name = "importlib-metadata" +version = "4.8.3" +description = "Read metadata from Python packages" +category = "main" +optional = false +python-versions = ">=3.6" + +[package.dependencies] +typing-extensions = {version = ">=3.6.4", markers = "python_version < \"3.8\""} +zipp = ">=0.5" + +[package.extras] +docs = ["sphinx", "jaraco.packaging (>=8.2)", "rst.linker (>=1.9)"] +perf = ["ipython"] +testing = ["pytest (>=6)", "pytest-checkdocs (>=2.4)", "pytest-flake8", "pytest-cov", "pytest-enabler (>=1.0.1)", "packaging", "pep517", "pyfakefs", "flufl.flake8", "pytest-perf (>=0.9.2)", "pytest-black (>=0.3.7)", "pytest-mypy", "importlib-resources (>=1.3)"] + +[[package]] +name = "itsdangerous" +version = "2.0.1" +description = "Safely pass data to untrusted environments and back." +category = "main" +optional = false +python-versions = ">=3.6" + +[[package]] +name = "jinja2" +version = "2.7.2" +description = "A small but fast and easy to use stand-alone template engine written in pure python." +category = "main" +optional = false +python-versions = "*" + +[package.dependencies] +markupsafe = "*" + +[package.extras] +i18n = ["Babel (>=0.8)"] + +[[package]] +name = "markupsafe" +version = "0.21" +description = "Implements a XML/HTML/XHTML Markup safe string for Python" +category = "main" +optional = false +python-versions = "*" + +[[package]] +name = "typing-extensions" +version = "4.0.1" +description = "Backported and Experimental Type Hints for Python 3.6+" +category = "main" +optional = false +python-versions = ">=3.6" + +[[package]] +name = "werkzeug" +version = "0.10.4" +description = "The Swiss Army knife of Python web development" +category = "main" +optional = false +python-versions = "*" + +[[package]] +name = "zipp" +version = "3.6.0" +description = "Backport of pathlib-compatible object wrapper for zip files" +category = "main" +optional = false +python-versions = ">=3.6" + +[package.extras] +docs = ["sphinx", "jaraco.packaging (>=8.2)", "rst.linker (>=1.9)"] +testing = ["pytest (>=4.6)", "pytest-checkdocs (>=2.4)", "pytest-flake8", "pytest-cov", "pytest-enabler (>=1.0.1)", "jaraco.itertools", "func-timeout", "pytest-black (>=0.3.7)", "pytest-mypy"] + +[metadata] +lock-version = "1.1" +python-versions = "^3.6" +content-hash = "75874e1fba4d48d9ad1eab0aafa3beffca5deeb34a4156fb16dab64861dd00a2" + +[metadata.files] +click = [ + {file = "click-8.0.3-py3-none-any.whl", hash = "sha256:353f466495adaeb40b6b5f592f9f91cb22372351c84caeb068132442a4518ef3"}, + {file = "click-8.0.3.tar.gz", hash = "sha256:410e932b050f5eed773c4cda94de75971c89cdb3155a72a0831139a79e5ecb5b"}, +] +colorama = [ + {file = "colorama-0.4.4-py2.py3-none-any.whl", hash = "sha256:9f47eda37229f68eee03b24b9748937c7dc3868f906e8ba69fbcbdd3bc5dc3e2"}, + {file = "colorama-0.4.4.tar.gz", hash = "sha256:5941b2b48a20143d2267e95b1c2a7603ce057ee39fd88e7329b0c292aa16869b"}, +] +flask = [ + {file = "Flask-0.12.3-py2.py3-none-any.whl", hash = "sha256:74bb782687731332b86aa8ab0817be14c9e63e5fa837934de8be4f9236d6d0d2"}, + {file = "Flask-0.12.3.tar.gz", hash = "sha256:0f431076a50908f0484dcddd0f2fd0241129ef9ca1876799b3ebe14d823f60de"}, +] +gunicorn = [ + {file = "gunicorn-19.5.0-py2.py3-none-any.whl", hash = "sha256:44cda4183586467493deaa616ac16f42b2f33bfaa57d4d5c07999d6db78c40ec"}, + {file = "gunicorn-19.5.0.tar.gz", hash = "sha256:1c62764ceea2d28602843e3e3abd459194f976b8f27627116787977ced47923f"}, +] +importlib-metadata = [ + {file = "importlib_metadata-4.8.3-py3-none-any.whl", hash = "sha256:65a9576a5b2d58ca44d133c42a241905cc45e34d2c06fd5ba2bafa221e5d7b5e"}, + {file = "importlib_metadata-4.8.3.tar.gz", hash = "sha256:766abffff765960fcc18003801f7044eb6755ffae4521c8e8ce8e83b9c9b0668"}, +] +itsdangerous = [ + {file = "itsdangerous-2.0.1-py3-none-any.whl", hash = "sha256:5174094b9637652bdb841a3029700391451bd092ba3db90600dea710ba28e97c"}, + {file = "itsdangerous-2.0.1.tar.gz", hash = "sha256:9e724d68fc22902a1435351f84c3fb8623f303fffcc566a4cb952df8c572cff0"}, +] +jinja2 = [ + {file = "Jinja2-2.7.2.tar.gz", hash = "sha256:310a35fbccac3af13ebf927297f871ac656b9da1d248b1fe6765affa71b53235"}, +] +markupsafe = [ + {file = "MarkupSafe-0.21.tar.gz", hash = "sha256:c6465cd6ed2b96509ef0100e7fff8718ed52c2affab1860ed5a9b67f604dd59a"}, +] +typing-extensions = [ + {file = "typing_extensions-4.0.1-py3-none-any.whl", hash = "sha256:7f001e5ac290a0c0401508864c7ec868be4e701886d5b573a9528ed3973d9d3b"}, + {file = "typing_extensions-4.0.1.tar.gz", hash = "sha256:4ca091dea149f945ec56afb48dae714f21e8692ef22a395223bcd328961b6a0e"}, +] +werkzeug = [ + {file = "Werkzeug-0.10.4-py2.py3-none-any.whl", hash = "sha256:a438aa8c3f513a5cf3dda02a62f9e022b7598e9fbfeba14ba2d6dd957c8ab436"}, + {file = "Werkzeug-0.10.4.tar.gz", hash = "sha256:9d2771e4c89be127bc4bac056ab7ceaf0e0064c723d6b6e195739c3af4fd5c1d"}, +] +zipp = [ + {file = "zipp-3.6.0-py3-none-any.whl", hash = "sha256:9fe5ea21568a0a70e50f273397638d39b03353731e6cbbb3fd8502a33fec40bc"}, + {file = "zipp-3.6.0.tar.gz", hash = "sha256:71c644c5369f4a6e07636f0aa966270449561fcea2e3d6747b8d23efaa9d7832"}, +] diff --git a/integration/testdata/poetry-dep-only/pyproject.toml b/integration/testdata/poetry-dep-only/pyproject.toml new file mode 100644 index 00000000..6943ada4 --- /dev/null +++ b/integration/testdata/poetry-dep-only/pyproject.toml @@ -0,0 +1,19 @@ +[tool.poetry] +name = "default_app" +version = "0.1.0" +description = "" +authors = [] + +[tool.poetry.dependencies] +python = "^3.6" +Flask = "0.12.3" +Jinja2 = "2.7.2" +MarkupSafe = "0.21" +Werkzeug = "0.10.4" +gunicorn = "19.5.0" + +[tool.poetry.dev-dependencies] + +[build-system] +requires = ["poetry-core>=1.0.0"] +build-backend = "poetry.core.masonry.api" diff --git a/integration/testdata/poetry-dep-only/server.py b/integration/testdata/poetry-dep-only/server.py new file mode 100644 index 00000000..e5ba5627 --- /dev/null +++ b/integration/testdata/poetry-dep-only/server.py @@ -0,0 +1,9 @@ +from flask import Flask +app = Flask(__name__) + +@app.route('/') +def hello_world(): + return 'Hello, World!' + +if __name__ == "__main__": + app.run()