-
Notifications
You must be signed in to change notification settings - Fork 0
/
organizations.go
314 lines (255 loc) · 8.54 KB
/
organizations.go
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
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
// Copyright 2023 Adam Chalkley
//
// https://github.com/atc0005/check-rsat
//
// Licensed under the MIT License. See LICENSE file in the project root for
// full license information.
package rsat
import (
"context"
"fmt"
"sort"
"time"
"github.com/atc0005/go-nagios"
)
// OrganizationsResponse represents the API response from a request for all
// organizations in the Red Hat Satellite server.
type OrganizationsResponse struct {
Organizations []Organization `json:"results"`
Search NullString `json:"search"`
Sort SortOptions `json:"sort"`
Subtotal int `json:"subtotal"`
Total int `json:"total"`
Page int `json:"page"`
PerPage int `json:"per_page"`
}
// Organization is an isolated collection of systems, content, and other
// functionality within a Red Hat Satellite deployment.
type Organization struct {
CreatedAt StandardAPITime `json:"created_at"`
UpdatedAt StandardAPITime `json:"updated_at"`
Description NullString `json:"description"`
Label string `json:"label"`
Name string `json:"name"`
Title string `json:"title"`
SyncPlans SyncPlans `json:"-"`
// Products Products `json:"-"`
// Hosts Hosts `json:"-"`
ID int `json:"id"`
}
// Organizations is a collection of Red Hat Satellite organizations.
type Organizations []Organization
// GetOrganizations uses the given client to retrieve all Red Hat Satellite
// organizations.
func GetOrganizations(ctx context.Context, client *APIClient) ([]Organization, error) {
funcTimeStart := time.Now()
if client == nil {
return nil, fmt.Errorf(
"required API client was not provided: %w",
ErrMissingValue,
)
}
logger := client.Logger
apiURL := fmt.Sprintf(
OrganizationsAPIEndPointURLTemplate,
client.AuthInfo.Server,
client.AuthInfo.Port,
)
request, err := prepareRequest(ctx, client, apiURL)
if err != nil {
return nil, err
}
logger.Debug().Msg("Submitting HTTP request")
response, respErr := client.Do(request)
if respErr != nil {
return nil, respErr
}
logger.Debug().Msg("Successfully submitted HTTP request")
// Make sure that we close the response body once we're done with it
defer func() {
if closeErr := response.Body.Close(); closeErr != nil {
logger.Error().Err(closeErr).Msgf("error closing response body")
}
}()
// Evaluate the response
validateErr := validateResponse(ctx, response, logger, client.AuthInfo.ReadLimit)
if validateErr != nil {
return nil, err
}
logger.Debug().Msgf(
"Decoding JSON data from %q using a limit of %d bytes",
apiURL,
client.AuthInfo.ReadLimit,
)
var orgsQueryResp OrganizationsResponse
decodeErr := decode(&orgsQueryResp, response.Body, logger, apiURL, client.AuthInfo.ReadLimit)
if decodeErr != nil {
return nil, decodeErr
}
logger.Debug().
Str("api_endpoint", apiURL).
Msg("Successfully decoded JSON data")
logger.Debug().
Str("runtime_total", time.Since(funcTimeStart).String()).
Msg("Completed retrieval of all organizations")
return orgsQueryResp.Organizations, nil
}
// Sort sorts the Organizations in the collection by name.
func (orgs Organizations) Sort() {
sort.SliceStable(orgs, func(i int, j int) bool {
return orgs[i].Name < orgs[j].Name
})
}
// GetOrgsWithSyncPlans uses the provided API client to retrieve all Red Hat
// Satellite organizations along with their sync plans.
func GetOrgsWithSyncPlans(ctx context.Context, client *APIClient) (Organizations, error) {
funcTimeStart := time.Now()
if client == nil {
return nil, fmt.Errorf(
"required API client was not provided: %w",
ErrMissingValue,
)
}
logger := client.Logger
logger.Debug().Msg("Retrieving organizations")
orgs, orgsErr := GetOrganizations(ctx, client)
if orgsErr != nil {
logger.Error().Err(orgsErr).Msg("Failed to retrieve organizations")
return nil, fmt.Errorf(
"failed to retrieve organizations: %w",
orgsErr,
)
}
logger.Debug().Msg("Successfully retrieved organizations")
reqsCounter := newRequestsCounter(len(orgs))
// Update all organizations with retrieved sync plans.
for i := range orgs {
subLogger := logger.With().
Int("org_id", orgs[i].ID).
Str("org_name", orgs[i].Name).
Stack().Logger()
retrievalStart := time.Now()
subLogger.Debug().Msg("Retrieving sync plans for organization")
syncPlans, syncPlansErr := GetSyncPlans(ctx, client, orgs[i])
if syncPlansErr != nil {
subLogger.Error().Err(syncPlansErr).Msg("Failed to retrieve sync plans")
return nil, fmt.Errorf(
"failed to retrieve sync plans for organization"+
" (name: %s, id: %d) %w",
orgs[i].Name,
orgs[i].ID,
syncPlansErr,
)
}
requestNum, requestsRemaining := reqsCounter()
subLogger.Debug().
Int("retrieved_plans", len(syncPlans)).
Int("request", requestNum).
Int("requests_remaining", requestsRemaining).
Str("runtime_request", time.Since(retrievalStart).String()).
Str("runtime_elapsed", time.Since(funcTimeStart).String()).
Msg("Finished sync plans retrieval for this organization")
orgs[i].SyncPlans = syncPlans
}
logger.Debug().Msg("Successfully retrieved sync plans for all organizations")
return orgs, nil
}
// NumOrgs returns the number of organizations in the collection.
func (orgs Organizations) NumOrgs() int {
return len(orgs)
}
// NumPlans returns the number of sync plans for all organizations in the
// collection.
func (orgs Organizations) NumPlans() int {
var num int
for _, org := range orgs {
num += len(org.SyncPlans)
}
return num
}
// NumPlansEnabled returns the total number of sync plans for all
// organizations in the collection with enabled state.
func (orgs Organizations) NumPlansEnabled() int {
var num int
for _, org := range orgs {
for _, syncPlan := range org.SyncPlans {
if syncPlan.Enabled {
num++
}
}
}
return num
}
// NumPlansStuck returns the total number of sync plans for all organizations
// in the collection with Next Sync state set to past date/time.
func (orgs Organizations) NumPlansStuck() int {
var num int
for _, org := range orgs {
num += org.SyncPlans.NumStuck()
}
return num
}
// NumPlansDisabled returns the total number of sync plans for all
// organizations in the collection with disabled state.
func (orgs Organizations) NumPlansDisabled() int {
var num int
for _, org := range orgs {
num += org.SyncPlans.NumDisabled()
}
return num
}
// NumProblemPlans returns the total number of sync plans for all
// organizations in the collection with a non-OK state.
func (orgs Organizations) NumProblemPlans() int {
// NOTE: While stuck plans are the current focus we may wish to expand the
// list of problem "symptoms" to include other attributes in the future.
// This method provides a more generic "are there any problems" status
// check to cover that possibility.
return orgs.NumPlansStuck()
}
// IsOKState indicates whether all items in the collection were evaluated to
// an OK state.
func (orgs Organizations) IsOKState() bool {
// return orgs.NumProblemPlans() == 0
// The scope is a higher level than just whether there are problematic
// sync plans (e.g., the Org might have problematic subscriptions that we
// can alert on in the future).
return !orgs.HasWarningState() && !orgs.HasCriticalState()
}
// HasCriticalState indicates whether any items in the collection were
// evaluated to a CRITICAL state.
func (orgs Organizations) HasCriticalState() bool {
// TODO: Add support for performing threshold check to determine how many
// days in the past a sync plan has been stuck. If greater than given
// threshold indicate CRITICAL state.
return false
}
// HasWarningState indicates whether any items in the collection were
// evaluated to a WARNING state.
func (orgs Organizations) HasWarningState() bool {
return !orgs.HasCriticalState() && orgs.NumProblemPlans() > 0
}
// ServiceState returns the appropriate Service Check Status label and exit
// code for the collection's evaluation results.
func (orgs Organizations) ServiceState() nagios.ServiceState {
var stateLabel string
var stateExitCode int
switch {
case orgs.HasCriticalState():
stateLabel = nagios.StateCRITICALLabel
stateExitCode = nagios.StateCRITICALExitCode
case orgs.HasWarningState():
stateLabel = nagios.StateWARNINGLabel
stateExitCode = nagios.StateWARNINGExitCode
case orgs.IsOKState():
stateLabel = nagios.StateOKLabel
stateExitCode = nagios.StateOKExitCode
default:
stateLabel = nagios.StateUNKNOWNLabel
stateExitCode = nagios.StateUNKNOWNExitCode
}
return nagios.ServiceState{
Label: stateLabel,
ExitCode: stateExitCode,
}
}