├── .gitignore ├── examples ├── nr2_nginx_content │ ├── nginx_files │ │ ├── public │ │ │ └── index.html │ │ └── nginx.conf │ ├── .gitignore │ ├── README.md │ └── pod.yaml ├── nr1_nginx_simple │ ├── .gitignore │ ├── pod.yaml │ └── README.md ├── nr3_nginx_logs_on_volume │ ├── nginx_files │ │ ├── public │ │ │ └── index.html │ │ └── nginx.conf │ ├── .gitignore │ ├── pod.yaml │ └── README.md ├── nr4_nginx_zero_downtime_reload │ ├── nginx_files │ │ ├── public │ │ │ └── index.html │ │ └── nginx.conf │ ├── .gitignore │ ├── README.md │ └── pod.yaml └── nr5_nginx_configs_and_secrets │ ├── config │ └── nginx_files │ │ ├── public │ │ └── index.html │ │ └── nginx.conf │ ├── .gitignore │ ├── pod.yaml │ └── README.md ├── make.sh ├── lib └── podman-runtime.yaml ├── CHANGELOG.md ├── LICENSE ├── Spacefile.yaml ├── .github └── workflows │ └── main.yml ├── boot2podman_download_create_and_run.sh ├── README.md ├── PODSPEC.md └── Spacefile.bash /.gitignore: -------------------------------------------------------------------------------- 1 | release 2 | *.cache 3 | -------------------------------------------------------------------------------- /examples/nr2_nginx_content/nginx_files/public/index.html: -------------------------------------------------------------------------------- 1 | Hello World! 2 | -------------------------------------------------------------------------------- /examples/nr1_nginx_simple/.gitignore: -------------------------------------------------------------------------------- 1 | pod 2 | log 3 | .pod.yaml 4 | .pod.status 5 | -------------------------------------------------------------------------------- /examples/nr2_nginx_content/.gitignore: -------------------------------------------------------------------------------- 1 | pod 2 | log 3 | .pod.yaml 4 | .pod.status 5 | -------------------------------------------------------------------------------- /examples/nr3_nginx_logs_on_volume/nginx_files/public/index.html: -------------------------------------------------------------------------------- 1 | Hello World! 2 | -------------------------------------------------------------------------------- /examples/nr4_nginx_zero_downtime_reload/nginx_files/public/index.html: -------------------------------------------------------------------------------- 1 | Hello World! 2 | -------------------------------------------------------------------------------- /examples/nr3_nginx_logs_on_volume/.gitignore: -------------------------------------------------------------------------------- 1 | pod 2 | log 3 | .pod.yaml 4 | .pod.status 5 | -------------------------------------------------------------------------------- /examples/nr5_nginx_configs_and_secrets/config/nginx_files/public/index.html: -------------------------------------------------------------------------------- 1 | Hello World! 2 | -------------------------------------------------------------------------------- /examples/nr4_nginx_zero_downtime_reload/.gitignore: -------------------------------------------------------------------------------- 1 | pod 2 | log 3 | .pod.yaml 4 | .pod.status 5 | -------------------------------------------------------------------------------- /examples/nr5_nginx_configs_and_secrets/.gitignore: -------------------------------------------------------------------------------- 1 | pod 2 | log 3 | .pod.yaml 4 | .pod.status 5 | -------------------------------------------------------------------------------- /examples/nr1_nginx_simple/pod.yaml: -------------------------------------------------------------------------------- 1 | api: 1.0.0-beta1 2 | podVersion: 0.0.1 3 | 4 | containers: 5 | - name: nginx 6 | image: nginx:1.16.1-alpine 7 | expose: 8 | - targetPort: 80 9 | hostPort: 8080 10 | -------------------------------------------------------------------------------- /make.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | # Script to export a release 3 | set -e 4 | mkdir -p ./release 5 | space /_cmdline/ -e SPACE_MUTE_EXIT_MESSAGE=1 -d >./release/podc 6 | chmod +x ./release/podc 7 | space -f lib/podman-runtime.yaml /podman/ -e SPACE_MUTE_EXIT_MESSAGE=1 -d >./release/podc-podman-runtime 8 | -------------------------------------------------------------------------------- /lib/podman-runtime.yaml: -------------------------------------------------------------------------------- 1 | @clone: string:1.4.0 network:2.1.0 file:1.6.0 2 | _env: 3 | - 4 | - RUNTIME_VERSION: podman 1.0.0 5 | - API_VERSION: >- 6 | @include: @{DIR}/../Spacefile.yaml|/_env/API_VERSION 7 | podman: 8 | _info: 9 | title: Pod runtime to run and manage a podman pod 10 | _env: 11 | - RUN: POD_ENTRY "\$@" 12 | -------------------------------------------------------------------------------- /examples/nr1_nginx_simple/README.md: -------------------------------------------------------------------------------- 1 | # Simple nginx example 2 | 3 | In this example we show the simplest way of running a pod with exposed ports. 4 | 5 | The vanilla nginx image serves as the container to run since it does present a welcome page by default and needs no further configurations. 6 | 7 | ```sh 8 | podc 9 | ./pod run 10 | ./pod ps 11 | curl 127.0.0.1:8080 12 | ./pod logs 13 | ./pod rm 14 | ``` 15 | -------------------------------------------------------------------------------- /examples/nr2_nginx_content/README.md: -------------------------------------------------------------------------------- 1 | # nginx example with content mounted from host 2 | 3 | In this example we will see how to mount content from disk into a nginx container. 4 | 5 | We do this by using a `volume` with `type: volume`, which we `mount` into the container at a specific directory, to where we point the nginx logs to be stored. 6 | 7 | ```sh 8 | podc 9 | ./pod run 10 | ./pod ps 11 | curl 127.0.0.1:8080 12 | ./pod logs 13 | ./pod rm 14 | ``` 15 | -------------------------------------------------------------------------------- /examples/nr4_nginx_zero_downtime_reload/README.md: -------------------------------------------------------------------------------- 1 | # nginx example showing how to reload nginx without any downtime 2 | 3 | When updating the `nginx.conf` file we have to send the nginx process a `HUP` signal to have it pick up the changes, without resorting to restarting the process. 4 | 5 | With Simplenetes `podc` we can specify signals for containers and send those signals by calling `pod signal [container]`. 6 | 7 | ```sh 8 | podc 9 | ./pod run 10 | ./pod ps 11 | curl 127.0.0.1:8080 12 | # Try changing some aspact of the nginx.conf file then run: 13 | ./pod signal 14 | # nginx.conf will now have been reloaded. 15 | ./pod rm 16 | ``` 17 | -------------------------------------------------------------------------------- /examples/nr5_nginx_configs_and_secrets/pod.yaml: -------------------------------------------------------------------------------- 1 | api: 1.0.0-beta1 2 | podVersion: 0.0.1 3 | 4 | # Create a volume binded to a directory on host. 5 | volumes: 6 | - name: nginx_files 7 | type: config 8 | 9 | containers: 10 | - name: nginx 11 | image: nginx:1.16.1-alpine 12 | mounts: 13 | - volume: nginx_files 14 | dest: /nginx_files 15 | command: 16 | - nginx 17 | - -c 18 | - /nginx_files/nginx.conf 19 | - -g 20 | - daemon off; 21 | signal: 22 | - sig: HUP 23 | expose: 24 | - targetPort: 80 25 | hostPort: 8080 26 | -------------------------------------------------------------------------------- /examples/nr4_nginx_zero_downtime_reload/pod.yaml: -------------------------------------------------------------------------------- 1 | api: 1.0.0-beta1 2 | podVersion: 0.0.1 3 | 4 | # Create a volume binded to a directory on host. 5 | volumes: 6 | - name: nginx_files 7 | type: host 8 | bind: ./nginx_files 9 | 10 | containers: 11 | - name: nginx 12 | image: nginx:1.16.1-alpine 13 | mounts: 14 | - volume: nginx_files 15 | dest: /nginx_files 16 | command: 17 | - nginx 18 | - -c 19 | - /nginx_files/nginx.conf 20 | - -g 21 | - daemon off; 22 | signal: 23 | - sig: HUP 24 | expose: 25 | - targetPort: 80 26 | hostPort: 8080 27 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # podc - Change log 2 | 3 | ## [0.4.0 - 2021-04-08] 4 | 5 | + Add CHANGELOG.md :) 6 | 7 | + Add support for `network: host` 8 | 9 | + Add cmd line option (-e/--source-env) to allow missing variables to be sourced from environment 10 | 11 | - Remove expose section for runtime executable 12 | 13 | * Add `lsof` to podman runtime to check for busy ports, do not fail if program non existing 14 | 15 | * Update pod API spec to 1.0.0-beta2 16 | 17 | * Allow config volumes names to begin with underscore 18 | 19 | * Allow compiler to be backwards compatible 20 | 21 | * Rename release/podman-runtime- to release/podc-podman-runtime, since backwards compatible now 22 | 23 | * Upgrade network module dependency to 2.1.0 24 | -------------------------------------------------------------------------------- /examples/nr2_nginx_content/pod.yaml: -------------------------------------------------------------------------------- 1 | api: 1.0.0-beta1 2 | podVersion: 0.0.1 3 | 4 | # Create a volume binded to a directory on host. 5 | # This is mostly useful when working in development mode, in such case the volume could be set within a preprocessor directive to not be present when compiling for production. See other examples on that. 6 | volumes: 7 | - name: nginx_files 8 | type: host 9 | bind: ./nginx_files 10 | 11 | containers: 12 | - name: nginx 13 | image: nginx:1.16.1-alpine 14 | mounts: 15 | - volume: nginx_files 16 | dest: /nginx_files 17 | command: 18 | - nginx 19 | - -c 20 | - /nginx_files/nginx.conf 21 | - -g 22 | - daemon off; 23 | expose: 24 | - targetPort: 80 25 | hostPort: 8080 26 | -------------------------------------------------------------------------------- /examples/nr3_nginx_logs_on_volume/pod.yaml: -------------------------------------------------------------------------------- 1 | api: 1.0.0-beta1 2 | podVersion: 0.0.1 3 | 4 | # Create a regular podman volume named "access_log". 5 | # We make it shared across pod version by setting `shared: pod`, 6 | # this means that when updating the pod version it will still use the same volume as the previous version. 7 | volumes: 8 | - name: access_log 9 | type: volume 10 | shared: pod 11 | - name: nginx_files 12 | type: host 13 | bind: ./nginx_files 14 | 15 | containers: 16 | - name: nginx 17 | image: nginx:1.16.1-alpine 18 | mounts: 19 | - volume: nginx_files 20 | dest: /nginx_files 21 | - volume: access_log 22 | dest: /mnt/access_log 23 | command: 24 | - nginx 25 | - -c 26 | - /nginx_files/nginx.conf 27 | - -g 28 | - daemon off; 29 | expose: 30 | - targetPort: 80 31 | hostPort: 8080 32 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2020-2021 Thomas Backlund and others 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 4 | 5 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 6 | 7 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 8 | -------------------------------------------------------------------------------- /examples/nr3_nginx_logs_on_volume/README.md: -------------------------------------------------------------------------------- 1 | # nginx example with logs stored on volumes 2 | 3 | In this example we will see how we can use podman volumes to store container logs elsewhere than in the pod directory. 4 | 5 | All `stdout` and `stderr` for all containers and the pod daemon management process are stored in the directory `./log` and are accessible with the `pod logs` command. 6 | 7 | However, we might want to redirect the nginx access log to be stored on a volume instead, which can make it accessible to monitoring tools, for example. 8 | 9 | We do this by using a `volume` with `type: volume`, which we `mount` into the container at a specific directory. To this directory we point the nginx access log. 10 | 11 | ```sh 12 | podc 13 | ./pod run 14 | ./pod ps 15 | curl 127.0.0.1:8080 16 | 17 | # Getting the pod logs will not show the access log 18 | ./pod logs 19 | 20 | # We'll get the access log by accessing the volume. For example via the pod it self since it already has the volume mounted. 21 | ./pod shell -c nginx -- tail -f /mnt/access_log/access.log 22 | 23 | ./pod rm 24 | ``` 25 | -------------------------------------------------------------------------------- /examples/nr2_nginx_content/nginx_files/nginx.conf: -------------------------------------------------------------------------------- 1 | user nginx; 2 | worker_processes auto; 3 | 4 | error_log /dev/stderr warn; 5 | pid /var/run/nginx.pid; 6 | 7 | events { 8 | worker_connections 1024; 9 | } 10 | 11 | http { 12 | types { 13 | text/html html htm shtml; 14 | text/css css; 15 | image/gif gif; 16 | image/jpeg jpeg jpg; 17 | application/javascript js; 18 | image/png png; 19 | } 20 | 21 | default_type text/html; 22 | 23 | log_format main '$remote_addr - $remote_user [$time_local] "$request" ' 24 | '$status $body_bytes_sent "$http_referer" ' 25 | '"$http_user_agent" "$http_x_forwarded_for"'; 26 | 27 | access_log /dev/stdout main; 28 | 29 | keepalive_timeout 65; 30 | 31 | server { 32 | listen 80; 33 | server_name localhost; 34 | 35 | location / { 36 | root /nginx_files/public; 37 | index index.html; 38 | } 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /examples/nr4_nginx_zero_downtime_reload/nginx_files/nginx.conf: -------------------------------------------------------------------------------- 1 | user nginx; 2 | worker_processes auto; 3 | 4 | error_log /dev/stderr warn; 5 | pid /var/run/nginx.pid; 6 | 7 | events { 8 | worker_connections 1024; 9 | } 10 | 11 | http { 12 | types { 13 | text/html html htm shtml; 14 | text/css css; 15 | image/gif gif; 16 | image/jpeg jpeg jpg; 17 | application/javascript js; 18 | image/png png; 19 | } 20 | 21 | default_type text/html; 22 | 23 | log_format main '$remote_addr - $remote_user [$time_local] "$request" ' 24 | '$status $body_bytes_sent "$http_referer" ' 25 | '"$http_user_agent" "$http_x_forwarded_for"'; 26 | 27 | access_log /dev/stdout main; 28 | 29 | keepalive_timeout 65; 30 | 31 | server { 32 | listen 80; 33 | server_name localhost; 34 | 35 | location / { 36 | root /nginx_files/public; 37 | index index.html; 38 | } 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /examples/nr5_nginx_configs_and_secrets/config/nginx_files/nginx.conf: -------------------------------------------------------------------------------- 1 | user nginx; 2 | worker_processes auto; 3 | 4 | error_log /dev/stderr warn; 5 | pid /var/run/nginx.pid; 6 | 7 | events { 8 | worker_connections 1024; 9 | } 10 | 11 | http { 12 | types { 13 | text/html html htm shtml; 14 | text/css css; 15 | image/gif gif; 16 | image/jpeg jpeg jpg; 17 | application/javascript js; 18 | image/png png; 19 | } 20 | 21 | default_type text/html; 22 | 23 | log_format main '$remote_addr - $remote_user [$time_local] "$request" ' 24 | '$status $body_bytes_sent "$http_referer" ' 25 | '"$http_user_agent" "$http_x_forwarded_for"'; 26 | 27 | access_log /dev/stdout main; 28 | 29 | keepalive_timeout 65; 30 | 31 | server { 32 | listen 80; 33 | server_name localhost; 34 | 35 | location / { 36 | root /nginx_files/public; 37 | index index.html; 38 | } 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /examples/nr3_nginx_logs_on_volume/nginx_files/nginx.conf: -------------------------------------------------------------------------------- 1 | user nginx; 2 | worker_processes auto; 3 | 4 | error_log /dev/stderr warn; 5 | pid /var/run/nginx.pid; 6 | 7 | events { 8 | worker_connections 1024; 9 | } 10 | 11 | http { 12 | types { 13 | text/html html htm shtml; 14 | text/css css; 15 | image/gif gif; 16 | image/jpeg jpeg jpg; 17 | application/javascript js; 18 | image/png png; 19 | } 20 | 21 | default_type text/html; 22 | 23 | log_format main '$remote_addr - $remote_user [$time_local] "$request" ' 24 | '$status $body_bytes_sent "$http_referer" ' 25 | '"$http_user_agent" "$http_x_forwarded_for"'; 26 | 27 | access_log /mnt/access_log/access.log main; 28 | 29 | keepalive_timeout 65; 30 | 31 | server { 32 | listen 80; 33 | server_name localhost; 34 | 35 | location / { 36 | root /nginx_files/public; 37 | index index.html; 38 | } 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /Spacefile.yaml: -------------------------------------------------------------------------------- 1 | @clone: yaml:1.0.2 string:1.4.0 text:1.1.1 file:1.6.0 2 | 3 | _env: 4 | # Current podc version 5 | - VERSION: 0.4.0 6 | # Current compiler supported API version 7 | - API_VERSION: 1.0.0-beta2 8 | # Current and former API versions which the compiler is compatible with 9 | - SUPPORTED_API_VERSIONS: ${API_VERSION} 1.0.0-beta1 10 | compile: 11 | _info: 12 | title: Compile a YAML pod spec into a standalone self managed executable. 13 | _env: 14 | - name: 15 | title: Name of the pod when created. 16 | desc: | 17 | For podman runtimes this is the name of the pod container and suffix of other containers. 18 | - inFile: 19 | title: YAML file containing the spec to compile into an executable. Default is pod.yaml. 20 | value: ${infile:-pod.yaml} 21 | - outFile: 22 | title: The output file (this also gives the target directory) 23 | - srcDir: 24 | title: Override the source directory of the pod.yaml file. 25 | desc: | 26 | If the inFile is not located where the original pod.yaml file is (which could happen on an outside preprocessing event) 27 | then we need to provide the source dir if compiling an executable runtime, since it looks for "./bin". 28 | - doPreprocessing: 29 | title: Preprocess and variable substitute pod.yaml file before compiling. Default true. 30 | values: 31 | - false 32 | - true 33 | - allowSourceEnv: 34 | title: Allow variables not defined in pod.yaml to be sourced from environment 35 | values: 36 | - false 37 | - true 38 | - RUN: COMPILE_ENTRY -- "${name}" "${inFile}" "${outFile}" "${srcDir}" "${doPreprocessing}" "${allowSourceEnv}" 39 | 40 | _cmdline: 41 | _info: 42 | title: Use podc as a standalone cmd line tool 43 | _env: 44 | - RUN: PODC_CMDLINE -- "\$@" 45 | -------------------------------------------------------------------------------- /examples/nr5_nginx_configs_and_secrets/README.md: -------------------------------------------------------------------------------- 1 | # nginx example showing how leverege configs 2 | 3 | In the last example we saw how to reload the `nginx.conf` file with zero downtime. 4 | 5 | Here we will see a similar example but we are using a Simplenetes concept called `configs` to get a unified experience which also works when running the pod inside a Simplenetes cluster to automatically get the zero downtime reload feature when updating configs. 6 | 7 | A `config` is a special volume type in Simplenetes which actually is a `host` type but with some extra functionality to it. 8 | 9 | Config files must be located in a sub directory to the `config` directory in the pod dir. That sub directory is the name of the config. 10 | 11 | We will put our nginx files there. In normal cases we don't put content inside configs, just small things such as config files and secrets. 12 | 13 | We specify the `HUP` signal for the nginx container, so that the pod knows how to signal the container when the config is to be reloaded. 14 | 15 | 16 | ```sh 17 | podc 18 | ./pod run 19 | ./pod ps 20 | curl 127.0.0.1:8080 21 | # Try changing some aspact of the nginx.conf file then run: 22 | ./pod reload-configs nginx_files 23 | # nginx.conf will now have been reloaded. 24 | ./pod rm 25 | ``` 26 | 27 | This looks very similar to the previous example when we ran `pod signal` instead, however leveraging configs scales better and reloading configs will make sure all containers dependent on the config will get signalled. 28 | 29 | Also when putting the pod inside a cluster the reloading part comes for free. 30 | 31 | ## Secrets 32 | Providing secrets, such as API keys for a process is often crucial to have it working. However such secrets should not be stored within the image it self, both for security reasons but also that secrets might change over time, sometimes more rapidly than we want to release new versions of the pod. 33 | 34 | Simplenetes offers a neat way of managing secrets by providing them to the pod as _configs_. Since configs have a nice trait of automatically signalling containers depending on them when they are updated they can be an excellent choice for secrets. 35 | 36 | Secrets can be encrypted by Simplenetes to increase security even more when running inside a cluster, however encryption of secrets is not yet supported. 37 | -------------------------------------------------------------------------------- /.github/workflows/main.yml: -------------------------------------------------------------------------------- 1 | # This is a basic workflow to help you get started with Actions 2 | 3 | name: CI 4 | 5 | # Controls when the action will run. 6 | on: 7 | push: 8 | tags: 9 | - '*' 10 | 11 | # Allows you to run this workflow manually from the Actions tab 12 | workflow_dispatch: 13 | 14 | # A workflow run is made up of one or more jobs that can run sequentially or in parallel 15 | jobs: 16 | # This workflow contains a single job called "build" 17 | build: 18 | # The type of runner that the job will run on 19 | runs-on: ubuntu-latest 20 | 21 | # Steps represent a sequence of tasks that will be executed as part of the job 22 | steps: 23 | # Checks-out your repository under $GITHUB_WORKSPACE, so your job can access it 24 | - uses: actions/checkout@v2 25 | 26 | - name: Build 27 | id: build 28 | run: | 29 | curl -s https://get.space.sh | sudo sh 30 | sh ./make.sh 31 | cd release 32 | echo ::set-output name=podcversion::$(sha256sum --tag podc) 33 | echo ::set-output name=podcruntimeversion::$(sha256sum --tag podc-podman-runtime) 34 | cd .. 35 | 36 | - name: Create Release 37 | id: create_release 38 | uses: actions/create-release@v1 39 | env: 40 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 41 | with: 42 | tag_name: ${{ github.ref }} 43 | release_name: ${{ github.ref }} 44 | body: | 45 | Check the attachments below for this release's prebuilt executables: 46 | * ${{ steps.build.outputs.podcversion }} 47 | * ${{ steps.build.outputs.podcruntimeversion }} 48 | draft: false 49 | prerelease: false 50 | 51 | - name: Upload Release podc 52 | id: upload-release-podc 53 | uses: actions/upload-release-asset@v1 54 | env: 55 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 56 | with: 57 | upload_url: ${{ steps.create_release.outputs.upload_url }} 58 | asset_path: ./release/podc 59 | asset_name: podc 60 | asset_content_type: application/x-shellscript 61 | 62 | - name: Upload Release podman-runtime 63 | id: upload-release-podc-podman-runtime 64 | uses: actions/upload-release-asset@v1 65 | env: 66 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 67 | with: 68 | upload_url: ${{ steps.create_release.outputs.upload_url }} 69 | asset_path: ./release/podc-podman-runtime 70 | asset_name: podc-podman-runtime 71 | asset_content_type: application/x-shellscript 72 | -------------------------------------------------------------------------------- /boot2podman_download_create_and_run.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | 3 | # 4 | # Check required programs 5 | if ! command -v curl >/dev/null; then 6 | printf "FAIL: curl program is required\n" 1>&2 7 | exit 1 8 | fi 9 | 10 | if ! command -v VBoxManage >/dev/null; then 11 | printf "FAIL: VBoxManage program is required\n" 1>&2 12 | exit 1 13 | fi 14 | 15 | # 16 | # Setup URL and file name 17 | _image_base_url="https://github.com/boot2podman/boot2podman/releases/download/v0.25" 18 | _image_name="boot2podman.iso" 19 | _image_full_url="${_image_base_url}/${_image_name}" 20 | 21 | # 22 | # Download boot2podman disk image 23 | # Check file is available and download it if needed 24 | if [ ! -f "./${_image_name}" ]; then 25 | printf "Image file %s was not found. Downloading from %s...\n" "${_image_name}" "${_image_full_url}" 1>&2 26 | curl -LO "${_image_full_url}" 27 | _exit_code="$?" 28 | if [ "$_exit_code" -ne 0 ]; then 29 | printf "Failed to curl %s from %s. Returned %s\n" "${_image_name}" "${_image_base_url}" "$_exit_code" 1>&2 30 | exit "$_exit_code" 31 | fi 32 | fi 33 | 34 | # 35 | # FIXME: missing file integrity check around here 36 | # 37 | 38 | if [ ! -f "${_image_name}" ]; then 39 | printf "Unexpected error: missing %s image file.\n" "${_image_name}" 1>&2 40 | exit 1 41 | fi 42 | 43 | # 44 | # Create new VM 45 | _vm_name="podcompilerVM" 46 | if ! VBoxManage showvminfo ${_vm_name} >/dev/null 2>&1; then 47 | VBoxManage createvm --name ${_vm_name} --ostype Debian_64 --register 48 | VBoxManage modifyvm ${_vm_name} --memory 1024 49 | VBoxManage modifyvm ${_vm_name} --natpf1 rule1,tcp,,2222,,22 50 | VBoxManage storagectl ${_vm_name} --name "IDE Controller" --add ide 51 | VBoxManage storageattach ${_vm_name} --storagectl "IDE Controller" --port 0 --device 0 --type dvddrive --medium ${_image_name} 52 | fi 53 | 54 | # 55 | # Run VM 56 | VBoxManage startvm ${_vm_name} 57 | if [ "$?" -ne 0 ]; then 58 | exit 1 59 | fi 60 | 61 | printf "\n===== INSTRUCTIONS =====\n\n Before proceeding, remove [127.0.0.1]:2222 entry from ~/.ssh/known_hosts if that exists 62 | 63 | > Inside %s (Guest): 64 | > Login is expected to happen automatically. 65 | > User: tc 66 | > 67 | > Then proceed with the following commands: 68 | $ passwd 69 | $ sudo -s 70 | # echo \"PasswordAuthentication yes\" >> /usr/local/etc/ssh/sshd_config 71 | # echo \"MaxAuthTries 100\" >> /usr/local/etc/ssh/sshd_config 72 | # /usr/local/etc/init.d/openssh restart 73 | # exit 74 | 75 | From the Host computer, ssh will now be available: 76 | $ ssh tc@127.0.0.1 -p 2222 77 | 78 | Running Space commands inside the Guest OS can be done with the assistance of the ssh Space Module: 79 | $ source env.sh; space /_export/ -m ssh /wrap/ -e SSHUSER=tc -e SSHHOST=127.0.0.1 -e SSHPORT=2222 -e SPACE_ENV=\"\${SPACE_ENV}\" -- run 80 | \n" "${_vm_name}" 81 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Simplenetes Pod Compiler (podc) 2 | 3 | `podc` takes a _Simplenetes_ Pod YAML [specification](PODSPEC.md) and turns it into a runnable standalone shell script. 4 | 5 | The compiled shell script is a POSIX compliant shell script which manages the full pod life cycle. It uses `podman` (instead of Docker) to create and manage pods and containers in a root-less environment. 6 | 7 | A Simplenetes pod is similar to a Kubernetes pod but is simpler. Simplenetes pods can be used on their own or within a [Simplenetes cluster](https://github.com/simplenetes-io/simplenetes). 8 | 9 | `podc` is written in Bash. 10 | 11 | ## Features 12 | podc features: 13 | 14 | - Standalone shell script to manage the full pod cycle 15 | - Uses podman for running root-less containers 16 | - Separates stdout and stderr logging, per container 17 | - Compile in dev mode to mount local directory while developing for quick iterations 18 | - Support for ramdisks to never put sensitive files on disk 19 | - Support for non interruptive config updates of processes running (say for updating haproxy.conf) 20 | - Step into a shell into any container in the pod 21 | - Signal some or all containers 22 | - Startup probes 23 | - Readiness probes 24 | - Liveness probes 25 | - Mount shared and private volumes 26 | - Small and easily understandable specification 27 | - Ingress configs if running in a Simplenetes cluster 28 | 29 | ## Examples 30 | 31 | See [https://github.com/simplenetes-io/podc/tree/master/examples](https://github.com/simplenetes-io/podc/tree/master/examples) for more examples. 32 | 33 | Quick example: 34 | ```sh 35 | cd examples/nginx 36 | podc 37 | ./pod run 38 | ./pod ps 39 | curl 127.0.0.1:8080 40 | ./pod logs 41 | ./pod rm 42 | ``` 43 | 44 | ## How does this work? 45 | The `podc` program (a bash script) parses the `pod.yaml` file and outputs a runnable shell script, which is the pod. 46 | 47 | The yaml file can contain variables such as `${portHttp}`, which can be defined in the `pod.env` file and are then substituted in. Variables which are not in the `.env` file are read from environment. 48 | 49 | The resulting `pod` file is embedded with shell script code to leverage `podman` to manage the full pod life cycle. 50 | 51 | `podc` and it's `yaml processor` are both written in Bash, the resulting output is however in POSIX shell, so you can run the pod using `dash`, `ash`, `busybox ash`, etc, no bash needed. 52 | 53 | Note: When podc is used by Simplenetes in a _cluster project_ it does it's own preprocessing and varibles are then not read from the pod's `.env` file nor from the environment but from the `cluster-vars.env` file only. 54 | 55 | ## But why? 56 | `podc` and the `Simplenetes` cluster manager are both a reaction to the too much magic that Kubernetes packs. 57 | 58 | `podc` was compiled using [space.sh](https://github.com/space-sh/space), which is your friend to make shell script applications. 59 | 60 | ## Install 61 | `podc` is a standalone executable, written in Bash and will run anywhere Bash is installed. 62 | 63 | Dependencies are: 64 | Optional dependecies used by the podman runtime to check if ports are busy prior to creating the pod: 65 | - `sockstat`, `netstat` or `lsof`. 66 | 67 | `netstat` is commonly contained in the `net-tools` package and can be installed on Debian-based distributions as: 68 | ``` 69 | sudo apt-get install -yq net-tools 70 | ``` 71 | 72 | The reason `podc` is written in Bash and not POSIX shell is that it has a built in YAML parser which requires the more feature rich Bash to run. 73 | Even though `podc` it self is a standalone executable it requires a runtime template file for generating pod scripts. This file must also be accessible on the system. 74 | `podc` will look for the `podman-runtime` template file first in the same directory as it self (`./`), then in `./release` and finally in `/opt/podc`. 75 | The reason for that it looks in `./release` is because it makes developing the pod compiler easier. 76 | 77 | Check out the latest release [here](https://github.com/simplenetes-io/podc/releases/latest) or manually download it from the command line: 78 | ```sh 79 | LATEST_VERSION=$(curl -s -H "Accept: application/vnd.github.v3+json" https://api.github.com/repos/simplenetes-io/podc/releases/latest | grep tag_name | cut -d":" -f2 | tr -d ",|\"| ") 80 | curl -LO https://github.com/simplenetes-io/podc/releases/download/$LATEST_VERSION/podc 81 | curl -LO https://github.com/simplenetes-io/podc/releases/download/$LATEST_VERSION/podc-podman-runtime 82 | chmod +x podc 83 | sudo mv podc /usr/local/bin 84 | sudo mv podc-podman-runtime /usr/local/bin 85 | ``` 86 | 87 | ## Set up ssh-enabled VM for running 88 | `podc` is for Linux only, run a VM if on any other OS. 89 | 90 | ```sh 91 | ./boot2podman_download_create_and_run.sh 92 | ``` 93 | -------------------------------------------------------------------------------- /PODSPEC.md: -------------------------------------------------------------------------------- 1 | # Simplenetes Pod specification 2 | 3 | ## Background 4 | Simplenetes pods are managed by a daemon process. This process is created on `create` together with the pod. The process PID is put as a label on the pod so the commands know where to find it. 5 | The process is signalled TERM/USR2 to terminate/kill it self, done by the `stop` and `kill` commands (or using `kill` directly). 6 | The daemon process constantly is updating the `.pod.status` file with the current runtime information. 7 | 8 | Each container run is wrapped in a process which is used to separate stdout/stderr logging per container. Logs are stored in `./logs`. 9 | 10 | Motivation for this project: 11 | 12 | - We use podman because: 13 | - containers are run root-less 14 | - No Docker daemon 15 | - Docker compatible images 16 | - podman has fine grained pod features exposed in the tool 17 | - We want more sophisticated restart policies than podman offers, such as `on-interval`. 18 | - We want to manage log files with rotation and separate stdout/stderr properly. 19 | - We want a single executable as the pod 20 | - We want the concept of ramdisks in the pod 21 | - We want the concept of configs in the pod 22 | - We want the concept of containers signalling each other on start events 23 | 24 | ## Noteworthy 25 | Rootless pods and containers cannot be paused/unpaused. 26 | 27 | `podc` does not either support stop/start without having the containers recreated. So always store important files on volumes. 28 | 29 | We don't rely on the native `--restart` flag for restarting containers. This module persistent process handles all restart logic. 30 | 31 | 32 | ## Logs 33 | Each container has a separate log for its stdout and its stderr. 34 | 35 | The daemon process has its own logs. 36 | 37 | ## Naming 38 | Pod names, container names and volume names must all match `a-z`, `0-9` and underscore `(_)`, and has to start with a letter. 39 | 40 | Container names are internally always automatically suffixed with `-podname-version` as they are created with podman. When referring to a container when calling the pod script the suffix is not to be used but is always automatically added. 41 | 42 | However is running `podman inspect` or similar you will need to add the suffix. 43 | 44 | Volume names can be configured so they are shared per host, per pod or even per pod and version (this is managed using naming suffixes). 45 | Volumes which are `configs` are allowed to begin with underscore, which will stop them from being propagated to Simplenetes clusters. 46 | 47 | ## Configs 48 | A config directory in a pod is something which the pod can mount to a container. 49 | When files in a config are modified the containers mounting those configs will get signalled (if a signal is defined) or restarted (depending on restart policy). This is a neat way of updating configs for a running pod without taking it down. 50 | An example is the Ingress pod running `haproxy` which gets automatically signalled to reload the `haproxy.conf` when the file inside the config dir gets updated. 51 | 52 | ### Configs and Simplenetes cluster projects 53 | The config dirs in a pod repo can be `imported` into the cluster project and from there they are available to the pod after it is compiled. 54 | Underscore prefixed dirs are copied to the cluster project but are then not copied to pod releases nor synced to hosts in cluster. 55 | Such underscores configs are used as templates for the cluster projects, such as the `ingress pod's` templates `(_tpl/)` for haproxy configuration snippets, and also for when running pods as standalone and deliberately not wanting particulars configs to be propagated to the cluster hosts. 56 | Note that only directories in the pod config directory are after import copied to pod releases, any files in the root config directory will never be part of a release (the directory is the config) 57 | This `import` procedure does not apply to standalone pods. 58 | 59 | ## Ramdisks 60 | If a pod is leveraging ramdisks (for sensitive data) those must be created by root before creating the pod. 61 | You can call `sudo pod create-ramdisks` to have the ramdisks created and `sudo pod create-ramdisks -d` to have them deleted. 62 | If not supplying ramdisks from the outside (by root) the pod will automatically create fake ramdisks (plain directories), which are useful when developing. 63 | 64 | Ramdisks is most useful when running pods inside a cluster, then most often simplenetesd is run as root and will create any ramdisks requested. 65 | 66 | Note that this is the only root priviligie needed for all of Simplenetes, to create ramdisks. 67 | 68 | ## Restart policies 69 | Simplenetes pods have their own restart policies. 70 | Containers are always created with the `podman` option `--restart=never`, then all restarting is managed by the pod daemon process (not simplenetesd, each pod has a daemon process taking care of business). 71 | 72 | In `podman` a root-less container cannot be paused/resumed, Simplenetes is even more harsh and saying that containers which are stopped cannot be started again. 73 | So when restarting a container it is actually removed and recreated as a new container. This is to force a behaviour which makes upgrading pods smoother 74 | as containers can never rely on temporary data inside the container, they must always use volumes for that, which then also can be used by next version of released pods. 75 | 76 | Restart policy for when a container exits: 77 | 78 | - always - no matter exit code restart container) 79 | - on-config - restart when i) config files on disk have changed, ii) when container exited with error code or iii) when in exited state and signalled by another container or manually by user 80 | - on-interval:x - as on-config, but also restart on successful exists after x seconds has passed 81 | - on-failure - restart container when exit code is not equal to 0 82 | - never - never restart container, unless manually restarted by user using "pod rerun " 83 | 84 | ## Configuring podman 85 | Configuring podman for rootless: 86 | The files /etc/sub{uid,gid} need id mappings. Single line in each file as: 87 | bashlund:10000:6553 88 | 89 | The above allocates 6553 IDs to be used by containers. 90 | Run `podman system migrate` to have podman pick that up. 91 | 92 | If any (rootless) containers are binding to host ports lower than 1024, then we must set on the host machines `sysctl net.ipv4.ip_unprivileged_port_start=80` (or whatever the lowest port number is). 93 | 94 | ## Probes 95 | There are three different probe supported for pods: 96 | 97 | - Startup probes 98 | - Readiness probes 99 | - Liveness probes 100 | 101 | All probes run a command in the container to determine if the probe was successful or not. The startup probe can also wait for a clean exit (code 0) of the container and consider it a successful startup. 102 | 103 | Each probe has a timeout for how long the command or the repeated invocations (for startup) of the command is allowed to take. 104 | 105 | A probe is always run inside the container, so when wanting to do TCP/HTTP probes, that would mean that `curl`, `wget` or `netcat` have to be installed in the container for the probe to be able to run. 106 | 107 | Simplenetes does not define a special case for command, HTTP or TCP probes, they are all command probes, and they all run inside the containers using `podman exec`. 108 | 109 | If having to delay the Liveness probe then use a Startup probe to either determine that liveness is safe to run now or which simply sleeps a number of seconds before letting the pod creation continue. 110 | 111 | ## Proxy and Ingress confs 112 | When a Pod is compiled, the compiler outputs alongside the `pod` executable possibly two extra files. 113 | These files only make sense when using pods not as standalone pods but within the Simplenetes pod orchestrator. 114 | 115 | - pod.portmappings.conf 116 | A conf file which describes the clusterPort->HostPort relations for the Pod, and some more. 117 | Each entry is as: 118 | clusterPort:hostPort:maxConn:sendProxy 119 | This file is later accessed by the `sns` project manager when finding new unused host ports to delegate when compiling. 120 | Also the `simplenetesd` accesses this file to configure the host-global `portmappings.conf` file, so that internal routing between pods on different hosts work. 121 | This file is synced to the cluster together with the pod executable. 122 | 123 | - pod.ingress.conf 124 | A conf file which describes the ingress configuration for the compiled pod. 125 | This file is used only by the `sns` project manager when generating the ingress configuration (haproxy.conf) for the ingress pod. 126 | This file is not synced to the cluster. 127 | 128 | ## The Simplenetes pod YAML specification 129 | There are a couple of different types of Pods supported so far in Simplenetes. 130 | 131 | - Container Pods (using podman) 132 | These are what most people refer to as pods, a collection of containers on the same container network. 133 | Just as Kuberenetes Pods. 134 | - Executable Pods 135 | These are single executables which conform to the Pod API. 136 | The Proxy Pod is an executable, not a real pod. 137 | 138 | Very important to note is the `podc` yaml interpretor has a few restrictions compared to other yaml 139 | processors (because it is implemented in Bash). The most notable is that lists *must be indented*. 140 | This will NOT work: 141 | ```yaml 142 | parent: 143 | - item1 144 | - item2 145 | ``` 146 | This will work: 147 | ```yaml 148 | parent: 149 | - item1 150 | - item2 151 | ``` 152 | 153 | ## Preprocessing 154 | The `pod.yaml` file is run through a simple preprocessor which does variable substitution and excluding/including of lines. 155 | 156 | The only two preprocessor directives available are `#ifdef` and `#iftrue`, together with their counterparts `#ifndef` and `#ifntrue`. 157 | 158 | `#ifdef ${variable}` simply checks if the variable has any value. 159 | 160 | `#iftrue ${variable}` checks if the variable is equal to "true". 161 | 162 | Each directive must end with an `#endif`. 163 | 164 | Each directive *must not* be indented. 165 | 166 | Directives cannot be nested. 167 | 168 | Variables are read from the `pod.env` file, and if not present there then optionally sourced from the environment (which however can be considered bad practice, but sometimes useful). 169 | 170 | The variable `${podVersion}` is special and is defined in the `yaml` as `podVersion: x.y.z` and made available as a variable by podc. 171 | 172 | ```yaml 173 | person: 174 | #ifdef ${myVar} 175 | name: ${myVar} 176 | #endif 177 | #ifndef ${myVar} 178 | # Use a default name 179 | name: McRaminov 180 | #endif 181 | #iftrue ${hazSkillz} 182 | rating: 10 183 | #endif 184 | #ifntrue ${hazSkillz} 185 | rating: 0 186 | #endif 187 | ``` 188 | 189 | ### Container Pod Spec (using podman) 190 | 191 | ```yaml 192 | # For the podcompiler to know what the yaml structure is supposed to look like and how to compile the output. 193 | api: 1.0.0-beta2 194 | 195 | # The runtime the pod will be built for. 196 | # Options are: podman or executable 197 | # Podman runs a container pod using podman. 198 | # Executable is an executable file which inplements the API interface for pod interactions on command line. 199 | # Default is podman. 200 | runtime: podman 201 | 202 | # The current version of this pod. This is used by the Simplenetes cluster project manager `(sns)` to distinguish different version of the same pod from eachother. 203 | # It is also suffixed to the pod name, so that pod instances of different version will not collide. 204 | # Must be on semver format: major.minor.patch[-tag] 205 | # This variable is also automatically made available by podc in the preproceesing stage. It cannot be defined in the .env file. 206 | podVersion: 0.0.1 207 | 208 | # Define any pod labels 209 | labels: 210 | - name: labelname 211 | value: labelvalue 212 | 213 | # Define the volumes which this pod can use. 214 | volumes: 215 | # Ramdisk volumes can be created 216 | - name: tmpstorage 217 | type: ramdisk 218 | size: 10M 219 | 220 | # Config volumes are host directory binded volumes, mounting the corresponding directory in `./config`. 221 | # This is good to use for configurations, but not for secrets (and really not for content either since they should not be too large in size). 222 | # When the files have changed on disk, the containers(s) mounting this volume will be automatically signalled/restarted. 223 | # They can also be manually signalled by `pod reload-configs config1 config2 etc`. This will signal each container that have signal defined and is currently running. 224 | # If the container is not running so it can receive the signal, then the restart-policy decides if a config update restarts the container (on-config and on-interval does that). 225 | # See more about the signalling further down in this document. 226 | - name: config1 227 | type: config 228 | 229 | # This config is special since it has defined it is "encrypted", this is how Simplenetes manages secrets. 230 | # What happens behind the scenes is that there is expected to be a config on disk named "my-secret", which will 231 | # be mounted into a new automatically configured container which role is is decrypt the configurations and store 232 | # the result in a automatically created ramdisk. 233 | # Any user container mounting this specific config will actually be mounting that ramdisk instead, where it can find the unencrypted secret. 234 | # A new "decrypter container" runs a specific image which responsibility it is to 235 | # fetch the decryption key for the secret from a vault and decrypt the config and store in on the ramdisk. 236 | # The user container mounting this secret ramdisk will get indirectly signalled when the underlaying config is updated, because it 237 | # will get signalled by the "decrypter container" as it becomes ready (if the user container has a signal defined) so it can re-read the unencrypted secret, or it will get restarted if it has the restart 238 | # policy on-config or on-interval. 239 | # So, in practice a user container is signalled/retarted the same way for an encrypted config (secret) as for when mounting a regular config. 240 | # See more about the signalling further down in this document. 241 | # NOTE: encrypted secret NOT YET IMPLEMENTED. 242 | - name: my-secret 243 | type: config 244 | encrypted: true 245 | 246 | # A podman managed volume. 247 | # These volumes persist over pod lifetimes and versions, but are bound to the host on which they were created. 248 | # shared defines if volumes are not shared and just for the specific pod version, or if shared between pod versions or shared for all pods on the host referencing the volume. 249 | # If shared = "no" (which is the default) then the volume name is suffixed with podname and podversion, to make it unique for the specific pod and its version. 250 | # If shared = "pod" then only the volume name is suffixed with the podname, to make it unique for the specific pod but shared for all its versions. 251 | # If shared = "host" then nothing is suffixed to the volume name making it shared for all pods on the host referencing it. 252 | - name: log 253 | type: volume 254 | shared: no (default) | pod | host 255 | 256 | # Mount directory or device on host. 257 | # If the bind given is relative then it will be made absolute, based on the current working directory ($PWD). 258 | # Using relative bind paths are useful for dev mode workflows when mounting local build directories. 259 | # The basedir can be overridden by compiling with the -d option. 260 | - name: extradisk 261 | type: host 262 | bind: /dev/sda1 263 | 264 | containers: 265 | # Each container has name, which will be suffixed by the podname and the version. 266 | - name: hello 267 | 268 | # Image, podman will try a few different registries by default. 269 | # Here ${podVersion} will be substituted in the preprocessing stage with the value defined above. 270 | # This is useful for keeping the image automatically in sync with the pod version. 271 | image: my-site:${podVersion} 272 | 273 | # entrypoint to the container can be changed. 274 | # Default is the Docker Image ENTRYPOINT. 275 | # Each argument as a separate list item 276 | command: 277 | - /usr/sbin/nginx 278 | - -c 279 | - /etc/nginx.conf 280 | 281 | # Arguments to the entrypoint command can be set, if the command only provides the binary. 282 | # Default is the docker Image CMD. 283 | # Each argument as a separate list item 284 | args: 285 | - -c 286 | - /etc/nginx.conf 287 | 288 | # Set the initial working directory of the container. 289 | # Defaults to the Docker image WORKDIR. 290 | workingDir: /opt/files 291 | 292 | # Define environment variables which will be accessible to the container. 293 | env: 294 | - name: WARP_SPEED 295 | value: activated 296 | 297 | # Restart policy for when a container exits. See above for options. 298 | # always - no matter exit code restart container) 299 | # on-config - restart when i) config files on disk have changed, ii) when container exited with error code or iii) when in exited state and signalled by another container or manually by user 300 | # on-interval:x - as on-config, but also restart on successful exists after x seconds has passed 301 | # on-failure - restart container when exit code is not equal to 0 302 | # never - never restart container, unless manually restarted by user using "pod rerun " 303 | restart: on-interval:10 304 | 305 | # Define how this container is signalled, optional. 306 | # A container can get get 1) signalled by other containers when they started and become ready 2) signalled on configuration changes or 3) manually from cmd line. 307 | # 1) A container which defines `startupProbe/signal` and a list of `- name: container` will when ready signal all those containers which are defined. 308 | # 2) If a container mounts a config and the config changes on disk or a `pod reload-configs ` command is issued for that specific config it will get signalled. 309 | # 3) By calling the pod executable `./pod signal [container1 container2 etc]` 310 | # When a container is signalled the main process of the container will get the signal. If the container exists but is has exited then it will be restarted upon signalling, 311 | # but only if it has the `on-config` or `on-interval` restart policy set. 312 | signal: 313 | # Define a signal to send the main process 314 | - sig: HUP 315 | # Or, define a command to be run in the container. 316 | # Each argument as a separate list item 317 | - cmd: 318 | - /usr/sbin/nginx 319 | - -s 320 | - reload 321 | 322 | # Define mount points for volumes create above. 323 | # As for now all volumes are mounted as shared, read-write and allowing devices to be mounted (:z,rw,dev), except for 324 | # mounted secrets which are read-only. 325 | mounts: 326 | # dest in the directory in the container 327 | - dest: /mnt/tmpstor 328 | volume: tmpstorage 329 | 330 | # By mounting a config this container can get signalled and possibly restarted (depending on restart-policy) when a configuration is updated. 331 | # This can manually be achieved by running "pod reload-configs conf1 [conf2]". 332 | # In a Simplenetes cluster environment simplenetesd takes care of detecting changes in configurations and calling the pods to reload their configs when that happens. 333 | - dest: /mnt/config1 334 | volume: config1 335 | 336 | # By mounting a config which is defined as `encrypted: true` under `/volumes` this container actually mounts 337 | # a ramdisk instead which is to have the unencrypted secret inside of it. 338 | # A new "decrypter" container is inserted into the mix and it will be then one mounting the config and being signalled when it is updated. The decrypter runs and decrypts the secret, stores it 339 | # on the ramdisk and then upon its exit it will signal the user defined container which is mounting the secret config. 340 | # So, even though this container does not directly mount the config is will still get signalled or restarted when the underlaying config is updated (as long as it has the `on-config` or `on-interval` restart policy set. 341 | # This is because the decrypter container will get restarted/signalled when the underlaying config changes and then eventually when it exits it will signal/restart this container. 342 | - dest: /mnt/my-secret 343 | volume: my-secret 344 | 345 | # Startup probe of the container. 346 | # The pod startup process will not continue until a container is started up. 347 | # Note that a successful startup could mean that the container started and then exited with code 0. 348 | # If there is no startup probe then the startup is counted as succeesful as soon as the container has been started. 349 | # If a container fails to startup later it will be destroyed and restarted according to its restart policy. 350 | startupProbe: 351 | # Wait max 60 seconds for the container to be ready. Default is 120. 352 | # The pod daemon process sleeps 1 second between invoking the command and will abort the startup after timeout is reached. 353 | timeout: 60 354 | 355 | # Set to true to wait for the container to exit with code 0. When the container has exited is is treated as started and ready. 356 | # Any other exit code means that the startup process failed. 357 | # The exit probe is exclusively for the startupProbe (not available for readiness/liveness probes) 358 | exit: false 359 | 360 | # Or, define a command to be run inside the container to determine when the container has started up properly. 361 | # Each argument as a separate list item 362 | cmd: 363 | - sh 364 | - -c 365 | - '[ -e "/tmp/$USER/ready" ] || [ -e "/home/$USER/ready" ]' 366 | 367 | # Note that `exit and `cmd` are mutually exclusive to each other. 368 | # If none of them are defined then the container is treated as successfully started as soon as the container is started. 369 | # If wanting to run HTTP GET or TCP socket connection tests to determine the startup state, curl/wget/netcat needs to be run inside the container. 370 | 371 | # Defined below `signal` we find other containers the pod daemon shall signal when this container has started up successfully. 372 | # Only containers which are running will get signalled, stopped containers will get restarted if they have the `on-config` or `on-interval` restart policy. 373 | # When a Pod is starting up fresh then only containers defined above this container can possibly be running/existing and therefore only those can get signalled. 374 | # However, if the container is restarted then all containers defined under `signal` will get signalled (regardless the definition order) if the startup was successful. 375 | # Any running containers to be signalled must have defined the `signal` property, otherwise the signalling targeted at them is ignored, 376 | # however exited containers who are signalled and have appropiate restart policies will get restarted. 377 | signal: 378 | - container: hello 379 | - container: secret 380 | 381 | # Check to determine if the container is ready to recieve traffic. 382 | # This check is automatically run during the whole pod lifecycle. 383 | # In this example we do a HTTP GET request to probe if the container is ready to receive traffic. 384 | # This command is run inside the container, and in this case it would require that `curl` is installed inside the container. `wget` or `netcat` can also be used, since any command could be run as long as 385 | # it is installed in the container. 386 | readinessProbe: 387 | timeout: 6 388 | cmd: 389 | - sh 390 | - c 391 | - 'code=$(curl -Lso /dev/null http://127.0.0.1:8080/healthz -H "Host: example.org" -w "%{http_code}") && [ "${code}" -ge 200 ] && [ "${code}" -lt 400 ]' 392 | 393 | # Check to determine the health of the container. 394 | # This check is automatically run during the whole pod lifecycle, but only after the startupProbe (if any) has finished. 395 | # If a check fails then the container will be stopped. Depending on it's restart-policy it might get restarted. 396 | # This has the same syntax as the "readinessProbe". 397 | # Here we will show an example using `wget`. `curl` is more precise in checking, but often only `busybox wget` is available in containers. 398 | livenessProbe: 399 | timeout: 6 400 | cmd: 401 | - sh 402 | - c 403 | - "wget -O /dev/null http://127.0.0.1:8080/healthz" 404 | 405 | # Optionally set the network of the container to be part of the host network. 406 | # Default (blank) means that the container connects to the pod network and can only be reched if it exposes ports (below). 407 | # The IngressPod for example needs to bind to the host network so that the client IP is the actual IP and not an internal IP caused by port forwarding 408 | # when forwarding traffic into the container using the "expose" configuration. 409 | # A container which connects to the host network cannot have an "expose" configuration. The process in the container will bind ports directly on the host network. 410 | network: host 411 | 412 | # Expose ports on the containers and possible create Ingress configuration to proxy traffic. 413 | expose: 414 | ## This first configuration is simply to expose a container port on the host on a specific host port. 415 | # targetPort is the port which the process inside the container is listening on. 416 | # Required if defining a hostPort. 417 | # Range is 1 to 65535. 418 | - targetPort: 80 419 | 420 | # hostPort is the what port of the node host machine we bind the container part to. 421 | # Required if using targetPort, 422 | # however in the context of a Simplenetes cluster project the hostPort can be assigned as `${HOSTPORTAUTOx}` and a unique host port will be assigned. The `x` is an integer meaning that if `${HOSTPORTAUTO1}` is used in two places in the yaml the same host port value will be substituted in. In standalone mode `${HOSTPORTAUTO1}` must then be defined in the `pod.env` file. 423 | # Host port must be between 1 and 65535 (but typically not between 61000-63999 nor 32767 (reserved for proxy)). 424 | # Note that the for each pod compile free host ports are scouted to fill the AUTO variables. The same host ports will not be set for AUTO variables in two different compilations even if the `x` is the same. 425 | hostPort: 8081 426 | 427 | # Optionally force what interface to bind mapped ports to. 428 | # Setting to "0.0.0.0" is typically required for pods which are receiving traffic from the public internet, such as the ingress. 429 | # However, for pods which are not to be publically exposed we should definetly not set it to "0.0.0.0". 430 | # If hostInterface is set it then overrides the "--host-interface" option which could be passed to the pod at creation time. 431 | # If no "--host-interface" option is passed on cmd line nor the "hostInterface" is set then podman defaults to "0.0.0.0". 432 | # When running pods in a Simplenetes cluster the simplenetesd passes the host local IP address to the pod using "--host-interface". 433 | # Most often do not set this value, except for the ingress pod which needs to have it set to "0.0.0.0". 434 | hostInterface: 0.0.0.0 435 | 436 | # Maximum connection count which the proxy will direct to this targetPort 437 | # Only relevant when using a clusterPort (within a Simplenetes cluster). 438 | # Default is 4096 439 | maxConn: 1024 440 | 441 | # Set to true to have the proxy connect using the PROXY-PROTOCOL 442 | # Only relevant when using a clusterPort so that the traffic is incoming via the cluster proxy mesh. 443 | # Default is false. 444 | sendProxy: true 445 | 446 | ## Second configuration shows how to map a clusterPort to the expose and also to configure Ingress details. 447 | - targetPort: 80 448 | # Generally we want Simplenetes to find free host ports for us when compiling, so we use the AUTO variable. 449 | hostPort: ${HOSTPORTAUTO1} 450 | 451 | # clusterPort is a TCP port in a given range which is cluster wide. 452 | # Cluster port must be between 1024 and 65535, but not between 30000-32767. 453 | # Anywhere in the cluster a pod can connect to this targetPort by connecting to this clusterPort on its local IP, as long a a `proxy pod` is running on the host. 454 | # Optional property, but required when wanting to route traffic within the cluster to the targetPort, either from other Pods or from the Ingress. 455 | # Only relevant when using pods in a cluster orchestrated by Simplenetes. 456 | # In the context of a sns cluster clusterPort can be assigned as `${CLUSTERPORTAUTOx}` and a unique cluster port will be assigned. The `x` is an integer meaning that if `${CLUSTERPORTAUTO1}` is used in two places in the yaml the same cluster port value will be substituted in. 457 | # Note that the for each pod compile free cluster ports are scouted to fill the AUTO variables. The same cluster ports will not be set for AUTO variables in two different compilations even if the `x` is the same. 458 | # If many pods and/or pod version use the same clusterPort they will all share incoming traffic. 459 | # Generally we want Simplenetes to find free cluster ports for us when compiling, so we use the AUTO variable. 460 | clusterPort: ${CLUSTERPORTAUTO1} 461 | 462 | # Define Ingress properties for the clusterPort. 463 | # Only relevant when using pods in a cluster orchestrated by Simplenetes. 464 | ingress: 465 | # This first ingress configuration example does not route traffic to any backend, it only redirects traffic up in the Ingress layer, 466 | # therefore it does not strictly require targetPort, hostPort and clusterPort. 467 | # Protocol must be set to http/https/tcp 468 | - protocol: http 469 | 470 | # default bind for http is 80, we can change that by defining the bind property. 471 | bind: 81 472 | 473 | # For for all protocol we can define domains, for tcp that is SNI checked. 474 | domain: abc.com def.com aaa.com *.yeees.com 475 | 476 | ## All redirections and errorfile below are exclusive to each other. 477 | 478 | # Redirect traffic to https, this does not require a backend answering any connections. It becomes purely a haproxy configuration. 479 | redirectToHttps: true 480 | 481 | # Redirect all traffic to another location. 482 | redirectLocation: https://domain/path 483 | 484 | # Serve an errorfile from HAProxy. 485 | # The format is HTTP_ERROR_CODE FILEPATH. 486 | # This can be used as a fallback with a low weight to become active when another pod with same ingress rules 487 | # is taken out of rotation and we want to display a nice "maintenance page". 488 | errorfile: 500 /mnt/errorfiles/500.http 489 | 490 | # Redirect to a different domain prefix. 491 | # Could be used to add/strip a www prefix to the domain. 492 | redirectPrefix: https://www.domain 493 | 494 | # Second ingress configuration does route traffic to targetPort and therefore requires targetPort, hostPort and clusterPort to be set. 495 | # https will terminate TLS in the Ingress for the domains provided. 496 | - protocol: https 497 | domain: abc.com 498 | 499 | # Match on path beginnings. 500 | pathBeg: / /static/ 501 | 502 | # Match on path endings, can be used together with pathBeg. 503 | pathEnd: .jpg .gif 504 | 505 | # Match on full path, not include query parameters. Exlusive to pathBeg and pathEnd. 506 | path: /admin/ /superuser/ 507 | 508 | # A heavier weight makes this match earlier in the Ingress. Default is 100. 509 | weight: 101 510 | - protocol: tcp 511 | 512 | # bind is mandatory for general TCP 513 | bind: 4433 514 | 515 | # HAProxy matches this on SNI. 516 | domain: aaa.com bbb.com 517 | ``` 518 | 519 | ### Natively executable Pod Spec 520 | Treat a single executable as a Pod. 521 | It is the coders responsibility that the executable implements the Pod API in the right way. 522 | 523 | ```yaml 524 | # For the podcompiler to know what the yaml structure is supposed to look like 525 | api: 1.0.0-beta2 526 | 527 | # Manage a single executable 528 | runtime: executable 529 | 530 | # The current version of this pod. This is used by Simplenetes project management to distinguish different version of the same pod from eachother. 531 | podVersion: 0.0.1 532 | 533 | executable: 534 | # The path to the executable, relative to the the pod.yaml file. 535 | # This file will be copied and named "pod". 536 | # The only requirement for this executable is that it implements the Pod API interface. 537 | # Note that the reason to not name the file "pod" straight away but copying it into place 538 | # is that we then can use preprocessing to choose between different executable depending 539 | # on target OS, etc. 540 | file: bin/proxy 541 | 542 | # An executable cannot have an "expose" section since it is on the host network. 543 | ``` 544 | ## Pod API 545 | Compiled pod scripts and other executables who implement the Pod API can be used as pods. 546 | 547 | The API is: 548 | ```sh 549 | ./pod action [arguments] 550 | ``` 551 | 552 | Where `action` `[arguments]` are one of the following: 553 | 554 | ```sh 555 | ./pod help 556 | # return 0 557 | # stdout: help text 558 | ``` 559 | Will show general usage for the pod. 560 | 561 | ```sh 562 | ./pod version 563 | # return 0 564 | # stdout: version data 565 | ``` 566 | Will show version information as: `runtime: podman 0.1\npodVersion: version\n`. Where runtime is `podman`, `executable`, etc and followed the runtime impl. version. 567 | `podVersion` is the version as depicted by the `pod.yaml`. 568 | 569 | "Podman" is the regular Pod runtime, "executable" can be any type of executable, script or binary. As example the Simplenetes Proxy uses an "executable" runtime. 570 | `version` is the version number of the runtime. 571 | 572 | ```sh 573 | ./pod info 574 | # return 0 on success 575 | # stdout: info data 576 | # return 1 on error 577 | ``` 578 | Will show configuration and setup for the pod, not current status. 579 | 580 | ```sh 581 | ./pod ps 582 | # return 0 583 | # stdout: status data 584 | ``` 585 | Will show up to date runtime status about the pod and containers. 586 | 587 | ```sh 588 | ./pod download [-f|--force] 589 | # return 0 on success 590 | # return 1 on error 591 | ``` 592 | For a container pod this means to pull all images. 593 | If -f option is set then always pull for updated images, even if they already exist locally. 594 | 595 | For an executable pod it could mean to download and install packages needed to run the service. 596 | 597 | ```sh 598 | ./pod create [--host-interface=] 599 | # return 0 on success 600 | # return 1 on error 601 | ``` 602 | For a container pod it will create the pod-container, daemon process and the volumes. 603 | 604 | For an executable pod it might not do anything. 605 | 606 | This is an idempotent command. 607 | 608 | ```sh 609 | ./pod start 610 | # return 0 on success 611 | # return 1 on error 612 | ``` 613 | For a container pod it will start the pod, as long as it has been created first and is not running. 614 | 615 | For an executable pod it will start the process, as long as it is not already started. 616 | 617 | ```sh 618 | ./pod stop 619 | # return 0 on success 620 | # return 1 on error 621 | ``` 622 | For a container pod it will stop the pod and all containers. 623 | 624 | For an executable pod it will gracefully stop the process. 625 | 626 | ```sh 627 | ./pod kill 628 | # return 0 on success 629 | # return 1 on error 630 | ``` 631 | For a container pod it will kill the pod and all containers. 632 | 633 | For an executable pod it will forcefully kill the process. 634 | 635 | ```sh 636 | ./pod run [--host-interface=] 637 | # return 0 on success 638 | # return 1 on error 639 | ``` 640 | For container pods this command makes sure all containers are in the running state. It creates and starts the pod and containers if necessary. 641 | 642 | For executables they need to understand if their process is already running and not start another one else start the process and keep the PID somewhere. 643 | 644 | ```sh 645 | ./pod rerun [--host-interface=] [-k|--kill] [container1 container2 etc] 646 | # return 0 on success 647 | # return 1 on error 648 | ``` 649 | For container pods, first stop, then remove and then restart all containers. 650 | Same effect as issuing rm and run in sequence. 651 | If container name(s) are provided then only cycle the containers, not the full pod. 652 | If -k flag is set then pod will be killed instead of stopped (not valid when defining individual containers). 653 | 654 | For executables, stop the current process and start a new one. 655 | 656 | ```sh 657 | ./pod signal [container1 container2 etc] 658 | # return 0 on success 659 | # return 1 on error 660 | ``` 661 | Send a signal to one, many or all containers. 662 | The signal sent is the SIG defined in the containers YAML specification. 663 | Invoking without arguments will invoke signals all all containers which have a SIG defined. 664 | 665 | For executables, signal the process. 666 | 667 | ```sh 668 | ./pod create-volumes 669 | # return 0 on success 670 | # return 1 on error 671 | ``` 672 | For a containers pod create all volumes used by this pod. 673 | 674 | For an executable pod it can mean something similiar such as provisioning storage space. 675 | 676 | ```sh 677 | ./pod reload-configs config1 [config2 config3 etc] 678 | When a "config" has been updated on disk, this command is automatically invoked to signal the container who mount the specific config(s). 679 | It can also be manually run from command line to trigger a config reload. 680 | Each container mounting the config will be signalled as defined in the YAML specification. 681 | # return 0 if successful 682 | # return 1 if pod does not exist or if pod is not running. 683 | ``` 684 | For containers pods this means that containers who mount the configs will be notified about changes. 685 | 686 | For executable pods it becomes implementation specific what it means. 687 | 688 | ```sh 689 | ./pod rm [-k|--kill] 690 | # return 0 if successful 691 | ``` 692 | For container pods stop and destroy all pods, leave volumes intact. 693 | If the pod and containers are running they will be stopped first and then removed. 694 | If -k flag is set then containers will be killed instead of stopped. 695 | 696 | For executable pods clean up any lingering files which are temporary. 697 | 698 | ```sh 699 | ./pod purge 700 | # return 0 if successful 701 | ``` 702 | For container pods which are non existing, remove all volumes associated. 703 | 704 | For executable pods remove all traces of activity, such as log files. 705 | 706 | 707 | ```sh 708 | ./pod shell -c container|--container= [-b|--bash] [-- commands] 709 | # return 0 if successful 710 | ``` 711 | 712 | Open interactive shell inside container. If -b option is provided force bash shell. 713 | If commands are provided run commands instead of opening interactive shell. 714 | 715 | 716 | ```sh 717 | ./pod create-ramdisks [-l|--list] [-d|--delete] 718 | If run as sudo/root create the ramdisks used by this pod. 719 | If -d flag is set then delete existing ramdisks, requires sudo/root. 720 | If -l flag is provided list ramdisks configuration (used by external tools to provide the ramdisks, for example the Simpleneted Daemon `simplenetesd`). 721 | If ramdisks are not prepared prior to the pod starting up then the pod will it self 722 | create regular directories (fake ramdisks) instead of real ramdisks. This is a fallback 723 | strategy in the case sudo/root priviligies are not available or if just running in dev mode. 724 | For applications where the security of ramdisks are important then ramdisks should be properly created. 725 | # return 0 726 | # stdout: disk1:10M 727 | disk2:2M 728 | etc 729 | ``` 730 | Output the ramdisks configuration for the pod. 731 | A newline separated list of tuples describing to the Daemon what ramdisks it is expected to create: [name:size]. 732 | Ex: "tmp1:10M\ntmp2:5M" 733 | 734 | Note that if ramdisks have not been created prior to starting the pod, they pod is expected to gracefully handle this by creating regular directories which is can use instead of provided ramdisks. 735 | 736 | ```sh 737 | ./pod logs [containers] [-p|--daemon-process] [-t timestamp|--timestamp=] [-l limit|--limit=] [-s streams|--stream=] [-d details|--details=] 738 | Output logs for one, many or all [containers]. If none given then show for all. 739 | -p Show pod daemon process logs (can also be used in combination with [containers]) 740 | -t timestamp=UNIX timestamp to get logs from, defaults to 0 741 | If negative value is given it is seconds relative to now (now-ts). 742 | -s streams=[stdout|stderr|stdout,stderr], defaults to \"stdout,stderr\". 743 | -l limit=nr of lines to get in total from the top, negative gets from the bottom (latest). 744 | -d details=[ts|name|stream|none], comma separated if many. 745 | if \"ts\" set will show the UNIX timestamp for each row. 746 | if \"age\" set will show age as seconds for each row. 747 | if \"name\" is set will show the container name for each row. 748 | if \"stream\" is set will show the std stream the logs came on. 749 | To not show any details set to \"none\". 750 | Defaults to \"ts,name\". 751 | # return 0 752 | # stdout: logs 753 | ``` 754 | -------------------------------------------------------------------------------- /Spacefile.bash: -------------------------------------------------------------------------------- 1 | PODC_CMDLINE() 2 | { 3 | SPACE_SIGNATURE="[action args]" 4 | SPACE_DEP="USAGE _GETOPTS COMPILE_ENTRY SHOW_VERSION" 5 | 6 | local _out_arguments="" 7 | local _out_h= 8 | local _out_V= 9 | local _out_p="true" 10 | 11 | local _out_e= 12 | local _out_f="" 13 | local _out_o="" 14 | local _out_d="" 15 | 16 | if ! _GETOPTS "_out_e=-e,--source-env/ _out_h=-h,--help/ _out_V=-V,--version/ _out_f=-f,--file/* _out_o=-o,--outfile/* _out_d=-d,--dir/* _out_p=-p,--preprocess/true|false" 0 1 "$@"; then 17 | printf "Usage: pod [podname] [-f|--file=] [-o|--outfile=] [-d|--dir=] [-p|--preprocess=true|false]\\n" >&2 18 | return 1 19 | fi 20 | 21 | if [ -n "${_out_h}" ]; then 22 | USAGE 23 | return 24 | fi 25 | 26 | if [ -n "${_out_V}" ]; then 27 | SHOW_VERSION 28 | return 29 | fi 30 | 31 | COMPILE_ENTRY "${_out_arguments}" "${_out_f}" "${_out_o}" "${_out_d}" "${_out_p}" "${_out_e:+true}" 32 | } 33 | 34 | SHOW_VERSION() 35 | { 36 | SPACE_ENV="API_VERSION VERSION" 37 | 38 | printf "podc %s\\napi %s\\n" "${VERSION}" "${API_VERSION}" 39 | } 40 | 41 | # options are on the format: 42 | # "_out_all=-a,--all/ _out_state=-s,--state/arg1|arg2|arg3" 43 | # For non argument options the variable will be increased by 1 for each occurrence. 44 | # The variable _out_arguments is reserved for positional arguments. 45 | # Expects _out_arguments and all _out_* to be defined. 46 | _GETOPTS() 47 | { 48 | SPACE_SIGNATURE="options minPositional maxPositional [args]" 49 | SPACE_DEP="_GETOPTS_SWITCH PRINT STRING_SUBSTR STRING_INDEXOF" 50 | 51 | local options="${1}" 52 | shift 53 | 54 | local minPositional="${1:-0}" 55 | shift 56 | 57 | local maxPositional="${1:-0}" 58 | shift 59 | 60 | _out_arguments="" 61 | local posCount="0" 62 | local skipOptions="false" 63 | while [ "$#" -gt 0 ]; do 64 | local option= 65 | local value= 66 | local _out_VARNAME= 67 | local _out_ARGUMENTS= 68 | 69 | if [ "${skipOptions}" = "false" ] && [ "${1}" = "--" ]; then 70 | skipOptions="true" 71 | shift 72 | continue 73 | fi 74 | 75 | if [ "${skipOptions}" = "false" ] && [ "${1#--}" != "${1}" ]; then # Check if it is a double dash GNU option 76 | local l= 77 | STRING_INDEXOF "=" "${1}" "l" 78 | if [ "$?" -eq 0 ]; then 79 | STRING_SUBSTR "${1}" 0 "${l}" "option" 80 | STRING_SUBSTR "${1}" "$((l+1))" "" "value" 81 | else 82 | option="${1}" 83 | fi 84 | shift 85 | # Fill _out_VARNAME and _out_ARGUMENTS 86 | _GETOPTS_SWITCH "${options}" "${option}" 87 | # Fall through to handle option 88 | elif [ "${skipOptions}" = "false" ] && [ "${1#-}" != "${1}" ] && [ "${#1}" -gt 1 ]; then # Check single dash OG-style option 89 | option="${1}" 90 | shift 91 | if [ "${#option}" -gt 2 ]; then 92 | PRINT "Invalid option '${option}'" "error" 0 93 | return 1 94 | fi 95 | # Fill _out_VARNAME and _out_ARGUMENTS 96 | _GETOPTS_SWITCH "${options}" "${option}" 97 | # Do we expect a value to the option? If so take it and shift it out 98 | if [ -n "${_out_ARGUMENTS}" ]; then 99 | if [ "$#" -gt 0 ]; then 100 | value="${1}" 101 | shift 102 | fi 103 | fi 104 | # Fall through to handle option 105 | else 106 | # Positional args 107 | posCount="$((posCount+1))" 108 | if [ "${posCount}" -gt "${maxPositional}" ]; then 109 | PRINT "Too many arguments. Max ${maxPositional} argument(s) allowed." "error" 0 110 | return 1 111 | fi 112 | _out_arguments="${_out_arguments}${_out_arguments:+ }${1}" 113 | shift 114 | continue 115 | fi 116 | 117 | # Handle option argument 118 | if [ -z "${_out_VARNAME}" ]; then 119 | PRINT "Unrecognized option: '${option}'" "error" 0 120 | return 1 121 | fi 122 | 123 | if [ -n "${_out_ARGUMENTS}" ] && [ -z "${value}" ]; then 124 | # If we are expecting a option arguments but none was provided. 125 | STRING_SUBST "_out_ARGUMENTS" " " ", " 1 126 | PRINT "Option ${option} is expecting an argument like: ${_out_ARGUMENTS}" "error" 0 127 | return 1 128 | elif [ -z "${_out_ARGUMENTS}" ] && [ -z "${value}" ]; then 129 | # This was a simple option without argument, increase counter of occurrences 130 | eval "value=\"\$${_out_VARNAME}\"" 131 | if [ -z "${value}" ]; then 132 | value=0 133 | fi 134 | value="$((value+1))" 135 | elif [ "${_out_ARGUMENTS}" = "*" ] || STRING_ITEM_INDEXOF "${_out_ARGUMENTS}" "${value}"; then 136 | # Value is OK, fall through 137 | : 138 | else 139 | # Invalid argument 140 | if [ -z "${_out_ARGUMENTS}" ]; then 141 | PRINT "Option ${option} does not take any arguments" "error" 0 142 | else 143 | PRINT "Invalid ${option} argument '${value}'. Valid arguments are: ${_out_ARGUMENTS}" "error" 0 144 | fi 145 | return 1 146 | fi 147 | 148 | # Store arguments in variable 149 | eval "${_out_VARNAME}=\"\${value}\"" 150 | done 151 | 152 | if [ "${posCount}" -lt "${minPositional}" ]; then 153 | PRINT "Too few arguments provided. Minimum ${minPositional} argument(s) required." "error" 0 154 | return 1 155 | fi 156 | } 157 | 158 | # Find a match in options and fill _out_VARNAME and _out_ARGUMENTS 159 | _GETOPTS_SWITCH() 160 | { 161 | SPACE_SIGNATURE="options option" 162 | SPACE_DEP="STRING_SUBST STRING_ITEM_INDEXOF STRING_ITEM_GET STRING_ITEM_COUNT" 163 | 164 | local options="${1}" 165 | shift 166 | 167 | local option="${1}" 168 | shift 169 | 170 | local varname= 171 | local arguments= 172 | 173 | local count=0 174 | local index=0 175 | STRING_ITEM_COUNT "${options}" "count" 176 | while [ "${index}" -lt "${count}" ]; do 177 | local item= 178 | STRING_ITEM_GET "${options}" ${index} "item" 179 | varname="${item%%=*}" 180 | arguments="${item#*/}" 181 | local allSwitches="${item#*=}" 182 | allSwitches="${allSwitches%%/*}" 183 | STRING_SUBST "allSwitches" "," " " 1 184 | if STRING_ITEM_INDEXOF "${allSwitches}" "${option}"; then 185 | STRING_SUBST "arguments" "|" " " 1 186 | _out_VARNAME="${varname}" 187 | _out_ARGUMENTS="${arguments}" 188 | return 0 189 | fi 190 | index=$((index+1)) 191 | done 192 | 193 | # No such option found 194 | return 1 195 | } 196 | 197 | 198 | USAGE() 199 | { 200 | printf "%s\\n" "Usage: 201 | 202 | podc -h | --help 203 | Output this help 204 | 205 | podc -V | --version 206 | Output version 207 | 208 | podc [podname] [-f infile] [-o outfile] [-d srcdir] [-p] 209 | 210 | podname 211 | The name of the pod, default is to take the directory name, 212 | but which might not be a valid name. Only a-z0-9 and underscore allowed. 213 | Must start with a letter. 214 | 215 | -f | --file=infile 216 | Optional path to the pod.yaml file. 217 | Default is to look for pod.yaml in the current directory. 218 | 219 | -o | --outfile=path 220 | Optional path where to write the executable pod file. 221 | Default is \"pod\" in the same directory as the pod yaml file. 222 | 223 | -p | --preprocess=true | false (default true) 224 | Optional flag do perform preprocessing on the pod.yaml file or not. 225 | 226 | -e | --source-env 227 | Set to allow sourcing variables from the environment if they are not provided in the .env file. 228 | 229 | -d srcdir 230 | Optional directory path to use as source directory if \"infile\" 231 | is inside another directory. 232 | This feature is used by other tools who do preprocessing 233 | on the original pod.yaml file and place a temporary file elsewhere. 234 | It can also be used to override the home directory of host volumes with relative mount points, 235 | then the srcdir is the basedir, otherwise it is the dir of infile. 236 | 237 | " 238 | } 239 | 240 | COMPILE_ENTRY() 241 | { 242 | SPACE_SIGNATURE="[podName inFile outFile srcDir doPreprocessing allowSourceEnv]" 243 | SPACE_DEP="_COMPILE_POD PRINT TEXT_EXTRACT_VARIABLES TEXT_VARIABLE_SUBST TEXT_FILTER TEXT_GET_ENV FILE_REALPATH" 244 | 245 | local podName="${1:-}" 246 | shift $(($# > 0 ? 1 : 0)) 247 | 248 | local inFile="${1:-}" 249 | shift $(($# > 0 ? 1 : 0)) 250 | 251 | local outFile="${1:-}" 252 | shift $(($# > 0 ? 1 : 0)) 253 | 254 | local srcDir="${1:-}" 255 | shift $(($# > 0 ? 1 : 0)) 256 | 257 | local doPreprocessing="${1:-true}" 258 | shift $(($# > 0 ? 1 : 0)) 259 | 260 | local allowSourceEnv="${1:-}" 261 | shift $(($# > 0 ? 1 : 0)) 262 | 263 | if [ -z "${podName}" ]; then 264 | podName="${PWD##*/}" 265 | #podName="$(printf "%s\\n" "${podName}" |tr '[:upper:]' '[:lower:]')" 266 | PRINT "No pod name given as first argument, assuming: ${podName}" "info" 0 267 | fi 268 | 269 | if [ -z "${inFile}" ]; then 270 | inFile="pod.yaml" 271 | PRINT "No in file given as second argument, assuming: ${inFile}" "info" 0 272 | fi 273 | 274 | inFile="$(FILE_REALPATH "${inFile}")" 275 | 276 | if [ ! -f "${inFile}" ]; then 277 | PRINT "Yaml file does not exist." "error" 0 278 | printf "Usage: pod [podname] [-f infile] [-o outfile] [-d srcdir] [-p true|false]\\n" >&2 279 | return 1 280 | fi 281 | 282 | if [ -z "${outFile}" ]; then 283 | outFile="${inFile%.yaml}" 284 | fi 285 | if [ "${inFile}" = "${outFile}" ]; then 286 | outFile="${inFile}.out" 287 | fi 288 | local buildDir="${inFile%/*}" 289 | 290 | local filename="${inFile##*/}" 291 | local yamlfile= 292 | if [ "${doPreprocessing}" = "true" ]; then 293 | yamlfile="${buildDir}/.${filename}" 294 | local text="$(cat "${inFile}")" 295 | local variablestosubst="$(TEXT_EXTRACT_VARIABLES "${text}")" 296 | # Load .env env file, if any 297 | local envfile="${inFile%.yaml}.env" 298 | # Preinject the podVersion as a value loaded from .env file 299 | local podVersion="$(grep -m1 "^podVersion:" "${inFile}" | awk -F: '{print $2}' | tr -d " \"'")" 300 | local values="podVersion=${podVersion}" 301 | local newline=" 302 | " 303 | if [ -f "${envfile}" ]; then 304 | PRINT "Loading variables from .env file." "info" 0 305 | values="${values}${newline}$(cat "${envfile}")" 306 | else 307 | PRINT "No .env file present." "info" 0 308 | fi 309 | 310 | # Fill in missing variables from environment 311 | local varname= 312 | local varnames="" 313 | for varname in ${variablestosubst}; do 314 | if ! printf "%s\\n" "${values}" | grep -q "^${varname}="; then 315 | varnames="${varnames}${varnames:+ }${varname}" 316 | fi 317 | done 318 | if [ -n "${varnames}" ]; then 319 | if [ "${allowSourceEnv}" = "true" ]; then 320 | PRINT "Sourcing variables from environment since they are not defined in .env file, for variables: ${varnames}" "info" 0 321 | if ! values="${values}${values:+${newline}}$(TEXT_GET_ENV "${varnames}" "1")"; then 322 | PRINT "There are missing variables in .env file or environment." "warning" 0 323 | fi 324 | else 325 | PRINT "There are missing variables in .env file." "warning" 0 326 | fi 327 | fi 328 | 329 | text="$(TEXT_VARIABLE_SUBST "${text}" "${variablestosubst}" "${values}")" 330 | 331 | # Parse contents 332 | printf "%s\\n" "${text}" |TEXT_FILTER >"${yamlfile}" 333 | else 334 | yamlfile="${inFile}" 335 | fi 336 | 337 | if ! _COMPILE_POD "${podName}" "${yamlfile}" "${outFile}" "${srcDir}"; then 338 | return 1 339 | fi 340 | } 341 | 342 | _COMPILE_POD() 343 | { 344 | SPACE_SIGNATURE="podName inFile outFile [srcDir]" 345 | SPACE_DEP="PRINT YAML_PARSE _CONTAINER_VARS _CONTAINER_SET_VAR _QUOTE_ARG STRING_ITEM_INDEXOF STRING_ITEM_GET _GET_CONTAINER_NR _COMPILE_INGRESS _COMPILE_RUN _COMPILE_LABELS _COMPILE_ENTRYPOINT _COMPILE_CPUMEM STRING_SUBST _GET_CONTAINER_VAR _COMPILE_PODMAN _COMPILE_EXECUTABLE FILE_REALPATH _COMPILE_ENV _COMPILE_MOUNTS _COMPILE_STARTUP_PROBE_SIGNAL _COMPILE_STARTUP_PROBE _COMPILE_STARTUP_PROBE_TIMEOUT _COMPILE_READINESS_PROBE _COMPILE_READINESS_PROBE_TIMEOUT _COMPILE_LIVENESS_PROBE _COMPILE_LIVENESS_PROBE_TIMEOUT _COMPILE_SIGNALEXEC _COMPILE_IMAGE _COMPILE_RESTART" 346 | SPACE_ENV="API_VERSION SUPPORTED_API_VERSIONS" 347 | 348 | local podName="${1}" 349 | shift 350 | 351 | if [[ ! $podName =~ ^[a-z]([_a-z0-9]*[a-z0-9])?$ ]]; then 352 | PRINT "Podname '${podName}' is malformed. only lowercase letters [a-z], numbers [0-9] and underscore is allowed. First character must be lowercase letter" "error" 0 353 | return 1 354 | fi 355 | 356 | local inFile="${1}" 357 | shift 358 | 359 | local outFile="${1}" 360 | shift 361 | 362 | if [ -d "${outFile}" ]; then 363 | PRINT "${outFile} is a directory" "error" 0 364 | return 1 365 | fi 366 | 367 | local srcDir="${1}" 368 | shift $(($# > 0 ? 1 : 0)) 369 | 370 | if [ ! -f "${inFile}" ]; then 371 | PRINT "Yaml file does not exist." "error" 0 372 | return 1 373 | fi 374 | 375 | if [ -z "${srcDir}" ]; then 376 | srcDir="${inFile%/*}" 377 | fi 378 | 379 | # Output the fill YAML file if in debug mode 380 | local dbgoutput="$(cat "${inFile}")" 381 | PRINT "Pod YAML: "$'\n'"${dbgoutput}" "debug" 0 382 | 383 | # Load and parse YAML 384 | # Bashism 385 | local evals=() 386 | YAML_PARSE "${inFile}" "evals" 387 | eval "${evals[@]}" 388 | 389 | # get api version 390 | local api= 391 | _copy "api" "/api" 392 | 393 | if ! STRING_ITEM_INDEXOF "${SUPPORTED_API_VERSIONS}" "${api}"; then 394 | PRINT "API version \"${api}\" not supported. \"api:\" must be set to \"${API_VERSION}\"" "error" 0 395 | fi 396 | 397 | # get runtime 398 | local runtime= 399 | _copy "runtime" "/runtime" 400 | if [ -z "${runtime}" ]; then 401 | runtime="podman" 402 | fi 403 | 404 | # get podVersion 405 | local podVersion= 406 | _copy "podVersion" "/podVersion" 407 | if [[ ! $podVersion =~ ^([0-9]+\.[0-9]+\.[0-9]+(-[-a-z0-9\.]*)?)$ ]]; then 408 | PRINT "podVersion is missing/invalid. Must be on semver format (major.minor.patch[-tag])." "error" 0 409 | return 1 410 | fi 411 | 412 | local runtimeDir="" 413 | local runtimeDir="${0%/*}" 414 | if [ "${runtimeDir}" = "${0}" ]; then 415 | # This is weird, not slash in path, but we will handle it. 416 | if [ -f "./${0}" ]; then 417 | # This happens when the script is invoked as `bash pod.sh`. 418 | runtimeDir="${PWD}" 419 | else 420 | PRINT "Could not determine the base dir podc." "error" 0 421 | return 1 422 | fi 423 | fi 424 | runtimeDir="$(FILE_REALPATH "${runtimeDir}")" 425 | 426 | PRINT "Compiling \"${podName}-${podVersion}\" for api version: \"${API_VERSION}\" and runtime \"${runtime}\", YAML file: ${inFile}." "info" 0 427 | 428 | if [ "${runtime}" = "podman" ]; then 429 | # Get path to runtime 430 | local runtimePath="" 431 | while true; do 432 | for runtimePath in "${runtimeDir}/podc-podman-runtime" "${runtimeDir}/lib/podc-podman-runtime" "${runtimeDir}/release/podc-podman-runtime" "/opt/podc/podc-podman-runtime"; do 433 | if [ -f "${runtimePath}" ]; then 434 | break 2 435 | fi 436 | done 437 | PRINT "Could not locate podc-podman-runtime" "error" 0 438 | return 1 439 | done 440 | local buildDir="${outFile%/*}" 441 | local POD_PROXYCONF="" 442 | local POD_INGRESSCONF="" 443 | local _out_pod="" 444 | if ! _COMPILE_PODMAN "${podName}" "${podVersion}" "${runtimePath}" "${buildDir}"; then 445 | PRINT "Could not compile pod for podman runtime." "error" 0 446 | return 1 447 | fi 448 | PRINT "Writing pod executable to ${outFile}" "ok" 0 449 | printf "%s\\n" "${_out_pod}" >"${outFile}" 450 | chmod +x "${outFile}" 451 | local portmappingsFile="${outFile}.portmappings.conf" 452 | 453 | local ingressFile="${outFile}.ingress.conf" 454 | 455 | if [ -n "${POD_PROXYCONF}" ]; then 456 | printf "%s\\n" "${POD_PROXYCONF}" >"${portmappingsFile}" 457 | else 458 | rm -f "${portmappingsFile}" 459 | fi 460 | if [ -n "${POD_INGRESSCONF}" ]; then 461 | printf "%s\\n" "${POD_INGRESSCONF}" >"${ingressFile}" 462 | else 463 | rm -f "${ingressFile}" 464 | fi 465 | elif [ "${runtime}" = "executable" ]; then 466 | if ! _COMPILE_EXECUTABLE "${podName}" "${podVersion}" "${srcDir}" "${outFile}"; then 467 | PRINT "Could not compile pod for executable runtime." "error" 0 468 | return 1 469 | fi 470 | PRINT "Writing pod executable to ${outFile}" "ok" 0 471 | else 472 | PRINT "Unknown runtime \"${runtime}\" . Only \"podman\" or \"executable\" runtimes are supported." "error" 0 473 | return 1 474 | fi 475 | } 476 | 477 | # Inherits YAML varibles 478 | # Compiles for a single executable pod. 479 | _COMPILE_EXECUTABLE() 480 | { 481 | SPACE_SIGNATURE="podName podVersion srcDir outFile" 482 | 483 | local podName="${1}" 484 | shift 485 | 486 | local podVersion="${1}" 487 | shift 488 | 489 | local srcDir="${1}" 490 | shift 491 | 492 | local outFile="${1}" 493 | shift 494 | 495 | local buildDir="${outFile%/*}" 496 | 497 | # Find out what file to copy as the `pod` executable. 498 | local executable= 499 | _copy "executable" "/executable/file" 500 | executable="${srcDir}/${executable}" 501 | executable="$(FILE_REALPATH "${executable}")" 502 | if [ ! -f "${executable}" ]; then 503 | PRINT "The executable file '${executable}' does not exist." "error" 0 504 | return 1 505 | fi 506 | 507 | # Copy the executable 508 | cp "${executable}" "${outFile}" 509 | chmod +x "${outFile}" 510 | } 511 | 512 | # Inherits YAML varibles 513 | # Take the YAML, analyze it and create space shell script which we then use space to export as a standalone sh file. 514 | # Expects: 515 | # POD_PROXYCONF 516 | # POD_INGRESSCONF 517 | # 518 | _COMPILE_PODMAN() 519 | { 520 | local podName="${1}" 521 | shift 522 | 523 | local podVersion="${1}" 524 | shift 525 | 526 | local runtimePath="${1}" 527 | shift 528 | 529 | local buildDir="${1}" 530 | shift 531 | 532 | ## Output header of runtime template file 533 | _out_pod="$(awk 'BEGIN {show=1} /^[^#]/ { show=0 } {if(show==1) {print}}' "${runtimePath}")" 534 | 535 | local POD="${podName}-${podVersion}" 536 | local POD_VERSION="${podVersion}" # Part of the pod name. 537 | local POD_LABELS="" 538 | local POD_CREATE="--name \${POD}" 539 | local POD_VOLUMES="" # Used by runtime to create container volumes. 540 | local POD_RAMDISKS="" # Read by the Daemon to create ramdisks. 541 | local POD_HOSTPORTS="" # Used by runtime to check that hosts ports are free before starting the pod. 542 | local POD_CONTAINER_COUNT=0 543 | 544 | local volumes_host="" 545 | local volumes_config="" 546 | local volumes_config_encrypted="" 547 | local volumes_host_bind="" 548 | 549 | # Go over all volumes 550 | # 551 | 552 | local valid_types="ramdisk config volume host" 553 | local all_volumes="" 554 | local volumes=() 555 | _list "volumes" "/volumes/" 556 | local index= 557 | local volume= 558 | local space="" 559 | local encrypted= 560 | local bind= 561 | for index in "${volumes[@]}"; do 562 | _copy "volume" "/volumes/${index}/name" 563 | STRING_SUBST "volume" "'" "" 1 564 | STRING_SUBST "volume" '"' "" 1 565 | _copy "type" "/volumes/${index}/type" 566 | STRING_SUBST "type" "'" "" 1 567 | STRING_SUBST "type" '"' "" 1 568 | # Check so type is recognized: 569 | if ! STRING_ITEM_INDEXOF "${valid_types}" "${type}"; then 570 | PRINT "Wrong type for volume ${volume}. Must be ramdisk, config, volume or host." "error" 0 571 | return 1 572 | fi 573 | 574 | # If config volume then allow to start with underscore. 575 | if [ "${type}" = "config" ]; then 576 | if [[ ! $volume =~ ^[_a-z]([_a-z0-9]*[a-z0-9])?$ ]]; then 577 | PRINT "Volume name '${volume}' is malformed. only lowercase letters [a-z], numbers [0-9] and underscore is allowed. First character must be lowercase letter or underscore for config volumes." "error" 0 578 | return 1 579 | fi 580 | else 581 | if [[ ! $volume =~ ^[a-z]([_a-z0-9]*[a-z0-9])?$ ]]; then 582 | PRINT "Volume name '${volume}' is malformed. only lowercase letters [a-z], numbers [0-9] and underscore is allowed. First character must be lowercase letter" "error" 0 583 | return 1 584 | fi 585 | fi 586 | 587 | 588 | # Check for duplicates 589 | if STRING_ITEM_INDEXOF "${all_volumes}" "${volume}"; then 590 | # Duplicate 591 | PRINT "Volume name ${volume} is already defined." "error" 0 592 | return 1 593 | fi 594 | 595 | _copy "encrypted" "/volumes/${index}/encrypted" 596 | STRING_SUBST "encrypted" "'" "" 1 597 | STRING_SUBST "encrypted" '"' "" 1 598 | _copy "bind" "/volumes/${index}/bind" 599 | STRING_SUBST "bind" "'" "" 1 600 | STRING_SUBST "bind" '"' "" 1 601 | 602 | if [ "${type}" = "ramdisk" ]; then 603 | local size= 604 | _copy "size" "/volumes/${index}/size" 605 | STRING_SUBST "size" "'" "" 1 606 | STRING_SUBST "size" '"' "" 1 607 | size="${size:-1M}" 608 | if [[ ! $size =~ ^[0-9]+M$ ]]; then 609 | PRINT "Ramdisk size must be in megabytes and followed by a capital M, such as: 2M" "error" 0 610 | return 1 611 | fi 612 | POD_RAMDISKS="${POD_RAMDISKS}${POD_RAMDISKS:+ }${volume}:${size}" 613 | elif [ "${type}" = "volume" ]; then 614 | # Add suffix 615 | local shared= 616 | _copy "shared" "/volumes/${index}/shared" 617 | STRING_SUBST "shared" "'" "" 1 618 | STRING_SUBST "shared" '"' "" 1 619 | local volumesuffix="" 620 | if [ -z "${shared}" ] || [ "${shared}" = "no" ]; then 621 | volumesuffix="-${podName}-${podVersion}" 622 | elif [ "${shared}" = "pod" ]; then 623 | volumesuffix="-${podName}" 624 | elif [ "${shared}" = "host" ]; then 625 | # Do nothing 626 | : 627 | else 628 | PRINT "Host volume must have shared set to \"no\" (default), \"pod\" or \"host\"." "error" 0 629 | return 1 630 | fi 631 | POD_VOLUMES="${POD_VOLUMES}${POD_VOLUMES:+ }${volume}${volumesuffix}" 632 | elif [ "${type}" = "config" ]; then 633 | if [ ! -d "${buildDir}/config/${volume}" ]; then 634 | PRINT "Config '${volume}' does not exist as: ${buildDir}/config/${volume}." "error" 0 635 | return 1 636 | fi 637 | volumes_config="${volumes_config} ${volume}" 638 | volumes_config_encrypted="${volumes_config_encrypted} ${encrypted:-false}" 639 | elif [ "${type}" = "host" ]; then 640 | volumes_host="${volumes_host} ${volume}" 641 | bind="$(FILE_REALPATH "${bind}" "${srcDir}")" 642 | volumes_host_bind="${volumes_host_bind} ${bind}" 643 | fi 644 | 645 | # Check if encrypted config drive, if so then create matching ramdisk for it 646 | if [ -n "${encrypted}" ]; then 647 | if [ "${encrypted}" = "true" ]; then 648 | if [ "${type}" = "config" ]; then 649 | # Create ramdisk, check so it doesn't exist already 650 | local newramdisk="${volume}-unencrypted" 651 | if STRING_ITEM_INDEXOF "${all_volumes}" "${newramdisk}"; then 652 | # Duplicate 653 | PRINT "Volume name ${newramdisk} is already defined." "error" 0 654 | return 1 655 | fi 656 | all_volumes="${all_volumes}${space}${newramdisk}" 657 | 658 | # Create decrypter container 659 | local decrypterimage="simplenetes/secret-decrypter:1.0" 660 | POD_CONTAINER_COUNT=$((POD_CONTAINER_COUNT+1)) 661 | eval "$(_CONTAINER_VARS "${POD_CONTAINER_COUNT}")" 662 | local decryptername="${volume}-decrypter" 663 | _CONTAINER_SET_VAR "${POD_CONTAINER_COUNT}" "NAME" "decryptername" 664 | _CONTAINER_SET_VAR "${POD_CONTAINER_COUNT}" "IMAGE" "decrypterimage" 665 | local v="on-config" 666 | _CONTAINER_SET_VAR "${POD_CONTAINER_COUNT}" "RESTARTPOLICY" "v" 667 | v="exit" 668 | _CONTAINER_SET_VAR "${POD_CONTAINER_COUNT}" "STARTUPPROBE" "v" 669 | v="120" 670 | _CONTAINER_SET_VAR "${POD_CONTAINER_COUNT}" "STARTUPTIMEOUT" "v" 671 | v="${volume}" 672 | _CONTAINER_SET_VAR "${POD_CONTAINER_COUNT}" "CONFIGS" "v" 673 | v="${volume}-unencrypted" 674 | POD_RAMDISKS="${POD_RAMDISKS}${POD_RAMDISKS:+ }${v}:1M" 675 | # Only this container should be able to access this config, hence the capital Z in :options. 676 | local mounts="-v ./config/${volume}:/mnt/config:Z,ro -v ./ramdisk/${v}:/mnt/ramdisk:z:rw" 677 | _CONTAINER_SET_VAR "${POD_CONTAINER_COUNT}" "MOUNTS" "mounts" 678 | _COMPILE_RUN 679 | else 680 | PRINT "Only config volumes can be encrypted" "error" 0 681 | return 1 682 | fi 683 | elif [ "${encrypted}" != "false" ]; then 684 | PRINT "Unknown value for 'encrypted' for volume ${volume}, ignoring" "warning" 0 685 | fi 686 | fi 687 | 688 | all_volumes="${all_volumes}${space}${volume}" 689 | space=" " 690 | done 691 | 692 | # Go over all containers 693 | # 694 | 695 | local containers=() 696 | _list "containers" "/containers/" 697 | 698 | # Check all container names and check for duplicates. 699 | local container_names="" 700 | local index= 701 | for index in "${containers[@]}"; do 702 | local container_name= 703 | _copy "container_name" "/containers/${index}/name" 704 | STRING_SUBST "container_name" "'" "" 1 705 | STRING_SUBST "container_name" '"' "" 1 706 | if [[ ! $container_name =~ ^[a-z]([_a-z0-9]*[a-z0-9])?$ ]]; then 707 | PRINT "Container name '${container_name}' is malformed. only lowercase letters [a-z], numbers [0-9] and underscore is allowed. First character must be lowercase letter" "error" 0 708 | return 1 709 | fi 710 | if STRING_ITEM_INDEXOF "${container_names}" "${container_name}"; then 711 | PRINT "Container name ${container_name} already defined, change the name." "error" 0 712 | return 1 713 | fi 714 | container_names="${container_names} ${container_name}" 715 | done 716 | 717 | local lines=() 718 | local value= 719 | for index in "${containers[@]}"; do 720 | POD_CONTAINER_COUNT=$((POD_CONTAINER_COUNT+1)) 721 | eval "$(_CONTAINER_VARS "${POD_CONTAINER_COUNT}")" 722 | 723 | ## name 724 | # 725 | local container_name= 726 | _copy "container_name" "/containers/${index}/name" 727 | STRING_SUBST "container_name" "'" "" 1 728 | STRING_SUBST "container_name" '"' "" 1 729 | _CONTAINER_SET_VAR "${POD_CONTAINER_COUNT}" "NAME" "container_name" 730 | 731 | ## network 732 | # 733 | local network= 734 | _copy "network" "/containers/${index}/network" 735 | STRING_SUBST "network" "'" "" 1 736 | STRING_SUBST "network" '"' "" 1 737 | if [ -n "${network}" ]; then 738 | if [ "${network}" != "host" ]; then 739 | PRINT "Unknown network \"${network}\". Network if set must be set to \"host\"." "error" 0 740 | return 1 741 | fi 742 | PRINT "Network set to host. Any expose section will be ignored." "info" 0 743 | network="--network=${network}" 744 | fi 745 | _CONTAINER_SET_VAR "${POD_CONTAINER_COUNT}" "NETWORK" "network" 746 | 747 | ## restart: 748 | # 749 | local value="" 750 | if ! _COMPILE_RESTART; then 751 | return 1 752 | fi 753 | _CONTAINER_SET_VAR "${POD_CONTAINER_COUNT}" "RESTARTPOLICY" "value" 754 | 755 | ## image: 756 | # 757 | local value="" 758 | if ! _COMPILE_IMAGE; then 759 | return 1 760 | fi 761 | _CONTAINER_SET_VAR "${POD_CONTAINER_COUNT}" "IMAGE" "value" 762 | 763 | ## signal: 764 | # 765 | local sig="" 766 | local cmd="" 767 | if ! _COMPILE_SIGNALEXEC; then 768 | return 1 769 | fi 770 | _CONTAINER_SET_VAR "${POD_CONTAINER_COUNT}" "SIGNALSIG" "sig" 771 | _CONTAINER_SET_VAR "${POD_CONTAINER_COUNT}" "SIGNALCMD" "cmd" 772 | 773 | ## startupProbe/timeout: 774 | # 775 | local value="" 776 | if ! _COMPILE_STARTUP_PROBE_TIMEOUT; then 777 | return 1 778 | fi 779 | _CONTAINER_SET_VAR "${POD_CONTAINER_COUNT}" "STARTUPTIMEOUT" "value" 780 | 781 | ## startupProbe/exit or startupProbe/cmd 782 | local value="" 783 | if ! _COMPILE_STARTUP_PROBE; then 784 | return 1 785 | fi 786 | _CONTAINER_SET_VAR "${POD_CONTAINER_COUNT}" "STARTUPPROBE" "value" 787 | 788 | ## startupProbe/signal 789 | # 790 | local value="" 791 | if ! _COMPILE_STARTUP_PROBE_SIGNAL; then 792 | return 1 793 | fi 794 | _CONTAINER_SET_VAR "${POD_CONTAINER_COUNT}" "STARTUPSIGNAL" "value" 795 | 796 | ## readinessProbe/timeout 797 | # 798 | local value="" 799 | if ! _COMPILE_READINESS_PROBE_TIMEOUT; then 800 | return 1 801 | fi 802 | _CONTAINER_SET_VAR "${POD_CONTAINER_COUNT}" "READINESSTIMEOUT" "value" 803 | 804 | ## livenessProbe/cmd 805 | # 806 | local value="" 807 | if ! _COMPILE_LIVENESS_PROBE; then 808 | return 1 809 | fi 810 | _CONTAINER_SET_VAR "${POD_CONTAINER_COUNT}" "LIVENESSPROBE" "value" 811 | 812 | ## livenessProbe/timeout 813 | # 814 | local value="" 815 | if ! _COMPILE_LIVENESS_PROBE_TIMEOUT; then 816 | return 1 817 | fi 818 | _CONTAINER_SET_VAR "${POD_CONTAINER_COUNT}" "LIVENESSTIMEOUT" "value" 819 | 820 | ## livenessProbe/cmd 821 | # 822 | local value="" 823 | if ! _COMPILE_READINESS_PROBE; then 824 | return 1 825 | fi 826 | _CONTAINER_SET_VAR "${POD_CONTAINER_COUNT}" "READINESSPROBE" "value" 827 | 828 | ## mounts: 829 | # 830 | local mounts="" 831 | if ! _COMPILE_MOUNTS; then 832 | return 1 833 | fi 834 | _CONTAINER_SET_VAR "${POD_CONTAINER_COUNT}" "MOUNTS" "mounts" 835 | 836 | ## env 837 | # 838 | local value="" 839 | if ! _COMPILE_ENV; then 840 | return 1 841 | fi 842 | _CONTAINER_SET_VAR "${POD_CONTAINER_COUNT}" "ENV" "value" 843 | 844 | ## ingress 845 | # 846 | local _out_container_ports="" 847 | if [ -z "${network}" ]; then 848 | if ! _COMPILE_INGRESS "/containers/${index}/expose/" "true"; then 849 | return 1 850 | fi 851 | fi 852 | _CONTAINER_SET_VAR "${POD_CONTAINER_COUNT}" "PORTS" "_out_container_ports" 853 | 854 | if ! _COMPILE_ENTRYPOINT; then 855 | return 1 856 | fi 857 | 858 | if ! _COMPILE_CPUMEM; then 859 | return 1 860 | fi 861 | 862 | if ! _COMPILE_RUN; then 863 | return 1 864 | fi 865 | done 866 | 867 | # Extract all labels. 868 | if ! _COMPILE_LABELS; then 869 | return 1 870 | fi 871 | 872 | ## Postfix 873 | # All STARTUPSIGNAL must be transformed to their number. 874 | local container_nr= 875 | for container_nr in $(seq 1 ${POD_CONTAINER_COUNT}); do 876 | local sendsignals= 877 | local sendsignals_nrs="" 878 | _GET_CONTAINER_VAR "${container_nr}" "STARTUPSIGNAL" "sendsignals" 879 | local targetcontainer= 880 | for targetcontainer in ${sendsignals}; do 881 | # Get the container nr for this name 882 | local nr= 883 | if ! nr="$(_GET_CONTAINER_NR "${targetcontainer}")"; then 884 | PRINT "Could not find container ${targetcontainer} referenced in startupProbe/signal." "error" 0 885 | return 1 886 | fi 887 | sendsignals_nrs="${sendsignals_nrs}${sendsignals_nrs:+ }${nr}" 888 | done 889 | # Overwrite with new string 890 | _CONTAINER_SET_VAR "${container_nr}" "STARTUPSIGNAL" "sendsignals_nrs" 891 | done 892 | 893 | ## Postfix 894 | # All container names must be suffixed with the podName-version 895 | for container_nr in $(seq 1 ${POD_CONTAINER_COUNT}); do 896 | local containername= 897 | _GET_CONTAINER_VAR "${container_nr}" "NAME" "containername" 898 | containername="${containername}-${POD}" 899 | # Overwrite with new string 900 | _CONTAINER_SET_VAR "${container_nr}" "NAME" "containername" 901 | done 902 | 903 | ## Output all generated variables 904 | local newline=" 905 | " 906 | local var= 907 | for var in POD POD_VERSION POD_LABELS POD_CREATE POD_VOLUMES POD_RAMDISKS POD_HOSTPORTS POD_CONTAINER_COUNT; do 908 | local value="${!var}" 909 | STRING_ESCAPE "value" '"' 910 | _out_pod="${_out_pod}${newline}$(printf "%s=\"%s\"\\n" "${var}" "${value}")" 911 | done 912 | 913 | local index= 914 | for index in $(seq 1 ${POD_CONTAINER_COUNT}); do 915 | for var in NAME RESTARTPOLICY NETWORK IMAGE STARTUPPROBE STARTUPTIMEOUT STARTUPSIGNAL LIVENESSPROBE LIVENESSTIMEOUT READINESSPROBE READINESSTIMEOUT SIGNALSIG SIGNALCMD CONFIGS MOUNTS ENV COMMAND ARGS WORKINGDIR CPUMEM PORTS RUN; do 916 | local varname="POD_CONTAINER_${var}_${index}" 917 | local value="${!varname}" 918 | _out_pod="${_out_pod}${newline}$(printf "%s=\"%s\"\\n" "${varname}" "${value}")" 919 | done 920 | _out_pod="${_out_pod}${newline}" 921 | done 922 | 923 | ## Output rest of runtime template file 924 | _out_pod="${_out_pod}${newline}$(awk 'BEGIN {show=0} /^[^#]/ { show=1 } {if(show==1) {print}}' "${runtimePath}")" 925 | } 926 | 927 | _COMPILE_LABELS() 928 | { 929 | SPACE_SIGNATURE="[extralabels]" 930 | SPACE_DEP="STRING_IS_ALL STRING_SUBST PRINT" 931 | 932 | local extraLabels="${1:-}" 933 | 934 | local labels="" 935 | local index=() 936 | _list "index" "/labels/" 937 | local index0= 938 | # TODO: validate keys and values, as: https://kubernetes.io/docs/concepts/overview/working-with-objects/labels/#syntax-and-character-set 939 | for index0 in "${index[@]}"; do 940 | local name= 941 | _copy "name" "/labels/${index0}/name" 942 | STRING_SUBST "name" "'" "" 1 943 | STRING_SUBST "name" '"' "" 1 944 | local value= 945 | _copy "value" "/labels/${index0}/value" 946 | STRING_SUBST "value" "'" "" 1 947 | STRING_SUBST "value" '"' "" 1 948 | if ! STRING_IS_ALL "${value}" "A-Za-z0-9_./-"; then 949 | PRINT "Invalid characters in labels. Only [A-Za-z0-9._-/] allowed" "error" 0 950 | return 1 951 | fi 952 | labels="${labels}${labels:+ }--label ${name}=${value}" 953 | done 954 | 955 | POD_LABELS="${labels}${labels:+ }${extraLabels}" 956 | } 957 | 958 | _COMPILE_ENTRYPOINT() 959 | { 960 | # Internal "macro" function. We don't define any SPACE_ headers for this. 961 | 962 | # --entrypoint of container 963 | # if this is set then the default CMD will get nullified and if wanted has to be 964 | # provided as "args" parameter in the pod.yaml. As extracted below. 965 | local subarg= 966 | local arg= 967 | local args= 968 | local value="" 969 | _list "args" "/containers/${index}/command/" "" 1 970 | for arg in "${args[@]}"; do 971 | _copy "subarg" "/containers/${index}/command/${arg}" 972 | _QUOTE_ARG "subarg" 973 | value="${value}${value:+, }${subarg}" 974 | done 975 | 976 | value="${value:+--entrypoint='[$value]'}" 977 | _CONTAINER_SET_VAR "${POD_CONTAINER_COUNT}" "COMMAND" "value" 978 | 979 | local subarg= 980 | local arg= 981 | local args= 982 | local value="" 983 | _list "args" "/containers/${index}/args/" "" 1 984 | for arg in "${args[@]}"; do 985 | _copy "subarg" "/containers/${index}/args/${arg}" 986 | _QUOTE_ARG "subarg" 987 | value="${value}${value:+ }${subarg}" 988 | done 989 | 990 | _CONTAINER_SET_VAR "${POD_CONTAINER_COUNT}" "ARGS" "value" 991 | 992 | local value= 993 | _copy "value" "/containers/${index}/workingDir" 994 | if ! STRING_IS_ALL "${value}" "A-Za-z0-9_./-"; then 995 | PRINT "Invalid characters in workingDir. Only [A-Za-z0-9._-/] allowed" "error" 0 996 | return 1 997 | fi 998 | value="${value:+--workdir=$value}" 999 | _CONTAINER_SET_VAR "${POD_CONTAINER_COUNT}" "WORKINGDIR" "value" 1000 | } 1001 | 1002 | # Macro helper function 1003 | _COMPILE_MOUNTS() 1004 | { 1005 | local args=() 1006 | _list "args" "/containers/${index}/mounts/" 1007 | 1008 | if [ "${#args[@]}" -gt 0 ]; then 1009 | local arg= 1010 | local dest= 1011 | local volume= 1012 | local indexof= 1013 | for arg in "${args[@]}"; do 1014 | _copy "dest" "/containers/${index}/mounts/${arg}/dest" 1015 | STRING_SUBST "dest" "'" "" 1 1016 | STRING_SUBST "dest" '"' "" 1 1017 | # Check so valid destination path 1018 | if [ "${dest#/}" = "${dest}" ]; then 1019 | PRINT "Dest path in mount must start with a slash." "error" 0 1020 | return 1 1021 | fi 1022 | 1023 | _copy "volume" "/containers/${index}/mounts/${arg}/volume" 1024 | STRING_SUBST "volume" "'" "" 1 1025 | STRING_SUBST "volume" '"' "" 1 1026 | # Check so volume exists and get the type 1027 | local type="" 1028 | local bind="" 1029 | local encrypted= 1030 | 1031 | # Need to compare when stripped away the :size parameter from the diskname 1032 | local isRamdisk=0 1033 | local ramdisk= 1034 | for ramdisk in ${POD_RAMDISKS}; do 1035 | local diskname="${ramdisk%:*}" 1036 | if [ "${diskname}" = "${volume}" ]; then 1037 | isRamdisk=1 1038 | break 1039 | fi 1040 | done 1041 | 1042 | if STRING_ITEM_INDEXOF "${POD_VOLUMES}" "${volume}" "indexof"; then 1043 | type="volume" 1044 | elif STRING_ITEM_INDEXOF "${POD_VOLUMES}" "${volume}-${podName}" "indexof"; then 1045 | type="volume" 1046 | elif STRING_ITEM_INDEXOF "${POD_VOLUMES}" "${volume}-${podName}-${podVersion}" "indexof"; then 1047 | type="volume" 1048 | fi 1049 | if [ "${type}" = "volume" ]; then 1050 | STRING_ITEM_GET "${POD_VOLUMES}" "${indexof}" "volume" 1051 | mounts="${mounts}${mounts:+ }-v ${volume}:${dest}:z,rw" 1052 | elif [ "${isRamdisk}" = "1" ]; then 1053 | type="ramdisk" 1054 | bind="./ramdisk/${volume}" 1055 | mounts="${mounts}${mounts:+ }-v ${bind}:${dest}:z,rw" 1056 | elif STRING_ITEM_INDEXOF "${volumes_host}" "${volume}" "indexof"; then 1057 | type="host" 1058 | STRING_ITEM_GET "${volumes_host_bind}" "${indexof}" "bind" 1059 | # dev option so that we can mount devices. 1060 | mounts="${mounts}${mounts:+ }-v ${bind}:${dest}:z,rw,dev" 1061 | elif STRING_ITEM_INDEXOF "${volumes_config}" "${volume}" "indexof"; then 1062 | type="config" 1063 | # Check if encrypted 1064 | STRING_ITEM_GET "${volumes_config_encrypted}" "${indexof}" "encrypted" 1065 | if [ "${encrypted}" = "true" ]; then 1066 | bind="./ramdisk/${volume}-unencrypted" 1067 | mounts="${mounts}${mounts:+ }-v ${bind}:${dest}:z,ro" 1068 | 1069 | # Add to unencrypter container to signal this container 1070 | local container_nr= 1071 | local containername="${volume}-decrypter" 1072 | if ! container_nr="$(_GET_CONTAINER_NR "${containername}")"; then 1073 | PRINT "Could not find container ${containername}." "error" 0 1074 | return 1 1075 | fi 1076 | # Add this container name to the decrypter containers send signals value 1077 | local sendsignals= 1078 | _GET_CONTAINER_VAR "${container_nr}" "STARTUPSIGNAL" "sendsignals" 1079 | sendsignals="${sendsignals}${sendsignals:+ }${container_name}" 1080 | _CONTAINER_SET_VAR "${container_nr}" "STARTUPSIGNAL" "sendsignals" 1081 | else 1082 | bind="./config/${volume}" 1083 | mounts="${mounts}${mounts:+ }-v ${bind}:${dest}:z,ro" 1084 | local configs= 1085 | _GET_CONTAINER_VAR "${POD_CONTAINER_COUNT}" "CONFIGS" "configs" 1086 | configs="${configs}${configs:+ }${volume}" 1087 | _CONTAINER_SET_VAR "${POD_CONTAINER_COUNT}" "CONFIGS" "configs" 1088 | fi 1089 | else 1090 | PRINT "Volume ${volume} is not defined." "error" 0 1091 | return 1 1092 | fi 1093 | done 1094 | fi 1095 | 1096 | } 1097 | 1098 | # Macro helper function 1099 | _COMPILE_RESTART() 1100 | { 1101 | _copy "value" "/containers/${index}/restart" 1102 | STRING_SUBST "value" "'" "" 1 1103 | STRING_SUBST "value" '"' "" 1 1104 | 1105 | value="${value:-never}" 1106 | local value2="${value%:*}" # Cut away the :x for on-interval:x 1107 | if [[ $value2 = "on-interval" ]] && [[ ! $value =~ ^on-interval:[0-9]+$ ]]; then 1108 | PRINT "Restart policy on-interval must be on format on-interval:seconds" "error" 0 1109 | return 1 1110 | fi 1111 | if ! STRING_ITEM_INDEXOF "always on-interval on-config on-failure never" "${value2}"; then 1112 | PRINT "Restart policy '${value}' for container ${container_name} is invalid. Must be always, on-interval:x, on-config, on-failure or never." "error" 0 1113 | return 1 1114 | fi 1115 | } 1116 | 1117 | # Macro helper function 1118 | _COMPILE_IMAGE() 1119 | { 1120 | _copy "value" "/containers/${index}/image" 1121 | STRING_SUBST "value" "'" "" 1 1122 | STRING_SUBST "value" '"' "" 1 1123 | if [ -z "${value}" ]; then 1124 | PRINT "Image for container ${container_name} is invalid." "error" 0 1125 | return 1 1126 | fi 1127 | } 1128 | 1129 | # Macro helper function 1130 | _COMPILE_SIGNALEXEC() 1131 | { 1132 | local args=() 1133 | _list "args" "/containers/${index}/signal/" "" 1 1134 | if [ "${#args[@]}" -gt 1 ]; then 1135 | PRINT "Only one of signal/sig and signal/cmd can be defined, for container ${container_name}." "error" 0 1136 | return 1 1137 | fi 1138 | 1139 | _copy "sig" "/containers/${index}/signal/0/sig" 1140 | STRING_SUBST "sig" "'" "" 1 1141 | STRING_SUBST "sig" '"' "" 1 1142 | if [ -n "${sig}" ]; then 1143 | return 1144 | else 1145 | _list "args" "/containers/${index}/signal/0/cmd/" "" 1 1146 | if [ "${#args[@]}" -gt 0 ]; then 1147 | local arg= 1148 | local subarg= 1149 | local space="" 1150 | for arg in "${args[@]}"; do 1151 | _copy "subarg" "/containers/${index}/signal/0/cmd/${arg}" 1152 | _QUOTE_ARG "subarg" 1153 | cmd="${cmd}${space}${subarg}" 1154 | space=" " 1155 | done 1156 | fi 1157 | fi 1158 | } 1159 | 1160 | # Macro helper function 1161 | _COMPILE_STARTUP_PROBE() 1162 | { 1163 | _copy "value" "/containers/${index}/startupProbe/exit" 1164 | STRING_SUBST "value" "'" "" 1 1165 | STRING_SUBST "value" '"' "" 1 1166 | local args=() 1167 | _list "args" "/containers/${index}/startupProbe/cmd/" "" 1 1168 | if [ "${value}" = "true" ] && [ "${#args[@]}" -gt 0 ]; then 1169 | PRINT "Only startupProbe/exit or startupProbe/cmd can be defined, for container ${container_name}." "error" 0 1170 | return 1 1171 | fi 1172 | 1173 | if [ "${value}" = "true" ]; then 1174 | value="exit" 1175 | return 1176 | else 1177 | value="" 1178 | if [ "${#args[@]}" -gt 0 ]; then 1179 | local arg= 1180 | local subarg= 1181 | local space="" 1182 | for arg in "${args[@]}"; do 1183 | _copy "subarg" "/containers/${index}/startupProbe/cmd/${arg}" 1184 | _QUOTE_ARG "subarg" 1185 | value="${value}${space}${subarg}" 1186 | space=" " 1187 | done 1188 | fi 1189 | fi 1190 | } 1191 | 1192 | 1193 | # Macro helper function 1194 | _COMPILE_STARTUP_PROBE_TIMEOUT() 1195 | { 1196 | _copy "value" "/containers/${index}/startupProbe/timeout" 1197 | STRING_SUBST "value" "'" "" 1 1198 | STRING_SUBST "value" '"' "" 1 1199 | value="${value:-120}" 1200 | if [[ ! $value =~ ^[0-9]+$ ]]; then 1201 | PRINT "Startup timeout for container ${container_name} must be an integer." "error" 0 1202 | return 1 1203 | fi 1204 | } 1205 | 1206 | # Macro helper function 1207 | _COMPILE_STARTUP_PROBE_SIGNAL() 1208 | { 1209 | local args=() 1210 | _list "args" "/containers/${index}/startupProbe/signal/" 1211 | 1212 | if [ "${#args[@]}" -gt 0 ]; then 1213 | local arg= 1214 | local containername= 1215 | local space="" 1216 | local container_nr= 1217 | for arg in "${args[@]}"; do 1218 | # Note: Don't have to check for validity here because 1219 | # these names will be checked and mapped into nrs further down. 1220 | _copy "containername" "/containers/${index}/startupProbe/signal/${arg}/container" 1221 | STRING_SUBST "containername" "'" "" 1 1222 | STRING_SUBST "containername" '"' "" 1 1223 | value="${value}${space}${containername}" 1224 | space=" " 1225 | done 1226 | fi 1227 | } 1228 | 1229 | # Macro helper function 1230 | _COMPILE_READINESS_PROBE() 1231 | { 1232 | local args=() 1233 | _list "args" "/containers/${index}/readinessProbe/cmd/" "" 1 1234 | value="" 1235 | if [ "${#args[@]}" -gt 0 ]; then 1236 | local arg= 1237 | local subarg= 1238 | local space="" 1239 | for arg in "${args[@]}"; do 1240 | _copy "subarg" "/containers/${index}/readinessProbe/cmd/${arg}" 1241 | _QUOTE_ARG "subarg" 1242 | value="${value}${space}${subarg}" 1243 | space=" " 1244 | done 1245 | fi 1246 | } 1247 | 1248 | # Macro helper function 1249 | _COMPILE_READINESS_PROBE_TIMEOUT() 1250 | { 1251 | _copy "value" "/containers/${index}/readinessProbe/timeout" 1252 | STRING_SUBST "value" "'" "" 1 1253 | STRING_SUBST "value" '"' "" 1 1254 | value="${value:-120}" 1255 | if [[ ! $value =~ ^[0-9]+$ ]]; then 1256 | PRINT "Readiness timeout for container ${container_name} must be an integer." "error" 0 1257 | return 1 1258 | fi 1259 | } 1260 | 1261 | # Macro helper function 1262 | _COMPILE_LIVENESS_PROBE() 1263 | { 1264 | local args=() 1265 | _list "args" "/containers/${index}/livenessProbe/cmd/" "" 1 1266 | value="" 1267 | if [ "${#args[@]}" -gt 0 ]; then 1268 | local arg= 1269 | local subarg= 1270 | local space="" 1271 | for arg in "${args[@]}"; do 1272 | _copy "subarg" "/containers/${index}/livenessProbe/cmd/${arg}" 1273 | _QUOTE_ARG "subarg" 1274 | value="${value}${space}${subarg}" 1275 | space=" " 1276 | done 1277 | fi 1278 | } 1279 | 1280 | # Macro helper function 1281 | _COMPILE_LIVENESS_PROBE_TIMEOUT() 1282 | { 1283 | _copy "value" "/containers/${index}/livenessProbe/timeout" 1284 | STRING_SUBST "value" "'" "" 1 1285 | STRING_SUBST "value" '"' "" 1 1286 | value="${value:-120}" 1287 | if [[ ! $value =~ ^[0-9]+$ ]]; then 1288 | PRINT "Liveness timeout for container ${container_name} must be an integer." "error" 0 1289 | return 1 1290 | fi 1291 | } 1292 | 1293 | # Macro helper function 1294 | _COMPILE_ENV() 1295 | { 1296 | local args=() 1297 | _list "args" "/containers/${index}/env/" 1 1 1298 | 1299 | if [ "${#args[@]}" -gt 0 ]; then 1300 | local arg= 1301 | local subarg= 1302 | local space="" 1303 | for arg in "${args[@]}"; do 1304 | _copy "subarg" "/containers/${index}/env/${arg}/name" 1305 | STRING_SUBST "subarg" "'" "" 1 1306 | STRING_SUBST "subarg" '"' "" 1 1307 | if [[ ! $subarg =~ ^[a-zA-Z_][a-zA-Z0-9_]*$ ]]; then 1308 | PRINT "Env variable name contains illegal characters: ${subarg}. a-zA-Z0-9_, ha sto start with a letter." "error" 0 1309 | return 1 1310 | fi 1311 | local subarg2= 1312 | _copy "subarg2" "/containers/${index}/env/${arg}/value" 1313 | _QUOTE_ARG "subarg2" 1314 | value="${value}${space}-e ${subarg}=${subarg2}" 1315 | space=" " 1316 | done 1317 | fi 1318 | } 1319 | 1320 | _COMPILE_CPUMEM() 1321 | { 1322 | # Internal "macro" function. We don't define any SPACE_ headers for this. 1323 | # TODO only do --memory=M for now. 1324 | : 1325 | } 1326 | 1327 | _COMPILE_RUN() 1328 | { 1329 | # Internal "macro" function. We don't define any SPACE_ headers for this. 1330 | local run="\\\${ADD_PROXY_IP:-} --name \${POD_CONTAINER_NAME_${POD_CONTAINER_COUNT}} \${POD_CONTAINER_ENV_${POD_CONTAINER_COUNT}} \${POD_CONTAINER_NETWORK_${POD_CONTAINER_COUNT}} \${POD_CONTAINER_PORTS_${POD_CONTAINER_COUNT}} \${POD_CONTAINER_COMMAND_${POD_CONTAINER_COUNT}} \${POD_CONTAINER_MOUNTS_${POD_CONTAINER_COUNT}} \${POD_CONTAINER_WORKINGDIR_${POD_CONTAINER_COUNT}} \${POD_CONTAINER_IMAGE_${POD_CONTAINER_COUNT}} \${POD_CONTAINER_ARGS_${POD_CONTAINER_COUNT}}" 1331 | _CONTAINER_SET_VAR "${POD_CONTAINER_COUNT}" "RUN" "run" 1332 | } 1333 | 1334 | _COMPILE_INGRESS() 1335 | { 1336 | SPACE_SIGNATURE="prefix useTargetPorts" 1337 | 1338 | local prefix="${1}" 1339 | shift 1340 | 1341 | local useTargetPorts="${1:-true}" 1342 | shift $(($# > 0 ? 1 : 0)) 1343 | 1344 | # Internal "macro" function. We don't define any SPACE_ headers for this. 1345 | 1346 | local portmappings="" 1347 | local lines=() 1348 | 1349 | local exposeindex=() 1350 | _list "exposeindex" "${prefix}" 1351 | local index0= 1352 | for index0 in "${exposeindex[@]}"; do 1353 | # Get targetPort, clusterPort, hostPort 1354 | local targetPort= 1355 | _copy "targetPort" "${prefix}${index0}/targetPort" 1356 | STRING_SUBST "targetPort" "'" "" 1 1357 | STRING_SUBST "targetPort" '"' "" 1 1358 | local clusterPort= 1359 | _copy "clusterPort" "${prefix}${index0}/clusterPort" 1360 | STRING_SUBST "clusterPort" "'" "" 1 1361 | STRING_SUBST "clusterPort" '"' "" 1 1362 | local hostPort= 1363 | _copy "hostPort" "${prefix}${index0}/hostPort" 1364 | STRING_SUBST "hostPort" "'" "" 1 1365 | STRING_SUBST "hostPort" '"' "" 1 1366 | local hostInterface= 1367 | _copy "hostInterface" "${prefix}${index0}/hostInterface" 1368 | STRING_SUBST "hostInterface" "'" "" 1 1369 | STRING_SUBST "hostInterface" '"' "" 1 1370 | local sendproxy= 1371 | _copy "sendproxy" "${prefix}${index0}/sendProxy" 1372 | STRING_SUBST "sendproxy" "'" "" 1 1373 | STRING_SUBST "sendproxy" '"' "" 1 1374 | local maxconn= 1375 | _copy "maxconn" "${prefix}${index0}/maxConn" 1376 | STRING_SUBST "maxconn" "'" "" 1 1377 | STRING_SUBST "maxconn" '"' "" 1 1378 | 1379 | if [ -n "${maxconn}" ]; then 1380 | if [[ ! $maxconn =~ ^([0-9])+$ ]]; then 1381 | PRINT "maxconn must be an integer." "error" 0 1382 | return 1 1383 | fi 1384 | else 1385 | maxconn=4096 # Default value 1386 | fi 1387 | if [ -n "${sendproxy}" ]; then 1388 | if [ "${sendproxy}" = "true" ] || [ "${sendproxy}" = "false" ]; then 1389 | : 1390 | else 1391 | PRINT "Illegal value for sendProxy. Must be set to true or false, default is false." "error" 0 1392 | return 1 1393 | fi 1394 | else 1395 | sendproxy="false" 1396 | fi 1397 | 1398 | if [ "${useTargetPorts}" = "false" ]; then 1399 | if [ -n "${targetPort}" ]; then 1400 | PRINT "targetPort not allowed for this pod runtime." "error" 0 1401 | return 1 1402 | fi 1403 | targetPort="0" 1404 | fi 1405 | 1406 | if [ -n "${hostInterface}" ]; then 1407 | if [[ ! $hostInterface =~ ^([0-9])+[.]([0-9])+[.]([0-9])+[.]([0-9])+$ ]]; then 1408 | PRINT "hostInterface must be an IP address." "error" 0 1409 | return 1 1410 | fi 1411 | fi 1412 | 1413 | if [ -n "${hostPort}" ]; then 1414 | if [[ ! $hostPort =~ ^([0-9])+$ ]]; then 1415 | PRINT "hostPort must be an integer." "error" 0 1416 | return 1 1417 | fi 1418 | 1419 | if [ "${hostPort}" -lt "1" ] || [ "${hostPort}" -gt "65535" ]; then 1420 | PRINT "Host port must be between 1 and 65535 (but typically not between 61000-63999 and 32767)." "error" 0 1421 | return 1 1422 | fi 1423 | 1424 | if [ "${useTargetPorts}" = "true" ]; then 1425 | if [[ ! $targetPort =~ ^([0-9])+$ ]]; then 1426 | PRINT "targetPort must be an integer." "error" 0 1427 | return 1 1428 | fi 1429 | 1430 | if [ "${targetPort}" -lt "1" ] || [ "${targetPort}" -gt "65535" ]; then 1431 | PRINT "Target port must be between 1 and 65535." "error" 0 1432 | return 1 1433 | fi 1434 | fi 1435 | 1436 | if [ -n "${clusterPort}" ]; then 1437 | if [[ ! $clusterPort =~ ^([0-9])+$ ]]; then 1438 | PRINT "clusterPort must be an integer." "error" 0 1439 | return 1 1440 | fi 1441 | 1442 | if [ "${clusterPort}" -lt "1024" ] || [ "${clusterPort}" -gt "65535" ]; then 1443 | PRINT "Cluster port must be between 1024 and 65535, but not between 30000-32767." "error" 0 1444 | return 1 1445 | fi 1446 | 1447 | if [ "${clusterPort}" -ge "30000" ] && [ "${clusterPort}" -le "32767" ]; then 1448 | PRINT "Cluster port cannot be in the reserved range of 30000-32767." "error" 0 1449 | return 1 1450 | fi 1451 | else 1452 | clusterPort="0" 1453 | fi 1454 | 1455 | portmappings="${portmappings} ${clusterPort}:${hostInterface}-${hostPort}:${targetPort}:${maxconn}:${sendproxy}" 1456 | 1457 | if STRING_ITEM_INDEXOF "${POD_HOSTPORTS}" "${hostPort}"; then 1458 | PRINT "A hostPort can only be defined once in ingress (even for different interfaces), for hostPort ${hostPort} and targetPort ${targetPort}." "error" 0 1459 | return 1 1460 | fi 1461 | POD_HOSTPORTS="${POD_HOSTPORTS}${POD_HOSTPORTS:+ }${hostPort}" 1462 | else 1463 | clusterPort="0" 1464 | fi 1465 | 1466 | local prefix2="${prefix}${index0}/ingress/" 1467 | local ingressindex=() 1468 | _list "ingressindex" "${prefix2}" 1469 | local index1= 1470 | for index1 in "${ingressindex[@]}"; do 1471 | local domain= 1472 | local bind= 1473 | local protocol= 1474 | local weight= 1475 | local path_beg= 1476 | local path_end= 1477 | local path= 1478 | local redirect_to_https= 1479 | local redirect_location= 1480 | local redirect_prefix= 1481 | local errorfile= 1482 | _copy "protocol" "${prefix2}${index1}/protocol" 1483 | STRING_SUBST "protocol" "'" "" 1 1484 | STRING_SUBST "protocol" '"' "" 1 1485 | _copy "bind" "${prefix2}${index1}/bind" 1486 | STRING_SUBST "bind" "'" "" 1 1487 | STRING_SUBST "bind" '"' "" 1 1488 | _copy "domain" "${prefix2}${index1}/domain" 1489 | STRING_SUBST "domain" "'" "" 1 1490 | STRING_SUBST "domain" '"' "" 1 1491 | _copy "weight" "${prefix2}${index1}/weight" 1492 | STRING_SUBST "weight" "'" "" 1 1493 | STRING_SUBST "weight" '"' "" 1 1494 | _copy "path_beg" "${prefix2}${index1}/pathBeg" 1495 | STRING_SUBST "path_beg" "'" "" 1 1496 | STRING_SUBST "path_beg" '"' "" 1 1497 | _copy "path_end" "${prefix2}${index1}/pathEnd" 1498 | STRING_SUBST "path_end" "'" "" 1 1499 | STRING_SUBST "path_end" '"' "" 1 1500 | _copy "path" "${prefix2}${index1}/path" 1501 | STRING_SUBST "path" "'" "" 1 1502 | STRING_SUBST "path" '"' "" 1 1503 | _copy "redirect_to_https" "${prefix2}${index1}/redirectToHttps" 1504 | STRING_SUBST "redirect_to_https" "'" "" 1 1505 | STRING_SUBST "redirect_to_https" '"' "" 1 1506 | _copy "redirect_location" "${prefix2}${index1}/redirectLocation" 1507 | STRING_SUBST "redirect_location" "'" "" 1 1508 | STRING_SUBST "redirect_location" '"' "" 1 1509 | _copy "redirect_prefix" "${prefix2}${index1}/redirectPrefix" 1510 | STRING_SUBST "redirect_prefix" "'" "" 1 1511 | STRING_SUBST "redirect_prefix" '"' "" 1 1512 | _copy "errorfile" "${prefix2}${index1}/errorfile" 1513 | STRING_SUBST "errorfile" "'" "" 1 1514 | STRING_SUBST "errorfile" '"' "" 1 1515 | 1516 | if ! STRING_ITEM_INDEXOF "http https tcp" "${protocol}"; then 1517 | PRINT "Unknown protocol ${protocol} in ingress. Only: http, https and tcp allowed" "error" 0 1518 | return 1 1519 | fi 1520 | 1521 | if [ -z "${bind}" ]; then 1522 | if [ "${protocol}" = "http" ]; then 1523 | bind="80" 1524 | elif [ "${protocol}" = "https" ]; then 1525 | bind="443" 1526 | elif [ "${protocol}" = "tcp" ]; then 1527 | PRINT "For tcp protocol a bind port must be provided." "error" 0 1528 | return 1 1529 | fi 1530 | else 1531 | # Check so bind is valid 1532 | if [[ ! $bind =~ ^([0-9])+$ ]]; then 1533 | PRINT "Bind must be an integer." "error" 0 1534 | return 1 1535 | fi 1536 | fi 1537 | 1538 | if [ -z "${weight}" ]; then 1539 | weight="100" 1540 | else 1541 | if [[ ! $weight =~ ^([0-9])+$ ]]; then 1542 | PRINT "Importance must be an integer." "error" 0 1543 | return 1 1544 | fi 1545 | fi 1546 | 1547 | lines+=("bind ${bind}") 1548 | lines+=("protocol ${protocol}") 1549 | lines+=("host ${domain}") 1550 | lines+=("weight ${weight}") 1551 | 1552 | local httpSpecific=0 1553 | if [ -n "${path}" ]; then 1554 | lines+=("path ${path}") 1555 | httpSpecific=1 1556 | fi 1557 | 1558 | if [ -n "${path_beg}" ]; then 1559 | lines+=("path_beg ${path_beg}") 1560 | httpSpecific=1 1561 | fi 1562 | 1563 | if [ -n "${path_end}" ]; then 1564 | lines+=("path_end ${path_end}") 1565 | httpSpecific=1 1566 | fi 1567 | 1568 | if [ "${redirect_to_https}" = "true" ]; then 1569 | lines+=("redirect_to_https true") 1570 | httpSpecific=1 1571 | elif [ -n "${redirect_location}" ]; then 1572 | lines+=("redirect_location ${redirect_location}") 1573 | httpSpecific=1 1574 | elif [ -n "${redirect_prefix}" ]; then 1575 | lines+=("redirect_prefix ${redirect_prefix}") 1576 | httpSpecific=1 1577 | elif [ -n "${errorfile}" ]; then 1578 | lines+=("errorfile ${errorfile}") 1579 | httpSpecific=1 1580 | else 1581 | # server backend. 1582 | # This requires that any http psecific criteria has been set, of http/s 1583 | if [ "${httpSpecific}" = "0" ] && { [ "${protocol}" = "http" ] || [ "${protocol}" = "https" ]; }; then 1584 | PRINT "Missing http/s specific criterion. Ex: path, pathBeg, pathEnd, etc" "error" 0 1585 | return 1 1586 | fi 1587 | 1588 | 1589 | # This requires a hostPort 1590 | if [ -z "${hostPort}" ]; then 1591 | PRINT "This pod yaml ingress is lacking a hostPort definition." "error" 0 1592 | return 1 1593 | fi 1594 | # This requires a clusterPort >0 1595 | if [ "${clusterPort}" -eq 0 ]; then 1596 | PRINT "This pod yaml ingress is lacking a clusterPort definition." "error" 0 1597 | return 1 1598 | fi 1599 | lines+=("clusterport ${clusterPort}") 1600 | fi 1601 | 1602 | if [ "${httpSpecific}" = "1" ] && [ "${protocol}" = "tcp" ]; then 1603 | PRINT "Cannot have HTTP specific matching and actions for TCP protocol. Such as path, pathBeg, redirect, etc." "error" 0 1604 | return 1 1605 | fi 1606 | 1607 | done 1608 | done 1609 | 1610 | if [ -n "${POD_INGRESSCONF}" ]; then 1611 | local nl=" 1612 | " 1613 | POD_INGRESSCONF="${POD_INGRESSCONF}${nl}$(printf "%s\\n" "${lines[@]}")" 1614 | else 1615 | POD_INGRESSCONF="$(printf "%s\\n" "${lines[@]}")" 1616 | fi 1617 | 1618 | # Create the port mapping for the container. 1619 | local arg= 1620 | for arg in ${portmappings}; do 1621 | local clusterPort="${arg%%:*}" 1622 | local hostPort="${arg%:*:*:*}" 1623 | hostPort="${hostPort#*:}" 1624 | hostInterface="${hostPort%-*}" 1625 | hostPort="${hostPort#*-}" 1626 | local targetPort="${arg%:*:*}" 1627 | targetPort="${targetPort#*:*:}" 1628 | local maxConn="${arg%:*}" 1629 | maxConn="${maxConn#*:*:*:}" 1630 | local sendProxy="${arg##*:}" 1631 | if [ "${clusterPort}" -gt 0 ]; then 1632 | # Only expose in cluster if clusterPort >0 1633 | local nl=" 1634 | " 1635 | # This gets written to the portmappings file. 1636 | POD_PROXYCONF="${POD_PROXYCONF}${POD_PROXYCONF:+$nl}${clusterPort}:${hostPort}:${maxConn}:${sendProxy}" 1637 | fi 1638 | # hostInterface set value of escaped varname 1639 | if [ -z "${hostInterface}" ]; then 1640 | hostInterface='\${HOST_INTERFACE}' 1641 | else 1642 | hostInterface="${hostInterface}:" 1643 | fi 1644 | _out_container_ports="${_out_container_ports}${_out_container_ports:+ }-p ${hostInterface}${hostPort}:${targetPort}" 1645 | done 1646 | } 1647 | 1648 | # Go thrugh all containers and find the one matching name 1649 | _GET_CONTAINER_NR() 1650 | { 1651 | SPACE_SIGNATURE="containername" 1652 | SPACE_DEP="_GET_CONTAINER_VAR" 1653 | 1654 | local containername="${1}" 1655 | shift 1656 | 1657 | local index= 1658 | local name= 1659 | for index in $(seq 1 ${POD_CONTAINER_COUNT}); do 1660 | _GET_CONTAINER_VAR "${index}" "NAME" "name" 1661 | if [ "${containername}" = "${name}" ]; then 1662 | printf "%s\\n" "${index}" 1663 | return 0 1664 | fi 1665 | done 1666 | return 1 1667 | } 1668 | 1669 | # argname is the name of a variable which we want to quote in place 1670 | _QUOTE_ARG() 1671 | { 1672 | SPACE_SIGNATURE="argname" 1673 | SPACE_DEP="STRING_ESCAPE STRING_SUBSTR" 1674 | 1675 | local argname="${1}" 1676 | shift 1677 | 1678 | # Dereference the variable, bashism 1679 | local argvalue="${!argname}" 1680 | 1681 | local firstchar= 1682 | local lastchar= 1683 | STRING_SUBSTR "${argvalue}" 0 1 "firstchar" 1684 | STRING_SUBSTR "${argvalue}" -1 1 "lastchar" 1685 | 1686 | local addquotes=0 1687 | if [ "${firstchar}" = "${argvalue}" ]; then 1688 | # Empty string or single char 1689 | addquotes=1 1690 | elif [ "${firstchar}" = "${lastchar}" ] && [ "${firstchar}" = "\"" ]; then 1691 | # Properly formatted string, do nothing 1692 | : 1693 | elif [ "${firstchar}" = "${lastchar}" ] && [ "${firstchar}" = "'" ]; then 1694 | # String enclosed in single quotes, 1695 | # remove single quotes 1696 | STRING_SUBSTR "${argvalue}" 1 -1 "${argname}" 1697 | addquotes=1 1698 | else 1699 | # Whatever this is, add quotes 1700 | addquotes=1 1701 | fi 1702 | 1703 | # Check if we need to enclose the argument in quotes 1704 | if [ "${addquotes}" = "1" ]; then 1705 | STRING_ESCAPE "${argname}" 1706 | eval "${argname}=\"\\\"\${${argname}}\\\"\"" 1707 | fi 1708 | 1709 | # Now lift the whole argument. 1710 | STRING_ESCAPE "${argname}" 1711 | } 1712 | 1713 | _CONTAINER_SET_VAR() 1714 | { 1715 | SPACE_SIGNATURE="container_nr varname valuevarname [quote]" 1716 | SPACE_DEP="STRING_ESCAPE" 1717 | 1718 | local container_nr="${1}" 1719 | shift 1720 | 1721 | local varname="${1}" 1722 | shift 1723 | 1724 | local valuevarname="${1}" 1725 | shift 1726 | 1727 | local quote="${1:-0}" 1728 | shift 1729 | 1730 | if [ "${quote}" = "1" ]; then 1731 | STRING_ESCAPE "${valuevarname}" '"' 1732 | fi 1733 | 1734 | eval "POD_CONTAINER_${varname}_${container_nr}=\"\${${valuevarname}}\"" 1735 | } 1736 | 1737 | _GET_CONTAINER_VAR() 1738 | { 1739 | SPACE_SIGNATURE="container_nr varname outname" 1740 | # The SPACE_DEP for this function is dynamic and we 1741 | # provide it from outside as -e SPACE_DEP. 1742 | 1743 | local container_nr="${1}" 1744 | shift 1745 | 1746 | local varname="${1}" 1747 | shift 1748 | 1749 | local outname="${1}" 1750 | shift 1751 | 1752 | eval "${outname}=\"\${POD_CONTAINER_${varname}_${container_nr}}\"" 1753 | } 1754 | 1755 | _CONTAINER_VARS() 1756 | { 1757 | SPACE_SIGNATURE="container_nr" 1758 | 1759 | local container_nr="${1}" 1760 | shift 1761 | 1762 | printf "%s\\n" " 1763 | local POD_CONTAINER_NAME_${container_nr}= 1764 | local POD_CONTAINER_STARTUPPROBE_${container_nr}= 1765 | local POD_CONTAINER_STARTUPTIMEOUT_${container_nr}= 1766 | local POD_CONTAINER_STARTUPSIGNAL_${container_nr}= 1767 | local POD_CONTAINER_READINESSPROBE_${container_nr}= 1768 | local POD_CONTAINER_READINESSTIMEOUT_${container_nr}= 1769 | local POD_CONTAINER_LIVENESSPROBE_${container_nr}= 1770 | local POD_CONTAINER_LIVENESSTIMEOUT_${container_nr}= 1771 | local POD_CONTAINER_SIGNALSIG_${container_nr}= 1772 | local POD_CONTAINER_SIGNALCMD_${container_nr}= 1773 | local POD_CONTAINER_RESTARTPOLICY_${container_nr}= 1774 | local POD_CONTAINER_NETWORK_${container_nr}= 1775 | local POD_CONTAINER_CONFIGS_${container_nr}= 1776 | local POD_CONTAINER_MOUNTS_${container_nr}= 1777 | local POD_CONTAINER_ENV_${container_nr}= 1778 | local POD_CONTAINER_COMMAND_${container_nr}= 1779 | local POD_CONTAINER_ARGS_${container_nr}= 1780 | local POD_CONTAINER_WORKINGDIR_${container_nr}= 1781 | local POD_CONTAINER_CPUMEM_${container_nr}= 1782 | local POD_CONTAINER_PORTS_${container_nr}= 1783 | local POD_CONTAINER_RUN_${container_nr}=" 1784 | } 1785 | --------------------------------------------------------------------------------