Skip to content

Commit 548d77e

Browse files
feat: Add Docker support and GitHub Actions workflow for container image publishing
1 parent 75a58d0 commit 548d77e

File tree

5 files changed

+259
-1
lines changed

5 files changed

+259
-1
lines changed
Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
name: Publish to GHCR.io
2+
3+
on: [push]
4+
5+
env:
6+
REGISTRY: ghcr.io
7+
IMAGE_NAME: ${{ github.repository }}
8+
9+
jobs:
10+
build_tag_push_to_ghcr:
11+
runs-on: ubuntu-latest
12+
permissions:
13+
contents: read
14+
packages: write
15+
16+
steps:
17+
- name: Checkout repository
18+
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4
19+
20+
21+
- name: Set up QEMU
22+
uses: docker/setup-qemu-action@49b3bc8e6bdd4a60e6116a5414239cba5943d3cf # v3
23+
24+
- name: Setup Docker buildx
25+
uses: docker/setup-buildx-action@c47758b77c9736f4b2ef4073d4d51994fabfe349 # v3.7.1
26+
27+
- name: Log into registry ${{ env.REGISTRY }}
28+
uses: docker/login-action@9780b0c442fbb1117ed29e0efdff1e18412f7567 # v3.3.0
29+
with:
30+
registry: ${{ env.REGISTRY }}
31+
username: ${{ github.actor }}
32+
password: ${{ secrets.GITHUB_TOKEN }}
33+
34+
- name: Extract Docker metadata
35+
id: meta
36+
uses: docker/metadata-action@8e5442c4ef9f78752691e2d8f8d19755c6f78e81 # v5.5.1
37+
with:
38+
github-token: ${{ secrets.GITHUB_TOKEN }}
39+
images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}
40+
tags: |
41+
type=ref,event=branch,prefix=
42+
type=ref,event=tag,prefix=
43+
type=sha,format=short,prefix=
44+
type=sha,format=long,prefix=
45+
env:
46+
DOCKER_METADATA_ANNOTATIONS_LEVELS: manifest,index
47+
48+
49+
50+
- name: Determine version
51+
id: determine_version
52+
run: |
53+
if [ "${GITHUB_REF_TYPE}" == "tag" ]; then
54+
VERSION=${GITHUB_REF_NAME}
55+
else
56+
VERSION=v0.0.0-${GITHUB_SHA::7}
57+
fi
58+
BUILD_TIMESTAMP=$(date -u +"%Y-%m-%dT%H:%M:%SZ")
59+
echo "VERSION=${VERSION}" >> $GITHUB_ENV
60+
echo "COMMIT_SHA=${GITHUB_SHA::7}" >> $GITHUB_ENV
61+
echo "BUILD_TIMESTAMP=${BUILD_TIMESTAMP}" >> $GITHUB_ENV
62+
63+
- name: Build and push Docker image
64+
uses: docker/build-push-action@4f58ea79222b3b9dc2c8bbdd6debcef730109a75 # v6
65+
with:
66+
context: .
67+
push: ${{ github.event_name != 'pull_request' }}
68+
tags: ${{ steps.meta.outputs.tags }}
69+
labels: ${{ steps.meta.outputs.labels }}
70+
build-args: |
71+
VERSION=${{ env.VERSION }}
72+
COMMIT_SHA=${{ env.COMMIT_SHA }}
73+
BUILD_TIMESTAMP=${{ env.BUILD_TIMESTAMP }}
74+
cache-from: type=gha
75+
cache-to: type=gha,mode=max

