Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions changelog/unreleased/Inventory-ADDED-20260630-125338.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
component: Inventory
kind: ADDED
body: New `cy inventory` commands to browse Terraform state inventory. `cy inventory outputs list` and `cy inventory resources list` support project/environment/component scoping, curated filter flags, and a generic repeatable `--filter 'attribute[condition]=value'`. `cy inventory outputs get <key>` prints the raw output value (scriptable), or the full object with `-o json`.
time: 2026-06-30T12:53:38Z
custom:
TYPE: CLI
PR: 469
DETAILS: ""
22 changes: 22 additions & 0 deletions cmd/cycloid/inventory/cmd.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
package inventory

import (
"github.com/spf13/cobra"
)

// NewCommands returns the root "inventory" command with all subcommands attached.
func NewCommands() *cobra.Command {
cmd := &cobra.Command{
Use: "inventory",
Aliases: []string{"inv"},
Short: "Manage the inventory",
Long: "Browse Terraform state outputs and resources tracked by the Cycloid inventory.",
}

cmd.AddCommand(
newOutputsCmd(),
newResourcesCmd(),
)

return cmd
}
44 changes: 44 additions & 0 deletions cmd/cycloid/inventory/common.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
package inventory

import (
"github.com/spf13/cobra"

"github.com/cycloidio/cycloid-cli/cmd/cycloid/middleware"
"github.com/cycloidio/cycloid-cli/internal/cyargs"
)

// addScopingFlags registers the shared project/environment/component flags
// (from cyargs) used to scope inventory queries.
func addScopingFlags(cmd *cobra.Command) {
cyargs.AddProjectFlag(cmd)
cyargs.AddEnvFlag(cmd)
cyargs.AddComponentFlag(cmd)
}

// appendScopingFilters reads the optional --project / --env / --component flags
// and appends the matching LHS filters when they are set.
func appendScopingFilters(cmd *cobra.Command, filters []middleware.LHSFilter) ([]middleware.LHSFilter, error) {
project, err := cyargs.GetProjectOrEmpty(cmd)
if err != nil {
return nil, err
}
env, err := cyargs.GetEnvOrEmpty(cmd)
if err != nil {
return nil, err
}
component, err := cyargs.GetComponentOrEmpty(cmd)
if err != nil {
return nil, err
}

if project != "" {
filters = append(filters, middleware.LHSFilter{Attribute: "project_canonical", Condition: "eq", Value: project})
}
if env != "" {
filters = append(filters, middleware.LHSFilter{Attribute: "environment_canonical", Condition: "eq", Value: env})
}
if component != "" {
filters = append(filters, middleware.LHSFilter{Attribute: "component_canonical", Condition: "eq", Value: component})
}
return filters, nil
}
30 changes: 30 additions & 0 deletions cmd/cycloid/inventory/outputs.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
package inventory

import (
"github.com/spf13/cobra"

"github.com/cycloidio/cycloid-cli/printer"
)

// outputsTableOptions is the shared table view for inventory outputs.
var outputsTableOptions = printer.Options{
Columns: []string{"Key", "Type", "Sensitive", "Pinned", "Description"},
Identifier: "Key",
}

func newOutputsCmd() *cobra.Command {
cmd := &cobra.Command{
Use: "outputs",
Aliases: []string{"output", "out"},
Short: "Manage inventory outputs",
Long: "List and fetch Terraform state outputs tracked by the Cycloid inventory.",
Args: cobra.NoArgs,
}

cmd.AddCommand(
newOutputsListCmd(),
newOutputsGetCmd(),
)

return cmd
}
96 changes: 96 additions & 0 deletions cmd/cycloid/inventory/outputs_get.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
package inventory

import (
"fmt"

"github.com/spf13/cobra"

"github.com/cycloidio/cycloid-cli/cmd/cycloid/common"
"github.com/cycloidio/cycloid-cli/cmd/cycloid/middleware"
"github.com/cycloidio/cycloid-cli/internal/cyargs"
"github.com/cycloidio/cycloid-cli/internal/cyout"
)

func newOutputsGetCmd() *cobra.Command {
cmd := &cobra.Command{
Use: "get <key>",
Args: cobra.ExactArgs(1),
Short: "Get the value of a single inventory output",
Long: `Get the value of a named Terraform state output.

By default this prints only the raw value (via the "value" field output),
which is convenient for scripting:

IP=$(cy --org my-org inventory outputs get instance_ip --project p --env e)

Output keys are scoped to a project/environment/component; if a key matches
more than one output, narrow the result with --project / --env / --component.

Override the default with -o json (full object), -o yaml, or -o jq=<expr>.`,
Example: `
# Print the raw value (default — scriptable)
cy --org my-org inventory outputs get instance_ip --project my-project --env prod

# Get the full object as JSON
cy --org my-org inventory outputs get instance_ip -o json
`,
RunE: outputsGet,
}

addScopingFlags(cmd)
cyargs.AddLHSFilterFlag(cmd)
cyout.RegisterModel(cmd, middleware.InventoryOutput{})

return cmd
}

