Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
164 changes: 164 additions & 0 deletions cmd/deploy.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,17 @@ package cmd
import (
"context"
"fmt"
"os"
"strconv"
"time"

"github.com/logrusorgru/aurora"
"github.com/uselagoon/lagoon-cli/pkg/output"

lclient "github.com/uselagoon/machinery/api/lagoon/client"

"github.com/spf13/cobra"
lagoonssh "github.com/uselagoon/lagoon-cli/pkg/lagoon/ssh"
"github.com/uselagoon/machinery/api/lagoon"
"github.com/uselagoon/machinery/api/schema"
)
Expand Down Expand Up @@ -47,6 +51,18 @@ use 'lagoon deploy latest' instead`,
if err != nil {
return err
}
follow, err := cmd.Flags().GetBool("follow")
if err != nil {
return err
}
showPod, err := cmd.Flags().GetBool("show-pod")
if err != nil {
return err
}
showTimestamp, err := cmd.Flags().GetBool("show-timestamp")
if err != nil {
return err
}
if err := requiredInputCheck("Project name", cmdProjectName, "Branch name", branch); err != nil {
return err
}
Expand Down Expand Up @@ -85,6 +101,10 @@ use 'lagoon deploy latest' instead`,
resultData := output.Result{Result: result.DeployEnvironmentBranch}
r := output.RenderResult(resultData, outputOptions)
fmt.Fprintf(cmd.OutOrStdout(), "%s", r)

if follow {
return followDeployLogs(cmd, cmdProjectName, branch, resultData.Result, debug, showPod, showTimestamp)
}
}
return nil
},
Expand Down Expand Up @@ -115,6 +135,18 @@ var deployPromoteCmd = &cobra.Command{
if err != nil {
return err
}
follow, err := cmd.Flags().GetBool("follow")
if err != nil {
return err
}
showPod, err := cmd.Flags().GetBool("show-pod")
if err != nil {
return err
}
showTimestamp, err := cmd.Flags().GetBool("show-timestamp")
if err != nil {
return err
}
if err := requiredInputCheck("Project name", cmdProjectName, "Source environment", sourceEnvironment, "Destination environment", destinationEnvironment); err != nil {
return err
}
Expand Down Expand Up @@ -150,6 +182,10 @@ var deployPromoteCmd = &cobra.Command{
resultData := output.Result{Result: result.DeployEnvironmentPromote}
r := output.RenderResult(resultData, outputOptions)
fmt.Fprintf(cmd.OutOrStdout(), "%s", r)

if follow {
return followDeployLogs(cmd, cmdProjectName, destinationEnvironment, resultData.Result, debug, showPod, showTimestamp)
}
}
return nil
},
Expand All @@ -175,6 +211,18 @@ This environment should already exist in lagoon. It is analogous with the 'Deplo
if err != nil {
return err
}
follow, err := cmd.Flags().GetBool("follow")
if err != nil {
return err
}
showPod, err := cmd.Flags().GetBool("show-pod")
if err != nil {
return err
}
showTimestamp, err := cmd.Flags().GetBool("show-timestamp")
if err != nil {
return err
}

buildVarStrings, err := cmd.Flags().GetStringArray("buildvar")
if err != nil {
Expand Down Expand Up @@ -213,6 +261,10 @@ This environment should already exist in lagoon. It is analogous with the 'Deplo
resultData := output.Result{Result: result.DeployEnvironmentLatest}
r := output.RenderResult(resultData, outputOptions)
fmt.Fprintf(cmd.OutOrStdout(), "%s", r)

if follow {
return followDeployLogs(cmd, cmdProjectName, cmdProjectEnvironment, resultData.Result, debug, showPod, showTimestamp)
}
}
return nil
},
Expand Down Expand Up @@ -273,6 +325,19 @@ This pullrequest may not already exist as an environment in lagoon.`,
if err != nil {
return err
}
follow, err := cmd.Flags().GetBool("follow")
if err != nil {
return err
}
showPod, err := cmd.Flags().GetBool("show-pod")
if err != nil {
return err
}
showTimestamp, err := cmd.Flags().GetBool("show-timestamp")
if err != nil {
return err
}

if yesNo(fmt.Sprintf("You are attempting to deploy pull request '%v' for project '%s', are you sure?", prNumber, cmdProjectName)) {
current := lagoonCLIConfig.Current
token := lagoonCLIConfig.Lagoons[current].Token
Expand Down Expand Up @@ -302,6 +367,10 @@ This pullrequest may not already exist as an environment in lagoon.`,
resultData := output.Result{Result: result.DeployEnvironmentPullrequest}
r := output.RenderResult(resultData, outputOptions)
fmt.Fprintf(cmd.OutOrStdout(), "%s", r)

