Repository: GoogleCloudPlatform/k8s-config-connector
Pull request #3181: Ftr logging link alpha
Author: login: tylerreidwaze
Labels: name: enhancement
description: New feature or request
name: lgtm
name: approved
Created: 2024-11-14T19:50:15Z | Merged: 2025-03-21T21:50:43Z

### Change description

This PR adds LoggingLinks as a KCC Resource

### Tests you have done

- [x] Run `make ready-pr` to ensure this PR is ready for review.
- [x] Perform necessary E2E testing for changed resources.



Diff:
diff --git a/apis/logging/v1alpha1/doc.go b/apis/logging/v1alpha1/doc.go
new file mode 100644
index 00000000000..e8683c47b26
--- /dev/null
+++ b/apis/logging/v1alpha1/doc.go
@@ -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.logging.v2
+package v1alpha1
diff --git a/apis/logging/v1alpha1/groupversion_info.go b/apis/logging/v1alpha1/groupversion_info.go
new file mode 100644
index 00000000000..8e8c421dfe2
--- /dev/null
+++ b/apis/logging/v1alpha1/groupversion_info.go
@@ -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=logging.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: "logging.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
+)
diff --git a/apis/logging/v1alpha1/link_identity.go b/apis/logging/v1alpha1/link_identity.go
new file mode 100644
index 00000000000..b9d00a08d76
--- /dev/null
+++ b/apis/logging/v1alpha1/link_identity.go
@@ -0,0 +1,118 @@
+// 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 v1alpha1
+
+import (
+	"context"
+	"fmt"
+	"strings"
+
+	"github.com/GoogleCloudPlatform/k8s-config-connector/apis/common"
+	refsv1beta1 "github.com/GoogleCloudPlatform/k8s-config-connector/apis/refs/v1beta1"
+	"sigs.k8s.io/controller-runtime/pkg/client"
+)
+
+// LinkIdentity defines the resource reference to LoggingLink, which "External" field
+// holds the GCP identifier for the KRM object.
+type LinkIdentity struct {
+	parent *LinkParent
+	id     string
+}
+
+func (i *LinkIdentity) String() string {
+	return i.parent.String() + "/links/" + i.id
+}
+
+func (i *LinkIdentity) ID() string {
+	return i.id
+}
+
+func (i *LinkIdentity) Parent() *LinkParent {
+	return i.parent
+}
+
+type LinkParent struct {
+	ProjectID          string
+	Location           string
+	LoggingLogBucketID string
+}
+
+func (p *LinkParent) String() string {
+	return "projects/" + p.ProjectID + "/locations/" + p.Location + "/buckets/" + p.LoggingLogBucketID
+}
+
+// New builds a LinkIdentity from the Config Connector Link object.
+func NewLinkIdentity(ctx context.Context, reader client.Reader, obj *LoggingLink) (*LinkIdentity, error) {
+
+	// Get Parent
+	bucketRef, err := refsv1beta1.ResolveLoggingLogBucketRef(ctx, reader, obj, obj.Spec.LoggingLogBucketRef)
+	if err != nil {
+		return nil, err
+	}
+	projectID := bucketRef.ProjectID
+	if projectID == "" {
+		return nil, fmt.Errorf("cannot resolve project")
+	}
+	location := bucketRef.Location
+	bucketID := bucketRef.LoggingLogBucketID
+
+	// 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
+		actualParent, actualResourceID, err := ParseLinkExternal(externalRef)
+		if err != nil {
+			return nil, err
+		}
+		if actualParent.String() != bucketRef.String() {
+			return nil, fmt.Errorf("actualParent changed, expect %s, got %s", actualParent.String(), bucketRef.String())
+		}
+		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 &LinkIdentity{
+		parent: &LinkParent{
+			ProjectID:          projectID,
+			Location:           location,
+			LoggingLogBucketID: bucketID,
+		},
+		id: resourceID,
+	}, nil
+}
+
+func ParseLinkExternal(external string) (parent *LinkParent, resourceID string, err error) {
+	tokens := strings.Split(external, "/")
+	if len(tokens) != 8 || tokens[0] != "projects" || tokens[2] != "locations" || tokens[4] != "buckets" || tokens[6] != "links" {
+		return nil, "", fmt.Errorf("format of LoggingLink external=%q was not known (use projects/{{projectID}}/locations/{{location}}/buckets/{{bucketID}}/links/{{linkID}})", external)
+	}
+	parent = &LinkParent{
+		ProjectID:          tokens[1],
+		Location:           tokens[3],
+		LoggingLogBucketID: tokens[5],
+	}
+	resourceID = tokens[7]
+	return parent, resourceID, nil
+}
diff --git a/apis/logging/v1alpha1/link_reference.go b/apis/logging/v1alpha1/link_reference.go
new file mode 100644
index 00000000000..3b6a4d5a3a0
--- /dev/null
+++ b/apis/logging/v1alpha1/link_reference.go
@@ -0,0 +1,141 @@
+// 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 = &LoggingLinkRef{}
+
+// LoggingLinkRef defines the resource reference to LoggingLink, which "External" field
+// holds the GCP identifier for the KRM object.
+type LoggingLinkRef struct {
+	// A reference to an externally managed LoggingLink resource.
+	// Should be in the format "projects/<projectID>/locations/<location>/buckets/<bucketID>/links/<linkID>".
+	External string `json:"external,omitempty"`
+
+	// The name of a LoggingLink resource.
+	Name string `json:"name,omitempty"`
+
+	// The namespace of a LoggingLink resource.
+	Namespace string `json:"namespace,omitempty"`
+
+	parent *LoggingLinkParent
+}
+
+func (l *LoggingLinkRef) String() string {
+	return l.External
+}
+
+// NormalizedExternal provision the "External" value for other resource that depends on LoggingLink.
+// If the "External" is given in the other resource's spec.LoggingLinkRef, the given value will be used.
+// Otherwise, the "Name" and "Namespace" will be used to query the actual LoggingLink object from the cluster.
+func (r *LoggingLinkRef) 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", LoggingLinkGVK.Kind)
+	}
+	// From given External
+	if r.External != "" {
+		if _, _, err := parseLoggingLinkExternal(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(LoggingLinkGVK)
+	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", LoggingLinkGVK, 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
+}
+
+func (r *LoggingLinkRef) Parent() (*LoggingLinkParent, error) {
+	if r.parent != nil {
+		return r.parent, nil
+	}
+	if r.External != "" {
+		parent, _, err := parseLoggingLinkExternal(r.External)
+		if err != nil {
+			return nil, err
+		}
+		return parent, nil
+	}
+	return nil, fmt.Errorf("LoggingLinkRef not initialized from `NewLoggingLinkRef` or `NormalizedExternal`")
+}
+
+type LoggingLinkParent struct {
+	ProjectID string
+	Location  string
+	LogBucket string
+}
+
+func (p *LoggingLinkParent) String() string {
+	return "projects/" + p.ProjectID + "/locations/" + p.Location + "/buckets/" + p.LogBucket
+}
+
+func asLoggingLinkExternal(parent *LoggingLinkParent, resourceID string) (external string) {
+	return parent.String() + "/links/" + resourceID
+}
+
+func parseLoggingLinkExternal(external string) (parent *LoggingLinkParent, resourceID string, err error) {
+	external = strings.TrimPrefix(external, "/")
+	tokens := strings.Split(external, "/")
+	if len(tokens) != 8 || tokens[0] != "projects" || tokens[2] != "locations" || tokens[4] != "buckets" || tokens[6] != "links" {
+		return nil, "", fmt.Errorf("format of LoggingLink external=%q was not known (use projects/<projectId>/locations/<location>/buckets/<bucketID>/links/<linkID>)", external)
+	}
+	parent = &LoggingLinkParent{
+		ProjectID: tokens[1],
+		Location:  tokens[3],
+		LogBucket: tokens[5],
+	}
+	resourceID = tokens[7]
+	return parent, resourceID, nil
+}
+
+func valueOf[T any](t *T) T {
+	var zeroVal T
+	if t == nil {
+		return zeroVal
+	}
+	return *t
+}
diff --git a/apis/logging/v1alpha1/link_types.go b/apis/logging/v1alpha1/link_types.go
new file mode 100644
index 00000000000..9d81ba2e5fb
--- /dev/null
+++ b/apis/logging/v1alpha1/link_types.go
@@ -0,0 +1,109 @@
+// 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 (
+	refs "github.com/GoogleCloudPlatform/k8s-config-connector/apis/refs/v1beta1"
+	"github.com/GoogleCloudPlatform/k8s-config-connector/pkg/apis/k8s/v1alpha1"
+	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+)
+
+var LoggingLinkGVK = GroupVersion.WithKind("LoggingLink")
+
+// LoggingLinkSpec defines the desired state of LoggingLink
+// +kcc:proto=google.logging.v2.Link
+type LoggingLinkSpec struct {
+	// Immutable.
+	// The LoggingLink name. If not given, the metadata.name will be used.
+	ResourceID *string `json:"resourceID,omitempty"`
+
+	// Describes this link.
+	//  The maximum length of the description is 8000 characters.
+	Description *string `json:"description,omitempty"`
+
+	// The LoggingLogBucket that this Link is associated with
+	LoggingLogBucketRef *refs.LoggingLogBucketRef `json:"loggingLogBucketRef,omitempty"`
+}
+
+// LoggingLinkStatus defines the config connector machine state of LoggingLink
+type LoggingLinkStatus 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 LoggingLink resource in GCP.
+	ExternalRef *string `json:"externalRef,omitempty"`
+
+	// ObservedState is the state of the resource as most recently observed in GCP.
+	ObservedState *LoggingLinkObservedState `json:"observedState,omitempty"`
+}
+
+// LoggingLinkObservedState is the state of the LoggingLink resource as most recently observed in GCP.
+type LoggingLinkObservedState struct {
+
+	// +optional
+	// Output only. The creation timestamp of the link.
+	CreateTime *string `json:"createTime,omitempty"`
+
+	// the lifecycle state might be something more complicated
+	// this is an ENUM, should be safe to use a string
+
+	// Output only. The resource lifecycle state.
+	LifecycleState *string `json:"lifecycleState,omitempty"`
+
+	// this field is just a string, but its an object(string)
+	// https://github.com/googleapis/googleapis/blob/master/google/logging/v2/logging_config.proto#L1063
+
+	// The information of a BigQuery Dataset. When a link is created, a BigQuery dataset is created
+	// along with it, in the same project as the LogBucket it's linked to. This dataset will also have
+	// BigQuery Views corresponding to the LogViews in the bucket.
+	BigQueryDataset *BigQueryDatasetObservedState `json:"bigQueryDataset,omitempty"`
+}
+
+// +genclient
+// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object
+// +kubebuilder:resource:categories=gcp,shortName=gcplogginglink;gcplogginglinks
+// +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'"
+
+// LoggingLink is the Schema for the LoggingLink API
+// +k8s:openapi-gen=true
+type LoggingLink struct {
+	metav1.TypeMeta   `json:",inline"`
+	metav1.ObjectMeta `json:"metadata,omitempty"`
+
+	// +required
+	Spec   LoggingLinkSpec   `json:"spec,omitempty"`
+	Status LoggingLinkStatus `json:"status,omitempty"`
+}
+
+// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object
+// LoggingLinkList contains a list of LoggingLink
+type LoggingLinkList struct {
+	metav1.TypeMeta `json:",inline"`
+	metav1.ListMeta `json:"metadata,omitempty"`
+	Items           []LoggingLink `json:"items"`
+}
+
+func init() {
+	SchemeBuilder.Register(&LoggingLink{}, &LoggingLinkList{})
+}
diff --git a/apis/logging/v1alpha1/types.generated.go b/apis/logging/v1alpha1/types.generated.go
new file mode 100644
index 00000000000..7643a8a512f
--- /dev/null
+++ b/apis/logging/v1alpha1/types.generated.go
@@ -0,0 +1,32 @@
+// 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 v1alpha1
+
+// +kcc:proto=google.logging.v2.BigQueryDataset
+type BigQueryDataset struct {
+}
+
+// +kcc:proto=google.logging.v2.BigQueryDataset
+type BigQueryDatasetObservedState struct {
+	// Output only. The full resource name of the BigQuery dataset. The DATASET_ID
+	//  will match the ID of the link, so the link must match the naming
+	//  restrictions of BigQuery datasets (alphanumeric characters and underscores
+	//  only).
+	//
+	//  The dataset will have a resource path of
+	//    "bigquery.googleapis.com/projects/[PROJECT_ID]/datasets/[DATASET_ID]"
+	// +kcc:proto:field=google.logging.v2.BigQueryDataset.dataset_id
+	DatasetID *string `json:"datasetID,omitempty"`
+}
diff --git a/apis/logging/v1alpha1/zz_generated.deepcopy.go b/apis/logging/v1alpha1/zz_generated.deepcopy.go
new file mode 100644
index 00000000000..3533d738d34
--- /dev/null
+++ b/apis/logging/v1alpha1/zz_generated.deepcopy.go
@@ -0,0 +1,284 @@
+//go:build !ignore_autogenerated
+
+// Copyright 2020 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.
+
+// Code generated by controller-gen. DO NOT EDIT.
+
+package v1alpha1
+
+import (
+	"github.com/GoogleCloudPlatform/k8s-config-connector/apis/refs/v1beta1"
+	k8sv1alpha1 "github.com/GoogleCloudPlatform/k8s-config-connector/pkg/apis/k8s/v1alpha1"
+	runtime "k8s.io/apimachinery/pkg/runtime"
+)
+
+// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
+func (in *BigQueryDataset) DeepCopyInto(out *BigQueryDataset) {
+	*out = *in
+}
+
+// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new BigQueryDataset.
+func (in *BigQueryDataset) DeepCopy() *BigQueryDataset {
+	if in == nil {
+		return nil
+	}
+	out := new(BigQueryDataset)
+	in.DeepCopyInto(out)
+	return out
+}
+
+// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
+func (in *BigQueryDatasetObservedState) DeepCopyInto(out *BigQueryDatasetObservedState) {
+	*out = *in
+	if in.DatasetID != nil {
+		in, out := &in.DatasetID, &out.DatasetID
+		*out = new(string)
+		**out = **in
+	}
+}
+
+// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new BigQueryDatasetObservedState.
+func (in *BigQueryDatasetObservedState) DeepCopy() *BigQueryDatasetObservedState {
+	if in == nil {
+		return nil
+	}
+	out := new(BigQueryDatasetObservedState)
+	in.DeepCopyInto(out)
+	return out
+}
+
+// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
+func (in *LinkIdentity) DeepCopyInto(out *LinkIdentity) {
+	*out = *in
+	if in.parent != nil {
+		in, out := &in.parent, &out.parent
+		*out = new(LinkParent)
+		**out = **in
+	}
+}
+
+// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new LinkIdentity.
+func (in *LinkIdentity) DeepCopy() *LinkIdentity {
+	if in == nil {
+		return nil
+	}
+	out := new(LinkIdentity)
+	in.DeepCopyInto(out)
+	return out
+}
+
+// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
+func (in *LinkParent) DeepCopyInto(out *LinkParent) {
+	*out = *in
+}
+
+// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new LinkParent.
+func (in *LinkParent) DeepCopy() *LinkParent {
+	if in == nil {
+		return nil
+	}
+	out := new(LinkParent)
+	in.DeepCopyInto(out)
+	return out
+}
+
+// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
+func (in *LoggingLink) DeepCopyInto(out *LoggingLink) {
+	*out = *in
+	out.TypeMeta = in.TypeMeta
+	in.ObjectMeta.DeepCopyInto(&out.ObjectMeta)
+	in.Spec.DeepCopyInto(&out.Spec)
+	in.Status.DeepCopyInto(&out.Status)
+}
+
+// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new LoggingLink.
+func (in *LoggingLink) DeepCopy() *LoggingLink {
+	if in == nil {
+		return nil
+	}
+	out := new(LoggingLink)
+	in.DeepCopyInto(out)
+	return out
+}
+
+// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.
+func (in *LoggingLink) DeepCopyObject() runtime.Object {
+	if c := in.DeepCopy(); c != nil {
+		return c
+	}
+	return nil
+}
+
+// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
+func (in *LoggingLinkList) DeepCopyInto(out *LoggingLinkList) {
+	*out = *in
+	out.TypeMeta = in.TypeMeta
+	in.ListMeta.DeepCopyInto(&out.ListMeta)
+	if in.Items != nil {
+		in, out := &in.Items, &out.Items
+		*out = make([]LoggingLink, len(*in))
+		for i := range *in {
+			(*in)[i].DeepCopyInto(&(*out)[i])
+		}
+	}
+}
+
+// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new LoggingLinkList.
+func (in *LoggingLinkList) DeepCopy() *LoggingLinkList {
+	if in == nil {
+		return nil
+	}
+	out := new(LoggingLinkList)
+	in.DeepCopyInto(out)
+	return out
+}
+
+// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.
+func (in *LoggingLinkList) DeepCopyObject() runtime.Object {
+	if c := in.DeepCopy(); c != nil {
+		return c
+	}
+	return nil
+}
+
+// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
+func (in *LoggingLinkObservedState) DeepCopyInto(out *LoggingLinkObservedState) {
+	*out = *in
+	if in.CreateTime != nil {
+		in, out := &in.CreateTime, &out.CreateTime
+		*out = new(string)
+		**out = **in
+	}
+	if in.LifecycleState != nil {
+		in, out := &in.LifecycleState, &out.LifecycleState
+		*out = new(string)
+		**out = **in
+	}
+	if in.BigQueryDataset != nil {
+		in, out := &in.BigQueryDataset, &out.BigQueryDataset
+		*out = new(BigQueryDatasetObservedState)
+		(*in).DeepCopyInto(*out)
+	}
+}
+
+// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new LoggingLinkObservedState.
+func (in *LoggingLinkObservedState) DeepCopy() *LoggingLinkObservedState {
+	if in == nil {
+		return nil
+	}
+	out := new(LoggingLinkObservedState)
+	in.DeepCopyInto(out)
+	return out
+}
+
+// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
+func (in *LoggingLinkParent) DeepCopyInto(out *LoggingLinkParent) {
+	*out = *in
+}
+
+// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new LoggingLinkParent.
+func (in *LoggingLinkParent) DeepCopy() *LoggingLinkParent {
+	if in == nil {
+		return nil
+	}
+	out := new(LoggingLinkParent)
+	in.DeepCopyInto(out)
+	return out
+}
+
+// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
+func (in *LoggingLinkRef) DeepCopyInto(out *LoggingLinkRef) {
+	*out = *in
+	if in.parent != nil {
+		in, out := &in.parent, &out.parent
+		*out = new(LoggingLinkParent)
+		**out = **in
+	}
+}
+
+// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new LoggingLinkRef.
+func (in *LoggingLinkRef) DeepCopy() *LoggingLinkRef {
+	if in == nil {
+		return nil
+	}
+	out := new(LoggingLinkRef)
+	in.DeepCopyInto(out)
+	return out
+}
+
+// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
+func (in *LoggingLinkSpec) DeepCopyInto(out *LoggingLinkSpec) {
+	*out = *in
+	if in.ResourceID != nil {
+		in, out := &in.ResourceID, &out.ResourceID
+		*out = new(string)
+		**out = **in
+	}
+	if in.Description != nil {
+		in, out := &in.Description, &out.Description
+		*out = new(string)
+		**out = **in
+	}
+	if in.LoggingLogBucketRef != nil {
+		in, out := &in.LoggingLogBucketRef, &out.LoggingLogBucketRef
+		*out = new(v1beta1.LoggingLogBucketRef)
+		**out = **in
+	}
+}
+
+// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new LoggingLinkSpec.
+func (in *LoggingLinkSpec) DeepCopy() *LoggingLinkSpec {
+	if in == nil {
+		return nil
+	}
+	out := new(LoggingLinkSpec)
+	in.DeepCopyInto(out)
+	return out
+}
+
+// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
+func (in *LoggingLinkStatus) DeepCopyInto(out *LoggingLinkStatus) {
+	*out = *in
+	if in.Conditions != nil {
+		in, out := &in.Conditions, &out.Conditions
+		*out = make([]k8sv1alpha1.Condition, len(*in))
+		copy(*out, *in)
+	}
+	if in.ObservedGeneration != nil {
+		in, out := &in.ObservedGeneration, &out.ObservedGeneration
+		*out = new(int64)
+		**out = **in
+	}
+	if in.ExternalRef != nil {
+		in, out := &in.ExternalRef, &out.ExternalRef
+		*out = new(string)
+		**out = **in
+	}
+	if in.ObservedState != nil {
+		in, out := &in.ObservedState, &out.ObservedState
+		*out = new(LoggingLinkObservedState)
+		(*in).DeepCopyInto(*out)
+	}
+}
+
+// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new LoggingLinkStatus.
+func (in *LoggingLinkStatus) DeepCopy() *LoggingLinkStatus {
+	if in == nil {
+		return nil
+	}
+	out := new(LoggingLinkStatus)
+	in.DeepCopyInto(out)
+	return out
+}
diff --git a/apis/refs/v1beta1/logbucketref.go b/apis/refs/v1beta1/logbucketref.go
new file mode 100644
index 00000000000..2469e62cfe8
--- /dev/null
+++ b/apis/refs/v1beta1/logbucketref.go
@@ -0,0 +1,118 @@
+// 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 v1beta1
+
+import (
+	"context"
+	"fmt"
+	"strings"
+
+	apierrors "k8s.io/apimachinery/pkg/api/errors"
+	"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
+	"k8s.io/apimachinery/pkg/runtime/schema"
+	"k8s.io/apimachinery/pkg/types"
+	"sigs.k8s.io/controller-runtime/pkg/client"
+)
+
+type LoggingLogBucketRef struct {
+	// Should be in the format "projects/{{projectID}}/locations/{{location}}/buckets/{{bucketID}}".
+	/* The LoggingLogBucket selfLink, when not managed by Config Connector. */
+	External string `json:"external,omitempty"`
+	/* The `name` field of a `LoggingLogBucket` resource. */
+	Name string `json:"name,omitempty"`
+	/* The `namespace` field of a `LoggingLogBucket` resource. */
+	Namespace string `json:"namespace,omitempty"`
+}
+
+type LoggingLogBucket struct {
+	ProjectID          string
+	Location           string
+	LoggingLogBucketID string
+}
+
+func (s *LoggingLogBucket) String() string {
+	return "projects/" + s.ProjectID + "/locations/" + s.Location + "/buckets/" + s.LoggingLogBucketID
+}
+
+func ResolveLoggingLogBucketRef(ctx context.Context, reader client.Reader, obj client.Object, ref *LoggingLogBucketRef) (*LoggingLogBucket, error) {
+	if ref == nil {
+		return nil, nil
+	}
+
+	if ref.Name == "" && ref.External == "" {
+		return nil, fmt.Errorf("must specify either name or external on loggingLogBucketRef")
+	}
+	if ref.External != "" && ref.Name != "" {
+		return nil, fmt.Errorf("cannot specify both spec.loggingLogBucketRef.name and spec.loggingLogBucketRef.external")
+	}
+
+	if ref.External != "" {
+		// External should be in the `projects/[projectID]/locations/[Location]/buckets/[bucketID]` format.
+		tokens := strings.Split(ref.External, "/")
+		if len(tokens) == 6 && tokens[0] == "projects" && tokens[2] == "locations" && tokens[4] == "buckets" {
+			return &LoggingLogBucket{
+				ProjectID:          tokens[1],
+				Location:           tokens[3],
+				LoggingLogBucketID: tokens[5],
+			}, nil
+		}
+		return nil, fmt.Errorf("format of LoggingLogBucket external=%q was not known (use projects/<projectId>/locations/<location>/buckets/<bucketID>)", ref.External)
+	}
+
+	key := types.NamespacedName{
+		Namespace: ref.Namespace,
+		Name:      ref.Name,
+	}
+	if key.Namespace == "" {
+		key.Namespace = obj.GetNamespace()
+	}
+
+	exchange := &unstructured.Unstructured{}
+	exchange.SetGroupVersionKind(schema.GroupVersionKind{
+		Group:   "logging.cnrm.cloud.google.com",
+		Version: "v1beta1",
+		Kind:    "LoggingLogBucket",
+	})
+	if err := reader.Get(ctx, key, exchange); err != nil {
+		if apierrors.IsNotFound(err) {
+			return nil, fmt.Errorf("referenced LoggingLogBucket %v not found", key)
+		}
+		return nil, fmt.Errorf("error reading referenced LoggingLogBucket %v: %w", key, err)
+	}
+
+	resourceID, _, err := unstructured.NestedString(exchange.Object, "spec", "resourceID")
+	if err != nil {
+		return nil, fmt.Errorf("reading spec.resourceID from LoggingLogBucket %s/%s: %w", exchange.GetNamespace(), exchange.GetName(), err)
+	}
+	if resourceID == "" {
+		resourceID = exchange.GetName()
+	}
+
+	location, _, err := unstructured.NestedString(exchange.Object, "spec", "location")
+	if err != nil {
+		return nil, fmt.Errorf("reading spec.region from LoggingLogBucket %s/%s: %w", exchange.GetNamespace(), exchange.GetName(), err)
+	}
+
+	projectID, err := ResolveProjectID(ctx, reader, exchange)
+	if err != nil {
+		return nil, err
+	}
+
+	return &LoggingLogBucket{
+		ProjectID:          projectID,
+		Location:           location,
+		LoggingLogBucketID: resourceID,
+	}, nil
+}
diff --git a/config/crds/resources/apiextensions.k8s.io_v1_customresourcedefinition_logginglinks.logging.cnrm.cloud.google.com.yaml b/config/crds/resources/apiextensions.k8s.io_v1_customresourcedefinition_logginglinks.logging.cnrm.cloud.google.com.yaml
new file mode 100644
index 00000000000..28677c14c27
--- /dev/null
+++ b/config/crds/resources/apiextensions.k8s.io_v1_customresourcedefinition_logginglinks.logging.cnrm.cloud.google.com.yaml
@@ -0,0 +1,176 @@
+apiVersion: apiextensions.k8s.io/v1
+kind: CustomResourceDefinition
+metadata:
+  annotations:
+    cnrm.cloud.google.com/version: 0.0.0-dev
+  creationTimestamp: null
+  labels:
+    cnrm.cloud.google.com/managed-by-kcc: "true"
+    cnrm.cloud.google.com/system: "true"
+  name: logginglinks.logging.cnrm.cloud.google.com
+spec:
+  group: logging.cnrm.cloud.google.com
+  names:
+    categories:
+    - gcp
+    kind: LoggingLink
+    listKind: LoggingLinkList
+    plural: logginglinks
+    shortNames:
+    - gcplogginglink
+    - gcplogginglinks
+    singular: logginglink
+  preserveUnknownFields: false
+  scope: Namespaced
+  versions:
+  - additionalPrinterColumns:
+    - jsonPath: .metadata.creationTimestamp
+      name: Age
+      type: date
+    - description: When 'True', the most recent reconcile of the resource succeeded
+      jsonPath: .status.conditions[?(@.type=='Ready')].status
+      name: Ready
+      type: string
+    - description: The reason for the value in 'Ready'
+      jsonPath: .status.conditions[?(@.type=='Ready')].reason
+      name: Status
+      type: string
+    - description: The last transition time for the value in 'Status'
+      jsonPath: .status.conditions[?(@.type=='Ready')].lastTransitionTime
+      name: Status Age
+      type: date
+    name: v1alpha1
+    schema:
+      openAPIV3Schema:
+        description: LoggingLink is the Schema for the LoggingLink API
+        properties:
+          apiVersion:
+            description: 'APIVersion defines the versioned schema of this representation
+              of an object. Servers should convert recognized schemas to the latest
+              internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources'
+            type: string
+          kind:
+            description: 'Kind is a string value representing the REST resource this
+              object represents. Servers may infer this from the endpoint the client
+              submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds'
+            type: string
+          metadata:
+            type: object
+          spec:
+            description: LoggingLinkSpec defines the desired state of LoggingLink
+            properties:
+              description:
+                description: Describes this link. The maximum length of the description
+                  is 8000 characters.
+                type: string
+              loggingLogBucketRef:
+                description: The LoggingLogBucket that this Link is associated with
+                oneOf:
+                - not:
+                    required:
+                    - external
+                  required:
+                  - name
+                - not:
+                    anyOf:
+                    - required:
+                      - name
+                    - required:
+                      - namespace
+                  required:
+                  - external
+                properties:
+                  external:
+                    description: Should be in the format "projects/{{projectID}}/locations/{{location}}/buckets/{{bucketID}}".
+                      The LoggingLogBucket selfLink, when not managed by Config Connector.
+                    type: string
+                  name:
+                    description: The `name` field of a `LoggingLogBucket` resource.
+                    type: string
+                  namespace:
+                    description: The `namespace` field of a `LoggingLogBucket` resource.
+                    type: string
+                type: object
+              resourceID:
+                description: Immutable. The LoggingLink name. If not given, the metadata.name
+                  will be used.
+                type: string
+            type: object
+          status:
+            description: LoggingLinkStatus defines the config connector machine state
+              of LoggingLink
+            properties:
+              conditions:
+                description: Conditions represent the latest available observations
+                  of the object's current state.
+                items:
+                  properties:
+                    lastTransitionTime:
+                      description: Last time the condition transitioned from one status
+                        to another.
+                      type: string
+                    message:
+                      description: Human-readable message indicating details about
+                        last transition.
+                      type: string
+                    reason:
+                      description: Unique, one-word, CamelCase reason for the condition's
+                        last transition.
+                      type: string
+                    status:
+                      description: Status is the status of the condition. Can be True,
+                        False, Unknown.
+                      type: string
+                    type:
+                      description: Type is the type of the condition.
+                      type: string
+                  type: object
+                type: array
+              externalRef:
+                description: A unique specifier for the LoggingLink resource in GCP.
+                type: string
+              observedGeneration:
+                description: 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.
+                format: int64
+                type: integer
+              observedState:
+                description: ObservedState is the state of the resource as most recently
+                  observed in GCP.
+                properties:
+                  bigQueryDataset:
+                    description: The information of a BigQuery Dataset. When a link
+                      is created, a BigQuery dataset is created along with it, in
+                      the same project as the LogBucket it's linked to. This dataset
+                      will also have BigQuery Views corresponding to the LogViews
+                      in the bucket.
+                    properties:
+                      datasetID:
+                        description: |-
+                          Output only. The full resource name of the BigQuery dataset. The DATASET_ID
+                           will match the ID of the link, so the link must match the naming
+                           restrictions of BigQuery datasets (alphanumeric characters and underscores
+                           only).
+
+                           The dataset will have a resource path of
+                             "bigquery.googleapis.com/projects/[PROJECT_ID]/datasets/[DATASET_ID]"
+                        type: string
+                    type: object
+                  createTime:
+                    description: Output only. The creation timestamp of the link.
+                    type: string
+                  lifecycleState:
+                    description: Output only. The resource lifecycle state.
+                    type: string
+                type: object
+            type: object
+        required:
+        - spec
+        type: object
+    served: true
+    storage: true
+    subresources:
+      status: {}
diff --git a/config/tests/samples/create/harness.go b/config/tests/samples/create/harness.go
index 9c180fb1386..d59dc0dd73e 100644
--- a/config/tests/samples/create/harness.go
+++ b/config/tests/samples/create/harness.go
@@ -853,6 +853,7 @@ func MaybeSkip(t *testing.T, name string, resources []*unstructured.Unstructured
 			case schema.GroupKind{Group: "logging.cnrm.cloud.google.com", Kind: "LoggingLogBucket"}:
 			case schema.GroupKind{Group: "logging.cnrm.cloud.google.com", Kind: "LoggingLogSink"}:
 			case schema.GroupKind{Group: "logging.cnrm.cloud.google.com", Kind: "LoggingLogView"}:
+			//case schema.GroupKind{Group: "logging.cnrm.cloud.google.com", Kind: "LoggingLink"}:
 
 			case schema.GroupKind{Group: "managedkafka.cnrm.cloud.google.com", Kind: "ManagedKafkaCluster"}:
 			case schema.GroupKind{Group: "managedkafka.cnrm.cloud.google.com", Kind: "ManagedKafkaTopic"}:
diff --git a/go.mod b/go.mod
index 6a26b2c8088..743aa6fd632 100644
--- a/go.mod
+++ b/go.mod
@@ -10,8 +10,8 @@ require (
 	cloud.google.com/go/aiplatform v1.74.0
 	cloud.google.com/go/alloydb v1.14.0
 	cloud.google.com/go/apikeys v1.1.12
-	cloud.google.com/go/apphub v0.2.3
-	cloud.google.com/go/backupdr v1.3.0
+	cloud.google.com/go/apphub v0.2.4
+	cloud.google.com/go/backupdr v1.3.1
 	cloud.google.com/go/batch v1.12.0
 	cloud.google.com/go/bigquery v1.66.2
 	cloud.google.com/go/certificatemanager v1.9.3
@@ -22,7 +22,7 @@ require (
 	cloud.google.com/go/dataform v0.10.3
 	cloud.google.com/go/dataplex v1.22.0
 	cloud.google.com/go/dataproc/v2 v2.11.0
-	cloud.google.com/go/datastream v1.13.1
+	cloud.google.com/go/datastream v1.13.0
 	cloud.google.com/go/deploy v1.26.2
 	cloud.google.com/go/discoveryengine v1.15.0
 	cloud.google.com/go/documentai v1.35.2
@@ -33,9 +33,10 @@ require (
 	cloud.google.com/go/iam v1.4.1
 	cloud.google.com/go/iap v1.10.3
 	cloud.google.com/go/kms v1.21.0
+	cloud.google.com/go/logging v1.13.0
 	cloud.google.com/go/managedkafka v0.4.0
 	cloud.google.com/go/monitoring v1.24.0
-	cloud.google.com/go/netapp v1.7.0
+	cloud.google.com/go/netapp v1.7.1
 	cloud.google.com/go/networksecurity v0.10.3
 	cloud.google.com/go/networkservices v0.2.4
 	cloud.google.com/go/notebooks v1.12.3
@@ -94,7 +95,7 @@ require (
 	google.golang.org/api v0.224.0
 	google.golang.org/genproto v0.0.0-20250303144028-a0af3efb3deb
 	google.golang.org/genproto/googleapis/api v0.0.0-20250303144028-a0af3efb3deb
-	google.golang.org/genproto/googleapis/rpc v0.0.0-20250227231956-55c901821b1e
+	google.golang.org/genproto/googleapis/rpc v0.0.0-20250303144028-a0af3efb3deb
 	google.golang.org/grpc v1.71.0
 	google.golang.org/protobuf v1.36.5
 	gopkg.in/dnaeon/go-vcr.v3 v3.2.0
diff --git a/go.sum b/go.sum
index 1e77a1ef6ab..9defa7192f2 100644
--- a/go.sum
+++ b/go.sum
@@ -32,14 +32,14 @@ cloud.google.com/go/alloydb v1.14.0 h1:aEmmIHmiHDs46wTr/YqXuumuUGNc5QKYA7317nEFj
 cloud.google.com/go/alloydb v1.14.0/go.mod h1:OTBY1HoL0Z8PsHoMMVhkaUPKyY8oP7hzIAe/Dna6UHk=
 cloud.google.com/go/apikeys v1.1.12 h1:ZTFWJ1ibGjiIrIhhtdWOm7AGJd+y9R17dVujlgU//6E=
 cloud.google.com/go/apikeys v1.1.12/go.mod h1:3tqZUj8CmCJm0maQyLufgyO5Ghf3AZQ6hcSkIqsSIm4=
-cloud.google.com/go/apphub v0.2.3 h1:RE0xuK6eCw815c0Pke9YCpaznhWcJbOv8HT+gJQLoms=
-cloud.google.com/go/apphub v0.2.3/go.mod h1:0X7fYzK3ZEXlyIW7Vj6HTGAbsmrBb8WvA5lZcr+DyAQ=
+cloud.google.com/go/apphub v0.2.4 h1:lhsd/6+16NUshw8c52T+DfMaAz04SX6ItMr8caGbtgY=
+cloud.google.com/go/apphub v0.2.4/go.mod h1:QJw0OWQIQv/NvBz2/RJGAD6iJTLamf3vqOEsvFMixHw=
 cloud.google.com/go/auth v0.15.0 h1:Ly0u4aA5vG/fsSsxu98qCQBemXtAtJf+95z9HK+cxps=
 cloud.google.com/go/auth v0.15.0/go.mod h1:WJDGqZ1o9E9wKIL+IwStfyn/+s59zl4Bi+1KQNVXLZ8=
 cloud.google.com/go/auth/oauth2adapt v0.2.7 h1:/Lc7xODdqcEw8IrZ9SvwnlLX6j9FHQM74z6cBk9Rw6M=
 cloud.google.com/go/auth/oauth2adapt v0.2.7/go.mod h1:NTbTTzfvPl1Y3V1nPpOgl2w6d/FjO7NNUQaWSox6ZMc=
-cloud.google.com/go/backupdr v1.3.0 h1:t15Td/dan4UN19sLLd7GZwufDTr6UhV0bPOU1KgxYTw=
-cloud.google.com/go/backupdr v1.3.0/go.mod h1:S2AyryY9u/EaQmjBLsebEud50tfvdGlzkJy6GtHxuz4=
+cloud.google.com/go/backupdr v1.3.1 h1:AwJ0pja16mNvZcq8crxGSDCtvkWtdppNGAEADuzS5ys=
+cloud.google.com/go/backupdr v1.3.1/go.mod h1:CKwXJPs/L9hwXvZ045exrv1x1rJUA4wJe5Q2ux8mEuQ=
 cloud.google.com/go/batch v1.12.0 h1:lXuTaELvU0P0ARbTFxxdpOC/dFnZZeGglSw06BtO//8=
 cloud.google.com/go/batch v1.12.0/go.mod h1:CATSBh/JglNv+tEU/x21Z47zNatLQ/gpGnpyKOzbbcM=
 cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o=
@@ -74,8 +74,8 @@ cloud.google.com/go/dataproc/v2 v2.11.0 h1:6aRpyoRfNOP+r2+pGb7HeHtF+SYQID8kzztfH
 cloud.google.com/go/dataproc/v2 v2.11.0/go.mod h1:9vgGrn57ra7KBqz+B2KD+ltzEXvnHAUClFgq/ryU99g=
 cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE=
 cloud.google.com/go/datastore v1.1.0/go.mod h1:umbIZjpQpHh4hmRpGhH4tLFup+FVzqBi1b3c64qFpCk=
-cloud.google.com/go/datastream v1.13.1 h1:tPSMLwsVrL158TPv+GSmelMRG5+tMEwDwuwgj1d/tzo=
-cloud.google.com/go/datastream v1.13.1/go.mod h1:v+5wd1WdAfAmQ1PJ8U+QuaPdsoF5LvvKvRpPbm79FqQ=
+cloud.google.com/go/datastream v1.13.0 h1:C5AeEdze55feJVb17a40QmlnyH/aMhn/uf3Go3hIqPA=
+cloud.google.com/go/datastream v1.13.0/go.mod h1:GrL2+KC8mV4GjbVG43Syo5yyDXp3EH+t6N2HnZb1GOQ=
 cloud.google.com/go/deploy v1.26.2 h1:1c2Cd3jdb0mrKHHfyzSQ5DRmxgYd07tIZZzuMNrwDxU=
 cloud.google.com/go/deploy v1.26.2/go.mod h1:XpS3sG/ivkXCfzbzJXY9DXTeCJ5r68gIyeOgVGxGNEs=
 cloud.google.com/go/discoveryengine v1.15.0 h1:CHUN1mbs5iTqWJRuU6rZCvcuV5iVMr/rW8Kr1ivn7p4=
@@ -97,14 +97,16 @@ cloud.google.com/go/iap v1.10.3 h1:OWNYFHPyIBNHEAEFdVKOltYWe0g3izSrpFJW6Iidovk=
 cloud.google.com/go/iap v1.10.3/go.mod h1:xKgn7bocMuCFYhzRizRWP635E2LNPnIXT7DW0TlyPJ8=
 cloud.google.com/go/kms v1.21.0 h1:x3EeWKuYwdlo2HLse/876ZrKjk2L5r7Uexfm8+p6mSI=
 cloud.google.com/go/kms v1.21.0/go.mod h1:zoFXMhVVK7lQ3JC9xmhHMoQhnjEDZFoLAr5YMwzBLtk=
+cloud.google.com/go/logging v1.13.0 h1:7j0HgAp0B94o1YRDqiqm26w4q1rDMH7XNRU34lJXHYc=
+cloud.google.com/go/logging v1.13.0/go.mod h1:36CoKh6KA/M0PbhPKMq6/qety2DCAErbhXT62TuXALA=
 cloud.google.com/go/longrunning v0.6.5 h1:sD+t8DO8j4HKW4QfouCklg7ZC1qC4uzVZt8iz3uTW+Q=
 cloud.google.com/go/longrunning v0.6.5/go.mod h1:Et04XK+0TTLKa5IPYryKf5DkpwImy6TluQ1QTLwlKmY=
 cloud.google.com/go/managedkafka v0.4.0 h1:/snDok/h6TZKw0ubmfj/LYIwSn16mWBoBI2OEl7ldIA=
 cloud.google.com/go/managedkafka v0.4.0/go.mod h1:AUCV9FeT5IbuIf4ctH6TsnovE/KuIPE6Mp/YowkTyx0=
 cloud.google.com/go/monitoring v1.24.0 h1:csSKiCJ+WVRgNkRzzz3BPoGjFhjPY23ZTcaenToJxMM=
 cloud.google.com/go/monitoring v1.24.0/go.mod h1:Bd1PRK5bmQBQNnuGwHBfUamAV1ys9049oEPHnn4pcsc=
-cloud.google.com/go/netapp v1.7.0 h1:go1/+KlHagFTea6yG7qzPeEvwUi6dVh16UPo2t4LmtM=
-cloud.google.com/go/netapp v1.7.0/go.mod h1:oNr0SvlU0vTA8gClrNM22cZa0YPk1FzJ9MIsl/zkxF4=
+cloud.google.com/go/netapp v1.7.1 h1:3MxBolN91fsXtzIQRsp8UZfLF0zTrCUpl+DnYC7/qBU=
+cloud.google.com/go/netapp v1.7.1/go.mod h1:8l21nS/sQ+7wtFYR6hNcJWk5ksc8gNJ2o4/dVkeg3Vg=
 cloud.google.com/go/networksecurity v0.10.3 h1:JLJBFbxc8D7/OS81MyRoKhc2OvnVJxy5VMoQqqAhA7k=
 cloud.google.com/go/networksecurity v0.10.3/go.mod h1:G85ABVcPscEgpw+gcu+HUxNZJWjn3yhTqEU7+SsltFM=
 cloud.google.com/go/networkservices v0.2.4 h1:3fN8arT8LBL5beo8eO6uahp7A/Ar4kEG/bkyN3MDPvc=
@@ -1502,8 +1504,8 @@ google.golang.org/genproto v0.0.0-20250303144028-a0af3efb3deb h1:ITgPrl429bc6+2Z
 google.golang.org/genproto v0.0.0-20250303144028-a0af3efb3deb/go.mod h1:sAo5UzpjUwgFBCzupwhcLcxHVDK7vG5IqI30YnwX2eE=
 google.golang.org/genproto/googleapis/api v0.0.0-20250303144028-a0af3efb3deb h1:p31xT4yrYrSM/G4Sn2+TNUkVhFCbG9y8itM2S6Th950=
 google.golang.org/genproto/googleapis/api v0.0.0-20250303144028-a0af3efb3deb/go.mod h1:jbe3Bkdp+Dh2IrslsFCklNhweNTBgSYanP1UXhJDhKg=
-google.golang.org/genproto/googleapis/rpc v0.0.0-20250227231956-55c901821b1e h1:YA5lmSs3zc/5w+xsRcHqpETkaYyK63ivEPzNTcUUlSA=
-google.golang.org/genproto/googleapis/rpc v0.0.0-20250227231956-55c901821b1e/go.mod h1:LuRYeWDFV6WOn90g357N17oMCaxpgCnbi/44qJvDn2I=
+google.golang.org/genproto/googleapis/rpc v0.0.0-20250303144028-a0af3efb3deb h1:TLPQVbx1GJ8VKZxz52VAxl1EBgKXXbTiU9Fc5fZeLn4=
+google.golang.org/genproto/googleapis/rpc v0.0.0-20250303144028-a0af3efb3deb/go.mod h1:LuRYeWDFV6WOn90g357N17oMCaxpgCnbi/44qJvDn2I=
 google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
 google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38=
 google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM=
diff --git a/mockgcp/mock_http_roundtrip.go b/mockgcp/mock_http_roundtrip.go
index 5d7e0f22c3c..c9b963b13ab 100644
--- a/mockgcp/mock_http_roundtrip.go
+++ b/mockgcp/mock_http_roundtrip.go
@@ -573,7 +573,7 @@ func (m *mockRoundTripper) RoundTrip(req *http.Request) (*http.Response, error)
 		body["email"] = "test@example.com"
 		response.StatusCode = 200
 	} else {
-		log.Printf("host name %q not known.  "+
+		klog.Errorf("host name %q not known.  "+
 			"Please verify the ExpectedHost in service.go and retry.", req.Host)
 	}
 
diff --git a/pkg/controller/direct/logging/link_controller.go b/pkg/controller/direct/logging/link_controller.go
new file mode 100644
index 00000000000..12fadc2da7a
--- /dev/null
+++ b/pkg/controller/direct/logging/link_controller.go
@@ -0,0 +1,264 @@
+// 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 logging
+
+import (
+	"context"
+	"fmt"
+
+	//"reflect"
+
+	krm "github.com/GoogleCloudPlatform/k8s-config-connector/apis/logging/v1alpha1"
+	"github.com/GoogleCloudPlatform/k8s-config-connector/pkg/config"
+	"github.com/GoogleCloudPlatform/k8s-config-connector/pkg/controller/direct"
+	"github.com/GoogleCloudPlatform/k8s-config-connector/pkg/controller/direct/common"
+	"github.com/GoogleCloudPlatform/k8s-config-connector/pkg/controller/direct/directbase"
+	"github.com/GoogleCloudPlatform/k8s-config-connector/pkg/controller/direct/registry"
+	"github.com/GoogleCloudPlatform/k8s-config-connector/pkg/k8s"
+
+	//"net/http"
+
+	gcp "cloud.google.com/go/logging/apiv2"
+	//"google.golang.org/api/option"
+	loggingpb "cloud.google.com/go/logging/apiv2/loggingpb"
+
+	//"google.golang.org/api/option"
+	//"google.golang.org/protobuf/types/known/fieldmaskpb"
+
+	"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
+	"k8s.io/apimachinery/pkg/runtime"
+	"k8s.io/klog/v2"
+	"sigs.k8s.io/controller-runtime/pkg/client"
+)
+
+func init() {
+	registry.RegisterModel(krm.LoggingLinkGVK, NewLoggingLinkModel)
+}
+
+func NewLoggingLinkModel(ctx context.Context, config *config.ControllerConfig) (directbase.Model, error) {
+	return &modelLoggingLink{config: *config}, nil
+}
+
+var _ directbase.Model = &modelLoggingLink{}
+
+type modelLoggingLink struct {
+	config config.ControllerConfig
+}
+
+func (m *modelLoggingLink) client(ctx context.Context) (*gcp.ConfigClient, error) {
+
+	gcpClient, err := gcp.NewConfigRESTClient(ctx)
+	if err != nil {
+		return nil, fmt.Errorf("building Logging Config client: %w", err)
+	}
+	return gcpClient, err
+}
+
+func (m *modelLoggingLink) AdapterForObject(ctx context.Context, reader client.Reader, u *unstructured.Unstructured) (directbase.Adapter, error) {
+	obj := &krm.LoggingLink{}
+	if err := runtime.DefaultUnstructuredConverter.FromUnstructured(u.Object, &obj); err != nil {
+		return nil, fmt.Errorf("error converting to %T: %w", obj, err)
+	}
+
+	linkIdentity, err := krm.NewLinkIdentity(ctx, reader, obj)
+	if err != nil {
+		return nil, err
+	}
+
+	gcpClient, err := m.client(ctx)
+	if err != nil {
+		return nil, err
+	}
+	return &LoggingLinkAdapter{
+		id:        linkIdentity,
+		gcpClient: gcpClient,
+		desired:   obj,
+	}, nil
+}
+
+/*
+func () ResolveExternalRef(externalRef string) {
+
+}
+*/
+
+func (m *modelLoggingLink) AdapterForURL(ctx context.Context, url string) (directbase.Adapter, error) {
+	// TODO: Support URLs
+	return nil, nil
+}
+
+type LoggingLinkAdapter struct {
+	id        *krm.LinkIdentity
+	gcpClient *gcp.ConfigClient
+	desired   *krm.LoggingLink
+	actual    *loggingpb.Link
+}
+
+var _ directbase.Adapter = &LoggingLinkAdapter{}
+
+func (a *LoggingLinkAdapter) Find(ctx context.Context) (bool, error) {
+	log := klog.FromContext(ctx)
+	log.V(2).Info("getting LoggingLink", "name", a.id)
+
+	req := &loggingpb.GetLinkRequest{Name: a.id.String()}
+	linkpb, err := a.gcpClient.GetLink(ctx, req)
+	if err != nil {
+		if direct.IsNotFound(err) {
+			return false, nil
+		}
+		return false, fmt.Errorf("getting LoggingLink %q: %w", a.id, err)
+	}
+
+	a.actual = linkpb
+	return true, nil
+}
+
+func (a *LoggingLinkAdapter) Create(ctx context.Context, createOp *directbase.CreateOperation) error {
+	u := createOp.GetUnstructured()
+	log := klog.FromContext(ctx).WithName(ctrlName)
+	log.V(2).Info("creating LoggingLink", "u", u)
+
+	mapCtx := &direct.MapContext{}
+
+	desired := a.desired.DeepCopy()
+	resource := LoggingLinkSpec_ToProto(mapCtx, &desired.Spec)
+	if mapCtx.Err() != nil {
+		return mapCtx.Err()
+	}
+
+	parent := a.id.Parent()
+
+	resourceID := direct.ValueOf(desired.Spec.ResourceID)
+	if resourceID == "" {
+		log.V(2).Info("ResourceID is not set, will use metadata.name")
+		resourceID = desired.Name
+	} else {
+		log.V(2).Info("ResourceID is set, will use")
+		resourceID = *desired.Spec.ResourceID
+	}
+
+	req := &loggingpb.CreateLinkRequest{
+		Parent: parent.String(),
+		Link:   resource,
+		LinkId: resourceID,
+	}
+	op, err := a.gcpClient.CreateLink(ctx, req)
+
+	if err != nil {
+		return fmt.Errorf("creating Link %s: %w\n", a.id, err)
+	}
+	created, err := op.Wait(ctx)
+	if err != nil {
+		// there is an implicit dependency on the bucket being active, this wait captures that
+		return fmt.Errorf("Link %s waiting creation: %w", a.id, err)
+	}
+	log.V(2).Info("successfully created Link", "name", a.id)
+
+	status := &krm.LoggingLinkStatus{}
+	status.ObservedState = LoggingLinkObservedState_FromProto(mapCtx, created)
+	if mapCtx.Err() != nil {
+		return mapCtx.Err()
+	}
+	status.ExternalRef = direct.LazyPtr(a.id.String())
+	return createOp.UpdateStatus(ctx, status, nil)
+}
+
+func (a *LoggingLinkAdapter) Update(ctx context.Context, updateOp *directbase.UpdateOperation) error {
+
+	log := klog.FromContext(ctx)
+	log.V(2).Info("updating Logging Link", "name", a.id)
+	mapCtx := &direct.MapContext{}
+
+	desiredPb := LoggingLinkSpec_ToProto(mapCtx, &a.desired.DeepCopy().Spec)
+	if mapCtx.Err() != nil {
+		return mapCtx.Err()
+	}
+
+	paths, err := common.CompareProtoMessage(desiredPb, a.actual, common.BasicDiff)
+	if err != nil {
+		return err
+	}
+	if len(paths) == 0 {
+		log.V(2).Info("no field needs update", "name", a.id)
+		status := &krm.LoggingLinkStatus{}
+		status.ObservedState = LoggingLinkObservedState_FromProto(mapCtx, a.actual)
+		if mapCtx.Err() != nil {
+			return mapCtx.Err()
+		}
+		return updateOp.UpdateStatus(ctx, status, nil)
+	} else {
+		return fmt.Errorf("update operation not supported for resource %v %v",
+			a.desired.GroupVersionKind(), k8s.GetNamespacedName(a.desired))
+	}
+}
+
+func (a *LoggingLinkAdapter) Export(ctx context.Context) (*unstructured.Unstructured, error) {
+	if a.actual == nil {
+		return nil, fmt.Errorf("Find() not called")
+	}
+	u := &unstructured.Unstructured{}
+
+	obj := &krm.LoggingLink{}
+	mapCtx := &direct.MapContext{}
+	obj.Spec = direct.ValueOf(LoggingLinkSpec_FromProto(mapCtx, a.actual))
+	if mapCtx.Err() != nil {
+		return nil, mapCtx.Err()
+	}
+
+	// TODO(user): Update other resource references
+	/* TODO Bucket Ref?
+	parent, err := a.id.Parent()
+	if err != nil {
+		return nil, err
+	}
+	 obj.Spec.ProjectRef = &refs.ProjectRef{External: parent.String()}
+	 obj.Spec.Location = parent.Location
+	uObj, err := runtime.DefaultUnstructuredConverter.ToUnstructured(obj)
+	if err != nil {
+		return nil, err
+	}
+	*/
+
+	u.SetName(a.actual.Name)
+	u.SetGroupVersionKind(krm.LoggingLinkGVK)
+
+	// TODO uObj is set in the commented out code
+	//u.Object = uObj
+	return u, nil
+}
+
+// Delete implements the Adapter interface.
+func (a *LoggingLinkAdapter) Delete(ctx context.Context, deleteOp *directbase.DeleteOperation) (bool, error) {
+	log := klog.FromContext(ctx)
+	log.V(2).Info("deleting Link", "name", a.id)
+
+	req := &loggingpb.DeleteLinkRequest{Name: a.id.String()}
+	op, err := a.gcpClient.DeleteLink(ctx, req)
+	if err != nil {
+		if direct.IsNotFound(err) {
+			// Return success if not found (assume it was already deleted).
+			log.V(2).Info("skipping delete for non-existent LoggingLink, assuming it was already deleted", "name", a.id.String())
+			return true, nil
+		}
+		return false, fmt.Errorf("deleting Link %s: %w", a.id, err)
+	}
+	log.V(2).Info("successfully deleted Link", "name", a.id)
+
+	err = op.Wait(ctx)
+	if err != nil {
+		return false, fmt.Errorf("waiting delete Link %s: %w", a.id, err)
+	}
+	return true, nil
+}
diff --git a/pkg/controller/direct/logging/mapper.go b/pkg/controller/direct/logging/mapper.go
new file mode 100644
index 00000000000..0c2c9f4f652
--- /dev/null
+++ b/pkg/controller/direct/logging/mapper.go
@@ -0,0 +1,125 @@
+// 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 logging
+
+import (
+	"strings"
+
+	pb "cloud.google.com/go/logging/apiv2/loggingpb"
+	krm "github.com/GoogleCloudPlatform/k8s-config-connector/apis/logging/v1alpha1"
+	refs "github.com/GoogleCloudPlatform/k8s-config-connector/apis/refs/v1beta1"
+	"github.com/GoogleCloudPlatform/k8s-config-connector/pkg/controller/direct"
+)
+
+func LoggingLinkSpec_LoggingLogBucketRef_FromProto(mapCtx *direct.MapContext, in string) *refs.LoggingLogBucketRef {
+	if in == "" {
+		return nil
+	}
+	return &refs.LoggingLogBucketRef{
+		External: in,
+	}
+}
+
+func LoggingLinkSpec_LoggingLogBucketRef_ToProto(mapCtx *direct.MapContext, in *refs.LoggingLogBucketRef) *string {
+	if in == nil {
+		return nil
+	}
+	if in.External == "" {
+		mapCtx.Errorf("reference %s was not pre-resolved", in.Name)
+	}
+	return direct.LazyPtr(in.External)
+}
+
+func LoggingLinkSpec_FromProto(mapCtx *direct.MapContext, in *pb.Link) *krm.LoggingLinkSpec {
+	if in == nil {
+		return nil
+	}
+	out := &krm.LoggingLinkSpec{}
+	name := in.GetName()
+	resourceID := name[strings.LastIndex(name, "/")+1:]
+	out.ResourceID = direct.LazyPtr(resourceID)
+	out.Description = direct.LazyPtr(in.GetDescription())
+	out.LoggingLogBucketRef = LoggingLinkSpec_LoggingLogBucketRef_FromProto(mapCtx, resourceID)
+	return out
+}
+func LoggingLinkSpec_ToProto(mapCtx *direct.MapContext, in *krm.LoggingLinkSpec) *pb.Link {
+	if in == nil {
+		return nil
+	}
+	out := &pb.Link{}
+	// out.Name = direct.ValueOf(in.Name) - keep in mind, this is set, but in the caller by the controller
+	out.Description = direct.ValueOf(in.Description)
+
+	return out
+}
+func LoggingLinkObservedState_FromProto(mapCtx *direct.MapContext, in *pb.Link) *krm.LoggingLinkObservedState {
+	if in == nil {
+		return nil
+	}
+	out := &krm.LoggingLinkObservedState{}
+	out.CreateTime = direct.StringTimestamp_FromProto(mapCtx, in.GetCreateTime())
+	// This is the first lifecycle state return by a direct controller, so this is a guess based on other enums
+	out.LifecycleState = direct.Enum_FromProto(mapCtx, in.GetLifecycleState())
+	out.BigQueryDataset = BigQueryDatasetObservedState_FromProto(mapCtx, in)
+	return out
+}
+func LoggingLinkObservedState_ToProto(mapCtx *direct.MapContext, in *krm.LoggingLinkObservedState) *pb.Link {
+	if in == nil {
+		return nil
+	}
+	out := &pb.Link{}
+	out.CreateTime = direct.StringTimestamp_ToProto(mapCtx, in.CreateTime)
+	// This is the first lifecycle state return by a direct controller, so this is a guess based on other enums
+	out.LifecycleState = direct.Enum_ToProto[pb.LifecycleState](mapCtx, in.LifecycleState)
+	out.BigqueryDataset = BigQueryDatasetObservedState_ToProto(mapCtx, in.BigQueryDataset)
+	return out
+}
+
+func BigQueryDataset_FromProto(mapCtx *direct.MapContext, in *pb.BigQueryDataset) *krm.BigQueryDataset {
+	if in == nil {
+		return nil
+	}
+	out := &krm.BigQueryDataset{}
+	// MISSING: DatasetID
+	return out
+}
+func BigQueryDataset_ToProto(mapCtx *direct.MapContext, in *krm.BigQueryDataset) *pb.BigQueryDataset {
+	if in == nil {
+		return nil
+	}
+	out := &pb.BigQueryDataset{}
+	// MISSING: DatasetID
+	return out
+}
+func BigQueryDatasetObservedState_FromProto(mapCtx *direct.MapContext, in *pb.Link) *krm.BigQueryDatasetObservedState {
+	if in == nil {
+		return nil
+	}
+	out := &krm.BigQueryDatasetObservedState{}
+	dataset := in.GetBigqueryDataset()
+	if dataset != nil {
+		out.DatasetID = direct.LazyPtr(dataset.GetDatasetId())
+	}
+
+	return out
+}
+func BigQueryDatasetObservedState_ToProto(mapCtx *direct.MapContext, in *krm.BigQueryDatasetObservedState) *pb.BigQueryDataset {
+	if in == nil {
+		return nil
+	}
+	out := &pb.BigQueryDataset{}
+	out.DatasetId = direct.ValueOf(in.DatasetID)
+	return out
+}
diff --git a/pkg/controller/direct/maputils.go b/pkg/controller/direct/maputils.go
index 202ab20e1e0..41407c8a1d7 100644
--- a/pkg/controller/direct/maputils.go
+++ b/pkg/controller/direct/maputils.go
@@ -267,6 +267,7 @@ func IsBadRequest(err error) bool {
 
 // HasHTTPCode returns true if the given error is an HTTP response with the given code.
 func HasHTTPCode(err error, code int) bool {
+
 	if err == nil {
 		return false
 	}
diff --git a/pkg/gvks/supportedgvks/gvks_generated.go b/pkg/gvks/supportedgvks/gvks_generated.go
index 9f35e8b6a57..c91b4391c29 100644
--- a/pkg/gvks/supportedgvks/gvks_generated.go
+++ b/pkg/gvks/supportedgvks/gvks_generated.go
@@ -3438,6 +3438,16 @@ var SupportedGVKs = map[schema.GroupVersionKind]GVKMetadata{
 			"cnrm.cloud.google.com/tf2crd":          "true",
 		},
 	},
+	{
+		Group:   "logging.cnrm.cloud.google.com",
+		Version: "v1alpha1",
+		Kind:    "LoggingLink",
+	}: {
+		Labels: map[string]string{
+			"cnrm.cloud.google.com/managed-by-kcc": "true",
+			"cnrm.cloud.google.com/system":         "true",
+		},
+	},
 	{
 		Group:   "logging.cnrm.cloud.google.com",
 		Version: "v1beta1",
diff --git a/pkg/test/resourcefixture/testdata/basic/logging/v1alpha1/logginglogginglink/basiclink/_generated_object_basiclink.golden.yaml b/pkg/test/resourcefixture/testdata/basic/logging/v1alpha1/logginglogginglink/basiclink/_generated_object_basiclink.golden.yaml
new file mode 100644
index 00000000000..9b315111d22
--- /dev/null
+++ b/pkg/test/resourcefixture/testdata/basic/logging/v1alpha1/logginglogginglink/basiclink/_generated_object_basiclink.golden.yaml
@@ -0,0 +1,29 @@
+apiVersion: logging.cnrm.cloud.google.com/v1alpha1
+kind: LoggingLink
+metadata:
+  finalizers:
+  - cnrm.cloud.google.com/finalizer
+  - cnrm.cloud.google.com/deletion-defender
+  generation: 1
+  labels:
+    cnrm-test: "true"
+  name: logginglink${uniqueId}
+  namespace: ${uniqueId}
+spec:
+  description: A sample log link
+  loggingLogBucketRef:
+    external: projects/${projectId}/locations/global/buckets/logginglogbucket-${uniqueId}
+status:
+  conditions:
+  - lastTransitionTime: "1970-01-01T00:00:00Z"
+    message: The resource is up to date
+    reason: UpToDate
+    status: "True"
+    type: Ready
+  externalRef: projects/${projectId}/locations/global/buckets/logginglogbucket-${uniqueId}/links/logginglink${uniqueId}
+  observedGeneration: 1
+  observedState:
+    bigQueryDataset:
+      datasetID: bigquery.googleapis.com/projects/${projectId}/datasets/logginglink${uniqueId}
+    createTime: "1970-01-01T00:00:00Z"
+    lifecycleState: ACTIVE
diff --git a/pkg/test/resourcefixture/testdata/basic/logging/v1alpha1/logginglogginglink/basiclink/_http.log b/pkg/test/resourcefixture/testdata/basic/logging/v1alpha1/logginglogginglink/basiclink/_http.log
new file mode 100644
index 00000000000..739cb69b2ff
--- /dev/null
+++ b/pkg/test/resourcefixture/testdata/basic/logging/v1alpha1/logginglogginglink/basiclink/_http.log
@@ -0,0 +1,99 @@
+GET https://logging.googleapis.com/v2/projects/${projectId}/locations/global/buckets/logginglogbucket-${uniqueId}?alt=json
+Content-Type: application/json
+User-Agent: kcc/controller-manager DeclarativeClientLib/0.0.1
+
+404 Not Found
+Content-Type: application/json; charset=UTF-8
+Server: ESF
+Vary: Origin
+Vary: X-Origin
+Vary: Referer
+X-Content-Type-Options: nosniff
+X-Frame-Options: SAMEORIGIN
+X-Xss-Protection: 0
+
+{
+  "error": {
+    "code": 404,
+    "message": "Bucket `logginglogbucket-${uniqueId}` in location `global` does not exist",
+    "status": "NOT_FOUND"
+  }
+}
+
+---
+
+POST https://logging.googleapis.com/v2/projects/${projectId}/locations/global/buckets?alt=json&bucketId=logginglogbucket-${uniqueId}
+Content-Type: application/json
+User-Agent: kcc/controller-manager DeclarativeClientLib/0.0.1
+
+{
+  "analyticsEnabled": true,
+  "description": "A sample log bucket",
+  "locked": false,
+  "name": "projects/${projectId}/locations/global/buckets/logginglogbucket-${uniqueId}",
+  "retentionDays": 30
+}
+
+200 OK
+Content-Type: application/json; charset=UTF-8
+Server: ESF
+Vary: Origin
+Vary: X-Origin
+Vary: Referer
+X-Content-Type-Options: nosniff
+X-Frame-Options: SAMEORIGIN
+X-Xss-Protection: 0
+
+{
+  "analyticsEnabled": true,
+  "createTime": "2024-04-01T12:34:56.123456Z",
+  "description": "A sample log bucket",
+  "lifecycleState": "ACTIVE",
+  "name": "projects/${projectId}/locations/global/buckets/logginglogbucket-${uniqueId}",
+  "retentionDays": 30,
+  "updateTime": "2024-04-01T12:34:56.123456Z"
+}
+
+---
+
+GET https://logging.googleapis.com/v2/projects/${projectId}/locations/global/buckets/logginglogbucket-${uniqueId}?alt=json
+Content-Type: application/json
+User-Agent: kcc/controller-manager DeclarativeClientLib/0.0.1
+
+200 OK
+Content-Type: application/json; charset=UTF-8
+Server: ESF
+Vary: Origin
+Vary: X-Origin
+Vary: Referer
+X-Content-Type-Options: nosniff
+X-Frame-Options: SAMEORIGIN
+X-Xss-Protection: 0
+
+{
+  "analyticsEnabled": true,
+  "createTime": "2024-04-01T12:34:56.123456Z",
+  "description": "A sample log bucket",
+  "lifecycleState": "ACTIVE",
+  "name": "projects/${projectId}/locations/global/buckets/logginglogbucket-${uniqueId}",
+  "retentionDays": 30,
+  "updateTime": "2024-04-01T12:34:56.123456Z"
+}
+
+---
+
+DELETE https://logging.googleapis.com/v2/projects/${projectId}/locations/global/buckets/logginglogbucket-${uniqueId}?alt=json
+Content-Type: application/json
+User-Agent: kcc/controller-manager DeclarativeClientLib/0.0.1
+
+200 OK
+Content-Type: application/json; charset=UTF-8
+Server: ESF
+Vary: Origin
+Vary: X-Origin
+Vary: Referer
+X-Content-Type-Options: nosniff
+X-Frame-Options: SAMEORIGIN
+X-Xss-Protection: 0
+
+{}
\ No newline at end of file
diff --git a/pkg/test/resourcefixture/testdata/basic/logging/v1alpha1/logginglogginglink/basiclink/create.yaml b/pkg/test/resourcefixture/testdata/basic/logging/v1alpha1/logginglogginglink/basiclink/create.yaml
new file mode 100644
index 00000000000..ca05d05f9ca
--- /dev/null
+++ b/pkg/test/resourcefixture/testdata/basic/logging/v1alpha1/logginglogginglink/basiclink/create.yaml
@@ -0,0 +1,23 @@
+# Copyright 2022 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.
+
+apiVersion: logging.cnrm.cloud.google.com/v1alpha1
+kind: LoggingLink
+metadata:
+  name: logginglink-${uniqueId}
+spec:
+  resourceID: logginglink${uniqueId}
+  loggingLogBucketRef:
+    name: "logginglogbucket-${uniqueId}"
+  description: "A sample log link"
diff --git a/pkg/test/resourcefixture/testdata/basic/logging/v1alpha1/logginglogginglink/basiclink/dependencies.yaml b/pkg/test/resourcefixture/testdata/basic/logging/v1alpha1/logginglogginglink/basiclink/dependencies.yaml
new file mode 100644
index 00000000000..8b71b06669e
--- /dev/null
+++ b/pkg/test/resourcefixture/testdata/basic/logging/v1alpha1/logginglogginglink/basiclink/dependencies.yaml
@@ -0,0 +1,26 @@
+# Copyright 2022 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.
+
+apiVersion: logging.cnrm.cloud.google.com/v1beta1
+kind: LoggingLogBucket
+metadata:
+  name: logginglogbucket-${uniqueId}
+spec:
+  projectRef:
+    external: "projects/${projectId}"
+  location: "global"
+  description: "A sample log bucket"
+  locked: false
+  retentionDays: 30
+  enableAnalytics: true


Reviews:
user: login: yuwenma
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-02-04T16:29:34Z
user: login: tylerreidwaze
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-02-07T22:40:35Z
user: login: maqiuyujoyce
body: Hi @tylerreidwaze , thank you so much for adding the resource.

before you addressing the comments, could you rebase to master HEAD and solve the conflict?
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-03-13T01:33:33Z
user: login: tylerreidwaze
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-03-14T17:12:10Z
user: login: tylerreidwaze
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-03-14T17:14:41Z
user: login: tylerreidwaze
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-03-14T17:21:24Z
user: login: tylerreidwaze
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-03-14T17:22:11Z
user: login: tylerreidwaze
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-03-14T17:39:02Z
user: login: tylerreidwaze
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-03-14T17:40:33Z
user: login: tylerreidwaze
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-03-14T17:41:16Z
user: login: tylerreidwaze
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-03-14T17:41:47Z
user: login: tylerreidwaze
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-03-14T17:42:48Z
user: login: tylerreidwaze
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-03-14T17:43:20Z
user: login: tylerreidwaze
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-03-14T17:43:37Z
user: login: tylerreidwaze
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-03-14T17:44:05Z
user: login: tylerreidwaze
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-03-14T17:46:06Z
user: login: tylerreidwaze
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-03-14T17:58:09Z
user: login: tylerreidwaze
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-03-14T18:04:03Z
user: login: tylerreidwaze
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-03-14T18:15:29Z
user: login: tylerreidwaze
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-03-14T18:40:51Z
user: login: tylerreidwaze
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-03-14T21:00:33Z
user: login: tylerreidwaze
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-03-14T21:03:24Z
user: login: tylerreidwaze
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-03-14T22:24:56Z
user: login: tylerreidwaze
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-03-14T22:25:21Z
user: login: maqiuyujoyce
body: Thank you for the addressing the comments, @tylerreidwaze !

You'll need to rebase against master HEAD again due to the new merge conflict.

Also, have you got the chance to run the tests locally against the real GCP API after you've made the change?
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-03-20T02:45:42Z
user: login: maqiuyujoyce
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-03-20T02:46:37Z
user: login: maqiuyujoyce
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-03-20T02:47:01Z
user: login: tylerreidwaze
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-03-20T15:06:23Z
user: login: tylerreidwaze
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-03-20T15:07:16Z
user: login: tylerreidwaze
body: I see the issue.  I didn't save a few files in vscode and thus the changes werent uploaded
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-03-20T15:42:15Z
user: login: tylerreidwaze
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-03-20T15:47:09Z
user: login: tylerreidwaze
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-03-20T15:49:29Z
user: login: tylerreidwaze
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-03-20T21:19:32Z
user: login: maqiuyujoyce
body: LGTM except for a small issue.

Also the validations failed so could you rerun `make ready-pr`?
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-03-21T02:50:12Z
user: login: tylerreidwaze
body: Getting closer :)
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-03-21T17:19:10Z
user: login: maqiuyujoyce
body: /lgtm
/approve
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-03-21T21:49:03Z

Review comments:
diff_hunk: @@ -0,0 +1,109 @@
+// 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 (
+	refs "github.com/GoogleCloudPlatform/k8s-config-connector/apis/refs/v1beta1"
+	"github.com/GoogleCloudPlatform/k8s-config-connector/pkg/apis/k8s/v1alpha1"
+	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+)
+
+var LoggingLinkGVK = GroupVersion.WithKind("LoggingLink")
+
+// EDIT THIS FILE!  THIS IS SCAFFOLDING FOR YOU TO OWN!
+// NOTE: json tags are required.  Any new fields you add must have json tags for the fields to be serialized.
+
+// LoggingLinkSpec defines the desired state of LoggingLink
+// +kcc:proto=google.logging.v2.Link
+type LoggingLinkSpec struct {
+	// +kubebuilder:validation:XValidation:rule="self == oldSelf",message="ResourceID field is immutable"
+	// Immutable.
+	// The LoggingLink name. If not given, the metadata.name will be used.
+	ResourceID *string `json:"resourceID,omitempty"`
+
+	// Describes this link.
+	//  The maximum length of the description is 8000 characters.
+	Description *string `json:"description,omitempty"`
+
+	// Placeholder description
+	LoggingLogBucketRef *refs.LoggingLogBucketRef `json:"loggingLogBucketRef,omitempty"`
+}
+
+// LoggingLinkStatus defines the config connector machine state of LoggingLink
+type LoggingLinkStatus 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 LoggingLink resource in GCP.
+	ExternalRef *string `json:"externalRef,omitempty"`
+
+	// ObservedState is the state of the resource as most recently observed in GCP.
+	ObservedState *LoggingLinkObservedState `json:"observedState,omitempty"`
+}
+
+// LoggingLinkObservedState is the state of the LoggingLink resource as most recently observed in GCP.
+type LoggingLinkObservedState struct {
+	// We need to add a reference here to lifecycle state and Bigquery Dataset here since they are in the proto
+
+	// +optional
+	// +kubebuilder:validation:Format=date-time
+	CreateTime *string `json:"createTime,omitempty"`
+
+	// the lifecycle state might be something more complicated
+	// this is an ENUM, should be safe to use a string
+	LifecycleState *string `json:"lifecycleState,omitempty"`
+
+	// this field is just a string, but its an object(string)
+	// https://github.com/googleapis/googleapis/blob/master/google/logging/v2/logging_config.proto#L1063
+	BigQueryDataset *BigQueryDataset `json:"bigQueryDataset,omitempty"`
path: apis/logging/v1alpha1/link_types.go
user: login: yuwenma
body: You can use `*refs.BigQueryDatasetRef`, where `refs` is `refs "github.com/GoogleCloudPlatform/k8s-config-connector/apis/refs/v1beta1"`
created_at: 2025-02-04T16:22:16Z
author_association: CONTRIBUTOR
path: apis/refs/v1beta1/logbucketref.go
user: login: yuwenma
body: Ah, no need to write this file anymore.  `apis/logging/v1alpha1/link_reference.go` is autogenerated, where `ResolveLoggingLogBucketRef` equals to `NormalizedExternal`
created_at: 2025-02-04T16:23:02Z
author_association: CONTRIBUTOR
path: pkg/clients/generated/apis/logging/v1alpha1/doc.go
user: login: yuwenma
body: You don't need to update the `pkg/clients/generated` any more (I'm curious, is there any doc asking you to do so?). They are the legacy way to generate the go client lib. With the direct approach, we uses the sig-kubernetes controller-gen and the `apis/logging` is enough. Here's the announcement if you are interested https://github.com/GoogleCloudPlatform/k8s-config-connector/blob/master/pkg/clients/generated/README.md 
created_at: 2025-02-04T16:27:56Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,109 @@
+// 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 (
+	refs "github.com/GoogleCloudPlatform/k8s-config-connector/apis/refs/v1beta1"
+	"github.com/GoogleCloudPlatform/k8s-config-connector/pkg/apis/k8s/v1alpha1"
+	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+)
+
+var LoggingLinkGVK = GroupVersion.WithKind("LoggingLink")
+
+// EDIT THIS FILE!  THIS IS SCAFFOLDING FOR YOU TO OWN!
+// NOTE: json tags are required.  Any new fields you add must have json tags for the fields to be serialized.
+
+// LoggingLinkSpec defines the desired state of LoggingLink
+// +kcc:proto=google.logging.v2.Link
+type LoggingLinkSpec struct {
+	// +kubebuilder:validation:XValidation:rule="self == oldSelf",message="ResourceID field is immutable"
+	// Immutable.
+	// The LoggingLink name. If not given, the metadata.name will be used.
+	ResourceID *string `json:"resourceID,omitempty"`
+
+	// Describes this link.
+	//  The maximum length of the description is 8000 characters.
+	Description *string `json:"description,omitempty"`
+
+	// Placeholder description
+	LoggingLogBucketRef *refs.LoggingLogBucketRef `json:"loggingLogBucketRef,omitempty"`
+}
+
+// LoggingLinkStatus defines the config connector machine state of LoggingLink
+type LoggingLinkStatus 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 LoggingLink resource in GCP.
+	ExternalRef *string `json:"externalRef,omitempty"`
+
+	// ObservedState is the state of the resource as most recently observed in GCP.
+	ObservedState *LoggingLinkObservedState `json:"observedState,omitempty"`
+}
+
+// LoggingLinkObservedState is the state of the LoggingLink resource as most recently observed in GCP.
+type LoggingLinkObservedState struct {
+	// We need to add a reference here to lifecycle state and Bigquery Dataset here since they are in the proto
+
+	// +optional
+	// +kubebuilder:validation:Format=date-time
+	CreateTime *string `json:"createTime,omitempty"`
+
+	// the lifecycle state might be something more complicated
+	// this is an ENUM, should be safe to use a string
+	LifecycleState *string `json:"lifecycleState,omitempty"`
+
+	// this field is just a string, but its an object(string)
+	// https://github.com/googleapis/googleapis/blob/master/google/logging/v2/logging_config.proto#L1063
+	BigQueryDataset *BigQueryDataset `json:"bigQueryDataset,omitempty"`
path: apis/logging/v1alpha1/link_types.go
user: login: tylerreidwaze
body: THis makes sense after processing.  I think this is resolve in latest push
created_at: 2025-02-07T22:40:35Z
author_association: COLLABORATOR
diff_hunk: @@ -0,0 +1,117 @@
+// 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 v1beta1
+
+import (
+	"context"
+	"fmt"
+	"strings"
+
+	apierrors "k8s.io/apimachinery/pkg/api/errors"
+	"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
+	"k8s.io/apimachinery/pkg/runtime/schema"
+	"k8s.io/apimachinery/pkg/types"
+	"sigs.k8s.io/controller-runtime/pkg/client"
+)
+
+type LoggingLogBucketRef struct {
+	/* The LoggingLogBucket selfLink, when not managed by Config Connector. */
+	External string `json:"external,omitempty"`
+	/* The `name` field of a `LoggingLogBucket` resource. */
+	Name string `json:"name,omitempty"`
+	/* The `namespace` field of a `LoggingLogBucket` resource. */
+	Namespace string `json:"namespace,omitempty"`
+}
+
+type LoggingLogBucket struct {
+	ProjectID          string
+	Location           string
+	LoggingLogBucketID string
+}
+
+func (s *LoggingLogBucket) String() string {
+	return "projects/" + s.ProjectID + "locations/" + s.Location + "/buckets/" + s.LoggingLogBucketID
+}
+
+func ResolveLoggingLogBucketRef(ctx context.Context, reader client.Reader, obj client.Object, ref *LoggingLogBucketRef) (*LoggingLogBucket, error) {
+	if ref == nil {
+		return nil, nil
+	}
+
+	if ref.Name == "" && ref.External == "" {
+		return nil, fmt.Errorf("must specify either name or external on loggingLogBucketRef")
+	}
+	if ref.External != "" && ref.Name != "" {
+		return nil, fmt.Errorf("cannot specify both spec.loggingLogBucketRef.name and spec.loggingLogBucketRef.external")
+	}
+
+	if ref.External != "" {
+		// External should be in the `projects/[projectID]/locations/[Location]/buckets/[bucketID]` format.
+		tokens := strings.Split(ref.External, "/")
+		if len(tokens) == 6 && tokens[0] == "projects" && tokens[2] == "locations" && tokens[4] == "buckets" {
+			return &LoggingLogBucket{
+				ProjectID:          tokens[1],
+				Location:           tokens[3],
+				LoggingLogBucketID: tokens[5],
+			}, nil
+		}
+		return nil, fmt.Errorf("format of LoggingLogBucket external=%q was not known (use projects/<projectId>/locations/<location>/buckets/<bucketID>)", ref.External)
+	}
+
+	key := types.NamespacedName{
+		Namespace: ref.Namespace,
+		Name:      ref.Name,
+	}
+	if key.Namespace == "" {
+		key.Namespace = obj.GetNamespace()
+	}
+
+	exchange := &unstructured.Unstructured{}
+	exchange.SetGroupVersionKind(schema.GroupVersionKind{
+		Group:   "logging.cnrm.cloud.google.com",
+		Version: "v1alpha1",
path: apis/refs/v1beta1/logbucketref.go
user: login: maqiuyujoyce
body: Should be "v1beta1". The only supported version for LoggingLogBucket is v1beta1: https://github.com/GoogleCloudPlatform/k8s-config-connector/blob/master/config/crds/resources/apiextensions.k8s.io_v1_customresourcedefinition_logginglogbuckets.logging.cnrm.cloud.google.com.yaml
created_at: 2025-03-12T23:40:12Z
author_association: COLLABORATOR
diff_hunk: @@ -0,0 +1,117 @@
+// 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 v1beta1
+
+import (
+	"context"
+	"fmt"
+	"strings"
+
+	apierrors "k8s.io/apimachinery/pkg/api/errors"
+	"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
+	"k8s.io/apimachinery/pkg/runtime/schema"
+	"k8s.io/apimachinery/pkg/types"
+	"sigs.k8s.io/controller-runtime/pkg/client"
+)
+
+type LoggingLogBucketRef struct {
+	/* The LoggingLogBucket selfLink, when not managed by Config Connector. */
+	External string `json:"external,omitempty"`
+	/* The `name` field of a `LoggingLogBucket` resource. */
+	Name string `json:"name,omitempty"`
+	/* The `namespace` field of a `LoggingLogBucket` resource. */
+	Namespace string `json:"namespace,omitempty"`
+}
+
+type LoggingLogBucket struct {
+	ProjectID          string
+	Location           string
+	LoggingLogBucketID string
+}
+
+func (s *LoggingLogBucket) String() string {
+	return "projects/" + s.ProjectID + "locations/" + s.Location + "/buckets/" + s.LoggingLogBucketID
+}
+
+func ResolveLoggingLogBucketRef(ctx context.Context, reader client.Reader, obj client.Object, ref *LoggingLogBucketRef) (*LoggingLogBucket, error) {
+	if ref == nil {
+		return nil, nil
+	}
+
+	if ref.Name == "" && ref.External == "" {
+		return nil, fmt.Errorf("must specify either name or external on loggingLogBucketRef")
+	}
+	if ref.External != "" && ref.Name != "" {
+		return nil, fmt.Errorf("cannot specify both spec.loggingLogBucketRef.name and spec.loggingLogBucketRef.external")
+	}
+
+	if ref.External != "" {
+		// External should be in the `projects/[projectID]/locations/[Location]/buckets/[bucketID]` format.
+		tokens := strings.Split(ref.External, "/")
+		if len(tokens) == 6 && tokens[0] == "projects" && tokens[2] == "locations" && tokens[4] == "buckets" {
+			return &LoggingLogBucket{
+				ProjectID:          tokens[1],
+				Location:           tokens[3],
+				LoggingLogBucketID: tokens[5],
+			}, nil
+		}
+		return nil, fmt.Errorf("format of LoggingLogBucket external=%q was not known (use projects/<projectId>/locations/<location>/buckets/<bucketID>)", ref.External)
+	}
+
+	key := types.NamespacedName{
+		Namespace: ref.Namespace,
+		Name:      ref.Name,
+	}
+	if key.Namespace == "" {
+		key.Namespace = obj.GetNamespace()
+	}
+
+	exchange := &unstructured.Unstructured{}
+	exchange.SetGroupVersionKind(schema.GroupVersionKind{
+		Group:   "logging.cnrm.cloud.google.com",
+		Version: "v1alpha1",
+		Kind:    "LoggingLogBucket",
+	})
+	if err := reader.Get(ctx, key, exchange); err != nil {
+		if apierrors.IsNotFound(err) {
+			return nil, fmt.Errorf("referenced LoggingLogBucket %v not found", key)
+		}
+		return nil, fmt.Errorf("error reading referenced LoggingLogBucket %v: %w", key, err)
+	}
+
+	resourceID, _, err := unstructured.NestedString(exchange.Object, "spec", "resourceID")
+	if err != nil {
+		return nil, fmt.Errorf("reading spec.resourceID from LoggingLogBucket %s/%s: %w", exchange.GetNamespace(), exchange.GetName(), err)
+	}
+	if resourceID == "" {
+		resourceID = exchange.GetName()
+	}
+
+	location, _, err := unstructured.NestedString(exchange.Object, "spec", "location")
+	if err != nil {
+		return nil, fmt.Errorf("reading spec.region from LoggingLogBucket %s/%s: %w", exchange.GetNamespace(), exchange.GetName(), err)
+	}
+
+	projectID, err := ResolveProjectID(ctx, reader, exchange)
path: apis/refs/v1beta1/logbucketref.go
user: login: maqiuyujoyce
body: Just curious: Do you only need the LoggingLink under projects? Asking because LoggingLogBucket can be under project, folder, organization or billingAccount.

Note that I didn't mean we need to support all of them now in this alpha resource.
created_at: 2025-03-12T23:43:08Z
author_association: COLLABORATOR
diff_hunk: @@ -0,0 +1,120 @@
+// 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 v1alpha1
+
+import (
+	"context"
+	"fmt"
+	"strings"
+
+	"github.com/GoogleCloudPlatform/k8s-config-connector/apis/common"
+	refsv1beta1 "github.com/GoogleCloudPlatform/k8s-config-connector/apis/refs/v1beta1"
+	"sigs.k8s.io/controller-runtime/pkg/client"
+)
+
+// LinkIdentity defines the resource reference to LoggingLink, which "External" field
+// holds the GCP identifier for the KRM object.
+type LinkIdentity struct {
+	parent *LinkParent
+	id     string
+}
+
+func (i *LinkIdentity) String() string {
+	return i.parent.String() + "/links/" + i.id
+}
+
+func (i *LinkIdentity) ID() string {
+	return i.id
+}
+
+func (i *LinkIdentity) Parent() *LinkParent {
+	return i.parent
+}
+
+type LinkParent struct {
+	ProjectID          string
+	Location           string
+	LoggingLogBucketID string
+}
+
+func (p *LinkParent) String() string {
+	return "projects/" + p.ProjectID + "/locations/" + p.Location + "/buckets/" + p.LoggingLogBucketID
+}
+
+// New builds a LinkIdentity from the Config Connector Link object.
+func NewLinkIdentity(ctx context.Context, reader client.Reader, obj *LoggingLink) (*LinkIdentity, error) {
+
+	// Get Parent
+	bucketRef, err := refsv1beta1.ResolveLoggingLogBucketRef(ctx, reader, obj, obj.Spec.LoggingLogBucketRef)
+	if err != nil {
+		return nil, err
+	}
+	projectID := bucketRef.ProjectID
+	if projectID == "" {
+		return nil, fmt.Errorf("cannot resolve project")
+	}
+	location := bucketRef.Location
+
+	// 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
+		actualParent, actualResourceID, err := ParseLinkExternal(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)
path: apis/logging/v1alpha1/link_identity.go
user: login: maqiuyujoyce
body: Could you compare `actualParent.String()` and `bucketRef.String()`? There is no `spec.projectRef`, `spec.location`, or `spec.bucketID` in this resource. Comparing the resolved value of loggingLogBucketRef (i.e. the parent) should be more intuitive logic wise and error message wise.
created_at: 2025-03-12T23:56:22Z
author_association: COLLABORATOR
diff_hunk: @@ -0,0 +1,193 @@
+// 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 = &LoggingLinkRef{}
+
+// LoggingLinkRef defines the resource reference to LoggingLink, which "External" field
+// holds the GCP identifier for the KRM object.
+type LoggingLinkRef struct {
+	// A reference to an externally managed LoggingLink resource.
+	// Should be in the format "projects/<projectID>/locations/<location>/buckets/<bucketID>/links/<linkID>".
+	External string `json:"external,omitempty"`
+
+	// The name of a LoggingLink resource.
+	Name string `json:"name,omitempty"`
+
+	// The namespace of a LoggingLink resource.
+	Namespace string `json:"namespace,omitempty"`
+
+	parent *LoggingLinkParent
+}
+
+// NormalizedExternal provision the "External" value for other resource that depends on LoggingLink.
+// If the "External" is given in the other resource's spec.LoggingLinkRef, the given value will be used.
+// Otherwise, the "Name" and "Namespace" will be used to query the actual LoggingLink object from the cluster.
+func (r *LoggingLinkRef) 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", LoggingLinkGVK.Kind)
+	}
+	// From given External
+	if r.External != "" {
+		if _, _, err := parseLoggingLinkExternal(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(LoggingLinkGVK)
+	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", LoggingLinkGVK, 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 LoggingLinkRef from the Config Connector LoggingLink object.
+func NewLoggingLinkRef(ctx context.Context, reader client.Reader, obj *LoggingLink) (*LoggingLinkRef, error) {
path: apis/logging/v1alpha1/link_reference.go
user: login: maqiuyujoyce
body: I think this function (and some relevant structs) is not needed. I can't remember for sure: is it generated or manually added?
created_at: 2025-03-13T00:29:21Z
author_association: COLLABORATOR
diff_hunk: @@ -0,0 +1,238 @@
+// 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 logging
+
+import (
+	"context"
+	"fmt"
+
+	//"reflect"
+
+	krm "github.com/GoogleCloudPlatform/k8s-config-connector/apis/logging/v1alpha1"
+	"github.com/GoogleCloudPlatform/k8s-config-connector/pkg/config"
+	"github.com/GoogleCloudPlatform/k8s-config-connector/pkg/controller/direct"
+	"github.com/GoogleCloudPlatform/k8s-config-connector/pkg/controller/direct/directbase"
+	"github.com/GoogleCloudPlatform/k8s-config-connector/pkg/controller/direct/registry"
+	"github.com/GoogleCloudPlatform/k8s-config-connector/pkg/k8s"
+
+	//"net/http"
+
+	gcp "cloud.google.com/go/logging/apiv2"
+	//"google.golang.org/api/option"
+	loggingpb "cloud.google.com/go/logging/apiv2/loggingpb"
+
+	//"google.golang.org/api/option"
+	//"google.golang.org/protobuf/types/known/fieldmaskpb"
+
+	"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
+	"k8s.io/apimachinery/pkg/runtime"
+	"k8s.io/klog/v2"
+	"sigs.k8s.io/controller-runtime/pkg/client"
+)
+
+func init() {
+	registry.RegisterModel(krm.LoggingLinkGVK, NewLoggingLinkModel)
+}
+
+func NewLoggingLinkModel(ctx context.Context, config *config.ControllerConfig) (directbase.Model, error) {
+	return &modelLoggingLink{config: *config}, nil
+}
+
+var _ directbase.Model = &modelLoggingLink{}
+
+type modelLoggingLink struct {
+	config config.ControllerConfig
+}
+
+func (m *modelLoggingLink) client(ctx context.Context) (*gcp.ConfigClient, error) {
+
+	gcpClient, err := gcp.NewConfigRESTClient(ctx)
+	if err != nil {
+		return nil, fmt.Errorf("building Logging Config client: %w", err)
+	}
+	return gcpClient, err
+}
+
+func (m *modelLoggingLink) AdapterForObject(ctx context.Context, reader client.Reader, u *unstructured.Unstructured) (directbase.Adapter, error) {
+	obj := &krm.LoggingLink{}
+	if err := runtime.DefaultUnstructuredConverter.FromUnstructured(u.Object, &obj); err != nil {
+		return nil, fmt.Errorf("error converting to %T: %w", obj, err)
+	}
+
+	id, err := krm.NewLoggingLinkRef(ctx, reader, obj)
path: pkg/controller/direct/logging/link_controller.go
user: login: maqiuyujoyce
body: I think we should call NewLinkIdentity() here.
created_at: 2025-03-13T00:30:13Z
author_association: COLLABORATOR
diff_hunk: @@ -0,0 +1,104 @@
+// 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 (
+	refs "github.com/GoogleCloudPlatform/k8s-config-connector/apis/refs/v1beta1"
+	"github.com/GoogleCloudPlatform/k8s-config-connector/pkg/apis/k8s/v1alpha1"
+	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+)
+
+var LoggingLinkGVK = GroupVersion.WithKind("LoggingLink")
+
+// LoggingLinkSpec defines the desired state of LoggingLink
+// +kcc:proto=google.logging.v2.Link
+type LoggingLinkSpec struct {
+	// +kubebuilder:validation:XValidation:rule="self == oldSelf",message="ResourceID field is immutable"
path: apis/logging/v1alpha1/link_types.go
user: login: maqiuyujoyce
body: This CEL rule should be removed.

`spec.resourceID` is immutable and optional. The CEL rule doesn't work well for optional fields.
created_at: 2025-03-13T00:33:46Z
author_association: COLLABORATOR
diff_hunk: @@ -0,0 +1,104 @@
+// 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 (
+	refs "github.com/GoogleCloudPlatform/k8s-config-connector/apis/refs/v1beta1"
+	"github.com/GoogleCloudPlatform/k8s-config-connector/pkg/apis/k8s/v1alpha1"
+	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+)
+
+var LoggingLinkGVK = GroupVersion.WithKind("LoggingLink")
+
+// LoggingLinkSpec defines the desired state of LoggingLink
+// +kcc:proto=google.logging.v2.Link
+type LoggingLinkSpec struct {
+	// +kubebuilder:validation:XValidation:rule="self == oldSelf",message="ResourceID field is immutable"
+	// Immutable.
+	// The LoggingLink name. If not given, the metadata.name will be used.
+	ResourceID *string `json:"resourceID,omitempty"`
+
+	// Describes this link.
+	//  The maximum length of the description is 8000 characters.
+	Description *string `json:"description,omitempty"`
+
+	// Placeholder description
path: apis/logging/v1alpha1/link_types.go
user: login: maqiuyujoyce
body: Could you add field description? E.g. `The the bucket to create a link for.`
created_at: 2025-03-13T00:35:42Z
author_association: COLLABORATOR
diff_hunk: @@ -0,0 +1,117 @@
+// 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 v1beta1
+
+import (
+	"context"
+	"fmt"
+	"strings"
+
+	apierrors "k8s.io/apimachinery/pkg/api/errors"
+	"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
+	"k8s.io/apimachinery/pkg/runtime/schema"
+	"k8s.io/apimachinery/pkg/types"
+	"sigs.k8s.io/controller-runtime/pkg/client"
+)
+
+type LoggingLogBucketRef struct {
+	/* The LoggingLogBucket selfLink, when not managed by Config Connector. */
path: apis/refs/v1beta1/logbucketref.go
user: login: maqiuyujoyce
body: Could you add the following to the field description?

`// Should be in the format "projects/{{projectID}}/locations/{{location}}/buckets/{{bucketID}}".`
created_at: 2025-03-13T00:37:58Z
author_association: COLLABORATOR
diff_hunk: @@ -0,0 +1,104 @@
+// 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 (
+	refs "github.com/GoogleCloudPlatform/k8s-config-connector/apis/refs/v1beta1"
+	"github.com/GoogleCloudPlatform/k8s-config-connector/pkg/apis/k8s/v1alpha1"
+	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+)
+
+var LoggingLinkGVK = GroupVersion.WithKind("LoggingLink")
+
+// LoggingLinkSpec defines the desired state of LoggingLink
+// +kcc:proto=google.logging.v2.Link
+type LoggingLinkSpec struct {
+	// +kubebuilder:validation:XValidation:rule="self == oldSelf",message="ResourceID field is immutable"
+	// Immutable.
+	// The LoggingLink name. If not given, the metadata.name will be used.
+	ResourceID *string `json:"resourceID,omitempty"`
+
+	// Describes this link.
+	//  The maximum length of the description is 8000 characters.
+	Description *string `json:"description,omitempty"`
+
+	// Placeholder description
+	LoggingLogBucketRef *refs.LoggingLogBucketRef `json:"loggingLogBucketRef,omitempty"`
+}
+
+// LoggingLinkStatus defines the config connector machine state of LoggingLink
+type LoggingLinkStatus 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 LoggingLink resource in GCP.
+	ExternalRef *string `json:"externalRef,omitempty"`
+
+	// ObservedState is the state of the resource as most recently observed in GCP.
+	ObservedState *LoggingLinkObservedState `json:"observedState,omitempty"`
+}
+
+// LoggingLinkObservedState is the state of the LoggingLink resource as most recently observed in GCP.
+type LoggingLinkObservedState struct {
+
+	// +optional
+	// +kubebuilder:validation:Format=date-time
+	CreateTime *string `json:"createTime,omitempty"`
path: apis/logging/v1alpha1/link_types.go
user: login: maqiuyujoyce
body: Could you add field description? E.g. `// Output only. The creation timestamp of the link.`
created_at: 2025-03-13T00:39:16Z
author_association: COLLABORATOR
diff_hunk: @@ -0,0 +1,104 @@
+// 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 (
+	refs "github.com/GoogleCloudPlatform/k8s-config-connector/apis/refs/v1beta1"
+	"github.com/GoogleCloudPlatform/k8s-config-connector/pkg/apis/k8s/v1alpha1"
+	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+)
+
+var LoggingLinkGVK = GroupVersion.WithKind("LoggingLink")
+
+// LoggingLinkSpec defines the desired state of LoggingLink
+// +kcc:proto=google.logging.v2.Link
+type LoggingLinkSpec struct {
+	// +kubebuilder:validation:XValidation:rule="self == oldSelf",message="ResourceID field is immutable"
+	// Immutable.
+	// The LoggingLink name. If not given, the metadata.name will be used.
+	ResourceID *string `json:"resourceID,omitempty"`
+
+	// Describes this link.
+	//  The maximum length of the description is 8000 characters.
+	Description *string `json:"description,omitempty"`
+
+	// Placeholder description
+	LoggingLogBucketRef *refs.LoggingLogBucketRef `json:"loggingLogBucketRef,omitempty"`
+}
+
+// LoggingLinkStatus defines the config connector machine state of LoggingLink
+type LoggingLinkStatus 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 LoggingLink resource in GCP.
+	ExternalRef *string `json:"externalRef,omitempty"`
+
+	// ObservedState is the state of the resource as most recently observed in GCP.
+	ObservedState *LoggingLinkObservedState `json:"observedState,omitempty"`
+}
+
+// LoggingLinkObservedState is the state of the LoggingLink resource as most recently observed in GCP.
+type LoggingLinkObservedState struct {
+
+	// +optional
+	// +kubebuilder:validation:Format=date-time
+	CreateTime *string `json:"createTime,omitempty"`
+
+	// the lifecycle state might be something more complicated
+	// this is an ENUM, should be safe to use a string
+	LifecycleState *string `json:"lifecycleState,omitempty"`
path: apis/logging/v1alpha1/link_types.go
user: login: maqiuyujoyce
body: If the comment is _just_ comment, could you change it to the following to exclude it from CRD and add field description?

```
	// the lifecycle state might be something more complicated
	// this is an ENUM, should be safe to use a string

        // Output only. The resource lifecycle state.
```
created_at: 2025-03-13T00:40:28Z
author_association: COLLABORATOR
diff_hunk: @@ -0,0 +1,104 @@
+// 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 (
+	refs "github.com/GoogleCloudPlatform/k8s-config-connector/apis/refs/v1beta1"
+	"github.com/GoogleCloudPlatform/k8s-config-connector/pkg/apis/k8s/v1alpha1"
+	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+)
+
+var LoggingLinkGVK = GroupVersion.WithKind("LoggingLink")
+
+// LoggingLinkSpec defines the desired state of LoggingLink
+// +kcc:proto=google.logging.v2.Link
+type LoggingLinkSpec struct {
+	// +kubebuilder:validation:XValidation:rule="self == oldSelf",message="ResourceID field is immutable"
+	// Immutable.
+	// The LoggingLink name. If not given, the metadata.name will be used.
+	ResourceID *string `json:"resourceID,omitempty"`
+
+	// Describes this link.
+	//  The maximum length of the description is 8000 characters.
+	Description *string `json:"description,omitempty"`
+
+	// Placeholder description
+	LoggingLogBucketRef *refs.LoggingLogBucketRef `json:"loggingLogBucketRef,omitempty"`
+}
+
+// LoggingLinkStatus defines the config connector machine state of LoggingLink
+type LoggingLinkStatus 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 LoggingLink resource in GCP.
+	ExternalRef *string `json:"externalRef,omitempty"`
+
+	// ObservedState is the state of the resource as most recently observed in GCP.
+	ObservedState *LoggingLinkObservedState `json:"observedState,omitempty"`
+}
+
+// LoggingLinkObservedState is the state of the LoggingLink resource as most recently observed in GCP.
+type LoggingLinkObservedState struct {
+
+	// +optional
+	// +kubebuilder:validation:Format=date-time
+	CreateTime *string `json:"createTime,omitempty"`
+
+	// the lifecycle state might be something more complicated
+	// this is an ENUM, should be safe to use a string
+	LifecycleState *string `json:"lifecycleState,omitempty"`
+
+	// this field is just a string, but its an object(string)
+	// https://github.com/googleapis/googleapis/blob/master/google/logging/v2/logging_config.proto#L1063
+	BigQueryDataset *BigQueryDatasetObservedState `json:"bigQueryDataset,omitempty"`
path: apis/logging/v1alpha1/link_types.go
user: login: maqiuyujoyce
body: Similarly, could you update the comment to be something like below?

```
	// this field is just a string, but its an object(string)
	// https://github.com/googleapis/googleapis/blob/master/google/logging/v2/logging_config.proto#L1063

	// The information of a BigQuery Dataset. When a link is created, a BigQuery dataset is created
	// along with it, in the same project as the LogBucket it's linked to. This dataset will also have
	// BigQuery Views corresponding to the LogViews in the bucket.
```
created_at: 2025-03-13T00:42:20Z
author_association: COLLABORATOR
diff_hunk: @@ -0,0 +1,117 @@
+// 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 v1beta1
+
+import (
+	"context"
+	"fmt"
+	"strings"
+
+	apierrors "k8s.io/apimachinery/pkg/api/errors"
+	"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
+	"k8s.io/apimachinery/pkg/runtime/schema"
+	"k8s.io/apimachinery/pkg/types"
+	"sigs.k8s.io/controller-runtime/pkg/client"
+)
+
+type LoggingLogBucketRef struct {
path: apis/refs/v1beta1/logbucketref.go
user: login: maqiuyujoyce
body: No action needed, but just a heads up that all reference struct and relevant functions will be moved to [resource]_reference.go once the resource is supported via direct.
created_at: 2025-03-13T00:43:52Z
author_association: COLLABORATOR
diff_hunk: @@ -0,0 +1,104 @@
+// 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 (
+	refs "github.com/GoogleCloudPlatform/k8s-config-connector/apis/refs/v1beta1"
+	"github.com/GoogleCloudPlatform/k8s-config-connector/pkg/apis/k8s/v1alpha1"
+	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+)
+
+var LoggingLinkGVK = GroupVersion.WithKind("LoggingLink")
+
+// LoggingLinkSpec defines the desired state of LoggingLink
+// +kcc:proto=google.logging.v2.Link
+type LoggingLinkSpec struct {
+	// +kubebuilder:validation:XValidation:rule="self == oldSelf",message="ResourceID field is immutable"
+	// Immutable.
+	// The LoggingLink name. If not given, the metadata.name will be used.
+	ResourceID *string `json:"resourceID,omitempty"`
+
+	// Describes this link.
+	//  The maximum length of the description is 8000 characters.
+	Description *string `json:"description,omitempty"`
+
+	// Placeholder description
+	LoggingLogBucketRef *refs.LoggingLogBucketRef `json:"loggingLogBucketRef,omitempty"`
+}
+
+// LoggingLinkStatus defines the config connector machine state of LoggingLink
+type LoggingLinkStatus 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 LoggingLink resource in GCP.
+	ExternalRef *string `json:"externalRef,omitempty"`
+
+	// ObservedState is the state of the resource as most recently observed in GCP.
+	ObservedState *LoggingLinkObservedState `json:"observedState,omitempty"`
+}
+
+// LoggingLinkObservedState is the state of the LoggingLink resource as most recently observed in GCP.
+type LoggingLinkObservedState struct {
+
+	// +optional
+	// +kubebuilder:validation:Format=date-time
path: apis/logging/v1alpha1/link_types.go
user: login: maqiuyujoyce
body: I'm concerned the strict format may cause some issues during the parsing and I think we can drop it for now.
created_at: 2025-03-13T00:45:14Z
author_association: COLLABORATOR
diff_hunk: @@ -0,0 +1,238 @@
+// 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 logging
+
+import (
+	"context"
+	"fmt"
+
+	//"reflect"
+
+	krm "github.com/GoogleCloudPlatform/k8s-config-connector/apis/logging/v1alpha1"
+	"github.com/GoogleCloudPlatform/k8s-config-connector/pkg/config"
+	"github.com/GoogleCloudPlatform/k8s-config-connector/pkg/controller/direct"
+	"github.com/GoogleCloudPlatform/k8s-config-connector/pkg/controller/direct/directbase"
+	"github.com/GoogleCloudPlatform/k8s-config-connector/pkg/controller/direct/registry"
+	"github.com/GoogleCloudPlatform/k8s-config-connector/pkg/k8s"
+
+	//"net/http"
+
+	gcp "cloud.google.com/go/logging/apiv2"
+	//"google.golang.org/api/option"
+	loggingpb "cloud.google.com/go/logging/apiv2/loggingpb"
+
+	//"google.golang.org/api/option"
+	//"google.golang.org/protobuf/types/known/fieldmaskpb"
+
+	"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
+	"k8s.io/apimachinery/pkg/runtime"
+	"k8s.io/klog/v2"
+	"sigs.k8s.io/controller-runtime/pkg/client"
+)
+
+func init() {
+	registry.RegisterModel(krm.LoggingLinkGVK, NewLoggingLinkModel)
+}
+
+func NewLoggingLinkModel(ctx context.Context, config *config.ControllerConfig) (directbase.Model, error) {
+	return &modelLoggingLink{config: *config}, nil
+}
+
+var _ directbase.Model = &modelLoggingLink{}
+
+type modelLoggingLink struct {
+	config config.ControllerConfig
+}
+
+func (m *modelLoggingLink) client(ctx context.Context) (*gcp.ConfigClient, error) {
+
+	gcpClient, err := gcp.NewConfigRESTClient(ctx)
+	if err != nil {
+		return nil, fmt.Errorf("building Logging Config client: %w", err)
+	}
+	return gcpClient, err
+}
+
+func (m *modelLoggingLink) AdapterForObject(ctx context.Context, reader client.Reader, u *unstructured.Unstructured) (directbase.Adapter, error) {
+	obj := &krm.LoggingLink{}
+	if err := runtime.DefaultUnstructuredConverter.FromUnstructured(u.Object, &obj); err != nil {
+		return nil, fmt.Errorf("error converting to %T: %w", obj, err)
+	}
+
+	id, err := krm.NewLoggingLinkRef(ctx, reader, obj)
+	if err != nil {
+		return nil, err
+	}
+
+	gcpClient, err := m.client(ctx)
+	if err != nil {
+		return nil, err
+	}
+	return &LoggingLinkAdapter{
+		id:        id,
+		gcpClient: gcpClient,
+		desired:   obj,
+	}, nil
+}
+
+/*
+func () ResolveExternalRef(externalRef string) {
+
+}
+*/
+
+func (m *modelLoggingLink) AdapterForURL(ctx context.Context, url string) (directbase.Adapter, error) {
+	// TODO: Support URLs
+	return nil, nil
+}
+
+type LoggingLinkAdapter struct {
+	id        *krm.LoggingLinkRef
+	gcpClient *gcp.ConfigClient
+	desired   *krm.LoggingLink
+	actual    *loggingpb.Link
+}
+
+var _ directbase.Adapter = &LoggingLinkAdapter{}
+
+func (a *LoggingLinkAdapter) Find(ctx context.Context) (bool, error) {
+	log := klog.FromContext(ctx)
+	log.V(2).Info("getting LoggingLink", "name", a.id.External)
+
+	req := &loggingpb.GetLinkRequest{Name: a.id.External}
+	linkpb, err := a.gcpClient.GetLink(ctx, req)
+	if err != nil {
+		if direct.IsNotFound(err) {
+			return false, nil
+		}
+		return false, fmt.Errorf("getting LoggingLink %q: %w", a.id.External, err)
+	}
+
+	a.actual = linkpb
+	return true, nil
+}
+
+func (a *LoggingLinkAdapter) Create(ctx context.Context, createOp *directbase.CreateOperation) error {
+	u := createOp.GetUnstructured()
+	log := klog.FromContext(ctx).WithName(ctrlName)
+	log.V(2).Info("creating object", "u", u)
path: pkg/controller/direct/logging/link_controller.go
user: login: maqiuyujoyce
body: Nit: Suggest the log message to be more specific, e.g. `creating LoggingLink...`.

Not blocking.
created_at: 2025-03-13T01:02:02Z
author_association: COLLABORATOR
diff_hunk: @@ -0,0 +1,238 @@
+// 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 logging
+
+import (
+	"context"
+	"fmt"
+
+	//"reflect"
+
+	krm "github.com/GoogleCloudPlatform/k8s-config-connector/apis/logging/v1alpha1"
+	"github.com/GoogleCloudPlatform/k8s-config-connector/pkg/config"
+	"github.com/GoogleCloudPlatform/k8s-config-connector/pkg/controller/direct"
+	"github.com/GoogleCloudPlatform/k8s-config-connector/pkg/controller/direct/directbase"
+	"github.com/GoogleCloudPlatform/k8s-config-connector/pkg/controller/direct/registry"
+	"github.com/GoogleCloudPlatform/k8s-config-connector/pkg/k8s"
+
+	//"net/http"
+
+	gcp "cloud.google.com/go/logging/apiv2"
+	//"google.golang.org/api/option"
+	loggingpb "cloud.google.com/go/logging/apiv2/loggingpb"
+
+	//"google.golang.org/api/option"
+	//"google.golang.org/protobuf/types/known/fieldmaskpb"
+
+	"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
+	"k8s.io/apimachinery/pkg/runtime"
+	"k8s.io/klog/v2"
+	"sigs.k8s.io/controller-runtime/pkg/client"
+)
+
+func init() {
+	registry.RegisterModel(krm.LoggingLinkGVK, NewLoggingLinkModel)
+}
+
+func NewLoggingLinkModel(ctx context.Context, config *config.ControllerConfig) (directbase.Model, error) {
+	return &modelLoggingLink{config: *config}, nil
+}
+
+var _ directbase.Model = &modelLoggingLink{}
+
+type modelLoggingLink struct {
+	config config.ControllerConfig
+}
+
+func (m *modelLoggingLink) client(ctx context.Context) (*gcp.ConfigClient, error) {
+
+	gcpClient, err := gcp.NewConfigRESTClient(ctx)
+	if err != nil {
+		return nil, fmt.Errorf("building Logging Config client: %w", err)
+	}
+	return gcpClient, err
+}
+
+func (m *modelLoggingLink) AdapterForObject(ctx context.Context, reader client.Reader, u *unstructured.Unstructured) (directbase.Adapter, error) {
+	obj := &krm.LoggingLink{}
+	if err := runtime.DefaultUnstructuredConverter.FromUnstructured(u.Object, &obj); err != nil {
+		return nil, fmt.Errorf("error converting to %T: %w", obj, err)
+	}
+
+	id, err := krm.NewLoggingLinkRef(ctx, reader, obj)
+	if err != nil {
+		return nil, err
+	}
+
+	gcpClient, err := m.client(ctx)
+	if err != nil {
+		return nil, err
+	}
+	return &LoggingLinkAdapter{
+		id:        id,
+		gcpClient: gcpClient,
+		desired:   obj,
+	}, nil
+}
+
+/*
+func () ResolveExternalRef(externalRef string) {
+
+}
+*/
+
+func (m *modelLoggingLink) AdapterForURL(ctx context.Context, url string) (directbase.Adapter, error) {
+	// TODO: Support URLs
+	return nil, nil
+}
+
+type LoggingLinkAdapter struct {
+	id        *krm.LoggingLinkRef
+	gcpClient *gcp.ConfigClient
+	desired   *krm.LoggingLink
+	actual    *loggingpb.Link
+}
+
+var _ directbase.Adapter = &LoggingLinkAdapter{}
+
+func (a *LoggingLinkAdapter) Find(ctx context.Context) (bool, error) {
+	log := klog.FromContext(ctx)
+	log.V(2).Info("getting LoggingLink", "name", a.id.External)
+
+	req := &loggingpb.GetLinkRequest{Name: a.id.External}
+	linkpb, err := a.gcpClient.GetLink(ctx, req)
+	if err != nil {
+		if direct.IsNotFound(err) {
+			return false, nil
+		}
+		return false, fmt.Errorf("getting LoggingLink %q: %w", a.id.External, err)
+	}
+
+	a.actual = linkpb
+	return true, nil
+}
+
+func (a *LoggingLinkAdapter) Create(ctx context.Context, createOp *directbase.CreateOperation) error {
+	u := createOp.GetUnstructured()
+	log := klog.FromContext(ctx).WithName(ctrlName)
+	log.V(2).Info("creating object", "u", u)
+
+	mapCtx := &direct.MapContext{}
+
+	desired := a.desired.DeepCopy()
+	resource := LoggingLinkSpec_ToProto(mapCtx, &desired.Spec)
+	if mapCtx.Err() != nil {
+		return mapCtx.Err()
+	}
+
+	// TODO(user): Complete the gcp "CREATE" or "INSERT" request with required fields.
+	// TODO(): 400 error needs to come through on the error
+	// TODO(): there is an implicit dependency on the bucket being active, do we need that here?
+	parent, err := a.id.Parent()
+	if err != nil {
+		return err
+	}
+
+	resourceID := direct.ValueOf(desired.Spec.ResourceID)
+	if resourceID == "" {
+		log.V(2).Info("ResourceID is not set, will use metadata.name")
+		resourceID = desired.Name
+	} else {
+		log.V(2).Info("ResourceID is set, will use")
+		resourceID = *desired.Spec.ResourceID
+	}
+
+	req := &loggingpb.CreateLinkRequest{
+		Parent: parent.String(),
+		Link:   resource,
+		LinkId: resourceID,
+	}
+	op, err := a.gcpClient.CreateLink(ctx, req)
+	if err != nil {
+		return fmt.Errorf("creating Link %s: %w\n", a.id.External, err)
path: pkg/controller/direct/logging/link_controller.go
user: login: maqiuyujoyce
body: `a.id` should be good as it'll automatically call its String() function when resolving the error message. All the usage of `a.id.External` in string value resolution should be replaced with `a.id`.
created_at: 2025-03-13T01:07:34Z
author_association: COLLABORATOR
diff_hunk: @@ -0,0 +1,238 @@
+// 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 logging
+
+import (
+	"context"
+	"fmt"
+
+	//"reflect"
+
+	krm "github.com/GoogleCloudPlatform/k8s-config-connector/apis/logging/v1alpha1"
+	"github.com/GoogleCloudPlatform/k8s-config-connector/pkg/config"
+	"github.com/GoogleCloudPlatform/k8s-config-connector/pkg/controller/direct"
+	"github.com/GoogleCloudPlatform/k8s-config-connector/pkg/controller/direct/directbase"
+	"github.com/GoogleCloudPlatform/k8s-config-connector/pkg/controller/direct/registry"
+	"github.com/GoogleCloudPlatform/k8s-config-connector/pkg/k8s"
+
+	//"net/http"
+
+	gcp "cloud.google.com/go/logging/apiv2"
+	//"google.golang.org/api/option"
+	loggingpb "cloud.google.com/go/logging/apiv2/loggingpb"
+
+	//"google.golang.org/api/option"
+	//"google.golang.org/protobuf/types/known/fieldmaskpb"
+
+	"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
+	"k8s.io/apimachinery/pkg/runtime"
+	"k8s.io/klog/v2"
+	"sigs.k8s.io/controller-runtime/pkg/client"
+)
+
+func init() {
+	registry.RegisterModel(krm.LoggingLinkGVK, NewLoggingLinkModel)
+}
+
+func NewLoggingLinkModel(ctx context.Context, config *config.ControllerConfig) (directbase.Model, error) {
+	return &modelLoggingLink{config: *config}, nil
+}
+
+var _ directbase.Model = &modelLoggingLink{}
+
+type modelLoggingLink struct {
+	config config.ControllerConfig
+}
+
+func (m *modelLoggingLink) client(ctx context.Context) (*gcp.ConfigClient, error) {
+
+	gcpClient, err := gcp.NewConfigRESTClient(ctx)
+	if err != nil {
+		return nil, fmt.Errorf("building Logging Config client: %w", err)
+	}
+	return gcpClient, err
+}
+
+func (m *modelLoggingLink) AdapterForObject(ctx context.Context, reader client.Reader, u *unstructured.Unstructured) (directbase.Adapter, error) {
+	obj := &krm.LoggingLink{}
+	if err := runtime.DefaultUnstructuredConverter.FromUnstructured(u.Object, &obj); err != nil {
+		return nil, fmt.Errorf("error converting to %T: %w", obj, err)
+	}
+
+	id, err := krm.NewLoggingLinkRef(ctx, reader, obj)
+	if err != nil {
+		return nil, err
+	}
+
+	gcpClient, err := m.client(ctx)
+	if err != nil {
+		return nil, err
+	}
+	return &LoggingLinkAdapter{
+		id:        id,
+		gcpClient: gcpClient,
+		desired:   obj,
+	}, nil
+}
+
+/*
+func () ResolveExternalRef(externalRef string) {
+
+}
+*/
+
+func (m *modelLoggingLink) AdapterForURL(ctx context.Context, url string) (directbase.Adapter, error) {
+	// TODO: Support URLs
+	return nil, nil
+}
+
+type LoggingLinkAdapter struct {
+	id        *krm.LoggingLinkRef
+	gcpClient *gcp.ConfigClient
+	desired   *krm.LoggingLink
+	actual    *loggingpb.Link
+}
+
+var _ directbase.Adapter = &LoggingLinkAdapter{}
+
+func (a *LoggingLinkAdapter) Find(ctx context.Context) (bool, error) {
+	log := klog.FromContext(ctx)
+	log.V(2).Info("getting LoggingLink", "name", a.id.External)
+
+	req := &loggingpb.GetLinkRequest{Name: a.id.External}
+	linkpb, err := a.gcpClient.GetLink(ctx, req)
+	if err != nil {
+		if direct.IsNotFound(err) {
+			return false, nil
+		}
+		return false, fmt.Errorf("getting LoggingLink %q: %w", a.id.External, err)
+	}
+
+	a.actual = linkpb
+	return true, nil
+}
+
+func (a *LoggingLinkAdapter) Create(ctx context.Context, createOp *directbase.CreateOperation) error {
+	u := createOp.GetUnstructured()
+	log := klog.FromContext(ctx).WithName(ctrlName)
+	log.V(2).Info("creating object", "u", u)
+
+	mapCtx := &direct.MapContext{}
+
+	desired := a.desired.DeepCopy()
+	resource := LoggingLinkSpec_ToProto(mapCtx, &desired.Spec)
+	if mapCtx.Err() != nil {
+		return mapCtx.Err()
+	}
+
+	// TODO(user): Complete the gcp "CREATE" or "INSERT" request with required fields.
+	// TODO(): 400 error needs to come through on the error
+	// TODO(): there is an implicit dependency on the bucket being active, do we need that here?
+	parent, err := a.id.Parent()
+	if err != nil {
+		return err
+	}
+
+	resourceID := direct.ValueOf(desired.Spec.ResourceID)
+	if resourceID == "" {
+		log.V(2).Info("ResourceID is not set, will use metadata.name")
+		resourceID = desired.Name
+	} else {
+		log.V(2).Info("ResourceID is set, will use")
+		resourceID = *desired.Spec.ResourceID
+	}
+
+	req := &loggingpb.CreateLinkRequest{
+		Parent: parent.String(),
+		Link:   resource,
+		LinkId: resourceID,
+	}
+	op, err := a.gcpClient.CreateLink(ctx, req)
+	if err != nil {
+		return fmt.Errorf("creating Link %s: %w\n", a.id.External, err)
+	}
+	created, err := op.Wait(ctx)
+	if err != nil {
+		return fmt.Errorf("Link %s waiting creation: %w", a.id.External, err)
path: pkg/controller/direct/logging/link_controller.go
user: login: maqiuyujoyce
body: ditto
created_at: 2025-03-13T01:07:41Z
author_association: COLLABORATOR
diff_hunk: @@ -0,0 +1,238 @@
+// 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 logging
+
+import (
+	"context"
+	"fmt"
+
+	//"reflect"
+
+	krm "github.com/GoogleCloudPlatform/k8s-config-connector/apis/logging/v1alpha1"
+	"github.com/GoogleCloudPlatform/k8s-config-connector/pkg/config"
+	"github.com/GoogleCloudPlatform/k8s-config-connector/pkg/controller/direct"
+	"github.com/GoogleCloudPlatform/k8s-config-connector/pkg/controller/direct/directbase"
+	"github.com/GoogleCloudPlatform/k8s-config-connector/pkg/controller/direct/registry"
+	"github.com/GoogleCloudPlatform/k8s-config-connector/pkg/k8s"
+
+	//"net/http"
+
+	gcp "cloud.google.com/go/logging/apiv2"
+	//"google.golang.org/api/option"
+	loggingpb "cloud.google.com/go/logging/apiv2/loggingpb"
+
+	//"google.golang.org/api/option"
+	//"google.golang.org/protobuf/types/known/fieldmaskpb"
+
+	"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
+	"k8s.io/apimachinery/pkg/runtime"
+	"k8s.io/klog/v2"
+	"sigs.k8s.io/controller-runtime/pkg/client"
+)
+
+func init() {
+	registry.RegisterModel(krm.LoggingLinkGVK, NewLoggingLinkModel)
+}
+
+func NewLoggingLinkModel(ctx context.Context, config *config.ControllerConfig) (directbase.Model, error) {
+	return &modelLoggingLink{config: *config}, nil
+}
+
+var _ directbase.Model = &modelLoggingLink{}
+
+type modelLoggingLink struct {
+	config config.ControllerConfig
+}
+
+func (m *modelLoggingLink) client(ctx context.Context) (*gcp.ConfigClient, error) {
+
+	gcpClient, err := gcp.NewConfigRESTClient(ctx)
+	if err != nil {
+		return nil, fmt.Errorf("building Logging Config client: %w", err)
+	}
+	return gcpClient, err
+}
+
+func (m *modelLoggingLink) AdapterForObject(ctx context.Context, reader client.Reader, u *unstructured.Unstructured) (directbase.Adapter, error) {
+	obj := &krm.LoggingLink{}
+	if err := runtime.DefaultUnstructuredConverter.FromUnstructured(u.Object, &obj); err != nil {
+		return nil, fmt.Errorf("error converting to %T: %w", obj, err)
+	}
+
+	id, err := krm.NewLoggingLinkRef(ctx, reader, obj)
+	if err != nil {
+		return nil, err
+	}
+
+	gcpClient, err := m.client(ctx)
+	if err != nil {
+		return nil, err
+	}
+	return &LoggingLinkAdapter{
+		id:        id,
+		gcpClient: gcpClient,
+		desired:   obj,
+	}, nil
+}
+
+/*
+func () ResolveExternalRef(externalRef string) {
+
+}
+*/
+
+func (m *modelLoggingLink) AdapterForURL(ctx context.Context, url string) (directbase.Adapter, error) {
+	// TODO: Support URLs
+	return nil, nil
+}
+
+type LoggingLinkAdapter struct {
+	id        *krm.LoggingLinkRef
+	gcpClient *gcp.ConfigClient
+	desired   *krm.LoggingLink
+	actual    *loggingpb.Link
+}
+
+var _ directbase.Adapter = &LoggingLinkAdapter{}
+
+func (a *LoggingLinkAdapter) Find(ctx context.Context) (bool, error) {
+	log := klog.FromContext(ctx)
+	log.V(2).Info("getting LoggingLink", "name", a.id.External)
+
+	req := &loggingpb.GetLinkRequest{Name: a.id.External}
+	linkpb, err := a.gcpClient.GetLink(ctx, req)
+	if err != nil {
+		if direct.IsNotFound(err) {
+			return false, nil
+		}
+		return false, fmt.Errorf("getting LoggingLink %q: %w", a.id.External, err)
+	}
+
+	a.actual = linkpb
+	return true, nil
+}
+
+func (a *LoggingLinkAdapter) Create(ctx context.Context, createOp *directbase.CreateOperation) error {
+	u := createOp.GetUnstructured()
+	log := klog.FromContext(ctx).WithName(ctrlName)
+	log.V(2).Info("creating object", "u", u)
+
+	mapCtx := &direct.MapContext{}
+
+	desired := a.desired.DeepCopy()
+	resource := LoggingLinkSpec_ToProto(mapCtx, &desired.Spec)
+	if mapCtx.Err() != nil {
+		return mapCtx.Err()
+	}
+
+	// TODO(user): Complete the gcp "CREATE" or "INSERT" request with required fields.
+	// TODO(): 400 error needs to come through on the error
+	// TODO(): there is an implicit dependency on the bucket being active, do we need that here?
+	parent, err := a.id.Parent()
+	if err != nil {
+		return err
+	}
+
+	resourceID := direct.ValueOf(desired.Spec.ResourceID)
+	if resourceID == "" {
+		log.V(2).Info("ResourceID is not set, will use metadata.name")
+		resourceID = desired.Name
+	} else {
+		log.V(2).Info("ResourceID is set, will use")
+		resourceID = *desired.Spec.ResourceID
+	}
+
+	req := &loggingpb.CreateLinkRequest{
+		Parent: parent.String(),
+		Link:   resource,
+		LinkId: resourceID,
+	}
+	op, err := a.gcpClient.CreateLink(ctx, req)
+	if err != nil {
+		return fmt.Errorf("creating Link %s: %w\n", a.id.External, err)
+	}
+	created, err := op.Wait(ctx)
+	if err != nil {
+		return fmt.Errorf("Link %s waiting creation: %w", a.id.External, err)
+	}
+	log.V(2).Info("successfully created Link", "name", a.id.External)
path: pkg/controller/direct/logging/link_controller.go
user: login: maqiuyujoyce
body: ditto
created_at: 2025-03-13T01:07:48Z
author_association: COLLABORATOR
diff_hunk: @@ -0,0 +1,238 @@
+// 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 logging
+
+import (
+	"context"
+	"fmt"
+
+	//"reflect"
+
+	krm "github.com/GoogleCloudPlatform/k8s-config-connector/apis/logging/v1alpha1"
+	"github.com/GoogleCloudPlatform/k8s-config-connector/pkg/config"
+	"github.com/GoogleCloudPlatform/k8s-config-connector/pkg/controller/direct"
+	"github.com/GoogleCloudPlatform/k8s-config-connector/pkg/controller/direct/directbase"
+	"github.com/GoogleCloudPlatform/k8s-config-connector/pkg/controller/direct/registry"
+	"github.com/GoogleCloudPlatform/k8s-config-connector/pkg/k8s"
+
+	//"net/http"
+
+	gcp "cloud.google.com/go/logging/apiv2"
+	//"google.golang.org/api/option"
+	loggingpb "cloud.google.com/go/logging/apiv2/loggingpb"
+
+	//"google.golang.org/api/option"
+	//"google.golang.org/protobuf/types/known/fieldmaskpb"
+
+	"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
+	"k8s.io/apimachinery/pkg/runtime"
+	"k8s.io/klog/v2"
+	"sigs.k8s.io/controller-runtime/pkg/client"
+)
+
+func init() {
+	registry.RegisterModel(krm.LoggingLinkGVK, NewLoggingLinkModel)
+}
+
+func NewLoggingLinkModel(ctx context.Context, config *config.ControllerConfig) (directbase.Model, error) {
+	return &modelLoggingLink{config: *config}, nil
+}
+
+var _ directbase.Model = &modelLoggingLink{}
+
+type modelLoggingLink struct {
+	config config.ControllerConfig
+}
+
+func (m *modelLoggingLink) client(ctx context.Context) (*gcp.ConfigClient, error) {
+
+	gcpClient, err := gcp.NewConfigRESTClient(ctx)
+	if err != nil {
+		return nil, fmt.Errorf("building Logging Config client: %w", err)
+	}
+	return gcpClient, err
+}
+
+func (m *modelLoggingLink) AdapterForObject(ctx context.Context, reader client.Reader, u *unstructured.Unstructured) (directbase.Adapter, error) {
+	obj := &krm.LoggingLink{}
+	if err := runtime.DefaultUnstructuredConverter.FromUnstructured(u.Object, &obj); err != nil {
+		return nil, fmt.Errorf("error converting to %T: %w", obj, err)
+	}
+
+	id, err := krm.NewLoggingLinkRef(ctx, reader, obj)
+	if err != nil {
+		return nil, err
+	}
+
+	gcpClient, err := m.client(ctx)
+	if err != nil {
+		return nil, err
+	}
+	return &LoggingLinkAdapter{
+		id:        id,
+		gcpClient: gcpClient,
+		desired:   obj,
+	}, nil
+}
+
+/*
+func () ResolveExternalRef(externalRef string) {
+
+}
+*/
+
+func (m *modelLoggingLink) AdapterForURL(ctx context.Context, url string) (directbase.Adapter, error) {
+	// TODO: Support URLs
+	return nil, nil
+}
+
+type LoggingLinkAdapter struct {
+	id        *krm.LoggingLinkRef
+	gcpClient *gcp.ConfigClient
+	desired   *krm.LoggingLink
+	actual    *loggingpb.Link
+}
+
+var _ directbase.Adapter = &LoggingLinkAdapter{}
+
+func (a *LoggingLinkAdapter) Find(ctx context.Context) (bool, error) {
+	log := klog.FromContext(ctx)
+	log.V(2).Info("getting LoggingLink", "name", a.id.External)
+
+	req := &loggingpb.GetLinkRequest{Name: a.id.External}
+	linkpb, err := a.gcpClient.GetLink(ctx, req)
+	if err != nil {
+		if direct.IsNotFound(err) {
+			return false, nil
+		}
+		return false, fmt.Errorf("getting LoggingLink %q: %w", a.id.External, err)
+	}
+
+	a.actual = linkpb
+	return true, nil
+}
+
+func (a *LoggingLinkAdapter) Create(ctx context.Context, createOp *directbase.CreateOperation) error {
+	u := createOp.GetUnstructured()
+	log := klog.FromContext(ctx).WithName(ctrlName)
+	log.V(2).Info("creating object", "u", u)
+
+	mapCtx := &direct.MapContext{}
+
+	desired := a.desired.DeepCopy()
+	resource := LoggingLinkSpec_ToProto(mapCtx, &desired.Spec)
+	if mapCtx.Err() != nil {
+		return mapCtx.Err()
+	}
+
+	// TODO(user): Complete the gcp "CREATE" or "INSERT" request with required fields.
+	// TODO(): 400 error needs to come through on the error
+	// TODO(): there is an implicit dependency on the bucket being active, do we need that here?
+	parent, err := a.id.Parent()
+	if err != nil {
+		return err
+	}
+
+	resourceID := direct.ValueOf(desired.Spec.ResourceID)
+	if resourceID == "" {
+		log.V(2).Info("ResourceID is not set, will use metadata.name")
+		resourceID = desired.Name
+	} else {
+		log.V(2).Info("ResourceID is set, will use")
+		resourceID = *desired.Spec.ResourceID
+	}
+
+	req := &loggingpb.CreateLinkRequest{
+		Parent: parent.String(),
+		Link:   resource,
+		LinkId: resourceID,
+	}
+	op, err := a.gcpClient.CreateLink(ctx, req)
+	if err != nil {
+		return fmt.Errorf("creating Link %s: %w\n", a.id.External, err)
+	}
+	created, err := op.Wait(ctx)
+	if err != nil {
+		return fmt.Errorf("Link %s waiting creation: %w", a.id.External, err)
+	}
+	log.V(2).Info("successfully created Link", "name", a.id.External)
+
+	status := &krm.LoggingLinkStatus{}
+	status.ObservedState = LoggingLinkObservedState_FromProto(mapCtx, created)
+	if mapCtx.Err() != nil {
+		return mapCtx.Err()
+	}
+	status.ExternalRef = &a.id.External
path: pkg/controller/direct/logging/link_controller.go
user: login: maqiuyujoyce
body: Should be `direct.LazyPtr(a.id.String())`. Similarly for other places when `a.id.External` is used, use `a.id.String()` instead.
created_at: 2025-03-13T01:08:27Z
author_association: COLLABORATOR
diff_hunk: @@ -0,0 +1,238 @@
+// 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 logging
+
+import (
+	"context"
+	"fmt"
+
+	//"reflect"
+
+	krm "github.com/GoogleCloudPlatform/k8s-config-connector/apis/logging/v1alpha1"
+	"github.com/GoogleCloudPlatform/k8s-config-connector/pkg/config"
+	"github.com/GoogleCloudPlatform/k8s-config-connector/pkg/controller/direct"
+	"github.com/GoogleCloudPlatform/k8s-config-connector/pkg/controller/direct/directbase"
+	"github.com/GoogleCloudPlatform/k8s-config-connector/pkg/controller/direct/registry"
+	"github.com/GoogleCloudPlatform/k8s-config-connector/pkg/k8s"
+
+	//"net/http"
+
+	gcp "cloud.google.com/go/logging/apiv2"
+	//"google.golang.org/api/option"
+	loggingpb "cloud.google.com/go/logging/apiv2/loggingpb"
+
+	//"google.golang.org/api/option"
+	//"google.golang.org/protobuf/types/known/fieldmaskpb"
+
+	"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
+	"k8s.io/apimachinery/pkg/runtime"
+	"k8s.io/klog/v2"
+	"sigs.k8s.io/controller-runtime/pkg/client"
+)
+
+func init() {
+	registry.RegisterModel(krm.LoggingLinkGVK, NewLoggingLinkModel)
+}
+
+func NewLoggingLinkModel(ctx context.Context, config *config.ControllerConfig) (directbase.Model, error) {
+	return &modelLoggingLink{config: *config}, nil
+}
+
+var _ directbase.Model = &modelLoggingLink{}
+
+type modelLoggingLink struct {
+	config config.ControllerConfig
+}
+
+func (m *modelLoggingLink) client(ctx context.Context) (*gcp.ConfigClient, error) {
+
+	gcpClient, err := gcp.NewConfigRESTClient(ctx)
+	if err != nil {
+		return nil, fmt.Errorf("building Logging Config client: %w", err)
+	}
+	return gcpClient, err
+}
+
+func (m *modelLoggingLink) AdapterForObject(ctx context.Context, reader client.Reader, u *unstructured.Unstructured) (directbase.Adapter, error) {
+	obj := &krm.LoggingLink{}
+	if err := runtime.DefaultUnstructuredConverter.FromUnstructured(u.Object, &obj); err != nil {
+		return nil, fmt.Errorf("error converting to %T: %w", obj, err)
+	}
+
+	id, err := krm.NewLoggingLinkRef(ctx, reader, obj)
+	if err != nil {
+		return nil, err
+	}
+
+	gcpClient, err := m.client(ctx)
+	if err != nil {
+		return nil, err
+	}
+	return &LoggingLinkAdapter{
+		id:        id,
+		gcpClient: gcpClient,
+		desired:   obj,
+	}, nil
+}
+
+/*
+func () ResolveExternalRef(externalRef string) {
+
+}
+*/
+
+func (m *modelLoggingLink) AdapterForURL(ctx context.Context, url string) (directbase.Adapter, error) {
+	// TODO: Support URLs
+	return nil, nil
+}
+
+type LoggingLinkAdapter struct {
+	id        *krm.LoggingLinkRef
+	gcpClient *gcp.ConfigClient
+	desired   *krm.LoggingLink
+	actual    *loggingpb.Link
+}
+
+var _ directbase.Adapter = &LoggingLinkAdapter{}
+
+func (a *LoggingLinkAdapter) Find(ctx context.Context) (bool, error) {
+	log := klog.FromContext(ctx)
+	log.V(2).Info("getting LoggingLink", "name", a.id.External)
+
+	req := &loggingpb.GetLinkRequest{Name: a.id.External}
+	linkpb, err := a.gcpClient.GetLink(ctx, req)
+	if err != nil {
+		if direct.IsNotFound(err) {
+			return false, nil
+		}
+		return false, fmt.Errorf("getting LoggingLink %q: %w", a.id.External, err)
+	}
+
+	a.actual = linkpb
+	return true, nil
+}
+
+func (a *LoggingLinkAdapter) Create(ctx context.Context, createOp *directbase.CreateOperation) error {
+	u := createOp.GetUnstructured()
+	log := klog.FromContext(ctx).WithName(ctrlName)
+	log.V(2).Info("creating object", "u", u)
+
+	mapCtx := &direct.MapContext{}
+
+	desired := a.desired.DeepCopy()
+	resource := LoggingLinkSpec_ToProto(mapCtx, &desired.Spec)
+	if mapCtx.Err() != nil {
+		return mapCtx.Err()
+	}
+
+	// TODO(user): Complete the gcp "CREATE" or "INSERT" request with required fields.
+	// TODO(): 400 error needs to come through on the error
+	// TODO(): there is an implicit dependency on the bucket being active, do we need that here?
+	parent, err := a.id.Parent()
+	if err != nil {
+		return err
+	}
+
+	resourceID := direct.ValueOf(desired.Spec.ResourceID)
+	if resourceID == "" {
+		log.V(2).Info("ResourceID is not set, will use metadata.name")
+		resourceID = desired.Name
+	} else {
+		log.V(2).Info("ResourceID is set, will use")
+		resourceID = *desired.Spec.ResourceID
+	}
+
+	req := &loggingpb.CreateLinkRequest{
+		Parent: parent.String(),
+		Link:   resource,
+		LinkId: resourceID,
+	}
+	op, err := a.gcpClient.CreateLink(ctx, req)
+	if err != nil {
+		return fmt.Errorf("creating Link %s: %w\n", a.id.External, err)
+	}
+	created, err := op.Wait(ctx)
+	if err != nil {
+		return fmt.Errorf("Link %s waiting creation: %w", a.id.External, err)
+	}
+	log.V(2).Info("successfully created Link", "name", a.id.External)
+
+	status := &krm.LoggingLinkStatus{}
+	status.ObservedState = LoggingLinkObservedState_FromProto(mapCtx, created)
+	if mapCtx.Err() != nil {
+		return mapCtx.Err()
+	}
+	status.ExternalRef = &a.id.External
+	return createOp.UpdateStatus(ctx, status, nil)
+}
+
+func (a *LoggingLinkAdapter) Update(ctx context.Context, updateOp *directbase.UpdateOperation) error {
+	return fmt.Errorf("update operation not supported for resource %v %v",
+		a.desired.GroupVersionKind(), k8s.GetNamespacedName(a.desired))
+}
+
+func (a *LoggingLinkAdapter) Export(ctx context.Context) (*unstructured.Unstructured, error) {
+	if a.actual == nil {
+		return nil, fmt.Errorf("Find() not called")
+	}
+	u := &unstructured.Unstructured{}
+
+	obj := &krm.LoggingLink{}
+	mapCtx := &direct.MapContext{}
+	obj.Spec = direct.ValueOf(LoggingLinkSpec_FromProto(mapCtx, a.actual))
+	if mapCtx.Err() != nil {
+		return nil, mapCtx.Err()
+	}
+
+	// TODO(user): Update other resource references
+	/* TODO Bucket Ref?
+	parent, err := a.id.Parent()
+	if err != nil {
+		return nil, err
+	}
+	 obj.Spec.ProjectRef = &refs.ProjectRef{External: parent.String()}
+	 obj.Spec.Location = parent.Location
+	uObj, err := runtime.DefaultUnstructuredConverter.ToUnstructured(obj)
+	if err != nil {
+		return nil, err
+	}
+	*/
+
+	u.SetName(a.actual.Name)
+	u.SetGroupVersionKind(krm.LoggingLinkGVK)
+
+	// TODO uObj is set in the commented out code
+	//u.Object = uObj
+	return u, nil
+}
+
+// Delete implements the Adapter interface.
+func (a *LoggingLinkAdapter) Delete(ctx context.Context, deleteOp *directbase.DeleteOperation) (bool, error) {
+	log := klog.FromContext(ctx)
+	log.V(2).Info("deleting Link", "name", a.id.External)
+
+	req := &loggingpb.DeleteLinkRequest{Name: a.id.External}
+	op, err := a.gcpClient.DeleteLink(ctx, req)
+	if err != nil {
+		return false, fmt.Errorf("deleting Link %s: %w", a.id.External, err)
path: pkg/controller/direct/logging/link_controller.go
user: login: maqiuyujoyce
body: A special handling for NotFound error would be useful here.
created_at: 2025-03-13T01:14:24Z
author_association: COLLABORATOR
diff_hunk: @@ -0,0 +1,47 @@
+# Copyright 2024 Google LLC
path: pkg/test/resourcefixture/testdata/basic/logging/v1alpha1/logginglogginglink/basiclink/_generated_object_projectlogbucket.golden.yaml
user: login: maqiuyujoyce
body: Could you remove this file? I think it won't be used or updated because the name doesn't match the testcase folder name.
created_at: 2025-03-13T01:21:37Z
author_association: COLLABORATOR
diff_hunk: @@ -0,0 +1,22 @@
+# Copyright 2022 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.
+
+apiVersion: logging.cnrm.cloud.google.com/v1alpha1
+kind: LoggingLink
+metadata:
+  name: logginglink${uniqueId} # no hyphen here as link ids can only have alphanumeric and underscores
path: pkg/test/resourcefixture/testdata/basic/logging/v1alpha1/logginglogginglink/basiclink/create.yaml
user: login: maqiuyujoyce
body: If no hyphen is supported in Logging link resource, I suggest you use `spec.resourceID` to specify the GCP resource ID, and add the hyphen back here. One reason we introduced `spec.resourceID` was to solve the problem about inconsistent naming conventions between K8s objects and GCP resources.
created_at: 2025-03-13T01:23:33Z
author_association: COLLABORATOR
diff_hunk: @@ -0,0 +1,22 @@
+# Copyright 2022 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.
+
+apiVersion: logging.cnrm.cloud.google.com/v1alpha1
+kind: LoggingLink
+metadata:
+  name: logginglink${uniqueId} # no hyphen here as link ids can only have alphanumeric and underscores
+spec:
+  loggingLogBucketRef:
+    external: "projects/${projectId}/locations/global/buckets/logginglogbucket-${uniqueId}"
path: pkg/test/resourcefixture/testdata/basic/logging/v1alpha1/logginglogginglink/basiclink/create.yaml
user: login: maqiuyujoyce
body: I suggest you use `name: logginglogbucket-${uniqueId}` to reference the dependent resource. This is a more common usage and will cover more code logic.
created_at: 2025-03-13T01:24:35Z
author_association: COLLABORATOR
diff_hunk: @@ -0,0 +1,238 @@
+// 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 logging
+
+import (
+	"context"
+	"fmt"
+
+	//"reflect"
+
+	krm "github.com/GoogleCloudPlatform/k8s-config-connector/apis/logging/v1alpha1"
+	"github.com/GoogleCloudPlatform/k8s-config-connector/pkg/config"
+	"github.com/GoogleCloudPlatform/k8s-config-connector/pkg/controller/direct"
+	"github.com/GoogleCloudPlatform/k8s-config-connector/pkg/controller/direct/directbase"
+	"github.com/GoogleCloudPlatform/k8s-config-connector/pkg/controller/direct/registry"
+	"github.com/GoogleCloudPlatform/k8s-config-connector/pkg/k8s"
+
+	//"net/http"
+
+	gcp "cloud.google.com/go/logging/apiv2"
+	//"google.golang.org/api/option"
+	loggingpb "cloud.google.com/go/logging/apiv2/loggingpb"
+
+	//"google.golang.org/api/option"
+	//"google.golang.org/protobuf/types/known/fieldmaskpb"
+
+	"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
+	"k8s.io/apimachinery/pkg/runtime"
+	"k8s.io/klog/v2"
+	"sigs.k8s.io/controller-runtime/pkg/client"
+)
+
+func init() {
+	registry.RegisterModel(krm.LoggingLinkGVK, NewLoggingLinkModel)
+}
+
+func NewLoggingLinkModel(ctx context.Context, config *config.ControllerConfig) (directbase.Model, error) {
+	return &modelLoggingLink{config: *config}, nil
+}
+
+var _ directbase.Model = &modelLoggingLink{}
+
+type modelLoggingLink struct {
+	config config.ControllerConfig
+}
+
+func (m *modelLoggingLink) client(ctx context.Context) (*gcp.ConfigClient, error) {
+
+	gcpClient, err := gcp.NewConfigRESTClient(ctx)
+	if err != nil {
+		return nil, fmt.Errorf("building Logging Config client: %w", err)
+	}
+	return gcpClient, err
+}
+
+func (m *modelLoggingLink) AdapterForObject(ctx context.Context, reader client.Reader, u *unstructured.Unstructured) (directbase.Adapter, error) {
+	obj := &krm.LoggingLink{}
+	if err := runtime.DefaultUnstructuredConverter.FromUnstructured(u.Object, &obj); err != nil {
+		return nil, fmt.Errorf("error converting to %T: %w", obj, err)
+	}
+
+	id, err := krm.NewLoggingLinkRef(ctx, reader, obj)
+	if err != nil {
+		return nil, err
+	}
+
+	gcpClient, err := m.client(ctx)
+	if err != nil {
+		return nil, err
+	}
+	return &LoggingLinkAdapter{
+		id:        id,
+		gcpClient: gcpClient,
+		desired:   obj,
+	}, nil
+}
+
+/*
+func () ResolveExternalRef(externalRef string) {
+
+}
+*/
+
+func (m *modelLoggingLink) AdapterForURL(ctx context.Context, url string) (directbase.Adapter, error) {
+	// TODO: Support URLs
+	return nil, nil
+}
+
+type LoggingLinkAdapter struct {
+	id        *krm.LoggingLinkRef
+	gcpClient *gcp.ConfigClient
+	desired   *krm.LoggingLink
+	actual    *loggingpb.Link
+}
+
+var _ directbase.Adapter = &LoggingLinkAdapter{}
+
+func (a *LoggingLinkAdapter) Find(ctx context.Context) (bool, error) {
+	log := klog.FromContext(ctx)
+	log.V(2).Info("getting LoggingLink", "name", a.id.External)
+
+	req := &loggingpb.GetLinkRequest{Name: a.id.External}
+	linkpb, err := a.gcpClient.GetLink(ctx, req)
+	if err != nil {
+		if direct.IsNotFound(err) {
+			return false, nil
+		}
+		return false, fmt.Errorf("getting LoggingLink %q: %w", a.id.External, err)
+	}
+
+	a.actual = linkpb
+	return true, nil
+}
+
+func (a *LoggingLinkAdapter) Create(ctx context.Context, createOp *directbase.CreateOperation) error {
+	u := createOp.GetUnstructured()
+	log := klog.FromContext(ctx).WithName(ctrlName)
+	log.V(2).Info("creating object", "u", u)
+
+	mapCtx := &direct.MapContext{}
+
+	desired := a.desired.DeepCopy()
+	resource := LoggingLinkSpec_ToProto(mapCtx, &desired.Spec)
+	if mapCtx.Err() != nil {
+		return mapCtx.Err()
+	}
+
+	// TODO(user): Complete the gcp "CREATE" or "INSERT" request with required fields.
+	// TODO(): 400 error needs to come through on the error
+	// TODO(): there is an implicit dependency on the bucket being active, do we need that here?
+	parent, err := a.id.Parent()
+	if err != nil {
+		return err
+	}
+
+	resourceID := direct.ValueOf(desired.Spec.ResourceID)
+	if resourceID == "" {
+		log.V(2).Info("ResourceID is not set, will use metadata.name")
+		resourceID = desired.Name
+	} else {
+		log.V(2).Info("ResourceID is set, will use")
+		resourceID = *desired.Spec.ResourceID
+	}
+
+	req := &loggingpb.CreateLinkRequest{
+		Parent: parent.String(),
+		Link:   resource,
+		LinkId: resourceID,
+	}
+	op, err := a.gcpClient.CreateLink(ctx, req)
+	if err != nil {
+		return fmt.Errorf("creating Link %s: %w\n", a.id.External, err)
+	}
+	created, err := op.Wait(ctx)
+	if err != nil {
+		return fmt.Errorf("Link %s waiting creation: %w", a.id.External, err)
+	}
+	log.V(2).Info("successfully created Link", "name", a.id.External)
+
+	status := &krm.LoggingLinkStatus{}
+	status.ObservedState = LoggingLinkObservedState_FromProto(mapCtx, created)
+	if mapCtx.Err() != nil {
+		return mapCtx.Err()
+	}
+	status.ExternalRef = &a.id.External
+	return createOp.UpdateStatus(ctx, status, nil)
+}
+
+func (a *LoggingLinkAdapter) Update(ctx context.Context, updateOp *directbase.UpdateOperation) error {
+	return fmt.Errorf("update operation not supported for resource %v %v",
path: pkg/controller/direct/logging/link_controller.go
user: login: maqiuyujoyce
body: Directly returning this error will block the resource from normal re-reconciliation and acquisition. In KCC, a resource is re-reconciled every 10-ish minutes. The directbase controller will identify that an underlying GCP resource already exists, so will call Update() instead of Create() for reconciliation. 

Here, you'll want to compare actual and desired, and only return this error when there is a diff. Here is an example of comparison: https://github.com/GoogleCloudPlatform/k8s-config-connector/blob/master/pkg/controller/direct/workstations/workstation_controller.go#L192.
created_at: 2025-03-13T01:32:40Z
author_association: COLLABORATOR
diff_hunk: @@ -0,0 +1,117 @@
+// 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 v1beta1
+
+import (
+	"context"
+	"fmt"
+	"strings"
+
+	apierrors "k8s.io/apimachinery/pkg/api/errors"
+	"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
+	"k8s.io/apimachinery/pkg/runtime/schema"
+	"k8s.io/apimachinery/pkg/types"
+	"sigs.k8s.io/controller-runtime/pkg/client"
+)
+
+type LoggingLogBucketRef struct {
+	/* The LoggingLogBucket selfLink, when not managed by Config Connector. */
+	External string `json:"external,omitempty"`
+	/* The `name` field of a `LoggingLogBucket` resource. */
+	Name string `json:"name,omitempty"`
+	/* The `namespace` field of a `LoggingLogBucket` resource. */
+	Namespace string `json:"namespace,omitempty"`
+}
+
+type LoggingLogBucket struct {
+	ProjectID          string
+	Location           string
+	LoggingLogBucketID string
+}
+
+func (s *LoggingLogBucket) String() string {
+	return "projects/" + s.ProjectID + "locations/" + s.Location + "/buckets/" + s.LoggingLogBucketID
+}
+
+func ResolveLoggingLogBucketRef(ctx context.Context, reader client.Reader, obj client.Object, ref *LoggingLogBucketRef) (*LoggingLogBucket, error) {
+	if ref == nil {
+		return nil, nil
+	}
+
+	if ref.Name == "" && ref.External == "" {
+		return nil, fmt.Errorf("must specify either name or external on loggingLogBucketRef")
+	}
+	if ref.External != "" && ref.Name != "" {
+		return nil, fmt.Errorf("cannot specify both spec.loggingLogBucketRef.name and spec.loggingLogBucketRef.external")
+	}
+
+	if ref.External != "" {
+		// External should be in the `projects/[projectID]/locations/[Location]/buckets/[bucketID]` format.
+		tokens := strings.Split(ref.External, "/")
+		if len(tokens) == 6 && tokens[0] == "projects" && tokens[2] == "locations" && tokens[4] == "buckets" {
+			return &LoggingLogBucket{
+				ProjectID:          tokens[1],
+				Location:           tokens[3],
+				LoggingLogBucketID: tokens[5],
+			}, nil
+		}
+		return nil, fmt.Errorf("format of LoggingLogBucket external=%q was not known (use projects/<projectId>/locations/<location>/buckets/<bucketID>)", ref.External)
+	}
+
+	key := types.NamespacedName{
+		Namespace: ref.Namespace,
+		Name:      ref.Name,
+	}
+	if key.Namespace == "" {
+		key.Namespace = obj.GetNamespace()
+	}
+
+	exchange := &unstructured.Unstructured{}
+	exchange.SetGroupVersionKind(schema.GroupVersionKind{
+		Group:   "logging.cnrm.cloud.google.com",
+		Version: "v1alpha1",
path: apis/refs/v1beta1/logbucketref.go
user: login: tylerreidwaze
body: Done
created_at: 2025-03-14T17:12:09Z
author_association: COLLABORATOR
diff_hunk: @@ -0,0 +1,117 @@
+// 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 v1beta1
+
+import (
+	"context"
+	"fmt"
+	"strings"
+
+	apierrors "k8s.io/apimachinery/pkg/api/errors"
+	"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
+	"k8s.io/apimachinery/pkg/runtime/schema"
+	"k8s.io/apimachinery/pkg/types"
+	"sigs.k8s.io/controller-runtime/pkg/client"
+)
+
+type LoggingLogBucketRef struct {
+	/* The LoggingLogBucket selfLink, when not managed by Config Connector. */
+	External string `json:"external,omitempty"`
+	/* The `name` field of a `LoggingLogBucket` resource. */
+	Name string `json:"name,omitempty"`
+	/* The `namespace` field of a `LoggingLogBucket` resource. */
+	Namespace string `json:"namespace,omitempty"`
+}
+
+type LoggingLogBucket struct {
+	ProjectID          string
+	Location           string
+	LoggingLogBucketID string
+}
+
+func (s *LoggingLogBucket) String() string {
+	return "projects/" + s.ProjectID + "locations/" + s.Location + "/buckets/" + s.LoggingLogBucketID
+}
+
+func ResolveLoggingLogBucketRef(ctx context.Context, reader client.Reader, obj client.Object, ref *LoggingLogBucketRef) (*LoggingLogBucket, error) {
+	if ref == nil {
+		return nil, nil
+	}
+
+	if ref.Name == "" && ref.External == "" {
+		return nil, fmt.Errorf("must specify either name or external on loggingLogBucketRef")
+	}
+	if ref.External != "" && ref.Name != "" {
+		return nil, fmt.Errorf("cannot specify both spec.loggingLogBucketRef.name and spec.loggingLogBucketRef.external")
+	}
+
+	if ref.External != "" {
+		// External should be in the `projects/[projectID]/locations/[Location]/buckets/[bucketID]` format.
+		tokens := strings.Split(ref.External, "/")
+		if len(tokens) == 6 && tokens[0] == "projects" && tokens[2] == "locations" && tokens[4] == "buckets" {
+			return &LoggingLogBucket{
+				ProjectID:          tokens[1],
+				Location:           tokens[3],
+				LoggingLogBucketID: tokens[5],
+			}, nil
+		}
+		return nil, fmt.Errorf("format of LoggingLogBucket external=%q was not known (use projects/<projectId>/locations/<location>/buckets/<bucketID>)", ref.External)
+	}
+
+	key := types.NamespacedName{
+		Namespace: ref.Namespace,
+		Name:      ref.Name,
+	}
+	if key.Namespace == "" {
+		key.Namespace = obj.GetNamespace()
+	}
+
+	exchange := &unstructured.Unstructured{}
+	exchange.SetGroupVersionKind(schema.GroupVersionKind{
+		Group:   "logging.cnrm.cloud.google.com",
+		Version: "v1alpha1",
+		Kind:    "LoggingLogBucket",
+	})
+	if err := reader.Get(ctx, key, exchange); err != nil {
+		if apierrors.IsNotFound(err) {
+			return nil, fmt.Errorf("referenced LoggingLogBucket %v not found", key)
+		}
+		return nil, fmt.Errorf("error reading referenced LoggingLogBucket %v: %w", key, err)
+	}
+
+	resourceID, _, err := unstructured.NestedString(exchange.Object, "spec", "resourceID")
+	if err != nil {
+		return nil, fmt.Errorf("reading spec.resourceID from LoggingLogBucket %s/%s: %w", exchange.GetNamespace(), exchange.GetName(), err)
+	}
+	if resourceID == "" {
+		resourceID = exchange.GetName()
+	}
+
+	location, _, err := unstructured.NestedString(exchange.Object, "spec", "location")
+	if err != nil {
+		return nil, fmt.Errorf("reading spec.region from LoggingLogBucket %s/%s: %w", exchange.GetNamespace(), exchange.GetName(), err)
+	}
+
+	projectID, err := ResolveProjectID(ctx, reader, exchange)
path: apis/refs/v1beta1/logbucketref.go
user: login: tylerreidwaze
body: Correct, we only need it under project.  I think in general it could only be at the project level.  LoggingLinks apply to GCS buckets which IIUC can only be deployed to projects.  Not Orgs/Folders
created_at: 2025-03-14T17:14:41Z
author_association: COLLABORATOR
diff_hunk: @@ -0,0 +1,120 @@
+// 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 v1alpha1
+
+import (
+	"context"
+	"fmt"
+	"strings"
+
+	"github.com/GoogleCloudPlatform/k8s-config-connector/apis/common"
+	refsv1beta1 "github.com/GoogleCloudPlatform/k8s-config-connector/apis/refs/v1beta1"
+	"sigs.k8s.io/controller-runtime/pkg/client"
+)
+
+// LinkIdentity defines the resource reference to LoggingLink, which "External" field
+// holds the GCP identifier for the KRM object.
+type LinkIdentity struct {
+	parent *LinkParent
+	id     string
+}
+
+func (i *LinkIdentity) String() string {
+	return i.parent.String() + "/links/" + i.id
+}
+
+func (i *LinkIdentity) ID() string {
+	return i.id
+}
+
+func (i *LinkIdentity) Parent() *LinkParent {
+	return i.parent
+}
+
+type LinkParent struct {
+	ProjectID          string
+	Location           string
+	LoggingLogBucketID string
+}
+
+func (p *LinkParent) String() string {
+	return "projects/" + p.ProjectID + "/locations/" + p.Location + "/buckets/" + p.LoggingLogBucketID
+}
+
+// New builds a LinkIdentity from the Config Connector Link object.
+func NewLinkIdentity(ctx context.Context, reader client.Reader, obj *LoggingLink) (*LinkIdentity, error) {
+
+	// Get Parent
+	bucketRef, err := refsv1beta1.ResolveLoggingLogBucketRef(ctx, reader, obj, obj.Spec.LoggingLogBucketRef)
+	if err != nil {
+		return nil, err
+	}
+	projectID := bucketRef.ProjectID
+	if projectID == "" {
+		return nil, fmt.Errorf("cannot resolve project")
+	}
+	location := bucketRef.Location
+
+	// 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
+		actualParent, actualResourceID, err := ParseLinkExternal(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)
path: apis/logging/v1alpha1/link_identity.go
user: login: tylerreidwaze
body: Done in incoming commit.  As an FYI, all of this was auto generated
created_at: 2025-03-14T17:21:24Z
author_association: COLLABORATOR
diff_hunk: @@ -0,0 +1,193 @@
+// 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 = &LoggingLinkRef{}
+
+// LoggingLinkRef defines the resource reference to LoggingLink, which "External" field
+// holds the GCP identifier for the KRM object.
+type LoggingLinkRef struct {
+	// A reference to an externally managed LoggingLink resource.
+	// Should be in the format "projects/<projectID>/locations/<location>/buckets/<bucketID>/links/<linkID>".
+	External string `json:"external,omitempty"`
+
+	// The name of a LoggingLink resource.
+	Name string `json:"name,omitempty"`
+
+	// The namespace of a LoggingLink resource.
+	Namespace string `json:"namespace,omitempty"`
+
+	parent *LoggingLinkParent
+}
+
+// NormalizedExternal provision the "External" value for other resource that depends on LoggingLink.
+// If the "External" is given in the other resource's spec.LoggingLinkRef, the given value will be used.
+// Otherwise, the "Name" and "Namespace" will be used to query the actual LoggingLink object from the cluster.
+func (r *LoggingLinkRef) 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", LoggingLinkGVK.Kind)
+	}
+	// From given External
+	if r.External != "" {
+		if _, _, err := parseLoggingLinkExternal(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(LoggingLinkGVK)
+	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", LoggingLinkGVK, 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 LoggingLinkRef from the Config Connector LoggingLink object.
+func NewLoggingLinkRef(ctx context.Context, reader client.Reader, obj *LoggingLink) (*LoggingLinkRef, error) {
path: apis/logging/v1alpha1/link_reference.go
user: login: tylerreidwaze
body: This was autogenerated.  Still want me to remove?
created_at: 2025-03-14T17:22:11Z
author_association: COLLABORATOR
diff_hunk: @@ -0,0 +1,104 @@
+// 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 (
+	refs "github.com/GoogleCloudPlatform/k8s-config-connector/apis/refs/v1beta1"
+	"github.com/GoogleCloudPlatform/k8s-config-connector/pkg/apis/k8s/v1alpha1"
+	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+)
+
+var LoggingLinkGVK = GroupVersion.WithKind("LoggingLink")
+
+// LoggingLinkSpec defines the desired state of LoggingLink
+// +kcc:proto=google.logging.v2.Link
+type LoggingLinkSpec struct {
+	// +kubebuilder:validation:XValidation:rule="self == oldSelf",message="ResourceID field is immutable"
path: apis/logging/v1alpha1/link_types.go
user: login: tylerreidwaze
body: Done in incoming commit
created_at: 2025-03-14T17:39:02Z
author_association: COLLABORATOR
diff_hunk: @@ -0,0 +1,104 @@
+// 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 (
+	refs "github.com/GoogleCloudPlatform/k8s-config-connector/apis/refs/v1beta1"
+	"github.com/GoogleCloudPlatform/k8s-config-connector/pkg/apis/k8s/v1alpha1"
+	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+)
+
+var LoggingLinkGVK = GroupVersion.WithKind("LoggingLink")
+
+// LoggingLinkSpec defines the desired state of LoggingLink
+// +kcc:proto=google.logging.v2.Link
+type LoggingLinkSpec struct {
+	// +kubebuilder:validation:XValidation:rule="self == oldSelf",message="ResourceID field is immutable"
+	// Immutable.
+	// The LoggingLink name. If not given, the metadata.name will be used.
+	ResourceID *string `json:"resourceID,omitempty"`
+
+	// Describes this link.
+	//  The maximum length of the description is 8000 characters.
+	Description *string `json:"description,omitempty"`
+
+	// Placeholder description
path: apis/logging/v1alpha1/link_types.go
user: login: tylerreidwaze
body: Done in incoming commit.  This field isn't really used as a note
created_at: 2025-03-14T17:40:33Z
author_association: COLLABORATOR
diff_hunk: @@ -0,0 +1,117 @@
+// 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 v1beta1
+
+import (
+	"context"
+	"fmt"
+	"strings"
+
+	apierrors "k8s.io/apimachinery/pkg/api/errors"
+	"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
+	"k8s.io/apimachinery/pkg/runtime/schema"
+	"k8s.io/apimachinery/pkg/types"
+	"sigs.k8s.io/controller-runtime/pkg/client"
+)
+
+type LoggingLogBucketRef struct {
+	/* The LoggingLogBucket selfLink, when not managed by Config Connector. */
path: apis/refs/v1beta1/logbucketref.go
user: login: tylerreidwaze
body: Done in incoming commit
created_at: 2025-03-14T17:41:16Z
author_association: COLLABORATOR
diff_hunk: @@ -0,0 +1,104 @@
+// 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 (
+	refs "github.com/GoogleCloudPlatform/k8s-config-connector/apis/refs/v1beta1"
+	"github.com/GoogleCloudPlatform/k8s-config-connector/pkg/apis/k8s/v1alpha1"
+	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+)
+
+var LoggingLinkGVK = GroupVersion.WithKind("LoggingLink")
+
+// LoggingLinkSpec defines the desired state of LoggingLink
+// +kcc:proto=google.logging.v2.Link
+type LoggingLinkSpec struct {
+	// +kubebuilder:validation:XValidation:rule="self == oldSelf",message="ResourceID field is immutable"
+	// Immutable.
+	// The LoggingLink name. If not given, the metadata.name will be used.
+	ResourceID *string `json:"resourceID,omitempty"`
+
+	// Describes this link.
+	//  The maximum length of the description is 8000 characters.
+	Description *string `json:"description,omitempty"`
+
+	// Placeholder description
+	LoggingLogBucketRef *refs.LoggingLogBucketRef `json:"loggingLogBucketRef,omitempty"`
+}
+
+// LoggingLinkStatus defines the config connector machine state of LoggingLink
+type LoggingLinkStatus 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 LoggingLink resource in GCP.
+	ExternalRef *string `json:"externalRef,omitempty"`
+
+	// ObservedState is the state of the resource as most recently observed in GCP.
+	ObservedState *LoggingLinkObservedState `json:"observedState,omitempty"`
+}
+
+// LoggingLinkObservedState is the state of the LoggingLink resource as most recently observed in GCP.
+type LoggingLinkObservedState struct {
+
+	// +optional
+	// +kubebuilder:validation:Format=date-time
+	CreateTime *string `json:"createTime,omitempty"`
path: apis/logging/v1alpha1/link_types.go
user: login: tylerreidwaze
body: Done in coming commit
created_at: 2025-03-14T17:41:47Z
author_association: COLLABORATOR
diff_hunk: @@ -0,0 +1,104 @@
+// 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 (
+	refs "github.com/GoogleCloudPlatform/k8s-config-connector/apis/refs/v1beta1"
+	"github.com/GoogleCloudPlatform/k8s-config-connector/pkg/apis/k8s/v1alpha1"
+	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+)
+
+var LoggingLinkGVK = GroupVersion.WithKind("LoggingLink")
+
+// LoggingLinkSpec defines the desired state of LoggingLink
+// +kcc:proto=google.logging.v2.Link
+type LoggingLinkSpec struct {
+	// +kubebuilder:validation:XValidation:rule="self == oldSelf",message="ResourceID field is immutable"
+	// Immutable.
+	// The LoggingLink name. If not given, the metadata.name will be used.
+	ResourceID *string `json:"resourceID,omitempty"`
+
+	// Describes this link.
+	//  The maximum length of the description is 8000 characters.
+	Description *string `json:"description,omitempty"`
+
+	// Placeholder description
+	LoggingLogBucketRef *refs.LoggingLogBucketRef `json:"loggingLogBucketRef,omitempty"`
+}
+
+// LoggingLinkStatus defines the config connector machine state of LoggingLink
+type LoggingLinkStatus 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 LoggingLink resource in GCP.
+	ExternalRef *string `json:"externalRef,omitempty"`
+
+	// ObservedState is the state of the resource as most recently observed in GCP.
+	ObservedState *LoggingLinkObservedState `json:"observedState,omitempty"`
+}
+
+// LoggingLinkObservedState is the state of the LoggingLink resource as most recently observed in GCP.
+type LoggingLinkObservedState struct {
+
+	// +optional
+	// +kubebuilder:validation:Format=date-time
+	CreateTime *string `json:"createTime,omitempty"`
+
+	// the lifecycle state might be something more complicated
+	// this is an ENUM, should be safe to use a string
+	LifecycleState *string `json:"lifecycleState,omitempty"`
path: apis/logging/v1alpha1/link_types.go
user: login: tylerreidwaze
body: Cleaned up in incoming commit
created_at: 2025-03-14T17:42:48Z
author_association: COLLABORATOR
diff_hunk: @@ -0,0 +1,104 @@
+// 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 (
+	refs "github.com/GoogleCloudPlatform/k8s-config-connector/apis/refs/v1beta1"
+	"github.com/GoogleCloudPlatform/k8s-config-connector/pkg/apis/k8s/v1alpha1"
+	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+)
+
+var LoggingLinkGVK = GroupVersion.WithKind("LoggingLink")
+
+// LoggingLinkSpec defines the desired state of LoggingLink
+// +kcc:proto=google.logging.v2.Link
+type LoggingLinkSpec struct {
+	// +kubebuilder:validation:XValidation:rule="self == oldSelf",message="ResourceID field is immutable"
+	// Immutable.
+	// The LoggingLink name. If not given, the metadata.name will be used.
+	ResourceID *string `json:"resourceID,omitempty"`
+
+	// Describes this link.
+	//  The maximum length of the description is 8000 characters.
+	Description *string `json:"description,omitempty"`
+
+	// Placeholder description
+	LoggingLogBucketRef *refs.LoggingLogBucketRef `json:"loggingLogBucketRef,omitempty"`
+}
+
+// LoggingLinkStatus defines the config connector machine state of LoggingLink
+type LoggingLinkStatus 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 LoggingLink resource in GCP.
+	ExternalRef *string `json:"externalRef,omitempty"`
+
+	// ObservedState is the state of the resource as most recently observed in GCP.
+	ObservedState *LoggingLinkObservedState `json:"observedState,omitempty"`
+}
+
+// LoggingLinkObservedState is the state of the LoggingLink resource as most recently observed in GCP.
+type LoggingLinkObservedState struct {
+
+	// +optional
+	// +kubebuilder:validation:Format=date-time
+	CreateTime *string `json:"createTime,omitempty"`
+
+	// the lifecycle state might be something more complicated
+	// this is an ENUM, should be safe to use a string
+	LifecycleState *string `json:"lifecycleState,omitempty"`
+
+	// this field is just a string, but its an object(string)
+	// https://github.com/googleapis/googleapis/blob/master/google/logging/v2/logging_config.proto#L1063
+	BigQueryDataset *BigQueryDatasetObservedState `json:"bigQueryDataset,omitempty"`
path: apis/logging/v1alpha1/link_types.go
user: login: tylerreidwaze
body: Done in incoming commit
created_at: 2025-03-14T17:43:20Z
author_association: COLLABORATOR
diff_hunk: @@ -0,0 +1,117 @@
+// 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 v1beta1
+
+import (
+	"context"
+	"fmt"
+	"strings"
+
+	apierrors "k8s.io/apimachinery/pkg/api/errors"
+	"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
+	"k8s.io/apimachinery/pkg/runtime/schema"
+	"k8s.io/apimachinery/pkg/types"
+	"sigs.k8s.io/controller-runtime/pkg/client"
+)
+
+type LoggingLogBucketRef struct {
path: apis/refs/v1beta1/logbucketref.go
user: login: tylerreidwaze
body: Good to know, thank you
created_at: 2025-03-14T17:43:37Z
author_association: COLLABORATOR
diff_hunk: @@ -0,0 +1,104 @@
+// 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 (
+	refs "github.com/GoogleCloudPlatform/k8s-config-connector/apis/refs/v1beta1"
+	"github.com/GoogleCloudPlatform/k8s-config-connector/pkg/apis/k8s/v1alpha1"
+	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+)
+
+var LoggingLinkGVK = GroupVersion.WithKind("LoggingLink")
+
+// LoggingLinkSpec defines the desired state of LoggingLink
+// +kcc:proto=google.logging.v2.Link
+type LoggingLinkSpec struct {
+	// +kubebuilder:validation:XValidation:rule="self == oldSelf",message="ResourceID field is immutable"
+	// Immutable.
+	// The LoggingLink name. If not given, the metadata.name will be used.
+	ResourceID *string `json:"resourceID,omitempty"`
+
+	// Describes this link.
+	//  The maximum length of the description is 8000 characters.
+	Description *string `json:"description,omitempty"`
+
+	// Placeholder description
+	LoggingLogBucketRef *refs.LoggingLogBucketRef `json:"loggingLogBucketRef,omitempty"`
+}
+
+// LoggingLinkStatus defines the config connector machine state of LoggingLink
+type LoggingLinkStatus 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 LoggingLink resource in GCP.
+	ExternalRef *string `json:"externalRef,omitempty"`
+
+	// ObservedState is the state of the resource as most recently observed in GCP.
+	ObservedState *LoggingLinkObservedState `json:"observedState,omitempty"`
+}
+
+// LoggingLinkObservedState is the state of the LoggingLink resource as most recently observed in GCP.
+type LoggingLinkObservedState struct {
+
+	// +optional
+	// +kubebuilder:validation:Format=date-time
path: apis/logging/v1alpha1/link_types.go
user: login: tylerreidwaze
body: Sounds good to me, removed in incoming commit
created_at: 2025-03-14T17:44:05Z
author_association: COLLABORATOR
diff_hunk: @@ -0,0 +1,47 @@
+# Copyright 2024 Google LLC
path: pkg/test/resourcefixture/testdata/basic/logging/v1alpha1/logginglogginglink/basiclink/_generated_object_projectlogbucket.golden.yaml
user: login: tylerreidwaze
body: Done in incoming commit
created_at: 2025-03-14T17:46:06Z
author_association: COLLABORATOR
diff_hunk: @@ -0,0 +1,117 @@
+// 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 v1beta1
+
+import (
+	"context"
+	"fmt"
+	"strings"
+
+	apierrors "k8s.io/apimachinery/pkg/api/errors"
+	"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
+	"k8s.io/apimachinery/pkg/runtime/schema"
+	"k8s.io/apimachinery/pkg/types"
+	"sigs.k8s.io/controller-runtime/pkg/client"
+)
+
+type LoggingLogBucketRef struct {
+	/* The LoggingLogBucket selfLink, when not managed by Config Connector. */
+	External string `json:"external,omitempty"`
+	/* The `name` field of a `LoggingLogBucket` resource. */
+	Name string `json:"name,omitempty"`
+	/* The `namespace` field of a `LoggingLogBucket` resource. */
+	Namespace string `json:"namespace,omitempty"`
+}
+
+type LoggingLogBucket struct {
+	ProjectID          string
+	Location           string
+	LoggingLogBucketID string
+}
+
+func (s *LoggingLogBucket) String() string {
+	return "projects/" + s.ProjectID + "locations/" + s.Location + "/buckets/" + s.LoggingLogBucketID
+}
+
+func ResolveLoggingLogBucketRef(ctx context.Context, reader client.Reader, obj client.Object, ref *LoggingLogBucketRef) (*LoggingLogBucket, error) {
+	if ref == nil {
+		return nil, nil
+	}
+
+	if ref.Name == "" && ref.External == "" {
+		return nil, fmt.Errorf("must specify either name or external on loggingLogBucketRef")
+	}
+	if ref.External != "" && ref.Name != "" {
+		return nil, fmt.Errorf("cannot specify both spec.loggingLogBucketRef.name and spec.loggingLogBucketRef.external")
+	}
+
+	if ref.External != "" {
+		// External should be in the `projects/[projectID]/locations/[Location]/buckets/[bucketID]` format.
+		tokens := strings.Split(ref.External, "/")
+		if len(tokens) == 6 && tokens[0] == "projects" && tokens[2] == "locations" && tokens[4] == "buckets" {
+			return &LoggingLogBucket{
+				ProjectID:          tokens[1],
+				Location:           tokens[3],
+				LoggingLogBucketID: tokens[5],
+			}, nil
+		}
+		return nil, fmt.Errorf("format of LoggingLogBucket external=%q was not known (use projects/<projectId>/locations/<location>/buckets/<bucketID>)", ref.External)
+	}
+
+	key := types.NamespacedName{
+		Namespace: ref.Namespace,
+		Name:      ref.Name,
+	}
+	if key.Namespace == "" {
+		key.Namespace = obj.GetNamespace()
+	}
+
+	exchange := &unstructured.Unstructured{}
+	exchange.SetGroupVersionKind(schema.GroupVersionKind{
+		Group:   "logging.cnrm.cloud.google.com",
+		Version: "v1alpha1",
path: apis/refs/v1beta1/logbucketref.go
user: login: tylerreidwaze
body: done
created_at: 2025-03-14T17:58:09Z
author_association: COLLABORATOR
diff_hunk: @@ -0,0 +1,22 @@
+# Copyright 2022 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.
+
+apiVersion: logging.cnrm.cloud.google.com/v1alpha1
+kind: LoggingLink
+metadata:
+  name: logginglink${uniqueId} # no hyphen here as link ids can only have alphanumeric and underscores
path: pkg/test/resourcefixture/testdata/basic/logging/v1alpha1/logginglogginglink/basiclink/create.yaml
user: login: tylerreidwaze
body: Done
created_at: 2025-03-14T18:04:02Z
author_association: COLLABORATOR
diff_hunk: @@ -0,0 +1,22 @@
+# Copyright 2022 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.
+
+apiVersion: logging.cnrm.cloud.google.com/v1alpha1
+kind: LoggingLink
+metadata:
+  name: logginglink${uniqueId} # no hyphen here as link ids can only have alphanumeric and underscores
+spec:
+  loggingLogBucketRef:
+    external: "projects/${projectId}/locations/global/buckets/logginglogbucket-${uniqueId}"
path: pkg/test/resourcefixture/testdata/basic/logging/v1alpha1/logginglogginglink/basiclink/create.yaml
user: login: tylerreidwaze
body: Done
created_at: 2025-03-14T18:15:29Z
author_association: COLLABORATOR
diff_hunk: @@ -0,0 +1,238 @@
+// 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 logging
+
+import (
+	"context"
+	"fmt"
+
+	//"reflect"
+
+	krm "github.com/GoogleCloudPlatform/k8s-config-connector/apis/logging/v1alpha1"
+	"github.com/GoogleCloudPlatform/k8s-config-connector/pkg/config"
+	"github.com/GoogleCloudPlatform/k8s-config-connector/pkg/controller/direct"
+	"github.com/GoogleCloudPlatform/k8s-config-connector/pkg/controller/direct/directbase"
+	"github.com/GoogleCloudPlatform/k8s-config-connector/pkg/controller/direct/registry"
+	"github.com/GoogleCloudPlatform/k8s-config-connector/pkg/k8s"
+
+	//"net/http"
+
+	gcp "cloud.google.com/go/logging/apiv2"
+	//"google.golang.org/api/option"
+	loggingpb "cloud.google.com/go/logging/apiv2/loggingpb"
+
+	//"google.golang.org/api/option"
+	//"google.golang.org/protobuf/types/known/fieldmaskpb"
+
+	"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
+	"k8s.io/apimachinery/pkg/runtime"
+	"k8s.io/klog/v2"
+	"sigs.k8s.io/controller-runtime/pkg/client"
+)
+
+func init() {
+	registry.RegisterModel(krm.LoggingLinkGVK, NewLoggingLinkModel)
+}
+
+func NewLoggingLinkModel(ctx context.Context, config *config.ControllerConfig) (directbase.Model, error) {
+	return &modelLoggingLink{config: *config}, nil
+}
+
+var _ directbase.Model = &modelLoggingLink{}
+
+type modelLoggingLink struct {
+	config config.ControllerConfig
+}
+
+func (m *modelLoggingLink) client(ctx context.Context) (*gcp.ConfigClient, error) {
+
+	gcpClient, err := gcp.NewConfigRESTClient(ctx)
+	if err != nil {
+		return nil, fmt.Errorf("building Logging Config client: %w", err)
+	}
+	return gcpClient, err
+}
+
+func (m *modelLoggingLink) AdapterForObject(ctx context.Context, reader client.Reader, u *unstructured.Unstructured) (directbase.Adapter, error) {
+	obj := &krm.LoggingLink{}
+	if err := runtime.DefaultUnstructuredConverter.FromUnstructured(u.Object, &obj); err != nil {
+		return nil, fmt.Errorf("error converting to %T: %w", obj, err)
+	}
+
+	id, err := krm.NewLoggingLinkRef(ctx, reader, obj)
+	if err != nil {
+		return nil, err
+	}
+
+	gcpClient, err := m.client(ctx)
+	if err != nil {
+		return nil, err
+	}
+	return &LoggingLinkAdapter{
+		id:        id,
+		gcpClient: gcpClient,
+		desired:   obj,
+	}, nil
+}
+
+/*
+func () ResolveExternalRef(externalRef string) {
+
+}
+*/
+
+func (m *modelLoggingLink) AdapterForURL(ctx context.Context, url string) (directbase.Adapter, error) {
+	// TODO: Support URLs
+	return nil, nil
+}
+
+type LoggingLinkAdapter struct {
+	id        *krm.LoggingLinkRef
+	gcpClient *gcp.ConfigClient
+	desired   *krm.LoggingLink
+	actual    *loggingpb.Link
+}
+
+var _ directbase.Adapter = &LoggingLinkAdapter{}
+
+func (a *LoggingLinkAdapter) Find(ctx context.Context) (bool, error) {
+	log := klog.FromContext(ctx)
+	log.V(2).Info("getting LoggingLink", "name", a.id.External)
+
+	req := &loggingpb.GetLinkRequest{Name: a.id.External}
+	linkpb, err := a.gcpClient.GetLink(ctx, req)
+	if err != nil {
+		if direct.IsNotFound(err) {
+			return false, nil
+		}
+		return false, fmt.Errorf("getting LoggingLink %q: %w", a.id.External, err)
+	}
+
+	a.actual = linkpb
+	return true, nil
+}
+
+func (a *LoggingLinkAdapter) Create(ctx context.Context, createOp *directbase.CreateOperation) error {
+	u := createOp.GetUnstructured()
+	log := klog.FromContext(ctx).WithName(ctrlName)
+	log.V(2).Info("creating object", "u", u)
+
+	mapCtx := &direct.MapContext{}
+
+	desired := a.desired.DeepCopy()
+	resource := LoggingLinkSpec_ToProto(mapCtx, &desired.Spec)
+	if mapCtx.Err() != nil {
+		return mapCtx.Err()
+	}
+
+	// TODO(user): Complete the gcp "CREATE" or "INSERT" request with required fields.
+	// TODO(): 400 error needs to come through on the error
+	// TODO(): there is an implicit dependency on the bucket being active, do we need that here?
+	parent, err := a.id.Parent()
+	if err != nil {
+		return err
+	}
+
+	resourceID := direct.ValueOf(desired.Spec.ResourceID)
+	if resourceID == "" {
+		log.V(2).Info("ResourceID is not set, will use metadata.name")
+		resourceID = desired.Name
+	} else {
+		log.V(2).Info("ResourceID is set, will use")
+		resourceID = *desired.Spec.ResourceID
+	}
+
+	req := &loggingpb.CreateLinkRequest{
+		Parent: parent.String(),
+		Link:   resource,
+		LinkId: resourceID,
+	}
+	op, err := a.gcpClient.CreateLink(ctx, req)
+	if err != nil {
+		return fmt.Errorf("creating Link %s: %w\n", a.id.External, err)
+	}
+	created, err := op.Wait(ctx)
+	if err != nil {
+		return fmt.Errorf("Link %s waiting creation: %w", a.id.External, err)
+	}
+	log.V(2).Info("successfully created Link", "name", a.id.External)
+
+	status := &krm.LoggingLinkStatus{}
+	status.ObservedState = LoggingLinkObservedState_FromProto(mapCtx, created)
+	if mapCtx.Err() != nil {
+		return mapCtx.Err()
+	}
+	status.ExternalRef = &a.id.External
+	return createOp.UpdateStatus(ctx, status, nil)
+}
+
+func (a *LoggingLinkAdapter) Update(ctx context.Context, updateOp *directbase.UpdateOperation) error {
+	return fmt.Errorf("update operation not supported for resource %v %v",
path: pkg/controller/direct/logging/link_controller.go
user: login: tylerreidwaze
body: Thanks for the tip and the reference.  Done
created_at: 2025-03-14T18:40:51Z
author_association: COLLABORATOR
diff_hunk: @@ -0,0 +1,238 @@
+// 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 logging
+
+import (
+	"context"
+	"fmt"
+
+	//"reflect"
+
+	krm "github.com/GoogleCloudPlatform/k8s-config-connector/apis/logging/v1alpha1"
+	"github.com/GoogleCloudPlatform/k8s-config-connector/pkg/config"
+	"github.com/GoogleCloudPlatform/k8s-config-connector/pkg/controller/direct"
+	"github.com/GoogleCloudPlatform/k8s-config-connector/pkg/controller/direct/directbase"
+	"github.com/GoogleCloudPlatform/k8s-config-connector/pkg/controller/direct/registry"
+	"github.com/GoogleCloudPlatform/k8s-config-connector/pkg/k8s"
+
+	//"net/http"
+
+	gcp "cloud.google.com/go/logging/apiv2"
+	//"google.golang.org/api/option"
+	loggingpb "cloud.google.com/go/logging/apiv2/loggingpb"
+
+	//"google.golang.org/api/option"
+	//"google.golang.org/protobuf/types/known/fieldmaskpb"
+
+	"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
+	"k8s.io/apimachinery/pkg/runtime"
+	"k8s.io/klog/v2"
+	"sigs.k8s.io/controller-runtime/pkg/client"
+)
+
+func init() {
+	registry.RegisterModel(krm.LoggingLinkGVK, NewLoggingLinkModel)
+}
+
+func NewLoggingLinkModel(ctx context.Context, config *config.ControllerConfig) (directbase.Model, error) {
+	return &modelLoggingLink{config: *config}, nil
+}
+
+var _ directbase.Model = &modelLoggingLink{}
+
+type modelLoggingLink struct {
+	config config.ControllerConfig
+}
+
+func (m *modelLoggingLink) client(ctx context.Context) (*gcp.ConfigClient, error) {
+
+	gcpClient, err := gcp.NewConfigRESTClient(ctx)
+	if err != nil {
+		return nil, fmt.Errorf("building Logging Config client: %w", err)
+	}
+	return gcpClient, err
+}
+
+func (m *modelLoggingLink) AdapterForObject(ctx context.Context, reader client.Reader, u *unstructured.Unstructured) (directbase.Adapter, error) {
+	obj := &krm.LoggingLink{}
+	if err := runtime.DefaultUnstructuredConverter.FromUnstructured(u.Object, &obj); err != nil {
+		return nil, fmt.Errorf("error converting to %T: %w", obj, err)
+	}
+
+	id, err := krm.NewLoggingLinkRef(ctx, reader, obj)
+	if err != nil {
+		return nil, err
+	}
+
+	gcpClient, err := m.client(ctx)
+	if err != nil {
+		return nil, err
+	}
+	return &LoggingLinkAdapter{
+		id:        id,
+		gcpClient: gcpClient,
+		desired:   obj,
+	}, nil
+}
+
+/*
+func () ResolveExternalRef(externalRef string) {
+
+}
+*/
+
+func (m *modelLoggingLink) AdapterForURL(ctx context.Context, url string) (directbase.Adapter, error) {
+	// TODO: Support URLs
+	return nil, nil
+}
+
+type LoggingLinkAdapter struct {
+	id        *krm.LoggingLinkRef
+	gcpClient *gcp.ConfigClient
+	desired   *krm.LoggingLink
+	actual    *loggingpb.Link
+}
+
+var _ directbase.Adapter = &LoggingLinkAdapter{}
+
+func (a *LoggingLinkAdapter) Find(ctx context.Context) (bool, error) {
+	log := klog.FromContext(ctx)
+	log.V(2).Info("getting LoggingLink", "name", a.id.External)
+
+	req := &loggingpb.GetLinkRequest{Name: a.id.External}
+	linkpb, err := a.gcpClient.GetLink(ctx, req)
+	if err != nil {
+		if direct.IsNotFound(err) {
+			return false, nil
+		}
+		return false, fmt.Errorf("getting LoggingLink %q: %w", a.id.External, err)
+	}
+
+	a.actual = linkpb
+	return true, nil
+}
+
+func (a *LoggingLinkAdapter) Create(ctx context.Context, createOp *directbase.CreateOperation) error {
+	u := createOp.GetUnstructured()
+	log := klog.FromContext(ctx).WithName(ctrlName)
+	log.V(2).Info("creating object", "u", u)
path: pkg/controller/direct/logging/link_controller.go
user: login: tylerreidwaze
body: good call, done
created_at: 2025-03-14T21:00:33Z
author_association: COLLABORATOR
diff_hunk: @@ -0,0 +1,238 @@
+// 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 logging
+
+import (
+	"context"
+	"fmt"
+
+	//"reflect"
+
+	krm "github.com/GoogleCloudPlatform/k8s-config-connector/apis/logging/v1alpha1"
+	"github.com/GoogleCloudPlatform/k8s-config-connector/pkg/config"
+	"github.com/GoogleCloudPlatform/k8s-config-connector/pkg/controller/direct"
+	"github.com/GoogleCloudPlatform/k8s-config-connector/pkg/controller/direct/directbase"
+	"github.com/GoogleCloudPlatform/k8s-config-connector/pkg/controller/direct/registry"
+	"github.com/GoogleCloudPlatform/k8s-config-connector/pkg/k8s"
+
+	//"net/http"
+
+	gcp "cloud.google.com/go/logging/apiv2"
+	//"google.golang.org/api/option"
+	loggingpb "cloud.google.com/go/logging/apiv2/loggingpb"
+
+	//"google.golang.org/api/option"
+	//"google.golang.org/protobuf/types/known/fieldmaskpb"
+
+	"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
+	"k8s.io/apimachinery/pkg/runtime"
+	"k8s.io/klog/v2"
+	"sigs.k8s.io/controller-runtime/pkg/client"
+)
+
+func init() {
+	registry.RegisterModel(krm.LoggingLinkGVK, NewLoggingLinkModel)
+}
+
+func NewLoggingLinkModel(ctx context.Context, config *config.ControllerConfig) (directbase.Model, error) {
+	return &modelLoggingLink{config: *config}, nil
+}
+
+var _ directbase.Model = &modelLoggingLink{}
+
+type modelLoggingLink struct {
+	config config.ControllerConfig
+}
+
+func (m *modelLoggingLink) client(ctx context.Context) (*gcp.ConfigClient, error) {
+
+	gcpClient, err := gcp.NewConfigRESTClient(ctx)
+	if err != nil {
+		return nil, fmt.Errorf("building Logging Config client: %w", err)
+	}
+	return gcpClient, err
+}
+
+func (m *modelLoggingLink) AdapterForObject(ctx context.Context, reader client.Reader, u *unstructured.Unstructured) (directbase.Adapter, error) {
+	obj := &krm.LoggingLink{}
+	if err := runtime.DefaultUnstructuredConverter.FromUnstructured(u.Object, &obj); err != nil {
+		return nil, fmt.Errorf("error converting to %T: %w", obj, err)
+	}
+
+	id, err := krm.NewLoggingLinkRef(ctx, reader, obj)
+	if err != nil {
+		return nil, err
+	}
+
+	gcpClient, err := m.client(ctx)
+	if err != nil {
+		return nil, err
+	}
+	return &LoggingLinkAdapter{
+		id:        id,
+		gcpClient: gcpClient,
+		desired:   obj,
+	}, nil
+}
+
+/*
+func () ResolveExternalRef(externalRef string) {
+
+}
+*/
+
+func (m *modelLoggingLink) AdapterForURL(ctx context.Context, url string) (directbase.Adapter, error) {
+	// TODO: Support URLs
+	return nil, nil
+}
+
+type LoggingLinkAdapter struct {
+	id        *krm.LoggingLinkRef
+	gcpClient *gcp.ConfigClient
+	desired   *krm.LoggingLink
+	actual    *loggingpb.Link
+}
+
+var _ directbase.Adapter = &LoggingLinkAdapter{}
+
+func (a *LoggingLinkAdapter) Find(ctx context.Context) (bool, error) {
+	log := klog.FromContext(ctx)
+	log.V(2).Info("getting LoggingLink", "name", a.id.External)
+
+	req := &loggingpb.GetLinkRequest{Name: a.id.External}
+	linkpb, err := a.gcpClient.GetLink(ctx, req)
+	if err != nil {
+		if direct.IsNotFound(err) {
+			return false, nil
+		}
+		return false, fmt.Errorf("getting LoggingLink %q: %w", a.id.External, err)
+	}
+
+	a.actual = linkpb
+	return true, nil
+}
+
+func (a *LoggingLinkAdapter) Create(ctx context.Context, createOp *directbase.CreateOperation) error {
+	u := createOp.GetUnstructured()
+	log := klog.FromContext(ctx).WithName(ctrlName)
+	log.V(2).Info("creating object", "u", u)
+
+	mapCtx := &direct.MapContext{}
+
+	desired := a.desired.DeepCopy()
+	resource := LoggingLinkSpec_ToProto(mapCtx, &desired.Spec)
+	if mapCtx.Err() != nil {
+		return mapCtx.Err()
+	}
+
+	// TODO(user): Complete the gcp "CREATE" or "INSERT" request with required fields.
+	// TODO(): 400 error needs to come through on the error
+	// TODO(): there is an implicit dependency on the bucket being active, do we need that here?
+	parent, err := a.id.Parent()
+	if err != nil {
+		return err
+	}
+
+	resourceID := direct.ValueOf(desired.Spec.ResourceID)
+	if resourceID == "" {
+		log.V(2).Info("ResourceID is not set, will use metadata.name")
+		resourceID = desired.Name
+	} else {
+		log.V(2).Info("ResourceID is set, will use")
+		resourceID = *desired.Spec.ResourceID
+	}
+
+	req := &loggingpb.CreateLinkRequest{
+		Parent: parent.String(),
+		Link:   resource,
+		LinkId: resourceID,
+	}
+	op, err := a.gcpClient.CreateLink(ctx, req)
+	if err != nil {
+		return fmt.Errorf("creating Link %s: %w\n", a.id.External, err)
path: pkg/controller/direct/logging/link_controller.go
user: login: tylerreidwaze
body: done
created_at: 2025-03-14T21:03:24Z
author_association: COLLABORATOR
diff_hunk: @@ -0,0 +1,238 @@
+// 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 logging
+
+import (
+	"context"
+	"fmt"
+
+	//"reflect"
+
+	krm "github.com/GoogleCloudPlatform/k8s-config-connector/apis/logging/v1alpha1"
+	"github.com/GoogleCloudPlatform/k8s-config-connector/pkg/config"
+	"github.com/GoogleCloudPlatform/k8s-config-connector/pkg/controller/direct"
+	"github.com/GoogleCloudPlatform/k8s-config-connector/pkg/controller/direct/directbase"
+	"github.com/GoogleCloudPlatform/k8s-config-connector/pkg/controller/direct/registry"
+	"github.com/GoogleCloudPlatform/k8s-config-connector/pkg/k8s"
+
+	//"net/http"
+
+	gcp "cloud.google.com/go/logging/apiv2"
+	//"google.golang.org/api/option"
+	loggingpb "cloud.google.com/go/logging/apiv2/loggingpb"
+
+	//"google.golang.org/api/option"
+	//"google.golang.org/protobuf/types/known/fieldmaskpb"
+
+	"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
+	"k8s.io/apimachinery/pkg/runtime"
+	"k8s.io/klog/v2"
+	"sigs.k8s.io/controller-runtime/pkg/client"
+)
+
+func init() {
+	registry.RegisterModel(krm.LoggingLinkGVK, NewLoggingLinkModel)
+}
+
+func NewLoggingLinkModel(ctx context.Context, config *config.ControllerConfig) (directbase.Model, error) {
+	return &modelLoggingLink{config: *config}, nil
+}
+
+var _ directbase.Model = &modelLoggingLink{}
+
+type modelLoggingLink struct {
+	config config.ControllerConfig
+}
+
+func (m *modelLoggingLink) client(ctx context.Context) (*gcp.ConfigClient, error) {
+
+	gcpClient, err := gcp.NewConfigRESTClient(ctx)
+	if err != nil {
+		return nil, fmt.Errorf("building Logging Config client: %w", err)
+	}
+	return gcpClient, err
+}
+
+func (m *modelLoggingLink) AdapterForObject(ctx context.Context, reader client.Reader, u *unstructured.Unstructured) (directbase.Adapter, error) {
+	obj := &krm.LoggingLink{}
+	if err := runtime.DefaultUnstructuredConverter.FromUnstructured(u.Object, &obj); err != nil {
+		return nil, fmt.Errorf("error converting to %T: %w", obj, err)
+	}
+
+	id, err := krm.NewLoggingLinkRef(ctx, reader, obj)
path: pkg/controller/direct/logging/link_controller.go
user: login: tylerreidwaze
body: done
created_at: 2025-03-14T22:24:56Z
author_association: COLLABORATOR
diff_hunk: @@ -0,0 +1,238 @@
+// 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 logging
+
+import (
+	"context"
+	"fmt"
+
+	//"reflect"
+
+	krm "github.com/GoogleCloudPlatform/k8s-config-connector/apis/logging/v1alpha1"
+	"github.com/GoogleCloudPlatform/k8s-config-connector/pkg/config"
+	"github.com/GoogleCloudPlatform/k8s-config-connector/pkg/controller/direct"
+	"github.com/GoogleCloudPlatform/k8s-config-connector/pkg/controller/direct/directbase"
+	"github.com/GoogleCloudPlatform/k8s-config-connector/pkg/controller/direct/registry"
+	"github.com/GoogleCloudPlatform/k8s-config-connector/pkg/k8s"
+
+	//"net/http"
+
+	gcp "cloud.google.com/go/logging/apiv2"
+	//"google.golang.org/api/option"
+	loggingpb "cloud.google.com/go/logging/apiv2/loggingpb"
+
+	//"google.golang.org/api/option"
+	//"google.golang.org/protobuf/types/known/fieldmaskpb"
+
+	"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
+	"k8s.io/apimachinery/pkg/runtime"
+	"k8s.io/klog/v2"
+	"sigs.k8s.io/controller-runtime/pkg/client"
+)
+
+func init() {
+	registry.RegisterModel(krm.LoggingLinkGVK, NewLoggingLinkModel)
+}
+
+func NewLoggingLinkModel(ctx context.Context, config *config.ControllerConfig) (directbase.Model, error) {
+	return &modelLoggingLink{config: *config}, nil
+}
+
+var _ directbase.Model = &modelLoggingLink{}
+
+type modelLoggingLink struct {
+	config config.ControllerConfig
+}
+
+func (m *modelLoggingLink) client(ctx context.Context) (*gcp.ConfigClient, error) {
+
+	gcpClient, err := gcp.NewConfigRESTClient(ctx)
+	if err != nil {
+		return nil, fmt.Errorf("building Logging Config client: %w", err)
+	}
+	return gcpClient, err
+}
+
+func (m *modelLoggingLink) AdapterForObject(ctx context.Context, reader client.Reader, u *unstructured.Unstructured) (directbase.Adapter, error) {
+	obj := &krm.LoggingLink{}
+	if err := runtime.DefaultUnstructuredConverter.FromUnstructured(u.Object, &obj); err != nil {
+		return nil, fmt.Errorf("error converting to %T: %w", obj, err)
+	}
+
+	id, err := krm.NewLoggingLinkRef(ctx, reader, obj)
+	if err != nil {
+		return nil, err
+	}
+
+	gcpClient, err := m.client(ctx)
+	if err != nil {
+		return nil, err
+	}
+	return &LoggingLinkAdapter{
+		id:        id,
+		gcpClient: gcpClient,
+		desired:   obj,
+	}, nil
+}
+
+/*
+func () ResolveExternalRef(externalRef string) {
+
+}
+*/
+
+func (m *modelLoggingLink) AdapterForURL(ctx context.Context, url string) (directbase.Adapter, error) {
+	// TODO: Support URLs
+	return nil, nil
+}
+
+type LoggingLinkAdapter struct {
+	id        *krm.LoggingLinkRef
+	gcpClient *gcp.ConfigClient
+	desired   *krm.LoggingLink
+	actual    *loggingpb.Link
+}
+
+var _ directbase.Adapter = &LoggingLinkAdapter{}
+
+func (a *LoggingLinkAdapter) Find(ctx context.Context) (bool, error) {
+	log := klog.FromContext(ctx)
+	log.V(2).Info("getting LoggingLink", "name", a.id.External)
+
+	req := &loggingpb.GetLinkRequest{Name: a.id.External}
+	linkpb, err := a.gcpClient.GetLink(ctx, req)
+	if err != nil {
+		if direct.IsNotFound(err) {
+			return false, nil
+		}
+		return false, fmt.Errorf("getting LoggingLink %q: %w", a.id.External, err)
+	}
+
+	a.actual = linkpb
+	return true, nil
+}
+
+func (a *LoggingLinkAdapter) Create(ctx context.Context, createOp *directbase.CreateOperation) error {
+	u := createOp.GetUnstructured()
+	log := klog.FromContext(ctx).WithName(ctrlName)
+	log.V(2).Info("creating object", "u", u)
+
+	mapCtx := &direct.MapContext{}
+
+	desired := a.desired.DeepCopy()
+	resource := LoggingLinkSpec_ToProto(mapCtx, &desired.Spec)
+	if mapCtx.Err() != nil {
+		return mapCtx.Err()
+	}
+
+	// TODO(user): Complete the gcp "CREATE" or "INSERT" request with required fields.
+	// TODO(): 400 error needs to come through on the error
+	// TODO(): there is an implicit dependency on the bucket being active, do we need that here?
+	parent, err := a.id.Parent()
+	if err != nil {
+		return err
+	}
+
+	resourceID := direct.ValueOf(desired.Spec.ResourceID)
+	if resourceID == "" {
+		log.V(2).Info("ResourceID is not set, will use metadata.name")
+		resourceID = desired.Name
+	} else {
+		log.V(2).Info("ResourceID is set, will use")
+		resourceID = *desired.Spec.ResourceID
+	}
+
+	req := &loggingpb.CreateLinkRequest{
+		Parent: parent.String(),
+		Link:   resource,
+		LinkId: resourceID,
+	}
+	op, err := a.gcpClient.CreateLink(ctx, req)
+	if err != nil {
+		return fmt.Errorf("creating Link %s: %w\n", a.id.External, err)
+	}
+	created, err := op.Wait(ctx)
+	if err != nil {
+		return fmt.Errorf("Link %s waiting creation: %w", a.id.External, err)
+	}
+	log.V(2).Info("successfully created Link", "name", a.id.External)
+
+	status := &krm.LoggingLinkStatus{}
+	status.ObservedState = LoggingLinkObservedState_FromProto(mapCtx, created)
+	if mapCtx.Err() != nil {
+		return mapCtx.Err()
+	}
+	status.ExternalRef = &a.id.External
+	return createOp.UpdateStatus(ctx, status, nil)
+}
+
+func (a *LoggingLinkAdapter) Update(ctx context.Context, updateOp *directbase.UpdateOperation) error {
+	return fmt.Errorf("update operation not supported for resource %v %v",
+		a.desired.GroupVersionKind(), k8s.GetNamespacedName(a.desired))
+}
+
+func (a *LoggingLinkAdapter) Export(ctx context.Context) (*unstructured.Unstructured, error) {
+	if a.actual == nil {
+		return nil, fmt.Errorf("Find() not called")
+	}
+	u := &unstructured.Unstructured{}
+
+	obj := &krm.LoggingLink{}
+	mapCtx := &direct.MapContext{}
+	obj.Spec = direct.ValueOf(LoggingLinkSpec_FromProto(mapCtx, a.actual))
+	if mapCtx.Err() != nil {
+		return nil, mapCtx.Err()
+	}
+
+	// TODO(user): Update other resource references
+	/* TODO Bucket Ref?
+	parent, err := a.id.Parent()
+	if err != nil {
+		return nil, err
+	}
+	 obj.Spec.ProjectRef = &refs.ProjectRef{External: parent.String()}
+	 obj.Spec.Location = parent.Location
+	uObj, err := runtime.DefaultUnstructuredConverter.ToUnstructured(obj)
+	if err != nil {
+		return nil, err
+	}
+	*/
+
+	u.SetName(a.actual.Name)
+	u.SetGroupVersionKind(krm.LoggingLinkGVK)
+
+	// TODO uObj is set in the commented out code
+	//u.Object = uObj
+	return u, nil
+}
+
+// Delete implements the Adapter interface.
+func (a *LoggingLinkAdapter) Delete(ctx context.Context, deleteOp *directbase.DeleteOperation) (bool, error) {
+	log := klog.FromContext(ctx)
+	log.V(2).Info("deleting Link", "name", a.id.External)
+
+	req := &loggingpb.DeleteLinkRequest{Name: a.id.External}
+	op, err := a.gcpClient.DeleteLink(ctx, req)
+	if err != nil {
+		return false, fmt.Errorf("deleting Link %s: %w", a.id.External, err)
path: pkg/controller/direct/logging/link_controller.go
user: login: tylerreidwaze
body: Just a note or something to implement here?
created_at: 2025-03-14T22:25:21Z
author_association: COLLABORATOR
diff_hunk: @@ -0,0 +1,120 @@
+// 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 v1alpha1
+
+import (
+	"context"
+	"fmt"
+	"strings"
+
+	"github.com/GoogleCloudPlatform/k8s-config-connector/apis/common"
+	refsv1beta1 "github.com/GoogleCloudPlatform/k8s-config-connector/apis/refs/v1beta1"
+	"sigs.k8s.io/controller-runtime/pkg/client"
+)
+
+// LinkIdentity defines the resource reference to LoggingLink, which "External" field
+// holds the GCP identifier for the KRM object.
+type LinkIdentity struct {
+	parent *LinkParent
+	id     string
+}
+
+func (i *LinkIdentity) String() string {
+	return i.parent.String() + "/links/" + i.id
+}
+
+func (i *LinkIdentity) ID() string {
+	return i.id
+}
+
+func (i *LinkIdentity) Parent() *LinkParent {
+	return i.parent
+}
+
+type LinkParent struct {
+	ProjectID          string
+	Location           string
+	LoggingLogBucketID string
+}
+
+func (p *LinkParent) String() string {
+	return "projects/" + p.ProjectID + "/locations/" + p.Location + "/buckets/" + p.LoggingLogBucketID
+}
+
+// New builds a LinkIdentity from the Config Connector Link object.
+func NewLinkIdentity(ctx context.Context, reader client.Reader, obj *LoggingLink) (*LinkIdentity, error) {
+
+	// Get Parent
+	bucketRef, err := refsv1beta1.ResolveLoggingLogBucketRef(ctx, reader, obj, obj.Spec.LoggingLogBucketRef)
+	if err != nil {
+		return nil, err
+	}
+	projectID := bucketRef.ProjectID
+	if projectID == "" {
+		return nil, fmt.Errorf("cannot resolve project")
+	}
+	location := bucketRef.Location
+
+	// 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
+		actualParent, actualResourceID, err := ParseLinkExternal(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)
path: apis/logging/v1alpha1/link_identity.go
user: login: maqiuyujoyce
body: Understood. The tricky thing is that the code is generated for generic cases, and manual fix will be needed if the resource has edge cases to handle.

Also, for "incoming commit", did you mean a separate PR?
created_at: 2025-03-20T02:16:19Z
author_association: COLLABORATOR
diff_hunk: @@ -0,0 +1,104 @@
+// 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 (
+	refs "github.com/GoogleCloudPlatform/k8s-config-connector/apis/refs/v1beta1"
+	"github.com/GoogleCloudPlatform/k8s-config-connector/pkg/apis/k8s/v1alpha1"
+	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+)
+
+var LoggingLinkGVK = GroupVersion.WithKind("LoggingLink")
+
+// LoggingLinkSpec defines the desired state of LoggingLink
+// +kcc:proto=google.logging.v2.Link
+type LoggingLinkSpec struct {
+	// +kubebuilder:validation:XValidation:rule="self == oldSelf",message="ResourceID field is immutable"
path: apis/logging/v1alpha1/link_types.go
user: login: maqiuyujoyce
body: For the "incoming commit", did you mean a separate PR or did I miss anything?
created_at: 2025-03-20T02:21:11Z
author_association: COLLABORATOR
diff_hunk: @@ -0,0 +1,104 @@
+// 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 (
+	refs "github.com/GoogleCloudPlatform/k8s-config-connector/apis/refs/v1beta1"
+	"github.com/GoogleCloudPlatform/k8s-config-connector/pkg/apis/k8s/v1alpha1"
+	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+)
+
+var LoggingLinkGVK = GroupVersion.WithKind("LoggingLink")
+
+// LoggingLinkSpec defines the desired state of LoggingLink
+// +kcc:proto=google.logging.v2.Link
+type LoggingLinkSpec struct {
+	// +kubebuilder:validation:XValidation:rule="self == oldSelf",message="ResourceID field is immutable"
+	// Immutable.
+	// The LoggingLink name. If not given, the metadata.name will be used.
+	ResourceID *string `json:"resourceID,omitempty"`
+
+	// Describes this link.
+	//  The maximum length of the description is 8000 characters.
+	Description *string `json:"description,omitempty"`
+
+	// Placeholder description
path: apis/logging/v1alpha1/link_types.go
user: login: maqiuyujoyce
body: ditto
created_at: 2025-03-20T02:22:01Z
author_association: COLLABORATOR
diff_hunk: @@ -0,0 +1,104 @@
+// 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 (
+	refs "github.com/GoogleCloudPlatform/k8s-config-connector/apis/refs/v1beta1"
+	"github.com/GoogleCloudPlatform/k8s-config-connector/pkg/apis/k8s/v1alpha1"
+	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+)
+
+var LoggingLinkGVK = GroupVersion.WithKind("LoggingLink")
+
+// LoggingLinkSpec defines the desired state of LoggingLink
+// +kcc:proto=google.logging.v2.Link
+type LoggingLinkSpec struct {
+	// +kubebuilder:validation:XValidation:rule="self == oldSelf",message="ResourceID field is immutable"
+	// Immutable.
+	// The LoggingLink name. If not given, the metadata.name will be used.
+	ResourceID *string `json:"resourceID,omitempty"`
+
+	// Describes this link.
+	//  The maximum length of the description is 8000 characters.
+	Description *string `json:"description,omitempty"`
+
+	// Placeholder description
+	LoggingLogBucketRef *refs.LoggingLogBucketRef `json:"loggingLogBucketRef,omitempty"`
+}
+
+// LoggingLinkStatus defines the config connector machine state of LoggingLink
+type LoggingLinkStatus 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 LoggingLink resource in GCP.
+	ExternalRef *string `json:"externalRef,omitempty"`
+
+	// ObservedState is the state of the resource as most recently observed in GCP.
+	ObservedState *LoggingLinkObservedState `json:"observedState,omitempty"`
+}
+
+// LoggingLinkObservedState is the state of the LoggingLink resource as most recently observed in GCP.
+type LoggingLinkObservedState struct {
+
+	// +optional
+	// +kubebuilder:validation:Format=date-time
+	CreateTime *string `json:"createTime,omitempty"`
+
+	// the lifecycle state might be something more complicated
+	// this is an ENUM, should be safe to use a string
+	LifecycleState *string `json:"lifecycleState,omitempty"`
+
+	// this field is just a string, but its an object(string)
+	// https://github.com/googleapis/googleapis/blob/master/google/logging/v2/logging_config.proto#L1063
+	BigQueryDataset *BigQueryDatasetObservedState `json:"bigQueryDataset,omitempty"`
path: apis/logging/v1alpha1/link_types.go
user: login: maqiuyujoyce
body: ditto
created_at: 2025-03-20T02:22:46Z
author_association: COLLABORATOR
diff_hunk: @@ -0,0 +1,23 @@
+# Copyright 2022 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.
+
+apiVersion: logging.cnrm.cloud.google.com/v1alpha1
+kind: LoggingLink
+metadata:
+  name: logginglink-${uniqueId} # no hyphen here as link ids can only have alphanumeric and underscores
+spec:
+  resourceId: logginglink${uniqueId}
path: pkg/test/resourcefixture/testdata/basic/logging/v1alpha1/logginglogginglink/basiclink/create.yaml
user: login: maqiuyujoyce
body: Field name should be `resourceID`.
created_at: 2025-03-20T02:36:32Z
author_association: COLLABORATOR
diff_hunk: @@ -0,0 +1,260 @@
+// 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 logging
+
+import (
+	"context"
+	"fmt"
+
+	//"reflect"
+
+	krm "github.com/GoogleCloudPlatform/k8s-config-connector/apis/logging/v1alpha1"
+	"github.com/GoogleCloudPlatform/k8s-config-connector/pkg/config"
+	"github.com/GoogleCloudPlatform/k8s-config-connector/pkg/controller/direct"
+	"github.com/GoogleCloudPlatform/k8s-config-connector/pkg/controller/direct/common"
+	"github.com/GoogleCloudPlatform/k8s-config-connector/pkg/controller/direct/directbase"
+	"github.com/GoogleCloudPlatform/k8s-config-connector/pkg/controller/direct/registry"
+	"github.com/GoogleCloudPlatform/k8s-config-connector/pkg/k8s"
+
+	//"net/http"
+
+	gcp "cloud.google.com/go/logging/apiv2"
+	//"google.golang.org/api/option"
+	loggingpb "cloud.google.com/go/logging/apiv2/loggingpb"
+
+	//"google.golang.org/api/option"
+	//"google.golang.org/protobuf/types/known/fieldmaskpb"
+
+	"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
+	"k8s.io/apimachinery/pkg/runtime"
+	"k8s.io/klog/v2"
+	"sigs.k8s.io/controller-runtime/pkg/client"
+)
+
+func init() {
+	registry.RegisterModel(krm.LoggingLinkGVK, NewLoggingLinkModel)
+}
+
+func NewLoggingLinkModel(ctx context.Context, config *config.ControllerConfig) (directbase.Model, error) {
+	return &modelLoggingLink{config: *config}, nil
+}
+
+var _ directbase.Model = &modelLoggingLink{}
+
+type modelLoggingLink struct {
+	config config.ControllerConfig
+}
+
+func (m *modelLoggingLink) client(ctx context.Context) (*gcp.ConfigClient, error) {
+
+	gcpClient, err := gcp.NewConfigRESTClient(ctx)
+	if err != nil {
+		return nil, fmt.Errorf("building Logging Config client: %w", err)
+	}
+	return gcpClient, err
+}
+
+func (m *modelLoggingLink) AdapterForObject(ctx context.Context, reader client.Reader, u *unstructured.Unstructured) (directbase.Adapter, error) {
+	obj := &krm.LoggingLink{}
+	if err := runtime.DefaultUnstructuredConverter.FromUnstructured(u.Object, &obj); err != nil {
+		return nil, fmt.Errorf("error converting to %T: %w", obj, err)
+	}
+
+	linkIdentity, err := krm.NewLinkIdentity(ctx, reader, obj)
+	if err != nil {
+		return nil, err
+	}
+
+	gcpClient, err := m.client(ctx)
+	if err != nil {
+		return nil, err
+	}
+	return &LoggingLinkAdapter{
+		id:        linkIdentity,
+		gcpClient: gcpClient,
+		desired:   obj,
+	}, nil
+}
+
+/*
+func () ResolveExternalRef(externalRef string) {
+
+}
+*/
+
+func (m *modelLoggingLink) AdapterForURL(ctx context.Context, url string) (directbase.Adapter, error) {
+	// TODO: Support URLs
+	return nil, nil
+}
+
+type LoggingLinkAdapter struct {
+	id        *krm.LinkIdentity
+	gcpClient *gcp.ConfigClient
+	desired   *krm.LoggingLink
+	actual    *loggingpb.Link
+}
+
+var _ directbase.Adapter = &LoggingLinkAdapter{}
+
+func (a *LoggingLinkAdapter) Find(ctx context.Context) (bool, error) {
+	log := klog.FromContext(ctx)
+	log.V(2).Info("getting LoggingLink", "name", a.id)
+
+	req := &loggingpb.GetLinkRequest{Name: a.id.String()}
+	linkpb, err := a.gcpClient.GetLink(ctx, req)
+	if err != nil {
+		if direct.IsNotFound(err) {
+			return false, nil
+		}
+		return false, fmt.Errorf("getting LoggingLink %q: %w", a.id, err)
+	}
+
+	a.actual = linkpb
+	return true, nil
+}
+
+func (a *LoggingLinkAdapter) Create(ctx context.Context, createOp *directbase.CreateOperation) error {
+	u := createOp.GetUnstructured()
+	log := klog.FromContext(ctx).WithName(ctrlName)
+	log.V(2).Info("creating object", "u", u)
+
+	mapCtx := &direct.MapContext{}
+
+	desired := a.desired.DeepCopy()
+	resource := LoggingLinkSpec_ToProto(mapCtx, &desired.Spec)
+	if mapCtx.Err() != nil {
+		return mapCtx.Err()
+	}
+
+	// TODO(user): Complete the gcp "CREATE" or "INSERT" request with required fields.
+	// TODO(): 400 error needs to come through on the error
+	// TODO(): there is an implicit dependency on the bucket being active, do we need that here?
+	parent := a.id.Parent()
+
+	resourceID := direct.ValueOf(desired.Spec.ResourceID)
+	if resourceID == "" {
+		log.V(2).Info("ResourceID is not set, will use metadata.name")
+		resourceID = desired.Name
+	} else {
+		log.V(2).Info("ResourceID is set, will use")
+		resourceID = *desired.Spec.ResourceID
+	}
+
+	req := &loggingpb.CreateLinkRequest{
+		Parent: parent.String(),
+		Link:   resource,
+		LinkId: resourceID,
+	}
+	op, err := a.gcpClient.CreateLink(ctx, req)
+	if err != nil {
+		return fmt.Errorf("creating Link %s: %w\n", a.id, err)
+	}
+	created, err := op.Wait(ctx)
+	if err != nil {
+		return fmt.Errorf("Link %s waiting creation: %w", a.id, err)
+	}
+	log.V(2).Info("successfully created Link", "name", a.id)
+
+	status := &krm.LoggingLinkStatus{}
+	status.ObservedState = LoggingLinkObservedState_FromProto(mapCtx, created)
+	if mapCtx.Err() != nil {
+		return mapCtx.Err()
+	}
+	status.ExternalRef = direct.LazyPtr(a.id.String())
+	return createOp.UpdateStatus(ctx, status, nil)
+}
+
+func (a *LoggingLinkAdapter) Update(ctx context.Context, updateOp *directbase.UpdateOperation) error {
+
+	log := klog.FromContext(ctx)
+	log.V(2).Info("updating Logging Link", "name", a.id)
+	mapCtx := &direct.MapContext{}
+
+	desiredPb := LoggingLinkSpec_ToProto(mapCtx, &a.desired.DeepCopy().Spec)
+	if mapCtx.Err() != nil {
+		return mapCtx.Err()
+	}
+
+	paths, err := common.CompareProtoMessage(desiredPb, a.actual, common.BasicDiff)
+	if err != nil {
+		return err
+	}
+	if len(paths) == 0 {
+		log.V(2).Info("no field needs update", "name", a.id)
+		status := &krm.LoggingLinkStatus{}
+		status.ObservedState = LoggingLinkObservedState_FromProto(mapCtx, a.actual)
+		if mapCtx.Err() != nil {
+			return mapCtx.Err()
+		}
+		return updateOp.UpdateStatus(ctx, status, nil)
+	} else {
+		return fmt.Errorf("update operation not supported for resource %v %v",
+			a.desired.GroupVersionKind(), k8s.GetNamespacedName(a.desired))
+	}
+}
+
+func (a *LoggingLinkAdapter) Export(ctx context.Context) (*unstructured.Unstructured, error) {
+	if a.actual == nil {
+		return nil, fmt.Errorf("Find() not called")
+	}
+	u := &unstructured.Unstructured{}
+
+	obj := &krm.LoggingLink{}
+	mapCtx := &direct.MapContext{}
+	obj.Spec = direct.ValueOf(LoggingLinkSpec_FromProto(mapCtx, a.actual))
+	if mapCtx.Err() != nil {
+		return nil, mapCtx.Err()
+	}
+
+	// TODO(user): Update other resource references
+	/* TODO Bucket Ref?
+	parent, err := a.id.Parent()
+	if err != nil {
+		return nil, err
+	}
+	 obj.Spec.ProjectRef = &refs.ProjectRef{External: parent.String()}
+	 obj.Spec.Location = parent.Location
+	uObj, err := runtime.DefaultUnstructuredConverter.ToUnstructured(obj)
+	if err != nil {
+		return nil, err
+	}
+	*/
+
+	u.SetName(a.actual.Name)
+	u.SetGroupVersionKind(krm.LoggingLinkGVK)
+
+	// TODO uObj is set in the commented out code
+	//u.Object = uObj
+	return u, nil
+}
+
+// Delete implements the Adapter interface.
+func (a *LoggingLinkAdapter) Delete(ctx context.Context, deleteOp *directbase.DeleteOperation) (bool, error) {
+	log := klog.FromContext(ctx)
+	log.V(2).Info("deleting Link", "name", a.id)
+
+	req := &loggingpb.DeleteLinkRequest{Name: a.id.String()}
+	op, err := a.gcpClient.DeleteLink(ctx, req)
+	if err != nil {
+		return false, fmt.Errorf("deleting Link %s: %w", a.id, err)
path: pkg/controller/direct/logging/link_controller.go
user: login: maqiuyujoyce
body: This is an example of the special handling when Delete returns 404: https://github.com/GoogleCloudPlatform/k8s-config-connector/blob/master/pkg/controller/direct/apigee/endpointattachment_controller.go#L202
created_at: 2025-03-20T02:45:07Z
author_association: COLLABORATOR
diff_hunk: @@ -0,0 +1,238 @@
+// 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 logging
+
+import (
+	"context"
+	"fmt"
+
+	//"reflect"
+
+	krm "github.com/GoogleCloudPlatform/k8s-config-connector/apis/logging/v1alpha1"
+	"github.com/GoogleCloudPlatform/k8s-config-connector/pkg/config"
+	"github.com/GoogleCloudPlatform/k8s-config-connector/pkg/controller/direct"
+	"github.com/GoogleCloudPlatform/k8s-config-connector/pkg/controller/direct/directbase"
+	"github.com/GoogleCloudPlatform/k8s-config-connector/pkg/controller/direct/registry"
+	"github.com/GoogleCloudPlatform/k8s-config-connector/pkg/k8s"
+
+	//"net/http"
+
+	gcp "cloud.google.com/go/logging/apiv2"
+	//"google.golang.org/api/option"
+	loggingpb "cloud.google.com/go/logging/apiv2/loggingpb"
+
+	//"google.golang.org/api/option"
+	//"google.golang.org/protobuf/types/known/fieldmaskpb"
+
+	"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
+	"k8s.io/apimachinery/pkg/runtime"
+	"k8s.io/klog/v2"
+	"sigs.k8s.io/controller-runtime/pkg/client"
+)
+
+func init() {
+	registry.RegisterModel(krm.LoggingLinkGVK, NewLoggingLinkModel)
+}
+
+func NewLoggingLinkModel(ctx context.Context, config *config.ControllerConfig) (directbase.Model, error) {
+	return &modelLoggingLink{config: *config}, nil
+}
+
+var _ directbase.Model = &modelLoggingLink{}
+
+type modelLoggingLink struct {
+	config config.ControllerConfig
+}
+
+func (m *modelLoggingLink) client(ctx context.Context) (*gcp.ConfigClient, error) {
+
+	gcpClient, err := gcp.NewConfigRESTClient(ctx)
+	if err != nil {
+		return nil, fmt.Errorf("building Logging Config client: %w", err)
+	}
+	return gcpClient, err
+}
+
+func (m *modelLoggingLink) AdapterForObject(ctx context.Context, reader client.Reader, u *unstructured.Unstructured) (directbase.Adapter, error) {
+	obj := &krm.LoggingLink{}
+	if err := runtime.DefaultUnstructuredConverter.FromUnstructured(u.Object, &obj); err != nil {
+		return nil, fmt.Errorf("error converting to %T: %w", obj, err)
+	}
+
+	id, err := krm.NewLoggingLinkRef(ctx, reader, obj)
+	if err != nil {
+		return nil, err
+	}
+
+	gcpClient, err := m.client(ctx)
+	if err != nil {
+		return nil, err
+	}
+	return &LoggingLinkAdapter{
+		id:        id,
+		gcpClient: gcpClient,
+		desired:   obj,
+	}, nil
+}
+
+/*
+func () ResolveExternalRef(externalRef string) {
+
+}
+*/
+
+func (m *modelLoggingLink) AdapterForURL(ctx context.Context, url string) (directbase.Adapter, error) {
+	// TODO: Support URLs
+	return nil, nil
+}
+
+type LoggingLinkAdapter struct {
+	id        *krm.LoggingLinkRef
+	gcpClient *gcp.ConfigClient
+	desired   *krm.LoggingLink
+	actual    *loggingpb.Link
+}
+
+var _ directbase.Adapter = &LoggingLinkAdapter{}
+
+func (a *LoggingLinkAdapter) Find(ctx context.Context) (bool, error) {
+	log := klog.FromContext(ctx)
+	log.V(2).Info("getting LoggingLink", "name", a.id.External)
+
+	req := &loggingpb.GetLinkRequest{Name: a.id.External}
+	linkpb, err := a.gcpClient.GetLink(ctx, req)
+	if err != nil {
+		if direct.IsNotFound(err) {
+			return false, nil
+		}
+		return false, fmt.Errorf("getting LoggingLink %q: %w", a.id.External, err)
+	}
+
+	a.actual = linkpb
+	return true, nil
+}
+
+func (a *LoggingLinkAdapter) Create(ctx context.Context, createOp *directbase.CreateOperation) error {
+	u := createOp.GetUnstructured()
+	log := klog.FromContext(ctx).WithName(ctrlName)
+	log.V(2).Info("creating object", "u", u)
+
+	mapCtx := &direct.MapContext{}
+
+	desired := a.desired.DeepCopy()
+	resource := LoggingLinkSpec_ToProto(mapCtx, &desired.Spec)
+	if mapCtx.Err() != nil {
+		return mapCtx.Err()
+	}
+
+	// TODO(user): Complete the gcp "CREATE" or "INSERT" request with required fields.
+	// TODO(): 400 error needs to come through on the error
+	// TODO(): there is an implicit dependency on the bucket being active, do we need that here?
+	parent, err := a.id.Parent()
+	if err != nil {
+		return err
+	}
+
+	resourceID := direct.ValueOf(desired.Spec.ResourceID)
+	if resourceID == "" {
+		log.V(2).Info("ResourceID is not set, will use metadata.name")
+		resourceID = desired.Name
+	} else {
+		log.V(2).Info("ResourceID is set, will use")
+		resourceID = *desired.Spec.ResourceID
+	}
+
+	req := &loggingpb.CreateLinkRequest{
+		Parent: parent.String(),
+		Link:   resource,
+		LinkId: resourceID,
+	}
+	op, err := a.gcpClient.CreateLink(ctx, req)
+	if err != nil {
+		return fmt.Errorf("creating Link %s: %w\n", a.id.External, err)
+	}
+	created, err := op.Wait(ctx)
+	if err != nil {
+		return fmt.Errorf("Link %s waiting creation: %w", a.id.External, err)
+	}
+	log.V(2).Info("successfully created Link", "name", a.id.External)
+
+	status := &krm.LoggingLinkStatus{}
+	status.ObservedState = LoggingLinkObservedState_FromProto(mapCtx, created)
+	if mapCtx.Err() != nil {
+		return mapCtx.Err()
+	}
+	status.ExternalRef = &a.id.External
+	return createOp.UpdateStatus(ctx, status, nil)
+}
+
+func (a *LoggingLinkAdapter) Update(ctx context.Context, updateOp *directbase.UpdateOperation) error {
+	return fmt.Errorf("update operation not supported for resource %v %v",
+		a.desired.GroupVersionKind(), k8s.GetNamespacedName(a.desired))
+}
+
+func (a *LoggingLinkAdapter) Export(ctx context.Context) (*unstructured.Unstructured, error) {
+	if a.actual == nil {
+		return nil, fmt.Errorf("Find() not called")
+	}
+	u := &unstructured.Unstructured{}
+
+	obj := &krm.LoggingLink{}
+	mapCtx := &direct.MapContext{}
+	obj.Spec = direct.ValueOf(LoggingLinkSpec_FromProto(mapCtx, a.actual))
+	if mapCtx.Err() != nil {
+		return nil, mapCtx.Err()
+	}
+
+	// TODO(user): Update other resource references
+	/* TODO Bucket Ref?
+	parent, err := a.id.Parent()
+	if err != nil {
+		return nil, err
+	}
+	 obj.Spec.ProjectRef = &refs.ProjectRef{External: parent.String()}
+	 obj.Spec.Location = parent.Location
+	uObj, err := runtime.DefaultUnstructuredConverter.ToUnstructured(obj)
+	if err != nil {
+		return nil, err
+	}
+	*/
+
+	u.SetName(a.actual.Name)
+	u.SetGroupVersionKind(krm.LoggingLinkGVK)
+
+	// TODO uObj is set in the commented out code
+	//u.Object = uObj
+	return u, nil
+}
+
+// Delete implements the Adapter interface.
+func (a *LoggingLinkAdapter) Delete(ctx context.Context, deleteOp *directbase.DeleteOperation) (bool, error) {
+	log := klog.FromContext(ctx)
+	log.V(2).Info("deleting Link", "name", a.id.External)
+
+	req := &loggingpb.DeleteLinkRequest{Name: a.id.External}
+	op, err := a.gcpClient.DeleteLink(ctx, req)
+	if err != nil {
+		return false, fmt.Errorf("deleting Link %s: %w", a.id.External, err)
path: pkg/controller/direct/logging/link_controller.go
user: login: maqiuyujoyce
body: Addressed in the new comment: https://github.com/GoogleCloudPlatform/k8s-config-connector/pull/3181#discussion_r2004721752
created_at: 2025-03-20T02:46:37Z
author_association: COLLABORATOR
diff_hunk: @@ -0,0 +1,193 @@
+// 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 = &LoggingLinkRef{}
+
+// LoggingLinkRef defines the resource reference to LoggingLink, which "External" field
+// holds the GCP identifier for the KRM object.
+type LoggingLinkRef struct {
+	// A reference to an externally managed LoggingLink resource.
+	// Should be in the format "projects/<projectID>/locations/<location>/buckets/<bucketID>/links/<linkID>".
+	External string `json:"external,omitempty"`
+
+	// The name of a LoggingLink resource.
+	Name string `json:"name,omitempty"`
+
+	// The namespace of a LoggingLink resource.
+	Namespace string `json:"namespace,omitempty"`
+
+	parent *LoggingLinkParent
+}
+
+// NormalizedExternal provision the "External" value for other resource that depends on LoggingLink.
+// If the "External" is given in the other resource's spec.LoggingLinkRef, the given value will be used.
+// Otherwise, the "Name" and "Namespace" will be used to query the actual LoggingLink object from the cluster.
+func (r *LoggingLinkRef) 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", LoggingLinkGVK.Kind)
+	}
+	// From given External
+	if r.External != "" {
+		if _, _, err := parseLoggingLinkExternal(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(LoggingLinkGVK)
+	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", LoggingLinkGVK, 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 LoggingLinkRef from the Config Connector LoggingLink object.
+func NewLoggingLinkRef(ctx context.Context, reader client.Reader, obj *LoggingLink) (*LoggingLinkRef, error) {
path: apis/logging/v1alpha1/link_reference.go
user: login: maqiuyujoyce
body: Looks like it's already gone after you've regenerated the file?
created_at: 2025-03-20T02:47:01Z
author_association: COLLABORATOR
diff_hunk: @@ -0,0 +1,193 @@
+// 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 = &LoggingLinkRef{}
+
+// LoggingLinkRef defines the resource reference to LoggingLink, which "External" field
+// holds the GCP identifier for the KRM object.
+type LoggingLinkRef struct {
+	// A reference to an externally managed LoggingLink resource.
+	// Should be in the format "projects/<projectID>/locations/<location>/buckets/<bucketID>/links/<linkID>".
+	External string `json:"external,omitempty"`
+
+	// The name of a LoggingLink resource.
+	Name string `json:"name,omitempty"`
+
+	// The namespace of a LoggingLink resource.
+	Namespace string `json:"namespace,omitempty"`
+
+	parent *LoggingLinkParent
+}
+
+// NormalizedExternal provision the "External" value for other resource that depends on LoggingLink.
+// If the "External" is given in the other resource's spec.LoggingLinkRef, the given value will be used.
+// Otherwise, the "Name" and "Namespace" will be used to query the actual LoggingLink object from the cluster.
+func (r *LoggingLinkRef) 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", LoggingLinkGVK.Kind)
+	}
+	// From given External
+	if r.External != "" {
+		if _, _, err := parseLoggingLinkExternal(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(LoggingLinkGVK)
+	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", LoggingLinkGVK, 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 LoggingLinkRef from the Config Connector LoggingLink object.
+func NewLoggingLinkRef(ctx context.Context, reader client.Reader, obj *LoggingLink) (*LoggingLinkRef, error) {
path: apis/logging/v1alpha1/link_reference.go
user: login: tylerreidwaze
body: I removed it manually since it wasn't being used after the most recent changes.  Maybe this wasnt auto generated?
created_at: 2025-03-20T15:06:23Z
author_association: COLLABORATOR
diff_hunk: @@ -0,0 +1,120 @@
+// 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 v1alpha1
+
+import (
+	"context"
+	"fmt"
+	"strings"
+
+	"github.com/GoogleCloudPlatform/k8s-config-connector/apis/common"
+	refsv1beta1 "github.com/GoogleCloudPlatform/k8s-config-connector/apis/refs/v1beta1"
+	"sigs.k8s.io/controller-runtime/pkg/client"
+)
+
+// LinkIdentity defines the resource reference to LoggingLink, which "External" field
+// holds the GCP identifier for the KRM object.
+type LinkIdentity struct {
+	parent *LinkParent
+	id     string
+}
+
+func (i *LinkIdentity) String() string {
+	return i.parent.String() + "/links/" + i.id
+}
+
+func (i *LinkIdentity) ID() string {
+	return i.id
+}
+
+func (i *LinkIdentity) Parent() *LinkParent {
+	return i.parent
+}
+
+type LinkParent struct {
+	ProjectID          string
+	Location           string
+	LoggingLogBucketID string
+}
+
+func (p *LinkParent) String() string {
+	return "projects/" + p.ProjectID + "/locations/" + p.Location + "/buckets/" + p.LoggingLogBucketID
+}
+
+// New builds a LinkIdentity from the Config Connector Link object.
+func NewLinkIdentity(ctx context.Context, reader client.Reader, obj *LoggingLink) (*LinkIdentity, error) {
+
+	// Get Parent
+	bucketRef, err := refsv1beta1.ResolveLoggingLogBucketRef(ctx, reader, obj, obj.Spec.LoggingLogBucketRef)
+	if err != nil {
+		return nil, err
+	}
+	projectID := bucketRef.ProjectID
+	if projectID == "" {
+		return nil, fmt.Errorf("cannot resolve project")
+	}
+	location := bucketRef.Location
+
+	// 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
+		actualParent, actualResourceID, err := ParseLinkExternal(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)
path: apis/logging/v1alpha1/link_identity.go
user: login: tylerreidwaze
body: No, I meant a commit that I hadn't pushed yet.  I am still getting used to github as opposed to gerrit
created_at: 2025-03-20T15:07:15Z
author_association: COLLABORATOR
diff_hunk: @@ -0,0 +1,104 @@
+// 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 (
+	refs "github.com/GoogleCloudPlatform/k8s-config-connector/apis/refs/v1beta1"
+	"github.com/GoogleCloudPlatform/k8s-config-connector/pkg/apis/k8s/v1alpha1"
+	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+)
+
+var LoggingLinkGVK = GroupVersion.WithKind("LoggingLink")
+
+// LoggingLinkSpec defines the desired state of LoggingLink
+// +kcc:proto=google.logging.v2.Link
+type LoggingLinkSpec struct {
+	// +kubebuilder:validation:XValidation:rule="self == oldSelf",message="ResourceID field is immutable"
+	// Immutable.
+	// The LoggingLink name. If not given, the metadata.name will be used.
+	ResourceID *string `json:"resourceID,omitempty"`
+
+	// Describes this link.
+	//  The maximum length of the description is 8000 characters.
+	Description *string `json:"description,omitempty"`
+
+	// Placeholder description
path: apis/logging/v1alpha1/link_types.go
user: login: tylerreidwaze
body: Fix in most recent, not sure what happened here.
created_at: 2025-03-20T15:22:49Z
author_association: COLLABORATOR
diff_hunk: @@ -0,0 +1,104 @@
+// 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 (
+	refs "github.com/GoogleCloudPlatform/k8s-config-connector/apis/refs/v1beta1"
+	"github.com/GoogleCloudPlatform/k8s-config-connector/pkg/apis/k8s/v1alpha1"
+	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+)
+
+var LoggingLinkGVK = GroupVersion.WithKind("LoggingLink")
+
+// LoggingLinkSpec defines the desired state of LoggingLink
+// +kcc:proto=google.logging.v2.Link
+type LoggingLinkSpec struct {
+	// +kubebuilder:validation:XValidation:rule="self == oldSelf",message="ResourceID field is immutable"
path: apis/logging/v1alpha1/link_types.go
user: login: tylerreidwaze
body: I am not sure what happened here.  Somehow this wasnt added to a commit.  Anywho, its fixed now
created_at: 2025-03-20T15:23:00Z
author_association: COLLABORATOR
diff_hunk: @@ -0,0 +1,104 @@
+// 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 (
+	refs "github.com/GoogleCloudPlatform/k8s-config-connector/apis/refs/v1beta1"
+	"github.com/GoogleCloudPlatform/k8s-config-connector/pkg/apis/k8s/v1alpha1"
+	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+)
+
+var LoggingLinkGVK = GroupVersion.WithKind("LoggingLink")
+
+// LoggingLinkSpec defines the desired state of LoggingLink
+// +kcc:proto=google.logging.v2.Link
+type LoggingLinkSpec struct {
+	// +kubebuilder:validation:XValidation:rule="self == oldSelf",message="ResourceID field is immutable"
+	// Immutable.
+	// The LoggingLink name. If not given, the metadata.name will be used.
+	ResourceID *string `json:"resourceID,omitempty"`
+
+	// Describes this link.
+	//  The maximum length of the description is 8000 characters.
+	Description *string `json:"description,omitempty"`
+
+	// Placeholder description
+	LoggingLogBucketRef *refs.LoggingLogBucketRef `json:"loggingLogBucketRef,omitempty"`
+}
+
+// LoggingLinkStatus defines the config connector machine state of LoggingLink
+type LoggingLinkStatus 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 LoggingLink resource in GCP.
+	ExternalRef *string `json:"externalRef,omitempty"`
+
+	// ObservedState is the state of the resource as most recently observed in GCP.
+	ObservedState *LoggingLinkObservedState `json:"observedState,omitempty"`
+}
+
+// LoggingLinkObservedState is the state of the LoggingLink resource as most recently observed in GCP.
+type LoggingLinkObservedState struct {
+
+	// +optional
+	// +kubebuilder:validation:Format=date-time
path: apis/logging/v1alpha1/link_types.go
user: login: tylerreidwaze
body: Another one that was lost, fixed.
created_at: 2025-03-20T15:23:20Z
author_association: COLLABORATOR
diff_hunk: @@ -0,0 +1,104 @@
+// 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 (
+	refs "github.com/GoogleCloudPlatform/k8s-config-connector/apis/refs/v1beta1"
+	"github.com/GoogleCloudPlatform/k8s-config-connector/pkg/apis/k8s/v1alpha1"
+	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+)
+
+var LoggingLinkGVK = GroupVersion.WithKind("LoggingLink")
+
+// LoggingLinkSpec defines the desired state of LoggingLink
+// +kcc:proto=google.logging.v2.Link
+type LoggingLinkSpec struct {
+	// +kubebuilder:validation:XValidation:rule="self == oldSelf",message="ResourceID field is immutable"
+	// Immutable.
+	// The LoggingLink name. If not given, the metadata.name will be used.
+	ResourceID *string `json:"resourceID,omitempty"`
+
+	// Describes this link.
+	//  The maximum length of the description is 8000 characters.
+	Description *string `json:"description,omitempty"`
+
+	// Placeholder description
+	LoggingLogBucketRef *refs.LoggingLogBucketRef `json:"loggingLogBucketRef,omitempty"`
+}
+
+// LoggingLinkStatus defines the config connector machine state of LoggingLink
+type LoggingLinkStatus 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 LoggingLink resource in GCP.
+	ExternalRef *string `json:"externalRef,omitempty"`
+
+	// ObservedState is the state of the resource as most recently observed in GCP.
+	ObservedState *LoggingLinkObservedState `json:"observedState,omitempty"`
+}
+
+// LoggingLinkObservedState is the state of the LoggingLink resource as most recently observed in GCP.
+type LoggingLinkObservedState struct {
+
+	// +optional
+	// +kubebuilder:validation:Format=date-time
+	CreateTime *string `json:"createTime,omitempty"`
path: apis/logging/v1alpha1/link_types.go
user: login: tylerreidwaze
body: Another one that disappeared on me, FIxed
created_at: 2025-03-20T15:24:16Z
author_association: COLLABORATOR
diff_hunk: @@ -0,0 +1,104 @@
+// 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 (
+	refs "github.com/GoogleCloudPlatform/k8s-config-connector/apis/refs/v1beta1"
+	"github.com/GoogleCloudPlatform/k8s-config-connector/pkg/apis/k8s/v1alpha1"
+	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+)
+
+var LoggingLinkGVK = GroupVersion.WithKind("LoggingLink")
+
+// LoggingLinkSpec defines the desired state of LoggingLink
+// +kcc:proto=google.logging.v2.Link
+type LoggingLinkSpec struct {
+	// +kubebuilder:validation:XValidation:rule="self == oldSelf",message="ResourceID field is immutable"
+	// Immutable.
+	// The LoggingLink name. If not given, the metadata.name will be used.
+	ResourceID *string `json:"resourceID,omitempty"`
+
+	// Describes this link.
+	//  The maximum length of the description is 8000 characters.
+	Description *string `json:"description,omitempty"`
+
+	// Placeholder description
+	LoggingLogBucketRef *refs.LoggingLogBucketRef `json:"loggingLogBucketRef,omitempty"`
+}
+
+// LoggingLinkStatus defines the config connector machine state of LoggingLink
+type LoggingLinkStatus 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 LoggingLink resource in GCP.
+	ExternalRef *string `json:"externalRef,omitempty"`
+
+	// ObservedState is the state of the resource as most recently observed in GCP.
+	ObservedState *LoggingLinkObservedState `json:"observedState,omitempty"`
+}
+
+// LoggingLinkObservedState is the state of the LoggingLink resource as most recently observed in GCP.
+type LoggingLinkObservedState struct {
+
+	// +optional
+	// +kubebuilder:validation:Format=date-time
+	CreateTime *string `json:"createTime,omitempty"`
+
+	// the lifecycle state might be something more complicated
+	// this is an ENUM, should be safe to use a string
+	LifecycleState *string `json:"lifecycleState,omitempty"`
path: apis/logging/v1alpha1/link_types.go
user: login: tylerreidwaze
body: Another one that was lost.  Fixed
created_at: 2025-03-20T15:25:07Z
author_association: COLLABORATOR
diff_hunk: @@ -0,0 +1,104 @@
+// 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 (
+	refs "github.com/GoogleCloudPlatform/k8s-config-connector/apis/refs/v1beta1"
+	"github.com/GoogleCloudPlatform/k8s-config-connector/pkg/apis/k8s/v1alpha1"
+	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+)
+
+var LoggingLinkGVK = GroupVersion.WithKind("LoggingLink")
+
+// LoggingLinkSpec defines the desired state of LoggingLink
+// +kcc:proto=google.logging.v2.Link
+type LoggingLinkSpec struct {
+	// +kubebuilder:validation:XValidation:rule="self == oldSelf",message="ResourceID field is immutable"
+	// Immutable.
+	// The LoggingLink name. If not given, the metadata.name will be used.
+	ResourceID *string `json:"resourceID,omitempty"`
+
+	// Describes this link.
+	//  The maximum length of the description is 8000 characters.
+	Description *string `json:"description,omitempty"`
+
+	// Placeholder description
+	LoggingLogBucketRef *refs.LoggingLogBucketRef `json:"loggingLogBucketRef,omitempty"`
+}
+
+// LoggingLinkStatus defines the config connector machine state of LoggingLink
+type LoggingLinkStatus 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 LoggingLink resource in GCP.
+	ExternalRef *string `json:"externalRef,omitempty"`
+
+	// ObservedState is the state of the resource as most recently observed in GCP.
+	ObservedState *LoggingLinkObservedState `json:"observedState,omitempty"`
+}
+
+// LoggingLinkObservedState is the state of the LoggingLink resource as most recently observed in GCP.
+type LoggingLinkObservedState struct {
+
+	// +optional
+	// +kubebuilder:validation:Format=date-time
+	CreateTime *string `json:"createTime,omitempty"`
+
+	// the lifecycle state might be something more complicated
+	// this is an ENUM, should be safe to use a string
+	LifecycleState *string `json:"lifecycleState,omitempty"`
+
+	// this field is just a string, but its an object(string)
+	// https://github.com/googleapis/googleapis/blob/master/google/logging/v2/logging_config.proto#L1063
+	BigQueryDataset *BigQueryDatasetObservedState `json:"bigQueryDataset,omitempty"`
path: apis/logging/v1alpha1/link_types.go
user: login: tylerreidwaze
body: Resolved.
created_at: 2025-03-20T15:25:38Z
author_association: COLLABORATOR
diff_hunk: @@ -0,0 +1,238 @@
+// 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 logging
+
+import (
+	"context"
+	"fmt"
+
+	//"reflect"
+
+	krm "github.com/GoogleCloudPlatform/k8s-config-connector/apis/logging/v1alpha1"
+	"github.com/GoogleCloudPlatform/k8s-config-connector/pkg/config"
+	"github.com/GoogleCloudPlatform/k8s-config-connector/pkg/controller/direct"
+	"github.com/GoogleCloudPlatform/k8s-config-connector/pkg/controller/direct/directbase"
+	"github.com/GoogleCloudPlatform/k8s-config-connector/pkg/controller/direct/registry"
+	"github.com/GoogleCloudPlatform/k8s-config-connector/pkg/k8s"
+
+	//"net/http"
+
+	gcp "cloud.google.com/go/logging/apiv2"
+	//"google.golang.org/api/option"
+	loggingpb "cloud.google.com/go/logging/apiv2/loggingpb"
+
+	//"google.golang.org/api/option"
+	//"google.golang.org/protobuf/types/known/fieldmaskpb"
+
+	"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
+	"k8s.io/apimachinery/pkg/runtime"
+	"k8s.io/klog/v2"
+	"sigs.k8s.io/controller-runtime/pkg/client"
+)
+
+func init() {
+	registry.RegisterModel(krm.LoggingLinkGVK, NewLoggingLinkModel)
+}
+
+func NewLoggingLinkModel(ctx context.Context, config *config.ControllerConfig) (directbase.Model, error) {
+	return &modelLoggingLink{config: *config}, nil
+}
+
+var _ directbase.Model = &modelLoggingLink{}
+
+type modelLoggingLink struct {
+	config config.ControllerConfig
+}
+
+func (m *modelLoggingLink) client(ctx context.Context) (*gcp.ConfigClient, error) {
+
+	gcpClient, err := gcp.NewConfigRESTClient(ctx)
+	if err != nil {
+		return nil, fmt.Errorf("building Logging Config client: %w", err)
+	}
+	return gcpClient, err
+}
+
+func (m *modelLoggingLink) AdapterForObject(ctx context.Context, reader client.Reader, u *unstructured.Unstructured) (directbase.Adapter, error) {
+	obj := &krm.LoggingLink{}
+	if err := runtime.DefaultUnstructuredConverter.FromUnstructured(u.Object, &obj); err != nil {
+		return nil, fmt.Errorf("error converting to %T: %w", obj, err)
+	}
+
+	id, err := krm.NewLoggingLinkRef(ctx, reader, obj)
+	if err != nil {
+		return nil, err
+	}
+
+	gcpClient, err := m.client(ctx)
+	if err != nil {
+		return nil, err
+	}
+	return &LoggingLinkAdapter{
+		id:        id,
+		gcpClient: gcpClient,
+		desired:   obj,
+	}, nil
+}
+
+/*
+func () ResolveExternalRef(externalRef string) {
+
+}
+*/
+
+func (m *modelLoggingLink) AdapterForURL(ctx context.Context, url string) (directbase.Adapter, error) {
+	// TODO: Support URLs
+	return nil, nil
+}
+
+type LoggingLinkAdapter struct {
+	id        *krm.LoggingLinkRef
+	gcpClient *gcp.ConfigClient
+	desired   *krm.LoggingLink
+	actual    *loggingpb.Link
+}
+
+var _ directbase.Adapter = &LoggingLinkAdapter{}
+
+func (a *LoggingLinkAdapter) Find(ctx context.Context) (bool, error) {
+	log := klog.FromContext(ctx)
+	log.V(2).Info("getting LoggingLink", "name", a.id.External)
+
+	req := &loggingpb.GetLinkRequest{Name: a.id.External}
+	linkpb, err := a.gcpClient.GetLink(ctx, req)
+	if err != nil {
+		if direct.IsNotFound(err) {
+			return false, nil
+		}
+		return false, fmt.Errorf("getting LoggingLink %q: %w", a.id.External, err)
+	}
+
+	a.actual = linkpb
+	return true, nil
+}
+
+func (a *LoggingLinkAdapter) Create(ctx context.Context, createOp *directbase.CreateOperation) error {
+	u := createOp.GetUnstructured()
+	log := klog.FromContext(ctx).WithName(ctrlName)
+	log.V(2).Info("creating object", "u", u)
path: pkg/controller/direct/logging/link_controller.go
user: login: tylerreidwaze
body: Fixed.
created_at: 2025-03-20T15:27:49Z
author_association: COLLABORATOR
diff_hunk: @@ -0,0 +1,117 @@
+// 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 v1beta1
+
+import (
+	"context"
+	"fmt"
+	"strings"
+
+	apierrors "k8s.io/apimachinery/pkg/api/errors"
+	"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
+	"k8s.io/apimachinery/pkg/runtime/schema"
+	"k8s.io/apimachinery/pkg/types"
+	"sigs.k8s.io/controller-runtime/pkg/client"
+)
+
+type LoggingLogBucketRef struct {
+	/* The LoggingLogBucket selfLink, when not managed by Config Connector. */
path: apis/refs/v1beta1/logbucketref.go
user: login: tylerreidwaze
body: Another one that slipped off, fixed.
created_at: 2025-03-20T15:33:24Z
author_association: COLLABORATOR
diff_hunk: @@ -0,0 +1,23 @@
+# Copyright 2022 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.
+
+apiVersion: logging.cnrm.cloud.google.com/v1alpha1
+kind: LoggingLink
+metadata:
+  name: logginglink-${uniqueId} # no hyphen here as link ids can only have alphanumeric and underscores
+spec:
+  resourceId: logginglink${uniqueId}
path: pkg/test/resourcefixture/testdata/basic/logging/v1alpha1/logginglogginglink/basiclink/create.yaml
user: login: tylerreidwaze
body: Done
created_at: 2025-03-20T15:47:09Z
author_association: COLLABORATOR
diff_hunk: @@ -0,0 +1,260 @@
+// 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 logging
+
+import (
+	"context"
+	"fmt"
+
+	//"reflect"
+
+	krm "github.com/GoogleCloudPlatform/k8s-config-connector/apis/logging/v1alpha1"
+	"github.com/GoogleCloudPlatform/k8s-config-connector/pkg/config"
+	"github.com/GoogleCloudPlatform/k8s-config-connector/pkg/controller/direct"
+	"github.com/GoogleCloudPlatform/k8s-config-connector/pkg/controller/direct/common"
+	"github.com/GoogleCloudPlatform/k8s-config-connector/pkg/controller/direct/directbase"
+	"github.com/GoogleCloudPlatform/k8s-config-connector/pkg/controller/direct/registry"
+	"github.com/GoogleCloudPlatform/k8s-config-connector/pkg/k8s"
+
+	//"net/http"
+
+	gcp "cloud.google.com/go/logging/apiv2"
+	//"google.golang.org/api/option"
+	loggingpb "cloud.google.com/go/logging/apiv2/loggingpb"
+
+	//"google.golang.org/api/option"
+	//"google.golang.org/protobuf/types/known/fieldmaskpb"
+
+	"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
+	"k8s.io/apimachinery/pkg/runtime"
+	"k8s.io/klog/v2"
+	"sigs.k8s.io/controller-runtime/pkg/client"
+)
+
+func init() {
+	registry.RegisterModel(krm.LoggingLinkGVK, NewLoggingLinkModel)
+}
+
+func NewLoggingLinkModel(ctx context.Context, config *config.ControllerConfig) (directbase.Model, error) {
+	return &modelLoggingLink{config: *config}, nil
+}
+
+var _ directbase.Model = &modelLoggingLink{}
+
+type modelLoggingLink struct {
+	config config.ControllerConfig
+}
+
+func (m *modelLoggingLink) client(ctx context.Context) (*gcp.ConfigClient, error) {
+
+	gcpClient, err := gcp.NewConfigRESTClient(ctx)
+	if err != nil {
+		return nil, fmt.Errorf("building Logging Config client: %w", err)
+	}
+	return gcpClient, err
+}
+
+func (m *modelLoggingLink) AdapterForObject(ctx context.Context, reader client.Reader, u *unstructured.Unstructured) (directbase.Adapter, error) {
+	obj := &krm.LoggingLink{}
+	if err := runtime.DefaultUnstructuredConverter.FromUnstructured(u.Object, &obj); err != nil {
+		return nil, fmt.Errorf("error converting to %T: %w", obj, err)
+	}
+
+	linkIdentity, err := krm.NewLinkIdentity(ctx, reader, obj)
+	if err != nil {
+		return nil, err
+	}
+
+	gcpClient, err := m.client(ctx)
+	if err != nil {
+		return nil, err
+	}
+	return &LoggingLinkAdapter{
+		id:        linkIdentity,
+		gcpClient: gcpClient,
+		desired:   obj,
+	}, nil
+}
+
+/*
+func () ResolveExternalRef(externalRef string) {
+
+}
+*/
+
+func (m *modelLoggingLink) AdapterForURL(ctx context.Context, url string) (directbase.Adapter, error) {
+	// TODO: Support URLs
+	return nil, nil
+}
+
+type LoggingLinkAdapter struct {
+	id        *krm.LinkIdentity
+	gcpClient *gcp.ConfigClient
+	desired   *krm.LoggingLink
+	actual    *loggingpb.Link
+}
+
+var _ directbase.Adapter = &LoggingLinkAdapter{}
+
+func (a *LoggingLinkAdapter) Find(ctx context.Context) (bool, error) {
+	log := klog.FromContext(ctx)
+	log.V(2).Info("getting LoggingLink", "name", a.id)
+
+	req := &loggingpb.GetLinkRequest{Name: a.id.String()}
+	linkpb, err := a.gcpClient.GetLink(ctx, req)
+	if err != nil {
+		if direct.IsNotFound(err) {
+			return false, nil
+		}
+		return false, fmt.Errorf("getting LoggingLink %q: %w", a.id, err)
+	}
+
+	a.actual = linkpb
+	return true, nil
+}
+
+func (a *LoggingLinkAdapter) Create(ctx context.Context, createOp *directbase.CreateOperation) error {
+	u := createOp.GetUnstructured()
+	log := klog.FromContext(ctx).WithName(ctrlName)
+	log.V(2).Info("creating object", "u", u)
+
+	mapCtx := &direct.MapContext{}
+
+	desired := a.desired.DeepCopy()
+	resource := LoggingLinkSpec_ToProto(mapCtx, &desired.Spec)
+	if mapCtx.Err() != nil {
+		return mapCtx.Err()
+	}
+
+	// TODO(user): Complete the gcp "CREATE" or "INSERT" request with required fields.
+	// TODO(): 400 error needs to come through on the error
+	// TODO(): there is an implicit dependency on the bucket being active, do we need that here?
+	parent := a.id.Parent()
+
+	resourceID := direct.ValueOf(desired.Spec.ResourceID)
+	if resourceID == "" {
+		log.V(2).Info("ResourceID is not set, will use metadata.name")
+		resourceID = desired.Name
+	} else {
+		log.V(2).Info("ResourceID is set, will use")
+		resourceID = *desired.Spec.ResourceID
+	}
+
+	req := &loggingpb.CreateLinkRequest{
+		Parent: parent.String(),
+		Link:   resource,
+		LinkId: resourceID,
+	}
+	op, err := a.gcpClient.CreateLink(ctx, req)
+	if err != nil {
+		return fmt.Errorf("creating Link %s: %w\n", a.id, err)
+	}
+	created, err := op.Wait(ctx)
+	if err != nil {
+		return fmt.Errorf("Link %s waiting creation: %w", a.id, err)
+	}
+	log.V(2).Info("successfully created Link", "name", a.id)
+
+	status := &krm.LoggingLinkStatus{}
+	status.ObservedState = LoggingLinkObservedState_FromProto(mapCtx, created)
+	if mapCtx.Err() != nil {
+		return mapCtx.Err()
+	}
+	status.ExternalRef = direct.LazyPtr(a.id.String())
+	return createOp.UpdateStatus(ctx, status, nil)
+}
+
+func (a *LoggingLinkAdapter) Update(ctx context.Context, updateOp *directbase.UpdateOperation) error {
+
+	log := klog.FromContext(ctx)
+	log.V(2).Info("updating Logging Link", "name", a.id)
+	mapCtx := &direct.MapContext{}
+
+	desiredPb := LoggingLinkSpec_ToProto(mapCtx, &a.desired.DeepCopy().Spec)
+	if mapCtx.Err() != nil {
+		return mapCtx.Err()
+	}
+
+	paths, err := common.CompareProtoMessage(desiredPb, a.actual, common.BasicDiff)
+	if err != nil {
+		return err
+	}
+	if len(paths) == 0 {
+		log.V(2).Info("no field needs update", "name", a.id)
+		status := &krm.LoggingLinkStatus{}
+		status.ObservedState = LoggingLinkObservedState_FromProto(mapCtx, a.actual)
+		if mapCtx.Err() != nil {
+			return mapCtx.Err()
+		}
+		return updateOp.UpdateStatus(ctx, status, nil)
+	} else {
+		return fmt.Errorf("update operation not supported for resource %v %v",
+			a.desired.GroupVersionKind(), k8s.GetNamespacedName(a.desired))
+	}
+}
+
+func (a *LoggingLinkAdapter) Export(ctx context.Context) (*unstructured.Unstructured, error) {
+	if a.actual == nil {
+		return nil, fmt.Errorf("Find() not called")
+	}
+	u := &unstructured.Unstructured{}
+
+	obj := &krm.LoggingLink{}
+	mapCtx := &direct.MapContext{}
+	obj.Spec = direct.ValueOf(LoggingLinkSpec_FromProto(mapCtx, a.actual))
+	if mapCtx.Err() != nil {
+		return nil, mapCtx.Err()
+	}
+
+	// TODO(user): Update other resource references
+	/* TODO Bucket Ref?
+	parent, err := a.id.Parent()
+	if err != nil {
+		return nil, err
+	}
+	 obj.Spec.ProjectRef = &refs.ProjectRef{External: parent.String()}
+	 obj.Spec.Location = parent.Location
+	uObj, err := runtime.DefaultUnstructuredConverter.ToUnstructured(obj)
+	if err != nil {
+		return nil, err
+	}
+	*/
+
+	u.SetName(a.actual.Name)
+	u.SetGroupVersionKind(krm.LoggingLinkGVK)
+
+	// TODO uObj is set in the commented out code
+	//u.Object = uObj
+	return u, nil
+}
+
+// Delete implements the Adapter interface.
+func (a *LoggingLinkAdapter) Delete(ctx context.Context, deleteOp *directbase.DeleteOperation) (bool, error) {
+	log := klog.FromContext(ctx)
+	log.V(2).Info("deleting Link", "name", a.id)
+
+	req := &loggingpb.DeleteLinkRequest{Name: a.id.String()}
+	op, err := a.gcpClient.DeleteLink(ctx, req)
+	if err != nil {
+		return false, fmt.Errorf("deleting Link %s: %w", a.id, err)
path: pkg/controller/direct/logging/link_controller.go
user: login: tylerreidwaze
body: Done
created_at: 2025-03-20T15:49:29Z
author_association: COLLABORATOR
diff_hunk: @@ -0,0 +1,22 @@
+# Copyright 2022 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.
+
+apiVersion: logging.cnrm.cloud.google.com/v1alpha1
+kind: LoggingLink
+metadata:
+  name: logginglink${uniqueId} # no hyphen here as link ids can only have alphanumeric and underscores
path: pkg/test/resourcefixture/testdata/basic/logging/v1alpha1/logginglogginglink/basiclink/create.yaml
user: login: tylerreidwaze
body: Now this is fixed, found a bug with this so good call :)
created_at: 2025-03-20T21:19:23Z
author_association: COLLABORATOR
diff_hunk: @@ -0,0 +1,115 @@
+// 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 v1alpha1
+
+import (
+	"context"
+	"fmt"
+	"strings"
+
+	"github.com/GoogleCloudPlatform/k8s-config-connector/apis/common"
+	refsv1beta1 "github.com/GoogleCloudPlatform/k8s-config-connector/apis/refs/v1beta1"
+	"sigs.k8s.io/controller-runtime/pkg/client"
+)
+
+// LinkIdentity defines the resource reference to LoggingLink, which "External" field
+// holds the GCP identifier for the KRM object.
+type LinkIdentity struct {
+	parent *LinkParent
+	id     string
+}
+
+func (i *LinkIdentity) String() string {
+	return i.parent.String() + "/links/" + i.id
+}
+
+func (i *LinkIdentity) ID() string {
+	return i.id
+}
+
+func (i *LinkIdentity) Parent() *LinkParent {
+	return i.parent
+}
+
+type LinkParent struct {
+	ProjectID          string
+	Location           string
+	LoggingLogBucketID string
+}
+
+func (p *LinkParent) String() string {
+	return "projects/" + p.ProjectID + "/locations/" + p.Location + "/buckets/" + p.LoggingLogBucketID
+}
+
+// New builds a LinkIdentity from the Config Connector Link object.
+func NewLinkIdentity(ctx context.Context, reader client.Reader, obj *LoggingLink) (*LinkIdentity, error) {
+
+	// Get Parent
+	bucketRef, err := refsv1beta1.ResolveLoggingLogBucketRef(ctx, reader, obj, obj.Spec.LoggingLogBucketRef)
+	if err != nil {
+		return nil, err
+	}
+	projectID := bucketRef.ProjectID
+	if projectID == "" {
+		return nil, fmt.Errorf("cannot resolve project")
+	}
+	location := bucketRef.Location
+	bucketID := bucketRef.LoggingLogBucketID
+
+	// 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
+		actualParent, _, err := ParseLinkExternal(externalRef)
+		if err != nil {
+			return nil, err
+		}
+		if actualParent.String() != bucketRef.String() {
+			return nil, fmt.Errorf("actualParent changed, expect %s, got %s", actualParent.String(), bucketRef.String())
+		}
+
path: apis/logging/v1alpha1/link_identity.go
user: login: maqiuyujoyce
body: You'll still want to compare actual resourceID (the second returned value of function ParseLinkExternal) and resourceID here. E.g.,
```
actualParent, actualResourceID, err := ParseLinkExternal(externalRef)
...
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)
}
```
created_at: 2025-03-21T02:39:08Z
author_association: COLLABORATOR
diff_hunk: @@ -0,0 +1,265 @@
+// 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 logging
+
+import (
+	"context"
+	"fmt"
+
+	//"reflect"
+
+	krm "github.com/GoogleCloudPlatform/k8s-config-connector/apis/logging/v1alpha1"
+	"github.com/GoogleCloudPlatform/k8s-config-connector/pkg/config"
+	"github.com/GoogleCloudPlatform/k8s-config-connector/pkg/controller/direct"
+	"github.com/GoogleCloudPlatform/k8s-config-connector/pkg/controller/direct/common"
+	"github.com/GoogleCloudPlatform/k8s-config-connector/pkg/controller/direct/directbase"
+	"github.com/GoogleCloudPlatform/k8s-config-connector/pkg/controller/direct/registry"
+	"github.com/GoogleCloudPlatform/k8s-config-connector/pkg/k8s"
+
+	//"net/http"
+
+	gcp "cloud.google.com/go/logging/apiv2"
+	//"google.golang.org/api/option"
+	loggingpb "cloud.google.com/go/logging/apiv2/loggingpb"
+
+	//"google.golang.org/api/option"
+	//"google.golang.org/protobuf/types/known/fieldmaskpb"
+
+	"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
+	"k8s.io/apimachinery/pkg/runtime"
+	"k8s.io/klog/v2"
+	"sigs.k8s.io/controller-runtime/pkg/client"
+)
+
+func init() {
+	registry.RegisterModel(krm.LoggingLinkGVK, NewLoggingLinkModel)
+}
+
+func NewLoggingLinkModel(ctx context.Context, config *config.ControllerConfig) (directbase.Model, error) {
+	return &modelLoggingLink{config: *config}, nil
+}
+
+var _ directbase.Model = &modelLoggingLink{}
+
+type modelLoggingLink struct {
+	config config.ControllerConfig
+}
+
+func (m *modelLoggingLink) client(ctx context.Context) (*gcp.ConfigClient, error) {
+
+	gcpClient, err := gcp.NewConfigRESTClient(ctx)
+	if err != nil {
+		return nil, fmt.Errorf("building Logging Config client: %w", err)
+	}
+	return gcpClient, err
+}
+
+func (m *modelLoggingLink) AdapterForObject(ctx context.Context, reader client.Reader, u *unstructured.Unstructured) (directbase.Adapter, error) {
+	obj := &krm.LoggingLink{}
+	if err := runtime.DefaultUnstructuredConverter.FromUnstructured(u.Object, &obj); err != nil {
+		return nil, fmt.Errorf("error converting to %T: %w", obj, err)
+	}
+
+	linkIdentity, err := krm.NewLinkIdentity(ctx, reader, obj)
+	if err != nil {
+		return nil, err
+	}
+
+	gcpClient, err := m.client(ctx)
+	if err != nil {
+		return nil, err
+	}
+	return &LoggingLinkAdapter{
+		id:        linkIdentity,
+		gcpClient: gcpClient,
+		desired:   obj,
+	}, nil
+}
+
+/*
+func () ResolveExternalRef(externalRef string) {
+
+}
+*/
+
+func (m *modelLoggingLink) AdapterForURL(ctx context.Context, url string) (directbase.Adapter, error) {
+	// TODO: Support URLs
+	return nil, nil
+}
+
+type LoggingLinkAdapter struct {
+	id        *krm.LinkIdentity
+	gcpClient *gcp.ConfigClient
+	desired   *krm.LoggingLink
+	actual    *loggingpb.Link
+}
+
+var _ directbase.Adapter = &LoggingLinkAdapter{}
+
+func (a *LoggingLinkAdapter) Find(ctx context.Context) (bool, error) {
+	log := klog.FromContext(ctx)
+	log.V(2).Info("getting LoggingLink", "name", a.id)
+
+	req := &loggingpb.GetLinkRequest{Name: a.id.String()}
+	linkpb, err := a.gcpClient.GetLink(ctx, req)
+	if err != nil {
+		if direct.IsNotFound(err) {
+			return false, nil
+		}
+		return false, fmt.Errorf("getting LoggingLink %q: %w", a.id, err)
+	}
+
+	a.actual = linkpb
+	return true, nil
+}
+
+func (a *LoggingLinkAdapter) Create(ctx context.Context, createOp *directbase.CreateOperation) error {
+	u := createOp.GetUnstructured()
+	log := klog.FromContext(ctx).WithName(ctrlName)
+	log.V(2).Info("creating LoggingLink", "u", u)
+
+	mapCtx := &direct.MapContext{}
+
+	desired := a.desired.DeepCopy()
+	resource := LoggingLinkSpec_ToProto(mapCtx, &desired.Spec)
+	if mapCtx.Err() != nil {
+		return mapCtx.Err()
+	}
+
+	// TODO(user): Complete the gcp "CREATE" or "INSERT" request with required fields.
+	// TODO(): 400 error needs to come through on the error
+	// TODO(): there is an implicit dependency on the bucket being active, do we need that here?
path: pkg/controller/direct/logging/link_controller.go
user: login: maqiuyujoyce
body: Let's leverage the response of the create call to enforce this dependency.
created_at: 2025-03-21T02:46:34Z
author_association: COLLABORATOR
diff_hunk: @@ -0,0 +1,314 @@
+=== RUN   TestAllInSeries
path: real.txt
user: login: maqiuyujoyce
body: Could you clean up this file?
created_at: 2025-03-21T02:48:11Z
author_association: COLLABORATOR
diff_hunk: @@ -0,0 +1,115 @@
+// 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 v1alpha1
+
+import (
+	"context"
+	"fmt"
+	"strings"
+
+	"github.com/GoogleCloudPlatform/k8s-config-connector/apis/common"
+	refsv1beta1 "github.com/GoogleCloudPlatform/k8s-config-connector/apis/refs/v1beta1"
+	"sigs.k8s.io/controller-runtime/pkg/client"
+)
+
+// LinkIdentity defines the resource reference to LoggingLink, which "External" field
+// holds the GCP identifier for the KRM object.
+type LinkIdentity struct {
+	parent *LinkParent
+	id     string
+}
+
+func (i *LinkIdentity) String() string {
+	return i.parent.String() + "/links/" + i.id
+}
+
+func (i *LinkIdentity) ID() string {
+	return i.id
+}
+
+func (i *LinkIdentity) Parent() *LinkParent {
+	return i.parent
+}
+
+type LinkParent struct {
+	ProjectID          string
+	Location           string
+	LoggingLogBucketID string
+}
+
+func (p *LinkParent) String() string {
+	return "projects/" + p.ProjectID + "/locations/" + p.Location + "/buckets/" + p.LoggingLogBucketID
+}
+
+// New builds a LinkIdentity from the Config Connector Link object.
+func NewLinkIdentity(ctx context.Context, reader client.Reader, obj *LoggingLink) (*LinkIdentity, error) {
+
+	// Get Parent
+	bucketRef, err := refsv1beta1.ResolveLoggingLogBucketRef(ctx, reader, obj, obj.Spec.LoggingLogBucketRef)
+	if err != nil {
+		return nil, err
+	}
+	projectID := bucketRef.ProjectID
+	if projectID == "" {
+		return nil, fmt.Errorf("cannot resolve project")
+	}
+	location := bucketRef.Location
+	bucketID := bucketRef.LoggingLogBucketID
+
+	// 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
+		actualParent, _, err := ParseLinkExternal(externalRef)
+		if err != nil {
+			return nil, err
+		}
+		if actualParent.String() != bucketRef.String() {
+			return nil, fmt.Errorf("actualParent changed, expect %s, got %s", actualParent.String(), bucketRef.String())
+		}
+
path: apis/logging/v1alpha1/link_identity.go
user: login: tylerreidwaze
body: Yes, thanks for the snippet.  Fixed
created_at: 2025-03-21T17:18:08Z
author_association: COLLABORATOR
diff_hunk: @@ -0,0 +1,265 @@
+// 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 logging
+
+import (
+	"context"
+	"fmt"
+
+	//"reflect"
+
+	krm "github.com/GoogleCloudPlatform/k8s-config-connector/apis/logging/v1alpha1"
+	"github.com/GoogleCloudPlatform/k8s-config-connector/pkg/config"
+	"github.com/GoogleCloudPlatform/k8s-config-connector/pkg/controller/direct"
+	"github.com/GoogleCloudPlatform/k8s-config-connector/pkg/controller/direct/common"
+	"github.com/GoogleCloudPlatform/k8s-config-connector/pkg/controller/direct/directbase"
+	"github.com/GoogleCloudPlatform/k8s-config-connector/pkg/controller/direct/registry"
+	"github.com/GoogleCloudPlatform/k8s-config-connector/pkg/k8s"
+
+	//"net/http"
+
+	gcp "cloud.google.com/go/logging/apiv2"
+	//"google.golang.org/api/option"
+	loggingpb "cloud.google.com/go/logging/apiv2/loggingpb"
+
+	//"google.golang.org/api/option"
+	//"google.golang.org/protobuf/types/known/fieldmaskpb"
+
+	"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
+	"k8s.io/apimachinery/pkg/runtime"
+	"k8s.io/klog/v2"
+	"sigs.k8s.io/controller-runtime/pkg/client"
+)
+
+func init() {
+	registry.RegisterModel(krm.LoggingLinkGVK, NewLoggingLinkModel)
+}
+
+func NewLoggingLinkModel(ctx context.Context, config *config.ControllerConfig) (directbase.Model, error) {
+	return &modelLoggingLink{config: *config}, nil
+}
+
+var _ directbase.Model = &modelLoggingLink{}
+
+type modelLoggingLink struct {
+	config config.ControllerConfig
+}
+
+func (m *modelLoggingLink) client(ctx context.Context) (*gcp.ConfigClient, error) {
+
+	gcpClient, err := gcp.NewConfigRESTClient(ctx)
+	if err != nil {
+		return nil, fmt.Errorf("building Logging Config client: %w", err)
+	}
+	return gcpClient, err
+}
+
+func (m *modelLoggingLink) AdapterForObject(ctx context.Context, reader client.Reader, u *unstructured.Unstructured) (directbase.Adapter, error) {
+	obj := &krm.LoggingLink{}
+	if err := runtime.DefaultUnstructuredConverter.FromUnstructured(u.Object, &obj); err != nil {
+		return nil, fmt.Errorf("error converting to %T: %w", obj, err)
+	}
+
+	linkIdentity, err := krm.NewLinkIdentity(ctx, reader, obj)
+	if err != nil {
+		return nil, err
+	}
+
+	gcpClient, err := m.client(ctx)
+	if err != nil {
+		return nil, err
+	}
+	return &LoggingLinkAdapter{
+		id:        linkIdentity,
+		gcpClient: gcpClient,
+		desired:   obj,
+	}, nil
+}
+
+/*
+func () ResolveExternalRef(externalRef string) {
+
+}
+*/
+
+func (m *modelLoggingLink) AdapterForURL(ctx context.Context, url string) (directbase.Adapter, error) {
+	// TODO: Support URLs
+	return nil, nil
+}
+
+type LoggingLinkAdapter struct {
+	id        *krm.LinkIdentity
+	gcpClient *gcp.ConfigClient
+	desired   *krm.LoggingLink
+	actual    *loggingpb.Link
+}
+
+var _ directbase.Adapter = &LoggingLinkAdapter{}
+
+func (a *LoggingLinkAdapter) Find(ctx context.Context) (bool, error) {
+	log := klog.FromContext(ctx)
+	log.V(2).Info("getting LoggingLink", "name", a.id)
+
+	req := &loggingpb.GetLinkRequest{Name: a.id.String()}
+	linkpb, err := a.gcpClient.GetLink(ctx, req)
+	if err != nil {
+		if direct.IsNotFound(err) {
+			return false, nil
+		}
+		return false, fmt.Errorf("getting LoggingLink %q: %w", a.id, err)
+	}
+
+	a.actual = linkpb
+	return true, nil
+}
+
+func (a *LoggingLinkAdapter) Create(ctx context.Context, createOp *directbase.CreateOperation) error {
+	u := createOp.GetUnstructured()
+	log := klog.FromContext(ctx).WithName(ctrlName)
+	log.V(2).Info("creating LoggingLink", "u", u)
+
+	mapCtx := &direct.MapContext{}
+
+	desired := a.desired.DeepCopy()
+	resource := LoggingLinkSpec_ToProto(mapCtx, &desired.Spec)
+	if mapCtx.Err() != nil {
+		return mapCtx.Err()
+	}
+
+	// TODO(user): Complete the gcp "CREATE" or "INSERT" request with required fields.
+	// TODO(): 400 error needs to come through on the error
+	// TODO(): there is an implicit dependency on the bucket being active, do we need that here?
path: pkg/controller/direct/logging/link_controller.go
user: login: tylerreidwaze
body: I think this is actually already happening due to the call to wait().  Cleaned up these comments
created_at: 2025-03-21T17:18:37Z
author_association: COLLABORATOR
diff_hunk: @@ -0,0 +1,314 @@
+=== RUN   TestAllInSeries
path: real.txt
user: login: tylerreidwaze
body: Yes, I ammended an old commit and complete removed any references to this in the source tree
created_at: 2025-03-21T17:19:00Z
author_association: COLLABORATOR

PR comments:
user: login: tylerreidwaze
created_at: 2025-03-20T21:20:30Z
body: Tests have been run locally and are passing against real GCP.   Rebase against master complete.  Sorry for the issues and confusing commits I had in here
author_association: COLLABORATOR
user: login: github-advanced-security[bot]
created_at: 2025-03-21T17:18:43Z
body: This pull request sets up GitHub code scanning for this repository. Once the scans have completed and the checks have passed, the analysis results for this pull request branch will appear on [this overview](/GoogleCloudPlatform/k8s-config-connector/security/code-scanning?query=pr%3A3181+is%3Aopen). Once you merge this pull request, the 'Security' tab will show more code scanning analysis results (for example, for the default branch). Depending on your configuration and choice of analysis tool, future pull requests will be annotated with code scanning analysis results. For more information about GitHub code scanning, check out [the documentation](https://docs.github.com/code-security/code-scanning/introduction-to-code-scanning/about-code-scanning). 
author_association: NONE
user: login: google-oss-prow[bot]
created_at: 2025-03-21T21:49:16Z
body: [APPROVALNOTIFIER] This PR is **APPROVED**

This pull-request has been approved by: *<a href="https://github.com/GoogleCloudPlatform/k8s-config-connector/pull/3181#pullrequestreview-2707339792" title="Approved">maqiuyujoyce</a>*

The full list of commands accepted by this bot can be found [here](https://oss.gprow.dev/command-help?repo=GoogleCloudPlatform%2Fk8s-config-connector).

The pull request process is described [here](https://git.k8s.io/community/contributors/guide/owners.md#the-code-review-process)

<details >
Needs approval from an approver in each of these files:

- ~~[OWNERS](https://github.com/GoogleCloudPlatform/k8s-config-connector/blob/master/OWNERS)~~ [maqiuyujoyce]

Approvers can indicate their approval by writing `/approve` in a comment
Approvers can cancel approval by writing `/approve cancel` in a comment
</details>
<!-- META={"approvers":[]} -->
author_association: CONTRIBUTOR