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  }