mirror of
https://github.com/kubevela/kubevela.git
synced 2026-03-02 01:30:47 +00:00
Compare commits
63 Commits
v1.3.0-alp
...
v1.3.0-bet
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d3fbd5fd45 | ||
|
|
b0202ebf14 | ||
|
|
295164229d | ||
|
|
ba09cf5c2e | ||
|
|
099e25a552 | ||
|
|
b2f5b69380 | ||
|
|
5d61cee3f6 | ||
|
|
2af198bda7 | ||
|
|
161d2646cb | ||
|
|
4f8e7506f9 | ||
|
|
160ef64855 | ||
|
|
905c49eb81 | ||
|
|
c4a8fcf29c | ||
|
|
e72690bade | ||
|
|
044c4bf73c | ||
|
|
795231ceb5 | ||
|
|
3f621e57b2 | ||
|
|
9fc992ea3e | ||
|
|
c2f5175fd1 | ||
|
|
a7d3cd5d1a | ||
|
|
ccfab6ce7f | ||
|
|
03cd0d144a | ||
|
|
741544c00c | ||
|
|
723a5d83ca | ||
|
|
8af5afa5dc | ||
|
|
aaa1db8760 | ||
|
|
c5baa1cae9 | ||
|
|
eda7e6c5a0 | ||
|
|
e5fd150cd5 | ||
|
|
d041d8c35d | ||
|
|
345e4c8144 | ||
|
|
9a8ec5d797 | ||
|
|
d9a676a688 | ||
|
|
9a3ad7ef84 | ||
|
|
9671e3b232 | ||
|
|
0c97f8311c | ||
|
|
a6460d67b6 | ||
|
|
3ea2ac6d0f | ||
|
|
13c420dada | ||
|
|
6354912bba | ||
|
|
5209be6da9 | ||
|
|
8f9908e723 | ||
|
|
296c82344b | ||
|
|
2d19454a35 | ||
|
|
f67d2db0d1 | ||
|
|
3634ad6f18 | ||
|
|
6c5a40d768 | ||
|
|
c91a7ac273 | ||
|
|
b437cf4310 | ||
|
|
040d5e1776 | ||
|
|
7bec3506f5 | ||
|
|
efc1597317 | ||
|
|
38665e319d | ||
|
|
1e0f329304 | ||
|
|
1300a980f0 | ||
|
|
b6b81c336e | ||
|
|
e2275efe56 | ||
|
|
c054ee32b1 | ||
|
|
19424cfaa4 | ||
|
|
a5fb09814e | ||
|
|
c1b116b360 | ||
|
|
3af893950e | ||
|
|
20583e089a |
4
.github/CODEOWNERS
vendored
4
.github/CODEOWNERS
vendored
@@ -1,7 +1,7 @@
|
||||
# This file is a github code protect rule follow the codeowners https://docs.github.com/en/github/creating-cloning-and-archiving-repositories/creating-a-repository-on-github/about-code-owners#example-of-a-codeowners-file
|
||||
|
||||
* @hongchaodeng @wonderflow @leejanee
|
||||
design/ @hongchaodeng @resouer @wonderflow
|
||||
* @barnettZQG @wonderflow @leejanee
|
||||
design/ @barnettZQG @leejanee @wonderflow
|
||||
|
||||
# Owner of CUE
|
||||
pkg/cue @leejanee @FogDong
|
||||
|
||||
4
.github/workflows/apiserver-test.yaml
vendored
4
.github/workflows/apiserver-test.yaml
vendored
@@ -92,10 +92,10 @@ jobs:
|
||||
kubectl wait --for=condition=Ready pod -l app=source-controller -n flux-system --timeout=600s
|
||||
kubectl wait --for=condition=Ready pod -l app=helm-controller -n flux-system --timeout=600s
|
||||
|
||||
- name: Run apiserver unit test
|
||||
- name: Run api server unit test
|
||||
run: make unit-test-apiserver
|
||||
|
||||
- name: Run apiserver e2e test
|
||||
- name: Run api server e2e test
|
||||
run: |
|
||||
export ALIYUN_ACCESS_KEY_ID=${{ secrets.ALIYUN_ACCESS_KEY_ID }}
|
||||
export ALIYUN_ACCESS_KEY_SECRET=${{ secrets.ALIYUN_ACCESS_KEY_SECRET }}
|
||||
|
||||
6
.github/workflows/issue-commands.yml
vendored
6
.github/workflows/issue-commands.yml
vendored
@@ -14,9 +14,9 @@ jobs:
|
||||
with:
|
||||
repository: "oam-dev/kubevela-github-actions"
|
||||
path: ./actions
|
||||
ref: v0.4.1
|
||||
ref: v0.4.2
|
||||
- name: Install Actions
|
||||
run: npm install --production --prefix ./actions
|
||||
run: npm ci --production --prefix ./actions
|
||||
- name: Run Commands
|
||||
uses: ./actions/commands
|
||||
with:
|
||||
@@ -66,4 +66,4 @@ jobs:
|
||||
uses: zeebe-io/backport-action@v0.0.6
|
||||
with:
|
||||
github_token: ${{ secrets.GITHUB_TOKEN }}
|
||||
github_workspace: ${{ github.workspace }}
|
||||
github_workspace: ${{ github.workspace }}
|
||||
|
||||
3
Makefile
3
Makefile
@@ -19,7 +19,7 @@ unit-test-core:
|
||||
go test -coverprofile=coverage.txt $(shell go list ./pkg/... ./cmd/... ./apis/... | grep -v apiserver)
|
||||
go test $(shell go list ./references/... | grep -v apiserver)
|
||||
unit-test-apiserver:
|
||||
go test -coverprofile=coverage.txt $(shell go list ./pkg/... ./cmd/... | grep -E 'apiserver|velaql')
|
||||
go test -gcflags=all=-l -coverprofile=coverage.txt $(shell go list ./pkg/... ./cmd/... | grep -E 'apiserver|velaql')
|
||||
|
||||
# Build vela cli binary
|
||||
build: fmt vet lint staticcheck vela-cli kubectl-vela
|
||||
@@ -132,5 +132,4 @@ def-install:
|
||||
|
||||
helm-doc-gen: helmdoc
|
||||
readme-generator -v charts/vela-core/values.yaml -r charts/vela-core/README.md
|
||||
cat charts/vela-core/README.md
|
||||
readme-generator -v charts/vela-minimal/values.yaml -r charts/vela-minimal/README.md
|
||||
@@ -7,6 +7,9 @@ Reviewers:
|
||||
- reetasingh
|
||||
- wangwang
|
||||
- evanli18
|
||||
- devholic
|
||||
- fourierr
|
||||
- JooKS-me
|
||||
|
||||
Approvers:
|
||||
- Somefive (Multi-Cluster)
|
||||
@@ -26,6 +29,7 @@ Maintainers:
|
||||
- leejanee
|
||||
- zzxwill
|
||||
- BinaryHB0916
|
||||
- dhiguero
|
||||
|
||||
Emeritus Members:
|
||||
- ryanzhang-oss
|
||||
|
||||
@@ -322,6 +322,23 @@ type PolicyStatus struct {
|
||||
Status *runtime.RawExtension `json:"status,omitempty"`
|
||||
}
|
||||
|
||||
// WorkflowStep defines how to execute a workflow step.
|
||||
type WorkflowStep struct {
|
||||
// Name is the unique name of the workflow step.
|
||||
Name string `json:"name"`
|
||||
|
||||
Type string `json:"type"`
|
||||
|
||||
// +kubebuilder:pruning:PreserveUnknownFields
|
||||
Properties *runtime.RawExtension `json:"properties,omitempty"`
|
||||
|
||||
DependsOn []string `json:"dependsOn,omitempty"`
|
||||
|
||||
Inputs StepInputs `json:"inputs,omitempty"`
|
||||
|
||||
Outputs StepOutputs `json:"outputs,omitempty"`
|
||||
}
|
||||
|
||||
// WorkflowStatus record the status of workflow
|
||||
type WorkflowStatus struct {
|
||||
AppRevision string `json:"appRevision,omitempty"`
|
||||
@@ -605,3 +622,17 @@ func ParseApplicationConditionType(s string) (ApplicationConditionType, error) {
|
||||
}
|
||||
return -1, errors.New("unknown condition type")
|
||||
}
|
||||
|
||||
// ReferredObject the referred Kubernetes object
|
||||
type ReferredObject struct {
|
||||
// +kubebuilder:validation:EmbeddedResource
|
||||
// +kubebuilder:pruning:PreserveUnknownFields
|
||||
runtime.RawExtension `json:",inline"`
|
||||
}
|
||||
|
||||
// ReferredObjectList a list of referred Kubernetes objects
|
||||
type ReferredObjectList struct {
|
||||
// Objects a list of Kubernetes objects.
|
||||
// +optional
|
||||
Objects []ReferredObject `json:"objects,omitempty"`
|
||||
}
|
||||
|
||||
@@ -469,6 +469,44 @@ func (in *RawExtensionPointer) DeepCopy() *RawExtensionPointer {
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *ReferredObject) DeepCopyInto(out *ReferredObject) {
|
||||
*out = *in
|
||||
in.RawExtension.DeepCopyInto(&out.RawExtension)
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ReferredObject.
|
||||
func (in *ReferredObject) DeepCopy() *ReferredObject {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
out := new(ReferredObject)
|
||||
in.DeepCopyInto(out)
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *ReferredObjectList) DeepCopyInto(out *ReferredObjectList) {
|
||||
*out = *in
|
||||
if in.Objects != nil {
|
||||
in, out := &in.Objects, &out.Objects
|
||||
*out = make([]ReferredObject, len(*in))
|
||||
for i := range *in {
|
||||
(*in)[i].DeepCopyInto(&(*out)[i])
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ReferredObjectList.
|
||||
func (in *ReferredObjectList) DeepCopy() *ReferredObjectList {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
out := new(ReferredObjectList)
|
||||
in.DeepCopyInto(out)
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *Revision) DeepCopyInto(out *Revision) {
|
||||
*out = *in
|
||||
@@ -636,6 +674,41 @@ func (in *WorkflowStatus) DeepCopy() *WorkflowStatus {
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *WorkflowStep) DeepCopyInto(out *WorkflowStep) {
|
||||
*out = *in
|
||||
if in.Properties != nil {
|
||||
in, out := &in.Properties, &out.Properties
|
||||
*out = new(runtime.RawExtension)
|
||||
(*in).DeepCopyInto(*out)
|
||||
}
|
||||
if in.DependsOn != nil {
|
||||
in, out := &in.DependsOn, &out.DependsOn
|
||||
*out = make([]string, len(*in))
|
||||
copy(*out, *in)
|
||||
}
|
||||
if in.Inputs != nil {
|
||||
in, out := &in.Inputs, &out.Inputs
|
||||
*out = make(StepInputs, len(*in))
|
||||
copy(*out, *in)
|
||||
}
|
||||
if in.Outputs != nil {
|
||||
in, out := &in.Outputs, &out.Outputs
|
||||
*out = make(StepOutputs, len(*in))
|
||||
copy(*out, *in)
|
||||
}
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new WorkflowStep.
|
||||
func (in *WorkflowStep) DeepCopy() *WorkflowStep {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
out := new(WorkflowStep)
|
||||
in.DeepCopyInto(out)
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *WorkflowStepStatus) DeepCopyInto(out *WorkflowStepStatus) {
|
||||
*out = *in
|
||||
|
||||
74
apis/core.oam.dev/v1alpha1/component_types.go
Normal file
74
apis/core.oam.dev/v1alpha1/component_types.go
Normal file
@@ -0,0 +1,74 @@
|
||||
/*
|
||||
Copyright 2021 The KubeVela Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package v1alpha1
|
||||
|
||||
const (
|
||||
// RefObjectsComponentType refers to the type of ref-objects
|
||||
RefObjectsComponentType = "ref-objects"
|
||||
)
|
||||
|
||||
// RefObjectsComponentSpec defines the spec of ref-objects component
|
||||
type RefObjectsComponentSpec struct {
|
||||
// Objects the referrers to the Kubernetes objects
|
||||
Objects []ObjectReferrer `json:"objects,omitempty"`
|
||||
}
|
||||
|
||||
// ObjectReferrer selects Kubernetes objects
|
||||
type ObjectReferrer struct {
|
||||
// ObjectTypeIdentifier identifies the type of referred objects
|
||||
ObjectTypeIdentifier `json:",inline"`
|
||||
// ObjectSelector select object by name or labelSelector
|
||||
ObjectSelector `json:",inline"`
|
||||
}
|
||||
|
||||
// ObjectTypeIdentifier identifies the scheme of Kubernetes object
|
||||
type ObjectTypeIdentifier struct {
|
||||
// Resource is the resource name of the Kubernetes object.
|
||||
Resource string `json:"resource"`
|
||||
// Group is the API Group of the Kubernetes object.
|
||||
Group string `json:"group"`
|
||||
// LegacyObjectTypeIdentifier is the legacy identifier
|
||||
// Deprecated: use resource/group instead
|
||||
LegacyObjectTypeIdentifier `json:",inline"`
|
||||
}
|
||||
|
||||
// LegacyObjectTypeIdentifier legacy object type identifier
|
||||
type LegacyObjectTypeIdentifier struct {
|
||||
// APIVersion is the APIVersion of the Kubernetes object.
|
||||
APIVersion string `json:"apiVersion"`
|
||||
// APIVersion is the Kind of the Kubernetes object.
|
||||
Kind string `json:"kind"`
|
||||
}
|
||||
|
||||
// ObjectSelector selector for Kubernetes object
|
||||
type ObjectSelector struct {
|
||||
// Name is the name of the Kubernetes object.
|
||||
// If empty, it will inherit the application component's name.
|
||||
Name string `json:"name,omitempty"`
|
||||
// Namespace is the namespace for selecting Kubernetes objects.
|
||||
// If empty, it will inherit the application's namespace.
|
||||
Namespace string `json:"namespace,omitempty"`
|
||||
// Cluster is the cluster for selecting Kubernetes objects.
|
||||
// If empty, it will use the local cluster
|
||||
Cluster string `json:"cluster,omitempty"`
|
||||
// LabelSelector selects Kubernetes objects by labels
|
||||
// Exclusive to "name"
|
||||
LabelSelector map[string]string `json:"labelSelector,omitempty"`
|
||||
// DeprecatedLabelSelector a deprecated alias to LabelSelector
|
||||
// Deprecated: use labelSelector instead.
|
||||
DeprecatedLabelSelector map[string]string `json:"selector,omitempty"`
|
||||
}
|
||||
@@ -20,7 +20,7 @@ import (
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
|
||||
"github.com/oam-dev/kubevela/apis/core.oam.dev/v1beta1"
|
||||
"github.com/oam-dev/kubevela/apis/core.oam.dev/common"
|
||||
)
|
||||
|
||||
// +kubebuilder:object:root=true
|
||||
@@ -61,7 +61,7 @@ type Workflow struct {
|
||||
metav1.TypeMeta `json:",inline"`
|
||||
metav1.ObjectMeta `json:"metadata,omitempty"`
|
||||
|
||||
Steps []v1beta1.WorkflowStep `json:"steps,omitempty"`
|
||||
Steps []common.WorkflowStep `json:"steps,omitempty"`
|
||||
}
|
||||
|
||||
// +kubebuilder:object:root=true
|
||||
|
||||
@@ -25,8 +25,25 @@ const (
|
||||
|
||||
// TopologyPolicySpec defines the spec of topology policy
|
||||
type TopologyPolicySpec struct {
|
||||
Clusters []string `json:"clusters,omitempty"`
|
||||
ClusterSelector map[string]string `json:"clusterSelector,omitempty"`
|
||||
// Placement embeds the selectors for choosing cluster
|
||||
Placement `json:",inline"`
|
||||
// Namespace is the target namespace to deploy in the selected clusters.
|
||||
// +optional
|
||||
Namespace string `json:"namespace,omitempty"`
|
||||
}
|
||||
|
||||
// Placement describes which clusters to be selected in this topology
|
||||
type Placement struct {
|
||||
// Clusters is the names of the clusters to select.
|
||||
Clusters []string `json:"clusters,omitempty"`
|
||||
|
||||
// ClusterLabelSelector is the label selector for clusters.
|
||||
// Exclusive to "clusters"
|
||||
ClusterLabelSelector map[string]string `json:"clusterLabelSelector,omitempty"`
|
||||
|
||||
// DeprecatedClusterSelector is a depreciated alias for ClusterLabelSelector.
|
||||
// Deprecated: Use clusterLabelSelector instead.
|
||||
DeprecatedClusterSelector map[string]string `json:"clusterSelector,omitempty"`
|
||||
}
|
||||
|
||||
// OverridePolicySpec defines the spec of override policy
|
||||
|
||||
@@ -38,6 +38,18 @@ var (
|
||||
AddToScheme = SchemeBuilder.AddToScheme
|
||||
)
|
||||
|
||||
// Policy meta
|
||||
var (
|
||||
PolicyKind = "Policy"
|
||||
PolicyGroupVersionKind = SchemeGroupVersion.WithKind(PolicyKind)
|
||||
)
|
||||
|
||||
// Workflow meta
|
||||
var (
|
||||
WorkflowKind = "Workflow"
|
||||
WorkflowGroupVersionKind = SchemeGroupVersion.WithKind(PolicyKind)
|
||||
)
|
||||
|
||||
func init() {
|
||||
SchemeBuilder.Register(&Policy{}, &PolicyList{})
|
||||
SchemeBuilder.Register(&Workflow{}, &WorkflowList{})
|
||||
|
||||
@@ -25,7 +25,6 @@ import (
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
|
||||
"github.com/oam-dev/kubevela/apis/core.oam.dev/common"
|
||||
"github.com/oam-dev/kubevela/apis/core.oam.dev/v1beta1"
|
||||
)
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
@@ -326,6 +325,21 @@ func (in *GarbageCollectPolicySpec) DeepCopy() *GarbageCollectPolicySpec {
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *LegacyObjectTypeIdentifier) DeepCopyInto(out *LegacyObjectTypeIdentifier) {
|
||||
*out = *in
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new LegacyObjectTypeIdentifier.
|
||||
func (in *LegacyObjectTypeIdentifier) DeepCopy() *LegacyObjectTypeIdentifier {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
out := new(LegacyObjectTypeIdentifier)
|
||||
in.DeepCopyInto(out)
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *NamespaceSelector) DeepCopyInto(out *NamespaceSelector) {
|
||||
*out = *in
|
||||
@@ -348,6 +362,68 @@ func (in *NamespaceSelector) DeepCopy() *NamespaceSelector {
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *ObjectReferrer) DeepCopyInto(out *ObjectReferrer) {
|
||||
*out = *in
|
||||
out.ObjectTypeIdentifier = in.ObjectTypeIdentifier
|
||||
in.ObjectSelector.DeepCopyInto(&out.ObjectSelector)
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ObjectReferrer.
|
||||
func (in *ObjectReferrer) DeepCopy() *ObjectReferrer {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
out := new(ObjectReferrer)
|
||||
in.DeepCopyInto(out)
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *ObjectSelector) DeepCopyInto(out *ObjectSelector) {
|
||||
*out = *in
|
||||
if in.LabelSelector != nil {
|
||||
in, out := &in.LabelSelector, &out.LabelSelector
|
||||
*out = make(map[string]string, len(*in))
|
||||
for key, val := range *in {
|
||||
(*out)[key] = val
|
||||
}
|
||||
}
|
||||
if in.DeprecatedLabelSelector != nil {
|
||||
in, out := &in.DeprecatedLabelSelector, &out.DeprecatedLabelSelector
|
||||
*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 ObjectSelector.
|
||||
func (in *ObjectSelector) DeepCopy() *ObjectSelector {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
out := new(ObjectSelector)
|
||||
in.DeepCopyInto(out)
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *ObjectTypeIdentifier) DeepCopyInto(out *ObjectTypeIdentifier) {
|
||||
*out = *in
|
||||
out.LegacyObjectTypeIdentifier = in.LegacyObjectTypeIdentifier
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ObjectTypeIdentifier.
|
||||
func (in *ObjectTypeIdentifier) DeepCopy() *ObjectTypeIdentifier {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
out := new(ObjectTypeIdentifier)
|
||||
in.DeepCopyInto(out)
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *OverridePolicySpec) DeepCopyInto(out *OverridePolicySpec) {
|
||||
*out = *in
|
||||
@@ -375,6 +451,40 @@ func (in *OverridePolicySpec) DeepCopy() *OverridePolicySpec {
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *Placement) DeepCopyInto(out *Placement) {
|
||||
*out = *in
|
||||
if in.Clusters != nil {
|
||||
in, out := &in.Clusters, &out.Clusters
|
||||
*out = make([]string, len(*in))
|
||||
copy(*out, *in)
|
||||
}
|
||||
if in.ClusterLabelSelector != nil {
|
||||
in, out := &in.ClusterLabelSelector, &out.ClusterLabelSelector
|
||||
*out = make(map[string]string, len(*in))
|
||||
for key, val := range *in {
|
||||
(*out)[key] = val
|
||||
}
|
||||
}
|
||||
if in.DeprecatedClusterSelector != nil {
|
||||
in, out := &in.DeprecatedClusterSelector, &out.DeprecatedClusterSelector
|
||||
*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 Placement.
|
||||
func (in *Placement) DeepCopy() *Placement {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
out := new(Placement)
|
||||
in.DeepCopyInto(out)
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *PlacementDecision) DeepCopyInto(out *PlacementDecision) {
|
||||
*out = *in
|
||||
@@ -453,22 +563,33 @@ func (in *PolicyList) DeepCopyObject() runtime.Object {
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *TopologyPolicySpec) DeepCopyInto(out *TopologyPolicySpec) {
|
||||
func (in *RefObjectsComponentSpec) DeepCopyInto(out *RefObjectsComponentSpec) {
|
||||
*out = *in
|
||||
if in.Clusters != nil {
|
||||
in, out := &in.Clusters, &out.Clusters
|
||||
*out = make([]string, len(*in))
|
||||
copy(*out, *in)
|
||||
}
|
||||
if in.ClusterSelector != nil {
|
||||
in, out := &in.ClusterSelector, &out.ClusterSelector
|
||||
*out = make(map[string]string, len(*in))
|
||||
for key, val := range *in {
|
||||
(*out)[key] = val
|
||||
if in.Objects != nil {
|
||||
in, out := &in.Objects, &out.Objects
|
||||
*out = make([]ObjectReferrer, len(*in))
|
||||
for i := range *in {
|
||||
(*in)[i].DeepCopyInto(&(*out)[i])
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new RefObjectsComponentSpec.
|
||||
func (in *RefObjectsComponentSpec) DeepCopy() *RefObjectsComponentSpec {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
out := new(RefObjectsComponentSpec)
|
||||
in.DeepCopyInto(out)
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *TopologyPolicySpec) DeepCopyInto(out *TopologyPolicySpec) {
|
||||
*out = *in
|
||||
in.Placement.DeepCopyInto(&out.Placement)
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new TopologyPolicySpec.
|
||||
func (in *TopologyPolicySpec) DeepCopy() *TopologyPolicySpec {
|
||||
if in == nil {
|
||||
@@ -486,7 +607,7 @@ func (in *Workflow) DeepCopyInto(out *Workflow) {
|
||||
in.ObjectMeta.DeepCopyInto(&out.ObjectMeta)
|
||||
if in.Steps != nil {
|
||||
in, out := &in.Steps, &out.Steps
|
||||
*out = make([]v1beta1.WorkflowStep, len(*in))
|
||||
*out = make([]common.WorkflowStep, len(*in))
|
||||
for i := range *in {
|
||||
(*in)[i].DeepCopyInto(&(*out)[i])
|
||||
}
|
||||
|
||||
@@ -50,21 +50,7 @@ type AppPolicy struct {
|
||||
}
|
||||
|
||||
// WorkflowStep defines how to execute a workflow step.
|
||||
type WorkflowStep struct {
|
||||
// Name is the unique name of the workflow step.
|
||||
Name string `json:"name"`
|
||||
|
||||
Type string `json:"type"`
|
||||
|
||||
// +kubebuilder:pruning:PreserveUnknownFields
|
||||
Properties *runtime.RawExtension `json:"properties,omitempty"`
|
||||
|
||||
DependsOn []string `json:"dependsOn,omitempty"`
|
||||
|
||||
Inputs common.StepInputs `json:"inputs,omitempty"`
|
||||
|
||||
Outputs common.StepOutputs `json:"outputs,omitempty"`
|
||||
}
|
||||
type WorkflowStep common.WorkflowStep
|
||||
|
||||
// Workflow defines workflow steps and other attributes
|
||||
type Workflow struct {
|
||||
|
||||
@@ -17,11 +17,10 @@
|
||||
package v1beta1
|
||||
|
||||
import (
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
|
||||
"github.com/oam-dev/kubevela/apis/core.oam.dev/common"
|
||||
"github.com/oam-dev/kubevela/apis/core.oam.dev/v1alpha1"
|
||||
)
|
||||
|
||||
// NOTE: json tags are required. Any new fields you add must have json tags for the fields to be serialized.
|
||||
@@ -52,19 +51,23 @@ type ApplicationRevisionSpec struct {
|
||||
// ScopeGVK records the apiVersion to GVK mapping
|
||||
ScopeGVK map[string]metav1.GroupVersionKind `json:"scopeGVK,omitempty"`
|
||||
|
||||
// Components records the rendered components from Application, it will contains the whole K8s CR of workload in it.
|
||||
// +deprecated
|
||||
Components []common.RawComponent `json:"components,omitempty"`
|
||||
// Policies records the external policies
|
||||
Policies map[string]v1alpha1.Policy `json:"policies,omitempty"`
|
||||
|
||||
// ApplicationConfiguration records the rendered applicationConfiguration from Application,
|
||||
// it will contains the whole K8s CR of trait and the reference component in it.
|
||||
// +kubebuilder:validation:EmbeddedResource
|
||||
// Workflow records the external workflow
|
||||
Workflow *v1alpha1.Workflow `json:"workflow,omitempty"`
|
||||
|
||||
// ReferredObjects records the referred objects used in the ref-object typed components
|
||||
// +kubebuilder:pruning:PreserveUnknownFields
|
||||
// +deprecated
|
||||
ApplicationConfiguration runtime.RawExtension `json:"applicationConfiguration,omitempty"`
|
||||
ReferredObjects []common.ReferredObject `json:"referredObjects,omitempty"`
|
||||
}
|
||||
|
||||
// ResourcesConfigMap references the ConfigMap that's generated to contain all final rendered resources.
|
||||
ResourcesConfigMap corev1.LocalObjectReference `json:"resourcesConfigMap,omitempty"`
|
||||
// ApplicationRevisionStatus is the status of ApplicationRevision
|
||||
type ApplicationRevisionStatus struct {
|
||||
// Succeeded records if the workflow finished running with success
|
||||
Succeeded bool `json:"succeeded"`
|
||||
// Workflow the running status of the workflow
|
||||
Workflow *common.WorkflowStatus `json:"workflow,omitempty"`
|
||||
}
|
||||
|
||||
// +kubebuilder:object:root=true
|
||||
@@ -72,14 +75,18 @@ type ApplicationRevisionSpec struct {
|
||||
// ApplicationRevision is the Schema for the ApplicationRevision API
|
||||
// +kubebuilder:storageversion
|
||||
// +kubebuilder:resource:categories={oam},shortName=apprev
|
||||
// +kubebuilder:subresource:status
|
||||
// +kubebuilder:printcolumn:name="AGE",type=date,JSONPath=".metadata.creationTimestamp"
|
||||
// +kubebuilder:printcolumn:name="PUBLISH_VERSION",type=string,JSONPath=`.metadata.annotations['app\.oam\.dev\/publishVersion']`
|
||||
// +kubebuilder:printcolumn:name="SUCCEEDED",type=string,JSONPath=`.status.succeeded`
|
||||
// +genclient
|
||||
// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object
|
||||
type ApplicationRevision struct {
|
||||
metav1.TypeMeta `json:",inline"`
|
||||
metav1.ObjectMeta `json:"metadata,omitempty"`
|
||||
|
||||
Spec ApplicationRevisionSpec `json:"spec,omitempty"`
|
||||
Spec ApplicationRevisionSpec `json:"spec,omitempty"`
|
||||
Status ApplicationRevisionStatus `json:"status,omitempty"`
|
||||
}
|
||||
|
||||
// +kubebuilder:object:root=true
|
||||
|
||||
@@ -26,6 +26,7 @@ import (
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
|
||||
"github.com/oam-dev/kubevela/apis/core.oam.dev/common"
|
||||
"github.com/oam-dev/kubevela/apis/core.oam.dev/v1alpha1"
|
||||
)
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
@@ -113,6 +114,7 @@ func (in *ApplicationRevision) DeepCopyInto(out *ApplicationRevision) {
|
||||
out.TypeMeta = in.TypeMeta
|
||||
in.ObjectMeta.DeepCopyInto(&out.ObjectMeta)
|
||||
in.Spec.DeepCopyInto(&out.Spec)
|
||||
in.Status.DeepCopyInto(&out.Status)
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ApplicationRevision.
|
||||
@@ -218,15 +220,25 @@ func (in *ApplicationRevisionSpec) DeepCopyInto(out *ApplicationRevisionSpec) {
|
||||
(*out)[key] = val
|
||||
}
|
||||
}
|
||||
if in.Components != nil {
|
||||
in, out := &in.Components, &out.Components
|
||||
*out = make([]common.RawComponent, len(*in))
|
||||
if in.Policies != nil {
|
||||
in, out := &in.Policies, &out.Policies
|
||||
*out = make(map[string]v1alpha1.Policy, len(*in))
|
||||
for key, val := range *in {
|
||||
(*out)[key] = *val.DeepCopy()
|
||||
}
|
||||
}
|
||||
if in.Workflow != nil {
|
||||
in, out := &in.Workflow, &out.Workflow
|
||||
*out = new(v1alpha1.Workflow)
|
||||
(*in).DeepCopyInto(*out)
|
||||
}
|
||||
if in.ReferredObjects != nil {
|
||||
in, out := &in.ReferredObjects, &out.ReferredObjects
|
||||
*out = make([]common.ReferredObject, len(*in))
|
||||
for i := range *in {
|
||||
(*in)[i].DeepCopyInto(&(*out)[i])
|
||||
}
|
||||
}
|
||||
in.ApplicationConfiguration.DeepCopyInto(&out.ApplicationConfiguration)
|
||||
out.ResourcesConfigMap = in.ResourcesConfigMap
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ApplicationRevisionSpec.
|
||||
@@ -239,6 +251,26 @@ func (in *ApplicationRevisionSpec) DeepCopy() *ApplicationRevisionSpec {
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *ApplicationRevisionStatus) DeepCopyInto(out *ApplicationRevisionStatus) {
|
||||
*out = *in
|
||||
if in.Workflow != nil {
|
||||
in, out := &in.Workflow, &out.Workflow
|
||||
*out = new(common.WorkflowStatus)
|
||||
(*in).DeepCopyInto(*out)
|
||||
}
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ApplicationRevisionStatus.
|
||||
func (in *ApplicationRevisionStatus) DeepCopy() *ApplicationRevisionStatus {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
out := new(ApplicationRevisionStatus)
|
||||
in.DeepCopyInto(out)
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *ApplicationSpec) DeepCopyInto(out *ApplicationSpec) {
|
||||
*out = *in
|
||||
|
||||
31
apis/types/multicluster.go
Normal file
31
apis/types/multicluster.go
Normal file
@@ -0,0 +1,31 @@
|
||||
/*
|
||||
Copyright 2021 The KubeVela Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package types
|
||||
|
||||
import "github.com/oam-dev/cluster-gateway/pkg/apis/cluster/v1alpha1"
|
||||
|
||||
const (
|
||||
// CredentialTypeInternal identifies the virtual cluster from internal kubevela system
|
||||
CredentialTypeInternal v1alpha1.CredentialType = "Internal"
|
||||
// CredentialTypeOCMManagedCluster identifies the virtual cluster from ocm
|
||||
CredentialTypeOCMManagedCluster v1alpha1.CredentialType = "ManagedCluster"
|
||||
// ClusterBlankEndpoint identifies the endpoint of a cluster as blank (not available)
|
||||
ClusterBlankEndpoint = "-"
|
||||
|
||||
// ClustersArg indicates the argument for specific clusters to install addon
|
||||
ClustersArg = "clusters"
|
||||
)
|
||||
@@ -41,6 +41,8 @@ var DefaultKubeVelaNS = "vela-system"
|
||||
const (
|
||||
// AnnoDefinitionDescription is the annotation which describe what is the capability used for in a WorkloadDefinition/TraitDefinition Object
|
||||
AnnoDefinitionDescription = "definition.oam.dev/description"
|
||||
// AnnoDefinitionIcon is the annotation which describe the icon url
|
||||
AnnoDefinitionIcon = "definition.oam.dev/icon"
|
||||
// AnnoDefinitionAppliedWorkloads is the annotation which describe what is the workloads used for in a TraitDefinition Object
|
||||
AnnoDefinitionAppliedWorkloads = "definition.oam.dev/appliedWorkloads"
|
||||
// LabelDefinition is the label for definition
|
||||
|
||||
@@ -2025,6 +2025,12 @@ spec:
|
||||
- jsonPath: .metadata.creationTimestamp
|
||||
name: AGE
|
||||
type: date
|
||||
- jsonPath: .metadata.annotations['app\.oam\.dev\/publishVersion']
|
||||
name: PUBLISH_VERSION
|
||||
type: string
|
||||
- jsonPath: .status.succeeded
|
||||
name: SUCCEEDED
|
||||
type: string
|
||||
name: v1beta1
|
||||
schema:
|
||||
openAPIV3Schema:
|
||||
@@ -2747,13 +2753,6 @@ spec:
|
||||
type: object
|
||||
type: object
|
||||
type: object
|
||||
applicationConfiguration:
|
||||
description: ApplicationConfiguration records the rendered applicationConfiguration
|
||||
from Application, it will contains the whole K8s CR of trait and
|
||||
the reference component in it.
|
||||
type: object
|
||||
x-kubernetes-embedded-resource: true
|
||||
x-kubernetes-preserve-unknown-fields: true
|
||||
componentDefinitions:
|
||||
additionalProperties:
|
||||
description: ComponentDefinition is the Schema for the componentdefinitions
|
||||
@@ -3087,20 +3086,51 @@ spec:
|
||||
description: ComponentDefinitions records the snapshot of the componentDefinitions
|
||||
related with the created/modified Application
|
||||
type: object
|
||||
components:
|
||||
description: Components records the rendered components from Application,
|
||||
it will contains the whole K8s CR of workload in it.
|
||||
items:
|
||||
description: RawComponent record raw component
|
||||
policies:
|
||||
additionalProperties:
|
||||
description: Policy is the Schema for the policy API
|
||||
properties:
|
||||
raw:
|
||||
apiVersion:
|
||||
description: 'APIVersion defines the versioned schema of this
|
||||
representation of an object. Servers should convert recognized
|
||||
schemas to the latest internal value, and may reject unrecognized
|
||||
values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources'
|
||||
type: string
|
||||
kind:
|
||||
description: 'Kind is a string value representing the REST resource
|
||||
this object represents. Servers may infer this from the endpoint
|
||||
the client submits requests to. Cannot be updated. In CamelCase.
|
||||
More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds'
|
||||
type: string
|
||||
metadata:
|
||||
properties:
|
||||
annotations:
|
||||
additionalProperties:
|
||||
type: string
|
||||
type: object
|
||||
finalizers:
|
||||
items:
|
||||
type: string
|
||||
type: array
|
||||
labels:
|
||||
additionalProperties:
|
||||
type: string
|
||||
type: object
|
||||
name:
|
||||
type: string
|
||||
namespace:
|
||||
type: string
|
||||
type: object
|
||||
properties:
|
||||
type: object
|
||||
x-kubernetes-embedded-resource: true
|
||||
x-kubernetes-preserve-unknown-fields: true
|
||||
type:
|
||||
type: string
|
||||
required:
|
||||
- raw
|
||||
- type
|
||||
type: object
|
||||
type: array
|
||||
description: Policies records the external policies
|
||||
type: object
|
||||
policyDefinitions:
|
||||
additionalProperties:
|
||||
description: PolicyDefinition is the Schema for the policydefinitions
|
||||
@@ -3377,15 +3407,16 @@ spec:
|
||||
description: PolicyDefinitions records the snapshot of the PolicyDefinitions
|
||||
related with the created/modified Application
|
||||
type: object
|
||||
resourcesConfigMap:
|
||||
description: ResourcesConfigMap references the ConfigMap that's generated
|
||||
to contain all final rendered resources.
|
||||
properties:
|
||||
name:
|
||||
description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names
|
||||
TODO: Add other useful fields. apiVersion, kind, uid?'
|
||||
type: string
|
||||
type: object
|
||||
referredObjects:
|
||||
description: ReferredObjects records the referred objects used in
|
||||
the ref-object typed components
|
||||
items:
|
||||
description: ReferredObject the referred Kubernetes object
|
||||
type: object
|
||||
x-kubernetes-embedded-resource: true
|
||||
x-kubernetes-preserve-unknown-fields: true
|
||||
type: array
|
||||
x-kubernetes-preserve-unknown-fields: true
|
||||
scopeDefinitions:
|
||||
additionalProperties:
|
||||
description: A ScopeDefinition registers a kind of Kubernetes custom
|
||||
@@ -3819,6 +3850,89 @@ spec:
|
||||
description: TraitDefinitions records the snapshot of the traitDefinitions
|
||||
related with the created/modified Application
|
||||
type: object
|
||||
workflow:
|
||||
description: Workflow records the external workflow
|
||||
properties:
|
||||
apiVersion:
|
||||
description: 'APIVersion defines the versioned schema of this
|
||||
representation of an object. Servers should convert recognized
|
||||
schemas to the latest internal value, and may reject unrecognized
|
||||
values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources'
|
||||
type: string
|
||||
kind:
|
||||
description: 'Kind is a string value representing the REST resource
|
||||
this object represents. Servers may infer this from the endpoint
|
||||
the client submits requests to. Cannot be updated. In CamelCase.
|
||||
More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds'
|
||||
type: string
|
||||
metadata:
|
||||
properties:
|
||||
annotations:
|
||||
additionalProperties:
|
||||
type: string
|
||||
type: object
|
||||
finalizers:
|
||||
items:
|
||||
type: string
|
||||
type: array
|
||||
labels:
|
||||
additionalProperties:
|
||||
type: string
|
||||
type: object
|
||||
name:
|
||||
type: string
|
||||
namespace:
|
||||
type: string
|
||||
type: object
|
||||
steps:
|
||||
items:
|
||||
description: WorkflowStep defines how to execute a workflow
|
||||
step.
|
||||
properties:
|
||||
dependsOn:
|
||||
items:
|
||||
type: string
|
||||
type: array
|
||||
inputs:
|
||||
description: StepInputs defines variable input of WorkflowStep
|
||||
items:
|
||||
properties:
|
||||
from:
|
||||
type: string
|
||||
parameterKey:
|
||||
type: string
|
||||
required:
|
||||
- from
|
||||
- parameterKey
|
||||
type: object
|
||||
type: array
|
||||
name:
|
||||
description: Name is the unique name of the workflow step.
|
||||
type: string
|
||||
outputs:
|
||||
description: StepOutputs defines output variable of WorkflowStep
|
||||
items:
|
||||
properties:
|
||||
name:
|
||||
type: string
|
||||
valueFrom:
|
||||
type: string
|
||||
required:
|
||||
- name
|
||||
- valueFrom
|
||||
type: object
|
||||
type: array
|
||||
properties:
|
||||
type: object
|
||||
x-kubernetes-preserve-unknown-fields: true
|
||||
type:
|
||||
type: string
|
||||
required:
|
||||
- name
|
||||
- type
|
||||
type: object
|
||||
type: array
|
||||
type: object
|
||||
workflowStepDefinitions:
|
||||
additionalProperties:
|
||||
description: WorkflowStepDefinition is the Schema for the workflowstepdefinitions
|
||||
@@ -4408,10 +4522,182 @@ spec:
|
||||
required:
|
||||
- application
|
||||
type: object
|
||||
status:
|
||||
description: ApplicationRevisionStatus is the status of ApplicationRevision
|
||||
properties:
|
||||
succeeded:
|
||||
description: Succeeded records if the workflow finished running with
|
||||
success
|
||||
type: boolean
|
||||
workflow:
|
||||
description: Workflow the running status of the workflow
|
||||
properties:
|
||||
appRevision:
|
||||
type: string
|
||||
contextBackend:
|
||||
description: 'ObjectReference contains enough information to let
|
||||
you inspect or modify the referred object. --- New uses of this
|
||||
type are discouraged because of difficulty describing its usage
|
||||
when embedded in APIs. 1. Ignored fields. It includes many
|
||||
fields which are not generally honored. For instance, ResourceVersion
|
||||
and FieldPath are both very rarely valid in actual usage. 2.
|
||||
Invalid usage help. It is impossible to add specific help for
|
||||
individual usage. In most embedded usages, there are particular restrictions
|
||||
like, "must refer only to types A and B" or "UID not honored"
|
||||
or "name must be restricted". Those cannot be well described
|
||||
when embedded. 3. Inconsistent validation. Because the usages
|
||||
are different, the validation rules are different by usage,
|
||||
which makes it hard for users to predict what will happen. 4.
|
||||
The fields are both imprecise and overly precise. Kind is not
|
||||
a precise mapping to a URL. This can produce ambiguity during
|
||||
interpretation and require a REST mapping. In most cases, the
|
||||
dependency is on the group,resource tuple and the version
|
||||
of the actual struct is irrelevant. 5. We cannot easily change
|
||||
it. Because this type is embedded in many locations, updates
|
||||
to this type will affect numerous schemas. Don''t make
|
||||
new APIs embed an underspecified API type they do not control.
|
||||
Instead of using this type, create a locally provided and used
|
||||
type that is well-focused on your reference. For example, ServiceReferences
|
||||
for admission registration: https://github.com/kubernetes/api/blob/release-1.17/admissionregistration/v1/types.go#L533
|
||||
.'
|
||||
properties:
|
||||
apiVersion:
|
||||
description: API version of the referent.
|
||||
type: string
|
||||
fieldPath:
|
||||
description: 'If referring to a piece of an object instead
|
||||
of an entire object, this string should contain a valid
|
||||
JSON/Go field access statement, such as desiredState.manifest.containers[2].
|
||||
For example, if the object reference is to a container within
|
||||
a pod, this would take on a value like: "spec.containers{name}"
|
||||
(where "name" refers to the name of the container that triggered
|
||||
the event) or if no container name is specified "spec.containers[2]"
|
||||
(container with index 2 in this pod). This syntax is chosen
|
||||
only to have some well-defined way of referencing a part
|
||||
of an object. TODO: this design is not final and this field
|
||||
is subject to change in the future.'
|
||||
type: string
|
||||
kind:
|
||||
description: 'Kind of the referent. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds'
|
||||
type: string
|
||||
name:
|
||||
description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names'
|
||||
type: string
|
||||
namespace:
|
||||
description: 'Namespace of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/namespaces/'
|
||||
type: string
|
||||
resourceVersion:
|
||||
description: 'Specific resourceVersion to which this reference
|
||||
is made, if any. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#concurrency-control-and-consistency'
|
||||
type: string
|
||||
uid:
|
||||
description: 'UID of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#uids'
|
||||
type: string
|
||||
type: object
|
||||
finished:
|
||||
type: boolean
|
||||
message:
|
||||
type: string
|
||||
mode:
|
||||
description: WorkflowMode describes the mode of workflow
|
||||
type: string
|
||||
startTime:
|
||||
format: date-time
|
||||
type: string
|
||||
steps:
|
||||
items:
|
||||
description: WorkflowStepStatus record the status of a workflow
|
||||
step
|
||||
properties:
|
||||
firstExecuteTime:
|
||||
description: FirstExecuteTime is the first time this step
|
||||
execution.
|
||||
format: date-time
|
||||
type: string
|
||||
id:
|
||||
type: string
|
||||
lastExecuteTime:
|
||||
description: LastExecuteTime is the last time this step
|
||||
execution.
|
||||
format: date-time
|
||||
type: string
|
||||
message:
|
||||
description: A human readable message indicating details
|
||||
about why the workflowStep is in this state.
|
||||
type: string
|
||||
name:
|
||||
type: string
|
||||
phase:
|
||||
description: WorkflowStepPhase describes the phase of a
|
||||
workflow step.
|
||||
type: string
|
||||
reason:
|
||||
description: A brief CamelCase message indicating details
|
||||
about why the workflowStep is in this state.
|
||||
type: string
|
||||
subSteps:
|
||||
description: SubStepsStatus record the status of workflow
|
||||
steps.
|
||||
properties:
|
||||
mode:
|
||||
description: WorkflowMode describes the mode of workflow
|
||||
type: string
|
||||
stepIndex:
|
||||
type: integer
|
||||
steps:
|
||||
items:
|
||||
description: WorkflowSubStepStatus record the status
|
||||
of a workflow step
|
||||
properties:
|
||||
id:
|
||||
type: string
|
||||
message:
|
||||
description: A human readable message indicating
|
||||
details about why the workflowStep is in this
|
||||
state.
|
||||
type: string
|
||||
name:
|
||||
type: string
|
||||
phase:
|
||||
description: WorkflowStepPhase describes the phase
|
||||
of a workflow step.
|
||||
type: string
|
||||
reason:
|
||||
description: A brief CamelCase message indicating
|
||||
details about why the workflowStep is in this
|
||||
state.
|
||||
type: string
|
||||
type:
|
||||
type: string
|
||||
required:
|
||||
- id
|
||||
type: object
|
||||
type: array
|
||||
type: object
|
||||
type:
|
||||
type: string
|
||||
required:
|
||||
- id
|
||||
type: object
|
||||
type: array
|
||||
suspend:
|
||||
type: boolean
|
||||
terminated:
|
||||
type: boolean
|
||||
required:
|
||||
- finished
|
||||
- mode
|
||||
- suspend
|
||||
- terminated
|
||||
type: object
|
||||
required:
|
||||
- succeeded
|
||||
type: object
|
||||
type: object
|
||||
served: true
|
||||
storage: true
|
||||
subresources: {}
|
||||
subresources:
|
||||
status: {}
|
||||
status:
|
||||
acceptedNames:
|
||||
kind: ""
|
||||
|
||||
@@ -7,10 +7,8 @@ data:
|
||||
registries: '{
|
||||
"KubeVela":{
|
||||
"name": "KubeVela",
|
||||
"oss": {
|
||||
"end_point": "https://addons.kubevela.net",
|
||||
"bucket": "",
|
||||
"path": ""
|
||||
"helm": {
|
||||
"url": "https://addons.kubevela.net",
|
||||
}
|
||||
}
|
||||
}'
|
||||
|
||||
@@ -89,9 +89,19 @@ spec:
|
||||
{{ end }}
|
||||
---
|
||||
{{ if .Values.multicluster.enabled }}
|
||||
# 1. Check whether APIService ""v1alpha1.cluster.core.oam.dev" is already present in the cluster
|
||||
# 2.a If the APIService doesn't exist, create it.
|
||||
# 2.b If the APIService exists without helm-chart related annotation, skip creating it to the
|
||||
# cluster because the APIService can be managed by an external controller.
|
||||
# 2.c If the APIService exists with valid helm-chart annotations, which means that the APIService
|
||||
# is previously managed by helm commands, hence update the APIService consistently.
|
||||
{{ $apiSvc := (lookup "apiregistration.k8s.io/v1" "APIService" "" "v1alpha1.cluster.core.oam.dev") }}
|
||||
{{ $shouldAdopt := (not $apiSvc) }}
|
||||
{{ if not $shouldAdopt }}{{ $shouldAdopt = (index ($apiSvc).metadata.annotations "meta.helm.sh/release-name") }}{{ end }}
|
||||
{{ if not $shouldAdopt }}
|
||||
{{ if $apiSvc.metadata.annotations }}
|
||||
{{ $shouldAdopt = (index ($apiSvc).metadata.annotations "meta.helm.sh/release-name") }}
|
||||
{{ end }}
|
||||
{{ end }}
|
||||
{{ if $shouldAdopt }}
|
||||
apiVersion: apiregistration.k8s.io/v1
|
||||
kind: APIService
|
||||
|
||||
@@ -16,17 +16,20 @@ spec:
|
||||
schematic:
|
||||
cue:
|
||||
template: |
|
||||
// +patchStrategy=jsonMergePatch
|
||||
patch: {
|
||||
metadata: annotations: {
|
||||
for k, v in parameter {
|
||||
"\(k)": v
|
||||
}
|
||||
}
|
||||
spec: template: metadata: annotations: {
|
||||
for k, v in parameter {
|
||||
"\(k)": v
|
||||
if context.output.spec != _|_ && context.output.spec.template != _|_ {
|
||||
spec: template: metadata: annotations: {
|
||||
for k, v in parameter {
|
||||
"\(k)": v
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
parameter: [string]: string
|
||||
parameter: [string]: string | null
|
||||
|
||||
|
||||
@@ -8,6 +8,8 @@ metadata:
|
||||
name: gateway
|
||||
namespace: {{ include "systemDefinitionNamespace" . }}
|
||||
spec:
|
||||
appliesToWorkloads:
|
||||
- '*'
|
||||
podDisruptive: false
|
||||
schematic:
|
||||
cue:
|
||||
|
||||
@@ -0,0 +1,49 @@
|
||||
# Code generated by KubeVela templates. DO NOT EDIT. Please edit the original cue file.
|
||||
# Definition source cue file: vela-templates/definitions/internal/generate-jdbc-connection.cue
|
||||
apiVersion: core.oam.dev/v1beta1
|
||||
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:
|
||||
schematic:
|
||||
cue:
|
||||
template: |
|
||||
import (
|
||||
"vela/op"
|
||||
"encoding/base64"
|
||||
)
|
||||
|
||||
output: op.#Read & {
|
||||
value: {
|
||||
apiVersion: "v1"
|
||||
kind: "Secret"
|
||||
metadata: {
|
||||
name: parameter.name
|
||||
if parameter.namespace != _|_ {
|
||||
namespace: parameter.namespace
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
dbHost: op.#ConvertString & {bt: base64.Decode(null, output.value.data["DB_HOST"])}
|
||||
dbPort: op.#ConvertString & {bt: base64.Decode(null, output.value.data["DB_PORT"])}
|
||||
dbName: op.#ConvertString & {bt: base64.Decode(null, output.value.data["DB_NAME"])}
|
||||
username: op.#ConvertString & {bt: base64.Decode(null, output.value.data["DB_USER"])}
|
||||
password: op.#ConvertString & {bt: base64.Decode(null, output.value.data["DB_PASSWORD"])}
|
||||
env: [
|
||||
{name: "url", value: "jdbc://" + dbHost.str + ":" + dbPort.str + "/" + dbName.str + "?characterEncoding=utf8&useSSL=false"},
|
||||
{name: "username", value: username.str},
|
||||
{name: "password", value: password.str},
|
||||
]
|
||||
parameter: {
|
||||
// +usage=Specify the name of the secret generated by database component
|
||||
name: string
|
||||
// +usage=Specify the namespace of the secret generated by database component
|
||||
namespace?: string
|
||||
}
|
||||
|
||||
@@ -16,17 +16,20 @@ spec:
|
||||
schematic:
|
||||
cue:
|
||||
template: |
|
||||
// +patchStrategy=jsonMergePatch
|
||||
patch: {
|
||||
metadata: labels: {
|
||||
for k, v in parameter {
|
||||
"\(k)": v
|
||||
}
|
||||
}
|
||||
spec: template: metadata: labels: {
|
||||
for k, v in parameter {
|
||||
"\(k)": v
|
||||
if context.output.spec != _|_ && context.output.spec.template != _|_ {
|
||||
spec: template: metadata: labels: {
|
||||
for k, v in parameter {
|
||||
"\(k)": v
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
parameter: [string]: string
|
||||
parameter: [string]: string | null
|
||||
|
||||
|
||||
@@ -291,8 +291,10 @@ spec:
|
||||
if parameter.email.from.password.value != _|_ {
|
||||
email1: op.#SendEmail & {
|
||||
from: {
|
||||
address: parameter.email.from.value
|
||||
alias: parameter.email.from.alias
|
||||
address: parameter.email.from.address
|
||||
if parameter.email.from.alias != _|_ {
|
||||
alias: parameter.email.from.alias
|
||||
}
|
||||
password: parameter.email.from.password.value
|
||||
host: parameter.email.from.host
|
||||
port: parameter.email.from.port
|
||||
@@ -318,8 +320,10 @@ spec:
|
||||
stringValue: op.#ConvertString & {bt: decoded}
|
||||
email2: op.#SendEmail & {
|
||||
from: {
|
||||
address: parameter.email.from.value
|
||||
alias: parameter.email.from.alias
|
||||
address: parameter.email.from.address
|
||||
if parameter.email.from.alias != _|_ {
|
||||
alias: parameter.email.from.alias
|
||||
}
|
||||
password: stringValue.str
|
||||
host: parameter.email.from.host
|
||||
port: parameter.email.from.port
|
||||
|
||||
@@ -29,6 +29,47 @@ spec:
|
||||
}
|
||||
}
|
||||
parameter: objects: [...#K8sObject]
|
||||
status:
|
||||
customStatus: |-
|
||||
if context.output.apiVersion == "apps/v1" && context.output.kind == "Deployment" {
|
||||
ready: {
|
||||
readyReplicas: *0 | int
|
||||
} & {
|
||||
if context.output.status.readyReplicas != _|_ {
|
||||
readyReplicas: context.output.status.readyReplicas
|
||||
}
|
||||
}
|
||||
message: "Ready:\(ready.readyReplicas)/\(context.output.spec.replicas)"
|
||||
}
|
||||
if context.output.apiVersion != "apps/v1" || context.output.kind != "Deployment" {
|
||||
message: ""
|
||||
}
|
||||
healthPolicy: |-
|
||||
if context.output.apiVersion == "apps/v1" && context.output.kind == "Deployment" {
|
||||
ready: {
|
||||
updatedReplicas: *0 | int
|
||||
readyReplicas: *0 | int
|
||||
replicas: *0 | int
|
||||
observedGeneration: *0 | int
|
||||
} & {
|
||||
if context.output.status.updatedReplicas != _|_ {
|
||||
updatedReplicas: context.output.status.updatedReplicas
|
||||
}
|
||||
if context.output.status.readyReplicas != _|_ {
|
||||
readyReplicas: context.output.status.readyReplicas
|
||||
}
|
||||
if context.output.status.replicas != _|_ {
|
||||
replicas: context.output.status.replicas
|
||||
}
|
||||
if context.output.status.observedGeneration != _|_ {
|
||||
observedGeneration: context.output.status.observedGeneration
|
||||
}
|
||||
}
|
||||
isHealth: (context.output.spec.replicas == ready.readyReplicas) && (context.output.spec.replicas == ready.updatedReplicas) && (context.output.spec.replicas == ready.replicas) && (ready.observedGeneration == context.output.metadata.generation || ready.observedGeneration > context.output.metadata.generation)
|
||||
}
|
||||
if context.output.apiVersion != "apps/v1" || context.output.kind != "Deployment" {
|
||||
isHealth: true
|
||||
}
|
||||
workload:
|
||||
type: autodetects.core.oam.dev
|
||||
|
||||
|
||||
@@ -23,7 +23,7 @@ spec:
|
||||
},
|
||||
] | []
|
||||
configMapVolumesList: *[
|
||||
for v in parameter.configMap {
|
||||
for v in parameter.configMap if v.mountPath != _|_ {
|
||||
{
|
||||
name: "configmap-" + v.name
|
||||
configMap: {
|
||||
@@ -37,7 +37,7 @@ spec:
|
||||
},
|
||||
] | []
|
||||
secretVolumesList: *[
|
||||
for v in parameter.secret {
|
||||
for v in parameter.secret if v.mountPath != _|_ {
|
||||
{
|
||||
name: "secret-" + v.name
|
||||
secret: {
|
||||
@@ -69,7 +69,7 @@ spec:
|
||||
},
|
||||
] | []
|
||||
configMapVolumeMountsList: *[
|
||||
for v in parameter.configMap {
|
||||
for v in parameter.configMap if v.mountPath != _|_ {
|
||||
{
|
||||
name: "configmap-" + v.name
|
||||
mountPath: v.mountPath
|
||||
@@ -88,7 +88,7 @@ spec:
|
||||
},
|
||||
] | []
|
||||
secretVolumeMountsList: *[
|
||||
for v in parameter.secret {
|
||||
for v in parameter.secret if v.mountPath != _|_ {
|
||||
{
|
||||
name: "secret-" + v.name
|
||||
mountPath: v.mountPath
|
||||
@@ -126,14 +126,14 @@ spec:
|
||||
// +patchKey=name
|
||||
volumes: pvcVolumesList + configMapVolumesList + secretVolumesList + emptyDirVolumesList
|
||||
|
||||
containers: [...{
|
||||
containers: [{
|
||||
// +patchKey=name
|
||||
env: configMapEnvMountsList + secretEnvMountsList
|
||||
// +patchKey=name
|
||||
volumeDevices: volumeDevicesList
|
||||
// +patchKey=name
|
||||
volumeMounts: pvcVolumeMountsList + configMapVolumeMountsList + secretVolumeMountsList + emptyDirVolumeMountsList
|
||||
}]
|
||||
}, ...]
|
||||
|
||||
}
|
||||
outputs: {
|
||||
@@ -248,7 +248,7 @@ spec:
|
||||
envName: string
|
||||
configMapKey: string
|
||||
}
|
||||
mountPath: string
|
||||
mountPath?: string
|
||||
defaultMode: *420 | int
|
||||
readOnly: *false | bool
|
||||
data?: {...}
|
||||
@@ -267,7 +267,7 @@ spec:
|
||||
envName: string
|
||||
secretKey: string
|
||||
}
|
||||
mountPath: string
|
||||
mountPath?: string
|
||||
defaultMode: *420 | int
|
||||
readOnly: *false | bool
|
||||
stringData?: {...}
|
||||
|
||||
@@ -244,6 +244,30 @@ spec:
|
||||
// +usage=Number of consecutive failures required to determine the container is not alive (liveness probe) or not ready (readiness probe).
|
||||
failureThreshold: *3 | int
|
||||
}
|
||||
status:
|
||||
customStatus: |-
|
||||
status: {
|
||||
active: *0 | int
|
||||
failed: *0 | int
|
||||
succeeded: *0 | int
|
||||
} & {
|
||||
if context.output.status.active != _|_ {
|
||||
active: context.output.status.active
|
||||
}
|
||||
if context.output.status.failed != _|_ {
|
||||
failed: context.output.status.failed
|
||||
}
|
||||
if context.output.status.succeeded != _|_ {
|
||||
succeeded: context.output.status.succeeded
|
||||
}
|
||||
}
|
||||
message: "Active/Failed/Succeeded:\(status.active)/\(status.failed)/\(status.succeeded)"
|
||||
healthPolicy: |-
|
||||
succeeded: *0 | int
|
||||
if context.output.status.succeeded != _|_ {
|
||||
succeeded: context.output.status.succeeded
|
||||
}
|
||||
isHealth: succeeded == context.output.spec.parallelism
|
||||
workload:
|
||||
definition:
|
||||
apiVersion: batch/v1
|
||||
|
||||
@@ -503,52 +503,35 @@ spec:
|
||||
}
|
||||
status:
|
||||
customStatus: |-
|
||||
import "strconv"
|
||||
ready: {
|
||||
if context.output.status.readyReplicas == _|_ {
|
||||
readyReplicas: 0
|
||||
}
|
||||
|
||||
readyReplicas: *0 | int
|
||||
} & {
|
||||
if context.output.status.readyReplicas != _|_ {
|
||||
readyReplicas: context.output.status.readyReplicas
|
||||
}
|
||||
}
|
||||
|
||||
message: "Ready:" + strconv.FormatInt(ready.readyReplicas, 10) + "/" + strconv.FormatInt(context.output.spec.replicas, 10)
|
||||
message: "Ready:\(ready.readyReplicas)/\(context.output.spec.replicas)"
|
||||
healthPolicy: |-
|
||||
ready: {
|
||||
if context.output.status.updatedReplicas == _|_ {
|
||||
updatedReplicas : 0
|
||||
updatedReplicas: *0 | int
|
||||
readyReplicas: *0 | int
|
||||
replicas: *0 | int
|
||||
observedGeneration: *0 | int
|
||||
} & {
|
||||
if context.output.status.updatedReplicas != _|_ {
|
||||
updatedReplicas: context.output.status.updatedReplicas
|
||||
}
|
||||
|
||||
if context.output.status.updatedReplicas != _|_ {
|
||||
updatedReplicas : context.output.status.updatedReplicas
|
||||
}
|
||||
|
||||
if context.output.status.readyReplicas == _|_ {
|
||||
readyReplicas: 0
|
||||
}
|
||||
|
||||
if context.output.status.readyReplicas != _|_ {
|
||||
readyReplicas: context.output.status.readyReplicas
|
||||
}
|
||||
|
||||
if context.output.status.replicas == _|_ {
|
||||
replicas: 0
|
||||
}
|
||||
if context.output.status.replicas != _|_ {
|
||||
replicas: context.output.status.replicas
|
||||
}
|
||||
|
||||
if context.output.status.observedGeneration != _|_ {
|
||||
observedGeneration: context.output.status.observedGeneration
|
||||
}
|
||||
|
||||
if context.output.status.observedGeneration == _|_ {
|
||||
observedGeneration: 0
|
||||
}
|
||||
}
|
||||
isHealth: (context.output.spec.replicas == ready.readyReplicas) && (context.output.spec.replicas == ready.updatedReplicas) && (context.output.spec.replicas == ready.replicas) && (ready.observedGeneration == context.output.metadata.generation || ready.observedGeneration > context.output.metadata.generation)
|
||||
isHealth: (context.output.spec.replicas == ready.readyReplicas) && (context.output.spec.replicas == ready.updatedReplicas) && (context.output.spec.replicas == ready.replicas) && (ready.observedGeneration == context.output.metadata.generation || ready.observedGeneration > context.output.metadata.generation)
|
||||
workload:
|
||||
definition:
|
||||
apiVersion: apps/v1
|
||||
|
||||
@@ -396,52 +396,35 @@ spec:
|
||||
}
|
||||
status:
|
||||
customStatus: |-
|
||||
import "strconv"
|
||||
ready: {
|
||||
if context.output.status.readyReplicas == _|_ {
|
||||
readyReplicas: 0
|
||||
}
|
||||
|
||||
readyReplicas: *0 | int
|
||||
} & {
|
||||
if context.output.status.readyReplicas != _|_ {
|
||||
readyReplicas: context.output.status.readyReplicas
|
||||
}
|
||||
}
|
||||
|
||||
message: "Ready:" + strconv.FormatInt(ready.readyReplicas, 10) + "/" + strconv.FormatInt(context.output.spec.replicas, 10)
|
||||
message: "Ready:\(ready.readyReplicas)/\(context.output.spec.replicas)"
|
||||
healthPolicy: |-
|
||||
ready: {
|
||||
if context.output.status.updatedReplicas == _|_ {
|
||||
updatedReplicas : 0
|
||||
updatedReplicas: *0 | int
|
||||
readyReplicas: *0 | int
|
||||
replicas: *0 | int
|
||||
observedGeneration: *0 | int
|
||||
} & {
|
||||
if context.output.status.updatedReplicas != _|_ {
|
||||
updatedReplicas: context.output.status.updatedReplicas
|
||||
}
|
||||
|
||||
if context.output.status.updatedReplicas != _|_ {
|
||||
updatedReplicas : context.output.status.updatedReplicas
|
||||
}
|
||||
|
||||
if context.output.status.readyReplicas == _|_ {
|
||||
readyReplicas: 0
|
||||
}
|
||||
|
||||
if context.output.status.readyReplicas != _|_ {
|
||||
readyReplicas: context.output.status.readyReplicas
|
||||
}
|
||||
|
||||
if context.output.status.replicas == _|_ {
|
||||
replicas: 0
|
||||
}
|
||||
if context.output.status.replicas != _|_ {
|
||||
replicas: context.output.status.replicas
|
||||
}
|
||||
|
||||
if context.output.status.observedGeneration != _|_ {
|
||||
observedGeneration: context.output.status.observedGeneration
|
||||
}
|
||||
|
||||
if context.output.status.observedGeneration == _|_ {
|
||||
observedGeneration: 0
|
||||
}
|
||||
}
|
||||
isHealth: (context.output.spec.replicas == ready.readyReplicas) && (context.output.spec.replicas == ready.updatedReplicas) && (context.output.spec.replicas == ready.replicas) && (ready.observedGeneration == context.output.metadata.generation || ready.observedGeneration > context.output.metadata.generation)
|
||||
isHealth: (context.output.spec.replicas == ready.readyReplicas) && (context.output.spec.replicas == ready.updatedReplicas) && (context.output.spec.replicas == ready.replicas) && (ready.observedGeneration == context.output.metadata.generation || ready.observedGeneration > context.output.metadata.generation)
|
||||
workload:
|
||||
definition:
|
||||
apiVersion: apps/v1
|
||||
|
||||
44
charts/vela-core/templates/velaql/applied-resources.yaml
Normal file
44
charts/vela-core/templates/velaql/applied-resources.yaml
Normal file
@@ -0,0 +1,44 @@
|
||||
apiVersion: "v1"
|
||||
kind: "ConfigMap"
|
||||
metadata:
|
||||
name: "service-applied-resources-view"
|
||||
namespace: {{ include "systemDefinitionNamespace" . }}
|
||||
data:
|
||||
template: |
|
||||
import (
|
||||
"vela/ql"
|
||||
)
|
||||
parameter: {
|
||||
appName: string
|
||||
appNs: string
|
||||
name?: string
|
||||
cluster?: string
|
||||
clusterNs?: string
|
||||
}
|
||||
response: ql.#ListAppliedResources & {
|
||||
app: {
|
||||
name: parameter.appName
|
||||
namespace: parameter.appNs
|
||||
filter: {
|
||||
if parameter.cluster != _|_ {
|
||||
cluster: parameter.cluster
|
||||
}
|
||||
if parameter.clusterNs != _|_ {
|
||||
clusterNamespace: parameter.clusterNs
|
||||
}
|
||||
if parameter.name != _|_ {
|
||||
components: [parameter.name]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if response.err == _|_ {
|
||||
status: {
|
||||
resources: response.list
|
||||
}
|
||||
}
|
||||
if response.err != _|_ {
|
||||
status: {
|
||||
error: response.err
|
||||
}
|
||||
}
|
||||
@@ -11,6 +11,7 @@ data:
|
||||
parameter: {
|
||||
appName: string
|
||||
appNs: string
|
||||
name?: string
|
||||
cluster?: string
|
||||
clusterNs?: string
|
||||
}
|
||||
@@ -25,6 +26,9 @@ data:
|
||||
if parameter.clusterNs != _|_ {
|
||||
clusterNamespace: parameter.clusterNs
|
||||
}
|
||||
if parameter.name != _|_ {
|
||||
components: [parameter.name]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2025,6 +2025,12 @@ spec:
|
||||
- jsonPath: .metadata.creationTimestamp
|
||||
name: AGE
|
||||
type: date
|
||||
- jsonPath: .metadata.annotations['app\.oam\.dev\/publishVersion']
|
||||
name: PUBLISH_VERSION
|
||||
type: string
|
||||
- jsonPath: .status.succeeded
|
||||
name: SUCCEEDED
|
||||
type: string
|
||||
name: v1beta1
|
||||
schema:
|
||||
openAPIV3Schema:
|
||||
@@ -2747,13 +2753,6 @@ spec:
|
||||
type: object
|
||||
type: object
|
||||
type: object
|
||||
applicationConfiguration:
|
||||
description: ApplicationConfiguration records the rendered applicationConfiguration
|
||||
from Application, it will contains the whole K8s CR of trait and
|
||||
the reference component in it.
|
||||
type: object
|
||||
x-kubernetes-embedded-resource: true
|
||||
x-kubernetes-preserve-unknown-fields: true
|
||||
componentDefinitions:
|
||||
additionalProperties:
|
||||
description: ComponentDefinition is the Schema for the componentdefinitions
|
||||
@@ -3087,20 +3086,51 @@ spec:
|
||||
description: ComponentDefinitions records the snapshot of the componentDefinitions
|
||||
related with the created/modified Application
|
||||
type: object
|
||||
components:
|
||||
description: Components records the rendered components from Application,
|
||||
it will contains the whole K8s CR of workload in it.
|
||||
items:
|
||||
description: RawComponent record raw component
|
||||
policies:
|
||||
additionalProperties:
|
||||
description: Policy is the Schema for the policy API
|
||||
properties:
|
||||
raw:
|
||||
apiVersion:
|
||||
description: 'APIVersion defines the versioned schema of this
|
||||
representation of an object. Servers should convert recognized
|
||||
schemas to the latest internal value, and may reject unrecognized
|
||||
values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources'
|
||||
type: string
|
||||
kind:
|
||||
description: 'Kind is a string value representing the REST resource
|
||||
this object represents. Servers may infer this from the endpoint
|
||||
the client submits requests to. Cannot be updated. In CamelCase.
|
||||
More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds'
|
||||
type: string
|
||||
metadata:
|
||||
properties:
|
||||
annotations:
|
||||
additionalProperties:
|
||||
type: string
|
||||
type: object
|
||||
finalizers:
|
||||
items:
|
||||
type: string
|
||||
type: array
|
||||
labels:
|
||||
additionalProperties:
|
||||
type: string
|
||||
type: object
|
||||
name:
|
||||
type: string
|
||||
namespace:
|
||||
type: string
|
||||
type: object
|
||||
properties:
|
||||
type: object
|
||||
x-kubernetes-embedded-resource: true
|
||||
x-kubernetes-preserve-unknown-fields: true
|
||||
type:
|
||||
type: string
|
||||
required:
|
||||
- raw
|
||||
- type
|
||||
type: object
|
||||
type: array
|
||||
description: Policies records the external policies
|
||||
type: object
|
||||
policyDefinitions:
|
||||
additionalProperties:
|
||||
description: PolicyDefinition is the Schema for the policydefinitions
|
||||
@@ -3377,15 +3407,16 @@ spec:
|
||||
description: PolicyDefinitions records the snapshot of the PolicyDefinitions
|
||||
related with the created/modified Application
|
||||
type: object
|
||||
resourcesConfigMap:
|
||||
description: ResourcesConfigMap references the ConfigMap that's generated
|
||||
to contain all final rendered resources.
|
||||
properties:
|
||||
name:
|
||||
description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names
|
||||
TODO: Add other useful fields. apiVersion, kind, uid?'
|
||||
type: string
|
||||
type: object
|
||||
referredObjects:
|
||||
description: ReferredObjects records the referred objects used in
|
||||
the ref-object typed components
|
||||
items:
|
||||
description: ReferredObject the referred Kubernetes object
|
||||
type: object
|
||||
x-kubernetes-embedded-resource: true
|
||||
x-kubernetes-preserve-unknown-fields: true
|
||||
type: array
|
||||
x-kubernetes-preserve-unknown-fields: true
|
||||
scopeDefinitions:
|
||||
additionalProperties:
|
||||
description: A ScopeDefinition registers a kind of Kubernetes custom
|
||||
@@ -3819,6 +3850,89 @@ spec:
|
||||
description: TraitDefinitions records the snapshot of the traitDefinitions
|
||||
related with the created/modified Application
|
||||
type: object
|
||||
workflow:
|
||||
description: Workflow records the external workflow
|
||||
properties:
|
||||
apiVersion:
|
||||
description: 'APIVersion defines the versioned schema of this
|
||||
representation of an object. Servers should convert recognized
|
||||
schemas to the latest internal value, and may reject unrecognized
|
||||
values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources'
|
||||
type: string
|
||||
kind:
|
||||
description: 'Kind is a string value representing the REST resource
|
||||
this object represents. Servers may infer this from the endpoint
|
||||
the client submits requests to. Cannot be updated. In CamelCase.
|
||||
More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds'
|
||||
type: string
|
||||
metadata:
|
||||
properties:
|
||||
annotations:
|
||||
additionalProperties:
|
||||
type: string
|
||||
type: object
|
||||
finalizers:
|
||||
items:
|
||||
type: string
|
||||
type: array
|
||||
labels:
|
||||
additionalProperties:
|
||||
type: string
|
||||
type: object
|
||||
name:
|
||||
type: string
|
||||
namespace:
|
||||
type: string
|
||||
type: object
|
||||
steps:
|
||||
items:
|
||||
description: WorkflowStep defines how to execute a workflow
|
||||
step.
|
||||
properties:
|
||||
dependsOn:
|
||||
items:
|
||||
type: string
|
||||
type: array
|
||||
inputs:
|
||||
description: StepInputs defines variable input of WorkflowStep
|
||||
items:
|
||||
properties:
|
||||
from:
|
||||
type: string
|
||||
parameterKey:
|
||||
type: string
|
||||
required:
|
||||
- from
|
||||
- parameterKey
|
||||
type: object
|
||||
type: array
|
||||
name:
|
||||
description: Name is the unique name of the workflow step.
|
||||
type: string
|
||||
outputs:
|
||||
description: StepOutputs defines output variable of WorkflowStep
|
||||
items:
|
||||
properties:
|
||||
name:
|
||||
type: string
|
||||
valueFrom:
|
||||
type: string
|
||||
required:
|
||||
- name
|
||||
- valueFrom
|
||||
type: object
|
||||
type: array
|
||||
properties:
|
||||
type: object
|
||||
x-kubernetes-preserve-unknown-fields: true
|
||||
type:
|
||||
type: string
|
||||
required:
|
||||
- name
|
||||
- type
|
||||
type: object
|
||||
type: array
|
||||
type: object
|
||||
workflowStepDefinitions:
|
||||
additionalProperties:
|
||||
description: WorkflowStepDefinition is the Schema for the workflowstepdefinitions
|
||||
@@ -4408,10 +4522,182 @@ spec:
|
||||
required:
|
||||
- application
|
||||
type: object
|
||||
status:
|
||||
description: ApplicationRevisionStatus is the status of ApplicationRevision
|
||||
properties:
|
||||
succeeded:
|
||||
description: Succeeded records if the workflow finished running with
|
||||
success
|
||||
type: boolean
|
||||
workflow:
|
||||
description: Workflow the running status of the workflow
|
||||
properties:
|
||||
appRevision:
|
||||
type: string
|
||||
contextBackend:
|
||||
description: 'ObjectReference contains enough information to let
|
||||
you inspect or modify the referred object. --- New uses of this
|
||||
type are discouraged because of difficulty describing its usage
|
||||
when embedded in APIs. 1. Ignored fields. It includes many
|
||||
fields which are not generally honored. For instance, ResourceVersion
|
||||
and FieldPath are both very rarely valid in actual usage. 2.
|
||||
Invalid usage help. It is impossible to add specific help for
|
||||
individual usage. In most embedded usages, there are particular restrictions
|
||||
like, "must refer only to types A and B" or "UID not honored"
|
||||
or "name must be restricted". Those cannot be well described
|
||||
when embedded. 3. Inconsistent validation. Because the usages
|
||||
are different, the validation rules are different by usage,
|
||||
which makes it hard for users to predict what will happen. 4.
|
||||
The fields are both imprecise and overly precise. Kind is not
|
||||
a precise mapping to a URL. This can produce ambiguity during
|
||||
interpretation and require a REST mapping. In most cases, the
|
||||
dependency is on the group,resource tuple and the version
|
||||
of the actual struct is irrelevant. 5. We cannot easily change
|
||||
it. Because this type is embedded in many locations, updates
|
||||
to this type will affect numerous schemas. Don''t make
|
||||
new APIs embed an underspecified API type they do not control.
|
||||
Instead of using this type, create a locally provided and used
|
||||
type that is well-focused on your reference. For example, ServiceReferences
|
||||
for admission registration: https://github.com/kubernetes/api/blob/release-1.17/admissionregistration/v1/types.go#L533
|
||||
.'
|
||||
properties:
|
||||
apiVersion:
|
||||
description: API version of the referent.
|
||||
type: string
|
||||
fieldPath:
|
||||
description: 'If referring to a piece of an object instead
|
||||
of an entire object, this string should contain a valid
|
||||
JSON/Go field access statement, such as desiredState.manifest.containers[2].
|
||||
For example, if the object reference is to a container within
|
||||
a pod, this would take on a value like: "spec.containers{name}"
|
||||
(where "name" refers to the name of the container that triggered
|
||||
the event) or if no container name is specified "spec.containers[2]"
|
||||
(container with index 2 in this pod). This syntax is chosen
|
||||
only to have some well-defined way of referencing a part
|
||||
of an object. TODO: this design is not final and this field
|
||||
is subject to change in the future.'
|
||||
type: string
|
||||
kind:
|
||||
description: 'Kind of the referent. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds'
|
||||
type: string
|
||||
name:
|
||||
description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names'
|
||||
type: string
|
||||
namespace:
|
||||
description: 'Namespace of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/namespaces/'
|
||||
type: string
|
||||
resourceVersion:
|
||||
description: 'Specific resourceVersion to which this reference
|
||||
is made, if any. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#concurrency-control-and-consistency'
|
||||
type: string
|
||||
uid:
|
||||
description: 'UID of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#uids'
|
||||
type: string
|
||||
type: object
|
||||
finished:
|
||||
type: boolean
|
||||
message:
|
||||
type: string
|
||||
mode:
|
||||
description: WorkflowMode describes the mode of workflow
|
||||
type: string
|
||||
startTime:
|
||||
format: date-time
|
||||
type: string
|
||||
steps:
|
||||
items:
|
||||
description: WorkflowStepStatus record the status of a workflow
|
||||
step
|
||||
properties:
|
||||
firstExecuteTime:
|
||||
description: FirstExecuteTime is the first time this step
|
||||
execution.
|
||||
format: date-time
|
||||
type: string
|
||||
id:
|
||||
type: string
|
||||
lastExecuteTime:
|
||||
description: LastExecuteTime is the last time this step
|
||||
execution.
|
||||
format: date-time
|
||||
type: string
|
||||
message:
|
||||
description: A human readable message indicating details
|
||||
about why the workflowStep is in this state.
|
||||
type: string
|
||||
name:
|
||||
type: string
|
||||
phase:
|
||||
description: WorkflowStepPhase describes the phase of a
|
||||
workflow step.
|
||||
type: string
|
||||
reason:
|
||||
description: A brief CamelCase message indicating details
|
||||
about why the workflowStep is in this state.
|
||||
type: string
|
||||
subSteps:
|
||||
description: SubStepsStatus record the status of workflow
|
||||
steps.
|
||||
properties:
|
||||
mode:
|
||||
description: WorkflowMode describes the mode of workflow
|
||||
type: string
|
||||
stepIndex:
|
||||
type: integer
|
||||
steps:
|
||||
items:
|
||||
description: WorkflowSubStepStatus record the status
|
||||
of a workflow step
|
||||
properties:
|
||||
id:
|
||||
type: string
|
||||
message:
|
||||
description: A human readable message indicating
|
||||
details about why the workflowStep is in this
|
||||
state.
|
||||
type: string
|
||||
name:
|
||||
type: string
|
||||
phase:
|
||||
description: WorkflowStepPhase describes the phase
|
||||
of a workflow step.
|
||||
type: string
|
||||
reason:
|
||||
description: A brief CamelCase message indicating
|
||||
details about why the workflowStep is in this
|
||||
state.
|
||||
type: string
|
||||
type:
|
||||
type: string
|
||||
required:
|
||||
- id
|
||||
type: object
|
||||
type: array
|
||||
type: object
|
||||
type:
|
||||
type: string
|
||||
required:
|
||||
- id
|
||||
type: object
|
||||
type: array
|
||||
suspend:
|
||||
type: boolean
|
||||
terminated:
|
||||
type: boolean
|
||||
required:
|
||||
- finished
|
||||
- mode
|
||||
- suspend
|
||||
- terminated
|
||||
type: object
|
||||
required:
|
||||
- succeeded
|
||||
type: object
|
||||
type: object
|
||||
served: true
|
||||
storage: true
|
||||
subresources: {}
|
||||
subresources:
|
||||
status: {}
|
||||
status:
|
||||
acceptedNames:
|
||||
kind: ""
|
||||
|
||||
@@ -16,17 +16,20 @@ spec:
|
||||
schematic:
|
||||
cue:
|
||||
template: |
|
||||
// +patchStrategy=jsonMergePatch
|
||||
patch: {
|
||||
metadata: annotations: {
|
||||
for k, v in parameter {
|
||||
"\(k)": v
|
||||
}
|
||||
}
|
||||
spec: template: metadata: annotations: {
|
||||
for k, v in parameter {
|
||||
"\(k)": v
|
||||
if context.output.spec != _|_ && context.output.spec.template != _|_ {
|
||||
spec: template: metadata: annotations: {
|
||||
for k, v in parameter {
|
||||
"\(k)": v
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
parameter: [string]: string
|
||||
parameter: [string]: string | null
|
||||
|
||||
|
||||
@@ -8,6 +8,8 @@ metadata:
|
||||
name: gateway
|
||||
namespace: {{ include "systemDefinitionNamespace" . }}
|
||||
spec:
|
||||
appliesToWorkloads:
|
||||
- '*'
|
||||
podDisruptive: false
|
||||
schematic:
|
||||
cue:
|
||||
|
||||
@@ -0,0 +1,49 @@
|
||||
# Code generated by KubeVela templates. DO NOT EDIT. Please edit the original cue file.
|
||||
# Definition source cue file: vela-templates/definitions/internal/generate-jdbc-connection.cue
|
||||
apiVersion: core.oam.dev/v1beta1
|
||||
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:
|
||||
schematic:
|
||||
cue:
|
||||
template: |
|
||||
import (
|
||||
"vela/op"
|
||||
"encoding/base64"
|
||||
)
|
||||
|
||||
output: op.#Read & {
|
||||
value: {
|
||||
apiVersion: "v1"
|
||||
kind: "Secret"
|
||||
metadata: {
|
||||
name: parameter.name
|
||||
if parameter.namespace != _|_ {
|
||||
namespace: parameter.namespace
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
dbHost: op.#ConvertString & {bt: base64.Decode(null, output.value.data["DB_HOST"])}
|
||||
dbPort: op.#ConvertString & {bt: base64.Decode(null, output.value.data["DB_PORT"])}
|
||||
dbName: op.#ConvertString & {bt: base64.Decode(null, output.value.data["DB_NAME"])}
|
||||
username: op.#ConvertString & {bt: base64.Decode(null, output.value.data["DB_USER"])}
|
||||
password: op.#ConvertString & {bt: base64.Decode(null, output.value.data["DB_PASSWORD"])}
|
||||
env: [
|
||||
{name: "url", value: "jdbc://" + dbHost.str + ":" + dbPort.str + "/" + dbName.str + "?characterEncoding=utf8&useSSL=false"},
|
||||
{name: "username", value: username.str},
|
||||
{name: "password", value: password.str},
|
||||
]
|
||||
parameter: {
|
||||
// +usage=Specify the name of the secret generated by database component
|
||||
name: string
|
||||
// +usage=Specify the namespace of the secret generated by database component
|
||||
namespace?: string
|
||||
}
|
||||
|
||||
@@ -16,17 +16,20 @@ spec:
|
||||
schematic:
|
||||
cue:
|
||||
template: |
|
||||
// +patchStrategy=jsonMergePatch
|
||||
patch: {
|
||||
metadata: labels: {
|
||||
for k, v in parameter {
|
||||
"\(k)": v
|
||||
}
|
||||
}
|
||||
spec: template: metadata: labels: {
|
||||
for k, v in parameter {
|
||||
"\(k)": v
|
||||
if context.output.spec != _|_ && context.output.spec.template != _|_ {
|
||||
spec: template: metadata: labels: {
|
||||
for k, v in parameter {
|
||||
"\(k)": v
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
parameter: [string]: string
|
||||
parameter: [string]: string | null
|
||||
|
||||
|
||||
@@ -291,8 +291,10 @@ spec:
|
||||
if parameter.email.from.password.value != _|_ {
|
||||
email1: op.#SendEmail & {
|
||||
from: {
|
||||
address: parameter.email.from.value
|
||||
alias: parameter.email.from.alias
|
||||
address: parameter.email.from.address
|
||||
if parameter.email.from.alias != _|_ {
|
||||
alias: parameter.email.from.alias
|
||||
}
|
||||
password: parameter.email.from.password.value
|
||||
host: parameter.email.from.host
|
||||
port: parameter.email.from.port
|
||||
@@ -318,8 +320,10 @@ spec:
|
||||
stringValue: op.#ConvertString & {bt: decoded}
|
||||
email2: op.#SendEmail & {
|
||||
from: {
|
||||
address: parameter.email.from.value
|
||||
alias: parameter.email.from.alias
|
||||
address: parameter.email.from.address
|
||||
if parameter.email.from.alias != _|_ {
|
||||
alias: parameter.email.from.alias
|
||||
}
|
||||
password: stringValue.str
|
||||
host: parameter.email.from.host
|
||||
port: parameter.email.from.port
|
||||
|
||||
@@ -29,6 +29,47 @@ spec:
|
||||
}
|
||||
}
|
||||
parameter: objects: [...#K8sObject]
|
||||
status:
|
||||
customStatus: |-
|
||||
if context.output.apiVersion == "apps/v1" && context.output.kind == "Deployment" {
|
||||
ready: {
|
||||
readyReplicas: *0 | int
|
||||
} & {
|
||||
if context.output.status.readyReplicas != _|_ {
|
||||
readyReplicas: context.output.status.readyReplicas
|
||||
}
|
||||
}
|
||||
message: "Ready:\(ready.readyReplicas)/\(context.output.spec.replicas)"
|
||||
}
|
||||
if context.output.apiVersion != "apps/v1" || context.output.kind != "Deployment" {
|
||||
message: ""
|
||||
}
|
||||
healthPolicy: |-
|
||||
if context.output.apiVersion == "apps/v1" && context.output.kind == "Deployment" {
|
||||
ready: {
|
||||
updatedReplicas: *0 | int
|
||||
readyReplicas: *0 | int
|
||||
replicas: *0 | int
|
||||
observedGeneration: *0 | int
|
||||
} & {
|
||||
if context.output.status.updatedReplicas != _|_ {
|
||||
updatedReplicas: context.output.status.updatedReplicas
|
||||
}
|
||||
if context.output.status.readyReplicas != _|_ {
|
||||
readyReplicas: context.output.status.readyReplicas
|
||||
}
|
||||
if context.output.status.replicas != _|_ {
|
||||
replicas: context.output.status.replicas
|
||||
}
|
||||
if context.output.status.observedGeneration != _|_ {
|
||||
observedGeneration: context.output.status.observedGeneration
|
||||
}
|
||||
}
|
||||
isHealth: (context.output.spec.replicas == ready.readyReplicas) && (context.output.spec.replicas == ready.updatedReplicas) && (context.output.spec.replicas == ready.replicas) && (ready.observedGeneration == context.output.metadata.generation || ready.observedGeneration > context.output.metadata.generation)
|
||||
}
|
||||
if context.output.apiVersion != "apps/v1" || context.output.kind != "Deployment" {
|
||||
isHealth: true
|
||||
}
|
||||
workload:
|
||||
type: autodetects.core.oam.dev
|
||||
|
||||
|
||||
@@ -23,7 +23,7 @@ spec:
|
||||
},
|
||||
] | []
|
||||
configMapVolumesList: *[
|
||||
for v in parameter.configMap {
|
||||
for v in parameter.configMap if v.mountPath != _|_ {
|
||||
{
|
||||
name: "configmap-" + v.name
|
||||
configMap: {
|
||||
@@ -37,7 +37,7 @@ spec:
|
||||
},
|
||||
] | []
|
||||
secretVolumesList: *[
|
||||
for v in parameter.secret {
|
||||
for v in parameter.secret if v.mountPath != _|_ {
|
||||
{
|
||||
name: "secret-" + v.name
|
||||
secret: {
|
||||
@@ -69,7 +69,7 @@ spec:
|
||||
},
|
||||
] | []
|
||||
configMapVolumeMountsList: *[
|
||||
for v in parameter.configMap {
|
||||
for v in parameter.configMap if v.mountPath != _|_ {
|
||||
{
|
||||
name: "configmap-" + v.name
|
||||
mountPath: v.mountPath
|
||||
@@ -88,7 +88,7 @@ spec:
|
||||
},
|
||||
] | []
|
||||
secretVolumeMountsList: *[
|
||||
for v in parameter.secret {
|
||||
for v in parameter.secret if v.mountPath != _|_ {
|
||||
{
|
||||
name: "secret-" + v.name
|
||||
mountPath: v.mountPath
|
||||
@@ -126,14 +126,14 @@ spec:
|
||||
// +patchKey=name
|
||||
volumes: pvcVolumesList + configMapVolumesList + secretVolumesList + emptyDirVolumesList
|
||||
|
||||
containers: [...{
|
||||
containers: [{
|
||||
// +patchKey=name
|
||||
env: configMapEnvMountsList + secretEnvMountsList
|
||||
// +patchKey=name
|
||||
volumeDevices: volumeDevicesList
|
||||
// +patchKey=name
|
||||
volumeMounts: pvcVolumeMountsList + configMapVolumeMountsList + secretVolumeMountsList + emptyDirVolumeMountsList
|
||||
}]
|
||||
}, ...]
|
||||
|
||||
}
|
||||
outputs: {
|
||||
@@ -248,7 +248,7 @@ spec:
|
||||
envName: string
|
||||
configMapKey: string
|
||||
}
|
||||
mountPath: string
|
||||
mountPath?: string
|
||||
defaultMode: *420 | int
|
||||
readOnly: *false | bool
|
||||
data?: {...}
|
||||
@@ -267,7 +267,7 @@ spec:
|
||||
envName: string
|
||||
secretKey: string
|
||||
}
|
||||
mountPath: string
|
||||
mountPath?: string
|
||||
defaultMode: *420 | int
|
||||
readOnly: *false | bool
|
||||
stringData?: {...}
|
||||
|
||||
@@ -244,6 +244,30 @@ spec:
|
||||
// +usage=Number of consecutive failures required to determine the container is not alive (liveness probe) or not ready (readiness probe).
|
||||
failureThreshold: *3 | int
|
||||
}
|
||||
status:
|
||||
customStatus: |-
|
||||
status: {
|
||||
active: *0 | int
|
||||
failed: *0 | int
|
||||
succeeded: *0 | int
|
||||
} & {
|
||||
if context.output.status.active != _|_ {
|
||||
active: context.output.status.active
|
||||
}
|
||||
if context.output.status.failed != _|_ {
|
||||
failed: context.output.status.failed
|
||||
}
|
||||
if context.output.status.succeeded != _|_ {
|
||||
succeeded: context.output.status.succeeded
|
||||
}
|
||||
}
|
||||
message: "Active/Failed/Succeeded:\(status.active)/\(status.failed)/\(status.succeeded)"
|
||||
healthPolicy: |-
|
||||
succeeded: *0 | int
|
||||
if context.output.status.succeeded != _|_ {
|
||||
succeeded: context.output.status.succeeded
|
||||
}
|
||||
isHealth: succeeded == context.output.spec.parallelism
|
||||
workload:
|
||||
definition:
|
||||
apiVersion: batch/v1
|
||||
|
||||
@@ -503,52 +503,35 @@ spec:
|
||||
}
|
||||
status:
|
||||
customStatus: |-
|
||||
import "strconv"
|
||||
ready: {
|
||||
if context.output.status.readyReplicas == _|_ {
|
||||
readyReplicas: 0
|
||||
}
|
||||
|
||||
readyReplicas: *0 | int
|
||||
} & {
|
||||
if context.output.status.readyReplicas != _|_ {
|
||||
readyReplicas: context.output.status.readyReplicas
|
||||
}
|
||||
}
|
||||
|
||||
message: "Ready:" + strconv.FormatInt(ready.readyReplicas, 10) + "/" + strconv.FormatInt(context.output.spec.replicas, 10)
|
||||
message: "Ready:\(ready.readyReplicas)/\(context.output.spec.replicas)"
|
||||
healthPolicy: |-
|
||||
ready: {
|
||||
if context.output.status.updatedReplicas == _|_ {
|
||||
updatedReplicas : 0
|
||||
updatedReplicas: *0 | int
|
||||
readyReplicas: *0 | int
|
||||
replicas: *0 | int
|
||||
observedGeneration: *0 | int
|
||||
} & {
|
||||
if context.output.status.updatedReplicas != _|_ {
|
||||
updatedReplicas: context.output.status.updatedReplicas
|
||||
}
|
||||
|
||||
if context.output.status.updatedReplicas != _|_ {
|
||||
updatedReplicas : context.output.status.updatedReplicas
|
||||
}
|
||||
|
||||
if context.output.status.readyReplicas == _|_ {
|
||||
readyReplicas: 0
|
||||
}
|
||||
|
||||
if context.output.status.readyReplicas != _|_ {
|
||||
readyReplicas: context.output.status.readyReplicas
|
||||
}
|
||||
|
||||
if context.output.status.replicas == _|_ {
|
||||
replicas: 0
|
||||
}
|
||||
if context.output.status.replicas != _|_ {
|
||||
replicas: context.output.status.replicas
|
||||
}
|
||||
|
||||
if context.output.status.observedGeneration != _|_ {
|
||||
observedGeneration: context.output.status.observedGeneration
|
||||
}
|
||||
|
||||
if context.output.status.observedGeneration == _|_ {
|
||||
observedGeneration: 0
|
||||
}
|
||||
}
|
||||
isHealth: (context.output.spec.replicas == ready.readyReplicas) && (context.output.spec.replicas == ready.updatedReplicas) && (context.output.spec.replicas == ready.replicas) && (ready.observedGeneration == context.output.metadata.generation || ready.observedGeneration > context.output.metadata.generation)
|
||||
isHealth: (context.output.spec.replicas == ready.readyReplicas) && (context.output.spec.replicas == ready.updatedReplicas) && (context.output.spec.replicas == ready.replicas) && (ready.observedGeneration == context.output.metadata.generation || ready.observedGeneration > context.output.metadata.generation)
|
||||
workload:
|
||||
definition:
|
||||
apiVersion: apps/v1
|
||||
|
||||
@@ -396,52 +396,35 @@ spec:
|
||||
}
|
||||
status:
|
||||
customStatus: |-
|
||||
import "strconv"
|
||||
ready: {
|
||||
if context.output.status.readyReplicas == _|_ {
|
||||
readyReplicas: 0
|
||||
}
|
||||
|
||||
readyReplicas: *0 | int
|
||||
} & {
|
||||
if context.output.status.readyReplicas != _|_ {
|
||||
readyReplicas: context.output.status.readyReplicas
|
||||
}
|
||||
}
|
||||
|
||||
message: "Ready:" + strconv.FormatInt(ready.readyReplicas, 10) + "/" + strconv.FormatInt(context.output.spec.replicas, 10)
|
||||
message: "Ready:\(ready.readyReplicas)/\(context.output.spec.replicas)"
|
||||
healthPolicy: |-
|
||||
ready: {
|
||||
if context.output.status.updatedReplicas == _|_ {
|
||||
updatedReplicas : 0
|
||||
updatedReplicas: *0 | int
|
||||
readyReplicas: *0 | int
|
||||
replicas: *0 | int
|
||||
observedGeneration: *0 | int
|
||||
} & {
|
||||
if context.output.status.updatedReplicas != _|_ {
|
||||
updatedReplicas: context.output.status.updatedReplicas
|
||||
}
|
||||
|
||||
if context.output.status.updatedReplicas != _|_ {
|
||||
updatedReplicas : context.output.status.updatedReplicas
|
||||
}
|
||||
|
||||
if context.output.status.readyReplicas == _|_ {
|
||||
readyReplicas: 0
|
||||
}
|
||||
|
||||
if context.output.status.readyReplicas != _|_ {
|
||||
readyReplicas: context.output.status.readyReplicas
|
||||
}
|
||||
|
||||
if context.output.status.replicas == _|_ {
|
||||
replicas: 0
|
||||
}
|
||||
if context.output.status.replicas != _|_ {
|
||||
replicas: context.output.status.replicas
|
||||
}
|
||||
|
||||
if context.output.status.observedGeneration != _|_ {
|
||||
observedGeneration: context.output.status.observedGeneration
|
||||
}
|
||||
|
||||
if context.output.status.observedGeneration == _|_ {
|
||||
observedGeneration: 0
|
||||
}
|
||||
}
|
||||
isHealth: (context.output.spec.replicas == ready.readyReplicas) && (context.output.spec.replicas == ready.updatedReplicas) && (context.output.spec.replicas == ready.replicas) && (ready.observedGeneration == context.output.metadata.generation || ready.observedGeneration > context.output.metadata.generation)
|
||||
isHealth: (context.output.spec.replicas == ready.readyReplicas) && (context.output.spec.replicas == ready.updatedReplicas) && (context.output.spec.replicas == ready.replicas) && (ready.observedGeneration == context.output.metadata.generation || ready.observedGeneration > context.output.metadata.generation)
|
||||
workload:
|
||||
definition:
|
||||
apiVersion: apps/v1
|
||||
|
||||
@@ -120,5 +120,5 @@ func (s *Server) buildSwagger() (*spec.Swagger, error) {
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("create apiserver failed : %w ", err)
|
||||
}
|
||||
return restfulspec.BuildSwagger(server.RegisterServices()), nil
|
||||
return restfulspec.BuildSwagger(server.RegisterServices(context.Background(), false)), nil
|
||||
}
|
||||
|
||||
@@ -35,6 +35,7 @@ import (
|
||||
ctrl "sigs.k8s.io/controller-runtime"
|
||||
"sigs.k8s.io/controller-runtime/pkg/healthz"
|
||||
|
||||
"github.com/oam-dev/kubevela/pkg/auth"
|
||||
ctrlClient "github.com/oam-dev/kubevela/pkg/client"
|
||||
standardcontroller "github.com/oam-dev/kubevela/pkg/controller"
|
||||
commonconfig "github.com/oam-dev/kubevela/pkg/controller/common"
|
||||
@@ -42,6 +43,7 @@ import (
|
||||
oamv1alpha2 "github.com/oam-dev/kubevela/pkg/controller/core.oam.dev/v1alpha2"
|
||||
"github.com/oam-dev/kubevela/pkg/controller/utils"
|
||||
"github.com/oam-dev/kubevela/pkg/cue/packages"
|
||||
_ "github.com/oam-dev/kubevela/pkg/features"
|
||||
_ "github.com/oam-dev/kubevela/pkg/monitor/metrics"
|
||||
"github.com/oam-dev/kubevela/pkg/multicluster"
|
||||
"github.com/oam-dev/kubevela/pkg/oam"
|
||||
@@ -86,6 +88,8 @@ func main() {
|
||||
var renewDeadline time.Duration
|
||||
var retryPeriod time.Duration
|
||||
var enableClusterGateway bool
|
||||
var enableClusterMetrics bool
|
||||
var clusterMetricsInterval time.Duration
|
||||
|
||||
flag.BoolVar(&useWebhook, "use-webhook", false, "Enable Admission Webhook")
|
||||
flag.StringVar(&certDir, "webhook-cert-dir", "/k8s-webhook-server/serving-certs", "Admission webhook cert/key dir.")
|
||||
@@ -133,6 +137,8 @@ func main() {
|
||||
flag.DurationVar(&retryPeriod, "leader-election-retry-period", 2*time.Second,
|
||||
"The duration the LeaderElector clients should wait between tries of actions")
|
||||
flag.BoolVar(&enableClusterGateway, "enable-cluster-gateway", false, "Enable cluster-gateway to use multicluster, disabled by default.")
|
||||
flag.BoolVar(&enableClusterMetrics, "enable-cluster-metrics", false, "Enable cluster-metrics-management to collect metrics from clusters with cluster-gateway, disabled by default. When this param is enabled, enable-cluster-gateway should be enabled")
|
||||
flag.DurationVar(&clusterMetricsInterval, "cluster-metrics-interval", 15*time.Second, "The interval that ClusterMetricsMgr will collect metrics from clusters, default value is 15 seconds.")
|
||||
flag.BoolVar(&controllerArgs.EnableCompatibility, "enable-asi-compatibility", false, "enable compatibility for asi")
|
||||
flag.BoolVar(&controllerArgs.IgnoreAppWithoutControllerRequirement, "ignore-app-without-controller-version", false, "If true, application controller will not process the app without 'app.oam.dev/controller-version-require' annotation")
|
||||
standardcontroller.AddOptimizeFlags()
|
||||
@@ -197,13 +203,23 @@ func main() {
|
||||
restConfig.UserAgent = kubevelaName + "/" + version.GitRevision
|
||||
restConfig.QPS = float32(qps)
|
||||
restConfig.Burst = burst
|
||||
restConfig.Wrap(auth.NewImpersonatingRoundTripper)
|
||||
|
||||
// wrapper the round tripper by multi cluster rewriter
|
||||
if enableClusterGateway {
|
||||
if _, err := multicluster.Initialize(restConfig, true); err != nil {
|
||||
client, err := multicluster.Initialize(restConfig, true)
|
||||
if err != nil {
|
||||
klog.ErrorS(err, "failed to enable multi-cluster capability")
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
if enableClusterMetrics {
|
||||
_, err := multicluster.NewClusterMetricsMgr(context.Background(), client, clusterMetricsInterval)
|
||||
if err != nil {
|
||||
klog.ErrorS(err, "failed to enable multi-cluster-metrics capability")
|
||||
os.Exit(1)
|
||||
}
|
||||
}
|
||||
}
|
||||
ctrl.SetLogger(klogr.New())
|
||||
|
||||
|
||||
@@ -6,3 +6,5 @@ coverage:
|
||||
patch:
|
||||
default:
|
||||
target: 70%
|
||||
ignore:
|
||||
- "**/zz_generated.deepcopy.go"
|
||||
File diff suppressed because it is too large
Load Diff
BIN
docs/examples/application/application-revision-arch.jpg
Normal file
BIN
docs/examples/application/application-revision-arch.jpg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 249 KiB |
139
docs/examples/application/versioning.md
Normal file
139
docs/examples/application/versioning.md
Normal file
@@ -0,0 +1,139 @@
|
||||
# Managing KubeVela Application Versions
|
||||
|
||||

|
||||
|
||||
In KubeVela, ApplicationRevision keeps the snapshot of application and all its runtime dependencies such as ComponentRevision, external Policy or referred objects.
|
||||
This revision can be used review the application changes and rollback to past configurations.
|
||||
|
||||
In KubeVela v1.3, for application which uses the `PublishVersion` feature, we support viewing the history revisions, checking the differences across revisions, and rolling back to the latest succeeded revision.
|
||||
|
||||
For application with the `app.oam.dev/publishVersion` annotation, the workflow runs are strictly controlled.
|
||||
The annotation, which is noted as *publishVersion* in the following paragraphs, is used to identify a static version of the application and its dependencies.
|
||||
|
||||
When the annotation is updated to a new value, the application will generate a new revision no matter if the application spec or the dependencies are changed.
|
||||
It will then trigger a fresh new run of workflow after terminating the previous run.
|
||||
|
||||
During the running of workflow, all related data are retrieved from the ApplicationRevision, which means the changes to the application spec or the dependencies will not take effects until a newer `publishVerison` is annotated.
|
||||
|
||||
Fo example, let's start with an application with has referred objects, external workflow and policies.
|
||||
|
||||
```yaml
|
||||
apiVersion: core.oam.dev/v1beta1
|
||||
kind: Application
|
||||
metadata:
|
||||
name: nginx-publish-version
|
||||
namespace: examples
|
||||
annotations:
|
||||
app.oam.dev/publishVersion: alpha1
|
||||
spec:
|
||||
components:
|
||||
- name: nginx-publish-version
|
||||
type: ref-objects
|
||||
properties:
|
||||
objects:
|
||||
- resource: deployment
|
||||
workflow:
|
||||
ref: make-release-in-hangzhou
|
||||
---
|
||||
apiVersion: core.oam.dev/v1alpha1
|
||||
kind: Policy
|
||||
metadata:
|
||||
name: topology-hangzhou-clusters
|
||||
namespace: examples
|
||||
type: topology
|
||||
properties:
|
||||
clusterLabelSelector:
|
||||
region: hangzhou
|
||||
---
|
||||
apiVersion: core.oam.dev/v1alpha1
|
||||
kind: Workflow
|
||||
metadata:
|
||||
name: make-release-in-hangzhou
|
||||
namespace: examples
|
||||
steps:
|
||||
- name: deploy-hangzhou
|
||||
type: deploy
|
||||
properties:
|
||||
policies: ["topology-hangzhou-clusters"]
|
||||
```
|
||||
|
||||
This application should be successful after a while.
|
||||
Now if we edit the referred deployment and set its image to an invalid value, such as `nginx:1.200`.
|
||||
The application will not re-run the workflow to make this change take effect automatically.
|
||||
But since the dependencies of this application changes, it means the next workflow run will update the deployment image.
|
||||
|
||||
Now let's run `vela live-diff nginx-publish-version -n examples` to check this diff
|
||||
```bash
|
||||
$ vela live-diff nginx-publish-version -n examples
|
||||
* Application (nginx-publish-version) has no change
|
||||
* External Policy (topology-hangzhou-clusters) has no change
|
||||
* External Workflow (make-release-in-hangzhou) has no change
|
||||
* Referred Object (apps/v1 Deployment examples/nginx-publish-version) has been modified(*)
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
annotations:
|
||||
- deployment.kubernetes.io/revision: "1"
|
||||
+ deployment.kubernetes.io/revision: "2"
|
||||
labels:
|
||||
app: nginx-publish-version
|
||||
name: nginx-publish-version
|
||||
namespace: examples
|
||||
spec:
|
||||
progressDeadlineSeconds: 600
|
||||
replicas: 1
|
||||
revisionHistoryLimit: 10
|
||||
selector:
|
||||
matchLabels:
|
||||
app: nginx-publish-version
|
||||
strategy:
|
||||
rollingUpdate:
|
||||
maxSurge: 25%
|
||||
maxUnavailable: 25%
|
||||
type: RollingUpdate
|
||||
template:
|
||||
metadata:
|
||||
creationTimestamp: null
|
||||
labels:
|
||||
app: nginx-publish-version
|
||||
spec:
|
||||
containers:
|
||||
- - image: nginx
|
||||
+ - image: nginx:1.200
|
||||
imagePullPolicy: Always
|
||||
name: nginx
|
||||
resources: {}
|
||||
terminationMessagePath: /dev/termination-log
|
||||
terminationMessagePolicy: File
|
||||
dnsPolicy: ClusterFirst
|
||||
restartPolicy: Always
|
||||
schedulerName: default-scheduler
|
||||
securityContext: {}
|
||||
terminationGracePeriodSeconds: 30
|
||||
```
|
||||
|
||||
We can see all the changes of the application spec and the dependencies.
|
||||
Now let's make this change take effects.
|
||||
Update the `publishVersion` annotation in the application to `alpha2` to trigger the re-run of workflow.
|
||||
We will find the application stuck at `runningWorkflow` as the deployment cannot finish the update progress due to the invalid image.
|
||||
|
||||
Now we can run `vela revision list nginx-publish-version -n examples` to list all the available revisions.
|
||||
```bash
|
||||
$ vela revision list nginx-publish-version -n examples
|
||||
NAME PUBLISH_VERSION SUCCEEDED HASH BEGIN_TIME STATUS SIZE
|
||||
nginx-publish-version-v1 alpha1 true d428eff1f0a7918 2022-03-28 20:54:25 Succeeded 8.1 KiB
|
||||
nginx-publish-version-v2 alpha2 false 4f04da8827d87922 2022-03-28 21:01:25 Executing 8.1 KiB
|
||||
```
|
||||
|
||||
Before rolling back, we need to suspend the workflow of the application first. Run `vela workflow suspend nginx-publish-version -n examples`.
|
||||
After the application workflow is suspended, run `vela workflow rollback nginx-publish-version -n examples`, the workflow will be rolled back and the application resources will restore to the succeeded state.
|
||||
```bash
|
||||
$ vela workflow suspend nginx-publish-version -n examples
|
||||
Successfully suspend workflow: nginx-publish-version
|
||||
$ vela workflow rollback nginx-publish-version -n examples
|
||||
Find succeeded application revision nginx-publish-version-v1 (PublishVersion: alpha1) to rollback.
|
||||
Application spec rollback successfully.
|
||||
Application status rollback successfully.
|
||||
Application rollback completed.
|
||||
Application outdated revision cleaned up.
|
||||
```
|
||||
103
docs/examples/custom-trait/README.md
Normal file
103
docs/examples/custom-trait/README.md
Normal file
@@ -0,0 +1,103 @@
|
||||
# How to use
|
||||
|
||||
1. define a stateful component with StatefulSet as output
|
||||
|
||||
```shell
|
||||
$ vela def apply stateful.cue
|
||||
ComponentDefinition test-stateful created in namespace vela-system.
|
||||
```
|
||||
|
||||
2. define a custom trait with patch volume
|
||||
|
||||
```shell
|
||||
$ vela def apply volume-trait.cue
|
||||
TraitDefinition storageclass created in namespace vela-system.
|
||||
```
|
||||
|
||||
3. You can validate it by:
|
||||
```
|
||||
$ vela def vet volume-trait.cue
|
||||
Validation succeed.
|
||||
```
|
||||
|
||||
|
||||
|
||||
4. try dry run your app:
|
||||
```
|
||||
vela dry-run -f app.yaml
|
||||
```
|
||||
|
||||
```yaml
|
||||
# Application(website) -- Component(custom-component)
|
||||
---
|
||||
|
||||
apiVersion: apps/v1
|
||||
kind: StatefulSet
|
||||
metadata:
|
||||
annotations: {}
|
||||
labels:
|
||||
app.oam.dev/appRevision: ""
|
||||
app.oam.dev/component: custom-component
|
||||
app.oam.dev/name: website
|
||||
app.oam.dev/namespace: default
|
||||
app.oam.dev/resourceType: WORKLOAD
|
||||
workload.oam.dev/type: test-stateful
|
||||
name: custom-component
|
||||
namespace: default
|
||||
spec:
|
||||
minReadySeconds: 10
|
||||
replicas: 1
|
||||
selector:
|
||||
matchLabels:
|
||||
app: custom-component
|
||||
serviceName: custom-component
|
||||
template:
|
||||
metadata:
|
||||
labels:
|
||||
app: custom-component
|
||||
spec:
|
||||
containers:
|
||||
- image: nginx:latest
|
||||
name: nginx
|
||||
ports:
|
||||
- containerPort: 80
|
||||
name: web
|
||||
volumeMounts:
|
||||
- mountPath: /usr/share/nginx/html
|
||||
name: test
|
||||
terminationGracePeriodSeconds: 10
|
||||
volumeClaimTemplates:
|
||||
- metadata:
|
||||
name: test
|
||||
spec:
|
||||
accessModes:
|
||||
- ReadWriteOnce
|
||||
resources:
|
||||
requests:
|
||||
storage: 10Gi
|
||||
storageClassName: cbs
|
||||
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: Service
|
||||
metadata:
|
||||
annotations: {}
|
||||
labels:
|
||||
app: custom-component
|
||||
app.oam.dev/appRevision: ""
|
||||
app.oam.dev/component: custom-component
|
||||
app.oam.dev/name: website
|
||||
app.oam.dev/namespace: default
|
||||
app.oam.dev/resourceType: TRAIT
|
||||
trait.oam.dev/resource: web
|
||||
trait.oam.dev/type: AuxiliaryWorkload
|
||||
name: custom-component
|
||||
namespace: default
|
||||
spec:
|
||||
clusterIP: None
|
||||
ports:
|
||||
- name: web
|
||||
port: 80
|
||||
selector:
|
||||
app: custom-component
|
||||
```
|
||||
20
docs/examples/custom-trait/app.yaml
Normal file
20
docs/examples/custom-trait/app.yaml
Normal file
@@ -0,0 +1,20 @@
|
||||
apiVersion: core.oam.dev/v1beta1
|
||||
kind: Application
|
||||
metadata:
|
||||
name: website
|
||||
namespace: default
|
||||
spec:
|
||||
components:
|
||||
- name: custom-component
|
||||
type: test-stateful
|
||||
properties:
|
||||
image: nginx:latest
|
||||
replicas: 1
|
||||
traits:
|
||||
- type: storageclass
|
||||
properties:
|
||||
volumeClaimTemplates:
|
||||
- name: test
|
||||
requests: 10Gi
|
||||
storageClassName: cbs
|
||||
mountPath: /usr/share/nginx/html
|
||||
58
docs/examples/custom-trait/stateful.cue
Normal file
58
docs/examples/custom-trait/stateful.cue
Normal file
@@ -0,0 +1,58 @@
|
||||
"test-stateful": {
|
||||
annotations: {}
|
||||
attributes: workload: definition: {
|
||||
apiVersion: "apps/v1"
|
||||
kind: "StatefulSet"
|
||||
}
|
||||
description: "StatefulSet component."
|
||||
labels: {}
|
||||
type: "component"
|
||||
}
|
||||
|
||||
template: {
|
||||
output: {
|
||||
apiVersion: "apps/v1"
|
||||
kind: "StatefulSet"
|
||||
metadata: name: context.name
|
||||
spec: {
|
||||
selector: matchLabels: app: context.name
|
||||
minReadySeconds: 10
|
||||
replicas: parameter.replicas
|
||||
serviceName: context.name
|
||||
template: {
|
||||
metadata: labels: app: context.name
|
||||
spec: {
|
||||
containers: [{
|
||||
name: "nginx"
|
||||
ports: [{
|
||||
name: "web"
|
||||
containerPort: 80
|
||||
}]
|
||||
image: parameter.image
|
||||
}]
|
||||
terminationGracePeriodSeconds: 10
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
outputs: web: {
|
||||
apiVersion: "v1"
|
||||
kind: "Service"
|
||||
metadata: {
|
||||
name: context.name
|
||||
labels: app: context.name
|
||||
}
|
||||
spec: {
|
||||
clusterIP: "None"
|
||||
ports: [{
|
||||
name: "web"
|
||||
port: 80
|
||||
}]
|
||||
selector: app: context.name
|
||||
}
|
||||
}
|
||||
parameter: {
|
||||
image: string
|
||||
replicas: int
|
||||
}
|
||||
}
|
||||
56
docs/examples/custom-trait/volume-trait.cue
Normal file
56
docs/examples/custom-trait/volume-trait.cue
Normal file
@@ -0,0 +1,56 @@
|
||||
storageclass: {
|
||||
type: "trait"
|
||||
annotations: {}
|
||||
labels: {}
|
||||
description: "Add storageclass on K8s pod for your workload which follows the pod spec in path 'spec.template'."
|
||||
attributes: {
|
||||
appliesToWorkloads: ["*"]
|
||||
}
|
||||
}
|
||||
template: {
|
||||
|
||||
volumeClaimTemplatesList: *[
|
||||
for v in parameter.volumeClaimTemplates {
|
||||
{
|
||||
metadata: name: v.name
|
||||
spec: {
|
||||
accessModes: ["ReadWriteOnce"]
|
||||
resources: requests: storage: v.requests
|
||||
storageClassName: v.storageClassName
|
||||
}
|
||||
}
|
||||
},
|
||||
] | []
|
||||
|
||||
volumeClaimTemplateVolumeMountsList: *[
|
||||
for v in parameter.volumeClaimTemplates {
|
||||
{
|
||||
name: v.name
|
||||
mountPath: v.mountPath
|
||||
}
|
||||
},
|
||||
] | []
|
||||
|
||||
patch: {
|
||||
// +patchKey=name
|
||||
spec: {
|
||||
template: spec: {
|
||||
containers: [...{
|
||||
// +patchKey=name
|
||||
volumeMounts: volumeClaimTemplateVolumeMountsList
|
||||
}]
|
||||
}
|
||||
// +patchKey=name
|
||||
volumeClaimTemplates: volumeClaimTemplatesList
|
||||
}
|
||||
}
|
||||
|
||||
parameter: {
|
||||
volumeClaimTemplates?: [...{
|
||||
name: string
|
||||
requests: string
|
||||
storageClassName: string
|
||||
mountPath: string
|
||||
}]
|
||||
}
|
||||
}
|
||||
433
docs/examples/multicluster/deploy.md
Normal file
433
docs/examples/multicluster/deploy.md
Normal file
@@ -0,0 +1,433 @@
|
||||
# Advanced examples for multi-cluster deployment
|
||||
|
||||
The below features are introduced in KubeVela v1.3.
|
||||
|
||||

|
||||
|
||||
## Topology Policy
|
||||
|
||||
Topology policy is a policy used to describe the location where application component should be deployed and managed.
|
||||
|
||||
The most straight forward way is directly specifying the names of clusters to be deployed.
|
||||
In the following example, the nginx webservice will be deployed to the `examples` namespace in both `hangzhou-1` and `hangzhou-2` clusters concurrently.
|
||||
After nginx in both clusters are ready, the application will finish running workflow and becomes healthy.
|
||||
|
||||
```yaml
|
||||
apiVersion: core.oam.dev/v1beta1
|
||||
kind: Application
|
||||
metadata:
|
||||
name: basic-topology
|
||||
namespace: examples
|
||||
spec:
|
||||
components:
|
||||
- name: nginx-basic
|
||||
type: webservice
|
||||
properties:
|
||||
image: nginx
|
||||
policies:
|
||||
- name: topology-hangzhou-clusters
|
||||
type: topology
|
||||
properties:
|
||||
clusters: ["hangzhou-1", "hangzhou-2"]
|
||||
```
|
||||
|
||||
The clusters in the topology can also be selected by labels instead of names.
|
||||
|
||||
```yaml
|
||||
apiVersion: core.oam.dev/v1beta1
|
||||
kind: Application
|
||||
metadata:
|
||||
name: label-selector-topology
|
||||
namespace: examples
|
||||
spec:
|
||||
components:
|
||||
- name: nginx-label-selector
|
||||
type: webservice
|
||||
properties:
|
||||
image: nginx
|
||||
policies:
|
||||
- name: topology-hangzhou-clusters
|
||||
type: topology
|
||||
properties:
|
||||
clusterLabelSelector:
|
||||
region: hangzhou
|
||||
```
|
||||
|
||||
If you want to deploy application components into the control plane cluster, you can use the `local` cluster.
|
||||
Besides, you can also deploy your application components in another namespace other than the application's namespace.
|
||||
|
||||
```yaml
|
||||
apiVersion: core.oam.dev/v1beta1
|
||||
kind: Application
|
||||
metadata:
|
||||
name: local-ns-topology
|
||||
namespace: examples
|
||||
spec:
|
||||
components:
|
||||
- name: nginx-local-ns
|
||||
type: webservice
|
||||
properties:
|
||||
image: nginx
|
||||
policies:
|
||||
- name: topology-local
|
||||
type: topology
|
||||
properties:
|
||||
clusters: ["local"]
|
||||
namespace: examples-alternative
|
||||
```
|
||||
|
||||
## Deploy WorkflowStep
|
||||
|
||||
By default, if you declare multiple topology policies in the application, the application components will be deployed in all destinations following the order of the policies.
|
||||
|
||||
If you want to manipulate the process of deploying them, for example, changing the order or adding manual-approval, you can use the `deploy` workflow step explicitly in the workflow to achieve that.
|
||||
|
||||
```yaml
|
||||
apiVersion: core.oam.dev/v1beta1
|
||||
kind: Application
|
||||
metadata:
|
||||
name: deploy-workflowstep
|
||||
namespace: examples
|
||||
spec:
|
||||
components:
|
||||
- name: nginx-deploy-workflowstep
|
||||
type: webservice
|
||||
properties:
|
||||
image: nginx
|
||||
policies:
|
||||
- name: topology-hangzhou-clusters
|
||||
type: topology
|
||||
properties:
|
||||
clusterLabelSelector:
|
||||
region: hangzhou
|
||||
- name: topology-local
|
||||
type: topology
|
||||
properties:
|
||||
clusters: ["local"]
|
||||
namespace: examples-alternative
|
||||
workflow:
|
||||
steps:
|
||||
- type: deploy
|
||||
name: deploy-local
|
||||
properties:
|
||||
policies: ["topology-local"]
|
||||
- type: deploy
|
||||
name: deploy-hangzhou
|
||||
properties:
|
||||
# require manual approval before running this step
|
||||
auto: false
|
||||
policies: ["topology-hangzhou-clusters"]
|
||||
```
|
||||
|
||||
You can also deploy application components with different topology policies concurrently, by filling these topology policies in on `deploy` step.
|
||||
|
||||
```yaml
|
||||
apiVersion: core.oam.dev/v1beta1
|
||||
kind: Application
|
||||
metadata:
|
||||
name: deploy-concurrently
|
||||
namespace: examples
|
||||
spec:
|
||||
components:
|
||||
- name: nginx-deploy-concurrently
|
||||
type: webservice
|
||||
properties:
|
||||
image: nginx
|
||||
policies:
|
||||
- name: topology-hangzhou-clusters
|
||||
type: topology
|
||||
properties:
|
||||
clusterLabelSelector:
|
||||
region: hangzhou
|
||||
- name: topology-local
|
||||
type: topology
|
||||
properties:
|
||||
clusters: ["local"]
|
||||
namespace: examples-alternative
|
||||
workflow:
|
||||
steps:
|
||||
- type: deploy
|
||||
name: deploy-all
|
||||
properties:
|
||||
policies: ["topology-local", "topology-hangzhou-clusters"]
|
||||
```
|
||||
|
||||
## Override Policy
|
||||
|
||||
Override policy helps you to customize the application components in different clusters. For example, using a different container image or changing the default number of replicas. The override policy should be used together with the topology policy in the `deploy` workflow step.
|
||||
|
||||
```yaml
|
||||
apiVersion: core.oam.dev/v1beta1
|
||||
kind: Application
|
||||
metadata:
|
||||
name: deploy-with-override
|
||||
namespace: examples
|
||||
spec:
|
||||
components:
|
||||
- name: nginx-with-override
|
||||
type: webservice
|
||||
properties:
|
||||
image: nginx
|
||||
policies:
|
||||
- name: topology-hangzhou-clusters
|
||||
type: topology
|
||||
properties:
|
||||
clusterLabelSelector:
|
||||
region: hangzhou
|
||||
- name: topology-local
|
||||
type: topology
|
||||
properties:
|
||||
clusters: ["local"]
|
||||
namespace: examples-alternative
|
||||
- name: override-nginx-legacy-image
|
||||
type: override
|
||||
properties:
|
||||
components:
|
||||
- name: nginx-with-override
|
||||
properties:
|
||||
image: nginx:1.20
|
||||
- name: override-high-availability
|
||||
type: override
|
||||
properties:
|
||||
components:
|
||||
- type: webservice
|
||||
traits:
|
||||
- type: scaler
|
||||
properties:
|
||||
replicas: 3
|
||||
workflow:
|
||||
steps:
|
||||
- type: deploy
|
||||
name: deploy-local
|
||||
properties:
|
||||
policies: ["topology-local"]
|
||||
- type: deploy
|
||||
name: deploy-hangzhou
|
||||
properties:
|
||||
policies: ["topology-hangzhou-clusters", "override-nginx-legacy-image", "override-high-availability"]
|
||||
```
|
||||
|
||||
The override policy has many advanced capabilities, such as adding new component or selecting components to use.
|
||||
The following example will deploy `nginx:1.20` to local cluster. `nginx` and `nginx:stable` will be deployed to hangzhou clusters.
|
||||
|
||||
```yaml
|
||||
apiVersion: core.oam.dev/v1beta1
|
||||
kind: Application
|
||||
metadata:
|
||||
name: advance-override
|
||||
namespace: examples
|
||||
spec:
|
||||
components:
|
||||
- name: nginx-advance-override-legacy
|
||||
type: webservice
|
||||
properties:
|
||||
image: nginx:1.20
|
||||
- name: nginx-advance-override-latest
|
||||
type: webservice
|
||||
properties:
|
||||
image: nginx
|
||||
policies:
|
||||
- name: topology-hangzhou-clusters
|
||||
type: topology
|
||||
properties:
|
||||
clusterLabelSelector:
|
||||
region: hangzhou
|
||||
- name: topology-local
|
||||
type: topology
|
||||
properties:
|
||||
clusters: ["local"]
|
||||
namespace: examples-alternative
|
||||
- name: override-nginx-legacy
|
||||
type: override
|
||||
properties:
|
||||
selector: ["nginx-advance-override-legacy"]
|
||||
- name: override-nginx-latest
|
||||
type: override
|
||||
properties:
|
||||
selector: ["nginx-advance-override-latest", "nginx-advance-override-stable"]
|
||||
components:
|
||||
- name: nginx-advance-override-stable
|
||||
type: webservice
|
||||
properties:
|
||||
image: nginx:stable
|
||||
workflow:
|
||||
steps:
|
||||
- type: deploy
|
||||
name: deploy-local
|
||||
properties:
|
||||
policies: ["topology-local", "override-nginx-legacy"]
|
||||
- type: deploy
|
||||
name: deploy-hangzhou
|
||||
properties:
|
||||
policies: ["topology-hangzhou-clusters", "override-nginx-latest"]
|
||||
```
|
||||
|
||||
## Ref-object Component
|
||||
|
||||
Sometimes, you may want to copy resources from one place to other places, such as copying secrets from the control plane cluster into managed clusters.
|
||||
You can use the `ref-object` typed component to achieve that.
|
||||
|
||||
```yaml
|
||||
apiVersion: core.oam.dev/v1beta1
|
||||
kind: Application
|
||||
metadata:
|
||||
name: ref-objects-example
|
||||
namespace: examples
|
||||
spec:
|
||||
components:
|
||||
- name: image-pull-secrets
|
||||
type: ref-objects
|
||||
properties:
|
||||
objects:
|
||||
- resource: secret
|
||||
name: image-credential-to-copy
|
||||
policies:
|
||||
- name: topology-hangzhou-clusters
|
||||
type: topology
|
||||
properties:
|
||||
clusterLabelSelector:
|
||||
region: hangzhou
|
||||
```
|
||||
|
||||
You can also select resources by labels and duplicate them from one cluster into another cluster.
|
||||
|
||||
```yaml
|
||||
apiVersion: core.oam.dev/v1beta1
|
||||
kind: Application
|
||||
metadata:
|
||||
name: ref-objects-duplicate-deployments
|
||||
namespace: examples
|
||||
spec:
|
||||
components:
|
||||
- name: duplicate-deployment
|
||||
type: ref-objects
|
||||
properties:
|
||||
objects:
|
||||
- resource: deployment
|
||||
cluster: hangzhou-1
|
||||
# select all deployment in the `examples` namespace in cluster `hangzhou-1` that matches the labelSelector
|
||||
labelSelector:
|
||||
need-duplicate: "true"
|
||||
policies:
|
||||
- name: topology-hangzhou-2
|
||||
type: topology
|
||||
properties:
|
||||
clusters: ["hangzhou-2"]
|
||||
```
|
||||
|
||||
You can also form a component by multiple referenced resources and even attach traits to the main workload.
|
||||
|
||||
```yaml
|
||||
apiVersion: core.oam.dev/v1beta1
|
||||
kind: Application
|
||||
metadata:
|
||||
name: ref-objects-multiple-resources
|
||||
namespace: examples
|
||||
spec:
|
||||
components:
|
||||
- name: nginx-ref-multiple-resources
|
||||
type: ref-objects
|
||||
properties:
|
||||
objects:
|
||||
- resource: deployment
|
||||
- resource: service
|
||||
traits:
|
||||
- type: scaler
|
||||
properties:
|
||||
replicas: 3
|
||||
policies:
|
||||
- name: topology-hangzhou-clusters
|
||||
type: topology
|
||||
properties:
|
||||
clusterLabelSelector:
|
||||
region: hangzhou
|
||||
```
|
||||
|
||||
## External Policies and Workflow
|
||||
|
||||
Sometimes, you may want to use the same policy across multiple applications or reuse previous workflow to deploy different resources.
|
||||
To reduce the repeated code, you can leverage the external policies and workflow and refer to them in your applications.
|
||||
|
||||
> NOTE: you can only refer to Policy and Workflow within your application's namespace.
|
||||
|
||||
```yaml
|
||||
apiVersion: core.oam.dev/v1alpha1
|
||||
kind: Policy
|
||||
metadata:
|
||||
name: topology-hangzhou-clusters
|
||||
namespace: examples
|
||||
type: topology
|
||||
properties:
|
||||
clusterLabelSelector:
|
||||
region: hangzhou
|
||||
---
|
||||
apiVersion: core.oam.dev/v1alpha1
|
||||
kind: Policy
|
||||
metadata:
|
||||
name: override-high-availability-webservice
|
||||
namespace: examples
|
||||
type: override
|
||||
properties:
|
||||
components:
|
||||
- type: webservice
|
||||
traits:
|
||||
- type: scaler
|
||||
properties:
|
||||
replicas: 3
|
||||
---
|
||||
apiVersion: core.oam.dev/v1alpha1
|
||||
kind: Workflow
|
||||
metadata:
|
||||
name: make-release-in-hangzhou
|
||||
namespace: examples
|
||||
steps:
|
||||
- type: deploy
|
||||
name: deploy-hangzhou
|
||||
properties:
|
||||
auto: false
|
||||
policies: ["override-high-availability-webservice", "topology-hangzhou-clusters"]
|
||||
```
|
||||
|
||||
```yaml
|
||||
apiVersion: core.oam.dev/v1beta1
|
||||
kind: Application
|
||||
metadata:
|
||||
name: external-policies-and-workflow
|
||||
namespace: examples
|
||||
spec:
|
||||
components:
|
||||
- name: nginx-external-policies-and-workflow
|
||||
type: webservice
|
||||
properties:
|
||||
image: nginx
|
||||
workflow:
|
||||
ref: make-release-in-hangzhou
|
||||
```
|
||||
|
||||
> NOTE: The internal policies will be loaded first. External policies will only be used when there is no corresponding policy inside the application. In the following example, we can reuse `tology-hangzhou-clusters` policy and `make-release-in-hangzhou` workflow but modify the `override-high-availability-webservice` by injecting the same-named policy inside the new application.
|
||||
|
||||
```yaml
|
||||
apiVersion: core.oam.dev/v1beta1
|
||||
kind: Application
|
||||
metadata:
|
||||
name: nginx-stable-ultra
|
||||
namespace: examples
|
||||
spec:
|
||||
components:
|
||||
- name: nginx-stable-ultra
|
||||
type: webservice
|
||||
properties:
|
||||
image: nginx:stable
|
||||
policies:
|
||||
- name: override-high-availability-webservice
|
||||
type: override
|
||||
properties:
|
||||
components:
|
||||
- type: webservice
|
||||
traits:
|
||||
- type: scaler
|
||||
properties:
|
||||
replicas: 5
|
||||
workflow:
|
||||
ref: make-release-in-hangzhou
|
||||
```
|
||||
BIN
docs/examples/multicluster/ref-arch.jpg
Normal file
BIN
docs/examples/multicluster/ref-arch.jpg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 111 KiB |
107
docs/examples/rbac/rbac.md
Normal file
107
docs/examples/rbac/rbac.md
Normal file
@@ -0,0 +1,107 @@
|
||||
# RBAC
|
||||
|
||||
User:
|
||||
|
||||
```yaml
|
||||
name: user
|
||||
userRoles: ["app-developer"]
|
||||
...
|
||||
```
|
||||
|
||||
ProjectUser:
|
||||
|
||||
```yaml
|
||||
username: user
|
||||
project: demo
|
||||
userRoles: ["app-developer"]
|
||||
```
|
||||
|
||||
Role:
|
||||
|
||||
```yaml
|
||||
name: app-developer
|
||||
project: demo
|
||||
permissions: ["app-manage"]
|
||||
```
|
||||
|
||||
```yaml
|
||||
name: admin
|
||||
permissions: ["all"]
|
||||
```
|
||||
|
||||
Permission:
|
||||
|
||||
```yaml
|
||||
name: app-manage
|
||||
project: demo
|
||||
resource: ["project:demo/application:*"]
|
||||
actions: ["*"]
|
||||
effect: Allow
|
||||
principal: {}
|
||||
condition: {}
|
||||
```
|
||||
|
||||
```yaml
|
||||
name: app1-manage
|
||||
project: demo
|
||||
resource: ["project:demo/application:app1/*"]
|
||||
actions: ["*"]
|
||||
effect: Allow
|
||||
principal: {}
|
||||
condition: {}
|
||||
|
||||
name: app2-manage
|
||||
project: demo
|
||||
resource: ["project:demo/application:app2/*"]
|
||||
actions: ["*"]
|
||||
effect: Allow
|
||||
principal: {}
|
||||
condition: {}
|
||||
```
|
||||
|
||||
```yaml
|
||||
name: cluster-manage
|
||||
resource: ["cluster:*"]
|
||||
actions: ["*"]
|
||||
effect: Allow
|
||||
principal: {}
|
||||
condition: {}
|
||||
```
|
||||
|
||||
```yaml
|
||||
name: cluster-beijing-manage
|
||||
resource: ["cluster:beijing"]
|
||||
actions: ["*"]
|
||||
effect: Allow
|
||||
principal: {}
|
||||
condition: {}
|
||||
```
|
||||
|
||||
```yaml
|
||||
name: all
|
||||
resource: ["*"]
|
||||
actions: ["*"]
|
||||
effect: Allow
|
||||
principal: {}
|
||||
condition: {}
|
||||
```
|
||||
|
||||
PermissionTemplate:
|
||||
|
||||
```yaml
|
||||
name: app-manage
|
||||
resource: ["project:${projectName}/application:*"]
|
||||
actions: ["*"]
|
||||
level: project
|
||||
effect: Allow
|
||||
principal: {}
|
||||
condition: {}
|
||||
```
|
||||
|
||||
```yaml
|
||||
name: deny-delete-cluster
|
||||
resource: ["cluster:*"]
|
||||
actions: ["delete"]
|
||||
level: platform
|
||||
effect: Deny
|
||||
```
|
||||
@@ -29,5 +29,5 @@ kubectl apply -f ./docs/examples/rollout-trait/app-v3.yaml
|
||||
|
||||
6. modify targetSize as 7 to scale
|
||||
```shell
|
||||
kubectl apply -f ./docs/examples/rollout-trait/app-sacle.yaml
|
||||
kubectl apply -f ./docs/examples/rollout-trait/app-scale.yaml
|
||||
```
|
||||
|
||||
40
docs/examples/terraform/app-jdbc.yaml
Normal file
40
docs/examples/terraform/app-jdbc.yaml
Normal file
@@ -0,0 +1,40 @@
|
||||
apiVersion: core.oam.dev/v1beta1
|
||||
kind: Application
|
||||
metadata:
|
||||
name: jdbc
|
||||
spec:
|
||||
components:
|
||||
- name: db
|
||||
type: alibaba-rds
|
||||
properties:
|
||||
instance_name: favorite-links
|
||||
database_name: db1
|
||||
account_name: oamtest
|
||||
password: U34rfwefwefffaked
|
||||
security_ips: [ "0.0.0.0/0" ]
|
||||
privilege: ReadWrite
|
||||
writeConnectionSecretToRef:
|
||||
name: db-conn
|
||||
- name: express-server
|
||||
type: webservice
|
||||
properties:
|
||||
image: crccheck/hello-world
|
||||
port: 8000
|
||||
|
||||
workflow:
|
||||
steps:
|
||||
- name: jdbc
|
||||
type: generate-jdbc-connection
|
||||
outputs:
|
||||
- name: jdbc
|
||||
valueFrom: jdbc
|
||||
properties:
|
||||
name: db-conn
|
||||
namespace: default
|
||||
- name: apply
|
||||
type: apply-component
|
||||
inputs:
|
||||
- from: jdbc
|
||||
parameterKey: env
|
||||
properties:
|
||||
component: express-server
|
||||
25
docs/examples/traits/annotations/example.yaml
Normal file
25
docs/examples/traits/annotations/example.yaml
Normal file
@@ -0,0 +1,25 @@
|
||||
apiVersion: core.oam.dev/v1beta1
|
||||
kind: Application
|
||||
metadata:
|
||||
name: busybox
|
||||
spec:
|
||||
components:
|
||||
- name: busybox
|
||||
type: webservice
|
||||
properties:
|
||||
image: busybox
|
||||
cmd: ["sleep", "86400"]
|
||||
annotations:
|
||||
annotation-key: annotation-value
|
||||
to-delete-annotation-key: to-delete-annotation-value
|
||||
traits:
|
||||
# the `annotations` trait will add/delete annotation key/value pair to the
|
||||
# labels of the workload and the template inside the spec of the workload (if exists)
|
||||
# 1. if original annotations contains the key, value will be overridden
|
||||
# 2. if original annotations do not contain the key, value will be added
|
||||
# 3. if original annotations contains the key and the value is null, the key will be removed
|
||||
- type: annotations
|
||||
properties:
|
||||
added-annotation-key: added-annotation-value
|
||||
annotation-key: modified-annotation-value
|
||||
to-delete-annotation-key: null
|
||||
@@ -23,11 +23,6 @@ spec:
|
||||
- type: json-patch
|
||||
properties:
|
||||
operations:
|
||||
- op: add
|
||||
path: "/metadata"
|
||||
value:
|
||||
labels:
|
||||
deploy-label-key: deploy-label-added-value
|
||||
- op: add
|
||||
path: "/spec/replicas"
|
||||
value: 3
|
||||
|
||||
25
docs/examples/traits/labels/example.yaml
Normal file
25
docs/examples/traits/labels/example.yaml
Normal file
@@ -0,0 +1,25 @@
|
||||
apiVersion: core.oam.dev/v1beta1
|
||||
kind: Application
|
||||
metadata:
|
||||
name: busybox
|
||||
spec:
|
||||
components:
|
||||
- name: busybox
|
||||
type: webservice
|
||||
properties:
|
||||
image: busybox
|
||||
cmd: ["sleep", "86400"]
|
||||
labels:
|
||||
label-key: label-value
|
||||
to-delete-label-key: to-delete-label-value
|
||||
traits:
|
||||
# the `labels` trait will add/delete label key/value pair to the
|
||||
# labels of the workload and the template inside the spec of the workload (if exists)
|
||||
# 1. if original labels contains the key, value will be overridden
|
||||
# 2. if original labels do not contain the key, value will be added
|
||||
# 3. if original labels contains the key and the value is null, the key will be removed
|
||||
- type: labels
|
||||
properties:
|
||||
added-label-key: added-label-value
|
||||
label-key: modified-label-value
|
||||
to-delete-label-key: null
|
||||
@@ -156,7 +156,7 @@ var _ = Describe("Test Kubectl Plugin", func() {
|
||||
tdName := "annotations"
|
||||
output, err := e2e.Exec(fmt.Sprintf("kubectl-vela show %s -n default", tdName))
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
Expect(output).Should(ContainSubstring("map[string]string"))
|
||||
Expect(output).Should(ContainSubstring("map[string](null|string)"))
|
||||
})
|
||||
It("Test show webservice def with cue ignore annotation ", func() {
|
||||
tdName := "webservice"
|
||||
@@ -728,9 +728,7 @@ spec:
|
||||
---
|
||||
`
|
||||
|
||||
var livediffResult = `---
|
||||
# Application (test-vela-app) has been modified(*)
|
||||
---
|
||||
var livediffResult = `Application (test-vela-app) has been modified(*)
|
||||
apiVersion: core.oam.dev/v1beta1
|
||||
kind: Application
|
||||
metadata:
|
||||
@@ -759,15 +757,11 @@ var livediffResult = `---
|
||||
type: test-webservice
|
||||
status: {}
|
||||
|
||||
---
|
||||
## Component (express-server) has been removed(-)
|
||||
---
|
||||
* Component (express-server) has been removed(-)
|
||||
- apiVersion: apps/v1
|
||||
- kind: Deployment
|
||||
- metadata:
|
||||
- annotations: {}
|
||||
- labels:
|
||||
- app.oam.dev/appRevision: ""
|
||||
- app.oam.dev/component: express-server
|
||||
- app.oam.dev/name: test-vela-app
|
||||
- app.oam.dev/namespace: default
|
||||
@@ -790,15 +784,11 @@ var livediffResult = `---
|
||||
- ports:
|
||||
- - containerPort: 80
|
||||
|
||||
---
|
||||
### Component (express-server) / Trait (test-ingress/service) has been removed(-)
|
||||
---
|
||||
* Component (express-server) / Trait (test-ingress/service) has been removed(-)
|
||||
- apiVersion: v1
|
||||
- kind: Service
|
||||
- metadata:
|
||||
- annotations: {}
|
||||
- labels:
|
||||
- app.oam.dev/appRevision: ""
|
||||
- app.oam.dev/component: express-server
|
||||
- app.oam.dev/name: test-vela-app
|
||||
- app.oam.dev/namespace: default
|
||||
@@ -814,15 +804,11 @@ var livediffResult = `---
|
||||
- selector:
|
||||
- app.oam.dev/component: express-server
|
||||
|
||||
---
|
||||
### Component (express-server) / Trait (test-ingress/ingress) has been removed(-)
|
||||
---
|
||||
* Component (express-server) / Trait (test-ingress/ingress) has been removed(-)
|
||||
- apiVersion: networking.k8s.io/v1beta1
|
||||
- kind: Ingress
|
||||
- metadata:
|
||||
- annotations: {}
|
||||
- labels:
|
||||
- app.oam.dev/appRevision: ""
|
||||
- app.oam.dev/component: express-server
|
||||
- app.oam.dev/name: test-vela-app
|
||||
- app.oam.dev/namespace: default
|
||||
@@ -841,15 +827,11 @@ var livediffResult = `---
|
||||
- servicePort: 80
|
||||
- path: /
|
||||
|
||||
---
|
||||
## Component (new-express-server) has been added(+)
|
||||
---
|
||||
* Component (new-express-server) has been added(+)
|
||||
+ apiVersion: apps/v1
|
||||
+ kind: Deployment
|
||||
+ metadata:
|
||||
+ annotations: {}
|
||||
+ labels:
|
||||
+ app.oam.dev/appRevision: ""
|
||||
+ app.oam.dev/component: new-express-server
|
||||
+ app.oam.dev/name: test-vela-app
|
||||
+ app.oam.dev/namespace: default
|
||||
@@ -877,15 +859,11 @@ var livediffResult = `---
|
||||
+ requests:
|
||||
+ cpu: "0.5"
|
||||
|
||||
---
|
||||
### Component (new-express-server) / Trait (test-ingress/service) has been added(+)
|
||||
---
|
||||
* Component (new-express-server) / Trait (test-ingress/service) has been added(+)
|
||||
+ apiVersion: v1
|
||||
+ kind: Service
|
||||
+ metadata:
|
||||
+ annotations: {}
|
||||
+ labels:
|
||||
+ app.oam.dev/appRevision: ""
|
||||
+ app.oam.dev/component: new-express-server
|
||||
+ app.oam.dev/name: test-vela-app
|
||||
+ app.oam.dev/namespace: default
|
||||
@@ -901,15 +879,11 @@ var livediffResult = `---
|
||||
+ selector:
|
||||
+ app.oam.dev/component: new-express-server
|
||||
|
||||
---
|
||||
### Component (new-express-server) / Trait (test-ingress/ingress) has been added(+)
|
||||
---
|
||||
* Component (new-express-server) / Trait (test-ingress/ingress) has been added(+)
|
||||
+ apiVersion: networking.k8s.io/v1beta1
|
||||
+ kind: Ingress
|
||||
+ metadata:
|
||||
+ annotations: {}
|
||||
+ labels:
|
||||
+ app.oam.dev/appRevision: ""
|
||||
+ app.oam.dev/component: new-express-server
|
||||
+ app.oam.dev/name: test-vela-app
|
||||
+ app.oam.dev/namespace: default
|
||||
|
||||
13
go.mod
13
go.mod
@@ -14,6 +14,7 @@ require (
|
||||
github.com/bmizerany/assert v0.0.0-20160611221934-b7ed37b82869
|
||||
github.com/briandowns/spinner v1.11.1
|
||||
github.com/containerd/containerd v1.4.13
|
||||
github.com/coreos/go-oidc v2.1.0+incompatible
|
||||
github.com/coreos/prometheus-operator v0.41.1
|
||||
github.com/crossplane/crossplane-runtime v0.14.1-0.20210722005935-0b469fcc77cd
|
||||
github.com/davecgh/go-spew v1.1.1
|
||||
@@ -22,6 +23,7 @@ require (
|
||||
github.com/emicklei/go-restful/v3 v3.0.0-rc2
|
||||
github.com/evanphx/json-patch v4.11.0+incompatible
|
||||
github.com/fatih/color v1.12.0
|
||||
github.com/form3tech-oss/jwt-go v3.2.3+incompatible
|
||||
github.com/fsnotify/fsnotify v1.5.1 // indirect
|
||||
github.com/gertd/go-pluralize v0.1.7
|
||||
github.com/getkin/kin-openapi v0.34.0
|
||||
@@ -41,7 +43,7 @@ require (
|
||||
github.com/kyokomi/emoji v2.2.4+incompatible
|
||||
github.com/mitchellh/hashstructure/v2 v2.0.1
|
||||
github.com/oam-dev/cluster-gateway v1.1.6
|
||||
github.com/oam-dev/cluster-register v1.0.3
|
||||
github.com/oam-dev/cluster-register v1.0.4-0.20220325092210-cee4a3d3fb7d
|
||||
github.com/oam-dev/terraform-config-inspect v0.0.0-20210418082552-fc72d929aa28
|
||||
github.com/oam-dev/terraform-controller v0.4.2
|
||||
github.com/olekukonko/tablewriter v0.0.5
|
||||
@@ -61,6 +63,7 @@ require (
|
||||
github.com/wonderflow/cert-manager-api v1.0.3
|
||||
go.mongodb.org/mongo-driver v1.5.1
|
||||
go.uber.org/zap v1.18.1
|
||||
golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97
|
||||
golang.org/x/oauth2 v0.0.0-20210402161424-2e8d93401602
|
||||
golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6 // indirect
|
||||
golang.org/x/tools v0.1.6 // indirect
|
||||
@@ -74,8 +77,10 @@ require (
|
||||
k8s.io/api v0.22.1
|
||||
k8s.io/apiextensions-apiserver v0.22.1
|
||||
k8s.io/apimachinery v0.22.1
|
||||
k8s.io/apiserver v0.22.1
|
||||
k8s.io/cli-runtime v0.21.0
|
||||
k8s.io/client-go v0.22.1
|
||||
k8s.io/component-base v0.22.1
|
||||
k8s.io/klog v1.0.0
|
||||
k8s.io/klog/v2 v2.9.0
|
||||
k8s.io/kube-aggregator v0.22.1
|
||||
@@ -146,7 +151,6 @@ require (
|
||||
github.com/evanphx/json-patch/v5 v5.1.0 // indirect
|
||||
github.com/exponent-io/jsonpath v0.0.0-20151013193312-d6023ce2651d // indirect
|
||||
github.com/fatih/camelcase v1.0.0 // indirect
|
||||
github.com/form3tech-oss/jwt-go v3.2.3+incompatible // indirect
|
||||
github.com/ghodss/yaml v1.0.0 // indirect
|
||||
github.com/go-errors/errors v1.0.1 // indirect
|
||||
github.com/go-logr/zapr v0.4.0 // indirect
|
||||
@@ -212,6 +216,7 @@ require (
|
||||
github.com/pelletier/go-toml v1.9.3 // indirect
|
||||
github.com/peterbourgon/diskv v2.0.1+incompatible // indirect
|
||||
github.com/pmezard/go-difflib v1.0.0 // indirect
|
||||
github.com/pquerna/cachecontrol v0.0.0-20171018203845-0dec1b30a021 // indirect
|
||||
github.com/prometheus/client_model v0.2.0 // indirect
|
||||
github.com/prometheus/common v0.26.0 // indirect
|
||||
github.com/prometheus/procfs v0.6.0 // indirect
|
||||
@@ -242,7 +247,6 @@ require (
|
||||
go.starlark.net v0.0.0-20200306205701-8dd3e2ee1dd5 // indirect
|
||||
go.uber.org/atomic v1.7.0 // indirect
|
||||
go.uber.org/multierr v1.6.0 // indirect
|
||||
golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97 // indirect
|
||||
golang.org/x/mod v0.4.2 // indirect
|
||||
golang.org/x/net v0.0.0-20211029224645-99673261e6eb // indirect
|
||||
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c // indirect
|
||||
@@ -258,14 +262,13 @@ require (
|
||||
gopkg.in/gorp.v1 v1.7.2 // indirect
|
||||
gopkg.in/inf.v0 v0.9.1 // indirect
|
||||
gopkg.in/ini.v1 v1.62.0 // indirect
|
||||
gopkg.in/square/go-jose.v2 v2.2.2 // indirect
|
||||
gopkg.in/src-d/go-billy.v4 v4.3.2 // indirect
|
||||
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 // indirect
|
||||
gopkg.in/warnings.v0 v0.1.2 // indirect
|
||||
gopkg.in/yaml.v2 v2.4.0 // indirect
|
||||
istio.io/api v0.0.0-20210128181506-0c4b8e54850f // indirect
|
||||
istio.io/gogo-genproto v0.0.0-20190930162913-45029607206a // indirect
|
||||
k8s.io/apiserver v0.22.1 // indirect
|
||||
k8s.io/component-base v0.22.1 // indirect
|
||||
sigs.k8s.io/apiserver-network-proxy v0.0.24 // indirect
|
||||
sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.0.24 // indirect
|
||||
sigs.k8s.io/apiserver-runtime v1.0.3-0.20210913073608-0663f60bfee2 // indirect
|
||||
|
||||
7
go.sum
7
go.sum
@@ -353,6 +353,7 @@ github.com/coreos/etcd v3.3.10+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc
|
||||
github.com/coreos/etcd v3.3.13+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE=
|
||||
github.com/coreos/etcd v3.3.15+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE=
|
||||
github.com/coreos/go-etcd v2.0.0+incompatible/go.mod h1:Jez6KQU2B/sWsbdaef3ED8NzMklzPG4d5KIOhIy30Tk=
|
||||
github.com/coreos/go-oidc v2.1.0+incompatible h1:sdJrfw8akMnCuUlaZU3tE/uYXFgfqom8DBE9so9EBsM=
|
||||
github.com/coreos/go-oidc v2.1.0+incompatible/go.mod h1:CgnwVTmzoESiwO9qyAFEMiHoZ1nMCKZlZ9V6mm3/LKc=
|
||||
github.com/coreos/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk=
|
||||
github.com/coreos/go-semver v0.3.0 h1:wkHLiw0WNATZnSG7epLsujiMCgPAc9xhjJ4tgnAxmfM=
|
||||
@@ -1250,8 +1251,8 @@ github.com/nxadm/tail v1.4.8 h1:nPr65rt6Y5JFSKQO7qToXr7pePgD6Gwiw05lkbyAQTE=
|
||||
github.com/nxadm/tail v1.4.8/go.mod h1:+ncqLTQzXmGhMZNUePPaPqPvBxHAIsmXswZKocGu+AU=
|
||||
github.com/oam-dev/cluster-gateway v1.1.6 h1:CY6m2Qcs6XJ/l/NY48CdHD7GAel9zZ/erUOz2zYzxkI=
|
||||
github.com/oam-dev/cluster-gateway v1.1.6/go.mod h1:SF7S4Ss+VUs2OVxmvSrrFGcaNFoXy6JWxHAnUxC1QcY=
|
||||
github.com/oam-dev/cluster-register v1.0.3 h1:n6OTkNYxXYtkl2KjFv8444hF0B3SV9uyP8FL8G1QPUk=
|
||||
github.com/oam-dev/cluster-register v1.0.3/go.mod h1:AoqoF9HgmluxtRBYyvKDbLNdlPY6Xvm+/6uo6LjLaBw=
|
||||
github.com/oam-dev/cluster-register v1.0.4-0.20220325092210-cee4a3d3fb7d h1:ZZsBkksYDzwJEjqx9/XBD+VwlhHz8flkZvMJYzO4ASA=
|
||||
github.com/oam-dev/cluster-register v1.0.4-0.20220325092210-cee4a3d3fb7d/go.mod h1:nKEUMfuEB8pHKsaSah9IA+UQzezrPYebBdRozyNtlZc=
|
||||
github.com/oam-dev/stern v1.13.2 h1:jlGgtJbKmIVhzkH44ft5plkgs8XEfvxbFrQdX60CQR4=
|
||||
github.com/oam-dev/stern v1.13.2/go.mod h1:0pLjZt0amXE/ErF16Rdrgd98H2owN8Hmn3/7CX5+AeA=
|
||||
github.com/oam-dev/terraform-config-inspect v0.0.0-20210418082552-fc72d929aa28 h1:tD8HiFKnt0jnwdTWjeqUnfnUYLD/+Nsmj8ZGIxqDWiU=
|
||||
@@ -1359,6 +1360,7 @@ github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZb
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/polyfloyd/go-errorlint v0.0.0-20210510181950-ab96adb96fea/go.mod h1:wi9BfjxjF/bwiZ701TzmfKu6UKC357IOAtNr0Td0Lvw=
|
||||
github.com/posener/complete v1.1.1/go.mod h1:em0nMJCgc9GFtwrmVmEMR/ZL6WyhyjMBndrE9hABlRI=
|
||||
github.com/pquerna/cachecontrol v0.0.0-20171018203845-0dec1b30a021 h1:0XM1XL/OFFJjXsYXlG30spTkV/E9+gmd5GD1w2HE8xM=
|
||||
github.com/pquerna/cachecontrol v0.0.0-20171018203845-0dec1b30a021/go.mod h1:prYjPmNq4d1NPVmpShWobRqXY3q7Vp+80DqgxxUrUIA=
|
||||
github.com/prometheus-community/prom-label-proxy v0.1.1-0.20200616110844-0fbfa11fa8f3/go.mod h1:XdjyZg7LCbCC5FADHtpgNp6kQ0W9beXVGfmcvndMj5Y=
|
||||
github.com/prometheus/alertmanager v0.18.0/go.mod h1:WcxHBl40VSPuOaqWae6l6HpnEOVRIycEJ7i9iYkadEE=
|
||||
@@ -2385,6 +2387,7 @@ gopkg.in/ini.v1 v1.62.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
|
||||
gopkg.in/natefinch/lumberjack.v2 v2.0.0 h1:1Lc07Kr7qY4U2YPouBjpCLxpiyxIVoxqXgkXLknAOE8=
|
||||
gopkg.in/natefinch/lumberjack.v2 v2.0.0/go.mod h1:l0ndWWf7gzL7RNwBG7wST/UCcT4T24xpD6X8LsfU/+k=
|
||||
gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo=
|
||||
gopkg.in/square/go-jose.v2 v2.2.2 h1:orlkJ3myw8CN1nVQHBFfloD+L3egixIa4FvUP6RosSA=
|
||||
gopkg.in/square/go-jose.v2 v2.2.2/go.mod h1:M9dMgbHiYLoDGQrXy7OpJDJWiKiU//h+vD76mk0e1AI=
|
||||
gopkg.in/src-d/go-billy.v4 v4.3.2 h1:0SQA1pRztfTFx2miS8sA97XvooFeNOmvUenF4o0EcVg=
|
||||
gopkg.in/src-d/go-billy.v4 v4.3.2/go.mod h1:nDjArDMp+XMs1aFAESLRjfGSgfvoYN0hDfzEk0GjC98=
|
||||
|
||||
@@ -42,7 +42,8 @@ func main() {
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
if err := ref.GenerateReferenceDocs(ctx, c, path, types.DefaultKubeVelaNS); err != nil {
|
||||
ref.Remote = &plugins.Remote{Namespace: types.DefaultKubeVelaNS}
|
||||
if err := ref.GenerateReferenceDocs(ctx, c, path); err != nil {
|
||||
fmt.Println(err)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
@@ -2025,6 +2025,12 @@ spec:
|
||||
- jsonPath: .metadata.creationTimestamp
|
||||
name: AGE
|
||||
type: date
|
||||
- jsonPath: .metadata.annotations['app\.oam\.dev\/publishVersion']
|
||||
name: PUBLISH_VERSION
|
||||
type: string
|
||||
- jsonPath: .status.succeeded
|
||||
name: SUCCEEDED
|
||||
type: string
|
||||
name: v1beta1
|
||||
schema:
|
||||
openAPIV3Schema:
|
||||
@@ -2747,13 +2753,6 @@ spec:
|
||||
type: object
|
||||
type: object
|
||||
type: object
|
||||
applicationConfiguration:
|
||||
description: ApplicationConfiguration records the rendered applicationConfiguration
|
||||
from Application, it will contains the whole K8s CR of trait and
|
||||
the reference component in it.
|
||||
type: object
|
||||
|
||||
|
||||
componentDefinitions:
|
||||
additionalProperties:
|
||||
description: ComponentDefinition is the Schema for the componentdefinitions
|
||||
@@ -3087,20 +3086,51 @@ spec:
|
||||
description: ComponentDefinitions records the snapshot of the componentDefinitions
|
||||
related with the created/modified Application
|
||||
type: object
|
||||
components:
|
||||
description: Components records the rendered components from Application,
|
||||
it will contains the whole K8s CR of workload in it.
|
||||
items:
|
||||
description: RawComponent record raw component
|
||||
policies:
|
||||
additionalProperties:
|
||||
description: Policy is the Schema for the policy API
|
||||
properties:
|
||||
raw:
|
||||
apiVersion:
|
||||
description: 'APIVersion defines the versioned schema of this
|
||||
representation of an object. Servers should convert recognized
|
||||
schemas to the latest internal value, and may reject unrecognized
|
||||
values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources'
|
||||
type: string
|
||||
kind:
|
||||
description: 'Kind is a string value representing the REST resource
|
||||
this object represents. Servers may infer this from the endpoint
|
||||
the client submits requests to. Cannot be updated. In CamelCase.
|
||||
More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds'
|
||||
type: string
|
||||
metadata:
|
||||
properties:
|
||||
annotations:
|
||||
additionalProperties:
|
||||
type: string
|
||||
type: object
|
||||
finalizers:
|
||||
items:
|
||||
type: string
|
||||
type: array
|
||||
labels:
|
||||
additionalProperties:
|
||||
type: string
|
||||
type: object
|
||||
name:
|
||||
type: string
|
||||
namespace:
|
||||
type: string
|
||||
type: object
|
||||
properties:
|
||||
type: object
|
||||
|
||||
|
||||
type:
|
||||
type: string
|
||||
required:
|
||||
- raw
|
||||
- type
|
||||
type: object
|
||||
type: array
|
||||
description: Policies records the external policies
|
||||
type: object
|
||||
policyDefinitions:
|
||||
additionalProperties:
|
||||
description: PolicyDefinition is the Schema for the policydefinitions
|
||||
@@ -3377,15 +3407,16 @@ spec:
|
||||
description: PolicyDefinitions records the snapshot of the PolicyDefinitions
|
||||
related with the created/modified Application
|
||||
type: object
|
||||
resourcesConfigMap:
|
||||
description: ResourcesConfigMap references the ConfigMap that's generated
|
||||
to contain all final rendered resources.
|
||||
properties:
|
||||
name:
|
||||
description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names
|
||||
TODO: Add other useful fields. apiVersion, kind, uid?'
|
||||
type: string
|
||||
type: object
|
||||
referredObjects:
|
||||
description: ReferredObjects records the referred objects used in
|
||||
the ref-object typed components
|
||||
items:
|
||||
description: ReferredObject the referred Kubernetes object
|
||||
type: object
|
||||
|
||||
|
||||
type: array
|
||||
|
||||
scopeDefinitions:
|
||||
additionalProperties:
|
||||
description: A ScopeDefinition registers a kind of Kubernetes custom
|
||||
@@ -3819,6 +3850,89 @@ spec:
|
||||
description: TraitDefinitions records the snapshot of the traitDefinitions
|
||||
related with the created/modified Application
|
||||
type: object
|
||||
workflow:
|
||||
description: Workflow records the external workflow
|
||||
properties:
|
||||
apiVersion:
|
||||
description: 'APIVersion defines the versioned schema of this
|
||||
representation of an object. Servers should convert recognized
|
||||
schemas to the latest internal value, and may reject unrecognized
|
||||
values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources'
|
||||
type: string
|
||||
kind:
|
||||
description: 'Kind is a string value representing the REST resource
|
||||
this object represents. Servers may infer this from the endpoint
|
||||
the client submits requests to. Cannot be updated. In CamelCase.
|
||||
More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds'
|
||||
type: string
|
||||
metadata:
|
||||
properties:
|
||||
annotations:
|
||||
additionalProperties:
|
||||
type: string
|
||||
type: object
|
||||
finalizers:
|
||||
items:
|
||||
type: string
|
||||
type: array
|
||||
labels:
|
||||
additionalProperties:
|
||||
type: string
|
||||
type: object
|
||||
name:
|
||||
type: string
|
||||
namespace:
|
||||
type: string
|
||||
type: object
|
||||
steps:
|
||||
items:
|
||||
description: WorkflowStep defines how to execute a workflow
|
||||
step.
|
||||
properties:
|
||||
dependsOn:
|
||||
items:
|
||||
type: string
|
||||
type: array
|
||||
inputs:
|
||||
description: StepInputs defines variable input of WorkflowStep
|
||||
items:
|
||||
properties:
|
||||
from:
|
||||
type: string
|
||||
parameterKey:
|
||||
type: string
|
||||
required:
|
||||
- from
|
||||
- parameterKey
|
||||
type: object
|
||||
type: array
|
||||
name:
|
||||
description: Name is the unique name of the workflow step.
|
||||
type: string
|
||||
outputs:
|
||||
description: StepOutputs defines output variable of WorkflowStep
|
||||
items:
|
||||
properties:
|
||||
name:
|
||||
type: string
|
||||
valueFrom:
|
||||
type: string
|
||||
required:
|
||||
- name
|
||||
- valueFrom
|
||||
type: object
|
||||
type: array
|
||||
properties:
|
||||
type: object
|
||||
|
||||
type:
|
||||
type: string
|
||||
required:
|
||||
- name
|
||||
- type
|
||||
type: object
|
||||
type: array
|
||||
type: object
|
||||
workflowStepDefinitions:
|
||||
additionalProperties:
|
||||
description: WorkflowStepDefinition is the Schema for the workflowstepdefinitions
|
||||
@@ -4408,10 +4522,182 @@ spec:
|
||||
required:
|
||||
- application
|
||||
type: object
|
||||
status:
|
||||
description: ApplicationRevisionStatus is the status of ApplicationRevision
|
||||
properties:
|
||||
succeeded:
|
||||
description: Succeeded records if the workflow finished running with
|
||||
success
|
||||
type: boolean
|
||||
workflow:
|
||||
description: Workflow the running status of the workflow
|
||||
properties:
|
||||
appRevision:
|
||||
type: string
|
||||
contextBackend:
|
||||
description: 'ObjectReference contains enough information to let
|
||||
you inspect or modify the referred object. --- New uses of this
|
||||
type are discouraged because of difficulty describing its usage
|
||||
when embedded in APIs. 1. Ignored fields. It includes many
|
||||
fields which are not generally honored. For instance, ResourceVersion
|
||||
and FieldPath are both very rarely valid in actual usage. 2.
|
||||
Invalid usage help. It is impossible to add specific help for
|
||||
individual usage. In most embedded usages, there are particular restrictions
|
||||
like, "must refer only to types A and B" or "UID not honored"
|
||||
or "name must be restricted". Those cannot be well described
|
||||
when embedded. 3. Inconsistent validation. Because the usages
|
||||
are different, the validation rules are different by usage,
|
||||
which makes it hard for users to predict what will happen. 4.
|
||||
The fields are both imprecise and overly precise. Kind is not
|
||||
a precise mapping to a URL. This can produce ambiguity during
|
||||
interpretation and require a REST mapping. In most cases, the
|
||||
dependency is on the group,resource tuple and the version
|
||||
of the actual struct is irrelevant. 5. We cannot easily change
|
||||
it. Because this type is embedded in many locations, updates
|
||||
to this type will affect numerous schemas. Don''t make
|
||||
new APIs embed an underspecified API type they do not control.
|
||||
Instead of using this type, create a locally provided and used
|
||||
type that is well-focused on your reference. For example, ServiceReferences
|
||||
for admission registration: https://github.com/kubernetes/api/blob/release-1.17/admissionregistration/v1/types.go#L533
|
||||
.'
|
||||
properties:
|
||||
apiVersion:
|
||||
description: API version of the referent.
|
||||
type: string
|
||||
fieldPath:
|
||||
description: 'If referring to a piece of an object instead
|
||||
of an entire object, this string should contain a valid
|
||||
JSON/Go field access statement, such as desiredState.manifest.containers[2].
|
||||
For example, if the object reference is to a container within
|
||||
a pod, this would take on a value like: "spec.containers{name}"
|
||||
(where "name" refers to the name of the container that triggered
|
||||
the event) or if no container name is specified "spec.containers[2]"
|
||||
(container with index 2 in this pod). This syntax is chosen
|
||||
only to have some well-defined way of referencing a part
|
||||
of an object. TODO: this design is not final and this field
|
||||
is subject to change in the future.'
|
||||
type: string
|
||||
kind:
|
||||
description: 'Kind of the referent. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds'
|
||||
type: string
|
||||
name:
|
||||
description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names'
|
||||
type: string
|
||||
namespace:
|
||||
description: 'Namespace of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/namespaces/'
|
||||
type: string
|
||||
resourceVersion:
|
||||
description: 'Specific resourceVersion to which this reference
|
||||
is made, if any. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#concurrency-control-and-consistency'
|
||||
type: string
|
||||
uid:
|
||||
description: 'UID of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#uids'
|
||||
type: string
|
||||
type: object
|
||||
finished:
|
||||
type: boolean
|
||||
message:
|
||||
type: string
|
||||
mode:
|
||||
description: WorkflowMode describes the mode of workflow
|
||||
type: string
|
||||
startTime:
|
||||
format: date-time
|
||||
type: string
|
||||
steps:
|
||||
items:
|
||||
description: WorkflowStepStatus record the status of a workflow
|
||||
step
|
||||
properties:
|
||||
firstExecuteTime:
|
||||
description: FirstExecuteTime is the first time this step
|
||||
execution.
|
||||
format: date-time
|
||||
type: string
|
||||
id:
|
||||
type: string
|
||||
lastExecuteTime:
|
||||
description: LastExecuteTime is the last time this step
|
||||
execution.
|
||||
format: date-time
|
||||
type: string
|
||||
message:
|
||||
description: A human readable message indicating details
|
||||
about why the workflowStep is in this state.
|
||||
type: string
|
||||
name:
|
||||
type: string
|
||||
phase:
|
||||
description: WorkflowStepPhase describes the phase of a
|
||||
workflow step.
|
||||
type: string
|
||||
reason:
|
||||
description: A brief CamelCase message indicating details
|
||||
about why the workflowStep is in this state.
|
||||
type: string
|
||||
subSteps:
|
||||
description: SubStepsStatus record the status of workflow
|
||||
steps.
|
||||
properties:
|
||||
mode:
|
||||
description: WorkflowMode describes the mode of workflow
|
||||
type: string
|
||||
stepIndex:
|
||||
type: integer
|
||||
steps:
|
||||
items:
|
||||
description: WorkflowSubStepStatus record the status
|
||||
of a workflow step
|
||||
properties:
|
||||
id:
|
||||
type: string
|
||||
message:
|
||||
description: A human readable message indicating
|
||||
details about why the workflowStep is in this
|
||||
state.
|
||||
type: string
|
||||
name:
|
||||
type: string
|
||||
phase:
|
||||
description: WorkflowStepPhase describes the phase
|
||||
of a workflow step.
|
||||
type: string
|
||||
reason:
|
||||
description: A brief CamelCase message indicating
|
||||
details about why the workflowStep is in this
|
||||
state.
|
||||
type: string
|
||||
type:
|
||||
type: string
|
||||
required:
|
||||
- id
|
||||
type: object
|
||||
type: array
|
||||
type: object
|
||||
type:
|
||||
type: string
|
||||
required:
|
||||
- id
|
||||
type: object
|
||||
type: array
|
||||
suspend:
|
||||
type: boolean
|
||||
terminated:
|
||||
type: boolean
|
||||
required:
|
||||
- finished
|
||||
- mode
|
||||
- suspend
|
||||
- terminated
|
||||
type: object
|
||||
required:
|
||||
- succeeded
|
||||
type: object
|
||||
type: object
|
||||
served: true
|
||||
storage: true
|
||||
subresources: {}
|
||||
subresources:
|
||||
status: {}
|
||||
status:
|
||||
acceptedNames:
|
||||
kind: ""
|
||||
|
||||
@@ -53,6 +53,7 @@ import (
|
||||
"sigs.k8s.io/yaml"
|
||||
|
||||
common2 "github.com/oam-dev/kubevela/apis/core.oam.dev/common"
|
||||
"github.com/oam-dev/kubevela/apis/core.oam.dev/v1alpha1"
|
||||
"github.com/oam-dev/kubevela/apis/core.oam.dev/v1beta1"
|
||||
"github.com/oam-dev/kubevela/apis/types"
|
||||
utils2 "github.com/oam-dev/kubevela/pkg/controller/utils"
|
||||
@@ -113,6 +114,9 @@ var (
|
||||
|
||||
// CLIMetaOptions get Addon metadata for CLI display
|
||||
CLIMetaOptions = ListOptions{}
|
||||
|
||||
// UnInstallOptions used for addon uninstalling
|
||||
UnInstallOptions = ListOptions{GetDefinition: true}
|
||||
)
|
||||
|
||||
const (
|
||||
@@ -282,6 +286,7 @@ func GetUIDataFromReader(r AsyncReader, meta *SourceMeta, opt ListOptions) (*UID
|
||||
return nil, fmt.Errorf("fail to generate openAPIschema for addon %s : %w", meta.Name, err)
|
||||
}
|
||||
}
|
||||
addon.AvailableVersions = []string{addon.Version}
|
||||
return addon, nil
|
||||
}
|
||||
|
||||
@@ -520,32 +525,22 @@ func genAddonAPISchema(addonRes *UIData) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// RenderApp render a K8s application
|
||||
func RenderApp(ctx context.Context, addon *InstallPackage, config *rest.Config, k8sClient client.Client, args map[string]interface{}) (*v1beta1.Application, error) {
|
||||
if args == nil {
|
||||
args = map[string]interface{}{}
|
||||
func getClusters(args map[string]interface{}) []string {
|
||||
ccr, ok := args[types.ClustersArg]
|
||||
if !ok {
|
||||
return nil
|
||||
}
|
||||
app := addon.AppTemplate
|
||||
if app == nil {
|
||||
app = &v1beta1.Application{
|
||||
TypeMeta: metav1.TypeMeta{APIVersion: "core.oam.dev/v1beta1", Kind: "Application"},
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: Convert2AppName(addon.Name),
|
||||
Namespace: types.DefaultKubeVelaNS,
|
||||
Labels: map[string]string{
|
||||
oam.LabelAddonName: addon.Name,
|
||||
},
|
||||
},
|
||||
Spec: v1beta1.ApplicationSpec{
|
||||
Components: []common2.ApplicationComponent{},
|
||||
},
|
||||
}
|
||||
cc, ok := ccr.([]string)
|
||||
if !ok {
|
||||
return nil
|
||||
}
|
||||
app.Labels = util.MergeMapOverrideWithDst(app.Labels, map[string]string{oam.LabelAddonName: addon.Name})
|
||||
|
||||
// force override the namespace defined vela with DefaultVelaNS,this value can be modified by Env
|
||||
app.SetNamespace(types.DefaultKubeVelaNS)
|
||||
return cc
|
||||
}
|
||||
|
||||
// renderNeededNamespaceAsComps will convert namespace as app components to create namespace for managed clusters
|
||||
func renderNeededNamespaceAsComps(addon *InstallPackage) []common2.ApplicationComponent {
|
||||
var nscomps []common2.ApplicationComponent
|
||||
// create namespace for managed clusters
|
||||
for _, namespace := range addon.NeedNamespace {
|
||||
// vela-system must exist before rendering vela addon
|
||||
if namespace == types.DefaultKubeVelaNS {
|
||||
@@ -556,42 +551,154 @@ func RenderApp(ctx context.Context, addon *InstallPackage, config *rest.Config,
|
||||
Name: fmt.Sprintf("%s-namespace", namespace),
|
||||
Properties: util.Object2RawExtension(renderNamespace(namespace)),
|
||||
}
|
||||
app.Spec.Components = append(app.Spec.Components, comp)
|
||||
nscomps = append(nscomps, comp)
|
||||
}
|
||||
return nscomps
|
||||
}
|
||||
|
||||
func renderResources(addon *InstallPackage, args map[string]interface{}) ([]common2.ApplicationComponent, error) {
|
||||
var resources []common2.ApplicationComponent
|
||||
if len(addon.YAMLTemplates) != 0 {
|
||||
comp, err := renderK8sObjectsComponent(addon.YAMLTemplates, addon.Name)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
app.Spec.Components = append(app.Spec.Components, *comp)
|
||||
resources = append(resources, *comp)
|
||||
}
|
||||
|
||||
for _, tmpl := range addon.CUETemplates {
|
||||
comp, err := renderCUETemplate(tmpl, addon.Parameters, args)
|
||||
if err != nil {
|
||||
return nil, ErrRenderCueTmpl
|
||||
return nil, NewAddonError(fmt.Sprintf("fail to render cue template %s", err.Error()))
|
||||
}
|
||||
if addon.Name == ObservabilityAddon && strings.HasSuffix(comp.Name, ".cue") {
|
||||
comp.Name = strings.Split(comp.Name, ".cue")[0]
|
||||
}
|
||||
app.Spec.Components = append(app.Spec.Components, *comp)
|
||||
resources = append(resources, *comp)
|
||||
}
|
||||
return resources, nil
|
||||
}
|
||||
|
||||
func formatAppFramework(addon *InstallPackage) *v1beta1.Application {
|
||||
app := addon.AppTemplate
|
||||
if app == nil {
|
||||
app = &v1beta1.Application{
|
||||
TypeMeta: metav1.TypeMeta{APIVersion: "core.oam.dev/v1beta1", Kind: "Application"},
|
||||
Spec: v1beta1.ApplicationSpec{
|
||||
Components: []common2.ApplicationComponent{},
|
||||
},
|
||||
}
|
||||
}
|
||||
app.Name = Convert2AppName(addon.Name)
|
||||
// force override the namespace defined vela with DefaultVelaNS,this value can be modified by Env
|
||||
app.SetNamespace(types.DefaultKubeVelaNS)
|
||||
if app.Labels == nil {
|
||||
app.Labels = make(map[string]string)
|
||||
}
|
||||
app.Labels[oam.LabelAddonName] = addon.Name
|
||||
app.Labels[oam.LabelAddonVersion] = addon.Version
|
||||
return app
|
||||
}
|
||||
|
||||
func checkDeployClusters(ctx context.Context, k8sClient client.Client, args map[string]interface{}) ([]string, error) {
|
||||
deployClusters := getClusters(args)
|
||||
if len(deployClusters) == 0 || k8sClient == nil {
|
||||
return nil, nil
|
||||
}
|
||||
vcs, err := multicluster.ListVirtualClusters(ctx, k8sClient)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var res []string
|
||||
for _, c := range deployClusters {
|
||||
c = strings.TrimSpace(c)
|
||||
if c == "" {
|
||||
continue
|
||||
}
|
||||
var found bool
|
||||
for _, vc := range vcs {
|
||||
if c == vc.Name {
|
||||
found = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if !found {
|
||||
return nil, errors.Errorf("cluster %s not exist", c)
|
||||
}
|
||||
res = append(res, c)
|
||||
}
|
||||
return res, nil
|
||||
}
|
||||
|
||||
// RenderApp render a K8s application
|
||||
func RenderApp(ctx context.Context, addon *InstallPackage, k8sClient client.Client, args map[string]interface{}) (*v1beta1.Application, error) {
|
||||
|
||||
if args == nil {
|
||||
args = map[string]interface{}{}
|
||||
}
|
||||
|
||||
app := formatAppFramework(addon)
|
||||
app.Spec.Components = append(app.Spec.Components, renderNeededNamespaceAsComps(addon)...)
|
||||
|
||||
resources, err := renderResources(addon, args)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
app.Spec.Components = append(app.Spec.Components, resources...)
|
||||
|
||||
deployClusters, err := checkDeployClusters(ctx, k8sClient, args)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
switch {
|
||||
case isDeployToRuntimeOnly(addon):
|
||||
if app.Spec.Workflow == nil {
|
||||
app.Spec.Workflow = &v1beta1.Workflow{Steps: make([]v1beta1.WorkflowStep, 0)}
|
||||
}
|
||||
app.Spec.Workflow.Steps = append(app.Spec.Workflow.Steps,
|
||||
v1beta1.WorkflowStep{
|
||||
Name: "deploy-control-plane",
|
||||
Type: "apply-application",
|
||||
},
|
||||
v1beta1.WorkflowStep{
|
||||
Name: "deploy-runtime",
|
||||
Type: "deploy2runtime",
|
||||
if len(deployClusters) == 0 {
|
||||
// deploy to all clusters
|
||||
app.Spec.Workflow = &v1beta1.Workflow{Steps: []v1beta1.WorkflowStep{
|
||||
{
|
||||
Name: "deploy-control-plane",
|
||||
Type: "apply-application",
|
||||
},
|
||||
{
|
||||
Name: "deploy-runtime",
|
||||
Type: "deploy2runtime",
|
||||
},
|
||||
}}
|
||||
// TODO(wonderflow): this can be merged into len(deployClusters) > 0 case
|
||||
/*
|
||||
allclusters, err := multicluster.ListVirtualClusters(ctx, k8sClient)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
for _, c := range allclusters {
|
||||
deployClusters = append(deployClusters, c.Name)
|
||||
}
|
||||
*/
|
||||
} else {
|
||||
var found bool
|
||||
for _, c := range deployClusters {
|
||||
if c == multicluster.ClusterLocalName {
|
||||
found = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if !found {
|
||||
deployClusters = append(deployClusters, multicluster.ClusterLocalName)
|
||||
}
|
||||
// deploy to specified clusters
|
||||
if app.Spec.Policies == nil {
|
||||
app.Spec.Policies = []v1beta1.AppPolicy{}
|
||||
}
|
||||
body, _ := json.Marshal(map[string][]string{types.ClustersArg: deployClusters})
|
||||
app.Spec.Policies = append(app.Spec.Policies, v1beta1.AppPolicy{
|
||||
Name: "specified-addon-clusters",
|
||||
Type: v1alpha1.TopologyPolicyType,
|
||||
Properties: &runtime.RawExtension{Raw: body},
|
||||
})
|
||||
// addon should not contain workflow, this also update legacy addon with deploy2runtime steps
|
||||
app.Spec.Workflow = nil
|
||||
}
|
||||
case addon.Name == ObservabilityAddon:
|
||||
clusters, err := allocateDomainForAddon(ctx, k8sClient)
|
||||
if err != nil {
|
||||
@@ -627,36 +734,7 @@ func RenderApp(ctx context.Context, addon *InstallPackage, config *rest.Config,
|
||||
app.Spec.Workflow.Steps = append(app.Spec.Workflow.Steps, workflowSteps...)
|
||||
|
||||
default:
|
||||
for _, cueDef := range addon.CUEDefinitions {
|
||||
def := definition.Definition{Unstructured: unstructured.Unstructured{}}
|
||||
err := def.FromCUEString(cueDef.Data, config)
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err, "fail to render definition: %s in cue's format", cueDef.Name)
|
||||
}
|
||||
if def.Unstructured.GetNamespace() == "" {
|
||||
def.Unstructured.SetNamespace(types.DefaultKubeVelaNS)
|
||||
}
|
||||
app.Spec.Components = append(app.Spec.Components, common2.ApplicationComponent{
|
||||
Name: cueDef.Name,
|
||||
Type: "raw",
|
||||
Properties: util.Object2RawExtension(&def.Unstructured),
|
||||
})
|
||||
}
|
||||
for _, teml := range addon.DefSchemas {
|
||||
u, err := renderSchemaConfigmap(teml)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
app.Spec.Components = append(app.Spec.Components, common2.ApplicationComponent{
|
||||
Name: teml.Name,
|
||||
Type: "raw",
|
||||
Properties: util.Object2RawExtension(u),
|
||||
})
|
||||
}
|
||||
// set to nil so workflow mode will be set to "DAG" automatically
|
||||
if app.Spec.Workflow != nil && len(app.Spec.Workflow.Steps) == 0 {
|
||||
app.Spec.Workflow = nil
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return app, nil
|
||||
@@ -695,14 +773,13 @@ func RenderDefinitions(addon *InstallPackage, config *rest.Config) ([]*unstructu
|
||||
func RenderDefinitionSchema(addon *InstallPackage) ([]*unstructured.Unstructured, error) {
|
||||
schemaConfigmaps := make([]*unstructured.Unstructured, 0)
|
||||
|
||||
if isDeployToRuntimeOnly(addon) {
|
||||
for _, teml := range addon.DefSchemas {
|
||||
u, err := renderSchemaConfigmap(teml)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
schemaConfigmaps = append(schemaConfigmaps, u)
|
||||
// No matter runtime mode or control mode , definition schemas only needs to control plane k8s.
|
||||
for _, teml := range addon.DefSchemas {
|
||||
u, err := renderSchemaConfigmap(teml)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
schemaConfigmaps = append(schemaConfigmaps, u)
|
||||
}
|
||||
return schemaConfigmaps, nil
|
||||
}
|
||||
@@ -996,26 +1073,37 @@ func (h *Installer) enableAddon(addon *InstallPackage) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (h *Installer) loadInstallPackage(name string) (*InstallPackage, error) {
|
||||
metas, err := h.getAddonMeta()
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "fail to get addon meta")
|
||||
func (h *Installer) loadInstallPackage(name, version string) (*InstallPackage, error) {
|
||||
var installPackage *InstallPackage
|
||||
var err error
|
||||
if !IsVersionRegistry(*h.r) {
|
||||
metas, err := h.getAddonMeta()
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "fail to get addon meta")
|
||||
}
|
||||
|
||||
meta, ok := metas[name]
|
||||
if !ok {
|
||||
return nil, ErrNotExist
|
||||
}
|
||||
var uiData *UIData
|
||||
uiData, err = h.cache.GetUIData(*h.r, name, version)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// enable this addon if it's invisible
|
||||
installPackage, err = h.r.GetInstallPackage(&meta, uiData)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "fail to find dependent addon in source repository")
|
||||
}
|
||||
} else {
|
||||
versionedRegistry := BuildVersionedRegistry(h.r.Name, h.r.Helm.URL)
|
||||
installPackage, err = versionedRegistry.GetAddonInstallPackage(context.Background(), name, version)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
meta, ok := metas[name]
|
||||
if !ok {
|
||||
return nil, ErrNotExist
|
||||
}
|
||||
var uiData *UIData
|
||||
uiData, err = h.cache.GetUIData(*h.r, name)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// enable this addon if it's invisible
|
||||
installPackage, err := h.r.GetInstallPackage(&meta, uiData)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "fail to find dependent addon in source repository")
|
||||
}
|
||||
return installPackage, nil
|
||||
}
|
||||
|
||||
@@ -1043,7 +1131,8 @@ func (h *Installer) installDependency(addon *InstallPackage) error {
|
||||
if !apierrors.IsNotFound(err) {
|
||||
return err
|
||||
}
|
||||
depAddon, err := h.loadInstallPackage(dep.Name)
|
||||
// always install addon's latest version
|
||||
depAddon, err := h.loadInstallPackage(dep.Name, "")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -1075,9 +1164,27 @@ func (h *Installer) checkDependency(addon *InstallPackage) ([]string, error) {
|
||||
}
|
||||
return needEnable, nil
|
||||
}
|
||||
func (h *Installer) createOrUpdate(app *v1beta1.Application) error {
|
||||
var getapp v1beta1.Application
|
||||
err := h.cli.Get(h.ctx, client.ObjectKey{Name: app.Name, Namespace: app.Namespace}, &getapp)
|
||||
if apierrors.IsNotFound(err) {
|
||||
return h.cli.Create(h.ctx, app)
|
||||
}
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
getapp.Spec = app.Spec
|
||||
err = h.cli.Update(h.ctx, &getapp)
|
||||
if err != nil {
|
||||
klog.Errorf("fail to create application: %v", err)
|
||||
return errors.Wrap(err, "fail to create application")
|
||||
}
|
||||
getapp.DeepCopyInto(app)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (h *Installer) dispatchAddonResource(addon *InstallPackage) error {
|
||||
app, err := RenderApp(h.ctx, addon, h.config, h.cli, h.args)
|
||||
app, err := RenderApp(h.ctx, addon, h.cli, h.args)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "render addon application fail")
|
||||
}
|
||||
@@ -1100,10 +1207,12 @@ func (h *Installer) dispatchAddonResource(addon *InstallPackage) error {
|
||||
return errors.Wrap(err, "render addon definitions' schema fail")
|
||||
}
|
||||
|
||||
err = h.apply.Apply(h.ctx, app, apply.DisableUpdateAnnotation())
|
||||
if err != nil {
|
||||
klog.Errorf("fail to create application: %v", err)
|
||||
return errors.Wrap(err, "fail to create application")
|
||||
if err := passDefInAppAnnotation(defs, app); err != nil {
|
||||
return errors.Wrapf(err, "cannot pass definition to addon app's annotation")
|
||||
}
|
||||
|
||||
if err = h.createOrUpdate(app); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for _, def := range defs {
|
||||
|
||||
@@ -21,7 +21,9 @@ import (
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"github.com/oam-dev/cluster-gateway/pkg/apis/cluster/v1alpha1"
|
||||
appsv1 "k8s.io/api/apps/v1"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
|
||||
v1 "k8s.io/api/core/v1"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
@@ -33,6 +35,7 @@ import (
|
||||
"sigs.k8s.io/yaml"
|
||||
|
||||
"github.com/oam-dev/kubevela/apis/core.oam.dev/common"
|
||||
v1alpha12 "github.com/oam-dev/kubevela/apis/core.oam.dev/v1alpha1"
|
||||
"github.com/oam-dev/kubevela/apis/core.oam.dev/v1beta1"
|
||||
"github.com/oam-dev/kubevela/apis/types"
|
||||
"github.com/oam-dev/kubevela/pkg/oam/util"
|
||||
@@ -270,6 +273,56 @@ var _ = Describe("Test addon util func", func() {
|
||||
|
||||
})
|
||||
|
||||
var _ = Describe("Test render addon with specified clusters", func() {
|
||||
BeforeEach(func() {
|
||||
Expect(k8sClient.Create(ctx, &v1.Secret{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "add-c1",
|
||||
Namespace: "vela-system",
|
||||
Labels: map[string]string{
|
||||
v1alpha1.LabelKeyClusterCredentialType: string(v1alpha1.CredentialTypeX509Certificate),
|
||||
v1alpha1.LabelKeyClusterEndpointType: v1alpha1.ClusterEndpointTypeConst,
|
||||
"key": "value",
|
||||
},
|
||||
},
|
||||
})).Should(SatisfyAny(BeNil(), &util.AlreadyExistMatcher{}))
|
||||
Expect(k8sClient.Create(ctx, &v1.Secret{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "add-c2",
|
||||
Namespace: "vela-system",
|
||||
Labels: map[string]string{
|
||||
v1alpha1.LabelKeyClusterCredentialType: string(v1alpha1.CredentialTypeX509Certificate),
|
||||
v1alpha1.LabelKeyClusterEndpointType: v1alpha1.ClusterEndpointTypeConst,
|
||||
"key": "value",
|
||||
},
|
||||
},
|
||||
})).Should(SatisfyAny(BeNil(), &util.AlreadyExistMatcher{}))
|
||||
})
|
||||
It("test render not exits cluster", func() {
|
||||
i := &baseAddon
|
||||
i.Name = "test-cluster-addon"
|
||||
|
||||
args := map[string]interface{}{
|
||||
"clusters": []string{"add-c1", "ne"},
|
||||
}
|
||||
_, err := RenderApp(ctx, i, k8sClient, args)
|
||||
Expect(err.Error()).Should(BeEquivalentTo("cluster ne not exist"))
|
||||
})
|
||||
It("test render normal addon with specified clusters", func() {
|
||||
i := &baseAddon
|
||||
i.DeployTo = &DeployTo{RuntimeCluster: true}
|
||||
i.Name = "test-cluster-addon-normal"
|
||||
args := map[string]interface{}{
|
||||
"clusters": []string{"add-c1", "add-c2"},
|
||||
}
|
||||
ap, err := RenderApp(ctx, i, k8sClient, args)
|
||||
Expect(err).Should(BeNil())
|
||||
Expect(ap.Spec.Policies).Should(BeEquivalentTo([]v1beta1.AppPolicy{{Name: "specified-addon-clusters",
|
||||
Type: v1alpha12.TopologyPolicyType,
|
||||
Properties: &runtime.RawExtension{Raw: []byte(`{"clusters":["add-c1","add-c2","local"]}`)}}}))
|
||||
})
|
||||
})
|
||||
|
||||
const (
|
||||
appYaml = `apiVersion: core.oam.dev/v1beta1
|
||||
kind: Application
|
||||
|
||||
@@ -61,6 +61,10 @@ var paths = []string{
|
||||
|
||||
"test-error-addon/metadata.yaml",
|
||||
"test-error-addon/resources/parameter.cue",
|
||||
|
||||
"test-disable-addon/metadata.yaml",
|
||||
"test-disable-addon/definitions/compDef.yaml",
|
||||
"test-disable-addon/definitions/traitDef.cue",
|
||||
}
|
||||
|
||||
var ossHandler http.HandlerFunc = func(rw http.ResponseWriter, req *http.Request) {
|
||||
@@ -128,7 +132,7 @@ func testReaderFunc(t *testing.T, reader AsyncReader) {
|
||||
rName := "KubeVela"
|
||||
uiDataList, err := ListAddonUIDataFromReader(reader, registryMeta, rName, UIMetaOptions)
|
||||
assert.True(t, strings.Contains(err.Error(), "#parameter.example: preference mark not allowed at this position"))
|
||||
assert.Equal(t, len(uiDataList), 3)
|
||||
assert.Equal(t, 4, len(uiDataList))
|
||||
assert.Equal(t, uiDataList[0].RegistryName, rName)
|
||||
|
||||
// test get install package
|
||||
@@ -224,15 +228,16 @@ func TestRender(t *testing.T) {
|
||||
|
||||
func TestRenderApp(t *testing.T) {
|
||||
addon := baseAddon
|
||||
app, err := RenderApp(ctx, &addon, nil, nil, map[string]interface{}{})
|
||||
app, err := RenderApp(ctx, &addon, nil, map[string]interface{}{})
|
||||
assert.NoError(t, err, "render app fail")
|
||||
assert.Equal(t, len(app.Spec.Components), 2)
|
||||
// definition should not be rendered
|
||||
assert.Equal(t, len(app.Spec.Components), 1)
|
||||
}
|
||||
|
||||
func TestRenderAppWithNeedNamespace(t *testing.T) {
|
||||
addon := baseAddon
|
||||
addon.NeedNamespace = append(addon.NeedNamespace, types.DefaultKubeVelaNS)
|
||||
app, err := RenderApp(ctx, &addon, nil, nil, map[string]interface{}{})
|
||||
addon.NeedNamespace = append(addon.NeedNamespace, types.DefaultKubeVelaNS, "test-ns2")
|
||||
app, err := RenderApp(ctx, &addon, nil, map[string]interface{}{})
|
||||
assert.NoError(t, err, "render app fail")
|
||||
assert.Equal(t, len(app.Spec.Components), 2)
|
||||
for _, c := range app.Spec.Components {
|
||||
@@ -253,7 +258,7 @@ func TestRenderDeploy2RuntimeAddon(t *testing.T) {
|
||||
assert.Equal(t, def.GetAPIVersion(), "core.oam.dev/v1beta1")
|
||||
assert.Equal(t, def.GetKind(), "TraitDefinition")
|
||||
|
||||
app, err := RenderApp(ctx, &addonDeployToRuntime, nil, nil, map[string]interface{}{})
|
||||
app, err := RenderApp(ctx, &addonDeployToRuntime, nil, map[string]interface{}{})
|
||||
assert.NoError(t, err)
|
||||
steps := app.Spec.Workflow.Steps
|
||||
assert.True(t, len(steps) >= 2)
|
||||
@@ -274,7 +279,7 @@ func TestRenderDefinitions(t *testing.T) {
|
||||
assert.Equal(t, def.GetAPIVersion(), "core.oam.dev/v1beta1")
|
||||
assert.Equal(t, def.GetKind(), "TraitDefinition")
|
||||
|
||||
app, err := RenderApp(ctx, &addonDeployToRuntime, nil, nil, map[string]interface{}{})
|
||||
app, err := RenderApp(ctx, &addonDeployToRuntime, nil, map[string]interface{}{})
|
||||
assert.NoError(t, err)
|
||||
// addon which app work on no-runtime-cluster mode workflow is nil
|
||||
assert.Nil(t, app.Spec.Workflow)
|
||||
@@ -287,7 +292,7 @@ func TestRenderK8sObjects(t *testing.T) {
|
||||
RuntimeCluster: false,
|
||||
}
|
||||
|
||||
app, err := RenderApp(ctx, &addonMultiYaml, nil, nil, map[string]interface{}{})
|
||||
app, err := RenderApp(ctx, &addonMultiYaml, nil, map[string]interface{}{})
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, len(app.Spec.Components), 1)
|
||||
comp := app.Spec.Components[0]
|
||||
@@ -547,12 +552,12 @@ func TestRenderApp4Observability(t *testing.T) {
|
||||
},
|
||||
},
|
||||
args: map[string]interface{}{},
|
||||
application: `{"kind":"Application","apiVersion":"core.oam.dev/v1beta1","metadata":{"name":"addon-observability","namespace":"vela-system","creationTimestamp":null,"labels":{"addons.oam.dev/name":"observability"}},"spec":{"components":[],"policies":[{"name":"domain","type":"env-binding","properties":{"envs":null}}],"workflow":{"steps":[{"name":"deploy-control-plane","type":"apply-application"}]}},"status":{}}`,
|
||||
application: `{"kind":"Application","apiVersion":"core.oam.dev/v1beta1","metadata":{"name":"addon-observability","namespace":"vela-system","creationTimestamp":null,"labels":{"addons.oam.dev/name":"observability","addons.oam.dev/version":""}},"spec":{"components":[],"policies":[{"name":"domain","type":"env-binding","properties":{"envs":null}}],"workflow":{"steps":[{"name":"deploy-control-plane","type":"apply-application"}]}},"status":{}}`,
|
||||
},
|
||||
}
|
||||
for _, tc := range testcases {
|
||||
t.Run("", func(t *testing.T) {
|
||||
app, err := RenderApp(ctx, &tc.addon, nil, k8sClient, tc.args)
|
||||
app, err := RenderApp(ctx, &tc.addon, k8sClient, tc.args)
|
||||
assert.Equal(t, tc.err, err)
|
||||
if app != nil {
|
||||
data, err := json.Marshal(app)
|
||||
@@ -594,12 +599,12 @@ func TestRenderApp4ObservabilityWithK8sData(t *testing.T) {
|
||||
},
|
||||
},
|
||||
args: map[string]interface{}{},
|
||||
application: `{"kind":"Application","apiVersion":"core.oam.dev/v1beta1","metadata":{"name":"addon-observability","namespace":"vela-system","creationTimestamp":null,"labels":{"addons.oam.dev/name":"observability"}},"spec":{"components":[],"policies":[{"name":"domain","type":"env-binding","properties":{"envs":[{"name":"test-secret","placement":{"clusterSelector":{"name":"test-secret"}}}]}}],"workflow":{"steps":[{"name":"deploy-control-plane","type":"apply-application-in-parallel"},{"name":"test-secret","type":"deploy2env","properties":{"env":"test-secret","parallel":true,"policy":"domain"}}]}},"status":{}}`,
|
||||
application: `{"kind":"Application","apiVersion":"core.oam.dev/v1beta1","metadata":{"name":"addon-observability","namespace":"vela-system","creationTimestamp":null,"labels":{"addons.oam.dev/name":"observability","addons.oam.dev/version":""}},"spec":{"components":[],"policies":[{"name":"domain","type":"env-binding","properties":{"envs":[{"name":"test-secret","placement":{"clusterSelector":{"name":"test-secret"}}}]}}],"workflow":{"steps":[{"name":"deploy-control-plane","type":"apply-application-in-parallel"},{"name":"test-secret","type":"deploy2env","properties":{"env":"test-secret","parallel":true,"policy":"domain"}}]}},"status":{}}`,
|
||||
},
|
||||
}
|
||||
for _, tc := range testcases {
|
||||
t.Run("", func(t *testing.T) {
|
||||
app, err := RenderApp(ctx, &tc.addon, nil, k8sClient, tc.args)
|
||||
app, err := RenderApp(ctx, &tc.addon, k8sClient, tc.args)
|
||||
assert.Equal(t, tc.err, err)
|
||||
if app != nil {
|
||||
data, err := json.Marshal(app)
|
||||
|
||||
@@ -43,6 +43,8 @@ type Cache struct {
|
||||
|
||||
registry map[string]Registry
|
||||
|
||||
versionedUIData map[string]map[string]*UIData
|
||||
|
||||
mutex *sync.RWMutex
|
||||
|
||||
ds RegistryDataStore
|
||||
@@ -51,11 +53,12 @@ type Cache struct {
|
||||
// NewCache will build a new cache instance
|
||||
func NewCache(ds RegistryDataStore) *Cache {
|
||||
return &Cache{
|
||||
uiData: make(map[string][]*UIData),
|
||||
registryMeta: make(map[string]map[string]SourceMeta),
|
||||
registry: make(map[string]Registry),
|
||||
mutex: new(sync.RWMutex),
|
||||
ds: ds,
|
||||
uiData: make(map[string][]*UIData),
|
||||
registryMeta: make(map[string]map[string]SourceMeta),
|
||||
registry: make(map[string]Registry),
|
||||
versionedUIData: make(map[string]map[string]*UIData),
|
||||
mutex: new(sync.RWMutex),
|
||||
ds: ds,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -80,21 +83,35 @@ func (u *Cache) ListAddonMeta(r Registry) (map[string]SourceMeta, error) {
|
||||
}
|
||||
|
||||
// GetUIData get addon data for UI display from cache, if cache not found, it will find from source
|
||||
func (u *Cache) GetUIData(r Registry, addonName string) (*UIData, error) {
|
||||
addon := u.getCachedUIData(r.Name, addonName)
|
||||
func (u *Cache) GetUIData(r Registry, addonName, version string) (*UIData, error) {
|
||||
addon := u.getCachedUIData(r, addonName, version)
|
||||
if addon != nil {
|
||||
return addon, nil
|
||||
}
|
||||
var err error
|
||||
registryMeta, err := u.ListAddonMeta(r)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
if !IsVersionRegistry(r) {
|
||||
registryMeta, err := u.ListAddonMeta(r)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
meta, ok := registryMeta[addonName]
|
||||
if !ok {
|
||||
return nil, ErrNotExist
|
||||
}
|
||||
addon, err = r.GetUIData(&meta, UIMetaOptions)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
} else {
|
||||
versionedRegistry := BuildVersionedRegistry(r.Name, r.Helm.URL)
|
||||
addon, err = versionedRegistry.GetAddonUIData(context.Background(), addonName, version)
|
||||
if err != nil {
|
||||
log.Logger.Errorf("fail to get addons from registry %s for cache updating, %v", r.Name, err)
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
meta, ok := registryMeta[addonName]
|
||||
if !ok {
|
||||
return nil, ErrNotExist
|
||||
}
|
||||
return r.GetUIData(&meta, UIMetaOptions)
|
||||
|
||||
return addon, nil
|
||||
}
|
||||
|
||||
// ListUIData will always list UIData from cache first, if not exist, read from source.
|
||||
@@ -104,24 +121,40 @@ func (u *Cache) ListUIData(r Registry) ([]*UIData, error) {
|
||||
if listAddons != nil {
|
||||
return listAddons, nil
|
||||
}
|
||||
addonMeta, err := u.ListAddonMeta(r)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
listAddons, err = r.ListUIData(addonMeta, UIMetaOptions)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("fail to get addons from registry %s, %w", r.Name, err)
|
||||
if !IsVersionRegistry(r) {
|
||||
addonMeta, err := u.ListAddonMeta(r)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
listAddons, err = r.ListUIData(addonMeta, UIMetaOptions)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("fail to get addons from registry %s, %w", r.Name, err)
|
||||
}
|
||||
} else {
|
||||
versionedRegistry := BuildVersionedRegistry(r.Name, r.Helm.URL)
|
||||
listAddons, err = versionedRegistry.ListAddon()
|
||||
if err != nil {
|
||||
log.Logger.Errorf("fail to get addons from registry %s for cache updating, %v", r.Name, err)
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
u.putAddonUIData2Cache(r.Name, listAddons)
|
||||
return listAddons, nil
|
||||
}
|
||||
|
||||
func (u *Cache) getCachedUIData(registry, addonName string) *UIData {
|
||||
addons := u.listCachedUIData(registry)
|
||||
for _, a := range addons {
|
||||
if a.Name == addonName {
|
||||
return a
|
||||
func (u *Cache) getCachedUIData(registry Registry, addonName, version string) *UIData {
|
||||
if !IsVersionRegistry(registry) {
|
||||
addons := u.listCachedUIData(registry.Name)
|
||||
for _, a := range addons {
|
||||
if a.Name == addonName {
|
||||
return a
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if len(version) == 0 {
|
||||
version = "latest"
|
||||
}
|
||||
return u.versionedUIData[registry.Name][fmt.Sprintf("%s-%s", addonName, version)]
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -201,6 +234,20 @@ func (u *Cache) putRegistry2Cache(registry []Registry) {
|
||||
}
|
||||
}
|
||||
|
||||
func (u *Cache) putVersionedUIData2Cache(registryName, addonName, version string, uiData *UIData) {
|
||||
if u == nil {
|
||||
return
|
||||
}
|
||||
|
||||
u.mutex.Lock()
|
||||
defer u.mutex.Unlock()
|
||||
|
||||
if u.versionedUIData[registryName] == nil {
|
||||
u.versionedUIData[registryName] = make(map[string]*UIData)
|
||||
}
|
||||
u.versionedUIData[registryName][fmt.Sprintf("%s-%s", addonName, version)] = uiData
|
||||
}
|
||||
|
||||
func (u *Cache) discoverAndRefreshRegistry() {
|
||||
registries, err := u.ds.ListRegistries(context.Background())
|
||||
if err != nil {
|
||||
@@ -210,17 +257,36 @@ func (u *Cache) discoverAndRefreshRegistry() {
|
||||
u.putRegistry2Cache(registries)
|
||||
|
||||
for _, r := range registries {
|
||||
registryMeta, err := r.ListAddonMeta()
|
||||
if err != nil {
|
||||
log.Logger.Errorf("fail to list registry %s metadata, %v", r.Name, err)
|
||||
continue
|
||||
if !IsVersionRegistry(r) {
|
||||
registryMeta, err := r.ListAddonMeta()
|
||||
if err != nil {
|
||||
log.Logger.Errorf("fail to list registry %s metadata, %v", r.Name, err)
|
||||
continue
|
||||
}
|
||||
u.putAddonMeta2Cache(r.Name, registryMeta)
|
||||
uiData, err := r.ListUIData(registryMeta, UIMetaOptions)
|
||||
if err != nil {
|
||||
log.Logger.Errorf("fail to get addons from registry %s for cache updating, %v", r.Name, err)
|
||||
continue
|
||||
}
|
||||
u.putAddonUIData2Cache(r.Name, uiData)
|
||||
} else {
|
||||
versionedRegistry := BuildVersionedRegistry(r.Name, r.Helm.URL)
|
||||
uiDatas, err := versionedRegistry.ListAddon()
|
||||
if err != nil {
|
||||
log.Logger.Errorf("fail to get addons from registry %s for cache updating, %v", r.Name, err)
|
||||
continue
|
||||
}
|
||||
for _, addon := range uiDatas {
|
||||
uiData, err := versionedRegistry.GetAddonUIData(context.Background(), addon.Name, addon.Version)
|
||||
if err != nil {
|
||||
log.Logger.Errorf("fail to get addon from registry %s, addon %s version %s for cache updating, %v", addon.Name, r.Name, err)
|
||||
continue
|
||||
}
|
||||
u.putVersionedUIData2Cache(r.Name, addon.Name, addon.Version, uiData)
|
||||
// we also no version key, if use get addonUIData without version will return this vale as latest data.
|
||||
u.putVersionedUIData2Cache(r.Name, addon.Name, "latest", uiData)
|
||||
}
|
||||
}
|
||||
u.putAddonMeta2Cache(r.Name, registryMeta)
|
||||
uiData, err := r.ListUIData(registryMeta, UIMetaOptions)
|
||||
if err != nil {
|
||||
log.Logger.Errorf("fail to get addons from registry %s for cache updating, %v", r.Name, err)
|
||||
continue
|
||||
}
|
||||
u.putAddonUIData2Cache(r.Name, uiData)
|
||||
}
|
||||
}
|
||||
|
||||
33
pkg/addon/cache_test.go
Normal file
33
pkg/addon/cache_test.go
Normal file
@@ -0,0 +1,33 @@
|
||||
/*
|
||||
Copyright 2021 The KubeVela Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package addon
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestPutVersionedUIData2cache(t *testing.T) {
|
||||
uiData := UIData{Meta: Meta{Name: "fluxcd", Icon: "test.com/fluxcd.png", Version: "1.0.0"}}
|
||||
u := NewCache(nil)
|
||||
u.putVersionedUIData2Cache("helm-repo", "fluxcd", "1.0.0", &uiData)
|
||||
assert.NotEmpty(t, u.versionedUIData)
|
||||
assert.NotEmpty(t, u.versionedUIData["helm-repo"])
|
||||
assert.NotEmpty(t, u.versionedUIData["helm-repo"]["fluxcd-1.0.0"])
|
||||
assert.Equal(t, u.versionedUIData["helm-repo"]["fluxcd-1.0.0"].Name, "fluxcd")
|
||||
}
|
||||
@@ -22,7 +22,6 @@ import (
|
||||
"fmt"
|
||||
|
||||
"k8s.io/client-go/discovery"
|
||||
|
||||
"k8s.io/klog/v2"
|
||||
|
||||
v1 "k8s.io/api/core/v1"
|
||||
@@ -32,8 +31,10 @@ import (
|
||||
"sigs.k8s.io/controller-runtime/pkg/client"
|
||||
|
||||
commontypes "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/multicluster"
|
||||
"github.com/oam-dev/kubevela/pkg/oam"
|
||||
"github.com/oam-dev/kubevela/pkg/utils/apply"
|
||||
)
|
||||
|
||||
@@ -51,9 +52,9 @@ const (
|
||||
)
|
||||
|
||||
// EnableAddon will enable addon with dependency check, source is where addon from.
|
||||
func EnableAddon(ctx context.Context, name string, cli client.Client, discoveryClient *discovery.DiscoveryClient, apply apply.Applicator, config *rest.Config, r Registry, args map[string]interface{}, cache *Cache) error {
|
||||
func EnableAddon(ctx context.Context, name string, version string, cli client.Client, discoveryClient *discovery.DiscoveryClient, apply apply.Applicator, config *rest.Config, r Registry, args map[string]interface{}, cache *Cache) error {
|
||||
h := NewAddonInstaller(ctx, cli, discoveryClient, apply, config, &r, args, cache)
|
||||
pkg, err := h.loadInstallPackage(name)
|
||||
pkg, err := h.loadInstallPackage(name, version)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -65,12 +66,24 @@ func EnableAddon(ctx context.Context, name string, cli client.Client, discoveryC
|
||||
}
|
||||
|
||||
// DisableAddon will disable addon from cluster.
|
||||
func DisableAddon(ctx context.Context, cli client.Client, name string) error {
|
||||
func DisableAddon(ctx context.Context, cli client.Client, name string, config *rest.Config, force bool) error {
|
||||
app, err := FetchAddonRelatedApp(ctx, cli, name)
|
||||
// if app not exist, report error
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if !force {
|
||||
var usingAddonApp []v1beta1.Application
|
||||
usingAddonApp, err = checkAddonHasBeenUsed(ctx, cli, name, *app, config)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if len(usingAddonApp) != 0 {
|
||||
return fmt.Errorf(fmt.Sprintf("%s please delete them first", usingAppsInfo(usingAddonApp)))
|
||||
}
|
||||
}
|
||||
|
||||
if err := cli.Delete(ctx, app); err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -118,21 +131,30 @@ func GetAddonStatus(ctx context.Context, cli client.Client, name string) (Status
|
||||
}
|
||||
return Status{}, err
|
||||
}
|
||||
var clusters = make(map[string]map[string]interface{})
|
||||
for _, r := range app.Status.AppliedResources {
|
||||
if r.Cluster == "" {
|
||||
r.Cluster = multicluster.ClusterLocalName
|
||||
}
|
||||
// TODO(wonderflow): we should collect all the necessary information as observability, currently we only collect cluster name
|
||||
clusters[r.Cluster] = make(map[string]interface{})
|
||||
}
|
||||
|
||||
if app.Status.Workflow != nil && app.Status.Workflow.Suspend {
|
||||
return Status{AddonPhase: suspend, AppStatus: &app.Status}, nil
|
||||
return Status{AddonPhase: suspend, AppStatus: &app.Status, Clusters: clusters, InstalledVersion: app.GetLabels()[oam.LabelAddonVersion]}, nil
|
||||
}
|
||||
switch app.Status.Phase {
|
||||
case commontypes.ApplicationRunning:
|
||||
|
||||
if name == ObservabilityAddon {
|
||||
// TODO(wonderflow): this is a hack Implementation and need be fixed in a unified way
|
||||
var (
|
||||
clusters = make(map[string]map[string]interface{})
|
||||
sec v1.Secret
|
||||
domain string
|
||||
sec v1.Secret
|
||||
domain string
|
||||
)
|
||||
if err = cli.Get(ctx, client.ObjectKey{Namespace: types.DefaultKubeVelaNS, Name: Convert2SecName(name)}, &sec); err != nil {
|
||||
klog.ErrorS(err, "failed to get observability secret")
|
||||
return Status{AddonPhase: enabling, AppStatus: &app.Status}, nil
|
||||
return Status{AddonPhase: enabling, AppStatus: &app.Status, Clusters: clusters}, nil
|
||||
}
|
||||
|
||||
if v, ok := sec.Data[ObservabilityAddonDomainArg]; ok {
|
||||
@@ -141,7 +163,7 @@ func GetAddonStatus(ctx context.Context, cli client.Client, name string) (Status
|
||||
observability, err := GetObservabilityAccessibilityInfo(ctx, cli, domain)
|
||||
if err != nil {
|
||||
klog.ErrorS(err, "failed to get observability accessibility info")
|
||||
return Status{AddonPhase: enabling, AppStatus: &app.Status}, nil
|
||||
return Status{AddonPhase: enabling, AppStatus: &app.Status, Clusters: clusters}, nil
|
||||
}
|
||||
|
||||
for _, o := range observability {
|
||||
@@ -158,11 +180,11 @@ func GetAddonStatus(ctx context.Context, cli client.Client, name string) (Status
|
||||
}
|
||||
return Status{AddonPhase: enabled, AppStatus: &app.Status, Clusters: clusters}, nil
|
||||
}
|
||||
return Status{AddonPhase: enabled, AppStatus: &app.Status}, nil
|
||||
return Status{AddonPhase: enabled, AppStatus: &app.Status, InstalledVersion: app.GetLabels()[oam.LabelAddonVersion], Clusters: clusters}, nil
|
||||
case commontypes.ApplicationDeleting:
|
||||
return Status{AddonPhase: disabling, AppStatus: &app.Status}, nil
|
||||
return Status{AddonPhase: disabling, AppStatus: &app.Status, Clusters: clusters}, nil
|
||||
default:
|
||||
return Status{AddonPhase: enabling, AppStatus: &app.Status}, nil
|
||||
return Status{AddonPhase: enabling, AppStatus: &app.Status, InstalledVersion: app.GetLabels()[oam.LabelAddonVersion], Clusters: clusters}, nil
|
||||
}
|
||||
}
|
||||
|
||||
@@ -221,5 +243,6 @@ type Status struct {
|
||||
AddonPhase string
|
||||
AppStatus *commontypes.AppStatus
|
||||
// the status of multiple clusters
|
||||
Clusters map[string]map[string]interface{} `json:"clusters,omitempty"`
|
||||
Clusters map[string]map[string]interface{} `json:"clusters,omitempty"`
|
||||
InstalledVersion string
|
||||
}
|
||||
|
||||
71
pkg/addon/memory_reader_test.go
Normal file
71
pkg/addon/memory_reader_test.go
Normal file
@@ -0,0 +1,71 @@
|
||||
/*
|
||||
Copyright 2021 The KubeVela Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package addon
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"helm.sh/helm/v3/pkg/chart/loader"
|
||||
)
|
||||
|
||||
var files = []*loader.BufferedFile{
|
||||
{
|
||||
Name: "metadata.yaml",
|
||||
Data: []byte(`name: test-helm-addon
|
||||
version: 1.0.0
|
||||
description: This is a addon for test when install addon from helm repo
|
||||
icon: https://www.terraform.io/assets/images/logo-text-8c3ba8a6.svg
|
||||
url: https://terraform.io/
|
||||
|
||||
tags: []
|
||||
|
||||
deployTo:
|
||||
control_plane: true
|
||||
runtime_cluster: false
|
||||
|
||||
dependencies: []
|
||||
|
||||
invisible: false`),
|
||||
},
|
||||
{
|
||||
Name: "/resources/parameter.cue",
|
||||
Data: []byte(`parameter: {
|
||||
// test wrong parameter
|
||||
example: *"default"
|
||||
}`),
|
||||
},
|
||||
}
|
||||
|
||||
func TestMemoryReader(t *testing.T) {
|
||||
m := MemoryReader{
|
||||
Name: "fluxcd",
|
||||
Files: files,
|
||||
}
|
||||
|
||||
meta, err := m.ListAddonMeta()
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, len(meta["fluxcd"].Items), 2)
|
||||
|
||||
metaFile, err := m.ReadFile("metadata.yaml")
|
||||
assert.NoError(t, err)
|
||||
assert.NotEmpty(t, metaFile)
|
||||
|
||||
paramterData, err := m.ReadFile("/resources/parameter.cue")
|
||||
assert.NoError(t, err)
|
||||
assert.NotEmpty(t, paramterData)
|
||||
}
|
||||
@@ -96,5 +96,4 @@ func TestGiteeReader(t *testing.T) {
|
||||
_, err := r.ReadFile("example/metadata.yaml")
|
||||
assert.NoError(t, err)
|
||||
|
||||
testReaderFunc(t, r)
|
||||
}
|
||||
|
||||
@@ -108,7 +108,6 @@ func TestGitHubReader(t *testing.T) {
|
||||
_, err := r.ReadFile("example/metadata.yaml")
|
||||
assert.NoError(t, err)
|
||||
|
||||
testReaderFunc(t, r)
|
||||
}
|
||||
|
||||
// Int is a helper routine that allocates a new int value
|
||||
|
||||
60
pkg/addon/reader_memory.go
Normal file
60
pkg/addon/reader_memory.go
Normal file
@@ -0,0 +1,60 @@
|
||||
/*
|
||||
Copyright 2021 The KubeVela Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package addon
|
||||
|
||||
import (
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"helm.sh/helm/v3/pkg/chart/loader"
|
||||
)
|
||||
|
||||
// MemoryReader is async reader for memory data
|
||||
type MemoryReader struct {
|
||||
Name string
|
||||
Files []*loader.BufferedFile
|
||||
fileData map[string]string
|
||||
}
|
||||
|
||||
// ListAddonMeta list all metadata of helm repo registry
|
||||
func (l *MemoryReader) ListAddonMeta() (map[string]SourceMeta, error) {
|
||||
metas := SourceMeta{Name: l.Name}
|
||||
for _, f := range l.Files {
|
||||
metas.Items = append(metas.Items, OSSItem{tp: "file", name: f.Name})
|
||||
if l.fileData == nil {
|
||||
l.fileData = make(map[string]string)
|
||||
}
|
||||
l.fileData[f.Name] = string(f.Data)
|
||||
}
|
||||
return map[string]SourceMeta{l.Name: metas}, nil
|
||||
}
|
||||
|
||||
// ReadFile ready file from memory
|
||||
func (l *MemoryReader) ReadFile(path string) (string, error) {
|
||||
if file, ok := l.fileData[path]; ok {
|
||||
return file, nil
|
||||
}
|
||||
return l.fileData[strings.TrimPrefix(path, l.Name+"/")], nil
|
||||
}
|
||||
|
||||
// RelativePath calculate the relative path of one file
|
||||
func (l *MemoryReader) RelativePath(item Item) string {
|
||||
if strings.HasPrefix(item.GetName(), l.Name) {
|
||||
return item.GetName()
|
||||
}
|
||||
return filepath.Join(l.Name, item.GetName())
|
||||
}
|
||||
@@ -37,6 +37,7 @@ const registriesKey = "registries"
|
||||
type Registry struct {
|
||||
Name string `json:"name"`
|
||||
|
||||
Helm *HelmSource `json:"helm,omitempty"`
|
||||
Git *GitAddonSource `json:"git,omitempty"`
|
||||
OSS *OSSAddonSource `json:"oss,omitempty"`
|
||||
Gitee *GiteeAddonSource `json:"gitee,omitempty"`
|
||||
|
||||
@@ -63,6 +63,11 @@ type GiteeAddonSource struct {
|
||||
Token string `json:"token,omitempty"`
|
||||
}
|
||||
|
||||
// HelmSource defines the information about the helm repo addon source
|
||||
type HelmSource struct {
|
||||
URL string `json:"url,omitempty" validate:"required"`
|
||||
}
|
||||
|
||||
// Item is a partial interface for github.RepositoryContent
|
||||
type Item interface {
|
||||
// GetType return "dir" or "file"
|
||||
|
||||
@@ -22,6 +22,9 @@ import (
|
||||
"time"
|
||||
|
||||
"k8s.io/client-go/discovery"
|
||||
ocmclusterv1 "open-cluster-management.io/api/cluster/v1"
|
||||
ocmclusterv1alpha1 "open-cluster-management.io/api/cluster/v1alpha1"
|
||||
ocmworkv1 "open-cluster-management.io/api/work/v1"
|
||||
|
||||
v12 "k8s.io/api/core/v1"
|
||||
crdv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"
|
||||
@@ -79,6 +82,9 @@ var _ = BeforeSuite(func(done Done) {
|
||||
Expect(coreoam.AddToScheme(scheme)).NotTo(HaveOccurred())
|
||||
Expect(clientgoscheme.AddToScheme(scheme)).NotTo(HaveOccurred())
|
||||
Expect(crdv1.AddToScheme(scheme)).NotTo(HaveOccurred())
|
||||
_ = ocmclusterv1alpha1.Install(scheme)
|
||||
_ = ocmclusterv1.Install(scheme)
|
||||
_ = ocmworkv1.Install(scheme)
|
||||
k8sClient, err = client.New(cfg, client.Options{Scheme: scheme})
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
Expect(k8sClient).ToNot(BeNil())
|
||||
|
||||
BIN
pkg/addon/testdata/helm-repo/fluxcd-1.0.0.tgz
vendored
Normal file
BIN
pkg/addon/testdata/helm-repo/fluxcd-1.0.0.tgz
vendored
Normal file
Binary file not shown.
15
pkg/addon/testdata/helm-repo/index.yaml
vendored
Normal file
15
pkg/addon/testdata/helm-repo/index.yaml
vendored
Normal file
@@ -0,0 +1,15 @@
|
||||
apiVersion: v1
|
||||
entries:
|
||||
fluxcd:
|
||||
- created: "2022-03-25T21:04:51.244331+08:00"
|
||||
description: Extended workload to do continuous and progressive delivery
|
||||
home: https://fluxcd.io
|
||||
icon: https://raw.githubusercontent.com/fluxcd/flux/master/docs/_files/weave-flux.png
|
||||
keywords:
|
||||
- extended_workload
|
||||
- gitops
|
||||
name: fluxcd
|
||||
urls:
|
||||
- http://127.0.0.1:18083/fluxcd-1.0.0.tgz
|
||||
version: 1.0.0
|
||||
generated: "0001-01-01T00:00:00Z"
|
||||
9
pkg/addon/testdata/test-disable-addon/definitions/compDef.yaml
vendored
Normal file
9
pkg/addon/testdata/test-disable-addon/definitions/compDef.yaml
vendored
Normal file
@@ -0,0 +1,9 @@
|
||||
apiVersion: core.oam.dev/v1beta1
|
||||
kind: ComponentDefinition
|
||||
metadata:
|
||||
name: my-comp
|
||||
namespace: vela-system
|
||||
spec:
|
||||
schematic:
|
||||
cue:
|
||||
template:
|
||||
19
pkg/addon/testdata/test-disable-addon/definitions/traitDef.cue
vendored
Normal file
19
pkg/addon/testdata/test-disable-addon/definitions/traitDef.cue
vendored
Normal file
@@ -0,0 +1,19 @@
|
||||
"my-trait": {
|
||||
type: "trait"
|
||||
annotations: {}
|
||||
description: "Rollout the component."
|
||||
attributes: {
|
||||
manageWorkload: true
|
||||
status: {
|
||||
customStatus: #"""
|
||||
message: context.outputs.rollout.status.rollingState
|
||||
"""#
|
||||
healthPolicy: #"""
|
||||
isHealth: context.outputs.rollout.status.batchRollingState == "batchReady"
|
||||
"""#
|
||||
}
|
||||
}
|
||||
}
|
||||
template: {
|
||||
outputs: rollout: {}
|
||||
}
|
||||
13
pkg/addon/testdata/test-disable-addon/metadata.yaml
vendored
Normal file
13
pkg/addon/testdata/test-disable-addon/metadata.yaml
vendored
Normal file
@@ -0,0 +1,13 @@
|
||||
name: test-disable-addon
|
||||
version: 1.0.0
|
||||
|
||||
tags:
|
||||
- only_test
|
||||
|
||||
deployTo:
|
||||
control_plane: true
|
||||
runtime_cluster: false
|
||||
|
||||
dependencies: []
|
||||
|
||||
invisible: false
|
||||
@@ -37,19 +37,37 @@ type UIData struct {
|
||||
CUEDefinitions []ElementFile `json:"CUEDefinitions"`
|
||||
Parameters string `json:"parameters"`
|
||||
RegistryName string `json:"registryName"`
|
||||
|
||||
AvailableVersions []string `json:"availableVersions"`
|
||||
}
|
||||
|
||||
// InstallPackage contains all necessary files that can be installed for an addon
|
||||
type InstallPackage struct {
|
||||
Meta
|
||||
|
||||
Definitions []ElementFile `json:"definitions"`
|
||||
CUEDefinitions []ElementFile `json:"CUEDefinitions"`
|
||||
Parameters string `json:"parameters"`
|
||||
CUETemplates []ElementFile `json:"CUETemplates"`
|
||||
YAMLTemplates []ElementFile `json:"YAMLTemplates,omitempty"`
|
||||
DefSchemas []ElementFile `json:"def_schemas,omitempty"`
|
||||
AppTemplate *v1beta1.Application `json:"appTemplate"`
|
||||
// Definitions and CUEDefinitions are converted as OAM X-Definitions, they will only in control plane cluster
|
||||
Definitions []ElementFile `json:"definitions"`
|
||||
CUEDefinitions []ElementFile `json:"CUEDefinitions"`
|
||||
// DefSchemas are UI schemas read by VelaUX, it will only be installed in control plane clusters
|
||||
DefSchemas []ElementFile `json:"defSchemas,omitempty"`
|
||||
|
||||
Parameters string `json:"parameters"`
|
||||
|
||||
// CUETemplates and YAMLTemplates are resources needed to be installed in managed clusters
|
||||
CUETemplates []ElementFile `json:"CUETemplates"`
|
||||
YAMLTemplates []ElementFile `json:"YAMLTemplates,omitempty"`
|
||||
AppTemplate *v1beta1.Application `json:"appTemplate"`
|
||||
}
|
||||
|
||||
// WholeAddonPackage contains all infos of an addon
|
||||
type WholeAddonPackage struct {
|
||||
InstallPackage
|
||||
|
||||
APISchema *openapi3.Schema `json:"schema"`
|
||||
|
||||
// Detail is README.md in an addon
|
||||
Detail string `json:"detail,omitempty"`
|
||||
AvailableVersions []string `json:"availableVersions"`
|
||||
}
|
||||
|
||||
// Meta defines the format for a single addon
|
||||
|
||||
226
pkg/addon/utils.go
Normal file
226
pkg/addon/utils.go
Normal file
@@ -0,0 +1,226 @@
|
||||
/*
|
||||
Copyright 2021 The KubeVela Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package addon
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
errors "github.com/pkg/errors"
|
||||
|
||||
"sigs.k8s.io/controller-runtime/pkg/client"
|
||||
|
||||
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
|
||||
rest "k8s.io/client-go/rest"
|
||||
|
||||
"github.com/oam-dev/kubevela/apis/core.oam.dev/v1beta1"
|
||||
"github.com/oam-dev/kubevela/pkg/definition"
|
||||
"github.com/oam-dev/kubevela/pkg/oam"
|
||||
"github.com/oam-dev/kubevela/pkg/oam/util"
|
||||
)
|
||||
|
||||
const (
|
||||
compDefAnnotation = "addon.oam.dev/componentDefinitions"
|
||||
traitDefAnnotation = "addon.oam.dev/traitDefinitions"
|
||||
workflowStepDefAnnotation = "addon.oam.dev/workflowStepDefinitions"
|
||||
defKeytemplate = "addon-%s-%s"
|
||||
)
|
||||
|
||||
// parse addon's created x-defs in addon-app's annotation, this will be used to check whether app still using it while disabling.
|
||||
func passDefInAppAnnotation(defs []*unstructured.Unstructured, app *v1beta1.Application) error {
|
||||
var comps, traits, workflowSteps []string
|
||||
for _, def := range defs {
|
||||
switch def.GetObjectKind().GroupVersionKind().Kind {
|
||||
case v1beta1.ComponentDefinitionKind:
|
||||
comps = append(comps, def.GetName())
|
||||
case v1beta1.TraitDefinitionKind:
|
||||
traits = append(traits, def.GetName())
|
||||
case v1beta1.WorkflowStepDefinitionKind:
|
||||
workflowSteps = append(workflowSteps, def.GetName())
|
||||
default:
|
||||
return fmt.Errorf("cannot handle definition types %s, name %s", def.GetObjectKind().GroupVersionKind().Kind, def.GetName())
|
||||
}
|
||||
}
|
||||
if len(comps) != 0 {
|
||||
app.SetAnnotations(util.MergeMapOverrideWithDst(app.GetAnnotations(), map[string]string{compDefAnnotation: strings.Join(comps, ",")}))
|
||||
}
|
||||
if len(traits) != 0 {
|
||||
app.SetAnnotations(util.MergeMapOverrideWithDst(app.GetAnnotations(), map[string]string{traitDefAnnotation: strings.Join(traits, ",")}))
|
||||
}
|
||||
if len(workflowSteps) != 0 {
|
||||
app.SetAnnotations(util.MergeMapOverrideWithDst(app.GetAnnotations(), map[string]string{workflowStepDefAnnotation: strings.Join(workflowSteps, ",")}))
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// check whether this addon has been used by some applications
|
||||
func checkAddonHasBeenUsed(ctx context.Context, k8sClient client.Client, name string, addonApp v1beta1.Application, config *rest.Config) ([]v1beta1.Application, error) {
|
||||
apps := v1beta1.ApplicationList{}
|
||||
if err := k8sClient.List(ctx, &apps, client.InNamespace("")); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if len(apps.Items) == 0 {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
createdDefs := make(map[string]bool)
|
||||
for key, defNames := range addonApp.GetAnnotations() {
|
||||
switch key {
|
||||
case compDefAnnotation, traitDefAnnotation, workflowStepDefAnnotation:
|
||||
merge2DefMap(key, defNames, createdDefs)
|
||||
}
|
||||
}
|
||||
|
||||
if len(createdDefs) == 0 {
|
||||
if err := findLegacyAddonDefs(ctx, k8sClient, name, addonApp.GetLabels()[oam.LabelAddonRegistry], config, createdDefs); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
var res []v1beta1.Application
|
||||
CHECKNEXT:
|
||||
for _, app := range apps.Items {
|
||||
for _, component := range app.Spec.Components {
|
||||
if createdDefs[fmt.Sprintf(defKeytemplate, "comp", component.Type)] {
|
||||
res = append(res, app)
|
||||
// this app has used this addon, there is no need check other components
|
||||
continue CHECKNEXT
|
||||
}
|
||||
for _, trait := range component.Traits {
|
||||
if createdDefs[fmt.Sprintf(defKeytemplate, "trait", trait.Type)] {
|
||||
res = append(res, app)
|
||||
continue CHECKNEXT
|
||||
}
|
||||
}
|
||||
}
|
||||
if app.Spec.Workflow == nil || len(app.Spec.Workflow.Steps) == 0 {
|
||||
return res, nil
|
||||
}
|
||||
for _, s := range app.Spec.Workflow.Steps {
|
||||
if createdDefs[fmt.Sprintf(defKeytemplate, "wfStep", s.Type)] {
|
||||
res = append(res, app)
|
||||
continue CHECKNEXT
|
||||
}
|
||||
}
|
||||
}
|
||||
return res, nil
|
||||
}
|
||||
|
||||
// merge2DefMap will parse annotation in addon's app to 'created x-definition'. Then stroe them in defMap
|
||||
func merge2DefMap(defType string, defNames string, defMap map[string]bool) {
|
||||
list := strings.Split(defNames, ",")
|
||||
template := "addon-%s-%s"
|
||||
for _, defName := range list {
|
||||
switch defType {
|
||||
case compDefAnnotation:
|
||||
defMap[fmt.Sprintf(template, "comp", defName)] = true
|
||||
case traitDefAnnotation:
|
||||
defMap[fmt.Sprintf(template, "trait", defName)] = true
|
||||
case workflowStepDefAnnotation:
|
||||
defMap[fmt.Sprintf(template, "wfStep", defName)] = true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// for old addon's app no 'created x-definitions' annotation, fetch the definitions from alive addon registry. Put them in defMap
|
||||
func findLegacyAddonDefs(ctx context.Context, k8sClient client.Client, addonName string, registryName string, config *rest.Config, defs map[string]bool) error {
|
||||
// if the addon enable by local we cannot fetch the source definitions yet, so skip the check
|
||||
if registryName == "local" {
|
||||
return nil
|
||||
}
|
||||
|
||||
registryDS := NewRegistryDataStore(k8sClient)
|
||||
registries, err := registryDS.ListRegistries(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
var defObjects []*unstructured.Unstructured
|
||||
for i, registry := range registries {
|
||||
if registry.Name == registryName {
|
||||
var uiData *UIData
|
||||
if !IsVersionRegistry(registry) {
|
||||
installer := NewAddonInstaller(ctx, k8sClient, nil, nil, config, ®istries[i], nil, nil)
|
||||
metas, err := installer.getAddonMeta()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
meta := metas[addonName]
|
||||
// only fetch definition files from registry.
|
||||
uiData, err = registry.GetUIData(&meta, UnInstallOptions)
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, "cannot fetch addon difinition files from registry")
|
||||
}
|
||||
} else {
|
||||
versionedRegistry := BuildVersionedRegistry(registry.Name, registry.Helm.URL)
|
||||
uiData, err = versionedRegistry.GetAddonUIData(ctx, addonName, "")
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, "cannot fetch addon difinition files from registry")
|
||||
}
|
||||
}
|
||||
|
||||
for _, defYaml := range uiData.Definitions {
|
||||
def, err := renderObject(defYaml)
|
||||
if err != nil {
|
||||
// don't let one error defined definition block whole disable process
|
||||
continue
|
||||
}
|
||||
defObjects = append(defObjects, def)
|
||||
}
|
||||
for _, cueDef := range uiData.CUEDefinitions {
|
||||
def := definition.Definition{Unstructured: unstructured.Unstructured{}}
|
||||
err := def.FromCUEString(cueDef.Data, config)
|
||||
if err != nil {
|
||||
// don't let one error defined cue definition block whole disable process
|
||||
continue
|
||||
}
|
||||
defObjects = append(defObjects, &def.Unstructured)
|
||||
}
|
||||
}
|
||||
}
|
||||
for _, defObject := range defObjects {
|
||||
switch defObject.GetObjectKind().GroupVersionKind().Kind {
|
||||
case v1beta1.ComponentDefinitionKind:
|
||||
defs[fmt.Sprintf(defKeytemplate, "comp", defObject.GetName())] = true
|
||||
case v1beta1.TraitDefinitionKind:
|
||||
defs[fmt.Sprintf(defKeytemplate, "trait", defObject.GetName())] = true
|
||||
case v1beta1.WorkflowStepDefinitionKind:
|
||||
defs[fmt.Sprintf(defKeytemplate, "wfStep", defObject.GetName())] = true
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func usingAppsInfo(apps []v1beta1.Application) string {
|
||||
res := "addon is being used :"
|
||||
appsNamespaceNameList := map[string][]string{}
|
||||
for _, app := range apps {
|
||||
appsNamespaceNameList[app.GetNamespace()] = append(appsNamespaceNameList[app.GetNamespace()], app.GetName())
|
||||
}
|
||||
for namespace, appNames := range appsNamespaceNameList {
|
||||
nameStr := strings.Join(appNames, ",")
|
||||
res += fmt.Sprintf("{%s} in namespace:%s,", nameStr, namespace)
|
||||
}
|
||||
res = strings.TrimSuffix(res, ",") + ".Please delete them before disabling the addon."
|
||||
return res
|
||||
}
|
||||
|
||||
// IsVersionRegistry check the repo source if support multi-version addon
|
||||
func IsVersionRegistry(r Registry) bool {
|
||||
return r.Helm != nil
|
||||
}
|
||||
258
pkg/addon/utils_test.go
Normal file
258
pkg/addon/utils_test.go
Normal file
@@ -0,0 +1,258 @@
|
||||
/*
|
||||
Copyright 2021 The KubeVela Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package addon
|
||||
|
||||
import (
|
||||
"net/http/httptest"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
|
||||
. "github.com/onsi/ginkgo"
|
||||
. "github.com/onsi/gomega"
|
||||
v1 "k8s.io/api/core/v1"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
|
||||
"sigs.k8s.io/yaml"
|
||||
|
||||
"github.com/oam-dev/kubevela/apis/core.oam.dev/v1beta1"
|
||||
velatypes "github.com/oam-dev/kubevela/apis/types"
|
||||
"github.com/oam-dev/kubevela/pkg/oam"
|
||||
"github.com/oam-dev/kubevela/pkg/oam/util"
|
||||
)
|
||||
|
||||
var _ = Describe("Test definition check", func() {
|
||||
var compDef v1beta1.ComponentDefinition
|
||||
var traitDef v1beta1.TraitDefinition
|
||||
var wfStepDef v1beta1.WorkflowStepDefinition
|
||||
|
||||
BeforeEach(func() {
|
||||
compDef = v1beta1.ComponentDefinition{}
|
||||
traitDef = v1beta1.TraitDefinition{}
|
||||
wfStepDef = v1beta1.WorkflowStepDefinition{}
|
||||
|
||||
Expect(yaml.Unmarshal([]byte(compDefYaml), &compDef)).Should(BeNil())
|
||||
Expect(k8sClient.Create(ctx, &compDef)).Should(SatisfyAny(BeNil(), util.AlreadyExistMatcher{}))
|
||||
|
||||
Expect(yaml.Unmarshal([]byte(traitDefYaml), &traitDef)).Should(BeNil())
|
||||
Expect(k8sClient.Create(ctx, &traitDef)).Should(SatisfyAny(BeNil(), util.AlreadyExistMatcher{}))
|
||||
|
||||
Expect(yaml.Unmarshal([]byte(wfStepDefYaml), &wfStepDef)).Should(BeNil())
|
||||
Expect(k8sClient.Create(ctx, &wfStepDef)).Should(SatisfyAny(BeNil(), util.AlreadyExistMatcher{}))
|
||||
})
|
||||
|
||||
It("Test pass def to app annotation", func() {
|
||||
c := v1beta1.ComponentDefinition{TypeMeta: metav1.TypeMeta{APIVersion: "core.oam.dev/v1beta1", Kind: "ComponentDefinition"}}
|
||||
c.SetName("my-comp")
|
||||
|
||||
t := v1beta1.TraitDefinition{TypeMeta: metav1.TypeMeta{APIVersion: "core.oam.dev/v1beta1", Kind: "TraitDefinition"}}
|
||||
t.SetName("my-trait")
|
||||
|
||||
w := v1beta1.WorkflowStepDefinition{TypeMeta: metav1.TypeMeta{APIVersion: "core.oam.dev/v1beta1", Kind: "WorkflowStepDefinition"}}
|
||||
w.SetName("my-wfstep")
|
||||
|
||||
var defs []*unstructured.Unstructured
|
||||
cDef, err := util.Object2Unstructured(c)
|
||||
Expect(err).Should(BeNil())
|
||||
defs = append(defs, cDef)
|
||||
tDef, err := util.Object2Unstructured(t)
|
||||
defs = append(defs, tDef)
|
||||
Expect(err).Should(BeNil())
|
||||
wDef, err := util.Object2Unstructured(w)
|
||||
Expect(err).Should(BeNil())
|
||||
defs = append(defs, wDef)
|
||||
|
||||
addonApp := v1beta1.Application{ObjectMeta: metav1.ObjectMeta{Name: "addon-app", Namespace: velatypes.DefaultKubeVelaNS}}
|
||||
err = passDefInAppAnnotation(defs, &addonApp)
|
||||
Expect(err).Should(BeNil())
|
||||
|
||||
anno := addonApp.GetAnnotations()
|
||||
Expect(len(anno)).Should(BeEquivalentTo(3))
|
||||
Expect(anno[compDefAnnotation]).Should(BeEquivalentTo("my-comp"))
|
||||
Expect(anno[traitDefAnnotation]).Should(BeEquivalentTo("my-trait"))
|
||||
Expect(anno[workflowStepDefAnnotation]).Should(BeEquivalentTo("my-wfstep"))
|
||||
})
|
||||
|
||||
It("Test checkAddonHasBeenUsed func", func() {
|
||||
addonApp := v1beta1.Application{}
|
||||
Expect(yaml.Unmarshal([]byte(addonAppYaml), &addonApp)).Should(BeNil())
|
||||
|
||||
app1 := v1beta1.Application{}
|
||||
Expect(yaml.Unmarshal([]byte(testApp1Yaml), &app1)).Should(BeNil())
|
||||
Expect(k8sClient.Create(ctx, &app1)).Should(BeNil())
|
||||
|
||||
app2 := v1beta1.Application{}
|
||||
Expect(yaml.Unmarshal([]byte(testApp2Yaml), &app2)).Should(BeNil())
|
||||
Expect(k8sClient.Create(ctx, &app2)).Should(BeNil())
|
||||
|
||||
Expect(k8sClient.Create(ctx, &v1.Namespace{ObjectMeta: metav1.ObjectMeta{Name: "test-ns"}}))
|
||||
app3 := v1beta1.Application{}
|
||||
Expect(yaml.Unmarshal([]byte(testApp3Yaml), &app3)).Should(BeNil())
|
||||
Expect(k8sClient.Create(ctx, &app3)).Should(BeNil())
|
||||
|
||||
usedApps, err := checkAddonHasBeenUsed(ctx, k8sClient, "my-addon", addonApp, cfg)
|
||||
Expect(err).Should(BeNil())
|
||||
Expect(len(usedApps)).Should(BeEquivalentTo(3))
|
||||
})
|
||||
|
||||
It("check fetch lagacy addon definitions", func() {
|
||||
res := make(map[string]bool)
|
||||
|
||||
server := httptest.NewServer(ossHandler)
|
||||
defer server.Close()
|
||||
|
||||
url := server.URL
|
||||
cmYaml := strings.ReplaceAll(registryCmYaml, "TEST_SERVER_URL", url)
|
||||
cm := v1.ConfigMap{}
|
||||
Expect(yaml.Unmarshal([]byte(cmYaml), &cm)).Should(BeNil())
|
||||
Expect(k8sClient.Create(ctx, &cm)).Should(BeNil())
|
||||
|
||||
disableTestAddonApp := v1beta1.Application{}
|
||||
Expect(yaml.Unmarshal([]byte(addonDisableTestAppYaml), &disableTestAddonApp)).Should(BeNil())
|
||||
Expect(findLegacyAddonDefs(ctx, k8sClient, "test-disable-addon", disableTestAddonApp.GetLabels()[oam.LabelAddonRegistry], cfg, res)).Should(BeNil())
|
||||
Expect(len(res)).Should(BeEquivalentTo(2))
|
||||
})
|
||||
})
|
||||
|
||||
func TestMerge2Map(t *testing.T) {
|
||||
res := make(map[string]bool)
|
||||
merge2DefMap(compDefAnnotation, "my-comp1,my-comp2", res)
|
||||
merge2DefMap(traitDefAnnotation, "my-trait1,my-trait2", res)
|
||||
merge2DefMap(workflowStepDefAnnotation, "my-wfStep1,my-wfStep2", res)
|
||||
assert.Equal(t, 6, len(res))
|
||||
}
|
||||
|
||||
func TestUsingAddonInfo(t *testing.T) {
|
||||
apps := []v1beta1.Application{
|
||||
v1beta1.Application{ObjectMeta: metav1.ObjectMeta{Namespace: "namespace-1", Name: "app-1"}},
|
||||
v1beta1.Application{ObjectMeta: metav1.ObjectMeta{Namespace: "namespace-2", Name: "app-2"}},
|
||||
v1beta1.Application{ObjectMeta: metav1.ObjectMeta{Namespace: "namespace-1", Name: "app-3"}},
|
||||
}
|
||||
res := usingAppsInfo(apps)
|
||||
assert.Equal(t, true, strings.Contains(res, "Please delete them before disabling the addon"))
|
||||
}
|
||||
|
||||
const (
|
||||
compDefYaml = `
|
||||
apiVersion: core.oam.dev/v1beta1
|
||||
kind: ComponentDefinition
|
||||
metadata:
|
||||
name: my-comp
|
||||
namespace: vela-system
|
||||
`
|
||||
traitDefYaml = `
|
||||
apiVersion: core.oam.dev/v1beta1
|
||||
kind: TraitDefinition
|
||||
metadata:
|
||||
name: my-trait
|
||||
namespace: vela-system
|
||||
`
|
||||
wfStepDefYaml = `
|
||||
apiVersion: core.oam.dev/v1beta1
|
||||
kind: WorkflowStepDefinition
|
||||
metadata:
|
||||
name: my-wfstep
|
||||
namespace: vela-system
|
||||
`
|
||||
)
|
||||
|
||||
const (
|
||||
addonAppYaml = `
|
||||
apiVersion: core.oam.dev/v1beta1
|
||||
kind: Application
|
||||
metadata:
|
||||
labels:
|
||||
addons.oam.dev/name: myaddon
|
||||
addons.oam.dev/registry: KubeVela
|
||||
annotations:
|
||||
addon.oam.dev/componentDefinitions: "my-comp"
|
||||
addon.oam.dev/traitDefinitions: "my-trait"
|
||||
addon.oam.dev/workflowStepDefinitions: "my-wfstep"
|
||||
name: addon-myaddon
|
||||
namespace: vela-system
|
||||
spec:
|
||||
`
|
||||
testApp1Yaml = `
|
||||
apiVersion: core.oam.dev/v1beta1
|
||||
kind: Application
|
||||
metadata:
|
||||
labels:
|
||||
name: app-1
|
||||
namespace: default
|
||||
spec:
|
||||
components:
|
||||
- name: comp1
|
||||
type: my-comp
|
||||
traits:
|
||||
- type: my-trait
|
||||
`
|
||||
testApp2Yaml = `
|
||||
apiVersion: core.oam.dev/v1beta1
|
||||
kind: Application
|
||||
metadata:
|
||||
labels:
|
||||
name: app-2
|
||||
namespace: default
|
||||
spec:
|
||||
components:
|
||||
- name: comp2
|
||||
type: webservice
|
||||
traits:
|
||||
- type: my-trait
|
||||
`
|
||||
testApp3Yaml = `
|
||||
apiVersion: core.oam.dev/v1beta1
|
||||
kind: Application
|
||||
metadata:
|
||||
name: app-3
|
||||
namespace: test-ns
|
||||
spec:
|
||||
components:
|
||||
- name: podinfo
|
||||
type: webservice
|
||||
|
||||
workflow:
|
||||
steps:
|
||||
- type: my-wfstep
|
||||
name: deploy
|
||||
`
|
||||
registryCmYaml = `
|
||||
apiVersion: v1
|
||||
data:
|
||||
registries: '{ "KubeVela":{ "name": "KubeVela", "oss": { "end_point": "TEST_SERVER_URL",
|
||||
"bucket": "", "path": "" } } }'
|
||||
kind: ConfigMap
|
||||
metadata:
|
||||
name: vela-addon-registry
|
||||
namespace: vela-system
|
||||
`
|
||||
addonDisableTestAppYaml = `
|
||||
apiVersion: core.oam.dev/v1beta1
|
||||
kind: Application
|
||||
metadata:
|
||||
name: addon-test-disable-addon
|
||||
namespace: vela-system
|
||||
labels:
|
||||
addons.oam.dev/name: test-disable-addon
|
||||
addons.oam.dev/registry: KubeVela
|
||||
spec:
|
||||
components:
|
||||
- name: podinfo
|
||||
type: webservice
|
||||
`
|
||||
)
|
||||
172
pkg/addon/versioned_registry.go
Normal file
172
pkg/addon/versioned_registry.go
Normal file
@@ -0,0 +1,172 @@
|
||||
/*
|
||||
Copyright 2021 The KubeVela Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package addon
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"fmt"
|
||||
"sort"
|
||||
|
||||
"helm.sh/helm/v3/pkg/chart/loader"
|
||||
"helm.sh/helm/v3/pkg/repo"
|
||||
|
||||
"github.com/oam-dev/kubevela/pkg/utils/common"
|
||||
"github.com/oam-dev/kubevela/pkg/utils/helm"
|
||||
)
|
||||
|
||||
// VersionedRegistry is the interface of support version registry
|
||||
type VersionedRegistry interface {
|
||||
ListAddon() ([]*UIData, error)
|
||||
GetAddonUIData(ctx context.Context, addonName, version string) (*UIData, error)
|
||||
GetAddonInstallPackage(ctx context.Context, addonName, version string) (*InstallPackage, error)
|
||||
}
|
||||
|
||||
// BuildVersionedRegistry is build versioned addon registry
|
||||
func BuildVersionedRegistry(name, repoURL string) VersionedRegistry {
|
||||
return &versionedRegistry{
|
||||
name: name,
|
||||
url: repoURL,
|
||||
h: helm.NewHelperWithCache(),
|
||||
}
|
||||
}
|
||||
|
||||
type versionedRegistry struct {
|
||||
url string
|
||||
name string
|
||||
h *helm.Helper
|
||||
}
|
||||
|
||||
func (i *versionedRegistry) ListAddon() ([]*UIData, error) {
|
||||
chartIndex, err := i.h.GetIndexInfo(i.url, false)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return i.resolveAddonListFromIndex(i.name, chartIndex), nil
|
||||
}
|
||||
|
||||
func (i *versionedRegistry) GetAddonUIData(ctx context.Context, addonName, version string) (*UIData, error) {
|
||||
wholePackage, err := i.loadAddon(ctx, addonName, version)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &UIData{
|
||||
Meta: wholePackage.Meta,
|
||||
APISchema: wholePackage.APISchema,
|
||||
Parameters: wholePackage.Parameters,
|
||||
Detail: wholePackage.Detail,
|
||||
Definitions: wholePackage.Definitions,
|
||||
AvailableVersions: wholePackage.AvailableVersions,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (i *versionedRegistry) GetAddonInstallPackage(ctx context.Context, addonName, version string) (*InstallPackage, error) {
|
||||
wholePackage, err := i.loadAddon(ctx, addonName, version)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &wholePackage.InstallPackage, nil
|
||||
}
|
||||
|
||||
func (i *versionedRegistry) resolveAddonListFromIndex(repoName string, index *repo.IndexFile) []*UIData {
|
||||
var res []*UIData
|
||||
for addonName, versions := range index.Entries {
|
||||
if len(versions) == 0 {
|
||||
continue
|
||||
}
|
||||
sort.Sort(sort.Reverse(versions))
|
||||
latestVersion := versions[0]
|
||||
var availableVersions []string
|
||||
for _, version := range versions {
|
||||
availableVersions = append(availableVersions, version.Version)
|
||||
}
|
||||
o := UIData{Meta: Meta{
|
||||
Name: addonName,
|
||||
Icon: latestVersion.Icon,
|
||||
Tags: latestVersion.Keywords,
|
||||
Description: latestVersion.Description,
|
||||
Version: latestVersion.Version,
|
||||
}, RegistryName: repoName, AvailableVersions: availableVersions}
|
||||
res = append(res, &o)
|
||||
}
|
||||
return res
|
||||
}
|
||||
|
||||
func (i versionedRegistry) loadAddon(ctx context.Context, name, version string) (*WholeAddonPackage, error) {
|
||||
versions, err := i.h.ListVersions(i.url, name, false)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if len(versions) == 0 {
|
||||
return nil, ErrNotExist
|
||||
}
|
||||
var addonVersion *repo.ChartVersion
|
||||
sort.Sort(sort.Reverse(versions))
|
||||
if len(version) == 0 {
|
||||
// if not specify version will always use the latest version
|
||||
addonVersion = versions[0]
|
||||
}
|
||||
var availableVersions []string
|
||||
for i, v := range versions {
|
||||
availableVersions = append(availableVersions, v.Version)
|
||||
if v.Version == version {
|
||||
addonVersion = versions[i]
|
||||
}
|
||||
}
|
||||
if addonVersion == nil {
|
||||
return nil, fmt.Errorf("specified version %s not exist", version)
|
||||
}
|
||||
for _, chartURL := range addonVersion.URLs {
|
||||
archive, err := common.HTTPGet(ctx, chartURL)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
bufferedFile, err := loader.LoadArchiveFiles(bytes.NewReader(archive))
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
addonPkg, err := loadAddonPackage(name, bufferedFile)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
addonPkg.AvailableVersions = availableVersions
|
||||
return addonPkg, nil
|
||||
}
|
||||
return nil, fmt.Errorf("cannot fetch addon package")
|
||||
}
|
||||
|
||||
func loadAddonPackage(addonName string, files []*loader.BufferedFile) (*WholeAddonPackage, error) {
|
||||
mr := MemoryReader{Name: addonName, Files: files}
|
||||
metas, err := mr.ListAddonMeta()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
meta := metas[addonName]
|
||||
addonUIData, err := GetUIDataFromReader(&mr, &meta, UIMetaOptions)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
installPackage, err := GetInstallPackageFromReader(&mr, &meta, addonUIData)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &WholeAddonPackage{
|
||||
InstallPackage: *installPackage,
|
||||
Detail: addonUIData.Detail,
|
||||
APISchema: addonUIData.APISchema,
|
||||
}, nil
|
||||
}
|
||||
76
pkg/addon/versioned_registry_test.go
Normal file
76
pkg/addon/versioned_registry_test.go
Normal file
@@ -0,0 +1,76 @@
|
||||
/*
|
||||
Copyright 2021 The KubeVela Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package addon
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"log"
|
||||
"net/http"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestVersionRegistry(t *testing.T) {
|
||||
go func() {
|
||||
http.HandleFunc("/", versionedHandler)
|
||||
err := http.ListenAndServe(fmt.Sprintf(":%d", 18083), nil)
|
||||
if err != nil {
|
||||
log.Fatal("Setup server error:", err)
|
||||
}
|
||||
}()
|
||||
// wait server setup
|
||||
time.Sleep(3 * time.Second)
|
||||
r := BuildVersionedRegistry("helm-repo", "http://127.0.0.1:18083")
|
||||
addons, err := r.ListAddon()
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, len(addons), 1)
|
||||
assert.Equal(t, addons[0].Name, "fluxcd")
|
||||
assert.Equal(t, len(addons[0].AvailableVersions), 1)
|
||||
|
||||
addonUIData, err := r.GetAddonUIData(context.Background(), "fluxcd", "1.0.0")
|
||||
assert.NoError(t, err)
|
||||
assert.NotEmpty(t, addonUIData.Definitions)
|
||||
assert.NotEmpty(t, addonUIData.Icon)
|
||||
|
||||
addonsInstallPackage, err := r.GetAddonInstallPackage(context.Background(), "fluxcd", "1.0.0")
|
||||
assert.NoError(t, err)
|
||||
assert.NotEmpty(t, addonsInstallPackage)
|
||||
assert.NotEmpty(t, addonsInstallPackage.YAMLTemplates)
|
||||
assert.NotEmpty(t, addonsInstallPackage.DefSchemas)
|
||||
}
|
||||
|
||||
var versionedHandler http.HandlerFunc = func(writer http.ResponseWriter, request *http.Request) {
|
||||
switch {
|
||||
case strings.Contains(request.URL.Path, "index.yaml"):
|
||||
files, err := ioutil.ReadFile("./testdata/helm-repo/index.yaml")
|
||||
if err != nil {
|
||||
_, _ = writer.Write([]byte(err.Error()))
|
||||
}
|
||||
writer.Write(files)
|
||||
case strings.Contains(request.URL.Path, "fluxcd-1.0.0.tgz"):
|
||||
files, err := ioutil.ReadFile("./testdata/helm-repo/fluxcd-1.0.0.tgz")
|
||||
if err != nil {
|
||||
_, _ = writer.Write([]byte(err.Error()))
|
||||
}
|
||||
writer.Write(files)
|
||||
}
|
||||
}
|
||||
@@ -17,6 +17,8 @@ limitations under the License.
|
||||
package clients
|
||||
|
||||
import (
|
||||
"errors"
|
||||
|
||||
"k8s.io/client-go/discovery"
|
||||
"k8s.io/client-go/rest"
|
||||
"sigs.k8s.io/controller-runtime/pkg/client"
|
||||
@@ -25,6 +27,7 @@ import (
|
||||
"github.com/oam-dev/kubevela/pkg/cue/packages"
|
||||
"github.com/oam-dev/kubevela/pkg/multicluster"
|
||||
"github.com/oam-dev/kubevela/pkg/oam/discoverymapper"
|
||||
"github.com/oam-dev/kubevela/pkg/utils/common"
|
||||
)
|
||||
|
||||
var kubeClient client.Client
|
||||
@@ -42,6 +45,18 @@ func GetKubeClient() (client.Client, error) {
|
||||
}
|
||||
var err error
|
||||
kubeClient, kubeConfig, err = multicluster.GetMulticlusterKubernetesClient()
|
||||
if err == nil {
|
||||
return kubeClient, nil
|
||||
}
|
||||
if !errors.Is(err, multicluster.ErrDetectClusterGateway) {
|
||||
return nil, err
|
||||
}
|
||||
// create single cluster client
|
||||
conf, err := config.GetConfig()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
kubeClient, err = client.New(conf, client.Options{Scheme: common.Scheme})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
@@ -73,6 +73,7 @@ type Entity interface {
|
||||
SetUpdateTime(time time.Time)
|
||||
PrimaryKey() string
|
||||
TableName() string
|
||||
ShortTableName() string
|
||||
Index() map[string]string
|
||||
}
|
||||
|
||||
@@ -111,9 +112,22 @@ type FuzzyQueryOption struct {
|
||||
Query string
|
||||
}
|
||||
|
||||
// InQueryOption defines the include search filter option
|
||||
type InQueryOption struct {
|
||||
Key string
|
||||
Values []string
|
||||
}
|
||||
|
||||
// IsNotExistQueryOption means the value is empty
|
||||
type IsNotExistQueryOption struct {
|
||||
Key string
|
||||
}
|
||||
|
||||
// FilterOptions filter query returned items
|
||||
type FilterOptions struct {
|
||||
Queries []FuzzyQueryOption
|
||||
Queries []FuzzyQueryOption
|
||||
In []InQueryOption
|
||||
IsNotExist []IsNotExistQueryOption
|
||||
}
|
||||
|
||||
// ListOptions list api options
|
||||
@@ -141,7 +155,7 @@ type DataStore interface {
|
||||
// Get entity from database, Name() and TableName() can't return zero value.
|
||||
Get(ctx context.Context, entity Entity) error
|
||||
|
||||
// List entities from database, TableName() can't return zero value.
|
||||
// List entities from database, TableName() can't return zero value, if no matches, it will return a zero list without error.
|
||||
List(ctx context.Context, query Entity, options *ListOptions) ([]Entity, error)
|
||||
|
||||
// Count entities from database, TableName() can't return zero value.
|
||||
|
||||
@@ -64,6 +64,7 @@ func New(ctx context.Context, cfg datastore.Config) (datastore.DataStore, error)
|
||||
return nil, fmt.Errorf("create namespace failure %w", err)
|
||||
}
|
||||
}
|
||||
migrate(cfg.Database)
|
||||
return &kubeapi{
|
||||
kubeclient: kubeClient,
|
||||
namespace: cfg.Database,
|
||||
@@ -71,7 +72,9 @@ func New(ctx context.Context, cfg datastore.Config) (datastore.DataStore, error)
|
||||
}
|
||||
|
||||
func generateName(entity datastore.Entity) string {
|
||||
name := fmt.Sprintf("veladatabase-%s-%s", entity.TableName(), entity.PrimaryKey())
|
||||
// record the old ways here, it'll be migrated
|
||||
// name := fmt.Sprintf("veladatabase-%s-%s", entity.TableName(), entity.PrimaryKey())
|
||||
name := fmt.Sprintf("%s-%s", entity.ShortTableName(), entity.PrimaryKey())
|
||||
return strings.ReplaceAll(name, "_", "-")
|
||||
}
|
||||
|
||||
@@ -117,23 +120,23 @@ func (m *kubeapi) Add(ctx context.Context, entity datastore.Entity) error {
|
||||
}
|
||||
|
||||
// BatchAdd batch add entity, this operation has some atomicity.
|
||||
func (m *kubeapi) BatchAdd(ctx context.Context, entitys []datastore.Entity) error {
|
||||
donotRollback := make(map[string]int)
|
||||
for i, saveEntity := range entitys {
|
||||
func (m *kubeapi) BatchAdd(ctx context.Context, entities []datastore.Entity) error {
|
||||
notRollback := make(map[string]int)
|
||||
for i, saveEntity := range entities {
|
||||
if err := m.Add(ctx, saveEntity); err != nil {
|
||||
if errors.Is(err, datastore.ErrRecordExist) {
|
||||
donotRollback[saveEntity.PrimaryKey()] = 1
|
||||
notRollback[saveEntity.PrimaryKey()] = 1
|
||||
}
|
||||
for _, deleteEntity := range entitys[:i] {
|
||||
if _, exit := donotRollback[deleteEntity.PrimaryKey()]; !exit {
|
||||
for _, deleteEntity := range entities[:i] {
|
||||
if _, exit := notRollback[deleteEntity.PrimaryKey()]; !exit {
|
||||
if err := m.Delete(ctx, deleteEntity); err != nil {
|
||||
if !errors.Is(err, datastore.ErrRecordNotExist) {
|
||||
log.Logger.Errorf("rollback delete component failure %w", err)
|
||||
log.Logger.Errorf("rollback delete entity failure %w", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return datastore.NewDBError(fmt.Errorf("save components occur error, %w", err))
|
||||
return datastore.NewDBError(fmt.Errorf("save entities occur error, %w", err))
|
||||
}
|
||||
}
|
||||
return nil
|
||||
@@ -327,7 +330,7 @@ func _filterConfigMapByFuzzyQueryOptions(items []corev1.ConfigMap, queries []dat
|
||||
return _items
|
||||
}
|
||||
|
||||
// TableName() can't return zero value.
|
||||
// List will list all database records by select labels according to table name
|
||||
func (m *kubeapi) List(ctx context.Context, entity datastore.Entity, op *datastore.ListOptions) ([]datastore.Entity, error) {
|
||||
if entity.TableName() == "" {
|
||||
return nil, datastore.ErrTableNameEmpty
|
||||
@@ -337,6 +340,10 @@ func (m *kubeapi) List(ctx context.Context, entity datastore.Entity, op *datasto
|
||||
if err != nil {
|
||||
return nil, datastore.NewDBError(err)
|
||||
}
|
||||
|
||||
rq, _ := labels.NewRequirement(MigrateKey, selection.DoesNotExist, []string{"ok"})
|
||||
selector = selector.Add(*rq)
|
||||
|
||||
for k, v := range entity.Index() {
|
||||
rq, err := labels.NewRequirement(k, selection.Equals, []string{v})
|
||||
if err != nil {
|
||||
@@ -344,6 +351,24 @@ func (m *kubeapi) List(ctx context.Context, entity datastore.Entity, op *datasto
|
||||
}
|
||||
selector = selector.Add(*rq)
|
||||
}
|
||||
if op != nil {
|
||||
for _, inFilter := range op.In {
|
||||
rq, err := labels.NewRequirement(inFilter.Key, selection.In, inFilter.Values)
|
||||
if err != nil {
|
||||
log.Logger.Errorf("new list requirement failure %s", err.Error())
|
||||
return nil, datastore.ErrIndexInvalid
|
||||
}
|
||||
selector = selector.Add(*rq)
|
||||
}
|
||||
for _, notFilter := range op.IsNotExist {
|
||||
rq, err := labels.NewRequirement(notFilter.Key, selection.DoesNotExist, []string{})
|
||||
if err != nil {
|
||||
log.Logger.Errorf("new list requirement failure %s", err.Error())
|
||||
return nil, datastore.ErrIndexInvalid
|
||||
}
|
||||
selector = selector.Add(*rq)
|
||||
}
|
||||
}
|
||||
options := &client.ListOptions{
|
||||
LabelSelector: selector,
|
||||
Namespace: m.namespace,
|
||||
@@ -382,7 +407,6 @@ func (m *kubeapi) List(ctx context.Context, entity datastore.Entity, op *datasto
|
||||
items = items[:limit]
|
||||
}
|
||||
var list []datastore.Entity
|
||||
log.Logger.Debugf("query %s result count %d", selector, len(items))
|
||||
for _, item := range items {
|
||||
ent, err := datastore.NewEntity(entity)
|
||||
if err != nil {
|
||||
@@ -413,6 +437,24 @@ func (m *kubeapi) Count(ctx context.Context, entity datastore.Entity, filterOpti
|
||||
}
|
||||
selector = selector.Add(*rq)
|
||||
}
|
||||
if filterOptions != nil {
|
||||
for _, inFilter := range filterOptions.In {
|
||||
rq, err := labels.NewRequirement(inFilter.Key, selection.In, inFilter.Values)
|
||||
if err != nil {
|
||||
return 0, datastore.ErrIndexInvalid
|
||||
}
|
||||
selector = selector.Add(*rq)
|
||||
}
|
||||
for _, notFilter := range filterOptions.IsNotExist {
|
||||
rq, err := labels.NewRequirement(notFilter.Key, selection.DoesNotExist, []string{})
|
||||
if err != nil {
|
||||
log.Logger.Errorf("new list requirement failure %s", err.Error())
|
||||
return 0, datastore.ErrIndexInvalid
|
||||
}
|
||||
selector = selector.Add(*rq)
|
||||
}
|
||||
}
|
||||
|
||||
options := &client.ListOptions{
|
||||
LabelSelector: selector,
|
||||
Namespace: m.namespace,
|
||||
|
||||
@@ -96,7 +96,7 @@ var _ = Describe("Test kubeapi datastore driver", func() {
|
||||
var datas = []datastore.Entity{
|
||||
&model.Application{Name: "kubevela-app-2", Description: "this is demo 2"},
|
||||
&model.Application{Name: "kubevela-app-3", Description: "this is demo 3"},
|
||||
&model.Application{Name: "kubevela-app-4", Description: "this is demo 4"},
|
||||
&model.Application{Name: "kubevela-app-4", Project: "testProject", Description: "this is demo 4"},
|
||||
}
|
||||
err := kubeStore.BatchAdd(context.TODO(), datas)
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
@@ -106,7 +106,7 @@ var _ = Describe("Test kubeapi datastore driver", func() {
|
||||
&model.Application{Name: "kubevela-app-2", Description: "this is demo 2"},
|
||||
}
|
||||
err = kubeStore.BatchAdd(context.TODO(), datas2)
|
||||
equal := cmp.Diff(strings.Contains(err.Error(), "save components occur error"), true)
|
||||
equal := cmp.Diff(strings.Contains(err.Error(), "save entities occur error"), true)
|
||||
Expect(equal).To(BeEmpty())
|
||||
})
|
||||
|
||||
@@ -157,6 +157,26 @@ var _ = Describe("Test kubeapi datastore driver", func() {
|
||||
Expect(err).ShouldNot(HaveOccurred())
|
||||
diff = cmp.Diff(len(list), 4)
|
||||
Expect(diff).Should(BeEmpty())
|
||||
|
||||
list, err = kubeStore.List(context.TODO(), &app, &datastore.ListOptions{FilterOptions: datastore.FilterOptions{In: []datastore.InQueryOption{
|
||||
{
|
||||
Key: "name",
|
||||
Values: []string{"kubevela-app-3", "kubevela-app-2"},
|
||||
},
|
||||
}}})
|
||||
Expect(err).ShouldNot(HaveOccurred())
|
||||
diff = cmp.Diff(len(list), 2)
|
||||
Expect(diff).Should(BeEmpty())
|
||||
|
||||
list, err = kubeStore.List(context.TODO(), &app, &datastore.ListOptions{FilterOptions: datastore.FilterOptions{IsNotExist: []datastore.IsNotExistQueryOption{
|
||||
{
|
||||
Key: "project",
|
||||
},
|
||||
}}})
|
||||
Expect(err).ShouldNot(HaveOccurred())
|
||||
diff = cmp.Diff(len(list), 3)
|
||||
Expect(diff).Should(BeEmpty())
|
||||
|
||||
})
|
||||
|
||||
It("Test list clusters with sort and fuzzy query", func() {
|
||||
@@ -221,6 +241,23 @@ var _ = Describe("Test kubeapi datastore driver", func() {
|
||||
})
|
||||
Expect(err).Should(Succeed())
|
||||
Expect(count).Should(Equal(int64(2)))
|
||||
|
||||
count, err = kubeStore.Count(context.TODO(), &app, &datastore.FilterOptions{In: []datastore.InQueryOption{
|
||||
{
|
||||
Key: "name",
|
||||
Values: []string{"kubevela-app-3", "kubevela-app-2"},
|
||||
},
|
||||
}})
|
||||
Expect(err).ShouldNot(HaveOccurred())
|
||||
Expect(count).Should(Equal(int64(2)))
|
||||
|
||||
count, err = kubeStore.Count(context.TODO(), &app, &datastore.FilterOptions{IsNotExist: []datastore.IsNotExistQueryOption{
|
||||
{
|
||||
Key: "project",
|
||||
},
|
||||
}})
|
||||
Expect(err).ShouldNot(HaveOccurred())
|
||||
Expect(count).Should(Equal(int64(3)))
|
||||
})
|
||||
|
||||
It("Test isExist function", func() {
|
||||
|
||||
88
pkg/apiserver/datastore/kubeapi/migrate.go
Normal file
88
pkg/apiserver/datastore/kubeapi/migrate.go
Normal file
@@ -0,0 +1,88 @@
|
||||
/*
|
||||
Copyright 2022 The KubeVela Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package kubeapi
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
"k8s.io/apimachinery/pkg/labels"
|
||||
"k8s.io/klog"
|
||||
"sigs.k8s.io/controller-runtime/pkg/client"
|
||||
|
||||
"github.com/oam-dev/kubevela/pkg/apiserver/clients"
|
||||
"github.com/oam-dev/kubevela/pkg/apiserver/model"
|
||||
)
|
||||
|
||||
// MigrateKey marks the label key of the migrated data
|
||||
const MigrateKey = "db.oam.dev/migrated"
|
||||
|
||||
// migrate will migrate the configmap to new short table name, it won't delete the configmaps:
|
||||
// users can delete by the following commands:
|
||||
// kubectl -n kubevela delete cm -l db.oam.dev/migrated=ok
|
||||
func migrate(dbns string) {
|
||||
kubeClient, err := clients.GetKubeClient()
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
models := model.GetRegisterModels()
|
||||
for _, k := range models {
|
||||
var configMaps corev1.ConfigMapList
|
||||
table := k.TableName()
|
||||
selector, _ := labels.Parse(fmt.Sprintf("table=%s", table))
|
||||
if err = kubeClient.List(context.Background(), &configMaps, &client.ListOptions{Namespace: dbns, LabelSelector: selector}); err != nil {
|
||||
err = client.IgnoreNotFound(err)
|
||||
if err != nil {
|
||||
klog.Errorf("migrate db for kubeapi storage err: %v", err)
|
||||
continue
|
||||
}
|
||||
}
|
||||
var migrated bool
|
||||
for _, cm := range configMaps.Items {
|
||||
if strings.HasPrefix(cm.Name, strings.ReplaceAll(k.ShortTableName()+"-", "_", "-")) {
|
||||
migrated = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if migrated || len(configMaps.Items) == 0 {
|
||||
continue
|
||||
}
|
||||
klog.Infof("migrating data for table %v", k.TableName())
|
||||
for _, cm := range configMaps.Items {
|
||||
cm := cm
|
||||
checkprefix := strings.ReplaceAll(fmt.Sprintf("veladatabase-%s", k.TableName()), "_", "-")
|
||||
if !strings.HasPrefix(cm.Name, checkprefix) {
|
||||
continue
|
||||
}
|
||||
|
||||
cm.Labels[MigrateKey] = "ok"
|
||||
err = kubeClient.Update(context.Background(), &cm)
|
||||
if err != nil {
|
||||
klog.Errorf("update migrated record %s for kubeapi storage err: %v", cm.Name, err)
|
||||
}
|
||||
cm.Name = strings.ReplaceAll(k.ShortTableName()+strings.TrimPrefix(cm.Name, checkprefix), "_", "-")
|
||||
cm.ResourceVersion = ""
|
||||
delete(cm.Labels, MigrateKey)
|
||||
err = kubeClient.Create(context.Background(), &cm)
|
||||
if err != nil {
|
||||
klog.Errorf("migrate record %s for kubeapi storage err: %v", cm.Name, err)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
60
pkg/apiserver/datastore/kubeapi/migrate_test.go
Normal file
60
pkg/apiserver/datastore/kubeapi/migrate_test.go
Normal file
@@ -0,0 +1,60 @@
|
||||
/*
|
||||
Copyright 2022 The KubeVela Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package kubeapi
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
. "github.com/onsi/ginkgo"
|
||||
. "github.com/onsi/gomega"
|
||||
v1 "k8s.io/api/core/v1"
|
||||
"sigs.k8s.io/controller-runtime/pkg/client"
|
||||
|
||||
"github.com/oam-dev/kubevela/pkg/apiserver/clients"
|
||||
"github.com/oam-dev/kubevela/pkg/apiserver/model"
|
||||
)
|
||||
|
||||
var _ = Describe("Test Migrate", func() {
|
||||
|
||||
It("Test migrate labels", func() {
|
||||
clients.SetKubeClient(k8sClient)
|
||||
|
||||
nsName := "test-migrate"
|
||||
ds := &kubeapi{kubeclient: k8sClient, namespace: nsName}
|
||||
ns := &v1.Namespace{}
|
||||
ns.Name = nsName
|
||||
Expect(k8sClient.Create(context.Background(), ns)).Should(BeNil())
|
||||
entity := &model.Application{Name: "my-app"}
|
||||
cm := ds.generateConfigMap(entity)
|
||||
name := fmt.Sprintf("veladatabase-%s-%s", entity.TableName(), entity.PrimaryKey())
|
||||
cm.Name = strings.ReplaceAll(name, "_", "-")
|
||||
cm.Namespace = nsName
|
||||
Expect(ds.kubeclient.Create(context.Background(), cm)).Should(BeNil())
|
||||
|
||||
migrate(nsName)
|
||||
cmList := v1.ConfigMapList{}
|
||||
Expect(k8sClient.List(context.Background(), &cmList, client.InNamespace(nsName))).Should(BeNil())
|
||||
Expect(len(cmList.Items)).Should(BeEquivalentTo(2))
|
||||
|
||||
es, err := ds.List(context.Background(), &model.Application{}, nil)
|
||||
Expect(err).Should(BeNil())
|
||||
Expect(len(es)).Should(BeEquivalentTo(1))
|
||||
})
|
||||
|
||||
})
|
||||
@@ -84,23 +84,23 @@ func (m *mongodb) Add(ctx context.Context, entity datastore.Entity) error {
|
||||
}
|
||||
|
||||
// BatchAdd batch add entity, this operation has some atomicity.
|
||||
func (m *mongodb) BatchAdd(ctx context.Context, entitys []datastore.Entity) error {
|
||||
donotRollback := make(map[string]int)
|
||||
for i, saveEntity := range entitys {
|
||||
func (m *mongodb) BatchAdd(ctx context.Context, entities []datastore.Entity) error {
|
||||
notRollback := make(map[string]int)
|
||||
for i, saveEntity := range entities {
|
||||
if err := m.Add(ctx, saveEntity); err != nil {
|
||||
if errors.Is(err, datastore.ErrRecordExist) {
|
||||
donotRollback[saveEntity.PrimaryKey()] = 1
|
||||
notRollback[saveEntity.PrimaryKey()] = 1
|
||||
}
|
||||
for _, deleteEntity := range entitys[:i] {
|
||||
if _, exit := donotRollback[deleteEntity.PrimaryKey()]; !exit {
|
||||
for _, deleteEntity := range entities[:i] {
|
||||
if _, exit := notRollback[deleteEntity.PrimaryKey()]; !exit {
|
||||
if err := m.Delete(ctx, deleteEntity); err != nil {
|
||||
if !errors.Is(err, datastore.ErrRecordNotExist) {
|
||||
log.Logger.Errorf("rollback delete component failure %w", err)
|
||||
log.Logger.Errorf("rollback delete entity failure %w", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return datastore.NewDBError(fmt.Errorf("save components occur error, %w", err))
|
||||
return datastore.NewDBError(fmt.Errorf("save entities occur error, %w", err))
|
||||
}
|
||||
}
|
||||
return nil
|
||||
@@ -192,10 +192,14 @@ func (m *mongodb) Delete(ctx context.Context, entity datastore.Entity) error {
|
||||
}
|
||||
|
||||
func _applyFilterOptions(filter bson.D, filterOptions datastore.FilterOptions) bson.D {
|
||||
if len(filterOptions.Queries) > 0 {
|
||||
for _, queryOp := range filterOptions.Queries {
|
||||
filter = append(filter, bson.E{Key: strings.ToLower(queryOp.Key), Value: bsonx.Regex(".*"+queryOp.Query+".*", "s")})
|
||||
}
|
||||
for _, queryOp := range filterOptions.Queries {
|
||||
filter = append(filter, bson.E{Key: strings.ToLower(queryOp.Key), Value: bsonx.Regex(".*"+queryOp.Query+".*", "s")})
|
||||
}
|
||||
for _, queryOp := range filterOptions.In {
|
||||
filter = append(filter, bson.E{Key: strings.ToLower(queryOp.Key), Value: bson.D{bson.E{Key: "$in", Value: queryOp.Values}}})
|
||||
}
|
||||
for _, queryOp := range filterOptions.IsNotExist {
|
||||
filter = append(filter, bson.E{Key: strings.ToLower(queryOp.Key), Value: bson.D{bson.E{Key: "$eq", Value: ""}}})
|
||||
}
|
||||
return filter
|
||||
}
|
||||
@@ -216,7 +220,7 @@ func (m *mongodb) List(ctx context.Context, entity datastore.Entity, op *datasto
|
||||
})
|
||||
}
|
||||
}
|
||||
if op != nil && len(op.Queries) > 0 {
|
||||
if op != nil {
|
||||
filter = _applyFilterOptions(filter, op.FilterOptions)
|
||||
}
|
||||
var findOptions options.FindOptions
|
||||
@@ -276,7 +280,7 @@ func (m *mongodb) Count(ctx context.Context, entity datastore.Entity, filterOpti
|
||||
})
|
||||
}
|
||||
}
|
||||
if filterOptions != nil && len(filterOptions.Queries) > 0 {
|
||||
if filterOptions != nil {
|
||||
filter = _applyFilterOptions(filter, *filterOptions)
|
||||
}
|
||||
count, err := collection.CountDocuments(ctx, filter)
|
||||
|
||||
@@ -70,7 +70,7 @@ var _ = Describe("Test mongodb datastore driver", func() {
|
||||
var datas = []datastore.Entity{
|
||||
&model.Application{Name: "kubevela-app-2", Description: "this is demo 2"},
|
||||
&model.Application{Name: "kubevela-app-3", Description: "this is demo 3"},
|
||||
&model.Application{Name: "kubevela-app-4", Description: "this is demo 4"},
|
||||
&model.Application{Name: "kubevela-app-4", Project: "test-project", Description: "this is demo 4"},
|
||||
&model.Workflow{Name: "kubevela-app-workflow", AppPrimaryKey: "kubevela-app-2", Description: "this is workflow"},
|
||||
&model.ApplicationTrigger{Name: "kubevela-app-trigger", AppPrimaryKey: "kubevela-app-2", Token: "token-test", Description: "this is demo 4"},
|
||||
}
|
||||
@@ -82,7 +82,7 @@ var _ = Describe("Test mongodb datastore driver", func() {
|
||||
&model.Application{Name: "kubevela-app-2", Description: "this is demo 2"},
|
||||
}
|
||||
err = mongodbDriver.BatchAdd(context.TODO(), datas2)
|
||||
equal := cmp.Diff(strings.Contains(err.Error(), "save components occur error"), true)
|
||||
equal := cmp.Diff(strings.Contains(err.Error(), "save entities occur error"), true)
|
||||
Expect(equal).To(BeEmpty())
|
||||
})
|
||||
|
||||
@@ -133,6 +133,25 @@ var _ = Describe("Test mongodb datastore driver", func() {
|
||||
Expect(err).ShouldNot(HaveOccurred())
|
||||
diff = cmp.Diff(len(list), 1)
|
||||
Expect(diff).Should(BeEmpty())
|
||||
|
||||
list, err = mongodbDriver.List(context.TODO(), &app, &datastore.ListOptions{FilterOptions: datastore.FilterOptions{In: []datastore.InQueryOption{
|
||||
{
|
||||
Key: "name",
|
||||
Values: []string{"kubevela-app-3", "kubevela-app-2"},
|
||||
},
|
||||
}}})
|
||||
Expect(err).ShouldNot(HaveOccurred())
|
||||
diff = cmp.Diff(len(list), 2)
|
||||
Expect(diff).Should(BeEmpty())
|
||||
|
||||
list, err = mongodbDriver.List(context.TODO(), &app, &datastore.ListOptions{FilterOptions: datastore.FilterOptions{IsNotExist: []datastore.IsNotExistQueryOption{
|
||||
{
|
||||
Key: "project",
|
||||
},
|
||||
}}})
|
||||
Expect(err).ShouldNot(HaveOccurred())
|
||||
diff = cmp.Diff(len(list), 3)
|
||||
Expect(diff).Should(BeEmpty())
|
||||
})
|
||||
|
||||
It("Test list clusters with sort and fuzzy query", func() {
|
||||
@@ -196,6 +215,23 @@ var _ = Describe("Test mongodb datastore driver", func() {
|
||||
})
|
||||
Expect(err).Should(Succeed())
|
||||
Expect(count).Should(Equal(int64(2)))
|
||||
|
||||
count, err = mongodbDriver.Count(context.TODO(), &app, &datastore.FilterOptions{In: []datastore.InQueryOption{
|
||||
{
|
||||
Key: "name",
|
||||
Values: []string{"kubevela-app-3", "kubevela-app-2"},
|
||||
},
|
||||
}})
|
||||
Expect(err).ShouldNot(HaveOccurred())
|
||||
Expect(count).Should(Equal(int64(2)))
|
||||
|
||||
count, err = mongodbDriver.Count(context.TODO(), &app, &datastore.FilterOptions{IsNotExist: []datastore.IsNotExistQueryOption{
|
||||
{
|
||||
Key: "project",
|
||||
},
|
||||
}})
|
||||
Expect(err).ShouldNot(HaveOccurred())
|
||||
Expect(count).Should(Equal(int64(3)))
|
||||
})
|
||||
|
||||
It("Test isExist function", func() {
|
||||
|
||||
@@ -18,13 +18,14 @@ package model
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/oam-dev/kubevela/apis/core.oam.dev/common"
|
||||
)
|
||||
|
||||
func init() {
|
||||
RegistModel(&ApplicationComponent{}, &ApplicationPolicy{}, &Application{}, &ApplicationRevision{}, &ApplicationTrigger{})
|
||||
RegisterModel(&ApplicationComponent{}, &ApplicationPolicy{}, &Application{}, &ApplicationRevision{}, &ApplicationTrigger{})
|
||||
}
|
||||
|
||||
// Application application delivery model
|
||||
@@ -43,7 +44,15 @@ func (a *Application) TableName() string {
|
||||
return tableNamePrefix + "application"
|
||||
}
|
||||
|
||||
// ShortTableName is the compressed version of table name for kubeapi storage and others
|
||||
func (a *Application) ShortTableName() string {
|
||||
return "app"
|
||||
}
|
||||
|
||||
// PrimaryKey return custom primary key
|
||||
// the app primary key is the app name, so the app name is globally unique in every namespace
|
||||
// when the app is synced from CR, the first synced one be same with app name,
|
||||
// if there's any conflicts, the name will be composed by <appname>-<namespace>
|
||||
func (a *Application) PrimaryKey() string {
|
||||
return a.Name
|
||||
}
|
||||
@@ -60,6 +69,38 @@ func (a *Application) Index() map[string]string {
|
||||
return index
|
||||
}
|
||||
|
||||
// GetAppNameForSynced will trim namespace suffix for synced CR
|
||||
func (a *Application) GetAppNameForSynced() string {
|
||||
if a.Labels == nil {
|
||||
return a.Name
|
||||
}
|
||||
namespace := a.Labels[LabelSyncNamespace]
|
||||
if namespace == "" {
|
||||
return a.Name
|
||||
}
|
||||
return strings.TrimSuffix(a.Name, "-"+namespace)
|
||||
}
|
||||
|
||||
// GetAppNamespaceForSynced will return the namespace of synced CR
|
||||
func (a *Application) GetAppNamespaceForSynced() string {
|
||||
if a.Labels == nil {
|
||||
return ""
|
||||
}
|
||||
return a.Labels[LabelSyncNamespace]
|
||||
}
|
||||
|
||||
// IsSynced answer if the app is synced one
|
||||
func (a *Application) IsSynced() bool {
|
||||
if a.Labels == nil {
|
||||
return false
|
||||
}
|
||||
sot := a.Labels[LabelSourceOfTruth]
|
||||
if sot == FromCR || sot == FromInner {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// ClusterSelector cluster selector
|
||||
type ClusterSelector struct {
|
||||
Name string `json:"name"`
|
||||
@@ -102,6 +143,11 @@ func (a *ApplicationComponent) TableName() string {
|
||||
return tableNamePrefix + "application_component"
|
||||
}
|
||||
|
||||
// ShortTableName is the compressed version of table name for kubeapi storage and others
|
||||
func (a *ApplicationComponent) ShortTableName() string {
|
||||
return "app_cmp"
|
||||
}
|
||||
|
||||
// PrimaryKey return custom primary key
|
||||
func (a *ApplicationComponent) PrimaryKey() string {
|
||||
return fmt.Sprintf("%s-%s", a.AppPrimaryKey, a.Name)
|
||||
@@ -138,6 +184,11 @@ func (a *ApplicationPolicy) TableName() string {
|
||||
return tableNamePrefix + "application_policy"
|
||||
}
|
||||
|
||||
// ShortTableName is the compressed version of table name for kubeapi storage and others
|
||||
func (a *ApplicationPolicy) ShortTableName() string {
|
||||
return "app_plc"
|
||||
}
|
||||
|
||||
// PrimaryKey return custom primary key
|
||||
func (a *ApplicationPolicy) PrimaryKey() string {
|
||||
return fmt.Sprintf("%s-%s", a.AppPrimaryKey, a.Name)
|
||||
@@ -270,6 +321,11 @@ func (a *ApplicationRevision) TableName() string {
|
||||
return tableNamePrefix + "application_revision"
|
||||
}
|
||||
|
||||
// ShortTableName is the compressed version of table name for kubeapi storage and others
|
||||
func (a *ApplicationRevision) ShortTableName() string {
|
||||
return "app_rev"
|
||||
}
|
||||
|
||||
// PrimaryKey return custom primary key
|
||||
func (a *ApplicationRevision) PrimaryKey() string {
|
||||
return fmt.Sprintf("%s-%s", a.AppPrimaryKey, a.Version)
|
||||
@@ -349,6 +405,11 @@ func (w *ApplicationTrigger) TableName() string {
|
||||
return tableNamePrefix + "trigger"
|
||||
}
|
||||
|
||||
// ShortTableName is the compressed version of table name for kubeapi storage and others
|
||||
func (w *ApplicationTrigger) ShortTableName() string {
|
||||
return "app_tg"
|
||||
}
|
||||
|
||||
// PrimaryKey return custom primary key
|
||||
func (w *ApplicationTrigger) PrimaryKey() string {
|
||||
return w.Token
|
||||
|
||||
@@ -23,7 +23,7 @@ import (
|
||||
)
|
||||
|
||||
func init() {
|
||||
RegistModel(&Cluster{})
|
||||
RegisterModel(&Cluster{})
|
||||
}
|
||||
|
||||
// ProviderInfo describes the information from provider API
|
||||
@@ -82,6 +82,11 @@ func (c *Cluster) TableName() string {
|
||||
return tableNamePrefix + "cluster"
|
||||
}
|
||||
|
||||
// ShortTableName is the compressed version of table name for kubeapi storage and others
|
||||
func (c *Cluster) ShortTableName() string {
|
||||
return "cls"
|
||||
}
|
||||
|
||||
// PrimaryKey primary key for datastore
|
||||
func (c *Cluster) PrimaryKey() string {
|
||||
return c.Name
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user