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
38 changes: 6 additions & 32 deletions cli/cmd/lint.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,41 +16,15 @@ import (
"github.com/spf13/cobra"
)

func (r *runners) InitLint(parent *cobra.Command) *cobra.Command {
cmd := &cobra.Command{
Use: "lint",
Short: "Lint Helm charts, Preflight specs, and Support Bundle specs",
Long: `Lint Helm charts, Preflight specs, and Support Bundle specs defined in .replicated config file.

This command reads paths from the .replicated config and executes linting locally
on each resource. Use --verbose to also display extracted container images.`,
Example: ` # Lint with default table output
replicated lint

# Output JSON to stdout
replicated lint --output json

# Use in CI/CD pipelines
replicated lint --output json | jq '.summary.overall_success'

# Verbose mode with image extraction
replicated lint --verbose --output json`,
SilenceUsage: true,
}

cmd.Flags().BoolVarP(&r.args.lintVerbose, "verbose", "v", false, "Show detailed output including extracted container images")
cmd.Flags().StringVarP(&r.outputFormat, "output", "o", "table", "The output format to use. One of: json|table")

cmd.RunE = r.runLint

parent.AddCommand(cmd)
return cmd
}
// InitLint is removed - the standalone "replicated lint" command has been removed.
// The linting functionality is now available via "replicated release lint" with the
// release-validation-v2 feature flag. The runLint function below is still used
// internally by the release lint command.

