mirror of
https://github.com/kubevela/kubevela.git
synced 2026-08-27 16:17:34 +00:00
refactor pkg/appfile (#1447)
Signed-off-by: roy wang <seiwy2010@gmail.com>
This commit is contained in:
@@ -0,0 +1,461 @@
|
||||
/*
|
||||
Copyright 2021 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 (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"regexp"
|
||||
"strings"
|
||||
|
||||
"cuelang.org/go/cue"
|
||||
"cuelang.org/go/cue/format"
|
||||
json2cue "cuelang.org/go/encoding/json"
|
||||
"github.com/crossplane/crossplane-runtime/apis/core/v1alpha1"
|
||||
"github.com/crossplane/crossplane-runtime/pkg/fieldpath"
|
||||
"github.com/pkg/errors"
|
||||
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||
"sigs.k8s.io/controller-runtime/pkg/client"
|
||||
|
||||
"github.com/oam-dev/kubevela/apis/core.oam.dev/common"
|
||||
"github.com/oam-dev/kubevela/apis/core.oam.dev/v1alpha2"
|
||||
"github.com/oam-dev/kubevela/apis/types"
|
||||
"github.com/oam-dev/kubevela/pkg/appfile/helm"
|
||||
"github.com/oam-dev/kubevela/pkg/dsl/definition"
|
||||
"github.com/oam-dev/kubevela/pkg/dsl/process"
|
||||
"github.com/oam-dev/kubevela/pkg/oam"
|
||||
"github.com/oam-dev/kubevela/pkg/oam/util"
|
||||
)
|
||||
|
||||
// constant error information
|
||||
const (
|
||||
errInvalidValueType = "require %q type parameter value"
|
||||
)
|
||||
|
||||
// Workload is component
|
||||
type Workload struct {
|
||||
Name string
|
||||
Type string
|
||||
CapabilityCategory types.CapabilityCategory
|
||||
Params map[string]interface{}
|
||||
Traits []*Trait
|
||||
Scopes []Scope
|
||||
FullTemplate *Template
|
||||
engine definition.AbstractEngine
|
||||
// OutputSecretName is the secret name which this workload will generate after it successfully generate a cloud resource
|
||||
OutputSecretName string
|
||||
// RequiredSecrets stores secret names which the workload needs from cloud resource component and its context
|
||||
RequiredSecrets []process.RequiredSecrets
|
||||
UserConfigs []map[string]string
|
||||
}
|
||||
|
||||
// GetUserConfigName get user config from AppFile, it will contain config file in it.
|
||||
func (wl *Workload) GetUserConfigName() string {
|
||||
if wl.Params == nil {
|
||||
return ""
|
||||
}
|
||||
t, ok := wl.Params[AppfileBuiltinConfig]
|
||||
if !ok {
|
||||
return ""
|
||||
}
|
||||
ts, ok := t.(string)
|
||||
if !ok {
|
||||
return ""
|
||||
}
|
||||
return ts
|
||||
}
|
||||
|
||||
// EvalContext eval workload template and set result to context
|
||||
func (wl *Workload) EvalContext(ctx process.Context) error {
|
||||
return wl.engine.Complete(ctx, wl.FullTemplate.TemplateStr, wl.Params)
|
||||
}
|
||||
|
||||
// EvalStatus eval workload status
|
||||
func (wl *Workload) EvalStatus(ctx process.Context, cli client.Client, ns string) (string, error) {
|
||||
return wl.engine.Status(ctx, cli, ns, wl.FullTemplate.CustomStatus)
|
||||
}
|
||||
|
||||
// EvalHealth eval workload health check
|
||||
func (wl *Workload) EvalHealth(ctx process.Context, client client.Client, namespace string) (bool, error) {
|
||||
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 {
|
||||
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 {
|
||||
requiredSecretTag := strings.TrimRight(InsertSecretToTag, "=")
|
||||
matched, err := regexp.Match(regexp.QuoteMeta(requiredSecretTag), []byte(wl.FullTemplate.TemplateStr))
|
||||
if err != nil || !matched {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
// Scope defines the scope of workload
|
||||
type Scope struct {
|
||||
Name string
|
||||
GVK schema.GroupVersionKind
|
||||
}
|
||||
|
||||
// Trait is ComponentTrait
|
||||
type Trait struct {
|
||||
// The Name is name of TraitDefinition, actually it's a type of the trait instance
|
||||
Name string
|
||||
CapabilityCategory types.CapabilityCategory
|
||||
Params map[string]interface{}
|
||||
|
||||
Template string
|
||||
HealthCheckPolicy string
|
||||
CustomStatusFormat string
|
||||
|
||||
FullTemplate *Template
|
||||
engine definition.AbstractEngine
|
||||
}
|
||||
|
||||
// EvalContext eval trait template and set result to context
|
||||
func (trait *Trait) EvalContext(ctx process.Context) error {
|
||||
return trait.engine.Complete(ctx, trait.Template, trait.Params)
|
||||
}
|
||||
|
||||
// EvalStatus eval trait status
|
||||
func (trait *Trait) EvalStatus(ctx process.Context, cli client.Client, ns string) (string, error) {
|
||||
return trait.engine.Status(ctx, cli, ns, trait.CustomStatusFormat)
|
||||
}
|
||||
|
||||
// EvalHealth eval trait health check
|
||||
func (trait *Trait) EvalHealth(ctx process.Context, client client.Client, namespace string) (bool, error) {
|
||||
return trait.engine.HealthCheck(ctx, client, namespace, trait.HealthCheckPolicy)
|
||||
}
|
||||
|
||||
// Appfile describes application
|
||||
type Appfile struct {
|
||||
Name string
|
||||
Namespace string
|
||||
RevisionName string
|
||||
Workloads []*Workload
|
||||
}
|
||||
|
||||
// TemplateValidate validate Template format
|
||||
func (af *Appfile) TemplateValidate() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// GenerateApplicationConfiguration converts an appFile to applicationConfig & Components
|
||||
func (af *Appfile) GenerateApplicationConfiguration() (*v1alpha2.ApplicationConfiguration,
|
||||
[]*v1alpha2.Component, error) {
|
||||
appconfig := &v1alpha2.ApplicationConfiguration{}
|
||||
appconfig.SetGroupVersionKind(v1alpha2.ApplicationConfigurationGroupVersionKind)
|
||||
appconfig.Name = af.Name
|
||||
appconfig.Namespace = af.Namespace
|
||||
|
||||
if appconfig.Labels == nil {
|
||||
appconfig.Labels = map[string]string{}
|
||||
}
|
||||
appconfig.Labels[oam.LabelAppName] = af.Name
|
||||
|
||||
var components []*v1alpha2.Component
|
||||
|
||||
for _, wl := range af.Workloads {
|
||||
var (
|
||||
comp *v1alpha2.Component
|
||||
acComp *v1alpha2.ApplicationConfigurationComponent
|
||||
err error
|
||||
)
|
||||
switch wl.CapabilityCategory {
|
||||
case types.HelmCategory:
|
||||
comp, acComp, err = generateComponentFromHelmModule(wl, af.Name, af.RevisionName, af.Namespace)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
case types.KubeCategory:
|
||||
comp, acComp, err = generateComponentFromKubeModule(wl, af.Name, af.RevisionName, af.Namespace)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
default:
|
||||
comp, acComp, err = generateComponentFromCUEModule(wl, af.Name, af.RevisionName, af.Namespace)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
}
|
||||
components = append(components, comp)
|
||||
appconfig.Spec.Components = append(appconfig.Spec.Components, *acComp)
|
||||
}
|
||||
return appconfig, components, nil
|
||||
}
|
||||
|
||||
// PrepareProcessContext prepares a DSL process Context
|
||||
func PrepareProcessContext(wl *Workload, applicationName, revision, namespace string) (process.Context, error) {
|
||||
pCtx := process.NewContext(namespace, wl.Name, applicationName, revision)
|
||||
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", applicationName, namespace)
|
||||
}
|
||||
return pCtx, nil
|
||||
}
|
||||
|
||||
func generateComponentFromCUEModule(wl *Workload, appName, revision, ns string) (*v1alpha2.Component, *v1alpha2.ApplicationConfigurationComponent, error) {
|
||||
var (
|
||||
outputSecretName string
|
||||
err error
|
||||
)
|
||||
if wl.IsCloudResourceProducer() {
|
||||
outputSecretName, err = GetOutputSecretNames(wl)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
wl.OutputSecretName = outputSecretName
|
||||
}
|
||||
pCtx, err := PrepareProcessContext(wl, appName, revision, ns)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
for _, tr := range wl.Traits {
|
||||
if err := tr.EvalContext(pCtx); err != nil {
|
||||
return nil, nil, errors.Wrapf(err, "evaluate template trait=%s app=%s", tr.Name, wl.Name)
|
||||
}
|
||||
}
|
||||
var comp *v1alpha2.Component
|
||||
var acComp *v1alpha2.ApplicationConfigurationComponent
|
||||
comp, acComp, err = evalWorkloadWithContext(pCtx, wl, appName, wl.Name)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
comp.Name = wl.Name
|
||||
acComp.ComponentName = comp.Name
|
||||
|
||||
for _, sc := range wl.Scopes {
|
||||
acComp.Scopes = append(acComp.Scopes, v1alpha2.ComponentScope{ScopeReference: v1alpha1.TypedReference{
|
||||
APIVersion: sc.GVK.GroupVersion().String(),
|
||||
Kind: sc.GVK.Kind,
|
||||
Name: sc.Name,
|
||||
}})
|
||||
}
|
||||
if len(comp.Namespace) == 0 {
|
||||
comp.Namespace = ns
|
||||
}
|
||||
if comp.Labels == nil {
|
||||
comp.Labels = map[string]string{}
|
||||
}
|
||||
comp.Labels[oam.LabelAppName] = appName
|
||||
comp.SetGroupVersionKind(v1alpha2.ComponentGroupVersionKind)
|
||||
|
||||
return comp, acComp, nil
|
||||
}
|
||||
|
||||
// evalWorkloadWithContext evaluate the workload's template to generate component and ACComponent
|
||||
func evalWorkloadWithContext(pCtx process.Context, wl *Workload, appName, compName string) (*v1alpha2.Component, *v1alpha2.ApplicationConfigurationComponent, error) {
|
||||
base, assists := pCtx.Output()
|
||||
componentWorkload, err := base.Unstructured()
|
||||
if err != nil {
|
||||
return nil, nil, errors.Wrapf(err, "evaluate base template component=%s app=%s", compName, appName)
|
||||
}
|
||||
|
||||
var commonLabels = definition.GetCommonLabels(pCtx.BaseContextLabels())
|
||||
util.AddLabels(componentWorkload, util.MergeMapOverrideWithDst(commonLabels, map[string]string{oam.WorkloadTypeLabel: wl.Type}))
|
||||
|
||||
component := &v1alpha2.Component{}
|
||||
// we need to marshal the workload to byte array before sending them to the k8s
|
||||
component.Spec.Workload = util.Object2RawExtension(componentWorkload)
|
||||
|
||||
acComponent := &v1alpha2.ApplicationConfigurationComponent{}
|
||||
for _, assist := range assists {
|
||||
tr, err := assist.Ins.Unstructured()
|
||||
if err != nil {
|
||||
return nil, nil, errors.Wrapf(err, "evaluate trait=%s template for component=%s app=%s", assist.Name, compName, appName)
|
||||
}
|
||||
labels := util.MergeMapOverrideWithDst(commonLabels, map[string]string{oam.TraitTypeLabel: assist.Type})
|
||||
if assist.Name != "" {
|
||||
labels[oam.TraitResource] = assist.Name
|
||||
}
|
||||
util.AddLabels(tr, labels)
|
||||
acComponent.Traits = append(acComponent.Traits, v1alpha2.ComponentTrait{
|
||||
// we need to marshal the trait to byte array before sending them to the k8s
|
||||
Trait: util.Object2RawExtension(tr),
|
||||
})
|
||||
}
|
||||
return component, acComponent, nil
|
||||
}
|
||||
|
||||
func generateComponentFromKubeModule(wl *Workload, appName, revision, ns string) (*v1alpha2.Component, *v1alpha2.ApplicationConfigurationComponent, error) {
|
||||
kubeObj := &unstructured.Unstructured{}
|
||||
err := json.Unmarshal(wl.FullTemplate.Kube.Template.Raw, kubeObj)
|
||||
if err != nil {
|
||||
return nil, nil, errors.Wrap(err, "cannot decode Kube template into K8s object")
|
||||
}
|
||||
|
||||
paramValues, err := resolveKubeParameters(wl.FullTemplate.Kube.Parameters, wl.Params)
|
||||
if err != nil {
|
||||
return nil, nil, errors.WithMessage(err, "cannot resolve parameter settings")
|
||||
}
|
||||
if err := setParameterValuesToKubeObj(kubeObj, paramValues); err != nil {
|
||||
return nil, nil, errors.WithMessage(err, "cannot set parameters value")
|
||||
}
|
||||
|
||||
// convert structured kube obj into CUE (go ==marshal==> json ==decoder==> cue)
|
||||
objRaw, err := kubeObj.MarshalJSON()
|
||||
if err != nil {
|
||||
return nil, nil, errors.Wrap(err, "cannot marshal kube object")
|
||||
}
|
||||
ins, err := json2cue.Decode(&cue.Runtime{}, "", objRaw)
|
||||
if err != nil {
|
||||
return nil, nil, errors.Wrap(err, "cannot decode object into CUE")
|
||||
}
|
||||
cueRaw, err := format.Node(ins.Value().Syntax())
|
||||
if err != nil {
|
||||
return nil, nil, errors.Wrap(err, "cannot format CUE")
|
||||
}
|
||||
|
||||
// NOTE a hack way to enable using CUE capabilities on KUBE schematic workload
|
||||
wl.FullTemplate.TemplateStr = fmt.Sprintf(`
|
||||
output: {
|
||||
%s
|
||||
}`, string(cueRaw))
|
||||
|
||||
// re-use the way CUE module generates comp & acComp
|
||||
comp, acComp, err := generateComponentFromCUEModule(wl, appName, revision, ns)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
return comp, acComp, nil
|
||||
}
|
||||
|
||||
// a helper map whose key is parameter name
|
||||
type paramValueSettings map[string]paramValueSetting
|
||||
type paramValueSetting struct {
|
||||
Value interface{}
|
||||
ValueType common.ParameterValueType
|
||||
FieldPaths []string
|
||||
}
|
||||
|
||||
func resolveKubeParameters(params []common.KubeParameter, settings map[string]interface{}) (paramValueSettings, error) {
|
||||
supported := map[string]*common.KubeParameter{}
|
||||
for _, p := range params {
|
||||
supported[p.Name] = p.DeepCopy()
|
||||
}
|
||||
|
||||
values := make(paramValueSettings)
|
||||
for name, v := range settings {
|
||||
// check unsupported parameter setting
|
||||
if supported[name] == nil {
|
||||
return nil, errors.Errorf("unsupported parameter %q", name)
|
||||
}
|
||||
// construct helper map
|
||||
values[name] = paramValueSetting{
|
||||
Value: v,
|
||||
ValueType: supported[name].ValueType,
|
||||
FieldPaths: supported[name].FieldPaths,
|
||||
}
|
||||
}
|
||||
|
||||
// check required parameter
|
||||
for _, p := range params {
|
||||
if p.Required != nil && *p.Required {
|
||||
if _, ok := values[p.Name]; !ok {
|
||||
return nil, errors.Errorf("require parameter %q", p.Name)
|
||||
}
|
||||
}
|
||||
}
|
||||
return values, nil
|
||||
}
|
||||
|
||||
func setParameterValuesToKubeObj(obj *unstructured.Unstructured, values paramValueSettings) error {
|
||||
paved := fieldpath.Pave(obj.Object)
|
||||
for paramName, v := range values {
|
||||
for _, f := range v.FieldPaths {
|
||||
switch v.ValueType {
|
||||
case common.StringType:
|
||||
vString, ok := v.Value.(string)
|
||||
if !ok {
|
||||
return errors.Errorf(errInvalidValueType, v.ValueType)
|
||||
}
|
||||
if err := paved.SetString(f, vString); err != nil {
|
||||
return errors.Wrapf(err, "cannot set parameter %q to field %q", paramName, f)
|
||||
}
|
||||
case common.NumberType:
|
||||
switch v.Value.(type) {
|
||||
case int, int8, int16, int32, int64, uint, uint8, uint16, uint32, uint64, float32, float64:
|
||||
if err := paved.SetValue(f, v.Value); err != nil {
|
||||
return errors.Wrapf(err, "cannot set parameter %q to field %q", paramName, f)
|
||||
}
|
||||
default:
|
||||
return errors.Errorf(errInvalidValueType, v.ValueType)
|
||||
}
|
||||
case common.BooleanType:
|
||||
vBoolean, ok := v.Value.(bool)
|
||||
if !ok {
|
||||
return errors.Errorf(errInvalidValueType, v.ValueType)
|
||||
}
|
||||
if err := paved.SetValue(f, vBoolean); err != nil {
|
||||
return errors.Wrapf(err, "cannot set parameter %q to field %q", paramName, f)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func generateComponentFromHelmModule(wl *Workload, appName, revision, ns string) (*v1alpha2.Component, *v1alpha2.ApplicationConfigurationComponent, error) {
|
||||
gv, err := schema.ParseGroupVersion(wl.FullTemplate.Reference.APIVersion)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
targetWorkloadGVK := gv.WithKind(wl.FullTemplate.Reference.Kind)
|
||||
|
||||
// NOTE this is a hack way to enable using CUE module capabilities on Helm module workload
|
||||
// construct an empty base workload according to its GVK
|
||||
wl.FullTemplate.TemplateStr = fmt.Sprintf(`
|
||||
output: {
|
||||
apiVersion: "%s"
|
||||
kind: "%s"
|
||||
}`, targetWorkloadGVK.GroupVersion().String(), targetWorkloadGVK.Kind)
|
||||
|
||||
// re-use the way CUE module generates comp & acComp
|
||||
comp, acComp, err := generateComponentFromCUEModule(wl, appName, revision, ns)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
release, repo, err := helm.RenderHelmReleaseAndHelmRepo(wl.FullTemplate.Helm, wl.Name, appName, ns, wl.Params)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
rlsBytes, err := json.Marshal(release.Object)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
repoBytes, err := json.Marshal(repo.Object)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
comp.Spec.Helm = &common.Helm{
|
||||
Release: runtime.RawExtension{Raw: rlsBytes},
|
||||
Repository: runtime.RawExtension{Raw: repoBytes},
|
||||
}
|
||||
return comp, acComp, nil
|
||||
}
|
||||
@@ -0,0 +1,575 @@
|
||||
/*
|
||||
Copyright 2020 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 (
|
||||
"fmt"
|
||||
"testing"
|
||||
|
||||
"github.com/crossplane/crossplane-runtime/pkg/test"
|
||||
"github.com/ghodss/yaml"
|
||||
"github.com/google/go-cmp/cmp"
|
||||
. "github.com/onsi/ginkgo"
|
||||
. "github.com/onsi/gomega"
|
||||
"github.com/pkg/errors"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"k8s.io/utils/pointer"
|
||||
|
||||
"github.com/oam-dev/kubevela/apis/core.oam.dev/common"
|
||||
"github.com/oam-dev/kubevela/apis/core.oam.dev/v1alpha2"
|
||||
oamtypes "github.com/oam-dev/kubevela/apis/types"
|
||||
"github.com/oam-dev/kubevela/pkg/dsl/definition"
|
||||
"github.com/oam-dev/kubevela/pkg/oam"
|
||||
"github.com/oam-dev/kubevela/pkg/oam/util"
|
||||
)
|
||||
|
||||
var _ = Describe("Test Helm schematic appfile", func() {
|
||||
var (
|
||||
appName = "test-app"
|
||||
compName = "test-comp"
|
||||
)
|
||||
|
||||
It("Test generate AppConfig resources from Helm schematic", func() {
|
||||
appFile := &Appfile{
|
||||
Name: appName,
|
||||
Namespace: "default",
|
||||
RevisionName: appName + "-v1",
|
||||
Workloads: []*Workload{
|
||||
{
|
||||
Name: compName,
|
||||
Type: "webapp-chart",
|
||||
CapabilityCategory: oamtypes.HelmCategory,
|
||||
Params: map[string]interface{}{
|
||||
"image": map[string]interface{}{
|
||||
"tag": "5.1.2",
|
||||
},
|
||||
},
|
||||
engine: definition.NewWorkloadAbstractEngine(compName, pd),
|
||||
Traits: []*Trait{
|
||||
{
|
||||
Name: "scaler",
|
||||
Params: map[string]interface{}{
|
||||
"replicas": float64(10),
|
||||
},
|
||||
engine: definition.NewTraitAbstractEngine("scaler", pd),
|
||||
Template: `
|
||||
outputs: scaler: {
|
||||
apiVersion: "core.oam.dev/v1alpha2"
|
||||
kind: "ManualScalerTrait"
|
||||
spec: {
|
||||
replicaCount: parameter.replicas
|
||||
}
|
||||
}
|
||||
parameter: {
|
||||
//+short=r
|
||||
replicas: *1 | int
|
||||
}
|
||||
`,
|
||||
},
|
||||
},
|
||||
FullTemplate: &Template{
|
||||
Reference: common.WorkloadGVK{
|
||||
APIVersion: "apps/v1",
|
||||
Kind: "Deployment",
|
||||
},
|
||||
Helm: &common.Helm{
|
||||
Release: util.Object2RawExtension(map[string]interface{}{
|
||||
"chart": map[string]interface{}{
|
||||
"spec": map[string]interface{}{
|
||||
"chart": "podinfo",
|
||||
"version": "5.1.4",
|
||||
},
|
||||
},
|
||||
}),
|
||||
Repository: util.Object2RawExtension(map[string]interface{}{
|
||||
"url": "http://oam.dev/catalog/",
|
||||
}),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
By("Generate ApplicationConfiguration and Components")
|
||||
ac, components, err := appFile.GenerateApplicationConfiguration()
|
||||
Expect(err).To(BeNil())
|
||||
|
||||
manuscaler := util.Object2RawExtension(&unstructured.Unstructured{
|
||||
Object: map[string]interface{}{
|
||||
"apiVersion": "core.oam.dev/v1alpha2",
|
||||
"kind": "ManualScalerTrait",
|
||||
"metadata": map[string]interface{}{
|
||||
"labels": map[string]interface{}{
|
||||
"app.oam.dev/component": compName,
|
||||
"app.oam.dev/name": appName,
|
||||
"trait.oam.dev/type": "scaler",
|
||||
"trait.oam.dev/resource": "scaler",
|
||||
"app.oam.dev/appRevision": appName + "-v1",
|
||||
},
|
||||
},
|
||||
"spec": map[string]interface{}{"replicaCount": int64(10)},
|
||||
},
|
||||
})
|
||||
expectAppConfig := &v1alpha2.ApplicationConfiguration{
|
||||
TypeMeta: metav1.TypeMeta{
|
||||
Kind: "ApplicationConfiguration",
|
||||
APIVersion: "core.oam.dev/v1alpha2",
|
||||
}, ObjectMeta: metav1.ObjectMeta{
|
||||
Name: appName,
|
||||
Namespace: "default",
|
||||
Labels: map[string]string{oam.LabelAppName: appName},
|
||||
},
|
||||
Spec: v1alpha2.ApplicationConfigurationSpec{
|
||||
Components: []v1alpha2.ApplicationConfigurationComponent{
|
||||
{
|
||||
ComponentName: compName,
|
||||
Traits: []v1alpha2.ComponentTrait{
|
||||
{
|
||||
Trait: manuscaler,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
expectComponent := &v1alpha2.Component{
|
||||
TypeMeta: metav1.TypeMeta{
|
||||
Kind: "Component",
|
||||
APIVersion: "core.oam.dev/v1alpha2",
|
||||
}, ObjectMeta: metav1.ObjectMeta{
|
||||
Name: compName,
|
||||
Namespace: "default",
|
||||
Labels: map[string]string{oam.LabelAppName: appName},
|
||||
},
|
||||
Spec: v1alpha2.ComponentSpec{
|
||||
Helm: &common.Helm{
|
||||
Release: util.Object2RawExtension(map[string]interface{}{
|
||||
"apiVersion": "helm.toolkit.fluxcd.io/v2beta1",
|
||||
"kind": "HelmRelease",
|
||||
"metadata": map[string]interface{}{
|
||||
"name": fmt.Sprintf("%s-%s", appName, compName),
|
||||
"namespace": "default",
|
||||
},
|
||||
"spec": map[string]interface{}{
|
||||
"chart": map[string]interface{}{
|
||||
"spec": map[string]interface{}{
|
||||
"sourceRef": map[string]interface{}{
|
||||
"kind": "HelmRepository",
|
||||
"name": fmt.Sprintf("%s-%s", appName, compName),
|
||||
"namespace": "default",
|
||||
},
|
||||
},
|
||||
},
|
||||
"interval": "5m0s",
|
||||
"values": map[string]interface{}{
|
||||
"image": map[string]interface{}{
|
||||
"tag": "5.1.2",
|
||||
},
|
||||
},
|
||||
},
|
||||
}),
|
||||
Repository: util.Object2RawExtension(map[string]interface{}{
|
||||
"apiVersion": "source.toolkit.fluxcd.io/v1beta1",
|
||||
"kind": "HelmRepository",
|
||||
"metadata": map[string]interface{}{
|
||||
"name": fmt.Sprintf("%s-%s", appName, compName),
|
||||
"namespace": "default",
|
||||
},
|
||||
"spec": map[string]interface{}{
|
||||
"url": "http://oam.dev/catalog/",
|
||||
},
|
||||
}),
|
||||
},
|
||||
Workload: util.Object2RawExtension(map[string]interface{}{
|
||||
"apiVersion": "apps/v1",
|
||||
"kind": "Deployment",
|
||||
"metadata": map[string]interface{}{
|
||||
"labels": map[string]interface{}{
|
||||
"workload.oam.dev/type": "webapp-chart",
|
||||
"app.oam.dev/component": compName,
|
||||
"app.oam.dev/name": appName,
|
||||
"app.oam.dev/appRevision": appName + "-v1",
|
||||
},
|
||||
},
|
||||
}),
|
||||
},
|
||||
}
|
||||
By("Verify expected ApplicationConfiguration")
|
||||
diff := cmp.Diff(ac, expectAppConfig)
|
||||
Expect(diff).Should(BeEmpty())
|
||||
By("Verify expected Component")
|
||||
diff = cmp.Diff(components[0], expectComponent)
|
||||
Expect(diff).ShouldNot(BeEmpty())
|
||||
})
|
||||
|
||||
})
|
||||
|
||||
var _ = Describe("Test Kube schematic appfile", func() {
|
||||
var (
|
||||
appName = "test-app"
|
||||
compName = "test-comp"
|
||||
)
|
||||
var testTemplate = func() runtime.RawExtension {
|
||||
yamlStr := `apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
spec:
|
||||
selector:
|
||||
matchLabels:
|
||||
app: nginx
|
||||
template:
|
||||
metadata:
|
||||
labels:
|
||||
app: nginx
|
||||
spec:
|
||||
containers:
|
||||
- name: nginx
|
||||
ports:
|
||||
- containerPort: 80 `
|
||||
b, _ := yaml.YAMLToJSON([]byte(yamlStr))
|
||||
return runtime.RawExtension{Raw: b}
|
||||
}
|
||||
var expectWorkload = func() runtime.RawExtension {
|
||||
yamlStr := `apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
spec:
|
||||
selector:
|
||||
matchLabels:
|
||||
app: nginx
|
||||
template:
|
||||
metadata:
|
||||
labels:
|
||||
app: nginx
|
||||
spec:
|
||||
containers:
|
||||
- name: nginx
|
||||
image: nginx:1.14.0
|
||||
ports:
|
||||
- containerPort: 80 `
|
||||
b, _ := yaml.YAMLToJSON([]byte(yamlStr))
|
||||
return runtime.RawExtension{Raw: b}
|
||||
}
|
||||
var testAppfile = func() *Appfile {
|
||||
return &Appfile{
|
||||
RevisionName: appName + "-v1",
|
||||
Name: appName,
|
||||
Namespace: "default",
|
||||
Workloads: []*Workload{
|
||||
{
|
||||
Name: compName,
|
||||
Type: "kube-worker",
|
||||
CapabilityCategory: oamtypes.KubeCategory,
|
||||
Params: map[string]interface{}{
|
||||
"image": "nginx:1.14.0",
|
||||
},
|
||||
engine: definition.NewWorkloadAbstractEngine(compName, pd),
|
||||
Traits: []*Trait{
|
||||
{
|
||||
Name: "scaler",
|
||||
Params: map[string]interface{}{
|
||||
"replicas": float64(10),
|
||||
},
|
||||
engine: definition.NewTraitAbstractEngine("scaler", pd),
|
||||
Template: `
|
||||
outputs: scaler: {
|
||||
apiVersion: "core.oam.dev/v1alpha2"
|
||||
kind: "ManualScalerTrait"
|
||||
spec: {
|
||||
replicaCount: parameter.replicas
|
||||
}
|
||||
}
|
||||
parameter: {
|
||||
//+short=r
|
||||
replicas: *1 | int
|
||||
}
|
||||
`,
|
||||
},
|
||||
},
|
||||
FullTemplate: &Template{
|
||||
Kube: &common.Kube{
|
||||
Template: testTemplate(),
|
||||
Parameters: []common.KubeParameter{
|
||||
{
|
||||
Name: "image",
|
||||
ValueType: common.StringType,
|
||||
Required: pointer.BoolPtr(true),
|
||||
FieldPaths: []string{"spec.template.spec.containers[0].image"},
|
||||
},
|
||||
},
|
||||
},
|
||||
Reference: common.WorkloadGVK{
|
||||
APIVersion: "apps/v1",
|
||||
Kind: "Deployment",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
manuscaler := util.Object2RawExtension(&unstructured.Unstructured{
|
||||
Object: map[string]interface{}{
|
||||
"apiVersion": "core.oam.dev/v1alpha2",
|
||||
"kind": "ManualScalerTrait",
|
||||
"metadata": map[string]interface{}{
|
||||
"labels": map[string]interface{}{
|
||||
"app.oam.dev/component": compName,
|
||||
"app.oam.dev/name": appName,
|
||||
"app.oam.dev/appRevision": appName + "-v1",
|
||||
"trait.oam.dev/type": "scaler",
|
||||
"trait.oam.dev/resource": "scaler",
|
||||
},
|
||||
},
|
||||
"spec": map[string]interface{}{"replicaCount": int64(10)},
|
||||
},
|
||||
})
|
||||
|
||||
It("Test generate AppConfig resources from Kube schematic", func() {
|
||||
By("Generate ApplicationConfiguration and Components")
|
||||
ac, components, err := testAppfile().GenerateApplicationConfiguration()
|
||||
Expect(err).To(BeNil())
|
||||
|
||||
expectAppConfig := &v1alpha2.ApplicationConfiguration{
|
||||
TypeMeta: metav1.TypeMeta{
|
||||
Kind: "ApplicationConfiguration",
|
||||
APIVersion: "core.oam.dev/v1alpha2",
|
||||
}, ObjectMeta: metav1.ObjectMeta{
|
||||
Name: appName,
|
||||
Namespace: "default",
|
||||
Labels: map[string]string{oam.LabelAppName: appName},
|
||||
},
|
||||
Spec: v1alpha2.ApplicationConfigurationSpec{
|
||||
Components: []v1alpha2.ApplicationConfigurationComponent{
|
||||
{
|
||||
ComponentName: compName,
|
||||
Traits: []v1alpha2.ComponentTrait{
|
||||
{
|
||||
Trait: manuscaler,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
expectComponent := &v1alpha2.Component{
|
||||
TypeMeta: metav1.TypeMeta{
|
||||
Kind: "Component",
|
||||
APIVersion: "core.oam.dev/v1alpha2",
|
||||
}, ObjectMeta: metav1.ObjectMeta{
|
||||
Name: compName,
|
||||
Namespace: "default",
|
||||
Labels: map[string]string{oam.LabelAppName: appName},
|
||||
},
|
||||
Spec: v1alpha2.ComponentSpec{
|
||||
Workload: expectWorkload(),
|
||||
},
|
||||
}
|
||||
By("Verify expected ApplicationConfiguration")
|
||||
diff := cmp.Diff(ac, expectAppConfig)
|
||||
Expect(diff).Should(BeEmpty())
|
||||
By("Verify expected Component")
|
||||
diff = cmp.Diff(components[0], expectComponent)
|
||||
Expect(diff).ShouldNot(BeEmpty())
|
||||
})
|
||||
|
||||
It("Test missing set required parameter", func() {
|
||||
appfile := testAppfile()
|
||||
// remove parameter settings
|
||||
appfile.Workloads[0].Params = nil
|
||||
_, _, err := appfile.GenerateApplicationConfiguration()
|
||||
|
||||
expectError := errors.WithMessage(errors.New(`require parameter "image"`), "cannot resolve parameter settings")
|
||||
diff := cmp.Diff(expectError, err, test.EquateErrors())
|
||||
Expect(diff).Should(BeEmpty())
|
||||
})
|
||||
})
|
||||
|
||||
func TestResolveKubeParameters(t *testing.T) {
|
||||
stringParam := &common.KubeParameter{
|
||||
Name: "strParam",
|
||||
ValueType: common.StringType,
|
||||
FieldPaths: []string{"spec"},
|
||||
}
|
||||
requiredParam := &common.KubeParameter{
|
||||
Name: "reqParam",
|
||||
Required: pointer.BoolPtr(true),
|
||||
ValueType: common.StringType,
|
||||
FieldPaths: []string{"spec"},
|
||||
}
|
||||
tests := map[string]struct {
|
||||
reason string
|
||||
params []common.KubeParameter
|
||||
settings map[string]interface{}
|
||||
want paramValueSettings
|
||||
wantErr error
|
||||
}{
|
||||
"EmptyParam": {
|
||||
reason: "Empty value settings and no error should be returned",
|
||||
want: make(paramValueSettings),
|
||||
},
|
||||
"UnsupportedParam": {
|
||||
reason: "An error shoulde be returned because of unsupported param",
|
||||
params: []common.KubeParameter{*stringParam},
|
||||
settings: map[string]interface{}{"unsupported": "invalid parameter"},
|
||||
want: nil,
|
||||
wantErr: errors.Errorf("unsupported parameter %q", "unsupported"),
|
||||
},
|
||||
"MissingRequiredParam": {
|
||||
reason: "An error should be returned because of missing required param",
|
||||
params: []common.KubeParameter{*stringParam, *requiredParam},
|
||||
settings: map[string]interface{}{"strParam": "string"},
|
||||
want: nil,
|
||||
wantErr: errors.Errorf("require parameter %q", "reqParam"),
|
||||
},
|
||||
"Succeed": {
|
||||
reason: "No error should be returned",
|
||||
params: []common.KubeParameter{*stringParam, *requiredParam},
|
||||
settings: map[string]interface{}{"strParam": "test", "reqParam": "test"},
|
||||
want: paramValueSettings{
|
||||
"strParam": paramValueSetting{
|
||||
Value: "test",
|
||||
ValueType: common.StringType,
|
||||
FieldPaths: stringParam.FieldPaths,
|
||||
},
|
||||
"reqParam": paramValueSetting{
|
||||
Value: "test",
|
||||
ValueType: common.StringType,
|
||||
FieldPaths: requiredParam.FieldPaths,
|
||||
},
|
||||
},
|
||||
wantErr: nil,
|
||||
},
|
||||
}
|
||||
|
||||
for tcName, tc := range tests {
|
||||
t.Run(tcName, func(t *testing.T) {
|
||||
result, err := resolveKubeParameters(tc.params, tc.settings)
|
||||
if diff := cmp.Diff(tc.want, result); diff != "" {
|
||||
t.Fatalf("\nresolveKubeParameters(...)(...) -want +get \nreason:%s\n%s\n", tc.reason, diff)
|
||||
}
|
||||
if diff := cmp.Diff(tc.wantErr, err, test.EquateErrors()); diff != "" {
|
||||
t.Fatalf("\nresolveKubeParameters(...)(...) -want +get \nreason:%s\n%s\n", tc.reason, diff)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func TestSetParameterValuesToKubeObj(t *testing.T) {
|
||||
tests := map[string]struct {
|
||||
reason string
|
||||
obj unstructured.Unstructured
|
||||
values paramValueSettings
|
||||
wantObj unstructured.Unstructured
|
||||
wantErr error
|
||||
}{
|
||||
"InvalidStringType": {
|
||||
reason: "An error should be returned",
|
||||
values: paramValueSettings{
|
||||
"strParam": paramValueSetting{
|
||||
Value: int32(100),
|
||||
ValueType: common.StringType,
|
||||
FieldPaths: []string{"spec.test"},
|
||||
},
|
||||
},
|
||||
wantErr: errors.Errorf(errInvalidValueType, common.StringType),
|
||||
},
|
||||
"InvalidNumberType": {
|
||||
reason: "An error should be returned",
|
||||
values: paramValueSettings{
|
||||
"intParam": paramValueSetting{
|
||||
Value: "test",
|
||||
ValueType: common.NumberType,
|
||||
FieldPaths: []string{"spec.test"},
|
||||
},
|
||||
},
|
||||
wantErr: errors.Errorf(errInvalidValueType, common.NumberType),
|
||||
},
|
||||
"InvalidBoolType": {
|
||||
reason: "An error should be returned",
|
||||
values: paramValueSettings{
|
||||
"boolParam": paramValueSetting{
|
||||
Value: "test",
|
||||
ValueType: common.BooleanType,
|
||||
FieldPaths: []string{"spec.test"},
|
||||
},
|
||||
},
|
||||
wantErr: errors.Errorf(errInvalidValueType, common.BooleanType),
|
||||
},
|
||||
"InvalidFieldPath": {
|
||||
reason: "An error should be returned",
|
||||
values: paramValueSettings{
|
||||
"strParam": paramValueSetting{
|
||||
Value: "test",
|
||||
ValueType: common.StringType,
|
||||
FieldPaths: []string{"spec[.test"}, // a invalid field path
|
||||
},
|
||||
},
|
||||
wantErr: errors.Wrap(errors.New(`cannot parse path "spec[.test": unterminated '[' at position 4`),
|
||||
`cannot set parameter "strParam" to field "spec[.test"`),
|
||||
},
|
||||
"Succeed": {
|
||||
reason: "No error should be returned",
|
||||
obj: unstructured.Unstructured{Object: make(map[string]interface{})},
|
||||
values: paramValueSettings{
|
||||
"strParam": paramValueSetting{
|
||||
Value: "test",
|
||||
ValueType: common.StringType,
|
||||
FieldPaths: []string{"spec.strField"},
|
||||
},
|
||||
"intParam": paramValueSetting{
|
||||
Value: 10,
|
||||
ValueType: common.NumberType,
|
||||
FieldPaths: []string{"spec.intField"},
|
||||
},
|
||||
"floatParam": paramValueSetting{
|
||||
Value: float64(10.01),
|
||||
ValueType: common.NumberType,
|
||||
FieldPaths: []string{"spec.floatField"},
|
||||
},
|
||||
"boolParam": paramValueSetting{
|
||||
Value: true,
|
||||
ValueType: common.BooleanType,
|
||||
FieldPaths: []string{"spec.boolField"},
|
||||
},
|
||||
},
|
||||
wantObj: unstructured.Unstructured{Object: map[string]interface{}{
|
||||
"spec": map[string]interface{}{
|
||||
"strField": "test",
|
||||
"intField": int64(10),
|
||||
"floatField": float64(10.01),
|
||||
"boolField": true,
|
||||
},
|
||||
}},
|
||||
},
|
||||
}
|
||||
|
||||
for tcName, tc := range tests {
|
||||
t.Run(tcName, func(t *testing.T) {
|
||||
obj := tc.obj.DeepCopy()
|
||||
err := setParameterValuesToKubeObj(obj, tc.values)
|
||||
if diff := cmp.Diff(tc.wantObj, *obj); diff != "" {
|
||||
t.Errorf("\nsetParameterValuesToKubeObj(...)error -want +get \nreason:%s\n%s\n", tc.reason, diff)
|
||||
}
|
||||
if diff := cmp.Diff(tc.wantErr, err, test.EquateErrors()); diff != "" {
|
||||
t.Errorf("\nsetParameterValuesToKubeObj(...)error -want +get \nreason:%s\n%s\n", tc.reason, diff)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,204 +0,0 @@
|
||||
/*
|
||||
Copyright 2021 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 (
|
||||
"testing"
|
||||
|
||||
"github.com/crossplane/crossplane-runtime/pkg/test"
|
||||
"github.com/google/go-cmp/cmp"
|
||||
"github.com/pkg/errors"
|
||||
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
|
||||
"k8s.io/utils/pointer"
|
||||
|
||||
"github.com/oam-dev/kubevela/apis/core.oam.dev/common"
|
||||
)
|
||||
|
||||
func TestResolveKubeParameters(t *testing.T) {
|
||||
stringParam := &common.KubeParameter{
|
||||
Name: "strParam",
|
||||
ValueType: common.StringType,
|
||||
FieldPaths: []string{"spec"},
|
||||
}
|
||||
requiredParam := &common.KubeParameter{
|
||||
Name: "reqParam",
|
||||
Required: pointer.BoolPtr(true),
|
||||
ValueType: common.StringType,
|
||||
FieldPaths: []string{"spec"},
|
||||
}
|
||||
tests := map[string]struct {
|
||||
reason string
|
||||
params []common.KubeParameter
|
||||
settings map[string]interface{}
|
||||
want paramValueSettings
|
||||
wantErr error
|
||||
}{
|
||||
"EmptyParam": {
|
||||
reason: "Empty value settings and no error should be returned",
|
||||
want: make(paramValueSettings),
|
||||
},
|
||||
"UnsupportedParam": {
|
||||
reason: "An error shoulde be returned because of unsupported param",
|
||||
params: []common.KubeParameter{*stringParam},
|
||||
settings: map[string]interface{}{"unsupported": "invalid parameter"},
|
||||
want: nil,
|
||||
wantErr: errors.Errorf("unsupported parameter %q", "unsupported"),
|
||||
},
|
||||
"MissingRequiredParam": {
|
||||
reason: "An error should be returned because of missing required param",
|
||||
params: []common.KubeParameter{*stringParam, *requiredParam},
|
||||
settings: map[string]interface{}{"strParam": "string"},
|
||||
want: nil,
|
||||
wantErr: errors.Errorf("require parameter %q", "reqParam"),
|
||||
},
|
||||
"Succeed": {
|
||||
reason: "No error should be returned",
|
||||
params: []common.KubeParameter{*stringParam, *requiredParam},
|
||||
settings: map[string]interface{}{"strParam": "test", "reqParam": "test"},
|
||||
want: paramValueSettings{
|
||||
"strParam": paramValueSetting{
|
||||
Value: "test",
|
||||
ValueType: common.StringType,
|
||||
FieldPaths: stringParam.FieldPaths,
|
||||
},
|
||||
"reqParam": paramValueSetting{
|
||||
Value: "test",
|
||||
ValueType: common.StringType,
|
||||
FieldPaths: requiredParam.FieldPaths,
|
||||
},
|
||||
},
|
||||
wantErr: nil,
|
||||
},
|
||||
}
|
||||
|
||||
for tcName, tc := range tests {
|
||||
t.Run(tcName, func(t *testing.T) {
|
||||
result, err := resolveKubeParameters(tc.params, tc.settings)
|
||||
if diff := cmp.Diff(tc.want, result); diff != "" {
|
||||
t.Fatalf("\nresolveKubeParameters(...)(...) -want +get \nreason:%s\n%s\n", tc.reason, diff)
|
||||
}
|
||||
if diff := cmp.Diff(tc.wantErr, err, test.EquateErrors()); diff != "" {
|
||||
t.Fatalf("\nresolveKubeParameters(...)(...) -want +get \nreason:%s\n%s\n", tc.reason, diff)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func TestSetParameterValuesToKubeObj(t *testing.T) {
|
||||
tests := map[string]struct {
|
||||
reason string
|
||||
obj unstructured.Unstructured
|
||||
values paramValueSettings
|
||||
wantObj unstructured.Unstructured
|
||||
wantErr error
|
||||
}{
|
||||
"InvalidStringType": {
|
||||
reason: "An error should be returned",
|
||||
values: paramValueSettings{
|
||||
"strParam": paramValueSetting{
|
||||
Value: int32(100),
|
||||
ValueType: common.StringType,
|
||||
FieldPaths: []string{"spec.test"},
|
||||
},
|
||||
},
|
||||
wantErr: errors.Errorf(errInvalidValueType, common.StringType),
|
||||
},
|
||||
"InvalidNumberType": {
|
||||
reason: "An error should be returned",
|
||||
values: paramValueSettings{
|
||||
"intParam": paramValueSetting{
|
||||
Value: "test",
|
||||
ValueType: common.NumberType,
|
||||
FieldPaths: []string{"spec.test"},
|
||||
},
|
||||
},
|
||||
wantErr: errors.Errorf(errInvalidValueType, common.NumberType),
|
||||
},
|
||||
"InvalidBoolType": {
|
||||
reason: "An error should be returned",
|
||||
values: paramValueSettings{
|
||||
"boolParam": paramValueSetting{
|
||||
Value: "test",
|
||||
ValueType: common.BooleanType,
|
||||
FieldPaths: []string{"spec.test"},
|
||||
},
|
||||
},
|
||||
wantErr: errors.Errorf(errInvalidValueType, common.BooleanType),
|
||||
},
|
||||
"InvalidFieldPath": {
|
||||
reason: "An error should be returned",
|
||||
values: paramValueSettings{
|
||||
"strParam": paramValueSetting{
|
||||
Value: "test",
|
||||
ValueType: common.StringType,
|
||||
FieldPaths: []string{"spec[.test"}, // a invalid field path
|
||||
},
|
||||
},
|
||||
wantErr: errors.Wrap(errors.New(`cannot parse path "spec[.test": unterminated '[' at position 4`),
|
||||
`cannot set parameter "strParam" to field "spec[.test"`),
|
||||
},
|
||||
"Succeed": {
|
||||
reason: "No error should be returned",
|
||||
obj: unstructured.Unstructured{Object: make(map[string]interface{})},
|
||||
values: paramValueSettings{
|
||||
"strParam": paramValueSetting{
|
||||
Value: "test",
|
||||
ValueType: common.StringType,
|
||||
FieldPaths: []string{"spec.strField"},
|
||||
},
|
||||
"intParam": paramValueSetting{
|
||||
Value: 10,
|
||||
ValueType: common.NumberType,
|
||||
FieldPaths: []string{"spec.intField"},
|
||||
},
|
||||
"floatParam": paramValueSetting{
|
||||
Value: float64(10.01),
|
||||
ValueType: common.NumberType,
|
||||
FieldPaths: []string{"spec.floatField"},
|
||||
},
|
||||
"boolParam": paramValueSetting{
|
||||
Value: true,
|
||||
ValueType: common.BooleanType,
|
||||
FieldPaths: []string{"spec.boolField"},
|
||||
},
|
||||
},
|
||||
wantObj: unstructured.Unstructured{Object: map[string]interface{}{
|
||||
"spec": map[string]interface{}{
|
||||
"strField": "test",
|
||||
"intField": int64(10),
|
||||
"floatField": float64(10.01),
|
||||
"boolField": true,
|
||||
},
|
||||
}},
|
||||
},
|
||||
}
|
||||
|
||||
for tcName, tc := range tests {
|
||||
t.Run(tcName, func(t *testing.T) {
|
||||
obj := tc.obj.DeepCopy()
|
||||
err := setParameterValuesToKubeObj(obj, tc.values)
|
||||
if diff := cmp.Diff(tc.wantObj, *obj); diff != "" {
|
||||
t.Errorf("\nsetParameterValuesToKubeObj(...)error -want +get \nreason:%s\n%s\n", tc.reason, diff)
|
||||
}
|
||||
if diff := cmp.Diff(tc.wantErr, err, test.EquateErrors()); diff != "" {
|
||||
t.Errorf("\nsetParameterValuesToKubeObj(...)error -want +get \nreason:%s\n%s\n", tc.reason, diff)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
}
|
||||
+84
-486
@@ -18,34 +18,21 @@ package appfile
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"regexp"
|
||||
"strings"
|
||||
|
||||
"cuelang.org/go/cue"
|
||||
"cuelang.org/go/cue/format"
|
||||
json2cue "cuelang.org/go/encoding/json"
|
||||
"github.com/crossplane/crossplane-runtime/apis/core/v1alpha1"
|
||||
"github.com/crossplane/crossplane-runtime/pkg/fieldpath"
|
||||
"github.com/pkg/errors"
|
||||
v1 "k8s.io/api/core/v1"
|
||||
kerrors "k8s.io/apimachinery/pkg/api/errors"
|
||||
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||
"sigs.k8s.io/controller-runtime/pkg/client"
|
||||
|
||||
"github.com/oam-dev/kubevela/apis/core.oam.dev/common"
|
||||
"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"
|
||||
"github.com/oam-dev/kubevela/pkg/appfile/helm"
|
||||
"github.com/oam-dev/kubevela/pkg/controller/utils"
|
||||
velacue "github.com/oam-dev/kubevela/pkg/cue"
|
||||
"github.com/oam-dev/kubevela/pkg/dsl/definition"
|
||||
"github.com/oam-dev/kubevela/pkg/dsl/process"
|
||||
"github.com/oam-dev/kubevela/pkg/oam"
|
||||
"github.com/oam-dev/kubevela/pkg/oam/discoverymapper"
|
||||
"github.com/oam-dev/kubevela/pkg/oam/util"
|
||||
)
|
||||
@@ -55,106 +42,6 @@ const (
|
||||
AppfileBuiltinConfig = "config"
|
||||
)
|
||||
|
||||
// constant error information
|
||||
const (
|
||||
errInvalidValueType = "require %q type parameter value"
|
||||
)
|
||||
|
||||
// Workload is component
|
||||
type Workload struct {
|
||||
Name string
|
||||
Type string
|
||||
CapabilityCategory types.CapabilityCategory
|
||||
Params map[string]interface{}
|
||||
Traits []*Trait
|
||||
Scopes []Scope
|
||||
FullTemplate *util.Template
|
||||
engine definition.AbstractEngine
|
||||
// OutputSecretName is the secret name which this workload will generate after it successfully generate a cloud resource
|
||||
OutputSecretName string
|
||||
// RequiredSecrets stores secret names which the workload needs from cloud resource component and its context
|
||||
RequiredSecrets []process.RequiredSecrets
|
||||
}
|
||||
|
||||
// GetUserConfigName get user config from AppFile, it will contain config file in it.
|
||||
func (wl *Workload) GetUserConfigName() string {
|
||||
if wl.Params == nil {
|
||||
return ""
|
||||
}
|
||||
t, ok := wl.Params[AppfileBuiltinConfig]
|
||||
if !ok {
|
||||
return ""
|
||||
}
|
||||
ts, ok := t.(string)
|
||||
if !ok {
|
||||
return ""
|
||||
}
|
||||
return ts
|
||||
}
|
||||
|
||||
// EvalContext eval workload template and set result to context
|
||||
func (wl *Workload) EvalContext(ctx process.Context) error {
|
||||
return wl.engine.Complete(ctx, wl.FullTemplate.TemplateStr, wl.Params)
|
||||
}
|
||||
|
||||
// EvalStatus eval workload status
|
||||
func (wl *Workload) EvalStatus(ctx process.Context, cli client.Client, ns string) (string, error) {
|
||||
return wl.engine.Status(ctx, cli, ns, wl.FullTemplate.CustomStatus)
|
||||
}
|
||||
|
||||
// EvalHealth eval workload health check
|
||||
func (wl *Workload) EvalHealth(ctx process.Context, client client.Client, namespace string) (bool, error) {
|
||||
return wl.engine.HealthCheck(ctx, client, namespace, wl.FullTemplate.Health)
|
||||
}
|
||||
|
||||
// Scope defines the scope of workload
|
||||
type Scope struct {
|
||||
Name string
|
||||
GVK schema.GroupVersionKind
|
||||
}
|
||||
|
||||
// Trait is ComponentTrait
|
||||
type Trait struct {
|
||||
// The Name is name of TraitDefinition, actually it's a type of the trait instance
|
||||
Name string
|
||||
CapabilityCategory types.CapabilityCategory
|
||||
Params map[string]interface{}
|
||||
|
||||
Template string
|
||||
HealthCheckPolicy string
|
||||
CustomStatusFormat string
|
||||
|
||||
FullTemplate *util.Template
|
||||
engine definition.AbstractEngine
|
||||
}
|
||||
|
||||
// EvalContext eval trait template and set result to context
|
||||
func (trait *Trait) EvalContext(ctx process.Context) error {
|
||||
return trait.engine.Complete(ctx, trait.Template, trait.Params)
|
||||
}
|
||||
|
||||
// EvalStatus eval trait status
|
||||
func (trait *Trait) EvalStatus(ctx process.Context, cli client.Client, ns string) (string, error) {
|
||||
return trait.engine.Status(ctx, cli, ns, trait.CustomStatusFormat)
|
||||
}
|
||||
|
||||
// EvalHealth eval trait health check
|
||||
func (trait *Trait) EvalHealth(ctx process.Context, client client.Client, namespace string) (bool, error) {
|
||||
return trait.engine.HealthCheck(ctx, client, namespace, trait.HealthCheckPolicy)
|
||||
}
|
||||
|
||||
// Appfile describes application
|
||||
type Appfile struct {
|
||||
Name string
|
||||
RevisionName string
|
||||
Workloads []*Workload
|
||||
}
|
||||
|
||||
// TemplateValidate validate Template format
|
||||
func (af *Appfile) TemplateValidate() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Parser is an application parser
|
||||
type Parser struct {
|
||||
client client.Client
|
||||
@@ -172,12 +59,16 @@ func NewApplicationParser(cli client.Client, dm discoverymapper.DiscoveryMapper,
|
||||
}
|
||||
|
||||
// GenerateAppFile converts an application to an Appfile
|
||||
func (p *Parser) GenerateAppFile(ctx context.Context, name string, app *v1beta1.Application) (*Appfile, error) {
|
||||
func (p *Parser) GenerateAppFile(ctx context.Context, app *v1beta1.Application) (*Appfile, error) {
|
||||
ns := app.Namespace
|
||||
appName := app.Name
|
||||
|
||||
appfile := new(Appfile)
|
||||
appfile.Name = name
|
||||
appfile.Name = appName
|
||||
appfile.Namespace = ns
|
||||
var wds []*Workload
|
||||
for _, comp := range app.Spec.Components {
|
||||
wd, err := p.parseWorkload(ctx, comp)
|
||||
wd, err := p.parseWorkload(ctx, comp, appName, ns)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -187,9 +78,10 @@ func (p *Parser) GenerateAppFile(ctx context.Context, name string, app *v1beta1.
|
||||
return appfile, nil
|
||||
}
|
||||
|
||||
func (p *Parser) parseWorkload(ctx context.Context, comp v1beta1.ApplicationComponent) (*Workload, error) {
|
||||
|
||||
templ, err := util.LoadTemplate(ctx, p.dm, p.client, comp.Type, types.TypeComponentDefinition)
|
||||
// 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) {
|
||||
templ, err := LoadTemplate(ctx, p.dm, p.client, comp.Type, types.TypeComponentDefinition)
|
||||
if err != nil && !kerrors.IsNotFound(err) {
|
||||
return nil, errors.WithMessagef(err, "fetch type of %s", comp.Name)
|
||||
}
|
||||
@@ -206,6 +98,34 @@ func (p *Parser) parseWorkload(ctx context.Context, comp v1beta1.ApplicationComp
|
||||
Params: settings,
|
||||
engine: definition.NewWorkloadAbstractEngine(comp.Name, p.pd),
|
||||
}
|
||||
|
||||
if workload.IsCloudResourceConsumer() {
|
||||
requiredSecrets, err := parseWorkloadInsertSecretTo(ctx, p.client, ns, workload)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
workload.RequiredSecrets = requiredSecrets
|
||||
}
|
||||
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
|
||||
}
|
||||
|
||||
for _, traitValue := range comp.Traits {
|
||||
properties, err := util.RawExtension2Map(&traitValue.Properties)
|
||||
if err != nil {
|
||||
@@ -219,7 +139,7 @@ func (p *Parser) parseWorkload(ctx context.Context, comp v1beta1.ApplicationComp
|
||||
workload.Traits = append(workload.Traits, trait)
|
||||
}
|
||||
for scopeType, instanceName := range comp.Scopes {
|
||||
gvk, err := util.GetScopeGVK(ctx, p.client, p.dm, scopeType)
|
||||
gvk, err := GetScopeGVK(ctx, p.client, p.dm, scopeType)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -232,7 +152,7 @@ func (p *Parser) parseWorkload(ctx context.Context, comp v1beta1.ApplicationComp
|
||||
}
|
||||
|
||||
func (p *Parser) parseTrait(ctx context.Context, name string, properties map[string]interface{}) (*Trait, error) {
|
||||
templ, err := util.LoadTemplate(ctx, p.dm, p.client, name, types.TypeTrait)
|
||||
templ, err := LoadTemplate(ctx, p.dm, p.client, name, types.TypeTrait)
|
||||
if kerrors.IsNotFound(err) {
|
||||
return nil, errors.Errorf("trait definition of %s not found", name)
|
||||
}
|
||||
@@ -251,322 +171,6 @@ func (p *Parser) parseTrait(ctx context.Context, name string, properties map[str
|
||||
}, nil
|
||||
}
|
||||
|
||||
// GenerateApplicationConfiguration converts an appFile to applicationConfig & Components
|
||||
func (p *Parser) GenerateApplicationConfiguration(app *Appfile, ns string) (*v1alpha2.ApplicationConfiguration,
|
||||
[]*v1alpha2.Component, error) {
|
||||
appconfig := &v1alpha2.ApplicationConfiguration{}
|
||||
appconfig.SetGroupVersionKind(v1alpha2.ApplicationConfigurationGroupVersionKind)
|
||||
appconfig.Name = app.Name
|
||||
appconfig.Namespace = ns
|
||||
|
||||
if appconfig.Labels == nil {
|
||||
appconfig.Labels = map[string]string{}
|
||||
}
|
||||
appconfig.Labels[oam.LabelAppName] = app.Name
|
||||
|
||||
var components []*v1alpha2.Component
|
||||
ctx := context.Background()
|
||||
|
||||
for _, wl := range app.Workloads {
|
||||
var (
|
||||
comp *v1alpha2.Component
|
||||
acComp *v1alpha2.ApplicationConfigurationComponent
|
||||
err error
|
||||
)
|
||||
|
||||
if wl.IsCloudResourceConsumer() {
|
||||
requiredSecrets, err := parseWorkloadInsertSecretTo(ctx, p.client, ns, wl)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
wl.RequiredSecrets = requiredSecrets
|
||||
}
|
||||
|
||||
switch wl.CapabilityCategory {
|
||||
case types.HelmCategory:
|
||||
comp, acComp, err = generateComponentFromHelmModule(p.client, wl, app.Name, app.RevisionName, ns)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
case types.KubeCategory:
|
||||
comp, acComp, err = generateComponentFromKubeModule(p.client, wl, app.Name, app.RevisionName, ns)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
default:
|
||||
comp, acComp, err = generateComponentFromCUEModule(p.client, wl, app.Name, app.RevisionName, ns)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
}
|
||||
components = append(components, comp)
|
||||
appconfig.Spec.Components = append(appconfig.Spec.Components, *acComp)
|
||||
}
|
||||
return appconfig, components, nil
|
||||
}
|
||||
|
||||
func generateComponentFromCUEModule(c client.Client, wl *Workload, appName, revision, ns string) (*v1alpha2.Component, *v1alpha2.ApplicationConfigurationComponent, error) {
|
||||
var (
|
||||
outputSecretName string
|
||||
err error
|
||||
)
|
||||
if wl.IsCloudResourceProducer() {
|
||||
outputSecretName, err = GetOutputSecretNames(wl)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
wl.OutputSecretName = outputSecretName
|
||||
}
|
||||
pCtx, err := PrepareProcessContext(c, wl, appName, revision, ns)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
for _, tr := range wl.Traits {
|
||||
if err := tr.EvalContext(pCtx); err != nil {
|
||||
return nil, nil, errors.Wrapf(err, "evaluate template trait=%s app=%s", tr.Name, wl.Name)
|
||||
}
|
||||
}
|
||||
var comp *v1alpha2.Component
|
||||
var acComp *v1alpha2.ApplicationConfigurationComponent
|
||||
comp, acComp, err = evalWorkloadWithContext(pCtx, wl, appName, wl.Name)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
comp.Name = wl.Name
|
||||
acComp.ComponentName = comp.Name
|
||||
|
||||
for _, sc := range wl.Scopes {
|
||||
acComp.Scopes = append(acComp.Scopes, v1alpha2.ComponentScope{ScopeReference: v1alpha1.TypedReference{
|
||||
APIVersion: sc.GVK.GroupVersion().String(),
|
||||
Kind: sc.GVK.Kind,
|
||||
Name: sc.Name,
|
||||
}})
|
||||
}
|
||||
if len(comp.Namespace) == 0 {
|
||||
comp.Namespace = ns
|
||||
}
|
||||
if comp.Labels == nil {
|
||||
comp.Labels = map[string]string{}
|
||||
}
|
||||
comp.Labels[oam.LabelAppName] = appName
|
||||
comp.SetGroupVersionKind(v1alpha2.ComponentGroupVersionKind)
|
||||
|
||||
return comp, acComp, nil
|
||||
}
|
||||
|
||||
func generateComponentFromKubeModule(c client.Client, wl *Workload, appName, revision, ns string) (*v1alpha2.Component, *v1alpha2.ApplicationConfigurationComponent, error) {
|
||||
kubeObj := &unstructured.Unstructured{}
|
||||
err := json.Unmarshal(wl.FullTemplate.Kube.Template.Raw, kubeObj)
|
||||
if err != nil {
|
||||
return nil, nil, errors.Wrap(err, "cannot decode Kube template into K8s object")
|
||||
}
|
||||
|
||||
paramValues, err := resolveKubeParameters(wl.FullTemplate.Kube.Parameters, wl.Params)
|
||||
if err != nil {
|
||||
return nil, nil, errors.WithMessage(err, "cannot resolve parameter settings")
|
||||
}
|
||||
if err := setParameterValuesToKubeObj(kubeObj, paramValues); err != nil {
|
||||
return nil, nil, errors.WithMessage(err, "cannot set parameters value")
|
||||
}
|
||||
|
||||
// convert structured kube obj into CUE (go ==marshal==> json ==decoder==> cue)
|
||||
objRaw, err := kubeObj.MarshalJSON()
|
||||
if err != nil {
|
||||
return nil, nil, errors.Wrap(err, "cannot marshal kube object")
|
||||
}
|
||||
ins, err := json2cue.Decode(&cue.Runtime{}, "", objRaw)
|
||||
if err != nil {
|
||||
return nil, nil, errors.Wrap(err, "cannot decode object into CUE")
|
||||
}
|
||||
cueRaw, err := format.Node(ins.Value().Syntax())
|
||||
if err != nil {
|
||||
return nil, nil, errors.Wrap(err, "cannot format CUE")
|
||||
}
|
||||
|
||||
// NOTE a hack way to enable using CUE capabilities on KUBE schematic workload
|
||||
wl.FullTemplate.TemplateStr = fmt.Sprintf(`
|
||||
output: {
|
||||
%s
|
||||
}`, string(cueRaw))
|
||||
|
||||
// re-use the way CUE module generates comp & acComp
|
||||
comp, acComp, err := generateComponentFromCUEModule(c, wl, appName, revision, ns)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
return comp, acComp, nil
|
||||
}
|
||||
|
||||
// a helper map whose key is parameter name
|
||||
type paramValueSettings map[string]paramValueSetting
|
||||
type paramValueSetting struct {
|
||||
Value interface{}
|
||||
ValueType common.ParameterValueType
|
||||
FieldPaths []string
|
||||
}
|
||||
|
||||
func resolveKubeParameters(params []common.KubeParameter, settings map[string]interface{}) (paramValueSettings, error) {
|
||||
supported := map[string]*common.KubeParameter{}
|
||||
for _, p := range params {
|
||||
supported[p.Name] = p.DeepCopy()
|
||||
}
|
||||
|
||||
values := make(paramValueSettings)
|
||||
for name, v := range settings {
|
||||
// check unsupported parameter setting
|
||||
if supported[name] == nil {
|
||||
return nil, errors.Errorf("unsupported parameter %q", name)
|
||||
}
|
||||
// construct helper map
|
||||
values[name] = paramValueSetting{
|
||||
Value: v,
|
||||
ValueType: supported[name].ValueType,
|
||||
FieldPaths: supported[name].FieldPaths,
|
||||
}
|
||||
}
|
||||
|
||||
// check required parameter
|
||||
for _, p := range params {
|
||||
if p.Required != nil && *p.Required {
|
||||
if _, ok := values[p.Name]; !ok {
|
||||
return nil, errors.Errorf("require parameter %q", p.Name)
|
||||
}
|
||||
}
|
||||
}
|
||||
return values, nil
|
||||
}
|
||||
|
||||
func setParameterValuesToKubeObj(obj *unstructured.Unstructured, values paramValueSettings) error {
|
||||
paved := fieldpath.Pave(obj.Object)
|
||||
for paramName, v := range values {
|
||||
for _, f := range v.FieldPaths {
|
||||
switch v.ValueType {
|
||||
case common.StringType:
|
||||
vString, ok := v.Value.(string)
|
||||
if !ok {
|
||||
return errors.Errorf(errInvalidValueType, v.ValueType)
|
||||
}
|
||||
if err := paved.SetString(f, vString); err != nil {
|
||||
return errors.Wrapf(err, "cannot set parameter %q to field %q", paramName, f)
|
||||
}
|
||||
case common.NumberType:
|
||||
switch v.Value.(type) {
|
||||
case int, int8, int16, int32, int64, uint, uint8, uint16, uint32, uint64, float32, float64:
|
||||
if err := paved.SetValue(f, v.Value); err != nil {
|
||||
return errors.Wrapf(err, "cannot set parameter %q to field %q", paramName, f)
|
||||
}
|
||||
default:
|
||||
return errors.Errorf(errInvalidValueType, v.ValueType)
|
||||
}
|
||||
case common.BooleanType:
|
||||
vBoolean, ok := v.Value.(bool)
|
||||
if !ok {
|
||||
return errors.Errorf(errInvalidValueType, v.ValueType)
|
||||
}
|
||||
if err := paved.SetValue(f, vBoolean); err != nil {
|
||||
return errors.Wrapf(err, "cannot set parameter %q to field %q", paramName, f)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func generateComponentFromHelmModule(c client.Client, wl *Workload, appName, revision, ns string) (*v1alpha2.Component, *v1alpha2.ApplicationConfigurationComponent, error) {
|
||||
gv, err := schema.ParseGroupVersion(wl.FullTemplate.Reference.APIVersion)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
targetWorkloadGVK := gv.WithKind(wl.FullTemplate.Reference.Kind)
|
||||
|
||||
// NOTE this is a hack way to enable using CUE module capabilities on Helm module workload
|
||||
// construct an empty base workload according to its GVK
|
||||
wl.FullTemplate.TemplateStr = fmt.Sprintf(`
|
||||
output: {
|
||||
apiVersion: "%s"
|
||||
kind: "%s"
|
||||
}`, targetWorkloadGVK.GroupVersion().String(), targetWorkloadGVK.Kind)
|
||||
|
||||
// re-use the way CUE module generates comp & acComp
|
||||
comp, acComp, err := generateComponentFromCUEModule(c, wl, appName, revision, ns)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
release, repo, err := helm.RenderHelmReleaseAndHelmRepo(wl.FullTemplate.Helm, wl.Name, appName, ns, wl.Params)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
rlsBytes, err := json.Marshal(release.Object)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
repoBytes, err := json.Marshal(repo.Object)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
comp.Spec.Helm = &common.Helm{
|
||||
Release: runtime.RawExtension{Raw: rlsBytes},
|
||||
Repository: runtime.RawExtension{Raw: repoBytes},
|
||||
}
|
||||
return comp, acComp, nil
|
||||
}
|
||||
|
||||
// evalWorkloadWithContext evaluate the workload's template to generate component and ACComponent
|
||||
func evalWorkloadWithContext(pCtx process.Context, wl *Workload, appName, compName string) (*v1alpha2.Component, *v1alpha2.ApplicationConfigurationComponent, error) {
|
||||
base, assists := pCtx.Output()
|
||||
componentWorkload, err := base.Unstructured()
|
||||
if err != nil {
|
||||
return nil, nil, errors.Wrapf(err, "evaluate base template component=%s app=%s", compName, appName)
|
||||
}
|
||||
|
||||
var commonLabels = definition.GetCommonLabels(pCtx.BaseContextLabels())
|
||||
util.AddLabels(componentWorkload, util.MergeMapOverrideWithDst(commonLabels, map[string]string{oam.WorkloadTypeLabel: wl.Type}))
|
||||
|
||||
component := &v1alpha2.Component{}
|
||||
// we need to marshal the workload to byte array before sending them to the k8s
|
||||
component.Spec.Workload = util.Object2RawExtension(componentWorkload)
|
||||
|
||||
acComponent := &v1alpha2.ApplicationConfigurationComponent{}
|
||||
for _, assist := range assists {
|
||||
tr, err := assist.Ins.Unstructured()
|
||||
if err != nil {
|
||||
return nil, nil, errors.Wrapf(err, "evaluate trait=%s template for component=%s app=%s", assist.Name, compName, appName)
|
||||
}
|
||||
labels := util.MergeMapOverrideWithDst(commonLabels, map[string]string{oam.TraitTypeLabel: assist.Type})
|
||||
if assist.Name != "" {
|
||||
labels[oam.TraitResource] = assist.Name
|
||||
}
|
||||
util.AddLabels(tr, labels)
|
||||
acComponent.Traits = append(acComponent.Traits, v1alpha2.ComponentTrait{
|
||||
// we need to marshal the trait to byte array before sending them to the k8s
|
||||
Trait: util.Object2RawExtension(tr),
|
||||
})
|
||||
}
|
||||
return component, acComponent, nil
|
||||
}
|
||||
|
||||
// PrepareProcessContext prepares a DSL process Context
|
||||
func PrepareProcessContext(k8sClient client.Client, wl *Workload, applicationName, revision, namespace string) (process.Context, error) {
|
||||
pCtx := process.NewContext(namespace, wl.Name, applicationName, revision)
|
||||
pCtx.InsertSecrets(wl.OutputSecretName, wl.RequiredSecrets)
|
||||
userConfig := wl.GetUserConfigName()
|
||||
if userConfig != "" {
|
||||
cg := config.Configmap{Client: k8sClient}
|
||||
// TODO(wonderflow): envName should not be namespace when we have serverside env
|
||||
var envName = namespace
|
||||
data, err := cg.GetConfigData(config.GenConfigMapName(applicationName, wl.Name, userConfig), envName)
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err, "get config=%s for app=%s in namespace=%s", userConfig, applicationName, namespace)
|
||||
}
|
||||
pCtx.SetConfigs(data)
|
||||
}
|
||||
if err := wl.EvalContext(pCtx); err != nil {
|
||||
return nil, errors.Wrapf(err, "evaluate base template app=%s in namespace=%s", applicationName, namespace)
|
||||
}
|
||||
return pCtx, nil
|
||||
}
|
||||
|
||||
// GetOutputSecretNames set all secret names, which are generated by cloud resource, to context
|
||||
func GetOutputSecretNames(workloads *Workload) (string, error) {
|
||||
secretName, err := getComponentSetting(process.OutputSecretName, workloads.Params)
|
||||
@@ -579,43 +183,54 @@ func GetOutputSecretNames(workloads *Workload) (string, error) {
|
||||
|
||||
func parseWorkloadInsertSecretTo(ctx context.Context, c client.Client, namespace string, wl *Workload) ([]process.RequiredSecrets, error) {
|
||||
var requiredSecret []process.RequiredSecrets
|
||||
api, err := utils.GenerateOpenAPISchemaFromDefinition(wl.Name, wl.FullTemplate.TemplateStr)
|
||||
cueStr := velacue.BaseTemplate + wl.FullTemplate.TemplateStr
|
||||
r := cue.Runtime{}
|
||||
ins, err := r.Compile("-", cueStr)
|
||||
if err != nil {
|
||||
if !errors.Is(err, errors.Errorf(utils.ErrNoSectionParameterInCue, wl.Name)) {
|
||||
return nil, nil
|
||||
}
|
||||
return nil, err
|
||||
return nil, errors.Wrap(err, "cannot compile CUE template")
|
||||
}
|
||||
|
||||
schema, err := utils.ConvertOpenAPISchema2SwaggerObject(api)
|
||||
params := ins.Lookup("parameter")
|
||||
if !params.Exists() {
|
||||
return nil, nil
|
||||
}
|
||||
paramsSt, err := params.Struct()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return nil, errors.Wrap(err, "cannot resolve parameters in CUE template")
|
||||
}
|
||||
for k, v := range schema.Properties {
|
||||
description := v.Value.Description
|
||||
if strings.Contains(description, utils.InsertSecretToTag) {
|
||||
contextName := strings.Split(description, utils.InsertSecretToTag)[1]
|
||||
contextName = strings.TrimSpace(contextName)
|
||||
secretNameInterface, err := getComponentSetting(k, wl.Params)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
for i := 0; i < paramsSt.Len(); i++ {
|
||||
fieldInfo := paramsSt.Field(i)
|
||||
fName := fieldInfo.Name
|
||||
cgs := fieldInfo.Value.Doc()
|
||||
for _, cg := range cgs {
|
||||
for _, comment := range cg.List {
|
||||
if comment == nil {
|
||||
continue
|
||||
}
|
||||
if strings.Contains(comment.Text, InsertSecretToTag) {
|
||||
contextName := strings.Split(comment.Text, InsertSecretToTag)[1]
|
||||
contextName = strings.TrimSpace(contextName)
|
||||
secretNameInterface, err := getComponentSetting(fName, wl.Params)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
secretName, ok := secretNameInterface.(string)
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("failed to convert secret name %v to string", secretNameInterface)
|
||||
}
|
||||
secretData, err := extractSecret(ctx, c, namespace, secretName)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
requiredSecret = append(requiredSecret, process.RequiredSecrets{
|
||||
Name: secretName,
|
||||
ContextName: contextName,
|
||||
Namespace: namespace,
|
||||
Data: secretData,
|
||||
})
|
||||
}
|
||||
}
|
||||
secretName, ok := secretNameInterface.(string)
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("failed to convert secret name %v to string", secretNameInterface)
|
||||
}
|
||||
secretData, err := extractSecret(ctx, c, namespace, secretName)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
requiredSecret = append(requiredSecret, process.RequiredSecrets{
|
||||
Name: secretName,
|
||||
ContextName: contextName,
|
||||
Namespace: namespace,
|
||||
Data: secretData,
|
||||
})
|
||||
}
|
||||
|
||||
}
|
||||
return requiredSecret, nil
|
||||
}
|
||||
@@ -642,20 +257,3 @@ func getComponentSetting(settingParamName string, params map[string]interface{})
|
||||
}
|
||||
return nil, fmt.Errorf("failed to get the value of component setting %s", settingParamName)
|
||||
}
|
||||
|
||||
// IsCloudResourceProducer checks whether a workload is cloud resource producer role
|
||||
func (wl *Workload) IsCloudResourceProducer() 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 {
|
||||
requiredSecretTag := strings.TrimRight(utils.InsertSecretToTag, "=")
|
||||
matched, err := regexp.Match(regexp.QuoteMeta(requiredSecretTag), []byte(wl.FullTemplate.TemplateStr))
|
||||
if err != nil || !matched {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
+16
-371
@@ -27,7 +27,6 @@ import (
|
||||
"github.com/google/go-cmp/cmp"
|
||||
. "github.com/onsi/ginkgo"
|
||||
. "github.com/onsi/gomega"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/stretchr/testify/assert"
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
@@ -35,12 +34,9 @@ import (
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||
"k8s.io/apimachinery/pkg/types"
|
||||
"k8s.io/utils/pointer"
|
||||
|
||||
"github.com/oam-dev/kubevela/apis/core.oam.dev/common"
|
||||
"github.com/oam-dev/kubevela/apis/core.oam.dev/v1alpha2"
|
||||
"github.com/oam-dev/kubevela/apis/core.oam.dev/v1beta1"
|
||||
oamtypes "github.com/oam-dev/kubevela/apis/types"
|
||||
"github.com/oam-dev/kubevela/pkg/dsl/definition"
|
||||
"github.com/oam-dev/kubevela/pkg/dsl/process"
|
||||
"github.com/oam-dev/kubevela/pkg/oam"
|
||||
@@ -48,7 +44,7 @@ import (
|
||||
)
|
||||
|
||||
var expectedExceptApp = &Appfile{
|
||||
Name: "test",
|
||||
Name: "application-sample",
|
||||
Workloads: []*Workload{
|
||||
{
|
||||
Name: "myweb",
|
||||
@@ -57,7 +53,7 @@ var expectedExceptApp = &Appfile{
|
||||
"image": "busybox",
|
||||
"cmd": []interface{}{"sleep", "1000"},
|
||||
},
|
||||
FullTemplate: &util.Template{
|
||||
FullTemplate: &Template{
|
||||
TemplateStr: `
|
||||
output: {
|
||||
apiVersion: "apps/v1"
|
||||
@@ -209,6 +205,7 @@ apiVersion: core.oam.dev/v1beta1
|
||||
kind: Application
|
||||
metadata:
|
||||
name: application-sample
|
||||
namespace: default
|
||||
spec:
|
||||
components:
|
||||
- name: myweb
|
||||
@@ -251,7 +248,7 @@ var _ = Describe("Test application parser", func() {
|
||||
},
|
||||
}
|
||||
|
||||
appfile, err := NewApplicationParser(&tclient, dm, pd).GenerateAppFile(context.TODO(), "test", &o)
|
||||
appfile, err := NewApplicationParser(&tclient, dm, pd).GenerateAppFile(context.TODO(), &o)
|
||||
Expect(err).ShouldNot(HaveOccurred())
|
||||
Expect(equal(expectedExceptApp, appfile)).Should(BeTrue())
|
||||
})
|
||||
@@ -291,6 +288,7 @@ var _ = Describe("Test appFile parser", func() {
|
||||
var TestApp = &Appfile{
|
||||
RevisionName: "test-v1",
|
||||
Name: "test",
|
||||
Namespace: "default",
|
||||
Workloads: []*Workload{
|
||||
{
|
||||
Name: "myweb",
|
||||
@@ -300,6 +298,10 @@ var _ = Describe("Test appFile parser", func() {
|
||||
"cmd": []interface{}{"sleep", "1000"},
|
||||
"config": "myconfig",
|
||||
},
|
||||
UserConfigs: []map[string]string{
|
||||
{"name": "c1", "value": "v1"},
|
||||
{"name": "c2", "value": "v2"},
|
||||
},
|
||||
Scopes: []Scope{
|
||||
{Name: "test-scope", GVK: schema.GroupVersionKind{
|
||||
Group: "core.oam.dev",
|
||||
@@ -308,7 +310,7 @@ var _ = Describe("Test appFile parser", func() {
|
||||
}},
|
||||
},
|
||||
engine: definition.NewWorkloadAbstractEngine("myweb", pd),
|
||||
FullTemplate: &util.Template{
|
||||
FullTemplate: &Template{
|
||||
TemplateStr: `
|
||||
output: {
|
||||
apiVersion: "apps/v1"
|
||||
@@ -382,7 +384,7 @@ var _ = Describe("Test appFile parser", func() {
|
||||
Data: map[string]string{"c1": "v1", "c2": "v2"},
|
||||
}
|
||||
Expect(k8sClient.Create(context.Background(), cm.DeepCopy())).Should(SatisfyAny(BeNil(), &util.AlreadyExistMatcher{}))
|
||||
ac, components, err := NewApplicationParser(k8sClient, dm, pd).GenerateApplicationConfiguration(TestApp, "default")
|
||||
ac, components, err := TestApp.GenerateApplicationConfiguration()
|
||||
Expect(err).To(BeNil())
|
||||
manuscaler := util.Object2RawExtension(&unstructured.Unstructured{
|
||||
Object: map[string]interface{}{
|
||||
@@ -499,6 +501,7 @@ var _ = Describe("Test appFile parser", func() {
|
||||
Expect(len(components)).To(BeEquivalentTo(1))
|
||||
Expect(components[0].ObjectMeta).To(BeEquivalentTo(expectComponent.ObjectMeta))
|
||||
Expect(components[0].TypeMeta).To(BeEquivalentTo(expectComponent.TypeMeta))
|
||||
By(string(components[0].Spec.Workload.Raw))
|
||||
Expect(components[0].Spec.Workload).Should(SatisfyAny(
|
||||
BeEquivalentTo(util.Object2RawExtension(expectWorkload)),
|
||||
BeEquivalentTo(util.Object2RawExtension(expectWorkloadOptional))))
|
||||
@@ -506,364 +509,6 @@ var _ = Describe("Test appFile parser", func() {
|
||||
|
||||
})
|
||||
|
||||
var _ = Describe("Test appfile parser to parse helm module", func() {
|
||||
var (
|
||||
appName = "test-app"
|
||||
compName = "test-comp"
|
||||
)
|
||||
|
||||
It("Test application containing helm module", func() {
|
||||
appFile := &Appfile{
|
||||
Name: appName,
|
||||
RevisionName: appName + "-v1",
|
||||
Workloads: []*Workload{
|
||||
{
|
||||
Name: compName,
|
||||
Type: "webapp-chart",
|
||||
CapabilityCategory: oamtypes.HelmCategory,
|
||||
Params: map[string]interface{}{
|
||||
"image": map[string]interface{}{
|
||||
"tag": "5.1.2",
|
||||
},
|
||||
},
|
||||
engine: definition.NewWorkloadAbstractEngine(compName, pd),
|
||||
Traits: []*Trait{
|
||||
{
|
||||
Name: "scaler",
|
||||
Params: map[string]interface{}{
|
||||
"replicas": float64(10),
|
||||
},
|
||||
engine: definition.NewTraitAbstractEngine("scaler", pd),
|
||||
Template: `
|
||||
outputs: scaler: {
|
||||
apiVersion: "core.oam.dev/v1alpha2"
|
||||
kind: "ManualScalerTrait"
|
||||
spec: {
|
||||
replicaCount: parameter.replicas
|
||||
}
|
||||
}
|
||||
parameter: {
|
||||
//+short=r
|
||||
replicas: *1 | int
|
||||
}
|
||||
`,
|
||||
},
|
||||
},
|
||||
FullTemplate: &util.Template{
|
||||
Reference: common.WorkloadGVK{
|
||||
APIVersion: "apps/v1",
|
||||
Kind: "Deployment",
|
||||
},
|
||||
Helm: &common.Helm{
|
||||
Release: util.Object2RawExtension(map[string]interface{}{
|
||||
"chart": map[string]interface{}{
|
||||
"spec": map[string]interface{}{
|
||||
"chart": "podinfo",
|
||||
"version": "5.1.4",
|
||||
},
|
||||
},
|
||||
}),
|
||||
Repository: util.Object2RawExtension(map[string]interface{}{
|
||||
"url": "http://oam.dev/catalog/",
|
||||
}),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
By("Generate ApplicationConfiguration and Components")
|
||||
ac, components, err := NewApplicationParser(k8sClient, dm, pd).GenerateApplicationConfiguration(appFile, "default")
|
||||
Expect(err).To(BeNil())
|
||||
|
||||
manuscaler := util.Object2RawExtension(&unstructured.Unstructured{
|
||||
Object: map[string]interface{}{
|
||||
"apiVersion": "core.oam.dev/v1alpha2",
|
||||
"kind": "ManualScalerTrait",
|
||||
"metadata": map[string]interface{}{
|
||||
"labels": map[string]interface{}{
|
||||
"app.oam.dev/component": compName,
|
||||
"app.oam.dev/name": appName,
|
||||
"trait.oam.dev/type": "scaler",
|
||||
"trait.oam.dev/resource": "scaler",
|
||||
"app.oam.dev/appRevision": appName + "-v1",
|
||||
},
|
||||
},
|
||||
"spec": map[string]interface{}{"replicaCount": int64(10)},
|
||||
},
|
||||
})
|
||||
expectAppConfig := &v1alpha2.ApplicationConfiguration{
|
||||
TypeMeta: metav1.TypeMeta{
|
||||
Kind: "ApplicationConfiguration",
|
||||
APIVersion: "core.oam.dev/v1alpha2",
|
||||
}, ObjectMeta: metav1.ObjectMeta{
|
||||
Name: appName,
|
||||
Namespace: "default",
|
||||
Labels: map[string]string{oam.LabelAppName: appName},
|
||||
},
|
||||
Spec: v1alpha2.ApplicationConfigurationSpec{
|
||||
Components: []v1alpha2.ApplicationConfigurationComponent{
|
||||
{
|
||||
ComponentName: compName,
|
||||
Traits: []v1alpha2.ComponentTrait{
|
||||
{
|
||||
Trait: manuscaler,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
expectComponent := &v1alpha2.Component{
|
||||
TypeMeta: metav1.TypeMeta{
|
||||
Kind: "Component",
|
||||
APIVersion: "core.oam.dev/v1alpha2",
|
||||
}, ObjectMeta: metav1.ObjectMeta{
|
||||
Name: compName,
|
||||
Namespace: "default",
|
||||
Labels: map[string]string{oam.LabelAppName: appName},
|
||||
},
|
||||
Spec: v1alpha2.ComponentSpec{
|
||||
Helm: &common.Helm{
|
||||
Release: util.Object2RawExtension(map[string]interface{}{
|
||||
"apiVersion": "helm.toolkit.fluxcd.io/v2beta1",
|
||||
"kind": "HelmRelease",
|
||||
"metadata": map[string]interface{}{
|
||||
"name": fmt.Sprintf("%s-%s", appName, compName),
|
||||
"namespace": "default",
|
||||
},
|
||||
"spec": map[string]interface{}{
|
||||
"chart": map[string]interface{}{
|
||||
"spec": map[string]interface{}{
|
||||
"sourceRef": map[string]interface{}{
|
||||
"kind": "HelmRepository",
|
||||
"name": fmt.Sprintf("%s-%s", appName, compName),
|
||||
"namespace": "default",
|
||||
},
|
||||
},
|
||||
},
|
||||
"interval": "5m0s",
|
||||
"values": map[string]interface{}{
|
||||
"image": map[string]interface{}{
|
||||
"tag": "5.1.2",
|
||||
},
|
||||
},
|
||||
},
|
||||
}),
|
||||
Repository: util.Object2RawExtension(map[string]interface{}{
|
||||
"apiVersion": "source.toolkit.fluxcd.io/v1beta1",
|
||||
"kind": "HelmRepository",
|
||||
"metadata": map[string]interface{}{
|
||||
"name": fmt.Sprintf("%s-%s", appName, compName),
|
||||
"namespace": "default",
|
||||
},
|
||||
"spec": map[string]interface{}{
|
||||
"url": "http://oam.dev/catalog/",
|
||||
},
|
||||
}),
|
||||
},
|
||||
Workload: util.Object2RawExtension(map[string]interface{}{
|
||||
"apiVersion": "apps/v1",
|
||||
"kind": "Deployment",
|
||||
"metadata": map[string]interface{}{
|
||||
"labels": map[string]interface{}{
|
||||
"workload.oam.dev/type": "webapp-chart",
|
||||
"app.oam.dev/component": compName,
|
||||
"app.oam.dev/name": appName,
|
||||
"app.oam.dev/appRevision": appName + "-v1",
|
||||
},
|
||||
},
|
||||
}),
|
||||
},
|
||||
}
|
||||
By("Verify expected ApplicationConfiguration")
|
||||
diff := cmp.Diff(ac, expectAppConfig)
|
||||
Expect(diff).Should(BeEmpty())
|
||||
By("Verify expected Component")
|
||||
diff = cmp.Diff(components[0], expectComponent)
|
||||
Expect(diff).ShouldNot(BeEmpty())
|
||||
})
|
||||
|
||||
})
|
||||
|
||||
var _ = Describe("Test appfile parser to parse kube module", func() {
|
||||
var (
|
||||
appName = "test-app"
|
||||
compName = "test-comp"
|
||||
)
|
||||
var testTemplate = func() runtime.RawExtension {
|
||||
yamlStr := `apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
spec:
|
||||
selector:
|
||||
matchLabels:
|
||||
app: nginx
|
||||
template:
|
||||
metadata:
|
||||
labels:
|
||||
app: nginx
|
||||
spec:
|
||||
containers:
|
||||
- name: nginx
|
||||
ports:
|
||||
- containerPort: 80 `
|
||||
b, _ := yaml.YAMLToJSON([]byte(yamlStr))
|
||||
return runtime.RawExtension{Raw: b}
|
||||
}
|
||||
var expectWorkload = func() runtime.RawExtension {
|
||||
yamlStr := `apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
spec:
|
||||
selector:
|
||||
matchLabels:
|
||||
app: nginx
|
||||
template:
|
||||
metadata:
|
||||
labels:
|
||||
app: nginx
|
||||
spec:
|
||||
containers:
|
||||
- name: nginx
|
||||
image: nginx:1.14.0
|
||||
ports:
|
||||
- containerPort: 80 `
|
||||
b, _ := yaml.YAMLToJSON([]byte(yamlStr))
|
||||
return runtime.RawExtension{Raw: b}
|
||||
}
|
||||
var testAppfile = func() *Appfile {
|
||||
return &Appfile{
|
||||
RevisionName: appName + "-v1",
|
||||
Name: appName,
|
||||
Workloads: []*Workload{
|
||||
{
|
||||
Name: compName,
|
||||
Type: "kube-worker",
|
||||
CapabilityCategory: oamtypes.KubeCategory,
|
||||
Params: map[string]interface{}{
|
||||
"image": "nginx:1.14.0",
|
||||
},
|
||||
engine: definition.NewWorkloadAbstractEngine(compName, pd),
|
||||
Traits: []*Trait{
|
||||
{
|
||||
Name: "scaler",
|
||||
Params: map[string]interface{}{
|
||||
"replicas": float64(10),
|
||||
},
|
||||
engine: definition.NewTraitAbstractEngine("scaler", pd),
|
||||
Template: `
|
||||
outputs: scaler: {
|
||||
apiVersion: "core.oam.dev/v1alpha2"
|
||||
kind: "ManualScalerTrait"
|
||||
spec: {
|
||||
replicaCount: parameter.replicas
|
||||
}
|
||||
}
|
||||
parameter: {
|
||||
//+short=r
|
||||
replicas: *1 | int
|
||||
}
|
||||
`,
|
||||
},
|
||||
},
|
||||
FullTemplate: &util.Template{
|
||||
Kube: &common.Kube{
|
||||
Template: testTemplate(),
|
||||
Parameters: []common.KubeParameter{
|
||||
{
|
||||
Name: "image",
|
||||
ValueType: common.StringType,
|
||||
Required: pointer.BoolPtr(true),
|
||||
FieldPaths: []string{"spec.template.spec.containers[0].image"},
|
||||
},
|
||||
},
|
||||
},
|
||||
Reference: common.WorkloadGVK{
|
||||
APIVersion: "apps/v1",
|
||||
Kind: "Deployment",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
manuscaler := util.Object2RawExtension(&unstructured.Unstructured{
|
||||
Object: map[string]interface{}{
|
||||
"apiVersion": "core.oam.dev/v1alpha2",
|
||||
"kind": "ManualScalerTrait",
|
||||
"metadata": map[string]interface{}{
|
||||
"labels": map[string]interface{}{
|
||||
"app.oam.dev/component": compName,
|
||||
"app.oam.dev/name": appName,
|
||||
"app.oam.dev/appRevision": appName + "-v1",
|
||||
"trait.oam.dev/type": "scaler",
|
||||
"trait.oam.dev/resource": "scaler",
|
||||
},
|
||||
},
|
||||
"spec": map[string]interface{}{"replicaCount": int64(10)},
|
||||
},
|
||||
})
|
||||
|
||||
It("Test application containing kube module", func() {
|
||||
By("Generate ApplicationConfiguration and Components")
|
||||
ac, components, err := NewApplicationParser(k8sClient, dm, pd).GenerateApplicationConfiguration(testAppfile(), "default")
|
||||
Expect(err).To(BeNil())
|
||||
|
||||
expectAppConfig := &v1alpha2.ApplicationConfiguration{
|
||||
TypeMeta: metav1.TypeMeta{
|
||||
Kind: "ApplicationConfiguration",
|
||||
APIVersion: "core.oam.dev/v1alpha2",
|
||||
}, ObjectMeta: metav1.ObjectMeta{
|
||||
Name: appName,
|
||||
Namespace: "default",
|
||||
Labels: map[string]string{oam.LabelAppName: appName},
|
||||
},
|
||||
Spec: v1alpha2.ApplicationConfigurationSpec{
|
||||
Components: []v1alpha2.ApplicationConfigurationComponent{
|
||||
{
|
||||
ComponentName: compName,
|
||||
Traits: []v1alpha2.ComponentTrait{
|
||||
{
|
||||
Trait: manuscaler,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
expectComponent := &v1alpha2.Component{
|
||||
TypeMeta: metav1.TypeMeta{
|
||||
Kind: "Component",
|
||||
APIVersion: "core.oam.dev/v1alpha2",
|
||||
}, ObjectMeta: metav1.ObjectMeta{
|
||||
Name: compName,
|
||||
Namespace: "default",
|
||||
Labels: map[string]string{oam.LabelAppName: appName},
|
||||
},
|
||||
Spec: v1alpha2.ComponentSpec{
|
||||
Workload: expectWorkload(),
|
||||
},
|
||||
}
|
||||
By("Verify expected ApplicationConfiguration")
|
||||
diff := cmp.Diff(ac, expectAppConfig)
|
||||
Expect(diff).Should(BeEmpty())
|
||||
By("Verify expected Component")
|
||||
diff = cmp.Diff(components[0], expectComponent)
|
||||
Expect(diff).ShouldNot(BeEmpty())
|
||||
})
|
||||
|
||||
It("Test missing set required parameter", func() {
|
||||
appfile := testAppfile()
|
||||
// remove parameter settings
|
||||
appfile.Workloads[0].Params = nil
|
||||
_, _, err := NewApplicationParser(k8sClient, dm, pd).GenerateApplicationConfiguration(appfile, "default")
|
||||
|
||||
expectError := errors.WithMessage(errors.New(`require parameter "image"`), "cannot resolve parameter settings")
|
||||
diff := cmp.Diff(expectError, err, test.EquateErrors())
|
||||
Expect(diff).Should(BeEmpty())
|
||||
})
|
||||
})
|
||||
|
||||
var _ = Describe("Test Get OutputSecretNames", func() {
|
||||
Context("Workload will generate cloud resource secret", func() {
|
||||
It("", func() {
|
||||
@@ -929,7 +574,7 @@ settings: {
|
||||
|
||||
wl := &Workload{
|
||||
Name: "abc",
|
||||
FullTemplate: &util.Template{TemplateStr: template},
|
||||
FullTemplate: &Template{TemplateStr: template},
|
||||
}
|
||||
By("call target function")
|
||||
secrets, err := parseWorkloadInsertSecretTo(ctx, k8sClient, ns, wl)
|
||||
@@ -969,7 +614,7 @@ parameter: {
|
||||
Params: map[string]interface{}{
|
||||
"dbSecret": targetSecretName,
|
||||
},
|
||||
FullTemplate: &util.Template{TemplateStr: template},
|
||||
FullTemplate: &Template{TemplateStr: template},
|
||||
}
|
||||
By("create secret")
|
||||
s := &corev1.Secret{
|
||||
@@ -1027,7 +672,7 @@ var _ = Describe("Test IsCloudResourceConsumer", func() {
|
||||
Context("Workload is a Cloud Resource consumer", func() {
|
||||
It("", func() {
|
||||
wl := &Workload{
|
||||
FullTemplate: &util.Template{TemplateStr: "// +insertSecretTo=dbConn"},
|
||||
FullTemplate: &Template{TemplateStr: "// +insertSecretTo=dbConn"},
|
||||
}
|
||||
Expect(wl.IsCloudResourceConsumer()).Should(Equal(true))
|
||||
})
|
||||
@@ -1036,7 +681,7 @@ var _ = Describe("Test IsCloudResourceConsumer", func() {
|
||||
Context("Workload is a Cloud Resource consumer", func() {
|
||||
It("", func() {
|
||||
wl := &Workload{
|
||||
FullTemplate: &util.Template{TemplateStr: "// +useage=dbConn"},
|
||||
FullTemplate: &Template{TemplateStr: "// +useage=dbConn"},
|
||||
}
|
||||
Expect(wl.IsCloudResourceProducer()).Should(Equal(false))
|
||||
})
|
||||
|
||||
@@ -14,7 +14,7 @@ See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package util
|
||||
package appfile
|
||||
|
||||
import (
|
||||
"context"
|
||||
@@ -32,6 +32,16 @@ import (
|
||||
"github.com/oam-dev/kubevela/apis/core.oam.dev/v1beta1"
|
||||
"github.com/oam-dev/kubevela/apis/types"
|
||||
"github.com/oam-dev/kubevela/pkg/oam/discoverymapper"
|
||||
oamutil "github.com/oam-dev/kubevela/pkg/oam/util"
|
||||
)
|
||||
|
||||
const (
|
||||
// UsageTag is usage comment annotation
|
||||
UsageTag = "+usage="
|
||||
// ShortTag is the short alias annotation
|
||||
ShortTag = "+short"
|
||||
// InsertSecretToTag marks the value should be set as an context
|
||||
InsertSecretToTag = "+insertSecretTo="
|
||||
)
|
||||
|
||||
// Template includes its string, health and its category
|
||||
@@ -54,12 +64,12 @@ func GetScopeGVK(ctx context.Context, cli client.Reader, dm discoverymapper.Disc
|
||||
name string) (schema.GroupVersionKind, error) {
|
||||
var gvk schema.GroupVersionKind
|
||||
sd := new(v1alpha2.ScopeDefinition)
|
||||
err := GetDefinition(ctx, cli, sd, name)
|
||||
err := oamutil.GetDefinition(ctx, cli, sd, name)
|
||||
if err != nil {
|
||||
return gvk, err
|
||||
}
|
||||
|
||||
return GetGVKFromDefinition(dm, sd.Spec.Reference)
|
||||
return oamutil.GetGVKFromDefinition(dm, sd.Spec.Reference)
|
||||
}
|
||||
|
||||
// LoadTemplate Get template according to key
|
||||
@@ -72,11 +82,11 @@ func LoadTemplate(ctx context.Context, dm discoverymapper.DiscoveryMapper, cli c
|
||||
var extension *runtime.RawExtension
|
||||
|
||||
cd := new(v1beta1.ComponentDefinition)
|
||||
err := GetDefinition(ctx, cli, cd, key)
|
||||
err := oamutil.GetDefinition(ctx, cli, cd, key)
|
||||
if err != nil {
|
||||
if kerrors.IsNotFound(err) {
|
||||
wd := new(v1beta1.WorkloadDefinition)
|
||||
if err := GetDefinition(ctx, cli, wd, key); err != nil {
|
||||
if err := oamutil.GetDefinition(ctx, cli, wd, key); err != nil {
|
||||
return nil, errors.WithMessagef(err, "LoadTemplate from workloadDefinition [%s] ", key)
|
||||
}
|
||||
schematic, status, extension = wd.Spec.Schematic, wd.Spec.Status, wd.Spec.Extension
|
||||
@@ -88,7 +98,7 @@ func LoadTemplate(ctx context.Context, dm discoverymapper.DiscoveryMapper, cli c
|
||||
tmpl.CapabilityCategory = types.TerraformCategory
|
||||
}
|
||||
tmpl.WorkloadDefinition = wd
|
||||
gvk, err := GetGVKFromDefinition(dm, wd.Spec.Reference)
|
||||
gvk, err := oamutil.GetGVKFromDefinition(dm, wd.Spec.Reference)
|
||||
if err != nil {
|
||||
return nil, errors.WithMessagef(err, "Get GVK from workload definition [%s]", key)
|
||||
}
|
||||
@@ -114,7 +124,7 @@ func LoadTemplate(ctx context.Context, dm discoverymapper.DiscoveryMapper, cli c
|
||||
|
||||
case types.TypeTrait:
|
||||
td := new(v1beta1.TraitDefinition)
|
||||
err := GetDefinition(ctx, cli, td, key)
|
||||
err := oamutil.GetDefinition(ctx, cli, td, key)
|
||||
if err != nil {
|
||||
return nil, errors.WithMessagef(err, "LoadTemplate [%s] ", key)
|
||||
}
|
||||
@@ -14,7 +14,7 @@ See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package util
|
||||
package appfile
|
||||
|
||||
import (
|
||||
"context"
|
||||
@@ -31,6 +31,7 @@ import (
|
||||
"github.com/oam-dev/kubevela/apis/core.oam.dev/v1beta1"
|
||||
"github.com/oam-dev/kubevela/apis/types"
|
||||
"github.com/oam-dev/kubevela/pkg/oam/mock"
|
||||
oamutil "github.com/oam-dev/kubevela/pkg/oam/util"
|
||||
)
|
||||
|
||||
func TestLoadComponentTemplate(t *testing.T) {
|
||||
@@ -100,7 +101,7 @@ spec:
|
||||
MockGet: func(ctx context.Context, key ktypes.NamespacedName, obj runtime.Object) error {
|
||||
switch o := obj.(type) {
|
||||
case *v1beta1.ComponentDefinition:
|
||||
cd, err := UnMarshalStringToComponentDefinition(componentDefintion)
|
||||
cd, err := oamutil.UnMarshalStringToComponentDefinition(componentDefintion)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -202,7 +203,7 @@ spec:
|
||||
MockGet: func(ctx context.Context, key ktypes.NamespacedName, obj runtime.Object) error {
|
||||
switch o := obj.(type) {
|
||||
case *v1alpha2.WorkloadDefinition:
|
||||
cd, err := UnMarshalStringToWorkloadDefinition(workloadDefintion)
|
||||
cd, err := oamutil.UnMarshalStringToWorkloadDefinition(workloadDefintion)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -324,7 +325,7 @@ spec:
|
||||
MockGet: func(ctx context.Context, key ktypes.NamespacedName, obj runtime.Object) error {
|
||||
switch o := obj.(type) {
|
||||
case *v1beta1.TraitDefinition:
|
||||
wd, err := UnMarshalStringToTraitDefinition(traitDefintion)
|
||||
wd, err := oamutil.UnMarshalStringToTraitDefinition(traitDefintion)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -114,7 +114,7 @@ func (r *Reconciler) Reconcile(req ctrl.Request) (ctrl.Result, error) {
|
||||
appParser := appfile.NewApplicationParser(r.Client, r.dm, r.pd)
|
||||
|
||||
ctx = oamutil.SetNamespaceInCtx(ctx, app.Namespace)
|
||||
generatedAppfile, err := appParser.GenerateAppFile(ctx, app.Name, app)
|
||||
generatedAppfile, err := appParser.GenerateAppFile(ctx, app)
|
||||
if err != nil {
|
||||
applog.Error(err, "[Handle Parse]")
|
||||
app.Status.SetConditions(errorCondition("Parsed", err))
|
||||
@@ -135,7 +135,7 @@ func (r *Reconciler) Reconcile(req ctrl.Request) (ctrl.Result, error) {
|
||||
|
||||
applog.Info("build template")
|
||||
// build template to applicationconfig & component
|
||||
ac, comps, err := appParser.GenerateApplicationConfiguration(generatedAppfile, app.Namespace)
|
||||
ac, comps, err := generatedAppfile.GenerateApplicationConfiguration()
|
||||
if err != nil {
|
||||
applog.Error(err, "[Handle GenerateApplicationConfiguration]")
|
||||
app.Status.SetConditions(errorCondition("Built", err))
|
||||
|
||||
@@ -224,9 +224,9 @@ var _ = Describe("test generate revision ", func() {
|
||||
ctx = util.SetNamespaceInCtx(ctx, app.Namespace)
|
||||
annoKey1 := "testKey1"
|
||||
app.SetAnnotations(map[string]string{annoKey1: "true"})
|
||||
generatedAppfile, err := appParser.GenerateAppFile(ctx, app.Name, &app)
|
||||
generatedAppfile, err := appParser.GenerateAppFile(ctx, &app)
|
||||
Expect(err).Should(Succeed())
|
||||
ac, comps, err = appParser.GenerateApplicationConfiguration(generatedAppfile, app.Namespace)
|
||||
ac, comps, err = generatedAppfile.GenerateApplicationConfiguration()
|
||||
Expect(err).Should(Succeed())
|
||||
handler.appfile = generatedAppfile
|
||||
Expect(ac.Namespace).Should(Equal(app.Namespace))
|
||||
@@ -320,9 +320,9 @@ var _ = Describe("test generate revision ", func() {
|
||||
}
|
||||
// persist the app
|
||||
Expect(k8sClient.Update(ctx, &app)).Should(SatisfyAny(BeNil(), &util.AlreadyExistMatcher{}))
|
||||
generatedAppfile, err = appParser.GenerateAppFile(ctx, app.Name, &app)
|
||||
generatedAppfile, err = appParser.GenerateAppFile(ctx, &app)
|
||||
Expect(err).Should(Succeed())
|
||||
ac, comps, err = appParser.GenerateApplicationConfiguration(generatedAppfile, app.Namespace)
|
||||
ac, comps, err = generatedAppfile.GenerateApplicationConfiguration()
|
||||
Expect(err).Should(Succeed())
|
||||
handler.appfile = generatedAppfile
|
||||
handler.app = &app
|
||||
@@ -372,9 +372,9 @@ var _ = Describe("test generate revision ", func() {
|
||||
ctx = util.SetNamespaceInCtx(ctx, app.Namespace)
|
||||
// mark the app as rollout
|
||||
app.SetAnnotations(map[string]string{oam.AnnotationAppRollout: strconv.FormatBool(true)})
|
||||
generatedAppfile, err := appParser.GenerateAppFile(ctx, app.Name, &app)
|
||||
generatedAppfile, err := appParser.GenerateAppFile(ctx, &app)
|
||||
Expect(err).Should(Succeed())
|
||||
ac, comps, err = appParser.GenerateApplicationConfiguration(generatedAppfile, app.Namespace)
|
||||
ac, comps, err = generatedAppfile.GenerateApplicationConfiguration()
|
||||
Expect(err).Should(Succeed())
|
||||
handler.appfile = generatedAppfile
|
||||
Expect(ac.Namespace).Should(Equal(app.Namespace))
|
||||
@@ -467,9 +467,9 @@ var _ = Describe("test generate revision ", func() {
|
||||
}
|
||||
// persist the app
|
||||
Expect(k8sClient.Update(ctx, &app)).Should(SatisfyAny(BeNil(), &util.AlreadyExistMatcher{}))
|
||||
generatedAppfile, err = appParser.GenerateAppFile(ctx, app.Name, &app)
|
||||
generatedAppfile, err = appParser.GenerateAppFile(ctx, &app)
|
||||
Expect(err).Should(Succeed())
|
||||
ac, comps, err = appParser.GenerateApplicationConfiguration(generatedAppfile, app.Namespace)
|
||||
ac, comps, err = generatedAppfile.GenerateApplicationConfiguration()
|
||||
Expect(err).Should(Succeed())
|
||||
handler.appfile = generatedAppfile
|
||||
handler.app = &app
|
||||
@@ -520,9 +520,9 @@ var _ = Describe("test generate revision ", func() {
|
||||
app.SetLabels(map[string]string{labelKey1: "true"})
|
||||
annoKey1 := "annoKey1"
|
||||
app.SetAnnotations(map[string]string{annoKey1: "true"})
|
||||
generatedAppfile, err := appParser.GenerateAppFile(ctx, app.Name, &app)
|
||||
generatedAppfile, err := appParser.GenerateAppFile(ctx, &app)
|
||||
Expect(err).Should(Succeed())
|
||||
ac, comps, err = appParser.GenerateApplicationConfiguration(generatedAppfile, app.Namespace)
|
||||
ac, comps, err = generatedAppfile.GenerateApplicationConfiguration()
|
||||
Expect(err).Should(Succeed())
|
||||
handler.appfile = generatedAppfile
|
||||
Expect(ac.Namespace).Should(Equal(app.Namespace))
|
||||
|
||||
@@ -37,6 +37,7 @@ 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"
|
||||
"github.com/oam-dev/kubevela/pkg/appfile/helm"
|
||||
mycue "github.com/oam-dev/kubevela/pkg/cue"
|
||||
"github.com/oam-dev/kubevela/pkg/dsl/definition"
|
||||
@@ -44,15 +45,6 @@ import (
|
||||
"github.com/oam-dev/kubevela/pkg/utils/common"
|
||||
)
|
||||
|
||||
const (
|
||||
// UsageTag is usage comment annotation
|
||||
UsageTag = "+usage="
|
||||
// ShortTag is the short alias annotation
|
||||
ShortTag = "+short"
|
||||
// InsertSecretToTag marks the value should be set as an context
|
||||
InsertSecretToTag = "+insertSecretTo="
|
||||
)
|
||||
|
||||
// ErrNoSectionParameterInCue means there is not parameter section in Cue template of a workload
|
||||
const ErrNoSectionParameterInCue = "capability %s doesn't contain section `parameter`"
|
||||
|
||||
@@ -96,9 +88,9 @@ func (def *CapabilityComponentDefinition) GetCapabilityObject(ctx context.Contex
|
||||
if err := k8sClient.Get(ctx, objectKey, wd); err != nil {
|
||||
return nil, fmt.Errorf("failed to get WorkloadDefinition that ComponentDefinition refers to")
|
||||
}
|
||||
capability, err = util.ConvertTemplateJSON2Object(name, wd.Spec.Extension, wd.Spec.Schematic)
|
||||
capability, err = appfile.ConvertTemplateJSON2Object(name, wd.Spec.Extension, wd.Spec.Schematic)
|
||||
default:
|
||||
capability, err = util.ConvertTemplateJSON2Object(name, componentDefinition.Spec.Extension, componentDefinition.Spec.Schematic)
|
||||
capability, err = appfile.ConvertTemplateJSON2Object(name, componentDefinition.Spec.Extension, componentDefinition.Spec.Schematic)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to convert ComponentDefinition to Capability Object")
|
||||
}
|
||||
@@ -205,7 +197,7 @@ func (def *CapabilityTraitDefinition) GetCapabilityObject(ctx context.Context, k
|
||||
return &capability, fmt.Errorf("failed to get WorkloadDefinition %s: %w", def.Name, err)
|
||||
}
|
||||
def.TraitDefinition = traitDefinition
|
||||
capability, err = util.ConvertTemplateJSON2Object(name, traitDefinition.Spec.Extension, traitDefinition.Spec.Schematic)
|
||||
capability, err = appfile.ConvertTemplateJSON2Object(name, traitDefinition.Spec.Extension, traitDefinition.Spec.Schematic)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to convert WorkloadDefinition to Capability Object")
|
||||
}
|
||||
@@ -384,11 +376,11 @@ func fixOpenAPISchema(name string, schema *openapi3.Schema) {
|
||||
}
|
||||
|
||||
description := schema.Description
|
||||
if strings.Contains(description, UsageTag) {
|
||||
description = strings.Split(description, UsageTag)[1]
|
||||
if strings.Contains(description, appfile.UsageTag) {
|
||||
description = strings.Split(description, appfile.UsageTag)[1]
|
||||
}
|
||||
if strings.Contains(description, ShortTag) {
|
||||
description = strings.Split(description, ShortTag)[0]
|
||||
if strings.Contains(description, appfile.ShortTag) {
|
||||
description = strings.Split(description, appfile.ShortTag)[0]
|
||||
description = strings.TrimSpace(description)
|
||||
}
|
||||
schema.Description = description
|
||||
|
||||
@@ -31,8 +31,8 @@ import (
|
||||
|
||||
"github.com/oam-dev/kubevela/apis/core.oam.dev/common"
|
||||
"github.com/oam-dev/kubevela/apis/types"
|
||||
"github.com/oam-dev/kubevela/pkg/appfile"
|
||||
mycue "github.com/oam-dev/kubevela/pkg/cue"
|
||||
"github.com/oam-dev/kubevela/pkg/oam/util"
|
||||
"github.com/oam-dev/kubevela/pkg/utils/system"
|
||||
)
|
||||
|
||||
@@ -74,7 +74,7 @@ func TestGetOpenAPISchema(t *testing.T) {
|
||||
Template: string(data),
|
||||
},
|
||||
}
|
||||
capability, _ := util.ConvertTemplateJSON2Object(tc.name, nil, schematic)
|
||||
capability, _ := appfile.ConvertTemplateJSON2Object(tc.name, nil, schematic)
|
||||
schema, err := getOpenAPISchema(capability, pd)
|
||||
if diff := cmp.Diff(tc.want.err, err, test.EquateErrors()); diff != "" {
|
||||
t.Errorf("\n%s\ngetOpenAPISchema(...): -want error, +got error:\n%s", tc.reason, diff)
|
||||
|
||||
@@ -30,7 +30,7 @@ func (h *ValidatingHandler) ValidateCreate(ctx context.Context, app *v1beta1.App
|
||||
var componentErrs field.ErrorList
|
||||
// try to generate an app file
|
||||
appParser := appfile.NewApplicationParser(h.Client, h.dm, h.pd)
|
||||
if _, err := appParser.GenerateAppFile(ctx, app.Name, app); err != nil {
|
||||
if _, err := appParser.GenerateAppFile(ctx, app); err != nil {
|
||||
componentErrs = append(componentErrs, field.Invalid(field.NewPath("spec"), app, err.Error()))
|
||||
}
|
||||
return componentErrs
|
||||
|
||||
@@ -31,9 +31,9 @@ import (
|
||||
"sigs.k8s.io/controller-runtime/pkg/webhook/admission"
|
||||
|
||||
"github.com/oam-dev/kubevela/apis/core.oam.dev/v1beta1"
|
||||
"github.com/oam-dev/kubevela/pkg/appfile"
|
||||
controller "github.com/oam-dev/kubevela/pkg/controller/core.oam.dev"
|
||||
"github.com/oam-dev/kubevela/pkg/oam/discoverymapper"
|
||||
"github.com/oam-dev/kubevela/pkg/oam/util"
|
||||
)
|
||||
|
||||
const (
|
||||
@@ -134,7 +134,7 @@ func ValidateDefinitionReference(_ context.Context, td v1beta1.TraitDefinition)
|
||||
if len(td.Spec.Reference.Name) > 0 {
|
||||
return nil
|
||||
}
|
||||
tmp, err := util.NewTemplate(td.Spec.Schematic, td.Spec.Status, td.Spec.Extension)
|
||||
tmp, err := appfile.NewTemplate(td.Spec.Schematic, td.Spec.Status, td.Spec.Extension)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, errValidateDefRef)
|
||||
}
|
||||
|
||||
@@ -63,7 +63,7 @@ func ApplyTerraform(app *v1beta1.Application, k8sClient client.Client, ioStream
|
||||
appParser := appfile.NewApplicationParser(k8sClient, dm, pd)
|
||||
|
||||
ctx := util2.SetNamespaceInCtx(context.Background(), namespace)
|
||||
appFile, err := appParser.GenerateAppFile(ctx, app.Name, app)
|
||||
appFile, err := appParser.GenerateAppFile(ctx, app)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to parse appfile: %w", err)
|
||||
}
|
||||
@@ -83,7 +83,7 @@ func ApplyTerraform(app *v1beta1.Application, k8sClient client.Client, ioStream
|
||||
name := wl.Name
|
||||
ioStream.Infof("\nApplying cloud resources %s\n", name)
|
||||
|
||||
tf, err := getTerraformJSONFiles(k8sClient, wl, appFile.Name, revisionName, namespace)
|
||||
tf, err := getTerraformJSONFiles(wl, appFile.Name, revisionName, namespace)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to get Terraform JSON files from workload %s: %w", name, err)
|
||||
}
|
||||
@@ -197,8 +197,8 @@ func generateSecretFromTerraformOutput(k8sClient client.Client, outputList []str
|
||||
}
|
||||
|
||||
// getTerraformJSONFiles gets Terraform JSON files or modules from workload
|
||||
func getTerraformJSONFiles(k8sClient client.Client, wl *appfile.Workload, applicationName, revisionName string, namespace string) ([]byte, error) {
|
||||
pCtx, err := appfile.PrepareProcessContext(k8sClient, wl, applicationName, revisionName, namespace)
|
||||
func getTerraformJSONFiles(wl *appfile.Workload, applicationName, revisionName string, namespace string) ([]byte, error) {
|
||||
pCtx, err := appfile.PrepareProcessContext(wl, applicationName, revisionName, namespace)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
@@ -102,12 +102,12 @@ func NewDryRunCommand(c common.Args, ioStreams cmdutil.IOStreams) *cobra.Command
|
||||
|
||||
ctx := oamutil.SetNamespaceInCtx(context.Background(), velaEnv.Namespace)
|
||||
|
||||
appFile, err := parser.GenerateAppFile(ctx, app.Name, app)
|
||||
appFile, err := parser.GenerateAppFile(ctx, app)
|
||||
if err != nil {
|
||||
return errors.WithMessage(err, "generate appFile")
|
||||
}
|
||||
|
||||
ac, comps, err := parser.GenerateApplicationConfiguration(appFile, app.Namespace)
|
||||
ac, comps, err := appFile.GenerateApplicationConfiguration()
|
||||
if err != nil {
|
||||
return errors.WithMessage(err, "generate OAM objects")
|
||||
}
|
||||
|
||||
@@ -30,6 +30,7 @@ import (
|
||||
commontypes "github.com/oam-dev/kubevela/apis/core.oam.dev/common"
|
||||
"github.com/oam-dev/kubevela/apis/core.oam.dev/v1beta1"
|
||||
"github.com/oam-dev/kubevela/apis/types"
|
||||
"github.com/oam-dev/kubevela/pkg/appfile"
|
||||
"github.com/oam-dev/kubevela/pkg/cue"
|
||||
"github.com/oam-dev/kubevela/pkg/oam/discoverymapper"
|
||||
"github.com/oam-dev/kubevela/pkg/oam/util"
|
||||
@@ -185,7 +186,7 @@ func GetDescription(annotation map[string]string) string {
|
||||
|
||||
// HandleTemplate will handle definition template to capability
|
||||
func HandleTemplate(in *runtime.RawExtension, schematic *commontypes.Schematic, name string) (types.Capability, error) {
|
||||
tmp, err := util.ConvertTemplateJSON2Object(name, in, schematic)
|
||||
tmp, err := appfile.ConvertTemplateJSON2Object(name, in, schematic)
|
||||
if err != nil {
|
||||
return types.Capability{}, err
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user