-
Notifications
You must be signed in to change notification settings - Fork 2
/
plugin.go
124 lines (108 loc) · 2.96 KB
/
plugin.go
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
package pipeline
import (
"encoding/json"
"net/url"
"path"
"strings"
"gopkg.in/yaml.v3"
)
var (
_ interface {
json.Marshaler
yaml.Marshaler
selfInterpolater
} = (*Plugin)(nil)
)
// Plugin models plugin configuration.
//
// Standard caveats apply - see the package comment.
type Plugin struct {
Source string
Config any
}
// MarshalJSON returns the plugin in "one-key object" form. Plugin sources are
// marshalled into "full" form. Plugins originally specified as a single string
// (no config, only source) are canonicalised into "one-key object" with config
// null.
func (p *Plugin) MarshalJSON() ([]byte, error) {
// NB: MarshalYAML (as seen below) never returns an error.
o, _ := p.MarshalYAML()
return json.Marshal(o)
}
// MarshalYAML returns the plugin in "one-item map" form. Plugin sources
// are marshalled into "full" form. Plugins originally specified as a single
// string (no config, only source) are canonicalised into "one-item map" with
// config nil. Configs that are zero-length maps are canonicalised to nil.
func (p *Plugin) MarshalYAML() (any, error) {
cfg := p.Config
switch x := cfg.(type) {
case map[string]any:
if len(x) == 0 {
cfg = nil
}
case []any:
// Should be invalid, but a different part of the process should be
// responsible for checking and complaining.
if len(x) == 0 {
cfg = nil
}
}
return map[string]any{
p.FullSource(): cfg,
}, nil
}
// FullSource attempts to canonicalise Source. If it fails, it returns Source
// unaltered. Otherwise, it resolves sources in a manner described at
// https://buildkite.com/docs/plugins/using#plugin-sources.
func (p *Plugin) FullSource() string {
if p.Source == "" {
return ""
}
// Looks like an absolute or relative file path.
if strings.HasPrefix(p.Source, "/") || strings.HasPrefix(p.Source, ".") || strings.HasPrefix(p.Source, `\`) {
return p.Source
}
u, err := url.Parse(p.Source)
if err != nil {
return p.Source
}
// They wrote something like ssh://..., https://..., or C:\...
// in which case they _mean it_.
if u.Scheme != "" || u.Opaque != "" {
return p.Source
}
// thing => thing-buildkite-plugin
// thing#main => thing-buildkite-plugin#main
lastSegment := func(n, f string) string {
n += "-buildkite-plugin"
if f == "" {
return n
}
return n + "#" + f
}
paths := strings.Split(strings.TrimPrefix(u.Path, "/"), "/")
switch len(paths) {
case 1:
// trimmed path contained no slash
return path.Join("github.com", "buildkite-plugins", lastSegment(paths[0], u.Fragment))
case 2:
// trimmed path contained one slash
return path.Join("github.com", paths[0], lastSegment(paths[1], u.Fragment))
default:
// trimmed path contained more than one slash - apply no smarts
return p.Source
}
}
func (p *Plugin) interpolate(tf stringTransformer) error {
name, err := tf.Transform(p.Source)
if err != nil {
return err
}
cfg, err := interpolateAny(tf, p.Config)
if err != nil {
return err
}
p.Source = name
p.Config = cfg
return nil
}