mirror of
https://github.com/kubevela/kubevela.git
synced 2026-08-18 03:56:36 +00:00
allow component to defer insert secret and config (#1869)
This commit is contained in:
@@ -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
|
||||
}
|
||||
|
||||
@@ -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
@@ -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
@@ -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
|
||||
}
|
||||
|
||||
@@ -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))
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user