Skip to content

Commit

Permalink
Add Appengine's firewall rules. (#1081)
Browse files Browse the repository at this point in the history
Merged PR #1081.
  • Loading branch information
nat-henderson authored and modular-magician committed Jan 3, 2019
1 parent a269f7d commit 7e78e6f
Show file tree
Hide file tree
Showing 13 changed files with 224 additions and 15 deletions.
4 changes: 1 addition & 3 deletions Gemfile.lock
Original file line number Diff line number Diff line change
Expand Up @@ -12,11 +12,9 @@ GEM
concurrent-ruby (1.0.5)
debug_inspector (0.0.3)
diff-lcs (1.3)
docile (1.3.1)
i18n (1.1.1)
concurrent-ruby (~> 1.0)
jaro_winkler (1.5.1-x86_64-darwin-17)
json (2.1.0)
metaclass (0.0.4)
minitest (5.11.3)
mocha (1.3.0)
Expand Down Expand Up @@ -69,4 +67,4 @@ DEPENDENCIES
rubocop (~> 0.60.0)

BUNDLED WITH
1.16.5
1.17.1
2 changes: 1 addition & 1 deletion build/terraform
2 changes: 1 addition & 1 deletion build/terraform-beta
64 changes: 64 additions & 0 deletions products/appengine/api.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
# Copyright 2018 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:Api::Product
name: Google App Engine
prefix: gappengine
versions:
- !ruby/object:Api::Product::Version
name: ga
base_url: https://appengine.googleapis.com/v1/
scopes:
- https://www.googleapis.com/auth/cloud-platform
objects:
- !ruby/object:Api::Resource
name: 'FirewallRule'
description: |
A single firewall rule that is evaluated against incoming traffic
and provides an action to take on matched requests.
base_url: 'apps/{{project}}/firewall/ingressRules'
self_link: 'apps/{{project}}/firewall/ingressRules/{{priority}}'
update_verb: :PATCH
references: !ruby/object:Api::Resource::ReferenceLinks
guides:
'Official Documentation':
'https://cloud.google.com/appengine/docs/standard/python/creating-firewalls#creating_firewall_rules'
api: 'https://cloud.google.com/appengine/docs/admin-api/reference/rest/v1/apps.firewall.ingressRules'
parameters:
- !ruby/object:Api::Type::Integer
name: 'priority'
description: |
A positive integer that defines the order of rule evaluation.
Rules with the lowest priority are evaluated first.
A default rule at priority Int32.MaxValue matches all IPv4 and
IPv6 traffic when no previous rule matches. Only the action of
this rule can be modified by the user.
properties:
- !ruby/object:Api::Type::String
name: 'description'
description: 'An optional string description of this rule.'
required: false
- !ruby/object:Api::Type::String
name: 'sourceRange'
description: 'IP address or range, defined using CIDR notation, of requests that this rule applies to.'
required: true
- !ruby/object:Api::Type::Enum
name: 'action'
description: |
The action to take if this rule matches.
required: true
values:
- UNSPECIFIED_ACTION
- ALLOW
- DENY
36 changes: 36 additions & 0 deletions products/appengine/terraform.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
# 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::Terraform::Config
overrides: !ruby/object:Provider::ResourceOverrides
FirewallRule: !ruby/object:Provider::Terraform::ResourceOverride
id_format: "{{project}}/{{priority}}"
import_format: ["{{project}}/{{priority}}"]
custom_code: !ruby/object:Provider::Terraform::CustomCode
pre_update: templates/terraform/pre_update/update_mask.erb
example:
- !ruby/object:Provider::Terraform::Examples
name: "appengine_firewall_rule_basic"
primary_resource_id: "rule"
version: <%= version_name %>
vars:
project_id: "test-project"
test_env_vars:
org_id: :ORG_ID

# This is for copying files over
files: !ruby/object:Provider::Config::Files
# These files have templating (ERB) code that will be run.
# This is usually to add licensing info, autogeneration notices, etc.
compile:
<%= lines(indent(compile('provider/terraform/product~compile.yaml'), 4)) -%>
39 changes: 36 additions & 3 deletions provider/terraform/custom_code.rb
Original file line number Diff line number Diff line change
Expand Up @@ -66,8 +66,27 @@ class Examples < Api::Object
# }
attr_reader :primary_resource_id

