mantis-cli 17.0.0__tar.gz → 19.0.0__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 (26) hide show
  1. {mantis-cli-17.0.0/mantis_cli.egg-info → mantis-cli-19.0.0}/PKG-INFO +3 -6
  2. mantis-cli-19.0.0/mantis/__init__.py +1 -0
  3. {mantis-cli-17.0.0 → mantis-cli-19.0.0}/mantis/logic.py +7 -3
  4. {mantis-cli-17.0.0 → mantis-cli-19.0.0}/mantis/managers.py +104 -64
  5. {mantis-cli-17.0.0 → mantis-cli-19.0.0}/mantis/mantis.tpl +7 -7
  6. {mantis-cli-17.0.0 → mantis-cli-19.0.0/mantis_cli.egg-info}/PKG-INFO +3 -6
  7. {mantis-cli-17.0.0 → mantis-cli-19.0.0}/mantis_cli.egg-info/entry_points.txt +1 -0
  8. mantis-cli-17.0.0/mantis/__init__.py +0 -1
  9. {mantis-cli-17.0.0 → mantis-cli-19.0.0}/LICENSE +0 -0
  10. {mantis-cli-17.0.0 → mantis-cli-19.0.0}/MANIFEST.in +0 -0
  11. {mantis-cli-17.0.0 → mantis-cli-19.0.0}/README.md +0 -0
  12. {mantis-cli-17.0.0 → mantis-cli-19.0.0}/mantis/__main__.py +0 -0
  13. {mantis-cli-17.0.0 → mantis-cli-19.0.0}/mantis/command_line.py +0 -0
  14. {mantis-cli-17.0.0 → mantis-cli-19.0.0}/mantis/crypto.py +0 -0
  15. {mantis-cli-17.0.0 → mantis-cli-19.0.0}/mantis/environment.py +0 -0
  16. {mantis-cli-17.0.0 → mantis-cli-19.0.0}/mantis/extensions/__init__.py +0 -0
  17. {mantis-cli-17.0.0 → mantis-cli-19.0.0}/mantis/extensions/django.py +0 -0
  18. {mantis-cli-17.0.0 → mantis-cli-19.0.0}/mantis/extensions/nginx.py +0 -0
  19. {mantis-cli-17.0.0 → mantis-cli-19.0.0}/mantis/extensions/postgres.py +0 -0
  20. {mantis-cli-17.0.0 → mantis-cli-19.0.0}/mantis/helpers.py +0 -0
  21. {mantis-cli-17.0.0 → mantis-cli-19.0.0}/mantis_cli.egg-info/SOURCES.txt +0 -0
  22. {mantis-cli-17.0.0 → mantis-cli-19.0.0}/mantis_cli.egg-info/dependency_links.txt +0 -0
  23. {mantis-cli-17.0.0 → mantis-cli-19.0.0}/mantis_cli.egg-info/requires.txt +0 -0
  24. {mantis-cli-17.0.0 → mantis-cli-19.0.0}/mantis_cli.egg-info/top_level.txt +0 -0
  25. {mantis-cli-17.0.0 → mantis-cli-19.0.0}/setup.cfg +0 -0
  26. {mantis-cli-17.0.0 → mantis-cli-19.0.0}/setup.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: mantis-cli
3
- Version: 17.0.0
3
+ Version: 19.0.0
4
4
  Summary: Management command to build and deploy webapps, especially based on Django
5
5
  Home-page: https://github.com/PragmaticMates/mantis-cli
6
6
  Author: Erik Telepovský
@@ -9,6 +9,7 @@ Maintainer: Pragmatic Mates
9
9
  Maintainer-email: info@pragmaticmates.com
10
10
  License: GNU General Public License (GPL)
11
11
  Keywords: management deployment docker command
12
+ Platform: UNKNOWN
12
13
  Classifier: Programming Language :: Python
13
14
  Classifier: Operating System :: OS Independent
14
15
  Classifier: Environment :: Web Environment
@@ -18,11 +19,6 @@ Classifier: License :: OSI Approved :: GNU General Public License (GPL)
18
19
  Classifier: Development Status :: 5 - Production/Stable
19
20
  Description-Content-Type: text/markdown
20
21
  License-File: LICENSE
21
- Requires-Dist: cffi
22
- Requires-Dist: cryptography
23
- Requires-Dist: pycryptodome
24
- Requires-Dist: PyYAML
25
- Requires-Dist: prettytable
26
22
 
