Просмотр исходного кода

Add IBM Cloud Usage Reports cloud cost integration (#4044)

Signed-off-by: Cliff Colvin <clifford.colvin@ibm.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: Warwick <warwick.peatey@ibm.com>
Cliff Colvin 5 дней назад
Родитель
Сommit
2d0614cd21

+ 3 - 0
core/pkg/opencost/assetprops.go

@@ -199,6 +199,9 @@ const OVHProvider = "OVH"
 // STACKITProvider describes the provider STACKIT
 // STACKITProvider describes the provider STACKIT
 const STACKITProvider = "STACKIT"
 const STACKITProvider = "STACKIT"
 
 
+// IBMProvider describes the provider IBM Cloud
+const IBMProvider = "IBM"
+
 // NilProvider describes unknown provider
 // NilProvider describes unknown provider
 const NilProvider = "-"
 const NilProvider = "-"
 
 

+ 10 - 5
go.mod

@@ -32,7 +32,7 @@ require (
 	github.com/aws/smithy-go v1.25.1
 	github.com/aws/smithy-go v1.25.1
 	github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc
 	github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc
 	github.com/digitalocean/godo v1.192.0
 	github.com/digitalocean/godo v1.192.0
-	github.com/go-playground/validator/v10 v10.30.1
+	github.com/go-playground/validator/v10 v10.30.4
 	github.com/google/martian v2.1.0+incompatible
 	github.com/google/martian v2.1.0+incompatible
 	github.com/google/uuid v1.6.0
 	github.com/google/uuid v1.6.0
 	github.com/hashicorp/go-hclog v1.6.3
 	github.com/hashicorp/go-hclog v1.6.3
@@ -69,6 +69,8 @@ require (
 )
 )
 
 
 require (
 require (
+	github.com/IBM/go-sdk-core/v5 v5.23.4
+	github.com/IBM/platform-services-go-sdk v0.103.2
 	github.com/google/go-cmp v0.7.0
 	github.com/google/go-cmp v0.7.0
 	github.com/opencost/bingen v0.2.0
 	github.com/opencost/bingen v0.2.0
 	github.com/oracle/oci-go-sdk/v65 v65.117.1
 	github.com/oracle/oci-go-sdk/v65 v65.117.1
@@ -121,13 +123,15 @@ require (
 	github.com/felixge/httpsnoop v1.0.4 // indirect
 	github.com/felixge/httpsnoop v1.0.4 // indirect
 	github.com/fsnotify/fsnotify v1.9.0 // indirect
 	github.com/fsnotify/fsnotify v1.9.0 // indirect
 	github.com/fxamacker/cbor/v2 v2.9.0 // indirect
 	github.com/fxamacker/cbor/v2 v2.9.0 // indirect
-	github.com/gabriel-vasile/mimetype v1.4.13 // indirect
+	github.com/gabriel-vasile/mimetype v1.4.15 // indirect
 	github.com/go-ini/ini v1.67.0 // indirect
 	github.com/go-ini/ini v1.67.0 // indirect
 	github.com/go-jose/go-jose/v4 v4.1.4 // indirect
 	github.com/go-jose/go-jose/v4 v4.1.4 // indirect
 	github.com/go-logr/logr v1.4.3 // indirect
 	github.com/go-logr/logr v1.4.3 // indirect
 	github.com/go-logr/stdr v1.2.2 // indirect
 	github.com/go-logr/stdr v1.2.2 // indirect
+	github.com/go-openapi/errors v0.22.8 // indirect
 	github.com/go-openapi/jsonpointer v0.22.4 // indirect
 	github.com/go-openapi/jsonpointer v0.22.4 // indirect
 	github.com/go-openapi/jsonreference v0.21.4 // indirect
 	github.com/go-openapi/jsonreference v0.21.4 // indirect
+	github.com/go-openapi/strfmt v0.27.2 // indirect
 	github.com/go-openapi/swag v0.25.5 // indirect
 	github.com/go-openapi/swag v0.25.5 // indirect
 	github.com/go-openapi/swag/cmdutils v0.25.5 // indirect
 	github.com/go-openapi/swag/cmdutils v0.25.5 // indirect
 	github.com/go-openapi/swag/conv v0.25.5 // indirect
 	github.com/go-openapi/swag/conv v0.25.5 // indirect
@@ -160,7 +164,7 @@ require (
 	github.com/hashicorp/errwrap v1.1.0 // indirect
 	github.com/hashicorp/errwrap v1.1.0 // indirect
 	github.com/hashicorp/go-cleanhttp v0.5.2 // indirect
 	github.com/hashicorp/go-cleanhttp v0.5.2 // indirect
 	github.com/hashicorp/go-multierror v1.1.1 // indirect
 	github.com/hashicorp/go-multierror v1.1.1 // indirect
-	github.com/hashicorp/go-retryablehttp v0.7.7 // indirect
+	github.com/hashicorp/go-retryablehttp v0.7.8 // indirect
 	github.com/hashicorp/yamux v0.1.2 // indirect
 	github.com/hashicorp/yamux v0.1.2 // indirect
 	github.com/inconshreveable/mousetrap v1.1.0 // indirect
 	github.com/inconshreveable/mousetrap v1.1.0 // indirect
 	github.com/jmespath/go-jmespath v0.4.0 // indirect
 	github.com/jmespath/go-jmespath v0.4.0 // indirect
@@ -169,7 +173,7 @@ require (
 	github.com/klauspost/cpuid/v2 v2.3.0 // indirect
 	github.com/klauspost/cpuid/v2 v2.3.0 // indirect
 	github.com/klauspost/crc32 v1.3.0 // indirect
 	github.com/klauspost/crc32 v1.3.0 // indirect
 	github.com/kylelemons/godebug v1.1.0 // indirect
 	github.com/kylelemons/godebug v1.1.0 // indirect
-	github.com/leodido/go-urn v1.4.0 // indirect
+	github.com/leodido/go-urn v1.5.0 // indirect
 	github.com/mattn/go-colorable v0.1.14 // indirect
 	github.com/mattn/go-colorable v0.1.14 // indirect
 	github.com/mattn/go-isatty v0.0.20 // indirect
 	github.com/mattn/go-isatty v0.0.20 // indirect
 	github.com/minio/crc64nvme v1.1.1 // indirect
 	github.com/minio/crc64nvme v1.1.1 // indirect
@@ -180,6 +184,7 @@ require (
 	github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee // indirect
 	github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee // indirect
 	github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect
 	github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect
 	github.com/oklog/run v1.2.0 // indirect
 	github.com/oklog/run v1.2.0 // indirect
+	github.com/oklog/ulid/v2 v2.1.2 // indirect
 	github.com/opentracing/opentracing-go v1.2.1-0.20220228012449-10b1cf09e00b // indirect
 	github.com/opentracing/opentracing-go v1.2.1-0.20220228012449-10b1cf09e00b // indirect
 	github.com/pelletier/go-toml/v2 v2.2.4 // indirect
 	github.com/pelletier/go-toml/v2 v2.2.4 // indirect
 	github.com/philhofer/fwd v1.2.0 // indirect
 	github.com/philhofer/fwd v1.2.0 // indirect
@@ -214,7 +219,7 @@ require (
 	go.opentelemetry.io/otel/sdk v1.44.0 // indirect
 	go.opentelemetry.io/otel/sdk v1.44.0 // indirect
 	go.opentelemetry.io/otel/sdk/metric v1.44.0 // indirect
 	go.opentelemetry.io/otel/sdk/metric v1.44.0 // indirect
 	go.opentelemetry.io/otel/trace v1.44.0 // indirect
 	go.opentelemetry.io/otel/trace v1.44.0 // indirect
-	go.yaml.in/yaml/v2 v2.4.3 // indirect
+	go.yaml.in/yaml/v2 v2.4.4 // indirect
 	go.yaml.in/yaml/v3 v3.0.5 // indirect
 	go.yaml.in/yaml/v3 v3.0.5 // indirect
 	golang.org/x/crypto v0.57.0 // indirect
 	golang.org/x/crypto v0.57.0 // indirect
 	golang.org/x/mod v0.41.0 // indirect
 	golang.org/x/mod v0.41.0 // indirect

+ 41 - 127
go.sum

@@ -1,61 +1,38 @@
-cel.dev/expr v0.25.1 h1:1KrZg61W6TWSxuNZ37Xy49ps13NUovb66QLprthtwi4=
-cel.dev/expr v0.25.1/go.mod h1:hrXvqGP6G6gyx8UAHSHJ5RGk//1Oj5nXQ2NI02Nrsg4=
 cel.dev/expr v0.25.2 h1:K6j46C81hXtZQfuX60cVWQFBJahKSE2gfRbNuvr5bFs=
 cel.dev/expr v0.25.2 h1:K6j46C81hXtZQfuX60cVWQFBJahKSE2gfRbNuvr5bFs=
 cel.dev/expr v0.25.2/go.mod h1:hrXvqGP6G6gyx8UAHSHJ5RGk//1Oj5nXQ2NI02Nrsg4=
 cel.dev/expr v0.25.2/go.mod h1:hrXvqGP6G6gyx8UAHSHJ5RGk//1Oj5nXQ2NI02Nrsg4=
 cloud.google.com/go v0.123.0 h1:2NAUJwPR47q+E35uaJeYoNhuNEM9kM8SjgRgdeOJUSE=
 cloud.google.com/go v0.123.0 h1:2NAUJwPR47q+E35uaJeYoNhuNEM9kM8SjgRgdeOJUSE=
 cloud.google.com/go v0.123.0/go.mod h1:xBoMV08QcqUGuPW65Qfm1o9Y4zKZBpGS+7bImXLTAZU=
 cloud.google.com/go v0.123.0/go.mod h1:xBoMV08QcqUGuPW65Qfm1o9Y4zKZBpGS+7bImXLTAZU=
-cloud.google.com/go/auth v0.18.2 h1:+Nbt5Ev0xEqxlNjd6c+yYUeosQ5TtEUaNcN/3FozlaM=
-cloud.google.com/go/auth v0.18.2/go.mod h1:xD+oY7gcahcu7G2SG2DsBerfFxgPAJz17zz2joOFF3M=
 cloud.google.com/go/auth v0.23.2 h1:pxSCpfiji41hpzpPdMCftEUCezpgpqmmDdYiAjCKXxo=
 cloud.google.com/go/auth v0.23.2 h1:pxSCpfiji41hpzpPdMCftEUCezpgpqmmDdYiAjCKXxo=
 cloud.google.com/go/auth v0.23.2/go.mod h1:4DhBRcqvtljQN3dJ57qtqbib5ZGCYE5f2crfiiC2EM0=
 cloud.google.com/go/auth v0.23.2/go.mod h1:4DhBRcqvtljQN3dJ57qtqbib5ZGCYE5f2crfiiC2EM0=
 cloud.google.com/go/auth/oauth2adapt v0.2.8 h1:keo8NaayQZ6wimpNSmW5OPc283g65QNIiLpZnkHRbnc=
 cloud.google.com/go/auth/oauth2adapt v0.2.8 h1:keo8NaayQZ6wimpNSmW5OPc283g65QNIiLpZnkHRbnc=
 cloud.google.com/go/auth/oauth2adapt v0.2.8/go.mod h1:XQ9y31RkqZCcwJWNSx2Xvric3RrU88hAYYbjDWYDL+c=
 cloud.google.com/go/auth/oauth2adapt v0.2.8/go.mod h1:XQ9y31RkqZCcwJWNSx2Xvric3RrU88hAYYbjDWYDL+c=
-cloud.google.com/go/bigquery v1.74.0 h1:Q6bAMv+eyvufOpIrfrYxhM46qq1D3ZQTdgUDQqKS+n8=
-cloud.google.com/go/bigquery v1.74.0/go.mod h1:iViO7Cx3A/cRKcHNRsHB3yqGAMInFBswrE9Pxazsc90=
 cloud.google.com/go/bigquery v1.79.0 h1:+tW6oRuP/4dOgdl/p32on0+3OktVQtU+veOMxhjoOhE=
 cloud.google.com/go/bigquery v1.79.0 h1:+tW6oRuP/4dOgdl/p32on0+3OktVQtU+veOMxhjoOhE=
 cloud.google.com/go/bigquery v1.79.0/go.mod h1:QTt5tgZxqqvZs3dOZKpvriGqy+CdvY9LyetirFZRPOE=
 cloud.google.com/go/bigquery v1.79.0/go.mod h1:QTt5tgZxqqvZs3dOZKpvriGqy+CdvY9LyetirFZRPOE=
 cloud.google.com/go/compute/metadata v0.9.0 h1:pDUj4QMoPejqq20dK0Pg2N4yG9zIkYGdBtwLoEkH9Zs=
 cloud.google.com/go/compute/metadata v0.9.0 h1:pDUj4QMoPejqq20dK0Pg2N4yG9zIkYGdBtwLoEkH9Zs=
 cloud.google.com/go/compute/metadata v0.9.0/go.mod h1:E0bWwX5wTnLPedCKqk3pJmVgCBSM6qQI1yTBdEb3C10=
 cloud.google.com/go/compute/metadata v0.9.0/go.mod h1:E0bWwX5wTnLPedCKqk3pJmVgCBSM6qQI1yTBdEb3C10=
-cloud.google.com/go/datacatalog v1.26.1 h1:bCRKA8uSQN8wGW3Tw0gwko4E9a64GRmbW1nCblhgC2k=
-cloud.google.com/go/datacatalog v1.26.1/go.mod h1:2Qcq8vsHNxMDgjgadRFmFG47Y+uuIVsyEGUrlrKEdrg=
 cloud.google.com/go/datacatalog v1.33.0 h1:8V80PpoAGdOOr2QhBrp4wZ66MDCbATdAB/fmVmo5rlU=
 cloud.google.com/go/datacatalog v1.33.0 h1:8V80PpoAGdOOr2QhBrp4wZ66MDCbATdAB/fmVmo5rlU=
-cloud.google.com/go/iam v1.5.3 h1:+vMINPiDF2ognBJ97ABAYYwRgsaqxPbQDlMnbHMjolc=
-cloud.google.com/go/iam v1.5.3/go.mod h1:MR3v9oLkZCTlaqljW6Eb2d3HGDGK5/bDv93jhfISFvU=
+cloud.google.com/go/datacatalog v1.33.0/go.mod h1:/EMN04S73fZcPdtNg86VYLDrhi2HheMehQtMCS86Klk=
 cloud.google.com/go/iam v1.12.0 h1:Aki3bX9aHUDKPHfnRJfDcTdVedvy6quGBQcTqx3DRXk=
 cloud.google.com/go/iam v1.12.0 h1:Aki3bX9aHUDKPHfnRJfDcTdVedvy6quGBQcTqx3DRXk=
 cloud.google.com/go/iam v1.12.0/go.mod h1:FEZ4lXpADAC2AIpQY7LANNjjwyQ2jK439CI2VaD+sLY=
 cloud.google.com/go/iam v1.12.0/go.mod h1:FEZ4lXpADAC2AIpQY7LANNjjwyQ2jK439CI2VaD+sLY=
-cloud.google.com/go/logging v1.13.2 h1:qqlHCBvieJT9Cdq4QqYx1KPadCQ2noD4FK02eNqHAjA=
-cloud.google.com/go/logging v1.13.2/go.mod h1:zaybliM3yun1J8mU2dVQ1/qDzjbOqEijZCn6hSBtKak=
 cloud.google.com/go/logging v1.19.0 h1:NCqhdVUg3wQ8Cobdf16FDSuTGi3+6+hdSBHrY5TsR6Q=
 cloud.google.com/go/logging v1.19.0 h1:NCqhdVUg3wQ8Cobdf16FDSuTGi3+6+hdSBHrY5TsR6Q=
-cloud.google.com/go/longrunning v0.8.0 h1:LiKK77J3bx5gDLi4SMViHixjD2ohlkwBi+mKA7EhfW8=
-cloud.google.com/go/longrunning v0.8.0/go.mod h1:UmErU2Onzi+fKDg2gR7dusz11Pe26aknR4kHmJJqIfk=
+cloud.google.com/go/logging v1.19.0/go.mod h1:i40NZCHC9Gqvod4yE+yQfDWwlgwW/SrshkkGibCHxcA=
 cloud.google.com/go/longrunning v1.2.0 h1:WjYH3YHBGCxGJP9M4dWGHBfXr/cFIjMkNgWcJj7/iMM=
 cloud.google.com/go/longrunning v1.2.0 h1:WjYH3YHBGCxGJP9M4dWGHBfXr/cFIjMkNgWcJj7/iMM=
-cloud.google.com/go/monitoring v1.24.3 h1:dde+gMNc0UhPZD1Azu6at2e79bfdztVDS5lvhOdsgaE=
-cloud.google.com/go/monitoring v1.24.3/go.mod h1:nYP6W0tm3N9H/bOw8am7t62YTzZY+zUeQ+Bi6+2eonI=
+cloud.google.com/go/longrunning v1.2.0/go.mod h1:5KMQALFGOCtFoi2xSOA1u3H7WKlhmckgiyFw7+LGQp0=
 cloud.google.com/go/monitoring v1.30.0 h1:r/d+JUbyKmJ8b07iznuKfzVzrIXTWxHQ3lBRm3x2LlY=
 cloud.google.com/go/monitoring v1.30.0 h1:r/d+JUbyKmJ8b07iznuKfzVzrIXTWxHQ3lBRm3x2LlY=
 cloud.google.com/go/monitoring v1.30.0/go.mod h1:htlUR0QWVMrjFzZmN4LGnMAve9xB/eduwjmINxVZ8RM=
 cloud.google.com/go/monitoring v1.30.0/go.mod h1:htlUR0QWVMrjFzZmN4LGnMAve9xB/eduwjmINxVZ8RM=
-cloud.google.com/go/storage v1.60.0 h1:oBfZrSOCimggVNz9Y/bXY35uUcts7OViubeddTTVzQ8=
-cloud.google.com/go/storage v1.60.0/go.mod h1:q+5196hXfejkctrnx+VYU8RKQr/L3c0cBIlrjmiAKE0=
 cloud.google.com/go/storage v1.62.3 h1:SZq1t23NCI+e96dH77Dg3PEfsNNEjqO8zE5AnD8gVD0=
 cloud.google.com/go/storage v1.62.3 h1:SZq1t23NCI+e96dH77Dg3PEfsNNEjqO8zE5AnD8gVD0=
 cloud.google.com/go/storage v1.62.3/go.mod h1:cpYz/kRVZ+UQAF1uHeea10/9ewcRbxGoGNKsS9daSXA=
 cloud.google.com/go/storage v1.62.3/go.mod h1:cpYz/kRVZ+UQAF1uHeea10/9ewcRbxGoGNKsS9daSXA=
-cloud.google.com/go/trace v1.11.7 h1:kDNDX8JkaAG3R2nq1lIdkb7FCSi1rCmsEtKVsty7p+U=
-cloud.google.com/go/trace v1.11.7/go.mod h1:TNn9d5V3fQVf6s4SCveVMIBS2LJUqo73GACmq/Tky0s=
 cloud.google.com/go/trace v1.16.0 h1:GmQovzFc5F0CNfl0VLgL64aoTtu7xsM0YajW2GlG9+E=
 cloud.google.com/go/trace v1.16.0 h1:GmQovzFc5F0CNfl0VLgL64aoTtu7xsM0YajW2GlG9+E=
+cloud.google.com/go/trace v1.16.0/go.mod h1:r+bdAn16dKLSV1G2D5v3e58IlQlizfxWrUfjx7kM7X0=
 dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU=
 dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU=
 github.com/Azure/azure-sdk-for-go v68.0.0+incompatible h1:fcYLmCpyNYRnvJbPerq7U0hS+6+I79yEDJBqVNcqUzU=
 github.com/Azure/azure-sdk-for-go v68.0.0+incompatible h1:fcYLmCpyNYRnvJbPerq7U0hS+6+I79yEDJBqVNcqUzU=
 github.com/Azure/azure-sdk-for-go v68.0.0+incompatible/go.mod h1:9XXNKU+eRnpl9moKnB4QOLf1HestfXbmab5FXxiDBjc=
 github.com/Azure/azure-sdk-for-go v68.0.0+incompatible/go.mod h1:9XXNKU+eRnpl9moKnB4QOLf1HestfXbmab5FXxiDBjc=
-github.com/Azure/azure-sdk-for-go/sdk/azcore v1.21.0 h1:fou+2+WFTib47nS+nz/ozhEBnvU96bKHy6LjRsY4E28=
-github.com/Azure/azure-sdk-for-go/sdk/azcore v1.21.0/go.mod h1:t76Ruy8AHvUAC8GfMWJMa0ElSbuIcO03NLpynfbgsPA=
 github.com/Azure/azure-sdk-for-go/sdk/azcore v1.23.1 h1:zvXfGJCWvywnCA814d8ZiVyt+fm9nnTE8xSb99zRyfo=
 github.com/Azure/azure-sdk-for-go/sdk/azcore v1.23.1 h1:zvXfGJCWvywnCA814d8ZiVyt+fm9nnTE8xSb99zRyfo=
 github.com/Azure/azure-sdk-for-go/sdk/azcore v1.23.1/go.mod h1:iptorS+VYKFL2N6PnebpS91dubG35eAOEERnT4PJbQU=
 github.com/Azure/azure-sdk-for-go/sdk/azcore v1.23.1/go.mod h1:iptorS+VYKFL2N6PnebpS91dubG35eAOEERnT4PJbQU=
-github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.13.1 h1:Hk5QBxZQC1jb2Fwj6mpzme37xbCDdNTxU7O9eb5+LB4=
-github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.13.1/go.mod h1:IYus9qsFobWIc2YVwe/WPjcnyCkPKtnHAqUYeebc8z0=
 github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.14.1 h1:u93s+zU2JD62im61Bm5CZIc1ZrOJaIAWEg0WOrMVkEo=
 github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.14.1 h1:u93s+zU2JD62im61Bm5CZIc1ZrOJaIAWEg0WOrMVkEo=
 github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.14.1/go.mod h1:oXtinPO4OLj9d1DOTrqrL1oRwGhcqadvAmrl6wTeGlk=
 github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.14.1/go.mod h1:oXtinPO4OLj9d1DOTrqrL1oRwGhcqadvAmrl6wTeGlk=
-github.com/Azure/azure-sdk-for-go/sdk/azidentity/cache v0.3.2 h1:yz1bePFlP5Vws5+8ez6T3HWXPmwOK7Yvq8QxDBD3SKY=
-github.com/Azure/azure-sdk-for-go/sdk/azidentity/cache v0.3.2/go.mod h1:Pa9ZNPuoNu/GztvBSKk9J1cDJW6vk/n0zLtV4mgd8N8=
 github.com/Azure/azure-sdk-for-go/sdk/azidentity/cache v0.4.0 h1:xFaZZ+IubdftrDHnGGwZ6QvQ3KHTtWl2MCK+GMt2vxs=
 github.com/Azure/azure-sdk-for-go/sdk/azidentity/cache v0.4.0 h1:xFaZZ+IubdftrDHnGGwZ6QvQ3KHTtWl2MCK+GMt2vxs=
-github.com/Azure/azure-sdk-for-go/sdk/internal v1.11.2 h1:9iefClla7iYpfYWdzPCRDozdmndjTm8DXdpCzPajMgA=
-github.com/Azure/azure-sdk-for-go/sdk/internal v1.11.2/go.mod h1:XtLgD3ZD34DAaVIIAyG3objl5DynM3CQ/vMcbBNJZGI=
+github.com/Azure/azure-sdk-for-go/sdk/azidentity/cache v0.4.0/go.mod h1:mCBhUhlMjLLJKr5aqw2TNS/VqJOie8MzWq3DAMJeKso=
 github.com/Azure/azure-sdk-for-go/sdk/internal v1.12.0 h1:fhqpLE3UEXi9lPaBRpQ6XuRW0nU7hgg4zlmZZa+a9q4=
 github.com/Azure/azure-sdk-for-go/sdk/internal v1.12.0 h1:fhqpLE3UEXi9lPaBRpQ6XuRW0nU7hgg4zlmZZa+a9q4=
 github.com/Azure/azure-sdk-for-go/sdk/internal v1.12.0/go.mod h1:7dCRMLwisfRH3dBupKeNCioWYUZ4SS09Z14H+7i8ZoY=
 github.com/Azure/azure-sdk-for-go/sdk/internal v1.12.0/go.mod h1:7dCRMLwisfRH3dBupKeNCioWYUZ4SS09Z14H+7i8ZoY=
 github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/storage/armstorage v1.8.1 h1:/Zt+cDPnpC3OVDm/JKLOs7M2DKmLRIIp3XIx9pHHiig=
 github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/storage/armstorage v1.8.1 h1:/Zt+cDPnpC3OVDm/JKLOs7M2DKmLRIIp3XIx9pHHiig=
@@ -89,13 +66,9 @@ github.com/Azure/go-autorest/tracing v0.6.0 h1:TYi4+3m5t6K48TGI9AUdb+IzbnSxvnvUM
 github.com/Azure/go-autorest/tracing v0.6.0/go.mod h1:+vhtPC754Xsa23ID7GlGsrdKBpUA79WCAKPPZVC2DeU=
 github.com/Azure/go-autorest/tracing v0.6.0/go.mod h1:+vhtPC754Xsa23ID7GlGsrdKBpUA79WCAKPPZVC2DeU=
 github.com/AzureAD/microsoft-authentication-extensions-for-go/cache v0.1.1 h1:WJTmL004Abzc5wDB5VtZG2PJk5ndYDgVacGqfirKxjM=
 github.com/AzureAD/microsoft-authentication-extensions-for-go/cache v0.1.1 h1:WJTmL004Abzc5wDB5VtZG2PJk5ndYDgVacGqfirKxjM=
 github.com/AzureAD/microsoft-authentication-extensions-for-go/cache v0.1.1/go.mod h1:tCcJZ0uHAmvjsVYzEFivsRTN00oz5BEsRgQHu5JZ9WE=
 github.com/AzureAD/microsoft-authentication-extensions-for-go/cache v0.1.1/go.mod h1:tCcJZ0uHAmvjsVYzEFivsRTN00oz5BEsRgQHu5JZ9WE=
-github.com/AzureAD/microsoft-authentication-library-for-go v1.6.0 h1:XRzhVemXdgvJqCH0sFfrBUTnUJSBrBf7++ypk+twtRs=
-github.com/AzureAD/microsoft-authentication-library-for-go v1.6.0/go.mod h1:HKpQxkWaGLJ+D/5H8QRpyQXA1eKjxkFlOMwck5+33Jk=
 github.com/AzureAD/microsoft-authentication-library-for-go v1.9.0 h1:MDT4FxAPve5FnYn6vOL1r7RCRDG+l9cI7a5LlCuHsqA=
 github.com/AzureAD/microsoft-authentication-library-for-go v1.9.0 h1:MDT4FxAPve5FnYn6vOL1r7RCRDG+l9cI7a5LlCuHsqA=
 github.com/AzureAD/microsoft-authentication-library-for-go v1.9.0/go.mod h1:Y33QHnf0FfdVewFFISOGe20mkZbxX4H839o955/PoeI=
 github.com/AzureAD/microsoft-authentication-library-for-go v1.9.0/go.mod h1:Y33QHnf0FfdVewFFISOGe20mkZbxX4H839o955/PoeI=
 github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
 github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
-github.com/GoogleCloudPlatform/opentelemetry-operations-go/detectors/gcp v1.31.0 h1:DHa2U07rk8syqvCge0QIGMCE1WxGj9njT44GH7zNJLQ=
-github.com/GoogleCloudPlatform/opentelemetry-operations-go/detectors/gcp v1.31.0/go.mod h1:P4WPRUkOhJC13W//jWpyfJNDAIpvRbAUIYLX/4jtlE0=
 github.com/GoogleCloudPlatform/opentelemetry-operations-go/detectors/gcp v1.33.0 h1:l7+6kwRMJNwdCvYdDl7Eax+wzEYHSnNY7zrrfbhDdTA=
 github.com/GoogleCloudPlatform/opentelemetry-operations-go/detectors/gcp v1.33.0 h1:l7+6kwRMJNwdCvYdDl7Eax+wzEYHSnNY7zrrfbhDdTA=
 github.com/GoogleCloudPlatform/opentelemetry-operations-go/detectors/gcp v1.33.0/go.mod h1:pJTkW8hEUIIi3Pf65lPZOnn4Y81yCllX6IWk2jNXdkM=
 github.com/GoogleCloudPlatform/opentelemetry-operations-go/detectors/gcp v1.33.0/go.mod h1:pJTkW8hEUIIi3Pf65lPZOnn4Y81yCllX6IWk2jNXdkM=
 github.com/GoogleCloudPlatform/opentelemetry-operations-go/exporter/metric v0.55.0 h1:UnDZ/zFfG1JhH/DqxIZYU/1CUAlTUScoXD/LcM2Ykk8=
 github.com/GoogleCloudPlatform/opentelemetry-operations-go/exporter/metric v0.55.0 h1:UnDZ/zFfG1JhH/DqxIZYU/1CUAlTUScoXD/LcM2Ykk8=
@@ -105,6 +78,10 @@ github.com/GoogleCloudPlatform/opentelemetry-operations-go/internal/cloudmock v0
 github.com/GoogleCloudPlatform/opentelemetry-operations-go/internal/resourcemapping v0.55.0 h1:0s6TxfCu2KHkkZPnBfsQ2y5qia0jl3MMrmBhu3nCOYk=
 github.com/GoogleCloudPlatform/opentelemetry-operations-go/internal/resourcemapping v0.55.0 h1:0s6TxfCu2KHkkZPnBfsQ2y5qia0jl3MMrmBhu3nCOYk=
 github.com/GoogleCloudPlatform/opentelemetry-operations-go/internal/resourcemapping v0.55.0/go.mod h1:Mf6O40IAyB9zR/1J8nGDDPirZQQPbYJni8Yisy7NTMc=
 github.com/GoogleCloudPlatform/opentelemetry-operations-go/internal/resourcemapping v0.55.0/go.mod h1:Mf6O40IAyB9zR/1J8nGDDPirZQQPbYJni8Yisy7NTMc=
 github.com/HdrHistogram/hdrhistogram-go v1.1.2/go.mod h1:yDgFjdqOqDEKOvasDdhWNXYg9BVp4O+o5f6V/ehm6Oo=
 github.com/HdrHistogram/hdrhistogram-go v1.1.2/go.mod h1:yDgFjdqOqDEKOvasDdhWNXYg9BVp4O+o5f6V/ehm6Oo=
+github.com/IBM/go-sdk-core/v5 v5.23.4 h1:pxQuxcRSuQYHf6HsWDKKQHU7cMjl817bqvFGiu53p5k=
+github.com/IBM/go-sdk-core/v5 v5.23.4/go.mod h1:STTfOZKIy3y4WowRDXYQTXb24nrAnIcm0dcxRXrinTk=
+github.com/IBM/platform-services-go-sdk v0.103.2 h1:Hi7DuIXdYTALeHKh3v06azd6LOwiVY4wO1wBNDi/pkk=
+github.com/IBM/platform-services-go-sdk v0.103.2/go.mod h1:6cWplM5AFkkZ8iWDxnbvzaPjEFcHCc7am16BSfVpbBI=
 github.com/Masterminds/semver/v3 v3.4.0 h1:Zog+i5UMtVoCU8oKka5P7i9q9HgrJeGzI9SA1Xbatp0=
 github.com/Masterminds/semver/v3 v3.4.0 h1:Zog+i5UMtVoCU8oKka5P7i9q9HgrJeGzI9SA1Xbatp0=
 github.com/Masterminds/semver/v3 v3.4.0/go.mod h1:4V+yj/TJE1HU9XfppCwVMZq3I84lprf4nC11bSS5beM=
 github.com/Masterminds/semver/v3 v3.4.0/go.mod h1:4V+yj/TJE1HU9XfppCwVMZq3I84lprf4nC11bSS5beM=
 github.com/ajstarks/svgo v0.0.0-20180226025133-644b8db467af/go.mod h1:K08gAheRH3/J6wwsYMMT4xOr94bZjxIelGM0+d/wbFw=
 github.com/ajstarks/svgo v0.0.0-20180226025133-644b8db467af/go.mod h1:K08gAheRH3/J6wwsYMMT4xOr94bZjxIelGM0+d/wbFw=
@@ -201,8 +178,8 @@ github.com/fsnotify/fsnotify v1.9.0 h1:2Ml+OJNzbYCTzsxtv8vKSFD9PbJjmhYF14k/jKC7S
 github.com/fsnotify/fsnotify v1.9.0/go.mod h1:8jBTzvmWwFyi3Pb8djgCCO5IBqzKJ/Jwo8TRcHyHii0=
 github.com/fsnotify/fsnotify v1.9.0/go.mod h1:8jBTzvmWwFyi3Pb8djgCCO5IBqzKJ/Jwo8TRcHyHii0=
 github.com/fxamacker/cbor/v2 v2.9.0 h1:NpKPmjDBgUfBms6tr6JZkTHtfFGcMKsw3eGcmD/sapM=
 github.com/fxamacker/cbor/v2 v2.9.0 h1:NpKPmjDBgUfBms6tr6JZkTHtfFGcMKsw3eGcmD/sapM=
 github.com/fxamacker/cbor/v2 v2.9.0/go.mod h1:vM4b+DJCtHn+zz7h3FFp/hDAI9WNWCsZj23V5ytsSxQ=
 github.com/fxamacker/cbor/v2 v2.9.0/go.mod h1:vM4b+DJCtHn+zz7h3FFp/hDAI9WNWCsZj23V5ytsSxQ=
-github.com/gabriel-vasile/mimetype v1.4.13 h1:46nXokslUBsAJE/wMsp5gtO500a4F3Nkz9Ufpk2AcUM=
-github.com/gabriel-vasile/mimetype v1.4.13/go.mod h1:d+9Oxyo1wTzWdyVUPMmXFvp4F9tea18J8ufA774AB3s=
+github.com/gabriel-vasile/mimetype v1.4.15 h1:05iP/CYtZ/w455R/KZM6rZ5ieAdh99UPtd+d3YzLmaI=
+github.com/gabriel-vasile/mimetype v1.4.15/go.mod h1:azpTcoLcDZRNgFou5j+APrqQx9HqVPWa6ijYQIIVswQ=
 github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU=
 github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU=
 github.com/go-ini/ini v1.67.0 h1:z6ZrTEZqSWOTyH2FlglNbNgARyHG8oLW9gMELqKr06A=
 github.com/go-ini/ini v1.67.0 h1:z6ZrTEZqSWOTyH2FlglNbNgARyHG8oLW9gMELqKr06A=
 github.com/go-ini/ini v1.67.0/go.mod h1:ByCAeIL28uOIIG0E3PJtZPDL8WnHpFKFOtgjp+3Ies8=
 github.com/go-ini/ini v1.67.0/go.mod h1:ByCAeIL28uOIIG0E3PJtZPDL8WnHpFKFOtgjp+3Ies8=
@@ -213,10 +190,14 @@ github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI=
 github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
 github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
 github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag=
 github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag=
 github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE=
 github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE=
+github.com/go-openapi/errors v0.22.8 h1:oP7sW7TWc3wFFjrzzj0nI83H2qMBkNjNfSd+XRejk/I=
+github.com/go-openapi/errors v0.22.8/go.mod h1:BuUoHcYrU6E7V9gfj1I5wLQqgtIHnup/alXZ8KdgQ0w=
 github.com/go-openapi/jsonpointer v0.22.4 h1:dZtK82WlNpVLDW2jlA1YCiVJFVqkED1MegOUy9kR5T4=
 github.com/go-openapi/jsonpointer v0.22.4 h1:dZtK82WlNpVLDW2jlA1YCiVJFVqkED1MegOUy9kR5T4=
 github.com/go-openapi/jsonpointer v0.22.4/go.mod h1:elX9+UgznpFhgBuaMQ7iu4lvvX1nvNsesQ3oxmYTw80=
 github.com/go-openapi/jsonpointer v0.22.4/go.mod h1:elX9+UgznpFhgBuaMQ7iu4lvvX1nvNsesQ3oxmYTw80=
 github.com/go-openapi/jsonreference v0.21.4 h1:24qaE2y9bx/q3uRK/qN+TDwbok1NhbSmGjjySRCHtC8=
 github.com/go-openapi/jsonreference v0.21.4 h1:24qaE2y9bx/q3uRK/qN+TDwbok1NhbSmGjjySRCHtC8=
 github.com/go-openapi/jsonreference v0.21.4/go.mod h1:rIENPTjDbLpzQmQWCj5kKj3ZlmEh+EFVbz3RTUh30/4=
 github.com/go-openapi/jsonreference v0.21.4/go.mod h1:rIENPTjDbLpzQmQWCj5kKj3ZlmEh+EFVbz3RTUh30/4=
+github.com/go-openapi/strfmt v0.27.2 h1:SG32SlbwNy92s0KJiVxt2joJeFdqIYHvwrA0OU6HqzQ=
+github.com/go-openapi/strfmt v0.27.2/go.mod h1:M4CKsMO0Fb8qR10+1Ra75wCKNNquy+Vj+4LWZrhTo2E=
 github.com/go-openapi/swag v0.25.5 h1:pNkwbUEeGwMtcgxDr+2GBPAk4kT+kJ+AaB+TMKAg+TU=
 github.com/go-openapi/swag v0.25.5 h1:pNkwbUEeGwMtcgxDr+2GBPAk4kT+kJ+AaB+TMKAg+TU=
 github.com/go-openapi/swag v0.25.5/go.mod h1:B3RT6l8q7X803JRxa2e59tHOiZlX1t8viplOcs9CwTA=
 github.com/go-openapi/swag v0.25.5/go.mod h1:B3RT6l8q7X803JRxa2e59tHOiZlX1t8viplOcs9CwTA=
 github.com/go-openapi/swag/cmdutils v0.25.5 h1:yh5hHrpgsw4NwM9KAEtaDTXILYzdXh/I8Whhx9hKj7c=
 github.com/go-openapi/swag/cmdutils v0.25.5 h1:yh5hHrpgsw4NwM9KAEtaDTXILYzdXh/I8Whhx9hKj7c=
@@ -245,16 +226,16 @@ github.com/go-openapi/swag/yamlutils v0.25.5 h1:kASCIS+oIeoc55j28T4o8KwlV2S4ZLPT
 github.com/go-openapi/swag/yamlutils v0.25.5/go.mod h1:Gek1/SjjfbYvM+Iq4QGwa/2lEXde9n2j4a3wI3pNuOQ=
 github.com/go-openapi/swag/yamlutils v0.25.5/go.mod h1:Gek1/SjjfbYvM+Iq4QGwa/2lEXde9n2j4a3wI3pNuOQ=
 github.com/go-openapi/testify/enable/yaml/v2 v2.4.0 h1:7SgOMTvJkM8yWrQlU8Jm18VeDPuAvB/xWrdxFJkoFag=
 github.com/go-openapi/testify/enable/yaml/v2 v2.4.0 h1:7SgOMTvJkM8yWrQlU8Jm18VeDPuAvB/xWrdxFJkoFag=
 github.com/go-openapi/testify/enable/yaml/v2 v2.4.0/go.mod h1:14iV8jyyQlinc9StD7w1xVPW3CO3q1Gj04Jy//Kw4VM=
 github.com/go-openapi/testify/enable/yaml/v2 v2.4.0/go.mod h1:14iV8jyyQlinc9StD7w1xVPW3CO3q1Gj04Jy//Kw4VM=
-github.com/go-openapi/testify/v2 v2.4.0 h1:8nsPrHVCWkQ4p8h1EsRVymA2XABB4OT40gcvAu+voFM=
-github.com/go-openapi/testify/v2 v2.4.0/go.mod h1:HCPmvFFnheKK2BuwSA0TbbdxJ3I16pjwMkYkP4Ywn54=
+github.com/go-openapi/testify/v2 v2.7.0 h1:bycOreEj6wfBvijg3YFogZ/sFjTCDmQnwSodSzHa3X8=
+github.com/go-openapi/testify/v2 v2.7.0/go.mod h1:SgsVHtfooshd0tublTtJ50FPKhujf47YRqauXXOUxfw=
 github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s=
 github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s=
 github.com/go-playground/assert/v2 v2.2.0/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4=
 github.com/go-playground/assert/v2 v2.2.0/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4=
 github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA=
 github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA=
 github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY=
 github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY=
 github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY=
 github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY=
 github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY=
 github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY=
-github.com/go-playground/validator/v10 v10.30.1 h1:f3zDSN/zOma+w6+1Wswgd9fLkdwy06ntQJp0BBvFG0w=
-github.com/go-playground/validator/v10 v10.30.1/go.mod h1:oSuBIQzuJxL//3MelwSLD5hc2Tu889bF0Idm9Dg26cM=
+github.com/go-playground/validator/v10 v10.30.4 h1:9Rcod2ZPO6mOEG6b4GqyoHE/H6//Ze0RuhOo1hT1x0w=
+github.com/go-playground/validator/v10 v10.30.4/go.mod h1:numpT+RPLE91R9oYWMY/R9zRgJBewr3IXHko4OISPpk=
 github.com/go-viper/mapstructure/v2 v2.5.0 h1:vM5IJoUAy3d7zRSVtIwQgBj7BiWtMPfmPEgAXnvj1Ro=
 github.com/go-viper/mapstructure/v2 v2.5.0 h1:vM5IJoUAy3d7zRSVtIwQgBj7BiWtMPfmPEgAXnvj1Ro=
 github.com/go-viper/mapstructure/v2 v2.5.0/go.mod h1:oJDH3BJKyqBA2TXFhDsKDGDTlndYOZ6rGS0BRZIxGhM=
 github.com/go-viper/mapstructure/v2 v2.5.0/go.mod h1:oJDH3BJKyqBA2TXFhDsKDGDTlndYOZ6rGS0BRZIxGhM=
 github.com/goccy/go-json v0.10.5 h1:Fq85nIqj+gXn/S5ahsiTlK3TmC85qgirsdTP/+DeaC4=
 github.com/goccy/go-json v0.10.5 h1:Fq85nIqj+gXn/S5ahsiTlK3TmC85qgirsdTP/+DeaC4=
@@ -295,12 +276,8 @@ github.com/google/s2a-go v0.1.9 h1:LGD7gtMgezd8a/Xak7mEWL0PjoTQFvpRudN895yqKW0=
 github.com/google/s2a-go v0.1.9/go.mod h1:YA0Ei2ZQL3acow2O62kdp9UlnvMmU7kA6Eutn0dXayM=
 github.com/google/s2a-go v0.1.9/go.mod h1:YA0Ei2ZQL3acow2O62kdp9UlnvMmU7kA6Eutn0dXayM=
 github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
 github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
 github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
 github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
-github.com/googleapis/enterprise-certificate-proxy v0.3.12 h1:Fg+zsqzYEs1ZnvmcztTYxhgCBsx3eEhEwQ1W/lHq/sQ=
-github.com/googleapis/enterprise-certificate-proxy v0.3.12/go.mod h1:vqVt9yG9480NtzREnTlmGSBmFrA+bzb0yl0TxoBQXOg=
 github.com/googleapis/enterprise-certificate-proxy v0.3.22 h1:NU4XpII6jD+Dxcot94fqjE+AfJoE/lQP9q3faYGzC/c=
 github.com/googleapis/enterprise-certificate-proxy v0.3.22 h1:NU4XpII6jD+Dxcot94fqjE+AfJoE/lQP9q3faYGzC/c=
 github.com/googleapis/enterprise-certificate-proxy v0.3.22/go.mod h1:L3D/IQExI6LqEjBdXcZQ1WluSgigQmSwBboFstVPM4w=
 github.com/googleapis/enterprise-certificate-proxy v0.3.22/go.mod h1:L3D/IQExI6LqEjBdXcZQ1WluSgigQmSwBboFstVPM4w=
-github.com/googleapis/gax-go/v2 v2.17.0 h1:RksgfBpxqff0EZkDWYuz9q/uWsTVz+kf43LsZ1J6SMc=
-github.com/googleapis/gax-go/v2 v2.17.0/go.mod h1:mzaqghpQp4JDh3HvADwrat+6M3MOIDp5YKHhb9PAgDY=
 github.com/googleapis/gax-go/v2 v2.24.1 h1:AtqTN21IXMMWo99LiEVAiBfNNQmO40d8xUfZI640mc0=
 github.com/googleapis/gax-go/v2 v2.24.1 h1:AtqTN21IXMMWo99LiEVAiBfNNQmO40d8xUfZI640mc0=
 github.com/googleapis/gax-go/v2 v2.24.1/go.mod h1:bWeBei0NVwaNZKb2y1HUBS7gLXIF3/Tu3pq7j8D2Tb0=
 github.com/googleapis/gax-go/v2 v2.24.1/go.mod h1:bWeBei0NVwaNZKb2y1HUBS7gLXIF3/Tu3pq7j8D2Tb0=
 github.com/gorilla/css v1.0.1 h1:ntNaBIghp6JmvWnxbZKANoLyuXTPZ4cAMlo6RyhlbO8=
 github.com/gorilla/css v1.0.1 h1:ntNaBIghp6JmvWnxbZKANoLyuXTPZ4cAMlo6RyhlbO8=
@@ -316,8 +293,8 @@ github.com/hashicorp/go-multierror v1.1.1 h1:H5DkEtf6CXdFp0N0Em5UCwQpXMWke8IA0+l
 github.com/hashicorp/go-multierror v1.1.1/go.mod h1:iw975J/qwKPdAO1clOe2L8331t/9/fmwbPZ6JB6eMoM=
 github.com/hashicorp/go-multierror v1.1.1/go.mod h1:iw975J/qwKPdAO1clOe2L8331t/9/fmwbPZ6JB6eMoM=
 github.com/hashicorp/go-plugin v1.7.0 h1:YghfQH/0QmPNc/AZMTFE3ac8fipZyZECHdDPshfk+mA=
 github.com/hashicorp/go-plugin v1.7.0 h1:YghfQH/0QmPNc/AZMTFE3ac8fipZyZECHdDPshfk+mA=
 github.com/hashicorp/go-plugin v1.7.0/go.mod h1:BExt6KEaIYx804z8k4gRzRLEvxKVb+kn0NMcihqOqb8=
 github.com/hashicorp/go-plugin v1.7.0/go.mod h1:BExt6KEaIYx804z8k4gRzRLEvxKVb+kn0NMcihqOqb8=
-github.com/hashicorp/go-retryablehttp v0.7.7 h1:C8hUCYzor8PIfXHa4UrZkU4VvK8o9ISHxT2Q8+VepXU=
-github.com/hashicorp/go-retryablehttp v0.7.7/go.mod h1:pkQpWZeYWskR+D1tR2O5OcBFOxfA7DoAO6xtkuQnHTk=
+github.com/hashicorp/go-retryablehttp v0.7.8 h1:ylXZWnqa7Lhqpk0L1P1LzDtGcCR0rPVUrx/c8Unxc48=
+github.com/hashicorp/go-retryablehttp v0.7.8/go.mod h1:rjiScheydd+CxvumBsIrFKlx3iS0jrZ7LvzFGFmuKbw=
 github.com/hashicorp/yamux v0.1.2 h1:XtB8kyFOyHXYVFnwT5C3+Bdo8gArse7j2AQ0DA0Uey8=
 github.com/hashicorp/yamux v0.1.2 h1:XtB8kyFOyHXYVFnwT5C3+Bdo8gArse7j2AQ0DA0Uey8=
 github.com/hashicorp/yamux v0.1.2/go.mod h1:C+zze2n6e/7wshOZep2A70/aQU6QBRWJO/G6FT1wIns=
 github.com/hashicorp/yamux v0.1.2/go.mod h1:C+zze2n6e/7wshOZep2A70/aQU6QBRWJO/G6FT1wIns=
 github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8=
 github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8=
@@ -356,8 +333,8 @@ github.com/kubecost/events v0.0.8 h1:FEglMSOGkjiSZT2FnSYM99s2M4DMiBOgHVheM7Vnurs
 github.com/kubecost/events v0.0.8/go.mod h1:PXnE7CSZs3OulOLcB8baQENploBp4NM7ERZVBCqNi4A=
 github.com/kubecost/events v0.0.8/go.mod h1:PXnE7CSZs3OulOLcB8baQENploBp4NM7ERZVBCqNi4A=
 github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc=
 github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc=
 github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw=
 github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw=
-github.com/leodido/go-urn v1.4.0 h1:WT9HwE9SGECu3lg4d/dIA+jxlljEa1/ffXKmRjqdmIQ=
-github.com/leodido/go-urn v1.4.0/go.mod h1:bvxc+MVxLKB4z00jd1z+Dvzr47oO32F/QSNjSBOlFxI=
+github.com/leodido/go-urn v1.5.0 h1:pLqT2kq1zpHW/1D18QMjMpdtX7cekxqtJJjg5ANyWw0=
+github.com/leodido/go-urn v1.5.0/go.mod h1:9BORnCDhdPBJNDEX+w1bJisa8yOKYi116VeO96s4ifE=
 github.com/mattn/go-colorable v0.1.9/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc=
 github.com/mattn/go-colorable v0.1.9/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc=
 github.com/mattn/go-colorable v0.1.12/go.mod h1:u5H1YNBxpqRaxsYJYSkiCWKzEfiAb1Gb520KVy5xxl4=
 github.com/mattn/go-colorable v0.1.12/go.mod h1:u5H1YNBxpqRaxsYJYSkiCWKzEfiAb1Gb520KVy5xxl4=
 github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg=
 github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg=
@@ -392,8 +369,16 @@ github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8m
 github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f h1:KUppIJq7/+SVif2QVs3tOP0zanoHgBEVAwHxUSIzRqU=
 github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f h1:KUppIJq7/+SVif2QVs3tOP0zanoHgBEVAwHxUSIzRqU=
 github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
 github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
 github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno=
 github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno=
+github.com/nxadm/tail v1.4.8 h1:nPr65rt6Y5JFSKQO7qToXr7pePgD6Gwiw05lkbyAQTE=
+github.com/nxadm/tail v1.4.8/go.mod h1:+ncqLTQzXmGhMZNUePPaPqPvBxHAIsmXswZKocGu+AU=
 github.com/oklog/run v1.2.0 h1:O8x3yXwah4A73hJdlrwo/2X6J62gE5qTMusH0dvz60E=
 github.com/oklog/run v1.2.0 h1:O8x3yXwah4A73hJdlrwo/2X6J62gE5qTMusH0dvz60E=
 github.com/oklog/run v1.2.0/go.mod h1:mgDbKRSwPhJfesJ4PntqFUbKQRZ50NgmZTSPlFA0YFk=
 github.com/oklog/run v1.2.0/go.mod h1:mgDbKRSwPhJfesJ4PntqFUbKQRZ50NgmZTSPlFA0YFk=
+github.com/oklog/ulid/v2 v2.1.2 h1:IEclFb9JNvzYA6MW2SCxbLzcHTVsfqm3PrqGQJH5zec=
+github.com/oklog/ulid/v2 v2.1.2/go.mod h1:rcEKHmBBKfef9DhnvX7y1HZBYxjXb0cP5ExxNsTT1QQ=
+github.com/onsi/ginkgo v1.16.5 h1:8xi0RTUf59SOSfEtZMvwTvXYMzG4gV23XVHOZiXNtnE=
+github.com/onsi/ginkgo v1.16.5/go.mod h1:+E8gABHa3K6zRBolWtd+ROzc/U5bkGt0FwiG042wbpU=
+github.com/onsi/gomega v1.43.0 h1:VlG/1FxqNxhSO+lq/OHBNaaqwiBK/mO8JbVkX9Y+FeU=
+github.com/onsi/gomega v1.43.0/go.mod h1:REff/hsDsodHoKlWsP2mAPhu1+5/6hVYNf9rIEBpeSg=
 github.com/opencost/bingen v0.2.0 h1:tx9QJmCzjvHLWvdJX9EzwQfoiRUaXA8jCkQYyTgGNpk=
 github.com/opencost/bingen v0.2.0 h1:tx9QJmCzjvHLWvdJX9EzwQfoiRUaXA8jCkQYyTgGNpk=
 github.com/opencost/bingen v0.2.0/go.mod h1:+E6xAqbLPnlYye2Mnnt0u0StHoLpBwEP57A5g33+W8o=
 github.com/opencost/bingen v0.2.0/go.mod h1:+E6xAqbLPnlYye2Mnnt0u0StHoLpBwEP57A5g33+W8o=
 github.com/opentracing/opentracing-go v1.2.1-0.20220228012449-10b1cf09e00b h1:FfH+VrHHk6Lxt9HdVS0PXzSXFyS2NbZKXv33FYPol0A=
 github.com/opentracing/opentracing-go v1.2.1-0.20220228012449-10b1cf09e00b h1:FfH+VrHHk6Lxt9HdVS0PXzSXFyS2NbZKXv33FYPol0A=
@@ -402,6 +387,7 @@ github.com/oracle/oci-go-sdk/v65 v65.117.1 h1:DurEtbj8xEuF2B6K73uifNqWJ4dh6CJPAD
 github.com/oracle/oci-go-sdk/v65 v65.117.1/go.mod h1:oo33NDf2XPqx3/N6oLG4jFlrqJ0xu4Rlt9SfuAbtDFs=
 github.com/oracle/oci-go-sdk/v65 v65.117.1/go.mod h1:oo33NDf2XPqx3/N6oLG4jFlrqJ0xu4Rlt9SfuAbtDFs=
 github.com/patrickmn/go-cache v2.1.0+incompatible h1:HRMgzkcYKYpi3C8ajMPV8OFXaaRUnok+kx1WdO15EQc=
 github.com/patrickmn/go-cache v2.1.0+incompatible h1:HRMgzkcYKYpi3C8ajMPV8OFXaaRUnok+kx1WdO15EQc=
 github.com/patrickmn/go-cache v2.1.0+incompatible/go.mod h1:3Qf8kWWT7OJRJbdiICTKqZju1ZixQ/KpMGzzAfe6+WQ=
 github.com/patrickmn/go-cache v2.1.0+incompatible/go.mod h1:3Qf8kWWT7OJRJbdiICTKqZju1ZixQ/KpMGzzAfe6+WQ=
+github.com/pborman/getopt v0.0.0-20170112200414-7148bc3a4c30/go.mod h1:85jBQOZwpVEaDAr341tbn15RS4fCAsIst0qp7i8ex1o=
 github.com/pelletier/go-toml/v2 v2.2.4 h1:mye9XuhQ6gvn5h28+VilKrrPoQVanw5PMw/TB0t5Ec4=
 github.com/pelletier/go-toml/v2 v2.2.4 h1:mye9XuhQ6gvn5h28+VilKrrPoQVanw5PMw/TB0t5Ec4=
 github.com/pelletier/go-toml/v2 v2.2.4/go.mod h1:2gIqNv+qfxSVS7cM2xJQKtLSTLUE9V8t9Stt+h56mCY=
 github.com/pelletier/go-toml/v2 v2.2.4/go.mod h1:2gIqNv+qfxSVS7cM2xJQKtLSTLUE9V8t9Stt+h56mCY=
 github.com/philhofer/fwd v1.2.0 h1:e6DnBTl7vGY+Gz322/ASL4Gyp1FspeMvx1RNDoToZuM=
 github.com/philhofer/fwd v1.2.0 h1:e6DnBTl7vGY+Gz322/ASL4Gyp1FspeMvx1RNDoToZuM=
@@ -457,8 +443,6 @@ github.com/spf13/pflag v1.0.10 h1:4EBh2KAYBwaONj6b2Ye1GiHfwjqyROoF4RwYO+vPwFk=
 github.com/spf13/pflag v1.0.10/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
 github.com/spf13/pflag v1.0.10/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
 github.com/spf13/viper v1.21.0 h1:x5S+0EU27Lbphp4UKm1C+1oQO+rKx36vfCoaVebLFSU=
 github.com/spf13/viper v1.21.0 h1:x5S+0EU27Lbphp4UKm1C+1oQO+rKx36vfCoaVebLFSU=
 github.com/spf13/viper v1.21.0/go.mod h1:P0lhsswPGWD/1lZJ9ny3fYnVqxiegrlNrEmgLjbTCAY=
 github.com/spf13/viper v1.21.0/go.mod h1:P0lhsswPGWD/1lZJ9ny3fYnVqxiegrlNrEmgLjbTCAY=
-github.com/spiffe/go-spiffe/v2 v2.6.0 h1:l+DolpxNWYgruGQVV0xsfeya3CsC7m8iBzDnMpsbLuo=
-github.com/spiffe/go-spiffe/v2 v2.6.0/go.mod h1:gm2SeUoMZEtpnzPNs2Csc0D/gX33k1xIx7lEzqblHEs=
 github.com/spiffe/go-spiffe/v2 v2.7.0 h1:uXe1MflJoHw58wAUvxVlcM7WpKtijWG7I1UidcGh6g4=
 github.com/spiffe/go-spiffe/v2 v2.7.0 h1:uXe1MflJoHw58wAUvxVlcM7WpKtijWG7I1UidcGh6g4=
 github.com/spiffe/go-spiffe/v2 v2.7.0/go.mod h1:47Q0Q9/AqGha8QLHp+kxpH4Wca7X7EnOtlIJy3mxZ3U=
 github.com/spiffe/go-spiffe/v2 v2.7.0/go.mod h1:47Q0Q9/AqGha8QLHp+kxpH4Wca7X7EnOtlIJy3mxZ3U=
 github.com/stackitcloud/stackit-sdk-go/core v0.24.0 h1:kHCcezCJ5OGSP7RRuGOxD5rF2wejpkEiRr/OdvNcuPQ=
 github.com/stackitcloud/stackit-sdk-go/core v0.24.0 h1:kHCcezCJ5OGSP7RRuGOxD5rF2wejpkEiRr/OdvNcuPQ=
@@ -466,14 +450,11 @@ github.com/stackitcloud/stackit-sdk-go/core v0.24.0/go.mod h1:osMglDby4csGZ5sIfh
 github.com/stackitcloud/stackit-sdk-go/services/cost v0.2.1 h1:U2sBfMeBCdZUvCW+vqPbo+HPtGxMjCF21PYyQncPnpg=
 github.com/stackitcloud/stackit-sdk-go/services/cost v0.2.1 h1:U2sBfMeBCdZUvCW+vqPbo+HPtGxMjCF21PYyQncPnpg=
 github.com/stackitcloud/stackit-sdk-go/services/cost v0.2.1/go.mod h1:Qt/scoasQrONlQ9FauvafUJ/3sP3xIFnhBQC8/Yhqgc=
 github.com/stackitcloud/stackit-sdk-go/services/cost v0.2.1/go.mod h1:Qt/scoasQrONlQ9FauvafUJ/3sP3xIFnhBQC8/Yhqgc=
 github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
 github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
-github.com/stretchr/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY=
-github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA=
 github.com/stretchr/objx v0.5.3 h1:jmXUvGomnU1o3W/V5h2VEradbpJDwGrzugQQvL0POH4=
 github.com/stretchr/objx v0.5.3 h1:jmXUvGomnU1o3W/V5h2VEradbpJDwGrzugQQvL0POH4=
+github.com/stretchr/objx v0.5.3/go.mod h1:rDQraq+vQZU7Fde9LOZLr8Tax6zZvy4kuNKF+QYS+U0=
 github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
 github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
 github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
 github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
 github.com/stretchr/testify v1.7.2/go.mod h1:R6va5+xMeoiuVRoj+gSkQ7d3FALtqAAGI1FQKckRals=
 github.com/stretchr/testify v1.7.2/go.mod h1:R6va5+xMeoiuVRoj+gSkQ7d3FALtqAAGI1FQKckRals=
-github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=
-github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
 github.com/stretchr/testify v1.12.1 h1:EuwCh5fleGS7H32xRwO3wRGT7DxrDhLAT6FF8MpWDWE=
 github.com/stretchr/testify v1.12.1 h1:EuwCh5fleGS7H32xRwO3wRGT7DxrDhLAT6FF8MpWDWE=
 github.com/stretchr/testify v1.12.1/go.mod h1:MDEgiDPPsNp5cuIrHPPCyornHKgEVbtFUmoNlxoYthg=
 github.com/stretchr/testify v1.12.1/go.mod h1:MDEgiDPPsNp5cuIrHPPCyornHKgEVbtFUmoNlxoYthg=
 github.com/subosito/gotenv v1.6.0 h1:9NlTDc1FTs4qu0DDq7AEtTPNw6SVm7uBMsUCUjABIf8=
 github.com/subosito/gotenv v1.6.0 h1:9NlTDc1FTs4qu0DDq7AEtTPNw6SVm7uBMsUCUjABIf8=
@@ -497,48 +478,32 @@ github.com/zeebo/xxh3 v1.0.2 h1:xZmwmqxHZA8AI603jOQ0tMqmBr9lPeFwGg6d+xy9DC0=
 github.com/zeebo/xxh3 v1.0.2/go.mod h1:5NWz9Sef7zIDm2JHfFlcQvNekmcEl9ekUZQQKCYaDcA=
 github.com/zeebo/xxh3 v1.0.2/go.mod h1:5NWz9Sef7zIDm2JHfFlcQvNekmcEl9ekUZQQKCYaDcA=
 go.opentelemetry.io/auto/sdk v1.2.1 h1:jXsnJ4Lmnqd11kwkBV2LgLoFMZKizbCi5fNZ/ipaZ64=
 go.opentelemetry.io/auto/sdk v1.2.1 h1:jXsnJ4Lmnqd11kwkBV2LgLoFMZKizbCi5fNZ/ipaZ64=
 go.opentelemetry.io/auto/sdk v1.2.1/go.mod h1:KRTj+aOaElaLi+wW1kO/DZRXwkF4C5xPbEe3ZiIhN7Y=
 go.opentelemetry.io/auto/sdk v1.2.1/go.mod h1:KRTj+aOaElaLi+wW1kO/DZRXwkF4C5xPbEe3ZiIhN7Y=
-go.opentelemetry.io/contrib/detectors/gcp v1.40.0 h1:Awaf8gmW99tZTOWqkLCOl6aw1/rxAWVlHsHIZ3fT2sA=
-go.opentelemetry.io/contrib/detectors/gcp v1.40.0/go.mod h1:99OY9ZCqyLkzJLTh5XhECpLRSxcZl+ZDKBEO+jMBFR4=
 go.opentelemetry.io/contrib/detectors/gcp v1.44.0 h1:NmLfL734pJhM0JKaYd2Y28+nY9dPRWYAAbxhRCrKXPw=
 go.opentelemetry.io/contrib/detectors/gcp v1.44.0 h1:NmLfL734pJhM0JKaYd2Y28+nY9dPRWYAAbxhRCrKXPw=
 go.opentelemetry.io/contrib/detectors/gcp v1.44.0/go.mod h1:tNAsgd8avTGke1+MndXlU5Cru4PQ9Ai/cCNWQv/ZJ/s=
 go.opentelemetry.io/contrib/detectors/gcp v1.44.0/go.mod h1:tNAsgd8avTGke1+MndXlU5Cru4PQ9Ai/cCNWQv/ZJ/s=
-go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.65.0 h1:XmiuHzgJt067+a6kwyAzkhXooYVv3/TOw9cM2VfJgUM=
-go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.65.0/go.mod h1:KDgtbWKTQs4bM+VPUr6WlL9m/WXcmkCcBlIzqxPGzmI=
 go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.67.0 h1:yI1/OhfEPy7J9eoa6Sj051C7n5dvpj0QX8g4sRchg04=
 go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.67.0 h1:yI1/OhfEPy7J9eoa6Sj051C7n5dvpj0QX8g4sRchg04=
 go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.67.0/go.mod h1:NoUCKYWK+3ecatC4HjkRktREheMeEtrXoQxrqYFeHSc=
 go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.67.0/go.mod h1:NoUCKYWK+3ecatC4HjkRktREheMeEtrXoQxrqYFeHSc=
-go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.65.0 h1:7iP2uCb7sGddAr30RRS6xjKy7AZ2JtTOPA3oolgVSw8=
-go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.65.0/go.mod h1:c7hN3ddxs/z6q9xwvfLPk+UHlWRQyaeR1LdgfL/66l0=
 go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.67.0 h1:OyrsyzuttWTSur2qN/Lm0m2a8yqyIjUVBZcxFPuXq2o=
 go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.67.0 h1:OyrsyzuttWTSur2qN/Lm0m2a8yqyIjUVBZcxFPuXq2o=
 go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.67.0/go.mod h1:C2NGBr+kAB4bk3xtMXfZ94gqFDtg/GkI7e9zqGh5Beg=
 go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.67.0/go.mod h1:C2NGBr+kAB4bk3xtMXfZ94gqFDtg/GkI7e9zqGh5Beg=
-go.opentelemetry.io/otel v1.41.0 h1:YlEwVsGAlCvczDILpUXpIpPSL/VPugt7zHThEMLce1c=
-go.opentelemetry.io/otel v1.41.0/go.mod h1:Yt4UwgEKeT05QbLwbyHXEwhnjxNO6D8L5PQP51/46dE=
 go.opentelemetry.io/otel v1.44.0 h1:JjwHmHpA4iZ3wBxluu2fbbE7j4kqlE8jXyAyPXH7HqU=
 go.opentelemetry.io/otel v1.44.0 h1:JjwHmHpA4iZ3wBxluu2fbbE7j4kqlE8jXyAyPXH7HqU=
 go.opentelemetry.io/otel v1.44.0/go.mod h1:BMgjTHL9WPRlRjL2oZCBTL4whCGtXch2H4BhOPIAyYc=
 go.opentelemetry.io/otel v1.44.0/go.mod h1:BMgjTHL9WPRlRjL2oZCBTL4whCGtXch2H4BhOPIAyYc=
-go.opentelemetry.io/otel/exporters/stdout/stdoutmetric v1.39.0 h1:5gn2urDL/FBnK8OkCfD1j3/ER79rUuTYmCvlXBKeYL8=
-go.opentelemetry.io/otel/exporters/stdout/stdoutmetric v1.39.0/go.mod h1:0fBG6ZJxhqByfFZDwSwpZGzJU671HkwpWaNe2t4VUPI=
 go.opentelemetry.io/otel/exporters/stdout/stdoutmetric v1.43.0 h1:TC+BewnDpeiAmcscXbGMfxkO+mwYUwE/VySwvw88PfA=
 go.opentelemetry.io/otel/exporters/stdout/stdoutmetric v1.43.0 h1:TC+BewnDpeiAmcscXbGMfxkO+mwYUwE/VySwvw88PfA=
-go.opentelemetry.io/otel/metric v1.41.0 h1:rFnDcs4gRzBcsO9tS8LCpgR0dxg4aaxWlJxCno7JlTQ=
-go.opentelemetry.io/otel/metric v1.41.0/go.mod h1:xPvCwd9pU0VN8tPZYzDZV/BMj9CM9vs00GuBjeKhJps=
+go.opentelemetry.io/otel/exporters/stdout/stdoutmetric v1.43.0/go.mod h1:J/ZyF4vfPwsSr9xJSPyQ4LqtcTPULFR64KwTikGLe+A=
 go.opentelemetry.io/otel/metric v1.44.0 h1:1w0gILTcHdr3YI+ixLyjemwrVnsMURbTZFrSYCdDdmc=
 go.opentelemetry.io/otel/metric v1.44.0 h1:1w0gILTcHdr3YI+ixLyjemwrVnsMURbTZFrSYCdDdmc=
 go.opentelemetry.io/otel/metric v1.44.0/go.mod h1:8O7hanEPBNgEMmybD3s2VBKcgWOCsA6tzHBPODAiquo=
 go.opentelemetry.io/otel/metric v1.44.0/go.mod h1:8O7hanEPBNgEMmybD3s2VBKcgWOCsA6tzHBPODAiquo=
-go.opentelemetry.io/otel/sdk v1.41.0 h1:YPIEXKmiAwkGl3Gu1huk1aYWwtpRLeskpV+wPisxBp8=
-go.opentelemetry.io/otel/sdk v1.41.0/go.mod h1:ahFdU0G5y8IxglBf0QBJXgSe7agzjE4GiTJ6HT9ud90=
+go.opentelemetry.io/otel/metric/x v0.66.0 h1:YkCrx1zLOChi9ZcZ6euupOcsgzbVlec7D/xoEU1+cTA=
+go.opentelemetry.io/otel/metric/x v0.66.0/go.mod h1:d1+BDj9t96do0/1LoU1ayfCv79ZgNE41qbhBvnMOBZk=
 go.opentelemetry.io/otel/sdk v1.44.0 h1:nHYwb9lK+fJPU/dnT6s7W7Z8itMWyqrnVfbheVYrZ58=
 go.opentelemetry.io/otel/sdk v1.44.0 h1:nHYwb9lK+fJPU/dnT6s7W7Z8itMWyqrnVfbheVYrZ58=
 go.opentelemetry.io/otel/sdk v1.44.0/go.mod h1:Osuydd3Se74nqjAKxid74N5eC+jfEqfTegHRnq58oK0=
 go.opentelemetry.io/otel/sdk v1.44.0/go.mod h1:Osuydd3Se74nqjAKxid74N5eC+jfEqfTegHRnq58oK0=
-go.opentelemetry.io/otel/sdk/metric v1.41.0 h1:siZQIYBAUd1rlIWQT2uCxWJxcCO7q3TriaMlf08rXw8=
-go.opentelemetry.io/otel/sdk/metric v1.41.0/go.mod h1:HNBuSvT7ROaGtGI50ArdRLUnvRTRGniSUZbxiWxSO8Y=
 go.opentelemetry.io/otel/sdk/metric v1.44.0 h1:3LlKgI+VjbVsjNRFZJZAJ30WjXC5VkNRks6si09iEfI=
 go.opentelemetry.io/otel/sdk/metric v1.44.0 h1:3LlKgI+VjbVsjNRFZJZAJ30WjXC5VkNRks6si09iEfI=
 go.opentelemetry.io/otel/sdk/metric v1.44.0/go.mod h1:5B5pMARnXxKhltooO4xUuCBorl65a4EpnTalObqOigA=
 go.opentelemetry.io/otel/sdk/metric v1.44.0/go.mod h1:5B5pMARnXxKhltooO4xUuCBorl65a4EpnTalObqOigA=
-go.opentelemetry.io/otel/trace v1.41.0 h1:Vbk2co6bhj8L59ZJ6/xFTskY+tGAbOnCtQGVVa9TIN0=
-go.opentelemetry.io/otel/trace v1.41.0/go.mod h1:U1NU4ULCoxeDKc09yCWdWe+3QoyweJcISEVa1RBzOis=
 go.opentelemetry.io/otel/trace v1.44.0 h1:jxF5CsGYCe74MCRx2X4g7WsY/VBKRqqpNvXlX/6gtIk=
 go.opentelemetry.io/otel/trace v1.44.0 h1:jxF5CsGYCe74MCRx2X4g7WsY/VBKRqqpNvXlX/6gtIk=
 go.opentelemetry.io/otel/trace v1.44.0/go.mod h1:oLl1jrMQAVo6v3GAggN+1VH9VIz9iUSvW53sW1Q8PIE=
 go.opentelemetry.io/otel/trace v1.44.0/go.mod h1:oLl1jrMQAVo6v3GAggN+1VH9VIz9iUSvW53sW1Q8PIE=
 go.uber.org/atomic v1.9.0 h1:ECmE8Bn/WFTYwEW/bpKD3M8VtR/zQVbavAoalC1PYyE=
 go.uber.org/atomic v1.9.0 h1:ECmE8Bn/WFTYwEW/bpKD3M8VtR/zQVbavAoalC1PYyE=
 go.uber.org/atomic v1.9.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc=
 go.uber.org/atomic v1.9.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc=
 go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto=
 go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto=
 go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE=
 go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE=
-go.yaml.in/yaml/v2 v2.4.3 h1:6gvOSjQoTB3vt1l+CU+tSyi/HOjfOjRLJ4YwYZGwRO0=
-go.yaml.in/yaml/v2 v2.4.3/go.mod h1:zSxWcmIDjOzPXpjlTTbAsKokqkDNAVtZO0WOMiT90s8=
-go.yaml.in/yaml/v3 v3.0.4 h1:tfq32ie2Jv2UxXFdLJdh3jXuOzWiL1fo0bu/FbuKpbc=
+go.yaml.in/yaml/v2 v2.4.4 h1:tuyd0P+2Ont/d6e2rl3be67goVK4R6deVxCUX5vyPaQ=
+go.yaml.in/yaml/v2 v2.4.4/go.mod h1:gMZqIpDtDqOfM0uNfy0SkpRhvUryYH0Z6wdMYcacYXQ=
 go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg=
 go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg=
 go.yaml.in/yaml/v3 v3.0.5 h1:N6y/pJk8buWs9NY5ERU2HSMfm+IuD/OtfdAnq6kESPw=
 go.yaml.in/yaml/v3 v3.0.5 h1:N6y/pJk8buWs9NY5ERU2HSMfm+IuD/OtfdAnq6kESPw=
 go.yaml.in/yaml/v3 v3.0.5/go.mod h1:HVTZu1O7/Vkt2N+BFy8Zza+lnLsABggaTM2ZpNIGuKg=
 go.yaml.in/yaml/v3 v3.0.5/go.mod h1:HVTZu1O7/Vkt2N+BFy8Zza+lnLsABggaTM2ZpNIGuKg=
@@ -549,10 +514,6 @@ golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8U
 golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
 golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
 golang.org/x/crypto v0.0.0-20220722155217-630584e8d5aa/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
 golang.org/x/crypto v0.0.0-20220722155217-630584e8d5aa/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
 golang.org/x/crypto v0.17.0/go.mod h1:gCAAfMLgwOJRpTjQ2zCCt2OcSfYMTeZVSRtQlPC7Nq4=
 golang.org/x/crypto v0.17.0/go.mod h1:gCAAfMLgwOJRpTjQ2zCCt2OcSfYMTeZVSRtQlPC7Nq4=
-golang.org/x/crypto v0.49.0 h1:+Ng2ULVvLHnJ/ZFEq4KdcDd/cfjrrjjNSXNzxg0Y4U4=
-golang.org/x/crypto v0.49.0/go.mod h1:ErX4dUh2UM+CFYiXZRTcMpEcN8b/1gxEuv3nODoYtCA=
-golang.org/x/crypto v0.53.0 h1:QZ4Muo8THX6CizN2vPPd5fBGHyogrdK9fG4wLPFUsto=
-golang.org/x/crypto v0.53.0/go.mod h1:DNLU434OwVakk9PzuwV8w62mAJpRJL3vsgcfp4Qnsio=
 golang.org/x/crypto v0.57.0 h1:3ZVCjf8Ggz7zneR/EHRVx68Ctf+2pmIMP2UFhh9cC6M=
 golang.org/x/crypto v0.57.0 h1:3ZVCjf8Ggz7zneR/EHRVx68Ctf+2pmIMP2UFhh9cC6M=
 golang.org/x/crypto v0.57.0/go.mod h1:Fdz0i5U6CoizGwLda9DttjSk6qlZo25zYNtR+ycvuZA=
 golang.org/x/crypto v0.57.0/go.mod h1:Fdz0i5U6CoizGwLda9DttjSk6qlZo25zYNtR+ycvuZA=
 golang.org/x/exp v0.0.0-20180321215751-8460e604b9de/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
 golang.org/x/exp v0.0.0-20180321215751-8460e604b9de/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
@@ -569,10 +530,6 @@ golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCc
 golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY=
 golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY=
 golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
 golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
 golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
 golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
-golang.org/x/mod v0.34.0 h1:xIHgNUUnW6sYkcM5Jleh05DvLOtwc6RitGHbDk4akRI=
-golang.org/x/mod v0.34.0/go.mod h1:ykgH52iCZe79kzLLMhyCUzhMci+nQj+0XkbXpNYtVjY=
-golang.org/x/mod v0.37.0 h1:vF1DjpVEshcIqoEaauuHebaLk1O1forxjxBaVn884JQ=
-golang.org/x/mod v0.37.0/go.mod h1:m8S8VeM9r4dzDwjrKO0a1sZP3YjeMamRRlD+fmR2Q/0=
 golang.org/x/mod v0.41.0 h1:qJmnOUb4YB+FsEuM3HcWucdZASCPGhsX6uljO6pog0c=
 golang.org/x/mod v0.41.0 h1:qJmnOUb4YB+FsEuM3HcWucdZASCPGhsX6uljO6pog0c=
 golang.org/x/mod v0.41.0/go.mod h1:Ek9pY8RKWXwsWvd3rQiHYtMqkjSUV+s1Rj7j4H5Ur6o=
 golang.org/x/mod v0.41.0/go.mod h1:Ek9pY8RKWXwsWvd3rQiHYtMqkjSUV+s1Rj7j4H5Ur6o=
 golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
 golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
@@ -582,23 +539,13 @@ golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qx
 golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
 golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
 golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
 golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
 golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg=
 golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg=
-golang.org/x/net v0.52.0 h1:He/TN1l0e4mmR3QqHMT2Xab3Aj3L9qjbhRm78/6jrW0=
-golang.org/x/net v0.52.0/go.mod h1:R1MAz7uMZxVMualyPXb+VaqGSa3LIaUqk0eEt3w36Sw=
-golang.org/x/net v0.56.0 h1:Rw8j/hFzGvJUZwNBXnAtf5sVDVt+65SK2C7IxCxZt5o=
-golang.org/x/net v0.56.0/go.mod h1:D3Ku6r+V6JROoZK144D2XfMHFcMq/0zSfLelVTCFKec=
 golang.org/x/net v0.59.0 h1:5zfYln+w5XCxwrnMMJPufRgNoXEaGxl0wo5GqPXyues=
 golang.org/x/net v0.59.0 h1:5zfYln+w5XCxwrnMMJPufRgNoXEaGxl0wo5GqPXyues=
 golang.org/x/net v0.59.0/go.mod h1:2DA/G1UfVbCpQPeWTmMPGY7Cs2PkBkwu743bVX5PIVg=
 golang.org/x/net v0.59.0/go.mod h1:2DA/G1UfVbCpQPeWTmMPGY7Cs2PkBkwu743bVX5PIVg=
-golang.org/x/oauth2 v0.35.0 h1:Mv2mzuHuZuY2+bkyWXIHMfhNdJAdwW3FuWeCPYN5GVQ=
-golang.org/x/oauth2 v0.35.0/go.mod h1:lzm5WQJQwKZ3nwavOZ3IS5Aulzxi68dUSgRHujetwEA=
 golang.org/x/oauth2 v0.37.0 h1:JUlcxA8oAtauLfiH8FX2/FkAWHAdi0QtGCGc+hofE98=
 golang.org/x/oauth2 v0.37.0 h1:JUlcxA8oAtauLfiH8FX2/FkAWHAdi0QtGCGc+hofE98=
 golang.org/x/oauth2 v0.37.0/go.mod h1:IxwZNxUULJmpBFf9K/9NTMSIfZZuvuTy1gGxhigP/58=
 golang.org/x/oauth2 v0.37.0/go.mod h1:IxwZNxUULJmpBFf9K/9NTMSIfZZuvuTy1gGxhigP/58=
 golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
 golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
 golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
 golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
 golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
 golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
-golang.org/x/sync v0.20.0 h1:e0PTpb7pjO8GAtTs2dQ6jYa5BWYlMuX047Dco/pItO4=
-golang.org/x/sync v0.20.0/go.mod h1:9xrNwdLfx4jkKbNva9FpL6vEN7evnE43NNNJQ2LF3+0=
-golang.org/x/sync v0.22.0 h1:SZjpbeLmrCk4xhRSZFNZW5gFUeCeFgjekvI/+gfScek=
-golang.org/x/sync v0.22.0/go.mod h1:9xrNwdLfx4jkKbNva9FpL6vEN7evnE43NNNJQ2LF3+0=
 golang.org/x/sync v0.23.0 h1:KameEIfc1IkluZyXWLn39Wd4tURc6GbCiISGiZm2bQk=
 golang.org/x/sync v0.23.0 h1:KameEIfc1IkluZyXWLn39Wd4tURc6GbCiISGiZm2bQk=
 golang.org/x/sync v0.23.0/go.mod h1:sUUOizhqBxiL6pEWpqNLUiaJn1ShEbZ6BBqskPbjZm0=
 golang.org/x/sync v0.23.0/go.mod h1:sUUOizhqBxiL6pEWpqNLUiaJn1ShEbZ6BBqskPbjZm0=
 golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
 golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
@@ -621,16 +568,8 @@ golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.15.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
 golang.org/x/sys v0.15.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
-golang.org/x/sys v0.42.0 h1:omrd2nAlyT5ESRdCLYdm3+fMfNFE/+Rf4bDIQImRJeo=
-golang.org/x/sys v0.42.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw=
-golang.org/x/sys v0.46.0 h1:noSf2Fq6F8DBgS+LysIkx7rIExoNHJsxOAtPp4rthXw=
-golang.org/x/sys v0.46.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw=
 golang.org/x/sys v0.48.0 h1:bbX/i/6MgT9BVLM9RT1thmxL04yeTAhbEz4SyadbXoo=
 golang.org/x/sys v0.48.0 h1:bbX/i/6MgT9BVLM9RT1thmxL04yeTAhbEz4SyadbXoo=
 golang.org/x/sys v0.48.0/go.mod h1:hNLxWAXmnKAxqDtdwIYC4bM9oQPEecfsnNMuSxOs3og=
 golang.org/x/sys v0.48.0/go.mod h1:hNLxWAXmnKAxqDtdwIYC4bM9oQPEecfsnNMuSxOs3og=
-golang.org/x/telemetry v0.0.0-20260311193753-579e4da9a98c h1:6a8FdnNk6bTXBjR4AGKFgUKuo+7GnR3FX5L7CbveeZc=
-golang.org/x/telemetry v0.0.0-20260311193753-579e4da9a98c/go.mod h1:TpUTTEp9frx7rTdLpC9gFG9kdI7zVLFTFFlqaH2Cncw=
-golang.org/x/telemetry v0.0.0-20260625142307-59b4966ccb57 h1:nwGZBCt+FnXUrGsj5vjzAsEmkcaFvd82BbOjECiFYZc=
-golang.org/x/telemetry v0.0.0-20260625142307-59b4966ccb57/go.mod h1:3AWMyWHS+caVoiEXpiq6+tzKA40J4vQT3MYr80ZtQpc=
 golang.org/x/telemetry v0.0.0-20260811182544-a038080d80e5 h1:ZUSxONxc981v7AW7QUg+I9WwZzSTTJ019ENBYr5pV/Q=
 golang.org/x/telemetry v0.0.0-20260811182544-a038080d80e5 h1:ZUSxONxc981v7AW7QUg+I9WwZzSTTJ019ENBYr5pV/Q=
 golang.org/x/telemetry v0.0.0-20260811182544-a038080d80e5/go.mod h1:LVehoXe41cL5SCVQilsV7Gg6BNG+Js6P9PhSbYTIUkQ=
 golang.org/x/telemetry v0.0.0-20260811182544-a038080d80e5/go.mod h1:LVehoXe41cL5SCVQilsV7Gg6BNG+Js6P9PhSbYTIUkQ=
 golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
 golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
@@ -638,10 +577,6 @@ golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuX
 golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
 golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
 golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo=
 golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo=
 golang.org/x/term v0.15.0/go.mod h1:BDl952bC7+uMoWR75FIrCDx79TPU9oHkTZ9yRbYOrX0=
 golang.org/x/term v0.15.0/go.mod h1:BDl952bC7+uMoWR75FIrCDx79TPU9oHkTZ9yRbYOrX0=
-golang.org/x/term v0.41.0 h1:QCgPso/Q3RTJx2Th4bDLqML4W6iJiaXFq2/ftQF13YU=
-golang.org/x/term v0.41.0/go.mod h1:3pfBgksrReYfZ5lvYM0kSO0LIkAl4Yl2bXOkKP7Ec2A=
-golang.org/x/term v0.44.0 h1:0rLvDRCtNj0gZkyIXhCyOb2OAzEhLVqc4B+hrsBhrmc=
-golang.org/x/term v0.44.0/go.mod h1:7ze4MdzUzLXpSAoFP1H0bOI9aXDqveSvatT5vKcFh2Y=
 golang.org/x/term v0.46.0 h1:3+OXuTbaKDgwk8jTi3aSLHRlmWqHEUDUtxnbFigO4YE=
 golang.org/x/term v0.46.0 h1:3+OXuTbaKDgwk8jTi3aSLHRlmWqHEUDUtxnbFigO4YE=
 golang.org/x/term v0.46.0/go.mod h1:+K02xbkittuwc0Am4abfA3Fc+XRGXkvBXNO88NCXPoc=
 golang.org/x/term v0.46.0/go.mod h1:+K02xbkittuwc0Am4abfA3Fc+XRGXkvBXNO88NCXPoc=
 golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
 golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
@@ -651,14 +586,8 @@ golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
 golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
 golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
 golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
 golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
 golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
 golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
-golang.org/x/text v0.36.0 h1:JfKh3XmcRPqZPKevfXVpI1wXPTqbkE5f7JA92a55Yxg=
-golang.org/x/text v0.36.0/go.mod h1:NIdBknypM8iqVmPiuco0Dh6P5Jcdk8lJL0CUebqK164=
-golang.org/x/text v0.40.0 h1:Ub2Z6/xjgF1WrYQz2nuITOEegKFtiIy+rieRJ5lHZKs=
-golang.org/x/text v0.40.0/go.mod h1:hpnzDAfGV753zIKo+wk3u1bVKCGPbrnF7+7LBF/UHVY=
 golang.org/x/text v0.42.0 h1:JbOZXgfeCPU9gacVtYliJqOhD+zhrEqK4LfdpmlUZqI=
 golang.org/x/text v0.42.0 h1:JbOZXgfeCPU9gacVtYliJqOhD+zhrEqK4LfdpmlUZqI=
 golang.org/x/text v0.42.0/go.mod h1:ojzP1Z+2QtioaF8DTtO8K5q7JWVVYwZKenzujK0Zd0E=
 golang.org/x/text v0.42.0/go.mod h1:ojzP1Z+2QtioaF8DTtO8K5q7JWVVYwZKenzujK0Zd0E=
-golang.org/x/time v0.14.0 h1:MRx4UaLrDotUKUdCIqzPC48t1Y9hANFKIRpNx+Te8PI=
-golang.org/x/time v0.14.0/go.mod h1:eL/Oa2bBBK0TkX57Fyni+NgnyQQN4LitPmob2Hjnqw4=
 golang.org/x/time v0.15.0 h1:bbrp8t3bGUeFOx08pvsMYRTCVSMk89u4tKbNOZbp88U=
 golang.org/x/time v0.15.0 h1:bbrp8t3bGUeFOx08pvsMYRTCVSMk89u4tKbNOZbp88U=
 golang.org/x/time v0.15.0/go.mod h1:Y4YMaQmXwGQZoFaVFk4YpCt4FLQMYKZe9oeV/f4MSno=
 golang.org/x/time v0.15.0/go.mod h1:Y4YMaQmXwGQZoFaVFk4YpCt4FLQMYKZe9oeV/f4MSno=
 golang.org/x/tools v0.0.0-20180525024113-a5b4c53f6e8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
 golang.org/x/tools v0.0.0-20180525024113-a5b4c53f6e8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
@@ -668,10 +597,6 @@ golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtn
 golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
 golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
 golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
 golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
 golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
 golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
-golang.org/x/tools v0.43.0 h1:12BdW9CeB3Z+J/I/wj34VMl8X+fEXBxVR90JeMX5E7s=
-golang.org/x/tools v0.43.0/go.mod h1:uHkMso649BX2cZK6+RpuIPXS3ho2hZo4FVwfoy1vIk0=
-golang.org/x/tools v0.47.0 h1:7Kn5x/d1svx/PzryTsqeoZN4TZwqeH5pGWjefhLi/1Q=
-golang.org/x/tools v0.47.0/go.mod h1:dFHnyTvFWY212G+h7ZY4Vsp/K3U4/7W9TyVaAul8uCA=
 golang.org/x/tools v0.49.0 h1:3NI7VXzL9+1WZD52Dx2ttoPwD5DWrFGpl9mFZDlmisI=
 golang.org/x/tools v0.49.0 h1:3NI7VXzL9+1WZD52Dx2ttoPwD5DWrFGpl9mFZDlmisI=
 golang.org/x/tools v0.49.0/go.mod h1:SJNXV9DBKT0UbdttsQjbfJlAE/q+y36++zo3uL3N0Oo=
 golang.org/x/tools v0.49.0/go.mod h1:SJNXV9DBKT0UbdttsQjbfJlAE/q+y36++zo3uL3N0Oo=
 golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
 golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
@@ -681,33 +606,20 @@ golang.org/x/xerrors v0.0.0-20240903120638-7835f813f4da h1:noIWHXmPHxILtqtCOPIhS
 golang.org/x/xerrors v0.0.0-20240903120638-7835f813f4da/go.mod h1:NDW/Ps6MPRej6fsCIbMTohpP40sJ/P/vI1MoTEGwX90=
 golang.org/x/xerrors v0.0.0-20240903120638-7835f813f4da/go.mod h1:NDW/Ps6MPRej6fsCIbMTohpP40sJ/P/vI1MoTEGwX90=
 gonum.org/v1/gonum v0.0.0-20180816165407-929014505bf4/go.mod h1:Y+Yx5eoAFn32cQvJDxZx5Dpnq+c3wtXuadVZAcxbbBo=
 gonum.org/v1/gonum v0.0.0-20180816165407-929014505bf4/go.mod h1:Y+Yx5eoAFn32cQvJDxZx5Dpnq+c3wtXuadVZAcxbbBo=
 gonum.org/v1/gonum v0.8.2/go.mod h1:oe/vMfY3deqTw+1EZJhuvEW2iwGF1bW9wwu7XCu0+v0=
 gonum.org/v1/gonum v0.8.2/go.mod h1:oe/vMfY3deqTw+1EZJhuvEW2iwGF1bW9wwu7XCu0+v0=
-gonum.org/v1/gonum v0.16.0 h1:5+ul4Swaf3ESvrOnidPp4GZbzf0mxVQpDCYUQE7OJfk=
-gonum.org/v1/gonum v0.16.0/go.mod h1:fef3am4MQ93R2HHpKnLk4/Tbh/s0+wqD5nfa6Pnwy4E=
 gonum.org/v1/gonum v0.17.0 h1:VbpOemQlsSMrYmn7T2OUvQ4dqxQXU+ouZFQsZOx50z4=
 gonum.org/v1/gonum v0.17.0 h1:VbpOemQlsSMrYmn7T2OUvQ4dqxQXU+ouZFQsZOx50z4=
+gonum.org/v1/gonum v0.17.0/go.mod h1:El3tOrEuMpv2UdMrbNlKEh9vd86bmQ6vqIcDwxEOc1E=
 gonum.org/v1/netlib v0.0.0-20190313105609-8cb42192e0e0/go.mod h1:wa6Ws7BG/ESfp6dHfk7C6KdzKA7wR7u/rKwOGE66zvw=
 gonum.org/v1/netlib v0.0.0-20190313105609-8cb42192e0e0/go.mod h1:wa6Ws7BG/ESfp6dHfk7C6KdzKA7wR7u/rKwOGE66zvw=
 gonum.org/v1/plot v0.0.0-20190515093506-e2840ee46a6b/go.mod h1:Wt8AAjI+ypCyYX3nZBvf6cAIx93T+c/OS2HFAYskSZc=
 gonum.org/v1/plot v0.0.0-20190515093506-e2840ee46a6b/go.mod h1:Wt8AAjI+ypCyYX3nZBvf6cAIx93T+c/OS2HFAYskSZc=
-google.golang.org/api v0.269.0 h1:qDrTOxKUQ/P0MveH6a7vZ+DNHxJQjtGm/uvdbdGXCQg=
-google.golang.org/api v0.269.0/go.mod h1:N8Wpcu23Tlccl0zSHEkcAZQKDLdquxK+l9r2LkwAauE=
 google.golang.org/api v0.297.0 h1:WktxTsnnx0yZNnsR6j0q6hR21RnnK81FHTOPy/ux4OE=
 google.golang.org/api v0.297.0 h1:WktxTsnnx0yZNnsR6j0q6hR21RnnK81FHTOPy/ux4OE=
 google.golang.org/api v0.297.0/go.mod h1:S4m8x0M6OkQpkOzGk1y9JG2sm4fFQrMh6dxzjCTszhE=
 google.golang.org/api v0.297.0/go.mod h1:S4m8x0M6OkQpkOzGk1y9JG2sm4fFQrMh6dxzjCTszhE=
-google.golang.org/genproto v0.0.0-20260226221140-a57be14db171 h1:RxhCsti413yL0IjU9dVvuTbCISo8gs3RW1jPMStck+4=
-google.golang.org/genproto v0.0.0-20260226221140-a57be14db171/go.mod h1:uhvzakVEqAuXU3TC2JCsxIRe5f77l+JySE3EqPoMyqM=
 google.golang.org/genproto v0.0.0-20260715232425-e75dac1f907d h1:C9v1o0/4quuhOAfmRXA2j+we0PqZIp8traLdeogF3Ms=
 google.golang.org/genproto v0.0.0-20260715232425-e75dac1f907d h1:C9v1o0/4quuhOAfmRXA2j+we0PqZIp8traLdeogF3Ms=
 google.golang.org/genproto v0.0.0-20260715232425-e75dac1f907d/go.mod h1:Wz2wFJntZFmLGo7pLDXZ3wYk5hyc0Mb+SkHhDDXT+lU=
 google.golang.org/genproto v0.0.0-20260715232425-e75dac1f907d/go.mod h1:Wz2wFJntZFmLGo7pLDXZ3wYk5hyc0Mb+SkHhDDXT+lU=
-google.golang.org/genproto/googleapis/api v0.0.0-20260226221140-a57be14db171 h1:tu/dtnW1o3wfaxCOjSLn5IRX4YDcJrtlpzYkhHhGaC4=
-google.golang.org/genproto/googleapis/api v0.0.0-20260226221140-a57be14db171/go.mod h1:M5krXqk4GhBKvB596udGL3UyjL4I1+cTbK0orROM9ng=
 google.golang.org/genproto/googleapis/api v0.0.0-20260715232425-e75dac1f907d h1:QwnJwPte4XXAkhPu26LTDIahnsMSUV0kK8HkxbC+Pc4=
 google.golang.org/genproto/googleapis/api v0.0.0-20260715232425-e75dac1f907d h1:QwnJwPte4XXAkhPu26LTDIahnsMSUV0kK8HkxbC+Pc4=
 google.golang.org/genproto/googleapis/api v0.0.0-20260715232425-e75dac1f907d/go.mod h1:WRrQ7/7N19PypuT0fxLOL5Lq0waoiRri4FbtHDEKrGE=
 google.golang.org/genproto/googleapis/api v0.0.0-20260715232425-e75dac1f907d/go.mod h1:WRrQ7/7N19PypuT0fxLOL5Lq0waoiRri4FbtHDEKrGE=
-google.golang.org/genproto/googleapis/rpc v0.0.0-20260316180232-0b37fe3546d5 h1:aJmi6DVGGIStN9Mobk/tZOOQUBbj0BPjZjjnOdoZKts=
-google.golang.org/genproto/googleapis/rpc v0.0.0-20260316180232-0b37fe3546d5/go.mod h1:4Hqkh8ycfw05ld/3BWL7rJOSfebL2Q+DVDeRgYgxUU8=
 google.golang.org/genproto/googleapis/rpc v0.0.0-20260908043556-f8649ddbbfe6 h1:ieEbjQ6lzbvntOXUB9nMx9uH+yIU/HbgkNDjnk/mJuk=
 google.golang.org/genproto/googleapis/rpc v0.0.0-20260908043556-f8649ddbbfe6 h1:ieEbjQ6lzbvntOXUB9nMx9uH+yIU/HbgkNDjnk/mJuk=
 google.golang.org/genproto/googleapis/rpc v0.0.0-20260908043556-f8649ddbbfe6/go.mod h1:DjtHYE8FKJLivXcBEjGwndXfIC23G0VpXiXKqG179uA=
 google.golang.org/genproto/googleapis/rpc v0.0.0-20260908043556-f8649ddbbfe6/go.mod h1:DjtHYE8FKJLivXcBEjGwndXfIC23G0VpXiXKqG179uA=
-google.golang.org/grpc v1.79.3 h1:sybAEdRIEtvcD68Gx7dmnwjZKlyfuc61Dyo9pGXXkKE=
-google.golang.org/grpc v1.79.3/go.mod h1:KmT0Kjez+0dde/v2j9vzwoAScgEPx/Bw1CYChhHLrHQ=
 google.golang.org/grpc v1.83.2 h1:EManeRomTObA0BU7I8vXgg/78uE5MJ9M8B39EX2WscU=
 google.golang.org/grpc v1.83.2 h1:EManeRomTObA0BU7I8vXgg/78uE5MJ9M8B39EX2WscU=
 google.golang.org/grpc v1.83.2/go.mod h1:YPI1hK3kDked6iHvgX3tR0y+nX/qpMFKhPgFsokw1S8=
 google.golang.org/grpc v1.83.2/go.mod h1:YPI1hK3kDked6iHvgX3tR0y+nX/qpMFKhPgFsokw1S8=
-google.golang.org/protobuf v1.36.12-0.20260120151049-f2248ac996af h1:+5/Sw3GsDNlEmu7TfklWKPdQ0Ykja5VEmq2i817+jbI=
-google.golang.org/protobuf v1.36.12-0.20260120151049-f2248ac996af/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco=
 google.golang.org/protobuf v1.36.12 h1:pJOKDDOyeXErUroCihFAd5LQuwXBSpVnKGrj5o/fwxc=
 google.golang.org/protobuf v1.36.12 h1:pJOKDDOyeXErUroCihFAd5LQuwXBSpVnKGrj5o/fwxc=
 google.golang.org/protobuf v1.36.12/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco=
 google.golang.org/protobuf v1.36.12/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco=
 gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
 gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
@@ -722,6 +634,8 @@ gopkg.in/inf.v0 v0.9.1 h1:73M5CoZyi3ZLMOyDlQh031Cx6N9NDJ2Vvfl76EDAgDc=
 gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw=
 gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw=
 gopkg.in/ini.v1 v1.67.0 h1:Dgnx+6+nfE+IfzjUEISNeydPJh9AXNNsWbGP9KzCsOA=
 gopkg.in/ini.v1 v1.67.0 h1:Dgnx+6+nfE+IfzjUEISNeydPJh9AXNNsWbGP9KzCsOA=
 gopkg.in/ini.v1 v1.67.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
 gopkg.in/ini.v1 v1.67.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
+gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ=
+gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=
 gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
 gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
 gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
 gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
 gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
 gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=

+ 40 - 0
pkg/cloud/config/configurations.go

@@ -10,6 +10,7 @@ import (
 	"github.com/opencost/opencost/pkg/cloud/aws"
 	"github.com/opencost/opencost/pkg/cloud/aws"
 	"github.com/opencost/opencost/pkg/cloud/azure"
 	"github.com/opencost/opencost/pkg/cloud/azure"
 	"github.com/opencost/opencost/pkg/cloud/gcp"
 	"github.com/opencost/opencost/pkg/cloud/gcp"
+	"github.com/opencost/opencost/pkg/cloud/ibm"
 	"github.com/opencost/opencost/pkg/cloud/oracle"
 	"github.com/opencost/opencost/pkg/cloud/oracle"
 	"github.com/opencost/opencost/pkg/cloud/stackit"
 	"github.com/opencost/opencost/pkg/cloud/stackit"
 )
 )
@@ -70,6 +71,7 @@ type Configurations struct {
 	Alibaba *AlibabaConfigs `json:"alibaba,omitempty"`
 	Alibaba *AlibabaConfigs `json:"alibaba,omitempty"`
 	OCI     *OCIConfigs     `json:"oci,omitempty"`
 	OCI     *OCIConfigs     `json:"oci,omitempty"`
 	STACKIT *STACKITConfigs `json:"stackit,omitempty"`
 	STACKIT *STACKITConfigs `json:"stackit,omitempty"`
+	IBM     *IBMConfigs     `json:"ibm,omitempty"`
 }
 }
 
 
 // UnmarshalJSON custom json unmarshalling to maintain support for MultiCloudConfig format
 // UnmarshalJSON custom json unmarshalling to maintain support for MultiCloudConfig format
@@ -127,6 +129,10 @@ func (c *Configurations) Equals(that *Configurations) bool {
 		return false
 		return false
 	}
 	}
 
 
+	if !c.IBM.Equals(that.IBM) {
+		return false
+	}
+
 	return true
 	return true
 }
 }
 
 
@@ -167,6 +173,11 @@ func (c *Configurations) Insert(keyedConfig cloud.Config) error {
 			c.STACKIT = &STACKITConfigs{}
 			c.STACKIT = &STACKITConfigs{}
 		}
 		}
 		c.STACKIT.CostAPI = append(c.STACKIT.CostAPI, keyedConfig.(*stackit.CostConfiguration))
 		c.STACKIT.CostAPI = append(c.STACKIT.CostAPI, keyedConfig.(*stackit.CostConfiguration))
+	case *ibm.UsageConfiguration:
+		if c.IBM == nil {
+			c.IBM = &IBMConfigs{}
+		}
+		c.IBM.UsageAPI = append(c.IBM.UsageAPI, keyedConfig.(*ibm.UsageConfiguration))
 	default:
 	default:
 		return fmt.Errorf("Configurations: Insert: failed to insert config of type: %T", keyedConfig)
 		return fmt.Errorf("Configurations: Insert: failed to insert config of type: %T", keyedConfig)
 	}
 	}
