Skip to content

Commit 11593bd

Browse files
authored
Merge pull request #2470 from IFRCGo/feature/schema-file
Add openapi schema file
2 parents 511d86f + b8f7eb3 commit 11593bd

File tree

6 files changed

+157
-1
lines changed

6 files changed

+157
-1
lines changed
Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
#!/bin/bash
2+
3+
set -e
4+
5+
if ! command -v jq &>/dev/null; then
6+
echo "jq is not installed."
7+
exit 1
8+
fi
9+
10+
: "${GITHUB_TOKEN:?GITHUB_TOKEN is required and cannot be empty}"
11+
: "${GITHUB_OWNER:?GITHUB_OWNER is required and cannot be empty}"
12+
: "${GITHUB_REPO:?GITHUB_REPO is required and cannot be empty}"
13+
: "${GITHUB_BRANCH:?GITHUB_BRANCH is required and cannot be empty}"
14+
: "${SOURCE_FILE_PATH:?SOURCE_FILE_PATH is required and cannot be empty}"
15+
: "${DEST_FILE_PATH:?DEST_FILE_PATH is required and cannot be empty}"
16+
: "${COMMIT_MESSAGE:?COMMIT_MESSAGE is required and cannot be empty}"
17+
18+
GITHUB_API_URL="https://api.github.com/repos/$GITHUB_OWNER/$GITHUB_REPO/contents/$DEST_FILE_PATH"
19+
20+
function get_existing_file_sha {
21+
# Get file SHA if it exists
22+
FILE_INFO=$(curl -s -H "Authorization: token $GITHUB_TOKEN" $GITHUB_API_URL?ref=$GITHUB_BRANCH)
23+
echo "$FILE_INFO" | jq -r .sha
24+
}
25+
26+
# Read and encode file
27+
if [ ! -f "$SOURCE_FILE_PATH" ]; then
28+
echo "File $SOURCE_FILE_PATH not found!"
29+
exit 1
30+
fi
31+
# NOTE: -w 0 disables line wrapping
32+
ENCODED_CONTENT=$(base64 -w 0 "$SOURCE_FILE_PATH")
33+
34+
EXISTING_FILE_SHA=$(get_existing_file_sha)
35+
36+
# Prepare API payload
37+
if [ "$EXISTING_FILE_SHA" == "null" ]; then
38+
echo "Creating new file..."
39+
SHA_PART=""
40+
else
41+
echo "Updating existing file..."
42+
SHA_PART=", \"sha\": \"$EXISTING_FILE_SHA\""
43+
fi
44+
45+
# To avoid `Argument list too long` error
46+
TMPFILE=$(mktemp)
47+
48+
if [ -n "$EXISTING_FILE_SHA" ]; then
49+
cat > "$TMPFILE" <<EOF
50+
{
51+
"message": "$COMMIT_MESSAGE",
52+
"content": "$ENCODED_CONTENT",
53+
"branch": "$GITHUB_BRANCH",
54+
"sha": "$EXISTING_FILE_SHA"
55+
}
56+
EOF
57+
else
58+
cat > "$TMPFILE" <<EOF
59+
{
60+
"message": "$COMMIT_MESSAGE",
61+
"content": "$ENCODED_CONTENT",
62+
"branch": "$GITHUB_BRANCH"
63+
}
64+
EOF
65+
fi
66+
67+
curl --fail -X PUT \
68+
$GITHUB_API_URL \
69+
-H "Authorization: token $GITHUB_TOKEN" \
70+
-H "Content-Type: application/json" \
71+
-H "Accept: application/vnd.github.v3+json" \
72+
-d @"$TMPFILE"

.github/workflows/build-publish-docker-helm.yml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,8 @@ jobs:
2222
uses: ./.github/workflows/ci.yml
2323
with:
2424
push_docker_image: true
25+
secrets:
26+
GO_API_ARTIFACTS_TOKEN: ${{ secrets.GO_API_ARTIFACTS_TOKEN }}
2527

2628
build:
2729
name: Publish Helm

.github/workflows/ci.yml

Lines changed: 31 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,10 @@ on:
66
push_docker_image:
77
type: string # true or false
88
default: "false"
9+
secrets:
10+
GO_API_ARTIFACTS_TOKEN:
11+
description: 'Token to pull/push to go-api-artifacts'
12+
required: true
913
outputs:
1014
docker_image_name:
1115
description: "Only docker image name"
@@ -64,7 +68,7 @@ jobs:
6468
username: ${{ github.actor }}
6569
password: ${{ secrets.GITHUB_TOKEN }}
6670

67-
- name: 🐳 Prepare Docker
71+
- name: Prepare
6872
id: prep
6973
env:
7074
IMAGE_NAME: ghcr.io/${{ github.repository }}
@@ -134,6 +138,32 @@ jobs:
134138
DOCKER_IMAGE: ${{ steps.prep.outputs.tagged_image }}
135139
run: docker compose run --rm serve ./manage.py test --keepdb -v 2 --pattern="test_fake.py"
136140

