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"
|
2021-09-12 10:12:46 +08:00
|
|
|
"reflect"
|
2021-04-11 14:10:16 +08:00
|
|
|
"strings"
|
|
|
|
|
2022-08-08 17:57:48 +08:00
|
|
|
"cuelang.org/go/cue/cuecontext"
|
2021-04-11 14:10:16 +08:00
|
|
|
"cuelang.org/go/cue/format"
|
|
|
|
json2cue "cuelang.org/go/encoding/json"
|
|
|
|
"github.com/crossplane/crossplane-runtime/pkg/fieldpath"
|
2022-05-09 15:10:31 +08:00
|
|
|
terraformapi "github.com/oam-dev/terraform-controller/api/v1beta2"
|
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"
|
2022-03-24 14:40:19 +08:00
|
|
|
kerrors "k8s.io/apimachinery/pkg/api/errors"
|
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"
|
|
|
|
|
2022-09-09 15:10:18 +08:00
|
|
|
velaclient "github.com/kubevela/pkg/controller/client"
|
2022-09-02 12:55:03 +08:00
|
|
|
workflowv1alpha1 "github.com/kubevela/workflow/api/v1alpha1"
|
|
|
|
"github.com/kubevela/workflow/pkg/cue/model/value"
|
|
|
|
"github.com/kubevela/workflow/pkg/cue/process"
|
|
|
|
|
2021-04-11 14:10:16 +08:00
|
|
|
"github.com/oam-dev/kubevela/apis/core.oam.dev/common"
|
2021-11-05 17:29:05 +08:00
|
|
|
"github.com/oam-dev/kubevela/apis/core.oam.dev/v1alpha1"
|
2021-09-12 10:12:46 +08:00
|
|
|
"github.com/oam-dev/kubevela/apis/core.oam.dev/v1beta1"
|
2021-04-11 14:10:16 +08:00
|
|
|
"github.com/oam-dev/kubevela/apis/types"
|
|
|
|
"github.com/oam-dev/kubevela/pkg/appfile/helm"
|
2022-05-28 01:26:06 +08:00
|
|
|
"github.com/oam-dev/kubevela/pkg/auth"
|
2022-04-28 16:29:34 +08:00
|
|
|
"github.com/oam-dev/kubevela/pkg/component"
|
2021-06-02 15:37:06 +08:00
|
|
|
"github.com/oam-dev/kubevela/pkg/cue/definition"
|
2022-09-02 12:55:03 +08:00
|
|
|
velaprocess "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"
|
2022-06-24 19:34:51 +08:00
|
|
|
utilscommon "github.com/oam-dev/kubevela/pkg/utils/common"
|
2021-04-11 14:10:16 +08:00
|
|
|
)
|
|
|
|
|
|
|
|
// constant error information
|
|
|
|
const (
|
2021-12-02 15:45:50 +08:00
|
|
|
errInvalidValueType = "require %q type parameter value"
|
|
|
|
errTerraformConfigurationIsNotSet = "terraform configuration is not set"
|
2022-05-20 18:17:10 +08:00
|
|
|
errTerraformComponentDefinition = "terraform component definition is not valid"
|
2021-12-02 15:45:50 +08:00
|
|
|
errFailToConvertTerraformComponentProperties = "failed to convert Terraform component properties"
|
2021-04-11 14:10:16 +08:00
|
|
|
)
|
|
|
|
|
2021-12-02 15:45:50 +08:00
|
|
|
const (
|
|
|
|
// WriteConnectionSecretToRefKey is used to create a secret for cloud resource connection
|
|
|
|
WriteConnectionSecretToRefKey = "writeConnectionSecretToRef"
|
|
|
|
// RegionKey is the region of a Cloud Provider
|
2022-05-09 15:10:31 +08:00
|
|
|
// It's used to override the region of a Cloud Provider
|
|
|
|
// Refer to https://github.com/oam-dev/terraform-controller/blob/master/api/v1beta2/configuration_types.go#L66 for details
|
|
|
|
RegionKey = "customRegion"
|
2021-12-02 15:45:50 +08:00
|
|
|
// ProviderRefKey is the reference of a Provider
|
|
|
|
ProviderRefKey = "providerRef"
|
2022-05-20 18:17:10 +08:00
|
|
|
// ForceDeleteKey is used to force delete Configuration
|
|
|
|
ForceDeleteKey = "forceDelete"
|
2021-12-02 15:45:50 +08:00
|
|
|
)
|
2021-04-30 16:28:00 +08:00
|
|
|
|
2021-04-11 14:10:16 +08:00
|
|
|
// Workload is component
|
|
|
|
type Workload struct {
|
|
|
|
Name string
|
|
|
|
Type string
|
2021-08-08 12:11:24 +08:00
|
|
|
ExternalRevision string
|
2021-04-11 14:10:16 +08:00
|
|
|
CapabilityCategory types.CapabilityCategory
|
|
|
|
Params map[string]interface{}
|
|
|
|
Traits []*Trait
|
|
|
|
Scopes []Scope
|
2021-09-12 10:12:46 +08:00
|
|
|
ScopeDefinition []*v1beta1.ScopeDefinition
|
2021-04-11 14:10:16 +08:00
|
|
|
FullTemplate *Template
|
2021-09-12 10:12:46 +08:00
|
|
|
Ctx process.Context
|
|
|
|
Patch *value.Value
|
2021-04-11 14:10:16 +08:00
|
|
|
engine definition.AbstractEngine
|
2022-01-13 19:08:43 +08:00
|
|
|
SkipApplyWorkload bool
|
2021-04-11 14:10:16 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
// 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
|
2022-07-11 16:46:14 +08:00
|
|
|
func (wl *Workload) EvalStatus(ctx process.Context, cli client.Client, accessor util.NamespaceAccessor) (string, error) {
|
2022-01-13 19:08:43 +08:00
|
|
|
// if the standard workload is managed by trait always return empty message
|
|
|
|
if wl.SkipApplyWorkload {
|
|
|
|
return "", nil
|
|
|
|
}
|
2022-07-11 16:46:14 +08:00
|
|
|
return wl.engine.Status(ctx, cli, accessor, wl.FullTemplate.CustomStatus, wl.Params)
|
2021-04-11 14:10:16 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
// EvalHealth eval workload health check
|
2022-07-11 16:46:14 +08:00
|
|
|
func (wl *Workload) EvalHealth(ctx process.Context, client client.Client, accessor util.NamespaceAccessor) (bool, error) {
|
2022-01-13 19:08:43 +08:00
|
|
|
// if health of template is not set or standard workload is managed by trait always return true
|
2022-08-02 10:56:37 +08:00
|
|
|
if wl.SkipApplyWorkload {
|
2021-09-12 10:12:46 +08:00
|
|
|
return true, nil
|
2021-04-11 14:10:16 +08:00
|
|
|
}
|
2022-10-18 17:16:23 +08:00
|
|
|
return wl.engine.HealthCheck(ctx, client, accessor, wl.FullTemplate.Health, wl.Params)
|
2021-04-11 14:10:16 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
// Scope defines the scope of workload
|
|
|
|
type Scope struct {
|
2021-09-12 10:12:46 +08:00
|
|
|
Name string
|
|
|
|
GVK metav1.GroupVersionKind
|
|
|
|
ResourceVersion string
|
2021-04-11 14:10:16 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
// 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
|
2022-07-11 16:46:14 +08:00
|
|
|
func (trait *Trait) EvalStatus(ctx process.Context, cli client.Client, accessor util.NamespaceAccessor) (string, error) {
|
|
|
|
return trait.engine.Status(ctx, cli, accessor, trait.CustomStatusFormat, trait.Params)
|
2021-04-11 14:10:16 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
// EvalHealth eval trait health check
|
2022-07-11 16:46:14 +08:00
|
|
|
func (trait *Trait) EvalHealth(ctx process.Context, client client.Client, accessor util.NamespaceAccessor) (bool, error) {
|
2022-10-18 17:16:23 +08:00
|
|
|
return trait.engine.HealthCheck(ctx, client, accessor, trait.HealthCheckPolicy, trait.Params)
|
2021-07-04 21:40:43 +08:00
|
|
|
}
|
|
|
|
|
2021-04-11 14:10:16 +08:00
|
|
|
// Appfile describes application
|
|
|
|
type Appfile struct {
|
2022-03-24 14:40:19 +08:00
|
|
|
Name string
|
|
|
|
Namespace string
|
|
|
|
Workloads []*Workload
|
2021-09-12 10:12:46 +08:00
|
|
|
|
2022-03-24 14:40:19 +08:00
|
|
|
AppRevision *v1beta1.ApplicationRevision
|
|
|
|
AppRevisionName string
|
2021-09-12 10:12:46 +08:00
|
|
|
AppRevisionHash string
|
|
|
|
|
2022-03-24 14:40:19 +08:00
|
|
|
AppLabels map[string]string
|
|
|
|
AppAnnotations map[string]string
|
|
|
|
|
|
|
|
RelatedTraitDefinitions map[string]*v1beta1.TraitDefinition
|
|
|
|
RelatedComponentDefinitions map[string]*v1beta1.ComponentDefinition
|
|
|
|
RelatedWorkflowStepDefinitions map[string]*v1beta1.WorkflowStepDefinition
|
|
|
|
RelatedScopeDefinitions map[string]*v1beta1.ScopeDefinition
|
2021-06-15 13:40:12 +08:00
|
|
|
|
2022-03-07 10:21:00 +08:00
|
|
|
Policies []v1beta1.AppPolicy
|
|
|
|
PolicyWorkloads []*Workload
|
|
|
|
Components []common.ApplicationComponent
|
|
|
|
Artifacts []*types.ComponentManifest
|
2022-09-02 12:55:03 +08:00
|
|
|
WorkflowSteps []workflowv1alpha1.WorkflowStep
|
|
|
|
WorkflowMode *workflowv1alpha1.WorkflowExecuteMode
|
2021-09-12 10:12:46 +08:00
|
|
|
|
2022-03-24 14:40:19 +08:00
|
|
|
ExternalPolicies map[string]*v1alpha1.Policy
|
2022-09-02 12:55:03 +08:00
|
|
|
ExternalWorkflow *workflowv1alpha1.Workflow
|
2022-03-28 16:09:12 +08:00
|
|
|
ReferredObjects []*unstructured.Unstructured
|
2022-03-24 14:40:19 +08:00
|
|
|
|
2021-09-12 10:12:46 +08:00
|
|
|
parser *Parser
|
2021-11-08 23:11:50 +08:00
|
|
|
app *v1beta1.Application
|
2022-04-18 11:06:14 +08:00
|
|
|
|
|
|
|
Debug bool
|
2021-09-12 10:12:46 +08:00
|
|
|
}
|
|
|
|
|
2022-03-24 14:40:19 +08:00
|
|
|
// GeneratePolicyManifests generates policy manifests from an appFile
|
|
|
|
// internal policies like apply-once, topology, will not render manifests
|
|
|
|
func (af *Appfile) GeneratePolicyManifests(ctx context.Context) ([]*unstructured.Unstructured, error) {
|
|
|
|
var manifests []*unstructured.Unstructured
|
2022-03-07 10:21:00 +08:00
|
|
|
for _, policy := range af.PolicyWorkloads {
|
2022-03-24 14:40:19 +08:00
|
|
|
un, err := af.generateUnstructured(policy)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
2021-11-24 15:38:13 +08:00
|
|
|
}
|
2022-03-24 14:40:19 +08:00
|
|
|
manifests = append(manifests, un)
|
2021-06-15 13:40:12 +08:00
|
|
|
}
|
2022-03-24 14:40:19 +08:00
|
|
|
return manifests, nil
|
2021-06-15 13:40:12 +08:00
|
|
|
}
|
|
|
|
|
2021-11-24 15:38:13 +08:00
|
|
|
func (af *Appfile) generateUnstructured(workload *Workload) (*unstructured.Unstructured, error) {
|
2022-02-28 10:30:55 +08:00
|
|
|
ctxData := GenerateContextDataFromAppFile(af, workload.Name)
|
|
|
|
un, err := generateUnstructuredFromCUEModule(workload, af.Artifacts, ctxData)
|
2021-11-24 15:38:13 +08:00
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
un.SetName(workload.Name)
|
|
|
|
if len(un.GetNamespace()) == 0 {
|
|
|
|
un.SetNamespace(af.Namespace)
|
2021-06-15 13:40:12 +08:00
|
|
|
}
|
2021-11-24 15:38:13 +08:00
|
|
|
return un, nil
|
2021-06-15 13:40:12 +08:00
|
|
|
}
|
|
|
|
|
2022-09-02 12:55:03 +08:00
|
|
|
func generateUnstructuredFromCUEModule(wl *Workload, artifacts []*types.ComponentManifest, ctxData velaprocess.ContextData) (*unstructured.Unstructured, error) {
|
|
|
|
pCtx := velaprocess.NewContext(ctxData)
|
|
|
|
pCtx.PushData(velaprocess.ContextDataArtifacts, prepareArtifactsData(artifacts))
|
2021-08-04 18:58:57 +08:00
|
|
|
if err := wl.EvalContext(pCtx); err != nil {
|
2022-02-28 10:30:55 +08:00
|
|
|
return nil, errors.Wrapf(err, "evaluate base template app=%s in namespace=%s", ctxData.AppName, ctxData.Namespace)
|
2021-06-15 13:40:12 +08:00
|
|
|
}
|
2022-02-28 10:30:55 +08:00
|
|
|
return makeWorkloadWithContext(pCtx, wl, ctxData.Namespace, ctxData.AppName)
|
2021-04-11 14:10:16 +08:00
|
|
|
}
|
|
|
|
|
2021-09-12 10:12:46 +08:00
|
|
|
// artifacts contains resources in unstructured shape of all components
|
2021-08-29 09:11:10 +08:00
|
|
|
// it allows to access values of workloads and traits in CUE template, i.g.,
|
|
|
|
// `if context.artifacts.<compName>.ready` to determine whether it's ready to access
|
|
|
|
// `context.artifacts.<compName>.workload` to access a workload
|
|
|
|
// `context.artifacts.<compName>.traits.<traitType>.<traitResource>` to access a trait
|
|
|
|
func prepareArtifactsData(comps []*types.ComponentManifest) map[string]interface{} {
|
|
|
|
artifacts := unstructured.Unstructured{Object: make(map[string]interface{})}
|
|
|
|
for _, pComp := range comps {
|
2021-09-12 10:12:46 +08:00
|
|
|
if pComp.StandardWorkload != nil {
|
|
|
|
_ = unstructured.SetNestedField(artifacts.Object, pComp.StandardWorkload.Object, pComp.Name, "workload")
|
2021-08-29 09:11:10 +08:00
|
|
|
}
|
|
|
|
for _, t := range pComp.Traits {
|
2021-09-12 10:12:46 +08:00
|
|
|
if t == nil {
|
|
|
|
continue
|
|
|
|
}
|
2021-08-29 09:11:10 +08:00
|
|
|
_ = unstructured.SetNestedField(artifacts.Object, t.Object, pComp.Name,
|
|
|
|
"traits",
|
|
|
|
t.GetLabels()[oam.TraitTypeLabel],
|
|
|
|
t.GetLabels()[oam.TraitResource])
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return artifacts.Object
|
|
|
|
}
|
|
|
|
|
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))
|
2021-08-29 09:11:10 +08:00
|
|
|
af.Artifacts = make([]*types.ComponentManifest, len(af.Workloads))
|
2021-06-24 15:06:58 +08:00
|
|
|
for i, wl := range af.Workloads {
|
2022-07-01 15:28:25 +08:00
|
|
|
cm, err := af.GenerateComponentManifest(wl, nil)
|
2021-07-02 15:09:45 +08:00
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
2021-04-11 14:10:16 +08:00
|
|
|
}
|
2021-09-12 10:12:46 +08:00
|
|
|
err = af.SetOAMContract(cm)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
2021-07-02 15:09:45 +08:00
|
|
|
compManifests[i] = cm
|
2021-08-29 09:11:10 +08:00
|
|
|
af.Artifacts[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
|
2022-09-02 12:55:03 +08:00
|
|
|
func (af *Appfile) GenerateComponentManifest(wl *Workload, mutate func(*velaprocess.ContextData)) (*types.ComponentManifest, error) {
|
2021-08-20 13:43:04 +08:00
|
|
|
if af.Namespace == "" {
|
|
|
|
af.Namespace = corev1.NamespaceDefault
|
|
|
|
}
|
2022-02-28 10:30:55 +08:00
|
|
|
ctxData := GenerateContextDataFromAppFile(af, wl.Name)
|
2022-07-01 15:28:25 +08:00
|
|
|
if mutate != nil {
|
|
|
|
mutate(&ctxData)
|
|
|
|
}
|
2021-11-07 11:50:27 +08:00
|
|
|
// generate context here to avoid nil pointer panic
|
2022-07-01 15:28:25 +08:00
|
|
|
wl.Ctx = NewBasicContext(ctxData, wl.Params)
|
2021-07-02 15:09:45 +08:00
|
|
|
switch wl.CapabilityCategory {
|
|
|
|
case types.HelmCategory:
|
2022-02-28 10:30:55 +08:00
|
|
|
return generateComponentFromHelmModule(wl, ctxData)
|
2021-07-02 15:09:45 +08:00
|
|
|
case types.KubeCategory:
|
2022-02-28 10:30:55 +08:00
|
|
|
return generateComponentFromKubeModule(wl, ctxData)
|
2021-07-02 15:09:45 +08:00
|
|
|
case types.TerraformCategory:
|
2021-11-07 11:50:27 +08:00
|
|
|
return generateComponentFromTerraformModule(wl, af.Name, af.Namespace)
|
2021-07-02 15:09:45 +08:00
|
|
|
default:
|
2022-02-28 10:30:55 +08:00
|
|
|
return generateComponentFromCUEModule(wl, ctxData)
|
2021-07-02 15:09:45 +08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-09-12 10:12:46 +08:00
|
|
|
// SetOAMContract will set OAM labels and annotations for resources as contract
|
|
|
|
func (af *Appfile) SetOAMContract(comp *types.ComponentManifest) error {
|
|
|
|
|
|
|
|
compName := comp.Name
|
|
|
|
commonLabels := af.generateAndFilterCommonLabels(compName)
|
|
|
|
af.assembleWorkload(comp.StandardWorkload, compName, commonLabels)
|
|
|
|
|
|
|
|
workloadRef := corev1.ObjectReference{
|
|
|
|
APIVersion: comp.StandardWorkload.GetAPIVersion(),
|
|
|
|
Kind: comp.StandardWorkload.GetKind(),
|
|
|
|
Name: comp.StandardWorkload.GetName(),
|
|
|
|
}
|
|
|
|
for _, trait := range comp.Traits {
|
|
|
|
af.assembleTrait(trait, compName, commonLabels)
|
|
|
|
if err := af.setWorkloadRefToTrait(workloadRef, trait); err != nil {
|
|
|
|
return errors.WithMessagef(err, "cannot set workload reference to trait %q", trait.GetName())
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// workload and trait in the same component both have these labels, except componentRevision which should be evaluated with input/output
|
|
|
|
func (af *Appfile) generateAndFilterCommonLabels(compName string) map[string]string {
|
|
|
|
filter := func(labels map[string]string, notAllowedKey []string) {
|
|
|
|
for _, l := range notAllowedKey {
|
|
|
|
delete(labels, strings.TrimSpace(l))
|
|
|
|
}
|
|
|
|
}
|
|
|
|
Labels := map[string]string{
|
|
|
|
oam.LabelAppName: af.Name,
|
2021-12-10 15:00:03 +08:00
|
|
|
oam.LabelAppNamespace: af.Namespace,
|
2021-09-12 10:12:46 +08:00
|
|
|
oam.LabelAppRevision: af.AppRevisionName,
|
|
|
|
oam.LabelAppComponent: compName,
|
|
|
|
}
|
|
|
|
// merge application's all labels
|
2022-11-02 22:38:59 +08:00
|
|
|
finalLabels := util.MergeMapOverrideWithDst(af.AppLabels, Labels)
|
2021-09-12 10:12:46 +08:00
|
|
|
filterLabels, ok := af.AppAnnotations[oam.AnnotationFilterLabelKeys]
|
|
|
|
if ok {
|
|
|
|
filter(finalLabels, strings.Split(filterLabels, ","))
|
|
|
|
}
|
|
|
|
return finalLabels
|
|
|
|
}
|
|
|
|
|
|
|
|
// workload and trait both have these annotations
|
|
|
|
func (af *Appfile) filterAndSetAnnotations(obj *unstructured.Unstructured) {
|
|
|
|
var allFilterAnnotation []string
|
|
|
|
allFilterAnnotation = append(allFilterAnnotation, types.DefaultFilterAnnots...)
|
|
|
|
|
|
|
|
passedFilterAnnotation, ok := af.AppAnnotations[oam.AnnotationFilterAnnotationKeys]
|
|
|
|
if ok {
|
|
|
|
allFilterAnnotation = append(allFilterAnnotation, strings.Split(passedFilterAnnotation, ",")...)
|
|
|
|
}
|
|
|
|
|
|
|
|
// pass application's all annotations
|
|
|
|
util.AddAnnotations(obj, af.AppAnnotations)
|
|
|
|
// remove useless annotations for workload/trait
|
|
|
|
util.RemoveAnnotations(obj, allFilterAnnotation)
|
|
|
|
}
|
|
|
|
|
|
|
|
func (af *Appfile) setNamespace(obj *unstructured.Unstructured) {
|
|
|
|
|
|
|
|
// we should not set namespace for namespace resources
|
|
|
|
gvk := obj.GetObjectKind().GroupVersionKind()
|
|
|
|
if gvk == corev1.SchemeGroupVersion.WithKind(reflect.TypeOf(corev1.Namespace{}).Name()) {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
// only set app's namespace when namespace is unspecified
|
|
|
|
// it's by design to set arbitrary namespace in render phase
|
|
|
|
if len(obj.GetNamespace()) == 0 {
|
|
|
|
obj.SetNamespace(af.Namespace)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func (af *Appfile) assembleWorkload(wl *unstructured.Unstructured, compName string, labels map[string]string) {
|
2021-09-23 15:05:25 +08:00
|
|
|
// use component name as workload name if workload name is not specified
|
|
|
|
// don't override the name set in render phase if exist
|
|
|
|
if len(wl.GetName()) == 0 {
|
|
|
|
wl.SetName(compName)
|
|
|
|
}
|
2021-09-13 14:17:12 +08:00
|
|
|
af.setNamespace(wl)
|
2021-09-12 10:12:46 +08:00
|
|
|
af.setWorkloadLabels(wl, labels)
|
|
|
|
af.filterAndSetAnnotations(wl)
|
|
|
|
}
|
|
|
|
|
2022-09-28 10:19:28 +08:00
|
|
|
/*
|
|
|
|
NOTE a workload has these possible labels
|
|
|
|
app.oam.dev/app-revision-hash: ce053923e2fb403f
|
|
|
|
app.oam.dev/appRevision: myapp-v2
|
|
|
|
app.oam.dev/component: mycomp
|
|
|
|
app.oam.dev/name: myapp
|
|
|
|
app.oam.dev/resourceType: WORKLOAD
|
|
|
|
app.oam.dev/revision: mycomp-v2
|
|
|
|
workload.oam.dev/type: kube-worker
|
|
|
|
|
2021-09-12 10:12:46 +08:00
|
|
|
// Component Revision name was not added here (app.oam.dev/revision: mycomp-v2)
|
|
|
|
*/
|
|
|
|
func (af *Appfile) setWorkloadLabels(wl *unstructured.Unstructured, commonLabels map[string]string) {
|
|
|
|
// add more workload-specific labels here
|
|
|
|
util.AddLabels(wl, map[string]string{oam.LabelOAMResourceType: oam.ResourceTypeWorkload})
|
|
|
|
util.AddLabels(wl, commonLabels)
|
|
|
|
}
|
|
|
|
|
|
|
|
func (af *Appfile) assembleTrait(trait *unstructured.Unstructured, compName string, labels map[string]string) {
|
|
|
|
traitType := trait.GetLabels()[oam.TraitTypeLabel]
|
|
|
|
// only set generated name when name is unspecified
|
|
|
|
// it's by design to set arbitrary name in render phase
|
|
|
|
if len(trait.GetName()) == 0 {
|
|
|
|
cpTrait := trait.DeepCopy()
|
2022-10-18 17:16:23 +08:00
|
|
|
// remove labels that should not be calculated into hash
|
2021-09-12 10:12:46 +08:00
|
|
|
util.RemoveLabels(cpTrait, []string{oam.LabelAppRevision})
|
|
|
|
traitName := util.GenTraitNameCompatible(compName, cpTrait, traitType)
|
|
|
|
trait.SetName(traitName)
|
|
|
|
}
|
|
|
|
af.setTraitLabels(trait, labels)
|
|
|
|
af.filterAndSetAnnotations(trait)
|
|
|
|
af.setNamespace(trait)
|
|
|
|
}
|
|
|
|
|
2022-09-28 10:19:28 +08:00
|
|
|
/*
|
|
|
|
NOTE a trait has these possible labels
|
|
|
|
app.oam.dev/app-revision-hash: ce053923e2fb403f
|
|
|
|
app.oam.dev/appRevision: myapp-v2
|
|
|
|
app.oam.dev/component: mycomp
|
|
|
|
app.oam.dev/name: myapp
|
|
|
|
app.oam.dev/resourceType: TRAIT
|
|
|
|
trait.oam.dev/resource: service
|
|
|
|
trait.oam.dev/type: ingress // already added in render phase
|
|
|
|
|
2021-09-12 10:12:46 +08:00
|
|
|
// Component Revision name was not added here (app.oam.dev/revision: mycomp-v2)
|
|
|
|
*/
|
|
|
|
func (af *Appfile) setTraitLabels(trait *unstructured.Unstructured, commonLabels map[string]string) {
|
|
|
|
// add more trait-specific labels here
|
|
|
|
util.AddLabels(trait, map[string]string{oam.LabelOAMResourceType: oam.ResourceTypeTrait})
|
|
|
|
util.AddLabels(trait, commonLabels)
|
|
|
|
}
|
|
|
|
|
|
|
|
func (af *Appfile) setWorkloadRefToTrait(wlRef corev1.ObjectReference, trait *unstructured.Unstructured) error {
|
|
|
|
traitType := trait.GetLabels()[oam.TraitTypeLabel]
|
|
|
|
if traitType == definition.AuxiliaryWorkload {
|
|
|
|
return nil
|
|
|
|
}
|
2021-09-14 10:26:57 +08:00
|
|
|
if strings.Contains(traitType, "-") {
|
|
|
|
splitName := traitType[0:strings.LastIndex(traitType, "-")]
|
|
|
|
_, ok := af.RelatedTraitDefinitions[splitName]
|
|
|
|
if ok {
|
|
|
|
traitType = splitName
|
|
|
|
}
|
|
|
|
}
|
2021-09-12 10:12:46 +08:00
|
|
|
traitDef, ok := af.RelatedTraitDefinitions[traitType]
|
|
|
|
if !ok {
|
|
|
|
return errors.Errorf("TraitDefinition %s not found in appfile", traitType)
|
|
|
|
}
|
|
|
|
workloadRefPath := traitDef.Spec.WorkloadRefPath
|
|
|
|
// only add workload reference to the trait if it asks for it
|
|
|
|
if len(workloadRefPath) != 0 {
|
|
|
|
tmpWLRef := corev1.ObjectReference{
|
|
|
|
APIVersion: wlRef.APIVersion,
|
|
|
|
Kind: wlRef.Kind,
|
|
|
|
Name: wlRef.Name,
|
|
|
|
}
|
|
|
|
if err := fieldpath.Pave(trait.UnstructuredContent()).SetValue(workloadRefPath, tmpWLRef); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2021-04-11 14:10:16 +08:00
|
|
|
// PrepareProcessContext prepares a DSL process Context
|
2022-09-02 12:55:03 +08:00
|
|
|
func PrepareProcessContext(wl *Workload, ctxData velaprocess.ContextData) (process.Context, error) {
|
2021-11-07 11:50:27 +08:00
|
|
|
if wl.Ctx == nil {
|
2022-02-28 10:30:55 +08:00
|
|
|
wl.Ctx = NewBasicContext(ctxData, wl.Params)
|
2021-11-07 11:50:27 +08:00
|
|
|
}
|
|
|
|
if err := wl.EvalContext(wl.Ctx); err != nil {
|
2022-02-28 10:30:55 +08:00
|
|
|
return nil, errors.Wrapf(err, "evaluate base template app=%s in namespace=%s", ctxData.AppName, ctxData.Namespace)
|
2021-04-30 16:28:00 +08:00
|
|
|
}
|
2021-11-07 11:50:27 +08:00
|
|
|
return wl.Ctx, nil
|
2021-04-30 16:28:00 +08:00
|
|
|
}
|
|
|
|
|
2021-05-13 21:46:45 +08:00
|
|
|
// NewBasicContext prepares a basic DSL process Context
|
2022-09-02 12:55:03 +08:00
|
|
|
func NewBasicContext(contextData velaprocess.ContextData, params map[string]interface{}) process.Context {
|
|
|
|
pCtx := velaprocess.NewContext(contextData)
|
2021-11-07 11:50:27 +08:00
|
|
|
if params != nil {
|
|
|
|
pCtx.SetParameters(params)
|
2021-08-06 10:48:00 +08:00
|
|
|
}
|
2021-04-30 16:28:00 +08:00
|
|
|
return pCtx
|
2021-04-11 14:10:16 +08:00
|
|
|
}
|
|
|
|
|
2022-09-02 12:55:03 +08:00
|
|
|
func generateComponentFromCUEModule(wl *Workload, ctxData velaprocess.ContextData) (*types.ComponentManifest, error) {
|
2022-02-28 10:30:55 +08:00
|
|
|
pCtx, err := PrepareProcessContext(wl, ctxData)
|
2021-04-30 16:28:00 +08:00
|
|
|
if err != nil {
|
2021-06-24 15:06:58 +08:00
|
|
|
return nil, err
|
2021-04-30 16:28:00 +08:00
|
|
|
}
|
2022-02-28 10:30:55 +08:00
|
|
|
return baseGenerateComponent(pCtx, wl, ctxData.AppName, ctxData.Namespace)
|
2021-04-30 16:28:00 +08:00
|
|
|
}
|
|
|
|
|
2021-11-07 11:50:27 +08:00
|
|
|
func generateComponentFromTerraformModule(wl *Workload, appName, ns string) (*types.ComponentManifest, error) {
|
|
|
|
return baseGenerateComponent(wl.Ctx, wl, appName, ns)
|
2021-04-30 16:28:00 +08:00
|
|
|
}
|
|
|
|
|
2021-06-24 15:06:58 +08:00
|
|
|
func baseGenerateComponent(pCtx process.Context, wl *Workload, appName, ns string) (*types.ComponentManifest, error) {
|
2021-09-12 10:12:46 +08:00
|
|
|
var err error
|
2022-09-02 12:55:03 +08:00
|
|
|
pCtx.PushData(velaprocess.ContextComponentType, wl.Type)
|
2021-04-11 14:10:16 +08:00
|
|
|
for _, tr := range wl.Traits {
|
|
|
|
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-09-12 10:12:46 +08:00
|
|
|
if patcher := wl.Patch; patcher != nil {
|
|
|
|
workload, auxiliaries := pCtx.Output()
|
|
|
|
if p, err := patcher.LookupValue("workload"); err == nil {
|
2022-08-08 17:57:48 +08:00
|
|
|
if err := workload.Unify(p.CueValue()); err != nil {
|
2021-09-12 10:12:46 +08:00
|
|
|
return nil, errors.WithMessage(err, "patch workload")
|
|
|
|
}
|
|
|
|
}
|
|
|
|
for _, aux := range auxiliaries {
|
2021-09-12 22:45:14 +08:00
|
|
|
if p, err := patcher.LookupByScript(fmt.Sprintf("traits[\"%s\"]", aux.Name)); err == nil && p.CueValue().Err() == nil {
|
2022-08-08 17:57:48 +08:00
|
|
|
if err := aux.Ins.Unify(p.CueValue()); err != nil {
|
2021-09-12 10:12:46 +08:00
|
|
|
return nil, errors.WithMessagef(err, "patch outputs.%s", aux.Name)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2022-08-17 14:04:57 +08:00
|
|
|
compManifest, err := evalWorkloadWithContext(pCtx, wl, ns, appName)
|
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-08-20 13:43:04 +08:00
|
|
|
compManifest.Namespace = ns
|
2021-08-08 12:11:24 +08:00
|
|
|
// we record the external revision name in ExternalRevision field
|
|
|
|
compManifest.ExternalRevision = wl.ExternalRevision
|
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{
|
2021-09-12 10:12:46 +08:00
|
|
|
APIVersion: metav1.GroupVersion{
|
|
|
|
Group: s.GVK.Group,
|
|
|
|
Version: s.GVK.Version,
|
|
|
|
}.String(),
|
|
|
|
Kind: s.GVK.Kind,
|
|
|
|
Name: s.Name,
|
2021-06-24 15:06:58 +08:00
|
|
|
}
|
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
|
|
|
}
|
2022-09-02 12:55:03 +08:00
|
|
|
commonLabels := definition.GetCommonLabels(definition.GetBaseContextLabels(pCtx))
|
2021-05-30 22:57:03 +08:00
|
|
|
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
|
2022-08-17 14:04:57 +08:00
|
|
|
func evalWorkloadWithContext(pCtx process.Context, wl *Workload, ns, appName string) (*types.ComponentManifest, error) {
|
2021-06-24 15:06:58 +08:00
|
|
|
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))
|
2022-09-02 12:55:03 +08:00
|
|
|
commonLabels := definition.GetCommonLabels(definition.GetBaseContextLabels(pCtx))
|
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 {
|
2022-08-17 14:04:57 +08:00
|
|
|
return nil, errors.Wrapf(err, "evaluate trait=%s template for component=%s app=%s", assist.Name, wl.Name, 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")
|
|
|
|
}
|
2022-08-08 17:57:48 +08:00
|
|
|
cuectx := cuecontext.New()
|
|
|
|
expr, err := json2cue.Extract("", objRaw)
|
2021-07-08 15:16:55 +08:00
|
|
|
if err != nil {
|
2022-08-08 17:57:48 +08:00
|
|
|
return templateStr, errors.Wrap(err, "cannot extract object into CUE")
|
2021-07-08 15:16:55 +08:00
|
|
|
}
|
2022-08-08 17:57:48 +08:00
|
|
|
v := cuectx.BuildExpr(expr)
|
|
|
|
cueRaw, err := format.Node(v.Syntax())
|
2021-07-08 15:16:55 +08:00
|
|
|
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(`
|
2022-08-08 17:57:48 +08:00
|
|
|
output: %s`, 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
|
|
|
|
}
|
|
|
|
|
2022-09-02 12:55:03 +08:00
|
|
|
func generateComponentFromKubeModule(wl *Workload, ctxData velaprocess.ContextData) (*types.ComponentManifest, error) {
|
2021-07-08 15:16:55 +08:00
|
|
|
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
|
2022-02-28 10:30:55 +08:00
|
|
|
compManifest, err := generateComponentFromCUEModule(wl, ctxData)
|
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) {
|
2021-10-08 11:35:52 +08:00
|
|
|
if wl.FullTemplate == nil || wl.FullTemplate.Terraform == nil || wl.FullTemplate.Terraform.Configuration == "" {
|
2021-04-30 16:28:00 +08:00
|
|
|
return nil, errors.New(errTerraformConfigurationIsNotSet)
|
|
|
|
}
|
|
|
|
params, err := json.Marshal(wl.Params)
|
|
|
|
if err != nil {
|
|
|
|
return nil, errors.Wrap(err, errFailToConvertTerraformComponentProperties)
|
|
|
|
}
|
|
|
|
|
2022-05-20 18:17:10 +08:00
|
|
|
if wl.FullTemplate.ComponentDefinition == nil || wl.FullTemplate.ComponentDefinition.Spec.Schematic == nil ||
|
|
|
|
wl.FullTemplate.ComponentDefinition.Spec.Schematic.Terraform == nil {
|
|
|
|
return nil, errors.New(errTerraformComponentDefinition)
|
|
|
|
}
|
|
|
|
|
2021-04-30 16:28:00 +08:00
|
|
|
configuration := terraformapi.Configuration{
|
2022-05-09 15:10:31 +08:00
|
|
|
TypeMeta: metav1.TypeMeta{APIVersion: "terraform.core.oam.dev/v1beta2", Kind: "Configuration"},
|
2021-11-28 17:21:12 +08:00
|
|
|
ObjectMeta: metav1.ObjectMeta{
|
2022-05-20 18:17:10 +08:00
|
|
|
Name: wl.Name,
|
|
|
|
Namespace: ns,
|
|
|
|
Annotations: wl.FullTemplate.ComponentDefinition.Annotations,
|
2021-11-28 17:21:12 +08:00
|
|
|
},
|
|
|
|
}
|
2022-05-20 18:17:10 +08:00
|
|
|
// 1. parse the spec of configuration
|
|
|
|
var spec terraformapi.ConfigurationSpec
|
|
|
|
if err := json.Unmarshal(params, &spec); err != nil {
|
|
|
|
return nil, errors.Wrap(err, errFailToConvertTerraformComponentProperties)
|
2021-04-30 16:28:00 +08:00
|
|
|
}
|
2022-05-20 18:17:10 +08:00
|
|
|
configuration.Spec = spec
|
2021-04-30 16:28:00 +08:00
|
|
|
|
2022-05-20 18:17:10 +08:00
|
|
|
if configuration.Spec.WriteConnectionSecretToReference == nil {
|
|
|
|
configuration.Spec.WriteConnectionSecretToReference = wl.FullTemplate.ComponentDefinition.Spec.Schematic.Terraform.WriteConnectionSecretToReference
|
2021-04-30 16:28:00 +08:00
|
|
|
}
|
2022-05-20 18:17:10 +08:00
|
|
|
if configuration.Spec.WriteConnectionSecretToReference != nil && configuration.Spec.WriteConnectionSecretToReference.Namespace == "" {
|
|
|
|
configuration.Spec.WriteConnectionSecretToReference.Namespace = ns
|
2021-04-30 16:28:00 +08:00
|
|
|
}
|
|
|
|
|
2022-05-20 18:17:10 +08:00
|
|
|
if configuration.Spec.ProviderReference == nil {
|
2022-01-06 19:02:03 +08:00
|
|
|
configuration.Spec.ProviderReference = wl.FullTemplate.ComponentDefinition.Spec.Schematic.Terraform.ProviderReference
|
2021-12-02 15:45:50 +08:00
|
|
|
}
|
2022-01-06 19:02:03 +08:00
|
|
|
|
2022-05-20 18:17:10 +08:00
|
|
|
switch wl.FullTemplate.Terraform.Type {
|
|
|
|
case "hcl":
|
|
|
|
configuration.Spec.HCL = wl.FullTemplate.Terraform.Configuration
|
|
|
|
case "remote":
|
|
|
|
configuration.Spec.Remote = wl.FullTemplate.Terraform.Configuration
|
|
|
|
configuration.Spec.Path = wl.FullTemplate.Terraform.Path
|
2021-04-30 16:28:00 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
// 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)
|
2021-12-02 15:45:50 +08:00
|
|
|
delete(variableMap, RegionKey)
|
|
|
|
delete(variableMap, ProviderRefKey)
|
2022-05-20 18:17:10 +08:00
|
|
|
delete(variableMap, ForceDeleteKey)
|
2021-04-30 16:28:00 +08:00
|
|
|
|
|
|
|
data, err := json.Marshal(variableMap)
|
|
|
|
if err != nil {
|
|
|
|
return nil, errors.Wrap(err, errFailToConvertTerraformComponentProperties)
|
|
|
|
}
|
2021-12-02 15:45:50 +08:00
|
|
|
|
2021-04-30 16:28:00 +08:00
|
|
|
configuration.Spec.Variable = &runtime.RawExtension{Raw: data}
|
|
|
|
raw := util.Object2RawExtension(&configuration)
|
2021-10-13 16:16:53 +08:00
|
|
|
return util.RawExtension2Unstructured(raw)
|
2021-04-30 16:28:00 +08:00
|
|
|
}
|
|
|
|
|
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
|
|
|
|
}
|
|
|
|
|
2022-09-02 12:55:03 +08:00
|
|
|
func generateComponentFromHelmModule(wl *Workload, ctxData velaprocess.ContextData) (*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{
|
2021-08-08 12:11:24 +08:00
|
|
|
Name: wl.Name,
|
2022-02-28 10:30:55 +08:00
|
|
|
Namespace: ctxData.Namespace,
|
2021-08-08 12:11:24 +08:00
|
|
|
ExternalRevision: wl.ExternalRevision,
|
2021-09-12 10:12:46 +08:00
|
|
|
StandardWorkload: &unstructured.Unstructured{},
|
2021-06-24 15:06:58 +08:00
|
|
|
}
|
2021-09-13 14:17:12 +08:00
|
|
|
|
2021-05-24 23:32:02 +08:00
|
|
|
if wl.FullTemplate.Reference.Type != types.AutoDetectWorkloadDefinition {
|
2022-02-28 10:30:55 +08:00
|
|
|
compManifest, err = generateComponentFromCUEModule(wl, ctxData)
|
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
|
|
|
}
|
|
|
|
|
2022-02-28 10:30:55 +08:00
|
|
|
rls, repo, err := helm.RenderHelmReleaseAndHelmRepo(wl.FullTemplate.Helm, wl.Name, ctxData.AppName, ctxData.Namespace, 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
|
|
|
}
|
2022-02-28 10:30:55 +08:00
|
|
|
|
|
|
|
// GenerateContextDataFromAppFile generates process context data from app file
|
2022-09-02 12:55:03 +08:00
|
|
|
func GenerateContextDataFromAppFile(appfile *Appfile, wlName string) velaprocess.ContextData {
|
|
|
|
data := velaprocess.ContextData{
|
2022-02-28 10:30:55 +08:00
|
|
|
Namespace: appfile.Namespace,
|
|
|
|
AppName: appfile.Name,
|
|
|
|
CompName: wlName,
|
|
|
|
AppRevisionName: appfile.AppRevisionName,
|
|
|
|
Components: appfile.Components,
|
|
|
|
}
|
|
|
|
if appfile.AppAnnotations != nil {
|
|
|
|
data.WorkflowName = appfile.AppAnnotations[oam.AnnotationWorkflowName]
|
|
|
|
data.PublishVersion = appfile.AppAnnotations[oam.AnnotationPublishVersion]
|
2022-03-21 12:02:30 +08:00
|
|
|
data.AppAnnotations = appfile.AppAnnotations
|
|
|
|
}
|
|
|
|
if appfile.AppLabels != nil {
|
|
|
|
data.AppLabels = appfile.AppLabels
|
2022-02-28 10:30:55 +08:00
|
|
|
}
|
|
|
|
return data
|
|
|
|
}
|
2022-03-24 14:40:19 +08:00
|
|
|
|
|
|
|
// WorkflowClient cache retrieved workflow if ApplicationRevision not exists in appfile
|
|
|
|
// else use the workflow in ApplicationRevision
|
|
|
|
func (af *Appfile) WorkflowClient(cli client.Client) client.Client {
|
2022-03-28 16:09:12 +08:00
|
|
|
return velaclient.DelegatingHandlerClient{
|
2022-03-24 14:40:19 +08:00
|
|
|
Client: cli,
|
|
|
|
Getter: func(ctx context.Context, key client.ObjectKey, obj client.Object) error {
|
2022-09-02 12:55:03 +08:00
|
|
|
if wf, ok := obj.(*workflowv1alpha1.Workflow); ok {
|
2022-03-24 14:40:19 +08:00
|
|
|
if af.AppRevision != nil {
|
|
|
|
if af.ExternalWorkflow != nil && af.ExternalWorkflow.Name == key.Name && af.ExternalWorkflow.Namespace == key.Namespace {
|
|
|
|
af.ExternalWorkflow.DeepCopyInto(wf)
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
return kerrors.NewNotFound(v1alpha1.SchemeGroupVersion.WithResource("workflow").GroupResource(), key.Name)
|
|
|
|
}
|
|
|
|
if err := cli.Get(ctx, key, obj); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
2022-09-02 12:55:03 +08:00
|
|
|
af.ExternalWorkflow = obj.(*workflowv1alpha1.Workflow)
|
2022-03-24 14:40:19 +08:00
|
|
|
return nil
|
|
|
|
}
|
|
|
|
return cli.Get(ctx, key, obj)
|
|
|
|
},
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// PolicyClient cache retrieved policy if ApplicationRevision not exists in appfile
|
|
|
|
// else use the policy in ApplicationRevision
|
|
|
|
func (af *Appfile) PolicyClient(cli client.Client) client.Client {
|
2022-03-28 16:09:12 +08:00
|
|
|
return velaclient.DelegatingHandlerClient{
|
2022-03-24 14:40:19 +08:00
|
|
|
Client: cli,
|
|
|
|
Getter: func(ctx context.Context, key client.ObjectKey, obj client.Object) error {
|
|
|
|
if po, ok := obj.(*v1alpha1.Policy); ok {
|
|
|
|
if af.AppRevision != nil {
|
|
|
|
if p, found := af.ExternalPolicies[key.String()]; found {
|
|
|
|
p.DeepCopyInto(po)
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
return kerrors.NewNotFound(v1alpha1.SchemeGroupVersion.WithResource("policy").GroupResource(), key.Name)
|
|
|
|
}
|
|
|
|
if err := cli.Get(ctx, key, obj); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
af.ExternalPolicies[key.String()] = obj.(*v1alpha1.Policy)
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
return cli.Get(ctx, key, obj)
|
|
|
|
},
|
|
|
|
}
|
|
|
|
}
|
2022-04-28 16:29:34 +08:00
|
|
|
|
|
|
|
// LoadDynamicComponent for ref-objects typed components, this function will load referred objects from stored revisions
|
|
|
|
func (af *Appfile) LoadDynamicComponent(ctx context.Context, cli client.Client, comp *common.ApplicationComponent) (*common.ApplicationComponent, error) {
|
|
|
|
if comp.Type != v1alpha1.RefObjectsComponentType {
|
|
|
|
return comp, nil
|
|
|
|
}
|
|
|
|
_comp := comp.DeepCopy()
|
|
|
|
spec := &v1alpha1.RefObjectsComponentSpec{}
|
|
|
|
if err := json.Unmarshal(comp.Properties.Raw, spec); err != nil {
|
|
|
|
return nil, errors.Wrapf(err, "invalid ref-objects component properties")
|
|
|
|
}
|
|
|
|
var uns []*unstructured.Unstructured
|
2022-05-28 01:26:06 +08:00
|
|
|
ctx = auth.ContextWithUserInfo(ctx, af.app)
|
2022-04-28 16:29:34 +08:00
|
|
|
for _, selector := range spec.Objects {
|
|
|
|
objs, err := component.SelectRefObjectsForDispatch(ctx, component.ReferredObjectsDelegatingClient(cli, af.ReferredObjects), af.Namespace, comp.Name, selector)
|
|
|
|
if err != nil {
|
|
|
|
return nil, errors.Wrapf(err, "failed to select objects from referred objects in revision storage")
|
|
|
|
}
|
|
|
|
uns = component.AppendUnstructuredObjects(uns, objs...)
|
|
|
|
}
|
2022-06-24 19:34:51 +08:00
|
|
|
// nolint
|
|
|
|
for _, url := range spec.URLs {
|
|
|
|
objs := utilscommon.FilterObjectsByCondition(af.ReferredObjects, func(obj *unstructured.Unstructured) bool {
|
|
|
|
return obj.GetAnnotations() != nil && obj.GetAnnotations()[oam.AnnotationResourceURL] == url
|
|
|
|
})
|
|
|
|
uns = component.AppendUnstructuredObjects(uns, objs...)
|
|
|
|
}
|
2022-04-28 16:29:34 +08:00
|
|
|
refObjs, err := component.ConvertUnstructuredsToReferredObjects(uns)
|
|
|
|
if err != nil {
|
|
|
|
return nil, errors.Wrapf(err, "failed to marshal referred object")
|
|
|
|
}
|
|
|
|
bs, err := json.Marshal(&common.ReferredObjectList{Objects: refObjs})
|
|
|
|
if err != nil {
|
|
|
|
return nil, errors.Wrapf(err, "failed to marshal loaded ref-objects")
|
|
|
|
}
|
|
|
|
_comp.Properties = &runtime.RawExtension{Raw: bs}
|
|
|
|
return _comp, nil
|
|
|
|
}
|