@@ -215,6 +226,12 @@ func (c *Configurations) ToSlice() []cloud.KeyedConfig {
 		}
 		}
 	}
 	}
 
 
+	if c.IBM != nil {
+		for _, usageConfig := range c.IBM.UsageAPI {
+			keyedConfigs = append(keyedConfigs, usageConfig)
+		}
+	}
+
 	return keyedConfigs
 	return keyedConfigs
 
 
 }
 }
@@ -378,3 +395,26 @@ func (sc *STACKITConfigs) Equals(that *STACKITConfigs) bool {
 	}
 	}
 	return true
 	return true
 }
 }
+
+type IBMConfigs struct {
+	UsageAPI []*ibm.UsageConfiguration `json:"usageApi,omitempty"`
+}
+
+func (ic *IBMConfigs) Equals(that *IBMConfigs) bool {
+	if ic == nil && that == nil {
+		return true
+	}
+	if ic == nil || that == nil {
+		return false
+	}
+	if len(ic.UsageAPI) != len(that.UsageAPI) {
+		return false
+	}
+	for i, thisUsage := range ic.UsageAPI {
+		thatUsage := that.UsageAPI[i]
+		if !thisUsage.Equals(thatUsage) {
+			return false
+		}
+	}
+	return true
+}

+ 8 - 0
pkg/cloud/config/controller_handlers.go

