Compare commits

..

36 Commits

Author SHA1 Message Date
github-actions[bot]
1ee5915546 Fix: the developer user can't load the definition (#5319)
Signed-off-by: barnettZQG <barnett.zqg@gmail.com>
(cherry picked from commit 78c9a1a370)

Co-authored-by: barnettZQG <barnett.zqg@gmail.com>
2023-01-11 18:28:36 +08:00
github-actions[bot]
3f6b38cc7f small optimzie for addon (#5318)
Signed-off-by: 楚岳 <wangyike.wyk@alibaba-inc.com>

name the compoennt

Signed-off-by: 楚岳 <wangyike.wyk@alibaba-inc.com>
(cherry picked from commit 3a020041d9)

Co-authored-by: 楚岳 <wangyike.wyk@alibaba-inc.com>
2023-01-11 16:33:16 +08:00
Jianbo Sun
c2c7ab91f9 Feat: vela dry-run render results should be affected by override policy and deploy workflowstep (#4815) (#5314)
* [Feature] vela dry-run render results should be affected by override policy and deploy workflowstep

Signed-off-by: cezhang <c1zhang.dev@gmail.com>

* multiple input files support; policy,workflow support; new flag: merge orphan policy or workflow

Signed-off-by: cezhang <c1zhang.dev@gmail.com>

* add more tests

Signed-off-by: cezhang <c1zhang.dev@gmail.com>

* fix comment issues

Signed-off-by: cezhang <c1zhang.dev@gmail.com>

* add tests

Signed-off-by: cezhang <c1zhang.dev@gmail.com>

* fix e2e

Signed-off-by: cezhang <c1zhang.dev@gmail.com>

* fix tests

Signed-off-by: cezhang <c1zhang.dev@gmail.com>

Signed-off-by: cezhang <c1zhang.dev@gmail.com>

Signed-off-by: cezhang <c1zhang.dev@gmail.com>
Co-authored-by: cezhang <c1zhang.dev@gmail.com>
2023-01-11 15:39:45 +08:00
github-actions[bot]
866ffb9689 Chore: vela delete doc (#5315)
Signed-off-by: Somefive <yd219913@alibaba-inc.com>
(cherry picked from commit ce79ab1691)

Co-authored-by: Somefive <yd219913@alibaba-inc.com>
2023-01-11 15:08:08 +08:00
github-actions[bot]
61afb366ee Fix: fix vela debug cli to find id for step (#5313)
Signed-off-by: FogDong <dongtianxin.tx@alibaba-inc.com>
(cherry picked from commit 2f77353cbe)

Co-authored-by: FogDong <dongtianxin.tx@alibaba-inc.com>
2023-01-11 14:57:02 +08:00
github-actions[bot]
a2a8d73a58 Fix: don't return err if subresource type is not found when listing application resources (#5312)
Signed-off-by: hnd4r7 <307365651@qq.com>
(cherry picked from commit 58a150d1d4)

Co-authored-by: hnd4r7 <307365651@qq.com>
2023-01-11 14:56:22 +08:00
github-actions[bot]
fc3b428788 fix: errorMsg when uninstall vela (#5310)
Signed-off-by: bitliu <bitliu@tencent.com>
(cherry picked from commit 771e0b5429)

Co-authored-by: bitliu <bitliu@tencent.com>
2023-01-11 14:49:14 +08:00
github-actions[bot]
500dc52b34 [Backport release-1.7] Fix: create a config with the same name reported an incorrect error (#5307)
* Fix: create a config with the same name reported an incorrect error

Signed-off-by: wuzhongjian <wuzhongjian_yewu@cmss.chinamobile.com>
(cherry picked from commit 3c1759896b)

* Fix: create a config with the same name reported an incorrect error

Signed-off-by: wuzhongjian <wuzhongjian_yewu@cmss.chinamobile.com>
(cherry picked from commit 0aa456642b)

Co-authored-by: wuzhongjian <wuzhongjian_yewu@cmss.chinamobile.com>
2023-01-11 14:25:13 +08:00
github-actions[bot]
789aa38476 [Backport release-1.7] Feat: enhance the application synchronizer (#5305)
* Feat: enhance the application synchronizer

Signed-off-by: barnettZQG <barnett.zqg@gmail.com>
(cherry picked from commit e5d112b04a)

* Fix: e2e test case

Signed-off-by: barnettZQG <barnett.zqg@gmail.com>
(cherry picked from commit 940ffb30f5)

* Fix: the unit test case

Signed-off-by: barnettZQG <barnett.zqg@gmail.com>
(cherry picked from commit c3e896fbc1)

Co-authored-by: barnettZQG <barnett.zqg@gmail.com>
2023-01-11 14:24:25 +08:00
github-actions[bot]
17adf35717 Fix: Index structure map[string]string,Mongo resulting in inconsistent results obtained by filtering non-string type by index. (#5303)
Signed-off-by: old.prince <di7zhang@gmail.com>
(cherry picked from commit a763d3da54)

Co-authored-by: old.prince <di7zhang@gmail.com>
2023-01-11 13:06:15 +08:00
github-actions[bot]
586f266798 [Backport release-1.7] Fix: more explicit error when addon package hasn't a metadata.yaml (#5302)
* more explicit error when addon package hasn't a metadata.yaml

Signed-off-by: 楚岳 <wangyike.wyk@alibaba-inc.com>

fix checkdiff

Signed-off-by: 楚岳 <wangyike.wyk@alibaba-inc.com>
(cherry picked from commit 6953fe872d)

* fix commets

Signed-off-by: 楚岳 <wangyike.wyk@alibaba-inc.com>
(cherry picked from commit 7cacb50976)

* fix test

Signed-off-by: 楚岳 <wangyike.wyk@alibaba-inc.com>
(cherry picked from commit 4d35cd58f9)

Co-authored-by: 楚岳 <wangyike.wyk@alibaba-inc.com>
2023-01-11 11:53:00 +08:00
github-actions[bot]
b8dcbe4964 Fix: Delete appplication fails if status.workflow.endTime not specified. (#5297)
Error Details:
E0106 08:12:02.807341       1 controller.go:317] controller/application "msg"="Reconciler error" "error"="Application.core.oam.dev \"test\" is invalid: status.workflow.endTime: Invalid value: \"null\": status.workflow.endTime in body must be of type string: \"null\"" "name"="test" "namespace"="test" "reconciler group"="core.oam.dev" "reconciler kind"="Application"

If the workflow is not completed, the endtime should be null, and the deletion of the application will fail

Signed-off-by: old.prince <di7zhang@gmail.com>
(cherry picked from commit 16b6a02018)

Co-authored-by: old.prince <di7zhang@gmail.com>
2023-01-10 15:16:26 +08:00
github-actions[bot]
778579c79b Test: let addon helper tests use local helm server (#5290)
Signed-off-by: Charlie Chiang <charlie_c_0129@outlook.com>
(cherry picked from commit 44eaa1d004)

Co-authored-by: Charlie Chiang <charlie_c_0129@outlook.com>
2023-01-09 13:30:04 +08:00
Somefive
69293f4094 Feat: upgrade cluster-gateway to support client-identity-exchange config (#5284)
Signed-off-by: Somefive <yd219913@alibaba-inc.com>

Signed-off-by: Somefive <yd219913@alibaba-inc.com>
2023-01-06 16:37:57 +08:00
barnettZQG
3a917cb6af Fix: the addon management APIs support the user impersonation (#5282)
* Fix: the addon management APIs support the user impersonation

Signed-off-by: barnettZQG <barnett.zqg@gmail.com>

* Fix: change the test case

Signed-off-by: barnettZQG <barnett.zqg@gmail.com>

Signed-off-by: barnettZQG <barnett.zqg@gmail.com>
2023-01-06 15:11:56 +08:00
barnettZQG
1c43c6d1c5 Fix: keep the workflow data structure in MongoDB (#5276)
Signed-off-by: barnettZQG <barnett.zqg@gmail.com>

Signed-off-by: barnettZQG <barnett.zqg@gmail.com>
2023-01-06 15:11:42 +08:00
barnettZQG
9bbf7bf01b Fix: make the synced workflow name normative (#5278)
* Fix: make the synced workflow name normative

Signed-off-by: barnettZQG <barnett.zqg@gmail.com>

* Fix: query the latest workflow

Signed-off-by: barnettZQG <barnett.zqg@gmail.com>

Signed-off-by: barnettZQG <barnett.zqg@gmail.com>
2023-01-06 15:11:29 +08:00
Somefive
5a845104fb Feat: support interactive mode to manually skip encountered errors (#5266) 2023-01-06 15:03:47 +08:00
Somefive
693eb3cb1d Feat: add pre-dispatch dryrun check (#5277)
Signed-off-by: Somefive <yd219913@alibaba-inc.com>

Signed-off-by: Somefive <yd219913@alibaba-inc.com>
2023-01-06 10:46:50 +08:00
Jianbo Sun
78f5827fa6 Fix: move notes to the right to avoid package head to be invalid format (#5280)
Signed-off-by: Jianbo Sun <jianbo.sjb@alibaba-inc.com>

Signed-off-by: Jianbo Sun <jianbo.sjb@alibaba-inc.com>
2023-01-06 10:29:34 +08:00
Tianxin Dong
af8a7eb695 Fix: check the legacy definitions in vela install (#5268)
* Fix: check the legacy definitions in vela install

Signed-off-by: FogDong <dongtianxin.tx@alibaba-inc.com>

* take over views

Signed-off-by: FogDong <dongtianxin.tx@alibaba-inc.com>
2023-01-05 19:13:19 +08:00
barnettZQG
97ce8ba500 Feat: add the API that rollbacks the application (#5273)
* Feat: add the API that rollbacks the application

Signed-off-by: barnettZQG <barnett.zqg@gmail.com>

* Fix: enhance the test cases

Signed-off-by: barnettZQG <barnett.zqg@gmail.com>

* Fix: use the klog/v2 package

Signed-off-by: barnettZQG <barnett.zqg@gmail.com>

Signed-off-by: barnettZQG <barnett.zqg@gmail.com>
2023-01-05 17:45:42 +08:00
qiaozp
03d892bcf1 Fix: apiserver k8sclient have duplicated multicluster wrapper (#5275)
Signed-off-by: Qiaozp <qiaozhongpei.qzp@alibaba-inc.com>

Signed-off-by: Qiaozp <qiaozhongpei.qzp@alibaba-inc.com>
2023-01-05 15:42:38 +08:00
Tianxin Dong
360c9e24b2 Feat: support hostpath in storage and vela cli (#5265)
Signed-off-by: FogDong <dongtianxin.tx@alibaba-inc.com>

Signed-off-by: FogDong <dongtianxin.tx@alibaba-inc.com>
2023-01-05 10:08:54 +08:00
barnettZQG
ba0a726cfc Fix: make the impersonation feature work (#5261)
* Fix: make the impersonation feature work

Signed-off-by: barnettZQG <barnett.zqg@gmail.com>

* Fix: update the document of the chart

Signed-off-by: barnettZQG <barnett.zqg@gmail.com>

Signed-off-by: barnettZQG <barnett.zqg@gmail.com>
2023-01-04 16:58:03 +08:00
aimuz
b2f4e237c2 docs: update bot.md link fail (#5262)
Signed-off-by: aimuz <mr.imuz@gmail.com>

Signed-off-by: aimuz <mr.imuz@gmail.com>
2023-01-04 11:52:29 +08:00
Tianxin Dong
5f71d05db1 Fix: fix inputs conflict for workflow (#5251)
Signed-off-by: FogDong <dongtianxin.tx@alibaba-inc.com>

Signed-off-by: FogDong <dongtianxin.tx@alibaba-inc.com>
2023-01-04 10:45:51 +08:00
barnettZQG
340059989b Feat: enhance the workflow restful APIs (#5252)
* Feat: enhange the workflow restful APIs

Signed-off-by: barnettZQG <barnett.zqg@gmail.com>

* Fix: change the test case

Signed-off-by: barnettZQG <barnett.zqg@gmail.com>

* Fix: the workflow record status is empty

Signed-off-by: barnettZQG <barnett.zqg@gmail.com>

* Fix: change the unit test case

Signed-off-by: barnettZQG <barnett.zqg@gmail.com>

* Fix: add some logs

Signed-off-by: barnettZQG <barnett.zqg@gmail.com>

* Fix: enhance the e2e test case

Signed-off-by: barnettZQG <barnett.zqg@gmail.com>

Signed-off-by: barnettZQG <barnett.zqg@gmail.com>
2023-01-04 09:56:36 +08:00
suwliang3
5b636e451a Feat: Detect the correctness of the custom addon repository when adding add… (#5221)
* Detect the correctness of the custom addon repository when adding addon registry

Signed-off-by: suwanliang_yewu <suwanliang_yewu@cmss.chinamobile.com>

* wrap error

Signed-off-by: suwanliang_yewu <suwanliang_yewu@cmss.chinamobile.com>

* don't check if the registry is not helm

Signed-off-by: suwanliang_yewu <suwanliang_yewu@cmss.chinamobile.com>

* modify unit-test

Signed-off-by: suwanliang_yewu <suwanliang_yewu@cmss.chinamobile.com>

* Modify unit-test

Signed-off-by: suwanliang_yewu <suwanliang_yewu@cmss.chinamobile.com>

Signed-off-by: suwanliang_yewu <suwanliang_yewu@cmss.chinamobile.com>
2023-01-03 20:02:59 +08:00
James Dobson
ecc77f8cae Test: prevent notification step definition test from failing when re-run. (#5253)
Signed-off-by: James Dobson <jdobson@guidewire.com>

Signed-off-by: James Dobson <jdobson@guidewire.com>
2023-01-03 13:46:07 +08:00
StevenLeiZhang
8a5239575a Feat: need one new Trait to support HorizontalPodAutoscaler of CPU/MEM/PodCustomMetrcs (#5225)
Signed-off-by: StevenLeiZhang <zhangleiic@163.com>

Signed-off-by: StevenLeiZhang <zhangleiic@163.com>
2023-01-03 11:17:31 +08:00
william302
6461625832 Docs: correct update project user api's doc description (#5244)
Signed-off-by: william302 <william902@qq.com>

Signed-off-by: william302 <william902@qq.com>
Co-authored-by: william302 <william902@qq.com>
2023-01-03 11:02:55 +08:00
James Dobson
28f6f42ed4 Fix: apply label to pod for vela-cli workflow step definition. Fixes #5247 (#5248)
Signed-off-by: James Dobson <jdobson@guidewire.com>

Signed-off-by: James Dobson <jdobson@guidewire.com>
2023-01-03 10:47:34 +08:00
old prince
7bc2f4e8d1 Fix:Dry-run from revision application,Problems caused by resource version lower than the current version (#5246)
Signed-off-by: old.prince <di7zhang@gmail.com>

Signed-off-by: old.prince <di7zhang@gmail.com>
2023-01-03 10:46:59 +08:00
barnettZQG
d588def0af Feat: versioned the context backend values to the app revision (#5231)
Signed-off-by: barnettZQG <barnett.zqg@gmail.com>

Signed-off-by: barnettZQG <barnett.zqg@gmail.com>
2023-01-02 21:23:12 +08:00
Somefive
6c12b968a7 Fix: add permission for release ci (#5245)
Signed-off-by: Somefive <yd219913@alibaba-inc.com>

Signed-off-by: Somefive <yd219913@alibaba-inc.com>
2022-12-31 13:23:03 +08:00
215 changed files with 5630 additions and 1708 deletions

2
.github/bot.md vendored
View File

@@ -1,6 +1,6 @@
### GitHub & kubevela automation
The bot is configured via [issue-commands.json](https://github.com/kubevela/kubevela/blob/master/.github/workflows/issue-commands.json)
The bot is configured via [issue-commands.json](https://github.com/kubevela/kubevela/blob/master/.github/issue-commands.json)
and some other GitHub [workflows](https://github.com/kubevela/kubevela/blob/master/.github/workflows).
By default, users with write access to the repo is allowed to use the comments,
the [userlist](https://github.com/kubevela/kubevela/blob/master/.github/comment.userlist)

View File

@@ -18,6 +18,15 @@ permissions:
jobs:
build:
permissions:
contents: write
actions: read
checks: write
issues: read
packages: write
pull-requests: read
repository-projects: read
statuses: read
runs-on: ubuntu-latest
name: build
strategy:
@@ -130,6 +139,15 @@ jobs:
./ossutil --config-file .ossutilconfig cp -u ./latest_version oss://$BUCKET/binary/vela/latest_version
upload-plugin-homebrew:
permissions:
contents: write
actions: read
checks: write
issues: read
packages: write
pull-requests: read
repository-projects: read
statuses: read
needs: build
runs-on: ubuntu-latest
name: upload-sha256sums

View File

@@ -336,7 +336,8 @@ type WorkflowStatus struct {
Steps []workflowv1alpha1.WorkflowStepStatus `json:"steps,omitempty"`
StartTime metav1.Time `json:"startTime,omitempty"`
EndTime metav1.Time `json:"endTime,omitempty"`
// +nullable
EndTime metav1.Time `json:"endTime,omitempty"`
}
// DefinitionType describes the type of DefinitionRevision.

View File

@@ -135,6 +135,8 @@ type ApplicationRevisionStatus struct {
Succeeded bool `json:"succeeded"`
// Workflow the running status of the workflow
Workflow *common.WorkflowStatus `json:"workflow,omitempty"`
// Record the context values to the revision.
WorkflowContext map[string]string `json:"workflowContext,omitempty"`
}
// +kubebuilder:object:root=true

View File

@@ -293,6 +293,13 @@ func (in *ApplicationRevisionStatus) DeepCopyInto(out *ApplicationRevisionStatus
*out = new(common.WorkflowStatus)
(*in).DeepCopyInto(*out)
}
if in.WorkflowContext != nil {
in, out := &in.WorkflowContext, &out.WorkflowContext
*out = make(map[string]string, len(*in))
for key, val := range *in {
(*out)[key] = val
}
}
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ApplicationRevisionStatus.

View File

@@ -99,6 +99,7 @@ helm install --create-namespace -n vela-system kubevela kubevela/vela-core --wai
| `featureGates.multiStageComponentApply` | if enabled, the multiStageComponentApply feature will be combined with the stage field in TraitDefinition to complete the multi-stage apply. | `false` |
| `featureGates.gzipApplicationRevision` | compress apprev using gzip (good) before being stored. This is reduces network throughput when dealing with huge apprevs. | `false` |
| `featureGates.zstdApplicationRevision` | compress apprev using zstd (fast and good) before being stored. This is reduces network throughput when dealing with huge apprevs. Note that zstd will be prioritized if you enable other compression options. | `true` |
| `featureGates.preDispatchDryRun` | enable dryrun before dispatching resources. Enable this flag can help prevent unsuccessful dispatch resources entering resourcetracker and improve the user experiences of gc but at the cost of increasing network requests. | `true` |
### MultiCluster parameters
@@ -110,7 +111,7 @@ helm install --create-namespace -n vela-system kubevela kubevela/vela-core --wai
| `multicluster.clusterGateway.replicaCount` | ClusterGateway replica count | `1` |
| `multicluster.clusterGateway.port` | ClusterGateway port | `9443` |
| `multicluster.clusterGateway.image.repository` | ClusterGateway image repository | `oamdev/cluster-gateway` |
| `multicluster.clusterGateway.image.tag` | ClusterGateway image tag | `v1.4.0` |
| `multicluster.clusterGateway.image.tag` | ClusterGateway image tag | `v1.7.0-alpha.3` |
| `multicluster.clusterGateway.image.pullPolicy` | ClusterGateway image pull policy | `IfNotPresent` |
| `multicluster.clusterGateway.resources.limits.cpu` | ClusterGateway cpu limit | `100m` |
| `multicluster.clusterGateway.resources.limits.memory` | ClusterGateway memory limit | `200Mi` |
@@ -149,7 +150,7 @@ helm install --create-namespace -n vela-system kubevela kubevela/vela-core --wai
| `kubeClient.qps` | The qps for reconcile clients, default is 100 | `100` |
| `kubeClient.burst` | The burst for reconcile clients, default is 200 | `200` |
| `authentication.enabled` | Enable authentication for application | `false` |
| `authentication.withUser` | Application authentication will impersonate as the request User | `false` |
| `authentication.withUser` | Application authentication will impersonate as the request User | `true` |
| `authentication.defaultUser` | Application authentication will impersonate as the User if no user provided in Application | `kubevela:vela-core` |
| `authentication.groupPattern` | Application authentication will impersonate as the request Group that matches the pattern | `kubevela:*` |

View File

@@ -848,6 +848,7 @@ spec:
x-kubernetes-map-type: atomic
endTime:
format: date-time
nullable: true
type: string
finished:
type: boolean
@@ -2806,6 +2807,7 @@ spec:
x-kubernetes-map-type: atomic
endTime:
format: date-time
nullable: true
type: string
finished:
type: boolean
@@ -4942,6 +4944,7 @@ spec:
x-kubernetes-map-type: atomic
endTime:
format: date-time
nullable: true
type: string
finished:
type: boolean
@@ -5042,6 +5045,11 @@ spec:
- suspend
- terminated
type: object
workflowContext:
additionalProperties:
type: string
description: Record the context values to the revision.
type: object
required:
- succeeded
type: object

View File

@@ -774,6 +774,7 @@ spec:
x-kubernetes-map-type: atomic
endTime:
format: date-time
nullable: true
type: string
finished:
type: boolean
@@ -1535,6 +1536,7 @@ spec:
x-kubernetes-map-type: atomic
endTime:
format: date-time
nullable: true
type: string
finished:
type: boolean

View File

@@ -32,6 +32,7 @@ spec:
- "--secure-port={{ .Values.multicluster.clusterGateway.port }}"
- "--secret-namespace={{ .Release.Namespace }}"
- "--feature-gates=APIPriorityAndFairness=false,ClientIdentityPenetration={{ .Values.authentication.enabled }}"
- "--cluster-gateway-proxy-config=/etc/proxy-config/config.yaml"
{{- if .Values.multicluster.clusterGateway.secureTLS.enabled }}
- "--tls-cert-file={{ .Values.multicluster.clusterGateway.secureTLS.certPath }}/tls.crt"
- "--tls-private-key-file={{ .Values.multicluster.clusterGateway.secureTLS.certPath }}/tls.key"
@@ -42,14 +43,20 @@ spec:
{{- toYaml .Values.multicluster.clusterGateway.resources | nindent 12 }}
ports:
- containerPort: {{ .Values.multicluster.clusterGateway.port }}
{{ if .Values.multicluster.clusterGateway.secureTLS.enabled }}
volumeMounts:
- mountPath: /etc/proxy-config
name: proxy-config
{{ if .Values.multicluster.clusterGateway.secureTLS.enabled }}
- mountPath: {{ .Values.multicluster.clusterGateway.secureTLS.certPath }}
name: tls-cert-vol
readOnly: true
{{- end }}
{{ if .Values.multicluster.clusterGateway.secureTLS.enabled }}
volumes:
- configMap:
defaultMode: 420
name: {{ .Release.Name }}-cluster-gateway-proxy-config
name: proxy-config
{{ if .Values.multicluster.clusterGateway.secureTLS.enabled }}
- name: tls-cert-vol
secret:
defaultMode: 420
@@ -74,6 +81,23 @@ spec:
maxUnavailable: 1
---
apiVersion: v1
kind: ConfigMap
metadata:
name: {{ .Release.Name }}-cluster-gateway-proxy-config
namespace: {{ .Release.Namespace }}
data:
config.yaml: |
apiVersion: cluster.core.oam.dev/v1alpha1
kind: ClusterGatewayProxyConfiguration
spec:
clientIdentityExchanger:
rules:
- name: super-user
source:
group: kubevela:ux
type: PrivilegedIdentityExchanger
---
apiVersion: v1
kind: Service
metadata:
name: {{ .Release.Name }}-cluster-gateway-service

View File

@@ -7,7 +7,6 @@ metadata:
definition.oam.dev/description: Apply a specific component and its corresponding traits in application
labels:
custom.definition.oam.dev/scope: Application
custom.definition.oam.dev/ui-hidden: "true"
name: apply-component
namespace: {{ include "systemDefinitionNamespace" . }}
spec:

View File

@@ -5,8 +5,6 @@ kind: WorkflowStepDefinition
metadata:
annotations:
definition.oam.dev/description: Wait for the specified Application to complete.
labels:
custom.definition.oam.dev/ui-hidden: "true"
name: depends-on-app
namespace: {{ include "systemDefinitionNamespace" . }}
spec:

View File

@@ -5,8 +5,6 @@ kind: WorkflowStepDefinition
metadata:
annotations:
definition.oam.dev/description: Export data to specified Kubernetes ConfigMap in your workflow.
labels:
custom.definition.oam.dev/ui-hidden: "true"
name: export2config
namespace: {{ include "systemDefinitionNamespace" . }}
spec:

View File

@@ -5,8 +5,6 @@ kind: WorkflowStepDefinition
metadata:
annotations:
definition.oam.dev/description: Export data to Kubernetes Secret in your workflow.
labels:
custom.definition.oam.dev/ui-hidden: "true"
name: export2secret
namespace: {{ include "systemDefinitionNamespace" . }}
spec:

View File

@@ -5,8 +5,6 @@ kind: WorkflowStepDefinition
metadata:
annotations:
definition.oam.dev/description: Generate a JDBC connection based on Component of alibaba-rds
labels:
custom.definition.oam.dev/ui-hidden: "true"
name: generate-jdbc-connection
namespace: {{ include "systemDefinitionNamespace" . }}
spec:

View File

@@ -0,0 +1,110 @@
# Code generated by KubeVela templates. DO NOT EDIT. Please edit the original cue file.
# Definition source cue file: vela-templates/definitions/internal/hpa.cue
apiVersion: core.oam.dev/v1beta1
kind: TraitDefinition
metadata:
annotations:
definition.oam.dev/description: Configure k8s HPA for Deployment or Statefulsets
name: hpa
namespace: {{ include "systemDefinitionNamespace" . }}
spec:
appliesToWorkloads:
- deployments.apps
- statefulsets.apps
podDisruptive: false
schematic:
cue:
template: |
outputs: hpa: {
if context.clusterVersion.minor < 23 {
apiVersion: "autoscaling/v2beta2"
}
if context.clusterVersion.minor >= 23 {
apiVersion: "autoscaling/v2"
}
kind: "HorizontalPodAutoscaler"
metadata: name: context.name
spec: {
scaleTargetRef: {
apiVersion: parameter.targetAPIVersion
kind: parameter.targetKind
name: context.name
}
minReplicas: parameter.min
maxReplicas: parameter.max
metrics: [
{
type: "Resource"
resource: {
name: "cpu"
target: {
type: parameter.cpu.type
if parameter.cpu.type == "Utilization" {
averageUtilization: parameter.cpu.value
}
if parameter.cpu.type == "AverageValue" {
averageValue: parameter.cpu.value
}
}
}
},
if parameter.mem != _|_ {
{
type: "Resource"
resource: {
name: "memory"
target: {
type: parameter.mem.type
if parameter.cpu.type == "Utilization" {
averageUtilization: parameter.cpu.value
}
if parameter.cpu.type == "AverageValue" {
averageValue: parameter.cpu.value
}
}
}
}
},
if parameter.podCustomMetrics != _|_ for m in parameter.podCustomMetrics {
type: "Pods"
pods: {
metric: name: m.name
target: {
type: "AverageValue"
averageValue: m.value
}
}
},
]
}
}
parameter: {
// +usage=Specify the minimal number of replicas to which the autoscaler can scale down
min: *1 | int
// +usage=Specify the maximum number of of replicas to which the autoscaler can scale up
max: *10 | int
// +usage=Specify the apiVersion of scale target
targetAPIVersion: *"apps/v1" | string
// +usage=Specify the kind of scale target
targetKind: *"Deployment" | string
cpu: {
// +usage=Specify resource metrics in terms of percentage("Utilization") or direct value("AverageValue")
type: *"Utilization" | "AverageValue"
// +usage=Specify the value of CPU utilization or averageValue
value: *50 | int
}
mem?: {
// +usage=Specify resource metrics in terms of percentage("Utilization") or direct value("AverageValue")
type: *"Utilization" | "AverageValue"
// +usage=Specify the value of MEM utilization or averageValue
value: *50 | int
}
// +usage=Specify custom metrics of pod type
podCustomMetrics?: [...{
// +usage=Specify name of custom metrics
name: string
// +usage=Specify target value of custom metrics
value: string
}]
}

View File

@@ -5,8 +5,6 @@ kind: WorkflowStepDefinition
metadata:
annotations:
definition.oam.dev/description: print message in workflow step status
labels:
custom.definition.oam.dev/ui-hidden: "true"
name: print-message-in-status
namespace: {{ include "systemDefinitionNamespace" . }}
spec:

View File

@@ -5,8 +5,6 @@ kind: WorkflowStepDefinition
metadata:
annotations:
definition.oam.dev/description: Read Kubernetes objects from cluster for your workflow steps
labels:
custom.definition.oam.dev/ui-hidden: "true"
name: read-object
namespace: {{ include "systemDefinitionNamespace" . }}
spec:

View File

@@ -5,8 +5,6 @@ kind: WorkflowStepDefinition
metadata:
annotations:
definition.oam.dev/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.
labels:
custom.definition.oam.dev/ui-hidden: "true"
name: step-group
namespace: {{ include "systemDefinitionNamespace" . }}
spec:

View File

@@ -54,6 +54,12 @@ spec:
emptyDir: medium: v.medium
}
},
if parameter.hostPath != _|_ for v in parameter.hostPath {
{
name: "hostpath-" + v.name
path: v.path
}
},
]
volumeMountsList: [
if parameter.pvc != _|_ for v in parameter.pvc {
@@ -94,6 +100,12 @@ spec:
}
}
},
if parameter.hostPath != _|_ for v in parameter.hostPath {
{
name: "hostpath-" + v.name
mountPath: v.mountPath
}
},
]
envList: [
if parameter.configMap != _|_ for v in parameter.configMap if v.mountToEnv != _|_ {
@@ -331,5 +343,13 @@ spec:
subPath?: string
medium: *"" | "Memory"
}]
// +usage=Declare host path type storage
hostPath?: [...{
name: string
path: string
mountPath: string
type: *"Directory" | "DirectoryOrCreate" | "FileOrCreate" | "File" | "Socket" | "CharDevice" | "BlockDevice"
}]
}

View File

@@ -19,18 +19,24 @@ spec:
mountsArray: [
if parameter.storage != _|_ && parameter.storage.secret != _|_ for v in parameter.storage.secret {
{
name: "secret-" + v.name
mountPath: v.mountPath
if v.subPath != _|_ {
subPath: v.subPath
}
name: v.name
}
},
if parameter.storage != _|_ && parameter.storage.hostPath != _|_ for v in parameter.storage.hostPath {
{
name: "hostpath-" + v.name
mountPath: v.mountPath
}
},
]
volumesList: [
if parameter.storage != _|_ && parameter.storage.secret != _|_ for v in parameter.storage.secret {
{
name: v.name
name: "secret-" + v.name
secret: {
defaultMode: v.defaultMode
secretName: v.secretName
@@ -39,6 +45,12 @@ spec:
}
}
}
if parameter.storage != _|_ && parameter.storage.hostPath != _|_ for v in parameter.storage.hostPath {
{
name: "hostpath-" + v.name
path: v.path
}
}
},
]
deDupVolumesArray: [
@@ -69,7 +81,7 @@ spec:
spec: {
backoffLimit: 3
template: {
labels: "workflow.oam.dev/step-name": "\(context.name)-\(context.stepName)"
metadata: labels: "workflow.oam.dev/step-name": "\(context.name)-\(context.stepName)"
spec: {
containers: [
{
@@ -125,6 +137,13 @@ spec:
mode: *511 | int
}]
}]
// +usage=Declare host path type storage
hostPath?: [...{
name: string
path: string
mountPath: string
type: *"Directory" | "DirectoryOrCreate" | "FileOrCreate" | "File" | "Socket" | "CharDevice" | "BlockDevice"
}]
}
}

View File

@@ -11,21 +11,6 @@ metadata:
{{- end }}
{{- end }}
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
name: {{ include "kubevela.fullname" . }}:manager-rolebinding
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: ClusterRole
name: {{ if .Values.authentication.enabled }} {{ include "kubevela.fullname" . }}:manager {{ else }} "cluster-admin" {{ end }}
subjects:
- kind: ServiceAccount
name: {{ include "kubevela.serviceAccountName" . }}
namespace: {{ .Release.Namespace }}
{{ if .Values.authentication.enabled }}
---
apiVersion: rbac.authorization.k8s.io/v1
@@ -66,9 +51,41 @@ rules:
- apiGroups: ["authorization.k8s.io"]
resources: ["subjectaccessreviews"]
verbs: ["*"]
{{ end }}
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
name: {{ include "kubevela.fullname" . }}:manager-authentication-rolebinding
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: ClusterRole
name: {{ include "kubevela.fullname" . }}:manager
subjects:
- kind: ServiceAccount
name: {{ include "kubevela.serviceAccountName" . }}
namespace: {{ .Release.Namespace }}
{{ else }}
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
name: {{ include "kubevela.fullname" . }}:manager-rolebinding
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: ClusterRole
name: "cluster-admin"
subjects:
- kind: ServiceAccount
name: {{ include "kubevela.serviceAccountName" . }}
namespace: {{ .Release.Namespace }}
{{ end }}
---
# permissions to do leader election.
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
@@ -257,6 +274,7 @@ spec:
- "--feature-gates=MultiStageComponentApply= {{- .Values.featureGates.multiStageComponentApply | toString -}}"
- "--feature-gates=GzipApplicationRevision={{- .Values.featureGates.gzipResourceTracker | toString -}}"
- "--feature-gates=ZstdApplicationRevision={{- .Values.featureGates.zstdResourceTracker | toString -}}"
- "--feature-gates=PreDispatchDryRun={{- .Values.featureGates.preDispatchDryRun | toString -}}"
{{ if .Values.authentication.enabled }}
{{ if .Values.authentication.withUser }}
- "--authentication-with-user"

View File

@@ -1,4 +1,3 @@
{{- if not (lookup "v1" "ConfigMap" (include "systemDefinitionNamespace" .) "component-pod-view") }}
apiVersion: v1
data:
template: |
@@ -74,4 +73,3 @@ kind: ConfigMap
metadata:
name: component-pod-view
namespace: {{ include "systemDefinitionNamespace" . }}
{{- end }}

View File

@@ -1,4 +1,3 @@
{{- if not (lookup "v1" "ConfigMap" (include "systemDefinitionNamespace" .) "component-service-view") }}
apiVersion: v1
data:
template: |
@@ -47,4 +46,3 @@ kind: ConfigMap
metadata:
name: component-service-view
namespace: {{ include "systemDefinitionNamespace" . }}
{{- end}}

View File

@@ -113,6 +113,7 @@ optimize:
##@param featureGates.multiStageComponentApply if enabled, the multiStageComponentApply feature will be combined with the stage field in TraitDefinition to complete the multi-stage apply.
##@param featureGates.gzipApplicationRevision compress apprev using gzip (good) before being stored. This is reduces network throughput when dealing with huge apprevs.
##@param featureGates.zstdApplicationRevision compress apprev using zstd (fast and good) before being stored. This is reduces network throughput when dealing with huge apprevs. Note that zstd will be prioritized if you enable other compression options.
##@param featureGates.preDispatchDryRun enable dryrun before dispatching resources. Enable this flag can help prevent unsuccessful dispatch resources entering resourcetracker and improve the user experiences of gc but at the cost of increasing network requests.
##@param
featureGates:
enableLegacyComponentRevision: false
@@ -122,6 +123,7 @@ featureGates:
multiStageComponentApply: false
gzipApplicationRevision: false
zstdApplicationRevision: true
preDispatchDryRun: true
## @section MultiCluster parameters
@@ -146,7 +148,7 @@ multicluster:
port: 9443
image:
repository: oamdev/cluster-gateway
tag: v1.4.0
tag: v1.7.0-alpha.3
pullPolicy: IfNotPresent
resources:
limits:
@@ -263,6 +265,6 @@ kubeClient:
## @param authentication.groupPattern Application authentication will impersonate as the request Group that matches the pattern
authentication:
enabled: false
withUser: false
withUser: true
defaultUser: kubevela:vela-core
groupPattern: kubevela:*

View File

@@ -848,6 +848,7 @@ spec:
x-kubernetes-map-type: atomic
endTime:
format: date-time
nullable: true
type: string
finished:
type: boolean
@@ -2806,6 +2807,7 @@ spec:
x-kubernetes-map-type: atomic
endTime:
format: date-time
nullable: true
type: string
finished:
type: boolean
@@ -4942,6 +4944,7 @@ spec:
x-kubernetes-map-type: atomic
endTime:
format: date-time
nullable: true
type: string
finished:
type: boolean
@@ -5042,6 +5045,11 @@ spec:
- suspend
- terminated
type: object
workflowContext:
additionalProperties:
type: string
description: Record the context values to the revision.
type: object
required:
- succeeded
type: object

View File

@@ -774,6 +774,7 @@ spec:
x-kubernetes-map-type: atomic
endTime:
format: date-time
nullable: true
type: string
finished:
type: boolean
@@ -1535,6 +1536,7 @@ spec:
x-kubernetes-map-type: atomic
endTime:
format: date-time
nullable: true
type: string
finished:
type: boolean

View File

@@ -7,7 +7,6 @@ metadata:
definition.oam.dev/description: Apply a specific component and its corresponding traits in application
labels:
custom.definition.oam.dev/scope: Application
custom.definition.oam.dev/ui-hidden: "true"
name: apply-component
namespace: {{ include "systemDefinitionNamespace" . }}
spec:

View File

@@ -5,8 +5,6 @@ kind: WorkflowStepDefinition
metadata:
annotations:
definition.oam.dev/description: Wait for the specified Application to complete.
labels:
custom.definition.oam.dev/ui-hidden: "true"
name: depends-on-app
namespace: {{ include "systemDefinitionNamespace" . }}
spec:

View File

@@ -5,8 +5,6 @@ kind: WorkflowStepDefinition
metadata:
annotations:
definition.oam.dev/description: Export data to specified Kubernetes ConfigMap in your workflow.
labels:
custom.definition.oam.dev/ui-hidden: "true"
name: export2config
namespace: {{ include "systemDefinitionNamespace" . }}
spec:

View File

@@ -5,8 +5,6 @@ kind: WorkflowStepDefinition
metadata:
annotations:
definition.oam.dev/description: Export data to Kubernetes Secret in your workflow.
labels:
custom.definition.oam.dev/ui-hidden: "true"
name: export2secret
namespace: {{ include "systemDefinitionNamespace" . }}
spec:

View File

@@ -5,8 +5,6 @@ kind: WorkflowStepDefinition
metadata:
annotations:
definition.oam.dev/description: Generate a JDBC connection based on Component of alibaba-rds
labels:
custom.definition.oam.dev/ui-hidden: "true"
name: generate-jdbc-connection
namespace: {{ include "systemDefinitionNamespace" . }}
spec:

View File

@@ -0,0 +1,110 @@
# Code generated by KubeVela templates. DO NOT EDIT. Please edit the original cue file.
# Definition source cue file: vela-templates/definitions/internal/hpa.cue
apiVersion: core.oam.dev/v1beta1
kind: TraitDefinition
metadata:
annotations:
definition.oam.dev/description: Configure k8s HPA for Deployment or Statefulsets
name: hpa
namespace: {{ include "systemDefinitionNamespace" . }}
spec:
appliesToWorkloads:
- deployments.apps
- statefulsets.apps
podDisruptive: false
schematic:
cue:
template: |
outputs: hpa: {
if context.clusterVersion.minor < 23 {
apiVersion: "autoscaling/v2beta2"
}
if context.clusterVersion.minor >= 23 {
apiVersion: "autoscaling/v2"
}
kind: "HorizontalPodAutoscaler"
metadata: name: context.name
spec: {
scaleTargetRef: {
apiVersion: parameter.targetAPIVersion
kind: parameter.targetKind
name: context.name
}
minReplicas: parameter.min
maxReplicas: parameter.max
metrics: [
{
type: "Resource"
resource: {
name: "cpu"
target: {
type: parameter.cpu.type
if parameter.cpu.type == "Utilization" {
averageUtilization: parameter.cpu.value
}
if parameter.cpu.type == "AverageValue" {
averageValue: parameter.cpu.value
}
}
}
},
if parameter.mem != _|_ {
{
type: "Resource"
resource: {
name: "memory"
target: {
type: parameter.mem.type
if parameter.cpu.type == "Utilization" {
averageUtilization: parameter.cpu.value
}
if parameter.cpu.type == "AverageValue" {
averageValue: parameter.cpu.value
}
}
}
}
},
if parameter.podCustomMetrics != _|_ for m in parameter.podCustomMetrics {
type: "Pods"
pods: {
metric: name: m.name
target: {
type: "AverageValue"
averageValue: m.value
}
}
},
]
}
}
parameter: {
// +usage=Specify the minimal number of replicas to which the autoscaler can scale down
min: *1 | int
// +usage=Specify the maximum number of of replicas to which the autoscaler can scale up
max: *10 | int
// +usage=Specify the apiVersion of scale target
targetAPIVersion: *"apps/v1" | string
// +usage=Specify the kind of scale target
targetKind: *"Deployment" | string
cpu: {
// +usage=Specify resource metrics in terms of percentage("Utilization") or direct value("AverageValue")
type: *"Utilization" | "AverageValue"
// +usage=Specify the value of CPU utilization or averageValue
value: *50 | int
}
mem?: {
// +usage=Specify resource metrics in terms of percentage("Utilization") or direct value("AverageValue")
type: *"Utilization" | "AverageValue"
// +usage=Specify the value of MEM utilization or averageValue
value: *50 | int
}
// +usage=Specify custom metrics of pod type
podCustomMetrics?: [...{
// +usage=Specify name of custom metrics
name: string
// +usage=Specify target value of custom metrics
value: string
}]
}

View File

@@ -5,8 +5,6 @@ kind: WorkflowStepDefinition
metadata:
annotations:
definition.oam.dev/description: print message in workflow step status
labels:
custom.definition.oam.dev/ui-hidden: "true"
name: print-message-in-status
namespace: {{ include "systemDefinitionNamespace" . }}
spec:

View File

@@ -5,8 +5,6 @@ kind: WorkflowStepDefinition
metadata:
annotations:
definition.oam.dev/description: Read Kubernetes objects from cluster for your workflow steps
labels:
custom.definition.oam.dev/ui-hidden: "true"
name: read-object
namespace: {{ include "systemDefinitionNamespace" . }}
spec:

View File

@@ -5,8 +5,6 @@ kind: WorkflowStepDefinition
metadata:
annotations:
definition.oam.dev/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.
labels:
custom.definition.oam.dev/ui-hidden: "true"
name: step-group
namespace: {{ include "systemDefinitionNamespace" . }}
spec:

View File

@@ -54,6 +54,12 @@ spec:
emptyDir: medium: v.medium
}
},
if parameter.hostPath != _|_ for v in parameter.hostPath {
{
name: "hostpath-" + v.name
path: v.path
}
},
]
volumeMountsList: [
if parameter.pvc != _|_ for v in parameter.pvc {
@@ -94,6 +100,12 @@ spec:
}
}
},
if parameter.hostPath != _|_ for v in parameter.hostPath {
{
name: "hostpath-" + v.name
mountPath: v.mountPath
}
},
]
envList: [
if parameter.configMap != _|_ for v in parameter.configMap if v.mountToEnv != _|_ {
@@ -331,5 +343,13 @@ spec:
subPath?: string
medium: *"" | "Memory"
}]
// +usage=Declare host path type storage
hostPath?: [...{
name: string
path: string
mountPath: string
type: *"Directory" | "DirectoryOrCreate" | "FileOrCreate" | "File" | "Socket" | "CharDevice" | "BlockDevice"
}]
}

View File

@@ -19,18 +19,24 @@ spec:
mountsArray: [
if parameter.storage != _|_ && parameter.storage.secret != _|_ for v in parameter.storage.secret {
{
name: "secret-" + v.name
mountPath: v.mountPath
if v.subPath != _|_ {
subPath: v.subPath
}
name: v.name
}
},
if parameter.storage != _|_ && parameter.storage.hostPath != _|_ for v in parameter.storage.hostPath {
{
name: "hostpath-" + v.name
mountPath: v.mountPath
}
},
]
volumesList: [
if parameter.storage != _|_ && parameter.storage.secret != _|_ for v in parameter.storage.secret {
{
name: v.name
name: "secret-" + v.name
secret: {
defaultMode: v.defaultMode
secretName: v.secretName
@@ -39,6 +45,12 @@ spec:
}
}
}
if parameter.storage != _|_ && parameter.storage.hostPath != _|_ for v in parameter.storage.hostPath {
{
name: "hostpath-" + v.name
path: v.path
}
}
},
]
deDupVolumesArray: [
@@ -69,7 +81,7 @@ spec:
spec: {
backoffLimit: 3
template: {
labels: "workflow.oam.dev/step-name": "\(context.name)-\(context.stepName)"
metadata: labels: "workflow.oam.dev/step-name": "\(context.name)-\(context.stepName)"
spec: {
containers: [
{
@@ -125,6 +137,13 @@ spec:
mode: *511 | int
}]
}]
// +usage=Declare host path type storage
hostPath?: [...{
name: string
path: string
mountPath: string
type: *"Directory" | "DirectoryOrCreate" | "FileOrCreate" | "File" | "Socket" | "CharDevice" | "BlockDevice"
}]
}
}

View File

@@ -17,10 +17,13 @@ limitations under the License.
package options
import (
"k8s.io/apiserver/pkg/util/feature"
"flag"
cliflag "k8s.io/component-base/cli/flag"
"k8s.io/klog/v2"
"github.com/oam-dev/kubevela/pkg/apiserver/config"
"github.com/oam-dev/kubevela/pkg/features"
)
// ServerRunOptions contains everything necessary to create and run api server
@@ -40,6 +43,9 @@ func NewServerRunOptions() *ServerRunOptions {
func (s *ServerRunOptions) Flags() (fss cliflag.NamedFlagSets) {
fs := fss.FlagSet("generic")
s.GenericServerRunOptions.AddFlags(fs, s.GenericServerRunOptions)
feature.DefaultMutableFeatureGate.AddFlag(fss.FlagSet("featuregate"))
features.APIServerMutableFeatureGate.AddFlag(fss.FlagSet("featuregate"))
local := flag.NewFlagSet("klog", flag.ExitOnError)
klog.InitFlags(local)
fs.AddGoFlagSet(local)
return fss
}

File diff suppressed because it is too large Load Diff

View File

@@ -199,7 +199,7 @@ var ApplicationDeleteWithWaitOptions = func(context string, appName string) bool
cli := fmt.Sprintf("vela delete %s --wait -y", appName)
output, err := e2e.ExecAndTerminate(cli)
gomega.Expect(err).NotTo(gomega.HaveOccurred())
gomega.Expect(output).To(gomega.ContainSubstring("deleted"))
gomega.Expect(output).To(gomega.ContainSubstring("succeeded"))
})
})
}

View File

@@ -160,7 +160,7 @@ var (
cli := fmt.Sprintf("vela delete %s -y", applicationName)
output, err := Exec(cli)
gomega.Expect(err).NotTo(gomega.HaveOccurred())
gomega.Expect(output).To(gomega.ContainSubstring("deleted from namespace"))
gomega.Expect(output).To(gomega.ContainSubstring("succeeded"))
})
})
}

5
go.mod
View File

@@ -49,16 +49,15 @@ require (
github.com/google/go-github/v32 v32.1.0
github.com/google/uuid v1.3.0
github.com/gorilla/websocket v1.4.2
github.com/gosuri/uilive v0.0.4
github.com/gosuri/uitable v0.0.4
github.com/hashicorp/go-version v1.6.0
github.com/hashicorp/hcl/v2 v2.12.0
github.com/hinshun/vt10x v0.0.0-20180616224451-1954e6464174
github.com/imdario/mergo v0.3.13
github.com/koding/websocketproxy v0.0.0-20181220232114-7ed82d81a28c
github.com/kubevela/pkg v0.0.0-20221220022408-126a9c58aa3a
github.com/kubevela/pkg v0.0.0-20230105054759-263dc191bf51
github.com/kubevela/prism v1.7.0-alpha.1
github.com/kubevela/workflow v0.3.6-0.20221228071359-3da7f1a4df6b
github.com/kubevela/workflow v0.3.6-0.20221230102636-6ae0c5cbc40f
github.com/kyokomi/emoji v2.2.4+incompatible
github.com/mitchellh/hashstructure/v2 v2.0.1
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd

10
go.sum
View File

@@ -1060,8 +1060,6 @@ github.com/gostaticanalysis/forcetypeassert v0.0.0-20200621232751-01d4955beaa5/g
github.com/gostaticanalysis/nilerr v0.1.1/go.mod h1:wZYb6YI5YAxxq0i1+VJbY0s2YONW0HU0GPE3+5PWN4A=
github.com/gostaticanalysis/testutil v0.3.1-0.20210208050101-bfb5c8eec0e4/go.mod h1:D+FIZ+7OahH3ePw/izIEeH5I06eKs1IKI4Xr64/Am3M=
github.com/gostaticanalysis/testutil v0.4.0/go.mod h1:bLIoPefWXrRi/ssLFWX1dx7Repi5x3CuviD3dgAZaBU=
github.com/gosuri/uilive v0.0.4 h1:hUEBpQDj8D8jXgtCdBu7sWsy5sbW/5GhuO8KBwJ2jyY=
github.com/gosuri/uilive v0.0.4/go.mod h1:V/epo5LjjlDE5RJUcqx8dbw+zc93y5Ya3yg8tfZ74VI=
github.com/gosuri/uitable v0.0.4 h1:IG2xLKRvErL3uhY6e1BylFzG+aJiwQviDDTfOKeKTpY=
github.com/gosuri/uitable v0.0.4/go.mod h1:tKR86bXuXPZazfOTG1FIzvjIdXzd0mo4Vtn16vt0PJo=
github.com/gregjones/httpcache v0.0.0-20170728041850-787624de3eb7/go.mod h1:FecbI9+v66THATjSRHfNgh1IVFe/9kFxbXtjV0ctIMA=
@@ -1287,12 +1285,12 @@ github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
github.com/kubevela/cue v0.4.4-0.20221107123854-a976b0e340be h1:0xj/Rh4yVy54mUD2nLmAuN1AYgBkkHxBh4PoLGbIg5g=
github.com/kubevela/cue v0.4.4-0.20221107123854-a976b0e340be/go.mod h1:Ya12qn7FZc+LSN0qgEhzEpnzQsvnGHVgoDrqe9i3eNg=
github.com/kubevela/pkg v0.0.0-20221220022408-126a9c58aa3a h1:kWBTjpxcA6ZQTGyk1Mm+1/MT2+3Fv3ffQnK/1GYdXo4=
github.com/kubevela/pkg v0.0.0-20221220022408-126a9c58aa3a/go.mod h1:IQ0/t6H7+580nwFlkt08gbPyH9S4zQNxnKTM2eiK0TI=
github.com/kubevela/pkg v0.0.0-20230105054759-263dc191bf51 h1:xrcNNaAjqC6tr1leSYcjLFgrXKpZ8u87jpB5TolhUIc=
github.com/kubevela/pkg v0.0.0-20230105054759-263dc191bf51/go.mod h1:ZRnxY/gOcg/8FilZA+eYr+rtVXb1ijT5HFTe8zrv9zo=
github.com/kubevela/prism v1.7.0-alpha.1 h1:oeZFn1Oy6gxSSFzMTfsWjLOCKaaooMVm1JGNK4j4Mlo=
github.com/kubevela/prism v1.7.0-alpha.1/go.mod h1:AJSDfdA+RkRSnWx3xEcogbmOTpX+l7RSIwqVHxwUtaI=
github.com/kubevela/workflow v0.3.6-0.20221228071359-3da7f1a4df6b h1:zbBG/fTXIyhwyS3XfoYxEWJ3F1xGGQN7bCZQhlI0KYI=
github.com/kubevela/workflow v0.3.6-0.20221228071359-3da7f1a4df6b/go.mod h1:AX/WL3G/YBkpmNpA/SKKm9M3Y0T9y95gZA8mFWylkyM=
github.com/kubevela/workflow v0.3.6-0.20221230102636-6ae0c5cbc40f h1:7EZWIfcTOgMlLgHkdDlf++hSjBTulfr4DYhZjeQbiJI=
github.com/kubevela/workflow v0.3.6-0.20221230102636-6ae0c5cbc40f/go.mod h1:AX/WL3G/YBkpmNpA/SKKm9M3Y0T9y95gZA8mFWylkyM=
github.com/kulti/thelper v0.4.0/go.mod h1:vMu2Cizjy/grP+jmsvOFDx1kYP6+PD1lqg4Yu5exl2U=
github.com/kunwardeep/paralleltest v1.0.3/go.mod h1:vLydzomDFpk7yu5UX02RmP0H8QfRPOV/oFhWN85Mjb4=
github.com/kylelemons/godebug v0.0.0-20160406211939-eadb3ce320cb/go.mod h1:B69LEHPfb2qLo0BaaOLcbitczOKLWTsrBG9LczfCD4k=

View File

@@ -848,6 +848,7 @@ spec:
x-kubernetes-map-type: atomic
endTime:
format: date-time
nullable: true
type: string
finished:
type: boolean
@@ -2806,6 +2807,7 @@ spec:
x-kubernetes-map-type: atomic
endTime:
format: date-time
nullable: true
type: string
finished:
type: boolean
@@ -4942,6 +4944,7 @@ spec:
x-kubernetes-map-type: atomic
endTime:
format: date-time
nullable: true
type: string
finished:
type: boolean
@@ -5042,6 +5045,11 @@ spec:
- suspend
- terminated
type: object
workflowContext:
additionalProperties:
type: string
description: Record the context values to the revision.
type: object
required:
- succeeded
type: object

View File

@@ -774,6 +774,7 @@ spec:
x-kubernetes-map-type: atomic
endTime:
format: date-time
nullable: true
type: string
finished:
type: boolean
@@ -1535,6 +1536,7 @@ spec:
x-kubernetes-map-type: atomic
endTime:
format: date-time
nullable: true
type: string
finished:
type: boolean

View File

@@ -60,6 +60,7 @@ import (
common2 "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"
apiutils "github.com/oam-dev/kubevela/pkg/apiserver/utils"
"github.com/oam-dev/kubevela/pkg/config"
"github.com/oam-dev/kubevela/pkg/cue/script"
"github.com/oam-dev/kubevela/pkg/definition"
@@ -910,7 +911,6 @@ func NewAddonInstaller(ctx context.Context, cli client.Client, discoveryClient *
func (h *Installer) enableAddon(addon *InstallPackage) (string, error) {
var err error
h.addon = addon
if !h.skipVersionValidate {
err = checkAddonVersionMeetRequired(h.ctx, addon.SystemRequirements, h.cli, h.dc)
if err != nil {
@@ -1075,23 +1075,25 @@ func (h *Installer) checkDependency(addon *InstallPackage) ([]string, error) {
// createOrUpdate will return true if updated
func (h *Installer) createOrUpdate(app *v1beta1.Application) (bool, error) {
var getapp v1beta1.Application
err := h.cli.Get(h.ctx, client.ObjectKey{Name: app.Name, Namespace: app.Namespace}, &getapp)
// Set the publish version for the addon application
oam.SetPublishVersion(app, apiutils.GenerateVersion("addon"))
var existApp v1beta1.Application
err := h.cli.Get(h.ctx, client.ObjectKey{Name: app.Name, Namespace: app.Namespace}, &existApp)
if apierrors.IsNotFound(err) {
return false, h.cli.Create(h.ctx, app)
}
if err != nil {
return false, err
}
getapp.Spec = app.Spec
getapp.Labels = app.Labels
getapp.Annotations = app.Annotations
err = h.cli.Update(h.ctx, &getapp)
existApp.Spec = app.Spec
existApp.Labels = app.Labels
existApp.Annotations = app.Annotations
err = h.cli.Update(h.ctx, &existApp)
if err != nil {
klog.Errorf("fail to create application: %v", err)
return false, errors.Wrap(err, "fail to create application")
}
getapp.DeepCopyInto(app)
existApp.DeepCopyInto(app)
return true, nil
}
@@ -1208,6 +1210,9 @@ func (h *Installer) dispatchAddonResource(addon *InstallPackage) error {
}
func (h *Installer) renderNotes(addon *InstallPackage) (string, error) {
if len(addon.Notes.Data) == 0 {
return "", nil
}
r := addonCueTemplateRender{
addon: addon,
inputArgs: h.args,
@@ -1219,7 +1224,7 @@ func (h *Installer) renderNotes(addon *InstallPackage) (string, error) {
if err != nil {
return "", err
}
notesFile := addon.Notes.Data + "\n" + contextFile
notesFile := contextFile + "\n" + addon.Notes.Data
val, err := value.NewValue(notesFile, nil, "")
if err != nil {
return "", errors.Wrap(err, "build values for NOTES.cue")

View File

@@ -67,13 +67,18 @@ func NewCache(ds RegistryDataStore) *Cache {
}
// DiscoverAndRefreshLoop will run a loop to automatically discovery and refresh addons from registry
func (u *Cache) DiscoverAndRefreshLoop(cacheTime time.Duration) {
func (u *Cache) DiscoverAndRefreshLoop(ctx context.Context, cacheTime time.Duration) {
ticker := time.NewTicker(cacheTime)
defer ticker.Stop()
// This is infinite loop, we can receive a channel for close
for ; true; <-ticker.C {
u.discoverAndRefreshRegistry()
for {
select {
case <-ticker.C:
u.discoverAndRefreshRegistry()
case <-ctx.Done():
return
}
}
}

View File

@@ -58,6 +58,9 @@ func EnableAddon(ctx context.Context, name string, version string, cli client.Cl
if err != nil {
return "", err
}
if err := validateAddonPackage(pkg); err != nil {
return "", errors.Wrap(err, fmt.Sprintf("failed to enable addon: %s", name))
}
return h.enableAddon(pkg)
}
@@ -106,6 +109,9 @@ func EnableAddonByLocalDir(ctx context.Context, name string, dir string, cli cli
if err != nil {
return "", err
}
if err := validateAddonPackage(pkg); err != nil {
return "", errors.Wrap(err, fmt.Sprintf("failed to enable addon by local dir: %s", dir))
}
h := NewAddonInstaller(ctx, cli, dc, applicator, config, &Registry{Name: LocalAddonRegistryName}, args, nil, nil, opts...)
needEnableAddonNames, err := h.checkDependency(pkg)
if err != nil {

View File

@@ -17,9 +17,12 @@ limitations under the License.
package addon
import (
"bytes"
"context"
"errors"
"net/http"
"net/http/httptest"
"os"
"strings"
v1 "k8s.io/api/core/v1"
@@ -29,6 +32,38 @@ import (
. "github.com/onsi/gomega"
)
func setupMockServer() *httptest.Server {
var listenURL string
s := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
fileList := []string{
"index.yaml",
"fluxcd-test-version-1.0.0.tgz",
"fluxcd-test-version-2.0.0.tgz",
"vela-workflow-v0.3.5.tgz",
"foo-v1.0.0.tgz",
"bar-v1.0.0.tgz",
"bar-v2.0.0.tgz",
"mock-be-dep-addon-v1.0.0.tgz",
}
for _, f := range fileList {
if strings.Contains(req.URL.Path, f) {
file, err := os.ReadFile("../../e2e/addon/mock/testrepo/helm-repo/" + f)
if err != nil {
_, _ = w.Write([]byte(err.Error()))
}
if f == "index.yaml" {
// in index.yaml, url is hardcoded to 127.0.0.1:9098,
// so we need to replace it with the real random listen url
file = bytes.ReplaceAll(file, []byte("http://127.0.0.1:9098"), []byte(listenURL))
}
_, _ = w.Write(file)
}
}
}))
listenURL = s.URL
return s
}
var _ = Describe("test FindAddonPackagesDetailFromRegistry", func() {
Describe("when no registry is added, no matter what you do, it will just return error", func() {
Context("when empty addonNames and registryNames is supplied", func() {
@@ -50,12 +85,15 @@ var _ = Describe("test FindAddonPackagesDetailFromRegistry", func() {
})
Describe("one versioned registry is added", func() {
var s *httptest.Server
BeforeEach(func() {
// Prepare KubeVela registry
s = setupMockServer()
// Prepare registry
reg := &Registry{
Name: "KubeVela",
Name: "addon_helper_test",
Helm: &HelmSource{
URL: "https://addons.kubevela.net",
URL: s.URL,
},
}
ds := NewRegistryDataStore(k8sClient)
@@ -63,38 +101,36 @@ var _ = Describe("test FindAddonPackagesDetailFromRegistry", func() {
})
AfterEach(func() {
// Clean up KubeVela registry
// Clean up registry
ds := NewRegistryDataStore(k8sClient)
Expect(ds.DeleteRegistry(context.Background(), "KubeVela")).To(Succeed())
Expect(ds.DeleteRegistry(context.Background(), "addon_helper_test")).To(Succeed())
s.Close()
})
Context("when empty addonNames and registryNames is supplied", func() {
It("should return error, empty addonNames are not allowed", func() {
_, err := FindAddonPackagesDetailFromRegistry(context.Background(), k8sClient, []string{}, []string{"KubeVela"})
_, err := FindAddonPackagesDetailFromRegistry(context.Background(), k8sClient, []string{}, []string{"addon_helper_test"})
Expect(err).To(HaveOccurred())
})
It("should return error, empty addonNames are not allowed", func() {
_, err := FindAddonPackagesDetailFromRegistry(context.Background(), k8sClient, nil, []string{"KubeVela"})
_, err := FindAddonPackagesDetailFromRegistry(context.Background(), k8sClient, nil, []string{"addon_helper_test"})
Expect(err).To(HaveOccurred())
})
})
Context("one existing addon name provided", func() {
It("should return one valid result, matching all registries", func() {
res, err := FindAddonPackagesDetailFromRegistry(context.Background(), k8sClient, []string{"velaux"}, nil)
res, err := FindAddonPackagesDetailFromRegistry(context.Background(), k8sClient, []string{"foo"}, nil)
Expect(err).To(Succeed())
Expect(res).To(HaveLen(1))
Expect(res[0].Name).To(Equal("velaux"))
Expect(res[0].InstallPackage).ToNot(BeNil())
Expect(res[0].APISchema).ToNot(BeNil())
Expect(res[0].Name).To(Equal("foo"))
})
It("should return one valid result, matching one registry", func() {
res, err := FindAddonPackagesDetailFromRegistry(context.Background(), k8sClient, []string{"velaux"}, []string{"KubeVela"})
res, err := FindAddonPackagesDetailFromRegistry(context.Background(), k8sClient, []string{"foo"}, []string{"addon_helper_test"})
Expect(err).To(Succeed())
Expect(res).To(HaveLen(1))
Expect(res[0].Name).To(Equal("velaux"))
Expect(res[0].InstallPackage).ToNot(BeNil())
Expect(res[0].APISchema).ToNot(BeNil())
Expect(res[0].Name).To(Equal("foo"))
})
})
@@ -108,26 +144,20 @@ var _ = Describe("test FindAddonPackagesDetailFromRegistry", func() {
Context("two existing addon names provided", func() {
It("should return two valid result", func() {
res, err := FindAddonPackagesDetailFromRegistry(context.Background(), k8sClient, []string{"velaux", "traefik"}, nil)
res, err := FindAddonPackagesDetailFromRegistry(context.Background(), k8sClient, []string{"foo", "bar"}, nil)
Expect(err).To(Succeed())
Expect(res).To(HaveLen(2))
Expect(res[0].Name).To(Equal("velaux"))
Expect(res[0].InstallPackage).ToNot(BeNil())
Expect(res[0].APISchema).ToNot(BeNil())
Expect(res[1].Name).To(Equal("traefik"))
Expect(res[1].InstallPackage).ToNot(BeNil())
Expect(res[1].APISchema).ToNot(BeNil())
Expect(res[0].Name).To(Equal("foo"))
Expect(res[1].Name).To(Equal("bar"))
})
})
Context("one existing addon name and one non-existent addon name provided", func() {
It("should return only one valid result", func() {
res, err := FindAddonPackagesDetailFromRegistry(context.Background(), k8sClient, []string{"velaux", "non-existent-addon"}, nil)
res, err := FindAddonPackagesDetailFromRegistry(context.Background(), k8sClient, []string{"foo", "non-existent-addon"}, nil)
Expect(err).To(Succeed())
Expect(res).To(HaveLen(1))
Expect(res[0].Name).To(Equal("velaux"))
Expect(res[0].InstallPackage).ToNot(BeNil())
Expect(res[0].APISchema).ToNot(BeNil())
Expect(res[0].Name).To(Equal("foo"))
})
})
})

View File

@@ -196,9 +196,13 @@ func (cmd *InitCmd) createHelmComponent() error {
tmpl := helmComponentTmpl{}
tmpl.Type = "helm"
tmpl.Properties.RepoType = "helm"
if strings.HasPrefix(cmd.HelmRepoURL, "oci") {
tmpl.Properties.RepoType = "oci"
}
tmpl.Properties.URL = cmd.HelmRepoURL
tmpl.Properties.Chart = cmd.HelmChartName
tmpl.Properties.Version = cmd.HelmChartVersion
tmpl.Name = "addon-" + cmd.AddonName
str, err := toCUEResourceString(tmpl)
if err != nil {
@@ -383,6 +387,7 @@ func (cmd *InitCmd) writeFiles() error {
// helmComponentTmpl is a template for a helm component .cue in an addon
type helmComponentTmpl struct {
Name string `json:"name"`
Type string `json:"type"`
Properties struct {
RepoType string `json:"repoType"`

View File

@@ -21,6 +21,7 @@ import (
"fmt"
"os"
"path/filepath"
"reflect"
"strings"
"github.com/pkg/errors"
@@ -503,6 +504,19 @@ func checkBondComponentExist(u unstructured.Unstructured, app v1beta1.Applicatio
return false
}
func validateAddonPackage(addonPkg *InstallPackage) error {
if reflect.DeepEqual(addonPkg.Meta, Meta{}) {
return fmt.Errorf("the addon package doesn't have `metadata.yaml`")
}
if addonPkg.Name == "" {
return fmt.Errorf("`matadata.yaml` must define the name of addon")
}
if addonPkg.Version == "" {
return fmt.Errorf("`matadata.yaml` must define the version of addon")
}
return nil
}
// FilterDependencyRegistries will return all registries besides the target registry itself
func FilterDependencyRegistries(i int, rs []Registry) []Registry {
if i >= len(rs) {

View File

@@ -17,9 +17,11 @@ limitations under the License.
package addon
import (
"fmt"
"net/http/httptest"
"os"
"path/filepath"
"reflect"
"strings"
"testing"
@@ -402,6 +404,30 @@ func TestFilterDependencyRegistries(t *testing.T) {
}
}
func TestCheckAddonPackageValid(t *testing.T) {
testCases := []struct {
testCase Meta
err error
}{{
testCase: Meta{},
err: fmt.Errorf("the addon package doesn't have `metadata.yaml`"),
}, {
testCase: Meta{Version: "v1.4.0"},
err: fmt.Errorf("`matadata.yaml` must define the name of addon"),
}, {
testCase: Meta{Name: "test-addon"},
err: fmt.Errorf("`matadata.yaml` must define the version of addon"),
}, {
testCase: Meta{Name: "test-addon", Version: "1.4.5"},
err: nil,
},
}
for _, testCase := range testCases {
err := validateAddonPackage(&InstallPackage{Meta: testCase.testCase})
assert.Equal(t, reflect.DeepEqual(err, testCase.err), true)
}
}
const (
compDefYaml = `
apiVersion: core.oam.dev/v1beta1

View File

@@ -149,7 +149,7 @@ func (i versionedRegistry) loadAddon(ctx context.Context, name, version string)
if addonVersion == nil {
return nil, errors.Errorf("specified version %s not exist", utils.Sanitize(version))
}
klog.Infof("Addon '%s' with version '%s' found from registry '%s'", addonVersion.Name, addonVersion.Version, i.name)
klog.V(5).Infof("Addon '%s' with version '%s' found from registry '%s'", addonVersion.Name, addonVersion.Version, i.name)
for _, chartURL := range addonVersion.URLs {
if !utils.IsValidURL(chartURL) {
chartURL, err = utils.JoinURL(i.url, chartURL)
@@ -159,10 +159,12 @@ func (i versionedRegistry) loadAddon(ctx context.Context, name, version string)
}
archive, err := common.HTTPGetWithOption(ctx, chartURL, i.Opts)
if err != nil {
klog.Warningf("failed to download the addon package %s:%s", chartURL, err.Error())
continue
}
bufferedFile, err := loader.LoadArchiveFiles(bytes.NewReader(archive))
if err != nil {
klog.Warningf("failed to load the addon package:%s", err.Error())
continue
}
addonPkg, err := loadAddonPackage(name, bufferedFile)
@@ -172,6 +174,7 @@ func (i versionedRegistry) loadAddon(ctx context.Context, name, version string)
addonPkg.AvailableVersions = availableVersions
addonPkg.RegistryName = i.name
addonPkg.Meta.SystemRequirements = LoadSystemRequirements(addonVersion.Annotations)
klog.V(5).Infof("Addon '%s' with version '%s' loaded successfully from registry '%s'", addonVersion.Name, addonVersion.Version, i.name)
return addonPkg, nil
}
return nil, ErrFetch

View File

@@ -59,8 +59,8 @@ func (a *Application) PrimaryKey() string {
}
// Index return custom index
func (a *Application) Index() map[string]string {
index := make(map[string]string)
func (a *Application) Index() map[string]interface{} {
index := make(map[string]interface{})
if a.Name != "" {
index["name"] = a.Name
}
@@ -154,8 +154,8 @@ func (a *ApplicationComponent) PrimaryKey() string {
}
// Index return custom index
func (a *ApplicationComponent) Index() map[string]string {
index := make(map[string]string)
func (a *ApplicationComponent) Index() map[string]interface{} {
index := make(map[string]interface{})
if a.Name != "" {
index["name"] = a.Name
}
@@ -202,8 +202,8 @@ func (a *ApplicationPolicy) PrimaryKey() string {
}
// Index return custom index
func (a *ApplicationPolicy) Index() map[string]string {
index := make(map[string]string)
func (a *ApplicationPolicy) Index() map[string]interface{} {
index := make(map[string]interface{})
if a.Name != "" {
index["name"] = a.Name
}
@@ -348,8 +348,8 @@ func (a *ApplicationRevision) PrimaryKey() string {
}
// Index return custom index
func (a *ApplicationRevision) Index() map[string]string {
index := make(map[string]string)
func (a *ApplicationRevision) Index() map[string]interface{} {
index := make(map[string]interface{})
if a.Version != "" {
index["version"] = a.Version
}
@@ -434,8 +434,8 @@ func (w *ApplicationTrigger) PrimaryKey() string {
}
// Index return custom index
func (w *ApplicationTrigger) Index() map[string]string {
index := make(map[string]string)
func (w *ApplicationTrigger) Index() map[string]interface{} {
index := make(map[string]interface{})
if w.AppPrimaryKey != "" {
index["appPrimaryKey"] = w.AppPrimaryKey
}

View File

@@ -93,8 +93,8 @@ func (c *Cluster) PrimaryKey() string {
}
// Index set to nil for list
func (c *Cluster) Index() map[string]string {
index := make(map[string]string)
func (c *Cluster) Index() map[string]interface{} {
index := make(map[string]interface{})
if c.Name != "" {
index["name"] = c.Name
}

View File

@@ -53,8 +53,8 @@ func (p *Env) PrimaryKey() string {
}
// Index return custom index
func (p *Env) Index() map[string]string {
index := make(map[string]string)
func (p *Env) Index() map[string]interface{} {
index := make(map[string]interface{})
if p.Name != "" {
index["name"] = p.Name
}

View File

@@ -62,8 +62,8 @@ func (e *EnvBinding) PrimaryKey() string {
}
// Index return custom index
func (e *EnvBinding) Index() map[string]string {
index := make(map[string]string)
func (e *EnvBinding) Index() map[string]interface{} {
index := make(map[string]interface{})
if e.Name != "" {
index["name"] = e.Name
}

View File

@@ -106,6 +106,11 @@ func (j *JSONStruct) JSON() string {
return string(b)
}
// Properties return the map
func (j *JSONStruct) Properties() map[string]interface{} {
return *j
}
// RawExtension Encoded as a RawExtension
func (j *JSONStruct) RawExtension() *runtime.RawExtension {
yamlByte, err := yaml.Marshal(j)

View File

@@ -61,8 +61,8 @@ func (p Pipeline) ShortTableName() string {
}
// Index return custom index
func (p Pipeline) Index() map[string]string {
var index = make(map[string]string)
func (p Pipeline) Index() map[string]interface{} {
var index = make(map[string]interface{})
if p.Project != "" {
index["project"] = p.Project
}
@@ -102,8 +102,8 @@ func (c *PipelineContext) PrimaryKey() string {
}
// Index return custom index
func (c *PipelineContext) Index() map[string]string {
index := make(map[string]string)
func (c *PipelineContext) Index() map[string]interface{} {
index := make(map[string]interface{})
if c.ProjectName != "" {
index["project_name"] = c.ProjectName
}

View File

@@ -54,8 +54,8 @@ func (p *Project) PrimaryKey() string {
}
// Index return custom index
func (p *Project) Index() map[string]string {
index := make(map[string]string)
func (p *Project) Index() map[string]interface{} {
index := make(map[string]interface{})
if p.Name != "" {
index["name"] = p.Name
}

View File

@@ -146,8 +146,8 @@ func (u *SystemInfo) PrimaryKey() string {
}
// Index return custom index
func (u *SystemInfo) Index() map[string]string {
index := make(map[string]string)
func (u *SystemInfo) Index() map[string]interface{} {
index := make(map[string]interface{})
if u.InstallID != "" {
index["installID"] = u.InstallID
}

View File

@@ -48,8 +48,8 @@ func (d *Target) PrimaryKey() string {
}
// Index return custom index
func (d *Target) Index() map[string]string {
index := make(map[string]string)
func (d *Target) Index() map[string]interface{} {
index := make(map[string]interface{})
if d.Name != "" {
index["name"] = d.Name
}

View File

@@ -67,8 +67,8 @@ func (u *User) PrimaryKey() string {
}
// Index return custom index
func (u *User) Index() map[string]string {
index := make(map[string]string)
func (u *User) Index() map[string]interface{} {
index := make(map[string]interface{})
if u.Name != "" {
index["name"] = u.Name
}
@@ -106,8 +106,8 @@ func (u *ProjectUser) PrimaryKey() string {
}
// Index return custom index
func (u *ProjectUser) Index() map[string]string {
index := make(map[string]string)
func (u *ProjectUser) Index() map[string]interface{} {
index := make(map[string]interface{})
if u.Username != "" {
index["username"] = u.Username
}
@@ -177,8 +177,8 @@ func (r *Role) PrimaryKey() string {
}
// Index return custom index
func (r *Role) Index() map[string]string {
index := make(map[string]string)
func (r *Role) Index() map[string]interface{} {
index := make(map[string]interface{})
if r.Name != "" {
index["name"] = r.Name
}
@@ -207,8 +207,8 @@ func (p *Permission) PrimaryKey() string {
}
// Index return custom index
func (p *Permission) Index() map[string]string {
index := make(map[string]string)
func (p *Permission) Index() map[string]interface{} {
index := make(map[string]interface{})
if p.Name != "" {
index["name"] = p.Name
}
@@ -250,8 +250,8 @@ func (p *PermissionTemplate) PrimaryKey() string {
}
// Index return custom index
func (p *PermissionTemplate) Index() map[string]string {
index := make(map[string]string)
func (p *PermissionTemplate) Index() map[string]interface{} {
index := make(map[string]interface{})
if p.Name != "" {
index["name"] = p.Name
}

View File

@@ -38,6 +38,8 @@ const (
// LabelSyncGeneration describes the generation synced from
LabelSyncGeneration = "ux.oam.dev/synced-generation"
// LabelSyncRevision describes the revision name synced from
LabelSyncRevision = "ux.oam.dev/synced-revision"
// LabelSyncNamespace describes the namespace synced from
LabelSyncNamespace = "ux.oam.dev/from-namespace"
)
@@ -56,26 +58,16 @@ const (
FromInner = "from-inner-system"
)
// DataStoreApp is a memory struct that describes the model of an application in datastore
type DataStoreApp struct {
AppMeta *Application
Env *Env
Eb *EnvBinding
Comps []*ApplicationComponent
Policies []*ApplicationPolicy
Workflow *Workflow
Targets []*Target
Record *WorkflowRecord
Revision *ApplicationRevision
}
const (
// DefaultInitName is default object name for initialization
DefaultInitName = "default"
// DefaultAddonProject is default addon projects
DefaultAddonProject = "addons"
// DefaultSystemProject is project name for the system
DefaultSystemProject = "system"
// DefaultSystemProjectAlias is project alias for the system
DefaultSystemProjectAlias = "System"
// DefaultInitNamespace is default namespace name for initialization
DefaultInitNamespace = "default"

View File

@@ -18,7 +18,6 @@ package model
import (
"fmt"
"strconv"
"time"
workflowv1alpha1 "github.com/kubevela/workflow/api/v1alpha1"
@@ -42,15 +41,16 @@ type Workflow struct {
Alias string `json:"alias"`
Description string `json:"description"`
// Workflow used by the default
Default *bool `json:"default"`
AppPrimaryKey string `json:"appPrimaryKey"`
EnvName string `json:"envName"`
Steps []WorkflowStep `json:"steps,omitempty"`
Default *bool `json:"default"`
AppPrimaryKey string `json:"appPrimaryKey"`
EnvName string `json:"envName"`
Mode workflowv1alpha1.WorkflowExecuteMode `json:"mode,omitempty"`
Steps []WorkflowStep `json:"steps,omitempty"`
}
// WorkflowStep defines how to execute a workflow step.
type WorkflowStep struct {
WorkflowStepBase `json:",inline"`
WorkflowStepBase `json:",inline" bson:",inline"`
SubSteps []WorkflowStepBase `json:"subSteps,omitempty"`
}
@@ -87,8 +87,8 @@ func (w *Workflow) PrimaryKey() string {
}
// Index return custom primary key
func (w *Workflow) Index() map[string]string {
index := make(map[string]string)
func (w *Workflow) Index() map[string]interface{} {
index := make(map[string]interface{})
if w.Name != "" {
index["name"] = w.Name
}
@@ -99,7 +99,7 @@ func (w *Workflow) Index() map[string]string {
index["envName"] = w.EnvName
}
if w.Default != nil {
index["default"] = strconv.FormatBool(*w.Default)
index["default"] = *w.Default
}
return index
@@ -116,14 +116,18 @@ type WorkflowRecord struct {
Name string `json:"name"`
Namespace string `json:"namespace"`
StartTime time.Time `json:"startTime,omitempty"`
EndTime time.Time `json:"endTime,omitempty"`
Finished string `json:"finished"`
Steps []WorkflowStepStatus `json:"steps,omitempty"`
Status string `json:"status"`
Message string `json:"message"`
Mode string `json:"mode"`
ContextValue map[string]string `json:"contextValue,omitempty"`
}
// WorkflowStepStatus is the workflow step status database model
type WorkflowStepStatus struct {
StepStatus `json:",inline"`
StepStatus `json:",inline" bson:",inline"`
SubStepsStatus []StepStatus `json:"subSteps,omitempty"`
}
@@ -156,8 +160,8 @@ func (w *WorkflowRecord) PrimaryKey() string {
}
// Index return custom primary key
func (w *WorkflowRecord) Index() map[string]string {
index := make(map[string]string)
func (w *WorkflowRecord) Index() map[string]interface{} {
index := make(map[string]interface{})
if w.Name != "" {
index["name"] = w.Name
}

View File

@@ -48,7 +48,7 @@ func CreateEnv(ctx context.Context, kubeClient client.Client, ds datastore.DataS
env.Namespace = env.Name
}
// create namespace at first
// Creating the namespace at first.
err = util.CreateOrUpdateNamespace(ctx, kubeClient, env.Namespace,
util.MergeOverrideLabels(map[string]string{
oam.LabelControlPlaneNamespaceUsage: oam.VelaNamespaceUsageEnv,

View File

@@ -67,6 +67,10 @@ func ListFullEnvBinding(ctx context.Context, ds datastore.DataStore, option EnvL
if err != nil {
return nil, err
}
workflows, err := ListWorkflowForApp(ctx, ds, option.AppPrimaryKey)
if err != nil {
return nil, err
}
var list []*apisv1.EnvBindingBase
for _, eb := range envBindings {
env, err := pickEnv(envs, eb.Name)
@@ -74,7 +78,11 @@ func ListFullEnvBinding(ctx context.Context, ds datastore.DataStore, option EnvL
klog.Errorf("envbinding invalid %s", err.Error())
continue
}
list = append(list, assembler.ConvertEnvBindingModelToBase(eb, env, targets))
workflow, err := pickEnvWorkflow(workflows, eb.Name)
if err != nil {
klog.Errorf("workflow invalid %s", err.Error())
}
list = append(list, assembler.ConvertEnvBindingModelToBase(eb, env, targets, workflow))
}
return list, nil
}
@@ -111,3 +119,12 @@ func pickEnv(envs []*model.Env, name string) (*model.Env, error) {
}
return nil, bcode.ErrEnvNotExisted
}
func pickEnvWorkflow(workflows []*model.Workflow, name string) (*model.Workflow, error) {
for _, w := range workflows {
if w.EnvName == name {
return w, nil
}
}
return nil, bcode.ErrWorkflowNotExist
}

View File

@@ -106,7 +106,7 @@ func NewDatastore(cfg datastore.Config) (ds datastore.DataStore, err error) {
return nil, fmt.Errorf("create mongodb datastore instance failure %w", err)
}
case "kubeapi":
ds, err = kubeapi.New(context.Background(), cfg)
ds, err = kubeapi.New(context.Background(), cfg, k8sClient)
if err != nil {
return nil, fmt.Errorf("create mongodb datastore instance failure %w", err)
}

View File

@@ -639,6 +639,38 @@ func GetWorkflowForApp(ctx context.Context, ds datastore.DataStore, app *model.A
return &workflow, nil
}
// GetWorkflowByEnv get the workflow by specified environment name.
func GetWorkflowByEnv(ctx context.Context, ds datastore.DataStore, app *model.Application, envName string) (*model.Workflow, error) {
var workflow = model.Workflow{
AppPrimaryKey: app.PrimaryKey(),
EnvName: envName,
}
res, err := ds.List(ctx, &workflow, nil)
if err != nil {
return nil, err
}
if len(res) > 0 {
return res[0].(*model.Workflow), nil
}
return nil, bcode.ErrWorkflowNotExist
}
// ListWorkflowForApp list all workflows of the application
func ListWorkflowForApp(ctx context.Context, ds datastore.DataStore, appPrimaryKey string) ([]*model.Workflow, error) {
var workflow = model.Workflow{
AppPrimaryKey: appPrimaryKey,
}
workflows, err := ds.List(ctx, &workflow, &datastore.ListOptions{SortBy: []datastore.SortOption{{Key: "createTime", Order: datastore.SortOrderDescending}}})
if err != nil {
return nil, err
}
var res []*model.Workflow
for _, w := range workflows {
res = append(res, w.(*model.Workflow))
}
return res, nil
}
// ConvertWorkflowName generate the workflow name
func ConvertWorkflowName(envName string) string {
return fmt.Sprintf("workflow-%s", envName)

View File

@@ -67,6 +67,7 @@ type AddonService interface {
DisableAddon(ctx context.Context, name string, force bool) error
ListEnabledAddon(ctx context.Context) ([]*apis.AddonBaseStatus, error)
UpdateAddon(ctx context.Context, name string, args apis.EnableAddonRequest) error
Init(ctx context.Context) error
}
// AddonImpl2AddonRes convert pkgaddon.UIData to the type apiserver need
@@ -116,44 +117,35 @@ func AddonImpl2AddonRes(impl *pkgaddon.UIData, config *rest.Config) (*apis.Detai
// NewAddonService returns an addon service
func NewAddonService(cacheTime time.Duration) AddonService {
config, err := clients.GetKubeConfig()
if err != nil {
panic(err)
}
kubecli, err := clients.GetKubeClient()
if err != nil {
panic(err)
}
dc, err := clients.GetDiscoveryClient()
if err != nil {
panic(err)
}
ds := pkgaddon.NewRegistryDataStore(kubecli)
cache := pkgaddon.NewCache(ds)
// TODO(@wonderflow): it's better to add a close channel here, but it should be fine as it's only invoke once in APIServer.
go cache.DiscoverAndRefreshLoop(cacheTime)
return &addonServiceImpl{
addonRegistryCache: cache,
addonRegistryDS: ds,
kubeClient: kubecli,
config: config,
apply: apply.NewAPIApplicator(kubecli),
mutex: new(sync.RWMutex),
discoveryClient: dc,
cacheTime: cacheTime,
mutex: new(sync.RWMutex),
discoveryClient: dc,
}
}
type addonServiceImpl struct {
cacheTime time.Duration
addonRegistryCache *pkgaddon.Cache
addonRegistryDS pkgaddon.RegistryDataStore
kubeClient client.Client
config *rest.Config
apply apply.Applicator
RegistryDS pkgaddon.RegistryDataStore `inject:"registryDatastore"`
KubeClient client.Client `inject:"kubeClient"`
KubeConfig *rest.Config `inject:"kubeConfig"`
Apply apply.Applicator `inject:"apply"`
discoveryClient *discovery.DiscoveryClient
mutex *sync.RWMutex
}
mutex *sync.RWMutex
func (u *addonServiceImpl) Init(ctx context.Context) error {
cache := pkgaddon.NewCache(u.RegistryDS)
// TODO(@wonderflow): it's better to add a close channel here, but it should be fine as it's only invoke once in APIServer.
go cache.DiscoverAndRefreshLoop(ctx, u.cacheTime)
u.addonRegistryCache = cache
return nil
}
// GetAddon will get addon information
@@ -161,7 +153,7 @@ func (u *addonServiceImpl) GetAddon(ctx context.Context, name string, registry s
var addon *pkgaddon.UIData
var err error
if registry == "" {
registries, err := u.addonRegistryDS.ListRegistries(ctx)
registries, err := u.RegistryDS.ListRegistries(ctx)
if err != nil {
return nil, err
}
@@ -175,7 +167,7 @@ func (u *addonServiceImpl) GetAddon(ctx context.Context, name string, registry s
}
}
} else {
addonRegistry, err := u.addonRegistryDS.GetRegistry(ctx, registry)
addonRegistry, err := u.RegistryDS.GetRegistry(ctx, registry)
if err != nil {
return nil, err
}
@@ -189,9 +181,9 @@ func (u *addonServiceImpl) GetAddon(ctx context.Context, name string, registry s
return nil, bcode.ErrAddonNotExist
}
addon.UISchema = renderAddonCustomUISchema(ctx, u.kubeClient, name, renderDefaultUISchema(addon.APISchema))
addon.UISchema = renderAddonCustomUISchema(ctx, u.KubeClient, name, renderDefaultUISchema(addon.APISchema))
a, err := AddonImpl2AddonRes(addon, u.config)
a, err := AddonImpl2AddonRes(addon, u.KubeConfig)
if err != nil {
return nil, err
}
@@ -199,12 +191,12 @@ func (u *addonServiceImpl) GetAddon(ctx context.Context, name string, registry s
}
func (u *addonServiceImpl) StatusAddon(ctx context.Context, name string) (*apis.AddonStatusResponse, error) {
status, err := pkgaddon.GetAddonStatus(ctx, u.kubeClient, name)
status, err := pkgaddon.GetAddonStatus(ctx, u.KubeClient, name)
if err != nil {
return nil, bcode.ErrGetAddonApplication
}
var allClusters []apis.NameAlias
clusters, err := multicluster.ListVirtualClusters(ctx, u.kubeClient)
clusters, err := multicluster.ListVirtualClusters(ctx, u.KubeClient)
if err != nil {
klog.Errorf("err while list all clusters: %v", err)
}
@@ -235,7 +227,7 @@ func (u *addonServiceImpl) StatusAddon(ctx context.Context, name string) (*apis.
}
var sec v1.Secret
err = u.kubeClient.Get(ctx, client.ObjectKey{
err = u.KubeClient.Get(ctx, client.ObjectKey{
Namespace: types.DefaultKubeVelaNS,
Name: addonutil.Addon2SecName(name),
}, &sec)
@@ -255,7 +247,7 @@ func (u *addonServiceImpl) StatusAddon(ctx context.Context, name string) (*apis.
func (u *addonServiceImpl) ListAddons(ctx context.Context, registry, query string) ([]*apis.DetailAddonResponse, error) {
var addons []*pkgaddon.UIData
rs, err := u.addonRegistryDS.ListRegistries(ctx)
rs, err := u.RegistryDS.ListRegistries(ctx)
if err != nil {
return nil, err
}
@@ -300,7 +292,7 @@ func (u *addonServiceImpl) ListAddons(ctx context.Context, registry, query strin
var addonResources []*apis.DetailAddonResponse
for _, a := range addons {
addonRes, err := AddonImpl2AddonRes(a, u.config)
addonRes, err := AddonImpl2AddonRes(a, u.KubeConfig)
if err != nil {
klog.Errorf("err while converting AddonImpl to DetailAddonResponse: %v", err)
continue
@@ -314,13 +306,13 @@ func (u *addonServiceImpl) ListAddons(ctx context.Context, registry, query strin
}
func (u *addonServiceImpl) DeleteAddonRegistry(ctx context.Context, name string) error {
return u.addonRegistryDS.DeleteRegistry(ctx, name)
return u.RegistryDS.DeleteRegistry(ctx, name)
}
func (u *addonServiceImpl) CreateAddonRegistry(ctx context.Context, req apis.CreateAddonRegistryRequest) (*apis.AddonRegistry, error) {
r := addonRegistryModelFromCreateAddonRegistryRequest(req)
err := u.addonRegistryDS.AddRegistry(ctx, r)
err := u.RegistryDS.AddRegistry(ctx, r)
if err != nil {
return nil, err
}
@@ -340,7 +332,7 @@ func convertAddonRegistry(r pkgaddon.Registry) *apis.AddonRegistry {
}
func (u *addonServiceImpl) GetAddonRegistry(ctx context.Context, name string) (*apis.AddonRegistry, error) {
r, err := u.addonRegistryDS.GetRegistry(ctx, name)
r, err := u.RegistryDS.GetRegistry(ctx, name)
if err != nil {
return nil, err
}
@@ -348,7 +340,7 @@ func (u *addonServiceImpl) GetAddonRegistry(ctx context.Context, name string) (*
}
func (u addonServiceImpl) UpdateAddonRegistry(ctx context.Context, name string, req apis.UpdateAddonRegistryRequest) (*apis.AddonRegistry, error) {
r, err := u.addonRegistryDS.GetRegistry(ctx, name)
r, err := u.RegistryDS.GetRegistry(ctx, name)
if err != nil {
return nil, bcode.ErrAddonRegistryNotExist
}
@@ -365,7 +357,7 @@ func (u addonServiceImpl) UpdateAddonRegistry(ctx context.Context, name string,
r.Gitlab = req.Gitlab
}
err = u.addonRegistryDS.UpdateRegistry(ctx, r)
err = u.RegistryDS.UpdateRegistry(ctx, r)
if err != nil {
return nil, err
}
@@ -376,7 +368,7 @@ func (u addonServiceImpl) UpdateAddonRegistry(ctx context.Context, name string,
func (u *addonServiceImpl) ListAddonRegistries(ctx context.Context) ([]*apis.AddonRegistry, error) {
var list []*apis.AddonRegistry
registries, err := u.addonRegistryDS.ListRegistries(ctx)
registries, err := u.RegistryDS.ListRegistries(ctx)
if err != nil {
// the storage configmap still not exist, don't return error add registry will create the configmap
if errors2.IsNotFound(err) {
@@ -396,7 +388,7 @@ func (u *addonServiceImpl) ListAddonRegistries(ctx context.Context) ([]*apis.Add
func (u *addonServiceImpl) EnableAddon(ctx context.Context, name string, args apis.EnableAddonRequest) error {
var err error
registries, err := u.addonRegistryDS.ListRegistries(ctx)
registries, err := u.RegistryDS.ListRegistries(ctx)
if err != nil {
return err
}
@@ -416,7 +408,7 @@ func (u *addonServiceImpl) EnableAddon(ctx context.Context, name string, args ap
continue
}
// TODO: response the additional info to velaux users
_, err = pkgaddon.EnableAddon(ctx, name, args.Version, u.kubeClient, u.discoveryClient, u.apply, u.config, r, args.Args, u.addonRegistryCache, pkgaddon.FilterDependencyRegistries(i, registries))
_, err = pkgaddon.EnableAddon(ctx, name, args.Version, u.KubeClient, u.discoveryClient, u.Apply, u.KubeConfig, r, args.Args, u.addonRegistryCache, pkgaddon.FilterDependencyRegistries(i, registries))
if err == nil {
return nil
}
@@ -441,7 +433,7 @@ func (u *addonServiceImpl) EnableAddon(ctx context.Context, name string, args ap
}
func (u *addonServiceImpl) DisableAddon(ctx context.Context, name string, force bool) error {
err := pkgaddon.DisableAddon(ctx, u.kubeClient, name, u.config, force)
err := pkgaddon.DisableAddon(ctx, u.KubeClient, name, u.KubeConfig, force)
if err != nil {
klog.Errorf("delete application fail: %s", err.Error())
return err
@@ -451,7 +443,7 @@ func (u *addonServiceImpl) DisableAddon(ctx context.Context, name string, force
func (u *addonServiceImpl) ListEnabledAddon(ctx context.Context) ([]*apis.AddonBaseStatus, error) {
apps := &v1beta1.ApplicationList{}
if err := u.kubeClient.List(ctx, apps, client.InNamespace(types.DefaultKubeVelaNS), client.HasLabels{oam.LabelAddonName}); err != nil {
if err := u.KubeClient.List(ctx, apps, client.InNamespace(types.DefaultKubeVelaNS), client.HasLabels{oam.LabelAddonName}); err != nil {
return nil, err
}
var response []*apis.AddonBaseStatus
@@ -470,10 +462,9 @@ func (u *addonServiceImpl) ListEnabledAddon(ctx context.Context) ([]*apis.AddonB
}
func (u *addonServiceImpl) UpdateAddon(ctx context.Context, name string, args apis.EnableAddonRequest) error {
var app v1beta1.Application
// check addon application whether exist
err := u.kubeClient.Get(ctx, client.ObjectKey{
err := u.KubeClient.Get(ctx, client.ObjectKey{
Namespace: types.DefaultKubeVelaNS,
Name: addonutil.Addon2AppName(name),
}, &app)
@@ -481,22 +472,20 @@ func (u *addonServiceImpl) UpdateAddon(ctx context.Context, name string, args ap
return err
}
registries, err := u.addonRegistryDS.ListRegistries(ctx)
registries, err := u.RegistryDS.ListRegistries(ctx)
if err != nil {
return err
}
for i, r := range registries {
// TODO: response the additional info to velaux users
_, err = pkgaddon.EnableAddon(ctx, name, args.Version, u.kubeClient, u.discoveryClient, u.apply, u.config, r, args.Args, u.addonRegistryCache, pkgaddon.FilterDependencyRegistries(i, registries))
_, err = pkgaddon.EnableAddon(ctx, name, args.Version, u.KubeClient, u.discoveryClient, u.Apply, u.KubeConfig, r, args.Args, u.addonRegistryCache, pkgaddon.FilterDependencyRegistries(i, registries))
if err == nil {
return nil
}
if errors.Is(err, pkgaddon.ErrNotExist) {
continue
}
// wrap this error with special bcode
if errors.As(err, &pkgaddon.VersionUnMatchError{}) {
return bcode.ErrAddonSystemVersionMismatch

View File

@@ -19,7 +19,6 @@ package service
import (
"bytes"
"context"
"encoding/json"
"errors"
"fmt"
"math/rand"
@@ -42,7 +41,7 @@ import (
velatypes "github.com/oam-dev/kubevela/apis/types"
"github.com/oam-dev/kubevela/pkg/apiserver/domain/model"
"github.com/oam-dev/kubevela/pkg/apiserver/domain/repository"
syncconvert "github.com/oam-dev/kubevela/pkg/apiserver/event/sync/convert"
"github.com/oam-dev/kubevela/pkg/apiserver/event/sync/convert"
"github.com/oam-dev/kubevela/pkg/apiserver/infrastructure/datastore"
assembler "github.com/oam-dev/kubevela/pkg/apiserver/interfaces/api/assembler/v1"
apisv1 "github.com/oam-dev/kubevela/pkg/apiserver/interfaces/api/dto/v1"
@@ -52,6 +51,7 @@ import (
"github.com/oam-dev/kubevela/pkg/oam"
"github.com/oam-dev/kubevela/pkg/oam/discoverymapper"
pkgUtils "github.com/oam-dev/kubevela/pkg/utils"
"github.com/oam-dev/kubevela/pkg/utils/app"
"github.com/oam-dev/kubevela/pkg/utils/apply"
commonutil "github.com/oam-dev/kubevela/pkg/utils/common"
)
@@ -90,6 +90,7 @@ type ApplicationService interface {
UpdateApplicationTrait(ctx context.Context, app *model.Application, component *model.ApplicationComponent, traitType string, req apisv1.UpdateApplicationTraitRequest) (*apisv1.ApplicationTrait, error)
ListRevisions(ctx context.Context, appName, envName, status string, page, pageSize int) (*apisv1.ListRevisionsResponse, error)
DetailRevision(ctx context.Context, appName, revisionName string) (*apisv1.DetailRevisionResponse, error)
RollbackWithRevision(ctx context.Context, app *model.Application, revisionName string) (*apisv1.ApplicationRollbackResponse, error)
Statistics(ctx context.Context, app *model.Application) (*apisv1.ApplicationStatisticsResponse, error)
ListRecords(ctx context.Context, appName string) (*apisv1.ListWorkflowRecordsResponse, error)
CompareApp(ctx context.Context, app *model.Application, compareReq apisv1.AppCompareReq) (*apisv1.AppCompareResponse, error)
@@ -538,7 +539,9 @@ func (c *applicationServiceImpl) ListRecords(ctx context.Context, appName string
AppPrimaryKey: appName,
Finished: model.UnFinished,
}
records, err := c.Store.List(ctx, &record, &datastore.ListOptions{})
records, err := c.Store.List(ctx, &record, &datastore.ListOptions{SortBy: []datastore.SortOption{
{Key: "createTime", Order: datastore.SortOrderDescending},
}})
if err != nil {
return nil, err
}
@@ -604,7 +607,8 @@ func (c *applicationServiceImpl) DetailComponent(ctx context.Context, app *model
return nil, err
}
var cd v1beta1.ComponentDefinition
if err := c.KubeClient.Get(ctx, types.NamespacedName{Name: component.Type, Namespace: velatypes.DefaultKubeVelaNS}, &cd); err != nil {
loadCtx := utils.WithProject(ctx, "")
if err := c.KubeClient.Get(loadCtx, types.NamespacedName{Name: component.Type, Namespace: velatypes.DefaultKubeVelaNS}, &cd); err != nil {
klog.Warningf("component definition %s get failure. %s", pkgUtils.Sanitize(component.Type), err.Error())
}
@@ -672,7 +676,7 @@ func (c *applicationServiceImpl) Deploy(ctx context.Context, app *model.Applicat
return nil, err
}
// step2: check and create deploy event
// step2: check and create application revision
if !req.Force {
var lastVersion = model.ApplicationRevision{
AppPrimaryKey: app.PrimaryKey(),
@@ -698,17 +702,18 @@ func (c *applicationServiceImpl) Deploy(ctx context.Context, app *model.Applicat
} else {
status = revision.Status
}
if status != model.RevisionStatusComplete && status != model.RevisionStatusTerminated {
klog.Warningf("last app revision can not complete %s/%s", list[0].(*model.ApplicationRevision).AppPrimaryKey, list[0].(*model.ApplicationRevision).Version)
if status != model.RevisionStatusComplete && status != model.RevisionStatusTerminated && status != model.RevisionStatusFail {
klog.Warningf("last app revision can not complete %s/%s,the current status is %s", list[0].(*model.ApplicationRevision).AppPrimaryKey, list[0].(*model.ApplicationRevision).Version, status)
return nil, bcode.ErrDeployConflict
}
}
}
var appRevision = &model.ApplicationRevision{
AppPrimaryKey: app.PrimaryKey(),
Version: version,
RevisionCRName: version,
AppPrimaryKey: app.PrimaryKey(),
Version: version,
// Setting it when syncing the workflow status
RevisionCRName: "",
ApplyAppConfig: string(configByte),
Status: model.RevisionStatusInit,
DeployUser: userName,
@@ -745,7 +750,8 @@ func (c *applicationServiceImpl) Deploy(ctx context.Context, app *model.Applicat
}
// step5: create workflow record
if err := c.WorkflowService.CreateWorkflowRecord(ctx, app, oamApp, workflow); err != nil {
record, err := c.WorkflowService.CreateWorkflowRecord(ctx, app, oamApp, workflow)
if err != nil {
klog.Warningf("create workflow record failure %s", err.Error())
}
@@ -764,9 +770,14 @@ func (c *applicationServiceImpl) Deploy(ctx context.Context, app *model.Applicat
klog.Warningf("failed to update app %s", err.Error())
}
return &apisv1.ApplicationDeployResponse{
res := &apisv1.ApplicationDeployResponse{
ApplicationRevisionBase: c.convertRevisionModelToBase(ctx, appRevision),
}, nil
}
if record != nil {
res.WorkflowRecord = assembler.ConvertFromRecordModel(record).WorkflowRecordBase
}
return res, nil
}
func (c *applicationServiceImpl) renderOAMApplication(ctx context.Context, appModel *model.Application, reqWorkflowName, envName, version string) (*v1beta1.Application, error) {
@@ -901,26 +912,41 @@ func (c *applicationServiceImpl) renderOAMApplication(ctx context.Context, appMo
app.Annotations[oam.AnnotationWorkflowName] = workflow.Name
var steps []workflowv1alpha1.WorkflowStep
for _, step := range workflow.Steps {
var workflowStep = workflowv1alpha1.WorkflowStep{
WorkflowStepBase: workflowv1alpha1.WorkflowStepBase{
Name: step.Name,
Type: step.Type,
Inputs: step.Inputs,
Outputs: step.Outputs,
},
workflowStep := workflowv1alpha1.WorkflowStep{
WorkflowStepBase: convertWorkflowModel2WorkflowSpec(step.WorkflowStepBase),
}
if step.Properties != nil {
workflowStep.Properties = step.Properties.RawExtension()
for _, subStep := range step.SubSteps {
workflowStep.SubSteps = append(workflowStep.SubSteps, convertWorkflowModel2WorkflowSpec(subStep))
}
steps = append(steps, workflowStep)
}
app.Spec.Workflow = &v1beta1.Workflow{
Steps: steps,
Mode: &workflow.Mode,
}
}
return app, nil
}
func convertWorkflowModel2WorkflowSpec(step model.WorkflowStepBase) workflowv1alpha1.WorkflowStepBase {
var workflowStep = workflowv1alpha1.WorkflowStepBase{
Name: step.Name,
Type: step.Type,
Inputs: step.Inputs,
Outputs: step.Outputs,
If: step.If,
Timeout: step.Timeout,
DependsOn: step.DependsOn,
Meta: &workflowv1alpha1.WorkflowStepMeta{
Alias: step.Alias,
},
}
if step.Properties != nil {
workflowStep.Properties = step.Properties.RawExtension()
}
return workflowStep
}
func (c *applicationServiceImpl) convertRevisionModelToBase(ctx context.Context, revision *model.ApplicationRevision) apisv1.ApplicationRevisionBase {
var deployUser *model.User
if revision.DeployUser != "" {
@@ -1047,7 +1073,8 @@ func (c *applicationServiceImpl) UpdateComponent(ctx context.Context, app *model
func (c *applicationServiceImpl) createComponent(ctx context.Context, app *model.Application, com apisv1.CreateComponentRequest, main bool) (*apisv1.ComponentBase, error) {
var cd v1beta1.ComponentDefinition
if err := c.KubeClient.Get(ctx, types.NamespacedName{Name: com.ComponentType, Namespace: velatypes.DefaultKubeVelaNS}, &cd); err != nil {
loadCtx := utils.WithProject(ctx, "")
if err := c.KubeClient.Get(loadCtx, types.NamespacedName{Name: com.ComponentType, Namespace: velatypes.DefaultKubeVelaNS}, &cd); err != nil {
klog.Warningf("component definition %s get failure. %s", pkgUtils.Sanitize(com.ComponentType), err.Error())
return nil, bcode.ErrComponentTypeNotSupport
}
@@ -1405,13 +1432,29 @@ func (c *applicationServiceImpl) CompareApp(ctx context.Context, appModel *model
var base, compareTarget *v1beta1.Application
var err error
var envNameByRevision string
getRunningApp := func() *v1beta1.Application {
var envName string
if compareReq.CompareLatestWithRunning != nil {
envName = compareReq.CompareLatestWithRunning.Env
}
if compareReq.CompareRevisionWithRunning != nil {
envName = envNameByRevision
}
if envName == "" {
return nil
}
app, err := c.GetApplicationCRInEnv(ctx, appModel, envName)
if err != nil {
klog.Errorf("failed to query the application CR %s", err.Error())
return nil
}
return app
}
switch {
case compareReq.CompareLatestWithRunning != nil:
base, err = c.renderOAMApplication(ctx, appModel, "", compareReq.CompareLatestWithRunning.Env, "")
if err != nil {
klog.Errorf("failed to build the latest application %s", err.Error())
break
}
base = getRunningApp()
case compareReq.CompareRevisionWithRunning != nil || compareReq.CompareRevisionWithLatest != nil:
var revision = ""
if compareReq.CompareRevisionWithRunning != nil {
@@ -1428,23 +1471,9 @@ func (c *applicationServiceImpl) CompareApp(ctx context.Context, appModel *model
}
switch {
case compareReq.CompareLatestWithRunning != nil || compareReq.CompareRevisionWithRunning != nil:
var envName string
if compareReq.CompareLatestWithRunning != nil {
envName = compareReq.CompareLatestWithRunning.Env
}
if compareReq.CompareRevisionWithRunning != nil {
envName = envNameByRevision
}
if envName == "" {
break
}
compareTarget, err = c.GetApplicationCRInEnv(ctx, appModel, envName)
if err != nil {
klog.Errorf("failed to query the application CR %s", err.Error())
break
}
case compareReq.CompareRevisionWithLatest != nil:
case compareReq.CompareRevisionWithRunning != nil:
compareTarget = getRunningApp()
case compareReq.CompareRevisionWithLatest != nil || compareReq.CompareLatestWithRunning != nil:
compareTarget, err = c.renderOAMApplication(ctx, appModel, "", envNameByRevision, "")
if err != nil {
klog.Errorf("failed to build the latest application %s", err.Error())
@@ -1513,6 +1542,13 @@ func (c *applicationServiceImpl) DryRunAppOrRevision(ctx context.Context, appMod
}
case "REVISION":
app, _, err = c.getAppModelFromRevision(ctx, appModel.Name, dryRunReq.Version)
originalApp := &v1beta1.Application{}
if err := c.KubeClient.Get(ctx, types.NamespacedName{
Name: app.Name,
Namespace: app.Namespace,
}, originalApp); err == nil {
app.ResourceVersion = originalApp.ResourceVersion
}
if err != nil {
return nil, err
}
@@ -1598,7 +1634,7 @@ func (c *applicationServiceImpl) resetApp(ctx context.Context, targetApp *v1beta
for _, comp := range targetComps {
// add or update new app's components from old app
if utils.StringsContain(readyToAdd, comp.Name) || utils.StringsContain(readyToUpdate, comp.Name) {
compModel, err := syncconvert.FromCRComponent(appPrimaryKey, comp)
compModel, err := convert.FromCRComponent(appPrimaryKey, comp)
if err != nil {
return &apisv1.AppResetResponse{}, bcode.ErrInvalidProperties
}
@@ -1623,6 +1659,71 @@ func (c *applicationServiceImpl) resetApp(ctx context.Context, targetApp *v1beta
return &apisv1.AppResetResponse{IsReset: true}, nil
}
func (c *applicationServiceImpl) RollbackWithRevision(ctx context.Context, application *model.Application, revisionVersion string) (*apisv1.ApplicationRollbackResponse, error) {
revision, err := c.DetailRevision(ctx, application.Name, revisionVersion)
if err != nil {
return nil, err
}
appCR, err := c.GetApplicationCRInEnv(ctx, application, revision.EnvName)
if err != nil {
return nil, err
}
var publishVersion = utils.GenerateVersion(revision.WorkflowName)
noRevision := false
var rollbackApplication *v1beta1.Application
if appCR != nil {
// The RevisionCRName is incorrect in the old version, ignore it.
if revision.RevisionCRName == revision.Version || revision.RevisionCRName == "" {
noRevision = true
} else {
_, appCR, err := app.RollbackApplicationWithRevision(ctx, c.KubeClient, appCR.Name, appCR.Namespace, revision.RevisionCRName, publishVersion)
if err != nil {
switch {
case errors.Is(err, app.ErrNotMatchRevision):
noRevision = true
case errors.Is(err, app.ErrRevisionNotChange):
return nil, bcode.ErrApplicationRevisionConflict
default:
return nil, err
}
}
rollbackApplication = appCR
}
}
// Rollback by the local revision
if appCR == nil || noRevision {
rollBackApp := &v1beta1.Application{}
if err := yaml.Unmarshal([]byte(revision.ApplyAppConfig), rollBackApp); err != nil {
return nil, err
}
oam.SetPublishVersion(rollBackApp, publishVersion)
if appCR != nil {
rollBackApp.ResourceVersion = appCR.ResourceVersion
} else {
rollBackApp.ResourceVersion = ""
}
err = c.Apply.Apply(ctx, rollBackApp)
if err != nil {
klog.Errorf("rollback the app %s failure %s", application.PrimaryKey(), err.Error())
return nil, err
}
rollbackApplication = rollBackApp
}
work, _, err := convert.FromCRWorkflow(ctx, c.KubeClient, application.PrimaryKey(), rollbackApplication, revision.EnvName)
if err != nil {
return nil, err
}
record, err := c.WorkflowService.CreateWorkflowRecord(ctx, application, rollbackApplication, &work)
if err != nil {
return nil, fmt.Errorf("create workflow record failure %w", err)
}
return &apisv1.ApplicationRollbackResponse{
WorkflowRecord: assembler.ConvertFromRecordModel(record).WorkflowRecordBase,
}, nil
}
func dryRunApplication(ctx context.Context, c commonutil.Args, app *v1beta1.Application) (bytes.Buffer, error) {
var buff = bytes.Buffer{}
if _, err := fmt.Fprintf(&buff, "---\n# Application(%s) \n---\n\n", app.Name); err != nil {
@@ -1666,9 +1767,7 @@ func dryRunApplication(ctx context.Context, c commonutil.Args, app *v1beta1.Appl
// ignore the workflow spec
func ignoreSomeParams(o *v1beta1.Application) {
var defaultApplication = v1beta1.Application{}
// only compare the spec without the workflow
defaultApplication.Spec = o.Spec
defaultApplication.Spec.Workflow = nil
defaultApplication.Name = o.Name
defaultApplication.Namespace = o.Namespace
@@ -1800,11 +1899,7 @@ func (c *applicationServiceImpl) handlePolicyBindingWorkflowStep(ctx context.Con
}
if added || deleted {
properties["policies"] = policies
pStr, err := json.Marshal(properties)
if err != nil {
return err
}
w.Steps[i].Properties = string(pStr)
w.Steps[i].Properties = properties
needUpdate = true
}
}

View File

@@ -26,6 +26,8 @@ import (
"github.com/google/go-cmp/cmp"
"github.com/google/go-cmp/cmp/cmpopts"
workflowv1alpha1 "github.com/kubevela/workflow/api/v1alpha1"
wfTypes "github.com/kubevela/workflow/pkg/types"
. "github.com/onsi/ginkgo"
. "github.com/onsi/gomega"
corev1 "k8s.io/api/core/v1"
@@ -530,8 +532,10 @@ var _ = Describe("Test application service function", func() {
})
Expect(err).Should(BeNil())
Expect(cmp.Diff(compareResponse.IsDiff, true)).Should(BeEmpty())
Expect(cmp.Diff(compareResponse.TargetAppYAML, "")).Should(BeEmpty())
Expect(cmp.Diff(compareResponse.BaseAppYAML, "")).ShouldNot(BeEmpty())
// The target represents the latest config
Expect(cmp.Diff(compareResponse.TargetAppYAML, "")).ShouldNot(BeEmpty())
// The base represents the running config
Expect(cmp.Diff(compareResponse.BaseAppYAML, "")).Should(BeEmpty())
By("compare when app's env add target, should return false")
_, err = targetService.CreateTarget(context.TODO(), v1.CreateTargetRequest{Name: "dev-target1", Project: appModel.Project, Cluster: &v1.ClusterTarget{ClusterName: "local", Namespace: "dev-target1"}})
@@ -850,21 +854,21 @@ var _ = Describe("Test apiserver policy rest api", func() {
WorkflowStepBase: v1.WorkflowStepBase{
Name: "default",
Type: "deploy",
Properties: `{"policies":["local"]}`,
Properties: map[string]interface{}{"policies": []string{"local"}},
},
},
{
WorkflowStepBase: v1.WorkflowStepBase{
Name: "suspend",
Type: "suspend",
Properties: `{"duration": "10m"}`,
Properties: map[string]interface{}{"duration": "10m"},
},
},
{
WorkflowStepBase: v1.WorkflowStepBase{
Name: "second",
Type: "deploy",
Properties: `{"policies":["cluster1"]}`,
Properties: map[string]interface{}{"policies": []string{"cluster1"}},
},
},
},
@@ -880,7 +884,7 @@ var _ = Describe("Test apiserver policy rest api", func() {
WorkflowStepBase: v1.WorkflowStepBase{
Name: "second",
Type: "deploy",
Properties: `{"policies":["cluster3"]}`,
Properties: map[string]interface{}{"policies": []string{"cluster3"}},
},
},
},
@@ -1015,11 +1019,32 @@ func createTestSuspendApp(ctx context.Context, appName, envName, revisionVersion
Traits: []common.ApplicationTrait{},
Scopes: map[string]string{},
}},
Workflow: &v1beta1.Workflow{
Steps: []workflowv1alpha1.WorkflowStep{
{
WorkflowStepBase: workflowv1alpha1.WorkflowStepBase{
Type: wfTypes.WorkflowStepTypeSuspend,
Name: "first",
},
},
},
},
},
Status: common.AppStatus{
Workflow: &common.WorkflowStatus{
AppRevision: recordName,
Suspend: true,
Phase: workflowv1alpha1.WorkflowStateSuspending,
Steps: []workflowv1alpha1.WorkflowStepStatus{
{
StepStatus: workflowv1alpha1.StepStatus{
Type: wfTypes.WorkflowStepTypeSuspend,
Name: "first",
ID: "first",
Phase: workflowv1alpha1.WorkflowStepPhaseRunning,
},
},
},
},
},
}

View File

@@ -37,7 +37,6 @@ import (
"github.com/oam-dev/terraform-controller/api/types"
"github.com/oam-dev/terraform-controller/api/v1beta1"
velatypes "github.com/oam-dev/kubevela/apis/types"
"github.com/oam-dev/kubevela/pkg/apiserver/domain/model"
"github.com/oam-dev/kubevela/pkg/apiserver/infrastructure/clients"
"github.com/oam-dev/kubevela/pkg/apiserver/infrastructure/datastore"
@@ -207,7 +206,7 @@ func joinClusterByKubeConfigString(ctx context.Context, k8sClient client.Client,
defer func() {
_ = os.Remove(tmpFileName)
}()
clusterConfig, err := multicluster.JoinClusterByKubeConfig(ctx, k8sClient, tmpFileName, clusterName, multicluster.JoinClusterCreateNamespaceOption(velatypes.DefaultKubeVelaNS))
clusterConfig, err := multicluster.JoinClusterByKubeConfig(ctx, k8sClient, tmpFileName, clusterName, multicluster.JoinClusterCreateNamespaceOption(""))
if err != nil {
if errors.Is(err, multicluster.ErrClusterExists) {
return "", bcode.ErrClusterExistsInKubernetes

View File

@@ -22,15 +22,14 @@ import (
"sort"
"github.com/pkg/errors"
apierrors "k8s.io/apimachinery/pkg/api/errors"
"k8s.io/klog/v2"
"sigs.k8s.io/controller-runtime/pkg/client"
"github.com/oam-dev/kubevela/apis/types"
apis "github.com/oam-dev/kubevela/pkg/apiserver/interfaces/api/dto/v1"
"github.com/oam-dev/kubevela/pkg/apiserver/utils"
"github.com/oam-dev/kubevela/pkg/apiserver/utils/bcode"
"github.com/oam-dev/kubevela/pkg/config"
"github.com/oam-dev/kubevela/pkg/utils"
"github.com/oam-dev/kubevela/pkg/utils/apply"
)
@@ -62,7 +61,8 @@ type configServiceImpl struct {
// ListTemplates list the config templates
func (u *configServiceImpl) ListTemplates(ctx context.Context, project, scope string) ([]*apis.ConfigTemplate, error) {
queryTemplates, err := u.Factory.ListTemplates(ctx, types.DefaultKubeVelaNS, scope)
listCtx := utils.WithProject(ctx, "")
queryTemplates, err := u.Factory.ListTemplates(listCtx, types.DefaultKubeVelaNS, scope)
if err != nil {
return nil, err
}
@@ -100,7 +100,8 @@ func (u *configServiceImpl) GetTemplate(ctx context.Context, tem config.Namespac
if tem.Namespace == "" {
tem.Namespace = types.DefaultKubeVelaNS
}
template, err := u.Factory.LoadTemplate(ctx, tem.Name, tem.Namespace)
getCtx := utils.WithProject(ctx, "")
template, err := u.Factory.LoadTemplate(getCtx, tem.Name, tem.Namespace)
if err != nil {
if errors.Is(err, config.ErrTemplateNotFound) {
return nil, bcode.ErrTemplateNotFound
@@ -133,9 +134,14 @@ func (u *configServiceImpl) CreateConfig(ctx context.Context, project string, re
return nil, err
}
ns = pro.GetNamespace()
if err := utils.CreateNamespace(ctx, u.KubeClient, ns); err != nil && !apierrors.IsAlreadyExists(err) {
return nil, err
}
}
exist, err := u.Factory.IsExist(ctx, ns, req.Name)
if err != nil {
klog.Errorf("check config name is exist failure %s", err.Error())
return nil, bcode.ErrConfigExist
}
if exist {
return nil, bcode.ErrConfigExist
}
var properties = make(map[string]interface{})
if err := json.Unmarshal([]byte(req.Properties), &properties); err != nil {
@@ -156,9 +162,6 @@ func (u *configServiceImpl) CreateConfig(ctx context.Context, project string, re
return nil, err
}
if err := u.Factory.CreateOrUpdateConfig(ctx, configItem, ns); err != nil {
if errors.Is(err, config.ErrConfigExist) {
return nil, bcode.ErrConfigExist
}
return nil, err
}
return convertConfig(project, *configItem), nil
@@ -196,9 +199,12 @@ func (u *configServiceImpl) UpdateConfig(ctx context.Context, project string, na
return nil, err
}
if err := u.Factory.CreateOrUpdateConfig(ctx, configItem, ns); err != nil {
if errors.Is(err, config.ErrConfigExist) {
if errors.Is(err, config.ErrChangeTemplate) {
return nil, bcode.ErrChangeTemplate
}
if errors.Is(err, config.ErrChangeSecretType) {
return nil, bcode.ErrChangeSecretType
}
return nil, err
}
return convertConfig(project, *configItem), nil
@@ -210,6 +216,7 @@ func (u *configServiceImpl) ListConfigs(ctx context.Context, project string, tem
var list []*apis.Config
scope := ""
var projectNamespace string
listCtx := utils.WithProject(ctx, "")
if project != "" {
scope = "project"
pro, err := u.ProjectService.GetProject(ctx, project)
@@ -218,7 +225,7 @@ func (u *configServiceImpl) ListConfigs(ctx context.Context, project string, tem
}
projectNamespace = pro.GetNamespace()
// query the configs belong to the project scope from the system namespace
configs, err := u.Factory.ListConfigs(ctx, pro.GetNamespace(), template, "", true)
configs, err := u.Factory.ListConfigs(listCtx, pro.GetNamespace(), template, "", true)
if err != nil {
return nil, err
}
@@ -227,7 +234,7 @@ func (u *configServiceImpl) ListConfigs(ctx context.Context, project string, tem
}
}
configs, err := u.Factory.ListConfigs(ctx, types.DefaultKubeVelaNS, template, scope, true)
configs, err := u.Factory.ListConfigs(listCtx, types.DefaultKubeVelaNS, template, scope, true)
if err != nil {
return nil, err
}

View File

@@ -239,7 +239,9 @@ func (p *envServiceImpl) UpdateEnv(ctx context.Context, name string, req apisv1.
return nil, err
}
if err := managePrivilegesForEnvironment(ctx, p.KubeClient, env, false); err != nil {
// Updating the role and role binding can't use the login user permissions.
updateRoleCtx := utils.WithProject(ctx, "")
if err := managePrivilegesForEnvironment(updateRoleCtx, p.KubeClient, env, false); err != nil {
return nil, err
}
@@ -289,12 +291,14 @@ func (p *envServiceImpl) CreateEnv(ctx context.Context, req apisv1.CreateEnvRequ
}
}
err = repository.CreateEnv(ctx, p.KubeClient, p.Store, newEnv)
// Creating the namespace can't use the login user permissions.
createNamespaceCtx := utils.WithProject(ctx, "")
err = repository.CreateEnv(createNamespaceCtx, p.KubeClient, p.Store, newEnv)
if err != nil {
return nil, err
}
if err := managePrivilegesForEnvironment(ctx, p.KubeClient, newEnv, false); err != nil {
if err := managePrivilegesForEnvironment(createNamespaceCtx, p.KubeClient, newEnv, false); err != nil {
return nil, err
}

View File

@@ -269,8 +269,12 @@ func (e *envBindingServiceImpl) DetailEnvBinding(ctx context.Context, app *model
if err != nil {
return nil, err
}
workflow, err := repository.GetWorkflowByEnv(ctx, e.Store, app, env.Name)
if err != nil {
klog.Warningf("failed to find the workflow for the app %s in environment %s:%s", app.Name, env.Name, err.Error())
}
return &apisv1.DetailEnvBindingResponse{
EnvBindingBase: *assembler.ConvertEnvBindingModelToBase(envBinding, env, targets),
EnvBindingBase: *assembler.ConvertEnvBindingModelToBase(envBinding, env, targets, workflow),
}, nil
}

View File

@@ -90,6 +90,8 @@ type PipelineRunService interface {
GetPipelineRunOutput(ctx context.Context, meta apis.PipelineRun, step string) (apis.GetPipelineRunOutputResponse, error)
GetPipelineRunInput(ctx context.Context, meta apis.PipelineRun, step string) (apis.GetPipelineRunInputResponse, error)
GetPipelineRunLog(ctx context.Context, meta apis.PipelineRun, step string) (apis.GetPipelineRunLogResponse, error)
ResumePipelineRun(ctx context.Context, meta apis.PipelineRunMeta) error
TerminatePipelineRun(ctx context.Context, meta apis.PipelineRunMeta) error
}
type pipelineRunServiceImpl struct {
@@ -173,6 +175,7 @@ func (p pipelineServiceImpl) ListPipelines(ctx context.Context, req apis.ListPip
if err != nil {
return nil, err
}
var availableProjectNames []string
var projectMap = make(map[string]model.Project, len(projects))
for _, project := range projects {
@@ -184,13 +187,23 @@ func (p pipelineServiceImpl) ListPipelines(ctx context.Context, req apis.ListPip
if len(availableProjectNames) == 0 {
return &apis.ListPipelineResponse{}, nil
}
queryProjects := req.Projects
if len(req.Projects) > 0 {
if !pkgutils.SliceIncludeSlice(availableProjectNames, req.Projects) {
return &apis.ListPipelineResponse{}, nil
}
}
if len(req.Projects) == 0 {
queryProjects = availableProjectNames
}
pp := model.Pipeline{}
pipelines, err := p.Store.List(ctx, &pp, &datastore.ListOptions{
FilterOptions: datastore.FilterOptions{
In: []datastore.InQueryOption{
{
Key: "project",
Values: availableProjectNames,
Values: queryProjects,
},
},
},
@@ -1287,13 +1300,90 @@ func (p pipelineRunServiceImpl) terminatePipelineRun(ctx context.Context, run *v
return err
}
return nil
}
func (p pipelineRunServiceImpl) ResumePipelineRun(ctx context.Context, meta apis.PipelineRunMeta) error {
project := ctx.Value(&apis.CtxKeyProject).(*model.Project)
run := v1alpha1.WorkflowRun{}
if err := p.KubeClient.Get(ctx, types.NamespacedName{
Namespace: project.GetNamespace(),
Name: meta.PipelineRunName,
}, &run); err != nil {
return err
}
if run.Status.Terminated || run.Status.Finished {
return bcode.ErrPipelineRunFinished
}
run.Status.Suspend = false
steps := run.Status.Steps
for i, step := range steps {
if step.Type == wfTypes.WorkflowStepTypeSuspend && step.Phase == v1alpha1.WorkflowStepPhaseRunning {
steps[i].Phase = v1alpha1.WorkflowStepPhaseSucceeded
}
for j, sub := range step.SubStepsStatus {
if sub.Type == wfTypes.WorkflowStepTypeSuspend && sub.Phase == v1alpha1.WorkflowStepPhaseRunning {
steps[i].SubStepsStatus[j].Phase = v1alpha1.WorkflowStepPhaseSucceeded
}
}
}
if err := p.KubeClient.Status().Patch(ctx, &run, client.Merge); err != nil {
return err
}
return nil
}
func (p pipelineRunServiceImpl) TerminatePipelineRun(ctx context.Context, meta apis.PipelineRunMeta) error {
project := ctx.Value(&apis.CtxKeyProject).(*model.Project)
run := v1alpha1.WorkflowRun{}
if err := p.KubeClient.Get(ctx, types.NamespacedName{
Namespace: project.GetNamespace(),
Name: meta.PipelineRunName,
}, &run); err != nil {
return err
}
if run.Status.Terminated || run.Status.Finished {
return bcode.ErrPipelineRunFinished
}
// set the pipeline run terminated to true
run.Status.Terminated = true
// set the pipeline run suspend to false
run.Status.Suspend = false
steps := run.Status.Steps
for i, step := range steps {
switch step.Phase {
case v1alpha1.WorkflowStepPhaseFailed:
if step.Reason != wfTypes.StatusReasonFailedAfterRetries && step.Reason != wfTypes.StatusReasonTimeout {
steps[i].Reason = wfTypes.StatusReasonTerminate
}
case v1alpha1.WorkflowStepPhaseRunning:
steps[i].Phase = v1alpha1.WorkflowStepPhaseFailed
steps[i].Reason = wfTypes.StatusReasonTerminate
default:
}
for j, sub := range step.SubStepsStatus {
switch sub.Phase {
case v1alpha1.WorkflowStepPhaseFailed:
if sub.Reason != wfTypes.StatusReasonFailedAfterRetries && sub.Reason != wfTypes.StatusReasonTimeout {
steps[i].SubStepsStatus[j].Reason = wfTypes.StatusReasonTerminate
}
case v1alpha1.WorkflowStepPhaseRunning:
steps[i].SubStepsStatus[j].Phase = v1alpha1.WorkflowStepPhaseFailed
steps[i].SubStepsStatus[j].Reason = wfTypes.StatusReasonTerminate
default:
}
}
}
if err := p.KubeClient.Status().Patch(ctx, &run, client.Merge); err != nil {
return err
}
return nil
}
func checkPipelineSpec(spec model.WorkflowSpec) error {
if len(spec.Steps) == 0 {
return bcode.ErrNoSteps
}
return nil
}

View File

@@ -113,6 +113,13 @@ var _ = Describe("Test pipeline service functions", func() {
Expect(pipelines.Total).Should(Equal(1))
Expect(len(pipelines.Pipelines)).Should(Equal(1))
Expect(pipelines.Pipelines[0].Info).ShouldNot(BeNil())
pipelinesFilterByProject, err := pipelineService.ListPipelines(ctx, apisv1.ListPipelineRequest{
Detailed: true,
Projects: []string{"not-found"},
})
Expect(err).Should(BeNil())
Expect(len(pipelinesFilterByProject.Pipelines)).Should(Equal(0))
})
It("get pipeline contexts", func() {

View File

@@ -17,12 +17,14 @@ limitations under the License.
package service
import (
"bytes"
"context"
"errors"
"fmt"
terraformapi "github.com/oam-dev/terraform-controller/api/v1beta1"
apierrors "k8s.io/apimachinery/pkg/api/errors"
"k8s.io/apimachinery/pkg/api/meta"
"k8s.io/klog/v2"
"sigs.k8s.io/controller-runtime/pkg/client"
@@ -30,7 +32,9 @@ import (
"github.com/oam-dev/kubevela/pkg/apiserver/domain/model"
"github.com/oam-dev/kubevela/pkg/apiserver/infrastructure/datastore"
apisv1 "github.com/oam-dev/kubevela/pkg/apiserver/interfaces/api/dto/v1"
apiutils "github.com/oam-dev/kubevela/pkg/apiserver/utils"
"github.com/oam-dev/kubevela/pkg/apiserver/utils/bcode"
"github.com/oam-dev/kubevela/pkg/auth"
"github.com/oam-dev/kubevela/pkg/multicluster"
"github.com/oam-dev/kubevela/pkg/utils"
)
@@ -290,6 +294,10 @@ func (p *projectServiceImpl) DeleteProject(ctx context.Context, name string) err
if err := p.Store.Delete(ctx, &model.Project{Name: name}); err != nil {
return err
}
if err := managePrivilegesForProject(ctx, p.K8sClient, &model.Project{Name: name}, true); err != nil {
return err
}
return nil
}
@@ -317,22 +325,30 @@ func (p *projectServiceImpl) CreateProject(ctx context.Context, req apisv1.Creat
}
}
if err := utils.CreateNamespace(ctx, p.K8sClient, req.Name); err != nil && !apierrors.IsAlreadyExists(err) {
namespace := req.Namespace
if namespace == "" {
namespace = req.Name
}
createCtx := apiutils.WithProject(ctx, "")
if err := utils.CreateNamespace(createCtx, p.K8sClient, namespace); err != nil && !apierrors.IsAlreadyExists(err) {
return nil, bcode.ErrProjectNamespaceFail
}
newProject := &model.Project{
Name: req.Name,
Description: req.Description,
Alias: req.Alias,
Owner: owner,
Namespace: req.Name,
Namespace: namespace,
}
if err := p.Store.Add(ctx, newProject); err != nil {
return nil, err
}
if err := managePrivilegesForProject(createCtx, p.K8sClient, newProject, false); err != nil {
return nil, err
}
if err := p.RbacService.SyncDefaultRoleAndUsersForProject(ctx, newProject); err != nil {
klog.Errorf("fail to sync the default role and users for the project: %s", err.Error())
}
@@ -340,6 +356,22 @@ func (p *projectServiceImpl) CreateProject(ctx context.Context, req apisv1.Creat
return ConvertProjectModel2Base(newProject, user), nil
}
// managePrivilegesForProject grant or revoke privileges for project
func managePrivilegesForProject(ctx context.Context, cli client.Client, project *model.Project, revoke bool) error {
p := &auth.ApplicationPrivilege{Cluster: types.ClusterLocalName, Namespace: project.Namespace}
identity := &auth.Identity{Groups: []string{apiutils.KubeVelaProjectGroupPrefix + project.Name}}
writer := &bytes.Buffer{}
f, msg := auth.GrantPrivileges, "GrantPrivileges"
if revoke {
f, msg = auth.RevokePrivileges, "RevokePrivileges"
}
if err := f(ctx, cli, []auth.PrivilegeDescription{p}, identity, writer); err != nil {
return err
}
klog.Infof("%s: %s", msg, writer.String())
return nil
}
// UpdateProject update project
func (p *projectServiceImpl) UpdateProject(ctx context.Context, projectName string, req apisv1.UpdateProjectRequest) (*apisv1.ProjectBase, error) {
project, err := p.GetProject(ctx, projectName)
@@ -368,6 +400,9 @@ func (p *projectServiceImpl) UpdateProject(ctx context.Context, projectName stri
if err != nil {
return nil, err
}
if err := managePrivilegesForProject(ctx, p.K8sClient, project, false); err != nil {
return nil, err
}
return ConvertProjectModel2Base(project, user), nil
}
@@ -505,7 +540,11 @@ func (p *projectServiceImpl) UpdateProjectUser(ctx context.Context, projectName
func (p *projectServiceImpl) ListTerraformProviders(ctx context.Context, projectName string) ([]*apisv1.TerraformProvider, error) {
l := &terraformapi.ProviderList{}
if err := p.K8sClient.List(ctx, l, client.InNamespace(types.ProviderNamespace)); err != nil {
listCtx := apiutils.WithProject(ctx, "")
if err := p.K8sClient.List(listCtx, l, client.InNamespace(types.ProviderNamespace)); err != nil {
if meta.IsNoMatchError(err) {
return []*apisv1.TerraformProvider{}, nil
}
return nil, err
}
var res []*apisv1.TerraformProvider

View File

@@ -101,7 +101,7 @@ var defaultProjectPermissionTemplate = []*model.PermissionTemplate{
Name: "pipeline-management",
Alias: "Pipeline Management",
Resources: []string{
"project:{projectName}/pipeline:*",
"project:{projectName}/pipeline:*/*",
},
Actions: []string{"*"},
Effect: "Allow",

View File

@@ -49,7 +49,7 @@ func InitServiceBean(c config.Config) []interface{} {
pipelineService := NewPipelineService(c.WorkflowVersion)
pipelineRunService := NewPipelineRunService()
contextService := NewContextService()
needInitData = []DataInit{clusterService, userService, rbacService, projectService, targetService, systemInfoService}
needInitData = []DataInit{clusterService, userService, rbacService, projectService, targetService, systemInfoService, addonService}
return []interface{}{
clusterService, rbacService, projectService, envService, targetService, workflowService, oamApplicationService,
velaQLService, definitionService, addonService, envBindingService, systemInfoService, helmService, userService,

View File

@@ -104,7 +104,7 @@ func NewDatastore(cfg datastore.Config) (ds datastore.DataStore, err error) {
return nil, fmt.Errorf("create mongodb datastore instance failure %w", err)
}
case "kubeapi":
ds, err = kubeapi.New(context.Background(), cfg)
ds, err = kubeapi.New(context.Background(), cfg, k8sClient)
if err != nil {
return nil, fmt.Errorf("create mongodb datastore instance failure %w", err)
}

View File

@@ -148,10 +148,11 @@ func (dt *targetServiceImpl) CreateTarget(ctx context.Context, req apisv1.Create
if req.Cluster == nil {
req.Cluster = &apisv1.ClusterTarget{ClusterName: multicluster.ClusterLocalName, Namespace: req.Name}
}
if err := repository.CreateTargetNamespace(ctx, dt.K8sClient, req.Cluster.ClusterName, req.Cluster.Namespace, req.Name); err != nil {
createTargetCtx := utils.WithProject(ctx, "")
if err := repository.CreateTargetNamespace(createTargetCtx, dt.K8sClient, req.Cluster.ClusterName, req.Cluster.Namespace, req.Name); err != nil {
return nil, err
}
if err := managePrivilegesForTarget(ctx, dt.K8sClient, &target, false); err != nil {
if err := managePrivilegesForTarget(createTargetCtx, dt.K8sClient, &target, false); err != nil {
return nil, err
}
err := repository.CreateTarget(ctx, dt.Store, &target)
@@ -167,7 +168,8 @@ func (dt *targetServiceImpl) UpdateTarget(ctx context.Context, target *model.Tar
return nil, err
}
// Compatible with historical data, if the existing Target has not been authorized, perform an update action.
if err := managePrivilegesForTarget(ctx, dt.K8sClient, targetModel, false); err != nil {
updateCtx := utils.WithProject(ctx, "")
if err := managePrivilegesForTarget(updateCtx, dt.K8sClient, targetModel, false); err != nil {
return nil, err
}
return dt.DetailTarget(ctx, targetModel)

View File

@@ -18,8 +18,6 @@ package service
import (
"fmt"
"encoding/json"
)
// guaranteePolicyExist check the slice whether contain the target policy, if not put it in.
@@ -51,29 +49,24 @@ func guaranteePolicyNotExist(c []string, policy string) ([]string, bool) {
// extractPolicyListAndProperty can extract policy from string-format properties, and return
// map-format properties in order to further update operation.
func extractPolicyListAndProperty(property string) ([]string, map[string]interface{}, error) {
func extractPolicyListAndProperty(property map[string]interface{}) ([]string, map[string]interface{}, error) {
if len(property) == 0 {
return nil, nil, nil
}
content := map[string]interface{}{}
err := json.Unmarshal([]byte(property), &content)
if err != nil {
return nil, nil, err
}
policies := content["policies"]
policies := property["policies"]
if policies == nil {
return nil, content, nil
return nil, property, nil
}
list, ok := policies.([]interface{})
if !ok {
return nil, nil, fmt.Errorf("the policies incorrrect")
return nil, nil, fmt.Errorf("the policies incorrect")
}
if len(list) == 0 {
return nil, content, nil
return nil, property, nil
}
res := []string{}
for _, i := range list {
res = append(res, i.(string))
}
return res, content, nil
return res, property, nil
}

View File

@@ -17,6 +17,7 @@ limitations under the License.
package service
import (
"encoding/json"
"testing"
"gotest.tools/assert"
@@ -209,7 +210,7 @@ func TestExtractPolicyListAndProperty(t *testing.T) {
}{noError: false},
},
{
input: ``,
input: `{}`,
res: struct {
policies []string
properties map[string]interface{}
@@ -218,7 +219,15 @@ func TestExtractPolicyListAndProperty(t *testing.T) {
},
}
for _, testCase := range testCases {
policy, properties, err := extractPolicyListAndProperty(testCase.input)
var in = map[string]interface{}{}
err := json.Unmarshal([]byte(testCase.input), &in)
if testCase.res.noError {
assert.NilError(t, err)
} else {
assert.Equal(t, err != nil, true)
continue
}
policy, properties, err := extractPolicyListAndProperty(in)
if testCase.res.noError {
assert.NilError(t, err)
} else {

View File

@@ -18,20 +18,27 @@ package service
import (
"context"
"encoding/json"
"errors"
"fmt"
"io"
"strconv"
"strings"
workflowv1alpha1 "github.com/kubevela/workflow/api/v1alpha1"
"helm.sh/helm/v3/pkg/time"
corev1 "k8s.io/api/core/v1"
apierrors "k8s.io/apimachinery/pkg/api/errors"
"k8s.io/apimachinery/pkg/types"
"k8s.io/client-go/rest"
"k8s.io/klog/v2"
"sigs.k8s.io/controller-runtime/pkg/client"
"sigs.k8s.io/yaml"
wfContext "github.com/kubevela/workflow/pkg/context"
"github.com/kubevela/workflow/pkg/cue/model/value"
wfTypes "github.com/kubevela/workflow/pkg/types"
wfUtils "github.com/kubevela/workflow/pkg/utils"
"github.com/oam-dev/kubevela/apis/core.oam.dev/v1beta1"
"github.com/oam-dev/kubevela/pkg/apiserver/domain/model"
@@ -47,6 +54,12 @@ import (
"github.com/oam-dev/kubevela/pkg/utils/apply"
)
// LogSourceResource Read the step logs from the pod stdout.
const LogSourceResource = "Resource"
// LogSourceURL Read the step logs from the URL.
const LogSourceURL = "URL"
// WorkflowService workflow manage api
type WorkflowService interface {
ListApplicationWorkflow(ctx context.Context, app *model.Application) ([]*apisv1.WorkflowBase, error)
@@ -57,13 +70,19 @@ type WorkflowService interface {
DeleteWorkflowByApp(ctx context.Context, app *model.Application) error
CreateOrUpdateWorkflow(ctx context.Context, app *model.Application, req apisv1.CreateWorkflowRequest) (*apisv1.DetailWorkflowResponse, error)
UpdateWorkflow(ctx context.Context, workflow *model.Workflow, req apisv1.UpdateWorkflowRequest) (*apisv1.DetailWorkflowResponse, error)
CreateWorkflowRecord(ctx context.Context, appModel *model.Application, app *v1beta1.Application, workflow *model.Workflow) error
GetWorkflowRecord(ctx context.Context, workflow *model.Workflow, recordName string) (*model.WorkflowRecord, error)
CreateWorkflowRecord(ctx context.Context, appModel *model.Application, app *v1beta1.Application, workflow *model.Workflow) (*model.WorkflowRecord, error)
ListWorkflowRecords(ctx context.Context, workflow *model.Workflow, page, pageSize int) (*apisv1.ListWorkflowRecordsResponse, error)
DetailWorkflowRecord(ctx context.Context, workflow *model.Workflow, recordName string) (*apisv1.DetailWorkflowRecordResponse, error)
SyncWorkflowRecord(ctx context.Context) error
ResumeRecord(ctx context.Context, appModel *model.Application, workflow *model.Workflow, recordName string) error
TerminateRecord(ctx context.Context, appModel *model.Application, workflow *model.Workflow, recordName string) error
RollbackRecord(ctx context.Context, appModel *model.Application, workflow *model.Workflow, recordName, revisionName string) error
RollbackRecord(ctx context.Context, appModel *model.Application, workflow *model.Workflow, recordName, revisionName string) (*apisv1.WorkflowRecordBase, error)
GetWorkflowRecordLog(ctx context.Context, record *model.WorkflowRecord, step string) (apisv1.GetPipelineRunLogResponse, error)
GetWorkflowRecordOutput(ctx context.Context, workflow *model.Workflow, record *model.WorkflowRecord, stepName string) (apisv1.GetPipelineRunOutputResponse, error)
GetWorkflowRecordInput(ctx context.Context, workflow *model.Workflow, record *model.WorkflowRecord, stepName string) (apisv1.GetPipelineRunInputResponse, error)
CountWorkflow(ctx context.Context, app *model.Application) int64
}
@@ -75,6 +94,7 @@ func NewWorkflowService() WorkflowService {
type workflowServiceImpl struct {
Store datastore.DataStore `inject:"datastore"`
KubeClient client.Client `inject:"kubeClient"`
KubeConfig *rest.Config `inject:"kubeConfig"`
Apply apply.Applicator `inject:"apply"`
EnvService EnvService `inject:""`
EnvBindingService EnvBindingService `inject:""`
@@ -153,11 +173,19 @@ func (w *workflowServiceImpl) CreateOrUpdateWorkflow(ctx context.Context, app *m
if err != nil {
return nil, err
}
if req.Mode == "" {
req.Mode = string(workflowv1alpha1.WorkflowModeStep)
}
if req.SubMode == "" {
req.Mode = string(workflowv1alpha1.WorkflowModeDAG)
}
if workflow != nil {
workflow.Steps = modelSteps
workflow.Alias = req.Alias
workflow.Description = req.Description
workflow.Default = req.Default
workflow.Mode.Steps = workflowv1alpha1.WorkflowMode(req.Mode)
workflow.Mode.SubSteps = workflowv1alpha1.WorkflowMode(req.SubMode)
if err := w.Store.Put(ctx, workflow); err != nil {
return nil, err
}
@@ -171,6 +199,10 @@ func (w *workflowServiceImpl) CreateOrUpdateWorkflow(ctx context.Context, app *m
Default: req.Default,
EnvName: req.EnvName,
AppPrimaryKey: app.PrimaryKey(),
Mode: workflowv1alpha1.WorkflowExecuteMode{
Steps: workflowv1alpha1.WorkflowMode(req.Mode),
SubSteps: workflowv1alpha1.WorkflowMode(req.SubMode),
},
}
klog.Infof("create workflow %s for app %s", pkgUtils.Sanitize(req.Name), pkgUtils.Sanitize(app.PrimaryKey()))
if err := w.Store.Add(ctx, workflow); err != nil {
@@ -186,6 +218,15 @@ func (w *workflowServiceImpl) UpdateWorkflow(ctx context.Context, workflow *mode
return nil, err
}
workflow.Description = req.Description
workflow.Alias = req.Alias
if req.Mode == "" {
req.Mode = string(workflowv1alpha1.WorkflowModeStep)
}
if req.SubMode == "" {
req.Mode = string(workflowv1alpha1.WorkflowModeDAG)
}
workflow.Mode.Steps = workflowv1alpha1.WorkflowMode(req.Mode)
workflow.Mode.SubSteps = workflowv1alpha1.WorkflowMode(req.SubMode)
// It is allowed to set multiple workflows as default, and only one takes effect.
if req.Default != nil {
workflow.Default = req.Default
@@ -249,7 +290,9 @@ func (w *workflowServiceImpl) ListWorkflowRecords(ctx context.Context, workflow
AppPrimaryKey: workflow.AppPrimaryKey,
WorkflowName: workflow.Name,
}
records, err := w.Store.List(ctx, &record, &datastore.ListOptions{Page: page, PageSize: pageSize})
records, err := w.Store.List(ctx, &record, &datastore.ListOptions{Page: page, PageSize: pageSize, SortBy: []datastore.SortOption{
{Key: "createTime", Order: datastore.SortOrderAscending},
}})
if err != nil {
return nil, err
}
@@ -345,6 +388,7 @@ func (w *workflowServiceImpl) SyncWorkflowRecord(ctx context.Context) error {
Namespace: record.Namespace,
}, app); err != nil {
if apierrors.IsNotFound(err) {
klog.Warningf("can't find the application %s/%s, set the record status to terminated", appName, record.Namespace)
if err := w.setRecordToTerminated(ctx, record.AppPrimaryKey, record.Name); err != nil {
klog.Errorf("failed to set the record status to terminated %s", err.Error())
}
@@ -358,14 +402,22 @@ func (w *workflowServiceImpl) SyncWorkflowRecord(ctx context.Context) error {
continue
}
// This means the application workflow has not run.
if app.Generation > app.Status.ObservedGeneration {
continue
}
// there is a ":" in the default app revision
recordName := strings.Replace(app.Status.Workflow.AppRevision, ":", "-", 1)
// try to sync the status from the running application
if app.Annotations != nil && app.Status.Workflow != nil && recordName == record.Name {
if err := w.syncWorkflowStatus(ctx, record.AppPrimaryKey, app, record.Name, app.Name); err != nil {
if err := w.syncWorkflowStatus(ctx, record.AppPrimaryKey, app, record.Name, app.Name, nil); err != nil {
klog.ErrorS(err, "failed to sync workflow status", "oam app name", appName, "workflow name", record.WorkflowName, "record name", record.Name)
}
}
if record.Name == oam.GetPublishVersion(app) {
continue
}
@@ -395,6 +447,7 @@ func (w *workflowServiceImpl) SyncWorkflowRecord(ctx context.Context) error {
var appRevision v1beta1.ApplicationRevision
if err := w.KubeClient.Get(ctx, types.NamespacedName{Namespace: app.Namespace, Name: revision.RevisionCRName}, &appRevision); err != nil {
if apierrors.IsNotFound(err) {
klog.Warningf("can't find the application revision %s/%s, set the record status to terminated", revision.RevisionCRName, app.Namespace)
if err := w.setRecordToTerminated(ctx, record.AppPrimaryKey, record.Name); err != nil {
klog.Errorf("failed to set the record status to terminated %s", err.Error())
}
@@ -411,7 +464,13 @@ func (w *workflowServiceImpl) SyncWorkflowRecord(ctx context.Context) error {
appRevision.Spec.Application.Status.Workflow.Terminated = true
}
}
if err := w.syncWorkflowStatus(ctx, record.AppPrimaryKey, &appRevision.Spec.Application, record.Name, revision.RevisionCRName); err != nil {
if err := w.syncWorkflowStatus(ctx,
record.AppPrimaryKey,
&appRevision.Spec.Application,
record.Name,
appRevision.Name,
appRevision.Status.WorkflowContext,
); err != nil {
klog.ErrorS(err, "failed to sync workflow status", "oam app name", appName, "workflow name", record.WorkflowName, "record name", record.Name)
continue
}
@@ -453,7 +512,12 @@ func (w *workflowServiceImpl) setRecordToTerminated(ctx context.Context, appPrim
return nil
}
func (w *workflowServiceImpl) syncWorkflowStatus(ctx context.Context, appPrimaryKey string, app *v1beta1.Application, recordName, source string) error {
func (w *workflowServiceImpl) syncWorkflowStatus(ctx context.Context,
appPrimaryKey string,
app *v1beta1.Application,
recordName,
source string,
workflowContext map[string]string) error {
var record = &model.WorkflowRecord{
AppPrimaryKey: appPrimaryKey,
Name: recordName,
@@ -472,19 +536,28 @@ func (w *workflowServiceImpl) syncWorkflowStatus(ctx context.Context, appPrimary
return err
}
if workflowContext != nil {
record.ContextValue = workflowContext
}
if app.Status.Workflow != nil {
if app.Status.Workflow.AppRevision != record.Name {
klog.Warningf("the app(%s) revision is not match the record(%s), try next time..", app.Name, record.Name)
return nil
}
status := app.Status.Workflow
summaryStatus := model.RevisionStatusRunning
switch {
case status.Phase == workflowv1alpha1.WorkflowStateFailed:
summaryStatus = model.RevisionStatusFail
case status.Finished:
summaryStatus = model.RevisionStatusComplete
case status.Terminated:
summaryStatus = model.RevisionStatusTerminated
record.Status = string(status.Phase)
record.Message = status.Message
record.Mode = status.Mode
if cb := app.Status.Workflow.ContextBackend; cb != nil && workflowContext == nil {
var cm corev1.ConfigMap
if err := w.KubeClient.Get(ctx, types.NamespacedName{Namespace: cb.Namespace, Name: cb.Name}, &cm); err != nil {
klog.Error(err, "failed to load the context values", "Application", app.Name)
}
record.ContextValue = cm.Data
}
record.Status = summaryStatus
stepStatus := make(map[string]*model.WorkflowStepStatus, len(status.Steps))
stepAlias := make(map[string]string)
for _, step := range record.Steps {
@@ -507,13 +580,24 @@ func (w *workflowServiceImpl) syncWorkflowStatus(ctx context.Context, appPrimary
record.Steps[i] = *stepStatus[step.Name]
}
}
record.Finished = strconv.FormatBool(status.Finished)
// the auto generated workflow steps should be sync
if (len(record.Steps) == 0) && len(status.Steps) > 0 {
for k := range stepStatus {
record.Steps = append(record.Steps, *stepStatus[k])
}
}
record.Finished = strconv.FormatBool(status.Finished)
record.EndTime = status.EndTime.Time
if err := w.Store.Put(ctx, record); err != nil {
return err
}
revision.Status = summaryStatus
revision.Status = generateRevisionStatus(status.Phase)
if app.Status.LatestRevision != nil {
revision.RevisionCRName = app.Status.LatestRevision.Name
}
if err := w.Store.Put(ctx, revision); err != nil {
return err
}
@@ -526,15 +610,28 @@ func (w *workflowServiceImpl) syncWorkflowStatus(ctx context.Context, appPrimary
return nil
}
func (w *workflowServiceImpl) CreateWorkflowRecord(ctx context.Context, appModel *model.Application, app *v1beta1.Application, workflow *model.Workflow) error {
func generateRevisionStatus(phase workflowv1alpha1.WorkflowRunPhase) string {
summaryStatus := model.RevisionStatusRunning
switch {
case phase == workflowv1alpha1.WorkflowStateFailed:
summaryStatus = model.RevisionStatusFail
case phase == workflowv1alpha1.WorkflowStateSucceeded:
summaryStatus = model.RevisionStatusComplete
case phase == workflowv1alpha1.WorkflowStateTerminated:
summaryStatus = model.RevisionStatusTerminated
}
return summaryStatus
}
func (w *workflowServiceImpl) CreateWorkflowRecord(ctx context.Context, appModel *model.Application, app *v1beta1.Application, workflow *model.Workflow) (*model.WorkflowRecord, error) {
if app.Annotations == nil {
return fmt.Errorf("empty annotations in application")
return nil, fmt.Errorf("empty annotations in application")
}
if app.Annotations[oam.AnnotationPublishVersion] == "" {
return fmt.Errorf("failed to get record version from application")
return nil, fmt.Errorf("failed to get record version from application")
}
if app.Annotations[oam.AnnotationDeployVersion] == "" {
return fmt.Errorf("failed to get deploy version from application")
return nil, fmt.Errorf("failed to get deploy version from application")
}
steps := make([]model.WorkflowStepStatus, len(workflow.Steps))
for i, step := range workflow.Steps {
@@ -555,7 +652,7 @@ func (w *workflowServiceImpl) CreateWorkflowRecord(ctx context.Context, appModel
}
}
if err := w.Store.Add(ctx, &model.WorkflowRecord{
workflowRecord := &model.WorkflowRecord{
WorkflowName: workflow.Name,
WorkflowAlias: workflow.Alias,
AppPrimaryKey: appModel.PrimaryKey(),
@@ -565,16 +662,18 @@ func (w *workflowServiceImpl) CreateWorkflowRecord(ctx context.Context, appModel
Finished: "false",
StartTime: time.Now().Time,
Steps: steps,
Status: model.RevisionStatusRunning,
}); err != nil {
return err
Status: string(workflowv1alpha1.WorkflowStateInitializing),
}
if err := w.Store.Add(ctx, workflowRecord); err != nil {
return nil, fmt.Errorf("failed to create the workflow record %s: %w", workflowRecord.Name, err)
}
if err := resetRevisionsAndRecords(ctx, w.Store, appModel.PrimaryKey(), workflow.Name, app.Annotations[oam.AnnotationDeployVersion], app.Annotations[oam.AnnotationPublishVersion]); err != nil {
return err
return workflowRecord, err
}
return nil
return workflowRecord, nil
}
func resetRevisionsAndRecords(ctx context.Context, ds datastore.DataStore, appName, workflowName, skipRevision, skipRecord string) error {
@@ -642,6 +741,21 @@ func (w *workflowServiceImpl) CountWorkflow(ctx context.Context, app *model.Appl
return count
}
func (w *workflowServiceImpl) GetWorkflowRecord(ctx context.Context, workflow *model.Workflow, recordName string) (*model.WorkflowRecord, error) {
var record = &model.WorkflowRecord{
WorkflowName: workflow.Name,
Name: recordName,
}
res, err := w.Store.List(ctx, record, nil)
if err != nil {
return nil, err
}
if len(res) == 0 {
return nil, bcode.ErrWorkflowRecordNotExist
}
return res[0].(*model.WorkflowRecord), nil
}
func (w *workflowServiceImpl) ResumeRecord(ctx context.Context, appModel *model.Application, workflow *model.Workflow, recordName string) error {
oamApp, err := w.checkRecordRunning(ctx, appModel, workflow.EnvName)
if err != nil {
@@ -652,7 +766,7 @@ func (w *workflowServiceImpl) ResumeRecord(ctx context.Context, appModel *model.
return err
}
if err := w.syncWorkflowStatus(ctx, appModel.PrimaryKey(), oamApp, recordName, oamApp.Name); err != nil {
if err := w.syncWorkflowStatus(ctx, appModel.PrimaryKey(), oamApp, recordName, oamApp.Name, nil); err != nil {
return err
}
@@ -667,7 +781,7 @@ func (w *workflowServiceImpl) TerminateRecord(ctx context.Context, appModel *mod
if err := TerminateWorkflow(ctx, w.KubeClient, oamApp); err != nil {
return err
}
if err := w.syncWorkflowStatus(ctx, appModel.PrimaryKey(), oamApp, recordName, oamApp.Name); err != nil {
if err := w.syncWorkflowStatus(ctx, appModel.PrimaryKey(), oamApp, recordName, oamApp.Name, nil); err != nil {
return err
}
@@ -732,7 +846,7 @@ func TerminateWorkflow(ctx context.Context, kubecli client.Client, app *v1beta1.
return nil
}
func (w *workflowServiceImpl) RollbackRecord(ctx context.Context, appModel *model.Application, workflow *model.Workflow, recordName, revisionVersion string) error {
func (w *workflowServiceImpl) RollbackRecord(ctx context.Context, appModel *model.Application, workflow *model.Workflow, recordName, revisionVersion string) (*apisv1.WorkflowRecordBase, error) {
if revisionVersion == "" {
// find the latest complete revision version
var revision = model.ApplicationRevision{
@@ -747,13 +861,13 @@ func (w *workflowServiceImpl) RollbackRecord(ctx context.Context, appModel *mode
SortBy: []datastore.SortOption{{Key: "createTime", Order: datastore.SortOrderDescending}},
})
if err != nil {
return err
return nil, err
}
if len(revisions) == 0 {
return bcode.ErrApplicationNoReadyRevision
return nil, bcode.ErrApplicationNoReadyRevision
}
revisionVersion = revisions[0].Index()["version"]
klog.Infof("select lastest complete revision %s", revisions[0].Index()["version"])
revisionVersion = pkgUtils.ToString(revisions[0].Index()["version"])
klog.Infof("select lastest complete revision %s", revisionVersion)
}
var record = &model.WorkflowRecord{
@@ -761,12 +875,12 @@ func (w *workflowServiceImpl) RollbackRecord(ctx context.Context, appModel *mode
Name: recordName,
}
if err := w.Store.Get(ctx, record); err != nil {
return err
return nil, err
}
oamApp, err := w.checkRecordRunning(ctx, appModel, workflow.EnvName)
if err != nil {
return err
return nil, err
}
var originalRevision = &model.ApplicationRevision{
@@ -774,7 +888,7 @@ func (w *workflowServiceImpl) RollbackRecord(ctx context.Context, appModel *mode
Version: record.RevisionPrimaryKey,
}
if err := w.Store.Get(ctx, originalRevision); err != nil {
return err
return nil, err
}
var rollbackRevision = &model.ApplicationRevision{
@@ -782,7 +896,7 @@ func (w *workflowServiceImpl) RollbackRecord(ctx context.Context, appModel *mode
Version: revisionVersion,
}
if err := w.Store.Get(ctx, rollbackRevision); err != nil {
return err
return nil, err
}
// update the original revision status to rollback
@@ -790,12 +904,12 @@ func (w *workflowServiceImpl) RollbackRecord(ctx context.Context, appModel *mode
originalRevision.RollbackVersion = revisionVersion
originalRevision.UpdateTime = time.Now().Time
if err := w.Store.Put(ctx, originalRevision); err != nil {
return err
return nil, err
}
rollBackApp := &v1beta1.Application{}
if err := yaml.Unmarshal([]byte(rollbackRevision.ApplyAppConfig), rollBackApp); err != nil {
return err
return nil, err
}
// replace the application spec
oamApp.Spec.Components = rollBackApp.Spec.Components
@@ -807,8 +921,9 @@ func (w *workflowServiceImpl) RollbackRecord(ctx context.Context, appModel *mode
oamApp.Annotations[oam.AnnotationDeployVersion] = revisionVersion
oamApp.Annotations[oam.AnnotationPublishVersion] = newRecordName
// create a new workflow record
if err := w.CreateWorkflowRecord(ctx, appModel, oamApp, workflow); err != nil {
return err
newRecord, err := w.CreateWorkflowRecord(ctx, appModel, oamApp, workflow)
if err != nil {
return nil, err
}
if err := w.Apply.Apply(ctx, oamApp); err != nil {
@@ -816,10 +931,10 @@ func (w *workflowServiceImpl) RollbackRecord(ctx context.Context, appModel *mode
if err := w.Store.Delete(ctx, &model.WorkflowRecord{Name: newRecordName}); err != nil {
klog.Error(err, "failed to delete record", newRecordName)
}
return err
return nil, err
}
return nil
return &assembler.ConvertFromRecordModel(newRecord).WorkflowRecordBase, nil
}
func (w *workflowServiceImpl) checkRecordRunning(ctx context.Context, appModel *model.Application, envName string) (*v1beta1.Application, error) {
@@ -846,3 +961,230 @@ func (w *workflowServiceImpl) checkRecordRunning(ctx context.Context, appModel *
oamApp.SetGroupVersionKind(v1beta1.ApplicationKindVersionKind)
return oamApp, nil
}
func (w *workflowServiceImpl) GetWorkflowRecordLog(ctx context.Context, record *model.WorkflowRecord, step string) (apisv1.GetPipelineRunLogResponse, error) {
if len(record.ContextValue) == 0 {
return apisv1.GetPipelineRunLogResponse{}, nil
}
logConfig, err := getLogConfigFromStep(record.ContextValue, step)
if err != nil {
if strings.Contains(err.Error(), "no log config found") {
return apisv1.GetPipelineRunLogResponse{
StepBase: getWorkflowStepBase(*record, step),
Log: "",
}, nil
}
return apisv1.GetPipelineRunLogResponse{}, err
}
var logs string
var source string
if logConfig.Source != nil {
if len(logConfig.Source.Resources) > 0 {
source = LogSourceResource
logs, err = getResourceLogs(ctx, w.KubeConfig, w.KubeClient, logConfig.Source.Resources, nil)
if err != nil {
return apisv1.GetPipelineRunLogResponse{LogSource: source}, err
}
}
if logConfig.Source.URL != "" {
source = LogSourceURL
var logsBuilder strings.Builder
readCloser, err := wfUtils.GetLogsFromURL(ctx, logConfig.Source.URL)
if err != nil {
klog.Errorf("get logs from url %s failed: %v", logConfig.Source.URL, err)
return apisv1.GetPipelineRunLogResponse{LogSource: source}, bcode.ErrReadSourceLog
}
//nolint:errcheck
defer readCloser.Close()
if _, err := io.Copy(&logsBuilder, readCloser); err != nil {
klog.Errorf("copy logs from url %s failed: %v", logConfig.Source.URL, err)
return apisv1.GetPipelineRunLogResponse{LogSource: source}, bcode.ErrReadSourceLog
}
logs = logsBuilder.String()
}
}
return apisv1.GetPipelineRunLogResponse{
LogSource: source,
StepBase: getWorkflowStepBase(*record, step),
Log: logs,
}, nil
}
func (w *workflowServiceImpl) GetWorkflowRecordOutput(ctx context.Context, workflow *model.Workflow, record *model.WorkflowRecord, stepName string) (apisv1.GetPipelineRunOutputResponse, error) {
outputsSpec := make(map[string]workflowv1alpha1.StepOutputs)
stepOutputs := make([]apisv1.StepOutputBase, 0)
for _, step := range workflow.Steps {
if step.Outputs != nil {
outputsSpec[step.Name] = step.Outputs
}
for _, sub := range step.SubSteps {
if sub.Outputs != nil {
outputsSpec[sub.Name] = sub.Outputs
}
}
}
ctxBackend := record.ContextValue
if ctxBackend == nil {
return apisv1.GetPipelineRunOutputResponse{}, nil
}
v, err := getDataFromContext(ctxBackend)
if err != nil {
klog.Errorf("get data from context backend failed: %v", err)
return apisv1.GetPipelineRunOutputResponse{}, bcode.ErrGetContextBackendData
}
for _, s := range record.Steps {
if stepName != "" && s.Name != stepName {
subStepStatus, ok := haveSubStep(s, stepName)
if !ok {
continue
}
subVars := getStepOutputs(convertWorkflowStep(*subStepStatus), outputsSpec, v)
stepOutputs = append(stepOutputs, subVars)
break
}
stepOutputs = append(stepOutputs, getStepOutputs(convertWorkflowStep(s.StepStatus), outputsSpec, v))
for _, sub := range s.SubStepsStatus {
stepOutputs = append(stepOutputs, getStepOutputs(convertWorkflowStep(sub), outputsSpec, v))
}
if stepName != "" && s.Name == stepName {
// already found the step
break
}
}
return apisv1.GetPipelineRunOutputResponse{StepOutputs: stepOutputs}, nil
}
func (w *workflowServiceImpl) GetWorkflowRecordInput(ctx context.Context, workflow *model.Workflow, record *model.WorkflowRecord, stepName string) (apisv1.GetPipelineRunInputResponse, error) {
// valueFromStep know which step the value came from
valueFromStep := make(map[string]string)
inputsSpec := make(map[string]workflowv1alpha1.StepInputs)
stepInputs := make([]apisv1.StepInputBase, 0)
for _, step := range workflow.Steps {
if step.Inputs != nil {
inputsSpec[step.Name] = step.Inputs
}
if step.Outputs != nil {
for _, o := range step.Outputs {
valueFromStep[o.Name] = step.Name
}
}
for _, sub := range step.SubSteps {
if sub.Inputs != nil {
inputsSpec[sub.Name] = sub.Inputs
}
if sub.Outputs != nil {
for _, o := range sub.Outputs {
valueFromStep[o.Name] = sub.Name
}
}
}
}
ctxBackend := record.ContextValue
if ctxBackend == nil {
return apisv1.GetPipelineRunInputResponse{}, nil
}
v, err := getDataFromContext(ctxBackend)
if err != nil {
klog.Errorf("get data from context backend failed: %v", err)
return apisv1.GetPipelineRunInputResponse{}, bcode.ErrGetContextBackendData
}
for _, s := range record.Steps {
if stepName != "" && s.Name != stepName {
subStepStatus, ok := haveSubStep(s, stepName)
if !ok {
continue
}
subVars := getStepInputs(convertWorkflowStep(*subStepStatus), inputsSpec, v, valueFromStep)
stepInputs = append(stepInputs, subVars)
break
}
stepInputs = append(stepInputs, getStepInputs(convertWorkflowStep(s.StepStatus), inputsSpec, v, valueFromStep))
for _, sub := range s.SubStepsStatus {
stepInputs = append(stepInputs, getStepInputs(convertWorkflowStep(sub), inputsSpec, v, valueFromStep))
}
if stepName != "" && s.Name == stepName {
// already found the step
break
}
}
return apisv1.GetPipelineRunInputResponse{StepInputs: stepInputs}, nil
}
func getWorkflowStepBase(record model.WorkflowRecord, step string) apisv1.StepBase {
for _, s := range record.Steps {
if s.Name == step {
return apisv1.StepBase{
ID: s.ID,
Name: s.Name,
Type: s.Type,
Phase: string(s.Phase),
}
}
}
return apisv1.StepBase{}
}
func getLogConfigFromStep(ctxValue map[string]string, step string) (*wfTypes.LogConfig, error) {
wc := wfContext.WorkflowContext{}
if err := wc.LoadFromConfigMap(corev1.ConfigMap{
Data: ctxValue,
}); err != nil {
return nil, err
}
config := make(map[string]wfTypes.LogConfig)
c := wc.GetMutableValue(wfTypes.ContextKeyLogConfig)
if c == "" {
return nil, fmt.Errorf("no log config found")
}
if err := json.Unmarshal([]byte(c), &config); err != nil {
return nil, err
}
stepConfig, ok := config[step]
if !ok {
return nil, fmt.Errorf("no log config found for step %s", step)
}
return &stepConfig, nil
}
func getDataFromContext(ctxValue map[string]string) (*value.Value, error) {
wc := wfContext.WorkflowContext{}
if err := wc.LoadFromConfigMap(corev1.ConfigMap{
Data: ctxValue,
}); err != nil {
return nil, err
}
v, err := wc.GetVar()
if err != nil {
return nil, err
}
if v.Error() != nil {
return nil, v.Error()
}
return v, nil
}
func haveSubStep(step model.WorkflowStepStatus, subStep string) (*model.StepStatus, bool) {
for _, s := range step.SubStepsStatus {
if s.Name == subStep {
return &s, true
}
}
return nil, false
}
func convertWorkflowStep(stepStatus model.StepStatus) workflowv1alpha1.StepStatus {
return workflowv1alpha1.StepStatus{
Name: stepStatus.Name,
ID: stepStatus.ID,
Type: stepStatus.Type,
Phase: stepStatus.Phase,
Message: stepStatus.Message,
Reason: stepStatus.Reason,
}
}

View File

@@ -177,7 +177,7 @@ var _ = Describe("Test workflow service functions", func() {
for i := 0; i < 3; i++ {
app.Annotations[oam.AnnotationPublishVersion] = fmt.Sprintf("list-workflow-name-%d", i)
app.Status.Workflow.AppRevision = fmt.Sprintf("list-workflow-name-%d", i)
err = workflowService.CreateWorkflowRecord(context.TODO(), &model.Application{
_, err = workflowService.CreateWorkflowRecord(context.TODO(), &model.Application{
Name: appName,
}, app, workflow)
Expect(err).Should(BeNil())
@@ -200,7 +200,7 @@ var _ = Describe("Test workflow service functions", func() {
Name: appName,
}, "test-workflow-2")
Expect(err).Should(BeNil())
err = workflowService.CreateWorkflowRecord(context.TODO(), &model.Application{
_, err = workflowService.CreateWorkflowRecord(context.TODO(), &model.Application{
Name: appName,
}, app, workflow)
Expect(err).Should(BeNil())
@@ -238,7 +238,7 @@ var _ = Describe("Test workflow service functions", func() {
Name: appName,
}, "test-workflow-2")
Expect(err).Should(BeNil())
err = workflowService.CreateWorkflowRecord(context.TODO(), &model.Application{
_, err = workflowService.CreateWorkflowRecord(context.TODO(), &model.Application{
Name: appName,
}, app, workflow)
Expect(err).Should(BeNil())
@@ -259,6 +259,7 @@ var _ = Describe("Test workflow service functions", func() {
app.Status.Workflow.Finished = true
err = workflowService.KubeClient.Create(ctx, app.DeepCopy())
Expect(err).Should(BeNil())
app.Status.ObservedGeneration = 1
err = workflowService.KubeClient.Status().Patch(ctx, app, client.Merge)
Expect(err).Should(BeNil())
err = workflowService.SyncWorkflowRecord(ctx)
@@ -271,7 +272,7 @@ var _ = Describe("Test workflow service functions", func() {
By("check the record")
record, err := workflowService.DetailWorkflowRecord(context.TODO(), workflow, "test-workflow-2-233")
Expect(err).Should(BeNil())
Expect(record.Status).Should(Equal(model.RevisionStatusFail))
Expect(record.Status).Should(Equal(string(workflowv1alpha1.WorkflowStateFailed)))
Expect(record.Steps[0].Alias).Should(Equal("step-alias-1"))
Expect(record.Steps[0].Phase).Should(Equal(workflowv1alpha1.WorkflowStepPhaseSucceeded))
Expect(record.Steps[1].Alias).Should(Equal("step-alias-2"))
@@ -291,7 +292,7 @@ var _ = Describe("Test workflow service functions", func() {
app.Annotations[oam.AnnotationPublishVersion] = "test-workflow-2-111"
app.Status.Workflow.AppRevision = "test-workflow-2-111"
app.Annotations[oam.AnnotationDeployVersion] = "1111"
err = workflowService.CreateWorkflowRecord(context.TODO(), &model.Application{
_, err = workflowService.CreateWorkflowRecord(context.TODO(), &model.Application{
Name: appName,
}, app, workflow)
Expect(err).Should(BeNil())
@@ -328,6 +329,7 @@ var _ = Describe("Test workflow service functions", func() {
err = workflowService.KubeClient.Create(ctx, appRevision)
Expect(err).Should(BeNil())
appRevision.Status.Workflow = appWithRevision.Status.Workflow
appRevision.Status.Workflow.AppRevision = app.Annotations[oam.AnnotationPublishVersion]
err = workflowService.KubeClient.Status().Update(ctx, appRevision)
Expect(err).Should(BeNil())
err = workflowService.SyncWorkflowRecord(ctx)
@@ -336,7 +338,7 @@ var _ = Describe("Test workflow service functions", func() {
By("check the record")
anotherRecord, err := workflowService.DetailWorkflowRecord(context.TODO(), workflow, "test-workflow-2-111")
Expect(err).Should(BeNil())
Expect(anotherRecord.Status).Should(Equal(model.RevisionStatusFail))
Expect(anotherRecord.Status).Should(Equal(string(workflowv1alpha1.WorkflowStepPhaseFailed)))
By("check the application revision")
err = workflowService.Store.Get(ctx, anotherRevision)
@@ -358,7 +360,7 @@ var _ = Describe("Test workflow service functions", func() {
app, err := createTestSuspendApp(ctx, "record-app", "default", "revision-123", "test-workflow", "test-record-3", workflowService.KubeClient)
Expect(err).Should(BeNil())
err = workflowService.CreateWorkflowRecord(ctx, &model.Application{
_, err = workflowService.CreateWorkflowRecord(ctx, &model.Application{
Name: "record-app",
}, app, &model.Workflow{Name: "test-workflow"})
Expect(err).Should(BeNil())
@@ -370,7 +372,7 @@ var _ = Describe("Test workflow service functions", func() {
}
err = workflowService.Store.Get(ctx, record)
Expect(err).Should(BeNil())
Expect(record.Status).Should(Equal(model.RevisionStatusRunning))
Expect(record.Status).Should(Equal(string(workflowv1alpha1.WorkflowStateInitializing)))
})
It("Test ResumeRecord function", func() {
@@ -396,7 +398,7 @@ var _ = Describe("Test workflow service functions", func() {
app, err := createTestSuspendApp(ctx, appName, "resume", "revision-resume1", ResumeWorkflow, "workflow-resume-1", workflowService.KubeClient)
Expect(err).Should(BeNil())
err = workflowService.CreateWorkflowRecord(context.TODO(), &model.Application{
_, err = workflowService.CreateWorkflowRecord(context.TODO(), &model.Application{
Name: appName,
}, app, &model.Workflow{Name: ResumeWorkflow})
Expect(err).Should(BeNil())
@@ -416,7 +418,8 @@ var _ = Describe("Test workflow service functions", func() {
record, err := workflowService.DetailWorkflowRecord(ctx, &model.Workflow{Name: ResumeWorkflow, AppPrimaryKey: appName}, "workflow-resume-1")
Expect(err).Should(BeNil())
Expect(record.Status).Should(Equal(model.RevisionStatusRunning))
Expect(len(record.Steps)).Should(Equal(1))
Expect(record.Steps[0].Phase).Should(Equal(workflowv1alpha1.WorkflowStepPhaseSucceeded))
})
It("Test TerminateRecord function", func() {
@@ -442,7 +445,7 @@ var _ = Describe("Test workflow service functions", func() {
app, err := createTestSuspendApp(ctx, appName, "terminate", "revision-terminate1", workflow.Name, "test-workflow-2-1", workflowService.KubeClient)
Expect(err).Should(BeNil())
err = workflowService.CreateWorkflowRecord(context.TODO(), &model.Application{
_, err = workflowService.CreateWorkflowRecord(context.TODO(), &model.Application{
Name: appName,
}, app, workflow)
Expect(err).Should(BeNil())
@@ -461,7 +464,8 @@ var _ = Describe("Test workflow service functions", func() {
record, err := workflowService.DetailWorkflowRecord(ctx, workflow, "test-workflow-2-1")
Expect(err).Should(BeNil())
Expect(record.Status).Should(Equal(model.RevisionStatusTerminated))
Expect(len(record.Steps)).Should(Equal(1))
Expect(record.Steps[0].Phase).Should(Equal(workflowv1alpha1.WorkflowStepPhaseFailed))
})
It("Test RollbackRecord function", func() {
@@ -486,7 +490,7 @@ var _ = Describe("Test workflow service functions", func() {
app, err := createTestSuspendApp(ctx, appName, "rollback", "revision-rollback1", workflow.Name, "test-workflow-2-2", workflowService.KubeClient)
Expect(err).Should(BeNil())
err = workflowService.CreateWorkflowRecord(context.TODO(), &model.Application{
_, err = workflowService.CreateWorkflowRecord(context.TODO(), &model.Application{
Name: appName,
}, app, workflow)
Expect(err).Should(BeNil())
@@ -509,7 +513,7 @@ var _ = Describe("Test workflow service functions", func() {
})
Expect(err).Should(BeNil())
err = workflowService.RollbackRecord(ctx, &model.Application{
_, err = workflowService.RollbackRecord(ctx, &model.Application{
Name: appName,
}, workflow, "test-workflow-2-2", "revision-rollback0")
Expect(err).Should(BeNil())
@@ -525,12 +529,12 @@ var _ = Describe("Test workflow service functions", func() {
By("rollback application without revision version")
app.Annotations[oam.AnnotationPublishVersion] = "workflow-rollback-2"
app.Status.Workflow.AppRevision = "workflow-rollback-2"
err = workflowService.CreateWorkflowRecord(context.TODO(), &model.Application{
_, err = workflowService.CreateWorkflowRecord(context.TODO(), &model.Application{
Name: appName,
}, app, workflow)
Expect(err).Should(BeNil())
err = workflowService.RollbackRecord(ctx, &model.Application{
_, err = workflowService.RollbackRecord(ctx, &model.Application{
Name: appName,
}, workflow, "workflow-rollback-2", "")
Expect(err).Should(BeNil())

View File

@@ -89,7 +89,7 @@ func NewDatastore(cfg datastore.Config) (ds datastore.DataStore, err error) {
return nil, fmt.Errorf("create mongodb datastore instance failure %w", err)
}
case "kubeapi":
ds, err = kubeapi.New(context.Background(), cfg)
ds, err = kubeapi.New(context.Background(), cfg, k8sClient)
if err != nil {
return nil, fmt.Errorf("create mongodb datastore instance failure %w", err)
}

View File

@@ -21,14 +21,13 @@ import (
"sort"
"time"
client2 "sigs.k8s.io/controller-runtime/pkg/client"
"sigs.k8s.io/controller-runtime/pkg/client"
"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/oam"
"github.com/oam-dev/kubevela/pkg/apiserver/infrastructure/clients"
"github.com/oam-dev/kubevela/pkg/multicluster"
"github.com/oam-dev/kubevela/pkg/apiserver/domain/model"
@@ -56,8 +55,9 @@ var waitBackOff = wait.Backoff{
// InfoCalculateCronJob is the cronJob to calculate the system info store in db
type InfoCalculateCronJob struct {
Store datastore.DataStore `inject:"datastore"`
cron *cron.Cron
KubeClient client.Client `inject:"kubeClient"`
Store datastore.DataStore `inject:"datastore"`
cron *cron.Cron
}
// Start start the worker
@@ -230,12 +230,8 @@ func (i InfoCalculateCronJob) calculateAppInfo(ctx context.Context) (int, []stri
}
func (i InfoCalculateCronJob) calculateAddonInfo(ctx context.Context) (map[string]string, error) {
client, err := clients.GetKubeClient()
if err != nil {
return nil, err
}
apps := &v1beta1.ApplicationList{}
if err := client.List(ctx, apps, client2.InNamespace(types.DefaultKubeVelaNS), client2.HasLabels{oam.LabelAddonName}); err != nil {
if err := i.KubeClient.List(ctx, apps, client.InNamespace(types.DefaultKubeVelaNS), client.HasLabels{oam.LabelAddonName}); err != nil {
return nil, err
}
res := map[string]string{}
@@ -257,11 +253,7 @@ func (i InfoCalculateCronJob) calculateAddonInfo(ctx context.Context) (map[strin
}
func (i InfoCalculateCronJob) calculateClusterInfo(ctx context.Context) (int, error) {
client, err := clients.GetKubeClient()
if err != nil {
return 0, err
}
cs, err := multicluster.ListVirtualClusters(ctx, client)
cs, err := multicluster.ListVirtualClusters(ctx, i.KubeClient)
if err != nil {
return 0, err
}

View File

@@ -83,7 +83,8 @@ var _ = Describe("Test calculate cronJob", func() {
testProject = "test-cronjob-project"
mockDataInDs()
i = InfoCalculateCronJob{
Store: ds,
Store: ds,
KubeClient: k8sClient,
}
systemInfo := model.SystemInfo{InstallID: "test-id", EnableCollection: true}
Expect(ds.Add(ctx, &systemInfo)).Should(SatisfyAny(BeNil(), DataExistMatcher{}))

View File

@@ -19,7 +19,6 @@ package sync
import (
"context"
"errors"
"strconv"
"strings"
"k8s.io/klog/v2"
@@ -31,8 +30,8 @@ import (
)
type cached struct {
generation int64
targets int64
revision string
targets int64
}
// initCache will initialize the cache
@@ -49,8 +48,8 @@ func (c *CR2UX) initCache(ctx context.Context) error {
if !ok {
continue
}
gen, ok := app.Labels[model.LabelSyncGeneration]
if !ok || gen == "" {
revision, ok := app.Labels[model.LabelSyncRevision]
if !ok {
continue
}
namespace := app.Labels[model.LabelSyncNamespace]
@@ -58,10 +57,9 @@ func (c *CR2UX) initCache(ctx context.Context) error {
if strings.HasSuffix(app.Name, namespace) {
key = app.Name
}
generation, _ := strconv.ParseInt(gen, 10, 64)
// we should check targets if we synced from app status
c.syncCache(key, generation, 0)
c.syncCache(key, revision, 0)
}
return nil
}
@@ -79,8 +77,10 @@ func (c *CR2UX) shouldSync(ctx context.Context, targetApp *v1beta1.Application,
}
// if no LabelSourceOfTruth label, it means the app is existing ones, check the existing labels and annotations
if _, appName := targetApp.Annotations[oam.AnnotationAppName]; appName {
return false
if targetApp.Annotations != nil {
if _, exist := targetApp.Annotations[oam.AnnotationAppName]; exist {
return false
}
}
key := formatAppComposedName(targetApp.Name, targetApp.Namespace)
@@ -91,16 +91,15 @@ func (c *CR2UX) shouldSync(ctx context.Context, targetApp *v1beta1.Application,
_, _, err := c.getApp(ctx, targetApp.Name, targetApp.Namespace)
if del || err != nil {
c.cache.Delete(key)
} else if cd.generation == targetApp.Generation {
klog.Infof("app %s/%s with generation(%v) hasn't updated, ignore the sync event..", targetApp.Name, targetApp.Namespace, targetApp.Generation)
} else if cd.revision == getRevision(*targetApp) {
klog.V(5).Infof("app %s/%s with resource revision(%v) hasn't updated, ignore the sync event..", targetApp.Name, targetApp.Namespace, targetApp.ResourceVersion)
return false
}
}
return true
}
func (c *CR2UX) syncCache(key string, generation, targets int64) {
func (c *CR2UX) syncCache(key string, revision string, targets int64) {
// update cache
c.cache.Store(key, &cached{generation: generation, targets: targets})
c.cache.Store(key, &cached{revision: revision, targets: targets})
}

View File

@@ -68,33 +68,36 @@ var _ = Describe("Test Cache", func() {
app1 := &v1beta1.Application{}
app1.Name = "app1"
app1.Namespace = "app1-ns"
app1.Generation = 1
Expect(cr2ux.shouldSync(ctx, app1, false)).Should(BeEquivalentTo(true))
app2 := &v1beta1.Application{}
app2.Name = "app2"
app2.Namespace = "app2-ns"
app2.Generation = 1
app2.Status.LatestRevision = &common.Revision{Name: "v1"}
Expect(cr2ux.shouldSync(ctx, app2, false)).Should(BeEquivalentTo(true))
// Only need to sync once.
cr2ux.syncCache(formatAppComposedName(app2.Name, app2.Namespace), "v1", 1)
Expect(cr2ux.shouldSync(ctx, app2, false)).Should(BeEquivalentTo(false))
app3 := &v1beta1.Application{}
app3.Name = "app3"
app3.Namespace = "app3-ns"
app3.Generation = 3
app3.ResourceVersion = "3"
app3.Labels = map[string]string{
model.LabelSyncGeneration: "1",
model.LabelSyncNamespace: "app3-ns",
model.LabelSourceOfTruth: model.FromUX,
model.LabelSourceOfTruth: model.FromUX,
}
Expect(cr2ux.shouldSync(ctx, app3, false)).Should(BeEquivalentTo(false))
Expect(ds.Put(ctx, &model.Application{Name: "app1", Labels: map[string]string{
model.LabelSyncGeneration: "1",
model.LabelSyncNamespace: "app1-ns",
model.LabelSyncRevision: "v1",
model.LabelSyncNamespace: "app1-ns",
}})).Should(BeNil())
cr2ux.syncCache(formatAppComposedName(app1.Name, app1.Namespace), 1, 0)
cr2ux.syncCache(formatAppComposedName(app1.Name, app1.Namespace), "v1", 0)
app1.Status.LatestRevision = &common.Revision{Name: "v1"}
Expect(cr2ux.shouldSync(ctx, app1, false)).Should(BeEquivalentTo(false))
Expect(cr2ux.shouldSync(ctx, app1, true)).Should(BeEquivalentTo(true))
Expect(ds.Delete(ctx, &model.Application{Name: "app1"})).Should(BeNil())

View File

@@ -25,25 +25,32 @@ import (
corev1 "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/types"
"k8s.io/klog/v2"
"github.com/oam-dev/kubevela/apis/core.oam.dev/v1beta1"
apitypes "github.com/oam-dev/kubevela/apis/types"
"github.com/oam-dev/kubevela/pkg/apiserver/domain/model"
"github.com/oam-dev/kubevela/pkg/apiserver/event/sync/convert"
v1 "github.com/oam-dev/kubevela/pkg/apiserver/interfaces/api/dto/v1"
"github.com/oam-dev/kubevela/pkg/apiserver/utils"
"github.com/oam-dev/kubevela/pkg/oam"
"github.com/oam-dev/kubevela/pkg/workflow/step"
)
// ConvertApp2DatastoreApp will convert Application CR to datastore application related resources
func (c *CR2UX) ConvertApp2DatastoreApp(ctx context.Context, targetApp *v1beta1.Application) (*model.DataStoreApp, error) {
func (c *CR2UX) ConvertApp2DatastoreApp(ctx context.Context, targetApp *v1beta1.Application) (*DataStoreApp, error) {
cli := c.cli
appName := c.getAppMetaName(ctx, targetApp.Name, targetApp.Namespace)
project := model.DefaultInitName
project := v1.CreateProjectRequest{
Name: model.DefaultInitName,
}
sourceOfTruth := model.FromCR
if _, ok := targetApp.Labels[oam.LabelAddonName]; ok && strings.HasPrefix(targetApp.Name, "addon-") {
project = model.DefaultAddonProject
if _, ok := targetApp.Labels[oam.LabelAddonName]; ok && strings.HasPrefix(targetApp.Name, "addon-") && targetApp.Namespace == apitypes.DefaultKubeVelaNS {
project = v1.CreateProjectRequest{
Name: model.DefaultSystemProject,
Alias: model.DefaultSystemProjectAlias,
Namespace: targetApp.Namespace,
}
sourceOfTruth = model.FromInner
}
@@ -51,18 +58,20 @@ func (c *CR2UX) ConvertApp2DatastoreApp(ctx context.Context, targetApp *v1beta1.
Name: appName,
Description: model.AutoGenDesc,
Alias: targetApp.Name,
Project: project,
Project: project.Name,
Labels: map[string]string{
model.LabelSyncNamespace: targetApp.Namespace,
model.LabelSyncGeneration: strconv.FormatInt(targetApp.Generation, 10),
model.LabelSyncRevision: getRevision(*targetApp),
model.LabelSourceOfTruth: sourceOfTruth,
},
}
appMeta.CreateTime = targetApp.CreationTimestamp.Time
appMeta.UpdateTime = time.Now()
// 1. convert app meta and env
dsApp := &model.DataStoreApp{
dsApp := &DataStoreApp{
AppMeta: appMeta,
Project: &project,
}
// 2. convert the target
@@ -72,54 +81,25 @@ func (c *CR2UX) ConvertApp2DatastoreApp(ctx context.Context, targetApp *v1beta1.
return nil, fmt.Errorf("fail to list the targets, %w", err)
}
var envTargetNames map[string]string
dsApp.Targets, envTargetNames = convert.FromCRTargets(ctx, c.cli, targetApp, existTargets, project)
dsApp.Targets, envTargetNames = convert.FromCRTargets(ctx, c.cli, targetApp, existTargets, project.Name)
// 3. convert the environment
existEnv := &model.Env{Namespace: targetApp.Namespace}
existEnvs, err := c.ds.List(ctx, existEnv, nil)
// 3. generate the environment
env, newProject, err := c.generateEnv(ctx, project.Name, targetApp.Namespace, envTargetNames)
if err != nil {
return nil, fmt.Errorf("fail to list the env, %w", err)
return nil, err
}
if len(existEnvs) > 0 {
env := existEnvs[0].(*model.Env)
dsApp.AppMeta.Project = env.Project
for name, project := range envTargetNames {
if !utils.StringsContain(env.Targets, name) && project == env.Project {
env.Targets = append(env.Targets, name)
}
}
dsApp.Env = env
}
if dsApp.Env == nil {
var newProject string
var targetNames []string
for name, project := range envTargetNames {
if newProject == "" {
newProject = project
}
if newProject == project {
targetNames = append(targetNames, name)
}
}
var namespace corev1.Namespace
envName := model.AutoGenEnvNamePrefix + targetApp.Namespace
// Get the env name from the label of namespace
// If the namespace created by `vela env init`
if c.cli.Get(ctx, types.NamespacedName{Name: targetApp.Namespace}, &namespace) == nil && namespace.Labels != nil {
if env := namespace.Labels[oam.LabelNamespaceOfEnvName]; env != "" {
envName = env
}
}
dsApp.Env = &model.Env{
Name: envName,
Namespace: targetApp.Namespace,
Description: model.AutoGenDesc,
Project: newProject,
Alias: model.AutoGenEnvNamePrefix + targetApp.Namespace,
Targets: targetNames,
klog.V(5).Infof("generate the environment %s for the application %s", env.Name, targetApp.Name)
dsApp.Env = env
if newProject != "" {
project = v1.CreateProjectRequest{
Name: newProject,
Namespace: targetApp.Namespace,
}
dsApp.Project = &project
dsApp.Env.Project = newProject
dsApp.AppMeta.Project = newProject
}
dsApp.Eb = &model.EnvBinding{
AppPrimaryKey: appMeta.PrimaryKey(),
Name: dsApp.Env.Name,
@@ -140,11 +120,10 @@ func (c *CR2UX) ConvertApp2DatastoreApp(ctx context.Context, targetApp *v1beta1.
}
// 5. convert workflow
wf, steps, err := convert.FromCRWorkflow(ctx, cli, appMeta.PrimaryKey(), targetApp)
wf, steps, err := convert.FromCRWorkflow(ctx, cli, appMeta.PrimaryKey(), targetApp, dsApp.Env.Name)
if err != nil {
return nil, err
}
wf.EnvName = dsApp.Env.Name
dsApp.Workflow = &wf
// 6. convert policy, some policies are references in workflow step, we need to sync all the outside policy to make that work
@@ -171,6 +150,8 @@ func (c *CR2UX) ConvertApp2DatastoreApp(ctx context.Context, targetApp *v1beta1.
// 7. convert the revision
if revision := convert.FromCRApplicationRevision(ctx, cli, targetApp, *dsApp.Workflow, dsApp.Env.Name); revision != nil {
dsApp.Revision = revision
} else {
klog.Warningf("can't generate the application revision(%s) for the app %s", getRevision(*targetApp), targetApp.Name)
}
// 8. convert the workflow record
if record := convert.FromCRWorkflowRecord(targetApp, *dsApp.Workflow, dsApp.Revision); record != nil {
@@ -178,3 +159,73 @@ func (c *CR2UX) ConvertApp2DatastoreApp(ctx context.Context, targetApp *v1beta1.
}
return dsApp, nil
}
func (c *CR2UX) generateEnv(ctx context.Context, defaultProject string, envNamespace string, envTargetNames map[string]string) (*model.Env, string, error) {
existEnv := &model.Env{Namespace: envNamespace}
existEnvs, err := c.ds.List(ctx, existEnv, nil)
if err != nil {
return nil, "", fmt.Errorf("fail to list the env, %w", err)
}
if len(existEnvs) > 0 {
env := existEnvs[0].(*model.Env)
for name, project := range envTargetNames {
if !utils.StringsContain(env.Targets, name) && project == env.Project {
env.Targets = append(env.Targets, name)
}
}
return env, "", nil
}
// generate new environment
var newProject string
var targetNames []string
for name, project := range envTargetNames {
if newProject == "" {
newProject = project
}
if newProject == project {
targetNames = append(targetNames, name)
}
}
envName := model.AutoGenEnvNamePrefix + envNamespace
alias := envName
if envNamespace == apitypes.DefaultKubeVelaNS {
envName = model.DefaultSystemProject
alias = model.DefaultSystemProjectAlias
}
// Get the env name from the label of namespace
// If the namespace created by `vela env init`
var namespace corev1.Namespace
if c.cli.Get(ctx, types.NamespacedName{Name: envNamespace}, &namespace) == nil && namespace.Labels != nil {
if env := namespace.Labels[oam.LabelNamespaceOfEnvName]; env != "" {
envName = env
}
}
env := &model.Env{
Name: envName,
Namespace: envNamespace,
Description: model.AutoGenDesc,
Alias: alias,
Targets: targetNames,
Project: defaultProject,
}
if defaultProject == model.DefaultInitName && newProject != "" {
return env, newProject, nil
}
return env, "", nil
}
func getRevision(app v1beta1.Application) string {
if app.Status.LatestRevision == nil {
return ""
}
return app.Status.LatestRevision.Name
}
func getSyncedRevision(rev *model.ApplicationRevision) string {
if rev == nil {
return ""
}
return rev.Version
}

View File

@@ -32,7 +32,9 @@ import (
"github.com/oam-dev/kubevela/apis/core.oam.dev/v1beta1"
"github.com/oam-dev/kubevela/pkg/apiserver/domain/model"
"github.com/oam-dev/kubevela/pkg/apiserver/infrastructure/datastore"
"github.com/oam-dev/kubevela/pkg/apiserver/utils"
"github.com/oam-dev/kubevela/pkg/multicluster"
"github.com/oam-dev/kubevela/pkg/oam"
"github.com/oam-dev/kubevela/pkg/policy"
)
@@ -85,17 +87,19 @@ func FromCRPolicy(appPrimaryKey string, policyCR v1beta1.AppPolicy, creator stri
}
// FromCRWorkflow converts Application CR Workflow section into velaux data store workflow
func FromCRWorkflow(ctx context.Context, cli client.Client, appPrimaryKey string, app *v1beta1.Application) (model.Workflow, []workflowv1alpha1.WorkflowStep, error) {
func FromCRWorkflow(ctx context.Context, cli client.Client, appPrimaryKey string, app *v1beta1.Application, envName string) (model.Workflow, []workflowv1alpha1.WorkflowStep, error) {
var defaultWorkflow = true
name := app.Annotations[oam.AnnotationWorkflowName]
if name == "" {
name = fmt.Sprintf("workflow-%s", envName)
}
dataWf := model.Workflow{
AppPrimaryKey: appPrimaryKey,
// every namespace has a synced env
EnvName: model.AutoGenEnvNamePrefix + app.Namespace,
// every application has a synced workflow
Name: model.AutoGenWorkflowNamePrefix + appPrimaryKey,
Alias: model.AutoGenWorkflowNamePrefix + app.Name,
Description: model.AutoGenDesc,
Default: &defaultWorkflow,
EnvName: envName,
Name: name,
Alias: fmt.Sprintf("%s Workflow", utils.FirstUpper(envName)),
Description: model.AutoGenDesc,
Default: &defaultWorkflow,
}
if app.Spec.Workflow == nil {
return dataWf, nil, nil
@@ -224,6 +228,8 @@ func FromCRWorkflowRecord(app *v1beta1.Application, workflow model.Workflow, rev
Name: strings.Replace(app.Status.Workflow.AppRevision, ":", "-", 1),
Namespace: app.Namespace,
Finished: model.UnFinished,
StartTime: app.Status.Workflow.StartTime.Time,
EndTime: app.Status.Workflow.EndTime.Time,
RevisionPrimaryKey: revision.Version,
Steps: steps,
Status: model.RevisionStatusRunning,
@@ -250,14 +256,30 @@ func FromCRApplicationRevision(ctx context.Context, cli client.Client, app *v1be
if app.Status.Workflow == nil || app.Status.Workflow.AppRevision == "" {
return nil
}
versions := strings.Split(app.Status.Workflow.AppRevision, ":")
versionName := versions[0]
var appRevision v1beta1.ApplicationRevision
ctxTimeout, cancel := context.WithTimeout(ctx, time.Second*20)
defer cancel()
if err := cli.Get(ctxTimeout, types.NamespacedName{Namespace: app.Namespace, Name: versionName}, &appRevision); err != nil {
klog.Errorf("failed to get the application revision %s", err.Error())
return nil
publishVersion := app.Status.Workflow.AppRevision
var appRevision *v1beta1.ApplicationRevision
var appRevisionList v1beta1.ApplicationRevisionList
if err := cli.List(ctx, &appRevisionList,
client.HasLabels{fmt.Sprintf("%s=%s", oam.LabelAppName, app.Name)},
client.InNamespace(app.Namespace)); err == nil && len(appRevisionList.Items) > 0 {
for i, rev := range appRevisionList.Items {
if rev.Annotations[oam.AnnotationPublishVersion] == publishVersion {
appRevision = &appRevisionList.Items[i]
break
}
}
}
if appRevision == nil {
versions := strings.Split(app.Status.Workflow.AppRevision, ":")
versionName := versions[0]
var loadAR v1beta1.ApplicationRevision
ctxTimeout, cancel := context.WithTimeout(ctx, time.Second*20)
defer cancel()
if err := cli.Get(ctxTimeout, types.NamespacedName{Namespace: app.Namespace, Name: versionName}, &loadAR); err != nil {
klog.Errorf("failed to get the application revision %s", err.Error())
return nil
}
appRevision = &loadAR
}
configByte, _ := yaml.Marshal(appRevision.Spec.Application)
return &model.ApplicationRevision{
@@ -268,7 +290,7 @@ func FromCRApplicationRevision(ctx context.Context, cli client.Client, app *v1be
AppPrimaryKey: workflow.AppPrimaryKey,
RevisionCRName: appRevision.Name,
WorkflowName: workflow.Name,
Version: versionName,
Version: appRevision.Name,
ApplyAppConfig: string(configByte),
TriggerType: "SyncFromCR",
EnvName: envName,

View File

@@ -84,9 +84,11 @@ func (c *CR2UX) AddOrUpdate(ctx context.Context, targetApp *v1beta1.Application)
klog.Errorf("Convert App to data store err %v", err)
return err
}
if err = StoreProject(ctx, dsApp.AppMeta.Project, ds, c.projectService); err != nil {
klog.Errorf("get or create project for sync process err %v", err)
return err
if dsApp.Project != nil {
if err = StoreProject(ctx, *dsApp.Project, ds, c.projectService); err != nil {
klog.Errorf("get or create project for sync process err %v", err)
return err
}
}
if err = StoreTargets(ctx, dsApp, ds, c.targetService); err != nil {
@@ -131,7 +133,10 @@ func (c *CR2UX) AddOrUpdate(ctx context.Context, targetApp *v1beta1.Application)
}
// update cache
c.syncCache(dsApp.AppMeta.PrimaryKey(), targetApp.Generation, int64(len(dsApp.Targets)))
key := formatAppComposedName(targetApp.Name, targetApp.Namespace)
syncedVersion := getSyncedRevision(dsApp.Revision)
c.syncCache(key, syncedVersion, int64(len(dsApp.Targets)))
klog.Infof("application %s/%s revision %s synced successful", targetApp.Name, targetApp.Namespace, syncedVersion)
return nil
}

View File

@@ -26,6 +26,7 @@ import (
corev1 "k8s.io/api/core/v1"
"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/pkg/apiserver/domain/model"
"github.com/oam-dev/kubevela/pkg/apiserver/domain/service"
@@ -115,6 +116,8 @@ var _ = Describe("Test CR convert to ux", func() {
app1 := &v1beta1.Application{}
Expect(common2.ReadYamlToObject("testdata/test-app1.yaml", app1)).Should(BeNil())
app1.Namespace = appNS1
envName := model.AutoGenEnvNamePrefix + app1.Namespace
Expect(cr2ux.AddOrUpdate(context.Background(), app1)).Should(BeNil())
comp1 := model.ApplicationComponent{AppPrimaryKey: apName1, Name: "nginx"}
Expect(ds.Get(context.Background(), &comp1)).Should(BeNil())
@@ -128,14 +131,15 @@ var _ = Describe("Test CR convert to ux", func() {
Expect(ds.Get(ctx, &appPlc1)).Should(BeNil())
appPlc2 := model.ApplicationPolicy{AppPrimaryKey: app1.Name, Name: "topology-local"}
Expect(ds.Get(ctx, &appPlc2)).Should(BeNil())
appwf1 := model.Workflow{AppPrimaryKey: app1.Name, Name: model.AutoGenWorkflowNamePrefix + app1.Name}
appwf1 := model.Workflow{AppPrimaryKey: app1.Name, Name: "workflow-" + envName}
Expect(ds.Get(ctx, &appwf1)).Should(BeNil())
Expect(len(appwf1.Steps)).Should(BeEquivalentTo(1))
app2 := &v1beta1.Application{}
Expect(common2.ReadYamlToObject("testdata/test-app2.yaml", app2)).Should(BeNil())
app1.Namespace = appNS1
app1.Generation = 2
app1.Status.LatestRevision = &common.Revision{Name: "v2"}
app1.Spec = app2.Spec
Expect(cr2ux.AddOrUpdate(context.Background(), app1)).Should(BeNil())
comp3 := model.ApplicationComponent{AppPrimaryKey: apName1, Name: "blog"}

View File

@@ -20,8 +20,6 @@ import (
"context"
"errors"
"golang.org/x/text/cases"
"golang.org/x/text/language"
"k8s.io/klog/v2"
"github.com/oam-dev/kubevela/pkg/apiserver/domain/model"
@@ -32,9 +30,23 @@ import (
"github.com/oam-dev/kubevela/pkg/utils"
)
// DataStoreApp is a memory struct that describes the model of an application in datastore
type DataStoreApp struct {
Project *v1.CreateProjectRequest
AppMeta *model.Application
Env *model.Env
Eb *model.EnvBinding
Comps []*model.ApplicationComponent
Policies []*model.ApplicationPolicy
Workflow *model.Workflow
Targets []*model.Target
Record *model.WorkflowRecord
Revision *model.ApplicationRevision
}
// StoreProject will create project for synced application
func StoreProject(ctx context.Context, name string, ds datastore.DataStore, projectService service.ProjectService) error {
err := ds.Get(ctx, &model.Project{Name: name})
func StoreProject(ctx context.Context, project v1.CreateProjectRequest, ds datastore.DataStore, projectService service.ProjectService) error {
err := ds.Get(ctx, &model.Project{Name: project.Name})
if err == nil {
// it means the record already exists, don't need to add anything
return nil
@@ -44,18 +56,16 @@ func StoreProject(ctx context.Context, name string, ds datastore.DataStore, proj
return err
}
if projectService != nil {
_, err := projectService.CreateProject(ctx, v1.CreateProjectRequest{
Name: name,
Alias: cases.Title(language.Und).String(name),
Owner: model.DefaultAdminUserName,
Description: model.AutoGenProj})
project.Owner = model.DefaultAdminUserName
project.Description = model.AutoGenProj
_, err := projectService.CreateProject(ctx, project)
return err
}
return nil
}
// StoreAppMeta will sync application metadata from CR to datastore
func StoreAppMeta(ctx context.Context, app *model.DataStoreApp, ds datastore.DataStore) error {
func StoreAppMeta(ctx context.Context, app *DataStoreApp, ds datastore.DataStore) error {
oldApp := &model.Application{Name: app.AppMeta.Name}
err := ds.Get(ctx, oldApp)
if err == nil {
@@ -71,7 +81,7 @@ func StoreAppMeta(ctx context.Context, app *model.DataStoreApp, ds datastore.Dat
}
// StoreEnv will sync application namespace from CR to datastore env, one namespace belongs to one env
func StoreEnv(ctx context.Context, app *model.DataStoreApp, ds datastore.DataStore, envService service.EnvService) error {
func StoreEnv(ctx context.Context, app *DataStoreApp, ds datastore.DataStore, envService service.EnvService) error {
curEnv := &model.Env{Name: app.Env.Name}
err := ds.Get(ctx, curEnv)
if err == nil {
@@ -235,7 +245,7 @@ func StorePolicy(ctx context.Context, appPrimaryKey string, expPolicies []*model
}
// StoreWorkflow will sync workflow application CR to datastore, it updates the only one workflow from the application with specified name
func StoreWorkflow(ctx context.Context, dsApp *model.DataStoreApp, ds datastore.DataStore) error {
func StoreWorkflow(ctx context.Context, dsApp *DataStoreApp, ds datastore.DataStore) error {
old := &model.Workflow{AppPrimaryKey: dsApp.AppMeta.Name, Name: dsApp.Workflow.Name}
err := ds.Get(ctx, old)
if err == nil {
@@ -251,7 +261,7 @@ func StoreWorkflow(ctx context.Context, dsApp *model.DataStoreApp, ds datastore.
}
// StoreWorkflowRecord will sync workflow status to datastore.
func StoreWorkflowRecord(ctx context.Context, dsApp *model.DataStoreApp, ds datastore.DataStore) error {
func StoreWorkflowRecord(ctx context.Context, dsApp *DataStoreApp, ds datastore.DataStore) error {
if dsApp.Record == nil {
return nil
}
@@ -267,7 +277,7 @@ func StoreWorkflowRecord(ctx context.Context, dsApp *model.DataStoreApp, ds data
}
// StoreApplicationRevision will sync the application revision to datastore.
func StoreApplicationRevision(ctx context.Context, dsApp *model.DataStoreApp, ds datastore.DataStore) error {
func StoreApplicationRevision(ctx context.Context, dsApp *DataStoreApp, ds datastore.DataStore) error {
if dsApp.Revision == nil {
return nil
}
@@ -285,7 +295,7 @@ func StoreApplicationRevision(ctx context.Context, dsApp *model.DataStoreApp, ds
}
// StoreTargets will sync targets from application CR to datastore
func StoreTargets(ctx context.Context, dsApp *model.DataStoreApp, ds datastore.DataStore, targetService service.TargetService) error {
func StoreTargets(ctx context.Context, dsApp *DataStoreApp, ds datastore.DataStore, targetService service.TargetService) error {
for _, t := range dsApp.Targets {
err := ds.Get(ctx, t)
if err == nil {

View File

@@ -87,7 +87,7 @@ func NewDatastore(cfg datastore.Config) (ds datastore.DataStore, err error) {
return nil, fmt.Errorf("create mongodb datastore instance failure %w", err)
}
case "kubeapi":
ds, err = kubeapi.New(context.Background(), cfg)
ds, err = kubeapi.New(context.Background(), cfg, k8sClient)
if err != nil {
return nil, fmt.Errorf("create mongodb datastore instance failure %w", err)
}

View File

@@ -101,7 +101,7 @@ func (a *ApplicationSync) Start(ctx context.Context, errorChan chan error) {
app := getApp(obj)
if app.DeletionTimestamp == nil {
a.Queue.Add(app)
klog.Infof("watched update/add app event, namespace: %s, name: %s", app.Namespace, app.Name)
klog.V(4).Infof("watched update/add app event, namespace: %s, name: %s", app.Namespace, app.Name)
}
}
@@ -114,7 +114,7 @@ func (a *ApplicationSync) Start(ctx context.Context, errorChan chan error) {
},
DeleteFunc: func(obj interface{}) {
app := getApp(obj)
klog.Infof("watched delete app event, namespace: %s, name: %s", app.Namespace, app.Name)
klog.V(4).Infof("watched delete app event, namespace: %s, name: %s", app.Namespace, app.Name)
a.Queue.Forget(app)
a.Queue.Done(app)
err = cu.DeleteApp(ctx, app)

View File

@@ -26,8 +26,10 @@ import (
corev1 "k8s.io/api/core/v1"
"k8s.io/client-go/util/workqueue"
"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/pkg/apiserver/domain/model"
"github.com/oam-dev/kubevela/pkg/apiserver/domain/repository"
"github.com/oam-dev/kubevela/pkg/apiserver/infrastructure/datastore"
"github.com/oam-dev/kubevela/pkg/oam/util"
common2 "github.com/oam-dev/kubevela/pkg/utils/common"
@@ -105,7 +107,7 @@ var _ = Describe("Test Worker CR sync to datastore", func() {
Expect(appPlc1.CreateTime.IsZero()).Should(BeFalse())
appPlc2 := model.ApplicationPolicy{AppPrimaryKey: app1.Name, Name: "topology-local"}
Expect(ds.Get(ctx, &appPlc2)).Should(BeNil())
appwf1 := model.Workflow{AppPrimaryKey: app1.Name, Name: model.AutoGenWorkflowNamePrefix + app1.Name}
appwf1 := model.Workflow{AppPrimaryKey: app1.Name, Name: repository.ConvertWorkflowName(env.Name)}
Expect(ds.Get(ctx, &appwf1)).Should(BeNil())
By("create test app2 and check the syncing results")
@@ -132,6 +134,9 @@ var _ = Describe("Test Worker CR sync to datastore", func() {
app2.Spec = newapp2.Spec
Expect(k8sClient.Update(context.TODO(), app2)).Should(BeNil())
app2.Status.LatestRevision = &common.Revision{Name: "v3"}
Expect(k8sClient.Status().Update(context.TODO(), app2)).Should(BeNil())
Eventually(func() error {
appm := model.ApplicationComponent{AppPrimaryKey: formatAppComposedName(app2.Name, app2.Namespace), Name: "nginx2"}
return ds.Get(ctx, &appm)

Some files were not shown because too many files have changed in this diff Show More