Skip to content

Commit 3572fd8

Browse files
authored
fix(api): yaml expand on stage pipelines (#1363)
1 parent 68f292d commit 3572fd8

File tree

3 files changed

+190
-4
lines changed

3 files changed

+190
-4
lines changed

api/pipeline/expand_test.go

Lines changed: 158 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,158 @@
1+
// SPDX-License-Identifier: Apache-2.0
2+
3+
package pipeline
4+
5+
import (
6+
"context"
7+
"io"
8+
"net/http"
9+
"net/http/httptest"
10+
"testing"
11+
12+
"github.com/gin-gonic/gin"
13+
"github.com/sirupsen/logrus"
14+
"github.com/urfave/cli/v3"
15+
"go.yaml.in/yaml/v3"
16+
17+
api "github.com/go-vela/server/api/types"
18+
"github.com/go-vela/server/compiler"
19+
"github.com/go-vela/server/compiler/native"
20+
"github.com/go-vela/server/constants"
21+
"github.com/go-vela/server/internal"
22+
pipelineMiddleware "github.com/go-vela/server/router/middleware/pipeline"
23+
repoMiddleware "github.com/go-vela/server/router/middleware/repo"
24+
userMiddleware "github.com/go-vela/server/router/middleware/user"
25+
)
26+
27+
func TestExpandPipelineStagesReturnsCleanYAML(t *testing.T) {
28+
gin.SetMode(gin.TestMode)
29+
30+
assertCleanStages := func(t *testing.T, w *httptest.ResponseRecorder) {
31+
t.Helper()
32+
33+
if w.Code != http.StatusOK {
34+
t.Fatalf("ExpandPipeline returned status %d, want %d", w.Code, http.StatusOK)
35+
}
36+
37+
var got struct {
38+
Stages map[string]any `yaml:"stages"`
39+
}
40+
41+
if err := yaml.Unmarshal(w.Body.Bytes(), &got); err != nil {
42+
t.Fatalf("unable to parse yaml response: %v", err)
43+
}
44+
45+
if len(got.Stages) == 0 {
46+
t.Fatalf("expected stages in response, got none")
47+
}
48+
49+
if _, ok := got.Stages["build"]; !ok {
50+
t.Fatalf("expected build stage in response, got keys %#v", got.Stages)
51+
}
52+
53+
if _, hasKind := got.Stages["kind"]; hasKind {
54+
t.Fatalf("unexpected raw yaml node output: %#v", got.Stages)
55+
}
56+
}
57+
58+
setup := func(t *testing.T, requestPath string) (*httptest.ResponseRecorder, *gin.Context) {
59+
t.Helper()
60+
61+
w := httptest.NewRecorder()
62+
c, _ := gin.CreateTestContext(w)
63+
64+
req := httptest.NewRequest(http.MethodPost, requestPath, nil)
65+
c.Request = req
66+
67+
logger := logrus.New()
68+
logger.SetOutput(io.Discard)
69+
c.Set("logger", logrus.NewEntry(logger))
70+
71+
c.Set("metadata", &internal.Metadata{})
72+
73+
repo := new(api.Repo)
74+
repo.SetOrg("foo")
75+
repo.SetName("bar")
76+
repo.SetFullName("foo/bar")
77+
repo.SetPipelineType(constants.PipelineTypeYAML)
78+
repoMiddleware.ToContext(c, repo)
79+
80+
pipeline := new(api.Pipeline)
81+
pipeline.SetCommit("123")
82+
pipeline.SetType(constants.PipelineTypeYAML)
83+
pipeline.SetData([]byte(`version: "1"
84+
stages:
85+
build:
86+
steps:
87+
- name: test
88+
image: alpine
89+
commands:
90+
- echo hello
91+
`))
92+
pipelineMiddleware.ToContext(c, pipeline)
93+
94+
user := new(api.User)
95+
userMiddleware.ToContext(c, user)
96+
97+
engine := newTestCompiler(t)
98+
compiler.WithGinContext(c, engine)
99+
100+
return w, c
101+
}
102+
103+
t.Run("explicit yaml", func(t *testing.T) {
104+
w, c := setup(t, "/api/v1/pipelines/foo/bar/123/expand?output=yaml")
105+
106+
ExpandPipeline(c)
107+
108+
assertCleanStages(t, w)
109+
})
110+
111+
t.Run("default yaml", func(t *testing.T) {
112+
w, c := setup(t, "/api/v1/pipelines/foo/bar/123/expand")
113+
114+
ExpandPipeline(c)
115+
116+
assertCleanStages(t, w)
117+
})
118+
}
119+
120+
func newTestCompiler(t *testing.T) compiler.Engine {
121+
t.Helper()
122+
123+
cmd := &cli.Command{
124+
Flags: []cli.Flag{
125+
&cli.StringFlag{
126+
Name: "clone-image",
127+
Value: "target/vela-git:latest",
128+
},
129+
&cli.IntFlag{
130+
Name: "max-template-depth",
131+
Value: 5,
132+
},
133+
&cli.BoolFlag{
134+
Name: "github-driver",
135+
Value: false,
136+
},
137+
&cli.StringFlag{
138+
Name: "github-url",
139+
Value: "",
140+
},
141+
&cli.StringFlag{
142+
Name: "github-token",
143+
Value: "",
144+
},
145+
&cli.Int64Flag{
146+
Name: "compiler-starlark-exec-limit",
147+
Value: 0,
148+
},
149+
},
150+
}
151+
152+
engine, err := native.FromCLICommand(context.Background(), cmd)
153+
if err != nil {
154+
t.Fatalf("unable to create compiler: %v", err)
155+
}
156+
157+
return engine
158+
}

api/pipeline/output.go

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,12 @@
33
package pipeline
44

55
import (
6+
"fmt"
67
"net/http"
78
"strings"
89

910
"github.com/gin-gonic/gin"
11+
yml "go.yaml.in/yaml/v3"
1012

1113
"github.com/go-vela/server/util"
1214
)
@@ -29,6 +31,20 @@ func writeOutput(c *gin.Context, value interface{}) {
2931
case outputYAML:
3032
fallthrough
3133
default:
32-
c.YAML(http.StatusOK, value)
34+
// TODO:
35+
// we should be able to use c.YAML here from gin,
36+
// but there's some incompatibility with us creating yaml.Node
37+
// with the go.yaml.in/yaml/v3 package and gin using gopkg.in/yaml.v3
38+
// when calling c.YAML. When gin switches to go.yaml.in/yaml/v3 we can
39+
// switch to using c.YAML here.
40+
body, err := yml.Marshal(value)
41+
if err != nil {
42+
reason := fmt.Errorf("unable to marshal YAML response: %w", err)
43+
util.HandleError(c, http.StatusInternalServerError, reason)
44+
45+
return
46+
}
47+
48+
c.Data(http.StatusOK, gin.MIMEYAML, body)
3349
}
3450
}

