mirror of https://github.com/kubevela/kubevela.git
Feat: SDK generating framework (#5431)
This commit is contained in:
parent
b1fc313519
commit
8ff77c4486
15
Makefile
15
Makefile
|
@ -39,16 +39,23 @@ fmt: goimports installcue
|
|||
$(CUE) fmt ./pkg/stdlib/op.cue
|
||||
$(CUE) fmt ./pkg/workflow/tasks/template/static/*
|
||||
# Run go vet against code
|
||||
|
||||
sdk_fmt:
|
||||
./hack/sdk/reviewable.sh
|
||||
|
||||
vet:
|
||||
go vet ./...
|
||||
@$(INFO) go vet
|
||||
@go vet $(shell go list ./...|grep -v scaffold)
|
||||
|
||||
staticcheck: staticchecktool
|
||||
$(STATICCHECK) ./...
|
||||
@$(INFO) staticcheck
|
||||
@$(STATICCHECK) $(shell go list ./...|grep -v scaffold)
|
||||
|
||||
lint: golangci
|
||||
$(GOLANGCILINT) run ./...
|
||||
@$(INFO) lint
|
||||
@$(GOLANGCILINT) run --skip-dirs 'scaffold'
|
||||
|
||||
reviewable: manifests fmt vet lint staticcheck helm-doc-gen
|
||||
reviewable: manifests fmt vet lint staticcheck helm-doc-gen sdk_fmt
|
||||
go mod tidy
|
||||
|
||||
# Execute auto-gen code commands and ensure branch is clean.
|
||||
|
|
Binary file not shown.
After Width: | Height: | Size: 30 KiB |
Binary file not shown.
After Width: | Height: | Size: 402 KiB |
|
@ -0,0 +1,146 @@
|
|||
# SDK Generating
|
||||
|
||||
# Introduce
|
||||
|
||||
This proposal introduces a framework which can generate KubeVela multiple language SDKs from scaffolds and the real
|
||||
world X-Definitions.
|
||||
|
||||
# Background
|
||||
|
||||
As KubeVela designs for separation of concerns, there are two roles of user: platform team and end user. They are
|
||||
dealing with Application in different ways.
|
||||
|
||||

|
||||
|
||||
### For platform team
|
||||
|
||||
When platform team users typically build a platform. The platform allow end user to pass parameters to
|
||||
component/trait/policy/workflow, then the platform assemble an Application automatically using the parameters.
|
||||
|
||||
Unfortunately, due to the extensibility of KubeVela X-Definition system, the property structure of different component (
|
||||
also for traits, workflow steps, policies) varies. In the source code, we use a `map[string]interface{}` to accept all
|
||||
kinds of properties. The unstructured code make it hard examine, modify, insert values of nested properties.
|
||||
|
||||
Before KubeVela provide the SDK, the problem is mainly solved by providing OpenAPI schema to frontend and generate the
|
||||
form dynamically. There is also an enhancement scheme: ui-schema. After filling the form, the JSON returned by frontend
|
||||
is already satisfied the specification of X-Definition properties. This is how VelaUX works when assembling Application.
|
||||
|
||||
### For end user
|
||||
|
||||
Basically there are two ways for end user to use Application:
|
||||
|
||||
1. Through dashboard provided by platform team like VelaUX. The data is kept by the internal platform. In this pattern
|
||||
user's behavior can be better guided with typed form and UI indications.
|
||||
2. Storing Application as part of the codebase. Then through some way (e.g. GitOps) the application is applied to
|
||||
cluster.
|
||||
|
||||
### Goals & Non-Goals
|
||||
|
||||
Goals include the following:
|
||||
|
||||
1. Provide a framework to generate SDK for different languages. Definition can be discovered from
|
||||
- Local directory
|
||||
- KubeVela control plane cluster
|
||||
2. Provide a Go SDK as one of the first core SDK to proof of concept.
|
||||
3. The SDK must provide functions to meet the following scenarios:
|
||||
- Be able to do CRUD on OAM concepts including Component/Trait/Workflow/Policy.
|
||||
- Be able to generate application YAML and directly apply to KubeVela control plane cluster.
|
||||
|
||||
Non-goals include the following:
|
||||
|
||||
1. We hope to provide SDK for as many languages as we can, but it's not possible to provide sdks for all languages.
|
||||
We'll just target the popular languages such as Go, Typescript, Java, Python, PHP, and etc.
|
||||
|
||||
# Proposal
|
||||
|
||||
## Benefits
|
||||
|
||||
In the aforementioned ways of using Application, there are some issues for both roles.
|
||||
|
||||
### For platform team
|
||||
|
||||
There are some scenarios which use JSON data passed from frontend is not enough:
|
||||
|
||||
1. Platform is responsible for some examination and modification on user's application:
|
||||
1. Adding some traits and policies which is taken care of by platform team. (e.g. observability)
|
||||
2. Execute some check which can't be done with OpenAPI schema and ui-schema. (e.g. call other services to access
|
||||
needed data)
|
||||
2. Platform want to execute some offline batch jobs. For example, add observability trait to existing Applications
|
||||
|
||||
In these scenarios, it's [painful](#3606) to develop with `map[string]interface{}`. With SDK, platform team can benefits
|
||||
from the typed X-Definition properties and builds, examines, modifies application more easily.
|
||||
|
||||
### For end user
|
||||
|
||||
If user is writing Application YAML, they have to refer to the doc. Although we have done some toolchain on this topic
|
||||
like `vela show`, a typed SDK also provide a possibility for end user to code Application in different languages.
|
||||
|
||||
## Design and tools
|
||||
|
||||
Thanks to the OpenAPI schema and related tool [openapi-generator](https://github.com/OpenAPITools/openapi-generator) in
|
||||
ecosystem, we can re-use the almost ready templates to generate code. The whole generation process is shown below.
|
||||
|
||||

|
||||
|
||||
**Step0**: The scaffold is written to destination if `--init` is specified. (omitted in pic)
|
||||
|
||||
**Step1**: Generate OpenAPI schema from CUE. The generated schema may not fully prepared as input of openapi-generator
|
||||
and it will be correct and adjust.
|
||||
|
||||
**Step2**: Call openapi-generator to generate SDK. After that some modification will be applied to make SDK API suited
|
||||
for KubeVela user.
|
||||
|
||||
## Examples
|
||||
|
||||
This command requires `docker` in PATH as we'll run openapi-generator in docker.
|
||||
|
||||
```shell
|
||||
# Initialize the SDK, generate API from all definitions,
|
||||
vela def gen-api --init -f /path/to/def/dir -o /path/to/sdk --lang go
|
||||
|
||||
# Incrementally generate API from definitions
|
||||
vela def gen-api -f /path/to/def/dir -o /path/to/sdk --lang go
|
||||
```
|
||||
|
||||
## Future work
|
||||
|
||||
This PR only provide a framework on generating SDK and there are lots of jobs to do to provide
|
||||
user-friendly SDKs in multiple languages.
|
||||
|
||||
### For SDK generating
|
||||
|
||||
1. Provide SDK in other languages. This is a LFX mentorship: #5365
|
||||
2. Allow user to customize the templates in Step2.
|
||||
|
||||
### For Golang SDK
|
||||
|
||||
1. The default value of properties is not set in apis. Although there is a `default` field in OpenAPI schema, it's not
|
||||
sed in generated code. We can set the default value in code.
|
||||
2. Parameter validation using CUE. Sometimes the `parameter` field in CUE have more restrictions. We can use CUE to
|
||||
validate the parameters. (e.g. `parameter: { a : >=0 }`). For golang there is
|
||||
a [package](https://pkg.go.dev/cuelang.org/go/encoding/gocode/gocodec) that can help.
|
||||
3. Validate if the trait type is suited to be attached to the component. For example, `K8s-object` can only
|
||||
accept `labels` and `annotation` trait.
|
||||
|
||||
## Known Issues
|
||||
|
||||
There are some problems when generating:
|
||||
|
||||
1. `apply-terraform-provider` workflow step. Parameter are like below will cause the problem. Related
|
||||
issue: https://github.com/cue-lang/cue/issues/2259
|
||||
|
||||
```cue
|
||||
basic: { info: string }
|
||||
#A:{
|
||||
info
|
||||
a: string
|
||||
}
|
||||
#B:{
|
||||
info
|
||||
b: string
|
||||
}
|
||||
parameter: *#A|#B
|
||||
```
|
||||
|
||||
2. `label` and `annotation` trait. openapi-generator doesn't support top-level schema to be a map. The two traits'
|
||||
properties is actually just `map[string]string` so it's easy to build it without using SDK.
|
|
@ -0,0 +1,129 @@
|
|||
# SDK 生成
|
||||
|
||||
# 介绍
|
||||
|
||||
该提案引入了一个框架,该框架可以从脚手架以及 X-Definitions 生成 KubeVela SDK.
|
||||
|
||||
# 背景
|
||||
|
||||
由于 KubeVela 设计了关注点分离,因此有两个用户角色:平台团队和最终用户。他们以不同的方式处理Application。
|
||||
|
||||

|
||||
|
||||
### 对于平台团队
|
||||
|
||||
当平台团队用户组装Application,填写组件、特性和工作流程步骤时,他们实际上是在组装Application YAML.不幸的是,由于 KubeVela
|
||||
X-Definition 系统的可扩展性,不同组件(包括特征、工作流步骤、策略)的属性结构各不相同。在源代码中,我们使用 `map[string]interface{}`
|
||||
来接受各种属性。非结构化代码使得难以检查、修改、插入嵌套属性的值。
|
||||
|
||||
当平台团队用户通常构建平台时。该平台允许最终用户将参数传递给组件/运维特征/策略/工作流,然后平台使用参数自动组装应用程序。
|
||||
不幸的是,由于 KubeVela X-Definition
|
||||
系统的可扩展性,不同组件(同样对于特征、工作流步骤、策略)也是不同的。在源代码中,我们使用一个 `map[string]interface{}` 来接受所有
|
||||
各种属性。非结构化代码使得难以检查、修改、插入嵌套属性的值。
|
||||
在 KubeVela 提供 SDK 之前,主要通过提供 OpenAPI Schema 给前端,由前端动态生成表单来解决问题。还有一个增强方案:UI-Schema。在填充表单之后,前端返回的
|
||||
JSON 已经满足 X-Definition 属性的规范。这也是 VelaUX 在组装Application时的工作方式。
|
||||
|
||||
### 对于最终用户
|
||||
|
||||
基本上,最终用户使用Application有两种方式:
|
||||
|
||||
1. 通过 VelaUX 等平台团队提供的仪表板。数据由内部平台保存。在这个模式中,用户的行为可以通过输入表单和 UI 指示得到更好的指导。
|
||||
2. 将Application存储为代码库的一部分。然后通过某种方式(例如 GitOps)将Application应用到集群中。
|
||||
|
||||
### 目标和非目标
|
||||
|
||||
目标包括以下内容:
|
||||
|
||||
1. 提供一个框架来生成不同语言的 SDK.
|
||||
2. 提供一个 Go SDK 作为示例。
|
||||
|
||||
非目标包括:
|
||||
|
||||
1. 虽然我们希望尽可能多地提供不同语言SDK,但是我们不会提供所有语言的SDK,我们的目标是为比较流行的语言提供SDK。
|
||||
|
||||
# 提案
|
||||
|
||||
## 好处
|
||||
|
||||
在上述使用Application的方式中,两个角色都面对一些问题。
|
||||
|
||||
### 对于平台团队
|
||||
|
||||
有一些场景使用从前端传递的 JSON 数据是不够的:
|
||||
|
||||
1. 平台负责对用户的Application进行一些审核和修改:
|
||||
1. 添加一些由平台团队负责的特性和策略。(例如可观测性)
|
||||
2. 执行一些无法使用 OpenAPI schema和 UI 模式完成的检查。(例如,调用其他服务以访问所需数据)
|
||||
2. 平台想要执行一些脱机批处理作业。例如,向现有Application添加可观测性特征
|
||||
|
||||
在这些场景中,用`map[string]interface{}`来开发是[痛苦的](#3606)的。有了 SDK,平台团队可以从类型化的 X-Definition
|
||||
属性中受益,并更轻松地构建、检查和修改Application。
|
||||
|
||||
### 对于最终用户
|
||||
|
||||
如果用户正在编写Application YAML,他们必须参考文档。虽然我们已经在这个主题上做了一些工具链,例如 `vela show` ,但类型化 SDK
|
||||
也为最终用户提供了用不同语言编写Application的可能性。
|
||||
|
||||
## 设计和工具
|
||||
|
||||
感谢 OpenAPI Schema和其生态中的相关工具: [openapi-generator](https://github.com/OpenAPITools/openapi-generator)
|
||||
,我们可以重复使用几乎准备好的模板来生成代码。整个生成过程如下所示。
|
||||
|
||||

|
||||
|
||||
**第0步**:如果 `--init` 指定了,则将脚手架写入目标位置。(图中省略)
|
||||
|
||||
**第1步**:从 CUE 生成 OpenAPI Schema。生成的Schema直接作为 OpenAPI-Generator 的输入可能有一些问题,它将被纠正和调整。
|
||||
|
||||
**第2步**:调用 openapi-generator 生成 SDK.在此之后,将应用一些修改,使 SDK API 适合 KubeVela 用户。
|
||||
|
||||
## 例子
|
||||
|
||||
这个命令在路径中需要 `docker`,因为我们正在利用 OpenAPI-Generator,将在 docker 中运行该程序。
|
||||
|
||||
```shell
|
||||
# Initialize the SDK, generate API from all definitions,
|
||||
vela def gen-api -f /path/to/def/dir -o /path/to/sdk --init
|
||||
|
||||
# Incrementally generate API from definitions
|
||||
vela def gen-api -f /path/to/def/dir -o /path/to/sdk
|
||||
```
|
||||
|
||||
## 未来的工作
|
||||
|
||||
这个 PR 只提供了一个生成 SDK 的框架,还有很多工作要做,以提供多种语言的用户友好的 SDK.
|
||||
|
||||
### 对于 SDK 生成本身
|
||||
|
||||
1. 提供其他语言的 SDK.这是一个 LFX 指导项目 #5365
|
||||
2. 允许用户自定义步骤 2 中的模板。
|
||||
|
||||
### 用于 Golang SDK
|
||||
|
||||
1 在 API 中, 属性的默认值没有设置。尽管 OpenAPI schema 中有一个“default ”字段,但它在生成的代码中不是 sed.我们可以在代码中设置默认值。
|
||||
|
||||
2. 使用 CUE 进行参数验证。有时,CUE 中的“参数”字段有更多限制。我们可以使用 CUE 来验证参数。(例如:` 参数:{a:>=0}`)。对于
|
||||
Golang,有一个 [套餐] (https://pkg.go.dev/cuelang.org/go/encoding/gocode/gocodec)可以提供帮助。
|
||||
验证特征类型是否适合附加到组件。例如,' k8s-object '只能接受' label '和' annotation '特征。
|
||||
|
||||
## 已知问题
|
||||
|
||||
生成 SDK 时存在一些问题:
|
||||
|
||||
1. `apply-terraform-provider` 工作流程步骤。如下所示的参数将导致问题。相关问题:https://github.com/cue-lang/cue/issues/2259
|
||||
|
||||
```cue
|
||||
basic: { info: string }
|
||||
#A:{
|
||||
info
|
||||
a: string
|
||||
}
|
||||
#B:{
|
||||
info
|
||||
b: string
|
||||
}
|
||||
parameter: *#A|#B
|
||||
```
|
||||
|
||||
2. `label` 和 `annotation` 特性。OpenAPI-Generator
|
||||
不支持将顶级Schema作为哈希表。不过这两个特征的属性实际上是 `map[string]string`, 很容易在不使用 SDK 的情况下构建它。
|
17
go.mod
17
go.mod
|
@ -29,8 +29,12 @@ require (
|
|||
github.com/emicklei/go-restful-openapi/v2 v2.9.1
|
||||
github.com/emicklei/go-restful/v3 v3.8.0
|
||||
github.com/evanphx/json-patch v5.6.0+incompatible
|
||||
github.com/fatih/camelcase v1.0.0
|
||||
github.com/fatih/color v1.14.1
|
||||
github.com/evanphx/json-patch/v5 v5.6.0 // indirect
|
||||
github.com/exponent-io/jsonpath v0.0.0-20151013193312-d6023ce2651d // indirect
|
||||
github.com/facebookgo/structtag v0.0.0-20150214074306-217e25fb9691 // indirect
|
||||
github.com/fatih/camelcase v1.0.0 // indirect
|
||||
github.com/fatih/color v1.13.0
|
||||
github.com/felixge/httpsnoop v1.0.1 // indirect
|
||||
github.com/fluxcd/helm-controller/api v0.21.0
|
||||
github.com/fluxcd/source-controller/api v0.24.4
|
||||
github.com/form3tech-oss/jwt-go v3.2.3+incompatible
|
||||
|
@ -117,6 +121,11 @@ require (
|
|||
sigs.k8s.io/yaml v1.3.0
|
||||
)
|
||||
|
||||
require (
|
||||
github.com/dave/jennifer v1.6.0
|
||||
github.com/ettle/strcase v0.1.1
|
||||
)
|
||||
|
||||
require (
|
||||
github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1 // indirect
|
||||
github.com/Azure/go-autorest v14.2.0+incompatible // indirect
|
||||
|
@ -173,10 +182,6 @@ require (
|
|||
github.com/docker/go-units v0.5.0 // indirect
|
||||
github.com/emicklei/proto v1.10.0 // indirect
|
||||
github.com/emirpasic/gods v1.18.1 // indirect
|
||||
github.com/evanphx/json-patch/v5 v5.6.0 // indirect
|
||||
github.com/exponent-io/jsonpath v0.0.0-20151013193312-d6023ce2651d // indirect
|
||||
github.com/facebookgo/structtag v0.0.0-20150214074306-217e25fb9691 // indirect
|
||||
github.com/felixge/httpsnoop v1.0.1 // indirect
|
||||
github.com/fluxcd/pkg/apis/acl v0.0.3 // indirect
|
||||
github.com/fluxcd/pkg/apis/kustomize v0.3.3 // indirect
|
||||
github.com/fluxcd/pkg/apis/meta v0.13.0 // indirect
|
||||
|
|
9
go.sum
9
go.sum
|
@ -339,6 +339,8 @@ github.com/cue-exp/kubevelafix v0.0.0-20220922150317-aead819d979d/go.mod h1:SyTr
|
|||
github.com/cyphar/filepath-securejoin v0.2.3 h1:YX6ebbZCZP7VkM3scTTokDgBL2TY741X51MTk3ycuNI=
|
||||
github.com/cyphar/filepath-securejoin v0.2.3/go.mod h1:aPGpWjXOXUn2NCNjFvBE6aRxGGx79pTxQpKOJNYHHl4=
|
||||
github.com/dave/jennifer v1.2.0/go.mod h1:fIb+770HOpJ2fmN9EPPKOqm1vMGhB+TwXKMZhrIygKg=
|
||||
github.com/dave/jennifer v1.6.0 h1:MQ/6emI2xM7wt0tJzJzyUik2Q3Tcn2eE0vtYgh4GPVI=
|
||||
github.com/dave/jennifer v1.6.0/go.mod h1:AxTG893FiZKqxy3FP1kL80VMshSMuz2G+EgvszgGRnk=
|
||||
github.com/davecgh/go-spew v0.0.0-20151105211317-5215b55f46b2/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||
|
@ -408,6 +410,8 @@ github.com/envoyproxy/go-control-plane v0.9.9-0.20210512163311-63b5d3c536b0/go.m
|
|||
github.com/envoyproxy/go-control-plane v0.9.10-0.20210907150352-cf90f659a021/go.mod h1:AFq3mo9L8Lqqiid3OhADV3RfLJnjiw63cSpi+fDTRC0=
|
||||
github.com/envoyproxy/go-control-plane v0.10.2-0.20220325020618-49ff273808a1/go.mod h1:KJwIaB5Mv44NWtYuAOFCVOjcI94vtpEz2JU/D2v6IjE=
|
||||
github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
|
||||
github.com/ettle/strcase v0.1.1 h1:htFueZyVeE1XNnMEfbqp5r67qAN/4r6ya1ysq8Q+Zcw=
|
||||
github.com/ettle/strcase v0.1.1/go.mod h1:hzDLsPC7/lwKyBOywSHEP89nt2pDgdy+No1NBA9o9VY=
|
||||
github.com/evanphx/json-patch v0.0.0-20190203023257-5858425f7550/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk=
|
||||
github.com/evanphx/json-patch v0.5.2/go.mod h1:ZWS5hhDbVDyob71nXKNL0+PWn6ToqBHMikGIFbs31qQ=
|
||||
github.com/evanphx/json-patch v4.2.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk=
|
||||
|
@ -433,8 +437,8 @@ github.com/fatih/color v0.0.0-20180516100307-2d684516a886/go.mod h1:Zm6kSWBoL9ey
|
|||
github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4=
|
||||
github.com/fatih/color v1.9.0/go.mod h1:eQcE1qtQxscV5RaZvpXrrb8Drkc3/DdQ+uUYCNjL+zU=
|
||||
github.com/fatih/color v1.12.0/go.mod h1:ELkj/draVOlAH/xkhN6mQ50Qd0MPOk5AAr3maGEBuJM=
|
||||
github.com/fatih/color v1.14.1 h1:qfhVLaG5s+nCROl1zJsZRxFeYrHLqWroPOQ8BWiNb4w=
|
||||
github.com/fatih/color v1.14.1/go.mod h1:2oHN61fhTpgcxD3TSWCgKDiH1+x4OiDVVGH8WlgGZGg=
|
||||
github.com/fatih/color v1.13.0 h1:8LOYc1KYPPmyKMuN8QV2DNRWNbLo6LZ0iLs8+mlH53w=
|
||||
github.com/fatih/color v1.13.0/go.mod h1:kLAiJbzzSOZDVNGyDpeOxJ47H46qBXwg5ILebYFFOfk=
|
||||
github.com/fatih/structtag v1.1.0/go.mod h1:mBJUNpUnHmRKrKlQQlmCrh5PuhftFbNv8Ys4/aAZl94=
|
||||
github.com/felixge/httpsnoop v1.0.1 h1:lvB5Jl89CsZtGIWuTcDM1E/vkVs49/Ml7JJe07l8SPQ=
|
||||
github.com/felixge/httpsnoop v1.0.1/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U=
|
||||
|
@ -1042,6 +1046,7 @@ github.com/mattn/go-colorable v0.1.4/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVc
|
|||
github.com/mattn/go-colorable v0.1.6/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc=
|
||||
github.com/mattn/go-colorable v0.1.7/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc=
|
||||
github.com/mattn/go-colorable v0.1.8/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc=
|
||||
github.com/mattn/go-colorable v0.1.9/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc=
|
||||
github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA=
|
||||
github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg=
|
||||
github.com/mattn/go-ieproxy v0.0.0-20190610004146-91bb50d98149/go.mod h1:31jz6HNzdxOmlERGGEc4v/dMssOfmp2p5bT/okiKFFc=
|
||||
|
|
|
@ -0,0 +1,30 @@
|
|||
#!/usr/bin/env bash
|
||||
|
||||
# Copyright 2022 The KubeVela Authors.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
set -o errexit
|
||||
set -o nounset
|
||||
set -o pipefail
|
||||
|
||||
# This script helps to lint and format the code in the SDK scaffold.
|
||||
|
||||
GO_SCAFFOLD_DIR=$(pwd)/pkg/definition/gen_sdk/_scaffold/go
|
||||
|
||||
pushd ${GO_SCAFFOLD_DIR}
|
||||
mv go.mod_ go.mod
|
||||
go version
|
||||
go mod tidy
|
||||
mv go.mod go.mod_
|
||||
popd
|
|
@ -15,7 +15,7 @@ RED := $(shell printf "\033[31m")
|
|||
GREEN := $(shell printf "\033[32m")
|
||||
CNone := $(shell printf "\033[0m")
|
||||
|
||||
INFO = echo ${TIME} ${BLUE}[ .. ]${CNone}
|
||||
INFO = echo ${TIME} ${BLUE}[INFO]${CNone}
|
||||
WARN = echo ${TIME} ${YELLOW}[WARN]${CNone}
|
||||
ERR = echo ${TIME} ${RED}[FAIL]${CNone}
|
||||
OK = echo ${TIME} ${GREEN}[ OK ]${CNone}
|
||||
|
|
|
@ -94,6 +94,15 @@ var (
|
|||
common.PolicyType: oam.LabelPolicyDefinitionName,
|
||||
common.WorkflowStepType: oam.LabelWorkflowStepDefinitionName,
|
||||
}
|
||||
// DefinitionKindToType maps the definition kinds to a shorter type
|
||||
DefinitionKindToType = map[string]string{
|
||||
v1beta1.ComponentDefinitionKind: "component",
|
||||
v1beta1.TraitDefinitionKind: "trait",
|
||||
v1beta1.PolicyDefinitionKind: "policy",
|
||||
v1beta1.WorkloadDefinitionKind: "workload",
|
||||
v1beta1.ScopeDefinitionKind: "scope",
|
||||
v1beta1.WorkflowStepDefinitionKind: "workflow-step",
|
||||
}
|
||||
)
|
||||
|
||||
// Definition the general struct for handling all kinds of definitions like ComponentDefinition or TraitDefinition
|
||||
|
|
|
@ -0,0 +1,51 @@
|
|||
# KubeVela Go SDK
|
||||
|
||||
This is a Go SDK for KubeVela generated via vela CLI
|
||||
|
||||
## Generating
|
||||
|
||||
1. Init the SDK project
|
||||
```shell
|
||||
vela def gen-api --lang go --output sdk -f /path/to/your/definition/dir --init
|
||||
```
|
||||
|
||||
2. Generate any definition
|
||||
```shell
|
||||
vela def gen-api --lang go --output sdk -f /another/definition/dir
|
||||
```
|
||||
|
||||
## Features:
|
||||
|
||||
- 🔧Application manipulating
|
||||
- [x] Add Components/Traits/Workflow Steps/Policies
|
||||
- [x] Setting Workflow Mode
|
||||
- [x] Convert to K8s Application Object
|
||||
- [x] Convert from K8s Application Object
|
||||
- [x] Get Components/Traits/Workflow Steps/Policies from app
|
||||
- [ ] Referring to external Workflow object.
|
||||
- 🔍Application client
|
||||
- [x] Create/Delete/Patch/Update Application
|
||||
- [x] List/Get Application
|
||||
|
||||
## Example
|
||||
|
||||
See [example](example) for more details.
|
||||
|
||||
## Future Work
|
||||
|
||||
There is some proper features for this SDK and possible to be added in the future. If you are interested in any of them, please feel free to contact us.
|
||||
|
||||
- Part of vela CLI functions
|
||||
- Application logs/exec/port-forward
|
||||
- Application resource in tree structure
|
||||
- VelaQL
|
||||
- ...
|
||||
- Standalone workflow functions
|
||||
- CRUD of workflow
|
||||
|
||||
## Known issues
|
||||
|
||||
There are some known issues in this SDK, please be aware of them before using it.
|
||||
|
||||
1. `labels` and `annotations` trait is not working as expected.
|
||||
2. `notification` workflow-step's parameter is not exactly the same as the one in KubeVela.
|
|
@ -0,0 +1,120 @@
|
|||
module github.com/kubevela/vela-go-sdk
|
||||
|
||||
go 1.19
|
||||
|
||||
require (
|
||||
github.com/oam-dev/kubevela-core-api v1.5.8
|
||||
github.com/pkg/errors v0.9.1
|
||||
k8s.io/apimachinery v0.23.6
|
||||
k8s.io/client-go v0.23.6
|
||||
sigs.k8s.io/controller-runtime v0.11.2
|
||||
)
|
||||
|
||||
require (
|
||||
cuelang.org/go v0.5.0-alpha.1 // indirect
|
||||
github.com/NYTimes/gziphandler v1.1.1 // indirect
|
||||
github.com/PuerkitoBio/purell v1.1.1 // indirect
|
||||
github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578 // indirect
|
||||
github.com/beorn7/perks v1.0.1 // indirect
|
||||
github.com/blang/semver v3.5.1+incompatible // indirect
|
||||
github.com/cespare/xxhash/v2 v2.1.2 // indirect
|
||||
github.com/cockroachdb/apd/v2 v2.0.2 // indirect
|
||||
github.com/coreos/go-semver v0.3.0 // indirect
|
||||
github.com/coreos/go-systemd/v22 v22.3.2 // indirect
|
||||
github.com/crossplane/crossplane-runtime v0.14.1-0.20210722005935-0b469fcc77cd // indirect
|
||||
github.com/davecgh/go-spew v1.1.1 // indirect
|
||||
github.com/emicklei/go-restful v2.9.5+incompatible // indirect
|
||||
github.com/evanphx/json-patch v4.12.0+incompatible // indirect
|
||||
github.com/felixge/httpsnoop v1.0.1 // indirect
|
||||
github.com/fsnotify/fsnotify v1.5.1 // indirect
|
||||
github.com/go-logr/logr v1.2.2 // indirect
|
||||
github.com/go-openapi/jsonpointer v0.19.5 // indirect
|
||||
github.com/go-openapi/jsonreference v0.19.5 // indirect
|
||||
github.com/go-openapi/swag v0.19.14 // indirect
|
||||
github.com/gogo/protobuf v1.3.2 // indirect
|
||||
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect
|
||||
github.com/golang/protobuf v1.5.2 // indirect
|
||||
github.com/google/go-cmp v0.5.8 // indirect
|
||||
github.com/google/gofuzz v1.2.0 // indirect
|
||||
github.com/google/uuid v1.3.0 // indirect
|
||||
github.com/googleapis/gnostic v0.5.5 // indirect
|
||||
github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0 // indirect
|
||||
github.com/grpc-ecosystem/grpc-gateway v1.16.0 // indirect
|
||||
github.com/imdario/mergo v0.3.12 // indirect
|
||||
github.com/inconshreveable/mousetrap v1.0.0 // indirect
|
||||
github.com/josharian/intern v1.0.0 // indirect
|
||||
github.com/json-iterator/go v1.1.12 // indirect
|
||||
github.com/mailru/easyjson v0.7.6 // indirect
|
||||
github.com/matttproud/golang_protobuf_extensions v1.0.2-0.20181231171920-c182affec369 // indirect
|
||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
|
||||
github.com/modern-go/reflect2 v1.0.2 // indirect
|
||||
github.com/mpvl/unique v0.0.0-20150818121801-cbe035fff7de // indirect
|
||||
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect
|
||||
github.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f // indirect
|
||||
github.com/oam-dev/cluster-gateway v1.4.0 // indirect
|
||||
github.com/oam-dev/terraform-controller v0.7.0 // indirect
|
||||
github.com/onsi/gomega v1.20.2 // indirect
|
||||
github.com/openshift/library-go v0.0.0-20220112153822-ac82336bd076 // indirect
|
||||
github.com/prometheus/client_golang v1.12.2 // indirect
|
||||
github.com/prometheus/client_model v0.2.0 // indirect
|
||||
github.com/prometheus/common v0.32.1 // indirect
|
||||
github.com/prometheus/procfs v0.7.3 // indirect
|
||||
github.com/spf13/cobra v1.4.0 // indirect
|
||||
github.com/spf13/pflag v1.0.5 // indirect
|
||||
github.com/stretchr/testify v1.7.1 // indirect
|
||||
go.etcd.io/etcd/api/v3 v3.5.0 // indirect
|
||||
go.etcd.io/etcd/client/pkg/v3 v3.5.0 // indirect
|
||||
go.etcd.io/etcd/client/v3 v3.5.0 // indirect
|
||||
go.opentelemetry.io/contrib v0.20.0 // indirect
|
||||
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.20.0 // indirect
|
||||
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.20.0 // indirect
|
||||
go.opentelemetry.io/otel v0.20.0 // indirect
|
||||
go.opentelemetry.io/otel/exporters/otlp v0.20.0 // indirect
|
||||
go.opentelemetry.io/otel/metric v0.20.0 // indirect
|
||||
go.opentelemetry.io/otel/sdk v0.20.0 // indirect
|
||||
go.opentelemetry.io/otel/sdk/export/metric v0.20.0 // indirect
|
||||
go.opentelemetry.io/otel/sdk/metric v0.20.0 // indirect
|
||||
go.opentelemetry.io/otel/trace v0.20.0 // indirect
|
||||
go.opentelemetry.io/proto/otlp v0.7.0 // indirect
|
||||
go.uber.org/atomic v1.9.0 // indirect
|
||||
go.uber.org/multierr v1.7.0 // indirect
|
||||
go.uber.org/zap v1.21.0 // indirect
|
||||
golang.org/x/crypto v0.0.0-20220926161630-eccd6366d1be // indirect
|
||||
golang.org/x/net v0.0.0-20220906165146-f3363e06e74c // indirect
|
||||
golang.org/x/oauth2 v0.0.0-20220622183110-fd043fe589d2 // indirect
|
||||
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4 // indirect
|
||||
golang.org/x/sys v0.0.0-20220928140112-f11e5e49a4ec // indirect
|
||||
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211 // indirect
|
||||
golang.org/x/text v0.3.8 // indirect
|
||||
golang.org/x/time v0.0.0-20220922220347-f3bd1da661af // indirect
|
||||
google.golang.org/appengine v1.6.7 // indirect
|
||||
google.golang.org/genproto v0.0.0-20220628213854-d9e0b6570c03 // indirect
|
||||
google.golang.org/grpc v1.48.0 // indirect
|
||||
google.golang.org/protobuf v1.28.0 // indirect
|
||||
gopkg.in/inf.v0 v0.9.1 // indirect
|
||||
gopkg.in/yaml.v2 v2.4.0 // indirect
|
||||
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||
k8s.io/api v0.23.6 // indirect
|
||||
k8s.io/apiextensions-apiserver v0.23.6 // indirect
|
||||
k8s.io/apiserver v0.23.6 // indirect
|
||||
k8s.io/component-base v0.23.6 // indirect
|
||||
k8s.io/klog v1.0.0 // indirect
|
||||
k8s.io/klog/v2 v2.60.1 // indirect
|
||||
k8s.io/kube-openapi v0.0.0-20211115234752-e816edb12b65 // indirect
|
||||
k8s.io/utils v0.0.0-20220210201930-3a6ce19ff2f9 // indirect
|
||||
open-cluster-management.io/api v0.7.0 // indirect
|
||||
sigs.k8s.io/apiserver-network-proxy v0.0.30 // indirect
|
||||
sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.0.30 // indirect
|
||||
sigs.k8s.io/apiserver-runtime v1.1.1 // indirect
|
||||
sigs.k8s.io/json v0.0.0-20211208200746-9f7c6b3444d2 // indirect
|
||||
sigs.k8s.io/structured-merge-diff/v4 v4.2.1 // indirect
|
||||
sigs.k8s.io/yaml v1.3.0 // indirect
|
||||
)
|
||||
|
||||
replace (
|
||||
cuelang.org/go => github.com/kubevela/cue v0.4.4-0.20221107123854-a976b0e340be
|
||||
github.com/docker/cli => github.com/docker/cli v20.10.9+incompatible
|
||||
github.com/docker/docker => github.com/moby/moby v17.12.0-ce-rc1.0.20200618181300-9dc6525e6118+incompatible
|
||||
github.com/wercker/stern => github.com/oam-dev/stern v1.13.2
|
||||
sigs.k8s.io/apiserver-network-proxy/konnectivity-client => sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.0.24
|
||||
)
|
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,312 @@
|
|||
/*
|
||||
Copyright 2021 The KubeVela Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package common
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
|
||||
. "github.com/kubevela/vela-go-sdk/pkg/apis"
|
||||
|
||||
"github.com/oam-dev/kubevela-core-api/apis/core.oam.dev/common"
|
||||
"github.com/oam-dev/kubevela-core-api/apis/core.oam.dev/v1beta1"
|
||||
)
|
||||
|
||||
type ApplicationBuilder struct {
|
||||
name string
|
||||
namespace string
|
||||
labels map[string]string
|
||||
annotations map[string]string
|
||||
resourceVersion string
|
||||
|
||||
components []Component
|
||||
steps []WorkflowStep
|
||||
policies []Policy
|
||||
workflowMode v1beta1.WorkflowExecuteMode
|
||||
}
|
||||
|
||||
// AddComponent set component to application, use component name to match
|
||||
// TODO: behavior when component not found?
|
||||
// 1. return error
|
||||
// 2. ignore
|
||||
// 3. add a new one to application
|
||||
func (a *ApplicationBuilder) AddComponent(component Component) TypedApplication {
|
||||
for i, c := range a.components {
|
||||
if c.ComponentName() == component.ComponentName() {
|
||||
a.components[i] = component
|
||||
return a
|
||||
}
|
||||
}
|
||||
return a
|
||||
}
|
||||
|
||||
func (a *ApplicationBuilder) AddWorkflowStep(step WorkflowStep) TypedApplication {
|
||||
for i, s := range a.steps {
|
||||
if s.WorkflowStepName() == step.WorkflowStepName() {
|
||||
a.steps[i] = step
|
||||
return a
|
||||
}
|
||||
}
|
||||
return a
|
||||
}
|
||||
|
||||
func (a *ApplicationBuilder) AddPolicy(policy Policy) TypedApplication {
|
||||
for i, p := range a.policies {
|
||||
if p.PolicyName() == policy.PolicyName() {
|
||||
a.policies[i] = policy
|
||||
return a
|
||||
}
|
||||
}
|
||||
return a
|
||||
}
|
||||
|
||||
func (a *ApplicationBuilder) GetComponentByName(name string) Component {
|
||||
for _, c := range a.components {
|
||||
if c.ComponentName() == name {
|
||||
return c
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (a *ApplicationBuilder) GetComponentsByType(typ string) []Component {
|
||||
var result []Component
|
||||
for _, c := range a.components {
|
||||
if c.DefType() == typ {
|
||||
result = append(result, c)
|
||||
}
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
func (a *ApplicationBuilder) GetWorkflowStepByName(name string) WorkflowStep {
|
||||
for _, s := range a.steps {
|
||||
if s.WorkflowStepName() == name {
|
||||
return s
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (a *ApplicationBuilder) GetWorkflowStepsByType(typ string) []WorkflowStep {
|
||||
var result []WorkflowStep
|
||||
for _, s := range a.steps {
|
||||
if s.DefType() == typ {
|
||||
result = append(result, s)
|
||||
}
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
func (a *ApplicationBuilder) GetPolicyByName(name string) Policy {
|
||||
for _, p := range a.policies {
|
||||
if p.PolicyName() == name {
|
||||
return p
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (a *ApplicationBuilder) GetPoliciesByType(typ string) []Policy {
|
||||
var result []Policy
|
||||
for _, p := range a.policies {
|
||||
if p.DefType() == typ {
|
||||
result = append(result, p)
|
||||
}
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
func (a *ApplicationBuilder) WithWorkflowSteps(step ...WorkflowStep) TypedApplication {
|
||||
a.steps = append(a.steps, step...)
|
||||
return a
|
||||
}
|
||||
|
||||
// WithComponents append components to application
|
||||
func (a *ApplicationBuilder) WithComponents(component ...Component) TypedApplication {
|
||||
a.components = append(a.components, component...)
|
||||
return a
|
||||
}
|
||||
|
||||
// WithPolicies append policies to application
|
||||
func (a *ApplicationBuilder) WithPolicies(policy ...Policy) TypedApplication {
|
||||
a.policies = append(a.policies, policy...)
|
||||
return a
|
||||
}
|
||||
|
||||
// WithWorkflowMode set the workflow mode of application
|
||||
func (a *ApplicationBuilder) WithWorkflowMode(steps, subSteps common.WorkflowMode) TypedApplication {
|
||||
a.workflowMode.Steps = steps
|
||||
a.workflowMode.SubSteps = subSteps
|
||||
return a
|
||||
}
|
||||
|
||||
func (a *ApplicationBuilder) Name(name string) TypedApplication {
|
||||
a.name = name
|
||||
return a
|
||||
}
|
||||
|
||||
func (a *ApplicationBuilder) Namespace(namespace string) TypedApplication {
|
||||
a.namespace = namespace
|
||||
return a
|
||||
}
|
||||
|
||||
func (a *ApplicationBuilder) Labels(labels map[string]string) TypedApplication {
|
||||
a.labels = labels
|
||||
return a
|
||||
}
|
||||
|
||||
func (a *ApplicationBuilder) Annotations(annotations map[string]string) TypedApplication {
|
||||
a.annotations = annotations
|
||||
return a
|
||||
}
|
||||
|
||||
func (a *ApplicationBuilder) GetName() string {
|
||||
return a.name
|
||||
}
|
||||
|
||||
func (a *ApplicationBuilder) GetNamespace() string {
|
||||
return a.namespace
|
||||
}
|
||||
|
||||
func (a *ApplicationBuilder) GetLabels() map[string]string {
|
||||
return a.labels
|
||||
}
|
||||
|
||||
func (a *ApplicationBuilder) GetAnnotations() map[string]string {
|
||||
return a.annotations
|
||||
}
|
||||
|
||||
// New creates a new application with the given components.
|
||||
func New() TypedApplication {
|
||||
app := &ApplicationBuilder{
|
||||
components: make([]Component, 0),
|
||||
steps: make([]WorkflowStep, 0),
|
||||
policies: make([]Policy, 0),
|
||||
}
|
||||
return app
|
||||
}
|
||||
|
||||
func (a *ApplicationBuilder) Build() v1beta1.Application {
|
||||
components := make([]common.ApplicationComponent, 0, len(a.components))
|
||||
for _, component := range a.components {
|
||||
components = append(components, component.Build())
|
||||
}
|
||||
steps := make([]v1beta1.WorkflowStep, 0, len(a.steps))
|
||||
for _, step := range a.steps {
|
||||
steps = append(steps, step.Build())
|
||||
}
|
||||
policies := make([]v1beta1.AppPolicy, 0)
|
||||
for _, policy := range a.policies {
|
||||
policies = append(policies, policy.Build())
|
||||
}
|
||||
|
||||
res := v1beta1.Application{
|
||||
TypeMeta: v1.TypeMeta{
|
||||
Kind: v1beta1.ApplicationKind,
|
||||
APIVersion: v1beta1.SchemeGroupVersion.String(),
|
||||
},
|
||||
ObjectMeta: v1.ObjectMeta{
|
||||
Name: a.name,
|
||||
Namespace: a.namespace,
|
||||
ResourceVersion: a.resourceVersion,
|
||||
},
|
||||
Spec: v1beta1.ApplicationSpec{
|
||||
Components: components,
|
||||
Workflow: &v1beta1.Workflow{
|
||||
Steps: steps,
|
||||
},
|
||||
Policies: policies,
|
||||
},
|
||||
}
|
||||
return res
|
||||
}
|
||||
|
||||
func (a *ApplicationBuilder) ToYAML() (string, error) {
|
||||
app := a.Build()
|
||||
marshal, err := json.Marshal(app)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return string(marshal), nil
|
||||
}
|
||||
|
||||
func FromK8sObject(app *v1beta1.Application) (TypedApplication, error) {
|
||||
a := &ApplicationBuilder{}
|
||||
a.Name(app.Name)
|
||||
a.Namespace(app.Namespace)
|
||||
a.resourceVersion = app.ResourceVersion
|
||||
|
||||
for _, comp := range app.Spec.Components {
|
||||
c, err := FromComponent(&comp)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "convert component from k8s object")
|
||||
}
|
||||
a.WithComponents(c)
|
||||
}
|
||||
if app.Spec.Workflow != nil {
|
||||
for _, step := range app.Spec.Workflow.Steps {
|
||||
s, err := FromWorkflowStep(&step)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "convert workflow step from k8s object")
|
||||
}
|
||||
a.WithWorkflowSteps(s)
|
||||
}
|
||||
}
|
||||
for _, policy := range app.Spec.Policies {
|
||||
p, err := FromPolicy(&policy)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "convert policy from k8s object")
|
||||
}
|
||||
a.WithPolicies(p)
|
||||
}
|
||||
return a, nil
|
||||
}
|
||||
|
||||
func FromComponent(component *common.ApplicationComponent) (Component, error) {
|
||||
build, ok := ComponentsBuilders[component.Type]
|
||||
if !ok {
|
||||
return nil, errors.Errorf("no component type %s registered", component.Type)
|
||||
}
|
||||
return build(*component)
|
||||
}
|
||||
|
||||
func FromWorkflowStep(step *v1beta1.WorkflowStep) (WorkflowStep, error) {
|
||||
build, ok := WorkflowStepsBuilders[step.Type]
|
||||
if !ok {
|
||||
return nil, errors.Errorf("no workflow step type %s registered", step.Type)
|
||||
}
|
||||
return build(*step)
|
||||
}
|
||||
|
||||
func FromPolicy(policy *v1beta1.AppPolicy) (Policy, error) {
|
||||
build, ok := PoliciesBuilders[policy.Type]
|
||||
if !ok {
|
||||
return nil, errors.Errorf("no policy type %s registered", policy.Type)
|
||||
}
|
||||
return build(*policy)
|
||||
}
|
||||
|
||||
func FromTrait(trait *common.ApplicationTrait) (Trait, error) {
|
||||
build, ok := TraitBuilders[trait.Type]
|
||||
if !ok {
|
||||
return nil, errors.Errorf("no trait type %s registered", trait.Type)
|
||||
}
|
||||
return build(*trait)
|
||||
}
|
|
@ -0,0 +1,60 @@
|
|||
/*
|
||||
Copyright 2021 The KubeVela Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package common
|
||||
|
||||
import (
|
||||
"github.com/kubevela/vela-go-sdk/pkg/apis"
|
||||
|
||||
"github.com/oam-dev/kubevela-core-api/apis/core.oam.dev/common"
|
||||
"github.com/oam-dev/kubevela-core-api/apis/core.oam.dev/v1beta1"
|
||||
)
|
||||
|
||||
type (
|
||||
ComponentConstructor func(comp common.ApplicationComponent) (apis.Component, error)
|
||||
TraitConstructor func(trait common.ApplicationTrait) (apis.Trait, error)
|
||||
WorkflowStepConstructor func(step v1beta1.WorkflowStep) (apis.WorkflowStep, error)
|
||||
WorkflowSubStepConstructor func(step common.WorkflowSubStep) (apis.WorkflowStep, error)
|
||||
PolicyConstructor func(policy v1beta1.AppPolicy) (apis.Policy, error)
|
||||
)
|
||||
|
||||
var (
|
||||
ComponentsBuilders = make(map[string]ComponentConstructor, 0)
|
||||
WorkflowStepsBuilders = make(map[string]WorkflowStepConstructor, 0)
|
||||
WorkflowSubStepsBuilders = make(map[string]WorkflowSubStepConstructor, 0)
|
||||
PoliciesBuilders = make(map[string]PolicyConstructor, 0)
|
||||
TraitBuilders = make(map[string]TraitConstructor, 0)
|
||||
)
|
||||
|
||||
func RegisterComponent(_type string, c ComponentConstructor) {
|
||||
ComponentsBuilders[_type] = c
|
||||
}
|
||||
|
||||
func RegisterPolicy(_type string, c PolicyConstructor) {
|
||||
PoliciesBuilders[_type] = c
|
||||
}
|
||||
|
||||
func RegisterWorkflowStep(_type string, c WorkflowStepConstructor) {
|
||||
WorkflowStepsBuilders[_type] = c
|
||||
}
|
||||
|
||||
func RegisterTrait(_type string, c TraitConstructor) {
|
||||
TraitBuilders[_type] = c
|
||||
}
|
||||
|
||||
func RegisterWorkflowSubStep(_type string, c WorkflowSubStepConstructor) {
|
||||
WorkflowSubStepsBuilders[_type] = c
|
||||
}
|
|
@ -0,0 +1,117 @@
|
|||
/*
|
||||
Copyright 2021 The KubeVela Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package apis
|
||||
|
||||
import (
|
||||
"github.com/oam-dev/kubevela-core-api/apis/core.oam.dev/common"
|
||||
"github.com/oam-dev/kubevela-core-api/apis/core.oam.dev/v1beta1"
|
||||
)
|
||||
|
||||
type TypedApplication interface {
|
||||
Name(name string) TypedApplication
|
||||
Namespace(namespace string) TypedApplication
|
||||
Labels(labels map[string]string) TypedApplication
|
||||
Annotations(annotations map[string]string) TypedApplication
|
||||
|
||||
WithComponents(component ...Component) TypedApplication
|
||||
WithWorkflowSteps(step ...WorkflowStep) TypedApplication
|
||||
WithPolicies(policy ...Policy) TypedApplication
|
||||
WithWorkflowMode(steps, subSteps common.WorkflowMode) TypedApplication
|
||||
|
||||
AddComponent(component Component) TypedApplication
|
||||
AddWorkflowStep(step WorkflowStep) TypedApplication
|
||||
AddPolicy(policy Policy) TypedApplication
|
||||
|
||||
GetName() string
|
||||
GetNamespace() string
|
||||
GetLabels() map[string]string
|
||||
GetAnnotations() map[string]string
|
||||
GetComponentByName(name string) Component
|
||||
GetComponentsByType(typ string) []Component
|
||||
GetWorkflowStepByName(name string) WorkflowStep
|
||||
GetWorkflowStepsByType(typ string) []WorkflowStep
|
||||
GetPolicyByName(name string) Policy
|
||||
GetPoliciesByType(typ string) []Policy
|
||||
|
||||
Build() v1beta1.Application
|
||||
ToYAML() (string, error)
|
||||
}
|
||||
|
||||
type Component interface {
|
||||
ComponentName() string
|
||||
DefType() string
|
||||
Build() common.ApplicationComponent
|
||||
GetTrait(typ string) Trait
|
||||
}
|
||||
|
||||
type Trait interface {
|
||||
DefType() string
|
||||
Build() common.ApplicationTrait
|
||||
}
|
||||
|
||||
type WorkflowStep interface {
|
||||
WorkflowStepName() string
|
||||
DefType() string
|
||||
Build() v1beta1.WorkflowStep
|
||||
}
|
||||
|
||||
type Policy interface {
|
||||
PolicyName() string
|
||||
DefType() string
|
||||
Build() v1beta1.AppPolicy
|
||||
}
|
||||
|
||||
type ComponentBase struct {
|
||||
Name string
|
||||
Type string
|
||||
DependsOn []string
|
||||
Inputs common.StepInputs
|
||||
Outputs common.StepOutputs
|
||||
Traits []Trait
|
||||
}
|
||||
|
||||
type TraitBase struct {
|
||||
Type string
|
||||
}
|
||||
|
||||
type WorkflowSubStepBase struct {
|
||||
Name string
|
||||
Type string
|
||||
Meta *common.WorkflowStepMeta
|
||||
If string
|
||||
Timeout string
|
||||
DependsOn []string
|
||||
Inputs common.StepInputs
|
||||
Outputs common.StepOutputs
|
||||
}
|
||||
|
||||
type WorkflowStepBase struct {
|
||||
Name string
|
||||
Type string
|
||||
Meta *common.WorkflowStepMeta
|
||||
SubSteps []WorkflowStep
|
||||
If string
|
||||
Timeout string
|
||||
DependsOn []string
|
||||
Inputs common.StepInputs
|
||||
Outputs common.StepOutputs
|
||||
}
|
||||
|
||||
type PolicyBase struct {
|
||||
Name string
|
||||
Type string
|
||||
}
|
|
@ -0,0 +1,138 @@
|
|||
/*
|
||||
Copyright 2021 The KubeVela Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package client
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
sdkcommon "github.com/kubevela/vela-go-sdk/pkg/apis/common"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"k8s.io/client-go/rest"
|
||||
"sigs.k8s.io/controller-runtime/pkg/client"
|
||||
"sigs.k8s.io/controller-runtime/pkg/client/config"
|
||||
|
||||
"github.com/oam-dev/kubevela-core-api/apis/core.oam.dev/v1beta1"
|
||||
|
||||
"github.com/kubevela/vela-go-sdk/pkg/apis"
|
||||
)
|
||||
|
||||
var schema *runtime.Scheme
|
||||
|
||||
type Client interface {
|
||||
Get(ctx context.Context, key client.ObjectKey) (apis.TypedApplication, error)
|
||||
List(ctx context.Context, opts client.ListOption) ([]apis.TypedApplication, error)
|
||||
Create(ctx context.Context, app apis.TypedApplication) error
|
||||
Delete(ctx context.Context, app apis.TypedApplication) error
|
||||
Update(ctx context.Context, app apis.TypedApplication) error
|
||||
Patch(ctx context.Context, app apis.TypedApplication, patch client.Patch) error
|
||||
}
|
||||
|
||||
type clientImpl struct {
|
||||
clt client.Client
|
||||
}
|
||||
|
||||
func init() {
|
||||
schema = runtime.NewScheme()
|
||||
_ = v1beta1.AddToScheme(schema)
|
||||
}
|
||||
func NewFromClient(clt client.Client) Client {
|
||||
return &clientImpl{clt: clt}
|
||||
}
|
||||
|
||||
func New(config *rest.Config) (Client, error) {
|
||||
clt, err := client.New(config, client.Options{Scheme: schema})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return NewFromClient(clt), nil
|
||||
}
|
||||
|
||||
func NewDefault() (Client, error) {
|
||||
restConf, err := config.GetConfig()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return New(restConf)
|
||||
}
|
||||
|
||||
func NewDefaultOrDie() Client {
|
||||
restConf, err := config.GetConfig()
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
clt, err := New(restConf)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return clt
|
||||
}
|
||||
|
||||
func NewFromConfigWithOptions(config *rest.Config, options client.Options) (Client, error) {
|
||||
clt, err := client.New(config, options)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return NewFromClient(clt), nil
|
||||
}
|
||||
|
||||
func (c clientImpl) Get(ctx context.Context, key client.ObjectKey) (apis.TypedApplication, error) {
|
||||
_app := v1beta1.Application{}
|
||||
err := c.clt.Get(ctx, key, &_app)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return sdkcommon.FromK8sObject(&_app)
|
||||
}
|
||||
|
||||
func (c clientImpl) List(ctx context.Context, opts client.ListOption) ([]apis.TypedApplication, error) {
|
||||
_appList := &v1beta1.ApplicationList{}
|
||||
err := c.clt.List(ctx, _appList, opts)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var apps []apis.TypedApplication
|
||||
for _, app := range _appList.Items {
|
||||
_app, err := sdkcommon.FromK8sObject(&app)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
apps = append(apps, _app)
|
||||
}
|
||||
return apps, nil
|
||||
}
|
||||
|
||||
func (c clientImpl) Create(ctx context.Context, app apis.TypedApplication) error {
|
||||
appObj := app.Build()
|
||||
return c.clt.Create(ctx, &appObj)
|
||||
}
|
||||
|
||||
func (c clientImpl) Delete(ctx context.Context, app apis.TypedApplication) error {
|
||||
appObj := app.Build()
|
||||
return c.clt.Delete(ctx, &appObj)
|
||||
}
|
||||
|
||||
func (c clientImpl) Update(ctx context.Context, app apis.TypedApplication) error {
|
||||
appObj := app.Build()
|
||||
return c.clt.Update(ctx, &appObj)
|
||||
}
|
||||
|
||||
func (c clientImpl) Patch(ctx context.Context, app apis.TypedApplication, patch client.Patch) error {
|
||||
appObj := app.Build()
|
||||
return c.clt.Patch(ctx, &appObj, patch)
|
||||
}
|
|
@ -0,0 +1,32 @@
|
|||
/*
|
||||
Copyright 2021 The KubeVela Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package gen_sdk
|
||||
|
||||
import "embed"
|
||||
|
||||
var (
|
||||
//go:embed openapi-generator/templates
|
||||
// Templates contains different template files for different languages
|
||||
Templates embed.FS
|
||||
// SupportedLangs is supported languages
|
||||
SupportedLangs = map[string]bool{"go": true}
|
||||
//go:embed _scaffold
|
||||
// Scaffold is scaffold files for different languages
|
||||
Scaffold embed.FS
|
||||
// ScaffoldDir is scaffold dir name
|
||||
ScaffoldDir = "_scaffold"
|
||||
)
|
|
@ -0,0 +1,505 @@
|
|||
/*
|
||||
Copyright 2021 The KubeVela Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package gen_sdk
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/fs"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path"
|
||||
"path/filepath"
|
||||
"runtime/debug"
|
||||
"strings"
|
||||
|
||||
"cuelang.org/go/cue"
|
||||
"cuelang.org/go/encoding/openapi"
|
||||
"github.com/getkin/kin-openapi/openapi3"
|
||||
"github.com/kubevela/workflow/pkg/cue/model/value"
|
||||
"github.com/pkg/errors"
|
||||
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
|
||||
"k8s.io/client-go/rest"
|
||||
"k8s.io/klog/v2"
|
||||
|
||||
velacue "github.com/oam-dev/kubevela/pkg/cue"
|
||||
"github.com/oam-dev/kubevela/pkg/definition"
|
||||
"github.com/oam-dev/kubevela/pkg/stdlib"
|
||||
"github.com/oam-dev/kubevela/pkg/utils/common"
|
||||
"github.com/oam-dev/kubevela/pkg/utils/system"
|
||||
)
|
||||
|
||||
type byteHandler func([]byte) []byte
|
||||
|
||||
// GenMeta stores the metadata for generator.
|
||||
type GenMeta struct {
|
||||
config *rest.Config
|
||||
|
||||
Output string
|
||||
Lang string
|
||||
Package string
|
||||
Template string
|
||||
File []string
|
||||
InitSDK bool
|
||||
Verbose bool
|
||||
|
||||
cuePaths []string
|
||||
templatePath string
|
||||
packageFunc byteHandler
|
||||
}
|
||||
|
||||
// Generator is used to generate SDK code from CUE template for one language.
|
||||
type Generator struct {
|
||||
meta *GenMeta
|
||||
name string
|
||||
kind string
|
||||
def definition.Definition
|
||||
openapiSchema []byte
|
||||
modifiers []Modifier
|
||||
}
|
||||
|
||||
// Modifier is used to modify the generated code.
|
||||
type Modifier interface {
|
||||
Modify() error
|
||||
Name() string
|
||||
}
|
||||
|
||||
// Init initializes the generator.
|
||||
// It will validate the param, analyze the CUE files, read them to memory, mkdir for output.
|
||||
func (meta *GenMeta) Init(c common.Args) (err error) {
|
||||
meta.config, err = c.GetConfig()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = stdlib.SetupBuiltinImports()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if _, ok := SupportedLangs[meta.Lang]; !ok {
|
||||
return fmt.Errorf("language %s is not supported", meta.Lang)
|
||||
}
|
||||
|
||||
packageFuncs := map[string]byteHandler{
|
||||
"go": func(b []byte) []byte {
|
||||
return bytes.ReplaceAll(b, []byte("github.com/kubevela/vela-go-sdk"), []byte(meta.Package))
|
||||
},
|
||||
}
|
||||
|
||||
meta.packageFunc = packageFuncs[meta.Lang]
|
||||
|
||||
// Analyze the all cue files from meta.File. It can be file or directory. If directory is given, it will recursively
|
||||
// analyze all cue files in the directory.
|
||||
for _, f := range meta.File {
|
||||
info, err := os.Stat(f)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if info.IsDir() {
|
||||
err = filepath.Walk(f, func(path string, info os.FileInfo, err error) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if !info.IsDir() && strings.HasSuffix(path, ".cue") {
|
||||
meta.cuePaths = append(meta.cuePaths, path)
|
||||
}
|
||||
return nil
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
} else if strings.HasSuffix(f, ".cue") {
|
||||
meta.cuePaths = append(meta.cuePaths, f)
|
||||
}
|
||||
|
||||
}
|
||||
return os.MkdirAll(meta.Output, 0750)
|
||||
}
|
||||
|
||||
// CreateScaffold will create a scaffold for the given language.
|
||||
// It will copy all files from embedded scaffold/{meta.Lang} to meta.Output.
|
||||
func (meta *GenMeta) CreateScaffold() error {
|
||||
if !meta.InitSDK {
|
||||
return nil
|
||||
}
|
||||
fmt.Println("Flag --init is set, creating scaffold...")
|
||||
langDirPrefix := fmt.Sprintf("%s/%s", ScaffoldDir, meta.Lang)
|
||||
err := fs.WalkDir(Scaffold, ScaffoldDir, func(_path string, d fs.DirEntry, err error) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if d.IsDir() {
|
||||
if !strings.HasPrefix(_path, langDirPrefix) && _path != ScaffoldDir {
|
||||
return fs.SkipDir
|
||||
}
|
||||
return nil
|
||||
}
|
||||
if d.Name() == "keep" {
|
||||
return nil
|
||||
}
|
||||
fileContent, err := Scaffold.ReadFile(_path)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
fileContent = meta.packageFunc(fileContent)
|
||||
fileName := path.Join(meta.Output, strings.TrimPrefix(_path, langDirPrefix))
|
||||
// go.mod_ is a special file name, it will be renamed to go.mod. Go will exclude directory go.mod located from the build process.
|
||||
fileName = strings.ReplaceAll(fileName, "go.mod_", "go.mod")
|
||||
fileDir := path.Dir(fileName)
|
||||
if err = os.MkdirAll(fileDir, 0750); err != nil {
|
||||
return err
|
||||
}
|
||||
return os.WriteFile(fileName, fileContent, 0600)
|
||||
})
|
||||
return err
|
||||
}
|
||||
|
||||
// PrepareGeneratorAndTemplate will make a copy of the embedded openapi-generator-cli and templates/{meta.Lang} to local
|
||||
func (meta *GenMeta) PrepareGeneratorAndTemplate() error {
|
||||
var err error
|
||||
ogImageName := "openapitools/openapi-generator-cli"
|
||||
ogImageTag := "v6.3.0"
|
||||
ogImage := fmt.Sprintf("%s:%s", ogImageName, ogImageTag)
|
||||
homeDir, err := system.GetVelaHomeDir()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
sdkDir := path.Join(homeDir, "sdk")
|
||||
if err = os.MkdirAll(sdkDir, 0750); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// nolint:gosec
|
||||
output, err := exec.Command("docker", "image", "ls", ogImage).CombinedOutput()
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, "failed to check image %s: %s", ogImage, output)
|
||||
}
|
||||
if !strings.Contains(string(output), ogImageName) {
|
||||
// nolint:gosec
|
||||
output, err = exec.Command("docker", "pull", ogImage).CombinedOutput()
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, "failed to pull %s: %s", ogImage, output)
|
||||
}
|
||||
}
|
||||
|
||||
// copy embedded templates/{meta.Lang} to sdkDir
|
||||
if meta.Template == "" {
|
||||
langDir := path.Join(sdkDir, "templates", meta.Lang)
|
||||
if err = os.MkdirAll(langDir, 0750); err != nil {
|
||||
return err
|
||||
}
|
||||
langTemplateDir := path.Join("openapi-generator", "templates", meta.Lang)
|
||||
langTemplateFiles, err := Templates.ReadDir(langTemplateDir)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for _, langTemplateFile := range langTemplateFiles {
|
||||
src, err := Templates.Open(path.Join(langTemplateDir, langTemplateFile.Name()))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
// nolint:gosec
|
||||
dst, err := os.Create(path.Join(langDir, langTemplateFile.Name()))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
_, err = io.Copy(dst, src)
|
||||
_ = dst.Close()
|
||||
_ = src.Close()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
meta.templatePath = langDir
|
||||
} else {
|
||||
meta.templatePath = meta.Template
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Run will generally do two thing:
|
||||
// 1. Generate OpenAPI schema from cue files
|
||||
// 2. Generate code from OpenAPI schema
|
||||
func (meta *GenMeta) Run() error {
|
||||
for _, cuePath := range meta.cuePaths {
|
||||
klog.Infof("Generating SDK for %s", cuePath)
|
||||
g := NewModifiableGenerator(meta)
|
||||
// nolint:gosec
|
||||
cueBytes, err := os.ReadFile(cuePath)
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, "failed to read %s", cuePath)
|
||||
}
|
||||
template, err := g.GetDefinitionValue(cueBytes)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = g.GenOpenAPISchema(template)
|
||||
if err != nil {
|
||||
if strings.Contains(err.Error(), "unsupported node string (*ast.Ident)") {
|
||||
// https://github.com/cue-lang/cue/issues/2259
|
||||
klog.Warningf("Skip generating OpenAPI schema for %s, known issue: %s", cuePath, err.Error())
|
||||
continue
|
||||
}
|
||||
return errors.Wrapf(err, "generate OpenAPI schema")
|
||||
}
|
||||
err = g.GenerateCode()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetDefinitionValue returns a value.Value from cue bytes
|
||||
func (g *Generator) GetDefinitionValue(cueBytes []byte) (*value.Value, error) {
|
||||
g.def = definition.Definition{Unstructured: unstructured.Unstructured{}}
|
||||
if err := g.def.FromCUEString(string(cueBytes), g.meta.config); err != nil {
|
||||
return nil, errors.Wrapf(err, "failed to parse CUE")
|
||||
}
|
||||
g.name = g.def.GetName()
|
||||
g.kind = g.def.GetKind()
|
||||
|
||||
templateString, _, err := unstructured.NestedString(g.def.Object, definition.DefinitionTemplateKeys...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
template, err := value.NewValue(templateString+velacue.BaseTemplate, nil, "")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return template, nil
|
||||
}
|
||||
|
||||
// GenOpenAPISchema generates OpenAPI json schema from cue.Instance
|
||||
func (g *Generator) GenOpenAPISchema(val *value.Value) error {
|
||||
var err error
|
||||
defer func() {
|
||||
if r := recover(); r != nil {
|
||||
err = fmt.Errorf("invalid cue definition to generate open api: %v", r)
|
||||
debug.PrintStack()
|
||||
return
|
||||
}
|
||||
}()
|
||||
if val.CueValue().Err() != nil {
|
||||
return val.CueValue().Err()
|
||||
}
|
||||
paramOnlyVal, err := common.RefineParameterValue(val)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defaultConfig := &openapi.Config{ExpandReferences: false, NameFunc: func(val cue.Value, path cue.Path) string {
|
||||
sels := path.Selectors()
|
||||
lastLabel := sels[len(sels)-1].String()
|
||||
return strings.TrimPrefix(lastLabel, "#")
|
||||
}, DescriptionFunc: func(v cue.Value) string {
|
||||
for _, d := range v.Doc() {
|
||||
if strings.HasPrefix(d.Text(), "+usage=") {
|
||||
return strings.TrimPrefix(d.Text(), "+usage=")
|
||||
}
|
||||
}
|
||||
return ""
|
||||
}}
|
||||
b, err := openapi.Gen(paramOnlyVal, defaultConfig)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
doc, err := openapi3.NewLoader().LoadFromData(b)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
g.completeOpenAPISchema(doc)
|
||||
openapiSchema, err := doc.MarshalJSON()
|
||||
g.openapiSchema = openapiSchema
|
||||
return err
|
||||
}
|
||||
|
||||
func (g *Generator) completeOpenAPISchema(doc *openapi3.T) {
|
||||
for key, schema := range doc.Components.Schemas {
|
||||
switch key {
|
||||
case "parameter":
|
||||
spec := g.name + "-spec"
|
||||
schema.Value.Title = spec
|
||||
completeFreeFormSchema(schema)
|
||||
completeSchemas(schema.Value.Properties)
|
||||
doc.Components.Schemas[spec] = schema
|
||||
delete(doc.Components.Schemas, key)
|
||||
default:
|
||||
completeSchema(key, schema)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// GenerateCode will call openapi-generator to generate code and modify it
|
||||
func (g *Generator) GenerateCode() (err error) {
|
||||
tmpFile, err := os.CreateTemp("", g.name+"-*.json")
|
||||
_, err = tmpFile.Write(g.openapiSchema)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "write openapi schema to temporary file")
|
||||
}
|
||||
defer func() {
|
||||
_ = tmpFile.Close()
|
||||
if err == nil {
|
||||
_ = os.Remove(tmpFile.Name())
|
||||
}
|
||||
}()
|
||||
apiDir, err := filepath.Abs(path.Join(g.meta.Output, "pkg", "apis"))
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, "get absolute path of %s", apiDir)
|
||||
}
|
||||
|
||||
// nolint:gosec
|
||||
cmd := exec.Command("docker", "run",
|
||||
"-v", fmt.Sprintf("%s:/local/output", apiDir),
|
||||
"-v", fmt.Sprintf("%s:/local/input", filepath.Dir(tmpFile.Name())),
|
||||
"-v", fmt.Sprintf("%s:/local/template", g.meta.templatePath),
|
||||
"-u", fmt.Sprintf("%d:%d", os.Getuid(), os.Getgid()),
|
||||
"--rm",
|
||||
"openapitools/openapi-generator-cli:v6.3.0",
|
||||
"generate",
|
||||
"-i", "/local/input/"+filepath.Base(tmpFile.Name()),
|
||||
"-g", g.meta.Lang,
|
||||
"-o", fmt.Sprintf("/local/output/%s/%s", definition.DefinitionKindToType[g.kind], g.name),
|
||||
"-t", "/local/template",
|
||||
"--skip-validate-spec",
|
||||
"--enable-post-process-file",
|
||||
"--generate-alias-as-model",
|
||||
"--inline-schema-name-defaults", "arrayItemSuffix=,mapItemSuffix=",
|
||||
"--additional-properties", fmt.Sprintf("isGoSubmodule=true,packageName=%s", strings.ReplaceAll(g.name, "-", "_")),
|
||||
"--global-property", "modelDocs=false,models,supportingFiles=utils.go",
|
||||
)
|
||||
if g.meta.Verbose {
|
||||
fmt.Println(cmd.String())
|
||||
}
|
||||
output, err := cmd.CombinedOutput()
|
||||
if err != nil {
|
||||
return errors.Wrap(err, string(output))
|
||||
}
|
||||
if g.meta.Verbose {
|
||||
fmt.Println(string(output))
|
||||
}
|
||||
|
||||
// Adjust the generated files and code
|
||||
for _, m := range g.modifiers {
|
||||
err := m.Modify()
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, "modify fail by %s", m.Name())
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// completeFreeFormSchema can complete the schema of free form parameter, such as `parameter: {...}`
|
||||
// This is a workaround for openapi-generator, which won't generate the correct code for free form parameter.
|
||||
func completeFreeFormSchema(schema *openapi3.SchemaRef) {
|
||||
v := schema.Value
|
||||
if v.OneOf == nil && v.AnyOf == nil && v.AllOf == nil && v.Properties == nil {
|
||||
if v.Type == "object" {
|
||||
schema.Value.AdditionalProperties = &openapi3.SchemaRef{
|
||||
Value: &openapi3.Schema{
|
||||
Type: "object",
|
||||
Nullable: true,
|
||||
},
|
||||
}
|
||||
} else if v.Type == "string" {
|
||||
schema.Value.AdditionalProperties = &openapi3.SchemaRef{
|
||||
Value: &openapi3.Schema{
|
||||
Type: "string",
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// fixSchemaWithOneAnyAllOf move properties in the root schema to sub schema in OneOf, AnyOf, AllOf
|
||||
// See https://github.com/OpenAPITools/openapi-generator/issues/14250
|
||||
func fixSchemaWithOneAnyAllOf(schema *openapi3.SchemaRef) {
|
||||
var schemaNeedFix []*openapi3.Schema
|
||||
refs := []openapi3.SchemaRefs{schema.Value.OneOf, schema.Value.AnyOf, schema.Value.AllOf}
|
||||
for _, ref := range refs {
|
||||
for _, s := range ref {
|
||||
completeSchemas(s.Value.Properties)
|
||||
// If the schema is without type or ref. It may need to be fixed.
|
||||
// Cases can be:
|
||||
// 1. A non-ref sub-schema maybe have no properties and the needed properties is in the root schema.
|
||||
// 2. A sub-schema maybe have no type and the needed type is in the root schema.
|
||||
// In both cases, we need to complete the sub-schema with the properties or type in the root schema if any of them is missing.
|
||||
if s.Ref == "" || s.Value.Type == "" {
|
||||
schemaNeedFix = append(schemaNeedFix, s.Value)
|
||||
}
|
||||
}
|
||||
}
|
||||
if schemaNeedFix == nil {
|
||||
return // no non-ref schema found
|
||||
}
|
||||
for _, s := range schemaNeedFix {
|
||||
if s.Properties == nil {
|
||||
s.Properties = schema.Value.Properties
|
||||
}
|
||||
if s.Type == "" {
|
||||
s.Type = schema.Value.Type
|
||||
}
|
||||
}
|
||||
schema.Value.Properties = nil
|
||||
}
|
||||
|
||||
func completeSchema(key string, schema *openapi3.SchemaRef) {
|
||||
schema.Value.Title = key
|
||||
if schema.Value.OneOf != nil || schema.Value.AnyOf != nil || schema.Value.AllOf != nil {
|
||||
fixSchemaWithOneAnyAllOf(schema)
|
||||
return
|
||||
}
|
||||
|
||||
// allow all the fields to be empty to avoid this case:
|
||||
// A field is initialized with empty value and marshalled to JSON with empty value (e.g. empty string)
|
||||
// However, the empty value is not allowed on the server side when it is conflict with the default value in CUE.
|
||||
schema.Value.Required = []string{}
|
||||
|
||||
switch schema.Value.Type {
|
||||
case "object":
|
||||
completeSchemas(schema.Value.Properties)
|
||||
case "array":
|
||||
completeSchema(key, schema.Value.Items)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func completeSchemas(schemas openapi3.Schemas) {
|
||||
for k, schema := range schemas {
|
||||
completeSchema(k, schema)
|
||||
}
|
||||
}
|
||||
|
||||
// NewModifiableGenerator returns a new Generator with modifiers
|
||||
func NewModifiableGenerator(meta *GenMeta) *Generator {
|
||||
g := &Generator{
|
||||
meta: meta,
|
||||
modifiers: []Modifier{},
|
||||
}
|
||||
mo := newModifierOnLanguage(meta.Lang, g)
|
||||
g.modifiers = append(g.modifiers, mo)
|
||||
return g
|
||||
}
|
||||
|
||||
func newModifierOnLanguage(lang string, generator *Generator) Modifier {
|
||||
switch lang {
|
||||
case "go":
|
||||
return &GoModifier{g: generator}
|
||||
default:
|
||||
panic("unsupported language: " + lang)
|
||||
}
|
||||
}
|
|
@ -0,0 +1,29 @@
|
|||
/*
|
||||
Copyright 2021 The KubeVela Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package gen_sdk_test
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
. "github.com/onsi/ginkgo"
|
||||
. "github.com/onsi/gomega"
|
||||
)
|
||||
|
||||
func TestGenSdk(t *testing.T) {
|
||||
RegisterFailHandler(Fail)
|
||||
RunSpecs(t, "GenSdk Suite")
|
||||
}
|
|
@ -0,0 +1,120 @@
|
|||
/*
|
||||
Copyright 2021 The KubeVela Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package gen_sdk
|
||||
|
||||
import (
|
||||
"os"
|
||||
"path/filepath"
|
||||
|
||||
. "github.com/onsi/ginkgo"
|
||||
. "github.com/onsi/gomega"
|
||||
|
||||
"github.com/oam-dev/kubevela/pkg/utils/common"
|
||||
)
|
||||
|
||||
var _ = Describe("Test Generating SDK", func() {
|
||||
var err error
|
||||
outputDir := filepath.Join("testdata", "output")
|
||||
meta := GenMeta{
|
||||
Output: outputDir,
|
||||
Lang: "go",
|
||||
Package: "github.com/kubevela/test-gen-sdk",
|
||||
File: []string{filepath.Join("testdata", "cron-task.cue")},
|
||||
InitSDK: true,
|
||||
}
|
||||
checkDirNotEmpty := func(dir string) {
|
||||
_, err = os.Stat(dir)
|
||||
Expect(err).Should(BeNil())
|
||||
}
|
||||
genWithMeta := func(meta GenMeta) {
|
||||
err = meta.Init(common.Args{})
|
||||
Expect(err).Should(BeNil())
|
||||
err = meta.CreateScaffold()
|
||||
Expect(err).Should(BeNil())
|
||||
err = meta.PrepareGeneratorAndTemplate()
|
||||
Expect(err).Should(BeNil())
|
||||
err = meta.Run()
|
||||
Expect(err).Should(BeNil())
|
||||
}
|
||||
It("Test generating SDK and init the scaffold", func() {
|
||||
genWithMeta(meta)
|
||||
checkDirNotEmpty(filepath.Join(outputDir, "pkg", "apis"))
|
||||
checkDirNotEmpty(filepath.Join(outputDir, "pkg", "apis", "component", "cron-task"))
|
||||
})
|
||||
|
||||
It("Test generating SDK, append apis", func() {
|
||||
meta.InitSDK = false
|
||||
meta.File = append(meta.File, "testdata/shared-resource.cue")
|
||||
|
||||
genWithMeta(meta)
|
||||
checkDirNotEmpty(filepath.Join(outputDir, "pkg", "apis", "policy", "shared-resource"))
|
||||
})
|
||||
|
||||
It("Test free form parameter {...}", func() {
|
||||
meta.InitSDK = false
|
||||
meta.File = []string{"testdata/json-merge-patch.cue"}
|
||||
meta.Verbose = true
|
||||
|
||||
genWithMeta(meta)
|
||||
checkDirNotEmpty(filepath.Join(outputDir, "pkg", "apis", "trait", "json-merge-patch"))
|
||||
})
|
||||
|
||||
It("Test workflow step", func() {
|
||||
meta.InitSDK = false
|
||||
meta.File = []string{"testdata/deploy.cue"}
|
||||
meta.Verbose = true
|
||||
|
||||
genWithMeta(meta)
|
||||
checkDirNotEmpty(filepath.Join(outputDir, "pkg", "apis", "workflow-step", "deploy"))
|
||||
})
|
||||
|
||||
It("Test step-group", func() {
|
||||
meta.InitSDK = false
|
||||
meta.File = []string{"testdata/step-group.cue"}
|
||||
meta.Verbose = true
|
||||
|
||||
genWithMeta(meta)
|
||||
checkDirNotEmpty(filepath.Join(outputDir, "pkg", "apis", "workflow-step", "step-group"))
|
||||
By("check if AddSubStep is generated")
|
||||
content, err := os.ReadFile(filepath.Join(outputDir, "pkg", "apis", "workflow-step", "step-group", "step_group.go"))
|
||||
Expect(err).Should(BeNil())
|
||||
Expect(string(content)).Should(ContainSubstring("AddSubStep"))
|
||||
})
|
||||
|
||||
It("Test oneOf", func() {
|
||||
meta.InitSDK = false
|
||||
meta.File = []string{"testdata/one_of.cue"}
|
||||
meta.Verbose = true
|
||||
|
||||
genWithMeta(meta)
|
||||
checkDirNotEmpty(filepath.Join(outputDir, "pkg", "apis", "workflow-step", "one_of"))
|
||||
By("check if ")
|
||||
})
|
||||
|
||||
It("Test known issue: apply-terraform-provider", func() {
|
||||
meta.InitSDK = false
|
||||
meta.Verbose = true
|
||||
meta.File = []string{"testdata/apply-terraform-provider.cue"}
|
||||
genWithMeta(meta)
|
||||
})
|
||||
|
||||
AfterSuite(func() {
|
||||
By("Cleaning up generated files")
|
||||
_ = os.RemoveAll(outputDir)
|
||||
})
|
||||
|
||||
})
|
|
@ -0,0 +1,640 @@
|
|||
/*
|
||||
Copyright 2021 The KubeVela Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package gen_sdk
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"go/format"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path"
|
||||
"regexp"
|
||||
"strings"
|
||||
|
||||
// we need dot import here to make the complex go code generating simpler
|
||||
// nolint:revive
|
||||
j "github.com/dave/jennifer/jen"
|
||||
"github.com/ettle/strcase"
|
||||
"github.com/pkg/errors"
|
||||
|
||||
"github.com/oam-dev/kubevela/apis/core.oam.dev/v1beta1"
|
||||
pkgdef "github.com/oam-dev/kubevela/pkg/definition"
|
||||
)
|
||||
|
||||
var (
|
||||
// DefinitionKindToPascal is the map of definition kind to pascal case
|
||||
DefinitionKindToPascal = map[string]string{
|
||||
v1beta1.ComponentDefinitionKind: "Component",
|
||||
v1beta1.TraitDefinitionKind: "Trait",
|
||||
v1beta1.WorkflowStepDefinitionKind: "WorkflowStep",
|
||||
v1beta1.PolicyDefinitionKind: "Policy",
|
||||
}
|
||||
// DefinitionKindToBaseType is the map of definition kind to base type
|
||||
DefinitionKindToBaseType = map[string]string{
|
||||
v1beta1.ComponentDefinitionKind: "ComponentBase",
|
||||
v1beta1.TraitDefinitionKind: "TraitBase",
|
||||
v1beta1.WorkflowStepDefinitionKind: "WorkflowStepBase",
|
||||
v1beta1.PolicyDefinitionKind: "PolicyBase",
|
||||
}
|
||||
// DefinitionKindToStatement is the map of definition kind to statement
|
||||
DefinitionKindToStatement = map[string]*j.Statement{
|
||||
v1beta1.ComponentDefinitionKind: j.Qual("common", "ApplicationComponent"),
|
||||
v1beta1.TraitDefinitionKind: j.Qual("common", "ApplicationTrait"),
|
||||
v1beta1.WorkflowStepDefinitionKind: j.Qual("v1beta1", "WorkflowStep"),
|
||||
v1beta1.PolicyDefinitionKind: j.Qual("v1beta1", "AppPolicy"),
|
||||
}
|
||||
)
|
||||
|
||||
// GoModifier is the Modifier for golang
|
||||
type GoModifier struct {
|
||||
g *Generator
|
||||
|
||||
defName string
|
||||
defKind string
|
||||
verbose bool
|
||||
|
||||
defDir string
|
||||
utilsDir string
|
||||
// def name of different cases
|
||||
nameInSnakeCase string
|
||||
nameInPascalCase string
|
||||
specNameInPascalCase string
|
||||
typeVarName string
|
||||
defStructName string
|
||||
defFuncReceiver string
|
||||
defStructPointer *j.Statement
|
||||
}
|
||||
|
||||
// Name the name of modifier
|
||||
func (m *GoModifier) Name() string {
|
||||
return "GoModifier"
|
||||
}
|
||||
|
||||
// Modify the modification of generated code
|
||||
func (m *GoModifier) Modify() error {
|
||||
for _, fn := range []func() error{
|
||||
m.init,
|
||||
m.clean,
|
||||
m.moveUtils,
|
||||
m.modifyDefs,
|
||||
m.addDefAPI,
|
||||
m.exportMethods,
|
||||
m.format,
|
||||
} {
|
||||
if err := fn(); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *GoModifier) init() error {
|
||||
m.defName = m.g.name
|
||||
m.defKind = m.g.kind
|
||||
m.verbose = m.g.meta.Verbose
|
||||
|
||||
pkgAPIDir := path.Join(m.g.meta.Output, "pkg", "apis")
|
||||
m.defDir = path.Join(pkgAPIDir, pkgdef.DefinitionKindToType[m.defKind], m.defName)
|
||||
m.utilsDir = path.Join(pkgAPIDir, "utils")
|
||||
m.nameInSnakeCase = strcase.ToSnake(m.defName)
|
||||
m.nameInPascalCase = strcase.ToPascal(m.defName)
|
||||
m.typeVarName = m.nameInPascalCase + "Type"
|
||||
m.specNameInPascalCase = m.nameInPascalCase + "Spec"
|
||||
m.defStructName = strcase.ToGoPascal(m.defName + "-" + pkgdef.DefinitionKindToType[m.defKind])
|
||||
m.defStructPointer = j.Op("*").Id(m.defStructName)
|
||||
m.defFuncReceiver = m.defName[:1]
|
||||
err := os.MkdirAll(m.utilsDir, 0750)
|
||||
return err
|
||||
}
|
||||
|
||||
func (m *GoModifier) clean() error {
|
||||
err := os.RemoveAll(path.Join(m.defDir, ".openapi-generator"))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = os.RemoveAll(path.Join(m.defDir, "api"))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
files, _ := os.ReadDir(m.defDir)
|
||||
for _, f := range files {
|
||||
dst := strings.TrimPrefix(f.Name(), "model_")
|
||||
if dst == m.nameInSnakeCase+"_spec.go" {
|
||||
dst = m.nameInSnakeCase + ".go"
|
||||
}
|
||||
err = os.Rename(path.Join(m.defDir, f.Name()), path.Join(m.defDir, dst))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
|
||||
}
|
||||
|
||||
// read all files in definition directory,
|
||||
// 1. replace the Nullable* Struct
|
||||
// 2. replace the package name
|
||||
func (m *GoModifier) modifyDefs() error {
|
||||
changeNullableType := func(b []byte) []byte {
|
||||
return regexp.MustCompile("Nullable(String|(Float|Int)(32|64)|Bool)").ReplaceAll(b, []byte("utils.Nullable$1"))
|
||||
}
|
||||
|
||||
files, err := os.ReadDir(m.defDir)
|
||||
defHandleFunc := []byteHandler{
|
||||
m.g.meta.packageFunc,
|
||||
changeNullableType,
|
||||
}
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for _, f := range files {
|
||||
loc := path.Join(m.defDir, f.Name())
|
||||
// nolint:gosec
|
||||
b, err := os.ReadFile(loc)
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, "read file")
|
||||
}
|
||||
for _, h := range defHandleFunc {
|
||||
b = h(b)
|
||||
}
|
||||
|
||||
_ = os.WriteFile(loc, b, 0600)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *GoModifier) moveUtils() error {
|
||||
// Adjust the generated files and code
|
||||
err := os.Rename(path.Join(m.defDir, "utils.go"), path.Join(m.utilsDir, "utils.go"))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
utilsFile := path.Join(m.utilsDir, "utils.go")
|
||||
|
||||
// nolint:gosec
|
||||
utilsBytes, err := os.ReadFile(utilsFile)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
utilsBytes = bytes.Replace(utilsBytes, []byte(fmt.Sprintf("package %s", strcase.ToSnake(m.defName))), []byte("package utils"), 1)
|
||||
err = os.WriteFile(utilsFile, utilsBytes, 0600)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// addDefAPI will add component/trait/workflowstep/policy Object to the api
|
||||
func (m *GoModifier) addDefAPI() error {
|
||||
file, err := os.OpenFile(path.Join(m.defDir, m.nameInSnakeCase+".go"), os.O_APPEND|os.O_WRONLY, 0600)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer func() {
|
||||
_ = file.Close()
|
||||
}()
|
||||
renderGroup := make([]*j.Statement, 0)
|
||||
renderGroup = append(renderGroup, m.genCommonFunc()...)
|
||||
renderGroup = append(renderGroup, m.genFromFunc()...)
|
||||
renderGroup = append(renderGroup, m.genDedicatedFunc()...)
|
||||
renderGroup = append(renderGroup, m.genNameTypeFunc()...)
|
||||
renderGroup = append(renderGroup, m.genUnmarshalFunc()...)
|
||||
renderGroup = append(renderGroup, m.genBaseSetterFunc()...)
|
||||
renderGroup = append(renderGroup, m.genAddSubStepFunc())
|
||||
|
||||
buf := new(bytes.Buffer)
|
||||
for _, r := range renderGroup {
|
||||
// write content at the end of file
|
||||
err := r.Render(buf)
|
||||
buf.WriteString("\n\n")
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "render code")
|
||||
}
|
||||
}
|
||||
_, err = file.Write(buf.Bytes())
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "append content to file")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *GoModifier) genCommonFunc() []*j.Statement {
|
||||
kind := m.defKind
|
||||
typeName := j.Id(m.nameInPascalCase + "Type")
|
||||
typeConst := j.Const().Add(typeName).Op("=").Lit(m.defName)
|
||||
j.Op("=").Lit(m.defName)
|
||||
defStruct := j.Type().Id(m.defStructName).Struct(
|
||||
j.Id("Base").Id("apis").Dot(DefinitionKindToBaseType[kind]),
|
||||
j.Id("Properties").Id(m.specNameInPascalCase),
|
||||
)
|
||||
|
||||
initFunc := j.Func().Id("init").Params().BlockFunc(func(g *j.Group) {
|
||||
g.Add(j.Qual("sdkcommon", "Register"+DefinitionKindToPascal[kind]).Call(j.Add(typeName), j.Id("From"+DefinitionKindToPascal[kind])))
|
||||
if kind == v1beta1.WorkflowStepDefinitionKind {
|
||||
g.Add(j.Qual("sdkcommon", "RegisterWorkflowSubStep").Call(j.Add(typeName), j.Id("FromWorkflowSubStep")))
|
||||
}
|
||||
},
|
||||
)
|
||||
|
||||
defStructConstructor := j.Func().Id(m.nameInPascalCase).Params(
|
||||
j.Do(func(s *j.Statement) {
|
||||
switch kind {
|
||||
case v1beta1.ComponentDefinitionKind, v1beta1.PolicyDefinitionKind, v1beta1.WorkflowStepDefinitionKind:
|
||||
s.Id("name").String()
|
||||
}
|
||||
}),
|
||||
).Add(m.defStructPointer).Block(
|
||||
j.Id(m.defFuncReceiver).Op(":=").Op("&").Id(m.defStructName).Values(j.Dict{
|
||||
j.Id("Base"): j.Id("apis").Dot(DefinitionKindToBaseType[kind]).BlockFunc(
|
||||
func(g *j.Group) {
|
||||
switch kind {
|
||||
case v1beta1.ComponentDefinitionKind, v1beta1.PolicyDefinitionKind, v1beta1.WorkflowStepDefinitionKind:
|
||||
g.Id("Name").Op(":").Id("name").Op(",")
|
||||
g.Id("Type").Op(":").Add(typeName).Op(",")
|
||||
}
|
||||
}),
|
||||
}),
|
||||
j.Return(j.Id(m.defFuncReceiver)),
|
||||
)
|
||||
traitType := DefinitionKindToStatement[v1beta1.TraitDefinitionKind]
|
||||
stepType := DefinitionKindToStatement[v1beta1.WorkflowStepDefinitionKind]
|
||||
builderDict := j.Dict{
|
||||
// all definition have type and properties
|
||||
j.Id("Type"): j.Add(typeName),
|
||||
j.Id("Properties"): j.Qual("util", "Object2RawExtension").Params(j.Id(m.defFuncReceiver).Dot("Properties")),
|
||||
}
|
||||
builderDictValues := map[string][]string{
|
||||
v1beta1.PolicyDefinitionKind: {"Name"},
|
||||
v1beta1.ComponentDefinitionKind: {"Name", "DependsOn", "Inputs", "Outputs"},
|
||||
v1beta1.WorkflowStepDefinitionKind: {"Name", "DependsOn", "Inputs", "Outputs", "If", "Timeout", "Meta"},
|
||||
}
|
||||
for _, v := range builderDictValues[kind] {
|
||||
builderDict[j.Id(v)] = j.Id(m.defFuncReceiver).Dot("Base").Dot(v)
|
||||
}
|
||||
switch kind {
|
||||
case v1beta1.ComponentDefinitionKind:
|
||||
builderDict[j.Id("Traits")] = j.Id("traits")
|
||||
case v1beta1.WorkflowStepDefinitionKind:
|
||||
builderDict[j.Id("SubSteps")] = j.Id("subSteps")
|
||||
}
|
||||
buildFunc := j.Func().
|
||||
Params(j.Id(m.defFuncReceiver).Add(m.defStructPointer)).
|
||||
Id("Build").Params().
|
||||
Add(DefinitionKindToStatement[kind]).BlockFunc(func(g *j.Group) {
|
||||
switch kind {
|
||||
case v1beta1.ComponentDefinitionKind:
|
||||
g.Add(j.Id("traits").Op(":=").Make(j.Index().Add(traitType), j.Lit(0)))
|
||||
g.Add(j.For(j.List(j.Id("_"), j.Id("trait")).Op(":=").Range().Id(m.defFuncReceiver).Dot("Base").Dot("Traits")).Block(
|
||||
j.Id("traits").Op("=").Append(j.Id("traits"), j.Id("trait").Dot("Build").Call()),
|
||||
))
|
||||
case v1beta1.WorkflowStepDefinitionKind:
|
||||
g.Add(j.Id("_subSteps").Op(":=").Make(j.Index().Add(stepType), j.Lit(0)))
|
||||
g.Add(j.For(j.List(j.Id("_"), j.Id("subStep")).Op(":=").Range().Id(m.defFuncReceiver).Dot("Base").Dot("SubSteps")).Block(
|
||||
j.Id("_subSteps").Op("=").Append(j.Id("_subSteps"), j.Id("subStep").Dot("Build").Call()),
|
||||
))
|
||||
g.Add(j.Id("subSteps").Op(":=").Make(j.Index().Qual("common", "WorkflowSubStep"), j.Lit(0)))
|
||||
g.Add(j.For(j.List(j.Id("_"), j.Id("_s").Op(":=").Range().Id("_subSteps"))).Block(
|
||||
j.Id("subSteps").Op("=").Append(j.Id("subSteps"), j.Qual("common", "WorkflowSubStep").ValuesFunc(
|
||||
func(_g *j.Group) {
|
||||
for _, v := range []string{"Name", "DependsOn", "Inputs", "Outputs", "If", "Timeout", "Meta", "Properties", "Type"} {
|
||||
_g.Add(j.Id(v).Op(":").Id("_s").Dot(v))
|
||||
}
|
||||
}),
|
||||
)),
|
||||
)
|
||||
}
|
||||
g.Add(j.Id("res").Op(":=").Add(DefinitionKindToStatement[kind]).Values(builderDict))
|
||||
g.Add(j.Return(j.Id("res")))
|
||||
})
|
||||
|
||||
return []*j.Statement{typeConst, initFunc, defStruct, defStructConstructor, buildFunc}
|
||||
}
|
||||
|
||||
func (m *GoModifier) genFromFunc() []*j.Statement {
|
||||
kind := m.g.kind
|
||||
kindBaseProperties := map[string][]string{
|
||||
v1beta1.ComponentDefinitionKind: {"Name", "DependsOn", "Inputs", "Outputs"},
|
||||
v1beta1.WorkflowStepDefinitionKind: {"Name", "DependsOn", "Inputs", "Outputs", "If", "Timeout", "Meta"},
|
||||
v1beta1.PolicyDefinitionKind: {"Name"},
|
||||
v1beta1.TraitDefinitionKind: {},
|
||||
}
|
||||
|
||||
// fromFuncRsv means build from a part of K8s Object (e.g. v1beta1.Application.spec.component[*] to internal presentation (e.g. Component)
|
||||
// fromFuncRsv will have a function receiver
|
||||
getSubSteps := func(sub bool) func(g *j.Group) {
|
||||
if m.defKind != v1beta1.WorkflowStepDefinitionKind || sub {
|
||||
return func(g *j.Group) {}
|
||||
}
|
||||
return func(g *j.Group) {
|
||||
g.Add(j.Id("subSteps").Op(":=").Make(j.Index().Qual("apis", DefinitionKindToPascal[kind]), j.Lit(0)))
|
||||
g.Add(
|
||||
j.For(
|
||||
j.List(j.Id("_"), j.Id("_s")).Op(":=").Range().Id("from").Dot("SubSteps")).Block(
|
||||
j.List(j.Id("subStep"), j.Err()).Op(":=").Id(m.defFuncReceiver).Dot("FromWorkflowSubStep").Call(j.Id("_s")),
|
||||
j.If(j.Err().Op("!=").Nil()).Block(
|
||||
j.Return(j.Nil(), j.Err()),
|
||||
),
|
||||
j.Id("subSteps").Op("=").Append(j.Id("subSteps"), j.Id("subStep")),
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
assignSubSteps := func(sub bool) func(g *j.Group) {
|
||||
if m.defKind != v1beta1.WorkflowStepDefinitionKind || sub {
|
||||
return func(g *j.Group) {}
|
||||
}
|
||||
return func(g *j.Group) {
|
||||
g.Add(j.Id(m.defFuncReceiver).Dot("Base").Dot("SubSteps").Op("=").Id("subSteps"))
|
||||
}
|
||||
}
|
||||
fromFuncRsv := func(sub bool) *j.Statement {
|
||||
funcName := "From" + DefinitionKindToPascal[kind]
|
||||
params := DefinitionKindToStatement[kind]
|
||||
if sub {
|
||||
funcName = "FromWorkflowSubStep"
|
||||
params = j.Qual("common", "WorkflowSubStep")
|
||||
}
|
||||
return j.Func().
|
||||
Params(j.Id(m.defFuncReceiver).Add(m.defStructPointer)).
|
||||
Id(funcName).
|
||||
Params(j.Id("from").Add(params)).Params(j.Add(m.defStructPointer), j.Error()).
|
||||
BlockFunc(func(g *j.Group) {
|
||||
if kind == v1beta1.ComponentDefinitionKind {
|
||||
g.Add(j.For(j.List(j.Id("_"), j.Id("trait")).Op(":=").Range().Id("from").Dot("Traits")).Block(
|
||||
j.List(j.Id("_t"), j.Err()).Op(":=").Qual("sdkcommon", "FromTrait").Call(j.Op("&").Id("trait")),
|
||||
j.If(j.Err().Op("!=").Nil()).Block(
|
||||
j.Return(j.Nil(), j.Err()),
|
||||
),
|
||||
j.Id(m.defFuncReceiver).Dot("Base").Dot("Traits").Op("=").Append(j.Id(m.defFuncReceiver).Dot("Base").Dot("Traits"), j.Id("_t")),
|
||||
))
|
||||
}
|
||||
g.Add(j.Var().Id("properties").Id(m.specNameInPascalCase))
|
||||
g.Add(
|
||||
j.If(j.Id("from").Dot("Properties").Op("!=").Nil()).Block(
|
||||
j.Err().Op(":=").Qual("json", "Unmarshal").Call(j.Id("from").Dot("Properties").Dot("Raw"), j.Op("&").Id("properties")),
|
||||
j.If(j.Err().Op("!=").Nil()).Block(
|
||||
j.Return(j.Nil(), j.Err()),
|
||||
),
|
||||
),
|
||||
)
|
||||
getSubSteps(sub)(g)
|
||||
|
||||
for _, prop := range kindBaseProperties[kind] {
|
||||
g.Add(j.Id(m.defFuncReceiver).Dot("Base").Dot(prop).Op("=").Id("from").Dot(prop))
|
||||
}
|
||||
g.Add(j.Id(m.defFuncReceiver).Dot("Base").Dot("Type").Op("=").Id(m.typeVarName))
|
||||
g.Add(j.Id(m.defFuncReceiver).Dot("Properties").Op("=").Id("properties"))
|
||||
|
||||
assignSubSteps(sub)(g)
|
||||
g.Add(j.Return(j.Id(m.defFuncReceiver), j.Nil()))
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
// fromFunc is like fromFuncRsv but not having function receiver, returning an internal presentation
|
||||
fromFunc := j.Func().
|
||||
Id("From"+DefinitionKindToPascal[kind]).
|
||||
Params(j.Id("from").Add(DefinitionKindToStatement[kind])).Params(j.Qual("apis", DefinitionKindToPascal[kind]), j.Error()).
|
||||
Block(
|
||||
j.Id(m.defFuncReceiver).Op(":=").Op("&").Id(m.defStructName).Values(j.Dict{}),
|
||||
j.Return(j.Id(m.defFuncReceiver).Dot("From"+DefinitionKindToPascal[kind]).Call(j.Id("from"))),
|
||||
)
|
||||
fromSubFunc := j.Func().Id("FromWorkflowSubStep").
|
||||
Params(j.Id("from").Qual("common", "WorkflowSubStep")).Params(j.Qual("apis", DefinitionKindToPascal[kind]), j.Error()).
|
||||
Block(
|
||||
j.Id(m.defFuncReceiver).Op(":=").Op("&").Id(m.defStructName).Values(j.Dict{}),
|
||||
j.Return(j.Id(m.defFuncReceiver).Dot("FromWorkflowSubStep").Call(j.Id("from"))),
|
||||
)
|
||||
|
||||
res := []*j.Statement{fromFuncRsv(false), fromFunc}
|
||||
if m.defKind == v1beta1.WorkflowStepDefinitionKind {
|
||||
res = append(res, fromFuncRsv(true), fromSubFunc)
|
||||
}
|
||||
return res
|
||||
}
|
||||
|
||||
// genDedicatedFunc generate functions for definition kinds
|
||||
func (m *GoModifier) genDedicatedFunc() []*j.Statement {
|
||||
switch m.defKind {
|
||||
case v1beta1.ComponentDefinitionKind:
|
||||
addTraitFunc := j.Func().
|
||||
Params(j.Id(m.defFuncReceiver).Add(m.defStructPointer)).
|
||||
Id("AddTrait").
|
||||
Params(j.Id("traits").Op("...").Qual("apis", "Trait")).
|
||||
Add(m.defStructPointer).
|
||||
Block(
|
||||
j.Id(m.defFuncReceiver).Dot("Base").Dot("Traits").Op("=").Append(j.Id(m.defFuncReceiver).Dot("Base").Dot("Traits"), j.Id("traits").Op("...")),
|
||||
j.Return(j.Id(m.defFuncReceiver)),
|
||||
)
|
||||
getTraitFunc := j.Func().
|
||||
Params(j.Id(m.defFuncReceiver).Add(m.defStructPointer)).
|
||||
Id("GetTrait").
|
||||
Params(j.Id("_type").String()).
|
||||
Params(j.Qual("apis", "Trait")).
|
||||
Block(
|
||||
j.For(j.List(j.Id("_"), j.Id("_t")).Op(":=").Range().Id(m.defFuncReceiver).Dot("Base").Dot("Traits")).Block(
|
||||
j.If(j.Id("_t").Dot("DefType").Call().Op("==").Id("_type")).Block(
|
||||
j.Return(j.Id("_t")),
|
||||
),
|
||||
),
|
||||
j.Return(j.Nil()),
|
||||
)
|
||||
|
||||
return []*j.Statement{addTraitFunc, getTraitFunc}
|
||||
case v1beta1.WorkflowStepDefinitionKind:
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *GoModifier) genNameTypeFunc() []*j.Statement {
|
||||
nameFunc := j.Func().Params(j.Id(m.defFuncReceiver).Add(m.defStructPointer)).Id(DefinitionKindToPascal[m.defKind] + "Name").Params().String().Block(
|
||||
j.Return(j.Id(m.defFuncReceiver).Dot("Base").Dot("Name")),
|
||||
)
|
||||
typeFunc := j.Func().Params(j.Id(m.defFuncReceiver).Add(m.defStructPointer)).Id("DefType").Params().String().Block(
|
||||
j.Return(j.Id(m.typeVarName)),
|
||||
)
|
||||
switch m.defKind {
|
||||
case v1beta1.ComponentDefinitionKind, v1beta1.WorkflowStepDefinitionKind, v1beta1.PolicyDefinitionKind:
|
||||
return []*j.Statement{nameFunc, typeFunc}
|
||||
case v1beta1.TraitDefinitionKind:
|
||||
return []*j.Statement{typeFunc}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *GoModifier) genUnmarshalFunc() []*j.Statement {
|
||||
return []*j.Statement{j.Null()}
|
||||
}
|
||||
|
||||
func (m *GoModifier) genBaseSetterFunc() []*j.Statement {
|
||||
baseFuncArgs := map[string][]struct {
|
||||
funcName string
|
||||
argName string
|
||||
argType *j.Statement
|
||||
dst *j.Statement
|
||||
isAppend bool
|
||||
}{
|
||||
v1beta1.ComponentDefinitionKind: {
|
||||
{funcName: "DependsOn", argName: "dependsOn", argType: j.Index().String()},
|
||||
{funcName: "Inputs", argName: "input", argType: j.Qual("common", "StepInputs")},
|
||||
{funcName: "Outputs", argName: "output", argType: j.Qual("common", "StepOutputs")},
|
||||
{funcName: "AddDependsOn", argName: "dependsOn", argType: j.String(), isAppend: true, dst: j.Dot("DependsOn")},
|
||||
// TODO: uncomment this after https://github.com/kubevela/workflow/pull/125 is released.
|
||||
// {funcName: "AddInput", argName: "input", argType: Qual("common", "StepInputs"), isAppend: true, dst: "Inputs"},
|
||||
// {funcName: "AddOutput", argName: "output", argType: Qual("common", "StepOutputs"), isAppend: true, dst: "Outputs"},
|
||||
},
|
||||
v1beta1.WorkflowStepDefinitionKind: {
|
||||
{funcName: "If", argName: "_if", argType: j.String()},
|
||||
{funcName: "Alias", argName: "alias", argType: j.String(), dst: j.Dot("Meta").Dot("Alias")},
|
||||
{funcName: "Timeout", argName: "timeout", argType: j.String()},
|
||||
{funcName: "DependsOn", argName: "dependsOn", argType: j.Index().String()},
|
||||
{funcName: "Inputs", argName: "input", argType: j.Qual("common", "StepInputs")},
|
||||
{funcName: "Outputs", argName: "output", argType: j.Qual("common", "StepOutputs")},
|
||||
// {funcName: "AddInput", argName: "input", argType: Qual("common", "StepInputs"), isAppend: true, dst: "Inputs"},
|
||||
// {funcName: "AddOutput", argName: "output", argType: Qual("common", "StepOutputs"), isAppend: true, dst: "Outputs"},
|
||||
},
|
||||
}
|
||||
baseFuncs := make([]*j.Statement, 0)
|
||||
for _, fn := range baseFuncArgs[m.defKind] {
|
||||
if fn.dst == nil {
|
||||
fn.dst = j.Dot(fn.funcName)
|
||||
}
|
||||
f := j.Func().
|
||||
Params(j.Id(m.defFuncReceiver).Add(m.defStructPointer)).
|
||||
Id(fn.funcName).
|
||||
Params(j.Id(fn.argName).Add(fn.argType)).
|
||||
Add(m.defStructPointer).
|
||||
BlockFunc(func(g *j.Group) {
|
||||
field := j.Id(m.defFuncReceiver).Dot("Base").Add(fn.dst)
|
||||
if fn.isAppend {
|
||||
g.Add(field.Clone().Op("=").Append(field.Clone(), j.Id(fn.argName)))
|
||||
} else {
|
||||
g.Add(field.Clone().Op("=").Id(fn.argName))
|
||||
}
|
||||
g.Add(j.Return(j.Id(m.defFuncReceiver)))
|
||||
})
|
||||
baseFuncs = append(baseFuncs, f)
|
||||
}
|
||||
return baseFuncs
|
||||
}
|
||||
|
||||
func (m *GoModifier) genAddSubStepFunc() *j.Statement {
|
||||
if m.defName != "step-group" || m.defKind != v1beta1.WorkflowStepDefinitionKind {
|
||||
return j.Null()
|
||||
}
|
||||
subList := j.Id(m.defFuncReceiver).Dot("Base").Dot("SubSteps")
|
||||
return j.Func().
|
||||
Params(j.Id(m.defFuncReceiver).Add(m.defStructPointer)).
|
||||
Id("AddSubStep").
|
||||
Params(j.Id("subStep").Qual("apis", "WorkflowStep")).
|
||||
Add(m.defStructPointer).
|
||||
Block(
|
||||
subList.Clone().Op("=").Append(subList.Clone(), j.Id("subStep")),
|
||||
j.Return(j.Id(m.defFuncReceiver)),
|
||||
)
|
||||
}
|
||||
|
||||
// exportMethods will export methods from definition spec struct to definition struct
|
||||
func (m *GoModifier) exportMethods() error {
|
||||
fileLoc := path.Join(m.defDir, m.nameInSnakeCase+".go")
|
||||
// nolint:gosec
|
||||
file, err := os.ReadFile(fileLoc)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
var fileStr = string(file)
|
||||
from := fmt.Sprintf("*%sSpec", m.nameInPascalCase)
|
||||
to := fmt.Sprintf("*%s", m.defStructName)
|
||||
// replace all the function receiver but not below functions
|
||||
// New{m.nameInPascalCase}SpecWith
|
||||
// New{m.nameInPascalCase}Spec
|
||||
fileStr = regexp.MustCompile(fmt.Sprintf(`func \(o \%s\) ([()\[\]{}\w ]+)\%s([ )])`, from, from)).ReplaceAllString(fileStr, fmt.Sprintf("func (o %s) $1%s$2", to, to))
|
||||
fileStr = strings.ReplaceAll(fileStr, "func (o "+from, "func (o "+to)
|
||||
|
||||
// replace all function receiver in function body
|
||||
// o.foo -> o.Properties.foo
|
||||
// seek the MarshalJSON function, replace functions before it
|
||||
from = "o."
|
||||
to = "o.Properties."
|
||||
parts := strings.SplitN(fileStr, "MarshalJSON", 2)
|
||||
if len(parts) != 2 {
|
||||
return fmt.Errorf("can't find MarshalJSON function")
|
||||
}
|
||||
fileStr = strings.ReplaceAll(parts[0], from, to) + "MarshalJSON" + parts[1]
|
||||
|
||||
return os.WriteFile(fileLoc, []byte(fileStr), 0600)
|
||||
}
|
||||
|
||||
func (m *GoModifier) format() error {
|
||||
// check if gofmt is installed
|
||||
|
||||
formatters := []string{"gofmt", "goimports"}
|
||||
formatterPaths := []string{}
|
||||
allFormattersInstalled := true
|
||||
for _, formatter := range formatters {
|
||||
p, err := exec.LookPath(formatter)
|
||||
if err != nil {
|
||||
allFormattersInstalled = false
|
||||
break
|
||||
}
|
||||
formatterPaths = append(formatterPaths, p)
|
||||
}
|
||||
if allFormattersInstalled {
|
||||
for _, fmter := range formatterPaths {
|
||||
if m.verbose {
|
||||
fmt.Printf("Use %s to format code\n", fmter)
|
||||
}
|
||||
// nolint:gosec
|
||||
cmd := exec.Command(fmter, "-w", m.defDir)
|
||||
output, err := cmd.CombinedOutput()
|
||||
if err != nil {
|
||||
return errors.Wrap(err, string(output))
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
// fallback to use go lib
|
||||
if m.verbose {
|
||||
fmt.Println("At least one of linters is not installed, use go/format lib to format code")
|
||||
}
|
||||
files, err := os.ReadDir(m.defDir)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "read dir")
|
||||
}
|
||||
for _, f := range files {
|
||||
if !strings.HasSuffix(f.Name(), ".go") {
|
||||
continue
|
||||
}
|
||||
filePath := path.Join(m.defDir, f.Name())
|
||||
// nolint:gosec
|
||||
content, err := os.ReadFile(filePath)
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, "read file %s", filePath)
|
||||
}
|
||||
formatted, err := format.Source(content)
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, "format file %s", filePath)
|
||||
}
|
||||
err = os.WriteFile(filePath, formatted, 0600)
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, "write file %s", filePath)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
|
@ -0,0 +1,30 @@
|
|||
{{>partial_header}}
|
||||
package {{packageName}}
|
||||
|
||||
{{#models}}
|
||||
import (
|
||||
"encoding/json"
|
||||
|
||||
"github.com/oam-dev/kubevela-core-api/apis/core.oam.dev/common"
|
||||
"github.com/oam-dev/kubevela-core-api/apis/core.oam.dev/v1beta1"
|
||||
"github.com/oam-dev/kubevela-core-api/pkg/oam/util"
|
||||
|
||||
"github.com/kubevela/vela-go-sdk/pkg/apis"
|
||||
"github.com/kubevela/vela-go-sdk/pkg/apis/utils"
|
||||
sdkcommon "github.com/kubevela/vela-go-sdk/pkg/apis/common"
|
||||
{{#imports}}
|
||||
"{{import}}"
|
||||
{{/imports}}
|
||||
)
|
||||
|
||||
{{#model}}
|
||||
{{#isEnum}}{{>model_enum}}{{/isEnum}}
|
||||
{{^isEnum}}
|
||||
{{#oneOf}}{{#-first}}{{>model_oneof}}{{/-first}}{{/oneOf}}
|
||||
{{^oneOf}}
|
||||
{{#anyOf}}{{#-first}}{{>model_anyof}}{{/-first}} {{/anyOf}}
|
||||
{{^anyOf}}{{>model_simple}} {{/anyOf}}
|
||||
{{/oneOf}}
|
||||
{{/isEnum}}
|
||||
{{/model}}
|
||||
{{/models}}
|
|
@ -0,0 +1,76 @@
|
|||
// {{classname}} {{{description}}}{{^description}}struct for {{{classname}}}{{/description}}
|
||||
type {{classname}} struct {
|
||||
{{#anyOf}}
|
||||
{{{.}}} *{{{.}}}
|
||||
{{/anyOf}}
|
||||
}
|
||||
|
||||
// Unmarshal JSON data into any of the pointers in the struct
|
||||
func (dst *{{classname}}) UnmarshalJSON(data []byte) error {
|
||||
var err error
|
||||
{{#isNullable}}
|
||||
// this object is nullable so check if the payload is null or empty string
|
||||
if string(data) == "" || string(data) == "{}" {
|
||||
return nil
|
||||
}
|
||||
|
||||
{{/isNullable}}
|
||||
{{#discriminator}}
|
||||
{{#mappedModels}}
|
||||
{{#-first}}
|
||||
// use discriminator value to speed up the lookup
|
||||
var jsonDict map[string]interface{}
|
||||
err = json.Unmarshal(data, &jsonDict)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to unmarshal JSON into map for the discriminator lookup")
|
||||
}
|
||||
|
||||
{{/-first}}
|
||||
// check if the discriminator value is '{{{mappingName}}}'
|
||||
if jsonDict["{{{propertyBaseName}}}"] == "{{{mappingName}}}" {
|
||||
// try to unmarshal JSON data into {{{modelName}}}
|
||||
err = json.Unmarshal(data, &dst.{{{modelName}}});
|
||||
if err == nil {
|
||||
json{{{modelName}}}, _ := json.Marshal(dst.{{{modelName}}})
|
||||
if string(json{{{modelName}}}) == "{}" { // empty struct
|
||||
dst.{{{modelName}}} = nil
|
||||
} else {
|
||||
return nil // data stored in dst.{{{modelName}}}, return on the first match
|
||||
}
|
||||
} else {
|
||||
dst.{{{modelName}}} = nil
|
||||
}
|
||||
}
|
||||
|
||||
{{/mappedModels}}
|
||||
{{/discriminator}}
|
||||
{{#anyOf}}
|
||||
// try to unmarshal JSON data into {{{.}}}
|
||||
err = json.Unmarshal(data, &dst.{{{.}}});
|
||||
if err == nil {
|
||||
json{{{.}}}, _ := json.Marshal(dst.{{{.}}})
|
||||
if string(json{{{.}}}) == "{}" { // empty struct
|
||||
dst.{{{.}}} = nil
|
||||
} else {
|
||||
return nil // data stored in dst.{{{.}}}, return on the first match
|
||||
}
|
||||
} else {
|
||||
dst.{{{.}}} = nil
|
||||
}
|
||||
|
||||
{{/anyOf}}
|
||||
return fmt.Errorf("data failed to match schemas in anyOf({{classname}})")
|
||||
}
|
||||
|
||||
// Marshal data from the first non-nil pointers in the struct to JSON
|
||||
func (src *{{classname}}) MarshalJSON() ([]byte, error) {
|
||||
{{#anyOf}}
|
||||
if src.{{{.}}} != nil {
|
||||
return json.Marshal(&src.{{{.}}})
|
||||
}
|
||||
|
||||
{{/anyOf}}
|
||||
return nil, nil // no data in anyOf schemas
|
||||
}
|
||||
|
||||
{{>nullable_model}}
|
|
@ -0,0 +1,97 @@
|
|||
{{#models}}{{#model}}# {{classname}}
|
||||
|
||||
{{^isEnum}}
|
||||
## Properties
|
||||
|
||||
Name | Type | Description | Notes
|
||||
------------ | ------------- | ------------- | -------------
|
||||
{{#vendorExtensions.x-is-one-of-interface}}
|
||||
**{{classname}}Interface** | **interface { {{#discriminator}}{{propertyGetter}}() {{propertyType}}{{/discriminator}} }** | An interface that can hold any of the proper implementing types |
|
||||
{{/vendorExtensions.x-is-one-of-interface}}
|
||||
{{^vendorExtensions.x-is-one-of-interface}}
|
||||
{{#vars}}**{{name}}** | {{^required}}Pointer to {{/required}}{{#isContainer}}{{#isArray}}{{#items}}{{^isPrimitiveType}}{{^isFile}}[{{/isFile}}{{/isPrimitiveType}}**[]{{dataType}}**{{^isPrimitiveType}}{{^isFile}}]({{dataType}}.md){{/isFile}}{{/isPrimitiveType}}{{/items}}{{/isArray}}{{#isMap}}{{#items}}{{^isPrimitiveType}}{{^isFile}}[{{/isFile}}{{/isPrimitiveType}}**map[string]{{dataType}}**{{^isPrimitiveType}}{{^isFile}}]({{^baseType}}{{dataType}}{{/baseType}}{{baseType}}.md){{/isFile}}{{/isPrimitiveType}}{{/items}}{{/isMap}}{{/isContainer}}{{^isContainer}}{{^isPrimitiveType}}{{^isFile}}{{^isDateTime}}[{{/isDateTime}}{{/isFile}}{{/isPrimitiveType}}**{{dataType}}**{{^isPrimitiveType}}{{^isFile}}{{^isDateTime}}]({{^baseType}}{{dataType}}{{/baseType}}{{baseType}}.md){{/isDateTime}}{{/isFile}}{{/isPrimitiveType}}{{/isContainer}} | {{description}} | {{^required}}[optional] {{/required}}{{#isReadOnly}}[readonly] {{/isReadOnly}}{{#defaultValue}}[default to {{{.}}}]{{/defaultValue}}
|
||||
{{/vars}}
|
||||
{{/vendorExtensions.x-is-one-of-interface}}
|
||||
|
||||
## Methods
|
||||
|
||||
{{^vendorExtensions.x-is-one-of-interface}}
|
||||
### New{{classname}}
|
||||
|
||||
`func New{{classname}}({{#vars}}{{#required}}{{nameInCamelCase}} {{dataType}}, {{/required}}{{/vars}}) *{{classname}}`
|
||||
|
||||
New{{classname}} instantiates a new {{classname}} object
|
||||
This constructor will assign default values to properties that have it defined,
|
||||
and makes sure properties required by API are set, but the set of arguments
|
||||
will change when the set of required properties is changed
|
||||
|
||||
### New{{classname}}WithDefaults
|
||||
|
||||
`func New{{classname}}WithDefaults() *{{classname}}`
|
||||
|
||||
New{{classname}}WithDefaults instantiates a new {{classname}} object
|
||||
This constructor will only assign default values to properties that have it defined,
|
||||
but it doesn't guarantee that properties required by API are set
|
||||
|
||||
{{#vars}}
|
||||
### Get{{name}}
|
||||
|
||||
`func (o *{{classname}}) Get{{name}}() {{vendorExtensions.x-go-base-type}}`
|
||||
|
||||
Get{{name}} returns the {{name}} field if non-nil, zero value otherwise.
|
||||
|
||||
### Get{{name}}Ok
|
||||
|
||||
`func (o *{{classname}}) Get{{name}}Ok() (*{{vendorExtensions.x-go-base-type}}, bool)`
|
||||
|
||||
Get{{name}}Ok returns a tuple with the {{name}} field if it's non-nil, zero value otherwise
|
||||
and a boolean to check if the value has been set.
|
||||
|
||||
### Set{{name}}
|
||||
|
||||
`func (o *{{classname}}) Set{{name}}(v {{vendorExtensions.x-go-base-type}})`
|
||||
|
||||
Set{{name}} sets {{name}} field to given value.
|
||||
|
||||
{{^required}}
|
||||
### Has{{name}}
|
||||
|
||||
`func (o *{{classname}}) Has{{name}}() bool`
|
||||
|
||||
Has{{name}} returns a boolean if a field has been set.
|
||||
{{/required}}
|
||||
|
||||
{{#isNullable}}
|
||||
### Set{{name}}Nil
|
||||
|
||||
`func (o *{{classname}}) Set{{name}}Nil(b bool)`
|
||||
|
||||
Set{{name}}Nil sets the value for {{name}} to be an explicit nil
|
||||
|
||||
### Unset{{name}}
|
||||
`func (o *{{classname}}) Unset{{name}}()`
|
||||
|
||||
Unset{{name}} ensures that no value is present for {{name}}, not even an explicit nil
|
||||
{{/isNullable}}
|
||||
{{/vars}}
|
||||
{{#vendorExtensions.x-implements}}
|
||||
|
||||
### As{{{.}}}
|
||||
|
||||
`func (s *{{classname}}) As{{{.}}}() {{{.}}}`
|
||||
|
||||
Convenience method to wrap this instance of {{classname}} in {{{.}}}
|
||||
{{/vendorExtensions.x-implements}}
|
||||
{{/vendorExtensions.x-is-one-of-interface}}
|
||||
{{/isEnum}}
|
||||
{{#isEnum}}
|
||||
## Enum
|
||||
|
||||
{{#allowableValues}}{{#enumVars}}
|
||||
* `{{name}}` (value: `{{{value}}}`)
|
||||
{{/enumVars}}{{/allowableValues}}
|
||||
{{/isEnum}}
|
||||
|
||||
[[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md)
|
||||
|
||||
{{/model}}{{/models}}
|
|
@ -0,0 +1,101 @@
|
|||
// {{{classname}}} {{{description}}}{{^description}}the model '{{{classname}}}'{{/description}}
|
||||
type {{{classname}}} {{{format}}}{{^format}}{{dataType}}{{/format}}
|
||||
|
||||
// List of {{{name}}}
|
||||
const (
|
||||
{{#allowableValues}}
|
||||
{{#enumVars}}
|
||||
{{^-first}}
|
||||
{{/-first}}
|
||||
{{#enumClassPrefix}}{{{classname.toUpperCase}}}_{{/enumClassPrefix}}{{name}} {{{classname}}} = {{{value}}}
|
||||
{{/enumVars}}
|
||||
{{/allowableValues}}
|
||||
)
|
||||
|
||||
// All allowed values of {{{classname}}} enum
|
||||
var Allowed{{{classname}}}EnumValues = []{{{classname}}}{
|
||||
{{#allowableValues}}
|
||||
{{#enumVars}}
|
||||
{{{value}}},
|
||||
{{/enumVars}}
|
||||
{{/allowableValues}}
|
||||
}
|
||||
|
||||
func (v *{{{classname}}}) UnmarshalJSON(src []byte) error {
|
||||
var value {{{format}}}{{^format}}{{dataType}}{{/format}}
|
||||
err := json.Unmarshal(src, &value)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
enumTypeValue := {{{classname}}}(value)
|
||||
for _, existing := range Allowed{{{classname}}}EnumValues {
|
||||
if existing == enumTypeValue {
|
||||
*v = enumTypeValue
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
return fmt.Errorf("%+v is not a valid {{classname}}", value)
|
||||
}
|
||||
|
||||
// New{{{classname}}}FromValue returns a pointer to a valid {{{classname}}}
|
||||
// for the value passed as argument, or an error if the value passed is not allowed by the enum
|
||||
func New{{{classname}}}FromValue(v {{{format}}}{{^format}}{{dataType}}{{/format}}) (*{{{classname}}}, error) {
|
||||
ev := {{{classname}}}(v)
|
||||
if ev.IsValid() {
|
||||
return &ev, nil
|
||||
} else {
|
||||
return nil, fmt.Errorf("invalid value '%v' for {{{classname}}}: valid values are %v", v, Allowed{{{classname}}}EnumValues)
|
||||
}
|
||||
}
|
||||
|
||||
// IsValid return true if the value is valid for the enum, false otherwise
|
||||
func (v {{{classname}}}) IsValid() bool {
|
||||
for _, existing := range Allowed{{{classname}}}EnumValues {
|
||||
if existing == v {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// Ptr returns reference to {{{name}}} value
|
||||
func (v {{{classname}}}) Ptr() *{{{classname}}} {
|
||||
return &v
|
||||
}
|
||||
|
||||
type Nullable{{{classname}}} struct {
|
||||
value *{{{classname}}}
|
||||
isSet bool
|
||||
}
|
||||
|
||||
func (v Nullable{{classname}}) Get() *{{classname}} {
|
||||
return v.value
|
||||
}
|
||||
|
||||
func (v *Nullable{{classname}}) Set(val *{{classname}}) {
|
||||
v.value = val
|
||||
v.isSet = true
|
||||
}
|
||||
|
||||
func (v Nullable{{classname}}) IsSet() bool {
|
||||
return v.isSet
|
||||
}
|
||||
|
||||
func (v *Nullable{{classname}}) Unset() {
|
||||
v.value = nil
|
||||
v.isSet = false
|
||||
}
|
||||
|
||||
func NewNullable{{classname}}(val *{{classname}}) *Nullable{{classname}} {
|
||||
return &Nullable{{classname}}{value: val, isSet: true}
|
||||
}
|
||||
|
||||
func (v Nullable{{{classname}}}) MarshalJSON() ([]byte, error) {
|
||||
return json.Marshal(v.value)
|
||||
}
|
||||
|
||||
func (v *Nullable{{{classname}}}) UnmarshalJSON(src []byte) error {
|
||||
v.isSet = true
|
||||
return json.Unmarshal(src, &v.value)
|
||||
}
|
|
@ -0,0 +1,144 @@
|
|||
// {{classname}} - {{{description}}}{{^description}}struct for {{{classname}}}{{/description}}
|
||||
type {{classname}} struct {
|
||||
{{#oneOf}}
|
||||
{{#lambda.type-to-name}}{{{.}}}{{/lambda.type-to-name}} *{{{.}}}
|
||||
{{/oneOf}}
|
||||
}
|
||||
|
||||
{{#oneOf}}
|
||||
// {{{.}}}As{{classname}} is a convenience function that returns {{{.}}} wrapped in {{classname}}
|
||||
func {{#lambda.type-to-name}}{{{.}}}{{/lambda.type-to-name}}As{{classname}}(v *{{{.}}}) {{classname}} {
|
||||
return {{classname}}{
|
||||
{{#lambda.type-to-name}}{{{.}}}{{/lambda.type-to-name}}: v,
|
||||
}
|
||||
}
|
||||
|
||||
{{/oneOf}}
|
||||
|
||||
// Unmarshal JSON data into one of the pointers in the struct
|
||||
func (dst *{{classname}}) UnmarshalJSON(data []byte) error {
|
||||
var err error
|
||||
{{#isNullable}}
|
||||
// this object is nullable so check if the payload is null or empty string
|
||||
if string(data) == "" || string(data) == "{}" {
|
||||
return nil
|
||||
}
|
||||
|
||||
{{/isNullable}}
|
||||
{{#useOneOfDiscriminatorLookup}}
|
||||
{{#discriminator}}
|
||||
{{#mappedModels}}
|
||||
{{#-first}}
|
||||
// use discriminator value to speed up the lookup
|
||||
var jsonDict map[string]interface{}
|
||||
err = utils.NewStrictDecoder(data).Decode(&jsonDict)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to unmarshal JSON into map for the discriminator lookup")
|
||||
}
|
||||
|
||||
{{/-first}}
|
||||
// check if the discriminator value is '{{{mappingName}}}'
|
||||
if jsonDict["{{{propertyBaseName}}}"] == "{{{mappingName}}}" {
|
||||
// try to unmarshal JSON data into {{{modelName}}}
|
||||
err = json.Unmarshal(data, &dst.{{{modelName}}})
|
||||
if err == nil {
|
||||
return nil // data stored in dst.{{{modelName}}}, return on the first match
|
||||
} else {
|
||||
dst.{{{modelName}}} = nil
|
||||
return fmt.Errorf("failed to unmarshal {{classname}} as {{{modelName}}}: %s", err.Error())
|
||||
}
|
||||
}
|
||||
|
||||
{{/mappedModels}}
|
||||
return nil
|
||||
{{/discriminator}}
|
||||
{{^discriminator}}
|
||||
match := 0
|
||||
{{#oneOf}}
|
||||
// try to unmarshal data into {{#lambda.type-to-name}}{{{.}}}{{/lambda.type-to-name}}
|
||||
err = json.Unmarshal(data, &dst.{{#lambda.type-to-name}}{{{.}}}{{/lambda.type-to-name}})
|
||||
if err == nil {
|
||||
json{{{.}}}, _ := json.Marshal(dst.{{#lambda.type-to-name}}{{{.}}}{{/lambda.type-to-name}})
|
||||
if string(json{{{.}}}) == "{}" { // empty struct
|
||||
dst.{{#lambda.type-to-name}}{{{.}}}{{/lambda.type-to-name}} = nil
|
||||
} else {
|
||||
match++
|
||||
}
|
||||
} else {
|
||||
dst.{{#lambda.type-to-name}}{{{.}}}{{/lambda.type-to-name}} = nil
|
||||
}
|
||||
|
||||
{{/oneOf}}
|
||||
if match > 1 { // more than 1 match
|
||||
// reset to nil
|
||||
{{#oneOf}}
|
||||
dst.{{#lambda.type-to-name}}{{{.}}}{{/lambda.type-to-name}} = nil
|
||||
{{/oneOf}}
|
||||
|
||||
return fmt.Errorf("data matches more than one schema in oneOf({{classname}})")
|
||||
} else if match == 1 {
|
||||
return nil // exactly one match
|
||||
} else { // no match
|
||||
return fmt.Errorf("data failed to match schemas in oneOf({{classname}})")
|
||||
}
|
||||
{{/discriminator}}
|
||||
{{/useOneOfDiscriminatorLookup}}
|
||||
{{^useOneOfDiscriminatorLookup}}
|
||||
match := 0
|
||||
{{#oneOf}}
|
||||
// try to unmarshal data into {{#lambda.type-to-name}}{{{.}}}{{/lambda.type-to-name}}
|
||||
err = utils.NewStrictDecoder(data).Decode(&dst.{{#lambda.type-to-name}}{{{.}}}{{/lambda.type-to-name}})
|
||||
if err == nil {
|
||||
json{{#lambda.type-to-name}}{{{.}}}{{/lambda.type-to-name}}, _ := json.Marshal(dst.{{#lambda.type-to-name}}{{{.}}}{{/lambda.type-to-name}})
|
||||
if string(json{{#lambda.type-to-name}}{{{.}}}{{/lambda.type-to-name}}) == "{}" { // empty struct
|
||||
dst.{{#lambda.type-to-name}}{{{.}}}{{/lambda.type-to-name}} = nil
|
||||
} else {
|
||||
match++
|
||||
}
|
||||
} else {
|
||||
dst.{{#lambda.type-to-name}}{{{.}}}{{/lambda.type-to-name}} = nil
|
||||
}
|
||||
|
||||
{{/oneOf}}
|
||||
if match > 1 { // more than 1 match
|
||||
// reset to nil
|
||||
{{#oneOf}}
|
||||
dst.{{#lambda.type-to-name}}{{{.}}}{{/lambda.type-to-name}} = nil
|
||||
{{/oneOf}}
|
||||
|
||||
return fmt.Errorf("data matches more than one schema in oneOf({{classname}})")
|
||||
} else if match == 1 {
|
||||
return nil // exactly one match
|
||||
} else { // no match
|
||||
return fmt.Errorf("data failed to match schemas in oneOf({{classname}})")
|
||||
}
|
||||
{{/useOneOfDiscriminatorLookup}}
|
||||
}
|
||||
|
||||
// Marshal data from the first non-nil pointers in the struct to JSON
|
||||
func (src {{classname}}) MarshalJSON() ([]byte, error) {
|
||||
{{#oneOf}}
|
||||
if src.{{#lambda.type-to-name}}{{{.}}}{{/lambda.type-to-name}} != nil {
|
||||
return json.Marshal(&src.{{#lambda.type-to-name}}{{{.}}}{{/lambda.type-to-name}})
|
||||
}
|
||||
|
||||
{{/oneOf}}
|
||||
return nil, nil // no data in oneOf schemas
|
||||
}
|
||||
|
||||
// Get the actual instance
|
||||
func (obj *{{classname}}) GetActualInstance() (interface{}) {
|
||||
if obj == nil {
|
||||
return nil
|
||||
}
|
||||
{{#oneOf}}
|
||||
if obj.{{#lambda.type-to-name}}{{{.}}}{{/lambda.type-to-name}} != nil {
|
||||
return obj.{{#lambda.type-to-name}}{{{.}}}{{/lambda.type-to-name}}
|
||||
}
|
||||
|
||||
{{/oneOf}}
|
||||
// all schemas are nil
|
||||
return nil
|
||||
}
|
||||
|
||||
{{>nullable_model}}
|
|
@ -0,0 +1,470 @@
|
|||
// checks if the {{classname}} type satisfies the MappedNullable interface at compile time
|
||||
var _ utils.MappedNullable = &{{classname}}{}
|
||||
|
||||
// {{classname}} {{{description}}}{{^description}}struct for {{{classname}}}{{/description}}
|
||||
type {{classname}} struct {
|
||||
{{#parent}}
|
||||
{{^isMap}}
|
||||
{{^isArray}}
|
||||
{{{parent}}}
|
||||
{{/isArray}}
|
||||
{{/isMap}}
|
||||
{{#isArray}}
|
||||
Items {{{parent}}}
|
||||
{{/isArray}}
|
||||
{{/parent}}
|
||||
{{#vars}}
|
||||
{{^-first}}
|
||||
{{/-first}}
|
||||
{{#description}}
|
||||
// {{{.}}}
|
||||
{{/description}}
|
||||
{{#deprecated}}
|
||||
// Deprecated
|
||||
{{/deprecated}}
|
||||
{{name}} {{^required}}{{^isNullable}}{{^isArray}}{{^isFreeFormObject}}*{{/isFreeFormObject}}{{/isArray}}{{/isNullable}}{{/required}}{{{dataType}}} `json:"{{baseName}}{{^required}},omitempty{{/required}}"{{#withXml}} xml:"{{baseName}}{{#isXmlAttribute}},attr{{/isXmlAttribute}}"{{/withXml}}{{#vendorExtensions.x-go-custom-tag}} {{{.}}}{{/vendorExtensions.x-go-custom-tag}}`
|
||||
{{/vars}}
|
||||
{{#isAdditionalPropertiesTrue}}
|
||||
AdditionalProperties map[string]interface{}
|
||||
{{/isAdditionalPropertiesTrue}}
|
||||
}
|
||||
|
||||
{{#isAdditionalPropertiesTrue}}
|
||||
type _{{{classname}}} {{{classname}}}
|
||||
|
||||
{{/isAdditionalPropertiesTrue}}
|
||||
// New{{classname}}With instantiates a new {{classname}} object
|
||||
// This constructor will assign default values to properties that have it defined,
|
||||
// and makes sure properties required by API are set, but the set of arguments
|
||||
// will change when the set of required properties is changed
|
||||
func New{{classname}}With({{#requiredVars}}{{nameInCamelCase}} {{dataType}}{{^-last}}, {{/-last}}{{/requiredVars}}) *{{classname}} {
|
||||
this := {{classname}}{}
|
||||
{{#allVars}}
|
||||
{{#required}}
|
||||
this.{{name}} = {{nameInCamelCase}}
|
||||
{{/required}}
|
||||
{{^required}}
|
||||
{{#defaultValue}}
|
||||
{{^vendorExtensions.x-golang-is-container}}
|
||||
{{^isReadOnly}}
|
||||
{{#isNullable}}
|
||||
var {{nameInCamelCase}} {{{datatypeWithEnum}}} = {{{.}}}
|
||||
this.{{name}} = *New{{{dataType}}}(&{{nameInCamelCase}})
|
||||
{{/isNullable}}
|
||||
{{^isNullable}}
|
||||
var {{nameInCamelCase}} {{{dataType}}} = {{{.}}}
|
||||
this.{{name}} = &{{nameInCamelCase}}
|
||||
{{/isNullable}}
|
||||
{{/isReadOnly}}
|
||||
{{/vendorExtensions.x-golang-is-container}}
|
||||
{{/defaultValue}}
|
||||
{{/required}}
|
||||
{{/allVars}}
|
||||
return &this
|
||||
}
|
||||
|
||||
// New{{classname}} instantiates a new {{classname}} object
|
||||
// This constructor will only assign default values to properties that have it defined,
|
||||
// but it doesn't guarantee that properties required by API are set
|
||||
func New{{classname}}() *{{classname}} {
|
||||
this := {{classname}}{}
|
||||
{{#vars}}
|
||||
{{#defaultValue}}
|
||||
{{^vendorExtensions.x-golang-is-container}}
|
||||
{{^isReadOnly}}
|
||||
{{#isNullable}}
|
||||
{{!we use datatypeWithEnum here, since it will represent the non-nullable name of the datatype, e.g. int64 for NullableInt64}}
|
||||
var {{nameInCamelCase}} {{{datatypeWithEnum}}} = {{{.}}}
|
||||
this.{{name}} = *New{{{dataType}}}(&{{nameInCamelCase}})
|
||||
{{/isNullable}}
|
||||
{{^isNullable}}
|
||||
var {{nameInCamelCase}} {{{dataType}}} = {{{.}}}
|
||||
this.{{name}} = {{^required}}&{{/required}}{{nameInCamelCase}}
|
||||
{{/isNullable}}
|
||||
{{/isReadOnly}}
|
||||
{{/vendorExtensions.x-golang-is-container}}
|
||||
{{/defaultValue}}
|
||||
{{/vars}}
|
||||
return &this
|
||||
}
|
||||
|
||||
// New{{classname}}s converts a list {{classname}} pointers to objects.
|
||||
// This is helpful when the Set{{classname}} requires a list of objects
|
||||
func New{{classname}}s(ps ...*{{classname}}) []{{classname}} {
|
||||
objs := []{{classname}}{}
|
||||
for _, p := range ps {
|
||||
objs = append(objs, *p)
|
||||
}
|
||||
return objs
|
||||
}
|
||||
|
||||
{{#vars}}
|
||||
{{#required}}
|
||||
// Get{{name}} returns the {{name}} field value
|
||||
{{#isNullable}}
|
||||
// If the value is explicit nil, the zero value for {{vendorExtensions.x-go-base-type}} will be returned
|
||||
{{/isNullable}}
|
||||
{{#deprecated}}
|
||||
// Deprecated
|
||||
{{/deprecated}}
|
||||
func (o *{{classname}}) Get{{name}}() {{vendorExtensions.x-go-base-type}} {
|
||||
if o == nil{{#isNullable}}{{^vendorExtensions.x-golang-is-container}} || o.{{name}}.Get() == nil{{/vendorExtensions.x-golang-is-container}}{{/isNullable}} {
|
||||
var ret {{vendorExtensions.x-go-base-type}}
|
||||
return ret
|
||||
}
|
||||
|
||||
{{#isNullable}}
|
||||
{{#vendorExtensions.x-golang-is-container}}
|
||||
return o.{{name}}
|
||||
{{/vendorExtensions.x-golang-is-container}}
|
||||
{{^vendorExtensions.x-golang-is-container}}
|
||||
return *o.{{name}}.Get()
|
||||
{{/vendorExtensions.x-golang-is-container}}
|
||||
{{/isNullable}}
|
||||
{{^isNullable}}
|
||||
return o.{{name}}
|
||||
{{/isNullable}}
|
||||
}
|
||||
|
||||
// Get{{name}}Ok returns a tuple with the {{name}} field value
|
||||
// and a boolean to check if the value has been set.
|
||||
{{#isNullable}}
|
||||
// NOTE: If the value is an explicit nil, `nil, true` will be returned
|
||||
{{/isNullable}}
|
||||
{{#deprecated}}
|
||||
// Deprecated
|
||||
{{/deprecated}}
|
||||
func (o *{{classname}}) Get{{name}}Ok() ({{^isArray}}{{^isFreeFormObject}}*{{/isFreeFormObject}}{{/isArray}}{{vendorExtensions.x-go-base-type}}, bool) {
|
||||
if o == nil{{#isNullable}}{{#vendorExtensions.x-golang-is-container}} || utils.IsNil(o.{{name}}){{/vendorExtensions.x-golang-is-container}}{{/isNullable}} {
|
||||
{{^isFreeFormObject}}
|
||||
return nil, false
|
||||
{{/isFreeFormObject}}
|
||||
{{#isFreeFormObject}}
|
||||
return {{vendorExtensions.x-go-base-type}}{}, false
|
||||
{{/isFreeFormObject}}
|
||||
}
|
||||
{{#isNullable}}
|
||||
{{#vendorExtensions.x-golang-is-container}}
|
||||
return {{^isArray}}{{^isFreeFormObject}}&{{/isFreeFormObject}}{{/isArray}}o.{{name}}, true
|
||||
{{/vendorExtensions.x-golang-is-container}}
|
||||
{{^vendorExtensions.x-golang-is-container}}
|
||||
return o.{{name}}.Get(), o.{{name}}.IsSet()
|
||||
{{/vendorExtensions.x-golang-is-container}}
|
||||
{{/isNullable}}
|
||||
{{^isNullable}}
|
||||
return {{^isArray}}{{^isFreeFormObject}}&{{/isFreeFormObject}}{{/isArray}}o.{{name}}, true
|
||||
{{/isNullable}}
|
||||
}
|
||||
|
||||
// Set{{name}} sets field value
|
||||
{{#deprecated}}
|
||||
// Deprecated
|
||||
{{/deprecated}}
|
||||
func (o *{{classname}}) Set{{name}}(v {{vendorExtensions.x-go-base-type}}) *{{classname}} {
|
||||
{{#isNullable}}
|
||||
{{#vendorExtensions.x-golang-is-container}}
|
||||
o.{{name}} = v
|
||||
return o
|
||||
{{/vendorExtensions.x-golang-is-container}}
|
||||
{{^vendorExtensions.x-golang-is-container}}
|
||||
o.{{name}}.Set(&v)
|
||||
return o
|
||||
{{/vendorExtensions.x-golang-is-container}}
|
||||
{{/isNullable}}
|
||||
{{^isNullable}}
|
||||
o.{{name}} = v
|
||||
return o
|
||||
{{/isNullable}}
|
||||
}
|
||||
|
||||
{{/required}}
|
||||
{{^required}}
|
||||
// Get{{name}} returns the {{name}} field value if set, zero value otherwise{{#isNullable}} (both if not set or set to explicit null){{/isNullable}}.
|
||||
{{#deprecated}}
|
||||
// Deprecated
|
||||
{{/deprecated}}
|
||||
func (o *{{classname}}) Get{{name}}() {{vendorExtensions.x-go-base-type}} {
|
||||
if o == nil{{^isNullable}} || utils.IsNil(o.{{name}}){{/isNullable}}{{#isNullable}}{{^vendorExtensions.x-golang-is-container}} || utils.IsNil(o.{{name}}.Get()){{/vendorExtensions.x-golang-is-container}}{{/isNullable}} {
|
||||
var ret {{vendorExtensions.x-go-base-type}}
|
||||
return ret
|
||||
}
|
||||
{{#isNullable}}
|
||||
{{#vendorExtensions.x-golang-is-container}}
|
||||
return o.{{name}}
|
||||
{{/vendorExtensions.x-golang-is-container}}
|
||||
{{^vendorExtensions.x-golang-is-container}}
|
||||
return *o.{{name}}.Get()
|
||||
{{/vendorExtensions.x-golang-is-container}}
|
||||
{{/isNullable}}
|
||||
{{^isNullable}}
|
||||
return {{^isArray}}{{^isFreeFormObject}}*{{/isFreeFormObject}}{{/isArray}}o.{{name}}
|
||||
{{/isNullable}}
|
||||
}
|
||||
|
||||
// Get{{name}}Ok returns a tuple with the {{name}} field value if set, nil otherwise
|
||||
// and a boolean to check if the value has been set.
|
||||
{{#isNullable}}
|
||||
// NOTE: If the value is an explicit nil, `nil, true` will be returned
|
||||
{{/isNullable}}
|
||||
{{#deprecated}}
|
||||
// Deprecated
|
||||
{{/deprecated}}
|
||||
func (o *{{classname}}) Get{{name}}Ok() ({{^isArray}}{{^isFreeFormObject}}*{{/isFreeFormObject}}{{/isArray}}{{vendorExtensions.x-go-base-type}}, bool) {
|
||||
if o == nil{{^isNullable}} || utils.IsNil(o.{{name}}){{/isNullable}}{{#isNullable}}{{#vendorExtensions.x-golang-is-container}} || utils.IsNil(o.{{name}}){{/vendorExtensions.x-golang-is-container}}{{/isNullable}} {
|
||||
{{^isFreeFormObject}}
|
||||
return nil, false
|
||||
{{/isFreeFormObject}}
|
||||
{{#isFreeFormObject}}
|
||||
return {{vendorExtensions.x-go-base-type}}{}, false
|
||||
{{/isFreeFormObject}}
|
||||
}
|
||||
{{#isNullable}}
|
||||
{{#vendorExtensions.x-golang-is-container}}
|
||||
return {{^isArray}}{{^isFreeFormObject}}&{{/isFreeFormObject}}{{/isArray}}o.{{name}}, true
|
||||
{{/vendorExtensions.x-golang-is-container}}
|
||||
{{^vendorExtensions.x-golang-is-container}}
|
||||
return o.{{name}}.Get(), o.{{name}}.IsSet()
|
||||
{{/vendorExtensions.x-golang-is-container}}
|
||||
{{/isNullable}}
|
||||
{{^isNullable}}
|
||||
return o.{{name}}, true
|
||||
{{/isNullable}}
|
||||
}
|
||||
|
||||
// Has{{name}} returns a boolean if a field has been set.
|
||||
func (o *{{classname}}) Has{{name}}() bool {
|
||||
if o != nil && {{^isNullable}}!utils.IsNil(o.{{name}}){{/isNullable}}{{#isNullable}}{{#vendorExtensions.x-golang-is-container}}utils.IsNil(o.{{name}}){{/vendorExtensions.x-golang-is-container}}{{^vendorExtensions.x-golang-is-container}}o.{{name}}.IsSet(){{/vendorExtensions.x-golang-is-container}}{{/isNullable}} {
|
||||
return true
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
// Set{{name}} gets a reference to the given {{dataType}} and assigns it to the {{nameInCamelCase}} field.
|
||||
// {{Name}}: {{#description}} {{{.}}} {{/description}}
|
||||
{{#deprecated}}
|
||||
// Deprecated
|
||||
{{/deprecated}}
|
||||
func (o *{{classname}}) Set{{name}}(v {{vendorExtensions.x-go-base-type}}) (*{{classname}}){
|
||||
{{#isNullable}}
|
||||
{{#vendorExtensions.x-golang-is-container}}
|
||||
o.{{name}} = v
|
||||
{{/vendorExtensions.x-golang-is-container}}
|
||||
{{^vendorExtensions.x-golang-is-container}}
|
||||
o.{{name}}.Set({{^isArray}}{{^isFreeFormObject}}&{{/isFreeFormObject}}{{/isArray}}v)
|
||||
{{/vendorExtensions.x-golang-is-container}}
|
||||
{{/isNullable}}
|
||||
{{^isNullable}}
|
||||
o.{{name}} = {{^isArray}}{{^isFreeFormObject}}&{{/isFreeFormObject}}{{/isArray}}v
|
||||
{{/isNullable}}
|
||||
return o
|
||||
}
|
||||
{{#isNullable}}
|
||||
{{^vendorExtensions.x-golang-is-container}}
|
||||
// Set{{name}}Nil sets the value for {{name}} to be an explicit nil
|
||||
func (o *{{classname}}) Set{{name}}Nil() {
|
||||
o.{{name}}.Set(nil)
|
||||
}
|
||||
|
||||
// Unset{{name}} ensures that no value is present for {{name}}, not even an explicit nil
|
||||
func (o *{{classname}}) Unset{{name}}() {
|
||||
o.{{name}}.Unset()
|
||||
}
|
||||
{{/vendorExtensions.x-golang-is-container}}
|
||||
{{/isNullable}}
|
||||
|
||||
{{/required}}
|
||||
{{/vars}}
|
||||
func (o {{classname}}) MarshalJSON() ([]byte, error) {
|
||||
toSerialize,err := o.ToMap()
|
||||
if err != nil {
|
||||
return []byte{}, err
|
||||
}
|
||||
return json.Marshal(toSerialize)
|
||||
}
|
||||
|
||||
func (o {{classname}}) ToMap() (map[string]interface{}, error) {
|
||||
toSerialize := {{#isArray}}make([]interface{}, len(o.Items)){{/isArray}}{{^isArray}}map[string]interface{}{}{{/isArray}}
|
||||
{{#parent}}
|
||||
{{^isMap}}
|
||||
{{^isArray}}
|
||||
serialized{{parent}}, err{{parent}} := json.Marshal(o.{{parent}})
|
||||
if err{{parent}} != nil {
|
||||
return map[string]interface{}{}, err{{parent}}
|
||||
}
|
||||
err{{parent}} = json.Unmarshal([]byte(serialized{{parent}}), &toSerialize)
|
||||
if err{{parent}} != nil {
|
||||
return map[string]interface{}{}, err{{parent}}
|
||||
}
|
||||
{{/isArray}}
|
||||
{{/isMap}}
|
||||
{{#isArray}}
|
||||
for i, item := range o.Items {
|
||||
toSerialize[i] = item
|
||||
}
|
||||
{{/isArray}}
|
||||
{{/parent}}
|
||||
{{#vars}}
|
||||
{{! if argument is nullable, only serialize it if it is set}}
|
||||
{{#isNullable}}
|
||||
{{#vendorExtensions.x-golang-is-container}}
|
||||
{{! support for container fields is not ideal at this point because of lack of Nullable* types}}
|
||||
if o.{{name}} != nil {
|
||||
toSerialize["{{baseName}}"] = o.{{name}}
|
||||
}
|
||||
{{/vendorExtensions.x-golang-is-container}}
|
||||
{{^vendorExtensions.x-golang-is-container}}
|
||||
{{#required}}
|
||||
toSerialize["{{baseName}}"] = o.{{name}}.Get()
|
||||
{{/required}}
|
||||
{{^required}}
|
||||
if o.{{name}}.IsSet() {
|
||||
toSerialize["{{baseName}}"] = o.{{name}}.Get()
|
||||
}
|
||||
{{/required}}
|
||||
{{/vendorExtensions.x-golang-is-container}}
|
||||
{{/isNullable}}
|
||||
{{! if argument is not nullable, don't set it if it is nil}}
|
||||
{{^isNullable}}
|
||||
{{^isReadOnly}}
|
||||
{{#required}}
|
||||
toSerialize["{{baseName}}"] = o.{{name}}
|
||||
{{/required}}
|
||||
{{^required}}
|
||||
if !utils.IsNil(o.{{name}}) {
|
||||
toSerialize["{{baseName}}"] = o.{{name}}
|
||||
}
|
||||
{{/required}}
|
||||
{{/isReadOnly}}
|
||||
{{#isReadOnly}}
|
||||
// skip: {{baseName}} is readOnly
|
||||
{{/isReadOnly}}
|
||||
{{/isNullable}}
|
||||
{{/vars}}
|
||||
{{#isAdditionalPropertiesTrue}}
|
||||
|
||||
for key, value := range o.AdditionalProperties {
|
||||
toSerialize[key] = value
|
||||
}
|
||||
|
||||
{{/isAdditionalPropertiesTrue}}
|
||||
return toSerialize, nil
|
||||
}
|
||||
|
||||
{{#isAdditionalPropertiesTrue}}
|
||||
func (o *{{{classname}}}) UnmarshalJSON(bytes []byte) (err error) {
|
||||
{{#parent}}
|
||||
{{^isMap}}
|
||||
type {{classname}}WithoutEmbeddedStruct struct {
|
||||
{{#vars}}
|
||||
{{^-first}}
|
||||
{{/-first}}
|
||||
{{#description}}
|
||||
// {{{.}}}
|
||||
{{/description}}
|
||||
{{#deprecated}}
|
||||
// Deprecated
|
||||
{{/deprecated}}
|
||||
{{name}} {{^required}}{{^isNullable}}{{^isArray}}{{^isFreeFormObject}}*{{/isFreeFormObject}}{{/isArray}}{{/isNullable}}{{/required}}{{{dataType}}} `json:"{{baseName}}{{^required}},omitempty{{/required}}"{{#withXml}} xml:"{{baseName}}{{#isXmlAttribute}},attr{{/isXmlAttribute}}"{{/withXml}}{{#vendorExtensions.x-go-custom-tag}} {{{.}}}{{/vendorExtensions.x-go-custom-tag}}`
|
||||
{{/vars}}
|
||||
}
|
||||
|
||||
var{{{classname}}}WithoutEmbeddedStruct := {{{classname}}}WithoutEmbeddedStruct{}
|
||||
|
||||
err = json.Unmarshal(bytes, &var{{{classname}}}WithoutEmbeddedStruct)
|
||||
if err == nil {
|
||||
var{{{classname}}} := _{{{classname}}}{}
|
||||
{{#vars}}
|
||||
var{{{classname}}}.{{{name}}} = var{{{classname}}}WithoutEmbeddedStruct.{{{name}}}
|
||||
{{/vars}}
|
||||
*o = {{{classname}}}(var{{{classname}}})
|
||||
} else {
|
||||
return err
|
||||
}
|
||||
|
||||
var{{{classname}}} := _{{{classname}}}{}
|
||||
|
||||
err = json.Unmarshal(bytes, &var{{{classname}}})
|
||||
if err == nil {
|
||||
o.{{{parent}}} = var{{{classname}}}.{{{parent}}}
|
||||
} else {
|
||||
return err
|
||||
}
|
||||
|
||||
additionalProperties := make(map[string]interface{})
|
||||
|
||||
if err = json.Unmarshal(bytes, &additionalProperties); err == nil {
|
||||
{{#vars}}
|
||||
delete(additionalProperties, "{{{baseName}}}")
|
||||
{{/vars}}
|
||||
|
||||
// remove fields from embedded structs
|
||||
reflect{{{parent}}} := reflect.ValueOf(o.{{{parent}}})
|
||||
for i := 0; i < reflect{{{parent}}}.Type().NumField(); i++ {
|
||||
t := reflect{{{parent}}}.Type().Field(i)
|
||||
|
||||
if jsonTag := t.Tag.Get("json"); jsonTag != "" {
|
||||
fieldName := ""
|
||||
if commaIdx := strings.Index(jsonTag, ","); commaIdx > 0 {
|
||||
fieldName = jsonTag[:commaIdx]
|
||||
} else {
|
||||
fieldName = jsonTag
|
||||
}
|
||||
if fieldName != "AdditionalProperties" {
|
||||
delete(additionalProperties, fieldName)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
o.AdditionalProperties = additionalProperties
|
||||
}
|
||||
|
||||
return err
|
||||
{{/isMap}}
|
||||
{{#isMap}}
|
||||
var{{{classname}}} := _{{{classname}}}{}
|
||||
|
||||
if err = json.Unmarshal(bytes, &var{{{classname}}}); err == nil {
|
||||
*o = {{{classname}}}(var{{{classname}}})
|
||||
}
|
||||
|
||||
additionalProperties := make(map[string]interface{})
|
||||
|
||||
if err = json.Unmarshal(bytes, &additionalProperties); err == nil {
|
||||
{{#vars}}
|
||||
delete(additionalProperties, "{{{baseName}}}")
|
||||
{{/vars}}
|
||||
o.AdditionalProperties = additionalProperties
|
||||
}
|
||||
|
||||
return err
|
||||
{{/isMap}}
|
||||
{{/parent}}
|
||||
{{^parent}}
|
||||
var{{{classname}}} := _{{{classname}}}{}
|
||||
|
||||
if err = json.Unmarshal(bytes, &var{{{classname}}}); err == nil {
|
||||
*o = {{{classname}}}(var{{{classname}}})
|
||||
}
|
||||
|
||||
additionalProperties := make(map[string]interface{})
|
||||
|
||||
if err = json.Unmarshal(bytes, &additionalProperties); err == nil {
|
||||
{{#vars}}
|
||||
delete(additionalProperties, "{{{baseName}}}")
|
||||
{{/vars}}
|
||||
o.AdditionalProperties = additionalProperties
|
||||
}
|
||||
|
||||
return err
|
||||
{{/parent}}
|
||||
}
|
||||
|
||||
{{/isAdditionalPropertiesTrue}}
|
||||
{{#isArray}}
|
||||
func (o *{{{classname}}}) UnmarshalJSON(bytes []byte) (err error) {
|
||||
return json.Unmarshal(bytes, &o.Items)
|
||||
}
|
||||
|
||||
{{/isArray}}
|
||||
{{>nullable_model}}
|
|
@ -0,0 +1,35 @@
|
|||
type Nullable{{{classname}}} struct {
|
||||
value {{^isArray}}{{^isFreeFormObject}}*{{/isFreeFormObject}}{{/isArray}}{{{classname}}}
|
||||
isSet bool
|
||||
}
|
||||
|
||||
func (v Nullable{{classname}}) Get() {{^isArray}}{{^isFreeFormObject}}*{{/isFreeFormObject}}{{/isArray}}{{classname}} {
|
||||
return v.value
|
||||
}
|
||||
|
||||
func (v *Nullable{{classname}}) Set(val {{^isArray}}{{^isFreeFormObject}}*{{/isFreeFormObject}}{{/isArray}}{{classname}}) {
|
||||
v.value = val
|
||||
v.isSet = true
|
||||
}
|
||||
|
||||
func (v Nullable{{classname}}) IsSet() bool {
|
||||
return v.isSet
|
||||
}
|
||||
|
||||
func (v *Nullable{{classname}}) Unset() {
|
||||
v.value = nil
|
||||
v.isSet = false
|
||||
}
|
||||
|
||||
func NewNullable{{classname}}(val {{^isArray}}{{^isFreeFormObject}}*{{/isFreeFormObject}}{{/isArray}}{{classname}}) *Nullable{{classname}} {
|
||||
return &Nullable{{classname}}{value: val, isSet: true}
|
||||
}
|
||||
|
||||
func (v Nullable{{{classname}}}) MarshalJSON() ([]byte, error) {
|
||||
return json.Marshal(v.value)
|
||||
}
|
||||
|
||||
func (v *Nullable{{{classname}}}) UnmarshalJSON(src []byte) error {
|
||||
v.isSet = true
|
||||
return json.Unmarshal(src, &v.value)
|
||||
}
|
|
@ -0,0 +1,18 @@
|
|||
/*
|
||||
{{#appName}}
|
||||
{{{.}}}
|
||||
|
||||
{{/appName}}
|
||||
{{#appDescription}}
|
||||
{{{.}}}
|
||||
|
||||
{{/appDescription}}
|
||||
{{#version}}
|
||||
API version: {{{.}}}
|
||||
{{/version}}
|
||||
{{#infoEmail}}
|
||||
Contact: {{{.}}}
|
||||
{{/infoEmail}}
|
||||
*/
|
||||
|
||||
// Code generated by OpenAPI Generator (https://openapi-generator.tech); DO NOT EDIT.
|
|
@ -0,0 +1,346 @@
|
|||
{{>partial_header}}
|
||||
package {{packageName}}
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"reflect"
|
||||
"time"
|
||||
)
|
||||
|
||||
// PtrBool is a helper routine that returns a pointer to given boolean value.
|
||||
func PtrBool(v bool) *bool { return &v }
|
||||
|
||||
// PtrInt is a helper routine that returns a pointer to given integer value.
|
||||
func PtrInt(v int) *int { return &v }
|
||||
|
||||
// PtrInt32 is a helper routine that returns a pointer to given integer value.
|
||||
func PtrInt32(v int32) *int32 { return &v }
|
||||
|
||||
// PtrInt64 is a helper routine that returns a pointer to given integer value.
|
||||
func PtrInt64(v int64) *int64 { return &v }
|
||||
|
||||
// PtrFloat32 is a helper routine that returns a pointer to given float value.
|
||||
func PtrFloat32(v float32) *float32 { return &v }
|
||||
|
||||
// PtrFloat64 is a helper routine that returns a pointer to given float value.
|
||||
func PtrFloat64(v float64) *float64 { return &v }
|
||||
|
||||
// PtrString is a helper routine that returns a pointer to given string value.
|
||||
func PtrString(v string) *string { return &v }
|
||||
|
||||
// PtrTime is helper routine that returns a pointer to given Time value.
|
||||
func PtrTime(v time.Time) *time.Time { return &v }
|
||||
|
||||
type NullableBool struct {
|
||||
value *bool
|
||||
isSet bool
|
||||
}
|
||||
|
||||
func (v NullableBool) Get() *bool {
|
||||
return v.value
|
||||
}
|
||||
|
||||
func (v *NullableBool) Set(val *bool) {
|
||||
v.value = val
|
||||
v.isSet = true
|
||||
}
|
||||
|
||||
func (v NullableBool) IsSet() bool {
|
||||
return v.isSet
|
||||
}
|
||||
|
||||
func (v *NullableBool) Unset() {
|
||||
v.value = nil
|
||||
v.isSet = false
|
||||
}
|
||||
|
||||
func NewNullableBool(val *bool) *NullableBool {
|
||||
return &NullableBool{value: val, isSet: true}
|
||||
}
|
||||
|
||||
func (v NullableBool) MarshalJSON() ([]byte, error) {
|
||||
return json.Marshal(v.value)
|
||||
}
|
||||
|
||||
func (v *NullableBool) UnmarshalJSON(src []byte) error {
|
||||
v.isSet = true
|
||||
return json.Unmarshal(src, &v.value)
|
||||
}
|
||||
|
||||
type NullableInt struct {
|
||||
value *int
|
||||
isSet bool
|
||||
}
|
||||
|
||||
func (v NullableInt) Get() *int {
|
||||
return v.value
|
||||
}
|
||||
|
||||
func (v *NullableInt) Set(val *int) {
|
||||
v.value = val
|
||||
v.isSet = true
|
||||
}
|
||||
|
||||
func (v NullableInt) IsSet() bool {
|
||||
return v.isSet
|
||||
}
|
||||
|
||||
func (v *NullableInt) Unset() {
|
||||
v.value = nil
|
||||
v.isSet = false
|
||||
}
|
||||
|
||||
func NewNullableInt(val *int) *NullableInt {
|
||||
return &NullableInt{value: val, isSet: true}
|
||||
}
|
||||
|
||||
func (v NullableInt) MarshalJSON() ([]byte, error) {
|
||||
return json.Marshal(v.value)
|
||||
}
|
||||
|
||||
func (v *NullableInt) UnmarshalJSON(src []byte) error {
|
||||
v.isSet = true
|
||||
return json.Unmarshal(src, &v.value)
|
||||
}
|
||||
|
||||
type NullableInt32 struct {
|
||||
value *int32
|
||||
isSet bool
|
||||
}
|
||||
|
||||
func (v NullableInt32) Get() *int32 {
|
||||
return v.value
|
||||
}
|
||||
|
||||
func (v *NullableInt32) Set(val *int32) {
|
||||
v.value = val
|
||||
v.isSet = true
|
||||
}
|
||||
|
||||
func (v NullableInt32) IsSet() bool {
|
||||
return v.isSet
|
||||
}
|
||||
|
||||
func (v *NullableInt32) Unset() {
|
||||
v.value = nil
|
||||
v.isSet = false
|
||||
}
|
||||
|
||||
func NewNullableInt32(val *int32) *NullableInt32 {
|
||||
return &NullableInt32{value: val, isSet: true}
|
||||
}
|
||||
|
||||
func (v NullableInt32) MarshalJSON() ([]byte, error) {
|
||||
return json.Marshal(v.value)
|
||||
}
|
||||
|
||||
func (v *NullableInt32) UnmarshalJSON(src []byte) error {
|
||||
v.isSet = true
|
||||
return json.Unmarshal(src, &v.value)
|
||||
}
|
||||
|
||||
type NullableInt64 struct {
|
||||
value *int64
|
||||
isSet bool
|
||||
}
|
||||
|
||||
func (v NullableInt64) Get() *int64 {
|
||||
return v.value
|
||||
}
|
||||
|
||||
func (v *NullableInt64) Set(val *int64) {
|
||||
v.value = val
|
||||
v.isSet = true
|
||||
}
|
||||
|
||||
func (v NullableInt64) IsSet() bool {
|
||||
return v.isSet
|
||||
}
|
||||
|
||||
func (v *NullableInt64) Unset() {
|
||||
v.value = nil
|
||||
v.isSet = false
|
||||
}
|
||||
|
||||
func NewNullableInt64(val *int64) *NullableInt64 {
|
||||
return &NullableInt64{value: val, isSet: true}
|
||||
}
|
||||
|
||||
func (v NullableInt64) MarshalJSON() ([]byte, error) {
|
||||
return json.Marshal(v.value)
|
||||
}
|
||||
|
||||
func (v *NullableInt64) UnmarshalJSON(src []byte) error {
|
||||
v.isSet = true
|
||||
return json.Unmarshal(src, &v.value)
|
||||
}
|
||||
|
||||
type NullableFloat32 struct {
|
||||
value *float32
|
||||
isSet bool
|
||||
}
|
||||
|
||||
func (v NullableFloat32) Get() *float32 {
|
||||
return v.value
|
||||
}
|
||||
|
||||
func (v *NullableFloat32) Set(val *float32) {
|
||||
v.value = val
|
||||
v.isSet = true
|
||||
}
|
||||
|
||||
func (v NullableFloat32) IsSet() bool {
|
||||
return v.isSet
|
||||
}
|
||||
|
||||
func (v *NullableFloat32) Unset() {
|
||||
v.value = nil
|
||||
v.isSet = false
|
||||
}
|
||||
|
||||
func NewNullableFloat32(val *float32) *NullableFloat32 {
|
||||
return &NullableFloat32{value: val, isSet: true}
|
||||
}
|
||||
|
||||
func (v NullableFloat32) MarshalJSON() ([]byte, error) {
|
||||
return json.Marshal(v.value)
|
||||
}
|
||||
|
||||
func (v *NullableFloat32) UnmarshalJSON(src []byte) error {
|
||||
v.isSet = true
|
||||
return json.Unmarshal(src, &v.value)
|
||||
}
|
||||
|
||||
type NullableFloat64 struct {
|
||||
value *float64
|
||||
isSet bool
|
||||
}
|
||||
|
||||
func (v NullableFloat64) Get() *float64 {
|
||||
return v.value
|
||||
}
|
||||
|
||||
func (v *NullableFloat64) Set(val *float64) {
|
||||
v.value = val
|
||||
v.isSet = true
|
||||
}
|
||||
|
||||
func (v NullableFloat64) IsSet() bool {
|
||||
return v.isSet
|
||||
}
|
||||
|
||||
func (v *NullableFloat64) Unset() {
|
||||
v.value = nil
|
||||
v.isSet = false
|
||||
}
|
||||
|
||||
func NewNullableFloat64(val *float64) *NullableFloat64 {
|
||||
return &NullableFloat64{value: val, isSet: true}
|
||||
}
|
||||
|
||||
func (v NullableFloat64) MarshalJSON() ([]byte, error) {
|
||||
return json.Marshal(v.value)
|
||||
}
|
||||
|
||||
func (v *NullableFloat64) UnmarshalJSON(src []byte) error {
|
||||
v.isSet = true
|
||||
return json.Unmarshal(src, &v.value)
|
||||
}
|
||||
|
||||
type NullableString struct {
|
||||
value *string
|
||||
isSet bool
|
||||
}
|
||||
|
||||
func (v NullableString) Get() *string {
|
||||
return v.value
|
||||
}
|
||||
|
||||
func (v *NullableString) Set(val *string) {
|
||||
v.value = val
|
||||
v.isSet = true
|
||||
}
|
||||
|
||||
func (v NullableString) IsSet() bool {
|
||||
return v.isSet
|
||||
}
|
||||
|
||||
func (v *NullableString) Unset() {
|
||||
v.value = nil
|
||||
v.isSet = false
|
||||
}
|
||||
|
||||
func NewNullableString(val *string) *NullableString {
|
||||
return &NullableString{value: val, isSet: true}
|
||||
}
|
||||
|
||||
func (v NullableString) MarshalJSON() ([]byte, error) {
|
||||
return json.Marshal(v.value)
|
||||
}
|
||||
|
||||
func (v *NullableString) UnmarshalJSON(src []byte) error {
|
||||
v.isSet = true
|
||||
return json.Unmarshal(src, &v.value)
|
||||
}
|
||||
|
||||
type NullableTime struct {
|
||||
value *time.Time
|
||||
isSet bool
|
||||
}
|
||||
|
||||
func (v NullableTime) Get() *time.Time {
|
||||
return v.value
|
||||
}
|
||||
|
||||
func (v *NullableTime) Set(val *time.Time) {
|
||||
v.value = val
|
||||
v.isSet = true
|
||||
}
|
||||
|
||||
func (v NullableTime) IsSet() bool {
|
||||
return v.isSet
|
||||
}
|
||||
|
||||
func (v *NullableTime) Unset() {
|
||||
v.value = nil
|
||||
v.isSet = false
|
||||
}
|
||||
|
||||
func NewNullableTime(val *time.Time) *NullableTime {
|
||||
return &NullableTime{value: val, isSet: true}
|
||||
}
|
||||
|
||||
func (v NullableTime) MarshalJSON() ([]byte, error) {
|
||||
return v.value.MarshalJSON()
|
||||
}
|
||||
|
||||
func (v *NullableTime) UnmarshalJSON(src []byte) error {
|
||||
v.isSet = true
|
||||
return json.Unmarshal(src, &v.value)
|
||||
}
|
||||
|
||||
// IsNil checks if an input is nil
|
||||
func IsNil(i interface{}) bool {
|
||||
if i == nil {
|
||||
return true
|
||||
}
|
||||
switch reflect.TypeOf(i).Kind() {
|
||||
case reflect.Chan, reflect.Func, reflect.Map, reflect.Ptr, reflect.UnsafePointer, reflect.Interface, reflect.Slice:
|
||||
return reflect.ValueOf(i).IsNil()
|
||||
case reflect.Array:
|
||||
return reflect.ValueOf(i).IsZero()
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
type MappedNullable interface {
|
||||
ToMap() (map[string]interface{}, error)
|
||||
}
|
||||
|
||||
// A wrapper for strict JSON decoding
|
||||
func NewStrictDecoder(data []byte) *json.Decoder {
|
||||
dec := json.NewDecoder(bytes.NewBuffer(data))
|
||||
dec.DisallowUnknownFields()
|
||||
return dec
|
||||
}
|
|
@ -0,0 +1,141 @@
|
|||
import (
|
||||
"vela/op"
|
||||
"strings"
|
||||
)
|
||||
|
||||
"apply-terraform-provider": {
|
||||
alias: ""
|
||||
attributes: {}
|
||||
description: "Apply terraform provider config"
|
||||
annotations: {
|
||||
"category": "Terraform"
|
||||
}
|
||||
labels: {}
|
||||
type: "workflow-step"
|
||||
}
|
||||
|
||||
template: {
|
||||
config: op.#CreateConfig & {
|
||||
name: "\(context.name)-\(context.stepName)"
|
||||
namespace: context.namespace
|
||||
template: "terraform-\(parameter.type)"
|
||||
config: {
|
||||
name: parameter.name
|
||||
if parameter.type == "alibaba" {
|
||||
ALICLOUD_ACCESS_KEY: parameter.accessKey
|
||||
ALICLOUD_SECRET_KEY: parameter.secretKey
|
||||
ALICLOUD_REGION: parameter.region
|
||||
}
|
||||
if parameter.type == "aws" {
|
||||
AWS_ACCESS_KEY_ID: parameter.accessKey
|
||||
AWS_SECRET_ACCESS_KEY: parameter.secretKey
|
||||
AWS_DEFAULT_REGION: parameter.region
|
||||
AWS_SESSION_TOKEN: parameter.token
|
||||
}
|
||||
if parameter.type == "azure" {
|
||||
ARM_CLIENT_ID: parameter.clientID
|
||||
ARM_CLIENT_SECRET: parameter.clientSecret
|
||||
ARM_SUBSCRIPTION_ID: parameter.subscriptionID
|
||||
ARM_TENANT_ID: parameter.tenantID
|
||||
}
|
||||
if parameter.type == "baidu" {
|
||||
BAIDUCLOUD_ACCESS_KEY: parameter.accessKey
|
||||
BAIDUCLOUD_SECRET_KEY: parameter.secretKey
|
||||
BAIDUCLOUD_REGION: parameter.region
|
||||
}
|
||||
if parameter.type == "ec" {
|
||||
EC_API_KEY: parameter.apiKey
|
||||
}
|
||||
if parameter.type == "gcp" {
|
||||
GOOGLE_CREDENTIALS: parameter.credentials
|
||||
GOOGLE_REGION: parameter.region
|
||||
GOOGLE_PROJECT: parameter.project
|
||||
}
|
||||
if parameter.type == "tencent" {
|
||||
TENCENTCLOUD_SECRET_ID: parameter.secretID
|
||||
TENCENTCLOUD_SECRET_KEY: parameter.secretKey
|
||||
TENCENTCLOUD_REGION: parameter.region
|
||||
}
|
||||
if parameter.type == "ucloud" {
|
||||
UCLOUD_PRIVATE_KEY: parameter.privateKey
|
||||
UCLOUD_PUBLIC_KEY: parameter.publicKey
|
||||
UCLOUD_PROJECT_ID: parameter.projectID
|
||||
UCLOUD_REGION: parameter.region
|
||||
}
|
||||
}
|
||||
}
|
||||
read: op.#Read & {
|
||||
value: {
|
||||
apiVersion: "terraform.core.oam.dev/v1beta1"
|
||||
kind: "Provider"
|
||||
metadata: {
|
||||
name: parameter.name
|
||||
namespace: context.namespace
|
||||
}
|
||||
}
|
||||
}
|
||||
check: op.#ConditionalWait & {
|
||||
if read.value.status != _|_ {
|
||||
continue: read.value.status.state == "ready"
|
||||
}
|
||||
if read.value.status == _|_ {
|
||||
continue: false
|
||||
}
|
||||
}
|
||||
providerBasic: {
|
||||
accessKey: string
|
||||
secretKey: string
|
||||
region: string
|
||||
}
|
||||
#AlibabaProvider: {
|
||||
providerBasic
|
||||
type: "alibaba"
|
||||
name: *"alibaba-provider" | string
|
||||
}
|
||||
#AWSProvider: {
|
||||
providerBasic
|
||||
token: *"" | string
|
||||
type: "aws"
|
||||
name: *"aws-provider" | string
|
||||
}
|
||||
#AzureProvider: {
|
||||
subscriptionID: string
|
||||
tenantID: string
|
||||
clientID: string
|
||||
clientSecret: string
|
||||
name: *"azure-provider" | string
|
||||
}
|
||||
#BaiduProvider: {
|
||||
providerBasic
|
||||
type: "baidu"
|
||||
name: *"baidu-provider" | string
|
||||
}
|
||||
#ECProvider: {
|
||||
type: "ec"
|
||||
apiKey: *"" | string
|
||||
name: *"ec-provider" | string
|
||||
}
|
||||
#GCPProvider: {
|
||||
credentials: string
|
||||
region: string
|
||||
project: string
|
||||
type: "gcp"
|
||||
name: *"gcp-provider" | string
|
||||
}
|
||||
#TencentProvider: {
|
||||
secretID: string
|
||||
secretKey: string
|
||||
region: string
|
||||
type: "tencent"
|
||||
name: *"tencent-provider" | string
|
||||
}
|
||||
#UCloudProvider: {
|
||||
publicKey: string
|
||||
privateKey: string
|
||||
projectID: string
|
||||
region: string
|
||||
type: "ucloud"
|
||||
name: *"ucloud-provider" | string
|
||||
}
|
||||
parameter: *#AlibabaProvider | #AWSProvider | #AzureProvider | #BaiduProvider | #ECProvider | #GCPProvider | #TencentProvider | #UCloudProvider
|
||||
}
|
|
@ -0,0 +1,323 @@
|
|||
"cron-task": {
|
||||
type: "component"
|
||||
annotations: {}
|
||||
labels: {}
|
||||
description: "Describes cron jobs that run code or a script to completion."
|
||||
attributes: workload: {
|
||||
definition: {
|
||||
apiVersion: "batch/v1beta1"
|
||||
kind: "CronJob"
|
||||
}
|
||||
type: "cronjobs.batch"
|
||||
}
|
||||
}
|
||||
template: {
|
||||
output: {
|
||||
if context.clusterVersion.minor < 25 {
|
||||
apiVersion: "batch/v1beta1"
|
||||
}
|
||||
if context.clusterVersion.minor >= 25 {
|
||||
apiVersion: "batch/v1"
|
||||
}
|
||||
kind: "CronJob"
|
||||
spec: {
|
||||
schedule: parameter.schedule
|
||||
concurrencyPolicy: parameter.concurrencyPolicy
|
||||
suspend: parameter.suspend
|
||||
successfulJobsHistoryLimit: parameter.successfulJobsHistoryLimit
|
||||
failedJobsHistoryLimit: parameter.failedJobsHistoryLimit
|
||||
if parameter.startingDeadlineSeconds != _|_ {
|
||||
startingDeadlineSeconds: parameter.startingDeadlineSeconds
|
||||
}
|
||||
jobTemplate: {
|
||||
metadata: {
|
||||
labels: {
|
||||
if parameter.labels != _|_ {
|
||||
parameter.labels
|
||||
}
|
||||
"app.oam.dev/name": context.appName
|
||||
"app.oam.dev/component": context.name
|
||||
}
|
||||
if parameter.annotations != _|_ {
|
||||
annotations: parameter.annotations
|
||||
}
|
||||
}
|
||||
spec: {
|
||||
parallelism: parameter.count
|
||||
completions: parameter.count
|
||||
if parameter.ttlSecondsAfterFinished != _|_ {
|
||||
ttlSecondsAfterFinished: parameter.ttlSecondsAfterFinished
|
||||
}
|
||||
if parameter.activeDeadlineSeconds != _|_ {
|
||||
activeDeadlineSeconds: parameter.activeDeadlineSeconds
|
||||
}
|
||||
backoffLimit: parameter.backoffLimit
|
||||
template: {
|
||||
metadata: {
|
||||
labels: {
|
||||
if parameter.labels != _|_ {
|
||||
parameter.labels
|
||||
}
|
||||
"app.oam.dev/name": context.appName
|
||||
"app.oam.dev/component": context.name
|
||||
}
|
||||
if parameter.annotations != _|_ {
|
||||
annotations: parameter.annotations
|
||||
}
|
||||
}
|
||||
spec: {
|
||||
restartPolicy: parameter.restart
|
||||
containers: [{
|
||||
name: context.name
|
||||
image: parameter.image
|
||||
if parameter["imagePullPolicy"] != _|_ {
|
||||
imagePullPolicy: parameter.imagePullPolicy
|
||||
}
|
||||
if parameter["cmd"] != _|_ {
|
||||
command: parameter.cmd
|
||||
}
|
||||
if parameter["env"] != _|_ {
|
||||
env: parameter.env
|
||||
}
|
||||
if parameter["cpu"] != _|_ {
|
||||
resources: {
|
||||
limits: cpu: parameter.cpu
|
||||
requests: cpu: parameter.cpu
|
||||
}
|
||||
}
|
||||
if parameter["memory"] != _|_ {
|
||||
resources: {
|
||||
limits: memory: parameter.memory
|
||||
requests: memory: parameter.memory
|
||||
}
|
||||
}
|
||||
if parameter["volumes"] != _|_ {
|
||||
volumeMounts: [ for v in parameter.volumes {
|
||||
{
|
||||
mountPath: v.mountPath
|
||||
name: v.name
|
||||
}}]
|
||||
}
|
||||
}]
|
||||
if parameter["volumes"] != _|_ {
|
||||
volumes: [ for v in parameter.volumes {
|
||||
{
|
||||
name: v.name
|
||||
if v.type == "pvc" {
|
||||
persistentVolumeClaim: claimName: v.claimName
|
||||
}
|
||||
if v.type == "configMap" {
|
||||
configMap: {
|
||||
defaultMode: v.defaultMode
|
||||
name: v.cmName
|
||||
if v.items != _|_ {
|
||||
items: v.items
|
||||
}
|
||||
}
|
||||
}
|
||||
if v.type == "secret" {
|
||||
secret: {
|
||||
defaultMode: v.defaultMode
|
||||
secretName: v.secretName
|
||||
if v.items != _|_ {
|
||||
items: v.items
|
||||
}
|
||||
}
|
||||
}
|
||||
if v.type == "emptyDir" {
|
||||
emptyDir: medium: v.medium
|
||||
}
|
||||
}}]
|
||||
}
|
||||
if parameter["imagePullSecrets"] != _|_ {
|
||||
imagePullSecrets: [ for v in parameter.imagePullSecrets {
|
||||
name: v
|
||||
},
|
||||
]
|
||||
}
|
||||
if parameter.hostAliases != _|_ {
|
||||
hostAliases: [ for v in parameter.hostAliases {
|
||||
ip: v.ip
|
||||
hostnames: v.hostnames
|
||||
},
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
parameter: {
|
||||
// +usage=Specify the labels in the workload
|
||||
labels?: [string]: string
|
||||
|
||||
// +usage=Specify the annotations in the workload
|
||||
annotations?: [string]: string
|
||||
|
||||
// +usage=Specify the schedule in Cron format, see https://en.wikipedia.org/wiki/Cron
|
||||
schedule: string
|
||||
|
||||
// +usage=Specify deadline in seconds for starting the job if it misses scheduled
|
||||
startingDeadlineSeconds?: int
|
||||
|
||||
// +usage=suspend subsequent executions
|
||||
suspend: *false | bool
|
||||
|
||||
// +usage=Specifies how to treat concurrent executions of a Job
|
||||
concurrencyPolicy: *"Allow" | "Allow" | "Forbid" | "Replace"
|
||||
|
||||
// +usage=The number of successful finished jobs to retain
|
||||
successfulJobsHistoryLimit: *3 | int
|
||||
|
||||
// +usage=The number of failed finished jobs to retain
|
||||
failedJobsHistoryLimit: *1 | int
|
||||
|
||||
// +usage=Specify number of tasks to run in parallel
|
||||
// +short=c
|
||||
count: *1 | int
|
||||
|
||||
// +usage=Which image would you like to use for your service
|
||||
// +short=i
|
||||
image: string
|
||||
|
||||
// +usage=Specify image pull policy for your service
|
||||
imagePullPolicy?: "Always" | "Never" | "IfNotPresent"
|
||||
|
||||
// +usage=Specify image pull secrets for your service
|
||||
imagePullSecrets?: [...string]
|
||||
|
||||
// +usage=Define the job restart policy, the value can only be Never or OnFailure. By default, it's Never.
|
||||
restart: *"Never" | string
|
||||
|
||||
// +usage=Commands to run in the container
|
||||
cmd?: [...string]
|
||||
|
||||
// +usage=Define arguments by using environment variables
|
||||
env?: [...{
|
||||
// +usage=Environment variable name
|
||||
name: string
|
||||
// +usage=The value of the environment variable
|
||||
value?: string
|
||||
// +usage=Specifies a source the value of this var should come from
|
||||
valueFrom?: {
|
||||
// +usage=Selects a key of a secret in the pod's namespace
|
||||
secretKeyRef?: {
|
||||
// +usage=The name of the secret in the pod's namespace to select from
|
||||
name: string
|
||||
// +usage=The key of the secret to select from. Must be a valid secret key
|
||||
key: string
|
||||
}
|
||||
// +usage=Selects a key of a config map in the pod's namespace
|
||||
configMapKeyRef?: {
|
||||
// +usage=The name of the config map in the pod's namespace to select from
|
||||
name: string
|
||||
// +usage=The key of the config map to select from. Must be a valid secret key
|
||||
key: string
|
||||
}
|
||||
}
|
||||
}]
|
||||
|
||||
// +usage=Number of CPU units for the service, like `0.5` (0.5 CPU core), `1` (1 CPU core)
|
||||
cpu?: string
|
||||
|
||||
// +usage=Specifies the attributes of the memory resource required for the container.
|
||||
memory?: string
|
||||
|
||||
// +usage=Declare volumes and volumeMounts
|
||||
volumes?: [...{
|
||||
name: string
|
||||
mountPath: string
|
||||
// +usage=Specify volume type, options: "pvc","configMap","secret","emptyDir", default to emptyDir
|
||||
type: *"emptyDir" | "pvc" | "configMap" | "secret"
|
||||
if type == "pvc" {
|
||||
claimName: string
|
||||
}
|
||||
if type == "configMap" {
|
||||
defaultMode: *420 | int
|
||||
cmName: string
|
||||
items?: [...{
|
||||
key: string
|
||||
path: string
|
||||
mode: *511 | int
|
||||
}]
|
||||
}
|
||||
if type == "secret" {
|
||||
defaultMode: *420 | int
|
||||
secretName: string
|
||||
items?: [...{
|
||||
key: string
|
||||
path: string
|
||||
mode: *511 | int
|
||||
}]
|
||||
}
|
||||
if type == "emptyDir" {
|
||||
medium: *"" | "Memory"
|
||||
}
|
||||
}]
|
||||
|
||||
// +usage=An optional list of hosts and IPs that will be injected into the pod's hosts file
|
||||
hostAliases?: [...{
|
||||
ip: string
|
||||
hostnames: [...string]
|
||||
}]
|
||||
|
||||
// +usage=Limits the lifetime of a Job that has finished
|
||||
ttlSecondsAfterFinished?: int
|
||||
|
||||
// +usage=The duration in seconds relative to the startTime that the job may be continuously active before the system tries to terminate it
|
||||
activeDeadlineSeconds?: int
|
||||
|
||||
// +usage=The number of retries before marking this job failed
|
||||
backoffLimit: *6 | int
|
||||
|
||||
// +usage=Instructions for assessing whether the container is alive.
|
||||
livenessProbe?: #HealthProbe
|
||||
|
||||
// +usage=Instructions for assessing whether the container is in a suitable state to serve traffic.
|
||||
readinessProbe?: #HealthProbe
|
||||
}
|
||||
|
||||
#HealthProbe: {
|
||||
|
||||
// +usage=Instructions for assessing container health by executing a command. Either this attribute or the httpGet attribute or the tcpSocket attribute MUST be specified. This attribute is mutually exclusive with both the httpGet attribute and the tcpSocket attribute.
|
||||
exec?: {
|
||||
// +usage=A command to be executed inside the container to assess its health. Each space delimited token of the command is a separate array element. Commands exiting 0 are considered to be successful probes, whilst all other exit codes are considered failures.
|
||||
command: [...string]
|
||||
}
|
||||
|
||||
// +usage=Instructions for assessing container health by executing an HTTP GET request. Either this attribute or the exec attribute or the tcpSocket attribute MUST be specified. This attribute is mutually exclusive with both the exec attribute and the tcpSocket attribute.
|
||||
httpGet?: {
|
||||
// +usage=The endpoint, relative to the port, to which the HTTP GET request should be directed.
|
||||
path: string
|
||||
// +usage=The TCP socket within the container to which the HTTP GET request should be directed.
|
||||
port: int
|
||||
httpHeaders?: [...{
|
||||
name: string
|
||||
value: string
|
||||
}]
|
||||
}
|
||||
|
||||
// +usage=Instructions for assessing container health by probing a TCP socket. Either this attribute or the exec attribute or the httpGet attribute MUST be specified. This attribute is mutually exclusive with both the exec attribute and the httpGet attribute.
|
||||
tcpSocket?: {
|
||||
// +usage=The TCP socket within the container that should be probed to assess container health.
|
||||
port: int
|
||||
}
|
||||
|
||||
// +usage=Number of seconds after the container is started before the first probe is initiated.
|
||||
initialDelaySeconds: *0 | int
|
||||
|
||||
// +usage=How often, in seconds, to execute the probe.
|
||||
periodSeconds: *10 | int
|
||||
|
||||
// +usage=Number of seconds after which the probe times out.
|
||||
timeoutSeconds: *1 | int
|
||||
|
||||
// +usage=Minimum consecutive successes for the probe to be considered successful after having failed.
|
||||
successThreshold: *1 | int
|
||||
|
||||
// +usage=Number of consecutive failures required to determine the container is not alive (liveness probe) or not ready (readiness probe).
|
||||
failureThreshold: *3 | int
|
||||
}
|
||||
}
|
|
@ -0,0 +1,31 @@
|
|||
import (
|
||||
"vela/op"
|
||||
)
|
||||
|
||||
"deploy": {
|
||||
type: "workflow-step"
|
||||
annotations: {
|
||||
"category": "Application Delivery"
|
||||
}
|
||||
labels: {
|
||||
"scope": "Application"
|
||||
}
|
||||
description: "A powerful and unified deploy step for components multi-cluster delivery with policies."
|
||||
}
|
||||
template: {
|
||||
deploy: op.#Deploy & {
|
||||
policies: parameter.policies
|
||||
parallelism: parameter.parallelism
|
||||
ignoreTerraformComponent: parameter.ignoreTerraformComponent
|
||||
}
|
||||
parameter: {
|
||||
//+usage=If set to false, the workflow will suspend automatically before this step, default to be true.
|
||||
auto: *true | bool
|
||||
//+usage=Declare the policies that used for this deployment. If not specified, the components will be deployed to the hub cluster.
|
||||
policies: *[] | [...string]
|
||||
//+usage=Maximum number of concurrent delivered components.
|
||||
parallelism: *5 | int
|
||||
//+usage=If set false, this step will apply the components with the terraform workload.
|
||||
ignoreTerraformComponent: *true | bool
|
||||
}
|
||||
}
|
|
@ -0,0 +1,17 @@
|
|||
"json-merge-patch": {
|
||||
type: "trait"
|
||||
annotations: {}
|
||||
labels: {
|
||||
"ui-hidden": "true"
|
||||
}
|
||||
description: "Patch the output following Json Merge Patch strategy, following RFC 7396."
|
||||
attributes: {
|
||||
podDisruptive: true
|
||||
appliesToWorkloads: ["*"]
|
||||
}
|
||||
}
|
||||
template: {
|
||||
parameter: {...}
|
||||
// +patchStrategy=jsonMergePatch
|
||||
patch: parameter
|
||||
}
|
|
@ -0,0 +1,28 @@
|
|||
import (
|
||||
"vela/op"
|
||||
"encoding/base64"
|
||||
)
|
||||
|
||||
"one_of": {
|
||||
type: "workflow-step"
|
||||
description: "Send notifications to Email, DingTalk, Slack, Lark or webhook in your workflow. For test one_of"
|
||||
}
|
||||
template: {
|
||||
parameter: {
|
||||
// +usage=Please fulfill its url and message if you want to send Lark messages
|
||||
lark?: {
|
||||
// +usage=Specify the the lark url, you can either sepcify it in value or use secretRef
|
||||
url: close({
|
||||
// +usage=the url address content in string
|
||||
value: string
|
||||
}) | close({
|
||||
secretRef: {
|
||||
// +usage=name is the name of the secret
|
||||
name: string
|
||||
// +usage=key is the key in the secret
|
||||
key: string
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,36 @@
|
|||
"shared-resource": {
|
||||
annotations: {}
|
||||
description: "Configure the resources to be sharable across applications."
|
||||
labels: {}
|
||||
attributes: {}
|
||||
type: "policy"
|
||||
}
|
||||
|
||||
template: {
|
||||
#SharedResourcePolicyRule: {
|
||||
// +usage=Specify how to select the targets of the rule
|
||||
selector: [...#ResourcePolicyRuleSelector]
|
||||
}
|
||||
|
||||
#ResourcePolicyRuleSelector: {
|
||||
// +usage=Select resources by component names
|
||||
componentNames?: [...string]
|
||||
// +usage=Select resources by component types
|
||||
componentTypes?: [...string]
|
||||
// +usage=Select resources by oamTypes (COMPONENT or TRAIT)
|
||||
oamTypes?: [...string]
|
||||
// +usage=Select resources by trait types
|
||||
traitTypes?: [...string]
|
||||
// +usage=Select resources by resource types (like Deployment)
|
||||
resourceTypes?: [...string]
|
||||
// +usage=Select resources by their names
|
||||
resourceNames?: [...string]
|
||||
}
|
||||
|
||||
parameter: {
|
||||
// +usage=Specify the list of rules to control shared-resource strategy at resource level.
|
||||
// The selected resource will be sharable across applications. (That means multiple applications
|
||||
// can all read it without conflict, but only the first one can write it)
|
||||
rules?: [...#SharedResourcePolicyRule]
|
||||
}
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
"step-group": {
|
||||
type: "workflow-step"
|
||||
annotations: {
|
||||
"category": "Process Control"
|
||||
}
|
||||
description: "A special step that you can declare 'subSteps' in it, 'subSteps' is an array containing any step type whose valid parameters do not include the `step-group` step type itself. The sub steps were executed in parallel."
|
||||
}
|
||||
template: {
|
||||
// no parameters, the nop only to make the template not empty or it's invalid
|
||||
nop: {}
|
||||
}
|
|
@ -1,312 +0,0 @@
|
|||
/*
|
||||
Copyright 2021 The KubeVela Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package definition
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"go/format"
|
||||
"strings"
|
||||
"unicode"
|
||||
|
||||
"cuelang.org/go/cue"
|
||||
"github.com/fatih/camelcase"
|
||||
"github.com/pkg/errors"
|
||||
|
||||
"github.com/oam-dev/kubevela/apis/types"
|
||||
velacue "github.com/oam-dev/kubevela/pkg/cue"
|
||||
)
|
||||
|
||||
// StructParameter is a parameter that can be printed as a struct.
|
||||
type StructParameter struct {
|
||||
types.Parameter
|
||||
// GoType is the same to parameter.Type but can be print in Go
|
||||
GoType string
|
||||
Fields []Field
|
||||
}
|
||||
|
||||
// Field is a field of a struct.
|
||||
type Field struct {
|
||||
Name string
|
||||
// GoType is the same to parameter.Type but can be print in Go
|
||||
GoType string
|
||||
OmitEmpty bool
|
||||
}
|
||||
|
||||
//nolint:gochecknoglobals
|
||||
var (
|
||||
WellKnownAbbreviations = map[string]bool{
|
||||
"API": true,
|
||||
"DB": true,
|
||||
"HTTP": true,
|
||||
"HTTPS": true,
|
||||
"ID": true,
|
||||
"JSON": true,
|
||||
"OS": true,
|
||||
"SQL": true,
|
||||
"SSH": true,
|
||||
"URI": true,
|
||||
"URL": true,
|
||||
"XML": true,
|
||||
"YAML": true,
|
||||
|
||||
"CPU": true,
|
||||
"PVC": true,
|
||||
}
|
||||
|
||||
DefaultNamer = NewFieldNamer("")
|
||||
)
|
||||
|
||||
// A FieldNamer generates a Go field name from a CUE label.
|
||||
type FieldNamer interface {
|
||||
FieldName(label string) string
|
||||
SetPrefix(string)
|
||||
}
|
||||
|
||||
// NewFieldNamer returns a new FieldNamer.
|
||||
func NewFieldNamer(prefix string) FieldNamer {
|
||||
return &AbbrFieldNamer{Prefix: prefix, Abbreviations: WellKnownAbbreviations}
|
||||
}
|
||||
|
||||
var structs []StructParameter
|
||||
|
||||
// GeneratorParameterStructs generates structs for parameters in cue.
|
||||
func GeneratorParameterStructs(param cue.Value) ([]StructParameter, error) {
|
||||
structs = []StructParameter{}
|
||||
err := parseParameters(param, "Parameter")
|
||||
return structs, err
|
||||
}
|
||||
|
||||
// NewStructParameter creates a StructParameter
|
||||
func NewStructParameter() StructParameter {
|
||||
return StructParameter{
|
||||
Parameter: types.Parameter{},
|
||||
GoType: "",
|
||||
Fields: []Field{},
|
||||
}
|
||||
}
|
||||
|
||||
// parseParameters will be called recursively to parse parameters
|
||||
// nolint:staticcheck
|
||||
func parseParameters(paraValue cue.Value, paramKey string) error {
|
||||
param := NewStructParameter()
|
||||
param.Name = paramKey
|
||||
param.Type = paraValue.IncompleteKind()
|
||||
param.Short, param.Usage, param.Alias, param.Ignore = velacue.RetrieveComments(paraValue)
|
||||
if def, ok := paraValue.Default(); ok && def.IsConcrete() {
|
||||
param.Default = velacue.GetDefault(def)
|
||||
}
|
||||
|
||||
// only StructKind will be separated go struct, other will be just a field
|
||||
if param.Type == cue.StructKind {
|
||||
arguments, err := paraValue.Struct()
|
||||
if err != nil {
|
||||
return fmt.Errorf("augument not as struct: %w", err)
|
||||
}
|
||||
if arguments.Len() == 0 { // in cue, empty struct like: foo: map[string]int
|
||||
tl := paraValue.Template()
|
||||
if tl != nil { // map type
|
||||
// TODO: kind maybe not simple type like string/int, if it is a struct, parseParameters should be called
|
||||
kind, err := trimIncompleteKind(tl("").IncompleteKind().String())
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "invalid parameter kind")
|
||||
}
|
||||
param.GoType = fmt.Sprintf("map[string]%s", kind)
|
||||
}
|
||||
}
|
||||
for i := 0; i < arguments.Len(); i++ {
|
||||
var subParam Field
|
||||
fi := arguments.Field(i)
|
||||
if fi.IsDefinition {
|
||||
continue
|
||||
}
|
||||
val := fi.Value
|
||||
name := fi.Name
|
||||
subParam.Name = name
|
||||
subParam.OmitEmpty = fi.IsOptional
|
||||
switch val.IncompleteKind() {
|
||||
case cue.StructKind:
|
||||
if subField, err := val.Struct(); err == nil && subField.Len() == 0 { // err cannot be not nil,so ignore it
|
||||
if mapValue, ok := val.Elem(); ok {
|
||||
// In the future we could recursively call to support complex map-value(struct or list)
|
||||
subParam.GoType = fmt.Sprintf("map[string]%s", mapValue.IncompleteKind().String())
|
||||
} else {
|
||||
// element in struct not defined, use interface{}
|
||||
subParam.GoType = "map[string]interface{}"
|
||||
}
|
||||
} else {
|
||||
if err := parseParameters(val, name); err != nil {
|
||||
return err
|
||||
}
|
||||
subParam.GoType = DefaultNamer.FieldName(name)
|
||||
}
|
||||
case cue.ListKind:
|
||||
elem, success := val.Elem()
|
||||
if !success {
|
||||
// fail to get elements, use the value of ListKind to be the type
|
||||
subParam.GoType = val.IncompleteKind().String()
|
||||
break
|
||||
}
|
||||
switch elem.Kind() {
|
||||
case cue.StructKind:
|
||||
subParam.GoType = fmt.Sprintf("[]%s", DefaultNamer.FieldName(name))
|
||||
if err := parseParameters(elem, name); err != nil {
|
||||
return err
|
||||
}
|
||||
default:
|
||||
subParam.GoType = fmt.Sprintf("[]%s", elem.IncompleteKind().String())
|
||||
}
|
||||
default:
|
||||
subParam.GoType = val.IncompleteKind().String()
|
||||
}
|
||||
param.Fields = append(param.Fields, subParam)
|
||||
}
|
||||
}
|
||||
structs = append(structs, param)
|
||||
return nil
|
||||
}
|
||||
|
||||
// GenGoCodeFromParams generates go code from parameters
|
||||
func GenGoCodeFromParams(parameters []StructParameter) (string, error) {
|
||||
var buf bytes.Buffer
|
||||
|
||||
for _, parameter := range parameters {
|
||||
if parameter.Usage == "" {
|
||||
parameter.Usage = "-"
|
||||
}
|
||||
fmt.Fprintf(&buf, "// %s %s\n", DefaultNamer.FieldName(parameter.Name), parameter.Usage)
|
||||
genField(parameter, &buf)
|
||||
}
|
||||
source, err := format.Source(buf.Bytes())
|
||||
if err != nil {
|
||||
fmt.Println("Failed to format source:", err)
|
||||
}
|
||||
|
||||
return string(source), nil
|
||||
}
|
||||
|
||||
// PrintParamGosStruct prints the StructParameter in Golang struct format
|
||||
func PrintParamGosStruct(parameters []StructParameter) {
|
||||
code, err := GenGoCodeFromParams(parameters)
|
||||
if err != nil {
|
||||
fmt.Println("Fail to gen code, err:", err)
|
||||
}
|
||||
fmt.Print(code)
|
||||
}
|
||||
|
||||
func genField(param StructParameter, buffer *bytes.Buffer) {
|
||||
fieldName := DefaultNamer.FieldName(param.Name)
|
||||
if param.Type == cue.StructKind { // only struct kind will be separated struct
|
||||
// cue struct can be Go map or struct
|
||||
if strings.HasPrefix(param.GoType, "map[string]") {
|
||||
fmt.Fprintf(buffer, "type %s %s", fieldName, param.GoType)
|
||||
} else {
|
||||
fmt.Fprintf(buffer, "type %s struct {\n", fieldName)
|
||||
for _, f := range param.Fields {
|
||||
jsonTag := f.Name
|
||||
if f.OmitEmpty {
|
||||
jsonTag = fmt.Sprintf("%s,omitempty", jsonTag)
|
||||
}
|
||||
fmt.Fprintf(buffer, " %s %s `json:\"%s\"`\n", DefaultNamer.FieldName(f.Name), f.GoType, jsonTag)
|
||||
}
|
||||
|
||||
fmt.Fprintf(buffer, "}\n")
|
||||
}
|
||||
} else {
|
||||
fmt.Fprintf(buffer, "type %s %s\n", fieldName, param.GoType)
|
||||
}
|
||||
}
|
||||
|
||||
// trimIncompleteKind allows 2 types of incomplete kind, return the non-null one, more than two types of incomplete kind will return error
|
||||
// 1. (null|someKind)
|
||||
// 2. someKind
|
||||
func trimIncompleteKind(mask string) (string, error) {
|
||||
mask = strings.Trim(mask, "()")
|
||||
ks := strings.Split(mask, "|")
|
||||
if len(ks) == 1 {
|
||||
return ks[0], nil
|
||||
}
|
||||
if len(ks) == 2 && ks[0] == "null" {
|
||||
return ks[1], nil
|
||||
}
|
||||
return "", fmt.Errorf("invalid incomplete kind: %s", mask)
|
||||
|
||||
}
|
||||
|
||||
// An AbbrFieldNamer generates Go field names from Go
|
||||
// struct field while keeping abbreviations uppercased.
|
||||
type AbbrFieldNamer struct {
|
||||
// Prefix is a prefix to add to all field names with first char capitalized automatically.
|
||||
Prefix string
|
||||
prefixWithFirstCharCapitalized string
|
||||
Abbreviations map[string]bool
|
||||
}
|
||||
|
||||
// SetPrefix set a prefix to namer.
|
||||
func (a *AbbrFieldNamer) SetPrefix(s string) {
|
||||
a.Prefix = s
|
||||
}
|
||||
|
||||
// FieldName implements FieldNamer.FieldName.
|
||||
func (a *AbbrFieldNamer) FieldName(field string) string {
|
||||
if a.prefixWithFirstCharCapitalized == "" && a.Prefix != "" {
|
||||
a.prefixWithFirstCharCapitalized = strings.ToUpper(a.Prefix[:1]) + a.Prefix[1:]
|
||||
}
|
||||
components := SplitComponents(field)
|
||||
for i, component := range components {
|
||||
switch {
|
||||
case component == "":
|
||||
// do nothing
|
||||
case a.Abbreviations[strings.ToUpper(component)]:
|
||||
components[i] = strings.ToUpper(component)
|
||||
case component == strings.ToUpper(component):
|
||||
runes := []rune(component)
|
||||
components[i] = string(runes[0]) + strings.ToLower(string(runes[1:]))
|
||||
default:
|
||||
runes := []rune(component)
|
||||
runes[0] = unicode.ToUpper(runes[0])
|
||||
components[i] = string(runes)
|
||||
}
|
||||
}
|
||||
runes := []rune(strings.Join(components, ""))
|
||||
for i, r := range runes {
|
||||
if !unicode.IsLetter(r) && !unicode.IsDigit(r) && r != '_' {
|
||||
runes[i] = '_'
|
||||
}
|
||||
}
|
||||
fieldName := string(runes)
|
||||
if !unicode.IsLetter(runes[0]) && runes[0] != '_' {
|
||||
fieldName = "_" + fieldName
|
||||
}
|
||||
if a.prefixWithFirstCharCapitalized != "" {
|
||||
fieldName = a.prefixWithFirstCharCapitalized + fieldName
|
||||
}
|
||||
return fieldName
|
||||
}
|
||||
|
||||
// SplitComponents splits name into components. name may be kebab case, snake
|
||||
// case, or camel case.
|
||||
func SplitComponents(name string) []string {
|
||||
switch {
|
||||
case strings.ContainsRune(name, '-'):
|
||||
return strings.Split(name, "-")
|
||||
case strings.ContainsRune(name, '_'):
|
||||
return strings.Split(name, "_")
|
||||
default:
|
||||
return camelcase.Split(name)
|
||||
}
|
||||
}
|
|
@ -1,364 +0,0 @@
|
|||
/*
|
||||
Copyright 2021 The KubeVela Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package definition
|
||||
|
||||
import (
|
||||
"os"
|
||||
"path/filepath"
|
||||
"testing"
|
||||
|
||||
"cuelang.org/go/cue"
|
||||
"github.com/stretchr/testify/assert"
|
||||
assert2 "gotest.tools/assert"
|
||||
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
|
||||
|
||||
"github.com/oam-dev/kubevela/apis/types"
|
||||
"github.com/oam-dev/kubevela/pkg/stdlib"
|
||||
"github.com/oam-dev/kubevela/pkg/utils/common"
|
||||
)
|
||||
|
||||
func TestDefaultFieldNamer(t *testing.T) {
|
||||
for name, expected := range map[string]string{
|
||||
"id": "ID",
|
||||
"foo": "Foo",
|
||||
"foo_bar": "FooBar",
|
||||
"fooBar": "FooBar",
|
||||
"FOO_BAR": "FooBar",
|
||||
"FOO_BAR_ID": "FooBarID",
|
||||
"123": "_123",
|
||||
"A|B": "A_B",
|
||||
} {
|
||||
assert.Equal(t, expected, DefaultNamer.FieldName(name))
|
||||
}
|
||||
// test add prefix to name
|
||||
namer := NewFieldNamer("prefix")
|
||||
for name, expected := range map[string]string{
|
||||
"id": "PrefixID",
|
||||
"foo": "PrefixFoo",
|
||||
"foo_bar": "PrefixFooBar",
|
||||
"fooBar": "PrefixFooBar",
|
||||
"FOO_BAR": "PrefixFooBar",
|
||||
"FOO_BAR_ID": "PrefixFooBarID",
|
||||
"123": "Prefix_123",
|
||||
"A|B": "PrefixA_B",
|
||||
} {
|
||||
assert.Equal(t, expected, namer.FieldName(name))
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func TestTrimIncompleteKind(t *testing.T) {
|
||||
incompleteKinds := []struct {
|
||||
kind string
|
||||
expected string
|
||||
err bool
|
||||
}{
|
||||
{
|
||||
kind: "string",
|
||||
expected: "string",
|
||||
err: false,
|
||||
},
|
||||
{
|
||||
kind: "(null|string)",
|
||||
expected: "string",
|
||||
err: false,
|
||||
},
|
||||
{
|
||||
kind: "(null|string|int)",
|
||||
err: true,
|
||||
},
|
||||
}
|
||||
for _, k := range incompleteKinds {
|
||||
actual, err := trimIncompleteKind(k.kind)
|
||||
if k.err {
|
||||
assert.Error(t, err)
|
||||
} else {
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, k.expected, actual)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestGeneratorParameterStructs(t *testing.T) {
|
||||
testCases := []struct {
|
||||
name string
|
||||
cue string
|
||||
expected []StructParameter
|
||||
err bool
|
||||
}{
|
||||
{
|
||||
name: "go struct",
|
||||
cue: defWithStruct,
|
||||
err: false,
|
||||
expected: structsWithStruct,
|
||||
},
|
||||
{
|
||||
name: "go list",
|
||||
cue: defWithList,
|
||||
err: false,
|
||||
expected: structsWithList,
|
||||
},
|
||||
{
|
||||
name: "go struct list",
|
||||
cue: defWithStructList,
|
||||
err: false,
|
||||
expected: structsWithStructList,
|
||||
},
|
||||
{
|
||||
name: "go map",
|
||||
cue: defWithMap,
|
||||
err: false,
|
||||
expected: structsWithMap,
|
||||
},
|
||||
{
|
||||
name: "map element not defined",
|
||||
cue: defWithEmptyMap,
|
||||
err: false,
|
||||
expected: structWithInterface,
|
||||
},
|
||||
{
|
||||
name: "omitempty",
|
||||
cue: defWithOptional,
|
||||
err: false,
|
||||
expected: structWithOptional,
|
||||
},
|
||||
}
|
||||
for _, tc := range testCases {
|
||||
value, err := common.GetCUEParameterValue(tc.cue, nil)
|
||||
assert.NoError(t, err)
|
||||
actual, err := GeneratorParameterStructs(value)
|
||||
if tc.err {
|
||||
assert.Error(t, err)
|
||||
} else {
|
||||
assert.NoError(t, err)
|
||||
assert2.DeepEqual(t, tc.expected, actual)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestGenGoCodeFromParams(t *testing.T) {
|
||||
testCases := []struct {
|
||||
structs []StructParameter
|
||||
result string
|
||||
}{
|
||||
{structs: structsWithStruct, result: resultWithStruct},
|
||||
{structs: structsWithList, result: resultWithList},
|
||||
{structs: structsWithStructList, result: resultWithStructList},
|
||||
{structs: structsWithMap, result: resultWithMap},
|
||||
{structs: structWithInterface, result: resultWithInterface},
|
||||
{structs: structWithOptional, result: resultWithOptional},
|
||||
}
|
||||
for _, tc := range testCases {
|
||||
actual, err := GenGoCodeFromParams(tc.structs)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
assert.Equal(t, tc.result, actual)
|
||||
}
|
||||
}
|
||||
|
||||
var (
|
||||
defWithStruct = `
|
||||
parameter: {
|
||||
// +usage=Specify the mapping relationship between the http path and the workload port
|
||||
http: {
|
||||
path: int
|
||||
}
|
||||
}`
|
||||
defWithList = `
|
||||
parameter: {
|
||||
http: [string]: int
|
||||
}`
|
||||
defWithStructList = `
|
||||
parameter: {
|
||||
emptyDir: [...{
|
||||
name: string
|
||||
mountPath: string
|
||||
medium: *"" | "Memory"
|
||||
}]
|
||||
}`
|
||||
defWithMap = `parameter: [string]: string | null`
|
||||
defWithEmptyMap = `
|
||||
parameter: {
|
||||
data: {}
|
||||
}`
|
||||
defWithOptional = `
|
||||
parameter:{
|
||||
data?: int
|
||||
}`
|
||||
|
||||
structsWithStruct = []StructParameter{
|
||||
{
|
||||
Parameter: types.Parameter{
|
||||
Name: "http",
|
||||
Type: cue.StructKind,
|
||||
Usage: "Specify the mapping relationship between the http path and the workload port",
|
||||
},
|
||||
Fields: []Field{
|
||||
{
|
||||
Name: "path",
|
||||
GoType: "int",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
Parameter: types.Parameter{
|
||||
Type: cue.StructKind,
|
||||
Name: "Parameter",
|
||||
},
|
||||
Fields: []Field{
|
||||
{
|
||||
Name: "http",
|
||||
GoType: "HTTP",
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
structsWithList = []StructParameter{
|
||||
{
|
||||
Parameter: types.Parameter{
|
||||
Type: cue.StructKind,
|
||||
Name: "Parameter",
|
||||
},
|
||||
Fields: []Field{
|
||||
{
|
||||
Name: "http",
|
||||
GoType: "map[string]int",
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
structsWithStructList = []StructParameter{
|
||||
{
|
||||
Parameter: types.Parameter{
|
||||
Type: cue.StructKind,
|
||||
Name: "emptyDir",
|
||||
},
|
||||
Fields: []Field{
|
||||
{Name: "name", GoType: "string"},
|
||||
{Name: "mountPath", GoType: "string"},
|
||||
{Name: "medium", GoType: "string"},
|
||||
},
|
||||
},
|
||||
{
|
||||
Parameter: types.Parameter{
|
||||
Type: cue.StructKind,
|
||||
Name: "Parameter",
|
||||
},
|
||||
Fields: []Field{
|
||||
{
|
||||
Name: "emptyDir",
|
||||
GoType: "[]EmptyDir",
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
structsWithMap = []StructParameter{
|
||||
{
|
||||
Parameter: types.Parameter{
|
||||
Type: cue.StructKind,
|
||||
Name: "Parameter",
|
||||
},
|
||||
GoType: "map[string]string",
|
||||
Fields: []Field{},
|
||||
},
|
||||
}
|
||||
structWithInterface = []StructParameter{
|
||||
{
|
||||
Parameter: types.Parameter{
|
||||
Type: cue.StructKind,
|
||||
Name: "Parameter",
|
||||
},
|
||||
GoType: "",
|
||||
Fields: []Field{
|
||||
{Name: "data", GoType: "map[string]interface{}"},
|
||||
},
|
||||
},
|
||||
}
|
||||
structWithOptional = []StructParameter{
|
||||
{
|
||||
Parameter: types.Parameter{
|
||||
Type: cue.StructKind,
|
||||
Name: "Parameter",
|
||||
},
|
||||
GoType: "",
|
||||
Fields: []Field{
|
||||
{Name: "data", GoType: "int", OmitEmpty: true},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
resultWithStruct = "// HTTP Specify the mapping relationship between the http path and the workload port\ntype HTTP struct {\n\tPath int `json:\"path\"`\n}\n\n// Parameter -\ntype Parameter struct {\n\tHTTP HTTP `json:\"http\"`\n}\n"
|
||||
resultWithList = "// Parameter -\ntype Parameter struct {\n\tHTTP map[string]int `json:\"http\"`\n}\n"
|
||||
resultWithStructList = "// EmptyDir -\ntype EmptyDir struct {\n\tName string `json:\"name\"`\n\tMountPath string `json:\"mountPath\"`\n\tMedium string `json:\"medium\"`\n}\n\n// Parameter -\ntype Parameter struct {\n\tEmptyDir []EmptyDir `json:\"emptyDir\"`\n}\n"
|
||||
resultWithMap = "// Parameter -\ntype Parameter map[string]string"
|
||||
resultWithInterface = "// Parameter -\ntype Parameter struct {\n\tData map[string]interface{} `json:\"data\"`\n}\n"
|
||||
resultWithOptional = "// Parameter -\ntype Parameter struct {\n\tData int `json:\"data,omitempty\"`\n}\n"
|
||||
)
|
||||
|
||||
func TestGenAllDef(t *testing.T) {
|
||||
err := stdlib.SetupBuiltinImports()
|
||||
assert.NoError(t, err)
|
||||
skipDefs := []string{
|
||||
// non-concrete structs like
|
||||
// foo: string|{secretRef: string}
|
||||
"container-image.cue",
|
||||
"export2config.cue",
|
||||
"webhook.cue",
|
||||
"notification.cue",
|
||||
"env.cue",
|
||||
"command.cue",
|
||||
"apply-terraform-config.cue",
|
||||
|
||||
// not supported
|
||||
"json-merge-patch.cue",
|
||||
"json-patch.cue",
|
||||
|
||||
// no args
|
||||
"apply-application.cue",
|
||||
"apply-application-in-parallel.cue",
|
||||
"step-group.cue",
|
||||
}
|
||||
glob, err := filepath.Glob("../../vela-templates/definitions/*/*/*.cue")
|
||||
assert.NoError(t, err)
|
||||
for _, f := range glob {
|
||||
if !stringInSlice(filepath.Base(f), skipDefs) {
|
||||
t.Run(filepath.Base(f), func(t *testing.T) {
|
||||
file, err := os.ReadFile(f)
|
||||
assert.NoError(t, err)
|
||||
def := Definition{Unstructured: unstructured.Unstructured{}}
|
||||
err = def.FromCUEString(string(file), nil)
|
||||
assert.NoError(t, err)
|
||||
templateString, _, err := unstructured.NestedString(def.Object, DefinitionTemplateKeys...)
|
||||
assert.NoError(t, err)
|
||||
value, err := common.GetCUEParameterValue(templateString, nil)
|
||||
assert.NoError(t, err)
|
||||
_, err = GeneratorParameterStructs(value)
|
||||
assert.NoError(t, err)
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func stringInSlice(a string, list []string) bool {
|
||||
for _, b := range list {
|
||||
if b == a {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
|
@ -116,6 +116,7 @@ func TestAddonEnableCmdWithErrLocalPath(t *testing.T) {
|
|||
ioStream := util.IOStreams{}
|
||||
commandArgs := common.Args{}
|
||||
cmd := NewAddonEnableCommand(commandArgs, ioStream)
|
||||
initCommand(cmd)
|
||||
|
||||
for _, s := range testcase {
|
||||
cmd.SetArgs(s.args)
|
||||
|
@ -180,6 +181,7 @@ func TestAddonUpgradeCmdWithErrLocalPath(t *testing.T) {
|
|||
ioStream := util.IOStreams{}
|
||||
commandArgs := common.Args{}
|
||||
cmd := NewAddonUpgradeCommand(commandArgs, ioStream)
|
||||
initCommand(cmd)
|
||||
|
||||
for _, s := range testcase {
|
||||
cmd.SetArgs(s.args)
|
||||
|
|
|
@ -46,13 +46,13 @@ import (
|
|||
"sigs.k8s.io/controller-runtime/pkg/client"
|
||||
|
||||
"github.com/kubevela/workflow/pkg/cue/model/sets"
|
||||
"github.com/kubevela/workflow/pkg/cue/packages"
|
||||
|
||||
commontype "github.com/oam-dev/kubevela/apis/core.oam.dev/common"
|
||||
"github.com/oam-dev/kubevela/apis/core.oam.dev/v1beta1"
|
||||
"github.com/oam-dev/kubevela/apis/types"
|
||||
"github.com/oam-dev/kubevela/pkg/cue/process"
|
||||
pkgdef "github.com/oam-dev/kubevela/pkg/definition"
|
||||
"github.com/oam-dev/kubevela/pkg/definition/gen_sdk"
|
||||
"github.com/oam-dev/kubevela/pkg/utils"
|
||||
addonutil "github.com/oam-dev/kubevela/pkg/utils/addon"
|
||||
"github.com/oam-dev/kubevela/pkg/utils/common"
|
||||
|
@ -1064,61 +1064,46 @@ func NewDefinitionValidateCommand(c common.Args) *cobra.Command {
|
|||
|
||||
// NewDefinitionGenAPICommand create the `vela def gen-api` command to help user generate Go code from the definition
|
||||
func NewDefinitionGenAPICommand(c common.Args) *cobra.Command {
|
||||
var (
|
||||
skipPackageName bool
|
||||
packageName string
|
||||
prefix string
|
||||
)
|
||||
meta := gen_sdk.GenMeta{}
|
||||
|
||||
cmd := &cobra.Command{
|
||||
Use: "gen-api DEFINITION.cue",
|
||||
Short: "Generate Go struct of Parameter from X-Definition.",
|
||||
Long: "Generate Go struct of Parameter from definition file.\n" +
|
||||
Short: "Generate SDK from X-Definition.",
|
||||
Long: "Generate SDK from X-definition file.\n" +
|
||||
"* This command leverage openapi-generator project. Therefore demands \"docker\" exist in PATH" +
|
||||
"* Currently, this function is still working in progress and not all formats of parameter in X-definition are supported yet.",
|
||||
Example: "# Command below will generate the Go struct for the my-def.cue file.\n" +
|
||||
"> vela def gen-api my-def.cue",
|
||||
Args: cobra.ExactArgs(1),
|
||||
Example: "# Generate SDK for golang with scaffold initialized\n" +
|
||||
"> vela def gen-api --init --lang go -f /path/to/def -o /path/to/sdk\n" +
|
||||
"# Generate incremental definition files to existing sdk directory\n" +
|
||||
"> vela def gen-api --lang go -f /path/to/def -o /path/to/sdk",
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
cueBytes, err := os.ReadFile(args[0])
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, "failed to read %s", args[0])
|
||||
}
|
||||
def := pkgdef.Definition{Unstructured: unstructured.Unstructured{}}
|
||||
config, err := c.GetConfig()
|
||||
err := meta.Init(c)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err := def.FromCUEString(string(cueBytes), config); err != nil {
|
||||
return errors.Wrapf(err, "failed to parse CUE")
|
||||
}
|
||||
templateString, _, err := unstructured.NestedString(def.Object, pkgdef.DefinitionTemplateKeys...)
|
||||
err = meta.CreateScaffold()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
pd, err := packages.NewPackageDiscover(config)
|
||||
err = meta.PrepareGeneratorAndTemplate()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
value, err := common.GetCUEParameterValue(templateString, pd)
|
||||
err = meta.Run()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
pkgdef.DefaultNamer.SetPrefix(prefix)
|
||||
structs, err := pkgdef.GeneratorParameterStructs(value)
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, "failed to generate Go code")
|
||||
}
|
||||
|
||||
if !skipPackageName {
|
||||
fmt.Printf("package %s\n\n", packageName)
|
||||
}
|
||||
pkgdef.PrintParamGosStruct(structs)
|
||||
return nil
|
||||
},
|
||||
}
|
||||
cmd.Flags().BoolVar(&skipPackageName, "skip-package-name", false, "Skip package name in generated Go code.")
|
||||
cmd.Flags().StringVar(&packageName, "package-name", "main", "Specify the package name in generated Go code.")
|
||||
cmd.Flags().StringVar(&prefix, "prefix", "", "Specify the prefix of the generated Go struct.")
|
||||
cmd.Flags().StringVarP(&meta.Output, "output", "o", "./apis", "Output directory path")
|
||||
cmd.Flags().StringVarP(&meta.Package, "package", "p", "github.com/kubevela/vela-go-sdk", "Package name of generated code")
|
||||
cmd.Flags().StringVarP(&meta.Lang, "lang", "g", "go", "Language to generate code. Valid languages: go")
|
||||
cmd.Flags().StringVarP(&meta.Template, "template", "t", "", "Template file path, if not specified, the default template will be used")
|
||||
cmd.Flags().StringSliceVarP(&meta.File, "file", "f", nil, "File name of definitions, can be specified multiple times, or use comma to separate multiple files. If directory specified, all files found recursively in the directory will be used")
|
||||
cmd.Flags().BoolVar(&meta.InitSDK, "init", false, "Init the whole SDK project, if not set, only the API file will be generated")
|
||||
cmd.Flags().BoolVarP(&meta.Verbose, "verbose", "v", false, "Print verbose logs")
|
||||
|
||||
return cmd
|
||||
}
|
||||
|
|
|
@ -629,3 +629,15 @@ func TestNewDefinitionVetCommand(t *testing.T) {
|
|||
t.Fatalf("expect validation failed but error not found")
|
||||
}
|
||||
}
|
||||
|
||||
func TestNewDefinitionGenAPICommand(t *testing.T) {
|
||||
c := initArgs()
|
||||
cmd := NewDefinitionGenAPICommand(c)
|
||||
initCommand(cmd)
|
||||
internalDefPath := "../../vela-templates/definitions/internal/"
|
||||
|
||||
cmd.SetArgs([]string{"-f", internalDefPath, "-o", "../vela-sdk-gen", "--init", "--verbose"})
|
||||
if err := cmd.Execute(); err != nil {
|
||||
t.Fatalf("unexpeced error when executing genapi command: %v", err)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -11,7 +11,7 @@
|
|||
delArgs: *null | [...string]
|
||||
}
|
||||
|
||||
parameter: *#PatchParams | close({
|
||||
parameter: #PatchParams | close({
|
||||
// +usage=Specify the commands for multiple containers
|
||||
containers: [...#PatchParams]
|
||||
})
|
||||
|
|
|
@ -8,7 +8,7 @@
|
|||
// +usage=Specify which existing environment variables to unset
|
||||
unset: *[] | [...string]
|
||||
}
|
||||
parameter: *#PatchParams | close({
|
||||
parameter: #PatchParams | close({
|
||||
// +usage=Specify the environment variables for multiple containers
|
||||
containers: [...#PatchParams]
|
||||
})
|
||||
|
|
Loading…
Reference in New Issue