diff --git a/go.mod b/go.mod index 2fd3db5ef..458e40ad2 100644 --- a/go.mod +++ b/go.mod @@ -81,6 +81,7 @@ require ( cloud.google.com/go/certificatemanager v1.6.0 cloud.google.com/go/compute/metadata v0.2.3 // indirect cloud.google.com/go/iam v0.12.0 // indirect + cloud.google.com/go/redis v1.11.0 github.com/Azure/azure-sdk-for-go/sdk/internal v1.1.2 // indirect github.com/AzureAD/microsoft-authentication-library-for-go v0.8.1 // indirect github.com/andybalholm/brotli v1.0.4 // indirect diff --git a/go.sum b/go.sum index 25647ef80..1b0d83a8e 100644 --- a/go.sum +++ b/go.sum @@ -16,6 +16,8 @@ cloud.google.com/go/longrunning v0.4.1 h1:v+yFJOfKC3yZdY6ZUI933pIYdhyhV8S3NpWrXW cloud.google.com/go/longrunning v0.4.1/go.mod h1:4iWDqhBZ70CvZ6BfETbvam3T8FMvLK+eFj0E6AaRQTo= cloud.google.com/go/monitoring v1.13.0 h1:2qsrgXGVoRXpP7otZ14eE1I568zAa92sJSDPyOJvwjM= cloud.google.com/go/monitoring v1.13.0/go.mod h1:k2yMBAB1H9JT/QETjNkgdCGD9bPF712XiLTVr+cBrpw= +cloud.google.com/go/redis v1.11.0 h1:JoAd3SkeDt3rLFAAxEvw6wV4t+8y4ZzfZcZmddqphQ8= +cloud.google.com/go/redis v1.11.0/go.mod h1:/X6eicana+BWcUda5PpwZC48o37SiFVTFSs0fWAJ7uQ= cloud.google.com/go/storage v1.30.0 h1:g1yrbxAWOrvg/594228pETWkOi00MLTrOWfh56veU5o= cloud.google.com/go/storage v1.30.0/go.mod h1:xAVretHSROm1BQX4IIsoVgJqw0LqOyX+I/O2GzRAzdE= github.com/Azure/azure-sdk-for-go/sdk/azcore v1.3.1 h1:gVXuXcWd1i4C2Ruxe321aU+IKGaStvGB/S90PUPB/W8= diff --git a/providers/gcp/gcp.go b/providers/gcp/gcp.go index e175edea1..a1ab210ab 100644 --- a/providers/gcp/gcp.go +++ b/providers/gcp/gcp.go @@ -8,6 +8,7 @@ import ( "github.com/tailwarden/komiser/providers/gcp/bigquery" certficate "github.com/tailwarden/komiser/providers/gcp/certificate" "github.com/tailwarden/komiser/providers/gcp/compute" + "github.com/tailwarden/komiser/providers/gcp/redis" "github.com/tailwarden/komiser/providers/gcp/storage" "github.com/tailwarden/komiser/utils" "github.com/uptrace/bun" @@ -19,6 +20,7 @@ func listOfSupportedServices() []providers.FetchDataFunction { storage.Buckets, bigquery.BigQueryTables, certficate.Certificates, + redis.Redis, } } diff --git a/providers/gcp/redis/redis.go b/providers/gcp/redis/redis.go new file mode 100644 index 000000000..f4e18da5d --- /dev/null +++ b/providers/gcp/redis/redis.go @@ -0,0 +1,108 @@ +package redis + +import ( + "context" + "fmt" + "regexp" + "time" + + redis "cloud.google.com/go/redis/apiv1" + "cloud.google.com/go/redis/apiv1/redispb" + "github.com/sirupsen/logrus" + log "github.com/sirupsen/logrus" + + "github.com/tailwarden/komiser/models" + "github.com/tailwarden/komiser/providers" + "google.golang.org/api/compute/v1" + "google.golang.org/api/iterator" + "google.golang.org/api/option" +) + +func Redis(ctx context.Context, client providers.ProviderClient) ([]models.Resource, error) { + resources := make([]models.Resource, 0) + + regions, err := listGCPRegions(client.GCPClient.Credentials.ProjectID, option.WithCredentials(client.GCPClient.Credentials)) + if err != nil { + logrus.WithError(err).Errorf("failed to list zones to fetch redis") + return resources, err + } + + redisClient, err := redis.NewCloudRedisRESTClient(ctx, option.WithCredentials(client.GCPClient.Credentials)) + if err != nil { + logrus.WithError(err).Errorf("failed to create redis client") + return resources, err + } + +RegionsLoop: + for _, regionName := range regions { + req := &redispb.ListInstancesRequest{ + Parent: "projects/" + client.GCPClient.Credentials.ProjectID + "/locations/" + regionName, + } + + redisInstances := redisClient.ListInstances(ctx, req) + + for { + redis, err := redisInstances.Next() + if err == iterator.Done { + break + } + if err != nil { + + if err.Error() == "googleapi: Error 403: Location "+regionName+" is not found or access is unauthorized." { + continue RegionsLoop + } else { + logrus.WithError(err).Errorf("failed to list redis for region " + regionName) + return resources, err + } + } + re := regexp.MustCompile(`instances\/(.+)$`) + redisInstanceName := re.FindStringSubmatch(redis.Name)[1] + + resources = append(resources, models.Resource{ + Provider: "GCP", + Account: client.Name, + ResourceId: redisInstanceName, + Service: "Redis", + Name: redis.DisplayName, + Region: regionName, + CreatedAt: redis.CreateTime.AsTime(), + Cost: 0, + FetchedAt: time.Now(), + Link: fmt.Sprintf("https://console.cloud.google.com/memorystore/redis/locations/%s/instances/%s/details/overview?project=%s", regionName, redisInstanceName, client.GCPClient.Credentials.ProjectID), + }) + + } + + } + + logrus.WithFields(logrus.Fields{ + "provider": "GCP", + "account": client.Name, + "service": "Redis", + "resources": len(resources), + }).Info("Fetched resources") + + return resources, nil +} + +func listGCPRegions(projectId string, creds option.ClientOption) ([]string, error) { + var regions []string + + ctx := context.Background() + computeService, err := compute.NewService(ctx, creds) + if err != nil { + log.WithError(err).Debug("failed to create new service for fetching GCP regions for redis instance") + return nil, err + } + + regionList, err := computeService.Regions.List(projectId).Do() + if err != nil { + log.WithError(err).Debug("failed to list regions for fetching GCP regions for redis instance") + return nil, err + } + + for _, region := range regionList.Items { + regions = append(regions, region.Name) + } + return regions, nil +}