Fix Terraform application status issue (#1611)

* Fix Terraform application status issue

Fix #1599

* add unit tests

* fix import issue
This commit is contained in:
Zheng Xi Zhou
2021-05-13 21:46:45 +08:00
committed by GitHub
parent 4ef8cb3f12
commit b4ae473dae
9 changed files with 303 additions and 43 deletions
+1 -1
View File
@@ -37,7 +37,7 @@ require (
github.com/mholt/archiver/v3 v3.3.0
github.com/mitchellh/hashstructure/v2 v2.0.1
github.com/oam-dev/terraform-config-inspect v0.0.0-20210418082552-fc72d929aa28
github.com/oam-dev/terraform-controller v0.1.4
github.com/oam-dev/terraform-controller v0.1.6
github.com/olekukonko/tablewriter v0.0.2
github.com/onsi/ginkgo v1.13.0
github.com/onsi/gomega v1.10.3
+2 -2
View File
@@ -944,8 +944,8 @@ github.com/oam-dev/stern v1.13.0-alpha h1:EVjM8Qvh6LssB6t4RZrjf9DtCq1cz+/cy6OF7f
github.com/oam-dev/stern v1.13.0-alpha/go.mod h1:AOkvfFUv0Arz7GBi0jz7S0Jsu4K/kdvSjNsnRt1+BIg=
github.com/oam-dev/terraform-config-inspect v0.0.0-20210418082552-fc72d929aa28 h1:tD8HiFKnt0jnwdTWjeqUnfnUYLD/+Nsmj8ZGIxqDWiU=
github.com/oam-dev/terraform-config-inspect v0.0.0-20210418082552-fc72d929aa28/go.mod h1:Mu8i0/DdplvnjwRbAYPsc8+LRR27n/mp8VWdkN10GzE=
github.com/oam-dev/terraform-controller v0.1.4 h1:GFd2RqfBZOmCtFzyl/QkB91X1KEfKOs6u6hO+JmlOtI=
github.com/oam-dev/terraform-controller v0.1.4/go.mod h1:8plSKkwgTSWnPDCEaQm/PAwPKed/kVo94Z2wwmJSL6k=
github.com/oam-dev/terraform-controller v0.1.6 h1:Uhd8iMibQ6SNeF5jQI5Z9w7/H7U7XaDwKtNtmbaHTeI=
github.com/oam-dev/terraform-controller v0.1.6/go.mod h1:8plSKkwgTSWnPDCEaQm/PAwPKed/kVo94Z2wwmJSL6k=
github.com/oklog/oklog v0.3.2/go.mod h1:FCV+B7mhrz4o+ueLpx+KqkyXRGMWOYEvfiXtdGtbWGs=
github.com/oklog/run v1.0.0/go.mod h1:dlhp/R75TPv97u0XWUtDeV/lRKWPKSdTuV0TZvrmrQA=
github.com/oklog/run v1.1.0/go.mod h1:sVPdnTZT1zYwAJeCMu2Th4T21pA3FPOQRfWjQlk7DVU=
+4 -9
View File
@@ -165,11 +165,6 @@ type Appfile struct {
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) {
@@ -221,15 +216,15 @@ func (af *Appfile) GenerateApplicationConfiguration() (*v1alpha2.ApplicationConf
// PrepareProcessContext prepares a DSL process Context
func PrepareProcessContext(wl *Workload, applicationName, revision, namespace string) (process.Context, error) {
pCtx := newContext(wl, applicationName, revision, namespace)
pCtx := NewBasicContext(wl, applicationName, revision, namespace)
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
}
// newContext prepares a basic DSL process Context
func newContext(wl *Workload, applicationName, revision, namespace string) process.Context {
// NewBasicContext prepares a basic DSL process Context
func NewBasicContext(wl *Workload, applicationName, revision, namespace string) process.Context {
pCtx := process.NewContext(namespace, wl.Name, applicationName, revision)
pCtx.InsertSecrets(wl.OutputSecretName, wl.RequiredSecrets)
if len(wl.UserConfigs) > 0 {
@@ -247,7 +242,7 @@ func generateComponentFromCUEModule(wl *Workload, appName, revision, ns string)
}
func generateComponentFromTerraformModule(wl *Workload, appName, revision, ns string) (*v1alpha2.Component, *v1alpha2.ApplicationConfigurationComponent, error) {
pCtx := newContext(wl, appName, revision, ns)
pCtx := NewBasicContext(wl, appName, revision, ns)
return baseGenerateComponent(pCtx, wl, appName, ns)
}
+16 -1
View File
@@ -29,6 +29,7 @@ import (
. "github.com/onsi/ginkgo"
. "github.com/onsi/gomega"
"github.com/pkg/errors"
"gotest.tools/assert"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
"k8s.io/apimachinery/pkg/runtime"
@@ -38,6 +39,7 @@ import (
"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/dsl/process"
"github.com/oam-dev/kubevela/pkg/oam"
"github.com/oam-dev/kubevela/pkg/oam/util"
)
@@ -471,6 +473,7 @@ variable "password" {
"writeConnectionSecretToRef": map[string]interface{}{
"name": "db",
},
process.OutputSecretName: "db-conn",
},
}
@@ -805,7 +808,7 @@ variable "password" {
revision: "v1",
}
pCtx := newContext(args.wl, args.appName, args.revision, ns)
pCtx := NewBasicContext(args.wl, args.appName, args.revision, ns)
comp, acc, err = evalWorkloadWithContext(pCtx, args.wl, ns, args.appName, compName)
Expect(comp.Spec.Workload).ShouldNot(BeNil())
Expect(acc.ComponentName).Should(Equal(""))
@@ -952,3 +955,15 @@ func TestGenerateTerraformConfigurationWorkload(t *testing.T) {
}
}
}
func TestGetUserConfigName(t *testing.T) {
wl1 := &Workload{Params: nil}
assert.Equal(t, wl1.GetUserConfigName(), "")
wl2 := &Workload{Params: map[string]interface{}{AppfileBuiltinConfig: 1}}
assert.Equal(t, wl2.GetUserConfigName(), "")
config := "abc"
wl3 := &Workload{Params: map[string]interface{}{AppfileBuiltinConfig: config}}
assert.Equal(t, wl3.GetUserConfigName(), config)
}
@@ -27,6 +27,8 @@ import (
"github.com/crossplane/crossplane-runtime/pkg/logging"
"github.com/crossplane/crossplane-runtime/pkg/meta"
"github.com/go-logr/logr"
terraformtypes "github.com/oam-dev/terraform-controller/api/types"
terraformapi "github.com/oam-dev/terraform-controller/api/v1beta1"
"github.com/pkg/errors"
v1 "k8s.io/api/core/v1"
apierrors "k8s.io/apimachinery/pkg/api/errors"
@@ -44,6 +46,7 @@ import (
"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"
"github.com/oam-dev/kubevela/pkg/controller/core.oam.dev/v1alpha2/applicationconfiguration"
"github.com/oam-dev/kubevela/pkg/controller/core.oam.dev/v1alpha2/applicationrollout"
@@ -214,8 +217,9 @@ func (h *appHandler) statusAggregate(appFile *appfile.Appfile) ([]common.Applica
var (
outputSecretName string
err error
pCtx process.Context
)
pCtx := process.NewContext(h.app.Namespace, wl.Name, appFile.Name, appFile.RevisionName)
if wl.IsCloudResourceProducer() {
outputSecretName, err = appfile.GetOutputSecretNames(wl)
if err != nil {
@@ -223,29 +227,49 @@ func (h *appHandler) statusAggregate(appFile *appfile.Appfile) ([]common.Applica
}
pCtx.InsertSecrets(outputSecretName, wl.RequiredSecrets)
}
if err := wl.EvalContext(pCtx); err != nil {
return nil, false, errors.WithMessagef(err, "app=%s, comp=%s, evaluate context error", appFile.Name, wl.Name)
switch wl.CapabilityCategory {
case types.TerraformCategory:
pCtx = appfile.NewBasicContext(wl, appFile.Name, appFile.RevisionName, appFile.Namespace)
ctx := context.Background()
var configuration terraformapi.Configuration
if err := h.r.Client.Get(ctx, client.ObjectKey{Name: wl.Name, Namespace: h.app.Namespace}, &configuration); err != nil {
return nil, false, errors.WithMessagef(err, "app=%s, comp=%s, check health error", appFile.Name, wl.Name)
}
if configuration.Status.State != terraformtypes.Available {
healthy = false
status.Healthy = false
} else {
status.Healthy = true
}
status.Message = configuration.Status.Message
default:
pCtx = process.NewContext(h.app.Namespace, wl.Name, appFile.Name, appFile.RevisionName)
if err := wl.EvalContext(pCtx); err != nil {
return nil, false, errors.WithMessagef(err, "app=%s, comp=%s, evaluate context error", appFile.Name, wl.Name)
}
workloadHealth, err := wl.EvalHealth(pCtx, h.r, h.app.Namespace)
if err != nil {
return nil, false, errors.WithMessagef(err, "app=%s, comp=%s, check health error", appFile.Name, wl.Name)
}
if !workloadHealth {
// TODO(wonderflow): we should add a custom way to let the template say why it's unhealthy, only a bool flag is not enough
status.Healthy = false
healthy = false
}
status.Message, err = wl.EvalStatus(pCtx, h.r, h.app.Namespace)
if err != nil {
return nil, false, errors.WithMessagef(err, "app=%s, comp=%s, evaluate workload status message error", appFile.Name, wl.Name)
}
}
for _, tr := range wl.Traits {
if err := tr.EvalContext(pCtx); err != nil {
return nil, false, errors.WithMessagef(err, "app=%s, comp=%s, trait=%s, evaluate context error", appFile.Name, wl.Name, tr.Name)
}
}
workloadHealth, err := wl.EvalHealth(pCtx, h.r, h.app.Namespace)
if err != nil {
return nil, false, errors.WithMessagef(err, "app=%s, comp=%s, check health error", appFile.Name, wl.Name)
}
if !workloadHealth {
// TODO(wonderflow): we should add a custom way to let the template say why it's unhealthy, only a bool flag is not enough
status.Healthy = false
healthy = false
}
status.Message, err = wl.EvalStatus(pCtx, h.r, h.app.Namespace)
if err != nil {
return nil, false, errors.WithMessagef(err, "app=%s, comp=%s, evaluate workload status message error", appFile.Name, wl.Name)
}
var traitStatusList []common.ApplicationTraitStatus
for _, trait := range wl.Traits {
var traitStatus = common.ApplicationTraitStatus{
@@ -22,21 +22,27 @@ import (
"strings"
"time"
"github.com/crossplane/crossplane-runtime/pkg/logging"
"github.com/ghodss/yaml"
"k8s.io/apimachinery/pkg/types"
"sigs.k8s.io/controller-runtime/pkg/reconcile"
terraformtypes "github.com/oam-dev/terraform-controller/api/types"
terraformapi "github.com/oam-dev/terraform-controller/api/v1beta1"
. "github.com/onsi/ginkgo"
. "github.com/onsi/gomega"
"github.com/crossplane/crossplane-runtime/pkg/logging"
corev1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/types"
"sigs.k8s.io/controller-runtime/pkg/client"
"sigs.k8s.io/controller-runtime/pkg/reconcile"
"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"
velatypes "github.com/oam-dev/kubevela/apis/types"
"github.com/oam-dev/kubevela/pkg/appfile"
"github.com/oam-dev/kubevela/pkg/controller/utils"
"github.com/oam-dev/kubevela/pkg/oam/util"
"github.com/oam-dev/kubevela/pkg/utils/apply"
)
const workloadDefinition = `
@@ -235,3 +241,103 @@ var _ = Describe("Test Application apply", func() {
Expect(strings.Compare(applabel, app.Name) == 0).Should(BeTrue())
})
})
var _ = Describe("Test applyHelmModuleResources", func() {
var handler appHandler
var app *v1beta1.Application
var ctx context.Context
BeforeEach(func() {
ctx = context.TODO()
app = &v1beta1.Application{}
handler = appHandler{
r: reconciler,
app: app,
logger: reconciler.Log.WithValues("application", "unit-test"),
}
handler.r.applicator = apply.NewAPIApplicator(reconciler.Client)
})
It("helm component is complete", func() {
release := map[string]interface{}{"chart": map[string]interface{}{"spec": map[string]interface{}{"chart": "abc", "version": "v1"}}}
repo := map[string]interface{}{"url": "http://abc.com"}
comp := &v1alpha2.Component{Spec: v1alpha2.ComponentSpec{Helm: &common.Helm{
Release: util.Object2RawExtension(release),
Repository: util.Object2RawExtension(repo),
}}}
err := handler.applyHelmModuleResources(ctx, comp, nil)
Expect(err).Should(HaveOccurred())
})
It("helm repo format is invalid", func() {
comp := &v1alpha2.Component{Spec: v1alpha2.ComponentSpec{Helm: &common.Helm{
Repository: runtime.RawExtension{Raw: []byte("abc")}}}}
err := handler.applyHelmModuleResources(ctx, comp, nil)
Expect(err).Should(HaveOccurred())
})
It("helm release format is invalid", func() {
repo := map[string]interface{}{"url": "http://abc.com"}
comp := &v1alpha2.Component{Spec: v1alpha2.ComponentSpec{Helm: &common.Helm{
Release: runtime.RawExtension{Raw: []byte("abc")},
Repository: util.Object2RawExtension(repo),
}}}
err := handler.applyHelmModuleResources(ctx, comp, nil)
Expect(err).Should(HaveOccurred())
})
})
var _ = Describe("Test statusAggregate", func() {
It("the component is Terraform type", func() {
var (
ctx = context.TODO()
componentName = "sample-oss"
ns = "default"
h = &appHandler{r: reconciler, app: &v1beta1.Application{
TypeMeta: metav1.TypeMeta{},
ObjectMeta: metav1.ObjectMeta{Namespace: ns},
}}
appFile = &appfile.Appfile{
Workloads: []*appfile.Workload{
{
Name: componentName,
FullTemplate: &appfile.Template{Reference: common.WorkloadGVK{APIVersion: "v1", Kind: "A1"}},
CapabilityCategory: velatypes.TerraformCategory,
},
},
}
)
By("aggregate status")
statuses, healthy, err := h.statusAggregate(appFile)
Expect(statuses).Should(BeNil())
Expect(healthy).Should(Equal(false))
Expect(err).Should(HaveOccurred())
By("create Terraform configuration")
configuration := terraformapi.Configuration{
TypeMeta: metav1.TypeMeta{APIVersion: "terraform.core.oam.dev/v1beta1", Kind: "Configuration"},
ObjectMeta: metav1.ObjectMeta{Name: componentName, Namespace: ns},
}
k8sClient.Create(ctx, &configuration)
By("aggregate status again")
statuses, healthy, err = h.statusAggregate(appFile)
Expect(len(statuses)).Should(Equal(1))
Expect(healthy).Should(Equal(false))
Expect(err).Should(BeNil())
By("set status for Terraform configuration")
var gotConfiguration terraformapi.Configuration
k8sClient.Get(ctx, client.ObjectKey{Namespace: ns, Name: componentName}, &gotConfiguration)
gotConfiguration.Status.State = terraformtypes.Available
k8sClient.Status().Update(ctx, &gotConfiguration)
By("aggregate status one more time")
statuses, healthy, err = h.statusAggregate(appFile)
Expect(len(statuses)).Should(Equal(1))
Expect(healthy).Should(Equal(true))
Expect(err).Should(BeNil())
})
})
@@ -24,17 +24,15 @@ import (
"testing"
"time"
"github.com/pkg/errors"
"k8s.io/apimachinery/pkg/api/meta"
. "github.com/onsi/ginkgo"
. "github.com/onsi/gomega"
"github.com/crossplane/crossplane-runtime/pkg/event"
"github.com/crossplane/crossplane-runtime/pkg/logging"
"github.com/go-logr/logr"
terraformv1beta1 "github.com/oam-dev/terraform-controller/api/v1beta1"
. "github.com/onsi/ginkgo"
. "github.com/onsi/gomega"
"github.com/pkg/errors"
corev1 "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/api/meta"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/client-go/kubernetes/scheme"
@@ -101,7 +99,7 @@ var _ = BeforeSuite(func(done Done) {
logf.Log.Info("start application suit test", "yaml_path", yamlPath)
testEnv = &envtest.Environment{
UseExistingCluster: pointer.BoolPtr(false),
CRDDirectoryPaths: []string{yamlPath},
CRDDirectoryPaths: []string{yamlPath, "./testdata/crds/terraform.core.oam.dev_configurations.yaml"},
}
var err error
@@ -120,6 +118,9 @@ var _ = BeforeSuite(func(done Done) {
err = scheme.AddToScheme(testScheme)
Expect(err).NotTo(HaveOccurred())
terraformv1beta1.AddToScheme(testScheme)
// +kubebuilder:scaffold:scheme
k8sClient, err = client.New(cfg, client.Options{Scheme: testScheme})
Expect(err).ToNot(HaveOccurred())
@@ -0,0 +1,117 @@
---
apiVersion: apiextensions.k8s.io/v1beta1
kind: CustomResourceDefinition
metadata:
annotations:
controller-gen.kubebuilder.io/version: v0.2.5
creationTimestamp: null
name: configurations.terraform.core.oam.dev
spec:
additionalPrinterColumns:
- JSONPath: .status.state
name: STATE
type: string
- JSONPath: .metadata.creationTimestamp
name: AGE
type: date
group: terraform.core.oam.dev
names:
kind: Configuration
listKind: ConfigurationList
plural: configurations
singular: configuration
scope: Namespaced
subresources:
status: {}
validation:
openAPIV3Schema:
description: Configuration is the Schema for the configurations API
properties:
apiVersion:
description: 'APIVersion defines the versioned schema of this representation
of an object. Servers should convert recognized schemas to the latest
internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources'
type: string
kind:
description: 'Kind is a string value representing the REST resource this
object represents. Servers may infer this from the endpoint the client
submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds'
type: string
metadata:
type: object
spec:
description: ConfigurationSpec defines the desired state of Configuration
properties:
JSON:
description: JSON is the Terraform JSON syntax configuration
type: string
backend:
description: Backend stores the state in a Kubernetes secret with locking
done using a Lease resource. TODO(zzxwill) If a backend exists in
HCL/JSON, this can be optional. Currently, if Backend is not set by
users, it still will set by the controller, ignoring the settings
in HCL/JSON backend
properties:
inClusterConfig:
description: InClusterConfig Used to authenticate to the cluster
from inside a pod. Only `true` is allowed
type: boolean
secretSuffix:
description: 'SecretSuffix used when creating secrets. Secrets will
be named in the format: tfstate-{workspace}-{secretSuffix}'
type: string
type: object
hcl:
description: HCL is the Terraform HCL type configuration
type: string
variable:
type: object
x-kubernetes-preserve-unknown-fields: true
writeConnectionSecretToRef:
description: WriteConnectionSecretToReference specifies the namespace
and name of a Secret to which any connection details for this managed
resource should be written. Connection details frequently include
the endpoint, username, and password required to connect to the managed
resource.
properties:
name:
description: Name of the secret.
type: string
namespace:
description: Namespace of the secret.
type: string
required:
- name
type: object
type: object
status:
description: ConfigurationStatus defines the observed state of Configuration
properties:
message:
type: string
outputs:
additionalProperties:
properties:
type:
type: string
value:
type: string
type: object
type: object
state:
description: A ResourceState represents the status of a resource
type: string
type: object
type: object
version: v1beta1
versions:
- name: v1beta1
served: true
storage: true
status:
acceptedNames:
kind: ""
plural: ""
conditions: []
storedVersions: []
+2
View File
@@ -33,6 +33,7 @@ import (
"cuelang.org/go/encoding/openapi"
"github.com/AlecAivazis/survey/v2"
"github.com/ghodss/yaml"
terraformv1beta1 "github.com/oam-dev/terraform-controller/api/v1beta1"
kruise "github.com/openkruise/kruise-api/apps/v1alpha1"
certmanager "github.com/wonderflow/cert-manager-api/pkg/apis/certmanager/v1"
istioclientv1beta1 "istio.io/client-go/pkg/apis/networking/v1beta1"
@@ -59,6 +60,7 @@ func init() {
_ = istioclientv1beta1.AddToScheme(Scheme)
_ = certmanager.AddToScheme(Scheme)
_ = kruise.AddToScheme(Scheme)
_ = terraformv1beta1.AddToScheme(Scheme)
// +kubebuilder:scaffold:scheme
}