-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
First component of github-profiles-automator charm (#18)
* Update requirements * Initialize charm code * Add initial tests * Change default config value for sync-period * Remove uneccessary comments * Update git-sync image * Add rust packages * Wait until blocked in integration test * Update git-sync-image in integration test * Resolve comments * Add docstrings * Update docstrings * Fix docstring in charm.py * Add git-revision config * Fix linting * Update health check * Update docstring
- Loading branch information
Showing
9 changed files
with
616 additions
and
42 deletions.
There are no files selected for viewing
This file contains 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 |
---|---|---|
@@ -1,21 +1,70 @@ | ||
name: github-profiles-automator | ||
type: charm | ||
title: GitHub Profiles Automator | ||
title: GitHub Profiles Automator charm | ||
|
||
summary: A charm for automating the management of Kubeflow Profiles from a GitHub repo | ||
summary: A charm for automating the management of Kubeflow Profiles from a GitHub repository | ||
|
||
description: | | ||
A charm to automatically sync Kubeflow Profiles from information a GitHub repository. | ||
This charm is responsible for monitoring a file from a GitHub repo that represents | ||
which Profiles and Contributors should exist in a cluster. Then, via a reconciliation loop | ||
the charm will update the Profiles and RoleBindings and AuthorizationPolicies in the | ||
cluster to align with this representation. | ||
It is useful for cluster administrators who want to automatically update | ||
the profiles on the cluster, based on a single source of truth. | ||
base: [email protected] | ||
platforms: | ||
amd64: | ||
|
||
config: | ||
options: | ||
repository: | ||
default: "" | ||
description: | | ||
The URL of the repository to fetch. Must be configured for the charm to | ||
operate. | ||
type: string | ||
git-revision: | ||
default: "HEAD" | ||
description: | | ||
The git revision to check out. | ||
type: string | ||
sync-period: | ||
default: 60 | ||
description: | | ||
How long to wait between sync attempts. | ||
type: int | ||
pmr-yaml-path: | ||
default: "pmr.yaml" | ||
description: | | ||
The relative path to the .yaml file inside the GitHub repository that | ||
contains the PMR information | ||
type: string | ||
ssh-key-secret-id: | ||
type: secret | ||
description: | | ||
A configuration option to store the secret ID needed to access the SSH key for the GitHub | ||
repository | ||
containers: | ||
git-sync: | ||
resource: git-sync-image | ||
mounts: | ||
- storage: content-from-git | ||
location: /git | ||
|
||
resources: | ||
git-sync-image: | ||
type: oci-image | ||
description: OCI image for the 'git-sync' container | ||
upstream-source: registry.k8s.io/git-sync/git-sync:v4.4.0 | ||
|
||
storage: | ||
content-from-git: | ||
type: filesystem | ||
|
||
parts: | ||
charm: | ||
|
@@ -25,9 +74,9 @@ parts: | |
build-snaps: | ||
- rustup | ||
build-packages: | ||
- pkg-config | ||
- libffi-dev | ||
- libssl-dev | ||
- pkg-config | ||
override-build: | | ||
rustup default stable | ||
craftctl default |
Large diffs are not rendered by default.
Oops, something went wrong.
This file contains 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 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 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 |
---|---|---|
|
@@ -11,25 +11,174 @@ | |
import logging | ||
|
||
import ops | ||
from ops.charm import InstallEvent | ||
from charmed_kubeflow_chisme.components import ContainerFileTemplate, LazyContainerFileTemplate | ||
from charmed_kubeflow_chisme.components.charm_reconciler import CharmReconciler | ||
from charmed_kubeflow_chisme.components.leadership_gate_component import LeadershipGateComponent | ||
from charmed_kubeflow_chisme.exceptions import ErrorWithStatus | ||
|
||
# Log messages can be retrieved using juju debug-log | ||
logger = logging.getLogger(__name__) | ||
from components.pebble_component import ( | ||
GitSyncInputs, | ||
GitSyncPebbleService, | ||
RepositoryType, | ||
) | ||
|
||
SSH_KEY_DESTINATION_PATH = "/etc/git-secret/ssh" | ||
SSH_KEY_PERMISSIONS = 0o400 | ||
EXECHOOK_SCRIPT_DESTINATION_PATH = "/git-sync-exechook.sh" | ||
EXECHOOK_SCRIPT_PERMISSIONS = 0o555 | ||
|
||
VALID_LOG_LEVELS = ["info", "debug", "warning", "error", "critical"] | ||
logger = logging.getLogger(__name__) | ||
|
||
|
||
class GithubProfilesAutomatorCharm(ops.CharmBase): | ||
"""Charm the service.""" | ||
"""A Juju charm for the GitHub Profiles Automator.""" | ||
|
||
def __init__(self, framework: ops.Framework): | ||
"""Initialize charm and setup the container.""" | ||
super().__init__(framework) | ||
self.pebble_service_name = "git-sync" | ||
self.container = self.unit.get_container("git-sync") | ||
|
||
self.files_to_push = [] | ||
|
||
try: | ||
self._validate_repository_config() | ||
except ErrorWithStatus as e: | ||
self.unit.status = e.status | ||
return | ||
|
||
self.charm_reconciler = CharmReconciler(self) | ||
|
||
self.leadership_gate = self.charm_reconciler.add( | ||
component=LeadershipGateComponent( | ||
charm=self, | ||
name="leadership-gate", | ||
), | ||
depends_on=[], | ||
) | ||
|
||
# Push the exechook script to the workload container | ||
self.files_to_push.append( | ||
ContainerFileTemplate( | ||
source_template_path="./src/components/git-sync-exechook.sh", | ||
destination_path=EXECHOOK_SCRIPT_DESTINATION_PATH, | ||
permissions=EXECHOOK_SCRIPT_PERMISSIONS, | ||
) | ||
) | ||
|
||
self.pebble_service_container = self.charm_reconciler.add( | ||
component=GitSyncPebbleService( | ||
charm=self, | ||
name="git-sync-pebble-service", | ||
container_name="git-sync", | ||
service_name=self.pebble_service_name, | ||
files_to_push=self.files_to_push, | ||
inputs_getter=lambda: GitSyncInputs( | ||
GIT_REVISION=str(self.config["git-revision"]), | ||
REPOSITORY=str(self.config["repository"]), | ||
REPOSITORY_TYPE=self.repository_type, | ||
SYNC_PERIOD=int(self.config["sync-period"]), | ||
), | ||
), | ||
depends_on=[self.leadership_gate], | ||
) | ||
|
||
self.charm_reconciler.install_default_event_handlers() | ||
|
||
@property | ||
def ssh_key(self) -> str | None: | ||
"""Retrieve the SSH key value from the Juju secrets, using the ssh-key-secret-id config. | ||
Returns: | ||
The SSH key as a string, or None if the Juju secret doesn't exist or the config | ||
hasn't been set. | ||
Raises: | ||
ErrorWithStatus: If the SSH key cannot be retrieved due to missing configuration or | ||
errors. | ||
""" | ||
ssh_key_secret_id = str(self.config.get("ssh-key-secret-id")) | ||
try: | ||
ssh_key_secret = self.model.get_secret(id=ssh_key_secret_id) | ||
ssh_key = str(ssh_key_secret.get_content(refresh=True)["ssh-key"]) | ||
# SSH key requires a newline at the end, so ensure it has one | ||
ssh_key += "\n\n" | ||
return ssh_key | ||
except (ops.SecretNotFoundError, ops.model.ModelError): | ||
logger.warning("The SSH key does not exist") | ||
return None | ||
|
||
def _validate_repository_config(self): | ||
"""Parse a repository string and raise appropriate errors. | ||
Raises: | ||
ErrorWithStatus: If the config `repository` is empty, an invalid GitHub URL, or | ||
there is a missing SSH key when needed. | ||
""" | ||
if self.config["repository"] == "": | ||
logger.warning("Charm is Blocked due to empty value of `repository`") | ||
raise ErrorWithStatus("Config `repository` cannot be empty.", ops.BlockedStatus) | ||
|
||
if is_ssh_url(str(self.config["repository"])): | ||
self.repository_type = RepositoryType.SSH | ||
if not self.ssh_key: | ||
raise ErrorWithStatus( | ||
"To connect via an SSH URL you need to provide an SSH key.", | ||
ops.BlockedStatus, | ||
) | ||
# If there is an SSH key, we push it to the workload container | ||
self.files_to_push.append( | ||
LazyContainerFileTemplate( | ||
source_template=self.ssh_key, | ||
destination_path=SSH_KEY_DESTINATION_PATH, | ||
permissions=SSH_KEY_PERMISSIONS, | ||
) | ||
) | ||
return | ||
|
||
self.repository_type = RepositoryType.HTTPS | ||
if not is_https_url(str(self.config["repository"])): | ||
logger.warning("Charm is Blocked due to incorrect value of `repository`") | ||
raise ErrorWithStatus( | ||
"Config `repository` isn't a valid GitHub URL.", ops.BlockedStatus | ||
) | ||
|
||
|
||
def is_https_url(url: str) -> bool: | ||
"""Check if a given string is a valid HTTPS URL for a GitHub repo. | ||
Args: | ||
url: The URL to check. | ||
Returns: | ||
True if the string is valid HTTPS URL for a GitHub repo, False otherwise. | ||
""" | ||
# Check if the URL starts with 'https://github.com' | ||
if not url.startswith("https://github.com/"): | ||
return False | ||
# Check if the URL ends with '.git' | ||
if not url.endswith(".git"): | ||
return False | ||
return True | ||
|
||
|
||
def is_ssh_url(url: str) -> bool: | ||
"""Check if a given string is a valid SSH URL for a GitHub repo. | ||
self.framework.observe(self.on.install, self._on_install) | ||
Args: | ||
url: The URL to check. | ||
def _on_install(self, _: InstallEvent): | ||
self.unit.status = ops.ActiveStatus("hello friend") | ||
Returns: | ||
True if the string is valid SSH URL for a GitHub repo, False otherwise. | ||
""" | ||
if not url.startswith("[email protected]:"): | ||
return False | ||
# Get the part after [email protected] | ||
path = url.split(":", 1)[-1] | ||
if "/" not in path: | ||
return False | ||
return True | ||
|
||
|
||
if __name__ == "__main__": # pragma: nocover | ||
ops.main(GithubProfilesAutomatorCharm) # type: ignore | ||
if __name__ == "__main__": | ||
ops.main(GithubProfilesAutomatorCharm) |
This file contains 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,8 @@ | ||
#!/bin/sh | ||
# This script will be called by `git-sync` whenever it receives new information | ||
# from the repository. | ||
# See https://github.com/kubernetes/git-sync/blob/69eb59185a073d4a08362d07bbe6459311027746/_test_tools/exechook_command_with_sleep.sh | ||
|
||
set -xe | ||
|
||
/charm/bin/pebble notify github-profiles-automator.com/sync |
Oops, something went wrong.