diff --git a/riocli/apply/manifests/project.yaml b/riocli/apply/manifests/project.yaml index 6f148c6e..e3cacac6 100644 --- a/riocli/apply/manifests/project.yaml +++ b/riocli/apply/manifests/project.yaml @@ -4,7 +4,7 @@ metadata: name: test-project-name labels: purpose: testing - version: 1.0 + version: "1.0" spec: users: - emailID: "user1@example.com" @@ -20,4 +20,9 @@ spec: vpn: enabled: true subnets: ["10.81.0.0/16"] - + dockerCache: + enabled: true + proxyDevice: "edge01" + proxyInterface: "eth0" + registrySecret: "quay" + registryURL: "https://quay.io" diff --git a/riocli/jsonschema/schemas/project-schema.yaml b/riocli/jsonschema/schemas/project-schema.yaml index ae521b4f..a1e180ef 100644 --- a/riocli/jsonschema/schemas/project-schema.yaml +++ b/riocli/jsonschema/schemas/project-schema.yaml @@ -82,6 +82,27 @@ definitions: properties: enabled: type: boolean + dockerCache: + oneOf: + - properties: + enabled: + const: false + - properties: + enabled: + const: true + proxyDevice: + type: string + proxyInterface: + type: string + registrySecret: + type: string + registryURL: + type: string + required: + - proxyDevice + - proxyInterface + - registrySecret + - registryURL userGroup: type: object properties: diff --git a/riocli/project/features/__init__.py b/riocli/project/features/__init__.py index ffbdb8dc..8f8ffaf8 100644 --- a/riocli/project/features/__init__.py +++ b/riocli/project/features/__init__.py @@ -15,6 +15,7 @@ from click_help_colors import HelpColorsGroup from riocli.constants import Colors +from riocli.project.features.dockercache import dockercache from riocli.project.features.vpn import vpn @@ -32,3 +33,4 @@ def features(): features.add_command(vpn) +features.add_command(dockercache) diff --git a/riocli/project/features/dockercache.py b/riocli/project/features/dockercache.py new file mode 100644 index 00000000..ed36f406 --- /dev/null +++ b/riocli/project/features/dockercache.py @@ -0,0 +1,110 @@ +# Copyright 2024 Rapyuta Robotics +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +import click +from click_help_colors import HelpColorsCommand + +from riocli.config import new_v2_client +from riocli.constants import Colors, Symbols +from riocli.project.util import name_to_guid +from riocli.utils.spinner import with_spinner + + +@click.command( + "dockercache", + cls=HelpColorsCommand, + help_headers_color=Colors.YELLOW, + help_options_color=Colors.GREEN, +) +@click.argument("project-name", type=str) +@click.argument("enable", type=bool) +@click.option( + "--proxy-device", + type=click.STRING, + default=(), + help="Name of the device for docker-cache proxy.", +) +@click.option( + "--proxy-interface", + type=click.STRING, + default=(), + help="Name of the network interface for docker-cache proxy.", +) +@click.option( + "--registry-url", + type=click.STRING, + default=(), + help="URL for the upstream docker registry.", +) +@click.option( + "--registry-secret", + type=click.STRING, + default=(), + help="Name of the secret for upstream docker registry", +) +@name_to_guid +@with_spinner(text="Updating DockerCache state...") +def dockercache( + project_name: str, + project_guid: str, + enable: bool, + proxy_device: str, + proxy_interface: str, + registry_url: str, + registry_secret: str, + spinner=None, +) -> None: + """ + Enable or disable DockerCache on a project + + Example: + + \b + rio project features dockercache "my-project" true \\ + --proxy-device edge01 \\ + --proxy-interface eth0 \\ + --registry-url https://quay.io \\ + --registry-secret quay + """ + client = new_v2_client(with_project=False) + + try: + project = client.get_project(project_guid) + except Exception as e: + spinner.text = click.style("Failed: {}".format(e), fg=Colors.RED) + spinner.red.fail(Symbols.ERROR) + raise SystemExit(1) from e + + project["spec"]["features"]["dockerCache"] = { + "enabled": enable, + "proxyDevice": proxy_device, + "proxyInterface": proxy_interface, + "registryURL": registry_url, + "registrySecret": registry_secret, + } + + is_enabled = project["spec"]["features"]["dockerCache"].get("enabled", False) + + status = "Enabling DockerCache..." if enable else "Disabling DockerCache..." + if is_enabled and enable: + status = "Updating DockerCache..." + spinner.text = status + + try: + client.update_project(project_guid, project) + spinner.text = click.style("Done", fg=Colors.GREEN) + spinner.green.ok(Symbols.SUCCESS) + except Exception as e: + spinner.text = click.style("Failed: {}".format(e), fg=Colors.RED) + spinner.red.fail(Symbols.ERROR) + raise SystemExit(1) from e diff --git a/riocli/project/model.py b/riocli/project/model.py index 575f1ae7..0758a504 100644 --- a/riocli/project/model.py +++ b/riocli/project/model.py @@ -20,7 +20,7 @@ from riocli.exceptions import ResourceNotFound from riocli.model import Model from riocli.project.util import ProjectNotFound, find_project_guid -from riocli.v2client.error import HttpAlreadyExistsError, HttpNotFoundError +from riocli.v2client.error import HttpNotFoundError PROJECT_READY_TIMEOUT = 150 @@ -39,19 +39,24 @@ def apply(self, *args, **kwargs) -> ApplyResult: project["metadata"]["organizationGUID"] = Configuration().organization_guid try: - client.create_project(project) + # We try to update before creating in Project. The DockerCache + # feature is only available in the Update API. If we instead try to + # create the Project with DockerCache feature enabled then the API + # will return BadRequest error. + guid = find_project_guid( + client, self.metadata.name, Configuration().organization_guid + ) + + client.update_project(guid, project) wait( self.is_ready, timeout_seconds=PROJECT_READY_TIMEOUT, sleep_seconds=(1, 30, 2), ) - return ApplyResult.CREATED - except HttpAlreadyExistsError: - guid = find_project_guid( - client, self.metadata.name, Configuration().organization_guid - ) - client.update_project(guid, project) return ApplyResult.UPDATED + except (HttpNotFoundError, ProjectNotFound): + client.create_project(project) + return ApplyResult.CREATED except Exception as e: raise e