Skip to content

Commit

Permalink
Merge remote-tracking branch 'origin/main' into tdstein/rename
Browse files Browse the repository at this point in the history
  • Loading branch information
tdstein committed Sep 20, 2023
2 parents b29f8d0 + be18719 commit 5c5b6f4
Show file tree
Hide file tree
Showing 15 changed files with 604 additions and 139 deletions.
4 changes: 2 additions & 2 deletions .vscode/launch.json
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
"args": [
"publish-ui",
"-n",
"devpwpg",
"dogfood",
"${workspaceFolder}/notebook1"
],
},
Expand All @@ -26,7 +26,7 @@
"args": [
"publish",
"-n",
"badauth2",
"dogfood",
"${workspaceFolder}/notebook1",
"--debug",
],
Expand Down
130 changes: 106 additions & 24 deletions internal/api_client/clients/client_connect.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import (
"io"
"net/http"
"regexp"
"strings"
"time"

"github.com/rstudio/publishing-client/internal/accounts"
Expand Down Expand Up @@ -241,19 +242,117 @@ func (c *ConnectClient) getTask(taskID types.TaskID, previous *taskDTO) (*taskDT
return &task, nil
}

var buildRPattern = regexp.MustCompile("Building (Shiny application|Plumber API|R Markdown document).*")
var buildPythonPattern = regexp.MustCompile("Building (.* application|.* API|Jupyter notebook).*")
var launchPattern = regexp.MustCompile("Launching .* (application|API|notebook)")
var staticPattern = regexp.MustCompile("(Building|Launching) static content")

func eventOpFromLogLine(currentOp events.Operation, line string) events.Operation {
if match, _ := regexp.MatchString("Building (Shiny application|Plumber API).*", line); match {
match := buildRPattern.MatchString(line)
if match || strings.Contains(line, "Bundle created with R version") {
return events.PublishRestoreREnvOp
} else if match, _ := regexp.MatchString("Building (.* application|.* API|Jupyter notebook).*", line); match {
}
match = buildPythonPattern.MatchString(line)
if match || strings.Contains(line, "Bundle requested Python version") {
return events.PublishRestorePythonEnvOp
} else if match, _ := regexp.MatchString("Launching .* (application|API|notebook)", line); match {
}
match = launchPattern.MatchString(line)
if match {
return events.PublishRunContentOp
} else if match, _ := regexp.MatchString("(Building|Launching) static content", line); match {
}
match = staticPattern.MatchString(line)
if match {
return events.PublishRunContentOp
}
return currentOp
}

type packageRuntime string

const (
rRuntime packageRuntime = "r"
pythonRuntime packageRuntime = "python"
)

type packageStatus string

const (
downloadAndInstallPackage packageStatus = "download+install"
downloadPackage packageStatus = "download"
installPackage packageStatus = "install"
)

var rPackagePattern = regexp.MustCompile(`Installing ([[:word:]\.]+) \((\S+)\) ...`)
var pythonCollectingPackagePattern = regexp.MustCompile(`Collecting (\S+)==(\S+)`)
var pythonInstallingPackagePattern = regexp.MustCompile(`Found existing installation: (\S+) ()\S+`)

type packageEvent struct {
runtime packageRuntime
status packageStatus
name string
version string
}

func makePackageEvent(match []string, rt packageRuntime, status packageStatus) *packageEvent {
return &packageEvent{
runtime: rt,
status: status,
name: match[1],
version: match[2],
}
}

func packageEventFromLogLine(line string) *packageEvent {
if match := rPackagePattern.FindStringSubmatch(line); match != nil {
return makePackageEvent(match, rRuntime, downloadAndInstallPackage)
} else if match := pythonCollectingPackagePattern.FindStringSubmatch(line); match != nil {
return makePackageEvent(match, pythonRuntime, downloadPackage)
} else if match := pythonInstallingPackagePattern.FindStringSubmatch(line); match != nil {
return makePackageEvent(match, pythonRuntime, installPackage)
}
return nil
}

func handleTaskUpdate(task *taskDTO, op types.Operation, log logging.Logger) (types.Operation, error) {
var nextOp types.Operation

for _, line := range task.Output {
// Detect state transitions from certain matching log lines.
nextOp = eventOpFromLogLine(op, line)
if nextOp != op {
if op != "" {
log.Success("Done", logging.LogKeyOp, op)
}
op = nextOp
log.Start(line, logging.LogKeyOp, op)
} else {
log.Info(line, logging.LogKeyOp, op)
}

// Log a progress event for certain matching log lines.
event := packageEventFromLogLine(line)
if event != nil {
log.Status("Package restore",
"runtime", event.runtime,
"status", event.status,
"name", event.name,
"version", event.version,
logging.LogKeyOp, op)
}
}
if task.Finished {
if task.Error != "" {
// TODO: make these errors more specific, maybe by
// using the Connect error codes from the logs.
err := types.NewAgentError(events.DeploymentFailedCode, errors.New(task.Error), nil)
err.SetOperation(op)
return op, err
}
log.Success("Done", logging.LogKeyOp, op)
}
return op, nil
}

func (c *ConnectClient) WaitForTask(taskID types.TaskID, log logging.Logger) error {
var previous *taskDTO
var op events.Operation
Expand All @@ -263,26 +362,9 @@ func (c *ConnectClient) WaitForTask(taskID types.TaskID, log logging.Logger) err
if err != nil {
return err
}
for _, line := range task.Output {
nextOp := eventOpFromLogLine(op, line)
if nextOp != op {
if op != "" {
log.Success("Done")
}
op = nextOp
log = log.With(logging.LogKeyOp, op)
}
log.Info(line)
}
if task.Finished {
if task.Error != "" {
// TODO: make these errors more specific, maybe by
// using the Connect error codes from the logs.
err := errors.New(task.Error)
return types.NewAgentError(events.DeploymentFailedCode, err, nil)
}
log.Success("Done")
return nil
op, err = handleTaskUpdate(task, op, log)
if err != nil || task.Finished {
return err
}
previous = task
time.Sleep(500 * time.Millisecond)
Expand Down
Loading

0 comments on commit 5c5b6f4

Please sign in to comment.