diff --git a/CHANGELOG.md b/CHANGELOG.md index 6fb23b6..9e5fe1b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,7 @@ - don't cache zones forever (for example removed permissions should lead to removed targets in the platform) - include tags in the discovery of Lambda functions (requires new permission `tag:GetResources`) - add vpc name to targets (requires new permission `ec2:DescribeVpcs`, can be disabled by `STEADYBIT_EXTENSION_DISCOVERY_DISABLED_VPC`) +- add subnet target discovery ## v2.3.6 diff --git a/README.md b/README.md index 9f9b449..eb1bd68 100644 --- a/README.md +++ b/README.md @@ -32,6 +32,8 @@ our [Reliability Hub](https://hub.steadybit.com/extension/com.steadybit.extensio | `STEADYBIT_EXTENSION_DISCOVERY_INTERVAL_LAMBDA` | | Discovery-Interval in seconds | no | 60 | | `STEADYBIT_EXTENSION_DISCOVERY_DISABLED_RDS` | `aws.discovery.disabled.rds` | Disable RDS-Discovery and all related definitions | no | false | | `STEADYBIT_EXTENSION_DISCOVERY_INTERVAL_RDS` | | Discovery-Interval in seconds | no | 30 | +| `STEADYBIT_EXTENSION_DISCOVERY_DISABLED_SUBNET` | `aws.discovery.disabled.subnet` | Disable Subnet-Discovery and all related definitions | no | false | +| `STEADYBIT_EXTENSION_DISCOVERY_INTERVAL_SUBNET` | | Discovery-Interval in seconds | no | 30 | | `STEADYBIT_EXTENSION_DISCOVERY_DISABLED_VPC` | `aws.discovery.disabled.vpc` | Disable VPC-Discovery and all related definitions | no | false | | `STEADYBIT_EXTENSION_DISCOVERY_DISABLED_ZONE` | `aws.discovery.disabled.zone` | Disable Zone-Discovery and all related definitions | no | false | | `STEADYBIT_EXTENSION_DISCOVERY_INTERVAL_ZONE` | | Discovery-Interval in seconds | no | 300 | @@ -45,6 +47,7 @@ our [Reliability Hub](https://hub.steadybit.com/extension/com.steadybit.extensio | `STEADYBIT_EXTENSION_DISCOVERY_ATTRIBUTES_EXCLUDES_MSK` | `aws.discovery.attributes.excludes.msk` | List of MSK Target Attributes which will be excluded during discovery. Checked by key equality and supporting trailing "*" | no | | | `STEADYBIT_EXTENSION_DISCOVERY_ATTRIBUTES_EXCLUDES_LAMBDA` | `aws.discovery.attributes.excludes.lambda` | List of Lambda Target Attributes which will be excluded during discovery. Checked by key equality and supporting trailing "*" | no | | | `STEADYBIT_EXTENSION_DISCOVERY_ATTRIBUTES_EXCLUDES_RDS` | `aws.discovery.attributes.excludes.rds` | List of RDS Target Attributes which will be excluded during discovery. Checked by key equality and supporting trailing "*" | no | | +| `STEADYBIT_EXTENSION_DISCOVERY_ATTRIBUTES_EXCLUDES_SUBNET` | `aws.discovery.attributes.excludes.subnet` | List of Subnet Target Attributes which will be excluded during discovery. Checked by key equality and supporting trailing "*" | no | | | `STEADYBIT_EXTENSION_DISCOVERY_ATTRIBUTES_EXCLUDES_ZONE` | `aws.discovery.attributes.excludes.zone` | List of Availibilty Zone Target Attributes which will be excluded during discovery. Checked by key equality and supporting trailing "*" | no | | The extension supports all environment variables provided by [steadybit/extension-kit](https://github.com/steadybit/extension-kit#environment-variables). diff --git a/charts/steadybit-extension-aws/Chart.yaml b/charts/steadybit-extension-aws/Chart.yaml index 006040d..230d230 100644 --- a/charts/steadybit-extension-aws/Chart.yaml +++ b/charts/steadybit-extension-aws/Chart.yaml @@ -1,7 +1,7 @@ apiVersion: v2 name: steadybit-extension-aws description: Steadybit AWS extension Helm chart for Kubernetes. -version: 2.1.24 +version: 2.1.25 appVersion: v2.3.6 home: https://www.steadybit.com/ icon: https://steadybit-website-assets.s3.amazonaws.com/logo-symbol-transparent.png diff --git a/charts/steadybit-extension-aws/templates/deployment.yaml b/charts/steadybit-extension-aws/templates/deployment.yaml index 3919940..b8ca0de 100644 --- a/charts/steadybit-extension-aws/templates/deployment.yaml +++ b/charts/steadybit-extension-aws/templates/deployment.yaml @@ -124,6 +124,14 @@ spec: - name: STEADYBIT_EXTENSION_DISCOVERY_ATTRIBUTES_EXCLUDES_RDS value: {{ join "," .Values.aws.discovery.attributes.excludes.rds | quote }} {{- end }} + {{- if .Values.aws.discovery.attributes.excludes.subnet }} + - name: STEADYBIT_EXTENSION_DISCOVERY_ATTRIBUTES_EXCLUDES_SUBNET + value: {{ join "," .Values.aws.discovery.attributes.excludes.subnet | quote }} + {{- end }} + {{- if .Values.aws.discovery.attributes.excludes.vpc }} + - name: STEADYBIT_EXTENSION_DISCOVERY_ATTRIBUTES_EXCLUDES_VPC + value: {{ join "," .Values.aws.discovery.attributes.excludes.vpc | quote }} + {{- end }} {{- with .Values.extraEnv }} {{- toYaml . | nindent 12 }} {{- end }} @@ -155,6 +163,14 @@ spec: - name: STEADYBIT_EXTENSION_DISCOVERY_DISABLED_RDS value: "true" {{- end }} + {{- if .Values.aws.discovery.disabled.subnet }} + - name: STEADYBIT_EXTENSION_DISCOVERY_DISABLED_SUBNET + value: "true" + {{- end }} + {{- if .Values.aws.discovery.disabled.vpc }} + - name: STEADYBIT_EXTENSION_DISCOVERY_DISABLED_VPC + value: "true" + {{- end }} {{- if .Values.aws.discovery.disabled.zone }} - name: STEADYBIT_EXTENSION_DISCOVERY_DISABLED_ZONE value: "true" diff --git a/charts/steadybit-extension-aws/tests/__snapshot__/deployment_test.yaml.snap b/charts/steadybit-extension-aws/tests/__snapshot__/deployment_test.yaml.snap index 1c75558..79b5666 100644 --- a/charts/steadybit-extension-aws/tests/__snapshot__/deployment_test.yaml.snap +++ b/charts/steadybit-extension-aws/tests/__snapshot__/deployment_test.yaml.snap @@ -50,6 +50,10 @@ manifest should add aws env vars: value: example.lambda.1,example.lambda.2 - name: STEADYBIT_EXTENSION_DISCOVERY_ATTRIBUTES_EXCLUDES_RDS value: example.rds.1,example.rds.2 + - name: STEADYBIT_EXTENSION_DISCOVERY_ATTRIBUTES_EXCLUDES_SUBNET + value: example.subnet.1,example.subnet.2 + - name: STEADYBIT_EXTENSION_DISCOVERY_ATTRIBUTES_EXCLUDES_VPC + value: example.vpc.1,example.vpc.2 - name: STEADYBIT_EXTENSION_DISCOVERY_DISABLED_EC2 value: "true" - name: STEADYBIT_EXTENSION_DISCOVERY_DISABLED_ECS @@ -64,6 +68,10 @@ manifest should add aws env vars: value: "true" - name: STEADYBIT_EXTENSION_DISCOVERY_DISABLED_RDS value: "true" + - name: STEADYBIT_EXTENSION_DISCOVERY_DISABLED_SUBNET + value: "true" + - name: STEADYBIT_EXTENSION_DISCOVERY_DISABLED_VPC + value: "true" - name: STEADYBIT_EXTENSION_DISCOVERY_DISABLED_ZONE value: "true" image: ghcr.io/steadybit/extension-aws:v0.0.0 diff --git a/charts/steadybit-extension-aws/tests/deployment_test.yaml b/charts/steadybit-extension-aws/tests/deployment_test.yaml index 797d4ef..259be37 100644 --- a/charts/steadybit-extension-aws/tests/deployment_test.yaml +++ b/charts/steadybit-extension-aws/tests/deployment_test.yaml @@ -23,7 +23,9 @@ tests: fis: true lambda: true rds: true + subnet: true zone: true + vpc: true attributes: excludes: ec2: @@ -44,9 +46,15 @@ tests: rds: - "example.rds.1" - "example.rds.2" + subnet: + - "example.subnet.1" + - "example.subnet.2" zone: - "example.zone.1" - "example.zone.2" + vpc: + - "example.vpc.1" + - "example.vpc.2" asserts: - matchSnapshot: {} - it: manifest should match snapshot using podAnnotations and Labels diff --git a/charts/steadybit-extension-aws/values.yaml b/charts/steadybit-extension-aws/values.yaml index 4bc3d3f..171f01f 100644 --- a/charts/steadybit-extension-aws/values.yaml +++ b/charts/steadybit-extension-aws/values.yaml @@ -25,6 +25,8 @@ aws: lambda: false # aws.discovery.disabled.rds -- Disables RDS discovery and the related actions. rds: false + # aws.discovery.disabled.subnet -- Disables Subnet discovery and the related actions. + subnet: false # aws.discovery.disabled.vpc -- Disables VPC discovery and the related actions. vpc: false # aws.discovery.disabled.zone -- Disables AZ discovery and the related actions. @@ -45,6 +47,8 @@ aws: msk: [] # aws.discovery.attributes.excludes.lambda -- List of attributes to exclude from Lambda discovery. lambda: [] + # aws.discovery.attributes.excludes.subnet -- List of attributes to exclude from Subnet discovery. + subnet: [] # aws.discovery.attributes.excludes.rds -- List of attributes to exclude from RDS discovery. rds: [] # aws.discovery.attributes.excludes.zone -- List of attributes to exclude from AZ discovery. diff --git a/config/specification.go b/config/specification.go index 2d30581..c8f5f6b 100644 --- a/config/specification.go +++ b/config/specification.go @@ -16,9 +16,10 @@ type Specification struct { DiscoveryDisabledMsk bool `json:"discoveryDisabledMsk" split_words:"true" required:"false" default:"false"` DiscoveryDisabledLambda bool `json:"discoveryDisabledLambda" split_words:"true" required:"false" default:"false"` DiscoveryDisabledRds bool `json:"discoveryDisabledRds" split_words:"true" required:"false" default:"false"` + DiscoveryDisabledSubnet bool `json:"discoveryDisabledSubnet" split_words:"true" required:"false" default:"false"` DiscoveryDisabledZone bool `json:"discoveryDisabledZone" split_words:"true" required:"false" default:"false"` - DiscoveryIntervalEc2 int `json:"discoveryIntervalEc2" split_words:"true" required:"false" default:"30"` DiscoveryDisabledVpc bool `json:"discoveryDisabledVpc" split_words:"true" required:"false" default:"false"` + DiscoveryIntervalEc2 int `json:"discoveryIntervalEc2" split_words:"true" required:"false" default:"30"` DiscoveryIntervalEcsService int `json:"discoveryIntervalEcsService" split_words:"true" required:"false" default:"30"` DiscoveryIntervalEcsTask int `json:"discoveryIntervalEcsTask" split_words:"true" required:"false" default:"30"` DiscoveryIntervalElasticacheReplicationGroup int `json:"discoveryIntervalElasticacheReplicationGroup" split_words:"true" required:"false" default:"30"` @@ -27,6 +28,7 @@ type Specification struct { DiscoveryIntervalFis int `json:"discoveryIntervalFis" split_words:"true" required:"false" default:"300"` DiscoveryIntervalLambda int `json:"discoveryIntervalLambda" split_words:"true" required:"false" default:"60"` DiscoveryIntervalRds int `json:"discoveryIntervalRds" split_words:"true" required:"false" default:"30"` + DiscoveryIntervalSubnet int `json:"discoveryIntervalSubnet" split_words:"true" required:"false" default:"30"` DiscoveryIntervalZone int `json:"discoveryIntervalZone" split_words:"true" required:"false" default:"300"` EnrichEc2DataForTargetTypes []string `json:"EnrichEc2DataForTargetTypes" split_words:"true" default:"com.steadybit.extension_jvm.jvm-instance,com.steadybit.extension_container.container,com.steadybit.extension_kubernetes.kubernetes-deployment,com.steadybit.extension_kubernetes.kubernetes-pod,com.steadybit.extension_kubernetes.kubernetes-daemonset,com.steadybit.extension_kubernetes.kubernetes-statefulset,com.steadybit.extension_http.client-location,com.steadybit.extension_jmeter.location,com.steadybit.extension_k6.location,com.steadybit.extension_gatling.location"` EnrichEc2DataMatcherAttribute string `json:"EnrichEc2DataMatcherAttribute" split_words:"true" default:"host.hostname"` @@ -38,6 +40,7 @@ type Specification struct { DiscoveryAttributesExcludesMsk []string `json:"discoveryAttributesExcludesMsk" split_words:"true" required:"false"` DiscoveryAttributesExcludesLambda []string `json:"discoveryAttributesExcludesLambda" split_words:"true" required:"false"` DiscoveryAttributesExcludesRds []string `json:"discoveryAttributesExcludesRds" split_words:"true" required:"false"` + DiscoveryAttributesExcludesSubnet []string `json:"discoveryAttributesExcludesSubnet" split_words:"true" required:"false"` DiscoveryAttributesExcludesZone []string `json:"discoveryAttributesExcludesZone" split_words:"true" required:"false"` DisableDiscoveryExcludes bool `required:"false" split_words:"true" default:"false"` } diff --git a/extec2/common.go b/extec2/common.go index 79d4ef2..1c73c5c 100644 --- a/extec2/common.go +++ b/extec2/common.go @@ -10,4 +10,6 @@ const ( ec2InstanceStateActionId = "com.steadybit.extension_aws.ec2_instance.state" ec2TargetType = "com.steadybit.extension_aws.ec2-instance" ec2Icon = "data:image/svg+xml,%3Csvg%20width%3D%2224%22%20height%3D%2224%22%20viewBox%3D%220%200%2024%2024%22%20fill%3D%22none%22%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%3E%0A%3Cpath%20fill-rule%3D%22evenodd%22%20clip-rule%3D%22evenodd%22%20d%3D%22M22.04%202.54998C21.83%202.33998%2021.56%202.22998%2021.27%202.22998H11.79C11.5%202.22998%2011.23%202.33998%2011.02%202.54998C10.81%202.75998%2010.7%203.02998%2010.7%203.31998V5.59998H12.09V3.61998H20.97V12.51H18.99V13.9H21.27C21.56%2013.9%2021.84%2013.78%2022.04%2013.58C22.25%2013.37%2022.36%2013.1%2022.36%2012.81V3.31998C22.36%203.02998%2022.25%202.74998%2022.04%202.54998ZM12.27%2021.2H3.39V12.32H5.37V10.93H3.09C2.8%2010.93%202.53%2011.04%202.32%2011.25C2.11%2011.46%202%2011.73%202%2012.02V21.5C2%2021.79%202.11%2022.06%202.32%2022.27C2.53%2022.48%202.8%2022.59%203.09%2022.59H12.57C12.86%2022.59%2013.13%2022.48%2013.34%2022.27C13.54%2022.07%2013.66%2021.79%2013.66%2021.5V19.22H12.27V21.2ZM16.83%207.02998C17%207.08998%2017.15%207.17998%2017.28%207.30998C17.41%207.43998%2017.5%207.58998%2017.56%207.75998H18.8V9.14998H17.61V9.73998H18.8V11.13H17.61V11.72H18.8V13.11H17.61V13.69H18.8V15.08H17.61V15.66H18.8V17.05H17.56C17.5%2017.22%2017.41%2017.37%2017.28%2017.5C17.15%2017.63%2017%2017.72%2016.83%2017.78V19.02H15.44V17.83H14.86V19.02H13.47V17.83H12.89V19.02H11.5V17.83H10.91V19.02H9.52001V17.83H8.93001V19.02H7.54001V17.78C7.37001%2017.72%207.22001%2017.62%207.09001%2017.5C6.96001%2017.38%206.87001%2017.22%206.81001%2017.05H5.57001V15.66H6.76001V15.08H5.57001V13.69H6.76001V13.11H5.57001V11.72H6.76001V11.13H5.57001V9.73998H6.76001V9.14998H5.57001V7.75998H6.81001C6.87001%207.58998%206.96001%207.43998%207.09001%207.30998C7.21001%207.17998%207.37001%207.08998%207.54001%207.02998V5.78998H8.93001V6.97998H9.52001V5.78998H10.91V6.97998H11.5V5.78998H12.89V6.97998H13.47V5.78998H14.86V6.97998H15.44V5.78998H16.83V7.02998ZM8.14001%2016.46H16.23V16.45V8.35998H8.14001V16.46Z%22%20fill%3D%22currentColor%22%2F%3E%0A%3C%2Fsvg%3E" + subnetTargetType = "com.steadybit.extension_aws.ec2-subnet" + subnetIcon = "data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMjIiIGhlaWdodD0iMjIiIHZpZXdCb3g9IjAgMCAyMiAyMiIgZmlsbD0ibm9uZSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj4KPHBhdGggZmlsbC1ydWxlPSJldmVub2RkIiBjbGlwLXJ1bGU9ImV2ZW5vZGQiIGQ9Ik05LjE3NjggMi43Njc5NkM4Ljk5MzcyIDIuNzY3OTYgOC44NDUzIDIuOTE2MzcgOC44NDUzIDMuMDk5NDVWNi43NDU4NkM4Ljg0NTMgNi45Mjg5MyA4Ljk5MzcyIDcuMDc3MzUgOS4xNzY4IDcuMDc3MzVMMTEgNy4wNzczNUwxMi44MjMyIDcuMDc3MzVDMTMuMDA2MyA3LjA3NzM1IDEzLjE1NDcgNi45Mjg5MyAxMy4xNTQ3IDYuNzQ1ODZWMy4wOTk0NUMxMy4xNTQ3IDIuOTE2MzcgMTMuMDA2MyAyLjc2Nzk2IDEyLjgyMzIgMi43Njc5Nkg5LjE3NjhaTTExLjg4NCA4Ljg0NTNIMTIuODIzMkMxMy45ODI3IDguODQ1MyAxNC45MjI3IDcuOTA1MzUgMTQuOTIyNyA2Ljc0NTg2VjMuMDk5NDVDMTQuOTIyNyAxLjkzOTk1IDEzLjk4MjcgMSAxMi44MjMyIDFIOS4xNzY4QzguMDE3MyAxIDcuMDc3MzUgMS45Mzk5NSA3LjA3NzM1IDMuMDk5NDVWNi43NDU4NkM3LjA3NzM1IDcuOTA1MzUgOC4wMTczIDguODQ1MyA5LjE3NjggOC44NDUzSDEwLjExNlYxMC43MjM4SDYuMTM4MTJDNS41ODEzMSAxMC43MjM4IDUuMDQ3MzEgMTAuOTQ0OSA0LjY1MzU5IDExLjMzODdDNC4yNTk4NiAxMS43MzI0IDQuMDM4NjcgMTIuMjY2NCA0LjAzODY3IDEyLjgyMzJWMTMuMTU0N0gzLjA5OTQ1QzEuOTM5OTYgMTMuMTU0NyAxIDE0LjA5NDcgMSAxNS4yNTQxVjE4LjkwMDZDMSAyMC4wNiAxLjkzOTk1IDIxIDMuMDk5NDUgMjFINi43NDU4NkM3LjkwNTM1IDIxIDguODQ1MyAyMC4wNiA4Ljg0NTMgMTguOTAwNlYxNS4yNTQxQzguODQ1MyAxNC4wOTQ3IDcuOTA1MzUgMTMuMTU0NyA2Ljc0NTg2IDEzLjE1NDdINS44MDY2M1YxMi44MjMyQzUuODA2NjMgMTIuNzM1MyA1Ljg0MTU2IDEyLjY1MSA1LjkwMzcyIDEyLjU4ODhDNS45NjU4OSAxMi41MjY2IDYuMDUwMiAxMi40OTE3IDYuMTM4MTIgMTIuNDkxN0gxMUgxNS44NjE5QzE1Ljk0OTggMTIuNDkxNyAxNi4wMzQxIDEyLjUyNjYgMTYuMDk2MyAxMi41ODg4QzE2LjE1ODQgMTIuNjUxIDE2LjE5MzQgMTIuNzM1MyAxNi4xOTM0IDEyLjgyMzJWMTMuMTU0N0gxNS4yNTQxQzE0LjA5NDcgMTMuMTU0NyAxMy4xNTQ3IDE0LjA5NDcgMTMuMTU0NyAxNS4yNTQxVjE4LjkwMDZDMTMuMTU0NyAyMC4wNiAxNC4wOTQ3IDIxIDE1LjI1NDEgMjFIMTguOTAwNkMyMC4wNiAyMSAyMSAyMC4wNiAyMSAxOC45MDA2VjE1LjI1NDFDMjEgMTQuMDk0NyAyMC4wNiAxMy4xNTQ3IDE4LjkwMDYgMTMuMTU0N0gxNy45NjEzVjEyLjgyMzJDMTcuOTYxMyAxMi4yNjY0IDE3Ljc0MDEgMTEuNzMyNCAxNy4zNDY0IDExLjMzODdDMTYuOTUyNyAxMC45NDQ5IDE2LjQxODcgMTAuNzIzOCAxNS44NjE5IDEwLjcyMzhIMTEuODg0VjguODQ1M1pNMy4wOTk0NSAxNC45MjI3QzIuOTE2MzcgMTQuOTIyNyAyLjc2Nzk2IDE1LjA3MTEgMi43Njc5NiAxNS4yNTQxVjE4LjkwMDZDMi43Njc5NiAxOS4wODM2IDIuOTE2MzcgMTkuMjMyIDMuMDk5NDUgMTkuMjMySDYuNzQ1ODZDNi45Mjg5MyAxOS4yMzIgNy4wNzczNSAxOS4wODM2IDcuMDc3MzUgMTguOTAwNlYxNS4yNTQxQzcuMDc3MzUgMTUuMDcxMSA2LjkyODkzIDE0LjkyMjcgNi43NDU4NiAxNC45MjI3TDQuOTIyNjUgMTQuOTIyN0wzLjA5OTQ1IDE0LjkyMjdaTTE1LjI1NDEgMTQuOTIyN0wxNy4wNzczIDE0LjkyMjdMMTguOTAwNiAxNC45MjI3QzE5LjA4MzYgMTQuOTIyNyAxOS4yMzIgMTUuMDcxMSAxOS4yMzIgMTUuMjU0MVYxOC45MDA2QzE5LjIzMiAxOS4wODM2IDE5LjA4MzYgMTkuMjMyIDE4LjkwMDYgMTkuMjMySDE1LjI1NDFDMTUuMDcxMSAxOS4yMzIgMTQuOTIyNyAxOS4wODM2IDE0LjkyMjcgMTguOTAwNlYxNS4yNTQxQzE0LjkyMjcgMTUuMDcxMSAxNS4wNzExIDE0LjkyMjcgMTUuMjU0MSAxNC45MjI3WiIgZmlsbD0iIzFEMjYzMiIvPgo8L3N2Zz4K" ) diff --git a/extec2/instance_discovery_test.go b/extec2/instance_discovery_test.go index a00af57..14c38a2 100644 --- a/extec2/instance_discovery_test.go +++ b/extec2/instance_discovery_test.go @@ -16,11 +16,11 @@ import ( "testing" ) -type instanceDiscoveryEc2UtilMock struct { +type instanceDiscoveryApiMock struct { mock.Mock } -func (m *instanceDiscoveryEc2UtilMock) DescribeInstances(ctx context.Context, params *ec2.DescribeInstancesInput, _ ...func(*ec2.Options)) (*ec2.DescribeInstancesOutput, error) { +func (m *instanceDiscoveryApiMock) DescribeInstances(ctx context.Context, params *ec2.DescribeInstancesInput, _ ...func(*ec2.Options)) (*ec2.DescribeInstancesOutput, error) { args := m.Called(ctx, params) if args.Get(0) == nil { return nil, args.Error(1) @@ -49,7 +49,7 @@ var instance = types.Instance{ func TestGetAllEc2Instances(t *testing.T) { // Given - mockedApi := new(instanceDiscoveryEc2UtilMock) + mockedApi := new(instanceDiscoveryApiMock) mockedReturnValue := ec2.DescribeInstancesOutput{ Reservations: []types.Reservation{ { @@ -99,7 +99,7 @@ func TestGetAllEc2InstancesWithFilteredAttributes(t *testing.T) { // Given // set env var to filter out all attributes starting with "aws-ec2" config.Config.DiscoveryAttributesExcludesEc2 = []string{"aws-ec2.label.*", "aws-ec2.image"} - mockedApi := new(instanceDiscoveryEc2UtilMock) + mockedApi := new(instanceDiscoveryApiMock) mockedReturnValue := ec2.DescribeInstancesOutput{ Reservations: []types.Reservation{ { @@ -150,7 +150,7 @@ func TestGetAllEc2InstancesWithFilteredAttributes(t *testing.T) { func TestNameNotSet(t *testing.T) { // Given - mockedApi := new(instanceDiscoveryEc2UtilMock) + mockedApi := new(instanceDiscoveryApiMock) mockedReturnValue := ec2.DescribeInstancesOutput{ Reservations: []types.Reservation{ { @@ -189,7 +189,7 @@ func TestNameNotSet(t *testing.T) { func TestGetAllEc2InstancesError(t *testing.T) { // Given - mockedApi := new(instanceDiscoveryEc2UtilMock) + mockedApi := new(instanceDiscoveryApiMock) mockedApi.On("DescribeInstances", mock.Anything, mock.Anything).Return(nil, errors.New("expected")) diff --git a/extec2/subnet_discovery.go b/extec2/subnet_discovery.go new file mode 100644 index 0000000..3313efd --- /dev/null +++ b/extec2/subnet_discovery.go @@ -0,0 +1,180 @@ +// SPDX-License-Identifier: MIT +// SPDX-FileCopyrightText: 2024 Steadybit GmbH + +package extec2 + +import ( + "context" + "errors" + "fmt" + "github.com/aws/aws-sdk-go-v2/aws" + awshttp "github.com/aws/aws-sdk-go-v2/aws/transport/http" + "github.com/aws/aws-sdk-go-v2/service/ec2" + "github.com/aws/aws-sdk-go-v2/service/ec2/types" + "github.com/rs/zerolog/log" + "github.com/steadybit/discovery-kit/go/discovery_kit_api" + "github.com/steadybit/discovery-kit/go/discovery_kit_commons" + "github.com/steadybit/discovery-kit/go/discovery_kit_sdk" + "github.com/steadybit/extension-aws/config" + "github.com/steadybit/extension-aws/utils" + "github.com/steadybit/extension-kit/extbuild" + "github.com/steadybit/extension-kit/extutil" + "strings" + "time" +) + +type subnetDiscovery struct{} + +var ( + _ discovery_kit_sdk.TargetDescriber = (*subnetDiscovery)(nil) + _ discovery_kit_sdk.AttributeDescriber = (*subnetDiscovery)(nil) +) + +func NewSubnetDiscovery(ctx context.Context) discovery_kit_sdk.TargetDiscovery { + discovery := &subnetDiscovery{} + return discovery_kit_sdk.NewCachedTargetDiscovery(discovery, + discovery_kit_sdk.WithRefreshTargetsNow(), + discovery_kit_sdk.WithRefreshTargetsInterval(ctx, time.Duration(config.Config.DiscoveryIntervalSubnet)*time.Second), + ) +} + +func (e *subnetDiscovery) Describe() discovery_kit_api.DiscoveryDescription { + return discovery_kit_api.DiscoveryDescription{ + Id: subnetTargetType, + Discover: discovery_kit_api.DescribingEndpointReferenceWithCallInterval{ + CallInterval: extutil.Ptr(fmt.Sprintf("%ds", config.Config.DiscoveryIntervalSubnet)), + }, + } +} + +func (e *subnetDiscovery) DescribeTarget() discovery_kit_api.TargetDescription { + return discovery_kit_api.TargetDescription{ + Id: subnetTargetType, + Label: discovery_kit_api.PluralLabel{One: "Subnet", Other: "Subnets"}, + Category: extutil.Ptr("cloud"), + Version: extbuild.GetSemverVersionStringOrUnknown(), + Icon: extutil.Ptr(subnetIcon), + + Table: discovery_kit_api.Table{ + Columns: []discovery_kit_api.Column{ + {Attribute: "aws.ec2.subnet.name"}, + {Attribute: "aws.ec2.subnet.id"}, + {Attribute: "aws.ec2.subnet.cidr"}, + {Attribute: "aws.account"}, + {Attribute: "aws.zone"}, + }, + OrderBy: []discovery_kit_api.OrderBy{ + { + Attribute: "aws.ec2.subnet.name", + Direction: "ASC", + }, + }, + }, + } +} + +func (e *subnetDiscovery) DescribeAttributes() []discovery_kit_api.AttributeDescription { + return []discovery_kit_api.AttributeDescription{ + { + Attribute: "aws.ec2.subnet.name", + Label: discovery_kit_api.PluralLabel{ + One: "Subnet name", + Other: "Subnet names", + }, + }, { + Attribute: "aws.ec2.subnet.id", + Label: discovery_kit_api.PluralLabel{ + One: "Subnet ID", + Other: "Subnet IDs", + }, + }, { + Attribute: "aws.ec2.subnet.cidr", + Label: discovery_kit_api.PluralLabel{ + One: "Subnet CIDR", + Other: "Subnet CIDRs", + }, + }, + } +} + +func (e *subnetDiscovery) DiscoverTargets(ctx context.Context) ([]discovery_kit_api.Target, error) { + return utils.ForEveryConfiguredAwsAccess(getEc2SubnetsForAccount, ctx, "ec2-subnet") +} + +func getEc2SubnetsForAccount(account *utils.AwsAccess, ctx context.Context) ([]discovery_kit_api.Target, error) { + client := ec2.NewFromConfig(account.AwsConfig) + result, err := GetAllSubnets(ctx, client, Util, account.AccountNumber, account.AwsConfig.Region) + if err != nil { + var re *awshttp.ResponseError + if errors.As(err, &re) && re.HTTPStatusCode() == 403 { + log.Error().Msgf("Not Authorized to discover ec2-subnets for account %s. If this is intended, you can disable the discovery by setting STEADYBIT_EXTENSION_DISCOVERY_DISABLED_SUBNET=true. Details: %s", account.AccountNumber, re.Error()) + return []discovery_kit_api.Target{}, nil + } + return nil, err + } + return result, nil +} + +type subnetDiscoveryEc2Util interface { + GetZoneUtil + GetVpcNameUtil +} + +func GetAllSubnets(ctx context.Context, ec2Api ec2.DescribeSubnetsAPIClient, ec2Util instanceDiscoveryEc2Util, awsAccountNumber string, awsRegion string) ([]discovery_kit_api.Target, error) { + result := make([]discovery_kit_api.Target, 0, 20) + + paginator := ec2.NewDescribeSubnetsPaginator(ec2Api, &ec2.DescribeSubnetsInput{}) + for paginator.HasMorePages() { + output, err := paginator.NextPage(ctx) + if err != nil { + return result, err + } + for _, subnet := range output.Subnets { + result = append(result, toSubnetTarget(subnet, ec2Util, awsAccountNumber, awsRegion)) + } + } + + return discovery_kit_commons.ApplyAttributeExcludes(result, config.Config.DiscoveryAttributesExcludesSubnet), nil +} + +func toSubnetTarget(subnet types.Subnet, ec2Util instanceDiscoveryEc2Util, awsAccountNumber string, awsRegion string) discovery_kit_api.Target { + var name *string + for _, tag := range subnet.Tags { + if *tag.Key == "Name" { + name = tag.Value + } + } + + label := *subnet.SubnetId + if name != nil { + label = label + " / " + *name + } + + attributes := make(map[string][]string) + attributes["aws.account"] = []string{awsAccountNumber} + attributes["aws.zone"] = []string{aws.ToString(subnet.AvailabilityZone)} + if subnet.AvailabilityZoneId != nil { + attributes["aws.zone.id"] = []string{aws.ToString(subnet.AvailabilityZoneId)} + } + if name != nil { + attributes["aws.ec2.subnet.name"] = []string{aws.ToString(name)} + } + attributes["aws.ec2.subnet.id"] = []string{aws.ToString(subnet.SubnetId)} + attributes["aws.ec2.subnet.cidr"] = []string{aws.ToString(subnet.CidrBlock)} + attributes["aws.region"] = []string{awsRegion} + attributes["aws.vpc.id"] = []string{aws.ToString(subnet.VpcId)} + attributes["aws.vpc.name"] = []string{ec2Util.GetVpcName(awsAccountNumber, awsRegion, aws.ToString(subnet.VpcId))} + for _, tag := range subnet.Tags { + if aws.ToString(tag.Key) == "Name" { + continue + } + attributes[fmt.Sprintf("aws.ec2.subnet.label.%s", strings.ToLower(aws.ToString(tag.Key)))] = []string{aws.ToString(tag.Value)} + } + + return discovery_kit_api.Target{ + Id: *subnet.SubnetId, + Label: label, + TargetType: subnetTargetType, + Attributes: attributes, + } +} diff --git a/extec2/subnet_discovery_test.go b/extec2/subnet_discovery_test.go new file mode 100644 index 0000000..3bd9db8 --- /dev/null +++ b/extec2/subnet_discovery_test.go @@ -0,0 +1,70 @@ +// SPDX-License-Identifier: MIT +// SPDX-FileCopyrightText: 2023 Steadybit GmbH + +package extec2 + +import ( + "context" + "github.com/aws/aws-sdk-go-v2/service/ec2" + "github.com/aws/aws-sdk-go-v2/service/ec2/types" + "github.com/steadybit/extension-kit/extutil" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/mock" + "testing" +) + +type subnetDiscoveryApiMock struct { + mock.Mock +} + +func (m *subnetDiscoveryApiMock) DescribeSubnets(ctx context.Context, params *ec2.DescribeSubnetsInput, _ ...func(*ec2.Options)) (*ec2.DescribeSubnetsOutput, error) { + args := m.Called(ctx, params) + if args.Get(0) == nil { + return nil, args.Error(1) + } + return args.Get(0).(*ec2.DescribeSubnetsOutput), args.Error(1) +} + +func TestGetAllSubnets(t *testing.T) { + // Given + mockedApi := new(subnetDiscoveryApiMock) + mockedReturnValue := ec2.DescribeSubnetsOutput{ + Subnets: []types.Subnet{ + { + SubnetId: extutil.Ptr("subnet-0ef9adc9fbd3b19c5"), + CidrBlock: extutil.Ptr("10.10.0.0/21"), + VpcId: extutil.Ptr("vpc-123"), + Tags: []types.Tag{ + {Key: extutil.Ptr("Name"), Value: extutil.Ptr("dev-demo-ngroup2")}, + {Key: extutil.Ptr("SpecialTag"), Value: extutil.Ptr("Great Thing")}, + }, + AvailabilityZone: extutil.Ptr("eu-central-1b"), + AvailabilityZoneId: extutil.Ptr("euc1-az3"), + }, + }, + } + mockedApi.On("DescribeSubnets", mock.Anything, mock.Anything).Return(&mockedReturnValue, nil) + + mockedZoneUtil := new(ec2UtilMock) + mockedZoneUtil.On("GetVpcName", mock.Anything, mock.Anything, mock.Anything).Return("vpc-123-name") + // When + targets, err := GetAllSubnets(context.Background(), mockedApi, mockedZoneUtil, "42", "eu-central-1") + + // Then + assert.Equal(t, nil, err) + assert.Equal(t, 1, len(targets)) + + target := targets[0] + assert.Equal(t, subnetTargetType, target.TargetType) + assert.Equal(t, "subnet-0ef9adc9fbd3b19c5 / dev-demo-ngroup2", target.Label) + assert.Equal(t, []string{"42"}, target.Attributes["aws.account"]) + assert.Equal(t, []string{"eu-central-1"}, target.Attributes["aws.region"]) + assert.Equal(t, []string{"eu-central-1b"}, target.Attributes["aws.zone"]) + assert.Equal(t, []string{"euc1-az3"}, target.Attributes["aws.zone.id"]) + assert.Equal(t, []string{"subnet-0ef9adc9fbd3b19c5"}, target.Attributes["aws.ec2.subnet.id"]) + assert.Equal(t, []string{"dev-demo-ngroup2"}, target.Attributes["aws.ec2.subnet.name"]) + assert.Equal(t, []string{"10.10.0.0/21"}, target.Attributes["aws.ec2.subnet.cidr"]) + assert.Equal(t, []string{"Great Thing"}, target.Attributes["aws.ec2.subnet.label.specialtag"]) + _, present := target.Attributes["label.name"] + assert.False(t, present) +} diff --git a/main.go b/main.go index 4216495..edb7d68 100644 --- a/main.go +++ b/main.go @@ -105,6 +105,10 @@ func registerHandlers(ctx context.Context) { action_kit_sdk.RegisterAction(extec2.NewAzBlackholeAction()) } + if !cfg.DiscoveryDisabledSubnet { + discovery_kit_sdk.Register(extec2.NewSubnetDiscovery(ctx)) + } + if !cfg.DiscoveryDisabledEc2 { discovery_kit_sdk.Register(extec2.NewEc2InstanceDiscovery(ctx)) action_kit_sdk.RegisterAction(extec2.NewEc2InstanceStateAction())