@@ -14,6 +14,7 @@ import (
 	"github.com/opencost/opencost/pkg/cloud/aws"
 	"github.com/opencost/opencost/pkg/cloud/aws"
 	"github.com/opencost/opencost/pkg/cloud/azure"
 	"github.com/opencost/opencost/pkg/cloud/azure"
 	"github.com/opencost/opencost/pkg/cloud/gcp"
 	"github.com/opencost/opencost/pkg/cloud/gcp"
+	"github.com/opencost/opencost/pkg/cloud/ibm"
 )
 )
 
 
 var protocol = proto.HTTP()
 var protocol = proto.HTTP()
@@ -156,6 +157,13 @@ func ParseConfig(configType string, body io.Reader) (cloud.KeyedConfig, error) {
 			return nil, fmt.Errorf("error unmarshalling Azure Storage Configuration: %w", err)
 			return nil, fmt.Errorf("error unmarshalling Azure Storage Configuration: %w", err)
 		}
 		}
 		return config, nil
 		return config, nil
+	case IBMUsageConfigType:
+		config := &ibm.UsageConfiguration{}
+		err = json.Unmarshal(bytes, config)
+		if err != nil {
+			return nil, fmt.Errorf("error unmarshalling IBM Usage Configuration: %w", err)
+		}
+		return config, nil
 
 
 	}
 	}
 	return nil, fmt.Errorf("provided config type was not recognised %s", configType)
 	return nil, fmt.Errorf("provided config type was not recognised %s", configType)

