Skip to content

Commit

Permalink
Merge pull request #3591 from gemmahou/cloudidentitygroup-types
Browse files Browse the repository at this point in the history
feat: Add CloudIdentityGroup types
  • Loading branch information
google-oss-prow[bot] authored Feb 5, 2025
2 parents 1569b32 + 0abc8bd commit cd511f7
Show file tree
Hide file tree
Showing 22 changed files with 4,772 additions and 4,113 deletions.
16 changes: 16 additions & 0 deletions apis/cloudidentity/v1beta1/doc.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
// Copyright 2025 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.

// +kcc:proto=mockgcp.cloud.cloudidentity.groups.v1beta1
package v1beta1
76 changes: 76 additions & 0 deletions apis/cloudidentity/v1beta1/group_identity.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
// Copyright 2025 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 v1beta1

import (
"context"
"fmt"
"strings"

"github.com/GoogleCloudPlatform/k8s-config-connector/apis/common"
"sigs.k8s.io/controller-runtime/pkg/client"
)

// GroupIdentity defines the resource reference to CloudIdentityGroup, which "External" field
// holds the GCP identifier for the KRM object.
type GroupIdentity struct {
id string
}

func (i *GroupIdentity) String() string {
return "/groups/" + i.id
}

func (i *GroupIdentity) ID() string {
return i.id
}

// NewGroupIdentity New builds a GroupIdentity from the Config Connector Group object.
func NewGroupIdentity(ctx context.Context, reader client.Reader, obj *CloudIdentityGroup) (*GroupIdentity, error) {
// Get desired ID
resourceID := common.ValueOf(obj.Spec.ResourceID)
if resourceID == "" {
resourceID = obj.GetName()
}
if resourceID == "" {
return nil, fmt.Errorf("cannot resolve resource ID")
}

// Use approved External
externalRef := common.ValueOf(obj.Status.ExternalRef)
if externalRef != "" {
// Validate desired with actual
actualResourceID, err := ParseGroupExternal(externalRef)
if err != nil {
return nil, err
}
if actualResourceID != resourceID {
return nil, fmt.Errorf("cannot reset `metadata.name` or `spec.resourceID` to %s, since it has already assigned to %s",
resourceID, actualResourceID)
}
}
return &GroupIdentity{
id: resourceID,
}, nil
}

func ParseGroupExternal(external string) (resourceID string, err error) {
tokens := strings.Split(external, "/")
if len(tokens) != 2 || tokens[0] != "groups" {
return "", fmt.Errorf("format of CloudIdentityGroup external=%q was not known (use groups/{{groupID}})", external)
}
resourceID = tokens[1]
return resourceID, nil
}
83 changes: 83 additions & 0 deletions apis/cloudidentity/v1beta1/group_reference.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
// Copyright 2025 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 v1beta1

import (
"context"
"fmt"

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"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
"k8s.io/apimachinery/pkg/types"
"sigs.k8s.io/controller-runtime/pkg/client"
)

var _ refsv1beta1.ExternalNormalizer = &GroupRef{}

// GroupRef defines the resource reference to CloudIdentityGroup, which "External" field
// holds the GCP identifier for the KRM object.
type GroupRef struct {
// A reference to an externally managed CloudIdentityGroup resource.
// Should be in the format "groups/{{groupID}}".
External string `json:"external,omitempty"`

// The name of a CloudIdentityGroup resource.
Name string `json:"name,omitempty"`

// The namespace of a CloudIdentityGroup resource.
Namespace string `json:"namespace,omitempty"`
}

// NormalizedExternal provision the "External" value for other resource that depends on CloudIdentityGroup.
// If the "External" is given in the other resource's spec.CloudIdentityGroupRef, the given value will be used.
// Otherwise, the "Name" and "Namespace" will be used to query the actual CloudIdentityGroup object from the cluster.
func (r *GroupRef) NormalizedExternal(ctx context.Context, reader client.Reader, otherNamespace string) (string, error) {
if r.External != "" && r.Name != "" {
return "", fmt.Errorf("cannot specify both name and external on %s reference", CloudIdentityGroupGVK.Kind)
}
// From given External
if r.External != "" {
if _, err := ParseGroupExternal(r.External); err != nil {
return "", err
}
return r.External, nil
}

// From the Config Connector object
if r.Namespace == "" {
r.Namespace = otherNamespace
}
key := types.NamespacedName{Name: r.Name, Namespace: r.Namespace}
u := &unstructured.Unstructured{}
u.SetGroupVersionKind(CloudIdentityGroupGVK)
if err := reader.Get(ctx, key, u); err != nil {
if apierrors.IsNotFound(err) {
return "", k8s.NewReferenceNotFoundError(u.GroupVersionKind(), key)
}
return "", fmt.Errorf("reading referenced %s %s: %w", CloudIdentityGroupGVK, key, err)
}
// Get external from status.externalRef. This is the most trustworthy place.
actualExternalRef, _, err := unstructured.NestedString(u.Object, "status", "externalRef")
if err != nil {
return "", fmt.Errorf("reading status.externalRef: %w", err)
}
if actualExternalRef == "" {
return "", k8s.NewReferenceNotReadyError(u.GroupVersionKind(), key)
}
r.External = actualExternalRef
return r.External, nil
}
131 changes: 131 additions & 0 deletions apis/cloudidentity/v1beta1/group_types.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,131 @@
// Copyright 2025 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 v1beta1

