-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathlivestate.go
More file actions
317 lines (279 loc) · 11.6 KB
/
livestate.go
File metadata and controls
317 lines (279 loc) · 11.6 KB
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
315
316
317
// Copyright 2025 The PipeCD Authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package sdk
import (
"context"
"time"
"go.uber.org/zap"
"google.golang.org/grpc"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/status"
"github.com/pipe-cd/pipecd/pkg/model"
"github.com/pipe-cd/pipecd/pkg/plugin/api/v1alpha1/livestate"
)
// LivestatePlugin is the interface that must be implemented by a Livestate plugin.
// In addition to the Plugin interface, it provides a method to get the live state of the resources.
// The Config and DeployTargetConfig are the plugin's config defined in piped's config.
type LivestatePlugin[Config, DeployTargetConfig, ApplicationConfigSpec any] interface {
// GetLivestate returns the live state of the resources in the given application.
// It returns the resources' live state and the difference between the desired state and the live state.
// It's allowed to return only the resources' live state if the difference is not available, or only the difference if the live state is not available.
GetLivestate(context.Context, *Config, []*DeployTarget[DeployTargetConfig], *GetLivestateInput[ApplicationConfigSpec]) (*GetLivestateResponse, error)
}
// LivestatePluginServer is a wrapper for LivestatePlugin to satisfy the LivestateServiceServer interface.
// It is used to register the plugin to the gRPC server.
type LivestatePluginServer[Config, DeployTargetConfig, ApplicationConfigSpec any] struct {
livestate.UnimplementedLivestateServiceServer
commonFields[Config, DeployTargetConfig]
base LivestatePlugin[Config, DeployTargetConfig, ApplicationConfigSpec]
}
// Register registers the plugin to the gRPC server.
func (s *LivestatePluginServer[Config, DeployTargetConfig, ApplicationConfigSpec]) Register(server *grpc.Server) {
livestate.RegisterLivestateServiceServer(server, s)
}
// GetLivestate returns the live state of the resources in the given application.
func (s *LivestatePluginServer[Config, DeployTargetConfig, ApplicationConfigSpec]) GetLivestate(ctx context.Context, request *livestate.GetLivestateRequest) (*livestate.GetLivestateResponse, error) {
// Get the deploy targets set on the deployment from the piped plugin config.
deployTargets := make([]*DeployTarget[DeployTargetConfig], 0, len(request.GetDeployTargets()))
for _, name := range request.GetDeployTargets() {
dt, ok := s.deployTargets[name]
if !ok {
return nil, status.Errorf(codes.Internal, "the deploy target %s is not found in the piped plugin config", name)
}
deployTargets = append(deployTargets, dt)
}
client := &Client{
base: s.client,
pluginName: s.name,
applicationID: request.GetApplicationId(),
toolRegistry: s.toolRegistry,
}
deploymentSource, err := newDeploymentSource[ApplicationConfigSpec](s.name, request.GetDeploySource())
if err != nil {
return nil, status.Errorf(codes.Internal, "failed to parse deployment source: %v", err)
}
response, err := s.base.GetLivestate(ctx, s.pluginConfig, deployTargets, &GetLivestateInput[ApplicationConfigSpec]{
Request: GetLivestateRequest[ApplicationConfigSpec]{
PipedID: request.GetPipedId(),
ApplicationID: request.GetApplicationId(),
ApplicationName: request.GetApplicationName(),
DeploymentSource: deploymentSource,
},
Client: client,
Logger: s.logger,
})
if err != nil {
return nil, status.Errorf(codes.Internal, "failed to get the live state: %v", err)
}
return response.toModel(s.config.Name, time.Now()), nil
}
// GetLivestateInput is the input for the GetLivestate method.
type GetLivestateInput[ApplicationConfigSpec any] struct {
// Request is the request for getting the live state.
Request GetLivestateRequest[ApplicationConfigSpec]
// Client is the client for accessing the piped API.
Client *Client
// Logger is the logger for logging.
Logger *zap.Logger
}
// GetLivestateRequest is the request for the GetLivestate method.
type GetLivestateRequest[ApplicationConfigSpec any] struct {
// PipedID is the ID of the piped.
PipedID string
// ApplicationID is the ID of the application.
ApplicationID string
// ApplicationName is the name of the application.
ApplicationName string
// DeploymentSource is the source of the deployment.
DeploymentSource DeploymentSource[ApplicationConfigSpec]
}
// GetLivestateResponse is the response for the GetLivestate method.
type GetLivestateResponse struct {
// LiveState is the live state of the application.
LiveState ApplicationLiveState
// SyncState is the sync state of the application.
SyncState ApplicationSyncState
}
// toModel converts the GetLivestateResponse to the model.GetLivestateResponse.
func (r *GetLivestateResponse) toModel(pluginName string, now time.Time) *livestate.GetLivestateResponse {
return &livestate.GetLivestateResponse{
ApplicationLiveState: r.LiveState.toModel(pluginName, now),
SyncState: r.SyncState.toModel(now),
}
}
// ApplicationLiveState represents the live state of an application.
type ApplicationLiveState struct {
Resources []ResourceState
}
// healthStatus returns the health status of the application.
// It returns ApplicationHealthStateUnknown in the following priority:
// 1. If there is any unknown health status resource, it returns ApplicationHealthStateUnknown.
// 2. If there is any unhealthy resource, it returns ApplicationHealthStateOther.
// 3. Otherwise, it returns ApplicationHealthStateHealthy.
func (s *ApplicationLiveState) healthStatus() ApplicationHealthStatus {
var (
unhealthy bool
unknown bool
)
for _, rs := range s.Resources {
switch rs.HealthStatus {
case ResourceHealthStateUnhealthy:
unhealthy = true
case ResourceHealthStateUnknown:
unknown = true
}
}
if unknown {
return ApplicationHealthStateUnknown
}
if unhealthy {
return ApplicationHealthStateOther
}
return ApplicationHealthStateHealthy
}
// toModel converts the ApplicationLiveState to the model.ApplicationLiveState.
func (s *ApplicationLiveState) toModel(pluginName string, now time.Time) *model.ApplicationLiveState {
resources := make([]*model.ResourceState, 0, len(s.Resources))
for _, rs := range s.Resources {
resources = append(resources, rs.toModel(pluginName, now))
}
return &model.ApplicationLiveState{
Resources: resources,
HealthStatus: s.healthStatus().toModel(),
}
}
// ResourceState represents the live state of a resource.
type ResourceState struct {
// ID is the unique identifier of the resource.
ID string
// ParentIDs is the list of the parent resource's IDs.
ParentIDs []string
// Name is the name of the resource.
Name string
// ResourceType is the type of the resource.
ResourceType string
// ResourceMetadata is the metadata of the resource.
ResourceMetadata map[string]string
// HealthStatus is the health status of the resource.
HealthStatus ResourceHealthStatus
// HealthDescription is the description of the health status.
HealthDescription string
// DeployTarget is the target where the resource is deployed.
DeployTarget string
// CreatedAt is the time when the resource was created.
CreatedAt time.Time
}
// toModel converts the ResourceState to the model.ResourceState.
func (s *ResourceState) toModel(pluginName string, now time.Time) *model.ResourceState {
return &model.ResourceState{
Id: s.ID,
ParentIds: s.ParentIDs,
Name: s.Name,
ResourceType: s.ResourceType,
ResourceMetadata: s.ResourceMetadata,
HealthStatus: s.HealthStatus.toModel(),
HealthDescription: s.HealthDescription,
DeployTarget: s.DeployTarget,
PluginName: pluginName,
CreatedAt: s.CreatedAt.Unix(),
UpdatedAt: now.Unix(),
}
}
// ApplicationHealthStatus represents the health status of an application.
type ApplicationHealthStatus int
const (
// ApplicationHealthStateUnknown represents the unknown health status of an application.
ApplicationHealthStateUnknown ApplicationHealthStatus = iota
// ApplicationHealthStateHealthy represents the healthy health status of an application.
ApplicationHealthStateHealthy
// ApplicationHealthStateOther represents the other health status of an application.
ApplicationHealthStateOther
)
// toModel converts the ApplicationHealthStatus to the model.ApplicationLiveState_Status.
func (s ApplicationHealthStatus) toModel() model.ApplicationLiveState_Status {
switch s {
case ApplicationHealthStateHealthy:
return model.ApplicationLiveState_HEALTHY
case ApplicationHealthStateOther:
return model.ApplicationLiveState_OTHER
default:
return model.ApplicationLiveState_UNKNOWN
}
}
// ResourceHealthStatus represents the health status of a resource.
type ResourceHealthStatus int
const (
// ResourceHealthStateUnknown represents the unknown health status of a resource.
ResourceHealthStateUnknown ResourceHealthStatus = iota
// ResourceHealthStateHealthy represents the healthy health status of a resource.
ResourceHealthStateHealthy
// ResourceHealthStateUnhealthy represents the unhealthy health status of a resource.
ResourceHealthStateUnhealthy
)
// toModel converts the ResourceHealthStatus to the model.ResourceState_HealthStatus.
func (s ResourceHealthStatus) toModel() model.ResourceState_HealthStatus {
switch s {
case ResourceHealthStateHealthy:
return model.ResourceState_HEALTHY
case ResourceHealthStateUnhealthy:
return model.ResourceState_UNHEALTHY
default:
return model.ResourceState_UNKNOWN
}
}
// ApplicationSyncState represents the sync state of an application.
type ApplicationSyncState struct {
// Status is the sync status of the application.
Status ApplicationSyncStatus
// ShortReason is the short reason of the sync status.
// for example, "The service manifest doesn't be synced"
ShortReason string
// Reason is the reason of the sync status.
// actually, it's the difference between the desired state and the live state.
Reason string
}
// toModel converts the ApplicationSyncState to the model.ApplicationSyncState.
func (s *ApplicationSyncState) toModel(now time.Time) *model.ApplicationSyncState {
return &model.ApplicationSyncState{
Status: s.Status.toModel(),
ShortReason: s.ShortReason,
Reason: s.Reason,
Timestamp: now.Unix(),
}
}
// ApplicationSyncStatus represents the sync status of an application.
type ApplicationSyncStatus int
const (
// ApplicationSyncStateUnknown represents the unknown sync status of an application.
ApplicationSyncStateUnknown ApplicationSyncStatus = iota
// ApplicationSyncStateSynced represents the synced sync status of an application.
ApplicationSyncStateSynced
// ApplicationSyncStateOutOfSync represents the out-of-sync sync status of an application.
ApplicationSyncStateOutOfSync
// ApplicationSyncStateInvalidConfig represents the invalid-config sync status of an application.
ApplicationSyncStateInvalidConfig
)
// toModel converts the ApplicationSyncStatus to the model.ApplicationSyncStatus.
func (s ApplicationSyncStatus) toModel() model.ApplicationSyncStatus {
switch s {
case ApplicationSyncStateSynced:
return model.ApplicationSyncStatus_SYNCED
case ApplicationSyncStateOutOfSync:
return model.ApplicationSyncStatus_OUT_OF_SYNC
case ApplicationSyncStateInvalidConfig:
return model.ApplicationSyncStatus_INVALID_CONFIG
default:
return model.ApplicationSyncStatus_UNKNOWN
}
}