mirror of
https://github.com/kubevela/kubevela.git
synced 2026-08-19 04:26:39 +00:00
refactor application controller
This commit is contained in:
+13
-23
@@ -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 {
|
||||
+10
-69
@@ -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{}).
|
||||
|
||||
+23
-31
@@ -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)
|
||||
}
|
||||
+59
-34
@@ -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")
|
||||
}
|
||||
}
|
||||
+10
-4
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user