poetry-plugin-ivcap 0.1.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- poetry_plugin_ivcap/docker.py +185 -0
- poetry_plugin_ivcap/ivcap.py +147 -0
- poetry_plugin_ivcap/plugin.py +70 -0
- poetry_plugin_ivcap/util.py +87 -0
- poetry_plugin_ivcap-0.1.0.dist-info/AUTHORS.md +5 -0
- poetry_plugin_ivcap-0.1.0.dist-info/LICENSE +29 -0
- poetry_plugin_ivcap-0.1.0.dist-info/METADATA +85 -0
- poetry_plugin_ivcap-0.1.0.dist-info/RECORD +10 -0
- poetry_plugin_ivcap-0.1.0.dist-info/WHEEL +4 -0
- poetry_plugin_ivcap-0.1.0.dist-info/entry_points.txt +3 -0
|
@@ -0,0 +1,185 @@
|
|
|
1
|
+
#
|
|
2
|
+
# Copyright (c) 2025 Commonwealth Scientific and Industrial Research Organisation (CSIRO). All rights reserved.
|
|
3
|
+
# Use of this source code is governed by a BSD-style license that can be
|
|
4
|
+
# found in the LICENSE file. See the AUTHORS file for names of contributors.
|
|
5
|
+
#
|
|
6
|
+
import os
|
|
7
|
+
import re
|
|
8
|
+
import sys
|
|
9
|
+
import tempfile
|
|
10
|
+
from typing import Dict, Optional
|
|
11
|
+
from pydantic import BaseModel, Field
|
|
12
|
+
import subprocess
|
|
13
|
+
|
|
14
|
+
from .util import command_exists, get_name
|
|
15
|
+
|
|
16
|
+
DOCKER_BUILD_TEMPLATE = """
|
|
17
|
+
docker buildx build
|
|
18
|
+
-t #DOCKER_NAME#
|
|
19
|
+
--platform linux/#ARCH#
|
|
20
|
+
--build-arg VERSION=#VERSION#
|
|
21
|
+
--build-arg BUILD_PLATFORM=linux/#ARCH#
|
|
22
|
+
-f #PROJECT_DIR#/#DOCKERFILE#
|
|
23
|
+
--load #PROJECT_DIR#
|
|
24
|
+
"""
|
|
25
|
+
|
|
26
|
+
DOCKER_RUN_TEMPLATE = """
|
|
27
|
+
docker run -it
|
|
28
|
+
-p #PORT#:#PORT#
|
|
29
|
+
--platform=linux/#ARCH#
|
|
30
|
+
--rm \
|
|
31
|
+
#NAME#_#ARCH#:#TAG# --port #PORT#
|
|
32
|
+
"""
|
|
33
|
+
|
|
34
|
+
class DockerConfig(BaseModel):
|
|
35
|
+
name: Optional[str] = Field(None)
|
|
36
|
+
tag: Optional[str] = Field(None)
|
|
37
|
+
arch: Optional[str] = Field(None)
|
|
38
|
+
version: Optional[str] = Field(None)
|
|
39
|
+
dockerfile: Optional[str] = Field("Dockerfile")
|
|
40
|
+
project_dir: Optional[str] = Field(".")
|
|
41
|
+
|
|
42
|
+
@property
|
|
43
|
+
def docker_name(self) -> str:
|
|
44
|
+
return f"{self.name}_{self.arch}:{self.tag}"
|
|
45
|
+
|
|
46
|
+
def from_build_template(self, data: dict) -> str:
|
|
47
|
+
pdata = data.get("tool", {}).get("poetry-plugin-ivcap", {})
|
|
48
|
+
template = pdata.get("docker-build-template", DOCKER_BUILD_TEMPLATE).strip()
|
|
49
|
+
t = template.strip()\
|
|
50
|
+
.replace("#DOCKER_NAME#", self.docker_name)\
|
|
51
|
+
.replace("#NAME#", self.name)\
|
|
52
|
+
.replace("#TAG#", self.tag)\
|
|
53
|
+
.replace("#ARCH#", self.arch)\
|
|
54
|
+
.replace("#VERSION#", self.version)\
|
|
55
|
+
.replace("#DOCKERFILE#", self.dockerfile)\
|
|
56
|
+
.replace("#PROJECT_DIR#", self.project_dir)\
|
|
57
|
+
.split()
|
|
58
|
+
return t
|
|
59
|
+
|
|
60
|
+
def from_run_template(self, data) -> str:
|
|
61
|
+
pdata = data.get("tool", {}).get("poetry-plugin-ivcap", {})
|
|
62
|
+
template = pdata.get("docker-run-template", DOCKER_RUN_TEMPLATE).strip()
|
|
63
|
+
opts = pdata.get("docker-run-opts", {"port": 8080})
|
|
64
|
+
t = template.strip()\
|
|
65
|
+
.replace("#NAME#", self.name)\
|
|
66
|
+
.replace("#TAG#", self.tag)\
|
|
67
|
+
.replace("#ARCH#", self.arch)\
|
|
68
|
+
.replace("#VERSION#", self.version)\
|
|
69
|
+
.replace("#PROJECT_DIR#", self.project_dir)
|
|
70
|
+
|
|
71
|
+
for key, value in opts.items():
|
|
72
|
+
t = t.replace(f"#{key.upper()}#", str(value))
|
|
73
|
+
|
|
74
|
+
return t.split()
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
def docker_build(data: dict, line, arch = None) -> None:
|
|
78
|
+
check_docker_cmd(line)
|
|
79
|
+
config = _docker_cfg(data, line, arch)
|
|
80
|
+
build_cmd = config.from_build_template(data)
|
|
81
|
+
line(f"<info>INFO: {' '.join(build_cmd)}</info>")
|
|
82
|
+
process = subprocess.Popen(build_cmd, stdout=sys.stdout, stderr=sys.stderr)
|
|
83
|
+
exit_code = process.wait()
|
|
84
|
+
if exit_code != 0:
|
|
85
|
+
line(f"<error>ERROR: Docker build failed with exit code {exit_code}</error>")
|
|
86
|
+
else:
|
|
87
|
+
line("<info>INFO: Docker build completed successfully</info>")
|
|
88
|
+
return config.docker_name
|
|
89
|
+
|
|
90
|
+
def docker_run(data: dict, line) -> None:
|
|
91
|
+
check_docker_cmd(line)
|
|
92
|
+
config = _docker_cfg(data, line)
|
|
93
|
+
build_run = config.from_run_template(data)
|
|
94
|
+
line(f"<info>INFO: {' '.join(build_run)}</info>")
|
|
95
|
+
process = subprocess.Popen(build_run, stdout=sys.stdout, stderr=sys.stderr)
|
|
96
|
+
exit_code = process.wait()
|
|
97
|
+
if exit_code != 0:
|
|
98
|
+
line(f"<error>ERROR: Docker run failed with exit code {exit_code}</error>")
|
|
99
|
+
else:
|
|
100
|
+
line("<info>INFO: Docker run completed successfully</info>")
|
|
101
|
+
|
|
102
|
+
def _docker_cfg(data: dict, line, arch = None) -> DockerConfig:
|
|
103
|
+
project_data = data.get("project", {})
|
|
104
|
+
name = get_name(data)
|
|
105
|
+
version = project_data.get("version", None)
|
|
106
|
+
|
|
107
|
+
pdata = data.get("tool", {}).get("poetry-plugin-ivcap", {})
|
|
108
|
+
config = DockerConfig(name=name, version=version, **pdata.get("docker", {}))
|
|
109
|
+
if arch:
|
|
110
|
+
# override architecture if provided
|
|
111
|
+
config.arch = arch
|
|
112
|
+
|
|
113
|
+
if not config.version:
|
|
114
|
+
try:
|
|
115
|
+
config.version = subprocess.check_output(['git', 'describe', '--tags', '--abbrev=0']).decode().strip()
|
|
116
|
+
except Exception as e:
|
|
117
|
+
line(f"<error>Error retrieving latest tag: {e}</error>")
|
|
118
|
+
config.version = "???"
|
|
119
|
+
|
|
120
|
+
if not config.tag:
|
|
121
|
+
try:
|
|
122
|
+
config.tag = subprocess.check_output(['git', 'rev-parse', "--short", 'HEAD']).decode().strip()
|
|
123
|
+
except Exception as e:
|
|
124
|
+
line(f"<warning>WARN: retrieving commit hash: {e}</warning>")
|
|
125
|
+
config.tag = "latest"
|
|
126
|
+
|
|
127
|
+
if not config.arch:
|
|
128
|
+
try:
|
|
129
|
+
config.arch = subprocess.check_output(['uname', '-m']).decode().strip()
|
|
130
|
+
except Exception as e:
|
|
131
|
+
line(f"<error>ERROR: cannot obtain build architecture: {e}</error>")
|
|
132
|
+
os.exit(1)
|
|
133
|
+
|
|
134
|
+
return config
|
|
135
|
+
|
|
136
|
+
def docker_push(docker_img, line):
|
|
137
|
+
push_cmd = ["ivcap", "package", "push", "--force", "--local", docker_img]
|
|
138
|
+
line(f"<debug>Running: {' '.join(push_cmd)} </debug>")
|
|
139
|
+
p1 = subprocess.Popen(push_cmd, stdout=subprocess.PIPE, stderr=sys.stderr)
|
|
140
|
+
|
|
141
|
+
with tempfile.NamedTemporaryFile(mode='w+', delete=False) as tmp:
|
|
142
|
+
tmp_path = tmp.name
|
|
143
|
+
|
|
144
|
+
try:
|
|
145
|
+
# Pipe its output to tee (or to the screen via /dev/tty)
|
|
146
|
+
p2 = subprocess.Popen(
|
|
147
|
+
["tee", tmp_path],
|
|
148
|
+
stdin=p1.stdout,
|
|
149
|
+
stdout=sys.stdout,
|
|
150
|
+
stderr=sys.stderr
|
|
151
|
+
)
|
|
152
|
+
p1.stdout.close()
|
|
153
|
+
# Wait for both to finish
|
|
154
|
+
p2.communicate()
|
|
155
|
+
exit_code = p1.wait()
|
|
156
|
+
if exit_code != 0:
|
|
157
|
+
line(f"<error>ERROR: package push failed with exit code {exit_code}</error>")
|
|
158
|
+
sys.exit(1)
|
|
159
|
+
|
|
160
|
+
# Lookginf for "45a06508-5c3a-4678-8e6d-e6399bf27538/gene_onology_term_mapper_amd64:9a9a7cc pushed\n"
|
|
161
|
+
pattern = re.compile(
|
|
162
|
+
r'([0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}/[^\s]+) pushed'
|
|
163
|
+
)
|
|
164
|
+
package_name = None
|
|
165
|
+
with open(tmp_path, 'r') as f:
|
|
166
|
+
for l in f:
|
|
167
|
+
match = pattern.search(l)
|
|
168
|
+
if match:
|
|
169
|
+
package_name = match.group(1)
|
|
170
|
+
break
|
|
171
|
+
|
|
172
|
+
if not package_name:
|
|
173
|
+
line("<error>ERROR: No package name found in output</error>")
|
|
174
|
+
sys.exit(1)
|
|
175
|
+
|
|
176
|
+
line("<info>INFO: package push completed successfully</info>")
|
|
177
|
+
return package_name
|
|
178
|
+
finally:
|
|
179
|
+
if os.path.exists(tmp_path):
|
|
180
|
+
os.remove(tmp_path)
|
|
181
|
+
|
|
182
|
+
def check_docker_cmd(line):
|
|
183
|
+
if not command_exists("docker"):
|
|
184
|
+
line("<error>'docker' command not found. Please install it first.</error>")
|
|
185
|
+
sys.exit(1)
|
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
#
|
|
2
|
+
# Copyright (c) 2025 Commonwealth Scientific and Industrial Research Organisation (CSIRO). All rights reserved.
|
|
3
|
+
# Use of this source code is governed by a BSD-style license that can be
|
|
4
|
+
# found in the LICENSE file. See the AUTHORS file for names of contributors.
|
|
5
|
+
#
|
|
6
|
+
import os
|
|
7
|
+
import re
|
|
8
|
+
import subprocess
|
|
9
|
+
import sys
|
|
10
|
+
import tempfile
|
|
11
|
+
import uuid
|
|
12
|
+
import humanize
|
|
13
|
+
|
|
14
|
+
from .docker import _docker_cfg, docker_build, docker_push
|
|
15
|
+
from .util import command_exists, execute_subprocess_and_capture_output, get_name, string_to_number
|
|
16
|
+
|
|
17
|
+
def docker_publish(data, line):
|
|
18
|
+
check_ivcap_cmd(line)
|
|
19
|
+
#dname = docker_build(data, line, arch="amd64")
|
|
20
|
+
dname = "gene_onology_term_mapper_amd64:9a9a7cc"
|
|
21
|
+
print(dname)
|
|
22
|
+
|
|
23
|
+
size_cmd = ["docker", "inspect", "--format='{{.Size}}'", dname]
|
|
24
|
+
line(f"<debug>Running: {' '.join(size_cmd)} </debug>")
|
|
25
|
+
size = string_to_number(subprocess.check_output(size_cmd).decode())
|
|
26
|
+
if size is None:
|
|
27
|
+
line("<error>Failed to retrieve image size</error>")
|
|
28
|
+
return
|
|
29
|
+
line(f"<info>INFO: Image size {humanize.naturalsize(size)}</info>")
|
|
30
|
+
pkg_name = docker_push(dname, line)
|
|
31
|
+
|
|
32
|
+
def service_register(data, line):
|
|
33
|
+
check_ivcap_cmd(line)
|
|
34
|
+
config = data.get("tool", {}).get("poetry-plugin-ivcap", {})
|
|
35
|
+
|
|
36
|
+
service = config.get("service-file")
|
|
37
|
+
if not service:
|
|
38
|
+
line("<error>Missing 'service-file' in [tool.poetry-plugin-ivcap]</error>")
|
|
39
|
+
return
|
|
40
|
+
|
|
41
|
+
dcfg = _docker_cfg(data, line, "amd64")
|
|
42
|
+
pkg_cmd = ["ivcap", "package", "list", dcfg.docker_name]
|
|
43
|
+
line(f"<debug>Running: {' '.join(pkg_cmd)} </debug>")
|
|
44
|
+
pkg = subprocess.check_output(pkg_cmd).decode()
|
|
45
|
+
if not pkg or pkg == "":
|
|
46
|
+
line(f"<error>No package '{dcfg.docker_name}' found. Please build and publish it first.</error>")
|
|
47
|
+
return
|
|
48
|
+
service_id = get_service_id(data, line)
|
|
49
|
+
|
|
50
|
+
cmd = ["poetry", "run", "python", service, "--print-service-description"]
|
|
51
|
+
line(f"<debug>Running: {' '.join(cmd)} </debug>")
|
|
52
|
+
svc = subprocess.check_output(cmd).decode()
|
|
53
|
+
|
|
54
|
+
svc = svc.replace("#DOCKER_IMG#", pkg.strip())\
|
|
55
|
+
.replace("#SERVICE_ID#", service_id)
|
|
56
|
+
|
|
57
|
+
with tempfile.NamedTemporaryFile(mode='w+', delete=False) as tmp:
|
|
58
|
+
tmp.write(svc)
|
|
59
|
+
tmp_path = tmp.name # Save the file name for subprocess
|
|
60
|
+
|
|
61
|
+
try:
|
|
62
|
+
policy = get_policy(data, line)
|
|
63
|
+
up_cmd = ["ivcap", "aspect", "update", "--policy", policy, service_id, "-f", tmp_path]
|
|
64
|
+
try:
|
|
65
|
+
line(f"<debug>Running: {' '.join(up_cmd)} </debug>")
|
|
66
|
+
jaid = subprocess.check_output(up_cmd).decode().strip()
|
|
67
|
+
p = re.compile(r'.*(urn:[^"]*)')
|
|
68
|
+
aid = p.search(jaid).group(1)
|
|
69
|
+
line(f"<info>INFO: service definition successfully uploaded - {aid}</info>")
|
|
70
|
+
except Exception as e:
|
|
71
|
+
line(f"<error>ERROR: cannot upload service definitiion: {e}</error>")
|
|
72
|
+
sys.exit(1)
|
|
73
|
+
finally:
|
|
74
|
+
if os.path.exists(tmp_path):
|
|
75
|
+
os.remove(tmp_path)
|
|
76
|
+
|
|
77
|
+
def tool_register(data, line):
|
|
78
|
+
check_ivcap_cmd(line)
|
|
79
|
+
config = data.get("tool", {}).get("poetry-plugin-ivcap", {})
|
|
80
|
+
|
|
81
|
+
service = config.get("service-file")
|
|
82
|
+
if not service:
|
|
83
|
+
line("<error>Missing 'service-file' in [tool.poetry-plugin-ivcap]</error>")
|
|
84
|
+
return
|
|
85
|
+
|
|
86
|
+
cmd = ["poetry", "run", "python", service, "--print-tool-description"]
|
|
87
|
+
line(f"<debug>Running: {' '.join(cmd)} </debug>")
|
|
88
|
+
svc = subprocess.check_output(cmd).decode()
|
|
89
|
+
|
|
90
|
+
service_id = get_service_id(data, line)
|
|
91
|
+
svc = svc.replace("#SERVICE_ID#", service_id)
|
|
92
|
+
|
|
93
|
+
with tempfile.NamedTemporaryFile(mode='w+', delete=False) as tmp:
|
|
94
|
+
tmp.write(svc)
|
|
95
|
+
tmp_path = tmp.name # Save the file name for subprocess
|
|
96
|
+
|
|
97
|
+
try:
|
|
98
|
+
policy = get_policy(data, line)
|
|
99
|
+
up_cmd = ["ivcap", "aspect", "update", "--policy", policy, service_id, "-f", tmp_path]
|
|
100
|
+
try:
|
|
101
|
+
line(f"<debug>Running: {' '.join(up_cmd)} </debug>")
|
|
102
|
+
jaid = subprocess.check_output(up_cmd).decode().strip()
|
|
103
|
+
p = re.compile(r'.*(urn:[^"]*)')
|
|
104
|
+
aid = p.search(jaid).group(1)
|
|
105
|
+
line(f"<info>INFO: tool description successfully uploaded - {aid}</info>")
|
|
106
|
+
except Exception as e:
|
|
107
|
+
line(f"<error>ERROR: cannot upload tool description: {e}</error>")
|
|
108
|
+
sys.exit(1)
|
|
109
|
+
finally:
|
|
110
|
+
if os.path.exists(tmp_path):
|
|
111
|
+
os.remove(tmp_path)
|
|
112
|
+
|
|
113
|
+
def get_service_id(data, line):
|
|
114
|
+
service_id = data.get("tool", {}).get("poetry-plugin-ivcap", {}).get("service-id")
|
|
115
|
+
if not service_id:
|
|
116
|
+
service_id = create_service_id(data, line)
|
|
117
|
+
return service_id
|
|
118
|
+
|
|
119
|
+
def create_service_id(data, line):
|
|
120
|
+
check_ivcap_cmd(line)
|
|
121
|
+
name = get_name(data)
|
|
122
|
+
account_id = get_account_id(data, line)
|
|
123
|
+
id = uuid.uuid5(uuid.NAMESPACE_DNS, f"{name}{account_id}")
|
|
124
|
+
return f"urn:ivcap:service:{id}"
|
|
125
|
+
|
|
126
|
+
def get_policy(data, line):
|
|
127
|
+
policy = data.get("tool", {}).get("poetry-plugin-ivcap", {}).get("policy")
|
|
128
|
+
if not policy:
|
|
129
|
+
policy = "urn:ivcap:policy:ivcap.open.metadata"
|
|
130
|
+
return policy
|
|
131
|
+
|
|
132
|
+
def get_account_id(data, line):
|
|
133
|
+
check_ivcap_cmd(line)
|
|
134
|
+
cmd = ["ivcap", "context", "get", "account-id"]
|
|
135
|
+
line(f"<debug>Running: {' '.join(cmd)} </debug>")
|
|
136
|
+
try:
|
|
137
|
+
account_id = subprocess.check_output(cmd).decode().strip()
|
|
138
|
+
return account_id
|
|
139
|
+
except subprocess.CalledProcessError as e:
|
|
140
|
+
line(f"<error>Error retrieving account ID: {e}</error>")
|
|
141
|
+
sys.exit(1)
|
|
142
|
+
|
|
143
|
+
def check_ivcap_cmd(line):
|
|
144
|
+
if not command_exists("ivcap"):
|
|
145
|
+
line("<error>'ivcap' command not found. Please install the IVCAP CLI tool.</error>")
|
|
146
|
+
line("<error>... see https://github.com/ivcap-works/ivcap-cli?tab=readme-ov-file#install-released-binaries for instructions</error>")
|
|
147
|
+
os.exit(1)
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
#
|
|
2
|
+
# Copyright (c) 2025 Commonwealth Scientific and Industrial Research Organisation (CSIRO). All rights reserved.
|
|
3
|
+
# Use of this source code is governed by a BSD-style license that can be
|
|
4
|
+
# found in the LICENSE file. See the AUTHORS file for names of contributors.
|
|
5
|
+
#
|
|
6
|
+
from poetry.plugins.application_plugin import ApplicationPlugin
|
|
7
|
+
from cleo.commands.command import Command
|
|
8
|
+
from cleo.helpers import argument
|
|
9
|
+
import subprocess
|
|
10
|
+
|
|
11
|
+
from .ivcap import create_service_id, service_register, tool_register
|
|
12
|
+
from .docker import docker_build, docker_run
|
|
13
|
+
from .ivcap import docker_publish
|
|
14
|
+
|
|
15
|
+
class IvcapCommand(Command):
|
|
16
|
+
name = "ivcap"
|
|
17
|
+
description = "IVCAP plugin `poetry ivcap <subcommand>`"
|
|
18
|
+
help = """\
|
|
19
|
+
IVCAP plugin
|
|
20
|
+
|
|
21
|
+
Available subcommands:
|
|
22
|
+
run Run the configured service
|
|
23
|
+
deploy Deploy using configured settings
|
|
24
|
+
|
|
25
|
+
Example:
|
|
26
|
+
poetry ivcap run
|
|
27
|
+
"""
|
|
28
|
+
arguments = [
|
|
29
|
+
argument("subcommand", optional=True, description="Subcommand: run, deploy, etc.")
|
|
30
|
+
]
|
|
31
|
+
|
|
32
|
+
def handle(self):
|
|
33
|
+
poetry = self.application.poetry
|
|
34
|
+
data = poetry.pyproject.data
|
|
35
|
+
|
|
36
|
+
sub = self.argument("subcommand")
|
|
37
|
+
if sub == "run":
|
|
38
|
+
self.run_service(data)
|
|
39
|
+
elif sub == "docker-build":
|
|
40
|
+
docker_build(data, self.line)
|
|
41
|
+
elif sub == "docker-run":
|
|
42
|
+
docker_run(data, self.line)
|
|
43
|
+
elif sub == "docker-publish":
|
|
44
|
+
docker_publish(data, self.line)
|
|
45
|
+
elif sub == "service-register":
|
|
46
|
+
service_register(data, self.line)
|
|
47
|
+
elif sub == "create-service-id":
|
|
48
|
+
sid = create_service_id(data, self.line)
|
|
49
|
+
print(sid)
|
|
50
|
+
elif sub == "tool-register":
|
|
51
|
+
tool_register(data, self.line)
|
|
52
|
+
else:
|
|
53
|
+
self.line(f"<error>Unknown subcommand: {sub}</error>")
|
|
54
|
+
|
|
55
|
+
def run_service(self, data):
|
|
56
|
+
config = data.get("tool", {}).get("poetry-plugin-ivcap", {})
|
|
57
|
+
|
|
58
|
+
service = config.get("service-file")
|
|
59
|
+
port = config.get("port")
|
|
60
|
+
|
|
61
|
+
if not service or not port:
|
|
62
|
+
self.line("<error>Missing 'service-file' or 'port' in [tool.poetry-plugin-ivcap]</error>")
|
|
63
|
+
return
|
|
64
|
+
|
|
65
|
+
self.line(f"<info>Running: python {service} --port {port}</info>")
|
|
66
|
+
subprocess.run(["poetry", "run", "python", service, "--port", str(port)])
|
|
67
|
+
|
|
68
|
+
class IvcapPlugin(ApplicationPlugin):
|
|
69
|
+
def activate(self, application):
|
|
70
|
+
application.command_loader.register_factory("ivcap", lambda: IvcapCommand())
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
#
|
|
2
|
+
# Copyright (c) 2025 Commonwealth Scientific and Industrial Research Organisation (CSIRO). All rights reserved.
|
|
3
|
+
# Use of this source code is governed by a BSD-style license that can be
|
|
4
|
+
# found in the LICENSE file. See the AUTHORS file for names of contributors.
|
|
5
|
+
#
|
|
6
|
+
import subprocess
|
|
7
|
+
import platform
|
|
8
|
+
|
|
9
|
+
def get_name(data) -> str:
|
|
10
|
+
project_data = data.get("project", {})
|
|
11
|
+
name = project_data.get("name").replace("-", "_").replace(".", "_")
|
|
12
|
+
return name
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def command_exists(cmd: str) -> bool:
|
|
16
|
+
system = platform.system()
|
|
17
|
+
|
|
18
|
+
check_cmd = {
|
|
19
|
+
"Windows": ["where", cmd],
|
|
20
|
+
"Linux": ["which", cmd],
|
|
21
|
+
"Darwin": ["which", cmd],
|
|
22
|
+
}.get(system)
|
|
23
|
+
|
|
24
|
+
if not check_cmd:
|
|
25
|
+
raise RuntimeError(f"Unsupported OS: {system}")
|
|
26
|
+
|
|
27
|
+
try:
|
|
28
|
+
subprocess.run(check_cmd, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL, check=True)
|
|
29
|
+
return True
|
|
30
|
+
except subprocess.CalledProcessError:
|
|
31
|
+
return False
|
|
32
|
+
|
|
33
|
+
def string_to_number(s):
|
|
34
|
+
"""Converts a string to a number (int)."""
|
|
35
|
+
s = s.replace("'", "") # Remove all single quotes
|
|
36
|
+
try:
|
|
37
|
+
return int(s.strip())
|
|
38
|
+
except ValueError as e:
|
|
39
|
+
print(e)
|
|
40
|
+
return None # Or raise an exception, depending on your needs
|
|
41
|
+
|
|
42
|
+
import io
|
|
43
|
+
import sys
|
|
44
|
+
|
|
45
|
+
def execute_subprocess_and_capture_output(command):
|
|
46
|
+
"""
|
|
47
|
+
Executes a subprocess, prints all outputs to the console in real-time, and returns a full copy of the output.
|
|
48
|
+
|
|
49
|
+
Args:
|
|
50
|
+
command (list): A list of strings representing the command to execute.
|
|
51
|
+
|
|
52
|
+
Returns:
|
|
53
|
+
str: A string containing the full output of the subprocess.
|
|
54
|
+
"""
|
|
55
|
+
process = subprocess.Popen(command, stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True)
|
|
56
|
+
|
|
57
|
+
full_output = ""
|
|
58
|
+
|
|
59
|
+
# Read stdout and stderr in real-time
|
|
60
|
+
while True:
|
|
61
|
+
stdout_line = process.stdout.readline()
|
|
62
|
+
stderr_line = process.stderr.readline()
|
|
63
|
+
|
|
64
|
+
if stdout_line:
|
|
65
|
+
print("Stdout:", stdout_line, end="") # Print to console immediately
|
|
66
|
+
sys.stdout.flush()
|
|
67
|
+
full_output += stdout_line
|
|
68
|
+
if stderr_line:
|
|
69
|
+
print("Stderr:", stderr_line, end="") # Print to console immediately
|
|
70
|
+
sys.stderr.flush()
|
|
71
|
+
full_output += stderr_line
|
|
72
|
+
|
|
73
|
+
# Check if the process has finished
|
|
74
|
+
if process.poll() is not None:
|
|
75
|
+
break
|
|
76
|
+
|
|
77
|
+
# Read any remaining output
|
|
78
|
+
for line in process.stdout:
|
|
79
|
+
print("Stdout:", line, end="")
|
|
80
|
+
sys.stdout.flush()
|
|
81
|
+
full_output += line
|
|
82
|
+
for line in process.stderr:
|
|
83
|
+
print("Stderr:", line, end="")
|
|
84
|
+
sys.stderr.flush()
|
|
85
|
+
full_output += line
|
|
86
|
+
|
|
87
|
+
return full_output
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
BSD 3-Clause License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2023, Commonwealth Scientific and Industrial Research Organisation (CSIRO) ABN 41 687 119 230
|
|
4
|
+
All rights reserved.
|
|
5
|
+
|
|
6
|
+
Redistribution and use in source and binary forms, with or without
|
|
7
|
+
modification, are permitted provided that the following conditions are met:
|
|
8
|
+
|
|
9
|
+
* Redistributions of source code must retain the above copyright notice, this
|
|
10
|
+
list of conditions and the following disclaimer.
|
|
11
|
+
|
|
12
|
+
* Redistributions in binary form must reproduce the above copyright notice,
|
|
13
|
+
this list of conditions and the following disclaimer in the documentation
|
|
14
|
+
and/or other materials provided with the distribution.
|
|
15
|
+
|
|
16
|
+
* Neither the name of the copyright holder nor the names of its
|
|
17
|
+
contributors may be used to endorse or promote products derived from
|
|
18
|
+
this software without specific prior written permission.
|
|
19
|
+
|
|
20
|
+
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
|
21
|
+
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
|
22
|
+
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
|
23
|
+
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
|
24
|
+
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
|
25
|
+
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
|
26
|
+
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
|
27
|
+
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
|
28
|
+
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
|
29
|
+
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
Metadata-Version: 2.3
|
|
2
|
+
Name: poetry-plugin-ivcap
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: A custom Poetry command for IVCAP deployments
|
|
5
|
+
License: MIT
|
|
6
|
+
Author: Max Ott
|
|
7
|
+
Author-email: max.ott@csiro.au
|
|
8
|
+
Requires-Python: >=3.9,<4.0
|
|
9
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
10
|
+
Classifier: Programming Language :: Python :: 3
|
|
11
|
+
Classifier: Programming Language :: Python :: 3.9
|
|
12
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
13
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
14
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
16
|
+
Requires-Dist: humanize (>=4.12.3,<5.0.0)
|
|
17
|
+
Requires-Dist: pydantic (>=2.11.5,<3.0.0)
|
|
18
|
+
Description-Content-Type: text/markdown
|
|
19
|
+
|
|
20
|
+
# poetry-plugin-ivcap
|
|
21
|
+
|
|
22
|
+
A custom Poetry plugin that adds a `poetry ivcap` command for local and Docker-based deployments.
|
|
23
|
+
|
|
24
|
+
## Example Configuration
|
|
25
|
+
|
|
26
|
+
Add to your `pyproject.toml`:
|
|
27
|
+
|
|
28
|
+
```toml
|
|
29
|
+
[tool.poetry-plugin-ivcap]
|
|
30
|
+
default_target = "docker"
|
|
31
|
+
docker_tag = "myapp:dev"
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
## Installation
|
|
35
|
+
|
|
36
|
+
```bash
|
|
37
|
+
poetry self add poetry-plugin-ivcap
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
## Usage
|
|
41
|
+
|
|
42
|
+
```bash
|
|
43
|
+
poetry ivcap
|
|
44
|
+
poetry ivcap docker
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
## Development
|
|
48
|
+
|
|
49
|
+
### Build the Plugin Package
|
|
50
|
+
|
|
51
|
+
```bash
|
|
52
|
+
poetry build
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
This creates .whl and .tar.gz files in the dist/ directory.
|
|
56
|
+
|
|
57
|
+
### Publish to PyPI
|
|
58
|
+
|
|
59
|
+
Create an account at https://pypi.org/account/register/
|
|
60
|
+
|
|
61
|
+
Add your credentials:
|
|
62
|
+
```bash
|
|
63
|
+
poetry config pypi-token.pypi <your-token>
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
Publish:
|
|
67
|
+
```bash
|
|
68
|
+
poetry publish --build
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
### Optional: Test on TestPyPI First
|
|
72
|
+
|
|
73
|
+
To verify your setup without publishing to the real PyPI:
|
|
74
|
+
|
|
75
|
+
```bash
|
|
76
|
+
poetry config repositories.test-pypi https://test.pypi.org/legacy/
|
|
77
|
+
poetry publish -r test-pypi --build
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
Then test installing via:
|
|
81
|
+
|
|
82
|
+
```bash
|
|
83
|
+
poetry self add --source test-pypi poetry-plugin-deploy
|
|
84
|
+
```
|
|
85
|
+
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
poetry_plugin_ivcap/docker.py,sha256=ePxs6RdDm0znmGpnybd1O-N9Ef1WjUX5eqRV0xf7pKk,6609
|
|
2
|
+
poetry_plugin_ivcap/ivcap.py,sha256=wXy1IGiOiNA8r9tJIE0_hhamQn6J6pXzc0Zc33euO3k,5697
|
|
3
|
+
poetry_plugin_ivcap/plugin.py,sha256=cOXF7ttBI883LC2fj7AUeDUF6w-tYI9l9Fi3FB23B-g,2432
|
|
4
|
+
poetry_plugin_ivcap/util.py,sha256=cAfofL50KZAcaghkDuizGMoW8qx3hkH3A0h9S9DraZc,2620
|
|
5
|
+
poetry_plugin_ivcap-0.1.0.dist-info/AUTHORS.md,sha256=s9xR4_HAHQgbNlj505LViebt5AtACQmhPf92aJvNYgg,88
|
|
6
|
+
poetry_plugin_ivcap-0.1.0.dist-info/LICENSE,sha256=dsQrDPPwW7iJs9pxahgJKDW8RNPf5FyXG70MFUlxcuk,1587
|
|
7
|
+
poetry_plugin_ivcap-0.1.0.dist-info/METADATA,sha256=qAsv8_FAyzJn2726zSRRniUmOWlgcywv21rFilKsqcY,1719
|
|
8
|
+
poetry_plugin_ivcap-0.1.0.dist-info/WHEEL,sha256=fGIA9gx4Qxk2KDKeNJCbOEwSrmLtjWCwzBz351GyrPQ,88
|
|
9
|
+
poetry_plugin_ivcap-0.1.0.dist-info/entry_points.txt,sha256=3xagEFBkGgrVe8WyjmhlHLr4JDEWPN_W4DwxnIBWbNY,74
|
|
10
|
+
poetry_plugin_ivcap-0.1.0.dist-info/RECORD,,
|