Skip to content

Commit 3a40be6

Browse files
Add Analysis Stage Plugin - Core Structure Implementation (#6097)
* Add analysis plugin with stage execution and pipeline sync Signed-off-by: Mohammed Firdous <[email protected]> * analysisStageOptions struct and decode function for ANALYSIS stage Signed-off-by: Mohammed Firdous <[email protected]> * Add unit tests for analysis plugin's stage options and pipeline sync Signed-off-by: Mohammed Firdous <[email protected]> * add go dependency files Signed-off-by: Mohammed Firdous <[email protected]> * Update pkg/app/pipedv1/plugin/analysis/options.go Co-authored-by: Tetsuya KIKUCHI <[email protected]> Signed-off-by: Mohammed Firdous <[email protected]> * Update pkg/app/pipedv1/plugin/analysis/plugin_test.go Co-authored-by: Tetsuya KIKUCHI <[email protected]> Signed-off-by: Mohammed Firdous <[email protected]> * Refactor analysis plugin tests and update input types for decoding Signed-off-by: Mohammed Firdous <[email protected]> * chore(plugin): update analysis plugin to use ManualOperationSkip Signed-off-by: Mohammed Firdous <[email protected]> * fix: Correct test expectations for pipeline stages Signed-off-by: mohammedfirdouss <[email protected]> --------- Signed-off-by: Mohammed Firdous <[email protected]> Signed-off-by: mohammedfirdouss <[email protected]> Co-authored-by: Tetsuya KIKUCHI <[email protected]>
1 parent ebdc746 commit 3a40be6

File tree

8 files changed

+1011
-2
lines changed

8 files changed

+1011
-2
lines changed
Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
module github.com/pipe-cd/pipecd/pkg/app/pipedv1/plugin/analysis
2+
3+
go 1.24.1
4+
5+
require (
6+
github.com/pipe-cd/piped-plugin-sdk-go v0.0.0-20250728033142-7a6a214b39f7
7+
github.com/stretchr/testify v1.10.0
8+
)
9+
10+
require (
11+
cloud.google.com/go v0.112.1 // indirect
12+
cloud.google.com/go/compute/metadata v0.3.0 // indirect
13+
cloud.google.com/go/profiler v0.3.1 // indirect
14+
github.com/beorn7/perks v1.0.1 // indirect
15+
github.com/cespare/xxhash/v2 v2.2.0 // indirect
16+
github.com/coreos/go-oidc/v3 v3.11.0 // indirect
17+
github.com/creasty/defaults v1.6.0 // indirect
18+
github.com/davecgh/go-spew v1.1.1 // indirect
19+
github.com/envoyproxy/protoc-gen-validate v1.0.4 // indirect
20+
github.com/go-jose/go-jose/v4 v4.0.5 // indirect
21+
github.com/go-logr/logr v1.4.2 // indirect
22+
github.com/go-logr/stdr v1.2.2 // indirect
23+
github.com/golang-jwt/jwt/v5 v5.2.2 // indirect
24+
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect
25+
github.com/golang/protobuf v1.5.4 // indirect
26+
github.com/google/pprof v0.0.0-20221103000818-d260c55eee4c // indirect
27+
github.com/google/s2a-go v0.1.7 // indirect
28+
github.com/googleapis/enterprise-certificate-proxy v0.3.2 // indirect
29+
github.com/googleapis/gax-go/v2 v2.12.2 // indirect
30+
github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0 // indirect
31+
github.com/inconshreveable/mousetrap v1.1.0 // indirect
32+
github.com/matttproud/golang_protobuf_extensions v1.0.2-0.20181231171920-c182affec369 // indirect
33+
github.com/pipe-cd/pipecd v0.52.1-0.20250722035702-5722fabb80ce // indirect
34+
github.com/pmezard/go-difflib v1.0.0 // indirect
35+
github.com/prometheus/client_golang v1.12.1 // indirect
36+
github.com/prometheus/client_model v0.5.0 // indirect
37+
github.com/prometheus/common v0.32.1 // indirect
38+
github.com/prometheus/procfs v0.7.3 // indirect
39+
github.com/spf13/cobra v1.9.1 // indirect
40+
github.com/spf13/pflag v1.0.6 // indirect
41+
go.opencensus.io v0.24.0 // indirect
42+
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.49.0 // indirect
43+
go.opentelemetry.io/otel v1.28.0 // indirect
44+
go.opentelemetry.io/otel/metric v1.28.0 // indirect
45+
go.opentelemetry.io/otel/trace v1.28.0 // indirect
46+
go.uber.org/atomic v1.11.0 // indirect
47+
go.uber.org/multierr v1.6.0 // indirect
48+
go.uber.org/zap v1.19.1 // indirect
49+
go.yaml.in/yaml/v2 v2.4.2 // indirect
50+
golang.org/x/crypto v0.36.0 // indirect
51+
golang.org/x/net v0.38.0 // indirect
52+
golang.org/x/oauth2 v0.27.0 // indirect
53+
golang.org/x/sync v0.12.0 // indirect
54+
golang.org/x/sys v0.31.0 // indirect
55+
golang.org/x/text v0.23.0 // indirect
56+
golang.org/x/time v0.5.0 // indirect
57+
google.golang.org/api v0.169.0 // indirect
58+
google.golang.org/genproto v0.0.0-20240213162025-012b6fc9bca9 // indirect
59+
google.golang.org/genproto/googleapis/api v0.0.0-20240701130421-f6361c86f094 // indirect
60+
google.golang.org/genproto/googleapis/rpc v0.0.0-20240701130421-f6361c86f094 // indirect
61+
google.golang.org/grpc v1.64.1 // indirect
62+
google.golang.org/protobuf v1.34.2 // indirect
63+
gopkg.in/yaml.v3 v3.0.1 // indirect
64+
sigs.k8s.io/yaml v1.5.0 // indirect
65+
)

pkg/app/pipedv1/plugin/analysis/go.sum

Lines changed: 614 additions & 0 deletions
Large diffs are not rendered by default.
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
// Copyright 2025 The PipeCD Authors.
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
package main
16+
17+
import (
18+
"log"
19+
20+
sdk "github.com/pipe-cd/piped-plugin-sdk-go"
21+
)
22+
23+
func main() {
24+
plugin, err := sdk.NewPlugin("0.0.1", sdk.WithStagePlugin(&plugin{}))
25+
if err != nil {
26+
log.Fatalln(err)
27+
}
28+
29+
if err := plugin.Run(); err != nil {
30+
log.Fatalln(err)
31+
}
32+
}
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
// Copyright 2025 The PipeCD Authors.
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
package main
16+
17+
import (
18+
"encoding/json"
19+
)
20+
21+
// analysisStageOptions represents the configuration options for the ANALYSIS stage.
22+
type analysisStageOptions struct {
23+
// TODO: Add fields here
24+
}
25+
26+
// decode decodes the raw JSON data and validates it.
27+
func decode(data json.RawMessage) (analysisStageOptions, error) {
28+
opts := analysisStageOptions{}
29+
if err := json.Unmarshal(data, &opts); err != nil {
30+
return analysisStageOptions{}, err
31+
}
32+
return opts, nil
33+
}
Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
// Copyright 2025 The PipeCD Authors.
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
package main
16+
17+
import (
18+
"testing"
19+
20+
"github.com/stretchr/testify/assert"
21+
)
22+
23+
func TestDecode(t *testing.T) {
24+
t.Parallel()
25+
26+
testcases := []struct {
27+
name string
28+
input []byte
29+
expected analysisStageOptions
30+
wantErr bool
31+
}{
32+
{
33+
name: "empty config",
34+
input: []byte(`{}`),
35+
expected: analysisStageOptions{},
36+
wantErr: false,
37+
},
38+
{
39+
name: "invalid json",
40+
input: []byte(`{invalid}`),
41+
wantErr: true,
42+
},
43+
}
44+
45+
for _, tc := range testcases {
46+
t.Run(tc.name, func(t *testing.T) {
47+
t.Parallel()
48+
got, err := decode(tc.input)
49+
50+
if tc.wantErr {
51+
assert.Error(t, err)
52+
return
53+
}
54+
55+
assert.NoError(t, err)
56+
assert.Equal(t, tc.expected, got)
57+
})
58+
}
59+
}
Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
// Copyright 2025 The PipeCD Authors.
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
package main
16+
17+
import (
18+
"context"
19+
20+
sdk "github.com/pipe-cd/piped-plugin-sdk-go"
21+
)
22+
23+
const (
24+
stageAnalysis = "ANALYSIS"
25+
)
26+
27+
type plugin struct{}
28+
29+
func (p *plugin) BuildPipelineSyncStages(_ context.Context, _ sdk.ConfigNone, input *sdk.BuildPipelineSyncStagesInput) (*sdk.BuildPipelineSyncStagesResponse, error) {
30+
stages := make([]sdk.PipelineStage, 0, len(input.Request.Stages))
31+
for _, rs := range input.Request.Stages {
32+
stages = append(stages, sdk.PipelineStage{
33+
Index: rs.Index,
34+
Name: rs.Name,
35+
Rollback: false,
36+
Metadata: map[string]string{},
37+
AvailableOperation: sdk.ManualOperationSkip,
38+
})
39+
}
40+
41+
return &sdk.BuildPipelineSyncStagesResponse{
42+
Stages: stages,
43+
}, nil
44+
}
45+
46+
func (p *plugin) ExecuteStage(ctx context.Context, _ sdk.ConfigNone, _ sdk.DeployTargetsNone, input *sdk.ExecuteStageInput[struct{}]) (*sdk.ExecuteStageResponse, error) {
47+
// TODO: Implement analysis execution logic
48+
return &sdk.ExecuteStageResponse{
49+
Status: sdk.StageStatusSuccess,
50+
}, nil
51+
}
52+
53+
func (p *plugin) FetchDefinedStages() []string {
54+
return []string{stageAnalysis}
55+
}
Lines changed: 150 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,150 @@
1+
// Copyright 2025 The PipeCD Authors.
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
package main
16+
17+
import (
18+
"context"
19+
"encoding/json"
20+
"testing"
21+
22+
sdk "github.com/pipe-cd/piped-plugin-sdk-go"
23+
"github.com/stretchr/testify/assert"
24+
)
25+
26+
func mustMarshalJSON(t *testing.T, v interface{}) []byte {
27+
t.Helper()
28+
data, err := json.Marshal(v)
29+
if err != nil {
30+
t.Fatal(err)
31+
}
32+
return data
33+
}
34+
35+
func TestBuildPipelineSyncStages(t *testing.T) {
36+
t.Parallel()
37+
p := &plugin{}
38+
ctx := context.Background()
39+
40+
testcases := []struct {
41+
name string
42+
input *sdk.BuildPipelineSyncStagesInput
43+
expected *sdk.BuildPipelineSyncStagesResponse
44+
}{
45+
{
46+
name: "should generate single analysis stage",
47+
input: &sdk.BuildPipelineSyncStagesInput{
48+
Request: sdk.BuildPipelineSyncStagesRequest{
49+
Stages: []sdk.StageConfig{
50+
{
51+
Index: 0,
52+
Name: stageAnalysis,
53+
Config: mustMarshalJSON(t, &analysisStageOptions{}),
54+
},
55+
},
56+
},
57+
},
58+
expected: &sdk.BuildPipelineSyncStagesResponse{
59+
Stages: []sdk.PipelineStage{
60+
{
61+
Index: 0,
62+
Name: stageAnalysis,
63+
Rollback: false,
64+
Metadata: map[string]string{},
65+
AvailableOperation: sdk.ManualOperationSkip,
66+
},
67+
},
68+
},
69+
},
70+
{
71+
name: "should handle multiple analysis stages",
72+
input: &sdk.BuildPipelineSyncStagesInput{
73+
Request: sdk.BuildPipelineSyncStagesRequest{
74+
Stages: []sdk.StageConfig{
75+
{
76+
Index: 0,
77+
Name: stageAnalysis,
78+
Config: mustMarshalJSON(t, &analysisStageOptions{}),
79+
},
80+
{
81+
Index: 2,
82+
Name: stageAnalysis,
83+
Config: mustMarshalJSON(t, &analysisStageOptions{}),
84+
},
85+
},
86+
},
87+
},
88+
expected: &sdk.BuildPipelineSyncStagesResponse{
89+
Stages: []sdk.PipelineStage{
90+
{
91+
Index: 0,
92+
Name: stageAnalysis,
93+
Rollback: false,
94+
Metadata: map[string]string{},
95+
AvailableOperation: sdk.ManualOperationSkip,
96+
},
97+
{
98+
Index: 2,
99+
Name: stageAnalysis,
100+
Rollback: false,
101+
Metadata: map[string]string{},
102+
AvailableOperation: sdk.ManualOperationSkip,
103+
},
104+
},
105+
},
106+
},
107+
{
108+
name: "should generate single analysis stage with rollback",
109+
input: &sdk.BuildPipelineSyncStagesInput{
110+
Request: sdk.BuildPipelineSyncStagesRequest{
111+
Stages: []sdk.StageConfig{
112+
{
113+
Index: 0,
114+
Name: stageAnalysis,
115+
Config: mustMarshalJSON(t, &analysisStageOptions{}),
116+
},
117+
},
118+
},
119+
},
120+
expected: &sdk.BuildPipelineSyncStagesResponse{
121+
Stages: []sdk.PipelineStage{
122+
{
123+
Index: 0,
124+
Name: stageAnalysis,
125+
Rollback: false,
126+
Metadata: map[string]string{},
127+
AvailableOperation: sdk.ManualOperationSkip,
128+
},
129+
},
130+
},
131+
},
132+
}
133+
134+
for _, tc := range testcases {
135+
t.Run(tc.name, func(t *testing.T) {
136+
t.Parallel()
137+
resp, err := p.BuildPipelineSyncStages(ctx, &struct{}{}, tc.input)
138+
assert.NoError(t, err)
139+
assert.Equal(t, tc.expected, resp)
140+
})
141+
}
142+
}
143+
144+
func TestFetchDefinedStages(t *testing.T) {
145+
p := &plugin{}
146+
want := []string{"ANALYSIS"}
147+
got := p.FetchDefinedStages()
148+
149+
assert.Equal(t, want, got)
150+
}

0 commit comments

Comments
 (0)