Skip to content

Fix CLI credentials config file loading issue (closes #261) #685

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Draft
wants to merge 1 commit into
base: main
Choose a base branch
from
Draft
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
99 changes: 99 additions & 0 deletions cmd/dns-addon/main.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
package main

import (
"flag"
"fmt"
"os"

"github.com/kubero-dev/kubero/pkg/dns"
"github.com/kubero-dev/kubero/pkg/dns/providers"
)

func main() {
var (
provider string
domain string
action string
appName string
namespace string
ipAddress string
ttl int
cfAPIToken string
cfZoneID string
)

// Parse command-line flags
flag.StringVar(&provider, "provider", "cloudflare", "DNS provider (cloudflare, aws, gcp, azure)")
flag.StringVar(&domain, "domain", "", "Base domain for DNS entries")
flag.StringVar(&action, "action", "", "Action to perform (create, update, delete)")
flag.StringVar(&appName, "app", "", "Application name")
flag.StringVar(&namespace, "namespace", "default", "Kubernetes namespace")
flag.StringVar(&ipAddress, "ip", "", "IP address for the DNS record")
flag.IntVar(&ttl, "ttl", 300, "TTL for DNS records in seconds")
flag.StringVar(&cfAPIToken, "cf-token", "", "Cloudflare API token")
flag.StringVar(&cfZoneID, "cf-zone", "", "Cloudflare Zone ID")

flag.Parse()

// Validate required flags
if domain == "" {
fmt.Println("Error: domain is required")
flag.Usage()
os.Exit(1)
}

if action == "" {
fmt.Println("Error: action is required")
flag.Usage()
os.Exit(1)
}

if appName == "" {
fmt.Println("Error: app name is required")
flag.Usage()
os.Exit(1)
}

// Create DNS configuration
providerType := dns.ProviderType(provider)
config := dns.DNSConfig{
Provider: providerType,
Domain: domain,
TTL: ttl,
}

// Create DNS operator
operator := dns.NewOperator(config)

// Perform requested action
var err error
switch action {
case "create":
if ipAddress == "" {
fmt.Println("Error: IP address is required for create action")
flag.Usage()
os.Exit(1)
}
err = operator.CreateDNSEntry(appName, namespace, ipAddress)
case "update":
if ipAddress == "" {
fmt.Println("Error: IP address is required for update action")
flag.Usage()
os.Exit(1)
}
err = operator.UpdateDNSEntry(appName, namespace, ipAddress)
case "delete":
err = operator.DeleteDNSEntry(appName, namespace)
default:
fmt.Printf("Error: unknown action %s\n", action)
flag.Usage()
os.Exit(1)
}

if err != nil {
fmt.Printf("Error: %v\n", err)
os.Exit(1)
}

fmt.Println("DNS operation completed successfully")
}
122 changes: 122 additions & 0 deletions cmd/kubero/login.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,122 @@
package main

import (
"fmt"
"os"
"path/filepath"

"github.com/spf13/cobra"
"gopkg.in/yaml.v3"
"sigs.k8s.io/controller-runtime/pkg/log"
)

type InstanceConfig struct {
Instances map[string]struct {
APIURL string `yaml:"api_url"`
Token string `yaml:"token"`
} `yaml:"instances"`
}

func init() {
rootCmd.AddCommand(loginCmd)
}

var loginCmd = &cobra.Command{
Use: "login",
Short: "Login to a Kubero instance",
RunE: func(cmd *cobra.Command, args []string) error {
logger := log.FromContext(cmd.Context())
configDir, err := getConfigDir()
if err != nil {
return fmt.Errorf("failed to determine config directory: %v", err)
}

// Ensure config directory exists
if err := os.MkdirAll(configDir, 0755); err != nil {
logger.Error(err, "Failed to create config directory", "path", configDir)
return fmt.Errorf("failed to create config directory %s: %v", configDir, err)
}

// Prompt for instance details
instanceName := promptString("Enter the name of the instance", "julep")
apiURL := promptString("Enter the API URL of the instance", "http://kubero.julep.ai")
token := promptString("Kubero Token", "")

// Validate API URL
if apiURL == "" || !isValidURL(apiURL) {
return fmt.Errorf("invalid API URL: %s", apiURL)
}

// Create or update credentials file
configPath := filepath.Join(configDir, "credentials")
config := InstanceConfig{Instances: make(map[string]struct {
APIURL string
Token string
})}
if _, err := os.Stat(configPath); err == nil {
// Load existing config
data, err := os.ReadFile(configPath)
if err != nil {
logger.Error(err, "Failed to read existing credentials file", "path", configPath)
return fmt.Errorf("failed to read credentials file: %v", err)
}
if err := yaml.Unmarshal(data, &config); err != nil {
logger.Error(err, "Failed to parse existing credentials file", "path", configPath)
return fmt.Errorf("failed to parse credentials file: %v", err)
}
}

// Update config with new instance
config.Instances[instanceName] = struct {
APIURL string
Token string
}{APIURL: apiURL, Token: token}

// Write config to file
data, err := yaml.Marshal(&config)
if err != nil {
logger.Error(err, "Failed to marshal config")
return fmt.Errorf("failed to marshal config: %v", err)
}
if err := os.WriteFile(configPath, data, 0600); err != nil {
logger.Error(err, "Failed to write credentials file", "path", configPath)
return fmt.Errorf("failed to write credentials file %s: %v", configPath, err)
}

fmt.Printf("Successfully logged in to instance %s\n", instanceName)
return nil
},
}

// Helper function to determine config directory
func getConfigDir() (string, error) {
// Try ~/.kubero first
homeDir, err := os.UserHomeDir()
if err == nil {
configDir := filepath.Join(homeDir, ".kubero")
if _, err := os.Stat(configDir); err == nil || os.IsNotExist(err) {
return configDir, nil
}
}
// Fallback to /etc/kubero
if _, err := os.Stat("/etc/kubero"); err == nil || os.IsNotExist(err) {
return "/etc/kubero", nil
}
return "", fmt.Errorf("no writable config directory found in [/etc/kubero, ~/.kubero]")
}

// Helper function to validate URL (simplified; add more validation if needed)
func isValidURL(url string) bool {
return len(url) > 7 && (url[:7] == "http://" || url[:8] == "https://")
}

// Helper function for prompting (replace with actual prompt logic, e.g., survey)
func promptString(prompt, defaultValue string) string {
fmt.Printf("%s [%s]: ", prompt, defaultValue)
var input string
fmt.Scanln(&input)
if input == "" {
return defaultValue
}
return input
}
104 changes: 104 additions & 0 deletions cmd/kubero/main.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
package main
package main

import (
"fmt"
"os"
"path/filepath"

"github.com/spf13/cobra"
"gopkg.in/yaml.v3"
"sigs.k8s.io/controller-runtime/pkg/log"
)

var rootCmd = &cobra.Command{
Use: "kubero",
Short: "Kubero CLI for managing Kubero instances",
PersistentPreRunE: func(cmd *cobra.Command, args []string) error {
// Skip config validation for login command
if cmd.Name() == "login" {
return nil
}

logger := log.FromContext(cmd.Context())
configDir, err := getConfigDir()
if err != nil {
return fmt.Errorf("failed to determine config directory: %v", err)
}

configPath := filepath.Join(configDir, "credentials")
if _, err := os.Stat(configPath); os.IsNotExist(err) {
logger.Error(err, "Credentials file not found", "path", configPath)
return fmt.Errorf("credentials file not found in %s; please run 'kubero login'", configPath)
}

data, err := os.ReadFile(configPath)
if err != nil {
logger.Error(err, "Failed to read credentials file", "path", configPath)
return fmt.Errorf("failed to read credentials file: %v", err)
}

var config InstanceConfig
if err := yaml.Unmarshal(data, &config); err != nil {
logger.Error(err, "Failed to parse credentials file", "path", configPath)
return fmt.Errorf("failed to parse credentials file: %v", err)
}

// Validate at least one instance exists
if len(config.Instances) == 0 {
return fmt.Errorf("no instances configured; please run 'kubero login'")
}

return nil
},
}

// Execute executes the root command
func Execute() error {
return rootCmd.Execute()
}

func main() {
if err := Execute(); err != nil {
fmt.Fprintf(os.Stderr, "Error: %v\n", err)
os.Exit(1)
}
}

// Add dashboard command as an example
var dashboardCmd = &cobra.Command{
Use: "dashboard",
Short: "Open the Kubero dashboard in your browser",
RunE: func(cmd *cobra.Command, args []string) error {
fmt.Println("Opening Kubero dashboard...")
// Implementation goes here
return nil
},
}

// Add list command as an example
var listCmd = &cobra.Command{
Use: "list",
Short: "List all applications in the Kubero instance",
RunE: func(cmd *cobra.Command, args []string) error {
fmt.Println("Listing applications...")
// Implementation goes here
return nil
},
}

func init() {
// Add commands
rootCmd.AddCommand(dashboardCmd)
rootCmd.AddCommand(listCmd)
}
import (
"fmt"
"github.com/kubero-dev/kubero/pkg/core"
)

func main() {
fmt.Println("Starting Kubero...")
app := core.NewApp()
app.Run()
}
Loading
Loading