// setGivenEnv takes the given env name and finds it in the env list // in the given settings object. It then populates the EnvironmentID and // ServiceID on the settings object with appropriate values. func setGivenEnv(envName string, settings *models.Settings) { for eName, e := range settings.Environments { if eName == envName { settings.EnvironmentID = e.EnvironmentID settings.ServiceID = e.ServiceID settings.EnvironmentName = envName } } }
// setFirstAssociatedEnv is the last line of defense. If no other environments // were found locally or from the default flag, then the first one in the list // of environments in the given settings object is used to populate // EnvironmentID and ServiceID with appropriate values. func setFirstAssociatedEnv(required bool, promptForEnv bool, settings *models.Settings) { for envName, e := range settings.Environments { settings.EnvironmentID = e.EnvironmentID settings.ServiceID = e.ServiceID settings.EnvironmentName = e.Name if promptForEnv { defaultEnvPrompt(envName) } break } }
func promptForCredentials(settings *models.Settings) { var username string fmt.Print("Username: "******"Password: "******"windows" { stdIn, _, _ := term.StdStreams() fd, _ = term.GetFdInfo(stdIn) } bytes, _ := terminal.ReadPassword(int(fd)) fmt.Println("") settings.Password = string(bytes) }
// verify tests whether or not the given session token is still valid func verify(settings *models.Settings) bool { resp := httpclient.Get(fmt.Sprintf("%s/v2/auth/verify", settings.BaasHost), false, settings) m := make(map[string]string) json.Unmarshal(resp, &m) // the verify route returns userId and not usersId like everything else... if m["userId"] != "" { settings.UsersID = m["userId"] } return m["userId"] != "" }
// GetSettings returns a Settings object for the current context func GetSettings(required bool, promptForEnv bool, envName string, baasHost string, paasHost string, username string, password string) *models.Settings { HomeDir, err := homedir.Dir() if err != nil { fmt.Println(err.Error()) os.Exit(1) } file, err := os.Open(filepath.Join(HomeDir, SettingsFile)) if os.IsNotExist(err) { file, err = os.Create(filepath.Join(HomeDir, SettingsFile)) } defer file.Close() if err != nil { fmt.Println(err.Error()) os.Exit(1) } var settings models.Settings json.NewDecoder(file).Decode(&settings) // would be best to default this to an initialized map rather than nil if settings.Environments == nil { settings.Environments = make(map[string]models.AssociatedEnv) } // try and set the given env first, if it exists if envName != "" { setGivenEnv(envName, &settings) if settings.EnvironmentID == "" || settings.ServiceID == "" { fmt.Printf("No environment named \"%s\" has been associated. Run \"catalyze associated\" to see what environments have been associated or run \"catalyze associate\" from a local git repo to create a new association\n", envName) os.Exit(1) } } // if no env name was given, try and fetch the local env if settings.EnvironmentID == "" || settings.ServiceID == "" { setLocalEnv(required, &settings) } // if its not there, fetch the default if settings.EnvironmentID == "" || settings.ServiceID == "" { setDefaultEnv(&settings) } // if no default, fetch the first associated env and print warning if settings.EnvironmentID == "" || settings.ServiceID == "" { // warn and ask setFirstAssociatedEnv(required, promptForEnv, &settings) } // if no env found, warn and quit if required && (settings.EnvironmentID == "" || settings.ServiceID == "") { fmt.Println("No Catalyze environment has been associated. Run \"catalyze associate\" from a local git repo first") os.Exit(1) } settings.BaasHost = baasHost settings.PaasHost = paasHost settings.Username = username settings.Password = password return &settings }
// DeleteBreadcrumb removes the config file at LocalSettingsPath func DeleteBreadcrumb(alias string, settings *models.Settings) { env := settings.Environments[alias] dir := env.Directory dir = filepath.Join(dir, ".git", LocalSettingsFile) defer os.Remove(dir) delete(settings.Environments, alias) if settings.Default == alias { settings.Default = "" } os.Remove(dir) SaveSettings(settings) }
// SignIn signs in the user and retrieves a session. The passed in Settings // object is updated with the most up to date credentials func SignIn(settings *models.Settings) { // if we're already signed in with a valid session, don't sign in again if verify(settings) { return } if settings.Username == "" || settings.Password == "" { promptForCredentials(settings) } login := models.Login{ Username: settings.Username, Password: settings.Password, } b, err := json.Marshal(login) if err != nil { fmt.Println(err.Error()) os.Exit(1) } resp := httpclient.Post(b, fmt.Sprintf("%s/v2/auth/signin", settings.BaasHost), true, settings) var user models.User json.Unmarshal(resp, &user) settings.SessionToken = user.SessionToken settings.UsersID = user.UsersID config.SaveSettings(settings) }
// SetDefault sets the default environment. This environment must already be // associated. Any commands run outside of a git directory will use the default // environment for context. func SetDefault(alias string, settings *models.Settings) { var found bool for name := range settings.Environments { if name == alias { found = true break } } if !found { fmt.Printf("No environment with an alias of \"%s\" has been associated. Please run \"catalyze associate\" first\n", alias) os.Exit(1) } settings.Default = alias config.SaveSettings(settings) fmt.Printf("%s is now the default environment\n", alias) }
// Metrics prints out metrics for a given service or if the service is not // specified, metrics for the entire environment are printed. func Metrics(serviceLabel string, jsonFlag bool, csvFlag bool, sparkFlag bool, streamFlag bool, mins int, settings *models.Settings) { if streamFlag && (jsonFlag || csvFlag || mins != 1) { fmt.Println("--stream cannot be used with a custom format and multiple records") os.Exit(1) } var singleRetriever func(mins int, settings *models.Settings) *models.Metrics if serviceLabel != "" { service := helpers.RetrieveServiceByLabel(serviceLabel, settings) if service == nil { fmt.Printf("Could not find a service with the label \"%s\"\n", serviceLabel) os.Exit(1) } settings.ServiceID = service.ID singleRetriever = helpers.RetrieveServiceMetrics } var transformer Transformer redraw := make(chan bool) if jsonFlag { transformer = Transformer{ SingleRetriever: singleRetriever, DataTransformer: &JSONTransformer{}, } } else if csvFlag { buffer := &bytes.Buffer{} transformer = Transformer{ SingleRetriever: singleRetriever, DataTransformer: &CSVTransformer{ HeadersWritten: false, GroupMode: serviceLabel == "", Buffer: buffer, Writer: csv.NewWriter(buffer), }, } } else if sparkFlag { // the spark lines interface stays up until closed by the user, so // we might as well keep updating it as long as it is there streamFlag = true mins = 60 err := ui.Init() if err != nil { fmt.Println(err.Error()) os.Exit(1) } defer ui.Close() ui.UseTheme("helloworld") p := ui.NewPar("PRESS q TO QUIT") p.HasBorder = false p.TextFgColor = ui.Theme().SparklineTitle ui.Body.AddRows( ui.NewRow(ui.NewCol(12, 0, p)), ) transformer = Transformer{ SingleRetriever: singleRetriever, DataTransformer: &SparkTransformer{ Redraw: redraw, SparkLines: make(map[string]*ui.Sparklines), }, } } else { transformer = Transformer{ SingleRetriever: singleRetriever, DataTransformer: &TextTransformer{}, } } transformer.GroupRetriever = helpers.RetrieveEnvironmentMetrics transformer.Stream = streamFlag transformer.GroupMode = serviceLabel == "" transformer.Mins = mins transformer.settings = settings helpers.SignIn(settings) if sparkFlag { go transformer.process() ui.Body.Align() ui.Render(ui.Body) quit := make(chan bool) go maintainSparkLines(redraw, quit) <-quit } else { transformer.process() } }
// Logs is a way to stream logs from Kibana to your local terminal. This is // useful because Kibana is hard to look at because it splits every single // log statement into a separate block that spans multiple lines so it's // not very cohesive. This is intended to be similar to the `heroku logs` // command. func Logs(queryString string, tail bool, hours int, minutes int, seconds int, settings *models.Settings) { if settings.Username == "" || settings.Password == "" { // sometimes this will be filled in from env variables // if it is, just use that and don't prompt them settings.Username = "" settings.Password = "" fmt.Println("Please enter your logging dashboard credentials") } // if we remove the session token, the CLI will prompt for the // username/password normally. It will also set the username/password // on the settings object. sessionToken := settings.SessionToken settings.SessionToken = "" helpers.SignIn(settings) env := helpers.RetrieveEnvironment("pod", settings) var domain = env.Data.DNSName if domain == "" { domain = fmt.Sprintf("%s.catalyze.io", env.Data.Namespace) } urlString := fmt.Sprintf("https://%s/__es", domain) offset := time.Duration(hours)*time.Hour + time.Duration(minutes)*time.Minute + time.Duration(seconds)*time.Second timestamp := time.Now().In(time.UTC).Add(-1 * offset) from := 0 query := &models.LogQuery{ Fields: []string{"@timestamp", "message"}, Query: &models.Query{ Wildcard: map[string]string{ "message": queryString, }, }, Filter: &models.FilterRange{ Range: &models.RangeTimestamp{ Timestamp: map[string]string{ "gt": fmt.Sprintf("%04d-%02d-%02dT%02d:%02d:%02dZ", timestamp.Year(), timestamp.Month(), timestamp.Day(), timestamp.Hour(), timestamp.Minute(), timestamp.Second()), }, }, }, Sort: &models.LogSort{ Timestamp: map[string]string{ "order": "asc", }, Message: map[string]string{ "order": "asc", }, }, From: from, Size: 50, } var tr = &http.Transport{ TLSClientConfig: &tls.Config{ MinVersion: tls.VersionTLS12, }, } client := &http.Client{ Transport: tr, } settings.SessionToken = sessionToken config.SaveSettings(settings) fmt.Println(" @timestamp - message") for { query.From = from b, err := json.Marshal(*query) if err != nil { panic(err) } reader := bytes.NewReader(b) req, _ := http.NewRequest("GET", fmt.Sprintf("%s/_search", urlString), reader) req.SetBasicAuth(settings.Username, settings.Password) resp, err := client.Do(req) if err != nil { fmt.Println(err.Error()) } respBody, _ := ioutil.ReadAll(resp.Body) resp.Body.Close() if resp.StatusCode < 200 || resp.StatusCode >= 300 { fmt.Println(fmt.Errorf("%d %s", resp.StatusCode, string(respBody)).Error()) os.Exit(1) } var logs models.Logs json.Unmarshal(respBody, &logs) for _, lh := range *logs.Hits.Hits { fmt.Printf("%s - %s\n", lh.Fields["@timestamp"][0], lh.Fields["message"][0]) } if !tail { break } time.Sleep(2 * time.Second) from += len(*logs.Hits.Hits) } }
// Logout clears the stored user information from the local machine. This does // not remove environment data. func Logout(settings *models.Settings) { settings.SessionToken = "" settings.UsersID = "" config.SaveSettings(settings) }
// Associate an environment so that commands can be run against it. This command // no longer adds a git remote. See commands.AddRemote(). func Associate(envLabel string, serviceLabel string, alias string, remote string, defaultEnv bool, settings *models.Settings) { if _, err := os.Stat(".git"); os.IsNotExist(err) { fmt.Println("Not git repo found in the current directory") os.Exit(1) } helpers.SignIn(settings) envs := helpers.ListEnvironments("pod", settings) for _, env := range *envs { if env.Data.Name == envLabel { if env.State == "defined" { fmt.Printf("Your environment is not yet provisioned. Please visit https://dashboard.catalyze.io/environments/update/%s to finish provisioning your environment\n", env.ID) return } // would be nice to have some sort of global filter() function var chosenService models.Service if serviceLabel != "" { labels := []string{} for _, service := range *env.Data.Services { if service.Type == "code" { labels = append(labels, service.Label) if service.Label == serviceLabel { chosenService = service break } } } if chosenService.Type == "" { fmt.Printf("No code service found with name '%s'. Code services found: %s\n", serviceLabel, strings.Join(labels, ", ")) os.Exit(1) } } else { for _, service := range *env.Data.Services { if service.Type == "code" { chosenService = service break } } if chosenService.Type == "" { fmt.Printf("No code service found for \"%s\" environment (ID = %s)\n", envLabel, settings.EnvironmentID) os.Exit(1) } } for _, r := range helpers.ListGitRemote() { if r == remote { helpers.RemoveGitRemote(remote) break } } helpers.AddGitRemote(remote, chosenService.Source) fmt.Printf("\"%s\" remote added.\n", remote) dir, err := filepath.Abs(filepath.Dir(os.Args[0])) if err != nil { panic(err) } name := alias if name == "" { name = envLabel } settings.Environments[name] = models.AssociatedEnv{ EnvironmentID: env.ID, ServiceID: chosenService.ID, Directory: dir, Name: envLabel, } if defaultEnv { settings.Default = name } config.DropBreadcrumb(name, settings) config.SaveSettings(settings) if len(settings.Environments) > 1 && settings.Default == "" { fmt.Printf("You now have %d environments associated. Consider running \"catalyze default ENV_NAME\" to set a default\n", len(settings.Environments)) } return } } fmt.Printf("No environment with label \"%s\" found\n", envLabel) os.Exit(1) }