diff --git a/.devcontainer.json b/.devcontainer.json.deprecated similarity index 75% rename from .devcontainer.json rename to .devcontainer.json.deprecated index 71d69d3..6ce7d60 100644 --- a/.devcontainer.json +++ b/.devcontainer.json.deprecated @@ -2,7 +2,6 @@ "name": "devtools", "shutdownAction": "none", "image": "us-docker.pkg.dev/runwhen-nonprod-shared/public-images/codecollection-devtools:latest", - // "image": "codecollectiondevtools:latest", "overrideCommand": false, "extensions": [ "robocorp.robotframework-lsp", @@ -20,10 +19,13 @@ "${localWorkspaceFolder}/auth/:/home/runwhen/auth/:z", "--name", "devtools", - "--dns", + "--dns", "8.8.8.8" ], - "postStartCommand":"pip install --user --no-cache-dir -r /home/runwhen/codecollection/requirements.txt", + "containerEnv": { + "RW_MODE": "dev" + }, + "postStartCommand": "pip install --user --no-cache-dir -r /home/runwhen/codecollection/requirements.txt", "forwardPorts": [ 3000 ], @@ -34,10 +36,7 @@ "version": "latest" } }, - // Configure tool-specific properties. "customizations": { - // Configure properties specific to VS Code. - // configuration following google standard "vscode": { "settings": { "python.languageServer": "Jedi", @@ -45,7 +44,7 @@ "python.linting.pylintEnabled": true, "python.linting.pylintArgs": [ "--max-line-length=120", - "--enable=W0614" // track unused imports + "--enable=W0614" ], "[python]": { "editor.insertSpaces": true, @@ -61,11 +60,8 @@ "robot.language-server.python": "/usr/local/bin/python", "robot.pythonpath": [ "/home/runwhen", - "/home/runwhen/rw-public-codecollection/libraries", - "/home/runwhen/rw-public-codecollection/libraries/RW", "/home/runwhen/codecollection/libraries", - "/home/runwhen/codecollection/libraries/RW", - "/home/runwhen/dev_facade" + "/home/runwhen/codecollection/libraries/RW" ] } } diff --git a/.devcontainer.source.json b/.devcontainer.source.json.deprecated similarity index 68% rename from .devcontainer.source.json rename to .devcontainer.source.json.deprecated index bd4c5a1..efd84a1 100644 --- a/.devcontainer.source.json +++ b/.devcontainer.source.json.deprecated @@ -1,16 +1,12 @@ { "name": "devtools", - // this devcontainer config is for running from source "dockerComposeFile": [ "docker-compose.yaml" ], "service": "devtools", "shutdownAction": "none", - "workspaceFolder": "/app/", - // Configure tool-specific properties. + "workspaceFolder": "/home/runwhen/", "customizations": { - // Configure properties specific to VS Code. - // configuration following google standard "vscode": { "extensions": [ "robocorp.robotframework-lsp", @@ -25,7 +21,7 @@ "python.linting.pylintEnabled": true, "python.linting.pylintArgs": [ "--max-line-length=120", - "--enable=W0614" // track unused imports + "--enable=W0614" ], "[python]": { "editor.insertSpaces": true, @@ -40,14 +36,11 @@ ], "robot.language-server.python": "/usr/local/bin/python", "robot.pythonpath": [ - "/app", - "/app/rw-public-codecollection/libraries", - "/app/rw-public-codecollection/libraries/RW", - "/app/codecollection/libraries", - "/app/codecollection/libraries/RW", - "/app/dev_facade" + "/home/runwhen", + "/home/runwhen/codecollection/libraries", + "/home/runwhen/codecollection/libraries/RW" ] } } } -} \ No newline at end of file +} diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json new file mode 100644 index 0000000..548d39d --- /dev/null +++ b/.devcontainer/devcontainer.json @@ -0,0 +1,73 @@ +{ + "name": "CodeCollection DevTools", + "image": "ghcr.io/runwhen-contrib/codecollection-devtools:latest", + "remoteUser": "runwhen", + "updateRemoteUserUID": false, + "overrideCommand": false, + "workspaceFolder": "/home/runwhen", + + "forwardPorts": [3000], + "portsAttributes": { + "3000": { + "label": "Robot Test Logs", + "onAutoForward": "notify" + } + }, + + "containerEnv": { + "RW_MODE": "dev", + "ROBOT_LOG_DIR": "/robot_logs", + "GITHUB_TOKEN": "${localEnv:GITHUB_TOKEN}", + "CODECOLLECTION_REPO": "${localEnv:CODECOLLECTION_REPO}", + "CODECOLLECTION_BRANCH": "${localEnv:CODECOLLECTION_BRANCH}", + "PR_NUMBER": "${localEnv:PR_NUMBER}" + }, + + "onCreateCommand": "bash /home/runwhen/.devcontainer/on-create.sh", + "postStartCommand": "python -m http.server --bind 0.0.0.0 --directory /robot_logs 3000 &", + + "features": { + "ghcr.io/devcontainers/features/docker-in-docker:2": {}, + "ghcr.io/devcontainers/features/sshd:1": { + "version": "latest" + } + }, + + "customizations": { + "vscode": { + "extensions": [ + "robocorp.robotframework-lsp", + "ms-python.pylint", + "ms-python.black-formatter", + "ms-python.python", + "njpwerner.autodocstring" + ], + "settings": { + "python.languageServer": "Jedi", + "python.linting.enabled": true, + "python.linting.pylintEnabled": true, + "python.linting.pylintArgs": [ + "--max-line-length=120", + "--enable=W0614" + ], + "[python]": { + "editor.insertSpaces": true, + "editor.tabSize": 4 + }, + "editor.formatOnSave": true, + "editor.lineNumbers": "on", + "python.formatting.provider": "black", + "python.formatting.blackArgs": ["--line-length", "120"], + "robot.language-server.python": "/usr/local/bin/python", + "robot.pythonpath": [ + "/home/runwhen", + "/home/runwhen/codecollection/libraries", + "/home/runwhen/codecollection/libraries/RW" + ] + } + }, + "codespaces": { + "openFiles": ["codecollection/README.md"] + } + } +} diff --git a/.devcontainer/on-create.sh b/.devcontainer/on-create.sh new file mode 100755 index 0000000..86e60c9 --- /dev/null +++ b/.devcontainer/on-create.sh @@ -0,0 +1,98 @@ +#!/bin/bash +# ====================================================================================== +# on-create.sh — Bootstrap a codecollection into the devtools environment +# +# Runs once when the devcontainer is created (onCreateCommand). +# Clones the target codecollection repo, installs its Python deps, and +# optionally checks out a PR branch for review. +# +# Environment variables (set via devcontainer.json or Codespaces secrets): +# +# CODECOLLECTION_REPO Git URL or GitHub shorthand (org/repo) of the +# codecollection to work on. +# Default: runwhen-contrib/rw-cli-codecollection +# +# CODECOLLECTION_BRANCH Branch to check out after clone. +# Default: main +# +# PR_NUMBER If set, fetch and check out the PR branch instead +# of CODECOLLECTION_BRANCH. Requires gh CLI auth. +# +# GITHUB_TOKEN Passed through for gh CLI auth (Codespaces injects +# this automatically). +# ====================================================================================== +set -euo pipefail + +RUNWHEN_HOME="/home/runwhen" +CODECOLLECTION_DIR="${RUNWHEN_HOME}/codecollection" +DEFAULT_REPO="runwhen-contrib/rw-cli-codecollection" + +REPO="${CODECOLLECTION_REPO:-$DEFAULT_REPO}" +BRANCH="${CODECOLLECTION_BRANCH:-main}" + +# Normalise shorthand "org/repo" → full HTTPS URL +if [[ "$REPO" != http* && "$REPO" != git@* ]]; then + REPO="https://github.com/${REPO}.git" +fi + +echo "=== CodeCollection DevTools Bootstrap ===" +echo " Repo: ${REPO}" +echo " Branch: ${BRANCH}" +echo " PR: ${PR_NUMBER:-none}" +echo "==========================================" + +# ------------------------------------------------------------------ +# 1. Clone the codecollection +# ------------------------------------------------------------------ +if [ -d "${CODECOLLECTION_DIR}/.git" ]; then + echo "Codecollection already cloned at ${CODECOLLECTION_DIR}, pulling latest..." + git -C "${CODECOLLECTION_DIR}" fetch --all --prune +else + # Remove placeholder dir if the image created one + rm -rf "${CODECOLLECTION_DIR}" + echo "Cloning ${REPO} → ${CODECOLLECTION_DIR} ..." + git clone --branch "${BRANCH}" "${REPO}" "${CODECOLLECTION_DIR}" +fi + +# ------------------------------------------------------------------ +# 2. Optionally check out a PR +# ------------------------------------------------------------------ +if [ -n "${PR_NUMBER:-}" ]; then + echo "Checking out PR #${PR_NUMBER}..." + cd "${CODECOLLECTION_DIR}" + if command -v gh &>/dev/null; then + gh pr checkout "${PR_NUMBER}" + else + echo "gh CLI not found — falling back to git fetch" + git fetch origin "pull/${PR_NUMBER}/head:pr-${PR_NUMBER}" + git checkout "pr-${PR_NUMBER}" + fi + echo "On branch: $(git branch --show-current)" +fi + +# ------------------------------------------------------------------ +# 3. Install codecollection Python dependencies +# ------------------------------------------------------------------ +if [ -f "${CODECOLLECTION_DIR}/requirements.txt" ]; then + echo "Installing codecollection requirements..." + pip install --user --no-cache-dir -r "${CODECOLLECTION_DIR}/requirements.txt" +fi + +# ------------------------------------------------------------------ +# 4. Ensure auth directory exists for credential mounts +# ------------------------------------------------------------------ +mkdir -p "${RUNWHEN_HOME}/auth" + +# ------------------------------------------------------------------ +# 5. Verify key tools are available +# ------------------------------------------------------------------ +echo "" +echo "--- Environment ready ---" +echo " ro: $(command -v ro && echo 'ok' || echo 'MISSING')" +echo " robot: $(command -v robot && echo 'ok' || echo 'MISSING')" +echo " kubectl: $(command -v kubectl && echo 'ok' || echo 'MISSING')" +echo " gh: $(command -v gh && echo 'ok' || echo 'MISSING')" +echo " python: $(python --version 2>&1)" +echo "" +echo "Codecollection bootstrapped at ${CODECOLLECTION_DIR}" +echo "Run 'cd codecollection/codebundles/ && ro' to test a codebundle." diff --git a/.github/workflows/build-push.yaml b/.github/workflows/build-push.yaml index 1179a93..a435194 100644 --- a/.github/workflows/build-push.yaml +++ b/.github/workflows/build-push.yaml @@ -13,6 +13,8 @@ on: paths: - "VERSION" - "Dockerfile" + - "requirements.txt" + - ".devcontainer/**" - ".github/workflows/build-push.yaml" workflow_dispatch: @@ -92,7 +94,7 @@ jobs: docker buildx inspect --bootstrap docker buildx build \ --push \ - --platform linux/amd64 \ + --platform linux/amd64,linux/arm64 \ --tag "${{ env.SHARED_ARTIFACT_REPOSITORY_PATH }}/${{ env.IMAGE }}:${{ env.VERSION }}" \ --tag "${{ env.SHARED_ARTIFACT_REPOSITORY_PATH }}/${{ env.IMAGE }}:latest" \ --tag "ghcr.io/${{ env.GHCR_ORG }}/${{ env.IMAGE }}:${{ env.VERSION }}" \ diff --git a/Dockerfile b/Dockerfile index 7039bbb..d68fb89 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,68 +1,160 @@ -ARG BASE_IMAGE=us-docker.pkg.dev/runwhen-nonprod-shared/public-images/robot-runtime-devtools-base-image:latest +############################################################################### +# CodeCollection DevTools +# +# Development environment for authoring and testing RunWhen codebundles. +# Built on the slim base image (Python + worker + runtime + rw-core-keywords). +# Adds ALL common CLI tools since authors may develop for any platform. +# +# rw-core-keywords handles both dev and production modes — no separate +# dev_facade needed. Set RW_MODE=dev (default in this image) for local +# development behavior. +############################################################################### + +ARG BASE_IMAGE=us-docker.pkg.dev/runwhen-nonprod-shared/public-images/robot-runtime-base-image:latest FROM ${BASE_IMAGE} +USER root + ENV RUNWHEN_HOME=/home/runwhen ENV ROBOT_LOG_DIR=/robot_logs +ENV RW_MODE=dev - -USER root -# Set up specific RunWhen Home Dir and Permissions -WORKDIR $RUNWHEN_HOME - -# Ensure `runwhen` user has a fixed UID and GID (1000) +# Ensure runwhen user has a fixed UID/GID (1000) for volume mounts RUN usermod -u 1000 runwhen && \ groupmod -g 1000 runwhen -# Install additional packages +############################################################################### +# Dev tools: sudo, lnav, go-task, terraform +############################################################################### RUN apt-get update && \ - apt-get install -y --no-install-recommends sudo && \ + apt-get install -y --no-install-recommends \ + sudo apt-transport-https gnupg2 lsb-release && \ apt-get clean && \ - rm -rf /var/lib/apt/lists/* /var/cache/apt + rm -rf /var/lib/apt/lists/* -# Install Terraform -ENV TERRAFORM_VERSION=1.11.2 -RUN wget https://releases.hashicorp.com/terraform/${TERRAFORM_VERSION}/terraform_${TERRAFORM_VERSION}_linux_amd64.zip && \ - unzip terraform_${TERRAFORM_VERSION}_linux_amd64.zip terraform -d /usr/local/bin/ && \ - rm terraform_${TERRAFORM_VERSION}_linux_amd64.zip +RUN echo "runwhen ALL=(ALL) NOPASSWD: ALL" >> /etc/sudoers -#Install go-task +# Architecture detection for multi-arch tool installs +RUN ARCH=$(uname -m) && \ + if [ "$ARCH" = "x86_64" ]; then \ + echo "ARCH_BIN=amd64" >> /tmp/arch_vars; \ + echo "AWS_ARCH=x86_64" >> /tmp/arch_vars; \ + elif [ "$ARCH" = "aarch64" ]; then \ + echo "ARCH_BIN=arm64" >> /tmp/arch_vars; \ + echo "AWS_ARCH=aarch64" >> /tmp/arch_vars; \ + else \ + echo "Unsupported architecture: $ARCH"; exit 1; \ + fi + +# go-task RUN sh -c "$(curl --location https://taskfile.dev/install.sh)" -- -d -b /usr/local/bin -# Create the Log Output Directory as root, then change ownership to `runwhen` +# Terraform +ENV TERRAFORM_VERSION=1.11.2 +RUN . /tmp/arch_vars && \ + wget -q https://releases.hashicorp.com/terraform/${TERRAFORM_VERSION}/terraform_${TERRAFORM_VERSION}_linux_${ARCH_BIN}.zip && \ + unzip terraform_${TERRAFORM_VERSION}_linux_${ARCH_BIN}.zip terraform -d /usr/local/bin/ && \ + rm terraform_${TERRAFORM_VERSION}_linux_${ARCH_BIN}.zip + +############################################################################### +# CLI tools — all common tools for multi-platform codebundle development +############################################################################### + +# kubectl +RUN . /tmp/arch_vars && \ + curl -LO "https://dl.k8s.io/release/$(curl -sL https://dl.k8s.io/release/stable.txt)/bin/linux/${ARCH_BIN}/kubectl" && \ + chmod +x kubectl && mv kubectl /usr/local/bin + +# Helm +RUN . /tmp/arch_vars && \ + HELM_VERSION=$(curl -s https://api.github.com/repos/helm/helm/releases/latest | jq -r '.tag_name') && \ + curl -fsSL -o /tmp/helm.tar.gz "https://get.helm.sh/helm-${HELM_VERSION}-linux-${ARCH_BIN}.tar.gz" && \ + tar -zxvf /tmp/helm.tar.gz -C /tmp && \ + mv /tmp/linux-${ARCH_BIN}/helm /usr/local/bin/helm && \ + chmod +x /usr/local/bin/helm && \ + rm -rf /tmp/helm.tar.gz /tmp/linux-${ARCH_BIN} + +# AWS CLI +RUN . /tmp/arch_vars && \ + curl "https://awscli.amazonaws.com/awscli-exe-linux-${AWS_ARCH}.zip" -o "awscliv2.zip" && \ + unzip awscliv2.zip && ./aws/install --update && \ + rm -rf awscliv2.zip ./aws + +# Azure CLI +RUN . /tmp/arch_vars && \ + curl -sL https://packages.microsoft.com/keys/microsoft.asc | gpg --dearmor | tee /etc/apt/trusted.gpg.d/microsoft.asc.gpg > /dev/null && \ + echo "deb [arch=${ARCH_BIN}] https://packages.microsoft.com/repos/azure-cli/ bookworm main" | tee /etc/apt/sources.list.d/azure-cli.list && \ + apt-get update && apt-get install -y --no-install-recommends azure-cli && \ + apt-get clean && rm -rf /var/lib/apt/lists/* + +# kubelogin (Azure AKS) +RUN . /tmp/arch_vars && \ + curl -Lo kubelogin.zip https://github.com/Azure/kubelogin/releases/latest/download/kubelogin-linux-${ARCH_BIN}.zip && \ + unzip kubelogin.zip && mv bin/linux_${ARCH_BIN}/kubelogin /usr/local/bin/ && \ + rm -rf kubelogin.zip bin && chmod +x /usr/local/bin/kubelogin + +# Google Cloud SDK +ARG CLOUD_SDK_VERSION=532.0.0 +RUN . /tmp/arch_vars && \ + if [ "$ARCH_BIN" = "amd64" ]; then GCLOUD_ARCH="x86_64"; else GCLOUD_ARCH="arm"; fi && \ + curl -O https://dl.google.com/dl/cloudsdk/channels/rapid/downloads/google-cloud-cli-${CLOUD_SDK_VERSION}-linux-${GCLOUD_ARCH}.tar.gz && \ + tar xzf google-cloud-cli-${CLOUD_SDK_VERSION}-linux-${GCLOUD_ARCH}.tar.gz && \ + rm google-cloud-cli-${CLOUD_SDK_VERSION}-linux-${GCLOUD_ARCH}.tar.gz && \ + rm -rf google-cloud-sdk/platform/bundledpythonunix +ENV PATH="${RUNWHEN_HOME}/google-cloud-sdk/bin:$PATH" +RUN gcloud config set core/disable_usage_reporting true && \ + gcloud config set component_manager/disable_update_check true && \ + gcloud components update --quiet && \ + gcloud components install -q beta gke-gcloud-auth-plugin && \ + rm -rf $(find google-cloud-sdk/ -regex ".*/__pycache__") \ + google-cloud-sdk/.install/.backup \ + google-cloud-sdk/bin/anthoscli + +# istioctl +RUN . /tmp/arch_vars && \ + ISTIO_VERSION="$(curl -s https://api.github.com/repos/istio/istio/releases/latest | jq -r '.tag_name')" && \ + curl -fsSL -o /tmp/istioctl.tar.gz \ + "https://github.com/istio/istio/releases/download/${ISTIO_VERSION}/istioctl-${ISTIO_VERSION}-linux-${ARCH_BIN}.tar.gz" && \ + tar -xzf /tmp/istioctl.tar.gz -C /tmp && \ + mv /tmp/istioctl /usr/local/bin/ && chmod +x /usr/local/bin/istioctl && \ + rm /tmp/istioctl.tar.gz + +# GitHub CLI (PR checkout in devcontainer) +RUN . /tmp/arch_vars && \ + curl -fsSL https://cli.github.com/packages/githubcli-archive-keyring.gpg \ + | dd of=/usr/share/keyrings/githubcli-archive-keyring.gpg && \ + echo "deb [arch=${ARCH_BIN} signed-by=/usr/share/keyrings/githubcli-archive-keyring.gpg] https://cli.github.com/packages stable main" \ + | tee /etc/apt/sources.list.d/github-cli.list > /dev/null && \ + apt-get update && \ + apt-get install -y --no-install-recommends gh && \ + apt-get clean && rm -rf /var/lib/apt/lists/* + +# Cleanup +RUN rm -f /tmp/arch_vars + +############################################################################### +# Dev environment setup +############################################################################### + +WORKDIR $RUNWHEN_HOME + RUN mkdir -p $ROBOT_LOG_DIR && \ chown runwhen:0 $ROBOT_LOG_DIR && \ chmod 775 $ROBOT_LOG_DIR -# Set custom TMPDIR -ENV TMPDIR=/tmp/runwhen -RUN mkdir -p $TMPDIR && \ - chown runwhen:0 $TMPDIR && \ - chmod 775 $TMPDIR - -# Set up dev scaffolding -# COPY --chown=runwhen:0 dev_facade dev_facade -COPY --chown=runwhen:0 auth auth COPY --chown=runwhen:0 .pylintrc.google LICENSE ro requirements.txt . +COPY --chown=runwhen:0 .devcontainer/ .devcontainer/ +RUN mkdir -p auth && \ + chown -R runwhen:0 ${RUNWHEN_HOME}/.devcontainer ${RUNWHEN_HOME}/auth && \ + chmod -R 0775 ${RUNWHEN_HOME}/ro ${RUNWHEN_HOME}/auth ${RUNWHEN_HOME}/.devcontainer - -# Add runwhen user to sudoers with no password prompt -RUN echo "runwhen ALL=(ALL) NOPASSWD: ALL" >> /etc/sudoers - -# Switch to `runwhen` user USER runwhen -ENV USER "runwhen" -# Set the PATH to include binaries the `runwhen` user will need -ENV PATH "$PATH:/usr/local/bin:/home/runwhen/.local/bin:$RUNWHEN_HOME" +ENV USER="runwhen" - -#Requirements for runrobot.py and the core and User RW libs: RUN pip install --user --no-cache-dir -r requirements.txt -RUN chmod -R g+w ${RUNWHEN_HOME} && \ - chmod -R 0775 $RUNWHEN_HOME - -# Set the pythonpath -ENV PYTHONPATH "$PYTHONPATH:.:$RUNWHEN_HOME/rw-public-codecollection/libraries:$RUNWHEN_HOME/rw-public-codecollection/codebundles:$RUNWHEN_HOME/codecollection/libraries:$RUNWHEN_HOME/codecollection/codebundles:$RUNWHEN_HOME/dev_facade" +ENV PATH="${PATH}:/usr/local/bin:${RUNWHEN_HOME}/.local/bin:${RUNWHEN_HOME}" +ENV PYTHONPATH="$PYTHONPATH:.:${RUNWHEN_HOME}/codecollection/libraries:${RUNWHEN_HOME}/codecollection/codebundles" EXPOSE 3000 CMD python -m http.server --bind 0.0.0.0 --directory $ROBOT_LOG_DIR 3000 diff --git a/README.md b/README.md index 4f3d1f3..ac3ce34 100644 --- a/README.md +++ b/README.md @@ -1,37 +1,246 @@ -