27
23
  # mantis-cli
28
24
 
@@ -341,3 +337,4 @@ Works as follows:
341
337
  ## Release notes
342
338
 
343
339
  Mantis uses semantic versioning. See more in [changelog](https://github.com/PragmaticMates/mantis-cli/blob/master/CHANGES.md).
340
+
@@ -0,0 +1 @@
1
+ VERSION = '19.0.0'
@@ -102,11 +102,15 @@ def load_config(config_file):
102
102
  CLI.error(f"Failed to load config from file {config_file}: {e}")
103
103
 
104
104
 
105
- def check_config(config):
106
- # Load config template file
105
+ def load_template_config():
107
106
  current_directory = dirname(abspath(__file__))
108
107
  template_path = normpath(f'{current_directory}/mantis.tpl')
109
- template = load_config(template_path)
108
+ return load_config(template_path)
109
+
110
+
111
+ def check_config(config):
112
+ # Load config template file
113
+ template = load_template_config()
110
114
 
111
115
  # validate config file
112
116
  config_keys_only = find_keys_only_in_config(config, template)
@@ -2,6 +2,7 @@ import json
2
2
  import os
3
3
  import time
4
4
  import yaml
5
+ from collections import defaultdict
5
6
  from os import path
6
7
  from os.path import dirname, normpath
7
8
  from time import sleep
@@ -9,7 +10,7 @@ from time import sleep
9
10
  from mantis.crypto import Crypto
10
11
  from mantis.environment import Environment
11
12
  from mantis.helpers import CLI, Colors
12
- from mantis.logic import find_config, load_config, check_config
13
+ from mantis.logic import find_config, load_config, check_config, load_template_config
13
14
 
14
15
 
15
16
  class AbstractManager(object):
@@ -37,7 +38,7 @@ class AbstractManager(object):
37
38
  self.init_environment()
38
39
 
39
40
  self.KEY = self.read_key()
40
- self.encrypt_deterministically = self.config.get('encryption', {}).get('deterministic', True)
41
+ self.encrypt_deterministically = self.config['encryption']['deterministic']
41
42
 
42
43
  @property
43
44
  def host(self):
@@ -101,7 +102,7 @@ class AbstractManager(object):
101
102
  setattr(self, property_name, details)
102
103
 
103
104
  # set project path
104
- self.project_path = self.config.get('project_path', f'/home/{self.user}/public_html/web')
105
+ self.project_path = self.config['project_path']
105
106
 
106
107
  return details
107
108
 
@@ -122,19 +123,24 @@ class AbstractManager(object):
122
123
  return ''
123
124
 
124
125
  def init_config(self, config):
125
- self.config = config
126
- check_config(self.config)
127
- self.config_file_path = path.normpath(path.join(self.config_file, os.pardir))
126
+ check_config(config)
127
+ config_file_path = path.normpath(path.join(self.config_file, os.pardir))
128
128
 
129
129
  def normalize(path):
130
- return os.path.normpath(path.replace('<MANTIS>', self.config_file_path))
130
+ return os.path.normpath(path.replace('<MANTIS>', config_file_path))
131
+
132
+ # Load config template file
133
+ defaults = load_template_config()
134
+
135
+ # Merge custom config and default values
136
+ defaults.update(config)
131
137
 
132
- self.key_path = normalize(self.config.get('encryption', {}).get('folder', '<MANTIS>'))
133
- self.key_file = normalize(path.join(self.key_path, 'mantis.key'))
134
- self.configs_path = normalize(self.config.get('configs', {}).get('folder', '<MANTIS>/configs'))
135
- self.environment_path = normalize(self.config.get('environment', {}).get('folder', '<MANTIS>/environments'))
136
- self.compose_path = normalize(self.config.get('compose', {}).get('folder', '<MANTIS>/configs/compose'))
137
- self.compose_command = self.config.get('compose', {}).get('command', 'docker compose')
138
+ # Save merged config to variable
139
+ self.config = defaults.copy()
140
+
141
+ self.key_file = normalize(path.join(self.config['encryption']['folder'], 'mantis.key'))
142
+ self.environment_path = normalize(self.config['environment']['folder'])
143
+ self.compose_path = normalize(path.join(self.config['compose']['folder'], self.environment_id))
138
144
 
139
145
  def init_environment(self):
140
146
  self.env = Environment(
@@ -143,16 +149,13 @@ class AbstractManager(object):
143
149
  )
144
150
 
145
151
  # connection
146
- self.connection = self.config.get('connections', {}).get(self.env.id, None)
152
+ self.connection = self.config['connections'].get(self.env.id, None)
147
153
 
148
- # compose file
149
- compose_prefix = f"docker-compose.{self.config.get('compose', {}).get('name', '')}".rstrip('.')
150
- self.compose_file = os.path.join(self.compose_path, f'{compose_prefix}.{self.env.id}.yml')
154
+ # compose files
155
+ compose_file_paths = os.popen(f'find {self.compose_path} -name "*.yml" -o -name "*.yaml"').read().strip().split('\n')
151
156
 
152
- # containers
153
- self.PROJECT_NAME = self.project_name()
154
- self.CONTAINER_PREFIX = self.PROJECT_NAME
155
- self.IMAGE_PREFIX = self.PROJECT_NAME
157
+ # Remove empty strings
158
+ self.compose_files = list(filter(None, compose_file_paths))
156
159
 
157
160
  def check_environment_encryption(self, env_file):
158
161
  decrypted_environment = self.decrypt_env(env_file=env_file, return_value=True) # .env.encrypted
@@ -233,8 +236,12 @@ class AbstractManager(object):
233
236
  )
