Skip to content

Commit 6cfeb58

Browse files
Warashiffjlabo
andauthored
Implement Livestate SDK (#5626)
* Refactor the LivestatePluginServer to set the common fields and configs in a single method Signed-off-by: Shinnosuke Sawada-Dazai <[email protected]> * Implement GetLivestate method in the LivestatePlugin interface Signed-off-by: Shinnosuke Sawada-Dazai <[email protected]> * Implement ApplicationLiveState DTOs Signed-off-by: Shinnosuke Sawada-Dazai <[email protected]> * Implement ApplicationSyncState DTOs Signed-off-by: Shinnosuke Sawada-Dazai <[email protected]> * Add documentation for GetLivestateInput, GetLivestateResponse, and toModel Signed-off-by: Shinnosuke Sawada-Dazai <[email protected]> * Add unit tests for GetLivestate method in LivestatePluginServer Signed-off-by: Shinnosuke Sawada-Dazai <[email protected]> * Add unit tests for ApplicationLiveState and ApplicationSyncState model conversions Signed-off-by: Shinnosuke Sawada-Dazai <[email protected]> * Add license header to livestate_test.go Signed-off-by: Shinnosuke Sawada-Dazai <[email protected]> * Refactor import order in livestate_test.go for consistency Signed-off-by: Shinnosuke Sawada-Dazai <[email protected]> * Fix formatting inconsistencies in resource state conversion tests Signed-off-by: Shinnosuke Sawada-Dazai <[email protected]> * Enable parallel execution for unit tests in livestate_test.go Signed-off-by: Shinnosuke Sawada-Dazai <[email protected]> * Update pkg/plugin/sdk/livestate.go Co-authored-by: Yoshiki Fujikane <[email protected]> Signed-off-by: Shinnosuke Sawada-Dazai <[email protected]> --------- Signed-off-by: Shinnosuke Sawada-Dazai <[email protected]> Co-authored-by: Yoshiki Fujikane <[email protected]>
1 parent d7af69c commit 6cfeb58

File tree

4 files changed

+637
-29
lines changed

4 files changed

+637
-29
lines changed

pkg/plugin/sdk/deployment.go

+6
Original file line numberDiff line numberDiff line change
@@ -118,6 +118,12 @@ type commonFields struct {
118118
toolRegistry *toolregistry.ToolRegistry
119119
}
120120

121+
// withLogger copies the commonFields and sets the logger to the given one.
122+
func (c commonFields) withLogger(logger *zap.Logger) commonFields {
123+
c.logger = logger
124+
return c
125+
}
126+
121127
// DeploymentPluginServiceServer is the gRPC server that handles requests from the piped.
122128
type DeploymentPluginServiceServer[Config, DeployTargetConfig any] struct {
123129
deployment.UnimplementedDeploymentServiceServer

pkg/plugin/sdk/livestate.go

+254-18
Original file line numberDiff line numberDiff line change
@@ -17,12 +17,14 @@ package sdk
1717
import (
1818
"context"
1919
"encoding/json"
20-
"fmt"
20+
"time"
2121

22+
"go.uber.org/zap"
2223
"google.golang.org/grpc"
2324
"google.golang.org/grpc/codes"
2425
"google.golang.org/grpc/status"
2526

27+
"github.com/pipe-cd/pipecd/pkg/model"
2628
"github.com/pipe-cd/pipecd/pkg/plugin/api/v1alpha1/livestate"
2729
)
2830

@@ -31,8 +33,7 @@ var (
3133
Plugin
3234

3335
Register(server *grpc.Server)
34-
setCommonFields(commonFields)
35-
setConfig([]byte) error
36+
setFields(commonFields) error
3637
livestate.LivestateServiceServer
3738
}
3839
)
@@ -46,7 +47,7 @@ type LivestatePlugin[Config, DeployTargetConfig any] interface {
4647
// GetLivestate returns the live state of the resources in the given application.
4748
// It returns the resources' live state and the difference between the desired state and the live state.
4849
// 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.
49-
GetLivestate(context.Context, *Config, []*DeployTarget[DeployTargetConfig], TODO) (TODO, error)
50+
GetLivestate(context.Context, *Config, []*DeployTarget[DeployTargetConfig], *GetLivestateInput) (*GetLivestateResponse, error)
5051
}
5152

5253
// LivestatePluginServer is a wrapper for LivestatePlugin to satisfy the LivestateServiceServer interface.
@@ -55,8 +56,9 @@ type LivestatePluginServer[Config, DeployTargetConfig any] struct {
5556
livestate.UnimplementedLivestateServiceServer
5657
commonFields
5758

58-
base LivestatePlugin[Config, DeployTargetConfig]
59-
config Config
59+
base LivestatePlugin[Config, DeployTargetConfig]
60+
config Config
61+
deployTargets map[string]*DeployTarget[DeployTargetConfig]
6062
}
6163

6264
// RegisterLivestatePlugin registers the given LivestatePlugin to the sdk.
@@ -79,23 +81,257 @@ func (s *LivestatePluginServer[Config, DeployTargetConfig]) Register(server *grp
7981
livestate.RegisterLivestateServiceServer(server, s)
8082
}
8183

82-
// setCommonFields sets the common fields to the plugin server.
83-
func (s *LivestatePluginServer[Config, DeployTargetConfig]) setCommonFields(c commonFields) {
84-
s.commonFields = c
85-
}
84+
// setFields sets the common fields and configs to the server.
85+
func (s *LivestatePluginServer[Config, DeployTargetConfig]) setFields(fields commonFields) error {
86+
s.commonFields = fields
8687

87-
// setConfig sets the configuration to the plugin server.
88-
func (s *LivestatePluginServer[Config, DeployTargetConfig]) setConfig(bytes []byte) error {
89-
if bytes == nil {
90-
return nil
88+
cfg := fields.config
89+
if cfg.Config != nil {
90+
if err := json.Unmarshal(cfg.Config, &s.config); err != nil {
91+
s.logger.Fatal("failed to unmarshal the plugin config", zap.Error(err))
92+
return err
93+
}
9194
}
92-
if err := json.Unmarshal(bytes, &s.config); err != nil {
93-
return fmt.Errorf("failed to unmarshal config: %w", err)
95+
96+
s.deployTargets = make(map[string]*DeployTarget[DeployTargetConfig], len(cfg.DeployTargets))
97+
for _, dt := range cfg.DeployTargets {
98+
var sdkDt DeployTargetConfig
99+
if err := json.Unmarshal(dt.Config, &sdkDt); err != nil {
100+
s.logger.Fatal("failed to unmarshal deploy target config", zap.Error(err))
101+
return err
102+
}
103+
s.deployTargets[dt.Name] = &DeployTarget[DeployTargetConfig]{
104+
Name: dt.Name,
105+
Labels: dt.Labels,
106+
Config: sdkDt,
107+
}
94108
}
109+
95110
return nil
96111
}
97112

98113
// GetLivestate returns the live state of the resources in the given application.
99-
func (s *LivestatePluginServer[Config, DeployTargetConfig]) GetLivestate(context.Context, *livestate.GetLivestateRequest) (*livestate.GetLivestateResponse, error) {
100-
return nil, status.Errorf(codes.Unimplemented, "method GetLivestate not implemented")
114+
func (s *LivestatePluginServer[Config, DeployTargetConfig]) GetLivestate(ctx context.Context, request *livestate.GetLivestateRequest) (*livestate.GetLivestateResponse, error) {
115+
// Get the deploy targets set on the deployment from the piped plugin config.
116+
deployTargets := make([]*DeployTarget[DeployTargetConfig], 0, len(request.GetDeployTargets()))
117+
for _, name := range request.GetDeployTargets() {
118+
dt, ok := s.deployTargets[name]
119+
if !ok {
120+
return nil, status.Errorf(codes.Internal, "the deploy target %s is not found in the piped plugin config", name)
121+
}
122+
123+
deployTargets = append(deployTargets, dt)
124+
}
125+
126+
client := &Client{
127+
base: s.client,
128+
pluginName: s.Name(),
129+
applicationID: request.GetApplicationId(),
130+
toolRegistry: s.toolRegistry,
131+
}
132+
133+
response, err := s.base.GetLivestate(ctx, &s.config, deployTargets, &GetLivestateInput{
134+
Request: GetLivestateRequest{
135+
ApplicationID: request.ApplicationId,
136+
DeploymentSource: newDeploymentSource(request.GetDeploySource()),
137+
},
138+
Client: client,
139+
Logger: s.logger,
140+
})
141+
if err != nil {
142+
return nil, status.Errorf(codes.Internal, "failed to get the live state: %v", err)
143+
}
144+
145+
return response.toModel(time.Now()), nil
146+
}
147+
148+
// GetLivestateInput is the input for the GetLivestate method.
149+
type GetLivestateInput struct {
150+
// Request is the request for getting the live state.
151+
Request GetLivestateRequest
152+
// Client is the client for accessing the piped API.
153+
Client *Client
154+
// Logger is the logger for logging.
155+
Logger *zap.Logger
156+
}
157+
158+
// GetLivestateRequest is the request for the GetLivestate method.
159+
type GetLivestateRequest struct {
160+
// ApplicationID is the ID of the application.
161+
ApplicationID string
162+
// DeploymentSource is the source of the deployment.
163+
DeploymentSource DeploymentSource
164+
}
165+
166+
// GetLivestateResponse is the response for the GetLivestate method.
167+
type GetLivestateResponse struct {
168+
// LiveState is the live state of the application.
169+
LiveState ApplicationLiveState
170+
// SyncState is the sync state of the application.
171+
SyncState ApplicationSyncState
172+
}
173+
174+
// toModel converts the GetLivestateResponse to the model.GetLivestateResponse.
175+
func (r *GetLivestateResponse) toModel(now time.Time) *livestate.GetLivestateResponse {
176+
return &livestate.GetLivestateResponse{
177+
ApplicationLiveState: r.LiveState.toModel(now),
178+
SyncState: r.SyncState.toModel(now),
179+
}
180+
}
181+
182+
// ApplicationLiveState represents the live state of an application.
183+
type ApplicationLiveState struct {
184+
Resources []ResourceState
185+
HealthStatus ApplicationHealthStatus
186+
}
187+
188+
// toModel converts the ApplicationLiveState to the model.ApplicationLiveState.
189+
func (s *ApplicationLiveState) toModel(now time.Time) *model.ApplicationLiveState {
190+
resources := make([]*model.ResourceState, 0, len(s.Resources))
191+
for _, rs := range s.Resources {
192+
resources = append(resources, rs.toModel(now))
193+
}
194+
return &model.ApplicationLiveState{
195+
Resources: resources,
196+
HealthStatus: s.HealthStatus.toModel(),
197+
}
198+
}
199+
200+
// ResourceState represents the live state of a resource.
201+
type ResourceState struct {
202+
// ID is the unique identifier of the resource.
203+
ID string
204+
// ParentIDs is the list of the parent resource's IDs.
205+
ParentIDs []string
206+
// Name is the name of the resource.
207+
Name string
208+
// ResourceType is the type of the resource.
209+
ResourceType string
210+
// ResourceMetadata is the metadata of the resource.
211+
ResourceMetadata map[string]string
212+
// HealthStatus is the health status of the resource.
213+
HealthStatus ResourceHealthStatus
214+
// HealthDescription is the description of the health status.
215+
HealthDescription string
216+
// DeployTarget is the target where the resource is deployed.
217+
DeployTarget string
218+
// PluginName is the name of the plugin that provides the resource.
219+
PluginName string
220+
// CreatedAt is the time when the resource was created.
221+
CreatedAt time.Time
222+
}
223+
224+
// toModel converts the ResourceState to the model.ResourceState.
225+
func (s *ResourceState) toModel(now time.Time) *model.ResourceState {
226+
return &model.ResourceState{
227+
Id: s.ID,
228+
ParentIds: s.ParentIDs,
229+
Name: s.Name,
230+
ResourceType: s.ResourceType,
231+
ResourceMetadata: s.ResourceMetadata,
232+
HealthStatus: s.HealthStatus.toModel(),
233+
HealthDescription: s.HealthDescription,
234+
DeployTarget: s.DeployTarget,
235+
PluginName: s.PluginName,
236+
CreatedAt: s.CreatedAt.Unix(),
237+
UpdatedAt: now.Unix(),
238+
}
239+
}
240+
241+
// ApplicationHealthStatus represents the health status of an application.
242+
type ApplicationHealthStatus int
243+
244+
const (
245+
// ApplicationHealthStateUnknown represents the unknown health status of an application.
246+
ApplicationHealthStateUnknown ApplicationHealthStatus = iota
247+
// ApplicationHealthStateHealthy represents the healthy health status of an application.
248+
ApplicationHealthStateHealthy
249+
// ApplicationHealthStateOther represents the other health status of an application.
250+
ApplicationHealthStateOther
251+
)
252+
253+
// toModel converts the ApplicationHealthStatus to the model.ApplicationLiveState_Status.
254+
func (s ApplicationHealthStatus) toModel() model.ApplicationLiveState_Status {
255+
switch s {
256+
case ApplicationHealthStateHealthy:
257+
return model.ApplicationLiveState_HEALTHY
258+
case ApplicationHealthStateOther:
259+
return model.ApplicationLiveState_OTHER
260+
default:
261+
return model.ApplicationLiveState_UNKNOWN
262+
}
263+
}
264+
265+
// ResourceHealthStatus represents the health status of a resource.
266+
type ResourceHealthStatus int
267+
268+
const (
269+
// ResourceHealthStateUnknown represents the unknown health status of a resource.
270+
ResourceHealthStateUnknown ResourceHealthStatus = iota
271+
// ResourceHealthStateHealthy represents the healthy health status of a resource.
272+
ResourceHealthStateHealthy
273+
// ResourceHealthStateUnhealthy represents the unhealthy health status of a resource.
274+
ResourceHealthStateUnhealthy
275+
)
276+
277+
// toModel converts the ResourceHealthStatus to the model.ResourceState_HealthStatus.
278+
func (s ResourceHealthStatus) toModel() model.ResourceState_HealthStatus {
279+
switch s {
280+
case ResourceHealthStateHealthy:
281+
return model.ResourceState_HEALTHY
282+
case ResourceHealthStateUnhealthy:
283+
return model.ResourceState_UNHEALTHY
284+
default:
285+
return model.ResourceState_UNKNOWN
286+
}
287+
}
288+
289+
// ApplicationSyncState represents the sync state of an application.
290+
type ApplicationSyncState struct {
291+
// Status is the sync status of the application.
292+
Status ApplicationSyncStatus
293+
// ShortReason is the short reason of the sync status.
294+
// for example, "The service manifest doesn't be synced"
295+
ShortReason string
296+
// Reason is the reason of the sync status.
297+
// actually, it's the difference between the desired state and the live state.
298+
Reason string
299+
}
300+
301+
// toModel converts the ApplicationSyncState to the model.ApplicationSyncState.
302+
func (s *ApplicationSyncState) toModel(now time.Time) *model.ApplicationSyncState {
303+
return &model.ApplicationSyncState{
304+
Status: s.Status.toModel(),
305+
ShortReason: s.ShortReason,
306+
Reason: s.Reason,
307+
Timestamp: now.Unix(),
308+
}
309+
}
310+
311+
// ApplicationSyncStatus represents the sync status of an application.
312+
type ApplicationSyncStatus int
313+
314+
const (
315+
// ApplicationSyncStateUnknown represents the unknown sync status of an application.
316+
ApplicationSyncStateUnknown ApplicationSyncStatus = iota
317+
// ApplicationSyncStateSynced represents the synced sync status of an application.
318+
ApplicationSyncStateSynced
319+
// ApplicationSyncStateOutOfSync represents the out-of-sync sync status of an application.
320+
ApplicationSyncStateOutOfSync
321+
// ApplicationSyncStateInvalidConfig represents the invalid-config sync status of an application.
322+
ApplicationSyncStateInvalidConfig
323+
)
324+
325+
// toModel converts the ApplicationSyncStatus to the model.ApplicationSyncStatus.
326+
func (s ApplicationSyncStatus) toModel() model.ApplicationSyncStatus {
327+
switch s {
328+
case ApplicationSyncStateSynced:
329+
return model.ApplicationSyncStatus_SYNCED
330+
case ApplicationSyncStateOutOfSync:
331+
return model.ApplicationSyncStatus_OUT_OF_SYNC
332+
case ApplicationSyncStateInvalidConfig:
333+
return model.ApplicationSyncStatus_INVALID_CONFIG
334+
default:
335+
return model.ApplicationSyncStatus_UNKNOWN
336+
}
101337
}

0 commit comments

Comments
 (0)