- - Join Slack - -

+
-# RunWhen CodeCollection Devtools +![CodeCollection DevTools banner](assets/banner.svg) -[![Tests](https://github.com/runwhen-contrib/codecollection-devtools/actions/workflows/smoke-test.yaml/badge.svg)](https://github.com/runwhen-contrib/codecollection-devtools/actions/workflows/smoke-test.yaml) +# CodeCollection DevTools -[![Build](https://github.com/runwhen-contrib/codecollection-devtools/actions/workflows/build-push.yaml/badge.svg)](https://github.com/runwhen-contrib/codecollection-devtools/actions/workflows/build-push.yaml) +**codecollection-devtools** is the standard development environment for authoring and testing [RunWhen](https://runwhen.com) codebundles. One image, any codecollection — pull the pre-built container, set an env var, and start developing. -This repository is for managing developer tooling related to creating your own CodeCollections. You can use it to do development directly from source, in a devcontainer from source, or from the image. CodeCollections can also use this image as a base for their dev images. Check the [docs](https://docs.runwhen.com/public/v/runwhen-authors/) for more information. +[![Build and Push](https://github.com/runwhen-contrib/codecollection-devtools/actions/workflows/build-push.yaml/badge.svg)](https://github.com/runwhen-contrib/codecollection-devtools/actions/workflows/build-push.yaml) +[![License: Apache-2.0](https://img.shields.io/badge/License-Apache%202.0-blue.svg)](https://opensource.org/licenses/Apache-2.0) -Looking to be a contributor for CodeCollections or start your own? We'd love to collaborate! Head on over to our [technical docs](https://docs.runwhen.com/public/v/runwhen-authors/codecollection-development/getting-started/running-your-first-codebundle) to get started. +[GitHub](https://github.com/runwhen-contrib/codecollection-devtools) · [GHCR](https://github.com/orgs/runwhen-contrib/packages/container/package/codecollection-devtools) · [Author Docs](https://docs.runwhen.com/public/v/runwhen-authors/) -## Getting Started -File Structure overview of devcontainer when launched from a CodeCollection: +
+ +--- + +## Key features + +- **One image for all codecollections** — set `CODECOLLECTION_REPO` to bootstrap any codecollection repo automatically. +- **PR review ready** — set `PR_NUMBER` and the environment checks out the PR branch for you. +- **Multi-arch** — pre-built for both `linux/amd64` (Codespaces, CI) and `linux/arm64` (Apple Silicon). +- **Batteries included** — Robot Framework, `ro` test runner, kubectl, Helm, AWS CLI, Azure CLI, gcloud, Terraform, gh CLI, and more. +- **Works everywhere** — GitHub Codespaces, VS Code devcontainers (local), or plain `docker run`. + +## Requirements + +- **Docker** or a compatible runtime (Podman, OrbStack, etc.) — for local devcontainer use +- **GitHub Codespaces** — no local runtime needed; runs in the cloud +- A codecollection repo to work on (defaults to `rw-cli-codecollection`) + +## Getting started + +### Option 1: GitHub Codespaces (recommended for PR review) + +Open a Codespace from the `codecollection-devtools` repo, passing the codecollection and PR number as environment variables: + +1. Go to **github.com/runwhen-contrib/codecollection-devtools** → **Code** → **Codespaces** → **New with options** +2. Set environment variables: + - `CODECOLLECTION_REPO` = `runwhen-contrib/rw-cli-codecollection` (or any org/repo) + - `PR_NUMBER` = `123` (optional — checks out the PR branch) +3. Click **Create codespace** + +The `on-create.sh` bootstrap script clones the repo, installs dependencies, and checks out the PR automatically. + +### Option 2: VS Code devcontainer (local) + +Clone this repo and open it in VS Code with the Dev Containers extension: + +```bash +git clone https://github.com/runwhen-contrib/codecollection-devtools.git +cd codecollection-devtools +``` + +Set environment variables before opening the devcontainer: + +```bash +export CODECOLLECTION_REPO="runwhen-contrib/rw-cli-codecollection" +export CODECOLLECTION_BRANCH="main" +# export PR_NUMBER="123" # optional +``` + +Then open in VS Code → **Reopen in Container** (or `Cmd+Shift+P` → "Dev Containers: Reopen in Container"). + +The devcontainer pulls the pre-built image from GHCR — no local Docker build required. + +### Option 3: Docker run (headless) + +```bash +docker run --rm -it \ + -e CODECOLLECTION_REPO="runwhen-contrib/rw-cli-codecollection" \ + -v "$HOME/.kube:/home/runwhen/auth/.kube:ro" \ + ghcr.io/runwhen-contrib/codecollection-devtools:latest \ + bash -c 'bash /home/runwhen/.devcontainer/on-create.sh && exec bash' ``` --/app/ - |- auth/ # store secrets here, it should already be properly gitignored for you - |- codecollection/ - | |- codebundles/ # stores codebundles that can be run during development - | |- libraries/ # stores python keyword libraries used by codebundles - |- dev_facade/ # provides interfaces equivalent to those used on the platform, but just dry runs the keywords to assist with development - ... + +--- + +## Environment variables + +| Variable | Default | Description | +|----------|---------|-------------| +| `CODECOLLECTION_REPO` | `runwhen-contrib/rw-cli-codecollection` | GitHub `org/repo` shorthand or full git URL of the codecollection to work on. | +| `CODECOLLECTION_BRANCH` | `main` | Branch to check out after cloning. | +| `PR_NUMBER` | *(none)* | If set, checks out the PR branch via `gh pr checkout`. Requires `GITHUB_TOKEN`. | +| `GITHUB_TOKEN` | *(injected by Codespaces)* | GitHub token for `gh` CLI auth. Codespaces provides this automatically. | +| `RW_MODE` | `dev` | Set to `dev` for local development behavior (handled by `rw-core-keywords`). | + +--- + +## Running codebundles + +Once the environment is bootstrapped, navigate to any codebundle and use the `ro` wrapper: + +```bash +cd codecollection/codebundles/k8s-namespace-healthcheck +ro runbook.robot ``` -The included script `ro` wraps the `robot` RobotFramework binary, and includes some extra functionality to write files to a consistent location for viewing in a HTTP server at http://localhost:3000/ that is always running as part of the devcontainer. +`ro` wraps the `robot` command with: +- **Isolated working directories** — each run gets its own temp dir with copied cloud CLI configs (`.azure`, `.gcloud`, `.kube`) +- **Log output** — HTML reports written to `/robot_logs`, served at [localhost:3000](http://localhost:3000) +- **Selective test execution** — `ro --test "Check Health" runbook.robot` + +```bash +ro # run all .robot files in current dir +ro runbook.robot # run a specific file +ro --test "Check Health" # run a specific test case +ro ../other-codebundle/ # run tests in a different directory +``` + +### Credentials + +Mount or copy credentials into the `auth/` directory: + +``` +/home/runwhen/ +├── auth/ +│ ├── .kube/config # kubectl +│ ├── .azure/ # Azure CLI +│ └── .gcloud/ # Google Cloud SDK +├── codecollection/ +│ ├── codebundles/ # your codebundles +│ └── libraries/ # shared keyword libraries +└── ro # test runner +``` + +`ro` copies these configs into an isolated temp directory per run, so parallel executions don't interfere with each other. + +--- + +## What's in the image + +| Category | Tools | +|----------|-------| +| **Core** | Python 3.x, Robot Framework, `ro`, `rw-core-keywords`, `rw-cli-keywords` | +| **Kubernetes** | kubectl, Helm, istioctl, kubelogin | +| **Cloud CLIs** | AWS CLI v2, Azure CLI, Google Cloud SDK (gcloud, gsutil, bq) | +| **Infrastructure** | Terraform, go-task | +| **Dev tools** | git, gh (GitHub CLI), sudo, jq | + +### Python packages + +Base packages installed in the image (from `requirements.txt`): + +- `rw-cli-keywords` (includes `rw-core-keywords` — handles `RW_MODE=dev` for local development) +- `jmespath`, `python-dateutil`, `thefuzz`, `jinja2` + +Each codecollection's `requirements.txt` is installed at bootstrap time by `on-create.sh`. + +--- + +## File structure + +``` +codecollection-devtools/ +├── .devcontainer/ +│ ├── devcontainer.json # devcontainer config (pulls pre-built image) +│ └── on-create.sh # bootstrap: clone repo, install deps, checkout PR +├── .github/ +│ └── workflows/ +│ ├── build-push.yaml # CI: multi-arch build → GHCR + GCP Artifact Registry +│ └── pypi.yaml # publish rw-devtools to PyPI (deprecated) +├── Dockerfile # image definition (built by CI, not locally) +├── ro # Robot Framework test runner wrapper +├── requirements.txt # base Python dependencies +├── dev_facade/ # DEPRECATED — use rw-core-keywords instead +└── README.md +``` + +--- + +## Architecture + +### Build pipeline + +All image builds happen in **GitHub Actions** — never locally: + +1. **Pull requests** → build test only (no push) +2. **Push to main** (when `VERSION`, `Dockerfile`, `requirements.txt`, `.devcontainer/**`, or workflow files change) → multi-arch build (`linux/amd64` + `linux/arm64`) → push to GHCR and GCP Artifact Registry + +### Image registries + +| Registry | Image | +|----------|-------| +| **GHCR** | `ghcr.io/runwhen-contrib/codecollection-devtools:latest` | +| **GCP Artifact Registry** | `us-docker.pkg.dev/runwhen-nonprod-shared/public-images/codecollection-devtools:latest` | + +### Bootstrap flow + +``` +devcontainer opens + → pulls pre-built image from GHCR + → runs on-create.sh: + 1. clones CODECOLLECTION_REPO into /home/runwhen/codecollection/ + 2. checks out PR_NUMBER branch (if set) + 3. pip installs codecollection's requirements.txt + 4. verifies tools (ro, robot, kubectl, gh, python) + → starts log HTTP server on port 3000 + → ready to develop +``` + +--- + +## For codecollection authors + +Each codecollection repo does **not** need its own devcontainer config. Instead, point users at this repo: + +```markdown +## Development + +Use [codecollection-devtools](https://github.com/runwhen-contrib/codecollection-devtools) +to spin up a dev environment: + +CODECOLLECTION_REPO=runwhen-contrib/your-codecollection + +See the [devtools README](https://github.com/runwhen-contrib/codecollection-devtools#getting-started) for full instructions. +``` + +### Supported codecollections + +Any codecollection that follows the standard layout works: + +``` +your-codecollection/ +├── codebundles/ +│ └── your-bundle/ +│ ├── runbook.robot +│ └── sli.robot +├── libraries/ +│ └── YourKeywords/ +├── requirements.txt +└── README.md +``` + +--- + +## Contributing + +We'd love to collaborate. Head to the [RunWhen author docs](https://docs.runwhen.com/public/v/runwhen-authors/codecollection-development/getting-started/running-your-first-codebundle) to get started with codebundle development. -### Quickstart +--- -Navigate to the codebundle directory -`cd codecollection/codebundles/hello_world/` +## License -Run the codebundle -`ro sli.robot` +Apache-2.0 diff --git a/assets/banner.svg b/assets/banner.svg new file mode 100644 index 0000000..db8c85c --- /dev/null +++ b/assets/banner.svg @@ -0,0 +1,28 @@ + + + + + + + + + + + + + + + + + + + + + + + + CodeCollection DevTools + One image. Any codecollection. Instant dev environment. + Author and test RunWhen codebundles in a pre-built, + multi-arch container — locally or in GitHub Codespaces. + diff --git a/dev_facade/DEPRECATED.md b/dev_facade/DEPRECATED.md new file mode 100644 index 0000000..c90d279 --- /dev/null +++ b/dev_facade/DEPRECATED.md @@ -0,0 +1,12 @@ +# dev_facade — DEPRECATED + +This directory is superseded by the `rw-core-keywords` package on PyPI. + +`rw-core-keywords` now handles both development and production modes via the +`RW_MODE` environment variable (defaults to `production`, set to `dev` for +local codebundle development). + +The `rw-devtools` PyPI package published from this directory is no longer +needed. Use `pip install rw-core-keywords` instead. + +See: https://github.com/runwhen-contrib/rw-core-keywords diff --git a/docker-compose.yaml b/docker-compose.yaml index 7e90f35..50296ac 100644 --- a/docker-compose.yaml +++ b/docker-compose.yaml @@ -1,5 +1,4 @@ version: '3.9' -# LOCAL DEV services: devtools: container_name: devtools @@ -7,8 +6,9 @@ services: build: context: ./ dockerfile: ./Dockerfile + environment: + - RW_MODE=dev volumes: - # Mount local codebase to reflect changes for local dev - .:/home/runwhen ports: - 3000:3000 diff --git a/requirements.txt b/requirements.txt index 4134f02..7185978 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,9 +1,5 @@ -robotframework>=4.1.2 jmespath>=1.0.1 python-dateutil>=2.9.0 -requests>=2.31.0 thefuzz>=0.20.0 -pyyaml>=6.0.1 jinja2>=3.1.4 -rw-devtools>=0.0.2 -rw-cli-keywords>=0.0.18 \ No newline at end of file +rw-cli-keywords>=0.0.18 diff --git a/ro b/ro index d29e25b..fa06270 100755 --- a/ro +++ b/ro @@ -49,6 +49,7 @@ if [[ "$1" == "--help" || "$1" == "-h" ]]; then fi TMPDIR="${TMPDIR:-/tmp/runwhen}" +export RW_MODE="${RW_MODE:-dev}" function ro () { # If no arguments given, pretend the user typed '.' diff --git a/setup.py b/setup.py index 5870f3b..959591c 100644 --- a/setup.py +++ b/setup.py @@ -1,3 +1,6 @@ +# DEPRECATED: rw-devtools is replaced by rw-core-keywords (PyPI). +# rw-core-keywords handles both dev and production modes via RW_MODE env var. +# This file is kept only for backward compatibility with existing installs. from setuptools import setup setup(