Skip to content
Open
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
9 changes: 9 additions & 0 deletions .dockerignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
.git
**/.gradle
**/build
libs
fips201-card-conformance-tool-*
cct-results-*.zip
cct-docker-data
*.log
.DS_Store
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -116,4 +116,6 @@ build
# Build artifacts
fips201-card-conformance-tool-[0-9]*\.[0-9]*\.[0-9]*-beta-[0-9]*\.zip
fips201-card-conformance-tool-[0-9]*\.[0-9]*\.[0-9]*-beta-[0-9]*/*
/cct-results-*.zip
/cct-docker-data/
libs/*
25 changes: 25 additions & 0 deletions DOCKER.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
# Docker

Build the image from the repository root:

```sh
docker build -t piv-conformance .
```

The image build compiles all modules and runs the focused GUI packaging tests. Running the Swing application with a smart-card reader is supported only on a native Linux Docker host because Docker Desktop does not directly expose host USB or PC/SC devices.

On Linux, expose the X11 display, the host PC/SC socket, and a persistent data directory:

```sh
mkdir -p cct-docker-data
docker run --rm \
-e DISPLAY \
-e XAUTHORITY=/tmp/.Xauthority \
-v "${XAUTHORITY:-$HOME/.Xauthority}:/tmp/.Xauthority:ro" \
-v /tmp/.X11-unix:/tmp/.X11-unix:ro \
-v /run/pcscd/pcscd.comm:/run/pcscd/pcscd.comm \
-v "$PWD/cct-docker-data:/data" \
piv-conformance
```

The container seeds `/data` with the required databases and configuration on first launch. Logs, artifacts, and generated result ZIPs remain in the mounted `cct-docker-data` directory.
39 changes: 39 additions & 0 deletions Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
FROM eclipse-temurin:11-jdk-jammy AS build

ARG CCT_GIT_COMMIT=unknown
ARG CCT_GIT_COMMIT_EPOCH=0

WORKDIR /workspace
COPY . .

RUN cd cardlib \
&& ./gradlew --no-daemon clean install \
-x test -x junitPlatformTest -x generateHtmlTestReports
RUN cd conformancelib \
&& ./gradlew --no-daemon clean install -x test -x junitPlatformTest
RUN cd tools/85b-swing-gui \
&& ./gradlew --no-daemon clean test shadowJar

RUN mkdir -p /opt/cct/bootstrap \
&& cp tools/85b-swing-gui/build/libs/*-shadow.jar /opt/cct/cct.jar \
&& cp cardlib/src/main/resources/user_log_config.xml /opt/cct/bootstrap/ \
&& cp conformancelib/src/main/resources/pdval.properties /opt/cct/bootstrap/ \
&& cp -R conformancelib/src/main/resources/x509-certs /opt/cct/bootstrap/ \
&& cp conformancelib/testdata/PIV*Cards.db /opt/cct/bootstrap/ \
&& cp tools/85b-swing-gui/src/main/resources/build.version /opt/cct/bootstrap/

FROM eclipse-temurin:11-jre-jammy AS runtime

RUN apt-get update \
&& apt-get install -y --no-install-recommends \
fontconfig libpcsclite1 libxext6 libxi6 libxrender1 libxtst6 \
&& rm -rf /var/lib/apt/lists/*

COPY --from=build /opt/cct /opt/cct
COPY docker/entrypoint.sh /usr/local/bin/cct-entrypoint
RUN chmod 0755 /usr/local/bin/cct-entrypoint \
&& mkdir -p /data

VOLUME ["/data"]
WORKDIR /data
ENTRYPOINT ["cct-entrypoint"]
23 changes: 10 additions & 13 deletions cardlib/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,6 @@ buildscript {

plugins {
id 'java-library'
id "org.ajoberstar.grgit" version "4.1.0"
id 'com.github.johnrengelman.shadow' version '4.0.4'
id 'de.jjohannes.extra-java-module-info' version '0.1'
}
Expand Down Expand Up @@ -161,13 +160,11 @@ junitPlatformTest {
println 'Cloning, please wait...'
File cardsDir = new File(project.getRootDir(), 'build/classes/java/test/gov/gsa/pivconformance/cardlib/test/gsa-icam-card-builder')
if (!cardsDir.exists()) {
def grgit = org.ajoberstar.grgit.Grgit.clone(dir: cardsDir, uri: 'https://github.com/GSA/gsa-icam-card-builder', checkout: true, refToCheckout: 'master')
if (grgit != null) {
grgit.describe()
System.out.println 'Cloned ' + cardsDir.getName()
} else {
System.out.println 'Couldn\'t clone gsa-icam-card-builder'
project.exec {
commandLine 'git', 'clone', '--branch', 'master', '--single-branch',
'https://github.com/GSA/gsa-icam-card-builder', cardsDir
}
System.out.println 'Cloned ' + cardsDir.getName()
} else {
System.out.println cardsDir.getName() + ' exists, reusing'
}
Expand Down Expand Up @@ -210,12 +207,12 @@ compileJava {
doFirst {
version = getVersion()

def dir = "${project.rootDir}/../"
def git = org.ajoberstar.grgit.Grgit.open(dir: dir)
def commit = git.head()

def commitId = commit.abbreviatedId
def commitDate = commit.getDate()
def repositoryDir = project.rootDir.parentFile
def commitId = System.getenv('CCT_GIT_COMMIT') ?:
['git', 'rev-parse', '--short', 'HEAD'].execute(null, repositoryDir).text.trim()
def commitDateText = System.getenv('CCT_GIT_COMMIT_EPOCH') ?:
['git', 'show', '-s', '--format=%ct', 'HEAD'].execute(null, repositoryDir).text.trim()
def commitDate = new Date(Long.parseLong(commitDateText) * 1000L)
def buildDate = new Date()
File resourcesDir = new File(project.getProjectDir(), 'src/main/resources/')
File propertiesFile = new File(resourcesDir, 'version.properties')
Expand Down
2 changes: 0 additions & 2 deletions conformancelib/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,6 @@ buildscript {

plugins {
id 'java-library'
id 'org.ajoberstar.grgit' version '2.1.0'
id 'com.github.johnrengelman.shadow' version '4.0.4'
id "de.jjohannes.extra-java-module-info" version "0.1"
}
Expand Down Expand Up @@ -302,4 +301,3 @@ task install(type: Copy) {
from jar
into '../libs'
}

Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,12 @@
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.Reader;
import java.nio.file.Path;
import java.sql.Connection;
import java.sql.DatabaseMetaData;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLClientInfoException;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.ArrayList;
Expand All @@ -23,6 +23,7 @@
public class ConformanceTestDatabase {
private static final Logger s_logger = LoggerFactory.getLogger(ConformanceTestDatabase.class);
private static final String TEST_SET = "SELECT * FROM TestCases WHERE Enabled = 1";
private Path m_databasePath;

public ConformanceTestDatabase(Connection conn) {
setConnconnection(conn);
Expand All @@ -32,8 +33,13 @@ public Connection getConnection() {
return m_conn;
}

public Path getDatabasePath() {
return m_databasePath;
}

public void setConnconnection(Connection conn) {
m_conn = conn;
m_databasePath = null;
}

public int getTestCaseCount() {
Expand All @@ -59,11 +65,14 @@ public void openDatabaseInFile(String filename) throws ConfigurationException {
throw new ConfigurationException("Database file " + filename + " does not exist");
}

String dbUrl = null;
try {
Class.forName("org.sqlite.JDBC");
dbUrl = "jdbc:sqlite:" + f.getCanonicalPath();
} catch (IOException | ClassNotFoundException e) {
String dbUrl = null;
Path databasePath = null;
try {
Class.forName("org.sqlite.JDBC");
File canonicalFile = f.getCanonicalFile();
dbUrl = "jdbc:sqlite:" + canonicalFile.getPath();
databasePath = canonicalFile.toPath().toAbsolutePath().normalize();
} catch (IOException | ClassNotFoundException e) {
s_logger.error("Unable to calculate canonical name for database file", e);
throw new ConfigurationException("Unable to calculate canonical name for database file", e);
}
Expand All @@ -82,14 +91,10 @@ public void openDatabaseInFile(String filename) throws ConfigurationException {
} catch (SQLException e) {
s_logger.error("Unable to read driver metadata", e);
}
}
m_conn = conn;
try {
m_conn.setClientInfo("filename", filename);
} catch (SQLClientInfoException e) {
s_logger.error("setClientInfo failed for database connection.", e);
}
s_logger.info("Opened conformance test database in {}", filename);
m_conn = conn;
m_databasePath = databasePath;
s_logger.info("Opened conformance test database in {}", filename);
}

public List<TestCaseModel> getTestCases() throws ConfigurationException {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,10 +19,13 @@
import java.io.File;
import java.io.IOException;
import java.io.PrintStream;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.NoSuchFileException;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.nio.file.StandardCopyOption;
import java.nio.file.StandardOpenOption;
import java.security.ProtectionDomain;
import java.util.Arrays;
import java.util.Calendar;
Expand All @@ -42,6 +45,7 @@ public class TestRunLogController {

private static final org.slf4j.Logger s_logger = LoggerFactory.getLogger(TestRunLogController.class);
private static final TestRunLogController INSTANCE = new TestRunLogController();
private static final String CONFORMANCE_HEADER = "Date,Test Id,Description,Expected Result,Actual Result";

/*
* Note that these names MUST match the user_log_config.xml appender names.
Expand Down Expand Up @@ -369,10 +373,33 @@ public void setStartTimes() {
Iterator<?> i = m_appenders.entrySet().iterator();
while (i.hasNext()) {
me = (Map.Entry<String, TimeStampedFileAppender<ILoggingEvent>>) i.next();
String logName = me.getKey();
TimeStampedFileAppender<ILoggingEvent> appender = me.getValue();
try {
appender.stop();
String filename = m_filenames.get(logName);
resetLogFile(Paths.get(filename), "CONFORMANCELOG".equals(logName));
appender.setFile(filename);
appender.setAppend(true);
appender.start();
appender.setAppend(false);
} catch (IOException e) {
s_logger.error("Unable to prepare {} for a new test run", logName, e);
throw new IllegalStateException("Unable to prepare logs for a new test run", e);
}
appender.setStartTime(startTime);
}
}

static void resetLogFile(Path file, boolean conformanceCsv) throws IOException {
Path absolute = file.toAbsolutePath().normalize();
Path parent = absolute.getParent();
if (parent != null) Files.createDirectories(parent);
byte[] contents = conformanceCsv
? (CONFORMANCE_HEADER + System.lineSeparator()).getBytes(StandardCharsets.UTF_8)
: new byte[0];
Files.write(absolute, contents, StandardOpenOption.CREATE, StandardOpenOption.TRUNCATE_EXISTING);
}

@SuppressWarnings("unchecked")
/**
Expand Down Expand Up @@ -474,30 +501,9 @@ private boolean rollFile(String oldPath, String newPath) {
*
*/

