Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
89 changes: 89 additions & 0 deletions cmd/oam-linked-accounts-check/main.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
// Copyright 2024 The Prometheus Authors
// 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
//
// http://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 main

import (
"context"
"flag"
"fmt"
"os"

"github.com/aws/aws-sdk-go-v2/config"
"github.com/aws/aws-sdk-go-v2/service/oam"
)

const usage = `Usage:
oam-linked-accounts-check --sink-identifier SINK_ARN [--region REGION]

Lists all source account links attached to the given OAM sink and prints
each source account ID with its resolved label.

Notes:
- Requires oam:ListAttachedLinks permission.
- Caller must be in the monitoring account that owns the sink.
`

func main() {
var sinkIdentifier string
var region string
flag.StringVar(&sinkIdentifier, "sink-identifier", "", "ARN of the OAM sink (required)")
flag.StringVar(&region, "region", "", "AWS region for OAM API (optional)")
flag.Usage = func() {
fmt.Fprint(flag.CommandLine.Output(), usage)
}
flag.Parse()

if sinkIdentifier == "" {
flag.Usage()
os.Exit(2)
}

ctx := context.Background()
loadOptions := []func(*config.LoadOptions) error{}
if region != "" {
loadOptions = append(loadOptions, config.WithRegion(region))
}
awsConfig, err := config.LoadDefaultConfig(ctx, loadOptions...)
if err != nil {
fmt.Fprintf(os.Stderr, "failed to load AWS config: %v\n", err)
os.Exit(1)
}

client := oam.NewFromConfig(awsConfig)

paginator := oam.NewListAttachedLinksPaginator(client, &oam.ListAttachedLinksInput{
SinkIdentifier: &sinkIdentifier,
})

count := 0
for paginator.HasMorePages() {
page, err := paginator.NextPage(ctx)
if err != nil {
fmt.Fprintf(os.Stderr, "ListAttachedLinks error: %v\n", err)
os.Exit(1)
}
for _, item := range page.Items {
linkArn := ""
label := ""
if item.LinkArn != nil {
linkArn = *item.LinkArn
}
if item.Label != nil {
label = *item.Label
}
fmt.Printf("link_arn=%s label=%s\n", linkArn, label)
count++
}
}
fmt.Printf("\nTotal linked accounts: %d\n", count)
}
7 changes: 4 additions & 3 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ go 1.24.0

