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
Show file tree
Hide file tree
Changes from 28 commits
Commits
Show all changes
37 commits
Select commit Hold shift + click to select a range
eedd890
Initial commit
sarunask Nov 21, 2017
5c394fa
Adding google_cloudfunction_function resource
sarunask Dec 14, 2017
93f5856
Merge remote-tracking branch 'upstream/master' into cloud-functions
sarunask Dec 14, 2017
8288741
Some FMT updates
sarunask Dec 14, 2017
c74e219
Working Cloud Function Create/Delete/Get
sarunask Dec 18, 2017
70c6fe9
Merge remote-tracking branch 'upstream/master' into cloud-functions
sarunask Dec 18, 2017
c169968
Fixed tests import
sarunask Dec 18, 2017
b0939d0
Terraform now is able to apply and destroy function
sarunask Dec 21, 2017
6fd1f58
Merge remote-tracking branch 'upstream/master' into cloud-functions
sarunask Dec 21, 2017
28ca0a8
Merge remote-tracking branch 'upstream/master' into cloud-functions
sarunask Dec 23, 2017
127b69c
Fully working Basic test
sarunask Dec 23, 2017
463af45
Added:
sarunask Dec 23, 2017
0c52870
Found better solution for conflicting values
sarunask Dec 24, 2017
e65ab5f
Adding description
sarunask Dec 24, 2017
0180d8d
Adding full basic test
sarunask Dec 24, 2017
3696812
dded Update functionality
sarunask Dec 24, 2017
859ed3b
Made few more optional params
sarunask Dec 24, 2017
94c2e50
Added test for Labels
sarunask Dec 24, 2017
361691d
Added update tests
sarunask Dec 24, 2017
898cfad
Added storage_* members and made function source deploy from storage …
sarunask Dec 25, 2017
5387b18
Adding comments
sarunask Dec 25, 2017
992b7a8
Adding tests for PubSub
sarunask Dec 26, 2017
ea2abdb
Adding tests for Bucket
sarunask Dec 26, 2017
45cace1
Adding Data provider
sarunask Dec 26, 2017
6623728
Fixing bug which allowed to miss error
sarunask Dec 26, 2017
2e31b1d
Amending Operation retrieval
sarunask Dec 26, 2017
02d5847
Merge remote-tracking branch 'upstream/master' into cloud-functions
sarunask Jan 3, 2018
ee5da4a
Fixing vet errors and vendoring cloudfunctions/v1
sarunask Jan 3, 2018
ea9f204
Merge remote-tracking branch 'upstream/master' into cloud-functions
sarunask Jan 6, 2018
172564a
Fixing according to comments
sarunask Jan 6, 2018
3d13310
Merge remote-tracking branch 'upstream/master' into cloud-functions
sarunask Jan 9, 2018
d9a36e8
Fixing according to comments round #2
sarunask Jan 9, 2018
f1e6cec
Fixing tabs to space
sarunask Jan 9, 2018
f466669
Fixing tabs to space and some comments #3
sarunask Jan 10, 2018
ea526b3
Re-done update to include labels in one update with others
sarunask Jan 10, 2018
79db2cf
Adding back default values. In case of such scenario, when user creat…
sarunask Jan 10, 2018
1478794
Fixed MixedCase and more tabs
sarunask Jan 10, 2018
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
97 changes: 97 additions & 0 deletions google/cloudfunctions_helpers.go
Original file line number Diff line number Diff line change
@@ -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


import (
"fmt"
"regexp"
"strconv"
"strings"
)

const (
CLOUDFUNCTIONS_FULL_NAME = 0
CLOUDFUNCTIONS_REGION_ONLY = 1
)

//Function would return formatted string to be used in API calls for name or location
// Arguments:
// funcType: {CLOUDFUNCTIONS_REGION_ONLY, CLOUDFUNCTIONS_FULL_NAME}
// If specifying CLOUDFUNCTIONS_FULL_NAME string would be in format 'projects/YOUR_PROJECT/locations/REGION/functions/FUNCTION_NAME'
// If specifying CLOUDFUNCTIONS_REGION_ONLY string would be in format 'projects/YOUR_PROJECT/locations/REGION'
// projectName: Name of project in Google Cloud
// region: In which region function is/should be located. NOTE: Not all regions are supported in 2017
// funcName: name of function. In case CLOUDFUNCTIONS_REGION_ONLY might empty
// Returns:
// path to function
func createCloudFunctionsPathString(funcType int, projectName string, region string, funcName string) (path string) {
path = fmt.Sprintf("projects/%s/locations/%s", projectName, region)
if funcType == CLOUDFUNCTIONS_FULL_NAME {
path = fmt.Sprintf("%s/functions/%s", path, funcName)
}
return
}