+ 24 - 0
pkg/cloud/config/controller_handlers_test.go

@@ -12,6 +12,7 @@ import (
 	"github.com/opencost/opencost/pkg/cloud/aws"
 	"github.com/opencost/opencost/pkg/cloud/aws"
 	"github.com/opencost/opencost/pkg/cloud/azure"
 	"github.com/opencost/opencost/pkg/cloud/azure"
 	"github.com/opencost/opencost/pkg/cloud/gcp"
 	"github.com/opencost/opencost/pkg/cloud/gcp"
+	"github.com/opencost/opencost/pkg/cloud/ibm"
 )
 )
 
 
 func Test_ParseConfig_InvalidType(t *testing.T) {
 func Test_ParseConfig_InvalidType(t *testing.T) {
@@ -135,6 +136,29 @@ func Test_ParseConfig_Azure(t *testing.T) {
 	}
 	}
 }
 }
 
 
+func Test_ParseConfig_IBM(t *testing.T) {
+	config := &ibm.UsageConfiguration{
+		AccountID: "account-id",
+		Authorizer: &ibm.APIKey{
+			Key: "api-key",
+		},
+	}
+
+	configBytes, err := json.Marshal(config)
+	if err != nil {
+		t.Fatalf("failed to marshal config: %v", err)
+	}
+
+	parsedConfig, err := ParseConfig(IBMUsageConfigType, bytes.NewReader(configBytes))
+	if err != nil {
+		t.Fatalf("failed to parse config: %v", err)
+	}
+
+	if !config.Equals(parsedConfig) {
+		t.Fatalf("parsed config does not match original config:\n%+v\n%+v", parsedConfig, config)
+	}
+}
+
 func Test_GetAddConfigHandler(t *testing.T) {
 func Test_GetAddConfigHandler(t *testing.T) {
 	controller := &Controller{
 	controller := &Controller{
 		storage: &MemoryControllerStorage{},
 		storage: &MemoryControllerStorage{},

+ 6 - 0
pkg/cloud/config/statuses.go

@@ -9,6 +9,7 @@ import (
 	"github.com/opencost/opencost/pkg/cloud/aws"
 	"github.com/opencost/opencost/pkg/cloud/aws"
 	"github.com/opencost/opencost/pkg/cloud/azure"
 	"github.com/opencost/opencost/pkg/cloud/azure"
 	"github.com/opencost/opencost/pkg/cloud/gcp"
 	"github.com/opencost/opencost/pkg/cloud/gcp"
+	"github.com/opencost/opencost/pkg/cloud/ibm"
 	"github.com/opencost/opencost/pkg/cloud/oracle"
 	"github.com/opencost/opencost/pkg/cloud/oracle"
 	"github.com/opencost/opencost/pkg/cloud/stackit"
 	"github.com/opencost/opencost/pkg/cloud/stackit"
 )
 )
@@ -20,6 +21,7 @@ const (
 	AzureStorageConfigType = "azurestorage"
 	AzureStorageConfigType = "azurestorage"
 	UsageApiConfigType     = "usageapi"
 	UsageApiConfigType     = "usageapi"
 	STACKITCostConfigType  = "stackitcost"
 	STACKITCostConfigType  = "stackitcost"
+	IBMUsageConfigType     = "ibmusage"
 )
 )
 
 
 func ConfigTypeFromConfig(config cloud.KeyedConfig) (string, error) {
 func ConfigTypeFromConfig(config cloud.KeyedConfig) (string, error) {
@@ -36,6 +38,8 @@ func ConfigTypeFromConfig(config cloud.KeyedConfig) (string, error) {
 		return UsageApiConfigType, nil
 		return UsageApiConfigType, nil
 	case *stackit.CostConfiguration:
 	case *stackit.CostConfiguration:
 		return STACKITCostConfigType, nil
 		return STACKITCostConfigType, nil
+	case *ibm.UsageConfiguration:
+		return IBMUsageConfigType, nil
 	}
 	}
 	return "", fmt.Errorf("failed to determine config type for config with key: %s, type %T", config.Key(), config)
 	return "", fmt.Errorf("failed to determine config type for config with key: %s, type %T", config.Key(), config)
 }
 }
@@ -126,6 +130,8 @@ func (s *Status) UnmarshalJSON(b []byte) error {
 		config = &oracle.UsageApiConfiguration{}
 		config = &oracle.UsageApiConfiguration{}
 	case STACKITCostConfigType:
 	case STACKITCostConfigType:
 		config = &stackit.CostConfiguration{}
 		config = &stackit.CostConfiguration{}
+	case IBMUsageConfigType:
+		config = &ibm.UsageConfiguration{}
 	default:
 	default:
 		return fmt.Errorf("Status: UnmarshalJSON: config type '%s' is not recognized", configType)
 		return fmt.Errorf("Status: UnmarshalJSON: config type '%s' is not recognized", configType)
 	}
 	}

