github.com/SAP/cloud-mta-build-tool@v1.2.27/internal/artifacts/manifest.go (about) 1 package artifacts 2 3 import ( 4 "fmt" 5 "io" 6 "os" 7 "path/filepath" 8 "strings" 9 "text/template" 10 11 "github.com/pkg/errors" 12 13 "github.com/SAP/cloud-mta/mta" 14 15 "github.com/SAP/cloud-mta-build-tool/internal/archive" 16 "github.com/SAP/cloud-mta-build-tool/internal/buildops" 17 "github.com/SAP/cloud-mta-build-tool/internal/commands" 18 "github.com/SAP/cloud-mta-build-tool/internal/conttype" 19 "github.com/SAP/cloud-mta-build-tool/internal/tpl" 20 "github.com/SAP/cloud-mta-build-tool/internal/version" 21 ) 22 23 // The deployment descriptor should be located within the META-INF folder of the JAR. 24 // The MANIFEST.MF file should contain at least a name section for each MTA module contained in the archive. 25 // Following the JAR specification, the value of a name must be a relative path to a file or directory, 26 // or an absolute URL referencing data outside the archive. 27 // It is required to add a row MTA-module: <modulename> to each name section that corresponds to an MTA module, and 28 // to bind archive file locations to module names as used in the deployment descriptor. 29 // The name sections with the MTA module attribute indicate the path to the file or directory which represents a module within the archive 30 // This is used by the deploy service to track the build project. 31 32 const ( 33 moduleEntry = "MTA-Module" 34 requiredEntry = "MTA-Requires" 35 resourceEntry = "MTA-Resource" 36 dirContentType = "text/directory" 37 ) 38 39 type entry struct { 40 EntryName string 41 EntryType string 42 ContentType string 43 EntryPath string 44 } 45 46 // setManifestDesc - Set the MANIFEST.MF file 47 func setManifestDesc(source dir.IModule, ep dir.ITargetArtifacts, targetPathGetter dir.ITargetPath, depDesc bool, mtaStr []*mta.Module, 48 mtaResources []*mta.Resource, platform string) error { 49 50 contentTypes, err := conttype.GetContentTypes() 51 if err != nil { 52 return errors.Wrap(err, contentTypeCfgMsg) 53 } 54 55 entries, err := getModulesEntries(source, targetPathGetter, depDesc, mtaStr, contentTypes, platform) 56 if err != nil { 57 return err 58 } 59 60 resourcesEntries, err := getResourcesEntries(targetPathGetter, mtaResources, contentTypes) 61 if err != nil { 62 return err 63 } 64 entries = append(entries, resourcesEntries...) 65 66 // Module entries that point to the same path should be merged in the manifest 67 entries = mergeDuplicateEntries(entries) 68 69 return genManifest(ep.GetManifestPath(), entries) 70 } 71 72 func mergeDuplicateEntries(entries []entry) []entry { 73 // Several MTA-Module entries can point to the same path. In that case, their names should in the same entry, comma-separated. 74 mergedEntries := make([]entry, 0) 75 modules := make(map[string]entry) 76 // To keep a consistent sort order for the map entries we must keep another data structure (slice of keys by order of addition here) 77 pathsOrder := make([]string, 0) 78 79 // Add module entries to modules. Add non-module entries to mergedEntries. 80 for index, entry := range entries { 81 if entry.EntryType == moduleEntry { 82 if existing, ok := modules[entry.EntryPath]; ok { 83 existing.EntryName += ", " + entry.EntryName 84 modules[entry.EntryPath] = existing 85 } else { 86 modules[entry.EntryPath] = entries[index] 87 pathsOrder = append(pathsOrder, entry.EntryPath) 88 } 89 } else { 90 mergedEntries = append(mergedEntries, entry) 91 } 92 } 93 94 // Sort module entries by order of insertion 95 moduleEntries := make([]entry, 0) 96 for _, path := range pathsOrder { 97 moduleEntries = append(moduleEntries, modules[path]) 98 } 99 100 // Add the module entries first to the merged entries 101 mergedEntries = append(moduleEntries, mergedEntries...) 102 return mergedEntries 103 } 104 105 func addModuleEntry(entries []entry, module *mta.Module, contentType, modulePath string) []entry { 106 result := entries 107 108 if modulePath != "" { 109 moduleEntry := entry{ 110 EntryName: module.Name, 111 EntryPath: filepath.ToSlash(modulePath), 112 ContentType: contentType, 113 EntryType: moduleEntry, 114 } 115 result = append(entries, moduleEntry) 116 } 117 return result 118 } 119 120 func getModulesEntries(source dir.IModule, targetPathGetter dir.ITargetPath, depDesc bool, moduleList []*mta.Module, 121 contentTypes *conttype.ContentTypes, platform string) ([]entry, error) { 122 123 var entries []entry 124 for _, mod := range moduleList { 125 if !buildops.IfNoSource(mod) && buildops.PlatformDefined(mod, platform) { 126 _, defaultBuildResult, err := commands.CommandProvider(*mod) 127 if err != nil { 128 return nil, err 129 } 130 modulePath, _, err := buildops.GetModuleTargetArtifactPath(source, depDesc, mod, defaultBuildResult, true) 131 if modulePath != "" && err == nil { 132 _, err = os.Stat(modulePath) 133 } 134 135 if err != nil { 136 return nil, errors.Wrapf(err, wrongArtifactPathMsg, mod.Name) 137 } 138 139 if modulePath != "" { 140 contentType, err1 := getContentType(modulePath, contentTypes) 141 if err1 != nil { 142 return nil, errors.Wrapf(err1, unknownModuleContentTypeMsg, mod.Name) 143 } 144 145 // get relative path of the module entry (excluding leading slash) 146 moduleEntryPath := strings.Replace(modulePath, targetPathGetter.GetTargetTmpDir(), "", 1)[1:] 147 entries = addModuleEntry(entries, mod, contentType, moduleEntryPath) 148 } 149 } 150 151 requiredDependenciesWithPath := getRequiredDependencies(mod) 152 requiredDependencyEntries, err := buildEntries(targetPathGetter, mod, requiredDependenciesWithPath, contentTypes) 153 if err != nil { 154 return nil, errors.Wrapf(err, requiredEntriesProblemMsg, mod.Name) 155 } 156 entries = append(entries, requiredDependencyEntries...) 157 } 158 return entries, nil 159 } 160 161 func getResourcesEntries(target dir.ITargetPath, resources []*mta.Resource, contentTypes *conttype.ContentTypes) ([]entry, error) { 162 var entries []entry 163 for _, resource := range resources { 164 if resource.Name == "" || resource.Parameters["path"] == nil { 165 continue 166 } 167 resourceRelativePath := getResourcePath(resource) 168 contentType, err := getContentType(filepath.Join(target.GetTargetTmpDir(), resourceRelativePath), contentTypes) 169 if err != nil { 170 return nil, errors.Wrapf(err, unknownResourceContentTypeMsg, resource.Name) 171 } 172 resourceEntry := entry{ 173 EntryName: resource.Name, 174 EntryPath: filepath.ToSlash(resourceRelativePath), 175 ContentType: contentType, 176 EntryType: resourceEntry, 177 } 178 entries = append(entries, resourceEntry) 179 } 180 return entries, nil 181 } 182 183 func buildEntries(target dir.ITargetPath, module *mta.Module, requiredDependencies []mta.Requires, contentTypes *conttype.ContentTypes) ([]entry, error) { 184 result := make([]entry, 0) 185 for _, requiredDependency := range requiredDependencies { 186 depPath := requiredDependency.Parameters["path"].(string) 187 contentType, err := getContentType(filepath.Join(target.GetTargetTmpDir(), depPath), contentTypes) 188 if err != nil { 189 return nil, err 190 } 191 requiredDependencyEntry := entry{ 192 EntryName: module.Name + "/" + requiredDependency.Name, 193 EntryPath: filepath.ToSlash(filepath.Clean(depPath)), 194 ContentType: contentType, 195 EntryType: requiredEntry, 196 } 197 result = append(result, requiredDependencyEntry) 198 } 199 return result, nil 200 } 201 202 func getContentType(path string, contentTypes *conttype.ContentTypes) (string, error) { 203 info, err := os.Stat(path) 204 if err != nil { 205 return "", fmt.Errorf(contentTypeDefMsg, path) 206 } 207 208 if info.IsDir() { 209 return dirContentType, nil 210 } 211 212 extension := filepath.Ext(path) 213 return conttype.GetContentType(contentTypes, extension) 214 } 215 216 func getRequiredDependencies(module *mta.Module) []mta.Requires { 217 result := make([]mta.Requires, 0) 218 for _, requiredDependency := range module.Requires { 219 if requiredDependency.Parameters["path"] != nil && requiredDependency.Name != "" { 220 result = append(result, requiredDependency) 221 } 222 } 223 return result 224 } 225 226 func getResourcePath(resource *mta.Resource) string { 227 return filepath.Clean(resource.Parameters["path"].(string)) 228 } 229 230 func genManifest(manifestPath string, entries []entry) (rerr error) { 231 232 v, err := version.GetVersion() 233 if err != nil { 234 return errors.Wrap(err, cliVersionMsg) 235 } 236 237 funcMap := template.FuncMap{ 238 "Entries": entries, 239 "CliVersion": v.CliVersion, 240 } 241 out, err := os.Create(manifestPath) 242 defer func() { 243 rerr = dir.CloseFile(out, rerr) 244 }() 245 if err != nil { 246 return errors.Wrap(err, initMsg) 247 } 248 return populateManifest(out, funcMap) 249 } 250 251 func populateManifest(file io.Writer, funcMap template.FuncMap) error { 252 t := template.Must(template.New("template").Parse(string(tpl.Manifest))) 253 err := t.Execute(file, funcMap) 254 if err != nil { 255 return errors.Wrap(err, populationMsg) 256 } 257 258 return nil 259 } 260 261 // moduleDefined - checks if module defined in the list 262 func moduleDefined(module string, modules []string) bool { 263 if len(modules) == 0 { 264 return true 265 } 266 for _, m := range modules { 267 if m == module { 268 return true 269 } 270 } 271 return false 272 }