@@ -3,7 +3,7 @@ package bundle
3
3
import (
4
4
"fmt"
5
5
"io/ioutil"
6
- "path "
6
+ "os "
7
7
"path/filepath"
8
8
"strings"
9
9
@@ -13,27 +13,30 @@ import (
13
13
)
14
14
15
15
const (
16
- defaultPermission = 0644
17
- registryV1Type = "registry+v1"
18
- plainType = "plain"
19
- helmType = "helm"
20
- manifestsMetadata = "manifests+metadata"
21
- annotationsFile = "annotations.yaml"
22
- dockerFile = "Dockerfile"
23
- resourcesLabel = "operators.operatorframework.io.bundle.resources"
24
- mediatypeLabel = "operators.operatorframework.io.bundle.mediatype"
16
+ defaultPermission = 0644
17
+ registryV1Type = "registry+v1"
18
+ plainType = "plain"
19
+ helmType = "helm"
20
+ annotationsFile = "annotations.yaml"
21
+ dockerFile = "Dockerfile"
22
+ manifestsDir = "manifests/"
23
+ metadataDir = "metadata/"
24
+ manifestsLabel = "operators.operatorframework.io.bundle.manifests.v1"
25
+ metadataLabel = "operators.operatorframework.io.bundle.metadata.v1"
26
+ mediatypeLabel = "operators.operatorframework.io.bundle.mediatype.v1"
27
+ packageLabel = "operators.operatorframework.io.bundle.package.v1"
28
+ channelsLabel = "operators.operatorframework.io.bundle.channels.v1"
29
+ channelDefaultLabel = "operators.operatorframework.io.bundle.channel.default.v1"
25
30
)
26
31
27
32
type AnnotationMetadata struct {
28
- Annotations AnnotationType `yaml:"annotations"`
33
+ Annotations map [ string ] string `yaml:"annotations"`
29
34
}
30
35
31
- type AnnotationType struct {
32
- Resources string `yaml:"operators.operatorframework.io.bundle.resources"`
33
- MediaType string `yaml:"operators.operatorframework.io.bundle.mediatype"`
34
- }
35
-
36
- func GenerateFunc (directory string ) error {
36
+ // GenerateFunc builds annotations.yaml with mediatype, manifests &
37
+ // metadata directories in bundle image, package name, channels and default
38
+ // channels information and then writes the file to `/metadata` directory.
39
+ func GenerateFunc (directory , packageName , channels , channelDefault string , overwrite bool ) error {
37
40
var mediaType string
38
41
39
42
// Determine mediaType
@@ -42,33 +45,48 @@ func GenerateFunc(directory string) error {
42
45
return err
43
46
}
44
47
45
- // Parent directory
46
- parentDir := path .Dir (path .Clean (directory ))
47
-
48
- log .Info ("Building annotations.yaml file" )
48
+ log .Info ("Building annotations.yaml" )
49
49
50
50
// Generate annotations.yaml
51
- content , err := GenerateAnnotations (manifestsMetadata , mediaType )
51
+ content , err := GenerateAnnotations (mediaType , manifestsDir , metadataDir , packageName , channels , channelDefault )
52
52
if err != nil {
53
53
return err
54
54
}
55
- err = WriteFile (annotationsFile , parentDir , content )
56
- if err != nil {
55
+
56
+ file , err := ioutil .ReadFile (filepath .Join (directory , metadataDir , annotationsFile ))
57
+ if os .IsNotExist (err ) || overwrite {
58
+ err = WriteFile (annotationsFile , filepath .Join (directory , metadataDir ), content )
59
+ if err != nil {
60
+ return err
61
+ }
62
+ } else if err != nil {
57
63
return err
64
+ } else {
65
+ log .Info ("An annotations.yaml already exists in directory" )
66
+ if err = ValidateAnnotations (file , content ); err != nil {
67
+ return err
68
+ }
58
69
}
59
70
60
71
log .Info ("Building Dockerfile" )
61
72
62
73
// Generate Dockerfile
63
- content = GenerateDockerfile (manifestsMetadata , mediaType , directory )
64
- err = WriteFile (dockerFile , parentDir , content )
74
+ content , err = GenerateDockerfile (directory , mediaType , manifestsDir , metadataDir , packageName , channels , channelDefault )
75
+ if err != nil {
76
+ return err
77
+ }
78
+
79
+ err = WriteFile (dockerFile , directory , content )
65
80
if err != nil {
66
81
return err
67
82
}
68
83
69
84
return nil
70
85
}
71
86
87
+ // GenerateFunc determines mediatype from files (yaml) in given directory
88
+ // Currently able to detect helm chart, registry+v1 (CSV) and plain k8s resources
89
+ // such as CRD.
72
90
func GetMediaType (directory string ) (string , error ) {
73
91
var files []string
74
92
@@ -100,14 +118,97 @@ func GetMediaType(directory string) (string, error) {
100
118
return plainType , nil
101
119
}
102
120
103
- func GenerateAnnotations (resourcesType , mediaType string ) ([]byte , error ) {
121
+ // ValidateAnnotations validates existing annotations.yaml against generated
122
+ // annotations.yaml to ensure existing annotations.yaml contains expected values.
123
+ func ValidateAnnotations (existing , expected []byte ) error {
124
+ var fileAnnotations AnnotationMetadata
125
+ var expectedAnnotations AnnotationMetadata
126
+
127
+ log .Info ("Validating existing annotations.yaml" )
128
+
129
+ err := yaml .Unmarshal (existing , & fileAnnotations )
130
+ if err != nil {
131
+ log .Errorf ("Unable to parse existing annotations.yaml" )
132
+ return err
133
+ }
134
+
135
+ err = yaml .Unmarshal (expected , & expectedAnnotations )
136
+ if err != nil {
137
+ log .Errorf ("Unable to parse expected annotations.yaml" )
138
+ return err
139
+ }
140
+
141
+ if len (fileAnnotations .Annotations ) != len (expectedAnnotations .Annotations ) {
142
+ return fmt .Errorf ("Unmatched number of fields. Expected (%d) vs existing (%d)" ,
143
+ len (expectedAnnotations .Annotations ), len (fileAnnotations .Annotations ))
144
+ }
145
+
146
+ for label , item := range expectedAnnotations .Annotations {
147
+ value , ok := fileAnnotations .Annotations [label ]
148
+ if ok == false {
149
+ return fmt .Errorf ("Missing field: %s" , label )
150
+ }
151
+
152
+ if item != value {
153
+ return fmt .Errorf (`Expect field "%s" to have value "%s" instead of "%s"` ,
154
+ label , item , value )
155
+ }
156
+ }
157
+
158
+ return nil
159
+ }
160
+
161
+ // ValidateAnnotations validates provided default channel to ensure it exists in
162
+ // provided channel list.
163
+ func ValidateChannelDefault (channels , channelDefault string ) (string , error ) {
164
+ var chanDefault string
165
+ var chanErr error
166
+ channelList := strings .Split (channels , "," )
167
+
168
+ if channelDefault != "" {
169
+ for _ , channel := range channelList {
170
+ if channel == channelDefault {
171
+ chanDefault = channelDefault
172
+ break
173
+ }
174
+ }
175
+ if chanDefault == "" {
176
+ chanDefault = channelList [0 ]
177
+ chanErr = fmt .Errorf (`The channel list "%s" doesn't contain channelDefault "%s"` , channels , channelDefault )
178
+ }
179
+ } else {
180
+ chanDefault = channelList [0 ]
181
+ }
182
+
183
+ if chanDefault != "" {
184
+ return chanDefault , chanErr
185
+ } else {
186
+ return chanDefault , fmt .Errorf ("Invalid channels is provied: %s" , channels )
187
+ }
188
+ }
189
+
190
+ // GenerateAnnotations builds annotations.yaml with mediatype, manifests &
191
+ // metadata directories in bundle image, package name, channels and default
192
+ // channels information.
193
+ func GenerateAnnotations (mediaType , manifests , metadata , packageName , channels , channelDefault string ) ([]byte , error ) {
104
194
annotations := & AnnotationMetadata {
105
- Annotations : AnnotationType {
106
- Resources : resourcesType ,
107
- MediaType : mediaType ,
195
+ Annotations : map [string ]string {
196
+ mediatypeLabel : mediaType ,
197
+ manifestsLabel : manifests ,
198
+ metadataLabel : metadata ,
199
+ packageLabel : packageName ,
200
+ channelsLabel : channels ,
201
+ channelDefaultLabel : channelDefault ,
108
202
},
109
203
}
110
204
205
+ chanDefault , err := ValidateChannelDefault (channels , channelDefault )
206
+ if err != nil {
207
+ return nil , err
208
+ }
209
+
210
+ annotations .Annotations [channelDefaultLabel ] = chanDefault
211
+
111
212
afile , err := yaml .Marshal (annotations )
112
213
if err != nil {
113
214
return nil , err
@@ -116,28 +217,42 @@ func GenerateAnnotations(resourcesType, mediaType string) ([]byte, error) {
116
217
return afile , nil
117
218
}
118
219
119
- func GenerateDockerfile (resourcesType , mediaType , directory string ) []byte {
220
+ // GenerateDockerfile builds Dockerfile with mediatype, manifests &
221
+ // metadata directories in bundle image, package name, channels and default
222
+ // channels information in LABEL section.
223
+ func GenerateDockerfile (directory , mediaType , manifests , metadata , packageName , channels , channelDefault string ) ([]byte , error ) {
120
224
var fileContent string
121
225
122
- metadataDir := path .Dir (path .Clean (directory ))
226
+ chanDefault , err := ValidateChannelDefault (channels , channelDefault )
227
+ if err != nil {
228
+ return nil , err
229
+ }
123
230
124
231
// FROM
125
232
fileContent += "FROM scratch\n \n "
126
233
127
234
// LABEL
128
- fileContent += fmt .Sprintf ("LABEL %s=%s\n " , resourcesLabel , resourcesType )
129
- fileContent += fmt .Sprintf ("LABEL %s=%s\n \n " , mediatypeLabel , mediaType )
235
+ fileContent += fmt .Sprintf ("LABEL %s=%s\n " , mediatypeLabel , mediaType )
236
+ fileContent += fmt .Sprintf ("LABEL %s=%s\n " , manifestsLabel , manifests )
237
+ fileContent += fmt .Sprintf ("LABEL %s=%s\n " , metadataLabel , metadata )
238
+ fileContent += fmt .Sprintf ("LABEL %s=%s\n " , packageLabel , packageName )
239
+ fileContent += fmt .Sprintf ("LABEL %s=%s\n " , channelsLabel , channels )
240
+ fileContent += fmt .Sprintf ("LABEL %s=%s\n \n " , channelDefaultLabel , chanDefault )
130
241
131
242
// CONTENT
132
- fileContent += fmt .Sprintf ("ADD %s %s\n " , directory , "/manifests" )
133
- fileContent += fmt .Sprintf ("ADD %s/%s %s%s\n " , metadataDir , annotationsFile , "/metadata/" , annotationsFile )
243
+ fileContent += fmt .Sprintf ("ADD %s %s\n " , filepath . Join ( directory , "*.yaml" ) , "/manifests" )
244
+ fileContent += fmt .Sprintf ("ADD %s %s%s\n " , filepath . Join ( directory , metadata , annotationsFile ) , "/metadata/" , annotationsFile )
134
245
135
- return []byte (fileContent )
246
+ return []byte (fileContent ), nil
136
247
}
137
248
138
249
// Write `fileName` file with `content` into a `directory`
139
250
// Note: Will overwrite the existing `fileName` file if it exists
140
251
func WriteFile (fileName , directory string , content []byte ) error {
252
+ if _ , err := os .Stat (directory ); os .IsNotExist (err ) {
253
+ os .Mkdir (directory , os .ModePerm )
254
+ }
255
+
141
256
err := ioutil .WriteFile (filepath .Join (directory , fileName ), content , defaultPermission )
142
257
if err != nil {
143
258
return err
0 commit comments