From 74806b75946dc53430caca3a77990ad63de3a1fc Mon Sep 17 00:00:00 2001 From: Idir Izitounene Date: Tue, 6 Feb 2024 11:22:13 +0100 Subject: [PATCH] Build/Test - Add new test with multiple filters and rename build workflows --- .build/.versions.yml | 2 +- .../{build-base.yml => build-test-base.yml} | 0 ...science.yml => build-test-datascience.yml} | 0 .../{build-spark.yml => build-test-spark.yml} | 0 .github/workflows/main.yml | 14 +- .github/workflows/unit-tests.yml | 2 +- README.md | 137 ++++++++++++++++-- doc/_images/build-pipeline.png | Bin 0 -> 61866 bytes .../test_version_compatibility_matrix.py | 82 ++++++++++- 9 files changed, 207 insertions(+), 30 deletions(-) rename .github/workflows/{build-base.yml => build-test-base.yml} (100%) rename .github/workflows/{build-datascience.yml => build-test-datascience.yml} (100%) rename .github/workflows/{build-spark.yml => build-test-spark.yml} (100%) create mode 100644 doc/_images/build-pipeline.png diff --git a/.build/.versions.yml b/.build/.versions.yml index b8d9345..b7282a0 100644 --- a/.build/.versions.yml +++ b/.build/.versions.yml @@ -35,7 +35,7 @@ compatibility-matrix: scala_version: [2.12, 2.13] hadoop_version: 3 spark_download_url: https://archive.apache.org/dist/spark/ -### Ovveride the matrix above by providing the versions to build +### Override the matrix above by providing the versions to build ### 1- The build-matrix is empty: build with all possible combintations ### 2- Override specific versions: build with all possible combinations which are compatible with that specific versions ### 3- The versions not present on compatibility-matrix are ignored diff --git a/.github/workflows/build-base.yml b/.github/workflows/build-test-base.yml similarity index 100% rename from .github/workflows/build-base.yml rename to .github/workflows/build-test-base.yml diff --git a/.github/workflows/build-datascience.yml b/.github/workflows/build-test-datascience.yml similarity index 100% rename from .github/workflows/build-datascience.yml rename to .github/workflows/build-test-datascience.yml diff --git a/.github/workflows/build-spark.yml b/.github/workflows/build-test-spark.yml similarity index 100% rename from .github/workflows/build-spark.yml rename to .github/workflows/build-test-spark.yml diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index dad509d..8b3eb04 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -25,7 +25,7 @@ on: push: branches: - - build-pipeline + - main paths: - ".github/workflows/main.yml" - ".github/workflows/build-base.yml" @@ -95,13 +95,13 @@ jobs: needs: [run-unit-tests] build-base: - name: build-base (python-${{ matrix.python.python_version }}) + name: build-test-base (python-${{ matrix.python.python_version }}) strategy: # 3 Jobs in //, the base jobs run in sequential max-parallel: 3 matrix: python: ${{ fromJson(needs.build-version-compatibility-matrix.outputs.python) }} - uses: ./.github/workflows/build-base.yml + uses: ./.github/workflows/build-test-base.yml with: python_version: ${{ matrix.python.python_version }} python_dev_tag: ${{ matrix.python.python_dev_tag }} @@ -113,13 +113,13 @@ jobs: needs: [build-version-compatibility-matrix] build-datascience: - name: build-datascience (python-${{ matrix.python.python_version }}) + name: build-test-datascience (python-${{ matrix.python.python_version }}) strategy: # 1 matrix call = +2 jobs in // (check the number here build-datascience.yml) max-parallel: 1 matrix: python: ${{ fromJson(needs.build-version-compatibility-matrix.outputs.python) }} - uses: ./.github/workflows/build-datascience.yml + uses: ./.github/workflows/build-test-datascience.yml with: python_dev_tag: ${{ matrix.python.python_dev_tag }} registry: ${{ vars.REGISTRY || 'ghcr.io' }} @@ -130,13 +130,13 @@ jobs: needs: [build-version-compatibility-matrix, build-base] build-spark: - name: build-spark (python-${{ matrix.spark.python_version }}) + name: build-test-spark (python-${{ matrix.spark.python_version }}) strategy: # 2 jobs in // max-parallel: 2 matrix: spark: ${{ fromJson(needs.build-version-compatibility-matrix.outputs.spark) }} - uses: ./.github/workflows/build-spark.yml + uses: ./.github/workflows/build-test-spark.yml with: spark_download_url: ${{ matrix.spark.spark_download_url }} python_version: ${{ matrix.spark.python_version }} diff --git a/.github/workflows/unit-tests.yml b/.github/workflows/unit-tests.yml index a85fe29..8c07a70 100644 --- a/.github/workflows/unit-tests.yml +++ b/.github/workflows/unit-tests.yml @@ -22,7 +22,7 @@ jobs: uses: ./docker-stacks/.github/actions/create-dev-env - name: Run unit tests - run: pytest python/tests -v + run: pytest python/tests -v --color=yes shell: bash diff --git a/README.md b/README.md index 9649b72..8bcf1ed 100644 --- a/README.md +++ b/README.md @@ -1,26 +1,131 @@ -# kdp-docker-stacks +# OKDP Jupyter Images -kdp jupyter images based on https://github.com/jupyter/docker-stacks +[![Build, test, tag, and push jupyter images](https://github.com/OKDP/jupyterlab-docker/actions/workflows/main.yml/badge.svg)](https://github.com/OKDP/jupyterlab-docker/actions/workflows/main.yml) +OKDP jupyter docker images based on [jupyter docker-stacks](https://github.com/jupyter/docker-stacks) source dockerfiles. It includes (read only copy) [jupyter docker-stacks](https://github.com/jupyter/docker-stacks) repository as a [git-subtree](https://www.atlassian.com/git/tutorials/git-subtree) sub project. -# Extension +The project leverages the features provided by [jupyter docker-stacks](https://github.com/jupyter/docker-stacks): +- Build from the original [source docker files](docker-stacks/images) +- Customize the images by using docker ```build-arg``` build arguments +- Run the original [tests](docker-stacks/tests) at every pipeline trigger +The project provides an up to date jupyter lab images especially for pyspark. -# Initial setup +# Images build workflow +## Build/Test + +The [main](.github/workflows/main.yml) build pipeline contains 6 main reusable workflows: + +1. [build-test-base](.github/workflows/build-test-base.yml): docker-stacks-foundation, base-notebook, minimal-notebook, scipy-notebook +2. [build-test-datascience](.github/workflows/build-test-datascience.yml): r-notebook, julia-notebook, tensorflow-notebook, pytorch-notebook +3. [build-test-spark](.github/workflows/build-test-spark.yml): pyspark-notebook, all-spark-notebook +4. [tag-push](.github/workflows/docker-tag-push.yml): push the built images to the container registry (main branch only) +5. [auto-rerun](.github/workflows/auto-rerun.yml): partially re-run jobs in case of failures (github runner issues/main branch only) +6. [unit-tests](.github/workflows/unit-tests.yml): run the unit tests (okdp extension) at every pipeline trigger + +![build pipeline](doc/_images/build-pipeline.png) + +The build is based on the [version compatibility matrix](.build/.versions.yml). + +The [build-matrix](.build/.versions.yml#L42) section defines the components versions to build. It behaves like a filter of the parent [version compatibility matrix](.build/.versions.yml) to limit the versions combintations to build. The build process ensures only the compatible versions are built: + +For example, the following build-matrix: + +```yaml +build-matrix: + python_version: ['3.9', '3.10', '3.11'] + spark_version: [3.2.4, 3.3.4, 3.4.2, 3.5.0] + java_version: [11, 17] + scala_version: [2.12] +``` + +Will build the following versions combinations in regards to [compatibility-matrix](.build/.versions.yml#5) section: +- spark3.3.4-python3.10-java17-scala2.12 +- spark3.5.0-python3.11-java17-scala2.12 +- spark3.4.2-python3.11-java17-scala2.12 +- spark3.2.4-python3.9-java11-scala2.12 + +By default, if no filter is specified: + +```yaml +build-matrix: +``` + +All compatible versions combinations are built. + +Finally, all the images are tested against the original [tests](docker-stacks/tests) at every pipeline trigger + +## Push + +Development images with tags ```--latest``` suffix (ex.: spark3.2.4-python3.9-java11-scala2.12--latest) are produced at every pipeline run regardless of the git branch (main or not). + +The [official images](#tagging) are pushed to the [container registry](https://github.com/orgs/OKDP/packages) when: + +1. The workflow is triggered on the main branch only and +2. The [tests](#build/test) are completed successfully + +This prevents pull requests or developement branchs to push the official images before they are reviewed or tested. It also provides the flexibility to test against developement images ```--latest``` before they are officially pushed. + +## Tagging + +The project builds the images with a long format tags. Each tag combines multiple compatible versions combinations. + +There are multiple tags levels and the format to use is depending on your convenience in term of stability and reproducibility. + +Here are some examples: + +### scipy-notebook: +- python-3.11-2024-02-06 +- python-3.11.7-2024-02-06 +- python-3.11.7-hub-4.0.2-lab-4.1.0 +- python-3.11.7-hub-4.0.2-lab-4.1.0-2024-02-06 + +### datascience-notebook: +- python-3.9-2024-02-06 +- python-3.9.18-2024-02-06 +- python-3.9.18-hub-4.0.2-lab-4.1.0 +- python-3.9.18-hub-4.0.2-lab-4.1.0-2024-02-06 +- python-3.9.18-r-4.3.2-julia-1.10.0-2024-02-06 +- python-3.9.18-r-4.3.2-julia-1.10.0-hub-4.0.2-lab-4.1.0 +- python-3.9.18-r-4.3.2-julia-1.10.0-hub-4.0.2-lab-4.1.0-2024-02-06 + +### pyspark-notebook: +- spark-3.5.0-python-3.11-java-17-scala-2.12 +- spark-3.5.0-python-3.11-java-17-scala-2.12-2024-02-06 +- spark-3.5.0-python-3.11.7-java-17.0.9-scala-2.12.18-hub-4.0.2-lab-4.1.0 +- spark-3.5.0-python-3.11.7-java-17.0.9-scala-2.12.18-hub-4.0.2-lab-4.1.0-2024-02-06 +- spark-3.5.0-python-3.11.7-r-4.3.2-java-17.0.9-scala-2.12.18-hub-4.0.2-lab-4.1.0 +- spark-3.5.0-python-3.11.7-r-4.3.2-java-17.0.9-scala-2.12.18-hub-4.0.2-lab-4.1.0-2024-02-06 + +Please, check the [container registry](https://github.com/orgs/OKDP/packages) for more images and tags. + +# Build locally with Act + +[Act](https://github.com/nektos/act) can be used to build and test locally. + +Here is an example command: ```shell -git remote add docker-stacks https://github.com/jupyter/docker-stacks.git -git subtree add --prefix=docker-stacks --squash docker-stacks main +$ act --container-architecture linux/amd64 \ + -W .github/workflows/main.yml \ + --env ACT_SKIP_TESTS= \ + --var REGISTRY=ghcr.io \ + --secret REGISTRY_USERNAME= \ + --secret REGISTRY_ROBOT_TOKEN= + --rm ``` +set the option ```--container-architecture linux/amd64``` if you are running locally with Apple's M1/M2 chips. + +For more information: + ```shell -act --container-architecture linux/amd64 \ - -W .github/workflows/docker.yml \ - --artifact-server-path /tmp/act/artifacts \ - --env ACT_SKIP_TESTS= \ - --env PUSH_TO_REGISTRY=true \ - --env REGISTRY=ghcr.io \ - --secret REGISTRY_USERNAME= \ - --secret REGISTRY_ROBOT_TOKEN= - --rm -``` \ No newline at end of file +$ act --help +``` + +# OKDP custom extensions + +1. [Tagging extension](python/okdp/extension/tagging) is based on the original [jupyter docker-stacks](docker-stacks/tagging) source files +2. [Patchs](python/okdp/patch/README.md) patchs the original [jupyter docker-stacks](docker-stacks/tests) in order to run the tests +3. [Version compatibility matrix](python/okdp/extension/matrix) to generate all the compatible versions combintations for pyspark +4. [Unit tests](python/tests) in order to test okdp extension at every pipeline run \ No newline at end of file diff --git a/doc/_images/build-pipeline.png b/doc/_images/build-pipeline.png new file mode 100644 index 0000000000000000000000000000000000000000..466bde37a4f8ae9a0ac3e2948756f19e26c0f0aa GIT binary patch literal 61866 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 None: # Given: version_compatibility_matrix_data @@ -67,7 +67,7 @@ def test_build_matrix_empty( assert to_dict(expected_build_matrix_empty) == spark_matrix -def test_filter_spark_version( +def test_filter_by_spark_version( version_compatibility_matrix_data: list[dict], ) -> None: # Given: version_compatibility_matrix_data @@ -111,7 +111,7 @@ def test_filter_spark_version( assert spark_matrix == to_dict(expected_test_filter_spark_version) assert python_version == to_dict("""[{"python_version": "3.9", "python_dev_tag": "python3.9-main-latest"}]""") -def test_filter_spark_version_scala_version( +def test_filter_by_spark_version_and_scala_version( version_compatibility_matrix_data: list[dict], ) -> None: # Given: version_compatibility_matrix_data @@ -145,7 +145,80 @@ def test_filter_spark_version_scala_version( assert spark_matrix == to_dict(expected_test_filter_spark_version) assert python_version == to_dict("""[{"python_version": "3.9", "python_dev_tag": "python3.9-main-latest"}]""") -def test_filter_wrong_version( +def test_filter_by_multiple_versions( + version_compatibility_matrix_data: list[dict], +) -> None: + # Given: version_compatibility_matrix_data + version_compatibility_matrix = version_compatibility_matrix_data + # The python_version is not supported by the compatibilty matrix + build_matrix = { + "python_version": ["3.9", "3.10", "3.11"], + "spark_version": ["3.2.4", "3.3.4", "3.4.2", "3.5.0"], + "java_version": [11, 17], + "scala_version": [2.12] + } + + # When: + vcm = MockedVersionCompatibilityMatrix(compatibility_matrix = version_compatibility_matrix, + build_matrix = build_matrix, + git_branch="main") + vcm._normalize_values_() + (spark_matrix, python_version) = vcm.generate_matrix() + + # Then: check the number of combinations when the build_matrix is empty + expected_nb_combinations = 4 + actual_nb_combinations = len(spark_matrix) + assert actual_nb_combinations == expected_nb_combinations, f"spark_matrix: The number of elements should be {expected_nb_combinations}, got {actual_nb_combinations}" + + assert spark_matrix == to_dict("""[ + { + "python_version": "3.10", + "spark_version": "3.3.4", + "java_version": "17", + "scala_version": "", + "hadoop_version": "3", + "spark_download_url": "https://archive.apache.org/dist/spark/", + "spark_dev_tag": "spark3.3.4-python3.10-java17-scala2.12-main-latest", + "python_dev_tag": "python3.10-main-latest" + }, + { + "python_version": "3.11", + "spark_version": "3.5.0", + "java_version": "17", + "scala_version": "", + "hadoop_version": "3", + "spark_download_url": "https://archive.apache.org/dist/spark/", + "spark_dev_tag": "spark3.5.0-python3.11-java17-scala2.12-main-latest", + "python_dev_tag": "python3.11-main-latest" + }, + { + "python_version": "3.11", + "spark_version": "3.4.2", + "java_version": "17", + "scala_version": "", + "hadoop_version": "3", + "spark_download_url": "https://archive.apache.org/dist/spark/", + "spark_dev_tag": "spark3.4.2-python3.11-java17-scala2.12-main-latest", + "python_dev_tag": "python3.11-main-latest" + }, + { + "python_version": "3.9", + "spark_version": "3.2.4", + "java_version": "11", + "scala_version": "", + "hadoop_version": "3.2", + "spark_download_url": "https://archive.apache.org/dist/spark/", + "spark_dev_tag": "spark3.2.4-python3.9-java11-scala2.12-main-latest", + "python_dev_tag": "python3.9-main-latest" + } + ]""") + assert python_version == to_dict("""[ + {"python_version": "3.10", "python_dev_tag": "python3.10-main-latest"}, + {"python_version": "3.11", "python_dev_tag": "python3.11-main-latest"}, + {"python_version": "3.9", "python_dev_tag": "python3.9-main-latest"} + ]""") + +def test_filter_by_wrong_version( version_compatibility_matrix_data: list[dict], ) -> None: # Given: version_compatibility_matrix_data @@ -165,4 +238,3 @@ def test_filter_wrong_version( actual_nb_combinations = len(spark_matrix) assert actual_nb_combinations == expected_nb_combinations, f"spark_matrix: The number of elements should be {expected_nb_combinations}, got {actual_nb_combinations}" assert len(python_version) == expected_nb_combinations, f"python_version: The number of elements should be {expected_nb_combinations}, got {actual_nb_combinations}" -