portmgr 1.8.0.dev1__tar.gz → 1.8.0.dev3__tar.gz

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.
Files changed (37) hide show
  1. portmgr-1.8.0.dev3/.gitignore +5 -0
  2. portmgr-1.8.0.dev3/.python-version +1 -0
  3. {portmgr-1.8.0.dev1/src/portmgr.egg-info → portmgr-1.8.0.dev3}/PKG-INFO +4 -5
  4. portmgr-1.8.0.dev3/dckrsub.schema.json +22 -0
  5. portmgr-1.8.0.dev3/json2yml/dckrcnf.schema.json +160 -0
  6. portmgr-1.8.0.dev3/json2yml/dckrjsn.py +38 -0
  7. portmgr-1.8.0.dev3/json2yml/json2yml.py +1 -0
  8. {portmgr-1.8.0.dev1 → portmgr-1.8.0.dev3}/pyproject.toml +7 -2
  9. portmgr-1.8.0.dev3/release.sh +8 -0
  10. portmgr-1.8.0.dev1/src/portmgr.egg-info/requires.txt → portmgr-1.8.0.dev3/requirements.txt +3 -3
  11. portmgr-1.8.0.dev3/src/portmgr/__init__.py +2 -0
  12. portmgr-1.8.0.dev3/src/portmgr/__main__.py +15 -0
  13. portmgr-1.8.0.dev3/src/portmgr/commands/__init__.py +0 -0
  14. portmgr-1.8.0.dev3/src/portmgr/commands/attach.py +51 -0
  15. portmgr-1.8.0.dev3/src/portmgr/commands/build.py +25 -0
  16. portmgr-1.8.0.dev3/src/portmgr/commands/down.py +27 -0
  17. portmgr-1.8.0.dev3/src/portmgr/commands/logs.py +19 -0
  18. portmgr-1.8.0.dev3/src/portmgr/commands/ps.py +19 -0
  19. portmgr-1.8.0.dev3/src/portmgr/commands/pull.py +19 -0
  20. portmgr-1.8.0.dev3/src/portmgr/commands/push.py +76 -0
  21. portmgr-1.8.0.dev3/src/portmgr/commands/scan.py +43 -0
  22. portmgr-1.8.0.dev3/src/portmgr/commands/stats.py +52 -0
  23. portmgr-1.8.0.dev3/src/portmgr/commands/stop.py +26 -0
  24. portmgr-1.8.0.dev3/src/portmgr/commands/top.py +18 -0
  25. portmgr-1.8.0.dev3/src/portmgr/commands/up.py +26 -0
  26. portmgr-1.8.0.dev3/src/portmgr/portmgr.py +170 -0
  27. portmgr-1.8.0.dev3/src/portmgr/wrapper.py +67 -0
  28. portmgr-1.8.0.dev3/uv.lock +150 -0
  29. portmgr-1.8.0.dev1/PKG-INFO +0 -82
  30. portmgr-1.8.0.dev1/setup.cfg +0 -4
  31. portmgr-1.8.0.dev1/src/portmgr/__init__.py +0 -2
  32. portmgr-1.8.0.dev1/src/portmgr.egg-info/SOURCES.txt +0 -10
  33. portmgr-1.8.0.dev1/src/portmgr.egg-info/dependency_links.txt +0 -1
  34. portmgr-1.8.0.dev1/src/portmgr.egg-info/entry_points.txt +0 -2
  35. portmgr-1.8.0.dev1/src/portmgr.egg-info/top_level.txt +0 -1
  36. {portmgr-1.8.0.dev1 → portmgr-1.8.0.dev3}/LICENSE +0 -0
  37. {portmgr-1.8.0.dev1 → portmgr-1.8.0.dev3}/README.md +0 -0
@@ -0,0 +1,5 @@
1
+ __pycache__
2
+ .idea/
3
+ build/
4
+ dist/
5
+ portmgr.egg-info/
@@ -0,0 +1 @@
1
+ 3.12
@@ -1,18 +1,17 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: portmgr
3
- Version: 1.8.0.dev1
3
+ Version: 1.8.0.dev3
4
4
  Summary: Simple command interface to manage multiple Docker container
5
- License-Expression: MIT
6
5
  Project-URL: Website, https://github.com/Craeckie/portmgr
7
6
  Project-URL: Repository, https://github.com/Craeckie/portmgr.git
8
- Requires-Python: >=3.12
9
- Description-Content-Type: text/markdown
7
+ License-Expression: MIT
10
8
  License-File: LICENSE
9
+ Requires-Python: >=3.11
11
10
  Requires-Dist: humanfriendly~=10.0
12
11
  Requires-Dist: jsonschema~=3.2.0
13
12
  Requires-Dist: pyyaml~=6.0.2
14
13
  Requires-Dist: tabulate~=0.8.9
15
- Dynamic: license-file
14
+ Description-Content-Type: text/markdown
16
15
 
17
16
  # portmgr
