| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354 |
- package config
- import (
- "fmt"
- "io/ioutil"
- "path"
- "github.com/opencost/opencost/pkg/cloud"
- "github.com/opencost/opencost/pkg/cloud/alibaba"
- "github.com/opencost/opencost/pkg/cloud/aws"
- "github.com/opencost/opencost/pkg/cloud/azure"
- "github.com/opencost/opencost/pkg/cloud/gcp"
- "github.com/opencost/opencost/pkg/cloud/models"
- "github.com/opencost/opencost/core/pkg/log"
- "github.com/opencost/opencost/core/pkg/util/fileutil"
- "github.com/opencost/opencost/core/pkg/util/json"
- "github.com/opencost/opencost/pkg/env"
- )
- const authSecretPath = "/var/secrets/service-key.json"
- const storageConfigSecretPath = "/var/azure-storage-config/azure-storage-config.json"
- const cloudIntegrationSecretPath = "/cloud-integration/cloud-integration.json"
- type HelmWatcher struct {
- providerConfig models.ProviderConfig
- }
- // GetConfigs checks secret files and config map set via the helm chart for Cloud Billing integrations. Returns
- // only one billing integration due to values being shared by different configuration types.
- func (hw *HelmWatcher) GetConfigs() []cloud.KeyedConfig {
- var configs []cloud.KeyedConfig
- customPricing, _ := hw.providerConfig.GetCustomPricingData()
- // check for Azure Storage config in secret file
- exists, err := fileutil.FileExists(storageConfigSecretPath)
- if err != nil {
- log.Errorf("HelmWatcher: AzureStorage: error checking file at '%s': %s", storageConfigSecretPath, err.Error())
- }
- // If file does not exist implies that this configuration method was not used
- if exists {
- result, err2 := ioutil.ReadFile(storageConfigSecretPath)
- if err2 != nil {
- log.Errorf("HelmWatcher: AzureStorage: Error reading file: %s", err2.Error())
- return nil
- }
- asc := &azure.AzureStorageConfig{}
- err2 = json.Unmarshal(result, asc)
- if err2 != nil {
- log.Errorf("HelmWatcher: AzureStorage: Error reading json: %s", err2.Error())
- return nil
- }
- if asc != nil && !asc.IsEmpty() {
- // If subscription id is not set it may be present in the rate card API
- if asc.SubscriptionId == "" {
- ask := &azure.AzureServiceKey{}
- err3 := loadFile(authSecretPath, ask)
- if err3 != nil {
- log.Errorf("HelmWatcher: AzureStorage: AzureRateCard: %s", err3)
- }
- if ask != nil {
- asc.SubscriptionId = ask.SubscriptionID
- }
- }
- // If SubscriptionID is still empty check the customPricing
- if asc.SubscriptionId == "" {
- asc.SubscriptionId = customPricing.AzureSubscriptionID
- }
- kc := azure.ConvertAzureStorageConfigToConfig(*asc)
- configs = append(configs, kc)
- return configs
- }
- }
- exists, err = fileutil.FileExists(authSecretPath)
- if err != nil {
- log.Errorf("HelmWatcher: error checking file at '%s': %s", authSecretPath, err.Error())
- }
- // If the Auth Secret is not set then the config file watch will be responsible for providing the configurer for the
- // config values present in the CustomPricing object
- if exists {
- if customPricing.BillingDataDataset != "" {
- // Big Query Configuration
- bqc := gcp.BigQueryConfig{
- ProjectID: customPricing.ProjectID,
- BillingDataDataset: customPricing.BillingDataDataset,
- }
- key := make(map[string]string)
- err2 := loadFile(authSecretPath, &key)
- if err2 != nil {
- log.Errorf("HelmWatcher: GCP: %s", err2)
- }
- if key != nil && len(key) != 0 {
- bqc.Key = key
- }
- kc := gcp.ConvertBigQueryConfigToConfig(bqc)
- configs = append(configs, kc)
- return configs
- }
- if customPricing.AthenaBucketName != "" {
- aai := aws.AwsAthenaInfo{
- AthenaBucketName: customPricing.AthenaBucketName,
- AthenaRegion: customPricing.AthenaRegion,
- AthenaDatabase: customPricing.AthenaDatabase,
- AthenaTable: customPricing.AthenaTable,
- AthenaWorkgroup: customPricing.AthenaWorkgroup,
- AccountID: customPricing.AthenaProjectID,
- MasterPayerARN: customPricing.MasterPayerARN,
- }
- // If Account ID is blank check ProjectID
- if aai.AccountID == "" {
- aai.AccountID = customPricing.ProjectID
- }
- var accessKey aws.AWSAccessKey
- err2 := loadFile(authSecretPath, &accessKey)
- if err2 != nil {
- log.Errorf("HelmWatcher: AWS: %s", err2)
- }
- aai.ServiceKeyName = accessKey.AccessKeyID
- aai.ServiceKeySecret = accessKey.SecretAccessKey
- kc := aws.ConvertAwsAthenaInfoToConfig(aai)
- configs = append(configs, kc)
- return configs
- }
- }
- return configs
- }
- type ConfigFileWatcher struct {
- providerConfig models.ProviderConfig
- }
- // GetConfigs checks secret files and config map set via the helm chart for Cloud Billing integrations. Returns
- // only one billing integration due to values being shared by different configuration types.
- func (cfw *ConfigFileWatcher) GetConfigs() []cloud.KeyedConfig {
- var configs []cloud.KeyedConfig
- customPricing, _ := cfw.providerConfig.GetCustomPricingData()
- // Detect Azure Storage configuration
- if customPricing.AzureSubscriptionID != "" {
- asc := azure.AzureStorageConfig{
- SubscriptionId: customPricing.AzureSubscriptionID,
- AccountName: customPricing.AzureStorageAccount,
- AccessKey: customPricing.AzureStorageAccessKey,
- ContainerName: customPricing.AzureStorageContainer,
- ContainerPath: customPricing.AzureContainerPath,
- AzureCloud: customPricing.AzureCloud,
- }
- kc := azure.ConvertAzureStorageConfigToConfig(asc)
- configs = append(configs, kc)
- return configs
- }
- // Detect Big Query Configuration
- if customPricing.BillingDataDataset != "" {
- bqc := gcp.BigQueryConfig{
- ProjectID: customPricing.ProjectID,
- BillingDataDataset: customPricing.BillingDataDataset,
- }
- var key map[string]string
- err2 := loadFile(env.GetGCPAuthSecretFilePath(), &key)
- if err2 != nil {
- log.Errorf("ConfigFileWatcher: GCP: %s", err2)
- }
- if key != nil && len(key) != 0 {
- bqc.Key = key
- }
- kc := gcp.ConvertBigQueryConfigToConfig(bqc)
- configs = append(configs, kc)
- return configs
- }
- // Detect AWS configuration
- if customPricing.AthenaBucketName != "" {
- aai := aws.AwsAthenaInfo{
- AthenaBucketName: customPricing.AthenaBucketName,
- AthenaRegion: customPricing.AthenaRegion,
- AthenaDatabase: customPricing.AthenaDatabase,
- AthenaTable: customPricing.AthenaTable,
- AthenaWorkgroup: customPricing.AthenaWorkgroup,
- ServiceKeyName: customPricing.ServiceKeyName,
- ServiceKeySecret: customPricing.ServiceKeySecret,
- AccountID: customPricing.AthenaProjectID,
- MasterPayerARN: customPricing.MasterPayerARN,
- }
- // If Account ID is blank check ProjectID
- if aai.AccountID == "" {
- aai.AccountID = customPricing.ProjectID
- }
- // If the sample nil service key name is set, zero it out so that it is not
- // misinterpreted as a real service key.
- if aai.ServiceKeyName == "AKIXXX" {
- aai.ServiceKeyName = ""
- }
- kc := aws.ConvertAwsAthenaInfoToConfig(aai)
- configs = append(configs, kc)
- return configs
- }
- //detect Alibaba Configuration
- if customPricing.AlibabaClusterRegion != "" {
- aliCloudInfo := alibaba.AlibabaInfo{
- AlibabaClusterRegion: customPricing.AlibabaClusterRegion,
- AlibabaServiceKeyName: customPricing.AlibabaServiceKeyName,
- AlibabaServiceKeySecret: customPricing.AlibabaServiceKeySecret,
- AlibabaAccountID: customPricing.ProjectID,
- }
- kc := alibaba.ConvertAlibabaInfoToConfig(aliCloudInfo)
- configs = append(configs, kc)
- return configs
- }
- return configs
- }
- // MultiCloudWatcher ingests values a MultiCloudConfig from the file pulled in from the secret by the helm chart
- type MultiCloudWatcher struct {
- }
- func (mcw *MultiCloudWatcher) GetConfigs() []cloud.KeyedConfig {
- multiConfigPath := env.GetCloudCostConfigPath()
- exists, err := fileutil.FileExists(multiConfigPath)
- if err != nil {
- log.Errorf("MultiCloudWatcher: error checking file at '%s': %s", multiConfigPath, err.Error())
- }
- // If config does not exist implies that this configuration method was not used
- if !exists {
- // check the original location of secret mount
- multiConfigPath = path.Join("/var", cloudIntegrationSecretPath)
- exists, err = fileutil.FileExists(multiConfigPath)
- if err != nil {
- log.Errorf("MultiCloudWatcher: error checking file at '%s': %s", multiConfigPath, err.Error())
- }
- // If config does not exist implies that this configuration method was not used
- if !exists {
- return nil
- }
- }
- log.Debugf("MultiCloudWatcher GetConfigs: multiConfigPath: %s", multiConfigPath)
- configurations := &Configurations{}
- err = loadFile(multiConfigPath, configurations)
- if err != nil {
- log.Errorf("MultiCloudWatcher: Error getting file '%s': %s", multiConfigPath, err.Error())
- return nil
- }
- return configurations.ToSlice()
- }
- func GetCloudBillingWatchers(providerConfig models.ProviderConfig) map[ConfigSource]cloud.KeyedConfigWatcher {
- watchers := make(map[ConfigSource]cloud.KeyedConfigWatcher, 3)
- watchers[MultiCloudSource] = &MultiCloudWatcher{}
- if providerConfig != nil {
- watchers[HelmSource] = &HelmWatcher{providerConfig: providerConfig}
- watchers[ConfigFileSource] = &ConfigFileWatcher{providerConfig: providerConfig}
- }
- return watchers
- }
- // loadFile unmarshals the json content of a file into the provided object
- // an empty return with no error indicates that the file did not exist.
- func loadFile[T any](path string, content T) error {
- exists, err := fileutil.FileExists(path)
- if err != nil {
- return fmt.Errorf("loadFile: error checking file at '%s': %s", path, err.Error())
- }
- // If file does not exist implies that this configuration method was not used
- if !exists {
- return nil
- }
- result, err := ioutil.ReadFile(path)
- if err != nil {
- return fmt.Errorf("loadFile: Error reading file: %s", err.Error())
- }
- err = json.Unmarshal(result, content)
- if err != nil {
- return fmt.Errorf("loadFile: Error reading json: %s", err.Error())
- }
- return nil
- }
- // ConfigSource is an Enum of the sources int value of the Source determines its priority
- type ConfigSource int
- const (
- UnknownSource ConfigSource = iota
- ConfigControllerSource
- MultiCloudSource
- ConfigFileSource
- HelmSource
- )
- func GetConfigSource(str string) ConfigSource {
- switch str {
- case "configController":
- return ConfigControllerSource
- case "configfile":
- return ConfigFileSource
- case "helm":
- return HelmSource
- case "multicloud":
- return MultiCloudSource
- default:
- return UnknownSource
- }
- }
- func (cs ConfigSource) String() string {
- switch cs {
- case ConfigControllerSource:
- return "configController"
- case ConfigFileSource:
- return "configfile"
- case HelmSource:
- return "helm"
- case MultiCloudSource:
- return "multicloud"
- case UnknownSource:
- return "unknown"
- default:
- return "unknown"
- }
- }
|