diff --git a/.github/workflows/generate.yaml b/.github/workflows/generate.yaml deleted file mode 100644 index b1066d8..0000000 --- a/.github/workflows/generate.yaml +++ /dev/null @@ -1,49 +0,0 @@ -name: generate - -on: - pull_request: - push: - tags: - - 'v[0-9].*' - -jobs: - push: - runs-on: ubuntu-latest - - steps: - - name: Checkout - uses: actions/checkout@master - - - name: Install Python - uses: actions/setup-python@v1 - with: - python-version: '3.7' - architecture: 'x64' - - - name: Install apigentools - run: pip install apigentools==1.1.0 # NB: If changes are needed, remeber to update the docker image accordingly at config/config.yaml - - - name: Validate specfile - run: apigentools validate - - - name: Generate clients - env: - APIGENTOOLS_GIT_VIA_HTTPS: true - APIGENTOOLS_GIT_VIA_HTTPS_OAUTH_TOKEN: ${{ secrets.GH_PUSH_TOKEN }} - run: apigentools generate --clone-repo - - - name: Align permissions of generated code - run: | - sudo chown -R ${USER}:${USER} generated - - - name: Push client repos - if: github.event_name == 'push' - env: - APIGENTOOLS_GIT_VIA_HTTPS: true - APIGENTOOLS_GIT_VIA_HTTPS_OAUTH_TOKEN: ${{ secrets.GH_PUSH_TOKEN }} - run: | - rm -rf generated/iot-client-js/docs/ - rm -rf generated/iot-client-js/test/ - git config --global user.email "arduinobot@arduino.cc" - git config --global user.name "ArduinoBot" - apigentools push diff --git a/.github/workflows/release.yaml b/.github/workflows/release.yaml index 92144d5..60dc1c6 100644 --- a/.github/workflows/release.yaml +++ b/.github/workflows/release.yaml @@ -1,14 +1,170 @@ name: release -on: workflow_dispatch - +on: + workflow_dispatch: + pull_request: + push: + tags: + - 'v[0-9].*' + env: GENERATOR_VERSION: 'v7.9.0' + INPUT_OPENAPI_FILE: 'openapi/openapi.yaml' + GO_VERSION: '1.23' + RELEASE_BRANCH: '' + LATEST_COMMIT: '' jobs: push: runs-on: ubuntu-latest steps: + - name: Env setup + run: | + git config --global user.email "github-actions-noreply@arduino.cc" + git config --global user.name "github-actions[bot]" + echo "RELEASE_BRANCH="release-"$(date +"%Y%m%d%H%M%S")" >> ${GITHUB_ENV} + + - name: Install Go + uses: actions/setup-go@v5 + with: + go-version: ${{ env.GO_VERSION }} + - name: Checkout uses: actions/checkout@v4 + + - name: Get latest commit + run: | + echo "LATEST_COMMIT=$(git log -1 | head -n1)" >> ${GITHUB_ENV} + + - name: Generate openapi yaml definition + run: | + cd openapi/compose && go run compose.go && cd .. && cd .. + + - name: Checkout generator templates + uses: actions/checkout@v4 + with: + repository: OpenAPITools/openapi-generator + path: official-generator-templates + ref: ${{ env.GENERATOR_VERSION }} + token: ${{ secrets.GH_PUSH_TOKEN }} + sparse-checkout: | + .github + modules/openapi-generator/src/main/resources + + - name: Move required templates + run: | + mkdir -p generated + mv official-generator-templates/modules/openapi-generator/src/main/resources/go official-generator-templates/ + mv official-generator-templates/modules/openapi-generator/src/main/resources/python official-generator-templates/ + mv official-generator-templates/modules/openapi-generator/src/main/resources/Javascript official-generator-templates/ + mv official-generator-templates/modules/openapi-generator/src/main/resources/htmlDocs2 official-generator-templates/ + + - name: Apply patches to templates + run: | + cd official-generator-templates + for f in ../template-patches/*.patch; do git apply ../template-patches/${f}; done; + cd .. + + - name: Checkout GO client arduino repo + uses: actions/checkout@v4 + with: + repository: arduino/iot-client-go + token: ${{ secrets.GH_PUSH_TOKEN }} + path: generated/go + - run: | + cd generated/go && git checkout -b ${{ env.RELEASE_BRANCH }} + + - name: Checkout PYTHON client arduino repo + uses: actions/checkout@v4 + with: + repository: arduino/iot-client-py + token: ${{ secrets.GH_PUSH_TOKEN }} + path: generated/python + - run: | + cd generated/python && git checkout -b ${{ env.RELEASE_BRANCH }} + + - name: Checkout JAVASCRIPT client arduino repo + uses: actions/checkout@v4 + with: + repository: arduino/iot-client-js + token: ${{ secrets.GH_PUSH_TOKEN }} + path: generated/javascript + - run: | + cd generated/javascript && git checkout -b ${{ env.RELEASE_BRANCH }} + + - name: Checkout DOCS client arduino repo + uses: actions/checkout@v4 + with: + repository: arduino/iot-client-docs + token: ${{ secrets.GH_PUSH_TOKEN }} + path: generated/html2 + - run: | + cd generated/html2 && git checkout -b ${{ env.RELEASE_BRANCH }} + + - name: Generate go client + uses: openapi-generators/openapitools-generator-action@v1 + with: + generator: go + generator-tag: ${{ env.GENERATOR_VERSION }} + config-file: config/languages/go_v2.json + openapi-file: ${{ env.INPUT_OPENAPI_FILE }} + template-dir: official-generator-templates/go/ + command-args: -o generated/go + + - name: Generate python client + uses: openapi-generators/openapitools-generator-action@v1 + with: + generator: python + generator-tag: ${{ env.GENERATOR_VERSION }} + config-file: config/languages/python_v2.json + openapi-file: ${{ env.INPUT_OPENAPI_FILE }} + template-dir: official-generator-templates/python/ + command-args: -o generated/python + + - name: Generate Javascript client + uses: openapi-generators/openapitools-generator-action@v1 + with: + generator: javascript + generator-tag: ${{ env.GENERATOR_VERSION }} + config-file: config/languages/javascript_v2.json + openapi-file: ${{ env.INPUT_OPENAPI_FILE }} + template-dir: official-generator-templates/Javascript/ + command-args: -o generated/javascript + + - name: Generate Docs + uses: openapi-generators/openapitools-generator-action@v1 + with: + generator: html2 + generator-tag: ${{ env.GENERATOR_VERSION }} + config-file: config/languages/html2_v2.json + openapi-file: ${{ env.INPUT_OPENAPI_FILE }} + template-dir: official-generator-templates/htmlDocs2/ + command-args: -o generated/html2 + + - name: Apply downstream templates + run: | + cp downstream-templates/go/README.md generated/go/README.md + cp downstream-templates/javascript/README.md generated/javascript/README.md + cp downstream-templates/python/README.md generated/python/README.md + cp downstream-templates/python/setup.py generated/python/setup.py + cp downstream-templates/python/utils.py generated/python/iot_api_client/utils.py + + - name: Copy shared assets + run: | + cp -R assets/shared/* generated/go/ + cp -R assets/shared/* generated/python + cp -R assets/shared/* generated/javascript + cp -R assets/shared/* generated/html2 + + - name: Open branches on external client REPOs + run: | + cd generated + for f in $(ls -d */); do cd ${f} && git add . && git commit -m "Release ${{ env.RELEASE_BRANCH }}" && git push -u origin ${{ env.RELEASE_BRANCH }} && cd ..; done; + + - name: Open PRs on client REPOs + run: | + cd generated + for f in $(ls -d */); do cd ${f} && gh pr create -B master -H ${{ env.RELEASE_BRANCH }} --title 'Regenerate clients - ${{ env.LATEST_COMMIT }}' --body 'Created by clients-iot-api clients release automation job' && cd ..; done; + env: + GH_TOKEN: ${{ secrets.GH_PUSH_TOKEN }} diff --git a/.gitignore b/.gitignore index c9d8084..9884e68 100644 --- a/.gitignore +++ b/.gitignore @@ -1,7 +1,8 @@ !generated generated/* !generated/.gitkeep -spec/*/full_spec.yaml !templates templates/* -!templates/.gitkeep \ No newline at end of file +!templates/.gitkeep +openapi/openapi.yaml +official-generator-templates diff --git a/README.md b/README.md index 1f630a0..fab3a0e 100644 --- a/README.md +++ b/README.md @@ -4,13 +4,7 @@ This repo contains the informations and the tools needed to automatically generate API clients for the `iot-api` service in [any language][0] supported by OpenAPI generator. -The OpenAPI generator is orchestrated by a Python tool called [apigentools][1] -that let us keep the spec files and the configuration bits in one single repo -(this one) without duplicating the boilerplate on each git repo hosting the -actual clients. - -There is a [blog post](https://blog.arduino.cc/2020/03/05/how-to-deal-with-api-clients-the-lazy-way-from-code-generation-to-release-management/) -describing the system architecture. +The OpenAPI generator is orchestrated by git workflow using openapi-generator official images. ## [IMPORTANT] Client generation process @@ -39,14 +33,6 @@ depending on the programming language). The operations are detailed in the following paragraphs. -### Requirements - -To be able to run the workflow locally in a developmnent environment, you'll -need the following: - -* Python 3.7+ -* Apigentools 1.0+ (`pip install apigentools`) - ### Get an updated version of the API specification In this case the specs are generated by Goa using Swagger and they can be found @@ -64,60 +50,14 @@ easily integrated in a CI, so major updates will be performed by either: Minor updates might be done manually since v3 uses Yaml and the resulting spec is human friendly. -### Validate the Spec - -The generator can validate the content of the spec, this should likely go in a -CI step: - -```sh -apigentools validate -``` - ### Generate the clients -To generate clients and documentation using a template, and apply pathces before -template filling, this is the command to run: - -```sh -apigentools generate -``` - -The previous command is fine for local development but in case the generated code -has to be pushed upstream to each client's repository, run the previous command as -follows: - -```sh - apigentools generate --clone-repo -``` - -### Push generated code to each client's git repository - -`apigentools` has a simple command that can be invoked for each client generated -that will push the resulting code to different repos, using different branches -so the code can be reviewed and merged through a regular PR: - -```sh -apigentools push -``` - -A release process should take from here and shouldn't be part of this workflow. - -### Render upstream templates - -This step is not currently necessary as the `generate` step does it implicitly, -but in case you want to only apply patches to the original (upstream) templates -**before** the generation step, you can run it to check how the pathces will -modify the generated code. The step consists of cloning the openapi-generator -repo, applying one or more patches in the form of patch files to it and copy -the relevant templates in the folder `templates`: - -```sh -apigentools templates -``` - -There are other ways to provide upstream templates other than the currently configured -`openapi-git` that might be useful to speed up development iterations, please refer to -`apigentools` docs for more details. +Update openapi definition in openapi/components folder. +'Release' github workflow will take care of: +* update and merge openapi specifications +* update templates with provided patches +* generate clients +* create PRs with updated code in destination clients repositories ## Customization diff --git a/assets/shared/img/selection_1.png b/assets/shared/img/selection_1.png index edf2e80..81cfd72 100644 Binary files a/assets/shared/img/selection_1.png and b/assets/shared/img/selection_1.png differ diff --git a/assets/shared/img/selection_2.png b/assets/shared/img/selection_2.png index 7d53de6..4f125c1 100644 Binary files a/assets/shared/img/selection_2.png and b/assets/shared/img/selection_2.png differ diff --git a/assets/shared/img/selection_3.png b/assets/shared/img/selection_3.png index e14405f..f9a5038 100644 Binary files a/assets/shared/img/selection_3.png and b/assets/shared/img/selection_3.png differ diff --git a/config/config.yaml b/config/config.yaml deleted file mode 100644 index 1a2b1cd..0000000 --- a/config/config.yaml +++ /dev/null @@ -1,125 +0,0 @@ -container_opts: - image: datadog/apigentools:1.6.6 -languages: - go: - generation: - default: - commands: - - commandline: - - function: openapi_generator_generate - - --type-mappings - - object=interface{} - description: Generate go code using openapi-generator - - commandline: - - cp - - -rfvp - - "{{version_output_dir}}/../../assets/shared/." - - "{{version_output_dir}}" - description: Copy shared assets in the generated folder - templates: - patches: - - template-patches/go-mod.patch - - template-patches/go-sum.patch - source: - type: openapi-git - git_committish: "v7.0.0" # git committish to checkout before extracting the templates - templates_dir: go # directory with templates for this language - system: true - downstream_templates: - downstream-templates/go/README.md: README.md - github_org_name: arduino - github_repo_name: iot-client-go - library_version: '1.3.5' - spec_versions: - - v2 - version_path_template: '' - html2: - generation: - default: - commands: - - commandline: - - function: openapi_generator_generate - description: Generate doc using openapi-generator - templates: - patches: - - template-patches/html2-arduino-css.patch - - template-patches/html2-index.patch - - template-patches/html2-sample-js.patch - source: - type: openapi-git - git_committish: "v7.0.0" # git committish to checkout before extracting the templates - templates_dir: htmlDocs2 # directory with templates for this language - system: true - github_org_name: arduino - github_repo_name: iot-client-docs - library_version: '1.3.5' - spec_versions: - - v2 - version_path_template: '' - javascript: - generation: - default: - commands: - - commandline: - - function: openapi_generator_generate - - --type-mappings - - AnyType=Object - description: Generate js code using openapi-generator - - commandline: - - cp - - -rfvp - - "{{version_output_dir}}/../../assets/shared/." - - "{{version_output_dir}}" - description: Copy shared assets in the generated folder - templates: - patches: - - template-patches/javascript-api-client.patch - source: - type: openapi-git - git_committish: "v7.0.0" # git committish to checkout before extracting the templates - templates_dir: Javascript # directory with templates for this language - system: true - downstream_templates: - downstream-templates/javascript/README.md: README.md - github_org_name: arduino - github_repo_name: iot-client-js - library_version: '1.3.5' - spec_versions: - - v2 - version_path_template: '' - python: - generation: - default: - commands: - - commandline: - - function: openapi_generator_generate - description: Generate py code using openapi-generator - - commandline: - - cp - - -rfvp - - "{{version_output_dir}}/../../assets/shared/." - - "{{version_output_dir}}" - description: Copy shared assets in the generated folder - templates: - source: - type: openapi-git - git_committish: "v7.7.0" # git committish to checkout before extracting the templates - templates_dir: python # directory with templates for this language - system: true - downstream_templates: - downstream-templates/python/README.md: README.md - downstream-templates/python/setup.py: setup.py - downstream-templates/python/utils.py: iot_api_client/utils.py - github_org_name: arduino - github_repo_name: iot-client-py - library_version: '1.3.5' - spec_versions: - - v2 - version_path_template: '' -spec_sections: - v2: - - swagger.yaml - - header.yaml - - shared.yaml -spec_versions: - - v2 diff --git a/config/languages/go_v2.json b/config/languages/go_v2.json index 3637a3c..3cf047a 100644 --- a/config/languages/go_v2.json +++ b/config/languages/go_v2.json @@ -2,7 +2,7 @@ "gitUserId": "arduino", "gitRepoId": "iot-client-go", "isGoSubmodule": true, - "packageName": "v2", - "packageVersion": "2.0.6", + "packageName": "v3", + "packageVersion": "3.0.0", "withGoCodegenComment": true } \ No newline at end of file diff --git a/config/languages/html2_v2.json b/config/languages/html2_v2.json index 06b2232..d48aa92 100644 --- a/config/languages/html2_v2.json +++ b/config/languages/html2_v2.json @@ -1,5 +1,6 @@ { "packageName": "iot", - "packageVersion": "2.0.6", - "usePromises": true + "packageVersion": "3.0.0", + "usePromises": true, + "pythonPackageName": "iot_api_client" } \ No newline at end of file diff --git a/config/languages/javascript_v2.json b/config/languages/javascript_v2.json index 3b7850b..19b1516 100644 --- a/config/languages/javascript_v2.json +++ b/config/languages/javascript_v2.json @@ -1,8 +1,8 @@ { "projectName": "@arduino/arduino-iot-client", "moduleName": "ArduinoIotClient", - "projectVersion": "2.0.6", - "packageVersion": "2.0.6", + "projectVersion": "3.0.0", + "packageVersion": "3.0.0", "usePromises": true, "licenseName":"GPLv3", "generateSourceCodeOnly": true diff --git a/config/languages/python_v2.json b/config/languages/python_v2.json index b9ca964..b14af3b 100644 --- a/config/languages/python_v2.json +++ b/config/languages/python_v2.json @@ -1,6 +1,6 @@ { "packageName": "iot_api_client", "projectName": "arduino-iot-client", - "packageVersion": "2.0.6", + "packageVersion": "3.0.0", "generateSourceCodeOnly": false } diff --git a/downstream-templates/go/README.md b/downstream-templates/go/README.md index a9d8bff..c3070bb 100644 --- a/downstream-templates/go/README.md +++ b/downstream-templates/go/README.md @@ -10,7 +10,10 @@ import cc "golang.org/x/oauth2/clientcredentials" // We need to pass the additional "audience" var to request an access token additionalValues := url.Values{} -additionalValues.Add("audience", "https://api2.arduino.cc/iot") +additionalValues.Add("audience", "https://api2.arduino.cc") +if organizationId != "" { + additionalValues.Add("organization_id", organizationId) +} // Set up OAuth2 configuration config := cc.Config{ ClientID: clientID, diff --git a/downstream-templates/javascript/README.md b/downstream-templates/javascript/README.md index 4c451cb..62f9886 100644 --- a/downstream-templates/javascript/README.md +++ b/downstream-templates/javascript/README.md @@ -49,7 +49,12 @@ var client = ArduinoIotClient.ApiClient.instance; var oauth2 = client.authentications['oauth2']; oauth2.accessToken = await getToken(); -var api = new ArduinoIotClient.DevicesV2Api(client) +var api = new ArduinoIotClient.DevicesV2Api(client) +/* if required, organization_id can be configured as follow and the, pass opts var to function. +let opts = { + 'xOrganization': "" +}; +*/ api.devicesV2List().then(devices => { console.log(devices); }, error => { @@ -77,6 +82,7 @@ var options = { client_id: 'YOUR_CLIENT_ID', client_secret: 'YOUR_CLIENT_SECRET', audience: 'https://api2.arduino.cc/iot' + // If required, specify organization identifier adding "organization_id: ''" in form. } }; diff --git a/downstream-templates/python/README.md b/downstream-templates/python/README.md index afe94b8..efa34eb 100644 --- a/downstream-templates/python/README.md +++ b/downstream-templates/python/README.md @@ -2,7 +2,7 @@ ## Requirements -* Python 3.7+ +* Python 3.10+ ## Installation @@ -68,31 +68,40 @@ Once you get a token, you can create an instance of the iot-api client: ```python import iot_api_client as iot -from iot_api_client.rest import ApiException +from iot_api_client.exceptions import ApiException +from iot_api_client.models import * from iot_api_client.configuration import Configuration -import iot_api_client.apis.tags.devices_v2_api as deviceApi +from iot_api_client.api import DevicesV2Api # configure and instance the API client -client_config = Configuration(host="https://api2.arduino.cc/iot") -client_config.access_token = YOUR_ACCESS_TOKEN +client_config = Configuration(host="https://api2.arduino.cc") +client_config.access_token = YOUR_ACCESS_TOKEN or token function client = iot.ApiClient(client_config) # as an example, interact with the devices API devices_api = deviceApi.DevicesV2Api(client) +client_config = Configuration(host="https://api2.arduino.cc") +client_config.access_token = access_token +client = iot.ApiClient(client_config) + try: - devices = devices_api.devices_v2_list() - if devices.response.status==200: - for device in devices.body: - print("Device ("+device["id"]+"): "+device["name"]) + api_instance = DevicesV2Api(client) + api_response = api_instance.devices_v2_list() + for device in api_response: + print(device.name) + print(device.type) + except ApiException as e: - print("Got an exception: {}".format(e)) + print("Exception when calling DevicesV2Api->devices_v2_list: %s\n" % e) ``` In case of organization access, you can specify organization identifier in this way: ```python client = iot.ApiClient(client_config,header_name="X-Organization",header_value=org_id) +# or you can specify at method level, like: +api_instance.devices_v2_list(x_organization="org_id") ``` For a working example, see [the example folder](https://github.com/arduino/iot-client-py/tree/master/example/main.py) in this repo. diff --git a/downstream-templates/python/utils.py b/downstream-templates/python/utils.py index acc8116..f3485c3 100644 --- a/downstream-templates/python/utils.py +++ b/downstream-templates/python/utils.py @@ -3,8 +3,7 @@ """ Arduino IoT Cloud API - Provides a set of endpoints to manage Arduino IoT Cloud **Devices**, **Things**, **Properties** and **Timeseries**. This API can be called just with any HTTP Client, or using one of these clients: * [Javascript NPM package](https://www.npmjs.com/package/@arduino/arduino-iot-client) * [Python PYPI Package](https://pypi.org/project/arduino-iot-client/) * [Golang Module](https://github.com/arduino/iot-client-go) # noqa: E501 - + Utility package """ import frozendict diff --git a/spec/v2/shared.yaml b/openapi/components/footer.yaml similarity index 66% rename from spec/v2/shared.yaml rename to openapi/components/footer.yaml index 417d97a..4fcefb2 100644 --- a/spec/v2/shared.yaml +++ b/openapi/components/footer.yaml @@ -1,12 +1,4 @@ components: - callbacks: {} - examples: {} - headers: {} - links: {} - parameters: {} - requestBodies: {} - responses: {} - schemas: {} securitySchemes: oauth2: type: oauth2 @@ -16,5 +8,3 @@ components: scopes: iot:devices: Read and write my devices iot:things: Read and write my things -security: [] -tags: [] diff --git a/openapi/components/header.yaml b/openapi/components/header.yaml new file mode 100644 index 0000000..c166af2 --- /dev/null +++ b/openapi/components/header.yaml @@ -0,0 +1,2 @@ +servers: + - url: https://api2.arduino.cc \ No newline at end of file diff --git a/spec/v2/swagger.yaml b/openapi/components/iotapi-openapi.yaml similarity index 100% rename from spec/v2/swagger.yaml rename to openapi/components/iotapi-openapi.yaml diff --git a/openapi/compose/compose.go b/openapi/compose/compose.go new file mode 100644 index 0000000..b69044b --- /dev/null +++ b/openapi/compose/compose.go @@ -0,0 +1,117 @@ +package main + +import ( + "encoding/json" + "fmt" + "log" + "os" + "slices" + + "github.com/getkin/kin-openapi/openapi3" + "gopkg.in/yaml.v3" + + _ "embed" +) + +const ( + BasePath = "../" + OpenapiComponentsFolder = BasePath + "components/" + IotApiOpenApi = OpenapiComponentsFolder + "iotapi-openapi.yaml" + HeaderOpenApi = OpenapiComponentsFolder + "header.yaml" + FooterOpenApi = OpenapiComponentsFolder + "footer.yaml" + + MergedOpenApi = BasePath + "openapi.yaml" +) + +//go:embed path-blacklist.json +var pathBlacklist string + +type blacklist struct { + Blacklist []string `json:"blacklist"` +} + +func main() { + // Load blacklist + var bl blacklist + _ = json.Unmarshal([]byte(pathBlacklist), &bl) + + log.Println("Compose OpenAPI...") + + // Load the main (IOT-API) OpenAPI specification + loader := openapi3.NewLoader() + masterOpenapi, err := loader.LoadFromFile(IotApiOpenApi) + if err != nil { + log.Fatalf("Failed to load %s: %v", IotApiOpenApi, err) + } + + mergedPaths := []openapi3.NewPathsOption{} + for _, path := range masterOpenapi.Paths.InMatchingOrder() { + if slices.Contains(bl.Blacklist, path) { + continue + } + pathItem := masterOpenapi.Paths.Find(path) + mergedPaths = append(mergedPaths, openapi3.WithPath(fmt.Sprintf("/iot%s", path), pathItem)) + } + + if masterOpenapi.Components.Schemas == nil { + masterOpenapi.Components.Schemas = make(map[string]*openapi3.SchemaRef) + } + if masterOpenapi.Components.Responses == nil { + masterOpenapi.Components.Responses = make(map[string]*openapi3.ResponseRef) + } + if masterOpenapi.Components.Parameters == nil { + masterOpenapi.Components.Parameters = make(map[string]*openapi3.ParameterRef) + } + if masterOpenapi.Components.RequestBodies == nil { + masterOpenapi.Components.RequestBodies = make(map[string]*openapi3.RequestBodyRef) + } + if masterOpenapi.Components.Headers == nil { + masterOpenapi.Components.Headers = make(map[string]*openapi3.HeaderRef) + } + if masterOpenapi.Components.SecuritySchemes == nil { + masterOpenapi.Components.SecuritySchemes = make(map[string]*openapi3.SecuritySchemeRef) + } + + // Merge the components + + // Load header and footer + headerOpenapi, err := loader.LoadFromFile(HeaderOpenApi) + if err != nil { + log.Fatalf("Failed to load %s: %v", HeaderOpenApi, err) + } + + if headerOpenapi.Servers != nil { + masterOpenapi.Servers = append(masterOpenapi.Servers, headerOpenapi.Servers...) + } + + footerOpenapi, err := loader.LoadFromFile(FooterOpenApi) + if err != nil { + log.Fatalf("Failed to load %s: %v", FooterOpenApi, err) + } + + if footerOpenapi.Components.SecuritySchemes != nil { + for name, securityScheme := range footerOpenapi.Components.SecuritySchemes { + masterOpenapi.Components.SecuritySchemes[name] = securityScheme + } + } + + // Add merged paths to the master document + masterOpenapi.Paths = openapi3.NewPaths(mergedPaths...) + + ym, err := masterOpenapi.MarshalYAML() + if err != nil { + log.Fatalf("Failed to marshal merged document to YAML: %v", err) + } + yamlData, err := yaml.Marshal(ym) + if err != nil { + log.Fatalf("Failed to marshal merged document to YAML: %v", err) + } + + err = os.WriteFile(MergedOpenApi, yamlData, 0644) + if err != nil { + log.Fatalf("Failed to write %s: %v", MergedOpenApi, err) + } + + log.Println("Merged OpenAPI saved to " + MergedOpenApi) + +} diff --git a/openapi/compose/go.mod b/openapi/compose/go.mod new file mode 100644 index 0000000..08e79f4 --- /dev/null +++ b/openapi/compose/go.mod @@ -0,0 +1,18 @@ +module github.com/arduino/clients-iot-api/compose + +go 1.23 + +require ( + github.com/getkin/kin-openapi v0.128.0 + gopkg.in/yaml.v3 v3.0.1 +) + +require ( + github.com/go-openapi/jsonpointer v0.21.0 // indirect + github.com/go-openapi/swag v0.23.0 // indirect + github.com/invopop/yaml v0.3.1 // 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 +) diff --git a/openapi/compose/go.sum b/openapi/compose/go.sum new file mode 100644 index 0000000..8f8145b --- /dev/null +++ b/openapi/compose/go.sum @@ -0,0 +1,37 @@ +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.128.0 h1:jqq3D9vC9pPq1dGcOCv7yOp1DaEe7c/T1vzcLbITSp4= +github.com/getkin/kin-openapi v0.128.0/go.mod h1:OZrfXzUfGrNbsKj+xmFBx6E5c6yH3At/tAKSc2UszXM= +github.com/go-openapi/jsonpointer v0.21.0 h1:YgdVicSA9vH5RiHs9TZW5oyafXZFc6+2Vc1rr/O9oNQ= +github.com/go-openapi/jsonpointer v0.21.0/go.mod h1:IUyH9l/+uyhIYQ/PXVA41Rexl+kOkAPDdXEYns6fzUY= +github.com/go-openapi/swag v0.23.0 h1:vsEVJDUo2hPJ2tu0/Xc+4noaxyEffXNIs3cOULZ+GrE= +github.com/go-openapi/swag v0.23.0/go.mod h1:esZ8ITTYEsH1V2trKHjAN8Ai7xHb8RV+YSZ577vPjgQ= +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.3.1 h1:f0+ZpmhfBSS4MhG+4HYseMdJhoeeopbSKbq5Rpeelso= +github.com/invopop/yaml v0.3.1/go.mod h1:PMOp3nn4/12yEZUFfmOuNHJsZToEEOwoWsT+D81KkeA= +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.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= +github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= +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.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/openapi/compose/path-blacklist.json b/openapi/compose/path-blacklist.json new file mode 100644 index 0000000..550bdb9 --- /dev/null +++ b/openapi/compose/path-blacklist.json @@ -0,0 +1,3 @@ +{ + "blacklist": [] +} \ No newline at end of file diff --git a/spec/v2/header.yaml b/spec/v2/header.yaml deleted file mode 100644 index f7efc54..0000000 --- a/spec/v2/header.yaml +++ /dev/null @@ -1,2 +0,0 @@ -servers: - - url: https://api2.arduino.cc/iot \ No newline at end of file diff --git a/template-patches/html2-index.patch b/template-patches/html2-index.patch index 3838b9c..3168a14 100644 --- a/template-patches/html2-index.patch +++ b/template-patches/html2-index.patch @@ -1,8 +1,8 @@ diff --git a/htmlDocs2/index.mustache b/htmlDocs2/index.mustache -index 65c210b..e09cc2e 100644 +index ec5d8c742f6..c2601b4c41c 100644 --- a/htmlDocs2/index.mustache +++ b/htmlDocs2/index.mustache -@@ -206,6 +206,41 @@ +@@ -212,6 +212,41 @@
{{{appDescription}}}
@@ -44,7 +44,7 @@ index 65c210b..e09cc2e 100644
-@@ -233,18 +268,18 @@ +@@ -239,18 +274,18 @@