//Function would extract short function name from long used in Google cloud
//Arguments:
// fullPath: function name in Google cloud
//Return:
// functionName: string short function name
// error: Error if fullPath is not correct Google cloud function name
func getCloudFunctionName(fullPath string) (string, error) {
allParts, err := splitCloudFunctionFullPath(fullPath)
if err != nil {
return "", err
}
return allParts[3], nil
}

//Function would extract region from long used in Google cloud
//Arguments:
// fullPath: function name in Google cloud
//Return:
// region: string zone in which function is deployed
// error: Error if fullPath is not correct Google cloud function name
func getCloudFunctionRegion(fullPath string) (string, error) {
allParts, err := splitCloudFunctionFullPath(fullPath)
if err != nil {
return "", err
}
return allParts[2], nil
}

//Function would extract project from long used in Google cloud
//Arguments:
// fullPath: function name in Google cloud
//Return:
// project: string project in which function is deployed
// error: Error if fullPath is not correct Google cloud function name
func getCloudFunctionProject(fullPath string) (string, error) {
allParts, err := splitCloudFunctionFullPath(fullPath)
if err != nil {
return "", err
}
return allParts[1], nil
}

//Function would split full CloudFunction Path into array of fullName,project,region,funcName
//Arguments:
// fullPath: function name in Google cloud
//Return:
// parts: array of strings which would include fullName,project,region,funcName
// error: Error if fullPath is not correct Google cloud function name
func splitCloudFunctionFullPath(fullPath string) ([]string, error) {
namePattern := regexp.MustCompile("^projects/([^/]+)/locations/([^/]+)/functions/([^/]+)$")
if !namePattern.MatchString(fullPath) {
return nil, fmt.Errorf("%s is not valid CloudFunction full name", fullPath)
}
return namePattern.FindStringSubmatch(fullPath), nil
}

//Function would read timeout value from GCloud
//Arguments:
// timeout: Timeout in string
//Return:
// timeout int
func readTimeout(s string) (int, error) {
sRemoved := strings.Replace(s, "s", "", -1)
return strconv.Atoi(sRemoved)
}
73 changes: 73 additions & 0 deletions google/cloudfunctions_operation.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
package google

import (
"fmt"
"log"
"time"

"github.com/hashicorp/terraform/helper/resource"
"google.golang.org/api/cloudfunctions/v1"
)

type CloudFunctionsOperationWaiter struct {
Service *cloudfunctions.Service
Op *cloudfunctions.Operation
}

func (w *CloudFunctionsOperationWaiter) RefreshFunc() resource.StateRefreshFunc {
return func() (interface{}, string, error) {
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


if err != nil {
return nil, "", err
}

status := "PENDING"
if op.Done == true {
status = "DONE"
}

log.Printf("[DEBUG] Got %q when asking for operation %q", status, w.Op.Name)
return op, status, nil
}
}

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

Target: []string{"DONE"},
Refresh: w.RefreshFunc(),
}
}

func cloudFunctionsOperationWait(client *cloudfunctions.Service,
op *cloudfunctions.Operation, activity string) error {
return cloudFunctionsOperationWaitTime(client, op, activity, 4)
}