+ 67 - 0
pkg/cloud/ibm/authorizer.go

@@ -0,0 +1,67 @@
+package ibm
+
+import (
+	"encoding/json"
+	"fmt"
+
+	"github.com/IBM/go-sdk-core/v5/core"
+	"github.com/opencost/opencost/pkg/cloud"
+)
+
+const APIKeyAuthorizerType = "IBMAPIKey"
+
+// Authorizer creates IBM Cloud SDK authenticators for Usage Reports clients.
+type Authorizer interface {
+	cloud.Authorizer
+	CreateAuthenticator() (core.Authenticator, error)
+}
+
+// SelectAuthorizerByType registers supported IBM authorizer types.
+func SelectAuthorizerByType(typeStr string) (Authorizer, error) {
+	switch typeStr {
+	case APIKeyAuthorizerType:
+		return &APIKey{}, nil
+	default:
+		return nil, fmt.Errorf("IBM: provider authorizer type '%s' is not valid", typeStr)
+	}
+}
+
+// APIKey authenticates to IBM Cloud IAM with an API key.
+// The key may belong to a user or service ID with billing.usage-report.read.
+type APIKey struct {
+	Key string `json:"apiKey"`
+}
+
+func (a *APIKey) MarshalJSON() ([]byte, error) {
+	fmap := map[string]any{
+		cloud.AuthorizerTypeProperty: APIKeyAuthorizerType,
+		"apiKey":                     a.Key,
+	}
+	return json.Marshal(fmap)
+}
+
+func (a *APIKey) Validate() error {
+	if a.Key == "" {
+		return fmt.Errorf("APIKey: missing apiKey")
+	}
+	return nil
+}
+
+func (a *APIKey) Equals(config cloud.Config) bool {
+	if config == nil {
+		return false
+	}
+	that, ok := config.(*APIKey)
+	if !ok {
+		return false
+	}
+	return a.Key == that.Key
+}
+
+func (a *APIKey) Sanitize() cloud.Config {
+	return &APIKey{Key: cloud.Redacted}
+}
+
+func (a *APIKey) CreateAuthenticator() (core.Authenticator, error) {
+	return core.NewIamAuthenticatorBuilder().SetApiKey(a.Key).Build()
+}

