Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feat/add regenerate #15

Merged
merged 13 commits into from
Jul 6, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .flake8
Original file line number Diff line number Diff line change
@@ -1,2 +1,2 @@
[flake8]
max-line-length=100
max-line-length=105
22 changes: 19 additions & 3 deletions action.yml
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,29 @@ inputs:
markdown_path:
description: Path relative to the repository path where the Trestle markdown files are located. See project README.md for more information.
required: true
assemble_model:
oscal_model:
description: OSCAL Model type to assemble. Values can be catalog, profile, compdef, or ssp.
required: true
check_only:
description: "Runs tasks and exits with an error if there is a diff. Defaults to false"
required: false
default: false
skip_assemble:
description: "Skip assembly task. Defaults to false"
required: false
default: false
skip_regenerate:
description: "Skip regenerate task. Defaults to false."
required: false
default: false
skip_items:
description: "Comma-separated list of content by Trestle name to skip during task execution. For example `profile_x,profile_y`."
required: false
default: ""
ssp_index_path:
description: Path relative to the repository path where the ssp index is located. See project README.md for information about the ssp index.
required: false
default: "ssp-index.txt"
default: "ssp-index.json"
commit_message:
description: Commit message
required: false
Expand All @@ -22,7 +38,7 @@ inputs:
required: false
default: ${{ github.ref_name }}
file_pattern:
description: File pattern used for `git add`. For example `component-definitions/*`. Defaults to (`.`)
description: Comma separated file pattern list used for `git add`. For example `component-definitions/*,*json`. Defaults to (`.`)
required: false
default: '.'
repository:
Expand Down
43 changes: 31 additions & 12 deletions entrypoint.sh
Original file line number Diff line number Diff line change
Expand Up @@ -23,18 +23,37 @@ exec 3>&1

trap exec 3>&- EXIT

output=$(python3.8 -m trestlebot \
--markdown-path="${INPUT_MARKDOWN_PATH}" \
--assemble-model="${INPUT_ASSEMBLE_MODEL}" \
--ssp-index-path="${INPUT_SSP_INDEX_PATH}" \
--commit-message="${INPUT_COMMIT_MESSAGE}" \
--branch="${INPUT_BRANCH}" \
--patterns="${INPUT_FILE_PATTERN}" \
--committer-name="${INPUT_COMMIT_USER_NAME}" \
--committer-email="${INPUT_COMMIT_USER_EMAIL}" \
--author-name="${INPUT_COMMIT_AUTHOR_NAME}" \
--author-email="${INPUT_COMMIT_AUTHOR_EMAIL}" \
--working-dir="${INPUT_REPOSITORY}" | tee /dev/fd/3)

# Initialize the command variable
command="python3.8 -m trestlebot \
--markdown-path=\"${INPUT_MARKDOWN_PATH}\" \
--oscal-model=\"${INPUT_OSCAL_MODEL}\" \
--ssp-index-path=\"${INPUT_SSP_INDEX_PATH}\" \
--commit-message=\"${INPUT_COMMIT_MESSAGE}\" \
--branch=\"${INPUT_BRANCH}\" \
--file-patterns=\"${INPUT_FILE_PATTERN}\" \
--committer-name=\"${INPUT_COMMIT_USER_NAME}\" \
--committer-email=\"${INPUT_COMMIT_USER_EMAIL}\" \
--author-name=\"${INPUT_COMMIT_AUTHOR_NAME}\" \
--author-email=\"${INPUT_COMMIT_AUTHOR_EMAIL}\" \
--working-dir=\"${INPUT_REPOSITORY}\" \
--skip-items=\"${INPUT_SKIP_ITEMS}\""

# Conditionally include flags
if [[ ${INPUT_SKIP_ASSEMBLE} == true ]]; then
command+=" --skip-assemble"
fi

if [[ ${INPUT_SKIP_REGENERATE} == true ]]; then
command+=" --skip-regenerate"
fi

if [[ ${INPUT_CHECK_ONLY} == true ]]; then
command+=" --check-only"
fi

output=$(eval "$command" 2>&1 > >(tee /dev/fd/3))


commit=$(echo "$output" | grep "Commit Hash:" | sed 's/.*: //')

Expand Down
15 changes: 14 additions & 1 deletion tests/testutils.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,9 +17,10 @@
"""Helper functions for unit test setup and teardown."""

import argparse
import json
import pathlib
import shutil
from typing import Optional
from typing import List, Optional

