Skip to content

Commit aee91d2

Browse files
authored
feat: add namespace agent (#157)
* add namespace agent * fix imports
1 parent af0e979 commit aee91d2

7 files changed

Lines changed: 225 additions & 9 deletions

File tree

cmd/agent.go

Lines changed: 12 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -4,16 +4,15 @@ import (
44
"os"
55
"time"
66

7+
"github.com/pluralsh/deployment-operator/pkg/controller"
8+
"github.com/pluralsh/deployment-operator/pkg/controller/namespaces"
9+
"github.com/pluralsh/deployment-operator/pkg/controller/pipelinegates"
10+
"github.com/pluralsh/deployment-operator/pkg/controller/restore"
11+
"github.com/pluralsh/deployment-operator/pkg/controller/service"
712
"github.com/samber/lo"
813
"golang.org/x/net/context"
914
"k8s.io/client-go/rest"
10-
11-
"github.com/pluralsh/deployment-operator/pkg/controller/pipelinegates"
1215
ctrclient "sigs.k8s.io/controller-runtime/pkg/client"
13-
14-
"github.com/pluralsh/deployment-operator/pkg/controller"
15-
"github.com/pluralsh/deployment-operator/pkg/controller/restore"
16-
"github.com/pluralsh/deployment-operator/pkg/controller/service"
1716
)
1817

1918
func runAgent(opt *options, config *rest.Config, ctx context.Context, k8sClient ctrclient.Client) (*controller.ControllerManager, *service.ServiceReconciler, *pipelinegates.GateReconciler) {
@@ -63,6 +62,13 @@ func runAgent(opt *options, config *rest.Config, ctx context.Context, k8sClient
6362
Queue: rr.RestoreQueue,
6463
})
6564

65+
ns := namespaces.NewNamespaceReconciler(mgr.GetClient(), k8sClient, r)
66+
mgr.AddController(&controller.Controller{
67+
Name: "Managed Namespace Controller",
68+
Do: ns,
69+
Queue: ns.NamespaceQueue,
70+
})
71+
6672
if err := mgr.Start(); err != nil {
6773
setupLog.Error(err, "unable to start controller manager")
6874
os.Exit(1)

go.mod

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ require (
1919
github.com/orcaman/concurrent-map/v2 v2.0.1
2020
github.com/osteele/liquid v1.3.2
2121
github.com/pkg/errors v0.9.1
22-
github.com/pluralsh/console-client-go v0.1.8
22+
github.com/pluralsh/console-client-go v0.1.11
2323
github.com/pluralsh/controller-reconcile-helper v0.0.4
2424
github.com/pluralsh/gophoenix v0.1.3-0.20231201014135-dff1b4309e34
2525
github.com/pluralsh/polly v0.1.7

go.sum

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -512,8 +512,8 @@ github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINE
512512
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
513513
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
514514
github.com/pkg/sftp v1.13.1/go.mod h1:3HaPG6Dq1ILlpPZRO0HVMrsydcdLt6HRDccSgb87qRg=
515-
github.com/pluralsh/console-client-go v0.1.8 h1:/MLVzacjCuqbb8bGsZornvya7ubMt2jeNM0dxPJduQU=
516-
github.com/pluralsh/console-client-go v0.1.8/go.mod h1:eyCiLA44YbXiYyJh8303jk5JdPkt9McgCo5kBjk4lKo=
515+
github.com/pluralsh/console-client-go v0.1.11 h1:QqbLOtEBQtfj/7gg7mDCwYLX3F7wCAiol9T5zCVAKq4=
516+
github.com/pluralsh/console-client-go v0.1.11/go.mod h1:eyCiLA44YbXiYyJh8303jk5JdPkt9McgCo5kBjk4lKo=
517517
github.com/pluralsh/controller-reconcile-helper v0.0.4 h1:1o+7qYSyoeqKFjx+WgQTxDz4Q2VMpzprJIIKShxqG0E=
518518
github.com/pluralsh/controller-reconcile-helper v0.0.4/go.mod h1:AfY0gtteD6veBjmB6jiRx/aR4yevEf6K0M13/pGan/s=
519519
github.com/pluralsh/gophoenix v0.1.3-0.20231201014135-dff1b4309e34 h1:ab2PN+6if/Aq3/sJM0AVdy1SYuMAnq4g20VaKhTm/Bw=

pkg/client/console.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -77,4 +77,6 @@ type Client interface {
7777
GetClusterGates(after *string, first *int64) (*console.PagedClusterGates, error)
7878
UpdateGate(id string, attributes console.GateUpdateAttributes) error
7979
UpsertConstraints(constraints []*console.PolicyConstraintAttributes) (*console.UpsertPolicyConstraints, error)
80+
GetNamespace(id string) (*console.ManagedNamespaceFragment, error)
81+
ListNamespaces(after *string, first *int64) (*console.ListClusterNamespaces_ClusterManagedNamespaces, error)
8082
}

pkg/client/namespace.go

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
package client
2+
3+
import (
4+
"fmt"
5+
6+
console "github.com/pluralsh/console-client-go"
7+
)
8+
9+
func (c *client) GetNamespace(id string) (*console.ManagedNamespaceFragment, error) {
10+
restore, err := c.consoleClient.GetNamespace(c.ctx, id)
11+
if err != nil {
12+
return nil, err
13+
}
14+
15+
return restore.ManagedNamespace, nil
16+
}
17+
18+
func (c *client) ListNamespaces(after *string, first *int64) (*console.ListClusterNamespaces_ClusterManagedNamespaces, error) {
19+
resp, err := c.consoleClient.ListClusterNamespaces(c.ctx, after, first, nil, nil)
20+
if err != nil {
21+
return nil, err
22+
}
23+
if resp.ClusterManagedNamespaces == nil {
24+
return nil, fmt.Errorf("the response from ListNamespaces is nil")
25+
}
26+
return resp.ClusterManagedNamespaces, nil
27+
}
Lines changed: 164 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,164 @@
1+
package namespaces
2+
3+
import (
4+
"context"
5+
"fmt"
6+
"reflect"
7+
"time"
8+
9+
console "github.com/pluralsh/console-client-go"
10+
clienterrors "github.com/pluralsh/deployment-operator/internal/errors"
11+
"github.com/pluralsh/deployment-operator/pkg/client"
12+
"github.com/pluralsh/deployment-operator/pkg/controller"
13+
"github.com/pluralsh/deployment-operator/pkg/websocket"
14+
"github.com/pluralsh/polly/algorithms"
15+
v1 "k8s.io/api/core/v1"
16+
apierrors "k8s.io/apimachinery/pkg/api/errors"
17+
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
18+
"k8s.io/client-go/util/workqueue"
19+
ctrlclient "sigs.k8s.io/controller-runtime/pkg/client"
20+
"sigs.k8s.io/controller-runtime/pkg/log"
21+
"sigs.k8s.io/controller-runtime/pkg/reconcile"
22+
)
23+
24+
type NamespaceReconciler struct {
25+
ConsoleClient client.Client
26+
K8sClient ctrlclient.Client
27+
NamespaceQueue workqueue.RateLimitingInterface
28+
NamespaceCache *client.Cache[console.ManagedNamespaceFragment]
29+
}
30+
31+
func NewNamespaceReconciler(consoleClient client.Client, k8sClient ctrlclient.Client, refresh time.Duration) *NamespaceReconciler {
32+
return &NamespaceReconciler{
33+
ConsoleClient: consoleClient,
34+
K8sClient: k8sClient,
35+
NamespaceQueue: workqueue.NewRateLimitingQueue(workqueue.DefaultControllerRateLimiter()),
36+
NamespaceCache: client.NewCache[console.ManagedNamespaceFragment](refresh, func(id string) (*console.ManagedNamespaceFragment, error) {
37+
return consoleClient.GetNamespace(id)
38+
}),
39+
}
40+
}
41+
42+
func (n *NamespaceReconciler) GetPublisher() (string, websocket.Publisher) {
43+
return "namespace.event", &socketPublisher{
44+
restoreQueue: n.NamespaceQueue,
45+
restoreCache: n.NamespaceCache,
46+
}
47+
}
48+
49+
func (n *NamespaceReconciler) WipeCache() {
50+
n.NamespaceCache.Wipe()
51+
}
52+
53+
func (n *NamespaceReconciler) ShutdownQueue() {
54+
n.NamespaceQueue.ShutDown()
55+
}
56+
57+
func (n *NamespaceReconciler) ListNamespaces(ctx context.Context) *algorithms.Pager[*console.ManagedNamespaceEdgeFragment] {
58+
logger := log.FromContext(ctx)
59+
logger.Info("create namespace pager")
60+
fetch := func(page *string, size int64) ([]*console.ManagedNamespaceEdgeFragment, *algorithms.PageInfo, error) {
61+
resp, err := n.ConsoleClient.ListNamespaces(page, &size)
62+
if err != nil {
63+
logger.Error(err, "failed to fetch namespaces")
64+
return nil, nil, err
65+
}
66+
pageInfo := &algorithms.PageInfo{
67+
HasNext: resp.PageInfo.HasNextPage,
68+
After: resp.PageInfo.EndCursor,
69+
PageSize: size,
70+
}
71+
return resp.Edges, pageInfo, nil
72+
}
73+
return algorithms.NewPager[*console.ManagedNamespaceEdgeFragment](controller.DefaultPageSize, fetch)
74+
}
75+
76+
func (n *NamespaceReconciler) Poll(ctx context.Context) (done bool, err error) {
77+
logger := log.FromContext(ctx)
78+
logger.Info("fetching namespaces")
79+
pager := n.ListNamespaces(ctx)
80+
81+
for pager.HasNext() {
82+
namespaces, err := pager.NextPage()
83+
if err != nil {
84+
logger.Error(err, "failed to fetch namespace list")
85+
return false, nil
86+
}
87+
for _, namespace := range namespaces {
88+
logger.Info("sending update for", "namespace", namespace.Node.ID)
89+
n.NamespaceQueue.Add(namespace.Node.ID)
90+
}
91+
}
92+
93+
return false, nil
94+
}
95+
96+
func (n *NamespaceReconciler) Reconcile(ctx context.Context, id string) (reconcile.Result, error) {
97+
logger := log.FromContext(ctx)
98+
logger.Info("attempting to sync namespace", "id", id)
99+
namespace, err := n.NamespaceCache.Get(id)
100+
if err != nil {
101+
if clienterrors.IsNotFound(err) {
102+
logger.Info("namespace already deleted", "id", id)
103+
return reconcile.Result{}, nil
104+
}
105+
logger.Error(err, fmt.Sprintf("failed to fetch namespace: %s, ignoring for now", id))
106+
return reconcile.Result{}, err
107+
}
108+
logger.Info("upsert namespace", "name", namespace.Name)
109+
if err = n.UpsertNamespace(ctx, namespace); err != nil {
110+
return reconcile.Result{}, err
111+
}
112+
113+
return reconcile.Result{}, nil
114+
}
115+
116+
func (n *NamespaceReconciler) UpsertNamespace(ctx context.Context, fragment *console.ManagedNamespaceFragment) error {
117+
var labels map[string]string
118+
var annotations map[string]string
119+
120+
if fragment.Labels != nil {
121+
labels = convertMap(fragment.Labels)
122+
}
123+
if fragment.Annotations != nil {
124+
annotations = convertMap(fragment.Annotations)
125+
}
126+
existing := &v1.Namespace{}
127+
err := n.K8sClient.Get(ctx, ctrlclient.ObjectKey{Name: fragment.Name}, existing)
128+
if err != nil {
129+
if apierrors.IsNotFound(err) {
130+
if err := n.K8sClient.Create(ctx, &v1.Namespace{
131+
ObjectMeta: metav1.ObjectMeta{
132+
Name: fragment.Name,
133+
Labels: labels,
134+
Annotations: annotations,
135+
},
136+
}); err != nil {
137+
return err
138+
}
139+
return nil
140+
}
141+
return err
142+
}
143+
144+
if !reflect.DeepEqual(labels, existing.Labels) || !reflect.DeepEqual(annotations, existing.Annotations) {
145+
existing.Labels = labels
146+
existing.Annotations = annotations
147+
if err := n.K8sClient.Update(ctx, existing); err != nil {
148+
return err
149+
}
150+
}
151+
152+
return nil
153+
}
154+
155+
func convertMap(in map[string]interface{}) map[string]string {
156+
res := make(map[string]string)
157+
for k, v := range in {
158+
value, ok := v.(string)
159+
if ok {
160+
res[k] = value
161+
}
162+
}
163+
return res
164+
}
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
package namespaces
2+
3+
import (
4+
console "github.com/pluralsh/console-client-go"
5+
"github.com/pluralsh/deployment-operator/pkg/client"
6+
"k8s.io/client-go/util/workqueue"
7+
)
8+
9+
type socketPublisher struct {
10+
restoreQueue workqueue.RateLimitingInterface
11+
restoreCache *client.Cache[console.ManagedNamespaceFragment]
12+
}
13+
14+
func (sp *socketPublisher) Publish(id string) {
15+
sp.restoreCache.Expire(id)
16+
sp.restoreQueue.Add(id)
17+
}

0 commit comments

Comments
 (0)