diff --git a/.gitignore b/.gitignore index 0fba1d2..1f2c8d7 100644 --- a/.gitignore +++ b/.gitignore @@ -35,4 +35,8 @@ config.yml config.yaml .scannerwork report.xml -coverage.xml \ No newline at end of file +coverage.xml + +# dont include vendor directory +/vendor/* +/otpt/* diff --git a/Gopkg.lock b/Gopkg.lock index a73a8bf..3f09e16 100644 --- a/Gopkg.lock +++ b/Gopkg.lock @@ -12,6 +12,14 @@ pruneopts = "UT" revision = "8c0e79dc5ff70fb2919f0d662f44fd72ccdf9be2" +[[projects]] + digest = "1:9afc639ef88d907f2e87ab68cbc63117b88d0d84238fd6b08224515d00a8136a" + name = "github.com/alecthomas/gometalinter" + packages = ["."] + pruneopts = "UT" + revision = "df395bfa67c5d0630d936c0044cf07ff05086655" + version = "v3.0.0" + [[projects]] branch = "master" digest = "1:315c5f2f60c76d89b871c73f9bd5fe689cad96597afd50fb9992228ef80bdd34" @@ -24,7 +32,15 @@ revision = "a0175ee3bccc567396460bf5acd36800cb10c49c" [[projects]] - digest = "1:7f93d2813efdc7396f4fc488e7f2f93ceb666974e36718e0f16a0f63e0f8228b" + branch = "master" + digest = "1:272de46b796b453a750d723a6ba064c2c1e1b579d9f1228dfab743f4dae3191d" + name = "github.com/alecthomas/units" + packages = ["."] + pruneopts = "UT" + revision = "c3de453c63f4bdb4dadffab9805ec00426c505f7" + +[[projects]] + digest = "1:b3eb10ccc087bb783f92288b96880c1c7384d0c26cfdaff7794e2387be7b7a5c" name = "github.com/aws/aws-sdk-go" packages = [ "aws", @@ -50,11 +66,17 @@ "internal/shareddefaults", "private/protocol", "private/protocol/ec2query", + "private/protocol/eventstream", + "private/protocol/eventstream/eventstreamapi", "private/protocol/query", "private/protocol/query/queryutil", "private/protocol/rest", + "private/protocol/restxml", "private/protocol/xml/xmlutil", "service/ec2", + "service/s3", + "service/s3/s3iface", + "service/s3/s3manager", "service/sts", ] pruneopts = "UT" @@ -69,6 +91,25 @@ revision = "2ee87856327ba09384cabd113bc6b5d174e9ec0f" version = "v3.5.1" +[[projects]] + branch = "master" + digest = "1:454adc7f974228ff789428b6dc098638c57a64aa0718f0bd61e53d3cd39d7a75" + name = "github.com/chzyer/readline" + packages = ["."] + pruneopts = "UT" + revision = "2972be24d48e78746da79ba8e24e8b488c9880de" + +[[projects]] + digest = "1:848ef40f818e59905140552cc49ff3dc1a15f955e4b56d1c5c2cc4b54dbadf0c" + name = "github.com/client9/misspell" + packages = [ + ".", + "cmd/misspell", + ] + pruneopts = "UT" + revision = "b90dc15cfd220ecf8bbc9043ecb928cef381f011" + version = "v0.3.4" + [[projects]] branch = "master" digest = "1:e2655cbc97a3a48d0efedf27fe75191b4946aa92ce03acd37e76458b218f9df1" @@ -93,6 +134,41 @@ revision = "7b294651033cd7d9e7f0d9ffa1b75ed1e198e737" version = "v1.38.3" +[[projects]] + branch = "travis-1.9" + digest = "1:e8f5d9c09a7209c740e769713376abda388c41b777ba8e9ed52767e21acf379f" + name = "github.com/golang/lint" + packages = [ + ".", + "golint", + ] + pruneopts = "UT" + revision = "883fe33ffc4344bad1ecd881f61afd5ec5d80e0a" + +[[projects]] + branch = "master" + digest = "1:473e7c5cbc58c538bce66e0ca2a033d31a3e7d9c6d5d96c6df1657b588bddac3" + name = "github.com/google/shlex" + packages = ["."] + pruneopts = "UT" + revision = "c34317bd91bf98fab745d77b03933cf8769299fe" + +[[projects]] + branch = "master" + digest = "1:00d592bdacdeb0412b09f8eb2f98c6cc04470c4b72150f20733511e0821225c1" + name = "github.com/gordonklaus/ineffassign" + packages = ["."] + pruneopts = "UT" + revision = "ed7b1b5ee0f816bbc0ff35bf7c6fdb4f53b6c59a" + +[[projects]] + digest = "1:cbec35fe4d5a4fba369a656a8cd65e244ea2c743007d8f6c1ccb132acf9d1296" + name = "github.com/gorilla/mux" + packages = ["."] + pruneopts = "UT" + revision = "00bdffe0f3c77e27d2cf6f5c70232a2d3e4d9c15" + version = "v1.7.3" + [[projects]] digest = "1:c0d19ab64b32ce9fe5cf4ddceba78d5bc9807f0016db6b1183599da3dcc24d10" name = "github.com/hashicorp/hcl" @@ -127,6 +203,25 @@ pruneopts = "UT" revision = "0b12d6b5" +[[projects]] + branch = "master" + digest = "1:e51f40f0c19b39c1825eadd07d5c0a98a2ad5942b166d9fc4f54750ce9a04810" + name = "github.com/juju/ansiterm" + packages = [ + ".", + "tabwriter", + ] + pruneopts = "UT" + revision = "720a0952cc2ac777afc295d9861263e2a4cf96a1" + +[[projects]] + digest = "1:31e761d97c76151dde79e9d28964a812c46efc5baee4085b86f68f0c654450de" + name = "github.com/konsorten/go-windows-terminal-sequences" + packages = ["."] + pruneopts = "UT" + revision = "f55edac94c9bbba5d6182a4be46d86a2c9b5b50e" + version = "v1.0.2" + [[projects]] branch = "master" digest = "1:99cc4de3311304019e47705d009fffd6d467320adedb7f1de47b64b10314ada7" @@ -161,6 +256,17 @@ pruneopts = "UT" revision = "fe44a20e6eaafce8ee5c026cd8b3e9be4c25cc59" +[[projects]] + branch = "master" + digest = "1:86ff240bc45a3b957383a4f7da88c9d8879298e1a8b1da499ade6fc4bcbd58de" + name = "github.com/kubernauts/tk8-provisioner-cattle-aws" + packages = [ + ".", + "internal/cluster", + ] + pruneopts = "UT" + revision = "12b98de2be290d65167cf89d1948167f6cd7bd0f" + [[projects]] branch = "master" digest = "1:d37390e6a304c4be387e10885d78d6ac5be8a6b91bda7b5f454fec4c0fb041ed" @@ -195,6 +301,25 @@ pruneopts = "UT" revision = "f29d34a5a87c0347618f17dfef609ad01486e759" +[[projects]] + branch = "master" + digest = "1:85a6e907a4e3336e52c86e4699045543323142e62af5b3589e20b9623a8c2ed2" + name = "github.com/kubernauts/tk8-provisioner-rke" + packages = [ + ".", + "internal/cluster", + ] + pruneopts = "UT" + revision = "a966b1acdb16b9111387b2e7f34ad01467b8a407" + +[[projects]] + digest = "1:2dc8db55c5b223e6cd50aa7915e698cfcf56d1ddfa89bbbf65e24729e0a0200a" + name = "github.com/lunixbochs/vtclean" + packages = ["."] + pruneopts = "UT" + revision = "88cfb0c2efe8ed7b0ccf0af83db39359829027bb" + version = "v1.0.0" + [[projects]] digest = "1:c568d7727aa262c32bdf8a3f7db83614f7af0ed661474b24588de635c20024c7" name = "github.com/magiconair/properties" @@ -203,6 +328,34 @@ revision = "c2353362d570a7bfa228149c62842019201cfb71" version = "v1.8.0" +[[projects]] + digest = "1:2a2a76072bd413b3484a0b5bb2fbb078b0b7dd8950e9276c900e14dce2354679" + name = "github.com/manifoldco/promptui" + packages = [ + ".", + "list", + "screenbuf", + ] + pruneopts = "UT" + revision = "20f2a94120aa14a334121a6de66616a7fa89a5cd" + version = "v0.3.2" + +[[projects]] + digest = "1:7c084e0e780596dd2a7e20d25803909a9a43689c153de953520dfbc0b0e51166" + name = "github.com/mattn/go-colorable" + packages = ["."] + pruneopts = "UT" + revision = "8029fb3788e5a4a9c00e415f586a6d033f5d38b3" + version = "v0.1.2" + +[[projects]] + digest = "1:9b90c7639a41697f3d4ad12d7d67dfacc9a7a4a6e0bbfae4fc72d0da57c28871" + name = "github.com/mattn/go-isatty" + packages = ["."] + pruneopts = "UT" + revision = "1311e847b0cb909da63b5fecfb5370aa66236465" + version = "v0.0.8" + [[projects]] branch = "master" digest = "1:78bbb1ba5b7c3f2ed0ea1eab57bdd3859aec7e177811563edc41198a760b06af" @@ -219,6 +372,19 @@ revision = "fe40af7a9c397fa3ddba203c38a5042c5d0475ad" version = "v1.1.1" +[[projects]] + digest = "1:7157fd3f337f20f9058789e47f3341ae5cb77081fa01c1d678af5a03afea6f3e" + name = "github.com/nicksnyder/go-i18n" + packages = [ + "i18n", + "i18n/bundle", + "i18n/language", + "i18n/translation", + ] + pruneopts = "UT" + revision = "3c6ee9071cf061f4b0101e14412f74d6e7a8ada6" + version = "v2.0.0" + [[projects]] digest = "1:95741de3af260a92cc5c7f3f3061e85273f5a81b5db20d4bd68da74bd521675e" name = "github.com/pelletier/go-toml" @@ -227,6 +393,14 @@ revision = "c01d1270ff3e442a8a57cddc1c92dc1138598194" version = "v1.2.0" +[[projects]] + digest = "1:04457f9f6f3ffc5fea48e71d62f2ca256637dee0a04d710288e27e05c8b41976" + name = "github.com/sirupsen/logrus" + packages = ["."] + pruneopts = "UT" + revision = "839c75faf7f98a33d445d181f3018b5c3409a45e" + version = "v1.4.2" + [[projects]] digest = "1:6a4a11ba764a56d2758899ec6f3848d24698d48442ebce85ee7a3f63284526cd" name = "github.com/spf13/afero" @@ -278,6 +452,14 @@ revision = "2c12c60302a5a0e62ee102ca9bc996277c2f64f5" version = "v1.2.1" +[[projects]] + branch = "master" + digest = "1:ba52e5a5fb800ce55108b7a5f181bb809aab71c16736051312b0aa969f82ad39" + name = "github.com/tsenart/deadcode" + packages = ["."] + pruneopts = "UT" + revision = "210d2dc333e90c7e3eedf4f2242507a8e83ed4ab" + [[projects]] branch = "master" digest = "1:f7e5d74f094d44e931785fc1ebc887704953b6689c7d47fce9ff1a8647b83eb6" @@ -301,6 +483,27 @@ revision = "f21a4dfb5e38f5895301dc265a8def02365cc3d0" version = "v0.3.0" +[[projects]] + branch = "master" + digest = "1:a45ec3bb7c73e52430410dff3e0a5534ce518f72a8eb4355bc8502c546b91ecc" + name = "golang.org/x/tools" + packages = [ + "go/ast/astutil", + "go/gcexportdata", + "go/internal/gcimporter", + "go/types/typeutil", + ] + pruneopts = "UT" + revision = "8bb11ff117ca820938b6d899b13b3f2832ef244c" + +[[projects]] + branch = "v3-unstable" + digest = "1:9e71973a6f518a213cbbfa8c24feab33046b2835684818cb6b2c9ea73e823639" + name = "gopkg.in/alecthomas/kingpin.v3-unstable" + packages = ["."] + pruneopts = "UT" + revision = "df19058c872cddcd279411d709047160e543a700" + [[projects]] digest = "1:342378ac4dcb378a5448dd723f0784ae519383532f5e70ade24132c4c8693202" name = "gopkg.in/yaml.v2" @@ -313,6 +516,7 @@ analyzer-name = "dep" analyzer-version = 1 input-imports = [ + "github.com/CrowdSurge/banner", "github.com/alecthomas/template", "github.com/aws/aws-sdk-go/aws", "github.com/aws/aws-sdk-go/aws/awserr", @@ -321,16 +525,28 @@ "github.com/aws/aws-sdk-go/aws/ec2metadata", "github.com/aws/aws-sdk-go/aws/session", "github.com/aws/aws-sdk-go/service/ec2", + "github.com/aws/aws-sdk-go/service/s3", + "github.com/aws/aws-sdk-go/service/s3/s3manager", + "github.com/blang/semver", "github.com/dustinkirkland/golang-petname", + "github.com/gorilla/mux", "github.com/kubernauts/tk8-provisioner-aws", + "github.com/kubernauts/tk8-provisioner-aws/internal/cluster", "github.com/kubernauts/tk8-provisioner-azure", "github.com/kubernauts/tk8-provisioner-baremetal", + "github.com/kubernauts/tk8-provisioner-cattle-aws", "github.com/kubernauts/tk8-provisioner-eks", + "github.com/kubernauts/tk8-provisioner-eks/internal/cluster", + "github.com/kubernauts/tk8-provisioner-eks/internal/templates", "github.com/kubernauts/tk8-provisioner-nutanix", "github.com/kubernauts/tk8-provisioner-openstack", + "github.com/kubernauts/tk8-provisioner-rke", + "github.com/manifoldco/promptui", "github.com/mitchellh/go-homedir", + "github.com/sirupsen/logrus", "github.com/spf13/cobra", "github.com/spf13/viper", + "gopkg.in/yaml.v2", ] solver-name = "gps-cdcl" solver-version = 1 diff --git a/Gopkg.toml b/Gopkg.toml index 87e52eb..28be8da 100644 --- a/Gopkg.toml +++ b/Gopkg.toml @@ -40,3 +40,7 @@ [prune] go-tests = true unused-packages = true + +[[constraint]] + name = "github.com/aws/aws-sdk-go" + version = "1.15.47" diff --git a/README.md b/README.md index caa9733..dcef776 100644 --- a/README.md +++ b/README.md @@ -65,6 +65,12 @@ If you no longer need the cluster, you can use the command: `tk8 cluster destroy aws` to automatically remove all of the resources. + +## Rest API + +TK8 can now run as a server rather than from the command line. +For usage details on rest api. Checkout the link [REST-API](https://github.com/kubernauts/tk8/REST-API-README.md) + ## Add-Ons You might want to check out our numerous add-ons for TK8: diff --git a/REST-API-README.md b/REST-API-README.md new file mode 100644 index 0000000..7a192e5 --- /dev/null +++ b/REST-API-README.md @@ -0,0 +1,99 @@ +![Logo](docs/images/tk8.png) + +# Tk8 Rest API +TK8 now also provides the ability to create clusters via REST API. +The configuration stored for the created clusters will be either stored on the local machine path provided by the user +or the S3 bucket provided by the user + + +## Usage + +By Default the REST server starts at port 8091. However that can changed to a port you desire. + +server-mode provides the following flags + +Flags: + -s, --config-store string Storage for config files - local or s3 (default "local") + -a, --config-store-path string Storage location for config files - directory path for local (default ".") or bucket name for s3 + -r, --config-store-region string Region for S3 bucket + -p, --port uint16 Port number for the Tk8 rest api (default 8091) + +To start the server using local storage, execute + +``` +tk8 server-mode -p 8091 -s local -a /path/to/the/local/directory/you/want +``` + +To start the server using s3 bucket, execute +``` +tk8 server-mode -p 8091 -s s3 -a -r +``` + +## Creating Clusters + +To create a cluster follow the configuration params required by each provisioner and create a POST request against the provisioner +Payload to be in JSON + +As an example + +To create an AWS cluster, use the below template to create a request payload and save as aws-create.json + +``` +{ + "clustername": "tk8-aws-cluster", + "os": "centos", + "aws_access_key_id": "", + "aws_secret_access_key": "", + "aws_ssh_keypair": "", + "aws_default_region": "", + "aws_vpc_cidr_block": "10.250.192.0/18", + "aws_cidr_subnets_private": "[\"10.250.192.0/20\",\"10.250.208.0/20\"]", + "aws_cidr_subnets_public": "[\"10.250.224.0/20\",\"10.250.240.0/20\"]", + "aws_bastion_size": "t2.medium", + "aws_kube_master_num": 1, + "aws_kube_master_size": "t2.medium", + "aws_etcd_num": 1, + "aws_etcd_size": "t2.medium", + "aws_kube_worker_num": 1, + "aws_kube_worker_size": "t2.medium", + "aws_elb_api_port": 6443, + "k8s_secure_api_port": 6443, + "kube_insecure_apiserver_address": "0.0.0.0", + "kubeadm_enabled": false, + "kube_network_plugin": "flannel" +} +``` + +Execute a curl request to AWS endpoint + +``` +curl -X POST -d @aws-create.json localhost:8091/v1/cluster/aws +``` + + + +Currently the supported provisioners are + + - AWS provisioner + - EKS Provisioner + - RKE Provisioner + + + ## Rest Endpoints + +### Get cluster details + + - GET /v1/cluster/aws/{id} + - GET /v1/cluster/rke/{id} + - GET /v1/cluster/eks/{id} + +### Delete created Cluster + + - DELETE /v1/cluster/aws/{id} + - DELETE /v1/cluster/eks/{id} + - DELETE /v1/cluster/rke/{id} + +### Create cluster + - POST /v1/cluster/aws + - POST /v1/cluster/rke + - POST /v1/cluster/eks \ No newline at end of file diff --git a/api/server/api.go b/api/server/api.go new file mode 100644 index 0000000..c0e989d --- /dev/null +++ b/api/server/api.go @@ -0,0 +1,336 @@ +package server + +import ( + "encoding/json" + "net/http" + + "github.com/gorilla/mux" + "github.com/kubernauts/tk8/internal/addon" + "github.com/kubernauts/tk8/pkg/common" + + aws "github.com/kubernauts/tk8-provisioner-aws" + azure "github.com/kubernauts/tk8-provisioner-azure" + baremetal "github.com/kubernauts/tk8-provisioner-baremetal" + eks "github.com/kubernauts/tk8-provisioner-eks" + nutanix "github.com/kubernauts/tk8-provisioner-nutanix" + openstack "github.com/kubernauts/tk8-provisioner-openstack" + rke "github.com/kubernauts/tk8-provisioner-rke" + provisioner "github.com/kubernauts/tk8/pkg/provisioner" +) + +const ( + // APIVersion for cluster APIs + APIVersion = "v1" +) + +type tk8Api struct { + restBase +} + +var Provisioners = map[string]provisioner.Provisioner{ + "aws": aws.NewAWS(), + "azure": azure.NewAzure(), + "baremetal": baremetal.NewBaremetal(), + "eks": eks.NewEKS(), + "nutanix": nutanix.NewNutanix(), + "openstack": openstack.NewOpenstack(), + "rke": rke.NewRKE(), +} + +func newTk8Api() restServer { + return &tk8Api{ + restBase: restBase{ + version: APIVersion, + name: "TK8 API", + }, + } +} + +func (c *tk8Api) installAddon(w http.ResponseWriter, r *http.Request) { + method := "install" + var addonReq AddonRequest + var addon addon.Addon + var err error + + if err = json.NewDecoder(r.Body).Decode(&addonReq); err != nil { + c.sendError(c.name, method, w, err.Error(), http.StatusBadRequest) + return + } + + err = addon.Install(addonReq.Name, addonReq.Scope) + if err != nil { + c.sendError(c.name, method, w, err.Error(), http.StatusInternalServerError) + return + } + + resp := &AddonResponse{Error: responseStatus(err)} + if err == nil { + resp.Status = "Successfully installed" + } + json.NewEncoder(w).Encode(&resp) + //json.NewEncoder(w).Encode(cluster) +} + +func (c *tk8Api) destroyAddon(w http.ResponseWriter, r *http.Request) { + method := "destroy" + var addonReq AddonRequest + var addon addon.Addon + var err error + + if err = json.NewDecoder(r.Body).Decode(&addonReq); err != nil { + c.sendError(c.name, method, w, err.Error(), http.StatusBadRequest) + return + } + + err, _ = addon.Destroy(addonReq.Name, addonReq.Scope) + if err != nil { + c.sendError(c.name, method, w, err.Error(), http.StatusInternalServerError) + return + } + + resp := &AddonResponse{Error: responseStatus(err)} + if err == nil { + resp.Status = "Successfully destroyed" + } + json.NewEncoder(w).Encode(&resp) +} + +func (c *tk8Api) sendNotImplemented(w http.ResponseWriter, method string) { + + c.sendError(c.name, method, w, "Not implemented.", http.StatusNotImplemented) + +} + +func (c *tk8Api) createAWSClusterHandler(w http.ResponseWriter, r *http.Request) { + method := "createHandler" + enableCors(&w) + var aws Aws + var err error + if err = json.NewDecoder(r.Body).Decode(&aws); err != nil { + c.sendError(c.name, method, w, err.Error(), http.StatusBadRequest) + return + } + err = aws.CreateCluster() + if err != nil { + c.sendError(c.name, method, w, err.Error(), http.StatusBadRequest) + return + } + json.NewEncoder(w).Encode(aws) + +} + +func (c *tk8Api) createRKEClusterHandler(w http.ResponseWriter, r *http.Request) { + method := "createHandler" + enableCors(&w) + var rke Rke + var err error + if err = json.NewDecoder(r.Body).Decode(&rke); err != nil { + c.sendError(c.name, method, w, err.Error(), http.StatusBadRequest) + return + } + err = rke.CreateCluster() + if err != nil { + c.sendError(c.name, method, w, err.Error(), http.StatusBadRequest) + return + } + json.NewEncoder(w).Encode(rke) + +} + +func (c *tk8Api) createEKSClusterHandler(w http.ResponseWriter, r *http.Request) { + method := "createHandler" + enableCors(&w) + var eks Eks + var err error + if err = json.NewDecoder(r.Body).Decode(&eks); err != nil { + c.sendError(c.name, method, w, err.Error(), http.StatusBadRequest) + return + } + err = eks.CreateCluster() + if err != nil { + c.sendError(c.name, method, w, err.Error(), http.StatusBadRequest) + return + } + json.NewEncoder(w).Encode(eks) + +} +func (c *tk8Api) getClusters(w http.ResponseWriter, r *http.Request) { + //method := "getClusters" + enableCors(&w) + + s := NewStore(common.REST_API_STORAGE, "", common.REST_API_STORAGEPATH, common.REST_API_STORAGEREGION) + clusters, err := s.GetConfigs() + if err != nil { + json.NewEncoder(w).Encode("Error getting clusters") + } + if len(clusters) > 0 { + json.NewEncoder(w).Encode(clusters) + return + } + + json.NewEncoder(w).Encode("No clusters found") +} + +func (c *tk8Api) getCluster(w http.ResponseWriter, r *http.Request) { + method := "getCluster" + vars := mux.Vars(r) + clusterID, ok := vars["id"] + + if !ok || clusterID == "" { + c.sendError(c.name, method, w, "Missing id param", http.StatusBadRequest) + return + } + // now that you have the name of the cluster that you want to get + // checkout +} + +func (c *tk8Api) getAWSClusterHandler(w http.ResponseWriter, r *http.Request) { + method := "getAWSClusterHandler" + vars := mux.Vars(r) + clusterName, ok := vars["id"] + + if !ok || clusterName == "" { + c.sendError(c.name, method, w, "Missing id param", http.StatusBadRequest) + return + } + + // check if cluster exists + + var aws *Aws + cluster, err := aws.GetCluster(clusterName) + + //aws, err := decodeAwsClusterConfigToStruct(clusterName) + if err != nil { + c.sendError(c.name, method, w, err.Error(), http.StatusBadRequest) + return + } + + json.NewEncoder(w).Encode(cluster) +} + +func (c *tk8Api) getEKSlusterHandler(w http.ResponseWriter, r *http.Request) { + method := "getEKSClusterHandler" + vars := mux.Vars(r) + clusterName, ok := vars["id"] + + if !ok || clusterName == "" { + c.sendError(c.name, method, w, "Missing id param", http.StatusBadRequest) + return + } + + // check if cluster exists + + var eks *Eks + cluster, err := eks.GetCluster(clusterName) + //eks, err := decodeEksClusterConfigToStruct(clusterName) + if err != nil { + c.sendError(c.name, method, w, err.Error(), http.StatusBadRequest) + return + } + + json.NewEncoder(w).Encode(cluster) +} + +func (c *tk8Api) getRKEClusterHandler(w http.ResponseWriter, r *http.Request) { + method := "getRKEClusterHandler" + vars := mux.Vars(r) + clusterName, ok := vars["id"] + + if !ok || clusterName == "" { + c.sendError(c.name, method, w, "Missing id param", http.StatusBadRequest) + return + } + + var rke *Rke + cluster, err := rke.GetCluster(clusterName) + + // check if cluster exists + //rke, err := decodeRkeClusterConfigToStruct(clusterName) + if err != nil { + c.sendError(c.name, method, w, err.Error(), http.StatusBadRequest) + return + } + + json.NewEncoder(w).Encode(cluster) +} + +func (c *tk8Api) destroyAWSClusterHandler(w http.ResponseWriter, r *http.Request) { + method := "destroyAWSClusterHandler" + vars := mux.Vars(r) + clusterName, ok := vars["id"] + + if !ok { + c.sendError(c.name, method, w, "Cluster name not passed", http.StatusBadRequest) + return + } + var err error + + if clusterName == "" { + c.sendError(c.name, method, w, "Cluster name cannot be empty", http.StatusBadRequest) + return + } + aws := &Aws{} + aws.Clustername = clusterName + err = aws.DestroyCluster() + if err != nil { + c.sendError(c.name, method, w, err.Error(), http.StatusBadRequest) + return + } + json.NewEncoder(w).Encode("Cluster deletion started ...") +} + +func (c *tk8Api) destroyEKSClusterHandler(w http.ResponseWriter, r *http.Request) { + method := "destroyEKSClusterHandler" + vars := mux.Vars(r) + clusterName, ok := vars["id"] + if !ok { + c.sendError(c.name, method, w, "Cluster name cannot be empty", http.StatusBadRequest) + return + } + var err error + + if clusterName == "" { + c.sendError(c.name, method, w, "Cluster name cannot be empty", http.StatusBadRequest) + return + } + + eks := &Eks{} + eks.ClusterName = clusterName + err = eks.DestroyCluster() + if err != nil { + c.sendError(c.name, method, w, err.Error(), http.StatusBadRequest) + return + } + json.NewEncoder(w).Encode("Cluster deletion started ...") +} + +func (c *tk8Api) destroyRKEClusterHandler(w http.ResponseWriter, r *http.Request) { + method := "destroyRkeClusterHandler" + vars := mux.Vars(r) + clusterName, ok := vars["id"] + if !ok { + c.sendError(c.name, method, w, "Cluster name cannot be empty", http.StatusBadRequest) + return + } + var err error + + if clusterName == "" { + c.sendError(c.name, method, w, "Cluster name cannot be empty", http.StatusBadRequest) + return + } + + rke := &Rke{} + rke.ClusterName = clusterName + err = rke.DestroyCluster() + if err != nil { + c.sendError(c.name, method, w, err.Error(), http.StatusBadRequest) + return + } + json.NewEncoder(w).Encode("Cluster deletion started ...") +} + +func (c *tk8Api) createInfraOnly(w http.ResponseWriter, req *http.Request) { + config := req.ParseForm() + Provisioners["aws"].Init(nil) + json.NewEncoder(w).Encode(config) +} diff --git a/api/server/aws.go b/api/server/aws.go new file mode 100644 index 0000000..f51fad0 --- /dev/null +++ b/api/server/aws.go @@ -0,0 +1,120 @@ +package server + +import ( + "fmt" + "gopkg.in/yaml.v2" + + "github.com/kubernauts/tk8/api" + "github.com/kubernauts/tk8/pkg/common" + "github.com/sirupsen/logrus" +) + +type AwsYaml struct { + Aws *Aws `yaml:"aws"` +} +type Aws struct { + Clustername string `yaml:"clustername" json:"clustername"` + Os string `yaml:"os" json:"os"` + AwsAccessKeyID string `yaml:"aws_access_key_id" json:"aws_access_key_id"` + AwsSecretAccessKey string `yaml:"aws_secret_access_key" json:"aws_secret_access_key"` + AwsSSHKeypair string `yaml:"aws_ssh_keypair" json:"aws_ssh_keypair"` + AwsDefaultRegion string `yaml:"aws_default_region" json:"aws_default_region"` + AwsVpcCidrBlock string `yaml:"aws_vpc_cidr_block" json:"aws_vpc_cidr_block"` + AwsCidrSubnetsPrivate string `yaml:"aws_cidr_subnets_private" json:"aws_cidr_subnets_private"` + AwsCidrSubnetsPublic string `yaml:"aws_cidr_subnets_public" json:"aws_cidr_subnets_public"` + AwsBastionSize string `yaml:"aws_bastion_size" json:"aws_bastion_size"` + AwsKubeMasterNum int `yaml:"aws_kube_master_num" json:"aws_kube_master_num"` + AwsKubeMasterSize string `yaml:"aws_kube_master_size" json:"aws_kube_master_size"` + AwsEtcdNum int `yaml:"aws_etcd_num" json:"aws_etcd_num"` + AwsEtcdSize string `yaml:"aws_etcd_size" json:"aws_etcd_size"` + AwsKubeWorkerNum int `yaml:"aws_kube_worker_num" json:"aws_kube_worker_num"` + AwsKubeWorkerSize string `yaml:"aws_kube_worker_size" json:"aws_kube_worker_size"` + AwsElbAPIPort int `yaml:"aws_elb_api_port" json:"aws_elb_api_port"` + K8SSecureAPIPort int `yaml:"k8s_secure_api_port" json:"k8s_secure_api_port"` + KubeInsecureApiserverAddress string `yaml:"kube_insecure_apiserver_address" json:"kube_insecure_apiserver_address"` + KubeadmEnabled bool `yaml:"kubeadm_enabled" json:"kubeadm_enabled"` + KubeNetworkPlugin string `yaml:"kube_network_plugin" json:"kube_network_plugin"` +} + +// CreateCluster creates AWS cluster +func (a *Aws) CreateCluster() error { + + // create AWS cluster config file + configFileName := "aws-" + a.Clustername + ".yaml" + s := NewStore(common.REST_API_STORAGE, configFileName, common.REST_API_STORAGEPATH, common.REST_API_STORAGEREGION) + + provisioner := "aws" + // validateJSON + err := s.ValidateConfig() + if err != nil { + logrus.Errorf("Error validating config ::: %s", err) + return err + } + + err = getProvisioner(provisioner) + if err != nil { + logrus.Errorf("Error getting provisioner ::: %s", err) + return err + } + err = s.CreateConfig(a) + if err != nil { + logrus.Errorf("Error creating config ::: %s", err) + return err + } + go func() { + Provisioners[provisioner].Init(nil) + Provisioners[provisioner].Setup(nil) + }() + return nil +} + +// DestroyCluster destroys AWS cluster +func (a *Aws) DestroyCluster() error { + configFileName := "aws-" + a.Clustername + ".yaml" + s := NewStore(common.REST_API_STORAGE, configFileName, common.REST_API_STORAGEPATH, common.REST_API_STORAGEREGION) + + exists, _ := s.CheckConfigExists() + if !exists { + logrus.Errorf("Error , no such cluster with name %s", a.Clustername) + return fmt.Errorf("No such cluster exists with name - %s", a.Clustername) + } + + go func() { + Provisioners["aws"].Destroy(nil) + }() + + // Delete AWS cluster config file + err := s.DeleteConfig() + if err != nil { + logrus.Errorf("Error , deleting cluster named %s , Error details are %s ", a.Clustername, err.Error()) + return fmt.Errorf("Error deleting cluster %s", a.Clustername) + } + + return nil +} + +// GetCluster gets the details of thge requested AWS cluster +func (a *Aws) GetCluster(name string) (api.Cluster, error) { + + configFileName := "aws-" + name + ".yaml" + s := NewStore(common.REST_API_STORAGE, configFileName, common.REST_API_STORAGEPATH, common.REST_API_STORAGEREGION) + exists, _ := s.CheckConfigExists() + + if !exists { + return nil, fmt.Errorf("No cluster found with the provided name ::: %s", name) + } + + awsConfig := &AwsYaml{} + yamlFile, err := s.GetConfig() + if err != nil { + logrus.Errorf("Error getting details of cluster named %s , Error details are %s ", name, err.Error()) + return nil, err + } + err = yaml.Unmarshal(yamlFile, awsConfig) + if err != nil { + logrus.Errorf("unable to decode into rke config struct, %v", err) + return nil, err + } + return awsConfig.Aws, nil + +} diff --git a/api/server/eks.go b/api/server/eks.go new file mode 100644 index 0000000..db6486c --- /dev/null +++ b/api/server/eks.go @@ -0,0 +1,106 @@ +package server + +import ( + "fmt" + "github.com/kubernauts/tk8/api" + "github.com/kubernauts/tk8/pkg/common" + "github.com/sirupsen/logrus" + "gopkg.in/yaml.v2" +) + +type EksYaml struct { + Eks *Eks `yaml:"eks"` +} +type Eks struct { + ClusterName string `yaml:"cluster-name" json:"cluster-name"` + AwsRegion string `yaml:"aws_region" json:"aws_region"` + NodeInstanceType string `yaml:"node-instance-type" json:"node-instance-type"` + DesiredCapacity int `yaml:"desired-capacity" json:"desired-capacity"` + AutoscallingMaxSize int `yaml:"autoscalling-max-size" json:"autoscalling-max-size"` + AutoscallingMinSize int `yaml:"autoscalling-min-size" json:"autoscalling-min-size"` + KeyFilePath string `yaml:"key-file-path" json:"key-file-path"` +} + +// CreateCluster creates EKS cluster +func (e *Eks) CreateCluster() error { + + // create AWS cluster config file + configFileName := "eks-" + e.ClusterName + ".yaml" + s := NewStore(common.REST_API_STORAGE, configFileName, common.REST_API_STORAGEPATH, common.REST_API_STORAGEREGION) + provisioner := "eks" + // validateJSON + err := s.ValidateConfig() + if err != nil { + logrus.Errorf("Error validating config ::: %s", err) + return err + } + + err = getProvisioner(provisioner) + if err != nil { + logrus.Errorf("Error getting provisioner ::: %s", err) + return err + } + + // create EKS cluster config file + err = s.CreateConfig(e) + if err != nil { + logrus.Errorf("Error creating config ::: %s", err) + return err + } + + go func() { + Provisioners[provisioner].Init(nil) + Provisioners[provisioner].Setup(nil) + }() + + return nil +} + +// DestroyCluster destroys EKS cluster +func (e *Eks) DestroyCluster() error { + configFileName := "eks-" + e.ClusterName + ".yaml" + + s := NewStore(common.REST_API_STORAGE, configFileName, common.REST_API_STORAGEPATH, common.REST_API_STORAGEREGION) + exists, _ := s.CheckConfigExists() + if !exists { + logrus.Errorf("No such cluster with name ::: %s", e.ClusterName) + return fmt.Errorf("No such cluster with name - %s", e.ClusterName) + } + go func() { + Provisioners["eks"].Destroy(nil) + }() + + // Delete AWS cluster config file + err := s.DeleteConfig() + if err != nil { + logrus.Errorf("Error deleting config ::: %s", err) + return fmt.Errorf("Error deleting cluster named %s", e.ClusterName) + } + return nil +} + +// GetCluster gets the details of thge requested EKS cluster +func (e *Eks) GetCluster(name string) (api.Cluster, error) { + + configFileName := "eks-" + name + ".yaml" + s := NewStore(common.REST_API_STORAGE, configFileName, common.REST_API_STORAGEPATH, common.REST_API_STORAGEREGION) + exists, _ := s.CheckConfigExists() + if !exists { + logrus.Errorf("Error getting config ::: %s", e.ClusterName) + return nil, fmt.Errorf("No cluster found with the provided name ::: %s", name) + } + + yamlFile, err := s.GetConfig() + if err != nil { + logrus.Errorf("Error getting details of cluster named %s , Error details are %s ", name, err.Error()) + return nil, err + } + eksConfig := &EksYaml{} + err = yaml.Unmarshal(yamlFile, eksConfig) + if err != nil { + logrus.Errorf("unable to decode into rke config struct, %v", err) + return nil, fmt.Errorf("unable to decode into rke config struct, %v", err) + } + + return eksConfig.Eks, nil +} diff --git a/api/server/local_storage.go b/api/server/local_storage.go new file mode 100644 index 0000000..8815249 --- /dev/null +++ b/api/server/local_storage.go @@ -0,0 +1,176 @@ +package server + +import ( + "fmt" + "io/ioutil" + "os" + "path/filepath" + "strings" + + "github.com/kubernauts/tk8/api" + // "github.com/kubernauts/tk8/pkg/common" + "github.com/sirupsen/logrus" + + "github.com/spf13/viper" + "gopkg.in/yaml.v2" +) + +func NewLocalStore(name, path string) *LocalStore { + return &LocalStore{ + FileName: name, + FilePath: path, + } +} + +func (l *LocalStore) DeleteConfig() error { + fullpath := filepath.Join(l.FilePath, l.FileName) + err := os.Remove(fullpath) + if err != nil { + return err + } + return nil +} + +func (l *LocalStore) ValidateConfig() error { + + return nil +} + +func (l *LocalStore) UpdateConfig() error { + + return nil +} + +func (l *LocalStore) CheckConfigExists() (bool, error) { + fullpath := filepath.Join(l.FilePath, l.FileName) + if _, err := os.Stat(fullpath); err != nil { + if os.IsNotExist(err) { + return false, nil + } + } + return true, nil +} + +func (l *LocalStore) CreateConfig(t api.Cluster) error { + viper.New() + viper.SetConfigType("yaml") + viper.SetConfigFile(l.FileName) + viper.AddConfigPath(l.FilePath) + + switch a := t.(type) { + case *Aws: + viper.Set("aws.clustername", a.Clustername) + viper.Set("aws.os", a.Os) + viper.Set("aws.aws_access_key_id", a.AwsAccessKeyID) + viper.Set("aws.aws_secret_access_key", a.AwsSecretAccessKey) + viper.Set("aws.aws_ssh_keypair", a.AwsSSHKeypair) + viper.Set("aws.aws_default_region", a.AwsDefaultRegion) + viper.Set("aws.aws_vpc_cidr_block", a.AwsVpcCidrBlock) + viper.Set("aws.aws_cidr_subnets_private", a.AwsCidrSubnetsPrivate) + viper.Set("aws.aws_cidr_subnets_public", a.AwsCidrSubnetsPublic) + viper.Set("aws.aws_bastion_size", a.AwsBastionSize) + viper.Set("aws.aws_kube_master_num", a.AwsKubeMasterNum) + viper.Set("aws.aws_kube_master_size", a.AwsKubeMasterSize) + viper.Set("aws.aws_etcd_num", a.AwsEtcdNum) + viper.Set("aws.aws_etcd_size", a.AwsEtcdSize) + viper.Set("aws.aws_kube_worker_num", a.AwsKubeWorkerNum) + viper.Set("aws.aws_kube_worker_size", a.AwsKubeWorkerSize) + viper.Set("aws.aws_elb_api_port", a.AwsElbAPIPort) + viper.Set("aws.k8s_secure_api_port", a.K8SSecureAPIPort) + viper.Set("aws.kube_insecure_apiserver_address", a.KubeInsecureApiserverAddress) + viper.Set("aws.kubeadm_enabled", a.KubeadmEnabled) + viper.Set("aws.kube_network_plugin", a.KubeNetworkPlugin) + case *Eks: + viper.Set("eks.cluster-name", a.ClusterName) + viper.Set("eks.aws_region", a.AwsRegion) + viper.Set("eks.node-instance-type", a.NodeInstanceType) + viper.Set("eks.desired-capacity", a.DesiredCapacity) + viper.Set("eks.autoscalling-max-size", a.AutoscallingMaxSize) + viper.Set("eks.autoscalling-min-size", a.AutoscallingMinSize) + viper.Set("eks.key-file-path", "~/.ssh/id_rsa.pub") + case *Rke: + viper.Set("rke.cluster_name", a.ClusterName) + viper.Set("rke.node_os", a.NodeOs) + viper.Set("rke.rke_aws_region", a.ClusterName) + + viper.Set("rke.authorization", a.ClusterName) + viper.Set("rke.rke_node_instance_type", a.ClusterName) + viper.Set("rke.node_count", a.ClusterName) + viper.Set("rke.cloud_provider", a.CloudProvider) + } + + logrus.Println(viper.AllKeys()) + logrus.Println(viper.AllSettings()) + + fullPath := filepath.Join(l.FilePath, l.FileName) + err := viper.WriteConfigAs(fullPath) + if err != nil { + return err + } + return nil +} + +func (l *LocalStore) GetConfigs() (api.AllClusters, error) { + files, _ := ioutil.ReadDir(l.FilePath) + clusters := make(api.AllClusters) + for _, file := range files { + switch { + case strings.Contains(file.Name(), "aws-"): + configFileName := filepath.Join(l.FilePath, file.Name()) + + awsConfig := &AwsYaml{} + yamlFile, err := ioutil.ReadFile(configFileName) + if err != nil { + logrus.Printf("yamlFile.Get err #%v ", err) + } + err = yaml.Unmarshal(yamlFile, awsConfig) + + if err != nil { + fmt.Printf("unable to decode into aws config struct, %v", err) + continue + } + clusters["aws"] = append([]api.Cluster{awsConfig.Aws}) + + case strings.Contains(file.Name(), "eks-"): + configFileName := filepath.Join(l.FilePath, file.Name()) + eksConfig := &EksYaml{} + + yamlFile, err := ioutil.ReadFile(configFileName) + if err != nil { + logrus.Printf("yamlFile.Get err #%v ", err) + } + err = yaml.Unmarshal(yamlFile, eksConfig) + if err != nil { + fmt.Printf("unable to decode into eks config struct, %v", err) + continue + } + clusters["eks"] = append([]api.Cluster{eksConfig.Eks}) + + case strings.Contains(file.Name(), "rke-"): + configFileName := filepath.Join(l.FilePath, file.Name()) + rkeConfig := &RkeYaml{} + yamlFile, err := ioutil.ReadFile(configFileName) + if err != nil { + logrus.Printf("yamlFile.Get err #%v ", err) + } + err = yaml.Unmarshal(yamlFile, rkeConfig) + if err != nil { + fmt.Printf("unable to decode into rke config struct, %v", err) + continue + } + clusters["rke"] = append([]api.Cluster{rkeConfig.Rke}) + } + } + return clusters, nil +} + +func (l *LocalStore) GetConfig() ([]byte, error) { + fullPath := filepath.Join(l.FilePath, l.FileName) + yamlFile, err := ioutil.ReadFile(fullPath) + if err != nil { + logrus.Printf("yamlFile.Get err #%v ", err) + return nil, err + } + + return yamlFile, nil +} diff --git a/api/server/models.go b/api/server/models.go new file mode 100644 index 0000000..7b54a40 --- /dev/null +++ b/api/server/models.go @@ -0,0 +1,15 @@ +package server + +type AddonRequest struct { + // Name of the addon + Name string + // Namespace of the addon + Scope string +} + +type AddonResponse struct { + // Status + Status string + // Error + Error string +} diff --git a/api/server/rke.go b/api/server/rke.go new file mode 100644 index 0000000..8519d37 --- /dev/null +++ b/api/server/rke.go @@ -0,0 +1,106 @@ +package server + +import ( + "fmt" + "github.com/kubernauts/tk8/api" + "github.com/kubernauts/tk8/pkg/common" + "github.com/sirupsen/logrus" + "gopkg.in/yaml.v2" +) + +type RkeYaml struct { + Rke *Rke `yaml:"rke"` +} + +type Rke struct { + ClusterName string `yaml:"cluster_name" json:"cluster_name"` + NodeOs string `yaml:"node_os" json:"node_os"` + RkeAwsRegion string `yaml:"rke_aws_region" json:"rke_aws_region"` + Authorization string `yaml:"authorization" json:"authorization"` + RkeNodeInstanceType string `yaml:"rke_node_instance_type" json:"rke_node_instance_type"` + NodeCount int `yaml:"node_count" json:"node_count"` + CloudProvider string `yaml:"cloud_provider" json:"cloud_provider"` +} + +// CreateCluster creates RKE cluster +func (r *Rke) CreateCluster() error { + + configFileName := "rke-" + r.ClusterName + ".yaml" + s := NewStore(common.REST_API_STORAGE, configFileName, common.REST_API_STORAGEPATH, common.REST_API_STORAGEREGION) + provisioner := "rke" + // validateJSON + err := s.ValidateConfig() + if err != nil { + logrus.Errorf("Error validating config -- %s", err.Error()) + return err + } + + err = getProvisioner(provisioner) + if err != nil { + return err + } + + // create RKE cluster config file + err = s.CreateConfig(r) + if err != nil { + logrus.Errorf("Error creating config -- %s", err.Error()) + return err + } + + go func() { + Provisioners[provisioner].Init(nil) + Provisioners[provisioner].Setup(nil) + }() + + return nil +} + +// DestroyCluster destroys given RKE cluster +func (r *Rke) DestroyCluster() error { + configFileName := "rke-" + r.ClusterName + ".yaml" + s := NewStore(common.REST_API_STORAGE, configFileName, common.REST_API_STORAGEPATH, common.REST_API_STORAGEREGION) + + exists, _ := s.CheckConfigExists() + if !exists { + logrus.Errorf("Error no such cluster existswith name -- %s", r.ClusterName) + return fmt.Errorf("No such cluster exists with name - %s", r.ClusterName) + } + go func() { + Provisioners["rke"].Destroy(nil) + }() + + // Delete AWS cluster config file + err := s.DeleteConfig() + if err != nil { + logrus.Errorf("Error deleting cluster -- %s", err.Error()) + return fmt.Errorf("Error deleting cluster named - %s", r.ClusterName) + } + + return nil +} + +// GetCluster for RKE +func (r *Rke) GetCluster(name string) (api.Cluster, error) { + + configFileName := "rke-" + name + ".yaml" + s := NewStore(common.REST_API_STORAGE, configFileName, common.REST_API_STORAGEPATH, common.REST_API_STORAGEREGION) + + exists, _ := s.CheckConfigExists() + if !exists { + logrus.Errorf("Error no such cluster existswith name -- %s", name) + return nil, fmt.Errorf("No cluster found with the provided name ::: %s", name) + } + + rkeConfig := &RkeYaml{} + yamlFile, err := s.GetConfig() + if err != nil { + logrus.Errorf("Error unable to get cluster details , %v", err) + return nil, err + } + err = yaml.Unmarshal(yamlFile, rkeConfig) + if err != nil { + return nil, fmt.Errorf("Error unable to get cluster, %v", err) + } + + return rkeConfig.Rke, nil +} diff --git a/api/server/routes.go b/api/server/routes.go new file mode 100644 index 0000000..0579b35 --- /dev/null +++ b/api/server/routes.go @@ -0,0 +1,42 @@ +package server + +import ( + "net/http" +) + +// Route is a specification and handler for a REST endpoint. +type Route struct { + verb string + path string + fn func(http.ResponseWriter, *http.Request) +} + +func (c *tk8Api) Routes() []*Route { + return []*Route{ + {verb: "GET", path: clusterPath("", APIVersion), fn: c.getClusters}, + {verb: "GET", path: clusterPath("/aws/{id}", APIVersion), fn: c.getAWSClusterHandler}, + {verb: "GET", path: clusterPath("/rke/{id}", APIVersion), fn: c.getRKEClusterHandler}, + {verb: "GET", path: clusterPath("/eks/{id}", APIVersion), fn: c.getEKSlusterHandler}, + {verb: "DELETE", path: clusterPath("/aws/{id}", APIVersion), fn: c.destroyAWSClusterHandler}, + {verb: "DELETE", path: clusterPath("/eks/{id}", APIVersion), fn: c.destroyEKSClusterHandler}, + {verb: "DELETE", path: clusterPath("/rke/{id}", APIVersion), fn: c.destroyRKEClusterHandler}, + {verb: "POST", path: clusterPath("/aws", APIVersion), fn: c.createAWSClusterHandler}, + {verb: "POST", path: clusterPath("/rke", APIVersion), fn: c.createRKEClusterHandler}, + {verb: "POST", path: clusterPath("/eks", APIVersion), fn: c.createEKSClusterHandler}, + + {verb: "POST", path: addonPath("/{id}", APIVersion), fn: c.installAddon}, + {verb: "DELETE", path: addonPath("/{id}", APIVersion), fn: c.destroyAddon}, + } +} + +func clusterPath(route, version string) string { + return apiVersion("cluster"+route, version) +} + +func addonPath(route, version string) string { + return apiVersion("addon"+route, version) +} + +func apiVersion(route, version string) string { + return "/" + version + "/" + route +} diff --git a/api/server/s3_storage.go b/api/server/s3_storage.go new file mode 100644 index 0000000..6f738e5 --- /dev/null +++ b/api/server/s3_storage.go @@ -0,0 +1,355 @@ +package server + +import ( + "fmt" + "github.com/sirupsen/logrus" + "io" + "os" + "path/filepath" + "strings" + + "github.com/aws/aws-sdk-go/aws" + "github.com/aws/aws-sdk-go/aws/awserr" + "github.com/aws/aws-sdk-go/aws/session" + "github.com/aws/aws-sdk-go/service/s3" + "github.com/aws/aws-sdk-go/service/s3/s3manager" + "github.com/kubernauts/tk8/api" + "github.com/kubernauts/tk8/pkg/common" +) + +func NewS3Store(name, bucketName, region string) *S3Store { + return &S3Store{ + FileName: name, + BucketName: bucketName, + Region: region, + } +} + +func (s *S3Store) DeleteConfig() error { + //select Region to use. + conf := aws.Config{Region: aws.String(s.Region)} + sess := session.New(&conf) + svc := s3.New(sess) + + _, err := svc.DeleteObject(&s3.DeleteObjectInput{ + Bucket: aws.String(s.BucketName), + Key: aws.String(s.FileName), + }) + + if err != nil { + return fmt.Errorf("Erorr deleting cluster ::") + } + + err = svc.WaitUntilObjectNotExists(&s3.HeadObjectInput{ + Bucket: aws.String(s.BucketName), + Key: aws.String(s.FileName), + }) + + if err != nil { + return fmt.Errorf("Erorr deleting cluster ::") + } + + return nil +} + +func (s *S3Store) ValidateConfig() error { + + return nil +} + +func (s *S3Store) UpdateConfig() error { + + return nil +} + +func (s *S3Store) GetConfigs() (api.AllClusters, error) { + var err error + tempPath := "/tmp" + conf := aws.Config{Region: aws.String(s.Region)} + + sess := session.New(&conf) + downloader := s3manager.NewDownloader(sess) + + svc := s3.New(sess) + input := &s3.ListObjectsInput{ + Bucket: aws.String(s.BucketName), + MaxKeys: aws.Int64(100), + } + + result, err := svc.ListObjects(input) + if err != nil { + if aerr, ok := err.(awserr.Error); ok { + switch aerr.Code() { + case s3.ErrCodeNoSuchBucket: + return nil, fmt.Errorf(s3.ErrCodeNoSuchBucket, aerr.Error()) + default: + return nil, fmt.Errorf(aerr.Error()) + } + } else { + // Message from an error. + return nil, fmt.Errorf(err.Error()) + } + } + + for _, item := range result.Contents { + path := filepath.Join(tempPath, *item.Key) + file, err := os.Create(path) + if err != nil { + return nil, err + } + _, err = downloader.Download(file, + &s3.GetObjectInput{ + Bucket: aws.String(s.BucketName), + Key: aws.String(*item.Key), + }) + if err != nil { + return nil, err + } + } + + l := NewLocalStore("", tempPath) + allClusters, err := l.GetConfigs() + if err != nil { + return nil, err + } + return allClusters, nil +} + +func (s *S3Store) CheckConfigExists() (bool, error) { + var err error + conf := aws.Config{Region: aws.String(s.Region)} + + svc := s3.New(session.New(&conf)) + input := &s3.ListObjectsInput{ + Bucket: aws.String(s.BucketName), + MaxKeys: aws.Int64(100), + } + + result, err := svc.ListObjects(input) + if err != nil { + if aerr, ok := err.(awserr.Error); ok { + switch aerr.Code() { + case s3.ErrCodeNoSuchBucket: + return false, fmt.Errorf(s3.ErrCodeNoSuchBucket, aerr.Error()) + default: + return false, fmt.Errorf(aerr.Error()) + } + } else { + // Message from an error. + return false, fmt.Errorf(err.Error()) + } + } + + for _, item := range result.Contents { + fmt.Println("Item --- ", *item.Key, " --- and --- filename -- ", s.FileName) + if *item.Key == s.FileName { + return true, nil + } + } + return false, nil +} + +func (s *S3Store) GetConfig() ([]byte, error) { + + tempPath := "/tmp" + path := filepath.Join(tempPath, s.FileName) + file, err := os.Create(path) + if err != nil { + return nil, err + } + + defer file.Close() + + conf := aws.Config{Region: aws.String(s.Region)} + sess := session.New(&conf) + downloader := s3manager.NewDownloader(sess) + + _, err = downloader.Download(file, + &s3.GetObjectInput{ + Bucket: aws.String(s.BucketName), + Key: aws.String(s.FileName), + }) + if err != nil { + return nil, err + } + + l := NewLocalStore(s.FileName, tempPath) + yamlFile, err := l.GetConfig() + if err != nil { + return nil, err + } + + deleteFileLocally(path) + return yamlFile, nil +} + +func (s *S3Store) CreateConfig(t api.Cluster) error { + + tempPath := "/tmp" + l := NewLocalStore(s.FileName, tempPath) + + err := l.CreateConfig(t) + if err != nil { + return err + } + // Check for existence of s3 bucket + err = checkIfBucketExists((s.BucketName)) + if err != nil { + return err + } + + // check if there exists a file with same cluster name + exists, err := s.CheckConfigExists() + if err != nil { + return err + } + + if exists { + return fmt.Errorf("cluster already exists with the same name, please provide a unique name") + } + + fullpath := filepath.Join(tempPath, s.FileName) + // upload the locally created config to the s3 bucket + err = uploadFileToS3(fullpath, s.BucketName) + if err != nil { + return err + } + + // upon successful upload delete the locally created file + filename := filepath.Join(tempPath, s.FileName) + err = deleteFileLocally(filename) + if err != nil { + return err + } + + return nil +} + +func deleteFileLocally(path string) error { + // delete file + var err = os.Remove(path) + if err != nil { + return err + } + return nil +} + +func uploadFileToS3(filename, bucketname string) error { + //select Region to use. + conf := aws.Config{Region: aws.String(common.REST_API_STORAGEREGION)} + sess := session.New(&conf) + svc := s3manager.NewUploader(sess) + + // open file + file, err := os.Open(filename) + if err != nil { + fmt.Println("Failed to open file", filename, err) + return err + } + defer file.Close() + + _, err = svc.Upload(&s3manager.UploadInput{ + Bucket: aws.String(bucketname), + Key: aws.String(filepath.Base(filename)), + Body: file, + }) + + if err != nil { + fmt.Println("error", err) + return err + } + return nil +} + +func checkIfBucketExists(bucketName string) error { + conf := aws.Config{Region: aws.String("eu-west-1")} + svc := s3.New(session.New(&conf)) + input := &s3.HeadBucketInput{ + Bucket: aws.String(bucketName), + } + + _, err := svc.HeadBucket(input) + if err != nil { + if aerr, ok := err.(awserr.Error); ok { + switch aerr.Code() { + case s3.ErrCodeNoSuchBucket: + return fmt.Errorf(s3.ErrCodeNoSuchBucket, aerr.Error()) + default: + return fmt.Errorf(aerr.Error()) + } + } else { + // Print the error, cast err to awserr.Error to get the Code and + // Message from an error. + return fmt.Errorf(err.Error()) + } + return err + } + return nil +} + +func getBucketObjects(sess *session.Session) { + query := &s3.ListObjectsV2Input{ + Bucket: aws.String(os.Args[2]), + } + svc := s3.New(sess) + + // Flag used to check if we need to go further + truncatedListing := true + + for truncatedListing { + resp, err := svc.ListObjectsV2(query) + + if err != nil { + // Print the error. + fmt.Println(err.Error()) + return + } + // Get all files + getObjectsAll(resp, svc) + // Set continuation token + query.ContinuationToken = resp.NextContinuationToken + truncatedListing = *resp.IsTruncated + } +} + +func getObjectsAll(bucketObjectsList *s3.ListObjectsV2Output, s3Client *s3.S3) { + // Iterate through the files inside the bucket + for _, key := range bucketObjectsList.Contents { + fmt.Println(*key.Key) + destFilename := *key.Key + if strings.HasSuffix(*key.Key, "/") { + fmt.Println("Got a directory") + continue + } + if strings.Contains(*key.Key, "/") { + var dirTree string + // split + s3FileFullPathList := strings.Split(*key.Key, "/") + fmt.Println(s3FileFullPathList) + fmt.Println("destFilename " + destFilename) + for _, dir := range s3FileFullPathList[:len(s3FileFullPathList)-1] { + dirTree += "/" + dir + } + os.MkdirAll(os.Args[3]+"/"+dirTree, 0775) + } + out, err := s3Client.GetObject(&s3.GetObjectInput{ + Bucket: aws.String(os.Args[2]), + Key: key.Key, + }) + if err != nil { + logrus.Fatal(err) + } + destFilePath := os.Args[3] + destFilename + destFile, err := os.Create(destFilePath) + if err != nil { + logrus.Fatal(err) + } + bytes, err := io.Copy(destFile, out.Body) + if err != nil { + logrus.Fatal(err) + } + fmt.Printf("File %s contanin %d bytes\n", destFilePath, bytes) + out.Body.Close() + destFile.Close() + } +} diff --git a/api/server/server.go b/api/server/server.go new file mode 100644 index 0000000..05e0663 --- /dev/null +++ b/api/server/server.go @@ -0,0 +1,69 @@ +package server + +import ( + "fmt" + "net/http" + + "github.com/gorilla/mux" + "github.com/sirupsen/logrus" +) + +type restServer interface { + Routes() []*Route + String() string + logRequest(request string, id string) *logrus.Entry + sendError(request string, id string, w http.ResponseWriter, msg string, code int) +} + +type restBase struct { + restServer + version string + name string +} + +func StartTK8API(tk8ApiBase string, tk8Port uint16) error { + tk8Api := newTk8Api() + + // start server as before + _, p, err := startServer("tk8", tk8ApiBase, tk8Port, tk8Api) + if err != nil { + return err + } + logrus.Println(p.Addr) + return nil +} + +func startServer(name string, sockBase string, port uint16, rs restServer) (*http.Server, *http.Server, error) { + router := mux.NewRouter() + router.NotFoundHandler = http.HandlerFunc(notFound) + for _, v := range rs.Routes() { + logrus.Println(v.path) + router.Methods(v.verb).Path(v.path).HandlerFunc(v.fn) + } + return startServerCommon(name, sockBase, port, rs, router) +} + +func startServerCommon(name string, sockBase string, port uint16, rs restServer, router *mux.Router) (*http.Server, *http.Server, error) { + + logrus.Printf("Starting REST service on port : %v", port) + portServer := &http.Server{Addr: fmt.Sprintf(":%d", port), Handler: router} + go portServer.ListenAndServe() + return nil, portServer, nil +} + +func (rest *restBase) logRequest(request string, id string) *logrus.Entry { + return logrus.WithFields(map[string]interface{}{ + "Driver": rest.name, + "Request": request, + "ID": id, + }) +} +func (rest *restBase) sendError(request string, id string, w http.ResponseWriter, msg string, code int) { + rest.logRequest(request, id).Warnln(code, " ", msg) + http.Error(w, msg, code) +} + +func notFound(w http.ResponseWriter, r *http.Request) { + logrus.Warnf("Not found: %+v ", r.URL) + http.NotFound(w, r) +} diff --git a/api/server/store.go b/api/server/store.go new file mode 100644 index 0000000..67bf246 --- /dev/null +++ b/api/server/store.go @@ -0,0 +1,32 @@ +package server + +import ( + "github.com/kubernauts/tk8/api" +) + +type Storage struct { + StoreType string +} +type LocalStore struct { + *Storage + FileName string + FilePath string +} + +type S3Store struct { + *Storage + FileName string + BucketName string + Region string +} + +func NewStore(storetype, name, path, region string) api.ConfigStore { + switch storetype { + case "s3": + return NewS3Store(name, path, region) + case "local": + return NewLocalStore(name, path) + default: + return nil + } +} diff --git a/api/server/util.go b/api/server/util.go new file mode 100644 index 0000000..5397e42 --- /dev/null +++ b/api/server/util.go @@ -0,0 +1,37 @@ +package server + +import ( + "errors" + "log" + "net/http" + "os" + + "github.com/kubernauts/tk8/pkg/common" +) + +func responseStatus(err error) string { + if err == nil { + return "" + } + return err.Error() +} + +func enableCors(w *http.ResponseWriter) { + (*w).Header().Set("Access-Control-Allow-Origin", "*") +} + +func getProvisioner(provisioner string) error { + if _, ok := Provisioners[provisioner]; ok { + if _, err := os.Stat("./provisioner/" + provisioner); err == nil { + return nil + } + log.Println("get provisioner " + provisioner) + os.Mkdir("./provisioner", 0755) + common.CloneGit("./provisioner", "https://github.com/kubernauts/tk8-provisioner-"+provisioner, provisioner) + common.ReplaceGit("./provisioner/" + provisioner) + return nil + + } + return errors.New("provisioner not supported") + +} diff --git a/api/types.go b/api/types.go new file mode 100644 index 0000000..5353aea --- /dev/null +++ b/api/types.go @@ -0,0 +1,19 @@ +package api + +type AllClusters map[string][]Cluster + +type Cluster interface { + CreateCluster() error + DestroyCluster() error + GetCluster(name string) (Cluster, error) +} + +type ConfigStore interface { + CreateConfig(Cluster) error + DeleteConfig() error + UpdateConfig() error + ValidateConfig() error + CheckConfigExists() (bool, error) + GetConfig() ([]byte, error) + GetConfigs() (AllClusters, error) +} diff --git a/cmd/cli/server-mode.go b/cmd/cli/server-mode.go new file mode 100644 index 0000000..d051307 --- /dev/null +++ b/cmd/cli/server-mode.go @@ -0,0 +1,87 @@ +// Copyright © 2018 The TK8 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 cmd + +import ( + "log" + "os" + + "github.com/kubernauts/tk8/api/server" + "github.com/kubernauts/tk8/pkg/common" + "github.com/sirupsen/logrus" + "github.com/spf13/cobra" +) + +var serverCmd = &cobra.Command{ + Use: "server-mode", + Short: "Start tk8 in server mode", + Long: `server mode for TK8 , listens on port ::8091`, + Run: func(cmd *cobra.Command, args []string) { + if len(args) > 0 { + cmd.Help() + os.Exit(0) + } + + // Start the REST server as well as cli + if err := server.StartTK8API("tk8", common.REST_API_PORT); err != nil { + logrus.Printf("Unable to start cluster API server: %v", err) + } + + // server does not exit + select {} + }, + PreRun: func(cmd *cobra.Command, args []string) { + + if common.REST_API_PORT <= 0 { + log.Fatal("Port number cannot be zero") + } + switch common.REST_API_STORAGE { + case "local": + isExists := checkStoragePath(common.REST_API_STORAGEPATH) + if !isExists { + log.Fatalf("Storage path [ %s ] either doesnt exist or there is an error", common.REST_API_STORAGEPATH) + } + case "s3": + if common.REST_API_STORAGEREGION == "" { + log.Fatal("Region cannot be empty") + } + default: + log.Fatal("storage flag accepts local or s3 as valid values") + } + }, +} + +func init() { + serverCmd.Flags().Uint16VarP(&common.REST_API_PORT, "port", "p", 8091, "Port number for the Tk8 rest api") + serverCmd.Flags().StringVarP(&common.REST_API_STORAGE, "config-store", "s", "local", "Storage for config files - local or s3") + serverCmd.Flags().StringVarP(&common.REST_API_STORAGEPATH, "config-store-path", "a", ".", "Storage location for config files - directory path for local , bucket name for s3") + serverCmd.Flags().StringVarP(&common.REST_API_STORAGEREGION, "config-store-region", "r", "", "Region for S3 bucket") + + rootCmd.AddCommand(serverCmd) +} + +func checkStoragePath(path string) bool { + exists := true + src, err := os.Stat(path) + if os.IsNotExist(err) { + exists = false + } + if os.IsExist(err) { + if src.Mode().IsDir() { + exists = true + } + } + return exists +} diff --git a/internal/addon/main.go b/internal/addon/main.go index fa007cf..d9409b2 100644 --- a/internal/addon/main.go +++ b/internal/addon/main.go @@ -68,20 +68,23 @@ func (a *Addon) Get(addonNameOrGitPath string) (error, string) { } -func (a *Addon) Install(addonNameOrGitPath string, scope string) { +func (a *Addon) Install(addonNameOrGitPath string, scope string) error { _, addonName := a.Get(addonNameOrGitPath) fmt.Println("Install", addonName) err := executeMainSh(addonName, scope) if err != nil { fmt.Println("Error in executing main.sh , aborting addon installation.") - return + return err } err = applyMainYml(addonName) if err == nil { fmt.Println(addonName, "installation complete") } else { fmt.Println(err) + return err } + + return nil } // KubeConfig provide the path to the local kube config diff --git a/pkg/common/main.go b/pkg/common/main.go index 665957c..beadbc0 100644 --- a/pkg/common/main.go +++ b/pkg/common/main.go @@ -21,6 +21,11 @@ var ( GITCOMMIT = "0" // VERSION will hold the version number to be used in the version command. VERSION = "dev" + + REST_API_PORT uint16 + REST_API_STORAGE string + REST_API_STORAGEPATH string + REST_API_STORAGEREGION string ) // ErrorCheck is responsbile to check if there is any error returned by a command. diff --git a/server-mode.go b/server-mode.go new file mode 100644 index 0000000..d051307 --- /dev/null +++ b/server-mode.go @@ -0,0 +1,87 @@ +// Copyright © 2018 The TK8 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 cmd + +import ( + "log" + "os" + + "github.com/kubernauts/tk8/api/server" + "github.com/kubernauts/tk8/pkg/common" + "github.com/sirupsen/logrus" + "github.com/spf13/cobra" +) + +var serverCmd = &cobra.Command{ + Use: "server-mode", + Short: "Start tk8 in server mode", + Long: `server mode for TK8 , listens on port ::8091`, + Run: func(cmd *cobra.Command, args []string) { + if len(args) > 0 { + cmd.Help() + os.Exit(0) + } + + // Start the REST server as well as cli + if err := server.StartTK8API("tk8", common.REST_API_PORT); err != nil { + logrus.Printf("Unable to start cluster API server: %v", err) + } + + // server does not exit + select {} + }, + PreRun: func(cmd *cobra.Command, args []string) { + + if common.REST_API_PORT <= 0 { + log.Fatal("Port number cannot be zero") + } + switch common.REST_API_STORAGE { + case "local": + isExists := checkStoragePath(common.REST_API_STORAGEPATH) + if !isExists { + log.Fatalf("Storage path [ %s ] either doesnt exist or there is an error", common.REST_API_STORAGEPATH) + } + case "s3": + if common.REST_API_STORAGEREGION == "" { + log.Fatal("Region cannot be empty") + } + default: + log.Fatal("storage flag accepts local or s3 as valid values") + } + }, +} + +func init() { + serverCmd.Flags().Uint16VarP(&common.REST_API_PORT, "port", "p", 8091, "Port number for the Tk8 rest api") + serverCmd.Flags().StringVarP(&common.REST_API_STORAGE, "config-store", "s", "local", "Storage for config files - local or s3") + serverCmd.Flags().StringVarP(&common.REST_API_STORAGEPATH, "config-store-path", "a", ".", "Storage location for config files - directory path for local , bucket name for s3") + serverCmd.Flags().StringVarP(&common.REST_API_STORAGEREGION, "config-store-region", "r", "", "Region for S3 bucket") + + rootCmd.AddCommand(serverCmd) +} + +func checkStoragePath(path string) bool { + exists := true + src, err := os.Stat(path) + if os.IsNotExist(err) { + exists = false + } + if os.IsExist(err) { + if src.Mode().IsDir() { + exists = true + } + } + return exists +}