Skip to content

Commit e827081

Browse files
committed
OCM controller - periodically pull the data and update corresponding
secret
1 parent 360c4bc commit e827081

File tree

6 files changed

+221
-1
lines changed

6 files changed

+221
-1
lines changed

config/local.yaml

+3
Original file line numberDiff line numberDiff line change
@@ -6,5 +6,8 @@ interval: "5m"
66
storagePath: /tmp/insights-operator
77
endpoint: http://[::1]:8081
88
impersonate: system:serviceaccount:openshift-insights:gather
9+
ocm:
10+
endpoint: https://jsonplaceholder.typicode.com/albums?id=1
11+
interval: "4h"
912
gather:
1013
- ALL

manifests/03-clusterrole.yaml

+11-1
Original file line numberDiff line numberDiff line change
@@ -187,7 +187,17 @@ rules:
187187
verbs:
188188
- get
189189
- list
190-
- watch
190+
- watch
191+
- apiGroups:
192+
- ""
193+
resources:
194+
- secrets
195+
verbs:
196+
- create
197+
- update
198+
- get
199+
- list
200+
- watch
191201

192202
---
193203
apiVersion: rbac.authorization.k8s.io/v1

pkg/config/config.go

+18
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,10 @@ type Serialized struct {
2323
} `json:"pull_report"`
2424
Impersonate string `json:"impersonate"`
2525
Gather []string `json:"gather"`
26+
Ocm struct {
27+
Endpoint string `json:"endpoint"`
28+
Interval string `json:"interval"`
29+
}
2630
}
2731

2832
// Controller defines the standard config for this operator.
@@ -37,6 +41,8 @@ type Controller struct {
3741
ReportPullingTimeout time.Duration
3842
Impersonate string
3943
Gather []string
44+
OcmEndpoint string
45+
OcmInterval time.Duration
4046

4147
Username string
4248
Password string
@@ -119,6 +125,18 @@ func ToController(s *Serialized, cfg *Controller) (*Controller, error) {
119125
if len(cfg.StoragePath) == 0 {
120126
return nil, fmt.Errorf("storagePath must point to a directory where snapshots can be stored")
121127
}
128+
129+
if len(s.Ocm.Endpoint) > 0 {
130+
cfg.OcmEndpoint = s.Ocm.Endpoint
131+
}
132+
133+
if len(s.Ocm.Interval) > 0 {
134+
i, err := time.ParseDuration(s.Ocm.Interval)
135+
if err != nil {
136+
return nil, fmt.Errorf("ocm interval must be a valid duration: %v", err)
137+
}
138+
cfg.OcmInterval = i
139+
}
122140
return cfg, nil
123141
}
124142

pkg/config/configobserver/configobserver.go

+22
Original file line numberDiff line numberDiff line change
@@ -189,6 +189,21 @@ func (c *Controller) retrieveConfig(ctx context.Context) error {
189189
nextConfig.Report = false
190190
}
191191
}
192+
193+
// OCM config
194+
if ocmEndpoint, ok := secret.Data["ocmEndpoint"]; ok {
195+
nextConfig.OcmEndpoint = string(ocmEndpoint)
196+
}
197+
if ocmInterval, ok := secret.Data["ocmInterval"]; ok {
198+
if oi, err := time.ParseDuration(string(ocmInterval)); err == nil {
199+
nextConfig.OcmInterval = oi
200+
} else {
201+
klog.Warningf(
202+
"secret contains an invalid value (%s) for ocmInterval. Using previous value",
203+
ocmInterval,
204+
)
205+
}
206+
}
192207
}
193208
if err != nil {
194209
return err
@@ -263,6 +278,13 @@ func (c *Controller) mergeConfigLocked() {
263278
if c.secretConfig.ReportMinRetryTime > 0 {
264279
cfg.ReportMinRetryTime = c.secretConfig.ReportMinRetryTime
265280
}
281+
// OCM config
282+
if len(c.secretConfig.OcmEndpoint) > 0 {
283+
cfg.OcmEndpoint = c.secretConfig.OcmEndpoint
284+
}
285+
if c.secretConfig.OcmInterval > 0 {
286+
cfg.OcmInterval = c.secretConfig.OcmInterval
287+
}
266288
cfg.HTTPConfig = c.secretConfig.HTTPConfig
267289
}
268290
if c.tokenConfig != nil {

pkg/controller/operator.go

+6
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ import (
3131
"github.com/openshift/insights-operator/pkg/insights/insightsclient"
3232
"github.com/openshift/insights-operator/pkg/insights/insightsreport"
3333
"github.com/openshift/insights-operator/pkg/insights/insightsuploader"
34+
"github.com/openshift/insights-operator/pkg/ocm"
3435
"github.com/openshift/insights-operator/pkg/recorder"
3536
"github.com/openshift/insights-operator/pkg/recorder/diskrecorder"
3637
)
@@ -105,6 +106,11 @@ func (s *Operator) Run(ctx context.Context, controller *controllercmd.Controller
105106
// the last sync time, if any was set
106107
statusReporter := status.NewController(configClient, gatherKubeClient.CoreV1(), configObserver, os.Getenv("POD_NAMESPACE"))
107108

109+
// OMC controller periodically checks and pull data from the OCM API
110+
// the data is exposed in the OpenShift API
111+
ocmController := ocm.New(ctx, gatherKubeClient.CoreV1(), configObserver)
112+
go ocmController.Run()
113+
108114
// the recorder periodically flushes any recorded data to disk as tar.gz files
109115
// in s.StoragePath, and also prunes files above a certain age
110116
recdriver := diskrecorder.New(s.StoragePath)

pkg/ocm/ocm.go

+161
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,161 @@
1+
package ocm
2+
3+
import (
4+
"context"
5+
"io/ioutil"
6+
"net/http"
7+
"time"
8+
9+
"github.com/openshift/insights-operator/pkg/config"
10+
v1 "k8s.io/api/core/v1"
11+
"k8s.io/apimachinery/pkg/api/errors"
12+
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
13+
corev1client "k8s.io/client-go/kubernetes/typed/core/v1"
14+
"k8s.io/klog/v2"
15+
)
16+
17+
const (
18+
targetNamespaceName = "openshift-config-managed"
19+
secretName = "simple-content-access-cert"
20+
)
21+
22+
// Controller holds all the required resources to be able to communicate with OCM API
23+
type Controller struct {
24+
coreClient corev1client.CoreV1Interface
25+
context context.Context
26+
configurator Configurator
27+
}
28+
29+
// Configurator represents the interface to retrieve the configuration for the gatherer
30+
type Configurator interface {
31+
Config() *config.Controller
32+
ConfigChanged() (<-chan struct{}, func())
33+
}
34+
35+
// New creates new instance
36+
func New(ctx context.Context, coreClient corev1client.CoreV1Interface, configurator Configurator) *Controller {
37+
return &Controller{
38+
coreClient: coreClient,
39+
context: ctx,
40+
configurator: configurator,
41+
}
42+
}
43+
44+
// Run periodically queries the OCM API and update corresponding secret accordingly
45+
func (c *Controller) Run() {
46+
cfg := c.configurator.Config()
47+
endpoint := cfg.OcmEndpoint
48+
interval := cfg.OcmInterval
49+
configCh, cancel := c.configurator.ConfigChanged()
50+
defer cancel()
51+
52+
c.requestDataAndCheckSecret(endpoint)
53+
for {
54+
select {
55+
case <-time.After(interval):
56+
c.requestDataAndCheckSecret(endpoint)
57+
case <-configCh:
58+
cfg := c.configurator.Config()
59+
interval = cfg.OcmInterval
60+
endpoint = cfg.OcmEndpoint
61+
}
62+
}
63+
}
64+
65+
func (c *Controller) requestDataAndCheckSecret(endpoint string) {
66+
data, err := requestData(endpoint)
67+
if err != nil {
68+
klog.Errorf("errror requesting data from %s: %v", endpoint, err)
69+
}
70+
// check & update the secret here
71+
ok, err := c.checkSecret(data)
72+
if !ok {
73+
// TODO - change IO status in case of failure ?
74+
klog.Errorf("error when checking the %s secret: %v", secretName, err)
75+
return
76+
}
77+
klog.Infof("%s secret successfuly updated", secretName)
78+
79+
}
80+
81+
// checkSecret checks "simple-content-access" secret in the "openshift-config-managed" namespace.
82+
// If the secret doesn't exist then it will create a new one.
83+
// If the secret already exist then it will update the data.
84+
func (c *Controller) checkSecret(data []byte) (bool, error) {
85+
scaSec, err := c.coreClient.Secrets(targetNamespaceName).Get(c.context, secretName, metav1.GetOptions{})
86+
87+
//if the secret doesn't exist then create one
88+
if errors.IsNotFound(err) {
89+
_, err = c.createSecret(data)
90+
if err != nil {
91+
return false, err
92+
}
93+
return true, nil
94+
}
95+
if err != nil {
96+
return false, err
97+
}
98+
99+
_, err = c.updateSecret(scaSec, data)
100+
if err != nil {
101+
return false, err
102+
}
103+
return true, nil
104+
}
105+
106+
func (o *Controller) createSecret(data []byte) (*v1.Secret, error) {
107+
newSCA := &v1.Secret{
108+
ObjectMeta: metav1.ObjectMeta{
109+
Name: secretName,
110+
Namespace: targetNamespaceName,
111+
},
112+
// TODO set the data correctly
113+
Data: map[string][]byte{
114+
v1.TLSCertKey: data,
115+
v1.TLSPrivateKeyKey: data,
116+
},
117+
Type: v1.SecretTypeTLS,
118+
}
119+
cm, err := o.coreClient.Secrets(targetNamespaceName).Create(o.context, newSCA, metav1.CreateOptions{})
120+
if err != nil {
121+
return nil, err
122+
}
123+
return cm, nil
124+
}
125+
126+
// updateSecret updates provided secret with given data
127+
func (o *Controller) updateSecret(s *v1.Secret, data []byte) (*v1.Secret, error) {
128+
129+
// TODO set the data correctly
130+
s.Data = map[string][]byte{
131+
v1.TLSCertKey: data,
132+
v1.TLSPrivateKeyKey: data,
133+
}
134+
s, err := o.coreClient.Secrets(s.Namespace).Update(o.context, s, metav1.UpdateOptions{})
135+
if err != nil {
136+
return nil, err
137+
}
138+
return s, nil
139+
}
140+
141+
// TODO
142+
// - no need to create new HTTP client every time
143+
// - add authorization
144+
// - add response status check
145+
// - move this to insightsclient?
146+
func requestData(endpoint string) ([]byte, error) {
147+
req, err := http.NewRequest(http.MethodGet, endpoint, nil)
148+
if err != nil {
149+
return nil, err
150+
}
151+
client := &http.Client{}
152+
res, err := client.Do(req)
153+
if err != nil {
154+
return nil, err
155+
}
156+
d, err := ioutil.ReadAll(res.Body)
157+
if err != nil {
158+
return nil, err
159+
}
160+
return d, nil
161+
}

0 commit comments

Comments
 (0)