package spec import ( "encoding/json" "os" agentFlag "forge.cadoles.com/Cadoles/emissary/internal/command/client/agent/flag" "forge.cadoles.com/Cadoles/emissary/internal/command/client/apierr" clientFlag "forge.cadoles.com/Cadoles/emissary/internal/command/client/flag" "forge.cadoles.com/Cadoles/emissary/internal/spec" "forge.cadoles.com/Cadoles/emissary/pkg/client" jsonpatch "github.com/evanphx/json-patch/v5" "github.com/pkg/errors" "github.com/urfave/cli/v2" "gitlab.com/wpetit/goweb/cli/format" ) 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 specification's name", }, &cli.StringFlag{ Name: "spec-version", Usage: "use `VERSION` as specification's version", Value: "0.0.0", }, &cli.StringFlag{ Name: "spec-data", Usage: "use `DATA` as specification's 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) } specDefName, err := assertSpecDefName(ctx) if err != nil { return errors.WithStack(err) } specDefVersion, err := assertSpecDefVersion(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.SpecDefinitionName() != specDefName || s.SpecDefinitionVersion() != specDefVersion { 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{ DefinitionName: specDefName, DefinitionVersion: specDefVersion, Revision: revision, Data: specData, } spec, err := client.UpdateAgentSpec(ctx.Context, agentID, rawSpec) if err != nil { return errors.WithStack(apierr.Wrap(err)) } hints := specHints(baseFlags.OutputMode) if err := format.Write(baseFlags.Format, os.Stdout, hints, spec); err != nil { return errors.WithStack(err) } return nil }, } } func assertSpecDefName(ctx *cli.Context) (string, error) { specDefName := ctx.String("spec-name") if specDefName == "" { return "", errors.New("flag 'spec-name' is required") } return specDefName, nil } func assertSpecDefVersion(ctx *cli.Context) (string, error) { specDefVersion := ctx.String("spec-version") if specDefVersion == "" { return "", errors.New("flag 'spec-name' is required") } return specDefVersion, 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 }