@SuppressWarnings("unchecked")
public void cleanup() {
Map.Entry<String, String> me = null;
Iterator<?> i = m_loggers.entrySet().iterator();
ArtifactWriter.prependNames(m_timeStamp);
ArtifactWriter.clean();
while (i.hasNext()) {
me = (Map.Entry<String, String>) i.next();
String loggerName = me.getKey();
String loggerClass = me.getValue();

Logger logger = (Logger) LoggerFactory.getLogger(loggerClass);
TimeStampedFileAppender<ILoggingEvent> appender = null;

try {
appender = (TimeStampedFileAppender<ILoggingEvent>) logger.getAppender(loggerName);
if (appender != null) {
File f = new File(appender.getFile());
f.delete();
}
} catch (Exception e) {
s_logger.warn("Can't delete {}: {}", appender.getFile(), e.getMessage());
}
}
}

/**
Expand Down
5 changes: 5 additions & 0 deletions docker/entrypoint.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
#!/bin/sh
set -eu

cp -Rn /opt/cct/bootstrap/. /data/
exec java -jar /opt/cct/cct.jar "$@"
3 changes: 2 additions & 1 deletion tools/85b-swing-gui/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@ buildscript {

plugins {
id 'java-library'
id 'org.ajoberstar.grgit' version '2.1.0'
id 'com.github.johnrengelman.shadow' version '4.0.4'
id 'de.jjohannes.extra-java-module-info' version '0.1'

Expand Down Expand Up @@ -57,6 +56,8 @@ dependencies {
implementation 'org.junit.jupiter:junit-jupiter-api:5.7.0'
implementation 'org.junit.platform:junit-platform-launcher:1.7.0'
implementation 'edu.washington.cs.types.checker:checker-framework:1.7.0'
testImplementation 'org.junit.jupiter:junit-jupiter-engine:5.7.0'
testImplementation 'org.junit.jupiter:junit-jupiter-params:5.7.0'
}

extraJavaModuleInfo {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
package gov.gsa.pivconformance.gui;

import java.nio.file.Path;
import java.util.Objects;

/** Immutable inputs identifying one successfully completed CCT run. */
public final class CompletedTestRun {
private final Path m_resultsDirectory;
private final Path m_databasePath;
private final String m_timeStampPrefix;

public CompletedTestRun(Path resultsDirectory, Path databasePath, String timeStampPrefix) {
m_resultsDirectory = Objects.requireNonNull(resultsDirectory, "resultsDirectory").toAbsolutePath().normalize();
m_databasePath = Objects.requireNonNull(databasePath, "databasePath").toAbsolutePath().normalize();
m_timeStampPrefix = Objects.requireNonNull(timeStampPrefix, "timeStampPrefix");
if (m_timeStampPrefix.trim().isEmpty()) {
throw new IllegalArgumentException("A completed run must have a timestamp prefix");
}
}

public Path getResultsDirectory() { return m_resultsDirectory; }
public Path getDatabasePath() { return m_databasePath; }
public String getTimeStampPrefix() { return m_timeStampPrefix; }
}
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ public class GuiRunnerAppController {
private GuiToggleTestTreeAction m_toggleTreeAction;
private GuiDisplayAboutDialogAction m_displayAboutDialogAction;
private GuiDisplayTestReportAction m_displayTestReportAction;
private PackageResultsAction m_packageResultsAction;
private OpenDefaultPIVDatabaseAction m_openDefaultPIVDatabaseAction;
private OpenDefaultPIVIDatabaseAction m_openDefaultPIVIDatabaseAction;
private GuiTestExecutionController m_tec;
Expand All @@ -55,6 +56,7 @@ public void reset() {
m_toggleTreeAction = null;
m_displayAboutDialogAction = null;
m_displayTestReportAction = null;
m_packageResultsAction = null;
m_openDefaultPIVDatabaseAction = null;
m_openDefaultPIVIDatabaseAction = null;
m_tec = null;
Expand Down Expand Up @@ -138,6 +140,10 @@ public GuiDisplayTestReportAction getDisplayTestReportAction() {
return m_displayTestReportAction;
}

public PackageResultsAction getPackageResultsAction() {
return m_packageResultsAction;
}

public void showAboutDialog() {
s_logger.error("Stubbed out showAboutDialog() is still here");
}
Expand All @@ -156,6 +162,9 @@ protected void createActions() {
m_toggleTreeAction = new GuiToggleTestTreeAction("Toggle test tree view", toggleIcon, "Show or hide the test tree");
ImageIcon displayReportIcon = getActionIcon("html", "Display HTML report");
m_displayTestReportAction = new GuiDisplayTestReportAction("Display Test Report", displayReportIcon, "Display test report for current log");
ImageIcon packageIcon = getActionIcon("database_save", "Package Results");
m_packageResultsAction = new PackageResultsAction("Package Results", packageIcon,
"Create a ZIP containing the latest completed run");
ImageIcon savingIcon = getActionIcon("folder", "Saving");
ImageIcon pivIcon = getActionIcon("PIV", "Open");
m_openDefaultPIVDatabaseAction = new OpenDefaultPIVDatabaseAction("Open Default PIV Database", pivIcon, "Open Default PIV conformance test database");
Expand Down
Loading
Loading