refactor application controller

This commit is contained in:
Ryan Zhang
2021-01-21 00:34:04 -08:00
parent 4661297ca5
commit d74dbb4fda
19 changed files with 289 additions and 461 deletions
@@ -1,57 +1,46 @@
package builder
package application
import (
"github.com/crossplane/crossplane-runtime/apis/core/v1alpha1"
"k8s.io/apimachinery/pkg/runtime"
"sigs.k8s.io/controller-runtime/pkg/client"
"github.com/oam-dev/kubevela/apis/core.oam.dev/v1alpha2"
"github.com/oam-dev/kubevela/pkg/appfile/config"
"github.com/oam-dev/kubevela/pkg/controller/core.oam.dev/v1alpha2/application/parser"
"github.com/oam-dev/kubevela/pkg/dsl/process"
"github.com/oam-dev/kubevela/pkg/oam"
)
type builder struct {
app *parser.Appfile
c client.Client
}
const (
// OAMApplicationLabel is application's metadata label
OAMApplicationLabel = "application.oam.dev"
)
// Build template to applicationConfig & Component
func Build(ns string, app *parser.Appfile, c client.Client) (*v1alpha2.ApplicationConfiguration, []*v1alpha2.Component, error) {
b := &builder{app: app, c: c}
return b.CompleteWithContext(ns)
}
func (b *builder) CompleteWithContext(ns string) (*v1alpha2.ApplicationConfiguration, []*v1alpha2.Component, error) {
// GenerateApplicationConfiguration converts an appFile to applicationConfig & Components
func (p *Parser) GenerateApplicationConfiguration(app *Appfile, ns string) (*v1alpha2.ApplicationConfiguration,
[]*v1alpha2.Component, error) {
appconfig := &v1alpha2.ApplicationConfiguration{}
appconfig.SetGroupVersionKind(v1alpha2.ApplicationConfigurationGroupVersionKind)
appconfig.Name = b.app.Name
appconfig.Name = app.Name
appconfig.Namespace = ns
appconfig.Spec.Components = []v1alpha2.ApplicationConfigurationComponent{}
if appconfig.Labels == nil {
appconfig.Labels = map[string]string{}
}
appconfig.Labels[OAMApplicationLabel] = b.app.Name
appconfig.Labels[OAMApplicationLabel] = app.Name
var components []*v1alpha2.Component
for _, wl := range b.app.Services {
for _, wl := range app.Workloads {
pCtx := process.NewContext(wl.Name)
userConfig := wl.GetUserConfigName()
if userConfig != "" {
cg := config.Configmap{Client: b.c}
cg := config.Configmap{Client: p.client}
// TODO(wonderflow): envName should not be namespace when we have serverside env
var envName = ns
data, err := cg.GetConfigData(config.GenConfigMapName(b.app.Name, wl.Name, userConfig), envName)
data, err := cg.GetConfigData(config.GenConfigMapName(app.Name, wl.Name, userConfig), envName)
if err != nil {
return nil, nil, err
}
@@ -66,7 +55,7 @@ func (b *builder) CompleteWithContext(ns string) (*v1alpha2.ApplicationConfigura
return nil, nil, err
}
}
comp, acComp, err := generateOAM(pCtx, wl)
comp, acComp, err := evalWorkloadWithContext(pCtx, wl)
if err != nil {
return nil, nil, err
}
@@ -85,7 +74,7 @@ func (b *builder) CompleteWithContext(ns string) (*v1alpha2.ApplicationConfigura
if comp.Labels == nil {
comp.Labels = map[string]string{}
}
comp.Labels[OAMApplicationLabel] = b.app.Name
comp.Labels[OAMApplicationLabel] = app.Name
comp.SetGroupVersionKind(v1alpha2.ComponentGroupVersionKind)
components = append(components, comp)
@@ -94,7 +83,8 @@ func (b *builder) CompleteWithContext(ns string) (*v1alpha2.ApplicationConfigura
return appconfig, components, nil
}
func generateOAM(pCtx process.Context, wl *parser.Workload) (*v1alpha2.Component, *v1alpha2.ApplicationConfigurationComponent, error) {
// evalWorkloadWithContext evaluate the workload's template to generate component and ACComponent
func evalWorkloadWithContext(pCtx process.Context, wl *Workload) (*v1alpha2.Component, *v1alpha2.ApplicationConfigurationComponent, error) {
base, assists := pCtx.Output()
componentWorkload, err := base.Unstructured()
if err != nil {
@@ -14,18 +14,17 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
package builder
package application
import (
"context"
"fmt"
"path/filepath"
"testing"
. "github.com/onsi/ginkgo"
. "github.com/onsi/gomega"
"github.com/crossplane/crossplane-runtime/apis/core/v1alpha1"
"github.com/google/go-cmp/cmp"
. "github.com/onsi/ginkgo"
. "github.com/onsi/gomega"
"github.com/stretchr/testify/assert"
corev1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
@@ -33,16 +32,8 @@ import (
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/runtime/schema"
"k8s.io/client-go/kubernetes/scheme"
"k8s.io/client-go/rest"
"sigs.k8s.io/controller-runtime/pkg/client"
"sigs.k8s.io/controller-runtime/pkg/envtest"
"sigs.k8s.io/controller-runtime/pkg/envtest/printer"
logf "sigs.k8s.io/controller-runtime/pkg/log"
"sigs.k8s.io/controller-runtime/pkg/log/zap"
"github.com/oam-dev/kubevela/apis/core.oam.dev/v1alpha2"
"github.com/oam-dev/kubevela/pkg/controller/core.oam.dev/v1alpha2/application/parser"
"github.com/oam-dev/kubevela/pkg/oam/util"
// +kubebuilder:scaffold:imports
)
@@ -50,58 +41,11 @@ import (
// These tests use Ginkgo (BDD-style Go testing framework). Refer to
// http://onsi.github.io/ginkgo/ to learn more about Ginkgo.
var cfg *rest.Config
var k8sClient client.Client
var testEnv *envtest.Environment
var testScheme = runtime.NewScheme()
func TestAPIs(t *testing.T) {
RegisterFailHandler(Fail)
RunSpecsWithDefaultAndCustomReporters(t,
"Controller Suite",
[]Reporter{printer.NewlineReporter{}})
}
var _ = BeforeSuite(func(done Done) {
logf.SetLogger(zap.LoggerTo(GinkgoWriter, true))
By("bootstrapping test environment")
testEnv = &envtest.Environment{
CRDDirectoryPaths: []string{filepath.Join("../../../../..", "charts", "vela-core", "crds")},
}
var err error
cfg, err = testEnv.Start()
Expect(err).ToNot(HaveOccurred())
Expect(cfg).ToNot(BeNil())
err = v1alpha2.SchemeBuilder.AddToScheme(testScheme)
Expect(err).NotTo(HaveOccurred())
err = scheme.AddToScheme(testScheme)
Expect(err).NotTo(HaveOccurred())
// +kubebuilder:scaffold:scheme
k8sClient, err = client.New(cfg, client.Options{Scheme: testScheme})
Expect(err).ToNot(HaveOccurred())
Expect(k8sClient).ToNot(BeNil())
close(done)
}, 60)
var _ = AfterSuite(func() {
By("tearing down the test environment")
err := testEnv.Stop()
Expect(err).ToNot(HaveOccurred())
})
var _ = Describe("Test Application Controller", func() {
var _ = Describe("Test appFile parser", func() {
// TestApp is test data
var TestApp = &parser.Appfile{
var TestApp = &Appfile{
Name: "test",
Services: []*parser.Workload{
Workloads: []*Workload{
{
Name: "myweb",
Type: "worker",
@@ -110,7 +54,7 @@ var _ = Describe("Test Application Controller", func() {
"cmd": []interface{}{"sleep", "1000"},
"config": "myconfig",
},
Scopes: []parser.Scope{
Scopes: []Scope{
{Name: "test-scope", GVK: schema.GroupVersionKind{
Group: "core.oam.dev",
Version: "v1alpha2",
@@ -159,7 +103,7 @@ var _ = Describe("Test Application Controller", func() {
cmd?: [...string]
}`,
Traits: []*parser.Trait{
Traits: []*Trait{
{
Name: "scaler",
Params: map[string]interface{}{
@@ -188,12 +132,9 @@ var _ = Describe("Test Application Controller", func() {
Data: map[string]string{"c1": "v1", "c2": "v2"},
}
BeforeEach(func() {})
AfterEach(func() {})
It("app-without-trait will only create workload", func() {
Expect(k8sClient.Create(context.Background(), cm.DeepCopy())).Should(SatisfyAny(BeNil(), &util.AlreadyExistMatcher{}))
ac, components, err := Build("default", TestApp, k8sClient)
ac, components, err := NewApplicationParser(k8sClient, nil).GenerateApplicationConfiguration(TestApp, "default")
Expect(err).To(BeNil())
expectAppConfig := &v1alpha2.ApplicationConfiguration{
TypeMeta: v1.TypeMeta{
@@ -30,9 +30,6 @@ import (
"github.com/oam-dev/kubevela/apis/core.oam.dev/v1alpha2"
core "github.com/oam-dev/kubevela/pkg/controller/core.oam.dev"
"github.com/oam-dev/kubevela/pkg/controller/core.oam.dev/v1alpha2/application/builder"
fclient "github.com/oam-dev/kubevela/pkg/controller/core.oam.dev/v1alpha2/application/defclient"
"github.com/oam-dev/kubevela/pkg/controller/core.oam.dev/v1alpha2/application/parser"
"github.com/oam-dev/kubevela/pkg/oam/discoverymapper"
)
@@ -76,9 +73,9 @@ func (r *Reconciler) Reconcile(req ctrl.Request) (ctrl.Result, error) {
applog.Info("parse template")
// parse template
appParser := parser.NewParser(fclient.NewDefinitionClient(r.Client, r.dm))
appParser := NewApplicationParser(r.Client, r.dm)
appfile, err := appParser.Parse(app.Name, app)
appfile, err := appParser.GenerateAppFile(app.Name, app)
if err != nil {
handler.l.Error(err, "[Handle Parse]")
app.Status.SetConditions(errorCondition("Parsed", err))
@@ -89,9 +86,9 @@ func (r *Reconciler) Reconcile(req ctrl.Request) (ctrl.Result, error) {
applog.Info("build template")
// build template to applicationconfig & component
ac, comps, err := builder.Build(app.Namespace, appfile, r.Client)
ac, comps, err := appParser.GenerateApplicationConfiguration(appfile, app.Namespace)
if err != nil {
handler.l.Error(err, "[Handle Build]")
handler.l.Error(err, "[Handle GenerateApplicationConfiguration]")
app.Status.SetConditions(errorCondition("Built", err))
return handler.Err(err)
}
@@ -133,7 +130,6 @@ func (r *Reconciler) Reconcile(req ctrl.Request) (ctrl.Result, error) {
// SetupWithManager install to manager
func (r *Reconciler) SetupWithManager(mgr ctrl.Manager) error {
// TODO(wonderflow): Own(ApplicationConfiguration), Own(Component) removed.
// If Application Own these two child objects, AC status change will notify application controller and recursively update AC again, and trigger application event again...
return ctrl.NewControllerManagedBy(mgr).
For(&v1alpha2.Application{}).
@@ -1,7 +1,6 @@
package parser
package application
import (
"cuelang.org/go/cue"
"github.com/pkg/errors"
kerrors "k8s.io/apimachinery/pkg/api/errors"
"k8s.io/apimachinery/pkg/runtime/schema"
@@ -9,24 +8,15 @@ import (
"github.com/oam-dev/kubevela/apis/core.oam.dev/v1alpha2"
"github.com/oam-dev/kubevela/apis/types"
"github.com/oam-dev/kubevela/pkg/controller/core.oam.dev/v1alpha2/application/defclient"
"github.com/oam-dev/kubevela/pkg/controller/core.oam.dev/v1alpha2/application/template"
"github.com/oam-dev/kubevela/pkg/dsl/definition"
"github.com/oam-dev/kubevela/pkg/dsl/process"
"github.com/oam-dev/kubevela/pkg/oam/discoverymapper"
"github.com/oam-dev/kubevela/pkg/oam/util"
)
// AppfileBuiltinConfig defines the built-in config variable
const AppfileBuiltinConfig = "config"
// Render is cue render
type Render interface {
// WithContext(ctx interface{}) Render
WithParams(params interface{}) Render
WithTemplate(raw string) Render
Complete() (*cue.Instance, error)
}
// Workload is component
type Workload struct {
Name string
@@ -90,8 +80,8 @@ func (trait *Trait) EvalHealth(ctx process.Context, client client.Client, name s
// Appfile describes application
type Appfile struct {
Name string
Services []*Workload
Name string
Workloads []*Workload
}
// TemplateValidate validate Template format
@@ -99,41 +89,43 @@ func (af *Appfile) TemplateValidate() error {
return nil
}
// Parser is appfile parser
// Parser is an application parser
type Parser struct {
templ template.Handler
cli defclient.DefinitionClient
client client.Client
dm discoverymapper.DiscoveryMapper
}
// NewParser create appfile parser
func NewParser(cli defclient.DefinitionClient) *Parser {
return &Parser{templ: template.GetHandler(cli), cli: cli}
// NewApplicationParser create appfile parser
func NewApplicationParser(cli client.Client, dm discoverymapper.DiscoveryMapper) *Parser {
return &Parser{
client: cli,
dm: dm,
}
}
// Parse convert map to Appfile
func (pser *Parser) Parse(name string, app *v1alpha2.Application) (*Appfile, error) {
// GenerateAppFile converts an application to an Appfile
func (p *Parser) GenerateAppFile(name string, app *v1alpha2.Application) (*Appfile, error) {
appfile := new(Appfile)
appfile.Name = name
var wds []*Workload
for _, comp := range app.Spec.Components {
wd, err := pser.parseWorkload(comp)
wd, err := p.parseWorkload(comp)
if err != nil {
return nil, err
}
wds = append(wds, wd)
}
appfile.Services = wds
appfile.Workloads = wds
return appfile, nil
}
func (pser *Parser) parseWorkload(comp v1alpha2.ApplicationComponent) (*Workload, error) {
func (p *Parser) parseWorkload(comp v1alpha2.ApplicationComponent) (*Workload, error) {
workload := new(Workload)
workload.Traits = []*Trait{}
workload.Name = comp.Name
workload.Type = comp.WorkloadType
templ, health, err := pser.templ(workload.Type, types.TypeWorkload)
templ, health, err := util.LoadTemplate(p.client, workload.Type, types.TypeWorkload)
if err != nil && !kerrors.IsNotFound(err) {
return nil, errors.WithMessagef(err, "fetch type of %s", comp.Name)
}
@@ -149,7 +141,7 @@ func (pser *Parser) parseWorkload(comp v1alpha2.ApplicationComponent) (*Workload
if err != nil {
return nil, errors.Errorf("fail to parse properties of %s for %s", traitValue.Name, comp.Name)
}
trait, err := pser.parseTrait(traitValue.Name, properties)
trait, err := p.parseTrait(traitValue.Name, properties)
if err != nil {
return nil, errors.WithMessagef(err, "component(%s) parse trait(%s)", comp.Name, traitValue.Name)
}
@@ -157,7 +149,7 @@ func (pser *Parser) parseWorkload(comp v1alpha2.ApplicationComponent) (*Workload
workload.Traits = append(workload.Traits, trait)
}
for scopeType, instanceName := range comp.Scopes {
gvk, err := pser.cli.GetScopeGVK(scopeType)
gvk, err := util.GetScopeGVK(p.client, p.dm, scopeType)
if err != nil {
return nil, err
}
@@ -169,8 +161,8 @@ func (pser *Parser) parseWorkload(comp v1alpha2.ApplicationComponent) (*Workload
return workload, nil
}
func (pser *Parser) parseTrait(name string, properties map[string]interface{}) (*Trait, error) {
templ, health, err := pser.templ(name, types.TypeTrait)
func (p *Parser) parseTrait(name string, properties map[string]interface{}) (*Trait, error) {
templ, health, err := util.LoadTemplate(p.client, name, types.TypeTrait)
if kerrors.IsNotFound(err) {
return nil, errors.Errorf("trait definition of %s not found", name)
}
@@ -1,20 +1,26 @@
package parser
package application
import (
"context"
"fmt"
"reflect"
"testing"
. "github.com/onsi/ginkgo"
. "github.com/onsi/gomega"
"github.com/crossplane/crossplane-runtime/pkg/test"
"github.com/ghodss/yaml"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/types"
"github.com/oam-dev/kubevela/apis/core.oam.dev/v1alpha2"
"github.com/oam-dev/kubevela/pkg/controller/core.oam.dev/v1alpha2/application/defclient"
"github.com/oam-dev/kubevela/pkg/oam/util"
)
// TestExceptApp is test data
var TestExceptApp = &Appfile{
var expectedExceptApp = &Appfile{
Name: "test",
Services: []*Workload{
Workloads: []*Workload{
{
Name: "myweb",
Type: "worker",
@@ -86,9 +92,7 @@ var TestExceptApp = &Appfile{
},
}
func TestParser(t *testing.T) {
mock := &defclient.MockClient{}
mock.AddTD(`
const traitDefinition = `
apiVersion: core.oam.dev/v1alpha2
kind: TraitDefinition
metadata:
@@ -114,8 +118,9 @@ spec:
parameter: {
//+short=r
replicas: *1 | int
}`)
mock.AddWD(`
}`
const workloadDefinition = `
apiVersion: core.oam.dev/v1alpha2
kind: WorkloadDefinition
metadata:
@@ -134,39 +139,39 @@ spec:
selector: matchLabels: {
"app.oam.dev/component": context.name
}
template: {
metadata: labels: {
"app.oam.dev/component": context.name
}
spec: {
containers: [{
name: context.name
image: parameter.image
if parameter["cmd"] != _|_ {
command: parameter.cmd
}
}]
}
}
selector:
matchLabels:
"app.oam.dev/component": context.name
}
}
parameter: {
// +usage=Which image would you like to use for your service
// +short=i
image: string
cmd?: [...string]
}`)
const appfileYaml = `
cmd?: [...string]
}`
const appfileYaml = `
apiVersion: core.oam.dev/v1alpha2
kind: Application
metadata:
@@ -186,26 +191,46 @@ spec:
replicas: 10
`
o := v1alpha2.Application{}
yaml.Unmarshal([]byte(appfileYaml), &o)
var _ = Describe("Test application parser", func() {
It("Test we can parse an application to an appFile", func() {
o := v1alpha2.Application{}
err := yaml.Unmarshal([]byte(appfileYaml), &o)
Expect(err).ShouldNot(HaveOccurred())
appfile, err := NewParser(mock).Parse("test", &o)
if err != nil {
t.Error(err)
return
}
if !equal(TestExceptApp, appfile) {
t.Error("parser appfile wrong")
}
// Create mock client
tclient := test.MockClient{
MockGet: func(ctx context.Context, key types.NamespacedName, obj runtime.Object) error {
switch o := obj.(type) {
case *v1alpha2.WorkloadDefinition:
wd, err := util.UnMarshalStringToWorkloadDefinition(workloadDefinition)
if err != nil {
return err
}
*o = *wd
case *v1alpha2.TraitDefinition:
td, err := util.UnMarshalStringToTraitDefinition(traitDefinition)
if err != nil {
return err
}
*o = *td
}
return nil
},
}
}
appfile, err := NewApplicationParser(&tclient, nil).GenerateAppFile("test", &o)
Expect(err).ShouldNot(HaveOccurred())
Expect(equal(expectedExceptApp, appfile)).Should(BeTrue())
})
})
func equal(af, dest *Appfile) bool {
if af.Name != dest.Name || len(af.Services) != len(dest.Services) {
if af.Name != dest.Name || len(af.Workloads) != len(dest.Workloads) {
return false
}
for i, wd := range af.Services {
destWd := dest.Services[i]
for i, wd := range af.Workloads {
destWd := dest.Workloads[i]
if wd.Name != destWd.Name || len(wd.Traits) != len(destWd.Traits) {
return false
}
@@ -15,7 +15,6 @@ import (
"sigs.k8s.io/controller-runtime/pkg/client"
"github.com/oam-dev/kubevela/apis/core.oam.dev/v1alpha2"
"github.com/oam-dev/kubevela/pkg/controller/core.oam.dev/v1alpha2/application/parser"
"github.com/oam-dev/kubevela/pkg/dsl/process"
)
@@ -73,8 +72,8 @@ func (ret *reter) apply(ctx context.Context, ac *v1alpha2.ApplicationConfigurati
return ret.Sync(ctx, ac, comps)
}
func (ret *reter) healthCheck(appfile *parser.Appfile) error {
for _, wl := range appfile.Services {
func (ret *reter) healthCheck(appfile *Appfile) error {
for _, wl := range appfile.Workloads {
pCtx := process.NewContext(wl.Name)
if err := wl.EvalContext(pCtx); err != nil {
return err
@@ -1,70 +0,0 @@
package defclient
import (
"context"
"k8s.io/apimachinery/pkg/runtime/schema"
"sigs.k8s.io/controller-runtime/pkg/client"
"github.com/oam-dev/kubevela/apis/core.oam.dev/v1alpha2"
"github.com/oam-dev/kubevela/pkg/oam/discoverymapper"
"github.com/oam-dev/kubevela/pkg/oam/util"
)
// DefinitionClient is a interface
type DefinitionClient interface {
GetWorkloadDefinition(name string) (*v1alpha2.WorkloadDefinition, error)
GetTraitDefinition(name string) (*v1alpha2.TraitDefinition, error)
GetScopeGVK(name string) (schema.GroupVersionKind, error)
}
// Factory can get wd|td|app
type Factory struct {
client client.Client
dm discoverymapper.DiscoveryMapper
}
// NewDefinitionClient generate definition fetcher
func NewDefinitionClient(cli client.Client, dm discoverymapper.DiscoveryMapper) *Factory {
f := &Factory{
client: cli,
dm: dm,
}
return f
}
// GetWorkloadDefinition Get WorkloadDefinition
func (f *Factory) GetWorkloadDefinition(name string) (*v1alpha2.WorkloadDefinition, error) {
wd := new(v1alpha2.WorkloadDefinition)
if err := f.client.Get(context.Background(), client.ObjectKey{
Name: name,
}, wd); err != nil {
return nil, err
}
return wd, nil
}
// GetTraitDefinition Get TraitDefinition
func (f *Factory) GetTraitDefinition(name string) (*v1alpha2.TraitDefinition, error) {
td := new(v1alpha2.TraitDefinition)
if err := f.client.Get(context.Background(), client.ObjectKey{
Name: name,
}, td); err != nil {
return nil, err
}
return td, nil
}
// GetScopeGVK Get ScopeDefinition
func (f *Factory) GetScopeGVK(name string) (schema.GroupVersionKind, error) {
var gvk schema.GroupVersionKind
sd := new(v1alpha2.ScopeDefinition)
if err := f.client.Get(context.Background(), client.ObjectKey{
Name: name,
}, sd); err != nil {
return gvk, err
}
return util.GetGVKFromDefinition(f.dm, sd.Spec.Reference)
}
@@ -1,111 +0,0 @@
/*
Copyright 2020 The KubeVela Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package defclient
import (
"encoding/json"
kerrors "k8s.io/apimachinery/pkg/api/errors"
"k8s.io/apimachinery/pkg/runtime/schema"
kyaml "sigs.k8s.io/yaml"
"github.com/oam-dev/kubevela/apis/core.oam.dev/v1alpha2"
)
var _ DefinitionClient = &MockClient{}
// MockClient simulate the behavior of client
type MockClient struct {
wds []*v1alpha2.WorkloadDefinition
tds []*v1alpha2.TraitDefinition
gvks map[string]schema.GroupVersionKind
}
// GetWorkloadDefinition Get WorkloadDefinition
func (mock *MockClient) GetWorkloadDefinition(name string) (*v1alpha2.WorkloadDefinition, error) {
for _, wd := range mock.wds {
if wd.Name == name {
return wd, nil
}
}
return nil, kerrors.NewNotFound(schema.GroupResource{
Group: v1alpha2.Group,
Resource: "WorkloadDefinition",
}, name)
}
// GetTraitDefinition Get TraitDefinition
func (mock *MockClient) GetTraitDefinition(name string) (*v1alpha2.TraitDefinition, error) {
for _, td := range mock.tds {
if td.Name == name {
return td, nil
}
}
return nil, kerrors.NewNotFound(schema.GroupResource{
Group: v1alpha2.Group,
Resource: "TraitDefinition",
}, name)
}
// GetScopeGVK return gvk
func (mock *MockClient) GetScopeGVK(name string) (schema.GroupVersionKind, error) {
return mock.gvks[name], nil
}
// AddGVK add gvk to Mock Manager
func (mock *MockClient) AddGVK(name string, gvk schema.GroupVersionKind) error {
if mock.gvks == nil {
mock.gvks = make(map[string]schema.GroupVersionKind)
}
mock.gvks[name] = gvk
return nil
}
// AddWD add workload definition to Mock Manager
func (mock *MockClient) AddWD(s string) error {
wd := &v1alpha2.WorkloadDefinition{}
_body, err := kyaml.YAMLToJSON([]byte(s))
if err != nil {
return err
}
if err := json.Unmarshal(_body, wd); err != nil {
return err
}
if mock.wds == nil {
mock.wds = []*v1alpha2.WorkloadDefinition{}
}
mock.wds = append(mock.wds, wd)
return nil
}
// AddTD add trait definition to Mock Manager
func (mock *MockClient) AddTD(s string) error {
td := &v1alpha2.TraitDefinition{}
_body, err := kyaml.YAMLToJSON([]byte(s))
if err != nil {
return err
}
if err := json.Unmarshal(_body, td); err != nil {
return err
}
if mock.tds == nil {
mock.tds = []*v1alpha2.TraitDefinition{}
}
mock.tds = append(mock.tds, td)
return nil
}
@@ -20,15 +20,14 @@ import (
"path/filepath"
"testing"
"github.com/oam-dev/kubevela/pkg/oam/discoverymapper"
ctrl "sigs.k8s.io/controller-runtime"
. "github.com/onsi/ginkgo"
. "github.com/onsi/gomega"
"k8s.io/utils/pointer"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/client-go/kubernetes/scheme"
"k8s.io/client-go/rest"
ctrl "sigs.k8s.io/controller-runtime"
"sigs.k8s.io/controller-runtime/pkg/client"
"sigs.k8s.io/controller-runtime/pkg/envtest"
"sigs.k8s.io/controller-runtime/pkg/envtest/printer"
@@ -36,16 +35,17 @@ import (
"sigs.k8s.io/controller-runtime/pkg/log/zap"
"github.com/oam-dev/kubevela/apis/core.oam.dev/v1alpha2"
"github.com/oam-dev/kubevela/pkg/oam/discoverymapper"
// +kubebuilder:scaffold:imports
)
// These tests use Ginkgo (BDD-style Go testing framework). Refer to
// http://onsi.github.io/ginkgo/ to learn more about Ginkgo.
var cfg *rest.Config
var k8sClient client.Client
var testEnv *envtest.Environment
var testScheme = runtime.NewScheme()
var reconciler *Reconciler
func TestAPIs(t *testing.T) {
@@ -62,7 +62,8 @@ var _ = BeforeSuite(func(done Done) {
By("bootstrapping test environment")
testEnv = &envtest.Environment{
CRDDirectoryPaths: []string{filepath.Join("../../../../..", "charts", "vela-core", "crds")},
UseExistingCluster: pointer.BoolPtr(false),
CRDDirectoryPaths: []string{filepath.Join("../../../../..", "charts", "vela-core", "crds")},
}
var err error
@@ -1,78 +0,0 @@
package template
import (
"encoding/json"
"fmt"
"github.com/pkg/errors"
"github.com/oam-dev/kubevela/apis/types"
"github.com/oam-dev/kubevela/pkg/controller/core.oam.dev/v1alpha2/application/defclient"
)
type manager struct {
defclient.DefinitionClient
}
// GetHandler get template handler
func GetHandler(cli defclient.DefinitionClient) Handler {
m := &manager{
DefinitionClient: cli,
}
return m.LoadTemplate
}
// Handler is template handler type
type Handler func(key string, kind types.CapType) (string, string, error)
// Kind is template kind
type Kind = types.CapType
// LoadTemplate Get template according to key
func (m *manager) LoadTemplate(key string, kd types.CapType) (string, string, error) {
switch kd {
case types.TypeWorkload:
wd, err := m.GetWorkloadDefinition(key)
if err != nil {
return "", "", errors.WithMessagef(err, "LoadTemplate [%s] ", key)
}
tmpl, health, err := getTemplAndHealth(wd.Spec.Extension.Raw)
if err != nil {
return "", "", errors.WithMessagef(err, "LoadTemplate [%s] ", key)
}
if tmpl == "" {
return "", "", errors.New("no template found in definition")
}
return tmpl, health, nil
case types.TypeTrait:
td, err := m.GetTraitDefinition(key)
if err != nil {
return "", "", errors.WithMessagef(err, "LoadTemplate [%s] ", key)
}
tmpl, health, err := getTemplAndHealth(td.Spec.Extension.Raw)
if err != nil {
return "", "", errors.WithMessagef(err, "LoadTemplate [%s] ", key)
}
if tmpl == "" {
return "", "", errors.New("no template found in definition")
}
return tmpl, health, nil
case types.TypeScope:
// TODO: add scope template support
}
return "", "", fmt.Errorf("kind(%s) of %s not supported", kd, key)
}
func getTemplAndHealth(raw []byte) (string, string, error) {
_tmp := map[string]interface{}{}
if err := json.Unmarshal(raw, &_tmp); err != nil {
return "", "", err
}
var health string
if _, ok := _tmp["healthPolicy"]; ok {
health = fmt.Sprint(_tmp["healthPolicy"])
}
return fmt.Sprint(_tmp["template"]), health, nil
}
@@ -1,103 +0,0 @@
package template
import (
"testing"
"cuelang.org/go/cue"
"github.com/oam-dev/kubevela/apis/types"
"github.com/oam-dev/kubevela/pkg/controller/core.oam.dev/v1alpha2/application/defclient"
)
func TestTemplate(t *testing.T) {
var (
mock = &defclient.MockClient{}
)
cueTemplate := `
context: {
name: "test"
}
output: {
apiVersion: "apps/v1"
kind: "Deployment"
spec: {
selector: matchLabels: {
"app.oam.dev/component": context.name
}
template: {
metadata: labels: {
"app.oam.dev/component": context.name
}
spec: {
containers: [{
name: context.name
image: parameter.image
if parameter["cmd"] != _|_ {
command: parameter.cmd
}
}]
}
}
selector:
matchLabels:
"app.oam.dev/component": context.name
}
}
parameter: {
// +usage=Which image would you like to use for your service
// +short=i
image: string
cmd?: [...string]
}
`
if err := mock.AddWD(`
apiVersion: core.oam.dev/v1alpha2
kind: WorkloadDefinition
metadata:
name: worker
annotations:
definition.oam.dev/description: "Long-running scalable backend worker without network endpoint"
spec:
definitionRef:
name: deployments.apps
extension:
template: |
` + cueTemplate); err != nil {
t.Error(err)
return
}
m := manager{
mock,
}
temp, _, err := m.LoadTemplate("worker", types.TypeWorkload)
if err != nil {
t.Error(err)
return
}
var r cue.Runtime
inst, err := r.Compile("-", temp)
if err != nil {
t.Error(err)
return
}
instDest, err := r.Compile("-", cueTemplate)
if err != nil {
t.Error(err)
return
}
s1, _ := inst.Value().String()
s2, _ := instDest.Value().String()
if s1 != s2 {
t.Errorf("parsered template is not correct")
}
}
@@ -2,6 +2,7 @@ package applicationdeployment
import (
"context"
"fmt"
"github.com/crossplane/crossplane-runtime/pkg/event"
"github.com/crossplane/crossplane-runtime/pkg/logging"
@@ -14,11 +15,13 @@ import (
"github.com/oam-dev/kubevela/apis/core.oam.dev/v1alpha2"
corev1alpha2 "github.com/oam-dev/kubevela/apis/core.oam.dev/v1alpha2"
controller "github.com/oam-dev/kubevela/pkg/controller/core.oam.dev"
"github.com/oam-dev/kubevela/pkg/oam/discoverymapper"
)
// Reconciler reconciles a PodSpecWorkload object
type Reconciler struct {
client.Client
dm discoverymapper.DiscoveryMapper
record event.Recorder
Scheme *runtime.Scheme
}
@@ -29,8 +32,6 @@ type Reconciler struct {
// +kubebuilder:rbac:groups=core.oam.dev,resources=applicationconfigurations,verbs=get;list;watch;create;update;patch;delete
func (r *Reconciler) Reconcile(req ctrl.Request) (ctrl.Result, error) {
ctx := context.Background()
klog.Info("Reconcile applicationdeployment")
var appdeploy v1alpha2.ApplicationDeployment
if err := r.Get(ctx, req.NamespacedName, &appdeploy); err != nil {
if apierrors.IsNotFound(err) {
@@ -38,7 +39,7 @@ func (r *Reconciler) Reconcile(req ctrl.Request) (ctrl.Result, error) {
}
return ctrl.Result{}, client.IgnoreNotFound(err)
}
klog.InfoS("Get the applicationdeployment", "apiVersion", appdeploy.APIVersion, "kind", appdeploy.Kind)
klog.InfoS("Start to reconcile ", "application deployment", klog.KObj(&appdeploy))
// TODO add reconcile logic here
@@ -51,14 +52,19 @@ func (r *Reconciler) SetupWithManager(mgr ctrl.Manager) error {
WithAnnotations("controller", "ApplicationDeployment")
return ctrl.NewControllerManagedBy(mgr).
For(&v1alpha2.ApplicationDeployment{}).
Owns(&corev1alpha2.ApplicationConfiguration{}).
Owns(&corev1alpha2.Application{}).
Complete(r)
}
// Setup adds a controller that reconciles ApplicationDeployment.
func Setup(mgr ctrl.Manager, _ controller.Args, _ logging.Logger) error {
dm, err := discoverymapper.New(mgr.GetConfig())
if err != nil {
return fmt.Errorf("create discovery dm fail %w", err)
}
reconciler := Reconciler{
Client: mgr.GetClient(),
dm: dm,
Scheme: mgr.GetScheme(),
}
return reconciler.SetupWithManager(mgr)
@@ -36,7 +36,7 @@ func Setup(mgr ctrl.Manager, args controller.Args, l logging.Logger) error {
for _, setup := range []func(ctrl.Manager, controller.Args, logging.Logger) error{
applicationconfiguration.Setup,
containerizedworkload.Setup, manualscalertrait.Setup, healthscope.Setup,
applicationdeployment.Setup, application.Setup,
application.Setup, applicationdeployment.Setup,
} {
if err := setup(mgr, args, l); err != nil {
return err