mirror of
https://github.com/netbirdio/netbird.git
synced 2025-06-24 11:41:35 +02:00
feat: add profile management commands for listing, adding, removing, and selecting profiles
This commit is contained in:
parent
790484bda2
commit
4b68a2a665
147
client/cmd/profile.go
Normal file
147
client/cmd/profile.go
Normal file
@ -0,0 +1,147 @@
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"github.com/spf13/cobra"
|
||||
|
||||
"github.com/netbirdio/netbird/client/internal/profilemanager"
|
||||
"github.com/netbirdio/netbird/util"
|
||||
)
|
||||
|
||||
var profileCmd = &cobra.Command{
|
||||
Use: "profile",
|
||||
Short: "manage Netbird profiles",
|
||||
Long: `Manage Netbird profiles, allowing you to list, switch, and remove profiles.`,
|
||||
}
|
||||
|
||||
var profileListCmd = &cobra.Command{
|
||||
Use: "list",
|
||||
Short: "list all profiles",
|
||||
Long: `List all available profiles in the Netbird client.`,
|
||||
RunE: listProfilesFunc,
|
||||
}
|
||||
|
||||
var profileAddCmd = &cobra.Command{
|
||||
Use: "add <profile_name>",
|
||||
Short: "add a new profile",
|
||||
Long: `Add a new profile to the Netbird client. The profile name must be unique.`,
|
||||
Args: cobra.ExactArgs(1),
|
||||
RunE: addProfileFunc,
|
||||
}
|
||||
|
||||
var profileRemoveCmd = &cobra.Command{
|
||||
Use: "remove <profile_name>",
|
||||
Short: "remove a profile",
|
||||
Long: `Remove a profile from the Netbird client. The profile must not be active.`,
|
||||
Args: cobra.ExactArgs(1),
|
||||
RunE: removeProfileFunc,
|
||||
}
|
||||
|
||||
var profileSelectCmd = &cobra.Command{
|
||||
Use: "select <profile_name>",
|
||||
Short: "select a profile",
|
||||
Long: `Select a profile to be the active profile in the Netbird client. The profile must exist.`,
|
||||
Args: cobra.ExactArgs(1),
|
||||
RunE: selectProfileFunc,
|
||||
}
|
||||
|
||||
func listProfilesFunc(cmd *cobra.Command, _ []string) error {
|
||||
SetFlagsFromEnvVars(rootCmd)
|
||||
SetFlagsFromEnvVars(cmd)
|
||||
|
||||
cmd.SetOut(cmd.OutOrStdout())
|
||||
|
||||
err := util.InitLog(logLevel, "console")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
profileManager := profilemanager.NewProfileManager()
|
||||
profiles, err := profileManager.ListProfiles()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// list profiles, add a tick if the profile is active
|
||||
cmd.Println("Found", len(profiles), "profiles:")
|
||||
for _, profile := range profiles {
|
||||
// use a cross to indicate the passive profiles
|
||||
activeMarker := "✗"
|
||||
if profile.IsActive {
|
||||
activeMarker = "✓"
|
||||
}
|
||||
cmd.Println(activeMarker, profile.Name)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func addProfileFunc(cmd *cobra.Command, args []string) error {
|
||||
SetFlagsFromEnvVars(rootCmd)
|
||||
SetFlagsFromEnvVars(cmd)
|
||||
|
||||
cmd.SetOut(cmd.OutOrStdout())
|
||||
|
||||
err := util.InitLog(logLevel, "console")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
profileManager := profilemanager.NewProfileManager()
|
||||
profileName := args[0]
|
||||
|
||||
err = profileManager.AddProfile(profilemanager.Profile{
|
||||
Name: profileName,
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
cmd.Println("Profile added successfully:", profileName)
|
||||
return nil
|
||||
}
|
||||
|
||||
func removeProfileFunc(cmd *cobra.Command, args []string) error {
|
||||
SetFlagsFromEnvVars(rootCmd)
|
||||
SetFlagsFromEnvVars(cmd)
|
||||
|
||||
cmd.SetOut(cmd.OutOrStdout())
|
||||
|
||||
err := util.InitLog(logLevel, "console")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
profileManager := profilemanager.NewProfileManager()
|
||||
profileName := args[0]
|
||||
|
||||
err = profileManager.RemoveProfile(profileName)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
cmd.Println("Profile removed successfully:", profileName)
|
||||
return nil
|
||||
}
|
||||
|
||||
func selectProfileFunc(cmd *cobra.Command, args []string) error {
|
||||
SetFlagsFromEnvVars(rootCmd)
|
||||
SetFlagsFromEnvVars(cmd)
|
||||
|
||||
cmd.SetOut(cmd.OutOrStdout())
|
||||
|
||||
err := util.InitLog(logLevel, "console")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
profileManager := profilemanager.NewProfileManager()
|
||||
profileName := args[0]
|
||||
|
||||
err = profileManager.SwitchProfile(profileName)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
cmd.Println("Profile switched successfully to:", profileName)
|
||||
return nil
|
||||
}
|
@ -152,6 +152,7 @@ func init() {
|
||||
rootCmd.AddCommand(networksCMD)
|
||||
rootCmd.AddCommand(forwardingRulesCmd)
|
||||
rootCmd.AddCommand(debugCmd)
|
||||
rootCmd.AddCommand(profileCmd)
|
||||
|
||||
serviceCmd.AddCommand(runCmd, startCmd, stopCmd, restartCmd) // service control commands are subcommands of service
|
||||
serviceCmd.AddCommand(installCmd, uninstallCmd) // service installer commands are subcommands of service
|
||||
@ -167,6 +168,12 @@ func init() {
|
||||
debugCmd.AddCommand(forCmd)
|
||||
debugCmd.AddCommand(persistenceCmd)
|
||||
|
||||
// profile commands
|
||||
profileCmd.AddCommand(profileListCmd)
|
||||
profileCmd.AddCommand(profileAddCmd)
|
||||
profileCmd.AddCommand(profileRemoveCmd)
|
||||
profileCmd.AddCommand(profileSelectCmd)
|
||||
|
||||
upCmd.PersistentFlags().StringSliceVar(&natExternalIPs, externalIPMapFlag, nil,
|
||||
`Sets external IPs maps between local addresses and interfaces.`+
|
||||
`You can specify a comma-separated list with a single IP and IP/IP or IP/Interface Name. `+
|
||||
|
@ -4,13 +4,20 @@ import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"sync"
|
||||
|
||||
log "github.com/sirupsen/logrus"
|
||||
|
||||
"github.com/netbirdio/netbird/util"
|
||||
)
|
||||
|
||||
const (
|
||||
defaultProfileName = "default"
|
||||
)
|
||||
|
||||
type Profile struct {
|
||||
Name string
|
||||
Email string
|
||||
@ -18,8 +25,7 @@ type Profile struct {
|
||||
}
|
||||
|
||||
type ProfileManager struct {
|
||||
mu sync.Mutex
|
||||
activeProfile *Profile
|
||||
mu sync.Mutex
|
||||
}
|
||||
|
||||
func NewProfileManager() *ProfileManager {
|
||||
@ -67,7 +73,7 @@ func (pm *ProfileManager) RemoveProfile(profileName string) error {
|
||||
return fmt.Errorf("failed to get active profile: %w", err)
|
||||
}
|
||||
|
||||
if activeProf.Name == profileName {
|
||||
if activeProf != nil && activeProf.Name == profileName {
|
||||
return fmt.Errorf("cannot remove active profile: %s", profileName)
|
||||
}
|
||||
|
||||
@ -82,18 +88,8 @@ func (pm *ProfileManager) GetActiveProfile() (*Profile, error) {
|
||||
pm.mu.Lock()
|
||||
defer pm.mu.Unlock()
|
||||
|
||||
if pm.activeProfile == nil {
|
||||
return nil, ErrNoActiveProfile
|
||||
}
|
||||
|
||||
return pm.activeProfile, nil
|
||||
}
|
||||
|
||||
func (pm *ProfileManager) SetActiveProfile(profileName string) {
|
||||
pm.mu.Lock()
|
||||
defer pm.mu.Unlock()
|
||||
|
||||
pm.activeProfile = &Profile{Name: profileName}
|
||||
prof := pm.getActiveProfileState()
|
||||
return &Profile{Name: prof}, nil
|
||||
}
|
||||
|
||||
func (pm *ProfileManager) ListProfiles() ([]Profile, error) {
|
||||
@ -115,7 +111,7 @@ func (pm *ProfileManager) ListProfiles() ([]Profile, error) {
|
||||
|
||||
var profiles []Profile
|
||||
// add default profile always
|
||||
profiles = append(profiles, Profile{Name: "default", IsActive: activeProfName == "default"})
|
||||
profiles = append(profiles, Profile{Name: "default", IsActive: activeProfName == "" || activeProfName == "default"})
|
||||
for _, file := range files {
|
||||
profileName := strings.TrimSuffix(filepath.Base(file), ".json")
|
||||
var isActive bool
|
||||
@ -128,24 +124,13 @@ func (pm *ProfileManager) ListProfiles() ([]Profile, error) {
|
||||
return profiles, nil
|
||||
}
|
||||
|
||||
// TODO(hakan): implement
|
||||
func (pm *ProfileManager) SwitchProfile(profileName string) error {
|
||||
pm.mu.Lock()
|
||||
defer pm.mu.Unlock()
|
||||
|
||||
// Check if the profile exists
|
||||
configDir, err := getConfigDir()
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to get config directory: %w", err)
|
||||
if err := pm.setActiveProfileState(profileName); err != nil {
|
||||
return fmt.Errorf("failed to switch profile: %w", err)
|
||||
}
|
||||
|
||||
profPath := filepath.Join(configDir, profileName+".json")
|
||||
if !fileExists(profPath) {
|
||||
return ErrProfileNotFound
|
||||
}
|
||||
// TODO(hakan): implement the logic to switch the profile in the application
|
||||
|
||||
// Set the active profile
|
||||
pm.activeProfile = &Profile{Name: profileName}
|
||||
return nil
|
||||
}
|
||||
|
||||
@ -159,3 +144,57 @@ func sanitazeUsername(username string) string {
|
||||
return r
|
||||
}, username)
|
||||
}
|
||||
|
||||
func (pm *ProfileManager) getActiveProfileState() string {
|
||||
|
||||
configDir, err := getConfigDir()
|
||||
if err != nil {
|
||||
log.Warnf("failed to get config directory: %v", err)
|
||||
return defaultProfileName
|
||||
}
|
||||
|
||||
statePath := filepath.Join(configDir, "active_profile.txt")
|
||||
|
||||
prof, err := os.ReadFile(statePath)
|
||||
if err != nil {
|
||||
if !os.IsNotExist(err) {
|
||||
log.Warnf("failed to read active profile state: %v", err)
|
||||
} else {
|
||||
pm.setActiveProfileState(defaultProfileName)
|
||||
}
|
||||
return defaultProfileName
|
||||
}
|
||||
profileName := strings.TrimSpace(string(prof))
|
||||
|
||||
if profileName == "" {
|
||||
log.Warnf("active profile state is empty, using default profile: %s", defaultProfileName)
|
||||
return defaultProfileName
|
||||
}
|
||||
if !fileExists(filepath.Join(configDir, profileName+".json")) {
|
||||
log.Warnf("active profile %s does not exist, using default profile: %s", profileName, defaultProfileName)
|
||||
return defaultProfileName
|
||||
}
|
||||
return profileName
|
||||
}
|
||||
|
||||
func (pm *ProfileManager) setActiveProfileState(profileName string) error {
|
||||
|
||||
configDir, err := getConfigDir()
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to get config directory: %w", err)
|
||||
}
|
||||
|
||||
profPath := filepath.Join(configDir, profileName+".json")
|
||||
if !fileExists(profPath) {
|
||||
return fmt.Errorf("profile %s does not exist", profileName)
|
||||
}
|
||||
|
||||
statePath := filepath.Join(configDir, "active_profile.txt")
|
||||
|
||||
err = os.WriteFile(statePath, []byte(profileName), 0644)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to write active profile state: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
@ -287,6 +287,7 @@ func (p *profileMenu) refresh() {
|
||||
item.Remove()
|
||||
item.cancel()
|
||||
}
|
||||
p.profileSubItems = make([]*subItem, 0, len(profiles))
|
||||
|
||||
if p.manageProfilesSubItem != nil {
|
||||
// Remove the manage profiles item if it exists
|
||||
@ -327,7 +328,7 @@ func (p *profileMenu) refresh() {
|
||||
return
|
||||
}
|
||||
log.Infof("Switched to profile '%s'", profile.Name)
|
||||
p.profileMenuItem.SetTitle(profile.Name)
|
||||
p.refresh()
|
||||
// TODO(hakan): update email menu item if needed
|
||||
}
|
||||
}
|
||||
@ -351,6 +352,7 @@ func (p *profileMenu) refresh() {
|
||||
}
|
||||
// Handle manage profiles click
|
||||
p.eventHandler.runSelfCommand("profiles", "true")
|
||||
p.refresh()
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
Loading…
x
Reference in New Issue
Block a user