diff --git a/apps/nodecontroller/.dockerignore b/apps/nodecontroller/.dockerignore deleted file mode 100644 index c8899319b..000000000 --- a/apps/nodecontroller/.dockerignore +++ /dev/null @@ -1 +0,0 @@ -*secrets diff --git a/apps/nodecontroller/Dockerfile b/apps/nodecontroller/Dockerfile deleted file mode 100644 index a867105fb..000000000 --- a/apps/nodecontroller/Dockerfile +++ /dev/null @@ -1,29 +0,0 @@ -# syntax=docker/dockerfile:1.4 -FROM golang:1.18.3-alpine3.16 AS base -USER 1001 -ENV GOPATH=/tmp/go -ENV GOCACHE=/tmp/go-cache -WORKDIR /tmp/app -COPY --chown=1001 --from=project-root ./go.mod ./go.sum ./tools.go ./ -RUN go mod download -x -COPY --chown=1001 --from=project-root pkg ./pkg -ARG APP -RUN mkdir -p ./apps/$APP -WORKDIR /tmp/app/apps/$APP -COPY --chown=1001 ./ ./ -RUN CGO_ENABLED=0 go build -tags musl -o /tmp/bin/$APP ./main.go -RUN chmod +x /tmp/bin/$APP - -#FROM gcr.io/distroless/static-debian11 -FROM alpine -RUN adduser -D -h /home/nonroot nonroot -RUN apk add curl git openssh-client -# RUN cd bin && curl -LO "https://dl.k8s.io/release/$(curl -L -s https://dl.k8s.io/release/stable.txt)/bin/linux/amd64/kubectl" && chmod +x kubectl -RUN cd bin && curl -o tf.zip https://releases.hashicorp.com/terraform/1.2.9/terraform_1.2.9_linux_amd64.zip && unzip tf.zip -# RUN cd bin && curl -L0 -o talosctl https://github.com/siderolabs/talos/releases/download/v1.2.3/talosctl-linux-amd64 && chmod +x talosctl -USER nonroot -WORKDIR /tmp/app -COPY --chown=nonroot --from=base /tmp/bin/nodecontroller ./nodecontroller -COPY --chown=nonroot --from=project-root ./pkg/infraClient/terraform /templates/terraform -# COPY --chown=nonroot --from=project-root ./pkg/infraClient/templates /tmp/app/pkg/infraClient/templates -CMD ["./nodecontroller"] diff --git a/apps/nodecontroller/Taskfile.yml b/apps/nodecontroller/Taskfile.yml deleted file mode 100644 index 3b0fb80a3..000000000 --- a/apps/nodecontroller/Taskfile.yml +++ /dev/null @@ -1,61 +0,0 @@ -version: "3" - -dotenv: - - .secrets/env - -tasks: - run: - sources: - - ./internal/**/*.go - - ./main.go - cmds: - # - go run -tags dynamic main.go --dev - - nodemon -e go --signal SIGKILL --exec 'go run main.go --dev || exit 1' - - docker-build: - vars: - APP: nodecontroller - IMAGE: registry.kloudlite.io/kloudlite/{{.EnvName}}/{{.APP}}:{{.Tag}} - preconditions: - - sh: '[[ -n "{{.Tag}}" ]]' - msg: 'var Tag must have a value' - - - sh: '[[ "{{.EnvName}}" == "development" ]] || [[ "{{.EnvName}}" == "staging" ]] || [[ "{{.EnvName}}" == "production" ]]' - msg: 'var EnvName must have one of [development, staging, production] as its value' - cmds: - - docker buildx build -f ./Dockerfile -t {{.IMAGE}} . --build-arg APP={{.APP}} --platform linux/amd64 --build-context project-root=../.. - - docker push {{.IMAGE}} - - local-build: - preconditions: - - sh: '[ -n "{{.EnvName}}" ]' - msg: 'var EnvName must have a value' - - sh: '[ -n "{{.Tag}}" ]' - msg: 'var Tag must have a value' - vars: - APP: nodecontroller - IMAGE: registry.kloudlite.io/kloudlite/{{.EnvName}}/{{.APP}}:{{.Tag}} - env: - CGO_ENABLED: 0 - GOOS: linux - GOARCH: amd64 - silent: true - cmds: - - |+ - lineNumbers=$(cat Dockerfile | grep -i '^FROM' -n | tail +2 | awk -F: '{print $1}') - - startLineNo=$(echo "$lineNumbers" | head -n+1) - finalLineNo=$(echo "$lineNumbers" | tail -1) - - tDir=$(mktemp -d) - - nDockerfile=$(cat Dockerfile | tail --lines=+$startLineNo | grep -i --invert-match 'from=base') - echo "$nDockerfile" | sed "1 i # syntax=docker/dockerfile:1.4" > $tDir/Dockerfile.base - - cat $tDir/Dockerfile.base | sed "10 i COPY --from=local-builder ./{{.APP}} ./{{.APP}}" > $tDir/Dockerfile - cat $tDir/Dockerfile - - CGO_ENABLED=0 go build -o $tDir/{{.APP}} . - - docker buildx build -f $tDir/Dockerfile -t {{.IMAGE}} . --build-context local-builder=${tDir} --build-context project-root=../.. - docker push {{.IMAGE}} diff --git a/apps/nodecontroller/internal/app/main.go b/apps/nodecontroller/internal/app/main.go deleted file mode 100644 index dec13f138..000000000 --- a/apps/nodecontroller/internal/app/main.go +++ /dev/null @@ -1,11 +0,0 @@ -package app - -import ( - "go.uber.org/fx" - "kloudlite.io/apps/nodecontroller/internal/domain" -) - -var Module = fx.Module( - "app", - domain.Module, -) diff --git a/apps/nodecontroller/internal/domain/aws.go b/apps/nodecontroller/internal/domain/aws.go deleted file mode 100644 index 73b7df7cb..000000000 --- a/apps/nodecontroller/internal/domain/aws.go +++ /dev/null @@ -1,86 +0,0 @@ -package domain - -import ( - "encoding/base64" - "errors" - - "gopkg.in/yaml.v3" - infraclient "kloudlite.io/pkg/infraClient" -) - -type awsConfig struct { - Version string `yaml:"version"` - Action string `yaml:"action"` - Provider string `yaml:"provider"` - Spec struct { - Provider struct { - AccessKey string `yaml:"accessKey"` - AccessSecret string `yaml:"accessSecret"` - AccountId string `yaml:"accountId"` - } `yaml:"provider"` - Node struct { - Region string `yaml:"region"` - InstanceType string `yaml:"instanceType"` - NodeId string `yaml:"nodeId"` - VPC string `yaml:"vpc"` - ImageId string `yaml:"imageId"` - } `yaml:"node"` - } `yaml:"spec"` -} - -func (d *domainI) doWithAWS() error { - - out, err := base64.StdEncoding.DecodeString(d.env.Config) - if err != nil { - return err - } - var awsConf awsConfig - e := yaml.Unmarshal(out, &awsConf) - if e != nil { - return e - } - klConf, err := d.getKlConf() - if err != nil { - return err - } - - awsProvider := infraclient.NewAWSProvider(infraclient.AWSProvider{ - AccessKey: awsConf.Spec.Provider.AccessKey, - AccessSecret: awsConf.Spec.Provider.AccessSecret, - AccountId: awsConf.Spec.Provider.AccountId, - }, infraclient.AWSProviderEnv{ - StorePath: klConf.Values.StorePath, - TfTemplates: klConf.Values.TfTemplates, - SSHPath: klConf.Values.SSHPath, - }) - - awsNode := infraclient.AWSNode{ - NodeId: awsConf.Spec.Node.NodeId, - Region: awsConf.Spec.Node.Region, - InstanceType: awsConf.Spec.Node.InstanceType, - VPC: awsConf.Spec.Node.VPC, - ImageId: awsConf.Spec.Node.ImageId, - } - - // return nil - - switch awsConf.Action { - case "create": - err = awsProvider.NewNode(awsNode) - if err != nil { - return err - } - - case "delete": - err = awsProvider.DeleteNode(awsNode) - - if err != nil { - return err - } - - default: - return errors.New("wrong action") - } - - return nil -} diff --git a/apps/nodecontroller/internal/domain/do.go b/apps/nodecontroller/internal/domain/do.go deleted file mode 100644 index b193522eb..000000000 --- a/apps/nodecontroller/internal/domain/do.go +++ /dev/null @@ -1,86 +0,0 @@ -package domain - -import ( - "encoding/base64" - "errors" - "fmt" - - "gopkg.in/yaml.v3" - infraclient "kloudlite.io/pkg/infraClient" -) - -type doConfig struct { - Version string `yaml:"version"` - Action string `yaml:"action"` - Provider string `yaml:"provider"` - Spec struct { - Provider struct { - ApiToken string `yaml:"apiToken"` - AccountId string `yaml:"accountId"` - } `yaml:"provider"` - Node struct { - Region string `yaml:"region"` - Size string `yaml:"size"` - NodeId string `yaml:"nodeId"` - ImageId string `yaml:"imageId"` - } `yaml:"node"` - } `yaml:"spec"` -} - -func (d *domainI) doWithDO() error { - - out, err := base64.StdEncoding.DecodeString(d.env.Config) - if err != nil { - fmt.Println("here") - return err - } - - var doConf doConfig - e := yaml.Unmarshal(out, &doConf) - if e != nil { - return e - } - klConf, err := d.getKlConf() - if err != nil { - fmt.Println("here") - return err - } - - doProvider := infraclient.NewDOProvider(infraclient.DoProvider{ - ApiToken: doConf.Spec.Provider.ApiToken, - AccountId: doConf.Spec.Provider.AccountId, - }, infraclient.DoProviderEnv{ - StorePath: klConf.Values.StorePath, - TfTemplates: klConf.Values.TfTemplates, - SSHPath: klConf.Values.SSHPath, - }) - - doNode := infraclient.DoNode{ - Region: doConf.Spec.Node.Region, - Size: doConf.Spec.Node.Size, - NodeId: doConf.Spec.Node.NodeId, - ImageId: doConf.Spec.Node.ImageId, - } - - // return nil - - switch doConf.Action { - case "create": - err = doProvider.NewNode(doNode) - if err != nil { - return err - } - - case "delete": - err = doProvider.DeleteNode(doNode) - - if err != nil { - return err - } - - default: - return errors.New("wrong action") - } - - return nil -} diff --git a/apps/nodecontroller/internal/domain/main.go b/apps/nodecontroller/internal/domain/main.go deleted file mode 100644 index 9ec955892..000000000 --- a/apps/nodecontroller/internal/domain/main.go +++ /dev/null @@ -1,88 +0,0 @@ -package domain - -import ( - "encoding/base64" - "errors" - "fmt" - - "gopkg.in/yaml.v3" - "kloudlite.io/pkg/config" - - "go.uber.org/fx" -) - -type domainI struct { - env *Env -} - -type KLConf struct { - Version string `yaml:"version"` - Values struct { - StorePath string `yaml:"storePath"` - TfTemplates string `yaml:"tfTemplatesPath"` - SSHPath string `yaml:"sshPath"` - PubKey string `yaml:"pubkey"` - } `yaml:"spec"` -} - -func (d *domainI) getKlConf() (*KLConf, error) { - out, err := base64.StdEncoding.DecodeString(d.env.KLConfig) - if err != nil { - fmt.Println("here") - return nil, err - } - - var klConf KLConf - e := yaml.Unmarshal(out, &klConf) - if e != nil { - - return nil, e - } - - return &klConf, nil -} - -// startJob implements Domain -func (d *domainI) StartJob() error { - - switch d.env.Provider { - case "do": - if err := d.doWithDO(); err != nil { - return err - } - - case "aws": - if err := d.doWithAWS(); err != nil { - return err - } - - default: - return errors.New("this type of provider not suported") - } - return nil -} - -func fxDomain(env *Env) Domain { - return &domainI{ - env: env, - } -} - -type Env struct { - Config string `env:"NODE_CONFIG" required:"true"` - Provider string `env:"PROVIDER" required:"true"` - KLConfig string `env:"KL_CONFIG" required:"true"` -} - -var Module = fx.Module( - "domain", - config.EnvFx[Env](), - fx.Provide(fxDomain), -) - -/* -main - -> framework () - -> app () - -> domain (main logic) -*/ diff --git a/apps/nodecontroller/internal/domain/port.go b/apps/nodecontroller/internal/domain/port.go deleted file mode 100644 index 184633284..000000000 --- a/apps/nodecontroller/internal/domain/port.go +++ /dev/null @@ -1,5 +0,0 @@ -package domain - -type Domain interface { - StartJob() error -} diff --git a/apps/nodecontroller/internal/framework/main.go b/apps/nodecontroller/internal/framework/main.go deleted file mode 100644 index 715725db1..000000000 --- a/apps/nodecontroller/internal/framework/main.go +++ /dev/null @@ -1,24 +0,0 @@ -package framework - -import ( - "fmt" - "os" - - "go.uber.org/fx" - "kloudlite.io/apps/nodecontroller/internal/app" - "kloudlite.io/apps/nodecontroller/internal/domain" -) - -var Module = fx.Module( - "framework", - app.Module, - fx.Invoke(func(d domain.Domain, shutdowner fx.Shutdowner) { - err := d.StartJob() - if err != nil { - fmt.Println(err) - os.Exit(1) - } else { - shutdowner.Shutdown() - } - }), -) diff --git a/apps/nodecontroller/main.go b/apps/nodecontroller/main.go deleted file mode 100644 index 08e57e786..000000000 --- a/apps/nodecontroller/main.go +++ /dev/null @@ -1,24 +0,0 @@ -package main - -import ( - "flag" - - "go.uber.org/fx" - "kloudlite.io/apps/nodecontroller/internal/framework" - "kloudlite.io/pkg/logging" -) - -func main() { - var isDev bool - flag.BoolVar(&isDev, "dev", false, "--dev") - flag.Parse() - fx.New( - framework.Module, - fx.Provide( - func() (logging.Logger, error) { - return logging.New(&logging.Options{Name: "auth", Dev: isDev}) - }, - ), - // fx.NopLogger, - ).Run() -} diff --git a/apps/nodecontroller/task.md b/apps/nodecontroller/task.md deleted file mode 100644 index 9ab842f47..000000000 --- a/apps/nodecontroller/task.md +++ /dev/null @@ -1,11 +0,0 @@ -# Tasks - -[ ] lib to create node - [ ] digital ocean - [ ] aws - [ ] azure - [ ] gcp -[ ] read message from kakfa -[ ] execute the message -[ ] commit the message as processed ( according to condition ) -[ ] apply the status in resource annotation of the resource diff --git a/apps/nodectrl/.dockerignore b/apps/nodectrl/.dockerignore deleted file mode 100644 index c8899319b..000000000 --- a/apps/nodectrl/.dockerignore +++ /dev/null @@ -1 +0,0 @@ -*secrets diff --git a/apps/nodectrl/internal/app/main.go b/apps/nodectrl/internal/app/main.go index dec13f138..aff934b6d 100644 --- a/apps/nodectrl/internal/app/main.go +++ b/apps/nodectrl/internal/app/main.go @@ -1,11 +1,68 @@ package app import ( + "context" + "fmt" + "go.uber.org/fx" - "kloudlite.io/apps/nodecontroller/internal/domain" + "kloudlite.io/apps/nodectrl/internal/domain" + "kloudlite.io/apps/nodectrl/internal/domain/common" + "kloudlite.io/apps/nodectrl/internal/domain/utils" + "kloudlite.io/apps/nodectrl/internal/env" ) -var Module = fx.Module( - "app", +var Module = fx.Module("app", domain.Module, + fx.Invoke( + func(env *env.Env, pc common.ProviderClient, shutdowner fx.Shutdowner, lifecycle fx.Lifecycle) { + lifecycle.Append(fx.Hook{ + OnStart: func(context.Context) error { + + go func() error { + ctx := context.Background() + if err := utils.SetupGetWorkDir(); err != nil { + return err + } + + err := func() error { + switch env.Action { + case "create": + + fmt.Println("needs to create node") + if err := pc.NewNode(ctx); err != nil { + return err + } + case "delete": + fmt.Println("needs to delete node") + if err := pc.DeleteNode(ctx); err != nil { + return err + } + + case "": + return fmt.Errorf("ACTION not provided, supported actions {create, delete} ") + default: + return fmt.Errorf("not supported actions '%s' please provide one of supported action like { create, delete }", env.Action) + + } + fmt.Println(utils.ColorText("\nšŸ™ƒ Successfully Exited šŸ™ƒ\n", 5)) + shutdowner.Shutdown() + return nil + }() + + if err != nil { + fmt.Println(utils.ColorText(fmt.Sprint("\n", "Error: ", err, "\n"), 1)) + return err + } + return nil + }() + + return nil + }, + OnStop: func(context.Context) error { + return nil + }, + }) + + }, + ), ) diff --git a/apps/nodectrl/internal/domain/aws.go b/apps/nodectrl/internal/domain/aws.go deleted file mode 100644 index 4b324380c..000000000 --- a/apps/nodectrl/internal/domain/aws.go +++ /dev/null @@ -1,103 +0,0 @@ -package domain - -import ( - "encoding/base64" - "encoding/json" - "errors" - "fmt" - - "gopkg.in/yaml.v3" - infraclient "kloudlite.io/pkg/infraClient" -) - -type awsConfig struct { - Version string `yaml:"version"` - Action string `yaml:"action"` - Provider string `yaml:"provider"` - Spec struct { - Provider struct { - AccessKey string `yaml:"accessKey"` - AccessSecret string `yaml:"accessSecret"` - AccountId string `yaml:"accountId"` - } `yaml:"provider"` - Node struct { - Region string `yaml:"region"` - InstanceType string `yaml:"instanceType"` - NodeId string `yaml:"nodeId"` - VPC string `yaml:"vpc"` - } `yaml:"node"` - } `yaml:"spec"` -} - -func (d *domainI) doWithAWS() error { - - out, err := base64.StdEncoding.DecodeString(d.env.Config) - if err != nil { - return err - } - var awsConf awsConfig - e := yaml.Unmarshal(out, &awsConf) - if e != nil { - return e - } - klConf, err := d.getKlConf() - if err != nil { - return err - } - - labels := map[string]string{} - if e := json.Unmarshal([]byte(d.env.Labels), &labels); e != nil { - fmt.Println(e) - } - - taints := []string{} - if e := json.Unmarshal([]byte(d.env.Taints), &taints); e != nil { - fmt.Println(e) - } - - awsProvider := infraclient.NewAWSProvider(infraclient.AWSProvider{ - AccessKey: awsConf.Spec.Provider.AccessKey, - AccessSecret: awsConf.Spec.Provider.AccessSecret, - AccountId: awsConf.Spec.Provider.AccountId, - }, infraclient.AWSProviderEnv{ - StorePath: klConf.Values.StorePath, - TfTemplates: klConf.Values.TfTemplates, - Labels: labels, - Taints: taints, - Secrets: klConf.Values.Secrets, - SSHPath: klConf.Values.SSHPath, - }) - - awsNode := infraclient.AWSNode{ - NodeId: awsConf.Spec.Node.NodeId, - Region: awsConf.Spec.Node.Region, - InstanceType: awsConf.Spec.Node.InstanceType, - VPC: awsConf.Spec.Node.VPC, - } - - // return nil - - switch awsConf.Action { - case "create": - err = awsProvider.NewNode(awsNode) - if err != nil { - return err - } - err = awsProvider.AttachNode(awsNode) - if err != nil { - return err - } - - case "delete": - err = awsProvider.DeleteNode(awsNode) - - if err != nil { - return err - } - - default: - return errors.New("wrong action") - } - - return nil -} diff --git a/apps/nodectrl/internal/domain/aws/main.go b/apps/nodectrl/internal/domain/aws/main.go new file mode 100644 index 000000000..85e6dcac4 --- /dev/null +++ b/apps/nodectrl/internal/domain/aws/main.go @@ -0,0 +1,162 @@ +package aws + +import ( + "fmt" + "path" + "time" + + "golang.org/x/net/context" + "kloudlite.io/apps/nodectrl/internal/domain/common" + "kloudlite.io/apps/nodectrl/internal/domain/utils" + mongogridfs "kloudlite.io/pkg/mongo-gridfs" +) + +type AwsProviderConfig struct { + AccessKey string `yaml:"accessKey"` + AccessSecret string `yaml:"accessSecret"` + AccountId string `yaml:"accountId"` +} + +type AWSNode struct { + NodeId string `yaml:"nodeId"` + Region string `yaml:"region"` + InstanceType string `yaml:"instanceType"` + VPC string `yaml:"vpc"` + ImageId string `yaml:"imageId"` +} + +type awsClient struct { + gfs mongogridfs.GridFs + node AWSNode + + accessKey string + accessSecret string + + SSHPath string + accountId string + providerDir string + tfTemplates string + labels map[string]string + taints []string +} + +func parseValues(a awsClient) map[string]string { + values := map[string]string{} + + values["access_key"] = a.accessKey + values["secret_key"] = a.accessSecret + + values["region"] = a.node.Region + values["node_id"] = a.node.NodeId + values["instance_type"] = a.node.InstanceType + values["keys-path"] = a.SSHPath + values["ami"] = a.node.ImageId + + return values +} + +func (a awsClient) SaveToDbGuranteed(ctx context.Context) { + for { + if err := utils.SaveToDb(ctx, a.node.NodeId, a.gfs); err == nil { + break + } else { + fmt.Println(err) + } + time.Sleep(time.Second * 20) + } +} + +// NewNode implements ProviderClient +func (a awsClient) NewNode(ctx context.Context) error { + + values := parseValues(a) + + /* + steps: + - check if state present in db + - if present load that to working dir + - else initialize new tf dir + - apply terraform + - upload the final state with defer + */ + + if err := utils.MakeTfWorkFileReady(ctx, a.node.NodeId, path.Join(a.tfTemplates, "aws"), a.gfs, true); err != nil { + return err + } + + defer a.SaveToDbGuranteed(ctx) + + // upload the final state to the db, upsert if db is already present + + // apply the tf file + if err := func() error { + if err := utils.InitTFdir(path.Join(utils.Workdir, a.node.NodeId)); err != nil { + return err + } + + if err := utils.ApplyTF(path.Join(utils.Workdir, a.node.NodeId), values); err != nil { + return err + } + + return nil + }(); err != nil { + return err + } + + return nil +} + +// DeleteNode implements ProviderClient +func (a awsClient) DeleteNode(ctx context.Context) error { + + values := parseValues(a) + + /* + steps: + - check if state present in db + - if present load that to working dir + - else initialize new tf dir + - destroy node with terraform + - delete final state + */ + + if err := utils.MakeTfWorkFileReady(ctx, a.node.NodeId, path.Join(a.tfTemplates, "aws"), a.gfs, false); err != nil { + return err + } + + // destroy the tf file + if err := func() error { + if err := utils.DestroyNode(a.node.NodeId, values); err != nil { + return err + } + + return nil + }(); err != nil { + return err + } + + filename := fmt.Sprintf("%s.zip", a.node.NodeId) + + if err := a.gfs.DeleteAllWithFilename(filename); err != nil { + return err + } + + return nil +} + +func NewAwsProviderClient(node AWSNode, cpd common.CommonProviderData, apc AwsProviderConfig, gfs mongogridfs.GridFs) common.ProviderClient { + return awsClient{ + node: node, + gfs: gfs, + + accessKey: apc.AccessKey, + accessSecret: apc.AccessSecret, + accountId: apc.AccountId, + + providerDir: "aws", + tfTemplates: cpd.TfTemplates, + labels: cpd.Labels, + taints: cpd.Taints, + SSHPath: cpd.SSHPath, + } +} diff --git a/apps/nodectrl/internal/domain/azure/azure.go b/apps/nodectrl/internal/domain/azure/azure.go new file mode 100644 index 000000000..c5930c983 --- /dev/null +++ b/apps/nodectrl/internal/domain/azure/azure.go @@ -0,0 +1,5 @@ +package domain + +func (d domain) StartAzureJob() error { + panic("not implemented yet") +} diff --git a/apps/nodectrl/internal/domain/common/common.go b/apps/nodectrl/internal/domain/common/common.go new file mode 100644 index 000000000..ec8c16d93 --- /dev/null +++ b/apps/nodectrl/internal/domain/common/common.go @@ -0,0 +1,8 @@ +package common + +type CommonProviderData struct { + TfTemplates string `yaml:"tfTemplates"` + Labels map[string]string `yaml:"labels"` + Taints []string `yaml:"taints"` + SSHPath string `yaml:"sshPath"` +} diff --git a/apps/nodectrl/internal/domain/common/interface.go b/apps/nodectrl/internal/domain/common/interface.go new file mode 100644 index 000000000..a23c9ba46 --- /dev/null +++ b/apps/nodectrl/internal/domain/common/interface.go @@ -0,0 +1,10 @@ +package common + +import "context" + +type ProviderClient interface { + NewNode(ctx context.Context) error + DeleteNode(ctx context.Context) error + + SaveToDbGuranteed(ctx context.Context) +} diff --git a/apps/nodectrl/internal/domain/do.go b/apps/nodectrl/internal/domain/do.go deleted file mode 100644 index d0bea72b4..000000000 --- a/apps/nodectrl/internal/domain/do.go +++ /dev/null @@ -1,106 +0,0 @@ -package domain - -import ( - "encoding/base64" - "encoding/json" - "errors" - "fmt" - - "gopkg.in/yaml.v3" - infraclient "kloudlite.io/pkg/infraClient" -) - -type doConfig struct { - Version string `yaml:"version"` - Action string `yaml:"action"` - Provider string `yaml:"provider"` - Spec struct { - Provider struct { - ApiToken string `yaml:"apiToken"` - AccountId string `yaml:"accountId"` - } `yaml:"provider"` - Node struct { - Region string `yaml:"region"` - Size string `yaml:"size"` - NodeId string `yaml:"nodeId"` - ImageId string `yaml:"imageId"` - } `yaml:"node"` - } `yaml:"spec"` -} - -func (d *domainI) doWithDO() error { - - out, err := base64.StdEncoding.DecodeString(d.env.Config) - if err != nil { - fmt.Println("here") - return err - } - - var doConf doConfig - e := yaml.Unmarshal(out, &doConf) - if e != nil { - return e - } - klConf, err := d.getKlConf() - if err != nil { - fmt.Println("here") - return err - } - - labels := map[string]string{} - if e := json.Unmarshal([]byte(d.env.Labels), &labels); e != nil { - fmt.Println(e) - } - - taints := []string{} - if e := json.Unmarshal([]byte(d.env.Taints), &taints); e != nil { - fmt.Println(e) - } - - doProvider := infraclient.NewDOProvider(infraclient.DoProvider{ - ApiToken: doConf.Spec.Provider.ApiToken, - AccountId: doConf.Spec.Provider.AccountId, - }, infraclient.DoProviderEnv{ - StorePath: klConf.Values.StorePath, - TfTemplates: klConf.Values.TfTemplates, - Secrets: klConf.Values.Secrets, - Labels: labels, - Taints: taints, - SSHPath: klConf.Values.SSHPath, - }) - - doNode := infraclient.DoNode{ - Region: doConf.Spec.Node.Region, - Size: doConf.Spec.Node.Size, - NodeId: doConf.Spec.Node.NodeId, - ImageId: doConf.Spec.Node.ImageId, - } - - // return nil - - switch doConf.Action { - case "create": - if err = doProvider.NewNode(doNode); err != nil { - return err - } - - if err = doProvider.AttachNode(doNode); err != nil { - return err - } - - case "delete": - - if err = doProvider.UnattachNode(doNode); err != nil { - return err - } - - if err = doProvider.DeleteNode(doNode); err != nil { - return err - } - - default: - return errors.New("wrong action") - } - - return nil -} diff --git a/apps/nodectrl/internal/domain/do/main.go b/apps/nodectrl/internal/domain/do/main.go new file mode 100644 index 000000000..96847bab5 --- /dev/null +++ b/apps/nodectrl/internal/domain/do/main.go @@ -0,0 +1,155 @@ +package do + +import ( + "context" + "fmt" + "path" + "time" + + "kloudlite.io/apps/nodectrl/internal/domain/common" + "kloudlite.io/apps/nodectrl/internal/domain/utils" + mongogridfs "kloudlite.io/pkg/mongo-gridfs" +) + +type DoProviderConfig struct { + ApiToken string `yaml:"apiToken"` + AccountId string `yaml:"accountId"` +} + +type DoNode struct { + Region string `yaml:"region"` + Size string `yaml:"size"` + NodeId string `yaml:"nodeId"` + ImageId string `yaml:"imageId"` +} + +type doClient struct { + gfs mongogridfs.GridFs + node DoNode + + apiToken string + + SSHPath string + accountId string + providerDir string + tfTemplates string + labels map[string]string + taints []string +} + +func parseValues(d doClient) map[string]string { + values := map[string]string{} + + values["do-token"] = d.apiToken + values["accountId"] = d.accountId + + values["do-image-id"] = "ubuntu-22-10-x64" + values["nodeId"] = d.node.NodeId + values["size"] = d.node.Size + values["keys-path"] = d.SSHPath + + return values +} + +// SaveToDbGuranteed implements ProviderClient +func (d doClient) SaveToDbGuranteed(ctx context.Context) { + for { + if err := utils.SaveToDb(ctx, d.node.NodeId, d.gfs); err == nil { + break + } else { + fmt.Println(err) + } + time.Sleep(time.Second * 20) + } +} + +// NewNode implements ProviderClient +func (d doClient) NewNode(ctx context.Context) error { + values := parseValues(d) + + /* + steps: + - check if state present in db + - if present load that to working dir + - else initialize new tf dir + - apply terraform + - upload the final state with defer + */ + + if err := utils.MakeTfWorkFileReady(ctx, d.node.NodeId, path.Join(d.tfTemplates, "do"), d.gfs, true); err != nil { + return err + } + + defer d.SaveToDbGuranteed(ctx) + + // apply the tf file + if err := func() error { + if err := utils.InitTFdir(path.Join(utils.Workdir, d.node.NodeId)); err != nil { + return err + } + + if err := utils.ApplyTF(path.Join(utils.Workdir, d.node.NodeId), values); err != nil { + return err + } + + return nil + }(); err != nil { + return err + } + + return nil +} + +// DeleteNode implements ProviderClient +func (d doClient) DeleteNode(ctx context.Context) error { + values := parseValues(d) + + /* + steps: + - check if state present in db + - if present load that to working dir + - else initialize new tf dir + - destroy node with terraform + - delete final state + */ + + if err := utils.MakeTfWorkFileReady(ctx, d.node.NodeId, path.Join(d.tfTemplates, "aws"), d.gfs, false); err != nil { + return err + } + + // destroy the tf file + if err := func() error { + if err := utils.DestroyNode(d.node.NodeId, values); err != nil { + return err + } + + return nil + }(); err != nil { + return err + } + + filename := fmt.Sprintf("%s.zip", d.node.NodeId) + + if err := d.gfs.DeleteAllWithFilename(filename); err != nil { + return err + } + + return nil +} + +func NewDoProviderClient(node DoNode, cpd common.CommonProviderData, dpc DoProviderConfig, gfs mongogridfs.GridFs) common.ProviderClient { + return doClient{ + node: node, + gfs: gfs, + + apiToken: dpc.ApiToken, + accountId: dpc.AccountId, + + providerDir: "do", + + tfTemplates: cpd.TfTemplates, + labels: cpd.Labels, + taints: cpd.Taints, + SSHPath: cpd.SSHPath, + } +} diff --git a/apps/nodectrl/internal/domain/gcp/gcp.go b/apps/nodectrl/internal/domain/gcp/gcp.go new file mode 100644 index 000000000..ca2d40eeb --- /dev/null +++ b/apps/nodectrl/internal/domain/gcp/gcp.go @@ -0,0 +1,5 @@ +package domain + +func (d domain) StartGCPJob() error { + panic("not implemented yet") +} diff --git a/apps/nodectrl/internal/domain/main.go b/apps/nodectrl/internal/domain/main.go index 2cb7dd50a..afb8613fb 100644 --- a/apps/nodectrl/internal/domain/main.go +++ b/apps/nodectrl/internal/domain/main.go @@ -1,91 +1,24 @@ package domain import ( - "encoding/base64" - "errors" - "fmt" - - "gopkg.in/yaml.v3" - "kloudlite.io/pkg/config" - "go.uber.org/fx" + "kloudlite.io/apps/nodectrl/internal/env" + mongogridfs "kloudlite.io/pkg/mongo-gridfs" ) -type domainI struct { - env *Env -} - -type KLConf struct { - Version string `yaml:"version"` - Values struct { - StorePath string `yaml:"storePath"` - TfTemplates string `yaml:"tfTemplatesPath"` - Secrets string `yaml:"secrets"` - SSHPath string `yaml:"sshPath"` - PubKey string `yaml:"pubkey"` - } `yaml:"spec"` -} - -func (d *domainI) getKlConf() (*KLConf, error) { - out, err := base64.StdEncoding.DecodeString(d.env.KLConfig) - if err != nil { - fmt.Println("here") - return nil, err - } - - var klConf KLConf - e := yaml.Unmarshal(out, &klConf) - if e != nil { - - return nil, e - } - - return &klConf, nil +type domain struct { + env *env.Env + gfs mongogridfs.GridFs } -// startJob implements Domain -func (d *domainI) StartJob() error { - - switch d.env.Provider { - case "do": - if err := d.doWithDO(); err != nil { - return err - } - - case "aws": - if err := d.doWithAWS(); err != nil { - return err - } - - default: - return errors.New("this type of provider not suported") - } - return nil -} - -func fxDomain(env *Env) Domain { - return &domainI{ - env: env, - } -} - -type Env struct { - Config string `env:"NODE_CONFIG" required:"true"` - Provider string `env:"PROVIDER" required:"true"` - KLConfig string `env:"KL_CONFIG" required:"true"` - Labels string `env:"LABELS" required:"true"` - Taints string `env:"TAINTS" required:"true"` -} - -var Module = fx.Module( - "domain", - config.EnvFx[Env](), - fx.Provide(fxDomain), +var Module = fx.Module("domain", + fx.Provide( + func(env *env.Env, gfs mongogridfs.GridFs) Domain { + return domain{ + env: env, + gfs: gfs, + } + }, + ), + ProviderClientFx, ) - -/* -main - -> framework () - -> app () - -> domain (main logic) -*/ diff --git a/apps/nodectrl/internal/domain/port.go b/apps/nodectrl/internal/domain/port.go index 184633284..fa83c82de 100644 --- a/apps/nodectrl/internal/domain/port.go +++ b/apps/nodectrl/internal/domain/port.go @@ -1,5 +1,4 @@ package domain type Domain interface { - StartJob() error } diff --git a/apps/nodectrl/internal/domain/provider-client-fx.go b/apps/nodectrl/internal/domain/provider-client-fx.go new file mode 100644 index 000000000..790ee8cac --- /dev/null +++ b/apps/nodectrl/internal/domain/provider-client-fx.go @@ -0,0 +1,61 @@ +package domain + +import ( + "go.uber.org/fx" + "kloudlite.io/apps/nodectrl/internal/domain/aws" + "kloudlite.io/apps/nodectrl/internal/domain/common" + "kloudlite.io/apps/nodectrl/internal/domain/do" + "kloudlite.io/apps/nodectrl/internal/domain/utils" + "kloudlite.io/apps/nodectrl/internal/env" + mongogridfs "kloudlite.io/pkg/mongo-gridfs" +) + +var ProviderClientFx = fx.Module("provider-client-fx", + fx.Provide(func(env *env.Env, gfs mongogridfs.GridFs) (common.ProviderClient, error) { + + cpd := common.CommonProviderData{} + + if err := utils.Base64YamlDecode(env.ProviderConfig, &cpd); err != nil { + return nil, err + } + + switch env.CloudProvider { + case "aws": + + node := aws.AWSNode{} + + if err := utils.Base64YamlDecode(env.NodeConfig, &node); err != nil { + return nil, err + } + + apc := aws.AwsProviderConfig{} + + if err := utils.Base64YamlDecode(env.AWSProviderConfig, &apc); err != nil { + return nil, err + } + + return aws.NewAwsProviderClient(node, cpd, apc, gfs), nil + case "azure": + panic("not implemented") + case "do": + + node := do.DoNode{} + + if err := utils.Base64YamlDecode(env.NodeConfig, &node); err != nil { + return nil, err + } + + dpc := do.DoProviderConfig{} + + if err := utils.Base64YamlDecode(env.DoProviderConfig, &dpc); err != nil { + return nil, err + } + + return do.NewDoProviderClient(node, cpd, dpc, gfs), nil + case "gcp": + panic("not implemented") + } + + return nil, nil + }), +) diff --git a/apps/nodectrl/internal/domain/utils/common.go b/apps/nodectrl/internal/domain/utils/common.go new file mode 100644 index 000000000..3436df7f7 --- /dev/null +++ b/apps/nodectrl/internal/domain/utils/common.go @@ -0,0 +1,307 @@ +package utils + +import ( + "context" + "encoding/base64" + "encoding/csv" + "encoding/json" + "fmt" + "os" + "os/exec" + "path" + "strings" + + "github.com/containerd/continuity/fs" + "gopkg.in/yaml.v2" + mongogridfs "kloudlite.io/pkg/mongo-gridfs" +) + +const ( + Workdir string = "/tmp/tf-workdir" +) + +func Base64YamlDecode(in string, out interface{}) error { + rawDecodedText, err := base64.StdEncoding.DecodeString(in) + if err != nil { + return err + } + + fmt.Println(string(rawDecodedText)) + + return yaml.Unmarshal(rawDecodedText, out) +} + +func SaveToDb(ctx context.Context, nodeId string, gfs mongogridfs.GridFs) error { + /* + Steps: + - compress the workdir into zip + - check if file present. if yes, upsert file else upload file + */ + + dir := path.Join(Workdir, nodeId) + filename := fmt.Sprintf("%s.zip", nodeId) + + // compress the workdir and upsert to db + if err := func() error { + if _, err := os.Stat(dir); err != nil { + return err + } + + source := fmt.Sprintf("%s.zip", dir) + + // compress + if err := ZipSource(dir, source); err != nil { + return err + } + + if err := gfs.Upsert(ctx, filename, source); err != nil { + return err + } + + return nil + }(); err != nil { + fmt.Println(ColorText(fmt.Sprint("Error: ", err), 1)) + return err + } + + return nil +} + +const ( + enableClear bool = false +) + +func CreateNodeWorkDir(nodeId string) error { + dir := path.Join(Workdir, nodeId) + if _, err := os.Stat(dir); err != nil { + return os.Mkdir(dir, os.ModePerm) + } + + if enableClear { + if err := os.RemoveAll(dir); err != nil { + return err + } + + return os.Mkdir(dir, os.ModePerm) + } else { + return nil + } +} + +func SetupGetWorkDir() error { + if _, err := os.Stat(Workdir); err != nil { + return os.Mkdir(Workdir, os.ModePerm) + } + return nil +} + +func MakeTfWorkFileReady(ctx context.Context, nodeId, tfPath string, gfs mongogridfs.GridFs, createIfNotExists bool) error { + + filename := fmt.Sprintf("%s.zip", nodeId) + // check if file exists in db + gf, err := gfs.FetchFileRef(ctx, filename) + if err != nil { + return err + } + + // not found create new dir + if gf == nil { + if !createIfNotExists { + return fmt.Errorf("no state file found with the nodeId %s to operate", nodeId) + } + + if err := CreateNodeWorkDir(nodeId); err != nil { + return err + } + + // a.tfTemplates + if err := fs.CopyDir(path.Join(Workdir, nodeId), tfPath); err != nil { + return err + } + + return nil + } + + // found file in db, download and extract to the workdir + fmt.Println(gf.Name, "found, extract it by downloading") + + source := path.Join(Workdir, filename) + // Download from db + if err := gfs.Download(ctx, filename, source); err != nil { + return err + } + + if s, err := Unzip(source, path.Join(Workdir)); err != nil { + return err + } else { + for _, v := range s { + fmt.Print(v, " \n") + } + } + + return nil +} + +func ColorText(text interface{}, code int) string { + return fmt.Sprintf("\033[38;05;%dm%v\033[0m", code, text) +} + +func DownloadDir() error { + return nil +} + +func UploadDir() error { + return nil +} + +func ExecCmd(cmdString string, logStr string) error { + r := csv.NewReader(strings.NewReader(cmdString)) + r.Comma = ' ' + cmdArr, err := r.Read() + if err != nil { + return err + } + + if logStr != "" { + fmt.Printf("[#] %s\n", logStr) + } else { + fmt.Printf("[#] %s\n", strings.Join(cmdArr, " ")) + } + + cmd := exec.Command(cmdArr[0], cmdArr[1:]...) + cmd.Stderr = os.Stderr + // cmd.Stdout = os.Stdout + + if err := cmd.Run(); err != nil { + fmt.Printf("err occurred: %s\n", err.Error()) + return err + } + return nil +} + +const ( + CLUSTER_ID = "kl" +) + +// rmTFdir implements doProviderClient +// func rmdir(folder string) error { +// return execCmd(fmt.Sprintf("rm -rf %q", folder), "") +// } + +// makeTFdir implements doProviderClient +func Mkdir(folder string) error { + return ExecCmd(fmt.Sprintf("mkdir -p %q", folder), "mkdir ") +} + +func getOutput(folder, key string) ([]byte, error) { + vars := []string{"output", "-json"} + fmt.Printf("[#] terraform %s\n", strings.Join(vars, " ")) + cmd := exec.Command("terraform", vars...) + cmd.Dir = folder + + // cmd.Stdout = os.Stdout + cmd.Stderr = os.Stderr + + out, err := cmd.Output() + if err != nil { + return nil, err + + } + + // fmt.Println(string(out)) + + var resp map[string]struct { + Value string `json:"value"` + } + + err = json.Unmarshal(out, &resp) + if err != nil { + return nil, err + } + + return []byte(resp[key].Value), nil +} + +func InitTFdir(dir string) error { + cmd := exec.Command("terraform", "init") + cmd.Dir = dir + + cmd.Stdout = os.Stdout + cmd.Stderr = os.Stderr + + return cmd.Run() +} + +// applyTF implements doProviderClient +func ApplyTF(folder string, values map[string]string) error { + + vars := []string{"apply", "-auto-approve"} + + fmt.Printf("[#] terraform %s", strings.Join(vars, " ")) + + for k, v := range values { + vars = append(vars, fmt.Sprintf("-var=%s=%s", k, v)) + } + + cmd := exec.Command("terraform", vars...) + cmd.Dir = folder + + cmd.Stdout = os.Stdout + cmd.Stderr = os.Stderr + + cmd.Dir = folder + + return cmd.Run() +} + +// destroyNode implements doProviderClient +func DestroyNode(nodeId string, values map[string]string) error { + dest := path.Join(Workdir, nodeId) + vars := []string{"destroy", "-auto-approve"} + + for k, v := range values { + vars = append(vars, fmt.Sprintf("-var=%s=%s", k, v)) + } + + cmd := exec.Command("terraform", vars...) + cmd.Dir = dest + + cmd.Stdout = os.Stdout + cmd.Stderr = os.Stderr + + err := cmd.Run() + if err != nil { + fmt.Println(err) + return err + } + return nil +} + +func GetOutput(folder, key string) ([]byte, error) { + vars := []string{"output", "-json"} + fmt.Printf("[#] terraform %s\n", strings.Join(vars, " ")) + cmd := exec.Command("terraform", vars...) + cmd.Dir = folder + + // cmd.Stdout = os.Stdout + cmd.Stderr = os.Stderr + + out, err := cmd.Output() + if err != nil { + return nil, err + + } + + // fmt.Println(string(out)) + + var resp map[string]struct { + Value string `json:"value"` + } + + err = json.Unmarshal(out, &resp) + if err != nil { + return nil, err + } + + return []byte(resp[key].Value), nil +} diff --git a/apps/nodectrl/internal/domain/utils/zipper.go b/apps/nodectrl/internal/domain/utils/zipper.go new file mode 100644 index 000000000..2accfe1d0 --- /dev/null +++ b/apps/nodectrl/internal/domain/utils/zipper.go @@ -0,0 +1,190 @@ +package utils + +import ( + "archive/zip" + "fmt" + "io" + "io/ioutil" + "log" + "os" + "path" + "path/filepath" + "strings" + + "github.com/otiai10/copy" +) + +func ZipSource(source, target string) error { + // 1. Create a ZIP file and zip.Writer + f, err := os.Create(target) + if err != nil { + return err + } + defer f.Close() + + writer := zip.NewWriter(f) + defer writer.Close() + + // 2. Go through all the files of the source + return filepath.Walk(source, func(path string, info os.FileInfo, err error) error { + if err != nil { + return err + } + + // 3. Create a local file header + header, err := zip.FileInfoHeader(info) + if err != nil { + return err + } + + // set compression + header.Method = zip.Deflate + + // 4. Set relative path of a file as the header name + header.Name, err = filepath.Rel(filepath.Dir(source), path) + if err != nil { + return err + } + if info.IsDir() { + header.Name += "/" + } + + // 5. Create writer for the file header and save content of the file + headerWriter, err := writer.CreateHeader(header) + if err != nil { + return err + } + + if info.IsDir() { + return nil + } + + f, err := os.Open(path) + if err != nil { + return err + } + defer f.Close() + + _, err = io.Copy(headerWriter, f) + return err + }) +} + +func Unzip(src string, destination string) ([]string, error) { + + var filenames []string + r, err := zip.OpenReader(src) + + if err != nil { + return filenames, err + } + defer r.Close() + + for _, f := range r.File { + // Store "path/filename" for returning and using later on + fpath := filepath.Join(destination, f.Name) + + // Checking for any invalid file paths + if !strings.HasPrefix(fpath, filepath.Clean(destination)+string(os.PathSeparator)) { + return filenames, fmt.Errorf("%s is an illegal filepath", fpath) + } + + filenames = append(filenames, fpath) + + if f.FileInfo().IsDir() { + + os.MkdirAll(fpath, os.ModePerm) + continue + } + + if err = os.MkdirAll(filepath.Dir(fpath), os.ModePerm); err != nil { + return filenames, err + } + + outFile, err := os.OpenFile(fpath, + os.O_WRONLY|os.O_CREATE|os.O_TRUNC, + f.Mode()) + + if err != nil { + return filenames, err + } + + rc, err := f.Open() + if err != nil { + return filenames, err + } + + _, err = io.Copy(outFile, rc) + + outFile.Close() + rc.Close() + + if err != nil { + return filenames, err + } + } + + return filenames, nil +} + +func ExtractZip(src, destination string) error { + if _, err := os.Stat(destination); err == nil { + if er := os.RemoveAll(destination); er != nil { + return err + } + } + + if _, err := os.Stat(src); err != nil { + if e := os.Mkdir(destination, os.ModePerm); e != nil { + return e + } + } else { + + tempDirName, err := ioutil.TempDir("/tmp", "zip_") + if err != nil { + return err + } + defer os.RemoveAll(tempDirName) + + if names, err := Unzip(src, tempDirName); err != nil { + return err + } else { + fmt.Println(names) + if err := copy.Copy(path.Join(tempDirName, destination), destination); err != nil { + return err + } + + } + + } + + return nil +} + +func mutateOperation() error { + + file, err := ioutil.TempFile("out", "prefix_") + if err != nil { + return err + } + + return os.WriteFile(file.Name(), []byte("hi"), os.ModePerm) +} + +func TestZip() error { + zipName, dirName := "ram.zip", "out" + if err := ExtractZip(zipName, dirName); err != nil { + return err + } + + if err := mutateOperation(); err != nil { + return err + } + + defer func() { + if err := ZipSource(dirName, zipName); err != nil { + log.Fatal(err) + } + }() + return nil +} diff --git a/apps/nodectrl/internal/env/env.go b/apps/nodectrl/internal/env/env.go new file mode 100644 index 000000000..bea846015 --- /dev/null +++ b/apps/nodectrl/internal/env/env.go @@ -0,0 +1,27 @@ +package env + +import "github.com/codingconcepts/env" + +type Env struct { + CloudProvider string `env:"CLOUD_PROVIDER" required:"true"` + Action string `env:"ACTION" required:"true"` + + NodeConfig string `env:"NODE_CONFIG" required:"true"` + ProviderConfig string `env:"PROVIDER_CONFIG" required:"true"` + + DBUrl string `env:"DB_URL" required:"true"` + DBName string `env:"DB_NAME" required:"true"` + + AWSProviderConfig string `env:"AWS_PROVIDER_CONFIG"` + GCPProviderConfig string `env:"GCP_PROVIDER_CONFIG"` + AzureProviderConfig string `env:"AZURE_PROVIDER_CONFIG"` + DoProviderConfig string `env:"DO_PROVIDER_CONFIG"` +} + +func LoadEnv() (*Env, error) { + var e Env + if err := env.Set(&e); err != nil { + return nil, err + } + return &e, nil +} diff --git a/apps/nodectrl/internal/framework/main.go b/apps/nodectrl/internal/framework/main.go index 7dd720d14..853d00a9f 100644 --- a/apps/nodectrl/internal/framework/main.go +++ b/apps/nodectrl/internal/framework/main.go @@ -1,24 +1,25 @@ package framework import ( - "fmt" - "os" - "go.uber.org/fx" "kloudlite.io/apps/nodectrl/internal/app" - "kloudlite.io/apps/nodectrl/internal/domain" + "kloudlite.io/apps/nodectrl/internal/env" + mongogridfs "kloudlite.io/pkg/mongo-gridfs" ) +type fm struct { + env *env.Env +} + +func (fm *fm) GetMongoConfig() (url string, dbName string) { + return fm.env.DBUrl, fm.env.DBName +} + var Module = fx.Module( "framework", - app.Module, - fx.Invoke(func(d domain.Domain, shutdowner fx.Shutdowner) { - err := d.StartJob() - if err != nil { - fmt.Println(err) - os.Exit(1) - } else { - shutdowner.Shutdown() - } + fx.Provide(func(env *env.Env) *fm { + return &fm{env} }), + mongogridfs.NewMongoGridFsClientFx[*fm](), + app.Module, ) diff --git a/apps/nodectrl/main.go b/apps/nodectrl/main.go index 64ad2e0a2..02ddc0276 100644 --- a/apps/nodectrl/main.go +++ b/apps/nodectrl/main.go @@ -4,6 +4,7 @@ import ( "flag" "go.uber.org/fx" + "kloudlite.io/apps/nodectrl/internal/env" "kloudlite.io/apps/nodectrl/internal/framework" "kloudlite.io/pkg/logging" ) @@ -13,13 +14,13 @@ func main() { flag.BoolVar(&isDev, "dev", false, "--dev") flag.Parse() fx.New( - framework.Module, + fx.Provide(env.LoadEnv), + // fx.NopLogger, fx.Provide( func() (logging.Logger, error) { - return logging.New(&logging.Options{Name: "auth", Dev: isDev}) + return logging.New(&logging.Options{Name: "nodectrl", Dev: isDev}) }, ), - // fx.NopLogger, + framework.Module, ).Run() } - diff --git a/apps/nodectrl/task.md b/apps/nodectrl/task.md deleted file mode 100644 index 9ab842f47..000000000 --- a/apps/nodectrl/task.md +++ /dev/null @@ -1,11 +0,0 @@ -# Tasks - -[ ] lib to create node - [ ] digital ocean - [ ] aws - [ ] azure - [ ] gcp -[ ] read message from kakfa -[ ] execute the message -[ ] commit the message as processed ( according to condition ) -[ ] apply the status in resource annotation of the resource diff --git a/pkg/infraClient/terraform/aws/init.sh b/apps/nodectrl/terraform/aws/init.sh similarity index 100% rename from pkg/infraClient/terraform/aws/init.sh rename to apps/nodectrl/terraform/aws/init.sh diff --git a/pkg/infraClient/terraform/aws/resource.tf b/apps/nodectrl/terraform/aws/resource.tf similarity index 100% rename from pkg/infraClient/terraform/aws/resource.tf rename to apps/nodectrl/terraform/aws/resource.tf diff --git a/pkg/infraClient/terraform/aws/variables.tf b/apps/nodectrl/terraform/aws/variables.tf similarity index 100% rename from pkg/infraClient/terraform/aws/variables.tf rename to apps/nodectrl/terraform/aws/variables.tf diff --git a/pkg/infraClient/terraform/do/init.sh b/apps/nodectrl/terraform/do/init.sh similarity index 100% rename from pkg/infraClient/terraform/do/init.sh rename to apps/nodectrl/terraform/do/init.sh diff --git a/pkg/infraClient/terraform/do/resource.tf b/apps/nodectrl/terraform/do/resource.tf similarity index 100% rename from pkg/infraClient/terraform/do/resource.tf rename to apps/nodectrl/terraform/do/resource.tf diff --git a/pkg/infraClient/terraform/do/variables.tf b/apps/nodectrl/terraform/do/variables.tf similarity index 100% rename from pkg/infraClient/terraform/do/variables.tf rename to apps/nodectrl/terraform/do/variables.tf diff --git a/go.mod b/go.mod index f401e6218..a5446d6a0 100644 --- a/go.mod +++ b/go.mod @@ -7,6 +7,7 @@ require ( github.com/Masterminds/sprig/v3 v3.2.3 github.com/bradleyfalzon/ghinstallation/v2 v2.0.4 github.com/codingconcepts/env v0.0.0-20200821220118-a8fbf8d84482 + github.com/containerd/continuity v0.4.1 github.com/go-redis/redis/v8 v8.11.5 github.com/gobuffalo/flect v1.0.2 github.com/gofiber/adaptor/v2 v2.1.23 @@ -20,6 +21,7 @@ require ( github.com/kloudlite/wg-operator v0.0.0-20230329090407-183297dc23b8 github.com/matoous/go-nanoid/v2 v2.0.0 github.com/miekg/dns v1.1.41 + github.com/otiai10/copy v1.11.0 github.com/pkg/errors v0.9.1 github.com/sendgrid/sendgrid-go v3.11.1+incompatible github.com/signintech/gopdf v0.12.0 @@ -34,6 +36,7 @@ require ( go.mongodb.org/mongo-driver v1.9.1 go.uber.org/fx v1.17.1 go.uber.org/zap v1.24.0 + golang.org/x/net v0.8.0 golang.org/x/oauth2 v0.3.0 golang.org/x/sync v0.1.0 golang.zx2c4.com/wireguard/wgctrl v0.0.0-20221104135756-97bc4ad4a1cb @@ -63,6 +66,7 @@ require ( contrib.go.opencensus.io/exporter/prometheus v0.4.0 // indirect github.com/Masterminds/goutils v1.1.1 // indirect github.com/Masterminds/semver/v3 v3.2.0 // indirect + github.com/Microsoft/go-winio v0.6.0 // indirect github.com/agnivade/levenshtein v1.1.1 // indirect github.com/andybalholm/brotli v1.0.4 // indirect github.com/beorn7/perks v1.0.1 // indirect @@ -95,7 +99,6 @@ require ( github.com/google/go-querystring v1.1.0 // indirect github.com/google/gofuzz v1.2.0 // indirect github.com/google/uuid v1.3.0 // indirect - github.com/graphql-go/graphql v0.8.1 github.com/grpc-ecosystem/grpc-gateway v1.16.0 // indirect github.com/hashicorp/errwrap v1.1.0 // indirect github.com/hashicorp/go-cleanhttp v0.5.2 // indirect @@ -131,6 +134,7 @@ require ( github.com/seancfoley/ipaddress-go v1.5.3 // indirect github.com/sendgrid/rest v2.6.9+incompatible // indirect github.com/shopspring/decimal v1.3.1 // indirect + github.com/sirupsen/logrus v1.9.0 // indirect github.com/spf13/cast v1.5.0 // indirect github.com/spf13/pflag v1.0.5 // indirect github.com/tektoncd/pipeline v0.43.2 // indirect @@ -152,7 +156,6 @@ require ( go.uber.org/multierr v1.8.0 // indirect golang.org/x/crypto v0.5.0 // indirect golang.org/x/mod v0.8.0 // indirect - golang.org/x/net v0.8.0 // indirect golang.org/x/sys v0.6.0 // indirect golang.org/x/term v0.6.0 // indirect golang.org/x/text v0.8.0 // indirect diff --git a/go.sum b/go.sum index eb3cb8ca6..8e22c8913 100644 --- a/go.sum +++ b/go.sum @@ -49,6 +49,8 @@ github.com/Masterminds/semver/v3 v3.2.0 h1:3MEsd0SM6jqZojhjLWWeBY+Kcjy9i6MQAeY7Y github.com/Masterminds/semver/v3 v3.2.0/go.mod h1:qvl/7zhW3nngYb5+80sSMF+FG2BjYrf8m9wsX0PNOMQ= github.com/Masterminds/sprig/v3 v3.2.3 h1:eL2fZNezLomi0uOLqjQoN6BfsDD+fyLtgbJMAj9n6YA= github.com/Masterminds/sprig/v3 v3.2.3/go.mod h1:rXcFaZ2zZbLRJv/xSysmlgIM1u11eBaRMhvYXJNkGuM= +github.com/Microsoft/go-winio v0.6.0 h1:slsWYD/zyx7lCXoZVlvQrj0hPTM1HI4+v1sIda2yDvg= +github.com/Microsoft/go-winio v0.6.0/go.mod h1:cTAf44im0RAYeL23bpB+fzCyDH2MJiz2BO69KH/soAE= github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU= github.com/agnivade/levenshtein v1.0.1/go.mod h1:CURSv5d9Uaml+FovSIICkLbAUZ9S4RqaHDIsdSBg7lM= github.com/agnivade/levenshtein v1.1.1 h1:QY8M92nrzkmr798gCo3kmMyqXFzdQVpxLlGPRBij0P8= @@ -93,6 +95,8 @@ github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnht github.com/cncf/xds/go v0.0.0-20210312221358-fbca930ec8ed/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= github.com/codingconcepts/env v0.0.0-20200821220118-a8fbf8d84482 h1:5/aEFreBh9hH/0G+33xtczJCvMaulqsm9nDuu2BZUEo= github.com/codingconcepts/env v0.0.0-20200821220118-a8fbf8d84482/go.mod h1:TM9ug+H/2cI3EjyIDr5xKCkFGyNE59URgH1wu5NyU8E= +github.com/containerd/continuity v0.4.1 h1:wQnVrjIyQ8vhU2sgOiL5T07jo+ouqc2bnKsv5/EqGhU= +github.com/containerd/continuity v0.4.1/go.mod h1:F6PTNCKepoxEaXLQp3wDAjygEnImnZ/7o4JzpodfroQ= github.com/cpuguy83/go-md2man/v2 v2.0.2 h1:p1EgwI/C7NhT0JmVkwCD2ZBK8j4aeHQX2pMHHBfMQ6w= github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= @@ -257,8 +261,6 @@ github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5m github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= github.com/gorilla/websocket v1.5.0 h1:PPwGk2jz7EePpoHN/+ClbZu8SPxiqlu12wZP/3sWmnc= github.com/gorilla/websocket v1.5.0/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= -github.com/graphql-go/graphql v0.8.1 h1:p7/Ou/WpmulocJeEx7wjQy611rtXGQaAcXGqanuMMgc= -github.com/graphql-go/graphql v0.8.1/go.mod h1:nKiHzRM0qopJEwCITUuIsxk9PlVlwIiiI8pnJEhordQ= github.com/grpc-ecosystem/grpc-gateway v1.14.6/go.mod h1:zdiPV4Yse/1gnckTHtghG4GkDEdKCRJduHpTxT3/jcw= 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= @@ -368,6 +370,9 @@ github.com/onsi/gomega v1.24.1 h1:KORJXNNTzJXzu4ScJWssJfJMnJ+2QJqhoQSRwNlze9E= github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U= github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM= github.com/openzipkin/zipkin-go v0.3.0 h1:XtuXmOLIXLjiU2XduuWREDT0LOKtSgos/g7i7RYyoZQ= +github.com/otiai10/copy v1.11.0 h1:OKBD80J/mLBrwnzXqGtFCzprFSGioo30JcmR4APsNwc= +github.com/otiai10/copy v1.11.0/go.mod h1:rSaLseMUsZFFbsFGc7wCJnnkTAvdc5L6VWxPE4308Ww= +github.com/otiai10/mint v1.5.1 h1:XaPLeE+9vGbuyEHem1JNk3bYc7KKqyI/na0/mLd/Kks= github.com/phpdave11/gofpdi v1.0.11 h1:wsBNx+3S0wy1dEp6fzv281S74ogZGgIdYWV2PugWgho= github.com/phpdave11/gofpdi v1.0.11/go.mod h1:vBmVV0Do6hSBHC8uKUQ71JGW+ZGQq74llk/7bXwjDoI= github.com/pierrec/lz4/v4 v4.1.14 h1:+fL8AQEZtz/ijeNnpduH0bROTu0O3NZAlPjQxGn8LwE= @@ -433,6 +438,7 @@ github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPx github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= github.com/sirupsen/logrus v1.6.0/go.mod h1:7uNnSEd1DgxDLC74fIahvMZmmYsHGZGEOFrfsX/uA88= github.com/sirupsen/logrus v1.9.0 h1:trlNQbNUG3OdDrDil03MCb1H2o9nJ1x4/5LYw7byDE0= +github.com/sirupsen/logrus v1.9.0/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= github.com/slack-go/slack v0.11.4 h1:ojSa7KlPm3PqY2AomX4VTxEsK5eci5JaxCjlzGV5zoM= github.com/slack-go/slack v0.11.4/go.mod h1:hlGi5oXA+Gt+yWTPP0plCdRKmjsDxecdHxYQdlMQKOw= github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= @@ -694,6 +700,7 @@ golang.org/x/sys v0.0.0-20220111092808-5a964db01320/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20220114195835-da31bd327af9/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220227234510-4e6760a101f9/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220908164124-27713097b956/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= diff --git a/pkg/infraClient/.dockerignore b/pkg/infraClient/.dockerignore deleted file mode 100644 index 002bae2c9..000000000 --- a/pkg/infraClient/.dockerignore +++ /dev/null @@ -1,3 +0,0 @@ -aws-test* -do-test* -consts.go diff --git a/pkg/infraClient/aws-test.go b/pkg/infraClient/aws-test.go deleted file mode 100644 index 548e8e327..000000000 --- a/pkg/infraClient/aws-test.go +++ /dev/null @@ -1,53 +0,0 @@ -package infraclient - -import ( - "fmt" -) - -func testAwsClient() { - env := GetEnvOrDie() - - awsp := NewAWSProvider(AWSProvider{ - AccessKey: "***REMOVED***", - AccessSecret: "***REMOVED***", - AccountId: "kl-core", - }, AWSProviderEnv{ - StorePath: "/home/vision/tf", - TfTemplates: "/home/vision/kloudlite/api-go/pkg/infraClient/terraform", - Secrets: env.Secret, - SSHPath: "/home/vision/.ssh", - }) - - var err error - - node := AWSNode{ - NodeId: "aws-worker-01", - Region: "ap-south-1", - InstanceType: "m5.large", - VPC: "", - } - - if false { - - if err = awsp.NewNode(node); err != nil { - fmt.Println(err) - return - } - - if err = awsp.AttachNode(node); err != nil { - fmt.Println(err) - return - } - - } else { - - if err = awsp.DeleteNode(node); err != nil { - fmt.Println(err) - return - } - - } - - // time.Sleep(time.Second * 10) - -} diff --git a/pkg/infraClient/aws.go b/pkg/infraClient/aws.go deleted file mode 100644 index 979ba2a2a..000000000 --- a/pkg/infraClient/aws.go +++ /dev/null @@ -1,305 +0,0 @@ -package infraclient - -import ( - "encoding/base64" - "fmt" - "os" - "os/exec" - "path" - "strings" - "time" - - "gopkg.in/yaml.v3" -) - -type awsProvider struct { - accessKey string - accessSecret string - - SSHPath string - accountId string - secrets string - providerDir string - storePath string - tfTemplates string - labels map[string]string - taints []string -} - -type AWSNode struct { - NodeId string - Region string - InstanceType string - VPC string - ImageId string -} - -type awsProviderClient interface { - NewNode(node AWSNode) error - DeleteNode(node AWSNode) error - - AttachNode(node AWSNode) error - UnattachNode(node AWSNode) error - - // mkdir(folder string) error - // rmdir(folder string) error - // getFolder(region, nodeId string) string - // initTFdir(region, nodeId string) error - // applyTF(region, nodeId string, values map[string]string) error - // destroyNode(folder string) error - // execCmd(cmd string) error -} - -// getFolder implements doProviderClient -func (a *awsProvider) getFolder(region string, nodeId string) string { - // eg -> /path/acc_id/do/blr1/node_id/do - - return path.Join(a.storePath, a.accountId, a.providerDir, region, nodeId) -} - -// initTFdir implements doProviderClient -func (d *awsProvider) initTFdir(node AWSNode) error { - - folder := d.getFolder(node.Region, node.NodeId) - - if err := execCmd(fmt.Sprintf("cp -r %s %s", fmt.Sprintf("%s/%s", d.tfTemplates, d.providerDir), folder), "initialize terraform"); err != nil { - return err - } - - cmd := exec.Command("terraform", "init") - cmd.Dir = path.Join(folder, d.providerDir) - - cmd.Stdout = os.Stdout - cmd.Stderr = os.Stderr - - return cmd.Run() -} - -type TalosAmi struct { - Cloud string - Version string - Region string - Arch string - Type string - Id string -} - -// NewNode implements awsProviderClient -func (a *awsProvider) NewNode(node AWSNode) error { - - values := map[string]string{} - - values["access_key"] = a.accessKey - values["secret_key"] = a.accessSecret - - values["region"] = node.Region - values["node_id"] = node.NodeId - values["instance_type"] = node.InstanceType - values["keys-path"] = a.SSHPath - values["ami"] = node.ImageId - - // making dir - if err := mkdir(a.getFolder(node.Region, node.NodeId)); err != nil { - return err - } - - // initialize directory - if err := a.initTFdir(node); err != nil { - return err - } - - // apply terraform - return applyTF(path.Join(a.getFolder(node.Region, node.NodeId), a.providerDir), values) - -} - -// AttachNode implements awsProviderClient -func (a *awsProvider) AttachNode(node AWSNode) error { - - var out, secretYaml []byte - var err error - - if out, err = getOutput(path.Join(a.getFolder(node.Region, node.NodeId), a.providerDir), "node-ip"); err != nil { - return err - } - - if secretYaml, err = base64.StdEncoding.DecodeString(a.secrets); err != nil { - return err - } - - var sec joinTokenSecret - - if err = yaml.Unmarshal(secretYaml, &sec); err != nil { - return err - } - - labels := func() []string { - l := []string{} - for k, v := range a.labels { - l = append(l, fmt.Sprintf("--node-label %s=%s", k, v)) - } - l = append(l, fmt.Sprintf("--node-label %s=%s", "kloudlite.io/public-ip", string(out))) - return l - }() - - count := 0 - - for { - if e := execCmd( - fmt.Sprintf("ssh -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -i %s root@%s ls", - fmt.Sprintf("%v/access", a.SSHPath), - string(out)), - "checking if node is ready "); e == nil { - break - } - - count++ - if count > 24 { - return fmt.Errorf("node is not ready even after 6 minutes") - } - time.Sleep(time.Second * 15) - } - - if err = execCmd(fmt.Sprintf("kubectl get node %s", node.NodeId), "checking if node attached"); err == nil { - fmt.Println("node already attached. clean exit") - return nil - } - - // // install k3s - // if e := execCmd( - // fmt.Sprintf("ssh -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -i %s root@%s sudo sh /tmp/k3s-install.sh", - // fmt.Sprintf("%v/access", d.SSHPath), string(out)), - // ""); e != nil { - // return e - // } - - // attach node - if e := execCmd( - fmt.Sprintf("ssh -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -i %s root@%s sudo sh /tmp/k3s-install.sh agent --server %s --token %s %s --node-name %s --node-external-ip %s --node-ip %s", - fmt.Sprintf("%v/access", a.SSHPath), string(out), sec.EndpointUrl, sec.JoinToken, - strings.Join(labels, " "), node.NodeId, string(out), string(out)), - "attaching to cluster"); e != nil { - return e - } - - count = 0 - for { - if err = execCmd(fmt.Sprintf("kubectl get node %s", node.NodeId), "checking if node attached"); err == nil { - fmt.Println("node attached successfully.") - break - } - - count++ - if count > 24 { - return fmt.Errorf("node not attached even after 6minutes") - } - time.Sleep(time.Second * 15) - } - - // "hostname": node.NodeId, - // "labels": strings.Join(labels, ","), - // TODO: needs to AttachNode here - return nil - -} - -// DeleteNode implements awsProviderClient -func (a *awsProvider) DeleteNode(node AWSNode) error { - var err error - - // time.Sleep(time.Minute * 2) - values := map[string]string{} - - //TODO: remove node from cluster after drain proceed following - - values["access_key"] = a.accessKey - values["secret_key"] = a.accessSecret - - values["region"] = node.Region - values["node_id"] = node.NodeId - values["instance_type"] = node.InstanceType - values["keys-path"] = a.SSHPath - values["ami"] = node.ImageId - - nodetfpath := path.Join(a.getFolder(node.Region, node.NodeId), a.providerDir) - - // check if dir present - if _, e := os.Stat(path.Join(nodetfpath, "init.sh")); e != nil && os.IsNotExist(e) { - fmt.Println("tf state not present nothing to do") - return nil - } - - // get node name - var out []byte - if out, err = getOutput(nodetfpath, "node-name"); err != nil { - return err - } else if strings.TrimSpace(string(out)) == "" { - fmt.Println("something went wrong, can't find node_name") - return nil - } - - // destroy node - return destroyNode(nodetfpath, values) - -} - -func (a *awsProvider) UnattachNode(node AWSNode) error { - var out []byte - var err error - - if out, err = getOutput(path.Join(a.getFolder(node.Region, node.NodeId), a.providerDir), "node-name"); err != nil { - return err - } else if strings.TrimSpace(string(out)) == "" { - fmt.Println("something went wrong, can't find node_name") - return nil - } - - if err = execCmd(fmt.Sprintf("kubectl get node %s", out), "checknode present"); err != nil { - fmt.Println("node not found may be already deleted") - return nil - } - - // drain node - if err = execCmd(fmt.Sprintf("kubectl taint nodes %s force=delete:NoExecute", node.NodeId), "drain node to delete"); err != nil { - return err - } - - fmt.Println("[#] waiting 10 seconds after drain") - time.Sleep(time.Second * 10) - - // delete node - return execCmd(fmt.Sprintf("kubectl delete node %s", out), - "delete node from cluster") -} - -type AWSProvider struct { - AccessKey string - AccessSecret string - AccountId string -} - -type AWSProviderEnv struct { - StorePath string - TfTemplates string - - Labels map[string]string - Taints []string - Secrets string - SSHPath string -} - -func NewAWSProvider(provider AWSProvider, p AWSProviderEnv) awsProviderClient { - return &awsProvider{ - accessKey: provider.AccessKey, - accessSecret: provider.AccessSecret, - accountId: provider.AccountId, - - providerDir: "aws", - secrets: p.Secrets, - storePath: p.StorePath, - tfTemplates: p.TfTemplates, - labels: p.Labels, - taints: p.Taints, - SSHPath: p.SSHPath, - } -} diff --git a/pkg/infraClient/azure.go b/pkg/infraClient/azure.go deleted file mode 100644 index da2ccd9bb..000000000 --- a/pkg/infraClient/azure.go +++ /dev/null @@ -1,11 +0,0 @@ -package infraclient - -type azureProvider struct { - provider string -} - -func NewAzureProvider() *azureProvider { - return &azureProvider{ - provider: "", - } -} diff --git a/pkg/infraClient/common.go b/pkg/infraClient/common.go deleted file mode 100644 index 5acbbd941..000000000 --- a/pkg/infraClient/common.go +++ /dev/null @@ -1,125 +0,0 @@ -package infraclient - -import ( - "encoding/csv" - "encoding/json" - "fmt" - "os" - "os/exec" - "strings" -) - -const ( - CLUSTER_ID = "kl" -) - -// rmTFdir implements doProviderClient -// func rmdir(folder string) error { -// return execCmd(fmt.Sprintf("rm -rf %q", folder), "") -// } - -// makeTFdir implements doProviderClient -func mkdir(folder string) error { - return execCmd(fmt.Sprintf("mkdir -p %q", folder), "mkdir ") -} - -// destroyNode implements doProviderClient -func destroyNode(folder string, values map[string]string) error { - vars := []string{"destroy", "-auto-approve"} - - for k, v := range values { - vars = append(vars, fmt.Sprintf("-var=%s=%s", k, v)) - } - - cmd := exec.Command("terraform", vars...) - cmd.Dir = folder - - cmd.Stdout = os.Stdout - cmd.Stderr = os.Stderr - - err := cmd.Run() - if err != nil { - fmt.Println(err) - return err - } - - return os.RemoveAll(folder) - - // return err -} - -func getOutput(folder, key string) ([]byte, error) { - vars := []string{"output", "-json"} - fmt.Printf("[#] terraform %s\n", strings.Join(vars, " ")) - cmd := exec.Command("terraform", vars...) - cmd.Dir = folder - - // cmd.Stdout = os.Stdout - cmd.Stderr = os.Stderr - - out, err := cmd.Output() - if err != nil { - return nil, err - - } - - // fmt.Println(string(out)) - - var resp map[string]struct { - Value string `json:"value"` - } - - err = json.Unmarshal(out, &resp) - if err != nil { - return nil, err - } - - return []byte(resp[key].Value), nil -} - -// applyTF implements doProviderClient -func applyTF(folder string, values map[string]string) error { - - vars := []string{"apply", "-auto-approve"} - - fmt.Printf("[#] terraform %s", strings.Join(vars, " ")) - - for k, v := range values { - vars = append(vars, fmt.Sprintf("-var=%s=%s", k, v)) - } - - cmd := exec.Command("terraform", vars...) - cmd.Dir = folder - - cmd.Stdout = os.Stdout - cmd.Stderr = os.Stderr - - cmd.Dir = folder - - return cmd.Run() -} - -func execCmd(cmdString string, logStr string) error { - r := csv.NewReader(strings.NewReader(cmdString)) - r.Comma = ' ' - cmdArr, err := r.Read() - if err != nil { - return err - } - - if logStr != "" { - fmt.Printf("[#] %s\n", logStr) - } else { - fmt.Printf("[#] %s\n", strings.Join(cmdArr, " ")) - } - - cmd := exec.Command(cmdArr[0], cmdArr[1:]...) - cmd.Stderr = os.Stderr - // cmd.Stdout = os.Stdout - - if err := cmd.Run(); err != nil { - fmt.Printf("err occurred: %s\n", err.Error()) - return err - } - return nil -} diff --git a/pkg/infraClient/do-test.go b/pkg/infraClient/do-test.go deleted file mode 100644 index 6505ce256..000000000 --- a/pkg/infraClient/do-test.go +++ /dev/null @@ -1,82 +0,0 @@ -package infraclient - -import ( - "fmt" - "time" - - "github.com/codingconcepts/env" -) - -type Env struct { - Secret string `env:"SECRETS" required:"true"` -} - -func GetEnvOrDie() *Env { - var ev Env - if err := env.Set(&ev); err != nil { - panic(err) - } - return &ev -} - -func testDoClient() { - env := GetEnvOrDie() - - dop := NewDOProvider(DoProvider{ - ApiToken: "***REMOVED***", - AccountId: "kl-core", - }, DoProviderEnv{ - StorePath: "/home/vision/tf", - TfTemplates: "/home/vision/kloudlite/api-go/pkg/infraClient/terraform", - Secrets: env.Secret, - Labels: map[string]string{ - // "kloudlite.io/region": "blr1", - - }, - - SSHPath: "/home/vision/.ssh", - }) - - var err error - - node := DoNode{ - Region: "blr1", - // Size: "s-4vcpu-8gb-amd", - // Size: "s-2vcpu-4gb-amd", - // Size: "s-1vcpu-1gb-amd", - Size: "c-2", - NodeId: "try-agent-01", - ImageId: "ubuntu-22-10-x64", - } - - // fmt.Println(node, err, dop) - - if false { - - if err = dop.NewNode(node); err != nil { - fmt.Println(err) - return - } - - for { - - if err = dop.AttachNode(node); err != nil { - fmt.Println(err) - - time.Sleep(time.Second * 5) - continue - } - - return - } - - } else { - - if err = dop.DeleteNode(node); err != nil { - fmt.Println(err) - return - } - - } - -} diff --git a/pkg/infraClient/do.go b/pkg/infraClient/do.go deleted file mode 100644 index 4e7768b17..000000000 --- a/pkg/infraClient/do.go +++ /dev/null @@ -1,300 +0,0 @@ -package infraclient - -import ( - "encoding/base64" - "fmt" - "os" - "os/exec" - "path" - "strings" - "time" - - "gopkg.in/yaml.v3" -) - -type doProviderClient interface { - NewNode(node DoNode) error - DeleteNode(node DoNode) error - - AttachNode(node DoNode) error - UnattachNode(node DoNode) error - - // mkdir(folder string) error - // rmdir(folder string) error - // getFolder(region, nodeId string) string - // initTFdir(region, nodeId string) error - // applyTF(region, nodeId string, values map[string]string) error - // destroyNode(folder string) error - // execCmd(cmd string) error -} - -type DoNode struct { - Region string - Size string - NodeId string - ImageId string -} - -type joinTokenSecret struct { - JoinToken string `json:"joinToken" yaml:"joinToken"` - EndpointUrl string `json:"endpointUrl" yaml:"endPointUrl"` -} - -type doProvider struct { - apiToken string - accountId string - providerDir string - storePath string - tfTemplates string - SSHPath string - PubKey string - labels map[string]string - taints []string - secrets string -} - -// getFolder implements doProviderClient -func (d *doProvider) getFolder(region string, nodeId string) string { - // eg -> /path/do/blr1/acc_id/node_id - return path.Join(d.storePath, d.accountId, d.providerDir, region, nodeId) -} - -// initTFdir implements doProviderClient -func (d *doProvider) initTFdir(node DoNode) error { - - folder := d.getFolder(node.Region, node.NodeId) - - if err := execCmd(fmt.Sprintf("cp -r %s %s", fmt.Sprintf("%s/%s", d.tfTemplates, d.providerDir), folder), "initialize terraform"); err != nil { - return err - } - - cmd := exec.Command("terraform", "init") - cmd.Dir = path.Join(folder, d.providerDir) - - cmd.Stdout = os.Stdout - cmd.Stderr = os.Stderr - - return cmd.Run() -} - -// NewNode implements doProviderClient -func (d *doProvider) NewNode(node DoNode) error { - values := map[string]string{} - - values["cluster-id"] = CLUSTER_ID - - // values["keys-path"] = d.sshKeyPath - values["do-token"] = d.apiToken - values["accountId"] = d.accountId - - values["do-image-id"] = "ubuntu-22-10-x64" - values["nodeId"] = node.NodeId - values["size"] = node.Size - values["keys-path"] = d.SSHPath - - // making dir - if err := mkdir(d.getFolder(node.Region, node.NodeId)); err != nil { - return err - } - - // initialize directory - if err := d.initTFdir(node); err != nil { - return err - } - - tfPath := path.Join(d.getFolder(node.Region, node.NodeId), d.providerDir) - - // apply terraform - return applyTF(tfPath, values) -} - -// DeleteNode implements ProviderClient -func (d *doProvider) DeleteNode(node DoNode) error { - // time.Sleep(time.Minute * 2) - values := map[string]string{} - - //TODO: remove node from cluster after drain proceed following - - values["cluster-id"] = CLUSTER_ID - - // values["keys-path"] = d.sshKeyPath - values["do-token"] = d.apiToken - values["accountId"] = d.accountId - - // values["do-image-id"] = node.ImageId - values["do-image-id"] = "ubuntu-22-10-x64" - values["nodeId"] = node.NodeId - values["keys-path"] = d.SSHPath - - nodetfpath := path.Join(d.getFolder(node.Region, node.NodeId), d.providerDir) - - // check if dir present - if _, err := os.Stat(path.Join(nodetfpath, "init.sh")); err != nil && os.IsNotExist(err) { - fmt.Println("tf state not present nothing to do") - return nil - } - - // get node name - var out []byte - var err error - if out, err = getOutput(nodetfpath, "node-name"); err != nil { - return err - } else if strings.TrimSpace(string(out)) == "" { - fmt.Println("something went wrong, can't find node_name") - return nil - } - - // destroy node - return destroyNode(nodetfpath, values) -} - -// AttachNode implements ProviderClient -func (d *doProvider) AttachNode(node DoNode) error { - - var out, secretYaml []byte - var err error - - if out, err = getOutput(path.Join(d.getFolder(node.Region, node.NodeId), d.providerDir), "node-ip"); err != nil { - return err - } - - if secretYaml, err = base64.StdEncoding.DecodeString(d.secrets); err != nil { - fmt.Println("here", d.secrets) - return err - } - - var sec joinTokenSecret - - if err = yaml.Unmarshal(secretYaml, &sec); err != nil { - return err - } - - labels := func() []string { - l := []string{} - for k, v := range d.labels { - l = append(l, fmt.Sprintf("--node-label %s=%s", k, v)) - } - l = append(l, fmt.Sprintf("--node-label %s=%s", "kloudlite.io/public-ip", string(out))) - return l - }() - - // fmt.Println(labels) - - // "hostname": node.NodeId, - // "labels": strings.Join(labels, ","), - //check is node ready - count := 0 - - for { - if e := execCmd( - fmt.Sprintf("ssh -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -i %s root@%s ls", - fmt.Sprintf("%v/access", d.SSHPath), - string(out)), - "checking if node is ready"); e == nil { - break - } - - count++ - if count > 24 { - return fmt.Errorf("node is not ready even after 6 minutes") - } - time.Sleep(time.Second * 15) - } - - if err = execCmd(fmt.Sprintf("kubectl get node %s", node.NodeId), "checking if node attached"); err == nil { - fmt.Println("node already attached. clean exit") - return nil - } - - // // install k3s - // if e := execCmd( - // fmt.Sprintf("ssh -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -i %s root@%s sudo sh /tmp/k3s-install.sh", - // fmt.Sprintf("%v/access", d.SSHPath), string(out)), - // ""); e != nil { - // return e - // } - - // attach node - if e := execCmd( - fmt.Sprintf("ssh -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -i %s root@%s sudo sh /tmp/k3s-install.sh agent --server %s --token %s %s --node-name %s --node-external-ip %s", - fmt.Sprintf("%v/access", d.SSHPath), string(out), sec.EndpointUrl, sec.JoinToken, - strings.Join(labels, " "), node.NodeId, string(out)), - "attaching to cluster"); e != nil { - return e - } - - count = 0 - for { - if err = execCmd(fmt.Sprintf("kubectl get node %s", node.NodeId), "checking if node attached"); err == nil { - fmt.Println("node attached successfully.") - break - } - - count++ - if count > 24 { - return fmt.Errorf("node not attached even after 6minutes") - } - time.Sleep(time.Second * 15) - } - - return nil -} - -// UnattachNode implements doProviderClient -func (d *doProvider) UnattachNode(node DoNode) error { - var out []byte - var err error - - if out, err = getOutput(path.Join(d.getFolder(node.Region, node.NodeId), d.providerDir), "node-name"); err != nil { - return err - } else if strings.TrimSpace(string(out)) == "" { - fmt.Println("something went wrong, can't find node_name") - return nil - } - - if err = execCmd(fmt.Sprintf("kubectl get node %s", out), "checking if node attached"); err != nil { - fmt.Println("node not found may be already deleted") - return nil - } - - // drain node - if err = execCmd(fmt.Sprintf("kubectl taint nodes %s force=delete:NoExecute", node.NodeId), "drain node to delete"); err != nil { - return err - } - - fmt.Println("[#] waiting 10 seconds after drain") - time.Sleep(time.Second * 10) - - // delete node - return execCmd(fmt.Sprintf("kubectl delete node %s", out), - "delete node from cluster") -} - -type DoProvider struct { - ApiToken string - AccountId string -} - -type DoProviderEnv struct { - StorePath string - TfTemplates string - - SSHPath string - Secrets string - Labels map[string]string - Taints []string -} - -func NewDOProvider(provider DoProvider, p DoProviderEnv) doProviderClient { - return &doProvider{ - secrets: p.Secrets, - apiToken: provider.ApiToken, - accountId: provider.AccountId, - providerDir: "do", - storePath: p.StorePath, - tfTemplates: p.TfTemplates, - labels: p.Labels, - taints: p.Taints, - SSHPath: p.SSHPath, - } -} diff --git a/pkg/infraClient/interface.go b/pkg/infraClient/interface.go deleted file mode 100644 index 98b6eb076..000000000 --- a/pkg/infraClient/interface.go +++ /dev/null @@ -1,8 +0,0 @@ -package infraclient - -type ProviderClient interface { - NewNode() error - DeleteNode() error - UpdateNode() error - AttachNode() error -} diff --git a/pkg/infraClient/main.go b/pkg/infraClient/main.go deleted file mode 100644 index 96dc63cfc..000000000 --- a/pkg/infraClient/main.go +++ /dev/null @@ -1,6 +0,0 @@ -package infraclient - -func InfraClientTest() { - testAwsClient() - // testDoClient() -} diff --git a/pkg/infraClient/terraform/e b/pkg/infraClient/terraform/e deleted file mode 100644 index fda47e4f0..000000000 --- a/pkg/infraClient/terraform/e +++ /dev/null @@ -1 +0,0 @@ -export SECRETS=***REMOVED*** diff --git a/pkg/infraClient/terraform/secrets.yml b/pkg/infraClient/terraform/secrets.yml deleted file mode 100644 index 8c5f7afb0..000000000 --- a/pkg/infraClient/terraform/secrets.yml +++ /dev/null @@ -1,2 +0,0 @@ -joinToken: ***REMOVED*** -endPointUrl: ***REMOVED*** diff --git a/pkg/mongo-gridfs/gridfs.go b/pkg/mongo-gridfs/gridfs.go new file mode 100644 index 000000000..4d8ff2a00 --- /dev/null +++ b/pkg/mongo-gridfs/gridfs.go @@ -0,0 +1,144 @@ +package mongogridfs + +import ( + "bytes" + "context" + "fmt" + "io" + "os" + + "go.mongodb.org/mongo-driver/bson" + "go.mongodb.org/mongo-driver/bson/primitive" + "go.mongodb.org/mongo-driver/mongo/gridfs" + "go.mongodb.org/mongo-driver/mongo/options" +) + +type gfs struct { + bucket *gridfs.Bucket +} + +type GridFs interface { + Upload(ctx context.Context, filename, source string) error + Download(ctx context.Context, filename, destination string) error + Upsert(ctx context.Context, filename, source string) error + DeleteById(id string) error + GetAllFiles() ([]GridfsFile, error) + FetchFileRef(ctx context.Context, filename string) (*GridfsFile, error) + DeleteAllWithFilename(filename string) error +} + +// Delete implements GridFs +func (g *gfs) DeleteById(id string) error { + _id, err := primitive.ObjectIDFromHex(id) + if err != nil { + return err + } + return g.bucket.Delete(_id) +} + +func (g *gfs) DeleteAllWithFilename(filename string) error { + gf, err := g.GetAllFiles() + if err != nil { + return err + } + for _, gf2 := range gf { + if gf2.Name == filename { + if err := g.DeleteById(gf2.Id); err != nil { + return err + } + } + } + return nil +} + +// Delete implements GridFs +func (g *gfs) Upsert(ctx context.Context, filename, source string) error { + if err := g.DeleteAllWithFilename(filename); err != nil { + return err + } + + return g.Upload(ctx, filename, source) +} + +// Download implements GridFs +func (g *gfs) Download(ctx context.Context, filename, destination string) error { + gf, err := g.FetchFileRef(ctx, filename) + if err != nil { + return err + } + + id, err := primitive.ObjectIDFromHex(gf.Id) + if err != nil { + return err + } + fileBuffer := bytes.NewBuffer(nil) + if _, err := g.bucket.DownloadToStream(id, fileBuffer); err != nil { + panic(err) + } + + if err := os.WriteFile(destination, fileBuffer.Bytes(), os.ModePerm); err != nil { + return err + } + return nil +} + +type Filter map[string]interface{} + +type GridfsFile struct { + Id string `bson:"_id"` + Name string `bson:"filename"` + Length int64 `bson:"length"` +} + +func (g *gfs) GetAllFiles() ([]GridfsFile, error) { + filter := bson.D{{}} + // filter := bson.D{{"length", bson.D{{"$lt", 1500}}}} + cursor, err := g.bucket.Find(filter) + if err != nil { + return nil, err + } + var foundFiles []GridfsFile + if err = cursor.All(context.TODO(), &foundFiles); err != nil { + return nil, err + } + + return foundFiles, nil +} + +// Search implements GridFs +func (g *gfs) FetchFileRef(ctx context.Context, filename string) (*GridfsFile, error) { + + cursor, err := g.bucket.Find(Filter{"filename": filename}) + if err != nil { + return nil, err + } + + var foundFiles []GridfsFile + + if err = cursor.All(context.TODO(), &foundFiles); err != nil { + return nil, err + } + + if len(foundFiles) == 0 { + return nil, nil + } + + return &foundFiles[0], nil + +} + +// Upload implements GridFs +func (g *gfs) Upload(ctx context.Context, filename, source string) error { + file, err := os.Open(source) + if err != nil { + return err + } + + uploadOpts := options.GridFSUpload() + objectID, err := g.bucket.UploadFromStream(filename, io.Reader(file), uploadOpts) + if err != nil { + return err + } + fmt.Printf("file %s uploaded with ID %s", filename, objectID) + return nil +} diff --git a/pkg/mongo-gridfs/main.go b/pkg/mongo-gridfs/main.go new file mode 100644 index 000000000..0abdb2566 --- /dev/null +++ b/pkg/mongo-gridfs/main.go @@ -0,0 +1,49 @@ +package mongogridfs + +import ( + "context" + "time" + + "go.mongodb.org/mongo-driver/mongo" + "go.mongodb.org/mongo-driver/mongo/gridfs" + "go.mongodb.org/mongo-driver/mongo/options" + "go.uber.org/fx" +) + +type MongoConfig interface { + GetMongoConfig() (url string, dbName string) +} + +func NewMongoGridFsClientFx[T MongoConfig]() fx.Option { + return fx.Module("mongodb-gridfs", + fx.Provide( + func(env T) (*gridfs.Bucket, GridFs, error) { + + ctx, cancel := context.WithTimeout( + context.Background(), + 10*time.Second, + ) + defer cancel() + + url, dbName := env.GetMongoConfig() + + client, err := mongo.Connect(ctx, options.Client().ApplyURI(url)) + if err != nil { + return nil, nil, err + } + + db := client.Database(dbName) + bucket, err := gridfs.NewBucket(db) + if err != nil { + return nil, nil, err + } + + gridfs := &gfs{ + bucket: bucket, + } + + return bucket, gridfs, nil + }, + ), + ) +}