Skip to content
Merged
3 changes: 2 additions & 1 deletion README.md
Original file line numberDiff line numberDiff line change
Expand Up@@ -11,7 +11,8 @@ pods, nodes, workloads, images, networks and one-shot lambdas.
- Eight command groups — `core`, `pod`, `node`, `workload`, `image`, `network`, `status`, `lambda` —
covering eru core's RPCs; `GetPod`, `GetWorkload`, `GetNodeStatus`, `GetNodeEngineInfo` and
`RawEngine` have no command of their own.
- Table, JSON or YAML output for every read command, selected once with `--output`.
- Table, JSON or YAML output for every read command that prints a described resource, selected once
with `--output`.
- Interactive streams: `workload exec` and `lambda` attach a raw terminal, forward `SIGWINCH`
and return the remote exit code as their own.
- Script-friendly exit status: a batch command that acts on many workloads exits non-zero when any
Expand Down
11 changes: 1 addition & 10 deletions cmd/image/build.go
Original file line numberDiff line numberDiff line change
Expand Up@@ -7,7 +7,6 @@ import (
"fmt"
"io"
"os"
"strings"

"github.com/projecteru2/core/log"
corepb "github.com/projecteru2/core/rpc/gen"
Expand DownExpand Up@@ -119,15 +118,7 @@ func generateBuildOptions(ctx context.Context, cmd *cli.Command) (*corepb.BuildI
specURI := cmd.Args().First()
log.WithFunc("image.generateBuildOptions").Debugf(ctx, "deploy %s", specURI)

var (
data []byte
err error
)
if strings.HasPrefix(specURI, "http://") || strings.HasPrefix(specURI, "https://") {
data, err = utils.GetSpecFromRemote(ctx, specURI)
} else {
data, err = os.ReadFile(specURI) //nolint:gosec
}
data, err := utils.ReadSpecURI(ctx, specURI)
if err != nil {
return nil, fmt.Errorf("read spec: %w", err)
}
Expand Down
22 changes: 22 additions & 0 deletions cmd/network/cmd.go
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,10 @@
package network

import (
"context"
"errors"

corepb "github.com/projecteru2/core/rpc/gen"
"github.com/urfave/cli/v3"

"github.com/projecteru2/cli/cmd/utils"
Expand DownExpand Up@@ -55,3 +59,21 @@ func Command() *cli.Command {
},
}
}

func networkTarget(ctx context.Context, cmd *cli.Command) (corepb.CoreRPCClient, []string, string, error) {
client, err := utils.NewCoreRPCClient(ctx, cmd)
if err != nil {
return nil, nil, "", err
}

ids := cmd.Args().Slice()
if len(ids) == 0 {
return nil, nil, "", errors.New("workload id(s) must be specified")
}

network := cmd.String(flagNetwork)
if network == "" {
return nil, nil, "", errors.New("network must be specified")
}
return client, ids, network, nil
}
14 changes: 1 addition & 13 deletions cmd/network/connect.go
Original file line numberDiff line numberDiff line change
Expand Up@@ -8,8 +8,6 @@ import (
"github.com/projecteru2/core/log"
corepb "github.com/projecteru2/core/rpc/gen"
"github.com/urfave/cli/v3"

"github.com/projecteru2/cli/cmd/utils"
)

type connectNetworkOptions struct {
Expand DownExpand Up@@ -40,21 +38,11 @@ func (o *connectNetworkOptions) run(ctx context.Context) error {
}

func cmdNetworkConnect(ctx context.Context, cmd *cli.Command) error {
client, err := utils.NewCoreRPCClient(ctx, cmd)
client, ids, network, err := networkTarget(ctx, cmd)
if err != nil {
return err
}

ids := cmd.Args().Slice()
if len(ids) == 0 {
return errors.New("workload id(s) must be specified")
}

network := cmd.String(flagNetwork)
if network == "" {
return errors.New("network must be specified")
}

o := &connectNetworkOptions{
client: client,
ids: ids,
Expand Down
14 changes: 1 addition & 13 deletions cmd/network/disconnect.go
Original file line numberDiff line numberDiff line change
Expand Up@@ -8,8 +8,6 @@ import (
"github.com/projecteru2/core/log"
corepb "github.com/projecteru2/core/rpc/gen"
"github.com/urfave/cli/v3"

"github.com/projecteru2/cli/cmd/utils"
)

type disconnectNetworkOptions struct {
Expand All@@ -35,21 +33,11 @@ func (o *disconnectNetworkOptions) run(ctx context.Context) error {
}

func cmdNetworkDisconnect(ctx context.Context, cmd *cli.Command) error {
client, err := utils.NewCoreRPCClient(ctx, cmd)
client, ids, network, err := networkTarget(ctx, cmd)
if err != nil {
return err
}

ids := cmd.Args().Slice()
if len(ids) == 0 {
return errors.New("workload id(s) must be specified")
}

network := cmd.String(flagNetwork)
if network == "" {
return errors.New("network must be specified")
}

o := &disconnectNetworkOptions{
client: client,
ids: ids,
Expand Down
13 changes: 3 additions & 10 deletions cmd/pod/nodes.go
Original file line numberDiff line numberDiff line change
Expand Up@@ -23,7 +23,7 @@ type listPodNodesOptions struct {
}

func (o *listPodNodesOptions) run(ctx context.Context) error {
ch, wait, err := o.listChan(ctx, &corepb.ListNodesOptions{
stream, err := o.client.ListPodNodes(ctx, &corepb.ListNodesOptions{
Podname: o.name,
All: o.filter != up,
Labels: o.labels,
Expand All@@ -33,22 +33,15 @@ func (o *listPodNodesOptions) run(ctx context.Context) error {
if err != nil {
return err
}

ch, wait := utils.StreamToChan(stream.Recv)
if o.filter == down {
ch = downOnly(ch)
}
describe.NodesStream(ch, o.showInfo, o.stream)
return wait()
}

func (o *listPodNodesOptions) listChan(ctx context.Context, opt *corepb.ListNodesOptions) (<-chan *corepb.Node, func() error, error) {
stream, err := o.client.ListPodNodes(ctx, opt)
if err != nil {
return nil, nil, err
}
ch, wait := utils.StreamToChan(stream.Recv)
return ch, wait, nil
}

func cmdPodListNodes(ctx context.Context, cmd *cli.Command) error {
client, err := utils.NewCoreRPCClient(ctx, cmd)
if err != nil {
Expand Down
7 changes: 7 additions & 0 deletions cmd/utils/file.go
Original file line numberDiff line numberDiff line change
Expand Up@@ -124,3 +124,10 @@ func GetSpecFromRemote(ctx context.Context, uri string) ([]byte, error) {
}
return io.ReadAll(resp.Body)
}

func ReadSpecURI(ctx context.Context, uri string) ([]byte, error) {
if strings.HasPrefix(uri, "http://") || strings.HasPrefix(uri, "https://") {
return GetSpecFromRemote(ctx, uri)
}
return os.ReadFile(uri) //nolint:gosec
}
6 changes: 2 additions & 4 deletions cmd/utils/utils.go
Original file line numberDiff line numberDiff line change
Expand Up@@ -19,10 +19,8 @@ import (
// GetNetworks returns a networkmode -> ip map.
func GetNetworks(network string) map[string]string {
var ip string
networkInfo := strings.Split(network, "=")
if len(networkInfo) == 2 {
network = networkInfo[0]
ip = networkInfo[1]
if name, address, ok := strings.Cut(network, "="); ok && !strings.Contains(address, "=") {
network, ip = name, address
}
networks := map[string]string{}
if network != "" {
Expand Down
54 changes: 8 additions & 46 deletions cmd/workload/deploy.go
Original file line numberDiff line numberDiff line change
Expand Up@@ -114,12 +114,7 @@ func doCreateWorkload(ctx context.Context, client corepb.CoreRPCClient, deployOp
}

func generateDeployOptions(ctx context.Context, cmd *cli.Command) (*corepb.DeployOptions, error) {
specs, err := loadSpecs(ctx, cmd)
if err != nil {
return nil, err
}

entrypoint, err := entrypointOptions(specs, cmd.String(flagEntry))
opts, specs, err := baseDeployOptions(ctx, cmd)
if err != nil {
return nil, err
}
Expand All@@ -134,14 +129,7 @@ func generateDeployOptions(ctx context.Context, cmd *cli.Command) (*corepb.Deplo
return nil, fmt.Errorf("parse storage: %w", err)
}

cpuRequest, cpuLimit := cpuOption(cmd)

cpumem := resourcetypes.RawParams{
flagCPURequest: cpuRequest,
flagCPULimit: cpuLimit,
flagMemoryRequest: memoryRequest,
flagMemoryLimit: memoryLimit,
}
cpumem := cpumemParams(cmd, memoryRequest, memoryLimit)
if cmd.Bool("cpu-bind") {
cpumem["cpu-bind"] = true
}
Expand All@@ -153,41 +141,15 @@ func generateDeployOptions(ctx context.Context, cmd *cli.Command) (*corepb.Deplo
return nil, err
}

files, err := utils.GenerateFileOptions(cmd)
if err != nil {
return nil, err
}

deployStrategy, err := utils.ParseDeployStrategy(cmd.String("deploy-strategy"))
if err != nil {
return nil, err
}

return &corepb.DeployOptions{
Name: specs.Appname,
Entrypoint: entrypoint,
Resources: resources,
Podname: cmd.String(flagPod),
NodeFilter: &corepb.NodeFilter{
Includes: cmd.StringSlice(flagNode),
Labels: utils.SplitEquality(cmd.StringSlice("nodelabel")),
},
Image: cmd.String(flagImage),
Count: int32(cmd.Int("count")), //nolint:gosec
Env: cmd.StringSlice(flagEnv),
Networks: utils.GetNetworks(cmd.String(flagNetwork)),
Labels: specs.Labels,
Dns: specs.DNS,
ExtraHosts: specs.ExtraHosts,
DeployStrategy: deployStrategy,
Data: files.Data,
Modes: files.Modes,
Owners: files.Owners,
User: cmd.String("user"),
Debug: cmd.Bool("debug"),
NodesLimit: int32(cmd.Int("nodes-limit")), //nolint:gosec
IgnoreHook: cmd.Bool("ignore-hook"),
AfterCreate: cmd.StringSlice("after-create"),
RawArgs: []byte(cmd.String("raw-args")),
}, nil
opts.Resources = resources
opts.NodeFilter.Labels = utils.SplitEquality(cmd.StringSlice("nodelabel"))
opts.DeployStrategy = deployStrategy
opts.NodesLimit = int32(cmd.Int("nodes-limit")) //nolint:gosec
opts.RawArgs = []byte(cmd.String("raw-args"))
return opts, nil
}
17 changes: 4 additions & 13 deletions cmd/workload/dissociate.go
Original file line numberDiff line numberDiff line change
Expand Up@@ -4,6 +4,7 @@ import (
"context"
"errors"
"fmt"
"slices"

"github.com/projecteru2/core/log"
corepb "github.com/projecteru2/core/rpc/gen"
Expand All@@ -20,27 +21,17 @@ type dissociateWorkloadsOptions struct {

func (o *dissociateWorkloadsOptions) run(ctx context.Context) error {
logger := log.WithFunc("workload.dissociateWorkloadsOptions.run")
ids := make([]string, 0, len(o.ids))
seen := map[string]struct{}{}
appendID := func(id string) {
if _, ok := seen[id]; ok {
return
}
seen[id] = struct{}{}
ids = append(ids, id)
}
for _, id := range o.ids {
appendID(id)
}
ids := slices.Clone(o.ids)
for _, node := range o.nodes {
wrks, err := o.client.ListNodeWorkloads(ctx, &corepb.GetNodeOptions{Nodename: node})
if err != nil {
return err
}
for _, wrk := range wrks.Workloads {
appendID(wrk.Id)
ids = append(ids, wrk.Id)
}
}
ids = slices.Compact(slices.Sorted(slices.Values(ids)))
if len(ids) == 0 {
return errors.New("no workloads found")
}
Expand Down
9 changes: 1 addition & 8 deletions cmd/workload/realloc.go
Original file line numberDiff line numberDiff line change
Expand Up@@ -78,14 +78,7 @@ func generateReallocOptions(cmd *cli.Command) (*corepb.ReallocOptions, error) {
return nil, err
}

cpuRequest, cpuLimit := cpuOption(cmd)

cpumem := resourcetypes.RawParams{
flagCPURequest: cpuRequest,
flagCPULimit: cpuLimit,
flagMemoryRequest: memoryRequest,
flagMemoryLimit: memoryLimit,
}
cpumem := cpumemParams(cmd, memoryRequest, memoryLimit)
switch {
case bindCPU:
cpumem["cpu-bind"] = true
Expand Down
43 changes: 1 addition & 42 deletions cmd/workload/replace.go
Original file line numberDiff line numberDiff line change
Expand Up@@ -33,7 +33,7 @@ func cmdWorkloadReplace(ctx context.Context, cmd *cli.Command) error {
return err
}

opts, err := generateReplaceOptions(ctx, cmd)
opts, _, err := baseDeployOptions(ctx, cmd)
if err != nil {
return err
}
Expand DownExpand Up@@ -87,44 +87,3 @@ func doReplaceWorkload(ctx context.Context, client corepb.CoreRPCClient, deployO
return nil
})
}

func generateReplaceOptions(ctx context.Context, cmd *cli.Command) (*corepb.DeployOptions, error) {
specs, err := loadSpecs(ctx, cmd)
if err != nil {
return nil, err
}

entrypoint, err := entrypointOptions(specs, cmd.String(flagEntry))
if err != nil {
return nil, err
}

files, err := utils.GenerateFileOptions(cmd)
if err != nil {
return nil, err
}

return &corepb.DeployOptions{
Name: specs.Appname,
Entrypoint: entrypoint,
Podname: cmd.String(flagPod),
NodeFilter: &corepb.NodeFilter{
Includes: cmd.StringSlice(flagNode),
},
Image: cmd.String(flagImage),
Count: int32(cmd.Int("count")), //nolint:gosec
Env: cmd.StringSlice(flagEnv),
Networks: utils.GetNetworks(cmd.String(flagNetwork)),
Labels: specs.Labels,
Dns: specs.DNS,
ExtraHosts: specs.ExtraHosts,
DeployStrategy: corepb.DeployOptions_AUTO,
Data: files.Data,
Modes: files.Modes,
Owners: files.Owners,
User: cmd.String("user"),
Debug: cmd.Bool("debug"),
IgnoreHook: cmd.Bool("ignore-hook"),
AfterCreate: cmd.StringSlice("after-create"),
}, nil
}
Loading