-
Notifications
You must be signed in to change notification settings - Fork 60
Add go init profile and go-extension #631
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
Merged
Merged
Changes from all commits
Commits
Show all changes
37 commits
Select commit
Hold shift + click to select a range
f0e53fa
WIP go-extension
javierdelapuente 3e4e484
fix linting
javierdelapuente 77d07d3
Fix linting
javierdelapuente 5666b03
fix linting
javierdelapuente f989661
Fix linting
javierdelapuente 9247039
typo fix
javierdelapuente 0accf93
Rename init profile go to go-extension
javierdelapuente 73d0d54
Add working dir
javierdelapuente 5818d3e
Replace go-extention base directory /go with /app
javierdelapuente d0e9162
Run github action
javierdelapuente 661b881
Preparing init profile
javierdelapuente a1e2ff6
fix pebble link
javierdelapuente b823934
Revert spread.yaml
javierdelapuente 0af7958
build-base not needed in the test anymore
javierdelapuente fb7f747
Reorder and improve code
javierdelapuente d7d4b28
Fix formatting
javierdelapuente a006232
Add 24.04 base to the go extension as default
javierdelapuente 5841c99
Revert spread.yaml file
javierdelapuente 62d835f
Add base 24.04 by default instead of 22.04
javierdelapuente 79eef98
Add asser for docs_url in exception
javierdelapuente 5648c15
Add extra platforms for flask, django and go
javierdelapuente d1cf4b2
Rerun actions
javierdelapuente 8362e97
Launch actions
javierdelapuente 2967405
launch actions
javierdelapuente c7edd5b
Update rockcraft/commands/init.py
javierdelapuente 9d7a90d
Update rockcraft/commands/init.py
javierdelapuente d26b707
Update rockcraft/extensions/go.py
javierdelapuente d255694
doc_url -> doc_slug
javierdelapuente a0b66ef
Improve _get_nested, as "." is a valid character in a yaml name
javierdelapuente f6e3e41
correct type to dict
javierdelapuente 0ad6687
remove pdb
javierdelapuente d8f1039
remove support for 22.04 in go
javierdelapuente 942bea2
comment non amd64 platforms in extensions
javierdelapuente 4858e27
Add comment for platforms in extensions
javierdelapuente cc913f7
Merge branch 'main' into go-extension
javierdelapuente 70974e9
add missing type annotations
javierdelapuente a15ee52
Apply suggestions from code review
tigarmo File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,214 @@ | ||
| # -*- Mode:Python; indent-tabs-mode:nil; tab-width:4 -*- | ||
| # | ||
| # Copyright 2024 Canonical Ltd. | ||
| # | ||
| # This program is free software: you can redistribute it and/or modify | ||
| # it under the terms of the GNU General Public License version 3 as | ||
| # published by the Free Software Foundation. | ||
| # | ||
| # This program is distributed in the hope that it will be useful, | ||
| # but WITHOUT ANY WARRANTY; without even the implied warranty of | ||
| # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||
| # GNU General Public License for more details. | ||
| # | ||
| # You should have received a copy of the GNU General Public License | ||
| # along with this program. If not, see <http://www.gnu.org/licenses/>. | ||
|
|
||
| """An extension for Go based applications.""" | ||
|
|
||
| import os | ||
| import re | ||
| from typing import Any, Dict, Tuple | ||
|
|
||
| from overrides import override | ||
|
|
||
| from ..errors import ExtensionError | ||
| from .extension import Extension | ||
|
|
||
|
|
||
| class GoFramework(Extension): | ||
| """An extension class for Go applications.""" | ||
|
|
||
| @staticmethod | ||
| @override | ||
| def get_supported_bases() -> Tuple[str, ...]: | ||
| """Return supported bases.""" | ||
| return "bare", "ubuntu@24.04" | ||
|
|
||
| @staticmethod | ||
| @override | ||
| def is_experimental(base: str | None) -> bool: | ||
| """Check if the extension is in an experimental state.""" | ||
| return True | ||
|
|
||
| @override | ||
| def get_root_snippet(self) -> dict[str, Any]: | ||
| """Return the root snippet to apply.""" | ||
| self._check_project() | ||
|
|
||
| snippet: Dict[str, Any] = { | ||
| "run_user": "_daemon_", | ||
| "services": { | ||
| "go": { | ||
| "override": "replace", | ||
| "startup": "enabled", | ||
| "command": self.project_name, | ||
| "user": "_daemon_", | ||
| "working-dir": "/app", | ||
| }, | ||
| }, | ||
| } | ||
|
|
||
| snippet["parts"] = { | ||
| # This is needed in case there is no assets part, as the working directory is /app | ||
| "go-framework/base-layout": { | ||
| "plugin": "nil", | ||
| "override-build": "mkdir -p ${CRAFT_PART_INSTALL}/app", | ||
| }, | ||
| "go-framework/install-app": self._get_install_app_part(), | ||
| "go-framework/runtime": { | ||
| "plugin": "nil", | ||
| "stage-packages": ["ca-certificates_data"], | ||
| }, | ||
| } | ||
|
|
||
| assets_part = self._get_install_assets_part() | ||
| if assets_part: | ||
| snippet["parts"]["go-framework/assets"] = assets_part | ||
|
|
||
| return snippet | ||
|
|
||
| @override | ||
| def get_parts_snippet(self) -> dict[str, Any]: | ||
| """Return the parts to add to parts.""" | ||
| return {} | ||
|
|
||
| @override | ||
| def get_part_snippet(self) -> dict[str, Any]: | ||
| """Return the part snippet to apply to existing parts.""" | ||
| return {} | ||
|
|
||
| @property | ||
| def project_name(self) -> str: | ||
| """Return the normalized name of the rockcraft project.""" | ||
| return self.yaml_data["name"] | ||
|
|
||
| def _check_project(self) -> None: | ||
| """Check go.mod file exist in project.""" | ||
| if not (self.project_root / "go.mod").exists(): | ||
| raise ExtensionError( | ||
| "missing go.mod file", | ||
| doc_slug="/reference/extensions/go-framework", | ||
| logpath_report=False, | ||
| ) | ||
|
|
||
| def _get_install_app_part(self) -> Dict[str, Any]: | ||
| """Generate install-app part with the Go plugin.""" | ||
| install_app = self._get_nested( | ||
| self.yaml_data, ["parts", "go-framework/install-app"] | ||
| ) | ||
|
|
||
| build_environment = install_app.get("build-environment", []) | ||
| if self.yaml_data["base"] == "bare": | ||
| for env_var in build_environment: | ||
| if "CGO_ENABLED" in env_var: | ||
| break | ||
| else: | ||
| build_environment = [{"CGO_ENABLED": "0"}] | ||
|
|
||
| organize = install_app.get("organize", {}) | ||
| binary_path = f"usr/local/bin/{self.project_name}" | ||
| for path in organize.values(): | ||
| if path == binary_path: | ||
| break | ||
| else: | ||
| if not self._get_nested(self.yaml_data, ["services", "go", "command"]): | ||
| organize[f"bin/{self.project_name}"] = binary_path | ||
|
|
||
| install_app_part = { | ||
| "plugin": "go", | ||
| "source": ".", | ||
| "organize": organize, | ||
| } | ||
|
|
||
| if not self._check_go_overriden(): | ||
| build_snaps = install_app.get("build-snaps", []) | ||
| build_snaps.append("go") | ||
| install_app_part["build-snaps"] = build_snaps | ||
|
|
||
| install_app_part["stage"] = list(organize.values()) | ||
| if build_environment: | ||
| install_app_part["build-environment"] = build_environment | ||
|
|
||
| return install_app_part | ||
|
|
||
| def _check_go_overriden(self) -> bool: | ||
| """Check if the user overrode the go snap or package for the build step.""" | ||
| install_app = self._get_nested( | ||
| self.yaml_data, ["parts", "go-framework/install-app"] | ||
| ) | ||
| build_snaps = install_app.get("build-snaps", []) | ||
| if build_snaps: | ||
| for snap in build_snaps: | ||
| if snap.startswith("go"): | ||
| return True | ||
| build_packages = install_app.get("build-packages", []) | ||
| if build_packages: | ||
| for package in build_packages: | ||
| if package in ["gccgo-go", "golang-go"]: | ||
| return True | ||
| return False | ||
|
|
||
| def _get_install_assets_part(self) -> Dict[str, Any] | None: | ||
| """Generate assets-stage part for extra assets in the project.""" | ||
| # if stage is not in exclude mode, use it to generate organize | ||
| if ( | ||
| self._assets_stage | ||
| and self._assets_stage[0] | ||
| and self._assets_stage[0][0] != "-" | ||
| ): | ||
| renaming_map = { | ||
| os.path.relpath(file, "app"): file for file in self._assets_stage | ||
| } | ||
| else: | ||
| return None | ||
|
|
||
| return { | ||
| "plugin": "dump", | ||
| "source": ".", | ||
| "organize": renaming_map, | ||
| "stage": self._assets_stage, | ||
| } | ||
|
|
||
| @property | ||
| def _assets_stage(self) -> list[str]: | ||
| """Return the assets stage list for the Go project.""" | ||
| user_stage = self._get_nested( | ||
| self.yaml_data, ["parts", "go-framework/assets"] | ||
| ).get("stage", []) | ||
|
|
||
| if not all(re.match("-? *app/", p) for p in user_stage): | ||
| raise ExtensionError( | ||
| "go-framework extension requires the 'stage' entry in the " | ||
| "go-framework/assets part to start with app", | ||
| doc_slug="/reference/extensions/go-framework", | ||
| logpath_report=False, | ||
| ) | ||
| if not user_stage: | ||
| user_stage = [ | ||
| f"app/{f}" | ||
| for f in ( | ||
| "migrate", | ||
| "migrate.sh", | ||
| "static", | ||
| "templates", | ||
| ) | ||
| if (self.project_root / f).exists() | ||
| ] | ||
| return user_stage | ||
|
|
||
| def _get_nested(self, obj: dict, paths: list[str]) -> dict: | ||
tigarmo marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| """Get a nested object using a path (a list of keys).""" | ||
| for key in paths: | ||
| obj = obj.get(key, {}) | ||
| return obj | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1 @@ | ||
| test |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,3 @@ | ||
| module github.com/canonical/NAME | ||
|
|
||
| go 1.22.4 |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,15 @@ | ||
| package main | ||
|
|
||
| import ( | ||
| "fmt" | ||
| "net/http" | ||
| ) | ||
|
|
||
| func hello(w http.ResponseWriter, req *http.Request) { | ||
| fmt.Fprintf(w, "ok") | ||
| } | ||
|
|
||
| func main() { | ||
| http.HandleFunc("/", hello) | ||
| http.ListenAndServe(":8000", nil) | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,46 @@ | ||
| summary: go extension test | ||
| environment: | ||
| SCENARIO/bare: bare | ||
| SCENARIO/base_2404: ubuntu-24.04 | ||
| ROCKCRAFT_ENABLE_EXPERIMENTAL_EXTENSIONS: "true" | ||
|
|
||
| execute: | | ||
| NAME="go-${SCENARIO//./-}" | ||
| ROCK_FILE="${NAME}_0.1_amd64.rock" | ||
| IMAGE="${NAME}:0.1" | ||
|
|
||
| run_rockcraft init --name "${NAME}" --profile go-framework | ||
| sed -i "s/^name: .*/name: ${NAME}/g" rockcraft.yaml | ||
| sed -i "s/^base: .*/base: ${SCENARIO//-/@}/g" rockcraft.yaml | ||
| if [ "${SCENARIO}" != "bare" ]; then | ||
| sed -i "s/^build-base: .*/build-base: ${SCENARIO//-/@}/g" rockcraft.yaml | ||
| fi | ||
| sed -i "s/NAME/${NAME}/g" go.mod | ||
|
|
||
| run_rockcraft pack | ||
|
|
||
| test -f "${ROCK_FILE}" | ||
| test ! -d work | ||
|
|
||
| # Ensure docker does not have this container image | ||
| docker rmi --force "${IMAGE}" | ||
| # Install container | ||
| sudo rockcraft.skopeo --insecure-policy copy "oci-archive:${ROCK_FILE}" "docker-daemon:${IMAGE}" | ||
| # Ensure container exists | ||
| docker images "${IMAGE}" | MATCH "${NAME}" | ||
|
|
||
| # ensure container doesn't exist | ||
| docker rm -f "${NAME}-container" | ||
|
|
||
| # test the default go service | ||
| docker run --name "${NAME}-container" -d -p 8137:8000 "${IMAGE}" | ||
| retry -n 5 --wait 2 curl localhost:8137 | ||
| [ "$(curl -sSf localhost:8137)" == "ok" ] | ||
|
|
||
| restore: | | ||
| NAME="go-${SCENARIO//./-}" | ||
| sed -i "s/${NAME}/NAME/g" go.mod | ||
| docker stop "${NAME}-container" | ||
| docker rm "${NAME}-container" | ||
| rm -f "*.rock" rockcraft.yaml | ||
| docker system prune -a -f |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.