2021-04-11 14:10:16 +08:00
|
|
|
/*
|
|
|
|
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 (
|
2021-07-02 15:09:45 +08:00
|
|
|
"context"
|
2021-04-11 14:10:16 +08:00
|
|
|
"encoding/json"
|
|
|
|
"fmt"
|
|
|
|
"regexp"
|
|
|
|
"strings"
|
|
|
|
|
2021-07-27 19:22:05 +08:00
|
|
|
"github.com/oam-dev/kubevela/pkg/workflow/providers"
|
|
|
|
"github.com/oam-dev/kubevela/pkg/workflow/providers/kube"
|
|
|
|
|
2021-07-22 18:53:30 +08:00
|
|
|
"github.com/oam-dev/kubevela/apis/core.oam.dev/v1beta1"
|
|
|
|
"github.com/oam-dev/kubevela/pkg/cue/packages"
|
|
|
|
"github.com/oam-dev/kubevela/pkg/oam/discoverymapper"
|
|
|
|
"github.com/oam-dev/kubevela/pkg/workflow/tasks"
|
|
|
|
wfTypes "github.com/oam-dev/kubevela/pkg/workflow/types"
|
|
|
|
|
2021-07-02 15:09:45 +08:00
|
|
|
"github.com/oam-dev/kubevela/pkg/appfile/config"
|
|
|
|
|
2021-04-11 14:10:16 +08:00
|
|
|
"cuelang.org/go/cue"
|
|
|
|
"cuelang.org/go/cue/format"
|
|
|
|
json2cue "cuelang.org/go/encoding/json"
|
|
|
|
"github.com/crossplane/crossplane-runtime/pkg/fieldpath"
|
2021-04-30 16:28:00 +08:00
|
|
|
terraformapi "github.com/oam-dev/terraform-controller/api/v1beta1"
|
2021-04-11 14:10:16 +08:00
|
|
|
"github.com/pkg/errors"
|
2021-06-24 15:06:58 +08:00
|
|
|
corev1 "k8s.io/api/core/v1"
|
2021-04-30 16:28:00 +08:00
|
|
|
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
2021-04-11 14:10:16 +08:00
|
|
|
"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/types"
|
|
|
|
"github.com/oam-dev/kubevela/pkg/appfile/helm"
|
2021-06-02 15:37:06 +08:00
|
|
|
"github.com/oam-dev/kubevela/pkg/cue/definition"
|
|
|
|
"github.com/oam-dev/kubevela/pkg/cue/process"
|
2021-04-11 14:10:16 +08:00
|
|
|
"github.com/oam-dev/kubevela/pkg/oam"
|
|
|
|
"github.com/oam-dev/kubevela/pkg/oam/util"
|
|
|
|
)
|
|
|
|
|
|
|
|
// constant error information
|
|
|
|
const (
|
2021-04-30 16:28:00 +08:00
|
|
|
errInvalidValueType = "require %q type parameter value"
|
|
|
|
errTerraformConfigurationIsNotSet = "terraform configuration is not set"
|
|
|
|
errFailToConvertTerraformComponentProperties = "failed to convert Terraform component properties"
|
|
|
|
errTerraformNameOfWriteConnectionSecretToRefNotSet = "the name of writeConnectionSecretToRef of terraform component is not set"
|
2021-04-11 14:10:16 +08:00
|
|
|
)
|
|
|
|
|
2021-04-30 16:28:00 +08:00
|
|
|
// WriteConnectionSecretToRefKey is used to create a secret for cloud resource connection
|
|
|
|
const WriteConnectionSecretToRefKey = "writeConnectionSecretToRef"
|
|
|
|
|
2021-04-11 14:10:16 +08:00
|
|
|
// 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
|
2021-07-02 15:09:45 +08:00
|
|
|
// ConfigNotReady indicates there's RequiredSecrets and UserConfigs but they're not ready yet.
|
|
|
|
ConfigNotReady bool
|
2021-04-11 14:10:16 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
// 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) {
|
2021-05-31 18:44:15 +08:00
|
|
|
return wl.engine.Status(ctx, cli, ns, wl.FullTemplate.CustomStatus, wl.Params)
|
2021-04-11 14:10:16 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
// 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)
|
|
|
|
}
|
|
|
|
|
2021-07-02 15:09:45 +08:00
|
|
|
// IsSecretProducer checks whether a workload is cloud resource producer role
|
|
|
|
func (wl *Workload) IsSecretProducer() bool {
|
2021-04-11 14:10:16 +08:00
|
|
|
var existed bool
|
|
|
|
_, existed = wl.Params[process.OutputSecretName]
|
|
|
|
return existed
|
|
|
|
}
|
|
|
|
|
2021-07-02 15:09:45 +08:00
|
|
|
// IsSecretConsumer checks whether a workload is cloud resource consumer role
|
|
|
|
func (wl *Workload) IsSecretConsumer() bool {
|
2021-04-11 14:10:16 +08:00
|
|
|
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
|
|
|
|
|
2021-07-04 21:40:43 +08:00
|
|
|
// RequiredSecrets stores secret names which the trait needs from cloud resource component and its context
|
|
|
|
RequiredSecrets []process.RequiredSecrets
|
|
|
|
|
2021-04-11 14:10:16 +08:00
|
|
|
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) {
|
2021-05-31 18:44:15 +08:00
|
|
|
return trait.engine.Status(ctx, cli, ns, trait.CustomStatusFormat, trait.Params)
|
2021-04-11 14:10:16 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
// 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)
|
|
|
|
}
|
|
|
|
|
2021-07-04 21:40:43 +08:00
|
|
|
// IsSecretConsumer checks whether a trait is cloud resource consumer role
|
|
|
|
func (trait *Trait) IsSecretConsumer() bool {
|
|
|
|
requiredSecretTag := strings.TrimRight(InsertSecretToTag, "=")
|
|
|
|
matched, err := regexp.Match(regexp.QuoteMeta(requiredSecretTag), []byte(trait.FullTemplate.TemplateStr))
|
|
|
|
if err != nil || !matched {
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
return true
|
|
|
|
}
|
|
|
|
|
2021-04-11 14:10:16 +08:00
|
|
|
// Appfile describes application
|
|
|
|
type Appfile struct {
|
|
|
|
Name string
|
|
|
|
Namespace string
|
|
|
|
RevisionName string
|
|
|
|
Workloads []*Workload
|
2021-06-15 13:40:12 +08:00
|
|
|
|
|
|
|
Policies []*Workload
|
2021-07-22 18:53:30 +08:00
|
|
|
WorkflowSteps []v1beta1.WorkflowStep
|
2021-08-04 18:58:57 +08:00
|
|
|
Components []common.ApplicationComponent
|
2021-06-15 13:40:12 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
// GenerateWorkflowAndPolicy generates workflow steps and policies from an appFile
|
2021-07-27 19:22:05 +08:00
|
|
|
func (af *Appfile) GenerateWorkflowAndPolicy(ctx context.Context, m discoverymapper.DiscoveryMapper, cli client.Client, pd *packages.PackageDiscover, dispatcher kube.Dispatcher) (policies []*unstructured.Unstructured, steps []wfTypes.TaskRunner, err error) {
|
2021-06-15 13:40:12 +08:00
|
|
|
policies, err = af.generateUnstructureds(af.Policies)
|
|
|
|
if err != nil {
|
|
|
|
return
|
|
|
|
}
|
2021-07-22 18:53:30 +08:00
|
|
|
|
2021-07-27 19:22:05 +08:00
|
|
|
steps, err = af.generateSteps(ctx, m, cli, pd, dispatcher)
|
2021-06-15 13:40:12 +08:00
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
func (af *Appfile) generateUnstructureds(workloads []*Workload) ([]*unstructured.Unstructured, error) {
|
2021-08-04 18:58:57 +08:00
|
|
|
var uns []*unstructured.Unstructured
|
2021-06-15 13:40:12 +08:00
|
|
|
for _, wl := range workloads {
|
2021-08-04 18:58:57 +08:00
|
|
|
un, err := generateUnstructuredFromCUEModule(wl, af.Name, af.RevisionName, af.Namespace, af.Components)
|
2021-06-15 13:40:12 +08:00
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
2021-08-04 18:58:57 +08:00
|
|
|
un.SetName(wl.Name)
|
|
|
|
if len(un.GetNamespace()) == 0 {
|
|
|
|
un.SetNamespace(af.Namespace)
|
|
|
|
}
|
2021-06-15 13:40:12 +08:00
|
|
|
uns = append(uns, un)
|
|
|
|
}
|
|
|
|
return uns, nil
|
|
|
|
}
|
|
|
|
|
2021-07-27 19:22:05 +08:00
|
|
|
func (af *Appfile) generateSteps(ctx context.Context, dm discoverymapper.DiscoveryMapper, cli client.Client, pd *packages.PackageDiscover, dispatcher kube.Dispatcher) ([]wfTypes.TaskRunner, error) {
|
2021-07-22 18:53:30 +08:00
|
|
|
loadTaskTemplate := func(ctx context.Context, name string) (string, error) {
|
|
|
|
templ, err := LoadTemplate(ctx, dm, cli, name, types.TypeWorkflowStep)
|
|
|
|
if err != nil {
|
|
|
|
return "", err
|
|
|
|
}
|
|
|
|
schematic := templ.WorkflowStepDefinition.Spec.Schematic
|
|
|
|
if schematic != nil && schematic.CUE != nil {
|
|
|
|
return schematic.CUE.Template, nil
|
|
|
|
}
|
|
|
|
return "", errors.New("custom workflowStep only support cue")
|
|
|
|
}
|
|
|
|
|
2021-07-27 19:22:05 +08:00
|
|
|
handlerProviders := providers.NewProviders()
|
|
|
|
kube.Install(handlerProviders, cli, dispatcher)
|
|
|
|
taskDiscover := tasks.NewTaskDiscover(handlerProviders, pd, loadTaskTemplate)
|
2021-07-22 18:53:30 +08:00
|
|
|
var tasks []wfTypes.TaskRunner
|
|
|
|
for _, step := range af.WorkflowSteps {
|
|
|
|
genTask, err := taskDiscover.GetTaskGenerator(ctx, step.Type)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
task, err := genTask(step)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
tasks = append(tasks, task)
|
|
|
|
}
|
|
|
|
return tasks, nil
|
|
|
|
}
|
|
|
|
|
2021-08-04 18:58:57 +08:00
|
|
|
func generateUnstructuredFromCUEModule(wl *Workload, appName, revision, ns string, components []common.ApplicationComponent) (*unstructured.Unstructured, error) {
|
|
|
|
pCtx := process.NewPolicyContext(ns, wl.Name, appName, revision, components)
|
|
|
|
if err := wl.EvalContext(pCtx); err != nil {
|
|
|
|
return nil, errors.Wrapf(err, "evaluate base template app=%s in namespace=%s", appName, ns)
|
2021-06-15 13:40:12 +08:00
|
|
|
}
|
|
|
|
return makeWorkloadWithContext(pCtx, wl, ns, appName)
|
2021-04-11 14:10:16 +08:00
|
|
|
}
|
|
|
|
|
2021-06-24 15:06:58 +08:00
|
|
|
// GenerateComponentManifests converts an appFile to a slice of ComponentManifest
|
|
|
|
func (af *Appfile) GenerateComponentManifests() ([]*types.ComponentManifest, error) {
|
|
|
|
compManifests := make([]*types.ComponentManifest, len(af.Workloads))
|
|
|
|
for i, wl := range af.Workloads {
|
2021-07-02 15:09:45 +08:00
|
|
|
cm, err := af.GenerateComponentManifest(wl)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
2021-04-11 14:10:16 +08:00
|
|
|
}
|
2021-07-02 15:09:45 +08:00
|
|
|
compManifests[i] = cm
|
2021-04-11 14:10:16 +08:00
|
|
|
}
|
2021-06-24 15:06:58 +08:00
|
|
|
return compManifests, nil
|
2021-04-11 14:10:16 +08:00
|
|
|
}
|
|
|
|
|
2021-07-02 15:09:45 +08:00
|
|
|
// GenerateComponentManifest generate only one ComponentManifest
|
|
|
|
func (af *Appfile) GenerateComponentManifest(wl *Workload) (*types.ComponentManifest, error) {
|
|
|
|
if wl.ConfigNotReady {
|
|
|
|
return &types.ComponentManifest{
|
|
|
|
Name: wl.Name,
|
|
|
|
InsertConfigNotReady: true,
|
|
|
|
}, nil
|
|
|
|
}
|
|
|
|
switch wl.CapabilityCategory {
|
|
|
|
case types.HelmCategory:
|
|
|
|
return generateComponentFromHelmModule(wl, af.Name, af.RevisionName, af.Namespace)
|
|
|
|
case types.KubeCategory:
|
|
|
|
return generateComponentFromKubeModule(wl, af.Name, af.RevisionName, af.Namespace)
|
|
|
|
case types.TerraformCategory:
|
|
|
|
return generateComponentFromTerraformModule(wl, af.Name, af.RevisionName, af.Namespace)
|
|
|
|
default:
|
|
|
|
return generateComponentFromCUEModule(wl, af.Name, af.RevisionName, af.Namespace)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-04-11 14:10:16 +08:00
|
|
|
// PrepareProcessContext prepares a DSL process Context
|
|
|
|
func PrepareProcessContext(wl *Workload, applicationName, revision, namespace string) (process.Context, error) {
|
2021-05-13 21:46:45 +08:00
|
|
|
pCtx := NewBasicContext(wl, applicationName, revision, namespace)
|
2021-04-30 16:28:00 +08:00
|
|
|
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
|
|
|
|
}
|
|
|
|
|
2021-05-13 21:46:45 +08:00
|
|
|
// NewBasicContext prepares a basic DSL process Context
|
|
|
|
func NewBasicContext(wl *Workload, applicationName, revision, namespace string) process.Context {
|
2021-04-11 14:10:16 +08:00
|
|
|
pCtx := process.NewContext(namespace, wl.Name, applicationName, revision)
|
|
|
|
pCtx.InsertSecrets(wl.OutputSecretName, wl.RequiredSecrets)
|
|
|
|
if len(wl.UserConfigs) > 0 {
|
|
|
|
pCtx.SetConfigs(wl.UserConfigs)
|
|
|
|
}
|
2021-04-30 16:28:00 +08:00
|
|
|
return pCtx
|
2021-04-11 14:10:16 +08:00
|
|
|
}
|
|
|
|
|
2021-07-02 15:09:45 +08:00
|
|
|
// GetSecretAndConfigs will get secrets and configs the workload requires
|
|
|
|
func GetSecretAndConfigs(cli client.Client, workload *Workload, appName, ns string) error {
|
|
|
|
if workload.IsSecretConsumer() {
|
2021-07-04 21:40:43 +08:00
|
|
|
requiredSecrets, err := parseInsertSecretTo(context.TODO(), cli, ns, workload.FullTemplate.TemplateStr, workload.Params)
|
2021-07-02 15:09:45 +08:00
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
workload.RequiredSecrets = requiredSecrets
|
|
|
|
}
|
|
|
|
|
2021-07-04 21:40:43 +08:00
|
|
|
for _, tr := range workload.Traits {
|
|
|
|
if tr.IsSecretConsumer() {
|
|
|
|
requiredSecrets, err := parseInsertSecretTo(context.TODO(), cli, ns, tr.FullTemplate.TemplateStr, tr.Params)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
tr.RequiredSecrets = requiredSecrets
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-07-02 15:09:45 +08:00
|
|
|
userConfig := workload.GetUserConfigName()
|
|
|
|
if userConfig != "" {
|
|
|
|
cg := config.Configmap{Client: cli}
|
|
|
|
// TODO(wonderflow): envName should not be namespace when we have serverside env
|
|
|
|
var envName = ns
|
|
|
|
data, err := cg.GetConfigData(config.GenConfigMapName(appName, workload.Name, userConfig), envName)
|
|
|
|
if err != nil {
|
|
|
|
return errors.Wrapf(err, "get config=%s for app=%s in namespace=%s", userConfig, appName, ns)
|
|
|
|
}
|
|
|
|
workload.UserConfigs = data
|
|
|
|
}
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2021-06-24 15:06:58 +08:00
|
|
|
func generateComponentFromCUEModule(wl *Workload, appName, revision, ns string) (*types.ComponentManifest, error) {
|
2021-04-30 16:28:00 +08:00
|
|
|
pCtx, err := PrepareProcessContext(wl, appName, revision, ns)
|
|
|
|
if err != nil {
|
2021-06-24 15:06:58 +08:00
|
|
|
return nil, err
|
2021-04-30 16:28:00 +08:00
|
|
|
}
|
|
|
|
return baseGenerateComponent(pCtx, wl, appName, ns)
|
|
|
|
}
|
|
|
|
|
2021-06-24 15:06:58 +08:00
|
|
|
func generateComponentFromTerraformModule(wl *Workload, appName, revision, ns string) (*types.ComponentManifest, error) {
|
2021-05-13 21:46:45 +08:00
|
|
|
pCtx := NewBasicContext(wl, appName, revision, ns)
|
2021-04-30 16:28:00 +08:00
|
|
|
return baseGenerateComponent(pCtx, wl, appName, ns)
|
|
|
|
}
|
|
|
|
|
2021-06-24 15:06:58 +08:00
|
|
|
func baseGenerateComponent(pCtx process.Context, wl *Workload, appName, ns string) (*types.ComponentManifest, error) {
|
2021-04-11 14:10:16 +08:00
|
|
|
var (
|
|
|
|
outputSecretName string
|
|
|
|
err error
|
|
|
|
)
|
2021-07-02 15:09:45 +08:00
|
|
|
if wl.IsSecretProducer() {
|
2021-04-11 14:10:16 +08:00
|
|
|
outputSecretName, err = GetOutputSecretNames(wl)
|
|
|
|
if err != nil {
|
2021-06-24 15:06:58 +08:00
|
|
|
return nil, err
|
2021-04-11 14:10:16 +08:00
|
|
|
}
|
|
|
|
wl.OutputSecretName = outputSecretName
|
|
|
|
}
|
|
|
|
for _, tr := range wl.Traits {
|
2021-07-04 21:40:43 +08:00
|
|
|
pCtx.InsertSecrets("", tr.RequiredSecrets)
|
2021-04-11 14:10:16 +08:00
|
|
|
if err := tr.EvalContext(pCtx); err != nil {
|
2021-06-24 15:06:58 +08:00
|
|
|
return nil, errors.Wrapf(err, "evaluate template trait=%s app=%s", tr.Name, wl.Name)
|
2021-04-11 14:10:16 +08:00
|
|
|
}
|
|
|
|
}
|
2021-06-24 15:06:58 +08:00
|
|
|
compManifest, err := evalWorkloadWithContext(pCtx, wl, ns, appName, wl.Name)
|
2021-04-11 14:10:16 +08:00
|
|
|
if err != nil {
|
2021-06-24 15:06:58 +08:00
|
|
|
return nil, err
|
2021-04-11 14:10:16 +08:00
|
|
|
}
|
2021-06-24 15:06:58 +08:00
|
|
|
compManifest.Name = wl.Name
|
2021-04-11 14:10:16 +08:00
|
|
|
|
2021-06-24 15:06:58 +08:00
|
|
|
compManifest.Scopes = make([]*corev1.ObjectReference, len(wl.Scopes))
|
|
|
|
for i, s := range wl.Scopes {
|
|
|
|
compManifest.Scopes[i] = &corev1.ObjectReference{
|
|
|
|
APIVersion: s.GVK.GroupVersion().String(),
|
|
|
|
Kind: s.GVK.Kind,
|
|
|
|
Name: s.Name,
|
|
|
|
}
|
2021-04-11 14:10:16 +08:00
|
|
|
}
|
2021-06-24 15:06:58 +08:00
|
|
|
return compManifest, nil
|
2021-04-11 14:10:16 +08:00
|
|
|
}
|
|
|
|
|
2021-05-30 22:57:03 +08:00
|
|
|
// makeWorkloadWithContext evaluate the workload's template to unstructured resource.
|
|
|
|
func makeWorkloadWithContext(pCtx process.Context, wl *Workload, ns, appName string) (*unstructured.Unstructured, error) {
|
2021-04-30 16:28:00 +08:00
|
|
|
var (
|
2021-05-30 22:57:03 +08:00
|
|
|
workload *unstructured.Unstructured
|
|
|
|
err error
|
2021-04-30 16:28:00 +08:00
|
|
|
)
|
2021-05-30 22:57:03 +08:00
|
|
|
base, _ := pCtx.Output()
|
2021-04-30 16:28:00 +08:00
|
|
|
switch wl.CapabilityCategory {
|
|
|
|
case types.TerraformCategory:
|
2021-05-30 22:57:03 +08:00
|
|
|
workload, err = generateTerraformConfigurationWorkload(wl, ns)
|
2021-04-30 16:28:00 +08:00
|
|
|
if err != nil {
|
2021-05-30 22:57:03 +08:00
|
|
|
return nil, errors.Wrapf(err, "failed to generate Terraform Configuration workload for workload %s", wl.Name)
|
2021-04-30 16:28:00 +08:00
|
|
|
}
|
|
|
|
default:
|
2021-05-30 22:57:03 +08:00
|
|
|
workload, err = base.Unstructured()
|
2021-04-30 16:28:00 +08:00
|
|
|
if err != nil {
|
2021-05-30 22:57:03 +08:00
|
|
|
return nil, errors.Wrapf(err, "evaluate base template component=%s app=%s", wl.Name, appName)
|
2021-04-30 16:28:00 +08:00
|
|
|
}
|
2021-04-11 14:10:16 +08:00
|
|
|
}
|
2021-05-30 22:57:03 +08:00
|
|
|
commonLabels := definition.GetCommonLabels(pCtx.BaseContextLabels())
|
|
|
|
util.AddLabels(workload, util.MergeMapOverrideWithDst(commonLabels, map[string]string{oam.WorkloadTypeLabel: wl.Type}))
|
|
|
|
return workload, nil
|
|
|
|
}
|
|
|
|
|
2021-06-24 15:06:58 +08:00
|
|
|
// evalWorkloadWithContext evaluate the workload's template to generate component manifest
|
|
|
|
func evalWorkloadWithContext(pCtx process.Context, wl *Workload, ns, appName, compName string) (*types.ComponentManifest, error) {
|
|
|
|
compManifest := &types.ComponentManifest{}
|
|
|
|
workload, err := makeWorkloadWithContext(pCtx, wl, ns, appName)
|
2021-05-30 22:57:03 +08:00
|
|
|
if err != nil {
|
2021-06-24 15:06:58 +08:00
|
|
|
return nil, err
|
2021-05-30 22:57:03 +08:00
|
|
|
}
|
2021-06-24 15:06:58 +08:00
|
|
|
compManifest.StandardWorkload = workload
|
2021-04-11 14:10:16 +08:00
|
|
|
|
2021-05-30 22:57:03 +08:00
|
|
|
_, assists := pCtx.Output()
|
2021-06-24 15:06:58 +08:00
|
|
|
compManifest.Traits = make([]*unstructured.Unstructured, len(assists))
|
2021-05-30 22:57:03 +08:00
|
|
|
commonLabels := definition.GetCommonLabels(pCtx.BaseContextLabels())
|
2021-06-24 15:06:58 +08:00
|
|
|
for i, assist := range assists {
|
2021-04-11 14:10:16 +08:00
|
|
|
tr, err := assist.Ins.Unstructured()
|
|
|
|
if err != nil {
|
2021-06-24 15:06:58 +08:00
|
|
|
return nil, errors.Wrapf(err, "evaluate trait=%s template for component=%s app=%s", assist.Name, compName, appName)
|
2021-04-11 14:10:16 +08:00
|
|
|
}
|
|
|
|
labels := util.MergeMapOverrideWithDst(commonLabels, map[string]string{oam.TraitTypeLabel: assist.Type})
|
|
|
|
if assist.Name != "" {
|
|
|
|
labels[oam.TraitResource] = assist.Name
|
|
|
|
}
|
|
|
|
util.AddLabels(tr, labels)
|
2021-06-24 15:06:58 +08:00
|
|
|
compManifest.Traits[i] = tr
|
2021-04-11 14:10:16 +08:00
|
|
|
}
|
2021-06-24 15:06:58 +08:00
|
|
|
return compManifest, nil
|
2021-04-11 14:10:16 +08:00
|
|
|
}
|
|
|
|
|
2021-07-08 15:16:55 +08:00
|
|
|
// GenerateCUETemplate generate CUE Template from Kube module and Helm module
|
|
|
|
func GenerateCUETemplate(wl *Workload) (string, error) {
|
|
|
|
var templateStr string
|
|
|
|
switch wl.CapabilityCategory {
|
|
|
|
case types.KubeCategory:
|
|
|
|
kubeObj := &unstructured.Unstructured{}
|
2021-04-11 14:10:16 +08:00
|
|
|
|
2021-07-08 15:16:55 +08:00
|
|
|
err := json.Unmarshal(wl.FullTemplate.Kube.Template.Raw, kubeObj)
|
|
|
|
if err != nil {
|
|
|
|
return templateStr, errors.Wrap(err, "cannot decode Kube template into K8s object")
|
|
|
|
}
|
2021-04-11 14:10:16 +08:00
|
|
|
|
2021-07-08 15:16:55 +08:00
|
|
|
paramValues, err := resolveKubeParameters(wl.FullTemplate.Kube.Parameters, wl.Params)
|
|
|
|
if err != nil {
|
|
|
|
return templateStr, errors.WithMessage(err, "cannot resolve parameter settings")
|
|
|
|
}
|
|
|
|
if err := setParameterValuesToKubeObj(kubeObj, paramValues); err != nil {
|
|
|
|
return templateStr, 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 templateStr, errors.Wrap(err, "cannot marshal kube object")
|
|
|
|
}
|
|
|
|
ins, err := json2cue.Decode(&cue.Runtime{}, "", objRaw)
|
|
|
|
if err != nil {
|
|
|
|
return templateStr, errors.Wrap(err, "cannot decode object into CUE")
|
|
|
|
}
|
|
|
|
cueRaw, err := format.Node(ins.Value().Syntax())
|
|
|
|
if err != nil {
|
|
|
|
return templateStr, errors.Wrap(err, "cannot format CUE")
|
|
|
|
}
|
2021-04-11 14:10:16 +08:00
|
|
|
|
2021-07-08 15:16:55 +08:00
|
|
|
// NOTE a hack way to enable using CUE capabilities on KUBE schematic workload
|
|
|
|
templateStr = fmt.Sprintf(`
|
2021-04-11 14:10:16 +08:00
|
|
|
output: {
|
2021-07-08 15:16:55 +08:00
|
|
|
%s
|
2021-04-11 14:10:16 +08:00
|
|
|
}`, string(cueRaw))
|
2021-07-08 15:16:55 +08:00
|
|
|
case types.HelmCategory:
|
|
|
|
gv, err := schema.ParseGroupVersion(wl.FullTemplate.Reference.Definition.APIVersion)
|
|
|
|
if err != nil {
|
|
|
|
return templateStr, err
|
|
|
|
}
|
|
|
|
targetWorkloadGVK := gv.WithKind(wl.FullTemplate.Reference.Definition.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
|
|
|
|
templateStr = fmt.Sprintf(`
|
|
|
|
output: {
|
|
|
|
apiVersion: "%s"
|
|
|
|
kind: "%s"
|
|
|
|
}`, targetWorkloadGVK.GroupVersion().String(), targetWorkloadGVK.Kind)
|
|
|
|
default:
|
|
|
|
}
|
|
|
|
return templateStr, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func generateComponentFromKubeModule(wl *Workload, appName, revision, ns string) (*types.ComponentManifest, error) {
|
|
|
|
templateStr, err := GenerateCUETemplate(wl)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
wl.FullTemplate.TemplateStr = templateStr
|
2021-04-11 14:10:16 +08:00
|
|
|
|
|
|
|
// re-use the way CUE module generates comp & acComp
|
2021-06-24 15:06:58 +08:00
|
|
|
compManifest, err := generateComponentFromCUEModule(wl, appName, revision, ns)
|
2021-04-11 14:10:16 +08:00
|
|
|
if err != nil {
|
2021-06-24 15:06:58 +08:00
|
|
|
return nil, err
|
2021-04-11 14:10:16 +08:00
|
|
|
}
|
2021-06-24 15:06:58 +08:00
|
|
|
return compManifest, nil
|
2021-04-11 14:10:16 +08:00
|
|
|
}
|
|
|
|
|
2021-04-30 16:28:00 +08:00
|
|
|
func generateTerraformConfigurationWorkload(wl *Workload, ns string) (*unstructured.Unstructured, error) {
|
|
|
|
if wl.FullTemplate.Terraform.Configuration == "" {
|
|
|
|
return nil, errors.New(errTerraformConfigurationIsNotSet)
|
|
|
|
}
|
|
|
|
params, err := json.Marshal(wl.Params)
|
|
|
|
if err != nil {
|
|
|
|
return nil, errors.Wrap(err, errFailToConvertTerraformComponentProperties)
|
|
|
|
}
|
|
|
|
|
|
|
|
configuration := terraformapi.Configuration{
|
|
|
|
TypeMeta: metav1.TypeMeta{APIVersion: "terraform.core.oam.dev/v1beta1", Kind: "Configuration"},
|
|
|
|
ObjectMeta: metav1.ObjectMeta{Name: wl.Name, Namespace: ns},
|
|
|
|
}
|
|
|
|
|
|
|
|
switch wl.FullTemplate.Terraform.Type {
|
|
|
|
case "hcl":
|
|
|
|
configuration.Spec.HCL = wl.FullTemplate.Terraform.Configuration
|
|
|
|
case "json":
|
|
|
|
configuration.Spec.JSON = wl.FullTemplate.Terraform.Configuration
|
|
|
|
}
|
|
|
|
|
|
|
|
// 1. parse writeConnectionSecretToRef
|
|
|
|
if err := json.Unmarshal(params, &configuration.Spec); err != nil {
|
|
|
|
return nil, errors.Wrap(err, errFailToConvertTerraformComponentProperties)
|
|
|
|
}
|
|
|
|
|
|
|
|
if configuration.Spec.WriteConnectionSecretToReference != nil {
|
|
|
|
if configuration.Spec.WriteConnectionSecretToReference.Name == "" {
|
|
|
|
return nil, errors.New(errTerraformNameOfWriteConnectionSecretToRefNotSet)
|
|
|
|
}
|
|
|
|
// set namespace for writeConnectionSecretToRef, developer needn't manually set it
|
|
|
|
if configuration.Spec.WriteConnectionSecretToReference.Namespace == "" {
|
|
|
|
configuration.Spec.WriteConnectionSecretToReference.Namespace = ns
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// 2. parse variable
|
|
|
|
variableRaw := &runtime.RawExtension{}
|
|
|
|
if err := json.Unmarshal(params, &variableRaw); err != nil {
|
|
|
|
return nil, errors.Wrap(err, errFailToConvertTerraformComponentProperties)
|
|
|
|
}
|
|
|
|
|
|
|
|
variableMap, err := util.RawExtension2Map(variableRaw)
|
|
|
|
if err != nil {
|
|
|
|
return nil, errors.Wrap(err, errFailToConvertTerraformComponentProperties)
|
|
|
|
}
|
|
|
|
delete(variableMap, WriteConnectionSecretToRefKey)
|
|
|
|
|
|
|
|
data, err := json.Marshal(variableMap)
|
|
|
|
if err != nil {
|
|
|
|
return nil, errors.Wrap(err, errFailToConvertTerraformComponentProperties)
|
|
|
|
}
|
|
|
|
configuration.Spec.Variable = &runtime.RawExtension{Raw: data}
|
|
|
|
raw := util.Object2RawExtension(&configuration)
|
|
|
|
return util.RawExtension2Unstructured(&raw)
|
|
|
|
}
|
|
|
|
|
2021-04-11 14:10:16 +08:00
|
|
|
// 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
|
|
|
|
}
|
|
|
|
|
2021-06-24 15:06:58 +08:00
|
|
|
func generateComponentFromHelmModule(wl *Workload, appName, revision, ns string) (*types.ComponentManifest, error) {
|
2021-07-08 15:16:55 +08:00
|
|
|
templateStr, err := GenerateCUETemplate(wl)
|
2021-04-11 14:10:16 +08:00
|
|
|
if err != nil {
|
2021-06-24 15:06:58 +08:00
|
|
|
return nil, err
|
2021-04-11 14:10:16 +08:00
|
|
|
}
|
2021-07-08 15:16:55 +08:00
|
|
|
wl.FullTemplate.TemplateStr = templateStr
|
2021-04-11 14:10:16 +08:00
|
|
|
|
|
|
|
// re-use the way CUE module generates comp & acComp
|
2021-06-24 15:06:58 +08:00
|
|
|
compManifest := &types.ComponentManifest{
|
|
|
|
Name: wl.Name,
|
|
|
|
}
|
2021-05-24 23:32:02 +08:00
|
|
|
if wl.FullTemplate.Reference.Type != types.AutoDetectWorkloadDefinition {
|
2021-06-24 15:06:58 +08:00
|
|
|
compManifest, err = generateComponentFromCUEModule(wl, appName, revision, ns)
|
2021-05-24 23:32:02 +08:00
|
|
|
if err != nil {
|
2021-06-24 15:06:58 +08:00
|
|
|
return nil, err
|
2021-05-24 23:32:02 +08:00
|
|
|
}
|
2021-04-11 14:10:16 +08:00
|
|
|
}
|
|
|
|
|
2021-06-24 15:06:58 +08:00
|
|
|
rls, repo, err := helm.RenderHelmReleaseAndHelmRepo(wl.FullTemplate.Helm, wl.Name, appName, ns, wl.Params)
|
2021-04-11 14:10:16 +08:00
|
|
|
if err != nil {
|
2021-06-24 15:06:58 +08:00
|
|
|
return nil, err
|
2021-04-11 14:10:16 +08:00
|
|
|
}
|
2021-06-24 15:06:58 +08:00
|
|
|
compManifest.PackagedWorkloadResources = []*unstructured.Unstructured{rls, repo}
|
|
|
|
return compManifest, nil
|
2021-04-11 14:10:16 +08:00
|
|
|
}
|