func (je *JobExecution) Fail(err JobError) error {
	log.Printf("JOB FAIL: %v\n", err)
	je.mux.Lock()
	je.StatusDetails = map[string]interface{}{
		"error": err.Error(),
	}
	je.Status = "FAILED"
	je.mux.Unlock()
	e := je.sendUpdate()
	if e != nil {
		return err
	}
	je.unsubscribeFromUpdates()
	return nil
}
/*
Success reports a fialed job execution to AWS IoT Device Management
By passing a StatusDetails structure to the function you can store some additional information regarding
the reason of the failure.
This function should be called to notify Device Management that the job failed.
If there are other jobs pending, they will be immediately notified to the client.
package awsiotjobs
import (
	"crypto/tls"
	"crypto/x509"
	"encoding/json"
	"fmt"
	"io/ioutil"
	"log"
	"sync"
	"time"
	mqtt "github.com/eclipse/paho.mqtt.golang"
)
const jobBaseTopic = "$aws/things/%s/jobs/%s"
const publishTimeout = 2 * time.Second
type Config struct {
	Port            int
	CaCertPath      string
	CertificatePath string
	PrivateKeyPath  string
	Endpoint        string
	ThingName       string
	ClientID        string
	Handler         func(je JobExecutioner)
}
func (c *Config) FromFile(file string) error {
	s, err := ioutil.ReadFile(file)
	if err != nil {
		fmt.Printf("Invalid config file - ignoring\n")
		return err
	}
	json.Unmarshal(s, &c)
	return nil
}
type nextJobPayload struct {
	clientToken string
}
func NewTLSConfig(caCertPath, certPath, privKeyPath string) *tls.Config {
	certpool := x509.NewCertPool()
	caCert, err := ioutil.ReadFile(caCertPath)
	if err == nil {
		certpool.AppendCertsFromPEM(caCert)
	} else {
		panic(err)
	}
	cert, err := tls.LoadX509KeyPair(certPath, privKeyPath)
	if err != nil {
		panic(err)
	}
	cert.Leaf, err = x509.ParseCertificate(cert.Certificate[0])
	if err != nil {
		panic(err)
	}
	return &tls.Config{
		RootCAs:            certpool,
		ClientAuth:         tls.NoClientCert,
		ClientCAs:          nil,
		InsecureSkipVerify: false,
		Certificates:       []tls.Certificate{cert},
	}
}
type JobError struct {
	ErrCode    string
	ErrMessage string
}
func (err JobError) Error() string {
	return fmt.Sprintf("code %s, msg: %s", err.ErrCode, err.ErrMessage)
}
type JobDocument map[string]interface{}
type StatusDetails map[string]interface{}
type JobExecutioner interface {
	GetJobDocument() JobDocument
	GetStatusDetails() StatusDetails
	Publish(string, byte, interface{})
	Success(StatusDetails) error
	Fail(JobError) error
	Reject(JobError) error
	InProgress(StatusDetails) error
	Terminate()
	GetThingName() string
	GetJobID() string
}
type JobExecution struct {
	JobID           string        `json:"jobId"`
	ThingName       string        `json:"thingName"`
	JobDocument     JobDocument   `json:"jobDocument"`
	Status          string        `json:"status"`
	StatusDetails   StatusDetails `json:"statusDetails"`
	QueuedAt        int64         `json:"queuedAt"`
	StartedAt       int64         `json:"startedAt"`
	LastUpdatedAt   int64         `json:"lastUpdatedAt"`
	VersionNumber   int64         `json:"versionNumber"`
	ExecutionNumber int64         `json:"executionNumber"`
	client          *Client
	mux             sync.Mutex
}
func (je *JobExecution) GetJobDocument() JobDocument {
	return je.JobDocument
}
func (je *JobExecution) GetStatusDetails() StatusDetails {
	return je.StatusDetails
}
func (je *JobExecution) GetThingName() string {
	return je.client.config.ThingName
}
func (je *JobExecution) GetJobID() string {
	return je.JobID
}
func (je *JobExecution) getUpdatePayload() interface{} {
	payload := make(map[string]interface{})
	payload["status"] = je.Status
	payload["statusDetails"] = je.StatusDetails
	payload["expectedVersion"] = je.VersionNumber
	payload["executionNumber"] = je.ExecutionNumber
	payload["includeJobExecutionState"] = true
	payload["clientToken"] = "client-token"
	jsonPayload, _ := json.Marshal(payload)
	return jsonPayload
}
func (je *JobExecution) sendUpdate() error {
	if je.client.Iot == nil {
		log.Panic("Iot client not set")
	}
	payload := je.getUpdatePayload()
	topic := fmt.Sprintf("%s/update", fmt.Sprintf(jobBaseTopic, je.client.config.ThingName, je.JobID))
	log.Printf("Updating status with %s\non topic %s\n", string(payload.([]byte)), topic)
	token := je.client.Iot.Publish(topic, 1, false, payload)  
	if token.WaitTimeout(publishTimeout) && token.Error() != nil {
		return token.Error()
	}
	return nil
}
func (je *JobExecution) InProgress(statusDetails StatusDetails) error {
	log.Printf("JOB IN_PROGRESS: %v\n", statusDetails)
	je.mux.Lock()
	je.StatusDetails = statusDetails
	je.Status = "IN_PROGRESS"
	je.mux.Unlock()
	return je.sendUpdate()
}
func (je *JobExecution) Success(statusDetails StatusDetails) error {
	log.Printf("JOB SUCCEEDED: %v\n", statusDetails)
	je.mux.Lock()
	je.StatusDetails = statusDetails
	je.Status = "SUCCEEDED"
	je.mux.Unlock()
	err := je.sendUpdate()
	if err != nil {
		return err
	}
	je.unsubscribeFromUpdates()
	return nil
}