Skip to content

Commit dcb1ac6

Browse files
Merge pull request GoogleCloudPlatform#2826 from maqiuyujoyce/202408-pam-mockgcp
Support mockgcp test for PrivilegedAccessManagerEntitlement
2 parents c0d768a + 1a1da34 commit dcb1ac6

File tree

5 files changed

+110
-27
lines changed

5 files changed

+110
-27
lines changed

config/tests/samples/create/harness.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -736,7 +736,7 @@ func MaybeSkip(t *testing.T, name string, resources []*unstructured.Unstructured
736736

737737
case schema.GroupKind{Group: "privateca.cnrm.cloud.google.com", Kind: "PrivateCACAPool"}:
738738

739-
case schema.GroupKind{Group: "privilegedaccessmanager.cnrm.google.com", Kind: "PrivilegedAccessManagerEntitlement"}:
739+
case schema.GroupKind{Group: "privilegedaccessmanager.cnrm.cloud.google.com", Kind: "PrivilegedAccessManagerEntitlement"}:
740740

741741
case schema.GroupKind{Group: "pubsub.cnrm.cloud.google.com", Kind: "PubSubSchema"}:
742742
case schema.GroupKind{Group: "pubsub.cnrm.cloud.google.com", Kind: "PubSubSubscription"}:

mockgcp/mockprivilegedaccessmanager/entitlement.go

+54-5
Original file line numberDiff line numberDiff line change
@@ -16,11 +16,16 @@ package mockprivilegedaccessmanager
1616

1717
import (
1818
"context"
19+
"crypto/md5"
20+
"encoding/base64"
21+
"fmt"
22+
"time"
1923

2024
"google.golang.org/genproto/googleapis/longrunning"
2125
"google.golang.org/grpc/codes"
2226
"google.golang.org/grpc/status"
2327
"google.golang.org/protobuf/proto"
28+
"google.golang.org/protobuf/types/known/timestamppb"
2429

2530
pb "github.com/GoogleCloudPlatform/k8s-config-connector/mockgcp/generated/mockgcp/cloud/privilegedaccessmanager/v1"
2631
)
@@ -40,6 +45,9 @@ func (s *PrivilegedAccessManager) GetEntitlement(ctx context.Context, req *pb.Ge
4045

4146
obj := &pb.Entitlement{}
4247
if err := s.storage.Get(ctx, fqn, obj); err != nil {
48+
if status.Code(err) == codes.NotFound {
49+
return nil, status.Errorf(codes.NotFound, "Resource '%s' was not found", fqn)
50+
}
4351
return nil, err
4452
}
4553

@@ -53,16 +61,25 @@ func (s *PrivilegedAccessManager) CreateEntitlement(ctx context.Context, req *pb
5361
return nil, err
5462
}
5563

64+
now := timestamppb.New(time.Now())
5665
fqn := name.String()
5766

5867
obj := proto.Clone(req.Entitlement).(*pb.Entitlement)
5968
obj.Name = fqn
60-
69+
obj.CreateTime = now
70+
obj.UpdateTime = now
71+
obj.Etag = computeEtag(obj)
72+
obj.State = pb.Entitlement_AVAILABLE
6173
if err := s.storage.Create(ctx, fqn, obj); err != nil {
6274
return nil, err
6375
}
6476

65-
return s.operations.NewLRO(ctx)
77+
metadata := constructOperationMetadata(fqn, "create")
78+
return s.operations.StartLRO(ctx, name.parent(), metadata, func() (proto.Message, error) {
79+
result := proto.Clone(obj).(*pb.Entitlement)
80+
metadata.EndTime = now
81+
return result, nil
82+
})
6683
}
6784

6885
func (s *PrivilegedAccessManager) UpdateEntitlement(ctx context.Context, req *pb.UpdateEntitlementRequest) (*longrunning.Operation, error) {
@@ -82,7 +99,6 @@ func (s *PrivilegedAccessManager) UpdateEntitlement(ctx context.Context, req *pb
8299
// Required. A list of fields to be updated in this request.
83100
paths := req.GetUpdateMask().GetPaths()
84101

85-
// TODO: Some sort of helper for fieldmask?
86102
for _, path := range paths {
87103
switch path {
88104
case "eligibleUsers":
@@ -106,7 +122,13 @@ func (s *PrivilegedAccessManager) UpdateEntitlement(ctx context.Context, req *pb
106122
return nil, err
107123
}
108124

109-
return s.operations.NewLRO(ctx)
125+
metadata := constructOperationMetadata(fqn, "update")
126+
return s.operations.StartLRO(ctx, name.parent(), metadata, func() (proto.Message, error) {
127+
result := proto.Clone(obj).(*pb.Entitlement)
128+
now := timestamppb.New(time.Now())
129+
metadata.EndTime = now
130+
return result, nil
131+
})
110132
}
111133

112134
func (s *PrivilegedAccessManager) DeleteEntitlement(ctx context.Context, req *pb.DeleteEntitlementRequest) (*longrunning.Operation, error) {
@@ -121,6 +143,33 @@ func (s *PrivilegedAccessManager) DeleteEntitlement(ctx context.Context, req *pb
121143
if err := s.storage.Delete(ctx, fqn, oldObj); err != nil {
122144
return nil, err
123145
}
146+
metadata := constructOperationMetadata(fqn, "delete")
147+
return s.operations.StartLRO(ctx, name.parent(), metadata, func() (proto.Message, error) {
148+
result := proto.Clone(oldObj).(*pb.Entitlement)
149+
result.State = pb.Entitlement_DELETED
150+
result.Name = "projects/${projectNumber}/locations/global/entitlements/privilegedaccessmanagerentitlement-${uniqueId}"
151+
now := timestamppb.New(time.Now())
152+
metadata.EndTime = now
153+
return result, nil
154+
})
155+
}
156+
157+
func computeEtag(obj proto.Message) string {
158+
b, err := proto.Marshal(obj)
159+
if err != nil {
160+
panic(fmt.Sprintf("converting to proto: %v", err))
161+
}
162+
hash := md5.Sum(b)
163+
return base64.URLEncoding.EncodeToString(hash[:])
164+
}
124165

125-
return s.operations.NewLRO(ctx)
166+
func constructOperationMetadata(target, verb string) *pb.OperationMetadata {
167+
now := timestamppb.New(time.Now())
168+
return &pb.OperationMetadata{
169+
Target: target,
170+
CreateTime: now,
171+
ApiVersion: "v1",
172+
RequestedCancellation: false,
173+
Verb: verb,
174+
}
126175
}

mockgcp/mockprivilegedaccessmanager/names.go

+18-17
Original file line numberDiff line numberDiff line change
@@ -15,43 +15,44 @@
1515
package mockprivilegedaccessmanager
1616

1717
import (
18+
"fmt"
1819
"strings"
1920

2021
"google.golang.org/grpc/codes"
2122
"google.golang.org/grpc/status"
22-
23-
"github.com/GoogleCloudPlatform/k8s-config-connector/mockgcp/common/projects"
2423
)
2524

2625
type entitlementName struct {
27-
Project *projects.ProjectData
26+
Container string
2827
Location string
2928
EntitlementID string
3029
}
3130

3231
func (n *entitlementName) String() string {
33-
return "projects/" + n.Project.ID + "/locations/" + n.Location + "/entitlements/" + n.EntitlementID
32+
return n.parent() + "/entitlements/" + n.EntitlementID
33+
}
34+
35+
func (n *entitlementName) parent() string {
36+
return n.Container + "/locations/" + n.Location
3437
}
3538

3639
// parseEntitlementName parses a string into a entitlementName.
37-
// The expected form is projects/<projectID>/locations/<region>/entitlements/<entitlementID>
40+
// The expected form is projects/<projectID>/locations/<region>/entitlements/<entitlementID>,
41+
// or folders/<folderID>/locations/<region>/entitlements/<entitlementID>, or
42+
// organizations/<organizationID>/locations/<region>/entitlements/<entitlementID>.
3843
func (s *MockService) parseEntitlementName(name string) (*entitlementName, error) {
3944
tokens := strings.Split(name, "/")
4045

41-
if len(tokens) == 6 && tokens[0] == "projects" && tokens[2] == "locations" && tokens[4] == "entitlements" {
42-
project, err := s.Projects.GetProjectByID(tokens[1])
43-
if err != nil {
44-
return nil, err
45-
}
46+
if len(tokens) == 6 &&
47+
(tokens[0] == "projects" || tokens[0] == "folders" || tokens[0] == "organizations") &&
48+
tokens[2] == "locations" && tokens[4] == "entitlements" {
4649

47-
name := &entitlementName{
48-
Project: project,
50+
return &entitlementName{
51+
Container: fmt.Sprintf("%s/%s", tokens[0], tokens[1]),
4952
Location: tokens[3],
5053
EntitlementID: tokens[5],
51-
}
52-
53-
return name, nil
54-
} else {
55-
return nil, status.Errorf(codes.InvalidArgument, "name %q is not valid", name)
54+
}, nil
5655
}
56+
57+
return nil, status.Errorf(codes.InvalidArgument, "name %q is not valid", name)
5758
}

mockgcp/mockprivilegedaccessmanager/service.go

+13-4
Original file line numberDiff line numberDiff line change
@@ -18,10 +18,10 @@ import (
1818
"context"
1919
"net/http"
2020

21-
"github.com/grpc-ecosystem/grpc-gateway/v2/runtime"
2221
"google.golang.org/grpc"
2322

2423
"github.com/GoogleCloudPlatform/k8s-config-connector/mockgcp/common"
24+
"github.com/GoogleCloudPlatform/k8s-config-connector/mockgcp/common/httpmux"
2525
"github.com/GoogleCloudPlatform/k8s-config-connector/mockgcp/common/operations"
2626
pb "github.com/GoogleCloudPlatform/k8s-config-connector/mockgcp/generated/mockgcp/cloud/privilegedaccessmanager/v1"
2727
"github.com/GoogleCloudPlatform/k8s-config-connector/mockgcp/pkg/storage"
@@ -49,19 +49,28 @@ func New(env *common.MockEnvironment, storage storage.Storage) *MockService {
4949
}
5050

5151
func (s *MockService) ExpectedHosts() []string {
52-
return []string{"privilegedaccessmanger.googleapis.com"}
52+
return []string{"privilegedaccessmanager.googleapis.com"}
5353
}
5454

5555
func (s *MockService) Register(grpcServer *grpc.Server) {
5656
pb.RegisterPrivilegedAccessManagerServer(grpcServer, s.v1)
5757
}
5858

5959
func (s *MockService) NewHTTPMux(ctx context.Context, conn *grpc.ClientConn) (http.Handler, error) {
60-
mux := runtime.NewServeMux()
6160

62-
if err := pb.RegisterPrivilegedAccessManagerHandler(ctx, mux, conn); err != nil {
61+
mux, err := httpmux.NewServeMux(ctx, conn, httpmux.Options{},
62+
pb.RegisterPrivilegedAccessManagerHandler,
63+
s.operations.RegisterOperationsPath("/v1/{prefix=**}/operations/{name}"),
64+
)
65+
if err != nil {
6366
return nil, err
6467
}
6568

69+
mux.RewriteError = func(ctx context.Context, error *httpmux.ErrorResponse) {
70+
if error.Code == 404 {
71+
error.Errors = nil
72+
}
73+
}
74+
6675
return mux, nil
6776
}

tests/e2e/unified_test.go

+24
Original file line numberDiff line numberDiff line change
@@ -652,6 +652,30 @@ func runScenario(ctx context.Context, t *testing.T, testPause bool, fixture reso
652652
}
653653
})
654654

655+
// Specific to PAM
656+
// Boolean fields in LRO are omitted when false so we need
657+
// to add them back.
658+
jsonMutators = append(jsonMutators, func(obj map[string]any) {
659+
if _, found, _ := unstructured.NestedMap(obj, "metadata"); found {
660+
if val, found, err := unstructured.NestedString(obj, "metadata", "@type"); err == nil && found && val == "type.googleapis.com/google.cloud.privilegedaccessmanager.v1.OperationMetadata" {
661+
if _, found, err := unstructured.NestedString(obj, "done"); err == nil && !found {
662+
// Explicitly set `done` to `false`.
663+
if err := unstructured.SetNestedField(obj, false, "done"); err != nil {
664+
t.Fatal(err)
665+
}
666+
}
667+
668+
if _, found, err := unstructured.NestedString(obj, "metadata", "requestedCancellation"); err == nil && !found {
669+
// Explicitly set `metadata.requestedCancellation` to `false`.
670+
if err := unstructured.SetNestedField(obj, false, "metadata", "requestedCancellation"); err != nil {
671+
t.Fatal(err)
672+
}
673+
}
674+
}
675+
676+
}
677+
})
678+
655679
// Specific to pubsub
656680
addReplacement("revisionCreateTime", "2024-04-01T12:34:56.123456Z")
657681
addReplacement("revisionId", "revision-id-placeholder")

0 commit comments

Comments
 (0)