1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
package plcidentity
import (
"context"
"errors"
"fmt"
"strings"
"time"
"github.com/johnandersen777/socialweb-computer-kcp/api/v1alpha1"
ctlruntime "github.com/johnandersen777/socialweb-computer-kcp/internal/controllers/runtime"
"github.com/johnandersen777/socialweb-computer-kcp/internal/kit/conds"
"github.com/johnandersen777/socialweb-computer-kcp/internal/statusstate"
)
const (
Name = "plcidentity"
Export = "applications"
ConditionReady = "Ready"
ReasonObserved = "Observed"
ReasonWaiting = "Waiting"
ReasonSourceUnavailable = "SourceUnavailable"
ReasonSourceError = "SourceError"
ReasonNotFound = "NotFound"
DefaultRefresh = 30 * time.Second
)
type Directory interface {
Identity(ctx context.Context, did string) (statusstate.PLCIdentity, error)
}
var _ Directory = (*statusstate.HTTPPLCClient)(nil)
type Options struct {
Directory Directory
Scope func(workspace string) bool
Refresh time.Duration
}
type Controller struct {
directory Directory
refresh time.Duration
}
func New(opts Options) *Controller {
return &Controller{directory: opts.Directory, refresh: ctlruntime.Period(opts.Refresh, DefaultRefresh)}
}
func Register(m *ctlruntime.Manager, opts Options) (*Controller, error) {
if err := m.AddToScheme(v1alpha1.AddToScheme); err != nil {
return nil, err
}
ctl := New(opts)
options := []ctlruntime.Option{ctlruntime.Export(Export)}
if opts.Scope != nil {
options = append(options, ctlruntime.InWorkspaces(opts.Scope))
}
if err := ctlruntime.For[*v1alpha1.PlcIdentity](m, Name, ctl, options...); err != nil {
return nil, fmt.Errorf("plcidentity: registering the PlcIdentity controller: %w", err)
}
return ctl, nil
}
func (c *Controller) Reconcile(ctx context.Context, req ctlruntime.Request, object *v1alpha1.PlcIdentity) (ctlruntime.Result, error) {
if !object.DeletionTimestamp.IsZero() {
return ctlruntime.Result{}, nil
}
generation := object.Generation
status := v1alpha1.PlcIdentityStatus{ObservedGeneration: generation, Conditions: object.Status.Conditions}
did := strings.TrimSpace(object.Spec.DID)
if did == "" {
conds.Set(&status.Conditions, generation, ConditionReady, false, ReasonWaiting, "the PlcIdentity needs a did")
return ctlruntime.Result{}, c.apply(ctx, req, object, status)
}
if c.directory == nil {
conds.Set(&status.Conditions, generation, ConditionReady, false, ReasonSourceUnavailable, "no PLC directory is configured")
return ctlruntime.Result{}, c.apply(ctx, req, object, status)
}
identity, err := c.directory.Identity(ctx, did)
if err != nil {
conds.Set(&status.Conditions, generation, ConditionReady, false, ReasonSourceError, "reading the PLC log: "+err.Error())
return ctlruntime.Result{}, errors.Join(fmt.Errorf("plcidentity: reading the PLC log of %s: %w", did, err), c.apply(ctx, req, object, status))
}
if !identity.Found {
conds.Set(&status.Conditions, generation, ConditionReady, false, ReasonNotFound, "the PLC directory holds no current operation for "+did)
} else {
status.Head = identity.Head
status.OpCount = identity.OpCount
status.Tombstoned = identity.Tombstoned
conds.Set(&status.Conditions, generation, ConditionReady, true, ReasonObserved, "the PLC identity has current operations")
}
if err := c.apply(ctx, req, object, status); err != nil {
return ctlruntime.Result{}, err
}
return ctlruntime.RefreshAfter(c.refresh), nil
}
func (c *Controller) apply(ctx context.Context, req ctlruntime.Request, object *v1alpha1.PlcIdentity, status v1alpha1.PlcIdentityStatus) error {
next := object.DeepCopy()
next.Status = status
return req.ApplyStatus(ctx, next)
}