Skip to content

Commit 537e812

Browse files
committed
Simplify the configuration loading
Moves the options to flags. Also moves the env variables to the main command, so there are no sideeffects when running the commands. Signed-off-by: Petr Kotas <pkotas@redhat.com>
1 parent f842c77 commit 537e812

File tree

7 files changed

+304
-109
lines changed

7 files changed

+304
-109
lines changed

cadctl/cmd/investigate/investigate.go

Lines changed: 34 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -19,10 +19,10 @@ package investigate
1919
import (
2020
"fmt"
2121
"os"
22-
"path/filepath"
2322
"strings"
2423

2524
cmv1 "github.com/openshift-online/ocm-sdk-go/clustersmgmt/v1"
25+
"github.com/openshift/configuration-anomaly-detection/cadctl/config"
2626
investigations "github.com/openshift/configuration-anomaly-detection/pkg/investigations"
2727
"github.com/openshift/configuration-anomaly-detection/pkg/investigations/ccam"
2828
investigation "github.com/openshift/configuration-anomaly-detection/pkg/investigations/investigation"
@@ -31,41 +31,49 @@ import (
3131
"github.com/openshift/configuration-anomaly-detection/pkg/metrics"
3232
ocm "github.com/openshift/configuration-anomaly-detection/pkg/ocm"
3333
"github.com/openshift/configuration-anomaly-detection/pkg/pagerduty"
34+
"go.uber.org/zap"
3435

3536
"github.com/spf13/cobra"
3637
)
3738

39+
var c config.Config
40+
3841
// InvestigateCmd represents the entry point for alert investigation
3942
var InvestigateCmd = &cobra.Command{
4043
Use: "investigate",
4144
SilenceUsage: true,
4245
Short: "Filter for and investigate supported alerts",
43-
RunE: run,
46+
PreRunE: func(cmd *cobra.Command, _ []string) error {
47+
var err error
48+
49+
c, err = config.BuildConfig(cmd)
50+
if err != nil {
51+
return fmt.Errorf("failed to build config: %w", err)
52+
}
53+
54+
// Initialize the logger here because it depends on user input
55+
logging.RawLogger = logging.InitLogger(c.LogLevel, "")
56+
57+
return nil
58+
},
59+
RunE: run,
4460
}
4561

46-
var (
47-
logLevelFlag = ""
48-
payloadPath = "./payload.json"
49-
)
62+
var payloadPath = "./payload.json"
5063

5164
const pagerdutyTitlePrefix = "[CAD Investigated]"
5265

5366
func init() {
5467
InvestigateCmd.Flags().StringVarP(&payloadPath, "payload-path", "p", payloadPath, "the path to the payload")
55-
InvestigateCmd.Flags().StringVarP(&logging.LogLevelString, "log-level", "l", "", "the log level [debug,info,warn,error,fatal], default = info")
5668

5769
err := InvestigateCmd.MarkFlagRequired("payload-path")
5870
if err != nil {
5971
logging.Warn("Could not mark flag 'payload-path' as required")
6072
}
6173
}
6274

63-
func run(cmd *cobra.Command, _ []string) error {
75+
func run(_ *cobra.Command, _ []string) error {
6476
// early init of logger for logs before clusterID is known
65-
if cmd.Flags().Changed("log-level") {
66-
flagValue, _ := cmd.Flags().GetString("log-level")
67-
logging.RawLogger = logging.InitLogger(flagValue, "")
68-
}
6977
payload, err := os.ReadFile(payloadPath)
7078
if err != nil {
7179
return fmt.Errorf("failed to read webhook payload: %w", err)
@@ -87,8 +95,7 @@ func run(cmd *cobra.Command, _ []string) error {
8795
}
8896
}()
8997

90-
_, cadExperimentalEnabled := os.LookupEnv("CAD_EXPERIMENTAL_ENABLED")
91-
alertInvestigation := investigations.GetInvestigation(pdClient.GetTitle(), cadExperimentalEnabled)
98+
alertInvestigation := investigations.GetInvestigation(pdClient.GetTitle(), c.Experimental)
9299

93100
// Escalate all unsupported alerts
94101
if alertInvestigation == nil {
@@ -109,8 +116,17 @@ func run(cmd *cobra.Command, _ []string) error {
109116
return err
110117
}
111118

112-
ocmClient, err := GetOCMClient()
113-
if err != nil {
119+
var ocmClient *ocm.SdkClient
120+
var ocmErr error
121+
// Initialize the OCM client
122+
// If the config file is set, use it to initialize the OCM client
123+
// use config file only when developing
124+
if c.ConfigFile != "" {
125+
ocmClient, ocmErr = ocm.NewFromConfig(c.ConfigFile)
126+
} else {
127+
ocmClient, ocmErr = ocm.NewFromClientKeyPair(c.CadOcmURL, c.CadOcmClientID, c.CadOcmClientSecret)
128+
}
129+
if ocmErr != nil {
114130
return fmt.Errorf("could not initialize ocm client: %w", err)
115131
}
116132

@@ -122,13 +138,8 @@ func run(cmd *cobra.Command, _ []string) error {
122138
// For installing clusters, externalID can be empty.
123139
internalClusterID := cluster.ID()
124140

125-
// re-initialize logger for the internal-cluster-id context
126-
// if log-level flag is set, take priority over env + default
127-
if cmd.Flags().Changed("log-level") {
128-
logging.RawLogger = logging.InitLogger(logLevelFlag, internalClusterID)
129-
} else {
130-
logging.RawLogger = logging.InitLogger(logging.LogLevelString, internalClusterID)
131-
}
141+
// add the internal-cluster-id context to the logger
142+
logging.RawLogger = logging.RawLogger.With(zap.String("cluster_id", internalClusterID))
132143

133144
requiresInvestigation, err := clusterRequiresInvestigation(cluster, pdClient, ocmClient)
134145
if err != nil || !requiresInvestigation {
@@ -184,22 +195,6 @@ func handleCADFailure(err error, resources *investigation.Resources, pdClient *p
184195
}
185196
}
186197

187-
// GetOCMClient will retrieve the OcmClient from the 'ocm' package
188-
func GetOCMClient() (*ocm.SdkClient, error) {
189-
cadOcmFilePath := os.Getenv("CAD_OCM_FILE_PATH")
190-
191-
_, err := os.Stat(cadOcmFilePath)
192-
if os.IsNotExist(err) {
193-
configDir, err := os.UserConfigDir()
194-
if err != nil {
195-
return nil, err
196-
}
197-
cadOcmFilePath = filepath.Join(configDir, "/ocm/ocm.json")
198-
}
199-
200-
return ocm.New(cadOcmFilePath)
201-
}
202-
203198
// Checks pre-requisites for a cluster investigation:
204199
// - the cluster's state is supported by CAD for an investigation (= not uninstalling)
205200
// - the cloud provider is supported by CAD (cluster is AWS)

cadctl/cmd/root.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,8 @@ limitations under the License.
1717
package cmd
1818

1919
import (
20+
21+
// "github.com/openshift-online/ocm-cli/pkg/config"
2022
investigate "github.com/openshift/configuration-anomaly-detection/cadctl/cmd/investigate"
2123
"github.com/openshift/configuration-anomaly-detection/pkg/logging"
2224
"github.com/openshift/configuration-anomaly-detection/pkg/metrics"
@@ -41,4 +43,6 @@ func Execute() {
4143

4244
func init() {
4345
rootCmd.AddCommand(investigate.InvestigateCmd)
46+
rootCmd.PersistentFlags().StringP("ocmconfig", "c", "", "Path to the OCM config file to use for CLI requests. When not set the OCM_CONFIG environment variable will be used. If that is not set, the default OCM config locations will be file will be used.")
47+
rootCmd.PersistentFlags().StringP("loglevel", "l", "info", "Log level to use. One of: debug, info, warn, error, fatal, panic")
4448
}

cadctl/config/config.go

Lines changed: 182 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,182 @@
1+
/*
2+
Copyright © 2025 Red Hat, Inc.
3+
4+
Licensed under the Apache License, Version 2.0 (the "License");
5+
you may not use this file except in compliance with the License.
6+
You may obtain a copy of the License at
7+
8+
http://www.apache.org/licenses/LICENSE-2.0
9+
10+
Unless required by applicable law or agreed to in writing, software
11+
distributed under the License is distributed on an "AS IS" BASIS,
12+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
See the License for the specific language governing permissions and
14+
limitations under the License.
15+
*/
16+
17+
package config
18+
19+
import (
20+
"fmt"
21+
"os"
22+
23+
"github.com/spf13/cobra"
24+
25+
"github.com/openshift/configuration-anomaly-detection/pkg/ocm"
26+
)
27+
28+
type Config struct {
29+
ConfigFile string
30+
LogLevel string
31+
CadOcmClientID string
32+
CadOcmClientSecret string
33+
CadOcmURL string
34+
Experimental bool
35+
}
36+
37+
func BuildConfig(cmd *cobra.Command) (Config, error) {
38+
// Get the config file path from command line flags or environment variables
39+
var c Config
40+
var err error
41+
var ocmConfigFile string
42+
43+
_, err = c.getLogLevel(cmd)
44+
if err != nil {
45+
return c, fmt.Errorf("failed to get log level: %w", err)
46+
}
47+
48+
if ocmConfigFile, err = c.getConfigFile(cmd); err != nil {
49+
return c, fmt.Errorf("failed to get config file: %w", err)
50+
}
51+
52+
if ocmConfigFile == "" {
53+
// loads the configuration unless config file is present
54+
// config should be used only when developing
55+
if _, err = c.getCadOcmClientID(); err != nil {
56+
return c, fmt.Errorf("failed to get cadOcmClientID: %w", err)
57+
}
58+
if _, err = c.getCadOcmClientSecret(); err != nil {
59+
return c, fmt.Errorf("failed to get cadOcmClientSecret: %w", err)
60+
}
61+
if _, err = c.getCadOcmURL(); err != nil {
62+
return c, fmt.Errorf("failed to get cadOcmURL: %w", err)
63+
}
64+
}
65+
66+
if _, err = c.getExperimental(); err != nil {
67+
return c, fmt.Errorf("failed to get experimental flag: %w", err)
68+
}
69+
70+
return c, err
71+
}
72+
73+
// getConfigFile retrieves the OCM config file path from the command line flags or environment variables.
74+
// It checks the following in order:
75+
// 1. Command line flag --ocmconfig
76+
// 2. Environment variable OCM_CONFIG
77+
// 3. Default OCM config locations
78+
// If the file is not found at any of these locations, the config path remains empty.
79+
// Missing config is a valid state, as the user may not have a config file.
80+
func (c *Config) getConfigFile(cmd *cobra.Command) (string, error) {
81+
ocmConfigPath, err := cmd.Flags().GetString("ocmconfig")
82+
if err != nil {
83+
return "", fmt.Errorf("failed to get ocmconfig flag: %w", err)
84+
}
85+
86+
if ocmConfigPath != "" {
87+
if _, err := os.Stat(ocmConfigPath); err != nil {
88+
return "", fmt.Errorf("ocmconfig file not found at %s: %w", ocmConfigPath, err)
89+
}
90+
91+
c.ConfigFile = ocmConfigPath
92+
return ocmConfigPath, nil
93+
}
94+
95+
ocmConfigPath, exists := os.LookupEnv("OCM_CONFIG")
96+
if exists {
97+
if _, err := os.Stat(ocmConfigPath); err != nil {
98+
return "", fmt.Errorf("ocmconfig file not found at %s: %w", ocmConfigPath, err)
99+
}
100+
101+
c.ConfigFile = ocmConfigPath
102+
return ocmConfigPath, nil
103+
}
104+
105+
ocmConfigPath, err = ocm.Location()
106+
if err != nil {
107+
// Location() checks for file existence
108+
return "", fmt.Errorf("failed to get ocmconfig file location: %w", err)
109+
}
110+
111+
c.ConfigFile = ocmConfigPath
112+
return ocmConfigPath, nil
113+
}
114+
115+
func (c *Config) getLogLevel(cmd *cobra.Command) (string, error) {
116+
logLevel, err := cmd.Flags().GetString("loglevel")
117+
if err != nil {
118+
return "", fmt.Errorf("failed to get loglevel flag: %w", err)
119+
}
120+
if logLevel != "" {
121+
c.LogLevel = logLevel
122+
return logLevel, nil
123+
}
124+
125+
if envLogLevel, exists := os.LookupEnv("LOG_LEVEL"); exists {
126+
c.LogLevel = envLogLevel
127+
return logLevel, nil
128+
}
129+
130+
c.LogLevel = "info"
131+
return logLevel, nil
132+
}
133+
134+
// getExperimental retrieves the experimental flag passed as a command line argument.
135+
// If not set, it checks the CAD_EXPERIMENTAL_ENABLED environment variable.
136+
// Defaults to false if neither is set.
137+
func (c *Config) getExperimental() (bool, error) {
138+
_, exist := os.LookupEnv("CAD_EXPERIMENTAL_ENABLED")
139+
if exist {
140+
c.Experimental = true
141+
return true, nil
142+
}
143+
144+
c.Experimental = false
145+
return false, nil
146+
}
147+
148+
// getCadOcmClientID retrieves the OCM cliend ID passed as
149+
// an CAD_OCM_CLIENT_ID environment variable.
150+
func (c *Config) getCadOcmClientID() (string, error) {
151+
cadOcmClientID, exist := os.LookupEnv("CAD_OCM_CLIENT_ID")
152+
if !exist {
153+
return "", fmt.Errorf("client-id flag or CAD_OCM_CLIENT_ID environment variable must be set")
154+
}
155+
156+
c.CadOcmClientID = cadOcmClientID
157+
return cadOcmClientID, nil
158+
}
159+
160+
// getCadOcmClientSecret retrieves the OCM client secret passed as
161+
// an environment CAD_OCM_CLIENT_SECRET variable.
162+
func (c *Config) getCadOcmClientSecret() (string, error) {
163+
cadOcmClientSecret, exist := os.LookupEnv("CAD_OCM_CLIENT_SECRET")
164+
if !exist {
165+
return "", fmt.Errorf("client-secret flag or CAD_OCM_CLIENT_SECRET environment variable must be set")
166+
}
167+
168+
c.CadOcmClientSecret = cadOcmClientSecret
169+
return cadOcmClientSecret, nil
170+
}
171+
172+
// getCadOcmURL retrieves the OCM URL passed as an environment
173+
// CAD_OCM_URL variable.
174+
func (c *Config) getCadOcmURL() (string, error) {
175+
cadOcmURL, exist := os.LookupEnv("CAD_OCM_URL")
176+
if !exist {
177+
return "", fmt.Errorf("ocm-url flag or CAD_OCM_URL environment variable must be set")
178+
}
179+
180+
c.CadOcmURL = cadOcmURL
181+
return cadOcmURL, nil
182+
}

interceptor/main.go

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import (
1212

1313
"github.com/openshift/configuration-anomaly-detection/interceptor/pkg/interceptor"
1414
"github.com/openshift/configuration-anomaly-detection/pkg/logging"
15+
zap "go.uber.org/zap"
1516
"knative.dev/pkg/signals"
1617
)
1718

@@ -22,7 +23,7 @@ const (
2223
idleTimeout = 60 * time.Second
2324
)
2425

25-
var logger = logging.InitLogger(logging.LogLevelString, "")
26+
var logger *zap.SugaredLogger
2627

2728
func main() {
2829
// set up signals so we handle the first shutdown signal gracefully
@@ -71,6 +72,17 @@ func main() {
7172
logger.Infof("Server exiting")
7273
}
7374

75+
func init() {
76+
// read log level from environment variable
77+
logLevel := os.Getenv("LOG_LEVEL")
78+
if logLevel != "" {
79+
logLevel = "info"
80+
}
81+
82+
// set up logging
83+
logger = logging.InitLogger(logLevel, logLevel)
84+
}
85+
7486
func readinessHandler(w http.ResponseWriter, r *http.Request) {
7587
w.WriteHeader(http.StatusOK)
7688
}

pkg/logging/logging.go

Lines changed: 1 addition & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -10,10 +10,8 @@ import (
1010
"go.uber.org/zap/zapcore"
1111
)
1212

13-
var LogLevelString = getLogLevel()
14-
1513
// RawLogger is the raw global logger object used for calls wrapped by the logging package
16-
var RawLogger = InitLogger(LogLevelString, "")
14+
var RawLogger = InitLogger("info", "")
1715

1816
// InitLogger initializes a cluster-id specific child logger
1917
func InitLogger(logLevelString string, clusterID string) *zap.SugaredLogger {
@@ -94,11 +92,3 @@ func Errorf(template string, args ...interface{}) {
9492
func Fatalf(template string, args ...interface{}) {
9593
RawLogger.Fatalf(template, args...)
9694
}
97-
98-
// getLogLevel returns the log level from the environment variable LOG_LEVEL
99-
func getLogLevel() string {
100-
if envLogLevel, exists := os.LookupEnv("LOG_LEVEL"); exists {
101-
return envLogLevel
102-
}
103-
return "info"
104-
}

0 commit comments

Comments
 (0)