234
237
 
235
238
  def docker_compose(self, command, return_output=False, use_connection=True):
239
+ compose_command = self.config['compose']['command']
240
+
241
+ compose_files = ' '.join([f'-f {compose_file}' for compose_file in self.compose_files])
242
+
236
243
  return self.docker_command(
237
- command=f'{self.compose_command} -f {self.compose_file} {command}',
244
+ command=f'{compose_command} {compose_files} {command}',
238
245
  return_output=return_output,
239
246
  use_connection=use_connection
240
247
  )
@@ -267,7 +274,7 @@ class AbstractManager(object):
267
274
  containers = list(filter(None, containers))
268
275
 
269
276
  # get project containers only
270
- containers = list(filter(lambda c: self.get_container_project(c) == self.PROJECT_NAME, containers))
277
+ containers = list(filter(lambda c: self.get_container_project(c) in self.project_services().keys(), containers))
271
278
 
272
279
  # find containers starting with custom prefix
273
280
  containers = list(filter(lambda s: s.startswith(prefix), containers))
@@ -526,7 +533,8 @@ class BaseManager(AbstractManager):
526
533
  Constructs container name with project prefix for given service
527
534
  """
528
535
  suffix = self.get_container_suffix(service)
529
- return f'{self.CONTAINER_PREFIX}{suffix}'.replace('_', '-')
536
+ prefix = self.get_project_by_service(service)
537
+ return f'{prefix}{suffix}'.replace('_', '-')
530
538
 
531
539
  def get_service_containers(self, service):
532
540
  """
@@ -553,7 +561,8 @@ class BaseManager(AbstractManager):
553
561
  Constructs image name for given service
554
562
  """
555
563
  suffix = self.get_image_suffix(service)
556
- return f'{self.IMAGE_PREFIX}{suffix}'.replace('-', '_')
564
+ prefix = self.get_project_by_service(service)
565
+ return f'{prefix}{suffix}'.replace('-', '_')
557
566
 
558
567
  def has_healthcheck(self, container):
559
568
  """
@@ -647,7 +656,7 @@ class BaseManager(AbstractManager):
647
656
  CLI.info(f'Params = {params}')
648
657
 
649
658
  # Construct build args from config
650
- build_args = self.config.get('build', {}).get('args', {})
659
+ build_args = self.config['build']['args']
651
660
  build_args = ','.join(map('='.join, build_args.items()))
652
661
 
653
662
  if build_args != '':
@@ -657,7 +666,7 @@ class BaseManager(AbstractManager):
657
666
 
658
667
  CLI.info(f'Args = {build_args}')
659
668
 
660
- build_tool = self.config.get('build', {}).get('tool', 'compose')
669
+ build_tool = self.config['build']['tool']
661
670
  available_tools = ['compose', 'docker']
662
671
 
663
672
  if build_tool == 'compose':
@@ -671,7 +680,7 @@ class BaseManager(AbstractManager):
671
680
  image = info['image'] if info['image'] != '' else f"{info['project_name']}-{service}".lstrip('-')
