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:
Somefive
2023-02-06 15:58:44 +08:00
committed by GitHub
parent 6324c16a68
commit b7e0cd86be
11 changed files with 169 additions and 31 deletions
+6 -1
View File
@@ -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 {
+47
View File
@@ -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) {
+5
View File
@@ -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, "@")]
+83
View File
@@ -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")
}
+1
View File
@@ -245,4 +245,5 @@
policies: [...string]
parallelism: int
ignoreTerraformComponent: bool
inlinePolicies: *[] | [...{...}]
}
+15 -3
View File
@@ -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(&param); 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 {