mirror of
https://github.com/kubevela/kubevela.git
synced 2026-08-19 04:26:39 +00:00
Feat: add deploy inline policy and support loading definitions when lack in the revision (#5416)
Signed-off-by: Somefive <yd219913@alibaba-inc.com>
This commit is contained in:
@@ -353,7 +353,7 @@ func (af *Appfile) SetOAMContract(comp *types.ComponentManifest) error {
|
||||
}
|
||||
for _, trait := range comp.Traits {
|
||||
af.assembleTrait(trait, compName, commonLabels)
|
||||
if err := af.setWorkloadRefToTrait(workloadRef, trait); err != nil {
|
||||
if err := af.setWorkloadRefToTrait(workloadRef, trait); err != nil && !IsNotFoundInAppFile(err) {
|
||||
return errors.WithMessagef(err, "cannot set workload reference to trait %q", trait.GetName())
|
||||
}
|
||||
}
|
||||
@@ -507,6 +507,11 @@ func (af *Appfile) setWorkloadRefToTrait(wlRef corev1.ObjectReference, trait *un
|
||||
return nil
|
||||
}
|
||||
|
||||
// IsNotFoundInAppFile check if the target error is `not found in appfile`
|
||||
func IsNotFoundInAppFile(err error) bool {
|
||||
return err != nil && strings.Contains(err.Error(), "not found in appfile")
|
||||
}
|
||||
|
||||
// PrepareProcessContext prepares a DSL process Context
|
||||
func PrepareProcessContext(wl *Workload, ctxData velaprocess.ContextData) (process.Context, error) {
|
||||
if wl.Ctx == nil {
|
||||
|
||||
@@ -638,6 +638,53 @@ func (p *Parser) ParseWorkloadFromRevision(comp common.ApplicationComponent, app
|
||||
return workload, nil
|
||||
}
|
||||
|
||||
// ParseWorkloadFromRevisionAndClient resolve an ApplicationComponent and generate a Workload
|
||||
// containing ALL information required by an Appfile from app revision, and will fall back to
|
||||
// load external definitions if not found
|
||||
func (p *Parser) ParseWorkloadFromRevisionAndClient(ctx context.Context, comp common.ApplicationComponent, appRev *v1beta1.ApplicationRevision) (*Workload, error) {
|
||||
workload, err := p.makeWorkloadFromRevision(comp.Name, comp.Type, types.TypeComponentDefinition, comp.Properties, appRev)
|
||||
if IsNotFoundInAppRevision(err) {
|
||||
workload, err = p.makeWorkload(ctx, comp.Name, comp.Type, types.TypeComponentDefinition, comp.Properties)
|
||||
}
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
workload.ExternalRevision = comp.ExternalRevision
|
||||
|
||||
for _, traitValue := range comp.Traits {
|
||||
properties, err := util.RawExtension2Map(traitValue.Properties)
|
||||
if err != nil {
|
||||
return nil, errors.Errorf("fail to parse properties of %s for %s", traitValue.Type, comp.Name)
|
||||
}
|
||||
trait, err := p.parseTraitFromRevision(traitValue.Type, properties, appRev)
|
||||
if IsNotFoundInAppRevision(err) {
|
||||
trait, err = p.parseTrait(ctx, traitValue.Type, properties)
|
||||
}
|
||||
if err != nil {
|
||||
return nil, errors.WithMessagef(err, "component(%s) parse trait(%s)", comp.Name, traitValue.Type)
|
||||
}
|
||||
|
||||
workload.Traits = append(workload.Traits, trait)
|
||||
}
|
||||
|
||||
for scopeType, instanceName := range comp.Scopes {
|
||||
sd, gvk, err := GetScopeDefAndGVKFromRevision(scopeType, appRev)
|
||||
if IsNotFoundInAppRevision(err) {
|
||||
sd, gvk, err = GetScopeDefAndGVK(ctx, p.client, p.dm, scopeType)
|
||||
}
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
workload.Scopes = append(workload.Scopes, Scope{
|
||||
Name: instanceName,
|
||||
GVK: gvk,
|
||||
ResourceVersion: sd.Spec.Reference.Name + "/" + sd.Spec.Reference.Version,
|
||||
})
|
||||
workload.ScopeDefinition = append(workload.ScopeDefinition, sd)
|
||||
}
|
||||
return workload, nil
|
||||
}
|
||||
|
||||
func (p *Parser) parseTrait(ctx context.Context, name string, properties map[string]interface{}) (*Trait, error) {
|
||||
templ, err := p.tmplLoader.LoadTemplate(ctx, p.dm, p.client, name, types.TypeTrait)
|
||||
if kerrors.IsNotFound(err) {
|
||||
|
||||
@@ -231,6 +231,11 @@ func LoadTemplateFromRevision(capName string, capType types.CapType, apprev *v1b
|
||||
}
|
||||
}
|
||||
|
||||
// IsNotFoundInAppRevision check if the error is `not found in app revision`
|
||||
func IsNotFoundInAppRevision(err error) bool {
|
||||
return err != nil && strings.Contains(err.Error(), "not found in app revision")
|
||||
}
|
||||
|
||||
func verifyRevisionName(capName string, capType types.CapType, apprev *v1beta1.ApplicationRevision) string {
|
||||
if strings.Contains(capName, "@") {
|
||||
splitName := capName[0:strings.LastIndex(capName, "@")]
|
||||
|
||||
@@ -0,0 +1,83 @@
|
||||
/*
|
||||
Copyright 2023 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 appfile
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"sigs.k8s.io/controller-runtime/pkg/client/fake"
|
||||
|
||||
"github.com/oam-dev/kubevela/apis/core.oam.dev/common"
|
||||
"github.com/oam-dev/kubevela/apis/core.oam.dev/v1beta1"
|
||||
)
|
||||
|
||||
func TestIsNotFoundInAppFile(t *testing.T) {
|
||||
require.True(t, IsNotFoundInAppFile(fmt.Errorf("ComponentDefinition XXX not found in appfile")))
|
||||
}
|
||||
|
||||
func TestIsNotFoundInAppRevision(t *testing.T) {
|
||||
require.True(t, IsNotFoundInAppRevision(fmt.Errorf("ComponentDefinition XXX not found in app revision")))
|
||||
}
|
||||
|
||||
func TestParseWorkloadFromRevisionAndClient(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
cli := fake.NewClientBuilder().WithScheme(scheme).Build()
|
||||
p := &Parser{
|
||||
client: cli,
|
||||
tmplLoader: LoadTemplate,
|
||||
}
|
||||
comp := common.ApplicationComponent{
|
||||
Name: "test",
|
||||
Type: "test",
|
||||
Properties: &runtime.RawExtension{Raw: []byte(`{}`)},
|
||||
Traits: []common.ApplicationTrait{{
|
||||
Type: "tr",
|
||||
Properties: &runtime.RawExtension{Raw: []byte(`{}`)},
|
||||
}, {
|
||||
Type: "internal",
|
||||
Properties: &runtime.RawExtension{Raw: []byte(`{}`)},
|
||||
}},
|
||||
}
|
||||
appRev := &v1beta1.ApplicationRevision{}
|
||||
cd := &v1beta1.ComponentDefinition{ObjectMeta: metav1.ObjectMeta{Name: "test"}}
|
||||
td := &v1beta1.TraitDefinition{ObjectMeta: metav1.ObjectMeta{Name: "tr"}}
|
||||
require.NoError(t, cli.Create(ctx, cd))
|
||||
require.NoError(t, cli.Create(ctx, td))
|
||||
appRev.Spec.TraitDefinitions = map[string]v1beta1.TraitDefinition{"internal": {}}
|
||||
_, err := p.ParseWorkloadFromRevisionAndClient(ctx, comp, appRev)
|
||||
require.NoError(t, err)
|
||||
|
||||
_comp1 := comp.DeepCopy()
|
||||
_comp1.Type = "bad"
|
||||
_, err = p.ParseWorkloadFromRevisionAndClient(ctx, *_comp1, appRev)
|
||||
require.Error(t, err)
|
||||
|
||||
_comp2 := comp.DeepCopy()
|
||||
_comp2.Traits[0].Type = "bad"
|
||||
_, err = p.ParseWorkloadFromRevisionAndClient(ctx, *_comp2, appRev)
|
||||
require.Error(t, err)
|
||||
|
||||
_comp3 := comp.DeepCopy()
|
||||
_comp3.Traits[0].Properties = &runtime.RawExtension{Raw: []byte(`bad`)}
|
||||
_, err = p.ParseWorkloadFromRevisionAndClient(ctx, *_comp3, appRev)
|
||||
require.Error(t, err)
|
||||
}
|
||||
@@ -106,16 +106,14 @@ func (h *AppHandler) GenerateApplicationSteps(ctx monitorContext.Context,
|
||||
oamProvider.Install(handlerProviders, app, af, h.r.Client, h.applyComponentFunc(
|
||||
appParser, appRev, af), h.renderComponentFunc(appParser, appRev, af))
|
||||
pCtx := velaprocess.NewContext(generateContextDataFromApp(app, appRev.Name))
|
||||
renderer := func(ctx context.Context, comp common.ApplicationComponent) (*appfile.Workload, error) {
|
||||
return appParser.ParseWorkloadFromRevisionAndClient(ctx, comp, appRev)
|
||||
}
|
||||
multiclusterProvider.Install(handlerProviders, h.r.Client, app, af,
|
||||
h.applyComponentFunc(appParser, appRev, af),
|
||||
h.checkComponentHealth(appParser, appRev, af),
|
||||
func(_ context.Context, comp common.ApplicationComponent) (*appfile.Workload, error) {
|
||||
return appParser.ParseWorkloadFromRevision(comp, appRev)
|
||||
},
|
||||
)
|
||||
terraformProvider.Install(handlerProviders, app, func(_ context.Context, comp common.ApplicationComponent) (*appfile.Workload, error) {
|
||||
return appParser.ParseWorkloadFromRevision(comp, appRev)
|
||||
})
|
||||
renderer)
|
||||
terraformProvider.Install(handlerProviders, app, renderer)
|
||||
query.Install(handlerProviders, h.r.Client, nil)
|
||||
|
||||
instance := generateWorkflowInstance(af, app, appRev.Name)
|
||||
@@ -432,7 +430,7 @@ func (h *AppHandler) prepareWorkloadAndManifests(ctx context.Context,
|
||||
appRev *v1beta1.ApplicationRevision,
|
||||
patcher *value.Value,
|
||||
af *appfile.Appfile) (*appfile.Workload, *types.ComponentManifest, error) {
|
||||
wl, err := appParser.ParseWorkloadFromRevision(comp, appRev)
|
||||
wl, err := appParser.ParseWorkloadFromRevisionAndClient(ctx, comp, appRev)
|
||||
if err != nil {
|
||||
return nil, nil, errors.WithMessage(err, "ParseWorkload")
|
||||
}
|
||||
|
||||
@@ -245,4 +245,5 @@
|
||||
policies: [...string]
|
||||
parallelism: int
|
||||
ignoreTerraformComponent: bool
|
||||
inlinePolicies: *[] | [...{...}]
|
||||
}
|
||||
|
||||
@@ -47,11 +47,13 @@ import (
|
||||
// DeployParameter is the parameter of deploy workflow step
|
||||
type DeployParameter struct {
|
||||
// Declare the policies that used for this deployment. If not specified, the components will be deployed to the hub cluster.
|
||||
Policies []string
|
||||
Policies []string `json:"policies,omitempty"`
|
||||
// Maximum number of concurrent delivered components.
|
||||
Parallelism int64
|
||||
Parallelism int64 `json:"parallelism"`
|
||||
// If set false, this step will apply the components with the terraform workload.
|
||||
IgnoreTerraformComponent bool
|
||||
IgnoreTerraformComponent bool `json:"ignoreTerraformComponent"`
|
||||
// The policies that embeds in the `deploy` step directly
|
||||
InlinePolicies []v1beta1.AppPolicy `json:"inlinePolicies,omitempty"`
|
||||
}
|
||||
|
||||
// DeployWorkflowStepExecutor executor to run deploy workflow step
|
||||
@@ -86,6 +88,7 @@ func (executor *deployWorkflowStepExecutor) Deploy(ctx context.Context) (bool, s
|
||||
if err != nil {
|
||||
return false, "", err
|
||||
}
|
||||
policies = append(policies, fillInlinePolicyNames(executor.parameter.InlinePolicies)...)
|
||||
components, err := loadComponents(ctx, executor.renderer, executor.cli, executor.af, executor.af.Components, executor.parameter.IgnoreTerraformComponent)
|
||||
if err != nil {
|
||||
return false, "", err
|
||||
@@ -123,6 +126,15 @@ func selectPolicies(policies []v1beta1.AppPolicy, policyNames []string) ([]v1bet
|
||||
return selectedPolicies, nil
|
||||
}
|
||||
|
||||
func fillInlinePolicyNames(policies []v1beta1.AppPolicy) []v1beta1.AppPolicy {
|
||||
for i := range policies {
|
||||
if policies[i].Name == "" {
|
||||
policies[i].Name = fmt.Sprintf("inline-%s-policy-%d", policies[i].Type, i)
|
||||
}
|
||||
}
|
||||
return policies
|
||||
}
|
||||
|
||||
func loadComponents(ctx context.Context, renderer oamProvider.WorkloadRenderer, cli client.Client, af *appfile.Appfile, components []common.ApplicationComponent, ignoreTerraformComponent bool) ([]common.ApplicationComponent, error) {
|
||||
var loadedComponents []common.ApplicationComponent
|
||||
for _, comp := range components {
|
||||
|
||||
@@ -172,26 +172,13 @@ func (p *provider) ListClusters(ctx monitorContext.Context, wfCtx wfContext.Cont
|
||||
}
|
||||
|
||||
func (p *provider) Deploy(ctx monitorContext.Context, _ wfContext.Context, v *value.Value, act wfTypes.Action) error {
|
||||
policyNames, err := v.GetStringSlice("policies")
|
||||
if err != nil {
|
||||
param := DeployParameter{}
|
||||
if err := v.CueValue().Decode(¶m); err != nil {
|
||||
return err
|
||||
}
|
||||
parallelism, err := v.GetInt64("parallelism")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if parallelism <= 0 {
|
||||
if param.Parallelism <= 0 {
|
||||
return errors.Errorf("parallelism cannot be smaller than 1")
|
||||
}
|
||||
ignoreTerraformComponent, err := v.GetBool("ignoreTerraformComponent")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
param := DeployParameter{
|
||||
Policies: policyNames,
|
||||
Parallelism: parallelism,
|
||||
IgnoreTerraformComponent: ignoreTerraformComponent,
|
||||
}
|
||||
executor := NewDeployWorkflowStepExecutor(p.Client, p.af, p.apply, p.healthCheck, p.renderer, param)
|
||||
healthy, reason, err := executor.Deploy(ctx)
|
||||
if err != nil {
|
||||
|
||||
Reference in New Issue
Block a user