import (
"github.com/GoogleCloudPlatform/k8s-config-connector/pkg/clients/generated/apis/k8s/v1alpha1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)

var CloudIdentityGroupGVK = GroupVersion.WithKind("CloudIdentityGroup")

// +kcc:proto=mockgcp.cloud.cloudidentity.groups.v1beta1.EntityKey
type EntityKey struct {
// Immutable. The ID of the entity. For Google-managed entities, the `id` must be the email address of an existing group or user. For external-identity-mapped entities, the `id` must be a string conforming to the Identity Source's requirements. Must be unique within a `namespace`.
// +required
ID string `json:"id"`

// Immutable. The namespace in which the entity exists. If not specified, the `EntityKey` represents a Google-managed entity such as a Google user or a Google Group. If specified, the `EntityKey` represents an external-identity-mapped group. The namespace must correspond to an identity source created in Admin Console and must be in the form of `identitysources/{identity_source_id}`.
Namespace *string `json:"namespace,omitempty"`
}

// CloudIdentityGroupSpec defines the desired state of CloudIdentityGroup
// +kcc:proto=mockgcp.cloud.cloudidentity.groups.v1beta1.Group
type CloudIdentityGroupSpec struct {
// An extended description to help users determine the purpose of a `Group`. Must not be longer than 4,096 characters.
Description *string `json:"description,omitempty"`

// The display name of the `Group`.
DisplayName *string `json:"displayName,omitempty"`

// +kubebuilder:validation:XValidation:rule="self == oldSelf",message="GroupKey field is immutable"
// Immutable. EntityKey of the Group.
// +required
GroupKey EntityKey `json:"groupKey"`

// Immutable. The initial configuration options for creating a Group. See the [API reference](https://cloud.google.com/identity/docs/reference/rest/v1beta1/groups/create#initialgroupconfig) for possible values. Default value: "EMPTY" Possible values: ["INITIAL_GROUP_CONFIG_UNSPECIFIED", "WITH_INITIAL_OWNER", "EMPTY"].
InitialGroupConfig *string `json:"initialGroupConfig,omitempty"`

// One or more label entries that apply to the Group. Currently supported labels contain a key with an empty value. Google Groups are the default type of group and have a label with a key of cloudidentity.googleapis.com/groups.discussion_forum and an empty value. Existing Google Groups can have an additional label with a key of cloudidentity.googleapis.com/groups.security and an empty value added to them. This is an immutable change and the security label cannot be removed once added. Dynamic groups have a label with a key of cloudidentity.googleapis.com/groups.dynamic. Identity-mapped groups for Cloud Search have a label with a key of system/groups/external and an empty value.
// +required
Labels map[string]string `json:"labels"`

// +kubebuilder:validation:XValidation:rule="self == oldSelf",message="Parent field is immutable"
// Immutable. The resource name of the entity under which this `Group` resides in the Cloud Identity resource hierarchy. Must be of the form `identitysources/{identity_source}` for external [identity-mapped groups](https://support.google.com/a/answer/9039510) or `customers/{customer_id}` for Google Groups. The `customer_id` must begin with "C" (for example, 'C046psxkn'). [Find your customer ID.] (https://support.google.com/cloudidentity/answer/10070793)
// +required
Parent *string `json:"parent,omitempty"`

// Immutable. The CloudIdentityGroup name. If not given, the metadata.name will be used.
ResourceID *string `json:"resourceID,omitempty"`
}

// CloudIdentityGroupStatus defines the config connector machine state of CloudIdentityGroup
type CloudIdentityGroupStatus struct {
/* Conditions represent the latest available observations of the
object's current state. */
Conditions []v1alpha1.Condition `json:"conditions,omitempty"`

// The time when the `Group` was created.
// +kcc:proto:field=mockgcp.cloud.cloudidentity.groups.v1beta1.Group.create_time
CreateTime *string `json:"createTime,omitempty"`

// The [resource name](https://cloud.google.com/apis/design/resource_names) of the `Group`. Shall be of the form `groups/{group_id}`.
// +kcc:proto:field=mockgcp.cloud.cloudidentity.groups.v1beta1.Group.name
Name *string `json:"name,omitempty"`

// A unique specifier for the CloudIdentityGroup resource in GCP.
ExternalRef *string `json:"externalRef,omitempty"`

// ObservedGeneration is the generation of the resource that was most recently observed by the Config Connector controller. If this is equal to metadata.generation, then that means that the current reported status reflects the most recent desired state of the resource.
ObservedGeneration *int64 `json:"observedGeneration,omitempty"`

// ObservedState is the state of the resource as most recently observed in GCP.
ObservedState *CloudIdentityGroupObservedState `json:"observedState,omitempty"`

// The time when the `Group` was last updated.
// +kcc:proto:field=mockgcp.cloud.cloudidentity.groups.v1beta1.Group.update_time
UpdateTime *string `json:"updateTime,omitempty"`
}

// CloudIdentityGroupObservedState is the state of the CloudIdentityGroup resource as most recently observed in GCP.
// +kcc:proto=mockgcp.cloud.cloudidentity.groups.v1beta1.Group
type CloudIdentityGroupObservedState struct {
// Additional group keys associated with the Group.
AdditionalGroupKeys []EntityKey `json:"additionalGroupKeys,omitempty"`
}

// +genclient
// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object
// +kubebuilder:resource:categories=gcp,shortName=gcpcloudidentitygroup;gcpcloudidentitygroups
// +kubebuilder:subresource:status
// +kubebuilder:metadata:labels="cnrm.cloud.google.com/managed-by-kcc=true";"cnrm.cloud.google.com/stability-level=stable";"cnrm.cloud.google.com/system=true";"cnrm.cloud.google.com/tf2crd=true"
// +kubebuilder:printcolumn:name="Age",JSONPath=".metadata.creationTimestamp",type="date"
// +kubebuilder:printcolumn:name="Ready",JSONPath=".status.conditions[?(@.type=='Ready')].status",type="string",description="When 'True', the most recent reconcile of the resource succeeded"
// +kubebuilder:printcolumn:name="Status",JSONPath=".status.conditions[?(@.type=='Ready')].reason",type="string",description="The reason for the value in 'Ready'"
// +kubebuilder:printcolumn:name="Status Age",JSONPath=".status.conditions[?(@.type=='Ready')].lastTransitionTime",type="date",description="The last transition time for the value in 'Status'"

// CloudIdentityGroup is the Schema for the CloudIdentityGroup API
// +k8s:openapi-gen=true
type CloudIdentityGroup struct {
metav1.TypeMeta `json:",inline"`
metav1.ObjectMeta `json:"metadata,omitempty"`

// +required
Spec CloudIdentityGroupSpec `json:"spec,omitempty"`
Status CloudIdentityGroupStatus `json:"status,omitempty"`
}

// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object

// CloudIdentityGroupList contains a list of CloudIdentityGroup
type CloudIdentityGroupList struct {
metav1.TypeMeta `json:",inline"`
metav1.ListMeta `json:"metadata,omitempty"`
Items []CloudIdentityGroup `json:"items"`
}

func init() {
SchemeBuilder.Register(&CloudIdentityGroup{}, &CloudIdentityGroupList{})
}
33 changes: 33 additions & 0 deletions apis/cloudidentity/v1beta1/groupversion_info.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
// Copyright 2025 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.

// +kubebuilder:object:generate=true
// +groupName=cloudidentity.cnrm.cloud.google.com
package v1beta1

import (
"k8s.io/apimachinery/pkg/runtime/schema"
"sigs.k8s.io/controller-runtime/pkg/scheme"
)

var (
// GroupVersion is group version used to register these objects
GroupVersion = schema.GroupVersion{Group: "cloudidentity.cnrm.cloud.google.com", Version: "v1beta1"}

// SchemeBuilder is used to add go types to the GroupVersionKind scheme
SchemeBuilder = &scheme.Builder{GroupVersion: GroupVersion}

// AddToScheme adds the types in this group-version to the given scheme.
AddToScheme = SchemeBuilder.AddToScheme
)
Loading

0 comments on commit cd511f7

Please sign in to comment.