Skip to content

Commit 7047898

Browse files
committed
oapi-codegen generator & Go client publishing
- Support oapi-codegen as default Go generator (creates cleaner single-file client) - Add bin/publish script to publish versioned Go clients to dedicated repository - Configure GitHub Actions workflow for automated publishing - Add operationId fields to CAPI endpoints for better SDK generation - Auto-generate go.mod with proper module paths and dependencies
1 parent 2b4d9c7 commit 7047898

File tree

5 files changed

+602
-48
lines changed

5 files changed

+602
-48
lines changed

Makefile

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
.PHONY: deps deps-openapi deps-spruce deps-jq deps-java help gen-go-client
1+
.PHONY: deps deps-openapi deps-oapi-codegen deps-spruce deps-jq deps-java help gen-go-client
22

33
# Default target
44
.DEFAULT_GOAL := help
@@ -12,7 +12,7 @@ CAPI_VERSION ?= 3.195.0
1212
help: ## Display this help screen
1313
@grep -h -E '^[a-zA-Z_-]+:.*?## .*$$' $(MAKEFILE_LIST) | awk 'BEGIN {FS = ":.*?## "}; {printf "\033[36m%-30s\033[0m %s\n", $$1, $$2}'
1414

15-
deps: deps-java deps-openapi deps-spruce deps-jq ## Install all dependencies
15+
deps: deps-java deps-openapi deps-oapi-codegen deps-spruce deps-jq ## Install all dependencies
1616

1717
deps-java: ## Install Java Runtime Environment
1818
@echo "Checking/Installing Java..."
@@ -46,6 +46,14 @@ deps-openapi: deps-java ## Install OpenAPI Generator CLI
4646
fi
4747
bun install @openapitools/openapi-generator-cli -g
4848

49+
deps-oapi-codegen: ## Install oapi-codegen for Go client generation
50+
@echo "Installing oapi-codegen..."
51+
@if ! command -v go &> /dev/null; then \
52+
echo "Go is not installed. Please install Go first: https://golang.org/"; \
53+
exit 1; \
54+
fi
55+
go install github.com/deepmap/oapi-codegen/cmd/oapi-codegen@latest
56+
4957
deps-spruce: ## Install Spruce
5058
@echo "Installing Spruce..."
5159
@if [ "$(UNAME_S)" = "Darwin" ]; then \
@@ -84,6 +92,7 @@ check-deps: ## Check if all dependencies are installed
8492
@echo "Checking dependencies..."
8593
@command -v spruce >/dev/null 2>&1 || { echo "spruce is not installed. Run 'make deps-spruce'"; exit 1; }
8694
@command -v jq >/dev/null 2>&1 || { echo "jq is not installed. Run 'make deps-jq'"; exit 1; }
95+
@command -v oapi-codegen >/dev/null 2>&1 || { echo "oapi-codegen is not installed. Run 'make deps-oapi-codegen'"; exit 1; }
8796
@echo "All dependencies are installed!"
8897

8998
prepare: check-deps ## Prepare the OpenAPI specification
@@ -94,7 +103,7 @@ gen-openapi-spec: check-deps ## Merge the CAPI OpenAPI specifications
94103
@echo "Merging CAPI OpenAPI specifications..."
95104
./bin/gen merge --version=$(CAPI_VERSION)
96105

97-
gen-go-client: gen-openapi-spec ## Generate Go client from OpenAPI spec
106+
gen-go-client: gen-openapi-spec ## Generate Go client from OpenAPI spec (uses oapi-codegen by default)
98107
@echo "Generating Go client..."
99108
./bin/gen --version=$(CAPI_VERSION) --language=go
100109

README.md

Lines changed: 57 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -79,7 +79,7 @@ The `bin/gen` script provides a flexible way to generate SDKs for different lang
7979
### Usage
8080
```bash
8181
# Generate SDK
82-
./bin/gen --version=VERSION --language=LANGUAGE [--output=PATH]
82+
./bin/gen --version=VERSION --language=LANGUAGE [--output=PATH] [--generator=GENERATOR]
8383

8484
# Prepare specifications (download and create YAML files)
8585
./bin/gen prepare --version=VERSION
@@ -88,11 +88,22 @@ The `bin/gen` script provides a flexible way to generate SDKs for different lang
8888
./bin/gen merge --version=VERSION
8989
```
9090

91+
### Generator Options
92+
93+
The script supports multiple code generators:
94+
- **oapi-codegen** (default for Go) - Generates a single, clean Go file with types and client
95+
- **openapi-generator** (default for other languages) - Full-featured generator with many customization options
96+
9197
### Examples
9298

