From 855d071114ebf7f1349f4cedee6eaff476fced76 Mon Sep 17 00:00:00 2001 From: Kyler Middleton Date: Wed, 17 Jan 2024 16:21:28 -0600 Subject: [PATCH 1/7] New data source ip_groups --- .../services/network/ip_groups_data_source.go | 106 ++++++++++++++++++ .../network/ip_groups_data_source_test.go | 95 ++++++++++++++++ internal/services/network/registration.go | 1 + website/docs/d/ip_groups.html.markdown | 46 ++++++++ 4 files changed, 248 insertions(+) create mode 100644 internal/services/network/ip_groups_data_source.go create mode 100644 internal/services/network/ip_groups_data_source_test.go create mode 100644 website/docs/d/ip_groups.html.markdown diff --git a/internal/services/network/ip_groups_data_source.go b/internal/services/network/ip_groups_data_source.go new file mode 100644 index 000000000000..09ef836bf023 --- /dev/null +++ b/internal/services/network/ip_groups_data_source.go @@ -0,0 +1,106 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package network + +import ( + "fmt" + "slices" + "strings" + "time" + + "github.com/hashicorp/go-azure-helpers/resourcemanager/commonschema" + "github.com/hashicorp/terraform-provider-azurerm/internal/clients" + "github.com/hashicorp/terraform-provider-azurerm/internal/tags" + "github.com/hashicorp/terraform-provider-azurerm/internal/tf/pluginsdk" + "github.com/hashicorp/terraform-provider-azurerm/internal/timeouts" +) + +func dataSourceIpGroups() *pluginsdk.Resource { + return &pluginsdk.Resource{ + Read: dataSourceIpGroupsRead, + + Timeouts: &pluginsdk.ResourceTimeout{ + Read: pluginsdk.DefaultTimeout(10 * time.Minute), + }, + + Schema: map[string]*pluginsdk.Schema{ + "name": { + Type: pluginsdk.TypeString, + Required: true, + }, + + "resource_group_name": commonschema.ResourceGroupNameForDataSource(), + + "location": commonschema.LocationComputed(), + + "ids": { + Type: pluginsdk.TypeSet, + Computed: true, + Elem: &pluginsdk.Schema{Type: pluginsdk.TypeString}, + Set: pluginsdk.HashString, + }, + + "names": { + Type: pluginsdk.TypeSet, + Computed: true, + Elem: &pluginsdk.Schema{Type: pluginsdk.TypeString}, + Set: pluginsdk.HashString, + }, + + "tags": tags.SchemaDataSource(), + }, + } +} + +// Find IDs and names of multiple IP Groups, filtered by name substring +func dataSourceIpGroupsRead(d *pluginsdk.ResourceData, meta interface{}) error { + + // Establish a client to handle i/o operations against the API + client := meta.(*clients.Client).Network.IPGroupsClient + + // Create a context for the request and defer cancellation + ctx, cancel := timeouts.ForRead(meta.(*clients.Client).StopContext, d) + defer cancel() + + // Make the request to the API to download all IP groups in the resource group + allGroups, err := client.ListByResourceGroup(ctx, d.Get("resource_group_name").(string)) + if err != nil { + return fmt.Errorf("error listing IP groups: %+v", err) + } + + // Establish lists of strings to append to, set equal to empty set to start + // If no IP groups are found, an empty set will be returned + names := []string{} + ids := []string{} + + // Filter IDs list by substring + for _, ipGroup := range allGroups.Values() { + if ipGroup.Name != nil && strings.Contains(*ipGroup.Name, d.Get("name").(string)) { + names = append(names, *ipGroup.Name) + ids = append(ids, *ipGroup.ID) + } + } + + // Sort lists of strings alphabetically + slices.Sort(names) + slices.Sort(ids) + + // Set names + err = d.Set("names", names) + if err != nil { + return fmt.Errorf("error setting names: %+v", err) + } + //fmt.Println("Names set as: ", d.Get("names").(*schema.Set).List()) + + // Set IDs + err = d.Set("ids", ids) + if err != nil { + return fmt.Errorf("error setting ids: %+v", err) + } + //fmt.Println("IDs set as: ", d.Get("ids").(*schema.Set).List()) + + // Return nil error + return nil + +} diff --git a/internal/services/network/ip_groups_data_source_test.go b/internal/services/network/ip_groups_data_source_test.go new file mode 100644 index 000000000000..84791459ffde --- /dev/null +++ b/internal/services/network/ip_groups_data_source_test.go @@ -0,0 +1,95 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package network_test + +import ( + "fmt" + "testing" + + "github.com/hashicorp/terraform-provider-azurerm/internal/acceptance" + "github.com/hashicorp/terraform-provider-azurerm/internal/acceptance/check" +) + +type IPGroupsDataSource struct{} + +func TestAccDataSourceIPGroups_noResults(t *testing.T) { + data := acceptance.BuildTestData(t, "data.azurerm_ip_groups", "test") + r := IPGroupsDataSource{} + + data.DataSourceTest(t, []acceptance.TestStep{ + { + Config: r.noResults(data), + Check: acceptance.ComposeTestCheckFunc( + check.That(data.ResourceName).Key("ids.#").HasValue("0"), + check.That(data.ResourceName).Key("names.#").HasValue("0"), + ), + }, + }) +} + +func TestAccDataSourceIPGroups_basic(t *testing.T) { + data := acceptance.BuildTestData(t, "data.azurerm_ip_groups", "test") + r := IPGroupsDataSource{} + + data.DataSourceTest(t, []acceptance.TestStep{ + { + Config: r.basic(data), + Check: acceptance.ComposeTestCheckFunc( + check.That(data.ResourceName).Key("ids.#").HasValue("1"), + check.That(data.ResourceName).Key("names.#").HasValue("1"), + ), + }, + }) +} + +func TestAccDataSourceIPGroups_complete(t *testing.T) { + data := acceptance.BuildTestData(t, "data.azurerm_ip_groups", "test") + r := IPGroupsDataSource{} + + data.DataSourceTest(t, []acceptance.TestStep{ + { + Config: r.complete(data), + Check: acceptance.ComposeTestCheckFunc( + check.That(data.ResourceName).Key("ids.#").HasValue("2"), + check.That(data.ResourceName).Key("names.#").HasValue("2"), + ), + }, + }) +} + +// Find IP group which doesn't exist +func (IPGroupsDataSource) noResults(data acceptance.TestData) string { + return fmt.Sprintf(` +%s + +data "azurerm_ip_groups" "test" { + name = "doesNotExist" + resource_group_name = azurerm_resource_group.test.name +} +`, IPGroupResource{}.basic(data)) +} + +// Find single IP group +func (IPGroupsDataSource) basic(data acceptance.TestData) string { + return fmt.Sprintf(` +%s + +data "azurerm_ip_groups" "test" { + name = "acceptanceTestIpGroup1" + resource_group_name = azurerm_resource_group.test.name +} +`, IPGroupResource{}.basic(data)) +} + +// Find multiple IP Groups, filtered by name substring +func (IPGroupsDataSource) complete(data acceptance.TestData) string { + return fmt.Sprintf(` +%s + +data "azurerm_ip_groups" "test" { + name = "acceptanceTestIpGroup" + resource_group_name = azurerm_resource_group.test.name +} +`, IPGroupResource{}.complete(data)) +} diff --git a/internal/services/network/registration.go b/internal/services/network/registration.go index f2826be05b5b..15c830e374a8 100644 --- a/internal/services/network/registration.go +++ b/internal/services/network/registration.go @@ -66,6 +66,7 @@ func (r Registration) SupportedDataSources() map[string]*pluginsdk.Resource { "azurerm_bastion_host": dataSourceBastionHost(), "azurerm_express_route_circuit": dataSourceExpressRouteCircuit(), "azurerm_ip_group": dataSourceIpGroup(), + "azurerm_ip_groups": dataSourceIpGroups(), "azurerm_nat_gateway": dataSourceNatGateway(), "azurerm_network_ddos_protection_plan": dataSourceNetworkDDoSProtectionPlan(), "azurerm_network_interface": dataSourceNetworkInterface(), diff --git a/website/docs/d/ip_groups.html.markdown b/website/docs/d/ip_groups.html.markdown new file mode 100644 index 000000000000..38df831cde28 --- /dev/null +++ b/website/docs/d/ip_groups.html.markdown @@ -0,0 +1,46 @@ +--- +subcategory: "Network" +layout: "azurerm" +page_title: "Azure Resource Manager: Data Source: azurerm_ip_groups" +description: |- + Gets information about existing IP Groups. +--- + +# Data Source: azurerm_ip_groups + +Use this data source to access information about existing IP Groups. + +## Example Usage + +```hcl +data "azurerm_ip_groups" "example" { + name = "existing" # Can be a substring to match multiple IP Groups + resource_group_name = "existing" +} + +output "id" { + value = data.azurerm_ip_groups.example.id +} +``` + +## Arguments Reference + +The following arguments are supported: + +* `name` - (Required) A substring to match some number of IP Groups. + +* `resource_group_name` - (Required) The name of the Resource Group where the IP Groups exist. + +## Attributes Reference + +In addition to the Arguments listed above - the following Attributes are exported: + +* `ids` - A list of IP Group IDs. + +* `names` - A list of IP Group Names. + +## Timeouts + +The `timeouts` block allows you to specify [timeouts](https://www.terraform.io/language/resources/syntax#operation-timeouts) for certain actions: + +* `read` - (Defaults to 10 minutes) Used when retrieving the IP Groups. \ No newline at end of file From bfdc484a6f1087371a798367828cf6d7e8722010 Mon Sep 17 00:00:00 2001 From: Kyler Middleton Date: Wed, 17 Jan 2024 16:35:08 -0600 Subject: [PATCH 2/7] Fix website linting --- internal/services/network/ip_groups_data_source.go | 2 -- website/docs/d/ip_groups.html.markdown | 2 +- 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/internal/services/network/ip_groups_data_source.go b/internal/services/network/ip_groups_data_source.go index 09ef836bf023..099c2595e24b 100644 --- a/internal/services/network/ip_groups_data_source.go +++ b/internal/services/network/ip_groups_data_source.go @@ -91,14 +91,12 @@ func dataSourceIpGroupsRead(d *pluginsdk.ResourceData, meta interface{}) error { if err != nil { return fmt.Errorf("error setting names: %+v", err) } - //fmt.Println("Names set as: ", d.Get("names").(*schema.Set).List()) // Set IDs err = d.Set("ids", ids) if err != nil { return fmt.Errorf("error setting ids: %+v", err) } - //fmt.Println("IDs set as: ", d.Get("ids").(*schema.Set).List()) // Return nil error return nil diff --git a/website/docs/d/ip_groups.html.markdown b/website/docs/d/ip_groups.html.markdown index 38df831cde28..2ffc9c63a8f7 100644 --- a/website/docs/d/ip_groups.html.markdown +++ b/website/docs/d/ip_groups.html.markdown @@ -14,7 +14,7 @@ Use this data source to access information about existing IP Groups. ```hcl data "azurerm_ip_groups" "example" { - name = "existing" # Can be a substring to match multiple IP Groups + name = "existing" resource_group_name = "existing" } From 8384469e1ce9476b5f81c923dc3d616b24ae6b94 Mon Sep 17 00:00:00 2001 From: Kyler Middleton Date: Wed, 17 Jan 2024 16:37:44 -0600 Subject: [PATCH 3/7] Fix website linting, part deux --- website/docs/d/ip_groups.html.markdown | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/website/docs/d/ip_groups.html.markdown b/website/docs/d/ip_groups.html.markdown index 2ffc9c63a8f7..ba9388ed3594 100644 --- a/website/docs/d/ip_groups.html.markdown +++ b/website/docs/d/ip_groups.html.markdown @@ -14,7 +14,7 @@ Use this data source to access information about existing IP Groups. ```hcl data "azurerm_ip_groups" "example" { - name = "existing" + name = "existing" resource_group_name = "existing" } From 3ca47ecb8431abb4a9492b30cff45fd8732b86ca Mon Sep 17 00:00:00 2001 From: Kyler Middleton Date: Thu, 18 Jan 2024 14:23:40 -0600 Subject: [PATCH 4/7] Add IP, update tests --- internal/services/network/ip_groups_data_source.go | 13 ++++++++++++- .../services/network/ip_groups_data_source_test.go | 12 ++++++------ 2 files changed, 18 insertions(+), 7 deletions(-) diff --git a/internal/services/network/ip_groups_data_source.go b/internal/services/network/ip_groups_data_source.go index 099c2595e24b..8c648e3a2cdf 100644 --- a/internal/services/network/ip_groups_data_source.go +++ b/internal/services/network/ip_groups_data_source.go @@ -9,6 +9,7 @@ import ( "strings" "time" + "github.com/hashicorp/go-azure-helpers/resourcemanager/commonids" "github.com/hashicorp/go-azure-helpers/resourcemanager/commonschema" "github.com/hashicorp/terraform-provider-azurerm/internal/clients" "github.com/hashicorp/terraform-provider-azurerm/internal/tags" @@ -63,8 +64,11 @@ func dataSourceIpGroupsRead(d *pluginsdk.ResourceData, meta interface{}) error { ctx, cancel := timeouts.ForRead(meta.(*clients.Client).StopContext, d) defer cancel() + // Get resource group name from data source + resourceGroupName := d.Get("resource_group_name").(string) + // Make the request to the API to download all IP groups in the resource group - allGroups, err := client.ListByResourceGroup(ctx, d.Get("resource_group_name").(string)) + allGroups, err := client.ListByResourceGroup(ctx, resourceGroupName) if err != nil { return fmt.Errorf("error listing IP groups: %+v", err) } @@ -86,6 +90,13 @@ func dataSourceIpGroupsRead(d *pluginsdk.ResourceData, meta interface{}) error { slices.Sort(names) slices.Sort(ids) + // Set resource ID, required for Terraform state + // Since this is a multi-resource data source, we need to create a unique ID + // Using the internal ID of the resource + subscriptionId := meta.(*clients.Client).Account.SubscriptionId + id := commonids.NewResourceGroupID(subscriptionId, resourceGroupName) + d.SetId(id.ID()) + // Set names err = d.Set("names", names) if err != nil { diff --git a/internal/services/network/ip_groups_data_source_test.go b/internal/services/network/ip_groups_data_source_test.go index 84791459ffde..31327bd1eef2 100644 --- a/internal/services/network/ip_groups_data_source_test.go +++ b/internal/services/network/ip_groups_data_source_test.go @@ -28,13 +28,13 @@ func TestAccDataSourceIPGroups_noResults(t *testing.T) { }) } -func TestAccDataSourceIPGroups_basic(t *testing.T) { +func TestAccDataSourceIPGroups_single(t *testing.T) { data := acceptance.BuildTestData(t, "data.azurerm_ip_groups", "test") r := IPGroupsDataSource{} data.DataSourceTest(t, []acceptance.TestStep{ { - Config: r.basic(data), + Config: r.single(data), Check: acceptance.ComposeTestCheckFunc( check.That(data.ResourceName).Key("ids.#").HasValue("1"), check.That(data.ResourceName).Key("names.#").HasValue("1"), @@ -43,13 +43,13 @@ func TestAccDataSourceIPGroups_basic(t *testing.T) { }) } -func TestAccDataSourceIPGroups_complete(t *testing.T) { +func TestAccDataSourceIPGroups_multiple(t *testing.T) { data := acceptance.BuildTestData(t, "data.azurerm_ip_groups", "test") r := IPGroupsDataSource{} data.DataSourceTest(t, []acceptance.TestStep{ { - Config: r.complete(data), + Config: r.multiple(data), Check: acceptance.ComposeTestCheckFunc( check.That(data.ResourceName).Key("ids.#").HasValue("2"), check.That(data.ResourceName).Key("names.#").HasValue("2"), @@ -71,7 +71,7 @@ data "azurerm_ip_groups" "test" { } // Find single IP group -func (IPGroupsDataSource) basic(data acceptance.TestData) string { +func (IPGroupsDataSource) single(data acceptance.TestData) string { return fmt.Sprintf(` %s @@ -83,7 +83,7 @@ data "azurerm_ip_groups" "test" { } // Find multiple IP Groups, filtered by name substring -func (IPGroupsDataSource) complete(data acceptance.TestData) string { +func (IPGroupsDataSource) multiple(data acceptance.TestData) string { return fmt.Sprintf(` %s From 409428d595a6c0668612225b5f8c4c2ce858271c Mon Sep 17 00:00:00 2001 From: Kyler Middleton Date: Thu, 18 Jan 2024 17:52:52 -0600 Subject: [PATCH 5/7] Fix names, add more IP groups for test case --- .../network/ip_group_resource_test.go | 27 ++++++++++++++++++- .../services/network/ip_groups_data_source.go | 2 +- .../network/ip_groups_data_source_test.go | 11 ++++---- 3 files changed, 33 insertions(+), 7 deletions(-) diff --git a/internal/services/network/ip_group_resource_test.go b/internal/services/network/ip_group_resource_test.go index 9c66a203d369..93ef90d31384 100644 --- a/internal/services/network/ip_group_resource_test.go +++ b/internal/services/network/ip_group_resource_test.go @@ -208,6 +208,32 @@ resource "azurerm_ip_group" "test" { cost_center = "MSFT" } } + +resource "azurerm_ip_group" "test2" { + name = "acceptanceTestIpGroup2" + location = azurerm_resource_group.test.location + resource_group_name = azurerm_resource_group.test.name + + cidrs = ["192.168.0.1", "172.16.240.0/20", "10.48.0.0/12"] + + tags = { + environment = "Production" + cost_center = "MSFT" + } +} + +resource "azurerm_ip_group" "test3" { + name = "acceptanceTestIpGroup3" + location = azurerm_resource_group.test.location + resource_group_name = azurerm_resource_group.test.name + + cidrs = ["192.168.0.1", "172.16.240.0/20", "10.48.0.0/12"] + + tags = { + environment = "Production" + cost_center = "MSFT" + } +} `, data.RandomInteger, data.Locations.Primary) } @@ -312,7 +338,6 @@ resource "azurerm_firewall_policy_rule_collection_group" "test" { } } - resource "azurerm_virtual_network" "test" { name = "testvnet" address_space = ["10.0.0.0/16"] diff --git a/internal/services/network/ip_groups_data_source.go b/internal/services/network/ip_groups_data_source.go index 8c648e3a2cdf..7acbb2a2ba3e 100644 --- a/internal/services/network/ip_groups_data_source.go +++ b/internal/services/network/ip_groups_data_source.go @@ -92,7 +92,7 @@ func dataSourceIpGroupsRead(d *pluginsdk.ResourceData, meta interface{}) error { // Set resource ID, required for Terraform state // Since this is a multi-resource data source, we need to create a unique ID - // Using the internal ID of the resource + // Using the ID of the resource group subscriptionId := meta.(*clients.Client).Account.SubscriptionId id := commonids.NewResourceGroupID(subscriptionId, resourceGroupName) d.SetId(id.ID()) diff --git a/internal/services/network/ip_groups_data_source_test.go b/internal/services/network/ip_groups_data_source_test.go index 31327bd1eef2..c8f52735b2a3 100644 --- a/internal/services/network/ip_groups_data_source_test.go +++ b/internal/services/network/ip_groups_data_source_test.go @@ -13,13 +13,14 @@ import ( type IPGroupsDataSource struct{} -func TestAccDataSourceIPGroups_noResults(t *testing.T) { +// Basic == No results, returns empty list +func TestAccDataSourceIPGroups_basic(t *testing.T) { data := acceptance.BuildTestData(t, "data.azurerm_ip_groups", "test") r := IPGroupsDataSource{} data.DataSourceTest(t, []acceptance.TestStep{ { - Config: r.noResults(data), + Config: r.basic(data), Check: acceptance.ComposeTestCheckFunc( check.That(data.ResourceName).Key("ids.#").HasValue("0"), check.That(data.ResourceName).Key("names.#").HasValue("0"), @@ -51,15 +52,15 @@ func TestAccDataSourceIPGroups_multiple(t *testing.T) { { Config: r.multiple(data), Check: acceptance.ComposeTestCheckFunc( - check.That(data.ResourceName).Key("ids.#").HasValue("2"), - check.That(data.ResourceName).Key("names.#").HasValue("2"), + check.That(data.ResourceName).Key("ids.#").HasValue("3"), + check.That(data.ResourceName).Key("names.#").HasValue("3"), ), }, }) } // Find IP group which doesn't exist -func (IPGroupsDataSource) noResults(data acceptance.TestData) string { +func (IPGroupsDataSource) basic(data acceptance.TestData) string { return fmt.Sprintf(` %s From 61dc9d269eb2849ce1f9e034d0714162c3e4697f Mon Sep 17 00:00:00 2001 From: Kyler Middleton Date: Fri, 19 Jan 2024 12:23:28 -0600 Subject: [PATCH 6/7] Fix tests --- .../network/ip_groups_data_source_test.go | 18 ++++++++++++++---- 1 file changed, 14 insertions(+), 4 deletions(-) diff --git a/internal/services/network/ip_groups_data_source_test.go b/internal/services/network/ip_groups_data_source_test.go index c8f52735b2a3..5e74c5a909e9 100644 --- a/internal/services/network/ip_groups_data_source_test.go +++ b/internal/services/network/ip_groups_data_source_test.go @@ -13,14 +13,13 @@ import ( type IPGroupsDataSource struct{} -// Basic == No results, returns empty list -func TestAccDataSourceIPGroups_basic(t *testing.T) { +func TestAccDataSourceIPGroups_noResults(t *testing.T) { data := acceptance.BuildTestData(t, "data.azurerm_ip_groups", "test") r := IPGroupsDataSource{} data.DataSourceTest(t, []acceptance.TestStep{ { - Config: r.basic(data), + Config: r.noResults(data), Check: acceptance.ComposeTestCheckFunc( check.That(data.ResourceName).Key("ids.#").HasValue("0"), check.That(data.ResourceName).Key("names.#").HasValue("0"), @@ -60,13 +59,16 @@ func TestAccDataSourceIPGroups_multiple(t *testing.T) { } // Find IP group which doesn't exist -func (IPGroupsDataSource) basic(data acceptance.TestData) string { +func (IPGroupsDataSource) noResults(data acceptance.TestData) string { return fmt.Sprintf(` %s data "azurerm_ip_groups" "test" { name = "doesNotExist" resource_group_name = azurerm_resource_group.test.name + depends_on = [ + azurerm_ip_group.test, + ] } `, IPGroupResource{}.basic(data)) } @@ -79,6 +81,9 @@ func (IPGroupsDataSource) single(data acceptance.TestData) string { data "azurerm_ip_groups" "test" { name = "acceptanceTestIpGroup1" resource_group_name = azurerm_resource_group.test.name + depends_on = [ + azurerm_ip_group.test, + ] } `, IPGroupResource{}.basic(data)) } @@ -91,6 +96,11 @@ func (IPGroupsDataSource) multiple(data acceptance.TestData) string { data "azurerm_ip_groups" "test" { name = "acceptanceTestIpGroup" resource_group_name = azurerm_resource_group.test.name + depends_on = [ + azurerm_ip_group.test, + azurerm_ip_group.test2, + azurerm_ip_group.test3, + ] } `, IPGroupResource{}.complete(data)) } From 819b538e21992fca327235a06eb11f9da325c8f8 Mon Sep 17 00:00:00 2001 From: Kyler Middleton Date: Mon, 22 Jan 2024 10:32:05 -0600 Subject: [PATCH 7/7] Update to List type so hash codes match (thanks Stephy!) Co-authored-by: stephybun --- internal/services/network/ip_groups_data_source.go | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/internal/services/network/ip_groups_data_source.go b/internal/services/network/ip_groups_data_source.go index 7acbb2a2ba3e..fa57da41b207 100644 --- a/internal/services/network/ip_groups_data_source.go +++ b/internal/services/network/ip_groups_data_source.go @@ -36,17 +36,15 @@ func dataSourceIpGroups() *pluginsdk.Resource { "location": commonschema.LocationComputed(), "ids": { - Type: pluginsdk.TypeSet, + Type: pluginsdk.TypeList, Computed: true, Elem: &pluginsdk.Schema{Type: pluginsdk.TypeString}, - Set: pluginsdk.HashString, }, "names": { - Type: pluginsdk.TypeSet, + Type: pluginsdk.TypeList, Computed: true, Elem: &pluginsdk.Schema{Type: pluginsdk.TypeString}, - Set: pluginsdk.HashString, }, "tags": tags.SchemaDataSource(),