Skip to content

Commit 3ec2db2

Browse files
committed
Merge pull request #39 from l-d-j/submodules
Allow custom submodules
2 parents 49d42a4 + c06ea30 commit 3ec2db2

File tree

7 files changed

+128
-60
lines changed

7 files changed

+128
-60
lines changed

README.md

+6-4
Original file line numberDiff line numberDiff line change
@@ -70,8 +70,6 @@ You can implement any of the methods that are typically used with Behave inside
7070
1. Clone repositories
7171
```
7272
git clone https://github.com/Containers-Testing-Framework/ctf-cli.git
73-
git clone https://github.com/Containers-Testing-Framework/common-steps.git
74-
git clone https://github.com/Containers-Testing-Framework/common-features.git
7573
```
7674
1. Optional: clone an example repository
7775
```
@@ -90,10 +88,14 @@ cd ctf-cli
9088
cp ctf.conf.sample ctf.conf
9189
cp tests.conf.sample tests.conf
9290
```
93-
1. Change to project directory and run tests
91+
1. Change to project directory and get common features
9492
```
9593
cd example-project-postgresql
96-
../ctf-cli/ctf-cli.py
94+
../ctf-cli/ctf-cli.py remote add features https://github.com/Containers-Testing-Framework/common-features.git
95+
```
96+
1. Run tests
97+
```
98+
../ctf-cli/ctf-cli.py run
9799
```
98100

99101
## CLI tool

circle.yml

+3-1
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,8 @@ test:
1515
override:
1616
- mkdir test_dir
1717
- cd test_dir && COVERAGE_FILE=../.coverage.init coverage run ../ctf-cli.py init
18+
- cd test_dir && COVAREGE_FILE=../.coverage.remote_add coverage run ../ctf-cli.py remote add steps https://github.com/Containers-Testing-Framework/common-steps.git
19+
- cd test_dir && COVAREGE_FILE=../.coverage.remote_add coverage run ../ctf-cli.py remote add features https://github.com/Containers-Testing-Framework/common-features.git
1820
- cd test_dir && COVERAGE_FILE=../.coverage.update coverage run ../ctf-cli.py update
1921
- cd test_dir && COVERAGE_FILE=../.coverage.run coverage run ../ctf-cli.py run
2022
- COVERAGE_FILE=.coverage.tests nosetests
@@ -23,4 +25,4 @@ test:
2325

2426
notify:
2527
webhooks:
26-
- url: https://webhooks.gitter.im/e/92b168ffacdc38a6facd
28+
- url: https://webhooks.gitter.im/e/92b168ffacdc38a6facd

ctf_cli/application.py

+41-29
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
# along with this program. If not, see <http://www.gnu.org/licenses/>.
1818

1919
import os
20+
import shutil
2021
import sys
2122
from subprocess import check_call, CalledProcessError
2223

@@ -99,23 +100,7 @@ def init(self):
99100
with open(steps_py_file, "w") as f:
100101
f.write(common_steps_py_content)
101102
check_call("git add %s" % steps_py_file, shell=True)
102-
103-
# Add common-features and common-steps as submodules
104-
# TODO: make this generic when a different type of container is specified
105-
common_features_dir = os.path.join(features_dir, "common-features")
106-
if os.path.exists(common_features_dir):
107-
logger.info("Directory tests/features/common-features already exists")
108-
else:
109-
logger.info("Adding tests/features/common-features as a submodule")
110-
check_call('git submodule add https://github.com/Containers-Testing-Framework/common-features.git tests/features/common-features', shell=True)
111-
112-
common_steps_dir = os.path.join(steps_dir, "common_steps")
113-
if os.path.exists(common_steps_dir):
114-
logger.info("Directory tests/steps/common_steps already exists")
115-
else:
116-
logger.info("Adding tests/steps/common_steps as a submodule")
117-
check_call('git submodule add https://github.com/Containers-Testing-Framework/common-steps.git tests/steps/common_steps', shell=True)
118-
103+
119104
# Copy sample configuration
120105
ctf_conf_file = os.path.join(self._execution_dir_path, "ctf.conf")
121106
if os.path.exists(ctf_conf_file):
@@ -127,6 +112,42 @@ def init(self):
127112
f.write(sample_ctl_ctf_config)
128113
check_call("git add %s" % ctf_conf_file, shell=True)
129114

