-
Notifications
You must be signed in to change notification settings - Fork 4
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
implement grid-compose #1130
base: development
Are you sure you want to change the base?
implement grid-compose #1130
Changes from 4 commits
7503ba9
84cbd10
7f0d28b
9e70865
7d3cff0
905394b
40e67a2
9fcedd2
09d55e2
3b095a7
9e227e1
b0f3b22
4bf7aaf
352ef37
de9f63a
07c5da9
124fe56
bd2aa64
8ddab7b
deb6275
f21cf97
bf550b0
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,2 @@ | ||
bin/* | ||
.pre-commit-config.yaml |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,6 @@ | ||
BIN_PATH = bin/main | ||
|
||
.PHONY: build | ||
|
||
build: | ||
@go build -o $(BIN_PATH) ./main.go |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,22 @@ | ||
# Grid-Compose | ||
|
||
is a tool for running multi-vm applications on TFGrid defined using a Yaml formatted file. | ||
|
||
## Usage | ||
|
||
`REQUIRED` EnvVars: | ||
|
||
- `MNEMONIC`: your secret words | ||
- `NETWORK`: one of (dev, qa, test, main) | ||
|
||
```bash | ||
grid-compose [OPTIONS] [COMMAND] | ||
|
||
OPTIONS: | ||
-f path to yaml file, default is ./grid-compose.yaml | ||
|
||
COMMANDS: | ||
- version: shows the project version | ||
- up: deploy the app | ||
- down: cancel all deployments | ||
``` |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,28 @@ | ||
package cmd | ||
|
||
import ( | ||
"github.com/rs/zerolog/log" | ||
"github.com/spf13/cobra" | ||
"github.com/threefoldtech/tfgrid-sdk-go/grid-compose/internal" | ||
) | ||
|
||
var downCmd = &cobra.Command{ | ||
Use: "down", | ||
Short: "cancel your project on the grid", | ||
Run: func(cmd *cobra.Command, args []string) { | ||
if err := down(); err != nil { | ||
log.Fatal().Err(err).Send() | ||
} | ||
}, | ||
} | ||
|
||
func down() error { | ||
for key := range app.Specs.Services { | ||
projectName := internal.GetProjectName(key, app.Client.TwinID) | ||
log.Info().Str("projectName", projectName).Msg("canceling deployments") | ||
if err := app.Client.CancelByProjectName(projectName); err != nil { | ||
return err | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. wrap the error here was e.g There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. move to to a method on App struct in app.go |
||
} | ||
} | ||
return nil | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,123 @@ | ||
package cmd | ||
|
||
import ( | ||
"context" | ||
"encoding/json" | ||
"fmt" | ||
"strconv" | ||
"strings" | ||
|
||
"github.com/rs/zerolog/log" | ||
"github.com/spf13/cobra" | ||
"github.com/threefoldtech/tfgrid-sdk-go/grid-client/deployer" | ||
"github.com/threefoldtech/tfgrid-sdk-go/grid-client/workloads" | ||
"github.com/threefoldtech/tfgrid-sdk-go/grid-compose/internal" | ||
"github.com/threefoldtech/tfgrid-sdk-go/grid-proxy/pkg/types" | ||
) | ||
|
||
var psCmd = &cobra.Command{ | ||
Use: "ps", | ||
Short: "list containers", | ||
Run: func(cmd *cobra.Command, args []string) { | ||
if err := ps(cmd.Context()); err != nil { | ||
log.Fatal().Err(err).Send() | ||
} | ||
}, | ||
} | ||
|
||
func ps(ctx context.Context) error { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. move to a method on the app |
||
twindId := uint64(app.Client.TwinID) | ||
filters := types.ContractFilter{ | ||
TwinID: &twindId, | ||
} | ||
limits := types.Limit{ | ||
Size: 100, | ||
} | ||
cache := make(map[string]bool, 0) | ||
|
||
contracts, _, err := app.Client.GridProxyClient.Contracts(ctx, filters, limits) | ||
|
||
if err != nil { | ||
return err | ||
} | ||
|
||
for _, contract := range contracts { | ||
if contract.Type != "node" || contract.State == "Deleted" { | ||
continue | ||
} | ||
|
||
details, err := workloads.ParseDeploymentData(contract.Details.(types.NodeContractDetails).DeploymentData) | ||
if err != nil { | ||
return err | ||
} | ||
|
||
if strings.Split(details.ProjectName, "/")[0] != "compose" || cache[details.ProjectName] { | ||
continue | ||
} | ||
|
||
res, err := GetVM(ctx, app.Client, details.Name) | ||
if err != nil { | ||
return err | ||
} | ||
|
||
s, err := json.MarshalIndent(res, "", "\t") | ||
if err != nil { | ||
log.Fatal().Err(err).Send() | ||
} | ||
|
||
log.Info().Msg("vm:\n" + string(s)) | ||
cache[details.ProjectName] = true | ||
} | ||
|
||
return nil | ||
} | ||
|
||
// GetVM gets a vm with its project name | ||
func GetVM(ctx context.Context, t deployer.TFPluginClient, name string) (workloads.Deployment, error) { | ||
name, _ = strings.CutSuffix(name, "net") | ||
projectName := internal.GetProjectName(name, app.Client.TwinID) | ||
|
||
// try to get contracts with the old project name format "<name>" | ||
contracts, err := t.ContractsGetter.ListContractsOfProjectName(projectName, true) | ||
if err != nil { | ||
return workloads.Deployment{}, err | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. consider returning a wrapper error with more context |
||
} | ||
|
||
if len(contracts.NodeContracts) == 0 { | ||
// if could not find any contracts try to get contracts with the new project name format "vm/<name>" | ||
projectName = fmt.Sprintf("vm/%s", name) | ||
contracts, err = t.ContractsGetter.ListContractsOfProjectName(projectName, true) | ||
if err != nil { | ||
return workloads.Deployment{}, err | ||
} | ||
|
||
if len(contracts.NodeContracts) == 0 { | ||
return workloads.Deployment{}, fmt.Errorf("couldn't find any contracts with name %s", name) | ||
} | ||
} | ||
|
||
var nodeID uint32 | ||
|
||
for _, contract := range contracts.NodeContracts { | ||
contractID, err := strconv.ParseUint(contract.ContractID, 10, 64) | ||
if err != nil { | ||
return workloads.Deployment{}, err | ||
} | ||
|
||
nodeID = contract.NodeID | ||
checkIfExistAndAppend(t, nodeID, contractID) | ||
|
||
} | ||
|
||
return t.State.LoadDeploymentFromGrid(ctx, nodeID, name) | ||
} | ||
|
||
func checkIfExistAndAppend(t deployer.TFPluginClient, node uint32, contractID uint64) { | ||
for _, n := range t.State.CurrentNodeDeployments[node] { | ||
if n == contractID { | ||
return | ||
} | ||
} | ||
|
||
t.State.CurrentNodeDeployments[node] = append(t.State.CurrentNodeDeployments[node], contractID) | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,94 @@ | ||
package cmd | ||
|
||
import ( | ||
"fmt" | ||
"io" | ||
"os" | ||
|
||
"github.com/rs/zerolog" | ||
"github.com/rs/zerolog/log" | ||
"github.com/spf13/cobra" | ||
"github.com/threefoldtech/tfgrid-sdk-go/grid-client/deployer" | ||
"github.com/threefoldtech/tfgrid-sdk-go/grid-compose/pkg/types" | ||
"gopkg.in/yaml.v3" | ||
) | ||
|
||
var ( | ||
app App | ||
configFile string | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. configPath |
||
network string | ||
mnemonic string | ||
) | ||
|
||
var rootCmd = &cobra.Command{ | ||
Use: "grid-compose", | ||
Short: "Grid-Compose is a tool for running multi-vm applications on TFGrid defined using a Yaml formatted file.", | ||
PersistentPreRun: func(cmd *cobra.Command, args []string) { | ||
var err error | ||
app, err = NewApp(network, mnemonic, configFile) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. if app, err := ... ; err != nil { |
||
if err != nil { | ||
log.Fatal().Err(err).Send() | ||
} | ||
}, | ||
} | ||
|
||
func init() { | ||
network = os.Getenv("NETWORK") | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. move getting the env to the Run in rootCmd |
||
mnemonic = os.Getenv("MNEMONIC") | ||
rootCmd.PersistentFlags().StringVarP(&configFile, "file", "f", "./grid-compose.yaml", "the grid-compose configuration file") | ||
|
||
log.Logger = log.Output(zerolog.ConsoleWriter{Out: os.Stderr}) | ||
|
||
rootCmd.AddCommand(versionCmd) | ||
rootCmd.AddCommand(upCmd) | ||
rootCmd.AddCommand(psCmd) | ||
rootCmd.AddCommand(downCmd) | ||
} | ||
|
||
type App struct { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Not the right place, should be in internal/app.go or similar |
||
Client deployer.TFPluginClient | ||
Specs types.Specs | ||
} | ||
|
||
func NewApp(net, mne, filePath string) (App, error) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. factory function should be in internal/app.go and use better names eg mnemonic instead of mne |
||
specs, err := LoadSpecsFromFile(filePath) | ||
if err != nil { | ||
return App{}, fmt.Errorf("failed to load specs from file: %w", err) | ||
} | ||
|
||
client, err := deployer.NewTFPluginClient(mne, deployer.WithNetwork(net)) | ||
if err != nil { | ||
return App{}, fmt.Errorf("failed to load grid client: %w", err) | ||
} | ||
|
||
return App{ | ||
Specs: specs, | ||
Client: client, | ||
}, nil | ||
} | ||
|
||
func LoadSpecsFromFile(filePath string) (types.Specs, error) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. create a loadSpecsFromReader with io.Reader and don't bother about paths, opening/closing the files. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. and of course, not the right place, internal/app.go |
||
file, err := os.Open(filePath) | ||
if err != nil { | ||
return types.Specs{}, fmt.Errorf("failed to open file: %w", err) | ||
} | ||
defer file.Close() | ||
|
||
content, err := io.ReadAll(file) | ||
if err != nil { | ||
return types.Specs{}, fmt.Errorf("failed to read file: %w", err) | ||
} | ||
|
||
var specs types.Specs | ||
if err := yaml.Unmarshal(content, &specs); err != nil { | ||
return types.Specs{}, fmt.Errorf("failed to parse file: %w", err) | ||
} | ||
|
||
return specs, nil | ||
} | ||
|
||
func Execute() { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. move this to the beginning of the file |
||
if err := rootCmd.Execute(); err != nil { | ||
log.Fatal().Err(err).Send() | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,95 @@ | ||
package cmd | ||
|
||
import ( | ||
"context" | ||
"fmt" | ||
"net" | ||
|
||
"github.com/rs/zerolog/log" | ||
"github.com/spf13/cobra" | ||
"github.com/threefoldtech/tfgrid-sdk-go/grid-client/workloads" | ||
"github.com/threefoldtech/tfgrid-sdk-go/grid-compose/internal" | ||
"github.com/threefoldtech/zos/pkg/gridtypes" | ||
) | ||
|
||
var upCmd = &cobra.Command{ | ||
Use: "up", | ||
Short: "deploy application on the grid", | ||
Run: func(cmd *cobra.Command, args []string) { | ||
if err := up(cmd.Context()); err != nil { | ||
log.Fatal().Err(err).Send() | ||
} | ||
}, | ||
} | ||
|
||
func up(ctx context.Context) error { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. move that into internal/app.go and make it a method There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. and same for the rest.. |
||
results := make(map[string]workloads.VM) | ||
for key, val := range app.Specs.Services { | ||
projectName := internal.GetProjectName(key, app.Client.TwinID) | ||
|
||
networkName := key + "net" | ||
log.Info().Str("projectName", projectName).Str("workloadName", networkName).Msg("deploying network") | ||
net := workloads.ZNet{ | ||
Name: networkName, | ||
Nodes: []uint32{uint32(val.NodeID)}, | ||
IPRange: gridtypes.NewIPNet(net.IPNet{ | ||
IP: net.IPv4(10, 20, 0, 0), | ||
Mask: net.CIDRMask(16, 32), | ||
}), | ||
SolutionType: projectName, | ||
} | ||
if err := app.Client.NetworkDeployer.Deploy(ctx, &net); err != nil { | ||
return err | ||
} | ||
|
||
log.Info().Str("projectName", projectName).Str("workloadName", key).Msg("deploying vm") | ||
vm := workloads.VM{ | ||
Name: key, | ||
Flist: val.Flist, | ||
Entrypoint: val.Entrypoint, | ||
EnvVars: val.Environment, | ||
CPU: int(val.Resources.CPU), | ||
Memory: int(val.Resources.Memory), | ||
NetworkName: net.Name, | ||
} | ||
|
||
for _, net := range val.Networks { | ||
switch net { | ||
case "publicIp4": | ||
vm.PublicIP = true | ||
case "publicIp6": | ||
vm.PublicIP6 = true | ||
case "yggdrasil": | ||
vm.Planetary = true | ||
case "mycelium": | ||
seed, err := internal.GetRandomMyceliumIPSeed() | ||
if err != nil { | ||
return fmt.Errorf("failed to get mycelium seed: %w", err) | ||
} | ||
vm.MyceliumIPSeed = seed | ||
} | ||
} | ||
|
||
dl := workloads.NewDeployment(vm.Name, uint32(val.NodeID), projectName, nil, net.Name, nil, nil, []workloads.VM{vm}, nil) | ||
if err := app.Client.DeploymentDeployer.Deploy(ctx, &dl); err != nil { | ||
log.Error().Err(err).Msg("reverting deployed network") | ||
if err := app.Client.NetworkDeployer.Cancel(ctx, &net); err != nil { | ||
return err | ||
} | ||
return err | ||
} | ||
|
||
res, err := app.Client.State.LoadVMFromGrid(ctx, uint32(val.NodeID), vm.Name, dl.Name) | ||
if err != nil { | ||
return err | ||
} | ||
|
||
results[vm.Name] = res | ||
} | ||
|
||
for key, val := range results { | ||
fmt.Printf("%s vm addresses:\n", key) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. why mixing logging with printing? |
||
fmt.Println(internal.GetVmAddresses(val)) | ||
} | ||
return nil | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
move to a method on App struct in internal/app.go