mock/server/pipeline.go

Lines changed: 15 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -433,7 +433,7 @@ func compilePipeline(c *gin.Context) {
433433

434434
_ = yml.Unmarshal(data, &body)
435435

436-
c.YAML(http.StatusOK, body)
436+
writeYAML(c, http.StatusOK, body)
437437
}
438438

439439
// expandPipeline has a param :pipeline returns mock YAML for a http GET.
@@ -456,7 +456,7 @@ func expandPipeline(c *gin.Context) {
456456

457457
_ = yml.Unmarshal(data, &body)
458458

459-
c.YAML(http.StatusOK, body)
459+
writeYAML(c, http.StatusOK, body)
460460
}
461461

462462
// getTemplates has a param :pipeline returns mock YAML for a http GET.
@@ -478,7 +478,19 @@ func getTemplates(c *gin.Context) {
478478
body := make(map[string]*yaml.Template)
479479
_ = yml.Unmarshal(data, &body)
480480

481-
c.YAML(http.StatusOK, body)
481+
writeYAML(c, http.StatusOK, body)
482+
}
483+
484+
func writeYAML(c *gin.Context, status int, value interface{}) {
485+
body, err := yml.Marshal(value)
486+
if err != nil {
487+
msg := fmt.Sprintf("unable to marshal YAML response: %v", err)
488+
c.AbortWithStatusJSON(http.StatusInternalServerError, api.Error{Message: &msg})
489+
490+
return
491+
}
492+
493+
c.Data(status, gin.MIMEYAML, body)
482494
}
483495

484496
// validatePipeline has a param :pipeline returns mock YAML for a http GET.

0 commit comments

Comments
 (0)