Dockerfile

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
# Use an official Python runtime as a parent image
2+
FROM python:3.11.10-alpine@sha256:65c34f59d896f939f204e64c2f098db4a4c235be425bd8f0804fd389b1e5fd80 AS builder
3+
4+
# Set working directory
5+
WORKDIR /app
6+
7+
# Copy the requirements file
8+
COPY requirements.txt .
9+
10+
# Install dependencies
11+
RUN pip install --no-cache-dir -r requirements.txt
12+
13+
# Use a smaller base image for the final image
14+
FROM python:3.11.10-alpine@sha256:65c34f59d896f939f204e64c2f098db4a4c235be425bd8f0804fd389b1e5fd80
15+
16+
# Set working directory
17+
WORKDIR /app
18+
19+
# Copy the dependencies from the builder stage
20+
COPY --from=builder /usr/local/lib/python3.11/site-packages /usr/local/lib/python3.11/site-packages
21+
COPY --from=builder /usr/local/bin /usr/local/bin
22+
23+
# Copy the application files
24+
COPY app /app
25+
26+
# Set environment variables
27+
ENV PYTHONUNBUFFERED=1
28+
29+
# Accept build arguments for versioning
30+
ARG VERSION=unknown
31+
ARG COMMIT_SHA=unknown
32+
ARG BUILD_TIMESTAMP=unknown
33+
34+
ENV VERSION=${VERSION}
35+
ENV COMMIT_SHA=${COMMIT_SHA}
36+
ENV BUILD_TIMESTAMP=${BUILD_TIMESTAMP}
37+
38+
# Create a non-root user and switch to it
39+
RUN adduser -D appuser
40+
USER appuser
41+
42+
# Make port 8000 available to the world outside this container
43+
EXPOSE 8000
44+
45+
# Use ENTRYPOINT to ensure the container runs as expected
46+
ENTRYPOINT ["python", "-u", "main.py"]

README.md

Lines changed: 27 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1,27 @@
1-
# getoutline-docs-update-github-organizations
1+
# getoutline-docs-update-github-organizations
2+
3+
Generates dynamic documentation of all our GitHub Organizations and their descriptions and updates GetOutline.com with these details. This makes it easier for discovery and search within getoutline.com
4+
5+
## Prerequisites
6+
7+
- Python (See version in Dockerfile))
8+
- Docker (optional, for containerized deployment)
9+
10+
## Running the app
11+
12+
2.
13+
```sh
14+
pipenv install -r requirements.txt
15+
source .env
16+
python app/main.py
17+
```
18+
19+
## Configuration
20+
21+
The application requires three environment variables to be set. You can create a `.env` file in the root directory of the project with the following content:
22+
23+
```env
24+
export GITHUB_TOKEN=<Uses a PAT> #Assumes that all orgs a user is part of is an ORG that we own.
25+
export GETOUTLINE_DOCUMENT_ID=<This is usually at the end of the document in the URL of the document you want to update>
26+
export GETOUTLINE_API_TOKEN=<Token is tied to a user account in GETOUTLINE>
27+
```

app/main.py

