feat: rename client subcommand as api
This commit is contained in:
53
internal/command/api/agent/count.go
Normal file
53
internal/command/api/agent/count.go
Normal file
@ -0,0 +1,53 @@
|
||||
package agent
|
||||
|
||||
import (
|
||||
"os"
|
||||
|
||||
"forge.cadoles.com/Cadoles/emissary/internal/client"
|
||||
"forge.cadoles.com/Cadoles/emissary/internal/command/api/apierr"
|
||||
clientFlag "forge.cadoles.com/Cadoles/emissary/internal/command/api/flag"
|
||||
"forge.cadoles.com/Cadoles/emissary/internal/format"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/urfave/cli/v2"
|
||||
)
|
||||
|
||||
func CountCommand() *cli.Command {
|
||||
return &cli.Command{
|
||||
Name: "count",
|
||||
Usage: "Count agents",
|
||||
Flags: clientFlag.ComposeFlags(),
|
||||
Action: func(ctx *cli.Context) error {
|
||||
baseFlags := clientFlag.GetBaseFlags(ctx)
|
||||
|
||||
token, err := clientFlag.GetToken(baseFlags)
|
||||
if err != nil {
|
||||
return errors.WithStack(apierr.Wrap(err))
|
||||
}
|
||||
|
||||
client := client.New(baseFlags.ServerURL, client.WithToken(token))
|
||||
|
||||
_, total, err := client.QueryAgents(ctx.Context)
|
||||
if err != nil {
|
||||
return errors.WithStack(apierr.Wrap(err))
|
||||
}
|
||||
|
||||
hints := format.Hints{
|
||||
OutputMode: baseFlags.OutputMode,
|
||||
}
|
||||
|
||||
results := []struct {
|
||||
Total int `json:"total"`
|
||||
}{
|
||||
{
|
||||
Total: total,
|
||||
},
|
||||
}
|
||||
|
||||
if err := format.Write(baseFlags.Format, os.Stdout, hints, clientFlag.AsAnySlice(results)...); err != nil {
|
||||
return errors.WithStack(err)
|
||||
}
|
||||
|
||||
return nil
|
||||
},
|
||||
}
|
||||
}
|
34
internal/command/api/agent/flag/flag.go
Normal file
34
internal/command/api/agent/flag/flag.go
Normal file
@ -0,0 +1,34 @@
|
||||
package flag
|
||||
|
||||
import (
|
||||
"errors"
|
||||
|
||||
clientFlag "forge.cadoles.com/Cadoles/emissary/internal/command/api/flag"
|
||||
"forge.cadoles.com/Cadoles/emissary/internal/datastore"
|
||||
"github.com/urfave/cli/v2"
|
||||
)
|
||||
|
||||
func WithAgentFlags(flags ...cli.Flag) []cli.Flag {
|
||||
baseFlags := clientFlag.ComposeFlags(
|
||||
&cli.Int64Flag{
|
||||
Name: "agent-id",
|
||||
Aliases: []string{"a"},
|
||||
Usage: "use `AGENT_ID` as selected agent",
|
||||
Value: -1,
|
||||
},
|
||||
)
|
||||
|
||||
flags = append(flags, baseFlags...)
|
||||
|
||||
return flags
|
||||
}
|
||||
|
||||
func AssertAgentID(ctx *cli.Context) (datastore.AgentID, error) {
|
||||
rawAgentID := ctx.Int64("agent-id")
|
||||
|
||||
if rawAgentID == -1 {
|
||||
return -1, errors.New("flag 'agent-id' is required")
|
||||
}
|
||||
|
||||
return datastore.AgentID(rawAgentID), nil
|
||||
}
|
49
internal/command/api/agent/get.go
Normal file
49
internal/command/api/agent/get.go
Normal file
@ -0,0 +1,49 @@
|
||||
package agent
|
||||
|
||||
import (
|
||||
"os"
|
||||
|
||||
"forge.cadoles.com/Cadoles/emissary/internal/client"
|
||||
agentFlag "forge.cadoles.com/Cadoles/emissary/internal/command/api/agent/flag"
|
||||
"forge.cadoles.com/Cadoles/emissary/internal/command/api/apierr"
|
||||
clientFlag "forge.cadoles.com/Cadoles/emissary/internal/command/api/flag"
|
||||
"forge.cadoles.com/Cadoles/emissary/internal/format"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/urfave/cli/v2"
|
||||
)
|
||||
|
||||
func GetCommand() *cli.Command {
|
||||
return &cli.Command{
|
||||
Name: "get",
|
||||
Usage: "Get agent",
|
||||
Flags: agentFlag.WithAgentFlags(),
|
||||
Action: func(ctx *cli.Context) error {
|
||||
baseFlags := clientFlag.GetBaseFlags(ctx)
|
||||
|
||||
token, err := clientFlag.GetToken(baseFlags)
|
||||
if err != nil {
|
||||
return errors.WithStack(apierr.Wrap(err))
|
||||
}
|
||||
|
||||
agentID, err := agentFlag.AssertAgentID(ctx)
|
||||
if err != nil {
|
||||
return errors.WithStack(err)
|
||||
}
|
||||
|
||||
client := client.New(baseFlags.ServerURL, client.WithToken(token))
|
||||
|
||||
agent, err := client.GetAgent(ctx.Context, agentID)
|
||||
if err != nil {
|
||||
return errors.WithStack(apierr.Wrap(err))
|
||||
}
|
||||
|
||||
hints := agentHints(baseFlags.OutputMode)
|
||||
|
||||
if err := format.Write(baseFlags.Format, os.Stdout, hints, agent); err != nil {
|
||||
return errors.WithStack(err)
|
||||
}
|
||||
|
||||
return nil
|
||||
},
|
||||
}
|
||||
}
|
43
internal/command/api/agent/query.go
Normal file
43
internal/command/api/agent/query.go
Normal file
@ -0,0 +1,43 @@
|
||||
package agent
|
||||
|
||||
import (
|
||||
"os"
|
||||
|
||||
"forge.cadoles.com/Cadoles/emissary/internal/client"
|
||||
"forge.cadoles.com/Cadoles/emissary/internal/command/api/apierr"
|
||||
clientFlag "forge.cadoles.com/Cadoles/emissary/internal/command/api/flag"
|
||||
"forge.cadoles.com/Cadoles/emissary/internal/format"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/urfave/cli/v2"
|
||||
)
|
||||
|
||||
func QueryCommand() *cli.Command {
|
||||
return &cli.Command{
|
||||
Name: "query",
|
||||
Usage: "Query agents",
|
||||
Flags: clientFlag.ComposeFlags(),
|
||||
Action: func(ctx *cli.Context) error {
|
||||
baseFlags := clientFlag.GetBaseFlags(ctx)
|
||||
|
||||
token, err := clientFlag.GetToken(baseFlags)
|
||||
if err != nil {
|
||||
return errors.WithStack(apierr.Wrap(err))
|
||||
}
|
||||
|
||||
client := client.New(baseFlags.ServerURL, client.WithToken(token))
|
||||
|
||||
agents, _, err := client.QueryAgents(ctx.Context)
|
||||
if err != nil {
|
||||
return errors.WithStack(apierr.Wrap(err))
|
||||
}
|
||||
|
||||
hints := agentHints(baseFlags.OutputMode)
|
||||
|
||||
if err := format.Write(baseFlags.Format, os.Stdout, hints, clientFlag.AsAnySlice(agents)...); err != nil {
|
||||
return errors.WithStack(err)
|
||||
}
|
||||
|
||||
return nil
|
||||
},
|
||||
}
|
||||
}
|
20
internal/command/api/agent/root.go
Normal file
20
internal/command/api/agent/root.go
Normal file
@ -0,0 +1,20 @@
|
||||
package agent
|
||||
|
||||
import (
|
||||
"forge.cadoles.com/Cadoles/emissary/internal/command/api/agent/spec"
|
||||
"github.com/urfave/cli/v2"
|
||||
)
|
||||
|
||||
func Root() *cli.Command {
|
||||
return &cli.Command{
|
||||
Name: "agent",
|
||||
Usage: "Agents related commands",
|
||||
Subcommands: []*cli.Command{
|
||||
QueryCommand(),
|
||||
CountCommand(),
|
||||
UpdateCommand(),
|
||||
GetCommand(),
|
||||
spec.Root(),
|
||||
},
|
||||
}
|
||||
}
|
50
internal/command/api/agent/spec/get.go
Normal file
50
internal/command/api/agent/spec/get.go
Normal file
@ -0,0 +1,50 @@
|
||||
package spec
|
||||
|
||||
import (
|
||||
"os"
|
||||
|
||||
"forge.cadoles.com/Cadoles/emissary/internal/client"
|
||||
agentFlag "forge.cadoles.com/Cadoles/emissary/internal/command/api/agent/flag"
|
||||
"forge.cadoles.com/Cadoles/emissary/internal/command/api/apierr"
|
||||
clientFlag "forge.cadoles.com/Cadoles/emissary/internal/command/api/flag"
|
||||
"forge.cadoles.com/Cadoles/emissary/internal/format"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/urfave/cli/v2"
|
||||
)
|
||||
|
||||
func GetCommand() *cli.Command {
|
||||
return &cli.Command{
|
||||
Name: "get",
|
||||
Usage: "Get agent specifications",
|
||||
Flags: agentFlag.WithAgentFlags(),
|
||||
Action: func(ctx *cli.Context) error {
|
||||
baseFlags := clientFlag.GetBaseFlags(ctx)
|
||||
agentID, err := agentFlag.AssertAgentID(ctx)
|
||||
if err != nil {
|
||||
return errors.WithStack(err)
|
||||
}
|
||||
|
||||
token, err := clientFlag.GetToken(baseFlags)
|
||||
if err != nil {
|
||||
return errors.WithStack(apierr.Wrap(err))
|
||||
}
|
||||
|
||||
client := client.New(baseFlags.ServerURL, client.WithToken(token))
|
||||
|
||||
specs, err := client.GetAgentSpecs(ctx.Context, agentID)
|
||||
if err != nil {
|
||||
return errors.WithStack(apierr.Wrap(err))
|
||||
}
|
||||
|
||||
hints := format.Hints{
|
||||
OutputMode: baseFlags.OutputMode,
|
||||
}
|
||||
|
||||
if err := format.Write(baseFlags.Format, os.Stdout, hints, clientFlag.AsAnySlice(specs)...); err != nil {
|
||||
return errors.WithStack(err)
|
||||
}
|
||||
|
||||
return nil
|
||||
},
|
||||
}
|
||||
}
|
16
internal/command/api/agent/spec/root.go
Normal file
16
internal/command/api/agent/spec/root.go
Normal file
@ -0,0 +1,16 @@
|
||||
package spec
|
||||
|
||||
import (
|
||||
"github.com/urfave/cli/v2"
|
||||
)
|
||||
|
||||
func Root() *cli.Command {
|
||||
return &cli.Command{
|
||||
Name: "spec",
|
||||
Usage: "Specifications related commands",
|
||||
Subcommands: []*cli.Command{
|
||||
GetCommand(),
|
||||
UpdateCommand(),
|
||||
},
|
||||
}
|
||||
}
|
190
internal/command/api/agent/spec/update.go
Normal file
190
internal/command/api/agent/spec/update.go
Normal file
@ -0,0 +1,190 @@
|
||||
package spec
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"os"
|
||||
|
||||
"forge.cadoles.com/Cadoles/emissary/internal/client"
|
||||
agentFlag "forge.cadoles.com/Cadoles/emissary/internal/command/api/agent/flag"
|
||||
"forge.cadoles.com/Cadoles/emissary/internal/command/api/apierr"
|
||||
clientFlag "forge.cadoles.com/Cadoles/emissary/internal/command/api/flag"
|
||||
"forge.cadoles.com/Cadoles/emissary/internal/format"
|
||||
"forge.cadoles.com/Cadoles/emissary/internal/spec"
|
||||
jsonpatch "github.com/evanphx/json-patch/v5"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/urfave/cli/v2"
|
||||
|
||||
// Import specs
|
||||
_ "forge.cadoles.com/Cadoles/emissary/internal/spec/app"
|
||||
_ "forge.cadoles.com/Cadoles/emissary/internal/spec/gateway"
|
||||
_ "forge.cadoles.com/Cadoles/emissary/internal/spec/uci"
|
||||
)
|
||||
|
||||
func UpdateCommand() *cli.Command {
|
||||
return &cli.Command{
|
||||
Name: "update",
|
||||
Usage: "Update agent specification",
|
||||
Flags: agentFlag.WithAgentFlags(
|
||||
&cli.StringFlag{
|
||||
Name: "spec-name",
|
||||
Usage: "use `NAME` as spec name",
|
||||
},
|
||||
&cli.StringFlag{
|
||||
Name: "spec-data",
|
||||
Usage: "use `DATA` as spec data, '-' to read from STDIN",
|
||||
},
|
||||
&cli.BoolFlag{
|
||||
Name: "no-patch",
|
||||
Usage: "Dont use spec-data as a patch to existing specification",
|
||||
},
|
||||
&cli.IntFlag{
|
||||
Name: "revision",
|
||||
Usage: "Use `REVISION` as specification revision number",
|
||||
},
|
||||
),
|
||||
Action: func(ctx *cli.Context) error {
|
||||
baseFlags := clientFlag.GetBaseFlags(ctx)
|
||||
agentID, err := agentFlag.AssertAgentID(ctx)
|
||||
if err != nil {
|
||||
return errors.WithStack(err)
|
||||
}
|
||||
|
||||
specName, err := assertSpecName(ctx)
|
||||
if err != nil {
|
||||
return errors.WithStack(err)
|
||||
}
|
||||
|
||||
specData, err := assertSpecData(ctx)
|
||||
if err != nil {
|
||||
return errors.WithStack(err)
|
||||
}
|
||||
|
||||
noPatch := ctx.Bool("no-patch")
|
||||
|
||||
token, err := clientFlag.GetToken(baseFlags)
|
||||
if err != nil {
|
||||
return errors.WithStack(apierr.Wrap(err))
|
||||
}
|
||||
|
||||
client := client.New(baseFlags.ServerURL, client.WithToken(token))
|
||||
|
||||
specs, err := client.GetAgentSpecs(ctx.Context, agentID)
|
||||
if err != nil {
|
||||
return errors.WithStack(apierr.Wrap(err))
|
||||
}
|
||||
|
||||
var existingSpec spec.Spec
|
||||
|
||||
for _, s := range specs {
|
||||
if s.SpecName() != specName {
|
||||
continue
|
||||
}
|
||||
|
||||
existingSpec = s
|
||||
|
||||
break
|
||||
}
|
||||
|
||||
revision := 0
|
||||
|
||||
if existingSpec != nil {
|
||||
originSpecData := existingSpec.SpecData()
|
||||
|
||||
if !noPatch {
|
||||
specData, err = applyPatch(originSpecData, specData)
|
||||
if err != nil {
|
||||
return errors.WithStack(err)
|
||||
}
|
||||
}
|
||||
|
||||
revision = existingSpec.SpecRevision()
|
||||
}
|
||||
|
||||
if specificRevision := ctx.Int("revision"); specificRevision != 0 {
|
||||
revision = specificRevision
|
||||
}
|
||||
|
||||
rawSpec := &spec.RawSpec{
|
||||
Name: specName,
|
||||
Revision: revision,
|
||||
Data: specData,
|
||||
}
|
||||
|
||||
if err := spec.Validate(ctx.Context, rawSpec); err != nil {
|
||||
return errors.WithStack(apierr.Wrap(err))
|
||||
}
|
||||
|
||||
spec, err := client.UpdateAgentSpec(ctx.Context, agentID, rawSpec)
|
||||
if err != nil {
|
||||
return errors.WithStack(apierr.Wrap(err))
|
||||
}
|
||||
|
||||
hints := format.Hints{
|
||||
OutputMode: baseFlags.OutputMode,
|
||||
}
|
||||
|
||||
if err := format.Write(baseFlags.Format, os.Stdout, hints, spec); err != nil {
|
||||
return errors.WithStack(err)
|
||||
}
|
||||
|
||||
return nil
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func assertSpecName(ctx *cli.Context) (spec.Name, error) {
|
||||
specName := ctx.String("spec-name")
|
||||
|
||||
if specName == "" {
|
||||
return "", errors.New("flag 'spec-name' is required")
|
||||
}
|
||||
|
||||
return spec.Name(specName), nil
|
||||
}
|
||||
|
||||
func assertSpecData(ctx *cli.Context) (map[string]any, error) {
|
||||
rawSpecData := ctx.String("spec-data")
|
||||
|
||||
if rawSpecData == "" {
|
||||
return nil, errors.New("flag 'spec-data' is required")
|
||||
}
|
||||
|
||||
var specData map[string]any
|
||||
|
||||
if rawSpecData == "-" {
|
||||
decoder := json.NewDecoder(os.Stdin)
|
||||
if err := decoder.Decode(&specData); err != nil {
|
||||
return nil, errors.WithStack(err)
|
||||
}
|
||||
} else {
|
||||
if err := json.Unmarshal([]byte(rawSpecData), &specData); err != nil {
|
||||
return nil, errors.WithStack(err)
|
||||
}
|
||||
}
|
||||
|
||||
return specData, nil
|
||||
}
|
||||
|
||||
func applyPatch(origin any, patch any) (map[string]any, error) {
|
||||
originJSON, err := json.Marshal(origin)
|
||||
if err != nil {
|
||||
return nil, errors.WithStack(err)
|
||||
}
|
||||
|
||||
patchJSON, err := json.Marshal(patch)
|
||||
if err != nil {
|
||||
return nil, errors.WithStack(err)
|
||||
}
|
||||
|
||||
result, err := jsonpatch.MergePatch(originJSON, patchJSON)
|
||||
if err != nil {
|
||||
return nil, errors.WithStack(err)
|
||||
}
|
||||
|
||||
var specData map[string]any
|
||||
|
||||
if err := json.Unmarshal(result, &specData); err != nil {
|
||||
}
|
||||
|
||||
return specData, nil
|
||||
}
|
62
internal/command/api/agent/update.go
Normal file
62
internal/command/api/agent/update.go
Normal file
@ -0,0 +1,62 @@
|
||||
package agent
|
||||
|
||||
import (
|
||||
"os"
|
||||
|
||||
"forge.cadoles.com/Cadoles/emissary/internal/client"
|
||||
agentFlag "forge.cadoles.com/Cadoles/emissary/internal/command/api/agent/flag"
|
||||
"forge.cadoles.com/Cadoles/emissary/internal/command/api/apierr"
|
||||
clientFlag "forge.cadoles.com/Cadoles/emissary/internal/command/api/flag"
|
||||
"forge.cadoles.com/Cadoles/emissary/internal/format"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/urfave/cli/v2"
|
||||
)
|
||||
|
||||
func UpdateCommand() *cli.Command {
|
||||
return &cli.Command{
|
||||
Name: "update",
|
||||
Usage: "Updata agent",
|
||||
Flags: agentFlag.WithAgentFlags(
|
||||
&cli.IntFlag{
|
||||
Name: "status",
|
||||
Usage: "Set `STATUS` to selected agent",
|
||||
Value: -1,
|
||||
},
|
||||
),
|
||||
Action: func(ctx *cli.Context) error {
|
||||
baseFlags := clientFlag.GetBaseFlags(ctx)
|
||||
|
||||
token, err := clientFlag.GetToken(baseFlags)
|
||||
if err != nil {
|
||||
return errors.WithStack(apierr.Wrap(err))
|
||||
}
|
||||
|
||||
agentID, err := agentFlag.AssertAgentID(ctx)
|
||||
if err != nil {
|
||||
return errors.WithStack(err)
|
||||
}
|
||||
|
||||
options := make([]client.UpdateAgentOptionFunc, 0)
|
||||
|
||||
status := ctx.Int("status")
|
||||
if status != -1 {
|
||||
options = append(options, client.WithAgentStatus(status))
|
||||
}
|
||||
|
||||
client := client.New(baseFlags.ServerURL, client.WithToken(token))
|
||||
|
||||
agent, err := client.UpdateAgent(ctx.Context, agentID, options...)
|
||||
if err != nil {
|
||||
return errors.WithStack(apierr.Wrap(err))
|
||||
}
|
||||
|
||||
hints := agentHints(baseFlags.OutputMode)
|
||||
|
||||
if err := format.Write(baseFlags.Format, os.Stdout, hints, agent); err != nil {
|
||||
return errors.WithStack(err)
|
||||
}
|
||||
|
||||
return nil
|
||||
},
|
||||
}
|
||||
}
|
16
internal/command/api/agent/util.go
Normal file
16
internal/command/api/agent/util.go
Normal file
@ -0,0 +1,16 @@
|
||||
package agent
|
||||
|
||||
import "forge.cadoles.com/Cadoles/emissary/internal/format"
|
||||
|
||||
func agentHints(outputMode format.OutputMode) format.Hints {
|
||||
return format.Hints{
|
||||
OutputMode: outputMode,
|
||||
Props: []format.Prop{
|
||||
format.NewProp("ID", "ID"),
|
||||
format.NewProp("Thumbprint", "Thumbprint"),
|
||||
format.NewProp("Status", "Status"),
|
||||
format.NewProp("CreatedAt", "CreatedAt"),
|
||||
format.NewProp("UpdatedAt", "UpdatedAt"),
|
||||
},
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user