Script Templates for Continuous Integration

Script template to create reports for selected systems.

#!/bin/bash
# @variable CREATE_PORTFOLIO Set to "1" to create the portfolio and other reports.
# @variable CREATE_THIP Set to "1" to generate the Technical Health Improvement Plan.
# @variable CORE_SIZE The desired core size for the Technical Health Improvement Plan.
# @variable VAULT_PATH The path to the vault storing scan data and reports.
PROJECT_NAME="${JOB_NAME}"
SYSTEM_VERSION="build-${BUILD_NUMBER}"

export CMRI_VAULT="${VAULT_PATH}"

# Default the core size to 30.
if [[ -z "$CORE_SIZE" ]]; then
  CORE_SIZE=30
fi

# @description Runs a script using the CodeMRI CLI.
#
# @stdin The script to run via CodeMRI CLI.
function run_codemri() {
  cat > temp.sc
  yes | cmri batch run temp.sc
}

if [[ "$CREATE_PORTFOLIO" = "1" ]]; then
  run_codemri <<EOF
select "${PROJECT_NAME}"/"${PROJECT_NAME}-${SYSTEM_VERSION}"

job clean produce_reports
job run produce_reports

job clean produce_portfolio
job run produce_portfolio
EOF
fi

if [[ "$CREATE_THIP" = "1" ]]; then
run_codemri <<EOF
select "${PROJECT_NAME}"/"${PROJECT_NAME}-${SYSTEM_VERSION}"
job run produce_thip --size $CORE_SIZE
EOF
fi
exit $?

Script template to clean output for selected systems.

#!/bin/bash
# @variable VAULT_PATH The path to the vault storing scan data and reports.
export CMRI_VAULT="${VAULT_PATH}"
PROJECT_NAME="${JOB_NAME}"
SYSTEM_VERSION="build-${BUILD_NUMBER}"

# @description Runs a script using the CodeMRI CLI.
#
# @stdin The script to run via CodeMRI CLI.
function run_codemri() {
  cat > temp.sc
  yes | cmri batch run temp.sc
}

run_codemri <<EOF
select "${PROJECT_NAME}/${PROJECT_NAME}-${SYSTEM_VERSION}"
job clean produce_reports
job clean produce_thip
select "${PROJECT_NAME}"/"${PROJECT_NAME}-${SYSTEM_VERSION}"
system remove -f
EOF

if [[ $? -gt 0 ]]; then
  echo "Cleanup failed. Please remove the system manually."
  exit 0
fi

Script template to scan a software system for architectural errors and create a list of known architectural errors if none are found.

#!/bin/bash
#
# Scans a given codebase for architectural errors, and updates the list of known architectural errors if no new errors are found.
#
# This script generates the following output files;
#
# * `component-errors.tsv`, which contains any new component errors not previously identified.
# * `.silverthread/known-component-errors.tsv`, which contains the list of known component errors.
# * `.silverthread/rules.txt (if not already existing), which contains a set of rules to flag known errors.
#
# @variable JOB_NAME The name to give to the codebase.
# @variable BUILD_NUMBER The numeric identifier of the build, may be substituted for the unique ID of the build if the
#                        build system does not support build numbering.
# @variable VAULT_PATH The path to the vault storing scan data.
# @variable SUBDIR The path to a subdirectory containing a subset of the code to scan. Useful for testing script changes
#                  on large codebases where scans may take several hours.
# @arg $1 The path to the UDA file defining codebase architecture.
# @arg $2 The path to the folder containing source code to scan.
PROJECT_NAME="${JOB_NAME}"
SYSTEM_VERSION="build-${BUILD_NUMBER}"
MAX_CORE_SIZE=255

UDA_PATH="$1"
SOURCE_PATH="$2"

# Path separator. Change to \\ on Windows.
SEP="/"

# Tell CodeMRI where to find the Vault.
export CMRI_VAULT="${VAULT_PATH}"

# @description Runs a script using the CodeMRI CLI.
#
# @stdin The script to run via CodeMRI CLI.
function run_codemri() {
  cat > temp.sc
  yes | cmri batch run temp.sc
}

# @description Attempts to cretae a new project within the data vault.
#
# @arg $1 The name of the project to create.
function create_project() {
  PROJECT_NAME=$1
  run_codemri <<EOF
project add -n "${PROJECT_NAME}"
EOF
}

