Skip to content

Commit

Permalink
Merge pull request GoogleCloudPlatform#3156 from ericpang777/ssm15
Browse files Browse the repository at this point in the history
Add SecureSourceManagerRepository controller and mockgcp
  • Loading branch information
google-oss-prow[bot] authored Dec 23, 2024
2 parents 977cce5 + 518ee2b commit 480c4e5
Show file tree
Hide file tree
Showing 14 changed files with 1,336 additions and 1 deletion.
34 changes: 34 additions & 0 deletions apis/securesourcemanager/v1alpha1/instance_reference.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,12 @@ package v1alpha1
import (
"context"
"fmt"
"strconv"
"strings"

resourcemanager "cloud.google.com/go/resourcemanager/apiv3"
resourcemanagerpb "cloud.google.com/go/resourcemanager/apiv3/resourcemanagerpb"

refsv1beta1 "github.com/GoogleCloudPlatform/k8s-config-connector/apis/refs/v1beta1"
"github.com/GoogleCloudPlatform/k8s-config-connector/pkg/k8s"
apierrors "k8s.io/apimachinery/pkg/api/errors"
Expand Down Expand Up @@ -70,6 +74,36 @@ func parseSecureSourceManagerExternal(external string) (*ProjectIDAndLocation, s
return nil, "", fmt.Errorf("format of SecureSourceManagerInstance external=%q was not known (use projects/{{projectId}}/locations/{{location}}/instances/{{instanceID}})", external)
}

// ConvertToProjectNumber converts the external reference to use a project number.
func (r *SecureSourceManagerInstanceRef) ConvertToProjectNumber(ctx context.Context, projectsClient *resourcemanager.ProjectsClient) error {
if r == nil {
return nil
}

parent, id, err := parseSecureSourceManagerExternal(r.External)

// Check if the project number is already a valid integer
// If not, we need to look it up
projectNumber, err := strconv.ParseInt(id, 10, 64)
if err != nil {
req := &resourcemanagerpb.GetProjectRequest{
Name: "projects/" + parent.ProjectID,
}
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
}
parent.ProjectID = strconv.FormatInt(projectNumber, 10)
r.External = fmt.Sprintf("%s/instances/%s", parent.String(), id)
return nil
}

// NormalizedExternal provision the "External" value for other resource that depends on SecureSourceManagerInstance.
// If the "External" is given in the other resource's spec.SecureSourceManagerInstanceRef, the given value will be used.
// Otherwise, the "Name" and "Namespace" will be used to query the actual SecureSourceManagerInstance object from the cluster.
Expand Down
1 change: 1 addition & 0 deletions config/tests/samples/create/harness.go
Original file line number Diff line number Diff line change
Expand Up @@ -857,6 +857,7 @@ func MaybeSkip(t *testing.T, name string, resources []*unstructured.Unstructured
case schema.GroupKind{Group: "secretmanager.cnrm.cloud.google.com", Kind: "SecretManagerSecretVersion"}:

case schema.GroupKind{Group: "securesourcemanager.cnrm.cloud.google.com", Kind: "SecureSourceManagerInstance"}:
case schema.GroupKind{Group: "securesourcemanager.cnrm.cloud.google.com", Kind: "SecureSourceManagerRepository"}:

case schema.GroupKind{Group: "servicedirectory.cnrm.cloud.google.com", Kind: "ServiceDirectoryNamespace"}:
case schema.GroupKind{Group: "servicedirectory.cnrm.cloud.google.com", Kind: "ServiceDirectoryService"}:
Expand Down
164 changes: 164 additions & 0 deletions mockgcp/mocksecuresourcemanager/repository.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,164 @@
// Copyright 2024 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package mocksecuresourcemanager

import (
"context"
"fmt"
"strings"
"time"

longrunning "google.golang.org/genproto/googleapis/longrunning"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/status"
"google.golang.org/protobuf/proto"
"google.golang.org/protobuf/types/known/emptypb"
"google.golang.org/protobuf/types/known/timestamppb"

"github.com/GoogleCloudPlatform/k8s-config-connector/mockgcp/common/projects"
pb "github.com/GoogleCloudPlatform/k8s-config-connector/mockgcp/generated/mockgcp/cloud/securesourcemanager/v1"
)

func (s *secureSourceManagerServer) GetRepository(ctx context.Context, req *pb.GetRepositoryRequest) (*pb.Repository, error) {
name, err := s.parseRepositoryName(req.Name)
if err != nil {
return nil, err
}

fqn := name.String()

obj := &pb.Repository{}
if err := s.storage.Get(ctx, fqn, obj); err != nil {
if status.Code(err) == codes.NotFound {
return nil, status.Errorf(codes.NotFound, "Resource \"%s\" was not found", fqn)
}
return nil, err
}

return obj, nil
}

func (s *secureSourceManagerServer) CreateRepository(ctx context.Context, req *pb.CreateRepositoryRequest) (*longrunning.Operation, error) {
reqName := req.Parent + "/repositories/" + req.RepositoryId
name, err := s.parseRepositoryName(reqName)
if err != nil {
return nil, err
}

fqn := name.String()

now := time.Now()

obj := proto.Clone(req.Repository).(*pb.Repository)
obj.Name = fqn

obj.CreateTime = timestamppb.New(now)

instanceName, err := s.parseInstanceName(obj.GetInstance())
if err != nil {
return nil, err
}
// Real GCP doesn't include initial config field.
obj.InitialConfig = nil

prefix := fmt.Sprintf("https://%s-%d", instanceName.InstanceID, name.Project.Number)
domain := "." + name.Location + ".sourcemanager.dev"
obj.Uris = &pb.Repository_URIs{
Html: prefix + domain + fmt.Sprintf("/%s/%s", name.Project.ID, req.GetRepositoryId()),
Api: prefix + "-api" + domain + fmt.Sprintf("/v1/projects/%s/locations/%s/repositories/%s", name.Project.ID, name.Location, req.GetRepositoryId()),
GitHttps: prefix + "-git" + domain + fmt.Sprintf("/%s/%s.git", name.Project.ID, req.GetRepositoryId()),
}

op := &pb.OperationMetadata{
CreateTime: timestamppb.New(now),
Target: name.String(),
Verb: "create",
ApiVersion: "v1",
}
if err := s.storage.Create(ctx, fqn, obj); err != nil {
return nil, err
}
return s.operations.DoneLRO(ctx, "", op, func() *pb.Repository {
op.EndTime = timestamppb.New(now)
return obj
}())
}

func (s *secureSourceManagerServer) DeleteRepository(ctx context.Context, req *pb.DeleteRepositoryRequest) (*longrunning.Operation, error) {
name, err := s.parseRepositoryName(req.GetName())
if err != nil {
return nil, err
}

fqn := name.String()
now := time.Now()

deleted := &pb.Repository{}
if err := s.storage.Delete(ctx, fqn, deleted); err != nil {
return nil, err
}

op := &pb.OperationMetadata{
CreateTime: timestamppb.New(now),
Target: name.String(),
Verb: "delete",
ApiVersion: "v1",
}

return s.operations.DoneLRO(ctx, "", op, func() *emptypb.Empty {
op.EndTime = timestamppb.New(now)
return &emptypb.Empty{}
}())
}

type RepositoryName struct {
Project *projects.ProjectData
Location string
RepositoryID string
}

func (n *RepositoryName) String() string {
return fmt.Sprintf("projects/%s/locations/%s/repositories/%s", n.Project.ID, n.Location, n.RepositoryID)
}

// func (n *RepositoryName) Target() string {
// return fmt.Sprintf("projects/%s/locations/%s/repositories/%s", n.Project.ID, n.Location, n.RepositoryID)
// }

// parseRepositoryName parses a string into a RepositoryName.
// The expected form is projects/*/locations/*/repositories/*
func (s *MockService) parseRepositoryName(name string) (*RepositoryName, error) {
tokens := strings.Split(name, "/")

if len(tokens) == 6 && tokens[0] == "projects" && tokens[2] == "locations" && tokens[4] == "repositories" {
projectName, err := projects.ParseProjectName(tokens[0] + "/" + tokens[1])
if err != nil {
return nil, err
}
project, err := s.Projects.GetProject(projectName)
if err != nil {
return nil, err
}

name := &RepositoryName{
Project: project,
Location: tokens[3],
RepositoryID: tokens[5],
}
return name, nil
} else {
return nil, status.Errorf(codes.InvalidArgument, "name %q is not valid", name)
}
}
2 changes: 2 additions & 0 deletions pkg/controller/direct/registry/registry.go
Original file line number Diff line number Diff line change
Expand Up @@ -143,6 +143,8 @@ func SupportsIAM(groupKind schema.GroupKind) (bool, error) {
return false, nil
case schema.GroupKind{Group: "securesourcemanager.cnrm.cloud.google.com", Kind: "SecureSourceManagerInstance"}:
return false, nil
case schema.GroupKind{Group: "securesourcemanager.cnrm.cloud.google.com", Kind: "SecureSourceManagerRepository"}:
return false, nil
case schema.GroupKind{Group: "discoveryengine.cnrm.cloud.google.com", Kind: "DiscoveryEngineDataStore"}:
return false, nil
}
Expand Down

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading

0 comments on commit 480c4e5

Please sign in to comment.