Skip to content

Commit 505d8ea

Browse files
committedOct 23, 2019
Fix bundle library to add package and channels info to annotations
1. Package name, channels, default channel info is now added to annotations.yaml 2. `bundle generate` will retain overwritten ability while `bundle build` will only overwrite Dockerfile. 3. Validate annotations.yaml if existed. 4. `--overwrite/o` flag is available to overwrite annotations.yaml during build command. Signed-off-by: Vu Dinh <[email protected]>
1 parent d8d5ec2 commit 505d8ea

File tree

2 files changed

+155
-40
lines changed

2 files changed

+155
-40
lines changed
 

‎pkg/lib/bundle/build.go

+3-3
Original file line numberDiff line numberDiff line change
@@ -40,16 +40,16 @@ func ExecuteCommand(cmd *exec.Cmd) error {
4040
return nil
4141
}
4242

43-
func BuildFunc(directory, imageTag, imageBuilder string) error {
43+
func BuildFunc(directory, imageTag, imageBuilder, packageName, channels, channelDefault string, overwrite bool) error {
4444
// Generate annotations.yaml and Dockerfile
45-
err := GenerateFunc(directory)
45+
err := GenerateFunc(directory, packageName, channels, channelDefault, overwrite)
4646
if err != nil {
4747
return err
4848
}
4949

5050
// Build bundle image
5151
log.Info("Building bundle image")
52-
buildCmd, err := BuildBundleImage(path.Dir(path.Clean(directory)), imageBuilder, imageTag)
52+
buildCmd, err := BuildBundleImage(path.Clean(directory), imageBuilder, imageTag)
5353
if err != nil {
5454
return err
5555
}

‎pkg/lib/bundle/generate.go

+152-37
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ package bundle
33
import (
44
"fmt"
55
"io/ioutil"
6-
"path"
6+
"os"
77
"path/filepath"
88
"strings"
99

@@ -13,27 +13,30 @@ import (
1313
)
1414

1515
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"
2530
)
2631

2732
type AnnotationMetadata struct {
28-
Annotations AnnotationType `yaml:"annotations"`
33+
Annotations map[string]string `yaml:"annotations"`
2934
}
3035

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 {
3740
var mediaType string
3841

3942
// Determine mediaType
@@ -42,33 +45,48 @@ func GenerateFunc(directory string) error {
4245
return err
4346
}
4447

45-
// Parent directory
46-
parentDir := path.Dir(path.Clean(directory))
47-
48-
log.Info("Building annotations.yaml file")
48+
log.Info("Building annotations.yaml")
4949

5050
// Generate annotations.yaml
51-
content, err := GenerateAnnotations(manifestsMetadata, mediaType)
51+
content, err := GenerateAnnotations(mediaType, manifestsDir, metadataDir, packageName, channels, channelDefault)
5252
if err != nil {
5353
return err
5454
}
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 {
5763
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+
}
5869
}
5970

6071
log.Info("Building Dockerfile")
6172

6273
// 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)
6580
if err != nil {
6681
return err
6782
}
6883

6984
return nil
7085
}
7186

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.
7290
func GetMediaType(directory string) (string, error) {
7391
var files []string
7492

@@ -100,14 +118,97 @@ func GetMediaType(directory string) (string, error) {
100118
return plainType, nil
101119
}
102120

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) {
104194
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,
108202
},
109203
}
110204

205+
chanDefault, err := ValidateChannelDefault(channels, channelDefault)
206+
if err != nil {
207+
return nil, err
208+
}
209+
210+
annotations.Annotations[channelDefaultLabel] = chanDefault
211+
111212
afile, err := yaml.Marshal(annotations)
112213
if err != nil {
113214
return nil, err
@@ -116,28 +217,42 @@ func GenerateAnnotations(resourcesType, mediaType string) ([]byte, error) {
116217
return afile, nil
117218
}
118219

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) {
120224
var fileContent string
121225

122-
metadataDir := path.Dir(path.Clean(directory))
226+
chanDefault, err := ValidateChannelDefault(channels, channelDefault)
227+
if err != nil {
228+
return nil, err
229+
}
123230

124231
// FROM
125232
fileContent += "FROM scratch\n\n"
126233

127234
// 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)
130241

131242
// 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)
134245

135-
return []byte(fileContent)
246+
return []byte(fileContent), nil
136247
}
137248

138249
// Write `fileName` file with `content` into a `directory`
139250
// Note: Will overwrite the existing `fileName` file if it exists
140251
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+
141256
err := ioutil.WriteFile(filepath.Join(directory, fileName), content, defaultPermission)
142257
if err != nil {
143258
return err

0 commit comments

Comments
 (0)
Please sign in to comment.