From ea6b025b9bfe4b05a7847b4f6a6495a5c21ddb55 Mon Sep 17 00:00:00 2001 From: Maximilian Beuscher <74049661+Beuscher@users.noreply.github.com> Date: Wed, 6 Mar 2024 15:14:55 +0100 Subject: [PATCH] init --- .github/workflows/container-ci.yml | 54 +++++++++++++++ .github/workflows/hello-ci.yml | 14 ++++ .github/workflows/python-ci.yml | 32 +++++++++ .gitignore | 9 +++ README.md | 14 ++++ SUMMARY.md | 8 +++ book.toml | 6 ++ container.md | 43 ++++++++++++ git.md | 88 +++++++++++++++++++++++ hello-ci.md | 108 +++++++++++++++++++++++++++++ intro.md | 91 ++++++++++++++++++++++++ main.tex | 26 +++++++ mandelbrot.py | 84 ++++++++++++++++++++++ mandelbrot.txt | 50 +++++++++++++ python.md | 80 +++++++++++++++++++++ requirements.txt | 3 + tools.md | 15 ++++ 17 files changed, 725 insertions(+) create mode 100644 .github/workflows/container-ci.yml create mode 100644 .github/workflows/hello-ci.yml create mode 100644 .github/workflows/python-ci.yml create mode 100644 .gitignore create mode 100644 README.md create mode 100644 SUMMARY.md create mode 100644 book.toml create mode 100644 container.md create mode 100644 git.md create mode 100644 hello-ci.md create mode 100644 intro.md create mode 100644 main.tex create mode 100644 mandelbrot.py create mode 100644 mandelbrot.txt create mode 100644 python.md create mode 100644 requirements.txt create mode 100644 tools.md diff --git a/.github/workflows/container-ci.yml b/.github/workflows/container-ci.yml new file mode 100644 index 0000000..49f2fd6 --- /dev/null +++ b/.github/workflows/container-ci.yml @@ -0,0 +1,54 @@ +name: Containerized CI +run-name: Container CI, triggered by ${{ github.event_name }} +on: [push, pull_request] +jobs: + plot: + name: Plot + runs-on: ubuntu-latest + steps: + - name: Check out repository code + uses: actions/checkout@v4 + - name: Set up Python 3.10 + uses: actions/setup-python@v5 + with: + python-version: '3.10' + - name: Install dependencies + run: | + python -m pip install --upgrade pip + pip install -r requirements.txt + - name: Run pylint + run: | + pylint --disable=import-outside-toplevel mandelbrot.py + - name: Generate the mandelbrot set + run: | + python mandelbrot.py + - name: Output as Artifact + uses: actions/upload-artifact@v4 + with: + name: mandelbrot + path: mandelbrot.pdf + retention-days: 1 + if-no-files-found: error + texlive: + runs-on: ubuntu-latest + needs: plot + container: + image: texlive/texlive:latest + steps: + - name: Checkout Repository + uses: actions/checkout@v4 + - name: Download artifact from run-matplotlib + uses: actions/download-artifact@v4 + with: + name: mandelbrot + path: + - name: Compile LaTeX document + run: | + latexmk -pdf main.tex + - name: Save matplotlib plot as atifact + uses: actions/upload-artifact@v4 + with: + name: pdf + path: main.pdf + retention-days: 1 + if-no-files-found: error diff --git a/.github/workflows/hello-ci.yml b/.github/workflows/hello-ci.yml new file mode 100644 index 0000000..3b0c9e1 --- /dev/null +++ b/.github/workflows/hello-ci.yml @@ -0,0 +1,14 @@ +name: Hello CI +run-name: Hello ${{ github.actor }}, this action was triggered by ${{ github.event_name }} +on: [push, pull_request] +jobs: + hello_job: + name: hello + runs-on: ubuntu-latest + steps: + - run: echo "Hello CI, running on ${{ runner.os }}" + - name: Check out repository code + uses: actions/checkout@v4 + - name: list files + run: | + ls ${{ github.workspace }} diff --git a/.github/workflows/python-ci.yml b/.github/workflows/python-ci.yml new file mode 100644 index 0000000..dfd7998 --- /dev/null +++ b/.github/workflows/python-ci.yml @@ -0,0 +1,32 @@ +name: First Python CI +run-name: Python CI, triggered by ${{ github.event_name }} +on: [push, pull_request] +jobs: + plot: + name: Plot + runs-on: ubuntu-latest + steps: + - name: Check out repository code + uses: actions/checkout@v4 + - name: Set up Python 3.10 + uses: actions/setup-python@v5 + with: + python-version: '3.10' + - name: Install dependencies + run: | + python -m pip install --upgrade pip + pip install -r requirements.txt + - name: Run pylint + run: | + pylint --disable=import-outside-toplevel mandelbrot.py + - name: Generate the mandelbrot set + run: | + python mandelbrot.py --pdf + - name: Output as Artifact + uses: actions/upload-artifact@v4 + with: + name: mandelbrot + path: mandelbrot.pdf + retention-days: 1 + if-no-files-found: error + diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..59cdd87 --- /dev/null +++ b/.gitignore @@ -0,0 +1,9 @@ +book +venv + +*.aux +*.log +*.fdb_latexmk +*.fls +*.pdf + diff --git a/README.md b/README.md new file mode 100644 index 0000000..1463483 --- /dev/null +++ b/README.md @@ -0,0 +1,14 @@ + +# deRSE24: Getting up and Running with your first CI Pipeline + +[mdbook](https://deRSE-ci.beuscher.dev) + +## Prerequisites + +- GitHub Account +- local instance of git, linked with GitHub account - if not: use [github.dev](https://github.dev) +- text editor (vs code, vim, ...) + +## Usefull Tools + +- [actionlint](https://rhysd.github.io/actionlint/) diff --git a/SUMMARY.md b/SUMMARY.md new file mode 100644 index 0000000..dd96dab --- /dev/null +++ b/SUMMARY.md @@ -0,0 +1,8 @@ +# Summary + +- [Introduction](./intro.md) + - [A git primer](./git.md) + - [Other useful tools](./tools.md) +- [A Hello World CI](./hello-ci.md) +- [Python and Artifacts](./python.md) +- [Containerization](./container.md) \ No newline at end of file diff --git a/book.toml b/book.toml new file mode 100644 index 0000000..d09e425 --- /dev/null +++ b/book.toml @@ -0,0 +1,6 @@ +[book] +authors = ["Maximilian Beuscher"] +language = "en" +multilingual = false +src = "" +title = "Getting up and running with your First CI/CD pipeline" diff --git a/container.md b/container.md new file mode 100644 index 0000000..7acec98 --- /dev/null +++ b/container.md @@ -0,0 +1,43 @@ +# Running containers in an Action + +There is the possibility that a lot of environments needed cannot or only be created in a time consuming manner in one of the provided runners. While one possibility is to use a selfhosted runner a far more straight forward solution could be to use containerization. Containers can provide an easy way to setup consistent environments and there is already a long list of pre build containers available (eg. on [docker.hub](https://docker.hub). + +It is also possitble to use a `dockerfile` to define a custom container environment, but in most cases it is sufficient to use a prebuild one. + +## Practical Example: A LaTeX Container + +In this example we will use the `texlive/texlive:latest` container to compile a simple latex document. + +The sample document can be downloaded through + +```sh +curl -L https://derse-ci.beuscher.dev/main.tex -o main.tex +``` + +Continuing our previous examples, we create a new job `texlive:`. + +GitHub provides already a straight forward way of creating a docker environment on the `ubuntu-latest` image by adding a `container` key and specify the container. + +```yml +{{#include .github/workflows/container-ci.yml:32:39}} +``` + +After checking out the repository we need to download the previously saved artifact using an action provided by GitHub> + +```yml +{{#include .github/workflows/container-ci.yml:40:44}} +``` + +Finally we invoke `latexmk`, starting the compilation and create an Artifact for the `main.pdf`: + +```yml +{{#include .github/workflows/container-ci.yml:45:47}} +``` + +The container part of the final workflow should be similar to: + +```yml +{{#include .github/workflows/container-ci.yml:48:}} +``` + +Note that since the - in terms of file size - large container needs to be downloaded from [docker.hub](https://docker.hub) this workflow will run for some time. diff --git a/git.md b/git.md new file mode 100644 index 0000000..d0a1407 --- /dev/null +++ b/git.md @@ -0,0 +1,88 @@ +# A short Git primer + +A more complete guide to Git can be fount here: + +- [SoftwareCarpentry Git Intro](https://swcarpentry.github.io/git-novice/index.html) + +## Installation + +On Linux `git` should already be installed, on MacOs or Windows, refer to [git downloads](https://git-scm.com/downloads) + +### Setting up the credentials + +```sh +git config --global user.name "Maximilian Beuscher" +git config --global user.email "max@beuscher.dev" +``` + +### SSH key setup + +```sh +cd ~/.ssh +``` + +```sh +ssh-keygen -t ed25519 -C "max@beuscher.dev" +``` + +When prompted for a file name just accept with `enter` to get the default naming, then `cat` the public key and copy it to the clipboard. + +```sh +cat ~/.ssh/id_ed25519.pub +``` + +Go to [Account > Keys](https://github.com/settings/keys) and create a new SSH Key. + +Check if ssh is configured correctly: + +```sh +ssh -T git@github.com +``` + +### Adding a Remote + +When cloning the repository from GitHub the remote should be already configured correctly, this can be verified with + +```sh +git remote -v +``` + +if not, modify the following command with your user and repository: + +```sh +git remote add origin git@github.com:beuscher/repo.git +``` + +git also allows to use https, requiring a slightly different "syntax" for the remote origin. + +## Git Command Cheat Sheet + +Add a file to the git repository: + +```sh +git add * +``` + +Check the current status of the repository, it will list for example uncommitted changes + +```sh +git status +``` + +Commit changes + +```sh +git commit -m [commit message] +``` + +Push changes to remote + +```sh +git push origin main +``` + +Pull changes fro remote + +```sh +git pull origin main +``` diff --git a/hello-ci.md b/hello-ci.md new file mode 100644 index 0000000..653071c --- /dev/null +++ b/hello-ci.md @@ -0,0 +1,108 @@ +# Hello CI + +## Setup a git repository and add a remote + +First we start on GitHub.com and create a new repository by selecting on the top right **New** > **Repository** and choose a suitable name. Next we select **SSH** instead of **HTTPS** and copy the address to the clipboard. + +We then switch to our local command line and start by creating a new folder and `cd` into it: + +```sh +mkdir first-ci +cd first-ci +``` + +then we locally initialize a new git repository, ensure we are on branch `main` and add the remote "origin": + +```sh +git init +git checkout -b main +git remote add origin git@github.com:beuscher/repo.git +``` + +one can verify this by running + +```sh +git remote -v +``` + +then we push our sample files to remote using + +```sh +git push origin main +``` + +Now we can start setting up our first CI pipeline. + +## A first workflow file + +Work flows are defined using `.yml` files located in the `.github/workflows/` directory, where each workflow is defined in its own file. Thus we create a new workflow `hello-ci` by creating a file named `hello-ci.yml`. + +```sh +mkdir .github/workflows/ +touch .github/workflows/hello-ci.yml +``` + +For each workflow some basic parameters are required, while `name` and `run-name` are technically optional (name would default to the file name of that action) they still should be set. +The `run-name` parameter specifies the name of a workflow run. When triggered by a pull or push event it will default to the commit message. +Here we will directly make use of a feature named Contexts, which allows us to access information about the repository, the current workflow run itself, the triggering event, etc. In this case we will use it to define the `run-name` in such a way that it will include who and which event triggered the run. + +One of the most important key:value pairs to be set is the `on: []` which defines which events will triggers this workflow, which we will set to `push, pull_request`. +To allow for a manually triggered workflow the `workflow_dispatch` event must be set. It is also possible to define more complex events like the creation of an issue or to restrict the tigger conditions by depending on changes to a subdirectory, but this is out of scope for this tutorial. + +The final top level key we need to set is `jobs:`. It groups all jobs of the workflow together. For our workflow to actually do something we will need to add a job. + +```yml +{{#include .github/workflows/hello-ci.yml::4}} +``` + +## The Hello CI job + +We start by creating our first job with the name `hello` and the job id `hello_job`. + +```yml +{{#include .github/workflows/hello-ci.yml:5:6}} +``` + +Further jobs (dependent or independent of other) can be defined by adding further named keys under `jobs:`. + +When then have to choose a runner for our job or in other words: We need to choose a platform and OS. In most cases this will be `ubuntu-latest`. + +```yml +{{#include .github/workflows/hello-ci.yml:5:7}} +``` + +GitHub also provides default runners for windows and mac (`windows-latest` and `macos-latest`) but one should note here, that those are billed with a factor x2, x10 on minutes. +Further there is also the option of self hosted runners. + +### Steps + +Each job is split up in multiple steps. In each step one can directly specify a command to run or run a script. Each step can also have a name. For a lot of common tasks like to checkout/clone the repository into virtual machine GitHub provides default actions. In our case we specify `actions/checkout@v4` which will allow us to access the contents of this repository. +We also `ls` to check that the checkout worked as expected. + +```yml +{{#include .github/workflows/hello-ci.yml:8:}} +``` + +these actions are essentially GitHub repositories containing robust scripts performing these tasks. + +Now one could use `actionlint` to check for syntax errors. + +## a first pipeline run + +The contents of `hello_ci.yml` should now be something like: + +```yml +{{#include .github/workflows/hello-ci.yml}} +``` + +Finally Sync with GitHub: + +```sh +git add .github/workflows/hello-ci.yml +git commit -m "hello-ci.yml" +git push origin main +``` + +### view run on GitHub + +In our repository (on [github.com](github.com)) when go check the Actions tab and select a workflow run to view details. diff --git a/intro.md b/intro.md new file mode 100644 index 0000000..169e79c --- /dev/null +++ b/intro.md @@ -0,0 +1,91 @@ +# Getting up and running with your First CI/CD pipeline + +[GitHub rpository](https://github.com/beuscher/deRSE-ci) + +## Prerequisites: + +- GitHub Account +- git +- local shell (preferably UNIX) +- text editor + +## What is CI/CD and why should you use it + +Continuous Integration/Deployment CI/CD refers to the automation of a large portion of human interaction usually necessary to propagate (small) code changes from the development stage into production. Using CI/CD pipelines can usually streamline the development and release usually also increasing productivity and reliability. This includes some of the following tasks: + +- testing and validation + - unit test + - code validation and formatting +- building of + - application + - website + - documentation +- deployment to + - package repositories + - infrastructure + +### Continuous Integration + +Continuous Integration is the practice of integrating source code changes frequently while utilizing automated testing and code validation to minimize code conflicts and help in the identification of error/bugs. + +### Continuous Deployment/Delivery + +Continuous Deployment/Delivery refers to the automated packaging (and building) of an application, website or project release - previously tested and validated by CI - with following deployment to infrastructure or repositories. + +## CI/CD integration + +There are several different choices for running CI/CD pipelines. While most are tied to a remote version control host some solutions are independent of those: + +- Travis CI +- GitHub Actions +- GitLab CI/CD +- containerization using self developed scripts (docker, podman, ...) +- ... + +### GitHub Actions + +GitHub Actions is the CI/CD Pipeline system provided by GitHub. For this tutorial we choose GitHub Actions for several reasons: + +- (Most) popular remote Git host +- Free Plan includes substantial amount of container minutes +- similar concepts compared to other implementations like GitLab CI + +#### Spending Limits and Billing + +Depending on the account type there is a free quota for GitHub Actions. Here minutes refer to the available processing time (in case of a Linux container) and Storage refers to the space available for storing "Artifacts". + +| Account type | Storage | Minutes | +|--------------|-----------|------------| +| GitHub Free | 500 MB | 2000 | +| GitHub Pro | 1 GB | 3000 | + +More details can be found under [Billing of GitHub Actions](https://docs.github.com/en/billing/managing-billing-for-github-actions/about-billing-for-github-actions). + +You can view the current usage in **Settings** > **Billing and plans** > **Plans and usage** then under "Usage this Month" > "Actions". + +#### YAML Syntax + +GitHub Actions are written in [YAML](https://yaml.org/spec/1.2.2/). It is a dataserialization language commonly used for configuration files. It uses python style indentation to indicate nesting of data. + +- `Key: value` pairs use intendation and newlines or are encapsuled by curly braces `{key1: 1, key2: 2}` +- Items in a list are prependen with `-` or can be inlined enclosed by `[Item1 ,Item2 ]`. +- Strings do not **need** to be encapsuled by `" "`. +- Multiline strings preserving the newline (usefull eg for chaining multiple commands) can be written using `|`: + ```yml + commands: | + echo "Hello," + echo "Wold" + ``` + +Example from the official website: + +```yml +YAML Resources: + YAML Specifications: + - YAML 1.2: + - Revision 1.2.2 # Oct 1, 2021 *New* + - Revision 1.2.1 # Oct 1, 2009 + - Revision 1.2.0 # Jul 21, 2009 + - YAML 1.1 + - YAML 1.0 +``` diff --git a/main.tex b/main.tex new file mode 100644 index 0000000..374e8cb --- /dev/null +++ b/main.tex @@ -0,0 +1,26 @@ +\documentclass{article} +\usepackage{graphicx} + +\title{Exploring the Mandelbrot Set} +\author{Your Name} + +\begin{document} +\maketitle + +\section{Introduction} +The Mandelbrot set is a famous mathematical object in complex dynamics. It is named after the mathematician Benoit Mandelbrot, who studied it in the 1970s. The Mandelbrot set is a set of complex numbers defined by a particular iterative process. Points within the Mandelbrot set remain bounded under iteration, while points outside it diverge to infinity. + +\section{Visualizing the Mandelbrot Set} +To visualize the Mandelbrot set, we can generate an image using computational methods. The image shown in Figure \ref{fig:mandelbrot} is generated using Python code. The colors represent the number of iterations required for each point to escape to infinity or to determine that it remains bounded. + +\begin{figure}[h] + \centering + \includegraphics[width=0.8\textwidth]{mandelbrot.pdf} + \caption{Visualization of the Mandelbrot Set} + \label{fig:mandelbrot} +\end{figure} + +\section{Conclusion} +The Mandelbrot set exhibits intricate and beautiful fractal patterns. It has captured the fascination of mathematicians, scientists, and artists alike. Exploring the Mandelbrot set reveals the depth and complexity of mathematics, offering insights into the nature of iteration, chaos, and infinity. + +\end{document} diff --git a/mandelbrot.py b/mandelbrot.py new file mode 100644 index 0000000..c886b86 --- /dev/null +++ b/mandelbrot.py @@ -0,0 +1,84 @@ +""" + Mandelbrot Script +""" +import argparse +import numpy as np + + +def mandelbrot(c, max_iter): + """ + Mandelbrot Iterator. + """ + z = 0 + n = 0 + while abs(z) <= 2 and n < max_iter: + z = z * z + c + n += 1 + return n + + +def generate_mandelbrot(width, height, x_minmax, y_minmax, max_iter): + """ + Generates the Mandelbrot set. + """ + x_min, x_max = x_minmax + y_min, y_max = y_minmax + mandelbrot_set = np.zeros((height, width), dtype=int) + for i in range(height): + for j in range(width): + real = x_min + (x_max - x_min) * j / (width - 1) + imag = y_min + (y_max - y_min) * i / (height - 1) + c = complex(real, imag) + mandelbrot_set[i, j] = mandelbrot(c, max_iter) + return mandelbrot_set + + +def draw_mandelbrot(mandelbrot_set, characters=" #@"): + """ + Prints the Mandelbrot set to stdout. + """ + max_iter = np.max(mandelbrot_set) + for row in mandelbrot_set: + for col in row: + index = int(col / max_iter * (len(characters) - 1)) + print(characters[index], end="") + print() + + +def save_mandelbrot_pdf(mandelbrot_set, filename="mandelbrot.pdf"): + """ + Saves the Mandelbrot set as a PDF. + """ + import matplotlib.pyplot as plt + + plt.imshow(mandelbrot_set, cmap="hot", origin="lower") + # plt.colorbar(label='Iterations') + plt.axis("off") + plt.savefig(filename, bbox_inches="tight") + plt.close() + + +if __name__ == "__main__": + parser = argparse.ArgumentParser(description="Generate Mandelbrot set") + parser.add_argument("--pdf", action="store_true", help="Save as PDF") + args = parser.parse_args() + + WIDTH = 120 + HEIGHT = 50 + X_MINMAX = (-2, 1) + Y_MINMAX = (-1, 1) + MAX_ITER = 50 + CHARACTERS = " .:-=+*#%@" + if args.pdf: + WIDTH = 800 + HEIGHT = 600 + + mandelbrot_array = generate_mandelbrot( + WIDTH, HEIGHT, X_MINMAX, Y_MINMAX, MAX_ITER + ) + + if args.pdf: + save_mandelbrot_pdf(mandelbrot_array) + print("Mandelbrot set saved as 'mandelbrot.pdf'") + else: + draw_mandelbrot(mandelbrot_array, CHARACTERS) diff --git a/mandelbrot.txt b/mandelbrot.txt new file mode 100644 index 0000000..7b298a0 --- /dev/null +++ b/mandelbrot.txt @@ -0,0 +1,50 @@ + .....-...... + ......-=..... + ......:=:..... + ....:..:@@%-..... + ....::--@@@@*-:-:... + .........:@@@@@@@@@%:.... + ............-@@@@@@@@@-*....... + ...............:=@@@@@@@@-............... + ..-.-::....--:::::-@@@@@@-:::..::........-. + .....+=-:%:.:@@=*@@@@@@@@@@@@@@@-@%-......:=.. + .......:-@@@*-@@@@@@@@@@@@@@@@@@@@@@=@+::@-#-=.. + ........:=@@@#@@@@@@@@@@@@@@@@@@@@@@@@@@-@@@@=.... + ..........::-@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@%.... + .........-=---@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@-:..... + .........................:-@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@=#..... + ..........................::@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@::.:. + ...::.......-:...........+@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@=+:. + ......:+---..:=#::-......:@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@+=:... + .......:+@@-*=@@=+*:*...::-@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@%.... + .......::@@@@@@@@@@@@@@:::-@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@-.. + ......-:-@@@@@@@@@@@@@@@@::@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@:... + ...........:-@@@@@@@@@@@@@@@@@%-#@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@=.. + ............::::::=@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@+... + ..................:-@@@+@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@:.... + .......:....:....::::%%@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@...... + .......:....:....::::%%@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@...... + ..................:-@@@+@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@:.... + ............::::::=@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@+... + ...........:-@@@@@@@@@@@@@@@@@%-#@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@=.. + ......-:-@@@@@@@@@@@@@@@@::@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@:... + .......::@@@@@@@@@@@@@@:::-@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@-.. + .......:+@@-*=@@=+*:*...::-@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@%.... + ......:+---..:=#::-......:@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@+=:... + ...::.......-:...........+@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@=+:. + ..........................::@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@::.:. + .........................:-@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@=#..... + .........-=---@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@-:..... + ..........::-@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@%.... + ........:=@@@#@@@@@@@@@@@@@@@@@@@@@@@@@@-@@@@=.... + .......:-@@@*-@@@@@@@@@@@@@@@@@@@@@@=@+::@-#-=.. + .....+=-:%:.:@@=*@@@@@@@@@@@@@@@-@%-......:=.. + ..-.-::....--:::::-@@@@@@-:::..::........-. + ...............:=@@@@@@@@-............... + ............-@@@@@@@@@-*....... + .........:@@@@@@@@@%:.... + ....::--@@@@*-:-:... + ....:..:@@%-..... + ......:=:..... + ......-=..... + .....-...... diff --git a/python.md b/python.md new file mode 100644 index 0000000..e6fdb28 --- /dev/null +++ b/python.md @@ -0,0 +1,80 @@ +# Hello Python + +Next we introduce some Python code into our workflow. First we will run `pylint` to check our code for possible syntax errors, then we run some python code to produce an output which we will save as an artifact. +As a test case i prepared a small python programm generating Mandelbrot Sets. It generates an ASCII version by default, printing to `stdout`, but also a higher resolution `.pdf` when `--pdf` is specified as an argument.. + +In many production cases one would run for example some sort of unit test or build a python package and deploy it to pip. + +To get the sample python script run + +```sh +curl -L https://derse-ci.beuscher.dev/mandelbrot.py -o mandelbrot.py +``` + +or [download](https://deRSE-ci.beuscher.dev/mandelbrot.py) it manually. + +## Setup Python and pylint + +Starting with a copy of the `hello-ci.yml` workflow, we choose suitable names the workflow(-run) and name the job `plot` + +```yml +{{#include .github/workflows/python-ci.yml:1:10}} +``` + +To setup our python environment we use an action provided by GitHub and specify a python version, in this case `3.10` + +```yml +{{#include .github/workflows/python-ci.yml:11:14}} +``` + +To install the `numpy` and `matplotlib` dependency used by this programm as well as pylint we provide `pip` (the python package manager) with a `requirements.txt` file which will be located in the top level directory of our repository. + +Our `requirements.txt`: + +```txt +{{#include requirements.txt}} +``` + +In the workflow file we add the following two commands: + +```yml +{{#include .github/workflows/python-ci.yml:15:18}} +``` + +Next, we run `pylint` on `mandelbrot.py` to check for syntax error and if it obeys code conventions. + +```yml +{{#include .github/workflows/python-ci.yml:19:21}} +``` + +Note: Here we need the argument `--disable=import-outside-toplevel` since the `matplotlib` dependency is only imported when `--pdf` is specified. + +When a syntax error is detected `pylint` will return an error and the job will be marked as *failed*. + +Finally we execute the python program with the `--pdf` argument: + +```yml +{{#include .github/workflows/python-ci.yml:22:24}} +``` + +## Save an Artifact + +By default we do not retain any build artifacts or other files generated during our workflow run. When files should be made available outside a job one needs to upload an artifact explicitly. This Artifact is then available in another job to "download" or can be viewed and downloaded from GitHub. If no retention days are specified GitHub will use the account default (initially set to 30 Days) + +```yml +{{#include .github/workflows/python-ci.yml:25:}} +``` + +The full action should now be similar to + +```yml +{{#include .github/workflows/python-ci.yml}} +``` + +Again one could check the worklfow using `actionlint`. + +## Commit and view the Artifacts + +We again commit our changes to the remote repository. + +After the run is finished we can access the generated artifacts when selecting a specific workflow run. diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..f2495ab --- /dev/null +++ b/requirements.txt @@ -0,0 +1,3 @@ +pylint==3.1.0 +numpy==1.21.2 +matplotlib==3.8.3 diff --git a/tools.md b/tools.md new file mode 100644 index 0000000..cefc022 --- /dev/null +++ b/tools.md @@ -0,0 +1,15 @@ +# Other useful tools + +## actionlint + +[actionlint](https://rhysd.github.io/actionlint/) is a simple tool which checks all actions in the current repository for syntax errors. It also provides a web version. + +- [Installation Instructions](https://github.com/rhysd/actionlint/blob/main/docs/install.md) + +## act + +act is a tool aiming to provide GitHub actions locally on your machine using a docker API compatible containerization solution. + +## github.dev + +GitHub provides web versions of vscode for repositories hosted on GitHUB allowing for straight forward code editing. Just change `.com` into `.dev` when viewing a repository.