Skip to content

Commit 3f10acb

Browse files
committed
diagnostics: refactor build-and-run for clarity
Improve the legibility of the code that builds and runs diagnostics. The main confusion was the need to track and report the number of diagnostic errors and warnings versus problems that halt execution prematurely and the need to return a correct status code at completion. In the end it seemed simplest to just have the logger report how many diagnostic errors and warnings were seen, leaving function signatures to return only build/run errors. As a side effect, I looked at the ConfigLoading code that does an early check to see if there is a client config, and concluded it was confusing and unnecessary for it to be a diagnostic, so I refactored it away. Main diagnostics as well as pod diagnostics are now implemented more uniformly.
1 parent a9387ba commit 3f10acb

File tree

10 files changed

+341
-483
lines changed

10 files changed

+341
-483
lines changed

pkg/oc/admin/diagnostics/client.go

+5-5
Original file line numberDiff line numberDiff line change
@@ -19,14 +19,14 @@ func availableClientDiagnostics() types.DiagnosticList {
1919
}
2020

2121
// buildClientDiagnostics builds client Diagnostic objects based on the rawConfig passed in.
22-
// Returns the Diagnostics built, "ok" bool for whether to proceed or abort, and an error if any was encountered during the building of diagnostics.) {
23-
func (o DiagnosticsOptions) buildClientDiagnostics(rawConfig *clientcmdapi.Config) ([]types.Diagnostic, bool, error) {
22+
// Returns the Diagnostics built, and any fatal errors encountered during the building of diagnostics.
23+
func (o DiagnosticsOptions) buildClientDiagnostics(rawConfig *clientcmdapi.Config) ([]types.Diagnostic, error) {
2424
available := availableClientDiagnostics().Names()
2525

2626
networkClient, err := o.Factory.OpenshiftInternalNetworkClient()
2727
kubeClient, clientErr := o.Factory.ClientSet()
2828
if clientErr != nil || err != nil {
29-
o.Logger.Notice("CED0001", "Could not configure a client, so client diagnostics are limited to testing configuration and connection")
29+
o.Logger().Notice("CED0001", "Could not configure a client, so client diagnostics are limited to testing configuration and connection")
3030
available = sets.NewString(clientdiags.ConfigContextsName)
3131
}
3232

@@ -66,8 +66,8 @@ func (o DiagnosticsOptions) buildClientDiagnostics(rawConfig *clientcmdapi.Confi
6666
nd.PreventModification = o.PreventModification
6767
diagnostics = append(diagnostics, nd)
6868
default:
69-
return nil, false, fmt.Errorf("unknown diagnostic: %v", diagnosticName)
69+
return nil, fmt.Errorf("unknown diagnostic: %v", diagnosticName)
7070
}
7171
}
72-
return diagnostics, true, clientErr
72+
return diagnostics, clientErr
7373
}

pkg/oc/admin/diagnostics/cluster.go

+53-51
Original file line numberDiff line numberDiff line change
@@ -43,52 +43,50 @@ func availableClusterDiagnostics() types.DiagnosticList {
4343
}
4444

