Skip to content

Commit

Permalink
Merge pull request #2819 from jasonvigil/workstationconfig
Browse files Browse the repository at this point in the history
feat: Add types for WorkstationConfig
  • Loading branch information
google-oss-prow[bot] authored Nov 13, 2024
2 parents b8c169e + 3b09d9c commit 9521ac6
Show file tree
Hide file tree
Showing 32 changed files with 4,824 additions and 200 deletions.
184 changes: 184 additions & 0 deletions apis/workstations/v1alpha1/cluster_reference.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,184 @@
// 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"

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

// WorkstationClusterRef defines the resource reference to WorkstationCluster, which "External" field
// holds the GCP identifier for the KRM object.
type WorkstationClusterRef struct {
// A reference to an externally managed WorkstationCluster resource.
// Should be in the format "projects/<projectID>/locations/<location>/workstationClusters/<workstationclusterID>".
External string `json:"external,omitempty"`

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

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

// NormalizedExternal provision the "External" value for other resource that depends on WorkstationCluster.
// If the "External" is given in the other resource's spec.WorkstationClusterRef, the given value will be used.
// Otherwise, the "Name" and "Namespace" will be used to query the actual WorkstationCluster object from the cluster.
func (r *WorkstationClusterRef) 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", WorkstationClusterGVK.Kind)
}
// From given External
if r.External != "" {
if _, _, err := parseWorkstationClusterExternal(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(WorkstationClusterGVK)
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", WorkstationClusterGVK, 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
}

// New builds a WorkstationClusterRef from the Config Connector WorkstationCluster object.
func NewWorkstationClusterRef(ctx context.Context, reader client.Reader, obj *WorkstationCluster) (*WorkstationClusterRef, error) {
id := &WorkstationClusterRef{}

// Get Parent
projectRef, err := refsv1beta1.ResolveProject(ctx, reader, obj, &obj.Spec.ProjectRef)
if err != nil {
return nil, err
}
projectID := projectRef.ProjectID
if projectID == "" {
return nil, fmt.Errorf("cannot resolve project")
}
location := obj.Spec.Location
if location == "" {
return nil, fmt.Errorf("cannot resolve location")
}

// Get desired ID
resourceID := valueOf(obj.Spec.ResourceID)
if resourceID == "" {
resourceID = obj.GetName()
}
if resourceID == "" {
return nil, fmt.Errorf("cannot resolve resource ID")
}

// Use approved External
externalRef := valueOf(obj.Status.ExternalRef)
if externalRef == "" {
parent := &WorkstationClusterParent{ProjectID: projectID, Location: location}
id.External = asWorkstationClusterExternal(parent, resourceID)
return id, nil
}

// Validate desired with actual
actualParent, actualResourceID, err := parseWorkstationClusterExternal(externalRef)
if err != nil {
return nil, err
}
if actualParent.ProjectID != projectID {
return nil, fmt.Errorf("spec.projectRef changed, expect %s, got %s", actualParent.ProjectID, projectID)
}
if actualParent.Location != location {
return nil, fmt.Errorf("spec.location changed, expect %s, got %s", actualParent.Location, location)
}
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)
}
id.External = externalRef
return id, nil
}

func (r *WorkstationClusterRef) Parent() (*WorkstationClusterParent, error) {
if r.External != "" {
parent, _, err := parseWorkstationClusterExternal(r.External)
if err != nil {
return nil, err
}
return parent, nil
}
return nil, fmt.Errorf("WorkstationClusterRef not initialized from `NewWorkstationClusterRef` or `NormalizedExternal`")
}

type WorkstationClusterParent struct {
ProjectID string
Location string
}

func (p *WorkstationClusterParent) String() string {
return "projects/" + p.ProjectID + "/locations/" + p.Location
}

func asWorkstationClusterExternal(parent *WorkstationClusterParent, resourceID string) (external string) {
return parent.String() + "/workstationConfigs/" + resourceID
}

func parseWorkstationClusterExternal(external string) (parent *WorkstationClusterParent, resourceID string, err error) {
external = strings.TrimPrefix(external, "/")
tokens := strings.Split(external, "/")
if len(tokens) != 6 || tokens[0] != "projects" || tokens[2] != "locations" || tokens[4] != "workstationClusters" {
return nil, "", fmt.Errorf("format of WorkstationCluster external=%q was not known (use projects/<projectID>/locations/<location>/workstationClusters/<workstationclusterID>)", external)
}
parent = &WorkstationClusterParent{
ProjectID: tokens[1],
Location: tokens[3],
}
resourceID = tokens[5]
return parent, resourceID, nil
}

func valueOf[T any](t *T) T {
var zeroVal T
if t == nil {
return zeroVal
}
return *t
}
35 changes: 3 additions & 32 deletions apis/workstations/v1alpha1/cluster_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -45,13 +45,13 @@ type WorkstationClusterSpec struct {
DisplayName *string `json:"displayName,omitempty"`

// Optional. Client-specified annotations.
Annotations []WorkstationClusterAnnotation `json:"annotations,omitempty"`
Annotations []WorkstationAnnotation `json:"annotations,omitempty"`

// Optional.
// [Labels](https://cloud.google.com/workstations/docs/label-resources) that
// are applied to the workstation cluster and that are also propagated to the
// underlying Compute Engine resources.
Labels []WorkstationClusterLabel `json:"labels,omitempty"`
Labels []WorkstationLabel `json:"labels,omitempty"`

// Immutable. Reference to the Compute Engine network in which instances associated
// with this workstation cluster will be created.
Expand All @@ -68,22 +68,6 @@ type WorkstationClusterSpec struct {
PrivateClusterConfig *WorkstationCluster_PrivateClusterConfig `json:"privateClusterConfig,omitempty"`
}

type WorkstationClusterAnnotation struct {
// Key for the annotation.
Key string `json:"key,omitempty"`

// Value for the annotation.
Value string `json:"value,omitempty"`
}

type WorkstationClusterLabel struct {
// Key for the annotation.
Key string `json:"key,omitempty"`

// Value for the annotation.
Value string `json:"value,omitempty"`
}

// +kcc:proto=google.cloud.workstations.v1.WorkstationCluster.PrivateClusterConfig
type WorkstationCluster_PrivateClusterConfig struct {
// Immutable. Whether Workstations endpoint is private.
Expand Down Expand Up @@ -164,20 +148,7 @@ type WorkstationClusterObservedState struct {

// Output only. Status conditions describing the workstation cluster's current
// state.
GCPConditions []WorkstationClusterGCPCondition `json:"gcpConditions,omitempty"`
}

// +kcc:proto=google.rpc.Status
type WorkstationClusterGCPCondition struct {
// The status code, which should be an enum value of
// [google.rpc.Code][google.rpc.Code].
Code *int32 `json:"code,omitempty"`

// A developer-facing error message, which should be in English. Any
// user-facing error message should be localized and sent in the
// [google.rpc.Status.details][google.rpc.Status.details] field, or localized
// by the client.
Message *string `json:"message,omitempty"`
GCPConditions []WorkstationServiceGCPCondition `json:"gcpConditions,omitempty"`
}

// +genclient
Expand Down
Loading

0 comments on commit 9521ac6

Please sign in to comment.