From 561ba16f5ab0b61faf7f656525c2e9efb581da50 Mon Sep 17 00:00:00 2001 From: justinsb Date: Wed, 18 Dec 2024 22:08:21 -0500 Subject: [PATCH] chore: create shared project number/id mapper --- apis/common/projects/mapper.go | 58 +++++++++++++++++++ apis/refs/v1beta1/computerefs.go | 23 ++------ pkg/config/controllerconfig.go | 23 ++++++++ .../cloudbuild/workerpool_controller.go | 28 ++------- pkg/controller/kccmanager/kccmanager.go | 4 ++ 5 files changed, 95 insertions(+), 41 deletions(-) create mode 100644 apis/common/projects/mapper.go diff --git a/apis/common/projects/mapper.go b/apis/common/projects/mapper.go new file mode 100644 index 0000000000..bec6538b7a --- /dev/null +++ b/apis/common/projects/mapper.go @@ -0,0 +1,58 @@ +package projects + +import ( + "context" + "fmt" + "strconv" + "strings" + + resourcemanager "cloud.google.com/go/resourcemanager/apiv3" + "cloud.google.com/go/resourcemanager/apiv3/resourcemanagerpb" +) + +type ProjectMapper struct { + client *resourcemanager.ProjectsClient +} + +func NewProjectMapper(client *resourcemanager.ProjectsClient) *ProjectMapper { + return &ProjectMapper{ + client: client, + } +} + +func (m *ProjectMapper) ReplaceProjectNumberWithID(ctx context.Context, projectID string) (string, error) { + if _, err := strconv.ParseInt(projectID, 10, 64); err != nil { + // Not a project number, no need to map + return projectID, nil + } + + req := &resourcemanagerpb.GetProjectRequest{ + Name: "projects/" + projectID, + } + project, err := m.client.GetProject(ctx, req) + if err != nil { + return "", fmt.Errorf("error getting project %q: %w", req.Name, err) + } + return project.ProjectId, nil +} + +func (m *ProjectMapper) LookupProjectNumber(ctx context.Context, projectID string) (int64, error) { + // Check if the project number is already a valid integer + // If not, we need to look it up + projectNumber, err := strconv.ParseInt(projectID, 10, 64) + if err != nil { + req := &resourcemanagerpb.GetProjectRequest{ + Name: "projects/" + projectID, + } + project, err := m.client.GetProject(ctx, req) + if err != nil { + return 0, fmt.Errorf("error getting project %q: %w", req.Name, err) + } + n, err := strconv.ParseInt(strings.TrimPrefix(project.Name, "projects/"), 10, 64) + if err != nil { + return 0, fmt.Errorf("error parsing project number for %q: %w", project.Name, err) + } + projectNumber = n + } + return projectNumber, nil +} diff --git a/apis/refs/v1beta1/computerefs.go b/apis/refs/v1beta1/computerefs.go index 707d95397d..86fdcbcc6a 100644 --- a/apis/refs/v1beta1/computerefs.go +++ b/apis/refs/v1beta1/computerefs.go @@ -20,8 +20,7 @@ import ( "strconv" "strings" - resourcemanager "cloud.google.com/go/resourcemanager/apiv3" - resourcemanagerpb "cloud.google.com/go/resourcemanager/apiv3/resourcemanagerpb" + "github.com/GoogleCloudPlatform/k8s-config-connector/apis/common/projects" "github.com/GoogleCloudPlatform/k8s-config-connector/pkg/k8s" apierrors "k8s.io/apimachinery/pkg/api/errors" "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" @@ -74,7 +73,7 @@ func ParseComputeNetworkID(external string) (*ComputeNetworkID, error) { } // ConvertToProjectNumber converts the external reference to use a project number. -func (ref *ComputeNetworkRef) ConvertToProjectNumber(ctx context.Context, projectsClient *resourcemanager.ProjectsClient) error { +func (ref *ComputeNetworkRef) ConvertToProjectNumber(ctx context.Context, projectMapper *projects.ProjectMapper) error { if ref == nil { return nil } @@ -84,24 +83,12 @@ func (ref *ComputeNetworkRef) ConvertToProjectNumber(ctx context.Context, projec return err } - // Check if the project number is already a valid integer - // If not, we need to look it up - projectNumber, err := strconv.ParseInt(id.Project, 10, 64) + projectNumber, err := projectMapper.LookupProjectNumber(ctx, id.Project) if err != nil { - req := &resourcemanagerpb.GetProjectRequest{ - Name: "projects/" + id.Project, - } - project, err := projectsClient.GetProject(ctx, req) - if err != nil { - return fmt.Errorf("error getting project %q: %w", req.Name, err) - } - n, err := strconv.ParseInt(strings.TrimPrefix(project.Name, "projects/"), 10, 64) - if err != nil { - return fmt.Errorf("error parsing project number for %q: %w", project.Name, err) - } - projectNumber = n + return fmt.Errorf("error looking up project number for project %q: %w", id.Project, err) } id.Project = strconv.FormatInt(projectNumber, 10) + ref.External = id.String() return nil } diff --git a/pkg/config/controllerconfig.go b/pkg/config/controllerconfig.go index c0e3ad7a7d..fefb4eca7d 100644 --- a/pkg/config/controllerconfig.go +++ b/pkg/config/controllerconfig.go @@ -15,8 +15,12 @@ package config import ( + "context" + "fmt" "net/http" + cloudresourcemanager "cloud.google.com/go/resourcemanager/apiv3" + "github.com/GoogleCloudPlatform/k8s-config-connector/apis/common/projects" "golang.org/x/oauth2" "google.golang.org/api/option" ) @@ -40,6 +44,25 @@ type ControllerConfig struct { // GCPTokenSource mints OAuth2 tokens to be passed with GCP API calls, // allowing use of a non-default OAuth2 identity GCPTokenSource oauth2.TokenSource + + // ProjectMapper maps between project ids and numbers + ProjectMapper *projects.ProjectMapper +} + +func (c *ControllerConfig) Init(ctx context.Context) error { + if c.ProjectMapper == nil { + opts, err := c.RESTClientOptions() + if err != nil { + return err + } + + projectsClient, err := cloudresourcemanager.NewProjectsRESTClient(ctx, opts...) + if err != nil { + return fmt.Errorf("building cloudresourcemanager client: %w", err) + } + c.ProjectMapper = projects.NewProjectMapper(projectsClient) + } + return nil } func (c *ControllerConfig) RESTClientOptions() ([]option.ClientOption, error) { diff --git a/pkg/controller/direct/cloudbuild/workerpool_controller.go b/pkg/controller/direct/cloudbuild/workerpool_controller.go index 037a611fd2..e073c016af 100644 --- a/pkg/controller/direct/cloudbuild/workerpool_controller.go +++ b/pkg/controller/direct/cloudbuild/workerpool_controller.go @@ -23,10 +23,10 @@ import ( gcp "cloud.google.com/go/cloudbuild/apiv1/v2" cloudbuildpb "cloud.google.com/go/cloudbuild/apiv1/v2/cloudbuildpb" - cloudresourcemanager "cloud.google.com/go/resourcemanager/apiv3" "google.golang.org/protobuf/types/known/fieldmaskpb" krm "github.com/GoogleCloudPlatform/k8s-config-connector/apis/cloudbuild/v1beta1" + "github.com/GoogleCloudPlatform/k8s-config-connector/apis/common/projects" refs "github.com/GoogleCloudPlatform/k8s-config-connector/apis/refs/v1beta1" "github.com/GoogleCloudPlatform/k8s-config-connector/pkg/config" "github.com/GoogleCloudPlatform/k8s-config-connector/pkg/controller/direct" @@ -69,19 +69,6 @@ func (m *model) client(ctx context.Context) (*gcp.Client, error) { return gcpClient, err } -func (m *model) projectsClient(ctx context.Context) (*cloudresourcemanager.ProjectsClient, error) { - opts, err := m.config.RESTClientOptions() - if err != nil { - return nil, err - } - - crmClient, err := cloudresourcemanager.NewProjectsRESTClient(ctx, opts...) - if err != nil { - return nil, fmt.Errorf("building cloudresourcemanager client: %w", err) - } - return crmClient, err -} - func (m *model) AdapterForObject(ctx context.Context, reader client.Reader, u *unstructured.Unstructured) (directbase.Adapter, error) { obj := &krm.CloudBuildWorkerPool{} if err := runtime.DefaultUnstructuredConverter.FromUnstructured(u.Object, &obj); err != nil { @@ -134,12 +121,6 @@ func (m *model) AdapterForObject(ctx context.Context, reader client.Reader, u *u } } - // Get Project GCP client - projectClient, err := m.projectsClient(ctx) - if err != nil { - return nil, err - } - // Get CloudBuild GCP client gcpClient, err := m.client(ctx) if err != nil { @@ -148,7 +129,7 @@ func (m *model) AdapterForObject(ctx context.Context, reader client.Reader, u *u return &Adapter{ id: id, - projectClient: projectClient, + projectMapper: m.config.ProjectMapper, gcpClient: gcpClient, reader: reader, desired: obj, @@ -170,6 +151,7 @@ func (m *model) AdapterForURL(ctx context.Context, url string) (directbase.Adapt } return &Adapter{ + projectMapper: m.config.ProjectMapper, id: &CloudBuildWorkerPoolIdentity{ project: tokens[1], location: tokens[3], @@ -184,7 +166,7 @@ func (m *model) AdapterForURL(ctx context.Context, url string) (directbase.Adapt type Adapter struct { id *CloudBuildWorkerPoolIdentity - projectClient *cloudresourcemanager.ProjectsClient + projectMapper *projects.ProjectMapper gcpClient *gcp.Client reader client.Reader desired *krm.CloudBuildWorkerPool @@ -370,7 +352,7 @@ func (a *Adapter) resolveDependencies(ctx context.Context, reader client.Reader, return err } - if err := networkSpec.PeeredNetworkRef.ConvertToProjectNumber(ctx, a.projectClient); err != nil { + if err := networkSpec.PeeredNetworkRef.ConvertToProjectNumber(ctx, a.projectMapper); err != nil { return err } } diff --git a/pkg/controller/kccmanager/kccmanager.go b/pkg/controller/kccmanager/kccmanager.go index 0a23b9bea6..908428ba4e 100644 --- a/pkg/controller/kccmanager/kccmanager.go +++ b/pkg/controller/kccmanager/kccmanager.go @@ -144,6 +144,10 @@ func New(ctx context.Context, restConfig *rest.Config, cfg Config) (manager.Mana UserAgent: gcp.KCCUserAgent, } + if err := controllerConfig.Init(ctx); err != nil { + return nil, err + } + // Initialize direct controllers if err := registry.Init(ctx, controllerConfig); err != nil { return nil, err