@@ -2,25 +2,50 @@ package composite
2
2
3
3
import (
4
4
"context"
5
+ "encoding/json"
5
6
"fmt"
7
+ "io"
8
+ "net/http"
9
+ "net/url"
10
+ "os"
6
11
7
12
"github.com/operator-framework/operator-registry/pkg/image"
13
+ "k8s.io/apimachinery/pkg/util/yaml"
8
14
)
9
15
10
16
type BuilderMap map [string ]Builder
11
17
12
18
type CatalogBuilderMap map [string ]BuilderMap
13
19
14
20
type Template struct {
15
- CatalogBuilders CatalogBuilderMap
16
- Registry image.Registry
21
+ CatalogFile string
22
+ ContributionFile string
23
+ Validate bool
24
+ OutputType string
25
+ Registry image.Registry
17
26
}
18
27
19
28
// TODO(everettraven): do we need the context here? If so, how should it be used?
20
- func (t * Template ) Render (ctx context.Context , config * CompositeConfig , validate bool ) error {
29
+ func (t * Template ) Render (ctx context.Context , validate bool ) error {
30
+
31
+ catalogFile , err := t .parseCatalogsSpec ()
32
+ if err != nil {
33
+ return err
34
+ }
35
+
36
+ contributionFile , err := t .parseContributionSpec ()
37
+ if err != nil {
38
+ return err
39
+ }
40
+
41
+ catalogBuilderMap , err := t .newCatalogBuilderMap (catalogFile .Catalogs , t .OutputType )
42
+ if err != nil {
43
+ return err
44
+ }
45
+
21
46
// TODO(everettraven): should we return aggregated errors?
22
- for _ , component := range config .Components {
23
- if builderMap , ok := t . CatalogBuilders [component .Name ]; ok {
47
+ for _ , component := range contributionFile .Components {
48
+ if builderMap , ok := ( * catalogBuilderMap ) [component .Name ]; ok {
24
49
if builder , ok := builderMap [component .Strategy .Template .Schema ]; ok {
25
50
// run the builder corresponding to the schema
26
51
err := builder .Build (ctx , t .Registry , component .Destination .Path , component .Strategy .Template )
@@ -40,11 +65,155 @@ func (t *Template) Render(ctx context.Context, config *CompositeConfig, validate
40
65
}
41
66
} else {
42
67
allowedComponents := []string {}
43
- for k := range t . CatalogBuilders {
68
+ for k := range builderMap {
44
69
allowedComponents = append (allowedComponents , k )
45
70
}
46
71
return fmt .Errorf ("building component %q: component does not exist in the catalog configuration. Available components are: %s" , component .Name , allowedComponents )
47
72
}
48
73
}
49
74
return nil
50
75
}
76
+
77
+ func builderForSchema (schema string , builderCfg BuilderConfig ) (Builder , error ) {
78
+ var builder Builder
79
+ switch schema {
80
+ case BasicBuilderSchema :
81
+ builder = NewBasicBuilder (builderCfg )
82
+ case SemverBuilderSchema :
83
+ builder = NewSemverBuilder (builderCfg )
84
+ case RawBuilderSchema :
85
+ builder = NewRawBuilder (builderCfg )
86
+ case CustomBuilderSchema :
87
+ builder = NewCustomBuilder (builderCfg )
88
+ default :
89
+ return nil , fmt .Errorf ("unknown schema %q" , schema )
90
+ }
91
+
92
+ return builder , nil
93
+ }
94
+
95
+ func (t * Template ) parseCatalogsSpec () (* CatalogConfig , error ) {
96
+ var tempCatalog io.ReadCloser
97
+ catalogURI , err := url .ParseRequestURI (t .CatalogFile )
98
+ if err != nil {
99
+ tempCatalog , err = os .Open (t .CatalogFile )
100
+ if err != nil {
101
+ return nil , fmt .Errorf ("opening catalog config file %q: %v" , t .CatalogFile , err )
102
+ }
103
+ defer tempCatalog .Close ()
104
+ } else {
105
+ tempResp , err := http .Get (catalogURI .String ())
106
+ if err != nil {
107
+ return nil , fmt .Errorf ("fetching remote catalog config file %q: %v" , t .CatalogFile , err )
108
+ }
109
+ tempCatalog = tempResp .Body
110
+ defer tempCatalog .Close ()
111
+ }
112
+ catalogData := tempCatalog
113
+
114
+ // get catalog configurations
115
+ catalogConfig := & CatalogConfig {}
116
+ catalogDoc := json.RawMessage {}
117
+ catalogDecoder := yaml .NewYAMLOrJSONDecoder (catalogData , 4096 )
118
+ err = catalogDecoder .Decode (& catalogDoc )
119
+ if err != nil {
120
+ return nil , fmt .Errorf ("decoding catalog config: %v" , err )
121
+ }
122
+ err = json .Unmarshal (catalogDoc , catalogConfig )
123
+ if err != nil {
124
+ return nil , fmt .Errorf ("unmarshalling catalog config: %v" , err )
125
+ }
126
+
127
+ if catalogConfig .Schema != CatalogSchema {
128
+ return nil , fmt .Errorf ("catalog configuration file has unknown schema, should be %q" , CatalogSchema )
129
+ }
130
+
131
+ return catalogConfig , nil
132
+ }
133
+
134
+ func (t * Template ) parseContributionSpec () (* CompositeConfig , error ) {
135
+
136
+ compositeData , err := os .Open (t .ContributionFile )
137
+ if err != nil {
138
+ return nil , fmt .Errorf ("opening composite config file %q: %v" , t .ContributionFile , err )
139
+ }
140
+ defer compositeData .Close ()
141
+
142
+ // parse data to composite config
143
+ compositeConfig := & CompositeConfig {}
144
+ compositeDoc := json.RawMessage {}
145
+ compositeDecoder := yaml .NewYAMLOrJSONDecoder (compositeData , 4096 )
146
+ err = compositeDecoder .Decode (& compositeDoc )
147
+ if err != nil {
148
+ return nil , fmt .Errorf ("decoding composite config: %v" , err )
149
+ }
150
+ err = json .Unmarshal (compositeDoc , compositeConfig )
151
+ if err != nil {
152
+ return nil , fmt .Errorf ("unmarshalling composite config: %v" , err )
153
+ }
154
+
155
+ if compositeConfig .Schema != CompositeSchema {
156
+ return nil , fmt .Errorf ("%q has unknown schema, should be %q" , t .ContributionFile , CompositeSchema )
157
+ }
158
+
159
+ return compositeConfig , nil
160
+ }
161
+
162
+ func (t * Template ) newCatalogBuilderMap (catalogs []Catalog , outputType string ) (* CatalogBuilderMap , error ) {
163
+
164
+ catalogBuilderMap := make (CatalogBuilderMap )
165
+
166
+ // setup the builders for each catalog
167
+ setupFailed := false
168
+ setupErrors := map [string ][]string {}
169
+ for _ , catalog := range catalogs {
170
+ errs := []string {}
171
+ if catalog .Destination .BaseImage == "" {
172
+ errs = append (errs , "destination.baseImage must not be an empty string" )
173
+ }
174
+
175
+ if catalog .Destination .WorkingDir == "" {
176
+ errs = append (errs , "destination.workingDir must not be an empty string" )
177
+ }
178
+
179
+ // check for validation errors and skip builder creation if there are any errors
180
+ if len (errs ) > 0 {
181
+ setupFailed = true
182
+ setupErrors [catalog .Name ] = errs
183
+ continue
184
+ }
185
+
186
+ if _ , ok := catalogBuilderMap [catalog .Name ]; ! ok {
187
+ builderMap := make (BuilderMap )
188
+ for _ , schema := range catalog .Builders {
189
+ builder , err := builderForSchema (schema , BuilderConfig {
190
+ ContainerCfg : ContainerConfig {
191
+ BaseImage : catalog .Destination .BaseImage ,
192
+ WorkingDir : catalog .Destination .WorkingDir ,
193
+ },
194
+ OutputType : outputType ,
195
+ })
196
+ if err != nil {
197
+ return nil , fmt .Errorf ("getting builder %q for catalog %q: %v" , schema , catalog .Name , err )
198
+ }
199
+ builderMap [schema ] = builder
200
+ }
201
+ catalogBuilderMap [catalog .Name ] = builderMap
202
+ }
203
+ }
204
+
205
+ // if there were errors validating the catalog configuration then exit
206
+ if setupFailed {
207
+ //build the error message
208
+ var errMsg string
209
+ for cat , errs := range setupErrors {
210
+ errMsg += fmt .Sprintf ("\n Catalog %v:\n " , cat )
211
+ for _ , err := range errs {
212
+ errMsg += fmt .Sprintf (" - %v\n " , err )
213
+ }
214
+ }
215
+ return nil , fmt .Errorf ("catalog configuration file field validation failed: %s" , errMsg )
216
+ }
217
+
218
+ return & catalogBuilderMap , nil
219
+ }
0 commit comments