diff --git a/.gitmodules b/.gitmodules index a480d6d1f614..402741a5e646 100644 --- a/.gitmodules +++ b/.gitmodules @@ -66,7 +66,8 @@ url = git@github.com:GoogleCloudPlatform/chef-google-logging [submodule "build/ansible"] path = build/ansible - url = git@github.com:ansible/ansible.git + url = git@github.com:modular-magician/ansible + branch = devel [submodule "build/terraform"] path = build/terraform url = git@github.com:terraform-providers/terraform-provider-google.git diff --git a/build/ansible b/build/ansible index 9706abf68518..ab9b1c843533 160000 --- a/build/ansible +++ b/build/ansible @@ -1 +1 @@ -Subproject commit 9706abf68518dc0f663f23f64475f2b270851ae4 +Subproject commit ab9b1c84353333ae6737a1e9c442daf2eb42002b diff --git a/products/compute/ansible.yaml b/products/compute/ansible.yaml index 5dbc61d22824..595d13464518 100644 --- a/products/compute/ansible.yaml +++ b/products/compute/ansible.yaml @@ -25,19 +25,48 @@ manifest: !ruby/object:Provider::Ansible::Manifest author: Google Inc. (@googlecloudplatform) # This is where custom code would be defined eventually. overrides: !ruby/object:Provider::ResourceOverrides - Firewall: !ruby/object:Provider::Ansible::ResourceOverride - exclude: true + BackendService: !ruby/object:Provider::Ansible::ResourceOverride + properties: + timeoutSec: !ruby/object:Provider::Ansible::PropertyOverride + aliases: + - timeout_seconds + Disk: !ruby/object:Provider::Ansible::ResourceOverride + editable: false + HealthCheck: !ruby/object:Provider::Ansible::ResourceOverride + properties: + timeoutSec: !ruby/object:Provider::Ansible::PropertyOverride + aliases: + - timeout_seconds + HttpHealthCheck: !ruby/object:Provider::Ansible::ResourceOverride + properties: + timeoutSec: !ruby/object:Provider::Ansible::PropertyOverride + aliases: + - timeout_seconds + checkIntervalSec: !ruby/object:Provider::Ansible::PropertyOverride + aliases: + - check_interval_seconds + HttpsHealthCheck: !ruby/object:Provider::Ansible::ResourceOverride + properties: + timeoutSec: !ruby/object:Provider::Ansible::PropertyOverride + aliases: + - timeout_seconds Instance: !ruby/object:Provider::Ansible::ResourceOverride - exclude: true - InstanceGroupManager: !ruby/object:Provider::Ansible::ResourceOverride - exclude: true + provider_helpers: + - 'products/compute/helpers/provider_instance.py' + - 'products/compute/helpers/instance_metadata.py' + InstanceGroup: !ruby/object:Provider::Ansible::ResourceOverride + editable: false InstanceTemplate: !ruby/object:Provider::Ansible::ResourceOverride - exclude: true + provider_helpers: + - 'products/compute/helpers/provider_instance.py' + - 'products/compute/helpers/instance_metadata.py' + TargetPool: !ruby/object:Provider::Ansible::ResourceOverride + provider_helpers: + - 'products/compute/helpers/provider_target_pool.py' + # Not yet implemented. Snapshot: !ruby/object:Provider::Ansible::ResourceOverride exclude: true - TargetHttpsProxy: !ruby/object:Provider::Ansible::ResourceOverride - exclude: true - TargetSslProxy: !ruby/object:Provider::Ansible::ResourceOverride + TargetVpnGateway: !ruby/object:Provider::Ansible::ResourceOverride exclude: true # Ansible tasks must alter infrastructure. # This means that virtual objects are a poor fit. @@ -51,33 +80,6 @@ overrides: !ruby/object:Provider::ResourceOverrides exclude: true Zone: !ruby/object:Provider::Ansible::ResourceOverride exclude: true -# TODO(https://github.com/GoogleCloudPlatform/magic-modules/issues/47): Migrate objects to overrides -objects: !ruby/object:Api::Resource::HashArray - Disk: - editable: false - InstanceGroup: - editable: false - HealthCheck: - aliases: - timeoutSec: - - timeout_seconds - HttpHealthCheck: - aliases: - timeoutSec: - - timeout_seconds - HttpsHealthCheck: - aliases: - timeoutSec: - - timeout_seconds - TargetPool: - provider_helpers: - include: - - 'products/compute/helpers/provider_target_pool.py' - # Not implemented yet. - BackendService: - aliases: - timeoutSec: - - timeout_seconds examples: !ruby/object:Api::Resource::HashArray files: !ruby/object:Provider::Config::Files copy: diff --git a/products/compute/examples/ansible/firewall.yaml b/products/compute/examples/ansible/firewall.yaml new file mode 100644 index 000000000000..2d82e2bb9089 --- /dev/null +++ b/products/compute/examples/ansible/firewall.yaml @@ -0,0 +1,37 @@ +# 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. +--- !ruby/object:Provider::Ansible::Example +task: !ruby/object:Provider::Ansible::Task + name: gcp_compute_firewall + code: | + name: <%= ctx[:name] %> + allowed: + - ip_protocol: 'tcp' + ports: + - "22" + target_tags: + - test-ssh-server + - staging-ssh-server + source_tags: + - test-ssh-clients + project: <%= ctx[:project] %> + auth_kind: <%= ctx[:auth_kind] %> + service_account_file: <%= ctx[:service_account_file] %> +verifier: !ruby/object:Provider::Ansible::Verifier + command: | + gcloud compute firewall-rules describe + --project="{{ gcp_project}}" + "{{ resource_name }}" + failure: !ruby/object:Provider::Ansible::ComputeFailureCondition + region: global + type: firewalls diff --git a/products/compute/examples/ansible/instance.yaml b/products/compute/examples/ansible/instance.yaml new file mode 100644 index 000000000000..6c68b7bce73d --- /dev/null +++ b/products/compute/examples/ansible/instance.yaml @@ -0,0 +1,73 @@ +# 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. +--- !ruby/object:Provider::Ansible::Example +dependencies: + - !ruby/object:Provider::Ansible::Task + name: gcp_compute_disk + register: disk + code: | + name: <%= dependency_name('disk', 'instance') %> + size_gb: 50 + source_image: 'projects/ubuntu-os-cloud/global/images/family/ubuntu-1604-lts' + zone: us-central1-a + project: <%= ctx[:project] %> + auth_kind: <%= ctx[:auth_kind] %> + service_account_file: <%= ctx[:service_account_file] %> + - !ruby/object:Provider::Ansible::Task + name: gcp_compute_network + register: network + code: | + name: <%= dependency_name('network', 'instance') %> + project: <%= ctx[:project] %> + auth_kind: <%= ctx[:auth_kind] %> + service_account_file: <%= ctx[:service_account_file] %> + - !ruby/object:Provider::Ansible::Task + name: gcp_compute_address + register: address + code: | + name: <%= dependency_name('address', 'instance') %> + region: 'us-central1' + project: <%= ctx[:project] %> + auth_kind: <%= ctx[:auth_kind] %> + service_account_file: <%= ctx[:service_account_file] %> +task: !ruby/object:Provider::Ansible::Task + name: gcp_compute_instance + code: | + name: <%= ctx[:name] %> + machine_type: n1-standard-1 + disks: + - auto_delete: true + boot: true + source: "{{ disk }}" + metadata: + startup-script-url: 'gs:://graphite-playground/bootstrap.sh' + cost-center: '12345' + network_interfaces: + - network: "{{ network }}" + access_configs: + - name: 'External NAT' + nat_ip: "{{ address }}" + type: 'ONE_TO_ONE_NAT' + zone: 'us-central1-a' + project: <%= ctx[:project] %> + auth_kind: <%= ctx[:auth_kind] %> + service_account_file: <%= ctx[:service_account_file] %> +verifier: !ruby/object:Provider::Ansible::Verifier + command: | + gcloud compute instances describe + --project="{{ gcp_project }}" + --zone="us-central1-a" + "{{ resource_name }}" + failure: !ruby/object:Provider::Ansible::ComputeFailureCondition + region: zones/us-central1-a + type: instances diff --git a/products/compute/examples/ansible/instance_group.yaml b/products/compute/examples/ansible/instance_group.yaml index 305a7e46929c..a466b69dd7b0 100644 --- a/products/compute/examples/ansible/instance_group.yaml +++ b/products/compute/examples/ansible/instance_group.yaml @@ -20,7 +20,6 @@ dependencies: project: <%= ctx[:project] %> auth_kind: <%= ctx[:auth_kind] %> service_account_file: <%= ctx[:service_account_file] %> - task: !ruby/object:Provider::Ansible::Task name: gcp_compute_instance_group code: | diff --git a/products/compute/examples/ansible/instance_group_manager.yaml b/products/compute/examples/ansible/instance_group_manager.yaml new file mode 100644 index 000000000000..210a9398d49e --- /dev/null +++ b/products/compute/examples/ansible/instance_group_manager.yaml @@ -0,0 +1,72 @@ +# 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. +--- !ruby/object:Provider::Ansible::Example +dependencies: + - !ruby/object:Provider::Ansible::Task + name: gcp_compute_network + register: network + code: | + name: <%= dependency_name('network', 'instanceTemplate') %> + project: <%= ctx[:project] %> + auth_kind: <%= ctx[:auth_kind] %> + service_account_file: <%= ctx[:service_account_file] %> + - !ruby/object:Provider::Ansible::Task + name: gcp_compute_address + register: address + code: | + name: <%= dependency_name('address', 'instanceTemplate') %> + region: 'us-west1' + project: <%= ctx[:project] %> + auth_kind: <%= ctx[:auth_kind] %> + service_account_file: <%= ctx[:service_account_file] %> + - !ruby/object:Provider::Ansible::Task + name: gcp_compute_instance_template + register: instancetemplate + code: | + name: <%= ctx[:name] %> + properties: + disks: + - auto_delete: true + boot: true + initialize_params: + source_image: 'projects/ubuntu-os-cloud/global/images/family/ubuntu-1604-lts' + machine_type: n1-standard-1 + network_interfaces: + - network: "{{ network }}" + access_configs: + - name: 'test-config' + type: 'ONE_TO_ONE_NAT' + nat_ip: "{{ address }}" + project: <%= ctx[:project] %> + auth_kind: <%= ctx[:auth_kind] %> + service_account_file: <%= ctx[:service_account_file] %> +task: !ruby/object:Provider::Ansible::Task + name: gcp_compute_instance_group_manager + code: | + name: <%= ctx[:name] %> + base_instance_name: 'test1-child' + instance_template: "{{ instancetemplate }}" + target_size: 3 + zone: 'us-west1-a' + project: <%= ctx[:project] %> + auth_kind: <%= ctx[:auth_kind] %> + service_account_file: <%= ctx[:service_account_file] %> +verifier: !ruby/object:Provider::Ansible::Verifier + command: | + gcloud compute instance-groups managed describe + --zone=us-west1-a + --project="{{ gcp_project}}" + "{{ resource_name }}" + failure: !ruby/object:Provider::Ansible::ComputeFailureCondition + region: zones/us-west1-a + type: instanceGroupManagers diff --git a/products/compute/examples/ansible/instance_template.yaml b/products/compute/examples/ansible/instance_template.yaml new file mode 100644 index 000000000000..07ced2cafde8 --- /dev/null +++ b/products/compute/examples/ansible/instance_template.yaml @@ -0,0 +1,59 @@ +# 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. +--- !ruby/object:Provider::Ansible::Example +dependencies: + - !ruby/object:Provider::Ansible::Task + name: gcp_compute_network + register: network + code: | + name: <%= dependency_name('network', 'instanceTemplate') %> + project: <%= ctx[:project] %> + auth_kind: <%= ctx[:auth_kind] %> + service_account_file: <%= ctx[:service_account_file] %> + - !ruby/object:Provider::Ansible::Task + name: gcp_compute_address + register: address + code: | + name: <%= dependency_name('address', 'instanceTemplate') %> + region: 'us-west1' + project: <%= ctx[:project] %> + auth_kind: <%= ctx[:auth_kind] %> + service_account_file: <%= ctx[:service_account_file] %> +task: !ruby/object:Provider::Ansible::Task + name: gcp_compute_instance_template + code: | + name: <%= ctx[:name] %> + properties: + disks: + - auto_delete: true + boot: true + initialize_params: + source_image: 'projects/ubuntu-os-cloud/global/images/family/ubuntu-1604-lts' + machine_type: n1-standard-1 + network_interfaces: + - network: "{{ network }}" + access_configs: + - name: 'test-config' + type: 'ONE_TO_ONE_NAT' + nat_ip: "{{ address }}" + project: <%= ctx[:project] %> + auth_kind: <%= ctx[:auth_kind] %> + service_account_file: <%= ctx[:service_account_file] %> +verifier: !ruby/object:Provider::Ansible::Verifier + command: | + gcloud compute instance-templates describe + --project="{{ gcp_project}}" + "{{ resource_name }}" + failure: !ruby/object:Provider::Ansible::ComputeFailureCondition + region: global + type: instanceTemplates diff --git a/products/compute/examples/ansible/target_https_proxy.yaml b/products/compute/examples/ansible/target_https_proxy.yaml new file mode 100644 index 000000000000..3ad1af779ff0 --- /dev/null +++ b/products/compute/examples/ansible/target_https_proxy.yaml @@ -0,0 +1,109 @@ +# 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. +--- !ruby/object:Provider::Ansible::Example +dependencies: + - !ruby/object:Provider::Ansible::Task + name: gcp_compute_instance_group + register: instancegroup + code: | + name: <%= dependency_name('instanceGroup', 'targetHttpsProxy') %> + zone: 'us-central1-a' + project: <%= ctx[:project] %> + auth_kind: <%= ctx[:auth_kind] %> + service_account_file: <%= ctx[:service_account_file] %> + - !ruby/object:Provider::Ansible::Task + name: gcp_compute_http_health_check + register: healthcheck + code: | + name: <%= dependency_name('httpHealthCheck', 'targetHttpsProxy') %> + healthy_threshold: 10 + port: 8080 + timeout_sec: 2 + unhealthy_threshold: 5 + project: <%= ctx[:project] %> + auth_kind: <%= ctx[:auth_kind] %> + service_account_file: <%= ctx[:service_account_file] %> + - !ruby/object:Provider::Ansible::Task + name: gcp_compute_backend_service + register: backendservice + code: | + name: <%= dependency_name('backendService', 'targetHttpsProxy') %> + backends: + - group: "{{ instancegroup }}" + health_checks: + - "{{ healthcheck.selfLink }}" + enable_cdn: true + project: <%= ctx[:project] %> + auth_kind: <%= ctx[:auth_kind] %> + service_account_file: <%= ctx[:service_account_file] %> + - !ruby/object:Provider::Ansible::Task + name: gcp_compute_url_map + register: urlmap + code: | + name: <%= dependency_name('urlMap', 'targetHttpsProxy') %> + default_service: "{{ backendservice }}" + project: <%= ctx[:project] %> + auth_kind: <%= ctx[:auth_kind] %> + service_account_file: <%= ctx[:service_account_file] %> + - !ruby/object:Provider::Ansible::Task + name: gcp_compute_ssl_certificate + register: sslcert + code: | + name: <%= dependency_name('sslCert', 'targetHttpsProxy') %> + description: | + "A certificate for testing. Do not use this certificate in production" + certificate: | + -----BEGIN CERTIFICATE----- + MIICqjCCAk+gAwIBAgIJAIuJ+0352Kq4MAoGCCqGSM49BAMCMIGwMQswCQYDVQQG + EwJVUzETMBEGA1UECAwKV2FzaGluZ3RvbjERMA8GA1UEBwwIS2lya2xhbmQxFTAT + BgNVBAoMDEdvb2dsZSwgSW5jLjEeMBwGA1UECwwVR29vZ2xlIENsb3VkIFBsYXRm + b3JtMR8wHQYDVQQDDBZ3d3cubXktc2VjdXJlLXNpdGUuY29tMSEwHwYJKoZIhvcN + AQkBFhJuZWxzb25hQGdvb2dsZS5jb20wHhcNMTcwNjI4MDQ1NjI2WhcNMjcwNjI2 + MDQ1NjI2WjCBsDELMAkGA1UEBhMCVVMxEzARBgNVBAgMCldhc2hpbmd0b24xETAP + BgNVBAcMCEtpcmtsYW5kMRUwEwYDVQQKDAxHb29nbGUsIEluYy4xHjAcBgNVBAsM + FUdvb2dsZSBDbG91ZCBQbGF0Zm9ybTEfMB0GA1UEAwwWd3d3Lm15LXNlY3VyZS1z + aXRlLmNvbTEhMB8GCSqGSIb3DQEJARYSbmVsc29uYUBnb29nbGUuY29tMFkwEwYH + KoZIzj0CAQYIKoZIzj0DAQcDQgAEHGzpcRJ4XzfBJCCPMQeXQpTXwlblimODQCuQ + 4mzkzTv0dXyB750fOGN02HtkpBOZzzvUARTR10JQoSe2/5PIwaNQME4wHQYDVR0O + BBYEFKIQC3A2SDpxcdfn0YLKineDNq/BMB8GA1UdIwQYMBaAFKIQC3A2SDpxcdfn + 0YLKineDNq/BMAwGA1UdEwQFMAMBAf8wCgYIKoZIzj0EAwIDSQAwRgIhALs4vy+O + M3jcqgA4fSW/oKw6UJxp+M6a+nGMX+UJR3YgAiEAvvl39QRVAiv84hdoCuyON0lJ + zqGNhIPGq2ULqXKK8BY= + -----END CERTIFICATE----- + private_key: | + -----BEGIN EC PRIVATE KEY----- + MHcCAQEEIObtRo8tkUqoMjeHhsOh2ouPpXCgBcP+EDxZCB/tws15oAoGCCqGSM49 + AwEHoUQDQgAEHGzpcRJ4XzfBJCCPMQeXQpTXwlblimODQCuQ4mzkzTv0dXyB750f + OGN02HtkpBOZzzvUARTR10JQoSe2/5PIwQ== + -----END EC PRIVATE KEY----- + project: <%= ctx[:project] %> + auth_kind: <%= ctx[:auth_kind] %> + service_account_file: <%= ctx[:service_account_file] %> +task: !ruby/object:Provider::Ansible::Task + name: gcp_compute_target_https_proxy + code: | + name: <%= ctx[:name] %> + ssl_certificates: + - "{{ sslcert }}" + url_map: "{{ urlmap }}" + project: <%= ctx[:project] %> + auth_kind: <%= ctx[:auth_kind] %> + service_account_file: <%= ctx[:service_account_file] %> +verifier: !ruby/object:Provider::Ansible::Verifier + command: | + gcloud compute target-https-proxies describe + --project="{{ gcp_project}}" + "{{ resource_name }}" + failure: !ruby/object:Provider::Ansible::ComputeFailureCondition + region: global + type: targetHttpsProxies diff --git a/products/compute/examples/ansible/target_ssl_proxy.yaml b/products/compute/examples/ansible/target_ssl_proxy.yaml new file mode 100644 index 000000000000..86c45f76f690 --- /dev/null +++ b/products/compute/examples/ansible/target_ssl_proxy.yaml @@ -0,0 +1,104 @@ +# 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. +--- !ruby/object:Provider::Ansible::Example +dependencies: + - !ruby/object:Provider::Ansible::Task + name: gcp_compute_instance_group + register: instancegroup + code: | + name: <%= dependency_name('instanceGroup', 'targetSslProxy') %> + zone: 'us-central1-a' + project: <%= ctx[:project] %> + auth_kind: <%= ctx[:auth_kind] %> + service_account_file: <%= ctx[:service_account_file] %> + - !ruby/object:Provider::Ansible::Task + name: gcp_compute_health_check + register: healthcheck + code: | + name: <%= dependency_name('healthCheck', 'targetSslProxy') %> + type: TCP + tcp_health_check: + port_name: service-health + request: ping + response: pong + healthy_threshold: 10 + timeout_sec: 2 + unhealthy_threshold: 5 + project: <%= ctx[:project] %> + auth_kind: <%= ctx[:auth_kind] %> + service_account_file: <%= ctx[:service_account_file] %> + - !ruby/object:Provider::Ansible::Task + name: gcp_compute_backend_service + register: backendservice + code: | + name: <%= dependency_name('backendService', 'targetSslProxy') %> + backends: + - group: "{{ instancegroup }}" + health_checks: + - "{{ healthcheck.selfLink }}" + protocol: 'SSL' + project: <%= ctx[:project] %> + auth_kind: <%= ctx[:auth_kind] %> + service_account_file: <%= ctx[:service_account_file] %> + - !ruby/object:Provider::Ansible::Task + name: gcp_compute_ssl_certificate + register: sslcert + code: | + name: <%= dependency_name('sslCert', 'targetSslProxy') %> + description: | + "A certificate for testing. Do not use this certificate in production" + certificate: | + -----BEGIN CERTIFICATE----- + MIICqjCCAk+gAwIBAgIJAIuJ+0352Kq4MAoGCCqGSM49BAMCMIGwMQswCQYDVQQG + EwJVUzETMBEGA1UECAwKV2FzaGluZ3RvbjERMA8GA1UEBwwIS2lya2xhbmQxFTAT + BgNVBAoMDEdvb2dsZSwgSW5jLjEeMBwGA1UECwwVR29vZ2xlIENsb3VkIFBsYXRm + b3JtMR8wHQYDVQQDDBZ3d3cubXktc2VjdXJlLXNpdGUuY29tMSEwHwYJKoZIhvcN + AQkBFhJuZWxzb25hQGdvb2dsZS5jb20wHhcNMTcwNjI4MDQ1NjI2WhcNMjcwNjI2 + MDQ1NjI2WjCBsDELMAkGA1UEBhMCVVMxEzARBgNVBAgMCldhc2hpbmd0b24xETAP + BgNVBAcMCEtpcmtsYW5kMRUwEwYDVQQKDAxHb29nbGUsIEluYy4xHjAcBgNVBAsM + FUdvb2dsZSBDbG91ZCBQbGF0Zm9ybTEfMB0GA1UEAwwWd3d3Lm15LXNlY3VyZS1z + aXRlLmNvbTEhMB8GCSqGSIb3DQEJARYSbmVsc29uYUBnb29nbGUuY29tMFkwEwYH + KoZIzj0CAQYIKoZIzj0DAQcDQgAEHGzpcRJ4XzfBJCCPMQeXQpTXwlblimODQCuQ + 4mzkzTv0dXyB750fOGN02HtkpBOZzzvUARTR10JQoSe2/5PIwaNQME4wHQYDVR0O + BBYEFKIQC3A2SDpxcdfn0YLKineDNq/BMB8GA1UdIwQYMBaAFKIQC3A2SDpxcdfn + 0YLKineDNq/BMAwGA1UdEwQFMAMBAf8wCgYIKoZIzj0EAwIDSQAwRgIhALs4vy+O + M3jcqgA4fSW/oKw6UJxp+M6a+nGMX+UJR3YgAiEAvvl39QRVAiv84hdoCuyON0lJ + zqGNhIPGq2ULqXKK8BY= + -----END CERTIFICATE----- + private_key: | + -----BEGIN EC PRIVATE KEY----- + MHcCAQEEIObtRo8tkUqoMjeHhsOh2ouPpXCgBcP+EDxZCB/tws15oAoGCCqGSM49 + AwEHoUQDQgAEHGzpcRJ4XzfBJCCPMQeXQpTXwlblimODQCuQ4mzkzTv0dXyB750f + OGN02HtkpBOZzzvUARTR10JQoSe2/5PIwQ== + -----END EC PRIVATE KEY----- + project: <%= ctx[:project] %> + auth_kind: <%= ctx[:auth_kind] %> + service_account_file: <%= ctx[:service_account_file] %> +task: !ruby/object:Provider::Ansible::Task + name: gcp_compute_target_ssl_proxy + code: | + name: <%= ctx[:name] %> + ssl_certificates: + - "{{ sslcert }}" + service: "{{ backendservice }}" + project: <%= ctx[:project] %> + auth_kind: <%= ctx[:auth_kind] %> + service_account_file: <%= ctx[:service_account_file] %> +verifier: !ruby/object:Provider::Ansible::Verifier + command: | + gcloud compute target-ssl-proxies describe + --project="{{ gcp_project}}" + "{{ resource_name }}" + failure: !ruby/object:Provider::Ansible::ComputeFailureCondition + region: global + type: targetSslProxies diff --git a/products/compute/helpers/instance_metadata.py b/products/compute/helpers/instance_metadata.py new file mode 100644 index 000000000000..4e9e63250492 --- /dev/null +++ b/products/compute/helpers/instance_metadata.py @@ -0,0 +1,49 @@ +# 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. +# Mask the fact healthChecks array is actually a single object of type +# HttpHealthCheck. + +# TODO(alexstephen): Implement updating metadata on exsiting resources. + +# Expose instance 'metadata' as a simple name/value pair hash. However the API +# defines metadata as a NestedObject with the following layout: +# +# metadata { +# fingerprint: 'hash-of-last-metadata' +# items: [ +# { +# key: 'metadata1-key' +# value: 'metadata1-value' +# }, +# ... +# ] +# } +# +def metadata_encoder(metadata): + metadata_new = [] + for key in metadata: + value = metadata[key] + metadata_new.append({key: value}) + return { + 'items': metadata_new + } + + +# Map metadata.items[]{key:,value:} => metadata[key]=value +def metadata_decoder(metadata): + items = {} + if 'items' in metadata: + metadata_items = metadata['items'] + for item in metadata_items: + items[item.keys()[0]] = item[item.keys()[0]] + return items diff --git a/products/compute/helpers/provider_instance.py b/products/compute/helpers/provider_instance.py new file mode 100644 index 000000000000..ad32d240a6fe --- /dev/null +++ b/products/compute/helpers/provider_instance.py @@ -0,0 +1,25 @@ +# 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. +# Mask the fact healthChecks array is actually a single object of type +# HttpHealthCheck. + +def encode_request(request, module): + if 'metadata' in request: + request['metadata'] = metadata_encoder(request['metadata']) + return request + + +def decode_response(response, module): + if 'metadata' in response: + response['metadata'] = metadata_decoder(response['metadata']) + return response diff --git a/products/compute/helpers/provider_instance_template.py b/products/compute/helpers/provider_instance_template.py new file mode 100644 index 000000000000..d7d55e6d7f62 --- /dev/null +++ b/products/compute/helpers/provider_instance_template.py @@ -0,0 +1,27 @@ +# 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. +# Mask the fact healthChecks array is actually a single object of type +# HttpHealthCheck. + +def encode_request(request, module): + if 'metadata' in request: + if 'properties' in request['metadata']: + request['metadata']['properties'] = metadata_encoder(request['metadata']['properties']) + return request + + +def decode_response(response, module): + if 'metadata' in response: + if 'properties' in response['metadata']: + response['metadata']['properties'] = metadata_encoder(response['metadata']['properties']) + return response diff --git a/products/compute/instance_disks.yaml b/products/compute/instance_disks.yaml index 1290aa2db57f..1b579a9eba1e 100644 --- a/products/compute/instance_disks.yaml +++ b/products/compute/instance_disks.yaml @@ -85,7 +85,6 @@ parameters to create boot disks or local SSDs attached to the new instance. input: true - required: true properties: - !ruby/object:Api::Type::String name: 'diskName' diff --git a/products/container/ansible.yaml b/products/container/ansible.yaml index a6f0f27989cc..df44970caa65 100644 --- a/products/container/ansible.yaml +++ b/products/container/ansible.yaml @@ -24,17 +24,15 @@ manifest: !ruby/object:Provider::Ansible::Manifest version_added: '2.6' author: Google Inc. (@googlecloudplatform) # This is where custom code would be defined eventually. -objects: !ruby/object:Api::Resource::HashArray - Cluster: +overrides: !ruby/object:Provider::ResourceOverrides + Cluster: !ruby/object:Provider::Ansible::ResourceOverride provider_helpers: - include: - - 'products/container/helpers/provider_cluster.py' - NodePool: + - 'products/container/helpers/provider_cluster.py' + KubeConfig: !ruby/object:Provider::Ansible::ResourceOverride + exclude: true + NodePool: !ruby/object:Provider::Ansible::ResourceOverride provider_helpers: - include: - - 'products/container/helpers/provider_node_pool.py' - KubeConfig: - skip: true + - 'products/container/helpers/provider_node_pool.py' examples: !ruby/object:Api::Resource::HashArray files: !ruby/object:Provider::Config::Files copy: diff --git a/products/dns/ansible.yaml b/products/dns/ansible.yaml index 0fe235d3a0f0..e2ef3b3bc3ac 100644 --- a/products/dns/ansible.yaml +++ b/products/dns/ansible.yaml @@ -25,16 +25,11 @@ manifest: !ruby/object:Provider::Ansible::Manifest author: Google Inc. (@googlecloudplatform) # This is where custom code would be defined eventually. overrides: !ruby/object:Provider::ResourceOverrides - Project: !ruby/object:Provider::Ansible::ResourceOverride - # TODO(alexstephen): Re-evaluate merging Project into Ansible - exclude: true - Change: !ruby/object:Provider::Ansible::ResourceOverride - exclude: true -# TODO(https://github.com/GoogleCloudPlatform/magic-modules/issues/47): Migrate objects to overrides -objects: !ruby/object:Api::Resource::HashArray - ManagedZone: + ManagedZone: !ruby/object:Provider::Ansible::ResourceOverride editable: false - ResourceRecordSet: + ResourceRecordSet: !ruby/object:Provider::Ansible::ResourceOverride + access_api_results: true + editable: true version_added: '2.6' imports: - copy @@ -64,14 +59,17 @@ objects: !ruby/object:Api::Resource::HashArray return fetch_wrapped_resource(module, 'dns#resourceRecordSet', 'dns#resourceRecordSetsListResponse', 'rrsets') - access_api_results: true provider_helpers: - visible: - unwrap_resource: false - resource_to_request: false - return_if_object: false - include: - - 'products/dns/helpers/ansible_provider_resource_set.py.erb' + - 'products/dns/helpers/ansible_provider_resource_set.py.erb' + hidden: + - unwrap_resource + - resource_to_request + - return_if_object + Project: !ruby/object:Provider::Ansible::ResourceOverride + # TODO(alexstephen): Re-evaluate merging Project into Ansible + exclude: true + Change: !ruby/object:Provider::Ansible::ResourceOverride + exclude: true examples: !ruby/object:Api::Resource::HashArray files: !ruby/object:Provider::Config::Files copy: diff --git a/products/dns/api.yaml b/products/dns/api.yaml index 88325dab5845..fdfed2396443 100644 --- a/products/dns/api.yaml +++ b/products/dns/api.yaml @@ -89,39 +89,44 @@ objects: description: | Unique numeric identifier for the resource; defined by the server. output: true - - !ruby/object:Api::Type::Integer - name: 'quota.managedZones' - description: Maximum allowed number of managed zones in the project. - output: true - - !ruby/object:Api::Type::Integer - name: 'quota.resourceRecordsPerRrset' - description: | - Maximum allowed number of ResourceRecords per ResourceRecordSet. - output: true - - !ruby/object:Api::Type::Integer - name: 'quota.rrsetAdditionsPerChange' - description: | - Maximum allowed number of ResourceRecordSets to add per - ChangesCreateRequest. - output: true - - !ruby/object:Api::Type::Integer - name: 'quota.rrsetDeletionsPerChange' - description: | - Maximum allowed number of ResourceRecordSets to delete per - ChangesCreateRequest. - output: true - - !ruby/object:Api::Type::Integer - name: 'quota.rrsetsPerManagedZone' - description: | - Maximum allowed number of ResourceRecordSets per zone in the - project. - output: true - - !ruby/object:Api::Type::Integer - name: 'quota.totalRrdataSizePerChange' - description: | - Maximum allowed size for total rrdata in one ChangesCreateRequest - in bytes. + - !ruby/object:Api::Type::NestedObject + name: 'quota' + description: 'Quota allowed in project' output: true + properties: + - !ruby/object:Api::Type::Integer + name: 'managedZones' + description: Maximum allowed number of managed zones in the project. + output: true + - !ruby/object:Api::Type::Integer + name: 'resourceRecordsPerRrset' + description: | + Maximum allowed number of ResourceRecords per ResourceRecordSet. + output: true + - !ruby/object:Api::Type::Integer + name: 'rrsetAdditionsPerChange' + description: | + Maximum allowed number of ResourceRecordSets to add per + ChangesCreateRequest. + output: true + - !ruby/object:Api::Type::Integer + name: 'rrsetDeletionsPerChange' + description: | + Maximum allowed number of ResourceRecordSets to delete per + ChangesCreateRequest. + output: true + - !ruby/object:Api::Type::Integer + name: 'rrsetsPerManagedZone' + description: | + Maximum allowed number of ResourceRecordSets per zone in the + project. + output: true + - !ruby/object:Api::Type::Integer + name: 'totalRrdataSizePerChange' + description: | + Maximum allowed size for total rrdata in one ChangesCreateRequest + in bytes. + output: true - !ruby/object:Api::Resource name: 'ResourceRecordSet' kind: 'dns#resourceRecordSet' diff --git a/products/dns/chef.yaml b/products/dns/chef.yaml index 2e997150164a..bf5d0b9250b5 100644 --- a/products/dns/chef.yaml +++ b/products/dns/chef.yaml @@ -13,7 +13,7 @@ --- !ruby/object:Provider::Chef::Config manifest: !ruby/object:Provider::Chef::Manifest - version: '0.1.0' + version: '0.1.1' source: 'https://github.com/GoogleCloudPlatform/chef-google-dns' issues: 'https://github.com/GoogleCloudPlatform/chef-google-dns/issues' summary: 'A Chef cookbook to manage Google Cloud DNS resources' @@ -115,3 +115,10 @@ style: - function: self.resource_to_hash exceptions: - Metrics/MethodLength +changelog: + - !ruby/object:Provider::Config::Changelog + version: '0.1.1' + date: 2018-05-02T06:00:00-0700 + fixes: + - Changed quota_* fields to a Hash. All quota_ fields must now be + keys in a hash named `quota` diff --git a/products/dns/helpers/ansible_provider_resource_set.py.erb b/products/dns/helpers/ansible_provider_resource_set.py.erb index 3ca8476b67a2..912732470559 100644 --- a/products/dns/helpers/ansible_provider_resource_set.py.erb +++ b/products/dns/helpers/ansible_provider_resource_set.py.erb @@ -36,6 +36,9 @@ class SOAForwardable(object): def fail_json(self, *args, **kwargs): self.module.fail_json(*args, **kwargs) + def raise_for_status(self, *args, **kwargs): + self.module.raise_for_status(*args, **kwargs) + def prefetch_soa_resource(module): name = module.params['name'].split('.')[1:] @@ -55,7 +58,7 @@ def prefetch_soa_resource(module): 'dns#resourceRecordSetsListResponse', 'rrsets') if not result: - raise ValueError("Google DNS Managed Zone %s not found" % module.params['managed_zone']) + raise ValueError("Google DNS Managed Zone %s not found" % module.params['managed_zone']['name']) return result @@ -140,7 +143,7 @@ def return_if_change_object(module, response): try: response.raise_for_status() result = response.json() - except Exception as inst: + except getattr(json.decoder, 'JSONDecodeError', ValueError) as inst: module.fail_json(msg="Invalid JSON response with error: %s" % inst) if result['kind'] != 'dns#change': diff --git a/products/dns/puppet.yaml b/products/dns/puppet.yaml index dbe0f82ad8da..d79adf2c5b50 100644 --- a/products/dns/puppet.yaml +++ b/products/dns/puppet.yaml @@ -13,7 +13,7 @@ --- !ruby/object:Provider::Puppet::Config manifest: !ruby/object:Provider::Puppet::Manifest - version: '0.1.1' + version: '0.1.3' source: 'https://github.com/GoogleCloudPlatform/puppet-google-dns' homepage: 'https://github.com/GoogleCloudPlatform/puppet-google-dns' issues: 'https://github.com/GoogleCloudPlatform/puppet-google-dns/issues' @@ -97,6 +97,12 @@ style: exceptions: - Metrics/MethodLength changelog: + - !ruby/object:Provider::Config::Changelog + version: '0.1.3' + date: 2018-05-02T06:00:00-0700 + fixes: + - Changed quota_* fields to a Hash. All quota_ fields must now be + keys in a hash named `quota` - !ruby/object:Provider::Config::Changelog version: '0.1.2' date: 2018-02-14T06:00:00-0700 diff --git a/products/pubsub/ansible.yaml b/products/pubsub/ansible.yaml index 04971e0cc3ab..b64b46391da9 100644 --- a/products/pubsub/ansible.yaml +++ b/products/pubsub/ansible.yaml @@ -24,18 +24,20 @@ manifest: !ruby/object:Provider::Ansible::Manifest version_added: '2.6' author: Google Inc. (@googlecloudplatform) # This is where custom code would be defined eventually. -objects: !ruby/object:Api::Resource::HashArray - Topic: - encoder: encode_request +overrides: !ruby/object:Provider::ResourceOverrides + Topic: !ruby/object:Provider::Ansible::ResourceOverride + transport: !ruby/object:Api::Resource::Transport + encoder: encode_request + decoder: decode_request provider_helpers: - include: - - 'products/pubsub/helpers/provider_topic.py' - Subscription: + - 'products/pubsub/helpers/provider_topic.py' + Subscription: !ruby/object:Provider::Ansible::ResourceOverride editable: false - encoder: encode_request + transport: !ruby/object:Api::Resource::Transport + encoder: encode_request + decoder: decode_request provider_helpers: - include: - - 'products/pubsub/helpers/provider_subscription.py' + - 'products/pubsub/helpers/provider_subscription.py' examples: !ruby/object:Api::Resource::HashArray files: !ruby/object:Provider::Config::Files copy: diff --git a/provider/ansible.rb b/provider/ansible.rb index 1d4d1100ddc6..55a09806b40d 100644 --- a/provider/ansible.rb +++ b/provider/ansible.rb @@ -56,7 +56,8 @@ class Core < Provider::Core 'Api::Type::NestedObject' => 'dict', 'Api::Type::Array' => 'list', 'Api::Type::Boolean' => 'bool', - 'Api::Type::Integer' => 'int' + 'Api::Type::Integer' => 'int', + 'Api::Type::NameValues' => 'dict' }.freeze include Provider::Ansible::Documentation @@ -133,10 +134,18 @@ def build_object_data(object, output_folder) # * extra_data is a dict of extra information. # * extra_url will have a URL chunk to be appended after the URL. # rubocop:disable Metrics/MethodLength - def emit_link(name, url, extra_data = false) - params = emit_link_var_args(url, extra_data) + # rubocop:disable Metrics/AbcSize + def emit_link(name, url, object, has_extra_data = false) + params = emit_link_var_args(url, has_extra_data) extra = (' + extra_url' if url.include?('<|extra|>')) || '' - if extra_data + if rrefs_in_link(url, object) + url_code = "#{url}.format(**res)#{extra}" + [ + "def #{name}(#{params.join(', ')}):", + indent("res = #{resourceref_hash_for_links(url, object)}", 4), + indent("return #{url_code}", 4).gsub('<|extra|>', '') + ].join("\n") + elsif has_extra_data [ "def #{name}(#{params.join(', ')}):", indent([ @@ -159,6 +168,40 @@ def emit_link(name, url, extra_data = false) end end # rubocop:enable Metrics/MethodLength + # rubocop:enable Metrics/AbcSize + + def rrefs_in_link(link, object) + props_in_link = link.scan(/{([a-z_]*)}/).flatten + (object.parameters || []).select do |p| + props_in_link.include?(Google::StringUtils.underscore(p.name)) && \ + p.is_a?(Api::Type::ResourceRef) && !p.resource_ref.virtual + end.any? + end + + # rubocop:disable Metrics/MethodLength + # rubocop:disable Metrics/AbcSize + def resourceref_hash_for_links(link, object) + props_in_link = link.scan(/{([a-z_]*)}/).flatten + props = props_in_link.map do |p| + # Select a resourceref if it exists. + rref = (object.parameters || []).select do |prop| + Google::StringUtils.underscore(prop.name) == p && \ + prop.is_a?(Api::Type::ResourceRef) && !prop.resource_ref.virtual + end + if rref.any? + [ + "#{quote_string(p)}:", + "replace_resource_dict(module.params[#{quote_string(p)}],", + "#{quote_string(rref[0].imports)})" + ].join(' ') + else + "#{quote_string(p)}: module.params[#{quote_string(p)}]" + end + end + ['{', indent_list(props, 4), '}'].join("\n") + end + # rubocop:enable Metrics/MethodLength + # rubocop:enable Metrics/AbcSize def emit_link_var_args(url, extra_data) extra_url = url.include?('<|extra|>') diff --git a/provider/ansible/common~compile.yaml b/provider/ansible/common~compile.yaml index 8c2d6b7c9cb7..7da9ee91128b 100644 --- a/provider/ansible/common~compile.yaml +++ b/provider/ansible/common~compile.yaml @@ -23,10 +23,13 @@ 'test/units/module_utils/gcp/test_gcp_utils.py': 'provider/ansible/test_gcp_utils.py' <% unless config.nil? -%> <% - skipped_props = config.objects.select { |k, v| v && (v['skip'] == true) } - .keys + # Overrides have not been assembled yet. + overrides = config.overrides + excludes = overrides.instance_variables + .select { |x| overrides.instance_variable_get(x).exclude } + .map { |x| x.to_s.tr(':@', '') } object_names = api.objects - .reject { |o| skipped_props.include?(o.name) } + .select { |o| !excludes.include?(o.name) } .map do |object| ["gcp_#{object.__product.prefix[1..-1]}", Google::StringUtils.underscore(object.name)].join('_') diff --git a/provider/ansible/documentation.rb b/provider/ansible/documentation.rb index b4c33ebdf907..9642acb7f221 100644 --- a/provider/ansible/documentation.rb +++ b/provider/ansible/documentation.rb @@ -20,6 +20,7 @@ module Provider module Ansible # Responsible for building out YAML documentation blocks. + # rubocop:disable Metrics/ModuleLength module Documentation # This is not a comprehensive list of unsafe characters. # Ansible's YAML linter is more forgiving than Ruby's. @@ -54,34 +55,38 @@ def bullet_lines(line, spaces) # the bullet properly # because of the : # character - def bullet_line(paragraph, spaces, multiline = true, add_period = true) - # + 2 for "- " and a potential period at the end of the sentence. - # Remove arbitrary newlines created by formatting in YAML files - indented = indent(wrap_field(paragraph.tr("\n", ' '), spaces + 3), 2) + def bullet_line(paragraph, spaces, _multiline = true, add_period = true) + paragraph += '.' unless paragraph.end_with?('.') || !add_period + paragraph = paragraph.tr("\n", ' ').strip - if multiline && UNSAFE_CHARS.any? { |c| paragraph.include?(c) } - indented = "- |\n" + indented - else - indented[0] = '-' - end + # Paragraph placed inside array to get bullet point. + yaml = [paragraph].to_yaml + # YAML documentation header is not necessary. + yaml = yaml.gsub("---\n", '') if yaml.include?("---\n") - if add_period - indented += '.' unless indented.end_with?('.') + # YAML dumper isn't very smart about line lengths. + # If any line is over 160 characters (with indents), build the YAML + # block using wrap_field. + # Using YAML.dump output ensures that all character escaping done + if yaml.split("\n").any? { |line| line.length > (160 - spaces) } + return wrap_field( + yaml.tr("\n", ' ').gsub(/\s+/, ' '), + spaces + 3 + ).each_with_index.map { |x, i| i.zero? ? x : indent(x, 2) } end - - indented + yaml end # Builds out a full YAML block for DOCUMENTATION # This includes the YAML for the property as well as any nested props - def doc_property_yaml(prop, config, spaces) - block = minimal_doc_block(prop, config, spaces) + def doc_property_yaml(prop, object, spaces) + block = minimal_doc_block(prop, object, spaces) # Ansible linter does not support nesting options this deep. if prop.is_a?(Api::Type::NestedObject) - block.concat(nested_doc(prop.properties, config, spaces)) + block.concat(nested_doc(prop.properties, object, spaces)) elsif prop.is_a?(Api::Type::Array) && prop.item_type.is_a?(Api::Type::NestedObject) - block.concat(nested_doc(prop.item_type.properties, config, spaces)) + block.concat(nested_doc(prop.item_type.properties, object, spaces)) else block end @@ -113,24 +118,24 @@ def nested_return(properties, spaces) ) end - def nested_doc(properties, config, spaces) + def nested_doc(properties, object, spaces) block = [indent('suboptions:', 4)] block.concat( properties.map do |p| - indent(doc_property_yaml(p, config, spaces + 4), 8) + indent(doc_property_yaml(p, object, spaces + 4), 8) end ) end # Builds out the minimal YAML block for DOCUMENTATION - def minimal_doc_block(prop, config, spaces) + # rubocop:disable Metrics/AbcSize + def minimal_doc_block(prop, object, spaces) [ minimal_yaml(prop, spaces), indent([ "required: #{prop.required ? 'true' : 'false'}", - ("type: bool" if prop.is_a? Api::Type::Boolean), - ("aliases: [#{config['aliases'][prop.name].join(', ')}]" \ - if config['aliases']&.keys&.include?(prop.name)), + ('type: bool' if prop.is_a? Api::Type::Boolean), + ("aliases: [#{prop.aliases.join(', ')}]" if prop.aliases), (if prop.is_a? Api::Type::Enum [ 'choices:', @@ -140,6 +145,7 @@ def minimal_doc_block(prop, config, spaces) ].compact, 4) ] end + # rubocop:enable Metrics/AbcSize # Builds out the minimal YAML block for RETURNS def minimal_return_block(prop, spaces) @@ -147,9 +153,9 @@ def minimal_return_block(prop, spaces) # Complex types only mentioned in reference to RETURNS YAML block # Complex types are nested objects traditionally, but arrays of nested # objects will be included to avoid linting errors. - type = 'complex' if prop.is_a?(Api::Type::NestedObject)|| \ - (prop.is_a?(Api::Type::Array) && \ - prop.item_type.is_a?(Api::Type::NestedObject)) + type = 'complex' if prop.is_a?(Api::Type::NestedObject) \ + || (prop.is_a?(Api::Type::Array) \ + && prop.item_type.is_a?(Api::Type::NestedObject)) [ minimal_yaml(prop, spaces), indent([ @@ -180,5 +186,6 @@ def autogen_notice_contrib 'https://www.github.com/GoogleCloudPlatform/magic-modules'] end end + # rubocop:enable Metrics/ModuleLength end end diff --git a/provider/ansible/gcp_utils.py b/provider/ansible/gcp_utils.py index 0c6b62b61067..599e8fca3e08 100644 --- a/provider/ansible/gcp_utils.py +++ b/provider/ansible/gcp_utils.py @@ -37,11 +37,15 @@ def navigate_hash(source, path, default=None): return result +class GcpRequestException(Exception): + pass + + def remove_nones_from_dict(obj): new_obj = {} for key in obj: value = obj[key] - if value is not None: + if value is not None and value != {} and value != []: new_obj[key] = value return new_obj @@ -50,7 +54,7 @@ def remove_nones_from_dict(obj): def replace_resource_dict(item, value): if isinstance(item, list): items = [] - for i in items: + for i in item: items.append(replace_resource_dict(i, value)) return items else: diff --git a/provider/ansible/module.rb b/provider/ansible/module.rb index 4086208b6188..62370848c044 100644 --- a/provider/ansible/module.rb +++ b/provider/ansible/module.rb @@ -18,15 +18,15 @@ module Ansible module Module # Returns the Python dictionary representing a simple property for # validation. - def python_dict_for_property(prop, config, spaces = 0) + def python_dict_for_property(prop, object, spaces = 0) if prop.is_a?(Api::Type::Array) && \ prop.item_type.is_a?(Api::Type::NestedObject) - nested_obj_dict(prop, config, prop.item_type.properties, spaces) + nested_obj_dict(prop, object, prop.item_type.properties, spaces) elsif prop.is_a? Api::Type::NestedObject - nested_obj_dict(prop, config, prop.properties, spaces) + nested_obj_dict(prop, object, prop.properties, spaces) else name = Google::StringUtils.underscore(prop.out_name) - "#{name}=dict(#{prop_options(prop, config, spaces).join(', ')})" + "#{name}=dict(#{prop_options(prop, object, spaces).join(', ')})" end end @@ -34,13 +34,13 @@ def python_dict_for_property(prop, config, spaces = 0) # Creates a Python dictionary representing a nested object property # for validation. - def nested_obj_dict(prop, config, properties, spaces) + def nested_obj_dict(prop, object, properties, spaces) name = Google::StringUtils.underscore(prop.out_name) - options = prop_options(prop, config, spaces).join(', ') + options = prop_options(prop, object, spaces).join(', ') [ "#{name}=dict(#{options}, options=dict(", indent_list(properties.map do |p| - python_dict_for_property(p, config, spaces + 4) + python_dict_for_property(p, object, spaces + 4) end, 4), '))' ] @@ -48,18 +48,15 @@ def nested_obj_dict(prop, config, properties, spaces) # Returns an array of all base options for a given property. # rubocop:disable Metrics/AbcSize - def prop_options(prop, config, spaces) + def prop_options(prop, object, spaces) [ ('required=True' if prop.required), "type=#{quote_string(python_type(prop))}", (choices_enum(prop, spaces) if prop.is_a? Api::Type::Enum), ("elements=#{quote_string(python_type(prop.item_type))}" \ if prop.is_a? Api::Type::Array), - (if config['aliases']&.keys&.include?(prop.name) - "aliases=[#{config['aliases'][prop.name].map do |x| - quote_string(x) - end.join(', ')}]" - end) + ("aliases=[#{prop.aliases.map { |x| quote_string(x) }.join(', ')}]" \ + if prop.aliases) ].compact end # rubocop:enable Metrics/AbcSize diff --git a/provider/ansible/property_override.rb b/provider/ansible/property_override.rb index 1ee926014a36..143a22aebe85 100644 --- a/provider/ansible/property_override.rb +++ b/provider/ansible/property_override.rb @@ -20,11 +20,17 @@ module Ansible # Collection of fields allowed in the PropertyOverride section for # Ansible. All fields should be `attr_reader :` module OverrideFields + attr_reader :aliases end # Ansible-specific overrides to api.yaml. class PropertyOverride < Provider::PropertyOverride include OverrideFields + def validate + super + + check_optional_property :aliases, ::Array + end private diff --git a/provider/ansible/request.rb b/provider/ansible/request.rb index 5e68299eaae1..eea04567a80f 100644 --- a/provider/ansible/request.rb +++ b/provider/ansible/request.rb @@ -15,35 +15,46 @@ module Provider module Ansible # Responsible for building out the resource_to_request and # request_from_hash methods. + # rubocop:disable Metrics/ModuleLength module Request # Takes in a list of properties and outputs a python hash that takes # in a module and outputs a formatted JSON request. def request_properties(properties, indent = 4) indent_list( - properties.map { |prop| request_property(prop, 'module.params') }, + properties.map do |prop| + request_property(prop, 'module.params', 'module') + end, indent ) end def response_properties(properties, indent = 8) indent_list( - properties.map { |prop| response_property(prop, 'response') }, + properties.map do |prop| + response_property(prop, 'response', 'module') + end, indent ) end def request_properties_in_classes(properties, indent = 4, - hash_name = 'self.request') + hash_name = 'self.request', + module_name = 'self.module') indent_list( - properties.map { |prop| request_property(prop, hash_name) }, + properties.map do |prop| + request_property(prop, hash_name, module_name) + end, indent ) end def response_properties_in_classes(properties, indent = 8, - hash_name = 'self.request') + hash_name = 'self.request', + module_name = 'self.module') indent_list( - properties.map { |prop| response_property(prop, hash_name) }, + properties.map do |prop| + response_property(prop, hash_name, module_name) + end, indent ) end @@ -54,7 +65,7 @@ def properties_with_classes(properties) if p.is_a? Api::Type::NestedObject [p] + properties_with_classes(p.properties) elsif p.is_a?(Api::Type::Array) && \ - p.item_type.is_a?(Api::Type::NestedObject) + p.item_type.is_a?(Api::Type::NestedObject) [p] + properties_with_classes(p.item_type.properties) end end.compact.flatten @@ -62,52 +73,61 @@ def properties_with_classes(properties) private - def request_property(prop, hash_name) + def request_property(prop, hash_name, module_name) [ "#{unicode_string(prop.field_name)}:", - request_output(prop, hash_name).to_s + request_output(prop, hash_name, module_name).to_s ].join(' ') end - def response_property(prop, hash_name) + def response_property(prop, hash_name, module_name) [ "#{unicode_string(prop.field_name)}:", - response_output(prop, hash_name).to_s + response_output(prop, hash_name, module_name).to_s ].join(' ') end - def response_output(prop, hash_name) + # rubocop:disable Metrics/MethodLength + def response_output(prop, hash_name, module_name) + # If input true, treat like request, but use module names. + return request_output(prop, "#{module_name}.params", module_name) \ + if prop.input if prop.is_a? Api::Type::NestedObject [ "#{prop.property_class[-1]}(", "#{hash_name}.get(#{unicode_string(prop.name)}, {})", - ').from_response()' + ", #{module_name}).from_response()" ].join elsif prop.is_a?(Api::Type::Array) && \ - prop.item_type.is_a?(Api::Type::NestedObject) + prop.item_type.is_a?(Api::Type::NestedObject) [ "#{prop.property_class[-1]}(", "#{hash_name}.get(#{unicode_string(prop.name)}, [])", - ').from_response()' + ", #{module_name}).from_response()" ].join else "#{hash_name}.get(#{unicode_string(prop.name)})" end end + # rubocop:enable Metrics/MethodLength - def request_output(prop, hash_name) + # rubocop:disable Metrics/MethodLength + # rubocop:disable Metrics/AbcSize + # rubocop:disable Metrics/CyclomaticComplexity + # rubocop:disable Metrics/PerceivedComplexity + def request_output(prop, hash_name, module_name) if prop.is_a? Api::Type::NestedObject [ "#{prop.property_class[-1]}(", "#{hash_name}.get(#{quote_string(prop.out_name)}, {})", - ').to_request()' + ", #{module_name}).to_request()" ].join elsif prop.is_a?(Api::Type::Array) && \ - prop.item_type.is_a?(Api::Type::NestedObject) + prop.item_type.is_a?(Api::Type::NestedObject) [ "#{prop.property_class[-1]}(", "#{hash_name}.get(#{quote_string(prop.out_name)}, [])", - ').to_request()' + ", #{module_name}).to_request()" ].join elsif prop.is_a?(Api::Type::ResourceRef) && !prop.resource_ref.virtual prop_name = Google::StringUtils.underscore(prop.name) @@ -117,24 +137,30 @@ def request_output(prop, hash_name) "#{quote_string(prop.imports)})" ].join elsif prop.is_a?(Api::Type::ResourceRef) && \ - prop.resource_ref.virtual && prop.imports == 'selfLink' + prop.resource_ref.virtual && prop.imports == 'selfLink' func_name = Google::StringUtils.underscore("#{prop.name}_selflink") [ "#{func_name}(#{hash_name}.get(#{quote_string(prop.out_name)}),", - "module.params)" + "#{module_name}.params)" ].join(' ') elsif prop.is_a?(Api::Type::Array) && \ - prop.item_type.is_a?(Api::Type::ResourceRef) && \ - !prop.item_type.resource_ref.virtual + prop.item_type.is_a?(Api::Type::ResourceRef) && \ + !prop.item_type.resource_ref.virtual + prop_name = Google::StringUtils.underscore(prop.name) [ "replace_resource_dict(#{hash_name}", - ".get(#{unicode_string(prop.name)}, []), ", + ".get(#{quote_string(prop_name)}, []), ", "#{quote_string(prop.item_type.imports)})" ].join else "#{hash_name}.get(#{quote_string(prop.out_name)})" end end + # rubocop:enable Metrics/MethodLength + # rubocop:enable Metrics/AbcSize + # rubocop:enable Metrics/CyclomaticComplexity + # rubocop:enable Metrics/PerceivedComplexity end + # rubocop:enable Metrics/ModuleLength end end diff --git a/provider/ansible/resource_override.rb b/provider/ansible/resource_override.rb index 7dd8856c1603..74f8cce45725 100644 --- a/provider/ansible/resource_override.rb +++ b/provider/ansible/resource_override.rb @@ -18,6 +18,15 @@ module Ansible # Ansible specific properties to be added to Api::Resource module OverrideProperties attr_reader :access_api_results + attr_reader :collection + attr_reader :create + attr_reader :delete + attr_reader :editable + attr_reader :hidden + attr_reader :imports + attr_reader :provider_helpers + attr_reader :update + attr_reader :version_added end # Product specific overriden properties for Ansible @@ -28,8 +37,21 @@ def validate super default_value_property :access_api_results, false + default_value_property :exclude, false + default_value_property :editable, true + default_value_property :imports, [] + default_value_property :provider_helpers, [] check_property :access_api_results, :boolean + check_optional_property :collection, ::String + check_optional_property :create, ::String + check_optional_property :delete, ::String + check_property :editable, :boolean + check_optional_property :hidden, ::Array + check_property :imports, ::Array + check_property :provider_helpers, ::Array + check_optional_property :update, ::String + check_optional_property :version_added, ::String end private diff --git a/provider/ansible/selflink.rb b/provider/ansible/selflink.rb index be2b5309925d..1523f5901d18 100644 --- a/provider/ansible/selflink.rb +++ b/provider/ansible/selflink.rb @@ -38,10 +38,10 @@ def selflink_functions(object) virtuals = virtual_selflink_rrefs(object).map(&:resource_ref) .uniq virtuals.map do |virt| - if virt = virtuals.last + if virt == virtuals.last lines(selflink_function(virt)) else - lines(selflink_function(virt), 2) + lines(selflink_function(virt), 1) end end end @@ -62,7 +62,6 @@ def selflink_function(resource) "url = r#{url}", 'if not re.match(url, name):', # '%s' confuses Rubocop (it's Python code, not Ruby) - # rubocop:disable Style/FormatStringToken indent([ "name = #{self_link_url(resource).gsub('{name}', '%s')}", '.format(**params) % name' diff --git a/templates/ansible/aliases b/templates/ansible/aliases index 26507c23cd3c..9812f019ca4b 100644 --- a/templates/ansible/aliases +++ b/templates/ansible/aliases @@ -1 +1,2 @@ cloud/gcp +unsupported diff --git a/templates/ansible/async.erb b/templates/ansible/async.erb index a3366eda3135..664c3f6827bd 100644 --- a/templates/ansible/async.erb +++ b/templates/ansible/async.erb @@ -3,7 +3,9 @@ stat_path = path2navigate(object.async.status.path) res_path = path2navigate(object.async.result.path) -%> -<%= lines(emit_link('async_op_url', async_operation_url(object), true), 2) -%> +<%= + lines(emit_link('async_op_url', async_operation_url(object), object, true), 2) +-%> def wait_for_operation(module, response): <% if object.kind? -%> <% op_kind = object.async.operation.kind -%> diff --git a/templates/ansible/properties.erb b/templates/ansible/properties.erb index ba89b373472a..7fd505bf52e4 100644 --- a/templates/ansible/properties.erb +++ b/templates/ansible/properties.erb @@ -1,7 +1,8 @@ <% properties_with_classes(object.all_user_properties).each do |prop| -%> class <%= prop.property_class[-1] -%>(object): <% if prop.is_a?(Api::Type::NestedObject) -%> - def __init__(self, request): + def __init__(self, request, module): + self.module = module if request: self.request = request else: @@ -17,7 +18,8 @@ class <%= prop.property_class[-1] -%>(object): <%= lines(response_properties_in_classes(prop.properties, 12)) -%> }) <% elsif prop.is_a?(Api::Type::Array) && prop.item_type.is_a?(Api::Type::NestedObject) -%> - def __init__(self, request): + def __init__(self, request, module): + self.module = module if request: self.request = request else: diff --git a/templates/ansible/provider_helpers.erb b/templates/ansible/provider_helpers.erb index 39ce7338ab1f..db854570bd65 100644 --- a/templates/ansible/provider_helpers.erb +++ b/templates/ansible/provider_helpers.erb @@ -1,11 +1,9 @@ <% - provider_helpers = Google::HashUtils.navigate(config, - %w[provider_helpers include], []) -%> -<% unless provider_helpers.empty? -%> +<% unless object.provider_helpers.empty? -%> <%= - provider_helpers.map do |f| - if f == provider_helpers.last + object.provider_helpers.map do |f| + if f == object.provider_helpers.last lines(compile(f)) else lines(compile(f), 2) diff --git a/templates/ansible/resource.erb b/templates/ansible/resource.erb index ce5d9a93895d..9db146423453 100644 --- a/templates/ansible/resource.erb +++ b/templates/ansible/resource.erb @@ -41,7 +41,7 @@ options: choices: ['present', 'absent'] default: 'present' <% object.all_user_properties.reject(&:output).each do |prop| -%> -<%= lines(indent(doc_property_yaml(prop, config, 4), 4)) -%> +<%= lines(indent(doc_property_yaml(prop, object, 4), 4)) -%> <% end -%> extends_documentation_fragment: gcp ''' @@ -76,11 +76,11 @@ RETURN = ''' <%= lines(import) -%> import json <% - config['imports'] ||= [] - config['imports'] << 'time' if object.async - config['imports'] << 're' unless virtual_selflink_rrefs(object).empty? + imports = object.imports || [] + imports << 'time' if object.async + imports << 're' unless virtual_selflink_rrefs(object).empty? -%> -<%= lines(config['imports'].sort.uniq.map { |i| "import #{i}" }) -%> +<%= lines(imports.sort.uniq.map { |i| "import #{i}" }) -%> ################################################################################ # Main @@ -92,7 +92,7 @@ def main(): <% mod_props = object.all_user_properties.reject(&:output).map do |prop| - python_dict_for_property(prop, config) + python_dict_for_property(prop, object) end -%> module = GcpModule( @@ -124,6 +124,7 @@ def main(): if not fetch: module.fail_json(msg="<%= object.name -%> is not valid") <% else # object.virtual -%> + if fetch: if state == 'present': if is_different(module, fetch): @@ -173,12 +174,11 @@ def main(): <% prod_name = object.__product.prefix[1..-1] -%> <% unless object.virtual -%> -<% custom_create = get_code_multiline config, 'create' -%> <%# TODO: kind param not always needed. # https://github.com/GoogleCloudPlatform/magic-modules/issues/45 -%> <%= method_decl('create', ['module', 'link', ('kind' if object.kind?)]) %> -<% if custom_create.nil? -%> +<% if object.create.nil? -%> auth = GcpSession(module, <%= quote_string(prod_name) -%>) <% if object.create_verb.nil? || object.create_verb == :POST @@ -201,18 +201,16 @@ def main(): -%> return <%= method %> <% else -%> -<%= lines(indent(custom_create, 4)) -%> +<%= lines(indent(object.create, 4)) -%> <% end -%> -<% custom_update = get_code_multiline config, 'update' -%> -<% editable = config['editable'] -%> <%= lines(method_decl('update', ['module', 'link', ('kind' if object.kind?), ('fetch' if object.save_api_results?)])) -%> -<% if custom_update.nil? -%> -<% if !false?(editable) -%> +<% if object.update.nil? -%> +<% if !false?(object.editable) -%> auth = GcpSession(module, <%= quote_string(prod_name) -%>) <% method = method_call( @@ -225,20 +223,19 @@ def main(): ) -%> return <%= method %> -<% else # !false?(editable) -%> +<% else # !false?(object.editable) -%> module.fail_json(msg="<%= object.name -%> cannot be edited") -<% end # !false?(editable) -%> -<% else # custom_update.nil? -%> -<%= lines(indent(custom_update, 4)) -%> -<% end # custom_update.nil? -%> +<% end # !false?(object.editable) -%> +<% else # object.update.nil? -%> +<%= lines(indent(object.update, 4)) -%> +<% end # object.update.nil? -%> -<% custom_delete = get_code_multiline config, 'delete' -%> <%= lines(method_decl('delete', ['module', 'link', ('kind' if object.kind?), ('fetch' if object.save_api_results?)])) -%> -<% if custom_delete.nil? -%> +<% if object.delete.nil? -%> auth = GcpSession(module, <%= quote_string(prod_name) -%>) <% method = method_call( @@ -250,18 +247,18 @@ def main(): ) -%> return <%= method %> -<% else # if custom_delete.nil? -%> -<%= lines(indent(custom_delete, 4)) -%> -<% end # if custom_delete.nil? -%> +<% else # if object.delete.nil? -%> +<%= lines(indent(object.delete, 4)) -%> +<% end # if object.delete.nil? -%> <% end # unless object.virtual -%> def resource_to_request(module): <% - properties_in_request = [] - properties_in_request += object.parameters if object.parameters - properties_in_request += object.properties.reject(&:output) - properties_in_request = properties_in_request.compact + properties_in_request = [ + object&.parameters&.select { |p| p.input }, + object.properties.reject(&:output) + ].flatten.compact -%> request = { <% if object.kind? -%> @@ -269,15 +266,8 @@ def resource_to_request(module): <% end # if object.kind? -%> <%= lines(indent(request_properties(properties_in_request), 4)) -%> } -<% - encoder_name = if object.encoder? - object.transport.encoder - else config['encoder'] - config['encoder'] - end --%> -<% if object.encoder? || config['encoder'] -%> - request = <%= encoder_name -%>(request, module) +<% if object.encoder? -%> + request = <%= object.transport.encoder -%>(request, module) <% end -%> return_vals = {} for k, v in request.items(): @@ -288,20 +278,10 @@ def resource_to_request(module): <%= lines(compile('templates/ansible/transport.erb'), 2) -%> -<% custom_self_link = get_code_multiline config, 'self_link' -%> -<% if custom_self_link.nil? -%> -<%= lines(emit_link('self_link', self_link_url(object))) -%> -<% else # custom_self_link.nil? -%> -<%= lines(emit_link('self_link', custom_self_link)) -%> -<% end # custom_self_link.nil? -%> +<%= lines(emit_link('self_link', self_link_url(object), object)) -%> -<% custom_collection = get_code_multiline config, 'collection' -%> -<% if custom_collection.nil? -%> -<%= lines(emit_link('collection', collection_url(object))) -%> -<% else # custom_collection.nil? -%> -<%= lines(emit_link('collection', custom_collection)) -%> -<% end # custom_collection.nil? -%> +<%= lines(emit_link('collection', collection_url(object), object)) -%> <%= @@ -322,15 +302,8 @@ def resource_to_request(module): except getattr(json.decoder, 'JSONDecodeError', ValueError) as inst: module.fail_json(msg="Invalid JSON response with error: %s" % inst) -<% - decoder_name = if object.decoder? - object.transport.decoder - else config['decoder'] - config['decoder'] - end --%> <% if object.decoder? -%> - result = <%= decoder_name -%>(result, module) + result = <%= object.transport.decoder -%>(result, module) <% end -%> if navigate_hash(result, ['error', 'errors']): @@ -346,15 +319,8 @@ def resource_to_request(module): def is_different(module, response): request = resource_to_request(module) response = response_to_hash(module, response) -<% - decoder_name = if object.decoder? - object.transport.decoder - else config['decoder'] - config['decoder'] - end --%> <% if object.decoder? -%> - request = <%= decoder_name -%>(request, module) + request = <%= object.transport.decoder -%>(request, module) <% end -%> # Remove all output-only from response. diff --git a/templates/ansible/transport.erb b/templates/ansible/transport.erb index 9acb26bb8576..13021e67910a 100644 --- a/templates/ansible/transport.erb +++ b/templates/ansible/transport.erb @@ -36,6 +36,7 @@ def fetch_wrapped_resource(module, kind, wrap_kind, wrap_path): return None if result['kind'] != kind: - raise "Incorrect result: {0} (expected {1})".format(result['kind'], kind) + module.fail_json(msg="Incorrect result: {kind}".format(**result)) + return result <% end -%>