Skip to content

Commit a21b7c5

Browse files
committed
Add support to additionally tag an existing OCI manifest
Signed-off-by: Tobias Wolf <[email protected]>
1 parent a4c3480 commit a21b7c5

File tree

8 files changed

+191
-43
lines changed

8 files changed

+191
-43
lines changed

.github/actions/features_parse/action.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ outputs:
1111
runs:
1212
using: composite
1313
steps:
14-
- uses: gardenlinux/python-gardenlinux-lib/.github/actions/[email protected].0
14+
- uses: gardenlinux/python-gardenlinux-lib/.github/actions/[email protected].1
1515
- id: result
1616
shell: bash
1717
run: |

.github/actions/flavors_parse/action.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ outputs:
1313
runs:
1414
using: composite
1515
steps:
16-
- uses: gardenlinux/python-gardenlinux-lib/.github/actions/[email protected].0
16+
- uses: gardenlinux/python-gardenlinux-lib/.github/actions/[email protected].1
1717
- id: matrix
1818
shell: bash
1919
run: |

.github/actions/setup/action.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ description: Installs the given GardenLinux Python library
44
inputs:
55
version:
66
description: GardenLinux Python library version
7-
default: "0.9.0"
7+
default: "0.9.1"
88
python_version:
99
description: Python version to setup
1010
default: "3.13"

pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[tool.poetry]
22
name = "gardenlinux"
3-
version = "0.9.0"
3+
version = "0.9.1"
44
description = "Contains tools to work with the features directory of gardenlinux, for example deducting dependencies from feature sets or validating cnames"
55
authors = ["Garden Linux Maintainers <[email protected]>"]
66
license = "Apache-2.0"

src/gardenlinux/oci/__main__.py

