Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support "edit and run local" command. #22

Merged
merged 13 commits into from
Jun 4, 2024
49 changes: 36 additions & 13 deletions tools/ctl/ide/rebuilder.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,16 +17,19 @@ package ide
import (
"bufio"
"context"
"encoding/json"
"fmt"
"io"
"log"
"net/http"
"net/url"
"os/exec"
"strings"
"sync"
"time"

"github.com/google/oss-rebuild/build/binary"
"github.com/google/oss-rebuild/build/container"
"github.com/google/oss-rebuild/pkg/rebuild/schema"
"github.com/google/oss-rebuild/tools/ctl/firestore"
"github.com/google/oss-rebuild/tools/docker"
"github.com/pkg/errors"
Expand Down Expand Up @@ -194,27 +197,47 @@ func (rb *Rebuilder) Restart(ctx context.Context) {
}
}

type RunLocalOpts struct {
Strategy *schema.StrategyOneOf
}

// RunLocal runs the rebuilder for the given example.
// Each element of extraParams is a url parameter like "param=value".
func (rb *Rebuilder) RunLocal(ctx context.Context, r firestore.Rebuild, extraParams ...string) {
func (rb *Rebuilder) RunLocal(ctx context.Context, r firestore.Rebuild, opts RunLocalOpts) {
_, err := rb.runningInstance(ctx)
if err != nil {
log.Println(err)
log.Println(err.Error())
return
}
log.Printf("Calling the rebuilder for %s\n", r.ID())
id := time.Now().UTC().Format(time.RFC3339)
var extra string
if len(extraParams) > 0 {
extra = "&" + strings.Join(extraParams, "&")
vals := url.Values{}
vals.Add("ecosystem", r.Ecosystem)
vals.Add("pkg", r.Package)
vals.Add("versions", r.Version)
vals.Add("id", id)
if opts.Strategy != nil {
byts, err := json.Marshal(opts.Strategy)
if err != nil {
log.Println(err)
return
}
vals.Add("strategy", string(byts))
}
cmd := exec.CommandContext(ctx, "curl", "--silent", "-d", fmt.Sprintf("ecosystem=%s&pkg=%s&versions=%s&id=%s%s", r.Ecosystem, r.Package, r.Version, id, extra), "-X", "POST", "127.0.0.1:8080/smoketest")
rllog := logWriter(log.New(log.Default().Writer(), logPrefix("runlocal"), 0))
cmd.Stdout = rllog
cmd.Stderr = rllog
log.Println(cmd.String())
if err := cmd.Start(); err != nil {
log.Println(err)
client := http.DefaultClient
resp, err := client.PostForm("http://127.0.0.1:8080/smoketest", vals)
if err != nil {
log.Println(err.Error())
return
}
var smkResp schema.SmoketestResponse
if err := json.NewDecoder(resp.Body).Decode(&smkResp); err != nil {
log.Println(errors.Wrap(err, "failed to decode smoketest response").Error())
}
// TODO: Is there a more usefull way to print this response?
log.Println(smkResp)
if len(smkResp.Verdicts) == 0 && smkResp.Verdicts[0].Message == "" {
log.Println("Success!")
}
}

Expand Down
85 changes: 35 additions & 50 deletions tools/ctl/ide/ui.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,9 +20,7 @@ import (
"context"
"encoding/json"
"fmt"
"io"
"log"
"net/url"
"os"
"os/exec"
"path/filepath"
Expand Down Expand Up @@ -227,76 +225,59 @@ func (e *explorer) showLogs(ctx context.Context, example firestore.Rebuild) {
}
}

func (e *explorer) editAndRun(ctx context.Context, example firestore.Rebuild) {
func (e *explorer) editAndRun(ctx context.Context, example firestore.Rebuild) error {
localAssets, err := localAssetStore(ctx, example.Run)
if err != nil {
log.Println(errors.Wrap(err, "failed to create local asset store"))
return
return errors.Wrap(err, "failed to create local asset store")
}
buildDefAsset := rebuild.Asset{Type: rebuild.BuildDef, Target: example.Target()}
var currentStratYaml []byte
var currentStrat schema.StrategyOneOf
{
if r, _, err := localAssets.Reader(ctx, buildDefAsset); err == nil {
buf := new(bytes.Buffer)
_, err = io.Copy(buf, r)
if err != nil {
log.Println(errors.Wrap(err, "failed to read existing build definition"))
return
d := yaml.NewDecoder(r)
if d.Decode(&currentStrat) != nil {
return errors.Wrap(err, "failed to read existing build definition")
}
currentStratYaml = buf.Bytes()
} else {
var oneof schema.StrategyOneOf
if err := json.Unmarshal([]byte(example.Strategy), &oneof); err != nil {
log.Println(errors.Wrap(err, "failed to parse strategy"))
return
}
var err error
if currentStratYaml, err = yaml.Marshal(oneof); err != nil {
log.Println(errors.Wrap(err, "failed to marshal strategy"))
return
if err := json.Unmarshal([]byte(example.Strategy), &currentStrat); err != nil {
return errors.Wrap(err, "failed to parse strategy")
}
}
}
var newStratYaml []byte
var newStrat schema.StrategyOneOf
{
w, uri, err := localAssets.Writer(ctx, buildDefAsset)
if err != nil {
log.Println(errors.Wrapf(err, "opening strategy file"))
return
return errors.Wrapf(err, "opening build definition")
}
if _, err = w.Write([]byte("# Edit the build definition below, then save and exit the file to begin a rebuild.\n")); err != nil {
return errors.Wrapf(err, "writing comment to build definition file")
}
e := yaml.NewEncoder(w)
if e.Encode(&currentStrat) != nil {
return errors.Wrapf(err, "populating build definition")
}
w.Write(append([]byte("# Edit the strategy below, then save and exit the file to begin a rebuild.\n"), currentStratYaml...))
w.Close()
// Send a "tmux wait -S" signal once the edit is complete.
cmd := exec.Command("tmux", "new-window", fmt.Sprintf("$EDITOR %s; tmux wait -S editing", uri))
if out, err := cmd.Output(); err != nil {
log.Println(errors.Wrap(err, "failed to edit strategy:"))
log.Println(out)
return
if _, err := cmd.Output(); err != nil {
return errors.Wrap(err, "failed to edit build definition")
}
// Wait to receive the tmux signal.
if _, err := exec.Command("tmux", "wait", "editing").Output(); err != nil {
log.Println(errors.Wrap(err, "failed to wait for tmux signal"))
return
return errors.Wrap(err, "failed to wait for tmux signal")
}
newStratYaml, err = os.ReadFile(uri)
r, _, err := localAssets.Reader(ctx, buildDefAsset)
if err != nil {
log.Println(errors.Wrap(err, "failed to read strategy"))
return
return errors.Wrap(err, "failed to open build definition after edits")
}
d := yaml.NewDecoder(r)
if err := d.Decode(&newStrat); err != nil {
return errors.Wrap(err, "manual strategy oneof failed to parse")
}
}
log.Println("New strategy: " + string(newStratYaml))
var oneof schema.StrategyOneOf
if err := yaml.Unmarshal(newStratYaml, &oneof); err != nil {
log.Println(errors.Wrap(err, "failed to parse new strategy"))
return
}
log.Printf("Decoded oneof: %+v", oneof)
newStratJsonBytes, err := json.Marshal(oneof)
if err != nil {
log.Println(errors.Wrap(err, "failed to convert new strategy to json"))
return
}
go e.rb.RunLocal(e.ctx, example, "strategy="+url.QueryEscape(string(newStratJsonBytes)))
e.rb.RunLocal(e.ctx, example, RunLocalOpts{Strategy: &newStrat})
return nil
}

func (e *explorer) makeExampleNode(example firestore.Rebuild) *tview.TreeNode {
Expand All @@ -306,16 +287,20 @@ func (e *explorer) makeExampleNode(example firestore.Rebuild) *tview.TreeNode {
children := node.GetChildren()
if len(children) == 0 {
node.AddChild(makeCommandNode("run local", func() {
go e.rb.RunLocal(e.ctx, example)
go e.rb.RunLocal(e.ctx, example, RunLocalOpts{})
}))
node.AddChild(makeCommandNode("restart && run local", func() {
go func() {
e.rb.Restart(e.ctx)
e.rb.RunLocal(e.ctx, example)
e.rb.RunLocal(e.ctx, example, RunLocalOpts{})
}()
}))
node.AddChild(makeCommandNode("edit and run local", func() {
go e.editAndRun(e.ctx, example)
go func() {
if err := e.editAndRun(e.ctx, example); err != nil {
log.Println(err.Error())
}
}()
}))
node.AddChild(makeCommandNode("details", func() {
go e.showDetails(e.ctx, example)
Expand Down