support rudr env

This commit is contained in:
天元
2020-07-30 19:19:08 +08:00
parent b9a5389678
commit 89601bee18
14 changed files with 405 additions and 35 deletions

View File

@@ -90,6 +90,7 @@ e2e-setup:
e2e-test:
# Run e2e test
go test .pkg/test
e2e-cleanup:
# Clean up

View File

@@ -89,6 +89,10 @@ func newCommand() *cobra.Command {
cmd.NewInitCommand(f, client, ioStream),
cmd.NewDeleteCommand(f, client, ioStream, os.Args[1:]),
cmd.NewAppsCommand(f, client, ioStream),
cmd.NewEnvInitCommand(f, ioStream),
cmd.NewEnvSwitchCommand(f, ioStream),
cmd.NewEnvDeleteCommand(f, ioStream),
cmd.NewEnvCommand(f, ioStream),
NewVersionCommand(),
)

3
go.mod
View File

@@ -4,13 +4,14 @@ go 1.13
require (
github.com/crossplane/crossplane-runtime v0.8.0
github.com/crossplane/oam-kubernetes-runtime v0.0.5
github.com/crossplane/oam-kubernetes-runtime v0.0.8
github.com/go-logr/logr v0.1.0
github.com/gosuri/uitable v0.0.4
github.com/onsi/ginkgo v1.11.0
github.com/onsi/gomega v1.8.1
github.com/pkg/errors v0.9.1
github.com/spf13/cobra v1.0.0
github.com/stretchr/testify v1.6.1
gotest.tools v2.2.0+incompatible
helm.sh/helm/v3 v3.2.4
k8s.io/api v0.18.6

6
go.sum
View File

@@ -122,6 +122,10 @@ github.com/crossplane/crossplane-runtime v0.8.0/go.mod h1:gNY/21MLBaz5KNP7hmfXbB
github.com/crossplane/crossplane-tools v0.0.0-20200219001116-bb8b2ce46330/go.mod h1:C735A9X0x0lR8iGVOOxb49Mt70Ua4EM2b7PGaRPBLd4=
github.com/crossplane/oam-kubernetes-runtime v0.0.5 h1:eOhRsrJBjCQfb79+59qtUt2q5i4iA7vSBL8E9/RgF5Q=
github.com/crossplane/oam-kubernetes-runtime v0.0.5/go.mod h1:qICT3mLgVRW3PSH0wbJTswj36lZWIDK7Pdofmvy19fg=
github.com/crossplane/oam-kubernetes-runtime v0.0.7 h1:vwCkzB+0vIsfXO6BCDbIxAFK3b/w4zIEuIWrdyLteOA=
github.com/crossplane/oam-kubernetes-runtime v0.0.7/go.mod h1:f5xqmo0B2WtaOTZh8jhP+0f0XuzqhJG2xRtxfMZR3jA=
github.com/crossplane/oam-kubernetes-runtime v0.0.8 h1:LzAPVtcWY5uqK81SpnsLqzVqYGU+lFiuXDGzBbA8zaI=
github.com/crossplane/oam-kubernetes-runtime v0.0.8/go.mod h1:f5xqmo0B2WtaOTZh8jhP+0f0XuzqhJG2xRtxfMZR3jA=
github.com/cyphar/filepath-securejoin v0.2.2 h1:jCwT2GTP+PY5nBz3c/YL5PAIbusElVrPujOBSCj8xRg=
github.com/cyphar/filepath-securejoin v0.2.2/go.mod h1:FpkQEhXnPnOthhzymB7CGsFk2G9VLXONKD9G7QGMM+4=
github.com/dave/jennifer v1.3.0/go.mod h1:fIb+770HOpJ2fmN9EPPKOqm1vMGhB+TwXKMZhrIygKg=
@@ -824,6 +828,8 @@ gopkg.in/yaml.v2 v2.2.5/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.8 h1:obN1ZagJSUGI0Ek/LBmuj4SNLPfIny3KsKFopxRdj10=
gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v3 v3.0.0-20190905181640-827449938966/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gotest.tools v2.2.0+incompatible h1:VsBPFP1AI068pPrMxtb/S8Zkgf9xEmTLJjfM+P5UIEo=
gotest.tools v2.2.0+incompatible/go.mod h1:DsYFclhRJ6vuDpmuTbkuFWG+y2sxOXAzmJt81HFBacw=
helm.sh/helm v2.16.9+incompatible h1:31XFG6KPAbh2A/oDgIaQFR8L63vtyxN7tOt64URUJvY=

View File

@@ -17,7 +17,7 @@ import (
)
type commandOptions struct {
Namespace string
Env *EnvMeta
Template cmdutil.Template
Component corev1alpha2.Component
AppConfig corev1alpha2.ApplicationConfiguration
@@ -30,9 +30,16 @@ func NewCommandOptions(ioStreams cmdutil.IOStreams) *commandOptions {
}
func NewBindCommand(f cmdutil.Factory, c client.Client, ioStreams cmdutil.IOStreams) *cobra.Command {
var err error
ctx := context.Background()
o := NewCommandOptions(ioStreams)
o.Env, err = GetEnv()
if err != nil {
return err
}
o.Client = c
cmd := &cobra.Command{
Use: "bind APPLICATION-NAME TRAIT-NAME [FLAG]",
@@ -47,15 +54,16 @@ func NewBindCommand(f cmdutil.Factory, c client.Client, ioStreams cmdutil.IOStre
}
var traitDefinitions corev1alpha2.TraitDefinitionList
c.List(ctx, &traitDefinitions)
//if err != nil {
// fmt.Println("Listing trait definitions hit an issue:", err)
// os.Exit(1)
//}
err = c.List(ctx, &traitDefinitions)
if err != nil {
fmt.Println("Listing trait definitions hit an issue:", err)
os.Exit(1)
}
for _, t := range traitDefinitions.Items {
var traitTemplate cmdutil.Template
traitTemplate, err := cmdutil.ConvertTemplateJson2Object(t.Spec.Extension)
if err != nil {
fmt.Errorf("applying the trait hit an issue: %s", err)
}
@@ -91,7 +99,7 @@ func (o *commandOptions) Complete(f cmdutil.Factory, cmd *cobra.Command, args []
return errors.New("please append the name of an application. Use `rudr bind -h` for more detailed information")
} else if argsLength <= 2 {
componentName = args[0]
err := c.Get(ctx, client.ObjectKey{Namespace: namespace, Name: componentName}, &o.AppConfig)
err := c.Get(ctx, client.ObjectKey{Namespace: o.Env.Namespace, Name: componentName}, &o.AppConfig)
if err != nil {
fmt.Println(err)
os.Exit(1)

View File

@@ -54,7 +54,7 @@ func TestNewBindCommand(t *testing.T) {
//traitTemplateExample.DeepCopy(),
},
},
ExpectedOutput: "Applying trait for component app2060\nSucceeded!",
ExpectedOutput: "Applying trait for component app2060\nSucceeded!\n",
Args: []string{"app2060", "ManualScaler", "--replicaCount", "5"},
},
}

View File

@@ -4,6 +4,7 @@ import (
"context"
"errors"
"fmt"
cmdutil "github.com/cloud-native-application/rudrx/pkg/cmd/util"
corev1alpha2 "github.com/crossplane/oam-kubernetes-runtime/apis/core/v1alpha2"
"github.com/spf13/cobra"
@@ -15,6 +16,7 @@ type deleteOptions struct {
AppConfig corev1alpha2.ApplicationConfiguration
client client.Client
cmdutil.IOStreams
Env *EnvMeta
}
func newDeleteOptions(ioStreams cmdutil.IOStreams) *deleteOptions {
@@ -39,6 +41,7 @@ func NewDeleteCommand(f cmdutil.Factory, c client.Client, ioStreams cmdutil.IOSt
cmd.SetOut(ioStreams.Out)
o := newDeleteOptions(ioStreams)
o.client = c
o.Env, _ = GetEnv()
cmd.RunE = func(cmd *cobra.Command, args []string) error {
if err := o.Complete(f, cmd, args); err != nil {
return err
@@ -49,19 +52,13 @@ func NewDeleteCommand(f cmdutil.Factory, c client.Client, ioStreams cmdutil.IOSt
}
func (o *deleteOptions) Complete(f cmdutil.Factory, cmd *cobra.Command, args []string) error {
namespace, _, err := f.ToRawKubeConfigLoader().Namespace()
if err != nil {
return err
}
if len(args) < 1 {
return errors.New("must specify name for workload")
}
namespaceCover := cmd.Flag("namespace").Value.String()
if namespaceCover != "" {
namespace = namespaceCover
}
namespace := o.Env.Namespace
o.Component.Name = args[0]
o.Component.Namespace = namespace
o.AppConfig.Name = args[0]

276
pkg/cmd/env.go Normal file
View File

@@ -0,0 +1,276 @@
package cmd
import (
"context"
"encoding/json"
"fmt"
"io/ioutil"
"os"
"path/filepath"
cmdutil "github.com/cloud-native-application/rudrx/pkg/cmd/util"
"github.com/gosuri/uitable"
"github.com/spf13/cobra"
)
const DefaultEnvName = "default"
func NewEnvInitCommand(f cmdutil.Factory, ioStreams cmdutil.IOStreams) *cobra.Command {
var envArgs EnvMeta
ctx := context.Background()
cmd := &cobra.Command{
Use: "env:init",
DisableFlagsInUseLine: true,
Short: "Create environments",
Long: "Create environment and switch to it",
Example: `rudr env:init test --namespace test`,
RunE: func(cmd *cobra.Command, args []string) error {
return CreateOrUpdateEnv(ctx, &envArgs, args, ioStreams)
},
}
cmd.SetOut(ioStreams.Out)
cmd.Flags().StringVar(&envArgs.Namespace, "namespace", "default", "specify K8s namespace for env")
return cmd
}
func NewEnvDeleteCommand(f cmdutil.Factory, ioStreams cmdutil.IOStreams) *cobra.Command {
ctx := context.Background()
cmd := &cobra.Command{
Use: "env:delete",
DisableFlagsInUseLine: true,
Short: "Delete environment",
Long: "Delete environment",
Example: `rudr env:delete test`,
RunE: func(cmd *cobra.Command, args []string) error {
return DeleteEnv(ctx, args, ioStreams)
},
}
cmd.SetOut(ioStreams.Out)
return cmd
}
func NewEnvCommand(f cmdutil.Factory, ioStreams cmdutil.IOStreams) *cobra.Command {
ctx := context.Background()
cmd := &cobra.Command{
Use: "env",
DisableFlagsInUseLine: true,
Short: "List environments",
Long: "List all environments",
Example: `rudr env [env-name]`,
RunE: func(cmd *cobra.Command, args []string) error {
return ListEnvs(ctx, args, ioStreams)
},
}
cmd.SetOut(ioStreams.Out)
return cmd
}
func NewEnvSwitchCommand(f cmdutil.Factory, ioStreams cmdutil.IOStreams) *cobra.Command {
ctx := context.Background()
cmd := &cobra.Command{
Use: "env:sw",
DisableFlagsInUseLine: true,
Short: "Switch environments",
Long: "switch to another environment",
Example: `rudr env test`,
RunE: func(cmd *cobra.Command, args []string) error {
return SwitchEnv(ctx, args, ioStreams)
},
}
cmd.SetOut(ioStreams.Out)
return cmd
}
type EnvMeta struct {
Namespace string `json:"namespace"`
}
func ListEnvs(ctx context.Context, args []string, ioStreams cmdutil.IOStreams) error {
table := uitable.New()
table.MaxColWidth = 60
table.AddRow("NAME", "NAMESPACE")
if len(args) > 0 {
envName := args[0]
env, err := getEnvByName(envName)
if err != nil {
return err
}
table.AddRow(envName, env.Namespace)
ioStreams.Infof(table.String())
return nil
}
envDir, err := getEnvDir()
if err != nil {
return err
}
files, err := ioutil.ReadDir(envDir)
if err != nil {
return err
}
for _, f := range files {
if f.IsDir() {
continue
}
data, err := ioutil.ReadFile(filepath.Join(envDir, f.Name()))
if err != nil {
continue
}
var envMeta EnvMeta
if err = json.Unmarshal(data, &envMeta); err != nil {
continue
}
table.AddRow(f.Name(), envMeta.Namespace)
}
ioStreams.Infof(table.String())
return nil
}
func DeleteEnv(ctx context.Context, args []string, ioStreams cmdutil.IOStreams) error {
if len(args) < 1 {
return fmt.Errorf("you must specify env name for rudr env:delete command")
}
envname := args[0]
curEnv, err := GetCurrentEnvName()
if err != nil {
return err
}
if envname == curEnv {
return fmt.Errorf("you can't delete current using env %s", curEnv)
}
envdir, err := getEnvDir()
if err != nil {
return err
}
if err = os.Remove(filepath.Join(envdir, envname)); err != nil {
return err
}
ioStreams.Info(envname + " deleted")
return nil
}
func InitDefaultEnv() error {
envDir, err := getEnvDir()
if err != nil {
return err
}
if err = os.MkdirAll(envDir, 0755); err != nil {
return err
}
data, _ := json.Marshal(&EnvMeta{Namespace: DefaultEnvName})
if err = ioutil.WriteFile(filepath.Join(envDir, DefaultEnvName), data, 0644); err != nil {
return err
}
curEnvPath, err := getCurrentEnvPath()
if err != nil {
return err
}
if err = ioutil.WriteFile(curEnvPath, []byte(DefaultEnvName), 0644); err != nil {
return err
}
return nil
}
func CreateOrUpdateEnv(ctx context.Context, envArgs *EnvMeta, args []string, ioStreams cmdutil.IOStreams) error {
if len(args) < 1 {
return fmt.Errorf("you must specify env name for rudr env:init command")
}
envname := args[0]
data, err := json.Marshal(envArgs)
if err != nil {
return err
}
envdir, err := getEnvDir()
if err != nil {
return err
}
if err = ioutil.WriteFile(filepath.Join(envdir, envname), data, 0644); err != nil {
return err
}
curEnvPath, err := getCurrentEnvPath()
if err != nil {
return err
}
if err = ioutil.WriteFile(curEnvPath, []byte(envname), 0644); err != nil {
return err
}
ioStreams.Info("Create env succeed, current env is " + envname)
return nil
}
func getCurrentEnvPath() (string, error) {
home, err := os.UserHomeDir()
if err != nil {
return "", err
}
return filepath.Join(home, ".rudr", "curenv"), nil
}
func getEnvDir() (string, error) {
home, err := os.UserHomeDir()
if err != nil {
return "", err
}
return filepath.Join(home, ".rudr", "envs"), nil
}
func SwitchEnv(ctx context.Context, args []string, ioStreams cmdutil.IOStreams) error {
if len(args) < 1 {
return fmt.Errorf("you must specify env name for rudr env command")
}
envname := args[0]
currentEnvPath, err := getCurrentEnvPath()
if err != nil {
return err
}
_, err = getEnvByName(envname)
if err != nil {
return err
}
if err = ioutil.WriteFile(currentEnvPath, []byte(envname), 0644); err != nil {
return err
}
ioStreams.Info("Switch env succeed, current env is " + envname)
return nil
}
func GetCurrentEnvName() (string, error) {
currentEnvPath, err := getCurrentEnvPath()
if err != nil {
return "", err
}
data, err := ioutil.ReadFile(currentEnvPath)
if err != nil {
return "", err
}
return string(data), nil
}
func GetEnv() (*EnvMeta, error) {
envName, err := GetCurrentEnvName()
if err != nil {
if !os.IsNotExist(err) {
return nil, err
}
if err = InitDefaultEnv(); err != nil {
return nil, err
}
envName = DefaultEnvName
}
return getEnvByName(envName)
}
func getEnvByName(name string) (*EnvMeta, error) {
envdir, err := getEnvDir()
if err != nil {
return nil, err
}
data, err := ioutil.ReadFile(filepath.Join(envdir, name))
if err != nil {
return nil, err
}
var meta EnvMeta
if err = json.Unmarshal(data, &meta); err != nil {
return nil, err
}
return &meta, nil
}

88
pkg/cmd/env_test.go Normal file
View File

@@ -0,0 +1,88 @@
package cmd
import (
"bytes"
"context"
"os"
"testing"
cmdutil "github.com/cloud-native-application/rudrx/pkg/cmd/util"
"github.com/stretchr/testify/assert"
)
func TestENV(t *testing.T) {
ctx := context.Background()
// Create Default Env
err := InitDefaultEnv()
assert.NoError(t, err)
// check and compare create default env success
curEnvName, err := GetCurrentEnvName()
assert.NoError(t, err)
assert.Equal(t, "default", curEnvName)
gotEnv, err := GetEnv()
assert.NoError(t, err)
assert.Equal(t, &EnvMeta{
Namespace: "default",
}, gotEnv)
ioStream := cmdutil.IOStreams{In: os.Stdin, Out: os.Stdout, ErrOut: os.Stderr}
exp := &EnvMeta{
Namespace: "test1",
}
// Create env1
err = CreateOrUpdateEnv(ctx, exp, []string{"env1"}, ioStream)
assert.NoError(t, err)
// check and compare create env success
curEnvName, err = GetCurrentEnvName()
assert.NoError(t, err)
assert.Equal(t, "env1", curEnvName)
gotEnv, err = GetEnv()
assert.NoError(t, err)
assert.Equal(t, exp, gotEnv)
// List all env
var b bytes.Buffer
ioStream.Out = &b
err = ListEnvs(ctx, []string{}, ioStream)
assert.NoError(t, err)
assert.Equal(t, `NAME NAMESPACE
default default
env1 test1 `, b.String())
b.Reset()
err = ListEnvs(ctx, []string{"env1"}, ioStream)
assert.NoError(t, err)
assert.Equal(t, `NAME NAMESPACE
env1 test1 `, b.String())
ioStream.Out = os.Stdout
// can not delete current env
err = DeleteEnv(ctx, []string{"env1"}, ioStream)
assert.Error(t, err)
// switch to default env
err = SwitchEnv(ctx, []string{"default"}, ioStream)
assert.NoError(t, err)
// check switch success
gotEnv, err = GetEnv()
assert.NoError(t, err)
assert.Equal(t, &EnvMeta{
Namespace: "default",
}, gotEnv)
// delete env
err = DeleteEnv(ctx, []string{"env1"}, ioStream)
assert.NoError(t, err)
// can not switch to non-exist env
err = SwitchEnv(ctx, []string{"env1"}, ioStream)
assert.Error(t, err)
// switch success
err = SwitchEnv(ctx, []string{"default"}, ioStream)
assert.NoError(t, err)
}

View File

@@ -17,8 +17,8 @@ import (
)
type runOptions struct {
Namespace string
Template cmdutil.Template
Env *EnvMeta
Component corev1alpha2.Component
AppConfig corev1alpha2.ApplicationConfiguration
client client.Client
@@ -49,6 +49,7 @@ func runSubRunCommand(parentCmd *cobra.Command, f cmdutil.Factory, c client.Clie
workloadNames := []string{}
o := newRunOptions(ioStreams)
o.client = c
o.Env, _ = GetEnv()
// init fake command and pass args to fake command
// flags and subcommand append to fake comand and parent command
@@ -119,12 +120,6 @@ func runSubRunCommand(parentCmd *cobra.Command, f cmdutil.Factory, c client.Clie
}
func (o *runOptions) Complete(f cmdutil.Factory, cmd *cobra.Command, args []string, ctx context.Context) error {
namespace, explicitNamespace, err := f.ToRawKubeConfigLoader().Namespace()
if err != nil {
return err
} else if !explicitNamespace {
namespace = "default"
}
argsLength := len(args)
lastCommandParam := o.Template.LastCommandParam
@@ -167,10 +162,7 @@ func (o *runOptions) Complete(f cmdutil.Factory, cmd *cobra.Command, args []stri
}
pvd.SetString("metadata.name", strings.ToLower(workloadName))
namespaceCover := cmd.Flag("namespace").Value.String()
if namespaceCover != "" {
namespace = namespaceCover
}
namespace := o.Env.Namespace
o.Component.Spec.Workload.Object = &unstructured.Unstructured{Object: pvd.UnstructuredContent()}
o.Component.Name = args[0]
o.Component.Namespace = namespace

View File

@@ -72,7 +72,7 @@ func TestNewRunCommand(t *testing.T) {
appconfigExample,
componentExample,
},
ExpectedOutput: "Creating AppConfig app2060\nSUCCEED",
ExpectedOutput: "Creating AppConfig app2060\nSUCCEED\n",
Args: []string{"containerized", "app2060", "nginx:1.9.4", "-p", "80"},
},
}

View File

@@ -28,7 +28,7 @@ func TestNewTraitCommand(t *testing.T) {
TraitsNotApply,
},
},
ExpectedOutput: "NAME SHORT DEFINITION APPLIES TO STATUS",
ExpectedOutput: "NAME SHORT DEFINITION APPLIES TO STATUS\n",
Args: []string{},
},
}

View File

@@ -37,7 +37,7 @@ func NewTestIOStreams() (IOStreams, *bytes.Buffer, *bytes.Buffer, *bytes.Buffer)
}
func (i *IOStreams) Info(a ...interface{}) {
i.Out.Write([]byte(fmt.Sprint(a...)))
i.Out.Write([]byte(fmt.Sprintln(a...)))
}
func (i *IOStreams) Infof(format string, a ...interface{}) {
@@ -49,5 +49,5 @@ func (i *IOStreams) Errorf(format string, a ...interface{}) {
}
func (i *IOStreams) Error(a ...interface{}) {
i.ErrOut.Write([]byte(fmt.Sprint(a...)))
i.ErrOut.Write([]byte(fmt.Sprintln(a...)))
}

View File

@@ -54,9 +54,6 @@ type WorkloadData struct {
}
func ListWorkloads(ctx context.Context, c client.Client) ([]WorkloadData, error) {
/*
Get trait list by optional filter `workloadName`
*/
var workloadList []WorkloadData
var workloadDefinitionList corev1alpha2.WorkloadDefinitionList
err := c.List(ctx, &workloadDefinitionList)