]> git-server-git.apps.pok.os.sepia.ceph.com Git - ceph.git/commitdiff
mgr/cephadm: add 'orch upgrade ls' to list available versions
authorSage Weil <sage@newdream.net>
Tue, 12 Oct 2021 19:22:14 +0000 (15:22 -0400)
committerSebastian Wagner <sewagner@redhat.com>
Tue, 2 Nov 2021 09:01:22 +0000 (10:01 +0100)
Optionally pass image name to query an alternative image/registry, or
--tags to list tags instead of ceph versions.

Signed-off-by: Sage Weil <sage@newdream.net>
(cherry picked from commit 7f750fe31aa62f0055e43e86d4461d48baaaa7d3)

src/pybind/mgr/cephadm/module.py
src/pybind/mgr/cephadm/registry.py [new file with mode: 0644]
src/pybind/mgr/cephadm/upgrade.py
src/pybind/mgr/orchestrator/_interface.py
src/pybind/mgr/orchestrator/module.py

index 0d98d89e082bec34fed1f111336b5c0ef0cd67e1..e226500af24d666bebcc9354db85a1a9d4b63ef3 100644 (file)
@@ -2610,6 +2610,10 @@ Then run the following:
     def upgrade_status(self) -> orchestrator.UpgradeStatusSpec:
         return self.upgrade.upgrade_status()
 
+    @handle_orch_error
+    def upgrade_ls(self, image: Optional[str], tags: bool) -> List[str]:
+        return self.upgrade.upgrade_ls(image, tags)
+
     @handle_orch_error
     def upgrade_start(self, image: str, version: str) -> str:
         if self.inventory.get_host_with_state("maintenance"):
diff --git a/src/pybind/mgr/cephadm/registry.py b/src/pybind/mgr/cephadm/registry.py
new file mode 100644 (file)
index 0000000..7b293a4
--- /dev/null
@@ -0,0 +1,61 @@
+import requests
+from typing import List, Dict, Tuple
+from requests import Response
+
+
+class Registry:
+
+    def __init__(self, url: str):
+        self._url: str = url
+
+    @property
+    def api_domain(self) -> str:
+        if self._url == 'docker.io':
+            return 'registry-1.docker.io'
+        return self._url
+
+    def get_token(self, response: Response) -> str:
+        realm, params = self.parse_www_authenticate(response.headers['Www-Authenticate'])
+        r = requests.get(realm, params=params)
+        r.raise_for_status()
+        ret = r.json()
+        if 'access_token' in ret:
+            return ret['access_token']
+        if 'token' in ret:
+            return ret['token']
+        raise ValueError(f'Unknown token reply {ret}')
+
+    def parse_www_authenticate(self, text: str) -> Tuple[str, Dict[str, str]]:
+        # 'Www-Authenticate': 'Bearer realm="https://auth.docker.io/token",service="registry.docker.io",scope="repository:ceph/ceph:pull"'
+        r: Dict[str, str] = {}
+        for token in text.split(','):
+            key, value = token.split('=', 1)
+            r[key] = value.strip('"')
+        realm = r.pop('Bearer realm')
+        return realm, r
+
+    def get_tags(self, image: str) -> List[str]:
+        tags = []
+        headers = {'Accept': 'application/json'}
+        url = f'https://{self.api_domain}/v2/{image}/tags/list'
+        while True:
+            r = requests.get(url, headers=headers)
+            if r.status_code == 401:
+                if 'Authorization' in headers:
+                    raise ValueError('failed authentication')
+                token = self.get_token(r)
+                headers['Authorization'] = f'Bearer {token}'
+                continue
+            r.raise_for_status()
+
+            new_tags = r.json()['tags']
+            tags.extend(new_tags)
+
+            if 'Link' not in r.headers:
+                break
+
+            # strip < > brackets off and prepend the domain
+            url = f'https://{self.api_domain}' + r.headers['Link'].split(';')[0][1:-1]
+            continue
+
+        return tags
index d05459d14090bfdebf2d8a93196af5eda1c1142e..3057307f6e4813676bb04d47384017b92846f0e6 100644 (file)
@@ -5,6 +5,7 @@ import uuid
 from typing import TYPE_CHECKING, Optional, Dict, List, Tuple
 
 import orchestrator
+from cephadm.registry import Registry
 from cephadm.serve import CephadmServe
 from cephadm.services.cephadmservice import CephadmDaemonDeploySpec
 from cephadm.utils import ceph_release_to_major, name_to_config_section, CEPH_UPGRADE_ORDER, MONITORING_STACK_TYPES
@@ -182,6 +183,34 @@ class CephadmUpgrade:
 
         return None
 
+    def upgrade_ls(self, image: Optional[str], tags: bool) -> List[str]:
+        if image:
+            reg_name, image = image.split('/', 1)
+        else:
+            reg_name, image = self.mgr.container_image_base.split('/', 1)
+        self.mgr.log.info(f'reg_name {reg_name} image {image}')
+        reg = Registry(reg_name)
+        versions = []
+        ls = reg.get_tags(image)
+        if not tags:
+            for t in ls:
+                if t[0] != 'v':
+                    continue
+                v = t[1:].split('.')
+                if len(v) != 3:
+                    continue
+                if '-' in v[2]:
+                    continue
+                versions.append('.'.join(v))
+            ls = sorted(
+                versions,
+                key=lambda k: list(map(int, k.split('.'))),
+                reverse=True
+            )
+        else:
+            ls = sorted(ls)
+        return ls
+
     def upgrade_start(self, image: str, version: str) -> str:
         if self.mgr.mode != 'root':
             raise OrchestratorError('upgrade is not supported in %s mode' % (
index a88ab9c71c7b37f95312a45e657d1b0f5aac9770..1770206476485e1d505c83f645a5f56f9efb21f6 100644 (file)
@@ -647,6 +647,9 @@ class Orchestrator(object):
     def upgrade_check(self, image: Optional[str], version: Optional[str]) -> OrchResult[str]:
         raise NotImplementedError()
 
+    def upgrade_ls(self, image: Optional[str], tags: bool) -> OrchResult[List[str]]:
+        raise NotImplementedError()
+
     def upgrade_start(self, image: Optional[str], version: Optional[str]) -> OrchResult[str]:
         raise NotImplementedError()
 
index 0dc21ba58c1348d45092f049afe719879a00554f..e222601865cb226f08feae5964cd7c33ed4bd48a 100644 (file)
@@ -1330,6 +1330,16 @@ Usage:
         raise_if_exception(completion)
         return HandleCommandResult(stdout=completion.result_str())
 
+    @_cli_read_command('orch upgrade ls')
+    def _upgrade_ls(self,
+                    image: Optional[str] = None,
+                    tags: bool = False) -> HandleCommandResult:
+        """Check for available versions (or tags) we can upgrade to"""
+        completion = self.upgrade_ls(image, tags)
+        r = raise_if_exception(completion)
+        out = json.dumps(r, indent=4)
+        return HandleCommandResult(stdout=out)
+
     @_cli_write_command('orch upgrade status')
     def _upgrade_status(self) -> HandleCommandResult:
         """Check service versions vs available and target containers"""