if follow {
return followDeployLogs(cmd, cmdProjectName, fmt.Sprintf("pr-%d", prNumber), resultData.Result, debug, showPod, showTimestamp)
}
}
return nil
},
Expand All @@ -315,16 +384,25 @@ func init() {

const returnDataUsageText = "Returns the build name instead of success text"
deployLatestCmd.Flags().Bool("returndata", false, returnDataUsageText)
deployLatestCmd.Flags().Bool("follow", false, "Follow the deploy logs")
deployLatestCmd.Flags().Bool("show-pod", true, "show pod/container name prefix on log lines")
deployLatestCmd.Flags().Bool("show-timestamp", true, "show timestamp prefix on log lines")
deployLatestCmd.Flags().StringArray("buildvar", []string{}, "Add one or more build variables to deployment (--buildvar KEY1=VALUE1 [--buildvar KEY2=VALUE2])")

deployBranchCmd.Flags().StringP("branch", "b", "", "Branch name to deploy")
deployBranchCmd.Flags().StringP("branch-ref", "r", "", "Branch ref to deploy")
deployBranchCmd.Flags().Bool("returndata", false, returnDataUsageText)
deployBranchCmd.Flags().Bool("follow", false, "Follow the deploy logs")
deployBranchCmd.Flags().Bool("show-pod", true, "show pod/container name prefix on log lines")
deployBranchCmd.Flags().Bool("show-timestamp", true, "show timestamp prefix on log lines")
deployBranchCmd.Flags().StringArray("buildvar", []string{}, "Add one or more build variables to deployment (--buildvar KEY1=VALUE1 [--buildvar KEY2=VALUE2])")

deployPromoteCmd.Flags().StringP("destination", "d", "", "Destination environment name to create")
deployPromoteCmd.Flags().StringP("source", "s", "", "Source environment name to use as the base to deploy from")
deployPromoteCmd.Flags().Bool("returndata", false, returnDataUsageText)
deployPromoteCmd.Flags().Bool("follow", false, "Follow the deploy logs")
deployPromoteCmd.Flags().Bool("show-pod", true, "show pod/container name prefix on log lines")
deployPromoteCmd.Flags().Bool("show-timestamp", true, "show timestamp prefix on log lines")
deployPromoteCmd.Flags().StringArray("buildvar", []string{}, "Add one or more build variables to deployment (--buildvar KEY1=VALUE1 [--buildvar KEY2=VALUE2])")

deployPullrequestCmd.Flags().StringP("title", "t", "", "Pullrequest title")
Expand All @@ -334,5 +412,91 @@ func init() {
deployPullrequestCmd.Flags().StringP("head-branch-name", "H", "", "Pullrequest head branch name")
deployPullrequestCmd.Flags().StringP("head-branch-ref", "M", "", "Pullrequest head branch reference hash")
deployPullrequestCmd.Flags().Bool("returndata", false, returnDataUsageText)
deployPullrequestCmd.Flags().Bool("follow", false, "Follow the deploy logs")
deployPullrequestCmd.Flags().Bool("show-pod", true, "show pod/container name prefix on log lines")
deployPullrequestCmd.Flags().Bool("show-timestamp", true, "show timestamp prefix on log lines")
deployPullrequestCmd.Flags().StringArray("buildvar", []string{}, "Add one or more build variables to deployment (--buildvar KEY1=VALUE1 [--buildvar KEY2=VALUE2])")
}

func followDeployLogs(
cmd *cobra.Command,
projectName,
environmentName,
buildName string,
debug,
showPod,
showTimestamp bool,
) error {
safeEnvName := makeSafe(shortenEnvironment(projectName, environmentName))
sshHost, sshPort, username, _, err := getSSHHostPort(safeEnvName, debug)
if err != nil {
return fmt.Errorf("couldn't get SSH endpoint: %v", err)
}
ignoreHostKey, acceptNewHostKey :=
lagoonssh.CheckStrictHostKey(strictHostKeyCheck)
sshConfig, closeSSHAgent, err := getSSHClientConfig(
username,
fmt.Sprintf("%s:%s", sshHost, sshPort),
ignoreHostKey,
acceptNewHostKey)
if err != nil {
return fmt.Errorf("couldn't get SSH client config: %v", err)
}
defer func() {
err = closeSSHAgent()
if err != nil {
fmt.Fprintf(os.Stderr, "error closing ssh agent:%v\n", err)
}
}()
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
// start background ticker to close session when deploy completes
ticker := time.NewTicker(10 * time.Second)
defer ticker.Stop()
go func() {
defer cancel()
for {
select {
case <-ctx.Done():
return
case <-ticker.C:
validateToken(lagoonCLIConfig.Current)
current := lagoonCLIConfig.Current
token := lagoonCLIConfig.Lagoons[current].Token
lc := lclient.New(
lagoonCLIConfig.Lagoons[current].GraphQL,
lagoonCLIVersion,
lagoonCLIConfig.Lagoons[current].Version,
&token,
debug)
// ignore errors here since we can't really do anything about them
deployment, _ := lagoon.GetDeploymentByName(
ctx, cmdProjectName, cmdProjectEnvironment, buildName, false, lc)
if deployment.Completed != "" && deployment.Status != "running" {
var status string
switch deployment.Status {
case "complete":
status = "complete ✅"
case "failed":
status = "failed ❌"
case "cancelled":
status = "cancelled 🛑"
default:
status = deployment.Status
}
fmt.Fprintf(
cmd.OutOrStdout(),
"Deployment %s finished with status: %s\n",
aurora.Yellow(buildName),
status)
return
}
}
}
}()
fmt.Fprintf(cmd.OutOrStdout(), "Streaming deploy logs...\n")
return lagoonssh.LogStream(ctx, sshConfig, sshHost, sshPort, []string{
"lagoonSystem=build",
"logs=tailLines=32,follow",
}, showPod, showTimestamp)
}
73 changes: 58 additions & 15 deletions cmd/logs.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,40 +16,80 @@ import (
"golang.org/x/crypto/ssh"
)

const (
buildNoOptDefVal = "all_builds"
taskNoOptDefVal = "all_tasks"
)

var (
// connTimeout is the network connection timeout used for SSH connections and
// calls to the Lagoon API.
connTimeout = 8 * time.Second
// these variables are assigned in init() to flag values
logsService string
logsContainer string
logsTailLines uint
logsFollow bool
logsService string
logsContainer string
logsBuild string
logsTask string
logsTailLines uint
logsFollow bool
logsShowPod bool
logsShowTimestamp bool
)

func init() {
logsCmd.Flags().StringVarP(&logsService, "service", "s", "", "specify a specific service name")
logsCmd.Flags().StringVarP(&logsContainer, "container", "c", "", "specify a specific container name")
logsCmd.Flags().StringVarP(&logsBuild, "build", "b", "", "specify build logs, with an optional specific build name")
logsCmd.Flags().Lookup("build").NoOptDefVal = buildNoOptDefVal
logsCmd.Flags().StringVarP(&logsTask, "task", "t", "", "specify task logs, with an optional specific task name")
logsCmd.Flags().Lookup("task").NoOptDefVal = taskNoOptDefVal
logsCmd.Flags().UintVarP(&logsTailLines, "lines", "n", 32, "the number of lines to return for each container")
logsCmd.Flags().BoolVarP(&logsFollow, "follow", "f", false, "continue outputting new lines as they are logged")
logsCmd.Flags().BoolVarP(&logsShowPod, "show-pod", "", true, "show pod/container name prefix on log lines")
logsCmd.Flags().BoolVarP(&logsShowTimestamp, "show-timestamp", "", true, "show timestamp prefix on log lines")
logsCmd.MarkFlagsMutuallyExclusive("service", "build", "task")
logsCmd.MarkFlagsMutuallyExclusive("container", "build", "task")
}

func generateLogsCommand(service, container string, lines uint,
follow bool) ([]string, error) {
func generateLogsCommand(
service,
container,
build,
task string,
lines uint,
follow bool,
) ([]string, error) {
var argv []string
if service == "" {
return nil, fmt.Errorf("empty service name")
}
if unsafeRegex.MatchString(service) {
return nil, fmt.Errorf("service name contains invalid characters")
if service != "" {
if unsafeRegex.MatchString(service) {
return nil, fmt.Errorf("service name contains invalid characters")
}
argv = append(argv, "service="+service)
}
argv = append(argv, "service="+service)
if container != "" {
if unsafeRegex.MatchString(container) {
return nil, fmt.Errorf("container name contains invalid characters")
}
argv = append(argv, "container="+container)
}
if build != "" {
argv = append(argv, "lagoonSystem=build")
if build != buildNoOptDefVal {
if unsafeRegex.MatchString(build) {
return nil, fmt.Errorf("build name contains invalid characters")
}
argv = append(argv, "name="+build)
}
}
if task != "" {
argv = append(argv, "lagoonSystem=task")
if task != taskNoOptDefVal {
if unsafeRegex.MatchString(task) {
return nil, fmt.Errorf("task name contains invalid characters")
}
argv = append(argv, "name="+task)
}
}
logsCmd := fmt.Sprintf("logs=tailLines=%d", lines)
if follow {
logsCmd += ",follow"
Expand Down Expand Up @@ -155,8 +195,8 @@ var logsCmd = &cobra.Command{
return fmt.Errorf("couldn't get debug value: %v", err)
}
ignoreHostKey, acceptNewHostKey := lagoonssh.CheckStrictHostKey(strictHostKeyCheck)
argv, err := generateLogsCommand(logsService, logsContainer, logsTailLines,
logsFollow)
argv, err := generateLogsCommand(
logsService, logsContainer, logsBuild, logsTask, logsTailLines, logsFollow)
if err != nil {
return fmt.Errorf("couldn't generate logs command: %v", err)
}
Expand All @@ -179,8 +219,11 @@ var logsCmd = &cobra.Command{
fmt.Fprintf(os.Stderr, "error closing ssh agent:%v\n", err)
}
}()
ctx, cancel := context.WithCancel(context.TODO())
defer cancel()
// start SSH log streaming session
err = lagoonssh.LogStream(sshConfig, sshHost, sshPort, argv)
err = lagoonssh.LogStream(
ctx, sshConfig, sshHost, sshPort, argv, logsShowPod, logsShowTimestamp)
if err != nil {
output.RenderError(err.Error(), outputOptions)
switch e := err.(type) {
Expand Down
Loading
Loading