Skip to content

Commit

Permalink
Merge pull request #2785 from jingyih/IAPSettings
Browse files Browse the repository at this point in the history
feat: add types and mapping functions for IAPSettings
  • Loading branch information
google-oss-prow[bot] authored Jan 22, 2025
2 parents 0521ae5 + 39f61ca commit 2baa6c5
Show file tree
Hide file tree
Showing 16 changed files with 1,478 additions and 3 deletions.
16 changes: 16 additions & 0 deletions apis/iap/v1alpha1/doc.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
// 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.

// +kcc:proto=google.cloud.iap.v1
package v1alpha1
33 changes: 33 additions & 0 deletions apis/iap/v1alpha1/groupversion_info.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
// 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.

// +kubebuilder:object:generate=true
// +groupName=iap.cnrm.cloud.google.com
package v1alpha1

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: "iap.cnrm.cloud.google.com", Version: "v1alpha1"}

// 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
)
144 changes: 144 additions & 0 deletions apis/iap/v1alpha1/iapsettings_identity.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,144 @@
// 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 v1alpha1

import (
"context"
"fmt"
"strings"

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

// IAPSettingsIdentity defines the resource reference to IAPSettings.
// The id could have the following format:
//
// organizations/{organization_id}
// folders/{folder_id}
// projects/{projects_id}
// projects/{projects_id}/iap_web
// projects/{projects_id}/iap_web/compute
// projects/{projects_id}/iap_web/compute-{region}
// projects/{projects_id}/iap_web/compute/service/{service_id}
// projects/{projects_id}/iap_web/compute-{region}/service/{service_id}
// projects/{projects_id}/iap_web/appengine-{app_id}
// projects/{projects_id}/iap_web/appengine-{app_id}/service/{service_id}
// projects/{projects_id}/iap_web/appengine-{app_id}/service/{service_id}/version/{version_id}
type IAPSettingsIdentity struct {
id string
}

func (i *IAPSettingsIdentity) String() string {
return i.id
}

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

// New builds a IAPSettingsIdentity from the Config Connector IAPSettings object.
func NewIAPSettingsIdentity(ctx context.Context, reader client.Reader, obj *IAPSettings) (*IAPSettingsIdentity, error) {
// Get desired ID
// Note that we cannot use `metadata.name` as resourceID since the supported resource ID formats are not valid Kubernetes names.
resourceID := common.ValueOf(obj.Spec.ResourceID)
if resourceID == "" {
resourceID = common.ValueOf(obj.Spec.Name)
}
if resourceID == "" {
return nil, fmt.Errorf("cannot resolve resource ID")
}
if err := ValidateIAPSettingsID(resourceID); err != nil {
return nil, err
}

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

// ValidateIAPSettingsID validates the IAPSettings resource ID.
func ValidateIAPSettingsID(id string) error {
if id == "" {
return fmt.Errorf("id cannot be empty")
}

parts := strings.Split(id, "/")
if len(parts) < 2 {
return fmt.Errorf("invalid IAP settings ID format %q: must have at least 2 segments (e.g., 'projects/my-project')", id)
}

// Validate root resource type
switch parts[0] {
case "organizations", "folders", "projects":
// Valid root types
default:
return fmt.Errorf("invalid root resource type %q: must be one of: organizations, folders, projects", parts[0])
}

// For organization and folder paths, only expect 2 parts
if parts[0] == "organizations" || parts[0] == "folders" {
if len(parts) != 2 {
return fmt.Errorf("invalid %s IAP settings path %q: must have exactly 2 segments (e.g., '%s/my-id')", parts[0], id, parts[0])
}
return nil
}

// For project paths, validate the structure
if len(parts) > 2 {
if parts[2] != "iap_web" {
return fmt.Errorf("invalid project IAP settings path %q: third segment must be 'iap_web', got %q", id, parts[2])
}
}

switch len(parts) {
case 2: // projects/{project_id}
return nil
case 3: // projects/{project_id}/iap_web
return nil
case 4: // projects/{project_id}/iap_web/compute or compute-{region}
if !strings.HasPrefix(parts[3], "compute") && !strings.HasPrefix(parts[3], "appengine-") {
return fmt.Errorf("invalid IAP web resource type %q: must start with 'compute' or 'appengine-'", parts[3])
}
case 6: // projects/{project_id}/iap_web/(compute|compute-{region}|appengine-{app_id})/service/{service_id}
if parts[4] != "service" {
return fmt.Errorf("invalid service path %q: fifth segment must be 'service', got %q", id, parts[4])
}
case 8: // projects/{project_id}/iap_web/appengine-{app_id}/service/{service_id}/version/{version_id}
if !strings.HasPrefix(parts[3], "appengine-") {
return fmt.Errorf("invalid path %q: version paths are only valid for App Engine resources (must start with 'appengine-')", id)
}
if parts[4] != "service" || parts[6] != "version" {
return fmt.Errorf("invalid App Engine version path %q: must follow pattern 'appengine-{app_id}/service/{service_id}/version/{version_id}'", id)
}
default:
return fmt.Errorf("invalid number of path segments in IAP settings ID %q: got %d segments, expected 2, 3, 4, 6, or 8", id, len(parts))
}

return nil
}
85 changes: 85 additions & 0 deletions apis/iap/v1alpha1/iapsettings_reference.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
// 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 v1alpha1

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 = &IAPSettingsRef{}

// IAPSettingsRef defines the resource reference to IAPSettings, which "External" field
// holds the GCP identifier for the KRM object.
type IAPSettingsRef struct {
// A reference to an externally managed IAPSettings resource.
External string `json:"external,omitempty"`

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

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

// NormalizedExternal provision the "External" value for other resource that depends on IAPSettings.
// If the "External" is given in the other resource's spec.IAPSettingsRef, the given value will be used.
// Otherwise, the "Name" and "Namespace" will be used to query the actual IAPSettings object from the cluster.
func (r *IAPSettingsRef) 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", IAPSettingsGVK.Kind)
}
// From given External
if r.External != "" {
if err := ValidateIAPSettingsID(r.External); err != nil {
return "", fmt.Errorf("invalid format of IAPSettings external %s: %w", r.External, 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(IAPSettingsGVK)
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", IAPSettingsGVK, 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)
}
if err := ValidateIAPSettingsID(actualExternalRef); err != nil {
return "", fmt.Errorf("invalid format of IAPSettings status.externalRef %s: %w", actualExternalRef, err)
}
r.External = actualExternalRef
return r.External, nil
}
112 changes: 112 additions & 0 deletions apis/iap/v1alpha1/iapsettings_types.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
// 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 v1alpha1

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

var IAPSettingsGVK = GroupVersion.WithKind("IAPSettings")

// IAPSettingsSpec defines the desired state of IAPSettings
// +kcc:proto=google.cloud.iap.v1.IapSettings
type IAPSettingsSpec struct {
// The IAPSettings name.
ResourceID *string `json:"resourceID,omitempty"`

// Required. The resource name of the IAP protected resource.
// The name could have the following format:
// organizations/{organization_id}
// folders/{folder_id}
// projects/{projects_id}
// projects/{projects_id}/iap_web
// projects/{projects_id}/iap_web/compute
// projects/{projects_id}/iap_web/compute-{region}
// projects/{projects_id}/iap_web/compute/service/{service_id}
// projects/{projects_id}/iap_web/compute-{region}/service/{service_id}
// projects/{projects_id}/iap_web/appengine-{app_id}
// projects/{projects_id}/iap_web/appengine-{app_id}/service/{service_id}
// projects/{projects_id}/iap_web/appengine-{app_id}/service/{service_id}/version/{version_id}
// +kcc:proto:field=google.cloud.iap.v1.IapSettings.name
// +required
Name *string `json:"name,omitempty"`

// Top level wrapper for all access related setting in IAP
// +kcc:proto:field=google.cloud.iap.v1.IapSettings.access_settings
AccessSettings *AccessSettings `json:"accessSettings,omitempty"`

// Top level wrapper for all application related settings in IAP
// +kcc:proto:field=google.cloud.iap.v1.IapSettings.application_settings
ApplicationSettings *ApplicationSettings `json:"applicationSettings,omitempty"`
}

// IAPSettingsStatus defines the config connector machine state of IAPSettings
type IAPSettingsStatus struct {
/* Conditions represent the latest available observations of the
object's current state. */
Conditions []v1alpha1.Condition `json:"conditions,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"`

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

// ObservedState is the state of the resource as most recently observed in GCP.
// NOTYET: there is no output only field
// ObservedState *IAPSettingsObservedState `json:"observedState,omitempty"`
}

// IAPSettingsSpec defines the desired state of IAPSettings
// +kcc:proto=google.cloud.iap.v1.IapSettings
// IAPSettingsObservedState is the state of the IAPSettings resource as most recently observed in GCP.
// NOTYET: there is no output only field
// type IAPSettingsObservedState struct {
// }

// +genclient
// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object
// TODO(user): make sure the pluralizaiton below is correct
// +kubebuilder:resource:categories=gcp,shortName=gcpiapsettings;gcpiapsettingss
// +kubebuilder:subresource:status
// +kubebuilder:metadata:labels="cnrm.cloud.google.com/managed-by-kcc=true";"cnrm.cloud.google.com/system=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'"

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

// +required
Spec IAPSettingsSpec `json:"spec,omitempty"`
Status IAPSettingsStatus `json:"status,omitempty"`
}

// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object
// IAPSettingsList contains a list of IAPSettings
type IAPSettingsList struct {
metav1.TypeMeta `json:",inline"`
metav1.ListMeta `json:"metadata,omitempty"`
Items []IAPSettings `json:"items"`
}

func init() {
SchemeBuilder.Register(&IAPSettings{}, &IAPSettingsList{})
}
Loading

0 comments on commit 2baa6c5

Please sign in to comment.