Lines changed: 84 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -31,26 +31,28 @@ def cli():
3131
help="Container Name",
3232
)
3333
@click.option(
34-
"--version",
35-
required=True,
36-
type=click.Path(),
37-
help="Version of image",
34+
"--cname", required=True, type=click.Path(), help="Canonical Name of Image"
3835
)
3936
@click.option(
40-
"--commit",
37+
"--arch",
4138
required=False,
4239
type=click.Path(),
4340
default=None,
44-
help="Commit of image",
41+
help="Target Image CPU Architecture",
4542
)
4643
@click.option(
47-
"--arch",
48-
required=True,
44+
"--version",
45+
required=False,
4946
type=click.Path(),
50-
help="Target Image CPU Architecture",
47+
default=None,
48+
help="Version of image",
5149
)
5250
@click.option(
53-
"--cname", required=True, type=click.Path(), help="Canonical Name of Image"
51+
"--commit",
52+
required=False,
53+
type=click.Path(),
54+
default=None,
55+
help="Commit of image",
5456
)
5557
@click.option("--dir", "directory", required=True, help="path to the build artifacts")
5658
@click.option(
@@ -76,10 +78,10 @@ def cli():
7678
)
7779
def push_manifest(
7880
container,
81+
cname,
82+
arch,
7983
version,
8084
commit,
81-
arch,
82-
cname,
8385
directory,
8486
cosign_file,
8587
manifest_file,
@@ -107,6 +109,76 @@ def push_manifest(
107109
print(manifest.digest, file=open(cosign_file, "w"))
108110

109111

112+
@cli.command()
113+
@click.option(
114+
"--container",
115+
required=True,
116+
type=click.Path(),
117+
help="Container Name",
118+
)
119+
@click.option(
120+
"--cname",
121+
required=False,
122+
type=click.Path(),
123+
default=None,
124+
help="Canonical Name of Image"
125+
)
126+
@click.option(
127+
"--arch",
128+
required=False,
129+
type=click.Path(),
130+
default=None,
131+
help="Target Image CPU Architecture",
132+
)
133+
@click.option(
134+
"--version",
135+
required=False,
136+
type=click.Path(),
137+
default=None,
138+
help="Version of image",
139+
)
140+
@click.option(
141+
"--commit",
142+
required=False,
143+
type=click.Path(),
144+
default=None,
145+
help="Commit of image",
146+
)
147+
@click.option(
148+
"--insecure",
149+
default=False,
150+
help="Use HTTP to communicate with the registry",
151+
)
152+
@click.option(
153+
"--tag",
154+
required=True,
155+
multiple=True,
156+
help="Tag to push the manifest with",
157+
)
158+
def push_manifest_tags(
159+
container,
160+
cname,
161+
arch,
162+
version,
163+
commit,
164+
insecure,
165+
tag,
166+
):
167+
"""
168+
Push artifacts and the manifest from a directory to a registry.
169+
170+
:since: 0.7.0
171+
"""
172+
173+
container = Container(
174+
f"{container}:{version}",
175+
insecure=insecure,
176+
)
177+
178+
manifest = container.read_or_generate_manifest(cname, arch, version, commit)
179+
container.push_manifest_for_tags(manifest, tag)
180+
181+
110182
@cli.command()
111183
@click.option(
112184
"--container",

src/gardenlinux/oci/container.py

Lines changed: 29 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -83,11 +83,21 @@ def __init__(
8383
self._container_version = container_data[1]
8484

8585
container_url_data = urlsplit(self._container_url)
86+
self._token = None
87+
88+
if token is None:
89+
token = getenv("GL_CLI_REGISTRY_TOKEN")
90+
91+
if token is not None:
92+
auth_backend = "token"
93+
self._token = b64encode(token.encode("utf-8")).decode("utf-8")
94+
else:
95+
auth_backend = "basic"
8696

8797
Registry.__init__(
8898
self,
8999
hostname=container_url_data.netloc,
90-
auth_backend="token",
100+
auth_backend=auth_backend,
91101
insecure=insecure,
92102
)
93103

@@ -97,11 +107,7 @@ def __init__(
97107
self._container_name = container_url_data.path[1:]
98108
self._logger = logger
99109

100-
if token is None:
101-
token = getenv("GL_CLI_REGISTRY_TOKEN")
102-
103-
if token is not None:
104-
self._token = b64encode(token.encode("utf-8")).decode("utf-8")
110+
if self._token is not None:
105111
self.auth.set_token_auth(self._token)
106112
else:
107113
# Authentication credentials from environment
@@ -520,14 +526,14 @@ def read_or_generate_index(self):
520526

521527
def read_or_generate_manifest(
522528
self,
523-
cname: str,
529+
cname: Optional[str] = None,
524530
architecture: Optional[str] = None,
525531
version: Optional[str] = None,
526532
commit: Optional[str] = None,
527533
feature_set: Optional[str] = None,
528534
) -> Manifest:
529535
"""
530-
Reads from registry or generates the OCI image manifest.
536+
Reads from registry or generates the OCI manifest.
531537
532538
:param cname: Canonical name of the manifest
533539
:param architecture: Target architecture of the manifest
@@ -539,19 +545,26 @@ def read_or_generate_manifest(
539545
:since: 0.7.0
540546
"""
541547

542-
if architecture is None:
543-
architecture = CName(cname, architecture, version).arch
548+
if cname is None:
549+
response = self._get_manifest_without_response_parsing(self._container_version)
550+
else:
551+
if architecture is None:
552+
architecture = CName(cname, architecture, version).arch
544553

545-
response = self._get_manifest_without_response_parsing(
546-
f"{self._container_version}-{cname}-{architecture}"
547-
)
554+
response = self._get_manifest_without_response_parsing(
555+
f"{self._container_version}-{cname}-{architecture}"
556+
)
557+
#
548558

549559
if response.ok:
550560
manifest = Manifest(**response.json())
551561
elif response.status_code == 404:
552-
manifest = self.generate_manifest(
553-
cname, architecture, version, commit, feature_set
554-
)
562+
if cname is None:
563+
manifest = Manifest()
564+
else:
565+
manifest = self.generate_manifest(
566+
cname, architecture, version, commit, feature_set
567+
)
555568
else:
556569
response.raise_for_status()
557570

tests/conftest.py

Lines changed: 27 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,16 @@
1+
from datetime import datetime, timedelta
2+
from tempfile import mkstemp
13
import json
24
import os
35
import shutil
46
import subprocess
57
import sys
6-
import tempfile
78
import pytest
89

910
from cryptography import x509
1011
from cryptography.x509.oid import NameOID
1112
from cryptography.hazmat.primitives import hashes, serialization
1213
from cryptography.hazmat.primitives.asymmetric import rsa
13-
from datetime import datetime, timedelta
1414
from dotenv import load_dotenv
1515
from gardenlinux.features import Parser
1616

@@ -26,6 +26,7 @@
2626
TEST_FEATURE_STRINGS_SHORT,
2727
TEST_ARCHITECTURES,
2828
)
29+
2930
from .helper import call_command, spawn_background_process
3031

3132

@@ -80,11 +81,6 @@ def generate_test_certificates():
8081
print(f"Generated test certificates in {CERT_DIR}")
8182

8283

83-
def write_zot_config(config_dict, file_path):
84-
with open(file_path, "w") as config_file:
85-
json.dump(config_dict, config_file, indent=4)
86-
87-
8884
def create_test_data():
8985
"""Generate test data for OCI registry tests (replaces build-test-data.sh)"""
9086
print("Creating fake artifacts...")
@@ -127,20 +123,38 @@ def create_test_data():
127123
f.write(f"dummy content for {file_path}")
128124

129125

126+
def write_zot_config(config_dict, fd):
127+
with os.fdopen(fd, "w") as fp:
128+
json.dump(config_dict, fp, indent=4)
129+
130+
130131
@pytest.fixture(autouse=False, scope="function")
131132
def zot_session():
132133
load_dotenv()
133134
print("start zot session")
135+
136+
fd, htpasswd_file = mkstemp(dir=TEST_DATA_DIR, suffix=".htpasswd")
137+
os.close(fd)
138+
134139
zot_config = {
135140
"distSpecVersion": "1.1.0",
136141
"storage": {"rootDirectory": "output/registry/zot"},
137-
"http": {"address": "127.0.0.1", "port": "18081"},
142+
"http": {
143+
"address": "127.0.0.1",
144+
"port": "18081",
145+
"auth": {"htpasswd": {"path": f"{htpasswd_file}"}},
146+
"accessControl": {
147+
"repositories": {
148+
"**": {"anonymousPolicy": ["read", "create", "update", "delete"]},
149+
"protected/**": {"anonymousPolicy": []},
150+
}
151+
},
152+
},
138153
"log": {"level": "warn"},
139154
}
140155

141-
with tempfile.NamedTemporaryFile(delete=False, suffix=".json") as temp_config_file:
142-
write_zot_config(zot_config, temp_config_file.name)
143-
zot_config_file_path = temp_config_file.name
156+
fd, zot_config_file_path = mkstemp(text=True, dir=TEST_DATA_DIR, suffix=".json")
157+
write_zot_config(zot_config, fd)
144158

145159
print(f"Spawning zot registry with config {zot_config_file_path}")
146160
zot_process = spawn_background_process(
@@ -156,6 +170,8 @@ def zot_session():
156170

157171
if os.path.isdir("./output"):
158172
shutil.rmtree("./output")
173+
if os.path.isfile(htpasswd_file):
174+
os.remove(htpasswd_file)
159175
if os.path.isfile(zot_config_file_path):
160176
os.remove(zot_config_file_path)
161177

0 commit comments

Comments
 (0)