internal/configuration/configuration.go (253 lines of code) (raw):
/*
Copyright 2023 Google LLC
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
https://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 configuration contains functionalities for agent configuration operations.
package configuration
import (
"fmt"
"os"
"path/filepath"
"google.golang.org/protobuf/encoding/protojson"
configpb "github.com/GoogleCloudPlatform/sql-server-agent/protos/sqlserveragentconfig"
"github.com/GoogleCloudPlatform/workloadagentplatform/sharedlibraries/log"
)
var (
defaultConfig = &configpb.Configuration{
CollectionConfiguration: &configpb.CollectionConfiguration{
CollectGuestOsMetrics: true,
GuestOsMetricsCollectionIntervalInSeconds: 3600,
CollectSqlMetrics: true,
SqlMetricsCollectionIntervalInSeconds: 3600,
},
CredentialConfiguration: []*configpb.CredentialConfiguration{
&configpb.CredentialConfiguration{
SqlConfigurations: []*configpb.CredentialConfiguration_SqlCredentials{
&configpb.CredentialConfiguration_SqlCredentials{
Host: ".",
UserName: "",
SecretName: "",
PortNumber: 1433,
},
},
GuestConfigurations: &configpb.CredentialConfiguration_LocalCollection{
LocalCollection: true,
},
},
},
LogLevel: "INFO",
LogToCloud: true,
CollectionTimeoutSeconds: 10,
MaxRetries: 5,
RetryIntervalInSeconds: 3600,
}
)
// SQLConfig .
type SQLConfig struct {
Host string
Username string
SecretName string
PortNumber int32
}
// GuestConfig .
type GuestConfig struct {
ServerName string
GuestUserName string
GuestSecretName string
GuestPortNumber int32
LinuxRemote bool
LinuxSSHPrivateKeyPath string
}
// LoadConfiguration loads configuration from config file.
// Returns default configurations with error if reading configuration file has an error.
// Returns nil with error if the configuration file is in invalid format.
func LoadConfiguration(p string) (*configpb.Configuration, error) {
// Read config file from file system.
b, err := os.ReadFile(filepath.Join(filepath.Dir(p), "configuration.json"))
if err != nil {
return defaultConfig, fmt.Errorf("failed to load the configuration file. filepath: %v, error: %v", p, err)
}
cfg := configpb.Configuration{}
if err := protojson.Unmarshal(b, &cfg); err != nil {
return nil, err
}
return validateConfigValues(&cfg), nil
}
// SQLConfigFromCredential returns config for SQL collection.
func SQLConfigFromCredential(creCfg *configpb.CredentialConfiguration) []*SQLConfig {
var sqlConfigs []*SQLConfig
for _, sqlCfg := range creCfg.GetSqlConfigurations() {
sqlConfigs = append(sqlConfigs, &SQLConfig{
Host: sqlCfg.GetHost(),
Username: sqlCfg.GetUserName(),
SecretName: sqlCfg.GetSecretName(),
PortNumber: sqlCfg.GetPortNumber(),
})
}
return sqlConfigs
}
// GuestConfigFromCredential returns config for guest OS collection.
func GuestConfigFromCredential(creCfg *configpb.CredentialConfiguration) *GuestConfig {
switch creCfg.GuestConfigurations.(type) {
case *configpb.CredentialConfiguration_RemoteWin:
return &GuestConfig{
ServerName: creCfg.GetRemoteWin().GetServerName(),
GuestUserName: creCfg.GetRemoteWin().GetGuestUserName(),
GuestSecretName: creCfg.GetRemoteWin().GetGuestSecretName(),
}
case *configpb.CredentialConfiguration_RemoteLinux:
return &GuestConfig{
ServerName: creCfg.GetRemoteLinux().GetServerName(),
GuestUserName: creCfg.GetRemoteLinux().GetGuestUserName(),
GuestPortNumber: creCfg.GetRemoteLinux().GetGuestPortNumber(),
LinuxRemote: true,
LinuxSSHPrivateKeyPath: creCfg.GetRemoteLinux().GetLinuxSshPrivateKeyPath(),
}
}
return &GuestConfig{}
}
// ValidateConfigValues verifies if the numeric values from the config file are valid.
// If not, the default value will be set to the field.
func validateConfigValues(config *configpb.Configuration) *configpb.Configuration {
fields := []struct {
name string
defaultValue int32
minValue int32
valueFromConfig int32
setDefaultValue func(int32)
}{
{
name: "collection_timeout_seconds",
defaultValue: 10,
minValue: 1,
valueFromConfig: config.GetCollectionTimeoutSeconds(),
setDefaultValue: func(defaultValue int32) {
config.CollectionTimeoutSeconds = defaultValue
},
},
{
name: "max_retries",
defaultValue: 3,
minValue: -1,
valueFromConfig: config.GetMaxRetries(),
setDefaultValue: func(defaultValue int32) {
config.MaxRetries = defaultValue
},
},
{
name: "retry_interval_in_seconds",
defaultValue: 3600,
minValue: 1,
valueFromConfig: config.GetRetryIntervalInSeconds(),
setDefaultValue: func(defaultValue int32) {
config.RetryIntervalInSeconds = defaultValue
},
},
{
name: "guest_os_metrics_collection_interval_in_seconds",
defaultValue: 3600,
minValue: 1,
valueFromConfig: config.GetCollectionConfiguration().GetGuestOsMetricsCollectionIntervalInSeconds(),
setDefaultValue: func(defaultValue int32) {
config.GetCollectionConfiguration().GuestOsMetricsCollectionIntervalInSeconds = defaultValue
},
},
{
name: "sql_metrics_collection_interval_in_seconds",
defaultValue: 3600,
minValue: 1,
valueFromConfig: config.GetCollectionConfiguration().GetSqlMetricsCollectionIntervalInSeconds(),
setDefaultValue: func(defaultValue int32) {
config.GetCollectionConfiguration().SqlMetricsCollectionIntervalInSeconds = defaultValue
},
},
}
for _, f := range fields {
if f.valueFromConfig < f.minValue {
log.Logger.Warnf("Invalid value for field %v. Using the default value %v", f.name, f.defaultValue)
f.setDefaultValue(f.defaultValue)
}
}
return config
}
// ValidateCredCfgSQL validates if the configuration file is valid for SQL collection.
// Each CredentialConfiguration must provide valid "user_name", "secret_name" and "port_number".
// If remote collection is enabled, the following fields must be provided:
//
// "host", "instance_id", "instance_name"
func ValidateCredCfgSQL(remote, windows bool, sqlCfg *SQLConfig, guestCfg *GuestConfig, instanceID, instanceName string) error {
errMsg := "invalid value for"
hasError := false
if sqlCfg.Username == "" {
errMsg = errMsg + ` "user_name"`
hasError = true
}
if sqlCfg.SecretName == "" {
errMsg = errMsg + ` "secret_name"`
hasError = true
}
if sqlCfg.PortNumber == 0 {
errMsg = errMsg + ` "port_number"`
hasError = true
}
if remote {
if sqlCfg.Host == "" {
errMsg = errMsg + ` "host"`
hasError = true
}
if guestCfg.ServerName == "" {
errMsg = errMsg + ` "server_name"`
hasError = true
}
if guestCfg.GuestUserName == "" {
errMsg = errMsg + ` "guest_user_name"`
hasError = true
}
if windows && guestCfg.GuestSecretName == "" {
errMsg = errMsg + ` "guest_secret_name"`
hasError = true
}
if instanceID == "" {
errMsg = errMsg + ` "instance_id"`
hasError = true
}
if instanceName == "" {
errMsg = errMsg + ` "instance_name"`
hasError = true
}
if !windows {
if guestCfg.LinuxSSHPrivateKeyPath == "" {
errMsg = errMsg + ` "linux_ssh_private_key_path"`
hasError = true
}
if guestCfg.GuestPortNumber == 0 {
errMsg = errMsg + ` "guest_port_number"`
hasError = true
}
}
}
if hasError {
return fmt.Errorf(errMsg)
}
return nil
}
// ValidateCredCfgGuest validates if the configuration file is valid for guest collection.
// If remote collection is enabled, the following fields must be provided:
// "server_name", "guest_user_name", "guest_secret_name", "instance_id", "instance_name"
func ValidateCredCfgGuest(remote, windows bool, guestCfg *GuestConfig, instanceID, instanceName string) error {
errMsg := "invalid value for"
hasError := false
if remote {
if guestCfg.ServerName == "" {
errMsg = errMsg + ` "server_name"`
hasError = true
}
if guestCfg.GuestUserName == "" {
errMsg = errMsg + ` "guest_user_name"`
hasError = true
}
if windows && guestCfg.GuestSecretName == "" {
errMsg = errMsg + ` "guest_secret_name"`
hasError = true
}
if instanceID == "" {
errMsg = errMsg + ` "instance_id"`
hasError = true
}
if instanceName == "" {
errMsg = errMsg + ` "instance_name"`
hasError = true
}
if !windows {
if guestCfg.LinuxSSHPrivateKeyPath == "" {
errMsg = errMsg + ` "linux_ssh_private_key_path"`
hasError = true
}
if guestCfg.GuestPortNumber == 0 {
errMsg = errMsg + ` "guest_port_number"`
hasError = true
}
}
}
if hasError {
return fmt.Errorf(errMsg)
}
return nil
}