Skip to content
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

Replace snap channel config for hard coded revision config #79

Merged
merged 4 commits into from
Apr 24, 2024

Conversation

addyess
Copy link
Contributor

@addyess addyess commented Apr 23, 2024

Applicable spec:

Overview

Rather than encode the snap revisions into the charm code directly, include a file installed by the charm to specify the revision or channel of the snap. Each charm branch may adjust this file's contents in order to either install by channel or by revision.

Rationale

  • The same snap-installation.yaml will be available for both the built k8s and k8s-worker charm.
  • the revision file is arch aware to support different snap revisions based on the architecture for which its built
  • later CI actions can be used to automate the update of the revisions
  • the charm can be influenced via adjustments to this file to allow for arbitrary snap revision installation

Checklist

Copy link
Contributor

@github-actions github-actions bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

license-eye has checked 91 files.

Valid Invalid Ignored Fixed
31 1 59 0
Click to see the invalid file list
  • charms/worker/k8s/templates/snap_installation.yaml
Use this command to fix any missing license headers
```bash

docker run -it --rm -v $(pwd):/github/workspace apache/skywalking-eyes header fix

</details>

@addyess addyess changed the title replace snap channel config for hard coded revision config [WIP] replace snap channel config for hard coded revision config Apr 24, 2024
@addyess addyess marked this pull request as ready for review April 24, 2024 13:58
@addyess addyess requested a review from a team as a code owner April 24, 2024 13:58
@addyess addyess added the enhancement New feature or request label Apr 24, 2024
@addyess addyess changed the title [WIP] replace snap channel config for hard coded revision config Replace snap channel config for hard coded revision config Apr 24, 2024
Comment on lines -45 to -48
channel:
default: edge
type: string
description: Snap channel of the k8s snap
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nope, not going to use snap channels anymore

Comment on lines -69 to +68
rm -rf $CRAFT_PRIME/lib
rm -rf $CRAFT_PRIME/lib $CRAFT_PRIME/templates
mv $CRAFT_PRIME/k8s/lib $CRAFT_PRIME/lib
mv $CRAFT_PRIME/k8s/templates $CRAFT_PRIME/templates
Copy link
Contributor Author

@addyess addyess Apr 24, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This moves the templates folder from ./k8s/templates to ./templates once installed on the k8s-worker units, along with the ./lib folder

Comment on lines -54 to -57
channel:
default: edge
type: string
description: Snap channel of the k8s snap
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nope, revisions are hard coded

Comment on lines -201 to +205
@on_error(ops.BlockedStatus("Failed to install k8s snap."), SnapError)
def _install_k8s_snap(self):
"""Install the k8s snap package."""
@on_error(ops.BlockedStatus("Failed to install snaps."), snap_lib.SnapError)
def _install_snaps(self):
"""Install snap packages."""
status.add(ops.MaintenanceStatus("Ensuring snap installation"))
log.info("Ensuring k8s snap version")
snap_ensure("k8s", SnapState.Latest.value, self.config["channel"])
snap_management()
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

let's move all the logic about managing the snap revision to another module

Comment on lines +25 to +42
class SnapFileArgument(BaseModel):
"""Structure to install a snap by file.

Attributes:
install_type (str): literal string defining this type
name (str): The name of the snap after installed
filename (Path): Path to the snap to locally install
classic (bool): If it should be installed as a classic snap
dangerous (bool): If it should be installed as a dangerouse snap
devmode (bool): If it should be installed as with dev mode enabled
"""

install_type: Literal["file"] = Field("file", alias="install-type", exclude=True)
name: str = Field(exclude=True)
filename: Optional[Path] = None
classic: Optional[bool] = None
devmode: Optional[bool] = None
dangerous: Optional[bool] = None
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This looks like all the arguments in snap_lib.install_local(...) All the names and type mimic those arguments intentionally.

two arguments name and install_type do not exist as arguments to that method -- so they are marked with exclude=True. When this.dict(...) is called -- those arguments won't show up in the call

Comment on lines +45 to +66
class SnapStoreArgument(BaseModel):
"""Structure to install a snap by snapstore.

Attributes:
install_type (str): literal string defining this type
name (str): The type of the request.
state (SnapState): a `SnapState` to reconcile to.
classic (bool): If it should be installed as a classic snap
devmode (bool): If it should be installed as with dev mode enabled
channel (bool): the channel to install from
cohort (str): the key of a cohort that this snap belongs to
revision (int): the revision of the snap to install
"""

install_type: Literal["store"] = Field("store", alias="install-type", exclude=True)
name: str = Field(exclude=True)
classic: Optional[bool] = None
devmode: Optional[bool] = None
state: Optional[snap_lib.SnapState] = Field(snap_lib.SnapState.Present)
channel: Optional[str] = None
cohort: Optional[str] = None
revision: Optional[int] = None
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This looks like all the arguments in snap_lib.Snap.ensure(...) All the names and types mimic those arguments intentionally.

Again, the two arguments name and install_type do not exist as arguments to that method -- so they are marked with exclude=True. When this.dict(...) is called -- those arguments won't show up in the call

Comment on lines +69 to +71
SnapArgument = Annotated[
Union[SnapFileArgument, SnapStoreArgument], Field(discriminator="install_type")
]
Copy link
Contributor Author

@addyess addyess Apr 24, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is pydantic's fancy way of creating a type which can parse data as either SnapFileArgument or SnapStoreArgument

the Field(discriminator="install_type") indicates to the parser how to determine which type of object it should be parsed as.

Comment on lines +92 to +93
dpkg_arch = ["dpkg", "--print-architecture"]
arch = subprocess.check_output(dpkg_arch).decode("UTF-8").strip()
Copy link
Contributor Author

@addyess addyess Apr 24, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

the snap_installation.yaml will have sections based on architecture. each architecture can specify a list of snaps to install (not just one). This lets us have the flexibility to add multiple snaps in the future if necessary. Maybe we want to add kubelet with its own set of args. Or maybe by default install k9s or ponysay.

raise snap_lib.SnapError(f"Failed to find revision for arch={arch}")

try:
args: List[SnapArgument] = [parse_obj_as(SnapArgument, arg) for arg in arch_spec] # type: ignore[arg-type]
Copy link
Contributor Author

@addyess addyess Apr 24, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

so, for every arg in the architecture specification (arch_spec) we want to parse the dict object into either a SnapFileArgument or a SnapStoreArgument.

Since we made that nice annotated type we can use parse_obj_as(...) asking pydantic to figure out which object type it is.

Comment on lines +111 to +117
for args in _parse_management_arguments():
which = cache[args.name]
if isinstance(args, SnapFileArgument) and which.revision != "x1":
snap_lib.install_local(**args.dict(exclude_none=True))
elif isinstance(args, SnapStoreArgument):
log.info("Ensuring %s snap version", args.name)
which.ensure(**args.dict(exclude_none=True))
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This management() will get called on every reconcile hook. We need to parse the snap_installation.yaml, determine the args, and either locally install or run ensure with the given arguments. Pydantic has validated the arguments match what's in snap_lib, so we can just turn the args back into a dict, and call the methods with keyword arguments.

We don't want to keep installing the local file over and over and over, -- so this checks for the revision x1 but that might not be the cleanest way. This would limit the number of times you can do a install_local to just one revision. Maybe there's a TODO needed here

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Definitely, let's add a TODO here

Comment on lines +5 to +11
- name: k8s
install-type: store
channel: edge
arm64:
- name: k8s
install-type: store
channel: edge
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

by default, the main branch will continue installing from the edge channel. Other branches will use specific snap revisions

Copy link
Member

@mateoflorido mateoflorido left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Amazing work! LGTM

Comment on lines +69 to +70
SnapArgument = Annotated[
Union[SnapFileArgument, SnapStoreArgument], Field(discriminator="install_type")
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice! I love the discriminator logic here.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this is what we have to work with in pydantic 1.x. This has been changed in pydantic 2.x a bit
https://docs.pydantic.dev/latest/migration/#introduction-of-typeadapter

@addyess addyess merged commit 81caac3 into main Apr 24, 2024
34 checks passed
@addyess addyess deleted the KU-698/encode-snap-revisions branch April 24, 2024 15:41
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
enhancement New feature or request
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants