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  }