4545
// buildClusterDiagnostics builds cluster Diagnostic objects if a cluster-admin client can be extracted from the rawConfig passed in.
46-
// Returns the Diagnostics built, "ok" bool for whether to proceed or abort, and an error if any was encountered during the building of diagnostics.) {
47-
func (o DiagnosticsOptions) buildClusterDiagnostics(rawConfig *clientcmdapi.Config) ([]types.Diagnostic, bool, error) {
46+
// Returns the Diagnostics built and any fatal error encountered during the building of diagnostics.
47+
func (o DiagnosticsOptions) buildClusterDiagnostics(rawConfig *clientcmdapi.Config) ([]types.Diagnostic, error) {
4848
requestedDiagnostics := availableClusterDiagnostics().Names().Intersection(sets.NewString(o.RequestedDiagnostics.List()...)).List()
4949
if len(requestedDiagnostics) == 0 { // no diagnostics to run here
50-
return nil, true, nil // don't waste time on discovery
50+
return nil, nil // don't waste time on discovery
5151
}
5252

53-
var (
54-
kclusterClient kclientset.Interface
55-
)
53+
var kclusterClient kclientset.Interface
5654

57-
config, kclusterClient, found, serverUrl, err := o.findClusterClients(rawConfig)
58-
if !found {
59-
o.Logger.Notice("CED1002", "Could not configure a client with cluster-admin permissions for the current server, so cluster diagnostics will be skipped")
60-
return nil, true, err
55+
config, kclusterClient, serverUrl, err := o.findClusterClients(rawConfig)
56+
if config == nil {
57+
o.Logger().Notice("CED1002", "Could not configure a client with cluster-admin permissions for the current server, so cluster diagnostics will be skipped")
58+
return nil, nil
6159
}
6260
if err != nil {
63-
return nil, false, err
61+
return nil, err
6462
}
6563
imageClient, err := imageclient.NewForConfig(config)
6664
if err != nil {
67-
return nil, false, err
65+
return nil, err
6866
}
6967
projectClient, err := projectclient.NewForConfig(config)
7068
if err != nil {
71-
return nil, false, err
69+
return nil, err
7270
}
7371
routeClient, err := routeclient.NewForConfig(config)
7472
if err != nil {
75-
return nil, false, err
73+
return nil, err
7674
}
7775
appsClient, err := appsclient.NewForConfig(config)
7876
if err != nil {
79-
return nil, false, err
77+
return nil, err
8078
}
8179
oauthClient, err := oauthclient.NewForConfig(config)
8280
if err != nil {
83-
return nil, false, err
81+
return nil, err
8482
}
8583
oauthorizationClient, err := oauthorizationclient.NewForConfig(config)
8684
if err != nil {
87-
return nil, false, err
85+
return nil, err
8886
}
8987
securityClient, err := securityclient.NewForConfig(config)
9088
if err != nil {
91-
return nil, false, err
89+
return nil, err
9290
}
9391

9492
diagnostics := []types.Diagnostic{}
@@ -116,64 +114,68 @@ func (o DiagnosticsOptions) buildClusterDiagnostics(rawConfig *clientcmdapi.Conf
116114
case clustdiags.RouteCertificateValidationName:
117115
d = &clustdiags.RouteCertificateValidation{SARClient: kclusterClient.Authorization(), RESTConfig: config}
118116
default:
119-
return nil, false, fmt.Errorf("unknown diagnostic: %v", diagnosticName)
117+
return nil, fmt.Errorf("unknown diagnostic: %v", diagnosticName)
120118
}
121119
diagnostics = append(diagnostics, d)
122120
}
123-
return diagnostics, true, nil
121+
return diagnostics, nil
124122
}
125123

126124
// attempts to find which context in the config might be a cluster-admin for the server in the current context.
127-
func (o DiagnosticsOptions) findClusterClients(rawConfig *clientcmdapi.Config) (*rest.Config, kclientset.Interface, bool, string, error) {
125+
// returns config for the context chosen, kclusterClient for same, serverUrl of same, and any fatal error
126+
func (o DiagnosticsOptions) findClusterClients(rawConfig *clientcmdapi.Config) (*rest.Config, kclientset.Interface, string, error) {
128127
if o.ClientClusterContext != "" { // user has specified cluster context to use
129-
if context, exists := rawConfig.Contexts[o.ClientClusterContext]; !exists {
128+
context, exists := rawConfig.Contexts[o.ClientClusterContext]
129+
if !exists {
130130
configErr := fmt.Errorf("Specified '%s' as cluster-admin context, but it was not found in your client configuration.", o.ClientClusterContext)
131-
o.Logger.Error("CED1003", configErr.Error())
132-
return nil, nil, false, "", configErr
133-
} else if config, kube, found, serverUrl, err := o.makeClusterClients(rawConfig, o.ClientClusterContext, context); found {
134-
return config, kube, true, serverUrl, err
135-
} else {
136-
return nil, nil, false, "", err
131+
o.Logger().Error("CED1003", configErr.Error())
132+
return nil, nil, "", configErr
137133
}
134+
config, kube, serverUrl, err := o.makeClusterClients(rawConfig, o.ClientClusterContext, context)
135+
if err != nil || config == nil {
136+
return nil, nil, "", err
137+
}
138+
return config, kube, serverUrl, nil
138139
}
139140
currentContext, exists := rawConfig.Contexts[rawConfig.CurrentContext]
140141
if !exists { // config specified cluster admin context that doesn't exist; complain and quit
141142
configErr := fmt.Errorf("Current context '%s' not found in client configuration; will not attempt cluster diagnostics.", rawConfig.CurrentContext)
142-
o.Logger.Error("CED1004", configErr.Error())
143-
return nil, nil, false, "", configErr
143+
o.Logger().Error("CED1004", configErr.Error())
144+
return nil, nil, "", configErr
144145
}
145146
// check if current context is already cluster admin
146-
if config, kube, found, serverUrl, err := o.makeClusterClients(rawConfig, rawConfig.CurrentContext, currentContext); found {
147-
return config, kube, true, serverUrl, err
147+
config, kube, serverUrl, err := o.makeClusterClients(rawConfig, rawConfig.CurrentContext, currentContext)
148+
if err == nil && config != nil {
149+
return config, kube, serverUrl, nil
148150
}
149151
// otherwise, for convenience, search for a context with the same server but with the system:admin user
150152
for name, context := range rawConfig.Contexts {
151153
if context.Cluster == currentContext.Cluster && name != rawConfig.CurrentContext && strings.HasPrefix(context.AuthInfo, "system:admin/") {
152-
if config, kube, found, serverUrl, err := o.makeClusterClients(rawConfig, name, context); found {
153-
return config, kube, true, serverUrl, err
154-
} else {
155-
return nil, nil, false, "", err // don't try more than one such context, they'll probably fail the same
154+
config, kube, serverUrl, err := o.makeClusterClients(rawConfig, name, context)
155+
if err != nil || config == nil {
156+
break // don't try more than one such context, they'll probably fail the same
156157
}
158+
return config, kube, serverUrl, nil
157159
}
158160
}
159-
return nil, nil, false, "", nil
161+
return nil, nil, "", nil
160162
}
161163

162164
// makes the client from the specified context and determines whether it is a cluster-admin.
163-
func (o DiagnosticsOptions) makeClusterClients(rawConfig *clientcmdapi.Config, contextName string, context *clientcmdapi.Context) (*rest.Config, kclientset.Interface, bool, string, error) {
165+
func (o DiagnosticsOptions) makeClusterClients(rawConfig *clientcmdapi.Config, contextName string, context *clientcmdapi.Context) (*rest.Config, kclientset.Interface, string, error) {
164166
overrides := &clientcmd.ConfigOverrides{Context: *context}
165167
clientConfig := clientcmd.NewDefaultClientConfig(*rawConfig, overrides)
166168
serverUrl := rawConfig.Clusters[context.Cluster].Server
167169
factory := osclientcmd.NewFactory(clientConfig)
168170
config, err := factory.ClientConfig()
169171
if err != nil {
170-
o.Logger.Debug("CED1006", fmt.Sprintf("Error creating client for context '%s':\n%v", contextName, err))
171-
return nil, nil, false, "", nil
172+
o.Logger().Debug("CED1006", fmt.Sprintf("Error creating client for context '%s':\n%v", contextName, err))
173+
return nil, nil, "", nil
172174
}
173-
o.Logger.Debug("CED1005", fmt.Sprintf("Checking if context is cluster-admin: '%s'", contextName))
175+
o.Logger().Debug("CED1005", fmt.Sprintf("Checking if context is cluster-admin: '%s'", contextName))
174176
if kubeClient, err := factory.ClientSet(); err != nil {
175-
o.Logger.Debug("CED1006", fmt.Sprintf("Error creating client for context '%s':\n%v", contextName, err))
176-
return nil, nil, false, "", nil
177+
o.Logger().Debug("CED1006", fmt.Sprintf("Error creating client for context '%s':\n%v", contextName, err))
178+
return nil, nil, "", nil
177179
} else {
178180
subjectAccessReview := &authorization.SelfSubjectAccessReview{
179181
Spec: authorization.SelfSubjectAccessReviewSpec{
@@ -187,17 +189,17 @@ func (o DiagnosticsOptions) makeClusterClients(rawConfig *clientcmdapi.Config, c
187189
}
188190
if resp, err := kubeClient.Authorization().SelfSubjectAccessReviews().Create(subjectAccessReview); err != nil {
189191
if regexp.MustCompile(`User "[\w:]+" cannot create \w+ at the cluster scope`).MatchString(err.Error()) {
190-
o.Logger.Debug("CED1007", fmt.Sprintf("Context '%s' does not have cluster-admin access:\n%v", contextName, err))
191-
return nil, nil, false, "", nil
192+
o.Logger().Debug("CED1007", fmt.Sprintf("Context '%s' does not have cluster-admin access:\n%v", contextName, err))
193+
return nil, nil, "", nil
192194
} else {
193-
o.Logger.Error("CED1008", fmt.Sprintf("Unknown error testing cluster-admin access for context '%s':\n%v", contextName, err))
194-
return nil, nil, false, "", err
195+
o.Logger().Error("CED1008", fmt.Sprintf("Unknown error testing cluster-admin access for context '%s':\n%v", contextName, err))
196+
return nil, nil, "", err
195197
}
196198
} else if resp.Status.Allowed {
197-
o.Logger.Info("CED1009", fmt.Sprintf("Using context for cluster-admin access: '%s'", contextName))
198-
return config, kubeClient, true, serverUrl, nil
199+
o.Logger().Info("CED1009", fmt.Sprintf("Using context for cluster-admin access: '%s'", contextName))
200+
return config, kubeClient, serverUrl, nil
199201
}
200202
}
201-
o.Logger.Debug("CED1010", fmt.Sprintf("Context does not have cluster-admin access: '%s'", contextName))
202-
return nil, nil, false, "", nil
203+
o.Logger().Debug("CED1010", fmt.Sprintf("Context does not have cluster-admin access: '%s'", contextName))
204+
return nil, nil, "", nil
203205
}

pkg/oc/admin/diagnostics/config.go

+119-16
Original file line numberDiff line numberDiff line change
@@ -2,28 +2,17 @@ package diagnostics
22

33
import (
44
"errors"
5+
"fmt"
6+
"io/ioutil"
7+
"os"
58

9+
"k8s.io/client-go/tools/clientcmd"
610
clientcmdapi "k8s.io/client-go/tools/clientcmd/api"
711

812
"github.com/openshift/origin/pkg/client/config"
9-
clientdiagnostics "github.com/openshift/origin/pkg/oc/admin/diagnostics/diagnostics/client"
10-
"github.com/openshift/origin/pkg/oc/admin/diagnostics/diagnostics/types"
13+
"github.com/openshift/origin/pkg/oc/admin/diagnostics/diagnostics/util"
1114
)
1215

13-
// determine if we even have a client config
14-
func (o DiagnosticsOptions) detectClientConfig() (bool, bool, []types.DiagnosticError, []types.DiagnosticError) {
15-
if o.ClientFlags == nil {
16-
return false, false, []types.DiagnosticError{}, []types.DiagnosticError{}
17-
}
18-
diagnostic := &clientdiagnostics.ConfigLoading{ConfFlagName: config.OpenShiftConfigFlagName, ClientFlags: o.ClientFlags}
19-
o.Logger.Notice("CED2011", "Determining if client configuration exists for client/cluster diagnostics")
20-
result := diagnostic.Check()
21-
for _, entry := range result.Logs() {
22-
o.Logger.LogEntry(entry)
23-
}
24-
return true, diagnostic.SuccessfulLoad(), result.Warnings(), result.Errors()
25-
}
26-
2716
// use the base factory to return a raw config (not specific to a context)
2817
func (o DiagnosticsOptions) buildRawConfig() (*clientcmdapi.Config, error) {
2918
kubeConfig, configErr := o.Factory.OpenShiftClientConfig().RawConfig()
@@ -35,3 +24,117 @@ func (o DiagnosticsOptions) buildRawConfig() (*clientcmdapi.Config, error) {
3524
}
3625
return &kubeConfig, nil
3726
}
27+
28+
// determine if we even have a client config
29+
func (o DiagnosticsOptions) detectClientConfig() (expected bool, detected bool) {
30+
if o.ClientFlags == nil {
31+
// options for client not provided, so it must not be expected.
32+
return false, false
33+
}
34+
o.Logger().Notice("CED2011", "Determining if client configuration exists for client/cluster diagnostics")
35+
confFlagName := config.OpenShiftConfigFlagName
36+
confFlagValue := o.ClientFlags.Lookup(confFlagName).Value.String()
37+
successfulLoad := false
38+
39+
var foundPath string
40+
rules := config.NewOpenShiftClientConfigLoadingRules()
41+
paths := append([]string{confFlagValue}, rules.Precedence...)
42+
for index, path := range paths {
43+
errmsg := ""
44+
switch index {
45+
case 0:
46+
errmsg = fmt.Sprintf("--%s specified that client config should be at %s\n", confFlagName, path)
47+
case len(paths) - 1: // config in ~/.kube
48+
// no error message indicated if it is not there... user didn't say it would be
49+
default: // can be multiple paths from the env var in theory; all cases should go here
50+
if len(os.Getenv(config.OpenShiftConfigPathEnvVar)) != 0 {
51+
errmsg = fmt.Sprintf("Env var %s specified that client config could be at %s\n", config.OpenShiftConfigPathEnvVar, path)
52+
}
53+
}
54+
55+
if o.canOpenConfigFile(path, errmsg) && foundPath == "" {
56+
successfulLoad = true
57+
foundPath = path
58+
}
59+
}
60+
if foundPath != "" {
61+
if confFlagValue != "" && confFlagValue != foundPath {
62+
// found config but not where --config said
63+
o.Logger().Error("DCli1001", fmt.Sprintf(`
64+
The client configuration file was not found where the --%s flag indicated:
65+
%s
66+
A config file was found at the following location:
67+
%s
68+
If you wish to use this file for client configuration, you can specify it
69+
with the --%[1]s flag, or just not specify the flag.
70+
`, confFlagName, confFlagValue, foundPath))
71+
}
72+
} else { // not found, check for master-generated ones to recommend
73+
if confFlagValue != "" {
74+
o.Logger().Error("DCli1002", fmt.Sprintf("Did not find config file where --%s=%s indicated", confFlagName, confFlagValue))
75+
}
76+
adminWarningF := `
77+
No client config file was available; however, one exists at
78+
%[2]s
79+
which may have been generated automatically by the master.
80+
If you want to use this config, you should copy it to the
81+
standard location (%[3]s),
82+
or you can set the environment variable %[1]s:
83+
export %[1]s=%[2]s
84+
If not, obtain a config file and place it in the standard
85+
location for use by the client and diagnostics.
86+
`
87+
// look for it in auto-generated locations when not found properly
88+
for _, path := range util.AdminKubeConfigPaths {
89+
msg := fmt.Sprintf("Looking for a possible client config at %s\n", path)
90+
if o.canOpenConfigFile(path, msg) {
91+
o.Logger().Warn("DCli1003", fmt.Sprintf(adminWarningF, config.OpenShiftConfigPathEnvVar, path, config.RecommendedHomeFile))
92+
break
93+
}
94+
}
95+
}
96+
return true, successfulLoad
97+
}
98+
99+
// ----------------------------------------------------------
100+
// Attempt to open file at path as client config
101+
// If there is a problem and errmsg is set, log an error
102+
func (o DiagnosticsOptions) canOpenConfigFile(path string, errmsg string) bool {
103+
var file *os.File
104+
var err error
105+
if path == "" { // empty param/envvar
106+
return false
107+
} else if file, err = os.Open(path); err == nil {
108+
o.Logger().Debug("DCli1004", fmt.Sprintf("Reading client config at %s", path))
109+
} else if errmsg == "" {
110+
o.Logger().Debug("DCli1005", fmt.Sprintf("Could not read client config at %s:\n%#v", path, err))
111+
} else if os.IsNotExist(err) {
112+
o.Logger().Debug("DCli1006", errmsg+"but that file does not exist.")
113+
} else if os.IsPermission(err) {
114+
o.Logger().Error("DCli1007", errmsg+"but lack permission to read that file.")
115+
} else {
116+
o.Logger().Error("DCli1008", fmt.Sprintf("%sbut there was an error opening it:\n%#v", errmsg, err))
117+
}
118+
if file != nil { // it is open for reading
119+
defer file.Close()
120+
if buffer, err := ioutil.ReadAll(file); err != nil {
121+
o.Logger().Error("DCli1009", fmt.Sprintf("Unexpected error while reading client config file (%s): %v", path, err))
122+
} else if _, err := clientcmd.Load(buffer); err != nil {
123+
o.Logger().Error("DCli1010", fmt.Sprintf(`
124+
Error reading YAML from client config file (%s):
125+
%v
126+
This file may have been truncated or mis-edited.
127+
Please fix, remove, or obtain a new client config`, file.Name(), err))
128+
} else {
129+
o.Logger().Info("DCli1011", fmt.Sprintf("Successfully read a client config file at '%s'", path))
130+
/* Note, we're not going to use this config file directly.
131+
* Instead, we'll defer to the openshift client code to assimilate
132+
* flags, env vars, and the potential hierarchy of config files
133+
* into an actual configuration that the client uses.
134+
* However, for diagnostic purposes, record the files we find.
135+
*/
136+
return true
137+
}
138+
}
139+
return false
140+
}

0 commit comments

Comments
 (0)