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

Cloud functions #899

Merged
merged 37 commits into from
Jan 10, 2018
Merged

Cloud functions #899

merged 37 commits into from
Jan 10, 2018

Conversation

sarunask
Copy link
Contributor

This is PR for #76
Added google_cloudfunctions_function resouse and data providers.

@sarunask
Copy link
Contributor Author

sarunask commented Jan 3, 2018

I have fixed missing vendor package for cloudfunctions.
In order to pass Acceptance test, Google account needs to have CloudFunctions API enabled.
Account, used for Acceptance tests, also should have such permissions:
Cloud Functions Developer
Pub/Sub Admin
Storage Admin

@danawillow danawillow self-assigned this Jan 5, 2018
Copy link
Contributor

@danawillow danawillow left a comment

Choose a reason for hiding this comment

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

Thanks for the PR @sarunask! There's a lot of code here, so apologies in advance that there'll probably be a lot of back and forth before this gets in. Here's the first batch of comments that I have. I haven't gotten around to looking at everything yet, but I think this is a really good starting point.

"description": {
Type: schema.TypeString,
Optional: true,
Elem: &schema.Schema{Type: schema.TypeString},
Copy link
Contributor

Choose a reason for hiding this comment

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

This line isn't necessary, you don't need an Elem for strings (this applies for the rest of the fields as well)

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Removed

Type: schema.TypeMap,
Optional: true,
Elem: &schema.Schema{Type: schema.TypeString},
Set: schema.HashString,
Copy link
Contributor

Choose a reason for hiding this comment

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

No need for Set on a TypeMap

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Fixed


"storage_bucket": {
Type: schema.TypeString,
Required: true,
Copy link
Contributor

Choose a reason for hiding this comment

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

We typically sort our fields in the schema in the order Required -> Optional -> Computed- it makes it a little easier to navigate.

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 that :)

return err
}

if FUNCTION_ALLOWED_REGION[region] != true {
Copy link
Contributor

Choose a reason for hiding this comment

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

Instead of this block, you can add a ValidateFunc into the schema for region. That'll catch these sorts of errors on plan-time instead of on apply-time. There's one that you can call called stringInSlice or something like that, look around for examples

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


if v, ok := d.GetOk("memory"); ok {
memory := v.(int)
if FUNCTION_ALLOWED_MEMORY[memory] != true {
Copy link
Contributor

Choose a reason for hiding this comment

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

same thing here with a ValidateFunc

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

func resourceCloudFunctionsCreate(d *schema.ResourceData, meta interface{}) error {
config := meta.(*Config)

service := config.clientCloudFunctions
Copy link
Contributor

Choose a reason for hiding this comment

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

this is fine, but since you're only using the service variable once you may as well inline it into the request below (that'll match the rest of the resources in this repo as well)

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Fixed

return err
}

name := d.Get("name").(string)
Copy link
Contributor

Choose a reason for hiding this comment

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

If you use d.Id() here instead of d.Get("name").(string), then you can use an ImportStatePassthrough importer instead of a custom one

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 :)

if err != nil {
return fmt.Errorf("Error while updating cloudfunction configuration: %s", err)
}
for i := range partialArr {
Copy link
Contributor

Choose a reason for hiding this comment

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

This would only be necessary if you were sending multiple requests. Since you're only sending one, no need for SetPartial

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Fixed

}
}

func dataSourceGoogleCloudFunctionsFunctionRead(d *schema.ResourceData, meta interface{}) error {
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 significantly different from the resource read function? If not, you should just call that one from this to save on some duplicated code. You might also be able to do a similar thing for the schema- check out data_source_google_container_cluster.go for an example of a data source that utilitzes the code from the resource really well.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Fixed, now code looks cleaner - thank you for heads up :)

@@ -0,0 +1,97 @@
package google
Copy link
Contributor

Choose a reason for hiding this comment

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

I'd highly recommend taking a look at how we do this sort of thing for other resources- resource_kms_key_ring.go would be a good place to start.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Removed this file

Copy link
Contributor Author

@sarunask sarunask left a comment

Choose a reason for hiding this comment

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

Thank you Dana for your review. After implementing changes code is really better :) But now it's quite different so you would have to review again...

@@ -0,0 +1,97 @@
package google
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Removed this file

}
}

func dataSourceGoogleCloudFunctionsFunctionRead(d *schema.ResourceData, meta interface{}) error {
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Fixed, now code looks cleaner - thank you for heads up :)

"description": {
Type: schema.TypeString,
Optional: true,
Elem: &schema.Schema{Type: schema.TypeString},
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Removed

Type: schema.TypeMap,
Optional: true,
Elem: &schema.Schema{Type: schema.TypeString},
Set: schema.HashString,
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Fixed

ConflictsWith: []string{"trigger_http", "trigger_topic"},
},

