diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000..58e2046 --- /dev/null +++ b/.dockerignore @@ -0,0 +1,5 @@ +target/ +.git/ +.github/ +.gitignore +*.md diff --git a/.github/workflows/docker.yml b/.github/workflows/docker.yml new file mode 100644 index 0000000..1f66bdb --- /dev/null +++ b/.github/workflows/docker.yml @@ -0,0 +1,84 @@ +name: Docker + +on: + push: + branches: + - main + tags: + - 'v*' + pull_request: + branches: + - main + workflow_run: + workflows: ["CI"] + types: + - completed + branches: + - main + +env: + REGISTRY: ghcr.io + IMAGE_NAME: ${{ github.repository }} + +jobs: + build-and-push: + runs-on: ubuntu-latest + if: > + (github.event_name == 'workflow_run' && github.event.workflow_run.conclusion == 'success') || + (github.event_name == 'push' && startsWith(github.ref, 'refs/tags/v')) || + github.event_name == 'pull_request' + permissions: + contents: read + packages: write + + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v3 + + - name: Log in to Container Registry + if: github.event_name != 'pull_request' + uses: docker/login-action@v3 + with: + registry: ${{ env.REGISTRY }} + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + + - name: Extract metadata (tags, labels) + id: meta + uses: docker/metadata-action@v5 + with: + images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }} + tags: | + type=ref,event=branch + type=ref,event=pr + type=semver,pattern={{version}} + type=semver,pattern={{major}}.{{minor}} + type=sha,prefix= + + - name: Build Docker image + uses: docker/build-push-action@v6 + with: + context: . + load: true + tags: ${{ steps.meta.outputs.tags }} + cache-from: type=gha + cache-to: type=gha,mode=max + + - name: Smoke test - verify container starts + run: | + IMAGE_TAG=$(echo "${{ steps.meta.outputs.tags }}" | head -n1) + docker run --rm "$IMAGE_TAG" + + - name: Build and push Docker image + if: github.event_name != 'pull_request' + uses: docker/build-push-action@v6 + with: + context: . + push: true + tags: ${{ steps.meta.outputs.tags }} + labels: ${{ steps.meta.outputs.labels }} + cache-from: type=gha + cache-to: type=gha,mode=max diff --git a/.gitignore b/.gitignore index 2f7896d..8b8c81d 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,2 @@ target/ +dependency-reduced-pom.xml diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..330991b --- /dev/null +++ b/Dockerfile @@ -0,0 +1,29 @@ +# Build stage: compile and package with Maven +FROM eclipse-temurin:17-jdk-alpine AS builder + +WORKDIR /build + +# Copy Maven configuration first for layer caching +COPY pom.xml . +RUN apk add --no-cache maven \ + && mvn dependency:go-offline -B + +# Copy source and build +COPY src ./src +RUN mvn package -DskipTests -q + +# Runtime stage: minimal JRE image +FROM eclipse-temurin:17-jre-alpine + +LABEL org.opencontainers.image.title="NDArray Library Demo" \ + org.opencontainers.image.description="NumPy-inspired array library for Java - Feature Demo" \ + org.opencontainers.image.source="https://github.com/sMouaad/DevOps-Project" \ + org.opencontainers.image.vendor="sadisamir" + +WORKDIR /app + +# Copy the built JAR from builder stage +COPY --from=builder /build/target/ndarray-library-*.jar ./ndarray-demo.jar + +# Run the demo on container start +ENTRYPOINT ["java", "-jar", "ndarray-demo.jar"] diff --git a/README.md b/README.md index 57caa44..64095e9 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,7 @@ # NDArray Library (Java) ![CI](https://github.com/sMouaad/DevOps-Project/actions/workflows/ci.yml/badge.svg) +![Docker](https://github.com/sMouaad/DevOps-Project/actions/workflows/docker.yml/badge.svg) Small NumPy-inspired Java library for ndarray operations, developed as a DevOps team project. @@ -89,6 +90,45 @@ Planned CI quality hardening: - Optional minimum coverage threshold gate once pipeline is stable. +## Docker + +A demo container is available that showcases all NDArray features on startup. + +### Quick Start (Pull and Run) + +```bash +docker run --rm ghcr.io/smouaad/devops-project:main +``` + +### Build Locally + +```bash +# Build the image +docker build -t ndarray-demo . + +# Run the demo +docker run --rm ndarray-demo +``` + +### Image Tags + +Images are published to GitHub Container Registry (`ghcr.io/smouaad/devops-project`): + +| Tag | Description | +|-----|-------------| +| `main` | Latest build from main branch | +| `` | Semantic version (e.g., `1.0.0`, `1.0`) | +| `` | Specific commit SHA | + +### CI/CD Pipeline + +The Docker workflow (`.github/workflows/docker.yml`): + +- Builds on every push to `main` and version tags (`v*`) +- Validates builds on pull requests (no push) +- Uses multi-stage build for minimal image size +- Caches layers with GitHub Actions cache + ## Current Status vs Mandatory Scope Implemented: diff --git a/pom.xml b/pom.xml index 57a727f..da8b5f4 100644 --- a/pom.xml +++ b/pom.xml @@ -22,6 +22,16 @@ + + org.slf4j + slf4j-api + 2.0.13 + + + org.slf4j + slf4j-simple + 2.0.13 + org.junit.jupiter junit-jupiter @@ -50,6 +60,40 @@ + + org.apache.maven.plugins + maven-jar-plugin + 3.4.2 + + + + org.sadisamir.ndarray.demo.NdArrayDemo + + + + + + + org.apache.maven.plugins + maven-shade-plugin + 3.6.0 + + + package + + shade + + + + + org.sadisamir.ndarray.demo.NdArrayDemo + + + + + + + org.jacoco jacoco-maven-plugin diff --git a/src/main/java/org/sadisamir/ndarray/demo/NdArrayDemo.java b/src/main/java/org/sadisamir/ndarray/demo/NdArrayDemo.java new file mode 100644 index 0000000..0f347cc --- /dev/null +++ b/src/main/java/org/sadisamir/ndarray/demo/NdArrayDemo.java @@ -0,0 +1,181 @@ +package org.sadisamir.ndarray.demo; + +import org.sadisamir.ndarray.NdArray; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * Demonstrates NDArray library features. + * This class serves as the entrypoint for the Docker demo container. + */ +public class NdArrayDemo { + + private static final Logger LOGGER = LoggerFactory.getLogger(NdArrayDemo.class); + private static final String SEPARATOR = "─".repeat(50); + private static final String RESULT_FORMAT = " Result: {}"; + private static final String RESULT_LABEL = " Result:"; + private static final String SHAPE_FORMAT = " shape: {}"; + + public static void main(String[] args) { + printBanner(); + demo1DArrayCreation(); + demo2DArrayCreation(); + demoArithmeticOperations(); + demoReshape(); + demoLargeArrayDisplay(); + printFooter(); + } + + private static void printBanner() { + LOGGER.info(""); + LOGGER.info("╔══════════════════════════════════════════════════╗"); + LOGGER.info("║ NDArray Library Feature Showcase ║"); + LOGGER.info("║ NumPy-inspired arrays for Java ║"); + LOGGER.info("╚══════════════════════════════════════════════════╝"); + LOGGER.info(""); + } + + private static void demo1DArrayCreation() { + printSection("1D Array Creation"); + + LOGGER.info("Creating array from values: NdArray.array(new float[]{{1, 2, 3, 4, 5}})"); + NdArray arr = NdArray.array(new float[]{1f, 2f, 3f, 4f, 5f}); + LOGGER.info(RESULT_FORMAT, arr); + LOGGER.info(" ndim: {}", arr.getNdim()); + logShape(arr.getShape()); + LOGGER.info(" size: {}", arr.getSize()); + LOGGER.info(""); + + LOGGER.info("Creating zeros array: NdArray.zeros(4)"); + NdArray zeros = NdArray.zeros(4); + LOGGER.info(RESULT_FORMAT, zeros); + LOGGER.info(""); + + LOGGER.info("Creating range array: NdArray.arange(0, 10, 2)"); + NdArray range = NdArray.arange(0f, 10f, 2f); + LOGGER.info(RESULT_FORMAT, range); + LOGGER.info(""); + } + + private static void demo2DArrayCreation() { + printSection("2D Array (Matrix) Creation"); + + LOGGER.info("Creating 2D array from matrix:"); + LOGGER.info(" NdArray.array(new float[][]{{{{1, 2, 3}}, {{4, 5, 6}}}})"); + NdArray matrix = NdArray.array(new float[][]{{1f, 2f, 3f}, {4f, 5f, 6f}}); + LOGGER.info(RESULT_LABEL); + printMatrix(matrix); + LOGGER.info(" ndim: {}", matrix.getNdim()); + logShape(matrix.getShape()); + LOGGER.info(" size: {}", matrix.getSize()); + LOGGER.info(""); + } + + private static void demoArithmeticOperations() { + printSection("Arithmetic Operations"); + + NdArray a = NdArray.array(new float[]{1f, 2f, 3f}); + NdArray b = NdArray.array(new float[]{10f, 20f, 30f}); + + LOGGER.info("Array a: {}", a); + LOGGER.info("Array b: {}", b); + LOGGER.info(""); + + LOGGER.info("Element-wise addition: a.add(b)"); + NdArray sum = a.add(b); + LOGGER.info(RESULT_FORMAT, sum); + LOGGER.info(""); + + LOGGER.info("In-place addition: a.addInPlace(b)"); + NdArray c = NdArray.array(new float[]{1f, 2f, 3f}); + LOGGER.info(" Before: {}", c); + c.addInPlace(b); + LOGGER.info(" After: {}", c); + LOGGER.info(""); + + LOGGER.info("2D Matrix addition:"); + NdArray m1 = NdArray.array(new float[][]{{1f, 2f}, {3f, 4f}}); + NdArray m2 = NdArray.array(new float[][]{{10f, 10f}, {10f, 10f}}); + LOGGER.info(" Matrix 1:"); + printMatrix(m1); + LOGGER.info(" Matrix 2:"); + printMatrix(m2); + LOGGER.info(" Sum (m1.add(m2)):"); + printMatrix(m1.add(m2)); + LOGGER.info(""); + } + + private static void demoReshape() { + printSection("Reshape Operations"); + + LOGGER.info("Creating 1D array: NdArray.arange(6)"); + NdArray flat = NdArray.arange(6f); + LOGGER.info(RESULT_FORMAT, flat); + logShape(flat.getShape()); + LOGGER.info(""); + + LOGGER.info("Reshaping to 2x3: flat.reshape(2, 3)"); + NdArray reshaped = flat.reshape(2, 3); + LOGGER.info(RESULT_LABEL); + printMatrix(reshaped); + logShape(reshaped.getShape()); + LOGGER.info(""); + + LOGGER.info("Reshaping to 3x2: flat.reshape(3, 2)"); + NdArray reshaped2 = flat.reshape(3, 2); + LOGGER.info(RESULT_LABEL); + printMatrix(reshaped2); + logShape(reshaped2.getShape()); + LOGGER.info(""); + } + + private static void demoLargeArrayDisplay() { + printSection("Large Array Display (Ellipsis)"); + + LOGGER.info("Creating large array: NdArray.arange(20)"); + NdArray large = NdArray.arange(20f); + LOGGER.info(RESULT_FORMAT, large); + LOGGER.info(" (Note: Large arrays use ellipsis notation)"); + LOGGER.info(""); + } + + private static void printSection(String title) { + LOGGER.info(SEPARATOR); + LOGGER.info("▶ {}", title); + LOGGER.info(SEPARATOR); + LOGGER.info(""); + } + + private static void printMatrix(NdArray matrix) { + if (LOGGER.isInfoEnabled()) { + String[] lines = matrix.toString().split("\n"); + for (String line : lines) { + LOGGER.info(" {}", line); + } + } + } + + private static void logShape(int[] shape) { + if (LOGGER.isInfoEnabled()) { + LOGGER.info(SHAPE_FORMAT, formatShape(shape)); + } + } + + private static String formatShape(int[] shape) { + StringBuilder sb = new StringBuilder("("); + for (int i = 0; i < shape.length; i++) { + if (i > 0) sb.append(", "); + sb.append(shape[i]); + } + sb.append(")"); + return sb.toString(); + } + + private static void printFooter() { + LOGGER.info(SEPARATOR); + LOGGER.info("✓ Demo completed successfully!"); + LOGGER.info(" Repository: https://github.com/sMouaad/DevOps-Project"); + LOGGER.info(SEPARATOR); + LOGGER.info(""); + } +} diff --git a/src/test/java/org/sadisamir/ndarray/demo/NdArrayDemoTest.java b/src/test/java/org/sadisamir/ndarray/demo/NdArrayDemoTest.java new file mode 100644 index 0000000..544cf42 --- /dev/null +++ b/src/test/java/org/sadisamir/ndarray/demo/NdArrayDemoTest.java @@ -0,0 +1,16 @@ +package org.sadisamir.ndarray.demo; + +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; + +/** + * Tests for NdArrayDemo to ensure demo execution completes without errors. + */ +class NdArrayDemoTest { + + @Test + void testMainExecutesWithoutException() { + assertDoesNotThrow(() -> NdArrayDemo.main(new String[]{})); + } +}