github.com/haagen/force@v0.19.6-0.20140911230915-22addd930b34/metadata.go (about) 1 package main 2 3 import ( 4 "archive/zip" 5 "bitbucket.org/pkg/inflect" 6 "bufio" 7 "bytes" 8 "encoding/base64" 9 "encoding/xml" 10 "errors" 11 "fmt" 12 "io/ioutil" 13 "os" 14 "path/filepath" 15 "reflect" 16 "strconv" 17 "strings" 18 "time" 19 ) 20 21 type ForceConnectedApps []ForceConnectedApp 22 23 type ForceConnectedApp struct { 24 Name string `xml:"fullName"` 25 Id string `xml:"id"` 26 Type string `xml:"type"` 27 } 28 29 type ComponentFailure struct { 30 Changed bool `xml:"changed"` 31 Created bool `xml:"created"` 32 Deleted bool `xml:"deleted"` 33 FileName string `xml:"fileName"` 34 FullName string `xml:"fullName"` 35 LineNumber int `xml:"lineNumber"` 36 Problem string `xml:"problem"` 37 ProblemType string `xml:"problemType"` 38 Success bool `xml:"success"` 39 } 40 41 type ComponentSuccess struct { 42 Changed bool `xml:"changed"` 43 Created bool `xml:"created"` 44 Deleted bool `xml:"deleted"` 45 FileName string `xml:"fileName"` 46 FullName string `xml:"fullName"` 47 Id string `xml:"id"` 48 Success bool `xml:"success"` 49 } 50 51 type RunTestResult struct { 52 NumberOfFailures int `xml:"numFailures"` 53 NumberOfTestsRun int `xml:"numTestsRun"` 54 TotalTime int `xml:"totalTime"` 55 } 56 57 type ComponentDetails struct { 58 ComponentSuccesses []ComponentSuccess `xml:"componentSuccesses"` 59 ComponentFailures []ComponentFailure `xml:"componentFailures"` 60 } 61 62 type ForceCheckDeploymentStatusResult struct { 63 CheckOnly bool `xml:"checkOnly"` 64 CompletedDate time.Time `xml:"completedDate"` 65 CreatedDate time.Time `xml:"createdDate"` 66 Details ComponentDetails `xml:"details"` 67 Done bool `xml:"done"` 68 Id string `xml:"id"` 69 NumberComponentErrors int `xml:"numberComponentErrors"` 70 NumberComponentsDeployed int `xml:"numberComponentsDeployed"` 71 NumberComponentsTotal int `xml:"numberComponentsTotal"` 72 NumberTestErrors int `xml:"numberTestErrors"` 73 NumberTestsCompleted int `xml:"numberTestsCompleted"` 74 NumberTestsTotal int `xml:"numberTestsTotal"` 75 RollbackOnError bool `xml:"rollbackOnError"` 76 Status string `xml:"status"` 77 Success bool `xml:"success"` 78 } 79 80 type ForceMetadataDeployProblem struct { 81 Changed bool `xml:"changed"` 82 Created bool `xml:"created"` 83 Deleted bool `xml:"deleted"` 84 Filename string `xml:"fileName"` 85 Name string `xml:"fullName"` 86 Problem string `xml:"problem"` 87 ProblemType string `xml:"problemType"` 88 Success bool `xml:"success"` 89 } 90 91 type ForceMetadataQueryElement struct { 92 Name string 93 Members string 94 } 95 96 type ForceMetadataQuery []ForceMetadataQueryElement 97 98 type ForceMetadataFiles map[string][]byte 99 100 type ForceMetadata struct { 101 ApiVersion string 102 Force *Force 103 } 104 105 type ForceDeployOptions struct { 106 AllowMissingFiles bool `xml:"allowMissingFiles"` 107 AutoUpdatePackage bool `xml:"autoUpdatePackage"` 108 CheckOnly bool `xml:"checkOnly"` 109 IgnoreWarnings bool `xml:"ignoreWarnings"` 110 PerformRetrieve bool `xml:"performRetrieve"` 111 PurgeOnDelete bool `xml:"purgeOnDelete"` 112 RollbackOnError bool `xml:"rollbackOnError"` 113 RunAllTests bool `xml:"runAllTests"` 114 runTests []string `xml:"runTests"` 115 SinglePackage bool `xml:"singlePackage"` 116 } 117 118 /* These structs define which options are available and which are 119 required for the various field types you can create. Reflection 120 is used to leverage these structs in validating options when creating 121 a custom field. 122 */ 123 type GeolocationFieldRequired struct { 124 DisplayLocationInDecimal bool `xml:"displayLocationInDecimal"` 125 Scale int `xml:"scale"` 126 } 127 128 type GeolocationField struct { 129 DsiplayLocationInDecimal bool `xml:"displayLocationInDecimal"` 130 Required bool `xml:"required"` 131 Scale int `xml:"scale"` 132 Description string `xml:"description"` 133 HelpText string `xml:"helpText"` 134 } 135 136 type AutoNumberFieldRequired struct { 137 StartingNumber int `xml:"startingNumber"` 138 DisplayFormat string `xml:"displayFormat"` 139 } 140 141 type AutoNumberField struct { 142 StartingNumber int `xml:"startingNumber"` 143 DisplayFormat string `xml:"displayFormat"` 144 Description string `xml:"description"` 145 HelpText string `xml:"helpText"` 146 ExternalId bool `xml:"externalId"` 147 } 148 149 type FloatFieldRequired struct { 150 Precision int `xml:"precision"` 151 Scale int `xml:"scale"` 152 } 153 154 type FloatField struct { 155 Length int `xml:"length"` 156 Description string `xml:"description"` 157 HelpText string `xml:"helpText"` 158 Unique bool `xml:"unique"` 159 ExternalId bool `xml:"externalId"` 160 DefaultValue uint `xml:"defaultValue"` 161 Precision int `xml:"precision"` 162 Scale int `xml:"scale"` 163 Formula string `xml:"formula"` 164 FormulaTreatBlanksAs string `xml:"formulaTreatBlanksAs"` 165 } 166 167 type NumberFieldRequired struct { 168 Precision int `xml:"precision"` 169 Scale int `xml:"scale"` 170 } 171 172 type NumberField struct { 173 Length int `xml:"length"` 174 Description string `xml:"description"` 175 HelpText string `xml:"helpText"` 176 Unique bool `xml:"unique"` 177 ExternalId bool `xml:"externalId"` 178 DefaultValue uint `xml:"defaultValue"` 179 Formula string `xml:"formula"` 180 FormulaTreatBlanksAs string `xml:"formulaTreatBlanksAs"` 181 } 182 183 type DatetimeFieldRequired struct { 184 } 185 186 type DatetimeField struct { 187 Description string `xml:"description"` 188 HelpText string `xml:"helpText"` 189 DefaultValue time.Time `xml:"defaultValue"` 190 Required bool `xml:"required"` 191 Formula string `xml:"formula"` 192 FormulaTreatBlanksAs string `xml:"formulaTreatBlanksAs"` 193 } 194 195 type BoolFieldRequired struct { 196 DefaultValue bool `xml:"defaultValue"` 197 } 198 199 type BoolField struct { 200 Description string `xml:"description"` 201 HelpText string `xml:"helpText"` 202 DefaultValue bool `xml:"defaultValue"` 203 Formula string `xml:"formula"` 204 FormulaTreatBlanksAs string `xml:"formulaTreatBlanksAs"` 205 } 206 207 type DescribeMetadataObject struct { 208 ChildXmlNames []string `xml:"childXmlNames"` 209 DirectoryName string `xml:"directoryName"` 210 InFolder bool `xml:"inFolder"` 211 MetaFile bool `xml:"metaFile"` 212 Suffix string `xml:"suffix"` 213 XmlName string `xml:"xmlName"` 214 } 215 216 type MetadataDescribeResult struct { 217 NamespacePrefix string `xml:"organizationNamespace"` 218 PartialSaveAllowed bool `xml:"partialSaveAllowed"` 219 TestRequired bool `xml:"testRequired"` 220 MetadataObjects []DescribeMetadataObject `xml:"metadataObjects"` 221 } 222 223 type EncryptedFieldRequired struct { 224 Length int `xml:"length"` 225 MaskType string `xml:"maskType"` 226 MaskChar string `xml:"maskChar"` 227 } 228 229 type EncryptedField struct { 230 Label string `xml:"label"` 231 Name string `xml:"fullName"` 232 Required bool `xml:"required"` 233 Length int `xml:"length"` 234 Description string `xml:"description"` 235 HelpText string `xml:"helpText"` 236 MaskType string `xml:"maskType"` 237 MaskChar string `xml:"maskChar"` 238 } 239 240 type StringFieldRequired struct { 241 Length int `xml:"length"` 242 } 243 244 type StringField struct { 245 Label string `xml:"label"` 246 Name string `xml:"fullName"` 247 Required bool `xml:"required"` 248 Length int `xml:"length"` 249 Description string `xml:"description"` 250 HelpText string `xml:"helpText"` 251 Unique bool `xml:"unique"` 252 CaseSensitive bool `xml:"caseSensitive"` 253 ExternalId bool `xml:"externalId"` 254 DefaultValue string `xml:"defaultValue"` 255 Formula string `xml:"formula"` 256 FormulaTreatBlanksAs string `xml:"formulaTreatBlanksAs"` 257 } 258 259 type PhoneFieldRequired struct { 260 } 261 262 type PhoneField struct { 263 Label string `xml:"label"` 264 Name string `xml:"fullName"` 265 Required bool `xml:"required"` 266 Description string `xml:"description"` 267 HelpText string `xml:"helpText"` 268 DefaultValue string `xml:"defaultValue"` 269 } 270 271 type EmailFieldRequired struct { 272 } 273 274 type TextAreaFieldRequired struct { 275 } 276 277 type TextAreaField struct { 278 Label string `xml:"label"` 279 Name string `xml:"fullName"` 280 Required bool `xml:"required"` 281 Description string `xml:"description"` 282 HelpText string `xml:"helpText"` 283 DefaultValue string `xml:"defaultValue"` 284 } 285 286 type LongTextAreaFieldRequired struct { 287 Length int `xml:"length"` 288 VisibleLines int `xml:"visibleLines"` 289 } 290 291 type LongTextAreaField struct { 292 Label string `xml:"label"` 293 Name string `xml:"fullName"` 294 Required bool `xml:"required"` 295 Description string `xml:"description"` 296 HelpText string `xml:"helpText"` 297 DefaultValue string `xml:"defaultValue"` 298 Length int `xml:"length"` 299 VisibleLines int `xml:"visibleLines"` 300 } 301 302 type RichTextAreaFieldRequired struct { 303 Length int `xml:"length"` 304 VisibleLines int `xml:"visibleLines"` 305 } 306 307 type RichTextAreaField struct { 308 Label string `xml:"label"` 309 Name string `xml:"fullName"` 310 Required bool `xml:"required"` 311 Description string `xml:"description"` 312 HelpText string `xml:"helpText"` 313 Length int `xml:"length"` 314 VisibleLines int `xml:"visibleLines"` 315 } 316 317 type LookupFieldRequired struct{} 318 319 type LookupField struct { 320 ReferenceTo string `xml:"referenceTo"` 321 RelationshipLabel string `xml:"relationshipLabel"` 322 RelationshipName string `xml:"relationshipName"` 323 } 324 325 type MasterDetailRequired struct{} 326 327 type MasterDetail struct { 328 ReferenceTo string `xml:"referenceTo"` 329 RelationshipLabel string `xml:"relationshipLabel"` 330 RelationshipName string `xml:"relationshipName"` 331 } 332 333 // Example of how to use Go's reflection 334 // Print the attributes of a Data Model 335 func getAttributes(m interface{}) map[string]reflect.StructField { 336 typ := reflect.TypeOf(m) 337 // if a pointer to a struct is passed, get the type of the dereferenced object 338 if typ.Kind() == reflect.Ptr { 339 typ = typ.Elem() 340 } 341 342 // create an attribute data structure as a map of types keyed by a string. 343 attrs := make(map[string]reflect.StructField) 344 // Only structs are supported so return an empty result if the passed object 345 // isn't a struct 346 if typ.Kind() != reflect.Struct { 347 fmt.Printf("%v type can't have attributes inspected\n", typ.Kind()) 348 return attrs 349 } 350 351 // loop through the struct's fields and set the map 352 for i := 0; i < typ.NumField(); i++ { 353 p := typ.Field(i) 354 if !p.Anonymous { 355 attrs[strings.ToLower(p.Name)] = p 356 } 357 } 358 359 return attrs 360 } 361 362 func ValidateOptionsAndDefaults(typ string, fields map[string]reflect.StructField, requiredDefaults reflect.Value, options map[string]string) (newOptions map[string]string, err error) { 363 newOptions = make(map[string]string) 364 365 // validate optional attributes 366 for name, value := range options { 367 field, ok := fields[strings.ToLower(name)] 368 if !ok { 369 ErrorAndExit(fmt.Sprintf("validation error: %s:%s is not a valid option for field type %s", name, value, typ)) 370 } else { 371 newOptions[field.Tag.Get("xml")] = options[name] 372 } 373 } 374 375 // validate required attributes 376 s := requiredDefaults 377 tod := s.Type() 378 for i := 0; i < s.NumField(); i++ { 379 _, ok := options[strings.ToLower(tod.Field(i).Name)] 380 if !ok { 381 switch s.Field(i).Type().Name() { 382 case "int": 383 newOptions[tod.Field(i).Tag.Get("xml")] = strconv.Itoa(s.Field(i).Interface().(int)) 384 break 385 case "bool": 386 if typ == "bool" { 387 if _, ok = options["formula"]; ok { 388 if tod.Field(i).Tag.Get("xml") == "defaultValue" { 389 break 390 } 391 } 392 } //else { 393 newOptions[tod.Field(i).Tag.Get("xml")] = strconv.FormatBool(s.Field(i).Interface().(bool)) 394 //} 395 break 396 case "string": 397 newOptions[tod.Field(i).Tag.Get("xml")] = s.Field(i).Interface().(string) 398 break 399 } 400 } else { 401 newOptions[tod.Field(i).Tag.Get("xml")] = options[strings.ToLower(tod.Field(i).Name)] 402 } 403 } 404 return newOptions, err 405 } 406 407 func (fm *ForceMetadata) ValidateFieldOptions(typ string, options map[string]string) (newOptions map[string]string, err error) { 408 409 newOptions = make(map[string]string) 410 var attrs map[string]reflect.StructField 411 var s reflect.Value 412 413 switch typ { 414 case "phone": 415 attrs = getAttributes(&PhoneField{}) 416 s = reflect.ValueOf(&PhoneFieldRequired{}).Elem() 417 break 418 case "email", "url": 419 attrs = getAttributes(&StringField{}) 420 s = reflect.ValueOf(&EmailFieldRequired{}).Elem() 421 break 422 case "encryptedtext": 423 attrs = getAttributes(&EncryptedField{}) 424 s = reflect.ValueOf(&EncryptedFieldRequired{175, "all", "asterisk"}).Elem() 425 break 426 case "string", "text": 427 attrs = getAttributes(&StringField{}) 428 if _, ok := options["formula"]; ok { 429 s = reflect.ValueOf(&StringFieldRequired{}).Elem() 430 } else { 431 s = reflect.ValueOf(&StringFieldRequired{255}).Elem() 432 } 433 break 434 case "textarea": 435 attrs = getAttributes(&TextAreaField{}) 436 s = reflect.ValueOf(&TextAreaFieldRequired{}).Elem() 437 break 438 case "longtextarea": 439 attrs = getAttributes(&LongTextAreaField{}) 440 s = reflect.ValueOf(&LongTextAreaFieldRequired{32768, 5}).Elem() 441 break 442 case "richtextarea": 443 attrs = getAttributes(&RichTextAreaField{}) 444 s = reflect.ValueOf(&RichTextAreaFieldRequired{32768, 5}).Elem() 445 break 446 case "bool", "boolean", "checkbox": 447 attrs = getAttributes(&BoolField{}) 448 if _, ok := options["formula"]; ok { 449 s = reflect.ValueOf(&BoolFieldRequired{}).Elem() 450 } else { 451 s = reflect.ValueOf(&BoolFieldRequired{false}).Elem() 452 } 453 break 454 case "datetime", "date": 455 attrs = getAttributes(&DatetimeField{}) 456 s = reflect.ValueOf(&DatetimeFieldRequired{}).Elem() 457 break 458 case "float", "double", "percent", "currency": 459 attrs = getAttributes(&FloatField{}) 460 s = reflect.ValueOf(&FloatFieldRequired{16, 2}).Elem() 461 break 462 case "number", "int": 463 attrs = getAttributes(&NumberField{}) 464 s = reflect.ValueOf(&NumberFieldRequired{18, 0}).Elem() 465 break 466 case "autonumber": 467 attrs = getAttributes(&AutoNumberField{}) 468 s = reflect.ValueOf(&AutoNumberFieldRequired{0, "AN-{00000}"}).Elem() 469 break 470 case "geolocation": 471 attrs = getAttributes(&GeolocationField{}) 472 s = reflect.ValueOf(&GeolocationFieldRequired{true, 5}).Elem() 473 break 474 case "lookup": 475 attrs = getAttributes(&LookupField{}) 476 s = reflect.ValueOf(&LookupFieldRequired{}).Elem() 477 break 478 case "masterdetail": 479 attrs = getAttributes(&MasterDetail{}) 480 s = reflect.ValueOf(&MasterDetailRequired{}).Elem() 481 break 482 default: 483 //ErrorAndExit(fmt.Sprintf("Field type %s is not implemented.", typ)) 484 break 485 } 486 487 newOptions, err = ValidateOptionsAndDefaults(typ, attrs, s, options) 488 489 return newOptions, nil 490 } 491 492 func NewForceMetadata(force *Force) (fm *ForceMetadata) { 493 fm = &ForceMetadata{ApiVersion: apiVersionNumber, Force: force} 494 return 495 } 496 497 func (fm *ForceMetadata) CheckStatus(id string) (err error) { 498 body, err := fm.soapExecute("checkStatus", fmt.Sprintf("<id>%s</id>", id)) 499 if err != nil { 500 return 501 } 502 var status struct { 503 Done bool `xml:"Body>checkStatusResponse>result>done"` 504 State string `xml:"Body>checkStatusResponse>result>state"` 505 Message string `xml:"Body>checkStatusResponse>result>message"` 506 } 507 if err = xml.Unmarshal(body, &status); err != nil { 508 return 509 } 510 switch { 511 case !status.Done: 512 return fm.CheckStatus(id) 513 case status.State == "Error": 514 return errors.New(status.Message) 515 } 516 return 517 } 518 519 func (fm *ForceMetadata) CheckDeployStatus(id string) (results ForceCheckDeploymentStatusResult, err error) { 520 body, err := fm.soapExecute("checkDeployStatus", fmt.Sprintf("<id>%s</id><includeDetails>true</includeDetails>", id)) 521 if err != nil { 522 return 523 } 524 525 var deployResult struct { 526 Results ForceCheckDeploymentStatusResult `xml:"Body>checkDeployStatusResponse>result"` 527 } 528 529 if err = xml.Unmarshal(body, &deployResult); err != nil { 530 ErrorAndExit(err.Error()) 531 } 532 533 results = deployResult.Results 534 return 535 } 536 537 func (fm *ForceMetadata) CheckRetrieveStatus(id string) (files ForceMetadataFiles, err error) { 538 body, err := fm.soapExecute("checkRetrieveStatus", fmt.Sprintf("<id>%s</id>", id)) 539 if err != nil { 540 return 541 } 542 var status struct { 543 ZipFile string `xml:"Body>checkRetrieveStatusResponse>result>zipFile"` 544 } 545 if err = xml.Unmarshal(body, &status); err != nil { 546 return 547 } 548 data, err := base64.StdEncoding.DecodeString(status.ZipFile) 549 if err != nil { 550 return 551 } 552 zipfiles, err := zip.NewReader(bytes.NewReader(data), int64(len(data))) 553 if err != nil { 554 return 555 } 556 files = make(map[string][]byte) 557 for _, file := range zipfiles.File { 558 fd, _ := file.Open() 559 defer fd.Close() 560 data, _ := ioutil.ReadAll(fd) 561 files[file.Name] = data 562 } 563 return 564 } 565 566 func (fm *ForceMetadata) DescribeMetadata() (describe MetadataDescribeResult, err error) { 567 body, err := fm.soapExecute("describeMetadata", fmt.Sprintf("<apiVersion>%s</apiVersion>", apiVersionNumber)) 568 if err != nil { 569 return 570 } 571 var result struct { 572 Data MetadataDescribeResult `xml:"Body>describeMetadataResponse>result"` 573 } 574 if err = xml.Unmarshal(body, &result); err != nil { // Let the calling method handle the error message 575 describe = result.Data 576 } 577 return 578 } 579 580 func (fm *ForceMetadata) CreateConnectedApp(name, callback string) (err error) { 581 soap := ` 582 <metadata xsi:type="ConnectedApp"> 583 <fullName>%s</fullName> 584 <version>%s</version> 585 <label>%s</label> 586 <contactEmail>%s</contactEmail> 587 <oauthConfig> 588 <callbackUrl>%s</callbackUrl> 589 <scopes>Full</scopes> 590 <scopes>RefreshToken</scopes> 591 </oauthConfig> 592 </metadata> 593 ` 594 me, err := fm.Force.Whoami() 595 if err != nil { 596 return err 597 } 598 email := me["Email"] 599 body, err := fm.soapExecute("create", fmt.Sprintf(soap, name, apiVersionNumber, name, email, callback)) 600 if err != nil { 601 return err 602 } 603 var status struct { 604 Id string `xml:"Body>createResponse>result>id"` 605 } 606 if err = xml.Unmarshal(body, &status); err != nil { 607 return 608 } 609 if err = fm.CheckStatus(status.Id); err != nil { 610 return 611 } 612 return 613 } 614 615 func (fm *ForceMetadata) CreateCustomField(object, field, typ string, options map[string]string) (err error) { 616 label := field 617 field = strings.Replace(field, " ", "_", -1) 618 soap := ` 619 <metadata xsi:type="CustomField" xmlns:cmd="http://soap.sforce.com/2006/04/metadata"> 620 <fullName>%s.%s__c</fullName> 621 <label>%s</label> 622 %s 623 </metadata> 624 ` 625 soapField := "" 626 switch strings.ToLower(typ) { 627 case "bool", "boolean", "checkbox": 628 soapField = `<type>Checkbox</type>` 629 for key, value := range options { 630 soapField += fmt.Sprintf("<%s>%s</%s>", key, value, key) 631 } 632 case "encryptedtext": 633 soapField = "<type>EncryptedText</type>" 634 for key, value := range options { 635 soapField += fmt.Sprintf("<%s>%s</%s>", key, value, key) 636 } 637 case "text", "string": 638 soapField = "<type>Text</type>" 639 for key, value := range options { 640 soapField += fmt.Sprintf("<%s>%s</%s>", key, value, key) 641 } 642 case "email": 643 soapField = "<type>Email</type>" 644 for key, value := range options { 645 soapField += fmt.Sprintf("<%s>%s</%s>", key, value, key) 646 } 647 case "url": 648 soapField = "<type>Url</type>" 649 for key, value := range options { 650 soapField += fmt.Sprintf("<%s>%s</%s>", key, value, key) 651 } 652 case "phone": 653 soapField = "<type>Phone</type>" 654 for key, value := range options { 655 soapField += fmt.Sprintf("<%s>%s</%s>", key, value, key) 656 } 657 case "date": 658 soapField = "<type>Date</type>" 659 for key, value := range options { 660 soapField += fmt.Sprintf("<%s>%s</%s>", key, value, key) 661 } 662 case "datetime": 663 soapField = "<type>DateTime</type>" 664 for key, value := range options { 665 soapField += fmt.Sprintf("<%s>%s</%s>", key, value, key) 666 } 667 case "number", "int": 668 soapField = "<type>Number</type>" 669 for key, value := range options { 670 soapField += fmt.Sprintf("<%s>%s</%s>", key, value, key) 671 } 672 case "percent": 673 soapField = "<type>Percent</type>" 674 for key, value := range options { 675 soapField += fmt.Sprintf("<%s>%s</%s>", key, value, key) 676 } 677 case "autonumber": 678 soapField = "<type>AutoNumber</type>" 679 for key, value := range options { 680 soapField += fmt.Sprintf("<%s>%s</%s>", key, value, key) 681 } 682 case "float", "double": 683 soapField = "<type>Number</type>" 684 for key, value := range options { 685 soapField += fmt.Sprintf("<%s>%s</%s>", key, value, key) 686 } 687 case "currency": 688 soapField = "<type>Currency</type>" 689 for key, value := range options { 690 soapField += fmt.Sprintf("<%s>%s</%s>", key, value, key) 691 } 692 case "geolocation": 693 soapField = "<type>Location</type>" 694 for key, value := range options { 695 soapField += fmt.Sprintf("<%s>%s</%s>", key, value, key) 696 } 697 case "lookup": 698 soapField = `<type>Lookup</type> 699 <referenceTo>%s</referenceTo> 700 <relationshipLabel>%ss</relationshipLabel> 701 <relationshipName>%s_del</relationshipName> 702 ` 703 scanner := bufio.NewScanner(os.Stdin) 704 705 var inp, inp2 string 706 fmt.Print("Enter object to lookup: ") 707 708 scanner.Scan() 709 inp = scanner.Text() 710 711 fmt.Print("What is the label for the loookup? ") 712 scanner.Scan() 713 inp2 = scanner.Text() 714 715 soapField = fmt.Sprintf(soapField, inp, inp2, strings.Replace(inp2, " ", "_", -1)) 716 case "masterdetail": 717 soapField = `<type>MasterDetail</type> 718 <externalId>false</externalId> 719 <referenceTo>%s</referenceTo> 720 <relationshipLabel>%ss</relationshipLabel> 721 <relationshipName>%s_del</relationshipName> 722 <relationshipOrder>0</relationshipOrder> 723 <reparentableMasterDetail>false</reparentableMasterDetail> 724 <trackTrending>false</trackTrending> 725 <writeRequiresMasterRead>false</writeRequiresMasterRead> 726 ` 727 728 scanner := bufio.NewScanner(os.Stdin) 729 var inp, inp2 string 730 fmt.Print("Enter object to lookup: ") 731 732 scanner.Scan() 733 inp = scanner.Text() 734 735 fmt.Print("What is the label for the loookup? ") 736 scanner.Scan() 737 inp2 = scanner.Text() 738 739 soapField = fmt.Sprintf(soapField, inp, inp2, strings.Replace(inp2, " ", "_", -1)) 740 case "textarea": 741 soapField = "<type>TextArea</type>" 742 for key, value := range options { 743 soapField += fmt.Sprintf("<%s>%s</%s>", key, value, key) 744 } 745 case "longtextarea": 746 soapField = "<type>LongTextArea</type>" 747 for key, value := range options { 748 soapField += fmt.Sprintf("<%s>%s</%s>", key, value, key) 749 } 750 case "richtextarea": 751 soapField = "<type>Html</type>" 752 for key, value := range options { 753 soapField += fmt.Sprintf("<%s>%s</%s>", key, value, key) 754 } 755 default: 756 ErrorAndExit("unable to create field type: %s", typ) 757 } 758 759 body, err := fm.soapExecute("create", fmt.Sprintf(soap, object, field, label, soapField)) 760 if err != nil { 761 return err 762 } 763 var status struct { 764 Id string `xml:"Body>createResponse>result>id"` 765 } 766 if err = xml.Unmarshal(body, &status); err != nil { 767 return 768 } 769 if err = fm.CheckStatus(status.Id); err != nil { 770 return 771 } 772 return 773 } 774 775 func (fm *ForceMetadata) DeleteCustomField(object, field string) (err error) { 776 soap := ` 777 <metadata xsi:type="CustomField" xmlns:cmd="http://soap.sforce.com/2006/04/metadata"> 778 <fullName>%s.%s</fullName> 779 </metadata> 780 ` 781 body, err := fm.soapExecute("delete", fmt.Sprintf(soap, object, field)) 782 if err != nil { 783 return err 784 } 785 var status struct { 786 Id string `xml:"Body>deleteResponse>result>id"` 787 } 788 if err = xml.Unmarshal(body, &status); err != nil { 789 return 790 } 791 if err = fm.CheckStatus(status.Id); err != nil { 792 return 793 } 794 return 795 } 796 797 func (fm *ForceMetadata) CreateCustomObject(object string) (err error) { 798 fld := "" 799 fld = strings.ToUpper(object) 800 fld = fld[0:1] 801 soap := ` 802 <metadata xsi:type="CustomObject" xmlns:cmd="http://soap.sforce.com/2006/04/metadata"> 803 <fullName>%s__c</fullName> 804 <label>%s</label> 805 <pluralLabel>%s</pluralLabel> 806 <deploymentStatus>Deployed</deploymentStatus> 807 <sharingModel>ReadWrite</sharingModel> 808 <nameField> 809 <label>%s Name</label> 810 <type>AutoNumber</type> 811 <displayFormat>%s-{00000}</displayFormat> 812 <startingNumber>1</startingNumber> 813 </nameField> 814 </metadata> 815 ` 816 body, err := fm.soapExecute("create", fmt.Sprintf(soap, object, object, inflect.Pluralize(object), object, fld)) 817 if err != nil { 818 return err 819 } 820 var status struct { 821 Id string `xml:"Body>createResponse>result>id"` 822 } 823 if err = xml.Unmarshal(body, &status); err != nil { 824 return 825 } 826 if err = fm.CheckStatus(status.Id); err != nil { 827 return 828 } 829 return 830 } 831 832 func (fm *ForceMetadata) DeleteCustomObject(object string) (err error) { 833 soap := ` 834 <metadata xsi:type="CustomObject" xmlns:cmd="http://soap.sforce.com/2006/04/metadata"> 835 <fullName>%s</fullName> 836 </metadata> 837 ` 838 body, err := fm.soapExecute("delete", fmt.Sprintf(soap, object)) 839 if err != nil { 840 return err 841 } 842 var status struct { 843 Id string `xml:"Body>deleteResponse>result>id"` 844 } 845 if err = xml.Unmarshal(body, &status); err != nil { 846 return 847 } 848 if err = fm.CheckStatus(status.Id); err != nil { 849 return 850 } 851 return 852 } 853 854 func (fm *ForceMetadata) Deploy(files ForceMetadataFiles, options ForceDeployOptions) (successes []ComponentSuccess, problems []ComponentFailure, err error) { 855 soap := ` 856 <zipFile>%s</zipFile> 857 <deployOptions> 858 <allowMissingFiles>%t</allowMissingFiles> 859 <autoUpdatePackage>%t</autoUpdatePackage> 860 <checkOnly>%t</checkOnly> 861 <ignoreWarnings>%t</ignoreWarnings> 862 <purgeOnDelete>%t</purgeOnDelete> 863 <rollbackOnError>%t</rollbackOnError> 864 <runAllTests>%t</runAllTests> 865 </deployOptions> 866 ` 867 zipfile := new(bytes.Buffer) 868 zipper := zip.NewWriter(zipfile) 869 for name, data := range files { 870 name = filepath.ToSlash(name) 871 wr, err := zipper.Create(fmt.Sprintf("unpackaged%s%s", string(os.PathSeparator), name)) 872 if err != nil { 873 return nil, nil, err 874 } 875 wr.Write(data) 876 } 877 zipper.Close() 878 879 //ioutil.WriteFile("package.zip", zipfile.Bytes(), 0644) 880 881 encoded := base64.StdEncoding.EncodeToString(zipfile.Bytes()) 882 body, err := fm.soapExecute("deploy", fmt.Sprintf(soap, encoded, options.AllowMissingFiles, options.AutoUpdatePackage, options.CheckOnly, options.IgnoreWarnings, options.PurgeOnDelete, options.RollbackOnError, options.RunAllTests)) 883 if err != nil { 884 fmt.Println(err.Error()) 885 return 886 } 887 888 var status struct { 889 Id string `xml:"Body>deployResponse>result>id"` 890 } 891 if err = xml.Unmarshal(body, &status); err != nil { 892 return 893 } 894 if err = fm.CheckStatus(status.Id); err != nil { 895 return 896 } 897 results, err := fm.CheckDeployStatus(status.Id) 898 899 for _, problem := range results.Details.ComponentFailures { 900 problems = append(problems, problem) 901 } 902 for _, success := range results.Details.ComponentSuccesses { 903 successes = append(successes, success) 904 } 905 return 906 } 907 908 func (fm *ForceMetadata) Retrieve(query ForceMetadataQuery) (files ForceMetadataFiles, err error) { 909 910 soap := ` 911 <retrieveRequest> 912 <apiVersion>%s</apiVersion> 913 <unpackaged> 914 %s 915 </unpackaged> 916 </retrieveRequest> 917 ` 918 soapType := ` 919 <types> 920 <name>%s</name> 921 <members>%s</members> 922 </types> 923 ` 924 types := "" 925 for _, element := range query { 926 types += fmt.Sprintf(soapType, element.Name, element.Members) 927 } 928 body, err := fm.soapExecute("retrieve", fmt.Sprintf(soap, apiVersionNumber, types)) 929 if err != nil { 930 return 931 } 932 var status struct { 933 Id string `xml:"Body>retrieveResponse>result>id"` 934 } 935 if err = xml.Unmarshal(body, &status); err != nil { 936 return 937 } 938 if err = fm.CheckStatus(status.Id); err != nil { 939 return 940 } 941 raw_files, err := fm.CheckRetrieveStatus(status.Id) 942 if err != nil { 943 return 944 } 945 files = make(ForceMetadataFiles) 946 for raw_name, data := range raw_files { 947 name := strings.Replace(raw_name, "unpackaged/", "", -1) 948 files[name] = data 949 } 950 return 951 } 952 953 func (fm *ForceMetadata) RetrievePackage(packageName string) (files ForceMetadataFiles, err error) { 954 soap := ` 955 <retrieveRequest> 956 <apiVersion>%s</apiVersion> 957 <packageNames>%s</packageNames> 958 </retrieveRequest> 959 ` 960 soap = fmt.Sprintf(soap, apiVersionNumber, packageName) 961 body, err := fm.soapExecute("retrieve", soap) 962 if err != nil { 963 return 964 } 965 var status struct { 966 Id string `xml:"Body>retrieveResponse>result>id"` 967 } 968 if err = xml.Unmarshal(body, &status); err != nil { 969 return 970 } 971 if err = fm.CheckStatus(status.Id); err != nil { 972 return 973 } 974 raw_files, err := fm.CheckRetrieveStatus(status.Id) 975 if err != nil { 976 return 977 } 978 files = make(ForceMetadataFiles) 979 for raw_name, data := range raw_files { 980 name := strings.Replace(raw_name, fmt.Sprintf("unpackaged%s", string(os.PathSeparator)), "", -1) 981 files[name] = data 982 } 983 return 984 } 985 986 func (fm *ForceMetadata) ListMetadata(query string) (res []byte, err error) { 987 return fm.soapExecute("listMetadata", fmt.Sprintf("<queries><type>%s</type></queries>", query)) 988 } 989 990 func (fm *ForceMetadata) ListConnectedApps() (apps ForceConnectedApps, err error) { 991 originalVersion := fm.ApiVersion 992 fm.ApiVersion = apiVersionNumber 993 body, err := fm.ListMetadata("ConnectedApp") 994 fm.ApiVersion = originalVersion 995 if err != nil { 996 return 997 } 998 var res struct { 999 ConnectedApps []ForceConnectedApp `xml:"Body>listMetadataResponse>result"` 1000 } 1001 if err = xml.Unmarshal(body, &res); err != nil { 1002 return 1003 } 1004 apps = res.ConnectedApps 1005 return 1006 } 1007 1008 func (fm *ForceMetadata) soapExecute(action, query string) (response []byte, err error) { 1009 login, err := fm.Force.Get(fm.Force.Credentials.Id) 1010 if err != nil { 1011 return 1012 } 1013 url := strings.Replace(login["urls"].(map[string]interface{})["metadata"].(string), "{version}", fm.ApiVersion, 1) 1014 soap := NewSoap(url, "http://soap.sforce.com/2006/04/metadata", fm.Force.Credentials.AccessToken) 1015 response, err = soap.Execute(action, query) 1016 return 1017 }