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
17 changes: 12 additions & 5 deletions cmd/dynatrace/dashboardCmd.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import (
"os/exec"
"runtime"

ocmutils "github.com/openshift/ocm-container/pkg/utils"
"github.com/spf13/cobra"
cmdutil "k8s.io/kubectl/pkg/cmd/util"
)
Expand All @@ -14,7 +15,8 @@ var (
clusterId string
)

// openBrowser opens the specified URL in the default browser
// openBrowser attempts to open the specified URL in the default system browser.
// Supports Linux (xdg-open), Windows (rundll32), and macOS (open).
func openBrowser(url string) error {
var cmd string
var args []string
Expand Down Expand Up @@ -66,10 +68,15 @@ func newCmdDashboard() *cobra.Command {
dashUrl := hcpCluster.DynatraceURL + "ui/apps/dynatrace.dashboards/dashboard/" + id + "#vfilter__id=" + hcpCluster.externalID
fmt.Printf("\n\nDashboard URL:\n %s\n", dashUrl)

// Open the dashboard in the default browser
fmt.Println("\nOpening dashboard in your browser...")
if err := openBrowser(dashUrl); err != nil {
fmt.Printf("Could not open browser automatically: %s\n", err)
// Only try to open browser if not in a container environment
if !ocmutils.IsRunningInOcmContainer() {
// Open the dashboard in the default browser
fmt.Println("\nOpening dashboard in your browser...")
if err := openBrowser(dashUrl); err != nil {
fmt.Printf("Could not open browser automatically: %s\n", err)
}
} else {
fmt.Println("\nRunning in container mode - open the URL above in your host browser.")
}
},
}
Expand Down
42 changes: 39 additions & 3 deletions cmd/dynatrace/vault.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ import (
"fmt"
"os"
"os/exec"

ocmutils "github.com/openshift/ocm-container/pkg/utils"
)

type response struct {
Expand All @@ -13,6 +15,9 @@ type response struct {
} `json:"data"`
}

// setupVaultToken ensures a valid Vault token exists by checking the current
// token and requesting a new one via OIDC if needed. In container environments,
// it configures authentication to work without browser auto-launch.
func setupVaultToken(vaultAddr string) error {
err := os.Setenv("VAULT_ADDR", vaultAddr)
if err != nil {
Expand All @@ -35,10 +40,41 @@ func setupVaultToken(vaultAddr string) error {
if err = tokenCheckCmd.Run(); err != nil {
fmt.Println("Vault token no longer valid, requesting new token")

loginCmd := exec.Command("vault", "login", "-method=oidc", "-no-print")
loginCmd.Stdout = nil
loginCmd.Stderr = nil
// Check if we're in a container environment (OCM_CONTAINER env var is set)
// If so, skip automatic browser launch and print the URL for manual authentication
loginArgs := []string{"login", "-method=oidc", "-no-print"}
if ocmutils.IsRunningInOcmContainer() {
fmt.Println("\nNOTE: Running in container mode - OIDC authentication requires port forwarding.")
fmt.Println("Ensure port 8250 is exposed in your ocm-container configuration:")
fmt.Println(" Add 'launch-opts: \"-p 8250:8250\"' to ~/.config/ocm-container/ocm-container.yaml")
fmt.Println("Then restart your ocm-container for the change to take effect.")

// In container: skip browser launch and listen on all interfaces (0.0.0.0)
// so the callback can be reached from the host browser via localhost:8250
loginArgs = []string{"login", "-method=oidc", "skip_browser=true", "listenaddress=0.0.0.0"}
}
Comment thread
coderabbitai[bot] marked this conversation as resolved.
loginCmd := exec.Command("vault", loginArgs...)

// Show output when using skip_browser so user can see the authentication URL
if ocmutils.IsRunningInOcmContainer() {
loginCmd.Stdout = os.Stdout
loginCmd.Stderr = os.Stderr
} else {
loginCmd.Stdout = nil
loginCmd.Stderr = nil
}

if err = loginCmd.Run(); err != nil {
if ocmutils.IsRunningInOcmContainer() {
return fmt.Errorf("vault login failed: %v\n\n"+
"If authentication timed out or the callback failed, this is likely because:\n"+
" 1. Port 8250 is not exposed in your ocm-container configuration\n"+
" 2. Your ocm-container was not restarted after adding the port\n\n"+
"To fix:\n"+
" - Add 'launch-opts: \"-p 8250:8250\"' to ~/.config/ocm-container/ocm-container.yaml\n"+
" - Exit and restart your ocm-container\n"+
" - Try the authentication again", err)
}
return fmt.Errorf("error running 'vault login': %v", err)
}

Expand Down
157 changes: 157 additions & 0 deletions cmd/dynatrace/vault_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,157 @@
package dynatrace

import (
"os"
"os/exec"
"testing"

ocmutils "github.com/openshift/ocm-container/pkg/utils"
)

// TestSetupVaultToken_ContainerEnvironment tests that the vault login command
// uses the correct flags when running inside a container (IO_OPENSHIFT_MANAGED_NAME env var set)
func TestSetupVaultToken_ContainerEnvironment(t *testing.T) {
tests := []struct {
name string
containerEnvValue string
expectNoBrowser bool
}{
{
name: "Container environment with IO_OPENSHIFT_MANAGED_NAME=ocm-container",
containerEnvValue: "ocm-container",
expectNoBrowser: true,
},
{
name: "Non-container environment (empty)",
containerEnvValue: "",
expectNoBrowser: false,
},
}

for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
// Set environment using t.Setenv - automatically cleaned up after test
// Always call t.Setenv to ensure clean environment, even for empty case
t.Setenv("IO_OPENSHIFT_MANAGED_NAME", tt.containerEnvValue)

// Build the command args as the code does
loginArgs := []string{"login", "-method=oidc", "-no-print"}
if ocmutils.IsRunningInOcmContainer() {
loginArgs = []string{"login", "-method=oidc", "skip_browser=true", "listenaddress=0.0.0.0"}
}
Comment thread
coderabbitai[bot] marked this conversation as resolved.

// Verify the correct parameter is used
cmd := exec.Command("vault", loginArgs...)
cmdArgs := cmd.Args[1:] // Skip the "vault" binary name

if tt.expectNoBrowser {
// Should have skip_browser=true and listenaddress=0.0.0.0 parameters
hasSkipBrowser := false
hasNoPrint := false
hasListenAddress := false
for _, arg := range cmdArgs {
if arg == "skip_browser=true" {
hasSkipBrowser = true
}
if arg == "-no-print" {
hasNoPrint = true
}
if arg == "listenaddress=0.0.0.0" {
hasListenAddress = true
}
}

if !hasSkipBrowser {
t.Errorf("Expected skip_browser=true parameter in container environment, got args: %v", cmdArgs)
}
if hasNoPrint {
t.Errorf("Did not expect -no-print flag in container environment, got args: %v", cmdArgs)
}
if !hasListenAddress {
t.Errorf("Expected listenaddress=0.0.0.0 parameter in container environment, got args: %v", cmdArgs)
}
} else {
// Should have -no-print flag
hasSkipBrowser := false
hasNoPrint := false
for _, arg := range cmdArgs {
if arg == "skip_browser=true" {
hasSkipBrowser = true
}
if arg == "-no-print" {
hasNoPrint = true
}
}

if hasSkipBrowser {
t.Errorf("Did not expect skip_browser=true parameter in non-container environment, got args: %v", cmdArgs)
}
if !hasNoPrint {
t.Errorf("Expected -no-print flag in non-container environment, got args: %v", cmdArgs)
}
}
})
}
}

// TestSetupVaultToken_OutputRedirection tests that stdout/stderr are properly
// redirected based on the environment
func TestSetupVaultToken_OutputRedirection(t *testing.T) {
tests := []struct {
name string
containerEnvValue string
expectOutput bool
}{
{
name: "Container environment shows output",
containerEnvValue: "ocm-container",
expectOutput: true,
},
{
name: "Non-container environment hides output",
containerEnvValue: "",
expectOutput: false,
},
}

for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
// Set environment using t.Setenv - automatically cleaned up after test
// Always call t.Setenv to ensure clean environment, even for empty case
t.Setenv("IO_OPENSHIFT_MANAGED_NAME", tt.containerEnvValue)

// Build the command as the code does
loginArgs := []string{"login", "-method=oidc", "-no-print"}
if ocmutils.IsRunningInOcmContainer() {
loginArgs = []string{"login", "-method=oidc", "skip_browser=true", "listenaddress=0.0.0.0"}
}
loginCmd := exec.Command("vault", loginArgs...)

// Set output redirection as the code does
if ocmutils.IsRunningInOcmContainer() {
loginCmd.Stdout = os.Stdout
loginCmd.Stderr = os.Stderr
} else {
loginCmd.Stdout = nil
loginCmd.Stderr = nil
}
Comment thread
coderabbitai[bot] marked this conversation as resolved.

// Verify output redirection is correct
if tt.expectOutput {
if loginCmd.Stdout != os.Stdout {
t.Error("Expected Stdout to be os.Stdout in container environment")
}
if loginCmd.Stderr != os.Stderr {
t.Error("Expected Stderr to be os.Stderr in container environment")
}
} else {
if loginCmd.Stdout != nil {
t.Error("Expected Stdout to be nil in non-container environment")
}
if loginCmd.Stderr != nil {
t.Error("Expected Stderr to be nil in non-container environment")
}
}
})
}
}
Loading