+ 117 - 0
pkg/cloud/ibm/authorizer_test.go

@@ -0,0 +1,117 @@
+package ibm
+
+import (
+	"fmt"
+	"testing"
+
+	"github.com/opencost/opencost/core/pkg/util/json"
+	"github.com/opencost/opencost/pkg/cloud"
+)
+
+func TestAPIKey_Validate(t *testing.T) {
+	testCases := map[string]struct {
+		auth     APIKey
+		expected error
+	}{
+		"valid": {
+			auth:     APIKey{Key: "ibm-api-key"},
+			expected: nil,
+		},
+		"missing key": {
+			auth:     APIKey{Key: ""},
+			expected: fmt.Errorf("APIKey: missing apiKey"),
+		},
+	}
+
+	for name, tc := range testCases {
+		t.Run(name, func(t *testing.T) {
+			actual := tc.auth.Validate()
+			actualString := "nil"
+			if actual != nil {
+				actualString = actual.Error()
+			}
+			expectedString := "nil"
+			if tc.expected != nil {
+				expectedString = tc.expected.Error()
+			}
+			if actualString != expectedString {
+				t.Errorf("errors do not match: Actual: '%s', Expected: '%s'", actualString, expectedString)
+			}
+		})
+	}
+}
+
+func TestAPIKey_Equals(t *testing.T) {
+	testCases := map[string]struct {
+		left     APIKey
+		right    cloud.Config
+		expected bool
+	}{
+		"matching": {
+			left:     APIKey{Key: "key"},
+			right:    &APIKey{Key: "key"},
+			expected: true,
+		},
+		"different key": {
+			left:     APIKey{Key: "key"},
+			right:    &APIKey{Key: "other"},
+			expected: false,
+		},
+		"wrong type": {
+			left:     APIKey{Key: "key"},
+			right:    nil,
+			expected: false,
+		},
+	}
+
+	for name, tc := range testCases {
+		t.Run(name, func(t *testing.T) {
+			if tc.left.Equals(tc.right) != tc.expected {
+				t.Errorf("incorrect Equals result")
+			}
+		})
+	}
+}
+
+func TestAPIKey_Sanitize(t *testing.T) {
+	auth := &APIKey{Key: "secret-key"}
+	sanitized := auth.Sanitize().(*APIKey)
+	if sanitized.Key != cloud.Redacted {
+		t.Errorf("expected redacted key, got %q", sanitized.Key)
+	}
+}
+
+func TestAPIKey_JSON(t *testing.T) {
+	auth := &APIKey{Key: "secret-key"}
+	b, err := json.Marshal(auth)
+	if err != nil {
+		t.Fatalf("marshal: %v", err)
+	}
+
+	var raw map[string]any
+	if err := json.Unmarshal(b, &raw); err != nil {
+		t.Fatalf("unmarshal map: %v", err)
+	}
+	if raw[cloud.AuthorizerTypeProperty] != APIKeyAuthorizerType {
+		t.Errorf("authorizerType = %v, want %s", raw[cloud.AuthorizerTypeProperty], APIKeyAuthorizerType)
+	}
+
+	selected, err := SelectAuthorizerByType(APIKeyAuthorizerType)
+	if err != nil {
+		t.Fatalf("SelectAuthorizerByType: %v", err)
+	}
+	unmarshalled := selected.(*APIKey)
+	if err := json.Unmarshal(b, unmarshalled); err != nil {
+		t.Fatalf("unmarshal authorizer: %v", err)
+	}
+	if !auth.Equals(unmarshalled) {
+		t.Error("round-trip authorizer mismatch")
+	}
+}
+
+func TestSelectAuthorizerByType_Invalid(t *testing.T) {
+	_, err := SelectAuthorizerByType("not-a-type")
+	if err == nil {
+		t.Fatal("expected error for invalid authorizer type")
+	}
+}

+ 114 - 0
pkg/cloud/ibm/usageconfiguration.go

@@ -0,0 +1,114 @@
+package ibm
+
+import (
+	"encoding/json"
+	"fmt"
+
+	"github.com/IBM/platform-services-go-sdk/usagereportsv4"
+	"github.com/opencost/opencost/core/pkg/opencost"
+	"github.com/opencost/opencost/pkg/cloud"
+)
+
+// UsageConfiguration connects OpenCost to the IBM Cloud Usage Reports API
+// (account cost and usage / CUR-style billing data).
+type UsageConfiguration struct {
+	AccountID  string     `json:"accountID"`
+	Authorizer Authorizer `json:"authorizer"`
+}
+
+func (c *UsageConfiguration) Validate() error {
+	if c.Authorizer == nil {
+		return fmt.Errorf("UsageConfiguration: missing Authorizer")
+	}
+	if err := c.Authorizer.Validate(); err != nil {
+		return fmt.Errorf("UsageConfiguration: %s", err)
+	}
+	if c.AccountID == "" {
+		return fmt.Errorf("UsageConfiguration: missing accountID")
+	}
+	return nil
+}
+
+func (c *UsageConfiguration) Equals(config cloud.Config) bool {
+	if config == nil {
+		return false
+	}
+	that, ok := config.(*UsageConfiguration)
+	if !ok {
+		return false
+	}
+	if c.Authorizer != nil {
+		if !c.Authorizer.Equals(that.Authorizer) {
+			return false
+		}
+	} else if that.Authorizer != nil {
+		return false
+	}
+	return c.AccountID == that.AccountID
+}
+
+func (c *UsageConfiguration) Sanitize() cloud.Config {
+	var auth Authorizer
+	if c.Authorizer != nil {
+		auth = c.Authorizer.Sanitize().(Authorizer)
+	}
+	return &UsageConfiguration{
+		AccountID:  c.AccountID,
+		Authorizer: auth,
+	}
+}
+
+// Key identifies the integration for storage paths. Account IDs are normalized
+// to bare hex so "a/<hex>" cannot create an extra path segment.
+func (c *UsageConfiguration) Key() string {
+	return normalizeAccountID(c.AccountID)
+}
+
+func (c *UsageConfiguration) Provider() string {
+	return opencost.IBMProvider
+}
+
+func (c *UsageConfiguration) GetUsageReportsClient() (*usagereportsv4.UsageReportsV4, error) {
+	if c.Authorizer == nil {
+		return nil, fmt.Errorf("missing authorizer")
+	}
+	authenticator, err := c.Authorizer.CreateAuthenticator()
+	if err != nil {
+		return nil, fmt.Errorf("creating IAM authenticator: %w", err)
+	}
+	client, err := usagereportsv4.NewUsageReportsV4(&usagereportsv4.UsageReportsV4Options{
+		Authenticator: authenticator,
+	})
+	if err != nil {
+		return nil, fmt.Errorf("creating usage reports client: %w", err)
+	}
+	return client, nil
+}
+
+func (c *UsageConfiguration) UnmarshalJSON(b []byte) error {
+	var f any
+	if err := json.Unmarshal(b, &f); err != nil {
+		return err
+	}
+	fmap, ok := f.(map[string]any)
+	if !ok {
+		return fmt.Errorf("UsageConfiguration: UnmarshalJSON: expected object")
+	}
+
+	accountID, err := cloud.GetInterfaceValue[string](fmap, "accountID")
+	if err != nil {
+		return fmt.Errorf("UsageConfiguration: UnmarshalJSON: %w", err)
+	}
+	c.AccountID = accountID
+
+	authAny, ok := fmap["authorizer"]
+	if !ok {
+		return fmt.Errorf("UsageConfiguration: UnmarshalJSON: missing authorizer")
+	}
+	authorizer, err := cloud.AuthorizerFromInterface(authAny, SelectAuthorizerByType)
+	if err != nil {
+		return fmt.Errorf("UsageConfiguration: UnmarshalJSON: %w", err)
+	}
+	c.Authorizer = authorizer
+	return nil
+}

+ 176 - 0
pkg/cloud/ibm/usageconfiguration_test.go

@@ -0,0 +1,176 @@
+package ibm
+
+import (
+	"fmt"
+	"testing"
+
+	"github.com/opencost/opencost/core/pkg/opencost"
+	"github.com/opencost/opencost/core/pkg/util/json"
+	"github.com/opencost/opencost/pkg/cloud"
+)
+
+func TestUsageConfiguration_Validate(t *testing.T) {
+	validAuth := &APIKey{Key: "ibm-api-key"}
+	testCases := map[string]struct {
+		config   UsageConfiguration
+		expected error
+	}{
+		"valid": {
+			config: UsageConfiguration{
+				AccountID:  "account-id",
+				Authorizer: validAuth,
+			},
+			expected: nil,
+		},
+		"missing authorizer": {
+			config: UsageConfiguration{
+				AccountID:  "account-id",
+				Authorizer: nil,
+			},
+			expected: fmt.Errorf("UsageConfiguration: missing Authorizer"),
+		},
+		"invalid authorizer": {
+			config: UsageConfiguration{
+				AccountID:  "account-id",
+				Authorizer: &APIKey{Key: ""},
+			},
+			expected: fmt.Errorf("UsageConfiguration: APIKey: missing apiKey"),
+		},
+		"missing accountID": {
+			config: UsageConfiguration{
+				AccountID:  "",
+				Authorizer: validAuth,
+			},
+			expected: fmt.Errorf("UsageConfiguration: missing accountID"),
+		},
+	}
+
+	for name, tc := range testCases {
+		t.Run(name, func(t *testing.T) {
+			actual := tc.config.Validate()
+			actualString := "nil"
+			if actual != nil {
+				actualString = actual.Error()
+			}
+			expectedString := "nil"
+			if tc.expected != nil {
+				expectedString = tc.expected.Error()
+			}
+			if actualString != expectedString {
+				t.Errorf("errors do not match: Actual: '%s', Expected: '%s'", actualString, expectedString)
+			}
+		})
+	}
+}
+
+func TestUsageConfiguration_Equals(t *testing.T) {
+	auth := &APIKey{Key: "key"}
+	testCases := map[string]struct {
+		left     UsageConfiguration
+		right    cloud.Config
+		expected bool
+	}{
+		"matching": {
+			left: UsageConfiguration{
+				AccountID:  "acct",
+				Authorizer: auth,
+			},
+			right: &UsageConfiguration{
+				AccountID:  "acct",
+				Authorizer: &APIKey{Key: "key"},
+			},
+			expected: true,
+		},
+		"different account": {
+			left: UsageConfiguration{
+				AccountID:  "acct",
+				Authorizer: auth,
+			},
+			right: &UsageConfiguration{
+				AccountID:  "other",
+				Authorizer: &APIKey{Key: "key"},
+			},
+			expected: false,
+		},
+		"different authorizer": {
+			left: UsageConfiguration{
+				AccountID:  "acct",
+				Authorizer: auth,
+			},
+			right: &UsageConfiguration{
+				AccountID:  "acct",
+				Authorizer: &APIKey{Key: "other"},
+			},
+			expected: false,
+		},
+		"both nil authorizer": {
+			left: UsageConfiguration{
+				AccountID:  "acct",
+				Authorizer: nil,
+			},
+			right: &UsageConfiguration{
+				AccountID:  "acct",
+				Authorizer: nil,
+			},
+			expected: true,
+		},
+		"left nil authorizer": {
+			left: UsageConfiguration{
+				AccountID:  "acct",
+				Authorizer: nil,
+			},
+			right: &UsageConfiguration{
+				AccountID:  "acct",
+				Authorizer: auth,
+			},
+			expected: false,
+		},
+	}
+
+	for name, tc := range testCases {
+		t.Run(name, func(t *testing.T) {
+			if tc.left.Equals(tc.right) != tc.expected {
+				t.Errorf("incorrect Equals result")
+			}
+		})
+	}
+}
+
+func TestUsageConfiguration_KeyProviderSanitize(t *testing.T) {
+	cfg := &UsageConfiguration{
+		AccountID:  "acct-123",
+		Authorizer: &APIKey{Key: "secret"},
+	}
+	if cfg.Key() != "acct-123" {
+		t.Errorf("Key() = %q, want acct-123", cfg.Key())
+	}
+	slashCfg := &UsageConfiguration{AccountID: "a/acct-123"}
+	if slashCfg.Key() != "acct-123" {
+		t.Errorf("Key() with a/ prefix = %q, want acct-123", slashCfg.Key())
+	}
+	if cfg.Provider() != opencost.IBMProvider {
+		t.Errorf("Provider() = %q, want %q", cfg.Provider(), opencost.IBMProvider)
+	}
+	sanitized := cfg.Sanitize().(*UsageConfiguration)
+	if sanitized.Authorizer.(*APIKey).Key != cloud.Redacted {
+		t.Errorf("Sanitize did not redact api key")
+	}
+}
+
+func TestUsageConfiguration_JSON(t *testing.T) {
+	cfg := UsageConfiguration{
+		AccountID:  "acct-123",
+		Authorizer: &APIKey{Key: "secret"},
+	}
+	b, err := json.Marshal(&cfg)
+	if err != nil {
+		t.Fatalf("marshal: %v", err)
+	}
+	unmarshalled := &UsageConfiguration{}
+	if err := json.Unmarshal(b, unmarshalled); err != nil {
+		t.Fatalf("unmarshal: %v", err)
+	}
+	if !cfg.Equals(unmarshalled) {
+		t.Error("config does not equal unmarshalled config")
+	}
+}

+ 421 - 0
pkg/cloud/ibm/usageintegration.go

