AMS service-log client: why the local openapi-generator snapshot is the wrong default
Was reviewing a merge request in uhc-account-manager (AMS) today — OCM-24913 — that removes a dependency on gitlab.cee.redhat.com/service/ocm-service-log and replaces it with a locally vendored OpenAPI snapshot + code generation. The motivation is legit: gitlab.cee.redhat.com is behind the Red Hat VPN, and Konflux CI runs on a public cluster that can't reach it. So the module has to go.
The fix they landed: fetch the OpenAPI spec from the live API (https://api.openshift.com/api/service_logs/v1/openapi), run openapi-generator-cli against it, commit the output into pkg/client/uhc/service_log/openapi/, and provide a Makefile to refresh it periodically. ~40 generated files, a .travis.yml nobody will ever run, a git_push.sh that will never be executed, and a drift risk every time the upstream API changes.
This pattern already exists in AMS for rbac/ and candlepin/ — clients for internal Red Hat services that have no public equivalents. For those, the local snapshot is the right call. For ocm-service-log, it isn't.
ocm-sdk-go is the canonical Go client for OCM services. It has a servicelogs/v1 package. And AMS already depends on ocm-sdk-go v0.1.500 — it's in go.mod. So is ocm-api-model/clientapi v0.0.454, which is where the actual types live (ocm-sdk-go re-exports them as type aliases).
The type you want is LogEntry from github.com/openshift-online/ocm-sdk-go/servicelogs/v1. It has every field AMS actually uses: ID(), Severity(), ServiceName(), Summary(), Description(), ClusterUUID(), InternalOnly(), LogType(), Timestamp(). All present.
No new dependency. No VPN-blocked module. No generated noise. The dep is already there.
The type shapes are different enough that it's a real migration, not a find-and-replace.
The openapi-generator output gives you a flat struct with pointer fields:
createdLog := &slapi.ClusterLog{}
json.Unmarshal(response.Bytes(), createdLog)
// access: *createdLog.Id, *createdLog.SeverityThe ocm-api-model type uses private fields with accessor methods and a custom jsoniter-based unmarshaler:
var createdLog v1.LogEntry
v1.UnmarshalLogEntry(bytes.NewReader(response.Bytes()), &createdLog)
// access: createdLog.ID(), createdLog.Severity()The mock also needs to change — you can't do &LogEntry{Id: util.ToPtr(...)} since the fields are private. You use the builder:
v1.NewLogEntry().ID("...").Severity(v1.SeverityInfo).Summary("...").Build()So the actual work is:
- Swap the import to
slv1 "github.com/openshift-online/ocm-sdk-go/servicelogs/v1" - Replace
json.Unmarshal(response.Bytes(), createdLog)withslv1.UnmarshalLogEntry(bytes.NewReader(response.Bytes()), createdLog)in three places - Change
*created.Id→created.ID()inpkg/triggers/actions.go - Rewrite the mock's struct literal to use the builder
- Delete all of
pkg/client/uhc/service_log/— the whole thing
Net result: ~50 line change, minus 40 generated files. Worth it.
AMS has a pattern of vendoring local OpenAPI snapshots for external dependencies. For services that don't have a public Go module (rbac, candlepin), that's fine — you have no better option. But for OCM services specifically, ocm-sdk-go exists precisely to avoid this. The question to ask before reaching for openapi-generator is: does ocm-sdk-go already have this?
For service logs, the answer is yes. The generated types in ocm-api-model are what ocm-sdk-go is built on, and they're generated from the same upstream API spec using ocm-api-metamodel — the OCM-specific codegen tool that produces idiomatic Go with proper builders, accessors, and custom JSON handling instead of the generic openapi-generator output.
If you're building something that talks to an OCM API endpoint, check ocm-sdk-go first. The snapshot pattern is a fallback, not a default.