diff --git a/Makefile b/Makefile index cefbbec85..2fb807ccd 100644 --- a/Makefile +++ b/Makefile @@ -141,7 +141,6 @@ e2e-test: CGO_ENABLED=0 go test -timeout 1h -count=1 -v -tags 'integration' ./test/integration @$(OK) tests pass - e2e-cleanup: # Clean up rm -rf ~/.vela diff --git a/pkg/appfile/appfile_test.go b/pkg/appfile/appfile_test.go index 2fcb46b3e..3b56d7181 100644 --- a/pkg/appfile/appfile_test.go +++ b/pkg/appfile/appfile_test.go @@ -26,11 +26,11 @@ func TestBuildOAMApplication2(t *testing.T) { tm := template.NewFakeTemplateManager() tm.Templates = map[string]*template.Template{ - "containerWorkload": &template.Template{ + "containerWorkload": { Captype: types.TypeWorkload, Raw: `{parameters : {image: string} }`, }, - "scaler": &template.Template{ + "scaler": { Captype: types.TypeTrait, Raw: `{parameters : {relicas: int} }`, }, diff --git a/pkg/controller/core.oam.dev/v1alpha2/application/builder/build.go b/pkg/controller/core.oam.dev/v1alpha2/application/appfile_parser.go similarity index 72% rename from pkg/controller/core.oam.dev/v1alpha2/application/builder/build.go rename to pkg/controller/core.oam.dev/v1alpha2/application/appfile_parser.go index 3cf4cf9ee..00294e6ed 100644 --- a/pkg/controller/core.oam.dev/v1alpha2/application/builder/build.go +++ b/pkg/controller/core.oam.dev/v1alpha2/application/appfile_parser.go @@ -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 { diff --git a/pkg/controller/core.oam.dev/v1alpha2/application/builder/suit_test.go b/pkg/controller/core.oam.dev/v1alpha2/application/appfile_parser_test.go similarity index 78% rename from pkg/controller/core.oam.dev/v1alpha2/application/builder/suit_test.go rename to pkg/controller/core.oam.dev/v1alpha2/application/appfile_parser_test.go index 97f6bb5ad..603df5a64 100644 --- a/pkg/controller/core.oam.dev/v1alpha2/application/builder/suit_test.go +++ b/pkg/controller/core.oam.dev/v1alpha2/application/appfile_parser_test.go @@ -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{ diff --git a/pkg/controller/core.oam.dev/v1alpha2/application/application_controller.go b/pkg/controller/core.oam.dev/v1alpha2/application/application_controller.go index 9c0a5b3f2..6d589d4a1 100644 --- a/pkg/controller/core.oam.dev/v1alpha2/application/application_controller.go +++ b/pkg/controller/core.oam.dev/v1alpha2/application/application_controller.go @@ -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{}). diff --git a/pkg/controller/core.oam.dev/v1alpha2/application/parser/service.go b/pkg/controller/core.oam.dev/v1alpha2/application/application_parser.go similarity index 75% rename from pkg/controller/core.oam.dev/v1alpha2/application/parser/service.go rename to pkg/controller/core.oam.dev/v1alpha2/application/application_parser.go index d8b07b63f..21776f02d 100644 --- a/pkg/controller/core.oam.dev/v1alpha2/application/parser/service.go +++ b/pkg/controller/core.oam.dev/v1alpha2/application/application_parser.go @@ -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) } diff --git a/pkg/controller/core.oam.dev/v1alpha2/application/parser/service_test.go b/pkg/controller/core.oam.dev/v1alpha2/application/application_parser_test.go similarity index 72% rename from pkg/controller/core.oam.dev/v1alpha2/application/parser/service_test.go rename to pkg/controller/core.oam.dev/v1alpha2/application/application_parser_test.go index 225120ba4..aabca2a99 100644 --- a/pkg/controller/core.oam.dev/v1alpha2/application/parser/service_test.go +++ b/pkg/controller/core.oam.dev/v1alpha2/application/application_parser_test.go @@ -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 } diff --git a/pkg/controller/core.oam.dev/v1alpha2/application/apply.go b/pkg/controller/core.oam.dev/v1alpha2/application/apply.go index e48d6a069..02a33db34 100644 --- a/pkg/controller/core.oam.dev/v1alpha2/application/apply.go +++ b/pkg/controller/core.oam.dev/v1alpha2/application/apply.go @@ -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 diff --git a/pkg/controller/core.oam.dev/v1alpha2/application/defclient/client.go b/pkg/controller/core.oam.dev/v1alpha2/application/defclient/client.go deleted file mode 100644 index 4f1aab6e6..000000000 --- a/pkg/controller/core.oam.dev/v1alpha2/application/defclient/client.go +++ /dev/null @@ -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) -} diff --git a/pkg/controller/core.oam.dev/v1alpha2/application/defclient/mock.go b/pkg/controller/core.oam.dev/v1alpha2/application/defclient/mock.go deleted file mode 100644 index 7ab05f246..000000000 --- a/pkg/controller/core.oam.dev/v1alpha2/application/defclient/mock.go +++ /dev/null @@ -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 -} diff --git a/pkg/controller/core.oam.dev/v1alpha2/application/suite_test.go b/pkg/controller/core.oam.dev/v1alpha2/application/suite_test.go index b4a88c364..041ab88fc 100644 --- a/pkg/controller/core.oam.dev/v1alpha2/application/suite_test.go +++ b/pkg/controller/core.oam.dev/v1alpha2/application/suite_test.go @@ -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 diff --git a/pkg/controller/core.oam.dev/v1alpha2/application/template/template.go b/pkg/controller/core.oam.dev/v1alpha2/application/template/template.go deleted file mode 100644 index 244302f0c..000000000 --- a/pkg/controller/core.oam.dev/v1alpha2/application/template/template.go +++ /dev/null @@ -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 -} diff --git a/pkg/controller/core.oam.dev/v1alpha2/applicationdeployment/applicationdeployment_controller.go b/pkg/controller/core.oam.dev/v1alpha2/applicationdeployment/applicationdeployment_controller.go index 1ed563951..7051aace6 100644 --- a/pkg/controller/core.oam.dev/v1alpha2/applicationdeployment/applicationdeployment_controller.go +++ b/pkg/controller/core.oam.dev/v1alpha2/applicationdeployment/applicationdeployment_controller.go @@ -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) diff --git a/pkg/controller/core.oam.dev/v1alpha2/setup.go b/pkg/controller/core.oam.dev/v1alpha2/setup.go index 53d1adfb8..686830aca 100644 --- a/pkg/controller/core.oam.dev/v1alpha2/setup.go +++ b/pkg/controller/core.oam.dev/v1alpha2/setup.go @@ -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 diff --git a/pkg/oam/discoverymapper/suit_test.go b/pkg/oam/discoverymapper/suit_test.go index 993b0f176..4a20029ae 100644 --- a/pkg/oam/discoverymapper/suit_test.go +++ b/pkg/oam/discoverymapper/suit_test.go @@ -14,6 +14,7 @@ import ( "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/runtime/schema" "k8s.io/client-go/rest" + "k8s.io/utils/pointer" "sigs.k8s.io/controller-runtime/pkg/client" "sigs.k8s.io/controller-runtime/pkg/envtest" "sigs.k8s.io/controller-runtime/pkg/envtest/printer" @@ -38,7 +39,9 @@ func TestMapper(t *testing.T) { var _ = BeforeSuite(func(done Done) { By("Bootstrapping test environment") - testEnv = &envtest.Environment{} + testEnv = &envtest.Environment{ + UseExistingCluster: pointer.BoolPtr(false), + } var err error cfg, err = testEnv.Start() Expect(err).ToNot(HaveOccurred()) diff --git a/pkg/oam/util/template.go b/pkg/oam/util/template.go new file mode 100644 index 000000000..490247421 --- /dev/null +++ b/pkg/oam/util/template.go @@ -0,0 +1,99 @@ +package util + +import ( + "context" + "encoding/json" + "fmt" + + "github.com/pkg/errors" + "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/apis/types" + "github.com/oam-dev/kubevela/pkg/oam/discoverymapper" +) + +// GetWorkloadDefinition Get WorkloadDefinition +func GetWorkloadDefinition(cli client.Client, workitemName string) (*v1alpha2.WorkloadDefinition, error) { + wd := new(v1alpha2.WorkloadDefinition) + if err := cli.Get(context.Background(), client.ObjectKey{ + Name: workitemName, + }, wd); err != nil { + return nil, err + } + return wd, nil +} + +// GetTraitDefinition Get TraitDefinition +func GetTraitDefinition(cli client.Client, traitName string) (*v1alpha2.TraitDefinition, error) { + td := new(v1alpha2.TraitDefinition) + if err := cli.Get(context.Background(), client.ObjectKey{ + Name: traitName, + }, td); err != nil { + return nil, err + } + return td, nil +} + +// GetScopeGVK Get ScopeDefinition +func GetScopeGVK(cli client.Client, dm discoverymapper.DiscoveryMapper, + name string) (schema.GroupVersionKind, error) { + var gvk schema.GroupVersionKind + sd := new(v1alpha2.ScopeDefinition) + if err := cli.Get(context.Background(), client.ObjectKey{ + Name: name, + }, sd); err != nil { + return gvk, err + } + return GetGVKFromDefinition(dm, sd.Spec.Reference) +} + +// LoadTemplate Get template according to key +func LoadTemplate(cli client.Client, key string, kd types.CapType) (string, string, error) { + switch kd { + case types.TypeWorkload: + wd, err := GetWorkloadDefinition(cli, 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 := GetTraitDefinition(cli, 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 +} diff --git a/pkg/controller/core.oam.dev/v1alpha2/application/template/template_test.go b/pkg/oam/util/template_test.go similarity index 71% rename from pkg/controller/core.oam.dev/v1alpha2/application/template/template_test.go rename to pkg/oam/util/template_test.go index ce979d455..f86ccecc8 100644 --- a/pkg/controller/core.oam.dev/v1alpha2/application/template/template_test.go +++ b/pkg/oam/util/template_test.go @@ -1,20 +1,19 @@ -package template +package util import ( + "context" "testing" "cuelang.org/go/cue" + "github.com/crossplane/crossplane-runtime/pkg/test" + "k8s.io/apimachinery/pkg/runtime" + ktypes "k8s.io/apimachinery/pkg/types" + "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" ) func TestTemplate(t *testing.T) { - - var ( - mock = &defclient.MockClient{} - ) - cueTemplate := ` context: { name: "test" @@ -59,7 +58,7 @@ func TestTemplate(t *testing.T) { } ` - if err := mock.AddWD(` + var workloadDefintion = ` apiVersion: core.oam.dev/v1alpha2 kind: WorkloadDefinition metadata: @@ -71,15 +70,24 @@ spec: name: deployments.apps extension: template: | -` + cueTemplate); err != nil { - t.Error(err) - return +` + cueTemplate + + // Create mock client + tclient := test.MockClient{ + MockGet: func(ctx context.Context, key ktypes.NamespacedName, obj runtime.Object) error { + switch o := obj.(type) { + case *v1alpha2.WorkloadDefinition: + wd, err := UnMarshalStringToWorkloadDefinition(workloadDefintion) + if err != nil { + return err + } + *o = *wd + } + return nil + }, } - m := manager{ - mock, - } - temp, _, err := m.LoadTemplate("worker", types.TypeWorkload) + temp, _, err := LoadTemplate(&tclient, "worker", types.TypeWorkload) if err != nil { t.Error(err) return diff --git a/pkg/oam/util/test_utils.go b/pkg/oam/util/test_utils.go index e4f304a09..de43c0fe6 100644 --- a/pkg/oam/util/test_utils.go +++ b/pkg/oam/util/test_utils.go @@ -6,6 +6,9 @@ import ( "github.com/onsi/gomega/format" "github.com/onsi/gomega/types" apierrors "k8s.io/apimachinery/pkg/api/errors" + "sigs.k8s.io/yaml" + + "github.com/oam-dev/kubevela/apis/core.oam.dev/v1alpha2" ) // JSONMarshal returns the JSON encoding @@ -124,3 +127,29 @@ func (matcher ErrorMatcher) NegatedFailureMessage(actual interface{}) (message s return format.Message(actual, "not to equal", expectedError) } + +// UnMarshalStringToWorkloadDefinition parse a string to a workloadDefinition object +func UnMarshalStringToWorkloadDefinition(s string) (*v1alpha2.WorkloadDefinition, error) { + obj := &v1alpha2.WorkloadDefinition{} + _body, err := yaml.YAMLToJSON([]byte(s)) + if err != nil { + return nil, err + } + if err := json.Unmarshal(_body, obj); err != nil { + return nil, err + } + return obj, nil +} + +// UnMarshalStringToTraitDefinition parse a string to a traitDefinition object +func UnMarshalStringToTraitDefinition(s string) (*v1alpha2.TraitDefinition, error) { + obj := &v1alpha2.TraitDefinition{} + _body, err := yaml.YAMLToJSON([]byte(s)) + if err != nil { + return nil, err + } + if err := json.Unmarshal(_body, obj); err != nil { + return nil, err + } + return obj, nil +} diff --git a/pkg/webhook/core.oam.dev/v1alpha2/application/validating_handler.go b/pkg/webhook/core.oam.dev/v1alpha2/application/validating_handler.go index 46b4dfc51..053f58a22 100644 --- a/pkg/webhook/core.oam.dev/v1alpha2/application/validating_handler.go +++ b/pkg/webhook/core.oam.dev/v1alpha2/application/validating_handler.go @@ -12,8 +12,7 @@ import ( "sigs.k8s.io/controller-runtime/pkg/webhook/admission" "github.com/oam-dev/kubevela/apis/core.oam.dev/v1alpha2" - 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/controller/core.oam.dev/v1alpha2/application" "github.com/oam-dev/kubevela/pkg/oam/discoverymapper" ) @@ -60,8 +59,8 @@ func (h *ValidatingHandler) Handle(ctx context.Context, req admission.Request) a } // try render to validate - appParser := parser.NewParser(fclient.NewDefinitionClient(h.Client, h.dm)) - if _, err := appParser.Parse(app.Name, app); err != nil { + appParser := application.NewApplicationParser(h.Client, h.dm) + if _, err := appParser.GenerateAppFile(app.Name, app); err != nil { return admission.Denied(err.Error()) } return admission.ValidationResponse(true, "")