Skip to content

Commit c527965

Browse files
authored
Implement SDK BuildPipelineSyncStages methods (#5584)
* Define types for BuildPipelineSyncStages Signed-off-by: Shinnosuke Sawada-Dazai <shin@warashi.dev> * Implement BuildPipelineSyncStages method Signed-off-by: Shinnosuke Sawada-Dazai <shin@warashi.dev> * Add config handlings Signed-off-by: Shinnosuke Sawada-Dazai <shin@warashi.dev> * Add ManualOperationNone constant to represent absence of manual operation Signed-off-by: Shinnosuke Sawada-Dazai <shin@warashi.dev> * Update BuildPipelineSyncStages to use config in deployment logic Signed-off-by: Shinnosuke Sawada-Dazai <shin@warashi.dev> --------- Signed-off-by: Shinnosuke Sawada-Dazai <shin@warashi.dev>
1 parent b099bc0 commit c527965

File tree

2 files changed

+140
-5
lines changed

2 files changed

+140
-5
lines changed

pkg/app/pipedv1/plugin/example/plugin.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -36,8 +36,8 @@ func (p *plugin) Version() string {
3636
}
3737

3838
// BuildPipelineSyncStages implements sdk.PipelineSyncPlugin.
39-
func (p *plugin) BuildPipelineSyncStages(context.Context, *config, *sdk.Client, sdk.TODO) (sdk.TODO, error) {
40-
return sdk.TODO{}, nil
39+
func (p *plugin) BuildPipelineSyncStages(context.Context, *config, *sdk.Client, *sdk.BuildPipelineSyncStagesRequest) (*sdk.BuildPipelineSyncStagesResponse, error) {
40+
return &sdk.BuildPipelineSyncStagesResponse{}, nil
4141
}
4242

4343
// ExecuteStage implements sdk.PipelineSyncPlugin.

pkg/plugin/sdk/deployment.go

Lines changed: 138 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -18,13 +18,15 @@ import (
1818
"context"
1919
"encoding/json"
2020
"fmt"
21+
"time"
2122

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

2728
config "github.com/pipe-cd/pipecd/pkg/configv1"
29+
"github.com/pipe-cd/pipecd/pkg/model"
2830
"github.com/pipe-cd/pipecd/pkg/plugin/api/v1alpha1/deployment"
2931
"github.com/pipe-cd/pipecd/pkg/plugin/logpersister"
3032
"github.com/pipe-cd/pipecd/pkg/plugin/pipedapi"
@@ -71,7 +73,7 @@ type PipelineSyncPlugin[Config, DeployTargetConfig any] interface {
7173
// FetchDefinedStages returns the list of stages that the plugin can execute.
7274
FetchDefinedStages() []string
7375
// BuildPipelineSyncStages builds the stages that will be executed by the plugin.
74-
BuildPipelineSyncStages(context.Context, *Config, *Client, TODO) (TODO, error)
76+
BuildPipelineSyncStages(context.Context, *Config, *Client, *BuildPipelineSyncStagesRequest) (*BuildPipelineSyncStagesResponse, error)
7577
// ExecuteStage executes the given stage.
7678
ExecuteStage(context.Context, *Config, []*DeployTarget[DeployTargetConfig], *Client, logpersister.StageLogPersister, TODO) (TODO, error)
7779
}
@@ -156,7 +158,7 @@ func (s *DeploymentPluginServiceServer[Config, DeployTargetConfig]) DetermineStr
156158
return nil, status.Errorf(codes.Unimplemented, "method DetermineStrategy not implemented")
157159
}
158160
func (s *DeploymentPluginServiceServer[Config, DeployTargetConfig]) BuildPipelineSyncStages(ctx context.Context, request *deployment.BuildPipelineSyncStagesRequest) (*deployment.BuildPipelineSyncStagesResponse, error) {
159-
return nil, status.Errorf(codes.Unimplemented, "method BuildPipelineSyncStages not implemented")
161+
return buildPipelineSyncStages(ctx, s.base, &s.config, nil, request) // TODO: pass the real client
160162
}
161163
func (s *DeploymentPluginServiceServer[Config, DeployTargetConfig]) BuildQuickSyncStages(context.Context, *deployment.BuildQuickSyncStagesRequest) (*deployment.BuildQuickSyncStagesResponse, error) {
162164
return nil, status.Errorf(codes.Unimplemented, "method BuildQuickSyncStages not implemented")
@@ -213,11 +215,144 @@ func (s *PipelineSyncPluginServiceServer[Config, DeployTargetConfig]) DetermineS
213215
return &deployment.DetermineStrategyResponse{Unsupported: true}, nil
214216
}
215217
func (s *PipelineSyncPluginServiceServer[Config, DeployTargetConfig]) BuildPipelineSyncStages(ctx context.Context, request *deployment.BuildPipelineSyncStagesRequest) (*deployment.BuildPipelineSyncStagesResponse, error) {
216-
return nil, status.Errorf(codes.Unimplemented, "method BuildPipelineSyncStages not implemented")
218+
return buildPipelineSyncStages(ctx, s.base, &s.config, nil, request) // TODO: pass the real client
217219
}
218220
func (s *PipelineSyncPluginServiceServer[Config, DeployTargetConfig]) BuildQuickSyncStages(context.Context, *deployment.BuildQuickSyncStagesRequest) (*deployment.BuildQuickSyncStagesResponse, error) {
219221
return nil, status.Errorf(codes.Unimplemented, "method BuildQuickSyncStages not implemented")
220222
}
221223
func (s *PipelineSyncPluginServiceServer[Config, DeployTargetConfig]) ExecuteStage(context.Context, *deployment.ExecuteStageRequest) (*deployment.ExecuteStageResponse, error) {
222224
return nil, status.Errorf(codes.Unimplemented, "method ExecuteStage not implemented")
223225
}
226+
227+
// buildPipelineSyncStages builds the stages that will be executed by the plugin.
228+
func buildPipelineSyncStages[Config, DeployTargetConfig any](ctx context.Context, plugin PipelineSyncPlugin[Config, DeployTargetConfig], config *Config, client *Client, request *deployment.BuildPipelineSyncStagesRequest) (*deployment.BuildPipelineSyncStagesResponse, error) {
229+
resp, err := plugin.BuildPipelineSyncStages(ctx, config, client, newPipelineSyncStagesRequest(request))
230+
if err != nil {
231+
return nil, status.Errorf(codes.Internal, "failed to build pipeline sync stages: %v", err)
232+
}
233+
return newPipelineSyncStagesResponse(plugin, time.Now(), request, resp)
234+
}
235+
236+
// ManualOperation represents the manual operation that the user can perform.
237+
type ManualOperation int
238+
239+
const (
240+
// ManualOperationNone indicates that there is no manual operation.
241+
ManualOperationNone ManualOperation = iota
242+
// ManualOperationSkip indicates that the manual operation is to skip the stage.
243+
ManualOperationSkip
244+
// ManualOperationApprove indicates that the manual operation is to approve the stage.
245+
ManualOperationApprove
246+
)
247+
248+
// toModelEnum converts the ManualOperation to the model.ManualOperation.
249+
func (o ManualOperation) toModelEnum() model.ManualOperation {
250+
switch o {
251+
case ManualOperationNone:
252+
return model.ManualOperation_MANUAL_OPERATION_NONE
253+
case ManualOperationSkip:
254+
return model.ManualOperation_MANUAL_OPERATION_SKIP
255+
case ManualOperationApprove:
256+
return model.ManualOperation_MANUAL_OPERATION_APPROVE
257+
default:
258+
return model.ManualOperation_MANUAL_OPERATION_UNKNOWN
259+
}
260+
}
261+
262+
// newPipelineSyncStagesRequest converts the request to the internal representation.
263+
func newPipelineSyncStagesRequest(request *deployment.BuildPipelineSyncStagesRequest) *BuildPipelineSyncStagesRequest {
264+
stages := make([]StageConfig, 0, len(request.Stages))
265+
for _, s := range request.GetStages() {
266+
stages = append(stages, StageConfig{
267+
Index: int(s.GetIndex()),
268+
Name: s.GetName(),
269+
Config: s.GetConfig(),
270+
})
271+
}
272+
return &BuildPipelineSyncStagesRequest{
273+
Rollback: request.GetRollback(),
274+
Stages: stages,
275+
}
276+
}
277+
278+
// newPipelineSyncStagesResponse converts the response to the external representation.
279+
func newPipelineSyncStagesResponse(plugin Plugin, now time.Time, request *deployment.BuildPipelineSyncStagesRequest, response *BuildPipelineSyncStagesResponse) (*deployment.BuildPipelineSyncStagesResponse, error) {
280+
// Convert the request stages to a map for easier access.
281+
requestStages := make(map[int]*deployment.BuildPipelineSyncStagesRequest_StageConfig, len(request.GetStages()))
282+
for _, s := range request.GetStages() {
283+
requestStages[int(s.GetIndex())] = s
284+
}
285+
286+
stages := make([]*model.PipelineStage, 0, len(response.Stages))
287+
for _, s := range response.Stages {
288+
// Find the corresponding stage in the request.
289+
requestStage, ok := requestStages[s.Index]
290+
if !ok {
291+
return nil, status.Errorf(codes.Internal, "missing stage with index %d in the request, it's unexpected behavior of the plugin", s.Index)
292+
}
293+
id := requestStage.GetId()
294+
if id == "" {
295+
id = fmt.Sprintf("%s-stage-%d", plugin.Name(), s.Index)
296+
}
297+
stages = append(stages, &model.PipelineStage{
298+
Id: id,
299+
Name: s.Name,
300+
Desc: requestStage.GetDesc(),
301+
Index: int32(s.Index),
302+
Status: model.StageStatus_STAGE_NOT_STARTED_YET,
303+
StatusReason: "", // TODO: set the reason
304+
Metadata: s.Metadata,
305+
Rollback: s.Rollback,
306+
CreatedAt: now.Unix(),
307+
UpdatedAt: now.Unix(),
308+
AvailableOperation: s.AvailableOperation.toModelEnum(),
309+
})
310+
}
311+
return &deployment.BuildPipelineSyncStagesResponse{
312+
Stages: stages,
313+
}, nil
314+
}
315+
316+
// BuildPipelineSyncStagesRequest is the request to build pipeline sync stages.
317+
// Rollback indicates whether the stages for rollback are requested.
318+
type BuildPipelineSyncStagesRequest struct {
319+
// Rollback indicates whether the stages for rollback are requested.
320+
Rollback bool
321+
// Stages contains the stage names and their configurations.
322+
Stages []StageConfig
323+
}
324+
325+
// StageConfig represents the configuration of a stage.
326+
type StageConfig struct {
327+
// Index is the order of the stage in the pipeline.
328+
Index int
329+
// Name is the name of the stage.
330+
// It must be one of the stages returned by FetchDefinedStages.
331+
Name string
332+
// Config is the configuration of the stage.
333+
// It should be marshaled to JSON bytes.
334+
// The plugin should unmarshal it to the appropriate struct.
335+
Config []byte
336+
}
337+
338+
// BuildPipelineSyncStagesResponse is the response of the request to build pipeline sync stages.
339+
type BuildPipelineSyncStagesResponse struct {
340+
Stages []PipelineStage
341+
}
342+
343+
// PipelineStage represents a stage in the pipeline.
344+
type PipelineStage struct {
345+
// Index is the order of the stage in the pipeline.
346+
// The value must be one of the index of the stage in the request.
347+
// The rollback stage should have the same index as the original stage.
348+
Index int
349+
// Name is the name of the stage.
350+
// It must be one of the stages returned by FetchDefinedStages.
351+
Name string
352+
// Rollback indicates whether the stage is for rollback.
353+
Rollback bool
354+
// Metadata contains the metadata of the stage.
355+
Metadata map[string]string
356+
// AvailableOperation indicates the manual operation that the user can perform.
357+
AvailableOperation ManualOperation
358+
}

0 commit comments

Comments
 (0)