allow component to defer insert secret and config (#1869)

This commit is contained in:
Jianbo Sun
2021-07-02 15:09:45 +08:00
committed by GitHub
parent 4eb33e9239
commit bd41d49311
14 changed files with 374 additions and 108 deletions
+5
View File
@@ -34,4 +34,9 @@ type ComponentManifest struct {
// Release, Git Repo or anything that can package and run a workload.
PackagedWorkloadResources []*unstructured.Unstructured
PackagedTraitResources map[string][]*unstructured.Unstructured
// InsertConfigNotReady is true indicates the ComponentManifest is not ready to apply for insertSecret and configs
// it's possible for some of the component not ready while others are ready, we should not block all of them if only
// part is not ready
InsertConfigNotReady bool
}
+19
View File
@@ -0,0 +1,19 @@
apiVersion: core.oam.dev/v1beta1
kind: Application
metadata:
name: myapp
namespace: default
spec:
components:
- name: comp1
type: webservice
properties:
image: busybox
cmd:
- "sleep"
- "1000"
- name: myweb
type: secretconsumer
properties:
image: nginx:1.14.0
dbSecret: mys
@@ -0,0 +1,59 @@
apiVersion: core.oam.dev/v1beta1
kind: ComponentDefinition
metadata:
name: secretconsumer
spec:
workload:
definition:
apiVersion: apps/v1
kind: Deployment
schematic:
cue:
template: |
output: {
apiVersion: "apps/v1"
kind: "Deployment"
spec: {
selector: matchLabels: {
"app.oam.dev/component": context.name
}
template: {
metadata: labels: {
"app.oam.dev/component": context.name
}
spec: {
containers: [{
name: context.name
image: parameter.image
if parameter["dbSecret"] != _|_ {
env: [
{
name: "username"
value: dbConn.username
},
{
name: "DB_PASSWORD"
value: dbConn.password
},
]
}
}]
}
}
}
}
parameter: {
// +usage=Which image would you like to use for your service
// +short=i
image: string
// +usage=Referred db secret
// +insertSecretTo=dbConn
dbSecret?: string
}
dbConn: {
username: string
password: string
}
+58 -30
View File
@@ -17,11 +17,14 @@ limitations under the License.
package appfile
import (
"context"
"encoding/json"
"fmt"
"regexp"
"strings"
"github.com/oam-dev/kubevela/pkg/appfile/config"
"cuelang.org/go/cue"
"cuelang.org/go/cue/format"
json2cue "cuelang.org/go/encoding/json"
@@ -70,6 +73,8 @@ type Workload struct {
// RequiredSecrets stores secret names which the workload needs from cloud resource component and its context
RequiredSecrets []process.RequiredSecrets
UserConfigs []map[string]string
// ConfigNotReady indicates there's RequiredSecrets and UserConfigs but they're not ready yet.
ConfigNotReady bool
}
// GetUserConfigName get user config from AppFile, it will contain config file in it.
@@ -103,15 +108,15 @@ func (wl *Workload) EvalHealth(ctx process.Context, client client.Client, namesp
return wl.engine.HealthCheck(ctx, client, namespace, wl.FullTemplate.Health)
}
// IsCloudResourceProducer checks whether a workload is cloud resource producer role
func (wl *Workload) IsCloudResourceProducer() bool {
// IsSecretProducer checks whether a workload is cloud resource producer role
func (wl *Workload) IsSecretProducer() bool {
var existed bool
_, existed = wl.Params[process.OutputSecretName]
return existed
}
// IsCloudResourceConsumer checks whether a workload is cloud resource consumer role
func (wl *Workload) IsCloudResourceConsumer() bool {
// IsSecretConsumer checks whether a workload is cloud resource consumer role
func (wl *Workload) IsSecretConsumer() bool {
requiredSecretTag := strings.TrimRight(InsertSecretToTag, "=")
matched, err := regexp.Match(regexp.QuoteMeta(requiredSecretTag), []byte(wl.FullTemplate.TemplateStr))
if err != nil || !matched {
@@ -204,36 +209,35 @@ func generateUnstructuredFromCUEModule(wl *Workload, appName, revision, ns strin
func (af *Appfile) GenerateComponentManifests() ([]*types.ComponentManifest, error) {
compManifests := make([]*types.ComponentManifest, len(af.Workloads))
for i, wl := range af.Workloads {
switch wl.CapabilityCategory {
case types.HelmCategory:
cm, err := generateComponentFromHelmModule(wl, af.Name, af.RevisionName, af.Namespace)
if err != nil {
return nil, err
}
compManifests[i] = cm
case types.KubeCategory:
cm, err := generateComponentFromKubeModule(wl, af.Name, af.RevisionName, af.Namespace)
if err != nil {
return nil, err
}
compManifests[i] = cm
case types.TerraformCategory:
cm, err := generateComponentFromTerraformModule(wl, af.Name, af.RevisionName, af.Namespace)
if err != nil {
return nil, err
}
compManifests[i] = cm
default:
cm, err := generateComponentFromCUEModule(wl, af.Name, af.RevisionName, af.Namespace)
if err != nil {
return nil, err
}
compManifests[i] = cm
cm, err := af.GenerateComponentManifest(wl)
if err != nil {
return nil, err
}
compManifests[i] = cm
}
return compManifests, nil
}
// GenerateComponentManifest generate only one ComponentManifest
func (af *Appfile) GenerateComponentManifest(wl *Workload) (*types.ComponentManifest, error) {
if wl.ConfigNotReady {
return &types.ComponentManifest{
Name: wl.Name,
InsertConfigNotReady: true,
}, nil
}
switch wl.CapabilityCategory {
case types.HelmCategory:
return generateComponentFromHelmModule(wl, af.Name, af.RevisionName, af.Namespace)
case types.KubeCategory:
return generateComponentFromKubeModule(wl, af.Name, af.RevisionName, af.Namespace)
case types.TerraformCategory:
return generateComponentFromTerraformModule(wl, af.Name, af.RevisionName, af.Namespace)
default:
return generateComponentFromCUEModule(wl, af.Name, af.RevisionName, af.Namespace)
}
}
// PrepareProcessContext prepares a DSL process Context
func PrepareProcessContext(wl *Workload, applicationName, revision, namespace string) (process.Context, error) {
pCtx := NewBasicContext(wl, applicationName, revision, namespace)
@@ -253,6 +257,30 @@ func NewBasicContext(wl *Workload, applicationName, revision, namespace string)
return pCtx
}
// GetSecretAndConfigs will get secrets and configs the workload requires
func GetSecretAndConfigs(cli client.Client, workload *Workload, appName, ns string) error {
if workload.IsSecretConsumer() {
requiredSecrets, err := parseWorkloadInsertSecretTo(context.TODO(), cli, ns, workload)
if err != nil {
return err
}
workload.RequiredSecrets = requiredSecrets
}
userConfig := workload.GetUserConfigName()
if userConfig != "" {
cg := config.Configmap{Client: cli}
// TODO(wonderflow): envName should not be namespace when we have serverside env
var envName = ns
data, err := cg.GetConfigData(config.GenConfigMapName(appName, workload.Name, userConfig), envName)
if err != nil {
return errors.Wrapf(err, "get config=%s for app=%s in namespace=%s", userConfig, appName, ns)
}
workload.UserConfigs = data
}
return nil
}
func generateComponentFromCUEModule(wl *Workload, appName, revision, ns string) (*types.ComponentManifest, error) {
pCtx, err := PrepareProcessContext(wl, appName, revision, ns)
if err != nil {
@@ -271,7 +299,7 @@ func baseGenerateComponent(pCtx process.Context, wl *Workload, appName, ns strin
outputSecretName string
err error
)
if wl.IsCloudResourceProducer() {
if wl.IsSecretProducer() {
outputSecretName, err = GetOutputSecretNames(wl)
if err != nil {
return nil, err
+18 -31
View File
@@ -21,6 +21,8 @@ import (
"fmt"
"strings"
"k8s.io/klog/v2"
"cuelang.org/go/cue"
"github.com/pkg/errors"
v1 "k8s.io/api/core/v1"
@@ -32,7 +34,6 @@ import (
"github.com/oam-dev/kubevela/apis/core.oam.dev/v1alpha2"
"github.com/oam-dev/kubevela/apis/core.oam.dev/v1beta1"
"github.com/oam-dev/kubevela/apis/types"
"github.com/oam-dev/kubevela/pkg/appfile/config"
velacue "github.com/oam-dev/kubevela/pkg/cue"
"github.com/oam-dev/kubevela/pkg/cue/definition"
"github.com/oam-dev/kubevela/pkg/cue/packages"
@@ -93,32 +94,38 @@ func (p *Parser) GenerateAppFile(ctx context.Context, app *v1beta1.Application)
appfile.Namespace = ns
var wds []*Workload
for _, comp := range app.Spec.Components {
wd, err := p.parseWorkload(ctx, comp, appName, ns)
wd, err := p.parseWorkload(ctx, comp)
if err != nil {
return nil, err
}
if err := GetSecretAndConfigs(p.client, wd, appName, ns); err != nil {
klog.InfoS("Failed to get secret and configs", "namespace", ns, "app name", appName, "workload name", wd.Name,
"err", err)
wd.ConfigNotReady = true
}
wds = append(wds, wd)
}
appfile.Workloads = wds
var err error
appfile.Policies, err = p.parsePolicies(ctx, appName, ns, app.Spec.Policies)
appfile.Policies, err = p.parsePolicies(ctx, app.Spec.Policies)
if err != nil {
return nil, fmt.Errorf("failed to parsePolicies: %w", err)
}
appfile.WorkflowSteps, err = p.parseWorkflow(ctx, appName, ns, app.Spec.Workflow)
appfile.WorkflowSteps, err = p.parseWorkflow(ctx, app.Spec.Workflow)
if err != nil {
return nil, fmt.Errorf("failed to parseWorkflow: %w", err)
}
return appfile, nil
}
func (p *Parser) parsePolicies(ctx context.Context, appName, ns string, policies []v1beta1.AppPolicy) ([]*Workload, error) {
func (p *Parser) parsePolicies(ctx context.Context, policies []v1beta1.AppPolicy) ([]*Workload, error) {
ws := []*Workload{}
for _, policy := range policies {
w, err := p.makeWorkload(ctx, appName, ns, policy.Name, policy.Type, types.TypePolicy, policy.Properties)
w, err := p.makeWorkload(ctx, policy.Name, policy.Type, types.TypePolicy, policy.Properties)
if err != nil {
return nil, err
}
@@ -127,10 +134,10 @@ func (p *Parser) parsePolicies(ctx context.Context, appName, ns string, policies
return ws, nil
}
func (p *Parser) parseWorkflow(ctx context.Context, appName, ns string, steps []v1beta1.WorkflowStep) ([]*Workload, error) {
func (p *Parser) parseWorkflow(ctx context.Context, steps []v1beta1.WorkflowStep) ([]*Workload, error) {
ws := []*Workload{}
for _, step := range steps {
w, err := p.makeWorkload(ctx, appName, ns, step.Name, step.Type, types.TypeWorkflowStep, step.Properties)
w, err := p.makeWorkload(ctx, step.Name, step.Type, types.TypeWorkflowStep, step.Properties)
if err != nil {
return nil, err
}
@@ -139,7 +146,7 @@ func (p *Parser) parseWorkflow(ctx context.Context, appName, ns string, steps []
return ws, nil
}
func (p *Parser) makeWorkload(ctx context.Context, appName, ns, name, typ string, capType types.CapType, props runtime.RawExtension) (*Workload, error) {
func (p *Parser) makeWorkload(ctx context.Context, name, typ string, capType types.CapType, props runtime.RawExtension) (*Workload, error) {
templ, err := p.tmplLoader.LoadTemplate(ctx, p.dm, p.client, typ, capType)
if err != nil && !kerrors.IsNotFound(err) {
return nil, errors.WithMessagef(err, "fetch type of %s", name)
@@ -162,33 +169,13 @@ func (p *Parser) makeWorkload(ctx context.Context, appName, ns, name, typ string
Params: settings,
engine: definition.NewWorkloadAbstractEngine(name, p.pd),
}
if workload.IsCloudResourceConsumer() {
requiredSecrets, err := parseWorkloadInsertSecretTo(ctx, p.client, ns, workload)
if err != nil {
return nil, err
}
workload.RequiredSecrets = requiredSecrets
}
userConfig := workload.GetUserConfigName()
if userConfig != "" {
cg := config.Configmap{Client: p.client}
// TODO(wonderflow): envName should not be namespace when we have serverside env
var envName = ns
data, err := cg.GetConfigData(config.GenConfigMapName(appName, workload.Name, userConfig), envName)
if err != nil {
return nil, errors.Wrapf(err, "get config=%s for app=%s in namespace=%s", userConfig, appName, ns)
}
workload.UserConfigs = data
}
return workload, nil
}
// parseWorkload resolve an ApplicationComponent and generate a Workload
// containing ALL information required by an Appfile.
func (p *Parser) parseWorkload(ctx context.Context, comp v1beta1.ApplicationComponent, appName, ns string) (*Workload, error) {
workload, err := p.makeWorkload(ctx, appName, ns, comp.Name, comp.Type, types.TypeComponentDefinition, comp.Properties)
func (p *Parser) parseWorkload(ctx context.Context, comp v1beta1.ApplicationComponent) (*Workload, error) {
workload, err := p.makeWorkload(ctx, comp.Name, comp.Type, types.TypeComponentDefinition, comp.Properties)
if err != nil {
return nil, err
}
+6 -6
View File
@@ -616,7 +616,7 @@ parameter: {
})
})
var _ = Describe("Test IsCloudResourceProducer", func() {
var _ = Describe("Test IsSecretProducer", func() {
Context("Workload is a Cloud Resource producer", func() {
It("", func() {
var targetSecretName = "db-conn"
@@ -625,25 +625,25 @@ var _ = Describe("Test IsCloudResourceProducer", func() {
"outputSecretName": targetSecretName,
},
}
Expect(wl.IsCloudResourceProducer()).Should(Equal(true))
Expect(wl.IsSecretProducer()).Should(Equal(true))
})
})
Context("Workload is a Cloud Resource producer", func() {
It("", func() {
wl := &Workload{}
Expect(wl.IsCloudResourceProducer()).Should(Equal(false))
Expect(wl.IsSecretProducer()).Should(Equal(false))
})
})
})
var _ = Describe("Test IsCloudResourceConsumer", func() {
var _ = Describe("Test IsSecretConsumer", func() {
Context("Workload is a Cloud Resource consumer", func() {
It("", func() {
wl := &Workload{
FullTemplate: &Template{TemplateStr: "// +insertSecretTo=dbConn"},
}
Expect(wl.IsCloudResourceConsumer()).Should(Equal(true))
Expect(wl.IsSecretConsumer()).Should(Equal(true))
})
})
@@ -652,7 +652,7 @@ var _ = Describe("Test IsCloudResourceConsumer", func() {
wl := &Workload{
FullTemplate: &Template{TemplateStr: "// +useage=dbConn"},
}
Expect(wl.IsCloudResourceProducer()).Should(Equal(false))
Expect(wl.IsSecretProducer()).Should(Equal(false))
})
})
})
+5 -4
View File
@@ -33,6 +33,11 @@ func (p *Parser) ValidateCUESchematicAppfile(a *Appfile) error {
if wl.CapabilityCategory != types.CUECategory {
continue
}
if wl.IsSecretConsumer() {
// we don't check CUE schema when it's has secret insert demand as the secret can not be ready
// we should deprecate the secret consumer when workflow ready and add the check back
continue
}
pCtx, err := newValidationProcessContext(wl, a.Name, a.RevisionName, a.Namespace)
if err != nil {
@@ -60,10 +65,6 @@ func newValidationProcessContext(wl *Workload, appName, revisionName, ns string)
}
pCtx := process.NewContextWithHooks(ns, wl.Name, appName, revisionName, baseHooks, auxiliaryHooks)
pCtx.InsertSecrets(wl.OutputSecretName, wl.RequiredSecrets)
if len(wl.UserConfigs) > 0 {
pCtx.SetConfigs(wl.UserConfigs)
}
if err := wl.EvalContext(pCtx); err != nil {
return nil, errors.Wrapf(err, "evaluate base template app=%s in namespace=%s", appName, ns)
}
@@ -133,30 +133,20 @@ func (r *Reconciler) Reconcile(req ctrl.Request) (ctrl.Result, error) {
"revisionHash", handler.currentRevHash, "isNewRevision", handler.isNewRevision)
var comps []*velatypes.ComponentManifest
if handler.isNewRevision {
comps, err = appFile.GenerateComponentManifests()
if err != nil {
klog.ErrorS(err, "Failed to render components", "application", klog.KObj(app))
app.Status.SetConditions(errorCondition("Render", err))
r.Recorder.Event(app, event.Warning(velatypes.ReasonFailedRender, err))
return handler.handleErr(err)
}
if err := handler.handleComponentsRevision(ctx, comps); err != nil {
klog.ErrorS(err, "Failed to handle compoents revision", "application", klog.KObj(app))
app.Status.SetConditions(errorCondition("Render", err))
r.Recorder.Event(app, event.Warning(velatypes.ReasonFailedRevision, err))
return handler.handleErr(err)
}
} else {
comps, err = oamutil.AppConfig2ComponentManifests(handler.latestAppRev.Spec.ApplicationConfiguration,
handler.latestAppRev.Spec.Components)
if err != nil {
klog.ErrorS(err, "Failed to get data from existing app revision", "application", klog.KObj(app))
app.Status.SetConditions(errorCondition("Revision", err))
r.Recorder.Event(app, event.Warning(velatypes.ReasonFailedRevision, err))
return handler.handleErr(err)
}
comps, err = appFile.GenerateComponentManifests()
if err != nil {
klog.ErrorS(err, "Failed to render components", "application", klog.KObj(app))
app.Status.SetConditions(errorCondition("Render", err))
r.Recorder.Event(app, event.Warning(velatypes.ReasonFailedRender, err))
return handler.handleErr(err)
}
if err := handler.handleComponentsRevision(ctx, comps); err != nil {
klog.ErrorS(err, "Failed to handle compoents revision", "application", klog.KObj(app))
app.Status.SetConditions(errorCondition("Render", err))
r.Recorder.Event(app, event.Warning(velatypes.ReasonFailedRevision, err))
return handler.handleErr(err)
}
if err := handler.finalizeAndApplyAppRevision(ctx, comps); err != nil {
klog.ErrorS(err, "Failed to apply app revision", "application", klog.KObj(app))
app.Status.SetConditions(errorCondition("Revision", err))
@@ -1505,8 +1505,84 @@ spec:
By("Delete Application, clean the resource")
Expect(k8sClient.Delete(ctx, app)).Should(BeNil())
})
It("app with two components and one component can apply first while another one has secret insert", func() {
appMix := appWithTwoComp.DeepCopy()
appMix.Spec.Components[1] = v1beta1.ApplicationComponent{
Name: "myconsumer",
Type: "secretconsumer",
Properties: runtime.RawExtension{Raw: []byte(`{"image":"nginx:1.14.0", "dbSecret":"mys"}`)},
}
ns := &corev1.Namespace{
ObjectMeta: metav1.ObjectMeta{
Name: "vela-test-two-components-insert-secrets",
},
}
appMix.SetName("vela-test-two-components-insert-secrets")
appMix.SetNamespace(ns.Name)
secretconsumer := &v1beta1.ComponentDefinition{}
wDDefJson, _ := yaml.YAMLToJSON([]byte(compDefSecretYaml))
Expect(json.Unmarshal(wDDefJson, secretconsumer)).Should(BeNil())
secretconsumer.SetNamespace(ns.Name)
Expect(k8sClient.Create(ctx, ns)).Should(BeNil())
Expect(k8sClient.Create(ctx, secretconsumer)).Should(SatisfyAny(BeNil(), &util.AlreadyExistMatcher{}))
Expect(k8sClient.Create(ctx, appMix.DeepCopyObject())).Should(BeNil())
appKey := client.ObjectKey{
Name: appMix.Name,
Namespace: appMix.Namespace,
}
res, _ := reconcileOnceAfterFinalizer(reconciler, reconcile.Request{NamespacedName: appKey})
Expect(res.RequeueAfter).ShouldNot(BeEquivalentTo(0))
By("Check Application Created with the correct phase")
curApp := &v1beta1.Application{}
Expect(k8sClient.Get(ctx, appKey, curApp)).Should(BeNil())
Expect(curApp.Status.Phase).Should(Equal(common.ApplicationHealthChecking))
By("Check One of the component created as expected")
comp1 := &v1.Deployment{}
Expect(k8sClient.Get(ctx, client.ObjectKey{
Namespace: curApp.Namespace,
Name: appMix.Spec.Components[0].Name,
}, comp1)).Should(BeNil())
By("Check another component not existed")
comp2 := &v1.Deployment{}
Expect(k8sClient.Get(ctx, client.ObjectKey{
Namespace: curApp.Namespace,
Name: appMix.Spec.Components[1].Name,
}, comp2)).ShouldNot(BeNil())
sec := &corev1.Secret{Data: map[string][]byte{
"username": []byte("abc"),
"password": []byte("123"),
}}
sec.Name = "mys"
sec.Namespace = appMix.Namespace
Expect(k8sClient.Create(ctx, sec)).Should(BeNil())
reconcileRetry(reconciler, reconcile.Request{NamespacedName: appKey})
By("Check another component is existed")
comp2 = &v1.Deployment{}
Expect(k8sClient.Get(ctx, client.ObjectKey{
Namespace: curApp.Namespace,
Name: appMix.Spec.Components[1].Name,
}, comp2)).Should(BeNil())
Expect(comp2.Spec.Template.Spec.Containers[0].Env[0].Value).Should(BeEquivalentTo("abc"))
Expect(comp2.Spec.Template.Spec.Containers[0].Env[1].Value).Should(BeEquivalentTo("123"))
})
})
func reconcileOnceAfterFinalizer(r reconcile.Reconciler, req reconcile.Request) (reconcile.Result, error) {
// 1st and 2nd time reconcile to add finalizer
r.Reconcile(req)
r.Reconcile(req)
return r.Reconcile(req)
}
func reconcileRetry(r reconcile.Reconciler, req reconcile.Request) {
// 1st and 2nd time reconcile to add finalizer
Eventually(func() error {
@@ -2277,6 +2353,67 @@ spec:
port: string
}
`
compDefSecretYaml = `apiVersion: core.oam.dev/v1beta1
kind: ComponentDefinition
metadata:
name: secretconsumer
spec:
workload:
definition:
apiVersion: apps/v1
kind: Deployment
schematic:
cue:
template: |
output: {
apiVersion: "apps/v1"
kind: "Deployment"
spec: {
selector: matchLabels: {
"app.oam.dev/component": context.name
}
template: {
metadata: labels: {
"app.oam.dev/component": context.name
}
spec: {
containers: [{
name: context.name
image: parameter.image
if parameter["dbSecret"] != _|_ {
env: [
{
name: "username"
value: dbConn.username
},
{
name: "DB_PASSWORD"
value: dbConn.password
},
]
}
}]
}
}
}
}
parameter: {
// +usage=Which image would you like to use for your service
// +short=i
image: string
// +usage=Referred db secret
// +insertSecretTo=dbConn
dbSecret?: string
}
dbConn: {
username: string
password: string
}
`
)
func newMockHTTP() *httptest.Server {
@@ -18,7 +18,6 @@ package application
import (
"context"
"fmt"
"time"
runtimev1alpha1 "github.com/crossplane/crossplane-runtime/apis/core/v1alpha1"
@@ -93,8 +92,8 @@ func (h *appHandler) applyAppManifests(ctx context.Context, comps []*types.Compo
return errors.WithMessage(err, "cannot dispatch packaged workload resources")
}
}
if checkAutoDetectComponent(comp.StandardWorkload) {
return fmt.Errorf("helm mode component doesn't specify workload, the traits attached to the helm mode component will fail to work")
if comp.InsertConfigNotReady {
continue
}
}
a := assemble.NewAppManifests(appRev).WithWorkloadOption(assemble.DiscoveryHelmBasedWorkload(ctx, h.r.Client))
@@ -108,13 +107,6 @@ func (h *appHandler) applyAppManifests(ctx context.Context, comps []*types.Compo
return nil
}
// checkAutoDetectComponent will check if the standardWorkload is empty,
// currently only Helm-based component is possible to be auto-detected
// TODO implement auto-detect mechanism
func checkAutoDetectComponent(wl *unstructured.Unstructured) bool {
return wl == nil || (len(wl.GetAPIVersion()) == 0 && len(wl.GetKind()) == 0)
}
func (h *appHandler) aggregateHealthStatus(appFile *appfile.Appfile) ([]common.ApplicationComponentStatus, bool, error) {
var appStatus []common.ApplicationComponentStatus
var healthy = true
@@ -131,7 +123,15 @@ func (h *appHandler) aggregateHealthStatus(appFile *appfile.Appfile) ([]common.A
pCtx process.Context
)
if wl.IsCloudResourceProducer() {
// this can help detect the componentManifest not ready and reconcile again
if wl.ConfigNotReady {
status.Healthy = false
status.Message = "secrets or configs not ready"
appStatus = append(appStatus, status)
healthy = false
continue
}
if wl.IsSecretProducer() {
outputSecretName, err = appfile.GetOutputSecretNames(wl)
if err != nil {
return nil, false, errors.WithMessagef(err, "app=%s, comp=%s, setting outputSecretName error", appFile.Name, wl.Name)
@@ -164,6 +164,13 @@ func (am *AppManifests) GroupAssembledManifests() (
return workloads, traits, scopes, nil
}
// checkAutoDetectComponent will check if the standardWorkload is empty,
// currently only Helm-based component is possible to be auto-detected
// TODO implement auto-detect mechanism
func checkAutoDetectComponent(wl *unstructured.Unstructured) bool {
return wl == nil || (len(wl.GetAPIVersion()) == 0 && len(wl.GetKind()) == 0)
}
func (am *AppManifests) assemble() {
am.complete()
klog.InfoS("Assemble manifests for application", "name", am.appName, "revision", am.AppRevision.GetName())
@@ -172,6 +179,13 @@ func (am *AppManifests) assemble() {
return
}
for _, comp := range am.componentManifests {
if comp.InsertConfigNotReady {
continue
}
if checkAutoDetectComponent(comp.StandardWorkload) {
klog.Warningf("component without specify workloadDef can not attach traits currently")
continue
}
compRevisionName := comp.RevisionName
compName := comp.Name
commonLabels := am.generateAndFilterCommonLabels(compName, compRevisionName)
@@ -59,12 +59,18 @@ func (h *appHandler) createResourcesConfigMap(ctx context.Context,
buf := &bytes.Buffer{}
for _, c := range comps {
if c.InsertConfigNotReady {
continue
}
r := c.StandardWorkload.DeepCopy()
r.SetName(c.Name)
r.SetNamespace(appRev.Namespace)
buf.Write(util.MustJSONMarshal(r))
}
for _, c := range comps {
if c.InsertConfigNotReady {
continue
}
for _, tr := range c.Traits {
r := tr.DeepCopy()
r.SetName(c.Name)
@@ -302,6 +308,9 @@ func DeepEqualRevision(old, new *v1beta1.ApplicationRevision) bool {
func (h *appHandler) handleComponentsRevision(ctx context.Context, compManifests []*types.ComponentManifest) error {
for _, cm := range compManifests {
if cm.InsertConfigNotReady {
continue
}
hash, err := computeComponentRevisionHash(cm)
if err != nil {
return err
@@ -471,6 +480,19 @@ func componentManifests2AppConfig(cms []*types.ComponentManifest) (runtime.RawEx
for i, cm := range cms {
acc := v1alpha2.ApplicationConfigurationComponent{}
acc.ComponentName = cm.Name
comp := &v1alpha2.Component{}
comp.SetGroupVersionKind(v1alpha2.ComponentGroupVersionKind)
comp.SetName(cm.Name)
if cm.InsertConfigNotReady {
// -- represent the component is not ready at all
acc.RevisionName = "--"
comps[i] = common.RawComponent{Raw: util.Object2RawExtension(comp)}
ac.Spec.Components[i] = acc
continue
}
acc.RevisionName = cm.RevisionName
acc.Traits = make([]v1alpha2.ComponentTrait, len(cm.Traits))
for j, t := range cm.Traits {
@@ -488,9 +510,7 @@ func componentManifests2AppConfig(cms []*types.ComponentManifest) (runtime.RawEx
},
}
}
comp := &v1alpha2.Component{}
comp.SetGroupVersionKind(v1alpha2.ComponentGroupVersionKind)
comp.SetName(cm.Name)
// this label is very important for handling component revision
util.AddLabels(comp, map[string]string{
oam.LabelComponentRevisionHash: cm.RevisionHash,
+5
View File
@@ -660,6 +660,11 @@ func AppConfig2ComponentManifests(acRaw runtime.RawExtension, comps []common.Raw
Name: acc.ComponentName,
RevisionName: acc.RevisionName,
}
if acc.RevisionName == "--" {
// generate ComponentManifest from appfile
cms[i] = cm
continue
}
if acc.ComponentName == "" && acc.RevisionName != "" {
cm.Name = ExtractComponentName(acc.RevisionName)
}
@@ -1,3 +1,4 @@
InsertConfigNotReady: false
Name: myweb
PackagedTraitResources: null
PackagedWorkloadResources: null