github.com/in4it/ecs-deploy@v0.0.42-0.20240508120354-ed77ff16df25/cmd/ecs-client/main.go (about)

     1  package main
     2  
     3  import (
     4  	"bufio"
     5  	"bytes"
     6  	"encoding/json"
     7  	"errors"
     8  	"fmt"
     9  	"io/ioutil"
    10  	"net/http"
    11  	"os"
    12  	"path/filepath"
    13  	"strings"
    14  	"syscall"
    15  	"time"
    16  
    17  	"github.com/ghodss/yaml"
    18  	"github.com/in4it/ecs-deploy/service"
    19  	"github.com/juju/loggo"
    20  	"github.com/spf13/pflag"
    21  	"golang.org/x/crypto/ssh/terminal"
    22  )
    23  
    24  var clientLogger = loggo.GetLogger("client")
    25  
    26  type Token struct {
    27  	Token  string `json:"token" binding:"required"`
    28  	Expire string `json:"expire" binding:"required"`
    29  }
    30  type Session struct {
    31  	Token  string
    32  	Url    string
    33  	Expire string
    34  }
    35  
    36  type LoginFlags struct {
    37  	Url string
    38  }
    39  type DeployFlags struct {
    40  	ServiceName string
    41  	Filename    string
    42  }
    43  
    44  type DeployResponse struct {
    45  	Errors   map[string]string      `json:"errors" binding:"required"`
    46  	Failures int64                  `json:"failures" binding:"required"`
    47  	Messages []service.DeployResult `json:"messages"`
    48  }
    49  type DeployStatusResponse struct {
    50  	Service service.DeployResult `json:"service" binding:"required"`
    51  }
    52  
    53  func addLoginFlags(f *LoginFlags, fs *pflag.FlagSet) {
    54  	fs.StringVar(&f.Url, "url", f.Url, "ecs-deploy url, e.g. https://127.0.0.1:8080/ecs-deploy")
    55  }
    56  func addDeployFlags(f *DeployFlags, fs *pflag.FlagSet) {
    57  	fs.StringVar(&f.ServiceName, "service-name", f.ServiceName, "Service name to deploy")
    58  	fs.StringVarP(&f.Filename, "filename", "f", f.Filename, "filename to deploy")
    59  }
    60  
    61  func main() {
    62  	var err error
    63  
    64  	// set logging
    65  	if os.Getenv("DEBUG") == "true" {
    66  		loggo.ConfigureLoggers(`<root>=DEBUG`)
    67  	} else {
    68  		loggo.ConfigureLoggers(`<root>=INFO`)
    69  	}
    70  
    71  	session, err := readSession()
    72  	if err != nil {
    73  		fmt.Printf("%v", err.Error())
    74  		os.Exit(1)
    75  	}
    76  
    77  	if len(os.Args) > 1 && os.Args[1] == "login" {
    78  		// login
    79  		loginFlags := &LoginFlags{}
    80  		addLoginFlags(loginFlags, pflag.CommandLine)
    81  
    82  		if len(os.Args) > 2 && os.Args[2] != "" {
    83  			pflag.CommandLine.Parse(os.Args[2:])
    84  			if loginFlags.Url == "" {
    85  				fmt.Fprintf(os.Stderr, "Usage of %s login:\n", os.Args[0])
    86  				pflag.PrintDefaults()
    87  				os.Exit(1)
    88  			}
    89  			err = login(loginFlags)
    90  		} else {
    91  			fmt.Fprintf(os.Stderr, "Usage of %s login:\n", os.Args[0])
    92  			pflag.PrintDefaults()
    93  		}
    94  	} else if len(os.Args) > 2 && os.Args[1] == "createrepo" && os.Args[2] != "" {
    95  		// create repo
    96  		var result string
    97  		result, err = createRepository(session, os.Args[2])
    98  		fmt.Printf("%v\n", result)
    99  	} else if len(os.Args) > 1 && os.Args[1] == "deploy" {
   100  		// deploy
   101  		deployFlags := &DeployFlags{}
   102  		addDeployFlags(deployFlags, pflag.CommandLine)
   103  
   104  		if len(os.Args) > 2 && os.Args[2] != "" {
   105  			pflag.CommandLine.Parse(os.Args[2:])
   106  			failure, err := deploy(session, deployFlags)
   107  			if failure {
   108  				if err != nil {
   109  					fmt.Printf("%v", err.Error())
   110  				}
   111  				os.Exit(1)
   112  			}
   113  		} else {
   114  			fmt.Fprintf(os.Stderr, "Usage of %s deploy:\n", os.Args[0])
   115  			pflag.PrintDefaults()
   116  		}
   117  	} else if len(os.Args) > 1 && os.Args[1] == "runtask" {
   118  		deployFlags := &DeployFlags{}
   119  		addDeployFlags(deployFlags, pflag.CommandLine)
   120  
   121  		if len(os.Args) > 2 && os.Args[2] != "" {
   122  			pflag.CommandLine.Parse(os.Args[2:])
   123  			failure, err := runtask(session, deployFlags)
   124  			if failure {
   125  				if err != nil {
   126  					fmt.Printf("%v", err.Error())
   127  				}
   128  				os.Exit(1)
   129  			}
   130  		} else {
   131  			fmt.Fprintf(os.Stderr, "Usage of %s runtask:\n", os.Args[0])
   132  			pflag.PrintDefaults()
   133  		}
   134  	} else {
   135  		fmt.Println("Usage: ")
   136  		fmt.Printf("%v login        login\n", os.Args[0])
   137  		fmt.Printf("%v createrepo   create repository\n", os.Args[0])
   138  		fmt.Printf("%v deploy       deploy services\n", os.Args[0])
   139  		fmt.Printf("%v runtask      run task on service\n", os.Args[0])
   140  	}
   141  	if err != nil {
   142  		fmt.Printf("%v", err.Error())
   143  		os.Exit(1)
   144  	}
   145  }
   146  
   147  func runtask(session Session, deployFlags *DeployFlags) (bool, error) {
   148  	if deployFlags.Filename == "" {
   149  		// default for ease
   150  		deployFlags.Filename = "ecs.json"
   151  	}
   152  	if fi, err := os.Stat(deployFlags.Filename); err != nil || fi.IsDir() {
   153  		return true, errors.New("runtask expects a single json file")
   154  	}
   155  	content, err := ioutil.ReadFile(deployFlags.Filename)
   156  	if err != nil {
   157  		return true, err
   158  	}
   159  	resp, err := doRunTaskAPICall(session, deployFlags.ServiceName, string(content))
   160  	if err != nil {
   161  		return true, err
   162  	}
   163  	fmt.Printf("Service %v started task: %v\n", deployFlags.ServiceName, string(resp))
   164  	return false, nil
   165  }
   166  
   167  // deploy with timeouts
   168  // if --service-name is set, look for ecs.[json|yaml] and ecs.*.[json|yaml] (if filename is set, use it as directory to look into)
   169  // if --service-name is set, with filename, give error
   170  // if filename is set but not service name, expect serviceName in json (normal behavior)
   171  
   172  func deploy(session Session, deployFlags *DeployFlags) (bool, error) {
   173  	deployData, err := getDeployData(session, deployFlags)
   174  	if err != nil {
   175  		return true, err
   176  	}
   177  	response, err := doDeployAPICall(session, deployData)
   178  	if err != nil {
   179  		return true, err
   180  	}
   181  	deployed, err := waitForDeploy(session, response)
   182  	if err != nil {
   183  		return true, err
   184  	}
   185  	fmt.Println("")
   186  	fmt.Println("---")
   187  	var failure bool
   188  	for k, status := range deployed {
   189  		fmt.Printf("Service %v deployment status: %v\n", k, status)
   190  		if status != "success" {
   191  			failure = true
   192  		}
   193  	}
   194  	return failure, nil
   195  }
   196  func waitForDeploy(session Session, response []byte) (map[string]string, error) {
   197  	// api call returned info to follow-up on deployment
   198  	var deploymentsFinished bool
   199  	var deployResponse DeployResponse
   200  	var finished int64
   201  	deployed := make(map[string]string)
   202  	maxWait := 1200
   203  	err := json.Unmarshal(response, &deployResponse)
   204  	if err != nil {
   205  		return deployed, err
   206  	}
   207  	for _, v := range deployResponse.Messages {
   208  		deployed[v.ServiceName] = "running"
   209  	}
   210  	for k, v := range deployResponse.Errors {
   211  		fmt.Printf("Service %v: %v\n", k, v)
   212  		deployed[k] = "error"
   213  		finished++
   214  	}
   215  	if int64(len(deployed)) == finished {
   216  		deploymentsFinished = true
   217  	}
   218  	for i := 0; i < (maxWait/15) && !deploymentsFinished; i++ {
   219  		time.Sleep(15 * time.Second)
   220  		for _, v := range deployResponse.Messages {
   221  			if deployed[v.ServiceName] == "running" {
   222  				status, err := checkDeployStatus(session, v.ServiceName, v.DeploymentTime.Format("2006-01-02T15:04:05.999999999Z"))
   223  				if err != nil {
   224  					return deployed, err
   225  				}
   226  				fmt.Printf(".")
   227  				if status != "running" {
   228  					deployed[v.ServiceName] = status
   229  					fmt.Printf("%v=%v", v.ServiceName, status)
   230  					finished++
   231  				}
   232  			}
   233  		}
   234  		if int64(len(deployed)) == finished {
   235  			deploymentsFinished = true
   236  		}
   237  	}
   238  	return deployed, nil
   239  }
   240  func checkDeployStatus(session Session, serviceName, deploymentTime string) (string, error) {
   241  	var status string
   242  	var deployStatusResponse DeployStatusResponse
   243  	req, err := http.NewRequest("GET", session.Url+"/api/v1/deploy/status/"+serviceName+"/"+deploymentTime, nil)
   244  	if err != nil {
   245  		return status, err
   246  	}
   247  	req.Header.Set("Authorization", "Bearer "+session.Token)
   248  	var client = &http.Client{
   249  		Timeout: time.Second * 15,
   250  	}
   251  	resp, err := client.Do(req)
   252  	if err != nil {
   253  		return status, err
   254  	}
   255  	defer resp.Body.Close()
   256  	body, err := ioutil.ReadAll(resp.Body)
   257  	if err != nil {
   258  		return status, err
   259  	}
   260  	if resp.StatusCode != 200 {
   261  		if resp.StatusCode == 401 {
   262  			return status, fmt.Errorf("Invalid credentials: use %v login --url <url> to login again\n", os.Args[0])
   263  		} else {
   264  			return status, fmt.Errorf("Error %d: %v", resp.StatusCode, string(body))
   265  		}
   266  	}
   267  	err = json.Unmarshal(body, &deployStatusResponse)
   268  	if err != nil {
   269  		return status, err
   270  	}
   271  	status = deployStatusResponse.Service.Status
   272  	return status, nil
   273  }
   274  func doRunTaskAPICall(session Session, service string, deployData string) ([]byte, error) {
   275  	url := fmt.Sprintf("service/runtask/%v", service)
   276  	return doAPICall(session, url, deployData)
   277  }
   278  func doDeployAPICall(session Session, deployData string) ([]byte, error) {
   279  	url := "deploy"
   280  	return doAPICall(session, url, deployData)
   281  }
   282  func doAPICall(session Session, url string, deployData string) ([]byte, error) {
   283  	var body []byte
   284  	clientLogger.Debugf("API Call data: %v", deployData)
   285  	req, err := http.NewRequest("POST", session.Url+"/api/v1/"+url, bytes.NewBuffer([]byte(deployData)))
   286  	if err != nil {
   287  		return body, err
   288  	}
   289  	req.Header.Set("Content-Type", "application/json")
   290  	req.Header.Set("Authorization", "Bearer "+session.Token)
   291  	var client = &http.Client{
   292  		Timeout: time.Second * 120,
   293  	}
   294  	resp, err := client.Do(req)
   295  	if err != nil {
   296  		return body, err
   297  	}
   298  	defer resp.Body.Close()
   299  	body, err = ioutil.ReadAll(resp.Body)
   300  	if err != nil {
   301  		return body, err
   302  	}
   303  	if resp.StatusCode != 200 {
   304  		if resp.StatusCode == 401 {
   305  			return body, fmt.Errorf("Invalid credentials: use %v login --url <url> to login again\n", os.Args[0])
   306  		} else {
   307  			return body, fmt.Errorf("Error %d: %v", resp.StatusCode, string(body))
   308  		}
   309  	}
   310  	return body, nil
   311  }
   312  func getDeployData(session Session, deployFlags *DeployFlags) (string, error) {
   313  	var deployData string
   314  	var deployServices service.DeployServices
   315  	var err error
   316  	if deployFlags.ServiceName != "" {
   317  		// serviceName is set
   318  		deployServices, err = getDeployDataWithService(deployFlags.ServiceName, deployFlags.Filename)
   319  		if err != nil {
   320  			return deployData, err
   321  		}
   322  	} else if deployFlags.ServiceName == "" && deployFlags.Filename != "" {
   323  		// serviceName is not set
   324  		deployServices, err = getDeployDataWithoutService(deployFlags.ServiceName, deployFlags.Filename)
   325  		if err != nil {
   326  			return deployData, err
   327  		}
   328  	} else {
   329  		fmt.Fprintf(os.Stderr, "Usage of %s:\n", os.Args[0])
   330  		pflag.PrintDefaults()
   331  		return deployData, errors.New("InvalidFlags")
   332  	}
   333  	// convert to JSON
   334  	deployData, err = convertDeployServiceToJson(deployServices)
   335  	if err != nil {
   336  		return deployData, err
   337  	}
   338  	return deployData, nil
   339  }
   340  func convertDeployServiceToJson(deployServices service.DeployServices) (string, error) {
   341  	var deployData string
   342  	b, err := json.Marshal(deployServices)
   343  	if err != nil {
   344  		return deployData, err
   345  	}
   346  	deployData = string(b)
   347  	if deployData == `{"deploy":null}` {
   348  		return deployData, errors.New("No deployment data found")
   349  	}
   350  	return deployData, nil
   351  }
   352  func getDeployDataWithoutService(serviceName, filename string) (service.DeployServices, error) {
   353  	var deployServices service.DeployServices
   354  	var err error
   355  	if ok, _ := isDir(filename); ok {
   356  		return deployServices, fmt.Errorf("%v is a directory. Specify a file or use the --service-name argument\n", filename)
   357  	}
   358  	fType := ""
   359  	if filepath.Ext(filename) == ".json" {
   360  		fType = "json"
   361  	} else if filepath.Ext(filename) == ".yaml" || filepath.Ext(filename) == ".yml" {
   362  		fType = "yaml"
   363  	}
   364  	deployService, err := parseFile(filename, fType, "")
   365  	if err != nil {
   366  		return deployServices, nil
   367  	}
   368  	deployServices.Services = append(deployServices.Services, deployService...)
   369  	return deployServices, nil
   370  }
   371  func getDeployDataWithService(serviceName, filename string) (service.DeployServices, error) {
   372  	var readDir string
   373  	var deployServices service.DeployServices
   374  	var err error
   375  	if filename != "" {
   376  		if ok, _ := isDir(filename); !ok {
   377  			return deployServices, fmt.Errorf("%v needs to be a directory if --service-name is specified\n", filename)
   378  		}
   379  		readDir = filename
   380  	} else {
   381  		readDir = "./"
   382  	}
   383  	// parse JSON/YAML files
   384  	deployServices, err = parseFiles(readDir, serviceName)
   385  	if err != nil {
   386  		return deployServices, err
   387  	}
   388  	if len(deployServices.Services) == 0 {
   389  		return deployServices, fmt.Errorf("No json/yaml files found to deploy\n")
   390  	}
   391  	return deployServices, nil
   392  }
   393  func parseFiles(readDir, serviceName string) (service.DeployServices, error) {
   394  	var deployServices service.DeployServices
   395  	fs := make(map[string]string)
   396  	files, err := ioutil.ReadDir(readDir)
   397  	if err != nil {
   398  		return deployServices, err
   399  	}
   400  	for _, f := range files {
   401  		if f.Name() == "ecs.json" {
   402  			fs[f.Name()] = "json"
   403  		} else if strings.HasPrefix(f.Name(), "ecs.") && strings.HasSuffix(f.Name(), ".json") {
   404  			fs[f.Name()] = "json"
   405  		} else if f.Name() == "ecs.yaml" || f.Name() == "ecs.yml" {
   406  			fs[f.Name()] = "yaml"
   407  		} else if strings.HasPrefix(f.Name(), "ecs.") && (strings.HasSuffix(f.Name(), ".yaml") || strings.HasSuffix(f.Name(), ".yml")) {
   408  			fs[f.Name()] = "yaml"
   409  		}
   410  	}
   411  	for f, fType := range fs {
   412  		deploy, err := parseFile(filepath.Join(readDir, f), fType, serviceName)
   413  		if err != nil {
   414  			return deployServices, nil
   415  		}
   416  		deployServices.Services = append(deployServices.Services, deploy...)
   417  	}
   418  	return deployServices, nil
   419  }
   420  func unmarshalWithDefaults(fType string, content []byte, deploy *service.DeployServices) error {
   421  	// set defaults
   422  	var err error
   423  	var singleDeploy service.Deploy
   424  	service.SetDeployDefaults(&singleDeploy)
   425  
   426  	y := len(deploy.Services)
   427  	deploy.Services = []service.Deploy{}
   428  	for i := 0; i < y; i++ {
   429  		deploy.Services = append(deploy.Services, singleDeploy)
   430  	}
   431  	// unmarshal with defaults
   432  	if fType == "json" {
   433  		err = json.Unmarshal(content, &deploy)
   434  	} else if fType == "yaml" {
   435  		err = yaml.Unmarshal(content, &deploy)
   436  	} else {
   437  		return fmt.Errorf("Wrong file extension (needs to be json, yaml, or yml)\n")
   438  	}
   439  	if err != nil {
   440  		return fmt.Errorf("Could not unmarshal with defaults: %v", err.Error())
   441  	}
   442  	return nil
   443  }
   444  func parseFile(filename, fType, serviceName string) ([]service.Deploy, error) {
   445  	var deploy service.DeployServices
   446  	var singleDeploy service.Deploy
   447  	fileBase := filepath.Base(filename)
   448  	// set defaults for singledeploy
   449  	service.SetDeployDefaults(&singleDeploy)
   450  
   451  	content, err := ioutil.ReadFile(filename)
   452  	if err != nil {
   453  		return deploy.Services, fmt.Errorf("Could not read file: %v\n", filename)
   454  	}
   455  	if fType == "json" {
   456  		err = json.Unmarshal(content, &deploy)
   457  		if err != nil {
   458  			return deploy.Services, fmt.Errorf("json file %v in wrong format: %v", filename, err.Error())
   459  		}
   460  		if len(deploy.Services) == 0 {
   461  			err = json.Unmarshal(content, &singleDeploy)
   462  			if err != nil {
   463  				return deploy.Services, fmt.Errorf("json file %v in wrong format: %v", filename, err.Error())
   464  			}
   465  			deploy.Services = append(deploy.Services, singleDeploy)
   466  		} else {
   467  			unmarshalWithDefaults("json", content, &deploy)
   468  		}
   469  	} else if fType == "yaml" {
   470  		err = yaml.Unmarshal(content, &deploy)
   471  		if err != nil {
   472  			return deploy.Services, fmt.Errorf("yaml file %v in wrong format: %v", filename, err.Error())
   473  		}
   474  		if len(deploy.Services) == 0 {
   475  			err = yaml.Unmarshal(content, &singleDeploy)
   476  			if err != nil {
   477  				return deploy.Services, fmt.Errorf("yaml file %v in wrong format: %v", filename, err.Error())
   478  			}
   479  			deploy.Services = append(deploy.Services, singleDeploy)
   480  		} else {
   481  			unmarshalWithDefaults("yaml", content, &deploy)
   482  		}
   483  	} else {
   484  		return deploy.Services, fmt.Errorf("Wrong file extension (needs to be json, yaml, or yml)\n")
   485  	}
   486  	// check whether we have services
   487  	if len(deploy.Services) == 0 {
   488  		return deploy.Services, fmt.Errorf("Unable to extract any services from the provided file(s)\n")
   489  	}
   490  	// set defaults and serviceName
   491  	for k, _ := range deploy.Services {
   492  		if serviceName != "" {
   493  			if fileBase == "ecs.json" || fileBase == "ecs.yaml" || fileBase == "ecs.yml" {
   494  				deploy.Services[k].ServiceName = serviceName
   495  			} else {
   496  				start := 4
   497  				filenameWithoutExt := strings.Replace(fileBase, ".yml", "", -1)
   498  				filenameWithoutExt = strings.Replace(filenameWithoutExt, ".json", "", -1)
   499  				filenameWithoutExt = strings.Replace(filenameWithoutExt, ".yaml", "", -1)
   500  				deploy.Services[k].ServiceName = serviceName + "-" + filenameWithoutExt[start:]
   501  			}
   502  		}
   503  		// check whether we not have an empty service
   504  		if deploy.Services[k].Cluster == "" {
   505  			return deploy.Services, fmt.Errorf("Service %v has no ClusterName defined\n", deploy.Services[k].ServiceName)
   506  		}
   507  	}
   508  	return deploy.Services, nil
   509  }
   510  
   511  func createRepository(session Session, repository string) (string, error) {
   512  	var res string
   513  	req, err := http.NewRequest("POST", session.Url+"/api/v1/ecr/create/"+repository, bytes.NewBuffer([]byte("")))
   514  	if err != nil {
   515  		return res, err
   516  	}
   517  	req.Header.Set("Content-Type", "application/json")
   518  	req.Header.Set("Authorization", "Bearer "+session.Token)
   519  	var client = &http.Client{
   520  		Timeout: time.Second * 60,
   521  	}
   522  	resp, err := client.Do(req)
   523  	if err != nil {
   524  		return res, err
   525  	}
   526  	if resp.StatusCode != 200 {
   527  		if resp.StatusCode == 401 {
   528  			return res, fmt.Errorf("Invalid credentials: use %v login --url <url> to login again\n", os.Args[0])
   529  		} else {
   530  			return res, fmt.Errorf("ecr create return http error %d", resp.StatusCode)
   531  		}
   532  	}
   533  	defer resp.Body.Close()
   534  	body, err := ioutil.ReadAll(resp.Body)
   535  	if err != nil {
   536  		return res, err
   537  	}
   538  	res = string(body)
   539  	return res, nil
   540  }
   541  
   542  func readSession() (Session, error) {
   543  	var session Session
   544  	content, err := ioutil.ReadFile(filepath.Join(os.Getenv("HOME"), ".ecsdeploy", "session.json"))
   545  	if err != nil {
   546  		// no file present, return empty session
   547  		return session, nil
   548  	}
   549  	err = json.Unmarshal(content, &session)
   550  	if err != nil {
   551  		return session, err
   552  	}
   553  	return session, nil
   554  
   555  }
   556  func login(loginFlags *LoginFlags) error {
   557  	var session Session
   558  	var err error
   559  	var username, password string
   560  
   561  	session.Url = loginFlags.Url
   562  	if os.Getenv("ECS_DEPLOY_LOGIN") != "" && os.Getenv("ECS_DEPLOY_PASSWORD") != "" {
   563  		username = os.Getenv("ECS_DEPLOY_LOGIN")
   564  		password = os.Getenv("ECS_DEPLOY_PASSWORD")
   565  	} else {
   566  		username, password, err = readCredentials()
   567  		if err != nil {
   568  			return err
   569  		}
   570  	}
   571  	token, err := auth(session.Url, username, password)
   572  	if err != nil {
   573  		return err
   574  	}
   575  	newpath := filepath.Join(os.Getenv("HOME"), ".ecsdeploy")
   576  	os.MkdirAll(newpath, os.ModePerm)
   577  
   578  	session.Token = token.Token
   579  	session.Expire = token.Expire
   580  
   581  	b, err := json.Marshal(session)
   582  	if err != nil {
   583  		return err
   584  	}
   585  	err = ioutil.WriteFile(filepath.Join(os.Getenv("HOME"), ".ecsdeploy", "session.json"), b, 0600)
   586  	if err != nil {
   587  		return err
   588  	}
   589  	fmt.Println("Authentication successful")
   590  	return nil
   591  }
   592  func readCredentials() (string, string, error) {
   593  	reader := bufio.NewReader(os.Stdin)
   594  
   595  	fmt.Print("Enter Username: ")
   596  	username, _ := reader.ReadString('\n')
   597  
   598  	fmt.Print("Enter Password: ")
   599  	bytePassword, err := terminal.ReadPassword(int(syscall.Stdin))
   600  	if err != nil {
   601  		return "", "", err
   602  	}
   603  	password := string(bytePassword)
   604  
   605  	return strings.TrimSpace(username), strings.TrimSpace(password), nil
   606  }
   607  
   608  func auth(url, login, password string) (Token, error) {
   609  	var token Token
   610  	var jsonStr = []byte("{\"username\":\"" + login + "\",\"password\":\"" + password + "\"}")
   611  	req, err := http.NewRequest("POST", url+"/login", bytes.NewBuffer(jsonStr))
   612  	if err != nil {
   613  		return token, err
   614  	}
   615  	req.Header.Set("Content-Type", "application/json")
   616  	var client = &http.Client{
   617  		Timeout: time.Second * 10,
   618  	}
   619  	resp, err := client.Do(req)
   620  	if err != nil {
   621  		return token, err
   622  	}
   623  	if resp.StatusCode != 200 {
   624  		return token, errors.New("Authentication failed")
   625  	}
   626  	defer resp.Body.Close()
   627  	body, err := ioutil.ReadAll(resp.Body)
   628  	if err != nil {
   629  		return token, err
   630  	}
   631  
   632  	err = json.Unmarshal(body, &token)
   633  	if err != nil {
   634  		return token, err
   635  	}
   636  
   637  	return token, nil
   638  }
   639  func isDir(pth string) (bool, error) {
   640  	fi, err := os.Stat(pth)
   641  	if err != nil {
   642  		return false, err
   643  	}
   644  
   645  	return fi.Mode().IsDir(), nil
   646  }