141+
# NOTE: Schema generation requires a valid database. Therefore, this step must run after "Run Django migrations."
142+
- name: 🕮 Generate latest openapi schema
143+
env:
144+
DOCKER_IMAGE: ${{ steps.prep.outputs.tagged_image }}
145+
DJANGO_DB_NAME: "test_test"
146+
run: |
147+
docker compose run --rm serve ./manage.py spectacular --file openapi-schema-latest.yaml
148+
149+
- name: 🕮 Upload latest openapi schema to IFRCGo/go-api-artifacts
150+
env:
151+
# Github
152+
# NOTE: `GO_API_ARTIFACTS_TOKEN` has an expiry date. Please follow `docs/go-artifacts.md` for token rotation.
153+
GITHUB_TOKEN: ${{ secrets.GO_API_ARTIFACTS_TOKEN }}
154+
GITHUB_OWNER: IFRCGo
155+
GITHUB_REPO: go-api-artifacts
156+
GITHUB_BRANCH: main
157+
# File
158+
SOURCE_FILE_PATH: openapi-schema-latest.yaml
159+
run: |
160+
COMMIT_SHA="${{ github.event.pull_request.head.sha || github.sha }}"
161+
export DEST_FILE_PATH="generated/$COMMIT_SHA/openapi-schema.yaml"
162+
export COMMIT_MESSAGE="Add openapi-schema of $GITHUB_REF_NAME@$COMMIT_SHA"
163+
.github/go-api-artifacts-add-or-replace.sh
164+
DEST_FILE_URL="https://github.com/$GITHUB_OWNER/$GITHUB_REPO/blob/$GITHUB_BRANCH/$DEST_FILE_PATH"
165+
echo "::notice::Uploaded latest openapi-schema to: ${DEST_FILE_URL}"
166+
137167
- name: 🤞 Run Test 🧪
138168
env:
139169
DOCKER_IMAGE: ${{ steps.prep.outputs.tagged_image }}

docs/go-artifacts.md

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
# Artifact Repository
2+
3+
https://github.com/IFRCGo/go-api-artifacts
4+
5+
## What is this?
6+
7+
This is a **GitHub repository** used to store and manage **build files**, **generated files**, or **automation results** made by other tools or workflows (like GitHub Actions or CI/CD pipelines).
8+
9+
- Why use this?
10+
- Keeps the main source code clean by storing big or auto-made files separately.
11+
- Other projects (like go-web-app) can download the required files as required.
12+
13+
## Creating a PAT Token
14+
15+
A GitHub PAT (Personal Access Token) is needed to upload files to the `go-api-artifacts` repository.
16+
This token is stored in the GitHub Actions secret: `secrets.GO_API_ARTIFACTS_TOKEN`.
17+
For more details, see [ci.yml](/.github/workflows/ci.yml).
18+
19+
> [!Important]
20+
> Currently, the PAT token is created using the https://github.com/ifrcgo-dev account.
21+
22+
### How to create a new token:
23+
24+
- **Go to**: [GitHub Personal Access Tokens](https://github.com/settings/personal-access-tokens)
25+
- **Token name**: `ifrc-go-artifacts-xyz` (replace `xyz` as needed)
26+
- **Resource owner**: `IFRCGO`
27+
- **Expiration**: No expiration
28+
- **Repository access**: Only select repositories
29+
- Select: `IFRCGo/go-api-artifacts`
30+
- **Permissions**:
31+
- **Repository permissions**:
32+
- **Contents**: Read and write

main/settings.py

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -817,6 +817,13 @@ def log_render_extra_context(record):
817817
"SERVE_INCLUDE_SCHEMA": False,
818818
"ENUM_ADD_EXPLICIT_BLANK_NULL_CHOICE": False,
819819
"ENUM_NAME_OVERRIDES": {},
820+
# "DEFAULT_GENERATOR_CLASS": "main.utils.OrderedSchemaGenerator",
821+
"SORT_OPERATION_PARAMETERS": False,
822+
"SORT_OPERATIONS": False,
823+
"POSTPROCESSING_HOOKS": [
824+
"drf_spectacular.hooks.postprocess_schema_enums",
825+
"main.utils.postprocess_schema",
826+
],
820827
}
821828

822829
# A character which is rarely used in strings – for separator:

main/utils.py

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -184,3 +184,16 @@ def logger_context(data):
184184
Check main.settings.py::log_render_extra_context
185185
"""
186186
return {"context": data}
187+
188+
189+
def sort_dict_recursively(d):
190+
if isinstance(d, dict):
191+
return {k: sort_dict_recursively(d[k]) for k in sorted(d)}
192+
elif isinstance(d, list):
193+
return [sort_dict_recursively(item) for item in d]
194+
else:
195+
return d
196+
197+
198+
def postprocess_schema(result, **kwargs):
199+
return sort_dict_recursively(result)

0 commit comments

Comments
 (0)