9399
#### Generate Go SDK for latest CAPI version (3.195.0)
94100
```bash
101+
# Using default oapi-codegen generator (creates single client.go file)
95102
./bin/gen --version=3.195.0 --language=go
103+
# Output: ./sdk/3.195.0/go/capiclient/client.go
104+
105+
# Using openapi-generator (creates multiple files)
106+
./bin/gen --version=3.195.0 --language=go --generator=openapi-generator
96107
# Output: ./sdk/3.195.0/go/
97108
```
98109

@@ -138,11 +149,51 @@ Run `./bin/gen --help` to see the full list of supported languages.
138149

139150
After generating an SDK, you may need to:
140151

141-
1. **Go**: Run `go mod tidy` in the generated directory
152+
1. **Go**:
153+
- With oapi-codegen: `go.mod` is automatically created and `go mod tidy` is run
154+
- With openapi-generator: Run `go mod tidy` in the generated directory
142155
2. **Ruby**: Build the gem with `gem build *.gemspec`
143156
3. **Python**: Install with `pip install -e .`
144157
4. **Java**: Build with Maven or Gradle
145158

159+
## Publishing Go Client
160+
161+
The repository includes an automated workflow for publishing the Go client to a separate repository for easy consumption.
162+
163+
### Manual Publishing
164+
165+
```bash
166+
# Generate the Go client
167+
./bin/gen --version=3.195.0 --language=go
168+
169+
# Publish to github.com/cloudfoundry-community/capi-openapi-go-client
170+
./bin/publish --version=3.195.0
171+
172+
# Dry run to preview what will be published
173+
./bin/publish --version=3.195.0 --dry-run
174+
175+
# Force overwrite if tag already exists
176+
./bin/publish --version=3.195.0 --force
177+
```
178+
179+
### Automated Publishing via GitHub Actions
180+
181+
1. Go to the [Actions tab](../../actions)
182+
2. Select "Publish Go Client" workflow
183+
3. Click "Run workflow"
184+
4. Enter the CAPI version (e.g., 3.195.0)
185+
5. Optionally check "Force" to overwrite existing tags
186+
187+
The published Go module will be available at:
188+
```go
189+
import "github.com/cloudfoundry-community/capi-openapi-go-client/capiclient/v3"
190+
```
191+
192+
### Publishing Requirements
193+
194+
- **SSH Deploy Key**: The GitHub Actions workflow requires a deploy key secret (`CAPI_GO_CLIENT_DEPLOY_KEY`) with write access to the target repository
195+
- **Target Repository**: `github.com/cloudfoundry-community/capi-openapi-go-client` must exist and be configured to accept the deploy key
196+
146197
## Version Information
147198

