repo_description: Automation engine to build, test and ship any codebase. Runs locally, in CI, or directly in the cloud
repo_topics: agents ai caching ci-cd containers continuous-deployment continuous-integration dag dagger devops docker graphql workflows
code: package main
  
  import (
  	"context"
  	_ "embed"
  	"encoding/json"
  	"fmt"
  	"path/filepath"
  
  	"dagger.io/dagger"
  	"github.com/dagger/dagger/engine/client"
  	"github.com/dagger/dagger/engine/client/pathutil"
  	"github.com/juju/ansiterm/tabwriter"
  	"github.com/spf13/cobra"
  )
  
  var (
  	generator      string
  	listJSONOutput bool
  )
  
  func init() {
  	clientListCmd.Flags().BoolVar(&listJSONOutput, "json", false, "Output the list of available clients in JSON format")
  
  	clientCmd.AddCommand(clientInstallCmd)
  	clientCmd.AddCommand(clientListCmd)
  	clientCmd.AddCommand(clientUninstallCmd)
  	clientCmd.AddCommand(clientUpdateCmd)
  }
  
  var clientCmd = &cobra.Command{
  	Use:    "client",
  	Short:  "Access Dagger client subcommands",
  	Hidden: true,
  	Annotations: map[string]string{
  		"experimental": "true",
  	},
  }
  
  var clientInstallCmd = &cobra.Command{
  	Use:     "install [options] generator [path]",
  	Aliases: []string{"use"},
  	Short:   "Generate a new Dagger client from the Dagger module",
  	Example: "dagger client install go ./dagger",
  	RunE: func(cmd *cobra.Command, args []string) error {
  		return withEngine(cmd.Context(), client.Params{}, func(ctx context.Context, engineClient *client.Client) error {
  			// default the output to the current working directory if it doesn't exist yet
  			cwd, err := pathutil.Getwd()
  			if err != nil {
  				return fmt.Errorf("failed to get current working directory: %w", err)
  			}
  
  			switch len(args) {
  			case 0:
  				return fmt.Errorf("generator must set (ts, go, python or custom generator)")
  			case 1:
  				generator = args[0]
  				outputPath = filepath.Join(cwd, "dagger")
  			case 2:
  				generator = args[0]
  				outputPath = args[1]
  			}
  
  			if filepath.IsAbs(outputPath) {
  				outputPath, err = filepath.Rel(cwd, outputPath)
  				if err != nil {
  					return fmt.Errorf("failed to get relative path: %w", err)
  				}
  			}
  
  			dag := engineClient.Dagger()
  
  			mod, err := initializeClientGeneratorModule(ctx, dag, ".")
  			if err != nil {
  				return fmt.Errorf("failed to initialize client generator module: %w", err)
  			}
  
  			contextDirPath, err := mod.Source.LocalContextDirectoryPath(ctx)
  			if err != nil {
  				return fmt.Errorf("failed to get local context directory path: %w", err)
  			}
  
  			_, err = mod.Source.
  				WithClient(generator, outputPath).
  				GeneratedContextDirectory().
  				Export(ctx, contextDirPath)
  			if err != nil {
  				return fmt.Errorf("failed to export client: %w", err)
  			}
  
  			w := cmd.OutOrStdout()
  			fmt.Fprintf(w, "Generated client at %s\n", outputPath)
  
  			return nil
  		})
  	},
  	Annotations: map[string]string{
  		"experimental": "true",
  	},
  }
  
  var clientUninstallCmd = &cobra.Command{
  	Use:     "uninstall [path]",
  	Short:   "Remove a Dagger client from the module",
  	Example: "dagger client uninstall ./dagger",
  	RunE: func(cmd *cobra.Command, args []string) error {
  		return withEngine(cmd.Context(), client.Params{}, func(ctx context.Context, engineClient *client.Client) error {
  			if len(args) != 1 {
  				return fmt.Errorf("expected only the path to the generated client to be set as argument")
  			}
  
  			cwd, err := pathutil.Getwd()
  			if err != nil {
  				return fmt.Errorf("failed to get current working directory: %w", err)
  			}
  
  			path := args[0]
  
  			if filepath.IsAbs(path) {
  				path, err = filepath.Rel(cwd, path)
  				if err != nil {
  					return fmt.Errorf("failed to get relative path: %w", err)
  				}
  			}
  
  			dag := engineClient.Dagger()
  
  			mod, err := initializeClientGeneratorModule(ctx, dag, ".")
  			if err != nil {
  				return fmt.Errorf("failed to initialize client generator module: %w", err)
  			}
  
  			contextDirPath, err := mod.Source.LocalContextDirectoryPath(ctx)
  			if err != nil {
  				return fmt.Errorf("failed to get local context directory path: %w", err)
  			}
  
  			_, err = mod.Source.
  				WithoutClient(path).
  				GeneratedContextDirectory().
  				Export(ctx, contextDirPath)
  			if err != nil {
  				return fmt.Errorf("failed to remove client from module: %w", err)
  			}
  
  			w := cmd.OutOrStdout()
  			fmt.Fprintf(w,
  				"Client at %s removed from config.\n"+
  					"Please manually remove any remaining files of the generated client from your host\n",
  				path)
  
  			return nil
  		})
  	},
  }
  
  //go:embed clientconf.graphql
  var loadModClientConfQuery string
  
  var clientListCmd = &cobra.Command{
  	Use:     "list",
  	Short:   "List all Dagger clients installed in the current module",
  	Example: "dagger client list",
  	RunE: func(cmd *cobra.Command, args []string) error {
  		return withEngine(cmd.Context(), client.Params{}, func(ctx context.Context, engineClient *client.Client) error {
  			dag := engineClient.Dagger()
  
  			mod, err := initializeClientGeneratorModule(ctx, dag, ".")
  			if err != nil {
  				return fmt.Errorf("failed to initialize client generator module: %w", err)
  			}
  
  			moduleSourceID, err := mod.Source.ID(ctx)
  			if err != nil {
  				return fmt.Errorf("failed to list clients: failed to get module source id: %w", err)
  			}
  
  			var res struct {
  				Source struct {
  					ConfigClients []struct {
  						Generator string
  						Directory string
  					}
  				}
  			}
  
  			err = dag.Do(ctx, &dagger.Request{
  				Query: loadModClientConfQuery,
  				Variables: map[string]any{
  					"source": moduleSourceID,
  				},
  			}, &dagger.Response{
  				Data: &res,
  			})
  
  			if err != nil {
  				return fmt.Errorf("failed to list clients: failed to get module source config clients: %w", err)
  			}
  
  			if listJSONOutput {
  				clientsContent, err := json.Marshal(res.Source.ConfigClients)
  				if err != nil {
  					return fmt.Errorf("failed to marshal clients results: %w", err)
  				}
  
  				cmd.OutOrStdout().Write(clientsContent)
  				return nil
  			}
  
  			tw := tabwriter.NewWriter(cmd.OutOrStdout(), 0, 0, 3, ' ', tabwriter.DiscardEmptyColumns)
  			fmt.Fprintf(tw, "GENERATOR\tPATH\n")
  			for _, client := range res.Source.ConfigClients {
  				fmt.Fprintf(tw, "%s\t%s\n", client.Generator, client.Directory)
  			}
  
  			return tw.Flush()
  		})
  	},
  }
  
  var clientUpdateCmd = &cobra.Command{
  	Use:     "update [<client>...]",
  	Short:   "Update one or more dagger clients in the current module",
  	Example: "dagger client update github.com/shykes/x/hello@v0.3.0",
  	RunE: func(cmd *cobra.Command, args []string) error {
  		return withEngine(cmd.Context(), client.Params{}, func(ctx context.Context, engineClient *client.Client) error {
  			dag := engineClient.Dagger()
  
  			mod, err := initializeClientGeneratorModule(ctx, dag, ".")
  			if err != nil {
  				return fmt.Errorf("failed to initialize client generator module: %w", err)
  			}
  
  			contextDirPath, err := mod.Source.LocalContextDirectoryPath(ctx)
  			if err != nil {
  				return fmt.Errorf("failed to get local context directory path: %w", err)
  			}
  
  			_, err = mod.Source.
  				WithUpdatedClients(args).
  				GeneratedContextDirectory().
  				Export(ctx, contextDirPath)
  			if err != nil {
  				return fmt.Errorf("failed to update clients: %w", err)
  			}
  
  			w := cmd.OutOrStdout()
  			_, _ = fmt.Fprintln(w, "clients updated")
  
  			return nil
  		})
  	},
  }
  