Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support unordered lists in Terraform. #351

Merged
merged 9 commits into from
Jul 17, 2018
64 changes: 64 additions & 0 deletions .ci/containers/puppet-forge/Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
from alpine:latest as resource

# Install Ruby from source.
RUN apk update
RUN apk add --no-cache --virtual .ruby-builddeps \
autoconf \
bash \
bison \
bzip2 \
bzip2-dev \
ca-certificates \
coreutils \
dpkg-dev dpkg \
gcc \
g++ \
gdbm-dev \
git \
glib-dev \
libc-dev \
libffi-dev \
libressl \
libressl-dev \
libxml2-dev \
libxslt-dev \
linux-headers \
make \
ncurses-dev \
openssh-client \
procps \
readline-dev \
ruby \
tar \
xz \
yaml-dev \
zlib-dev
ENV RUBY_VERSION 2.5.0
ENV RUBYGEMS_VERSION 2.7.4
ENV BUNDLER_VERSION 1.16.1

# Set up Github SSH cloning.
RUN ssh-keyscan github.com >> /known_hosts
RUN echo "UserKnownHostsFile /known_hosts" >> /etc/ssh/ssh_config

RUN git clone https://github.com/rbenv/ruby-build.git
RUN PREFIX=/usr/local ./ruby-build/install.sh
ENV RUBY_CONFIGURE_OPTS="ac_cv_func_isnan=yes ac_cv_func_isinf=yes"
RUN ruby-build "$RUBY_VERSION" /usr/

RUN gem update --system "$RUBYGEMS_VERSION"
RUN gem install bundler --version "$BUNDLER_VERSION" --force

ENV GEM_HOME /usr/local/bundle
ENV BUNDLE_PATH="$GEM_HOME" \
BUNDLE_BIN="$GEM_HOME/bin" \
BUNDLE_SILENCE_ROOT_WARNING=1 \
BUNDLE_APP_CONFIG="$GEM_HOME"
ENV PATH $BUNDLE_BIN:$PATH
RUN mkdir -p "$GEM_HOME" "$BUNDLE_BIN" \
&& chmod 777 "$GEM_HOME" "$BUNDLE_BIN"
COPY in.rb /opt/resource/in
COPY check.rb /opt/resource/check
COPY out.rb /opt/resource/out
COPY Gemfile /opt/resource/Gemfile
RUN cd /opt/resource/ && bundle install
4 changes: 4 additions & 0 deletions .ci/containers/puppet-forge/Gemfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
source 'https://rubygems.org'

gem 'puppet_forge'
gem 'puppet-blacksmith'
27 changes: 27 additions & 0 deletions .ci/containers/puppet-forge/check.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
#! /usr/bin/env ruby
# 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.

require 'puppet_forge'
require 'json'

config = JSON.parse(STDIN.read)
raise 'You need to define `module_name`' unless config['source'].key? 'module_name'
m = PuppetForge::Module.find(config['source']['module_name'])
releases = m.releases.map! { |r| { 'release' => r.version } }

if config.key? 'version'
releases = releases[0..releases.index(config['version'])]
end

puts JSON.dump(releases)
32 changes: 32 additions & 0 deletions .ci/containers/puppet-forge/in.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
#! /usr/bin/env ruby
# 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.

require 'puppet_forge'
require 'json'

config = JSON.parse(STDIN.read)
unless config['source'].key? 'module_name'
raise 'You need to define `module_name`'
end
raise 'Surprised by lack of directory.' if ARGV.empty?

release_slug = "#{config['source']['module_name']}-" \
"#{config['version']['release']}"
release = PuppetForge::Release.find(release_slug)
release_tarball = release_slug + '.tar.gz'
release.download(Pathname(release_tarball))
release.verify(Pathname(release_tarball))
PuppetForge::Unpacker.unpack(release_tarball, ARGV[0], '/tmp/resource_download')

puts JSON.dump('version' => { 'release' => release.version })
57 changes: 57 additions & 0 deletions .ci/containers/puppet-forge/out.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
#! /usr/bin/env ruby
# 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.

require 'puppet_forge'
require 'puppet_blacksmith'
require 'json'

config = JSON.parse(STDIN.read)
unless config['source'].key? 'module_name'
raise 'You need to define `module_name`'
end
raise 'This is being called without an output directory.' if ARGV.empty?

module_name = config['source']['module_name']
release = PuppetForge::Module.find(module_name).releases.first.version
major, minor, patch = release.split('.')

if major.nil? || minor.nil? || patch.nil?
raise "Cowardly refusing to work with non-semver release ID #{release}"
end

if config['params']['patch_bump'] == true
patch += 1
else
patch = 0
minor += 1
end

output_folder = ARGV[0]
metadata = JSON.parse(File.open(File.join(output_folder, 'metadata.json')).read)
unless metadata['name'] == module_name
raise "Cowardly refusing to push #{metadata['name']} to #{module_name}"
end
metadata['version'] = "#{major}.#{minor}.#{patch}"
File.write(File.join(output_folder, 'metadata.json'), JSON.dump(metadata))

Dir.chdir(output_folder) { %x(puppet module build) }

