Skip to content
Open
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
24 changes: 12 additions & 12 deletions internal/fleetcontrol/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -158,9 +158,9 @@ Create a fleet to group and manage entities of the same type.
**Required Flags:**
- `--name` - Fleet name
- `--managed-entity-type` - Type of entities this fleet will manage
- Allowed values: `HOST`, `KUBERNETESCLUSTER` (case-insensitive)
- Allowed values: `HOST`, `KUBERNETESCLUSTER`
- `--operating-system` - Operating system type (**required for HOST fleets only**)
- Allowed values: `LINUX`, `WINDOWS` (case-insensitive)
- Allowed values: `LINUX`, `WINDOWS`
- **Must be specified** when creating `HOST` fleets to ensure proper agent configuration
- **Must NOT be specified** for `KUBERNETESCLUSTER` fleets (Kubernetes manages its own OS)

Expand Down Expand Up @@ -520,9 +520,9 @@ Create a versioned configuration for fleet agents.
**Required Flags:**
- `--name` - Configuration name
- `--agent-type` - Type of agent this configuration targets
- Allowed values: `NRInfra`, `NRDOT`, `FluentBit`, `NRPrometheusAgent` (case-insensitive)
- Allowed values: `NRInfra`, `NRDOT`, `FluentBit`, `NRPrometheusAgent`
- `--managed-entity-type` - Type of entities this configuration applies to
- Allowed values: `HOST`, `KUBERNETESCLUSTER` (case-insensitive)
- Allowed values: `HOST`, `KUBERNETESCLUSTER`
- **Exactly one of:**
- `--configuration-file-path` - Path to configuration file (JSON/YAML) - **recommended for production**
- `--configuration-content` - Inline configuration content (JSON/YAML) - **for testing/development only**
Expand Down Expand Up @@ -1283,7 +1283,7 @@ newrelic fleetcontrol fleet members add \

### Agent Types

Used in configuration commands. Values are case-insensitive.
Used in configuration commands. Values must match exactly.

**Allowed values:**
- `NRInfra` - New Relic Infrastructure agent
Expand All @@ -1293,37 +1293,37 @@ Used in configuration commands. Values are case-insensitive.

**Example:**
```bash
--agent-type "nrinfra" # Case-insensitive, works fine
--agent-type "NRInfra" # Must use exact casing
```

---

### Managed Entity Types

Used in fleet and configuration commands. Values are case-insensitive.
Used in fleet and configuration commands. Values must match exactly.

**Allowed values:**
- `HOST` - Physical or virtual hosts
- `KUBERNETESCLUSTER` - Kubernetes clusters

**Example:**
```bash
--managed-entity-type "host" # Case-insensitive, works fine
--managed-entity-type "HOST" # Must use exact casing
```

---

### Configuration Modes

Used in configuration get command. Values are case-insensitive.
Used in configuration get command. Values must match exactly.

**Allowed values:**
- `ConfigEntity` (default) - Query by configuration entity ID
- `ConfigVersionEntity` - Query by version entity ID

**Example:**
```bash
--mode "configversionentity" # Case-insensitive, works fine
--mode "ConfigVersionEntity" # Must use exact casing
```

---
Expand Down Expand Up @@ -1447,7 +1447,7 @@ The syntax using separate flags can be preferred in the case of single-agent dep
| **"Authentication failed"** | Verify `NEW_RELIC_API_KEY` is set correctly. Ensure it's a User API key, not Browser or License key. |
| **"Account not found"** | Check `NEW_RELIC_ACCOUNT_ID` is correct. Find it in New Relic UI or URL. |
| **"required flag not set"** | Ensure flag syntax is correct: `--flag-name value` or `--flag-name=value` (not `flag-name=value`) |
| **"invalid value for flag"** | Check validation rules above. Values may need to match allowed values (case-insensitive) |
| **"invalid value for flag"** | Check validation rules above. Values must match allowed values exactly (case-sensitive) |
| **"mutually exclusive flags"** | Only one of the mutually exclusive flags should be provided (e.g., `--fleet-id` OR `--fleet-ids`, not both). For deployments, use either `--agent` or all three legacy flags, not a mix. |
| **"agent version '*' not supported for HOST fleets"** | Wildcard version (`"*"`) is only allowed for KUBERNETESCLUSTER fleets. Use an explicit version (e.g., `"1.70.0"`) for HOST fleets. |
| **"--configuration-version-ids is required"** | When using legacy deployment syntax, you must provide all three flags: `--agent-type`, `--agent-version`, AND `--configuration-version-ids`. |
Expand Down Expand Up @@ -1476,7 +1476,7 @@ flag-name=value # Missing -- prefix

