-
Notifications
You must be signed in to change notification settings - Fork 46
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
extensions: new flask extension #317
Conversation
rockcraft/extensions/paas_flask.py
Outdated
"paas-flask/install-app": { | ||
"plugin": "dump", | ||
"source": ".", | ||
"organize": renaming_map, | ||
"stage": list(renaming_map.values()), |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
how is this supposed to work? Were we not supposed to use the npm target for this?
We should add some minimal documentation on how to work with this
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This approach aligns with the web team's requirements, as they prefer to construct the static assets themselves before executing the rockcraft pack
command. And let rockcraft should incorporate the built artifact into the OCI image without making any modifications. I will add some comments about this.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
For flask, developers don't use npm for static assets since flask is a server side rendered framework primarily. Hence we decided not to add support for npm
based static assets in the flask extension. For others it could make sense or those assets should be served, e.g., using a content cache instead or dedicated 12-factor extension
That'a perfecly reasonable explanation. I would still like to see a spread
test for this and perhaps some documentation. Documentation can be a
separate PR, but a spread test show casing this seems like a must.
|
Spread test added, thanks! |
docs/how-to/use-flask-extension.rst
Outdated
@@ -0,0 +1,58 @@ | |||
The Flask Extension |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
this looks like a good start, it does however mixup reference, explanation, tutorial and how-to
ref: https://diataxis.fr/
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Gotcha, I have splited the document into a "how-to" and a "reference".
I also added the services
field to the extension as well following last week's discussion.
@tigarmo I have updated the pull request with some unit tests for the flask extension. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks! I haven't finished reviewing yet, will pick this up again in a little bit
docs/how-to/use-flask-extension.rst
Outdated
|
||
.. code-block:: yaml | ||
|
||
name: example |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think we can improve the fields a bit, even if they are just an example:
name: example-flask
summary: A Flask application
description: A ROCK packing a Flask application via the flask extension
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, indeed, updated, thanks!
@@ -0,0 +1,16 @@ | |||
Extensions |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
👍 ty for starting this page
rockcraft/extensions/__init__.py
Outdated
@@ -26,3 +26,7 @@ | |||
"register", | |||
"unregister", | |||
] | |||
|
|||
from .flask import Flask as _Flask |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
why the rename?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I am not exactly sure why, but indeed it's unnecessary, updated, thanks!
rockcraft/extensions/flask.py
Outdated
snippet["flask/container-processing"] = { | ||
"plugin": "nil", | ||
"source": ".", | ||
"override-prime": "craftctl default\nmkdir -p tmp\nchmod 777 tmp", |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
a bit surprised that this works, I guess craftctl default
is changing the current dir to CRAFT_PRIME
?
regardless, I think it's cleaner/more idiomatic to do something like "override-build": "mkdir -m 777 ${CRAFT_PART_INSTALL}/tmp"
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That's more cleaner, thanks, updated!
rockcraft/extensions/flask.py
Outdated
|
||
@override | ||
def get_parts_snippet(self) -> Dict[str, Any]: | ||
"""Create necessary parts to facilitate the flask application.""" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I guess this docstring is redundant/misleading
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, I forgot to updated the docstring, updated, thanks!
Co-authored-by: Tiago Nobrega <[email protected]>
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sorry for the delay, now I'm done!
rockcraft/extensions/flask.py
Outdated
"working-dir": "/srv/flask/app", | ||
} | ||
} | ||
existing_services = self.yaml_data.get("services", {}) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think for consistency with the part handling this should also be a deepcopy
of the existing data
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Added, perhaps we should define the yaml_data
as a property method. This way, we can prevent such issues at the framework level.
@property
def yaml_data(self) -> Dict[str, Any]:
return self._yaml_data
"srv/flask/app/requirements.txt", | ||
"srv/flask/app/static", | ||
] | ||
del parts["flask/install-app"]["stage"] |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
an alternative to sorting + deleting it here, which I assume you did because of the unpredictable ordering, is to have the extension always sort the list of stage files. Then it's more deterministic which is a plus
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Good idea, updated, thanks!
|
||
input_yaml["extensions"] = ["flask"] | ||
input_yaml["parts"] = {"flask/install-app": {"prime": ["-srv/flask/app/foobar"]}} | ||
input_yaml["services"] = { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
possibly add another service here, to test that this new service is preserved
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Added, thanks!
rockcraft/extensions/flask.py
Outdated
- flask/install-app: copy the flask project into the OCI image | ||
""" | ||
if not (self.project_root / "requirements.txt").exists(): | ||
raise ValueError( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think errors.ExtensionError
is more specific here
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Indeed, updated, thanks!
@@ -134,3 +134,102 @@ def test_project_load_extensions(fake_extensions, tmp_path): | |||
|
|||
# New part | |||
assert parts[f"{FullExtension.NAME}/new-part"] == {"plugin": "nil", "source": None} | |||
|
|||
|
|||
def test_flask_extensions(fake_extensions, tmp_path, input_yaml, monkeypatch): |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks for the tests! I think a couple are missing:
- A test that checks that
apply_extensions()
fails ifrequirements.txt
is missing - A test that checks that
build_base: ubuntu:22.04
gets added if the key is missing andbase
is bare - Since the extension has a custom merge for the parts, a test that merges existing keys. For example: a yaml that has:
parts:
flask/dependencies:
python-requirements: ["requirements-jammy.txt"]
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In light of this, I think all of these tests (and these new ones) should be moved to a test_flask
module in unit/extensions
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Gotcha, I have included more tests and move they to the test_flask.py
, thanks!
|
||
Example: | ||
|
||
.. code-block:: yaml |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Check that this meets all the requirements, e.g., we have made the prime
required now
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Documents has been updated, thanks!
rockcraft/extensions/flask.py
Outdated
import copy | ||
import os | ||
import posixpath | ||
from typing import Any, Dict, Optional, Tuple |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Which python version is used on rockcraft? Some of these are deprecated and should be replaced with the built in types dict
, tuple
etc
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
rockcraft is targeting python>=3.8
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@jdkandersson is right with regards to the current best practices but the rest of rockcraft's codebase is still using the types from typing
, so I suggest we keep this for consistency and then in a separate issue we can convert all code
rockcraft/extensions/flask.py
Outdated
ignores = [".git", "node_modules", ".yarn"] | ||
source_files = [ | ||
f | ||
for f in os.listdir(self.project_root) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
what about using pathlib?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Updated, thanks!
rockcraft/extensions/flask.py
Outdated
for f in os.listdir(self.project_root) | ||
if f not in ignores and not f.endswith(".rock") | ||
] | ||
renaming_map = {f: posixpath.join("srv/flask/app", f) for f in source_files} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I was trying to find the handling of requiring prime and checking that everything in there has the srv/flask/app
prefix?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That has been added in the latest commit, thanks!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
thanks for all this work @weiiwang01
rockcraft/extensions/flask.py
Outdated
import copy | ||
import os | ||
import posixpath | ||
from typing import Any, Dict, Optional, Tuple |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@jdkandersson is right with regards to the current best practices but the rest of rockcraft's codebase is still using the types from typing
, so I suggest we keep this for consistency and then in a separate issue we can convert all code
@@ -25,6 +25,11 @@ execute: | | |||
|
|||
# test the part merging | |||
docker run --rm --entrypoint /bin/python3 flask-extension -c "import pathlib;assert not pathlib.Path('/srv/flask/app/README').exists()" | |||
|
|||
# test the default flask service | |||
docker run --rm --name flask-extension-container -d -p 8137:8000 flask-extension |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
we should remove this container in the restore
step
docker rm -f flask-extension-container
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Added, thanks!
tests/unit/extensions/test_flask.py
Outdated
|
||
|
||
@pytest.mark.usefixtures("flask_extension") | ||
def test_flask_extension_error(tmp_path): |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
thanks for testing the error cases! I think we should split this into separate tests. It's a bit of work but it makes each individual test easier to reason about, and lowers the chance that they might interfere with each other in unexpected ways
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Gotcha, separate them into different test cases with appropriate names, thanks!
when this is merged, a snap of rockcraft with this implementation will show up as edge/12f (the feature branch leaf name) |
rockcraft/extensions/flask.py
Outdated
"missing requirements.txt file, " | ||
"flask extension requires this file with flask specified as a dependency" | ||
) | ||
source_files = [f.name for f in sorted(self.project_root.iterdir())] |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
we can go with this for now, but this extension as is, only serves 12f, as one cannot have https://github.com/flaskfactory/my-flask.git as source entry for building your application
well done @weiiwang01 ! |
This commit is the squashed version of the long-lived `feature/12f` branch containing the most recent version of the Flask extension in the context of the 12-factor App methodology. The original commit log is kept here for posterity, and follows: ------ * feat: new flask extension (#317) Co-authored-by: Sergio Schvezov <[email protected]> Co-authored-by: Tiago Nobrega <[email protected]> * feat: provide a default prime in the flask extension (#374) * feat(extensions): add a profile option to the rockcraft init command (#379) * chore(deps): update craft-providers to 1.19.2 The current version has two bugs (the "looping timer" one and the "not collecting error logs" one) that are blocking for many users. Ideally we should merge main into this branch, this is a stopgap before we can allocate time for it. * chore: manual updates to align with origin/main These are related to the change in base notation (ubuntu@ instead of ubuntu:) and the changes due to general craft-application-flow. * fix(docs): remove duplicate how-to entries * Fix schema validation * Fix typo * Template requires input variables * Fix lint * Fix lint * Update flask-framework extension * Remove support for ubuntu:20.04 * ROCK to rock and update copyright info * Update flask doc with real examples * Update use-flask-extension.rst * Update pyright exclude * Fix some linting issues * enable extension in spread tests * Fix use-flask-extension spread test * Rename ROCK to rock * Apply suggestions from code review Co-authored-by: Alex Lowe <[email protected]> --------- Co-authored-by: Sergio Schvezov <[email protected]> Co-authored-by: Tiago Nobrega <[email protected]> Co-authored-by: Gregory Schiano <[email protected]> Co-authored-by: Alex Lowe <[email protected]>
This pull request introduces an experimental Flask extension for the Juju Platform as a Service ecosystem in rockcraft.
The extension aims to facilitate the deployment and management of Flask-based web applications on Juju PaaS.
Here's an example project that uses the
flask-extension
https://github.com/weiiwang01/dqlite.io/blob/flask-extension/rockcraft.yaml.