require (
github.com/aws/aws-sdk-go v1.55.7
github.com/aws/aws-sdk-go-v2 v1.41.0
github.com/aws/aws-sdk-go-v2 v1.41.1
github.com/aws/aws-sdk-go-v2/config v1.32.0
github.com/aws/aws-sdk-go-v2/credentials v1.19.0
github.com/aws/aws-sdk-go-v2/service/amp v1.42.1
Expand All @@ -18,6 +18,7 @@ require (
github.com/aws/aws-sdk-go-v2/service/elasticache v1.51.8
github.com/aws/aws-sdk-go-v2/service/iam v1.52.1
github.com/aws/aws-sdk-go-v2/service/lambda v1.87.0
github.com/aws/aws-sdk-go-v2/service/oam v1.23.11
github.com/aws/aws-sdk-go-v2/service/rds v1.113.1
github.com/aws/aws-sdk-go-v2/service/resourcegroupstaggingapi v1.31.1
github.com/aws/aws-sdk-go-v2/service/shield v1.34.13
Expand All @@ -42,8 +43,8 @@ require (
github.com/alecthomas/units v0.0.0-20240927000941-0f3dac36c52b // indirect
github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.7.4 // indirect
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.18.14 // indirect
github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.16 // indirect
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.16 // indirect
github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.17 // indirect
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.17 // indirect
github.com/aws/aws-sdk-go-v2/internal/ini v1.8.4 // indirect
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.13.4 // indirect
github.com/aws/aws-sdk-go-v2/service/internal/endpoint-discovery v1.11.16 // indirect
Expand Down
14 changes: 8 additions & 6 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,8 @@ github.com/alecthomas/units v0.0.0-20240927000941-0f3dac36c52b h1:mimo19zliBX/vS
github.com/alecthomas/units v0.0.0-20240927000941-0f3dac36c52b/go.mod h1:fvzegU4vN3H1qMT+8wDmzjAcDONcgo2/SZ/TyfdUOFs=
github.com/aws/aws-sdk-go v1.55.7 h1:UJrkFq7es5CShfBwlWAC8DA077vp8PyVbQd3lqLiztE=
github.com/aws/aws-sdk-go v1.55.7/go.mod h1:eRwEWoyTWFMVYVQzKMNHWP5/RV4xIUGMQfXQHfHkpNU=
github.com/aws/aws-sdk-go-v2 v1.41.0 h1:tNvqh1s+v0vFYdA1xq0aOJH+Y5cRyZ5upu6roPgPKd4=
github.com/aws/aws-sdk-go-v2 v1.41.0/go.mod h1:MayyLB8y+buD9hZqkCW3kX1AKq07Y5pXxtgB+rRFhz0=
github.com/aws/aws-sdk-go-v2 v1.41.1 h1:ABlyEARCDLN034NhxlRUSZr4l71mh+T5KAeGh6cerhU=
github.com/aws/aws-sdk-go-v2 v1.41.1/go.mod h1:MayyLB8y+buD9hZqkCW3kX1AKq07Y5pXxtgB+rRFhz0=
github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.7.4 h1:489krEF9xIGkOaaX3CE/Be2uWjiXrkCH6gUX+bZA/BU=
github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.7.4/go.mod h1:IOAPF6oT9KCsceNTvvYMNHy0+kMF8akOjeDvPENWxp4=
github.com/aws/aws-sdk-go-v2/config v1.32.0 h1:T5WWJYnam9SzBLbsVYDu2HscLDe+GU1AUJtfcDAc/vA=
Expand All @@ -14,10 +14,10 @@ github.com/aws/aws-sdk-go-v2/credentials v1.19.0 h1:7zm+ez+qEqLaNsCSRaistkvJRJv8
github.com/aws/aws-sdk-go-v2/credentials v1.19.0/go.mod h1:pHKPblrT7hqFGkNLxqoS3FlGoPrQg4hMIa+4asZzBfs=
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.18.14 h1:WZVR5DbDgxzA0BJeudId89Kmgy6DIU4ORpxwsVHz0qA=
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.18.14/go.mod h1:Dadl9QO0kHgbrH1GRqGiZdYtW5w+IXXaBNCHTIaheM4=
github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.16 h1:rgGwPzb82iBYSvHMHXc8h9mRoOUBZIGFgKb9qniaZZc=
github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.16/go.mod h1:L/UxsGeKpGoIj6DxfhOWHWQ/kGKcd4I1VncE4++IyKA=
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.16 h1:1jtGzuV7c82xnqOVfx2F0xmJcOw5374L7N6juGW6x6U=
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.16/go.mod h1:M2E5OQf+XLe+SZGmmpaI2yy+J326aFf6/+54PoxSANc=
github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.17 h1:xOLELNKGp2vsiteLsvLPwxC+mYmO6OZ8PYgiuPJzF8U=
github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.17/go.mod h1:5M5CI3D12dNOtH3/mk6minaRwI2/37ifCURZISxA/IQ=
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.17 h1:WWLqlh79iO48yLkj1v3ISRNiv+3KdQoZ6JWyfcsyQik=
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.17/go.mod h1:EhG22vHRrvF8oXSTYStZhJc1aUgKtnJe+aOiFEV90cM=
github.com/aws/aws-sdk-go-v2/internal/ini v1.8.4 h1:WKuaxf++XKWlHWu9ECbMlha8WOEGm0OUEZqm4K/Gcfk=
github.com/aws/aws-sdk-go-v2/internal/ini v1.8.4/go.mod h1:ZWy7j6v1vWGmPReu0iSGvRiise4YI5SkR3OHKTZ6Wuc=
github.com/aws/aws-sdk-go-v2/service/amp v1.42.1 h1:fJorvFIiVlizskYTlQUtckFmb21hjkgT1PUSvH8cq/U=
Expand Down Expand Up @@ -48,6 +48,8 @@ github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.16 h1:oHjJHeUy
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.16/go.mod h1:iRSNGgOYmiYwSCXxXaKb9HfOEj40+oTKn8pTxMlYkRM=
github.com/aws/aws-sdk-go-v2/service/lambda v1.87.0 h1:E5UXxF3vK3JuViwKCHfTJBIiFjvE4aytSucZjI2UAlQ=
github.com/aws/aws-sdk-go-v2/service/lambda v1.87.0/go.mod h1:6f64Y1BEf6e1uCI+LtGbcZSKDK1GvgJ+iI4vP/bbE8s=
github.com/aws/aws-sdk-go-v2/service/oam v1.23.11 h1:tGBgzz6uJTBdQ6aTg8VNibn6vCqPro6+nhzsw86wxAU=
github.com/aws/aws-sdk-go-v2/service/oam v1.23.11/go.mod h1:6QtLWHhXxj3jblHDwWp4d6R16dAxJXzcD5g7ODv7bOo=
github.com/aws/aws-sdk-go-v2/service/rds v1.113.1 h1:/vV0g/Su8rCTqT57UUYiFU/aRrPXz//fGDn1dkXblG4=
github.com/aws/aws-sdk-go-v2/service/rds v1.113.1/go.mod h1:q02df+DL73LN+jDXzj86tMsI6kKf1kfv61nB684H+o8=
github.com/aws/aws-sdk-go-v2/service/resourcegroupstaggingapi v1.31.1 h1:dSSvIM4/755D7EkUeUc+BChEC6my1174OZ9U3glm3KI=
Expand Down
6 changes: 3 additions & 3 deletions pkg/clients/cloudwatch/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ type Client interface {
// ListMetrics returns the list of metrics and dimensions for a given namespace
// and metric name. Results pagination is handled automatically: the caller can
// optionally pass a non-nil func in order to handle results pages.
ListMetrics(ctx context.Context, namespace string, metric *model.MetricConfig, recentlyActiveOnly bool, fn func(page []*model.Metric)) error
ListMetrics(ctx context.Context, namespace string, metric *model.MetricConfig, includeLinkedAccounts []string, recentlyActiveOnly bool, fn func(page []*model.Metric)) error

// GetMetricData returns the output of the GetMetricData CloudWatch API.
// Results pagination is handled automatically.
Expand Down Expand Up @@ -90,9 +90,9 @@ func (c limitedConcurrencyClient) GetMetricData(ctx context.Context, getMetricDa
return res
}

func (c limitedConcurrencyClient) ListMetrics(ctx context.Context, namespace string, metric *model.MetricConfig, recentlyActiveOnly bool, fn func(page []*model.Metric)) error {
func (c limitedConcurrencyClient) ListMetrics(ctx context.Context, namespace string, metric *model.MetricConfig, includeLinkedAccounts []string, recentlyActiveOnly bool, fn func(page []*model.Metric)) error {
c.limiter.Acquire(listMetricsCall)
err := c.client.ListMetrics(ctx, namespace, metric, recentlyActiveOnly, fn)
err := c.client.ListMetrics(ctx, namespace, metric, includeLinkedAccounts, recentlyActiveOnly, fn)
c.limiter.Release(listMetricsCall)
return err
}
47 changes: 36 additions & 11 deletions pkg/clients/cloudwatch/v1/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ package v1
import (
"context"
"log/slog"
"slices"
"time"

"github.com/aws/aws-sdk-go/aws"
Expand All @@ -38,11 +39,14 @@ func NewClient(logger *slog.Logger, cloudwatchAPI cloudwatchiface.CloudWatchAPI)
}
}

func (c client) ListMetrics(ctx context.Context, namespace string, metric *model.MetricConfig, recentlyActiveOnly bool, fn func(page []*model.Metric)) error {
func (c client) ListMetrics(ctx context.Context, namespace string, metric *model.MetricConfig, includeLinkedAccounts []string, recentlyActiveOnly bool, fn func(page []*model.Metric)) error {
filter := &cloudwatch.ListMetricsInput{
MetricName: aws.String(metric.Name),
Namespace: aws.String(namespace),
}
if len(includeLinkedAccounts) > 0 {
filter.IncludeLinkedAccounts = aws.Bool(true)
}
if recentlyActiveOnly {
filter.RecentlyActive = aws.String("PT3H")
}
Expand All @@ -52,7 +56,7 @@ func (c client) ListMetrics(ctx context.Context, namespace string, metric *model
err := c.cloudwatchAPI.ListMetricsPagesWithContext(ctx, filter, func(page *cloudwatch.ListMetricsOutput, lastPage bool) bool {
promutil.CloudwatchAPICounter.WithLabelValues("ListMetrics").Inc()

metricsPage := toModelMetric(page)
metricsPage := toModelMetric(page, includeLinkedAccounts)

c.logger.Debug("ListMetrics", "output", metricsPage, "last_page", lastPage)

Expand All @@ -68,15 +72,32 @@ func (c client) ListMetrics(ctx context.Context, namespace string, metric *model
return nil
}

func toModelMetric(page *cloudwatch.ListMetricsOutput) []*model.Metric {
func toModelMetric(page *cloudwatch.ListMetricsOutput, includeLinkedAccounts []string) []*model.Metric {
modelMetrics := make([]*model.Metric, 0, len(page.Metrics))
for _, cloudwatchMetric := range page.Metrics {
modelMetric := &model.Metric{
MetricName: *cloudwatchMetric.MetricName,
Namespace: *cloudwatchMetric.Namespace,
Dimensions: toModelDimensions(cloudwatchMetric.Dimensions),
if len(includeLinkedAccounts) > 0 {
includeAll := slices.Contains(includeLinkedAccounts, "*")
for i := 0; i < len(page.Metrics); i++ {
linkedAccountID := *page.OwningAccounts[i]
if !includeAll && !slices.Contains(includeLinkedAccounts, linkedAccountID) {
continue
}
modelMetric := &model.Metric{
MetricName: *page.Metrics[i].MetricName,
Namespace: *page.Metrics[i].Namespace,
Dimensions: toModelDimensions(page.Metrics[i].Dimensions),
LinkedAccountID: linkedAccountID,
}
modelMetrics = append(modelMetrics, modelMetric)
}
} else {
for _, cloudwatchMetric := range page.Metrics {
modelMetric := &model.Metric{
MetricName: *cloudwatchMetric.MetricName,
Namespace: *cloudwatchMetric.Namespace,
Dimensions: toModelDimensions(cloudwatchMetric.Dimensions),
}
modelMetrics = append(modelMetrics, modelMetric)
}
modelMetrics = append(modelMetrics, modelMetric)
}
return modelMetrics
}
Expand Down Expand Up @@ -106,12 +127,16 @@ func (c client) GetMetricData(ctx context.Context, getMetricData []*model.Cloudw
Period: &data.GetMetricDataProcessingParams.Period,
Stat: &data.GetMetricDataProcessingParams.Statistic,
}
metricDataQueries = append(metricDataQueries, &cloudwatch.MetricDataQuery{
metricDataQuery := &cloudwatch.MetricDataQuery{
Id: &data.GetMetricDataProcessingParams.QueryID,
MetricStat: metricStat,
ReturnData: aws.Bool(true),
})
}
exportAllDataPoints = exportAllDataPoints || data.MetricMigrationParams.ExportAllDataPoints
if data.LinkedAccountID != "" {
metricDataQuery.AccountId = aws.String(data.LinkedAccountID)
}
metricDataQueries = append(metricDataQueries, metricDataQuery)
}
input := &cloudwatch.GetMetricDataInput{
EndTime: &endTime,
Expand Down
Loading