func outputsGet(cmd *cobra.Command, args []string) error {
key := args[0]

// Step 1: all flags first.
org, err := cyargs.GetOrg(cmd)
if err != nil {
return err
}

filters, err := cyargs.GetLHSFilters(cmd)
if err != nil {
return err
}
filters, err = appendScopingFilters(cmd, filters)
if err != nil {
return err
}
filters = append(filters, middleware.LHSFilter{Attribute: "output_key", Condition: "eq", Value: key})

// Step 2: API + middleware.
api := common.NewAPI()
m := middleware.NewMiddleware(api)

outputs, _, err := m.ListInventoryOutputs(org, filters...)
if err != nil {
return cyout.PrintWithOptions(cmd, nil, err, "unable to fetch inventory output", outputsTableOptions)
}

switch len(outputs) {
case 0:
return fmt.Errorf("output %q not found in org %q (try --project / --env / --component to scope the search)", key, org)

case 1:
// Default to the raw value (field printer) unless the user asked for a
// specific format via --output / --jq / CY_OUTPUT / config.
effective, err := cyargs.GetOutput(cmd)
if err != nil {
return err
}
if effective == "table" && !cmd.Flags().Changed("output") && !cmd.Flags().Changed("jq") {
if err := cmd.Flags().Set("output", "value"); err != nil {
return err
}
}
return cyout.PrintWithOptions(cmd, outputs[0], nil, "", outputsTableOptions)

default:
return fmt.Errorf("output %q matched %d results across multiple scopes; narrow with --project / --env / --component", key, len(outputs))
}
}
81 changes: 81 additions & 0 deletions cmd/cycloid/inventory/outputs_list.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
package inventory

import (
"github.com/spf13/cobra"

"github.com/cycloidio/cycloid-cli/cmd/cycloid/common"
"github.com/cycloidio/cycloid-cli/cmd/cycloid/middleware"
"github.com/cycloidio/cycloid-cli/internal/cyargs"
"github.com/cycloidio/cycloid-cli/internal/cyout"
)

func newOutputsListCmd() *cobra.Command {
cmd := &cobra.Command{
Use: "list",
Args: cobra.NoArgs,
Short: "List inventory outputs",
Example: `
# List all outputs for an org
cy --org my-org inventory outputs list

# Filter by project and environment
cy --org my-org inventory outputs list --project my-project --env prod

# Show only pinned outputs as JSON
cy --org my-org inventory outputs list --pinned -o json

# Use a raw LHS filter
cy --org my-org inventory outputs list --filter 'output_type[eq]=string'
`,
RunE: outputsList,
}

addScopingFlags(cmd)
cyargs.AddLHSFilterFlag(cmd)
cmd.Flags().String("key", "", "Filter by output key name")
cmd.Flags().String("type", "", "Filter by output type (e.g. string, number, bool, list, map, object)")
cmd.Flags().Bool("pinned", false, "Show only pinned outputs")
cyout.RegisterModel(cmd, middleware.InventoryOutput{})

return cmd
}

func outputsList(cmd *cobra.Command, args []string) error {
// Step 1: all flags first.
org, err := cyargs.GetOrg(cmd)
if err != nil {
return err
}

filters, err := cyargs.GetLHSFilters(cmd)
if err != nil {
return err
}
filters, err = appendScopingFilters(cmd, filters)
if err != nil {
return err
}

if key, _ := cmd.Flags().GetString("key"); key != "" {
filters = append(filters, middleware.LHSFilter{Attribute: "output_key", Condition: "eq", Value: key})
}
if outputType, _ := cmd.Flags().GetString("type"); outputType != "" {
filters = append(filters, middleware.LHSFilter{Attribute: "output_type", Condition: "eq", Value: outputType})
}
if cmd.Flags().Changed("pinned") {
pinned, _ := cmd.Flags().GetBool("pinned")
val := "false"
if pinned {
val = "true"
}
filters = append(filters, middleware.LHSFilter{Attribute: "output_is_pinned", Condition: "eq", Value: val})
}

// Step 2: API + middleware.
api := common.NewAPI()
m := middleware.NewMiddleware(api)

// Step 3: call + print.
outputs, _, err := m.ListInventoryOutputs(org, filters...)
return cyout.PrintWithOptions(cmd, outputs, err, "unable to list inventory outputs", outputsTableOptions)
}
29 changes: 29 additions & 0 deletions cmd/cycloid/inventory/resources.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
package inventory

import (
"github.com/spf13/cobra"

"github.com/cycloidio/cycloid-cli/printer"
)

// resourcesTableOptions is the shared table view for inventory resources.
var resourcesTableOptions = printer.Options{
Columns: []string{"Name", "Provider", "Type", "Module", "Mode", "Label"},
Identifier: "Name",
}

func newResourcesCmd() *cobra.Command {
cmd := &cobra.Command{
Use: "resources",
Aliases: []string{"resource", "res"},
Short: "Manage inventory resources",
Long: "List Terraform resources tracked by the Cycloid inventory.",
Args: cobra.NoArgs,
}

cmd.AddCommand(
newResourcesListCmd(),
)

return cmd
}
Loading
Loading