Merge pull request #216 from wonderflow/scope

support scopes in appfile and make health scope as default
This commit is contained in:
Sun Jianbo
2020-08-28 10:32:10 +08:00
committed by GitHub
15 changed files with 214 additions and 98 deletions
+1 -1
View File
@@ -24,6 +24,6 @@ var _ = ginkgo.Describe("Application", func() {
e2e.TraitManualScalerAttachContext("vela attach trait", traitAlias, applicationName)
//e2e.ApplicationListContext("app ls", applicationName, traitAlias)
e2e.ApplicationShowContext("app show", applicationName, workloadType)
e2e.ApplicationStatusContext("app status", applicationName, workloadType)
e2e.ApplicationStatusContext("comp status", applicationName, workloadType)
e2e.WorkloadDeleteContext("delete", applicationName)
})
+1 -3
View File
@@ -177,13 +177,11 @@ var (
ApplicationStatusContext = func(context string, applicationName string, workloadType string) bool {
return ginkgo.Context(context, func() {
ginkgo.It("should get status for the application", func() {
cli := fmt.Sprintf("vela app status %s", applicationName)
cli := fmt.Sprintf("vela comp status %s", applicationName)
output, err := Exec(cli)
gomega.Expect(err).NotTo(gomega.HaveOccurred())
gomega.Expect(output).To(gomega.ContainSubstring(applicationName))
// TODO(zzxwill) need to check workloadType after app status is refined
//gomega.Expect(output).To(gomega.ContainSubstring(workloadType))
gomega.Expect(output).To(gomega.ContainSubstring("Workload"))
})
})
}
+29 -7
View File
@@ -12,6 +12,10 @@ import (
"strings"
"time"
"github.com/crossplane/oam-kubernetes-runtime/pkg/oam"
"github.com/crossplane/crossplane-runtime/apis/core/v1alpha1"
"cuelang.org/go/cue"
"k8s.io/apimachinery/pkg/runtime"
@@ -39,7 +43,7 @@ type Application struct {
// key of map is component name
Components map[string]map[string]interface{} `json:"components"`
Secrets map[string]map[string]interface{} `json:"secrets"`
Scopes map[string]map[string]interface{} `json:"appScopes"`
Scopes map[string]map[string]interface{} `json:"globalScopes"`
CreateTime time.Time `json:"createTime,omitempty"`
UpdateTime time.Time `json:"updateTime,omitempty"`
}
@@ -166,6 +170,7 @@ func (app *Application) Validate() error {
if !ok {
return fmt.Errorf("format of scopes in '%s' must be string array", name)
}
//TODO(wonderflow) check scope exist
}
if lenth != 1 {
return fmt.Errorf("you must have only one workload in component '%s'", name)
@@ -340,17 +345,25 @@ func (app *Application) GetComponentTraits(componentName string) ([]v1alpha2.Com
return traits, nil
}
func FormatDefaultHealthScopeName(appName string) string {
return appName + "-default-health"
}
//TODO(wonderflow) add scope support here
func (app *Application) OAM(env *types.EnvMeta) ([]v1alpha2.Component, v1alpha2.ApplicationConfiguration, error) {
func (app *Application) OAM(env *types.EnvMeta) ([]v1alpha2.Component, v1alpha2.ApplicationConfiguration, []oam.Object, error) {
var appConfig v1alpha2.ApplicationConfiguration
if err := app.Validate(); err != nil {
return nil, appConfig, err
return nil, appConfig, nil, err
}
appConfig.Name = app.Name
appConfig.Namespace = env.Namespace
var components []v1alpha2.Component
var health v1alpha2.HealthScope
health.Name = FormatDefaultHealthScopeName(app.Name)
health.Namespace = env.Namespace
health.Spec.WorkloadReferences = make([]v1alpha1.TypedReference, 0)
var components []v1alpha2.Component
for name := range app.Components {
// fulfill component
var component v1alpha2.Component
@@ -358,7 +371,7 @@ func (app *Application) OAM(env *types.EnvMeta) ([]v1alpha2.Component, v1alpha2.
component.Namespace = env.Namespace
obj, workloadType, err := app.GetWorkloadObject(name)
if err != nil {
return nil, v1alpha2.ApplicationConfiguration{}, err
return nil, v1alpha2.ApplicationConfiguration{}, nil, err
}
anns := component.Annotations
if anns == nil {
@@ -372,13 +385,22 @@ func (app *Application) OAM(env *types.EnvMeta) ([]v1alpha2.Component, v1alpha2.
var appConfigComp v1alpha2.ApplicationConfigurationComponent
appConfigComp.ComponentName = name
//TODO(wonderflow): Temporarily we add health scope here, should change to use scope framework
appConfigComp.Scopes = append(appConfigComp.Scopes, v1alpha2.ComponentScope{ScopeReference: v1alpha1.TypedReference{
APIVersion: v1alpha2.SchemeGroupVersion.String(),
Kind: v1alpha2.HealthScopeKind,
Name: health.Name,
}})
//TODO(wonderflow): handle component data input/output here
compTraits, err := app.GetComponentTraits(name)
if err != nil {
return nil, v1alpha2.ApplicationConfiguration{}, err
return nil, v1alpha2.ApplicationConfiguration{}, nil, err
}
appConfigComp.Traits = compTraits
appConfig.Spec.Components = append(appConfig.Spec.Components, appConfigComp)
}
return components, appConfig, nil
return components, appConfig, []oam.Object{&health}, nil
}
+23 -1
View File
@@ -3,6 +3,8 @@ package application
import (
"context"
"github.com/crossplane/oam-kubernetes-runtime/pkg/oam"
apierrors "k8s.io/apimachinery/pkg/api/errors"
"github.com/crossplane/oam-kubernetes-runtime/apis/core/v1alpha2"
@@ -13,7 +15,7 @@ import (
)
func (app *Application) Run(ctx context.Context, client client.Client, env *types.EnvMeta) error {
components, appconfig, err := app.OAM(env)
components, appconfig, scopes, err := app.OAM(env)
if err != nil {
return err
}
@@ -22,6 +24,9 @@ func (app *Application) Run(ctx context.Context, client client.Client, env *type
return err
}
}
if err = CreateScopes(ctx, client, scopes); err != nil {
return err
}
return CreateOrUpdateAppConfig(ctx, client, appconfig)
}
@@ -58,3 +63,20 @@ func CreateOrUpdateAppConfig(ctx context.Context, client client.Client, appConfi
appConfig.ResourceVersion = geta.ResourceVersion
return client.Update(ctx, &appConfig)
}
func CreateScopes(ctx context.Context, client client.Client, scopes []oam.Object) error {
for _, obj := range scopes {
key := ctypes.NamespacedName{Name: obj.GetName(), Namespace: obj.GetNamespace()}
err := client.Get(ctx, key, obj)
if err == nil {
return nil
}
if !apierrors.IsNotFound(err) {
return err
}
if err = client.Create(ctx, obj); err != nil {
return err
}
}
return nil
}
+9 -5
View File
@@ -20,11 +20,15 @@ components:
container_port: 8080
scopes:
- public-scope
- myapp-default-health
secrets:
secret-foo:
key1: 'pass-word'
appScopes:
public-scope:
networkPolicy: public
private-scope:
networkPolicy: private
globalScopes:
network:
public-scope:
networkPolicy: public
private-scope:
networkPolicy: private
health:
myapp-default-health: {}
-26
View File
@@ -1,26 +0,0 @@
package builtin
import (
"testing"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
"github.com/ghodss/yaml"
"github.com/stretchr/testify/assert"
"github.com/cloud-native-application/rudrx/pkg/builtin/traitdefinition"
"github.com/cloud-native-application/rudrx/pkg/builtin/workloaddefinition"
)
func TestValidYaml(t *testing.T) {
cases := map[string]string{
"scale": traitdefinition.ManualScaler,
"rollout": traitdefinition.SimpleRollout,
"containerized": workloaddefinition.ContainerizedWorkload,
"deployment": workloaddefinition.Deployment,
}
for name, val := range cases {
data := unstructured.Unstructured{}
assert.NoError(t, yaml.Unmarshal([]byte(val), &data), name)
}
}
+21
View File
@@ -0,0 +1,21 @@
package traitdefinition
import (
"testing"
"github.com/crossplane/oam-kubernetes-runtime/apis/core/v1alpha2"
"github.com/ghodss/yaml"
"github.com/stretchr/testify/assert"
)
func TestValidTraitDefinition(t *testing.T) {
cases := map[string]string{
"scale": ManualScaler,
"rollout": SimpleRollout,
}
for name, val := range cases {
data := v1alpha2.TraitDefinition{}
assert.NoError(t, yaml.Unmarshal([]byte(val), &data), name)
}
}
@@ -0,0 +1,21 @@
package workloaddefinition
import (
"testing"
"github.com/crossplane/oam-kubernetes-runtime/apis/core/v1alpha2"
"github.com/ghodss/yaml"
"github.com/stretchr/testify/assert"
)
func TestValidWorkloadDefinition(t *testing.T) {
cases := map[string]string{
"containerized": ContainerizedWorkload,
"deployment": Deployment,
}
for name, val := range cases {
data := v1alpha2.WorkloadDefinition{}
assert.NoError(t, yaml.Unmarshal([]byte(val), &data), name)
}
}
-1
View File
@@ -20,7 +20,6 @@ func NewAppsCommand(c types.Args, ioStreams cmdutil.IOStreams) *cobra.Command {
cmd.AddCommand(NewAppListCommand(c, ioStreams),
NewDeleteCommand(c, ioStreams),
NewAppStatusCommand(c, ioStreams),
NewAppShowCommand(ioStreams),
NewRunCommand(c, ioStreams))
return cmd
+2 -3
View File
@@ -43,6 +43,7 @@ func AddCompCommands(c types.Args, ioStreams util.IOStreams) *cobra.Command {
NewCompListCommand(c, ioStreams),
NewCompRunCommands(c, ioStreams),
NewCompShowCommand(ioStreams),
NewCompStatusCommand(c, ioStreams),
NewCompDeleteCommand(c, ioStreams),
)
return compCommands
@@ -132,9 +133,7 @@ func (o *runOptions) Complete(cmd *cobra.Command, args []string, ctx context.Con
if err = flags.Parse(args); err != nil {
return err
}
var flagSet = cmd.Flags()
app, err := oam.BaseComplete(envName, workloadName, appGroup, flagSet, workloadType)
app, err := oam.BaseComplete(envName, workloadName, appGroup, flags, workloadType)
if err != nil {
return err
}
+11 -1
View File
@@ -77,8 +77,9 @@ func (o *deleteOptions) DeleteApp() error {
}
return fmt.Errorf("delete appconfig err %s", err)
}
for _, comp := range appConfig.Status.Workloads {
for _, comp := range appConfig.Spec.Components {
var c corev1alpha2.Component
//TODO(wonderflow): what if we use componentRevision here?
c.Name = comp.ComponentName
c.Namespace = o.Env.Namespace
err = o.client.Delete(ctx, &c)
@@ -90,6 +91,15 @@ func (o *deleteOptions) DeleteApp() error {
if err != nil && !apierrors.IsNotFound(err) {
return fmt.Errorf("delete appconfig err %s", err)
}
var healthscope corev1alpha2.HealthScope
healthscope.Name = application.FormatDefaultHealthScopeName(o.appName)
healthscope.Namespace = o.Env.Namespace
err = o.client.Delete(ctx, &healthscope)
if err != nil && !apierrors.IsNotFound(err) {
return fmt.Errorf("delete health scope %s err %v", healthscope.Name, err)
}
o.Info("DELETE SUCCEED")
return nil
}
+1 -1
View File
@@ -116,7 +116,7 @@ func mergeStagingComponents(deployed []oam.ComponentMeta, env *types.EnvMeta, io
}
var all []oam.ComponentMeta
for _, app := range apps {
comps, appConfig, err := app.OAM(env)
comps, appConfig, _, err := app.OAM(env)
if err != nil {
ioStreams.Errorf("convert app %s err %v\n", app.Name, err)
continue
+32 -12
View File
@@ -3,18 +3,20 @@ package cmd
import (
"context"
"os"
"time"
"github.com/crossplane/oam-kubernetes-runtime/apis/core/v1alpha2"
"github.com/cloud-native-application/rudrx/pkg/application"
"github.com/cloud-native-application/rudrx/api/types"
cmdutil "github.com/cloud-native-application/rudrx/pkg/cmd/util"
"github.com/cloud-native-application/rudrx/pkg/oam"
"github.com/ghodss/yaml"
"github.com/spf13/cobra"
"sigs.k8s.io/controller-runtime/pkg/client"
)
func NewAppStatusCommand(c types.Args, ioStreams cmdutil.IOStreams) *cobra.Command {
func NewCompStatusCommand(c types.Args, ioStreams cmdutil.IOStreams) *cobra.Command {
ctx := context.Background()
cmd := &cobra.Command{
Use: "status <APPLICATION-NAME>",
@@ -27,18 +29,18 @@ func NewAppStatusCommand(c types.Args, ioStreams cmdutil.IOStreams) *cobra.Comma
ioStreams.Errorf("Hint: please specify an application")
os.Exit(1)
}
appName := args[0]
compName := args[0]
env, err := GetEnv(cmd)
if err != nil {
ioStreams.Errorf("Error: failed to get Env: %s", err)
return err
}
namespace := env.Namespace
newClient, err := client.New(c.Config, client.Options{Scheme: c.Schema})
if err != nil {
return err
}
return printApplicationStatus(ctx, newClient, ioStreams, appName, namespace)
appName, _ := cmd.Flags().GetString(App)
return printComponentStatus(ctx, newClient, ioStreams, compName, appName, env)
},
Annotations: map[string]string{
types.TagCommandType: types.TypeApp,
@@ -48,15 +50,33 @@ func NewAppStatusCommand(c types.Args, ioStreams cmdutil.IOStreams) *cobra.Comma
return cmd
}
func printApplicationStatus(ctx context.Context, c client.Client, ioStreams cmdutil.IOStreams, appName string, namespace string) error {
application, err := oam.RetrieveApplicationStatusByName(ctx, c, appName, namespace)
func printComponentStatus(ctx context.Context, c client.Client, ioStreams cmdutil.IOStreams, compName, appName string, env *types.EnvMeta) error {
ioStreams.Infof("Showing status of Component %s deployed in Environment %s\n", compName, env.Name)
var app *application.Application
var err error
if appName != "" {
app, err = application.Load(env.Name, appName)
} else {
app, err = application.MatchAppByComp(env.Name, compName)
}
if err != nil {
return err
}
out, err := yaml.Marshal(application)
if err != nil {
var health v1alpha2.HealthScope
if err = c.Get(ctx, client.ObjectKey{Namespace: env.Namespace, Name: application.FormatDefaultHealthScopeName(app.Name)}, &health); err != nil {
return err
}
ioStreams.Info(string(out))
ioStreams.Info("Component Status:")
//TODO(wonderflow): add more information from health scope
ioStreams.Infof("\n %s \n\n", health.Status.Health)
var appConfig v1alpha2.ApplicationConfiguration
if err = c.Get(ctx, client.ObjectKey{Namespace: env.Namespace, Name: app.Name}, &appConfig); err != nil {
return err
}
ioStreams.Infof("Last Deployment:\n\n")
ioStreams.Infof("\tCreated at:\t%v\n", appConfig.CreationTimestamp)
ioStreams.Infof("\tUpdated at:\t%v\n", app.UpdateTime.Format(time.RFC3339))
return nil
}
+31 -27
View File
@@ -189,51 +189,55 @@ func GetOAMReleaseVersion() (string, error) {
func GenNativeResourceDefinition(c client.Client) error {
var capabilities []string
ctx := context.Background()
for name, manifest := range workloadResource {
workloadDefinition, err := NewWorkloadDefinition(manifest)
if err != nil {
continue
}
err = c.Get(context.Background(), client.ObjectKey{Name: name}, &workloadDefinition)
if kubeerrors.IsNotFound(err) {
if err := c.Create(context.Background(), &workloadDefinition); err != nil {
wd := NewWorkloadDefinition(manifest)
capabilities = append(capabilities, name)
nwd := &oamv1.WorkloadDefinition{}
err := c.Get(ctx, client.ObjectKey{Name: name}, nwd)
if err != nil && kubeerrors.IsNotFound(err) {
if err := c.Create(context.Background(), &wd); err != nil {
return fmt.Errorf("create workload definition %s hit an issue: %v", name, err)
}
} else if err != nil {
return fmt.Errorf("get workload definition hit an issue: %v", err)
continue
}
wd.ResourceVersion = nwd.ResourceVersion
if err := c.Update(ctx, &wd); err != nil {
return fmt.Errorf("update workload definition %s err %v", wd.Name, err)
}
capabilities = append(capabilities, name)
}
for name, manifest := range traitResource {
traitDefinition, err := NewTraitDefinition(manifest)
if err != nil {
fmt.Printf("creating local definition %s err %v", name, err)
td := NewTraitDefinition(manifest)
capabilities = append(capabilities, name)
ntd := &oamv1.TraitDefinition{}
err := c.Get(context.Background(), client.ObjectKey{Name: name}, ntd)
if err != nil && kubeerrors.IsNotFound(err) {
if err := c.Create(context.Background(), &td); err != nil {
return fmt.Errorf("create trait definition %s hit an issue: %v", name, err)
}
continue
}
err = c.Get(context.Background(), client.ObjectKey{Name: name}, &traitDefinition)
if kubeerrors.IsNotFound(err) {
if err := c.Create(context.Background(), &traitDefinition); err != nil {
return fmt.Errorf("create workload definition %s hit an issue: %v", name, err)
}
} else if err != nil {
return fmt.Errorf("get workload definition hit an issue: %v", err)
td.ResourceVersion = ntd.ResourceVersion
if err := c.Update(ctx, &td); err != nil {
return fmt.Errorf("update trait definition %s err %v", td.Name, err)
}
capabilities = append(capabilities, name)
}
fmt.Printf("Successful applied %d kinds of Workloads and Traits: %s.", len(capabilities), strings.Join(capabilities, ","))
return nil
}
func NewWorkloadDefinition(manifest string) (oamv1.WorkloadDefinition, error) {
func NewWorkloadDefinition(manifest string) oamv1.WorkloadDefinition {
var workloadDefinition oamv1.WorkloadDefinition
err := yaml.Unmarshal([]byte(manifest), &workloadDefinition)
return workloadDefinition, err
// We have tests to make sure built-in resource can always unmarshal succeed
_ = yaml.Unmarshal([]byte(manifest), &workloadDefinition)
return workloadDefinition
}
func NewTraitDefinition(manifest string) (oamv1.TraitDefinition, error) {
func NewTraitDefinition(manifest string) oamv1.TraitDefinition {
var traitDefinition oamv1.TraitDefinition
err := yaml.Unmarshal([]byte(manifest), &traitDefinition)
return traitDefinition, err
// We have tests to make sure built-in resource can always unmarshal succeed
_ = yaml.Unmarshal([]byte(manifest), &traitDefinition)
return traitDefinition
}
+32 -10
View File
@@ -4,6 +4,7 @@ import (
"context"
"fmt"
"strconv"
"strings"
"github.com/cloud-native-application/rudrx/pkg/plugins"
@@ -65,23 +66,44 @@ func BaseComplete(envName string, workloadName string, appGroup string, flagSet
}
for _, v := range template.Parameters {
flagValue, _ := flagSet.GetString(v.Name)
// Cli can check required flag before make a request to backend, but API itself could not, so validate flags here
if v.Required && v.Name != "name" && flagValue == "" {
return app, fmt.Errorf("required flag(s) \"%s\" not set", v.Name)
flag := flagSet.Lookup(v.Name)
if v.Name == "name" {
continue
}
if flag == nil || flag.Value.String() == "" {
if v.Required {
return nil, fmt.Errorf("required flag(s) \"%s\" not set", v.Name)
}
continue
}
switch v.Type {
case cue.IntKind:
d, _ := strconv.ParseInt(flagValue, 10, 64)
workloadData[v.Name] = d
workloadData[v.Name], err = flagSet.GetInt64(v.Name)
case cue.StringKind:
workloadData[v.Name] = flagValue
workloadData[v.Name], err = flagSet.GetString(v.Name)
case cue.BoolKind:
d, _ := strconv.ParseBool(flagValue)
workloadData[v.Name] = d
workloadData[v.Name], err = flagSet.GetBool(v.Name)
case cue.NumberKind, cue.FloatKind:
d, _ := strconv.ParseFloat(flagValue, 64)
workloadData[v.Name] = d
workloadData[v.Name], err = flagSet.GetFloat64(v.Name)
}
if err != nil {
if strings.Contains(err.Error(), "of flag of type string") {
data, _ := flagSet.GetString(v.Name)
switch v.Type {
case cue.IntKind:
workloadData[v.Name], err = strconv.ParseInt(data, 10, 64)
case cue.BoolKind:
workloadData[v.Name], err = strconv.ParseBool(data)
case cue.NumberKind, cue.FloatKind:
workloadData[v.Name], err = strconv.ParseFloat(data, 64)
}
if err != nil {
return nil, fmt.Errorf("get flag(s) \"%s\" err %v", v.Name, err)
}
continue
}
return nil, fmt.Errorf("get flag(s) \"%s\" err %v", v.Name, err)
}
}
if err = app.SetWorkload(workloadName, tp, workloadData); err != nil {