From db234f388b85d11dfe805c8bd1e976b23f910994 Mon Sep 17 00:00:00 2001 From: Jake Coffman Date: Tue, 21 May 2024 14:12:07 -0500 Subject: [PATCH 1/2] hack: dependabot ls command --- cmd/dependabot/internal/cmd/ls.go | 91 +++++++++++++++++++++++++++++++ internal/infra/run.go | 15 +++++ internal/server/api.go | 22 ++++++-- 3 files changed, 122 insertions(+), 6 deletions(-) create mode 100644 cmd/dependabot/internal/cmd/ls.go diff --git a/cmd/dependabot/internal/cmd/ls.go b/cmd/dependabot/internal/cmd/ls.go new file mode 100644 index 0000000..e5ba43a --- /dev/null +++ b/cmd/dependabot/internal/cmd/ls.go @@ -0,0 +1,91 @@ +package cmd + +import ( + "context" + "errors" + "github.com/MakeNowJust/heredoc" + "github.com/dependabot/cli/internal/infra" + "github.com/spf13/cobra" + "log" +) + +var listCmd = NewListCommand() + +func init() { + rootCmd.AddCommand(listCmd) +} + +func NewListCommand() *cobra.Command { + var flags UpdateFlags + + cmd := &cobra.Command{ + Use: "ls [ ] [flags]", + Short: "List the dependencies of a manifest/lockfile", + Example: heredoc.Doc(` + $ dependabot ls go_modules rsc/quote + $ dependabot ls go_modules --local . + `), + RunE: func(cmd *cobra.Command, args []string) error { + input, err := readArguments(cmd, &flags) + if err != nil { + return err + } + + processInput(input, &flags) + + input.Job.Source.Provider = "github" // TODO why isn't this being set? + + if err := infra.Run(infra.RunParams{ + CacheDir: flags.cache, + CollectorConfigPath: flags.collectorConfigPath, + CollectorImage: collectorImage, + Creds: input.Credentials, + Debug: flags.debugging, + Flamegraph: flags.flamegraph, + Expected: nil, // update subcommand doesn't use expectations + ExtraHosts: flags.extraHosts, + InputName: flags.file, + Job: &input.Job, + ListDependencies: true, // list dependencies, then exit + LocalDir: flags.local, + Output: flags.output, + ProxyCertPath: flags.proxyCertPath, + ProxyImage: proxyImage, + PullImages: flags.pullImages, + Timeout: flags.timeout, + UpdaterImage: updaterImage, + Volumes: flags.volumes, + Writer: nil, // prevent outputting all API responses to stdout, we only want dependencies + }); err != nil { + if errors.Is(err, context.DeadlineExceeded) { + log.Fatalf("update timed out after %s", flags.timeout) + } + // HACK: we cancel context to stop the containers, so we don't know if there was a failure. + // A correct solution would involve changes with dependabot-core, which is good, but + // I am just hacking this together right now. + // log.Fatalf("updater failure: %v", err) + return nil + } + + return nil + }, + } + + cmd.Flags().StringVarP(&flags.branch, "branch", "b", "", "target branch to update") + cmd.Flags().StringVarP(&flags.directory, "directory", "d", "/", "directory to update") + cmd.Flags().StringVarP(&flags.commit, "commit", "", "", "commit to update") + + cmd.Flags().StringVarP(&flags.output, "output", "o", "", "write scenario to file") + cmd.Flags().StringVar(&flags.cache, "cache", "", "cache import/export directory") + cmd.Flags().StringVar(&flags.local, "local", "", "local directory to use as fetched source") + cmd.Flags().StringVar(&flags.proxyCertPath, "proxy-cert", "", "path to a certificate the proxy will trust") + cmd.Flags().StringVar(&flags.collectorConfigPath, "collector-config", "", "path to an OpenTelemetry collector config file") + cmd.Flags().BoolVar(&flags.pullImages, "pull", true, "pull the image if it isn't present") + cmd.Flags().BoolVar(&flags.debugging, "debug", false, "run an interactive shell inside the updater") + cmd.Flags().BoolVar(&flags.flamegraph, "flamegraph", false, "generate a flamegraph and other metrics") + cmd.Flags().StringArrayVarP(&flags.volumes, "volume", "v", nil, "mount volumes in Docker") + cmd.Flags().StringArrayVar(&flags.extraHosts, "extra-hosts", nil, "Docker extra hosts setting on the proxy") + cmd.Flags().DurationVarP(&flags.timeout, "timeout", "t", 0, "max time to run an update") + + return cmd +} diff --git a/internal/infra/run.go b/internal/infra/run.go index a04982a..20ec13a 100644 --- a/internal/infra/run.go +++ b/internal/infra/run.go @@ -35,6 +35,8 @@ type RunParams struct { Job *model.Job // expectations asserted at the end of a test Expected []model.Output + // if true, the containers will be stopped once the dependencies are listed + ListDependencies bool // directory to copy into the updater container as the repo LocalDir string // credentials passed to the proxy @@ -107,6 +109,19 @@ func Run(params RunParams) error { api := server.NewAPI(params.Expected, params.Writer) defer api.Stop() + if params.ListDependencies { + go func() { + dependencyList := <-api.UpdateDependencyList + encoder := json.NewEncoder(os.Stdout) + encoder.SetIndent("", " ") + err := encoder.Encode(dependencyList.Dependencies) + if err != nil { + log.Printf("failed to write dependency list: %v\n", err) + } + cancel() + }() + } + var outFile *os.File if params.Output != "" { var err error diff --git a/internal/server/api.go b/internal/server/api.go index 00d24d5..84b880f 100644 --- a/internal/server/api.go +++ b/internal/server/api.go @@ -36,6 +36,9 @@ type API struct { hasExpectations bool port int writer io.Writer + + // UpdateDependencyList is a channel you can listen on to get the dependencies as soon as they're published + UpdateDependencyList chan model.UpdateDependencyList } // NewAPI creates a new API instance and starts the server @@ -63,12 +66,13 @@ func NewAPI(expected []model.Output, writer io.Writer) *API { IdleTimeout: 60 * time.Second, } api := &API{ - server: server, - Expectations: expected, - writer: writer, - cursor: 0, - hasExpectations: len(expected) > 0, - port: l.Addr().(*net.TCPAddr).Port, + server: server, + Expectations: expected, + writer: writer, + cursor: 0, + hasExpectations: len(expected) > 0, + port: l.Addr().(*net.TCPAddr).Port, + UpdateDependencyList: make(chan model.UpdateDependencyList, 1), // buffer of 1 to prevent blocking } server.Handler = api @@ -122,6 +126,12 @@ func (a *API) ServeHTTP(w http.ResponseWriter, r *http.Request) { a.pushError(err) } + switch object := actual.Data.(type) { + case model.UpdateDependencyList: + a.UpdateDependencyList <- object + close(a.UpdateDependencyList) + } + if actual == nil { // indicates the kind (endpoint) isn't implemented in decodeWrapper, so return a 501 w.WriteHeader(http.StatusNotImplemented) From 18283dda428874f3a76fd3e0917a4ba346482acd Mon Sep 17 00:00:00 2001 From: Jake Coffman Date: Wed, 19 Jun 2024 08:39:12 -0500 Subject: [PATCH 2/2] print the error we're suppressing --- cmd/dependabot/internal/cmd/ls.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cmd/dependabot/internal/cmd/ls.go b/cmd/dependabot/internal/cmd/ls.go index e5ba43a..a7dd5fd 100644 --- a/cmd/dependabot/internal/cmd/ls.go +++ b/cmd/dependabot/internal/cmd/ls.go @@ -63,7 +63,7 @@ func NewListCommand() *cobra.Command { // HACK: we cancel context to stop the containers, so we don't know if there was a failure. // A correct solution would involve changes with dependabot-core, which is good, but // I am just hacking this together right now. - // log.Fatalf("updater failure: %v", err) + log.Printf("HACK: suppressing updater failure: %v", err) return nil }