feat: move to image_manifest
diff --git a/atmosphere/cmd/cli.py b/atmosphere/cmd/cli.py
deleted file mode 100644
index 93ef73f..0000000
--- a/atmosphere/cmd/cli.py
+++ /dev/null
@@ -1,65 +0,0 @@
-import shutil
-
-import click
-from oslo_concurrency import processutils
-from oslo_config import cfg
-from oslo_log import log as logging
-
-from atmosphere.operator import constants, utils
-
-CONF = cfg.CONF
-LOG = logging.getLogger(__name__)
-DOMAIN = "atmosphere"
-
-logging.register_options(CONF)
-logging.setup(CONF, DOMAIN)
-
-
-@click.group()
-def main():
- pass
-
-
-@main.group()
-def image():
- pass
-
-
-@image.command()
-@click.argument("destination")
-def mirror(destination):
- crane_path = shutil.which("crane")
-
- if crane_path is None:
- raise click.UsageError(
- "Crane is not installed. Please install it before running this command."
- )
-
- seen = []
- for image in constants.IMAGE_LIST:
- if constants.IMAGE_LIST[image] in seen:
- continue
-
- src = constants.IMAGE_LIST[image]
- dst = utils.get_image_ref(image, override_registry=destination).string()
-
- LOG.debug(f"Starting to mirror {src} to {dst}")
-
- try:
- processutils.execute(
- crane_path,
- "copy",
- src,
- dst,
- )
- except processutils.ProcessExecutionError as e:
- if "401 Unauthorized" in e.stderr:
- LOG.error(
- "Authentication failed. Please ensure you're logged in via Crane"
- )
- return
-
- raise
-
- seen.append(constants.IMAGE_LIST[image])
- LOG.info(f"Successfully mirrored {src} to {dst}")
diff --git a/docs/user-guide/custom-registry.md b/docs/user-guide/custom-registry.md
deleted file mode 100644
index 48d32ac..0000000
--- a/docs/user-guide/custom-registry.md
+++ /dev/null
@@ -1,36 +0,0 @@
-# Custom Registry
-
-You can use your own local registry to store the Atmosphere images. The scope
-of this document does not include how to setup a local registry.
-
-1. Mirror all images to your own registry
-
- ```bash
- atmosphere image mirror 192.168.0.20:5000/atmosphere
- ```
-
-2. Update the Atmosphere image repository variable
-
- ```yaml
- atmosphere_image_repository: 192.168.0.20:5000/atmosphere
- ```
-
-3. If needed, you can also set your registry to be an insecure registry inside
- `containerd`:
-
- ```yaml
- containerd_insecure_registries:
- - 192.168.0.20:5000/atmosphere
- ```
-
-4. You can verify that all your images are running from your local registry by
- running the following command:
-
- ```bash
- kubectl get pods --all-namespaces \
- -o jsonpath="{.items[*].spec.containers[*].image}" | \
- tr -s '[[:space:]]' '\n' | \
- sort | \
- uniq -c | \
- sort -n
- ```
diff --git a/playbooks/image_manifest.yml b/playbooks/image_manifest.yml
new file mode 100644
index 0000000..ce536ab
--- /dev/null
+++ b/playbooks/image_manifest.yml
@@ -0,0 +1,9 @@
+- name: Generate image list
+ hosts: localhost
+ gather_facts: false
+ vars_prompt:
+ - name: image_manifest_path
+ prompt: Image manifest path
+ private: false
+ roles:
+ - image_manifest
diff --git a/plugins/filter/docker_image.py b/plugins/filter/docker_image.py
index c095c83..f291e80 100644
--- a/plugins/filter/docker_image.py
+++ b/plugins/filter/docker_image.py
@@ -47,6 +47,11 @@
required: true
description:
- Part of the Docker image reference to return
+ registry:
+ type: string
+ required: false
+ description:
+ - Override the registry in the Docker image reference
author:
- Mohammed Naser <mnaser@vexxhost.com>
"""
@@ -64,7 +69,7 @@
"""
-def docker_image(value, part="ref"):
+def docker_image(value, part="ref", registry=None):
if not is_string(value):
raise AnsibleFilterError(
"Invalid value type (%s) for docker_image (%r)" % (type(value), value)
@@ -75,12 +80,29 @@
"Failed to import docker-image-py module, ensure it is installed on the controller"
)
- ref = reference.Reference.parse(value)
+ def _lookup_part(ref, part):
+ if part == "ref":
+ return ref.string()
+ if part == "name":
+ return ref["name"]
- if part == "ref":
- return ref.string()
- if part == "name":
- return ref["name"]
+ ref = reference.Reference.parse(value)
+ if not registry:
+ return _lookup_part(ref, part)
+
+ # NOTE(mnaser): We re-write the name of a few images to make sense of them
+ # in the context of the override registry.
+ ref_name = ref.repository["path"].split("/")[-1]
+ if value == "skopeo":
+ ref_name = "skopeo-stable"
+
+ # NOTE(mnaser): Since the attributes inside of reference.Reference are not
+ # determined during parse time, we need to re-parse the
+ # string to get the correct attributes.
+ ref["name"] = "{}/{}".format(registry, ref_name)
+ ref = reference.Reference.parse(ref.string())
+
+ return _lookup_part(ref, part)
class FilterModule(object):
diff --git a/pyproject.toml b/pyproject.toml
index cc7092e..ffd057f 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -6,7 +6,6 @@
readme = "README.md"
[tool.poetry.scripts]
-atmosphere = "atmosphere.cmd.cli:main"
atmosphere-operator = "atmosphere.cmd.operator:main"
[tool.poetry.dependencies]
diff --git a/roles/image_manifest/README.md b/roles/image_manifest/README.md
new file mode 100644
index 0000000..43e8546
--- /dev/null
+++ b/roles/image_manifest/README.md
@@ -0,0 +1,140 @@
+# `image_manifest`
+
+Since Atmosphere uses containers for all of its components, it is important to
+make sure that the images are available to the cluster. By default, all of the
+images are loaded by default using the `vexxhost.atmosphere.defaults` role which
+is a dependency of all of the other roles.
+
+The `defaults` role will load all of the images from the `atmosphere_images`
+variable, which by default will pull all of the different images from the
+internet. You can override this variable to use your own local registry or to
+override a specific image.
+
+Atmosphere can help you to generate this list of images (named "image manifest")
+as well as mirror them to your own registry.
+
+## Using a custom image for a specific container
+
+If you want to use a custom image for a specific container, you will need to
+first generate an image manifest that are used by Atmosphere.
+
+> **Warning**
+>
+> Ansible's default behaviour is not to merge dictionaries, so if you want to
+> override a specific image, you will need to copy the entire image manifest
+> and then override the specific image that you want to change.
+
+You can generate an image manifest for Atmosphere by using the following
+playbook:
+
+```bash
+ansible-playbook vexxhost.atmosphere.image_manifest
+```
+
+The playbook will prompt you for the location of the file that you want to
+write the image manifest to. You can also provide it as a command line
+argument:
+
+```bash
+ansible-playbook vexxhost.atmosphere.image_manifest \
+ -e image_manifest_path=/tmp/atmosphere_images.yml
+```
+
+Once you have the image manifest, you can override the specific image that you
+want to change. Once you're done, you can include this file as part of your
+Atmosphere inventory so that it can be used by the other roles.
+
+If you want to generate an image manifest with a custom registry, you can use
+the `image_manifest_registry` variable:
+
+```bash
+ansible-playbook vexxhost.atmosphere.image_manifest \
+ -e image_manifest_registry=registry.example.com \
+ -e image_manifest_path=/tmp/atmosphere_images.yml
+```
+
+If you want to mirror the images and generate the image manifest at the same
+time, check out the next section.
+
+## Mirroring images to a local registry
+
+If you want to mirror all of the images to a local registry, you can use the
+`vexxhost.atmosphere.mirror_images` playbook. This playbook will mirror all of
+the images to a local registry and then generate an image manifest that can be
+used by the other roles.
+
+This playbook relies on the `crane` tool to mirror the images. You can follow
+the [`crane` installation instructions](https://github.com/google/go-containerregistry/blob/main/cmd/crane/README.md#installation)
+to install it on your Ansible controller.
+
+> **Note**
+>
+> You will need to be authenticated to the target registry using `crane` before
+> you can use the playbook. You can use the `crane auth login` command to
+> authenticate to the registry. For more information, refer to the [`crane` documentation](https://github.com/google/go-containerregistry/blob/main/cmd/crane/doc/crane_auth_login.md)
+
+The playbook will prompt you for the target registry and the location of the
+file that you want to write the image manifest to.
+
+```bash
+ansible-playbook vexxhost.atmosphere.image_manifest \
+ -e image_manifest_mirror=true \
+```
+
+If you want to provide the registry and the path as command line arguments, you
+can use the `image_manifest_registry` and `image_manifest_path` variables:
+
+```bash
+ansible-playbook vexxhost.atmosphere.image_manifest \
+ -e image_manifest_registry=registry.example.com \
+ -e image_manifest_mirror=true \
+ -e image_manifest_path=/tmp/atmosphere_images.yml
+```
+
+Once you have the image manifest, you can include this file as part of your
+Atmosphere inventory so that it can be used by the other roles, as well as
+updated the `atmosphere_image_repository` variable to point to your local
+registry.
+
+> **Note**
+>
+> If needed, you can also set your registry to be insecure inside of the
+> container runtime interface (CRI) by using the `containerd_insecure_registries`
+> variable.
+>
+> ```yaml
+> containerd_insecure_registries:
+> - registry.example.com
+> ```
+
+Once you've deployed Atmosphere, you can use the following command in order to
+validate that all images are being loaded from your local registry:
+
+```bash
+kubectl get pods --all-namespaces \
+ -o jsonpath="{.items[*].spec.containers[*].image}" | \
+ tr -s '[[:space:]]' '\n' | \
+ sort | \
+ uniq -c | \
+ sort -n
+```
+
+This command will list all of the images that are being used by the pods in the
+cluster, which should all be pointing to your local registry.
+
+### Mirroring a single image
+
+If you only want to mirror a single image, you can use the
+`vexxhost.atmosphere.image_manifest` playbook, it has an extra option that can
+be used to specify a single image to mirror.
+
+You can get the image name from the image manifest that is generated by the
+`vexxhost.atmosphere.image_manifest` playbook.
+
+```bash
+ansible-playbook vexxhost.atmosphere.image_manifest \
+ -e image_manifest_registry=registry.example.com \
+ -e image_manifest_mirror=true \
+ -e image_manifest_path=/tmp/atmosphere_images.yml \
+ -e image_manifest_images=keystone_api
+```
diff --git a/roles/image_manifest/defaults/main.yml b/roles/image_manifest/defaults/main.yml
new file mode 100644
index 0000000..9bb6354
--- /dev/null
+++ b/roles/image_manifest/defaults/main.yml
@@ -0,0 +1,11 @@
+image_manifest_force: false
+image_manifest_registry: null
+image_manifest_mirror: false
+image_manifest_images: "{{ atmosphere_images.keys() }}"
+image_manifest_data:
+ atmosphere_images: "{{ dict(
+ atmosphere_images.keys() |
+ zip(
+ atmosphere_images.values() | map('vexxhost.atmosphere.docker_image', 'ref', registry=image_manifest_registry)
+ )
+ ) }}"
diff --git a/roles/image_manifest/meta/main.yml b/roles/image_manifest/meta/main.yml
new file mode 100644
index 0000000..98299f0
--- /dev/null
+++ b/roles/image_manifest/meta/main.yml
@@ -0,0 +1,27 @@
+# Copyright (c) 2022 VEXXHOST, Inc.
+#
+# 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.
+
+galaxy_info:
+ author: VEXXHOST, Inc.
+ description: Ansible role for generating an Atmosphere image manifest
+ license: Apache-2.0
+ min_ansible_version: 5.5.0
+ standalone: false
+ platforms:
+ - name: Ubuntu
+ versions:
+ - focal
+
+dependencies:
+ - role: defaults
diff --git a/roles/image_manifest/tasks/main.yml b/roles/image_manifest/tasks/main.yml
new file mode 100644
index 0000000..843f226
--- /dev/null
+++ b/roles/image_manifest/tasks/main.yml
@@ -0,0 +1,29 @@
+- name: Check if file already exists
+ when: image_manifest_force is false
+ block:
+ - name: Get details about file
+ ansible.builtin.stat:
+ path: "{{ image_manifest_path }}"
+ register: _stat
+
+ - name: Stop if file already exists
+ ansible.builtin.fail:
+ msg: "File {{ image_manifest_path }} already exists, use -e image_manifest_force=true to overwrite"
+ when: _stat.stat.exists
+
+- name: Mirror images
+ when: image_manifest_mirror
+ ansible.builtin.command:
+ crane copy {{ atmosphere_images[item] }} {{ image_manifest_data['atmosphere_images'][item] }}
+ loop: |
+ {%- if image_manifest_images is string -%}
+ {{ image_manifest_images | split(',') }}
+ {%- else -%}
+ {{ image_manifest_images }}
+ {%- endif -%}
+
+- name: Write file with image list
+ ansible.builtin.copy:
+ dest: "{{ image_manifest_path }}"
+ content: "{{ image_manifest_data | to_nice_yaml(indent=2) }}"
+ mode: 0644