mirror of
https://github.com/woodpecker-ci/woodpecker.git
synced 2026-02-13 21:00:00 +00:00
Add cli contexts (#5929)
This commit is contained in:
174
cli/context/context.go
Normal file
174
cli/context/context.go
Normal file
@@ -0,0 +1,174 @@
|
||||
// Copyright 2026 Woodpecker Authors
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package context
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"os"
|
||||
|
||||
"github.com/rs/zerolog/log"
|
||||
"github.com/urfave/cli/v3"
|
||||
|
||||
"go.woodpecker-ci.org/woodpecker/v3/cli/common"
|
||||
"go.woodpecker-ci.org/woodpecker/v3/cli/internal/config"
|
||||
"go.woodpecker-ci.org/woodpecker/v3/cli/output"
|
||||
)
|
||||
|
||||
// Command exports the context command set.
|
||||
var Command = &cli.Command{
|
||||
Name: "context",
|
||||
Aliases: []string{"ctx"},
|
||||
Usage: "manage contexts",
|
||||
Description: "Contexts can be used to manage users on one or multiple servers.\nTo create a new context run the setup command",
|
||||
Commands: []*cli.Command{
|
||||
listCommand,
|
||||
useCommand,
|
||||
deleteCommand,
|
||||
renameCommand,
|
||||
},
|
||||
}
|
||||
|
||||
var listCommand = &cli.Command{
|
||||
Name: "list",
|
||||
Aliases: []string{"ls"},
|
||||
Usage: "list all contexts",
|
||||
Flags: append(common.OutputFlags("table"), []cli.Flag{
|
||||
&cli.BoolFlag{
|
||||
Name: "output-no-headers",
|
||||
Usage: "do not print headers in output",
|
||||
},
|
||||
}...),
|
||||
Action: listContexts,
|
||||
}
|
||||
|
||||
var useCommand = &cli.Command{
|
||||
Name: "use",
|
||||
Usage: "set the current context",
|
||||
ArgsUsage: "<context-name>",
|
||||
Action: useContext,
|
||||
}
|
||||
|
||||
var deleteCommand = &cli.Command{
|
||||
Name: "delete",
|
||||
Aliases: []string{"rm"},
|
||||
Usage: "delete a context",
|
||||
ArgsUsage: "<context-name>",
|
||||
Action: deleteContext,
|
||||
}
|
||||
|
||||
var renameCommand = &cli.Command{
|
||||
Name: "rename",
|
||||
Usage: "rename a context",
|
||||
ArgsUsage: "<old-name> <new-name>",
|
||||
Action: renameContext,
|
||||
}
|
||||
|
||||
func listContexts(_ context.Context, c *cli.Command) error {
|
||||
contexts, err := config.LoadContexts()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if len(contexts.Contexts) == 0 {
|
||||
fmt.Println("No contexts found. Run 'woodpecker-cli setup' to create one.")
|
||||
return nil
|
||||
}
|
||||
|
||||
_, outOpt := output.ParseOutputOptions(c.String("output"))
|
||||
out := os.Stdout
|
||||
noHeader := c.Bool("output-no-headers")
|
||||
table := output.NewTable(out)
|
||||
|
||||
// Add custom field mapping
|
||||
table.AddFieldFn("Name", func(obj any) string {
|
||||
c, ok := obj.(config.Context)
|
||||
if !ok {
|
||||
return "???"
|
||||
}
|
||||
|
||||
if contexts.CurrentContext == c.Name {
|
||||
return c.Name + " *"
|
||||
}
|
||||
|
||||
return c.Name
|
||||
})
|
||||
table.AddFieldAlias("ServerURL", "Server URL")
|
||||
table.AddFieldAlias("LogLevel", "Log Level")
|
||||
table.AddFieldAlias("Name", "Name (selected)")
|
||||
|
||||
cols := []string{"Name (selected)", "Server URL"}
|
||||
|
||||
if len(outOpt) > 0 {
|
||||
cols = outOpt
|
||||
}
|
||||
if !noHeader {
|
||||
table.WriteHeader(cols)
|
||||
}
|
||||
for _, c := range contexts.Contexts {
|
||||
if err := table.Write(cols, c); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return table.Flush()
|
||||
}
|
||||
|
||||
func useContext(_ context.Context, c *cli.Command) error {
|
||||
contextName := c.Args().First()
|
||||
if contextName == "" {
|
||||
return fmt.Errorf("context name is required")
|
||||
}
|
||||
|
||||
err := config.SetCurrentContext(contextName)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
log.Info().Msgf("Switched to context '%s'", contextName)
|
||||
return nil
|
||||
}
|
||||
|
||||
func deleteContext(_ context.Context, c *cli.Command) error {
|
||||
contextName := c.Args().First()
|
||||
if contextName == "" {
|
||||
return fmt.Errorf("context name is required")
|
||||
}
|
||||
|
||||
err := config.DeleteContext(c, contextName)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
log.Info().Msgf("Context '%s' deleted", contextName)
|
||||
return nil
|
||||
}
|
||||
|
||||
func renameContext(_ context.Context, c *cli.Command) error {
|
||||
if c.Args().Len() < 2 { //nolint:mnd // min args
|
||||
return fmt.Errorf("both old name and new name are required")
|
||||
}
|
||||
|
||||
oldName := c.Args().Get(0)
|
||||
newName := c.Args().Get(1)
|
||||
|
||||
err := config.RenameContext(oldName, newName)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
log.Info().Msgf("Context renamed from '%s' to '%s'", oldName, newName)
|
||||
return nil
|
||||
}
|
||||
@@ -31,20 +31,46 @@ func (c *Config) MergeIfNotSet(c2 *Config) {
|
||||
}
|
||||
}
|
||||
|
||||
var skipSetupForCommands = []string{"setup", "help", "h", "version", "update", "lint", "exec", "completion", ""}
|
||||
var skipSetupForCommands = []string{"setup", "help", "h", "version", "update", "lint", "exec", "completion", "", "context", "ctx"}
|
||||
|
||||
func Load(ctx context.Context, c *cli.Command) error {
|
||||
if firstArg := c.Args().First(); slices.Contains(skipSetupForCommands, firstArg) {
|
||||
return nil
|
||||
}
|
||||
|
||||
contextConfig, contextErr := GetCurrentContext(ctx, c)
|
||||
if contextErr == nil {
|
||||
if !c.IsSet("server") {
|
||||
err := c.Set("server", contextConfig.ServerURL)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
if !c.IsSet("token") {
|
||||
err := c.Set("token", contextConfig.Token)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
if !c.IsSet("log-level") && contextConfig.LogLevel != "" {
|
||||
err := c.Set("log-level", contextConfig.LogLevel)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
log.Debug().Any("config", contextConfig).Msg("loaded config from context")
|
||||
return nil
|
||||
}
|
||||
|
||||
// TODO: remove with next major release
|
||||
// Fallback: try legacy config file (for backward compatibility)
|
||||
config, err := Get(ctx, c, c.String("config"))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if config.ServerURL == "" || config.Token == "" {
|
||||
log.Info().Msg("woodpecker-cli is not set up, run `woodpecker-cli setup` or provide required environment variables/flags")
|
||||
log.Info().Msg("woodpecker-cli is not set up, run `woodpecker-cli setup` to create a context")
|
||||
return errors.New("woodpecker-cli is not configured")
|
||||
}
|
||||
|
||||
@@ -63,7 +89,7 @@ func Load(ctx context.Context, c *cli.Command) error {
|
||||
return err
|
||||
}
|
||||
|
||||
log.Debug().Any("config", config).Msg("loaded config")
|
||||
log.Debug().Any("config", config).Msg("loaded config from legacy file")
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
245
cli/internal/config/context.go
Normal file
245
cli/internal/config/context.go
Normal file
@@ -0,0 +1,245 @@
|
||||
// Copyright 2026 Woodpecker Authors
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package config
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
|
||||
"github.com/adrg/xdg"
|
||||
"github.com/rs/zerolog/log"
|
||||
"github.com/urfave/cli/v3"
|
||||
"github.com/zalando/go-keyring"
|
||||
)
|
||||
|
||||
// Context represents a single CLI context with its connection details.
|
||||
type Context struct {
|
||||
Name string `json:"name"`
|
||||
ServerURL string `json:"server_url"`
|
||||
LogLevel string `json:"log_level,omitempty"`
|
||||
}
|
||||
|
||||
// Contexts holds all contexts and tracks the current active one.
|
||||
type Contexts struct {
|
||||
CurrentContext string `json:"current_context"`
|
||||
Contexts map[string]Context `json:"contexts"`
|
||||
}
|
||||
|
||||
func getContextsPath() (string, error) {
|
||||
configPath, err := xdg.ConfigFile("woodpecker/contexts.json")
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return configPath, nil
|
||||
}
|
||||
|
||||
// LoadContexts loads all contexts from the contexts file.
|
||||
func LoadContexts() (*Contexts, error) {
|
||||
contextsPath, err := getContextsPath()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
content, err := os.ReadFile(contextsPath)
|
||||
if err != nil {
|
||||
if os.IsNotExist(err) {
|
||||
return &Contexts{
|
||||
Contexts: make(map[string]Context),
|
||||
}, nil
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var contexts Contexts
|
||||
err = json.Unmarshal(content, &contexts)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if contexts.Contexts == nil {
|
||||
contexts.Contexts = make(map[string]Context)
|
||||
}
|
||||
|
||||
return &contexts, nil
|
||||
}
|
||||
|
||||
// SaveContexts saves all contexts to the contexts file.
|
||||
func SaveContexts(contexts *Contexts) error {
|
||||
data, err := json.MarshalIndent(contexts, "", " ")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
contextsPath, err := getContextsPath()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Ensure the directory exists.
|
||||
dir := filepath.Dir(contextsPath)
|
||||
if err := os.MkdirAll(dir, 0o755); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return os.WriteFile(contextsPath, data, 0o600)
|
||||
}
|
||||
|
||||
// GetCurrentContext returns the current active context.
|
||||
func GetCurrentContext(ctx context.Context, c *cli.Command) (*Config, error) {
|
||||
contexts, err := LoadContexts()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if contexts.CurrentContext == "" {
|
||||
return nil, errors.New("no context is currently set")
|
||||
}
|
||||
|
||||
context, exists := contexts.Contexts[contexts.CurrentContext]
|
||||
if !exists {
|
||||
return nil, fmt.Errorf("current context '%s' not found", contexts.CurrentContext)
|
||||
}
|
||||
|
||||
return GetContextConfig(c, &context)
|
||||
}
|
||||
|
||||
// GetContextConfig loads the config for a specific context including the token from keyring.
|
||||
func GetContextConfig(c *cli.Command, ctx *Context) (*Config, error) {
|
||||
conf := &Config{
|
||||
ServerURL: ctx.ServerURL,
|
||||
LogLevel: ctx.LogLevel,
|
||||
}
|
||||
|
||||
// Load token from keyring
|
||||
service := c.Root().Name
|
||||
secret, err := keyring.Get(service, ctx.ServerURL)
|
||||
if errors.Is(err, keyring.ErrUnsupportedPlatform) {
|
||||
log.Warn().Msg("keyring is not supported on this platform")
|
||||
return conf, nil
|
||||
}
|
||||
if errors.Is(err, keyring.ErrNotFound) {
|
||||
return nil, fmt.Errorf("token not found in keyring for context '%s'", ctx.Name)
|
||||
}
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
conf.Token = secret
|
||||
return conf, nil
|
||||
}
|
||||
|
||||
// AddOrUpdateContext adds or updates a context and optionally sets it as current.
|
||||
func AddOrUpdateContext(c *cli.Command, name, serverURL, token, logLevel string, setCurrent bool) error {
|
||||
contexts, err := LoadContexts()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
contexts.Contexts[name] = Context{
|
||||
Name: name,
|
||||
ServerURL: serverURL,
|
||||
LogLevel: logLevel,
|
||||
}
|
||||
|
||||
if setCurrent || contexts.CurrentContext == "" {
|
||||
contexts.CurrentContext = name
|
||||
}
|
||||
|
||||
// Save token to keyring
|
||||
service := c.Root().Name
|
||||
err = keyring.Set(service, serverURL, token)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return SaveContexts(contexts)
|
||||
}
|
||||
|
||||
// DeleteContext removes a context.
|
||||
func DeleteContext(c *cli.Command, name string) error {
|
||||
contexts, err := LoadContexts()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
context, exists := contexts.Contexts[name]
|
||||
if !exists {
|
||||
return fmt.Errorf("context '%s' not found", name)
|
||||
}
|
||||
|
||||
// Try to delete token from keyring
|
||||
service := c.Root().Name
|
||||
err = keyring.Delete(service, context.ServerURL)
|
||||
if err != nil && !errors.Is(err, keyring.ErrNotFound) {
|
||||
log.Warn().Err(err).Msg("failed to delete token from keyring")
|
||||
}
|
||||
|
||||
delete(contexts.Contexts, name)
|
||||
|
||||
// If we deleted the current context, unset it
|
||||
if contexts.CurrentContext == name {
|
||||
contexts.CurrentContext = ""
|
||||
}
|
||||
|
||||
return SaveContexts(contexts)
|
||||
}
|
||||
|
||||
// SetCurrentContext sets the current active context.
|
||||
func SetCurrentContext(name string) error {
|
||||
contexts, err := LoadContexts()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if _, exists := contexts.Contexts[name]; !exists {
|
||||
return fmt.Errorf("context '%s' not found", name)
|
||||
}
|
||||
|
||||
contexts.CurrentContext = name
|
||||
return SaveContexts(contexts)
|
||||
}
|
||||
|
||||
// RenameContext renames an existing context.
|
||||
func RenameContext(oldName, newName string) error {
|
||||
contexts, err := LoadContexts()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
context, exists := contexts.Contexts[oldName]
|
||||
if !exists {
|
||||
return fmt.Errorf("context '%s' not found", oldName)
|
||||
}
|
||||
|
||||
if _, exists := contexts.Contexts[newName]; exists {
|
||||
return fmt.Errorf("context '%s' already exists", newName)
|
||||
}
|
||||
|
||||
// Update the name in the context
|
||||
context.Name = newName
|
||||
contexts.Contexts[newName] = context
|
||||
delete(contexts.Contexts, oldName)
|
||||
|
||||
// Update current context if necessary
|
||||
if contexts.CurrentContext == oldName {
|
||||
contexts.CurrentContext = newName
|
||||
}
|
||||
|
||||
return SaveContexts(contexts)
|
||||
}
|
||||
142
cli/internal/config/context_test.go
Normal file
142
cli/internal/config/context_test.go
Normal file
@@ -0,0 +1,142 @@
|
||||
// Copyright 2026 Woodpecker Authors
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package config
|
||||
|
||||
import (
|
||||
"os"
|
||||
"testing"
|
||||
|
||||
"github.com/adrg/xdg"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestContextManagement(t *testing.T) {
|
||||
// Create a temporary directory for test contexts
|
||||
tmpDir := t.TempDir()
|
||||
|
||||
// Override xdg directories for testing
|
||||
t.Setenv("HOME", tmpDir)
|
||||
xdg.Reload()
|
||||
contextsFile, err := xdg.ConfigFile("woodpecker/contexts.json")
|
||||
require.NoError(t, err)
|
||||
|
||||
t.Run("LoadContexts returns empty when file doesn't exist", func(t *testing.T) {
|
||||
contexts, err := LoadContexts()
|
||||
require.NoError(t, err)
|
||||
assert.NotNil(t, contexts)
|
||||
assert.Empty(t, contexts.Contexts)
|
||||
assert.Empty(t, contexts.CurrentContext)
|
||||
})
|
||||
|
||||
t.Run("SaveContexts creates valid JSON", func(t *testing.T) {
|
||||
contexts := &Contexts{
|
||||
CurrentContext: "test",
|
||||
Contexts: map[string]Context{
|
||||
"test": {
|
||||
Name: "test",
|
||||
ServerURL: "https://test.example.com",
|
||||
LogLevel: "info",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
err := SaveContexts(contexts)
|
||||
require.NoError(t, err)
|
||||
|
||||
// Verify file exists and contains valid JSON
|
||||
data, err := os.ReadFile(contextsFile)
|
||||
require.NoError(t, err)
|
||||
assert.Contains(t, string(data), "test.example.com")
|
||||
})
|
||||
|
||||
t.Run("LoadContexts reads saved contexts", func(t *testing.T) {
|
||||
contexts, err := LoadContexts()
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, "test", contexts.CurrentContext)
|
||||
assert.Len(t, contexts.Contexts, 1)
|
||||
assert.Equal(t, "https://test.example.com", contexts.Contexts["test"].ServerURL)
|
||||
})
|
||||
|
||||
t.Run("SetCurrentContext updates current context", func(t *testing.T) {
|
||||
contexts := &Contexts{
|
||||
CurrentContext: "test",
|
||||
Contexts: map[string]Context{
|
||||
"test": {
|
||||
Name: "test",
|
||||
ServerURL: "https://test.example.com",
|
||||
},
|
||||
"prod": {
|
||||
Name: "prod",
|
||||
ServerURL: "https://prod.example.com",
|
||||
},
|
||||
},
|
||||
}
|
||||
err := SaveContexts(contexts)
|
||||
require.NoError(t, err)
|
||||
|
||||
err = SetCurrentContext("prod")
|
||||
require.NoError(t, err)
|
||||
|
||||
contexts, err = LoadContexts()
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, "prod", contexts.CurrentContext)
|
||||
})
|
||||
|
||||
t.Run("SetCurrentContext fails for non-existent context", func(t *testing.T) {
|
||||
err := SetCurrentContext("nonexistent")
|
||||
assert.Error(t, err)
|
||||
assert.Contains(t, err.Error(), "not found")
|
||||
})
|
||||
|
||||
t.Run("RenameContext updates context name", func(t *testing.T) {
|
||||
contexts := &Contexts{
|
||||
CurrentContext: "old",
|
||||
Contexts: map[string]Context{
|
||||
"old": {
|
||||
Name: "old",
|
||||
ServerURL: "https://test.example.com",
|
||||
},
|
||||
},
|
||||
}
|
||||
err := SaveContexts(contexts)
|
||||
require.NoError(t, err)
|
||||
|
||||
err = RenameContext("old", "new")
|
||||
require.NoError(t, err)
|
||||
|
||||
contexts, err = LoadContexts()
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, "new", contexts.CurrentContext)
|
||||
assert.Contains(t, contexts.Contexts, "new")
|
||||
assert.NotContains(t, contexts.Contexts, "old")
|
||||
assert.Equal(t, "new", contexts.Contexts["new"].Name)
|
||||
})
|
||||
|
||||
t.Run("RenameContext fails if target exists", func(t *testing.T) {
|
||||
contexts := &Contexts{
|
||||
Contexts: map[string]Context{
|
||||
"ctx1": {Name: "ctx1", ServerURL: "https://test1.example.com"},
|
||||
"ctx2": {Name: "ctx2", ServerURL: "https://test2.example.com"},
|
||||
},
|
||||
}
|
||||
err := SaveContexts(contexts)
|
||||
require.NoError(t, err)
|
||||
|
||||
err = RenameContext("ctx1", "ctx2")
|
||||
assert.Error(t, err)
|
||||
assert.Contains(t, err.Error(), "already exists")
|
||||
})
|
||||
}
|
||||
@@ -3,6 +3,7 @@ package setup
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/rs/zerolog/log"
|
||||
@@ -26,16 +27,29 @@ var Command = &cli.Command{
|
||||
Name: "token",
|
||||
Usage: "token to authenticate with the woodpecker server",
|
||||
},
|
||||
&cli.StringFlag{
|
||||
Name: "context",
|
||||
Aliases: []string{"ctx"},
|
||||
Usage: "name for the context (defaults to 'default')",
|
||||
},
|
||||
},
|
||||
Action: setup,
|
||||
}
|
||||
|
||||
func setup(ctx context.Context, c *cli.Command) error {
|
||||
_config, err := config.Get(ctx, c, c.String("config"))
|
||||
contextName := c.String("context")
|
||||
if contextName == "" {
|
||||
contextName = "default"
|
||||
}
|
||||
|
||||
// Check if context already exists
|
||||
contexts, err := config.LoadContexts()
|
||||
if err != nil {
|
||||
return err
|
||||
} else if _config != nil {
|
||||
setupAgain, err := ui.Confirm("The woodpecker-cli was already configured. Do you want to configure it again?")
|
||||
}
|
||||
|
||||
if existingCtx, exists := contexts.Contexts[contextName]; exists {
|
||||
setupAgain, err := ui.Confirm(fmt.Sprintf("Context '%s' already exists (server: %s). Do you want to reconfigure it?", contextName, existingCtx.ServerURL))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -78,16 +92,13 @@ func setup(ctx context.Context, c *cli.Command) error {
|
||||
}
|
||||
}
|
||||
|
||||
err = config.Save(ctx, c, c.String("config"), &config.Config{
|
||||
ServerURL: serverURL,
|
||||
Token: token,
|
||||
LogLevel: "info",
|
||||
})
|
||||
// Save as context
|
||||
err = config.AddOrUpdateContext(c, contextName, serverURL, token, "info", true)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
log.Info().Msg("woodpecker-cli has been successfully setup")
|
||||
log.Info().Msgf("Context '%s' has been successfully created and set as current", contextName)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -19,6 +19,7 @@ import (
|
||||
|
||||
"go.woodpecker-ci.org/woodpecker/v3/cli/admin"
|
||||
"go.woodpecker-ci.org/woodpecker/v3/cli/common"
|
||||
"go.woodpecker-ci.org/woodpecker/v3/cli/context"
|
||||
"go.woodpecker-ci.org/woodpecker/v3/cli/exec"
|
||||
"go.woodpecker-ci.org/woodpecker/v3/cli/info"
|
||||
"go.woodpecker-ci.org/woodpecker/v3/cli/lint"
|
||||
@@ -47,6 +48,7 @@ func newApp() *cli.Command {
|
||||
}
|
||||
app.Commands = []*cli.Command{
|
||||
admin.Command,
|
||||
context.Command,
|
||||
exec.Command,
|
||||
info.Command,
|
||||
lint.Command,
|
||||
|
||||
Reference in New Issue
Block a user