672
681
 
673
682
  # build paths for docker build command (paths in compose are relative to compose file, but paths for docker command are relative to $PWD)
674
- context = normpath(path.join(dirname(self.compose_file), info['context']))
683
+ context = normpath(path.join(dirname(self.compose_path), info['context']))
675
684
  dockerfile = normpath(path.join(context, info['dockerfile']))
676
685
 
677
686
  # Build service using docker
@@ -680,47 +689,73 @@ class BaseManager(AbstractManager):
680
689
  else:
681
690
  CLI.error(f'Unknown build tool: {build_tool}. Available tools: {", ".join(available_tools)}')
682
691
 
683
- def project_name(self):
692
+ def project_services(self):
684
693
  """
685
- Returns project name by compose name
694
+ Returns project names by compose files
686
695
  """
687
- with open(self.compose_file, 'r') as file:
688
- compose_data = yaml.safe_load(file)
696
+ projects = defaultdict(list)
697
+
698
+ for compose_file in self.compose_files:
699
+ with open(compose_file, 'r') as file:
700
+ compose_data = yaml.safe_load(file)
701
+ name = compose_data.get('name', '')
702
+ services = compose_data.get('services', {}).keys()
703
+ projects[name].extend(services)
704
+
705
+ return projects
706
+
707
+ def get_project_by_service(self, service):
708
+ project_services = self.project_services()
709
+
710
+ for project, services in project_services.items():
711
+ if service in services:
712
+ return project
689
713
 
690
- return compose_data.get('name', '')
714
+ return None
691
715
 
692
- def services(self):
716
+ def services(self, compose_file=None):
693
717
  """
694
- Prints all defined services
718
+ Returns all defined services
695
719
  """
696
- with open(self.compose_file, 'r') as file:
697
- compose_data = yaml.safe_load(file)
720
+ services = []
721
+
722
+ compose_files = [compose_file] if compose_file else self.compose_files
723
+
724
+ for compose_file in compose_files:
725
+ with open(compose_file, 'r') as file:
726
+ compose_data = yaml.safe_load(file)
727
+ compose_services = compose_data.get('services', {}).keys()
698
728
 
699
- return compose_data.get('services', {}).keys()
729
+ services += compose_services
700
730
 
701
- def services_to_build(self):
731
+ return services
732
+
733
+ def services_to_build(self, compose_file=None):
702
734
  """
703
735
  Prints all services which will be build
704
736
  """
705
- with open(self.compose_file, 'r') as file:
706
- compose_data = yaml.safe_load(file)
707
-
708
737
  data = {}
709
738
 
710
- services = compose_data.get('services', {})
711
- for service_name, service_config in services.items():
712
- build = service_config.get('build', None)
713
-
714
- if build:
715
- data[service_name] = {
716
- 'project_name': compose_data.get('name', ''),
717
- 'dockerfile': build.get('dockerfile', 'Dockerfile'),
718
- 'context': build.get('context', '.'),
719
- 'cache_from': build.get('cache_from', []),
720
- 'args': build.get('args', {}),
721
- 'image': service_config.get('image', ''),
722
- 'platform': service_config.get('platform', '')
723
- }
739
+ compose_files = [compose_file] if compose_file else self.compose_files
740
+
741
+ for compose_file in compose_files:
742
+ with open(compose_file, 'r') as file:
743
+ compose_data = yaml.safe_load(file)
744
+
745
+ services = compose_data.get('services', {})
746
+ for service_name, service_config in services.items():
747
+ build = service_config.get('build', None)
748
+
749
+ if build:
750
+ data[service_name] = {
751
+ 'project_name': compose_data.get('name', ''),
752
+ 'dockerfile': build.get('dockerfile', 'Dockerfile'),
753
+ 'context': build.get('context', '.'),
754
+ 'cache_from': build.get('cache_from', []),
755
+ 'args': build.get('args', {}),
756
+ 'image': service_config.get('image', ''),
757
+ 'platform': service_config.get('platform', '')
758
+ }
724
759
 
725
760
  return data
726
761
 
@@ -757,7 +792,7 @@ class BaseManager(AbstractManager):
757
792
  elif self.mode == 'ssh':
758
793
  CLI.info('Uploading docker compose configs, environment files and mantis')
759
794
 
