diff --git a/.gitignore b/.gitignore index 68bc17f..5582937 100644 --- a/.gitignore +++ b/.gitignore @@ -158,3 +158,5 @@ cython_debug/ # and can be added to the global gitignore or merged into this file. For a more nuclear # option (not recommended) you can uncomment the following to ignore the entire idea folder. #.idea/ + +.github/workflows/test_**.yml \ No newline at end of file diff --git a/README.md b/README.md index 263df7f..59ce4b3 100644 --- a/README.md +++ b/README.md @@ -1,10 +1,11 @@ # ci generator CI generator is a tool to generate CI configuration files for your project. -It's a command line tool that can be used to generate CI configuration files for your project. It's written in Python and uses Jinja2 templates to generate the files. +It's a command line tool that can be used to generate CI configuration files for your project. It's written in Python and uses templates to generate the files. + Possible CI systems are: -- [ ] Github Actions +- [x] Github Actions - [ ] Jenkins - [ ] Docker - [ ] Gitlab CI @@ -31,6 +32,80 @@ pip install ci-generator cigen --help ``` +### Github Actions + +```bash +cigen github-actions --help +``` + +github-actions subcommand can be used to generate Build and Test Github Actions configuration files. + +github-actions Golang example: + +```bash +cigen github-actions go -n myproject -b push main -a 1 -v 1.21.1 +``` + +github-actions Python example: + +```bash +cigen github-actions python -n myproject -b push main -a 1 -v 3.9.6 +``` + +### Jenkins + +```bash +cigen jenkins --help +``` + +jenkins subcommand can be used to generate Build and Test Jenkins configuration files. + +jenkins Golang example: + +```bash +cigen jenkins go -n myproject -b push main -a 1 -v 1.21.1 +``` + +jenkins Python example: + +```bash +cigen jenkins python -n myproject -b push main -a 1 -v 3.9.6 +``` + +### Docker + +```bash +cigen docker --help +``` + +docker subcommand can be used to generate Docker configuration files. + +docker example: + +```bash +cigen docker -n dockerfile -i golang -v 1.21.1 -s multi +``` + +### Gitlab CI + +```bash +cigen gitlab-ci --help +``` + +gitlab-ci subcommand can be used to generate Build and Test Gitlab CI configuration files. + +gitlab-ci Golang example: + +```bash +cigen gitlab-ci go -n myproject -b push main -a 1 -v 1.21.1 +``` + +gitlab-ci Python example: + +```bash +cigen gitlab-ci python -n myproject -b push main -a 1 -v 3.9.6 +``` + ## Contributing Pull requests are welcome. For major changes, please open an issue first to discuss what you would like to change. diff --git a/cigen/adapter/input/command.py b/cigen/__init__.py similarity index 100% rename from cigen/adapter/input/command.py rename to cigen/__init__.py diff --git a/cigen/__main__.py b/cigen/__main__.py new file mode 100644 index 0000000..412e4f4 --- /dev/null +++ b/cigen/__main__.py @@ -0,0 +1,5 @@ +from cigen.cmd.command import cli + + +if __name__ == '__main__': + cli() diff --git a/cigen/cmd/cli.py b/cigen/adapter/__init__.py similarity index 100% rename from cigen/cmd/cli.py rename to cigen/adapter/__init__.py diff --git a/cigen/adapter/input/__init__.py b/cigen/adapter/input/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/cigen/adapter/input/docker_command/__init__.py b/cigen/adapter/input/docker_command/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/cigen/adapter/input/docker_command/docker_group.py b/cigen/adapter/input/docker_command/docker_group.py new file mode 100644 index 0000000..ffc181e --- /dev/null +++ b/cigen/adapter/input/docker_command/docker_group.py @@ -0,0 +1,9 @@ +import click + + +@click.group() +def docker(): + """ + This is the main command for the Docker + """ + pass diff --git a/cigen/adapter/input/github_command/__init__.py b/cigen/adapter/input/github_command/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/cigen/adapter/input/github_command/github_action_group.py b/cigen/adapter/input/github_command/github_action_group.py new file mode 100644 index 0000000..54165f6 --- /dev/null +++ b/cigen/adapter/input/github_command/github_action_group.py @@ -0,0 +1,13 @@ +import click +from cigen.adapter.input.github_command.go_command import action_go + + +@click.group() +def github_action(): + """ + This is the main command for the GitHub Actions + """ + pass + + +github_action.add_command(action_go) diff --git a/cigen/adapter/input/github_command/go_command.py b/cigen/adapter/input/github_command/go_command.py new file mode 100644 index 0000000..8a79ac2 --- /dev/null +++ b/cigen/adapter/input/github_command/go_command.py @@ -0,0 +1,226 @@ +import pprint + +import click + +from cigen.adapter.output.github_out.file_action import generate_action +from cigen.core.github.github_action import OnEventFactory +from cigen.core.github.go_action import GoActionBuilderImpl, ActionCIGenGolang + +ACTION_DOC_LONG = """ + Actions: + 0 - base 2 - version_list 3 - build_base_with_version_list 4 - checkout 5 - setup_go 6 - + setup_go_with_version_list 7 - build 8 - cache 9 - install_dependencies 10 - tests 11 - tests_and_coverage + 12 - tests_and_coverage_with_coverage 13 - tests_and_coverage_with_coverage_and_html 14 - + tests_and_coverage_with_coverage_and_html_and_upload + + base ou version_list 0 ou 1 is required like last parameter + :example: 2,3,5 0 + """ + + +def action_params_valid(action_ciGen_golang, action): + action_ciGen = None + lastElement = action.split(" ")[len(action.split(" ")) - 1] + + dropLastElement = action.split(" ") + if len(dropLastElement) > 1: + dropLastElement.pop() + dropLastElement = dropLastElement[0].split(",") + + elements = dropLastElement + + if "2" in elements or "1" in elements: + if len(elements) > 1: + click.echo(""" + The action after the value 1 or 2 is [optional] the last value after is a base action of a simple code. + The action 1 or 2 cannot be followed by any value separated by a comma. the last parameter is [optional] base. + + Example: 1 or 2 - is valid + Example: 1 0 or 2 1 - is valid + Example: 1,2 0 or 2,3 1 - is invalid + """) + return + else: + if len(action.split(",")) == 1: + click.echo("Action required 2 parameters! Example: 3,4,6 0") + return + + for actions in elements: + if actions.isnumeric(): + actions = int(actions) + action_ciGen = action_builder(action_ciGen_golang, actions) + + if lastElement == "0" or elements[0] == "1": + click.echo(pprint.pprint(action_ciGen.action_build_base())) + return action_ciGen.action_build_base() + elif lastElement == "1" or elements[0] == "2": + click.echo(pprint.pprint(action_ciGen.action_build_base_with_version_list())) + return action_ciGen.action_build_base_with_version_list() + else: + click.echo("Action not found") + return + + +def on_events(listEvent: list[str]) -> dict: + OnEventsName = {} + branchesName = {} + + if len(listEvent) == 1: + names = listEvent[0].split(" ") + branchesName['branches'] = [names[1]] + OnEventsName[names[0]] = branchesName + return OnEventsName + + if len(listEvent) == 2: + branchesName['branches'] = listEvent[1].split(",") + OnEventsName[listEvent[0]] = branchesName + return OnEventsName + + countElements = 0 + for i in range(len(listEvent)): + if countElements >= len(listEvent): + break + + branchesName['branches'] = listEvent[countElements + 1].split(",") + OnEventsName[listEvent[countElements]] = branchesName + countElements += 2 + + return OnEventsName + + +@click.command("go", help="Generate action github actions for go", epilog=ACTION_DOC_LONG) +@click.option("-n", "--name", nargs=1, help="Name of the action", prompt="Name of the action", required=True) +@click.option("-b", "--branch_name", help="Branch to add", prompt="Name of the branch", required=True) +@click.option("-a", "--action", help="Action to add", prompt="Action to add", required=True) +@click.option("-v", "--version", nargs=1, help="Version of go [default=1.17]", type=click.STRING, default="1.17", + required=False) +def action_go(name, branch_name, action, version): + version = version.split(",") + if len(version) == 1: + version = [version[0]] + + if len(branch_name.split(" ")) == 1: + click.echo("Branch required 2 parameters! Example: push main,master") + return + + on_event = OnEventFactory.create_events(on_events(branch_name.split(","))) + action_ciGen_golang = ActionCIGenGolang() + action_ciGen_golang.builder = GoActionBuilderImpl(name, version, on_event) + + ciGen = action_params_valid(action_ciGen_golang, action) + + confirm = click.confirm("Do you want to generate the action?") + if confirm: + print("Generating action...") + nameFile = name.replace(" ", "_") + pathFile = ".github/workflows/{}.yml".format(nameFile.lower()) + generate_action(path=pathFile, content=ciGen) + else: + click.echo("Aborted!") + + +def action_builder(action_ciGen_golang, actions): + action_mapping = { + "build_base": { + "action_id": 1, + "steps": [ + action_ciGen_golang.builder.step_checkout, + action_ciGen_golang.builder.step_setup_go, + action_ciGen_golang.builder.step_run_build, + action_ciGen_golang.builder.step_run_tests, + ] + }, + "build_base_with_version_list": { + "action_id": 2, + "steps": [ + action_ciGen_golang.builder.step_checkout, + action_ciGen_golang.builder.step_setup_go_with_versions_matrix, + action_ciGen_golang.builder.step_run_build, + action_ciGen_golang.builder.step_run_tests + ] + }, + "checkout": { + "action_id": 3, + "steps": [ + action_ciGen_golang.builder.step_checkout + ] + }, + "setup_go": { + "action_id": 4, + "steps": [ + action_ciGen_golang.builder.step_setup_go + ] + }, + "setup_go_with_version_list": { + "action_id": 5, + "steps": [ + action_ciGen_golang.builder.step_setup_go_with_versions_matrix + ] + }, + "build": { + "action_id": 6, + "steps": [ + action_ciGen_golang.builder.step_run_build + ] + }, + "cache": { + "action_id": 7, + "steps": [ + action_ciGen_golang.builder.step_run_cache + ] + }, + "install_dependencies": { + "action_id": 8, + "steps": [ + action_ciGen_golang.builder.step_run_install_dependencies + ] + }, + "tests": { + "action_id": 9, + "steps": [ + action_ciGen_golang.builder.step_run_tests + ] + }, + "tests_and_coverage": { + "action_id": 10, + "steps": [ + action_ciGen_golang.builder.step_run_tests_and_coverage + ] + }, + "tests_and_coverage_with_coverage": { + "action_id": 11, + "steps": [ + action_ciGen_golang.builder.step_run_tests_and_coverage_with_coverage + ] + }, + "tests_and_coverage_with_coverage_and_html": { + "action_id": 12, + "steps": [ + action_ciGen_golang.builder.step_run_tests_and_coverage_with_coverage_and_html + ] + }, + "tests_and_coverage_with_coverage_and_html_and_upload": { + "action_id": 13, + "steps": [ + action_ciGen_golang.builder.step_run_tests_and_coverage_with_coverage_and_html_and_upload + ] + } + } + + if isinstance(actions, int): + matching_action = next((key for key, value in action_mapping.items() if value["action_id"] == actions), + None) + if matching_action: + action_data = action_mapping[matching_action] + for step in action_data["steps"]: + step() + else: + print("Action ID not found") + elif actions in action_mapping: + action_data = action_mapping[actions] + for step in action_data["steps"]: + step() + else: + print("Action not found") + + return action_ciGen_golang diff --git a/cigen/adapter/input/github_command/go_command_test.py b/cigen/adapter/input/github_command/go_command_test.py new file mode 100644 index 0000000..9eb881e --- /dev/null +++ b/cigen/adapter/input/github_command/go_command_test.py @@ -0,0 +1,21 @@ +import unittest + +from cigen.adapter import go_command +from click.testing import CliRunner + + +class GoCommandTestCase(unittest.TestCase): + runner = CliRunner() + + def test_something(self): + self.assertEqual(True, True) # add assertion here + + def test_action_build_base(self): + result = self.runner.invoke(go_command.action_go, ["-n", "Go Action", "-b", "push main", "-a", "1"], input="y") + + self.assertEqual(result.exit_code, 0) + self.assertIn("'name': 'Go Action'", result.output) + + +if __name__ == '__main__': + unittest.main() diff --git a/cigen/adapter/input/github_command_in/go_command.py b/cigen/adapter/input/github_command_in/go_command.py deleted file mode 100644 index d7e7ce6..0000000 --- a/cigen/adapter/input/github_command_in/go_command.py +++ /dev/null @@ -1,142 +0,0 @@ -import pprint - -import click - -from cigen.core.github.github_action import OnEventFactory -from cigen.core.github.go_action import GoActionBuilderImpl, ActionCIGenGolang - - -class AddCommand: - def __init__(self): - pass - - @staticmethod - def list_steps(): - return [ - "build_base", - "build_base_with_version_list", - "checkout", - "setup_go", - "setup_go_with_version_list", - "build", - "cache", - "install_dependencies", - "tests", - "tests_and_coverage", - "tests_and_coverage_with_coverage", - "tests_and_coverage_with_coverage_and_html", - "tests_and_coverage_with_coverage_and_html_and_upload", - ] - - @staticmethod - def on_events(listEvent: list[str]) -> dict: - branchs = {} - brancheName = {} - if len(listEvent) == 2: - brancheName['branches'] = listEvent[1].split(",") - branchs[listEvent[0]] = brancheName - return branchs - - countElements = 0 - for i in range(len(listEvent)): - if countElements >= len(listEvent): - break - - brancheName['branches'] = listEvent[countElements + 1].split(",") - branchs[listEvent[countElements]] = brancheName - countElements += 2 - - return branchs - - @click.command() - @click.option("--step", "-s", nargs=-1, type=click.Choice(list_steps()), help="Steps list", required=False) - def steps(self, steps): - click.echo(pprint.pprint(self.list_steps())) - - @click.command() - @click.option("-n", "--name", nargs=1, help="Name of the action", prompt="Name of the action", required=True) - @click.option("-b", "--branch_name", nargs=-1, help="Branch to add", prompt="Name of the branch", required=True) - @click.option("-a", "--action", nargs=-1, help="Action to add", default="go", required=True) - @click.option("-v", "--version", nargs=1, help="Version of go", type=click.STRING, default="1.17", required=False) - def action(self, name, branch_name, action, version): - """ - - :param name: - :param branch_name: - :param action: - :param version: - :return: - - Actions: - 0 - base - 1 - version_list - 2 - build_base - 3 - build_base_with_version_list - 4 - checkout - 5 - setup_go - 6 - setup_go_with_version_list - 7 - build - 8 - cache - 9 - install_dependencies - 10 - tests - 11 - tests_and_coverage - 12 - tests_and_coverage_with_coverage - 13 - tests_and_coverage_with_coverage_and_html - 14 - tests_and_coverage_with_coverage_and_html_and_upload - - base ou version_list 0 ou 1 is required like last parameter - - :example: 2,3,5 0 - - - """ - - on_events = OnEventFactory.create_events(self.on_events(branch_name)) - action_ciGen_golang = ActionCIGenGolang() - action_ciGen_golang.builder = GoActionBuilderImpl(name, version, on_events) - for actions in action: - self.action_builder(action_ciGen_golang, actions) - - @staticmethod - def action_builder(action_ciGen_golang, actions): - if actions == "base" or actions == 0: - action_ciGen_golang.action_build_base() - elif actions == "version_list" or actions == 1: - action_ciGen_golang.action_build_base_with_version_list() - if actions == "build_base" or actions == 2: - action_ciGen_golang.builder.step_checkout() - action_ciGen_golang.builder.step_setup_go() - action_ciGen_golang.builder.step_run_build() - action_ciGen_golang.builder.step_run_tests() - action_ciGen_golang.action_build_base() - elif actions == "build_base_with_version_list" or actions == 3: - action_ciGen_golang.builder.step_checkout() - action_ciGen_golang.builder.step_setup_go() - action_ciGen_golang.builder.step_run_build() - action_ciGen_golang.builder.step_run_tests() - action_ciGen_golang.action_build_base_with_version_list() - elif actions == "checkout" or actions == 4: - action_ciGen_golang.builder.step_checkout() - elif actions == "setup_go" or actions == 5: - action_ciGen_golang.builder.step_setup_go() - elif actions == "setup_go_with_version_list" or actions == 6: - action_ciGen_golang.builder.step_setup_go_with_version_list() - elif actions == "build" or actions == 7: - action_ciGen_golang.builder.step_run_build() - elif actions == "cache" or actions == 8: - action_ciGen_golang.builder.step_run_cache() - elif actions == "install_dependencies" or actions == 9: - action_ciGen_golang.builder.step_run_install_dependencies() - elif actions == "tests" or actions == 10: - action_ciGen_golang.builder.step_run_tests() - elif actions == "tests_and_coverage" or actions == 11: - action_ciGen_golang.builder.step_run_tests_and_coverage() - elif actions == "tests_and_coverage_with_coverage" or actions == 12: - action_ciGen_golang.builder.step_run_tests_and_coverage_with_coverage() - elif actions == "tests_and_coverage_with_coverage_and_html" or actions == 13: - action_ciGen_golang.builder.step_run_tests_and_coverage_with_coverage_and_html() - elif actions == "tests_and_coverage_with_coverage_and_html_and_upload" or actions == 14: - action_ciGen_golang.builder.step_run_tests_and_coverage_with_coverage_and_html_and_upload() - else: - click.echo("Action not found") - return diff --git a/cigen/adapter/input/gitlab_command/__init__.py b/cigen/adapter/input/gitlab_command/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/cigen/adapter/input/gitlab_command/gitlab_group.py b/cigen/adapter/input/gitlab_command/gitlab_group.py new file mode 100644 index 0000000..5dfbf8f --- /dev/null +++ b/cigen/adapter/input/gitlab_command/gitlab_group.py @@ -0,0 +1,9 @@ +import click + + +@click.group() +def gitlab(): + """ + This is the main command for the GitLab + """ + pass diff --git a/cigen/adapter/input/jenkins_command/__init__.py b/cigen/adapter/input/jenkins_command/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/cigen/adapter/input/jenkins_command/jenkins_group.py b/cigen/adapter/input/jenkins_command/jenkins_group.py new file mode 100644 index 0000000..fac3e7b --- /dev/null +++ b/cigen/adapter/input/jenkins_command/jenkins_group.py @@ -0,0 +1,9 @@ +import click + + +@click.group() +def jenkins(): + """ + This is the main command for the Jenkins + """ + pass diff --git a/cigen/adapter/output/__init__.py b/cigen/adapter/output/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/cigen/adapter/output/github_out/__init__.py b/cigen/adapter/output/github_out/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/cigen/cmd/__init__.py b/cigen/cmd/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/cigen/cmd/command.py b/cigen/cmd/command.py new file mode 100644 index 0000000..50e6847 --- /dev/null +++ b/cigen/cmd/command.py @@ -0,0 +1,20 @@ +import click +from cigen.adapter.input.github_command import github_action_group +from cigen.adapter.input.gitlab_command import gitlab_group +from cigen.adapter.input.jenkins_command import jenkins_group +from cigen.adapter.input.docker_command import docker_group + + + +@click.group() +def cli(): + """ + ciGen is a Continuous Integration Generator + """ + pass + + +cli.add_command(github_action_group.github_action) +cli.add_command(gitlab_group.gitlab) +cli.add_command(jenkins_group.jenkins) +cli.add_command(docker_group.docker) diff --git a/cigen/core/github/go_action.py b/cigen/core/github/go_action.py index 0b979c1..63a15fc 100644 --- a/cigen/core/github/go_action.py +++ b/cigen/core/github/go_action.py @@ -206,7 +206,7 @@ def __init__(self, name, version, on, steps: Steps, env=None) -> None: def base(self): print(self.on) - return { + action_base = { 'name': self.name, "on": self.on, 'jobs': { @@ -217,12 +217,13 @@ def base(self): } } } + return self.order_json(action_base, ['name', 'on', 'jobs']) def base_version_list(self): if self.version == [] or self.version == "" or self.version is None: self.version = ['1.19', '1.20', '1.21.x'] - return { + action_base = { 'name': self.name, 'on': self.on, 'jobs': { @@ -238,6 +239,7 @@ def base_version_list(self): } } } + return self.order_json(action_base, ['name', 'on', 'jobs']) def order_json(self, json_obj, ordem): ordered_json = {key: json_obj[key] for key in ordem if key in json_obj} diff --git a/cigen/core/github/go_action_test.py b/cigen/core/github/go_action_test.py index 880ef4e..10efdd0 100644 --- a/cigen/core/github/go_action_test.py +++ b/cigen/core/github/go_action_test.py @@ -1,6 +1,5 @@ import unittest -from cigen.adapter.output.github_out.file_action import generate_action from cigen.core.github.go_action import GoAction, GoActionSteps, ActionCIGenGolang, GoActionBuilderImpl from cigen.core.github.github_action import On, Steps, Push, PullRequest, OnEventFactory diff --git a/main.py b/main.py deleted file mode 100644 index 1dc45ac..0000000 --- a/main.py +++ /dev/null @@ -1 +0,0 @@ -print("Hello World!") \ No newline at end of file diff --git a/requirements.txt b/requirements.txt index d13be23..2992534 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,14 +1,6 @@ -pip==23.3.1 wheel==0.37.1 PyYAML==6.0.1 -build==1.0.3 click==8.1.7 setuptools==58.0.4 -## The following requirements were added by pip freeze: -markdown-it-py==3.0.0 -mdurl==0.1.2 -packaging==23.2 -pip-tools==7.3.0 -Pygments==2.17.1 -pyproject_hooks==1.0.0 rich==13.7.0 +twine \ No newline at end of file diff --git a/setup.py b/setup.py index be49cea..a8e5440 100644 --- a/setup.py +++ b/setup.py @@ -4,13 +4,23 @@ README = fh.read() setup( - name='ci_generator', + name='cigen', version='0.1.0', description='A Continuous Integration Generator', author='Paulo Lopes Estevao', - packages=find_packages(), + packages=find_packages(include=['cigen', 'cigen.*']), include_package_data=True, + install_requires=[ + 'Click', + 'PyYAML', + 'rich', + ], license='MIT', long_description=README, url='https://github.com/Paulo-Lopes-Estevao/ci-generator', + entry_points={ + 'console_scripts': [ + 'cigen = cigen.__main__:cli', + ] + }, ) \ No newline at end of file