18
17
  portmgr is a wrapper around [docker-compose](https://docs.docker.com/compose/) that allows running typical docker-compose commands recursively. Additionally, it shortens commands to a single letter.
@@ -0,0 +1,22 @@
1
+ {
2
+ "$schema": "http://json-schema.org/draft-04/schema#",
3
+ "title": "dckrsub",
4
+
5
+ "description": "dckrmgr subfolders description",
6
+
7
+ "type": "array",
8
+
9
+ "items" : {
10
+ "type": "object",
11
+
12
+ "properties": {
13
+ "folder": {
14
+ "type": "string"
15
+ }
16
+ },
17
+
18
+ "additionalProperties": false
19
+ },
20
+
21
+ "uniqueItems": true
22
+ }
@@ -0,0 +1,160 @@
1
+ {
2
+ "$schema": "http://json-schema.org/draft-04/schema#",
3
+ "title": "dckrcnf",
4
+
5
+ "description": "dckrmgr container description",
6
+
7
+ "type": "object",
8
+
9
+ "properties": {
10
+ "name": {
11
+ "type": "string"
12
+ },
13
+
14
+ "image": {
15
+ "type": "object",
16
+
17
+ "properties": {
18
+ "name": {
19
+ "type": "string"
20
+ },
21
+
22
+ "version": {
23
+ "type": "string"
24
+ }
25
+ },
26
+
27
+ "additionalProperties": false,
28
+
29
+ "required": ["name", "version"]
30
+ },
31
+
32
+ "hostname": {
33
+ "type": "string"
34
+ },
35
+
36
+ "environment": {
37
+ "type": "array",
38
+
39
+ "items": {
40
+ "type": "object",
41
+
42
+ "properties": {
43
+ "name": {
44
+ "type": "string"
45
+ },
46
+
47
+ "value": {
48
+ "type": "string"
49
+ }
50
+ },
51
+
52
+ "additionalProperties": false,
53
+
54
+ "required": ["name", "value"]
55
+ },
56
+
57
+ "minitems": 0
58
+ },
59
+
60
+ "volumes": {
61
+ "type": "array",
62
+
63
+ "items": {
64
+ "type": "object",
65
+
66
+ "properties": {
67
+ "host_path": {
68
+ "type": "string"
69
+ },
70
+
71
+ "container_path": {
72
+ "type": "string"
73
+ },
74
+
75
+ "mode": {
76
+ "type": "string",
77
+
78
+ "pattern": "^((ro)|(rw))(?!(.|\n))"
79
+ }
80
+ },
81
+
82
+ "additionalProperties": false,
83
+
84
+ "required": ["host_path", "container_path", "mode"]
85
+ },
86
+
87
+ "minitems": 0
88
+ },
89
+
90
+ "ports": {
91
+ "type": "array",
92
+
93
+ "items": {
94
+ "type": "object",
95
+
96
+ "properties": {
97
+ "container_port": {
98
+ "type": "integer",
99
+
100
+ "minimum": 0,
101
+
102
+ "maximum": 65535
103
+ },
104
+
105
+ "host_port": {
106
+ "type": "integer",
107
+
108
+ "minimum": 0,
109
+
110
+ "maximum": 65535
111
+ },
112
+
113
+ "address": {
114
+ "type": "string"
115
+ },
116
+
117
+ "protocol": {
118
+ "type": "string",
119
+
120
+ "pattern": "^((tcp)|(udp))(?!(.|\n))"
121
+ }
122
+ },
123
+
124
+ "additionalProperties": false,
125
+
126
+ "required": ["container_port", "host_port"]
127
+ },
128
+
129
+ "minitems": 0
130
+ },
131
+
132
+ "links": {
133
+ "type": "array",
134
+
135
+ "items": {
136
+ "type": "object",
137
+
138
+ "properties": {
139
+ "name": {
140
+ "type": "string"
141
+ },
142
+
143
+ "alias": {
144
+ "type": "string"
145
+ }
146
+ },
147
+
148
+ "additionalProperties": false,
149
+
150
+ "required": ["name", "alias"]
151
+ },
152
+
153
+ "minitems": 0
154
+ }
155
+ },
156
+
157
+ "additionalProperties": false,
158
+
159
+ "required": ["name", "image"]
160
+ }
@@ -0,0 +1,38 @@
1
+ import os
2
+ import json
3
+ import jsonschema
4
+
5
+ def read_json(pth, sch=None):
6
+ bsn = os.path.basename(pth)
7
+
8
+ try:
9
+ f_jsn = open(pth, 'r')
10
+ except FileNotFoundError:
11
+ print('Couldn\'t open ' + bsn + ': Not found')
12
+ exit(1)
13
+ except PermissionError:
14
+ print('Couldn\'t open ' + bsn + ': Insufficient rights')
15
+ exit(1)
16
+ except OSError:
17
+ print('Couldn\'t open ' + bsn)
18
+ exit(1)
19
+
20
+ try:
21
+ jsn = json.load(f_jsn)
22
+ except ValueError:
23
+ print('Couldn\'t deserialize ' + bsn + ': Invalid json')
24
+ exit(1)
25
+ finally:
26
+ f_jsn.close()
27
+
28
+ if sch is not None:
29
+ try:
30
+ jsonschema.validate(jsn, sch)
31
+ except jsonschema.exceptions.SchemaError:
32
+ print('Couldn\'t validate ' + bsn + ': Invalid schema')
33
+ exit(1)
34
+ except jsonschema.exceptions.ValidationError as error:
35
+ print('Couldn\'t accept '+ bsn + ': ' + error.message)
36
+ exit(1)
37
+
38
+ return jsn
@@ -0,0 +1 @@
1
+ #!/bin/usr/python3
@@ -1,16 +1,21 @@
1
1
  [project]
2
2
  name = "portmgr"
3
- version = "1.8.0dev1"
3
+ version = "1.8.0dev3"
4
4
  description = "Simple command interface to manage multiple Docker container"
5
5
  readme = "README.md"
6
6
  license = "MIT"
7
- requires-python = ">=3.12"
7
+ requires-python = ">=3.11"
8
8
  dependencies = [
9
9
  "humanfriendly~=10.0",
10
10
  "jsonschema~=3.2.0",
11
11
  "pyyaml~=6.0.2",
12
12
  "tabulate~=0.8.9",
13
13
  ]
14
+ [tool.uv]
15
+ package = true
16
+ [build-system]
17
+ requires = ["hatchling"]
18
+ build-backend = "hatchling.build"
14
19
  [project.urls]
15
20
  Website = "https://github.com/Craeckie/portmgr"
16
21
  Repository = "https://github.com/Craeckie/portmgr.git"
@@ -0,0 +1,8 @@
1
+ #!/bin/bash
2
+ set -x
3
+ set -e
4
+ rm -rf dist/ portmgr.egg-info/
5
+ uv build
6
+ uv publish
7
+ #curl -X PURGE https://pypi.org/project/portmgr
8
+ rm -rf build/ dist/ portmgr.egg-info/
@@ -1,4 +1,4 @@
1
- humanfriendly~=10.0
2
- jsonschema~=3.2.0
3
- pyyaml~=6.0.2
4
1
  tabulate~=0.8.9
2
+ PyYAML~=6.0.2
3
+ jsonschema~=3.2.0
4
+ humanfriendly~=10.0
@@ -0,0 +1,2 @@
1
+ from .portmgr import main, command_list, bcolors
2
+ from .wrapper import runCompose, runBuildx
@@ -0,0 +1,15 @@
1
+ #!/usr/bin/env python3
2
+ from __future__ import unicode_literals
3
+
4
+ import sys
5
+
6
+ if __package__ is None and not hasattr(sys, 'frozen'):
7
+ # direct call of __main__.py
8
+ import os.path
9
+ path = os.path.realpath(os.path.abspath(__file__))
10
+ sys.path.insert(0, os.path.dirname(os.path.dirname(path)))
11
+
12
+ import portmgr
13
+
14
+ if __name__ == '__main__':
15
+ portmgr.main()
File without changes
@@ -0,0 +1,51 @@
1
+ from portmgr import command_list, bcolors
2
+ import subprocess
3
+
4
+ from portmgr.wrapper import getServicesRunning
5
+
6
+
7
+ def func(action):
8
+ directory = action['directory']
9
+ relative = action['relative']
10
+
11
+ names = getServicesRunning()
12
+
13
+ index = 0
14
+ cont_count = len(names)
15
+ if cont_count == 0:
16
+ print("No containers found!")
17
+ return 1
18
+ elif cont_count > 1:
19
+ i = 0
20
+ for cont in names:
21
+ print("(" + str(i) + ") " + cont)
22
+ i += 1
23
+
24
+ while True:
25
+ choice = input("Choose container: ")
26
+ if choice:
27
+ try:
28
+ index = int(choice)
29
+ if index >= 0 and index < cont_count:
30
+ break
31
+ except ValueError:
32
+ print("Please enter a number!")
33
+ pass
34
+ print("Please enter a number between 0 and " + cont_count - 1 + "!")
35
+
36
+ container_id = names[index]
37
+ print("Attaching to " + container_id)
38
+ subprocess.call(["docker", "exec", "-it", container_id, "sh"])
39
+
40
+ # res = subprocess.call(["docker-compose", "logs", "--follow", "--tail=200"])
41
+
42
+ # if res != 0:
43
+ # print("Error showing logs for " + relative + "!\n")
44
+
45
+ return 0
46
+
47
+ command_list['a'] = {
48
+ 'hlp': 'Attach to process',
49
+ 'ord': 'nrm',
50
+ 'fnc': func
51
+ }
@@ -0,0 +1,25 @@
1
+ from portmgr import command_list, bcolors, runCompose
2
+
3
+
4
+ def func(action):
5
+ directory = action['directory']
6
+ relative = action['relative']
7
+
8
+ res = runCompose(
9
+ ['build', '--pull'],
10
+ env={
11
+ 'COMPOSE_DOCKER_CLI_BUILD': '1',
12
+ 'DOCKER_BUILDKIT': '1'
13
+ },
14
+ )
15
+
16
+ if res != 0:
17
+ print("Error building " + relative + "!")
18
+
19
+ return res
20
+
21
+ command_list['b'] = {
22
+ 'hlp': 'Build local image',
23
+ 'ord': 'nrm',
24
+ 'fnc': func
25
+ }
@@ -0,0 +1,27 @@
1
+ from portmgr import command_list, bcolors, runCompose
2
+
3
+
4
+ def func(action):
5
+ directory = action['directory']
6
+ relative = action['relative']
7
+
8
+ res = runCompose(["down"])
9
+ # p = subprocess.Popen(["docker-compose", "down"], stdout=subprocess.PIPE, stderr=subprocess.PIPE)
10
+
11
+ # out, err = p.communicate()
12
+
13
+ # if out != "":
14
+ # print(out.decode("UTF-8"))
15
+
16
+ if not res == 0:
17
+ print("Error removing " + relative + "!")
18
+
19
+ # print(bcolors.FAIL + err.decode("UTF-8") + bcolors.ENDC)
20
+
21
+ return 0
22
+
23
+ command_list['d'] = {
24
+ 'hlp': 'Stop and remove container',
25
+ 'ord': 'rev',
26
+ 'fnc': func
27
+ }
@@ -0,0 +1,19 @@
1
+ from portmgr import command_list, bcolors, runCompose
2
+
3
+
4
+ def func(action):
5
+ directory = action['directory']
6
+ relative = action['relative']
7
+
8
+ res = runCompose(["logs", "--follow", "--tail=200"])
9
+
10
+ if res != 0:
11
+ print("Error showing logs for " + relative + "!\n")
12
+
13
+ return 0
14
+
15
+ command_list['l'] = {
16
+ 'hlp': 'Show logs',
17
+ 'ord': 'nrm',
18
+ 'fnc': func
19
+ }
@@ -0,0 +1,19 @@
1
+ from portmgr import command_list, bcolors, runCompose
2
+
3
+
4
+ def func(action):
5
+ directory = action['directory']
6
+ relative = action['relative']
7
+
8
+ res = runCompose(["ps"])
9
+
10
+ if res != 0:
11
+ print("Error listing containers for " + relative + "!\n")
12
+
13
+ return 0
14
+
15
+ command_list['c'] = {
16
+ 'hlp': 'List containers',
17
+ 'ord': 'nrm',
18
+ 'fnc': func
19
+ }
@@ -0,0 +1,19 @@
1
+ from portmgr import command_list, bcolors, runCompose
2
+
3
+
4
+ def func(action):
5
+ directory = action['directory']
6
+ relative = action['relative']
7
+
8
+ res = runCompose(["pull"])
9
+
10
+ if res != 0:
11
+ print("Error pulling " + relative + "!")
12
+
13
+ return 0
14
+
15
+ command_list['p'] = {
16
+ 'hlp': 'Pull image from repository',
17
+ 'ord': 'nrm',
18
+ 'fnc': func
19
+ }
@@ -0,0 +1,76 @@
1
+ from portmgr import command_list, bcolors, runCompose, runBuildx
2
+ import subprocess
3
+ import os
4
+
5
+ from portmgr.wrapper import getServices
6
+
7
+
8
+ def func(action):
9
+ directory = action['directory']
10
+ relative = action['relative']
11
+
12
+ services = getServices(includeOnlyBuildable=True)
13
+
14
+ print('Services to build: ' + ', '.join(services))
15
+
16
+ res = 0
17
+ for service in services.keys():
18
+ print(f"\nBuilding {service}")
19
+
20
+ new_res = 2
21
+ if multi_platform := os.environ.get("PORTMGR_MULTI_PLATFORM", "").lower():
22
+ try:
23
+ new_res = runBuildx(
24
+ ['bake',
25
+ '--pull',
26
+ '--push',
27
+ '--set', f'*.platform={multi_platform}',
28
+ service
29
+ ], timeout=1200 #kill after 20mins
30
+ )
31
+ if new_res != 0:
32
+ res = new_res
33
+ print(f"Error building {service}!")
34
+ except TimeoutExpired:
35
+ print(f"Error building {service}! Build timed out.")
36
+ else:
37
+ try:
38
+ new_res = runCompose(
39
+ ['build',
40
+ '--pull',
41
+ '--force-rm',
42
+ '--compress',
43
+ service
44
+ ], timeout=1200
45
+ )
46
+ if new_res != 0:
47
+ res = new_res
48
+ print(f"Error building {service}!")
49
+ else:
50
+ new_res = runCompose(
51
+ ['push',
52
+ '--ignore-push-failures',
53
+ service
54
+ ]
55
+ )
56
+ print(f"Error pushing {service}!")
57
+ except TimeoutExpired:
58
+ print(f"Error building {service}! Build timed out.")
59
+ if new_res != 1:
60
+ res = new_res
61
+ if os.environ.get("PORTMGR_CLEAN_AFTER_PUSH", "").lower() == "true":
62
+ subprocess.call(['docker', 'system', 'prune', '--all', '--force'])
63
+ subprocess.call(['docker', 'buildx', 'prune', '--all', '--force'])
64
+
65
+ if res != 0:
66
+ print("Error building&pushing " + relative + "!")
67
+ return res
68
+
69
+ return res
70
+
71
+
72
+ command_list['r'] = {
73
+ 'hlp': 'build, push to registry & remove image',
74
+ 'ord': 'nrm',
75
+ 'fnc': func
76
+ }
@@ -0,0 +1,43 @@
1
+ from portmgr import command_list
2
+ import subprocess
3
+
4
+ from portmgr.wrapper import getImages
5
+
6
+
7
+ def func(action):
8
+ directory = action['directory']
9
+ relative = action['relative']
10
+
11
+ images = getImages()
12
+
13
+ res = 0
14
+
15
+ for image in images:
16
+ image_name = image["Name"]
17
+ print(f'Scanning {image_name} of {image["ContainerName"]}')
18
+ scan_res = subprocess.run(['trivy', '-q', 'image', '-s', 'CRITICAL', '--exit-code', '1', image_name], stdout=subprocess.PIPE, stderr=subprocess.STDOUT, text=True)
19
+ if scan_res.returncode != 0:
20
+ print('Found vulnerabilites:')
21
+ print(scan_res.stdout)
22
+ res = scan_res.returncode
23
+
24
+ #print("Name: %s" % container.name)
25
+ #names.append(container.name)
26
+ #config_str = subprocess.check_output(["docker", "inspect", "-f", '{{json .}}', container.id], stdout=subprocess.PIPE)
27
+ #json.loads(config_str)
28
+ #print("IP: %s" % ip)
29
+ #print(container.inspect)
30
+
31
+
32
+ #ID = subprocess.run(["docker-compose", "ps", '-q'], stdout=subprocess.PIPE).stdout
33
+
34
+ #if res != 0:
35
+ # print("Error listing containers for " + relative + "!\n")
36
+
37
+ return res
38
+
39
+ command_list['v'] = {
40
+ 'hlp': 'Scan container images for vulnerabilities',
41
+ 'ord': 'nrm',
42
+ 'fnc': func
43
+ }
@@ -0,0 +1,52 @@
1
+ from functools import cmp_to_key
2
+
3
+ from portmgr import command_list
4
+
5
+ from tabulate import tabulate
6
+ from humanfriendly import format_size, parse_size
7
+
8
+ from portmgr.wrapper import getStats
9
+
10
+
11
+ def func(action):
12
+ directory = action['directory']
13
+ relative = action['relative']
14
+
15
+ stats_list = getStats()
16
+
17
+ values = []
18
+ has_network_stats = False
19
+ for stats in stats_list:
20
+ name = stats['Name'] if 'Name' in stats else stats['Container']
21
+ #memory = stats["memory_stats"]
22
+ #usage = format_size(memory['usage'])
23
+ #limit = format_size(memory['limit'])
24
+ memory_string = stats["MemUsage"]
25
+ usage, limit = [p.strip() for p in memory_string.split('/')]
26
+ if 'NetIO' in stats:
27
+ network = stats["NetIO"]
28
+ received, sent = [p.strip() for p in memory_string.split('/')]
29
+ #received = format_size(sum(stats['rx_bytes'] for iface, stats in network.items()))
30
+ #sent = format_size(sum(stats['tx_bytes'] for iface, stats in network.items()))
31
+ columns = (stats['Name'], usage, limit, received, sent)
32
+ has_network_stats = True
33
+ else:
34
+ columns = (stats['Name'], usage, limit)
35
+ values.append(columns)
36
+ if values:
37
+ # sort by memory usage
38
+ values = sorted(values, key=cmp_to_key(lambda s1, s2: parse_size(s2[1]) - parse_size(s1[1])))
39
+ print(tabulate(values,
40
+ headers=['Service', 'Mem Usage', 'Mem Limit', 'Net Recv', 'Net Sent'],
41
+ colalign=['left', 'right', 'right', 'right', 'right'] if has_network_stats
42
+ else ['left', 'right', 'right']))
43
+ print('')
44
+
45
+ return 0
46
+
47
+
48
+ command_list['o'] = {
49
+ 'hlp': 'Show container stats',
50
+ 'ord': 'nrm',
51
+ 'fnc': func
52
+ }
@@ -0,0 +1,26 @@
1
+ from portmgr import command_list, bcolors, runCompose
2
+
3
+ def func(action):
4
+ directory = action['directory']
5
+ relative = action['relative']
6
+
7
+ res = runCompose(["stop"])
8
+ # p = subprocess.Popen(["docker-compose", "stop"], stdout=subprocess.PIPE, stderr=subprocess.PIPE)
9
+
10
+ #out, err = p.communicate()
11
+
12
+ #if out != "":
13
+ # print(out.decode("UTF-8"))
14
+
15
+ if res != 0:
16
+ print("Error stopping " + relative + "!")
17
+
18
+ # print(bcolors.FAIL + err.decode("UTF-8") + bcolors.ENDC)
19
+
20
+ return 0
21
+
22
+ command_list['s'] = {
23
+ 'hlp': 'Stop container',
24
+ 'ord': 'rev',
25
+ 'fnc': func
26
+ }
@@ -0,0 +1,18 @@
1
+ from portmgr import command_list, runCompose
2
+
3
+ def func(action):
4
+ relative = action['relative']
5
+
6
+ res = runCompose(["top"])
7
+
8
+ if res != 0:
9
+ print("Error listing processes of containers in " + relative + "!\n")
10
+
11
+ return 0
12
+
13
+
14
+ command_list['t'] = {
15
+ 'hlp': 'List processes in containers',
16
+ 'ord': 'nrm',
17
+ 'fnc': func
18
+ }
@@ -0,0 +1,26 @@
1
+ from portmgr import command_list, bcolors, runCompose
2
+
3
+ def func(action):
4
+ directory = action['directory']
5
+ relative = action['relative']
6
+
7
+ res = runCompose(["up", "-d"])
8
+ # p = subprocess.Popen(["docker-compose", "up", "-d"], stdout=subprocess.PIPE, stderr=subprocess.PIPE)
9
+
10
+ # out, err = p.communicate()
11
+
12
+ # if out != "":
13
+ # print(out.decode("UTF-8"))
14
+
15
+ if res != 0:
16
+ print("Error creating " + relative + "!")
17
+
18
+ # print(bcolors.FAIL + err.decode("UTF-8") + bcolors.ENDC)
19
+
20
+ return 0
21
+
22
+ command_list['u'] = {
23
+ 'hlp': 'Create container',
24
+ 'ord': 'nrm',
25
+ 'fnc': func
26
+ }
@@ -0,0 +1,170 @@
1
+ #!/usr/bin/python3
2
+ import os, sys
3
+ import argparse
4
+ import importlib
5
+ import re
6
+
7
+ import yaml
8
+
9
+
10
+ class MyParser(argparse.ArgumentParser):
11
+ def error(self, message):
12
+ sys.stderr.write('Error: %s\n' % message)
13
+ self.print_help()
14
+ sys.exit(2)
15
+
16
+
17
+ sub_names = [x.strip() for x in re.split('[ ,;]+', os.environ.get('PORTMGR_SUB_NAME', 'dckrsub.yml'))]
18
+ compose_names = [x.strip() for x in
19
+ os.environ.get('PORTMGR_COMPOSE_NAME', 'docker-compose.yml, docker-compose.yaml').split(',')]
20
+
21
+ # sub_scheme_name = 'dckrsub.schema.yml'
22
+
23
+ src_path = os.path.dirname(os.path.abspath(__file__))
24
+
25
+
26
+ # conf_scheme_path = os.path.join(src_path, sub_scheme_name)
27
+ # sub_scheme_path = os.path.join(src_path, sub_scheme_name)
28
+
29
+ # conf_scheme = dckrjsn.read_json(conf_scheme_path)
30
+ # sub_scheme = dckrjsn.read_json(sub_scheme_path)
31
+
32
+ class bcolors:
33
+ HEADER = '\033[95m'
34
+ OKBLUE = '\033[94m'
35
+ OKGREEN = '\033[92m'
36
+ WARNING = '\033[93m'
37
+ FAIL = '\033[91m'
38
+ ENDC = '\033[0m'
39
+ BOLD = '\033[1m'
40
+ UNDERLINE = '\033[4m'
41
+
42
+
43
+ command_list = {}
44
+ action_list = []
45
+
46
+
47
+ def addCommand(cur_directory):
48
+ if not any(action['directory'] == cur_directory for action in action_list):
49
+ relative_dir = os.path.relpath(cur_directory, base_directory)
50
+ if relative_dir == '.':
51
+ relative_dir = os.path.basename(os.path.normpath(cur_directory))
52
+ action_list.append({
53
+ 'directory': cur_directory,
54
+ 'relative': relative_dir
55
+ })
56
+
57
+
58
+ def read_yaml(path):
59
+ with open(path, 'r') as stream:
60
+ try:
61
+ return yaml.load(stream, Loader=yaml.SafeLoader)
62
+ except yaml.YAMLError as exc:
63
+ print(exc)
64
+
65
+
66
+ def traverse(cur_directory):
67
+ # print("Traversing in " + cur_directory)
68
+ for sub_name in sub_names:
69
+ sub_path = os.path.join(cur_directory, sub_name)
70
+ compose_paths = [os.path.join(cur_directory, name) for name in compose_names]
71
+
72
+ # print("Checking file at " + sub_path)
73
+ if os.path.isfile(sub_path): # has sub folders
74
+ # print("Has sub folders!")
75
+ # sub_folders = dckrjsn.read_json(sub_path, sch = sub_scheme)
76
+ sub_folders = read_yaml(sub_path)
77
+ for sub_folder in sub_folders:
78
+ # print("Checking out " + sub_folder)
79
+ next_directory = os.path.join(cur_directory, sub_folder)
80
+ traverse(next_directory)
81
+ elif any(os.path.isfile(path) for path in compose_paths): # has a docker-compose file
82
+ addCommand(cur_directory)
83
+
84
+
85
+ def main():
86
+ # global cli
87
+ global base_directory
88
+
89
+ # cli = docker.Client('unix://var/run/docker.sock')
90
+
91
+ # Include external source files for commands
92
+ # These fill the m_cmd list
93
+ for file in os.listdir(os.path.join(src_path, 'commands')):
94
+ ext_file = os.path.splitext(file)
95
+
96
+ if ext_file[1] == '.py' and not ext_file[0] == '__init__':
97
+ importlib.import_module('portmgr.commands.' + ext_file[0])
98
+
99
+ parser = MyParser()
100
+ parser.add_argument('-D',
101
+ dest='base_directory',
102
+ action='store',
103
+ default='',
104
+ help='Set working directory')
105
+ # parser.add_argument('-R',
106
+ # dest='recursive',
107
+ # action='store_true',
108
+ # help='Use dckrsub.json files to recursively apply operations')
109
+
110
+ for cmd in command_list.items():
111
+ parser.add_argument('-' + cmd[0],
112
+ dest='a_cmd',
113
+ action='append_const',
114
+ const=cmd[0],
115
+ help=cmd[1]['hlp'])
116
+
117
+ argv = sys.argv[1:]
118
+ if len(argv) == 1 and not argv[0].startswith('-'):
119
+ argv[0] = '-' + argv[0]
120
+ args = parser.parse_args(argv)
121
+
122
+ if len(sys.argv) == 1:
123
+ parser.print_help()
124
+ sys.exit(1)
125
+
126
+ base_directory = os.path.join(os.getcwd(), args.base_directory)
127
+
128
+ # if args.recursive:
129
+ traverse(base_directory)
130
+ # else:
131
+ # addCommand(base_directory)
132
+
133
+ last_cmd = ''
134
+ for cmd in args.a_cmd: # loop over all passed arguments (t, r, u)
135
+ cur_cmd = command_list[cmd]
136
+ cmd_function = cur_cmd['fnc']
137
+ cmd_order = cur_cmd['ord'];
138
+
139
+ # if last_cmd == 'r' and cur_cmd == 'u':
140
+ # print("Waiting 3 seconds.. ")
141
+ # sleep(3)
142
+
143
+ if cmd_order == 'nrm':
144
+ action_list_sorted = action_list
145
+ elif cmd_order == 'rev':
146
+ action_list_sorted = reversed(action_list)
147
+ else:
148
+ exit(1)
149
+
150
+ failed_list = []
151
+
152
+ for action in action_list_sorted:
153
+ origWD = os.getcwd()
154
+ newWD = action['directory']
155
+ os.chdir(newWD)
156
+ print('-> ' + action["relative"])
157
+ if cmd_function(action) != 0: # execute the function through reflection
158
+ failed_list.append(action)
159
+ os.chdir(origWD)
160
+ if failed_list:
161
+ print('Failed containers:')
162
+ for action in failed_list:
163
+ print('- ' + action['relative'])
164
+ print("")
165
+
166
+ exit(0)
167
+
168
+
169
+ if __name__ == '__main__':
170
+ main()
@@ -0,0 +1,67 @@
1
+ import json
2
+ import os
3
+ from subprocess import run, check_output
4
+
5
+
6
+ def getServices(includeOnlyBuildable=False):
7
+ data = check_output(['docker', 'compose', 'config', '--format', 'json'])
8
+ config = json.loads(data)
9
+ services = config['services']
10
+ if includeOnlyBuildable:
11
+ services = {name: values for name, values in services.items() if 'build' in values.keys()}
12
+ return services
13
+
14
+
15
+ def getServicesRunning():
16
+ data = check_output(['docker', 'compose', 'ps', '--format', 'json'])
17
+ try:
18
+ lines = data.decode().splitlines()
19
+ if len(lines) > 1:
20
+ container_list = []
21
+ for l in lines:
22
+ #print(f'l: {l}')
23
+ container_list.append(json.loads(l.strip()))
24
+ else:
25
+ container_list = json.loads(data)
26
+ except Exception as e:
27
+ print(e)
28
+ print(data.decode())
29
+ if isinstance(container_list, dict):
30
+ container_names = [container_list['Name']]
31
+ else:
32
+ container_names = [s['Name'] for s in container_list]
33
+ return container_names
34
+
35
+
36
+ def getImages():
37
+ data = check_output(['docker', 'compose', 'images', '--format', 'json'])
38
+ image_list = json.loads(data)
39
+ images = [
40
+ {'ID': image['ID'],
41
+ 'Name': image['Repository'],
42
+ 'ContainerName': image['ContainerName'],
43
+ 'Tag': image['Tag']}
44
+ for image in image_list
45
+ ]
46
+ return images
47
+
48
+
49
+ def getStats():
50
+ containers = getServicesRunning()
51
+ data = check_output(['docker', 'stats', '--format', 'json', '--no-stream'] + containers, text=True).strip()
52
+ data_lines = data.split('\n')
53
+ stats = [json.loads(line) for line in data_lines]
54
+ return stats
55
+
56
+
57
+ def runCompose(args, **kwargs):
58
+ command = ['docker', 'compose']
59
+ if os.environ.get("PORTMGR_IN_SCRIPT", "").lower() == "true":
60
+ command += ["--ansi", "never"]
61
+ command += list(args)
62
+ return run(command, **kwargs).returncode
63
+
64
+ def runBuildx(args, **kwargs):
65
+ command = ['docker', 'buildx']
66
+ command += list(args)
67
+ return run(command, **kwargs).returncode
@@ -0,0 +1,150 @@
1
+ version = 1
2
+ revision = 2
3
+ requires-python = ">=3.11"
4
+
5
+ [[package]]
6
+ name = "attrs"
7
+ version = "25.3.0"
8
+ source = { registry = "https://pypi.org/simple" }
9
+ sdist = { url = "https://files.pythonhosted.org/packages/5a/b0/1367933a8532ee6ff8d63537de4f1177af4bff9f3e829baf7331f595bb24/attrs-25.3.0.tar.gz", hash = "sha256:75d7cefc7fb576747b2c81b4442d4d4a1ce0900973527c011d1030fd3bf4af1b", size = 812032, upload-time = "2025-03-13T11:10:22.779Z" }
10
+ wheels = [
11
+ { url = "https://files.pythonhosted.org/packages/77/06/bb80f5f86020c4551da315d78b3ab75e8228f89f0162f2c3a819e407941a/attrs-25.3.0-py3-none-any.whl", hash = "sha256:427318ce031701fea540783410126f03899a97ffc6f61596ad581ac2e40e3bc3", size = 63815, upload-time = "2025-03-13T11:10:21.14Z" },
12
+ ]
13
+
14
+ [[package]]
15
+ name = "humanfriendly"
16
+ version = "10.0"
17
+ source = { registry = "https://pypi.org/simple" }
18
+ dependencies = [
19
+ { name = "pyreadline3", marker = "sys_platform == 'win32'" },
20
+ ]
21
+ sdist = { url = "https://files.pythonhosted.org/packages/cc/3f/2c29224acb2e2df4d2046e4c73ee2662023c58ff5b113c4c1adac0886c43/humanfriendly-10.0.tar.gz", hash = "sha256:6b0b831ce8f15f7300721aa49829fc4e83921a9a301cc7f606be6686a2288ddc", size = 360702, upload-time = "2021-09-17T21:40:43.31Z" }
22
+ wheels = [
23
+ { url = "https://files.pythonhosted.org/packages/f0/0f/310fb31e39e2d734ccaa2c0fb981ee41f7bd5056ce9bc29b2248bd569169/humanfriendly-10.0-py2.py3-none-any.whl", hash = "sha256:1697e1a8a8f550fd43c2865cd84542fc175a61dcb779b6fee18cf6b6ccba1477", size = 86794, upload-time = "2021-09-17T21:40:39.897Z" },
24
+ ]
25
+
26
+ [[package]]
27
+ name = "jsonschema"
28
+ version = "3.2.0"
29
+ source = { registry = "https://pypi.org/simple" }
30
+ dependencies = [
31
+ { name = "attrs" },
32
+ { name = "pyrsistent" },
33
+ { name = "setuptools" },
34
+ { name = "six" },
35
+ ]
36
+ sdist = { url = "https://files.pythonhosted.org/packages/69/11/a69e2a3c01b324a77d3a7c0570faa372e8448b666300c4117a516f8b1212/jsonschema-3.2.0.tar.gz", hash = "sha256:c8a85b28d377cc7737e46e2d9f2b4f44ee3c0e1deac6bf46ddefc7187d30797a", size = 167226, upload-time = "2019-11-18T12:57:10.704Z" }
37
+ wheels = [
38
+ { url = "https://files.pythonhosted.org/packages/c5/8f/51e89ce52a085483359217bc72cdbf6e75ee595d5b1d4b5ade40c7e018b8/jsonschema-3.2.0-py2.py3-none-any.whl", hash = "sha256:4e5b3cf8216f577bee9ce139cbe72eca3ea4f292ec60928ff24758ce626cd163", size = 56305, upload-time = "2019-11-18T12:57:08.454Z" },
39
+ ]
40
+
41
+ [[package]]
42
+ name = "portmgr"
43
+ version = "1.8.0.dev2"
44
+ source = { editable = "." }
45
+ dependencies = [
46
+ { name = "humanfriendly" },
47
+ { name = "jsonschema" },
48
+ { name = "pyyaml" },
49
+ { name = "tabulate" },
50
+ ]
51
+
52
+ [package.metadata]
53
+ requires-dist = [
54
+ { name = "humanfriendly", specifier = "~=10.0" },
55
+ { name = "jsonschema", specifier = "~=3.2.0" },
56
+ { name = "pyyaml", specifier = "~=6.0.2" },
57
+ { name = "tabulate", specifier = "~=0.8.9" },
58
+ ]
59
+
60
+ [[package]]
61
+ name = "pyreadline3"
62
+ version = "3.5.4"
63
+ source = { registry = "https://pypi.org/simple" }
64
+ sdist = { url = "https://files.pythonhosted.org/packages/0f/49/4cea918a08f02817aabae639e3d0ac046fef9f9180518a3ad394e22da148/pyreadline3-3.5.4.tar.gz", hash = "sha256:8d57d53039a1c75adba8e50dd3d992b28143480816187ea5efbd5c78e6c885b7", size = 99839, upload-time = "2024-09-19T02:40:10.062Z" }
65
+ wheels = [
66
+ { url = "https://files.pythonhosted.org/packages/5a/dc/491b7661614ab97483abf2056be1deee4dc2490ecbf7bff9ab5cdbac86e1/pyreadline3-3.5.4-py3-none-any.whl", hash = "sha256:eaf8e6cc3c49bcccf145fc6067ba8643d1df34d604a1ec0eccbf7a18e6d3fae6", size = 83178, upload-time = "2024-09-19T02:40:08.598Z" },
67
+ ]
68
+
69
+ [[package]]
70
+ name = "pyrsistent"
71
+ version = "0.20.0"
72
+ source = { registry = "https://pypi.org/simple" }
73
+ sdist = { url = "https://files.pythonhosted.org/packages/ce/3a/5031723c09068e9c8c2f0bc25c3a9245f2b1d1aea8396c787a408f2b95ca/pyrsistent-0.20.0.tar.gz", hash = "sha256:4c48f78f62ab596c679086084d0dd13254ae4f3d6c72a83ffdf5ebdef8f265a4", size = 103642, upload-time = "2023-10-25T21:06:56.342Z" }
74
+ wheels = [
75
+ { url = "https://files.pythonhosted.org/packages/df/63/7544dc7d0953294882a5c587fb1b10a26e0c23d9b92281a14c2514bac1f7/pyrsistent-0.20.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:0f3b1bcaa1f0629c978b355a7c37acd58907390149b7311b5db1b37648eb6958", size = 83481, upload-time = "2023-10-25T21:06:15.238Z" },
76
+ { url = "https://files.pythonhosted.org/packages/ae/a0/49249bc14d71b1bf2ffe89703acfa86f2017c25cfdabcaea532b8c8a5810/pyrsistent-0.20.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5cdd7ef1ea7a491ae70d826b6cc64868de09a1d5ff9ef8d574250d0940e275b8", size = 120222, upload-time = "2023-10-25T21:06:17.144Z" },
77
+ { url = "https://files.pythonhosted.org/packages/a1/94/9808e8c9271424120289b9028a657da336ad7e43da0647f62e4f6011d19b/pyrsistent-0.20.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:cae40a9e3ce178415040a0383f00e8d68b569e97f31928a3a8ad37e3fde6df6a", size = 120002, upload-time = "2023-10-25T21:06:18.727Z" },
78
+ { url = "https://files.pythonhosted.org/packages/3f/f6/9ecfb78b2fc8e2540546db0fe19df1fae0f56664a5958c21ff8861b0f8da/pyrsistent-0.20.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6288b3fa6622ad8a91e6eb759cfc48ff3089e7c17fb1d4c59a919769314af224", size = 116850, upload-time = "2023-10-25T21:06:20.424Z" },
79
+ { url = "https://files.pythonhosted.org/packages/83/c8/e6d28bc27a0719f8eaae660357df9757d6e9ca9be2691595721de9e8adfc/pyrsistent-0.20.0-cp311-cp311-win32.whl", hash = "sha256:7d29c23bdf6e5438c755b941cef867ec2a4a172ceb9f50553b6ed70d50dfd656", size = 60775, upload-time = "2023-10-25T21:06:21.815Z" },
80
+ { url = "https://files.pythonhosted.org/packages/98/87/c6ef52ff30388f357922d08de012abdd3dc61e09311d88967bdae23ab657/pyrsistent-0.20.0-cp311-cp311-win_amd64.whl", hash = "sha256:59a89bccd615551391f3237e00006a26bcf98a4d18623a19909a2c48b8e986ee", size = 63306, upload-time = "2023-10-25T21:06:22.874Z" },
81
+ { url = "https://files.pythonhosted.org/packages/15/ee/ff2ed52032ac1ce2e7ba19e79bd5b05d152ebfb77956cf08fcd6e8d760ea/pyrsistent-0.20.0-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:09848306523a3aba463c4b49493a760e7a6ca52e4826aa100ee99d8d39b7ad1e", size = 83537, upload-time = "2023-10-25T21:06:24.17Z" },
82
+ { url = "https://files.pythonhosted.org/packages/80/f1/338d0050b24c3132bcfc79b68c3a5f54bce3d213ecef74d37e988b971d8a/pyrsistent-0.20.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a14798c3005ec892bbada26485c2eea3b54109cb2533713e355c806891f63c5e", size = 122615, upload-time = "2023-10-25T21:06:25.815Z" },
83
+ { url = "https://files.pythonhosted.org/packages/07/3a/e56d6431b713518094fae6ff833a04a6f49ad0fbe25fb7c0dc7408e19d20/pyrsistent-0.20.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b14decb628fac50db5e02ee5a35a9c0772d20277824cfe845c8a8b717c15daa3", size = 122335, upload-time = "2023-10-25T21:06:28.631Z" },
84
+ { url = "https://files.pythonhosted.org/packages/4a/bb/5f40a4d5e985a43b43f607250e766cdec28904682c3505eb0bd343a4b7db/pyrsistent-0.20.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:2e2c116cc804d9b09ce9814d17df5edf1df0c624aba3b43bc1ad90411487036d", size = 118510, upload-time = "2023-10-25T21:06:30.718Z" },
85
+ { url = "https://files.pythonhosted.org/packages/1c/13/e6a22f40f5800af116c02c28e29f15c06aa41cb2036f6a64ab124647f28b/pyrsistent-0.20.0-cp312-cp312-win32.whl", hash = "sha256:e78d0c7c1e99a4a45c99143900ea0546025e41bb59ebc10182e947cf1ece9174", size = 60865, upload-time = "2023-10-25T21:06:32.742Z" },
86
+ { url = "https://files.pythonhosted.org/packages/75/ef/2fa3b55023ec07c22682c957808f9a41836da4cd006b5f55ec76bf0fbfa6/pyrsistent-0.20.0-cp312-cp312-win_amd64.whl", hash = "sha256:4021a7f963d88ccd15b523787d18ed5e5269ce57aa4037146a2377ff607ae87d", size = 63239, upload-time = "2023-10-25T21:06:34.035Z" },
87
+ { url = "https://files.pythonhosted.org/packages/23/88/0acd180010aaed4987c85700b7cc17f9505f3edb4e5873e4dc67f613e338/pyrsistent-0.20.0-py3-none-any.whl", hash = "sha256:c55acc4733aad6560a7f5f818466631f07efc001fd023f34a6c203f8b6df0f0b", size = 58106, upload-time = "2023-10-25T21:06:54.387Z" },
88
+ ]
89
+
90
+ [[package]]
91
+ name = "pyyaml"
92
+ version = "6.0.2"
93
+ source = { registry = "https://pypi.org/simple" }
94
+ sdist = { url = "https://files.pythonhosted.org/packages/54/ed/79a089b6be93607fa5cdaedf301d7dfb23af5f25c398d5ead2525b063e17/pyyaml-6.0.2.tar.gz", hash = "sha256:d584d9ec91ad65861cc08d42e834324ef890a082e591037abe114850ff7bbc3e", size = 130631, upload-time = "2024-08-06T20:33:50.674Z" }
95
+ wheels = [
96
+ { url = "https://files.pythonhosted.org/packages/f8/aa/7af4e81f7acba21a4c6be026da38fd2b872ca46226673c89a758ebdc4fd2/PyYAML-6.0.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:cc1c1159b3d456576af7a3e4d1ba7e6924cb39de8f67111c735f6fc832082774", size = 184612, upload-time = "2024-08-06T20:32:03.408Z" },
97
+ { url = "https://files.pythonhosted.org/packages/8b/62/b9faa998fd185f65c1371643678e4d58254add437edb764a08c5a98fb986/PyYAML-6.0.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:1e2120ef853f59c7419231f3bf4e7021f1b936f6ebd222406c3b60212205d2ee", size = 172040, upload-time = "2024-08-06T20:32:04.926Z" },
98
+ { url = "https://files.pythonhosted.org/packages/ad/0c/c804f5f922a9a6563bab712d8dcc70251e8af811fce4524d57c2c0fd49a4/PyYAML-6.0.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5d225db5a45f21e78dd9358e58a98702a0302f2659a3c6cd320564b75b86f47c", size = 736829, upload-time = "2024-08-06T20:32:06.459Z" },
99
+ { url = "https://files.pythonhosted.org/packages/51/16/6af8d6a6b210c8e54f1406a6b9481febf9c64a3109c541567e35a49aa2e7/PyYAML-6.0.2-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5ac9328ec4831237bec75defaf839f7d4564be1e6b25ac710bd1a96321cc8317", size = 764167, upload-time = "2024-08-06T20:32:08.338Z" },
100
+ { url = "https://files.pythonhosted.org/packages/75/e4/2c27590dfc9992f73aabbeb9241ae20220bd9452df27483b6e56d3975cc5/PyYAML-6.0.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3ad2a3decf9aaba3d29c8f537ac4b243e36bef957511b4766cb0057d32b0be85", size = 762952, upload-time = "2024-08-06T20:32:14.124Z" },
101
+ { url = "https://files.pythonhosted.org/packages/9b/97/ecc1abf4a823f5ac61941a9c00fe501b02ac3ab0e373c3857f7d4b83e2b6/PyYAML-6.0.2-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:ff3824dc5261f50c9b0dfb3be22b4567a6f938ccce4587b38952d85fd9e9afe4", size = 735301, upload-time = "2024-08-06T20:32:16.17Z" },
102
+ { url = "https://files.pythonhosted.org/packages/45/73/0f49dacd6e82c9430e46f4a027baa4ca205e8b0a9dce1397f44edc23559d/PyYAML-6.0.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:797b4f722ffa07cc8d62053e4cff1486fa6dc094105d13fea7b1de7d8bf71c9e", size = 756638, upload-time = "2024-08-06T20:32:18.555Z" },
103
+ { url = "https://files.pythonhosted.org/packages/22/5f/956f0f9fc65223a58fbc14459bf34b4cc48dec52e00535c79b8db361aabd/PyYAML-6.0.2-cp311-cp311-win32.whl", hash = "sha256:11d8f3dd2b9c1207dcaf2ee0bbbfd5991f571186ec9cc78427ba5bd32afae4b5", size = 143850, upload-time = "2024-08-06T20:32:19.889Z" },
104
+ { url = "https://files.pythonhosted.org/packages/ed/23/8da0bbe2ab9dcdd11f4f4557ccaf95c10b9811b13ecced089d43ce59c3c8/PyYAML-6.0.2-cp311-cp311-win_amd64.whl", hash = "sha256:e10ce637b18caea04431ce14fabcf5c64a1c61ec9c56b071a4b7ca131ca52d44", size = 161980, upload-time = "2024-08-06T20:32:21.273Z" },
105
+ { url = "https://files.pythonhosted.org/packages/86/0c/c581167fc46d6d6d7ddcfb8c843a4de25bdd27e4466938109ca68492292c/PyYAML-6.0.2-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:c70c95198c015b85feafc136515252a261a84561b7b1d51e3384e0655ddf25ab", size = 183873, upload-time = "2024-08-06T20:32:25.131Z" },
106
+ { url = "https://files.pythonhosted.org/packages/a8/0c/38374f5bb272c051e2a69281d71cba6fdb983413e6758b84482905e29a5d/PyYAML-6.0.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:ce826d6ef20b1bc864f0a68340c8b3287705cae2f8b4b1d932177dcc76721725", size = 173302, upload-time = "2024-08-06T20:32:26.511Z" },
107
+ { url = "https://files.pythonhosted.org/packages/c3/93/9916574aa8c00aa06bbac729972eb1071d002b8e158bd0e83a3b9a20a1f7/PyYAML-6.0.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1f71ea527786de97d1a0cc0eacd1defc0985dcf6b3f17bb77dcfc8c34bec4dc5", size = 739154, upload-time = "2024-08-06T20:32:28.363Z" },
108
+ { url = "https://files.pythonhosted.org/packages/95/0f/b8938f1cbd09739c6da569d172531567dbcc9789e0029aa070856f123984/PyYAML-6.0.2-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9b22676e8097e9e22e36d6b7bda33190d0d400f345f23d4065d48f4ca7ae0425", size = 766223, upload-time = "2024-08-06T20:32:30.058Z" },
109
+ { url = "https://files.pythonhosted.org/packages/b9/2b/614b4752f2e127db5cc206abc23a8c19678e92b23c3db30fc86ab731d3bd/PyYAML-6.0.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:80bab7bfc629882493af4aa31a4cfa43a4c57c83813253626916b8c7ada83476", size = 767542, upload-time = "2024-08-06T20:32:31.881Z" },
110
+ { url = "https://files.pythonhosted.org/packages/d4/00/dd137d5bcc7efea1836d6264f049359861cf548469d18da90cd8216cf05f/PyYAML-6.0.2-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:0833f8694549e586547b576dcfaba4a6b55b9e96098b36cdc7ebefe667dfed48", size = 731164, upload-time = "2024-08-06T20:32:37.083Z" },
111
+ { url = "https://files.pythonhosted.org/packages/c9/1f/4f998c900485e5c0ef43838363ba4a9723ac0ad73a9dc42068b12aaba4e4/PyYAML-6.0.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:8b9c7197f7cb2738065c481a0461e50ad02f18c78cd75775628afb4d7137fb3b", size = 756611, upload-time = "2024-08-06T20:32:38.898Z" },
112
+ { url = "https://files.pythonhosted.org/packages/df/d1/f5a275fdb252768b7a11ec63585bc38d0e87c9e05668a139fea92b80634c/PyYAML-6.0.2-cp312-cp312-win32.whl", hash = "sha256:ef6107725bd54b262d6dedcc2af448a266975032bc85ef0172c5f059da6325b4", size = 140591, upload-time = "2024-08-06T20:32:40.241Z" },
113
+ { url = "https://files.pythonhosted.org/packages/0c/e8/4f648c598b17c3d06e8753d7d13d57542b30d56e6c2dedf9c331ae56312e/PyYAML-6.0.2-cp312-cp312-win_amd64.whl", hash = "sha256:7e7401d0de89a9a855c839bc697c079a4af81cf878373abd7dc625847d25cbd8", size = 156338, upload-time = "2024-08-06T20:32:41.93Z" },
114
+ { url = "https://files.pythonhosted.org/packages/ef/e3/3af305b830494fa85d95f6d95ef7fa73f2ee1cc8ef5b495c7c3269fb835f/PyYAML-6.0.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:efdca5630322a10774e8e98e1af481aad470dd62c3170801852d752aa7a783ba", size = 181309, upload-time = "2024-08-06T20:32:43.4Z" },
115
+ { url = "https://files.pythonhosted.org/packages/45/9f/3b1c20a0b7a3200524eb0076cc027a970d320bd3a6592873c85c92a08731/PyYAML-6.0.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:50187695423ffe49e2deacb8cd10510bc361faac997de9efef88badc3bb9e2d1", size = 171679, upload-time = "2024-08-06T20:32:44.801Z" },
116
+ { url = "https://files.pythonhosted.org/packages/7c/9a/337322f27005c33bcb656c655fa78325b730324c78620e8328ae28b64d0c/PyYAML-6.0.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0ffe8360bab4910ef1b9e87fb812d8bc0a308b0d0eef8c8f44e0254ab3b07133", size = 733428, upload-time = "2024-08-06T20:32:46.432Z" },
117
+ { url = "https://files.pythonhosted.org/packages/a3/69/864fbe19e6c18ea3cc196cbe5d392175b4cf3d5d0ac1403ec3f2d237ebb5/PyYAML-6.0.2-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:17e311b6c678207928d649faa7cb0d7b4c26a0ba73d41e99c4fff6b6c3276484", size = 763361, upload-time = "2024-08-06T20:32:51.188Z" },
118
+ { url = "https://files.pythonhosted.org/packages/04/24/b7721e4845c2f162d26f50521b825fb061bc0a5afcf9a386840f23ea19fa/PyYAML-6.0.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:70b189594dbe54f75ab3a1acec5f1e3faa7e8cf2f1e08d9b561cb41b845f69d5", size = 759523, upload-time = "2024-08-06T20:32:53.019Z" },
119
+ { url = "https://files.pythonhosted.org/packages/2b/b2/e3234f59ba06559c6ff63c4e10baea10e5e7df868092bf9ab40e5b9c56b6/PyYAML-6.0.2-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:41e4e3953a79407c794916fa277a82531dd93aad34e29c2a514c2c0c5fe971cc", size = 726660, upload-time = "2024-08-06T20:32:54.708Z" },
120
+ { url = "https://files.pythonhosted.org/packages/fe/0f/25911a9f080464c59fab9027482f822b86bf0608957a5fcc6eaac85aa515/PyYAML-6.0.2-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:68ccc6023a3400877818152ad9a1033e3db8625d899c72eacb5a668902e4d652", size = 751597, upload-time = "2024-08-06T20:32:56.985Z" },
121
+ { url = "https://files.pythonhosted.org/packages/14/0d/e2c3b43bbce3cf6bd97c840b46088a3031085179e596d4929729d8d68270/PyYAML-6.0.2-cp313-cp313-win32.whl", hash = "sha256:bc2fa7c6b47d6bc618dd7fb02ef6fdedb1090ec036abab80d4681424b84c1183", size = 140527, upload-time = "2024-08-06T20:33:03.001Z" },
122
+ { url = "https://files.pythonhosted.org/packages/fa/de/02b54f42487e3d3c6efb3f89428677074ca7bf43aae402517bc7cca949f3/PyYAML-6.0.2-cp313-cp313-win_amd64.whl", hash = "sha256:8388ee1976c416731879ac16da0aff3f63b286ffdd57cdeb95f3f2e085687563", size = 156446, upload-time = "2024-08-06T20:33:04.33Z" },
123
+ ]
124
+
125
+ [[package]]
126
+ name = "setuptools"
127
+ version = "80.9.0"
128
+ source = { registry = "https://pypi.org/simple" }
129
+ sdist = { url = "https://files.pythonhosted.org/packages/18/5d/3bf57dcd21979b887f014ea83c24ae194cfcd12b9e0fda66b957c69d1fca/setuptools-80.9.0.tar.gz", hash = "sha256:f36b47402ecde768dbfafc46e8e4207b4360c654f1f3bb84475f0a28628fb19c", size = 1319958, upload-time = "2025-05-27T00:56:51.443Z" }
130
+ wheels = [
131
+ { url = "https://files.pythonhosted.org/packages/a3/dc/17031897dae0efacfea57dfd3a82fdd2a2aeb58e0ff71b77b87e44edc772/setuptools-80.9.0-py3-none-any.whl", hash = "sha256:062d34222ad13e0cc312a4c02d73f059e86a4acbfbdea8f8f76b28c99f306922", size = 1201486, upload-time = "2025-05-27T00:56:49.664Z" },
132
+ ]
133
+
134
+ [[package]]
135
+ name = "six"
136
+ version = "1.17.0"
137
+ source = { registry = "https://pypi.org/simple" }
138
+ sdist = { url = "https://files.pythonhosted.org/packages/94/e7/b2c673351809dca68a0e064b6af791aa332cf192da575fd474ed7d6f16a2/six-1.17.0.tar.gz", hash = "sha256:ff70335d468e7eb6ec65b95b99d3a2836546063f63acc5171de367e834932a81", size = 34031, upload-time = "2024-12-04T17:35:28.174Z" }
139
+ wheels = [
140
+ { url = "https://files.pythonhosted.org/packages/b7/ce/149a00dd41f10bc29e5921b496af8b574d8413afcd5e30dfa0ed46c2cc5e/six-1.17.0-py2.py3-none-any.whl", hash = "sha256:4721f391ed90541fddacab5acf947aa0d3dc7d27b2e1e8eda2be8970586c3274", size = 11050, upload-time = "2024-12-04T17:35:26.475Z" },
141
+ ]
142
+
143
+ [[package]]
144
+ name = "tabulate"
145
+ version = "0.8.10"
146
+ source = { registry = "https://pypi.org/simple" }
147
+ sdist = { url = "https://files.pythonhosted.org/packages/7a/53/afac341569b3fd558bf2b5428e925e2eb8753ad9627c1f9188104c6e0c4a/tabulate-0.8.10.tar.gz", hash = "sha256:6c57f3f3dd7ac2782770155f3adb2db0b1a269637e42f27599925e64b114f519", size = 60154, upload-time = "2022-06-21T16:26:42.76Z" }
148
+ wheels = [
149
+ { url = "https://files.pythonhosted.org/packages/92/4e/e5a13fdb3e6f81ce11893523ff289870c87c8f1f289a7369fb0e9840c3bb/tabulate-0.8.10-py3-none-any.whl", hash = "sha256:0ba055423dbaa164b9e456abe7920c5e8ed33fcc16f6d1b2f2d152c8e1e8b4fc", size = 29068, upload-time = "2022-06-21T16:26:37.943Z" },
150
+ ]
@@ -1,82 +0,0 @@
1
- Metadata-Version: 2.4
2
- Name: portmgr
3
- Version: 1.8.0.dev1
4
- Summary: Simple command interface to manage multiple Docker container
5
- License-Expression: MIT
6
- Project-URL: Website, https://github.com/Craeckie/portmgr
7
- Project-URL: Repository, https://github.com/Craeckie/portmgr.git
8
- Requires-Python: >=3.12
9
- Description-Content-Type: text/markdown
10
- License-File: LICENSE
11
- Requires-Dist: humanfriendly~=10.0
12
- Requires-Dist: jsonschema~=3.2.0
13
- Requires-Dist: pyyaml~=6.0.2
14
- Requires-Dist: tabulate~=0.8.9
15
- Dynamic: license-file
16
-
17
- # portmgr
18
- portmgr is a wrapper around [docker-compose](https://docs.docker.com/compose/) that allows running typical docker-compose commands recursively. Additionally, it shortens commands to a single letter.
19
-
20
- Let's say you have organized your compose files like this, you just add a `dckrsub.yml` in each parent folder:
21
- <pre>
22
- docker/
23
- ├── <b>dckrsub.yml</b>
24
- ├── reverse-proxy/
25
- │ └── docker-compose.yml
26
- ├── storage
27
- │ ├── <b>dckrsub.yml</b>
28
- │ ├── nextcloud/
29
- │ │ └── docker-compose.yml
30
- │ └── immich/
31
- │ └── docker-compose.yml
32
- └── scripts
33
- </pre>
34
-
35
- Each `dckrsub.yml` has a list of subdirectories, which portmgr should decend into.
36
- For example, the `dckrsub.yml` in `docker/` might look like this:
37
- ```yaml
38
- - reverse-proxy
39
- - storage
40
- ```
41
-
42
- And the `dckrsub.yml` in `docker/storage/` like this:
43
- ```yaml
44
- - nextcloud
45
- - immich
46
- ```
47
-
48
- Now, if you run `portmgr u` in `docker/` it will run `docker compose up -d` in `reverse-proxy/`, `storage/nextcloud/` and `storage/immich/`.
49
-
50
- portmgr starts from the current directory, so when running it in `docker/storage/`, it will run `docker compose` only in `nextcloud/` and `immich/`. You can also use it in a directory with a `docker-compose.yml` as a shortener for docker-compose commands.
51
-
52
- ### Commands
53
- The following commands are available. The respective docker-compose commands are in brackets.
54
-
55
- ```
56
- u Create and start containers (up)
57
- p Pull images (pull)
58
- s Stop services (stop)
59
- d Stop and remove containers (down)
60
- l Show container logs (logs)
61
- a Run shell in container (exec -it <service> sh)
62
- b Build images (build)
63
- c List containers (ps)
64
- t List processes in containers (top)
65
- r Build and push to registry (build, push)
66
- v Scan container images for vulnerabilities
67
- ```
68
-
69
- You combine multiple commands. For example `portmgr dul`, runs docker compose with `down`, `up` and `logs`, thus stopping, removing and starting all containers and then showing the logs.
70
-
71
- ### Installation
72
- ```
73
- sudo pip install portmgr
74
- ```
75
-
76
- Or build it from source (here using the latest commit on master branch)
77
- ```
78
- sudo pip install https://github.com/Craeckie/portmgr.git
79
- ```
80
-
81
- ### Tipps
82
- If you use portmgr a lot like me, you might want to shorten it to one letter. For bash, you can add `alias p='portmgr'` to `~/.bashrc`. For fish-shell you can add `abbr p portmgr` to `~/.config/fish/config.fish`.
@@ -1,4 +0,0 @@
1
- [egg_info]
2
- tag_build =
3
- tag_date = 0
4
-
@@ -1,2 +0,0 @@
1
- def main() -> None:
2
- print("Hello from portmgr!")
@@ -1,10 +0,0 @@
1
- LICENSE
2
- README.md
3
- pyproject.toml
4
- src/portmgr/__init__.py
5
- src/portmgr.egg-info/PKG-INFO
6
- src/portmgr.egg-info/SOURCES.txt
7
- src/portmgr.egg-info/dependency_links.txt
8
- src/portmgr.egg-info/entry_points.txt
9
- src/portmgr.egg-info/requires.txt
10
- src/portmgr.egg-info/top_level.txt
@@ -1,2 +0,0 @@
1
- [console_scripts]
2
- portmgr = portmgr:main
@@ -1 +0,0 @@
1
- portmgr
File without changes
File without changes