Feat: SDK generating framework (#5431)

This commit is contained in:
qiaozp 2023-02-21 15:54:44 +08:00 committed by GitHub
parent b1fc313519
commit 8ff77c4486
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
46 changed files with 6701 additions and 728 deletions

View File

@ -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

View File

@ -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.
![image](../resources/separation-of-roles.png)
### 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.
![image](../resources/sdk-gen-process.png)
**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.

View File

@ -0,0 +1,129 @@
# SDK 生成
# 介绍
该提案引入了一个框架,该框架可以从脚手架以及 X-Definitions 生成 KubeVela SDK.
# 背景
由于 KubeVela 设计了关注点分离因此有两个用户角色平台团队和最终用户。他们以不同的方式处理Application。
![image](../resources/separation-of-roles.png)
### 对于平台团队
当平台团队用户组装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)
,我们可以重复使用几乎准备好的模板来生成代码。整个生成过程如下所示。
![image](../resources/sdk-gen-process.png)
**第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
View File

@ -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
View File

@ -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=

30
hack/sdk/reviewable.sh Executable file
View File

@ -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

View File

@ -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}

View File

@ -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

View File

@ -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.

View File

@ -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

View File

@ -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)
}

View File

@ -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
}

View File

@ -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
}

View File

@ -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)
}

View File

@ -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"
)

View File

@ -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)
}
}

View File

@ -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")
}

View File

@ -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)
})
})

View File

@ -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
}

View File

@ -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}}

View File

@ -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}}

View File

@ -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}}

View File

@ -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)
}

View File

@ -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}}

View File

@ -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}}

View File

@ -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)
}

View File

@ -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.

View File

@ -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
}

View File

@ -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
}

View File

@ -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
}
}

View File

@ -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
}
}

View File

@ -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
}

View File

@ -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
}
})
}
}
}

View File

@ -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]
}
}

View File

@ -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: {}
}

View File

@ -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)
}
}

View File

@ -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
}

View File

@ -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)

View File

@ -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
}

View File

@ -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)
}
}

View File

@ -11,7 +11,7 @@
delArgs: *null | [...string]
}
parameter: *#PatchParams | close({
parameter: #PatchParams | close({
// +usage=Specify the commands for multiple containers
containers: [...#PatchParams]
})

View File

@ -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]
})