Lines changed: 109 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,109 @@
1+
import requests
2+
import os
3+
import glueops.setup_logging
4+
5+
6+
7+
GITHUB_API_URL = "https://api.github.com"
8+
# Environment Variables
9+
REQUIRED_ENV_VARS = [
10+
"GITHUB_TOKEN",
11+
"GETOUTLINE_DOCUMENT_ID",
12+
"GETOUTLINE_API_TOKEN",
13+
]
14+
OPTIONAL_ENV_VARS = {
15+
"VERSION": "unknown",
16+
"COMMIT_SHA": "unknown",
17+
"BUILD_TIMESTAMP": "unknown",
18+
}
19+
20+
def get_env_variable(var_name: str, default=None):
21+
"""Retrieve environment variable or return default if not set."""
22+
value = os.getenv(var_name, default)
23+
if var_name in REQUIRED_ENV_VARS and value is None:
24+
logger.error(f"Environment variable '{var_name}' is not set.")
25+
raise EnvironmentError(f"Environment variable '{var_name}' is required but not set.")
26+
logger.debug(f"Environment variable '{var_name}' retrieved.")
27+
return value
28+
29+
LOG_LEVEL = os.getenv("LOG_LEVEL", "INFO")
30+
logger = glueops.setup_logging.configure(level=LOG_LEVEL)
31+
logger.info(f"Logger initialized with level: {LOG_LEVEL}")
32+
logger.info({
33+
"version": os.getenv("VERSION", "unknown"),
34+
"commit_sha": os.getenv("COMMIT_SHA", "unknown"),
35+
"build_timestamp": os.getenv("BUILD_TIMESTAMP", "unknown")
36+
})
37+
38+
try:
39+
GITHUB_TOKEN = get_env_variable('GITHUB_TOKEN')
40+
GETOUTLINE_DOCUMENT_ID = get_env_variable('GETOUTLINE_DOCUMENT_ID')
41+
GETOUTLINE_API_TOKEN = get_env_variable('GETOUTLINE_API_TOKEN')
42+
VERSION = get_env_variable('VERSION', OPTIONAL_ENV_VARS['VERSION'])
43+
COMMIT_SHA = get_env_variable('COMMIT_SHA', OPTIONAL_ENV_VARS['COMMIT_SHA'])
44+
BUILD_TIMESTAMP = get_env_variable('BUILD_TIMESTAMP', OPTIONAL_ENV_VARS['BUILD_TIMESTAMP'])
45+
logger.info("All required environment variables retrieved successfully.")
46+
except EnvironmentError as env_err:
47+
logger.critical(f"Environment setup failed: {env_err}")
48+
raise
49+
50+
def get_organizations():
51+
logger.debug("Fetching organizations from GitHub API.")
52+
headers = {
53+
'Authorization': f'token {GITHUB_TOKEN}',
54+
'Accept': 'application/vnd.github.v3+json',
55+
}
56+
try:
57+
response = requests.get(f"{GITHUB_API_URL}/user/orgs", headers=headers)
58+
response.raise_for_status()
59+
logger.debug("Organizations fetched successfully.")
60+
return response.json()
61+
except requests.exceptions.RequestException as e:
62+
logger.error(f"Error fetching organizations: {e}")
63+
return []
64+
65+
def generate_markdown(orgs):
66+
logger.debug("Generating markdown for organizations.")
67+
markdown_content = "# GitHub Organizations\n\n"
68+
markdown_content += "| Organization Name | Description |\n"
69+
markdown_content += "|-------------------|-------------|\n"
70+
71+
for org in orgs:
72+
name = org['login']
73+
url = f"https://github.com/{org['login']}"
74+
description = org.get('description', 'No description available.')
75+
markdown_content += f"| [{name}]({url}) | {description} |\n"
76+
77+
logger.debug("Markdown generation completed.")
78+
return markdown_content
79+
80+
def update_document(markdown_text):
81+
logger.debug("Updating document on Outline.")
82+
url = "https://app.getoutline.com/api/documents.update"
83+
payload = {
84+
"id": GETOUTLINE_DOCUMENT_ID,
85+
"text": markdown_text
86+
}
87+
headers = {
88+
"Content-Type": "application/json",
89+
"Authorization": f"Bearer {GETOUTLINE_API_TOKEN}"
90+
}
91+
try:
92+
response = requests.post(url, json=payload, headers=headers)
93+
response.raise_for_status()
94+
logger.info(f"Document update response code: {response.status_code}")
95+
except requests.exceptions.RequestException as e:
96+
logger.error(f"Error updating document: {e}")
97+
98+
def main():
99+
logger.info("Starting.")
100+
organizations = get_organizations()
101+
if organizations:
102+
markdown = generate_markdown(organizations)
103+
update_document(markdown)
104+
else:
105+
logger.warning("No organizations found.")
106+
logger.info("Finished.")
107+
108+
if __name__ == "__main__":
109+
main()

requirements.txt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
requests==2.32.3
2+
glueops-helpers @ https://github.com/GlueOps/python-glueops-helpers-library/archive/refs/tags/v0.4.1.zip

0 commit comments

Comments
 (0)