from git.repo import Repo
from trestle.common.model_utils import ModelUtils
Expand Down Expand Up @@ -157,3 +158,15 @@ def setup_for_compdef(
)

return args


def write_index_json(
file_path: str, ssp_name: str, profile: str, component_definitions: List[str]
) -> None:
"""Write out ssp index JSON for tests"""
data = {
ssp_name: {"profile": profile, "component_definitions": component_definitions}
}

with open(file_path, "w") as file:
json.dump(data, file, indent=4)
88 changes: 79 additions & 9 deletions tests/trestlebot/tasks/authored/test_ssp.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,17 +16,19 @@

"""Test for Trestle Bot Authored SSP."""

import os
import pathlib
from typing import Dict

import pytest
from trestle.common import const
from trestle.common.model_utils import ModelUtils
from trestle.core.commands.author.ssp import SSPGenerate
from trestle.core.models.file_content_type import FileContentType
from trestle.oscal import ssp as ossp

from tests import testutils
from trestlebot.tasks.authored.ssp import AuthoredSSP
from trestlebot.tasks.authored.base_authored import AuthoredObjectException
from trestlebot.tasks.authored.ssp import AuthoredSSP, SSPIndex


test_prof = "simplified_nist_profile"
Expand All @@ -44,10 +46,11 @@ def test_assemble(tmp_trestle_dir: str) -> None:
ssp_generate = SSPGenerate()
assert ssp_generate._run(args) == 0

comps_by_ssp: Dict[str, str] = {}
comps_by_ssp[test_ssp_output] = test_comp
ssp_index_path = os.path.join(tmp_trestle_dir, "ssp-index.json")
testutils.write_index_json(ssp_index_path, test_ssp_output, test_prof, [test_comp])
ssp_index: SSPIndex = SSPIndex(ssp_index_path)

authored_ssp = AuthoredSSP(tmp_trestle_dir, comps_by_ssp)
authored_ssp = AuthoredSSP(tmp_trestle_dir, ssp_index)

# Run to ensure no exceptions are raised
authored_ssp.assemble(md_path)
Expand All @@ -68,10 +71,77 @@ def test_assemble_no_ssp_entry(tmp_trestle_dir: str) -> None:
ssp_generate = SSPGenerate()
assert ssp_generate._run(args) == 0

comps_by_ssp: Dict[str, str] = {}
comps_by_ssp["fake"] = test_comp
ssp_index_path = os.path.join(tmp_trestle_dir, "ssp-index.json")
testutils.write_index_json(ssp_index_path, "fake", test_prof, [test_comp])
ssp_index: SSPIndex = SSPIndex(ssp_index_path)

authored_ssp = AuthoredSSP(tmp_trestle_dir, comps_by_ssp)
authored_ssp = AuthoredSSP(tmp_trestle_dir, ssp_index)

with pytest.raises(ValueError):
with pytest.raises(
AuthoredObjectException, match="SSP test-ssp does not exists in the index"
):
authored_ssp.assemble(md_path)


def test_regenerate(tmp_trestle_dir: str) -> None:
"""Test to test regenerate functionality for SSPs"""
# Prepare the workspace and generate the markdown
trestle_root = pathlib.Path(tmp_trestle_dir)
md_path = os.path.join(markdown_dir, test_ssp_output)
_ = testutils.setup_for_ssp(trestle_root, test_prof, test_comp, md_path)

ssp_index_path = os.path.join(tmp_trestle_dir, "ssp-index.json")
testutils.write_index_json(ssp_index_path, test_ssp_output, test_prof, [test_comp])
ssp_index: SSPIndex = SSPIndex(ssp_index_path)

authored_ssp = AuthoredSSP(tmp_trestle_dir, ssp_index)

# Run to ensure no exceptions are raised
model_path = os.path.join(const.MODEL_DIR_SSP, test_ssp_output)
authored_ssp.regenerate(model_path, md_path)

assert os.path.exists(os.path.join(tmp_trestle_dir, markdown_dir, test_ssp_output))


def test_regenerate_no_ssp_entry(tmp_trestle_dir: str) -> None:
"""Test to trigger failure for missing SSP index"""
# Prepare the workspace and generate the markdown
trestle_root = pathlib.Path(tmp_trestle_dir)
md_path = os.path.join(markdown_dir, test_ssp_output)
_ = testutils.setup_for_ssp(trestle_root, test_prof, test_comp, md_path)

ssp_index_path = os.path.join(tmp_trestle_dir, "ssp-index.json")
testutils.write_index_json(ssp_index_path, "fake", test_prof, [test_comp])
ssp_index: SSPIndex = SSPIndex(ssp_index_path)

