diff --git a/.gitignore b/.gitignore index 8fd03bda00d5..2d5dc6e28609 100644 --- a/.gitignore +++ b/.gitignore @@ -31,7 +31,6 @@ ehthumbs.db Thumbs.db -final_api.yaml -# Ansible PR Script -tools/ansible-pr/templates/* -!tools/ansible-pr/templates/.keep +.vscode/ + +.bundle/ diff --git a/.gitmodules b/.gitmodules index c9f42990286a..61a3472c0aa7 100644 --- a/.gitmodules +++ b/.gitmodules @@ -17,3 +17,6 @@ path = build/terraform-mapper url = https://github.com/GoogleCloudPlatform/terraform-google-conversion.git branch = master +[submodule "azure/magic-module-specs"] + path = azure/magic-module-specs + url = git@github.com:Azure/magic-module-specs.git diff --git a/.travis.yml b/.travis.yml index 1cfe76311839..caec58390169 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,7 +1,11 @@ language: ruby # ruby version defined in .ruby-version will be used +before_install: +- gem update --system +- gem install bundler + script: -- bundle exec rake test +- bundle exec rake erblint git: submodules: false diff --git a/AZURE_DEVELOPER.md b/AZURE_DEVELOPER.md new file mode 100644 index 000000000000..9d0e738e0832 --- /dev/null +++ b/AZURE_DEVELOPER.md @@ -0,0 +1,111 @@ +# Developer Guide for Azure Resources + +We extended magic-modules to support Azure SDKs and resources. Our design principle is to share as much code as we can, but if not, we will put Azure specific code and templates in `azure` folder, and under `Azure` namespace. For example, default data type definitions are under `Api::Type` namespace in `api/type.rb` file, while Azure specific type definitions are under `Api::Azure::Type` namespace in `api/azure/type.rb`. + +For the original magic-modules development documentation, please refer to [DEVELOPER.md](DEVELOPER.md). Before reading the documentation, please make sure you know the basic concepts and coding technologies of Ruby and Ruby template (erb). + +## Folder Structure + +We reused most of the folder structure defined by the Google's magic-modules, but extended some Azure specific folders. Here is the big-picture of some important folders: + +``` +magic-modules + |- api + | |- azure + | | |- *.rb [all Azure specific types could be used in api.yaml] + | |- *.rb [all types could be used in api.yaml] + |- google + | |- *.rb [all utility functions by Google] + |- provider + | |- ansible [Ansible specific type and helper function definitions] + | |- terraform [Terraform specific type and helper function definitions] + | |- azure + | | |- ansible [Azure-ansible specific type and helper function definitions] + | | |- terraform [Azure-terraform specific type and helper function definitions] + | | |- example [Shared example related type definitions] + | | |- ansible.rb [Root object to include all sub-modules in ansible folder] + | | |- terraform.rb [Root object to include all sub-modules in terraform folder] + | | |- core.rb [Helper functions to parse example types] + | |- core.rb, abstract_core.rb, terraform.rb, ansible.rb, config.rb [See below] + | |- *.rb [all types for .yaml and helper functions for templates] + |- templates + | |- ansible + | | |- facts.erb [Ansible info module template] + | | |- resource.erb [Ansible module template] + | | |- integration_test.erb [Ansible test template] + | |- terraform + | | |- datasource.erb [Terraform data source template] + | | |- resource.erb [Terraform resource template] + | | |- resource.html.markdown.erb [Terraform resource documentation template] + | | |- datasource.html.markdown.erb [Terraform data source documentation template] + | | |- schemas + | | | |- *.erb [Terraform schema sub-templates, including definition, d.Get, d.Set, etc.] + | | |- *.erb [Terraform sub-template, e.g. expand, flatten, etc.] + | |- azure + | | |- ansible + | | | |- example [Ansible test yaml and documentation yaml templates] + | | | |- module [Sub-tempaltes to generate Ansible modules or info modules] + | | | |- sdk [Sub-templates to generate Python SDK related code like method call] + | | | |- sdktypes [Sub-templates to generate schema<->SDK marshalling code] + | | | |- test [Ansible test helper templates] + | | |- terraform + | | | |- acctest [Helper sub-templates to generate Terraform tests] + | | | |- example [Terraform test HCL and documentation HCL templates] + | | | |- schemas [Azure-specific schema sub-temapltes, including definition, d.Get, d.Set, etc.] + | | | |- sdk [Sub-templates to generate Go SDK related code like method call, ID parse, fmt.Errorf] + | | | |- sdktypes [Sub-templates to generate schema<->SDK marshalling code] + | |- *.erb [sharable templates like auto-gen comment] + |- compiler.rb [entry point] +``` + +## Compiler + +The entry point of magic module is `compiler.rb`. It will parse the command line, try to read `api.yaml`, `terraform.yaml` and `ansible.yaml` in the input directory, and load the corresponding provider. For Terraform, the provider is located in `provider/terraform.rb`; while for Ansible is `provider/ansible.rb`. When generating code for a specific product (let's say Terraform), all code templates (erb files) will only see functions defined in the corresponding provider. + +Class inheritance structure is illustrated below: + +``` +Provider::Core (provider/core.rb) + |- Provider::AbstractCore (provider/abstract_core.rb) + | |- Provider::Terraform (provider/terraform.rb) + |- Provider::Ansible::Core (provider/ansible.rb) +``` + +These providers are root object, besides defining some common helper functions, they will `include` all submodules of the provider (for example, the definition of all Azure specific data types `provider/azure/terraform.rb` and `provider/azure/ansible.rb`). Together with the configuration definitions (`provider/terraform/config.rb` or `provider/ansible/config.rb`), we will be able to use all defined data types and properties in `api.yaml`, `terraform.yaml`, `ansible.yaml` and all ERB templates. As a developer, you need to make sure your types are eventually included in these root objects, otherwise magic-modules will raise errors. + +## Code Templates + +The overall structure of all code templates are listed in the Folder Structure section. Typically for each template, we introduced a helper function with it, and throughout the code base, we should call that helper function to actually apply the code template. + +For example, for a template `property_to_sdkobject.erb` in Terraform, we have the following functions/files: + +* Actual template file: `templates/azure/terraform/sdktypes/property_to_sdkobject.erb` +* Helper function: `build_property_to_sdk_object` in `provider/azure/terraform/sdk/sub_template.rb` + +We should always call the helper functions throughout the code base like the one in `templates/azure/terraform/sdktypes/nested_object_field_assign.erb`. + +## Logic + +We handled both Terraform and Ansible resource code generation in a similar way. + +1. Magic-modules core handles all definitions (`api.yaml`) and overrides (`terraform.yaml`/`ansible.yaml`) for us +2. Generate schema definition structure +3. Generate code to marshal data from schema to SDK +4. Generate code to call CRUDL SDK APIs +5. Generate code to marshal data from SDK to schema + +Since magic-modules core handles all overrides, I will not talk too much about it, please reference to the original magic-modules documentation. As a developer for Azure, we only need to define the overridable attributes (which will be used in `terraform.yaml`/`ansible.yaml`) in one of the `resource_override.rb` or `property_override.rb`, and then we are able to use them throughout the code base including templates and helper functions. + +Generating schema definition is simple in Ansible since we only need to call `to_yaml` helper function. It requires some additional efforts for Terraform. That's the reason why `templates/terraform/schemas` and `templates/azure/terraform/schemas` exist. + +Marshalling is another tough job to do because Azure SDK objects are deeply hierarchical objects. Both Ansible and Terraform handle it in a recursive way (recursive code template). We put both-way marshalling code templates in `templates/azure/[terraform|ansible]/sdktypes` folder. You will be able to find the corresponding helper functions by using the names of the templates. + +It is not too difficult to generate Azure SDK API calls since they are all defined in `azure_sdk_definitions` section of `api.yaml`. Typically API calls are put directly in the resource/module code template, but with an reusable method call sub-template. + +## Command Line Option + +Besides the basic command line options, we add the `-c (--cloud)` option for users to specify the target cloud platform. The supported values are: +* `gcp`: Google Cloud Platform (by default). +* `azure`: Microsoft Azure Cloud + +The option records the choice of cloud platform by maintaining a global variable in the entry file `compiler.rb`. The variable controls the switches to different code logic according to different cloud platforms in the following files, `api/resource.rb`, `api/type.rb`, `provider/core.rb`, `provider/config.rb`, `provider/ansible.rb`, `provider/ansible/documentation.rb`, `provider/terraform.rb`, `provider/terraform/sub_template.rb`. \ No newline at end of file diff --git a/Gemfile.lock b/Gemfile.lock index ad930ce6caf1..1df1e3b40f21 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -104,6 +104,7 @@ GEM PLATFORMS ruby + x64-mingw32 DEPENDENCIES activesupport @@ -117,4 +118,4 @@ DEPENDENCIES rubocop (~> 0.63.1) BUNDLED WITH - 1.17.2 + 2.0.1 diff --git a/api/azure/resource.rb b/api/azure/resource.rb new file mode 100644 index 000000000000..ddeedd07695e --- /dev/null +++ b/api/azure/resource.rb @@ -0,0 +1,33 @@ +# Copyright 2019 Microsoft Corp. +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +require 'google/yaml_validator' +require 'api/azure/sdk_definition' + +module Api + module Azure + module Resource + + # The Azure-extended properties which supplement Api::Resource::Properties + module Properties + attr_reader :azure_sdk_definition + end + + # Azure-extended validate function of Api::Resource::validate + def azure_validate + check :azure_sdk_definition, type: Api::Azure::SDKDefinition, required: true + end + + end + end +end diff --git a/api/azure/sdk_definition.rb b/api/azure/sdk_definition.rb new file mode 100644 index 000000000000..86b948297898 --- /dev/null +++ b/api/azure/sdk_definition.rb @@ -0,0 +1,64 @@ +# Copyright 2019 Microsoft Corp. +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +require 'api/object' +require 'api/azure/sdk_operation_definition' + +module Api + module Azure + class SDKDefinition < Api::Object + attr_reader :provider_name + attr_reader :go_client_namespace + attr_reader :go_client + attr_reader :python_client_namespace + attr_reader :python_client + attr_reader :create + attr_reader :read + attr_reader :update + attr_reader :delete + attr_reader :list_by_parent + attr_reader :list_by_resource_group + attr_reader :list_by_subscription + + def validate + super + check :provider_name, type: ::String, required: true + check :go_client_namespace, type: ::String, required: true + check :go_client, type: ::String, required: true + check :python_client_namespace, type: ::String, required: true + check :python_client, type: ::String, required: true + check :create, type: Api::Azure::SDKOperationDefinition, required: true + check :read, type: Api::Azure::SDKOperationDefinition, required: true + check :update, type: Api::Azure::SDKOperationDefinition + check :delete, type: Api::Azure::SDKOperationDefinition, required: true + check :list_by_parent, type: Api::Azure::SDKOperationDefinition + check :list_by_resource_group, type: Api::Azure::SDKOperationDefinition + check :list_by_subscription, type: Api::Azure::SDKOperationDefinition + end + + def filter_language!(language) + @create.filter_language!(language) unless @create.nil? + @read.filter_language!(language) unless @read.nil? + @update.filter_language!(language) unless @update.nil? + @delete.filter_language!(language) unless @delete.nil? + end + + def merge_overrides!(overrides) + @create.merge_overrides!(overrides.create) if !@create.nil? && !overrides.create.nil? + @read.merge_overrides!(overrides.read) if !@read.nil? && !overrides.read.nil? + @update.merge_overrides!(overrides.update) if !@update.nil? && !overrides.update.nil? + @delete.merge_overrides!(overrides.delete) if !@delete.nil? && !overrides.delete.nil? + end + end + end +end diff --git a/api/azure/sdk_definition_override.rb b/api/azure/sdk_definition_override.rb new file mode 100644 index 000000000000..4f3c32cacf33 --- /dev/null +++ b/api/azure/sdk_definition_override.rb @@ -0,0 +1,34 @@ +# Copyright 2019 Microsoft Corp. +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +require 'api/object' +require 'api/azure/sdk_operation_definition_override' + +module Api + module Azure + class SDKDefinitionOverride < Api::Object + attr_reader :create + attr_reader :read + attr_reader :update + attr_reader :delete + + def validate + super + check :create, type: SDKOperationDefinitionOverride + check :read, type: SDKOperationDefinitionOverride + check :update, type: SDKOperationDefinitionOverride + check :delete, type: SDKOperationDefinitionOverride + end + end + end +end diff --git a/api/azure/sdk_operation_definition.rb b/api/azure/sdk_operation_definition.rb new file mode 100644 index 000000000000..5ed3fbd00d79 --- /dev/null +++ b/api/azure/sdk_operation_definition.rb @@ -0,0 +1,67 @@ +# Copyright 2019 Microsoft Corp. +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +require 'api/object' +require 'api/azure/sdk_type_definition' + +module Api + module Azure + class SDKOperationDefinition < Api::Object + attr_reader :go_func_name + attr_reader :python_func_name + attr_reader :async + attr_reader :request + attr_reader :response + + def validate + super + @request ||= Hash.new + @response ||= Hash.new + + check :go_func_name, type: ::String, required: true + check :python_func_name, type: ::String, required: true + check :async, type: :boolean + check_ext :request, type: ::Hash, key_type: ::String, item_type: SDKTypeDefinition, required: true + check_ext :response, type: ::Hash, key_type: ::String, item_type: SDKTypeDefinition, required: true + end + + def filter_language!(language) + filter_applicable! @request, language + filter_applicable!(@response, language) unless @response.nil? + end + + def merge_overrides!(overrides) + merge_hash_table!(@request, overrides.request) unless overrides.request.nil? + merge_hash_table!(@response, overrides.response) unless overrides.response.nil? + end + + private + + def filter_applicable!(fields, language) + fields.reject!{|name, value| !value.applicable_to.nil? && !value.applicable_to.include?(language)} + end + + def merge_hash_table!(fields, overrides) + overrides.each do |name, value| + if value.remove + fields.delete(name) + elsif !fields.has_key?(name) + fields[name] = value + else + fields[name].merge_overrides! value + end + end + end + end + end +end diff --git a/api/azure/sdk_operation_definition_override.rb b/api/azure/sdk_operation_definition_override.rb new file mode 100644 index 000000000000..1167d88d0a9e --- /dev/null +++ b/api/azure/sdk_operation_definition_override.rb @@ -0,0 +1,30 @@ +# Copyright 2019 Microsoft Corp. +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +require 'api/object' +require 'api/azure/sdk_type_definition_override' + +module Api + module Azure + class SDKOperationDefinitionOverride < Api::Object + attr_reader :request + attr_reader :response + + def validate + super + check_ext :request, type: ::Hash, key_type: ::String, item_type: SDKTypeDefinitionOverride + check_ext :response, type: ::Hash, key_type: ::String, item_type: SDKTypeDefinitionOverride + end + end + end +end diff --git a/api/azure/sdk_type_definition.rb b/api/azure/sdk_type_definition.rb new file mode 100644 index 000000000000..8651d96d1294 --- /dev/null +++ b/api/azure/sdk_type_definition.rb @@ -0,0 +1,128 @@ +# Copyright 2019 Microsoft Corp. +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +require 'api/object' + +module Api + module Azure + class SDKTypeDefinition < Api::Object + attr_reader :id_portion + attr_reader :applicable_to + attr_reader :empty_value_sensitive + attr_reader :go_variable_name + attr_reader :go_field_name + attr_reader :go_type_name + attr_reader :is_pointer_type + attr_reader :python_parameter_name + attr_reader :python_variable_name + attr_reader :python_field_name + + def validate + super + check :id_portion, type: ::String + check_ext :applicable_to, type: ::Array, item_type: ::String, item_allowed: ['go', 'python'], default: ['go', 'python'] + check :empty_value_sensitive, type: :boolean, default: false + check :go_variable_name, type: ::String + check :go_field_name, type: ::String + check :go_type_name, type: ::String + check :is_pointer_type, type: :boolean, default: false + check :python_parameter_name, type: ::String + check :python_variable_name, type: ::String + check :python_field_name, type: ::String + end + + def merge_overrides!(overrides) + @id_portion = overrides.id_portion unless overrides.id_portion.nil? + @empty_value_sensitive = overrides.empty_value_sensitive unless overrides.empty_value_sensitive.nil? + @go_variable_name = overrides.go_variable_name unless overrides.go_variable_name.nil? + @go_field_name = overrides.go_field_name unless overrides.go_field_name.nil? + @go_type_name = overrides.go_type_name unless overrides.go_type_name.nil? + @is_pointer_type = overrides.is_pointer_type unless overrides.is_pointer_type.nil? + @python_parameter_name = overrides.python_parameter_name unless overrides.python_parameter_name.nil? + @python_variable_name = overrides.python_variable_name unless overrides.python_variable_name.nil? + @python_field_name = overrides.python_field_name unless overrides.python_field_name.nil? + end + + class BooleanObject < SDKTypeDefinition + end + + class IntegerObject < SDKTypeDefinition + end + + class Integer32Object < SDKTypeDefinition + end + + class Integer64Object < SDKTypeDefinition + end + + class Integer32ArrayObject < SDKTypeDefinition + end + + class Integer64ArrayObject < SDKTypeDefinition + end + + class FloatObject < SDKTypeDefinition + end + + class FloatArrayObject < FloatObject + end + + class StringObject < SDKTypeDefinition + end + + class EnumObject < SDKTypeDefinition + attr_reader :go_enum_type_name + attr_reader :go_enum_const_prefix + + def validate + super + check :go_enum_type_name, type: ::String + check :go_enum_const_prefix, type: ::String, default: '' + end + + def merge_overrides!(overrides) + super + # `overrides` is instance of either SDKTypeDefinitionOverride or + # SDKTypeDefinitionOverride::EnumObjectOverride. We only merge type specific + # attribute for the latter case. + return unless overrides.instance_of? Api::Azure::SDKTypeDefinitionOverride::EnumObjectOverride + + @go_enum_type_name = overrides.go_enum_type_name unless overrides.go_enum_type_name.nil? + @go_enum_const_prefix = overrides.go_enum_const_prefix unless overrides.go_enum_const_prefix.nil? + end + end + + class EnumArrayObject < EnumObject + end + + class ISO8601DurationObject < StringObject + end + + class ISO8601DateTimeObject < SDKTypeDefinition + end + + class ComplexObject < SDKTypeDefinition + end + + class StringArrayObject < SDKTypeDefinition + end + + class ComplexArrayObject < ComplexObject + end + + class StringMapObject < SDKTypeDefinition + end + + end + end +end diff --git a/api/azure/sdk_type_definition_override.rb b/api/azure/sdk_type_definition_override.rb new file mode 100644 index 000000000000..946d9ad5ad25 --- /dev/null +++ b/api/azure/sdk_type_definition_override.rb @@ -0,0 +1,83 @@ +# Copyright 2019 Microsoft Corp. +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +require 'api/object' + +module Api + module Azure + class SDKTypeDefinitionOverride < SDKTypeDefinition + attr_reader :remove + + def validate + super + check :remove, type: :boolean, default: false + end + + class BooleanObjectOverride < SDKTypeDefinitionOverride + end + + class IntegerObjectOverride < SDKTypeDefinitionOverride + end + + class Integer32ObjectOverride < SDKTypeDefinitionOverride + end + + class Integer64ObjectOverride < SDKTypeDefinitionOverride + end + + class Integer32ArrayObjectOverride < SDKTypeDefinitionOverride + end + + class Integer64ArrayObjectOverride < SDKTypeDefinitionOverride + end + + class FloatObjectOverride < SDKTypeDefinitionOverride + end + + class StringObjectOverride < SDKTypeDefinitionOverride + end + + class EnumObjectOverride < SDKTypeDefinitionOverride + attr_reader :go_enum_type_name + attr_reader :go_enum_const_prefix + + def validate + super + check :go_enum_type_name, type: ::String + check :go_enum_const_prefix, type: ::String, default: '' + end + end + + class EnumArrayObjectOverride < EnumObjectOverride + end + + class ISO8601DurationObjectOverride < StringObjectOverride + end + + class ISO8601DateTimeObjectOverride < SDKTypeDefinitionOverride + end + + class ComplexObjectOverride < SDKTypeDefinitionOverride + end + + class StringArrayObjectOverride < SDKTypeDefinitionOverride + end + + class ComplexArrayObjectOverride < ComplexObjectOverride + end + + class StringMapObjectOverride < SDKTypeDefinitionOverride + end + end + end +end diff --git a/api/azure/type.rb b/api/azure/type.rb new file mode 100644 index 000000000000..99aabbd8248e --- /dev/null +++ b/api/azure/type.rb @@ -0,0 +1,69 @@ +# Copyright 2019 Microsoft Corp. +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +require 'api/type' + +module Api + module Azure + module Type + + class ResourceGroupName < Api::Type::String + def validate + @order ||= 3 + super + end + end + + class Location < Api::Type::String + def validate + @order ||= 5 + super + end + end + + class Tags < Api::Type::KeyValuePairs + def validate + @order ||= 20 + super + end + end + + class ResourceReference < Api::Type::String + attr_reader :resource_type_name + + def validate + super + check :resource_type_name, type: ::String, required: true + end + end + + class BooleanEnum < Api::Type::Boolean + attr_reader :true_value + attr_reader :false_value + + def validate + super + check :true_value, type: [Symbol], required: true + check :false_value, type: [Symbol], required: true + end + end + + class ISO8601Duration < Api::Type::String + end + + class ISO8601DateTime < Api::Type::String + end + + end + end +end diff --git a/api/azure/type_extension.rb b/api/azure/type_extension.rb new file mode 100644 index 000000000000..6d8b4e90b5be --- /dev/null +++ b/api/azure/type_extension.rb @@ -0,0 +1,36 @@ +# Copyright 2019 Microsoft Corp. +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +module Api + module Azure + module Type + + module Fields + attr_reader :order + attr_reader :sample_value + attr_reader :azure_sdk_references + end + + module TypeExtension + def azure_validate + default_order = 10 + default_order = 1 if @name == "name" + default_order = -1 if @name == "id" + check :order, type: ::Integer, default: default_order + check :azure_sdk_references, type: ::Array, item_type: ::String + end + end + + end + end +end diff --git a/api/compiler.rb b/api/compiler.rb index 3e7ac68ba7b3..1faf7eca054a 100644 --- a/api/compiler.rb +++ b/api/compiler.rb @@ -18,6 +18,10 @@ require 'compile/core' require 'google/yaml_validator' +require 'api/azure/type' +require 'api/azure/sdk_definition' +require 'api/azure/sdk_definition_override' + module Api # Process .yaml and produces output module class Compiler diff --git a/api/resource.rb b/api/resource.rb index f776f4071742..698ef0ac3bca 100644 --- a/api/resource.rb +++ b/api/resource.rb @@ -16,10 +16,13 @@ require 'api/resource/nested_query' require 'api/resource/reference_links' require 'google/string_utils' +require 'api/azure/resource' module Api # An object available in the product class Resource < Api::Object::Named + include Api::Azure::Resource + # The list of properties (attr_reader) that can be overridden in # .yaml. module Properties @@ -122,6 +125,8 @@ module Properties end include Properties + # Azure specific properties, will skip the default overrides + include Api::Azure::Resource::Properties # Parameters can be overridden via Provider::PropertyOverride # A custom getter is used for :parameters instead of `attr_reader` @@ -176,6 +181,8 @@ def validate check :exclude_resource, type: :boolean, default: false validate_identity unless @identity.nil? + + azure_validate if $target_is_azure end # ==================== diff --git a/api/type.rb b/api/type.rb index 02a175aabded..a099255e09e2 100644 --- a/api/type.rb +++ b/api/type.rb @@ -13,14 +13,17 @@ require 'api/object' require 'google/string_utils' +require 'api/azure/type_extension' module Api # Represents a property type class Type < Api::Object::Named + include Api::Azure::Type::TypeExtension # The list of properties (attr_reader) that can be overridden in # .yaml. module Fields include Api::Object::Named::Properties + include Api::Azure::Type::Fields attr_reader :default_value attr_reader :description @@ -71,6 +74,7 @@ module Fields def validate super + azure_validate if $target_is_azure check :description, type: ::String, required: true check :exclude, type: :boolean, default: false, required: true check :deprecation_message, type: ::String diff --git a/azure/golang_utils.rb b/azure/golang_utils.rb new file mode 100644 index 000000000000..7668e9ad7784 --- /dev/null +++ b/azure/golang_utils.rb @@ -0,0 +1,25 @@ +# Copyright 2019 Microsoft Corp. +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +require 'google/golang_utils' + +module Azure + module GolangUtils + + def azure_go_literal(value, go_package = nil) + return "string(#{go_package}#{'.' unless go_package.nil?}#{value})" if value.is_a?(Symbol) + go_literal value + end + + end +end diff --git a/azure/magic-module-specs b/azure/magic-module-specs new file mode 160000 index 000000000000..2215b479ebb5 --- /dev/null +++ b/azure/magic-module-specs @@ -0,0 +1 @@ +Subproject commit 2215b479ebb52f02f898cf09893ae847d0e27ec9 diff --git a/azure/python_utils.rb b/azure/python_utils.rb new file mode 100644 index 000000000000..f35948a0be03 --- /dev/null +++ b/azure/python_utils.rb @@ -0,0 +1,38 @@ +# Copyright 2019 Microsoft Corp. +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +require 'google/python_utils' + +module Azure + module PythonUtils + include Google::PythonUtils + + # Azure extended `python_literal` function (see 'google/python_utils.rb') + # It will treat Azure SDK enumerations differently + def azure_python_literal(value, **opts) + return "'#{value.to_s.underscore}'" if value.is_a?(Symbol) + python_literal(value, opts) + end + + # Get the python variable name of a property + # If we can find the corresponding SDK definition, we use its `python_variable_name`, otherwise we `underscore` the property's name + def azure_python_variable_name(property, sdk_op_def) + sdk_ref = get_applicable_reference(property.azure_sdk_references, sdk_op_def.request) + return property.out_name.underscore if sdk_ref.nil? + python_var = get_sdk_typedef_by_references(property.azure_sdk_references, sdk_op_def.request).python_variable_name + return property.out_name.underscore if python_var.nil? + python_var + end + + end +end diff --git a/azure/yaml_validator_extension.rb b/azure/yaml_validator_extension.rb new file mode 100644 index 000000000000..45b59199812c --- /dev/null +++ b/azure/yaml_validator_extension.rb @@ -0,0 +1,56 @@ +# Copyright 2019 Microsoft Corp. +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +module Azure + module YamlValidatorExtension + + # Does extended validation checking for a variable + # options: + # :default - the default value for this variable if its nil + # :key_type - the allowed types that all keys in a hash should be + # :item_type - the allowed types that all values in an array or hash should be + # :allowed - the allowed values that this non-array variable should be + # :item_allowed - the allowed values that all values in an array or hash should be + # :required - is the variable required? (defaults: false) + def check_ext(variable, **opts) + check variable, opts + + value = instance_variable_get("@#{variable}") + + # Check key_type and item_type + if value.is_a?(::Hash) + raise "#{variable} must have key_type and item_type on hashes" unless opts[:key_type] && opts[:item_type] + + value.each do |k, v| + check_property_value("#{variable} key (#{k})", k, opts[:key_type]) + check_property_value("#{variable}[#{k}]", v, opts[:item_type]) + end + end + + # Check if item values are allowed + return unless opts[:item_allowed] + if value.is_a?(::Array) + value.each_with_index do |v, i| + raise "#{v} on #{variable}[#{i}] should be one of #{opts[:item_allowed]}" \ + unless opts[:item_allowed].include?(v) + end + elsif value.is_a?(::Hash) + value.each do |k, v| + raise "#{v} on #{variable}[#{k}] should be one of #{opts[:item_allowed]}" \ + unless opts[:item_allowed].include?(v) + end + end + end + + end +end diff --git a/build/ansible b/build/ansible index 1139dfc0046a..adf6ec907a40 160000 --- a/build/ansible +++ b/build/ansible @@ -1 +1 @@ -Subproject commit 1139dfc0046a7d01d239ec55d0c2480c5939042c +Subproject commit adf6ec907a402079b7656354bb000eb6b5aec339 diff --git a/build/inspec b/build/inspec index 9de2048e8a0b..38254818353a 160000 --- a/build/inspec +++ b/build/inspec @@ -1 +1 @@ -Subproject commit 9de2048e8a0bdae93c659bcda5515b906905c2f7 +Subproject commit 38254818353ad195ffe7a50100b728c60d28b41a diff --git a/build/terraform b/build/terraform index 2265fbc91f6f..23ac5efd95b6 160000 --- a/build/terraform +++ b/build/terraform @@ -1 +1 @@ -Subproject commit 2265fbc91f6f6d13d654f19cfdd8f75d55a4cbe3 +Subproject commit 23ac5efd95b6efde8cfb74bc8eb58f40699e1e53 diff --git a/build/terraform-beta b/build/terraform-beta index 40d2c97ce784..3b0064d17133 160000 --- a/build/terraform-beta +++ b/build/terraform-beta @@ -1 +1 @@ -Subproject commit 40d2c97ce784914030fb0f87815c1452783813b2 +Subproject commit 3b0064d171336a2523b4b4a94cd053a96b8aabab diff --git a/compile/core.rb b/compile/core.rb index 9bd1f7c7cbcc..78d220407120 100644 --- a/compile/core.rb +++ b/compile/core.rb @@ -169,7 +169,7 @@ def to_yaml(obj, options = {}) # Refer to Compile::Core.compile for full details about the compilation # process. def compile_file(ctx, source) - compile_string(ctx, File.read(source)) + compile_string(ctx, File.read(source), filename: source) rescue StandardError => e puts "Error compiling file: #{source}" raise e @@ -232,11 +232,13 @@ def lines_before(code, number = 0) # Compiles an ERB template using the data from a key-value pair. # The key-value pair may be a Hash or a Binding - def compile_string(ctx, source) + def compile_string(ctx, source, filename: nil) + erb = ERB.new(source, trim_mode: '->') + erb.filename = filename if ctx.is_a? Binding - ERB.new(source, trim_mode: '->').result(ctx).split("\n") + erb.result(ctx).split("\n") elsif ctx.is_a? Hash - ERB.new(source, trim_mode: '->').result( + erb.result( OpenStruct.new(ctx).instance_eval { binding.of_caller(1) } ).split("\n") else diff --git a/compiler.rb b/compiler.rb index ea2adb0ee6f8..fb0e6654c73f 100755 --- a/compiler.rb +++ b/compiler.rb @@ -41,6 +41,7 @@ force_provider = nil types_to_generate = [] version = 'ga' +$target_is_azure = false ARGV << '-h' if ARGV.empty? Google::LOGGER.level = Logger::INFO @@ -71,6 +72,11 @@ opt.on('-v', '--version VERSION', 'API version to generate') do |v| version = v end + opt.on('-c', '--cloud CLOUD', 'Target cloud platform ("gcp" for Google Cloud Platform or + "azure" for Microsoft Azure Cloud, "gcp" by default)') do |c| + $target_is_azure = true if c == 'azure' + raise 'Option -c/--cloud must be either "gcp" or "azure"' if c != 'gcp' && c != 'azure' + end opt.on('-h', '--help', 'Show this message') do puts opt exit @@ -153,7 +159,9 @@ # In order to only copy/compile files once per provider this must be called outside # of the products loop. This will get called with the provider from the final iteration # of the loop -provider&.copy_common_files(output_path, version) -provider&.compile_common_files(output_path, version) +unless $target_is_azure + provider&.copy_common_files(output_path, version) + provider&.compile_common_files(output_path, version) +end # rubocop:enable Metrics/BlockLength diff --git a/google/yaml_validator.rb b/google/yaml_validator.rb index 0857ba944684..1ed198a4f634 100644 --- a/google/yaml_validator.rb +++ b/google/yaml_validator.rb @@ -13,16 +13,18 @@ require 'google/logger' require 'yaml' +require 'azure/yaml_validator_extension' module Google # A helper class to validate contents coming from YAML files. class YamlValidator + include Azure::YamlValidatorExtension class << self def parse(content) # TODO(nelsonjr): Allow specifying which symbols to restrict it further. # But it requires inspecting all configuration files for symbol sources, # such as Enum values. Leaving it as a nice-to-have for the future. - YAML.safe_load(content, allowed_classes) + YAML.safe_load(content, allowed_classes, aliases: true) end def allowed_classes diff --git a/overrides/azure/resources_extension.rb b/overrides/azure/resources_extension.rb new file mode 100644 index 000000000000..385a38570d17 --- /dev/null +++ b/overrides/azure/resources_extension.rb @@ -0,0 +1,28 @@ +# Copyright 2019 Microsoft Corp. +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +module Overrides + module Azure + + module ResourceOverrideExtension + def filter_azure_sdk_language(_resource, language) + _resource.azure_sdk_definition.filter_language!(language) unless _resource.azure_sdk_definition.nil? + end + + def merge_azure_sdk_definition(_resource, overrides) + _resource.azure_sdk_definition.merge_overrides!(overrides) unless _resource.azure_sdk_definition.nil? || overrides.nil? + end + end + + end +end diff --git a/overrides/resources.rb b/overrides/resources.rb index 8716d1d96e25..b059cb6eb225 100644 --- a/overrides/resources.rb +++ b/overrides/resources.rb @@ -12,6 +12,7 @@ # limitations under the License. require 'google/yaml_validator' +require 'overrides/azure/resources_extension' module Overrides # All overrides act as a Hash under-the-hood. @@ -82,6 +83,7 @@ class ResourceOverrides < OverrideResource # Override to an Api::Resource in api.yaml class ResourceOverride < OverrideResource + include Overrides::Azure::ResourceOverrideExtension def apply(_resource) self end diff --git a/provider/ansible.rb b/provider/ansible.rb index beb80fb72a2c..9ef758278d8d 100644 --- a/provider/ansible.rb +++ b/provider/ansible.rb @@ -23,11 +23,13 @@ require 'provider/ansible/facts_override' require 'overrides/ansible/resource_override' require 'overrides/ansible/property_override' +require 'provider/azure/ansible_extension' module Provider # Ansible Provider module containing helper functions and the Ansible Provider # implementation "Core" module Ansible + include Provider::Azure::AnsibleExtension # Code generator for Ansible Cookbooks that manage Google Cloud Platform # resources. # TODO(alexstephen): Split up class into multiple modules. @@ -59,7 +61,7 @@ def api_version_setup(version_name) @api.set_properties_based_on_version(version) # Generate version_added_file - @version_added = build_version_added + @version_added = build_version_added unless $target_is_azure version end @@ -232,6 +234,8 @@ def get_example(cfg_file) end def generate_resource(data) + # Azure Switch + return azure_generate_resource data if $target_is_azure target_folder = data.output_folder name = module_name(data.object) path = File.join(target_folder, @@ -244,6 +248,8 @@ def generate_resource(data) end def generate_resource_tests(data) + # Azure Switch + return azure_generate_resource_tests data if $target_is_azure prod_name = data.object.name.underscore path = ["products/#{data.product.api_name}", "examples/ansible/#{prod_name}.yaml"].join('/') @@ -277,6 +283,8 @@ def generate_resource_tests(data) end def compile_datasource(data) + # Azure Switch + return azure_compile_datasource data if $target_is_azure target_folder = data.output_folder name = "#{module_name(data.object)}_facts" data.generate('templates/ansible/facts.erb', diff --git a/provider/ansible/documentation.rb b/provider/ansible/documentation.rb index e9e302942f96..031fc9153d0c 100644 --- a/provider/ansible/documentation.rb +++ b/provider/ansible/documentation.rb @@ -14,6 +14,7 @@ require 'compile/core' require 'provider/config' require 'provider/core' +require 'provider/azure/ansible/documentation_extension' # Rubocop doesn't like this file because the hashes are complicated. # Humans like this file because the hashes are explicit and easy to read. @@ -21,6 +22,7 @@ module Provider module Ansible # Responsible for building out YAML documentation blocks. module Documentation + include Provider::Azure::Ansible::DocumentationExtension # Builds out the DOCUMENTATION for a property. # This will eventually be converted to YAML def documentation_for_property(prop) @@ -82,6 +84,7 @@ def returns_for_property(prop) end def autogen_notice_contrib + return azure_autogen_notic_contrib if $target_is_azure ['Please read more about how to change this file at', 'https://www.github.com/GoogleCloudPlatform/magic-modules'] end diff --git a/provider/ansible/module.rb b/provider/ansible/module.rb index 08bd351d4328..cf0a5ab28f23 100644 --- a/provider/ansible/module.rb +++ b/provider/ansible/module.rb @@ -12,6 +12,7 @@ # limitations under the License. require 'google/python_utils' +require 'provider/azure/ansible/module_extension' module Provider module Ansible @@ -19,6 +20,7 @@ module Ansible # AnsibleModule is responsible for input validation. module Module include Google::PythonUtils + include Provider::Azure::Ansible::ModuleExtension # Returns an array of all base options for a given property. def ansible_module(properties) properties.reject(&:output) diff --git a/provider/azure/ansible/config.rb b/provider/azure/ansible/config.rb new file mode 100644 index 000000000000..24e792e8dc05 --- /dev/null +++ b/provider/azure/ansible/config.rb @@ -0,0 +1,45 @@ +# Copyright 2019 Microsoft Corp. +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +require 'provider/ansible/config' + +module Provider + module Azure + module Ansible + + class Config < Provider::Ansible::Config + attr_reader :author + attr_reader :version_added + + def provider + Provider::Ansible::Core + end + + def resource_override + Provider::Azure::Ansible::ResourceOverride + end + + def property_override + Provider::Azure::Ansible::PropertyOverride + end + + def validate + super + check :author, type: ::String, required: true + check :version_added, type: ::String, required: true + end + end + + end + end +end diff --git a/provider/azure/ansible/documentation_extension.rb b/provider/azure/ansible/documentation_extension.rb new file mode 100644 index 000000000000..f71e5b98ba90 --- /dev/null +++ b/provider/azure/ansible/documentation_extension.rb @@ -0,0 +1,86 @@ +# Copyright 2019 Microsoft Corp. +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +module Provider + module Azure + module Ansible + module DocumentationExtension + + def azure_documentation_for_property(prop, object, is_data_source = false) + required = prop.required && !prop.default_value && !is_location?(prop) ? true : nil + { + azure_python_variable_name(prop, object.azure_sdk_definition.create) => { + 'description' => [ + (is_data_source && is_tags?(prop) ? "Limit results by providing a list of tags. Format tags as 'key' or 'key:value'." : format_description(prop.description)), + (resourceref_description(prop) if prop.is_a?(Api::Type::ResourceRef) && !prop.resource_ref.readonly), + (azure_resource_ref_description(prop) if prop.is_a?(Api::Azure::Type::ResourceReference)) + ].flatten.compact, + 'required' => required, + 'default' => (prop.default_value.to_s.underscore if prop.default_value), + 'type' => azure_python_type(prop), + 'choices' => (prop.values.map{|v| v.to_s.underscore} if prop.is_a?(Api::Type::Enum)), + 'aliases' => prop.aliases, + 'suboptions' => ( + if (prop.is_a?(Api::Type::NestedObject) || prop.is_a?(Api::Type::Array) && prop.item_type.is_a?(Api::Type::NestedObject)) && prop.nested_properties? + prop.nested_properties.reject(&:output).map { |p| azure_documentation_for_property(p, object) } + .reduce({}, :merge) + end + ) + }.reject { |_, v| v.nil? } + } + end + + def azure_returns_for_property(prop, object) + type = azure_python_type(prop) || 'str' + type = 'str' if type == 'path' || prop.is_a?(Api::Azure::Type::ResourceReference) + type = 'dict' if prop.is_a?(Api::Azure::Type::Tags) + type = 'complex' if prop.is_a?(Api::Type::NestedObject) \ + || (prop.is_a?(Api::Type::Array) \ + && prop.item_type.is_a?(Api::Type::NestedObject)) + sample = prop.document_sample_value || prop.sample_value + sample = sample.to_s.underscore if sample.is_a? Symbol + { + azure_python_variable_name(prop, object.azure_sdk_definition.create) => { + 'description' => format_description(prop.description), + 'returned' => 'always', + 'type' => type, + 'sample' => sample, + 'contains' => ( + if prop.nested_properties? + prop.nested_properties.map { |p| azure_returns_for_property(p, object) } + .reduce({}, :merge) + end + ) + }.reject { |_, v| v.nil? } + } + end + + def azure_autogen_notic_contrib + ['Please read more about how to change this file at', + 'https://github.com/Azure/magic-module-specs'] + end + + private + + def azure_resource_ref_description(prop) + [ + "It can be the #{prop.resource_type_name} name which is in the same resource group.", + "It can be the #{prop.resource_type_name} ID. e.g., #{prop.document_sample_value || prop.sample_value}.", + "It can be a dict which contains C(name) and C(resource_group) of the #{prop.resource_type_name}." + ] + end + + end + end + end +end diff --git a/provider/azure/ansible/example/helpers.rb b/provider/azure/ansible/example/helpers.rb new file mode 100644 index 000000000000..9eed906db919 --- /dev/null +++ b/provider/azure/ansible/example/helpers.rb @@ -0,0 +1,31 @@ +# Copyright 2019 Microsoft Corp. +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +module Provider + module Azure + module Ansible + module Example + module Helpers + def generate_info_assert_list(example_name) + example = get_example_by_names(example_name) + asserts = ["- output['items'][0]['id'] != None"] + example.properties.each_key do |p| + asserts << "- output['items'][0]['#{p.underscore}'] != None" + end + asserts + end + end + end + end + end +end diff --git a/provider/azure/ansible/example/sub_template.rb b/provider/azure/ansible/example/sub_template.rb new file mode 100644 index 000000000000..76dd8c4598e1 --- /dev/null +++ b/provider/azure/ansible/example/sub_template.rb @@ -0,0 +1,95 @@ +# Copyright 2019 Microsoft Corp. +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +module Provider + module Azure + module Ansible + module Example + module SubTemplate + def build_test_yaml_from_example(example_name, name_postfix = nil, register_name = 'output') + random_vars = Set.new + deps, main = build_yaml_from_example(nil, example_name, random_vars, name_postfix, {}, register_name) + return deps, main, random_vars + end + + def build_documentation_yaml_from_example(example) + _, main = build_yaml_from_example(nil, example.example, Set.new, nil, example.resource_name_hints, nil) + return main + end + + def build_yaml_from_example(product_name, example_name, random_variables, name_postfix, name_hints, register_name) + example = get_example_by_names(example_name, product_name) + yaml_deps = compile 'templates/azure/ansible/example/example_deps_yaml.erb', 1 + yaml_raw = compile 'templates/azure/ansible/example/example_yaml.erb', 1 + context = ExampleContextBinding.new(name_hints, random_variables) + yaml_deps = compile_string context.get_binding, yaml_deps + yaml_raw = compile_string context.get_binding, yaml_raw + return yaml_deps, yaml_raw + end + + def build_yaml_properties(properties, indentation = 2) + result = compile 'templates/azure/ansible/example/yaml_properties.erb', 1 + indent result, indentation + end + + private + + class ExampleContextBinding + attr_reader :my_binding + attr_reader :name_hints + attr_reader :random_variables + + def initialize(name_hints, random_vars) + @my_binding = binding + @name_hints = name_hints + @random_variables = random_vars + end + + def get_binding() + @my_binding + end + + def get_resource_name(name_hint, random_var_name, random_var_prefix = '') + return "#{name_hints[name_hint]}\n" if name_hints.has_key?(name_hint) + @random_variables << RandomizedVariable.new(:Standard, random_var_name, random_var_prefix) + "\"{{ #{random_var_name} }}\"\n" + end + end + + class RandomizedVariable + attr_reader :variable_name + attr_reader :variable_value + + def initialize(type, var_name, prefix) + case type + when :Standard + @variable_name = var_name + @variable_value = prefix + "{{ resource_group | hash('md5') | truncate(7, True, '') }}{{ 1000 | random }}" + end + end + + def hash + hash = 17 * 31 + @variable_name.hash + hash * 31 + @variable_value.hash + end + + def eql?(other) + return false unless other.is_a?(RandomizedVariable) + @variable_name.eql?(other.variable_name) && @variable_value.eql?(other.variable_value) + end + end + end + end + end + end +end diff --git a/provider/azure/ansible/helpers.rb b/provider/azure/ansible/helpers.rb new file mode 100644 index 000000000000..18feba8a88fc --- /dev/null +++ b/provider/azure/ansible/helpers.rb @@ -0,0 +1,109 @@ +# Copyright 2019 Microsoft Corp. +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +require 'api/azure/type' + +module Provider + module Azure + module Ansible + module Helpers + def is_resource_name?(property) + property.parent.nil? && property.name == 'name' + end + + def is_tags?(property) + property.is_a? Api::Azure::Type::Tags + end + + def is_tags_defined?(object) + object.all_user_properties.any?{|p| is_tags?(p)} + end + + def get_tags_property(object) + object.all_user_properties.find{|p| is_tags?(p)} + end + + def is_location?(property) + property.parent.nil? && property.is_a?(Api::Azure::Type::Location) + end + + def is_location_defined?(object) + object.all_user_properties.any?{|p| is_location?(p)} + end + + def is_resource_group?(property) + property.parent.nil? && property.is_a?(Api::Azure::Type::ResourceGroupName) + end + + def always_has_value?(property) + property.required || !property.default_value.nil? + end + + def order_azure_properties(properties, data_source_input = []) + special_props = properties.select{|p| p.name == 'id' || p.name == 'name' || p.name == 'location' || p.name == 'resourceGroupName' || p.name == 'resourceGroup' || data_source_input.include?(p)} + other_props = properties.reject{|p| p.name == 'id' || p.name == 'name' || p.name == 'location' || p.name == 'resourceGroupName' || p.name == 'resourceGroup' || data_source_input.include?(p)} + sorted_special = special_props.sort_by{|p| p.name == 'resourceGroup' || p.name == 'resourceGroupName' ? 0 : p.order } + sorted_special + other_props.sort_by(&:name) + end + + def word_wrap_for_yaml(lines, width = 160) + wrapped = Array.new + lines.each do |line| + quoted = false + first_line = true + while line.length > width + # Calculate leading spaces for the following lines + striped = line.lstrip + spaces = line.length - striped.length + if first_line + spaces += 2 + first_line = false + end + + # Quote the whole line using quotation mark if not quoted + quoted = true unless striped.start_with? '- ' + unless quoted + line = line[0..spaces - 1] + '"' + line[spaces..-1] + '"' if line[spaces] != '"' + quoted = true + end + + # Find the last possible word-break character + wb_index = find_word_break_index(line, /[ \t@=,;]/, width - spaces) + wb_index = find_word_break_index(line, /[ \t@=,;\/]/, width - spaces) if wb_index.nil? || wb_index <= spaces + break if wb_index.nil? || wb_index <= spaces + + # Break this line into two + wb_char = line[wb_index] + cur_line = line[0..wb_index - 1] + cur_line += wb_char unless wb_char == ' ' + line = ' ' * spaces + line[wb_index + 1..-1] + wrapped << cur_line + end + wrapped << line + end + wrapped + end + + private + + def find_word_break_index(line, wb_chars, width) + wb_index = line.rindex(wb_chars) + while !wb_index.nil? && wb_index > width + wb_index = line.rindex(wb_chars, wb_index - 1) + end + wb_index + end + end + end + end +end diff --git a/provider/azure/ansible/module/sub_template.rb b/provider/azure/ansible/module/sub_template.rb new file mode 100644 index 000000000000..08f483aa670b --- /dev/null +++ b/provider/azure/ansible/module/sub_template.rb @@ -0,0 +1,32 @@ +# Copyright 2019 Microsoft Corp. +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +module Provider + module Azure + module Ansible + module Module + module SubTemplate + def build_class_instance_variable_init(sdk_operation, object, indentation = 8) + result = compile 'templates/azure/ansible/module/class_instance_variable_init.erb', 1 + indent result, indentation + end + + def build_response_properties_update(properties, sdk_response_def, indentation = 16) + result = compile 'templates/azure/ansible/module/response_properties_update.erb', 1 + indent_list result, indentation + end + end + end + end + end +end \ No newline at end of file diff --git a/provider/azure/ansible/module_extension.rb b/provider/azure/ansible/module_extension.rb new file mode 100644 index 000000000000..0be41058b2be --- /dev/null +++ b/provider/azure/ansible/module_extension.rb @@ -0,0 +1,85 @@ +# Copyright 2019 Microsoft Corp. +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +require 'google/python_utils' +require 'azure/python_utils' + +module Provider + module Azure + module Ansible + module ModuleExtension + + include Google::PythonUtils + include ::Azure::PythonUtils + + def azure_python_dict_for_property(prop, object, spaces = 0) + if prop.is_a?(Api::Type::Array) && \ + prop.item_type.is_a?(Api::Type::NestedObject) + azure_nested_obj_dict(prop, object, prop.item_type.properties, spaces) + elsif prop.is_a? Api::Type::NestedObject + azure_nested_obj_dict(prop, object, prop.properties, spaces) + else + name = azure_python_variable_name(prop, object.azure_sdk_definition.create) + options = azure_prop_options(prop, object, spaces).join("\n") + "#{name}=dict(\n#{indent_list(options, 4)}\n)" + end + end + + private + + # Creates a Python dictionary representing a nested object property + # for validation. + def azure_nested_obj_dict(prop, object, properties, spaces) + name = azure_python_variable_name(prop, object.azure_sdk_definition.create) + options = azure_prop_options(prop, object, spaces).join("\n") + [ + "#{name}=dict(\n#{indent_list(options, 4, true)}\n options=dict(", + indent_list(properties.map do |p| + azure_python_dict_for_property(p, object, spaces + 4) + end, 8), + " )\n)" + ] + end + + # Returns an array of all base options for a given property. + def azure_prop_options(prop, _object, spaces) + [ + ('required=True' if prop.required && !prop.default_value && !is_location?(prop)), + ("default=#{azure_python_literal(prop.default_value)}" \ + if prop.default_value), + "type=#{quote_string(azure_python_type(prop))}", + (azure_choices_enum(prop, spaces) if prop.is_a? Api::Type::Enum), + ("elements=#{quote_string(azure_python_type(prop.item_type))}" \ + if prop.is_a? Api::Type::Array), + ("aliases=[#{prop.aliases.map { |x| quote_string(x) }.join(', ')}]" \ + if prop.aliases), + ('updatable=False' if prop.input && !is_resource_group?(prop) && !is_resource_name?(prop)), + ("disposition='/'" if prop.input && !is_resource_group?(prop) && !is_resource_name?(prop)) + ].compact + end + + # Returns a formatted string represented the choices of an enum + def azure_choices_enum(prop, spaces) + name = prop.out_name.underscore + type = "type=#{quote_string(azure_python_type(prop))}" + # + 6 for =dict( + choices_indent = spaces + name.length + type.length + 6 + "choices=[#{prop.values.map do |x| + quote_string(x.to_s.underscore) + end.join(', ')}]" + end + + end + end + end +end diff --git a/provider/azure/ansible/property_override.rb b/provider/azure/ansible/property_override.rb new file mode 100644 index 000000000000..7c7009bf2f23 --- /dev/null +++ b/provider/azure/ansible/property_override.rb @@ -0,0 +1,45 @@ +# Copyright 2019 Microsoft Corp. +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +require 'overrides/ansible/property_override' + +module Provider + module Azure + module Ansible + + class PropertyOverride < Overrides::Ansible::PropertyOverride + # Collection of fields allowed in the PropertyOverride section for + # Ansible. All fields should be `attr_reader :` + def self.attributes + super.concat(%i[ + resource_type_name + document_sample_value + custom_normalize + inline_custom_response_format + ]) + end + + attr_reader(*attributes) + + def validate + super + check :resource_type_name, type: ::String + check :document_sample_value, type: ::String + check :custom_normalize, type: ::String + check :inline_custom_response_format, type: ::String + end + end + + end + end +end diff --git a/provider/azure/ansible/resource_override.rb b/provider/azure/ansible/resource_override.rb new file mode 100644 index 000000000000..22b6f2258679 --- /dev/null +++ b/provider/azure/ansible/resource_override.rb @@ -0,0 +1,70 @@ +# Copyright 2019 Microsoft Corp. +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +require 'overrides/ansible/resource_override' +require 'provider/azure/example/example' + +module Provider + module Azure + module Ansible + + class IntegrationTestDefinition < ExampleReference + attr_reader :delete_example + attr_reader :info_by_name_example + attr_reader :info_by_resource_group_example + + def validate + super + check :delete_example, type: ::String, required: true + check :info_by_name_example, type: ::String + check :info_by_resource_group_example, type: ::String + end + end + + class DocumentExampleReference < ExampleReference + attr_reader :resource_name_hints + + def validate + super + check_ext :resource_name_hints, type: ::Hash, key_type: ::String, item_type: ::String + end + end + + class ResourceOverride < Overrides::Ansible::ResourceOverride + def self.attributes + super.concat(%i[ + azure_sdk_definition + inttests + examples + ]) + end + + attr_reader(*attributes) + + def validate + super + check :examples, type: ::Array, default: [], item_type: DocumentExampleReference + check :inttests, type: ::Array, default: [], item_type: IntegrationTestDefinition + end + + def apply(_resource) + filter_azure_sdk_language _resource, "python" + merge_azure_sdk_definition _resource, @azure_sdk_definition + @azure_sdk_definition = nil + super + end + end + + end + end +end diff --git a/provider/azure/ansible/sdk/helpers.rb b/provider/azure/ansible/sdk/helpers.rb new file mode 100644 index 000000000000..2060406ed227 --- /dev/null +++ b/provider/azure/ansible/sdk/helpers.rb @@ -0,0 +1,81 @@ +# Copyright 2019 Microsoft Corp. +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +module Provider + module Azure + module Ansible + module SDK + + module Helpers + def get_parent_reference(reference) + parent = reference[0..reference.rindex('/')] + return parent if parent == '/' + parent.chomp '/' + end + + def get_properties_matching_sdk_reference(sdk_reference, object) + object.all_user_properties + .select{|p| p.azure_sdk_references.include?(sdk_reference)} + .sort_by{|p| [p.order, p.name]} + end + + def get_applicable_reference(references, typedefs) + references.each do |ref| + return ref if typedefs.has_key?(ref) + end + nil + end + + def get_sdk_typedef_by_references(references, typedefs) + ref = get_applicable_reference(references, typedefs) + return nil if ref.nil? + typedefs[ref] + end + + def self_require_type_marshal?(property, sdk_marshal) + return true if is_location? property + + sdk_ref = get_applicable_reference(property.azure_sdk_references, sdk_marshal.operation.request) + return false if !sdk_ref.start_with?('/') + + return true if property.is_a? Api::Type::Enum + return true if property.is_a? Api::Azure::Type::ResourceReference + + var_name = azure_python_variable_name(property, sdk_marshal.operation) + sdk_type = get_sdk_typedef_by_references(property.azure_sdk_references, sdk_marshal.operation.request) + return true if var_name != sdk_type.python_field_name + false + end + + def descendants_require_type_marshal?(property, sdk_marshal) + return property.nested_properties.any?{|p| require_type_marshal?(p, sdk_marshal)} if property.nested_properties? + false + end + + def require_type_marshal?(property, sdk_marshal) + self_require_type_marshal?(property, sdk_marshal) || descendants_require_type_marshal?(property, sdk_marshal) + end + + def property_normalization_template(property) + return get_custom_template_path(property.custom_normalize) if property.custom_normalize + return 'templates/azure/ansible/sdktypes/location_property_normalize.erb' if is_location? property + return 'templates/azure/ansible/sdktypes/property_normalize.erb' if property.is_a? Api::Type::Enum + return 'templates/azure/ansible/sdktypes/property_normalize.erb' if property.is_a? Api::Azure::Type::ResourceReference + 'templates/azure/ansible/sdktypes/unsupported.erb' + end + end + + end + end + end +end diff --git a/provider/azure/ansible/sdk/sdk_marshal_descriptor.rb b/provider/azure/ansible/sdk/sdk_marshal_descriptor.rb new file mode 100644 index 000000000000..239bc07e472b --- /dev/null +++ b/provider/azure/ansible/sdk/sdk_marshal_descriptor.rb @@ -0,0 +1,49 @@ +# Copyright 2019 Microsoft Corp. +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +module Provider + module Azure + module Ansible + module SDK + + class MarshalDescriptor + attr_reader :properties + attr_reader :operation + attr_reader :parent_reference + attr_reader :marshalled_references + attr_reader :input + attr_reader :output + + def initialize(properties, sdk_operation, input, output, parent_sdk_reference = '/', marshalled = nil) + @properties = properties + @operation = sdk_operation + @input = input + @output = output + @parent_reference = parent_sdk_reference + @marshalled_references = marshalled || { '/' => @output } + end + + def add_marshalled_reference(reference, expression) + @marshalled_references[reference] = expression + end + + def create_child_descriptor(property, sdk_reference, sdk_type) + input_expression = "#{@input}['#{sdk_type.python_field_name}']" + MarshalDescriptor.new property.nested_properties, @operation, input_expression, @output, sdk_reference, @marshalled_references + end + end + + end + end + end +end diff --git a/provider/azure/ansible/sdk/sub_template.rb b/provider/azure/ansible/sdk/sub_template.rb new file mode 100644 index 000000000000..75e1ebe34aa9 --- /dev/null +++ b/provider/azure/ansible/sdk/sub_template.rb @@ -0,0 +1,50 @@ +# Copyright 2019 Microsoft Corp. +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +module Provider + module Azure + module Ansible + module SDK + + module SubTemplate + def build_sdk_method_invocation(sdk_client, sdk_op_def, indentation = 12) + result = compile 'templates/azure/ansible/sdk/method_invocation.erb', 1 + indent result, indentation + end + + def build_property_normalization(property, sdk_marshal) + result = compile property_normalization_template(property), 1 + end + + def build_sdk_reference_assignment(input_expression, reference, sdk_marshal, indentation = 0) + result = compile 'templates/azure/ansible/sdktypes/reference_assignment.erb', 1 + indent result, indentation + end + + def build_property_to_sdk_object(sdk_marshal, indentation = 0) + result = compile 'templates/azure/ansible/sdktypes/property_to_sdkobject.erb', 1 + indent result, indentation + end + + def build_property_inline_response_format(property, sdk_operation, indentation = 12) + template = get_custom_template_path(property.inline_custom_response_format) + template ||= 'templates/azure/ansible/sdktypes/property_inline_response_format.erb' + result = compile template, 1 + indent result, indentation + end + end + + end + end + end +end diff --git a/provider/azure/ansible/sub_template.rb b/provider/azure/ansible/sub_template.rb new file mode 100644 index 000000000000..ed85c620a896 --- /dev/null +++ b/provider/azure/ansible/sub_template.rb @@ -0,0 +1,26 @@ +# Copyright 2019 Microsoft Corp. +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +module Provider + module Azure + module Ansible + module SubTemplate + def build_multiline_method_call(call_prefix, args, call_postfix, indentation = 0) + multiline_args = args.join(",\n#{' ' * call_prefix.length}") + result = "#{call_prefix}#{multiline_args}#{call_postfix}" + indent result, indentation + end + end + end + end +end \ No newline at end of file diff --git a/provider/azure/ansible_extension.rb b/provider/azure/ansible_extension.rb new file mode 100644 index 000000000000..ec8e63713be6 --- /dev/null +++ b/provider/azure/ansible_extension.rb @@ -0,0 +1,85 @@ +# Copyright 2019 Microsoft Corp. +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +require 'provider/azure/ansible/config' +require 'provider/azure/ansible/helpers' +require 'provider/azure/ansible/sub_template' +require 'provider/azure/ansible/sdk/sdk_marshal_descriptor' +require 'provider/azure/ansible/sdk/helpers' +require 'provider/azure/ansible/module/sub_template' +require 'provider/azure/ansible/sdk/sub_template' +require 'provider/azure/ansible/example/helpers' +require 'provider/azure/ansible/example/sub_template' + +require 'provider/azure/ansible/resource_override' +require 'provider/azure/ansible/property_override' + +module Provider + module Azure + module AnsibleExtension + include Provider::Azure::Ansible::Helpers + include Provider::Azure::Ansible::SDK::Helpers + include Provider::Azure::Ansible::SubTemplate + include Provider::Azure::Ansible::Module::SubTemplate + include Provider::Azure::Ansible::SDK::SubTemplate + include Provider::Azure::Ansible::Example::Helpers + include Provider::Azure::Ansible::Example::SubTemplate + + def initialize(config, api, start_time) + super + @provider = 'ansible' + end + + def azure_python_type(prop) + return 'raw' if prop.is_a? Api::Azure::Type::ResourceReference + return 'list' if prop.is_a? Api::Azure::Type::Tags + python_type prop + end + + def azure_module_name(object) + "azure_rm_#{object.name.downcase}" + end + + def azure_generate_resource(data) + target_folder = data.output_folder + path = File.join(target_folder, "lib/ansible/modules/cloud/azure/#{azure_module_name(data.object)}.py") + data.generate( + data.object.template || 'templates/azure/ansible/resource.erb', + path, + self + ) + end + + def azure_generate_resource_tests(data) + return unless data.object.has_tests + return if data.object.inttests.empty? + + name = azure_module_name(data.object) + target_folder = data.output_folder + target_folder = File.join(target_folder, "test/integration/targets/#{name}") + + data.generate('templates/azure/ansible/integration_test.erb', File.join(target_folder, 'tasks/main.yml'), self) + data.generate('templates/azure/ansible/test/meta.erb', File.join(target_folder, 'meta/main.yml'), self) + data.generate('templates/azure/ansible/test/aliases.erb', File.join(target_folder, 'aliases'), self) + end + + def azure_compile_datasource(data) + target_folder = data.output_folder + name = "#{azure_module_name(data.object)}_info" + path = File.join(target_folder, "lib/ansible/modules/cloud/azure/#{name}.py") + data.generate('templates/azure/ansible/info.erb', path, self) + end + + end + end +end diff --git a/provider/azure/config_extension.rb b/provider/azure/config_extension.rb new file mode 100644 index 000000000000..6d86268c2d1b --- /dev/null +++ b/provider/azure/config_extension.rb @@ -0,0 +1,31 @@ +# Copyright 2019 Microsoft Corp. +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +module Provider + module Azure + module ConfigExtension + # The configuration file path, this should be the root path relative to + # all API definitions, overrides and examples. + attr_reader :config_file + + # Azure-extended Provider::Config::validate + def azure_validate + end + + # Azure-extended Provider::Config::parse + def azure_parse(cfg_file) + @config_file = cfg_file + end + end + end +end diff --git a/provider/azure/core.rb b/provider/azure/core.rb new file mode 100644 index 000000000000..841bd4a336c9 --- /dev/null +++ b/provider/azure/core.rb @@ -0,0 +1,64 @@ +# Copyright 2019 Microsoft Corp. +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +require 'provider/azure/example/example' + +module Provider + module Azure + module Core + + def get_example_by_reference(reference) + get_example_by_names reference.example, reference.product + end + + def get_example_by_names(example_name, product_name = nil) + resource_dir = get_resource_dir(File.dirname(@config.config_file)) + resource_dir = File.join(File.dirname(resource_dir), product_name) unless product_name.nil? + spec_dir = get_spec_dir(resource_dir) + example_yaml = File.join(spec_dir, 'examples', @provider, "#{example_name}.yaml") + example = Google::YamlValidator.parse(File.read(example_yaml)) + raise "#{example_yaml}(#{example.class}) is not Provider::Azure::Example" unless example.is_a?(Provider::Azure::Example) + example.validate + example + end + + # The folder structure generated by autorest.cli is different than the magic-module-spec folder + # structure, where autorest.cli has one more subdirectory named after `package-YY-MM-DD` for each + # resource, which contains the content otherwise had been placed directly under the resource_dir + # in magic-module-spec. + # + # Given a `config_dir` and return the `resource_dir` taking *package-xxx* subdirectory + # into consideration. + def get_resource_dir(config_dir) + return File.dirname(config_dir) if /^package-[\d-]+(-preview)?$/ =~ File.basename(config_dir) + + config_dir + end + + # Given a `resource_dir` and return the `config_dir` taking *package-xxx* subdirectory + # into consideration. + # NOTE: currently we assume there is at most only one subdirectory. + def get_spec_dir(resource_dir) + potential_subdir = Dir.glob(File.join(resource_dir, 'package-[0-9-]*'))[0] + potential_subdir.nil? ? resource_dir : potential_subdir + end + + def get_custom_template_path(template_path) + return nil if template_path.nil? + spec_dir = File.dirname(@config.config_file) + File.join(spec_dir, template_path) + end + + end + end +end diff --git a/provider/azure/example/example.rb b/provider/azure/example/example.rb new file mode 100644 index 000000000000..3fc57e7721d0 --- /dev/null +++ b/provider/azure/example/example.rb @@ -0,0 +1,49 @@ +# Copyright 2019 Microsoft Corp. +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +require 'provider/core' +require 'api/object' + +module Provider + module Azure + class ExampleReference < Api::Object + attr_reader :product + attr_reader :example + + def validate + super + check :product, type: ::String + check :example, type: ::String, required: true + end + end + + class Example < Api::Object + attr_reader :resource + attr_reader :description + attr_reader :prerequisites + attr_reader :properties + attr_reader :property_check_excludes + attr_reader :property_check_includes + + def validate + super + check :resource, type: ::String, required: true + check :description, type: ::String + check :prerequisites, type: ::Array, item_type: ExampleReference + check :properties, type: ::Hash, required: true + check :property_check_excludes, type: ::Array, item_type: ::String, required: false + check :property_check_includes, type: ::Array, item_type: ::String, required: false + end + end + end +end diff --git a/provider/azure/terraform/acctest/sub_template.rb b/provider/azure/terraform/acctest/sub_template.rb new file mode 100644 index 000000000000..c7b88c38929e --- /dev/null +++ b/provider/azure/terraform/acctest/sub_template.rb @@ -0,0 +1,31 @@ +# Copyright 2019 Microsoft Corp. +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +module Provider + module Azure + module Terraform + module AccTest + module SubTemplate + + def build_acctest_parameters_from_schema(sdk_op_def, properties, object, indentation = 8) + compile_template 'templates/azure/terraform/acctest/parameters_from_schema.erb', + indentation: indentation, + sdk_op_def: sdk_op_def, + properties: properties, + object: object + end + end + end + end + end +end diff --git a/provider/azure/terraform/config.rb b/provider/azure/terraform/config.rb new file mode 100644 index 000000000000..3bdf674d21b7 --- /dev/null +++ b/provider/azure/terraform/config.rb @@ -0,0 +1,36 @@ +# Copyright 2019 Microsoft Corp. +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +require 'provider/config' + +module Provider + module Azure + module Terraform + + class Config < Provider::Config + def provider + Provider::Terraform + end + + def resource_override + Provider::Azure::Terraform::ResourceOverride + end + + def property_override + Provider::Azure::Terraform::PropertyOverride + end + end + + end + end +end diff --git a/provider/azure/terraform/custom_code.rb b/provider/azure/terraform/custom_code.rb new file mode 100644 index 000000000000..800f319d1b54 --- /dev/null +++ b/provider/azure/terraform/custom_code.rb @@ -0,0 +1,39 @@ +# Copyright 2019 Microsoft Corp. +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +require 'provider/terraform/custom_code' + +module Provider + module Azure + module Terraform + + class CustomCode < Provider::Terraform::CustomCode + # This code is run after the Read call succeeds and before setting + # schema attributes. It's placed in the Read function directly + # without modification. + attr_reader :post_read + + # This code snippet will be put after all CRUD and expand/flatten functions + # of a Terraform resource without modification. + attr_reader :extra_functions + + def validate + super + check :post_read, type: ::String + check :extra_functions, type: ::String + end + end + + end + end +end diff --git a/provider/azure/terraform/example/helpers.rb b/provider/azure/terraform/example/helpers.rb new file mode 100644 index 000000000000..1fd18b3f803b --- /dev/null +++ b/provider/azure/terraform/example/helpers.rb @@ -0,0 +1,96 @@ +# Copyright 2019 Microsoft Corp. +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +require 'api/object' + +module Provider + module Azure + module Terraform + class Example < Api::Object + module Helpers + def get_example_properties_to_check(example_name, object) + request = object.azure_sdk_definition.read.request + param_props = object.all_user_properties.select{|p| p.azure_sdk_references.any?{|ref| request.has_key?(ref)}} + params = param_props.map{|p| p.name.underscore}.to_set + + example = get_example_by_names(example_name) + example_props = example.properties + .reject do |pn, pv| + params.include?(pn) || pn == 'location' + end + .transform_values do |v| + v.is_a?(String) && !v.match(/\$\{.+\}/).nil? ? :AttrSet : v + end + includes = example.property_check_includes + excludes = example.property_check_excludes + flat_properties = flatten_example_properties_to_check(example_props, true) + return include_example_properties_to_check(flat_properties, includes) if includes + return exlcude_example_properties_to_check(flat_properties, excludes) if excludes + return flat_properties + end + + def flatten_example_properties_to_check(properties, has_nested_item) + return properties unless has_nested_item + flat_properties = Hash.new + has_nested_item = false + properties.each do |pn, pv| + if pv.is_a?(Hash) + flat_properties["#{pn}.%"] = pv.length if !pn.include?('.') + pv.each do |key, val| + flat_properties["#{pn}.#{key}"] = val if is_valid_value_for_check(val) + has_nested_item = true if val.is_a?(Hash) || val.is_a?(Array) + end + elsif pv.is_a?(Array) + flat_properties["#{pn}.#"] = pv.length + pv.each_index do |ind| + flat_properties["#{pn}.#{ind}"] = pv[ind] if is_valid_value_for_check(pv[ind]) + has_nested_item = true if pv[ind].is_a?(Hash) || pv[ind].is_a?(Array) + end + else + flat_properties[pn] = pv + end + end + return flatten_example_properties_to_check(flat_properties, has_nested_item) + end + + def is_valid_value_for_check(value) + return !value.is_a?(String) || value.nil? || value[0] != '$' + end + + def exlcude_example_properties_to_check(properties_to_check, excludes) + return properties_to_check unless excludes + single_excludes = excludes.select{|elem| elem[-1] != '*'} + recursive_excludes = excludes.select{|elem| elem[-1] == '*'} + properties_to_check.select{|key, val| + in_recursive_excludes = false + recursive_excludes.each{|k| in_recursive_excludes ||= k[0..k.length-2] == key[0..k.length-2]} + !single_excludes.include?(key) && !in_recursive_excludes + } + end + + def include_example_properties_to_check(properties_to_check, includes) + return properties_to_check unless includes + single_includes = includes.select{|elem| elem[-1] != '*'} + recursive_includes = includes.select{|elem| elem[-1] == '*'} + filterd_properties = properties_to_check.select{|key, val| + in_recursive_includes = false + recursive_includes.each{|k| in_recursive_includes ||= k[0..k.length-2] == key[0..k.length-2]} + single_includes.include?(key) || in_recursive_includes + } + return filterd_properties + end + end + end + end + end +end diff --git a/provider/azure/terraform/example/sub_template.rb b/provider/azure/terraform/example/sub_template.rb new file mode 100644 index 000000000000..7843923937ed --- /dev/null +++ b/provider/azure/terraform/example/sub_template.rb @@ -0,0 +1,142 @@ +# Copyright 2019 Microsoft Corp. +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +require 'api/object' + +module Provider + module Azure + module Terraform + class Example < Api::Object + module SubTemplate + def build_test_hcl_from_example(example_name) + random_vars = Array.new + hcl = build_hcl_from_example(nil, example_name, "test", {}, random_vars, true) + return hcl, random_vars + end + + def build_documentation_hcl_from_example(example_name, name_hints) + build_hcl_from_example(nil, example_name, "example", name_hints, [], true) + end + + def build_hcl_from_example(product_name, example_name, id_hint, name_hints, random_vars, with_dependencies = false) + hcl_raw = compile_template 'templates/azure/terraform/example/example_hcl.erb', + example: get_example_by_names(example_name, product_name), + random_variables: random_vars, + resource_id_hint: id_hint, + name_hints: name_hints, + with_dependencies: with_dependencies + context = ExampleContextBinding.new(id_hint, name_hints, random_vars) + compile_string context.get_binding, hcl_raw + end + + def build_documentation_import_resource_id(object, example_reference) + compile_template 'templates/azure/terraform/example/import_resource_id.erb', + name_hints: example_reference.resource_name_hints, + object: object + end + + def build_hcl_properties(properties_hash, indentation = 2) + compile_template 'templates/azure/terraform/example/hcl_properties.erb', + properties: properties_hash, + indentation: indentation + end + end + + private + + class ExampleContextBinding + attr_reader :my_binding + attr_reader :name_hints + attr_reader :random_variables + + def initialize(resource_id_hint, name_hints, random_vars) + @my_binding = binding + @my_binding.local_variable_set(:resource_id_hint, resource_id_hint) + @name_hints = name_hints + @random_variables = random_vars + end + + def get_binding() + @my_binding + end + + def get_resource_name(name_hint, postfix) + return name_hints[name_hint] if name_hints.has_key?(name_hint) + @random_variables <<= RandomizedVariable.new(:AccDefaultInt) + "acctest#{postfix}-#{@random_variables.last.format_string}" + end + + def get_location() + return name_hints["location"] if name_hints.has_key?("location") + @random_variables <<= RandomizedVariable.new(:AccLocation) + @random_variables.last.format_string + end + + def get_storage_account_name() + return name_hints["storageAccounts"] if name_hints.has_key?("storageAccounts") + @random_variables <<= RandomizedVariable.new(:AccStorageAccount) + "acctestsa#{@random_variables.last.format_string}" + end + + def get_batch_account_name() + return name_hints["batchAccounts"] if name_hints.has_key?("batchAccounts") + @random_variables <<= RandomizedVariable.new(:AccBatchAccount) + "acctestba#{@random_variables.last.format_string}" + end + end + + class RandomizedVariable + attr_reader :variable_name + attr_reader :parameter_name + attr_reader :go_type + attr_reader :create_expression + attr_reader :format_string + attr_reader :declare_order + + def initialize(type) + case type + when :AccDefaultInt + @variable_name = "ri" + @parameter_name = "rInt" + @go_type = "int" + @create_expression = "data.RandomInteger" + @format_string = "%d" + @declare_order = 0 + when :AccStorageAccount + @variable_name = "rs" + @parameter_name = "rString" + @go_type = "string" + @create_expression = "strings.ToLower(acctest.RandString(11))" + @format_string = "%s" + @declare_order = 1 + when :AccBatchAccount + @variable_name = "rs" + @parameter_name = "rString" + @go_type = "string" + @create_expression = "strings.ToLower(acctest.RandString(11))" + @format_string = "%s" + @declare_order = 2 + when :AccLocation + @variable_name = @parameter_name = "location" + @go_type = "string" + @create_expression = "data.Locations.Primary" + @format_string = "%s" + @declare_order = 3 + end + end + end + + end + end + end +end diff --git a/provider/azure/terraform/helpers.rb b/provider/azure/terraform/helpers.rb new file mode 100644 index 000000000000..c2d9ddabd274 --- /dev/null +++ b/provider/azure/terraform/helpers.rb @@ -0,0 +1,41 @@ +# Copyright 2019 Microsoft Corp. +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +module Provider + module Azure + module Terraform + module Helpers + def get_property_value(obj, prop_name, default_value) + return default_value unless obj.instance_variable_defined?("@#{prop_name}") + obj.instance_variable_get("@#{prop_name}") + end + + def order_azure_properties(properties, data_source_input = []) + special_props = properties.select{|p| p.name == 'name' || p.name == 'location' || p.name == 'resourceGroupName' || p.name == 'resourceGroup' || data_source_input.include?(p)} + other_props = properties.reject{|p| p.name == 'name' || p.name == 'location' || p.name == 'resourceGroupName' || p.name == 'resourceGroup' || p.name == 'tags' || data_source_input.include?(p)} + sorted_special = special_props.sort_by{|p| p.name == 'location' ? 2 : p.order } + sorted_other = data_source_input.empty? ? order_properties(other_props) : other_props.sort_by(&:name) + sorted_special + sorted_other + properties.select{|p| p.name == 'tags'} + end + + def is_tags_defined?(sdk_operation) + sdk_operation.response.has_key?('/tags') + end + + def go_field_name_of_tags(sdk_operation) + sdk_operation.response['/tags'].go_field_name + end + end + end + end +end diff --git a/provider/azure/terraform/property_override.rb b/provider/azure/terraform/property_override.rb new file mode 100644 index 000000000000..dbd4d481eeea --- /dev/null +++ b/provider/azure/terraform/property_override.rb @@ -0,0 +1,53 @@ +# Copyright 2019 Microsoft Corp. +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +require 'overrides/terraform/property_override' + +module Provider + module Azure + module Terraform + + class PropertyOverride < Overrides::Terraform::PropertyOverride + def self.attributes + super.concat(%i[ + name_in_logs + hide_from_schema + true_value + false_value + custom_schema_definition + custom_schema_get + custom_schema_set + custom_sdkfield_assign + custom_ef_func_name + ]) + end + + attr_reader(*attributes) + + def validate + super + check :name_in_logs, type: ::String + check :hide_from_schema, type: :boolean, default: false + check :true_value, type: [Symbol] + check :false_value, type: [Symbol] + check :custom_schema_definition, type: ::String + check :custom_schema_get, type: ::String + check :custom_schema_set, type: ::String + check :custom_sdkfield_assign, type: ::String + check :custom_ef_func_name, type: ::String + end + end + + end + end +end diff --git a/provider/azure/terraform/resource_override.rb b/provider/azure/terraform/resource_override.rb new file mode 100644 index 000000000000..827249b8a9d2 --- /dev/null +++ b/provider/azure/terraform/resource_override.rb @@ -0,0 +1,90 @@ +# Copyright 2019 Microsoft Corp. +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +require 'overrides/terraform/resource_override' +require 'api/azure/sdk_definition_override' + +module Provider + module Azure + module Terraform + + class DocumentExampleReference < Api::Object + attr_reader :title + attr_reader :example_name + attr_reader :resource_name_hints + + def validate + super + check :title, type: ::String, required: true + check :example_name, type: ::String, required: true + check_ext :resource_name_hints, type: ::Hash, key_type: ::String, item_type: ::String + end + end + + class DataSourceExampleReference < Api::Object + attr_reader :title + attr_reader :example_name + + def validate + super + check :title, type: ::String, required: true + check :example_name, type: ::String, required: true + end + end + + class AccTestDefinition < Api::Object + attr_reader :name + attr_reader :steps + + def validate + super + @initialized = false + + check :name, type: ::String, required: true + check :steps, type: ::Array, item_type: ::String, required: true + end + end + + class ResourceOverride < Overrides::Terraform::ResourceOverride + def self.attributes + super.concat(%i[ + azure_sdk_definition + name_in_logs + document_examples + acctests + datasource_example_outputs + ]) + end + + attr_reader(*attributes) + + def validate + super + check :azure_sdk_definition, type: Api::Azure::SDKDefinitionOverride + check :name_in_logs, type: ::String + check :document_examples, type: ::Array, item_type: DocumentExampleReference + check :acctests, type: ::Array, item_type: AccTestDefinition + check :datasource_example_outputs, type: ::Hash + end + + def apply(_resource) + filter_azure_sdk_language _resource, "go" + merge_azure_sdk_definition _resource, @azure_sdk_definition + @azure_sdk_definition = nil + super + end + end + + end + end +end diff --git a/provider/azure/terraform/schema.rb b/provider/azure/terraform/schema.rb new file mode 100644 index 000000000000..184c440bffcc --- /dev/null +++ b/provider/azure/terraform/schema.rb @@ -0,0 +1,142 @@ +# Copyright 2019 Microsoft Corp. +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +module Provider + module Azure + module Terraform + module Schema + + def go_type(property) + if property.is_a?(Api::Type::Boolean) + 'bool' + elsif property.is_a?(Api::Type::Enum) || + property.is_a?(Api::Type::String) + 'string' + elsif property.is_a?(Api::Type::Integer) + 'int' + elsif property.is_a?(Api::Type::Double) + 'float64' + elsif property.is_a?(Api::Type::KeyValuePairs) + 'map[string]interface{}' + elsif property.is_a?(Api::Type::Array) || + property.is_a?(Api::Type::NestedObject) + '[]interface{}' + else + 'interface{}' + end + end + + def go_empty_value(property) + if property.is_a?(Api::Type::Enum) || property.is_a?(Api::Type::String) + '""' + else + 'nil' + end + end + + def expand_func(property) + expand_funcs[property.class] + end + + def expand_funcs + { + Api::Type::Boolean => 'utils.Bool', + Api::Type::String => 'utils.String', + Api::Type::Integer => 'utils.Int', + Api::Type::Double => 'utils.Float', + Api::Azure::Type::Location => "utils.String", + Api::Azure::Type::Tags => 'tags.Expand', + Api::Azure::Type::ResourceReference => "utils.String" + } + end + + def schema_property_template(property, is_data_source) + return property.custom_schema_definition unless get_property_value(property, "custom_schema_definition", nil).nil? + if property.is_a?(Api::Azure::Type::ResourceGroupName) + !is_data_source ? 'templates/azure/terraform/schemas/resource_group_name.erb' : 'templates/azure/terraform/schemas/datasource_resource_group_name.erb' + elsif property.is_a?(Api::Azure::Type::Location) + !is_data_source ? 'templates/azure/terraform/schemas/location.erb' : 'templates/azure/terraform/schemas/datasource_location.erb' + elsif property.is_a?(Api::Azure::Type::Tags) + !is_data_source ? 'templates/azure/terraform/schemas/tags.erb' : 'templates/azure/terraform/schemas/datasource_tags.erb' + elsif property.is_a?(Api::Type::Boolean) || + property.is_a?(Api::Type::Enum) || + property.is_a?(Api::Type::String) || + property.is_a?(Api::Type::Integer) || + property.is_a?(Api::Type::Double) || + property.is_a?(Api::Type::Array) || + property.is_a?(Api::Type::KeyValuePairs) || + property.is_a?(Api::Type::NestedObject) || + property.is_a?(Api::Azure::Type::ISO8601DateTime) || + property.is_a?(Api::Azure::Type::ISO8601Duration) + 'templates/azure/terraform/schemas/primitive.erb' + else + 'templates/azure/terraform/schemas/unsupport.erb' + end + end + + def schema_property_get_template(property) + return property.custom_schema_get unless get_property_value(property, "custom_schema_get", nil).nil? + return 'templates/azure/terraform/schemas/hide_from_schema.erb' if get_property_value(property, "hide_from_schema", false) + if property.is_a?(Api::Azure::Type::Location) + 'templates/azure/terraform/schemas/location_get.erb' + elsif property.is_a?(Api::Azure::Type::BooleanEnum) + 'templates/azure/terraform/schemas/boolean_enum_get.erb' + elsif property.is_a?(Api::Type::Boolean) || + property.is_a?(Api::Type::Enum) || + property.is_a?(Api::Type::String) || + property.is_a?(Api::Type::Integer) || + property.is_a?(Api::Type::Double) || + property.is_a?(Api::Type::Array) || + property.is_a?(Api::Type::KeyValuePairs) || + property.is_a?(Api::Type::NestedObject) + 'templates/azure/terraform/schemas/basic_get.erb' + else + 'templates/azure/terraform/schemas/unsupport.erb' + end + end + + def schema_property_set_template(property) + return property.custom_schema_set unless get_property_value(property, "custom_schema_set", nil).nil? + return 'templates/azure/terraform/schemas/hide_from_schema.erb' if get_property_value(property, "hide_from_schema", false) + if property.is_a?(Api::Azure::Type::Location) + 'templates/azure/terraform/schemas/location_set.erb' + elsif property.is_a?(Api::Azure::Type::Tags) + 'templates/azure/terraform/schemas/tags_set.erb' + elsif property.is_a?(Api::Azure::Type::BooleanEnum) + 'templates/azure/terraform/schemas/boolean_enum_set.erb' + elsif property.is_a?(Api::Azure::Type::ISO8601DateTime) || property.is_a?(Api::Azure::Type::ISO8601Duration) + 'templates/azure/terraform/schemas/datetime_and_duration_set.erb' + elsif property.is_a?(Api::Type::Boolean) || + property.is_a?(Api::Type::Enum) || + property.is_a?(Api::Type::String) || + property.is_a?(Api::Type::Integer) || + property.is_a?(Api::Type::Double) + 'templates/azure/terraform/schemas/basic_set.erb' + elsif property.is_a?(Api::Type::Array) || + property.is_a?(Api::Type::NestedObject) || + property.is_a?(Api::Type::KeyValuePairs) + return 'templates/azure/terraform/schemas/string_array_set.erb' if property.is_a?(Api::Type::Array) && (property.item_type.is_a?(Api::Type::String) || + property.item_type == "Api::Type::String" || property.item_type == "Api::Azure::Type::ResourceReference") + return 'templates/azure/terraform/schemas/integer_array_set.erb' if property.is_a?(Api::Type::Array) && property.item_type == "Api::Type::Integer" + return 'templates/azure/terraform/schemas/float_array_set.erb' if property.is_a?(Api::Type::Array) && property.item_type == "Api::Type::Double" + return 'templates/azure/terraform/schemas/key_value_pairs_set.erb' if property.is_a?(Api::Type::KeyValuePairs) + 'templates/azure/terraform/schemas/flatten_set.erb' + else + 'templates/azure/terraform/schemas/unsupport.erb' + end + end + + end + end + end +end diff --git a/provider/azure/terraform/sdk/expand_flatten_descriptor.rb b/provider/azure/terraform/sdk/expand_flatten_descriptor.rb new file mode 100644 index 000000000000..72a98378ebc5 --- /dev/null +++ b/provider/azure/terraform/sdk/expand_flatten_descriptor.rb @@ -0,0 +1,63 @@ +# Copyright 2019 Microsoft Corp. +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +require 'provider/azure/terraform/sdk/sdk_type_definition_descriptor' + +module Provider + module Azure + module Terraform + module SDK + class ExpandFlattenDescriptor + attr_reader :func_name + attr_reader :property + attr_reader :sdkmarshal + + def initialize(property, sdkmarshal) + @property = property + @sdkmarshal = sdkmarshal + @func_name = @sdkmarshal.sdktype.go_type_name + end + + # This function compares the structure (hierarchical structure, declared type and name) of prop1 and prop2. + # The major target is to distinguish whether two complex properties could share the same expand/flatten (ef) function. + def equals?(other) + return false if @sdkmarshal.sdktype.go_type_name != other.sdkmarshal.sdktype.go_type_name + return false unless property_structure_equals? @property, other.property + true + end + + private + + def property_structure_equals?(prop1, prop2, verify_name = false) + return false if verify_name && prop1.name != prop2.name + return false if prop1.class.name != prop2.class.name + subprops1 = sub_properties(prop1).sort_by!(&:name) + subprops2 = sub_properties(prop2).sort_by!(&:name) + return false if subprops1.size != subprops2.size + subprops1.each_index do |i| + return false unless property_structure_equals?(subprops1[i], subprops2[i], true) + end + true + end + + def sub_properties(property) + return property.properties if property.is_a?(Api::Type::NestedObject) + return property.item_type.properties if property.is_a?(Api::Type::Array) && property.item_type.is_a?(Api::Type::NestedObject) + return property.value_type.properties if property.is_a?(Api::Type::Map) + [] + end + end + end + end + end +end diff --git a/provider/azure/terraform/sdk/helpers.rb b/provider/azure/terraform/sdk/helpers.rb new file mode 100644 index 000000000000..8e3787c5b940 --- /dev/null +++ b/provider/azure/terraform/sdk/helpers.rb @@ -0,0 +1,91 @@ +# Copyright 2019 Microsoft Corp. +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +require 'provider/azure/terraform/sdk/expand_flatten_descriptor' + +module Provider + module Azure + module Terraform + module SDK + module Helpers + def get_properties_matching_sdk_reference(properties, sdk_reference) + properties.select{|p| p.azure_sdk_references.include?(sdk_reference)}.sort_by{|p| [p.order, p.name]} + end + + def get_applicable_reference(references, typedefs) + references.each do |ref| + return ref if typedefs.has_key?(ref) + end + nil + end + + def get_sdk_typedef_by_references(references, typedefs) + ref = get_applicable_reference(references, typedefs) + return nil if ref.nil? + typedefs[ref] + end + + def property_to_sdk_field_assignment_template(property, sdk_type) + return property.custom_sdkfield_assign unless get_property_value(property, "custom_sdkfield_assign", nil).nil? + return 'templates/azure/terraform/schemas/hide_from_schema.erb' if get_property_value(property, "hide_from_schema", false) + return 'templates/azure/terraform/sdktypes/plain_var_field_assign.erb' if property.is_a?(Api::Azure::Type::BooleanEnum) + case sdk_type + when Api::Azure::SDKTypeDefinition::Integer32ArrayObject, Api::Azure::SDKTypeDefinition::Integer64ArrayObject + 'templates/azure/terraform/sdktypes/integer_array_field_assign.erb' + when Api::Azure::SDKTypeDefinition::FloatArrayObject + 'templates/azure/terraform/sdktypes/float_array_field_assign.erb' + when Api::Azure::SDKTypeDefinition::ISO8601DateTimeObject, Api::Azure::SDKTypeDefinition::ISO8601DurationObject + 'templates/azure/terraform/sdktypes/datetime_and_duration_field_assign.erb' + when Api::Azure::SDKTypeDefinition::BooleanObject, Api::Azure::SDKTypeDefinition::StringObject, + Api::Azure::SDKTypeDefinition::IntegerObject, Api::Azure::SDKTypeDefinition::FloatObject, + Api::Azure::SDKTypeDefinition::StringArrayObject, Api::Azure::SDKTypeDefinition::StringMapObject, + Api::Azure::SDKTypeDefinition::EnumArrayObject + 'templates/azure/terraform/sdktypes/expand_func_field_assign.erb' + when Api::Azure::SDKTypeDefinition::Integer32Object, Api::Azure::SDKTypeDefinition::Integer64Object + 'templates/azure/terraform/sdktypes/integer_field_assign.erb' + when Api::Azure::SDKTypeDefinition::EnumObject + 'templates/azure/terraform/sdktypes/enum_field_assign.erb' + when Api::Azure::SDKTypeDefinition::ComplexObject + return 'templates/azure/terraform/sdktypes/nested_object_field_assign.erb' if property.nil? + 'templates/azure/terraform/sdktypes/expand_func_field_assign.erb' + else + 'templates/azure/terraform/sdktypes/unsupport.erb' + end + end + + def property_to_schema_assignment_template(property, sdk_operation, api_path) + return 'templates/azure/terraform/sdktypes/primitive_schema_assign.erb' if property.is_a?(Api::Azure::Type::BooleanEnum) + sdk_type = sdk_operation.response[api_path] || sdk_operation.request[api_path] + case sdk_type + when Api::Azure::SDKTypeDefinition::BooleanObject, Api::Azure::SDKTypeDefinition::StringObject, + Api::Azure::SDKTypeDefinition::IntegerObject, Api::Azure::SDKTypeDefinition::Integer32Object, + Api::Azure::SDKTypeDefinition::Integer64Object, Api::Azure::SDKTypeDefinition::FloatObject, + Api::Azure::SDKTypeDefinition::StringArrayObject, Api::Azure::SDKTypeDefinition::StringMapObject, + Api::Azure::SDKTypeDefinition::ISO8601DateTimeObject, Api::Azure::SDKTypeDefinition::ISO8601DurationObject, + Api::Azure::SDKTypeDefinition::Integer32ArrayObject, Api::Azure::SDKTypeDefinition::Integer64ArrayObject, + Api::Azure::SDKTypeDefinition::FloatArrayObject + 'templates/azure/terraform/sdktypes/primitive_schema_assign.erb' + when Api::Azure::SDKTypeDefinition::EnumObject + 'templates/azure/terraform/sdktypes/enum_schema_assign.erb' + when Api::Azure::SDKTypeDefinition::ComplexObject + return 'templates/azure/terraform/sdktypes/nested_object_schema_assign.erb' if property.nil? + 'templates/azure/terraform/sdktypes/primitive_schema_assign.erb' + else + 'templates/azure/terraform/sdktypes/unsupport.erb' + end + end + end + end + end + end +end \ No newline at end of file diff --git a/provider/azure/terraform/sdk/sdk_marshal_descriptor.rb b/provider/azure/terraform/sdk/sdk_marshal_descriptor.rb new file mode 100644 index 000000000000..23308a121c0e --- /dev/null +++ b/provider/azure/terraform/sdk/sdk_marshal_descriptor.rb @@ -0,0 +1,52 @@ +# Copyright 2019 Microsoft Corp. +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +require 'provider/azure/terraform/sdk/sdk_type_definition_descriptor' + +module Provider + module Azure + module Terraform + module SDK + class MarshalDescriptor + attr_reader :package + attr_reader :resource + attr_reader :queue + attr_reader :sdktype + attr_reader :properties + + def initialize(package, resource, queue, sdktype, properties) + @package = package + @resource = resource + @queue = queue + @sdktype = sdktype + @properties = properties + end + + def clone(typedef_reference = nil, properties = nil) + sdktype = @sdktype.clone(typedef_reference) + MarshalDescriptor.new @package, @resource, @queue, sdktype, (properties || @properties) + end + + def enqueue(property, global_queue) + ef_desc = ExpandFlattenDescriptor.new(property, self) + exist = @queue.find{|q| q.equals?(ef_desc)} + global_exist = global_queue.find{|q| q.equals?(ef_desc)} + @queue << ef_desc if global_exist.nil? + global_queue << ef_desc if global_exist.nil? + (exist || ef_desc).func_name + end + end + end + end + end +end diff --git a/provider/azure/terraform/sdk/sdk_type_definition_descriptor.rb b/provider/azure/terraform/sdk/sdk_type_definition_descriptor.rb new file mode 100644 index 000000000000..45d2b0279aa0 --- /dev/null +++ b/provider/azure/terraform/sdk/sdk_type_definition_descriptor.rb @@ -0,0 +1,54 @@ +# Copyright 2019 Microsoft Corp. +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +module Provider + module Azure + module Terraform + module SDK + class TypeDefinitionDescriptor + attr_reader :operation + attr_reader :typedef_reference + + def initialize(operation, isRequest, reference = nil) + @operation = operation + @isRequest = isRequest + @typedef_reference = reference || (@isRequest ? '/' : '') + end + + def clone(typedef_reference = nil) + TypeDefinitionDescriptor.new @operation, @isRequest, (typedef_reference || @typedef_reference) + end + + def type_definitions + return @operation.request if @isRequest + @operation.response.has_key?(@typedef_reference) ? @operation.response : @operation.request + end + + def type_definition + type_definitions[@typedef_reference] + end + + def go_type_name + return type_definition.go_type_name unless type_definition.nil? + nil + end + + def go_field_name + return type_definition.go_field_name unless type_definition.nil? + nil + end + end + end + end + end +end diff --git a/provider/azure/terraform/sdk/sub_template.rb b/provider/azure/terraform/sdk/sub_template.rb new file mode 100644 index 000000000000..d1ad25febfd2 --- /dev/null +++ b/provider/azure/terraform/sdk/sub_template.rb @@ -0,0 +1,82 @@ +# Copyright 2019 Microsoft Corp. +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +module Provider + module Azure + module Terraform + module SDK + module SubTemplate + def build_schema_property_get(input, output, property, sdk_marshal, indentation = 0) + compile_template schema_property_get_template(property), + indentation: indentation, + input_var: input, + output_var: output, + sdk_marshal: sdk_marshal, + property: property, + prop_name: property.name.underscore + end + + def build_schema_property_set(input, output, property, sdk_marshal, indentation = 0) + compile_template schema_property_set_template(property), + indentation: indentation, + input_var: input, + output_var: output, + property: property, + sdk_marshal: sdk_marshal, + prop_name: property.name.underscore + end + + def build_sdk_field_assignment(property, sdk_marshal, in_structure = true) + compile_template property_to_sdk_field_assignment_template(property, sdk_marshal.sdktype.type_definition), + property: property, + sdk_marshal: sdk_marshal, + in_structure: in_structure + end + + def build_property_to_sdk_object(sdk_marshal, indentation = 4, include_empty = false) + compile_template 'templates/azure/terraform/sdktypes/property_to_sdkobject.erb', + indentation: indentation, + sdk_marshal: sdk_marshal, + include_empty: include_empty + end + + def build_property_to_sdk_object_empty_sensitive(sdk_marshal, indentation = 4) + compile_template 'templates/azure/terraform/sdktypes/property_to_sdkobject_empty_sensitive.erb', + indentation: indentation, + sdk_marshal: sdk_marshal + end + + def build_schema_assignment(input, output, property, sdk_marshal) + compile_template property_to_schema_assignment_template(property, sdk_marshal.sdktype.operation, sdk_marshal.sdktype.typedef_reference), + input: input, + output: output, + sdk_marshal: sdk_marshal + end + + def build_sdk_object_to_property(input, output, sdk_marshal, indentation = 4) + compile_template 'templates/azure/terraform/sdktypes/sdkobject_to_property.erb', + indentation: indentation, + input: input, + output: output, + sdk_marshal: sdk_marshal + end + + def build_sdk_func_invocation(sdk_op_def) + compile_template 'templates/azure/terraform/sdk/function_invocation.erb', + sdk_op_def: sdk_op_def + end + end + end + end + end +end diff --git a/provider/azure/terraform/sub_template.rb b/provider/azure/terraform/sub_template.rb new file mode 100644 index 000000000000..de39c50cd3c4 --- /dev/null +++ b/provider/azure/terraform/sub_template.rb @@ -0,0 +1,77 @@ +# Copyright 2019 Microsoft Corp. +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +module Provider + module Azure + module Terraform + + module SubTemplate + def azure_compile_template(template, data) + indent template, data[:indentation] || 0 + end + + def build_azure_id_parser(sdk_op_def, object, indentation = 4) + compile_template 'templates/azure/terraform/sdk/azure_id_parser.erb', + indentation: indentation, + sdk_op_def: sdk_op_def, + object: object + end + + def build_errorf_with_resource_name(format_string, include_error, sdk_op_def, properties, object) + compile_template 'templates/azure/terraform/sdk/errorf_with_resource_name.erb', + format_string: format_string, + include_error: include_error, + sdk_op_def: sdk_op_def, + properties: properties, + object: object + end + + def build_azure_schema_property(property, object, indentation = 0, data_source_input = []) + compile_template schema_property_template(property, !data_source_input.empty?), + indentation: indentation, + prop_name: property.name.underscore, + property: property, + data_source_input: data_source_input, + object: object + end + + # Transforms a Cloud API representation of a property into a Terraform + # schema representation. + def build_azure_flatten_method(ef_desc) + compile_template 'templates/azure/terraform/flatten_property_method.erb', + descriptor: ef_desc + end + + # Transforms a Terraform schema representation of a property into a + # representation used by the Cloud API. + def build_azure_expand_method(ef_desc) + compile_template 'templates/azure/terraform/expand_property_method.erb', + descriptor: ef_desc + end + + def build_azure_property_documentation(property, data_source_input = []) + compile_template 'templates/azure/terraform/property_documentation.erb', + property: property, + data_source_input: data_source_input + end + + def build_azure_nested_property_documentation(property, data_source_input = []) + compile_template 'templates/azure/terraform/nested_property_documentation.erb', + property: property, + data_source_input: data_source_input + end + end + + end + end +end diff --git a/provider/azure/terraform_extension.rb b/provider/azure/terraform_extension.rb new file mode 100644 index 000000000000..276202785b02 --- /dev/null +++ b/provider/azure/terraform_extension.rb @@ -0,0 +1,116 @@ +# Copyright 2019 Microsoft Corp. +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +require 'provider/azure/terraform/config' +require 'provider/azure/terraform/custom_code' +require 'provider/azure/terraform/helpers' +require 'provider/azure/terraform/schema' +require 'provider/azure/terraform/sub_template' +require 'provider/azure/terraform/sdk/sdk_type_definition_descriptor' +require 'provider/azure/terraform/sdk/sdk_marshal_descriptor' +require 'provider/azure/terraform/sdk/expand_flatten_descriptor' +require 'provider/azure/terraform/sdk/sub_template' +require 'provider/azure/terraform/sdk/helpers' +require 'provider/azure/terraform/example/sub_template' +require 'provider/azure/terraform/example/helpers' +require 'provider/azure/terraform/acctest/sub_template' + +require 'provider/azure/terraform/resource_override' +require 'provider/azure/terraform/property_override' + +module Provider + module Azure + module TerraformExtension + include Provider::Azure::Terraform::Helpers + include Provider::Azure::Terraform::Schema + include Provider::Azure::Terraform::SubTemplate + include Provider::Azure::Terraform::SDK::SubTemplate + include Provider::Azure::Terraform::SDK::Helpers + include Provider::Azure::Terraform::Example::SubTemplate + include Provider::Azure::Terraform::Example::Helpers + include Provider::Azure::Terraform::AccTest::SubTemplate + + def initialize(config, api, start_time) + super + @provider = 'terraform' + end + + def azure_tf_types(map) + map[Api::Azure::Type::ResourceReference] = 'schema.TypeString' + map[Api::Azure::Type::BooleanEnum] = 'schema.TypeBool' + map[Api::Azure::Type::ISO8601DateTime] = 'schema.TypeString' + map[Api::Azure::Type::ISO8601Duration] = 'schema.TypeString' + map['Api::Azure::Type::ResourceReference'] = 'schema.TypeString' + map['Api::Type::Integer'] = 'schema.TypeInt' + map['Api::Type::Double'] = 'schema.TypeFloat' + map + end + + def azure_generate_resource(data) + rp_name = data.object.api_name.downcase + target_folder = File.join(data.output_folder, 'azurerm', 'internal', 'services', rp_name) + FileUtils.mkpath target_folder + + name = data.object.name.underscore + filepath = File.join(target_folder, "resource_arm_#{name}.go") + + data.generate('templates/azure/terraform/resource.erb', filepath, self) + generate_documentation(data) + end + + def azure_generate_documentation(data) + target_folder = data.output_folder + target_folder = File.join(target_folder, 'website', 'docs', 'r') + FileUtils.mkpath target_folder + + name = data.object.name.underscore + filepath = File.join(target_folder, "#{name}.html.markdown") + + data.generate('templates/azure/terraform/resource.html.markdown.erb', filepath, self) + end + + def azure_generate_resource_tests(data) + rp_name = data.object.api_name.downcase + target_folder = File.join(data.output_folder, 'azurerm', 'internal', 'services', rp_name, 'tests') + FileUtils.mkpath target_folder + + name = data.object.name.underscore + filepath = File.join(target_folder, "resource_arm_#{name}_test.go") + + data.product = data.product.name + data.resource_name = data.object.name.camelize(:upper) + data.generate('templates/azure/terraform/test_file.go.erb', filepath, self) + end + + def compile_datasource(data) + name = data.object.name.underscore + rp_name = data.object.api_name.downcase + + datasource_folder = File.join(data.output_folder, 'azurerm', 'internal', 'services', rp_name) + FileUtils.mkpath datasource_folder + filepath = File.join(datasource_folder, "data_source_#{name}.go") + data.generate('templates/azure/terraform/datasource.erb', filepath, self) + + datasource_test_folder = File.join(datasource_folder, 'tests') + FileUtils.mkpath datasource_test_folder + filepath = File.join(datasource_test_folder, "data_source_#{name}_test.go") + data.generate('templates/azure/terraform/datasource_test.go.erb', filepath, self) + + datasource_doc_folder = File.join(data.output_folder, 'website', 'docs', 'd') + FileUtils.mkpath datasource_doc_folder + filepath = File.join(datasource_doc_folder, "#{name}.html.markdown") + data.generate('templates/azure/terraform/datasource.html.markdown.erb', filepath, self) + end + end + end +end diff --git a/provider/config.rb b/provider/config.rb index 81663658f0d7..d44f3b6ae337 100644 --- a/provider/config.rb +++ b/provider/config.rb @@ -14,11 +14,13 @@ require 'api/object' require 'compile/core' require 'overrides/runner' +require 'provider/azure/config_extension' module Provider # Settings for the provider class Config < Api::Object include Compile::Core + include Provider::Azure::ConfigExtension extend Compile::Core # Overrides for datasources @@ -65,6 +67,7 @@ def self.parse(cfg_file, api = nil, version_name = 'ga') config.resource_override, config.property_override) config.spread_api config, api, [], '' unless api.nil? + config.azure_parse cfg_file if $target_is_azure config.validate api.validate [api, config] @@ -84,6 +87,8 @@ def validate check :files, type: Provider::Config::Files check :overrides, type: Overrides::ResourceOverrides, default: Overrides::ResourceOverrides.new + + azure_validate if $target_is_azure end # Provides the API object to any type that requires, e.g. for validation diff --git a/provider/core.rb b/provider/core.rb index bf724b92ffd4..33e4d952e200 100644 --- a/provider/core.rb +++ b/provider/core.rb @@ -18,6 +18,7 @@ require 'pathname' require 'json' require 'overrides/runner' +require 'provider/azure/core' module Provider # Responsible for generating a file @@ -100,12 +101,12 @@ def generate(template, path, provider) # Files are often generated in parallel. # We can use thread-local variables to ensure that autogen checking # stays specific to the file each thred represents. - raise "#{path} missing autogen" unless Thread.current[:autogen] + raise "#{path} missing autogen" unless Thread.current[:autogen] unless $target_is_azure old_file_chmod_mode = File.stat(template).mode FileUtils.chmod(old_file_chmod_mode, path) - format_output_file(path) + format_output_file(path) unless $target_is_azure end private @@ -133,6 +134,7 @@ def relative_path(target, base) # such as compiling and including files, formatting data, etc. class Core include Compile::Core + include Provider::Azure::Core def initialize(config, api, start_time) @config = config diff --git a/provider/terraform.rb b/provider/terraform.rb index efed0ee829e4..8e5ecf525c98 100644 --- a/provider/terraform.rb +++ b/provider/terraform.rb @@ -21,6 +21,8 @@ require 'overrides/terraform/property_override' require 'provider/terraform/sub_template' require 'google/golang_utils' +require 'azure/golang_utils' +require 'provider/azure/terraform_extension' module Provider # Code generator for Terraform Resources that manage Google Cloud Platform @@ -29,6 +31,8 @@ class Terraform < Provider::AbstractCore include Provider::Terraform::Import include Provider::Terraform::SubTemplate include Google::GolangUtils + include ::Azure::GolangUtils + include Provider::Azure::TerraformExtension # FileTemplate with Terraform specific fields class TerraformFileTemplate < Provider::FileTemplate @@ -70,7 +74,7 @@ def namespace_property_from_object(property, object) # Converts between the Magic Modules type of an object and its type in the # TF schema def tf_types - { + azure_tf_types({ Api::Type::Boolean => 'schema.TypeBool', Api::Type::Double => 'schema.TypeFloat', Api::Type::Integer => 'schema.TypeInt', @@ -85,7 +89,7 @@ def tf_types Api::Type::KeyValuePairs => 'schema.TypeMap', Api::Type::Map => 'schema.TypeSet', Api::Type::Fingerprint => 'schema.TypeString' - } + }) end def updatable?(resource, properties) @@ -131,6 +135,8 @@ def titlelize_property(property) # per resource. The resource.erb template forms the basis of a single # GCP Resource on Terraform. def generate_resource(data) + # Azure Switch + return azure_generate_resource data if $target_is_azure dir = data.version == 'beta' ? 'google-beta' : 'google' target_folder = File.join(data.output_folder, dir) @@ -143,6 +149,8 @@ def generate_resource(data) end def generate_documentation(data) + # Azure Switch + return azure_generate_documentation data if $target_is_azure target_folder = data.output_folder target_folder = File.join(target_folder, 'website', 'docs', 'r') FileUtils.mkpath target_folder @@ -155,6 +163,8 @@ def generate_documentation(data) end def generate_resource_tests(data) + # Azure Switch + return azure_generate_resource_tests data if $target_is_azure return if data.object.examples .reject(&:skip_test) .reject do |e| diff --git a/provider/terraform/sub_template.rb b/provider/terraform/sub_template.rb index 6f9fe222ebbe..3a66750cbb1c 100644 --- a/provider/terraform/sub_template.rb +++ b/provider/terraform/sub_template.rb @@ -66,6 +66,9 @@ def build_nested_property_documentation(property) private def autogen_notice_contrib + # Azure Switch + return ['Please read more about how to change this file at', + 'https://github.com/Azure/magic-module-specs'] if $target_is_azure ['Please read more about how to change this file in', '.github/CONTRIBUTING.md.'] end @@ -77,6 +80,7 @@ def autogen_notice_text(line) def compile_template(template_file, data) ctx = binding data.each { |name, value| ctx.local_variable_set(name, value) } + return azure_compile_template(compile_file(ctx, template_file).join("\n"), data) if $target_is_azure compile_file(ctx, template_file).join("\n") end end diff --git a/templates/azure/ansible/documentation.erb b/templates/azure/ansible/documentation.erb new file mode 100644 index 000000000000..796fa29b2ea6 --- /dev/null +++ b/templates/azure/ansible/documentation.erb @@ -0,0 +1,66 @@ +ANSIBLE_METADATA = {'metadata_version': '1.1', + 'status': ['preview'], + 'supported_by': 'community'} + + +DOCUMENTATION = ''' +--- +module: <%= azure_module_name(object) -%><%= is_data_source ? lines('_info') : "\n" -%> +version_added: "<%= @config.version_added.to_f -%>" +short_description: <%= lines(object.description) -%> +description: + - <%= lines(is_data_source ? object.description + '.' : "Create, update and delete instance of Azure #{object.name.titlecase}.") -%> + +<%= lines(word_wrap_for_yaml(to_yaml({ + 'options' => [ + input_properties.reject{|p| is_tags?(p) && !is_data_source}.map{|p| azure_documentation_for_property(p, object, is_data_source)}, + ({ + 'state' => { + 'description' => [ + "Assert the state of the #{object.name.titlecase}.", + "Use 'present' to create or update a #{object.name.titlecase} and 'absent' to delete it." + ], + 'default' => 'present', + 'choices' => ['present', 'absent'], + }, + } if !is_data_source) + ].flatten.compact.reduce({}, :merge), + 'notes' => ( + [ + ("API Reference: U(#{object.references.api})" if object.references.api), + object.references.guides.map { |guide, link| "#{guide}: U(#{link})" } + ].flatten.compact if object.references + ) +}, { :line_width => -1, :indentation => 4 }).split("\n"))) -%> + +extends_documentation_fragment: + - azure +<%= lines(' - azure_tags') if is_tags_defined?(object) && !is_data_source -%> + +author: + - <%= lines(@config.author) -%> +''' + +<% unless object.examples.empty? -%> +<% code = object.examples.map{|exref| lines(build_documentation_yaml_from_example(exref))} -%> +EXAMPLES = ''' +<%= lines(indent(code.join("\n"), 2)) -%> +''' +<% end -%> + +<% + output_hash = output_properties.map{|p| azure_returns_for_property(p, object)}.reduce({}, :merge) + output_hash = { + 'items' => { + 'description' => ['List of items.'], + 'returned' => 'always', + 'type' => 'complex', + 'contains' => output_hash + } + } if is_data_source +-%> +RETURN = ''' +<%= lines(word_wrap_for_yaml(to_yaml( + output_hash, + { :line_width => -1, :indentation => 4 }).split("\n"))) -%> +''' \ No newline at end of file diff --git a/templates/azure/ansible/example/example_deps_yaml.erb b/templates/azure/ansible/example/example_deps_yaml.erb new file mode 100644 index 000000000000..a2855da5714e --- /dev/null +++ b/templates/azure/ansible/example/example_deps_yaml.erb @@ -0,0 +1,6 @@ +<% if !example.prerequisites.nil? -%> +<% example.prerequisites.each do |pre_ex| -%> +<% _, yml = build_yaml_from_example(pre_ex.product, pre_ex.example, random_variables, nil, name_hints, nil) -%> +<%= lines(yml, 1) -%> +<% end -%> +<% end -%> \ No newline at end of file diff --git a/templates/azure/ansible/example/example_yaml.erb b/templates/azure/ansible/example/example_yaml.erb new file mode 100644 index 000000000000..11bf64be285b --- /dev/null +++ b/templates/azure/ansible/example/example_yaml.erb @@ -0,0 +1,4 @@ +- name: <%= lines(example.description + (!name_postfix.nil? && !name_postfix.empty? ? " -- #{name_postfix}" : '')) -%> + <%= example.resource -%>: +<%= lines(indent lines(build_yaml_properties(example.properties, 2)), 4) -%> +<%= lines(" register: #{register_name}") unless register_name.nil? || register_name.empty? -%> \ No newline at end of file diff --git a/templates/azure/ansible/example/yaml_properties.erb b/templates/azure/ansible/example/yaml_properties.erb new file mode 100644 index 000000000000..b6f4562b33f5 --- /dev/null +++ b/templates/azure/ansible/example/yaml_properties.erb @@ -0,0 +1,10 @@ +<% properties.each do |name, value| -%> +<% if value.is_a?(Hash) -%> +<%= name.underscore -%>: +<%= lines(build_yaml_properties(value, indentation)) -%> +<% elsif value.is_a?(String) || value.is_a?(Integer) || value.is_a?(TrueClass) || value.is_a?(FalseClass) -%> +<%= name.underscore -%>: <%= lines(value) -%> +<% else -%> +// TODO: Unsupported property "<%= prop[0] -%>" value <%= lines(prop[1]) -%> +<% end -%> +<% end -%> \ No newline at end of file diff --git a/templates/azure/ansible/info.erb b/templates/azure/ansible/info.erb new file mode 100644 index 000000000000..26b27de89f3c --- /dev/null +++ b/templates/azure/ansible/info.erb @@ -0,0 +1,153 @@ +#!/usr/bin/python +# +# Copyright (C) 2019 <%= lines(@config.author) -%> +# +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +<%= lines(autogen_notice :python) -%> + +from __future__ import absolute_import, division, print_function +__metaclass__ = type + +<% + python_sdk_client = object.azure_sdk_definition.python_client.split(".")[0] + python_sdk_sub_client = object.azure_sdk_definition.python_client.split(".")[1] + + is_data_source = true + + sdk_operation = object.azure_sdk_definition.read.request + input_properties = order_azure_properties(object.all_user_properties.reject{|p| !is_tags?(p) && (p.output || get_applicable_reference(p.azure_sdk_references, sdk_operation).nil?)}) + output_properties = order_azure_properties(object.all_user_properties) +-%> +<%= lines(compile('templates/azure/ansible/documentation.erb')) -%> + +from ansible.module_utils.azure_rm_common import AzureRMModuleBase +from ansible.module_utils.common.dict_transformations import _camel_to_snake + +try: + from msrestazure.azure_exceptions import CloudError + from <%= object.azure_sdk_definition.python_client_namespace -%> import <%= lines(python_sdk_client) -%> + from msrest.serialization import Model +except ImportError: + # This is handled in azure_rm_common + pass + + +class AzureRM<%= object.name -%>Info(AzureRMModuleBase): + def __init__(self): + # define user inputs into argument + self.module_arg_spec = dict( +<%= indent_list(input_properties.map{|p| azure_python_dict_for_property(p, object)}, 12, false) -%> +<%= "\n" -%> ) + # store the results of the module operation + self.results = dict( + changed=False + ) + self.mgmt_client = None +<%= lines(build_class_instance_variable_init(object.azure_sdk_definition.read, object)) -%> + super(AzureRM<%= object.name -%>Info, self).__init__(self.module_arg_spec, supports_tags=False) + + def exec_module(self, **kwargs): + for key in self.module_arg_spec: + setattr(self, key, kwargs[key]) + self.mgmt_client = self.get_mgmt_svc_client(<%= python_sdk_client -%>, + base_url=self._cloud_environment.endpoints.resource_manager) + +<% + support_subs = !object.azure_sdk_definition.list_by_subscription.nil? + support_rgs = !object.azure_sdk_definition.list_by_resource_group.nil? +-%> +<% if support_subs && support_rgs -%> + if self.resource_group is not None and self.name is not None: + self.results['items'] = self.get() + elif self.resource_group is not None: + self.results['items'] = self.list_by_resource_group() + else: + self.results['items'] = self.list_by_subscription() +<% else -%> + # TODO: Implement more info list here +<% end -%> + return self.results + +<% sdk_operation = object.azure_sdk_definition.read -%> + def get(self): + response = None + results = [] + try: +<%= lines(build_sdk_method_invocation(python_sdk_sub_client, sdk_operation)) -%> + self.log("Response : {0}".format(response)) + except CloudError as e: + self.log('Could not get info for <%= object.name.titlecase -%>.') + +<% if is_tags_defined?(object) -%> + if response and self.has_tags(response.tags, self.tags): + results.append(self.format_response(response)) +<% else -%> + # TODO: Implement format response without tags +<% end -%> + + return results + +<% sdk_operation = object.azure_sdk_definition.list_by_resource_group -%> +<% unless sdk_operation.nil? -%> + def list_by_resource_group(self): + response = None + results = [] + try: +<%= lines(build_sdk_method_invocation(python_sdk_sub_client, sdk_operation)) -%> + self.log("Response : {0}".format(response)) + except CloudError as e: + self.log('Could not get info for <%= object.name.titlecase -%>.') + + if response is not None: + for item in response: +<% if is_tags_defined?(object) -%> + if self.has_tags(item.tags, self.tags): + results.append(self.format_response(item)) +<% else -%> + # TODO: Implement format response without tags +<% end -%> + + return results +<% end -%> + +<% sdk_operation = object.azure_sdk_definition.list_by_subscription -%> +<% unless sdk_operation.nil? -%> + def list_by_subscription(self): + response = None + results = [] + try: +<%= lines(build_sdk_method_invocation(python_sdk_sub_client, sdk_operation)) -%> + self.log("Response : {0}".format(response)) + except CloudError as e: + self.log('Could not get info for <%= object.name.titlecase -%>.') + + if response is not None: + for item in response: +<% if is_tags_defined?(object) -%> + if self.has_tags(item.tags, self.tags): + results.append(self.format_response(item)) +<% else -%> + # TODO: Implement format response without tags +<% end -%> + + return results +<% end -%> + +<% sdk_operation = object.azure_sdk_definition.read -%> + def format_response(self, item): + d = item.as_dict() + d = { +<% output_properties.each do |prop| -%> +<%= lines(build_property_inline_response_format(prop, sdk_operation)) -%> +<% end -%> + } + return d + + +def main(): + AzureRM<%= object.name -%>Info() + + +if __name__ == "__main__": + main() diff --git a/templates/azure/ansible/integration_test.erb b/templates/azure/ansible/integration_test.erb new file mode 100644 index 000000000000..37ff537cbc54 --- /dev/null +++ b/templates/azure/ansible/integration_test.erb @@ -0,0 +1,58 @@ +--- +<%= lines(autogen_notice :yaml) -%> +<% deps_yaml, yaml, random_vars = build_test_yaml_from_example(object.inttests[0].example) -%> +- name: Prepare random number + set_fact: +<% random_vars.reject{|v| v.variable_name == 'resource_group'}.each do |rnd| -%> + <%= rnd.variable_name -%>: "<%= rnd.variable_value -%>" +<% end -%> + run_once: yes + +<%= lines(deps_yaml) -%> + +<%= lines(yaml) -%> + +- name: Assert the resource was created + assert: + that: + - output.changed + +<% _, same_yaml, _ = build_test_yaml_from_example(object.inttests[0].example, 'idempotent') -%> +<%= lines(same_yaml) -%> + +- name: Assert the resource was created + assert: + that: + - not output.changed + +<% unless object.inttests[0].info_by_name_example.nil? -%> +<% _, info_yaml, _ = build_test_yaml_from_example(object.inttests[0].info_by_name_example) -%> +<%= lines(info_yaml) -%> + +- name: Assert that info is returned + assert: + that: + - not output.changed +<%= lines(indent(generate_info_assert_list(object.inttests[0].example), 6)) -%> + +<% end -%> +<% unless object.inttests[0].info_by_resource_group_example.nil? -%> +<% _, info_yaml, _ = build_test_yaml_from_example(object.inttests[0].info_by_resource_group_example) -%> +<%= lines(info_yaml) -%> + +- name: Assert that info is returned + assert: + that: + - not output.changed +<%= lines(indent(generate_info_assert_list(object.inttests[0].example), 6)) -%> + +<% end -%> +<% deps_yaml, delete_yaml, _ = build_test_yaml_from_example(object.inttests[0].delete_example) -%> +<%= lines(delete_yaml) -%> + +- name: Assert that state has changed + assert: + that: + - output.changed + +<%= lines(deps_yaml) -%> diff --git a/templates/azure/ansible/module/class_instance_variable_init.erb b/templates/azure/ansible/module/class_instance_variable_init.erb new file mode 100644 index 000000000000..e01d88a79490 --- /dev/null +++ b/templates/azure/ansible/module/class_instance_variable_init.erb @@ -0,0 +1,12 @@ +<% params = sdk_operation.request.reject{|k, v| k.start_with?("/") && k != "/"} -%> +<% params.each do |name, value| -%> +<% case value -%> +<% when Api::Azure::SDKTypeDefinition::StringObject -%> +self.<%= value.python_variable_name -%> = None +<% when Api::Azure::SDKTypeDefinition::ComplexObject -%> +self.<%= value.python_variable_name -%> = dict() +<% else -%> +# TODO: <%= value -%> is not supported for initialization +<% end -%> +<% end -%> +<%= lines('self.tags = None') if is_tags_defined?(object) -%> \ No newline at end of file diff --git a/templates/azure/ansible/module/response_properties_update.erb b/templates/azure/ansible/module/response_properties_update.erb new file mode 100644 index 000000000000..6c1df72d1ed8 --- /dev/null +++ b/templates/azure/ansible/module/response_properties_update.erb @@ -0,0 +1,3 @@ +<% properties.each do |prop| -%> +'<%= prop.name.underscore -%>': response.get('<%= prop.name.underscore -%>', None) +<% end -%> \ No newline at end of file diff --git a/templates/azure/ansible/resource.erb b/templates/azure/ansible/resource.erb new file mode 100644 index 000000000000..c1a115ef1503 --- /dev/null +++ b/templates/azure/ansible/resource.erb @@ -0,0 +1,205 @@ +#!/usr/bin/python +# +# Copyright (C) 2019 <%= lines(@config.author) -%> +# +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +<%= lines(autogen_notice :python) -%> + +from __future__ import absolute_import, division, print_function +__metaclass__ = type + +<% + combine_create_update = object.azure_sdk_definition.update.nil? || (object.azure_sdk_definition.create.python_func_name == object.azure_sdk_definition.update.python_func_name) + python_sdk_client = object.azure_sdk_definition.python_client.split(".")[0] + python_sdk_sub_client = object.azure_sdk_definition.python_client.split(".")[1] + + is_data_source = false + + input_properties = order_azure_properties(object.all_user_properties.reject(&:output)) + output_properties = object.all_user_properties.select(&:output) + root_object_field = object.azure_sdk_definition.create.request["/"].python_variable_name +-%> +<%= lines(compile('templates/azure/ansible/documentation.erb')) -%> + +import time +<%= lines('from ansible.module_utils.azure_rm_common import normalize_location_name') if is_location_defined?(object) -%> +from ansible.module_utils.azure_rm_common_ext import AzureRMModuleBaseExt +from ansible.module_utils.common.dict_transformations import _snake_to_camel + +try: + from msrestazure.azure_exceptions import CloudError + from msrest.polling import LROPoller + from msrestazure.azure_operation import AzureOperationPoller + from msrest.serialization import Model + from <%= object.azure_sdk_definition.python_client_namespace -%> import <%= python_sdk_client -%> + +except ImportError: + # This is handled in azure_rm_common + pass + + +class Actions: + NoAction, Create, Update, Delete = range(4) + + +class AzureRM<%= object.name -%>(AzureRMModuleBaseExt): + """Configuration class for an Azure RM <%= object.name.titleize -%> resource""" + + def __init__(self): + self.module_arg_spec = dict( +<%= lines(indent_list(input_properties.reject{|p| is_tags?(p)}.map{|p| azure_python_dict_for_property(p, object)}, 12, true)) -%> + state=dict( + type='str', + default='present', + choices=['present', 'absent'] + ) + ) + +<%= lines(build_class_instance_variable_init(object.azure_sdk_definition.create, object)) -%> + + self.results = dict(changed=False) + self.mgmt_client = None + self.state = None + self.to_do = Actions.NoAction + +<%= lines(build_multiline_method_call("super(AzureRM#{object.name}, self).__init__(", [ + "derived_arg_spec=self.module_arg_spec", + "supports_check_mode=True", + "supports_tags=#{python_literal(is_tags_defined?(object))}" + ], ')', 8)) -%> + + def exec_module(self, **kwargs): + """Main module execution method""" + + for key in list(self.module_arg_spec.keys())<%= " + ['tags']" if is_tags_defined?(object) -%>: + if hasattr(self, key): + setattr(self, key, kwargs[key]) + elif kwargs[key] is not None: + self.<%= root_object_field -%>[key] = kwargs[key] + +<% sdk_marshal = Provider::Azure::Ansible::SDK::MarshalDescriptor.new input_properties.reject{|p| is_tags?(p)}, object.azure_sdk_definition.create, "self.#{root_object_field}", "self.#{root_object_field}" -%> +<%= lines(build_property_to_sdk_object(sdk_marshal, 8)) -%> + + response = None + + self.mgmt_client = self.get_mgmt_svc_client(<%= python_sdk_client -%>, + base_url=self._cloud_environment.endpoints.resource_manager) + + old_response = self.get_<%= object.name.downcase -%>() + + if not old_response: + self.log("<%= object.name.titlecase -%> instance doesn't exist") + if self.state == 'absent': + self.log("Old instance didn't exist") + else: + self.to_do = Actions.Create + else: + self.log("<%= object.name.titlecase -%> instance already exists") + if self.state == 'absent': + self.to_do = Actions.Delete + elif self.state == 'present': + self.results['old'] = old_response + self.results['new'] = self.<%= lines(root_object_field) -%> + if not self.idempotency_check(old_response, self.<%= root_object_field -%>): + self.to_do = Actions.Update + + if (self.to_do == Actions.Create) or (self.to_do == Actions.Update): + self.log("Need to Create / Update the <%= object.name.titlecase -%> instance") + + self.results['changed'] = True + if self.check_mode: + return self.results + + response = self.create_update_<%= object.name.downcase -%>() + + self.log("Creation / Update done") + elif self.to_do == Actions.Delete: + self.log("<%= object.name.titlecase -%> instance deleted") + self.results['changed'] = True + + if self.check_mode: + return self.results + + self.delete_<%= object.name.downcase -%>() + else: + self.log("<%= object.name.titlecase -%> instance unchanged") + self.results['changed'] = False + response = old_response + + if self.state == 'present': + self.results.update({ +<%= lines(build_response_properties_update(output_properties, object.azure_sdk_definition.read.response)) -%> + }) + return self.results + + def create_update_<%= object.name.downcase -%>(self): + ''' + Creates or updates <%= object.name.titlecase -%> with the specified configuration. + + :return: deserialized <%= object.name.titlecase -%> instance state dictionary + ''' + self.log("Creating / Updating the <%= object.name.titlecase -%> instance {0}".format(self.name)) + + try: +<% if combine_create_update -%> +<%= lines(build_sdk_method_invocation(python_sdk_sub_client, object.azure_sdk_definition.create)) -%> +<% else -%> + if self.to_do == Actions.Create: +<%= lines(build_sdk_method_invocation(python_sdk_sub_client, object.azure_sdk_definition.create, 16)) -%> + else: +<%= lines(build_sdk_method_invocation(python_sdk_sub_client, object.azure_sdk_definition.update, 16)) -%> +<% end -%> + if isinstance(response, LROPoller) or isinstance(response, AzureOperationPoller): + response = self.get_poller_result(response) + except CloudError as exc: + self.log('Error attempting to create the <%= object.name.titlecase -%> instance.') + self.fail("Error creating the <%= object.name.titlecase -%> instance: {0}".format(str(exc))) + return response.as_dict() + +<% sdk_operation = object.azure_sdk_definition.delete -%> + def delete_<%= object.name.downcase -%>(self): + ''' + Deletes specified <%= object.name.titlecase -%> instance in the specified subscription and resource group. + + :return: True + ''' + self.log("Deleting the <%= object.name.titlecase -%> instance {0}".format(self.name)) + try: +<%= lines(build_sdk_method_invocation(python_sdk_sub_client, sdk_operation)) -%> + except CloudError as e: + self.log('Error attempting to delete the <%= object.name.titlecase -%> instance.') + self.fail("Error deleting the <%= object.name.titlecase -%> instance: {0}".format(str(e))) + + if isinstance(response, LROPoller) or isinstance(response, AzureOperationPoller): + response = self.get_poller_result(response) + return True + +<% sdk_operation = object.azure_sdk_definition.read -%> + def get_<%= object.name.downcase -%>(self): + ''' + Gets the properties of the specified <%= object.name.titlecase -%> + + :return: deserialized <%= object.name.titlecase -%> instance state dictionary + ''' + self.log("Checking if the <%= object.name.titlecase -%> instance {0} is present".format(self.name)) + found = False + try: +<%= lines(build_sdk_method_invocation(python_sdk_sub_client, sdk_operation)) -%> + found = True + self.log("Response : {0}".format(response)) + self.log("<%= object.name.titlecase -%> instance : {0} found".format(response.name)) + except CloudError as e: + self.log('Did not find the <%= object.name.titlecase -%> instance.') + if found is True: + return response.as_dict() + return False + + +def main(): + """Main execution""" + AzureRM<%= object.name -%>() + + +if __name__ == '__main__': + main() diff --git a/templates/azure/ansible/sdk/method_invocation.erb b/templates/azure/ansible/sdk/method_invocation.erb new file mode 100644 index 000000000000..df3d40304f8f --- /dev/null +++ b/templates/azure/ansible/sdk/method_invocation.erb @@ -0,0 +1,3 @@ +<% params = sdk_op_def.request.reject{|k, v| k.include?("/") && k != "/"} -%> +<% param_assignments = params.map{|k, v| "#{v.python_parameter_name}=self.#{v.python_variable_name}"} -%> +<%= lines(build_multiline_method_call("response = self.mgmt_client.#{sdk_client}.#{sdk_op_def.python_func_name}(", param_assignments, ")")) -%> \ No newline at end of file diff --git a/templates/azure/ansible/sdktypes/location_property_normalize.erb b/templates/azure/ansible/sdktypes/location_property_normalize.erb new file mode 100644 index 000000000000..4cadd94a9639 --- /dev/null +++ b/templates/azure/ansible/sdktypes/location_property_normalize.erb @@ -0,0 +1,3 @@ +resource_group = self.get_resource_group(self.resource_group) +if <%= sdk_marshal.input -%>.get('location') is None: + <%= sdk_marshal.output -%>['location'] = resource_group.location \ No newline at end of file diff --git a/templates/azure/ansible/sdktypes/property_inline_response_format.erb b/templates/azure/ansible/sdktypes/property_inline_response_format.erb new file mode 100644 index 000000000000..0da0379d4487 --- /dev/null +++ b/templates/azure/ansible/sdktypes/property_inline_response_format.erb @@ -0,0 +1,19 @@ +<% + py_var = azure_python_variable_name(property, sdk_operation) + py_fields = [] + sdk_ref = get_applicable_reference(property.azure_sdk_references, sdk_operation.response) + unless sdk_ref.nil? + sdk_refs = sdk_ref.split('/').reject(&:empty?) + sdk_ref = '' + sdk_refs.each do |ref| + sdk_ref += '/' + ref + sdk_type_def = sdk_operation.response[sdk_ref] + py_fields <<= sdk_type_def.python_field_name || property.name.underscore + end + end + access_gets = py_fields[0..-2].map{|f| ".get('#{f}', {})"} + access_gets <<= ".get('#{py_fields.last}')" + access_chain = 'd' + access_gets.join + access_chain = "_camel_to_snake(#{access_chain})" if property.is_a? Api::Type::Enum +-%> +'<%= py_var -%>': <%= py_fields.empty? ? "self.#{py_var}" : "#{access_chain}" -%>, \ No newline at end of file diff --git a/templates/azure/ansible/sdktypes/property_normalize.erb b/templates/azure/ansible/sdktypes/property_normalize.erb new file mode 100644 index 000000000000..25691ebacd7b --- /dev/null +++ b/templates/azure/ansible/sdktypes/property_normalize.erb @@ -0,0 +1,33 @@ +<% + must_have_value = property.required || !property.default_value.nil? + indentation = (must_have_value ? 0 : 4) + + reference = get_applicable_reference(property.azure_sdk_references, sdk_marshal.operation.request) + var_name = azure_python_variable_name(property, sdk_marshal.operation) +-%> +<% if !must_have_value -%> +if <%= sdk_marshal.input -%>.get('<%= var_name -%>') is not None: +<% end -%> +<% + need_remove = false + if reference.start_with?(sdk_marshal.parent_reference) + relative_references = reference[sdk_marshal.parent_reference.length..-1].split('/').reject(&:empty?) + need_remove = true if relative_references.length != 1 + sdk_type = get_sdk_typedef_by_references(property.azure_sdk_references, sdk_marshal.operation.request) + need_remove = true if relative_references.length == 1 && sdk_type.python_field_name != var_name + else + need_remove = true + end + input_expression = "#{sdk_marshal.input}#{need_remove ? '.pop(' : '['}'#{var_name}'#{need_remove ? ')' : ']'}" +-%> +<% + normalize_format = '%s' + if property.is_a? Api::Type::Enum + normalize_format = '_snake_to_camel(%s, True)' + elsif property.is_a? Api::Azure::Type::ResourceReference + normalize_format = "self.normalize_resource_id(\n %s,\n '#{property.sample_value}')" + else + normalize_format = '# TODO: Not implemented template for %s' + end +-%> +<%= lines(build_sdk_reference_assignment(normalize_format % input_expression, reference, sdk_marshal, indentation)) -%> \ No newline at end of file diff --git a/templates/azure/ansible/sdktypes/property_to_sdkobject.erb b/templates/azure/ansible/sdktypes/property_to_sdkobject.erb new file mode 100644 index 000000000000..e05cddb7f545 --- /dev/null +++ b/templates/azure/ansible/sdktypes/property_to_sdkobject.erb @@ -0,0 +1,36 @@ +<% + # We support the following different SDK marshalling types (property path -> sdk path): + # 1. x -> x (e.g. enum, or "a" in case 5 - 7) + # 2. x -> a (e.g. property rename) + # 3. x/y -> a (e.g. property restructure, we handle x using its own definition) + # 4. x -> a/b (e.g. property restructure) + # 5. a/x -> a/b (actually equivalent to case 2 after we handled "a") + # 6. a/x/y -> a/b (actually equivalent to case 3 after we handled "a") + # 7. a/x -> a/b/c (actually equivalent to case 4 after we handled "a") + + sdk_marshal.properties.each do |prop| + reference = get_applicable_reference(prop.azure_sdk_references, sdk_marshal.operation.request) + sdk_type = sdk_marshal.operation.request[reference] + var_name = azure_python_variable_name(prop, sdk_marshal.operation) + parent_ref = get_parent_reference(reference) + + if require_type_marshal?(prop, sdk_marshal) + if self_require_type_marshal?(prop, sdk_marshal) +-%> +<%= lines(build_property_normalization(prop, sdk_marshal)) -%> +<% else -%> +if <%= sdk_marshal.input -%>.get('<%= var_name -%>') is not None: +<% + child_marshal = sdk_marshal.create_child_descriptor(prop, reference, sdk_type) + output_expression = "#{sdk_marshal.marshalled_references[parent_ref]}['#{sdk_type.python_field_name}']" + sdk_marshal.add_marshalled_reference(reference, output_expression) +-%> +<%= lines(build_property_to_sdk_object(child_marshal, 4)) -%> +<% + end + else + output_expression = "#{sdk_marshal.marshalled_references[parent_ref]}['#{sdk_type.python_field_name}']" + sdk_marshal.add_marshalled_reference(reference, output_expression) + end + end +-%> \ No newline at end of file diff --git a/templates/azure/ansible/sdktypes/reference_assignment.erb b/templates/azure/ansible/sdktypes/reference_assignment.erb new file mode 100644 index 000000000000..250e2a2f804a --- /dev/null +++ b/templates/azure/ansible/sdktypes/reference_assignment.erb @@ -0,0 +1,19 @@ +<% + sdk_type = sdk_marshal.operation.request[reference] + parent_ref = get_parent_reference(reference) + + output_expression = "#{sdk_marshal.marshalled_references[parent_ref]}['#{sdk_type.python_field_name}']" + + assignment = "# TODO: SDK assignment not implemented for #{reference}" + if sdk_marshal.marshalled_references.include?(parent_ref) + assignment = "#{output_expression} = #{input_expression}" + else + new_object = "'#{sdk_type.python_field_name}': #{input_expression}" + new_object = "{\n#{indent(new_object, 4)}\n}" + assignment = build_sdk_reference_assignment(new_object, parent_ref, sdk_marshal) + output_expression = "#{sdk_marshal.marshalled_references[parent_ref]}['#{sdk_type.python_field_name}']" + end + + sdk_marshal.add_marshalled_reference(reference, output_expression) +-%> +<%= lines(assignment) -%> \ No newline at end of file diff --git a/templates/azure/ansible/sdktypes/unsupported.erb b/templates/azure/ansible/sdktypes/unsupported.erb new file mode 100644 index 000000000000..d5ec6dcf40c6 --- /dev/null +++ b/templates/azure/ansible/sdktypes/unsupported.erb @@ -0,0 +1 @@ +# TODO: <%= property.name -%> SDK normalize is not implemented diff --git a/templates/azure/ansible/test/aliases.erb b/templates/azure/ansible/test/aliases.erb new file mode 100644 index 000000000000..6c384350fe59 --- /dev/null +++ b/templates/azure/ansible/test/aliases.erb @@ -0,0 +1,3 @@ +cloud/azure +destructive +shippable/azure/group2 \ No newline at end of file diff --git a/templates/azure/ansible/test/meta.erb b/templates/azure/ansible/test/meta.erb new file mode 100644 index 000000000000..48f5726d896a --- /dev/null +++ b/templates/azure/ansible/test/meta.erb @@ -0,0 +1,2 @@ +dependencies: + - setup_azure \ No newline at end of file diff --git a/templates/azure/terraform/acctest/parameters_from_schema.erb b/templates/azure/terraform/acctest/parameters_from_schema.erb new file mode 100644 index 000000000000..320f17616371 --- /dev/null +++ b/templates/azure/terraform/acctest/parameters_from_schema.erb @@ -0,0 +1,8 @@ +id, err := parse.<%= object.name -%>ID(rs.Primary.ID) +if err != nil { + return err +} +<% properties.reject{|p| get_applicable_reference(p.azure_sdk_references, sdk_op_def.request).nil?}.sort_by{|p| [p.order, p.name]}.each do |prop| -%> +<% var_name = get_sdk_typedef_by_references(prop.azure_sdk_references, sdk_op_def.request).go_variable_name -%> +<%= var_name -%> := id.<%= var_name.capitalize -%> +<% end -%> \ No newline at end of file diff --git a/templates/azure/terraform/datasource.erb b/templates/azure/terraform/datasource.erb new file mode 100644 index 000000000000..9e794373a58a --- /dev/null +++ b/templates/azure/terraform/datasource.erb @@ -0,0 +1,65 @@ +<%# lines(autogen_notice :go) -%> +<% + resource_name = "Arm" + object.name + sdk_package = object.azure_sdk_definition.go_client_namespace + sdk_operation = object.azure_sdk_definition.read + + properties = object.all_user_properties + schema_properties = properties.reject{|p| p.name == 'id' || get_property_value(p, 'hide_from_schema', false)} + input_properties = properties.reject{|p| get_applicable_reference(p.azure_sdk_references, sdk_operation.request).nil?} + + flatten_queue = Array.new + sdktype = Provider::Azure::Terraform::SDK::TypeDefinitionDescriptor.new sdk_operation, false + sdk_marshal = Provider::Azure::Terraform::SDK::MarshalDescriptor.new sdk_package, resource_name, flatten_queue, sdktype, schema_properties + + provider_name = object.api_name + provider_client_name = object.azure_sdk_definition.go_client +-%> +package <%= provider_name.downcase -%> + +func dataSource<%= resource_name -%>() *schema.Resource { + return &schema.Resource{ + Read: dataSource<%= resource_name -%>Read, + + Timeouts: &schema.ResourceTimeout{ + Read: schema.DefaultTimeout(5 * time.Minute), + }, + + Schema: map[string]*schema.Schema{<% # This block will remove the line-ending here -%> +<% order_azure_properties(schema_properties, input_properties).each do |prop| -%> +<%= lines_before(build_azure_schema_property(prop, object, 12, input_properties)) -%> + +<% end -%> +<%= lines(compile(object.custom_code.extra_schema_entry)) if object.custom_code.extra_schema_entry -%> + }, + } +} + +func dataSource<%= resource_name -%>Read(d *schema.ResourceData, meta interface{}) error { + client := meta.(*clients.Client).<%= provider_name -%>.<%= provider_client_name %> + ctx, cancel := timeouts.ForRead(meta.(*clients.Client).StopContext, d) + defer cancel() + +<% input_properties.sort_by{|p| [p.order, p.name]}.each do |prop| -%> +<% var_name = get_sdk_typedef_by_references(prop.azure_sdk_references, sdk_operation.request).go_variable_name -%> +<% special_known_name = (prop.name == 'tags' ? 't' : nil) -%> +<% output_var = var_name || special_known_name || prop.name.camelcase(:lower) -%> +<%= lines(build_schema_property_get('d', output_var, prop, sdk_marshal, 4)) -%> +<% end -%> + + resp, err := <%= lines(build_sdk_func_invocation(sdk_operation)) -%> + if err != nil { + if utils.ResponseWasNotFound(resp.Response) { + return <%= lines(build_errorf_with_resource_name("Error: %s was not found", false, sdk_operation, schema_properties, object)) -%> + } + return <%= lines(build_errorf_with_resource_name("Error reading %s", true, sdk_operation, schema_properties, object)) -%> + } + + d.SetId(*resp.ID) + +<%= lines(compile_template(object.custom_code.post_read, indentation: 4)) if object.custom_code.respond_to?(:post_read) && object.custom_code.post_read -%> + +<%= lines(build_sdk_object_to_property('resp', 'd', sdk_marshal)) -%> + + return nil +} \ No newline at end of file diff --git a/templates/azure/terraform/datasource.html.markdown.erb b/templates/azure/terraform/datasource.html.markdown.erb new file mode 100644 index 000000000000..b9532d91ca0b --- /dev/null +++ b/templates/azure/terraform/datasource.html.markdown.erb @@ -0,0 +1,79 @@ +<% + resource_name = object.name.underscore + terraform_name = "azurerm_" + object.name.underscore + + sdk_operation = object.azure_sdk_definition.read + properties = object.all_user_properties.reject{|p| get_property_value(p, 'hide_from_schema', false)} + input_properties = properties.reject{|p| get_applicable_reference(p.azure_sdk_references, sdk_operation.request).nil?} + output_properties = properties.select{|p| get_applicable_reference(p.azure_sdk_references, sdk_operation.request).nil?} +-%> +--- +<%# lines(autogen_notice :yaml) -%> +subcategory: "" +layout: "azurerm" +page_title: "Azure Resource Manager: azurerm_<%= resource_name -%>" +description: |- + Gets information about an existing <%= lines(object.name.titlecase) -%> +--- + +# Data Source: azurerm_<%= lines(resource_name) -%> + +Use this data source to access information about an existing <%= object.name.titlecase -%>. + +<%- unless object.docs.warning.nil? -%> +~> **Warning:** <%= lines(object.docs.warning) -%> +<%- end -%> + +<% if object.respond_to?(:document_examples) && !object.document_examples.nil? -%> +<% object.document_examples.each do |example_ref| -%> +<% datasource_props = input_properties.map do |p| -%> +<% sdk_type = get_sdk_typedef_by_references(p.azure_sdk_references, sdk_operation.request) -%> +<% [p.name.underscore, example_ref.resource_name_hints[sdk_type.id_portion]] -%> +<% end.to_h -%> +## <%= lines(example_ref.title) -%> + +```hcl +data "<%= terraform_name -%>" "example" { +<%= lines(build_hcl_properties(datasource_props)) -%> +} +<% object.datasource_example_outputs.each do |outName, output| -%> + +output "<%= outName -%>" { + value = "${data.<%= terraform_name -%>.example.<%= output -%>}" +} +<% end -%> +``` + +<% end -%> +<% end -%> + +## Argument Reference + +The following arguments are supported: +<% input_properties.select(&:required).sort_by{|p| [p.order, p.name]}.each do |prop| -%> +<%= lines(build_azure_property_documentation(prop, input_properties)) -%> +<% end -%> +<% input_properties.select(&:required).sort_by{|p| [p.order, p.name]}.each do |prop| -%> +<%= lines(build_azure_nested_property_documentation(prop, input_properties)) -%> +<% end -%> +<% input_properties.reject(&:required).sort_by{|p| [p.order, p.name]}.each do |prop| -%> +<%= lines(build_azure_property_documentation(prop, input_properties)) -%> +<% end -%> +<% input_properties.reject(&:required).sort_by{|p| [p.order, p.name]}.each do |prop| -%> +<%= lines(build_azure_nested_property_documentation(prop, input_properties)) -%> +<% end -%> + + +## Attributes Reference + +The following attributes are exported: +<% output_properties.sort_by{|p| [p.order, p.name]}.each do |prop| -%> +<%= lines(build_azure_property_documentation(prop, input_properties)) -%> +<% end -%> + +<% output_properties.sort_by{|p| [p.order, p.name]}.each do |prop| -%> +<%= lines(build_azure_nested_property_documentation(prop, input_properties)) -%> +<% end -%> +<%- unless object.docs.attributes.nil? -%> +<%= "\n" + object.docs.attributes -%> +<% end -%> diff --git a/templates/azure/terraform/datasource_test.go.erb b/templates/azure/terraform/datasource_test.go.erb new file mode 100644 index 000000000000..716f0094fd39 --- /dev/null +++ b/templates/azure/terraform/datasource_test.go.erb @@ -0,0 +1,76 @@ +<%# lines(autogen_notice :go) -%> +package azurerm + +import ( + "fmt" + "testing" + + "github.com/hashicorp/terraform/helper/resource" + "github.com/terraform-providers/terraform-provider-azurerm/azurerm/helpers/tf" +) + +<% + resource_name = object.name + terraform_name = "azurerm_" + object.name.underscore + + sdk_operation = object.azure_sdk_definition.read + properties = object.all_user_properties + input_properties = properties.reject{|p| get_applicable_reference(p.azure_sdk_references, sdk_operation.request).nil?} + datasource_props = input_properties.map{|p| [p.name.underscore, "${#{terraform_name}.test.#{p.name.underscore}}"]}.to_h + + test_hcls = Hash.new +-%> +<% + if object.respond_to? :acctests + object.acctests.each do |test| + test.steps.uniq.each do |name| + test_hcl, random_vars = build_test_hcl_from_example(name) + test_hcls[name] = { :hcl => test_hcl, :random_vars => random_vars } + end +-%> +func TestAccDataSourceAzureRM<%= resource_name -%>_<%= test.name -%>(t *testing.T) { + data := acceptance.BuildTestData(t, "<%= terraform_name -%>", "test") + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { acceptance.PreCheck(t) }, + Providers: acceptance.SupportedProviders, + Steps: []resource.TestStep{ +<% test.steps.each do |step| -%> +<% hcl_params = test_hcls[step][:random_vars].map(&:variable_name).uniq -%> +<% props_to_check = get_example_properties_to_check(step, object) -%> + { + Config: testAccDataSource<%= resource_name -%>_<%= step -%>(data), + Check: resource.ComposeTestCheckFunc( +<% props_to_check.each do |propName, propValue| -%> +<% if propValue == :AttrSet -%> + resource.TestCheckResourceAttrSet(data.ResourceName, "<%= propName -%>"), +<% else -%> + resource.TestCheckResourceAttr(data.ResourceName, "<%= propName -%>", "<%= propValue -%>"), +<% end -%> +<% end -%> + ), + }, +<% end -%> + }, + }) +} +<% end -%> +<% end -%> + +<% + if object.respond_to? :acctests + test_hcls.each do |name, test_hcl| +-%> +func testAccDataSource<%= resource_name -%>_<%= name -%>(data acceptance.TestData) string { + config := testAccAzureRM<%= resource_name -%>_<%= name -%>(data) + return fmt.Sprintf(` +%s + +data "<%= terraform_name -%>" "test" { +<%= lines(build_hcl_properties(datasource_props)) -%> +} +`, config) +} + +<% end -%> +<% end -%> \ No newline at end of file diff --git a/templates/azure/terraform/example/example_hcl.erb b/templates/azure/terraform/example/example_hcl.erb new file mode 100644 index 000000000000..c84f6db3bc70 --- /dev/null +++ b/templates/azure/terraform/example/example_hcl.erb @@ -0,0 +1,9 @@ +<% if with_dependencies && !example.prerequisites.nil? -%> +<% example.prerequisites.each do |pre_ex| -%> +<%= lines(build_hcl_from_example(pre_ex.product, pre_ex.example, resource_id_hint, name_hints, random_variables)) -%> + +<% end -%> +<% end -%> +resource "<%= example.resource -%>" "<%= resource_id_hint -%>" { +<%= lines(build_hcl_properties(example.properties)) -%> +} \ No newline at end of file diff --git a/templates/azure/terraform/example/hcl_properties.erb b/templates/azure/terraform/example/hcl_properties.erb new file mode 100644 index 000000000000..3323d635a0bf --- /dev/null +++ b/templates/azure/terraform/example/hcl_properties.erb @@ -0,0 +1,47 @@ +<% + # props_left: [[name_1, val_1], [name_2, val_2], ...] + props_left = properties.to_a + is_first_section = true + begin + simple_props = props_left.take_while{|p| !p[1].is_a?(Hash) && !(p[1].is_a?(Array) && p[1].length > 0 && p[1][0].is_a?(Hash))} + props_left = props_left.drop_while{|p| !p[1].is_a?(Hash) && !(p[1].is_a?(Array) && p[1].length > 0 && p[1][0].is_a?(Hash))} + +-%> +<%= "\n" if !is_first_section && !simple_props.empty? -%> +<% + is_first_section = false unless simple_props.empty? + + prop_alignment = simple_props.map{|p| p[0].length}.max + simple_props.each do |prop| + if prop[1].is_a?(String) +-%> +<%= prop[0].ljust(prop_alignment) -%> = "<%= prop[1] -%>" +<% elsif prop[1].is_a?(Integer) || prop[1].is_a?(TrueClass) || prop[1].is_a?(FalseClass) -%> +<%= prop[0].ljust(prop_alignment) -%> = <%= lines(prop[1].to_s) -%> +<% elsif prop[1].is_a?(Array) && (prop[1].length == 0 || prop[1][0].is_a?(String)) + res_arr = prop[1].map{|elem| "\"#{elem}\""} -%> +<%= prop[0].ljust(prop_alignment) -%> = [<%= res_arr.join(", ") -%>] +<% else -%> +// TODO: Unsupported property "<%= prop[0] -%>" value <%= lines(prop[1]) -%> +<% + end + end + + complex_props = props_left.take_while{|p| p[1].is_a?(Hash) || (p[1].is_a?(Array) && p[1].length > 0 && p[1][0].is_a?(Hash))} + props_left = props_left.drop_while{|p| p[1].is_a?(Hash) || (p[1].is_a?(Array) && p[1].length > 0 && p[1][0].is_a?(Hash))} + complex_props.each do |prop| + blocks = prop[1].is_a?(Array) ? prop[1] : [prop[1]] + blocks.each do |block| +-%> +<%= "\n" unless is_first_section -%> +<% + is_first_section = false +-%> +<%= prop[0] -%> <%= "= " if prop[0] == "tags"-%>{ +<%= lines(build_hcl_properties(block)) -%> +} +<% + end + end + end until props_left.empty? +-%> \ No newline at end of file diff --git a/templates/azure/terraform/example/import_resource_id.erb b/templates/azure/terraform/example/import_resource_id.erb new file mode 100644 index 000000000000..22ffe148468f --- /dev/null +++ b/templates/azure/terraform/example/import_resource_id.erb @@ -0,0 +1,13 @@ +<% + sdk = object.azure_sdk_definition + rg_name = "" + sub_ids = ['providers/' + sdk.provider_name] + sdk.read.request.reject {|k, v| v.id_portion.nil?}.each do |name, value| + if value.id_portion == "resourceGroups" + rg_name = name_hints[value.id_portion] + else + sub_ids <<= "#{value.id_portion}/#{name_hints[value.id_portion]}" + end + end +-%> +/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/<%= rg_name -%><%= sub_ids.length > 1 ? "/" : "" -%><%= sub_ids.join("/") -%> \ No newline at end of file diff --git a/templates/azure/terraform/expand_property_method.erb b/templates/azure/terraform/expand_property_method.erb new file mode 100644 index 000000000000..20ce0e50604b --- /dev/null +++ b/templates/azure/terraform/expand_property_method.erb @@ -0,0 +1,151 @@ +<% + property = descriptor.property + sdk_marshal = descriptor.sdkmarshal +-%> +<% if property.custom_expand -%> +<%= lines(compile_template(property.custom_expand, + prefix: sdk_marshal.resource, + property: property)) -%> +<% else -%> +<% if property.is_a?(Api::Azure::Type::ISO8601DateTime) -%> +func convertStringToDate(input interface{}) *date.Time { + v := input.(string) + + dateTime, err := date.ParseTime(time.RFC3339, v) + if err != nil { + log.Printf("[ERROR] Cannot convert an invalid string to RFC3339 date %q: %+v", v, err) + return nil + } + + result := date.Time{ + Time: dateTime, + } + return &result +} +<% elsif property.is_a?(Api::Type::Map) -%> +func expand<%= property.custom_ef_func_name.nil? ? sdk_marshal.resource+titlelize_property(property) : property.custom_ef_func_name -%>(v interface{}, d *schema.ResourceData, config *Config) (map[string]interface{}, error) { + if v == nil { + return map[string]interface{}{}, nil + } + m := make(map[string]interface{}) + for _, raw := range v.(*schema.Set).List() { + original := raw.(map[string]interface{}) + transformed := make(map[string]interface{}) + +<% property.value_type.nested_properties.each do |prop| -%> +<% next if prop.name == property.key_name -%> + transformed<%= titlelize_property(prop) -%>, err := expand<%= property.custom_ef_func_name.nil? ? sdk_marshal.resource+titlelize_property(property) : property.custom_ef_func_name -%><%= titlelize_property(prop) -%>(original["<%= Google::StringUtils.underscore(prop.name) -%>"], d, config) + if err != nil { + return nil, err + } + transformed["<%= prop.api_name -%>"] = transformed<%= titlelize_property(prop) -%> + +<% end -%> + + m[original["<%= property.key_name -%>"].(string)] = transformed + } + return m, nil +} + +<% property.value_type.nested_properties.each do |prop| -%> +<% next if prop.name == property.key_name -%> +<%# lines(build_azure_expand_method(sdk_marshal.resource + titlelize_property(property), prop), 1) -%> +<% end -%> +<% elsif property.is_a?(Api::Type::KeyValuePairs) -%> +func expand<%= property.custom_ef_func_name.nil? ? sdk_marshal.resource+titlelize_property(property) : property.custom_ef_func_name -%>(v interface{}, d *schema.ResourceData, config *Config) (map[string]string, error) { + if v == nil { + return map[string]string{}, nil + } + m := make(map[string]string) + for k, val := range v.(map[string]interface{}) { + m[k] = val.(string) + } + return m, nil +} +<% elsif tf_types.include?(property.class) -%> +func expand<%= property.custom_ef_func_name.nil? ? sdk_marshal.resource+descriptor.func_name : property.custom_ef_func_name -%>(input <%= go_type(property) -%>) *<%= '[]' if property.is_a?(Api::Type::Array) -%><%= sdk_marshal.package -%>.<%= sdk_marshal.sdktype.go_type_name -%> { +<% + if !property.nested_properties.empty? + nested_properties = property.nested_properties + if property.is_set +-%> + v := input.(*schema.Set).List() +<% elsif property.is_a?(Api::Type::NestedObject) -%> + if len(input) == 0 { + return nil + } + v := input[0].(map[string]interface{}) + +<% nested_properties.each do |prop| -%> +<% var_name = get_sdk_typedef_by_references(prop.azure_sdk_references, sdk_marshal.sdktype.type_definitions).go_variable_name -%> +<% special_known_name = (prop.name == 'tags' ? 't' : nil) -%> +<% output_var = var_name || special_known_name || prop.name.camelcase(:lower) -%> +<%= lines(build_schema_property_get('v', output_var, prop, sdk_marshal, 4)) if !prop.output -%> +<% end -%> + + result := <%= sdk_marshal.package -%>.<%= sdk_marshal.sdktype.go_type_name -%>{ +<%= lines(build_property_to_sdk_object(sdk_marshal.clone(nil, nested_properties), 8)) -%> + } + return &result +<% elsif property.is_a?(Api::Type::Array) -%> + results := make([]<%= sdk_marshal.package -%>.<%= sdk_marshal.sdktype.go_type_name -%>, 0) + for _, item := range input { + v := item.(map[string]interface{}) +<% nested_properties.each do |prop| -%> +<% var_name = get_sdk_typedef_by_references(prop.azure_sdk_references, sdk_marshal.sdktype.type_definitions).go_variable_name -%> +<% special_known_name = (prop.name == 'tags' ? 't' : nil) -%> +<% output_var = var_name || special_known_name || prop.name.camelcase(:lower) -%> +<%= lines(build_schema_property_get('v', output_var, prop, sdk_marshal, 8)) if !prop.output -%> +<% end -%> + + result := <%= sdk_marshal.package -%>.<%= sdk_marshal.sdktype.go_type_name -%>{ +<%= lines(build_property_to_sdk_object(sdk_marshal.clone(nil, nested_properties), 12)) -%> + } + + results = append(results, result) + } + return &results +<% end -%> +} + +<% nested_properties.each do |prop| -%> +<%# lines(build_azure_expand_method(sdk_marshal.resource + titlelize_property(property), prop), 1) -%> +<% end -%> +<% elsif property.is_a?(Api::Type::Array) && property.item_type.is_a?(Api::Type::ResourceRef) -%> + l := v.([]interface{}) + req := make([]interface{}, 0, len(l)) + for _, raw := range l { + f, err := <%= build_expand_resource_ref('raw.(string)', property.item_type) %> + if err != nil { + return nil, fmt.Errorf("Invalid value for <%= property.name.underscore -%>: %s", err) + } + req = append(req, f.RelativeLink()) + } + return req, nil +} +<% elsif property.is_a?(Api::Type::Array) && property.item_type.is_a?(Api::Type::Enum) -%> + results := make([]<%= sdk_marshal.package -%>.<%= sdk_marshal.sdktype.go_type_name -%>, 0) + for _, item := range input { + v := item.(string) + result := <%= sdk_marshal.package -%>.<%= sdk_marshal.sdktype.go_type_name -%>(v) + results = append(results, result) + } + return &results +} +<% else -%> +<% if property.is_a?(Api::Type::ResourceRef) -%> + f, err := <%= build_expand_resource_ref('v.(string)', property) %> + if err != nil { + return nil, fmt.Errorf("Invalid value for <%= property.name.underscore -%>: %s", err) + } + return f.RelativeLink(), nil +} +<% else -%> + return v, nil +} +<% end -%> +<% end # nested_properties, array of resourcerefs, else -%> +<% else -%> +// TODO: Expand Property '<%= property.name -%>' of type <%= property.class -%> is not supported +<% end # tf_types.include?(property.class) -%> +<% end # custom_code check -%> diff --git a/templates/azure/terraform/flatten_property_method.erb b/templates/azure/terraform/flatten_property_method.erb new file mode 100644 index 000000000000..17461782f151 --- /dev/null +++ b/templates/azure/terraform/flatten_property_method.erb @@ -0,0 +1,119 @@ +<%# The license inside this block applies to this file. + # Copyright 2017 Google Inc. + # Licensed under the Apache License, Version 2.0 (the "License"); + # you may not use this file except in compliance with the License. + # You may obtain a copy of the License at + # + # http://www.apache.org/licenses/LICENSE-2.0 + # + # Unless required by applicable law or agreed to in writing, software + # distributed under the License is distributed on an "AS IS" BASIS, + # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + # See the License for the specific language governing permissions and + # limitations under the License. +-%> +<% + property = descriptor.property + sdk_marshal = descriptor.sdkmarshal +-%> +<% if property.custom_flatten -%> +<%= lines(compile_template(property.custom_flatten, + prefix: sdk_marshal.resource, + property: property)) -%> +<% else -%> +<% if tf_types.include?(property.class) -%> +func flatten<%= property.custom_ef_func_name.nil? ? sdk_marshal.resource+descriptor.func_name : property.custom_ef_func_name -%>(input *<%= '[]' if property.is_a?(Api::Type::Array) -%><%= sdk_marshal.package -%>.<%= sdk_marshal.sdktype.go_type_name -%>) []interface{} { +<% if property.is_a?(Api::Type::NestedObject) -%> + if input == nil { + return make([]interface{}, 0) + } + + result := make(map[string]interface{}) + +<%= lines(build_sdk_object_to_property('input', 'result', sdk_marshal.clone(nil, property.nested_properties))) -%> + + return []interface{}{result} +<% elsif property.is_a?(Api::Type::Array) && property.item_type.is_a?(Api::Type::NestedObject) -%> + results := make([]interface{}, 0) + if input == nil { + return results + } + + for _, item := range *input { + v := make(map[string]interface{}) + +<%= lines(build_sdk_object_to_property('item', 'v', sdk_marshal.clone(nil, property.nested_properties), 8)) -%> + + results = append(results, v) + } + + return results +<% elsif property.is_a?(Api::Type::Array) && property.item_type.is_a?(Api::Type::Enum) -%> + results := make([]interface{}, 0) + if input == nil { + return results + } + + for _, item := range *input { + result := string(item) + results = append(results, result) + } + + return results +<% elsif property.is_a?(Api::Type::Map) -%> + if v == nil { + return v + } + l := v.(map[string]interface{}) + transformed := make([]interface{}, 0, len(l)) + for k, raw := range l { + original := raw.(map[string]interface{}) + transformed = append(transformed, map[string]interface{}{ + "<%= property.key_name -%>": k, + <% property.value_type.properties.each do |prop| -%> + "<%= Google::StringUtils.underscore(prop.name) -%>": flatten<%= property.custom_ef_func_name.nil? ? sdk_marshal.resource+titlelize_property(property) : property.custom_ef_func_name -%><%= titlelize_property(prop) -%>(original["<%= prop.api_name -%>"]), + <% end -%> + }) + } + return transformed +<% elsif property.is_a?(Api::Type::Integer) -%> + // Handles the string fixed64 format + if strVal, ok := v.(string); ok { + if intVal, err := strconv.ParseInt(strVal, 10, 64); err == nil { + return intVal + } // let terraform core handle it if we can't convert the string to an int. + } + return v +<% elsif property.is_a?(Api::Type::Array) && property.item_type.is_a?(Api::Type::ResourceRef) -%> + if v == nil { + return v + } + return convertAndMapStringArr(v.([]interface{}), ConvertSelfLinkToV1) +<% elsif property.is_a?(Api::Type::ResourceRef) -%> + if v == nil { + return v + } + return ConvertSelfLinkToV1(v.(string)) +<% elsif property.is_set -%> + if v == nil { + return v + } + <% if !property.set_hash_func.nil? -%> + return schema.NewSet(<%= property.set_hash_func -%>, v.([]interface{})) + <% elsif property.item_type.is_a?(String) -%> + return schema.NewSet(schema.HashString, v.([]interface{})) + <% else raise 'Unknown hash function for property #{property.name}' -%> + <% end -%> +<% else -%> + return v +<% end # property.is_a?(Api::Type::NestedObject) -%> +} +<% if !property.nested_properties.empty? -%> + <% property.nested_properties.each do |prop| -%> + <%# lines(build_azure_flatten_method(sdk_marshal.resource + titlelize_property(property), prop), 1) -%> + <% end -%> +<% end -%> +<% else -%> + // TODO: Flatten Property '<%= property.name -%>' of type <%= property.class -%> is not supported +<% end # tf_types.include?(property.class) -%> +<% end # custom code check -%> \ No newline at end of file diff --git a/templates/azure/terraform/nested_property_documentation.erb b/templates/azure/terraform/nested_property_documentation.erb new file mode 100644 index 000000000000..3f2455884899 --- /dev/null +++ b/templates/azure/terraform/nested_property_documentation.erb @@ -0,0 +1,18 @@ + +<% + if property.nested_properties? && (property.flatten_object.nil? || !property.flatten_object) +-%> +--- + +The `<%= property.name.underscore.singularize -%>` block <%= if property.output || !data_source_input.empty? then "contains" else "supports" end -%> the following: +<%- if property.is_a?(Api::Type::Map) %> +* `<%= property.key_name.underscore -%>` - (Required) The identifier for this object. Format specified above. +<% end -%> +<% property.nested_properties.each do |prop| -%> +<%= lines(build_azure_property_documentation(prop, data_source_input)) -%> +<% end -%> + +<% property.nested_properties.each do |prop| -%> +<%= lines(build_azure_nested_property_documentation(prop, data_source_input)) -%> +<% end -%> +<% end -%> diff --git a/templates/azure/terraform/property_documentation.erb b/templates/azure/terraform/property_documentation.erb new file mode 100644 index 000000000000..e6c92ede9c91 --- /dev/null +++ b/templates/azure/terraform/property_documentation.erb @@ -0,0 +1,17 @@ + +<% + prefix = "" + prefix = "(Required) " if property.required && !property.output && data_source_input.empty? || property.required && data_source_input.include?(property) + prefix = "(Optional) " if !property.required && !property.output && data_source_input.empty? || !property.required && data_source_input.include?(property) + + force_new_postfix = "" + force_new_postfix = " Changing this forces a new resource to be created." if property.input && !property.output && data_source_input.empty? + + default_value_postfix = "" + default_value_postfix = " Defaults to `#{property.default_value}`." if property.default_value != nil && !property.output && data_source_input.empty? +-%> +<% if property.is_a?(Api::Type::NestedObject) || property.is_a?(Api::Type::Map) || (property.is_a?(Api::Type::Array) && property.item_type.is_a?(Api::Type::NestedObject)) -%> +* `<%= property.name.underscore -%>` - <%= prefix %>One<%= ' or more' unless property.is_a?(Api::Type::NestedObject) -%> `<%= property.name.underscore.singularize -%>` block defined below.<%= force_new_postfix -%> +<% else -%> +* `<%= property.name.underscore -%>` - <%= prefix %><%= property.description.strip.gsub("\n\n", "\n") -%><%= default_value_postfix -%><%= force_new_postfix -%> +<% end -%> diff --git a/templates/azure/terraform/resource.erb b/templates/azure/terraform/resource.erb new file mode 100644 index 000000000000..632bae9fc0f0 --- /dev/null +++ b/templates/azure/terraform/resource.erb @@ -0,0 +1,296 @@ +<%# lines(autogen_notice :go) -%> +<% + properties = object.all_user_properties + schema_properties = properties.reject{|p| p.name == 'id' || get_property_value(p, 'hide_from_schema', false)} + # Fingerprints aren't *really* settable properties, but they behave like one. At Create, they have no value but they + # can just be read in anyways, and after a Read they will need to be set in every Update. + settable_properties = properties.reject{|p| get_applicable_reference(p.azure_sdk_references, object.azure_sdk_definition.create.request).nil?} + .reject{|v| v.output && !v.is_a?(Api::Type::Fingerprint) } + .reject(&:url_param_only) + # PUT needs parameters like `name` to be set in the resource body, but we don't want to send them in PATCH + updatable_properties = settable_properties.reject{|p| !object.azure_sdk_definition.update.nil? && get_applicable_reference(p.azure_sdk_references, object.azure_sdk_definition.update.request).nil?} + .reject { |p| p.name == "location" } # location is always force new in terraform + output_properties = properties.reject{|p| p.name == 'id'} + + provider_name = object.api_name + provider_client_name = object.azure_sdk_definition.go_client +-%> +package <%= provider_name.downcase -%> + +<%= lines(compile(object.custom_code.constants)) if object.custom_code.constants -%> + +<% + resource_name = "Arm" + object.name + terraform_name = "azurerm_" + object.name.underscore + sdk_package = object.azure_sdk_definition.go_client_namespace + + $global_expand_queue = Array.new unless defined? $global_expand_queue + $global_flatten_queue = Array.new unless defined? $global_flatten_queue + expand_queue = Array.new + flatten_queue = Array.new + + combine_create_update = object.azure_sdk_definition.update.nil? || (object.azure_sdk_definition.create.go_func_name == object.azure_sdk_definition.update.go_func_name) + create_func_name_postfix = (combine_create_update ? "CreateUpdate" : "Create") + update_func_name_postfix = (combine_create_update ? "CreateUpdate" : "Update") +-%> + +import azSchema "github.com/terraform-providers/terraform-provider-azurerm/azurerm/internal/tf/schema" + +func resource<%= resource_name -%>() *schema.Resource { + return &schema.Resource{ + Create: resource<%= resource_name -%><%= create_func_name_postfix -%>, + Read: resource<%= resource_name -%>Read, +<% if updatable?(object, properties) -%> + Update: resource<%= resource_name -%><%= update_func_name_postfix -%>, +<% end -%> + Delete: resource<%= resource_name -%>Delete, +<% if settable_properties.any? {|p| p.unordered_list} && !object.custom_code.resource_definition -%> + CustomizeDiff: customdiff.All( +<%= + settable_properties.select { |p| p.unordered_list } + .map { |p| "resource#{resource_name}#{p.name.camelize(:upper)}SetStyleDiff"} + .join(",\n") +-%> + ), +<% end -%> + + Importer: &azSchema.ValidateResourceIDPriorToImport(func(id string) error { + _, err := parse.<%= object.name -%>ID(id) + return err + }) + + Timeouts: &schema.ResourceTimeout{ + Create: schema.DefaultTimeout(30 * time.Minute), + Read: schema.DefaultTimeout(5 * time.Minute), + Update: schema.DefaultTimeout(30 * time.Minute), + Delete: schema.DefaultTimeout(30 * time.Minute), + }, + +<% unless object.async.nil? -%> + Timeouts: &schema.ResourceTimeout { + Create: schema.DefaultTimeout(<%= object.async.operation.timeouts.insert_sec -%> * time.Second), +<% if updatable?(object, properties) -%> + Update: schema.DefaultTimeout(<%= object.async.operation.timeouts.update_sec -%> * time.Second), +<% end -%> + Delete: schema.DefaultTimeout(<%= object.async.operation.timeouts.delete_sec -%> * time.Second), + }, +<% end -%> +<%= lines(compile(object.custom_code.resource_definition)) if object.custom_code.resource_definition -%> + + Schema: map[string]*schema.Schema{<% # This block will remove the line-ending here -%> +<% order_azure_properties(schema_properties).each do |prop| -%> +<%= lines_before(build_azure_schema_property(prop, object, 12)) -%> + +<% end -%> +<%= lines(compile(object.custom_code.extra_schema_entry)) if object.custom_code.extra_schema_entry -%> + }, + } +} +<% settable_properties.select {|p| p.unordered_list}.each do |prop| -%> +func resource<%= resource_name -%><%= prop.name.camelize(:upper) -%>SetStyleDiff(diff *schema.ResourceDiff, meta interface{}) error { +<%= + compile_template('templates/terraform/unordered_list_customize_diff.erb', + prop: prop, + resource_name: resource_name) +-%> +} +<% end -%> + +<% sdk_operation = object.azure_sdk_definition.create -%> +<% sdktype = Provider::Azure::Terraform::SDK::TypeDefinitionDescriptor.new sdk_operation, true -%> +<% sdk_marshal = Provider::Azure::Terraform::SDK::MarshalDescriptor.new sdk_package, resource_name, expand_queue, sdktype, settable_properties -%> +func resource<%= resource_name -%><%= create_func_name_postfix -%>(d *schema.ResourceData, meta interface{}) error { + client := meta.(*clients.Client).<%= provider_name -%>.<%= provider_client_name %> + ctx, cancel := timeouts.<%= combine_create_update ? "ForCreateUpdate" : "ForCreate"-%>(meta.(*clients.Client).StopContext, d) + defer cancel() + +<% settable_properties.reject{|p| get_applicable_reference(p.azure_sdk_references, sdk_operation.request).start_with?("/")}.sort_by{|p| [p.order, p.name]}.each do |prop| -%> +<% var_name = get_sdk_typedef_by_references(prop.azure_sdk_references, sdk_operation.request).go_variable_name -%> +<% output_var = var_name || prop.name.camelcase(:lower) -%> +<%= lines(build_schema_property_get('d', output_var, prop, sdk_marshal, 4)) -%> +<% end -%> + + if features.ShouldResourcesBeImported() && d.IsNewResource() { + existing, err := <%= lines(build_sdk_func_invocation(object.azure_sdk_definition.read)) -%> + if err != nil { + if !utils.ResponseWasNotFound(existing.Response) { + return <%= lines(build_errorf_with_resource_name("Error checking for present of existing %s", true, object.azure_sdk_definition.read, properties, object)) -%> + } + } + if existing.ID != nil && *existing.ID != "" { + return tf.ImportAsExistsError("<%= terraform_name -%>", *existing.ID) + } + } + +<% settable_properties.select{|p| get_applicable_reference(p.azure_sdk_references, sdk_operation.request).start_with?("/")}.sort_by{|p| [p.order, p.name]}.each do |prop| -%> +<% var_name = get_sdk_typedef_by_references(prop.azure_sdk_references, sdk_operation.request).go_variable_name -%> +<% special_known_name = (prop.name == 'tags' ? 't' : nil) -%> +<% output_var = var_name || special_known_name || prop.name.camelcase(:lower) -%> +<%= lines(build_schema_property_get('d', output_var, prop, sdk_marshal, 4)) -%> +<% end -%> + + <%= sdk_operation.request['/'].go_variable_name -%> := <%= sdk_package -%>.<%= sdk_operation.request['/'].go_type_name -%>{ +<%= lines(build_property_to_sdk_object(sdk_marshal, 8)) -%> + } + +<%= lines(build_property_to_sdk_object_empty_sensitive(sdk_marshal)) -%> +<% if object.mutex -%> + lockName, err := replaceVars(d, config, "<%= object.mutex -%>") + if err != nil { + return err + } + mutexKV.Lock(lockName) + defer mutexKV.Unlock(lockName) +<% end -%> + +<% unless sdk_operation.async -%> + if _, err := <%= build_sdk_func_invocation(sdk_operation) -%>; err != nil { + return <%= lines(build_errorf_with_resource_name("Error creating %s", true, sdk_operation, properties, object)) -%> + } +<% else -%> + future, err := <%= lines(build_sdk_func_invocation(sdk_operation)) -%> + if err != nil { + return <%= lines(build_errorf_with_resource_name("Error creating %s", true, sdk_operation, properties, object)) -%> + } + if err = future.WaitForCompletionRef(ctx, client.Client); err != nil { + return <%= lines(build_errorf_with_resource_name("Error waiting for creation of %s", true, sdk_operation, properties, object)) -%> + } +<% end -%> + +<%= lines(compile(object.custom_code.post_create)) if object.custom_code.post_create -%> + + resp, err := <%= lines(build_sdk_func_invocation(object.azure_sdk_definition.read)) -%> + if err != nil { + return <%= lines(build_errorf_with_resource_name("Error retrieving %s", true, object.azure_sdk_definition.read, properties, object)) -%> + } + if resp.ID == nil { + return <%= lines(build_errorf_with_resource_name("Cannot read %s ID", false, object.azure_sdk_definition.read, properties, object)) -%> + } + d.SetId(*resp.ID) + + return resource<%= resource_name -%>Read(d, meta) +} + +<% sdk_operation = object.azure_sdk_definition.read -%> +<% sdktype = Provider::Azure::Terraform::SDK::TypeDefinitionDescriptor.new sdk_operation, false -%> +<% sdk_marshal = Provider::Azure::Terraform::SDK::MarshalDescriptor.new sdk_package, resource_name, flatten_queue, sdktype, output_properties -%> +func resource<%= resource_name -%>Read(d *schema.ResourceData, meta interface{}) error { + client := meta.(*clients.Client).<%= provider_name -%>.<%= provider_client_name %> + ctx, cancel := timeouts.ForRead(meta.(*clients.Client).StopContext, d) + defer cancel() + +<%= lines(build_azure_id_parser(object.azure_sdk_definition.read, object)) -%> + + resp, err := <%= lines(build_sdk_func_invocation(object.azure_sdk_definition.read)) -%> + if err != nil { + if utils.ResponseWasNotFound(resp.Response) { + log.Printf("[INFO] <%= object.name.titlecase -%> %q does not exist - removing from state", d.Id()) + d.SetId("") + return nil + } + return <%= lines(build_errorf_with_resource_name("Error reading %s", true, object.azure_sdk_definition.read, output_properties, object)) -%> + } + +<%= lines(compile_template(object.custom_code.post_read, indentation: 4)) if object.custom_code.respond_to?(:post_read) && object.custom_code.post_read -%> + +<%= lines(build_sdk_object_to_property('resp', 'd', sdk_marshal)) -%> + + return <%= is_tags_defined?(sdk_operation) ? "tags.FlattenAndSet(d, resp.#{go_field_name_of_tags(sdk_operation)})" : 'nil' -%> + +} + +<% if !combine_create_update -%> +<% sdk_operation = object.azure_sdk_definition.update -%> +func resource<%= resource_name -%><%= update_func_name_postfix -%>(d *schema.ResourceData, meta interface{}) error { + client := meta.(*clients.Client).<%= provider_name -%>.<%= provider_client_name %> + ctx, cancel := timeouts.ForUpdate(meta.(*clients.Client).StopContext, d) + defer cancel() + + <% updatable_properties.sort_by{|p| [p.order, p.name]}.each do |prop| -%> +<% var_name = get_sdk_typedef_by_references(prop.azure_sdk_references, sdk_operation.request).go_variable_name -%> +<% special_known_name = (prop.name == 'tags' ? 't' : nil) -%> +<% output_var = var_name || special_known_name || prop.name.camelcase(:lower) -%> +<%= lines(build_schema_property_get('d', output_var, prop, sdk_marshal, 4)) -%> +<% end -%> + + <%= sdk_operation.request['/'].go_variable_name -%> := <%= sdk_package -%>.<%= sdk_operation.request['/'].go_type_name -%>{ +<% sdktype = Provider::Azure::Terraform::SDK::TypeDefinitionDescriptor.new sdk_operation, true -%> +<% sdk_marshal = Provider::Azure::Terraform::SDK::MarshalDescriptor.new sdk_package, resource_name, expand_queue, sdktype, updatable_properties -%> +<%= lines(build_property_to_sdk_object(sdk_marshal, 8)) -%> + } + +<%= lines(build_property_to_sdk_object_empty_sensitive(sdk_marshal)) -%> + +<% unless sdk_operation.async -%> + if _, err := <%= build_sdk_func_invocation(sdk_operation) -%>; err != nil { + return <%= lines(build_errorf_with_resource_name("Error updating %s", true, sdk_operation, properties, object)) -%> + } +<% else -%> + future, err := <%= lines(build_sdk_func_invocation(sdk_operation)) -%> + if err != nil { + return <%= lines(build_errorf_with_resource_name("Error updating %s", true, sdk_operation, properties, object)) -%> + } + if err = future.WaitForCompletionRef(ctx, client.Client); err != nil { + return <%= lines(build_errorf_with_resource_name("Error waiting for update of %s", true, sdk_operation, properties, object)) -%> + } +<% end -%> + + return resource<%= resource_name -%>Read(d, meta) +} +<% end -%> + +<% sdk_operation = object.azure_sdk_definition.delete -%> +func resource<%= resource_name -%>Delete(d *schema.ResourceData, meta interface{}) error { + client := meta.(*clients.Client).<%= provider_name -%>.<%= provider_client_name %> + ctx, cancel := timeouts.ForDelete(meta.(*clients.Client).StopContext, d) + defer cancel() + +<% if object.mutex -%> + lockName, err := replaceVars(d, config, "<%= object.mutex -%>") + if err != nil { + return err + } + mutexKV.Lock(lockName) + defer mutexKV.Unlock(lockName) +<% end -%> + +<%= lines(build_azure_id_parser(sdk_operation, object)) -%> + +<% unless sdk_operation.async -%> + if _, err := <%= build_sdk_func_invocation(sdk_operation) -%>; err != nil { + return <%= lines(build_errorf_with_resource_name("Error deleting %s", true, sdk_operation, properties, object)) -%> + } +<% else -%> + future, err := <%= lines(build_sdk_func_invocation(sdk_operation)) -%> + if err != nil { + if response.WasNotFound(future.Response()) { + return nil + } + return <%= lines(build_errorf_with_resource_name("Error deleting %s", true, sdk_operation, properties, object)) -%> + } + + if err = future.WaitForCompletionRef(ctx, client.Client); err != nil { + if !response.WasNotFound(future.Response()) { + return <%= lines(build_errorf_with_resource_name("Error waiting for deleting %s", true, sdk_operation, properties, object)) -%> + } + } +<% end -%> + + return nil +} + +<% while !expand_queue.empty? -%> +<% descriptor = expand_queue.shift -%> +<%= lines(build_azure_expand_method(descriptor)) -%> + +<% end -%> + +<% while !flatten_queue.empty? -%> +<% descriptor = flatten_queue.shift -%> +<%= lines(build_azure_flatten_method(descriptor)) -%> + +<% end -%> + +<% if object.custom_code.respond_to?(:extra_functions) && object.custom_code.extra_functions -%> +<%= lines(compile(get_custom_template_path(object.custom_code.extra_functions))) -%> +<% end -%> \ No newline at end of file diff --git a/templates/azure/terraform/resource.html.markdown.erb b/templates/azure/terraform/resource.html.markdown.erb new file mode 100644 index 000000000000..83449205d26c --- /dev/null +++ b/templates/azure/terraform/resource.html.markdown.erb @@ -0,0 +1,109 @@ +<%# NOTE NOTE NOTE + The newlines in this file are *load bearing*. This file outputs + Markdown, which is extremely sensitive to newlines. You have got + to have a newline after every attribute and property, because + otherwise MD will think the next element is part of the previous + property's bullet point. You cannot have any double newlines in the + middle of a property or attribute, because MD will think that the + empty line ends the bullet point and the indentation will be off. + You must have a newline before and after all --- document indicators, + and you must have a newline before and after all - - - hlines. + You cannot have more than one blank line between properties. + The --- document indicator must be the first line of the file. + As long as you only use `build_azure_property_documentation`, it all works + fine - but when you need to add custom docs (notes, etc), you need + to remember these things. + + Know also that the `lines` function in heavy use in MagicModules will + strip exactly one trailing newline - unless that's what you've designed + your docstring for, it's easier to insert newlines where you need them + manually. That's why, in this file, we use `lines` on anything which + is generated from a ruby function, but skip it on anything that is + directly inserted from YAML. +-%> +<% + resource_name = object.name.underscore + properties = object.all_user_properties.reject{|p| get_property_value(p, 'hide_from_schema', false)} +-%> +--- +<%# lines(autogen_notice :yaml) -%> +subcategory: "" +layout: "azurerm" +page_title: "Azure Resource Manager: azurerm_<%= resource_name -%>" +description: |- +<%= indent(object.description.first_sentence, 2) %> +--- + +# azurerm_<%= resource_name -%> + + +<%= lines(object.description) -%> + +<% if !object.references.nil? -%> +To get more information about <%= object.name -%>, see: + +<% if !object.references.api.nil? -%> +* [API documentation](<%= object.references.api -%>) +<% end # object...api.nil? -%> +<% if !object.references.guides.empty? -%> +* How-to Guides +<% object.references.guides.each do |title, link| -%> + * [<%= title -%>](<%= link -%>) +<% end # object...guides.each -%> +<% end # object...guides.empty? -%> +<% end # object...api.nil? -%> +<%- unless object.docs.warning.nil? -%> + +~> **Warning:** <%= object.docs.warning -%> +<%- end -%> + +<% if object.instance_variable_defined?(:@document_examples) && !object.document_examples.nil? -%> +<% object.document_examples.each do |example_ref| -%> +## <%= example_ref.title -%> + + +```hcl +<%= lines(build_documentation_hcl_from_example(example_ref.example_name, example_ref.resource_name_hints)) -%> +``` + +<% end -%> +<% end -%> +## Argument Reference + +The following arguments are supported: +<% properties.select(&:required).sort_by{|p| [p.order, p.name]}.each do |prop| -%> +<%= lines(build_azure_property_documentation(prop)) -%> +<% end -%> +<% properties.select(&:required).sort_by{|p| [p.order, p.name]}.each do |prop| -%> +<%= lines(build_azure_nested_property_documentation(prop)) -%> +<% end -%> +<% properties.reject(&:required).reject(&:output).sort_by{|p| [p.order, p.name]}.each do |prop| -%> +<%= lines(build_azure_property_documentation(prop)) -%> +<% end -%> +<% properties.reject(&:required).reject(&:output).sort_by{|p| [p.order, p.name]}.each do |prop| -%> +<%= lines(build_azure_nested_property_documentation(prop)) -%> +<% end -%> + +## Attributes Reference + +The following attributes are exported: +<% properties.select(&:output).each do |prop| -%> +<%= lines(build_azure_property_documentation(prop)) -%> +<% end -%> + +<% properties.select(&:output).each do |prop| -%> +<%= lines(build_azure_nested_property_documentation(prop)) -%> +<% end -%> +<%- unless object.docs.attributes.nil? -%> +<%= "\n" + object.docs.attributes -%> +<% end -%> + +<% if object.instance_variable_defined?(:@document_examples) && !object.document_examples.nil? && !object.document_examples.empty? -%> +## Import + +<%= object.name.titlecase -%> can be imported using the `resource id`, e.g. + +```shell +$ terraform import azurerm_<%= resource_name -%>.example <%= lines(build_documentation_import_resource_id(object, object.document_examples[0])) -%> +``` +<% end -%> diff --git a/templates/azure/terraform/schemas/basic_get.erb b/templates/azure/terraform/schemas/basic_get.erb new file mode 100644 index 000000000000..0b35802e28ed --- /dev/null +++ b/templates/azure/terraform/schemas/basic_get.erb @@ -0,0 +1,5 @@ +<% if input_var == 'd' -%> +<%= output_var -%> := d.Get("<%= prop_name -%>").(<%= go_type(property) -%>) +<% else -%> +<%= output_var -%> := <%= input_var -%>["<%= prop_name -%>"].(<%= go_type(property) -%>) +<% end -%> \ No newline at end of file diff --git a/templates/azure/terraform/schemas/basic_set.erb b/templates/azure/terraform/schemas/basic_set.erb new file mode 100644 index 000000000000..7cae377be4d0 --- /dev/null +++ b/templates/azure/terraform/schemas/basic_set.erb @@ -0,0 +1,21 @@ +<% + format_str = '%s' + case sdk_marshal.sdktype.type_definition + when Api::Azure::SDKTypeDefinition::Integer32Object, Api::Azure::SDKTypeDefinition::Integer64Object + format_str = 'int(%s)' + end +-%> +<% if output_var == 'd' -%> +<% if sdk_marshal.sdktype.type_definition.is_a?(Api::Azure::SDKTypeDefinition::Integer32Object) || sdk_marshal.sdktype.type_definition.is_a?(Api::Azure::SDKTypeDefinition::Integer64Object) -%> +d.Set("<%= prop_name -%>", <%= format_str % "*#{input_var}" -%>) +<% else -%> +d.Set("<%= prop_name -%>", <%= format_str % input_var -%>) +<% end -%> +<% elsif property.is_a? Api::Type::Enum -%> +<%= output_var -%>["<%= prop_name -%>"] = <%= lines(format_str % "#{input_var}") -%> +<% else -%> +<% temp_var = prop_name.camelize(:lower) -%> +if <%= temp_var -%> := <%= input_var -%>; <%= temp_var -%> != nil { + <%= output_var -%>["<%= prop_name -%>"] = <%= lines(format_str % "*#{temp_var}") -%> +} +<% end -%> \ No newline at end of file diff --git a/templates/azure/terraform/schemas/boolean_enum_get.erb b/templates/azure/terraform/schemas/boolean_enum_get.erb new file mode 100644 index 000000000000..087be28361fb --- /dev/null +++ b/templates/azure/terraform/schemas/boolean_enum_get.erb @@ -0,0 +1,9 @@ +<% enum_prefix = get_sdk_typedef_by_references(property.azure_sdk_references, sdk_marshal.sdktype.type_definitions).go_enum_const_prefix -%> +<%= output_var -%> := <%= sdk_marshal.package -%>.<%= lines(enum_prefix + property.false_value.to_s) -%> +<% if input_var == 'd' -%> +if d.Get("<%= prop_name -%>").(bool) { +<% else -%> +if <%= input_var -%>["<%= prop_name -%>"].(bool) { +<% end -%> + <%= output_var -%> = <%= sdk_marshal.package -%>.<%= lines(enum_prefix + property.true_value.to_s) -%> +} \ No newline at end of file diff --git a/templates/azure/terraform/schemas/boolean_enum_set.erb b/templates/azure/terraform/schemas/boolean_enum_set.erb new file mode 100644 index 000000000000..0e48a36ecf0c --- /dev/null +++ b/templates/azure/terraform/schemas/boolean_enum_set.erb @@ -0,0 +1,6 @@ +<% enum_prefix = get_sdk_typedef_by_references(property.azure_sdk_references, sdk_marshal.sdktype.type_definitions).go_enum_const_prefix -%> +<% if output_var == 'd' -%> +d.Set("<%= prop_name -%>", <%= input_var -%> == <%= sdk_marshal.package -%>.<%= enum_prefix + property.false_value.to_s -%>) +<% else -%> +<%= output_var -%>["<%= prop_name -%>"] = <%= input_var -%> == <%= sdk_marshal.package -%>.<%= enum_prefix + property.false_value.to_s -%> +<% end -%> \ No newline at end of file diff --git a/templates/azure/terraform/schemas/datasource_location.erb b/templates/azure/terraform/schemas/datasource_location.erb new file mode 100644 index 000000000000..a82232ca28c7 --- /dev/null +++ b/templates/azure/terraform/schemas/datasource_location.erb @@ -0,0 +1 @@ +"<%= property.name.underscore -%>": azure.SchemaLocationForDataSource(), \ No newline at end of file diff --git a/templates/azure/terraform/schemas/datasource_resource_group_name.erb b/templates/azure/terraform/schemas/datasource_resource_group_name.erb new file mode 100644 index 000000000000..16d818450f34 --- /dev/null +++ b/templates/azure/terraform/schemas/datasource_resource_group_name.erb @@ -0,0 +1 @@ +"<%= prop_name -%>": azure.SchemaResourceGroupNameForDataSource(), \ No newline at end of file diff --git a/templates/azure/terraform/schemas/datasource_tags.erb b/templates/azure/terraform/schemas/datasource_tags.erb new file mode 100644 index 000000000000..9cf0afd45d19 --- /dev/null +++ b/templates/azure/terraform/schemas/datasource_tags.erb @@ -0,0 +1 @@ +"<%= property.name.underscore -%>": tags.SchemaDataSource(), \ No newline at end of file diff --git a/templates/azure/terraform/schemas/datetime_and_duration_set.erb b/templates/azure/terraform/schemas/datetime_and_duration_set.erb new file mode 100644 index 000000000000..2928e95c3680 --- /dev/null +++ b/templates/azure/terraform/schemas/datetime_and_duration_set.erb @@ -0,0 +1,9 @@ +<% format_str = property.is_a?(Api::Azure::Type::ISO8601Duration) ? '%s' : '(%s).String()' -%> +<% if output_var == 'd' -%> +d.Set("<%= prop_name -%>", <%= format_str % input_var-%>) +<% else -%> +<% temp_var = prop_name.camelize(:lower) -%> +if <%= temp_var -%> := <%= input_var -%>; <%= temp_var -%> != nil { + <%= output_var -%>["<%= prop_name -%>"] = <%= lines(format_str % "*#{temp_var}") -%> +} +<% end -%> diff --git a/templates/azure/terraform/schemas/empty.erb b/templates/azure/terraform/schemas/empty.erb new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/templates/azure/terraform/schemas/flatten_set.erb b/templates/azure/terraform/schemas/flatten_set.erb new file mode 100644 index 000000000000..5c74aed6fd99 --- /dev/null +++ b/templates/azure/terraform/schemas/flatten_set.erb @@ -0,0 +1,8 @@ +<% flatten_func_name = sdk_marshal.enqueue(property, $global_flatten_queue) -%> +<% if output_var == 'd' -%> +if err := d.Set("<%= prop_name -%>", flatten<%= property.custom_ef_func_name.nil? ? sdk_marshal.resource+flatten_func_name : property.custom_ef_func_name -%>(<%= input_var -%>)); err != nil { + return fmt.Errorf("Error setting `<%= prop_name -%>`: %+v", err) +} +<% else -%> +<%= output_var -%>["<%= prop_name -%>"] = flatten<%= property.custom_ef_func_name.nil? ? sdk_marshal.resource+flatten_func_name : property.custom_ef_func_name -%>(<%= input_var -%>) +<% end -%> \ No newline at end of file diff --git a/templates/azure/terraform/schemas/float_array_set.erb b/templates/azure/terraform/schemas/float_array_set.erb new file mode 100644 index 000000000000..69b3d2532aba --- /dev/null +++ b/templates/azure/terraform/schemas/float_array_set.erb @@ -0,0 +1,7 @@ +<% if output_var == 'd' -%> +if err := d.Set("<%= prop_name -%>", utils.FlattenFloat64Slice(<%= input_var -%>)); err != nil { + return fmt.Errorf("Error setting `<%= prop_name -%>`: %+v", err) +} +<% else -%> +<%= output_var -%>["<%= prop_name -%>"] = utils.FlattenFloat64Slice(<%= input_var -%>) +<% end -%> diff --git a/templates/azure/terraform/schemas/hide_from_schema.erb b/templates/azure/terraform/schemas/hide_from_schema.erb new file mode 100644 index 000000000000..06d187eff3ff --- /dev/null +++ b/templates/azure/terraform/schemas/hide_from_schema.erb @@ -0,0 +1 @@ +// TODO: Implement customized logic for property '<%= property.name -%>' since it is not included in schema \ No newline at end of file diff --git a/templates/azure/terraform/schemas/integer_array_set.erb b/templates/azure/terraform/schemas/integer_array_set.erb new file mode 100644 index 000000000000..d3b7d44efc7d --- /dev/null +++ b/templates/azure/terraform/schemas/integer_array_set.erb @@ -0,0 +1,9 @@ +<% bit_number = 32 if sdk_marshal.sdktype.type_definition.is_a?(Api::Azure::SDKTypeDefinition::Integer32ArrayObject) -%> +<% bit_number = 64 if sdk_marshal.sdktype.type_definition.is_a?(Api::Azure::SDKTypeDefinition::Integer64ArrayObject) -%> +<% if output_var == 'd' -%> +if err := d.Set("<%= prop_name -%>", utils.FlattenInteger<%= bit_number -%>Slice(<%= input_var -%>)); err != nil { + return fmt.Errorf("Error setting `<%= prop_name -%>`: %+v", err) +} +<% else -%> +<%= output_var -%>["<%= prop_name -%>"] = utils.FlattenInteger<%= bit_number -%>Slice(<%= input_var -%>) +<% end -%> diff --git a/templates/azure/terraform/schemas/key_value_pairs_set.erb b/templates/azure/terraform/schemas/key_value_pairs_set.erb new file mode 100644 index 000000000000..19cfd77b1397 --- /dev/null +++ b/templates/azure/terraform/schemas/key_value_pairs_set.erb @@ -0,0 +1,5 @@ +<% if output_var == 'd' -%> +d.Set("<%= prop_name -%>", utils.FlattenKeyValuePairs(<%= input_var -%>)) +<% else -%> +<%= output_var -%>["<%= prop_name -%>"] = utils.FlattenKeyValuePairs(<%= input_var -%>) +<% end -%> diff --git a/templates/azure/terraform/schemas/location.erb b/templates/azure/terraform/schemas/location.erb new file mode 100644 index 000000000000..6d8a7d13d08e --- /dev/null +++ b/templates/azure/terraform/schemas/location.erb @@ -0,0 +1 @@ +"<%= property.name.underscore -%>": azure.SchemaLocation(), \ No newline at end of file diff --git a/templates/azure/terraform/schemas/location_get.erb b/templates/azure/terraform/schemas/location_get.erb new file mode 100644 index 000000000000..d76b2793b91f --- /dev/null +++ b/templates/azure/terraform/schemas/location_get.erb @@ -0,0 +1,5 @@ +<% if input_var == 'd' -%> +<%= output_var -%> := azure.NormalizeLocation(d.Get("<%= prop_name -%>").(<%= go_type(property) -%>)) +<% else -%> +<%= output_var -%> := azure.NormalizeLocation(<%= input_var -%>["<%= prop_name -%>"].(<%= go_type(property) -%>)) +<% end -%> diff --git a/templates/azure/terraform/schemas/location_set.erb b/templates/azure/terraform/schemas/location_set.erb new file mode 100644 index 000000000000..51875c9796eb --- /dev/null +++ b/templates/azure/terraform/schemas/location_set.erb @@ -0,0 +1,7 @@ +if location := <%= input_var -%>; location != nil { +<% if output_var == 'd' -%> + d.Set("<%= prop_name -%>", azure.NormalizeLocation(*location)) +<% else -%> + <%= output_var -%>["<%= prop_name -%>"] = azure.NormalizeLocation(*location) +<% end -%> +} \ No newline at end of file diff --git a/templates/azure/terraform/schemas/primitive.erb b/templates/azure/terraform/schemas/primitive.erb new file mode 100644 index 000000000000..cd66a1bc6d43 --- /dev/null +++ b/templates/azure/terraform/schemas/primitive.erb @@ -0,0 +1,162 @@ +<% + sdk_package = object.azure_sdk_definition.go_client_namespace +-%> +<% if property.flatten_object -%> +<% order_properties(property.properties).each do |prop| -%> +<%= lines(build_azure_schema_property(prop, object, 0, data_source_input)) -%> +<% end -%> +<% elsif tf_types.include?(property.class) -%> +"<%= property.name.underscore -%>": { +<% if property.is_set -%> + Type: schema.TypeSet, +<% else -%> + Type: <%= tf_type(property) %>, +<% end -%> +<% if property.default_from_api && data_source_input.empty? -%> + Computed: true, + Optional: true, +<% if property.schema_config_mode_attr -%> + ConfigMode: schema.SchemaConfigModeAttr, +<% end -%> +<% elsif property.required && (data_source_input.empty? || data_source_input.include?(property)) -%> + Required: true, +<% elsif property.output || (!data_source_input.empty? && !data_source_input.include?(property)) -%> + Computed: true, +<% else -%> + Optional: true, +<% end -%> +<% if property.deprecated? -%> + Deprecated: "<%= property.deprecation_message %>", +<% end -%> +<% if force_new?(property, object) && data_source_input.empty? -%> + ForceNew: true, +<% end -%> +<% if !property.validation.nil? && !property.output -%> +<% if !property.validation.regex.nil? && (data_source_input.empty? || data_source_input.include?(property)) -%> + ValidateFunc: validateRegexp(`<%= property.validation.regex -%>`), +<% elsif !property.validation.function.nil? && (data_source_input.empty? || data_source_input.include?(property)) -%> + ValidateFunc: <%= property.validation.function -%>, +<% end # property.validation.nil? -%> +<% elsif property.required && property.is_a?(Api::Type::String) && (data_source_input.empty? || data_source_input.include?(property)) -%> + ValidateFunc: validate.NoEmptyStrings, +<% end # property.validation.nil? -%> +<% if property.is_a?(Api::Azure::Type::ISO8601DateTime) && property.validation.nil? && !property.output && (data_source_input.empty? || data_source_input.include?(property)) -%> + ValidateFunc: validateRFC3339Date, +<% elsif property.is_a?(Api::Azure::Type::ISO8601Duration) && property.validation.nil? && !property.output && (data_source_input.empty? || data_source_input.include?(property)) -%> + ValidateFunc: validateIso8601Duration(), +<% elsif property.is_a?(Api::Type::Enum) && property.validation.nil? && !property.output && (data_source_input.empty? || data_source_input.include?(property)) -%> +<% + enum_values = property.values + sdk_type = get_sdk_typedef_by_references(property.azure_sdk_references, object.azure_sdk_definition.create.request) +-%> + ValidateFunc: validation.StringInSlice([]string{ +<% enum_values.each do |val| -%> + <%= azure_go_literal((sdk_type.go_enum_const_prefix + val.to_s).to_sym, sdk_package) -%>, +<% end -%> + }, false), +<% end -%> +<% if !property.diff_suppress_func.nil? -%> + DiffSuppressFunc: <%= property.diff_suppress_func %>, +<% elsif property.is_a?(Api::Type::ResourceRef) -%> + DiffSuppressFunc: compareSelfLinkOrResourceName, +<% end -%> +<% unless property.state_func.nil? -%> + StateFunc: <%= property.state_func %>, +<% end -%> +<% if property.is_a?(Api::Type::NestedObject) -%> +<%= lines(' MaxItems: 1,') unless property.output || (!data_source_input.empty? && !data_source_input.include?(property)) -%> + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ +<% order_properties(property.properties).each do |prop| -%> +<%= lines(build_azure_schema_property(prop, object, 12, data_source_input)) -%> +<% end -%> + }, + }, +<% elsif property.is_a?(Api::Type::Array) -%> +<% if !property.min_size.nil? && data_source_input.empty? -%> + MinItems: <%= property.min_size %>, +<% end -%> +<% if !property.max_size.nil? && data_source_input.empty? -%> + MaxItems: <%= property.max_size %>, +<% end -%> +<% if property.item_type.is_a?(Api::Type::NestedObject) -%> +<% if property.is_set -%> + Elem: <%= namespace_property_from_object(property, object) -%>Schema(), +<% else -%> + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ +<% order_properties(property.item_type.properties).each do |prop| -%> +<%= lines(build_azure_schema_property(prop, object, 12, data_source_input)) -%> +<% end -%> + }, + }, +<% end -%> +<% elsif property.item_type.is_a?(String) # Basic type like Api::Type::String -%> + Elem: &schema.Schema{ + Type: <%= tf_types[property.item_type] -%>, + }, +<% else # array of basic types -%> + Elem: &schema.Schema{ + Type: <%= tf_types[property.item_type.class] -%>, +<% if property.item_type.is_a?(Api::Type::Enum) && property.validation.nil? && !property.output + enum_values = property.item_type.values + sdk_type = get_sdk_typedef_by_references(property.azure_sdk_references, object.azure_sdk_definition.create.request) +-%> + ValidateFunc: validation.StringInSlice([]string{ +<% enum_values.each do |val| -%> + <%= azure_go_literal((sdk_type.go_enum_const_prefix + val.to_s).to_sym, sdk_package) -%>, +<% end -%> + }, false), +<% end -%> + }, +<% end -%> +<% if property.is_set -%> +<% if !property.set_hash_func.nil? -%> + Set: <%= property.set_hash_func -%>, +<% elsif property.item_type.is_a?(String) -%> + Set: schema.HashString, +<% else -%> + // Default schema.HashSchema is used. +<% end -%> +<% end -%> +<% elsif property.is_a?(Api::Type::KeyValuePairs) -%> + Elem: &schema.Schema{Type: schema.TypeString}, +<% elsif property.is_a?(Api::Type::Map) -%> + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "<%= property.key_name -%>": { + Type: schema.TypeString, + Required: true, +<% if force_new?(property, object) -%> + ForceNew: true, +<% end -%> + }, +<% order_properties(property.value_type.properties).each do |prop| -%> +<%= lines(build_azure_schema_property(prop, object, 16, data_source_input)) -%> +<% end -%> + }, + }, +<% if !property.set_hash_func.nil? -%> + Set: <%= property.set_hash_func -%>, +<% end -%> +<% end -%> +<% if property.sensitive -%> + Sensitive: true, +<% end -%> +<% unless property.output || property.default_value.nil? || !data_source_input.empty? -%> +<% if property.is_a?(Api::Type::Enum) -%> + Default: <%= azure_go_literal((sdk_type.go_enum_const_prefix + property.default_value.to_s).to_sym, sdk_package) -%>, +<% elsif property.is_a?(Api::Azure::Type::BooleanEnum) -%> + Default: <%= (property.default_value == property.true_value).to_s -%>, +<% else -%> + Default: <%= azure_go_literal(property.default_value) -%>, +<% end -%> +<% end -%> +<% unless property.conflicting().empty? -%> +<% conflicting_props = property.conflicting().map(&:name).map(&:underscore) -%> + ConflictsWith: <%= azure_go_literal(conflicting_props) -%>, +<% end -%> +}, +<% else -%> +// TODO: Property '<%= property.name -%>' of type <%= property.class -%> is not supported in primitive.erb +<% end # tf_types.include?(property.class) -%> diff --git a/templates/azure/terraform/schemas/resource_group_name.erb b/templates/azure/terraform/schemas/resource_group_name.erb new file mode 100644 index 000000000000..e37ee7bc80c7 --- /dev/null +++ b/templates/azure/terraform/schemas/resource_group_name.erb @@ -0,0 +1 @@ +"<%= prop_name -%>": azure.SchemaResourceGroupName(), \ No newline at end of file diff --git a/templates/azure/terraform/schemas/string_array_set.erb b/templates/azure/terraform/schemas/string_array_set.erb new file mode 100644 index 000000000000..91774c2304ef --- /dev/null +++ b/templates/azure/terraform/schemas/string_array_set.erb @@ -0,0 +1,5 @@ +<% if output_var == 'd' -%> +d.Set("<%= prop_name -%>", utils.FlattenStringSlice(<%= input_var -%>)) +<% else -%> +<%= output_var -%>["<%= prop_name -%>"] = utils.FlattenStringSlice(<%= input_var -%>) +<% end -%> \ No newline at end of file diff --git a/templates/azure/terraform/schemas/tags.erb b/templates/azure/terraform/schemas/tags.erb new file mode 100644 index 000000000000..a890164c48df --- /dev/null +++ b/templates/azure/terraform/schemas/tags.erb @@ -0,0 +1 @@ +"<%= property.name.underscore -%>": tags.Schema(), \ No newline at end of file diff --git a/templates/azure/terraform/schemas/tags_set.erb b/templates/azure/terraform/schemas/tags_set.erb new file mode 100644 index 000000000000..7fd0dff35817 --- /dev/null +++ b/templates/azure/terraform/schemas/tags_set.erb @@ -0,0 +1,5 @@ +<% if output_var == 'd' -%> +tags.FlattenAndSet(d, <%= input_var -%>) +<% else -%> +// TODO: setting tags to <%= output_var -%> is not supported +<% end -%> \ No newline at end of file diff --git a/templates/azure/terraform/schemas/unsupport.erb b/templates/azure/terraform/schemas/unsupport.erb new file mode 100644 index 000000000000..5259e075da8d --- /dev/null +++ b/templates/azure/terraform/schemas/unsupport.erb @@ -0,0 +1 @@ +// TODO: Property '<%= prop_name -%>' of type <%= property.class -%> is not supported \ No newline at end of file diff --git a/templates/azure/terraform/sdk/azure_id_parser.erb b/templates/azure/terraform/sdk/azure_id_parser.erb new file mode 100644 index 000000000000..f75f0f895dd1 --- /dev/null +++ b/templates/azure/terraform/sdk/azure_id_parser.erb @@ -0,0 +1,8 @@ +id, err := parse.<%= object.name -%>ID(d.Id()) +if err != nil { + return err +} +<% sdk_op_def.request.reject {|k, v| v.id_portion.nil?}.each_value do |param| -%> +<%= param.go_variable_name -%> := id.<%= param.go_variable_name.capitalize-%> + +<% end -%> \ No newline at end of file diff --git a/templates/azure/terraform/sdk/errorf_with_resource_name.erb b/templates/azure/terraform/sdk/errorf_with_resource_name.erb new file mode 100644 index 000000000000..3770ad6e3348 --- /dev/null +++ b/templates/azure/terraform/sdk/errorf_with_resource_name.erb @@ -0,0 +1,23 @@ +<% + res_name = "" + res_var = nil + depends = [] + depends_vars = [] + properties.select{|p| !get_applicable_reference(p.azure_sdk_references, sdk_op_def.request).nil? && !get_applicable_reference(p.azure_sdk_references, sdk_op_def.request).start_with?("/") }.each do |prop| + param = get_sdk_typedef_by_references(prop.azure_sdk_references, sdk_op_def.request) + case prop.name + when "name" + res_name = "#{get_property_value(prop, "name_in_logs", nil) || object.name.titlecase} %q" + res_var = param.go_variable_name + when "resourceGroupName" + depends += ["Resource Group %q"] + depends_vars += [param.go_variable_name] + else + depends += ["#{get_property_value(prop, "name_in_logs", nil) || prop.name.titlecase} %q"] + depends_vars += [param.go_variable_name] + end + end + res_name = "#{get_property_value(object, "name_in_logs", nil) || object.name.titlecase}" if res_name == "" + res_name += " (#{depends.reverse.join(' / ')})" unless depends.empty? +-%> +fmt.Errorf("<%= format_string % [res_name] -%><%= include_error ? ": %+v" : "" -%>", <%= (([res_var] + (depends_vars.reverse)).compact).join(", ") -%><%= include_error ? ", err" : "" -%>) \ No newline at end of file diff --git a/templates/azure/terraform/sdk/function_invocation.erb b/templates/azure/terraform/sdk/function_invocation.erb new file mode 100644 index 000000000000..9c32466c85f8 --- /dev/null +++ b/templates/azure/terraform/sdk/function_invocation.erb @@ -0,0 +1,3 @@ +<% params = sdk_op_def.request.reject {|k, v| k.start_with?("/") && k != "/"} -%> +<% param_vars = params.values.map{|v| (v.is_pointer_type ? '&' : '') + v.go_variable_name} -%> +client.<%= sdk_op_def.go_func_name -%>(ctx, <%= param_vars.join(", ") -%>) \ No newline at end of file diff --git a/templates/azure/terraform/sdktypes/datetime_and_duration_field_assign.erb b/templates/azure/terraform/sdktypes/datetime_and_duration_field_assign.erb new file mode 100644 index 000000000000..1ee034a30916 --- /dev/null +++ b/templates/azure/terraform/sdktypes/datetime_and_duration_field_assign.erb @@ -0,0 +1,8 @@ +<%= sdk_marshal.sdktype.go_field_name -%><%= in_structure ? ': ' : ' = ' -%> +<% if property.is_a?(Api::Azure::Type::ISO8601Duration) -%> +utils.String(<%= property.name.camelcase(:lower) -%> +<% elsif property.is_a?(Api::Azure::Type::ISO8601DateTime) -%> +<% sdk_marshal.enqueue(property, $global_expand_queue) -%> +convertStringToDate(<%= property.name.camelcase(:lower) -%> +<% end -%> +)<%= ',' if in_structure -%> diff --git a/templates/azure/terraform/sdktypes/enum_field_assign.erb b/templates/azure/terraform/sdktypes/enum_field_assign.erb new file mode 100644 index 000000000000..9ac558c971e4 --- /dev/null +++ b/templates/azure/terraform/sdktypes/enum_field_assign.erb @@ -0,0 +1 @@ +<%= sdk_marshal.sdktype.go_field_name -%><%= in_structure ? ': ' : ' = ' -%><%= sdk_marshal.package -%>.<%= sdk_marshal.sdktype.type_definition.go_enum_type_name -%>(<%= property.name.camelcase(:lower) -%>)<%= ',' if in_structure -%> \ No newline at end of file diff --git a/templates/azure/terraform/sdktypes/enum_schema_assign.erb b/templates/azure/terraform/sdktypes/enum_schema_assign.erb new file mode 100644 index 000000000000..f2cc40a01b47 --- /dev/null +++ b/templates/azure/terraform/sdktypes/enum_schema_assign.erb @@ -0,0 +1,4 @@ +<% input_var = "string(" + input + "." + sdk_marshal.sdktype.type_definition.go_field_name + ")" -%> +<% get_properties_matching_sdk_reference(sdk_marshal.properties, sdk_marshal.sdktype.typedef_reference).each do |property| -%> +<%= lines(build_schema_property_set(input_var, output, property, sdk_marshal.clone())) -%> +<% end -%> \ No newline at end of file diff --git a/templates/azure/terraform/sdktypes/expand_func_field_assign.erb b/templates/azure/terraform/sdktypes/expand_func_field_assign.erb new file mode 100644 index 000000000000..727c7254be2a --- /dev/null +++ b/templates/azure/terraform/sdktypes/expand_func_field_assign.erb @@ -0,0 +1,15 @@ +<%= sdk_marshal.sdktype.go_field_name -%><%= in_structure ? ': ' : ' = ' -%> +<% var_name = sdk_marshal.sdktype.type_definition.go_variable_name -%> +<% special_known_name = (property.name == 'tags' ? 't' : nil) -%> +<% input_var = var_name || special_known_name || property.name.camelcase(:lower) -%> +<% if expand_funcs.include?(property.class) -%> +<%= expand_func(property) -%>(<%= input_var -%> +<% elsif property.is_a?(Api::Type::Array) && (property.item_type.is_a?(Api::Type::String) || property.item_type == "Api::Type::String" || property.item_type == "Api::Azure::Type::ResourceReference") -%> +utils.ExpandStringSlice(<%= property.name.camelcase(:lower) -%> +<% elsif property.is_a?(Api::Type::KeyValuePairs) -%> +utils.ExpandKeyValuePairs(<%= property.name.camelcase(:lower) -%> +<% else -%> +<% expand_func_name = sdk_marshal.enqueue(property, $global_expand_queue) -%> +expand<%= property.custom_ef_func_name.nil? ? sdk_marshal.resource+expand_func_name : property.custom_ef_func_name -%>(<%= property.name.camelcase(:lower) -%> +<% end -%> +)<%= ',' if in_structure -%> \ No newline at end of file diff --git a/templates/azure/terraform/sdktypes/float_array_field_assign.erb b/templates/azure/terraform/sdktypes/float_array_field_assign.erb new file mode 100644 index 000000000000..3ab87586aa8a --- /dev/null +++ b/templates/azure/terraform/sdktypes/float_array_field_assign.erb @@ -0,0 +1,3 @@ +<%= sdk_marshal.sdktype.go_field_name -%><%= in_structure ? ': ' : ' = ' -%> +utils.ExpandFloat64Slice(<%= property.name.camelcase(:lower) -%> +)<%= ',' if in_structure -%> diff --git a/templates/azure/terraform/sdktypes/integer_array_field_assign.erb b/templates/azure/terraform/sdktypes/integer_array_field_assign.erb new file mode 100644 index 000000000000..48e73090483f --- /dev/null +++ b/templates/azure/terraform/sdktypes/integer_array_field_assign.erb @@ -0,0 +1,5 @@ +<% bit_number = 32 if sdk_marshal.sdktype.type_definition.is_a?(Api::Azure::SDKTypeDefinition::Integer32ArrayObject) -%> +<% bit_number = 64 if sdk_marshal.sdktype.type_definition.is_a?(Api::Azure::SDKTypeDefinition::Integer64ArrayObject) -%> +<%= sdk_marshal.sdktype.go_field_name -%><%= in_structure ? ': ' : ' = ' -%> +utils.ExpandInteger<%= bit_number -%>Slice(<%= property.name.camelcase(:lower) -%> +)<%= ',' if in_structure -%> diff --git a/templates/azure/terraform/sdktypes/integer_field_assign.erb b/templates/azure/terraform/sdktypes/integer_field_assign.erb new file mode 100644 index 000000000000..793601dc7fdf --- /dev/null +++ b/templates/azure/terraform/sdktypes/integer_field_assign.erb @@ -0,0 +1,2 @@ +<% bit_number = sdk_marshal.sdktype.type_definition.is_a?(Api::Azure::SDKTypeDefinition::Integer32Object) ? 32 : 64 -%> +<%= sdk_marshal.sdktype.go_field_name -%><%= in_structure ? ': ' : ' = ' -%>utils.Int<%= bit_number -%>(int<%= bit_number -%>(<%= property.name.camelcase(:lower) -%>))<%= ',' if in_structure -%> \ No newline at end of file diff --git a/templates/azure/terraform/sdktypes/nested_object_field_assign.erb b/templates/azure/terraform/sdktypes/nested_object_field_assign.erb new file mode 100644 index 000000000000..961bbc7d9e18 --- /dev/null +++ b/templates/azure/terraform/sdktypes/nested_object_field_assign.erb @@ -0,0 +1,3 @@ +<%= sdk_marshal.sdktype.go_field_name -%><%= in_structure ? ': ' : ' = ' -%>&<%= sdk_marshal.package -%>.<%= sdk_marshal.sdktype.go_type_name -%>{ +<%= lines(build_property_to_sdk_object(sdk_marshal)) -%> +}<%= ',' if in_structure -%> \ No newline at end of file diff --git a/templates/azure/terraform/sdktypes/nested_object_schema_assign.erb b/templates/azure/terraform/sdktypes/nested_object_schema_assign.erb new file mode 100644 index 000000000000..51dfe77aa78d --- /dev/null +++ b/templates/azure/terraform/sdktypes/nested_object_schema_assign.erb @@ -0,0 +1,7 @@ +<% + sdk_type = sdk_marshal.sdktype.type_definition + temp_var = sdk_type.go_variable_name || sdk_type.go_field_name.camelcase(:lower) || input +-%> +if <%= temp_var -%> := <%= input -%>.<%= sdk_type.go_field_name -%>; <%= temp_var -%> != nil { +<%= lines(build_sdk_object_to_property(temp_var, output, sdk_marshal.clone())) -%> +} \ No newline at end of file diff --git a/templates/azure/terraform/sdktypes/plain_var_field_assign.erb b/templates/azure/terraform/sdktypes/plain_var_field_assign.erb new file mode 100644 index 000000000000..0d02505ea127 --- /dev/null +++ b/templates/azure/terraform/sdktypes/plain_var_field_assign.erb @@ -0,0 +1 @@ +<%= sdk_marshal.sdktype.go_field_name -%><%= in_structure ? ': ' : ' = ' -%><%= property.name.camelcase(:lower) -%><%= ',' if in_structure -%> \ No newline at end of file diff --git a/templates/azure/terraform/sdktypes/primitive_schema_assign.erb b/templates/azure/terraform/sdktypes/primitive_schema_assign.erb new file mode 100644 index 000000000000..71d2e5e00dd3 --- /dev/null +++ b/templates/azure/terraform/sdktypes/primitive_schema_assign.erb @@ -0,0 +1,6 @@ +<% input_var = sdk_marshal.sdktype.type_definition.go_variable_name || input + "." + sdk_marshal.sdktype.type_definition.go_field_name -%> +<% get_properties_matching_sdk_reference(sdk_marshal.properties, sdk_marshal.sdktype.typedef_reference).each do |property| + if !property.is_a?(Api::Azure::Type::Tags) -%> +<%= lines(build_schema_property_set(input_var, output, property, sdk_marshal.clone())) -%> +<% end -%> +<% end -%> \ No newline at end of file diff --git a/templates/azure/terraform/sdktypes/property_to_sdkobject.erb b/templates/azure/terraform/sdktypes/property_to_sdkobject.erb new file mode 100644 index 000000000000..6a2557cfaa54 --- /dev/null +++ b/templates/azure/terraform/sdktypes/property_to_sdkobject.erb @@ -0,0 +1,29 @@ +<% + direct_children = Set.new + sdk_marshal.properties.sort_by{|p| [p.order, p.name]}.each do |prop| + prop.azure_sdk_references.select{|ref| sdk_marshal.sdktype.type_definitions.has_key?(ref)}.each do |child_api_path| + child_type_def = sdk_marshal.sdktype.type_definitions[child_api_path] + api_path = sdk_marshal.sdktype.typedef_reference + if api_path == '/' && child_api_path.start_with?(api_path) + sub_paths = child_api_path.split('/') + api_path = '' + elsif child_api_path.start_with?(api_path + '/') + sub_paths = child_api_path[api_path.length..-1].split('/') + end + skip_empty = child_type_def.empty_value_sensitive && !include_empty + if !sub_paths.nil? && sub_paths.length > 1 && sub_paths[0] == '' && !skip_empty + sub_api_path = api_path + '/' + sub_paths[1] + direct_children << sub_api_path if sub_paths.length > 2 + direct_children << child_api_path if sub_paths.length == 2 + end + end if !prop.output + end + + direct_children.to_a.sort.each do |child_api_path| + matched_properties = get_properties_matching_sdk_reference(sdk_marshal.properties, child_api_path) +-%> +<%= lines(build_sdk_field_assignment(nil, sdk_marshal.clone(child_api_path))) if matched_properties.empty? -%> +<% matched_properties.each do |property| -%> +<%= lines(build_sdk_field_assignment(property, sdk_marshal.clone(child_api_path))) if !property.output-%> +<% end -%> +<% end -%> diff --git a/templates/azure/terraform/sdktypes/property_to_sdkobject_empty_sensitive.erb b/templates/azure/terraform/sdktypes/property_to_sdkobject_empty_sensitive.erb new file mode 100644 index 000000000000..d6a863b62894 --- /dev/null +++ b/templates/azure/terraform/sdktypes/property_to_sdkobject_empty_sensitive.erb @@ -0,0 +1,34 @@ +<% + sdk_marshal.properties.sort_by{|p| [p.order, p.name]}.each do |prop| + prop.azure_sdk_references.select{|ref| sdk_marshal.sdktype.type_definitions.has_key?(ref)}.each do |sdk_reference| + sdk_type = sdk_marshal.sdktype.type_definitions[sdk_reference] + if sdk_type.empty_value_sensitive + # Get the first initialized ancester + ancesters = [sdk_reference] + begin + parent_ref = ancesters[-1].split('/')[0..-2].join('/') + parent_type = sdk_marshal.sdktype.type_definitions[parent_ref] + initialized = sdk_marshal.properties.any? do |p| + p.azure_sdk_references.select{|r| sdk_marshal.sdktype.type_definitions.has_key?(r)}.any? do |r| + r.start_with?(parent_ref) && !sdk_marshal.sdktype.type_definitions[r].empty_value_sensitive + end + end + ancesters <<= parent_ref + end until !parent_type.empty_value_sensitive && initialized + + local_var = sdk_type.go_variable_name || prop.name.camelcase(:lower) +-%> +if <%= local_var -%> != <%= go_empty_value(prop) -%> { +<% if ancesters.size() == 2 -%> + parameters.<%= ancesters.reverse[0..-2].map{|r| sdk_marshal.sdktype.type_definitions[r].go_field_name}.join('.') -%>.<%= lines(build_sdk_field_assignment(prop, sdk_marshal.clone(sdk_reference, [prop]), false)) -%> +} +<% else -%> + parameters.<%= ancesters.reverse[0..1].map{|r| sdk_marshal.sdktype.type_definitions[r].go_field_name}.join('.') -%> = &<%= sdk_marshal.package -%>.<%= sdk_marshal.sdktype.type_definitions[ancesters[-2]].go_type_name -%>{ +<%= lines(build_property_to_sdk_object(sdk_marshal.clone(ancesters[-2], [prop]), 8, true)) -%> + } +} + +<% end -%> +<% end -%> +<% end -%> +<% end -%> \ No newline at end of file diff --git a/templates/azure/terraform/sdktypes/sdkobject_to_property.erb b/templates/azure/terraform/sdktypes/sdkobject_to_property.erb new file mode 100644 index 000000000000..fe5cab5b2d60 --- /dev/null +++ b/templates/azure/terraform/sdktypes/sdkobject_to_property.erb @@ -0,0 +1,30 @@ +<% + direct_children = Set.new + sdk_marshal.properties.sort_by{|p| [p.order, p.name]}.each do |prop| + child_api_path = nil + prop.azure_sdk_references.each do |ref| + child_api_path = ref if sdk_marshal.sdktype.operation.response.has_key?(ref) || (sdk_marshal.sdktype.operation.request.has_key?(ref) && child_api_path.nil?) + end + unless child_api_path.nil? + if sdk_marshal.sdktype.typedef_reference == '' + sub_paths = child_api_path.split('/') + direct_children << child_api_path if sub_paths.length == 1 + elsif child_api_path.start_with?(sdk_marshal.sdktype.typedef_reference + '/') + sub_paths = child_api_path[sdk_marshal.sdktype.typedef_reference.length..-1].split('/') + end + if !sub_paths.nil? && sub_paths.length > 1 && sub_paths[0] == '' + sub_api_path = sdk_marshal.sdktype.typedef_reference + '/' + sub_paths[1] + direct_children << sub_api_path if sub_paths.length > 2 + direct_children << child_api_path if sub_paths.length == 2 + end + end + end + + direct_children.each do |child_api_path| + matched_properties = get_properties_matching_sdk_reference(sdk_marshal.properties, child_api_path) +-%> +<%= lines(build_schema_assignment(input, output, nil, sdk_marshal.clone(child_api_path))) if matched_properties.empty? -%> +<% matched_properties.each do |property| -%> +<%= lines(build_schema_assignment(input, output, property, sdk_marshal.clone(child_api_path))) -%> +<% end -%> +<% end -%> \ No newline at end of file diff --git a/templates/azure/terraform/sdktypes/unsupport.erb b/templates/azure/terraform/sdktypes/unsupport.erb new file mode 100644 index 000000000000..80eb41ad8cb5 --- /dev/null +++ b/templates/azure/terraform/sdktypes/unsupport.erb @@ -0,0 +1 @@ +// TODO: SDK Reference <%= sdk_marshal.sdktype.typedef_reference -%> is not supported \ No newline at end of file diff --git a/templates/azure/terraform/test_file.go.erb b/templates/azure/terraform/test_file.go.erb new file mode 100644 index 000000000000..47304a502202 --- /dev/null +++ b/templates/azure/terraform/test_file.go.erb @@ -0,0 +1,126 @@ +<%# lines(autogen_notice :go) -%> +package azurerm + +import ( + "fmt" + "strings" + "testing" + + "github.com/hashicorp/terraform/helper/acctest" + "github.com/hashicorp/terraform/helper/resource" + "github.com/hashicorp/terraform/terraform" + "github.com/terraform-providers/terraform-provider-azurerm/azurerm/helpers/tf" + "github.com/terraform-providers/terraform-provider-azurerm/azurerm/utils" +) +<% + resource_name = "AzureRM" + object.name + terraform_name = "azurerm_" + object.name.underscore + properties = object.all_user_properties + contains_acctests = object.respond_to?(:acctests) && !object.acctests.nil? && !object.acctests.empty? + provider_name = object.api_name + provider_client_name = object.azure_sdk_definition.go_client +-%> + +<% + test_hcls = Hash.new + if contains_acctests + object.acctests.each do |test| + test.steps.uniq.each do |name| + test_hcl, random_vars = build_test_hcl_from_example(name) + test_hcls[name] = { :hcl => test_hcl, :random_vars => random_vars } + end +-%> +func TestAcc<%= resource_name -%>_<%= test.name -%>(t *testing.T) { + data := acceptance.BuildTestData(t, "<%= terraform_name -%>", "test") + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { acceptance.PreCheck(t) }, + Providers: acceptance.SupportedProviders, + CheckDestroy: testCheck<%= resource_name -%>Destroy, + Steps: []resource.TestStep{ +<% test.steps.each do |step| -%> +<% props_to_check = get_example_properties_to_check(step, object) -%> + { + Config: testAcc<%= resource_name -%>_<%= step -%>(data), + Check: resource.ComposeTestCheckFunc( + testCheck<%= resource_name -%>Exists(data.ResourceName), +<% props_to_check.each do |propName, propValue| -%> +<% if propValue == :AttrSet -%> + resource.TestCheckResourceAttrSet(data.ResourceName, "<%= propName -%>"), +<% else -%> + resource.TestCheckResourceAttr(data.ResourceName, "<%= propName -%>", "<%= propValue -%>"), +<% end -%> +<% end -%> + ), + }, +<% end -%> + data.ImportStep(), + }, + }) +} +<% end -%> +<% end -%> + +func testCheck<%= resource_name -%>Exists(resourceName string) resource.TestCheckFunc { + return func(s *terraform.State) error { + rs, ok := s.RootModule().Resources[resourceName] + if !ok { + return fmt.Errorf("<%= object.name.titlecase -%> not found: %s", resourceName) + } + +<%= lines(build_acctest_parameters_from_schema(object.azure_sdk_definition.read, properties, object)) -%> + + client := acceptance.AzureProvider.Meta().(*clients.Client).<%= provider_name -%>.<%= provider_client_name -%> + + ctx := acceptance.AzureProvider.Meta().(*clients.Client).StopContext + + if resp, err := <%= build_sdk_func_invocation(object.azure_sdk_definition.read) -%>; err != nil { + if utils.ResponseWasNotFound(resp.Response) { + return <%= build_errorf_with_resource_name("Bad: %s does not exist", false, object.azure_sdk_definition.delete, properties, object) -%> + + } + return fmt.Errorf("Bad: Get on <%= provider_client_name -%>: %+v", err) + } + + return nil + } +} + +func testCheck<%= resource_name -%>Destroy(s *terraform.State) error { + client := acceptance.AzureProvider.Meta().(*clients.Client).<%= provider_name -%>.<%= provider_client_name -%> + + ctx := acceptance.AzureProvider.Meta().(*clients.Client).StopContext + + for _, rs := range s.RootModule().Resources { + if rs.Type != "<%= terraform_name -%>" { + continue + } + +<%= lines(build_acctest_parameters_from_schema(object.azure_sdk_definition.read, properties, object)) -%> + + if resp, err := <%= build_sdk_func_invocation(object.azure_sdk_definition.read) -%>; err != nil { + if !utils.ResponseWasNotFound(resp.Response) { + return fmt.Errorf("Bad: Get on <%= provider_client_name -%>: %+v", err) + } + } + + return nil + } + + return nil +} + +<% + if contains_acctests + test_hcls.each do |name, test_hcl| + uniq_params = test_hcl[:random_vars].uniq(&:parameter_name).sort_by(&:declare_order).map{|p| "#{p.parameter_name} #{p.go_type}"} +-%> +func testAcc<%= resource_name -%>_<%= name -%>(data acceptance.TestData) string { + return fmt.Sprintf(` +<%= lines(test_hcl[:hcl]) -%> +`, <%= test_hcl[:random_vars].map(&:create_expression).join(", ") -%>) +} + +<% + end + end +-%> \ No newline at end of file diff --git a/templates/terraform/nested_property_documentation.erb b/templates/terraform/nested_property_documentation.erb index 5d49c4071158..fee1bb16d3bb 100644 --- a/templates/terraform/nested_property_documentation.erb +++ b/templates/terraform/nested_property_documentation.erb @@ -13,4 +13,4 @@ The `<%= property.name.underscore -%>` block <%= if property.output then "contai <% property.nested_properties.each do |prop| -%> <%= lines(build_nested_property_documentation(prop)) -%> <% end -%> -<% end -%> +<% end -%> \ No newline at end of file