When you see "invalid value for flag", check:
1. Value is in the allowed values list (see Validation Rules Reference)
2. Spelling is correct (validation is case-insensitive but value must be in the list)
2. Spelling and casing are correct (validation is case-sensitive - values must match exactly)
3. No extra spaces or quotes

**Example validation error:**
Expand Down
2 changes: 0 additions & 2 deletions internal/fleetcontrol/configs/fleet_configuration_create.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -28,14 +28,12 @@ flags:
description: the agent type (e.g., NRInfra, NRDOT)
validation:
allowed_values: ["NRInfra","NRDOT", "FluentBit", "NRPrometheusAgent"]
case_insensitive: true
- name: managed-entity-type
type: string
required: true
description: the type of entities this configuration manages
validation:
allowed_values: ["HOST", "KUBERNETESCLUSTER"]
case_insensitive: true
- name: organization-id
type: string
description: the organization ID; if not provided, will be derived from current organization
Expand Down
1 change: 0 additions & 1 deletion internal/fleetcontrol/configs/fleet_configuration_get.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,6 @@ flags:
description: the entity type to retrieve (ConfigEntity for configuration, ConfigVersionEntity for specific version entity)
validation:
allowed_values: ["ConfigEntity", "ConfigVersionEntity"]
case_insensitive: true
- name: version
type: int
default: -1
Expand Down
2 changes: 2 additions & 0 deletions internal/fleetcontrol/configs/fleet_deployment_create.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,8 @@ flags:
- name: agent-type
type: string
description: "(legacy) the agent type for single agent deployment (e.g., NRInfra, NRDOT). Mutually exclusive with --agent"
validation:
allowed_values: ["NRInfra", "NRDOT", "FluentBit", "NRPrometheusAgent"]
- name: agent-version
type: string
description: "(legacy) the agent version for single agent deployment (e.g., 1.70.0, 2.0.0). Mutually exclusive with --agent"
Expand Down
2 changes: 0 additions & 2 deletions internal/fleetcontrol/configs/fleet_management_create.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,6 @@ flags:
description: the type of entities this fleet manages
validation:
allowed_values: ["HOST", "KUBERNETESCLUSTER"]
case_insensitive: true
- name: description
type: string
description: a description of the fleet
Expand All @@ -42,7 +41,6 @@ flags:
description: the operating system type for HOST fleets (LINUX or WINDOWS); not applicable for KUBERNETESCLUSTER fleets
validation:
allowed_values: ["LINUX", "WINDOWS"]
case_insensitive: true
- name: tags
type: stringSlice
description: tags in format key:value1,value2 (can be specified multiple times)
5 changes: 4 additions & 1 deletion internal/fleetcontrol/fleet_configuration_create.go
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,10 @@ func handleFleetCreateConfiguration(cmd *cobra.Command, args []string, flags *Fl
configBodyBytes := []byte(configBody)

// Get organization ID (provided or fetched from API)
orgID := GetOrganizationID(f.OrganizationID)
orgID, err := GetOrganizationID(f.OrganizationID)
if err != nil {
return PrintError(fmt.Errorf("failed to determine organization ID: %w", err))
}

// Build custom headers required by the API
// These headers specify the entity name, agent type, and managed entity type
Expand Down
7 changes: 5 additions & 2 deletions internal/fleetcontrol/fleet_configuration_delete.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,10 +30,13 @@ func handleFleetDeleteConfiguration(cmd *cobra.Command, args []string, flags *Fl
f := flags.DeleteConfiguration()

// Get organization ID (provided or fetched from API)
orgID := GetOrganizationID(f.OrganizationID)
orgID, err := GetOrganizationID(f.OrganizationID)
if err != nil {
return PrintError(fmt.Errorf("failed to determine organization ID: %w", err))
}

// Call New Relic API to delete the configuration
_, err := client.NRClient.FleetControl.FleetControlDeleteConfiguration(
_, err = client.NRClient.FleetControl.FleetControlDeleteConfiguration(
f.ConfigurationID,
orgID,
)
Expand Down
5 changes: 4 additions & 1 deletion internal/fleetcontrol/fleet_configuration_get.go
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,10 @@ func handleFleetGetConfiguration(cmd *cobra.Command, args []string, flags *FlagV
f := flags.GetConfiguration()

// Get organization ID (provided or fetched from API)
orgID := GetOrganizationID(f.OrganizationID)
orgID, err := GetOrganizationID(f.OrganizationID)
if err != nil {
return PrintError(fmt.Errorf("failed to determine organization ID: %w", err))
}

// Map validated mode to client library type
// YAML validation has already confirmed this value is in allowed_values
Expand Down
5 changes: 4 additions & 1 deletion internal/fleetcontrol/fleet_configuration_version_add.go
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,10 @@ func handleFleetAddVersion(cmd *cobra.Command, args []string, flags *FlagValues)
configBodyBytes := []byte(configBody)

// Get organization ID (provided or fetched from API)
orgID := GetOrganizationID(f.OrganizationID)
orgID, err := GetOrganizationID(f.OrganizationID)
if err != nil {
return PrintError(fmt.Errorf("failed to determine organization ID: %w", err))
}

// Build custom headers with the configuration GUID
// This tells the API to add a version to the existing configuration
Expand Down
7 changes: 5 additions & 2 deletions internal/fleetcontrol/fleet_configuration_version_delete.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,10 +30,13 @@ func handleFleetDeleteVersion(cmd *cobra.Command, args []string, flags *FlagValu
f := flags.DeleteVersion()

// Get organization ID (provided or fetched from API)
orgID := GetOrganizationID(f.OrganizationID)
orgID, err := GetOrganizationID(f.OrganizationID)
if err != nil {
return PrintError(fmt.Errorf("failed to determine organization ID: %w", err))
}

// Call New Relic API to delete the version
err := client.NRClient.FleetControl.FleetControlDeleteConfigurationVersion(
err = client.NRClient.FleetControl.FleetControlDeleteConfigurationVersion(
f.VersionID,
orgID,
)
Expand Down
5 changes: 4 additions & 1 deletion internal/fleetcontrol/fleet_configuration_version_list.go
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,10 @@ func handleFleetGetConfigurationVersions(cmd *cobra.Command, args []string, flag
f := flags.GetVersions()

// Get organization ID (provided or fetched from API)
orgID := GetOrganizationID(f.OrganizationID)
orgID, err := GetOrganizationID(f.OrganizationID)
if err != nil {
return PrintError(fmt.Errorf("failed to determine organization ID: %w", err))
}

// Call New Relic API to get all configuration versions
result, err := client.NRClient.FleetControl.FleetControlGetConfigurationVersions(
Expand Down
3 changes: 3 additions & 0 deletions internal/fleetcontrol/fleet_deployment_create.go
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,9 @@ func handleFleetCreateDeployment(cmd *cobra.Command, args []string, flags *FlagV
return PrintError(fmt.Errorf("--configuration-version-ids is required when not using --agent flag"))
}

// Note: agent-type values are validated by YAML framework
// See configs/fleet_deployment_create.yaml allowed_values

// Convert configuration version IDs to the required format for agent input
var configVersionList []fleetcontrol.FleetControlConfigurationVersionListInput
for _, versionID := range f.ConfigurationVersionIDs {
Expand Down
6 changes: 3 additions & 3 deletions internal/fleetcontrol/fleet_management_create.go
Original file line number Diff line number Diff line change
Expand Up @@ -57,9 +57,9 @@ func handleFleetCreate(cmd *cobra.Command, args []string, flags *FlagValues) err

// Get organization ID (either from flag or fetch from API)
// This avoids an unnecessary API call if the user already knows their org ID
orgID := GetOrganizationID(f.OrganizationID)
if orgID == "" {
return PrintError(fmt.Errorf("failed to determine organization ID"))
orgID, err := GetOrganizationID(f.OrganizationID)
if err != nil {
return PrintError(fmt.Errorf("failed to determine organization ID: %w", err))
}

// Parse tags from "key:value1,value2" format to API format
Expand Down
44 changes: 27 additions & 17 deletions internal/fleetcontrol/helpers.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,6 @@ import (
"strings"
"time"

log "github.com/sirupsen/logrus"

"github.com/newrelic/newrelic-cli/internal/client"
"github.com/newrelic/newrelic-client-go/v2/pkg/fleetcontrol"
)
Expand All @@ -24,17 +22,17 @@ import (
//
// Returns:
// - The organization ID (either provided or fetched)
func GetOrganizationID(providedOrgID string) string {
// - Error if the API call fails when fetching the organization ID
func GetOrganizationID(providedOrgID string) (string, error) {
if providedOrgID != "" {
return providedOrgID
return providedOrgID, nil
}

org, err := client.NRClient.Organization.GetOrganization()
if err != nil {
log.Warnf("Failed to get organization: %v", err)
return ""
return "", fmt.Errorf("failed to get organization from API: %w", err)
}
return org.ID
return org.ID, nil
}

// ParseTags converts tag strings in the format "key:value1,value2" into FleetControlTagInput structs.
Expand Down Expand Up @@ -128,6 +126,18 @@ func ParseAgentSpec(agentSpec string) (fleetcontrol.FleetControlAgentInput, erro
if agentType == "" {
return fleetcontrol.FleetControlAgentInput{}, fmt.Errorf("agent type cannot be empty")
}

// Validate agent type against allowed values
validAgentTypes := map[string]bool{
"NRInfra": true,
"NRDOT": true,
"FluentBit": true,
"NRPrometheusAgent": true,
}
if !validAgentTypes[agentType] {
return fleetcontrol.FleetControlAgentInput{}, fmt.Errorf("invalid agent type '%s': must be one of [NRInfra, NRDOT, FluentBit, NRPrometheusAgent]", agentType)
}

if version == "" {
return fleetcontrol.FleetControlAgentInput{}, fmt.Errorf("agent version cannot be empty")
}
Expand Down Expand Up @@ -222,15 +232,15 @@ func ValidateAgentVersionsForFleet(fleetID string, agents []fleetcontrol.FleetCo
func MapManagedEntityType(typeStr string) (fleetcontrol.FleetControlManagedEntityType, error) {
// Note: YAML validation has already confirmed this value is in allowed_values
// This mapping must match the YAML allowed_values exactly
switch strings.ToUpper(typeStr) {
switch typeStr {
case "HOST":
return fleetcontrol.FleetControlManagedEntityTypeTypes.HOST, nil
case "KUBERNETESCLUSTER":
return fleetcontrol.FleetControlManagedEntityTypeTypes.KUBERNETESCLUSTER, nil
default:
// This should never happen if YAML validation is working correctly
return fleetcontrol.FleetControlManagedEntityType(""), fmt.Errorf(
"unrecognized managed entity type '%s' - YAML validation may have failed", typeStr)
"unrecognized managed entity type '%s' - expected exact match for 'HOST' or 'KUBERNETESCLUSTER'", typeStr)
}
}

Expand All @@ -246,15 +256,15 @@ func MapManagedEntityType(typeStr string) (fleetcontrol.FleetControlManagedEntit
func MapScopeType(typeStr string) (fleetcontrol.FleetControlEntityScope, error) {
// Note: YAML validation has already confirmed this value is in allowed_values
// This mapping must match the YAML allowed_values exactly
switch strings.ToUpper(typeStr) {
switch typeStr {
case "ACCOUNT":
return fleetcontrol.FleetControlEntityScopeTypes.ACCOUNT, nil
case "ORGANIZATION":
return fleetcontrol.FleetControlEntityScopeTypes.ORGANIZATION, nil
default:
// This should never happen if YAML validation is working correctly
return fleetcontrol.FleetControlEntityScope(""), fmt.Errorf(
"unrecognized scope type '%s' - YAML validation may have failed", typeStr)
"unrecognized scope type '%s' - expected exact match for 'ACCOUNT' or 'ORGANIZATION'", typeStr)
}
}

Expand All @@ -270,15 +280,15 @@ func MapScopeType(typeStr string) (fleetcontrol.FleetControlEntityScope, error)
func MapConfigurationMode(modeStr string) (fleetcontrol.GetConfigurationMode, error) {
// Note: YAML validation has already confirmed this value is in allowed_values
// This mapping must match the YAML allowed_values exactly
switch strings.ToLower(modeStr) {
case "configentity", "":
switch modeStr {
case "ConfigEntity", "":
return fleetcontrol.GetConfigurationModeTypes.ConfigEntity, nil
case "configversionentity":
case "ConfigVersionEntity":
return fleetcontrol.GetConfigurationModeTypes.ConfigVersionEntity, nil
default:
// This should never happen if YAML validation is working correctly
return fleetcontrol.GetConfigurationMode(""), fmt.Errorf(
"unrecognized configuration mode '%s' - YAML validation may have failed", modeStr)
"unrecognized configuration mode '%s' - expected exact match for 'ConfigEntity' or 'ConfigVersionEntity'", modeStr)
}
}

Expand All @@ -294,15 +304,15 @@ func MapConfigurationMode(modeStr string) (fleetcontrol.GetConfigurationMode, er
func MapOperatingSystemType(osStr string) (fleetcontrol.FleetControlOperatingSystemType, error) {
// Note: YAML validation has already confirmed this value is in allowed_values
// This mapping must match the YAML allowed_values exactly
switch strings.ToUpper(osStr) {
switch osStr {
case "LINUX":
return fleetcontrol.FleetControlOperatingSystemTypeTypes.LINUX, nil
case "WINDOWS":
return fleetcontrol.FleetControlOperatingSystemTypeTypes.WINDOWS, nil
default:
// This should never happen if YAML validation is working correctly
return fleetcontrol.FleetControlOperatingSystemType(""), fmt.Errorf(
"unrecognized operating system type '%s' - YAML validation may have failed", osStr)
"unrecognized operating system type '%s' - expected exact match for 'LINUX' or 'WINDOWS'", osStr)
}
}

Expand Down
Loading