diff --git a/internal/converter/internal/otelcolconvert/converter_sigv4authextension.go b/internal/converter/internal/otelcolconvert/converter_sigv4authextension.go new file mode 100644 index 000000000000..de22285cdb9b --- /dev/null +++ b/internal/converter/internal/otelcolconvert/converter_sigv4authextension.go @@ -0,0 +1,55 @@ +package otelcolconvert + +import ( + "fmt" + + "github.com/grafana/agent/internal/component/otelcol/auth/sigv4" + "github.com/grafana/agent/internal/converter/diag" + "github.com/grafana/agent/internal/converter/internal/common" + "github.com/open-telemetry/opentelemetry-collector-contrib/extension/sigv4authextension" + "go.opentelemetry.io/collector/component" +) + +func init() { + converters = append(converters, sigV4AuthExtensionConverter{}) +} + +type sigV4AuthExtensionConverter struct{} + +func (sigV4AuthExtensionConverter) Factory() component.Factory { + return sigv4authextension.NewFactory() +} + +func (sigV4AuthExtensionConverter) InputComponentName() string { + return "otelcol.auth.sigv4" +} + +func (sigV4AuthExtensionConverter) ConvertAndAppend(state *state, id component.InstanceID, cfg component.Config) diag.Diagnostics { + var diags diag.Diagnostics + + label := state.FlowComponentLabel() + + args := toSigV4AuthExtension(cfg.(*sigv4authextension.Config)) + block := common.NewBlockWithOverride([]string{"otelcol", "auth", "sigv4"}, label, args) + + diags.Add( + diag.SeverityLevelInfo, + fmt.Sprintf("Converted %s into %s", stringifyInstanceID(id), stringifyBlock(block)), + ) + + state.Body().AppendBlock(block) + + return diags +} + +func toSigV4AuthExtension(cfg *sigv4authextension.Config) *sigv4.Arguments { + return &sigv4.Arguments{ + Region: cfg.Region, + Service: cfg.Service, + AssumeRole: sigv4.AssumeRole{ + ARN: cfg.AssumeRole.ARN, + SessionName: cfg.AssumeRole.SessionName, + STSRegion: cfg.AssumeRole.STSRegion, + }, + } +} diff --git a/internal/converter/internal/otelcolconvert/otelcolconvert_test.go b/internal/converter/internal/otelcolconvert/otelcolconvert_test.go index 570a5dc08aef..ef35060e1c0b 100644 --- a/internal/converter/internal/otelcolconvert/otelcolconvert_test.go +++ b/internal/converter/internal/otelcolconvert/otelcolconvert_test.go @@ -10,6 +10,7 @@ import ( func TestConvert(t *testing.T) { // TODO(rfratto): support -update flag. test_common.TestDirectory(t, "testdata", ".yaml", true, []string{}, otelcolconvert.Convert) + test_common.TestDirectory(t, "testdata/otelcol_without_validation", ".yaml", true, []string{}, otelcolconvert.ConvertWithoutValidation) } // TestConvertErrors tests errors specifically regarding the reading of diff --git a/internal/converter/internal/otelcolconvert/testdata/otelcol_without_validation/sigv4auth.river b/internal/converter/internal/otelcolconvert/testdata/otelcol_without_validation/sigv4auth.river new file mode 100644 index 000000000000..ec425d72aa7e --- /dev/null +++ b/internal/converter/internal/otelcolconvert/testdata/otelcol_without_validation/sigv4auth.river @@ -0,0 +1,28 @@ +otelcol.auth.sigv4 "default" { + region = "ap-southeast-1" + service = "s3" + + assume_role { + arn = "arn:aws:iam::123456789012:role/aws-service-role/access" + sts_region = "us-east-1" + } +} + +otelcol.receiver.otlp "default" { + grpc { } + + http { } + + output { + metrics = [otelcol.exporter.otlp.default.input] + logs = [otelcol.exporter.otlp.default.input] + traces = [otelcol.exporter.otlp.default.input] + } +} + +otelcol.exporter.otlp "default" { + client { + endpoint = "database:4317" + auth = otelcol.auth.sigv4.default.handler + } +} diff --git a/internal/converter/internal/otelcolconvert/testdata/otelcol_without_validation/sigv4auth.yaml b/internal/converter/internal/otelcolconvert/testdata/otelcol_without_validation/sigv4auth.yaml new file mode 100644 index 000000000000..a244a5911eb5 --- /dev/null +++ b/internal/converter/internal/otelcolconvert/testdata/otelcol_without_validation/sigv4auth.yaml @@ -0,0 +1,35 @@ +extensions: + sigv4auth: + region: "ap-southeast-1" + service: "s3" + assume_role: + arn: "arn:aws:iam::123456789012:role/aws-service-role/access" + sts_region: "us-east-1" + +receivers: + otlp: + protocols: + grpc: + http: + +exporters: + otlp: + auth: + authenticator: sigv4auth + endpoint: database:4317 + +service: + extensions: [sigv4auth] + pipelines: + metrics: + receivers: [otlp] + processors: [] + exporters: [otlp] + logs: + receivers: [otlp] + processors: [] + exporters: [otlp] + traces: + receivers: [otlp] + processors: [] + exporters: [otlp] \ No newline at end of file diff --git a/internal/converter/internal/otelcolconvert/utils.go b/internal/converter/internal/otelcolconvert/utils.go index d3515919ff11..176a5a420aa6 100644 --- a/internal/converter/internal/otelcolconvert/utils.go +++ b/internal/converter/internal/otelcolconvert/utils.go @@ -1,9 +1,12 @@ package otelcolconvert import ( + "bytes" "fmt" "strings" + "github.com/grafana/agent/internal/converter/diag" + "github.com/grafana/agent/internal/converter/internal/common" "github.com/grafana/river/token/builder" "go.opentelemetry.io/collector/component" ) @@ -32,3 +35,42 @@ func stringifyKind(k component.Kind) string { func stringifyBlock(block *builder.Block) string { return fmt.Sprintf("%s.%s", strings.Join(block.Name, "."), block.Label) } + +// ConvertWithoutValidation is similar to `otelcolconvert.go`'s Convert but without validating generated configs +// This is to help testing `sigv4authextension` converter as its Validate() method calls up external cloud +// service and we can't inject mock SigV4 credential provider since the attribute is set as internal in the +// upstream. +// Remove this once credentials provider is open for mocking. +func ConvertWithoutValidation(in []byte, extraArgs []string) ([]byte, diag.Diagnostics) { + var diags diag.Diagnostics + + if len(extraArgs) > 0 { + diags.Add(diag.SeverityLevelCritical, fmt.Sprintf("extra arguments are not supported for the otelcol converter: %s", extraArgs)) + return nil, diags + } + + cfg, err := readOpentelemetryConfig(in) + if err != nil { + diags.Add(diag.SeverityLevelCritical, err.Error()) + return nil, diags + } + + f := builder.NewFile() + + diags.AddAll(AppendConfig(f, cfg, "")) + diags.AddAll(common.ValidateNodes(f)) + + var buf bytes.Buffer + if _, err := f.WriteTo(&buf); err != nil { + diags.Add(diag.SeverityLevelCritical, fmt.Sprintf("failed to render Flow config: %s", err.Error())) + return nil, diags + } + + if len(buf.Bytes()) == 0 { + return nil, diags + } + + prettyByte, newDiags := common.PrettyPrint(buf.Bytes()) + diags.AddAll(newDiags) + return prettyByte, diags +} diff --git a/internal/converter/internal/test_common/testing.go b/internal/converter/internal/test_common/testing.go index fec09f30af06..f3de5823e88e 100644 --- a/internal/converter/internal/test_common/testing.go +++ b/internal/converter/internal/test_common/testing.go @@ -44,8 +44,9 @@ const ( // configuration generated by calling convert in step 1. func TestDirectory(t *testing.T, folderPath string, sourceSuffix string, loadFlowConfig bool, extraArgs []string, convert func(in []byte, extraArgs []string) ([]byte, diag.Diagnostics)) { require.NoError(t, filepath.WalkDir(folderPath, func(path string, d fs.DirEntry, _ error) error { - if d.IsDir() { - return nil + // Only skip iterating child folders + if d.IsDir() && path != folderPath { + return filepath.SkipDir } if strings.HasSuffix(path, sourceSuffix) {