func cloudFunctionsOperationWaitTime(client *cloudfunctions.Service, op *cloudfunctions.Operation,
activity string, timeoutMin int) error {
w := &CloudFunctionsOperationWaiter{
Service: client,
Op: op,
}

state := w.Conf()
state.Delay = 10 * time.Second
state.Timeout = time.Duration(timeoutMin) * time.Minute
state.MinTimeout = 2 * time.Second
opRaw, err := state.WaitForState()
if err != nil {
return fmt.Errorf("Error waiting for %s: %s", activity, err)
}

resultOp := opRaw.(*cloudfunctions.Operation)
if resultOp.Error != nil {
return fmt.Errorf(resultOp.Error.Message)
}

return nil
}
9 changes: 9 additions & 0 deletions google/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import (
"golang.org/x/oauth2/jwt"
"google.golang.org/api/bigquery/v2"
"google.golang.org/api/cloudbilling/v1"
"google.golang.org/api/cloudfunctions/v1"
"google.golang.org/api/cloudkms/v1"
"google.golang.org/api/cloudresourcemanager/v1"
resourceManagerV2Beta1 "google.golang.org/api/cloudresourcemanager/v2beta1"
Expand Down Expand Up @@ -64,6 +65,7 @@ type Config struct {
clientIAM *iam.Service
clientServiceMan *servicemanagement.APIService
clientBigQuery *bigquery.Service
clientCloudFunctions *cloudfunctions.Service

bigtableClientFactory *BigtableClientFactory
}
Expand Down Expand Up @@ -244,6 +246,13 @@ func (c *Config) loadAndValidate() error {
}
c.clientBigQuery.UserAgent = userAgent

log.Printf("[INFO] Instantiating Google Cloud CloudFunctions Client...")
c.clientCloudFunctions, err = cloudfunctions.New(client)
if err != nil {
return err
}
c.clientCloudFunctions.UserAgent = userAgent

c.bigtableClientFactory = &BigtableClientFactory{
UserAgent: userAgent,
TokenSource: tokenSource,
Expand Down
153 changes: 153 additions & 0 deletions google/data_source_google_cloudfunctions_function.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,153 @@
package google

import (
"strings"

"github.com/hashicorp/terraform/helper/schema"
)

func dataSourceGoogleCloudFunctionsFunction() *schema.Resource {
return &schema.Resource{
Read: dataSourceGoogleCloudFunctionsFunctionRead,

Schema: map[string]*schema.Schema{
"name": {
Type: schema.TypeString,
Required: true,
},

"description": {
Type: schema.TypeString,
Optional: true,
Computed: true,
},

"entry_point": {
Type: schema.TypeString,
Optional: true,
Computed: true,
},

"memory": {
Type: schema.TypeInt,
Optional: true,
Computed: true,
},

"region": {
Type: schema.TypeString,
Optional: true,
Computed: true,
},

"timeout": {
Type: schema.TypeInt,
Optional: true,
Computed: true,
},

"storage_bucket": {
Type: schema.TypeString,
Optional: true,
Computed: true,
},

"storage_object": {
Type: schema.TypeString,
Optional: true,
Computed: true,
},

"trigger_bucket": {
Type: schema.TypeString,
Optional: true,
Computed: true,
},

"trigger_http": {
Type: schema.TypeBool,
Optional: true,
Computed: true,
},

"trigger_topic": {
Type: schema.TypeString,
Optional: true,
Computed: true,
},

"project": {
Type: schema.TypeString,
Optional: true,
Computed: true,
},
},
}
}

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

config := meta.(*Config)

service := config.clientCloudFunctions

project, err := getProject(d, config)
if err != nil {
return err
}

region, err := getRegion(d, config)
if err != nil {
return err
}

name := d.Get("name").(string)

getOpt, err := service.Projects.Locations.Functions.Get(
createCloudFunctionsPathString(CLOUDFUNCTIONS_FULL_NAME, project, region, name)).Do()
if err != nil {
return err
}

funcRegion, err := getCloudFunctionRegion(getOpt.Name)
if err != nil {
return err
}
funcProject, err := getCloudFunctionProject(getOpt.Name)
if err != nil {
return err
}

d.Set("description", getOpt.Description)
d.Set("entry_point", getOpt.EntryPoint)
d.Set("memory", getOpt.AvailableMemoryMb)
d.Set("region", funcRegion)
timeout, err := readTimeout(getOpt.Timeout)
if err != nil {
return err
}
d.Set("timeout", timeout)
if getOpt.SourceArchiveUrl != "" {
sourceArr := strings.Split(getOpt.SourceArchiveUrl, "/")
d.Set("storage_bucket", sourceArr[2])
d.Set("storage_object", sourceArr[3])
}

if getOpt.HttpsTrigger != nil {
d.Set("trigger_http", true)
}
if getOpt.EventTrigger != nil {
switch getOpt.EventTrigger.EventType {
//From https://github.com/google/google-api-go-client/blob/master/cloudfunctions/v1/cloudfunctions-gen.go#L335
case "providers/cloud.pubsub/eventTypes/topic.publish":
d.Set("trigger_topic", extractLastResourceFromUri(getOpt.EventTrigger.Resource))
case "providers/cloud.storage/eventTypes/object.change":
d.Set("trigger_bucket", extractLastResourceFromUri(getOpt.EventTrigger.Resource))
}
}
d.Set("project", funcProject)

//Name of function should be unique
d.SetId(d.Get("name").(string))

return nil
}
Loading