...

Source file src/edge-infra.dev/pkg/lib/gcp/project/project.go

Documentation: edge-infra.dev/pkg/lib/gcp/project

     1  // Package project contains convenience functions for working with GCP projects, eg creation, deletion,
     2  // updating billing accounts
     3  package project
     4  
     5  import (
     6  	"fmt"
     7  	"log"
     8  	"math/rand"
     9  	"regexp"
    10  	"time"
    11  
    12  	cb "google.golang.org/api/cloudbilling/v1"
    13  	crm "google.golang.org/api/cloudresourcemanager/v1"
    14  )
    15  
    16  const (
    17  	// ProjectIDRegexpString is the regular expression that represents valid GCP
    18  	// Project ID strings, per https://cloud.google.com/resource-manager/docs/creating-managing-projects#before_you_begin
    19  	ProjectIDRegexpString = `^[a-z][-a-z0-9]{5,29}[a-z0-9]$`
    20  )
    21  
    22  func init() {
    23  	_, err := regexp.Compile(ProjectIDRegexpString)
    24  	if err != nil {
    25  		log.Fatal("project.init: failed  to compile the ProjectID regular expression")
    26  	}
    27  }
    28  
    29  // CreateWithBillingAccount creates a new gcp project and associates it with the given billing
    30  // account.
    31  func CreateWithBillingAccount(crmSvc *crm.Service, cbSvc *cb.APIService, name, folderID, billing string) (*crm.Project, error) {
    32  	// stay <= 30 characters for gcp naming requirements, with enough room for sufficient
    33  	// randomness
    34  	if len(name) > 19 {
    35  		return nil, fmt.Errorf("len of project name cant be > 19, save room for random id generation")
    36  	}
    37  	projectID := fmt.Sprintf("%s-%s", name, RandAN(29-len(name)))
    38  	project := &crm.Project{
    39  		Name:      name,
    40  		ProjectId: projectID,
    41  		Parent: &crm.ResourceId{
    42  			Id:   folderID,
    43  			Type: "folder",
    44  		},
    45  	}
    46  	createProjectOp, err := crmSvc.Projects.Create(project).Do()
    47  	if err != nil {
    48  		return nil, err
    49  	}
    50  	for {
    51  		if createProjectOp.Done {
    52  			break
    53  		}
    54  		createProjectOp, err = crmSvc.Operations.Get(createProjectOp.Name).Do()
    55  		if err != nil {
    56  			return nil, err
    57  		}
    58  		time.Sleep(5 * time.Second)
    59  	}
    60  	newProject, err := crmSvc.Projects.Get(projectID).Do()
    61  	if err != nil {
    62  		return nil, err
    63  	}
    64  
    65  	pbi := &cb.ProjectBillingInfo{
    66  		BillingAccountName: fmt.Sprintf("billingAccounts/%s", billing),
    67  	}
    68  	_, err = cbSvc.Projects.UpdateBillingInfo(fmt.Sprintf("projects/%s", projectID), pbi).Do()
    69  	if err != nil {
    70  		return nil, err
    71  	}
    72  	return newProject, nil
    73  }
    74  
    75  // DeleteProject deletes a gcp project associated to the
    76  // provided project id.
    77  func DeleteProject(crmSvc *crm.Service, projectID string) error {
    78  	_, err := crmSvc.Projects.Delete(projectID).Do()
    79  	if err != nil {
    80  		return err
    81  	}
    82  	return nil
    83  }
    84  
    85  // generates random names that conform to gcp project naming requirements
    86  func RandAN(n int) string {
    87  	var runes = []rune("abcdefghijklmnopqrstuvwxyz0123456789")
    88  	genName := make([]rune, n)
    89  	for i := range genName {
    90  		genName[i] = runes[rand.Intn(len(runes))] // #nosec G404 - random string is cosmetic
    91  	}
    92  	return string(genName)
    93  }
    94  
    95  // IsValidProjectID checks the input string against the regular expression for
    96  // valid GCP Project IDs
    97  func IsValidProjectID(id string) error {
    98  	// We can drop the error because we confirm the Regexp string compiles during
    99  	// init, which is the only type of error regexp.MatchString can return
   100  	ok, _ := regexp.MatchString(ProjectIDRegexpString, id)
   101  	if !ok {
   102  		return &IDValidationError{id}
   103  	}
   104  	return nil
   105  }
   106  
   107  // IDValidationError is a contextualized validation error for the Project ID
   108  type IDValidationError struct {
   109  	id string
   110  }
   111  
   112  // Error prints the contextualized validaton error message for the Project ID
   113  func (e *IDValidationError) Error() string {
   114  	return fmt.Sprintf("invalid GCP Project ID: %s", e.id)
   115  }
   116  

View as plain text