"trigger_http": {
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 was looking into 'gcloud beta functions deploy' CLI and there is trigger-http. So left this field as in gcloud CLI.


if v, ok := d.GetOk("memory"); ok {
memory := v.(int)
if FUNCTION_ALLOWED_MEMORY[memory] != true {
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 fmt.Errorf("Allowed values for memory are: 128MB, 256MB, 512MB, 1024MB, and 2048MB. "+
"Got %d", memory)
}
function.AvailableMemoryMb = int64(memory)
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Renamed

}

if !triggHttpOk && !triggTopicOk && !triggBucketOk {
return fmt.Errorf("One of aguments [trigger_topic, trigger_bucket, trigger_http] is required: " +
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Fixed

return err
}

name := d.Get("name").(string)
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 :)

if err != nil {
return fmt.Errorf("Error while updating cloudfunction configuration: %s", err)
}
for i := range partialArr {
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Fixed

Copy link
Contributor

@danawillow danawillow left a comment

Choose a reason for hiding this comment

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

Thanks for those fixes! Here's your next batch of comments :)


func (w *CloudFunctionsOperationWaiter) Conf() *resource.StateChangeConf {
return &resource.StateChangeConf{
Pending: []string{"PENDING", "RUNNING"},
Copy link
Contributor

Choose a reason for hiding this comment

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

Is "RUNNING" actually a possible state? It looks like you manually set either "PENDING" or "DONE"

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Fixed

var op *cloudfunctions.Operation
var err error

op, err = w.Service.Operations.Get(w.Op.Name).Do()
Copy link
Contributor

Choose a reason for hiding this comment

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

you can use := here so you don't have to define the two vars above

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

topicName := fmt.Sprintf("tf-test-sub-%s", acctest.RandString(10))
zipFilePath, err := createZIParchiveForIndexJs(testHTTPTriggerPath)
if err != nil {
t.Errorf(err.Error())
Copy link
Contributor

Choose a reason for hiding this comment

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

I think these two lines can just be t.Fatal(err)

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Fixed

}

resource "google_cloudfunctions_function" "function_http" {
name = "%s-http"
Copy link
Contributor

Choose a reason for hiding this comment

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

nit: align =s

Copy link
Contributor Author

Choose a reason for hiding this comment

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

If I understood correctly, I made all ='s in resource have same alignment :)

}

resource "google_pubsub_topic" "sub" {
name = "%s"
Copy link
Contributor

Choose a reason for hiding this comment

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

nit: consistency with tabs vs spaces for indentation (either is fine, just be consistent)

Copy link
Contributor Author

Choose a reason for hiding this comment

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

As per comment above

switch n {
case FUNCTION_TRIGGER_HTTP:
if function.HttpsTrigger == nil {
return fmt.Errorf("Expected trigger_http to be set")
Copy link
Contributor

Choose a reason for hiding this comment

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

This error gets thrown if field on the returned object isn't set, so instead of trigger_http it would be HttpsTrigger

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Fixed

return fmt.Errorf("Expected trigger_bucket to be set")
}
if strings.Index(function.EventTrigger.EventType, "cloud.storage") == -1 {
return fmt.Errorf("Expected trigger_bucket to be set")
Copy link
Contributor

Choose a reason for hiding this comment

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

and this one would be something like "Expected cloud.storage EventType" (or even better, "Expected cloud.storage EventType, found %s")

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Fixed

}
}

func createZIParchiveForIndexJs(sourcePath string) (string, error) {
Copy link
Contributor

Choose a reason for hiding this comment

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

nit: archive -> Archive

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Fixed

resource "google_cloudfunctions_function" "function" {
name = "%s"
description = "test function"
available_memory_mb = 128
Copy link
Contributor

Choose a reason for hiding this comment

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

nit: align =s

}

resource "google_storage_bucket_object" "archive" {
name = "index.zip"
Copy link
Contributor

Choose a reason for hiding this comment

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

nit: tabs vs spaces consistency

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Fixed

addRequiredFieldsToSchema(dsSchema, "name")

// Set 'Optional' schema elements
addOptionalFieldsToSchema(dsSchema, "source_archive_bucket", "source_archive_object")
Copy link
Contributor

Choose a reason for hiding this comment

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

Since nothing in the read function actually looks at the value of these in state, I don't think they need to be set to Optional. However, the read function does look at the values of project and region, so you should probably add those here.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Fixed

bucketName := fmt.Sprintf("tf-test-bucket-%d", acctest.RandInt())
zipFilePath, err := createZIPArchiveForIndexJs(testHTTPTriggerPath)
if err != nil {
t.Errorf(err.Error())
Copy link
Contributor

Choose a reason for hiding this comment

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

you can do fatal here too

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

}
case FUNCTION_TRIGGER_BUCKET:
if function.EventTrigger == nil {
return fmt.Errorf("Expected trigger_bucket to be set")
Copy link
Contributor

Choose a reason for hiding this comment

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

trigger_bucket -> EventTrigger

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Fixed

}
case FUNCTION_TRIGGER_TOPIC:
if function.EventTrigger == nil {
return fmt.Errorf("Expected trigger_bucket to be set")
Copy link
Contributor

Choose a reason for hiding this comment

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

trigger_bucket -> EventTrigger

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Fixed

source_archive_bucket = "${google_storage_bucket.bucket.name}"
source_archive_object = "${google_storage_bucket_object.archive.name}"
trigger_http = true
timeout = 61
Copy link
Contributor

Choose a reason for hiding this comment

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

nit: missed one (alignment)

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Fixed, sorry for this - my editor didn't show those tabs...

source_archive_bucket = "${google_storage_bucket.bucket.name}"
source_archive_object = "${google_storage_bucket_object.archive.name}"
trigger_http = true
timeout = 91
Copy link
Contributor

Choose a reason for hiding this comment

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

and here

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Fixed, sorry for this - my editor didn't show those tabs...

source_archive_bucket = "${google_storage_bucket.bucket.name}"
source_archive_object = "${google_storage_bucket_object.archive.name}"
trigger_topic = "${google_pubsub_topic.sub.name}"
timeout = 61
Copy link
Contributor

Choose a reason for hiding this comment

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

and here

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Fixed, sorry for this - my editor didn't show those tabs...

source_archive_bucket = "${google_storage_bucket.bucket.name}"
source_archive_object = "${google_storage_bucket_object.archive.name}"
trigger_bucket = "${google_storage_bucket.bucket.name}"
timeout = 61
Copy link
Contributor

Choose a reason for hiding this comment

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

and here

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Fixed, sorry for this - my editor didn't show those tabs...

return fmt.Errorf("Function %s doesn't exists.", d.Get("name").(string))
}

if d.HasChange("labels") {
Copy link
Contributor

Choose a reason for hiding this comment

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

Hmm, my guess is that's because the SetPartial wasn't in the right spot before. Can you try again?

@sarunask
Copy link
Contributor Author

@danawillow As we discussed I have also changed Update part, so now labels are updated together with other attributes.

…es function with some values for "timeout" or "available_memory_mb", and then disables those attributes. Terraform plan then gives:

No changes. Infrastructure is up-to-date.
This is an error. By adding const we would avoid this error.
@danawillow
Copy link
Contributor

re: your last commit- the reason it was giving that message was because when you removed the default, you added Computed: true. If you remove that then it should work just fine.

source_archive_bucket = "${google_storage_bucket.bucket.name}"
source_archive_object = "${google_storage_bucket_object.archive.name}"
trigger_http = true
timeout = 61
Copy link
Contributor

Choose a reason for hiding this comment

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

found some more tabs for you here (and on the same line in the other tests in this file)

)

// Min is 1 second, max is 9 minutes 540 sec
const FUNCTION_TIMEOUT_MAX = 540
Copy link
Contributor

Choose a reason for hiding this comment

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

@sarunask
Copy link
Contributor Author

I fixed your comments :)

Regarding default values. If I omit defaults for timeout and available_memory_mb and create function with terraform apply, then issue 'terraform plan', I would get such output:
~ module.cloudfunc.google_cloudfunctions_function.test
available_memory_mb: "256" => "0"
timeout: "60" => "0"
So plan is always not empty.
I suppose this is because in resourceCloudFunctionsRead I read and setup timeout and available_memory_mb values from function. So is it best practice to leave them empty if not available in initial plan?

@danawillow
Copy link
Contributor

Ah ok that's fine then. This looks good, thanks again!

@danawillow danawillow merged commit f07c332 into hashicorp:master Jan 10, 2018
@sarunask
Copy link
Contributor Author

Thank you very much for your review @danawillow :)

@sarunask sarunask deleted the cloud-functions branch January 11, 2018 05:59
@sarunask
Copy link
Contributor Author

Now as code for CloudFunctions is merged into master, what are the next steps? Shall I add some documentation for it? Or is it done by someone else?

@danawillow
Copy link
Contributor

Yes please!

@Farhie
Copy link

Farhie commented Jan 23, 2018

This looks super promising. Thanks for the hard work @sarunask and @danawillow :)

modular-magician added a commit to modular-magician/terraform-provider-google that referenced this pull request Sep 27, 2019
@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.

3 participants