# vars is a Hash from template variable names to output variable names
# vars is a Hash from template variable names to output variable names.
# It will use the provided value as a prefix for generated tests, and
# insert it into the docs verbatim.
attr_reader :vars
# Some variables need to hold special values during tests, and cannot
# be inferred by Open in Cloud Shell. For instance, org_id
# needs to be the correct value during integration tests, or else
# org tests cannot pass. Other examples include an existing project_id,
# a zone, a service account name, etc.
#
# test_env_vars is a Hash from template variable names to one of the
# following symbols:
# - :PROJECT_NAME
# - :CREDENTIALS
# - :REGION
# - :ORG_ID
# - :ORG_TARGET
# - :BILLING_ACCT
# - :SERVICE_ACCT
# This list corresponds to the `get*FromEnv` methods in provider_test.go.
attr_reader :test_env_vars

# the version (ga, beta, etc.) this example is being generated at
attr_reader :version
Expand All @@ -80,9 +99,21 @@ class Examples < Api::Object
attr_reader :skip_test

def config_documentation
docs_defaults = {
PROJECT_NAME: 'my-project-name',
CREDENTIALS: 'my/credentials/filename.json',
REGION: 'us-west1',
ORG_ID: '123456789',
ORG_TARGET: '123456789',
BILLING_ACCT: '000000-0000000-0000000-000000',
SERVICE_ACCT: 'emailAddress:[email protected]'
}
@vars ||= {}
@test_env_vars ||= {}
body = lines(compile_file(
{
vars: vars,
test_env_vars: test_env_vars.map { |k, v| [k, docs_defaults[v]] }.to_h,
primary_resource_id: primary_resource_id,
version: version
},
Expand All @@ -95,10 +126,12 @@ def config_documentation
end

def config_test
@vars ||= []
@vars ||= {}
@test_env_vars ||= {}
body = lines(compile_file(
{
vars: vars.map { |k, str| [k, "#{str}-%s"] }.to_h,
vars: vars.map { |k, str| [k, "#{str}-%{random_suffix}"] }.to_h,
test_env_vars: test_env_vars.map { |k, _| [k, "%{#{k}}"] }.to_h,
primary_resource_id: primary_resource_id,
version: version
},
Expand Down
17 changes: 17 additions & 0 deletions templates/terraform/examples/appengine_firewall_rule_basic.tf.erb
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
resource "google_project" "my_project" {
name = "tf-test-project"
project_id = "<%= ctx[:vars]['project_id'] %>"
org_id = "<%= ctx[:test_env_vars]['org_id'] %>"
}

resource "google_app_engine_application" "app" {
project = "${google_project.my_project.project_id}"
location_id = "us-central"
}

resource "google_appengine_firewall_rule" "rule" {
project = "${google_app_engine_application.app.project}"
priority = 1000
action = "ALLOW"
source_range = "*"
}
5 changes: 2 additions & 3 deletions templates/terraform/examples/base_configs/test_body.go.erb
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
return fmt.Sprintf(`
return Nprintf(`
<%= ctx[:content] -%>
`,<%= " val," * ctx[:count] %>
)
`, context)
25 changes: 23 additions & 2 deletions templates/terraform/examples/base_configs/test_file.go.erb
Original file line number Diff line number Diff line change
Expand Up @@ -27,13 +27,34 @@ import (
func TestAcc<%= test_slug -%>(t *testing.T) {
t.Parallel()

context := map[string]interface{} {
<% example.test_env_vars.each do |var_name, var_type| -%>
<% if var_type == :ORG_ID -%>
"<%= var_name -%>": getTestOrgFromEnv(t),
<% elsif var_type == :CREDENTIALS -%>
"<%= var_name -%>": getTestCredsFromEnv(t),
<% elsif var_type == :REGION -%>
"<%= var_name -%>": getTestRegionFromEnv(t),
<% elsif var_type == :ORG_TARGET -%>
"<%= var_name -%>": getTestOrgTargetFromEnv(t),
<% elsif var_type == :BILLING_ACCT -%>
"<%= var_name -%>": getTestBillingAccountFromEnv(t),
<% elsif var_type == :SERVICE_ACCT -%>
"<%= var_name -%>": getTestServiceAccountFromEnv(t),
<% elsif var_type == :PROJECT_NAME -%>
"<%= var_name -%>": getTestProjectFromEnv(t),
<% end -%>
<% end -%>
"random_suffix": acctest.RandString(10),
}

resource.Test(t, resource.TestCase{
PreCheck: func() { testAccPreCheck(t) },
Providers: testAccProviders,
CheckDestroy: testAccCheck<%= "#{resource_name}" -%>Destroy,
Steps: []resource.TestStep{
{
Config: testAcc<%= test_slug -%>(acctest.RandString(10)),
Config: testAcc<%= test_slug -%>(context),
},
{
ResourceName: "<%= terraform_name -%>.<%= example.primary_resource_id -%>",
Expand All @@ -47,7 +68,7 @@ func TestAcc<%= test_slug -%>(t *testing.T) {
})
}

func testAcc<%= test_slug -%>(val string) string {
func testAcc<%= test_slug -%>(context map[string]interface{}) string {
<%= example.config_test -%>
}
<%- end %>
Expand Down
31 changes: 30 additions & 1 deletion third_party/terraform/utils/import.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package google
import (
"fmt"
"regexp"
"strconv"
"strings"
)

Expand All @@ -25,7 +26,35 @@ func parseImportId(idRegexes []string, d TerraformResourceData, config *Config)
// Starting at index 1, the first match is the full string.
for i := 1; i < len(fieldValues); i++ {
fieldName := re.SubexpNames()[i]
d.Set(fieldName, fieldValues[i])
fieldValue := fieldValues[i]
// Because we do not know at this point whether 'fieldName'
// corresponds to a TypeString or a TypeInteger in the resource
// schema, we need to determine the type in an unintutitive way.
// We call d.Get, because examining the empty value is the easiest
// way to get that out. Normally, we would be able to just
// use a try/catch pattern - try as a string, and if that doesn't
// work, try as an integer, and if that doesn't work, return the
// error. Unfortunately, this is not possible here - during tests,
// d.Set(...) will panic if there is an error.
val, _ := d.GetOk(fieldName)
if _, ok := val.(string); val == nil || ok {
if err = d.Set(fieldName, fieldValue); err != nil {
return err
}
} else if _, ok := val.(int); ok {
if intVal, atoiErr := strconv.Atoi(fieldValue); atoiErr == nil {
// If the value can be parsed as an integer, we try to set the
// value as an integer.
if err = d.Set(fieldName, intVal); err != nil {
return err
}
} else {
return fmt.Errorf("%s appears to be an integer, but %v cannot be parsed as an int", fieldName, fieldValue)
}
} else {
return fmt.Errorf(
"cannot handle %s, which currently has value %v, and should be set to %#v, during import", fieldName, val, fieldValue)
}
}

// The first id format is applied first and contains all the fields.
Expand Down
1 change: 1 addition & 0 deletions third_party/terraform/utils/provider.go.erb
Original file line number Diff line number Diff line change
Expand Up @@ -125,6 +125,7 @@ func ResourceMapWithErrors() (map[string]*schema.Resource, error) {
GeneratedAccessContextManagerResourcesMap,
// end beta-only products
<% end -%>
GeneratedAppengineResourcesMap,
GeneratedComputeResourcesMap,
GeneratedDnsResourcesMap,
GeneratedRedisResourcesMap,
Expand Down
2 changes: 1 addition & 1 deletion third_party/terraform/utils/transport.go
Original file line number Diff line number Diff line change
Expand Up @@ -156,7 +156,7 @@ func replaceVars(d TerraformResourceData, config *Config, linkTmpl string) (stri
}
v, ok := d.GetOk(m)
if ok {
return v.(string)
return fmt.Sprintf("%v", v)
}
return ""
}
Expand Down
11 changes: 11 additions & 0 deletions third_party/terraform/utils/utils.go
Original file line number Diff line number Diff line change
Expand Up @@ -365,6 +365,17 @@ func lockedCall(lockKey string, f func() error) error {
return f()
}

// This is a Printf sibling (Nprintf; Named Printf), which handles strings like
// Nprintf("Hello %{target}!", map[string]interface{}{"target":"world"}) == "Hello world!".
// This is particularly useful for generated tests, where we don't want to use Printf,
// since that would require us to generate a very particular ordering of arguments.
func Nprintf(format string, params map[string]interface{}) string {
for key, val := range params {
format = strings.Replace(format, "%{"+key+"}", fmt.Sprintf("%v", val), -1)
}
return format
}

// serviceAccountFQN will attempt to generate the fully qualified name in the format of:
// "projects/(-|<project>)/serviceAccounts/<service_account_id>@<project>.iam.gserviceaccount.com"
// A project is required if we are trying to build the FQN from a service account id and
Expand Down

0 comments on commit 7e78e6f

Please sign in to comment.