--- -2.34.1 - diff --git a/template-patches/html2-sample-python.patch b/template-patches/html2-sample-python.patch new file mode 100644 index 0000000..23ba895 --- /dev/null +++ b/template-patches/html2-sample-python.patch @@ -0,0 +1,55 @@ +diff --git a/htmlDocs2/sample_python.mustache b/htmlDocs2/sample_python.mustache +index aa92efaaf5b..dfc9940a270 100644 +--- a/htmlDocs2/sample_python.mustache ++++ b/htmlDocs2/sample_python.mustache +@@ -1,33 +1,23 @@ +-from __future__ import print_statement +-import time +-import {{{pythonPackageName}}} +-from {{{pythonPackageName}}}.rest import ApiException +-from pprint import pprint +-{{#hasAuthMethods}} +-{{#authMethods}} +-{{#isBasicBasic}} +-# Configure HTTP basic authorization: {{{name}}} +-{{{pythonPackageName}}}.configuration.username = 'YOUR_USERNAME' +-{{{pythonPackageName}}}.configuration.password = 'YOUR_PASSWORD'{{/isBasicBasic}}{{#isBasicBearer}} +-# Configure Bearer{{#bearerFormat}} ({{{.}}}){{/bearerFormat}} access token for authorization: {{{name}}} +-{{{pythonPackageName}}}.configuration.access_token = 'YOUR_ACCESS_TOKEN'{{/isBasicBearer}}{{#isApiKey}} +-# Configure API key authorization: {{{name}}} +-{{{pythonPackageName}}}.configuration.api_key['{{{keyParamName}}}'] = 'YOUR_API_KEY' +-# Uncomment below to setup prefix (e.g. Bearer) for API key, if needed +-# {{{pythonPackageName}}}.configuration.api_key_prefix['{{{keyParamName}}}'] = 'Bearer'{{/isApiKey}}{{#isOAuth}} +-# Configure OAuth2 access token for authorization: {{{name}}} +-{{{pythonPackageName}}}.configuration.access_token = 'YOUR_ACCESS_TOKEN'{{/isOAuth}} +-{{/authMethods}} +-{{/hasAuthMethods}} ++import iot_api_client as iot ++from iot_api_client.configuration import Configuration ++from iot_api_client.api import {{{classname}}} ++from iot_api_client.models import * ++from iot_api_client.exceptions import ApiException ++ ++client_config = Configuration(host="https://api2.arduino.cc") ++client_config.access_token = "JWT_ACCESS_TOKEN" ++client = iot.ApiClient(client_config) + + # Create an instance of the API class +-api_instance = {{{pythonPackageName}}}.{{{classname}}}() +-{{#allParams}}{{paramName}} = {{{example}}} # {{{dataType}}} | {{{unescapedDescription}}}{{^required}} (optional){{/required}}{{#defaultValue}} (default to {{{.}}}){{/defaultValue}} ++apiInstance = {{{classname}}}(client) ++{{#allParams}} ++{{paramName}} = {{{example}}} # {{{dataType}}} object instance | {{{unescapedDescription}}}{{^required}} (optional){{/required}}{{#defaultValue}} (default to {{{.}}}){{/defaultValue}} + {{/allParams}} + + try: + {{#summary}} # {{{.}}} +-{{/summary}} {{#returnType}}api_response = {{/returnType}}api_instance.{{{operationIdSnakeCase}}}({{#allParams}}{{#required}}{{paramName}}{{/required}}{{^required}}{{paramName}}={{paramName}}{{/required}}{{^-last}}, {{/-last}}{{/allParams}}){{#returnType}} +- pprint(api_response){{/returnType}} ++{{/summary}} {{#returnType}}api_response = {{/returnType}}apiInstance.{{{operationIdSnakeCase}}}({{#allParams}}{{#required}}{{paramName}}{{/required}}{{^required}}{{paramName}}={{paramName}}{{/required}}{{^-last}}, {{/-last}}{{/allParams}}){{#returnType}} ++ print(api_response){{/returnType}} + except ApiException as e: +- print("Exception when calling {{classname}}->{{operationId}}: %s\n" % e) +\ No newline at end of file ++ print("Exception when calling {{classname}}->{{operationId}}: %s\n" % e) ++