-
Notifications
You must be signed in to change notification settings - Fork 189
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add github action to sync api docs to the user docs site
- Loading branch information
1 parent
cc2499b
commit 16dca25
Showing
11 changed files
with
363 additions
and
0 deletions.
There are no files selected for viewing
Validating CODEOWNERS rules …
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,38 @@ | ||
name: Synchronize API Docs | ||
|
||
on: | ||
workflow_dispatch: | ||
schedule: | ||
- cron: '0 * * * 1-5' # Mon-Fri every hour | ||
push: | ||
branches: [chore/docs-action] | ||
|
||
jobs: | ||
build: | ||
name: synchronize-api-docs | ||
runs-on: ubuntu-latest | ||
steps: | ||
- run: | | ||
gh auth setup-git | ||
git config --global user.email "[email protected]" | ||
git config --global user.name "$GITHUB_ACTOR" | ||
gh repo clone snyk/user-docs user-docs -- --depth=1 --quiet --branch chore/docs-action | ||
cd ./user-docs | ||
OUTPUT=$(cd tools/api-docs-generator && go mod tidy && go run . config.yml ../../) | ||
if [[ $(git status --porcelain) ]]; then | ||
echo "Documentation changes detected" | ||
git --no-pager diff --name-only | ||
git add . | ||
git commit -m "docs: synchronizing api spec with user-docs" | ||
git checkout -b docs/automatic-api-docs-update | ||
git push --force origin docs/automatic-api-docs-update | ||
if [[ ! $(gh pr view docs/automatic-api-docs-update 2>&1 | grep -q "no open pull requests";) ]]; then | ||
echo "Creating PR" | ||
gh pr create --title="Generate API docs from spec" --body="Automatic PR controlled by GitHub Action\n$OUTPUT" --head docs/automatic-api-docs-update | ||
fi | ||
echo "PR exists, pushed changes to it." | ||
else | ||
echo "No documentation changes detected, exiting." | ||
fi | ||
env: | ||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
test: | ||
go test ./... | ||
|
||
run: | ||
go run . config.yml ../.. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,30 @@ | ||
package main | ||
|
||
import ( | ||
"gopkg.in/yaml.v3" | ||
"os" | ||
) | ||
|
||
type config struct { | ||
Fetcher struct { | ||
Source string `yaml:"source"` | ||
Destination string `yaml:"destination"` | ||
} `yaml:"fetcher"` | ||
Specs []struct { | ||
Path string `yaml:"path"` | ||
Suffix string `yaml:"suffix,omitempty"` | ||
DocsHint string `yaml:"docsHint,omitempty"` | ||
} `yaml:"specs"` | ||
Output struct { | ||
APIReferencePath string `yaml:"apiReferencePath"` | ||
} `yaml:"output"` | ||
} | ||
|
||
func parseConfigFile(filename string) (config, error) { | ||
cfg := config{} | ||
file, err := os.Open(filename) | ||
if err != nil { | ||
return cfg, err | ||
} | ||
return cfg, yaml.NewDecoder(file).Decode(&cfg) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,12 @@ | ||
fetcher: | ||
source: https://api.snyk.io/rest/openapi | ||
destination: .gitbook/assets/rest-spec.json | ||
specs: | ||
- path: .gitbook/assets/spec.yaml | ||
suffix: " (v1)" | ||
docsHint: This document uses the v1 API. For more details, see the [v1 API](../v1-api). | ||
- path: .gitbook/assets/rest-spec.json | ||
docsHint: This document uses the REST API. For more details, see the [Authentication for API](../authentication-for-api/) page. | ||
|
||
output: | ||
apiReferencePath: snyk-api/reference |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,18 @@ | ||
module github.com/snyk/user-docs/tools/api-docs-generator | ||
|
||
go 1.22.1 | ||
|
||
require ( | ||
github.com/getkin/kin-openapi v0.124.0 | ||
gopkg.in/yaml.v3 v3.0.1 | ||
) | ||
|
||
require ( | ||
github.com/go-openapi/jsonpointer v0.20.2 // indirect | ||
github.com/go-openapi/swag v0.22.8 // indirect | ||
github.com/invopop/yaml v0.2.0 // indirect | ||
github.com/josharian/intern v1.0.0 // indirect | ||
github.com/mailru/easyjson v0.7.7 // indirect | ||
github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826 // indirect | ||
github.com/perimeterx/marshmallow v1.1.5 // indirect | ||
) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,38 @@ | ||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= | ||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= | ||
github.com/getkin/kin-openapi v0.124.0 h1:VSFNMB9C9rTKBnQ/fpyDU8ytMTr4dWI9QovSKj9kz/M= | ||
github.com/getkin/kin-openapi v0.124.0/go.mod h1:wb1aSZA/iWmorQP9KTAS/phLj/t17B5jT7+fS8ed9NM= | ||
github.com/go-openapi/jsonpointer v0.20.2 h1:mQc3nmndL8ZBzStEo3JYF8wzmeWffDH4VbXz58sAx6Q= | ||
github.com/go-openapi/jsonpointer v0.20.2/go.mod h1:bHen+N0u1KEO3YlmqOjTT9Adn1RfD91Ar825/PuiRVs= | ||
github.com/go-openapi/swag v0.22.8 h1:/9RjDSQ0vbFR+NyjGMkFTsA1IA0fmhKSThmfGZjicbw= | ||
github.com/go-openapi/swag v0.22.8/go.mod h1:6QT22icPLEqAM/z/TChgb4WAveCHF92+2gF0CNjHpPI= | ||
github.com/go-test/deep v1.0.8 h1:TDsG77qcSprGbC6vTN8OuXp5g+J+b5Pcguhf7Zt61VM= | ||
github.com/go-test/deep v1.0.8/go.mod h1:5C2ZWiW0ErCdrYzpqxLbTX7MG14M9iiw8DgHncVwcsE= | ||
github.com/invopop/yaml v0.2.0 h1:7zky/qH+O0DwAyoobXUqvVBwgBFRxKoQ/3FjcVpjTMY= | ||
github.com/invopop/yaml v0.2.0/go.mod h1:2XuRLgs/ouIrW3XNzuNj7J3Nvu/Dig5MXvbCEdiBN3Q= | ||
github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY= | ||
github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y= | ||
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= | ||
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= | ||
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= | ||
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= | ||
github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0= | ||
github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc= | ||
github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826 h1:RWengNIwukTxcDr9M+97sNutRR1RKhG96O6jWumTTnw= | ||
github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826/go.mod h1:TaXosZuwdSHYgviHp1DAtfrULt5eUgsSMsZf+YrPgl8= | ||
github.com/perimeterx/marshmallow v1.1.5 h1:a2LALqQ1BlHM8PZblsDdidgv1mWi1DgC2UmX50IvK2s= | ||
github.com/perimeterx/marshmallow v1.1.5/go.mod h1:dsXbUu8CRzfYP5a87xpp0xq9S3u0Vchtcl8we9tYaXw= | ||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= | ||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= | ||
github.com/rogpeppe/go-internal v1.12.0 h1:exVL4IDcn6na9z1rAb56Vxr+CgyK3nn3O+epU5NdKM8= | ||
github.com/rogpeppe/go-internal v1.12.0/go.mod h1:E+RYuTGaKKdloAfM02xzb0FW3Paa99yedzYV+kq4uf4= | ||
github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk= | ||
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= | ||
github.com/ugorji/go/codec v1.2.7 h1:YPXUKf7fYbp/y8xloBqZOw2qaVggbfwMlI8WM3wZUJ0= | ||
github.com/ugorji/go/codec v1.2.7/go.mod h1:WGN1fab3R1fzQlVQTkfxVtIBhWDRqOviHU95kRgeqEY= | ||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= | ||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= | ||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= | ||
gopkg.in/yaml.v3 v3.0.0/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= | ||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= | ||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,25 @@ | ||
package main | ||
|
||
import ( | ||
"log" | ||
"os" | ||
) | ||
|
||
func main() { | ||
if len(os.Args) != 3 { | ||
log.Fatal("usage: api-docs <config-file> <docs-dir>") | ||
} | ||
cfg, err := parseConfigFile(os.Args[1]) | ||
if err != nil { | ||
log.Fatal(err) | ||
} | ||
docsDirectory := os.Args[2] | ||
|
||
err = fetchSpec(cfg, docsDirectory) | ||
|
||
err = generateReferenceDocs(cfg, docsDirectory) | ||
if err != nil { | ||
log.Fatal(err) | ||
} | ||
|
||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,101 @@ | ||
package main | ||
|
||
import ( | ||
"fmt" | ||
"github.com/getkin/kin-openapi/openapi3" | ||
"os" | ||
"path" | ||
"sort" | ||
"strings" | ||
) | ||
|
||
type operationPath struct { | ||
operation *openapi3.Operation | ||
pathItem *openapi3.PathItem | ||
pathUrl string | ||
specPath string | ||
method string | ||
docsHint string | ||
} | ||
|
||
func generateReferenceDocs(config config, docsPath string) error { | ||
aggregatedDocs := map[string][]operationPath{} | ||
|
||
assetPathBase := path.Join(docsPath, "docs") | ||
|
||
for _, spec := range config.Specs { | ||
loader := openapi3.NewLoader() | ||
doc, err := loader.LoadFromFile(path.Join(assetPathBase, spec.Path)) | ||
if err != nil { | ||
return err | ||
} | ||
for pathUrl, pathItem := range doc.Paths.Map() { | ||
for method, operation := range pathItem.Operations() { | ||
for _, tag := range operation.Tags { | ||
tag = tag + spec.Suffix | ||
aggregatedDocs[tag] = append(aggregatedDocs[tag], operationPath{ | ||
operation: operation, | ||
pathItem: pathItem, | ||
pathUrl: pathUrl, | ||
specPath: spec.Path, | ||
method: method, | ||
docsHint: spec.DocsHint, | ||
}) | ||
} | ||
} | ||
} | ||
} | ||
|
||
var summary []string | ||
for label, operation := range aggregatedDocs { | ||
if label == "OpenAPI" { | ||
continue | ||
} | ||
filePath := path.Join(docsPath, "docs/", config.Output.APIReferencePath, labelToFileName(label)) | ||
docsFile, err := os.Create(filePath) | ||
if err != nil { | ||
return err | ||
} | ||
summary = append(summary, fmt.Sprintf("* [%s](%s)\n", label, path.Join(config.Output.APIReferencePath, labelToFileName(label)))) | ||
|
||
fmt.Fprintf(docsFile, `# %s | ||
{%% hint style="info" %%} | ||
%s | ||
{%% endhint %%}`, label, operation[0].docsHint) | ||
|
||
// sort for stability | ||
sort.Slice(operation, func(i, j int) bool { | ||
return operation[i].pathUrl+operation[i].method > operation[j].pathUrl+operation[j].method | ||
}) | ||
|
||
for _, op := range operation { | ||
_, err = fmt.Fprintf(docsFile, | ||
` | ||
{%% swagger src="../../%s" path="%s" method="%s" %%} | ||
[spec.yaml](../../%s) | ||
{%% endswagger %%} | ||
`, | ||
op.specPath, | ||
op.pathUrl, | ||
strings.ToLower(op.method), | ||
op.specPath, | ||
) | ||
if err != nil { | ||
return err | ||
} | ||
} | ||
} | ||
sort.Strings(summary) | ||
fmt.Printf("%s", strings.Join(summary, "")) | ||
|
||
return nil | ||
} | ||
|
||
func labelToFileName(label string) string { | ||
replacements := []string{"(", ")"} | ||
for _, replacement := range replacements { | ||
label = strings.ReplaceAll(label, replacement, "") | ||
} | ||
return strings.ToLower(strings.ReplaceAll(label, " ", "-")) + ".md" | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,66 @@ | ||
package main | ||
|
||
import ( | ||
"bytes" | ||
"encoding/json" | ||
"io" | ||
"net/http" | ||
"net/url" | ||
"os" | ||
"path" | ||
"sort" | ||
"strings" | ||
) | ||
|
||
func fetchSpec(cfg config, directory string) error { | ||
resp, err := http.Get(cfg.Fetcher.Source) | ||
if err != nil { | ||
return err | ||
} | ||
|
||
var versions []string | ||
|
||
if err = json.NewDecoder(resp.Body).Decode(&versions); err != nil { | ||
return err | ||
} | ||
if err = resp.Body.Close(); err != nil { | ||
return err | ||
} | ||
|
||
gaVersion := getLatestGAVersion(versions) | ||
|
||
specPath, err := url.JoinPath(cfg.Fetcher.Source, gaVersion) | ||
if err != nil { | ||
return err | ||
} | ||
|
||
resp, err = http.Get(specPath) | ||
if err != nil { | ||
return err | ||
} | ||
defer resp.Body.Close() | ||
|
||
jsonSpec, err := io.ReadAll(resp.Body) | ||
if err != nil { | ||
return err | ||
} | ||
|
||
formattedSpec := bytes.NewBufferString("") | ||
err = json.Indent(formattedSpec, jsonSpec, "", " ") | ||
if err != nil { | ||
return err | ||
} | ||
|
||
return os.WriteFile(path.Join(directory, "docs", cfg.Fetcher.Destination), formattedSpec.Bytes(), 0644) | ||
} | ||
|
||
func getLatestGAVersion(versions []string) string { | ||
gaVersions := []string{} | ||
for _, version := range versions { | ||
if !strings.Contains(version, "~") { | ||
gaVersions = append(gaVersions, version) | ||
} | ||
} | ||
sort.Strings(gaVersions) | ||
return gaVersions[len(gaVersions)-1] | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,29 @@ | ||
package main | ||
|
||
import "testing" | ||
|
||
func Test_getLatestGAVersion(t *testing.T) { | ||
tests := []struct { | ||
name string | ||
versions []string | ||
want string | ||
}{ | ||
{ | ||
name: "gets the latest version", | ||
versions: []string{"2024-03-12~experimental", "2024-03-12~beta", "2024-03-12", "2024-03-15~experimental", "2024-03-15~beta", "2024-03-15", "2024-04-11~experimental", "2024-04-11~beta", "2024-04-11", "2024-04-22~experimental", "2024-04-22~beta", "2024-04-22", "2024-04-25~experimental", "2024-04-25~beta", "2024-04-25", "2024-04-29~experimental", "2024-04-29~beta", "2024-04-29", "2024-05-08~experimental", "2024-05-08~beta", "2024-05-08"}, | ||
want: "2024-05-08", | ||
}, | ||
{ | ||
name: "gets the latest GA version", | ||
versions: []string{"2024-03-12~experimental", "2024-03-12~beta", "2024-03-12", "2024-03-15~experimental", "2024-03-15~beta", "2024-03-15", "2024-04-11~experimental", "2024-04-11~beta", "2024-04-11", "2024-04-22~experimental", "2024-04-22~beta", "2024-04-22", "2024-04-25~experimental", "2024-04-25~beta", "2024-04-25", "2024-04-29~experimental", "2024-04-29~beta", "2024-04-29", "2024-05-08~experimental", "2024-05-08~beta"}, | ||
want: "2024-04-29", | ||
}, | ||
} | ||
for _, tt := range tests { | ||
t.Run(tt.name, func(t *testing.T) { | ||
if got := getLatestGAVersion(tt.versions); got != tt.want { | ||
t.Errorf("getLatestGAVersion() = %v, want %v", got, tt.want) | ||
} | ||
}) | ||
} | ||
} |