Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Load and display image tags for neuro images #873

Merged
merged 12 commits into from
Jul 8, 2019
34 changes: 32 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
* [neuro image ls](#neuro-image-ls)
* [neuro image push](#neuro-image-push)
* [neuro image pull](#neuro-image-pull)
* [neuro image tags](#neuro-image-tags)
* [neuro config](#neuro-config)
* [neuro config login](#neuro-config-login)
* [neuro config login-with-token](#neuro-config-login-with-token)
Expand Down Expand Up @@ -649,6 +650,7 @@ Name | Description|
| _[neuro image ls](#neuro-image-ls)_| List images |
| _[neuro image push](#neuro-image-push)_| Push an image to platform registry |
| _[neuro image pull](#neuro-image-pull)_| Pull an image from platform registry |
| _[neuro image tags](#neuro-image-tags)_| List tags for image in platform registry |



Expand All @@ -674,7 +676,7 @@ Name | Description|

### neuro image push

Push an image to platform registry.<br/><br/>Remote image must be URL with image:// scheme. Image names can contains tag.<br/>If tags not specified 'latest' will be used as value.<br/>
Push an image to platform registry.<br/><br/>Remote image must be URL with image:// scheme. Image names can contain tag.<br/>If tags not specified 'latest' will be used as value.<br/>

**Usage:**

Expand Down Expand Up @@ -732,6 +734,34 @@ Name | Description|



### neuro image tags

List tags for image in platform registry.<br/><br/>Image name must be URL with image:// scheme.<br/>

**Usage:**

```bash
neuro image tags [OPTIONS] IMAGE
```

**Examples:**

```bash

neuro image tags image://myfriend/alpine
neuro image tags image:myimage

```

**Options:**

Name | Description|
|----|------------|
|_--help_|Show this message and exit.|




## neuro config

Client configuration.
Expand Down Expand Up @@ -1564,7 +1594,7 @@ Name | Description|

## neuro push

Push an image to platform registry.<br/><br/>Remote image must be URL with image:// scheme. Image names can contains tag.<br/>If tags not specified 'latest' will be used as value.<br/>
Push an image to platform registry.<br/><br/>Remote image must be URL with image:// scheme. Image names can contain tag.<br/>If tags not specified 'latest' will be used as value.<br/>

**Usage:**

Expand Down
23 changes: 18 additions & 5 deletions neuromation/api/images.py
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,12 @@ def as_local_str(self) -> str:
post = f":{self.tag}" if self.tag else ""
return self.name + post

def as_api_str(self) -> str:
if self.owner:
return f"{self.owner}/{self.name}"
else:
return self.name


class Images(metaclass=NoPublicConstructor):
def __init__(self, core: _Core, config: _Config) -> None:
Expand Down Expand Up @@ -175,11 +181,18 @@ async def pull(
async def ls(self) -> List[URL]:
async with self._registry.request("GET", URL("_catalog")) as resp:
ret = await resp.json()
result = []
for name in ret["repositories"]:
if name.startswith("image://"):
url = URL(name)
prefix = "image://"
result: List[URL] = []
for repo in ret["repositories"]:
if repo.startswith(prefix):
url = URL(repo)
else:
url = URL(f"image://{name}")
url = URL(f"{prefix}{repo}")
result.append(url)
return result

async def tags(self, image: DockerImage) -> List[str]:
name = image.as_api_str()
async with self._registry.request("GET", URL(f"{name}/tags/list")) as resp:
ret = await resp.json()
return ret.get("tags", [])
27 changes: 24 additions & 3 deletions neuromation/cli/image.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,11 @@

import click

from neuromation.api import DockerImageOperation, ImageNameParser
from neuromation.api import DockerImage, DockerImageOperation, ImageNameParser
from neuromation.cli.formatters import DockerImageProgress

from .root import Root
from .utils import async_cmd, command, deprecated_quiet_option, group
from .utils import ImageType, async_cmd, command, deprecated_quiet_option, group


log = logging.getLogger(__name__)
Expand All @@ -29,7 +29,7 @@ async def push(root: Root, image_name: str, remote_image_name: str) -> None:
Push an image to platform registry.

Remote image must be URL with image:// scheme.
Image names can contains tag. If tags not specified 'latest' will
Image names can contain tag. If tags not specified 'latest' will
be used as value.

Examples:
Expand Down Expand Up @@ -117,6 +117,27 @@ async def ls(root: Root) -> None:
click.echo(image)


@command()
@click.argument("image", type=ImageType())
@async_cmd()
async def tags(root: Root, image: DockerImage) -> None:
"""
List tags for image in platform registry.

Image name must be URL with image:// scheme.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is it needed to add support for relative URIs like image:myimage?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

please use ImageNameParser to parse image: str to image: DockerImage

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think, CLI methods for all image operations must use the same image URI parser -- this is the only way not to confuse the users

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There is a difference. Other operations accept image URI with tag, but this operation should only accept image URI without tag.


Examples:

neuro image tags image://myfriend/alpine
neuro image tags image:myimage
"""

tags = await root.client.images.tags(image)
for tag in tags:
click.echo(tag)


image.add_command(ls)
image.add_command(push)
image.add_command(pull)
image.add_command(tags)
4 changes: 4 additions & 0 deletions tests/api/test_images.py
Original file line number Diff line number Diff line change
Expand Up @@ -520,6 +520,7 @@ def test_as_str_in_neuro_registry_tag_none(self) -> None:
assert image.as_url_str() == "image://me/ubuntu"
assert image.as_repo_str() == "registry.io/me/ubuntu"
assert image.as_local_str() == "ubuntu"
assert image.as_api_str() == "me/ubuntu"

def test_as_str_in_neuro_registry_tag_yes(self) -> None:
image = DockerImage(
Expand All @@ -528,18 +529,21 @@ def test_as_str_in_neuro_registry_tag_yes(self) -> None:
assert image.as_url_str() == "image://me/ubuntu:v10.04"
assert image.as_repo_str() == "registry.io/me/ubuntu:v10.04"
assert image.as_local_str() == "ubuntu:v10.04"
assert image.as_api_str() == "me/ubuntu"

def test_as_str_not_in_neuro_registry_tag_none(self) -> None:
image = DockerImage(name="ubuntu", tag=None, owner=None, registry=None)
assert image.as_url_str() == "ubuntu"
assert image.as_repo_str() == "ubuntu"
assert image.as_local_str() == "ubuntu"
assert image.as_api_str() == "ubuntu"

def test_as_str_not_in_neuro_registry_tag_yes(self) -> None:
image = DockerImage(name="ubuntu", tag="v10.04", owner=None, registry=None)
assert image.as_url_str() == "ubuntu:v10.04"
assert image.as_repo_str() == "ubuntu:v10.04"
assert image.as_local_str() == "ubuntu:v10.04"
assert image.as_api_str() == "ubuntu"


@pytest.mark.usefixtures("patch_docker_host")
Expand Down
17 changes: 17 additions & 0 deletions tests/e2e/test_e2e_images.py
Original file line number Diff line number Diff line change
Expand Up @@ -127,6 +127,23 @@ def test_images_complete_lifecycle(
helper.check_job_output(job_id, re.escape(tag))


def test_image_tags(helper: Helper, image: str, tag: str) -> None:
# push image
captured = helper.run_cli(["image", "push", image])

image_full_str = f"image://{helper.username}/{image}"
assert captured.out.endswith(image_full_str)

# check the tag is present now
image_full_str_no_tag = image_full_str.replace(f":{tag}", "")
captured = helper.run_cli(["image", "tags", image_full_str_no_tag])
assert tag in captured.out

image_full_str_latest_tag = image_full_str.replace(f":{tag}", ":latest")
captured = helper.run_cli(["image", "tags", image_full_str_latest_tag])
assert tag in captured.out


@pytest.mark.e2e
def test_images_push_with_specified_name(
helper: Helper,
Expand Down