760
- files_to_upload = [self.config_file, self.compose_file] + self.env.files
795
+ files_to_upload = [self.config_file] + self.compose_files + self.env.files
761
796
 
762
797
  # mantis config file
763
798
  for file in files_to_upload:
@@ -834,7 +869,7 @@ class BaseManager(AbstractManager):
834
869
  Runs zero-downtime deployment of services (or given service)
835
870
  """
836
871
  if not service:
837
- zero_downtime_services = self.config.get('zero_downtime', [])
872
+ zero_downtime_services = self.config['zero_downtime']
838
873
  for index, service in enumerate(zero_downtime_services):
839
874
  CLI.step(index + 1, len(zero_downtime_services), f'Zero downtime services: {zero_downtime_services}')
840
875
  self.zero_downtime(service)
@@ -1127,10 +1162,15 @@ class BaseManager(AbstractManager):
1127
1162
  """
1128
1163
  Returns default number of deploy replicas of given services
1129
1164
  """
1130
- with open(self.compose_file, 'r') as file:
1131
- compose_data = yaml.safe_load(file)
1165
+ replicas = 1
1132
1166
 
1133
- try:
1134
- return compose_data['services'][service]['deploy']['replicas']
1135
- except KeyError:
1136
- return 1
1167
+ for compose_file in self.compose_files:
1168
+ with open(compose_file, 'r') as file:
1169
+ compose_data = yaml.safe_load(file)
1170
+
1171
+ try:
1172
+ replicas = compose_data['services'][service]['deploy']['replicas']
1173
+ except KeyError:
1174
+ pass
1175
+
1176
+ return replicas
@@ -12,25 +12,25 @@
12
12
  },
13
13
  "encryption": {
14
14
  "deterministic": true,
15
- "folder": ""
15
+ "folder": "<MANTIS>"
16
16
  },
17
17
  "configs": {
18
- "folder": "./"
18
+ "folder": "<MANTIS>/.."
19
19
  },
20
20
  "build": {
21
- "tool": "docker"
21
+ "tool": "compose",
22
+ "args": {}
22
23
  },
23
24
  "compose": {
24
25
  "command": "docker-compose",
25
- "name": "",
26
- "folder": ""
26
+ "folder": "<MANTIS>/../compose"
27
27
  },
28
28
  "environment": {
29
- "folder": "env",
29
+ "folder": "<MANTIS>/../environments",
30
30
  "file_prefix": ""
31
31
  },
32
32
  "zero_downtime": [],
33
- "project_path": "",
33
+ "project_path": "~",
34
34
  "connections": {
35
35
  }
36
36
  }
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: mantis-cli
3
- Version: 17.0.0
3
+ Version: 19.0.0
4
4
  Summary: Management command to build and deploy webapps, especially based on Django
5
5
  Home-page: https://github.com/PragmaticMates/mantis-cli
6
6
  Author: Erik Telepovský
@@ -9,6 +9,7 @@ Maintainer: Pragmatic Mates
9
9
  Maintainer-email: info@pragmaticmates.com
10
10
  License: GNU General Public License (GPL)
11
11
  Keywords: management deployment docker command
12
+ Platform: UNKNOWN
12
13
  Classifier: Programming Language :: Python
13
14
  Classifier: Operating System :: OS Independent
14
15
  Classifier: Environment :: Web Environment
@@ -18,11 +19,6 @@ Classifier: License :: OSI Approved :: GNU General Public License (GPL)
18
19
  Classifier: Development Status :: 5 - Production/Stable
19
20
  Description-Content-Type: text/markdown
20
21
  License-File: LICENSE
21
- Requires-Dist: cffi
22
- Requires-Dist: cryptography
23
- Requires-Dist: pycryptodome
24
- Requires-Dist: PyYAML
25
- Requires-Dist: prettytable
26
22
 
27
23
  # mantis-cli
28
24
 
@@ -341,3 +337,4 @@ Works as follows:
341
337
  ## Release notes
342
338
 
343
339
  Mantis uses semantic versioning. See more in [changelog](https://github.com/PragmaticMates/mantis-cli/blob/master/CHANGES.md).
340
+
@@ -1,2 +1,3 @@
1
1
  [console_scripts]
2
2
  mantis = mantis.command_line:run
3
+
@@ -1 +0,0 @@
1
- VERSION = '17.0.0'
File without changes
File without changes
File without changes
File without changes
File without changes