diff --git a/Makefile b/Makefile index e3c2f7482..8ad61369a 100644 --- a/Makefile +++ b/Makefile @@ -58,9 +58,6 @@ vela-cli: kubectl-vela: $(GOBUILD_ENV) go build -o bin/kubectl-vela -a -ldflags $(LDFLAGS) ./cmd/plugin/main.go -dashboard-build: - cd references/dashboard && npm install && cd .. - doc-gen: rm -r docs/en/cli/* go run hack/docgen/gen.go @@ -98,6 +95,9 @@ compress: run: go run ./cmd/core/main.go --application-revision-limit 5 +run-apiserver: + go run ./cmd/apiserver/main.go + # Run go fmt against code fmt: goimports installcue go fmt ./... @@ -239,7 +239,7 @@ manifests: installcue kustomize ./vela-templates/gen_definitions.sh go run ./vela-templates/gen_addons.go -GOLANGCILINT_VERSION ?= v1.31.0 +GOLANGCILINT_VERSION ?= v1.38.0 HOSTOS := $(shell uname -s | tr '[:upper:]' '[:lower:]') HOSTARCH := $(shell uname -m) ifeq ($(HOSTARCH),x86_64) diff --git a/cmd/apiserver/main.go b/cmd/apiserver/main.go index 14886586c..82b0374c6 100644 --- a/cmd/apiserver/main.go +++ b/cmd/apiserver/main.go @@ -21,6 +21,8 @@ import ( "flag" "fmt" "os" + "os/signal" + "syscall" "github.com/oam-dev/kubevela/pkg/apiserver/log" "github.com/oam-dev/kubevela/pkg/apiserver/rest" @@ -29,14 +31,30 @@ import ( func main() { s := &server{} - - flag.IntVar(&s.restCfg.Port, "port", 8000, "The port number used to serve the http APIs.") + flag.StringVar(&s.restCfg.BindAddr, "bind-addr", "0.0.0.0:8000", "The bind address used to serve the http APIs.") + flag.StringVar(&s.restCfg.MetricPath, "metrics-path", "/metrics", "The path to expose the metrics.") + flag.StringVar(&s.restCfg.Datastore.Type, "datastore-type", "kubeapi", "Metadata storage driver type, support kubeapi and mongodb") + flag.StringVar(&s.restCfg.Datastore.Database, "datastore-database", "kubevela", "Metadata storage database name, takes effect when the storage driver is mongodb.") + flag.StringVar(&s.restCfg.Datastore.URL, "datastore-url", "", "Metadata storage database url,takes effect when the storage driver is mongodb.") flag.Parse() - if err := s.run(); err != nil { - log.Logger.Errorf("failed to run apiserver: %v", err) + srvc := make(chan struct{}) + + go func() { + if err := s.run(); err != nil { + log.Logger.Errorf("failed to run apiserver: %v", err) + } + close(srvc) + }() + var term = make(chan os.Signal, 1) + signal.Notify(term, os.Interrupt, syscall.SIGTERM) + select { + case <-term: + log.Logger.Infof("Received SIGTERM, exiting gracefully...") + case <-srvc: os.Exit(1) } + log.Logger.Infof("See you next time!") } type server struct { diff --git a/go.mod b/go.mod index bf55a41dd..2590bd55a 100644 --- a/go.mod +++ b/go.mod @@ -15,11 +15,15 @@ require ( github.com/crossplane/crossplane-runtime v0.14.1-0.20210722005935-0b469fcc77cd github.com/davecgh/go-spew v1.1.1 github.com/deckarep/golang-set v1.7.1 + github.com/emicklei/go-restful-openapi/v2 v2.3.0 + github.com/emicklei/go-restful/v3 v3.0.0-rc2 github.com/evanphx/json-patch v4.11.0+incompatible github.com/fatih/color v1.12.0 github.com/gertd/go-pluralize v0.1.7 github.com/getkin/kin-openapi v0.34.0 github.com/go-logr/logr v0.4.0 + github.com/go-openapi/spec v0.19.8 + github.com/go-playground/validator/v10 v10.9.0 github.com/google/go-cmp v0.5.6 github.com/google/go-github/v32 v32.1.0 github.com/gosuri/uitable v0.0.4 @@ -27,7 +31,6 @@ require ( github.com/hinshun/vt10x v0.0.0-20180616224451-1954e6464174 github.com/imdario/mergo v0.3.12 github.com/kyokomi/emoji v2.2.4+incompatible - github.com/labstack/echo/v4 v4.5.0 github.com/mitchellh/hashstructure/v2 v2.0.1 github.com/oam-dev/cluster-gateway v0.0.0-20210907072424-2f8720b116f8 github.com/oam-dev/terraform-config-inspect v0.0.0-20210418082552-fc72d929aa28 @@ -45,6 +48,7 @@ require ( github.com/tidwall/gjson v1.6.8 github.com/wercker/stern v0.0.0-20190705090245-4fa46dd6987f github.com/wonderflow/cert-manager-api v1.0.3 + go.mongodb.org/mongo-driver v1.3.2 go.uber.org/zap v1.18.1 golang.org/x/oauth2 v0.0.0-20210402161424-2e8d93401602 gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b diff --git a/go.sum b/go.sum index 35a423660..4e16c94b0 100644 --- a/go.sum +++ b/go.sum @@ -402,6 +402,10 @@ github.com/elazarl/goproxy v0.0.0-20180725130230-947c36da3153/go.mod h1:/Zj4wYkg github.com/emicklei/go-restful v0.0.0-20170410110728-ff4f55a20633/go.mod h1:otzb+WCGbkyDHkqmQmT5YD2WR4BBwUdeQoFo8l/7tVs= github.com/emicklei/go-restful v2.9.5+incompatible h1:spTtZBk5DYEvbxMVutUuTyh1Ao2r4iyvLdACqsl/Ljk= github.com/emicklei/go-restful v2.9.5+incompatible/go.mod h1:otzb+WCGbkyDHkqmQmT5YD2WR4BBwUdeQoFo8l/7tVs= +github.com/emicklei/go-restful-openapi/v2 v2.3.0 h1:tDgSCzQrkk4N+Isos0zGBYX/GTINjmQuP9BvITbEe38= +github.com/emicklei/go-restful-openapi/v2 v2.3.0/go.mod h1:bs67E3SEVgSmB3qDuRLqpS0NcpheqtsCCMhW2/jml1E= +github.com/emicklei/go-restful/v3 v3.0.0-rc2 h1:UkWzdUozgtjQzYuqSNQy+PuYxD4/DCzYucakgzWKolU= +github.com/emicklei/go-restful/v3 v3.0.0-rc2/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc= github.com/emicklei/proto v1.6.15 h1:XbpwxmuOPrdES97FrSfpyy67SSCV/wBIKXqgJzh6hNw= github.com/emicklei/proto v1.6.15/go.mod h1:rn1FgRS/FANiZdD2djyH7TMA9jdRDcYQ9IEN9yvjX0A= github.com/envoyproxy/go-control-plane v0.6.9/go.mod h1:SBwIajubJHhxtWwsL9s8ss4safvEdbitLhGGK48rN6g= @@ -558,6 +562,7 @@ github.com/go-openapi/swag v0.17.2/go.mod h1:AByQ+nYG6gQg71GINrmuDXCPWdL640yX49/ github.com/go-openapi/swag v0.18.0/go.mod h1:AByQ+nYG6gQg71GINrmuDXCPWdL640yX49/kXLo40Tg= github.com/go-openapi/swag v0.19.2/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh66Z9tfKk= github.com/go-openapi/swag v0.19.5/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh66Z9tfKk= +github.com/go-openapi/swag v0.19.6/go.mod h1:ao+8BpOPyKdpQz3AOJfbeEVpLmWAvlT1IfTe5McPyhY= github.com/go-openapi/swag v0.19.7/go.mod h1:ao+8BpOPyKdpQz3AOJfbeEVpLmWAvlT1IfTe5McPyhY= github.com/go-openapi/swag v0.19.9/go.mod h1:ao+8BpOPyKdpQz3AOJfbeEVpLmWAvlT1IfTe5McPyhY= github.com/go-openapi/swag v0.19.11/go.mod h1:Uc0gKkdR+ojzsEpjh39QChyu92vPgIr72POcgHMAgSY= @@ -569,11 +574,20 @@ github.com/go-openapi/validate v0.19.2/go.mod h1:1tRCw7m3jtI8eNWEEliiAqUIcBztB2K github.com/go-openapi/validate v0.19.3/go.mod h1:90Vh6jjkTn+OT1Eefm0ZixWNFjhtOH7vS9k0lo6zwJo= github.com/go-openapi/validate v0.19.5/go.mod h1:8DJv2CVJQ6kGNpFW6eV9N3JviE1C85nY1c2z52x1Gk4= github.com/go-openapi/validate v0.19.8/go.mod h1:8DJv2CVJQ6kGNpFW6eV9N3JviE1C85nY1c2z52x1Gk4= +github.com/go-playground/assert/v2 v2.0.1 h1:MsBgLAaY856+nPRTKrp3/OZK38U/wa0CcBYNjji3q3A= +github.com/go-playground/assert/v2 v2.0.1/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4= +github.com/go-playground/locales v0.14.0 h1:u50s323jtVGugKlcYeyzC0etD1HifMjqmJqb8WugfUU= +github.com/go-playground/locales v0.14.0/go.mod h1:sawfccIbzZTqEDETgFXqTho0QybSa7l++s0DH+LDiLs= +github.com/go-playground/universal-translator v0.18.0 h1:82dyy6p4OuJq4/CByFNOn/jYrnRPArHwAcmLoJZxyho= +github.com/go-playground/universal-translator v0.18.0/go.mod h1:UvRDBj+xPUEGrFYl+lu/H90nyDXpg0fqeB/AQUGNTVA= +github.com/go-playground/validator/v10 v10.9.0 h1:NgTtmN58D0m8+UuxtYmGztBJB7VnPgjj221I1QHci2A= +github.com/go-playground/validator/v10 v10.9.0/go.mod h1:74x4gJWsvQexRdW8Pn3dXSGrTK4nAUsbPlLADvpJkos= github.com/go-redis/redis v6.15.8+incompatible/go.mod h1:NAIEuMOZ/fxfXJIrKDQDz8wamY7mA7PouImQ2Jvg6kA= github.com/go-sql-driver/mysql v1.4.0/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w= github.com/go-sql-driver/mysql v1.4.1/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w= github.com/go-sql-driver/mysql v1.5.0 h1:ozyZYNQW3x3HtqT1jira07DN2PArx2v7/mN66gGcHOs= github.com/go-sql-driver/mysql v1.5.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg= +github.com/go-stack/stack v1.8.0 h1:5SgMzNM5HxrEjV0ww2lTmX6E2Izsfxas4+YHWRs3Lsk= github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0/go.mod h1:fyg7847qk6SyHyPtNmDHnmrv/HOrqktSC+C9fM+CJOE= github.com/go-test/deep v1.0.3 h1:ZrJSEWsXzPOxaZnFteGEfooLba+ju3FYIbOrS+rQd68= @@ -642,8 +656,6 @@ github.com/gogo/protobuf v1.3.0/go.mod h1:SlYgWuQ5SjCEi6WLHjHCa1yvBfUnHcTbrrZtXP github.com/gogo/protobuf v1.3.1/go.mod h1:SlYgWuQ5SjCEi6WLHjHCa1yvBfUnHcTbrrZtXPKa29o= github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= -github.com/golang-jwt/jwt v3.2.2+incompatible h1:IfV12K8xAKAnZqdXVzCZ+TOjboZ2keLg81eXfW3O+oY= -github.com/golang-jwt/jwt v3.2.2+incompatible/go.mod h1:8pz2t5EyA70fFQQSrl6XZXzqecmYZeUEB8OUGHkxJ+I= github.com/golang-sql/civil v0.0.0-20190719163853-cb61b32ac6fe/go.mod h1:8vg3r2VgvsThLBIFL93Qb5yWzgyZWhEmBwUJWevAkK0= github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0/go.mod h1:E/TSTwGwJL78qG/PmXZO1EjYhfJinVAhrmmHX6Z8B9k= github.com/golang/geo v0.0.0-20190916061304-5b978397cfec/go.mod h1:QZ0nwyI2jOfgRAoBvP+ab5aRr7c9x7lhGEJrKvBwjWI= @@ -685,6 +697,7 @@ github.com/golang/protobuf v1.5.1/go.mod h1:DopwsBzvsk0Fs44TXzsVbJyPhcCPeIwnvohx github.com/golang/protobuf v1.5.2 h1:ROPKBNFfQgOUMifHyP+KYbvpjbdoFNs+aK7DXlji0Tw= github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= github.com/golang/snappy v0.0.0-20180518054509-2e65f85255db/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= +github.com/golang/snappy v0.0.1 h1:Qgr9rKW7uDUkrbSmQeiDsGa8SjGyCOGtuasMWwvp2P4= github.com/golang/snappy v0.0.1/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= github.com/golangci/check v0.0.0-20180506172741-cfe4005ccda2/go.mod h1:k9Qvh+8juN+UKMCS/3jFtGICgW8O96FVaZsaxdzDkR4= github.com/golangci/dupl v0.0.0-20180902072040-3e9179ac440a/go.mod h1:ryS0uhF+x9jgbj/N71xsEqODy9BN81/GonCZiOzirOk= @@ -959,6 +972,7 @@ github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+o github.com/klauspost/compress v1.4.0/go.mod h1:RyIbtBH6LamlWaDj8nUwkbUhJ87Yi3uG0guNDohfE1A= github.com/klauspost/compress v1.9.5/go.mod h1:RyIbtBH6LamlWaDj8nUwkbUhJ87Yi3uG0guNDohfE1A= github.com/klauspost/compress v1.10.7/go.mod h1:aoV0uJVorq1K+umq18yTdKaF57EivdYsUV+/s2qKfXs= +github.com/klauspost/compress v1.11.0 h1:wJbzvpYMVGG9iTI9VxpnNZfd4DzMPoCWze3GgSqz8yg= github.com/klauspost/compress v1.11.0/go.mod h1:aoV0uJVorq1K+umq18yTdKaF57EivdYsUV+/s2qKfXs= github.com/klauspost/cpuid v0.0.0-20170728055534-ae7887de9fa5/go.mod h1:Pj4uuM528wm8OyEC2QMXAi2YiTZ96dNQPGgoMS4s3ek= github.com/klauspost/crc32 v0.0.0-20161016154125-cb6bfca970f6/go.mod h1:+ZoRqAPRLkC4NPOvfYeR5KNOrY6TD+/sAC3HXPZgDYg= @@ -970,8 +984,9 @@ github.com/kr/fs v0.1.0/go.mod h1:FFnZGqtBN9Gxj7eW1uZ42v5BccTP0vu6NEaFoC2HwRg= github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= github.com/kr/pretty v0.2.0/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= -github.com/kr/pretty v0.2.1 h1:Fmg33tUaq4/8ym9TJN1x7sLJnHVwhP33CNkpYV/7rwI= github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= +github.com/kr/pretty v0.3.0 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0= +github.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/pty v1.1.4/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/pty v1.1.5 h1:hyz3dwM5QLc1Rfoz4FuWJQG5BN7tc6K1MndAUnGpQr4= @@ -988,10 +1003,6 @@ github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+ github.com/kyoh86/exportloopref v0.1.8/go.mod h1:1tUcJeiioIs7VWe5gcOObrux3lb66+sBqGZrRkMwPgg= github.com/kyokomi/emoji v2.2.4+incompatible h1:np0woGKwx9LiHAQmwZx79Oc0rHpNw3o+3evou4BEPv4= github.com/kyokomi/emoji v2.2.4+incompatible/go.mod h1:mZ6aGCD7yk8j6QY6KICwnZ2pxoszVseX1DNoGtU2tBA= -github.com/labstack/echo/v4 v4.5.0 h1:JXk6H5PAw9I3GwizqUHhYyS4f45iyGebR/c1xNCeOCY= -github.com/labstack/echo/v4 v4.5.0/go.mod h1:czIriw4a0C1dFun+ObrXp7ok03xON0N1awStJ6ArI7Y= -github.com/labstack/gommon v0.3.0 h1:JEeO0bvc78PKdyHxloTKiF8BD5iGrH8T6MSeGvSgob0= -github.com/labstack/gommon v0.3.0/go.mod h1:MULnywXg0yavhxWKc+lOruYdAhDwPK9wf0OL7NoOu+k= github.com/lann/builder v0.0.0-20180802200727-47ae307949d0 h1:SOEGU9fKiNWd/HOJuq6+3iTQz8KNCLtVX6idSoTLdUw= github.com/lann/builder v0.0.0-20180802200727-47ae307949d0/go.mod h1:dXGbAdH5GtBTC4WfIxhKZfyBF/HBFgRZSWwZ9g/He9o= github.com/lann/ps v0.0.0-20150810152359-62de8c46ede0 h1:P6pPBnrTSX3DEVR4fDembhRWSsG5rVo6hYhAB/ADZrk= @@ -999,6 +1010,8 @@ github.com/lann/ps v0.0.0-20150810152359-62de8c46ede0/go.mod h1:vmVJ0l/dxyfGW6Fm github.com/ldez/gomoddirectives v0.2.1/go.mod h1:sGicqkRgBOg//JfpXwkB9Hj0X5RyJ7mlACM5B9f6Me4= github.com/ldez/tagliatelle v0.2.0/go.mod h1:8s6WJQwEYHbKZDsp/LjArytKOG8qaMrKQQ3mFukHs88= github.com/leanovate/gopter v0.2.4/go.mod h1:gNcbPWNEWRe4lm+bycKqxUYoH5uoVje5SkOJ3uoLer8= +github.com/leodido/go-urn v1.2.1 h1:BqpAaACuzVSgi/VLzGZIobT2z4v53pjosyNd9Yv6n/w= +github.com/leodido/go-urn v1.2.1/go.mod h1:zt4jvISO2HfUBqxjfIshjdMTYS56ZS/qv49ictyFfxY= github.com/letsencrypt/pkcs11key/v4 v4.0.0/go.mod h1:EFUvBDay26dErnNb70Nd0/VW3tJiIbETBPTl9ATXQag= github.com/lib/pq v1.0.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= github.com/lib/pq v1.1.1/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= @@ -1047,7 +1060,6 @@ github.com/mattn/go-ieproxy v0.0.0-20191113090002-7c0f6868bffe/go.mod h1:pYabZ6I github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= github.com/mattn/go-isatty v0.0.4/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= -github.com/mattn/go-isatty v0.0.9/go.mod h1:YNRxwqDuOph6SZLI9vUUz6OYw3QyUt7WiY2yME+cCiQ= github.com/mattn/go-isatty v0.0.10/go.mod h1:qgIWMr58cqv1PHHyhnkY9lrL7etaEgOFcMEpPG5Rm84= github.com/mattn/go-isatty v0.0.11/go.mod h1:PhnuNfih5lzO57/f3n+odYbM4JtupLOxQOAqxQCu2WE= github.com/mattn/go-isatty v0.0.12 h1:wuysRhFDzyxgEmMf5xjvJ2M9dZoWAXNNr5LSBS7uHXY= @@ -1161,7 +1173,6 @@ github.com/nats-io/nkeys v0.1.3/go.mod h1:xpnFELMwJABBLVhffcfd1MZx6VsNRFpEugbxzi github.com/nats-io/nuid v1.0.1/go.mod h1:19wcPz3Ph3q0Jbyiqsd0kePYG7A95tJPxeL+1OSON2c= github.com/nbutton23/zxcvbn-go v0.0.0-20210217022336-fa2cb2858354/go.mod h1:KSVJerMDfblTH7p5MZaTt+8zaT2iEk3AkVb9PQdZuE8= github.com/ncw/swift v1.0.47/go.mod h1:23YIA4yWVnGwv2dQlN4bB7egfYX6YLn0Yo/S6zZO/ZM= -github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e h1:fD57ERR4JtEqsWbfPhv4DMiApHyliiK5xCTNVSPiaAs= github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno= github.com/nishanths/exhaustive v0.1.0/go.mod h1:S1j9110vxV1ECdCudXRkeMnFQ/DQk9ajLT0Uf2MYZQQ= github.com/nishanths/predeclared v0.0.0-20190419143655-18a43bb90ffc/go.mod h1:62PewwiQTlm/7Rj+cxVYqZvDIUc+JjZq6GHAC1fsObQ= @@ -1262,6 +1273,7 @@ github.com/phayes/freeport v0.0.0-20180830031419-95f893ade6f2/go.mod h1:iIss55rK github.com/philhofer/fwd v1.0.0/go.mod h1:gk3iGcWd9+svBvR0sR+KPcfE+RNWozjowpeBVG3ZVNU= github.com/pierrec/lz4 v1.0.2-0.20190131084431-473cd7ce01a1/go.mod h1:3/3N9NVKO0jef7pBehbT1qWhCMrIgbYNnFAZCqQ5LRc= github.com/pierrec/lz4 v2.0.5+incompatible/go.mod h1:pdkljMzZIN41W+lC3N2tnIh5sFi+IEE17M5jbnwPHcY= +github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA= github.com/pkg/errors v0.0.0-20180311214515-816c9085562c/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= @@ -1358,8 +1370,10 @@ github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFR github.com/rogpeppe/go-internal v1.3.2/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc= github.com/rogpeppe/go-internal v1.4.0/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc= github.com/rogpeppe/go-internal v1.6.0/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc= -github.com/rogpeppe/go-internal v1.6.2 h1:aIihoIOHCiLZHxyoNQ+ABL4NKhFTgKLBdMLyEAh98m0= +github.com/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc= github.com/rogpeppe/go-internal v1.6.2/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc= +github.com/rogpeppe/go-internal v1.8.0 h1:FCbCCtXNOY3UtUuHUYaghJg4y7Fd14rXifAYUAtL9R8= +github.com/rogpeppe/go-internal v1.8.0/go.mod h1:WmiCO8CzOY8rg0OYDC4/i/2WRWAB6poM+XZ2dLUbcbE= github.com/rs/cors v1.6.0/go.mod h1:gFx+x8UowdsKA9AchylcLynDq+nNFfI8FkUZdN/jGCU= github.com/rs/cors v1.7.0/go.mod h1:gFx+x8UowdsKA9AchylcLynDq+nNFfI8FkUZdN/jGCU= github.com/rubenv/sql-migrate v0.0.0-20200616145509-8d140a17f351 h1:HXr/qUllAWv9riaI4zh2eXWKmCSDqVS/XH1MRHLKRwk= @@ -1508,12 +1522,8 @@ github.com/urfave/cli v1.21.0/go.mod h1:lxDj6qX9Q6lWQxIrbrT0nwecwUtRnhVZAJjJZrVU github.com/urfave/cli v1.22.1/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0= github.com/urfave/cli v1.22.2/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0= github.com/uudashr/gocognit v1.0.1/go.mod h1:j44Ayx2KW4+oB6SWMv8KsmHzZrOInQav7D3cQMJ5JUM= -github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw= github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc= github.com/valyala/fasthttp v1.16.0/go.mod h1:YOKImeEosDdBPnxc0gy7INqi3m1zK6A+xl6TwOBhHCA= -github.com/valyala/fasttemplate v1.0.1/go.mod h1:UQGH1tvbgY+Nz5t2n7tXsz52dQxojPUpymEIMZ47gx8= -github.com/valyala/fasttemplate v1.2.1 h1:TVEnxayobAdVkhQfrfes2IzOB6o+z4roRkPF52WA1u4= -github.com/valyala/fasttemplate v1.2.1/go.mod h1:KHLXt3tVN2HBp8eijSv/kGJopbvo7S+qRAEEKiv+SiQ= github.com/valyala/quicktemplate v1.6.3/go.mod h1:fwPzK2fHuYEODzJ9pkw0ipCPNHZ2tD5KW4lOuSdPKzY= github.com/valyala/tcplisten v0.0.0-20161114210144-ceec8f93295a/go.mod h1:v3UYOV9WzVtRmSR+PDvWpU/qWl4Wa5LApYYX4ZtKbio= github.com/vektah/gqlparser v1.1.2/go.mod h1:1ycwN7Ij5njmMkPPAOaRFY4rET2Enx7IkVv3vaXspKw= @@ -1527,7 +1537,9 @@ github.com/willf/bitset v1.1.3/go.mod h1:RjeCKbqT1RxIR/KWY6phxZiaY1IyutSBfGjNPyS github.com/willf/bitset v1.1.11/go.mod h1:83CECat5yLh5zVOf4P1ErAgKA5UDvKtgyUABdr3+MjI= github.com/wonderflow/cert-manager-api v1.0.3 h1:xQQMkJNQ12oYyy00jOQUlSKgdraApaURxv3PHFdVTfA= github.com/wonderflow/cert-manager-api v1.0.3/go.mod h1:1Se7MSg11/eNYlo4fWv6vOM55/jTBMOzg2DN1kVFiSc= +github.com/xdg/scram v0.0.0-20180814205039-7eeb5667e42c h1:u40Z8hqBAAQyv+vATcGgV0YCnDjqSL7/q/JyPhhJSPk= github.com/xdg/scram v0.0.0-20180814205039-7eeb5667e42c/go.mod h1:lB8K/P019DLNhemzwFU4jHLhdvlE6uDZjXFejJXr49I= +github.com/xdg/stringprep v0.0.0-20180714160509-73f8eece6fdc h1:n+nNi93yXLkJvKwXNP9d55HC7lGK4H/SRcwB5IaUZLo= github.com/xdg/stringprep v0.0.0-20180714160509-73f8eece6fdc/go.mod h1:Jhud4/sHMO4oL310DaZAKk9ZaJ08SJfe+sJh0HrGL1Y= github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f h1:J9EGpcZtP0E/raorCMxlFGSTBrsSlaDGf3jU/qvAE2c= github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f/go.mod h1:N2zxlSyiKSe5eX1tZViRH5QA0qijqEDrYZiPEAiq3wU= @@ -1591,6 +1603,7 @@ go.mongodb.org/mongo-driver v1.0.3/go.mod h1:u7ryQJ+DOzQmeO7zB6MHyr8jkEQvC8vH7qL go.mongodb.org/mongo-driver v1.1.1/go.mod h1:u7ryQJ+DOzQmeO7zB6MHyr8jkEQvC8vH7qLUO4lqsUM= go.mongodb.org/mongo-driver v1.1.2/go.mod h1:u7ryQJ+DOzQmeO7zB6MHyr8jkEQvC8vH7qLUO4lqsUM= go.mongodb.org/mongo-driver v1.3.0/go.mod h1:MSWZXKOynuguX+JSvwP8i+58jYCXxbia8HS3gZBapIE= +go.mongodb.org/mongo-driver v1.3.2 h1:IYppNjEV/C+/3VPbhHVxQ4t04eVW0cLp0/pNdW++6Ug= go.mongodb.org/mongo-driver v1.3.2/go.mod h1:MSWZXKOynuguX+JSvwP8i+58jYCXxbia8HS3gZBapIE= go.mozilla.org/mozlog v0.0.0-20170222151521-4bb13139d403/go.mod h1:jHoPAGnDrCy6kaI2tAze5Prf0Nr0w/oNkROt2lw3n3o= go.opencensus.io v0.20.1/go.mod h1:6WKK9ahsWS3RSO+PY9ZHZUfv2irvY6gN279GOPZjmmk= @@ -1684,8 +1697,9 @@ golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPh golang.org/x/crypto v0.0.0-20201002170205-7f63de1d35b0/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20201221181555-eec23a3978ad/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I= golang.org/x/crypto v0.0.0-20210220033148-5ea612d1eb83/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I= -golang.org/x/crypto v0.0.0-20210322153248-0c34fe9e7dc2 h1:It14KIkyBFYkHkwZ7k45minvA9aorojkyjGk9KJ5B/w= golang.org/x/crypto v0.0.0-20210322153248-0c34fe9e7dc2/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4= +golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97 h1:/UOmuWzQfxxo9UtlXMwuQU8CMgg1eZXqTRwkSQJWKOI= +golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/exp v0.0.0-20180321215751-8460e604b9de/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20180807140117-3d87b88a115f/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= @@ -1925,9 +1939,11 @@ golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20210426230700-d19ff857e887/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210603081109-ebe580a85c40/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210616094352-59db8d763f22/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c h1:F1jZWGFhYfh0Ci55sIpILtKKK8p3i2/krTr0H1rg74I= golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210806184541-e5e7981a1069 h1:siQdpVirKtzPhKl3lZWozZraCFObP8S1v6PRp0bLrtU= +golang.org/x/sys v0.0.0-20210806184541-e5e7981a1069/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210220032956-6a3ed077a48d h1:SZxvLBoTP5yHO3Frd4z4vrF+DBX9vMVanchswa69toE= @@ -1950,7 +1966,6 @@ golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxb golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20200416051211-89c76fbcd5d1/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= -golang.org/x/time v0.0.0-20201208040808-7e3f01d25324/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20210220033141-f8bda1e9f3ba/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20210611083556-38a9dc6acbc6/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20210723032227-1f47c861a9ac h1:7zkz7BUtwNFFqcowJ+RIgu2MaV/MapERkDIy+mwPyjs= @@ -2236,8 +2251,9 @@ gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8 gopkg.in/check.v1 v1.0.0-20141024133853-64131543e789/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f h1:BLraFXnmrev5lT+xlilqcH8XK9/i0At2xKjWk4p6zsU= gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= gopkg.in/cheggaaa/pb.v1 v1.0.25/go.mod h1:V/YB90LKu/1FcN3WVnfiiE5oMCibMjukxqG/qStrOgw= gopkg.in/cheggaaa/pb.v1 v1.0.27/go.mod h1:V/YB90LKu/1FcN3WVnfiiE5oMCibMjukxqG/qStrOgw= gopkg.in/cheggaaa/pb.v1 v1.0.28/go.mod h1:V/YB90LKu/1FcN3WVnfiiE5oMCibMjukxqG/qStrOgw= diff --git a/pkg/apiserver/datastore/datastore.go b/pkg/apiserver/datastore/datastore.go new file mode 100644 index 000000000..312da9be2 --- /dev/null +++ b/pkg/apiserver/datastore/datastore.go @@ -0,0 +1,57 @@ +/* +Copyright 2021 The KubeVela Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package datastore + +import ( + "context" +) + +// Config datastore config +type Config struct { + Type string + URL string + Database string +} + +// DataStore datastore interface +type DataStore interface { + Add(ctx context.Context, kind string, entity interface{}) error + + Put(ctx context.Context, kind, name string, entity interface{}) error + + Delete(ctx context.Context, kind, name string) error + + Get(ctx context.Context, kind, name string, decodeTo interface{}) error + + // Find executes a find command and returns an iterator over the matching items. + Find(ctx context.Context, kind string) (Iterator, error) + + FindOne(ctx context.Context, kind, name string) (Iterator, error) + + IsExist(ctx context.Context, kind, name string) (bool, error) +} + +// Iterator dataset query +type Iterator interface { + // Next gets the next item for this cursor. + Next(ctx context.Context) bool + + // Decode will unmarshal the current item into given entity. + Decode(entity interface{}) error + + Close(ctx context.Context) error +} diff --git a/pkg/apiserver/datastore/kubeapi/kubeapi.go b/pkg/apiserver/datastore/kubeapi/kubeapi.go new file mode 100644 index 000000000..b9b271a05 --- /dev/null +++ b/pkg/apiserver/datastore/kubeapi/kubeapi.go @@ -0,0 +1,67 @@ +/* +Copyright 2021 The KubeVela Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package kubeapi + +import ( + "context" + + "github.com/oam-dev/kubevela/pkg/apiserver/datastore" +) + +type kubeapi struct { + // kubeclient client.Client +} + +// New new kubeapi datastore instance +func New(ctx context.Context, cfg datastore.Config) (datastore.DataStore, error) { + return &kubeapi{}, nil +} + +// Add add data model +func (m *kubeapi) Add(ctx context.Context, kind string, entity interface{}) error { + return nil +} + +// Get get data model +func (m *kubeapi) Get(ctx context.Context, kind, name string, decodeTo interface{}) error { + return nil +} + +// Put update data model +func (m *kubeapi) Put(ctx context.Context, kind, name string, entity interface{}) error { + return nil +} + +// Find find data model +func (m *kubeapi) Find(ctx context.Context, kind string) (datastore.Iterator, error) { + return nil, nil +} + +// FindOne find one data model +func (m *kubeapi) FindOne(ctx context.Context, kind, name string) (datastore.Iterator, error) { + return nil, nil +} + +// IsExist determine whether data exists. +func (m *kubeapi) IsExist(ctx context.Context, kind, name string) (bool, error) { + return true, nil +} + +// Delete delete data +func (m *kubeapi) Delete(ctx context.Context, kind, name string) error { + return nil +} diff --git a/pkg/apiserver/datastore/mongodb/iterator.go b/pkg/apiserver/datastore/mongodb/iterator.go new file mode 100644 index 000000000..2b2f083e0 --- /dev/null +++ b/pkg/apiserver/datastore/mongodb/iterator.go @@ -0,0 +1,43 @@ +/* +Copyright 2021 The KubeVela Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package mongodb + +import ( + "context" + + "go.mongodb.org/mongo-driver/mongo" +) + +// Iterator mongo iterator implementation +type Iterator struct { + cur *mongo.Cursor +} + +// Close iterator close +func (i *Iterator) Close(ctx context.Context) error { + return i.cur.Close(ctx) +} + +// Next read next data +func (i *Iterator) Next(ctx context.Context) bool { + return i.cur.Next(ctx) +} + +// Decode decode data +func (i *Iterator) Decode(entity interface{}) error { + return i.cur.Decode(entity) +} diff --git a/pkg/apiserver/datastore/mongodb/mongodb.go b/pkg/apiserver/datastore/mongodb/mongodb.go new file mode 100644 index 000000000..3e34510a2 --- /dev/null +++ b/pkg/apiserver/datastore/mongodb/mongodb.go @@ -0,0 +1,137 @@ +/* +Copyright 2021 The KubeVela Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package mongodb + +import ( + "context" + "errors" + "fmt" + + "cuelang.org/go/pkg/strings" + "go.mongodb.org/mongo-driver/bson" + "go.mongodb.org/mongo-driver/mongo" + "go.mongodb.org/mongo-driver/mongo/options" + + "github.com/oam-dev/kubevela/pkg/apiserver/datastore" +) + +type mongodb struct { + client *mongo.Client + database string +} + +// New new mongodb datastore instance +func New(ctx context.Context, cfg datastore.Config) (datastore.DataStore, error) { + if strings.HasPrefix(cfg.URL, "mongodb://") { + cfg.URL = fmt.Sprintf("mongodb://%s", cfg.URL) + } + clientOpts := options.Client().ApplyURI(cfg.URL) + client, err := mongo.Connect(ctx, clientOpts) + if err != nil { + return nil, err + } + + m := &mongodb{ + client: client, + database: cfg.Database, + } + return m, nil +} + +// Add add data model +func (m *mongodb) Add(ctx context.Context, kind string, entity interface{}) error { + collection := m.client.Database(m.database).Collection(kind) + _, err := collection.InsertOne(ctx, entity) + if err != nil { + return err + } + return nil +} + +// Get get data model +func (m *mongodb) Get(ctx context.Context, kind, name string, decodeTo interface{}) error { + collection := m.client.Database(m.database).Collection(kind) + return collection.FindOne(ctx, makeNameFilter(name)).Decode(decodeTo) +} + +// Put update data model +func (m *mongodb) Put(ctx context.Context, kind, name string, entity interface{}) error { + collection := m.client.Database(m.database).Collection(kind) + _, err := collection.UpdateOne(ctx, makeNameFilter(name), makeEntityUpdate(entity)) + if err != nil { + return err + } + return nil +} + +// Find find data model +func (m *mongodb) Find(ctx context.Context, kind string) (datastore.Iterator, error) { + collection := m.client.Database(m.database).Collection(kind) + // bson.D{{}} specifies 'all documents' + filter := bson.D{} + cur, err := collection.Find(ctx, filter) + if err != nil { + return nil, err + } + return &Iterator{cur: cur}, nil +} + +// FindOne find one data model +func (m *mongodb) FindOne(ctx context.Context, kind, name string) (datastore.Iterator, error) { + collection := m.client.Database(m.database).Collection(kind) + filter := bson.M{"name": name} + cur, err := collection.Find(ctx, filter) + if err != nil { + return nil, err + } + return &Iterator{cur: cur}, nil +} + +// IsExist determine whether data exists. +func (m *mongodb) IsExist(ctx context.Context, kind, name string) (bool, error) { + collection := m.client.Database(m.database).Collection(kind) + err := collection.FindOne(ctx, makeNameFilter(name)).Err() + if errors.Is(err, mongo.ErrNoDocuments) { + return false, nil + } else if err != nil { + return false, err + } + + return true, nil +} + +// Delete delete data +func (m *mongodb) Delete(ctx context.Context, kind, name string) error { + collection := m.client.Database(m.database).Collection(kind) + // delete at most one document in which the "name" field is "Bob" or "bob" + // specify the SetCollation option to provide a collation that will ignore case for string comparisons + opts := options.Delete().SetCollation(&options.Collation{ + Locale: "en_US", + Strength: 1, + CaseLevel: false, + }) + _, err := collection.DeleteOne(ctx, makeNameFilter(name), opts) + return err +} + +func makeNameFilter(name string) bson.D { + return bson.D{{Key: "name", Value: name}} +} + +func makeEntityUpdate(entity interface{}) bson.M { + return bson.M{"$set": entity} +} diff --git a/pkg/apiserver/rest/apis/catalog.go b/pkg/apiserver/rest/apis/v1/catalog.go similarity index 98% rename from pkg/apiserver/rest/apis/catalog.go rename to pkg/apiserver/rest/apis/v1/catalog.go index bb7b56221..b3b9e7130 100644 --- a/pkg/apiserver/rest/apis/catalog.go +++ b/pkg/apiserver/rest/apis/v1/catalog.go @@ -14,7 +14,7 @@ See the License for the specific language governing permissions and limitations under the License. */ -package apis +package v1 import ( "github.com/oam-dev/kubevela/pkg/apiserver/model" diff --git a/pkg/apiserver/rest/apis/core.go b/pkg/apiserver/rest/apis/v1/oam_application.go similarity index 95% rename from pkg/apiserver/rest/apis/core.go rename to pkg/apiserver/rest/apis/v1/oam_application.go index a340f076a..d24a79b7c 100644 --- a/pkg/apiserver/rest/apis/core.go +++ b/pkg/apiserver/rest/apis/v1/oam_application.go @@ -5,7 +5,7 @@ Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at - http://www.apache.org/licenses/LICENSE-2.0 + http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, @@ -14,7 +14,7 @@ See the License for the specific language governing permissions and limitations under the License. */ -package apis +package v1 import ( "github.com/oam-dev/kubevela/apis/core.oam.dev/common" diff --git a/pkg/apiserver/rest/apis/v1/types.go b/pkg/apiserver/rest/apis/v1/types.go new file mode 100644 index 000000000..318494251 --- /dev/null +++ b/pkg/apiserver/rest/apis/v1/types.go @@ -0,0 +1,250 @@ +/* +Copyright 2021 The KubeVela Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package v1 + +import ( + "time" +) + +// CreateClusterRequest request parameters to create a cluster +type CreateClusterRequest struct { + Name string `json:"name" validate:"required"` + Description string `json:"description,omitempty"` + Icon string `json:"icon"` + KubeConfig string `json:"kubeConfig" validate:"required_without=kubeConfigSecret"` + KubeConfigSecret string `json:"kubeConfigSecret,omitempty" validate:"required_without=kubeConfig"` + Labels map[string]string `json:"labels,omitempty"` +} + +// DetailClusterResponse cluster detail information model +type DetailClusterResponse struct { + ClusterBase + ResourceInfo ClusterResourceInfo `json:"resourceInfo"` + // remote manage url, eg. ACK cluster manage url. + RemoteManageURL string `json:"remoteManageURL,omitempty"` + // Dashboard URL + DashboardURL string `json:"dashboardURL,omitempty"` +} + +// ClusterResourceInfo resource info of cluster +type ClusterResourceInfo struct { + WorkerNumber int `json:"workerNumber"` + MasterNumber int `json:"masterNumber"` + MemoryCapacity int64 `json:"memoryCapacity"` + CPUCapacity int64 `json:"cpuCapacity"` + GPUCapacity int64 `json:"gpuCapacity,omitempty"` + StorageClassList []string `json:"storageClassList,omitempty"` +} + +// ListClusterResponse list cluster +type ListClusterResponse struct { + Clusters []ClusterBase `json:"clusters"` +} + +// ClusterBase cluster base model +type ClusterBase struct { + Name string `json:"name"` + Description string `json:"description"` + Icon string `json:"icon"` + Labels map[string]string `json:"labels"` + Status string `json:"status"` + Reason string `json:"reason"` +} + +// ListClusterAddonResponse list all addon of the cluster +type ListClusterAddonResponse struct { + Addons []ClusterAddonBase `json:"addons"` +} + +// ClusterAddonBase cluster addon base model +type ClusterAddonBase struct { + Name string `json:"name"` + Description string `json:"description"` + Status string `json:"status"` +} + +// DeatilClusterAddonResponse detail addon info +type DeatilClusterAddonResponse struct { + ClusterAddonBase +} + +// ListApplicationResponse list applications by query params +type ListApplicationResponse struct { + Applications []*ApplicationBase `json:"applications"` +} + +// ApplicationBase application base model +type ApplicationBase struct { + Name string `json:"name"` + Namespace string `json:"namespace"` + Description string `json:"description"` + CreateTime time.Time `json:"createTime"` + UpdateTime time.Time `json:"updateTime"` + Icon string `json:"icon"` + Labels map[string]string `json:"labels,omitempty"` + ClusterBindList []ClusterBase `json:"clusterList,omitempty"` + Status string `json:"status"` + GatewayRuleList []GatewayRule `json:"gatewayRule"` +} + +// RuleType gateway rule type +type RuleType string + +const ( + // HTTPRule Layer 7 HTTP policy. + HTTPRule RuleType = "http" + // StreamRule Layer 4 policy, such as TCP and UDP + StreamRule RuleType = "stream" +) + +// GatewayRule application gateway rule +type GatewayRule struct { + RuleType RuleType `json:"ruleType"` + Address string `json:"address"` + Protocol string `json:"protocol"` + ComponentName string `json:"componentName"` + ComponentPort int32 `json:"componentPort"` +} + +// CreateApplicationRequest create application request body +type CreateApplicationRequest struct { + Name string `json:"name" validate:"required"` + Namespace string `json:"namespace" validate:"required"` + Description string `json:"description"` + Icon string `json:"icon"` + Labels map[string]string `json:"labels,omitempty"` + ClusterList []string `json:"clusterList,omitempty"` + YamlConfig string `json:"yamlConfig,omitempty"` +} + +// DetailApplicationResponse application detail +type DetailApplicationResponse struct { + ApplicationBase + Policies []string `json:"policies"` + Status string `json:"status"` + ResourceInfo ApplicationResourceInfo `json:"resourceInfo"` + WorkflowStatus []WorkflowStepStatus `json:"workflowStatus"` +} + +// WorkflowStepStatus workflow step status model +type WorkflowStepStatus struct { + Name string `json:"name"` + Status string `json:"status"` + TakeTime time.Duration `json:"takeTime"` +} + +// ApplicationResourceInfo application-level resource consumption statistics +type ApplicationResourceInfo struct { + ComponentNum int `json:"componentNum"` + // Others, such as: Memory、CPU、GPU、Storage +} + +// ComponentBase component base model +type ComponentBase struct { + Name string `json:"name"` + Description string `json:"description"` + Labels map[string]string `json:"labels,omitempty"` + ComponentType string `json:"componentType"` + BindClusters []string `json:"bindClusters"` + Icon string `json:"icon,omitempty"` + DependOn []string `json:"dependOn"` + Creator string `json:"creator,omitempty"` + DeployVersion string `json:"deployVersion"` +} + +// ComponentListResponse list component +type ComponentListResponse struct { + Components []ComponentBase `json:"components"` +} + +// CreateComponentRequest create component request model +type CreateComponentRequest struct { + ApplicationName string `json:"appName" validate:"required"` + Name string `json:"name" validate:"required"` + Description string `json:"description"` + Labels map[string]string `json:"labels,omitempty"` + ComponentType string `json:"componentType" validate:"required"` + BindClusters []string `json:"bindClusters"` + Properties string `json:"properties,omitempty"` +} + +// CreateApplicationTemplateRequest create app template request model +type CreateApplicationTemplateRequest struct { + TemplateName string `json:"templateName" validate:"required"` + Version string `json:"version" validate:"required"` + Description string `json:"description"` +} + +// ApplicationTemplateBase app template model +type ApplicationTemplateBase struct { + TemplateName string `json:"templateName"` + Versions []*ApplicationTemplateVersion `json:"versions,omitempty"` +} + +// ApplicationTemplateVersion template version model +type ApplicationTemplateVersion struct { + Version string `json:"version"` + Description string `json:"description"` + CreateUser string `json:"createUser"` + CreateTime time.Time `json:"createTime"` + UpdateTime time.Time `json:"updateTime"` +} + +// ListNamespaceResponse namesace list model +type ListNamespaceResponse struct { + Namespaces []NamesapceBase `json:"namesapces"` +} + +// NamesapceBase namespace base model +type NamesapceBase struct { + Name string `json:"name"` + Description string `json:"description"` +} + +// CreateNamespaceRequest create namespace request body +type CreateNamespaceRequest struct { + Name string `json:"name" validate:"required"` + Description string `json:"description"` +} + +// NamesapceDetailResponse namespace detail response +type NamesapceDetailResponse struct { + NamesapceBase + ClusterBind map[string]string `json:"clusterBind"` +} + +// ListComponentDefinitionResponse list component dedinition response model +type ListComponentDefinitionResponse struct { + ComponentDefinitions []ComponentDefinitionBase `json:"componentDefinitions"` +} + +// ComponentDefinitionBase component definition base model +type ComponentDefinitionBase struct { + Name string `json:"name"` + Description string `json:"description"` + Icon string `json:"icon"` + RequiredParams []Param `json:"requiredParams"` +} + +// Param For rendering forms +type Param struct { + Key string `json:"key"` + Name string `json:"name"` + DefaultValue interface{} `json:"defaultValue"` + Type string `json:"type"` + Description string `json:"description"` +} diff --git a/pkg/apiserver/rest/rest_server.go b/pkg/apiserver/rest/rest_server.go index 5b65bf714..6d81b7862 100644 --- a/pkg/apiserver/rest/rest_server.go +++ b/pkg/apiserver/rest/rest_server.go @@ -21,20 +21,28 @@ import ( "fmt" "net/http" - "github.com/labstack/echo/v4" - "github.com/labstack/echo/v4/middleware" - "sigs.k8s.io/controller-runtime/pkg/client" + restfulspec "github.com/emicklei/go-restful-openapi/v2" + restful "github.com/emicklei/go-restful/v3" + "github.com/go-openapi/spec" + "github.com/oam-dev/kubevela/pkg/apiserver/datastore" + "github.com/oam-dev/kubevela/pkg/apiserver/datastore/kubeapi" + "github.com/oam-dev/kubevela/pkg/apiserver/datastore/mongodb" "github.com/oam-dev/kubevela/pkg/apiserver/log" - "github.com/oam-dev/kubevela/pkg/apiserver/rest/services" - "github.com/oam-dev/kubevela/pkg/utils/common" + "github.com/oam-dev/kubevela/pkg/apiserver/rest/webservice" ) var _ APIServer = &restServer{} // Config config for server type Config struct { - Port int + // api server bind address + BindAddr string + // monitor metric path + MetricPath string + + // Datastore config + Datastore datastore.Config } // APIServer interface for call api server @@ -43,45 +51,38 @@ type APIServer interface { } type restServer struct { - server *echo.Echo - k8sClient client.Client - cfg Config + webContainer *restful.Container + cfg Config + dataStore datastore.DataStore } // New create restserver with config data -func New(cfg Config) (APIServer, error) { - client, err := common.NewK8sClient() - if err != nil { - return nil, fmt.Errorf("create client for clusterService failed") +func New(cfg Config) (a APIServer, err error) { + var ds datastore.DataStore + switch cfg.Datastore.Type { + case "mongodb": + ds, err = mongodb.New(context.Background(), cfg.Datastore) + if err != nil { + return nil, fmt.Errorf("create mongodb datastore instance failure %w", err) + } + case "kubeapi": + ds, err = kubeapi.New(context.Background(), cfg.Datastore) + if err != nil { + return nil, fmt.Errorf("create mongodb datastore instance failure %w", err) + } + default: + return nil, fmt.Errorf("not support datastore type %s", cfg.Datastore.Type) } s := &restServer{ - server: newEchoInstance(), - k8sClient: client, - cfg: cfg, + webContainer: restful.NewContainer(), + cfg: cfg, + dataStore: ds, } - return s, nil } -func newEchoInstance() *echo.Echo { - e := echo.New() - e.HideBanner = true - - e.Use(middleware.Gzip()) - e.Use(middleware.Logger()) - e.Pre(middleware.RemoveTrailingSlash()) - - e.Use(middleware.CORSWithConfig(middleware.CORSConfig{ - AllowOrigins: []string{"*"}, - AllowMethods: []string{http.MethodGet, http.MethodHead, http.MethodPut, http.MethodPatch, http.MethodPost, http.MethodDelete}, - AllowCredentials: true, - MaxAge: 86400, - })) - - return e -} - func (s *restServer) Run(ctx context.Context) error { + webservice.Init(ctx) err := s.registerServices() if err != nil { return err @@ -94,28 +95,58 @@ func (s *restServer) registerServices() error { /* ************************************************************** */ /* ************* Open API Route Group ***************** */ /* ************************************************************** */ - openapi := s.server.Group("/v1") - // catalog - catalogService := services.NewCatalogService(s.k8sClient) - openapi.GET("/catalogs", catalogService.ListCatalogs) - openapi.POST("/catalogs", catalogService.AddCatalog) - openapi.PUT("/catalogs", catalogService.UpdateCatalog) - openapi.GET("/catalogs/:catalogName", catalogService.GetCatalog) - openapi.DELETE("/catalogs/:catalogName", catalogService.DelCatalog) + // Add container filter to enable CORS + cors := restful.CrossOriginResourceSharing{ + ExposeHeaders: []string{}, + AllowedHeaders: []string{"Content-Type", "Accept"}, + AllowedMethods: []string{"GET", "POST", "PUT", "DELETE", "OPTIONS", "PATCH"}, + CookiesAllowed: true, + Container: s.webContainer} + s.webContainer.Filter(cors.Filter) - // application - applicationService := services.NewApplicationService(s.k8sClient) - openapi.GET("/namespaces/:namespace/applications/:appname", applicationService.GetApplication) - openapi.POST("/namespaces/:namespace/applications/:appname", applicationService.CreateOrUpdateApplication) - openapi.DELETE("/namespaces/:namespace/applications/:appname", applicationService.DeleteApplication) + // Add container filter to respond to OPTIONS + s.webContainer.Filter(s.webContainer.OPTIONSFilter) + // Regist all custom webservice + for _, handler := range webservice.GetRegistedWebService() { + s.webContainer.Add(handler.GetWebService()) + } + + config := restfulspec.Config{ + WebServices: s.webContainer.RegisteredWebServices(), // you control what services are visible + APIPath: "/apidocs.json", + PostBuildSwaggerObjectHandler: enrichSwaggerObject} + s.webContainer.Add(restfulspec.NewOpenAPIService(config)) return nil } +func enrichSwaggerObject(swo *spec.Swagger) { + swo.Info = &spec.Info{ + InfoProps: spec.InfoProps{ + Title: "Kubevela api doc", + Description: "Kubevela api doc", + Contact: &spec.ContactInfo{ + ContactInfoProps: spec.ContactInfoProps{ + Name: "kubevela", + Email: "feedback@mail.kubevela.io", + URL: "https://kubevela.io/", + }, + }, + License: &spec.License{ + LicenseProps: spec.LicenseProps{ + Name: "Apache License 2.0", + URL: "https://github.com/oam-dev/kubevela/blob/master/LICENSE", + }, + }, + Version: "v1beta1", + }, + } +} + func (s *restServer) startHTTP(ctx context.Context) error { // Start HTTP apiserver - log.Logger.Infof("HTTP APIs are being served on port: %d, ctx: %s", s.cfg.Port, ctx) - addr := fmt.Sprintf(":%d", s.cfg.Port) - return s.server.Start(addr) + log.Logger.Infof("HTTP APIs are being served on: %s, ctx: %s", s.cfg.BindAddr, ctx) + server := &http.Server{Addr: s.cfg.BindAddr, Handler: s.webContainer} + return server.ListenAndServe() } diff --git a/pkg/apiserver/rest/services/application.go b/pkg/apiserver/rest/services/application.go deleted file mode 100644 index 346e23d1c..000000000 --- a/pkg/apiserver/rest/services/application.go +++ /dev/null @@ -1,122 +0,0 @@ -/* -Copyright 2021 The KubeVela Authors. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -package services - -import ( - "context" - "net/http" - - "github.com/labstack/echo/v4" - apierrors "k8s.io/apimachinery/pkg/api/errors" - "sigs.k8s.io/controller-runtime/pkg/client" - - "github.com/oam-dev/kubevela/apis/core.oam.dev/v1beta1" - "github.com/oam-dev/kubevela/pkg/apiserver/rest/apis" -) - -// ApplicationService serves as Application Open API for request -type ApplicationService struct { - k8sClient client.Client -} - -// NewApplicationService create an application service -func NewApplicationService(kc client.Client) *ApplicationService { - return &ApplicationService{ - k8sClient: kc, - } -} - -// GetApplication will get application status -// GET /namespaces//applications/ -func (s *ApplicationService) GetApplication(c echo.Context) error { - namespace := c.Param("namespace") - appName := c.Param("appname") - - ctx := context.TODO() - var existApp v1beta1.Application - err := s.k8sClient.Get(ctx, client.ObjectKey{Namespace: namespace, Name: appName}, &existApp) - if err != nil { - if apierrors.IsNotFound(err) { - return c.JSON(http.StatusNotFound, map[string]string{"error": "application does not exist: " + err.Error()}) - } - return c.JSON(http.StatusInternalServerError, map[string]string{"error": "fail to get application: " + err.Error()}) - } - - var appResp = &apis.ApplicationResponse{ - APIVersion: existApp.APIVersion, - Kind: existApp.Kind, - Spec: existApp.Spec, - Status: existApp.Status, - } - - return c.JSON(http.StatusOK, appResp) -} - -// CreateOrUpdateApplication will create or update application -// POST /namespaces//applications/ -func (s *ApplicationService) CreateOrUpdateApplication(c echo.Context) error { - namespace := c.Param("namespace") - name := c.Param("appname") - appReq := new(apis.ApplicationRequest) - if err := c.Bind(appReq); err != nil { - return c.JSON(http.StatusBadRequest, map[string]string{"error": "invalid request body: " + err.Error()}) - } - ctx := context.TODO() - var app, existApp v1beta1.Application - app.Namespace = namespace - app.Name = name - app.Spec.Components = appReq.Components - app.Spec.Policies = appReq.Policies - app.Spec.Workflow = appReq.Workflow - err := s.k8sClient.Get(ctx, client.ObjectKey{Namespace: namespace, Name: name}, &existApp) - if err != nil { - if !apierrors.IsNotFound(err) { - return c.JSON(http.StatusInternalServerError, map[string]string{"error": "fail to get application: " + err.Error()}) - } - err = s.k8sClient.Create(ctx, &app) - if err != nil { - return c.JSON(http.StatusInternalServerError, map[string]string{"error": "fail to create application: " + err.Error()}) - } - return c.JSON(http.StatusOK, struct{}{}) - } - existApp.Spec = app.Spec - err = s.k8sClient.Update(ctx, &existApp) - if err != nil { - return c.JSON(http.StatusInternalServerError, map[string]string{"error": "fail to update application: " + err.Error()}) - } - return c.JSON(http.StatusOK, struct{}{}) -} - -// DeleteApplication will delete application -// delete /v1/namespaces//applications/ -func (s *ApplicationService) DeleteApplication(c echo.Context) error { - namespace := c.Param("namespace") - appName := c.Param("appname") - - ctx := context.TODO() - var existApp v1beta1.Application - existApp.Namespace = namespace - existApp.Name = appName - err := s.k8sClient.Delete(ctx, &existApp) - if err != nil { - if apierrors.IsNotFound(err) { - return c.JSON(http.StatusNotFound, map[string]string{"error": "application does not exist: " + err.Error()}) - } - return c.JSON(http.StatusInternalServerError, map[string]string{"error": "fail to delete application: " + err.Error()}) - } - return c.JSON(http.StatusOK, struct{}{}) -} diff --git a/pkg/apiserver/rest/services/application_test.go b/pkg/apiserver/rest/services/application_test.go deleted file mode 100644 index 466e86b30..000000000 --- a/pkg/apiserver/rest/services/application_test.go +++ /dev/null @@ -1,272 +0,0 @@ -/* -Copyright 2021 The KubeVela Authors. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -package services - -import ( - "bytes" - "context" - "encoding/json" - "net/http" - "net/http/httptest" - "strings" - "testing" - - "github.com/labstack/echo/v4" - "github.com/stretchr/testify/assert" - apierrors "k8s.io/apimachinery/pkg/api/errors" - "k8s.io/apimachinery/pkg/runtime" - "sigs.k8s.io/controller-runtime/pkg/client" - "sigs.k8s.io/controller-runtime/pkg/client/fake" - - common2 "github.com/oam-dev/kubevela/apis/core.oam.dev/common" - "github.com/oam-dev/kubevela/apis/core.oam.dev/v1beta1" - "github.com/oam-dev/kubevela/pkg/apiserver/rest/apis" - "github.com/oam-dev/kubevela/pkg/utils/common" -) - -func TestApplicationCreateOrUpdate(t *testing.T) { - cw := fake.NewClientBuilder().WithScheme(common.Scheme).Build() - appSvc := NewApplicationService(cw) - - appComp1 := common2.ApplicationComponent{ - Name: "mycomp", - Type: "webservice", - Properties: runtime.RawExtension{Raw: []byte(`{"image":"nginx:v1"}`)}, - } - appComp2 := common2.ApplicationComponent{ - Name: "mycomp2", - Type: "webservice", - Properties: runtime.RawExtension{Raw: []byte(`{"image":"nginx:v2"}`)}, - } - tests := map[string]struct { - appReq *apis.ApplicationRequest - rawReq []byte - name string - namespace string - expHttpCode int - expErr string - expApp *v1beta1.Application - }{ - "normal create with only component": { - appReq: &apis.ApplicationRequest{ - Components: []common2.ApplicationComponent{appComp1}, - }, - expHttpCode: 200, - name: "myapp", - namespace: "mynamespace", - expApp: &v1beta1.Application{ - Spec: v1beta1.ApplicationSpec{ - Components: []common2.ApplicationComponent{appComp1}, - }, - }, - }, - "create with bind error": { - rawReq: []byte("XXXX"), - expHttpCode: 400, - name: "myapp", - namespace: "mynamespace", - expErr: "invalid request body: code=400", - }, - "normal update with component and trait": { - appReq: &apis.ApplicationRequest{ - Components: []common2.ApplicationComponent{appComp1, appComp2}, - }, - expHttpCode: 200, - name: "myapp", - namespace: "mynamespace", - expApp: &v1beta1.Application{ - Spec: v1beta1.ApplicationSpec{ - Components: []common2.ApplicationComponent{appComp1, appComp2}, - }, - }, - }, - } - for casename, c := range tests { - var err error - if c.appReq != nil { - c.rawReq, err = json.Marshal(c.appReq) - assert.NoError(t, err, casename) - } - req := httptest.NewRequest(http.MethodPost, "/", bytes.NewBuffer(c.rawReq)) - req.Header.Set(echo.HeaderContentType, echo.MIMEApplicationJSON) - rec := httptest.NewRecorder() - echoCtx := echo.New().NewContext(req, rec) - echoCtx.SetParamNames("namespace", "appname") - echoCtx.SetParamValues(c.namespace, c.name) - - err = appSvc.CreateOrUpdateApplication(echoCtx) - assert.NoError(t, err, casename) - - // check response - assert.Equal(t, c.expHttpCode, rec.Code, casename) - if c.expErr != "" { // compare return error with map type - gotResp := map[string]string{} - err = json.Unmarshal(rec.Body.Bytes(), &gotResp) - assert.NoError(t, err, casename) - assert.True(t, strings.Contains(gotResp["error"], c.expErr), casename) - } else { // check app spec in fake cluster - var appObj v1beta1.Application - err = cw.Get(context.TODO(), client.ObjectKey{Namespace: c.namespace, Name: c.name}, &appObj) - assert.NoError(t, err, casename) - assert.Equal(t, c.expApp.Spec, appObj.Spec, casename) - } - } -} - -func TestApplicationGet(t *testing.T) { - cw := fake.NewClientBuilder().WithScheme(common.Scheme).Build() - appSvc := NewApplicationService(cw) - - tests := map[string]struct { - rawReq []byte - name string - namespace string - expHttpCode int - expErr string - expApp *v1beta1.Application - }{ - "normal get method for application": { - expHttpCode: 200, - name: "commonName", - namespace: "commonNamespace", - }, - "get app failed with resource not found": { - expHttpCode: 404, - name: "notExistName", - namespace: "commonNamespace", - expErr: "application does not exist", - }, - } - // create an application for get - createAppForTest(t, appSvc) - - for casename, c := range tests { - var err error - req := httptest.NewRequest(http.MethodGet, "/", bytes.NewBuffer(c.rawReq)) - req.Header.Set(echo.HeaderContentType, echo.MIMEApplicationJSON) - rec := httptest.NewRecorder() - echoCtx := echo.New().NewContext(req, rec) - echoCtx.SetParamNames("namespace", "appname") - echoCtx.SetParamValues(c.namespace, c.name) - - err = appSvc.GetApplication(echoCtx) - assert.NoError(t, err, casename) - - // check response - assert.Equal(t, c.expHttpCode, rec.Code, casename) - if c.expErr != "" { // compare return error with map type - gotResp := map[string]string{} - err = json.Unmarshal(rec.Body.Bytes(), &gotResp) - assert.NoError(t, err, casename) - assert.True(t, strings.Contains(gotResp["error"], c.expErr), casename) - } else { // check app spec in fake cluster - var gotResp apis.ApplicationResponse - err = json.Unmarshal(rec.Body.Bytes(), &gotResp) - assert.NoError(t, err, casename) - - var appObj v1beta1.Application - err = cw.Get(context.TODO(), client.ObjectKey{Namespace: c.namespace, Name: c.name}, &appObj) - assert.NoError(t, err, casename) - assert.Equal(t, gotResp.APIVersion, appObj.APIVersion, casename) - assert.Equal(t, gotResp.Kind, appObj.Kind, casename) - assert.Equal(t, gotResp.Spec, appObj.Spec, casename) - assert.Equal(t, gotResp.Status, appObj.Status, casename) - } - - } -} - -func TestApplicationDelete(t *testing.T) { - cw := fake.NewClientBuilder().WithScheme(common.Scheme).Build() - appSvc := NewApplicationService(cw) - - tests := map[string]struct { - rawReq []byte - name string - namespace string - expHttpCode int - expErr string - }{ - "normal delete method for application": { - expHttpCode: 200, - name: "commonName", - namespace: "commonNamespace", - }, - "delete app failed with resource not found": { - expHttpCode: 404, - name: "notExistName", - namespace: "commonNamespace", - expErr: "application does not exist", - }, - } - for casename, c := range tests { - // create common app - createAppForTest(t, appSvc) - var err error - req := httptest.NewRequest(http.MethodDelete, "/", bytes.NewBuffer(c.rawReq)) - req.Header.Set(echo.HeaderContentType, echo.MIMEApplicationJSON) - rec := httptest.NewRecorder() - echoCtx := echo.New().NewContext(req, rec) - echoCtx.SetParamNames("namespace", "appname") - echoCtx.SetParamValues(c.namespace, c.name) - - err = appSvc.DeleteApplication(echoCtx) - assert.NoError(t, err, casename) - - // check response - assert.Equal(t, c.expHttpCode, rec.Code, casename) - if c.expErr != "" { - gotResp := map[string]string{} - err = json.Unmarshal(rec.Body.Bytes(), &gotResp) - assert.NoError(t, err, casename) - assert.True(t, strings.Contains(gotResp["error"], c.expErr), casename) - } else { - // checkout app status in fake cluster - var appObj v1beta1.Application - err = cw.Get(context.TODO(), client.ObjectKey{Namespace: c.namespace, Name: c.name}, &appObj) - assert.Equal(t, apierrors.IsNotFound(err), true) - } - } -} - -func createAppForTest(t *testing.T, appSvc *ApplicationService) { - appComp := common2.ApplicationComponent{ - Name: "mycomp", - Type: "webservice", - Properties: runtime.RawExtension{Raw: []byte(`{"image":"nginx:v1"}`)}, - } - - var appReq = &apis.ApplicationRequest{ - Components: []common2.ApplicationComponent{appComp}, - } - - var rawReq []byte - var err error - if appReq != nil { - rawReq, err = json.Marshal(appReq) - assert.NoError(t, err, "marshal request for create app") - } - req := httptest.NewRequest(http.MethodPost, "/", bytes.NewBuffer(rawReq)) - req.Header.Set(echo.HeaderContentType, echo.MIMEApplicationJSON) - rec := httptest.NewRecorder() - echoCtx := echo.New().NewContext(req, rec) - echoCtx.SetParamNames("namespace", "appname") - echoCtx.SetParamValues("commonNamespace", "commonName") - - err = appSvc.CreateOrUpdateApplication(echoCtx) - assert.NoError(t, err, "craete application in service") -} diff --git a/pkg/apiserver/rest/services/catalog.go b/pkg/apiserver/rest/services/catalog.go deleted file mode 100644 index 245ff9fc1..000000000 --- a/pkg/apiserver/rest/services/catalog.go +++ /dev/null @@ -1,223 +0,0 @@ -/* -Copyright 2021 The KubeVela Authors. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -package services - -import ( - "context" - "fmt" - "net/http" - "strconv" - "time" - - "github.com/labstack/echo/v4" - 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/oam-dev/kubevela/apis/types" - "github.com/oam-dev/kubevela/pkg/apiserver/model" - "github.com/oam-dev/kubevela/pkg/apiserver/rest/apis" -) - -// CatalogService catalog service -type CatalogService struct { - k8sClient client.Client -} - -// NewCatalogService new catalog service -func NewCatalogService(kc client.Client) *CatalogService { - - return &CatalogService{ - k8sClient: kc, - } -} - -// ListCatalogs list method for catalog configmap -func (s *CatalogService) ListCatalogs(c echo.Context) error { - var cmList corev1.ConfigMapList - labels := &metav1.LabelSelector{ - MatchLabels: map[string]string{ - "catalog": "configdata", - }, - } - selector, err := metav1.LabelSelectorAsSelector(labels) - if err != nil { - return err - } - err = s.k8sClient.List(context.Background(), &cmList, &client.ListOptions{ - LabelSelector: selector, - }) - if err != nil { - return err - } - var catalogList = make([]*model.Catalog, 0, len(cmList.Items)) - for i, c := range cmList.Items { - UpdateInt, err := strconv.ParseInt(cmList.Items[i].Data["UpdatedAt"], 10, 64) - if err != nil { - return err - } - catalog := model.Catalog{ - Name: c.Name, - UpdatedAt: UpdateInt, - Desc: cmList.Items[i].Data["Desc"], - Type: cmList.Items[i].Data["Type"], - URL: cmList.Items[i].Data["Url"], - Token: cmList.Items[i].Data["Token"], - } - catalogList = append(catalogList, &catalog) - } - - return c.JSON(http.StatusOK, apis.CatalogListResponse{Catalogs: catalogList}) -} - -// GetCatalog get method for catalog configmap -func (s *CatalogService) GetCatalog(c echo.Context) error { - catalogName := c.Param("catalogName") - - var cm corev1.ConfigMap - err := s.k8sClient.Get(context.Background(), client.ObjectKey{Namespace: types.DefaultKubeVelaNS, Name: catalogName}, &cm) - if err != nil { - return c.JSON(http.StatusInternalServerError, fmt.Sprintf("get configMap for %s failed %s", catalogName, err.Error())) - } - UpdatedInt, err := strconv.ParseInt(cm.Data["UpdatedAt"], 10, 64) - if err != nil { - return c.JSON(http.StatusInternalServerError, fmt.Errorf("unable to resolve update parameter in %s: %w ", catalogName, err)) - } - var catalog = model.Catalog{ - Name: catalogName, - Desc: cm.Data["Desc"], - UpdatedAt: UpdatedInt, - Type: cm.Data["Type"], - URL: cm.Data["Url"], - Token: cm.Data["Token"], - } - return c.JSON(http.StatusOK, apis.CatalogResponse{Catalog: &catalog}) -} - -// AddCatalog add method for catalog configmap -func (s *CatalogService) AddCatalog(c echo.Context) error { - catalogReq := new(apis.CatalogRequest) - if err := c.Bind(catalogReq); err != nil { - return err - } - exist, err := s.checkCatalogExist(catalogReq.Name) - if err != nil { - return err - } - if exist { - return c.JSON(http.StatusInternalServerError, fmt.Sprintf("catalog %s exist", catalogReq.Name)) - } - - var cm *corev1.ConfigMap - configdata := map[string]string{ - "Name": catalogReq.Name, - "Desc": catalogReq.Desc, - "UpdatedAt": fmt.Sprintf("%d", time.Now().UnixNano()), - } - - label := map[string]string{ - "catalog": "configdata", - } - cm = toConfigMap(catalogReq.Name, types.DefaultKubeVelaNS, label, configdata) - err = s.k8sClient.Create(context.Background(), cm) - if err != nil { - return fmt.Errorf("unable to create configmap for %s : %w ", catalogReq.Name, err) - } - catalog := convertToCatalog(catalogReq) - return c.JSON(http.StatusCreated, apis.CatalogResponse{Catalog: &catalog}) -} - -// UpdateCatalog update method for catalog configmap -func (s *CatalogService) UpdateCatalog(c echo.Context) error { - catalogReq := new(apis.CatalogRequest) - if err := c.Bind(catalogReq); err != nil { - return err - } - catalog := convertToCatalog(catalogReq) - configdata := map[string]string{ - "Name": catalogReq.Name, - "Desc": catalogReq.Desc, - "UpdatedAt": time.Now().String(), - } - - label := map[string]string{ - "catalog": "configdata", - } - cm := toConfigMap(catalogReq.Name, types.DefaultKubeVelaNS, label, configdata) - err := s.k8sClient.Update(context.Background(), cm) - if err != nil { - return fmt.Errorf("unable to update configmap for %s : %w ", catalogReq.Name, err) - } - - return c.JSON(http.StatusOK, apis.CatalogResponse{Catalog: &catalog}) -} - -// DelCatalog delete method for catalog configmap -func (s *CatalogService) DelCatalog(c echo.Context) error { - catalogName := c.Param("catalogName") - - var cm corev1.ConfigMap - cm.SetName(catalogName) - cm.SetNamespace(types.DefaultKubeVelaNS) - if err := s.k8sClient.Delete(context.Background(), &cm); err != nil { - return c.JSON(http.StatusInternalServerError, false) - } - - return c.JSON(http.StatusOK, true) -} - -// checkCatalogExist check whether catalog exist with name -func (s *CatalogService) checkCatalogExist(catalogName string) (bool, error) { - var cm corev1.ConfigMap - err := s.k8sClient.Get(context.Background(), client.ObjectKey{Namespace: types.DefaultKubeVelaNS, Name: catalogName}, &cm) - if err != nil { - if apierrors.IsNotFound(err) { // not found - return false, nil - } - // other error - return false, err - } - // found - return true, nil -} - -// convertToCatalog get catalog model from request -func convertToCatalog(catalogReq *apis.CatalogRequest) model.Catalog { - return model.Catalog{ - Name: catalogReq.Name, - Desc: catalogReq.Desc, - UpdatedAt: time.Now().Unix(), - Type: catalogReq.Type, - URL: catalogReq.URL, - Token: catalogReq.Token, - } -} - -func toConfigMap(name, namespace string, label map[string]string, configData map[string]string) *corev1.ConfigMap { - var cm = corev1.ConfigMap{ - TypeMeta: metav1.TypeMeta{ - APIVersion: "v1", - Kind: "ConfigMap", - }, - } - cm.SetName(name) - cm.SetNamespace(namespace) - cm.SetLabels(label) - cm.Data = configData - return &cm -} diff --git a/pkg/apiserver/rest/services/catalog_test.go b/pkg/apiserver/rest/services/catalog_test.go deleted file mode 100644 index e2dcd1809..000000000 --- a/pkg/apiserver/rest/services/catalog_test.go +++ /dev/null @@ -1,80 +0,0 @@ -/* -Copyright 2021 The KubeVela Authors. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -package services - -import ( - "bytes" - "encoding/json" - "net/http" - "net/http/httptest" - - "github.com/labstack/echo/v4" - . "github.com/onsi/ginkgo" - . "github.com/onsi/gomega" - - "github.com/oam-dev/kubevela/pkg/apiserver/rest/apis" -) - -var _ = Describe("Test Catalog Service", func() { - - var catalogService *CatalogService - - BeforeEach(func() { - catalogService = NewCatalogService(k8sClient) - }) - - AfterEach(func() { - }) - - It("should add catalog successfully", func() { - e := echo.New() - cr := &apis.CatalogRequest{ - Name: "test", - } - b, err := json.Marshal(cr) - Expect(err).To(BeNil()) - req := httptest.NewRequest(http.MethodPost, "/", bytes.NewBuffer(b)) - req.Header.Set(echo.HeaderContentType, echo.MIMEApplicationJSON) - rec := httptest.NewRecorder() - c := e.NewContext(req, rec) - - Expect(catalogService.AddCatalog(c)).To(BeNil()) - checkCatalogResponse(rec, cr, http.StatusCreated) - - req = httptest.NewRequest(http.MethodGet, "/", nil) - req.Header.Set(echo.HeaderContentType, echo.MIMEApplicationJSON) - rec = httptest.NewRecorder() - c = e.NewContext(req, rec) - c.SetPath("/v1/catalogs/:catalogName") - c.SetParamNames("catalogName") - c.SetParamValues(cr.Name) - - Expect(catalogService.GetCatalog(c)).To(BeNil()) - checkCatalogResponse(rec, cr, http.StatusOK) - }) -}) - -func checkCatalogResponse(rec *httptest.ResponseRecorder, cr *apis.CatalogRequest, httpcode int) { - Expect(rec.Code).To(Equal(httpcode)) - - get := &apis.CatalogResponse{} - err := json.Unmarshal(rec.Body.Bytes(), get) - Expect(err).To(BeNil()) - - Expect(get.Catalog.Name).To(Equal(cr.Name)) - Expect(get.Catalog.UpdatedAt).NotTo(BeZero()) -} diff --git a/pkg/apiserver/rest/services/suite_test.go b/pkg/apiserver/rest/services/suite_test.go deleted file mode 100644 index 606280adc..000000000 --- a/pkg/apiserver/rest/services/suite_test.go +++ /dev/null @@ -1,110 +0,0 @@ -/* -Copyright 2021 The KubeVela Authors. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -package services - -import ( - "context" - "math/rand" - "os" - "path/filepath" - "testing" - "time" - - . "github.com/onsi/ginkgo" - . "github.com/onsi/gomega" - corev1 "k8s.io/api/core/v1" - crdv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/apimachinery/pkg/runtime" - clientgoscheme "k8s.io/client-go/kubernetes/scheme" - "k8s.io/client-go/rest" - "k8s.io/utils/pointer" - "sigs.k8s.io/controller-runtime/pkg/client" - "sigs.k8s.io/controller-runtime/pkg/envtest" - logf "sigs.k8s.io/controller-runtime/pkg/log" - "sigs.k8s.io/controller-runtime/pkg/log/zap" - - oamcorealpha "github.com/oam-dev/kubevela/apis/core.oam.dev/v1alpha2" - oamcore "github.com/oam-dev/kubevela/apis/core.oam.dev/v1beta1" - oamstandard "github.com/oam-dev/kubevela/apis/standard.oam.dev/v1alpha1" - // +kubebuilder:scaffold:imports -) - -// These tests use Ginkgo (BDD-style Go testing framework). Refer to -// http://onsi.github.io/ginkgo/ to learn more about Ginkgo. -var cfg *rest.Config -var k8sClient client.Client -var testEnv *envtest.Environment -var testScheme = runtime.NewScheme() - -func TestAPIs(t *testing.T) { - RegisterFailHandler(Fail) - RunSpecs(t, "APIServer Suite") -} - -var _ = BeforeSuite(func() { - logf.SetLogger(zap.New(zap.UseDevMode(true), zap.WriteTo(GinkgoWriter))) - rand.Seed(time.Now().UnixNano()) - By("bootstrapping test environment") - var yamlPath string - if _, set := os.LookupEnv("COMPATIBILITY_TEST"); set { - yamlPath = "../../../../../test/compatibility-test/testdata" - } else { - yamlPath = filepath.Join("../../../../..", "charts", "vela-core", "crds") - } - logf.Log.Info("start application suit test", "yaml_path", yamlPath) - testEnv = &envtest.Environment{ - ControlPlaneStartTimeout: time.Minute, - ControlPlaneStopTimeout: time.Minute, - UseExistingCluster: pointer.BoolPtr(false), - CRDDirectoryPaths: []string{yamlPath, "./testdata/crds/terraform.core.oam.dev_configurations.yaml"}, - } - - var err error - cfg, err = testEnv.Start() - Expect(err).ToNot(HaveOccurred()) - Expect(cfg).ToNot(BeNil()) - - err = oamcorealpha.SchemeBuilder.AddToScheme(testScheme) - Expect(err).NotTo(HaveOccurred()) - - err = oamstandard.SchemeBuilder.AddToScheme(testScheme) - Expect(err).NotTo(HaveOccurred()) - - err = oamcore.SchemeBuilder.AddToScheme(testScheme) - Expect(err).NotTo(HaveOccurred()) - - err = clientgoscheme.AddToScheme(testScheme) - Expect(err).NotTo(HaveOccurred()) - - crdv1.AddToScheme(testScheme) - - // +kubebuilder:scaffold:scheme - k8sClient, err = client.New(cfg, client.Options{Scheme: testScheme}) - Expect(err).ToNot(HaveOccurred()) - Expect(k8sClient).ToNot(BeNil()) - - definitonNs := corev1.Namespace{ObjectMeta: metav1.ObjectMeta{Name: "vela-system"}} - Expect(k8sClient.Create(context.Background(), definitonNs.DeepCopy())).Should(BeNil()) - -}) - -var _ = AfterSuite(func() { - By("tearing down the test environment") - err := testEnv.Stop() - Expect(err).ToNot(HaveOccurred()) -}) diff --git a/pkg/apiserver/rest/usecase/cluster_usecase.go b/pkg/apiserver/rest/usecase/cluster_usecase.go new file mode 100644 index 000000000..7c54406c5 --- /dev/null +++ b/pkg/apiserver/rest/usecase/cluster_usecase.go @@ -0,0 +1,42 @@ +/* +Copyright 2021 The KubeVela Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package usecase + +import ( + "context" + + "github.com/oam-dev/kubevela/pkg/apiserver/datastore" + apis "github.com/oam-dev/kubevela/pkg/apiserver/rest/apis/v1" +) + +// ClusterUsecase cluster manage +type ClusterUsecase interface { + CreateKubeCluster(context.Context, apis.CreateClusterRequest) (*apis.ClusterBase, error) +} + +type clusterUsecaseImpl struct { + ds datastore.DataStore +} + +// NewClusterUsecase new cluster usecase +func NewClusterUsecase(ds datastore.DataStore) ClusterUsecase { + return &clusterUsecaseImpl{ds: ds} +} + +func (c *clusterUsecaseImpl) CreateKubeCluster(context.Context, apis.CreateClusterRequest) (*apis.ClusterBase, error) { + return nil, nil +} diff --git a/pkg/apiserver/rest/utils/bcode/bcode.go b/pkg/apiserver/rest/utils/bcode/bcode.go new file mode 100644 index 000000000..08495e248 --- /dev/null +++ b/pkg/apiserver/rest/utils/bcode/bcode.go @@ -0,0 +1,67 @@ +/* +Copyright 2021 The KubeVela Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package bcode + +import ( + "errors" + "fmt" + + restful "github.com/emicklei/go-restful/v3" + "github.com/go-playground/validator/v10" + + "github.com/oam-dev/kubevela/pkg/apiserver/log" +) + +// Bcode business error code +type Bcode struct { + HTTPCode int32 `json:"-"` + BusinessCode int32 + Message string +} + +func (b *Bcode) Error() string { + return fmt.Sprintf("HTTPCode:%d BusinessCode:%d Message:%s", b.HTTPCode, b.BusinessCode, b.Message) +} + +// ReturnError Unified handling of all types of errors, generating a standard return structure. +func ReturnError(req *restful.Request, res *restful.Response, err error) { + var bcode *Bcode + if errors.As(err, &bcode) { + if err := res.WriteEntity(err); err != nil { + log.Logger.Error("write entity failure %s", err.Error()) + } + return + } + var restfulerr *restful.ServiceError + if errors.As(err, restfulerr) { + if err := res.WriteEntity(Bcode{HTTPCode: int32(restfulerr.Code), BusinessCode: int32(restfulerr.Code), Message: restfulerr.Message}); err != nil { + log.Logger.Error("write entity failure %s", err.Error()) + } + return + } + var validErr *validator.ValidationErrors + if errors.As(err, validErr) { + if err := res.WriteEntity(Bcode{HTTPCode: 400, BusinessCode: 400, Message: err.Error()}); err != nil { + log.Logger.Error("write entity failure %s", err.Error()) + } + return + } + log.Logger.Errorf("Business exceptions, message %s, path:%s method:%s", err.Error(), req.Request.URL, req.Request.Method) + if err := res.WriteEntity(Bcode{HTTPCode: 500, BusinessCode: 500, Message: err.Error()}); err != nil { + log.Logger.Error("write entity failure %s", err.Error()) + } +} diff --git a/pkg/apiserver/rest/webservice/application.go b/pkg/apiserver/rest/webservice/application.go new file mode 100644 index 000000000..e768e9180 --- /dev/null +++ b/pkg/apiserver/rest/webservice/application.go @@ -0,0 +1,90 @@ +/* +Copyright 2021 The KubeVela Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package webservice + +import ( + restfulspec "github.com/emicklei/go-restful-openapi/v2" + restful "github.com/emicklei/go-restful/v3" + + apis "github.com/oam-dev/kubevela/pkg/apiserver/rest/apis/v1" +) + +type applicationWebService struct { +} + +func (c *applicationWebService) GetWebService() *restful.WebService { + ws := new(restful.WebService) + ws.Path(versionPrefix+"/applications"). + Consumes(restful.MIME_XML, restful.MIME_JSON). + Produces(restful.MIME_JSON, restful.MIME_XML). + Doc("api for application manage") + + tags := []string{"application"} + + ws.Route(ws.GET("/").To(noop). + Doc("list all applications"). + Metadata(restfulspec.KeyOpenAPITags, tags). + Param(ws.QueryParameter("query", "Fuzzy search based on name or description").DataType("string")). + Param(ws.QueryParameter("namespace", "Namespace-based search").DataType("string")). + Param(ws.QueryParameter("cluster", "Cluster-based search").DataType("string")). + Writes(apis.ListApplicationResponse{})) + + ws.Route(ws.POST("/").To(noop). + Doc("create one application"). + Metadata(restfulspec.KeyOpenAPITags, tags). + Reads(apis.CreateApplicationRequest{}). + Writes(apis.ApplicationBase{})) + + ws.Route(ws.DELETE("/{name}").To(noop). + Doc("delete one application"). + Metadata(restfulspec.KeyOpenAPITags, tags). + Param(ws.PathParameter("name", "identifier of the application").DataType("string")). + Writes(apis.ApplicationBase{})) + + ws.Route(ws.GET("/{name}").To(noop). + Doc("detail one application"). + Metadata(restfulspec.KeyOpenAPITags, tags). + Param(ws.PathParameter("name", "identifier of the application").DataType("string")). + Writes(apis.DetailApplicationResponse{})) + + ws.Route(ws.POST("/{name}/template").To(noop). + Doc("create one application template"). + Metadata(restfulspec.KeyOpenAPITags, tags). + Param(ws.PathParameter("name", "identifier of the application").DataType("string")). + Reads(apis.CreateApplicationTemplateRequest{}). + Writes(apis.ApplicationTemplateBase{})) + + ws.Route(ws.POST("/{name}/deploy").To(noop). + Doc("deploy or update the application"). + Metadata(restfulspec.KeyOpenAPITags, tags). + Param(ws.PathParameter("name", "identifier of the application").DataType("string")). + Writes(apis.ApplicationBase{})) + + ws.Route(ws.GET("/{name}/components").To(noop). + Doc("gets the component topology of the application"). + Param(ws.PathParameter("name", "identifier of the application").DataType("string")). + Metadata(restfulspec.KeyOpenAPITags, tags). + Writes(apis.ComponentListResponse{})) + + ws.Route(ws.POST("/{name}/components").To(noop). + Doc("create component for application"). + Param(ws.PathParameter("name", "identifier of the application").DataType("string")). + Metadata(restfulspec.KeyOpenAPITags, tags). + Reads(apis.CreateComponentRequest{}). + Writes(apis.ComponentBase{})) + return ws +} diff --git a/pkg/apiserver/rest/webservice/catalog.go b/pkg/apiserver/rest/webservice/catalog.go new file mode 100644 index 000000000..aaa8a697d --- /dev/null +++ b/pkg/apiserver/rest/webservice/catalog.go @@ -0,0 +1,45 @@ +/* +Copyright 2021 The KubeVela Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package webservice + +import ( + restfulspec "github.com/emicklei/go-restful-openapi/v2" + restful "github.com/emicklei/go-restful/v3" + + apis "github.com/oam-dev/kubevela/pkg/apiserver/rest/apis/v1" +) + +type catalogWebService struct { +} + +func (c *catalogWebService) GetWebService() *restful.WebService { + ws := new(restful.WebService) + ws.Path("/v1/catalogs"). + Consumes(restful.MIME_XML, restful.MIME_JSON). + Produces(restful.MIME_JSON, restful.MIME_XML). + Doc("api for cluster manage") + + tags := []string{"cluster"} + + ws.Route(ws.GET("/").To(noop). + Doc("list all clusters"). + Metadata(restfulspec.KeyOpenAPITags, tags). + Param(ws.QueryParameter("query", "Fuzzy search based on name or description").DataType("string")). + Writes(apis.ListClusterResponse{}).Do(returns200, returns500)) + + return ws +} diff --git a/pkg/apiserver/rest/webservice/cluster.go b/pkg/apiserver/rest/webservice/cluster.go new file mode 100644 index 000000000..9ff430740 --- /dev/null +++ b/pkg/apiserver/rest/webservice/cluster.go @@ -0,0 +1,97 @@ +/* +Copyright 2021 The KubeVela Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package webservice + +import ( + restfulspec "github.com/emicklei/go-restful-openapi/v2" + restful "github.com/emicklei/go-restful/v3" + + apis "github.com/oam-dev/kubevela/pkg/apiserver/rest/apis/v1" + "github.com/oam-dev/kubevela/pkg/apiserver/rest/usecase" + "github.com/oam-dev/kubevela/pkg/apiserver/rest/utils/bcode" +) + +type clusterWebService struct { + clusterUsecase usecase.ClusterUsecase +} + +func (c *clusterWebService) GetWebService() *restful.WebService { + ws := new(restful.WebService) + ws.Path(versionPrefix+"/clusters"). + Consumes(restful.MIME_XML, restful.MIME_JSON). + Produces(restful.MIME_JSON, restful.MIME_XML). + Doc("api for cluster manage") + + tags := []string{"cluster"} + + ws.Route(ws.GET("/").To(noop). + Doc("list all clusters"). + Metadata(restfulspec.KeyOpenAPITags, tags). + Param(ws.QueryParameter("query", "Fuzzy search based on name or description").DataType("string")). + Writes(apis.ListClusterResponse{}).Do(returns200, returns500)) + + ws.Route(ws.POST("/").To(c.createKubeCluster). + Doc("create cluster"). + Metadata(restfulspec.KeyOpenAPITags, tags). + Reads(&apis.CreateClusterRequest{}). + Writes(apis.ClusterBase{})) + + ws.Route(ws.GET("/{clusterName}").To(noop). + Doc("detail cluster info"). + Metadata(restfulspec.KeyOpenAPITags, tags). + Param(ws.PathParameter("clusterName", "identifier of the cluster").DataType("string")). + Writes(apis.DetailClusterResponse{})) + + // Do not implement this dimension for now. + // ws.Route(ws.GET("/{clusterName}/addons").To(noop). + // Doc("list cluster addons info"). + // Metadata(restfulspec.KeyOpenAPITags, tags). + // Param(ws.PathParameter("clusterName", "identifier of the cluster").DataType("string")). + // Writes(apis.ListClusterAddonResponse{})) + + // ws.Route(ws.POST("/{clusterName}/addons").To(noop). + // Doc("add addon for the cluster"). + // Metadata(restfulspec.KeyOpenAPITags, tags). + // Param(ws.PathParameter("clusterName", "identifier of the cluster").DataType("string")). + // Writes(apis.DeatilClusterAddonResponse{}).Returns(200, "", apis.DeatilClusterAddonResponse{})) + return ws +} + +func (c *clusterWebService) createKubeCluster(req *restful.Request, res *restful.Response) { + // Verify the validity of parameters + var createReq apis.CreateClusterRequest + if err := req.ReadEntity(&createReq); err != nil { + bcode.ReturnError(req, res, err) + return + } + if err := validate.Struct(&createReq); err != nil { + bcode.ReturnError(req, res, err) + return + } + // Call the usecase layer code + clusterBase, err := c.clusterUsecase.CreateKubeCluster(req.Request.Context(), createReq) + if err != nil { + bcode.ReturnError(req, res, err) + return + } + + // Write back response data + if err := res.WriteEntity(clusterBase); err != nil { + bcode.ReturnError(req, res, err) + return + } +} diff --git a/pkg/apiserver/rest/webservice/component_definition.go b/pkg/apiserver/rest/webservice/component_definition.go new file mode 100644 index 000000000..f26d922ea --- /dev/null +++ b/pkg/apiserver/rest/webservice/component_definition.go @@ -0,0 +1,45 @@ +/* +Copyright 2021 The KubeVela Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package webservice + +import ( + restfulspec "github.com/emicklei/go-restful-openapi/v2" + restful "github.com/emicklei/go-restful/v3" + + apis "github.com/oam-dev/kubevela/pkg/apiserver/rest/apis/v1" +) + +type componentDefinitionWebservice struct { +} + +func (c *componentDefinitionWebservice) GetWebService() *restful.WebService { + ws := new(restful.WebService) + ws.Path(versionPrefix+"/componentdefinitions"). + Consumes(restful.MIME_XML, restful.MIME_JSON). + Produces(restful.MIME_JSON, restful.MIME_XML). + Doc("api for componentdefinition manage") + + tags := []string{"componentdefinition"} + + ws.Route(ws.GET("/").To(noop). + Doc("list all componentdefinition"). + Metadata(restfulspec.KeyOpenAPITags, tags). + Param(ws.QueryParameter("appName", "if specified, query the componentdefinition supported by the cluster where the application resides.").DataType("string")). + Param(ws.QueryParameter("clusterName", "if specified, query the componentdefinition supported by the cluster.").DataType("string")). + Writes(apis.ListComponentDefinitionResponse{})) + return ws +} diff --git a/pkg/apiserver/rest/webservice/namespace.go b/pkg/apiserver/rest/webservice/namespace.go new file mode 100644 index 000000000..31af705dc --- /dev/null +++ b/pkg/apiserver/rest/webservice/namespace.go @@ -0,0 +1,76 @@ +/* +Copyright 2021 The KubeVela Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package webservice + +import ( + restfulspec "github.com/emicklei/go-restful-openapi/v2" + restful "github.com/emicklei/go-restful/v3" + + apis "github.com/oam-dev/kubevela/pkg/apiserver/rest/apis/v1" +) + +type namespaceWebService struct { +} + +func (c *namespaceWebService) GetWebService() *restful.WebService { + ws := new(restful.WebService) + ws.Path(versionPrefix+"/namespaces"). + Consumes(restful.MIME_XML, restful.MIME_JSON). + Produces(restful.MIME_JSON, restful.MIME_XML). + Doc("api for namespace manage") + + tags := []string{"namespace"} + + ws.Route(ws.GET("/").To(noop). + Doc("list all namespaces"). + Metadata(restfulspec.KeyOpenAPITags, tags). + Writes(apis.ListNamespaceResponse{})) + + ws.Route(ws.POST("/").To(noop). + Doc("create namespace"). + Metadata(restfulspec.KeyOpenAPITags, tags). + Reads(apis.CreateNamespaceRequest{}). + Writes(apis.NamesapceDetailResponse{})) + + ws.Route(ws.GET("/{namespace}").To(noop). + Doc("get one namespace"). + Metadata(restfulspec.KeyOpenAPITags, tags). + Param(ws.PathParameter("namespace", "identifier of the namespace").DataType("string")). + Writes(apis.NamesapceDetailResponse{})) + + // Compatible with historical apis + ws.Route(ws.GET("/{namespace}/applications/:appname").To(noop). + Doc("get the specified oam application in the specified namespace"). + Metadata(restfulspec.KeyOpenAPITags, tags). + Param(ws.PathParameter("namespace", "identifier of the namespace").DataType("string")). + Param(ws.PathParameter("appname", "identifier of the oam application").DataType("string")). + Writes(apis.ApplicationResponse{})) + + ws.Route(ws.POST("/{namespace}/applications/:appname").To(noop). + Doc("create or update oam application in the specified namespace"). + Metadata(restfulspec.KeyOpenAPITags, tags). + Param(ws.PathParameter("namespace", "identifier of the namespace").DataType("string")). + Param(ws.PathParameter("appname", "identifier of the oam application").DataType("string")). + Reads(apis.ApplicationRequest{})) + + ws.Route(ws.DELETE("/{namespace}/applications/:appname").To(noop). + Doc("create or update oam application in the specified namespace"). + Metadata(restfulspec.KeyOpenAPITags, tags). + Param(ws.PathParameter("namespace", "identifier of the namespace").DataType("string")). + Param(ws.PathParameter("appname", "identifier of the oam application").DataType("string"))) + return ws +} diff --git a/pkg/apiserver/rest/webservice/oam_application.go b/pkg/apiserver/rest/webservice/oam_application.go new file mode 100644 index 000000000..7e19b3648 --- /dev/null +++ b/pkg/apiserver/rest/webservice/oam_application.go @@ -0,0 +1,58 @@ +/* +Copyright 2021 The KubeVela Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package webservice + +import ( + restfulspec "github.com/emicklei/go-restful-openapi/v2" + restful "github.com/emicklei/go-restful/v3" + + apis "github.com/oam-dev/kubevela/pkg/apiserver/rest/apis/v1" +) + +type oamApplicationWebService struct { +} + +func (c *oamApplicationWebService) GetWebService() *restful.WebService { + ws := new(restful.WebService) + ws.Path("/v1"). + Consumes(restful.MIME_XML, restful.MIME_JSON). + Produces(restful.MIME_JSON, restful.MIME_XML). + Doc("api for oam application manage") + + tags := []string{"oam"} + + ws.Route(ws.GET("/{namespace}/applications/:appname").To(noop). + Doc("get the specified oam application in the specified namespace"). + Metadata(restfulspec.KeyOpenAPITags, tags). + Param(ws.PathParameter("namespace", "identifier of the namespace").DataType("string")). + Param(ws.PathParameter("appname", "identifier of the oam application").DataType("string")). + Writes(apis.ApplicationResponse{})) + + ws.Route(ws.POST("/{namespace}/applications/{appname}").To(noop). + Doc("create or update oam application in the specified namespace"). + Metadata(restfulspec.KeyOpenAPITags, tags). + Param(ws.PathParameter("namespace", "identifier of the namespace").DataType("string")). + Param(ws.PathParameter("appname", "identifier of the oam application").DataType("string")). + Reads(apis.ApplicationRequest{})) + + ws.Route(ws.DELETE("/{namespace}/applications/:appname").To(noop). + Doc("create or update oam application in the specified namespace"). + Metadata(restfulspec.KeyOpenAPITags, tags). + Param(ws.PathParameter("namespace", "identifier of the namespace").DataType("string")). + Param(ws.PathParameter("appname", "identifier of the oam application").DataType("string"))) + return ws +} diff --git a/pkg/apiserver/rest/webservice/webservice.go b/pkg/apiserver/rest/webservice/webservice.go new file mode 100644 index 000000000..ed8fb5c6a --- /dev/null +++ b/pkg/apiserver/rest/webservice/webservice.go @@ -0,0 +1,67 @@ +/* +Copyright 2021 The KubeVela Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package webservice + +import ( + "context" + "net/http" + + restful "github.com/emicklei/go-restful/v3" + "github.com/go-playground/validator/v10" +) + +// versionPrefix API version prefix. +var versionPrefix = "/api/v1" + +var validate = validator.New() + +// WebService webservice interface +type WebService interface { + GetWebService() *restful.WebService +} + +var registedWebService []WebService + +// RegistWebService regist webservice +func RegistWebService(ws WebService) { + registedWebService = append(registedWebService, ws) +} + +// GetRegistedWebService return registedWebService +func GetRegistedWebService() []WebService { + return registedWebService +} + +func noop(req *restful.Request, resp *restful.Response) {} + +func returns200(b *restful.RouteBuilder) { + b.Returns(http.StatusOK, "OK", map[string]string{"status": "ok"}) +} + +func returns500(b *restful.RouteBuilder) { + b.Returns(http.StatusInternalServerError, "Bummer, something went wrong", nil) +} + +// Init init all webservice, pass in the required parameter object. +func Init(ctx context.Context) { + RegistWebService(&clusterWebService{}) + RegistWebService(&applicationWebService{}) + RegistWebService(&namespaceWebService{}) + RegistWebService(&componentDefinitionWebservice{}) + RegistWebService(&catalogWebService{}) + RegistWebService(&oamApplicationWebService{}) +}