authored_ssp = AuthoredSSP(tmp_trestle_dir, ssp_index)

model_path = os.path.join(const.MODEL_DIR_SSP, test_ssp_output)
with pytest.raises(
AuthoredObjectException, match="SSP test-ssp does not exists in the index"
):
authored_ssp.regenerate(model_path, md_path)


# SSPIndex tests


def test_get_comps_by_ssp(tmp_trestle_dir: str) -> None:
"""Test to get component definition list from index"""
ssp_index_path = os.path.join(tmp_trestle_dir, "ssp-index.json")
testutils.write_index_json(
ssp_index_path, test_ssp_output, test_prof, [test_comp, "another_comp"]
)
ssp_index: SSPIndex = SSPIndex(ssp_index_path)

assert test_comp in ssp_index.get_comps_by_ssp(test_ssp_output)
assert "another_comp" in ssp_index.get_comps_by_ssp(test_ssp_output)


def test_get_profile_by_ssp(tmp_trestle_dir: str) -> None:
"""Test to get profile from index"""
ssp_index_path = os.path.join(tmp_trestle_dir, "ssp-index.json")
testutils.write_index_json(ssp_index_path, test_ssp_output, test_prof, [test_comp])
ssp_index: SSPIndex = SSPIndex(ssp_index_path)

assert ssp_index.get_profile_by_ssp(test_ssp_output) == test_prof
100 changes: 100 additions & 0 deletions tests/trestlebot/tasks/authored/test_types.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
#!/usr/bin/python

# Copyright 2023 Red Hat, Inc.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.

"""Test author types for Trestlebot"""

import os

import pytest

from tests import testutils
from trestlebot.tasks.authored import types
from trestlebot.tasks.authored.base_authored import (
AuthoredObjectException,
AuthorObjectBase,
)
from trestlebot.tasks.authored.catalog import AuthoredCatalog
from trestlebot.tasks.authored.compdef import AuthoredComponentsDefinition
from trestlebot.tasks.authored.profile import AuthoredProfile
from trestlebot.tasks.authored.ssp import AuthoredSSP


test_prof = "simplified_nist_profile"
test_comp = "test_comp"
test_ssp_output = "test-ssp"
markdown_dir = "md_ssp"


def test_get_authored_catalog(tmp_trestle_dir: str) -> None:
"""Test get authored type for catalogs"""

authored_object: AuthorObjectBase = types.get_authored_object(
types.AuthoredType.CATALOG.value, tmp_trestle_dir, ""
)

assert authored_object.get_trestle_root() == tmp_trestle_dir
assert isinstance(authored_object, AuthoredCatalog)


def test_get_authored_profile(tmp_trestle_dir: str) -> None:
"""Test get authored type for catalogs"""

authored_object: AuthorObjectBase = types.get_authored_object(
types.AuthoredType.PROFILE.value, tmp_trestle_dir, ""
)

assert authored_object.get_trestle_root() == tmp_trestle_dir
assert isinstance(authored_object, AuthoredProfile)


def test_get_authored_compdef(tmp_trestle_dir: str) -> None:
"""Test get authored type for catalogs"""

# Test with profile
authored_object: AuthorObjectBase = types.get_authored_object(
types.AuthoredType.COMPDEF.value, tmp_trestle_dir, ""
)

assert authored_object.get_trestle_root() == tmp_trestle_dir
assert isinstance(authored_object, AuthoredComponentsDefinition)


def test_get_authored_ssp(tmp_trestle_dir: str) -> None:
"""Test get authored type for catalogs"""
ssp_index_path = os.path.join(tmp_trestle_dir, "ssp-index.json")
testutils.write_index_json(ssp_index_path, test_ssp_output, test_prof, [test_comp])

with pytest.raises(
FileNotFoundError,
):
_ = types.get_authored_object(types.AuthoredType.SSP.value, tmp_trestle_dir, "")

# Test with profile
authored_object: AuthorObjectBase = types.get_authored_object(
types.AuthoredType.SSP.value, tmp_trestle_dir, ssp_index_path
)

assert authored_object.get_trestle_root() == tmp_trestle_dir
assert isinstance(authored_object, AuthoredSSP)


def test_invalid_authored_type(tmp_trestle_dir: str) -> None:
"""Test triggering an error with an invalid type"""
with pytest.raises(
AuthoredObjectException,
match="Invalid authored type fake",
):
_ = types.get_authored_object("fake", tmp_trestle_dir, "")
Loading