func oneLogLine(in []byte) []byte {
	return bytes.Replace(oneLogLineRegex.ReplaceAll(in, nil), []byte("\n"), []byte("\\n "), -1)
}
oneLogLine removes indentation at the beginning of each line and
escapes new line characters.
package marathon
import (
	"bytes"
	"encoding/json"
	"errors"
	"fmt"
	"io"
	"io/ioutil"
	"log"
	"net/http"
	"net/url"
	"regexp"
	"sync"
	"time"
)
type Marathon interface {
	ListApplications(url.Values) ([]string, error)
	ApplicationVersions(name string) (*ApplicationVersions, error)
	HasApplicationVersion(name, version string) (bool, error)
	SetApplicationVersion(name string, version *ApplicationVersion) (*DeploymentID, error)
	ApplicationOK(name string) (bool, error)
	CreateApplication(application *Application) (*Application, error)
	DeleteApplication(name string, force bool) (*DeploymentID, error)
	UpdateApplication(application *Application, force bool) (*DeploymentID, error)
	ApplicationDeployments(name string) ([]*DeploymentID, error)
	ScaleApplicationInstances(name string, instances int, force bool) (*DeploymentID, error)
	RestartApplication(name string, force bool) (*DeploymentID, error)
	Applications(url.Values) (*Applications, error)
	Application(name string) (*Application, error)
	ApplicationBy(name string, opts *GetAppOpts) (*Application, error)
	ApplicationByVersion(name, version string) (*Application, error)
	WaitOnApplication(name string, timeout time.Duration) error
	Tasks(application string) (*Tasks, error)
	AllTasks(opts *AllTasksOpts) (*Tasks, error)
	TaskEndpoints(name string, port int, healthCheck bool) ([]string, error)
	KillApplicationTasks(applicationID string, opts *KillApplicationTasksOpts) (*Tasks, error)
	KillTask(taskID string, opts *KillTaskOpts) (*Task, error)
	KillTasks(taskIDs []string, opts *KillTaskOpts) error
	Groups() (*Groups, error)
	Group(name string) (*Group, error)
	GroupsBy(opts *GetGroupOpts) (*Groups, error)
	GroupBy(name string, opts *GetGroupOpts) (*Group, error)
	CreateGroup(group *Group) error
	DeleteGroup(name string, force bool) (*DeploymentID, error)
	UpdateGroup(id string, group *Group, force bool) (*DeploymentID, error)
	HasGroup(name string) (bool, error)
	WaitOnGroup(name string, timeout time.Duration) error
	Deployments() ([]*Deployment, error)
	DeleteDeployment(id string, force bool) (*DeploymentID, error)
	HasDeployment(id string) (bool, error)
	WaitOnDeployment(id string, timeout time.Duration) error
	Subscriptions() (*Subscriptions, error)
	AddEventsListener(filter int) (EventsChannel, error)
	RemoveEventsListener(channel EventsChannel)
	Subscribe(string) error
	Unsubscribe(string) error
	Queue() (*Queue, error)
	DeleteQueueDelay(appID string) error
	GetMarathonURL() string
	Ping() (bool, error)
	Info() (*Info, error)
	Leader() (string, error)
	AbdicateLeader() (string, error)
}
var (
	ErrInvalidResponse = errors.New("invalid response from Marathon")
	ErrMarathonDown = errors.New("all the Marathon hosts are presently down")
	ErrTimeoutError = errors.New("the operation has timed out")
)
type EventsChannelContext struct {
	filter     int
	done       chan struct{}
	completion *sync.WaitGroup
}
type marathonClient struct {
	sync.RWMutex
	config Config
	subscribedToSSE bool
	ipAddress string
	eventsHTTP *http.Server
	hosts *cluster
	listeners map[EventsChannel]EventsChannelContext
	debugLog *log.Logger
	client *httpClient
}
type httpClient struct {
	config Config
}
func NewClient(config Config) (Marathon, error) {
	if config.HTTPClient == nil {
		config.HTTPClient = http.DefaultClient
	}
	if config.PollingWaitTime == 0 {
		config.PollingWaitTime = defaultPollingWaitTime
	}
	client := &httpClient{config: config}
	hosts, err := newCluster(client, config.URL, config.DCOSToken != "")
	if err != nil {
		return nil, err
	}
	debugLogOutput := config.LogOutput
	if debugLogOutput == nil {
		debugLogOutput = ioutil.Discard
	}
	return &marathonClient{
		config:    config,
		listeners: make(map[EventsChannel]EventsChannelContext),
		hosts:     hosts,
		debugLog:  log.New(debugLogOutput, "", 0),
		client:    client,
	}, nil
}
func (r *marathonClient) GetMarathonURL() string {
	return r.config.URL
}
func (r *marathonClient) Ping() (bool, error) {
	if err := r.apiGet(marathonAPIPing, nil, nil); err != nil {
		return false, err
	}
	return true, nil
}
func (r *marathonClient) apiGet(uri string, post, result interface{}) error {
	return r.apiCall("GET", uri, post, result)
}
func (r *marathonClient) apiPut(uri string, post, result interface{}) error {
	return r.apiCall("PUT", uri, post, result)
}
func (r *marathonClient) apiPost(uri string, post, result interface{}) error {
	return r.apiCall("POST", uri, post, result)
}
func (r *marathonClient) apiDelete(uri string, post, result interface{}) error {
	return r.apiCall("DELETE", uri, post, result)
}
func (r *marathonClient) apiCall(method, url string, body, result interface{}) error {
	for {
		var requestBody []byte
		var err error
		if body != nil {
			if requestBody, err = json.Marshal(body); err != nil {
				return err
			}
		}
		request, member, err := r.buildAPIRequest(method, url, bytes.NewReader(requestBody))
		if err != nil {
			return err
		}
		response, err := r.client.Do(request)
		if err != nil {
			r.hosts.markDown(member)
			r.debugLog.Printf("apiCall(): request failed on host: %s, error: %s, trying another\n", member, err)
			continue
		}
		defer response.Body.Close()
		respBody, err := ioutil.ReadAll(response.Body)
		if err != nil {
			return err
		}
		if len(requestBody) > 0 {
			r.debugLog.Printf("apiCall(): %v %v %s returned %v %s\n", request.Method, request.URL.String(), requestBody, response.Status, oneLogLine(respBody))
		} else {
			r.debugLog.Printf("apiCall(): %v %v returned %v %s\n", request.Method, request.URL.String(), response.Status, oneLogLine(respBody))
		}
		if response.StatusCode >= 200 && response.StatusCode <= 299 {
			if result != nil {
				if err := json.Unmarshal(respBody, result); err != nil {
					r.debugLog.Printf("apiCall(): failed to unmarshall the response from marathon, error: %s\n", err)
					return ErrInvalidResponse
				}
			}
			return nil
		}
		if response.StatusCode >= 500 && response.StatusCode <= 599 {
			r.hosts.markDown(member)
			r.debugLog.Printf("apiCall(): request failed, host: %s, status: %d, trying another\n", member, response.StatusCode)
			continue
		}
		return NewAPIError(response.StatusCode, respBody)
	}
}
func (r *marathonClient) buildAPIRequest(method, uri string, reader io.Reader) (request *http.Request, member string, err error) {
	member, err = r.hosts.getMember()
	if err != nil {
		return nil, "", ErrMarathonDown
	}
	request, err = r.client.buildMarathonRequest(method, member, uri, reader)
	if err != nil {
		return nil, member, err
	}
	return request, member, nil
}
func (rc *httpClient) buildMarathonRequest(method string, member string, uri string, reader io.Reader) (request *http.Request, err error) {
	url := fmt.Sprintf("%s/%s", member, uri)
	request, err = http.NewRequest(method, url, reader)
	if err != nil {
		return nil, err
	}
	if rc.config.HTTPBasicAuthUser != "" && rc.config.HTTPBasicPassword != "" {
		request.SetBasicAuth(rc.config.HTTPBasicAuthUser, rc.config.HTTPBasicPassword)
	}
	if rc.config.DCOSToken != "" {
		request.Header.Add("Authorization", "token="+rc.config.DCOSToken)
	}
	request.Header.Add("Content-Type", "application/json")
	request.Header.Add("Accept", "application/json")
	return request, nil
}
func (rc *httpClient) Do(request *http.Request) (response *http.Response, err error) {
	return rc.config.HTTPClient.Do(request)
}
var oneLogLineRegex = regexp.MustCompile(`(?m)^\s*`)