Skip to content

Commit 63876db

Browse files
authored
Config file and env var for istioctl --istioNamespace, --xds-address, and --cert-dir (istio#25280)
* Config file and env var for some istioctl CLI options * ISTIOCONFIG variable for overriding default istioctl configuration * 'prefer-experimental' option for commands with regular and experimental variants * Defaults for --xds-san and --insecure * Column for 'istioctl x config list' that lets users tell defaults from configured values * Fail if user supplies invalid ISTIOCONFIG env var * Don't fail if config file does not exist * Initialize defaults in tests * Show origin of config var; move defaulting close to command so tests work * Use Istio RegisterXXXVar for environment overrides to istioctl * Allow user to make XDS-based proxy-status the default with env or config setting * Added release note * Sort 'x config list' output * release note fix
1 parent 9e4a19b commit 63876db

File tree

6 files changed

+296
-11
lines changed

6 files changed

+296
-11
lines changed

Diff for: istioctl/cmd/config.go

+140
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,140 @@
1+
// Copyright Istio Authors
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
package cmd
16+
17+
import (
18+
"fmt"
19+
"io"
20+
"sort"
21+
"text/tabwriter"
22+
23+
"github.com/spf13/cobra"
24+
"github.com/spf13/viper"
25+
26+
"istio.io/istio/pilot/pkg/serviceregistry/kube/controller"
27+
"istio.io/pkg/env"
28+
)
29+
30+
var (
31+
// settableFlags are the flags used to istioctl
32+
settableFlags = map[string]interface{}{
33+
"istioNamespace": env.RegisterStringVar("ISTIOCTL_ISTIONAMESPACE", controller.IstioNamespace, "istioctl --istioNamespace override"),
34+
"xds-address": env.RegisterStringVar("ISTIOCTL_XDS_ADDRESS", "", "istioctl --xds-address override"),
35+
"xds-port": env.RegisterIntVar("ISTIOCTL_XDS_PORT", 15012, "istioctl --xds-port override"),
36+
"xds-san": env.RegisterStringVar("ISTIOCTL_XDS_SAN", "", "istioctl --xds-san override"),
37+
"cert-dir": env.RegisterStringVar("ISTIOCTL_CERT_DIR", "", "istioctl --cert-dir override"),
38+
"insecure": env.RegisterBoolVar("ISTIOCTL_INSECURE", false, "istioctl --insecure override"),
39+
"prefer-experimental": env.RegisterBoolVar("ISTIOCTL_PREFER_EXPERIMENTAL", false, "istioctl should use experimental subcommand variants"),
40+
}
41+
)
42+
43+
// configCmd represents the config subcommand command
44+
func configCmd() *cobra.Command {
45+
configCmd := &cobra.Command{
46+
Use: "config SUBCOMMAND",
47+
Short: "Gonfigure istioctl defaults",
48+
Args: cobra.NoArgs,
49+
Example: `
50+
# list configuration parameters
51+
istioctl config list
52+
`,
53+
}
54+
configCmd.AddCommand(listCommand())
55+
return configCmd
56+
}
57+
58+
func listCommand() *cobra.Command {
59+
listCmd := &cobra.Command{
60+
Use: "list",
61+
Short: "List istio configurable defaults",
62+
Args: cobra.ExactArgs(0),
63+
RunE: func(c *cobra.Command, _ []string) error {
64+
scope.Debugf("Config file %q", IstioConfig)
65+
return runList(c.OutOrStdout())
66+
},
67+
}
68+
return listCmd
69+
}
70+
71+
func runList(writer io.Writer) error {
72+
// Sort flag names
73+
keys := make([]string, len(settableFlags))
74+
i := 0
75+
for key := range settableFlags {
76+
keys[i] = key
77+
i++
78+
}
79+
sort.Strings(keys)
80+
81+
w := new(tabwriter.Writer).Init(writer, 0, 8, 5, ' ', 0)
82+
fmt.Fprintf(w, "FLAG\tVALUE\tFROM\n")
83+
for _, flag := range keys {
84+
v := settableFlags[flag]
85+
fmt.Fprintf(w, "%s\t%s\t%v\n", flag, viper.GetString(flag), configSource(flag, v))
86+
}
87+
return w.Flush()
88+
}
89+
90+
func configSource(flag string, v interface{}) string {
91+
// Environment variables have high precedence in Viper
92+
if isVarSet(v) {
93+
return "$" + getVarVar(v).Name
94+
}
95+
96+
if viper.InConfig(flag) {
97+
return IstioConfig
98+
}
99+
100+
return "default"
101+
}
102+
103+
func getVarVar(v interface{}) env.Var {
104+
switch ev := v.(type) {
105+
case env.StringVar:
106+
return ev.Var
107+
case env.BoolVar:
108+
return ev.Var
109+
case env.IntVar:
110+
return ev.Var
111+
case env.DurationVar:
112+
return ev.Var
113+
case env.FloatVar:
114+
return ev.Var
115+
default:
116+
panic(fmt.Sprintf("Unexpected environment var type %v", v))
117+
}
118+
}
119+
120+
func isVarSet(v interface{}) bool {
121+
switch ev := v.(type) {
122+
case env.StringVar:
123+
_, ok := ev.Lookup()
124+
return ok
125+
case env.BoolVar:
126+
_, ok := ev.Lookup()
127+
return ok
128+
case env.IntVar:
129+
_, ok := ev.Lookup()
130+
return ok
131+
case env.DurationVar:
132+
_, ok := ev.Lookup()
133+
return ok
134+
case env.FloatVar:
135+
_, ok := ev.Lookup()
136+
return ok
137+
default:
138+
panic(fmt.Sprintf("Unexpected environment var type %v", v))
139+
}
140+
}

Diff for: istioctl/cmd/config_test.go

+52
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
// Copyright Istio Authors
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
package cmd
16+
17+
import (
18+
"fmt"
19+
"regexp"
20+
"strings"
21+
"testing"
22+
)
23+
24+
func TestConfigList(t *testing.T) {
25+
26+
cases := []testCase{
27+
{ // case 0
28+
args: strings.Split("experimental config get istioNamespace", " "),
29+
expectedRegexp: regexp.MustCompile("Usage:\n istioctl experimental config"),
30+
wantException: false,
31+
},
32+
{ // case 1
33+
args: strings.Split("experimental config list", " "),
34+
expectedOutput: `FLAG VALUE FROM
35+
cert-dir default
36+
insecure default
37+
istioNamespace istio-system default
38+
prefer-experimental default
39+
xds-address default
40+
xds-port 15012 default
41+
xds-san default
42+
`,
43+
wantException: false,
44+
},
45+
}
46+
47+
for i, c := range cases {
48+
t.Run(fmt.Sprintf("case %d %s", i, strings.Join(c.args, " ")), func(t *testing.T) {
49+
verifyOutput(t, c)
50+
})
51+
}
52+
}

Diff for: istioctl/cmd/istioctl/main.go

+7
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
package main
1616

1717
import (
18+
"fmt"
1819
"os"
1920

2021
// import all known client auth plugins
@@ -24,6 +25,12 @@ import (
2425
)
2526

2627
func main() {
28+
if err := cmd.ConfigAndEnvProcessing(); err != nil {
29+
fmt.Fprintf(os.Stderr, "Could not initialize: %v\n", err)
30+
exitCode := cmd.GetExitCode(err)
31+
os.Exit(exitCode)
32+
}
33+
2734
rootCmd := cmd.GetRootCmd(os.Args[1:])
2835

2936
if err := rootCmd.Execute(); err != nil {

Diff for: istioctl/cmd/root.go

+76-6
Original file line numberDiff line numberDiff line change
@@ -17,9 +17,12 @@ package cmd
1717
import (
1818
"errors"
1919
"fmt"
20+
"path/filepath"
21+
"strings"
2022

2123
"github.com/spf13/cobra"
2224
"github.com/spf13/cobra/doc"
25+
"github.com/spf13/viper"
2326
v1 "k8s.io/api/core/v1"
2427
"k8s.io/client-go/tools/clientcmd"
2528

@@ -30,6 +33,7 @@ import (
3033
"istio.io/istio/pilot/pkg/serviceregistry/kube/controller"
3134
"istio.io/istio/pkg/cmd"
3235
"istio.io/pkg/collateral"
36+
"istio.io/pkg/env"
3337
"istio.io/pkg/log"
3438
)
3539

@@ -42,7 +46,16 @@ func (c CommandParseError) Error() string {
4246
return c.e.Error()
4347
}
4448

49+
const (
50+
// Location to read istioctl defaults from
51+
defaultIstioctlConfig = "$HOME/.istioctl/config.yaml"
52+
)
53+
4554
var (
55+
// IstioConfig is the name of the istioctl config file (if any)
56+
IstioConfig = env.RegisterStringVar("ISTIOCONFIG", defaultIstioctlConfig,
57+
"Default values for istioctl flags").Get()
58+
4659
kubeconfig string
4760
configContext string
4861
namespace string
@@ -56,6 +69,9 @@ var (
5669
kubeClient = newKubeClient
5770

5871
loggingOptions = defaultLogOptions()
72+
73+
// scope is for dev logging. Warning: log levels are not set by --log_output_level until command is Run().
74+
scope = log.RegisterScope("cli", "istioctl", 0)
5975
)
6076

6177
func defaultLogOptions() *log.Options {
@@ -74,6 +90,34 @@ func defaultLogOptions() *log.Options {
7490
return o
7591
}
7692

93+
// ConfigAndEnvProcessing uses spf13/viper for overriding CLI parameters
94+
func ConfigAndEnvProcessing() error {
95+
configPath := filepath.Dir(IstioConfig)
96+
baseName := filepath.Base(IstioConfig)
97+
configType := filepath.Ext(IstioConfig)
98+
configName := baseName[0 : len(baseName)-len(configType)]
99+
if configType != "" {
100+
configType = configType[1:]
101+
}
102+
103+
// Allow users to override some variables through $HOME/.istioctl/config.yaml
104+
// and environment variables.
105+
viper.SetEnvPrefix("ISTIOCTL")
106+
viper.AutomaticEnv()
107+
viper.AllowEmptyEnv(true) // So we can say ISTIOCTL_CERT_DIR="" to suppress certs
108+
viper.SetConfigName(configName)
109+
viper.SetConfigType(configType)
110+
viper.AddConfigPath(configPath)
111+
viper.SetEnvKeyReplacer(strings.NewReplacer("-", "_"))
112+
err := viper.ReadInConfig()
113+
// Ignore errors reading the configuration unless the file is explicitly customized
114+
if IstioConfig != defaultIstioctlConfig {
115+
return err
116+
}
117+
118+
return nil
119+
}
120+
77121
// GetRootCmd returns the root of the cobra command-tree.
78122
func GetRootCmd(args []string) *cobra.Command {
79123
rootCmd := &cobra.Command{
@@ -95,7 +139,8 @@ debug and diagnose their Istio mesh.
95139
rootCmd.PersistentFlags().StringVar(&configContext, "context", "",
96140
"The name of the kubeconfig context to use")
97141

98-
rootCmd.PersistentFlags().StringVarP(&istioNamespace, "istioNamespace", "i", controller.IstioNamespace,
142+
viper.SetDefault("istioNamespace", controller.IstioNamespace)
143+
rootCmd.PersistentFlags().StringVarP(&istioNamespace, "istioNamespace", "i", viper.GetString("istioNamespace"),
99144
"Istio system namespace")
100145

101146
rootCmd.PersistentFlags().StringVarP(&namespace, "namespace", "n", v1.NamespaceAll,
@@ -111,7 +156,6 @@ debug and diagnose their Istio mesh.
111156

112157
cmd.AddFlags(rootCmd)
113158

114-
rootCmd.AddCommand(newVersionCommand())
115159
rootCmd.AddCommand(register())
116160
rootCmd.AddCommand(deregisterCmd)
117161
rootCmd.AddCommand(injectCommand())
@@ -127,12 +171,40 @@ debug and diagnose their Istio mesh.
127171
Short: "Experimental commands that may be modified or deprecated",
128172
}
129173

174+
xdsBasedTroubleshooting := []*cobra.Command{
175+
xdsVersionCommand(),
176+
xdsStatusCommand(),
177+
}
178+
debugBasedTroubleshooting := []*cobra.Command{
179+
newVersionCommand(),
180+
statusCommand(),
181+
}
182+
var debugCmdAttachmentPoint *cobra.Command
183+
if viper.GetBool("PREFER-EXPERIMENTAL") {
184+
legacyCmd := &cobra.Command{
185+
Use: "legacy",
186+
Short: "Legacy command variants",
187+
}
188+
rootCmd.AddCommand(legacyCmd)
189+
for _, c := range xdsBasedTroubleshooting {
190+
rootCmd.AddCommand(c)
191+
}
192+
debugCmdAttachmentPoint = legacyCmd
193+
} else {
194+
debugCmdAttachmentPoint = rootCmd
195+
}
196+
for _, c := range xdsBasedTroubleshooting {
197+
experimentalCmd.AddCommand(c)
198+
}
199+
for _, c := range debugBasedTroubleshooting {
200+
debugCmdAttachmentPoint.AddCommand(c)
201+
}
202+
130203
rootCmd.AddCommand(experimentalCmd)
131204
rootCmd.AddCommand(proxyConfig())
132205

133206
rootCmd.AddCommand(convertIngress())
134207
rootCmd.AddCommand(dashboard())
135-
rootCmd.AddCommand(statusCommand())
136208
rootCmd.AddCommand(Analyze())
137209

138210
rootCmd.AddCommand(install.NewVerifyCommand())
@@ -150,9 +222,7 @@ debug and diagnose their Istio mesh.
150222
experimentalCmd.AddCommand(vmBootstrapCommand())
151223
experimentalCmd.AddCommand(waitCmd())
152224
experimentalCmd.AddCommand(mesh.UninstallCmd(loggingOptions))
153-
154-
experimentalCmd.AddCommand(xdsVersionCommand())
155-
experimentalCmd.AddCommand(xdsStatusCommand())
225+
experimentalCmd.AddCommand(configCmd())
156226

157227
postInstallCmd.AddCommand(Webhook())
158228
experimentalCmd.AddCommand(postInstallCmd)

0 commit comments

Comments
 (0)