Skip to content

Commit

Permalink
Improve Windows support (#414)
Browse files Browse the repository at this point in the history
* Update vendor/oscal to v1.0.2 tag.

* Add workflow for running the Java and Python example tests.

* Remove Overmind devtools.

* Example don't require complete initialization - only submodules are
required, as all other dependencies are in the appropriate Dockerfiles.

* Build Schematron before running example tests.

* Run example tests on a matrix of [ubuntu, windows]. Follow-up: add a
separate Windows Dockerfile to test Python code on Windows.

* Turn off fail-fast, so the complete matrix will run despite failures.

* Use bash on all operating systems (pwsh is default on Windows).

* Test: remove BASE_DIR usage from validations module.mk, in attempt to
make paths work on Windows.

* Debugging

* Try adding separate container for Windows python tests.

* Run Python on Windows directly - rather than via make & docker.

* Turn on pytest verbose logging.

* Add note about msys2 usage for Windows users who may want to use the
Makefile.

* For Windows compatibility: pass local path arguments as file URIs.

* Update main test runner to run example tests on both Windows and Linux.

* Add Schematron build step to test runner.

* Don't suppress curl output/status code

* Add argument for curl to fail on 404

* Update xspec to use dnaab/support-msys. Temporary, until PR is merged: xspec/xspec#1620

* Local python script: use "python" vs "python3", so default Windows
install of Python 3 will work without change.

* Explicitly pass down SAXON_CP as environment var - appears to be
necessary on MSYS2.

* Remove Windows workflow for Python tests, in favor of running just the
Linux tests via the Makefile. Running the Python tests on Windows now
works, but is difficult to automate via Github actions.

* Remove platform attribute

* Java example container: linux/amd64

* For Python example, update Saxon/C version to 11.3. This includes code
changes due to an updated API interface.

* Remove Windows Dockerfile, for now - wait for better testing
opportunity.

* Run complete test suite on Linux; exclude the sample projects on
Windows, since they require Docker.

* Remove currently unnecessary Java example platform.

* Remove unused Windows Python make target

* Explicitly pass SAXON_CP var to compile-sch.sh, for the benefit of
Windows.

* Windows test: remove SAXON arg passing

* Test: remove another explicit SAXON env var passing

* Print installed version of node on errors

* Update documentation for Windows dependencies

* Format Python code with black

* Try using strip function for Windows compat problem

* Tweak error str

* Update package-lock.json

* Update to latest node.js LTS (16.14.2)

* Update error message

* Remove terminate on error message - this fixes a non-problematic path
mismatch on Windows.

* Update mkdirp package for windows compat

* Reorder help target so the default target is `help`.

* remove duplicate help target.
  • Loading branch information
danielnaab authored Apr 11, 2022
1 parent d6778ec commit 4f2bf38
Show file tree
Hide file tree
Showing 19 changed files with 208 additions and 2,013 deletions.
1 change: 1 addition & 0 deletions .gitattributes
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
* text=auto eol=lf
34 changes: 27 additions & 7 deletions .github/workflows/run-tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,11 @@ on: push
jobs:
# one job that runs tests
run-tests:
# Run on ubuntu
runs-on: ubuntu-latest
runs-on: ${{ matrix.os }}
strategy:
fail-fast: false
matrix:
os: [ubuntu-latest, windows-latest]

# Checkout repository and its submodules
steps:
Expand All @@ -20,32 +23,48 @@ jobs:

- name: Read node version from `.nvmrc` file
id: nvmrc
uses: browniebroke/read-nvmrc-action@v1
shell: bash
run: echo ::set-output name=NODE_VERSION::$(cat .nvmrc)

- name: Install required node.js version
uses: actions/setup-node@v1
with:
node-version: "${{ steps.nvmrc.outputs.node_version }}"
node-version: ${{ steps.nvmrc.outputs.NODE_VERSION }}

# Initialize the workspace with submodules and dependencies.
- name: Initialize workspace
shell: bash
run: make init

- name: Run test suite
# Compile Schematron to XSL.
- name: Compile Schematron
shell: bash
run: make build-validations

- name: Run complete test suite
shell: bash
if: runner.os == 'Linux'
run: |
make test
- name: Run limited test suite
shell: bash
if: runner.os == 'Windows'
run: |
make test-web test-validations
make test-validations test-web
# Sets the test report path for visibility
- name: Publish XSpec Test Results
uses: mikepenz/action-junit-report@v1
if: runner.os == 'Linux'
with:
report_paths: "**/report/test/*junit.xml"
github_token: ${{ secrets.GITHUB_TOKEN }}

# Publish the test summary as comment on the PR
- name: Publish XSpec Test Results Summary
uses: EnricoMi/[email protected]
if: always()
if: runner.os == 'Linux'
with:
github_token: ${{ secrets.GITHUB_TOKEN }}
check_name: XSpec Test Results
Expand All @@ -55,6 +74,7 @@ jobs:

- name: Upload Resulting Schematron SVRL Report
uses: actions/upload-artifact@27bce4eee761b5bc643f46a8dfb41b430c8d05f6 # v2
if: runner.os == 'Linux'
with:
name: fedramp-automation-validation-unit-tests-${{ github.sha }}
path: |
Expand Down
2 changes: 1 addition & 1 deletion .nvmrc
Original file line number Diff line number Diff line change
@@ -1 +1 @@
v16.4.0
v16.14.2
6 changes: 3 additions & 3 deletions Makefile
Original file line number Diff line number Diff line change
@@ -1,15 +1,15 @@
export BASE_DIR=$(shell pwd)

help:
@grep -h -E '^[a-zA-Z_-]+:.*?## .*$$' $(MAKEFILE_LIST) | sort | awk 'BEGIN {FS = ":.*?## "}; {printf "\033[36m%-30s\033[0m %s\n", $$1, $$2}'

# Most of the real work of the build is in sub-project Makefiles.
include src/examples/module.mk
include src/validations/module.mk
include src/web/module.mk

.PHONY: help

help:
@grep -h -E '^[a-zA-Z_-]+:.*?## .*$$' $(MAKEFILE_LIST) | sort | awk 'BEGIN {FS = ":.*?## "}; {printf "\033[36m%-30s\033[0m %s\n", $$1, $$2}'

all: clean test build ## Complete clean build with tests

init: init-repo init-validations init-web ## Initialize project dependencies
Expand Down
4 changes: 4 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -63,13 +63,17 @@ Build requirements are:
- gnu make
- node.js (as versioned in [./nvmrc](./.nvmrc))
- Java 8+
- Python 3.9+
- Docker

For usage information, use the default target:

```
make
```

If you are developing on Windows, [msys2](https://www.msys2.org/) may be used for the required build tools (`make` and `bash`, in particular). Follow all the suggested installation steps on the msys2 home page for a complete environment. Additionally, make sure all the build requirements (above) are available on your path.

### Implementation details

FedRAMP automation is composed of the following implementation details:
Expand Down
1 change: 1 addition & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

14 changes: 7 additions & 7 deletions src/examples/python/Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -14,18 +14,18 @@ RUN apt-get update \

# Download, build, and configure the Saxon-HE c-library
# See here for platform-specific packages: https://www.saxonica.com/download/c.xml
RUN wget -O /tmp/saxon.zip https://www.saxonica.com/saxon-c/libsaxon-HEC-setup64-v1.2.1.zip \
&& unzip /tmp/saxon.zip -d /tmp \
&& (echo "/opt/saxonica" && cat) | ./tmp/libsaxon-HEC-setup64-v1.2.1 \
&& ln -s /opt/saxonica/libsaxonhec.so /usr/lib/libsaxonhec.so \
&& ln -s /opt/saxonica/rt /usr/lib/rt
RUN wget -O /tmp/saxon.zip https://www.saxonica.com/download/libsaxon-HEC-setup64-v11.3.zip \
&& unzip /tmp/saxon.zip -d /opt/saxonica/ \
&& ln -s /opt/saxonica/libsaxon-HEC-11.3/libsaxonhec.so /usr/lib/libsaxonhec.so \
&& ln -s /opt/saxonica/libsaxon-HEC-11.3/rt /usr/lib/rt
ENV SAXONC_HOME=/usr/lib
ENV LD_LIBRARY_PATH=/usr/lib/rt/lib/amd64:$LD_LIBRARY_PATH

# Build the saxon-c Python extension and put on PYTHONPATH
RUN cd /opt/saxonica/Saxon.C.API/python-saxon \
RUN cd /opt/saxonica/libsaxon-HEC-11.3/Saxon.C.API/python-saxon \
&& pip install cython \
&& python3 saxon-setup.py build_ext -if
ENV PYTHONPATH=/opt/saxonica/Saxon.C.API/python-saxon
ENV PYTHONPATH=/opt/saxonica/libsaxon-HEC-11.3/Saxon.C.API/python-saxon

# Install Python dependencies
ADD requirements.txt /tmp
Expand Down
40 changes: 23 additions & 17 deletions src/examples/python/test_json_to_xml.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,32 +29,38 @@ def saxon_processor() -> saxonc.PySaxonProcessor:


@pytest.fixture
def xslt_processor(saxon_processor: saxonc.PySaxonProcessor) -> saxonc.PyXsltProcessor:
xslt_processor = saxon_processor.new_xslt_processor()
xslt_processor.set_property("it", "from-json")
def xslt_processor(
saxon_processor: saxonc.PySaxonProcessor,
) -> saxonc.PyXslt30Processor:
xslt_processor = saxon_processor.new_xslt30_processor()

# Create a data URI for a JSON sample SSP.
with open(EXAMPLE_SSP_PATH_JSON, 'r') as f:
with open(EXAMPLE_SSP_PATH_JSON, "r") as f:
sample_ssp_json = f.read()
file_base64_encoded = base64.b64encode(sample_ssp_json.encode('utf-8')).decode("utf-8")
data_uri = f'data:application/json;base64,{file_base64_encoded}'
file_base64_encoded = base64.b64encode(sample_ssp_json.encode("utf-8")).decode(
"utf-8"
)
data_uri = f"data:application/json;base64,{file_base64_encoded}"

xslt_processor.set_parameter(
# To process an in-memory JSON string, you may pass it as a data URI.
"file", saxon_processor.make_string_value(data_uri)

"file",
saxon_processor.make_string_value(data_uri)
# To process an external document, you may pass it as a URI.
#"file", saxon_processor.make_string_value(EXAMPLE_SSP_PATH_JSON),
# "file", saxon_processor.make_string_value(EXAMPLE_SSP_PATH_JSON),
)
return xslt_processor


def test_json_to_xml(xslt_processor: saxonc.PyXsltProcessor) -> None:
xml_string = xslt_processor.transform_to_string(
source_file=OSCAL_JSON_TO_XML,
stylesheet_file=OSCAL_JSON_TO_XML,
def test_json_to_xml(xslt_processor: saxonc.PyXslt30Processor) -> None:
xslt_executable = xslt_processor.compile_stylesheet(
stylesheet_file=OSCAL_JSON_TO_XML
)
assert xslt_processor.exception_count() == 0
# To get exception details, use the following:
#assert xslt_processor.get_error_message(0) == None
assert '<system-security-plan ' in xml_string
xslt_executable.set_property("it", "from-json")
xslt_executable.set_initial_match_selection(file_name=OSCAL_JSON_TO_XML)

xml_string = xslt_executable.call_template_returning_string("from-json")

assert xslt_executable.exception_occurred == False
assert xslt_executable.error_message is None
assert "<system-security-plan " in xml_string
40 changes: 27 additions & 13 deletions src/examples/python/test_validate_ssp.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
import os
from urllib.request import pathname2url
from urllib.parse import urljoin

import pytest
import saxonc # type: ignore
Expand All @@ -21,18 +23,28 @@
)
)
# FedRAMP OSCAL LOW/MODERATE/HIGH/LI baselines
BASELINES_BASE_PATH = os.path.abspath(
os.path.join(
os.path.dirname(__file__),
"../../../dist/content/baselines/rev4/xml",
)
BASELINES_BASE_PATH = urljoin(
"file:",
pathname2url(
os.path.abspath(
os.path.join(
os.path.dirname(__file__),
"../../../dist/content/baselines/rev4/xml",
)
)
),
)
# FedRAMP OSCAL custom values (fedramp-values.xml)
REGISTRY_BASE_PATH = os.path.abspath(
os.path.join(
os.path.dirname(__file__),
"../../../dist/content/resources/xml",
)
REGISTRY_BASE_PATH = urljoin(
"file:",
pathname2url(
os.path.abspath(
os.path.join(
os.path.dirname(__file__),
"../../../dist/content/resources/xml",
)
)
),
)


Expand All @@ -48,7 +60,9 @@ def saxon_processor() -> saxonc.PySaxonProcessor:


@pytest.fixture
def xslt_processor(saxon_processor: saxonc.PySaxonProcessor) -> saxonc.PyXsltProcessor:
def xslt_processor(
saxon_processor: saxonc.PySaxonProcessor,
) -> saxonc.PyXslt30Processor:
xslt_processor = saxon_processor.new_xslt30_processor()
# Set parameters to `fedramp-automation` baselines and fedramp-values files.
xslt_processor.set_parameter(
Expand All @@ -67,7 +81,7 @@ def xslt_processor(saxon_processor: saxonc.PySaxonProcessor) -> saxonc.PyXsltPro

@pytest.fixture
def svrl_node(
saxon_processor: saxonc.PySaxonProcessor, xslt_processor: saxonc.PyXsltProcessor
saxon_processor: saxonc.PySaxonProcessor, xslt_processor: saxonc.PyXslt30Processor
) -> saxonc.PyXdmNode:
# Validate the SSP, returning an SVRL document as a string.
svrl_string = xslt_processor.transform_to_string(
Expand All @@ -85,7 +99,7 @@ def svrl_node(

@pytest.fixture
def xpath_processor(
saxon_processor: saxonc.PyXsltProcessor, svrl_node: saxonc.PyXsltProcessor
saxon_processor: saxonc.PyXslt30Processor, svrl_node: saxonc.PyXslt30Processor
) -> saxonc.PyXPathProcessor:
xpath_processor = saxon_processor.new_xpath_processor()
xpath_processor.set_context(xdm_item=svrl_node)
Expand Down
2 changes: 1 addition & 1 deletion src/validations/bin/assert-svrl.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
#!/usr/bin/env python3
#!/usr/bin/env python

import argparse
import sys
Expand Down
2 changes: 1 addition & 1 deletion src/validations/bin/compile-sch.sh
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
#!/bin/bash
#!/usr/bin/env bash

set -euxo pipefail

Expand Down
12 changes: 6 additions & 6 deletions src/validations/module.mk
Original file line number Diff line number Diff line change
Expand Up @@ -3,18 +3,18 @@ SAXON_JAR := Saxon-HE-$(SAXON_VERSION).jar
SAXON_LOCATION := saxon/Saxon-HE/$(SAXON_VERSION)/$(SAXON_JAR)
SAXON_URL := https://repo1.maven.org/maven2/net/sf/$(SAXON_LOCATION)
export SAXON_OPTS = allow-foreign=true diagnose=true
export SAXON_CP = $(BASE_DIR)/vendor/$(SAXON_JAR)
export SAXON_CP = vendor/$(SAXON_JAR)

VALIDATIONS_DIR := $(BASE_DIR)/src/validations
VALIDATIONS_DIR := src/validations

COMPILE_SCH := $(VALIDATIONS_DIR)/bin/compile-sch.sh
EVAL_SCHEMATRON := $(VALIDATIONS_DIR)/bin/evaluate-compiled-schematron.sh
EVAL_XSPEC := TEST_DIR=$(VALIDATIONS_DIR)/report/test $(BASE_DIR)/vendor/xspec/bin/xspec.sh -s -j
COMPILE_SCH := bash $(VALIDATIONS_DIR)/bin/compile-sch.sh
EVAL_SCHEMATRON := bash $(VALIDATIONS_DIR)/bin/evaluate-compiled-schematron.sh
EVAL_XSPEC := TEST_DIR=$(VALIDATIONS_DIR)/report/test bash vendor/xspec/bin/xspec.sh -s -j

init-validations: $(SAXON_CP) ## Initialize validations dependencies

$(SAXON_CP): ## Download Saxon-HE to the vendor directory
curl -H "Accept: application/zip" -o "$(SAXON_CP)" "$(SAXON_URL)" &> /dev/null
curl -f -H "Accept: application/zip" -o "$(SAXON_CP)" "$(SAXON_URL)"

clean-validations: ## Clean validations artifact
@echo "Cleaning validations..."
Expand Down
3 changes: 1 addition & 2 deletions src/validations/rules/rules.xsl
Original file line number Diff line number Diff line change
Expand Up @@ -125,8 +125,7 @@
<!-- that's correct --></xsl:when>
<xsl:otherwise>
<xsl:message
expand-text="true"
terminate="true">{base-uri()} is not static-base-uri()</xsl:message>
expand-text="true">{base-uri()} is not static-base-uri(). expected: `{static-base-uri()}`</xsl:message>
</xsl:otherwise>
</xsl:choose>

Expand Down
6 changes: 3 additions & 3 deletions src/web/module.mk
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
REQUIRED_NODE_VERSION = $(shell cat .nvmrc)
INSTALLED_NODE_VERSION = $(shell node --version)
REQUIRED_NODE_VERSION = $(strip $(shell cat .nvmrc))
INSTALLED_NODE_VERSION = $(strip $(shell node --version))

init-web: node
cd src/web && \
npm install

node:
ifneq ($(REQUIRED_NODE_VERSION),$(INSTALLED_NODE_VERSION))
$(error node.js version $(REQUIRED_NODE_VERSION) required)
$(error node.js version `$(REQUIRED_NODE_VERSION)` required, `$(INSTALLED_NODE_VERSION)` installed)
endif

clean-web: ## Clean web artifacts
Expand Down
Loading

0 comments on commit 4f2bf38

Please sign in to comment.