@@ -0,0 +1,421 @@
+package ibm
+
+import (
+	"fmt"
+	"strings"
+	"time"
+
+	"github.com/IBM/platform-services-go-sdk/usagereportsv4"
+	"github.com/opencost/opencost/core/pkg/log"
+	"github.com/opencost/opencost/core/pkg/opencost"
+	"github.com/opencost/opencost/pkg/cloud"
+)
+
+// Field-mapping contract for IBM CloudCost (shared with Cloudability CAC path):
+//
+//	Provider          = "IBM"
+//	ProviderID        = ResourceInstanceID from Usage Reports (typically a full CRN)
+//	AccountID         = account_id normalized to bare 32-hex (strip leading "a/" if present)
+//	InvoiceEntityID   = AccountID (no payer/enterprise column in row data)
+//	Service           = ResourceID (stable service id — not the display name)
+//	Category          = selectIBMCategory(ResourceID) only — pure function of service id
+//	UsageType         = N/A on CloudCostProperties in this tree (and must remain unset if added later)
+//	ListCost / AmortizedCost           = sum(rated_cost) converted to USD, prorated
+//	NetCost / AmortizedNetCost / InvoicedCost = sum(cost) converted to USD, prorated
+//
+// ResourceName (when names=true) is stored as label "ibm_resource_name", not Service.
+// Daily values are synthetic rather than true per-day usage: report totals ÷ covered days
+// (full month, or MTD day-of-month). Each refresh rewrites every covered day of each touched
+// month together so the stored month reconciles to IBM's report even when the ingestor window
+// starts mid-month. Non-billable instances are skipped.
+
+// UsageIntegration ingests IBM Cloud Usage Reports into CloudCost.
+type UsageIntegration struct {
+	UsageConfiguration
+	ConnectionStatus cloud.ConnectionStatus
+	clientFactory    func() (*usagereportsv4.UsageReportsV4, error)
+}
+
+func (ui *UsageIntegration) GetCloudCost(start, end time.Time) (*opencost.CloudCostSetRange, error) {
+	return ui.getCloudCost(start, end, time.Now().UTC())
+}
+
+func (ui *UsageIntegration) getCloudCost(start, end, asOf time.Time) (*opencost.CloudCostSetRange, error) {
+	client, err := ui.usageReportsClient()
+	if err != nil {
+		ui.ConnectionStatus = cloud.FailedConnection
+		return nil, fmt.Errorf("getting IBM usage reports client: %w", err)
+	}
+
+	// Cover whole months: cloudCostsFromInstance prorates a month total across every covered day,
+	// so all of those days have to be rewritten together for the stored month to equal IBM's total.
+	rangeStart, rangeEnd := monthRangeCovering(start, end)
+	ccsr, err := opencost.NewCloudCostSetRange(rangeStart, rangeEnd, opencost.AccumulateOptionDay, ui.Key())
+	if err != nil {
+		return nil, err
+	}
+
+	months := monthsOverlapping(start, end)
+	itemsSeen := 0
+	for _, month := range months {
+		options := client.NewGetResourceUsageAccountOptions(normalizeAccountID(ui.AccountID), month)
+		options.SetLimit(200)
+		// names=true populates ResourceName for the ibm_resource_name label only;
+		// Service always keys on ResourceID for CAC / billing-export agreement.
+		options.SetNames(true)
+		options.SetTags(true)
+
+		pager, err := client.NewGetResourceUsageAccountPager(options)
+		if err != nil {
+			ui.ConnectionStatus = cloud.FailedConnection
+			return nil, fmt.Errorf("creating usage pager for %s: %w", month, err)
+		}
+
+		for pager.HasNext() {
+			page, err := pager.GetNext()
+			if err != nil {
+				ui.ConnectionStatus = cloud.FailedConnection
+				return nil, fmt.Errorf("querying IBM resource usage for %s: %w", month, err)
+			}
+			for _, item := range page {
+				itemsSeen++
+				record, ok := instanceUsageFromSDK(item)
+				if !ok {
+					continue
+				}
+				for _, cc := range cloudCostsFromInstance(record, start, end, asOf) {
+					ccsr.LoadCloudCost(cc)
+				}
+			}
+		}
+	}
+
+	if itemsSeen == 0 && ui.ConnectionStatus != cloud.SuccessfulConnection {
+		ui.ConnectionStatus = cloud.MissingData
+		return ccsr, nil
+	}
+
+	ui.ConnectionStatus = cloud.SuccessfulConnection
+	return ccsr, nil
+}
+
+func (ui *UsageIntegration) usageReportsClient() (*usagereportsv4.UsageReportsV4, error) {
+	if ui.clientFactory != nil {
+		return ui.clientFactory()
+	}
+	return ui.GetUsageReportsClient()
+}
+
+func (ui *UsageIntegration) GetStatus() cloud.ConnectionStatus {
+	if ui.ConnectionStatus.String() == "" {
+		ui.ConnectionStatus = cloud.InitialStatus
+	}
+	return ui.ConnectionStatus
+}
+
+func (ui *UsageIntegration) RefreshStatus() cloud.ConnectionStatus {
+	log.Warn("status refresh is not supported for the IBM Cloud provider")
+	return ui.ConnectionStatus
+}
+
+// instanceUsageRecord is a testable projection of Usage Reports instance usage.
+// Costs are stored after conversion to USD.
+type instanceUsageRecord struct {
+	AccountID          string
+	ResourceInstanceID string
+	ResourceID         string
+	ResourceName       string
+	Region             string
+	Month              string
+	Cost               float64
+	RatedCost          float64
+	Tags               []any
+}
+
+// instanceUsageFromSDK projects an SDK row. ok is false when the row should be skipped
+// (explicitly non-billable).
+func instanceUsageFromSDK(item usagereportsv4.InstanceUsage) (instanceUsageRecord, bool) {
+	if item.Billable != nil && !*item.Billable {
+		return instanceUsageRecord{}, false
+	}
+
+	record := instanceUsageRecord{
+		Tags: mergeTagSlices(item.Tags, item.ServiceTags),
+	}
+	if item.AccountID != nil {
+		record.AccountID = normalizeAccountID(*item.AccountID)
+	}
+	if item.ResourceInstanceID != nil {
+		record.ResourceInstanceID = *item.ResourceInstanceID
+	}
+	if item.ResourceID != nil {
+		record.ResourceID = *item.ResourceID
+	}
+	if item.ResourceName != nil {
+		record.ResourceName = *item.ResourceName
+	}
+	if item.Region != nil {
+		record.Region = *item.Region
+	}
+	if item.Month != nil {
+		record.Month = *item.Month
+	}
+
+	rate := 1.0
+	if item.CurrencyRate != nil && *item.CurrencyRate > 0 {
+		rate = *item.CurrencyRate
+	}
+
+	for _, metric := range item.Usage {
+		if metric.NonChargeable != nil && *metric.NonChargeable {
+			continue
+		}
+		if metric.Cost != nil {
+			record.Cost += *metric.Cost * rate
+		}
+		if metric.RatedCost != nil {
+			record.RatedCost += *metric.RatedCost * rate
+		}
+	}
+	return record, true
+}
+
+func mergeTagSlices(parts ...[]any) []any {
+	var out []any
+	for _, part := range parts {
+		out = append(out, part...)
+	}
+	return out
+}
+
+func cloudCostsFromInstance(item instanceUsageRecord, start, end, asOf time.Time) []*opencost.CloudCost {
+	if item.Month == "" || (item.Cost == 0 && item.RatedCost == 0) {
+		return nil
+	}
+	monthStart, err := time.Parse("2006-01", item.Month)
+	if err != nil {
+		return nil
+	}
+	monthStart = time.Date(monthStart.Year(), monthStart.Month(), 1, 0, 0, 0, 0, time.UTC)
+	days := prorationDays(monthStart, asOf)
+	if days <= 0 {
+		return nil
+	}
+
+	dailyNet := item.Cost / float64(days)
+	dailyList := item.RatedCost / float64(days)
+
+	labels := parseTags(item.Tags)
+	if item.ResourceName != "" {
+		labels["ibm_resource_name"] = item.ResourceName
+	}
+
+	properties := &opencost.CloudCostProperties{
+		ProviderID: item.ResourceInstanceID,
+		Provider:   opencost.IBMProvider,
+		AccountID:  item.AccountID,
+		// IBM billing data carries no account display name. The billing-export producer collapses
+		// both names onto the account id; match it exactly or the two split rows on aggregation.
+		AccountName:       item.AccountID,
+		InvoiceEntityID:   item.AccountID,
+		InvoiceEntityName: item.AccountID,
+		RegionID:          item.Region,
+		Service:           item.ResourceID,
+		Category:          selectIBMCategory(item.ResourceID),
+		Labels:            labels,
+	}
+
+	k8sPct := 0.0
+	if isKubernetesResource(item.ResourceID, item.ResourceInstanceID) {
+		k8sPct = 1.0
+	}
+
+	var costs []*opencost.CloudCost
+	// Every day the total was divided across must be emitted, not just the days inside the caller's
+	// window. The repository replaces whole day-sets, so a partial rewrite leaves the rest of the
+	// month holding a rate computed at a different asOf and the stored month sums to neither total.
+	// getCloudCost widens its range to the months covered here so all of these days are persisted.
+	for d := 0; d < days; d++ {
+		dayStart := monthStart.AddDate(0, 0, d)
+		dayEnd := dayStart.AddDate(0, 0, 1)
+		ds := dayStart
+		de := dayEnd
+		costs = append(costs, &opencost.CloudCost{
+			Properties: properties,
+			Window:     opencost.NewWindow(&ds, &de),
+			ListCost: opencost.CostMetric{
+				Cost:              dailyList,
+				KubernetesPercent: k8sPct,
+			},
+			NetCost: opencost.CostMetric{
+				Cost:              dailyNet,
+				KubernetesPercent: k8sPct,
+			},
+			AmortizedNetCost: opencost.CostMetric{
+				Cost:              dailyNet,
+				KubernetesPercent: k8sPct,
+			},
+			AmortizedCost: opencost.CostMetric{
+				Cost:              dailyList,
+				KubernetesPercent: k8sPct,
+			},
+			InvoicedCost: opencost.CostMetric{
+				Cost:              dailyNet,
+				KubernetesPercent: k8sPct,
+			},
+		})
+	}
+	return costs
+}
+
+// kubernetesServiceID is IBM's service identifier for both IKS and ROKS clusters.
+const kubernetesServiceID = "containers-kubernetes"
+
+// isKubernetesResource reports whether a usage row belongs to an IKS or ROKS cluster, by service
+// identifier or by the service segment of the resource CRN. Mirrors the billing-export producer so
+// both paths mark the same rows as Kubernetes.
+func isKubernetesResource(serviceID, providerID string) bool {
+	if strings.EqualFold(strings.TrimSpace(serviceID), kubernetesServiceID) {
+		return true
+	}
+	return strings.Contains(strings.ToLower(providerID), ":"+kubernetesServiceID+":")
+}
+
+// prorationDays returns the divisor for spreading a monthly (or MTD) report total.
+// Completed months use calendar days; the asOf month uses day-of-month (MTD).
+func prorationDays(monthStart, asOf time.Time) int {
+	asOf = asOf.UTC()
+	monthStart = monthStart.UTC()
+	full := daysInMonth(monthStart.Year(), int(monthStart.Month()))
+	if asOf.Year() == monthStart.Year() && asOf.Month() == monthStart.Month() {
+		if asOf.Day() < 1 {
+			return full
+		}
+		if asOf.Day() < full {
+			return asOf.Day()
+		}
+	}
+	return full
+}
+
+// monthRangeCovering returns the half-open range spanning every whole month that [start, end)
+// touches. It mirrors monthsOverlapping's exclusive-end convention: an end landing exactly on a
+// month boundary does not pull in that month.
+func monthRangeCovering(start, end time.Time) (time.Time, time.Time) {
+	rangeStart := time.Date(start.Year(), start.Month(), 1, 0, 0, 0, 0, time.UTC)
+	lastMonth := time.Date(end.Year(), end.Month(), 1, 0, 0, 0, 0, time.UTC)
+	if end.Equal(lastMonth) {
+		lastMonth = lastMonth.AddDate(0, -1, 0)
+	}
+	rangeEnd := lastMonth.AddDate(0, 1, 0)
+	if !rangeStart.Before(rangeEnd) {
+		return start, end
+	}
+	return rangeStart, rangeEnd
+}
+
+func monthsOverlapping(start, end time.Time) []string {
+	if !start.Before(end) {
+		return nil
+	}
+	cursor := time.Date(start.Year(), start.Month(), 1, 0, 0, 0, 0, time.UTC)
+	last := time.Date(end.Year(), end.Month(), 1, 0, 0, 0, 0, time.UTC)
+	// end is exclusive; if end is exactly month start, previous month is last needed
+	if end.Equal(last) {
+		last = last.AddDate(0, -1, 0)
+	}
+	var months []string
+	for !cursor.After(last) {
+		months = append(months, cursor.Format("2006-01"))
+		cursor = cursor.AddDate(0, 1, 0)
+	}
+	return months
+}
+
+func daysInMonth(year, month int) int {
+	start := time.Date(year, time.Month(month), 1, 0, 0, 0, 0, time.UTC)
+	return start.AddDate(0, 1, -1).Day()
+}
+
+func parseTags(raw []any) opencost.CloudCostLabels {
+	labels := opencost.CloudCostLabels{}
+	for _, tag := range raw {
+		switch v := tag.(type) {
+		case string:
+			key, value, ok := splitTagString(v)
+			if !ok {
+				continue
+			}
+			labels[key] = value
+		case map[string]any:
+			key, _ := v["key"].(string)
+			if key == "" {
+				key, _ = v["Key"].(string)
+			}
+			value, _ := v["value"].(string)
+			if value == "" {
+				value, _ = v["Value"].(string)
+			}
+			if key == "" || value == "" {
+				continue
+			}
+			labels[key] = value
+		}
+	}
+	return labels
+}
+
+func splitTagString(tag string) (string, string, bool) {
+	tag = strings.TrimSpace(tag)
+	if tag == "" {
+		return "", "", false
+	}
+	key, value, found := strings.Cut(tag, ":")
+	key = strings.TrimSpace(key)
+	value = strings.TrimSpace(value)
+	if !found || key == "" || value == "" {
+		return "", "", false
+	}
+	return key, value, true
+}
+
+// normalizeAccountID returns the bare IBM account GUID. Billing exports and CRNs
+// may carry "a/<hex>"; CloudCost AccountID must not contain "/" (aggregation key
+// and storage path). Published Usage Reports samples are already bare hex.
+func normalizeAccountID(accountID string) string {
+	accountID = strings.TrimSpace(accountID)
+	return strings.TrimPrefix(accountID, "a/")
+}
+
+// selectIBMCategory maps IBM Usage Reports resource_id (service id) to an OpenCost category.
+// Pure function of resourceID. Exact matches only, except the documented databases-for-* family.
+func selectIBMCategory(resourceID string) string {
+	id := strings.ToLower(strings.TrimSpace(resourceID))
+
+	switch id {
+	case "is.instance",
+		"is.bare-metal-server",
+		"is.dedicated-host",
+		"codeengine",
+		"containers-kubernetes":
+		return opencost.ComputeCategory
+	case "is.volume",
+		"is.snapshot",
+		"is.share",
+		"cloud-object-storage":
+		return opencost.StorageCategory
+	case "is.load-balancer",
+		"is.floating-ip",
+		"is.public-gateway",
+		"is.vpn",
+		"transit",
+		"internet-svcs":
+		return opencost.NetworkCategory
+	}
+
+	// Prefix family (shared with billing-export / CAC): all IBM Databases for X services.
+	if strings.HasPrefix(id, "databases-for-") {
+		return opencost.StorageCategory
+	}
+	return opencost.OtherCategory
+}

+ 243 - 0
pkg/cloud/ibm/usageintegration_http_test.go

@@ -0,0 +1,243 @@
+package ibm
+
+import (
+	"encoding/json"
+	"net/http"
+	"net/http/httptest"
+	"net/url"
+	"strings"
+	"sync"
+	"testing"
+	"time"
+
+	"github.com/IBM/go-sdk-core/v5/core"
+	"github.com/IBM/platform-services-go-sdk/usagereportsv4"
+	"github.com/opencost/opencost/pkg/cloud"
+)
+
+const usageReportsTestAccountID = "b09edf5642ebfad587c594f4d4a354b0"
+
+type capturedUsageRequest struct {
+	method string
+	path   string
+	query  url.Values
+}
+
+func TestUsageIntegrationGetCloudCostPaginatesAndMapsResponses(t *testing.T) {
+	var (
+		mu       sync.Mutex
+		requests []capturedUsageRequest
+	)
+
+	server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
+		query := r.URL.Query()
+		mu.Lock()
+		requests = append(requests, capturedUsageRequest{method: r.Method, path: r.URL.EscapedPath(), query: query})
+		mu.Unlock()
+
+		w.Header().Set("Content-Type", "application/json")
+		response := map[string]any{
+			"count": 1,
+			"limit": 1,
+		}
+		switch query.Get("_start") {
+		case "":
+			response["next"] = map[string]any{
+				"href": "https://unused.test/resource-usage?_start=page-2",
+			}
+			response["resources"] = []any{
+				usageReportsTestResource("instance-1", "is.instance", 31, 62, "env:test"),
+			}
+		case "page-2":
+			response["resources"] = []any{
+				usageReportsTestResource("instance-2", "cloud-object-storage", 62, 93, "team:storage"),
+			}
+		default:
+			http.Error(w, `{"error":"unexpected page token"}`, http.StatusBadRequest)
+			return
+		}
+
+		if err := json.NewEncoder(w).Encode(response); err != nil {
+			t.Errorf("encoding response: %v", err)
+		}
+	}))
+	t.Cleanup(server.Close)
+
+	integration := newUsageReportsTestIntegration(t, server.URL)
+	start := time.Date(2026, 1, 15, 0, 0, 0, 0, time.UTC)
+	end := time.Date(2026, 1, 16, 0, 0, 0, 0, time.UTC)
+	asOf := time.Date(2026, 2, 1, 0, 0, 0, 0, time.UTC)
+
+	result, err := integration.getCloudCost(start, end, asOf)
+	if err != nil {
+		t.Fatalf("getCloudCost: %v", err)
+	}
+	if integration.ConnectionStatus != cloud.SuccessfulConnection {
+		t.Fatalf("ConnectionStatus = %s, want %s", integration.ConnectionStatus, cloud.SuccessfulConnection)
+	}
+
+	mu.Lock()
+	gotRequests := append([]capturedUsageRequest(nil), requests...)
+	mu.Unlock()
+	if len(gotRequests) != 2 {
+		t.Fatalf("request count = %d, want 2", len(gotRequests))
+	}
+	wantPath := "/v4/accounts/" + usageReportsTestAccountID + "/resource_instances/usage/2026-01"
+	for i, request := range gotRequests {
+		if request.method != http.MethodGet {
+			t.Errorf("request %d method = %q, want GET", i+1, request.method)
+		}
+		if request.path != wantPath {
+			t.Errorf("request %d path = %q, want %q", i+1, request.path, wantPath)
+		}
+		for key, want := range map[string]string{
+			"_limit": "200",
+			"_names": "true",
+			"_tags":  "true",
+		} {
+			if got := request.query.Get(key); got != want {
+				t.Errorf("request %d query %s = %q, want %q", i+1, key, got, want)
+			}
+		}
+	}
+	if got := gotRequests[0].query.Get("_start"); got != "" {
+		t.Errorf("first request _start = %q, want empty", got)
+	}
+	if got := gotRequests[1].query.Get("_start"); got != "page-2" {
+		t.Errorf("second request _start = %q, want page-2", got)
+	}
+
+	if len(result.CloudCostSets) != 31 {
+		t.Fatalf("CloudCostSet count = %d, want 31", len(result.CloudCostSets))
+	}
+	accumulated, err := result.AccumulateAll()
+	if err != nil {
+		t.Fatalf("accumulating result: %v", err)
+	}
+	if accumulated.Length() != 2 {
+		t.Fatalf("mapped CloudCost count = %d, want 2", accumulated.Length())
+	}
+
+	byProviderID := map[string]float64{}
+	for _, cost := range accumulated.CloudCosts {
+		byProviderID[cost.Properties.ProviderID] = cost.NetCost.Cost
+		if cost.Properties.AccountID != usageReportsTestAccountID {
+			t.Errorf("AccountID = %q, want normalized %q", cost.Properties.AccountID, usageReportsTestAccountID)
+		}
+		switch cost.Properties.ProviderID {
+		case "instance-1":
+			if cost.Properties.Service != "is.instance" || cost.Properties.Labels["env"] != "test" {
+				t.Errorf("instance-1 mapping = service %q, labels %#v", cost.Properties.Service, cost.Properties.Labels)
+			}
+		case "instance-2":
+			if cost.Properties.Service != "cloud-object-storage" || cost.Properties.Labels["team"] != "storage" {
+				t.Errorf("instance-2 mapping = service %q, labels %#v", cost.Properties.Service, cost.Properties.Labels)
+			}
+		default:
+			t.Errorf("unexpected ProviderID %q", cost.Properties.ProviderID)
+		}
+	}
+	if got := byProviderID["instance-1"]; got != 31 {
+		t.Errorf("instance-1 NetCost = %v, want 31", got)
+	}
+	if got := byProviderID["instance-2"]; got != 62 {
+		t.Errorf("instance-2 NetCost = %v, want 62", got)
+	}
+}
+
+func TestUsageIntegrationGetCloudCostMarksMissingData(t *testing.T) {
+	server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
+		w.Header().Set("Content-Type", "application/json")
+		if err := json.NewEncoder(w).Encode(map[string]any{
+			"count":     0,
+			"limit":     200,
+			"resources": []any{},
+		}); err != nil {
+			t.Errorf("encoding response: %v", err)
+		}
+	}))
+	t.Cleanup(server.Close)
+
+	integration := newUsageReportsTestIntegration(t, server.URL)
+	start := time.Date(2026, 1, 1, 0, 0, 0, 0, time.UTC)
+	end := time.Date(2026, 2, 1, 0, 0, 0, 0, time.UTC)
+	result, err := integration.getCloudCost(start, end, end)
+	if err != nil {
+		t.Fatalf("getCloudCost: %v", err)
+	}
+	if !result.IsEmpty() {
+		t.Fatal("expected an empty CloudCostSetRange")
+	}
+	if integration.ConnectionStatus != cloud.MissingData {
+		t.Errorf("ConnectionStatus = %s, want %s", integration.ConnectionStatus, cloud.MissingData)
+	}
+}
+
+func TestUsageIntegrationGetCloudCostMarksFailedConnection(t *testing.T) {
+	server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
+		w.Header().Set("Content-Type", "application/json")
+		w.WriteHeader(http.StatusInternalServerError)
+		_, _ = w.Write([]byte(`{"errors":[{"code":"server_error","message":"test failure"}]}`))
+	}))
+	t.Cleanup(server.Close)
+
+	integration := newUsageReportsTestIntegration(t, server.URL)
+	start := time.Date(2026, 1, 1, 0, 0, 0, 0, time.UTC)
+	end := time.Date(2026, 2, 1, 0, 0, 0, 0, time.UTC)
+	result, err := integration.getCloudCost(start, end, end)
+	if err == nil {
+		t.Fatal("getCloudCost error = nil, want request failure")
+	}
+	if result != nil {
+		t.Fatal("getCloudCost result is non-nil on request failure")
+	}
+	if !strings.Contains(err.Error(), "querying IBM resource usage for 2026-01") {
+		t.Errorf("error = %q, want month-scoped query context", err)
+	}
+	if integration.ConnectionStatus != cloud.FailedConnection {
+		t.Errorf("ConnectionStatus = %s, want %s", integration.ConnectionStatus, cloud.FailedConnection)
+	}
+}
+
+func newUsageReportsTestIntegration(t *testing.T, serviceURL string) *UsageIntegration {
+	t.Helper()
+	client, err := usagereportsv4.NewUsageReportsV4(&usagereportsv4.UsageReportsV4Options{
+		URL:           serviceURL,
+		Authenticator: &core.NoAuthAuthenticator{},
+	})
+	if err != nil {
+		t.Fatalf("creating Usage Reports test client: %v", err)
+	}
+
+	return &UsageIntegration{
+		UsageConfiguration: UsageConfiguration{AccountID: "a/" + usageReportsTestAccountID},
+		clientFactory: func() (*usagereportsv4.UsageReportsV4, error) {
+			return client, nil
+		},
+	}
+}
+
+func usageReportsTestResource(providerID, service string, cost, ratedCost float64, tag string) map[string]any {
+	return map[string]any{
+		"account_id":           "a/" + usageReportsTestAccountID,
+		"resource_instance_id": providerID,
+		"resource_id":          service,
+		"resource_name":        service + " display name",
+		"pricing_country":      "USA",
+		"currency_code":        "USD",
+		"currency_rate":        1,
+		"billable":             true,
+		"plan_id":              "test-plan",
+		"month":                "2026-01",
+		"tags":                 []any{tag},
+		"usage": []any{
+			map[string]any{
+				"metric":     "VCPU_HOURS",
+				"quantity":   1,
+				"cost":       cost,
+				"rated_cost": ratedCost,
+				"discounts":  []any{},
+			},
+		},
+	}
+}

+ 503 - 0
pkg/cloud/ibm/usageintegration_test.go

