diff --git a/.github/workflows/test-microk8s.yaml b/.github/workflows/test-microk8s.yaml index 5165bc2..e7d0931 100644 --- a/.github/workflows/test-microk8s.yaml +++ b/.github/workflows/test-microk8s.yaml @@ -22,7 +22,7 @@ jobs: run: black --check . - name: Check flake8 - run: flake8 . + run: flake8 . --max-line-length 100 test: name: Test @@ -39,7 +39,7 @@ jobs: - name: Install dependencies run: | set -eux - sudo snap install charm --classic + sudo pip3 install charmcraft sudo snap install juju --classic sudo snap install juju-wait --classic @@ -52,10 +52,8 @@ jobs: - name: Deploy MinIO run: | set -eux - charm build . - juju deploy /tmp/charm-builds/minio \ - --config secret-key=minio-secret-key \ - --resource oci-image=minio/minio:RELEASE.2018-02-09T22-40-05Z + charmcraft build + juju deploy ./minio.charm --resource oci-image=minio/minio:RELEASE.2021-02-07T01-31-02Z juju wait -wvt 300 - name: Test MinIO @@ -71,7 +69,7 @@ jobs: "mc alias set ci http://minio.minio.svc.cluster.local:9000 minio minio-secret-key && mc mb ci/foo && mc rb ci/foo" - - name: Get pod statuses + - name: Get all run: kubectl get all -A if: failure() diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..644cf3a --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +*.charm +build/ diff --git a/config.yaml b/config.yaml index d2e170a..526e8df 100644 --- a/config.yaml +++ b/config.yaml @@ -7,7 +7,3 @@ options: type: string default: 'minio' description: Access key - secret-key: - type: string - default: ~ - description: Secret key diff --git a/layer.yaml b/layer.yaml deleted file mode 100644 index 31ae291..0000000 --- a/layer.yaml +++ /dev/null @@ -1,6 +0,0 @@ -repo: https://github.com/juju-solutions/bundle-kubeflow.git -includes: - - "layer:caas-base" - - "layer:status" - - "layer:docker-resource" - - "interface:generic-ip-port-user-pass" diff --git a/metadata.yaml b/metadata.yaml index 57d4125..49ae16e 100755 --- a/metadata.yaml +++ b/metadata.yaml @@ -17,10 +17,10 @@ resources: type: oci-image description: Backing OCI image auto-fetch: true - upstream-source: minio/minio:RELEASE.2018-02-09T22-40-05Z + upstream-source: minio/minio:RELEASE.2021-02-07T01-31-02Z provides: minio: - interface: generic-ip-port-user-pass + interface: minio storage: minio-data: type: filesystem diff --git a/reactive/minio.py b/reactive/minio.py deleted file mode 100644 index e0fdbd2..0000000 --- a/reactive/minio.py +++ /dev/null @@ -1,94 +0,0 @@ -from charms import layer -from charms.reactive import ( - hook, - set_flag, - clear_flag, - when, - when_any, - when_not, - hookenv, -) -from pathlib import Path -from string import ascii_uppercase, digits -from random import choices - - -@hook("upgrade-charm") -def upgrade_charm(): - clear_flag("charm.started") - - -@when("charm.started") -def charm_ready(): - layer.status.active("") - - -@when("endpoint.minio.joined") -def configure_minio(gipup): - if not Path("/run/password").exists(): - Path("/run/password").write_text( - "".join(choices(ascii_uppercase + digits, k=30)) - ) - - gipup.publish_info( - port=hookenv.config("port"), - ip=hookenv.application_name(), - user=hookenv.config("access-key"), - password=Path("/run/password").read_text(), - ) - - -@when_any("layer.docker-resource.oci-image.changed", "config.changed") -def update_image(): - clear_flag("charm.started") - - -@when("layer.docker-resource.oci-image.available") -@when_not("charm.started") -def start_charm(): - if not hookenv.is_leader(): - hookenv.log("This unit is not a leader.") - return False - - layer.status.maintenance("configuring container") - - image_info = layer.docker_resource.get_info("oci-image") - config = dict(hookenv.config()) - - if not Path("/run/password").exists(): - if config.get("secret-key"): - secret_key = config["secret-key"] - else: - secret_key = "".join(choices(ascii_uppercase + digits, k=30)) - - Path("/run/password").write_text(secret_key) - - layer.caas_base.pod_spec_set( - { - "version": 3, - "containers": [ - { - "name": "minio", - "args": ["server", "/data"], - "imageDetails": { - "imagePath": image_info.registry_path, - "username": image_info.username, - "password": image_info.password, - }, - "ports": [ - { - "name": "minio", - "containerPort": config["port"], - } - ], - "envConfig": { - "MINIO_ACCESS_KEY": config["access-key"], - "MINIO_SECRET_KEY": Path("/run/password").read_text(), - }, - } - ], - } - ) - - layer.status.maintenance("creating container") - set_flag("charm.started") diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..740d937 --- /dev/null +++ b/requirements.txt @@ -0,0 +1,2 @@ +ops==1.1.0 +git+https://github.com/juju-solutions/resource-oci-image@1964d748022b762b9dce6e8bb7bdf12835102c72 diff --git a/src/charm.py b/src/charm.py new file mode 100755 index 0000000..669f30b --- /dev/null +++ b/src/charm.py @@ -0,0 +1,89 @@ +#!/usr/bin/env python3 + +import logging +from pathlib import Path +from random import choices +from string import ascii_uppercase, digits +from typing import Union, Callable +from ops.charm import CharmBase +from ops.main import main +from ops.model import ActiveStatus, MaintenanceStatus + +from oci_image import OCIImageResource, OCIImageResourceError + +log = logging.getLogger() + + +def get_or_set(name: str, *, default: Union[str, Callable[[], str]]) -> str: + try: + path = Path(f"/run/{name}") + return path.read_text() + except FileNotFoundError: + value = default() if callable(default) else default + path.write_text(value) + return value + + +def gen_pass() -> str: + return "".join(choices(ascii_uppercase + digits, k=30)) + + +class MinioCharm(CharmBase): + def __init__(self, *args): + super().__init__(*args) + self.image = OCIImageResource(self, "oci-image") + self.framework.observe(self.on.install, self.set_pod_spec) + self.framework.observe(self.on.upgrade_charm, self.set_pod_spec) + self.framework.observe(self.on.config_changed, self.set_pod_spec) + self.framework.observe(self.on.minio_relation_joined, self.send_info) + + def send_info(self, event): + secret_key = get_or_set("password", default=gen_pass) + event.relation.data[self.unit]["service"] = self.model.app.name + event.relation.data[self.unit]["port"] = str(self.model.config["port"]) + event.relation.data[self.unit]["access-key"] = self.model.config["access-key"] + event.relation.data[self.unit]["secret-key"] = secret_key + + def set_pod_spec(self, event): + if not self.model.unit.is_leader(): + log.info("Not a leader, skipping set_pod_spec") + self.model.unit.status = ActiveStatus() + return + + try: + image_details = self.image.fetch() + except OCIImageResourceError as e: + self.model.unit.status = e.status + log.info(e) + return + + secret_key = get_or_set("password", default=gen_pass) + + self.model.unit.status = MaintenanceStatus("Setting pod spec") + self.model.pod.set_spec( + { + "version": 3, + "containers": [ + { + "name": "minio", + "args": ["server", "/data"], + "imageDetails": image_details, + "ports": [ + { + "name": "minio", + "containerPort": self.model.config["port"], + } + ], + "envConfig": { + "MINIO_ACCESS_KEY": self.model.config["access-key"], + "MINIO_SECRET_KEY": secret_key, + }, + } + ], + } + ) + self.model.unit.status = ActiveStatus() + + +if __name__ == "__main__": + main(MinioCharm)