mirror of
https://github.com/kevin-DL/services.git
synced 2026-01-12 03:05:14 +00:00
functions branch and region support (#330)
* branch and region support * . * . * . * . * fix functions * . * break loop * update example * update examples
This commit is contained in:
@@ -6,10 +6,12 @@ import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"math/rand"
|
||||
"net/http"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/micro/micro/v3/service/config"
|
||||
"github.com/micro/micro/v3/service/errors"
|
||||
@@ -18,7 +20,7 @@ import (
|
||||
"github.com/micro/micro/v3/service/store"
|
||||
function "github.com/micro/services/function/proto"
|
||||
"github.com/micro/services/pkg/tenant"
|
||||
"gopkg.in/yaml.v2"
|
||||
"github.com/teris-io/shortid"
|
||||
)
|
||||
|
||||
type GoogleFunction struct {
|
||||
@@ -29,6 +31,10 @@ type GoogleFunction struct {
|
||||
limit int
|
||||
// function identity
|
||||
identity string
|
||||
// custom domain
|
||||
domain string
|
||||
|
||||
*Function
|
||||
}
|
||||
|
||||
var (
|
||||
@@ -51,8 +57,27 @@ var (
|
||||
"ruby26",
|
||||
"php74",
|
||||
}
|
||||
|
||||
// hardcoded list of supported regions
|
||||
GoogleRegions = []string{"europe-west1", "us-central1", "us-east1", "us-west1", "asia-east1"}
|
||||
)
|
||||
|
||||
var (
|
||||
alphanum = "abcdefghijklmnopqrstuvwxyz0123456789"
|
||||
)
|
||||
|
||||
func random(i int) string {
|
||||
bytes := make([]byte, i)
|
||||
for {
|
||||
rand.Read(bytes)
|
||||
for i, b := range bytes {
|
||||
bytes[i] = alphanum[b%byte(len(alphanum))]
|
||||
}
|
||||
return string(bytes)
|
||||
}
|
||||
return fmt.Sprintf("%d", time.Now().Unix())
|
||||
}
|
||||
|
||||
func NewFunction() *GoogleFunction {
|
||||
v, err := config.Get("function.service_account_json")
|
||||
if err != nil {
|
||||
@@ -98,6 +123,11 @@ func NewFunction() *GoogleFunction {
|
||||
log.Fatalf("function.service_identity: %v", err)
|
||||
}
|
||||
identity := v.String("")
|
||||
v, err = config.Get("function.domain")
|
||||
if err != nil {
|
||||
log.Fatalf("function.domain: %v", err)
|
||||
}
|
||||
domain := v.String("")
|
||||
|
||||
m := map[string]interface{}{}
|
||||
err = json.Unmarshal(keyfile, &m)
|
||||
@@ -124,12 +154,16 @@ func NewFunction() *GoogleFunction {
|
||||
if err != nil {
|
||||
log.Fatalf(string(outp))
|
||||
}
|
||||
|
||||
log.Info(string(outp))
|
||||
|
||||
return &GoogleFunction{
|
||||
project: project,
|
||||
address: address,
|
||||
limit: limit,
|
||||
identity: identity,
|
||||
domain: domain,
|
||||
Function: new(Function),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -159,17 +193,36 @@ func (e *GoogleFunction) Deploy(ctx context.Context, req *function.DeployRequest
|
||||
return errors.BadRequest("function.deploy", "invalid runtime")
|
||||
}
|
||||
|
||||
gitter := git.NewGitter(map[string]string{})
|
||||
var supportedRegion bool
|
||||
|
||||
var err error
|
||||
if len(req.Region) == 0 {
|
||||
// set to default region
|
||||
req.Region = GoogleRegions[0]
|
||||
supportedRegion = true
|
||||
}
|
||||
|
||||
for _, branch := range []string{"master", "main"} {
|
||||
err = gitter.Checkout(req.Repo, branch)
|
||||
if err == nil {
|
||||
// check if its in the supported regions
|
||||
for _, reg := range GoogleRegions {
|
||||
if req.Region == reg {
|
||||
supportedRegion = true
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
// unsupported region requested
|
||||
if !supportedRegion {
|
||||
return errors.BadRequest("function.deploy", "Unsupported region")
|
||||
}
|
||||
|
||||
if len(req.Branch) == 0 {
|
||||
req.Branch = "master"
|
||||
}
|
||||
|
||||
gitter := git.NewGitter(map[string]string{})
|
||||
|
||||
var err error
|
||||
|
||||
err = gitter.Checkout(req.Repo, req.Branch)
|
||||
if err != nil {
|
||||
return errors.InternalServerError("function.deploy", err.Error())
|
||||
}
|
||||
@@ -179,18 +232,12 @@ func (e *GoogleFunction) Deploy(ctx context.Context, req *function.DeployRequest
|
||||
tenantId = "micro"
|
||||
}
|
||||
|
||||
multitenantPrefix := strings.Replace(tenantId, "/", "-", -1)
|
||||
if req.Entrypoint == "" {
|
||||
req.Entrypoint = req.Name
|
||||
}
|
||||
|
||||
project := req.Project
|
||||
if project == "" {
|
||||
project = "default"
|
||||
}
|
||||
|
||||
key := fmt.Sprintf("function/%s/%s/%s", tenantId, project, req.Name)
|
||||
|
||||
// read the function by owner
|
||||
key := fmt.Sprintf(OwnerKey+"%s/%s", tenantId, req.Name)
|
||||
records, err := store.Read(key)
|
||||
if err != nil && err != store.ErrNotFound {
|
||||
return err
|
||||
@@ -203,7 +250,7 @@ func (e *GoogleFunction) Deploy(ctx context.Context, req *function.DeployRequest
|
||||
// check for function limit
|
||||
if e.limit > 0 {
|
||||
// read all the records for the user
|
||||
records, err := store.Read("function/"+tenantId+"/", store.ReadPrefix())
|
||||
records, err := store.Read(OwnerKey+tenantId+"/", store.ReadPrefix())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -213,6 +260,26 @@ func (e *GoogleFunction) Deploy(ctx context.Context, req *function.DeployRequest
|
||||
}
|
||||
}
|
||||
|
||||
// set the id
|
||||
id := req.Name
|
||||
|
||||
// check the owner isn't already running it
|
||||
recs, err := store.Read(FunctionKey+req.Name, store.ReadLimit(1))
|
||||
|
||||
// if there's an existing function then generate a unique id
|
||||
if err == nil && len(recs) > 0 {
|
||||
// generate an id for the service
|
||||
sid, err := shortid.Generate()
|
||||
if err != nil || len(sid) == 0 {
|
||||
sid = random(8)
|
||||
}
|
||||
|
||||
sid = strings.ToLower(sid)
|
||||
sid = strings.Replace(sid, "-", "", -1)
|
||||
sid = strings.Replace(sid, "_", "", -1)
|
||||
id = req.Name + "-" + sid
|
||||
}
|
||||
|
||||
// process the env vars to the required format
|
||||
var envVars []string
|
||||
|
||||
@@ -220,40 +287,122 @@ func (e *GoogleFunction) Deploy(ctx context.Context, req *function.DeployRequest
|
||||
envVars = append(envVars, k+"="+v)
|
||||
}
|
||||
|
||||
go func() {
|
||||
fn := &function.Func{
|
||||
Id: id,
|
||||
Name: req.Name,
|
||||
Repo: req.Repo,
|
||||
Subfolder: req.Subfolder,
|
||||
Entrypoint: req.Entrypoint,
|
||||
Runtime: req.Runtime,
|
||||
EnvVars: req.EnvVars,
|
||||
Region: req.Region,
|
||||
Branch: req.Branch,
|
||||
Created: time.Now().Format(time.RFC3339Nano),
|
||||
Status: "Deploying",
|
||||
Url: fmt.Sprintf("https://%s-%s.cloudfunctions.net/%s", req.Region, e.project, id),
|
||||
}
|
||||
|
||||
// write the owner key
|
||||
rec := store.NewRecord(key, fn)
|
||||
if err := store.Write(rec); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// write the global key
|
||||
rec = store.NewRecord(FunctionKey+fn.Id, fn)
|
||||
if err := store.Write(rec); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// set the custom domain
|
||||
if len(e.domain) > 0 {
|
||||
fn.Url = fmt.Sprintf("https://%s.%s", fn.Id, e.domain)
|
||||
}
|
||||
|
||||
// set the response
|
||||
rsp.Function = fn
|
||||
|
||||
go func(fn *function.Func) {
|
||||
// https://jsoverson.medium.com/how-to-deploy-node-js-functions-to-google-cloud-8bba05e9c10a
|
||||
cmd := exec.Command("gcloud", "functions", "deploy",
|
||||
multitenantPrefix+"-"+req.Name, "--region", "europe-west1", "--service-account", e.identity,
|
||||
"--allow-unauthenticated", "--entry-point", req.Entrypoint,
|
||||
"--trigger-http", "--project", e.project, "--runtime", req.Runtime)
|
||||
cmd := exec.Command("gcloud", "functions", "deploy", fn.Id, "--quiet",
|
||||
"--region", fn.Region, "--service-account", e.identity,
|
||||
"--allow-unauthenticated", "--entry-point", fn.Entrypoint,
|
||||
"--trigger-http", "--project", e.project, "--runtime", fn.Runtime)
|
||||
|
||||
// if env vars exist then set them
|
||||
if len(envVars) > 0 {
|
||||
cmd.Args = append(cmd.Args, "--set-env-vars", strings.Join(envVars, ","))
|
||||
}
|
||||
|
||||
cmd.Dir = filepath.Join(gitter.RepoDir(), req.Subfolder)
|
||||
cmd.Dir = filepath.Join(gitter.RepoDir(), fn.Subfolder)
|
||||
outp, err := cmd.CombinedOutput()
|
||||
if err != nil {
|
||||
log.Error(fmt.Errorf(string(outp)))
|
||||
fn.Status = "DeploymentError"
|
||||
store.Write(store.NewRecord(key, fn))
|
||||
return
|
||||
}
|
||||
}()
|
||||
|
||||
id := fmt.Sprintf("%v-%v-%v", tenantId, project, req.Name)
|
||||
rec := store.NewRecord(key, map[string]interface{}{
|
||||
"id": id,
|
||||
"project": project,
|
||||
"name": req.Name,
|
||||
"tenantId": tenantId,
|
||||
"repo": req.Repo,
|
||||
"subfolder": req.Subfolder,
|
||||
"entrypoint": req.Entrypoint,
|
||||
"runtime": req.Runtime,
|
||||
"env_vars": envVars,
|
||||
})
|
||||
var status string
|
||||
|
||||
// write the record
|
||||
return store.Write(rec)
|
||||
LOOP:
|
||||
// wait for the deployment and status update
|
||||
for i := 0; i < 120; i++ {
|
||||
cmd = exec.Command("gcloud", "functions", "describe", "--format", "json",
|
||||
"--region", fn.Region, "--project", e.project, fn.Id)
|
||||
|
||||
outp, err = cmd.CombinedOutput()
|
||||
if err != nil {
|
||||
log.Error(fmt.Errorf(string(outp)))
|
||||
return
|
||||
}
|
||||
|
||||
log.Info(string(outp))
|
||||
|
||||
var m map[string]interface{}
|
||||
if err := json.Unmarshal(outp, &m); err != nil {
|
||||
log.Error(err)
|
||||
return
|
||||
}
|
||||
|
||||
// write back the url
|
||||
trigger := m["httpsTrigger"].(map[string]interface{})
|
||||
|
||||
if v := trigger["url"].(string); len(v) > 0 {
|
||||
fn.Url = v
|
||||
} else {
|
||||
fn.Url = fmt.Sprintf("https://%s-%s.cloudfunctions.net/%s", fn.Region, e.project, fn.Id)
|
||||
}
|
||||
|
||||
v := m["status"].(string)
|
||||
|
||||
switch v {
|
||||
case "ACTIVE":
|
||||
status = "Deployed"
|
||||
break LOOP
|
||||
case "DEPLOY_IN_PROGRESS":
|
||||
status = "Deploying"
|
||||
case "OFFLINE":
|
||||
status = "DeploymentError"
|
||||
break LOOP
|
||||
}
|
||||
|
||||
// we need to try get the url again
|
||||
time.Sleep(time.Second)
|
||||
}
|
||||
|
||||
fn.Updated = time.Now().Format(time.RFC3339Nano)
|
||||
fn.Status = status
|
||||
|
||||
// write the owners key
|
||||
store.Write(store.NewRecord(key, fn))
|
||||
|
||||
// write the global key
|
||||
rec = store.NewRecord(FunctionKey+fn.Id, fn)
|
||||
store.Write(rec)
|
||||
}(fn)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (e *GoogleFunction) Update(ctx context.Context, req *function.UpdateRequest, rsp *function.UpdateResponse) error {
|
||||
@@ -263,29 +412,12 @@ func (e *GoogleFunction) Update(ctx context.Context, req *function.UpdateRequest
|
||||
return errors.BadRequest("function.update", "Missing name")
|
||||
}
|
||||
|
||||
if len(req.Repo) == 0 {
|
||||
return errors.BadRequest("function.update", "Missing repo")
|
||||
}
|
||||
if req.Runtime == "" {
|
||||
return fmt.Errorf("missing runtime field, please specify nodejs14, go116 etc")
|
||||
}
|
||||
|
||||
tenantId, ok := tenant.FromContext(ctx)
|
||||
if !ok {
|
||||
tenantId = "micro"
|
||||
}
|
||||
|
||||
multitenantPrefix := strings.Replace(tenantId, "/", "-", -1)
|
||||
if req.Entrypoint == "" {
|
||||
req.Entrypoint = req.Name
|
||||
}
|
||||
|
||||
project := req.Project
|
||||
if project == "" {
|
||||
project = "default"
|
||||
}
|
||||
|
||||
key := fmt.Sprintf("function/%s/%s/%s", tenantId, project, req.Name)
|
||||
key := fmt.Sprintf(OwnerKey+"%s/%s", tenantId, req.Name)
|
||||
|
||||
records, err := store.Read(key)
|
||||
if err != nil {
|
||||
@@ -296,60 +428,87 @@ func (e *GoogleFunction) Update(ctx context.Context, req *function.UpdateRequest
|
||||
return errors.BadRequest("function.deploy", "function does not exist")
|
||||
}
|
||||
|
||||
gitter := git.NewGitter(map[string]string{})
|
||||
|
||||
for _, branch := range []string{"master", "main"} {
|
||||
err = gitter.Checkout(req.Repo, branch)
|
||||
if err == nil {
|
||||
break
|
||||
}
|
||||
fn := new(function.Func)
|
||||
if err := records[0].Decode(fn); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
if len(fn.Region) == 0 {
|
||||
fn.Region = GoogleRegions[0]
|
||||
}
|
||||
|
||||
if len(fn.Branch) == 0 {
|
||||
fn.Branch = "master"
|
||||
}
|
||||
|
||||
gitter := git.NewGitter(map[string]string{})
|
||||
if err := gitter.Checkout(fn.Repo, fn.Branch); err != nil {
|
||||
return errors.InternalServerError("function.update", err.Error())
|
||||
}
|
||||
|
||||
// process the env vars to the required format
|
||||
var envVars []string
|
||||
|
||||
for k, v := range req.EnvVars {
|
||||
for k, v := range fn.EnvVars {
|
||||
envVars = append(envVars, k+"="+v)
|
||||
}
|
||||
|
||||
var status string
|
||||
|
||||
go func() {
|
||||
// https://jsoverson.medium.com/how-to-deploy-node-js-functions-to-google-cloud-8bba05e9c10a
|
||||
cmd := exec.Command("gcloud", "functions", "deploy",
|
||||
multitenantPrefix+"-"+req.Name, "--region", "europe-west1", "--service-account", e.identity,
|
||||
"--allow-unauthenticated", "--entry-point", req.Entrypoint,
|
||||
"--trigger-http", "--project", e.project, "--runtime", req.Runtime)
|
||||
|
||||
// if env vars exist then set them
|
||||
if len(envVars) > 0 {
|
||||
cmd.Args = append(cmd.Args, "--set-env-vars", strings.Join(envVars, ","))
|
||||
LOOP:
|
||||
// wait for the deployment and status update
|
||||
for i := 0; i < 120; i++ {
|
||||
cmd := exec.Command("gcloud", "functions", "describe", "--quiet", "--format", "json",
|
||||
"--region", fn.Region, "--project", e.project, fn.Id)
|
||||
|
||||
outp, err := cmd.CombinedOutput()
|
||||
if err != nil {
|
||||
log.Error(fmt.Errorf(string(outp)))
|
||||
return
|
||||
}
|
||||
|
||||
log.Info(string(outp))
|
||||
|
||||
var m map[string]interface{}
|
||||
if err := json.Unmarshal(outp, &m); err != nil {
|
||||
log.Error(err)
|
||||
return
|
||||
}
|
||||
|
||||
// write back the url
|
||||
trigger := m["httpsTrigger"].(map[string]interface{})
|
||||
if v := trigger["url"].(string); len(v) > 0 {
|
||||
fn.Url = v
|
||||
} else {
|
||||
fn.Url = fmt.Sprintf("https://%s-%s.cloudfunctions.net/%s", fn.Region, e.project, fn.Id)
|
||||
}
|
||||
|
||||
v := m["status"].(string)
|
||||
|
||||
switch v {
|
||||
case "ACTIVE":
|
||||
status = "Deployed"
|
||||
break LOOP
|
||||
case "DEPLOY_IN_PROGRESS":
|
||||
status = "Deploying"
|
||||
case "OFFLINE":
|
||||
status = "DeploymentError"
|
||||
break LOOP
|
||||
}
|
||||
|
||||
// we need to try get the url again
|
||||
time.Sleep(time.Second)
|
||||
}
|
||||
|
||||
cmd.Dir = filepath.Join(gitter.RepoDir(), req.Subfolder)
|
||||
outp, err := cmd.CombinedOutput()
|
||||
if err != nil {
|
||||
log.Error(fmt.Errorf(string(outp)))
|
||||
}
|
||||
fn.Status = status
|
||||
fn.Updated = time.Now().Format(time.RFC3339Nano)
|
||||
store.Write(store.NewRecord(key, fn))
|
||||
}()
|
||||
|
||||
id := fmt.Sprintf("%v-%v-%v", tenantId, project, req.Name)
|
||||
rec := store.NewRecord(key, map[string]interface{}{
|
||||
"id": id,
|
||||
"project": project,
|
||||
"name": req.Name,
|
||||
"tenantId": tenantId,
|
||||
"repo": req.Repo,
|
||||
"subfolder": req.Subfolder,
|
||||
"entrypoint": req.Entrypoint,
|
||||
"runtime": req.Runtime,
|
||||
"env_vars": envVars,
|
||||
})
|
||||
|
||||
// write the record
|
||||
return store.Write(rec)
|
||||
// TODO: allow updating of branch and related?
|
||||
return nil
|
||||
}
|
||||
|
||||
func (e *GoogleFunction) Call(ctx context.Context, req *function.CallRequest, rsp *function.CallResponse) error {
|
||||
@@ -363,10 +522,28 @@ func (e *GoogleFunction) Call(ctx context.Context, req *function.CallRequest, rs
|
||||
if !ok {
|
||||
tenantId = "micro"
|
||||
}
|
||||
multitenantPrefix := strings.Replace(tenantId, "/", "-", -1)
|
||||
|
||||
url := e.address + multitenantPrefix + "-" + req.Name
|
||||
fmt.Println("URL:>", url)
|
||||
// get the function id based on the tenant
|
||||
recs, err := store.Read(OwnerKey + tenantId + "/" + req.Name)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if len(recs) == 0 {
|
||||
return errors.NotFound("function.call", "not found")
|
||||
}
|
||||
|
||||
fn := new(function.Func)
|
||||
recs[0].Decode(fn)
|
||||
|
||||
if len(fn.Id) == 0 {
|
||||
return errors.NotFound("function.call", "not found")
|
||||
}
|
||||
|
||||
url := fn.Url
|
||||
|
||||
if len(url) == 0 {
|
||||
url = fmt.Sprintf("https://%s-%s.cloudfunctions.net/%s", fn.Region, e.project, fn.Id)
|
||||
}
|
||||
|
||||
js, _ := json.Marshal(req.Request)
|
||||
if req.Request == nil || len(req.Request.Fields) == 0 {
|
||||
@@ -412,23 +589,40 @@ func (e *GoogleFunction) Delete(ctx context.Context, req *function.DeleteRequest
|
||||
if !ok {
|
||||
tenantId = "micro"
|
||||
}
|
||||
multitenantPrefix := strings.Replace(tenantId, "/", "-", -1)
|
||||
|
||||
project := req.Project
|
||||
if project == "" {
|
||||
project = "default"
|
||||
key := fmt.Sprintf(OwnerKey+"%v/%v", tenantId, req.Name)
|
||||
|
||||
records, err := store.Read(key)
|
||||
if err != nil && err == store.ErrNotFound {
|
||||
return nil
|
||||
}
|
||||
|
||||
cmd := exec.Command("gcloud", "functions", "delete", "--project", e.project, "--region", "europe-west1", multitenantPrefix+"-"+req.Name)
|
||||
outp, err := cmd.CombinedOutput()
|
||||
if err != nil && !strings.Contains(string(outp), "does not exist") {
|
||||
log.Error(fmt.Errorf(string(outp)))
|
||||
if len(records) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
fn := new(function.Func)
|
||||
if err := records[0].Decode(fn); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
key := fmt.Sprintf("function/%v/%v/%v", tenantId, project, req.Name)
|
||||
// async delete
|
||||
go func() {
|
||||
cmd := exec.Command("gcloud", "functions", "delete", "--quiet", "--project", e.project, "--region", fn.Region, fn.Id)
|
||||
outp, err := cmd.CombinedOutput()
|
||||
if err != nil && !strings.Contains(string(outp), "does not exist") {
|
||||
log.Error(fmt.Errorf(string(outp)))
|
||||
return
|
||||
}
|
||||
|
||||
return store.Delete(key)
|
||||
// delete the owner key
|
||||
store.Delete(key)
|
||||
|
||||
// delete the global key
|
||||
store.Delete(FunctionKey + fn.Id)
|
||||
}()
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (e *GoogleFunction) List(ctx context.Context, req *function.ListRequest, rsp *function.ListResponse) error {
|
||||
@@ -439,45 +633,31 @@ func (e *GoogleFunction) List(ctx context.Context, req *function.ListRequest, rs
|
||||
tenantId = "micro"
|
||||
}
|
||||
|
||||
key := "function/" + tenantId + "/"
|
||||
|
||||
project := req.Project
|
||||
if len(project) > 0 {
|
||||
key = key + "/" + project + "/"
|
||||
}
|
||||
key := OwnerKey + tenantId + "/"
|
||||
|
||||
records, err := store.Read(key, store.ReadPrefix())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
multitenantPrefix := strings.Replace(tenantId, "/", "-", -1)
|
||||
cmd := exec.Command("gcloud", "functions", "list", "--project", e.project, "--filter", "name~"+multitenantPrefix+"*")
|
||||
outp, err := cmd.CombinedOutput()
|
||||
if err != nil {
|
||||
log.Error(fmt.Errorf(string(outp)))
|
||||
}
|
||||
|
||||
lines := strings.Split(string(outp), "\n")
|
||||
statuses := map[string]string{}
|
||||
|
||||
for _, line := range lines {
|
||||
fields := strings.Fields(line)
|
||||
if len(fields) < 2 {
|
||||
for _, record := range records {
|
||||
fn := new(function.Func)
|
||||
if err := record.Decode(fn); err != nil {
|
||||
continue
|
||||
}
|
||||
statuses[fields[0]] = fields[1]
|
||||
}
|
||||
|
||||
rsp.Functions = []*function.Func{}
|
||||
|
||||
for _, record := range records {
|
||||
f := new(function.Func)
|
||||
if err := record.Decode(f); err != nil {
|
||||
return err
|
||||
if len(fn.Region) == 0 {
|
||||
fn.Region = GoogleRegions[0]
|
||||
}
|
||||
f.Status = statuses[multitenantPrefix+"-"+f.Name]
|
||||
rsp.Functions = append(rsp.Functions, f)
|
||||
|
||||
if len(fn.Branch) == 0 {
|
||||
fn.Branch = "master"
|
||||
}
|
||||
// set the custom domain
|
||||
if len(e.domain) > 0 {
|
||||
fn.Url = fmt.Sprintf("https://%s.%s", fn.Id, e.domain)
|
||||
}
|
||||
|
||||
rsp.Functions = append(rsp.Functions, fn)
|
||||
}
|
||||
|
||||
return nil
|
||||
@@ -493,20 +673,41 @@ func (e *GoogleFunction) Describe(ctx context.Context, req *function.DescribeReq
|
||||
tenantId = "micro"
|
||||
}
|
||||
|
||||
project := req.Project
|
||||
if project == "" {
|
||||
project = "default"
|
||||
}
|
||||
|
||||
multitenantPrefix := strings.Replace(tenantId, "/", "-", -1)
|
||||
key := fmt.Sprintf("function/%v/%v/%v", tenantId, project, req.Name)
|
||||
key := fmt.Sprintf(OwnerKey+"%v/%v", tenantId, req.Name)
|
||||
|
||||
records, err := store.Read(key)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
cmd := exec.Command("gcloud", "functions", "describe", "--region", "europe-west1", "--project", e.project, multitenantPrefix+"-"+req.Name)
|
||||
if len(records) == 0 {
|
||||
return errors.NotFound("function.describe", "function does not exist")
|
||||
}
|
||||
|
||||
fn := new(function.Func)
|
||||
|
||||
if err := records[0].Decode(fn); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if len(fn.Region) == 0 {
|
||||
fn.Region = GoogleRegions[0]
|
||||
}
|
||||
|
||||
if len(fn.Branch) == 0 {
|
||||
fn.Branch = "master"
|
||||
}
|
||||
|
||||
// set the custom domain
|
||||
if len(e.domain) > 0 {
|
||||
fn.Url = fmt.Sprintf("https://%s.%s", fn.Id, e.domain)
|
||||
}
|
||||
|
||||
// set the response function
|
||||
rsp.Function = fn
|
||||
|
||||
// get the current status
|
||||
cmd := exec.Command("gcloud", "functions", "describe", "--format", "json", "--region", fn.Region, "--project", e.project, fn.Id)
|
||||
outp, err := cmd.CombinedOutput()
|
||||
if err != nil {
|
||||
log.Error(fmt.Errorf(string(outp)))
|
||||
@@ -515,28 +716,65 @@ func (e *GoogleFunction) Describe(ctx context.Context, req *function.DescribeReq
|
||||
|
||||
log.Info(string(outp))
|
||||
m := map[string]interface{}{}
|
||||
err = yaml.Unmarshal(outp, m)
|
||||
|
||||
if err := json.Unmarshal(outp, &m); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// set describe info
|
||||
status := m["status"].(string)
|
||||
status = strings.Replace(status, "_", " ", -1)
|
||||
status = strings.Title(strings.ToLower(status))
|
||||
fn.Status = status
|
||||
|
||||
// set the url
|
||||
if len(fn.Url) == 0 && status == "Active" {
|
||||
v := m["httpsTrigger"].(map[string]interface{})
|
||||
fn.Url = v["url"].(string)
|
||||
}
|
||||
|
||||
// write it back
|
||||
go store.Write(store.NewRecord(key, fn))
|
||||
|
||||
rsp.Function.Status = status
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (g *GoogleFunction) Proxy(ctx context.Context, req *function.ProxyRequest, rsp *function.ProxyResponse) error {
|
||||
if len(req.Id) == 0 {
|
||||
return errors.BadRequest("function.proxy", "missing id")
|
||||
}
|
||||
|
||||
if !IDFormat.MatchString(req.Id) {
|
||||
return errors.BadRequest("function.proxy", "invalid id")
|
||||
}
|
||||
|
||||
recs, err := store.Read(FunctionKey+req.Id, store.ReadLimit(1))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if len(records) > 0 {
|
||||
f := &function.Func{}
|
||||
if err := records[0].Decode(f); err != nil {
|
||||
return err
|
||||
}
|
||||
rsp.Function = f
|
||||
} else {
|
||||
rsp.Function = &function.Func{
|
||||
Name: req.Name,
|
||||
Project: req.Project,
|
||||
}
|
||||
if len(recs) == 0 {
|
||||
return errors.BadRequest("function.proxy", "function does not exist")
|
||||
}
|
||||
|
||||
// set describe info
|
||||
rsp.Function.Status = m["status"].(string)
|
||||
rsp.Timeout = m["timeout"].(string)
|
||||
rsp.UpdatedAt = m["updateTime"].(string)
|
||||
fn := new(function.Func)
|
||||
recs[0].Decode(fn)
|
||||
|
||||
url := fn.Url
|
||||
|
||||
// backup plan is to construct https://region-project.cloudfunctions.net/function-name
|
||||
if len(url) == 0 {
|
||||
url = fmt.Sprintf("https://%s-%s.cloudfunctions.net/%s", fn.Region, g.project, fn.Id)
|
||||
}
|
||||
|
||||
rsp.Url = url
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (e *GoogleFunction) Regions(ctx context.Context, req *function.RegionsRequest, rsp *function.RegionsResponse) error {
|
||||
rsp.Regions = GoogleRegions
|
||||
return nil
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user