Blacksmith::Forge.initialize(config['source']['username'],
config['source']['password'])
Blacksmith::Forge.push!(metadata['name'].split('-').last,
File.join(output_folder,
'pkg',
"#{metadata['name']}-#{metadata['version']}" \
'.tar.gz'))
puts JSON.dump('version' => { 'release' => metadata['version'] })
6 changes: 0 additions & 6 deletions .travis.yml
Original file line number Diff line number Diff line change
@@ -1,11 +1,5 @@
language: ruby # ruby version defined in .ruby-version will be used

before_install:
# some ruby versions come with a broken version of rubygems, update to
# consistent version
- gem update --system 2.7.6
- gem install bundler -v '>= 1.16.1'

script:
- bundle exec rake test:rubocop
- bundle exec rake test:spec
Expand Down
2 changes: 1 addition & 1 deletion build/terraform
2 changes: 2 additions & 0 deletions products/compute/terraform.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -657,6 +657,8 @@ overrides: !ruby/object:Provider::ResourceOverrides
function: 'validateGCPName'
secondaryIpRanges: !ruby/object:Provider::Terraform::PropertyOverride
name: secondaryIpRange
unordered_list: true
default_from_api: true
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why this change?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The problem comes from the fact that we're implicitly comparing against the returned value from the API - we wouldn't have any diff problems otherwise.

secondaryIpRanges.rangeName: !ruby/object:Provider::Terraform::PropertyOverride
validation: !ruby/object:Provider::Terraform::Validation
function: 'validateGCPName'
Expand Down
3 changes: 3 additions & 0 deletions provider/terraform/property_override.rb
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,8 @@ module OverrideFields
attr_reader :state_func # Adds a StateFunc to the schema
attr_reader :sensitive # Adds `Sensitive: true` to the schema
attr_reader :validation # Adds a ValidateFunc to the schema
# Indicates that this is an Array that should have Set diff semantics.
attr_reader :unordered_list

attr_reader :is_set # Uses a Set instead of an Array
# Optional function to determine the unique ID of an item in the set
Expand Down Expand Up @@ -92,6 +94,7 @@ def validate
# Ensures boolean values are set to false if nil
@sensitive ||= false
@is_set ||= false
@unordered_list ||= false
@default_from_api ||= false

check_property :sensitive, :boolean
Expand Down
14 changes: 14 additions & 0 deletions templates/terraform/resource.erb
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,13 @@ func resource<%= resource_name -%>() *schema.Resource {
Update: resource<%= resource_name -%>Update,
<% 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}#{Google::StringUtils.camelize(p.name, :upper)}SetStyleDiff"}
.join(",\n")-%>
),
<% end -%>

Importer: &schema.ResourceImporter{
State: resource<%= resource_name -%>Import,
Expand Down Expand Up @@ -74,6 +81,13 @@ func resource<%= resource_name -%>() *schema.Resource {
},
}
}
<% settable_properties.select {|p| p.unordered_list}.each do |prop| -%>
func resource<%= resource_name -%><%= Google::StringUtils.camelize(prop.name, :upper) -%>SetStyleDiff(diff *schema.ResourceDiff, meta interface{}) error {
<%= compile_template('templates/terraform/unordered_list_customize_diff.erb',
prop: prop,
resource_name: resource_name) -%>
}
<% end -%>

func resource<%= resource_name -%>Create(d *schema.ResourceData, meta interface{}) error {
config := meta.(*Config)
Expand Down
3 changes: 2 additions & 1 deletion templates/terraform/resource_definition/subnetwork.erb
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
CustomizeDiff: customdiff.All(
customdiff.ForceNewIfChange("ip_cidr_range", isShrinkageIpCidr),
customdiff.ForceNewIfChange("ip_cidr_range", isShrinkageIpCidr),
resourceComputeSubnetworkSecondaryIpRangeSetStyleDiff,
),
39 changes: 39 additions & 0 deletions templates/terraform/unordered_list_customize_diff.erb
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
keys := diff.GetChangedKeysPrefix(<%= go_literal(Google::StringUtils.underscore(prop.name)) -%>)
if len(keys) == 0 {
return nil
}
oldCount, newCount := diff.GetChange("<%= Google::StringUtils.underscore(prop.name) -%>.#")
var count int
// There could be duplicates - worth continuing even if the counts are unequal.
if oldCount.(int) < newCount.(int) {
count = newCount.(int)
} else {
count = oldCount.(int)
}

if count < 1 {
return nil
}
old := make([]interface{}, count)
new := make([]interface{}, count)
for i := 0; i < count; i++ {
o, n := diff.GetChange(fmt.Sprintf("<%= Google::StringUtils.underscore(prop.name) -%>.%d", i))

if o != nil {
old = append(old, o)
}
if n != nil {
new = append(new, n)
}
}

oldSet := schema.NewSet(schema.HashResource(resource<%= resource_name -%>().Schema[<%= go_literal(Google::StringUtils.underscore(prop.name)) -%>].Elem.(*schema.Resource)), old)
newSet := schema.NewSet(schema.HashResource(resource<%= resource_name -%>().Schema[<%= go_literal(Google::StringUtils.underscore(prop.name)) -%>].Elem.(*schema.Resource)), new)

if oldSet.Equal(newSet) {
if err := diff.Clear(<%= go_literal(Google::StringUtils.underscore(prop.name)) -%>); err != nil {
return err
}
}

return nil