115+
def add_remote(self):
116+
if 'feature' in self._cli_conf.get(CTFCliConfig.GLOBAL_SECTION_NAME, CTFCliConfig.CONFIG_REMOTE_TYPE):
117+
self.add_remote_feature()
118+
else:
119+
self.add_remote_step()
120+
121+
def add_remote_feature(self):
122+
project = self._cli_conf.get(CTFCliConfig.GLOBAL_SECTION_NAME, CTFCliConfig.CONFIG_REMOTE_PROJECT)
123+
if project is None:
124+
path = "tests/features/"
125+
else:
126+
path = "tests/features/" + project
127+
self.add_submodule(path)
128+
129+
def add_remote_step(self):
130+
project = self._cli_conf.get(CTFCliConfig.GLOBAL_SECTION_NAME, CTFCliConfig.CONFIG_REMOTE_PROJECT)
131+
if project is None:
132+
path = "tests/steps/"
133+
else:
134+
path = "tests/steps/" + project
135+
self.add_submodule(path)
136+
137+
def list_remotes(self):
138+
check_call("git submodule foreach 'git config --get remote.origin.url'", shell=True)
139+
140+
def add_submodule(self, path):
141+
url = self._cli_conf.get(CTFCliConfig.GLOBAL_SECTION_NAME, CTFCliConfig.CONFIG_REMOTE_URL)
142+
dirname = os.path.splitext(os.path.basename(url))[0].replace('-', '_')
143+
check_call('git submodule add %s %s' % (url, path + "/" + dirname), shell=True)
144+
145+
def remove_remote(self):
146+
name = self._cli_conf.get(CTFCliConfig.GLOBAL_SECTION_NAME, CTFCliConfig.CONFIG_REMOTE_NAME)
147+
check_call('git submodule deinit -f %s' %name, shell=True)
148+
check_call('git config -f .gitmodules --remove-section "submodule.%s"' %name, shell=True)
149+
shutil.rmtree(name)
150+
130151
def run(self):
131152
"""
132153
The main application execution method
@@ -163,15 +184,6 @@ def update(self):
163184
"""
164185
Update app submodules
165186
"""
166-
logger.info("Updating Containers Testing Framework common steps and features")
167-
168-
common_steps_dir = os.path.join(self._execution_dir_path, "tests", "steps", "common_steps")
169-
common_features_dir = os.path.join(self._execution_dir_path, "tests", "features", "common-features")
170-
for directory in [common_steps_dir, common_features_dir]:
171-
logger.info("Updating %s", directory)
172-
check_call("git fetch origin", shell=True, cwd=directory)
173-
check_call("git checkout origin/master", shell=True, cwd=directory)
174-
175-
# Check that steps are not contradicting with each other
176-
logger.info("Checking project steps sanity")
177-
check_call("behave tests -d", shell=True)
187+
logger.info("Updating remote test and feautres")
188+
189+
check_call("git submodule foreach git pull --rebase", shell=True)

ctf_cli/arguments_parser.py

+60-21
Original file line numberDiff line numberDiff line change
@@ -25,62 +25,101 @@ class ArgumentsParser(object):
2525
def __init__(self, args=None):
2626
""" parse arguments """
2727
self.parser = argparse.ArgumentParser(description='CLI for running Containers Testing Framework')
28+
self.subparsers = self.parser.add_subparsers(dest="cli_action")
2829
self.add_args()
30+
self.add_remote_subparser()
31+
self.add_init_subparser()
32+
self.add_run_subparser()
33+
self.add_update_subparser()
2934
self.args = self.parser.parse_args(args)
3035

31-
def add_args(self):
32-
self.parser.add_argument(
33-
dest='cli_action',
34-
choices=['init', 'run', 'update'],
35-
default=['init', 'run'],
36-
nargs='?',
37-
help="Action to perform (default - init and run)"
38-
)
39-
self.parser.add_argument(
40-
"-v",
41-
"--verbose",
42-
default=False,
43-
action="store_true",
44-
help="Output is more verbose (recommended)"
45-
)
46-
self.parser.add_argument(
36+
def add_remote_add_subparser(self, subparser):
37+
subparser.add_argument(
38+
dest='remote_type',
39+
choices=['steps', 'features'],
40+
)
41+
42+
subparser.add_argument(
43+
dest='url',
44+
help='module url'
45+
)
46+
47+
subparser.add_argument(
48+
"--project",
49+
dest='project',
50+
help="name of test project")
51+
52+
def add_remote_remove_subparser(self, subparser):
53+
subparser.add_argument(
54+
dest='name'
55+
)
56+
57+
def add_remote_subparser(self):
58+
remote_subparser=self.subparsers.add_parser('remote', help='addidng/removing test suites')
59+
remote_oper_subparser=remote_subparser.add_subparsers(dest='remote_action')
60+
self.add_remote_add_subparser(remote_oper_subparser.add_parser('add', help='add remote repository'))
61+
self.add_remote_remove_subparser(remote_oper_subparser.add_parser('remove', help='remove remote repository'))
62+
remote_oper_subparser.add_parser('list', help='list remote repositories')
63+
64+
def add_run_subparser(self):
65+
run_subparser=self.subparsers.add_parser('run', help="run test suite - default")
66+
run_subparser.add_argument(
4767
"-c",
4868
"--cli-config",
4969
default=None,
5070
dest='cli_config_path',
5171
help="Path to CLI configuration file (By default use only CLI arguments and default values)"
5272
)
53-
self.parser.add_argument(
73+
run_subparser.add_argument(
5474
"-t",
5575
"--tests-config",
5676
default=None,
5777
dest='tests_config_path',
5878
help="Path to tests configuration file. By default it will be searched for in test/ dir"
5979
)
60-
self.parser.add_argument(
80+
run_subparser.add_argument(
6181
"-f",
6282
"--dockerfile",
6383
default=None,
6484
dest='dockerfile',
6585
help="Path to Dockerfile to use. If not passed, will be searched for in the current directory"
6686
)
67-
self.parser.add_argument(
87+
run_subparser.add_argument(
6888
"-i",
6989
"--image",
7090
default=None,
7191
dest='image',
7292
help="Image to use for testing. If not passed, the image will be built from the Dockerfile."
7393
)
74-
self.parser.add_argument(
94+
run_subparser.add_argument(
7595
"-j",
7696
"--junit",
7797
default=None,
7898
dest='junit',
7999
help="Junit folder to store results. If not passed junit reports will not be generated"
80100
)
81101

102+
def add_update_subparser(self):
103+
self.subparsers.add_parser('update', help="update suites")
104+
105+
def add_init_subparser(self):
106+
self.subparsers.add_parser('init', help="update suites")
107+
108+
def add_args(self):
109+
self.parser.add_argument(
110+
"-v",
111+
"--verbose",
112+
default=False,
113+
action="store_true",
114+
help="Output is more verbose (recommended)"
115+
)
116+
117+
82118
def __getattr__(self, name):
83119
try:
84120
return getattr(self.args, name)
85121
except AttributeError:
86-
return object.__getattribute__(self, name)
122+
try:
123+
return object.__getattribute__(self, name)
124+
except AttributeError:
125+
return None

ctf_cli/behave.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -272,7 +272,7 @@ def _write_environment_py(self):
272272
"""
273273
project_env_py = glob.glob(os.path.join(self._working_dir, '*environment.py'))
274274
if project_env_py:
275-
module_name = os.path.basename(project_env_py[0]).replace('.py', '')
275+
module_name = os.path.basename(project_env_py[0]).replace('.py', '').replace('-', '_')
276276
import_statement = 'from {0} import *\n'.format(module_name)
277277
import_statement += 'import {0}'.format(module_name)
278278
else:

