// 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: "", Action: useContext, } var deleteCommand = &cli.Command{ Name: "delete", Aliases: []string{"rm"}, Usage: "delete a context", ArgsUsage: "", Action: deleteContext, } var renameCommand = &cli.Command{ Name: "rename", Usage: "rename a context", ArgsUsage: " ", 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 }