github.com/haagen/force@v0.19.6-0.20140911230915-22addd930b34/push.go (about)

     1  package main
     2  
     3  import (
     4  	"encoding/xml"
     5  	"fmt"
     6  	"io/ioutil"
     7  	"os"
     8  	"path/filepath"
     9  	"strings"
    10  )
    11  
    12  var cmdPush = &Command{
    13  	Run:   runPush,
    14  	Usage: "push (<metadata> <name> | <file>...)",
    15  	Short: "Deploy artifact from a local directory",
    16  	Long: `
    17  Deploy artifact from a local directory
    18  <metadata>: Accepts either actual directory name or Metadata type
    19  
    20  Examples:
    21    force push classes MyClass
    22    force push ApexClass MyClass
    23    force push src/classes/MyClass.cls
    24  `,
    25  }
    26  
    27  // Structs for XML building
    28  type Package struct {
    29  	Xmlns   string     `xml:"xmlns,attr"`
    30  	Types   []MetaType `xml:"types"`
    31  	Version string     `xml:"version"`
    32  }
    33  
    34  type MetaType struct {
    35  	Members []string `xml:"members"`
    36  	Name    string   `xml:"name"`
    37  }
    38  
    39  func createPackage() Package {
    40  	return Package{
    41  		Version: strings.TrimPrefix(apiVersion, "v"),
    42  		Xmlns:   "http://soap.sforce.com/2006/04/metadata",
    43  	}
    44  }
    45  
    46  type metapath struct {
    47  	path string
    48  	name string
    49  }
    50  
    51  var metapaths = []metapath{
    52  	metapath{"classes", "ApexClass"},
    53  	metapath{"objects", "CustomObject"},
    54  	metapath{"tabs", "CustomTab"},
    55  	metapath{"labels", "CustomLabels"},
    56  	metapath{"flexipages", "FlexiPage"},
    57  	metapath{"components", "ApexComponent"},
    58  	metapath{"triggers", "ApexTrigger"},
    59  	metapath{"pages", "ApexPage"},
    60  }
    61  
    62  var namePaths = make(map[string]string)
    63  var byName = false
    64  
    65  func getPathForMeta(metaname string) string {
    66  	for _, mp := range metapaths {
    67  		if strings.EqualFold(mp.name, metaname) {
    68  			return mp.path
    69  		}
    70  	}
    71  
    72  	// Unknown, so use metaname
    73  	return metaname
    74  }
    75  
    76  func getMetaForPath(path string) string {
    77  	for _, mp := range metapaths {
    78  		if mp.path == path {
    79  			return mp.name
    80  		}
    81  	}
    82  
    83  	// Unknown, so use path
    84  	return path
    85  }
    86  
    87  func argIsFile(fpath string) bool {
    88  	if _, err := os.Stat(fpath); err != nil {
    89  		return false
    90  	}
    91  	return true
    92  }
    93  
    94  func runPush(cmd *Command, args []string) {
    95  	if len(args) == 0 {
    96  		cmd.printUsage()
    97  		return
    98  	}
    99  
   100  	if argIsFile(args[0]) {
   101  		pushByPaths(args)
   102  		return
   103  	}
   104  
   105  	if len(args) == 2 {
   106  		// If arg[0] is already path or meta, the method will return arg[0]
   107  		objPath := getPathForMeta(args[0])
   108  		objName := args[1]
   109  		pushByName(objPath, objName)
   110  		return
   111  	}
   112  
   113  	fmt.Println("Could not find file or determine metadata")
   114  
   115  	// If we got here, something is not valid
   116  	cmd.printUsage()
   117  }
   118  
   119  func pushByName(objPath string, objName string) {
   120  	wd, _ := os.Getwd()
   121  	byName = true
   122  
   123  	// First try for metadata directory
   124  	root := filepath.Join(wd, "metadata")
   125  	if _, err := os.Stat(filepath.Join(root, "package.xml")); os.IsNotExist(err) {
   126  		// If not found, try for src directory
   127  		//root = filepath.Join(wd, "src")
   128  		if _, err := os.Stat(filepath.Join(wd, "src", "package.xml")); os.IsNotExist(err) {
   129  			//ErrorAndExit("Current directory must contain a src or metadata directory")
   130  		} else {
   131  			root = filepath.Join(wd, "src")
   132  		}
   133  	}
   134  	if _, err := os.Stat(filepath.Join(root, objPath)); os.IsNotExist(err) {
   135  		ErrorAndExit("Folder " + objPath + " not found, must specify valid metadata")
   136  	}
   137  
   138  	// Find file by walking directory and ignoring extension
   139  	found := false
   140  	var fpath string
   141  	err := filepath.Walk(filepath.Join(root, objPath), func(path string, f os.FileInfo, err error) error {
   142  		if f.Mode().IsRegular() {
   143  			fname := strings.TrimSuffix(f.Name(), filepath.Ext(f.Name()))
   144  			if strings.EqualFold(fname, objName) {
   145  				found = true
   146  				fpath = filepath.Join(root, objPath, f.Name())
   147  			}
   148  		}
   149  		return nil
   150  	})
   151  	if err != nil {
   152  		ErrorAndExit(err.Error())
   153  	}
   154  	if !found {
   155  		ErrorAndExit("Could not find " + objName + " in " + objPath)
   156  	}
   157  
   158  	pushByPath(fpath)
   159  }
   160  
   161  func pushByPath(fpath string) {
   162  	pushByPaths([]string{fpath})
   163  }
   164  
   165  // Push metadata object by path to a file
   166  func pushByPaths(fpaths []string) {
   167  	files := make(ForceMetadataFiles)
   168  	xmlMap := make(map[string][]string)
   169  
   170  	for _, fpath := range fpaths {
   171  		name := addFile(files, xmlMap, fpath)
   172  		// Store paths by name for error messages
   173  		namePaths[name] = fpath
   174  	}
   175  
   176  	files["package.xml"] = buildXml(xmlMap)
   177  
   178  	deployFiles(files)
   179  }
   180  
   181  func addFile(files ForceMetadataFiles, xmlMap map[string][]string, fpath string) string {
   182  	fpath, err := filepath.Abs(fpath)
   183  	if err != nil {
   184  		ErrorAndExit("Cound not find " + fpath)
   185  	}
   186  	if _, err := os.Stat(fpath); err != nil {
   187  		ErrorAndExit("Cound not open " + fpath)
   188  	}
   189  
   190  	hasMeta := true
   191  	fname := filepath.Base(fpath)
   192  	fname = strings.TrimSuffix(fname, filepath.Ext(fname))
   193  	fdir := filepath.Dir(fpath)
   194  	typePath := filepath.Base(fdir)
   195  	srcDir := filepath.Dir(fdir)
   196  	metaType := getMetaForPath(typePath)
   197  	// Should be present since we worked back to srcDir
   198  	frel, _ := filepath.Rel(srcDir, fpath)
   199  
   200  	// Try to find meta file
   201  	fmeta := fpath + "-meta.xml"
   202  	fmetarel := ""
   203  	if _, err := os.Stat(fmeta); err != nil {
   204  		if os.IsNotExist(err) {
   205  			hasMeta = false
   206  		} else {
   207  			ErrorAndExit("Cound not open " + fmeta)
   208  		}
   209  	} else {
   210  		// Should be present since we worked back to srcDir
   211  		fmetarel, _ = filepath.Rel(srcDir, fmeta)
   212  	}
   213  
   214  	xmlMap[metaType] = append(xmlMap[metaType], fname)
   215  
   216  	fdata, err := ioutil.ReadFile(fpath)
   217  	files[frel] = fdata
   218  	if hasMeta {
   219  		fdata, err = ioutil.ReadFile(fmeta)
   220  		files[fmetarel] = fdata
   221  	}
   222  
   223  	return fname
   224  }
   225  
   226  func buildXml(xmlMap map[string][]string) []byte {
   227  	p := createPackage()
   228  
   229  	for metaType, members := range xmlMap {
   230  		t := MetaType{Name: metaType}
   231  		for _, member := range members {
   232  			t.Members = append(t.Members, member)
   233  		}
   234  		p.Types = append(p.Types, t)
   235  	}
   236  
   237  	byteXml, _ := xml.MarshalIndent(p, "", "    ")
   238  	byteXml = append([]byte(xml.Header), byteXml...)
   239  	return byteXml
   240  }
   241  
   242  func deployFiles(files ForceMetadataFiles) {
   243  	force, _ := ActiveForce()
   244  	var DeploymentOptions ForceDeployOptions
   245  	successes, problems, err := force.Metadata.Deploy(files, DeploymentOptions)
   246  	if err != nil {
   247  		ErrorAndExit(err.Error())
   248  	}
   249  	fmt.Printf("\nFailures - %d\n", len(problems))
   250  	for _, problem := range problems {
   251  		if problem.FullName == "" {
   252  			fmt.Println(problem.Problem)
   253  		} else {
   254  			if byName {
   255  				fmt.Printf("ERROR with %s, line %d\n %s\n", problem.FullName, problem.LineNumber, problem.Problem)
   256  			} else {
   257  				fname, found := namePaths[problem.FullName]
   258  				if !found {
   259  					fname = problem.FullName
   260  				}
   261  				fmt.Printf("\"%s\", line %d: %s %s\n", fname, problem.LineNumber, problem.ProblemType, problem.Problem)
   262  			}
   263  		}
   264  	}
   265  
   266  	fmt.Printf("\nSuccesses - %d\n", len(successes)-1)
   267  	for _, success := range successes {
   268  		if success.FullName != "package.xml" {
   269  			verb := "unchanged"
   270  			if success.Changed {
   271  				verb = "changed"
   272  			} else if success.Deleted {
   273  				verb = "deleted"
   274  			} else if success.Created {
   275  				verb = "created"
   276  			}
   277  			fmt.Printf("%s\n\tstatus: %s\n\tid=%s\n", success.FullName, verb, success.Id)
   278  		}
   279  	}
   280  
   281  	// Handle notifications
   282  	notifySuccess("push", len(problems) == 0)
   283  }