github.com/sap/cf-mta-plugin@v2.6.3+incompatible/util/archive_builder.go (about)

     1  package util
     2  
     3  import (
     4  	"fmt"
     5  	"io"
     6  	"io/ioutil"
     7  	"os"
     8  	"path"
     9  	"path/filepath"
    10  	"strings"
    11  
    12  	"github.com/cloudfoundry-incubator/multiapps-cli-plugin/ui"
    13  )
    14  
    15  const deploymentDescriptorYamlName string = "mtad.yaml"
    16  
    17  // MtaArchiveBuilder builds mta archive
    18  type MtaArchiveBuilder struct {
    19  	modules   []string
    20  	resources []string
    21  }
    22  
    23  // NewMtaArchiveBuilder constructs new MtaArchiveBuilder
    24  func NewMtaArchiveBuilder(modules, resources []string) MtaArchiveBuilder {
    25  	return MtaArchiveBuilder{
    26  		modules:   modules,
    27  		resources: resources,
    28  	}
    29  }
    30  
    31  // Build creates deployment archive from the provided deployment descriptor
    32  func (builder MtaArchiveBuilder) Build(deploymentDescriptorLocation string) (string, error) {
    33  	descriptor, deploymentDescriptorFile, err := ParseDeploymentDescriptor(deploymentDescriptorLocation)
    34  	if err != nil {
    35  		return "", err
    36  	}
    37  
    38  	modulesPaths, err := builder.getModulesPaths(descriptor.Modules)
    39  	if err != nil {
    40  		return "", fmt.Errorf("Error building MTA Archive: %s", err.Error())
    41  	}
    42  	resourcesPaths, err := builder.getResourcesPaths(descriptor.Resources)
    43  	if err != nil {
    44  		return "", fmt.Errorf("Error building MTA Archive: %s", err.Error())
    45  	}
    46  	bindingParametersPaths := builder.getBindingParametersPaths(descriptor.Modules)
    47  
    48  	modulesSections := buildSection(normalizePathsUsingUnixSeparator(modulesPaths), MtaModule)
    49  	resourcesSections := buildSection(normalizePathsUsingUnixSeparator(resourcesPaths), MtaResource)
    50  	bindingParametersSections := buildSection(normalizePathsUsingUnixSeparator(bindingParametersPaths), MtaRequires)
    51  
    52  	manifestBuilder := NewMtaManifestBuilder()
    53  	manifestBuilder.ManifestSections(modulesSections)
    54  	manifestBuilder.ManifestSections(resourcesSections)
    55  	manifestBuilder.ManifestSections(bindingParametersSections)
    56  	manifestBuilder.ManifestSections([]ManifestSection{NewMtaManifestSectionBuilder().Name(MtadAttribute).Build()})
    57  
    58  	manifestLocation, err := manifestBuilder.Build()
    59  	if err != nil {
    60  		return "", err
    61  	}
    62  	defer os.Remove(manifestLocation)
    63  
    64  	mtaAssembly, err := ioutil.TempDir("", "mta-assembly")
    65  	if err != nil {
    66  		return "", err
    67  	}
    68  	defer os.RemoveAll(mtaAssembly)
    69  
    70  	metaInfLocation := filepath.Join(mtaAssembly, "META-INF")
    71  	err = os.Mkdir(metaInfLocation, os.ModePerm)
    72  	if err != nil {
    73  		return "", err
    74  	}
    75  
    76  	manifestInfo, err := os.Stat(manifestLocation)
    77  	if err != nil {
    78  		return "", err
    79  	}
    80  
    81  	err = copyFile(manifestLocation, filepath.Join(metaInfLocation, manifestInfo.Name()))
    82  	if err != nil {
    83  		return "", err
    84  	}
    85  
    86  	err = copyFile(deploymentDescriptorFile, filepath.Join(metaInfLocation, "mtad.yaml"))
    87  	if err != nil {
    88  		return "", err
    89  	}
    90  	// TODO: modify the deployment descriptor after copying it in order not to contain any path parameters...
    91  
    92  	err = copyContent(deploymentDescriptorLocation, modulesPaths, mtaAssembly)
    93  	if err != nil {
    94  		return "", err
    95  	}
    96  
    97  	err = copyContent(deploymentDescriptorLocation, resourcesPaths, mtaAssembly)
    98  	if err != nil {
    99  		return "", err
   100  	}
   101  
   102  	err = copyContent(deploymentDescriptorLocation, bindingParametersPaths, mtaAssembly)
   103  	if err != nil {
   104  		return "", err
   105  	}
   106  
   107  	mtaArchiveName := descriptor.ID + ".mtar"
   108  	mtaArchiveLocation := filepath.Join(deploymentDescriptorLocation, mtaArchiveName)
   109  	err = CreateMtaArchive(mtaAssembly, mtaArchiveLocation)
   110  	if err != nil {
   111  		return "", err
   112  	}
   113  
   114  	mtaArchiveAbsolutePath, err := filepath.Abs(mtaArchiveLocation)
   115  	if err != nil {
   116  		return "", err
   117  	}
   118  
   119  	return mtaArchiveAbsolutePath, nil
   120  }
   121  
   122  func copyContent(sourceDirectory string, elementsPaths map[string]string, targetLocation string) error {
   123  	for name, path := range elementsPaths {
   124  		if path != "" {
   125  			sourceLocation := filepath.Join(sourceDirectory, path)
   126  			filesInSourceInfo, err := os.Stat(sourceLocation)
   127  			if err != nil {
   128  				return fmt.Errorf("Error building MTA Archive: file path %s not found", sourceLocation)
   129  			}
   130  			destinationLocation := filepath.Join(targetLocation, name, filepath.Base(path))
   131  			if filesInSourceInfo.IsDir() {
   132  				err = copyDirectory(sourceLocation, destinationLocation)
   133  			} else {
   134  				os.MkdirAll(filepath.Dir(destinationLocation), os.ModePerm)
   135  				err = copyFile(sourceLocation, destinationLocation)
   136  			}
   137  			if err != nil {
   138  				return err
   139  			}
   140  		}
   141  	}
   142  	return nil
   143  }
   144  
   145  func copyDirectory(src, dest string) error {
   146  	var err error
   147  	var filesInDestinationInfo []os.FileInfo
   148  	var sourceInfo os.FileInfo
   149  
   150  	if sourceInfo, err = os.Stat(src); err != nil {
   151  		return err
   152  	}
   153  
   154  	if err = os.MkdirAll(dest, sourceInfo.Mode()); err != nil {
   155  		return err
   156  	}
   157  
   158  	if filesInDestinationInfo, err = ioutil.ReadDir(src); err != nil {
   159  		return err
   160  	}
   161  	for _, fd := range filesInDestinationInfo {
   162  		srcfp := path.Join(src, fd.Name())
   163  		dstfp := path.Join(dest, fd.Name())
   164  
   165  		if fd.IsDir() {
   166  			if err = copyDirectory(srcfp, dstfp); err != nil {
   167  				return err
   168  			}
   169  		} else {
   170  			if err = copyFile(srcfp, dstfp); err != nil {
   171  				return err
   172  			}
   173  		}
   174  	}
   175  	return nil
   176  }
   177  
   178  func copyFile(src, dest string) error {
   179  	fileFrom, err := os.Open(src)
   180  	if err != nil {
   181  		return err
   182  	}
   183  	defer fileFrom.Close()
   184  
   185  	toFile, err := os.OpenFile(dest, os.O_RDWR|os.O_CREATE, 0666)
   186  	if err != nil {
   187  		return err
   188  	}
   189  	defer toFile.Close()
   190  
   191  	_, err = io.Copy(toFile, fileFrom)
   192  	if err != nil {
   193  		return err
   194  	}
   195  
   196  	return nil
   197  }
   198  
   199  func buildSection(elements map[string]string, locatorName string) []ManifestSection {
   200  	result := make([]ManifestSection, 0)
   201  	for key, value := range concatenateElementsWithSameValue(elements) {
   202  		manifestSectionBuilder := NewMtaManifestSectionBuilder()
   203  		manifestSectionBuilder.Name(key)
   204  		manifestSectionBuilder.Attribute(locatorName, strings.Join(value, ","))
   205  		result = append(result, manifestSectionBuilder.Build())
   206  	}
   207  	return result
   208  }
   209  
   210  func concatenateElementsWithSameValue(elements map[string]string) map[string][]string {
   211  	result := make(map[string][]string)
   212  	for key, value := range elements {
   213  		if value == "" {
   214  			continue
   215  		}
   216  		if len(result[value]) != 0 {
   217  			result[value] = append(result[value], key)
   218  		} else {
   219  			result[value] = []string{key}
   220  		}
   221  	}
   222  	return result
   223  }
   224  
   225  func (builder MtaArchiveBuilder) getBindingParametersPaths(deploymentDescriptorResources []Module) map[string]string {
   226  	result := make(map[string]string, 0)
   227  	modulesToAdd := filterModules(deploymentDescriptorResources, func(module Module) bool {
   228  		return Contains(builder.modules, module.Name)
   229  	})
   230  	for _, module := range modulesToAdd {
   231  		requiredDependenciesConfigPaths := getRequiredDependenciesConfigPaths(module.RequiredDependencies)
   232  		for requiredDependencyName, configFile := range requiredDependenciesConfigPaths {
   233  			result[module.Name+"/"+requiredDependencyName] = configFile
   234  		}
   235  	}
   236  	return result
   237  }
   238  
   239  func getRequiredDependenciesConfigPaths(requiredDependencies []RequiredDependency) map[string]string {
   240  	result := make(map[string]string, 0)
   241  	for _, requiredDependency := range requiredDependencies {
   242  		if requiredDependency.Parameters["path"] != nil {
   243  			result[requiredDependency.Name] = getString(requiredDependency.Parameters["path"])
   244  		}
   245  	}
   246  	return result
   247  }
   248  
   249  func (builder MtaArchiveBuilder) getResourcesPaths(deploymentDescriptorResources []Resource) (map[string]string, error) {
   250  	err := validateSpecifiedResources(builder.resources, deploymentDescriptorResources)
   251  	if err != nil {
   252  		return nil, err
   253  	}
   254  	result := make(map[string]string)
   255  	resourcesToAdd := filterResources(deploymentDescriptorResources, func(resource Resource) bool {
   256  		return Contains(builder.resources, resource.Name)
   257  	})
   258  
   259  	for _, resource := range resourcesToAdd {
   260  		result[resource.Name] = getString(resource.Parameters["path"])
   261  	}
   262  	return result, nil
   263  }
   264  
   265  func validateSpecifiedResources(resourcesForDeployment []string, deploymentDescriptorResources []Resource) error {
   266  	deploymentDescriptorResourceNames := make([]string, 0)
   267  	for _, deploymentDescriptorResource := range deploymentDescriptorResources {
   268  		deploymentDescriptorResourceNames = append(deploymentDescriptorResourceNames, deploymentDescriptorResource.Name)
   269  	}
   270  	specifiedResourcesNotPartOfDeploymentDescriptor := make([]string, 0)
   271  	for _, resourceForDeployment := range resourcesForDeployment {
   272  		if !Contains(deploymentDescriptorResourceNames, resourceForDeployment) {
   273  			specifiedResourcesNotPartOfDeploymentDescriptor = append(specifiedResourcesNotPartOfDeploymentDescriptor, resourceForDeployment)
   274  		}
   275  	}
   276  
   277  	if len(specifiedResourcesNotPartOfDeploymentDescriptor) == 0 {
   278  		return nil
   279  	}
   280  
   281  	return fmt.Errorf("Resources %s are specified for deployment but are not part of deployment descriptor resources", strings.Join(specifiedResourcesNotPartOfDeploymentDescriptor, ", "))
   282  }
   283  
   284  func getString(value interface{}) string {
   285  	if value == nil {
   286  		return ""
   287  	}
   288  	return value.(string)
   289  }
   290  
   291  func (builder MtaArchiveBuilder) getModulesPaths(deploymentDescriptorResources []Module) (map[string]string, error) {
   292  	err := validateSpecifiedModules(builder.modules, deploymentDescriptorResources)
   293  	if err != nil {
   294  		return nil, err
   295  	}
   296  	modulesToAdd := filterModules(deploymentDescriptorResources, func(module Module) bool {
   297  		return Contains(builder.modules, module.Name)
   298  	})
   299  	specifiedModulesWithoutPaths := filterModules(modulesToAdd, func(module Module) bool {
   300  		return module.Path == ""
   301  	})
   302  	moduleNamesWithoutPaths := make([]string, 0)
   303  	for _, moduleWithoutPath := range specifiedModulesWithoutPaths {
   304  		moduleNamesWithoutPaths = append(moduleNamesWithoutPaths, moduleWithoutPath.Name)
   305  	}
   306  	if len(moduleNamesWithoutPaths) > 0 {
   307  		ui.Warn("Modules %s do not have a path, specified for their binaries and will be ignored", strings.Join(moduleNamesWithoutPaths, ", "))
   308  	}
   309  	result := make(map[string]string)
   310  	for _, moduleToAdd := range modulesToAdd {
   311  		if moduleToAdd.Path != "" {
   312  			result[moduleToAdd.Name] = moduleToAdd.Path
   313  		}
   314  	}
   315  
   316  	return result, nil
   317  }
   318  
   319  func normalizePathsUsingUnixSeparator(elementsPaths map[string]string) map[string]string {
   320  	normalizedPaths := make(map[string]string, len(elementsPaths))
   321  	for key, value := range elementsPaths {
   322  		if value != "" {
   323  			zipEntryNormalizedPath := fmt.Sprintf("%s/%s", key, filepath.Base(value))
   324  			normalizedPaths[key] = zipEntryNormalizedPath
   325  		}
   326  	}
   327  	return normalizedPaths
   328  }
   329  
   330  func validateSpecifiedModules(modulesForDeployment []string, deploymentDescriptorResources []Module) error {
   331  	deploymentDescriptorResourceNames := make([]string, 0)
   332  	for _, deploymentDescriptorResource := range deploymentDescriptorResources {
   333  		deploymentDescriptorResourceNames = append(deploymentDescriptorResourceNames, deploymentDescriptorResource.Name)
   334  	}
   335  	specifiedResourcesNotPartOfDeploymentDescriptor := make([]string, 0)
   336  	for _, moduleForDeployment := range modulesForDeployment {
   337  		if !Contains(deploymentDescriptorResourceNames, moduleForDeployment) {
   338  			specifiedResourcesNotPartOfDeploymentDescriptor = append(specifiedResourcesNotPartOfDeploymentDescriptor, moduleForDeployment)
   339  		}
   340  	}
   341  
   342  	if len(specifiedResourcesNotPartOfDeploymentDescriptor) == 0 {
   343  		return nil
   344  	}
   345  
   346  	return fmt.Errorf("Modules %s are specified for deployment but are not part of deployment descriptor modules", strings.Join(specifiedResourcesNotPartOfDeploymentDescriptor, ", "))
   347  }
   348  
   349  func filterModules(modulesSlice []Module, prediacte func(element Module) bool) []Module {
   350  	result := make([]Module, 0)
   351  	for _, sliceElement := range modulesSlice {
   352  		if prediacte(sliceElement) {
   353  			result = append(result, sliceElement)
   354  		}
   355  	}
   356  	return result
   357  }
   358  
   359  func filterResources(resourcesSlice []Resource, predicate func(element Resource) bool) []Resource {
   360  	result := make([]Resource, 0)
   361  	for _, sliceElement := range resourcesSlice {
   362  		if predicate(sliceElement) {
   363  			result = append(result, sliceElement)
   364  		}
   365  	}
   366  	return result
   367  }
   368  
   369  func Contains(slice []string, element string) bool {
   370  	for _, sliceElement := range slice {
   371  		if sliceElement == element {
   372  			return true
   373  		}
   374  	}
   375  	return false
   376  }