mirror of
https://github.com/sourcegraph/sourcegraph.git
synced 2026-02-06 15:12:02 +00:00
feat(sg): add images [list|build] command (#62030)
This commit is contained in:
parent
1f09814d6f
commit
da08670715
@ -21,6 +21,7 @@ go_library(
|
||||
"sg_embeddings_qa.go",
|
||||
"sg_generate.go",
|
||||
"sg_help.go",
|
||||
"sg_images.go",
|
||||
"sg_insights.go",
|
||||
"sg_install.go",
|
||||
"sg_lint.go",
|
||||
@ -141,6 +142,7 @@ go_test(
|
||||
timeout = "short",
|
||||
srcs = [
|
||||
"main_test.go",
|
||||
"sg_images_test.go",
|
||||
"sg_start_test.go",
|
||||
],
|
||||
# Required by func findRoot() to check if sg is running in sourcegraph/sourcegraph
|
||||
|
||||
@ -283,6 +283,7 @@ var sg = &cli.App{
|
||||
setupCommand,
|
||||
srcCommand,
|
||||
srcInstanceCommand,
|
||||
imagesCommand,
|
||||
|
||||
// Company
|
||||
teammateCommand,
|
||||
|
||||
162
dev/sg/sg_images.go
Normal file
162
dev/sg/sg_images.go
Normal file
@ -0,0 +1,162 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"github.com/grafana/regexp"
|
||||
"github.com/urfave/cli/v2"
|
||||
|
||||
"github.com/sourcegraph/run"
|
||||
"github.com/sourcegraph/sourcegraph/dev/sg/internal/category"
|
||||
"github.com/sourcegraph/sourcegraph/dev/sg/internal/std"
|
||||
"github.com/sourcegraph/sourcegraph/lib/errors"
|
||||
"github.com/sourcegraph/sourcegraph/lib/output"
|
||||
)
|
||||
|
||||
var imagesCommand = &cli.Command{
|
||||
Name: "images",
|
||||
Usage: "Commands for interacting with containers",
|
||||
Category: category.Dev,
|
||||
Subcommands: []*cli.Command{
|
||||
{
|
||||
Name: "list",
|
||||
Usage: "List container images available to build",
|
||||
Action: func(cctx *cli.Context) error {
|
||||
lines, err := listBazelOCITarballs(cctx.Context)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
std.Out.WriteLine(output.Styledf(output.StylePending, "Found %d targets", len(lines)))
|
||||
std.Out.WriteLine(output.Styledf(output.StyleReset, strings.Join(lines, "\n")))
|
||||
std.Out.WriteLine(output.Styledf(output.StylePending, "💡 You can build and load the above targets using glob patterns with 'sg images build [pattern]..."))
|
||||
std.Out.WriteLine(output.Styledf(output.StylePending, "💡 Examples:"))
|
||||
std.Out.WriteLine(output.Styledf(output.StyleReset, " 'worker' to match //cmd/worker:image_tarball"))
|
||||
std.Out.WriteLine(output.Styledf(output.StyleReset, " 'cmd/*' to match all containers under //cmd/"))
|
||||
std.Out.WriteLine(output.Styledf(output.StyleReset, " 'postgres*' to match"))
|
||||
std.Out.WriteLine(output.Styledf(output.StyleReset, " //docker-images/postgres-12-alpine:base_tarball//docker-images/postgres-12-alpine:image_tarball"))
|
||||
std.Out.WriteLine(output.Styledf(output.StyleReset, " //docker-images/postgres-12-alpine:legacy_tarball"))
|
||||
std.Out.WriteLine(output.Styledf(output.StyleReset, " //docker-images/postgres_exporter:base_tarball"))
|
||||
std.Out.WriteLine(output.Styledf(output.StyleReset, " //docker-images/postgres_exporter:image_tarball"))
|
||||
|
||||
return nil
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "build",
|
||||
Usage: "builds a container image by matching [pattern] using glob syntax to the target.\nExamples:\n- sg images build worker\n- sg images build cmd/*\n- sg images build postgres*",
|
||||
UsageText: "build [pattern1] ([pattern2] ...)",
|
||||
Flags: []cli.Flag{
|
||||
&cli.BoolFlag{
|
||||
Name: "load",
|
||||
Usage: "Load the image into the local Docker daemon",
|
||||
Value: true,
|
||||
},
|
||||
},
|
||||
Action: func(cctx *cli.Context) error {
|
||||
allTargets, err := listBazelOCITarballs(cctx.Context)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
patterns := cctx.Args().Slice()
|
||||
selectedTargets := []string{}
|
||||
|
||||
for _, name := range patterns {
|
||||
if strings.HasPrefix(name, "//") {
|
||||
std.Out.WriteLine(output.Styledf(output.StyleYellow, "Detected a Bazel target path (%q) aborting.", name))
|
||||
std.Out.WriteLine(output.Styledf(output.StyleBold, "Run the following command instead:"))
|
||||
std.Out.WriteLine(output.Styledf(output.StyleReset, "Building the image without loading it:\n sg bazel build %s", name))
|
||||
std.Out.WriteLine(output.Styledf(output.StyleReset, "Building the image and loading it:\n sg bazel run %s", name))
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
for _, target := range allTargets {
|
||||
for _, name := range patterns {
|
||||
var ok bool
|
||||
var err error
|
||||
if strings.Contains(name, "/") {
|
||||
ok, err = filepath.Match(name, trimImageTarballTarget(target))
|
||||
} else {
|
||||
ok, err = filepath.Match(fmt.Sprintf("*/%s", name), trimImageTarballTarget(target))
|
||||
}
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if ok {
|
||||
selectedTargets = append(selectedTargets, target)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
std.Out.WriteLine(output.Styledf(output.StylePending, "Found Bazel targets: \n%s", strings.Join(selectedTargets, "\n")))
|
||||
|
||||
commandText := fmt.Sprintf("sg bazel build %s", strings.Join(selectedTargets, " "))
|
||||
std.Out.WriteLine(output.Styledf(output.StyleBold, "Running Bazel 'build' command for you"))
|
||||
std.Out.WriteLine(output.Styledf(output.StyleYellow, " "+commandText))
|
||||
std.Out.WriteLine(output.Styledf(output.StyleReset, "--- 👇 Bazel output ---"))
|
||||
|
||||
// Please note the added --color flag here, to ensure we keep the colors when streaming back the output.
|
||||
// And we run `bazel` directly, because we know that for build/run you don't need `sg bazel` but our users
|
||||
// don't have to know that.
|
||||
cmd := run.Bash(cctx.Context, fmt.Sprintf("bazel build --color=yes %s", strings.Join(selectedTargets, " ")))
|
||||
if err := cmd.Run().Stream(os.Stdout); err != nil {
|
||||
return err
|
||||
}
|
||||
std.Out.WriteLine(output.Styledf(output.StyleReset, "----------------------"))
|
||||
|
||||
if cctx.Bool("load") {
|
||||
for _, target := range selectedTargets {
|
||||
commandText := fmt.Sprintf("bazel run %s", target)
|
||||
std.Out.WriteLine(output.Styledf(output.StyleBold, "Running Bazel command for you"))
|
||||
std.Out.WriteLine(output.Styledf(output.StyleYellow, " "+commandText))
|
||||
std.Out.WriteLine(output.Styledf(output.StyleReset, "--- 👇 Bazel output ---"))
|
||||
|
||||
// Please note the added --color flag here, to ensure we keep the colors when streaming back the output.
|
||||
cmd := run.Bash(cctx.Context, fmt.Sprintf("sg bazel run --color=yes %s", target))
|
||||
if err := cmd.Run().Stream(os.Stdout); err != nil {
|
||||
return err
|
||||
}
|
||||
std.Out.WriteLine(output.Styledf(output.StyleReset, "----------------------"))
|
||||
}
|
||||
}
|
||||
return nil
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
func listBazelOCITarballs(ctx context.Context) ([]string, error) {
|
||||
cmd := run.Cmd(ctx, "bazel", "query", "kind('oci_tarball', //...)")
|
||||
lines, err := cmd.Run().Lines()
|
||||
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "failed to list bazel images")
|
||||
}
|
||||
|
||||
if len(lines) > 0 {
|
||||
if strings.HasPrefix(lines[0], "Loading") {
|
||||
// Trim the first line, which is just "Loading: (...)"
|
||||
lines = lines[1:]
|
||||
}
|
||||
}
|
||||
|
||||
return lines, nil
|
||||
}
|
||||
|
||||
// trimImageTarballTarget simplifies the Bazel target path for easier matching with glob syntax.
|
||||
// For example, the target '//cmd/worker:image_tarball' becomes 'worker'.
|
||||
func trimImageTarballTarget(target string) string {
|
||||
// Remove the leading '//'
|
||||
target = strings.TrimPrefix(target, "//")
|
||||
|
||||
// Remove the trailing ':.*'
|
||||
reg := regexp.MustCompile(":.*")
|
||||
target = reg.ReplaceAllString(target, "")
|
||||
|
||||
return target
|
||||
}
|
||||
32
dev/sg/sg_images_test.go
Normal file
32
dev/sg/sg_images_test.go
Normal file
@ -0,0 +1,32 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func Test_trimImageTarballTarget(t *testing.T) {
|
||||
tests := []struct {
|
||||
target string
|
||||
want string
|
||||
}{
|
||||
{
|
||||
target: "//cmd/worker:image_tarball",
|
||||
want: "cmd/worker",
|
||||
},
|
||||
{
|
||||
target: "//docker-images/caddy:image_tarball",
|
||||
want: "docker-images/caddy",
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(fmt.Sprintf("%q to %q", tt.target, tt.want), func(t *testing.T) {
|
||||
got := trimImageTarballTarget(tt.target)
|
||||
if got != tt.want {
|
||||
t.Logf("got %q but wanted %q", got, tt.want)
|
||||
t.Fail()
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
Loading…
Reference in New Issue
Block a user