Skip to content

Commit

Permalink
Enabling management of default Dialogflow resources without terraform…
Browse files Browse the repository at this point in the history
… import: `is_default_x` fields (GoogleCloudPlatform#9336)

* Enabling management of Default Start Flow + Default Welcome Intent + Default Negative Intent, without terraform import

Fixes hashicorp/terraform-provider-google#16308

* Basic tests for default flow + intents

* Tests asserting resource IDs

* Making Flow + Intent import respect is_default_X; fixing description on examples

* Bah, making isFallback output-only might break users that'd imported the Default Negative Intent :(

* Update mmv1/templates/terraform/pre_create/dialogflowcx_set_location_skip_default_obj.go.erb

Co-authored-by: Sarah French <[email protected]>

* Adding callout notes about multiple is_default_x resources

* More words around the pre-create + pre-delete code

* Adding more tests around Default Start Flow

* Adding more tests around Default Welcome Intent and Default Negative Intent

---------

Co-authored-by: Sarah French <[email protected]>
  • Loading branch information
2 people authored and BBBmau committed Nov 28, 2023
1 parent 8c92a9f commit 3365c57
Show file tree
Hide file tree
Showing 13 changed files with 592 additions and 4 deletions.
24 changes: 22 additions & 2 deletions mmv1/products/dialogflowcx/Flow.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -27,17 +27,37 @@ timeouts: !ruby/object:Api::Timeouts
update_minutes: 40
custom_code: !ruby/object:Provider::Terraform::CustomCode
custom_import: templates/terraform/custom_import/dialogflowcx_flow.go.erb
pre_create: templates/terraform/pre_create/dialogflow_set_location.go.erb
pre_create: templates/terraform/pre_create/dialogflowcx_set_location_skip_default_obj.go.erb
pre_update: templates/terraform/pre_create/dialogflow_set_location.go.erb
pre_delete: templates/terraform/pre_create/dialogflow_set_location.go.erb
pre_delete: templates/terraform/pre_delete/dialogflowcx_set_location_skip_default_obj.go.erb
pre_read: templates/terraform/pre_create/dialogflow_set_location.go.erb
virtual_fields:
- !ruby/object:Api::Type::Boolean
name: is_default_start_flow
immutable: true
description: |
Marks this as the [Default Start Flow](https://cloud.google.com/dialogflow/cx/docs/concept/flow#start) for an agent. When you create an agent, the Default Start Flow is created automatically.
The Default Start Flow cannot be deleted; deleting the `google_dialogflow_cx_flow` resource does nothing to the underlying GCP resources.
~> Avoid having multiple `google_dialogflow_cx_flow` resources linked to the same agent with `is_default_start_flow = true` because they will compete to control a single Default Start Flow resource in GCP.
examples:
- !ruby/object:Provider::Terraform::Examples
name: 'dialogflowcx_flow_basic'
primary_resource_id: 'basic_flow'
vars:
agent_name: 'dialogflowcx-agent'
- !ruby/object:Provider::Terraform::Examples
name: 'dialogflowcx_flow_full'
primary_resource_id: 'basic_flow'
vars:
agent_name: 'dialogflowcx-agent'
bucket_name: 'dialogflowcx-bucket'
- !ruby/object:Provider::Terraform::Examples
skip_docs: true
name: 'dialogflowcx_flow_default_start_flow'
primary_resource_id: 'default_start_flow'
vars:
agent_name: 'dialogflowcx-agent'
skip_sweeper: true
id_format: '{{parent}}/flows/{{name}}'
import_format: ['{{parent}}/flows/{{name}}']
Expand Down
34 changes: 32 additions & 2 deletions mmv1/products/dialogflowcx/Intent.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -27,16 +27,45 @@ timeouts: !ruby/object:Api::Timeouts
update_minutes: 40
custom_code: !ruby/object:Provider::Terraform::CustomCode
custom_import: templates/terraform/custom_import/dialogflowcx_intent.go.erb
pre_create: templates/terraform/pre_create/dialogflow_set_location.go.erb
pre_create: templates/terraform/pre_create/dialogflowcx_set_location_skip_default_obj.go.erb
pre_update: templates/terraform/pre_create/dialogflow_set_location.go.erb
pre_delete: templates/terraform/pre_create/dialogflow_set_location.go.erb
pre_delete: templates/terraform/pre_delete/dialogflowcx_set_location_skip_default_obj.go.erb
pre_read: templates/terraform/pre_create/dialogflow_set_location.go.erb
virtual_fields:
- !ruby/object:Api::Type::Boolean
name: is_default_welcome_intent
immutable: true
description: |
Marks this as the [Default Welcome Intent](https://cloud.google.com/dialogflow/cx/docs/concept/intent#welcome) for an agent. When you create an agent, a Default Welcome Intent is created automatically.
The Default Welcome Intent cannot be deleted; deleting the `google_dialogflow_cx_intent` resource does nothing to the underlying GCP resources.
~> Avoid having multiple `google_dialogflow_cx_intent` resources linked to the same agent with `is_default_welcome_intent = true` because they will compete to control a single Default Welcome Intent resource in GCP.
- !ruby/object:Api::Type::Boolean
name: is_default_negative_intent
immutable: true
description: |
Marks this as the [Default Negative Intent](https://cloud.google.com/dialogflow/cx/docs/concept/intent#negative) for an agent. When you create an agent, a Default Negative Intent is created automatically.
The Default Negative Intent cannot be deleted; deleting the `google_dialogflow_cx_intent` resource does nothing to the underlying GCP resources.
~> Avoid having multiple `google_dialogflow_cx_intent` resources linked to the same agent with `is_default_negative_intent = true` because they will compete to control a single Default Negative Intent resource in GCP.
examples:
- !ruby/object:Provider::Terraform::Examples
name: 'dialogflowcx_intent_full'
primary_resource_id: 'basic_intent'
vars:
agent_name: 'dialogflowcx-agent'
- !ruby/object:Provider::Terraform::Examples
skip_docs: true
name: 'dialogflowcx_intent_default_negative_intent'
primary_resource_id: 'default_negative_intent'
vars:
agent_name: 'dialogflowcx-agent'
- !ruby/object:Provider::Terraform::Examples
skip_docs: true
name: 'dialogflowcx_intent_default_welcome_intent'
primary_resource_id: 'default_welcome_intent'
vars:
agent_name: 'dialogflowcx-agent'
skip_sweeper: true
id_format: '{{parent}}/intents/{{name}}'
import_format: ['{{parent}}/intents/{{name}}']
Expand Down Expand Up @@ -144,6 +173,7 @@ properties:
description: |
Indicates whether this is a fallback intent. Currently only default fallback intent is allowed in the agent, which is added upon agent creation.
Adding training phrases to fallback intent is useful in the case of requests that are mistakenly matched, since training phrases assigned to fallback intents act as negative examples that triggers no-match event.
To manage the fallback intent, set `is_default_negative_intent = true`
- !ruby/object:Api::Type::KeyValueLabels
name: 'labels'
description: |
Expand Down
5 changes: 5 additions & 0 deletions mmv1/provider/terraform/virtual_fields.rb
Original file line number Diff line number Diff line change
Expand Up @@ -49,12 +49,17 @@ class VirtualFields < Api::Object
# The default value for the field (defaults to false)
attr_reader :default_value

# If set to true, changes in the field's value require recreating the
# resource.
attr_reader :immutable

def validate
super
check :name, type: String, required: true
check :description, type: String, required: true
check :type, type: Class, default: Api::Type::Boolean
check :default_value, default: false
check :immutable, default: false
end
end
end
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,4 +15,9 @@ if err != nil {
}
d.SetId(id)

// Set is_default_start_flow if the resource is actually the Default Start Flow
if d.Get("name").(string) == "00000000-0000-0000-0000-000000000000" {
d.Set("is_default_start_flow", true)
}

return []*schema.ResourceData{d}, nil
Original file line number Diff line number Diff line change
Expand Up @@ -15,4 +15,12 @@
}
d.SetId(id)

// Set is_default_X if the resource is actually a Default Intent
if d.Get("name").(string) == "00000000-0000-0000-0000-000000000000" {
d.Set("is_default_welcome_intent", true)
}
if d.Get("name").(string) == "00000000-0000-0000-0000-000000000001" {
d.Set("is_default_negative_intent", true)
}

return []*schema.ResourceData{d}, nil
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
resource "google_dialogflow_cx_agent" "agent" {
display_name = "<%= ctx[:vars]["agent_name"] %>"
location = "global"
default_language_code = "en"
time_zone = "America/New_York"
}

resource "google_dialogflow_cx_intent" "default_welcome_intent" {
parent = google_dialogflow_cx_agent.agent.id
is_default_welcome_intent = true
display_name = "Default Welcome Intent"
priority = 1
training_phrases {
parts {
text = "Hello"
}
repeat_count = 1
}
}


resource "google_dialogflow_cx_flow" "<%= ctx[:primary_resource_id] %>" {
parent = google_dialogflow_cx_agent.agent.id
is_default_start_flow = true
display_name = "Default Start Flow"
description = "A start flow created along with the agent"

nlu_settings {
classification_threshold = 0.3
model_type = "MODEL_TYPE_STANDARD"
}

transition_routes {
intent = google_dialogflow_cx_intent.default_welcome_intent.id
trigger_fulfillment {
messages {
text {
text = ["Response to default welcome intent."]
}
}
}
}

event_handlers {
event = "custom-event"
trigger_fulfillment {
messages {
text {
text = ["This is a default flow."]
}
}
}
}

event_handlers {
event = "sys.no-match-default"
trigger_fulfillment {
messages {
text {
text = ["We've updated the default flow no-match response!"]
}
}
}
}

event_handlers {
event = "sys.no-input-default"
trigger_fulfillment {
messages {
text {
text = ["We've updated the default flow no-input response!"]
}
}
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
resource "google_dialogflow_cx_agent" "agent" {
display_name = "<%= ctx[:vars]["agent_name"] %>"
location = "global"
default_language_code = "en"
time_zone = "America/New_York"
}


resource "google_dialogflow_cx_intent" "<%= ctx[:primary_resource_id] %>" {
parent = google_dialogflow_cx_agent.agent.id
is_default_negative_intent = true
display_name = "Default Negative Intent"
priority = 1
is_fallback = true
training_phrases {
parts {
text = "Never match this phrase"
}
repeat_count = 1
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
resource "google_dialogflow_cx_agent" "agent" {
display_name = "<%= ctx[:vars]["agent_name"] %>"
location = "global"
default_language_code = "en"
time_zone = "America/New_York"
}


resource "google_dialogflow_cx_intent" "<%= ctx[:primary_resource_id] %>" {
parent = google_dialogflow_cx_agent.agent.id
is_default_welcome_intent = true
display_name = "Default Welcome Intent"
priority = 1
training_phrases {
parts {
text = "Hello"
}
repeat_count = 1
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@

// extract location from the parent
location := ""

if parts := regexp.MustCompile(`locations\/([^\/]*)\/`).FindStringSubmatch(d.Get("parent").(string)); parts != nil {
location = parts[1]
} else {
return fmt.Errorf(
"Saw %s when the parent is expected to contains location %s",
d.Get("parent"),
"projects/{{project}}/locations/{{location}}/...",
)
}

url = strings.Replace(url,"-dialogflow",fmt.Sprintf("%s-dialogflow",location),1)

// if it's a default object Dialogflow creates for you, "Update" instead of "Create"
// Note: below we try to access fields that aren't present in the resource, because this custom code is reused across multiple Dialogflow resources that contain different fields. When the field isn't present, we deliberately ignore the error and the boolean is false.
isDefaultStartFlow, _ := d.Get("is_default_start_flow").(bool)
isDefaultWelcomeIntent, _ := d.Get("is_default_welcome_intent").(bool)
isDefaultNegativeIntent, _ := d.Get("is_default_negative_intent").(bool)
if isDefaultStartFlow || isDefaultWelcomeIntent || isDefaultNegativeIntent {
// hardcode the default object ID:
var defaultObjName string
if isDefaultStartFlow || isDefaultWelcomeIntent {
defaultObjName = "00000000-0000-0000-0000-000000000000"
}
if isDefaultNegativeIntent {
defaultObjName = "00000000-0000-0000-0000-000000000001"
}

// Store the ID
d.Set("name", defaultObjName)
id, err := tpgresource.ReplaceVars(d, config, "<%= id_format(object) -%>")
if err != nil {
return fmt.Errorf("Error constructing id: %s", err)
}
d.SetId(id)

// and defer to the Update method:
log.Printf("[DEBUG] Updating default <%= resource_name -%>")
return resource<%= resource_name -%>Update(d, meta)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@

// extract location from the parent
location := ""

if parts := regexp.MustCompile(`locations\/([^\/]*)\/`).FindStringSubmatch(d.Get("parent").(string)); parts != nil {
location = parts[1]
} else {
return fmt.Errorf(
"Saw %s when the parent is expected to contains location %s",
d.Get("parent"),
"projects/{{project}}/locations/{{location}}/...",
)
}

url = strings.Replace(url,"-dialogflow",fmt.Sprintf("%s-dialogflow",location),1)

// if it's a default object Dialogflow creates for you, skip deletion
// Note: below we try to access fields that aren't present in the resource, because this custom code is reused across multiple Dialogflow resources that contain different fields. When the field isn't present, we deliberately ignore the error and the boolean is false.
isDefaultStartFlow, _ := d.Get("is_default_start_flow").(bool)
isDefaultWelcomeIntent, _ := d.Get("is_default_welcome_intent").(bool)
isDefaultNegativeIntent, _ := d.Get("is_default_negative_intent").(bool)
if isDefaultStartFlow || isDefaultWelcomeIntent || isDefaultNegativeIntent {
// we can't delete these resources so do nothing
log.Printf("[DEBUG] Not deleting default <%= resource_name -%>")
return nil
}
3 changes: 3 additions & 0 deletions mmv1/templates/terraform/resource.erb
Original file line number Diff line number Diff line change
Expand Up @@ -155,6 +155,9 @@ func Resource<%= resource_name -%>() *schema.Resource {
"<%= field.name -%>": {
Type: <%= tf_type(field) -%>,
Optional: true,
<% if field.immutable -%>
ForceNew: true,
<% end -%>
<% unless field.default_value.nil? -%>
Default: <%= go_literal(field.default_value) -%>,
<% end -%>
Expand Down
Loading

0 comments on commit 3365c57

Please sign in to comment.