diff --git a/README.md b/README.md index 2e542f6..cd19661 100644 --- a/README.md +++ b/README.md @@ -77,6 +77,7 @@ If fully supported for multi-level json input, the `-format` feature can be much ### References - [How to build and run](/docs/build_and_run.md) +- [How to add a module](/docs/modules.md) - [Explanation of test code](/docs/example_explanation.md) - [Examples](/examples/README.md) - Formatting output diff --git a/docs/build_and_run.md b/docs/build_and_run.md index 2aa6a3e..a18e588 100644 --- a/docs/build_and_run.md +++ b/docs/build_and_run.md @@ -53,6 +53,8 @@ The following changes are made to your system: - file `/usr/local/bin/tcli` is created or updated - note: requires sudo if you are non root +To add your own Swagger/OpenAPI service as a new command module, see [How to add a module](/docs/modules.md). + ### How to build and run from docker The following is only tested in linux but should work similarly for windows. This method will depend on docker and a couple of base images available. The advantage @@ -192,6 +194,7 @@ This will use the number of available cpu cores to set the max parallel paths. ### References - [README.md](/README.md) +- [How to add a module](/docs/modules.md) - [Explanation of test code](/docs/example_explanation.md) - [Examples](/examples/README.md) - Format diff --git a/docs/modules.md b/docs/modules.md new file mode 100644 index 0000000..c619342 --- /dev/null +++ b/docs/modules.md @@ -0,0 +1,301 @@ +## Managing modules in `tcli` + +This guide explains how to add a new module to the `tcli` command structure using a Swagger/OpenAPI JSON file. + +### Table of contents + +- [Quick start](#quick-start-junior-friendly) +- [Prerequisites](#prerequisites) +- [Command anatomy](#command-anatomy) +- [How sub-modules are created](#how-sub-modules-are-created-from-swagger-tags) +- [How API calls work](#how-api-calls-work) +- [Add a module (runtime)](#add-a-new-module-installed-runtime-config) +- [Add a module (source control)](#add-a-new-module-in-source-control-repo-owner-workflow) +- [Troubleshooting](#troubleshooting-common-issues) + +--- + +### Who should use this guide + +Use this guide if you are: + +- adding a new Swagger/OpenAPI service as a `tcli` module +- trying to understand how module -> sub-module -> API command is generated +- building chained test flows using `-format` and pipes + +### Quick start + +If you only want the minimum steps to add a module: + +1. Ensure tcli is installed (run `make install` if `~/.tcli` is missing) +2. Copy your Swagger JSON to `~/.tcli/data/.json` + > **Note:** The `data` folder must exist inside `~/.tcli`. Do not create a file named `data`. +3. Add module entry in `~/.tcli/modules.yaml` +4. Run `tcli` and confirm your module appears +5. Run `tcli ` and confirm sub-modules/commands appear + +If any of these fail, see the [troubleshooting](#troubleshooting-common-issues) section at the end. + +### Prerequisites + +Install/build `tcli` first. + +Check if config exists: + +```console +ls ~/.tcli +``` + +If not found, run from the repository root: + +```console +make install +``` + +This creates `$HOME/.tcli` and copies: + +- `config.yaml` +- `modules.yaml` +- `data/` + +Quick verify: + +```console +tcli +``` + +You should see a list of supported modules. + + +### Command anatomy + +Given a command like: + +```console +tcli pce pce-customer createCustomer +``` + +The parts are: + +- `tcli` -> CLI command +- `pce` -> module +- `pce-customer` -> sub-module (tag/group) +- `createCustomer` -> API operation/command + +Pipelines can be chained with `|` where each command reads stdin from the previous command output. + +### How sub-modules are created (from Swagger `tags`) + +In `tcli`, sub-modules come from Swagger/OpenAPI operation tags. + +- Module is from `modules.yaml` (`name` field) +- Sub-module is from each operation's `tags` value +- API command is from operation `operationId` + +Example command: + +```console +tcli pce pce-customer createCustomer +``` + +Mapping: + +- `pce` -> module entry in `modules.yaml` +- `pce-customer` -> tag on the `createCustomer` API operation +- `createCustomer` -> operationId in Swagger JSON + +Example Swagger pattern: + +```json +{ + "tags": [ + { "name": "pce-customer", "description": "API for Customer Management" }, + { "name": "pce-cron", "description": "Cron job APIs" } + ], + "paths": { + "/customer": { + "post": { + "tags": ["pce-customer"], + "operationId": "createCustomer" + } + } + } +} +``` + +#### Important notes + +- If root-level `tags` are missing, `tcli` discovers tags from path operations. +- If an operation has multiple tags, that operation can appear under each tagged sub-module. +- If an operation has no tag, it is exposed directly as a module-level command. + +#### Quick checks + +```console +tcli pce +tcli pce pce-customer +``` + +Use these to confirm sub-modules and API commands were generated as expected. + +### How API calls work + +`tcli` reads Swagger/OpenAPI JSON for each module and uniquely identifies each REST API by `operationId`. + +Generic pattern: + +```console +tcli +``` + +#### Request mapping + +Input and request behavior are derived from Swagger fields: + +- `parameters` -> path, query, header, and body inputs +- HTTP verb + endpoint -> from operation under `paths` +- `consumes` (Swagger 2.0) or request `content` (OpenAPI 3) -> request content type + +This is why `tcli` can map CLI input and stdin JSON to the API request shape directly from spec. + +#### Test Case chaining + +`tcli` supports sequential test execution and response-to-request transformation through shell pipelines. + +- Sequential execution using `|` +- Response to request transformation using `-format` +- Built-in jq via `gojq` for lightweight JSON shaping + +Example chaining: + +```console +tcli pce pce-customer getTenantByTenantId -tenantId tenant11 -format '{tenantId:.tenantId}' | \ +tcli pce pce-cams-data createCamsData +``` + +Flow: + +1. First command fetches tenant data +2. `-format` keeps/transforms required fields (for example `tenantId`) +3. Next command consumes transformed JSON as input and executes + +This helps build setup -> action -> verify style test cases with minimal external tooling. + + +### Add a new module (installed runtime config) + +Use this path when you want the module available immediately on your machine. + +#### 1) Copy your Swagger JSON + +Copy your service spec into: + +```text +~/.tcli/data/.json +``` + +Example: + +```text +~/.tcli/data/pce.json +~/.tcli/data/carepack_service.json +``` + +#### 2) Update module registry + +Edit: + +```text +~/.tcli/modules.yaml +``` + +Add an entry under `modules:`: + +```yaml +modules: + - name: pce + config: data/pce.json + description: pc entitlement Service api commands + - name: carepack + config: data/carepack_service.json + description: Carepack Service Swagger +``` + +Notes: + +- `name` is the top-level CLI module (`tcli ...`) +- `config` is relative to `~/.tcli` +- Keep names lowercase and shell-friendly + +#### 3) Verify module is loaded + +Run: + +```console +tcli +``` + +You should see your new module in the supported module list. + +Then inspect commands in your module: + +```console +tcli +``` + +Then inspect API commands under a sub-module: + +```console +tcli +``` + +### Add a new module in source control (repo owner workflow) + +Use this path when you want to share the module definition with the team. + +1. Add Swagger JSON to: + +```text +tools/data/.json +``` + +2. Add module entry in: + +```text +tools/modules.yaml +``` + +3. Re-install to sync runtime config: + +```console +make install +``` + +This copies updated `tools/modules.yaml` and `tools/data/*` into `~/.tcli`. + +### Quick validation checklist + +- `~/.tcli` exists +- Swagger file exists in `~/.tcli/data` +- `~/.tcli/modules.yaml` has the new module entry +- `tcli` lists the module +- `tcli ` lists module commands/sub-modules + +### Troubleshooting (common issues) + +| Symptom | Likely cause | Fix | +|---------|--------------|-----| +| Module not visible | YAML syntax error | Check indentation in `~/.tcli/modules.yaml` | +| Module not visible | Wrong config path | Use relative path (e.g., `data/pce.json`) | +| No sub-modules | Missing tags | Add `tags` to operations in Swagger JSON | +| API command missing | Missing operationId | Ensure `operationId` exists for endpoint | +| Source changes not reflected | Stale install | Run `make install` to sync `~/.tcli` | + +--- + +### See also + +- [README](/README.md) +- [How to build and run](/docs/build_and_run.md) +- [Example explanation](/docs/example_explanation.md) +- [jq language reference](https://github.com/jqlang/jq/wiki/jq-Language-Description) diff --git a/go.mod b/go.mod index 2bf8797..69a260f 100644 --- a/go.mod +++ b/go.mod @@ -1,6 +1,6 @@ module github.com/hpinc/tcli -go 1.25.3 +go 1.26.0 require ( github.com/itchyny/gojq v0.12.17 diff --git a/internal/utils/retry_cli.go b/internal/utils/retry_cli.go index c76eee4..a11bf64 100644 --- a/internal/utils/retry_cli.go +++ b/internal/utils/retry_cli.go @@ -80,6 +80,7 @@ func (c *Client) RetriableDo(req *http.Request) (*http.Response, error) { logger.HttpRequest(req) var i int64 for { + // #nosec G704 -- request target is intentionally provided by CLI input. resp, err := c.HttpClient.Do(req) if c.RetryWithBackoff(logger, i, resp, err) { logger.Debugf("%d/%d\n", i, c.MaxRetry) diff --git a/tools/Dockerfile b/tools/Dockerfile index cd6b293..12c5dee 100644 --- a/tools/Dockerfile +++ b/tools/Dockerfile @@ -1,5 +1,4 @@ -FROM golang:1.25.3-alpine3.22 AS builder - +FROM golang:1.26-alpine3.22 AS builder ADD . /go/src/tcli WORKDIR /go/src/tcli @@ -9,10 +8,10 @@ go build -o bin/tcli \ -ldflags "-s -w" cmd/main.go # use a minimal alpine image -FROM alpine:3.22.2 +FROM alpine:3.23.3 RUN apk update --no-cache && \ apk upgrade && \ -apk add busybox=1.37.0-r20 +apk add busybox=1.37.0-r30 # make tcli available in path COPY --from=builder /go/src/tcli/bin/tcli /usr/local/bin/tcli diff --git a/tools/run_linter.sh b/tools/run_linter.sh index 2585322..8002ce4 100755 --- a/tools/run_linter.sh +++ b/tools/run_linter.sh @@ -1,5 +1,5 @@ #!/bin/sh -LINTER_IMAGE=golangci/golangci-lint:v2.4.0-alpine +LINTER_IMAGE=golangci/golangci-lint:v2.10.1-alpine DIR=${1:-$(pwd)} # flag overrides