Skip to content

Commit

Permalink
Direct code tagstagkey
Browse files Browse the repository at this point in the history
  • Loading branch information
justinsb committed Feb 7, 2024
1 parent eba170a commit ae7f29c
Show file tree
Hide file tree
Showing 4 changed files with 306 additions and 9 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -207,25 +207,25 @@ func (a *adapter) buildCreateRequest(ctx context.Context) (*pb.CreateKeyRequest,
}, nil
}

func (a *adapter) Create(ctx context.Context) (*unstructured.Unstructured, error) {
func (a *adapter) Create(ctx context.Context, u *unstructured.Unstructured) error {
req, err := a.buildCreateRequest(ctx)
if err != nil {
return nil, err
return err
}

op, err := a.gcp.CreateKey(ctx, req)
if err != nil {
return nil, fmt.Errorf("creating key: %w", err)
return fmt.Errorf("creating key: %w", err)
}
// TODO: Is the resourceID returned if it is dynamically created? Maybe we should create the UUID?
created, err := op.Wait(ctx)
if err != nil {
return nil, fmt.Errorf("waiting for key creation: %w", err)
return fmt.Errorf("waiting for key creation: %w", err)
}
log := klog.FromContext(ctx)
log.V(2).Info("created key", "key", created)
// TODO: Return created object
return nil, nil
return nil
}

func (a *adapter) Update(ctx context.Context) (*unstructured.Unstructured, error) {
Expand Down
8 changes: 4 additions & 4 deletions pkg/controller/direct/directbase/directbase_controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ type Model interface {
type Adapter interface {
Find(ctx context.Context) (bool, error)
Delete(ctx context.Context) error
Create(ctx context.Context) (*unstructured.Unstructured, error)
Create(ctx context.Context, u *unstructured.Unstructured) error
Update(ctx context.Context) (*unstructured.Unstructured, error)
}

Expand Down Expand Up @@ -239,7 +239,7 @@ func (r *reconcileContext) doReconcile(ctx context.Context, u *unstructured.Unst
//policy.Spec.Etag = ""

if !existsAlready {
if _, err = adapter.Create(ctx); err != nil {
if err := adapter.Create(ctx, u); err != nil {
if unwrappedErr, ok := lifecyclehandler.CausedByUnresolvableDeps(err); ok {
logger.Info(unwrappedErr.Error(), "resource", k8s.GetNamespacedName(u))
return r.handleUnresolvableDeps(ctx, u, unwrappedErr)
Expand All @@ -261,8 +261,8 @@ func (r *reconcileContext) doReconcile(ctx context.Context, u *unstructured.Unst
return false, nil
}

func (r *reconcileContext) handleUpToDate(ctx context.Context, policy *unstructured.Unstructured) error {
resource, err := toK8sResource(policy)
func (r *reconcileContext) handleUpToDate(ctx context.Context, u *unstructured.Unstructured) error {
resource, err := toK8sResource(u)
if err != nil {
return fmt.Errorf("error converting to k8s resource while handling %v event: %w", k8s.UpToDate, err)
}
Expand Down
292 changes: 292 additions & 0 deletions pkg/controller/direct/tags/tagstagkey_controller.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,292 @@
// 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 tagstagkey

import (
"context"
"fmt"
"strings"

"google.golang.org/api/option"
"google.golang.org/protobuf/types/known/fieldmaskpb"
"k8s.io/klog/v2"

pb "cloud.google.com/go/resourcemanager/apiv3/resourcemanagerpb"
"github.com/GoogleCloudPlatform/k8s-config-connector/pkg/clients/generated/apis/tags/v1beta1"
"github.com/GoogleCloudPlatform/k8s-config-connector/pkg/controller"
"github.com/GoogleCloudPlatform/k8s-config-connector/pkg/controller/direct/directbase"
"github.com/googleapis/gax-go/v2/apierror"

gcp "cloud.google.com/go/resourcemanager/apiv3"

"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
"k8s.io/apimachinery/pkg/runtime"
"sigs.k8s.io/controller-runtime/pkg/manager"

. "github.com/GoogleCloudPlatform/k8s-config-connector/pkg/controller/direct/mappings"
)

// Add creates a new controller and adds it to the Manager.
// The Manager will set fields on the Controller and start it when the Manager is started.
func Add(mgr manager.Manager, config *controller.Config) error {
gvk := v1beta1.TagsTagKeyGVK

return directbase.Add(mgr, gvk, &model{config: *config})
}

type model struct {
config controller.Config
}

var keyMapping = NewMapping(&pb.TagKey{}, &v1beta1.TagsTagKey{},
Spec("shortName"),
Spec("description"),
Spec("parent"),
Status("namespacedName"),
Ignore("createTime"),
Ignore("updateTime"),
Ignore("etag"),
Ignore("etag"),
Ignore("name"), // TODO: Should be ResourceID?
Ignore("purpose"), // TODO: Map
Ignore("purposeData"), // TODO: Map
).
MustBuild()

type adapter struct {
id string

desired *v1beta1.TagsTagKey
actual *v1beta1.TagsTagKey

gcp *gcp.TagKeysClient
}

func (m *model) client(ctx context.Context) (*gcp.TagKeysClient, error) {
var opts []option.ClientOption
if m.config.UserAgent != "" {
opts = append(opts, option.WithUserAgent(m.config.UserAgent))
}
if m.config.HTTPClient != nil {
opts = append(opts, option.WithHTTPClient(m.config.HTTPClient))
}
if m.config.UserProjectOverride && m.config.BillingProject != "" {
opts = append(opts, option.WithQuotaProject(m.config.BillingProject))
}

// if m.config.Endpoint != "" {
// opts = append(opts, option.WithEndpoint(m.config.Endpoint))
// }

gcpClient, err := gcp.NewTagKeysRESTClient(ctx, opts...)
if err != nil {
return nil, fmt.Errorf("building tagKeys client: %w", err)
}
return gcpClient, err
}

func (m *model) AdapterForObject(ctx context.Context, u *unstructured.Unstructured) (directbase.Adapter, error) {
gcp, err := m.client(ctx)
if err != nil {
return nil, err
}

// TODO: Just fetch this object?
obj := &v1beta1.TagsTagKey{}
if err := runtime.DefaultUnstructuredConverter.FromUnstructured(u.Object, &obj); err != nil {
return nil, fmt.Errorf("error converting to %T: %w", obj, err)
}

id := ValueOf(obj.Spec.ResourceID)

return &adapter{
id: id,
desired: obj,
gcp: gcp,
}, nil
}

func ValueOf[T any](p *T) T {
var v T
if p != nil {
v = *p
}
return v
}

func (a *adapter) Find(ctx context.Context) (bool, error) {
log := klog.FromContext(ctx)
log.Info("find tagKey", "id", a.id)
if a.id == "" {
return false, nil
}

req := &pb.GetTagKeyRequest{
Name: a.fullyQualifiedName(),
}
key, err := a.gcp.GetTagKey(ctx, req)
if err != nil {
if IsNotFound(err) {
klog.Warningf("tagKey was not found: %v", err)
return false, nil
}
return false, err
}

u := &v1beta1.TagsTagKey{}
if err := keyMapping.Map(key, u); err != nil {
return false, err
}
a.actual = u

return true, nil
}

func (a *adapter) Delete(ctx context.Context) error {
// TODO: Delete via status selfLink?
req := &pb.DeleteTagKeyRequest{
Name: a.fullyQualifiedName(),
}
op, err := a.gcp.DeleteTagKey(ctx, req)
if err != nil {
return fmt.Errorf("deleting tagKey: %w", err)
}

if _, err := op.Wait(ctx); err != nil {
return fmt.Errorf("waiting for tagKey deletion to complete: %w", err)
}

return nil
}

func (a *adapter) Create(ctx context.Context, u *unstructured.Unstructured) error {
desired := &pb.TagKey{}
if err := keyMapping.Map(a.desired, desired); err != nil {
return err
}

req := &pb.CreateTagKeyRequest{
TagKey: desired,
}

op, err := a.gcp.CreateTagKey(ctx, req)
if err != nil {
return fmt.Errorf("creating tagKey: %w", err)
}
created, err := op.Wait(ctx)
if err != nil {
return fmt.Errorf("waiting for tagKey creation: %w", err)
}
log := klog.FromContext(ctx)
log.Info("created tagKey", "tagKey", created)

resourceID := strings.TrimPrefix(created.Name, "tagKeys/")
if err := unstructured.SetNestedField(u.Object, resourceID, "spec", "resourceID"); err != nil {
return err
}
return nil
// createdObj := &v1beta1.TagsTagKey{}
// if err := keyMapping.Map(created, createdObj); err != nil {
// return nil, err
// }
// log.Info("created tagKey", "tagKey", createdObj)

// data, err := runtime.DefaultUnstructuredConverter.ToUnstructured(createdObj)
// if err != nil {
// return nil, err
// }
// u := &unstructured.Unstructured{Object: data}
// log.Info("created tagKey", "tagKey", u)
// return u, nil
}

func (a *adapter) Update(ctx context.Context) (*unstructured.Unstructured, error) {

if true {
req := &pb.GetTagKeyRequest{
Name: a.fullyQualifiedName(),
}
a.gcp.GetTagKey(ctx, req)
}

if true {
req := &pb.GetTagKeyRequest{
Name: a.fullyQualifiedName(),
}
a.gcp.GetTagKey(ctx, req)
}

tagKey := &pb.TagKey{}

updateMask := &fieldmaskpb.FieldMask{}

if a.desired.Spec.Description != nil {
tagKey.Description = *a.desired.Spec.Description
if tagKey.Description != ValueOf(a.actual.Spec.Description) {
updateMask.Paths = append(updateMask.Paths, "description")
}
}

if len(updateMask.Paths) == 0 {
// TODO: Log/warn/error?
return nil, nil
}

tagKey.Name = a.fullyQualifiedName()

req := &pb.UpdateTagKeyRequest{
TagKey: tagKey,
UpdateMask: updateMask,
}

req.TagKey.Name = a.fullyQualifiedName()

op, err := a.gcp.UpdateTagKey(ctx, req)
if err != nil {
return nil, fmt.Errorf("updating tagKey: %w", err)
}
updated, err := op.Wait(ctx)
if err != nil {
return nil, fmt.Errorf("waiting for tagKey update: %w", err)
}
log := klog.FromContext(ctx)
log.Info("updated tagKey", "tagKey", updated)
return nil, nil
}

func (a *adapter) fullyQualifiedName() string {
return fmt.Sprintf("tagKeys/%s", a.id)
}

// 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
}
switch err := err.(type) {
case *apierror.APIError:
if err.HTTPCode() == code {
return true
}
default:
klog.Warningf("unexpected error type %T", err)
}
return false
}

// IsNotFound returns true if the given error is an HTTP 404.
func IsNotFound(err error) bool {
return HasHTTPCode(err, 404)
}
5 changes: 5 additions & 0 deletions pkg/controller/registration/registration_controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ import (
dclcontroller "github.com/GoogleCloudPlatform/k8s-config-connector/pkg/controller/dcl"
"github.com/GoogleCloudPlatform/k8s-config-connector/pkg/controller/deletiondefender"
"github.com/GoogleCloudPlatform/k8s-config-connector/pkg/controller/direct/apikeys/apikeyskey"
"github.com/GoogleCloudPlatform/k8s-config-connector/pkg/controller/direct/tags/tagstagkey"
"github.com/GoogleCloudPlatform/k8s-config-connector/pkg/controller/gsakeysecretgenerator"
"github.com/GoogleCloudPlatform/k8s-config-connector/pkg/controller/iam/auditconfig"
"github.com/GoogleCloudPlatform/k8s-config-connector/pkg/controller/iam/partialpolicy"
Expand Down Expand Up @@ -191,6 +192,10 @@ func registerDefaultController(r *ReconcileRegistration, config *controller.Conf
if err := apikeyskey.Add(r.mgr, config); err != nil {
return nil, err
}
case "TagsTagKey":
if err := tagstagkey.Add(r.mgr, config); err != nil {
return nil, err
}
default:
// register controllers for dcl-based CRDs
if val, ok := crd.Labels[k8s.DCL2CRDLabel]; ok && val == "true" {
Expand Down

0 comments on commit ae7f29c

Please sign in to comment.