Skip to content

Add support for Google Cloud IoT Core registry #970

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

Merged
merged 11 commits into from
Jan 24, 2018

Conversation

Ebolon
Copy link
Contributor

@Ebolon Ebolon commented Jan 17, 2018

This pull request adds support for the Google Cloud IoT Core registry. The config structure reflects the structure of the registry API. The downside is a lot of nesting in the config. But hopefully it allows a easy extension if the API evolves.

@nat-henderson nat-henderson self-requested a review January 17, 2018 20:46
@@ -88,6 +88,7 @@ func Provider() terraform.ResourceProvider {
"google_bigtable_instance": resourceBigtableInstance(),
"google_bigtable_table": resourceBigtableTable(),
"google_cloudfunctions_function": resourceCloudFunctionsFunction(),
"google_cloudiot_registry": resourceCloudiotRegistry(),
Copy link
Contributor

Choose a reason for hiding this comment

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

I prefer CloudIoT for capitalization consistency - I think most monospace fonts render the I sufficiently differently from an l that it probably won't be an issue, and this is what https://github.com/golang/go/wiki/CodeReviewComments#initialisms requests.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Looks much better 👍 Done.

{
"public_key_certificate" = {
format = "X509_CERTIFICATE_PEM"
certificate = "-----BEGIN CERTIFICATE-----\nMIICnjCCAYYCCQC/5gx7LgJFqTANBgkqhkiG9w0BAQsFADARMQ8wDQYDVQQDDAZ1\nbnVzZWQwHhcNMTgwMTEyMjAxMzQzWhcNMjMwMTExMjAxMzQzWjARMQ8wDQYDVQQD\nDAZ1bnVzZWQwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDStfQvJzmN\nCYLSWpwvTmyCKn8t19cfWZ69wFaB3OSglxXgYe3w9An0QHybDpKITt61PpfsKov3\nEcnzH5IA+Ox+4jUppBL1mSkO/BWig+sd1dG7pQMbGi4nMxW704A0PRUaNIOarOlR\nrNUJZQrsghkMjLayCTJ2HISBBiPnKKB3f3KCc9sDhj2Z7zy7HfeW0apZ1m6xAQCC\neSZNW0IyGIYKTd9F7HEJFzOWg9JHvabbciBEcFWKGVzM8nQr1q8KU8Xi3iN2mpNK\nJkbRLNnqKhvjPyIZ4s4cDSEZN1/OaGQ4XP2mvU03+4UAoMPoJ8IczBKTB0mFxfX8\nlDZZa5IWU9sNAgMBAAEwDQYJKoZIhvcNAQELBQADggEBAHnkTIghRj/cerR9ctji\nkancnjlsdNEuPiVpMj+SOtOH8cvlgl0oWG6segYTVzk4VEHlq3POB67Yjoz829XM\nCEgUxSqGvDrQ7IaPLPryYy8o5azMLnEZDr+Yd6CUKr/pUZzJoZxHj7z3iqeQZnMW\nS6kb6HYvG5PKlJ7+JUIKLou0RQmaM9BQ0Nln/YDRRIerD0MY9k7No2ZEDbywZqQK\nGRIqT+BlN84oHOR44h2RqWhn9O50tkbcmAIKgmeg/mxwmeAm/6hQ8VrOhDHqsFdT\nzh2l6IeCl8EF8MjNrFRcQx21TTqeU6vGIPgM3E0k8PQUc+s+lir8UFsIzKaOFsIh\nuKU=\n-----END CERTIFICATE-----\n"
Copy link
Contributor

Choose a reason for hiding this comment

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

Consider committing this as a file to google/test-fixtures instead.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

👍

}
}

func testAccCloudiotRegistryUpdate(n string) resource.TestCheckFunc {
Copy link
Contributor

Choose a reason for hiding this comment

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

This seems elaborate - I think it only tests that the state matches the config, which is implicitly part of a test that has config. If the state doesn't match the config post-update, the test will fail even without this method, and so removing this method reduces the difficulty of maintenance.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

👍 Have removed this function. I was not aware of this internal test.

Copy link
Contributor

Choose a reason for hiding this comment

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

Oh, I apologize but I was wrong about this - this is true only when there's an importstate step on the test. If you can add one of those in, these tests will be easier to maintain, but if that's a hassle for unforeseen reasons you can definitely go back to this method. My bad!

{
public_key_certificate = {
format = "X509_CERTIFICATE_PEM"
certificate = "<CERTIFICATE>"
Copy link
Contributor

Choose a reason for hiding this comment

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

Wouldn't hurt to do, maybe, "${file("foo.pem")}" or something like that so the example is more valid?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yep! Sounds much better!

Type: schema.TypeList,
Optional: true,
ForceNew: false,
MaxItems: 10,
Copy link
Contributor

Choose a reason for hiding this comment

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

Where does the max of 10 come from?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I found the max 10 in the documentation of DeviceRegistry for the credentials field: "No more than 10 credentials can be bound to a single registry at a time.". Hope I got it right.


parent := fmt.Sprintf("projects/%s/locations/%s", project, region)

deviceRegistry := expandDeviceRegistry(d)
Copy link
Contributor

Choose a reason for hiding this comment

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

Here, for instance, createDeviceRegistry would be more accurate.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

👍

}
d.Set("event_notification_configs", eventConfigs)
}
// to keep the data model lean only changes are pushed if not the default is
Copy link
Contributor

Choose a reason for hiding this comment

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

Could you clean up the wording in this comment a bit?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Done


d.Set("name", res.Id)

if res.EventNotificationConfigs != nil {
Copy link
Contributor

Choose a reason for hiding this comment

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

If they are nil, is it right to clear them from the ResourceData object? To stay up to date with, for instance, a deleted config from the console?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yes I missed that. Now I reset first everything to nil.

return nil
}

func resourceCloudiotRegistryStateImporter(d *schema.ResourceData, meta interface{}) ([]*schema.ResourceData, error) {
Copy link
Contributor

Choose a reason for hiding this comment

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

Need to see an import test.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Done

return nil, err
}

id := fmt.Sprintf("projects/%s/locations/%s/registries/%s", project, region, d.Id())
Copy link
Contributor

Choose a reason for hiding this comment

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

Problem - in this mode, the user must configure the provider such that the default region and project are the right ones - d.Id() is the only thing you'll have going into the import.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Not thought about that. I now ask for full path projects/{project}/locations/{region}/registries/{name} as id. (see also registry get API).

- Removed default parameters from schema.
- Added import test.
- Refactored naming of methods (expand->build)
- Before read set all parameters of model to nil.
- Moved cert data to external file.
@Ebolon
Copy link
Contributor Author

Ebolon commented Jan 21, 2018

Thank you very much for your feedback @ndmckinley! Hope I got now everything right. How does it look now?

PS: I added a validation function for the registry id. I hope the function name is ok. This can be reuses later for a device id (see: Creating or editing a device).

return handleNotFoundError(err, d, fmt.Sprintf("Registry %q", name))
}

d.Set("name", res.Id)
Copy link
Contributor

Choose a reason for hiding this comment

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

A comment here which explains that you're speculatively setting all attributes to nil and updating the ones you find later might help.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

After some testing I saw that all the nil checks are not necessary. Hope my changes now makes it more readable.
Just to make sure we have the same thing in mind when we read the code:
This is

resource "google_cloudiot_registry" "hello" {
  name = "hello"
}

comparable with

{
 "id": "hello",
 "name": "projects/<...>/locations/<...>/registries/hello",
 "mqttConfig": {
  "mqttEnabledState": "MQTT_ENABLED"
 },
 "stateNotificationConfig": {
 },
 "httpConfig": {
  "httpEnabledState": "HTTP_ENABLED"
 }
}

because the code ignores default state, mqtt, http config:

@@ -107,3 +109,16 @@ func validateRFC1035Name(min, max int) schema.SchemaValidateFunc {

return validateRegexp(fmt.Sprintf("^"+RFC1035NameTemplate+"$", min-2, max-2))
}

func validateCloudIoTId(v interface{}, k string) (warnings []string, errors []error) {
Copy link
Contributor

Choose a reason for hiding this comment

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

nitpick: "ID" is capitalized when it's short for "identifier"
https://github.com/golang/go/wiki/CodeReviewComments#initialisms

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Done.

@@ -18,6 +19,7 @@ const (
SubnetworkLinkRegex = "projects/(" + ProjectRegex + ")/regions/(" + RegionRegex + ")/subnetworks/(" + SubnetworkRegex + ")$"

RFC1035NameTemplate = "[a-z](?:[-a-z0-9]{%d,%d}[a-z0-9])"
CloudIoTIdRegex = "[a-zA-Z][-a-zA-Z0-9._+~%]{2,254}"
Copy link
Contributor

Choose a reason for hiding this comment

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

Is this well-tested? The formatter is trying to complain about the % as if it's a format-string specifier and I'm pretty sure it's a formatter bug and not a bug in your code. :)

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Unit test is now included. Found bug and yep this seems to be a formatter bug :-)

if err != nil {
return err
}
d.SetId(res.Name)
Copy link
Contributor

Choose a reason for hiding this comment

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

It can be helpful to do d.SetId(name) before the create call, then do d.SetId(nil) in the event of an error - that way, if the process is killed during the request, the state file will include the resource, and we'll be able to do a Read() on refresh to determine if the call ever actually succeeded.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Done.

@nat-henderson
Copy link
Contributor

It's looking pretty good to me - I made a mistake in the earlier review, sorry for the back-and-forth, but other than that, and these small changes, I'm happy with it.

Ebolon and others added 3 commits January 22, 2018 23:27
- Removed unnecessary nil checks in read function.
- Added unit test for cloudiot id validation func.
- Added start/end delimiter to cloud iot id regex.
- Fixed bug were certficates were missing in read.
- Set id to empty if creating a registry fails.
@Ebolon
Copy link
Contributor Author

Ebolon commented Jan 22, 2018

Have implemented the requested changes. Found a bug where the certificates were not properly read into the state, because I forgot the public_key_certificate key. Ready for review.

@nat-henderson
Copy link
Contributor

Great - I'm running the tests now and if they pass, I'll merge it.

@nat-henderson
Copy link
Contributor

nat-henderson commented Jan 22, 2018

Hey there, looks like we're getting errors from the tests, which seem to assume a particular setup. Specifically, for TestAccCloudIoTRegistryUpdate we're getting

* google_cloudiot_registry.foobar: googleapi: Error 403: Topic 'projects/<OUR PROJECT NAME>/topics/psregistry-test-devicestatus-pqzo18advm' was specified, but user '[email protected]' does not have 'pubsub.topics.publish' permission to publish events to the topic, or the topic doesn't exist. Make sure that the topic exists, and edit the topic permissions to add the 'Pub/Sub Publisher' role https://cloud.google.com/pubsub/docs/access_control#tbl_roles to the '[email protected]' account. You can also run the following command to add publish permission to all topics in your project:\n\ngcloud projects add-iam-policy-binding $PROJECT --member=serviceAccount:[email protected] --role=roles/pubsub.publisher, forbidden

@Ebolon
Copy link
Contributor Author

Ebolon commented Jan 23, 2018

The test creates two pubsub topics and sets no polices. I were not aware of this restricted test env. My setup IAM Role has those simple settings:

I am trying to reproduce this on my side.

--

I found that the terraform schema validation did not work if certificate in public_key_certificate is missing, although it is marked as required. Should I catch those errors or wait for terraform to catch those errors?

PS: Sorry for last minute changes. I played a bit more with the API. UNSPECIFIED_PUBLIC_KEY_CERTIFICATE_FORMAT only results in an error and MQTT_STATE_UNSPECIFIED or MQTT_STATE_UNSPECIFIED are never returned. (Only _ENABLEDis returned) Just thought I remove those before it generates some confusion.

@nat-henderson
Copy link
Contributor

The important thing is that the test passes without any special configuration on our side ahead of time - ideally we wouldn't need to manually create a service account and grant it permissions. The best case would be for you to put that service account creation and permission grants into the test terraform config! But if that doesn't work out, let me know how to set it up and I'll see what I can do. :)

@nat-henderson
Copy link
Contributor

Thanks! I'll run the tests again.

@Ebolon
Copy link
Contributor Author

Ebolon commented Jan 23, 2018

Ouch! This was a RTFM moment. Got it fixed now :-)

@nat-henderson
Copy link
Contributor

Hey, wonderful, it passes! :) Last thing - the import test is obsolete since your other tests do an import now, and you can remove it without decreasing coverage. After that, I'll merge it. :)

@Ebolon
Copy link
Contributor Author

Ebolon commented Jan 23, 2018

Do you mean TestAccCloudIoTRegistry_import?

@nat-henderson
Copy link
Contributor

Yes, you can remove the entire import_*_test.go file.

@Ebolon
Copy link
Contributor Author

Ebolon commented Jan 24, 2018

Done.

@nat-henderson nat-henderson merged commit fec2f0b into hashicorp:master Jan 24, 2018
@nat-henderson
Copy link
Contributor

Thank you!

chrisst pushed a commit to chrisst/terraform-provider-google that referenced this pull request Nov 9, 2018
@ghost
Copy link

ghost commented Mar 29, 2020

I'm going to lock this issue because it has been closed for 30 days ⏳. This helps our maintainers find and focus on the active issues.

If you feel this issue should be reopened, we encourage creating a new issue linking back to this one for added context. If you feel I made an error 🤖 🙉 , please reach out to my human friends 👉 [email protected]. Thanks!

@ghost ghost locked and limited conversation to collaborators Mar 29, 2020
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants