github.com/ouraigua/jenkins-library@v0.0.0-20231028010029-fbeaf2f3aa9b/pkg/cloudfoundry/ManifestUtils.go (about) 1 package cloudfoundry 2 3 import ( 4 "fmt" 5 "os" 6 "reflect" 7 8 "github.com/ghodss/yaml" 9 "github.com/pkg/errors" 10 11 "github.com/SAP/jenkins-library/pkg/log" 12 ) 13 14 const constPropApplications = "applications" 15 const constPropBuildpacks = "buildpacks" 16 const constPropBuildpack = "buildpack" 17 18 // Manifest ... 19 type Manifest interface { 20 GetFileName() string 21 GetAppName(index int) (string, error) 22 ApplicationHasProperty(index int, name string) (bool, error) 23 GetApplicationProperty(index int, name string) (interface{}, error) 24 Transform() error 25 IsModified() bool 26 GetApplications() ([]map[string]interface{}, error) 27 WriteManifest() error 28 } 29 30 // manifest ... 31 type manifest struct { 32 self map[string]interface{} 33 modified bool 34 name string 35 } 36 37 var _readFile = os.ReadFile 38 var _writeFile = os.WriteFile 39 40 // ReadManifest Reads the manifest denoted by 'name' 41 func ReadManifest(name string) (Manifest, error) { 42 43 log.Entry().Infof("Reading manifest file '%s'", name) 44 45 m := &manifest{self: make(map[string]interface{}), name: name, modified: false} 46 47 content, err := _readFile(name) 48 if err != nil { 49 return m, errors.Wrapf(err, "cannot read file '%v'", m.name) 50 } 51 52 err = yaml.Unmarshal(content, &m.self) 53 if err != nil { 54 return m, errors.Wrapf(err, "Cannot parse yaml file '%s': %s", m.name, string(content)) 55 } 56 57 log.Entry().Infof("Manifest file '%s' has been parsed", m.name) 58 59 return m, nil 60 } 61 62 // WriteManifest Writes the manifest to the file denoted 63 // by the name property (GetFileName()). The modified flag is 64 // resetted after the write operation. 65 func (m *manifest) WriteManifest() error { 66 67 d, err := yaml.Marshal(&m.self) 68 if err != nil { 69 return err 70 } 71 72 log.Entry().Debugf("Writing manifest file '%s'", m.GetFileName()) 73 err = _writeFile(m.GetFileName(), d, 0644) 74 75 if err == nil { 76 m.modified = false 77 } 78 79 log.Entry().Debugf("Manifest file '%s' has been written", m.name) 80 return err 81 } 82 83 // GetFileName returns the file name of the manifest. 84 func (m *manifest) GetFileName() string { 85 return m.name 86 } 87 88 // GetApplications Returns all applications denoted in the manifest file. 89 // The applications are returned as a slice of maps. Each app is represented by 90 // a map. 91 func (m *manifest) GetApplications() ([]map[string]interface{}, error) { 92 apps, err := toSlice(m.self["applications"]) 93 if err != nil { 94 return nil, err 95 } 96 97 result := make([]map[string]interface{}, 0) 98 99 for _, app := range apps { 100 if _app, ok := app.(map[string]interface{}); ok { 101 result = append(result, _app) 102 } else { 103 return nil, fmt.Errorf("Cannot cast applications to map. Manifest file '%s' has invalid format", m.GetFileName()) 104 } 105 } 106 return result, nil 107 } 108 109 // ApplicationHasProperty Checks if the application denoted by 'index' has the property 'name' 110 func (m *manifest) ApplicationHasProperty(index int, name string) (bool, error) { 111 112 sliced, err := toSlice(m.self[constPropApplications]) 113 if err != nil { 114 return false, err 115 } 116 117 if index >= len(sliced) { 118 return false, fmt.Errorf("Index (%d) out of bound. Number of apps: %d", index, len(sliced)) 119 } 120 121 _m, err := toMap(sliced[index]) 122 if err != nil { 123 return false, err 124 } 125 126 _, ok := _m[name] 127 128 return ok, nil 129 } 130 131 // GetApplicationProperty ... 132 func (m *manifest) GetApplicationProperty(index int, name string) (interface{}, error) { 133 134 sliced, err := toSlice(m.self[constPropApplications]) 135 if err != nil { 136 return nil, err 137 } 138 139 if index >= len(sliced) { 140 return nil, fmt.Errorf("Index (%d) out of bound. Number of apps: %d", index, len(sliced)) 141 } 142 143 app, err := toMap(sliced[index]) 144 if err != nil { 145 return nil, err 146 } 147 148 value, exists := app[name] 149 if exists { 150 return value, nil 151 } 152 153 return nil, fmt.Errorf("No such property: '%s' available in application at position %d", name, index) 154 } 155 156 // GetAppName Gets the name of the app at 'index' 157 func (m *manifest) GetAppName(index int) (string, error) { 158 159 appName, err := m.GetApplicationProperty(index, "name") 160 if err != nil { 161 return "", err 162 } 163 164 if name, ok := appName.(string); ok { 165 return name, nil 166 } 167 168 return "", fmt.Errorf("Cannot retrieve application name for app at index %d", index) 169 } 170 171 // Transform For each app in the manifest the first entry in the build packs list 172 // gets moved to the top level under the key 'buildpack'. The 'buildpacks' list is 173 // deleted. 174 func (m *manifest) Transform() error { 175 176 sliced, err := toSlice(m.self[constPropApplications]) 177 if err != nil { 178 return err 179 } 180 181 for _, app := range sliced { 182 appAsMap, err := toMap(app) 183 if err != nil { 184 return err 185 } 186 187 err = transformApp(appAsMap, m) 188 if err != nil { 189 return err 190 } 191 } 192 193 return nil 194 } 195 196 func transformApp(app map[string]interface{}, m *manifest) error { 197 198 appName := "n/a" 199 200 if name, ok := app["name"].(string); ok { 201 if len(name) > 0 { 202 appName = name 203 } 204 } 205 206 if app[constPropBuildpacks] == nil { 207 // Revisit: not sure if a build pack is mandatory. 208 // In that case we should check that app.buildpack 209 // is present. 210 return nil 211 } 212 213 buildPacks, err := toSlice(app[constPropBuildpacks]) 214 if err != nil { 215 return err 216 } 217 218 if len(buildPacks) > 1 { 219 return fmt.Errorf("More than one Cloud Foundry Buildpack is not supported. Please check manifest file '%s', application '%s'", m.name, appName) 220 } 221 222 if len(buildPacks) == 1 { 223 app[constPropBuildpack] = buildPacks[0] 224 delete(app, constPropBuildpacks) 225 m.modified = true 226 } 227 228 return nil 229 } 230 231 // IsModified ... 232 func (m *manifest) IsModified() bool { 233 return m.modified 234 } 235 236 func toMap(i interface{}) (map[string]interface{}, error) { 237 238 if m, ok := i.(map[string]interface{}); ok { 239 return m, nil 240 } 241 return nil, fmt.Errorf("Failed to convert %v to map. Was %v", i, reflect.TypeOf(i)) 242 } 243 244 func toSlice(i interface{}) ([]interface{}, error) { 245 246 if s, ok := i.([]interface{}); ok { 247 return s, nil 248 } 249 return nil, fmt.Errorf("Failed to convert %v to slice. Was %v", i, reflect.TypeOf(i)) 250 }