Skip to content

Commit da4a9d0

Browse files
committed
Add client billing event support
Signed-off-by: Tamal Saha <tamal@appscode.com>
1 parent a8e7b9e commit da4a9d0

File tree

12 files changed

+576
-36
lines changed

12 files changed

+576
-36
lines changed

go.mod

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -8,16 +8,16 @@ require (
88
github.com/cloudevents/sdk-go/v2 v2.15.2
99
github.com/nats-io/nats.go v1.38.0
1010
github.com/pkg/errors v0.9.1
11-
go.bytebuilders.dev/license-verifier v0.14.4
12-
go.bytebuilders.dev/license-verifier/kubernetes v0.14.4
11+
go.bytebuilders.dev/license-verifier v0.14.6
12+
go.bytebuilders.dev/license-verifier/kubernetes v0.14.6
1313
gomodules.xyz/counter v0.0.1
1414
gomodules.xyz/sync v0.1.0
1515
k8s.io/api v0.30.2
1616
k8s.io/apimachinery v0.30.2
1717
k8s.io/client-go v0.30.2
1818
k8s.io/klog/v2 v2.130.1
1919
kmodules.xyz/client-go v0.30.44
20-
kmodules.xyz/resource-metadata v0.24.1
20+
kmodules.xyz/resource-metadata v0.24.3-0.20250128191600-6318acdd841d
2121
sigs.k8s.io/controller-runtime v0.18.4
2222
)
2323

