Implement env APIs (#164)

* Implement API `api/envs/default/apps/

implement API `api/envs/default/apps/ and refactor code

* address comments from @wonderflow and @ryan

* fix code rebase issue

* Implement env APIs

implemented APIs for env and make api-test
and e2e-setup

* fix ci issues

* address comments
This commit is contained in:
Zheng Xi Zhou
2020-08-18 18:15:28 +08:00
committed by GitHub
parent 978dd05cce
commit 79d3d3c446
27 changed files with 482 additions and 175 deletions
+1
View File
@@ -38,6 +38,7 @@ jobs:
run: |
make e2e-setup
make e2e-test
make e2e-api-test
make e2e-cleanup
- name: Run Make
+7 -2
View File
@@ -41,11 +41,16 @@ docker-push:
docker push ${IMG}
e2e-setup:
# install oam-k8s-runtime
ginkgo -v -r e2e/setup
/tmp/vela dashboard & # Somehow API server service doesn't start during setup in Github environment
e2e-test:
# Run e2e test
ginkgo -v -r e2e
ginkgo -v -r e2e -skipPackage setup,apiserver
e2e-api-test:
# Run e2e test
ginkgo -v -r e2e/apiserver
e2e-cleanup:
# Clean up
+2 -1
View File
@@ -22,8 +22,9 @@ const (
)
type EnvMeta struct {
Namespace string `json:"namespace"`
Name string `json:"name"`
Current string `json:"current,omitempty"`
Namespace string `json:"namespace"`
}
const (
+16
View File
@@ -0,0 +1,16 @@
package e2e
import (
"net/http"
"testing"
"github.com/onsi/ginkgo"
"github.com/onsi/gomega"
)
var restServer http.Handler
func TestApplication(t *testing.T) {
gomega.RegisterFailHandler(ginkgo.Fail)
ginkgo.RunSpecs(t, "ApiServer Suite")
}
+97
View File
@@ -0,0 +1,97 @@
package e2e
import (
"encoding/json"
"fmt"
"io/ioutil"
"net/http"
"github.com/cloud-native-application/rudrx/e2e"
"github.com/cloud-native-application/rudrx/api/types"
"github.com/cloud-native-application/rudrx/pkg/server/util"
"github.com/cloud-native-application/rudrx/pkg/server/apis"
"github.com/onsi/gomega"
"github.com/onsi/ginkgo"
)
var envHelloMeta = types.EnvMeta{
Name: "env-e2e-hello",
Namespace: "env-e2e-hello",
}
var envWorldMeta = types.EnvMeta{
Name: "env-e2e-world",
Namespace: "env-e2e-world",
}
var _ = ginkgo.Describe("API Env", func() {
e2e.APIEnvInitContext("post /envs/", envHelloMeta)
ginkgo.Context("get /envs/:envName", func() {
ginkgo.It("should get an env", func() {
resp, err := http.Get(util.URL("/envs/" + envHelloMeta.Name))
gomega.Expect(err).NotTo(gomega.HaveOccurred())
defer resp.Body.Close()
result, err := ioutil.ReadAll(resp.Body)
gomega.Expect(err).NotTo(gomega.HaveOccurred())
var r apis.Response
err = json.Unmarshal(result, &r)
gomega.Expect(http.StatusOK).To(gomega.Equal(r.Code))
//TODO(zzxwill) Need to compare r.Data with envMeta
})
})
e2e.APIEnvInitContext("post /envs/", envWorldMeta)
ginkgo.Context("switch /envs/:envName", func() {
ginkgo.It("should switch an env", func() {
req, err := http.NewRequest("PATCH", util.URL("/envs/"+envHelloMeta.Name), nil)
gomega.Expect(err).NotTo(gomega.HaveOccurred())
resp, err := http.DefaultClient.Do(req)
gomega.Expect(err).NotTo(gomega.HaveOccurred())
defer resp.Body.Close()
result, err := ioutil.ReadAll(resp.Body)
gomega.Expect(err).NotTo(gomega.HaveOccurred())
var r apis.Response
err = json.Unmarshal(result, &r)
gomega.Expect(http.StatusOK).To(gomega.Equal(r.Code))
content := fmt.Sprintf("Switch env succeed, current env is " + envHelloMeta.Name)
gomega.Expect(r.Data.(string)).To(gomega.ContainSubstring(content))
})
})
ginkgo.Context("get /envs/", func() {
ginkgo.It("should get an env", func() {
resp, err := http.Get(util.URL("/envs/"))
gomega.Expect(err).NotTo(gomega.HaveOccurred())
defer resp.Body.Close()
result, err := ioutil.ReadAll(resp.Body)
gomega.Expect(err).NotTo(gomega.HaveOccurred())
var r apis.Response
err = json.Unmarshal(result, &r)
gomega.Expect(http.StatusOK).To(gomega.Equal(r.Code))
//TODO(zzxwill) Need to compare r.Data with envMeta
})
})
ginkgo.Context("delete /envs/:envName", func() {
ginkgo.It("should delete an env", func() {
req, err := http.NewRequest("DELETE", util.URL("/envs/"+envWorldMeta.Name), nil)
gomega.Expect(err).NotTo(gomega.HaveOccurred())
resp, err := http.DefaultClient.Do(req)
gomega.Expect(err).NotTo(gomega.HaveOccurred())
defer resp.Body.Close()
result, err := ioutil.ReadAll(resp.Body)
gomega.Expect(err).NotTo(gomega.HaveOccurred())
var r apis.Response
err = json.Unmarshal(result, &r)
gomega.Expect(http.StatusOK).To(gomega.Equal(r.Code))
gomega.Expect(r.Data.(string)).To(gomega.ContainSubstring(envWorldMeta.Name + " deleted"))
})
})
})
@@ -3,15 +3,10 @@ package e2e
import (
"testing"
"github.com/cloud-native-application/rudrx/e2e"
"github.com/onsi/ginkgo"
"github.com/onsi/gomega"
)
var _ = ginkgo.BeforeSuite(func() {
e2e.BeforeSuit()
})
func TestApplication(t *testing.T) {
gomega.RegisterFailHandler(ginkgo.Fail)
ginkgo.RunSpecs(t, "Application Suite")
+12 -9
View File
@@ -14,27 +14,21 @@ import (
)
var (
rudrPath = ""
rudrPath = "/tmp"
)
//GetCliBinary is to build rudr binary.
func GetCliBinary() (string, error) {
cwd, _ := os.Getwd()
rudrPath = path.Join(cwd, "..")
mainPath := path.Join(rudrPath, "../cmd/vela/main.go")
mainPath := path.Join(cwd, "../../cmd/vela/main.go")
cmd := exec.Command("go", "build", "-o", path.Join(rudrPath, "vela"), mainPath)
_, err := cmd.Output()
return rudrPath, err
}
func Exec(cli string) (string, error) {
c := strings.Fields(cli)
commandName := path.Join(rudrPath, c[0])
command := exec.Command(commandName, c[1:]...)
var output []byte
session, err := gexec.Start(command, ginkgo.GinkgoWriter, ginkgo.GinkgoWriter)
session, err := AsyncExec(cli)
if err != nil {
return string(output), err
}
@@ -42,10 +36,19 @@ func Exec(cli string) (string, error) {
return string(s.Out.Contents()) + string(s.Err.Contents()), nil
}
func AsyncExec(cli string) (*gexec.Session, error) {
c := strings.Fields(cli)
commandName := path.Join(rudrPath, c[0])
command := exec.Command(commandName, c[1:]...)
session, err := gexec.Start(command, ginkgo.GinkgoWriter, ginkgo.GinkgoWriter)
return session, err
}
func BeforeSuit() {
_, err := GetCliBinary()
gomega.Expect(err).NotTo(gomega.HaveOccurred())
Exec("vela system:init")
//Without this line, will hit issue like `<string>: Error: unknown command "scale" for "vela"`
Exec("vela system:update")
AsyncExec("vela dashboard &")
}
+54 -5
View File
@@ -1,7 +1,15 @@
package e2e
import (
"encoding/json"
"fmt"
"io/ioutil"
"net/http"
"strings"
"github.com/cloud-native-application/rudrx/api/types"
"github.com/cloud-native-application/rudrx/pkg/server/apis"
"github.com/cloud-native-application/rudrx/pkg/server/util"
"github.com/onsi/ginkgo"
"github.com/onsi/gomega"
@@ -14,13 +22,24 @@ var (
ginkgo.It("Install OAM runtime and vela builtin capabilities.", func() {
output, err := Exec("vela system:init")
gomega.Expect(err).NotTo(gomega.HaveOccurred())
gomega.Expect(output).To(gomega.ContainSubstring("Install OAM Kubernetes Runtime"))
gomega.Expect(output).To(gomega.ContainSubstring("Apply builtin capabilities"))
gomega.Expect(output).To(gomega.ContainSubstring("- Installing OAM Kubernetes Runtime"))
gomega.Expect(output).To(gomega.ContainSubstring("- Installing builtin capabilities"))
gomega.Expect(output).To(gomega.ContainSubstring("Successful applied"))
})
})
}
SystemUpdateContext = func(context string) bool {
return ginkgo.Context(context, func() {
ginkgo.It("Synchronize workload/trait definitions from cluster", func() {
output, err := Exec("vela system:update")
gomega.Expect(err).NotTo(gomega.HaveOccurred())
gomega.Expect(output).To(gomega.ContainSubstring("workload definitions successfully synced"))
gomega.Expect(output).To(gomega.ContainSubstring("trait definitions successfully synced"))
})
})
}
// Refresh
RefreshContext = func(context string) bool {
return ginkgo.Context(context, func() {
@@ -33,6 +52,17 @@ var (
})
}
//Dashboard
DashboardContext = func(context string) bool {
return ginkgo.Context(context, func() {
ginkgo.It("Run APIServer and Dashboard", func() {
Exec("vela dashboard &")
// gomega.Expect(err).NotTo(gomega.HaveOccurred())
// TODO(zzxwill) Need to check output
})
})
}
// Env
EnvInitContext = func(context string, envName string) bool {
return ginkgo.Context(context, func() {
@@ -52,10 +82,9 @@ var (
cli := fmt.Sprintf("vela env %s", envName)
output, err := Exec(cli)
gomega.Expect(err).NotTo(gomega.HaveOccurred())
expectedOutput := fmt.Sprintf("%s\t%s", envName, envName)
gomega.Expect(output).To(gomega.ContainSubstring("NAME"))
gomega.Expect(output).To(gomega.ContainSubstring("NAMESPACE"))
gomega.Expect(output).To(gomega.ContainSubstring(expectedOutput))
gomega.Expect(output).To(gomega.ContainSubstring(envName))
})
})
}
@@ -74,7 +103,7 @@ var (
EnvDeleteContext = func(context string, envName string) bool {
return ginkgo.Context(context, func() {
ginkgo.It("should delete all envs", func() {
ginkgo.It("should delete an env", func() {
cli := fmt.Sprintf("vela env:delete %s", envName)
output, err := Exec(cli)
gomega.Expect(err).NotTo(gomega.HaveOccurred())
@@ -155,4 +184,24 @@ var (
})
})
}
// APIServer
APIEnvInitContext = func(context string, envMeta types.EnvMeta) bool {
return ginkgo.Context("Post /envs/", func() {
ginkgo.It("should create an env", func() {
data, err := json.Marshal(&envMeta)
gomega.Expect(err).NotTo(gomega.HaveOccurred())
resp, err := http.Post(util.URL("/envs/"), "application/json", strings.NewReader(string(data)))
gomega.Expect(err).NotTo(gomega.HaveOccurred())
defer resp.Body.Close()
result, err := ioutil.ReadAll(resp.Body)
gomega.Expect(err).NotTo(gomega.HaveOccurred())
var r apis.Response
err = json.Unmarshal(result, &r)
gomega.Expect(http.StatusOK).Should(gomega.Equal(r.Code))
output := fmt.Sprintf("Create env succeed, current env is " + envMeta.Name + " namespace is " + envMeta.Namespace)
gomega.Expect(r.Data.(string)).To(gomega.ContainSubstring(output))
})
})
}
)
-7
View File
@@ -3,17 +3,10 @@ package e2e
import (
"testing"
"github.com/cloud-native-application/rudrx/e2e"
"github.com/onsi/ginkgo"
"github.com/onsi/gomega"
)
var rudrPath string
var _ = ginkgo.BeforeSuite(func() {
e2e.BeforeSuit()
})
func TestEnv(t *testing.T) {
gomega.RegisterFailHandler(ginkgo.Fail)
ginkgo.RunSpecs(t, "Env Suite")
+1
View File
@@ -31,4 +31,5 @@ var _ = ginkgo.Describe("Env", func() {
e2e.EnvDeleteContext("env delete", envName2)
e2e.EnvDeleteCurrentUsingContext("env delete currently using one", envName)
// TODO(zzxwill) Delete an env which does not exist
})
+19
View File
@@ -0,0 +1,19 @@
package e2e
import (
"testing"
"github.com/cloud-native-application/rudrx/e2e"
"github.com/onsi/ginkgo"
"github.com/onsi/gomega"
)
var _ = ginkgo.BeforeSuite(func() {
e2e.BeforeSuit()
})
func TestApplication(t *testing.T) {
gomega.RegisterFailHandler(ginkgo.Fail)
ginkgo.RunSpecs(t, "Setup Suite")
}
+9
View File
@@ -0,0 +1,9 @@
package e2e
import (
"github.com/onsi/ginkgo"
)
var _ = ginkgo.Describe("Setup", func() {
})
-6
View File
@@ -3,18 +3,12 @@ package e2e
import (
"testing"
"github.com/cloud-native-application/rudrx/e2e"
"github.com/onsi/ginkgo"
"github.com/onsi/gomega"
)
var rudrPath string
var _ = ginkgo.BeforeSuite(func() {
e2e.BeforeSuit()
})
func TestApplication(t *testing.T) {
gomega.RegisterFailHandler(ginkgo.Fail)
ginkgo.RunSpecs(t, "System Suite")
-8
View File
@@ -3,18 +3,10 @@ package e2e
import (
"testing"
"github.com/cloud-native-application/rudrx/e2e"
"github.com/onsi/ginkgo"
"github.com/onsi/gomega"
)
var rudrPath string
var _ = ginkgo.BeforeSuite(func() {
e2e.BeforeSuit()
})
func TestEnv(t *testing.T) {
gomega.RegisterFailHandler(ginkgo.Fail)
ginkgo.RunSpecs(t, "Trait Suite")
-7
View File
@@ -3,17 +3,10 @@ package e2e
import (
"testing"
"github.com/cloud-native-application/rudrx/e2e"
"github.com/onsi/ginkgo"
"github.com/onsi/gomega"
)
var rudrPath string
var _ = ginkgo.BeforeSuite(func() {
e2e.BeforeSuit()
})
func TestWorkload(t *testing.T) {
gomega.RegisterFailHandler(ginkgo.Fail)
ginkgo.RunSpecs(t, "Workload Suite")
+1
View File
@@ -19,6 +19,7 @@ require (
github.com/stretchr/testify v1.6.1
go.uber.org/zap v1.10.0
golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45
google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55
gopkg.in/natefinch/lumberjack.v2 v2.0.0
gotest.tools v2.2.0+incompatible
helm.sh/helm/v3 v3.2.4
+4
View File
@@ -133,7 +133,9 @@ github.com/coreos/pkg v0.0.0-20160727233714-3ac0863d7acf/go.mod h1:E3G3o1h8I7cfc
github.com/coreos/pkg v0.0.0-20180108230652-97fdf19511ea/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA=
github.com/coreos/pkg v0.0.0-20180928190104-399ea9e2e55f h1:lBNOc5arjvs8E5mO2tbpBpLoyyu8B6e44T7hJy6potg=
github.com/coreos/pkg v0.0.0-20180928190104-399ea9e2e55f/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA=
github.com/cpuguy83/go-md2man v1.0.10 h1:BSKMNlYxDvnunlTymqtgONjNnaRV1sTpcovwwjF22jk=
github.com/cpuguy83/go-md2man v1.0.10/go.mod h1:SmD6nW6nTyfqj6ABTjUi3V3JVMnlJmwcJI5acqYI6dE=
github.com/cpuguy83/go-md2man/v2 v2.0.0 h1:EoUDS0afbrsXAZ9YQ9jdu/mZ2sXgT1/2yyNng4PGlyM=
github.com/cpuguy83/go-md2man/v2 v2.0.0/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU=
github.com/creack/pty v1.1.7/go.mod h1:lj5s0c3V2DBrqTV7llrYr5NG6My20zk30Fl46Y7DoTY=
github.com/crossplane/crossplane-runtime v0.8.0 h1:IBm5LWVeqB2BjHpkykURHY0PSozXWfIXk7LfWtr27wY=
@@ -597,10 +599,12 @@ github.com/rubenv/sql-migrate v0.0.0-20200212082348-64f95ea68aa3 h1:xkBtI5JktwbW
github.com/rubenv/sql-migrate v0.0.0-20200212082348-64f95ea68aa3/go.mod h1:rtQlpHw+eR6UrqaS3kX1VYeaCxzCVdimDS7g5Ln4pPc=
github.com/russross/blackfriday v1.5.2 h1:HyvC0ARfnZBqnXwABFeSZHpKvJHJJfPz81GNueLj0oo=
github.com/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g=
github.com/russross/blackfriday/v2 v2.0.1 h1:lPqVAte+HuHNfhJ/0LC98ESWRz8afy9tM/0RK8m9o+Q=
github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
github.com/satori/go.uuid v1.2.0 h1:0uYX9dsZ2yD7q2RtLRtPSdGDWzjeM3TbMJP9utgA0ww=
github.com/satori/go.uuid v1.2.0/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdhQKdks0=
github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo=
github.com/shurcooL/sanitized_anchor_name v1.0.0 h1:PdmoCO6wvbs+7yrJyMORt4/BmY5IYyJwS/kOiWx8mHo=
github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc=
github.com/sirupsen/logrus v1.0.4-0.20170822132746-89742aefa4b2/go.mod h1:pMByvHTf9Beacp5x1UXfOR9xyW/9antXMhjMPG0dEzc=
github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=
+6 -5
View File
@@ -2,6 +2,7 @@ package cmd
import (
"context"
"flag"
"io"
"os"
"os/signal"
@@ -54,11 +55,11 @@ func SetupApiServer(kubeClient client.Client) {
var logRetainDate int
var logCompress, development bool
//flag.StringVar(&logFilePath, "log-file-path", "", "The log file path.")
//flag.IntVar(&logRetainDate, "log-retain-date", 7, "The number of days of logs history to retain.")
//flag.BoolVar(&logCompress, "log-compress", true, "Enable compression on the rotated logs.")
//flag.BoolVar(&development, "development", true, "Development mode.")
//flag.Parse()
flag.StringVar(&logFilePath, "log-file-path", "", "The log file path.")
flag.IntVar(&logRetainDate, "log-retain-date", 7, "The number of days of logs history to retain.")
flag.BoolVar(&logCompress, "log-compress", true, "Enable compression on the rotated logs.")
flag.BoolVar(&development, "development", true, "Development mode.")
flag.Parse()
// setup logging
var w io.Writer
+18 -107
View File
@@ -2,15 +2,9 @@ package cmd
import (
"context"
"encoding/json"
"fmt"
"io/ioutil"
"os"
"path/filepath"
corev1 "k8s.io/api/core/v1"
apierrors "k8s.io/apimachinery/pkg/api/errors"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"sigs.k8s.io/controller-runtime/pkg/client"
"github.com/cloud-native-application/rudrx/api/types"
@@ -123,49 +117,16 @@ func ListEnvs(ctx context.Context, args []string, ioStreams cmdutil.IOStreams) e
table := uitable.New()
table.MaxColWidth = 60
table.AddRow("NAME", "CURRENT", "NAMESPACE")
var envName = ""
if len(args) > 0 {
envName := args[0]
env, err := oam.GetEnvByName(envName)
if err != nil {
if os.IsNotExist(err) {
ioStreams.Info(fmt.Sprintf("env %s not exist", envName))
return nil
}
return err
}
table.AddRow(envName, env.Namespace)
ioStreams.Info(table.String())
return nil
envName = args[0]
}
envDir, err := system.GetEnvDir()
envList, err := oam.ListEnvs(envName)
if err != nil {
return err
}
files, err := ioutil.ReadDir(envDir)
if err != nil {
return err
}
curEnv, err := GetCurrentEnvName()
if err != nil {
curEnv = types.DefaultEnvName
}
for _, f := range files {
if !f.IsDir() {
continue
}
data, err := ioutil.ReadFile(filepath.Join(envDir, f.Name(), system.EnvConfigName))
if err != nil {
continue
}
var envMeta types.EnvMeta
if err = json.Unmarshal(data, &envMeta); err != nil {
continue
}
if curEnv == f.Name() {
table.AddRow(f.Name(), "*", envMeta.Namespace)
} else {
table.AddRow(f.Name(), "", envMeta.Namespace)
}
for _, env := range envList {
table.AddRow(env.Name, env.Current, env.Namespace)
}
ioStreams.Info(table.String())
return nil
@@ -175,56 +136,25 @@ func DeleteEnv(ctx context.Context, args []string, ioStreams cmdutil.IOStreams)
if len(args) < 1 {
return fmt.Errorf("you must specify env name for vela env:delete command")
}
envname := args[0]
curEnv, err := GetCurrentEnvName()
if err != nil {
return err
envName := args[0]
msg, err := oam.DeleteEnv(envName)
if err == nil {
ioStreams.Info(msg)
}
if envname == curEnv {
return fmt.Errorf("you can't delete current using env %s", curEnv)
}
envdir, err := system.GetEnvDir()
if err != nil {
return err
}
if err = os.RemoveAll(filepath.Join(envdir, envname)); err != nil {
return err
}
ioStreams.Info(envname + " deleted")
return nil
return err
}
func CreateOrUpdateEnv(ctx context.Context, c client.Client, envArgs *types.EnvMeta, args []string, ioStreams cmdutil.IOStreams) error {
if len(args) < 1 {
return fmt.Errorf("you must specify env name for vela env:init command")
}
envname := args[0]
envArgs.Name = envname
data, err := json.Marshal(envArgs)
envName := args[0]
namespace := envArgs.Namespace
err, msg := oam.CreateOrUpdateEnv(ctx, c, envName, namespace)
if err != nil {
return err
}
envdir, err := system.GetEnvDir()
if err != nil {
return err
}
subEnvDir := filepath.Join(envdir, envname)
system.CreateIfNotExist(subEnvDir)
if err = ioutil.WriteFile(filepath.Join(subEnvDir, system.EnvConfigName), data, 0644); err != nil {
return err
}
curEnvPath, err := system.GetCurrentEnvPath()
if err != nil {
return err
}
if err := c.Create(ctx, &corev1.Namespace{ObjectMeta: metav1.ObjectMeta{Name: envArgs.Namespace}}); err != nil && !apierrors.IsAlreadyExists(err) {
return err
}
if err = ioutil.WriteFile(curEnvPath, []byte(envname), 0644); err != nil {
return err
}
ioStreams.Info("Create env succeed, current env is " + envname + " namespace is " + envArgs.Namespace + ", use --namespace=<namespace> to specify namespace with env:init")
ioStreams.Info(msg)
return nil
}
@@ -232,34 +162,15 @@ func SwitchEnv(ctx context.Context, args []string, ioStreams cmdutil.IOStreams)
if len(args) < 1 {
return fmt.Errorf("you must specify env name for vela env command")
}
envname := args[0]
currentEnvPath, err := system.GetCurrentEnvPath()
envName := args[0]
msg, err := oam.SwitchEnv(envName)
if err != nil {
return err
}
envMeta, err := oam.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 + ", namespace is " + envMeta.Namespace)
ioStreams.Info(msg)
return nil
}
func GetCurrentEnvName() (string, error) {
currentEnvPath, err := system.GetCurrentEnvPath()
if err != nil {
return "", err
}
data, err := ioutil.ReadFile(currentEnvPath)
if err != nil {
return "", err
}
return string(data), nil
}
func GetEnv(cmd *cobra.Command) (*types.EnvMeta, error) {
var envName string
var err error
@@ -269,7 +180,7 @@ func GetEnv(cmd *cobra.Command) (*types.EnvMeta, error) {
if envName != "" {
return oam.GetEnvByName(envName)
}
envName, err = GetCurrentEnvName()
envName, err = oam.GetCurrentEnvName()
if err != nil {
if !os.IsNotExist(err) {
return nil, err
+6 -4
View File
@@ -7,6 +7,8 @@ import (
"strings"
"testing"
"github.com/cloud-native-application/rudrx/pkg/oam"
"github.com/crossplane/crossplane-runtime/pkg/test"
"github.com/cloud-native-application/rudrx/api/types"
@@ -30,7 +32,7 @@ func TestENV(t *testing.T) {
assert.NoError(t, err)
// check and compare create default env success
curEnvName, err := GetCurrentEnvName()
curEnvName, err := oam.GetCurrentEnvName()
assert.NoError(t, err)
assert.Equal(t, "default", curEnvName)
gotEnv, err := GetEnv(nil)
@@ -43,7 +45,7 @@ func TestENV(t *testing.T) {
ioStream := cmdutil.IOStreams{In: os.Stdin, Out: os.Stdout, ErrOut: os.Stderr}
exp := &types.EnvMeta{
Namespace: "test1",
Name: "default",
Name: "env1",
}
client := test.NewMockClient()
// Create env1
@@ -51,7 +53,7 @@ func TestENV(t *testing.T) {
assert.NoError(t, err)
// check and compare create env success
curEnvName, err = GetCurrentEnvName()
curEnvName, err = oam.GetCurrentEnvName()
assert.NoError(t, err)
assert.Equal(t, "env1", curEnvName)
gotEnv, err = GetEnv(nil)
@@ -67,7 +69,7 @@ func TestENV(t *testing.T) {
b.Reset()
err = ListEnvs(ctx, []string{"env1"}, ioStream)
assert.NoError(t, err)
assert.Equal(t, "NAME\tCURRENT\tNAMESPACE\nenv1\ttest1 \n", b.String())
assert.Equal(t, "NAME\tCURRENT\tNAMESPACE\nenv1\t \ttest1 \n", b.String())
ioStream.Out = os.Stdout
// can not delete current env
+138
View File
@@ -1,12 +1,21 @@
package oam
import (
"context"
"encoding/json"
"errors"
"fmt"
"io/ioutil"
"os"
"path/filepath"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
corev1 "k8s.io/api/core/v1"
apierrors "k8s.io/apimachinery/pkg/api/errors"
"sigs.k8s.io/controller-runtime/pkg/client"
"github.com/cloud-native-application/rudrx/api/types"
"github.com/cloud-native-application/rudrx/pkg/utils/system"
)
@@ -25,3 +34,132 @@ func GetEnvByName(name string) (*types.EnvMeta, error) {
}
return &meta, nil
}
func CreateOrUpdateEnv(ctx context.Context, c client.Client, envName string, namespace string) (error, string) {
var message = ""
var envArgs types.EnvMeta
envArgs.Name = envName
envArgs.Namespace = namespace
data, err := json.Marshal(envArgs)
if err != nil {
return err, message
}
envdir, err := system.GetEnvDir()
if err != nil {
return err, message
}
subEnvDir := filepath.Join(envdir, envName)
system.CreateIfNotExist(subEnvDir)
if err = ioutil.WriteFile(filepath.Join(subEnvDir, system.EnvConfigName), data, 0644); err != nil {
return err, message
}
curEnvPath, err := system.GetCurrentEnvPath()
if err != nil {
return err, message
}
if err := c.Create(ctx, &corev1.Namespace{ObjectMeta: metav1.ObjectMeta{Name: envArgs.Namespace}}); err != nil && !apierrors.IsAlreadyExists(err) {
return err, message
}
if err = ioutil.WriteFile(curEnvPath, []byte(envName), 0644); err != nil {
return err, message
}
message = fmt.Sprintf("Create env succeed, current env is " + envName + " namespace is " + envArgs.Namespace + ", use --namespace=<namespace> to specify namespace with env:init")
return nil, message
}
func ListEnvs(envName string) ([]*types.EnvMeta, error) {
var envList []*types.EnvMeta
if envName != "" {
env, err := GetEnvByName(envName)
if err != nil {
if os.IsNotExist(err) {
err = errors.New(fmt.Sprintf("env %s not exist", envName))
}
return envList, err
}
envList = append(envList, env)
return envList, err
}
envDir, err := system.GetEnvDir()
if err != nil {
return envList, err
}
files, err := ioutil.ReadDir(envDir)
if err != nil {
return envList, err
}
curEnv, err := GetCurrentEnvName()
if err != nil {
curEnv = types.DefaultEnvName
}
for _, f := range files {
if !f.IsDir() {
continue
}
data, err := ioutil.ReadFile(filepath.Join(envDir, f.Name(), system.EnvConfigName))
if err != nil {
continue
}
var envMeta types.EnvMeta
if err = json.Unmarshal(data, &envMeta); err != nil {
continue
}
if curEnv == f.Name() {
envMeta.Current = "*"
}
envList = append(envList, &envMeta)
}
return envList, nil
}
func GetCurrentEnvName() (string, error) {
currentEnvPath, err := system.GetCurrentEnvPath()
if err != nil {
return "", err
}
data, err := ioutil.ReadFile(currentEnvPath)
if err != nil {
return "", err
}
return string(data), nil
}
func DeleteEnv(envName string) (string, error) {
var message string
var err error
curEnv, err := GetCurrentEnvName()
if err != nil {
return message, err
}
if envName == curEnv {
err = fmt.Errorf("you can't delete current using env %s", curEnv)
return message, err
}
envdir, err := system.GetEnvDir()
if err != nil {
return message, err
}
if err = os.RemoveAll(filepath.Join(envdir, envName)); err != nil {
return message, err
}
message = envName + " deleted"
return message, err
}
func SwitchEnv(envName string) (string, error) {
var msg string
currentEnvPath, err := system.GetCurrentEnvPath()
if err != nil {
return msg, err
}
envMeta, err := GetEnvByName(envName)
if err != nil {
return msg, err
}
if err = ioutil.WriteFile(currentEnvPath, []byte(envName), 0644); err != nil {
return msg, err
}
msg = fmt.Sprintf("Switch env succeed, current env is " + envName + ", namespace is " + envMeta.Namespace)
return msg, nil
}
+3 -1
View File
@@ -5,6 +5,8 @@ import (
"net/http"
"time"
"github.com/cloud-native-application/rudrx/pkg/server/util"
ctrl "sigs.k8s.io/controller-runtime"
"sigs.k8s.io/controller-runtime/pkg/client"
)
@@ -15,7 +17,7 @@ type ApiServer struct {
func (s *ApiServer) Launch(kubeClient client.Client) {
s.server = &http.Server{
Addr: ":8080",
Addr: util.Port,
Handler: setupRoute(kubeClient),
ReadTimeout: 5 * time.Second,
WriteTimeout: 10 * time.Second,
+5
View File
@@ -13,3 +13,8 @@ type AppConfig struct {
DefinitionType string `json:"definitionType" binding:"required,max=32"`
DefinitionName string `json:"definitionName" binding:"required,max=64"`
}
type Response struct {
Code int `json:"code"`
Data interface{} `json:"data"`
}
+7 -1
View File
@@ -3,6 +3,8 @@ package handler
import (
"net/http"
"github.com/cloud-native-application/rudrx/pkg/server/apis"
"github.com/cloud-native-application/rudrx/pkg/server/util"
"sigs.k8s.io/controller-runtime/pkg/client"
@@ -47,7 +49,11 @@ func ListApps(c *gin.Context) {
util.HandleError(c, util.StatusInternalServerError, err.Error())
return
}
c.JSON(http.StatusOK, applicationMetaList)
resp := apis.Response{
Code: http.StatusOK,
Data: applicationMetaList,
}
c.JSON(http.StatusOK, resp)
}
func DeleteApps(c *gin.Context) {
+42 -7
View File
@@ -3,8 +3,13 @@ package handler
import (
"net/http"
"github.com/cloud-native-application/rudrx/api/types"
"github.com/cloud-native-application/rudrx/pkg/oam"
"github.com/gin-gonic/gin"
ctrl "sigs.k8s.io/controller-runtime"
"sigs.k8s.io/controller-runtime/pkg/client"
"github.com/cloud-native-application/rudrx/pkg/server/apis"
"github.com/cloud-native-application/rudrx/pkg/server/util"
@@ -12,31 +17,61 @@ import (
// ENV related handlers
func CreateEnv(c *gin.Context) {
var envConfig apis.Environment
var envConfig types.EnvMeta
if err := c.ShouldBindJSON(&envConfig); err != nil {
util.HandleError(c, util.InvalidArgument, "the create environment request body is invalid")
return
}
ctrl.Log.Info("Get a create environment request", "env", envConfig)
// TODO: implement this
name := envConfig.Name
namespace := envConfig.Namespace
if namespace == "" {
namespace = "default"
}
ctx := util.GetContext(c)
kubeClient := c.MustGet("KubeClient")
err, message := oam.CreateOrUpdateEnv(ctx, kubeClient.(client.Client), name, namespace)
c.Status(http.StatusOK)
var code = http.StatusOK
if err != nil {
code = http.StatusInternalServerError
message = err.Error()
}
c.JSON(code, apis.Response{
Code: code,
Data: message,
})
}
func GetEnv(c *gin.Context) {
envName := c.Param("envName")
ctrl.Log.Info("Get a get environment request", "envName", envName)
envList, err := oam.ListEnvs(envName)
// TODO: implement this
c.JSON(http.StatusOK, apis.Environment{
EnvironmentName: envName,
Namespace: "test",
var code = http.StatusOK
if err != nil {
code = http.StatusInternalServerError
}
c.JSON(code, apis.Response{
Code: code,
Data: envList,
})
}
func ListEnv(c *gin.Context) {
GetEnv(c)
}
func DeleteEnv(c *gin.Context) {
envName := c.Param("envName")
ctrl.Log.Info("Delete a delete environment request", "envName", envName)
msg, err := oam.DeleteEnv(envName)
util.AssembleResponse(c, msg, err)
}
func SwitchEnv(c *gin.Context) {
envName := c.Param("envName")
ctrl.Log.Info("Patch a switch environment request", "envName", envName)
msg, err := oam.SwitchEnv(envName)
util.AssembleResponse(c, msg, err)
}
+1
View File
@@ -47,6 +47,7 @@ func setupRoute(kubeClient client.Client) http.Handler {
envs.GET("/:envName", handler.GetEnv)
envs.GET("/", handler.ListEnv)
envs.DELETE("/:envName", handler.DeleteEnv)
envs.PATCH("/:envName", handler.SwitchEnv)
// app related operation
apps := envs.Group("/:envName/apps")
{
+33
View File
@@ -0,0 +1,33 @@
package util
import (
"fmt"
"net/http"
"github.com/gin-gonic/gin"
"github.com/cloud-native-application/rudrx/pkg/server/apis"
)
var Port = ":8080"
func AssembleResponse(c *gin.Context, data interface{}, err error) {
var code = http.StatusOK
if err != nil {
code = http.StatusInternalServerError
c.JSON(code, apis.Response{
Code: code,
Data: err.Error(),
})
return
}
c.JSON(code, apis.Response{
Code: code,
Data: data,
})
}
func URL(url string) string {
return fmt.Sprintf("http://127.0.0.1%s/api%s", Port, url)
}