From ab91be136ef33e1f2a0c69bdef1d3244dd4daeda Mon Sep 17 00:00:00 2001 From: tombuildsstuff Date: Thu, 30 Apr 2020 10:02:27 +0200 Subject: [PATCH 1/4] adding a gitignore --- .gitignore | 3 +++ 1 file changed, 3 insertions(+) create mode 100644 .gitignore diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000000..187669ea86 --- /dev/null +++ b/.gitignore @@ -0,0 +1,3 @@ +.idea/ +.DS_Store +vendor/ From 1306c1f221c1a34fc249400f9994fd34342a5b4c Mon Sep 17 00:00:00 2001 From: tombuildsstuff Date: Thu, 30 Apr 2020 11:31:41 +0200 Subject: [PATCH 2/4] Feature: Service Packages / Registration This commit introduces the Service Registration and Service Builder The AzureRM Repository is using Service Packages to allow us to break up the codebase into smaller pieces. A key component of this is the Service Registration functionality - which allows each Service to return a selection of Meta Data relevant for that Service. The Service Builder is a new bit of functionality which can be used to compose Service Packages together, to be able to obtain a canonical list of Data Sources and Resource supported by a Terraform Provider. --- helper/schema/service_builder.go | 53 ++++++ helper/schema/service_builder_test.go | 263 ++++++++++++++++++++++++++ helper/schema/service_registration.go | 21 ++ 3 files changed, 337 insertions(+) create mode 100644 helper/schema/service_builder.go create mode 100644 helper/schema/service_builder_test.go create mode 100644 helper/schema/service_registration.go diff --git a/helper/schema/service_builder.go b/helper/schema/service_builder.go new file mode 100644 index 0000000000..d83ffb3f89 --- /dev/null +++ b/helper/schema/service_builder.go @@ -0,0 +1,53 @@ +package schema + +import "fmt" + +// ServiceBuilder is a helper type which allows accessing a canonical list of +// Data Sources and Resources supported by this Provider, or Service Package. +type ServiceBuilder struct { + services []ServiceRegistration +} + +// NewServiceBuilder returns a ServiceBuilder which allows accessing a canonical +// list of Data Sources and Resources from a list of Service Registrations +func NewServiceBuilder(services []ServiceRegistration) ServiceBuilder { + return ServiceBuilder{ + services: services, + } +} + +// Resources returns a canonical list of Data Sources supported by the +// Services registered with this Service Builder. +func (s ServiceBuilder) DataSources() (*map[string]*Resource, error) { + dataSources := make(map[string]*Resource) + + for _, service := range s.services { + for k, v := range service.SupportedDataSources() { + if existing := dataSources[k]; existing != nil { + return nil, fmt.Errorf("An existing Data Source exists for %q", k) + } + + dataSources[k] = v + } + } + + return &dataSources, nil +} + +// Resources returns a canonical list of Resources supported by the +// Services registered with this Service Builder. +func (s ServiceBuilder) Resources() (*map[string]*Resource, error) { + resources := make(map[string]*Resource) + + for _, service := range s.services { + for k, v := range service.SupportedResources() { + if existing := resources[k]; existing != nil { + return nil, fmt.Errorf("An existing Resource exists for %q", k) + } + + resources[k] = v + } + } + + return &resources, nil +} diff --git a/helper/schema/service_builder_test.go b/helper/schema/service_builder_test.go new file mode 100644 index 0000000000..5fd28ae774 --- /dev/null +++ b/helper/schema/service_builder_test.go @@ -0,0 +1,263 @@ +package schema + +import ( + "testing" +) + +func TestServiceBuilder_DataSources(t *testing.T) { + testCases := []struct { + name string + services []ServiceRegistration + expectedDataSources map[string]*Resource + shouldError bool + }{ + { + name: "none defined", + services: []ServiceRegistration{ + testServiceRegistration{ + dataSources: map[string]*Resource{}, + }, + }, + expectedDataSources: map[string]*Resource{}, + shouldError: false, + }, + { + name: "single item", + services: []ServiceRegistration{ + testServiceRegistration{ + dataSources: map[string]*Resource{ + "hello_world": {}, + }, + }, + }, + expectedDataSources: map[string]*Resource{ + "hello_world": {}, + }, + shouldError: false, + }, + { + name: "multiple items same service", + services: []ServiceRegistration{ + testServiceRegistration{ + dataSources: map[string]*Resource{ + "hello": {}, + "world": {}, + }, + }, + }, + expectedDataSources: map[string]*Resource{ + "hello": {}, + "world": {}, + }, + shouldError: false, + }, + { + name: "multiple items different service", + services: []ServiceRegistration{ + testServiceRegistration{ + dataSources: map[string]*Resource{ + "hello": {}, + "world": {}, + }, + }, + testServiceRegistration{ + dataSources: map[string]*Resource{ + "rick": {}, + "morty": {}, + }, + }, + }, + expectedDataSources: map[string]*Resource{ + "hello": {}, + "world": {}, + "rick": {}, + "morty": {}, + }, + shouldError: false, + }, + { + name: "conflicting items different services", + services: []ServiceRegistration{ + testServiceRegistration{ + dataSources: map[string]*Resource{ + "hello": {}, + }, + }, + testServiceRegistration{ + dataSources: map[string]*Resource{ + "hello": {}, + }, + }, + }, + expectedDataSources: map[string]*Resource{}, + shouldError: true, + }, + } + + for _, testCase := range testCases { + t.Logf("Testing %q..", testCase.name) + builder := NewServiceBuilder(testCase.services) + dataSources, err := builder.DataSources() + if testCase.shouldError { + if err != nil { + continue + } + + t.Fatalf("Expected an error but didn't get one!") + } + if !testCase.shouldError { + if err == nil { + continue + } + + t.Fatalf("Expected no error but got: %+v", err) + } + + if len(*dataSources) != len(testCase.expectedDataSources) { + t.Fatalf("Expected %d Data Sources but got %d", len(testCase.expectedDataSources), len(*dataSources)) + } + for k := range *dataSources { + if _, ok := testCase.expectedDataSources[k]; !ok { + t.Fatalf("Expected %q to be present but it wasn't!", k) + } + } + } +} + +func TestServiceBuilder_Resources(t *testing.T) { + testCases := []struct { + name string + services []ServiceRegistration + expectedResources map[string]*Resource + shouldError bool + }{ + { + name: "none defined", + services: []ServiceRegistration{ + testServiceRegistration{ + resources: map[string]*Resource{}, + }, + }, + expectedResources: map[string]*Resource{}, + shouldError: false, + }, + { + name: "single item", + services: []ServiceRegistration{ + testServiceRegistration{ + resources: map[string]*Resource{ + "hello_world": {}, + }, + }, + }, + expectedResources: map[string]*Resource{ + "hello_world": {}, + }, + shouldError: false, + }, + { + name: "multiple items same service", + services: []ServiceRegistration{ + testServiceRegistration{ + resources: map[string]*Resource{ + "hello": {}, + "world": {}, + }, + }, + }, + expectedResources: map[string]*Resource{ + "hello": {}, + "world": {}, + }, + shouldError: false, + }, + { + name: "multiple items different service", + services: []ServiceRegistration{ + testServiceRegistration{ + dataSources: map[string]*Resource{ + "hello": {}, + "world": {}, + }, + }, + testServiceRegistration{ + dataSources: map[string]*Resource{ + "rick": {}, + "morty": {}, + }, + }, + }, + expectedResources: map[string]*Resource{ + "hello": {}, + "world": {}, + "rick": {}, + "morty": {}, + }, + shouldError: false, + }, + { + name: "conflicting items different services", + services: []ServiceRegistration{ + testServiceRegistration{ + resources: map[string]*Resource{ + "hello": {}, + }, + }, + testServiceRegistration{ + resources: map[string]*Resource{ + "hello": {}, + }, + }, + }, + expectedResources: map[string]*Resource{}, + shouldError: true, + }, + } + + for _, testCase := range testCases { + t.Logf("Testing %q..", testCase.name) + builder := NewServiceBuilder(testCase.services) + resources, err := builder.Resources() + if testCase.shouldError { + if err != nil { + continue + } + + t.Fatalf("Expected an error but didn't get one!") + } + if !testCase.shouldError { + if err == nil { + continue + } + + t.Fatalf("Expected no error but got: %+v", err) + } + + if len(*resources) != len(testCase.expectedResources) { + t.Fatalf("Expected %d Resources but got %d", len(testCase.expectedResources), len(*resources)) + } + for k := range *resources { + if _, ok := testCase.expectedResources[k]; !ok { + t.Fatalf("Expected %q to be present but it wasn't!", k) + } + } + } +} + +type testServiceRegistration struct { + dataSources map[string]*Resource + resources map[string]*Resource +} + +func (r testServiceRegistration) Name() string { + return "test" +} +func (r testServiceRegistration) WebsiteCategories() []string { + return []string{} +} +func (r testServiceRegistration) SupportedDataSources() map[string]*Resource { + return r.dataSources +} +func (r testServiceRegistration) SupportedResources() map[string]*Resource { + return r.resources +} diff --git a/helper/schema/service_registration.go b/helper/schema/service_registration.go new file mode 100644 index 0000000000..77d7b88b5b --- /dev/null +++ b/helper/schema/service_registration.go @@ -0,0 +1,21 @@ +package schema + +// ServiceRegistration returns the MetaData for a given Service, which +// is a collection of related Data Sources and Resources. +// +// Services allow larger providers to become more maintainable by +// grouping relevant functionality together - which allows helper methods +// to be better scoped and rebuilding only the components that have changed. +type ServiceRegistration interface { + // Name is the name of this Service + Name() string + + // WebsiteCategories returns a list of categories used in the Website + WebsiteCategories() []string + + // SupportedDataSources returns the supported Data Sources supported by this Service + SupportedDataSources() map[string]*Resource + + // SupportedResources returns the supported Resources supported by this Service + SupportedResources() map[string]*Resource +} From f81e5539b9a07f899995df5e9a900fa0d9e67d20 Mon Sep 17 00:00:00 2001 From: tombuildsstuff Date: Thu, 30 Apr 2020 19:47:34 +0200 Subject: [PATCH 3/4] schema/service_builder: using the right method name for the docs --- helper/schema/service_builder.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/helper/schema/service_builder.go b/helper/schema/service_builder.go index d83ffb3f89..00e3f71c05 100644 --- a/helper/schema/service_builder.go +++ b/helper/schema/service_builder.go @@ -16,7 +16,7 @@ func NewServiceBuilder(services []ServiceRegistration) ServiceBuilder { } } -// Resources returns a canonical list of Data Sources supported by the +// DataSources returns a canonical list of Data Sources supported by the // Services registered with this Service Builder. func (s ServiceBuilder) DataSources() (*map[string]*Resource, error) { dataSources := make(map[string]*Resource) From 1b381607fec290bc8387a47968b53bb16de87cd3 Mon Sep 17 00:00:00 2001 From: tombuildsstuff Date: Thu, 30 Apr 2020 19:56:12 +0200 Subject: [PATCH 4/4] schema/service_builder: providing more information in the errors --- helper/schema/service_builder.go | 10 ++++++++-- helper/schema/service_builder_test.go | 17 ++++++++++++++++- 2 files changed, 24 insertions(+), 3 deletions(-) diff --git a/helper/schema/service_builder.go b/helper/schema/service_builder.go index 00e3f71c05..3a4d3483ac 100644 --- a/helper/schema/service_builder.go +++ b/helper/schema/service_builder.go @@ -20,14 +20,17 @@ func NewServiceBuilder(services []ServiceRegistration) ServiceBuilder { // Services registered with this Service Builder. func (s ServiceBuilder) DataSources() (*map[string]*Resource, error) { dataSources := make(map[string]*Resource) + dataSourceRegistrationSource := make(map[string]string) for _, service := range s.services { for k, v := range service.SupportedDataSources() { if existing := dataSources[k]; existing != nil { - return nil, fmt.Errorf("An existing Data Source exists for %q", k) + existingRegName := dataSourceRegistrationSource[k] + return nil, fmt.Errorf("Both %q and %q register Data Source %q!", existingRegName, service.Name(), k) } dataSources[k] = v + dataSourceRegistrationSource[k] = service.Name() } } @@ -38,14 +41,17 @@ func (s ServiceBuilder) DataSources() (*map[string]*Resource, error) { // Services registered with this Service Builder. func (s ServiceBuilder) Resources() (*map[string]*Resource, error) { resources := make(map[string]*Resource) + resourceRegistrationSource := make(map[string]string) for _, service := range s.services { for k, v := range service.SupportedResources() { if existing := resources[k]; existing != nil { - return nil, fmt.Errorf("An existing Resource exists for %q", k) + existingRegName := resourceRegistrationSource[k] + return nil, fmt.Errorf("Both %q and %q register Resource %q!", existingRegName, service.Name(), k) } resources[k] = v + resourceRegistrationSource[k] = service.Name() } } diff --git a/helper/schema/service_builder_test.go b/helper/schema/service_builder_test.go index 5fd28ae774..fa975e5ace 100644 --- a/helper/schema/service_builder_test.go +++ b/helper/schema/service_builder_test.go @@ -15,6 +15,7 @@ func TestServiceBuilder_DataSources(t *testing.T) { name: "none defined", services: []ServiceRegistration{ testServiceRegistration{ + name: "first", dataSources: map[string]*Resource{}, }, }, @@ -25,6 +26,7 @@ func TestServiceBuilder_DataSources(t *testing.T) { name: "single item", services: []ServiceRegistration{ testServiceRegistration{ + name: "first", dataSources: map[string]*Resource{ "hello_world": {}, }, @@ -39,6 +41,7 @@ func TestServiceBuilder_DataSources(t *testing.T) { name: "multiple items same service", services: []ServiceRegistration{ testServiceRegistration{ + name: "first", dataSources: map[string]*Resource{ "hello": {}, "world": {}, @@ -55,12 +58,14 @@ func TestServiceBuilder_DataSources(t *testing.T) { name: "multiple items different service", services: []ServiceRegistration{ testServiceRegistration{ + name: "first", dataSources: map[string]*Resource{ "hello": {}, "world": {}, }, }, testServiceRegistration{ + name: "second", dataSources: map[string]*Resource{ "rick": {}, "morty": {}, @@ -79,11 +84,13 @@ func TestServiceBuilder_DataSources(t *testing.T) { name: "conflicting items different services", services: []ServiceRegistration{ testServiceRegistration{ + name: "first", dataSources: map[string]*Resource{ "hello": {}, }, }, testServiceRegistration{ + name: "second", dataSources: map[string]*Resource{ "hello": {}, }, @@ -135,6 +142,7 @@ func TestServiceBuilder_Resources(t *testing.T) { name: "none defined", services: []ServiceRegistration{ testServiceRegistration{ + name: "first", resources: map[string]*Resource{}, }, }, @@ -145,6 +153,7 @@ func TestServiceBuilder_Resources(t *testing.T) { name: "single item", services: []ServiceRegistration{ testServiceRegistration{ + name: "first", resources: map[string]*Resource{ "hello_world": {}, }, @@ -159,6 +168,7 @@ func TestServiceBuilder_Resources(t *testing.T) { name: "multiple items same service", services: []ServiceRegistration{ testServiceRegistration{ + name: "first", resources: map[string]*Resource{ "hello": {}, "world": {}, @@ -175,12 +185,14 @@ func TestServiceBuilder_Resources(t *testing.T) { name: "multiple items different service", services: []ServiceRegistration{ testServiceRegistration{ + name: "first", dataSources: map[string]*Resource{ "hello": {}, "world": {}, }, }, testServiceRegistration{ + name: "second", dataSources: map[string]*Resource{ "rick": {}, "morty": {}, @@ -199,11 +211,13 @@ func TestServiceBuilder_Resources(t *testing.T) { name: "conflicting items different services", services: []ServiceRegistration{ testServiceRegistration{ + name: "first", resources: map[string]*Resource{ "hello": {}, }, }, testServiceRegistration{ + name: "second", resources: map[string]*Resource{ "hello": {}, }, @@ -245,12 +259,13 @@ func TestServiceBuilder_Resources(t *testing.T) { } type testServiceRegistration struct { + name string dataSources map[string]*Resource resources map[string]*Resource } func (r testServiceRegistration) Name() string { - return "test" + return r.name } func (r testServiceRegistration) WebsiteCategories() []string { return []string{}