Feat: takeover & readonly policy (#5102)

* Feat: takeover & readonly

Signed-off-by: Somefive <yd219913@alibaba-inc.com>

* Feat: add tests

Signed-off-by: Somefive <yd219913@alibaba-inc.com>

* Feat: add cue def for read-only and take-over

Signed-off-by: Somefive <yd219913@alibaba-inc.com>

* Docs: add example doc

Signed-off-by: Somefive <yd219913@alibaba-inc.com>

Signed-off-by: Somefive <yd219913@alibaba-inc.com>
This commit is contained in:
Somefive
2022-11-24 09:48:27 +08:00
committed by GitHub
parent 277d94f447
commit 734025f03f
32 changed files with 817 additions and 163 deletions
@@ -59,8 +59,13 @@ type ApplyOnceStrategy struct {
ApplyOnceAffectStrategy ApplyOnceAffectStrategy `json:"affect"`
}
// Type the type name of the policy
func (in *ApplyOncePolicySpec) Type() string {
return ApplyOncePolicyType
}
// FindStrategy find apply-once strategy for target resource
func (in ApplyOncePolicySpec) FindStrategy(manifest *unstructured.Unstructured) *ApplyOnceStrategy {
func (in *ApplyOncePolicySpec) FindStrategy(manifest *unstructured.Unstructured) *ApplyOnceStrategy {
if !in.Enable {
return nil
}
@@ -18,10 +18,6 @@ package v1alpha1
import (
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
"k8s.io/utils/pointer"
"k8s.io/utils/strings/slices"
"github.com/oam-dev/kubevela/pkg/oam"
)
const (
@@ -57,59 +53,6 @@ type GarbageCollectPolicyRule struct {
Strategy GarbageCollectStrategy `json:"strategy"`
}
// ResourcePolicyRuleSelector select the targets of the rule
// if multiple conditions are specified, combination logic is AND
type ResourcePolicyRuleSelector struct {
CompNames []string `json:"componentNames,omitempty"`
CompTypes []string `json:"componentTypes,omitempty"`
OAMResourceTypes []string `json:"oamTypes,omitempty"`
TraitTypes []string `json:"traitTypes,omitempty"`
ResourceTypes []string `json:"resourceTypes,omitempty"`
ResourceNames []string `json:"resourceNames,omitempty"`
}
// Match check if current rule selector match the target resource
// If at least one condition is matched and no other condition failed (could be empty), return true
// Otherwise, return false
func (in *ResourcePolicyRuleSelector) Match(manifest *unstructured.Unstructured) bool {
var compName, compType, oamType, traitType, resourceType, resourceName string
if labels := manifest.GetLabels(); labels != nil {
compName = labels[oam.LabelAppComponent]
compType = labels[oam.WorkloadTypeLabel]
oamType = labels[oam.LabelOAMResourceType]
traitType = labels[oam.TraitTypeLabel]
}
resourceType = manifest.GetKind()
resourceName = manifest.GetName()
match := func(src []string, val string) (found *bool) {
if len(src) == 0 {
return nil
}
return pointer.Bool(val != "" && slices.Contains(src, val))
}
conditions := []*bool{
match(in.CompNames, compName),
match(in.CompTypes, compType),
match(in.OAMResourceTypes, oamType),
match(in.TraitTypes, traitType),
match(in.ResourceTypes, resourceType),
match(in.ResourceNames, resourceName),
}
hasMatched := false
for _, cond := range conditions {
// if any non-empty condition failed, return false
if cond != nil && !*cond {
return false
}
// if condition succeed, record it
if cond != nil && *cond {
hasMatched = true
}
}
// if at least one condition is met, return true
return hasMatched
}
// GarbageCollectStrategy the strategy for target resource to recycle
type GarbageCollectStrategy string
@@ -123,8 +66,13 @@ const (
GarbageCollectStrategyOnAppUpdate GarbageCollectStrategy = "onAppUpdate"
)
// Type the type name of the policy
func (in *GarbageCollectPolicySpec) Type() string {
return GarbageCollectPolicyType
}
// FindStrategy find gc strategy for target resource
func (in GarbageCollectPolicySpec) FindStrategy(manifest *unstructured.Unstructured) *GarbageCollectStrategy {
func (in *GarbageCollectPolicySpec) FindStrategy(manifest *unstructured.Unstructured) *GarbageCollectStrategy {
for _, rule := range in.Rules {
if rule.Selector.Match(manifest) {
return &rule.Strategy
@@ -16,8 +16,6 @@ limitations under the License.
package v1alpha1
import "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
const (
// TopologyPolicyType refers to the type of topology policy
TopologyPolicyType = "topology"
@@ -25,8 +23,6 @@ const (
OverridePolicyType = "override"
// DebugPolicyType refers to the type of debug policy
DebugPolicyType = "debug"
// SharedResourcePolicyType refers to the type of shared resource policy
SharedResourcePolicyType = "shared-resource"
// ReplicationPolicyType refers to the type of replication policy
ReplicationPolicyType = "replication"
)
@@ -64,26 +60,6 @@ type OverridePolicySpec struct {
Selector []string `json:"selector,omitempty"`
}
// SharedResourcePolicySpec defines the spec of shared-resource policy
type SharedResourcePolicySpec struct {
Rules []SharedResourcePolicyRule `json:"rules"`
}
// SharedResourcePolicyRule defines the rule for sharing resources
type SharedResourcePolicyRule struct {
Selector ResourcePolicyRuleSelector `json:"selector"`
}
// FindStrategy return if the target resource should be shared
func (in SharedResourcePolicySpec) FindStrategy(manifest *unstructured.Unstructured) bool {
for _, rule := range in.Rules {
if rule.Selector.Match(manifest) {
return true
}
}
return false
}
// ReplicationPolicySpec defines the spec of replication policy
// Override policy should be used together with replication policy to select the deployment target components
type ReplicationPolicySpec struct {
@@ -0,0 +1,49 @@
/*
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 v1alpha1
import "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
const (
// ReadOnlyPolicyType refers to the type of read-only policy
ReadOnlyPolicyType = "read-only"
)
// ReadOnlyPolicySpec defines the spec of read-only policy
type ReadOnlyPolicySpec struct {
Rules []ReadOnlyPolicyRule `json:"rules"`
}
// Type the type name of the policy
func (in *ReadOnlyPolicySpec) Type() string {
return ReadOnlyPolicyType
}
// ReadOnlyPolicyRule defines the rule for read-only resources
type ReadOnlyPolicyRule struct {
Selector ResourcePolicyRuleSelector `json:"selector"`
}
// FindStrategy return if the target resource is read-only
func (in *ReadOnlyPolicySpec) FindStrategy(manifest *unstructured.Unstructured) bool {
for _, rule := range in.Rules {
if rule.Selector.Match(manifest) {
return true
}
}
return false
}
@@ -0,0 +1,78 @@
/*
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 v1alpha1
import (
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
"k8s.io/utils/pointer"
stringslices "k8s.io/utils/strings/slices"
"github.com/oam-dev/kubevela/pkg/oam"
)
// ResourcePolicyRuleSelector select the targets of the rule
// if multiple conditions are specified, combination logic is AND
type ResourcePolicyRuleSelector struct {
CompNames []string `json:"componentNames,omitempty"`
CompTypes []string `json:"componentTypes,omitempty"`
OAMResourceTypes []string `json:"oamTypes,omitempty"`
TraitTypes []string `json:"traitTypes,omitempty"`
ResourceTypes []string `json:"resourceTypes,omitempty"`
ResourceNames []string `json:"resourceNames,omitempty"`
}
// Match check if current rule selector match the target resource
// If at least one condition is matched and no other condition failed (could be empty), return true
// Otherwise, return false
func (in *ResourcePolicyRuleSelector) Match(manifest *unstructured.Unstructured) bool {
var compName, compType, oamType, traitType, resourceType, resourceName string
if labels := manifest.GetLabels(); labels != nil {
compName = labels[oam.LabelAppComponent]
compType = labels[oam.WorkloadTypeLabel]
oamType = labels[oam.LabelOAMResourceType]
traitType = labels[oam.TraitTypeLabel]
}
resourceType = manifest.GetKind()
resourceName = manifest.GetName()
match := func(src []string, val string) (found *bool) {
if len(src) == 0 {
return nil
}
return pointer.Bool(val != "" && stringslices.Contains(src, val))
}
conditions := []*bool{
match(in.CompNames, compName),
match(in.CompTypes, compType),
match(in.OAMResourceTypes, oamType),
match(in.TraitTypes, traitType),
match(in.ResourceTypes, resourceType),
match(in.ResourceNames, resourceName),
}
hasMatched := false
for _, cond := range conditions {
// if any non-empty condition failed, return false
if cond != nil && !*cond {
return false
}
// if condition succeed, record it
if cond != nil && *cond {
hasMatched = true
}
}
// if at least one condition is met, return true
return hasMatched
}
@@ -0,0 +1,49 @@
/*
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 v1alpha1
import "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
const (
// SharedResourcePolicyType refers to the type of shared resource policy
SharedResourcePolicyType = "shared-resource"
)
// SharedResourcePolicySpec defines the spec of shared-resource policy
type SharedResourcePolicySpec struct {
Rules []SharedResourcePolicyRule `json:"rules"`
}
// Type the type name of the policy
func (in *SharedResourcePolicySpec) Type() string {
return SharedResourcePolicyType
}
// SharedResourcePolicyRule defines the rule for sharing resources
type SharedResourcePolicyRule struct {
Selector ResourcePolicyRuleSelector `json:"selector"`
}
// FindStrategy return if the target resource should be shared
func (in *SharedResourcePolicySpec) FindStrategy(manifest *unstructured.Unstructured) bool {
for _, rule := range in.Rules {
if rule.Selector.Match(manifest) {
return true
}
}
return false
}
@@ -0,0 +1,49 @@
/*
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 v1alpha1
import "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
const (
// TakeOverPolicyType refers to the type of take-over policy
TakeOverPolicyType = "take-over"
)
// TakeOverPolicySpec defines the spec of take-over policy
type TakeOverPolicySpec struct {
Rules []TakeOverPolicyRule `json:"rules"`
}
// Type the type name of the policy
func (in *TakeOverPolicySpec) Type() string {
return TakeOverPolicyType
}
// TakeOverPolicyRule defines the rule for taking over resources
type TakeOverPolicyRule struct {
Selector ResourcePolicyRuleSelector `json:"selector"`
}
// FindStrategy return if the target resource should be taken over
func (in *TakeOverPolicySpec) FindStrategy(manifest *unstructured.Unstructured) bool {
for _, rule := range in.Rules {
if rule.Selector.Match(manifest) {
return true
}
}
return false
}
@@ -585,6 +585,44 @@ func (in *PolicyList) DeepCopyObject() runtime.Object {
return nil
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *ReadOnlyPolicyRule) DeepCopyInto(out *ReadOnlyPolicyRule) {
*out = *in
in.Selector.DeepCopyInto(&out.Selector)
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ReadOnlyPolicyRule.
func (in *ReadOnlyPolicyRule) DeepCopy() *ReadOnlyPolicyRule {
if in == nil {
return nil
}
out := new(ReadOnlyPolicyRule)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *ReadOnlyPolicySpec) DeepCopyInto(out *ReadOnlyPolicySpec) {
*out = *in
if in.Rules != nil {
in, out := &in.Rules, &out.Rules
*out = make([]ReadOnlyPolicyRule, len(*in))
for i := range *in {
(*in)[i].DeepCopyInto(&(*out)[i])
}
}
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ReadOnlyPolicySpec.
func (in *ReadOnlyPolicySpec) DeepCopy() *ReadOnlyPolicySpec {
if in == nil {
return nil
}
out := new(ReadOnlyPolicySpec)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *RefObjectsComponentSpec) DeepCopyInto(out *RefObjectsComponentSpec) {
*out = *in
@@ -720,6 +758,44 @@ func (in *SharedResourcePolicySpec) DeepCopy() *SharedResourcePolicySpec {
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *TakeOverPolicyRule) DeepCopyInto(out *TakeOverPolicyRule) {
*out = *in
in.Selector.DeepCopyInto(&out.Selector)
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new TakeOverPolicyRule.
func (in *TakeOverPolicyRule) DeepCopy() *TakeOverPolicyRule {
if in == nil {
return nil
}
out := new(TakeOverPolicyRule)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *TakeOverPolicySpec) DeepCopyInto(out *TakeOverPolicySpec) {
*out = *in
if in.Rules != nil {
in, out := &in.Rules, &out.Rules
*out = make([]TakeOverPolicyRule, len(*in))
for i := range *in {
(*in)[i].DeepCopyInto(&(*out)[i])
}
}
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new TakeOverPolicySpec.
func (in *TakeOverPolicySpec) DeepCopy() *TakeOverPolicySpec {
if in == nil {
return nil
}
out := new(TakeOverPolicySpec)
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
@@ -0,0 +1,38 @@
# Code generated by KubeVela templates. DO NOT EDIT. Please edit the original cue file.
# Definition source cue file: vela-templates/definitions/internal/read-only.cue
apiVersion: core.oam.dev/v1beta1
kind: PolicyDefinition
metadata:
annotations:
definition.oam.dev/description: Configure the resources to be read-only in the application (no update / state-keep).
name: read-only
namespace: {{ include "systemDefinitionNamespace" . }}
spec:
schematic:
cue:
template: |
#PolicyRule: {
// +usage=Specify how to select the targets of the rule
selector: [...#RuleSelector]
}
#RuleSelector: {
// +usage=Select resources by component names
componentNames?: [...string]
// +usage=Select resources by component types
componentTypes?: [...string]
// +usage=Select resources by oamTypes (COMPONENT or TRAIT)
oamTypes?: [...string]
// +usage=Select resources by trait types
traitTypes?: [...string]
// +usage=Select resources by resource types (like Deployment)
resourceTypes?: [...string]
// +usage=Select resources by their names
resourceNames?: [...string]
}
parameter: {
// +usage=Specify the list of rules to control read only strategy at resource level.
// The selected resource will be read-only to the current application. If the target resource does
// not exist, error will be raised.
rules?: [...#PolicyRule]
}
@@ -0,0 +1,38 @@
# Code generated by KubeVela templates. DO NOT EDIT. Please edit the original cue file.
# Definition source cue file: vela-templates/definitions/internal/take-over.cue
apiVersion: core.oam.dev/v1beta1
kind: PolicyDefinition
metadata:
annotations:
definition.oam.dev/description: Configure the resources to be able to take over when it belongs to no application.
name: take-over
namespace: {{ include "systemDefinitionNamespace" . }}
spec:
schematic:
cue:
template: |
#PolicyRule: {
// +usage=Specify how to select the targets of the rule
selector: [...#RuleSelector]
}
#RuleSelector: {
// +usage=Select resources by component names
componentNames?: [...string]
// +usage=Select resources by component types
componentTypes?: [...string]
// +usage=Select resources by oamTypes (COMPONENT or TRAIT)
oamTypes?: [...string]
// +usage=Select resources by trait types
traitTypes?: [...string]
// +usage=Select resources by resource types (like Deployment)
resourceTypes?: [...string]
// +usage=Select resources by their names
resourceNames?: [...string]
}
parameter: {
// +usage=Specify the list of rules to control take over strategy at resource level.
// The selected resource will be able to be taken over by the current application when the resource belongs to no
// one.
rules?: [...#PolicyRule]
}
@@ -0,0 +1,38 @@
# Code generated by KubeVela templates. DO NOT EDIT. Please edit the original cue file.
# Definition source cue file: vela-templates/definitions/internal/read-only.cue
apiVersion: core.oam.dev/v1beta1
kind: PolicyDefinition
metadata:
annotations:
definition.oam.dev/description: Configure the resources to be read-only in the application (no update / state-keep).
name: read-only
namespace: {{ include "systemDefinitionNamespace" . }}
spec:
schematic:
cue:
template: |
#PolicyRule: {
// +usage=Specify how to select the targets of the rule
selector: [...#RuleSelector]
}
#RuleSelector: {
// +usage=Select resources by component names
componentNames?: [...string]
// +usage=Select resources by component types
componentTypes?: [...string]
// +usage=Select resources by oamTypes (COMPONENT or TRAIT)
oamTypes?: [...string]
// +usage=Select resources by trait types
traitTypes?: [...string]
// +usage=Select resources by resource types (like Deployment)
resourceTypes?: [...string]
// +usage=Select resources by their names
resourceNames?: [...string]
}
parameter: {
// +usage=Specify the list of rules to control read only strategy at resource level.
// The selected resource will be read-only to the current application. If the target resource does
// not exist, error will be raised.
rules?: [...#PolicyRule]
}
@@ -0,0 +1,38 @@
# Code generated by KubeVela templates. DO NOT EDIT. Please edit the original cue file.
# Definition source cue file: vela-templates/definitions/internal/take-over.cue
apiVersion: core.oam.dev/v1beta1
kind: PolicyDefinition
metadata:
annotations:
definition.oam.dev/description: Configure the resources to be able to take over when it belongs to no application.
name: take-over
namespace: {{ include "systemDefinitionNamespace" . }}
spec:
schematic:
cue:
template: |
#PolicyRule: {
// +usage=Specify how to select the targets of the rule
selector: [...#RuleSelector]
}
#RuleSelector: {
// +usage=Select resources by component names
componentNames?: [...string]
// +usage=Select resources by component types
componentTypes?: [...string]
// +usage=Select resources by oamTypes (COMPONENT or TRAIT)
oamTypes?: [...string]
// +usage=Select resources by trait types
traitTypes?: [...string]
// +usage=Select resources by resource types (like Deployment)
resourceTypes?: [...string]
// +usage=Select resources by their names
resourceNames?: [...string]
}
parameter: {
// +usage=Specify the list of rules to control take over strategy at resource level.
// The selected resource will be able to be taken over by the current application when the resource belongs to no
// one.
rules?: [...#PolicyRule]
}
+7 -7
View File
@@ -87,10 +87,10 @@ require (
github.com/xlab/treeprint v1.1.0
go.mongodb.org/mongo-driver v1.5.1
go.uber.org/zap v1.21.0
golang.org/x/crypto v0.0.0-20220926161630-eccd6366d1be
golang.org/x/crypto v0.1.0
golang.org/x/oauth2 v0.0.0-20220622183110-fd043fe589d2
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211
golang.org/x/text v0.3.8
golang.org/x/term v0.1.0
golang.org/x/text v0.4.0
gomodules.xyz/jsonpatch/v2 v2.2.0
gopkg.in/src-d/go-git.v4 v4.13.1
gopkg.in/yaml.v3 v3.0.1
@@ -294,12 +294,12 @@ require (
go.starlark.net v0.0.0-20200306205701-8dd3e2ee1dd5 // indirect
go.uber.org/atomic v1.9.0 // indirect
go.uber.org/multierr v1.7.0 // indirect
golang.org/x/mod v0.6.0-dev.0.20220818022119-ed83ed61efb9 // indirect
golang.org/x/net v0.0.0-20220906165146-f3363e06e74c // indirect
golang.org/x/mod v0.6.0 // indirect
golang.org/x/net v0.1.0 // indirect
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4 // indirect
golang.org/x/sys v0.0.0-20220928140112-f11e5e49a4ec // indirect
golang.org/x/sys v0.1.0 // indirect
golang.org/x/time v0.0.0-20220922220347-f3bd1da661af // indirect
golang.org/x/tools v0.1.12 // indirect
golang.org/x/tools v0.2.0 // indirect
google.golang.org/appengine v1.6.7 // indirect
google.golang.org/grpc v1.48.0 // indirect
google.golang.org/protobuf v1.28.0 // indirect
+14 -13
View File
@@ -2201,8 +2201,8 @@ golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97/go.mod h1:GvvjBRRGRdwPK5y
golang.org/x/crypto v0.0.0-20210817164053-32db794688a5/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/crypto v0.0.0-20211108221036-ceb1ce70b4fa/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/crypto v0.0.0-20220926161630-eccd6366d1be h1:fmw3UbQh+nxngCAHrDCCztao/kbYFnWjoqop8dHx05A=
golang.org/x/crypto v0.0.0-20220926161630-eccd6366d1be/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
golang.org/x/crypto v0.1.0 h1:MDRAIl0xIo9Io2xV565hzXHw3zVseKrJKodhohM5CjU=
golang.org/x/crypto v0.1.0/go.mod h1:RecgLatLF4+eUMCP1PoPZQb+cVrJcOPbHkTkbkB9sbw=
golang.org/x/exp v0.0.0-20180321215751-8460e604b9de/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20180807140117-3d87b88a115f/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
@@ -2246,8 +2246,8 @@ golang.org/x/mod v0.4.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.4.1/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.5.0/go.mod h1:5OXOZSfqPIIbmVBIIKWRFfZjPR0E5r58TLhUjH0a2Ro=
golang.org/x/mod v0.6.0-dev.0.20220818022119-ed83ed61efb9 h1:VtCrPQXM5Wo9l7XN64SjBMczl48j8mkP+2e3OhYlz+0=
golang.org/x/mod v0.6.0-dev.0.20220818022119-ed83ed61efb9/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
golang.org/x/mod v0.6.0 h1:b9gGHsz9/HhJ3HF5DHQytPpuwocVTChQJK3AvoLRD5I=
golang.org/x/mod v0.6.0/go.mod h1:4mET923SAdbXp2ki8ey+zGs1SLqsuM2Y0uvdZR/fUNI=
golang.org/x/net v0.0.0-20170114055629-f2499483f923/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20180811021610-c39426892332/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
@@ -2327,8 +2327,8 @@ golang.org/x/net v0.0.0-20220325170049-de3da57026de/go.mod h1:CfG3xpIq0wQ8r1q4Su
golang.org/x/net v0.0.0-20220412020605-290c469a71a5/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
golang.org/x/net v0.0.0-20220425223048-2871e0cb64e4/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
golang.org/x/net v0.0.0-20220607020251-c690dde0001d/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
golang.org/x/net v0.0.0-20220906165146-f3363e06e74c h1:yKufUcDwucU5urd+50/Opbt4AYpqthk7wHpHok8f1lo=
golang.org/x/net v0.0.0-20220906165146-f3363e06e74c/go.mod h1:YDH+HFinaLZZlnHAfSS6ZXJJ9M9t4Dl22yv3iI2vPwk=
golang.org/x/net v0.1.0 h1:hZ/3BUoy5aId7sCpA/Tc5lt8DkFgdVS2onTpJsZ/fl0=
golang.org/x/net v0.1.0/go.mod h1:Cx3nUiGt4eDBEyega/BKRp+/AlGL8hYe7U9odMt2Cco=
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/oauth2 v0.0.0-20190402181905-9f3314589c9a/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
@@ -2520,15 +2520,16 @@ golang.org/x/sys v0.0.0-20220502124256-b6088ccd6cba/go.mod h1:oPkhp1MJrh7nUepCBc
golang.org/x/sys v0.0.0-20220503163025-988cb79eb6c6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220610221304-9f5ed59c137d/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220928140112-f11e5e49a4ec h1:BkDtF2Ih9xZ7le9ndzTA7KJow28VbQW3odyk/8drmuI=
golang.org/x/sys v0.0.0-20220928140112-f11e5e49a4ec/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.1.0 h1:kunALQeHf1/185U1i0GOB/fy1IPRDDpuoOOqRReG57U=
golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.0.0-20201210144234-2321bbc49cbf/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.0.0-20210220032956-6a3ed077a48d/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.0.0-20210615171337-6886f2dfbf5b/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211 h1:JGgROgKl9N8DuW20oFS5gxc+lE67/N3FcwmBPMe7ArY=
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
golang.org/x/term v0.1.0 h1:g6Z6vPFA9dYBAF7DWcH6sCcOntplXsDKcliusYijMlw=
golang.org/x/term v0.1.0/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
golang.org/x/text v0.0.0-20160726164857-2910a502d2bf/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
@@ -2541,8 +2542,8 @@ golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
golang.org/x/text v0.3.8 h1:nAL+RVCQ9uMn3vJZbV+MRnydTJFPf8qqY42YiA6MrqY=
golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ=
golang.org/x/text v0.4.0 h1:BrVqGRd7+k1DiOgtnFvAkoQEWQvBc25ouMJM6429SFg=
golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
golang.org/x/time v0.0.0-20161028155119-f51c12702a4d/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.0.0-20180412165947-fbb02b2291d2/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
@@ -2685,8 +2686,8 @@ golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
golang.org/x/tools v0.1.6-0.20210820212750-d4cc65f0b2ff/go.mod h1:YD9qOF0M9xpSpdWTBbzEl5e/RnCefISl8E5Noe10jFM=
golang.org/x/tools v0.1.6/go.mod h1:LGqMHiF4EqQNHR1JncWGqT5BVaXmza+X+BDGol+dOxo=
golang.org/x/tools v0.1.7/go.mod h1:LGqMHiF4EqQNHR1JncWGqT5BVaXmza+X+BDGol+dOxo=
golang.org/x/tools v0.1.12 h1:VveCTK38A2rkS8ZqFY25HIDFscX5X9OoEhJd3quQmXU=
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
golang.org/x/tools v0.2.0 h1:G6AHpWxTMGY1KyEYoAQ5WTtIekUUvDNjan3ugu60JvE=
golang.org/x/tools v0.2.0/go.mod h1:y4OqIKeOV/fWJetJ8bXPU1sEVniLMIyDAZWeHdV+NTA=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
+4
View File
@@ -389,6 +389,8 @@ func (p *Parser) parsePoliciesFromRevision(ctx context.Context, af *Appfile) (er
case v1alpha1.GarbageCollectPolicyType:
case v1alpha1.ApplyOncePolicyType:
case v1alpha1.SharedResourcePolicyType:
case v1alpha1.TakeOverPolicyType:
case v1alpha1.ReadOnlyPolicyType:
case v1alpha1.EnvBindingPolicyType:
case v1alpha1.TopologyPolicyType:
case v1alpha1.OverridePolicyType:
@@ -418,6 +420,8 @@ func (p *Parser) parsePolicies(ctx context.Context, af *Appfile) (err error) {
case v1alpha1.GarbageCollectPolicyType:
case v1alpha1.ApplyOncePolicyType:
case v1alpha1.SharedResourcePolicyType:
case v1alpha1.TakeOverPolicyType:
case v1alpha1.ReadOnlyPolicyType:
case v1alpha1.EnvBindingPolicyType:
case v1alpha1.TopologyPolicyType:
case v1alpha1.ReplicationPolicyType:
+13 -7
View File
@@ -17,7 +17,6 @@ limitations under the License.
package definition
import (
"context"
"encoding/json"
"fmt"
@@ -171,7 +170,7 @@ func (wd *workloadDef) getTemplateContext(ctx process.Context, cli client.Reader
return nil, err
}
// workload main resource will have a unique label("app.oam.dev/resourceType"="WORKLOAD") in per component/app level
object, err := getResourceFromObj(ctx.GetCtx(), componentWorkload, cli, accessor.For(componentWorkload), util.MergeMapOverrideWithDst(map[string]string{
object, err := getResourceFromObj(ctx, componentWorkload, cli, accessor.For(componentWorkload), util.MergeMapOverrideWithDst(map[string]string{
oam.LabelOAMResourceType: oam.ResourceTypeWorkload,
}, commonLabels), "")
if err != nil {
@@ -191,7 +190,7 @@ func (wd *workloadDef) getTemplateContext(ctx process.Context, cli client.Reader
return nil, err
}
// AuxiliaryWorkload will have a unique label("trait.oam.dev/resource"="name of outputs") in per component/app level
object, err := getResourceFromObj(ctx.GetCtx(), traitRef, cli, accessor.For(traitRef), util.MergeMapOverrideWithDst(map[string]string{
object, err := getResourceFromObj(ctx, traitRef, cli, accessor.For(traitRef), util.MergeMapOverrideWithDst(map[string]string{
oam.TraitTypeLabel: AuxiliaryWorkload,
}, commonLabels), assist.Name)
if err != nil {
@@ -452,7 +451,7 @@ func (td *traitDef) getTemplateContext(ctx process.Context, cli client.Reader, a
if err != nil {
return nil, err
}
object, err := getResourceFromObj(ctx.GetCtx(), traitRef, cli, accessor.For(traitRef), util.MergeMapOverrideWithDst(map[string]string{
object, err := getResourceFromObj(ctx, traitRef, cli, accessor.For(traitRef), util.MergeMapOverrideWithDst(map[string]string{
oam.TraitTypeLabel: assist.Type,
}, commonLabels), assist.Name)
if err != nil {
@@ -484,18 +483,18 @@ func (td *traitDef) HealthCheck(ctx process.Context, cli client.Client, accessor
return checkHealth(templateContext, healthPolicyTemplate, parameter)
}
func getResourceFromObj(ctx context.Context, obj *unstructured.Unstructured, client client.Reader, namespace string, labels map[string]string, outputsResource string) (map[string]interface{}, error) {
func getResourceFromObj(ctx process.Context, obj *unstructured.Unstructured, client client.Reader, namespace string, labels map[string]string, outputsResource string) (map[string]interface{}, error) {
if outputsResource != "" {
labels[oam.TraitResource] = outputsResource
}
if obj.GetName() != "" {
u, err := util.GetObjectGivenGVKAndName(ctx, client, obj.GroupVersionKind(), namespace, obj.GetName())
u, err := util.GetObjectGivenGVKAndName(ctx.GetCtx(), client, obj.GroupVersionKind(), namespace, obj.GetName())
if err != nil {
return nil, err
}
return u.Object, nil
}
list, err := util.GetObjectsGivenGVKAndLabels(ctx, client, obj.GroupVersionKind(), namespace, labels)
list, err := util.GetObjectsGivenGVKAndLabels(ctx.GetCtx(), client, obj.GroupVersionKind(), namespace, labels)
if err != nil {
return nil, err
}
@@ -507,5 +506,12 @@ func getResourceFromObj(ctx context.Context, obj *unstructured.Unstructured, cli
return v.Object, nil
}
}
if ctxName := ctx.GetData(model.ContextName).(string); ctxName != "" {
u, err := util.GetObjectGivenGVKAndName(ctx.GetCtx(), client, obj.GroupVersionKind(), namespace, ctxName)
if err != nil {
return nil, err
}
return u.Object, nil
}
return nil, errors.Errorf("no resources found gvk(%v) labels(%v)", obj.GroupVersionKind(), labels)
}
+13 -36
View File
@@ -18,49 +18,26 @@ package policy
import (
"encoding/json"
"fmt"
"github.com/pkg/errors"
"github.com/oam-dev/kubevela/apis/core.oam.dev/v1alpha1"
"github.com/oam-dev/kubevela/apis/core.oam.dev/v1beta1"
)
// parsePolicy parse policy for application
func parsePolicy(app *v1beta1.Application, policyType string, policySpec interface{}) (exists bool, err error) {
type typer[T any] interface {
*T
Type() string
}
// ParsePolicy parse policy for the given type
func ParsePolicy[T any, P typer[T]](app *v1beta1.Application) (*T, error) {
spec := new(T)
for _, policy := range app.Spec.Policies {
if policy.Type == policyType && policy.Properties != nil && policy.Properties.Raw != nil {
if err := json.Unmarshal(policy.Properties.Raw, policySpec); err != nil {
return true, errors.Wrapf(err, "invalid %s policy: %s", policy.Type, policy.Name)
if policy.Type == P(spec).Type() && policy.Properties != nil && policy.Properties.Raw != nil {
if err := json.Unmarshal(policy.Properties.Raw, spec); err != nil {
return nil, fmt.Errorf("invalid %s policy %s: %w", policy.Type, policy.Name, err)
}
return true, nil
return spec, nil
}
}
return false, nil
}
// ParseGarbageCollectPolicy parse garbage-collect policy
func ParseGarbageCollectPolicy(app *v1beta1.Application) (*v1alpha1.GarbageCollectPolicySpec, error) {
spec := &v1alpha1.GarbageCollectPolicySpec{}
if exists, err := parsePolicy(app, v1alpha1.GarbageCollectPolicyType, spec); exists {
return spec, err
}
return nil, nil
}
// ParseApplyOncePolicy parse apply-once policy
func ParseApplyOncePolicy(app *v1beta1.Application) (*v1alpha1.ApplyOncePolicySpec, error) {
spec := &v1alpha1.ApplyOncePolicySpec{}
if exists, err := parsePolicy(app, v1alpha1.ApplyOncePolicyType, spec); exists {
return spec, err
}
return nil, nil
}
// ParseSharedResourcePolicy parse shared-resource policy
func ParseSharedResourcePolicy(app *v1beta1.Application) (*v1alpha1.SharedResourcePolicySpec, error) {
spec := &v1alpha1.SharedResourcePolicySpec{}
if exists, err := parsePolicy(app, v1alpha1.SharedResourcePolicyType, spec); exists {
return spec, err
}
return nil, nil
}
+12 -12
View File
@@ -32,14 +32,14 @@ func TestParseGarbageCollectPolicy(t *testing.T) {
app := &v1beta1.Application{Spec: v1beta1.ApplicationSpec{
Policies: []v1beta1.AppPolicy{{Type: "example"}},
}}
spec, err := ParseGarbageCollectPolicy(app)
spec, err := ParsePolicy[v1alpha1.GarbageCollectPolicySpec](app)
r.NoError(err)
r.Nil(spec)
app.Spec.Policies = append(app.Spec.Policies, v1beta1.AppPolicy{
Type: "garbage-collect",
Properties: &runtime.RawExtension{Raw: []byte("bad value")},
})
_, err = ParseGarbageCollectPolicy(app)
_, err = ParsePolicy[v1alpha1.GarbageCollectPolicySpec](app)
r.Error(err)
policySpec := &v1alpha1.GarbageCollectPolicySpec{
KeepLegacyResource: false,
@@ -54,7 +54,7 @@ func TestParseGarbageCollectPolicy(t *testing.T) {
bs, err := json.Marshal(policySpec)
r.NoError(err)
app.Spec.Policies[1].Properties.Raw = bs
spec, err = ParseGarbageCollectPolicy(app)
spec, err = ParsePolicy[v1alpha1.GarbageCollectPolicySpec](app)
r.NoError(err)
r.Equal(policySpec, spec)
}
@@ -64,20 +64,20 @@ func TestParseApplyOncePolicy(t *testing.T) {
app := &v1beta1.Application{Spec: v1beta1.ApplicationSpec{
Policies: []v1beta1.AppPolicy{{Type: "example"}},
}}
spec, err := ParseApplyOncePolicy(app)
spec, err := ParsePolicy[v1alpha1.ApplyOncePolicySpec](app)
r.NoError(err)
r.Nil(spec)
app.Spec.Policies = append(app.Spec.Policies, v1beta1.AppPolicy{
Type: "apply-once",
Properties: &runtime.RawExtension{Raw: []byte("bad value")},
})
_, err = ParseApplyOncePolicy(app)
_, err = ParsePolicy[v1alpha1.ApplyOncePolicySpec](app)
r.Error(err)
policySpec := &v1alpha1.ApplyOncePolicySpec{Enable: true}
bs, err := json.Marshal(policySpec)
r.NoError(err)
app.Spec.Policies[1].Properties.Raw = bs
spec, err = ParseApplyOncePolicy(app)
spec, err = ParsePolicy[v1alpha1.ApplyOncePolicySpec](app)
r.NoError(err)
r.Equal(policySpec, spec)
}
@@ -87,14 +87,14 @@ func TestParseSharedResourcePolicy(t *testing.T) {
app := &v1beta1.Application{Spec: v1beta1.ApplicationSpec{
Policies: []v1beta1.AppPolicy{{Type: "example"}},
}}
spec, err := ParseSharedResourcePolicy(app)
spec, err := ParsePolicy[v1alpha1.SharedResourcePolicySpec](app)
r.NoError(err)
r.Nil(spec)
app.Spec.Policies = append(app.Spec.Policies, v1beta1.AppPolicy{
Type: "shared-resource",
Properties: &runtime.RawExtension{Raw: []byte("bad value")},
})
_, err = ParseSharedResourcePolicy(app)
_, err = ParsePolicy[v1alpha1.SharedResourcePolicySpec](app)
r.Error(err)
policySpec := &v1alpha1.SharedResourcePolicySpec{
Rules: []v1alpha1.SharedResourcePolicyRule{{
@@ -103,7 +103,7 @@ func TestParseSharedResourcePolicy(t *testing.T) {
bs, err := json.Marshal(policySpec)
r.NoError(err)
app.Spec.Policies[1].Properties.Raw = bs
spec, err = ParseSharedResourcePolicy(app)
spec, err = ParsePolicy[v1alpha1.SharedResourcePolicySpec](app)
r.NoError(err)
r.Equal(policySpec, spec)
}
@@ -112,9 +112,9 @@ func TestParsePolicy(t *testing.T) {
r := require.New(t)
// Test skipping empty policy
app := &v1beta1.Application{Spec: v1beta1.ApplicationSpec{
Policies: []v1beta1.AppPolicy{{Type: "example", Name: "s", Properties: nil}},
Policies: []v1beta1.AppPolicy{{Type: v1alpha1.GarbageCollectPolicyType, Name: "s", Properties: nil}},
}}
exists, err := parsePolicy(app, "example", nil)
r.False(exists, "empty policy should not be included")
exists, err := ParsePolicy[v1alpha1.GarbageCollectPolicySpec](app)
r.Nil(exists)
r.NoError(err)
}
+6
View File
@@ -142,6 +142,12 @@ func (h *resourceKeeper) dispatch(ctx context.Context, manifests []*unstructured
if h.isShared(manifest) {
ao = append([]apply.ApplyOption{apply.SharedByApp(h.app)}, ao...)
}
if h.isReadOnly(manifest) {
ao = append([]apply.ApplyOption{apply.ReadOnly()}, ao...)
}
if h.canTakeOver(manifest) {
ao = append([]apply.ApplyOption{apply.TakeOver()}, ao...)
}
manifest, err := ApplyStrategies(applyCtx, h, manifest, v1alpha1.ApplyOnceStrategyOnAppUpdate)
if err != nil {
return errors.Wrapf(err, "failed to apply once policy for application %s,%s", h.app.Name, err.Error())
+11 -3
View File
@@ -61,6 +61,8 @@ type resourceKeeper struct {
applyOncePolicy *v1alpha1.ApplyOncePolicySpec
garbageCollectPolicy *v1alpha1.GarbageCollectPolicySpec
sharedResourcePolicy *v1alpha1.SharedResourcePolicySpec
takeOverPolicy *v1alpha1.TakeOverPolicySpec
readOnlyPolicy *v1alpha1.ReadOnlyPolicySpec
cache *resourceCache
}
@@ -93,18 +95,24 @@ func (h *resourceKeeper) getComponentRevisionRT(ctx context.Context) (crRT *v1be
}
func (h *resourceKeeper) parseApplicationResourcePolicy() (err error) {
if h.applyOncePolicy, err = policy.ParseApplyOncePolicy(h.app); err != nil {
if h.applyOncePolicy, err = policy.ParsePolicy[v1alpha1.ApplyOncePolicySpec](h.app); err != nil {
return errors.Wrapf(err, "failed to parse apply-once policy")
}
if h.applyOncePolicy == nil && metav1.HasLabel(h.app.ObjectMeta, oam.LabelAddonName) {
h.applyOncePolicy = &v1alpha1.ApplyOncePolicySpec{Enable: true}
}
if h.garbageCollectPolicy, err = policy.ParseGarbageCollectPolicy(h.app); err != nil {
if h.garbageCollectPolicy, err = policy.ParsePolicy[v1alpha1.GarbageCollectPolicySpec](h.app); err != nil {
return errors.Wrapf(err, "failed to parse garbage-collect policy")
}
if h.sharedResourcePolicy, err = policy.ParseSharedResourcePolicy(h.app); err != nil {
if h.sharedResourcePolicy, err = policy.ParsePolicy[v1alpha1.SharedResourcePolicySpec](h.app); err != nil {
return errors.Wrapf(err, "failed to parse shared-resource policy")
}
if h.takeOverPolicy, err = policy.ParsePolicy[v1alpha1.TakeOverPolicySpec](h.app); err != nil {
return errors.Wrapf(err, "failed to parse take-over policy")
}
if h.readOnlyPolicy, err = policy.ParsePolicy[v1alpha1.ReadOnlyPolicySpec](h.app); err != nil {
return errors.Wrapf(err, "failed to parse read-only policy")
}
return nil
}
+6
View File
@@ -71,6 +71,12 @@ func (h *resourceKeeper) StateKeep(ctx context.Context) error {
if h.isShared(manifest) {
ao = append([]apply.ApplyOption{apply.SharedByApp(h.app)}, ao...)
}
if h.isReadOnly(manifest) {
ao = append([]apply.ApplyOption{apply.ReadOnly()}, ao...)
}
if h.canTakeOver(manifest) {
ao = append([]apply.ApplyOption{apply.TakeOver()}, ao...)
}
if err = h.applicator.Apply(applyCtx, manifest, ao...); err != nil {
return errors.Wrapf(err, "failed to re-apply resource %s from resourcetracker %s", mr.ResourceKey(), rt.Name)
}
+14
View File
@@ -37,3 +37,17 @@ func (h *resourceKeeper) isShared(manifest *unstructured.Unstructured) bool {
}
return h.sharedResourcePolicy.FindStrategy(manifest)
}
func (h *resourceKeeper) canTakeOver(manifest *unstructured.Unstructured) bool {
if h.takeOverPolicy == nil {
return false
}
return h.takeOverPolicy.FindStrategy(manifest)
}
func (h *resourceKeeper) isReadOnly(manifest *unstructured.Unstructured) bool {
if h.readOnlyPolicy == nil {
return false
}
return h.readOnlyPolicy.FindStrategy(manifest)
}
+24 -2
View File
@@ -56,6 +56,8 @@ type Applicator interface {
}
type applyAction struct {
takeOver bool
readOnly bool
isShared bool
skipUpdate bool
updateAnnotation bool
@@ -228,6 +230,9 @@ func createOrGetExisting(ctx context.Context, act *applyAction, c client.Client,
if err := executeApplyOptions(act, nil, desired, ao); err != nil {
return nil, err
}
if act.readOnly {
return nil, fmt.Errorf("cannot create %s (%s) in read-only mode", desired.GetObjectKind().GroupVersionKind().Kind, desired.GetName())
}
if act.updateAnnotation {
if err := addLastAppliedConfigAnnotation(desired); err != nil {
return nil, err
@@ -297,6 +302,23 @@ func NotUpdateRenderHashEqual() ApplyOption {
}
}
// ReadOnly skip apply fo the resource
func ReadOnly() ApplyOption {
return func(act *applyAction, _, _ client.Object) error {
act.readOnly = true
act.skipUpdate = true
return nil
}
}
// TakeOver allow take over resources without app owner
func TakeOver() ApplyOption {
return func(act *applyAction, _, _ client.Object) error {
act.takeOver = true
return nil
}
}
// MustBeControllableBy requires that the new object is controllable by an
// object with the supplied UID. An object is controllable if its controller
// reference includes the supplied UID.
@@ -319,14 +341,14 @@ func MustBeControllableBy(u types.UID) ApplyOption {
// MustBeControlledByApp requires that the new object is controllable by versioned resourcetracker
func MustBeControlledByApp(app *v1beta1.Application) ApplyOption {
return func(act *applyAction, existing, _ client.Object) error {
if existing == nil || act.isShared {
if existing == nil || act.isShared || act.readOnly {
return nil
}
appKey, controlledBy := GetAppKey(app), GetControlledBy(existing)
// if the existing object has no resource version, it means this resource is an API response not directly from
// an etcd object but from some external services, such as vela-prism. Then the response does not necessarily
// contain the owner
if controlledBy == "" && !utilfeature.DefaultMutableFeatureGate.Enabled(features.LegacyResourceOwnerValidation) && existing.GetResourceVersion() != "" {
if controlledBy == "" && !utilfeature.DefaultMutableFeatureGate.Enabled(features.LegacyResourceOwnerValidation) && existing.GetResourceVersion() != "" && !act.takeOver {
return fmt.Errorf("%s %s/%s exists but not managed by any application now", existing.GetObjectKind().GroupVersionKind().Kind, existing.GetNamespace(), existing.GetName())
}
if controlledBy != "" && controlledBy != appKey {
@@ -0,0 +1,22 @@
```yaml
apiVersion: core.oam.dev/v1beta1
kind: Application
metadata:
name: read-only
spec:
components:
- name: busybox
type: worker
properties:
image: busybox
cmd:
- sleep
- '1000000'
policies:
- type: read-only
name: read-only
properties:
rules:
- selector:
resourceTypes: ["Deployment"]
```
@@ -0,0 +1,23 @@
```yaml
apiVersion: core.oam.dev/v1beta1
kind: Application
metadata:
name: take-over
spec:
components:
- name: busybox
type: k8s-objects
properties:
objects:
- apiVersion: apps/v1
kind: Deployment
metadata:
name: busybox-ref
policies:
- type: take-over
name: take-over
properties:
rules:
- selector:
resourceTypes: ["Deployment"]
```
@@ -24,6 +24,7 @@ import (
"strings"
"time"
workflowv1alpha1 "github.com/kubevela/workflow/api/v1alpha1"
. "github.com/onsi/ginkgo"
. "github.com/onsi/gomega"
appsv1 "k8s.io/api/apps/v1"
@@ -714,5 +715,74 @@ var _ = Describe("Test multicluster scenario", func() {
Expect(cm.Data["cluster"]).Should(Equal("cluster-worker"))
Expect(k8sClient.Delete(hubCtx, def)).Should(Succeed())
})
It("Test application with read-only policy", func() {
By("create deployment")
bs, err := os.ReadFile("./testdata/app/standalone/deployment-busybox.yaml")
Expect(err).Should(Succeed())
deploy := &appsv1.Deployment{}
Expect(yaml.Unmarshal(bs, deploy)).Should(Succeed())
deploy.SetNamespace(namespace)
Expect(k8sClient.Create(hubCtx, deploy)).Should(Succeed())
By("create application")
bs, err = os.ReadFile("./testdata/app/app-readonly.yaml")
Expect(err).Should(Succeed())
app := &v1beta1.Application{}
Expect(yaml.Unmarshal(bs, app)).Should(Succeed())
app.SetNamespace(namespace)
Expect(k8sClient.Create(hubCtx, app)).Should(Succeed())
Eventually(func(g Gomega) {
g.Expect(k8sClient.Get(hubCtx, client.ObjectKeyFromObject(app), app)).Should(Succeed())
g.Expect(app.Status.Workflow).ShouldNot(BeNil())
g.Expect(app.Status.Workflow.Steps[0].Phase).Should(Equal(workflowv1alpha1.WorkflowStepPhaseFailed))
}, 20*time.Second).Should(Succeed())
By("update application")
Eventually(func(g Gomega) {
g.Expect(k8sClient.Get(hubCtx, client.ObjectKeyFromObject(app), app)).Should(Succeed())
app.Spec.Components[0].Name = "busybox-ref"
g.Expect(k8sClient.Update(hubCtx, app)).Should(Succeed())
}, 20*time.Second).Should(Succeed())
Eventually(func(g Gomega) {
g.Expect(k8sClient.Get(hubCtx, client.ObjectKeyFromObject(app), app)).Should(Succeed())
g.Expect(app.Status.Phase).Should(Equal(common.ApplicationRunning))
}, 20*time.Second).Should(Succeed())
By("delete application")
appKey := client.ObjectKeyFromObject(app)
deployKey := client.ObjectKeyFromObject(deploy)
Expect(k8sClient.Delete(hubCtx, app)).Should(Succeed())
Eventually(func(g Gomega) {
g.Expect(kerrors.IsNotFound(k8sClient.Get(hubCtx, appKey, app))).Should(BeTrue())
}, 20*time.Second).Should(Succeed())
Expect(k8sClient.Get(hubCtx, deployKey, deploy)).Should(Succeed())
})
It("Test application with take-over policy", func() {
By("create deployment")
bs, err := os.ReadFile("./testdata/app/standalone/deployment-busybox.yaml")
Expect(err).Should(Succeed())
deploy := &appsv1.Deployment{}
Expect(yaml.Unmarshal(bs, deploy)).Should(Succeed())
deploy.SetNamespace(namespace)
Expect(k8sClient.Create(hubCtx, deploy)).Should(Succeed())
By("create application")
bs, err = os.ReadFile("./testdata/app/app-takeover.yaml")
Expect(err).Should(Succeed())
app := &v1beta1.Application{}
Expect(yaml.Unmarshal(bs, app)).Should(Succeed())
app.SetNamespace(namespace)
Expect(k8sClient.Create(hubCtx, app)).Should(Succeed())
Eventually(func(g Gomega) {
g.Expect(k8sClient.Get(hubCtx, client.ObjectKeyFromObject(app), app)).Should(Succeed())
g.Expect(app.Status.Phase).Should(Equal(common.ApplicationRunning))
}, 20*time.Second).Should(Succeed())
By("delete application")
appKey := client.ObjectKeyFromObject(app)
deployKey := client.ObjectKeyFromObject(deploy)
Expect(k8sClient.Delete(hubCtx, app)).Should(Succeed())
Eventually(func(g Gomega) {
g.Expect(kerrors.IsNotFound(k8sClient.Get(hubCtx, appKey, app))).Should(BeTrue())
g.Expect(kerrors.IsNotFound(k8sClient.Get(hubCtx, deployKey, deploy))).Should(BeTrue())
}, 20*time.Second).Should(Succeed())
})
})
})
@@ -0,0 +1,20 @@
apiVersion: core.oam.dev/v1beta1
kind: Application
metadata:
name: read-only
spec:
components:
- name: busybox
type: worker
properties:
image: busybox
cmd:
- sleep
- '1000000'
policies:
- type: read-only
name: read-only
properties:
rules:
- selector:
resourceTypes: ["Deployment"]
@@ -0,0 +1,21 @@
apiVersion: core.oam.dev/v1beta1
kind: Application
metadata:
name: take-over
spec:
components:
- name: busybox
type: k8s-objects
properties:
objects:
- apiVersion: apps/v1
kind: Deployment
metadata:
name: busybox-ref
policies:
- type: take-over
name: take-over
properties:
rules:
- selector:
resourceTypes: ["Deployment"]
@@ -0,0 +1,36 @@
"read-only": {
annotations: {}
description: "Configure the resources to be read-only in the application (no update / state-keep)."
labels: {}
attributes: {}
type: "policy"
}
template: {
#PolicyRule: {
// +usage=Specify how to select the targets of the rule
selector: [...#RuleSelector]
}
#RuleSelector: {
// +usage=Select resources by component names
componentNames?: [...string]
// +usage=Select resources by component types
componentTypes?: [...string]
// +usage=Select resources by oamTypes (COMPONENT or TRAIT)
oamTypes?: [...string]
// +usage=Select resources by trait types
traitTypes?: [...string]
// +usage=Select resources by resource types (like Deployment)
resourceTypes?: [...string]
// +usage=Select resources by their names
resourceNames?: [...string]
}
parameter: {
// +usage=Specify the list of rules to control read only strategy at resource level.
// The selected resource will be read-only to the current application. If the target resource does
// not exist, error will be raised.
rules?: [...#PolicyRule]
}
}
@@ -0,0 +1,36 @@
"take-over": {
annotations: {}
description: "Configure the resources to be able to take over when it belongs to no application."
labels: {}
attributes: {}
type: "policy"
}
template: {
#PolicyRule: {
// +usage=Specify how to select the targets of the rule
selector: [...#RuleSelector]
}
#RuleSelector: {
// +usage=Select resources by component names
componentNames?: [...string]
// +usage=Select resources by component types
componentTypes?: [...string]
// +usage=Select resources by oamTypes (COMPONENT or TRAIT)
oamTypes?: [...string]
// +usage=Select resources by trait types
traitTypes?: [...string]
// +usage=Select resources by resource types (like Deployment)
resourceTypes?: [...string]
// +usage=Select resources by their names
resourceNames?: [...string]
}
parameter: {
// +usage=Specify the list of rules to control take over strategy at resource level.
// The selected resource will be able to be taken over by the current application when the resource belongs to no
// one.
rules?: [...#PolicyRule]
}
}