# @description Attempts to import code into the given system under the given project. For simplicity, the
# system name will be <project-name>-<system-version>.
#
# @arg $1 The name of the project.
# @arg $2 The version of the system.
function import_code() {
    PROJECT_NAME=$1
    SYSTEM_VERSION=$2
    run_codemri <<EOF
system add -s "${PROJECT_NAME}" -n "${PROJECT_NAME}" -v "${SYSTEM_VERSION}" -o "${SOURCE_PATH}${SUBDIR}"
select "${PROJECT_NAME}/${PROJECT_NAME}-${SYSTEM_VERSION}"
system config set uda_file "${UDA_PATH}"
system config set scan_in_place True

# Only set the languages if we have set a value for them.
$([[ ! -z "$LANGUAGES" ]] && echo system config set languages ${LANGUAGES})
EOF
}

# @description Attempts to generate a file containing known issues to flag as warnings. This should be run when running the build for
# the first time.
#
# This generates two files:
#
# * `.silverthread/rules.txt`, which contains the rules to flag the known errors.
# * `.silverthread/known-component-errors.tsv`, which contains a list of known issues.
function generate_baseline() {
    # Generate our baseline errors file if none exists.
    if [[ "$BASELINE" = "1" ]]; then
        echo "No baseline errors. Running baseline errors."
        run_codemri <<EOF
select "${PROJECT_NAME}"/"${PROJECT_NAME}-${SYSTEM_VERSION}"
job run generate_query_data
system query component-relationships --severity error --out "${SOURCE_PATH}${SEP}.silverthread${SEP}known-component-errors.tsv" --output-format tsv
EOF
    else
        echo "Baseline errors already exist, skipping generation."
    fi
}

# @description Scans the codebase, and generates files that contain any architectural violations. These files are named:
#
# * `component-errors.tsv`, which contains any architectural violations at the component level.
#
# If there are no architectural errors, then the list of known errors will be regenerated and placed into .silverthread/known-component-errors.tsv.
function run_analysis() {
    run_codemri <<EOF
select "${PROJECT_NAME}"/"${PROJECT_NAME}-${SYSTEM_VERSION}"
job run generate_query_data
system query component-relationships --severity error --out "component-errors.tsv" --output-format tsv
system query entity-relationships --from-component @from_component --to-component @to_component --out "component-error-entities.tsv" --output-format tsv
EOF
    COUNT_VIOLATIONS=$(expr `wc -l component-errors.tsv | awk '{print $1;}'` - 1)

    if [[ ${COUNT_VIOLATIONS} -gt 0 ]]; then
        echo "${COUNT_VIOLATIONS} architectural violation(s) detected. Failing build."
        echo "Architectural Violations Follow:"
        column -t < component-errors.tsv
        echo "Entity Relationships in Errors:"
        column -t < component-error-entities.tsv
        exit 1
    fi

    echo 'No new architectural violations detected.'

    echo 'Checking core size.'
    run_codemri <<EOF
select "${PROJECT_NAME}"/"${PROJECT_NAME}-${SYSTEM_VERSION}"
system query files --out "files.tsv" --output-format tsv
EOF

    FIELD_CYCLIC_GROUP_SIZE=$(cat files.tsv | head -n 1 | tr "\t" "\n" | cat -n | grep cyclic_group_size | cut -f1)
    LARGEST_CORE_SIZE=$(cut -f$FIELD_CYCLIC_GROUP_SIZE files.tsv | sort -nr | uniq | grep -v 'cyclic_group_size' | head -n 1)

    if [[ $LARGEST_CORE_SIZE -gt $MAX_CORE_SIZE ]]; then
        echo "The core size of $LARGEST_CORE_SIZE exceeds the maximum allowed core size of ${MAX_CORE_SIZE}."
    else
		echo "The core size of $LARGEST_CORE_SIZE is within an acceptable range (below ${MAX_CORE_SIZE})."
    fi

    echo "Updating list of known errors..."
    run_codemri <<EOF
select "${PROJECT_NAME}"/"${PROJECT_NAME}-${SYSTEM_VERSION}"
system query component-relationships --severity warning --out "${SOURCE_PATH}${SEP}.silverthread${SEP}known-component-errors.tsv" --output-format tsv
EOF
}

# And check to see if our vault exists, if not create it. We'll verify the vault exists by checking for the vault folder.
if [[ ! -e "${VAULT_PATH}" ]]; then
	echo "No vault found. Please create a vault at \"${VAULT_PATH}\" by running cmri vault create --vault \"${VAULT_PATH}\"."
	echo "After creating the vault, log in by running cmri account login --vault \"${VAULT_PATH}\""
	exit 1
fi

# Check to see if our project0 already exists, if not, create it.
if [[ ! -e "${VAULT_PATH}${SEP}projects${SEP}${PROJECT_NAME}" ]]; then
    create_project "${PROJECT_NAME}"
fi

import_code "${PROJECT_NAME}" "${SYSTEM_VERSION}"

# If we don't have a set of rules yet (either nothing checked in, or nothing previously generated), we'll need to generate a new set.
generate_baseline

run_analysis
exit $?