148199
| Component | Version |
@@ -210,10 +261,14 @@ capi/
210261
└── 3.195.0.openapi.json (generated)
211262
bin/
212263
├── gen (main processing script for prepare, merge, and SDK generation)
264+
├── publish (publishes Go client to separate repository)
213265
└── validate (OpenAPI spec validation script)
214266
sdk/
215267
└── VERSION/
216268
└── LANGUAGE/ (generated SDKs)
269+
.github/
270+
└── workflows/
271+
└── publish-go-client.yml (automated publishing workflow)
217272
```
218273

219274
### Validation

bin/gen

Lines changed: 146 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -22,12 +22,14 @@ my $command;
2222
my $capi_version;
2323
my $language;
2424
my $output_path;
25+
my $generator;
2526
my $help;
2627

2728
GetOptions(
2829
'version=s' => \$capi_version,
2930
'language=s' => \$language,
3031
'output=s' => \$output_path,
32+
'generator=s' => \$generator,
3133
'help' => \$help,
3234
) or die usage();
3335

@@ -51,8 +53,20 @@ if ($command eq 'prepare') {
5153
merge_openapi_spec($capi_version);
5254
} elsif ($capi_version && $language) {
5355
# Generate SDK (original functionality)
56+
# Set default generator based on language
57+
if ($language eq 'go' && !$generator) {
58+
$generator = 'oapi-codegen';
59+
} else {
60+
$generator //= 'openapi-generator';
61+
}
62+
5463
# Set default output path if not provided
55-
$output_path //= File::Spec->catfile($project_root, 'sdk', $capi_version, $language);
64+
if ($language eq 'go') {
65+
# For Go, append the package name to the path
66+
$output_path //= File::Spec->catfile($project_root, 'sdk', $capi_version, $language, 'capiclient');
67+
} else {
68+
$output_path //= File::Spec->catfile($project_root, 'sdk', $capi_version, $language);
69+
}
5670

5771
# Validate inputs
5872
validate_inputs();
@@ -79,6 +93,8 @@ SDK Generation Options:
7993
8094
Optional options:
8195
--output=PATH Output directory (default: ./sdk/VERSION/LANGUAGE/)
96+
--generator=GEN Generator to use: openapi-generator, oapi-codegen
97+
(default: oapi-codegen for Go, openapi-generator for others)
8298
--help Show this help message
8399
84100
Supported languages:
@@ -100,7 +116,8 @@ Supported languages:
100116
Examples:
101117
$0 prepare --version=3.195.0
102118
$0 merge --version=3.195.0
103-
$0 --version=3.195.0 --language=go
119+
$0 --version=3.195.0 --language=go # Uses oapi-codegen by default
120+
$0 --version=3.195.0 --language=go --generator=openapi-generator
104121
$0 --version=3.181.0 --language=python --output=/tmp/capi-python-sdk
105122
EOF
106123
}
@@ -113,12 +130,21 @@ sub validate_inputs {
113130
"Please run './bin/gen merge --version=$capi_version' first to generate the specification.\n";
114131
}
115132

116-
# Check if openapi-generator is available
117-
my $generator_check = `which openapi-generator 2>/dev/null || which openapi-generator-cli 2>/dev/null`;
118-
chomp $generator_check;
119-
unless ($generator_check) {
120-
die "Error: openapi-generator-cli not found.\n" .
121-
"Please run 'make deps' to install dependencies.\n";
133+
# Check if selected generator is available
134+
if ($generator eq 'oapi-codegen') {
135+
my $generator_check = `which oapi-codegen 2>/dev/null`;
136+
chomp $generator_check;
137+
unless ($generator_check) {
138+
die "Error: oapi-codegen not found.\n" .
139+
"Please install it with: go install github.com/deepmap/oapi-codegen/cmd/oapi-codegen\@latest\n";
140+
}
141+
} else {
142+
my $generator_check = `which openapi-generator 2>/dev/null || which openapi-generator-cli 2>/dev/null`;
143+
chomp $generator_check;
144+
unless ($generator_check) {
145+
die "Error: openapi-generator-cli not found.\n" .
146+
"Please run 'make deps' to install dependencies.\n";
147+
}
122148
}
123149
}
124150

@@ -128,36 +154,73 @@ sub generate_sdk {
128154
# Create output directory if it doesn't exist
129155
make_path($output_path) unless -d $output_path;
130156

131-
# Determine which command to use (openapi-generator or openapi-generator-cli)
132-
my $generator_cmd = `which openapi-generator 2>/dev/null`;
133-
chomp $generator_cmd;
134-
$generator_cmd = 'openapi-generator-cli' unless $generator_cmd;
135-
136-
# Build the command
137-
my $cmd = "$generator_cmd generate " .
138-
"-i '$spec_file' " .
139-
"-g '$language' " .
140-
"-o '$output_path' " .
141-
"--skip-validate-spec";
157+
my $cmd;
158+
my $result;
142159

143-
# Add GitHub configuration
144-
my $github_org = 'cloudfoundry-community';
145-
my $github_repo = "capi-openapi-${language}-client";
146-
$cmd .= " --git-user-id='$github_org' --git-repo-id='$github_repo'";
147-
148-
# Add language-specific options
149-
my $additional_props = get_additional_properties($language);
150-
$cmd .= " --additional-properties='$additional_props'" if $additional_props;
151-
152-
# Execute the command
153-
say "Generating $language SDK for CAPI $capi_version...";
154-
say "Command: $cmd";
155-
156-
my $result = system($cmd);
160+
if ($generator eq 'oapi-codegen') {
161+
# Use oapi-codegen for Go
162+
if ($language ne 'go') {
163+
die "Error: oapi-codegen only supports Go language generation.\n";
164+
}
165+
166+
# Build output file path
167+
my $output_file = File::Spec->catfile($output_path, 'client.go');
168+
169+
# Build the command
170+
$cmd = "oapi-codegen -generate types,client -package capiclient '$spec_file' > '$output_file'";
171+
172+
# Execute the command
173+
say "Generating $language SDK for CAPI $capi_version using oapi-codegen...";
174+
say "Command: $cmd";
175+
176+
$result = system($cmd);
177+
178+
# Generate go.mod file if successful
179+
if ($result == 0) {
180+
generate_go_mod($output_path, $capi_version);
181+
}
182+
} else {
183+
# Use openapi-generator
184+
# Determine which command to use (openapi-generator or openapi-generator-cli)
185+
my $generator_cmd = `which openapi-generator 2>/dev/null`;
186+
chomp $generator_cmd;
187+
$generator_cmd = 'openapi-generator-cli' unless $generator_cmd;
188+
189+
# Build the command
190+
$cmd = "$generator_cmd generate " .
191+
"-i '$spec_file' " .
192+
"-g '$language' " .
193+
"-o '$output_path' " .
194+
"--skip-validate-spec";
195+
196+
# Add config file if it exists for Go
197+
my $config_file = File::Spec->catfile($project_root, 'openapi-generator-config.yml');
198+
if ($language eq 'go' && -f $config_file) {
199+
$cmd .= " -c '$config_file'";
200+
}
201+
202+
# Add GitHub configuration
203+
my $github_org = 'cloudfoundry-community';
204+
my $github_repo = "capi-openapi-${language}-client";
205+
$cmd .= " --git-user-id='$github_org' --git-repo-id='$github_repo'";
206+
207+
# Add language-specific options
208+
my $additional_props = get_additional_properties($language);
209+
$cmd .= " --additional-properties='$additional_props'" if $additional_props;
210+
211+
# Execute the command
212+
say "Generating $language SDK for CAPI $capi_version using openapi-generator...";
213+
say "Command: $cmd";
214+
215+
$result = system($cmd);
216+
}
157217

158218
if ($result == 0) {
159219
say "\nSDK generated successfully!";
160220
say "Output directory: $output_path";
221+
if ($generator eq 'oapi-codegen') {
222+
say "Generated file: " . File::Spec->catfile($output_path, 'client.go');
223+
}
161224
} else {
162225
die "\nError: Failed to generate SDK. Exit code: " . ($result >> 8) . "\n";
163226
}
@@ -168,7 +231,7 @@ sub get_additional_properties {
168231

169232
# Language-specific additional properties
170233
my %lang_props = (
171-
'go' => 'packageName=capiclient,isGoSubmodule=true,generateInterfaces=true',
234+
'go' => 'packageName=capiclient,generateInterfaces=true',
172235
'python' => 'packageName=capi_client,projectName=capi-client',
173236
'java' => 'groupId=org.cloudfoundry,artifactId=capi-client,artifactVersion=' . $capi_version,
174237
'javascript' => 'npmName=\@cloudfoundry/capi-client,npmVersion=' . $capi_version,
@@ -182,6 +245,55 @@ sub get_additional_properties {
182245
return $lang_props{$lang} // '';
183246
}
184247

248+
sub generate_go_mod {
249+
my ($output_dir, $version) = @_;
250+
251+
my $go_mod_path = File::Spec->catfile($output_dir, 'go.mod');
252+
253+
# Extract major version from version string (e.g., 3.195.0 -> v3)
254+
my $major_version = '';
255+
if ($version =~ /^(\d+)\./) {
256+
$major_version = "/v$1" if $1 >= 2; # Go modules use /v2, /v3, etc for v2+
257+
}
258+
259+
my $go_mod_content = <<EOF;
260+
module github.com/cloudfoundry-community/capi-openapi-go-client/capiclient${major_version}
261+
262+
go 1.21
263+
264+
require (
265+
\tgithub.com/oapi-codegen/runtime v1.1.1
266+
\tgopkg.in/yaml.v2 v2.4.0
267+
)
268+
269+
require (
270+
\tgithub.com/apapsch/go-jsonmerge/v2 v2.0.0 // indirect
271+
\tgithub.com/google/uuid v1.5.0 // indirect
272+
)
273+
EOF
274+
275+
say "Creating go.mod file...";
276+
open(my $fh, '>', $go_mod_path) or die "Cannot create go.mod: $!";
277+
print $fh $go_mod_content;
278+
close($fh);
279+
280+
# Run go mod tidy to ensure dependencies are correct
281+
my $original_dir = `pwd`;
282+
chomp $original_dir;
283+
chdir($output_dir);
284+
285+
say "Running go mod tidy...";
286+
my $tidy_result = system("go mod tidy");
287+
288+
chdir($original_dir);
289+
290+
if ($tidy_result == 0) {
291+
say "go.mod created successfully!";
292+
} else {
293+
say "Warning: go mod tidy failed. You may need to run it manually.";
294+
}
295+
}
296+
185297
sub check_deps {
186298
my @deps = qw(spruce jq);
187299
for my $dep (@deps) {

0 commit comments

Comments
 (0)