From 25c1569b0d964285cb6f0ab00d73023c83d88d12 Mon Sep 17 00:00:00 2001 From: lucklypriyansh-2 Date: Mon, 25 Sep 2023 20:35:33 +0530 Subject: [PATCH] Add initial Salad cloud virtual kubelet provider (#7) --- README.md | 31 +++ cmd/virtual-kubelet/main.go | 169 +++++++++++++--- go.mod | 5 +- go.sum | 36 +--- internal/models/error.go | 13 ++ internal/models/models.go | 56 ++++++ internal/provider/provider.go | 308 ++++++++++++++++++++++++++++- internal/provider/provider_util.go | 76 +++++++ internal/utils/utils.go | 75 +++++++ sample-deployment-duplicate.yaml | 98 +++++++++ sample-deployment.yaml | 31 +++ sample-pod.yaml | 22 +++ 12 files changed, 848 insertions(+), 72 deletions(-) create mode 100644 internal/models/error.go create mode 100644 internal/models/models.go create mode 100644 internal/provider/provider_util.go create mode 100644 internal/utils/utils.go create mode 100644 sample-deployment-duplicate.yaml create mode 100644 sample-deployment.yaml create mode 100644 sample-pod.yaml diff --git a/README.md b/README.md index 5fa2987..19e0842 100644 --- a/README.md +++ b/README.md @@ -3,3 +3,34 @@ [![License](https://img.shields.io/github/license/SaladTechnologies/virtual-kubelet-saladcloud)](./LICENSE) [![CI Workflow](https://github.com/SaladTechnologies/virtual-kubelet-saladcloud/actions/workflows/ci.yml/badge.svg?branch=main&event=push)](https://github.com/SaladTechnologies/virtual-kubelet-saladcloud/actions/workflows/ci.yml) [![Go Report Card](https://goreportcard.com/badge/github.com/SaladTechnologies/virtual-kubelet-saladcloud)](https://goreportcard.com/report/github.com/SaladTechnologies/virtual-kubelet-saladcloud) Salad's Virtual Kubelet (VK) provider for SaladCloud enables running Kubernetes (K8s) pods as container group deployments. + +To Setup the project +1. Clone the repo command +```bash +git clone +``` +2. Install the dependencies +```bash +go mod download +``` +3. Build the project +```bash +go build +``` +4. Run the project +```bash +go run main.go --nodename {valid_node_name} --projectName {projectName} --organizationName {organizationName} --api-key {api-key} --kubeconfig {kubeconfig} +``` + +## Prerequisites +You should have valid configuration required to run the project + +go to portal.salad.io and create a project and get the api-key and organization name +set the kubeconfig to the valid kubeconfig file + + +1. Valid ApiKey and OrganizationName +2. Valid Kubeconfig +3. Valid NodeName +4. Valid ProjectName + diff --git a/cmd/virtual-kubelet/main.go b/cmd/virtual-kubelet/main.go index 0889c16..d3a371f 100644 --- a/cmd/virtual-kubelet/main.go +++ b/cmd/virtual-kubelet/main.go @@ -2,71 +2,157 @@ package main import ( "context" + "errors" "fmt" "math/rand" + "os" + "os/signal" "path/filepath" + "github.com/SaladTechnologies/virtual-kubelet-saladcloud/internal/models" "github.com/SaladTechnologies/virtual-kubelet-saladcloud/internal/provider" "github.com/mitchellh/go-homedir" - log "github.com/sirupsen/logrus" + "github.com/sirupsen/logrus" + "github.com/spf13/cobra" + "github.com/virtual-kubelet/virtual-kubelet/errdefs" + "github.com/virtual-kubelet/virtual-kubelet/log" + logruslogger "github.com/virtual-kubelet/virtual-kubelet/log/logrus" "github.com/virtual-kubelet/virtual-kubelet/node" "github.com/virtual-kubelet/virtual-kubelet/node/nodeutil" + v1 "k8s.io/api/core/v1" ) +var ( + binaryFilename = filepath.Base(os.Args[0]) + description = fmt.Sprintf("%s implements a node on a Kubernetes cluster using Workload API to run pods.", binaryFilename) + inputs = defaultInputs() + letters = []rune("0123456789abcdefghijklmnopqrstuvwxyz") + taintEffectMapping = map[string]v1.TaintEffect{ + "NoSchedule": v1.TaintEffectNoSchedule, + "NoExecute": v1.TaintEffectNoExecute, + "PreferNoSchedule": v1.TaintEffectPreferNoSchedule, + } +) + +func defaultInputs() models.InputVars { + home, err := homedir.Dir() + kubeConfig := os.Getenv("KUBECONFIG") + if err == nil && home != "" { + kubeConfig = filepath.Join(home, ".kube", "config") + } + + return models.InputVars{ + NodeName: "saladcloud-edge-provider", + KubeConfig: kubeConfig, + LogLevel: "info", + OrganizationName: "", + TaintKey: "virtual-kubelet.io/provider", + TaintEffect: "NoSchedule", + TaintValue: "saladCloud", + ProjectName: "", + ApiKey: "", + } +} + func main() { - log.Info("SaladCloud Virtual Kubelet Provider") + ctx, cancel := signal.NotifyContext(context.Background(), os.Interrupt) + defer cancel() - name := fmt.Sprintf("saladcloud-%s", randSeq(8)) - node, err := nodeutil.NewNode(name, func(pc nodeutil.ProviderConfig) (nodeutil.Provider, node.NodeProvider, error) { - p, err := provider.NewSaladCloudProvider(context.Background()) - if err != nil { - return nil, nil, err - } + if err := virtualKubeletCommand.ExecuteContext(ctx); err != nil && !errors.Is(err, context.Canceled) { + logrus.WithError(err).Error("Failed to execute virtual kubelet command") + os.Exit(1) + } +} + +func initCommandFlags() { + virtualKubeletCommand.Flags().StringVar(&inputs.NodeName, "nodename", inputs.NodeName, "Kubernetes node name") + virtualKubeletCommand.Flags().StringVar(&inputs.KubeConfig, "kube-config", inputs.KubeConfig, "Kubeconfig file") + virtualKubeletCommand.Flags().StringVar(&inputs.OrganizationName, "organizationName", inputs.OrganizationName, "Organization name for Salad Client") + virtualKubeletCommand.Flags().BoolVar(&inputs.DisableTaint, "Disable taint flag", inputs.DisableTaint, "Disable the tainted effect") + virtualKubeletCommand.Flags().StringVar(&inputs.ApiKey, "api-key", inputs.ApiKey, "API key for the Salad Client") + virtualKubeletCommand.Flags().StringVar(&inputs.ProjectName, "projectName", inputs.ProjectName, "Project name for Salad Client") + + markFlagRequired("organizationName") + markFlagRequired("projectName") +} + +func markFlagRequired(flagName string) { + if err := virtualKubeletCommand.MarkFlagRequired(flagName); err != nil { + logrus.WithError(err).Errorf("Failed to mark %s as required", flagName) + os.Exit(1) + } +} + +func runNode(ctx context.Context) error { + logrus.Infof("Running node with name prefix: %s", inputs.NodeName) + inputs.NodeName = fmt.Sprintf("%s-%s", inputs.NodeName, randSeq(3)) - return p, nil, nil - }, withClient) + node, err := nodeutil.NewNode(inputs.NodeName, newSaladCloudProvider, withClient, withTaint) if err != nil { - log.Fatal(err) + logrus.WithError(err).Error("Failed to create new node") + return err } go func() { - if err := node.Run(context.Background()); err != nil { - log.Fatal(err) + if err := node.Run(ctx); err != nil { + logrus.WithError(err).Error("Node runtime error") } }() - err = node.WaitReady(context.Background(), 0) - if err != nil { - log.Fatal(err) + if err = node.WaitReady(ctx, 0); err != nil { + logrus.WithError(err).Error("Node readiness error") + return err } <-node.Done() - err = node.Err() + if err = node.Err(); err != nil { + logrus.WithError(err).Error("Node finished with error") + return err + } + return nil +} + +func newSaladCloudProvider(pc nodeutil.ProviderConfig) (nodeutil.Provider, node.NodeProvider, error) { + p, err := provider.NewSaladCloudProvider(context.Background(), inputs) if err != nil { - log.Fatal(err) + logrus.WithError(err).Error("Failed to create SaladCloud provider") + return nil, nil, err } + p.ConfigureNode(context.Background(), pc.Node) + return p, nil, nil } -func withClient(cfg *nodeutil.NodeConfig) error { - var conf string - home, _ := homedir.Dir() - if home != "" { - conf = filepath.Join(home, ".kube", "config") - } else { - conf = "config" +func withTaint(cfg *nodeutil.NodeConfig) error { + if inputs.DisableTaint { + return nil } - client, err := nodeutil.ClientsetFromEnv(conf) - if err != nil { + taintEffect, validEffect := taintEffectMapping[inputs.TaintEffect] + if !validEffect { + err := errdefs.InvalidInputf("Taint effect %q is not supported", inputs.TaintEffect) + logrus.WithError(err).Error("Invalid taint effect provided") return err } - return nodeutil.WithClient(client)(cfg) + cfg.NodeSpec.Spec.Taints = append(cfg.NodeSpec.Spec.Taints, v1.Taint{ + Key: inputs.TaintKey, + Value: inputs.TaintValue, + Effect: taintEffect, + }) + + return nil } -var letters = []rune("0123456789abcdefghijklmnopqrstuvwxyz") +func withClient(cfg *nodeutil.NodeConfig) error { + client, err := nodeutil.ClientsetFromEnv(inputs.KubeConfig) + if err != nil { + logrus.WithError(err).Error("Failed to retrieve clientset from environment") + return err + } + cfg.Client = client + return nil +} -// https://stackoverflow.com/a/22892986/2709066 func randSeq(n int) string { b := make([]rune, n) for i := range b { @@ -74,3 +160,26 @@ func randSeq(n int) string { } return string(b) } + +var virtualKubeletCommand = &cobra.Command{ + Use: binaryFilename, + Short: description, + Long: description, + Run: func(cmd *cobra.Command, args []string) { + logger := logrus.StandardLogger() + if logLevel, err := logrus.ParseLevel(inputs.LogLevel); err == nil { + logger.SetLevel(logLevel) + } else { + logrus.WithError(err).Error("Failed to parse log level, defaulting to INFO") + } + + ctx := log.WithLogger(cmd.Context(), logruslogger.FromLogrus(logrus.NewEntry(logger))) + if err := runNode(ctx); err != nil { + logrus.WithError(err).Fatal("Node failed to run") + } + }, +} + +func init() { + initCommandFlags() +} diff --git a/go.mod b/go.mod index 3f39e85..441c7d7 100644 --- a/go.mod +++ b/go.mod @@ -1,8 +1,9 @@ module github.com/SaladTechnologies/virtual-kubelet-saladcloud -go 1.21.0 +go 1.20 require ( + github.com/lucklypriyansh-2/salad-client v0.0.0-20230902193233-7c5a02b4c6d7 github.com/prometheus/client_model v0.4.0 github.com/sirupsen/logrus v1.9.3 github.com/virtual-kubelet/virtual-kubelet v1.10.0 @@ -55,7 +56,7 @@ require ( github.com/prometheus/client_golang v1.16.0 // indirect github.com/prometheus/common v0.44.0 // indirect github.com/prometheus/procfs v0.10.1 // indirect - github.com/spf13/cobra v1.7.0 // indirect + github.com/spf13/cobra v1.7.0 github.com/spf13/pflag v1.0.5 // indirect github.com/stoewer/go-strcase v1.2.0 // indirect go.etcd.io/etcd/api/v3 v3.5.9 // indirect diff --git a/go.sum b/go.sum index 377cc38..4dadc81 100644 --- a/go.sum +++ b/go.sum @@ -1,26 +1,21 @@ cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= cloud.google.com/go v0.110.0 h1:Zc8gqp3+a9/Eyph2KDmcGaPtbKRIoqq4YTlL4NMD0Ys= cloud.google.com/go/compute v1.19.0 h1:+9zda3WGgW1ZSTlVppLCYFIr48Pa35q1uG2N1itbCEQ= -cloud.google.com/go/compute v1.19.0/go.mod h1:rikpw2y+UMidAe9tISo04EHNOIf42RLYF/q8Bs93scU= cloud.google.com/go/compute/metadata v0.2.3 h1:mg4jlk7mCAj6xXp9UJ4fjI9VUI5rubuGBW5aJ7UnBMY= -cloud.google.com/go/compute/metadata v0.2.3/go.mod h1:VAV5nSsACxMJvgaAuX6Pk2AawlZn8kiOGuCv6gTkwuA= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/NYTimes/gziphandler v1.1.1 h1:ZUDjpQae29j0ryrS0u/B8HZfJBtBQHjqw2rQ2cqUQ3I= github.com/NYTimes/gziphandler v1.1.1/go.mod h1:n/CVRwUEOgIxrgPvAQhUUr9oeUtvrhMomdKFjzJNB0c= github.com/antlr/antlr4/runtime/Go/antlr/v4 v4.0.0-20230305170008-8188dc5388df h1:7RFfzj4SSt6nnvCPbCqijJi1nWCd+TqAT3bYCStRC18= github.com/antlr/antlr4/runtime/Go/antlr/v4 v4.0.0-20230305170008-8188dc5388df/go.mod h1:pSwJ0fSY5KhvocuWSx4fz3BA8OrA1bQn+K1Eli3BRwM= github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5 h1:0CwZNZbxp69SHPdPJAN/hZIm0C4OItdklCFmMRWYpio= -github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5/go.mod h1:wHh0iHkYZB8zMSxRWpUBQtwG5a7fFgvEO+odwuTv2gs= github.com/asaskevich/govalidator v0.0.0-20190424111038-f61b66f89f4a h1:idn718Q4B6AGu/h5Sxe66HYVdqdGu2l9Iebqhi/AEoA= github.com/asaskevich/govalidator v0.0.0-20190424111038-f61b66f89f4a/go.mod h1:lB+ZfQJz7igIIfQNfa7Ml4HSf2uFQQRzpGGRXenZAgY= github.com/benbjohnson/clock v1.1.0 h1:Q92kusRqC1XV2MjkWETPvjJVqKetz1OzxZB7mHJLju8= -github.com/benbjohnson/clock v1.1.0/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA= github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= github.com/blang/semver/v4 v4.0.0 h1:1PFHFE6yCCTv8C1TeyNNarDzntLi7wMI5i/pzqYIsAM= github.com/blang/semver/v4 v4.0.0/go.mod h1:IbckMUScFkM3pff0VJDNKRiT6TG/YpiHIM2yvyW5YoQ= github.com/bombsimon/logrusr/v3 v3.0.0 h1:tcAoLfuAhKP9npBxWzSdpsvKPQt1XV02nSf2lZA82TQ= -github.com/bombsimon/logrusr/v3 v3.0.0/go.mod h1:PksPPgSFEL2I52pla2glgCyyd2OqOHAnFF5E+g8Ixco= github.com/cenkalti/backoff/v4 v4.2.1 h1:y4OZtCnogmCPw98Zjyt5a6+QwPLGkiQsYW5oUqylYbM= github.com/cenkalti/backoff/v4 v4.2.1/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE= github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= @@ -38,7 +33,6 @@ github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSs github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY= -github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto= github.com/emicklei/go-restful/v3 v3.9.0 h1:XwGDlfxEnQZzuopoqxwSEllNcCOM9DhhFyhFIIGKwxE= github.com/emicklei/go-restful/v3 v3.9.0/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc= github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= @@ -48,7 +42,6 @@ github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7 github.com/evanphx/json-patch v5.6.0+incompatible h1:jBYDEEiFBPxA0v50tFdvOzQQTCvpL6mnFh5mB2/l16U= github.com/evanphx/json-patch v5.6.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk= github.com/evanphx/json-patch/v5 v5.6.0 h1:b91NhWfaz02IuVxO9faSllyAtNXHMPkC5J8sJCLunww= -github.com/evanphx/json-patch/v5 v5.6.0/go.mod h1:G79N1coSVB93tBe7j6PhzjmR3/2VvlbKOFpnXhI9Bw4= github.com/felixge/httpsnoop v1.0.3 h1:s/nj+GCswXYzN5v2DpNMuMQYe+0DDwt5WVCU6CWBdXk= github.com/felixge/httpsnoop v1.0.3/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= github.com/fsnotify/fsnotify v1.6.0 h1:n+5WquG0fcWoWp6xPWfHdbskMCQaFnG6PfBrh1Ky4HY= @@ -60,7 +53,6 @@ github.com/go-logr/logr v1.2.4/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbV github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= github.com/go-logr/zapr v1.2.3 h1:a9vnzlIBPQBBkeaR9IuMUfmVOrQlkoC4YfPoFkX3T7A= -github.com/go-logr/zapr v1.2.3/go.mod h1:eIauM6P8qSvTw5o2ez6UEAfGjQKrxQTl5EoK+Qa2oG4= github.com/go-openapi/jsonpointer v0.19.6 h1:eCs3fxoIi3Wh6vtgmLTOjdhSpiqphQ+DaPn38N2ZdrE= github.com/go-openapi/jsonpointer v0.19.6/go.mod h1:osyAmYz/mB/C3I+WsTTSgw1ONzaLJoLCyoi6/zppojs= github.com/go-openapi/jsonreference v0.20.2 h1:3sVjiK66+uXK/6oQ8xgcRKcFgQ5KXa2KvnJRumpMGbE= @@ -68,15 +60,12 @@ github.com/go-openapi/jsonreference v0.20.2/go.mod h1:Bl1zwGIM8/wsvqjsOQLJ/SH+En github.com/go-openapi/swag v0.22.3 h1:yMBqmnQ0gyZvEb/+KzuWZOXgllrXT4SADYbvDaXHv/g= github.com/go-openapi/swag v0.22.3/go.mod h1:UzaqsxGiab7freDnrUUra0MwWfN/q7tE4j+VcZ0yl14= github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 h1:tfuBGBXKqDEevZMzYi5KSi8KkcZtzBcTgAUUtapy0OI= -github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572/go.mod h1:9Pwr4B2jHnOSGXyyzV8ROjYa2ojvAY6HCGYYfMoC3Ls= github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= 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/v4 v4.5.0 h1:7cYmW1XlMY7h7ii7UhUyChSgS5wUJEnm9uZVTGqOWzg= -github.com/golang-jwt/jwt/v4 v4.5.0/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= github.com/golang/glog v1.1.0 h1:/d3pCKDPWNnvIWe0vVUpNP32qc8U3PDVxySP/y360qE= -github.com/golang/glog v1.1.0/go.mod h1:pfYeQZ3JWZoXTV5sFc986z3HTpwQs9At6P4ImfuP3NQ= github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da h1:oI5xCqsCo564l8iNU+DwB5epxmsaqB+rhGL0m5jtYqE= github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= @@ -95,7 +84,6 @@ github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaS github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg= github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= github.com/google/btree v1.0.1 h1:gK4Kx5IaGY9CD5sPJ36FHiBJ6ZXl0kilRiiCj+jdYp4= -github.com/google/btree v1.0.1/go.mod h1:xXMiIv4Fb/0kKde4SpL7qlzvu5cMJDRkFDxJfI9uaxA= github.com/google/cel-go v0.16.0 h1:DG9YQ8nFCFXAs/FDDwBxmL1tpKNrdlGUM9U3537bX/Y= github.com/google/cel-go v0.16.0/go.mod h1:HXZKzB0LXqer5lHHgfWAnlYwJaQBDKMjxjulNQzhwhY= github.com/google/gnostic-models v0.6.8 h1:yo/ABAfM5IMRsS1VnXjTBvUb61tFIHozhlYvRgGre9I= @@ -113,7 +101,6 @@ github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/ github.com/google/gofuzz v1.2.0 h1:xRy4A+RhZaiKjJ1bPfwQ8sedCA+YS2YcCHW6ec7JMi0= github.com/google/gofuzz v1.2.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/pprof v0.0.0-20210720184732-4bb14d4b1be1 h1:K6RDEckDVWvDI9JAJYCmNdQXq6neHJOYx3V6jnqNEec= -github.com/google/pprof v0.0.0-20210720184732-4bb14d4b1be1/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I= github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= @@ -122,11 +109,9 @@ github.com/gorilla/mux v1.8.0/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB7 github.com/gorilla/websocket v1.4.2 h1:+/TMaTYc4QFitKJxsQ7Yye35DkWvkdLcvGKqM+x0Ufc= github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= github.com/grpc-ecosystem/go-grpc-middleware v1.3.0 h1:+9834+KizmvFV7pXQGSXQTsaWhq2GjuNUt0aUU0YBYw= -github.com/grpc-ecosystem/go-grpc-middleware v1.3.0/go.mod h1:z0ButlSOZa5vEBq9m2m2hlwIgKw+rp3sdCBRoJY+30Y= github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0 h1:Ovs26xHkKqVztRpIrF/92BcuyuQ/YW4NSIpoGtfXNho= github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk= github.com/grpc-ecosystem/grpc-gateway v1.16.0 h1:gmcG1KaJ57LophUzW0Hy8NmPhnMZb4M0+kPpLofRdBo= -github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw= github.com/grpc-ecosystem/grpc-gateway/v2 v2.16.0 h1:YBftPWNWd4WwGqtY2yeZL2ef8rHAxPBD8KFhJpmcqms= github.com/grpc-ecosystem/grpc-gateway/v2 v2.16.0/go.mod h1:YN5jB8ie0yfIUg6VvR9Kz84aCaG7AsGZnLjhHbUqwPg= github.com/imdario/mergo v0.3.12 h1:b6R2BslTbIEToALKP7LxUvijTsNI9TAe80pLWN2g/HU= @@ -134,7 +119,6 @@ github.com/imdario/mergo v0.3.12/go.mod h1:jmQim1M+e3UYxmgPu/WyfjB3N3VflVyUjjjwH github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= github.com/jonboulle/clockwork v0.2.2 h1:UOGuzwb1PwsrDAObMuhUnj0p5ULPj8V/xJ7Kx9qUBdQ= -github.com/jonboulle/clockwork v0.2.2/go.mod h1:Pkfl5aHPm1nk2H9h0bjmnJD/BcgbGXUBGnn1kMkgxc8= github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY= github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y= github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= @@ -143,11 +127,12 @@ github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= -github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= +github.com/lucklypriyansh-2/salad-client v0.0.0-20230902193233-7c5a02b4c6d7 h1:N7D71xaSmphJuSZdtJnhq/B5sF2/qEkId00LhrqWusg= +github.com/lucklypriyansh-2/salad-client v0.0.0-20230902193233-7c5a02b4c6d7/go.mod h1:VjkYRyCaHqCXvxGIDnUCRg8QEcvOuOkhNnbHsrAPETc= github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0= github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc= github.com/matttproud/golang_protobuf_extensions v1.0.4 h1:mmDVorXM7PCGKw94cs5zkfA9PSy5pEvNWRP0ET0TIVo= @@ -164,9 +149,7 @@ github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjY github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA= github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= github.com/onsi/ginkgo/v2 v2.9.4 h1:xR7vG4IXt5RWx6FfIjyAtsoMAtnc3C/rFXBBd2AjZwE= -github.com/onsi/ginkgo/v2 v2.9.4/go.mod h1:gCQYp2Q+kSoIj7ykSVb9nskRSsR6PUj4AiLywzIhbKM= github.com/onsi/gomega v1.27.6 h1:ENqfyGeS5AX/rlXDd/ETokDz93u0YufY1Pgxuy/PvWE= -github.com/onsi/gomega v1.27.6/go.mod h1:PIQNjfQwkP3aQAH7lf7j87O/5FiNr+ZR8+ipb+qQlhg= github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= @@ -181,12 +164,10 @@ github.com/prometheus/common v0.44.0/go.mod h1:ofAIvZbQ1e/nugmZGz4/qCb9Ap1VoSTIO github.com/prometheus/procfs v0.10.1 h1:kYK1Va/YMlutzCGazswoHKo//tZVlFpKYh+PymziUAg= github.com/prometheus/procfs v0.10.1/go.mod h1:nwNm2aOCAYw8uTR/9bWRREkZFxAUcWzPHWJq+XBB/FM= github.com/rogpeppe/go-internal v1.10.0 h1:TMyTOH3F/DB16zRVcYyreMH6GnZZrwQVAoYjRBZyWFQ= -github.com/rogpeppe/go-internal v1.10.0/go.mod h1:UQnix2H7Ngw/k4C5ijL5+65zddjncjaFoBhdsK/akog= github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ= github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= github.com/soheilhy/cmux v0.1.5 h1:jjzc5WVemNEDTLwv9tlmemhC73tI08BNOIGwBOo10Js= -github.com/soheilhy/cmux v0.1.5/go.mod h1:T7TcVDs9LWfQgPlPsdngu6I6QIoyIFZDDC6sNE1GqG0= github.com/spf13/cobra v1.7.0 h1:hyqWnYt1ZQShIddO5kBpj3vu05/++x6tJ6dg8EC572I= github.com/spf13/cobra v1.7.0/go.mod h1:uLxZILRyS/50WlhOIKD7W6V5bgeIt+4sICxh6uRMrb0= github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= @@ -203,31 +184,23 @@ github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/ github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= github.com/stretchr/testify v1.8.3 h1:RP3t2pwF7cMEbC1dqtB6poj3niw/9gnV4Cjg5oW5gtY= -github.com/stretchr/testify v1.8.3/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= github.com/tmc/grpc-websocket-proxy v0.0.0-20220101234140-673ab2c3ae75 h1:6fotK7otjonDflCTK0BCfls4SPy3NcCVb5dqqmbRknE= -github.com/tmc/grpc-websocket-proxy v0.0.0-20220101234140-673ab2c3ae75/go.mod h1:KO6IkyS8Y3j8OdNO85qEYBsRPuteD+YciPomcXdrMnk= github.com/virtual-kubelet/virtual-kubelet v1.10.0 h1:eV/mFFqThOJLz7Gjn1Ev8LchanGKGA2qZlsW6wipb4g= github.com/virtual-kubelet/virtual-kubelet v1.10.0/go.mod h1:7Pvdei1p82C9uWS1VzLrnXbHTwQcGBoqShahChpacgI= github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2 h1:eY9dn8+vbi4tKz5Qo6v2eYzo7kUS51QINcR5jNpbZS8= -github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU= github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= go.etcd.io/bbolt v1.3.7 h1:j+zJOnnEjF/kyHlDDgGnVL/AIqIJPq8UoB2GSNfkUfQ= -go.etcd.io/bbolt v1.3.7/go.mod h1:N9Mkw9X8x5fupy0IKsmuqVtoGDyxsaDlbk4Rd05IAQw= go.etcd.io/etcd/api/v3 v3.5.9 h1:4wSsluwyTbGGmyjJktOf3wFQoTBIURXHnq9n/G/JQHs= go.etcd.io/etcd/api/v3 v3.5.9/go.mod h1:uyAal843mC8uUVSLWz6eHa/d971iDGnCRpmKd2Z+X8k= go.etcd.io/etcd/client/pkg/v3 v3.5.9 h1:oidDC4+YEuSIQbsR94rY9gur91UPL6DnxDCIYd2IGsE= go.etcd.io/etcd/client/pkg/v3 v3.5.9/go.mod h1:y+CzeSmkMpWN2Jyu1npecjB9BBnABxGM4pN8cGuJeL4= go.etcd.io/etcd/client/v2 v2.305.9 h1:YZ2OLi0OvR0H75AcgSUajjd5uqKDKocQUqROTG11jIo= -go.etcd.io/etcd/client/v2 v2.305.9/go.mod h1:0NBdNx9wbxtEQLwAQtrDHwx58m02vXpDcgSYI2seohQ= go.etcd.io/etcd/client/v3 v3.5.9 h1:r5xghnU7CwbUxD/fbUtRyJGaYNfDun8sp/gTr1hew6E= go.etcd.io/etcd/client/v3 v3.5.9/go.mod h1:i/Eo5LrZ5IKqpbtpPDuaUnDOUv471oDg8cjQaUr2MbA= go.etcd.io/etcd/pkg/v3 v3.5.9 h1:6R2jg/aWd/zB9+9JxmijDKStGJAPFsX3e6BeJkMi6eQ= -go.etcd.io/etcd/pkg/v3 v3.5.9/go.mod h1:BZl0SAShQFk0IpLWR78T/+pyt8AruMHhTNNX73hkNVY= go.etcd.io/etcd/raft/v3 v3.5.9 h1:ZZ1GIHoUlHsn0QVqiRysAm3/81Xx7+i2d7nSdWxlOiI= -go.etcd.io/etcd/raft/v3 v3.5.9/go.mod h1:WnFkqzFdZua4LVlVXQEGhmooLeyS7mqzS4Pf4BCVqXg= go.etcd.io/etcd/server/v3 v3.5.9 h1:vomEmmxeztLtS5OEH7d0hBAg4cjVIu9wXuNzUZx2ZA0= -go.etcd.io/etcd/server/v3 v3.5.9/go.mod h1:GgI1fQClQCFIzuVjlvdbMxNbnISt90gdfYyqiAIt65g= go.opencensus.io v0.24.0 h1:y73uSU6J157QMP2kn2r30vwW1A2W2WFwSCGnAVxeaD0= go.opencensus.io v0.24.0/go.mod h1:vNK8G9p7aAivkbmorf4v+7Hgx+Zs0yY+0fOtgBfjQKo= go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.42.0 h1:ZOLJc06r4CB42laIXg/7udr0pbZyuAihN10A/XuiQRY= @@ -253,7 +226,6 @@ go.opentelemetry.io/proto/otlp v0.20.0/go.mod h1:3QgjzPALBIv9pcknj2EXGPXjYPFdUh/ go.uber.org/atomic v1.10.0 h1:9qC72Qh0+3MqyJbAn8YU5xVq1frD8bn3JtD2oXtafVQ= go.uber.org/atomic v1.10.0/go.mod h1:LUxbIzbOniOlMKjJjyPfpl4v+PKK2cNJn91OQbhoJI0= go.uber.org/goleak v1.2.1 h1:NBol2c7O1ZokfZ0LEU9K6Whx/KnwvepVetCUhtKja4A= -go.uber.org/goleak v1.2.1/go.mod h1:qlT2yGI9QafXHhZZLxlSuNsMw3FFLxBr+tBRlmO1xH4= go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0= go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y= go.uber.org/zap v1.24.0 h1:FiJd5l1UOLj0wCgbSE0rwwXHzEdAZS6hiiSnxJN/D60= @@ -320,7 +292,6 @@ golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtn golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.8.0 h1:vSDcovVPld282ceKgDimkRSC8kpaH1dgyc9UMzlt84Y= -golang.org/x/tools v0.8.0/go.mod h1:JxBZ99ISMI5ViVkT1tr6tdNmXeTrcpVSD3vZ1RsRdN4= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= @@ -374,13 +345,11 @@ gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gotest.tools v2.2.0+incompatible h1:VsBPFP1AI068pPrMxtb/S8Zkgf9xEmTLJjfM+P5UIEo= -gotest.tools v2.2.0+incompatible/go.mod h1:DsYFclhRJ6vuDpmuTbkuFWG+y2sxOXAzmJt81HFBacw= honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= k8s.io/api v0.28.1 h1:i+0O8k2NPBCPYaMB+uCkseEbawEt/eFaiRqUx8aB108= k8s.io/api v0.28.1/go.mod h1:uBYwID+66wiL28Kn2tBjBYQdEU0Xk0z5qF8bIBqk/Dg= k8s.io/apiextensions-apiserver v0.27.2 h1:iwhyoeS4xj9Y7v8YExhUwbVuBhMr3Q4bd/laClBV6Bo= -k8s.io/apiextensions-apiserver v0.27.2/go.mod h1:Oz9UdvGguL3ULgRdY9QMUzL2RZImotgxvGjdWRq6ZXQ= k8s.io/apimachinery v0.28.1 h1:EJD40og3GizBSV3mkIoXQBsws32okPOy+MkRyzh6nPY= k8s.io/apimachinery v0.28.1/go.mod h1:X0xh/chESs2hP9koe+SdIAcXWcQ+RM5hy0ZynB+yEvw= k8s.io/apiserver v0.28.1 h1:dw2/NKauDZCnOUAzIo2hFhtBRUo6gQK832NV8kuDbGM= @@ -400,7 +369,6 @@ k8s.io/utils v0.0.0-20230726121419-3b25d923346b/go.mod h1:OLgZIPagt7ERELqWJFomSt sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.1.4 h1:1RSHUg/47zxbcYkN4r+zMS8ZObRFpyDDBkcmWjTD5vM= sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.1.4/go.mod h1:e7I0gvW7fYKOqZDDsvaETBEyfM4dXh6DQ/SsqNInVC0= sigs.k8s.io/controller-runtime v0.15.0 h1:ML+5Adt3qZnMSYxZ7gAverBLNPSMQEibtzAgp0UPojU= -sigs.k8s.io/controller-runtime v0.15.0/go.mod h1:7ngYvp1MLT+9GeZ+6lH3LOlcHkp/+tzA/fmHa4iq9kk= sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd h1:EDPBXCAspyGV4jQlpZSudPeMmr1bNJefnuqLsRAsHZo= sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd/go.mod h1:B8JuhiUyNFVKdsE8h686QcCxMaH6HrOAZj4vswFpcB0= sigs.k8s.io/structured-merge-diff/v4 v4.3.0 h1:UZbZAZfX0wV2zr7YZorDz6GXROfDFj6LvqCRm4VUVKk= diff --git a/internal/models/error.go b/internal/models/error.go new file mode 100644 index 0000000..c99f816 --- /dev/null +++ b/internal/models/error.go @@ -0,0 +1,13 @@ +package models + +type InvalidClientSecretError struct{} + +func NewInvalidClientSecretError() *InvalidClientSecretError { + return &InvalidClientSecretError{} +} + +func (e *InvalidClientSecretError) Error() string { + return "invalid SaladCloud client api key" +} + +//Resolve error from response diff --git a/internal/models/models.go b/internal/models/models.go new file mode 100644 index 0000000..4fd7856 --- /dev/null +++ b/internal/models/models.go @@ -0,0 +1,56 @@ +package models + +type InputVars struct { + NodeName string + KubeConfig string + DisableTaint bool + LogLevel string + TaintKey string + TaintEffect string + TaintValue string + OrganizationName string + ProjectName string + ApiKey string +} + +type CreateContainerGroupModel struct { + Name string `json:"name"` + Container ContainerSpec `json:"container"` + RestartPolicy string `json:"restart_policy"` + LivenessProbe Probe `json:"liveness_probe"` + ReadinessProbe Probe `json:"readiness_probe"` + StartupProbe Probe `json:"startup_probe"` + Annotations map[string]string `json:"annotations"` +} + +type ContainerSpec struct { + Resources Resources `json:"resources"` + EnvironmentVariables map[string]string `json:"environment_variables"` + Image string `json:"image"` + Command []string `json:"command"` +} + +type Resources struct { + Requests ResourceList `json:"requests"` + Limits ResourceList `json:"limits"` +} + +type ResourceList struct { + CPU string `json:"cpu"` + Memory string `json:"memory"` +} + +type Probe struct { + HTTPGetAction *HTTPGetAction `json:"httpGet,omitempty"` + InitialDelaySeconds int32 `json:"initialDelaySeconds,omitempty"` + PeriodSeconds int32 `json:"periodSeconds,omitempty"` + TimeoutSeconds int32 `json:"timeoutSeconds,omitempty"` + SuccessThreshold int32 `json:"successThreshold,omitempty"` + FailureThreshold int32 `json:"failureThreshold,omitempty"` +} + +type HTTPGetAction struct { + Path string `json:"path"` + Port int32 `json:"port"` + Scheme string `json:"scheme"` +} diff --git a/internal/provider/provider.go b/internal/provider/provider.go index 9632736..59658c5 100644 --- a/internal/provider/provider.go +++ b/internal/provider/provider.go @@ -2,22 +2,146 @@ package provider import ( "context" - "io" - + "encoding/json" + "github.com/SaladTechnologies/virtual-kubelet-saladcloud/internal/models" + "github.com/SaladTechnologies/virtual-kubelet-saladcloud/internal/utils" + saladclient "github.com/lucklypriyansh-2/salad-client" dto "github.com/prometheus/client_model/go" + "github.com/virtual-kubelet/virtual-kubelet/log" nodeapi "github.com/virtual-kubelet/virtual-kubelet/node/api" "github.com/virtual-kubelet/virtual-kubelet/node/api/statsv1alpha1" + "github.com/virtual-kubelet/virtual-kubelet/trace" + "io" corev1 "k8s.io/api/core/v1" + "k8s.io/apimachinery/pkg/api/resource" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "strings" + "time" ) type SaladCloudProvider struct { + inputVars models.InputVars + cpu string + memory string + pods string + storage string + operatingSystem string + apiClient *saladclient.APIClient + countryCodes []saladclient.CountryCode +} + +const ( + defaultCPUCoresNumber = "10000" + defaultMemorySize = "1Ti" + defaultStorageSize = "1Ti" + defaultPodsLimit = "1000" + defaultOperatingSystem = "Linux" +) + +func NewSaladCloudProvider(ctx context.Context, inputVars models.InputVars) (*SaladCloudProvider, error) { + cloudProvider := &SaladCloudProvider{ + inputVars: inputVars, + apiClient: saladclient.NewAPIClient(saladclient.NewConfiguration()), + } + cloudProvider.setNodeCapacity() + cloudProvider.setCountryCodes([]string{"US"}) + + return cloudProvider, nil +} +func (p *SaladCloudProvider) setCountryCodes(countries []string) { + for _, countryCode := range countries { + p.countryCodes = append(p.countryCodes, saladclient.CountryCode(countryCode)) + } +} + +func (p *SaladCloudProvider) setNodeCapacity() { + p.cpu = defaultCPUCoresNumber + p.memory = defaultMemorySize + p.pods = defaultPodsLimit + p.storage = defaultStorageSize + p.operatingSystem = defaultOperatingSystem +} + +func (p *SaladCloudProvider) ConfigureNode(ctx context.Context, node *corev1.Node) { + node.Status.Capacity = p.getNodeCapacity() + node.Status.Allocatable = p.getNodeCapacity() + node.Status.NodeInfo.OperatingSystem = p.operatingSystem } -func NewSaladCloudProvider(ctx context.Context) (*SaladCloudProvider, error) { - return &SaladCloudProvider{}, nil +func (p *SaladCloudProvider) getNodeCapacity() corev1.ResourceList { + resourceList := corev1.ResourceList{ + corev1.ResourceCPU: resource.MustParse(p.cpu), + corev1.ResourceMemory: resource.MustParse(p.memory), + corev1.ResourcePods: resource.MustParse(p.pods), + corev1.ResourceStorage: resource.MustParse(p.storage), + } + + return resourceList } func (p *SaladCloudProvider) CreatePod(ctx context.Context, pod *corev1.Pod) error { + ctx, span := trace.StartSpan(ctx, "CreatePod") + defer span.End() + log.G(ctx).Debug("creating a CreatePod", pod.Name) + + createContainerObject := p.createContainersObject(pod) + createContainerGroup := p.createContainerGroup(createContainerObject, pod) + + _, r, err := p.apiClient. + ContainerGroupsAPI.CreateContainerGroup( + p.contextWithAuth(), + p.inputVars.OrganizationName, + p.inputVars.ProjectName).CreateContainerGroup( + createContainerGroup[0], + ).Execute() + if err != nil { + log.G(ctx).Errorf("Error when calling `ContainerGroupsAPI.CreateContainerGroupModel`", r) + return err + } + + // wait for 3 second + time.Sleep(3 * time.Second) + + startHttpResponse, err := p.apiClient.ContainerGroupsAPI.StartContainerGroup(p.contextWithAuth(), p.inputVars.OrganizationName, p.inputVars.ProjectName, utils.GetPodName(pod.Namespace, pod.Name, nil)).Execute() + if err != nil { + log.G(ctx).Errorf("Error when calling `ContainerGroupsAPI.CreateContainerGroupModel`", startHttpResponse) + err := p.DeletePod(ctx, pod) + if err != nil { + return err + } + return err + } + + now := metav1.NewTime(time.Now()) + pod.ObjectMeta.CreationTimestamp = now + pod.Status = corev1.PodStatus{ + Phase: corev1.PodRunning, + StartTime: &now, + Conditions: []corev1.PodCondition{ + { + Type: corev1.PodInitialized, + Status: corev1.ConditionTrue, + }, + { + Type: corev1.PodReady, + Status: corev1.ConditionTrue, + }, + { + Type: corev1.PodScheduled, + Status: corev1.ConditionTrue, + }, + }, + } + for _, container := range pod.Spec.Containers { + pod.Status.ContainerStatuses = append(pod.Status.ContainerStatuses, corev1.ContainerStatus{ + Name: container.Name, + Image: container.Image, + Ready: true, + RestartCount: 0, + }) + } + + log.G(ctx).Infof("Done creating the container and initiating the startup ", pod) return nil } @@ -26,19 +150,135 @@ func (p *SaladCloudProvider) UpdatePod(ctx context.Context, pod *corev1.Pod) err } func (p *SaladCloudProvider) DeletePod(ctx context.Context, pod *corev1.Pod) error { + ctx, span := trace.StartSpan(ctx, "DeletePod") + defer span.End() + log.G(ctx).Debug("deleting a pod") + response, err := p.apiClient.ContainerGroupsAPI.DeleteContainerGroup(p.contextWithAuth(), p.inputVars.OrganizationName, p.inputVars.ProjectName, utils.GetPodName(pod.Namespace, pod.Name, pod)).Execute() + pod.Status.Phase = corev1.PodSucceeded + pod.Status.Reason = "Pod Deleted" + if err != nil { + log.G(ctx).Errorf("Error when deleting the container ", response) + return err + } + now := metav1.Now() + for idx := range pod.Status.ContainerStatuses { + pod.Status.ContainerStatuses[idx].Ready = false + pod.Status.ContainerStatuses[idx].State = corev1.ContainerState{ + Terminated: &corev1.ContainerStateTerminated{ + Message: "Salad Provider Pod Deleted", + FinishedAt: now, + Reason: "Salad Provider Pod Deleted", + }, + } + } + log.G(ctx).Infof("Done deleting the container ", pod) return nil } func (p *SaladCloudProvider) GetPod(ctx context.Context, namespace string, name string) (*corev1.Pod, error) { - return nil, nil + + resp, r, err := saladclient.NewAPIClient(saladclient.NewConfiguration()).ContainerGroupsAPI.GetContainerGroup(p.contextWithAuth(), p.inputVars.OrganizationName, p.inputVars.ProjectName, utils.GetPodName(namespace, name, nil)).Execute() + if err != nil { + log.G(ctx).Errorf("Error when calling `ContainerGroupsAPI.GetPod`", r) + return nil, err + } + startTime := metav1.NewTime(resp.CreateTime) + pod := &corev1.Pod{ + Spec: corev1.PodSpec{ + Containers: []corev1.Container{ + { + Name: resp.Name, + Image: resp.Container.Image, + }, + }, + }, + Status: corev1.PodStatus{ + Phase: utils.GetPodPhaseFromContainerGroupState(resp.CurrentState), + StartTime: &startTime, + ContainerStatuses: []corev1.ContainerStatus{ + { + Name: resp.Name, + Image: resp.Container.Image, + Ready: utils.GetPodPhaseFromContainerGroupState(resp.CurrentState) == corev1.PodRunning, + }, + }, + }, + } + + return pod, nil +} + +func (p *SaladCloudProvider) contextWithAuth() context.Context { + auth := context.WithValue( + context.Background(), + saladclient.ContextAPIKeys, + map[string]saladclient.APIKey{ + "ApiKeyAuth": {Key: p.inputVars.ApiKey}, + }, + ) + return auth } func (p *SaladCloudProvider) GetPodStatus(ctx context.Context, namespace string, name string) (*corev1.PodStatus, error) { - return nil, nil + ctx, span := trace.StartSpan(ctx, "GetPodStatus") + defer span.End() + + containerGroup, response, err := p.apiClient.ContainerGroupsAPI.GetContainerGroup(p.contextWithAuth(), p.inputVars.OrganizationName, p.inputVars.ProjectName, utils.GetPodName(namespace, name, nil)).Execute() + if err != nil { + log.G(ctx).Errorf("ContainerGroupsAPI.GetPodStatus ", response) + return nil, err + } + + startTime := metav1.NewTime(containerGroup.CreateTime) + return &corev1.PodStatus{ + Phase: utils.GetPodPhaseFromContainerGroupState(containerGroup.CurrentState), + StartTime: &startTime, + ContainerStatuses: []corev1.ContainerStatus{ + { + Name: containerGroup.Name, + Image: containerGroup.Container.Image, + Ready: utils.GetPodPhaseFromContainerGroupState(containerGroup.CurrentState) == corev1.PodRunning, + }, + }, + }, nil + } func (p *SaladCloudProvider) GetPods(ctx context.Context) ([]*corev1.Pod, error) { + + resp, r, err := p.apiClient.ContainerGroupsAPI.ListContainerGroups(p.contextWithAuth(), p.inputVars.OrganizationName, p.inputVars.ProjectName).Execute() + if err != nil { + log.G(ctx).Errorf("Error when list ContainerGroupsAPI.ListContainerGroups ", r) + return nil, err + } pods := make([]*corev1.Pod, 0) + for _, containerGroup := range resp.GetItems() { + startTime := metav1.NewTime(containerGroup.CreateTime) + pod := &corev1.Pod{ + Spec: corev1.PodSpec{ + Containers: []corev1.Container{ + { + Name: containerGroup.Name, + Image: containerGroup.Container.Image, + }, + }, + }, + Status: corev1.PodStatus{ + Phase: utils.GetPodPhaseFromContainerGroupState(containerGroup.CurrentState), + StartTime: &startTime, + ContainerStatuses: []corev1.ContainerStatus{ + { + Name: containerGroup.Name, + Image: containerGroup.Container.Image, + Ready: utils.GetPodPhaseFromContainerGroupState(containerGroup.CurrentState) == corev1.PodRunning, + }, + }, + }, + } + + pods = append(pods, pod) + + } return pods, nil } @@ -65,3 +305,59 @@ func (p *SaladCloudProvider) GetMetricsResource(context.Context) ([]*dto.MetricF func (p *SaladCloudProvider) PortForward(ctx context.Context, namespace, pod string, port int32, stream io.ReadWriteCloser) error { return nil } + +func (p *SaladCloudProvider) createContainersObject(pod *corev1.Pod) []saladclient.CreateContainer { + + cpu, memory := utils.GetPodResource(pod.Spec) + + creteContainersArray := make([]saladclient.CreateContainer, 0) + for _, container := range pod.Spec.Containers { + + containerResourceRequirement := saladclient.NewContainerResourceRequirements(int32(cpu), int32(memory)) + createContainer := saladclient.NewCreateContainer(container.Image, *containerResourceRequirement) + + marshallerObjectMetadata, err := json.Marshal(pod.ObjectMeta) + if err != nil { + log.G(context.Background()).Errorf("Failed Marshalling ", err) + } + + var mapString = make(map[string]string) + if marshallerObjectMetadata != nil { + mapString["POD_METADATA_YAM"] = string(marshallerObjectMetadata) + } + createContainer.SetEnvironmentVariables(mapString) + if container.Command != nil { + createContainer.SetCommand(container.Command) + } + creteContainersArray = append(creteContainersArray, *createContainer) + // TODO Add support for container Registry auth + } + return creteContainersArray + +} + +func (p *SaladCloudProvider) createContainerGroup(createContainerList []saladclient.CreateContainer, pod *corev1.Pod) []saladclient.CreateContainerGroup { + + createContainerGroups := make([]saladclient.CreateContainerGroup, 0) + + if pod.ObjectMeta.GetAnnotations()["countryCodes"] == "" { + pod.ObjectMeta.SetAnnotations(map[string]string{ + "countryCodes": "US", + }) + } + + var countryCodesEnum []saladclient.CountryCode + for _, countryCode := range strings.Split(pod.ObjectMeta.GetAnnotations()["countryCodes"], ",") { + countryCodeEnum := saladclient.CountryCode(countryCode) + countryCodesEnum = append(countryCodesEnum, countryCodeEnum) + } + + for _, container := range createContainerList { + createContainerGroupRequest := *saladclient.NewCreateContainerGroup(utils.GetPodName(pod.Namespace, pod.Name, pod), container, "always", 1) + createContainerGroupRequest.SetCountryCodes(countryCodesEnum) + createContainerGroups = append(createContainerGroups, createContainerGroupRequest) + } + + return createContainerGroups + +} diff --git a/internal/provider/provider_util.go b/internal/provider/provider_util.go new file mode 100644 index 0000000..2f4500a --- /dev/null +++ b/internal/provider/provider_util.go @@ -0,0 +1,76 @@ +package provider + +import ( + "errors" + "github.com/SaladTechnologies/virtual-kubelet-saladcloud/internal/models" + corev1 "k8s.io/api/core/v1" +) + +func MapPodToCreateContainerGroup(pod *corev1.Pod) (*models.CreateContainerGroupModel, error) { + if len(pod.Spec.Containers) == 0 { + return nil, errors.New("no containers found in the pod") + } + + firstContainer := pod.Spec.Containers[0] + envVars := make(map[string]string) + for _, envVar := range firstContainer.Env { + envVars[envVar.Name] = envVar.Value + } + + cpuRequest := getResourceValue(firstContainer.Resources.Requests, corev1.ResourceCPU) + memoryRequest := getResourceValue(firstContainer.Resources.Requests, corev1.ResourceMemory) + + containerResources := models.Resources{ + Requests: models.ResourceList{ + CPU: cpuRequest, + Memory: memoryRequest, + }, + Limits: models.ResourceList{ + CPU: cpuRequest, + Memory: memoryRequest, + }, + } + + containerSpec := models.ContainerSpec{ + Resources: containerResources, + EnvironmentVariables: envVars, + Image: firstContainer.Image, + Command: firstContainer.Command, + } + + group := &models.CreateContainerGroupModel{ + Name: pod.Name, + Container: containerSpec, + RestartPolicy: string(pod.Spec.RestartPolicy), + LivenessProbe: convertProbe(firstContainer.LivenessProbe), + ReadinessProbe: convertProbe(firstContainer.ReadinessProbe), + StartupProbe: convertProbe(firstContainer.StartupProbe), + Annotations: pod.Annotations, + } + + return group, nil +} + +func convertProbe(probe *corev1.Probe) models.Probe { + if probe == nil { + return models.Probe{} + } + return models.Probe{ + HTTPGetAction: &models.HTTPGetAction{ + Path: probe.HTTPGet.Path, + Port: int32(probe.HTTPGet.Port.IntValue()), + Scheme: string(probe.HTTPGet.Scheme), + }, + InitialDelaySeconds: probe.InitialDelaySeconds, + PeriodSeconds: probe.PeriodSeconds, + TimeoutSeconds: probe.TimeoutSeconds, + SuccessThreshold: probe.SuccessThreshold, + FailureThreshold: probe.FailureThreshold, + } +} +func getResourceValue(resources corev1.ResourceList, resourceType corev1.ResourceName) string { + if val, ok := resources[resourceType]; ok { + return val.String() + } + return "" +} diff --git a/internal/utils/utils.go b/internal/utils/utils.go new file mode 100644 index 0000000..f3c7d08 --- /dev/null +++ b/internal/utils/utils.go @@ -0,0 +1,75 @@ +package utils + +import ( + saladclient "github.com/lucklypriyansh-2/salad-client" + corev1 "k8s.io/api/core/v1" +) + +// roundUpToNearest returns the nearest larger integer from the given list. +func roundUpToNearest(value int64, list []int64) int64 { + for _, v := range list { + if value <= v { + return v + } + } + return list[len(list)-1] +} + +// GetPodResource returns the total CPU in rounded cores and memory rounded to gigabytes (GB) for the provided PodSpec. +func GetPodResource(podSpec corev1.PodSpec) (cpu int64, memory int64) { + allowedCPUValues := []int64{1, 2, 3, 4, 6, 8, 12, 16} + + allowedMemoryValues := []int64{1024, 2048, 3, 4, 5, 6, 12} // in GB + + for _, container := range podSpec.Containers { + // Convert milliCPU to cores and round to nearest value in the list + cpuValue := container.Resources.Requests.Cpu().MilliValue() / 1000 + cpu += roundUpToNearest(cpuValue, allowedCPUValues) + + // Convert bytes to gigabytes (MB) and ensure it's a multiple of 1 GB (1 GB = 1e9 bytes) + memValue := container.Resources.Requests.Memory().Value() / 1e6 + memory += roundUpToNearest(memValue, allowedMemoryValues) + } + return +} + +func GetPodName(nameSpace, containerGroup string, pod *corev1.Pod) string { + + if nameSpace == "" { + nameSpace = pod.ObjectMeta.Namespace + } + if containerGroup == "" { + containerGroup = pod.ObjectMeta.Name + } + if nameSpace == "" && containerGroup == "" && pod.Spec.Containers[0].Name != "" { + return pod.Spec.Containers[0].Name + } + return "salad-cloud-" + nameSpace + "-" + containerGroup +} + +func GetPodPhaseFromContainerGroupState(containerGroupState saladclient.ContainerGroupState) corev1.PodPhase { + + switch containerGroupState.Status { + case saladclient.CONTAINERGROUPSTATUS_PENDING: + return corev1.PodPending + case saladclient.CONTAINERGROUPSTATUS_RUNNING: + { + if containerGroupState.InstanceStatusCount.RunningCount > 0 { + return corev1.PodRunning + } + return corev1.PodPending + } + case saladclient.CONTAINERGROUPSTATUS_FAILED: + return corev1.PodFailed + case saladclient.CONTAINERGROUPSTATUS_SUCCEEDED: + return corev1.PodSucceeded + case saladclient.CONTAINERGROUPSTATUS_STOPPED: + return corev1.PodSucceeded + case saladclient.CONTAINERGROUPSTATUS_DEPLOYING: + return corev1.PodPending + + } + + return "" + +} diff --git a/sample-deployment-duplicate.yaml b/sample-deployment-duplicate.yaml new file mode 100644 index 0000000..2f1e1d6 --- /dev/null +++ b/sample-deployment-duplicate.yaml @@ -0,0 +1,98 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: Test-deployment +spec: + replicas: 1 + selector: + matchLabels: + app: test-deployment + template: + metadata: + labels: + app: test-deployment + annotations: + logging.new_relic.host: HOST_1 + logging.new_relic.ingestion_key: KEY_1 + logging.splunk.host: HOST_2 + logging.splunk.token: TOKEN_2 + logging.tcp.host: HOST_3 + logging.tcp.port: "1212" + registry_authentication.basic.username: USER_1 + registry_authentication.basic.password: PASSWORD_1 + registry_authentication.gcp_gcr.service_key: asdadasdasd + registry_authentication.aws_ecr.access_key_id: adasdasd + registry_authentication.aws_ecr.secret_access_key: asdasd + registry_authentication.docker_hub.username: asdasdad + registry_authentication.docker_hub.personal_access_token: asdasdasd + country_codes: "al, dz, ad" + spec: + containers: + - name: my-container + image: docker.io/heygordian/node-app:latest + command: + - "/command" + - "args 1" + resources: + requests: + memory: "1Gi" + cpu: "1" + limits: + memory: "1Gi" + cpu: "1" + env: + - name: ENV_VAR_1 + value: "VALUE_1" + ports: + - containerPort: 1212 + livenessProbe: + httpGet: + path: "/health" + port: 121 + scheme: "http" + httpHeaders: + - name: "OBJECT_1" + value: "OBJECT_2" + tcpSocket: + port: 121 + initialDelaySeconds: 12 + periodSeconds: 1 + timeoutSeconds: 1 + failureThreshold: 1 # Mapping failure_threshold + startupProbe: + httpGet: + path: "/path-1" + port: 121 + scheme: "http" + httpHeaders: + - name: "KEY" + value: "Value" + - name: "KEY2" + value: "Value" + tcpSocket: + port: 1211 + initialDelaySeconds: 12 + periodSeconds: 1 + timeoutSeconds: 1 + successThreshold: 1 + failureThreshold: 1 + readinessProbe: + httpGet: + path: "/" + port: 80 + volumeMounts: + - name: data-volume + mountPath: /data + volumes: + - name: data-volume + emptyDir: {} + nodeSelector: + kubernetes.io/role: agent + tolerations: + - key: "virtual-kubelet.io/provider" + operator: "Equal" + value: "saladCloud" + effect: "NoSchedule" + restartPolicy: "Always" + imagePullSecrets: + - name: my-secret diff --git a/sample-deployment.yaml b/sample-deployment.yaml new file mode 100644 index 0000000..fe3e13c --- /dev/null +++ b/sample-deployment.yaml @@ -0,0 +1,31 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: demo-deployment +spec: + replicas: 1 + selector: + matchLabels: + app: demo-app + template: + metadata: + labels: + app: demo-app + spec: + containers: + - name: my-container + image: docker.io/heygordian/node-app:latest + resources: + requests: + memory: "1Gi" + cpu: "1" + limits: + memory: "1Gi" + cpu: "1" + nodeSelector: + kubernetes.io/role: agent + tolerations: + - key: "virtual-kubelet.io/provider" + operator: "Equal" + value: "saladCloud" + effect: "NoSchedule" diff --git a/sample-pod.yaml b/sample-pod.yaml new file mode 100644 index 0000000..3d56741 --- /dev/null +++ b/sample-pod.yaml @@ -0,0 +1,22 @@ +apiVersion: v1 +kind: Pod +metadata: + name: demo-pod-8 +spec: + containers: + - name: my-container + image: docker.io/heygordian/node-app:latest + resources: + requests: + memory: "1Gi" + cpu: "1" + limits: + memory: "1Gi" + cpu: "1" + nodeSelector: + kubernetes.io/role: agent + tolerations: + - key: "virtual-kubelet.io/provider" + operator: "Equal" + value: "saladCloud" + effect: "NoSchedule"