@@ -91,7 +91,7 @@ require (
9191
github.com/yudai/gojsondiff v1.0.0 // indirect
9292
github.com/yudai/golcs v0.0.0-20170316035057-ecda9a501e82 // indirect
9393
github.com/zeebo/xxh3 v1.0.2 // indirect
94-
go.bytebuilders.dev/license-proxyserver v0.0.19 // indirect
94+
go.bytebuilders.dev/license-proxyserver v0.0.20 // indirect
9595
go.uber.org/multierr v1.11.0 // indirect
9696
go.uber.org/zap v1.27.0 // indirect
9797
golang.org/x/crypto v0.31.0 // indirect

go.sum

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -210,12 +210,12 @@ github.com/zeebo/assert v1.3.0 h1:g7C04CbJuIDKNPFHmsk4hwZDO5O+kntRxzaUoNXj+IQ=
210210
github.com/zeebo/assert v1.3.0/go.mod h1:Pq9JiuJQpG8JLJdtkwrJESF0Foym2/D9XMU5ciN/wJ0=
211211
github.com/zeebo/xxh3 v1.0.2 h1:xZmwmqxHZA8AI603jOQ0tMqmBr9lPeFwGg6d+xy9DC0=
212212
github.com/zeebo/xxh3 v1.0.2/go.mod h1:5NWz9Sef7zIDm2JHfFlcQvNekmcEl9ekUZQQKCYaDcA=
213-
go.bytebuilders.dev/license-proxyserver v0.0.19 h1:mY7zPDN0JCw2a1UajOuQUQKQKjjm5KBx2CbkT/+a1N8=
214-
go.bytebuilders.dev/license-proxyserver v0.0.19/go.mod h1:B3Ig2Fo1qUollSV9GgfyFK8tXBI0RmUSpP1KFMZ2N7Q=
215-
go.bytebuilders.dev/license-verifier v0.14.4 h1:JwTGQFew4nudwv8Pk3BdfQRts8KfgUQ5xhu138w1wt4=
216-
go.bytebuilders.dev/license-verifier v0.14.4/go.mod h1:LqWXJKee5ofDcCYM6T5WilYlUc4NlKeZz58tHwO8GEs=
217-
go.bytebuilders.dev/license-verifier/kubernetes v0.14.4 h1:NeHq6SuVhRIVaMW2kSXdr8DcuUOg2jVL9rsODIQp9Fc=
218-
go.bytebuilders.dev/license-verifier/kubernetes v0.14.4/go.mod h1:1C7SaOJShC60mIXP1hXBaDWGpb0hrHQ4p4nUEvI6YQY=
213+
go.bytebuilders.dev/license-proxyserver v0.0.20 h1:gzRSwUmX/LSwPVE6T9oy5RLIutU1EeI7hmS+QGsYBY4=
214+
go.bytebuilders.dev/license-proxyserver v0.0.20/go.mod h1:2PJmjMCXncVyeP3fIVQ+hwZnuhmWSTmbcuEMBrFKIac=
215+
go.bytebuilders.dev/license-verifier v0.14.6 h1:0iHYGURUbx8toiXvFKftn/qMpeHzqHbAgEnEzOCNLvo=
216+
go.bytebuilders.dev/license-verifier v0.14.6/go.mod h1:LqWXJKee5ofDcCYM6T5WilYlUc4NlKeZz58tHwO8GEs=
217+
go.bytebuilders.dev/license-verifier/kubernetes v0.14.6 h1:NxmASX0A3lu+ABd4zuT5Ib+I63y3j5uJxmlUFEGxqWg=
218+
go.bytebuilders.dev/license-verifier/kubernetes v0.14.6/go.mod h1:N5QxsJF4EGLduOsTsW9gGfRuuMvN33T8pg5Y9NfKzuo=
219219
go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto=
220220
go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE=
221221
go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0=
@@ -336,8 +336,8 @@ kmodules.xyz/go-containerregistry v0.0.12 h1:Tl32QGmSqRVm9PUEb/f3dgDeu9zW5fVzt3q
336336
kmodules.xyz/go-containerregistry v0.0.12/go.mod h1:KgeNg0hDsgeda+qc0NzWk0iVRdF0+ZIg/oRzGoYh78I=
337337
kmodules.xyz/offshoot-api v0.30.1 h1:TrulAYO+oBsXe9sZZGTmNWIuI8qD2izMpgcTSPvgAmI=
338338
kmodules.xyz/offshoot-api v0.30.1/go.mod h1:T3mpjR6fui0QzOcmQvIuANytW48fe9ytmy/1cgx6D4g=
339-
kmodules.xyz/resource-metadata v0.24.1 h1:l4PmP+zbccPUQmSHKfEmmFLWMXtAHcgaRLWZ3v4WjYM=
340-
kmodules.xyz/resource-metadata v0.24.1/go.mod h1:TIHbYsuqmLl8yhYwV/4Uwwm4CO4GdqGHy1KAOgKhpYY=
339+
kmodules.xyz/resource-metadata v0.24.3-0.20250128191600-6318acdd841d h1:wkvfhG1H11RKz1uBC7Ub152ZJK8zyI2ZhcOTls2D8MI=
340+
kmodules.xyz/resource-metadata v0.24.3-0.20250128191600-6318acdd841d/go.mod h1:rPUZSMR0e1Vi+gONQ2ZhOFW+GvUeK+1AI7h9fzTZoKI=
341341
kmodules.xyz/resource-metrics v0.30.5 h1:ZhpGeR9DCz1HTrKUg/mWhr95wlFzCPRdgVAqwaggy1o=
342342
kmodules.xyz/resource-metrics v0.30.5/go.mod h1:w9+rz7/s/kGP1GWzYSuRdCn+l7EwpesmESSEHkLBnIQ=
343343
moul.io/http2curl/v2 v2.3.1-0.20221024080105-10c404f653f7 h1:NykkTlRB+X40z86cLHdEmuoTxhNKhQebLT379b1EumA=

lib/billing_event.go

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,8 +17,11 @@ limitations under the License.
1717
package lib
1818

1919
import (
20+
"context"
21+
2022
api "go.bytebuilders.dev/audit/api/v1"
2123

24+
core "k8s.io/api/core/v1"
2225
kmapi "kmodules.xyz/client-go/api/v1"
2326
"kmodules.xyz/client-go/discovery"
2427
corev1alpha1 "kmodules.xyz/resource-metadata/apis/core/v1alpha1"
@@ -28,6 +31,9 @@ import (
2831
type BillingEventCreator struct {
2932
Mapper discovery.ResourceMapper
3033
ClusterMetadata *kmapi.ClusterMetadata
34+
ClientBilling bool
35+
PodLister client.Reader
36+
PVCLister client.Reader
3137
}
3238

3339
func (p *BillingEventCreator) CreateEvent(obj client.Object) (*api.Event, error) {
@@ -41,8 +47,66 @@ func (p *BillingEventCreator) CreateEvent(obj client.Object) (*api.Event, error)
4147
return nil, err
4248
}
4349

50+
if p.ClientBilling {
51+
if r, ok := obj.(Resource); ok {
52+
var podList core.PodList
53+
err = p.PodLister.List(context.TODO(), &podList, client.InNamespace(obj.GetNamespace()), client.MatchingLabels(r.OffshootLabels()))
54+
if err != nil {
55+
return nil, err
56+
}
57+
podresources := make([]corev1alpha1.ComputeResource, 0, len(podList.Items))
58+
for _, pod := range podList.Items {
59+
pr := corev1alpha1.ComputeResource{
60+
UID: pod.UID,
61+
Name: pod.Name,
62+
CreationTimestamp: pod.CreationTimestamp,
63+
Containers: make([]corev1alpha1.ContainerResource, 0, len(pod.Spec.Containers)),
64+
InitContainers: make([]corev1alpha1.ContainerResource, 0, len(pod.Spec.Containers)),
65+
}
66+
for _, c := range pod.Spec.Containers {
67+
pr.Containers = append(pr.Containers, corev1alpha1.ContainerResource{
68+
Name: c.Name,
69+
Resource: c.Resources,
70+
RestartPolicy: c.RestartPolicy,
71+
})
72+
}
73+
for _, c := range pod.Spec.InitContainers {
74+
pr.InitContainers = append(pr.InitContainers, corev1alpha1.ContainerResource{
75+
Name: c.Name,
76+
Resource: c.Resources,
77+
RestartPolicy: c.RestartPolicy,
78+
})
79+
}
80+
podresources = append(podresources, pr)
81+
}
82+
res.Spec.Pods = podresources
83+
84+
var pvcList core.PersistentVolumeClaimList
85+
err = p.PVCLister.List(context.TODO(), &pvcList, client.InNamespace(obj.GetNamespace()), client.MatchingLabels(r.OffshootLabels()))
86+
if err != nil {
87+
return nil, err
88+
}
89+
pvcresources := make([]corev1alpha1.StorageResource, 0, len(podList.Items))
90+
for _, pvc := range pvcList.Items {
91+
if pvc.Status.Phase == core.ClaimBound {
92+
pvcresources = append(pvcresources, corev1alpha1.StorageResource{
93+
UID: pvc.UID,
94+
Name: pvc.Name,
95+
CreationTimestamp: pvc.CreationTimestamp,
96+
Resources: pvc.Spec.Resources,
97+
})
98+
}
99+
}
100+
res.Spec.Storage = pvcresources
101+
}
102+
}
103+
44104
return &api.Event{
45105
Resource: res,
46106
ResourceID: *rid,
47107
}, nil
48108
}
109+
110+
type Resource interface {
111+
OffshootLabels() map[string]string
112+
}

lib/lister.go

Lines changed: 136 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,136 @@
1+
/*
2+
Copyright AppsCode Inc. and Contributors
3+
4+
Licensed under the Apache License, Version 2.0 (the "License");
5+
you may not use this file except in compliance with the License.
6+
You may obtain a copy of the License at
7+
8+
http://www.apache.org/licenses/LICENSE-2.0
9+
10+
Unless required by applicable law or agreed to in writing, software
11+
distributed under the License is distributed on an "AS IS" BASIS,
12+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
See the License for the specific language governing permissions and
14+
limitations under the License.
15+
*/
16+
17+
package lib
18+
19+
import (
20+
"context"
21+
"reflect"
22+
23+
core "k8s.io/api/core/v1"
24+
"k8s.io/apimachinery/pkg/labels"
25+
corelisters "k8s.io/client-go/listers/core/v1"
26+
"sigs.k8s.io/controller-runtime/pkg/client"
27+
)
28+
29+
type PodReader struct {
30+
delegate corelisters.PodLister
31+
}
32+
33+
var _ client.Reader = &PodReader{}
34+
35+
func NewPodReader(delegate corelisters.PodLister) PodReader {
36+
return PodReader{delegate: delegate}
37+
}
38+
39+
func (r PodReader) Get(ctx context.Context, key client.ObjectKey, obj client.Object, _ ...client.GetOption) error {
40+
pod, err := r.delegate.Pods(key.Namespace).Get(key.Name)
41+
if err != nil {
42+
return err
43+
}
44+
assign(obj, pod)
45+
return nil
46+
}
47+
48+
func (r PodReader) List(ctx context.Context, list client.ObjectList, opts ...client.ListOption) error {
49+
var o client.ListOptions
50+
o.ApplyOptions(opts)
51+
52+
var ls labels.Selector
53+
if o.LabelSelector != nil {
54+
ls = o.LabelSelector
55+
} else if o.Raw != nil && o.Raw.LabelSelector != "" {
56+
var err error
57+
ls, err = labels.Parse(o.Raw.LabelSelector)
58+
if err != nil {
59+
return err
60+
}
61+
}
62+
63+
pods, err := r.delegate.Pods(o.Namespace).List(ls)
64+
if err != nil {
65+
return err
66+
}
67+
68+
podList := core.PodList{
69+
Items: make([]core.Pod, 0, len(pods)),
70+
}
71+
for _, pod := range pods {
72+
podList.Items = append(podList.Items, *pod)
73+
}
74+
assign(list, podList)
75+
76+
return nil
77+
}
78+
79+
type PersistentVolumeClaimReader struct {
80+
delegate corelisters.PersistentVolumeClaimLister
81+
}
82+
83+
var _ client.Reader = &PersistentVolumeClaimReader{}
84+
85+
func NewPersistentVolumeClaimReader(delegate corelisters.PersistentVolumeClaimLister) PersistentVolumeClaimReader {
86+
return PersistentVolumeClaimReader{delegate: delegate}
87+
}
88+
89+
func (r PersistentVolumeClaimReader) Get(ctx context.Context, key client.ObjectKey, obj client.Object, _ ...client.GetOption) error {
90+
pod, err := r.delegate.PersistentVolumeClaims(key.Namespace).Get(key.Name)
91+
if err != nil {
92+
return err
93+
}
94+
assign(obj, pod)
95+
96+
return nil
97+
}
98+
99+
func (r PersistentVolumeClaimReader) List(ctx context.Context, list client.ObjectList, opts ...client.ListOption) error {
100+
var o client.ListOptions
101+
o.ApplyOptions(opts)
102+
103+
var ls labels.Selector
104+
if o.LabelSelector != nil {
105+
ls = o.LabelSelector
106+
} else if o.Raw != nil && o.Raw.LabelSelector != "" {
107+
var err error
108+
ls, err = labels.Parse(o.Raw.LabelSelector)
109+
if err != nil {
110+
return err
111+
}
112+
}
113+
114+
pvcs, err := r.delegate.PersistentVolumeClaims(o.Namespace).List(ls)
115+
if err != nil {
116+
return err
117+
}
118+
119+
pvcList := core.PersistentVolumeClaimList{
120+
Items: make([]core.PersistentVolumeClaim, 0, len(pvcs)),
121+
}
122+
for _, pvc := range pvcs {
123+
pvcList.Items = append(pvcList.Items, *pvc)
124+
}
125+
assign(list, pvcList)
126+
127+
return nil
128+
}
129+
130+
func assign(target, src any) {
131+
srcValue := reflect.ValueOf(src)
132+
if srcValue.Kind() == reflect.Pointer {
133+
srcValue = srcValue.Elem()
134+
}
135+
reflect.ValueOf(target).Elem().Set(srcValue)
136+
}

vendor/go.bytebuilders.dev/license-verifier/apis/licenses/v1alpha1/helper.go

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,11 @@ limitations under the License.
1717
package v1alpha1
1818

1919
func (l License) DisableAnalytics() bool {
20-
return len(l.FeatureFlags) > 0 && l.FeatureFlags["DisableAnalytics"] == "true"
20+
return len(l.FeatureFlags) > 0 && l.FeatureFlags[FeatureDisableAnalytics] == "true"
21+
}
22+
23+
func (l License) EnableClientBilling() bool {
24+
return len(l.FeatureFlags) > 0 && l.FeatureFlags[FeatureEnableClientBilling] == "true"
2125
}
2226

2327
func (i *License) Less(j *License) bool {

vendor/go.bytebuilders.dev/license-verifier/apis/licenses/v1alpha1/types.go

Lines changed: 51 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,11 @@ limitations under the License.
1717
package v1alpha1
1818

1919
import (
20+
"fmt"
21+
2022
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
23+
"k8s.io/apimachinery/pkg/util/errors"
24+
"k8s.io/apimachinery/pkg/util/sets"
2125
)
2226

2327
// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object
@@ -26,20 +30,20 @@ import (
2630
type License struct {
2731
metav1.TypeMeta `json:",inline,omitempty"`
2832

29-
Data []byte `json:"-"`
30-
Issuer string `json:"issuer,omitempty"` // byte.builders
31-
ProductLine string `json:"productLine,omitempty"`
32-
TierName string `json:"tierName,omitempty"`
33-
PlanName string `json:"planName,omitempty"`
34-
Features []string `json:"features,omitempty"`
35-
FeatureFlags map[string]string `json:"featureFlags,omitempty"`
36-
Clusters []string `json:"clusters,omitempty"` // cluster_id ?
37-
User *User `json:"user,omitempty"`
38-
NotBefore *metav1.Time `json:"notBefore,omitempty"` // start of subscription start
39-
NotAfter *metav1.Time `json:"notAfter,omitempty"` // if set, use this
40-
ID string `json:"id,omitempty"` // license ID
41-
Status LicenseStatus `json:"status"`
42-
Reason string `json:"reason"`
33+
Data []byte `json:"-"`
34+
Issuer string `json:"issuer,omitempty"` // byte.builders
35+
ProductLine string `json:"productLine,omitempty"`
36+
TierName string `json:"tierName,omitempty"`
37+
PlanName string `json:"planName,omitempty"`
38+
Features []string `json:"features,omitempty"`
39+
FeatureFlags FeatureFlags `json:"featureFlags,omitempty"`
40+
Clusters []string `json:"clusters,omitempty"` // cluster_id ?
41+
User *User `json:"user,omitempty"`
42+
NotBefore *metav1.Time `json:"notBefore,omitempty"` // start of subscription start
43+
NotAfter *metav1.Time `json:"notAfter,omitempty"` // if set, use this
44+
ID string `json:"id,omitempty"` // license ID
45+
Status LicenseStatus `json:"status"`
46+
Reason string `json:"reason"`
4347
}
4448

4549
type User struct {
@@ -62,3 +66,36 @@ type Contract struct {
6266
StartTimestamp metav1.Time `json:"startTimestamp"`
6367
ExpiryTimestamp metav1.Time `json:"expiryTimestamp"`
6468
}
69+
70+
type FeatureFlag string
71+
72+
const (
73+
FeatureDisableAnalytics FeatureFlag = "DisableAnalytics"
74+
FeatureRestrictions FeatureFlag = "Restrictions"
75+
FeatureEnableClientBilling FeatureFlag = "EnableClientBilling"
76+
)
77+
78+
var knownFlags = sets.New[FeatureFlag](FeatureDisableAnalytics, FeatureRestrictions, FeatureEnableClientBilling)
79+
80+
type FeatureFlags map[FeatureFlag]string
81+
82+
func (f FeatureFlags) IsValid() error {
83+
var errs []error
84+
for k := range f {
85+
if !knownFlags.Has(k) {
86+
errs = append(errs, fmt.Errorf("unknown feature flag %q", k))
87+
}
88+
}
89+
return errors.NewAggregate(errs)
90+
}
91+
92+
func (f FeatureFlags) ToSlice() []string {
93+
if len(f) == 0 {
94+
return nil
95+
}
96+
result := make([]string, 0, len(f))
97+
for k, v := range f {
98+
result = append(result, fmt.Sprintf("%s=%s", k, v))
99+
}
100+
return result
101+
}

0 commit comments

Comments
 (0)