From 79d3d3c4461b6cc05eaf39c467bed709104e8d29 Mon Sep 17 00:00:00 2001 From: Zheng Xi Zhou Date: Tue, 18 Aug 2020 18:15:28 +0800 Subject: [PATCH] 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 --- .github/workflows/go.yml | 1 + Makefile | 9 +- api/types/types.go | 3 +- e2e/apiserver/apiserver_suite_test.go | 16 +++ e2e/apiserver/apiserver_test.go | 97 +++++++++++++++ e2e/application/application_suite_test.go | 5 - e2e/cli.go | 21 ++-- e2e/commonContext.go | 59 ++++++++- e2e/env/env_suite_test.go | 7 -- e2e/env/env_test.go | 1 + e2e/setup/setup_suite_test.go | 19 +++ e2e/setup/setup_test.go | 9 ++ e2e/system/system_suite_test.go | 6 - e2e/trait/trait_suite_test.go | 8 -- e2e/workload/workload_suite_test.go | 7 -- go.mod | 1 + go.sum | 4 + pkg/cmd/dashboard.go | 11 +- pkg/cmd/env.go | 125 +++----------------- pkg/cmd/env_test.go | 10 +- pkg/oam/env.go | 138 ++++++++++++++++++++++ pkg/server/api-server.go | 4 +- pkg/server/apis/types.go | 5 + pkg/server/handler/appHandlers.go | 8 +- pkg/server/handler/envHandlers.go | 49 ++++++-- pkg/server/route.go | 1 + pkg/server/util/api.go | 33 ++++++ 27 files changed, 482 insertions(+), 175 deletions(-) create mode 100644 e2e/apiserver/apiserver_suite_test.go create mode 100644 e2e/apiserver/apiserver_test.go create mode 100644 e2e/setup/setup_suite_test.go create mode 100644 e2e/setup/setup_test.go create mode 100644 pkg/server/util/api.go diff --git a/.github/workflows/go.yml b/.github/workflows/go.yml index 58af90bc6..67a0e37ff 100644 --- a/.github/workflows/go.yml +++ b/.github/workflows/go.yml @@ -38,6 +38,7 @@ jobs: run: | make e2e-setup make e2e-test + make e2e-api-test make e2e-cleanup - name: Run Make diff --git a/Makefile b/Makefile index 51c94a56f..7c5b1a825 100644 --- a/Makefile +++ b/Makefile @@ -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 \ No newline at end of file diff --git a/api/types/types.go b/api/types/types.go index b9c2c034d..b51a6f9a7 100644 --- a/api/types/types.go +++ b/api/types/types.go @@ -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 ( diff --git a/e2e/apiserver/apiserver_suite_test.go b/e2e/apiserver/apiserver_suite_test.go new file mode 100644 index 000000000..f1267926e --- /dev/null +++ b/e2e/apiserver/apiserver_suite_test.go @@ -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") +} diff --git a/e2e/apiserver/apiserver_test.go b/e2e/apiserver/apiserver_test.go new file mode 100644 index 000000000..42a3b5651 --- /dev/null +++ b/e2e/apiserver/apiserver_test.go @@ -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")) + }) + }) +}) diff --git a/e2e/application/application_suite_test.go b/e2e/application/application_suite_test.go index 8f6cd8ace..05f817c76 100644 --- a/e2e/application/application_suite_test.go +++ b/e2e/application/application_suite_test.go @@ -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") diff --git a/e2e/cli.go b/e2e/cli.go index 85df778d0..b2e380f01 100644 --- a/e2e/cli.go +++ b/e2e/cli.go @@ -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 `: Error: unknown command "scale" for "vela"` Exec("vela system:update") + AsyncExec("vela dashboard &") } diff --git a/e2e/commonContext.go b/e2e/commonContext.go index a7606a681..97d9ab31c 100644 --- a/e2e/commonContext.go +++ b/e2e/commonContext.go @@ -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)) + }) + }) + } ) diff --git a/e2e/env/env_suite_test.go b/e2e/env/env_suite_test.go index 0ddff42aa..758ea6948 100644 --- a/e2e/env/env_suite_test.go +++ b/e2e/env/env_suite_test.go @@ -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") diff --git a/e2e/env/env_test.go b/e2e/env/env_test.go index 3c933256b..13e59b4b7 100644 --- a/e2e/env/env_test.go +++ b/e2e/env/env_test.go @@ -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 }) diff --git a/e2e/setup/setup_suite_test.go b/e2e/setup/setup_suite_test.go new file mode 100644 index 000000000..775611992 --- /dev/null +++ b/e2e/setup/setup_suite_test.go @@ -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") +} diff --git a/e2e/setup/setup_test.go b/e2e/setup/setup_test.go new file mode 100644 index 000000000..ff896e395 --- /dev/null +++ b/e2e/setup/setup_test.go @@ -0,0 +1,9 @@ +package e2e + +import ( + "github.com/onsi/ginkgo" +) + +var _ = ginkgo.Describe("Setup", func() { + +}) diff --git a/e2e/system/system_suite_test.go b/e2e/system/system_suite_test.go index c7cd8ca50..0f31e7f0c 100644 --- a/e2e/system/system_suite_test.go +++ b/e2e/system/system_suite_test.go @@ -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") diff --git a/e2e/trait/trait_suite_test.go b/e2e/trait/trait_suite_test.go index 6aa67abd2..a45db5b7a 100644 --- a/e2e/trait/trait_suite_test.go +++ b/e2e/trait/trait_suite_test.go @@ -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") diff --git a/e2e/workload/workload_suite_test.go b/e2e/workload/workload_suite_test.go index 76514ad77..050136046 100644 --- a/e2e/workload/workload_suite_test.go +++ b/e2e/workload/workload_suite_test.go @@ -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") diff --git a/go.mod b/go.mod index 048c7b2ca..aaba9ec09 100644 --- a/go.mod +++ b/go.mod @@ -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 diff --git a/go.sum b/go.sum index 903b8af2b..e328eff64 100644 --- a/go.sum +++ b/go.sum @@ -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= diff --git a/pkg/cmd/dashboard.go b/pkg/cmd/dashboard.go index 0a22f315e..a853c604c 100644 --- a/pkg/cmd/dashboard.go +++ b/pkg/cmd/dashboard.go @@ -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 diff --git a/pkg/cmd/env.go b/pkg/cmd/env.go index 6391aa232..41be16ee8 100644 --- a/pkg/cmd/env.go +++ b/pkg/cmd/env.go @@ -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= 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 diff --git a/pkg/cmd/env_test.go b/pkg/cmd/env_test.go index bdde1b7ef..9dcc9b61c 100644 --- a/pkg/cmd/env_test.go +++ b/pkg/cmd/env_test.go @@ -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 diff --git a/pkg/oam/env.go b/pkg/oam/env.go index 2c02ac1ab..771976dba 100644 --- a/pkg/oam/env.go +++ b/pkg/oam/env.go @@ -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= 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 +} diff --git a/pkg/server/api-server.go b/pkg/server/api-server.go index 242161559..d1b7620b5 100644 --- a/pkg/server/api-server.go +++ b/pkg/server/api-server.go @@ -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, diff --git a/pkg/server/apis/types.go b/pkg/server/apis/types.go index ed0768a6d..1ecedbcdc 100644 --- a/pkg/server/apis/types.go +++ b/pkg/server/apis/types.go @@ -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"` +} diff --git a/pkg/server/handler/appHandlers.go b/pkg/server/handler/appHandlers.go index fb17724d0..c2c337290 100644 --- a/pkg/server/handler/appHandlers.go +++ b/pkg/server/handler/appHandlers.go @@ -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) { diff --git a/pkg/server/handler/envHandlers.go b/pkg/server/handler/envHandlers.go index 1c4732092..67b771b87 100644 --- a/pkg/server/handler/envHandlers.go +++ b/pkg/server/handler/envHandlers.go @@ -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) } diff --git a/pkg/server/route.go b/pkg/server/route.go index 9e32f2afc..707d8110f 100644 --- a/pkg/server/route.go +++ b/pkg/server/route.go @@ -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") { diff --git a/pkg/server/util/api.go b/pkg/server/util/api.go new file mode 100644 index 000000000..d2c3d16dd --- /dev/null +++ b/pkg/server/util/api.go @@ -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) +}