Skip to content

Commit

Permalink
Add GitHub Action plugin and tests
Browse files Browse the repository at this point in the history
  • Loading branch information
Sebastian Simon committed Oct 9, 2024
1 parent a405148 commit 7e1a83d
Show file tree
Hide file tree
Showing 9 changed files with 176 additions and 43 deletions.
8 changes: 4 additions & 4 deletions src/cfgnet/config_types/config_type_inferer.py
Original file line number Diff line number Diff line change
Expand Up @@ -84,7 +84,7 @@ class ConfigTypeInferer:

@staticmethod
def is_boolean(value: str) -> bool:
return bool(re.match(ConfigTypeInferer.regex_boolean, value))
return bool(re.fullmatch(ConfigTypeInferer.regex_boolean, value))

# pylint: disable=too-many-return-statements
@staticmethod
Expand All @@ -95,6 +95,9 @@ def get_config_type( # noqa: C901
# Check option name and value against types for which an option name and value regex exists.
option_name = option_name.split(".")[-1]

if ConfigTypeInferer.is_boolean(value):
return ConfigType.BOOLEAN

if bool(
re.match(ConfigTypeInferer.regex_port_option, option_name)
) and bool(re.fullmatch(ConfigTypeInferer.regex_port_value, value)):
Expand Down Expand Up @@ -192,9 +195,6 @@ def get_config_type( # noqa: C901
if bool(re.fullmatch(ConfigTypeInferer.regex_number, value)):
return ConfigType.NUMBER

if bool(re.fullmatch(ConfigTypeInferer.regex_boolean, value)):
return ConfigType.BOOLEAN

if bool(re.fullmatch(ConfigTypeInferer.regex_size_value, value)):
return ConfigType.SIZE

Expand Down
47 changes: 47 additions & 0 deletions src/cfgnet/plugins/concept/github_actions_plugin.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
# This file is part of the CfgNet module.
#
# This program is free software: you can redistribute it and/or modify it under
# the terms of the GNU General Public License as published by the Free Software
# Foundation, either version 3 of the License, or (at your option) any later
# version.
#
# This program is distributed in the hope that it will be useful, but WITHOUT
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
# details.
#
# You should have received a copy of the GNU General Public License along with
# this program. If not, see <https://www.gnu.org/licenses/>.
import re
from cfgnet.plugins.file_type.yaml_plugin import YAMLPlugin
from cfgnet.config_types.config_types import ConfigType


class GitHubActionPlugin(YAMLPlugin):
file_name = re.compile(r".*?\.github\/workflows\/[^\/]*\.yml$")

def __init__(self):
super().__init__("github-action")

def is_responsible(self, abs_file_path):
if self.file_name.search(abs_file_path):
return True
return False

def get_config_type(self, option_name: str) -> ConfigType:
"""
Find config type based on option name.
:param option_name: name of option
:return: config type
"""
if option_name in ("run"):
return ConfigType.COMMAND

if option_name in ("name", "uses"):
return ConfigType.NAME

if option_name in ("python-version"):
return ConfigType.VERSION_NUMBER

return ConfigType.UNKNOWN
19 changes: 10 additions & 9 deletions src/cfgnet/plugins/file_type/yaml_plugin.py
Original file line number Diff line number Diff line change
Expand Up @@ -80,17 +80,18 @@ def _parse_mapping_node(self, node, parent):
self._iter_tree(child, parent)

def _parse_sequence_node(self, node, parent):
index = 0
# index = 0
for child in node.value:
if isinstance(child, MappingNode):
offset_option = OptionNode(
"offset:" + str(index),
node.start_mark.line + 1,
)
parent.add_child(offset_option)

self._iter_tree(child, offset_option)
index += 1
# offset_option = OptionNode(
# "offset:" + str(index),
# node.start_mark.line + 1,
# )
# parent.add_child(offset_option)

# self._iter_tree(child, offset_option)
self._iter_tree(child, parent)
# index += 1
else:
self._iter_tree(child, parent)

Expand Down
2 changes: 2 additions & 0 deletions src/cfgnet/plugins/plugin_manager.py
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@
from cfgnet.plugins.concept.mapreduce_plugin import MapReducePlugin
from cfgnet.plugins.concept.circleci_plugin import CircleCiPlugin
from cfgnet.plugins.concept.cargo_plugin import CargoPlugin
from cfgnet.plugins.concept.github_actions_plugin import GitHubActionPlugin


class PluginManager:
Expand Down Expand Up @@ -87,6 +88,7 @@ class PluginManager:
MapReducePlugin(),
CircleCiPlugin(),
CargoPlugin(),
GitHubActionPlugin(),
]

file_type_plugins: List[Plugin] = [
Expand Down
30 changes: 15 additions & 15 deletions tests/cfgnet/plugins/concept/test_ansible_playbook_plugin.py
Original file line number Diff line number Diff line change
Expand Up @@ -62,15 +62,15 @@ def test_parse_ansible_playbook_file(get_plugin):

assert artifact is not None
assert len(nodes) == 34
assert make_id("playbook.yml", "offset:0", "remote_user", "root") in ids
assert make_id("playbook.yml", "offset:0", "tasks", "offset:0", "ansible.builtin.yum", "name", "httpd") in ids
assert make_id("playbook.yml", "offset:2", "tasks", "offset:0", "win_get_url", "url", "https://test.html") in ids
assert make_id("playbook.yml", "offset:3", "tasks", "offset:0", "win_user", "password", "test123") in ids
assert make_id("playbook.yml", "offset:3", "tasks", "offset:0", "win_user", "state", "present") in ids
assert make_id("playbook.yml", "offset:4", "tasks", "offset:0", "ansible.builtin.git", "dest", "/home/www") in ids
assert make_id("playbook.yml", "offset:4", "tasks", "offset:0", "ansible.builtin.git", "accept_hostkey", "true") in ids
assert make_id("playbook.yml", "offset:4", "tasks", "offset:0", "ansible.builtin.git", "version", "master") in ids
assert make_id("playbook.yml", "offset:4", "hosts", "localhost") in ids
assert make_id("playbook.yml", "remote_user", "root") in ids
assert make_id("playbook.yml", "tasks", "ansible.builtin.yum", "name", "httpd") in ids
assert make_id("playbook.yml", "tasks", "win_get_url", "url", "https://test.html") in ids
assert make_id("playbook.yml", "tasks", "win_user", "password", "test123") in ids
assert make_id("playbook.yml", "tasks", "win_user", "state", "present") in ids
assert make_id("playbook.yml", "tasks", "ansible.builtin.git", "dest", "/home/www") in ids
assert make_id("playbook.yml", "tasks", "ansible.builtin.git", "accept_hostkey", "true") in ids
assert make_id("playbook.yml", "tasks", "ansible.builtin.git", "version", "master") in ids
assert make_id("playbook.yml", "hosts", "localhost") in ids


def test_config_types(get_plugin):
Expand All @@ -79,12 +79,12 @@ def test_config_types(get_plugin):
artifact = ansible_playbook_plugin.parse_file(playbook_file, "playbook.yml")
nodes = artifact.get_nodes()

path_node = next(filter(lambda x: x.id == make_id("playbook.yml", "offset:4", "tasks", "offset:0", "ansible.builtin.git", "dest", "/home/www"), nodes))
name_node = next(filter(lambda x: x.id == make_id("playbook.yml", "offset:0", "tasks", "offset:0", "ansible.builtin.yum", "name", "httpd"), nodes))
state_node = next(filter(lambda x: x.id == make_id("playbook.yml", "offset:3", "tasks", "offset:0", "win_user", "state", "present"), nodes))
url_node = next(filter(lambda x: x.id == make_id("playbook.yml", "offset:2", "tasks", "offset:0", "win_get_url", "url", "https://test.html"), nodes))
password_node = next(filter(lambda x: x.id == make_id("playbook.yml", "offset:3", "tasks", "offset:0", "win_user", "password", "test123"), nodes))
host_node = next(filter(lambda x: x.id == make_id("playbook.yml", "offset:4", "hosts", "localhost"), nodes))
path_node = next(filter(lambda x: x.id == make_id("playbook.yml", "tasks", "ansible.builtin.git", "dest", "/home/www"), nodes))
name_node = next(filter(lambda x: x.id == make_id("playbook.yml", "tasks", "ansible.builtin.yum", "name", "httpd"), nodes))
state_node = next(filter(lambda x: x.id == make_id("playbook.yml", "tasks", "win_user", "state", "present"), nodes))
url_node = next(filter(lambda x: x.id == make_id("playbook.yml", "tasks", "win_get_url", "url", "https://test.html"), nodes))
password_node = next(filter(lambda x: x.id == make_id("playbook.yml", "tasks", "win_user", "password", "test123"), nodes))
host_node = next(filter(lambda x: x.id == make_id("playbook.yml", "hosts", "localhost"), nodes))

assert path_node.config_type == ConfigType.PATH
assert name_node.config_type == ConfigType.NAME
Expand Down
28 changes: 14 additions & 14 deletions tests/cfgnet/plugins/concept/test_circleci_plugin.py
Original file line number Diff line number Diff line change
Expand Up @@ -55,20 +55,20 @@ def test_parse_circle_file(get_plugin):
assert len(nodes) == 16

assert make_id("config.yml", "file", "config.yml") in ids
assert make_id("config.yml", "jobs", "build", "docker", "offset:0", "image", "circleci/node:14") in ids
assert make_id("config.yml", "jobs", "build", "docker", "image", "circleci/node:14") in ids
assert make_id("config.yml", "jobs", "build", "steps", "checkout") in ids
assert make_id("config.yml", "jobs", "build", "steps", "offset:0", "run", "name", "Install dependencies") in ids
assert make_id("config.yml", "jobs", "build", "steps", "offset:0", "run", "command", "npm install") in ids
assert make_id("config.yml", "jobs", "build", "steps", "offset:1", "persist_to_workspace", "root", ".") in ids
assert make_id("config.yml", "jobs", "build", "steps", "offset:1", "persist_to_workspace", "paths", "dist") in ids
assert make_id("config.yml", "jobs", "build", "steps", "offset:1", "persist_to_workspace", "paths", "src") in ids
assert make_id("config.yml", "jobs", "deploy", "docker", "offset:0", "image", "circleci/node:14") in ids
assert make_id("config.yml", "jobs", "deploy", "steps", "offset:0", "attach_workspace", "at", "/workspace") in ids
assert make_id("config.yml", "jobs", "deploy", "steps", "offset:1", "run", "name", "Deploy application") in ids
assert make_id("config.yml", "jobs", "deploy", "steps", "offset:1", "run", "command", 'echo "Deploying application..."') in ids
assert make_id("config.yml", "jobs", "build", "steps", "run", "name", "Install dependencies") in ids
assert make_id("config.yml", "jobs", "build", "steps", "run", "command", "npm install") in ids
assert make_id("config.yml", "jobs", "build", "steps", "persist_to_workspace", "root", ".") in ids
assert make_id("config.yml", "jobs", "build", "steps", "persist_to_workspace", "paths", "dist") in ids
assert make_id("config.yml", "jobs", "build", "steps", "persist_to_workspace", "paths", "src") in ids
assert make_id("config.yml", "jobs", "deploy", "docker", "image", "circleci/node:14") in ids
assert make_id("config.yml", "jobs", "deploy", "steps", "attach_workspace", "at", "/workspace") in ids
assert make_id("config.yml", "jobs", "deploy", "steps", "run", "name", "Deploy application") in ids
assert make_id("config.yml", "jobs", "deploy", "steps", "run", "command", 'echo "Deploying application..."') in ids
assert make_id("config.yml", "workflows", "version", "2") in ids
assert make_id("config.yml", "workflows", "build_and_deploy", "jobs", "build") in ids
assert make_id("config.yml", "workflows", "build_and_deploy", "jobs", "offset:0", "deploy", "requires", "build") in ids
assert make_id("config.yml", "workflows", "build_and_deploy", "jobs", "deploy", "requires", "build") in ids


def test_config_types(get_plugin):
Expand All @@ -79,9 +79,9 @@ def test_config_types(get_plugin):
nodes = artifact.get_nodes()

version_node = next(filter(lambda x: x.id == make_id("config.yml", "workflows", "version", "2"), nodes))
command_node = next(filter(lambda x: x.id == make_id("config.yml", "jobs", "build", "steps", "offset:0", "run", "command", "npm install"), nodes))
path_node = next(filter(lambda x: x.id == make_id("config.yml", "jobs", "deploy", "steps", "offset:0", "attach_workspace", "at", "/workspace"), nodes))
image_node = next(filter(lambda x: x.id == make_id("config.yml", "jobs", "build", "docker", "offset:0", "image", "circleci/node:14"), nodes))
command_node = next(filter(lambda x: x.id == make_id("config.yml", "jobs", "build", "steps", "run", "command", "npm install"), nodes))
path_node = next(filter(lambda x: x.id == make_id("config.yml", "jobs", "deploy", "steps", "attach_workspace", "at", "/workspace"), nodes))
image_node = next(filter(lambda x: x.id == make_id("config.yml", "jobs", "build", "docker", "image", "circleci/node:14"), nodes))

assert version_node.config_type == ConfigType.VERSION_NUMBER
assert command_node.config_type == ConfigType.COMMAND
Expand Down
51 changes: 51 additions & 0 deletions tests/cfgnet/plugins/concept/test_github_action_plugin.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
# This file is part of the CfgNet module.
#
# This program is free software: you can redistribute it and/or modify it under
# the terms of the GNU General Public License as published by the Free Software
# Foundation, either version 3 of the License, or (at your option) any later
# version.
#
# This program is distributed in the hope that it will be useful, but WITHOUT
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
# details.
#
# You should have received a copy of the GNU General Public License along with
# this program. If not, see <https://www.gnu.org/licenses/>.

import os
import pytest

from cfgnet.plugins.concept.github_actions_plugin import GitHubActionPlugin
from cfgnet.config_types.config_types import ConfigType
from tests.utility.id_creator import make_id


@pytest.fixture(name="get_plugin")
def get_plugin_():
plugin = GitHubActionPlugin()
return plugin


def test_is_responsible(get_plugin):
plugin = get_plugin

assert plugin.is_responsible("tests/files/.github/workflows/ci.yml")
assert not plugin.is_responsible("tests/files/test.yml")


def test_config_types(get_plugin):
plugin = get_plugin
file_name = os.path.abspath("tests/files/.github/workflows/ci.yml")
artifact = plugin.parse_file(file_name, "ci.yml")
nodes = artifact.get_nodes()

name_node = next(filter(lambda x: x.id == make_id("ci.yml", "name", "Code Quality"), nodes))
version_node = next(filter(lambda x: x.id == make_id("ci.yml", "jobs", "code_style", "steps", "with", "python-version", "3.9"), nodes))
command_node = next(filter(lambda x: x.id == make_id("ci.yml", "jobs", "code_style", "steps", "run", "poetry install"), nodes))
boolean_node = next(filter(lambda x: x.id == make_id("ci.yml", "jobs", "code_style", "steps", "with", "virtualenvs-create", "true"), nodes))

assert name_node.config_type == ConfigType.NAME
assert version_node.config_type == ConfigType.VERSION_NUMBER
assert command_node.config_type == ConfigType.COMMAND
assert boolean_node.config_type == ConfigType.BOOLEAN
4 changes: 3 additions & 1 deletion tests/cfgnet/plugins/test_plugin_manager.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@
def test_get_all_plugins():
all_plugins = PluginManager.get_plugins()

assert len(all_plugins) == 28
assert len(all_plugins) == 29


def test_get_responsible_plugin():
Expand Down Expand Up @@ -53,6 +53,7 @@ def test_get_responsible_plugin():
mapreduce_plugin = PluginManager.get_responsible_plugin(plugins, "path/to/mapred-site.xml")
circleci_plugin = PluginManager.get_responsible_plugin(plugins, "path/to/.circleci/config.yml")
cargo_plugin = PluginManager.get_responsible_plugin(plugins, "path/to/Cargo.toml")
github_action_plugin = PluginManager.get_responsible_plugin(plugins, ".github/workflows/test.yml")

assert docker_plugin.concept_name == "docker"
assert maven_plugin.concept_name == "maven"
Expand Down Expand Up @@ -82,3 +83,4 @@ def test_get_responsible_plugin():
assert mapreduce_plugin.concept_name == "mapreduce"
assert circleci_plugin.concept_name == "circleci"
assert cargo_plugin.concept_name == "cargo"
assert github_action_plugin.concept_name == "github-action"
30 changes: 30 additions & 0 deletions tests/files/.github/workflows/ci.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
name: Code Quality

on: [push]

jobs:

code_style:
runs-on: ubuntu-latest

steps:
- uses: actions/checkout@v2

- name: Set up Python 3.9
uses: actions/setup-python@v2
with:
python-version: 3.9

- name: Install Poetry
uses: snok/install-poetry@v1
with:
version: latest
virtualenvs-create: true
virtualenvs-in-project: true

- name: Install Dependencies
run: poetry install
if: steps.cache.outputs.cache-hit != 'true'

- name: Run linters
run: make linter

0 comments on commit 7e1a83d

Please sign in to comment.