diff --git a/build/ansible b/build/ansible index d979b11fbe02..f6f37925afc3 160000 --- a/build/ansible +++ b/build/ansible @@ -1 +1 @@ -Subproject commit d979b11fbe025cd91f7396a39d7f7720f288c494 +Subproject commit f6f37925afc3ff29a7c5af79fa3ca4402d135ed8 diff --git a/build/terraform b/build/terraform index b5462e6dc85c..1454cba6646c 160000 --- a/build/terraform +++ b/build/terraform @@ -1 +1 @@ -Subproject commit b5462e6dc85c2d3a3b0f0b7f20eebea040a196c2 +Subproject commit 1454cba6646c304e14f8911995124e748d5dfd1f diff --git a/build/terraform-beta b/build/terraform-beta index 1f182c35df50..3a1d01bc0337 160000 --- a/build/terraform-beta +++ b/build/terraform-beta @@ -1 +1 @@ -Subproject commit 1f182c35df505add2ddbfefbac6eaa21891ac525 +Subproject commit 3a1d01bc0337459101a4cd0824da291162835dd3 diff --git a/products/appengine/ansible.yaml b/products/appengine/ansible.yaml index 3db68c761202..257b1c66f8af 100644 --- a/products/appengine/ansible.yaml +++ b/products/appengine/ansible.yaml @@ -23,6 +23,10 @@ overrides: !ruby/object:Overrides::ResourceOverrides # differently than Terraform. identity: - priority + Service: !ruby/object:Overrides::Ansible::ResourceOverride + exclude: true + StandardAppVersion: !ruby/object:Overrides::Ansible::ResourceOverride + exclude: true files: !ruby/object:Provider::Config::Files resource: <%= lines(indent(compile('provider/ansible/resource~compile.yaml'), 4)) -%> diff --git a/products/appengine/api.yaml b/products/appengine/api.yaml index 40e7619e38f2..383e229dd199 100644 --- a/products/appengine/api.yaml +++ b/products/appengine/api.yaml @@ -49,11 +49,13 @@ objects: properties: - !ruby/object:Api::Type::String name: 'description' - description: 'An optional string description of this rule.' + 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.' + 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' @@ -64,3 +66,262 @@ objects: - UNSPECIFIED_ACTION - ALLOW - DENY + - !ruby/object:Api::Resource + name: 'Service' + description: | + A Service resource is a logical component of an application that can share state and communicate in a secure fashion with other services. + For example, an application that handles customer requests might include separate services to handle tasks such as backend data analysis or API requests from mobile devices. + Each service has a collection of versions that define a specific set of code used to implement the functionality of that service. + base_url: 'apps/{{project}}/services' + self_link: 'apps/{{project}}/services/{{id}}' + references: !ruby/object:Api::Resource::ReferenceLinks + guides: + 'Official Documentation': + 'https://cloud.google.com/appengine/docs/admin-api/deploying-overview' + api: 'https://cloud.google.com/appengine/docs/admin-api/reference/rest/v1/apps.services' + properties: + - !ruby/object:Api::Type::String + name: 'name' + output: true + description: | + Full path to the Service resource in the API. Example apps/myapp/services/default. + This field is used in responses only. Any value specified here in a request is ignored. + - !ruby/object:Api::Type::String + name: 'id' + output: true + description: | + Relative name of the service within the application. Example default. + - !ruby/object:Api::Resource + name: 'StandardAppVersion' + description: | + Standard App Version resource to create a new version of standard GAE Application. + Currently supporting Zip and File Containers. + Currently does not support async operation checking. + base_url: 'apps/{{project}}/services/{{service}}/versions' + delete_url: 'apps/{{project}}/services/{{service}}/versions/{{version_id}}' + delete_verb: :DELETE + update_url: 'apps/{{project}}/services/{{service}}/versions' + update_verb: :POST + update_mask: false + self_link: 'apps/{{project}}/services/{{service}}/versions/{{version_id}}' + references: !ruby/object:Api::Resource::ReferenceLinks + guides: + 'Official Documentation': + 'https://cloud.google.com/appengine/docs/admin-api/deploying-overview' + api: 'https://cloud.google.com/appengine/docs/admin-api/reference/rest/v1/apps.services.versions' + async: !ruby/object:Api::Async + operation: !ruby/object:Api::Async::Operation + kind: 'appengine#operation' + path: 'name' + base_url: 'projects/{{project}}/global/operations/{{op_id}}' + wait_ms: 1000 + result: !ruby/object:Api::Async::Result + path: 'targetLink' + status: !ruby/object:Api::Async::Status + path: 'status' + complete: 'DONE' + allowed: + - 'PENDING' + - 'RUNNING' + - 'DONE' + error: !ruby/object:Api::Async::Error + path: 'error/errors' + message: 'message' + parameters: + - !ruby/object:Api::Type::ResourceRef + name: 'service' + url_param_only: true + resource: 'Service' + imports: 'name' + description: | + AppEngine service resource + properties: + - !ruby/object:Api::Type::String + name: 'name' + output: true + description: | + Full path to the Version resource in the API. Example, "v1". + - !ruby/object:Api::Type::String + name: 'id' + input: true + description: | + Relative name of the version within the service. For example, `v1`. Version names can contain only lowercase letters, numbers, or hyphens. Reserved names,"default", "latest", and any name with the prefix "ah-". + - !ruby/object:Api::Type::String + name: 'runtime' + description: | + Desired runtime. Example python27. + required: true + - !ruby/object:Api::Type::Boolean + name: 'threadsafe' + description: | + Whether multiple requests can be dispatched to this version at once. + - !ruby/object:Api::Type::String + name: 'runtimeApiVersion' + description: | + The version of the API in the given runtime environment. + Please see the app.yaml reference for valid values at https://cloud.google.com/appengine/docs/standard//config/appref + - !ruby/object:Api::Type::Array + name: 'handlers' + description: | + An ordered list of URL-matching patterns that should be applied to incoming requests. + The first matching URL handles the request and other request handlers are not attempted. + item_type: !ruby/object:Api::Type::NestedObject + properties: + - !ruby/object:Api::Type::String + name: 'urlRegex' + description: | + URL prefix. Uses regular expression syntax, which means regexp special characters must be escaped, but should not contain groupings. + All URLs that begin with this prefix are handled by this handler, using the portion of the URL after the prefix as part of the file path. + - !ruby/object:Api::Type::Enum + name: 'securityLevel' + required: false + description: | + Security (HTTPS) enforcement for this URL. + values: + - :SECURE_UNSPECIFIED + - :SECURE_DEFAULT + - :SECURE_NEVER + - :SECURE_OPTIONAL + - :SECURE_ALWAYS + - !ruby/object:Api::Type::Enum + name: 'login' + description: | + Methods to restrict access to a URL based on login status. + required: false + values: + - :LOGIN_UNSPECIFIED + - :LOGIN_OPTIONAL + - :LOGIN_ADMIN + - :LOGIN_REQUIRED + - !ruby/object:Api::Type::Enum + name: 'authFailAction' + description: | + Actions to take when the user is not logged in. + required: false + values: + - :AUTH_FAIL_ACTION_UNSPECIFIED + - :AUTH_FAIL_ACTION_REDIRECT + - :AUTH_FAIL_ACTION_UNAUTHORIZED + - !ruby/object:Api::Type::Enum + name: 'redirectHttpResponseCode' + description: | + Redirect codes. + required: false + values: + - :REDIRECT_HTTP_RESPONSE_CODE_UNSPECIFIED + - :REDIRECT_HTTP_RESPONSE_CODE_301 + - :REDIRECT_HTTP_RESPONSE_CODE_302 + - :REDIRECT_HTTP_RESPONSE_CODE_303 + - :REDIRECT_HTTP_RESPONSE_CODE_307 + - !ruby/object:Api::Type::NestedObject + name: 'script' + description: | + Executes a script to handle the requests that match this URL pattern. + Only the auto value is supported for Node.js in the App Engine standard environment, for example "script:" "auto". + properties: + - !ruby/object:Api::Type::String + name: 'scriptPath' + description: | + Path to the script from the application root directory. + - !ruby/object:Api::Type::NestedObject + name: 'staticFiles' + description: | + Files served directly to the user for a given URL, such as images, CSS stylesheets, or JavaScript source files. Static file handlers describe which files in the application directory are static files, and which URLs serve them. + properties: + - !ruby/object:Api::Type::String + name: 'path' + description: | + Path to the static files matched by the URL pattern, from the application root directory. The path can refer to text matched in groupings in the URL pattern. + - !ruby/object:Api::Type::String + name: 'uploadPathRegex' + description: | + Regular expression that matches the file paths for all files that should be referenced by this handler. + - !ruby/object:Api::Type::KeyValuePairs + name: 'httpHeaders' + description: | + HTTP headers to use for all responses from these URLs. + An object containing a list of "key:value" value pairs.". + - !ruby/object:Api::Type::String + name: 'mimeType' + description: | + MIME type used to serve all files served by this handler. + Defaults to file-specific MIME types, which are derived from each file's filename extension. + - !ruby/object:Api::Type::String + name: 'expiration' + description: | + Time a static file served by this handler should be cached by web proxies and browsers. + A duration in seconds with up to nine fractional digits, terminated by 's'. Example "3.5s". + - !ruby/object:Api::Type::Boolean + name: 'requireMatchingFile' + description: | + Whether this handler should match the request if the file referenced by the handler does not exist. + - !ruby/object:Api::Type::Boolean + name: 'applicationReadable' + description: | + Whether files should also be uploaded as code data. By default, files declared in static file handlers are uploaded as static data and are only served to end users; they cannot be read by the application. If enabled, uploads are charged against both your code and static data storage resource quotas. + - !ruby/object:Api::Type::Array + name: 'libraries' + description: | + Configuration for third-party Python runtime libraries that are required by the application. + item_type: !ruby/object:Api::Type::NestedObject + properties: + - !ruby/object:Api::Type::String + name: 'name' + description: | + Name of the library. Example "django". + - !ruby/object:Api::Type::String + name: 'version' + description: | + Version of the library to select, or "latest". + - !ruby/object:Api::Type::KeyValuePairs + name: 'envVariables' + description: | + Environment variables available to the application. + - !ruby/object:Api::Type::NestedObject + name: 'deployment' + description: | + Code and application artifacts that make up this version. + required: false + properties: + - !ruby/object:Api::Type::NestedObject + name: 'zip' + description: 'Zip File' + required: false + properties: + - !ruby/object:Api::Type::String + name: 'sourceUrl' + description: 'Source URL' + - !ruby/object:Api::Type::Integer + name: 'filesCount' + description: 'files count' + required: false + - !ruby/object:Api::Type::Map + name: 'files' + description: | + Manifest of the files stored in Google Cloud Storage that are included as part of this version. + All files must be readable using the credentials supplied with this call. + required: false + key_name: 'name' + key_description: | + name of file + value_type: !ruby/object:Api::Type::NestedObject + properties: + - !ruby/object:Api::Type::String + name: 'sha1Sum' + description: | + SHA1 checksum of the file + - !ruby/object:Api::Type::String + name: 'sourceUrl' + description: | + Source URL + - !ruby/object:Api::Type::NestedObject + name: 'entrypoint' + description: | + The entrypoint for the application. + required: false + properties: + - !ruby/object:Api::Type::String + name: 'shell' + description: | + The format should be a shell command that can be fed to bash -c. + diff --git a/products/appengine/terraform.yaml b/products/appengine/terraform.yaml index 6d7a019a2a16..a590f3cc2c51 100644 --- a/products/appengine/terraform.yaml +++ b/products/appengine/terraform.yaml @@ -24,7 +24,47 @@ overrides: !ruby/object:Overrides::ResourceOverrides project_id: "test-project" test_env_vars: org_id: :ORG_ID - + StandardAppVersion: !ruby/object:Overrides::Terraform::ResourceOverride + id_format: "apps/{{project}}/services/{{service}}/versions/{{version_id}}" + import_format: ["apps/{{project}}/services/{{service}}/versions/{{version_id}}"] + mutex: "apps/{{project}}/services/{{service}}" + parameters: + service: !ruby/object:Overrides::Terraform::PropertyOverride + default_from_api: true + required: false + virtual_fields: + - !ruby/object:Provider::Terraform::VirtualFields + name: 'noop_on_destroy' + description: | + If set to `true`, the application version will not be deleted. + custom_code: !ruby/object:Provider::Terraform::CustomCode + custom_delete: templates/terraform/custom_delete/noop_on_destroy_appengine_version.go.erb + test_check_destroy: templates/terraform/custom_check_destroy/appengine_version.go.erb + properties: + id: !ruby/object:Overrides::Terraform::PropertyOverride + name: 'version_id' + deployment: !ruby/object:Overrides::Terraform::PropertyOverride + ignore_read: true + entrypoint: !ruby/object:Overrides::Terraform::PropertyOverride + ignore_read: true + envVariables: !ruby/object:Overrides::Terraform::PropertyOverride + ignore_read: true + threadsafe: !ruby/object:Overrides::Terraform::PropertyOverride + ignore_read: true + examples: + - !ruby/object:Provider::Terraform::Examples + name: "app_engine_standard_app_version" + primary_resource_id: "version_id" + ignore_read_extra: + - "noop_on_destroy" + vars: + project_id: "test-project" + bucket_name: "appengine-static-content" + service_name: "tf-test-service" + test_env_vars: + org_id: :ORG_ID + Service: !ruby/object:Overrides::Terraform::ResourceOverride + exclude: true # This is for copying files over files: !ruby/object:Provider::Config::Files # These files have templating (ERB) code that will be run. diff --git a/templates/terraform/custom_check_destroy/appengine_version.go.erb b/templates/terraform/custom_check_destroy/appengine_version.go.erb new file mode 100644 index 000000000000..07e0d309886f --- /dev/null +++ b/templates/terraform/custom_check_destroy/appengine_version.go.erb @@ -0,0 +1 @@ +log.Printf("[DEBUG] Ignoring destroy during test") diff --git a/templates/terraform/custom_delete/noop_on_destroy_appengine_version.go.erb b/templates/terraform/custom_delete/noop_on_destroy_appengine_version.go.erb new file mode 100644 index 000000000000..142d187a69fb --- /dev/null +++ b/templates/terraform/custom_delete/noop_on_destroy_appengine_version.go.erb @@ -0,0 +1,49 @@ +if d.Get("noop_on_destroy") == true { + log.Printf("[DEBUG] Keeping the StandardAppVersion %q", d.Id()) + return nil +} +config := meta.(*Config) + +project, err := getProject(d, config) +if err != nil { + return err +} + +lockName, err := replaceVars(d, config, "apps/{{project}}/services/{{service}}") +if err != nil { + return err +} +mutexKV.Lock(lockName) +defer mutexKV.Unlock(lockName) + +url, err := replaceVars(d, config, "{{AppEngineBasePath}}apps/{{project}}/services/{{service}}/versions/{{version_id}}") +if err != nil { + return err +} + +var obj map[string]interface{} +log.Printf("[DEBUG] Deleting StandardAppVersion %q", d.Id()) + +res, err := sendRequestWithTimeout(config, "DELETE", project, url, obj, d.Timeout(schema.TimeoutDelete)) +if err != nil { + return handleNotFoundError(err, d, "StandardAppVersion") +} + +op := &appengine.Operation{} +err = Convert(res, op) +if err != nil { + return err +} + +err = appEngineOperationWaitTime( + config.clientAppEngine, op, project, "Deleting StandardAppVersion", + int(d.Timeout(schema.TimeoutDelete).Minutes())) + +if err != nil { + return err +} + +log.Printf("[DEBUG] Finished deleting StandardAppVersion %q: %#v", d.Id(), res) +return nil + + diff --git a/templates/terraform/examples/app_engine_standard_app_version.tf.erb b/templates/terraform/examples/app_engine_standard_app_version.tf.erb new file mode 100644 index 000000000000..c8386a18da23 --- /dev/null +++ b/templates/terraform/examples/app_engine_standard_app_version.tf.erb @@ -0,0 +1,28 @@ +resource "google_storage_bucket" "bucket" { + name = "<%= ctx[:vars]['bucket_name'] %>" +} + +resource "google_storage_bucket_object" "object" { + name = "hello-world.zip" + bucket = "${google_storage_bucket.bucket.name}" + source = "./test-fixtures/appengine/hello-world.zip" +} + +resource "google_app_engine_standard_app_version" "<%= ctx[:primary_resource_id] %>" { + version_id = "v2" + service = "default" + runtime = "nodejs10" + noop_on_destroy = true + entrypoint { + shell = "node ./app.js" + } + deployment { + zip { + source_url = "https://storage.googleapis.com/${google_storage_bucket.bucket.name}/hello-world.zip" + } + } + env_variables = { + port = "8080" + } + +} diff --git a/third_party/terraform/utils/test-fixtures/appengine/hello-world.zip b/third_party/terraform/utils/test-fixtures/appengine/hello-world.zip new file mode 100644 index 000000000000..658a65aed109 Binary files /dev/null and b/third_party/terraform/utils/test-fixtures/appengine/hello-world.zip differ diff --git a/third_party/terraform/website-compiled/google.erb b/third_party/terraform/website-compiled/google.erb index b88947e8d687..a88c29a621f8 100644 --- a/third_party/terraform/website-compiled/google.erb +++ b/third_party/terraform/website-compiled/google.erb @@ -218,6 +218,9 @@