ctf_cli/cli_runner.py

+8-2
Original file line numberDiff line numberDiff line change
@@ -35,9 +35,7 @@ def run():
3535
try:
3636
# add application-wide debug log
3737
LoggerHelper.add_debug_log_file(os.getcwd())
38-
3938
args = ArgumentsParser(sys.argv[1:])
40-
4139
if args.verbose is True:
4240
LoggerHelper.add_stream_handler(logger,
4341
logging.Formatter('%(levelname)s:\t%(message)s'),
@@ -54,6 +52,14 @@ def run():
5452
app.run()
5553
if 'update' in args.cli_action:
5654
app.update()
55+
if 'remote' in args.cli_action:
56+
if 'add' in args.remote_action:
57+
app.add_remote()
58+
if 'remove' in args.remote_action:
59+
app.remove_remote()
60+
if 'list' in args.remote_action:
61+
app.list_remotes()
62+
5763
except KeyboardInterrupt:
5864
logger.info('Interrupted by user')
5965
except CTFCliError as e:

ctf_cli/config.py

+9-2
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,10 @@ class CTFCliConfig(object):
3838
CONFIG_IMAGE = 'Image'
3939
CONFIG_JUNIT = 'Junit'
4040
CONFIG_EXEC_TYPE = 'ExecType'
41+
CONFIG_REMOTE_TYPE = 'remote_type'
42+
CONFIG_REMOTE_URL = 'url'
43+
CONFIG_REMOTE_PROJECT = 'project'
44+
CONFIG_REMOTE_NAME = 'name'
4145

4246
ANSIBLE_SECTION_NAME = 'ansible'
4347
CONFIG_ANSIBLE_HOST = 'Host'
@@ -99,8 +103,11 @@ def _add_commandline_arguments(self, cli_conf):
99103
self.CONFIG_IMAGE: cli_conf.image,
100104
self.CONFIG_JUNIT: cli_conf.junit,
101105
self.CONFIG_EXEC_TYPE: 'ansible',
106+
self.CONFIG_REMOTE_TYPE: cli_conf.remote_type,
107+
self.CONFIG_REMOTE_URL: cli_conf.url,
108+
self.CONFIG_REMOTE_PROJECT: cli_conf.project,
109+
self.CONFIG_REMOTE_NAME: cli_conf.name,
102110
}}
103-
104111
self.config_parser_read_dict(self._config, cli_settings)
105112

106113
def __getattr__(self, name):
@@ -133,4 +140,4 @@ def config_parser_read_dict(config_parser_obj, dictionary):
133140
# go through all key: value in the section
134141
for option, value in six.iteritems(conf):
135142
if config_parser_obj.has_option(section, option) is False:
136-
config_parser_obj.set(section, option, value)
143+
config_parser_obj.set(section, option, value)

0 commit comments

Comments
 (0)