refactor(api): move CLI-specific API contract into this repo, add openapi as submodule (#95)
This commit is contained in:
parent
8e73906437
commit
b1851eb819
@ -26,6 +26,9 @@ jobs:
|
||||
working_directory: /home/circleci/go/src/github.com/influxdata/influx-cli
|
||||
steps:
|
||||
- checkout
|
||||
- run:
|
||||
name: Init openapi submodule
|
||||
command: git submodule update --init --recursive
|
||||
- run:
|
||||
name: Upgrade Go
|
||||
command: |
|
||||
|
3
.gitmodules
vendored
Normal file
3
.gitmodules
vendored
Normal file
@ -0,0 +1,3 @@
|
||||
[submodule "openapi"]
|
||||
path = internal/api/contract/openapi
|
||||
url = ../openapi
|
@ -1,20 +1,24 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
declare -r ETC_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" &> /dev/null && pwd)"
|
||||
declare -r ROOT_DIR="$(dirname ${ETC_DIR})"
|
||||
declare -r API_DIR="${ROOT_DIR}/internal/api"
|
||||
|
||||
declare -r GENERATED_PATTERN='^// Code generated .* DO NOT EDIT\.$'
|
||||
declare -r MERGE_DOCKER_IMG=quay.io/influxdb/swagger-cli
|
||||
declare -r GENERATOR_DOCKER_IMG=openapitools/openapi-generator-cli:v5.1.0
|
||||
declare -r OPENAPI_COMMIT=dd675843404dbb3881f4d245581b916df319091f
|
||||
|
||||
# Clean up all the generated files in the target directory.
|
||||
rm $(grep -Elr "${GENERATED_PATTERN}" "${API_DIR}")
|
||||
|
||||
# Download our target API spec.
|
||||
# NOTE: openapi-generator supports HTTP references to API docs, but using that feature
|
||||
# causes the host of the URL to be injected into the base paths of generated code.
|
||||
curl -o "${API_DIR}/cli.yml" https://raw.githubusercontent.com/influxdata/openapi/${OPENAPI_COMMIT}/contracts/cli.yml
|
||||
# Merge all API contracts into a single file to drive codegen.
|
||||
docker run --rm -it -u "$(id -u):$(id -g)" \
|
||||
-v "${API_DIR}":/api \
|
||||
${MERGE_DOCKER_IMG} \
|
||||
swagger-cli bundle /api/contract/cli.yml \
|
||||
--outfile /api/cli.yml \
|
||||
--type yaml
|
||||
|
||||
# Run the generator - This produces many more files than we want to track in git.
|
||||
docker run --rm -it -u "$(id -u):$(id -g)" \
|
||||
|
@ -1,4 +1,7 @@
|
||||
# Influx CLI - HTTP Client
|
||||
|
||||
The `.go` files in this module are generated using [`OpenAPITools/openapi-generator`](https://github.com/OpenAPITools/openapi-generator),
|
||||
based off of our public API documentation. Run `etc/generate-openapi.sh` to regenerate files as needed.
|
||||
based off of our public API documentation.
|
||||
|
||||
Run `make openapi` from the project root to regenerate files as needed. See [`contract/README.md`](./contract/README.md)
|
||||
and [`templates/README.md`](./templates/README.md) for more detailed information and use-cases.
|
||||
|
53
internal/api/contract/README.md
Normal file
53
internal/api/contract/README.md
Normal file
@ -0,0 +1,53 @@
|
||||
# API Contract
|
||||
|
||||
This directory contains the source YMLs used to drive code generation of HTTP clients in [`internal/api`](../).
|
||||
|
||||
## YML Structure
|
||||
|
||||
Most YMLs used here are pulled from the source-of-truth [`openapi`](https://github.com/influxdata/openapi) repo via a git
|
||||
submodule. In rare cases, the full description of an API is too complex for our codegen tooling to handle;
|
||||
the [`overrides/`](./overrides) directory contains alternate definitions for paths/schemas that work around these cases.
|
||||
[`cli.yml`](./cli.yml) ties together all the pieces by linking all routes and schemas used by the CLI.
|
||||
|
||||
## Updating the API contract
|
||||
|
||||
To extend/modify the API contract used by the CLI, first make sure the `openapi` submodule is cloned and up-to-date:
|
||||
```shell
|
||||
# Run from the project root.
|
||||
git submodule update --init --recursive
|
||||
```
|
||||
|
||||
Then create a new branch to track your work:
|
||||
```shell
|
||||
git checkout <new-branch-name>
|
||||
```
|
||||
|
||||
Next, decide if any modifications are needed in the source-of-truth `openapi` repo. If so, create a branch in the
|
||||
submodule to track changes there:
|
||||
```shell
|
||||
cd internal/api/contract/openapi && git checkout -b <new-branch-name>
|
||||
```
|
||||
|
||||
Edit/add to the files under `api-contract/` to describe the new API contract. Run the following from the project
|
||||
root test your changes and see the outputs in Go code:
|
||||
```shell
|
||||
make openapi
|
||||
# Use `git status` to see new/modified files under `internal/api`
|
||||
```
|
||||
|
||||
Once you're happy with the new API contract, submit your changes for review & merge.
|
||||
If you added/edited files within `openapi`, you'll first need to:
|
||||
1. Push your submodule branch to GitHub
|
||||
```shell
|
||||
cd internal/api/contract/openapi && git push <your-branch-name>
|
||||
```
|
||||
2. Create a PR in `openapi`, eventually merge to `master` there
|
||||
3. Update your submodule to point at the merge result:
|
||||
```shell
|
||||
cd internal/api/contract/openapi && git fetch && git checkout master && git pull origin master
|
||||
```
|
||||
4. Update the submodule reference from the main repo:
|
||||
```shell
|
||||
git add internal/api/contract/openapi
|
||||
git commit
|
||||
```
|
132
internal/api/contract/cli.yml
Normal file
132
internal/api/contract/cli.yml
Normal file
@ -0,0 +1,132 @@
|
||||
openapi: "3.0.0"
|
||||
info:
|
||||
title: Subset of Influx API covered by Influx CLI
|
||||
version: 2.0.0
|
||||
servers:
|
||||
- url: /api/v2
|
||||
paths:
|
||||
/health:
|
||||
servers:
|
||||
- url: ''
|
||||
$ref: "./openapi/src/oss/paths/health.yml"
|
||||
/setup:
|
||||
$ref: "./openapi/src/common/paths/setup.yml"
|
||||
/write:
|
||||
$ref: "./openapi/src/common/paths/write.yml"
|
||||
/buckets:
|
||||
$ref: "./openapi/src/common/paths/buckets.yml"
|
||||
/buckets/{bucketID}:
|
||||
$ref: "./openapi/src/common/paths/buckets_bucketID.yml"
|
||||
/orgs:
|
||||
$ref: "./openapi/src/common/paths/orgs.yml"
|
||||
/orgs/{orgID}:
|
||||
$ref: "./openapi/src/common/paths/orgs_orgID.yml"
|
||||
/orgs/{orgID}/members:
|
||||
$ref: "./openapi/src/common/paths/orgs_orgID_members.yml"
|
||||
/orgs/{orgID}/members/{userID}:
|
||||
$ref: "./openapi/src/common/paths/orgs_orgID_members_userID.yml"
|
||||
/buckets/{bucketID}/schema/measurements:
|
||||
$ref: "./openapi/src/cloud/paths/measurements.yml"
|
||||
/buckets/{bucketID}/schema/measurements/{measurementID}:
|
||||
$ref: "./openapi/src/cloud/paths/measurements_measurementID.yml"
|
||||
/query:
|
||||
$ref: "./overrides/paths/query.yml"
|
||||
components:
|
||||
parameters:
|
||||
TraceSpan:
|
||||
$ref: "./openapi/src/common/parameters/TraceSpan.yml"
|
||||
Offset:
|
||||
$ref: "./openapi/src/common/parameters/Offset.yml"
|
||||
Limit:
|
||||
$ref: "./openapi/src/common/parameters/Limit.yml"
|
||||
After:
|
||||
$ref: "./openapi/src/common/parameters/After.yml"
|
||||
Descending:
|
||||
$ref: "./openapi/src/common/parameters/Descending.yml"
|
||||
schemas:
|
||||
Error:
|
||||
$ref: "./openapi/src/common/schemas/Error.yml"
|
||||
ErrorCode:
|
||||
$ref: "./openapi/src/common/schemas/ErrorCode.yml"
|
||||
HealthCheck:
|
||||
$ref: "./openapi/src/common/schemas/HealthCheck.yml"
|
||||
HealthCheckStatus:
|
||||
$ref: "./openapi/src/common/schemas/HealthCheckStatus.yml"
|
||||
OnboardingRequest:
|
||||
$ref: "./openapi/src/common/schemas/OnboardingRequest.yml"
|
||||
OnboardingResponse:
|
||||
$ref: "./openapi/src/common/schemas/OnboardingResponse.yml"
|
||||
UserResponse:
|
||||
$ref: "./openapi/src/common/schemas/UserResponse.yml"
|
||||
Links:
|
||||
$ref: "./openapi/src/common/schemas/Links.yml"
|
||||
Link:
|
||||
$ref: "./openapi/src/common/schemas/Link.yml"
|
||||
Organizations:
|
||||
$ref: "./openapi/src/common/schemas/Organizations.yml"
|
||||
Organization:
|
||||
$ref: "./openapi/src/common/schemas/Organization.yml"
|
||||
PostOrganizationRequest:
|
||||
$ref: "./openapi/src/common/schemas/PostOrganizationRequest.yml"
|
||||
PatchOrganizationRequest:
|
||||
$ref: "./openapi/src/common/schemas/PatchOrganizationRequest.yml"
|
||||
Buckets:
|
||||
$ref: "./openapi/src/common/schemas/Buckets.yml"
|
||||
Bucket:
|
||||
$ref: "./openapi/src/common/schemas/Bucket.yml"
|
||||
PostBucketRequest:
|
||||
$ref: "./openapi/src/common/schemas/PostBucketRequest.yml"
|
||||
RetentionRules:
|
||||
$ref: "./openapi/src/common/schemas/RetentionRules.yml"
|
||||
RetentionRule:
|
||||
$ref: "./openapi/src/common/schemas/RetentionRule.yml"
|
||||
PatchBucketRequest:
|
||||
$ref: "./openapi/src/common/schemas/PatchBucketRequest.yml"
|
||||
PatchRetentionRules:
|
||||
$ref: "./openapi/src/common/schemas/PatchRetentionRules.yml"
|
||||
PatchRetentionRule:
|
||||
$ref: "./openapi/src/common/schemas/PatchRetentionRule.yml"
|
||||
Labels:
|
||||
$ref: "./openapi/src/common/schemas/Labels.yml"
|
||||
Label:
|
||||
$ref: "./openapi/src/common/schemas/Label.yml"
|
||||
Authorization:
|
||||
$ref: "./openapi/src/common/schemas/Authorization.yml"
|
||||
AuthorizationUpdateRequest:
|
||||
$ref: "./openapi/src/common/schemas/AuthorizationUpdateRequest.yml"
|
||||
Permission:
|
||||
$ref: "./openapi/src/common/schemas/Permission.yml"
|
||||
ResourceMembers:
|
||||
$ref: "./openapi/src/common/schemas/ResourceMembers.yml"
|
||||
ResourceMember:
|
||||
$ref: "./openapi/src/common/schemas/ResourceMember.yml"
|
||||
AddResourceMemberRequestBody:
|
||||
$ref: "./openapi/src/common/schemas/AddResourceMemberRequestBody.yml"
|
||||
WritePrecision:
|
||||
$ref: "./openapi/src/common/schemas/WritePrecision.yml"
|
||||
LineProtocolError:
|
||||
$ref: "./openapi/src/common/schemas/LineProtocolError.yml"
|
||||
LineProtocolLengthError:
|
||||
$ref: "./openapi/src/common/schemas/LineProtocolLengthError.yml"
|
||||
SchemaType:
|
||||
$ref: "./openapi/src/common/schemas/SchemaType.yml"
|
||||
ColumnDataType:
|
||||
$ref: "./openapi/src/cloud/schemas/ColumnDataType.yml"
|
||||
ColumnSemanticType:
|
||||
$ref: "./openapi/src/cloud/schemas/ColumnSemanticType.yml"
|
||||
MeasurementSchema:
|
||||
$ref: "./openapi/src/cloud/schemas/MeasurementSchema.yml"
|
||||
MeasurementSchemaColumn:
|
||||
$ref: "./openapi/src/cloud/schemas/MeasurementSchemaColumn.yml"
|
||||
MeasurementSchemaCreateRequest:
|
||||
$ref: "./openapi/src/cloud/schemas/MeasurementSchemaCreateRequest.yml"
|
||||
MeasurementSchemaList:
|
||||
$ref: "./openapi/src/cloud/schemas/MeasurementSchemaList.yml"
|
||||
MeasurementSchemaUpdateRequest:
|
||||
$ref: "./openapi/src/cloud/schemas/MeasurementSchemaUpdateRequest.yml"
|
||||
Query:
|
||||
$ref: "./overrides/schemas/Query.yml"
|
||||
Dialect:
|
||||
$ref: "./openapi/src/common/schemas/Dialect.yml"
|
||||
Extern:
|
||||
$ref: "./overrides/schemas/Extern.yml"
|
1
internal/api/contract/openapi
Submodule
1
internal/api/contract/openapi
Submodule
@ -0,0 +1 @@
|
||||
Subproject commit ea92cf2265f9bfa7d9f70a50c4bade78a263e0e7
|
81
internal/api/contract/overrides/paths/query.yml
Normal file
81
internal/api/contract/overrides/paths/query.yml
Normal file
@ -0,0 +1,81 @@
|
||||
post:
|
||||
operationId: PostQuery
|
||||
tags:
|
||||
- Query
|
||||
summary: Query InfluxDB
|
||||
parameters:
|
||||
- $ref: "../../openapi/src/common/parameters/TraceSpan.yml"
|
||||
- in: header
|
||||
name: Accept-Encoding
|
||||
description: The Accept-Encoding request HTTP header advertises which content encoding, usually a compression algorithm, the client is able to understand.
|
||||
schema:
|
||||
type: string
|
||||
description: Specifies that the query response in the body should be encoded with gzip or not encoded with identity.
|
||||
default: identity
|
||||
enum:
|
||||
- gzip
|
||||
- identity
|
||||
- in: header
|
||||
name: Content-Type
|
||||
schema:
|
||||
type: string
|
||||
enum:
|
||||
- application/json
|
||||
- in: query
|
||||
name: org
|
||||
description: Specifies the name of the organization executing the query. Takes either the ID or Name interchangeably. If both `orgID` and `org` are specified, `org` takes precedence.
|
||||
schema:
|
||||
type: string
|
||||
- in: query
|
||||
name: orgID
|
||||
description: Specifies the ID of the organization executing the query. If both `orgID` and `org` are specified, `org` takes precedence.
|
||||
schema:
|
||||
type: string
|
||||
requestBody:
|
||||
description: Flux query or specification to execute
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: "../schemas/Query.yml"
|
||||
responses:
|
||||
"200":
|
||||
description: Query results
|
||||
headers:
|
||||
Content-Encoding:
|
||||
description: The Content-Encoding entity header is used to compress the media-type. When present, its value indicates which encodings were applied to the entity-body
|
||||
schema:
|
||||
type: string
|
||||
description: Specifies that the response in the body is encoded with gzip or not encoded with identity.
|
||||
default: identity
|
||||
enum:
|
||||
- gzip
|
||||
- identity
|
||||
Trace-Id:
|
||||
description: The Trace-Id header reports the request's trace ID, if one was generated.
|
||||
schema:
|
||||
type: string
|
||||
description: Specifies the request's trace ID.
|
||||
content:
|
||||
text/csv:
|
||||
schema:
|
||||
type: string
|
||||
format: binary
|
||||
example: >
|
||||
result,table,_start,_stop,_time,region,host,_value
|
||||
mean,0,2018-05-08T20:50:00Z,2018-05-08T20:51:00Z,2018-05-08T20:50:00Z,east,A,15.43
|
||||
mean,0,2018-05-08T20:50:00Z,2018-05-08T20:51:00Z,2018-05-08T20:50:20Z,east,B,59.25
|
||||
mean,0,2018-05-08T20:50:00Z,2018-05-08T20:51:00Z,2018-05-08T20:50:40Z,east,C,52.62
|
||||
"429":
|
||||
description: Token is temporarily over quota. The Retry-After header describes when to try the read again.
|
||||
headers:
|
||||
Retry-After:
|
||||
description: A non-negative decimal integer indicating the seconds to delay after the response is received.
|
||||
schema:
|
||||
type: integer
|
||||
format: int32
|
||||
default:
|
||||
description: Error processing query
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: "../../openapi/src/common/schemas/Error.yml"
|
12
internal/api/contract/overrides/schemas/Extern.yml
Normal file
12
internal/api/contract/overrides/schemas/Extern.yml
Normal file
@ -0,0 +1,12 @@
|
||||
description: Free-form Flux AST to prepend to query requests
|
||||
type: object
|
||||
properties:
|
||||
type:
|
||||
type: string
|
||||
enum: [File]
|
||||
default: File
|
||||
# NOTE: Intentionally type-less here because the boilerplate produced
|
||||
# by codegen off the Flux AST spec is unmangeable. The CLI only needs
|
||||
# to use a small subset of the AST and rarely changes what it sends,
|
||||
# so we can live with a generic map in the codegen request body.
|
||||
additionalProperties: true
|
22
internal/api/contract/overrides/schemas/Query.yml
Normal file
22
internal/api/contract/overrides/schemas/Query.yml
Normal file
@ -0,0 +1,22 @@
|
||||
description: Query influx using the Flux language
|
||||
type: object
|
||||
required:
|
||||
- query
|
||||
properties:
|
||||
extern:
|
||||
$ref: "./Extern.yml"
|
||||
query:
|
||||
description: Query script to execute.
|
||||
type: string
|
||||
type:
|
||||
description: The type of query. Must be "flux".
|
||||
type: string
|
||||
enum:
|
||||
- flux
|
||||
default: flux
|
||||
dialect:
|
||||
$ref: "../../openapi/src/common/schemas/Dialect.yml"
|
||||
now:
|
||||
description: Specifies the time that should be reported as "now" in the query. Default is the server's now time.
|
||||
type: string
|
||||
format: date-time
|
@ -30,3 +30,6 @@ multiple locations.
|
||||
`configuration.mustache`
|
||||
* Deleted `ContextOAuth2` key to match modification in client
|
||||
* Fixed error strings to be idiomatic according to staticcheck (lowercase, no punctuation)
|
||||
|
||||
`model_oneof.mustache`
|
||||
* Fixed error strings to be idiomatic according to staticcheck (lowercase, no punctuation)
|
||||
|
@ -114,7 +114,7 @@ func TestClient_Delete(t *testing.T) {
|
||||
svc.EXPECT().DeleteConfig(gomock.Eq("foo")).
|
||||
Return(iconfig.Config{Name: "foo", Host: "bar", Org: "baz"}, nil)
|
||||
},
|
||||
out: []string{`^\s+foo\s+bar\s+baz\s+true`},
|
||||
out: []string{`\s+foo\s+bar\s+baz\s+true`},
|
||||
},
|
||||
{
|
||||
name: "many",
|
||||
@ -128,7 +128,7 @@ func TestClient_Delete(t *testing.T) {
|
||||
Return(iconfig.Config{Name: "wibble", Host: "bar", Active: true}, nil)
|
||||
},
|
||||
out: []string{
|
||||
`^\s+foo\s+bar\s+baz\s+true`,
|
||||
`\s+foo\s+bar\s+baz\s+true`,
|
||||
`\*\s+wibble\s+bar\s+true`,
|
||||
},
|
||||
},
|
||||
@ -151,9 +151,13 @@ func TestClient_Delete(t *testing.T) {
|
||||
|
||||
cli := config.Client{CLI: cmd.CLI{ConfigService: svc, StdIO: stdio}}
|
||||
require.NoError(t, cli.Delete(tc.in))
|
||||
testutils.MatchLines(t,
|
||||
append([]string{`Active\s+Name\s+URL\s+Org\s+Deleted`}, tc.out...),
|
||||
strings.Split(writtenBytes.String(), "\n"))
|
||||
|
||||
// Can't use our usual 'MatchLines' because list output depends on map iteration,
|
||||
// so the order isn't well-defined.
|
||||
out := writtenBytes.String()
|
||||
for _, l := range append([]string{`Active\s+Name\s+URL\s+Org\s+Deleted`}, tc.out...) {
|
||||
require.Regexp(t, l, out)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user