@@ -0,0 +1,503 @@
+package ibm
+
+import (
+	"testing"
+	"time"
+
+	"github.com/IBM/platform-services-go-sdk/usagereportsv4"
+	"github.com/opencost/opencost/core/pkg/opencost"
+)
+
+func TestMonthsOverlapping(t *testing.T) {
+	start := time.Date(2026, 1, 28, 0, 0, 0, 0, time.UTC)
+	end := time.Date(2026, 3, 2, 0, 0, 0, 0, time.UTC)
+	got := monthsOverlapping(start, end)
+	want := []string{"2026-01", "2026-02", "2026-03"}
+	if len(got) != len(want) {
+		t.Fatalf("monthsOverlapping len = %d, want %d (%v)", len(got), len(want), got)
+	}
+	for i := range want {
+		if got[i] != want[i] {
+			t.Errorf("monthsOverlapping[%d] = %q, want %q", i, got[i], want[i])
+		}
+	}
+}
+
+func TestMonthsOverlappingSameMonth(t *testing.T) {
+	start := time.Date(2026, 2, 1, 0, 0, 0, 0, time.UTC)
+	end := time.Date(2026, 2, 10, 0, 0, 0, 0, time.UTC)
+	got := monthsOverlapping(start, end)
+	if len(got) != 1 || got[0] != "2026-02" {
+		t.Errorf("got %v, want [2026-02]", got)
+	}
+}
+
+func TestDaysInMonth(t *testing.T) {
+	if got := daysInMonth(2026, 2); got != 28 {
+		t.Errorf("Feb 2026 days = %d, want 28", got)
+	}
+	if got := daysInMonth(2024, 2); got != 29 {
+		t.Errorf("Feb 2024 days = %d, want 29", got)
+	}
+	if got := daysInMonth(2026, 1); got != 31 {
+		t.Errorf("Jan 2026 days = %d, want 31", got)
+	}
+}
+
+func TestProrationDays(t *testing.T) {
+	monthStart := time.Date(2026, 1, 1, 0, 0, 0, 0, time.UTC)
+	if got := prorationDays(monthStart, time.Date(2026, 2, 15, 0, 0, 0, 0, time.UTC)); got != 31 {
+		t.Errorf("completed month = %d, want 31", got)
+	}
+	if got := prorationDays(monthStart, time.Date(2026, 1, 5, 12, 0, 0, 0, time.UTC)); got != 5 {
+		t.Errorf("MTD day 5 = %d, want 5", got)
+	}
+	if got := prorationDays(monthStart, time.Date(2026, 1, 31, 0, 0, 0, 0, time.UTC)); got != 31 {
+		t.Errorf("last day of month = %d, want 31", got)
+	}
+}
+
+func TestSelectIBMCategory(t *testing.T) {
+	tests := []struct {
+		service string
+		want    string
+	}{
+		{"is.instance", opencost.ComputeCategory},
+		{"is.bare-metal-server", opencost.ComputeCategory},
+		{"is.dedicated-host", opencost.ComputeCategory},
+		{"codeengine", opencost.ComputeCategory},
+		{"containers-kubernetes", opencost.ComputeCategory},
+		{"is.volume", opencost.StorageCategory},
+		{"is.snapshot", opencost.StorageCategory},
+		{"is.share", opencost.StorageCategory},
+		{"cloud-object-storage", opencost.StorageCategory},
+		{"databases-for-postgresql", opencost.StorageCategory},
+		{"databases-for-redis", opencost.StorageCategory},
+		{"is.load-balancer", opencost.NetworkCategory},
+		{"is.floating-ip", opencost.NetworkCategory},
+		{"is.public-gateway", opencost.NetworkCategory},
+		{"is.vpn", opencost.NetworkCategory},
+		{"transit", opencost.NetworkCategory},
+		{"internet-svcs", opencost.NetworkCategory},
+		// Prefix arms removed: these must not silently widen beyond the locked table.
+		{"is.vpn-server", opencost.OtherCategory},
+		{"transit-gateway", opencost.OtherCategory},
+		{"codeengine-app", opencost.OtherCategory},
+		{"is.vpc", opencost.OtherCategory},
+		{"dns", opencost.OtherCategory},
+		{"cdn", opencost.OtherCategory},
+		{"", opencost.OtherCategory},
+		{"unknown-service", opencost.OtherCategory},
+	}
+	for _, tt := range tests {
+		if got := selectIBMCategory(tt.service); got != tt.want {
+			t.Errorf("selectIBMCategory(%q) = %q, want %q", tt.service, got, tt.want)
+		}
+	}
+}
+
+func TestParseTags(t *testing.T) {
+	tags := parseTags([]any{
+		"env:prod",
+		"staging",
+		map[string]any{"key": "owner", "value": "platform"},
+		map[string]any{"Key": "team", "Value": "sre"},
+		map[string]any{"key": "empty", "value": ""},
+	})
+	if tags["env"] != "prod" {
+		t.Errorf("env tag = %q, want prod", tags["env"])
+	}
+	if tags["owner"] != "platform" {
+		t.Errorf("owner tag = %q, want platform", tags["owner"])
+	}
+	if tags["team"] != "sre" {
+		t.Errorf("team tag = %q, want sre", tags["team"])
+	}
+	if _, ok := tags["staging"]; ok {
+		t.Error("non key:value string tag should be omitted")
+	}
+	if _, ok := tags["empty"]; ok {
+		t.Error("empty tag value should be omitted")
+	}
+}
+
+func TestInstanceUsageFromSDK_SkipsNonBillable(t *testing.T) {
+	billable := false
+	cost := 10.0
+	item := usagereportsv4.InstanceUsage{
+		Billable: &billable,
+		Usage: []usagereportsv4.Metric{
+			{Cost: &cost, RatedCost: &cost},
+		},
+	}
+	if _, ok := instanceUsageFromSDK(item); ok {
+		t.Fatal("expected non-billable instance to be skipped")
+	}
+}
+
+func TestInstanceUsageFromSDK_CurrencyConversion(t *testing.T) {
+	billable := true
+	cost := 10.0
+	rated := 20.0
+	rate := 1.5
+	month := "2026-01"
+	account := "acct"
+	resourceID := "is.instance"
+	item := usagereportsv4.InstanceUsage{
+		Billable:     &billable,
+		AccountID:    &account,
+		ResourceID:   &resourceID,
+		Month:        &month,
+		CurrencyRate: &rate,
+		Usage: []usagereportsv4.Metric{
+			{Cost: &cost, RatedCost: &rated},
+		},
+		Tags:        []any{"env:test"},
+		ServiceTags: []any{"svc:iks"},
+	}
+	record, ok := instanceUsageFromSDK(item)
+	if !ok {
+		t.Fatal("expected billable instance")
+	}
+	if record.Cost != 15.0 {
+		t.Errorf("Cost = %v, want 15", record.Cost)
+	}
+	if record.RatedCost != 30.0 {
+		t.Errorf("RatedCost = %v, want 30", record.RatedCost)
+	}
+	labels := parseTags(record.Tags)
+	if labels["env"] != "test" || labels["svc"] != "iks" {
+		t.Errorf("merged tags = %#v", labels)
+	}
+}
+
+func TestInstanceUsageFromSDK_NormalizesPrefixedAccountID(t *testing.T) {
+	billable := true
+	month := "2026-01"
+	account := "a/b09edf5642ebfad587c594f4d4a354b0"
+	resourceID := "is.instance"
+	cost := 1.0
+	item := usagereportsv4.InstanceUsage{
+		Billable:   &billable,
+		AccountID:  &account,
+		ResourceID: &resourceID,
+		Month:      &month,
+		Usage: []usagereportsv4.Metric{
+			{Cost: &cost, RatedCost: &cost},
+		},
+	}
+	record, ok := instanceUsageFromSDK(item)
+	if !ok {
+		t.Fatal("expected billable instance")
+	}
+	if record.AccountID != "b09edf5642ebfad587c594f4d4a354b0" {
+		t.Errorf("AccountID = %q, want bare hex", record.AccountID)
+	}
+}
+
+func TestInstanceUsageFromSDK_SkipsNonChargeableMetrics(t *testing.T) {
+	billable := true
+	cost := 5.0
+	rated := 5.0
+	info := true
+	month := "2026-01"
+	item := usagereportsv4.InstanceUsage{
+		Billable: &billable,
+		Month:    &month,
+		Usage: []usagereportsv4.Metric{
+			{Cost: &cost, RatedCost: &rated, NonChargeable: &info},
+			{Cost: &cost, RatedCost: &rated},
+		},
+	}
+	record, ok := instanceUsageFromSDK(item)
+	if !ok {
+		t.Fatal("expected billable instance")
+	}
+	if record.Cost != 5.0 || record.RatedCost != 5.0 {
+		t.Errorf("got cost=%v rated=%v, want 5/5", record.Cost, record.RatedCost)
+	}
+}
+
+func TestCloudCostsFromInstance(t *testing.T) {
+	start := time.Date(2026, 1, 30, 0, 0, 0, 0, time.UTC)
+	end := time.Date(2026, 2, 2, 0, 0, 0, 0, time.UTC)
+	asOf := time.Date(2026, 2, 15, 0, 0, 0, 0, time.UTC)
+
+	item := instanceUsageRecord{
+		AccountID:          "b09edf5642ebfad587c594f4d4a354b0",
+		ResourceInstanceID: "crn:v1:bluemix:public:containers-kubernetes:us-south:a/b09edf5642ebfad587c594f4d4a354b0:8042b2a8af6a4a5cbf6dbe09e07311d2:worker:kube-w1",
+		ResourceID:         "containers-kubernetes",
+		ResourceName:       "IBM Cloud Kubernetes Service",
+		Region:             "us-south",
+		Month:              "2026-01",
+		Cost:               31.0,
+		RatedCost:          62.0,
+		Tags:               []any{"team:sre"},
+	}
+
+	costs := cloudCostsFromInstance(item, start, end, asOf)
+	// Every day the month total was prorated across is emitted, not just the days inside the
+	// window, so the whole of the completed January is returned.
+	if len(costs) != 31 {
+		t.Fatalf("got %d cloud costs, want 31 (whole prorated January)", len(costs))
+	}
+
+	dailyNet := 31.0 / 31.0
+	dailyList := 62.0 / 31.0
+	for _, cc := range costs {
+		if cc.Properties.Provider != opencost.IBMProvider {
+			t.Errorf("provider = %q", cc.Properties.Provider)
+		}
+		if cc.Properties.AccountID != "b09edf5642ebfad587c594f4d4a354b0" {
+			t.Errorf("account = %q", cc.Properties.AccountID)
+		}
+		if cc.Properties.InvoiceEntityID != cc.Properties.AccountID {
+			t.Errorf("InvoiceEntityID = %q, want AccountID", cc.Properties.InvoiceEntityID)
+		}
+		if cc.Properties.Service != "containers-kubernetes" {
+			t.Errorf("service = %q, want ResourceID", cc.Properties.Service)
+		}
+		if cc.Properties.Category != opencost.ComputeCategory {
+			t.Errorf("category = %q", cc.Properties.Category)
+		}
+		if cc.Properties.Labels["team"] != "sre" {
+			t.Errorf("labels = %#v", cc.Properties.Labels)
+		}
+		if cc.Properties.Labels["ibm_resource_name"] != "IBM Cloud Kubernetes Service" {
+			t.Errorf("ibm_resource_name = %q", cc.Properties.Labels["ibm_resource_name"])
+		}
+		if cc.NetCost.Cost != dailyNet {
+			t.Errorf("net cost = %v, want %v", cc.NetCost.Cost, dailyNet)
+		}
+		if cc.ListCost.Cost != dailyList {
+			t.Errorf("list cost = %v, want %v", cc.ListCost.Cost, dailyList)
+		}
+		if cc.AmortizedCost.Cost != dailyList {
+			t.Errorf("amortized cost = %v, want list/rated %v", cc.AmortizedCost.Cost, dailyList)
+		}
+	}
+}
+
+func TestCloudCostsFromInstance_RatedCostZeroKept(t *testing.T) {
+	start := time.Date(2026, 1, 1, 0, 0, 0, 0, time.UTC)
+	end := time.Date(2026, 1, 2, 0, 0, 0, 0, time.UTC)
+	asOf := time.Date(2026, 2, 1, 0, 0, 0, 0, time.UTC)
+	item := instanceUsageRecord{
+		AccountID:          "acct",
+		ResourceInstanceID: "inst",
+		ResourceID:         "is.instance",
+		Month:              "2026-01",
+		Cost:               31.0,
+		RatedCost:          0,
+	}
+	costs := cloudCostsFromInstance(item, start, end, asOf)
+	if len(costs) != 31 {
+		t.Fatalf("got %d costs, want 31 (whole prorated month)", len(costs))
+	}
+	if costs[0].ListCost.Cost != 0 {
+		t.Errorf("ListCost = %v, want 0 (rated_cost zero is valid)", costs[0].ListCost.Cost)
+	}
+	if costs[0].NetCost.Cost != 1.0 {
+		t.Errorf("NetCost = %v, want 1", costs[0].NetCost.Cost)
+	}
+}
+
+func TestCloudCostsFromInstance_MTDProration(t *testing.T) {
+	start := time.Date(2026, 1, 1, 0, 0, 0, 0, time.UTC)
+	end := time.Date(2026, 1, 6, 0, 0, 0, 0, time.UTC)
+	asOf := time.Date(2026, 1, 5, 0, 0, 0, 0, time.UTC)
+	item := instanceUsageRecord{
+		AccountID:          "acct",
+		ResourceInstanceID: "inst",
+		ResourceID:         "is.instance",
+		Month:              "2026-01",
+		Cost:               50.0,
+		RatedCost:          50.0,
+	}
+	costs := cloudCostsFromInstance(item, start, end, asOf)
+	if len(costs) != 5 {
+		t.Fatalf("got %d costs, want 5 MTD days", len(costs))
+	}
+	if costs[0].NetCost.Cost != 10.0 {
+		t.Errorf("daily net = %v, want 10 (50/5)", costs[0].NetCost.Cost)
+	}
+}
+
+func TestServiceUsesResourceID(t *testing.T) {
+	item := instanceUsageRecord{
+		AccountID:          "acct",
+		ResourceInstanceID: "inst",
+		ResourceID:         "is.instance",
+		ResourceName:       "Virtual Server for VPC",
+		Month:              "2026-02",
+		Cost:               28.0,
+		RatedCost:          28.0,
+	}
+	start := time.Date(2026, 2, 1, 0, 0, 0, 0, time.UTC)
+	end := time.Date(2026, 2, 2, 0, 0, 0, 0, time.UTC)
+	asOf := time.Date(2026, 3, 1, 0, 0, 0, 0, time.UTC)
+	costs := cloudCostsFromInstance(item, start, end, asOf)
+	if len(costs) != 28 {
+		t.Fatalf("got %d costs, want 28 (whole prorated February)", len(costs))
+	}
+	if costs[0].Properties.Service != "is.instance" {
+		t.Errorf("service = %q, want is.instance", costs[0].Properties.Service)
+	}
+	if costs[0].Properties.Labels["ibm_resource_name"] != "Virtual Server for VPC" {
+		t.Errorf("display name label = %q", costs[0].Properties.Labels["ibm_resource_name"])
+	}
+}
+
+func TestNormalizeAccountID(t *testing.T) {
+	tests := []struct {
+		in, want string
+	}{
+		{"b09edf5642ebfad587c594f4d4a354b0", "b09edf5642ebfad587c594f4d4a354b0"},
+		{"a/b09edf5642ebfad587c594f4d4a354b0", "b09edf5642ebfad587c594f4d4a354b0"},
+		{"  a/acct  ", "acct"},
+		{"", ""},
+	}
+	for _, tt := range tests {
+		if got := normalizeAccountID(tt.in); got != tt.want {
+			t.Errorf("normalizeAccountID(%q) = %q, want %q", tt.in, got, tt.want)
+		}
+	}
+}
+
+func TestUsageConfigurationKeySanitizesSlash(t *testing.T) {
+	cfg := &UsageConfiguration{AccountID: "a/b09edf5642ebfad587c594f4d4a354b0"}
+	if got := cfg.Key(); got != "b09edf5642ebfad587c594f4d4a354b0" {
+		t.Errorf("Key() = %q, want bare hex", got)
+	}
+}
+
+// The billing-export producer (cloudability/kubecost-saas, bingen/ibm) marks every IKS/ROKS row
+// as fully Kubernetes. Both producers write into the same aggregation, so if this path leaves
+// KubernetesPercent at zero, migrating a customer from the export to an API key silently zeroes
+// their entire IBM Kubernetes spend.
+func TestKubernetesPercentMatchesBillingExportProducer(t *testing.T) {
+	const workerCRN = "crn:v1:bluemix:public:containers-kubernetes:jp-tok:a/f8ce6d5aa4cf4:" +
+		"ch9goilt0elehhb3utbg:worker:kube-ch9goilt0elehhb3utbg-ccmdefault-0000017d"
+
+	tests := []struct {
+		name       string
+		resourceID string
+		providerID string
+		want       float64
+	}{
+		{"kubernetes by service id", "containers-kubernetes", "crn:v1:bluemix:public:containers-kubernetes:us-south:a/x:y::", 1.0},
+		{"kubernetes by crn service segment", "", workerCRN, 1.0},
+		{"service id is case insensitive", "Containers-Kubernetes", "", 1.0},
+		{"unrelated service", "is.volume", "crn:v1:bluemix:public:is:us-south:a/x::volume:r006-1", 0.0},
+	}
+
+	start := time.Date(2026, 2, 1, 0, 0, 0, 0, time.UTC)
+	end := time.Date(2026, 2, 2, 0, 0, 0, 0, time.UTC)
+	asOf := time.Date(2026, 3, 1, 0, 0, 0, 0, time.UTC)
+
+	for _, tt := range tests {
+		tt := tt
+		t.Run(tt.name, func(t *testing.T) {
+			item := instanceUsageRecord{
+				AccountID:          "acct",
+				ResourceInstanceID: tt.providerID,
+				ResourceID:         tt.resourceID,
+				Month:              "2026-02",
+				Cost:               28.0,
+				RatedCost:          56.0,
+			}
+			costs := cloudCostsFromInstance(item, start, end, asOf)
+			if len(costs) != 28 {
+				t.Fatalf("got %d costs, want 28 (whole prorated February)", len(costs))
+			}
+			cc := costs[0]
+			for metric, got := range map[string]float64{
+				"ListCost":         cc.ListCost.KubernetesPercent,
+				"NetCost":          cc.NetCost.KubernetesPercent,
+				"AmortizedNetCost": cc.AmortizedNetCost.KubernetesPercent,
+				"AmortizedCost":    cc.AmortizedCost.KubernetesPercent,
+				"InvoicedCost":     cc.InvoicedCost.KubernetesPercent,
+			} {
+				if got != tt.want {
+					t.Errorf("%s.KubernetesPercent = %v, want %v", metric, got, tt.want)
+				}
+			}
+		})
+	}
+}
+
+// bingen/ibm sets AccountName and InvoiceEntityName to the same normalized account id, because IBM
+// billing data carries no account display name. Both are user-selectable aggregation properties, so
+// leaving them empty here would split rows when a customer switches producers.
+func TestAccountNamesMatchBillingExportProducer(t *testing.T) {
+	item := instanceUsageRecord{
+		AccountID:          "4756fbd48cbc4b6d968340888c27fd05",
+		ResourceInstanceID: "inst",
+		ResourceID:         "is.instance",
+		Month:              "2026-02",
+		Cost:               28.0,
+		RatedCost:          28.0,
+	}
+	start := time.Date(2026, 2, 1, 0, 0, 0, 0, time.UTC)
+	end := time.Date(2026, 2, 2, 0, 0, 0, 0, time.UTC)
+	asOf := time.Date(2026, 3, 1, 0, 0, 0, 0, time.UTC)
+
+	costs := cloudCostsFromInstance(item, start, end, asOf)
+	if len(costs) != 28 {
+		t.Fatalf("got %d costs, want 28 (whole prorated February)", len(costs))
+	}
+	props := costs[0].Properties
+	if props.AccountName != item.AccountID {
+		t.Errorf("AccountName = %q, want %q", props.AccountName, item.AccountID)
+	}
+	if props.InvoiceEntityName != item.AccountID {
+		t.Errorf("InvoiceEntityName = %q, want %q", props.InvoiceEntityName, item.AccountID)
+	}
+	// Pinned so a later edit cannot populate it and split rows against the other producer.
+	if props.AvailabilityZone != "" {
+		t.Errorf("AvailabilityZone = %q, want empty", props.AvailabilityZone)
+	}
+}
+
+// A month total is divided by a divisor derived from asOf, so the resulting flat daily rate is only
+// self-consistent if every day it covers is written in the same pass. The ingestor's Put replaces
+// whole day-sets, so emitting only the days inside the caller's window leaves the rest of the month
+// holding rates computed at an earlier asOf, and the stored month sums to neither total.
+//
+// The integration already fetches the whole month from IBM regardless of the window, so covering
+// every prorated day costs no extra API calls.
+func TestCloudCostsFromInstanceCoversWholeProratedMonth(t *testing.T) {
+	// Stock ingestor shape: a 7-day window that does not reach the first of the month.
+	start := time.Date(2026, 10, 8, 0, 0, 0, 0, time.UTC)
+	end := time.Date(2026, 10, 15, 0, 0, 0, 0, time.UTC)
+	asOf := time.Date(2026, 10, 11, 0, 0, 0, 0, time.UTC)
+
+	item := instanceUsageRecord{
+		AccountID:          "acct",
+		ResourceInstanceID: "inst",
+		ResourceID:         "is.instance",
+		Month:              "2026-10",
+		Cost:               110.0,
+		RatedCost:          110.0,
+	}
+
+	costs := cloudCostsFromInstance(item, start, end, asOf)
+
+	// asOf is the 11th, so the month-to-date total spreads over days 1..11.
+	if len(costs) != 11 {
+		t.Fatalf("got %d costs, want 11 (every prorated day of the month)", len(costs))
+	}
+
+	var total float64
+	for _, cc := range costs {
+		total += cc.NetCost.Cost
+	}
+	if diff := total - item.Cost; diff > 1e-9 || diff < -1e-9 {
+		t.Errorf("emitted total = %v, want %v (IBM's reported month total)", total, item.Cost)
+	}
+
+	// First emitted day must be the first of the month, not the window start.
+	if got := costs[0].Window.Start().UTC(); !got.Equal(time.Date(2026, 10, 1, 0, 0, 0, 0, time.UTC)) {
+		t.Errorf("first day = %s, want 2026-10-01", got.Format("2006-01-02"))
+	}
+}

+ 5 - 0
pkg/cloudcost/integration.go

@@ -9,6 +9,7 @@ import (
 	"github.com/opencost/opencost/pkg/cloud/aws"
 	"github.com/opencost/opencost/pkg/cloud/aws"
 	"github.com/opencost/opencost/pkg/cloud/azure"
 	"github.com/opencost/opencost/pkg/cloud/azure"
 	"github.com/opencost/opencost/pkg/cloud/gcp"
 	"github.com/opencost/opencost/pkg/cloud/gcp"
+	"github.com/opencost/opencost/pkg/cloud/ibm"
 	"github.com/opencost/opencost/pkg/cloud/oracle"
 	"github.com/opencost/opencost/pkg/cloud/oracle"
 	"github.com/opencost/opencost/pkg/cloud/stackit"
 	"github.com/opencost/opencost/pkg/cloud/stackit"
 )
 )
@@ -110,6 +111,10 @@ func GetIntegrationFromConfig(kc cloud.KeyedConfig) CloudCostIntegration {
 		return &stackit.CostIntegration{
 		return &stackit.CostIntegration{
 			CostConfiguration: *keyedConfig,
 			CostConfiguration: *keyedConfig,
 		}
 		}
+	case *ibm.UsageConfiguration:
+		return &ibm.UsageIntegration{
+			UsageConfiguration: *keyedConfig,
+		}
 	default:
 	default:
 		return nil
 		return nil
 	}
 	}