func (r *runners) runLint(cmd *cobra.Command, args []string) error {
// Validate format
// Validate output format
if r.outputFormat != "table" && r.outputFormat != "json" {
return errors.Errorf("invalid format: %s. Supported formats: json, table", r.outputFormat)
return errors.Errorf("invalid output: %s. Supported output formats: json, table", r.outputFormat)
}

// Load .replicated config using tools parser (supports monorepos)
Expand Down
4 changes: 4 additions & 0 deletions cli/cmd/profile.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ Authentication priority:

Use the various subcommands to:
- Add new profiles
- Edit existing profiles
- List all profiles
- Remove profiles
- Set the default profile`,
Expand All @@ -36,6 +37,9 @@ replicated profile add prod --token=your-prod-token
# Add a development profile with custom API origin
replicated profile add dev --token=your-dev-token --api-origin=https://vendor-api-dev.com

# Edit an existing profile's API origin
replicated profile edit dev --api-origin=https://vendor-api-noahecampbell.okteto.repldev.com

# List all profiles
replicated profile ls

Expand Down
17 changes: 14 additions & 3 deletions cli/cmd/profile_add.go
Original file line number Diff line number Diff line change
Expand Up @@ -39,19 +39,29 @@ replicated profile add dev \
parent.AddCommand(cmd)

cmd.Flags().StringVar(&r.args.profileAddToken, "token", "", "API token for this profile (optional, will prompt if not provided)")
cmd.Flags().StringVar(&r.args.profileAddAPIOrigin, "api-origin", "", "API origin (optional, e.g., https://api.replicated.com/vendor)")
cmd.Flags().StringVar(&r.args.profileAddRegistryOrigin, "registry-origin", "", "Registry origin (optional, e.g., registry.replicated.com)")
cmd.Flags().StringVar(&r.args.profileAddAPIOrigin, "api-origin", "", "API origin (optional, e.g., https://api.replicated.com/vendor). Mutually exclusive with --namespace")
cmd.Flags().StringVar(&r.args.profileAddRegistryOrigin, "registry-origin", "", "Registry origin (optional, e.g., registry.replicated.com). Mutually exclusive with --namespace")
cmd.Flags().StringVar(&r.args.profileAddNamespace, "namespace", "", "Okteto namespace for dev environments (e.g., 'noahecampbell'). Auto-generates service URLs. Mutually exclusive with --api-origin and --registry-origin")

return cmd
}

func (r *runners) profileAdd(_ *cobra.Command, args []string) error {
func (r *runners) profileAdd(cmd *cobra.Command, args []string) error {
profileName := args[0]

if profileName == "" {
return errors.New("profile name cannot be empty")
}

// Check for mutually exclusive flags
hasNamespace := cmd.Flags().Changed("namespace")
hasAPIOrigin := cmd.Flags().Changed("api-origin")
hasRegistryOrigin := cmd.Flags().Changed("registry-origin")

if hasNamespace && (hasAPIOrigin || hasRegistryOrigin) {
return errors.New("--namespace cannot be used with --api-origin or --registry-origin. Use --namespace for dev environments, or use explicit origins for custom endpoints")
}

// If token is not provided via flag, prompt for it securely
token := r.args.profileAddToken
if token == "" {
Expand All @@ -66,6 +76,7 @@ func (r *runners) profileAdd(_ *cobra.Command, args []string) error {
APIToken: token,
APIOrigin: r.args.profileAddAPIOrigin,
RegistryOrigin: r.args.profileAddRegistryOrigin,
Namespace: r.args.profileAddNamespace,
}

if err := credentials.AddProfile(profileName, profile); err != nil {
Expand Down
126 changes: 126 additions & 0 deletions cli/cmd/profile_edit.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,126 @@
package cmd

import (
"fmt"

"github.com/pkg/errors"
"github.com/replicatedhq/replicated/pkg/credentials"
"github.com/spf13/cobra"
)

func (r *runners) InitProfileEditCommand(parent *cobra.Command) *cobra.Command {
cmd := &cobra.Command{
Use: "edit [profile-name]",
Short: "Edit an existing authentication profile",
Long: `Edit an existing authentication profile.

You can update the API token, API origin, and/or registry origin for an existing profile.
Only the flags you provide will be updated; other fields will remain unchanged.

The profile will be stored in ~/.replicated/config.yaml with file permissions 600 (owner read/write only).`,
Example: `# Update the token for a profile
replicated profile edit dev --token=new-dev-token

# Update the API origin for a profile
replicated profile edit dev --api-origin=https://vendor-api-noahecampbell.okteto.repldev.com

# Update multiple fields at once
replicated profile edit dev \
--token=new-token \
--api-origin=https://vendor-api-noahecampbell.okteto.repldev.com \
--registry-origin=vendor-registry-v2-noahecampbell.okteto.repldev.com`,
Args: cobra.ExactArgs(1),
SilenceUsage: true,
RunE: r.profileEdit,
}
parent.AddCommand(cmd)

cmd.Flags().StringVar(&r.args.profileEditToken, "token", "", "New API token for this profile (optional)")
cmd.Flags().StringVar(&r.args.profileEditAPIOrigin, "api-origin", "", "New API origin (optional, e.g., https://api.replicated.com/vendor). Mutually exclusive with --namespace")
cmd.Flags().StringVar(&r.args.profileEditRegistryOrigin, "registry-origin", "", "New registry origin (optional, e.g., registry.replicated.com). Mutually exclusive with --namespace")
cmd.Flags().StringVar(&r.args.profileEditNamespace, "namespace", "", "Okteto namespace for dev environments (e.g., 'noahecampbell'). Auto-generates service URLs. Mutually exclusive with --api-origin and --registry-origin")

return cmd
}

func (r *runners) profileEdit(cmd *cobra.Command, args []string) error {
profileName := args[0]

if profileName == "" {
return errors.New("profile name cannot be empty")
}

// Check for mutually exclusive flags
hasNamespace := cmd.Flags().Changed("namespace")
hasAPIOrigin := cmd.Flags().Changed("api-origin")
hasRegistryOrigin := cmd.Flags().Changed("registry-origin")

if hasNamespace && (hasAPIOrigin || hasRegistryOrigin) {
return errors.New("--namespace cannot be used with --api-origin or --registry-origin. Use --namespace for dev environments, or use explicit origins for custom endpoints")
}

// Load existing profile
profile, err := credentials.GetProfile(profileName)
if err != nil {
return errors.Wrapf(err, "failed to load profile '%s'. Use 'replicated profile ls' to see available profiles", profileName)
}

// Track if any changes were made
changed := false

// Update token if provided
if cmd.Flags().Changed("token") {
profile.APIToken = r.args.profileEditToken
changed = true
}

// Update namespace if provided (clears explicit origins)
if cmd.Flags().Changed("namespace") {
profile.Namespace = r.args.profileEditNamespace
// Clear explicit origins when using namespace
profile.APIOrigin = ""
profile.RegistryOrigin = ""
changed = true
}

// Update API origin if provided (clears namespace)
if cmd.Flags().Changed("api-origin") {
profile.APIOrigin = r.args.profileEditAPIOrigin
profile.Namespace = "" // Clear namespace when using explicit origin
changed = true
}

// Update registry origin if provided (clears namespace)
if cmd.Flags().Changed("registry-origin") {
profile.RegistryOrigin = r.args.profileEditRegistryOrigin
profile.Namespace = "" // Clear namespace when using explicit origin
changed = true
}

if !changed {
return errors.New("no changes specified. Use --token, --namespace, --api-origin, or --registry-origin to update the profile")
}

// Save the updated profile (dereference the pointer)
if err := credentials.AddProfile(profileName, *profile); err != nil {
return errors.Wrap(err, "failed to update profile")
}

fmt.Printf("Profile '%s' updated successfully\n", profileName)
if cmd.Flags().Changed("api-origin") {
if profile.APIOrigin != "" {
fmt.Printf(" API Origin: %s\n", profile.APIOrigin)
} else {
fmt.Printf(" API Origin: (removed, using default)\n")
}
}
if cmd.Flags().Changed("registry-origin") {
if profile.RegistryOrigin != "" {
fmt.Printf(" Registry Origin: %s\n", profile.RegistryOrigin)
} else {
fmt.Printf(" Registry Origin: (removed, using default)\n")
}
}

return nil
}
47 changes: 44 additions & 3 deletions cli/cmd/release_lint.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,15 +26,22 @@ var (
func (r *runners) InitReleaseLint(parent *cobra.Command) {
cmd := &cobra.Command{
Use: "lint",
Short: "Lint a directory of KOTS manifests",
Long: "Lint a directory of KOTS manifests",
Short: "Lint a directory of KOTS manifests or local resources",
Long: "Lint a directory of KOTS manifests or local resources. Behavior depends on the release-validation-v2 feature flag.",
SilenceUsage: true,
}
parent.AddCommand(cmd)

cmd.Flags().StringVar(&r.args.lintReleaseYamlDir, "yaml-dir", "", "The directory containing multiple yamls for a Kots release. Cannot be used with the `yaml` flag.")
// Old flags (for remote API lint - when flag=0 or --yaml-dir/--chart provided)
cmd.Flags().StringVar(&r.args.lintReleaseYamlDir, "yaml-dir", "", "The directory containing multiple yamls for a Kots release. Cannot be used with the `yaml` flag.")
cmd.Flags().StringVar(&r.args.lintReleaseChart, "chart", "", "Helm chart to lint from. Cannot be used with the --yaml, --yaml-file, or --yaml-dir flags.")
cmd.Flags().StringVar(&r.args.lintReleaseFailOn, "fail-on", "error", "The minimum severity to cause the command to exit with a non-zero exit code. Supported values are [info, warn, error, none].")

// New flags (for local lint - when flag=1)
cmd.Flags().BoolVarP(&r.args.lintVerbose, "verbose", "v", false, "Show detailed output including extracted container images (local lint only)")
cmd.Flags().StringVarP(&r.args.lintOutputFile, "output-file", "", "", "Write output to file at specified path (local lint only)")

// Output format flag works for both old and new lint
cmd.Flags().StringVarP(&r.outputFormat, "output", "o", "table", "The output format to use. One of: json|table")

cmd.Flags().MarkHidden("chart")
Expand All @@ -46,6 +53,40 @@ func (r *runners) InitReleaseLint(parent *cobra.Command) {
// the hosted version (lint.replicated.com). There are not changes and no auth required or sent.
// This could be vendored in and run locally (respecting the size of the polcy files)
func (r *runners) releaseLint(cmd *cobra.Command, args []string) error {
// If user provided old-style flags (--yaml-dir or --chart), use old remote API behavior
if r.args.lintReleaseYamlDir != "" || r.args.lintReleaseChart != "" {
return r.releaseLintV1(cmd, args)
}

// Check for environment variable override for testing
if envOverride := os.Getenv("REPLICATED_RELEASE_VALIDATION_V2"); envOverride != "" {
if envOverride == "1" {
return r.runLint(cmd, args)
}
return r.releaseLintV1(cmd, args)
}

// Fetch feature flags from vendor-api
features, err := r.platformAPI.GetFeatures(cmd.Context())
if err != nil {
// If feature flag fetch fails, default to old release lint behavior (flag=0)
// This maintains backward compatibility when API is unavailable
return r.releaseLintV1(cmd, args)
}

// Check the release-validation-v2 feature flag
releaseValidationV2 := features.GetFeatureValue("release-validation-v2")
if releaseValidationV2 == "1" {
// New behavior: use local lint functionality
return r.runLint(cmd, args)
}

// Default behavior (flag=0 or not found): use old remote API lint functionality
return r.releaseLintV1(cmd, args)
}

// releaseLintV1 is the original release lint implementation (used when flag=0)
func (r *runners) releaseLintV1(_ *cobra.Command, _ []string) error {
if !r.hasApp() {
return errors.New("no app specified")
}
Expand Down
Loading