Skip to content

Commit

Permalink
ci: When models change, dump gql schema (#1671)
Browse files Browse the repository at this point in the history
Co-authored-by: Kyujin Cho <kyujin.cho@lablup.com>
  • Loading branch information
Yaminyam and kyujin-cho committed Mar 29, 2024
1 parent d7d7b76 commit 50fe965
Show file tree
Hide file tree
Showing 10 changed files with 86,390 additions and 13 deletions.
76 changes: 76 additions & 0 deletions .github/workflows/update-api-schema.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
name: graphql

on:
pull_request:
paths:
- 'src/ai/backend/manager/models/**'

jobs:
graphql-updated:
runs-on: ubuntu-latest
steps:
- name: Calculate the fetch depth
run: |
if [ "$GITHUB_EVENT_NAME" == "pull_request" ]; then
echo "GIT_FETCH_DEPTH=$(( ${{ github.event.pull_request.commits }} + 1 ))" >> "${GITHUB_ENV}"
else
echo "GIT_FETCH_DEPTH=2" >> "${GITHUB_ENV}"
fi
- uses: actions/checkout@v4
with:
fetch-depth: ${{ env.GIT_FETCH_DEPTH }}
- name: Extract Python version from pants.toml
run: |
PYTHON_VERSION=$(grep -m 1 -oP '(?<=CPython==)([^"]+)' pants.toml)
echo "PANTS_CONFIG_FILES=pants.ci.toml" >> $GITHUB_ENV
echo "PROJECT_PYTHON_VERSION=$PYTHON_VERSION" >> $GITHUB_ENV
- name: Set up Python as Runtime
uses: actions/setup-python@v4
with:
python-version: ${{ env.PROJECT_PYTHON_VERSION }}
- name: Set up remote cache backend (if applicable)
run: |
echo "PANTS_REMOTE_STORE_ADDRESS=${REMOTE_CACHE_BACKEND_ENDPOINT}" >> $GITHUB_ENV
echo "PANTS_REMOTE_CACHE_READ=true" >> $GITHUB_ENV
echo "PANTS_REMOTE_CACHE_WRITE=true" >> $GITHUB_ENV
echo "PANTS_REMOTE_INSTANCE_NAME=main" >> $GITHUB_ENV
env:
REMOTE_CACHE_BACKEND_ENDPOINT: ${{ secrets.PANTS_REMOTE_CACHE_ENDPOINT_ARC }}
if: ${{ env.REMOTE_CACHE_BACKEND_ENDPOINT != '' }}
- name: Bootstrap Pants
uses: ./actions/init-pants
# See: github.com/pantsbuild/actions/tree/main/init-pants/
# ref) https://github.com/pantsbuild/example-python/blob/main/.github/workflows/pants.yaml#L30-L49
with:
named-caches-hash: ${{ hashFiles('python*.lock', 'tools/*.lock') }}
cache-lmdb-store: 'true'
- name: Pants export
run: pants export --resolve=python-default
- name: Create GraphQL schema dump
run: |
./backend.ai mgr api dump-gql-schema --output src/ai/backend/manager/api/schema.graphql
- name: Extract the author information
id: get_author_info
run: |
author_name=$(git show -q --pretty='format:%an')
author_email=$(git show -q --pretty='format:%ae')
echo "Retrieved author information: $author_name <$author_email>"
echo "name=$author_name" >> $GITHUB_OUTPUT
echo "email=$author_email" >> $GITHUB_OUTPUT
- name: Make commit message for changing change log file
uses: stefanzweifel/git-auto-commit-action@v4
with:
commit_author: ${{ steps.get_author_info.outputs.name }} <${{ steps.get_author_info.outputs.email }}>
commit_message: 'chore: update GraphQL schema dump'

graphql-inspector:
needs: graphql-updated
name: Check Schema
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: kamilkisiela/graphql-inspector@release-1689086705050
with:
schema: 'main:src/ai/backend/manager/api/schema.graphql'
rules: |
custom-rule.js
78 changes: 78 additions & 0 deletions custom-rule.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
module.exports = (props) => {
const { changes, newSchema, oldSchema } = props;
return changes.map((change) => {
// console.log(change);
if (
[
"FIELD_DEPRECATION_REASON_ADDED",
"FIELD_DEPRECATION_REASON_CHANGED",
].includes(change.type) &&
change.criticality.level !== "BREAKING"
) {
const newReason =
change.meta.addedDeprecationReason || change.meta.newDeprecationReason;
const regex = /Deprecated since (\d{2}\.\d{2})/;
if (!newReason.match(regex)) {
change.criticality.level = "BREAKING";
change.criticality.reason =
'Deprecation reason must include a version number in the format "Deprecated since XX.XX"';
change.message =
'Deprecation reason must include a version number in the format "Deprecated since XX.XX", ' +
change.message;
}
} else if (
["FIELD_ADDED", "INPUT_FIELD_ADDED"].includes(change.type) &&
change.criticality.level !== "BREAKING"
) {
const [typeName, fieldName] = change.path.split(".");
const description = newSchema.getTypeMap()[typeName].getFields()[
fieldName
].astNode.description?.value;
if (!description || !description.match(/since (\d{2}\.\d{2})/)) {
change.criticality.level = "BREAKING";
change.criticality.reason =
'New fields must include a description with a version number in the format "since XX.XX"';
change.message =
'New fields must include a description with a version number in the format "XX.XX", ' +
change.message;
}
} else if (
change.type === "TYPE_ADDED" &&
change.criticality.level !== "BREAKING"
) {
const typeName = change.path.split(".")[0];
const description =
newSchema.getTypeMap()[typeName].astNode.description?.value;
if (!description || !description.match(/since (\d{2}\.\d{2})/)) {
change.criticality.level = "BREAKING";
change.criticality.reason =
'New types must include a description with a version number in the format "since XX.XX"';
change.message =
'New types must include a description with a version number in the format "XX.XX", ' +
change.message;
}
} else if (
["FIELD_ARGUMENT_ADDED", "FIELD_ARGUMENT_DESCRIPTION_CHANGED"].includes(
change.type
) &&
change.criticality.level !== "BREAKING"
) {
const [type, filedName, argumentName] = change.path.split(".");
const field = newSchema.getTypeMap()[type].getFields()[filedName];
const description = field.args.find(
(arg) => arg.name === argumentName
)?.description;

if (!description || !description.match(/since (\d{2}\.\d{2})/)) {
change.criticality.level = "BREAKING";
change.criticality.reason =
'New arguments must include a description with a version number in the format "since XX.XX"';
change.message =
'New arguments must include a description with a version number in the format "XX.XX", ' +
change.message;
}
}
return change;
});
};
// TODO: update the rule to check for the version number in the description.
20 changes: 15 additions & 5 deletions docs/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ $ pip install -U -r requirements.txt

## Building API Reference JSON file
```console
$ ./py -m ai.backend.manager.openapi docs/manager/rest-reference/openapi.json
$ ./backend.ai mgr api dump-openapi --output docs/manager/rest-reference/openapi.json
```
This script must be executed on behalf of the virtual environment managed by pants, not by the venv for the sphinx.
Generated OpenAPI JSON file will be located at under `manager/rest-reference/openapi.json`.
Expand Down Expand Up @@ -148,6 +148,19 @@ To preview the full documentation including the REST API reference seamlessly, y
```
2. Executing the command above inside `docs` folder will serve the documentation page on port 8000 (http://localhost:8000).


### Interactive REST API browser

You may use [GraphiQL](https://github.com/graphql/graphiql/tree/main/packages/graphiql#graphiql)
to interact and inspect the Backend.AI Manager's GraphQL API.

1. Ensure you have the access to the manager server.
The manager's *etcd* configuration should say `config/api/allow-openapi-schema-introspection` is true.
2. Run `backend.ai proxy` command of the client SDK. Depending on your setup, adjust `--bind` and `--port` options.
Use the client SDK version 21.03.7+ or 20.09.9+ at least to avoid unexpected CORS issues.
3. From your web browser, avigate to `/spec/openapi` under proxy server set up at step 2.
Enjoy auto-completion and schema introspection of Backend.AI admin API!

### Interactive GraphQL browser

You may use [GraphiQL](https://github.com/graphql/graphiql/tree/main/packages/graphiql#graphiql)
Expand All @@ -157,10 +170,7 @@ to interact and inspect the Backend.AI Manager's GraphQL API.
The manager's *etcd* configuration should say `config/api/allow-graphql-schema-introspection` is true.
2. Run `backend.ai proxy` command of the client SDK. Depending on your setup, adjust `--bind` and `--port` options.
Use the client SDK version 21.03.7+ or 20.09.9+ at least to avoid unexpected CORS issues.
3. Copy `index.html` from https://gist.github.com/achimnol/dc9996aeffc7cf15e96478e635eb0699
4. Replace `"<proxy-address>"` with the real address (host:port) of the proxy, which can be accessed from your browser as well.
5. Run `python -m http.server` command in the directory where `index.html` is located.
6. Open the page served by the HTTP server in the previous step in your web browser.
3. From your web browser, avigate to `/spec/graphiql` under proxy server set up at step 2.
Enjoy auto-completion and schema introspection of Backend.AI admin API!


Expand Down

0 comments on commit 50fe965

Please sign in to comment.