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 }