From c33dc5138f9ce0bb699852415da049e55b160034 Mon Sep 17 00:00:00 2001 From: Solomon Hykes Date: Tue, 23 Mar 2021 07:22:50 +0000 Subject: [PATCH 01/23] Mockup CLI backend API Signed-off-by: Solomon Hykes --- dagger/route.go | 56 +++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 56 insertions(+) create mode 100644 dagger/route.go diff --git a/dagger/route.go b/dagger/route.go new file mode 100644 index 00000000..2e9e1992 --- /dev/null +++ b/dagger/route.go @@ -0,0 +1,56 @@ +package dagger + +import ( + "context" + + "dagger.io/go/dagger/compiler" +) + + +// A deployment route +type Route struct { + // Globally unique route ID + ID string + +} + +func CreateRoute(ctx context.Context, opts ...CreateOpt) (*Route, error) { + panic("NOT IMPLEMENTED") +} +type CreateOpt interface{} // FIXME + +func DeleteRoute(ctx context.Context, opts ...DeleteOpt) (*Route, error) { + panic("NOT IMPLEMENTED") +} +type DeleteOpt interface{} // FIXME + +func LookupRoute(name string, opts ...LookupOpt) (string, error) { + panic("NOT IMPLEMENTED") +} +type LookupOpt interface{} // FIXME + + +func LoadRoute(ctx context.Context, ID string, opts ...LoadOpt) (*Route, error) { + panic("NOT IMPLEMENTED") +} +type LoadOpt interface{} // FIXME + + +func (r *Route) Up(ctx context.Context, opts ...UpOpt) error { + panic("NOT IMPLEMENTED") +} +type UpOpt interface{} // FIXME + +func (r *Route) Down(ctx context.Context, opts ...DownOpt) error { + panic("NOT IMPLEMENTED") +} +type DownOpt interface{} // FIXME + +func (r *Route) Query(ctx context.Context, expr interface{}, opts ...QueryOpt) (*compiler.Value, error) { + panic("NOT IMPLEMENTED") +} +type QueryOpt interface{} // FIXME + +// FIXME: manage base +// FIXME: manage inputs +// FIXME: manage outputs From 84188535f30b6829f38186fc6fbe381f9d21be2f Mon Sep 17 00:00:00 2001 From: Sam Alba Date: Tue, 23 Mar 2021 15:27:16 -0700 Subject: [PATCH 02/23] implemented basic commands: up, down, new, query, list Signed-off-by: Sam Alba --- cmd/dagger/cmd/common.go | 33 ++++++++++++++++++++ cmd/dagger/cmd/down.go | 45 +++++++++++++++++++++++++++ cmd/dagger/cmd/list.go | 31 +++++++++++++++++++ cmd/dagger/cmd/new.go | 66 ++++++++++++++++++++++++++++++++++++++++ cmd/dagger/cmd/query.go | 58 +++++++++++++++++++++++++++++++++++ cmd/dagger/cmd/root.go | 17 +++++------ cmd/dagger/cmd/up.go | 45 +++++++++++++++++++++++++++ dagger/route.go | 15 +++++---- 8 files changed, 294 insertions(+), 16 deletions(-) create mode 100644 cmd/dagger/cmd/common.go create mode 100644 cmd/dagger/cmd/down.go create mode 100644 cmd/dagger/cmd/list.go create mode 100644 cmd/dagger/cmd/new.go create mode 100644 cmd/dagger/cmd/query.go create mode 100644 cmd/dagger/cmd/up.go diff --git a/cmd/dagger/cmd/common.go b/cmd/dagger/cmd/common.go new file mode 100644 index 00000000..a3de45b2 --- /dev/null +++ b/cmd/dagger/cmd/common.go @@ -0,0 +1,33 @@ +package cmd + +import ( + "os" + "path/filepath" + + "github.com/rs/zerolog" + "github.com/spf13/cobra" +) + +// getRouteName returns the selected route name (based on explicit CLI selection or current work dir) +func getRouteName(lg zerolog.Logger, cmd *cobra.Command) string { + routeName, err := cmd.Flags().GetString("route") + if err != nil { + lg.Fatal().Err(err).Str("flag", "route").Msg("unable to resolve flag") + } + + if routeName != "" { + return routeName + } + + workDir, err := os.Getwd() + if err != nil { + lg.Fatal().Err(err).Msg("failed to get current working dir") + } + + currentDir := filepath.Base(workDir) + if currentDir == "/" { + return "root" + } + + return currentDir +} diff --git a/cmd/dagger/cmd/down.go b/cmd/dagger/cmd/down.go new file mode 100644 index 00000000..fb0f3dac --- /dev/null +++ b/cmd/dagger/cmd/down.go @@ -0,0 +1,45 @@ +package cmd + +import ( + "dagger.io/go/cmd/dagger/logger" + "dagger.io/go/dagger" + + "github.com/spf13/cobra" + "github.com/spf13/viper" +) + +var downCmd = &cobra.Command{ + Use: "down", + Short: "Take a route offline (WARNING: may destroy infrastructure)", + Args: cobra.NoArgs, + PreRun: func(cmd *cobra.Command, args []string) { + // Fix Viper bug for duplicate flags: + // https://github.com/spf13/viper/issues/233 + if err := viper.BindPFlags(cmd.Flags()); err != nil { + panic(err) + } + }, + Run: func(cmd *cobra.Command, args []string) { + lg := logger.New() + ctx := lg.WithContext(cmd.Context()) + + routeName := getRouteName(lg, cmd) + route, err := dagger.LookupRoute(routeName) + if err != nil { + lg.Fatal().Err(err).Str("route-name", routeName).Msg("failed to lookup route") + } + + // TODO: Implement options: --no-cache + if err := route.Down(ctx); err != nil { + lg.Fatal().Err(err).Str("route-name", routeName).Str("route-id", route.ID).Msg("failed to up the route") + } + }, +} + +func init() { + newCmd.Flags().Bool("--no-cache", false, "Disable all run cache") + + if err := viper.BindPFlags(newCmd.Flags()); err != nil { + panic(err) + } +} diff --git a/cmd/dagger/cmd/list.go b/cmd/dagger/cmd/list.go new file mode 100644 index 00000000..108f9928 --- /dev/null +++ b/cmd/dagger/cmd/list.go @@ -0,0 +1,31 @@ +package cmd + +import ( + "github.com/spf13/cobra" + "github.com/spf13/viper" +) + +var listCmd = &cobra.Command{ + Use: "list", + Short: "List available routes", + Args: cobra.NoArgs, + PreRun: func(cmd *cobra.Command, args []string) { + // Fix Viper bug for duplicate flags: + // https://github.com/spf13/viper/issues/233 + if err := viper.BindPFlags(cmd.Flags()); err != nil { + panic(err) + } + }, + Run: func(cmd *cobra.Command, args []string) { + // lg := logger.New() + // ctx := lg.WithContext(cmd.Context()) + + panic("not implemented") + }, +} + +func init() { + if err := viper.BindPFlags(listCmd.Flags()); err != nil { + panic(err) + } +} diff --git a/cmd/dagger/cmd/new.go b/cmd/dagger/cmd/new.go new file mode 100644 index 00000000..5ea4a13f --- /dev/null +++ b/cmd/dagger/cmd/new.go @@ -0,0 +1,66 @@ +package cmd + +import ( + "dagger.io/go/cmd/dagger/logger" + "dagger.io/go/dagger" + + "github.com/spf13/cobra" + "github.com/spf13/viper" +) + +var newCmd = &cobra.Command{ + Use: "new", + Short: "Create a new route", + Args: cobra.NoArgs, + PreRun: func(cmd *cobra.Command, args []string) { + // Fix Viper bug for duplicate flags: + // https://github.com/spf13/viper/issues/233 + if err := viper.BindPFlags(cmd.Flags()); err != nil { + panic(err) + } + }, + Run: func(cmd *cobra.Command, args []string) { + lg := logger.New() + ctx := lg.WithContext(cmd.Context()) + + // nolint:staticcheck + upRoute, err := cmd.Flags().GetBool("up") + if err != nil { + lg.Fatal().Err(err).Str("flag", "up").Msg("unable to resolve flag") + } + + // nolint:staticcheck + routeName := getRouteName(lg, cmd) + + // TODO: Implement options: --layout-*, --setup + // FIXME: give route name in create opts + route, err := dagger.CreateRoute(ctx) + if err != nil { + lg.Fatal().Err(err).Msg("failed to create route") + } + lg.Info().Str("route-id", route.ID).Str("route-name", routeName).Msg("created route") + + if upRoute { + lg.Info().Str("route-id", route.ID).Msg("bringing route online") + if err := route.Up(ctx); err != nil { + lg.Fatal().Err(err).Str("route-id", route.ID).Msg("failed to create route") + } + } + }, +} + +func init() { + newCmd.Flags().StringP("name", "n", "", "Specify a route name") + newCmd.Flags().BoolP("up", "u", false, "Bring the route online") + + newCmd.Flags().String("layout-dir", "", "Load layout from a local directory") + newCmd.Flags().String("layout-git", "", "Load layout from a git repository") + newCmd.Flags().String("layout-package", "", "Load layout from a cue package") + newCmd.Flags().String("layout-file", "", "Load layout from a cue or json file") + + newCmd.Flags().String("setup", "auto", "Specify whether to prompt user for initial setup (no|yes|auto)") + + if err := viper.BindPFlags(newCmd.Flags()); err != nil { + panic(err) + } +} diff --git a/cmd/dagger/cmd/query.go b/cmd/dagger/cmd/query.go new file mode 100644 index 00000000..a3d39496 --- /dev/null +++ b/cmd/dagger/cmd/query.go @@ -0,0 +1,58 @@ +package cmd + +import ( + "fmt" + + "dagger.io/go/cmd/dagger/logger" + "dagger.io/go/dagger" + + "github.com/spf13/cobra" + "github.com/spf13/viper" +) + +var queryCmd = &cobra.Command{ + Use: "query", + Short: "Query the contents of a route", + Args: cobra.ExactArgs(1), + PreRun: func(cmd *cobra.Command, args []string) { + // Fix Viper bug for duplicate flags: + // https://github.com/spf13/viper/issues/233 + if err := viper.BindPFlags(cmd.Flags()); err != nil { + panic(err) + } + }, + Run: func(cmd *cobra.Command, args []string) { + lg := logger.New() + ctx := lg.WithContext(cmd.Context()) + + routeName := getRouteName(lg, cmd) + route, err := dagger.LookupRoute(routeName) + if err != nil { + lg.Fatal().Err(err).Str("route-name", routeName).Msg("failed to lookup route") + } + + expr := args[0] + + out, err := route.Query(ctx, expr) + if err != nil { + lg.Fatal().Err(err).Str("route-name", routeName).Str("route-id", route.ID).Msg("failed to query route") + } + + fmt.Println(out) + // TODO: Implement options: --no-*, --format, --revision + + }, +} + +func init() { + newCmd.Flags().String("revision", "latest", "Query a specific version of the route") + newCmd.Flags().StringP("format", "f", "", "Output format (json|yaml|cue|text|env)") + + newCmd.Flags().BoolP("--no-input", "I", false, "Exclude inputs from query") + newCmd.Flags().BoolP("--no-output", "O", false, "Exclude outputs from query") + newCmd.Flags().BoolP("--no-layout", "L", false, "Exclude outputs from query") + + if err := viper.BindPFlags(newCmd.Flags()); err != nil { + panic(err) + } +} diff --git a/cmd/dagger/cmd/root.go b/cmd/dagger/cmd/root.go index d791760b..2bbdf465 100644 --- a/cmd/dagger/cmd/root.go +++ b/cmd/dagger/cmd/root.go @@ -14,24 +14,21 @@ import ( var rootCmd = &cobra.Command{ Use: "dagger", - Short: "Open-source workflow engine", + Short: "A system for application delivery as code (ADC)", } func init() { rootCmd.PersistentFlags().String("log-format", "", "Log format (json, pretty). Defaults to json if the terminal is not a tty") rootCmd.PersistentFlags().StringP("log-level", "l", "debug", "Log level") + rootCmd.PersistentFlags().StringP("route", "r", "", "Select a route") rootCmd.AddCommand( computeCmd, - // Create an env - // Change settings on an env - // View or edit env serti - // settingsCmd, - // Query the state of an env - // getCmd, - // unsetCmd, - // computeCmd, - // listCmd, + newCmd, + listCmd, + queryCmd, + upCmd, + downCmd, ) if err := viper.BindPFlags(rootCmd.PersistentFlags()); err != nil { diff --git a/cmd/dagger/cmd/up.go b/cmd/dagger/cmd/up.go new file mode 100644 index 00000000..833cc7f7 --- /dev/null +++ b/cmd/dagger/cmd/up.go @@ -0,0 +1,45 @@ +package cmd + +import ( + "dagger.io/go/cmd/dagger/logger" + "dagger.io/go/dagger" + + "github.com/spf13/cobra" + "github.com/spf13/viper" +) + +var upCmd = &cobra.Command{ + Use: "up", + Short: "Bring a route online with latest layout and inputs", + Args: cobra.NoArgs, + PreRun: func(cmd *cobra.Command, args []string) { + // Fix Viper bug for duplicate flags: + // https://github.com/spf13/viper/issues/233 + if err := viper.BindPFlags(cmd.Flags()); err != nil { + panic(err) + } + }, + Run: func(cmd *cobra.Command, args []string) { + lg := logger.New() + ctx := lg.WithContext(cmd.Context()) + + routeName := getRouteName(lg, cmd) + route, err := dagger.LookupRoute(routeName) + if err != nil { + lg.Fatal().Err(err).Str("route-name", routeName).Msg("failed to lookup route") + } + + // TODO: Implement options: --no-cache + if err := route.Up(ctx); err != nil { + lg.Fatal().Err(err).Str("route-name", routeName).Str("route-id", route.ID).Msg("failed to up the route") + } + }, +} + +func init() { + newCmd.Flags().Bool("--no-cache", false, "Disable all run cache") + + if err := viper.BindPFlags(newCmd.Flags()); err != nil { + panic(err) + } +} diff --git a/dagger/route.go b/dagger/route.go index 2e9e1992..5773834c 100644 --- a/dagger/route.go +++ b/dagger/route.go @@ -6,49 +6,52 @@ import ( "dagger.io/go/dagger/compiler" ) - // A deployment route type Route struct { // Globally unique route ID ID string - } func CreateRoute(ctx context.Context, opts ...CreateOpt) (*Route, error) { panic("NOT IMPLEMENTED") } + type CreateOpt interface{} // FIXME func DeleteRoute(ctx context.Context, opts ...DeleteOpt) (*Route, error) { panic("NOT IMPLEMENTED") } + type DeleteOpt interface{} // FIXME -func LookupRoute(name string, opts ...LookupOpt) (string, error) { +func LookupRoute(name string, opts ...LookupOpt) (*Route, error) { panic("NOT IMPLEMENTED") } + type LookupOpt interface{} // FIXME - -func LoadRoute(ctx context.Context, ID string, opts ...LoadOpt) (*Route, error) { +func LoadRoute(ctx context.Context, id string, opts ...LoadOpt) (*Route, error) { panic("NOT IMPLEMENTED") } -type LoadOpt interface{} // FIXME +type LoadOpt interface{} // FIXME func (r *Route) Up(ctx context.Context, opts ...UpOpt) error { panic("NOT IMPLEMENTED") } + type UpOpt interface{} // FIXME func (r *Route) Down(ctx context.Context, opts ...DownOpt) error { panic("NOT IMPLEMENTED") } + type DownOpt interface{} // FIXME func (r *Route) Query(ctx context.Context, expr interface{}, opts ...QueryOpt) (*compiler.Value, error) { panic("NOT IMPLEMENTED") } + type QueryOpt interface{} // FIXME // FIXME: manage base From 661affa4cb8d8806a5cc154fbbd162da5a64d5c0 Mon Sep 17 00:00:00 2001 From: Sam Alba Date: Tue, 23 Mar 2021 15:53:36 -0700 Subject: [PATCH 03/23] more basic commands + lint fixes Signed-off-by: Sam Alba --- cmd/dagger/cmd/delete.go | 31 +++++++++++++++++++++++++++++++ cmd/dagger/cmd/down.go | 6 ++++-- cmd/dagger/cmd/history.go | 31 +++++++++++++++++++++++++++++++ cmd/dagger/cmd/login.go | 31 +++++++++++++++++++++++++++++++ cmd/dagger/cmd/logout.go | 31 +++++++++++++++++++++++++++++++ cmd/dagger/cmd/query.go | 13 +++++++------ cmd/dagger/cmd/root.go | 4 ++++ cmd/dagger/cmd/up.go | 4 +++- 8 files changed, 142 insertions(+), 9 deletions(-) create mode 100644 cmd/dagger/cmd/delete.go create mode 100644 cmd/dagger/cmd/history.go create mode 100644 cmd/dagger/cmd/login.go create mode 100644 cmd/dagger/cmd/logout.go diff --git a/cmd/dagger/cmd/delete.go b/cmd/dagger/cmd/delete.go new file mode 100644 index 00000000..8dbc5bd8 --- /dev/null +++ b/cmd/dagger/cmd/delete.go @@ -0,0 +1,31 @@ +package cmd + +import ( + "github.com/spf13/cobra" + "github.com/spf13/viper" +) + +var deleteCmd = &cobra.Command{ + Use: "delete", + Short: "Delete a route after taking it offline (WARNING: may destroy infrastructure)", + Args: cobra.NoArgs, + PreRun: func(cmd *cobra.Command, args []string) { + // Fix Viper bug for duplicate flags: + // https://github.com/spf13/viper/issues/233 + if err := viper.BindPFlags(cmd.Flags()); err != nil { + panic(err) + } + }, + Run: func(cmd *cobra.Command, args []string) { + // lg := logger.New() + // ctx := lg.WithContext(cmd.Context()) + + panic("not implemented") + }, +} + +func init() { + if err := viper.BindPFlags(deleteCmd.Flags()); err != nil { + panic(err) + } +} diff --git a/cmd/dagger/cmd/down.go b/cmd/dagger/cmd/down.go index fb0f3dac..e588e999 100644 --- a/cmd/dagger/cmd/down.go +++ b/cmd/dagger/cmd/down.go @@ -1,3 +1,4 @@ +// nolint:dupl package cmd import ( @@ -21,6 +22,7 @@ var downCmd = &cobra.Command{ }, Run: func(cmd *cobra.Command, args []string) { lg := logger.New() + // nolint:staticcheck ctx := lg.WithContext(cmd.Context()) routeName := getRouteName(lg, cmd) @@ -37,9 +39,9 @@ var downCmd = &cobra.Command{ } func init() { - newCmd.Flags().Bool("--no-cache", false, "Disable all run cache") + downCmd.Flags().Bool("--no-cache", false, "Disable all run cache") - if err := viper.BindPFlags(newCmd.Flags()); err != nil { + if err := viper.BindPFlags(downCmd.Flags()); err != nil { panic(err) } } diff --git a/cmd/dagger/cmd/history.go b/cmd/dagger/cmd/history.go new file mode 100644 index 00000000..05104135 --- /dev/null +++ b/cmd/dagger/cmd/history.go @@ -0,0 +1,31 @@ +package cmd + +import ( + "github.com/spf13/cobra" + "github.com/spf13/viper" +) + +var historyCmd = &cobra.Command{ + Use: "history", + Short: "List past changes to a route", + Args: cobra.NoArgs, + PreRun: func(cmd *cobra.Command, args []string) { + // Fix Viper bug for duplicate flags: + // https://github.com/spf13/viper/issues/233 + if err := viper.BindPFlags(cmd.Flags()); err != nil { + panic(err) + } + }, + Run: func(cmd *cobra.Command, args []string) { + // lg := logger.New() + // ctx := lg.WithContext(cmd.Context()) + + panic("not implemented") + }, +} + +func init() { + if err := viper.BindPFlags(historyCmd.Flags()); err != nil { + panic(err) + } +} diff --git a/cmd/dagger/cmd/login.go b/cmd/dagger/cmd/login.go new file mode 100644 index 00000000..58028c0a --- /dev/null +++ b/cmd/dagger/cmd/login.go @@ -0,0 +1,31 @@ +package cmd + +import ( + "github.com/spf13/cobra" + "github.com/spf13/viper" +) + +var loginCmd = &cobra.Command{ + Use: "login", + Short: "Login to Dagger Cloud", + Args: cobra.NoArgs, + PreRun: func(cmd *cobra.Command, args []string) { + // Fix Viper bug for duplicate flags: + // https://github.com/spf13/viper/issues/233 + if err := viper.BindPFlags(cmd.Flags()); err != nil { + panic(err) + } + }, + Run: func(cmd *cobra.Command, args []string) { + // lg := logger.New() + // ctx := lg.WithContext(cmd.Context()) + + panic("not implemented") + }, +} + +func init() { + if err := viper.BindPFlags(loginCmd.Flags()); err != nil { + panic(err) + } +} diff --git a/cmd/dagger/cmd/logout.go b/cmd/dagger/cmd/logout.go new file mode 100644 index 00000000..484015d5 --- /dev/null +++ b/cmd/dagger/cmd/logout.go @@ -0,0 +1,31 @@ +package cmd + +import ( + "github.com/spf13/cobra" + "github.com/spf13/viper" +) + +var logoutCmd = &cobra.Command{ + Use: "logout", + Short: "Logout from Dagger Cloud", + Args: cobra.NoArgs, + PreRun: func(cmd *cobra.Command, args []string) { + // Fix Viper bug for duplicate flags: + // https://github.com/spf13/viper/issues/233 + if err := viper.BindPFlags(cmd.Flags()); err != nil { + panic(err) + } + }, + Run: func(cmd *cobra.Command, args []string) { + // lg := logger.New() + // ctx := lg.WithContext(cmd.Context()) + + panic("not implemented") + }, +} + +func init() { + if err := viper.BindPFlags(logoutCmd.Flags()); err != nil { + panic(err) + } +} diff --git a/cmd/dagger/cmd/query.go b/cmd/dagger/cmd/query.go index a3d39496..e4527e92 100644 --- a/cmd/dagger/cmd/query.go +++ b/cmd/dagger/cmd/query.go @@ -23,6 +23,7 @@ var queryCmd = &cobra.Command{ }, Run: func(cmd *cobra.Command, args []string) { lg := logger.New() + // nolint:staticcheck ctx := lg.WithContext(cmd.Context()) routeName := getRouteName(lg, cmd) @@ -45,14 +46,14 @@ var queryCmd = &cobra.Command{ } func init() { - newCmd.Flags().String("revision", "latest", "Query a specific version of the route") - newCmd.Flags().StringP("format", "f", "", "Output format (json|yaml|cue|text|env)") + queryCmd.Flags().String("revision", "latest", "Query a specific version of the route") + queryCmd.Flags().StringP("format", "f", "", "Output format (json|yaml|cue|text|env)") - newCmd.Flags().BoolP("--no-input", "I", false, "Exclude inputs from query") - newCmd.Flags().BoolP("--no-output", "O", false, "Exclude outputs from query") - newCmd.Flags().BoolP("--no-layout", "L", false, "Exclude outputs from query") + queryCmd.Flags().BoolP("--no-input", "I", false, "Exclude inputs from query") + queryCmd.Flags().BoolP("--no-output", "O", false, "Exclude outputs from query") + queryCmd.Flags().BoolP("--no-layout", "L", false, "Exclude outputs from query") - if err := viper.BindPFlags(newCmd.Flags()); err != nil { + if err := viper.BindPFlags(queryCmd.Flags()); err != nil { panic(err) } } diff --git a/cmd/dagger/cmd/root.go b/cmd/dagger/cmd/root.go index 2bbdf465..31864c98 100644 --- a/cmd/dagger/cmd/root.go +++ b/cmd/dagger/cmd/root.go @@ -29,6 +29,10 @@ func init() { queryCmd, upCmd, downCmd, + deleteCmd, + historyCmd, + loginCmd, + logoutCmd, ) if err := viper.BindPFlags(rootCmd.PersistentFlags()); err != nil { diff --git a/cmd/dagger/cmd/up.go b/cmd/dagger/cmd/up.go index 833cc7f7..9ee78784 100644 --- a/cmd/dagger/cmd/up.go +++ b/cmd/dagger/cmd/up.go @@ -1,3 +1,4 @@ +// nolint:dupl package cmd import ( @@ -21,6 +22,7 @@ var upCmd = &cobra.Command{ }, Run: func(cmd *cobra.Command, args []string) { lg := logger.New() + // nolint:staticcheck ctx := lg.WithContext(cmd.Context()) routeName := getRouteName(lg, cmd) @@ -39,7 +41,7 @@ var upCmd = &cobra.Command{ func init() { newCmd.Flags().Bool("--no-cache", false, "Disable all run cache") - if err := viper.BindPFlags(newCmd.Flags()); err != nil { + if err := viper.BindPFlags(upCmd.Flags()); err != nil { panic(err) } } From 37bf20e24bb7dd6552a5f2b13fea1236bec66f8c Mon Sep 17 00:00:00 2001 From: Solomon Hykes Date: Tue, 23 Mar 2021 22:41:26 +0000 Subject: [PATCH 04/23] Expand mockup backend for CLI Signed-off-by: Solomon Hykes --- cmd/dagger/cmd/down.go | 4 +- cmd/dagger/cmd/new.go | 5 +-- cmd/dagger/cmd/query.go | 4 +- cmd/dagger/cmd/up.go | 4 +- dagger/env.go | 2 + dagger/route.go | 91 ++++++++++++++++++++++++++++++++++------- 6 files changed, 87 insertions(+), 23 deletions(-) diff --git a/cmd/dagger/cmd/down.go b/cmd/dagger/cmd/down.go index e588e999..b62667b9 100644 --- a/cmd/dagger/cmd/down.go +++ b/cmd/dagger/cmd/down.go @@ -26,13 +26,13 @@ var downCmd = &cobra.Command{ ctx := lg.WithContext(cmd.Context()) routeName := getRouteName(lg, cmd) - route, err := dagger.LookupRoute(routeName) + route, err := dagger.LookupRoute(routeName, nil) if err != nil { lg.Fatal().Err(err).Str("route-name", routeName).Msg("failed to lookup route") } // TODO: Implement options: --no-cache - if err := route.Down(ctx); err != nil { + if err := route.Down(ctx, nil); err != nil { lg.Fatal().Err(err).Str("route-name", routeName).Str("route-id", route.ID).Msg("failed to up the route") } }, diff --git a/cmd/dagger/cmd/new.go b/cmd/dagger/cmd/new.go index 5ea4a13f..f317caca 100644 --- a/cmd/dagger/cmd/new.go +++ b/cmd/dagger/cmd/new.go @@ -33,8 +33,7 @@ var newCmd = &cobra.Command{ routeName := getRouteName(lg, cmd) // TODO: Implement options: --layout-*, --setup - // FIXME: give route name in create opts - route, err := dagger.CreateRoute(ctx) + route, err := dagger.CreateRoute(ctx, routeName, nil) if err != nil { lg.Fatal().Err(err).Msg("failed to create route") } @@ -42,7 +41,7 @@ var newCmd = &cobra.Command{ if upRoute { lg.Info().Str("route-id", route.ID).Msg("bringing route online") - if err := route.Up(ctx); err != nil { + if err := route.Up(ctx, nil); err != nil { lg.Fatal().Err(err).Str("route-id", route.ID).Msg("failed to create route") } } diff --git a/cmd/dagger/cmd/query.go b/cmd/dagger/cmd/query.go index e4527e92..913b03a5 100644 --- a/cmd/dagger/cmd/query.go +++ b/cmd/dagger/cmd/query.go @@ -27,14 +27,14 @@ var queryCmd = &cobra.Command{ ctx := lg.WithContext(cmd.Context()) routeName := getRouteName(lg, cmd) - route, err := dagger.LookupRoute(routeName) + route, err := dagger.LookupRoute(routeName, nil) if err != nil { lg.Fatal().Err(err).Str("route-name", routeName).Msg("failed to lookup route") } expr := args[0] - out, err := route.Query(ctx, expr) + out, err := route.Query(ctx, expr, nil) if err != nil { lg.Fatal().Err(err).Str("route-name", routeName).Str("route-id", route.ID).Msg("failed to query route") } diff --git a/cmd/dagger/cmd/up.go b/cmd/dagger/cmd/up.go index 9ee78784..c04655f1 100644 --- a/cmd/dagger/cmd/up.go +++ b/cmd/dagger/cmd/up.go @@ -26,13 +26,13 @@ var upCmd = &cobra.Command{ ctx := lg.WithContext(cmd.Context()) routeName := getRouteName(lg, cmd) - route, err := dagger.LookupRoute(routeName) + route, err := dagger.LookupRoute(routeName, nil) if err != nil { lg.Fatal().Err(err).Str("route-name", routeName).Msg("failed to lookup route") } // TODO: Implement options: --no-cache - if err := route.Up(ctx); err != nil { + if err := route.Up(ctx, nil); err != nil { lg.Fatal().Err(err).Str("route-name", routeName).Str("route-id", route.ID).Msg("failed to up the route") } }, diff --git a/dagger/env.go b/dagger/env.go index a99534ed..c2e1896e 100644 --- a/dagger/env.go +++ b/dagger/env.go @@ -135,6 +135,8 @@ func (env *Env) LocalDirs() map[string]string { if err != nil { return err } + // nolint:goconst + // FIXME: merge Env into Route, or fix the linter error if do != "local" { return nil } diff --git a/dagger/route.go b/dagger/route.go index 5773834c..3b1fed93 100644 --- a/dagger/route.go +++ b/dagger/route.go @@ -10,49 +10,112 @@ import ( type Route struct { // Globally unique route ID ID string + + // Human-friendly route name. + // A route may have more than one name. + Name string } -func CreateRoute(ctx context.Context, opts ...CreateOpt) (*Route, error) { +func CreateRoute(ctx context.Context, name string, o *CreateOpts) (*Route, error) { panic("NOT IMPLEMENTED") } -type CreateOpt interface{} // FIXME +type CreateOpts struct{} -func DeleteRoute(ctx context.Context, opts ...DeleteOpt) (*Route, error) { +func DeleteRoute(ctx context.Context, o *DeleteOpts) (*Route, error) { panic("NOT IMPLEMENTED") } -type DeleteOpt interface{} // FIXME +type DeleteOpts struct{} -func LookupRoute(name string, opts ...LookupOpt) (*Route, error) { +func LookupRoute(name string, o *LookupOpts) (*Route, error) { panic("NOT IMPLEMENTED") } -type LookupOpt interface{} // FIXME +type LookupOpts struct{} -func LoadRoute(ctx context.Context, id string, opts ...LoadOpt) (*Route, error) { +func LoadRoute(ctx context.Context, id string, o *LoadOpts) (*Route, error) { panic("NOT IMPLEMENTED") } -type LoadOpt interface{} // FIXME +type LoadOpts struct{} -func (r *Route) Up(ctx context.Context, opts ...UpOpt) error { +func (r *Route) Up(ctx context.Context, o *UpOpts) error { panic("NOT IMPLEMENTED") } -type UpOpt interface{} // FIXME +type UpOpts struct{} -func (r *Route) Down(ctx context.Context, opts ...DownOpt) error { +func (r *Route) Down(ctx context.Context, o *DownOpts) error { panic("NOT IMPLEMENTED") } -type DownOpt interface{} // FIXME +type DownOpts struct{} -func (r *Route) Query(ctx context.Context, expr interface{}, opts ...QueryOpt) (*compiler.Value, error) { +func (r *Route) Query(ctx context.Context, expr interface{}, o *QueryOpts) (*compiler.Value, error) { panic("NOT IMPLEMENTED") } -type QueryOpt interface{} // FIXME +type QueryOpts struct{} + +func (r *Route) SetLayout(ctx context.Context, a *Artifact) error { + panic("NOT IMPLEMENTED") +} + +func (r *Route) Layout() (*Artifact, error) { + panic("NOT IMPLEMENTED") +} + +func (r *Route) AddInputArtifact(ctx context.Context, target string, a *Artifact) error { + panic("NOT IMPLEMENTED") +} + +func (r *Route) AddInputValue(ctx context.Context, target string, v *compiler.Value) error { + panic("NOT IMPLEMENTED") +} + +// FIXME: how does remove work? Does it require a specific file layout? +func (r *Route) RemoveInputs(ctx context.Context, target string) error { + panic("NOT IMPLEMENTED") +} + +// FIXME: connect outputs to auto-export values and artifacts. + +// An artifact is a piece of data, like a source code checkout, +// binary bundle, container image, database backup etc. +// +// Artifacts can be passed as inputs, generated dynamically from +// other inputs, and received as outputs. +// +// Under the hood, an artifact is encoded as a LLB pipeline, and +// attached to the cue configuration as a +type Artifact struct { + llb interface{} +} + +func Dir(path string, include []string) *Artifact { + var llb struct { + Do string + Include []string + } + llb.Do = "local" + llb.Include = include + return &Artifact{ + llb: llb, + } +} + +func Git(remote, ref, dir string) *Artifact { + panic("NOT IMPLEMENTED") +} + +func Container(ref string) *Artifact { + panic("NOT IMPLEMENTED") +} + +func LLB(code interface{}) *Artifact { + panic("NOT IMPLEMENTED") +} // FIXME: manage base // FIXME: manage inputs From 9c110716a5ea17205c5ea1e3770e4a51c82b106f Mon Sep 17 00:00:00 2001 From: Sam Alba Date: Tue, 23 Mar 2021 17:05:30 -0700 Subject: [PATCH 05/23] added input, output, layout cmds Signed-off-by: Sam Alba --- cmd/dagger/cmd/input/container.go | 31 +++++++++++++++++++++++++++++++ cmd/dagger/cmd/input/dir.go | 31 +++++++++++++++++++++++++++++++ cmd/dagger/cmd/input/git.go | 31 +++++++++++++++++++++++++++++++ cmd/dagger/cmd/input/root.go | 19 +++++++++++++++++++ cmd/dagger/cmd/input/secret.go | 31 +++++++++++++++++++++++++++++++ cmd/dagger/cmd/input/value.go | 31 +++++++++++++++++++++++++++++++ cmd/dagger/cmd/layout/dir.go | 31 +++++++++++++++++++++++++++++++ cmd/dagger/cmd/layout/file.go | 31 +++++++++++++++++++++++++++++++ cmd/dagger/cmd/layout/git.go | 31 +++++++++++++++++++++++++++++++ cmd/dagger/cmd/layout/package.go | 31 +++++++++++++++++++++++++++++++ cmd/dagger/cmd/layout/root.go | 18 ++++++++++++++++++ cmd/dagger/cmd/output/dir.go | 31 +++++++++++++++++++++++++++++++ cmd/dagger/cmd/output/root.go | 15 +++++++++++++++ cmd/dagger/cmd/query.go | 2 +- cmd/dagger/cmd/root.go | 6 ++++++ 15 files changed, 369 insertions(+), 1 deletion(-) create mode 100644 cmd/dagger/cmd/input/container.go create mode 100644 cmd/dagger/cmd/input/dir.go create mode 100644 cmd/dagger/cmd/input/git.go create mode 100644 cmd/dagger/cmd/input/root.go create mode 100644 cmd/dagger/cmd/input/secret.go create mode 100644 cmd/dagger/cmd/input/value.go create mode 100644 cmd/dagger/cmd/layout/dir.go create mode 100644 cmd/dagger/cmd/layout/file.go create mode 100644 cmd/dagger/cmd/layout/git.go create mode 100644 cmd/dagger/cmd/layout/package.go create mode 100644 cmd/dagger/cmd/layout/root.go create mode 100644 cmd/dagger/cmd/output/dir.go create mode 100644 cmd/dagger/cmd/output/root.go diff --git a/cmd/dagger/cmd/input/container.go b/cmd/dagger/cmd/input/container.go new file mode 100644 index 00000000..5187ca9b --- /dev/null +++ b/cmd/dagger/cmd/input/container.go @@ -0,0 +1,31 @@ +package input + +import ( + "github.com/spf13/cobra" + "github.com/spf13/viper" +) + +var containerCmd = &cobra.Command{ + Use: "container ID", + Short: "Add a container image as input artifact", + Args: cobra.ExactArgs(1), + PreRun: func(cmd *cobra.Command, args []string) { + // Fix Viper bug for duplicate flags: + // https://github.com/spf13/viper/issues/233 + if err := viper.BindPFlags(cmd.Flags()); err != nil { + panic(err) + } + }, + Run: func(cmd *cobra.Command, args []string) { + // lg := logger.New() + // ctx := lg.WithContext(cmd.Context()) + + panic("not implemented") + }, +} + +func init() { + if err := viper.BindPFlags(containerCmd.Flags()); err != nil { + panic(err) + } +} diff --git a/cmd/dagger/cmd/input/dir.go b/cmd/dagger/cmd/input/dir.go new file mode 100644 index 00000000..170aa955 --- /dev/null +++ b/cmd/dagger/cmd/input/dir.go @@ -0,0 +1,31 @@ +package input + +import ( + "github.com/spf13/cobra" + "github.com/spf13/viper" +) + +var dirCmd = &cobra.Command{ + Use: "dir PATH", + Short: "Add a local directory as input artifact", + Args: cobra.ExactArgs(1), + PreRun: func(cmd *cobra.Command, args []string) { + // Fix Viper bug for duplicate flags: + // https://github.com/spf13/viper/issues/233 + if err := viper.BindPFlags(cmd.Flags()); err != nil { + panic(err) + } + }, + Run: func(cmd *cobra.Command, args []string) { + // lg := logger.New() + // ctx := lg.WithContext(cmd.Context()) + + panic("not implemented") + }, +} + +func init() { + if err := viper.BindPFlags(dirCmd.Flags()); err != nil { + panic(err) + } +} diff --git a/cmd/dagger/cmd/input/git.go b/cmd/dagger/cmd/input/git.go new file mode 100644 index 00000000..3452f1a7 --- /dev/null +++ b/cmd/dagger/cmd/input/git.go @@ -0,0 +1,31 @@ +package input + +import ( + "github.com/spf13/cobra" + "github.com/spf13/viper" +) + +var gitCmd = &cobra.Command{ + Use: "git REMOTE REF [SUBDIR]", + Short: "Add a git repository as input artifact", + Args: cobra.MinimumNArgs(2), + PreRun: func(cmd *cobra.Command, args []string) { + // Fix Viper bug for duplicate flags: + // https://github.com/spf13/viper/issues/233 + if err := viper.BindPFlags(cmd.Flags()); err != nil { + panic(err) + } + }, + Run: func(cmd *cobra.Command, args []string) { + // lg := logger.New() + // ctx := lg.WithContext(cmd.Context()) + + panic("not implemented") + }, +} + +func init() { + if err := viper.BindPFlags(gitCmd.Flags()); err != nil { + panic(err) + } +} diff --git a/cmd/dagger/cmd/input/root.go b/cmd/dagger/cmd/input/root.go new file mode 100644 index 00000000..a8cb9f06 --- /dev/null +++ b/cmd/dagger/cmd/input/root.go @@ -0,0 +1,19 @@ +package input + +import "github.com/spf13/cobra" + +// Cmd exposes the top-level command +var Cmd = &cobra.Command{ + Use: "input", + Short: "Manage a route's inputs", +} + +func init() { + Cmd.AddCommand( + dirCmd, + gitCmd, + containerCmd, + secretCmd, + valueCmd, + ) +} diff --git a/cmd/dagger/cmd/input/secret.go b/cmd/dagger/cmd/input/secret.go new file mode 100644 index 00000000..b1abd078 --- /dev/null +++ b/cmd/dagger/cmd/input/secret.go @@ -0,0 +1,31 @@ +package input + +import ( + "github.com/spf13/cobra" + "github.com/spf13/viper" +) + +var secretCmd = &cobra.Command{ + Use: "secret VALUE", + Short: "Add an encrypted input secret", + Args: cobra.ExactArgs(1), + PreRun: func(cmd *cobra.Command, args []string) { + // Fix Viper bug for duplicate flags: + // https://github.com/spf13/viper/issues/233 + if err := viper.BindPFlags(cmd.Flags()); err != nil { + panic(err) + } + }, + Run: func(cmd *cobra.Command, args []string) { + // lg := logger.New() + // ctx := lg.WithContext(cmd.Context()) + + panic("not implemented") + }, +} + +func init() { + if err := viper.BindPFlags(secretCmd.Flags()); err != nil { + panic(err) + } +} diff --git a/cmd/dagger/cmd/input/value.go b/cmd/dagger/cmd/input/value.go new file mode 100644 index 00000000..f8573493 --- /dev/null +++ b/cmd/dagger/cmd/input/value.go @@ -0,0 +1,31 @@ +package input + +import ( + "github.com/spf13/cobra" + "github.com/spf13/viper" +) + +var valueCmd = &cobra.Command{ + Use: "value VALUE", + Short: "Add an input value", + Args: cobra.ExactArgs(1), + PreRun: func(cmd *cobra.Command, args []string) { + // Fix Viper bug for duplicate flags: + // https://github.com/spf13/viper/issues/233 + if err := viper.BindPFlags(cmd.Flags()); err != nil { + panic(err) + } + }, + Run: func(cmd *cobra.Command, args []string) { + // lg := logger.New() + // ctx := lg.WithContext(cmd.Context()) + + panic("not implemented") + }, +} + +func init() { + if err := viper.BindPFlags(valueCmd.Flags()); err != nil { + panic(err) + } +} diff --git a/cmd/dagger/cmd/layout/dir.go b/cmd/dagger/cmd/layout/dir.go new file mode 100644 index 00000000..21a1fc4f --- /dev/null +++ b/cmd/dagger/cmd/layout/dir.go @@ -0,0 +1,31 @@ +package layout + +import ( + "github.com/spf13/cobra" + "github.com/spf13/viper" +) + +var dirCmd = &cobra.Command{ + Use: "dir PATH", + Short: "Load layout from a local directory", + Args: cobra.ExactArgs(1), + PreRun: func(cmd *cobra.Command, args []string) { + // Fix Viper bug for duplicate flags: + // https://github.com/spf13/viper/issues/233 + if err := viper.BindPFlags(cmd.Flags()); err != nil { + panic(err) + } + }, + Run: func(cmd *cobra.Command, args []string) { + // lg := logger.New() + // ctx := lg.WithContext(cmd.Context()) + + panic("not implemented") + }, +} + +func init() { + if err := viper.BindPFlags(dirCmd.Flags()); err != nil { + panic(err) + } +} diff --git a/cmd/dagger/cmd/layout/file.go b/cmd/dagger/cmd/layout/file.go new file mode 100644 index 00000000..de503531 --- /dev/null +++ b/cmd/dagger/cmd/layout/file.go @@ -0,0 +1,31 @@ +package layout + +import ( + "github.com/spf13/cobra" + "github.com/spf13/viper" +) + +var fileCmd = &cobra.Command{ + Use: "file PATH|-", + Short: "Load layout from a cue file", + Args: cobra.ExactArgs(1), + PreRun: func(cmd *cobra.Command, args []string) { + // Fix Viper bug for duplicate flags: + // https://github.com/spf13/viper/issues/233 + if err := viper.BindPFlags(cmd.Flags()); err != nil { + panic(err) + } + }, + Run: func(cmd *cobra.Command, args []string) { + // lg := logger.New() + // ctx := lg.WithContext(cmd.Context()) + + panic("not implemented") + }, +} + +func init() { + if err := viper.BindPFlags(fileCmd.Flags()); err != nil { + panic(err) + } +} diff --git a/cmd/dagger/cmd/layout/git.go b/cmd/dagger/cmd/layout/git.go new file mode 100644 index 00000000..dd4ca02d --- /dev/null +++ b/cmd/dagger/cmd/layout/git.go @@ -0,0 +1,31 @@ +package layout + +import ( + "github.com/spf13/cobra" + "github.com/spf13/viper" +) + +var gitCmd = &cobra.Command{ + Use: "git REMOTE REF [SUBDIR]", + Short: "Load layout from a git package", + Args: cobra.MinimumNArgs(2), + PreRun: func(cmd *cobra.Command, args []string) { + // Fix Viper bug for duplicate flags: + // https://github.com/spf13/viper/issues/233 + if err := viper.BindPFlags(cmd.Flags()); err != nil { + panic(err) + } + }, + Run: func(cmd *cobra.Command, args []string) { + // lg := logger.New() + // ctx := lg.WithContext(cmd.Context()) + + panic("not implemented") + }, +} + +func init() { + if err := viper.BindPFlags(gitCmd.Flags()); err != nil { + panic(err) + } +} diff --git a/cmd/dagger/cmd/layout/package.go b/cmd/dagger/cmd/layout/package.go new file mode 100644 index 00000000..e8705d7a --- /dev/null +++ b/cmd/dagger/cmd/layout/package.go @@ -0,0 +1,31 @@ +package layout + +import ( + "github.com/spf13/cobra" + "github.com/spf13/viper" +) + +var packageCmd = &cobra.Command{ + Use: "package PKG", + Short: "Load layout from a cue package", + Args: cobra.ExactArgs(1), + PreRun: func(cmd *cobra.Command, args []string) { + // Fix Viper bug for duplicate flags: + // https://github.com/spf13/viper/issues/233 + if err := viper.BindPFlags(cmd.Flags()); err != nil { + panic(err) + } + }, + Run: func(cmd *cobra.Command, args []string) { + // lg := logger.New() + // ctx := lg.WithContext(cmd.Context()) + + panic("not implemented") + }, +} + +func init() { + if err := viper.BindPFlags(packageCmd.Flags()); err != nil { + panic(err) + } +} diff --git a/cmd/dagger/cmd/layout/root.go b/cmd/dagger/cmd/layout/root.go new file mode 100644 index 00000000..9f968a5c --- /dev/null +++ b/cmd/dagger/cmd/layout/root.go @@ -0,0 +1,18 @@ +package layout + +import "github.com/spf13/cobra" + +// Cmd exposes the top-level command +var Cmd = &cobra.Command{ + Use: "layout", + Short: "Manage a route's layout", +} + +func init() { + Cmd.AddCommand( + packageCmd, + dirCmd, + gitCmd, + fileCmd, + ) +} diff --git a/cmd/dagger/cmd/output/dir.go b/cmd/dagger/cmd/output/dir.go new file mode 100644 index 00000000..9de621d3 --- /dev/null +++ b/cmd/dagger/cmd/output/dir.go @@ -0,0 +1,31 @@ +package output + +import ( + "github.com/spf13/cobra" + "github.com/spf13/viper" +) + +var dirCmd = &cobra.Command{ + Use: "dir PATH", + Short: "Add a local directory as output artifact", + Args: cobra.ExactArgs(1), + PreRun: func(cmd *cobra.Command, args []string) { + // Fix Viper bug for duplicate flags: + // https://github.com/spf13/viper/issues/233 + if err := viper.BindPFlags(cmd.Flags()); err != nil { + panic(err) + } + }, + Run: func(cmd *cobra.Command, args []string) { + // lg := logger.New() + // ctx := lg.WithContext(cmd.Context()) + + panic("not implemented") + }, +} + +func init() { + if err := viper.BindPFlags(dirCmd.Flags()); err != nil { + panic(err) + } +} diff --git a/cmd/dagger/cmd/output/root.go b/cmd/dagger/cmd/output/root.go new file mode 100644 index 00000000..057c9a37 --- /dev/null +++ b/cmd/dagger/cmd/output/root.go @@ -0,0 +1,15 @@ +package output + +import "github.com/spf13/cobra" + +// Cmd exposes the top-level command +var Cmd = &cobra.Command{ + Use: "output", + Short: "Manage a route's outputs", +} + +func init() { + Cmd.AddCommand( + dirCmd, + ) +} diff --git a/cmd/dagger/cmd/query.go b/cmd/dagger/cmd/query.go index 913b03a5..e60f6a47 100644 --- a/cmd/dagger/cmd/query.go +++ b/cmd/dagger/cmd/query.go @@ -11,7 +11,7 @@ import ( ) var queryCmd = &cobra.Command{ - Use: "query", + Use: "query [EXPR] [flags]", Short: "Query the contents of a route", Args: cobra.ExactArgs(1), PreRun: func(cmd *cobra.Command, args []string) { diff --git a/cmd/dagger/cmd/root.go b/cmd/dagger/cmd/root.go index 31864c98..420c2b8e 100644 --- a/cmd/dagger/cmd/root.go +++ b/cmd/dagger/cmd/root.go @@ -4,6 +4,9 @@ import ( "os" "strings" + inputCmd "dagger.io/go/cmd/dagger/cmd/input" + "dagger.io/go/cmd/dagger/cmd/layout" + "dagger.io/go/cmd/dagger/cmd/output" "dagger.io/go/cmd/dagger/logger" "github.com/moby/buildkit/util/appcontext" "github.com/opentracing/opentracing-go" @@ -33,6 +36,9 @@ func init() { historyCmd, loginCmd, logoutCmd, + layout.Cmd, + inputCmd.Cmd, + output.Cmd, ) if err := viper.BindPFlags(rootCmd.PersistentFlags()); err != nil { From 7bf05d3cc96b071131e74efc4d659dc0f854cfff Mon Sep 17 00:00:00 2001 From: Solomon Hykes Date: Wed, 24 Mar 2021 17:37:50 +0000 Subject: [PATCH 06/23] CLI backend to manage layout and inputs Signed-off-by: Solomon Hykes --- cmd/dagger/cmd/down.go | 2 +- cmd/dagger/cmd/new.go | 6 +- cmd/dagger/cmd/query.go | 2 +- cmd/dagger/cmd/up.go | 2 +- dagger/deprecated_input.go | 298 +++++++++++++++++++++++++++++++++ dagger/input.go | 335 ++++++++++--------------------------- dagger/route.go | 116 ++++++------- 7 files changed, 447 insertions(+), 314 deletions(-) create mode 100644 dagger/deprecated_input.go diff --git a/cmd/dagger/cmd/down.go b/cmd/dagger/cmd/down.go index b62667b9..64e7f692 100644 --- a/cmd/dagger/cmd/down.go +++ b/cmd/dagger/cmd/down.go @@ -33,7 +33,7 @@ var downCmd = &cobra.Command{ // TODO: Implement options: --no-cache if err := route.Down(ctx, nil); err != nil { - lg.Fatal().Err(err).Str("route-name", routeName).Str("route-id", route.ID).Msg("failed to up the route") + lg.Fatal().Err(err).Str("route-name", routeName).Str("route-id", route.ID()).Msg("failed to up the route") } }, } diff --git a/cmd/dagger/cmd/new.go b/cmd/dagger/cmd/new.go index f317caca..6c8197a3 100644 --- a/cmd/dagger/cmd/new.go +++ b/cmd/dagger/cmd/new.go @@ -37,12 +37,12 @@ var newCmd = &cobra.Command{ if err != nil { lg.Fatal().Err(err).Msg("failed to create route") } - lg.Info().Str("route-id", route.ID).Str("route-name", routeName).Msg("created route") + lg.Info().Str("route-id", route.ID()).Str("route-name", routeName).Msg("created route") if upRoute { - lg.Info().Str("route-id", route.ID).Msg("bringing route online") + lg.Info().Str("route-id", route.ID()).Msg("bringing route online") if err := route.Up(ctx, nil); err != nil { - lg.Fatal().Err(err).Str("route-id", route.ID).Msg("failed to create route") + lg.Fatal().Err(err).Str("route-id", route.ID()).Msg("failed to create route") } } }, diff --git a/cmd/dagger/cmd/query.go b/cmd/dagger/cmd/query.go index e60f6a47..91b0775a 100644 --- a/cmd/dagger/cmd/query.go +++ b/cmd/dagger/cmd/query.go @@ -36,7 +36,7 @@ var queryCmd = &cobra.Command{ out, err := route.Query(ctx, expr, nil) if err != nil { - lg.Fatal().Err(err).Str("route-name", routeName).Str("route-id", route.ID).Msg("failed to query route") + lg.Fatal().Err(err).Str("route-name", routeName).Str("route-id", route.ID()).Msg("failed to query route") } fmt.Println(out) diff --git a/cmd/dagger/cmd/up.go b/cmd/dagger/cmd/up.go index c04655f1..d5c9cf3f 100644 --- a/cmd/dagger/cmd/up.go +++ b/cmd/dagger/cmd/up.go @@ -33,7 +33,7 @@ var upCmd = &cobra.Command{ // TODO: Implement options: --no-cache if err := route.Up(ctx, nil); err != nil { - lg.Fatal().Err(err).Str("route-name", routeName).Str("route-id", route.ID).Msg("failed to up the route") + lg.Fatal().Err(err).Str("route-name", routeName).Str("route-id", route.ID()).Msg("failed to up the route") } }, } diff --git a/dagger/deprecated_input.go b/dagger/deprecated_input.go new file mode 100644 index 00000000..bff4442e --- /dev/null +++ b/dagger/deprecated_input.go @@ -0,0 +1,298 @@ +package dagger + +import ( + "encoding/json" + "errors" + "fmt" + "net/url" + "os" + "strings" + + "cuelang.org/go/cue" + "github.com/spf13/pflag" + + "dagger.io/go/dagger/compiler" + + "go.mozilla.org/sops" + "go.mozilla.org/sops/decrypt" +) + +// A mutable cue value with an API suitable for user inputs, +// such as command-line flag parsing. +type InputValue struct { + root *compiler.Value +} + +func (iv *InputValue) Value() *compiler.Value { + return iv.root +} + +func (iv *InputValue) String() string { + s, _ := iv.root.SourceString() + return s +} + +func NewInputValue(base interface{}) (*InputValue, error) { + root, err := compiler.Compile("base", base) + if err != nil { + return nil, err + } + return &InputValue{ + root: root, + }, nil +} + +func (iv *InputValue) Set(s string, enc func(string) (interface{}, error)) error { + // Split from eg. 'foo.bar={bla:"bla"}` + k, vRaw := splitkv(s) + v, err := enc(vRaw) + if err != nil { + return err + } + root, err := iv.root.MergePath(v, k) + if err != nil { + return err + } + iv.root = root + return nil +} + +// Adapter to receive string values from pflag +func (iv *InputValue) StringFlag() pflag.Value { + return stringFlag{ + iv: iv, + } +} + +type stringFlag struct { + iv *InputValue +} + +func (sf stringFlag) Set(s string) error { + return sf.iv.Set(s, func(s string) (interface{}, error) { + return s, nil + }) +} + +func (sf stringFlag) String() string { + return sf.iv.String() +} + +func (sf stringFlag) Type() string { + return "STRING" +} + +// DIR FLAG +// Receive a local directory path and translate it into a component +func (iv *InputValue) DirFlag(include ...string) pflag.Value { + if include == nil { + include = []string{} + } + return dirFlag{ + iv: iv, + include: include, + } +} + +type dirFlag struct { + iv *InputValue + include []string +} + +func (f dirFlag) Set(s string) error { + return f.iv.Set(s, func(s string) (interface{}, error) { + // FIXME: this is a hack because cue API can't merge into a list + include, err := json.Marshal(f.include) + if err != nil { + return nil, err + } + return compiler.Compile("", fmt.Sprintf( + `#compute: [{do:"local",dir:"%s", include:%s}]`, + s, + include, + )) + }) +} + +func (f dirFlag) String() string { + return f.iv.String() +} + +func (f dirFlag) Type() string { + return "PATH" +} + +// GIT FLAG +// Receive a git repository reference and translate it into a component +func (iv *InputValue) GitFlag() pflag.Value { + return gitFlag{ + iv: iv, + } +} + +type gitFlag struct { + iv *InputValue +} + +func (f gitFlag) Set(s string) error { + return f.iv.Set(s, func(s string) (interface{}, error) { + u, err := url.Parse(s) + if err != nil { + return nil, fmt.Errorf("invalid git url") + } + ref := u.Fragment // eg. #main + u.Fragment = "" + remote := u.String() + + return compiler.Compile("", fmt.Sprintf( + `#compute: [{do:"fetch-git", remote:"%s", ref:"%s"}]`, + remote, + ref, + )) + }) +} + +func (f gitFlag) String() string { + return f.iv.String() +} + +func (f gitFlag) Type() string { + return "REMOTE,REF" +} + +// SOURCE FLAG +// Adapter to receive a simple source description and translate it to a loader script. +// For example 'git+https://github.com/cuelang/cue#master` -> [{do:"git",remote:"https://github.com/cuelang/cue",ref:"master"}] + +func (iv *InputValue) SourceFlag() pflag.Value { + return sourceFlag{ + iv: iv, + } +} + +type sourceFlag struct { + iv *InputValue +} + +func (f sourceFlag) Set(s string) error { + return f.iv.Set(s, func(s string) (interface{}, error) { + u, err := url.Parse(s) + if err != nil { + return nil, err + } + switch u.Scheme { + case "", "file": + return compiler.Compile( + "source", + // FIXME: include only cue files as a shortcut. Make this configurable somehow. + fmt.Sprintf(`[{do:"local",dir:"%s",include:["*.cue","cue.mod"]}]`, u.Host+u.Path), + ) + default: + return nil, fmt.Errorf("unsupported source scheme: %q", u.Scheme) + } + }) +} + +func (f sourceFlag) String() string { + return f.iv.String() +} + +func (f sourceFlag) Type() string { + return "PATH | file://PATH | git+ssh://HOST/PATH | git+https://HOST/PATH" +} + +// RAW CUE FLAG +// Adapter to receive raw cue values from pflag +func (iv *InputValue) CueFlag() pflag.Value { + return cueFlag{ + iv: iv, + } +} + +type cueFlag struct { + iv *InputValue +} + +func (f cueFlag) Set(s string) error { + return f.iv.Set(s, func(s string) (interface{}, error) { + return compiler.Compile("cue input", s) + }) +} + +func (f cueFlag) String() string { + return f.iv.String() +} + +func (f cueFlag) Type() string { + return "CUE" +} + +func (iv *InputValue) YAMLFlag() pflag.Value { + return fileFlag{ + iv: iv, + format: "yaml", + } +} + +func (iv *InputValue) JSONFlag() pflag.Value { + return fileFlag{ + iv: iv, + format: "json", + } +} + +type fileFlag struct { + format string + iv *InputValue +} + +func (f fileFlag) Set(s string) error { + return f.iv.Set(s, func(s string) (interface{}, error) { + content, err := os.ReadFile(s) + if err != nil { + return nil, err + } + + plaintext, err := decrypt.Data(content, f.format) + if err != nil && !errors.Is(err, sops.MetadataNotFound) { + return nil, fmt.Errorf("unable to decrypt %q: %w", s, err) + } + + if len(plaintext) > 0 { + content = plaintext + } + + switch f.format { + case "json": + return compiler.DecodeJSON(s, content) + case "yaml": + return compiler.DecodeYAML(s, content) + default: + panic("unsupported file format") + } + }) +} + +func (f fileFlag) String() string { + return f.iv.String() +} + +func (f fileFlag) Type() string { + return strings.ToUpper(f.format) +} + +// UTILITIES + +func splitkv(kv string) (cue.Path, string) { + parts := strings.SplitN(kv, "=", 2) + if len(parts) == 2 { + if parts[0] == "." || parts[0] == "" { + return cue.MakePath(), parts[1] + } + return cue.ParsePath(parts[0]), parts[1] + } + if len(parts) == 1 { + return cue.MakePath(), parts[0] + } + return cue.MakePath(), "" +} diff --git a/dagger/input.go b/dagger/input.go index bff4442e..b58aacb9 100644 --- a/dagger/input.go +++ b/dagger/input.go @@ -2,297 +2,144 @@ package dagger import ( "encoding/json" - "errors" "fmt" - "net/url" - "os" - "strings" - - "cuelang.org/go/cue" - "github.com/spf13/pflag" "dagger.io/go/dagger/compiler" - - "go.mozilla.org/sops" - "go.mozilla.org/sops/decrypt" ) -// A mutable cue value with an API suitable for user inputs, -// such as command-line flag parsing. -type InputValue struct { - root *compiler.Value +// An input is a value or artifact supplied by the user. +// +// - A value is any structured data which can be encoded as cue. +// +// - An artifact is a piece of data, like a source code checkout, +// binary bundle, docker image, database backup etc. +// +// Artifacts can be passed as inputs, generated dynamically from +// other inputs, and received as outputs. +// Under the hood, an artifact is encoded as a LLB pipeline, and +// attached to the cue configuration as a +// +type Input interface { + // Compile to a cue value which can be merged into a route config + Compile() (*compiler.Value, error) } -func (iv *InputValue) Value() *compiler.Value { - return iv.root +// An input artifact loaded from a local directory +func DirInput(path string, include []string) Input { + return &dirInput{ + Type: "dir", + Path: path, + Include: include, + } } -func (iv *InputValue) String() string { - s, _ := iv.root.SourceString() - return s +type dirInput struct { + Type string + Path string + Include []string } -func NewInputValue(base interface{}) (*InputValue, error) { - root, err := compiler.Compile("base", base) +func (dir dirInput) Compile() (*compiler.Value, error) { + // FIXME: serialize an intermediate struct, instead of generating cue source + includeLLB, err := json.Marshal(dir.Include) if err != nil { return nil, err } - return &InputValue{ - root: root, - }, nil + llb := fmt.Sprintf( + `[{do:"local",dir:"%s",include:%s}]`, + dir.Path, + includeLLB, + ) + return compiler.Compile("", llb) } -func (iv *InputValue) Set(s string, enc func(string) (interface{}, error)) error { - // Split from eg. 'foo.bar={bla:"bla"}` - k, vRaw := splitkv(s) - v, err := enc(vRaw) - if err != nil { - return err - } - root, err := iv.root.MergePath(v, k) - if err != nil { - return err - } - iv.root = root - return nil +// An input artifact loaded from a git repository +type gitInput struct { + Type string + Remote string + Ref string + Dir string } -// Adapter to receive string values from pflag -func (iv *InputValue) StringFlag() pflag.Value { - return stringFlag{ - iv: iv, +func GitInput(remote, ref, dir string) Input { + return &gitInput{ + Type: "git", + Remote: remote, + Ref: ref, + Dir: dir, } } -type stringFlag struct { - iv *InputValue +func (git gitInput) Compile() (*compiler.Value, error) { + panic("NOT IMPLEMENTED") } -func (sf stringFlag) Set(s string) error { - return sf.iv.Set(s, func(s string) (interface{}, error) { - return s, nil - }) -} - -func (sf stringFlag) String() string { - return sf.iv.String() -} - -func (sf stringFlag) Type() string { - return "STRING" -} - -// DIR FLAG -// Receive a local directory path and translate it into a component -func (iv *InputValue) DirFlag(include ...string) pflag.Value { - if include == nil { - include = []string{} - } - return dirFlag{ - iv: iv, - include: include, +// An input artifact loaded from a docker container +func DockerInput(ref string) Input { + return &dockerInput{ + Type: "docker", + Ref: ref, } } -type dirFlag struct { - iv *InputValue - include []string +type dockerInput struct { + Type string + Ref string } -func (f dirFlag) Set(s string) error { - return f.iv.Set(s, func(s string) (interface{}, error) { - // FIXME: this is a hack because cue API can't merge into a list - include, err := json.Marshal(f.include) - if err != nil { - return nil, err - } - return compiler.Compile("", fmt.Sprintf( - `#compute: [{do:"local",dir:"%s", include:%s}]`, - s, - include, - )) - }) +func (i dockerInput) Compile() (*compiler.Value, error) { + panic("NOT IMPLEMENTED") } -func (f dirFlag) String() string { - return f.iv.String() -} - -func (f dirFlag) Type() string { - return "PATH" -} - -// GIT FLAG -// Receive a git repository reference and translate it into a component -func (iv *InputValue) GitFlag() pflag.Value { - return gitFlag{ - iv: iv, +// An input value encoded as text +func TextInput(data string) Input { + return &textInput{ + Type: "text", + Data: data, } } -type gitFlag struct { - iv *InputValue +type textInput struct { + Type string + Data string } -func (f gitFlag) Set(s string) error { - return f.iv.Set(s, func(s string) (interface{}, error) { - u, err := url.Parse(s) - if err != nil { - return nil, fmt.Errorf("invalid git url") - } - ref := u.Fragment // eg. #main - u.Fragment = "" - remote := u.String() - - return compiler.Compile("", fmt.Sprintf( - `#compute: [{do:"fetch-git", remote:"%s", ref:"%s"}]`, - remote, - ref, - )) - }) +func (i textInput) Compile() (*compiler.Value, error) { + panic("NOT IMPLEMENTED") } -func (f gitFlag) String() string { - return f.iv.String() -} - -func (f gitFlag) Type() string { - return "REMOTE,REF" -} - -// SOURCE FLAG -// Adapter to receive a simple source description and translate it to a loader script. -// For example 'git+https://github.com/cuelang/cue#master` -> [{do:"git",remote:"https://github.com/cuelang/cue",ref:"master"}] - -func (iv *InputValue) SourceFlag() pflag.Value { - return sourceFlag{ - iv: iv, +// An input value encoded as JSON +func JSONInput(data string) Input { + return &jsonInput{ + Type: "json", + Data: data, } } -type sourceFlag struct { - iv *InputValue +type jsonInput struct { + Type string + // Marshalled JSON data + Data string } -func (f sourceFlag) Set(s string) error { - return f.iv.Set(s, func(s string) (interface{}, error) { - u, err := url.Parse(s) - if err != nil { - return nil, err - } - switch u.Scheme { - case "", "file": - return compiler.Compile( - "source", - // FIXME: include only cue files as a shortcut. Make this configurable somehow. - fmt.Sprintf(`[{do:"local",dir:"%s",include:["*.cue","cue.mod"]}]`, u.Host+u.Path), - ) - default: - return nil, fmt.Errorf("unsupported source scheme: %q", u.Scheme) - } - }) +func (i jsonInput) Compile() (*compiler.Value, error) { + panic("NOT IMPLEMENTED") } -func (f sourceFlag) String() string { - return f.iv.String() -} - -func (f sourceFlag) Type() string { - return "PATH | file://PATH | git+ssh://HOST/PATH | git+https://HOST/PATH" -} - -// RAW CUE FLAG -// Adapter to receive raw cue values from pflag -func (iv *InputValue) CueFlag() pflag.Value { - return cueFlag{ - iv: iv, +// An input value encoded as YAML +func YAMLInput(data string) Input { + return &yamlInput{ + Type: "yaml", + Data: data, } } -type cueFlag struct { - iv *InputValue +type yamlInput struct { + Type string + // Marshalled YAML data + Data string } -func (f cueFlag) Set(s string) error { - return f.iv.Set(s, func(s string) (interface{}, error) { - return compiler.Compile("cue input", s) - }) -} - -func (f cueFlag) String() string { - return f.iv.String() -} - -func (f cueFlag) Type() string { - return "CUE" -} - -func (iv *InputValue) YAMLFlag() pflag.Value { - return fileFlag{ - iv: iv, - format: "yaml", - } -} - -func (iv *InputValue) JSONFlag() pflag.Value { - return fileFlag{ - iv: iv, - format: "json", - } -} - -type fileFlag struct { - format string - iv *InputValue -} - -func (f fileFlag) Set(s string) error { - return f.iv.Set(s, func(s string) (interface{}, error) { - content, err := os.ReadFile(s) - if err != nil { - return nil, err - } - - plaintext, err := decrypt.Data(content, f.format) - if err != nil && !errors.Is(err, sops.MetadataNotFound) { - return nil, fmt.Errorf("unable to decrypt %q: %w", s, err) - } - - if len(plaintext) > 0 { - content = plaintext - } - - switch f.format { - case "json": - return compiler.DecodeJSON(s, content) - case "yaml": - return compiler.DecodeYAML(s, content) - default: - panic("unsupported file format") - } - }) -} - -func (f fileFlag) String() string { - return f.iv.String() -} - -func (f fileFlag) Type() string { - return strings.ToUpper(f.format) -} - -// UTILITIES - -func splitkv(kv string) (cue.Path, string) { - parts := strings.SplitN(kv, "=", 2) - if len(parts) == 2 { - if parts[0] == "." || parts[0] == "" { - return cue.MakePath(), parts[1] - } - return cue.ParsePath(parts[0]), parts[1] - } - if len(parts) == 1 { - return cue.MakePath(), parts[0] - } - return cue.MakePath(), "" +func (i yamlInput) Compile() (*compiler.Value, error) { + panic("NOT IMPLEMENTED") } diff --git a/dagger/route.go b/dagger/route.go index 3b1fed93..5d02e0db 100644 --- a/dagger/route.go +++ b/dagger/route.go @@ -8,16 +8,67 @@ import ( // A deployment route type Route struct { + st routeState +} + +func (r Route) ID() string { + return r.st.ID +} + +func (r Route) Name() string { + return r.st.Name +} + +func (r Route) LayoutSource() Input { + return r.st.LayoutSource +} + +func (r *Route) SetLayoutSource(ctx context.Context, src Input) error { + r.st.LayoutSource = src + return nil +} + +func (r *Route) AddInput(ctx context.Context, key string, value Input) error { + r.st.Inputs = append(r.st.Inputs, inputKV{Key: key, Value: value}) + return nil +} + +// Remove all inputs at the given key, including sub-keys. +// For example RemoveInputs("foo.bar") will remove all inputs +// at foo.bar, foo.bar.baz, etc. +func (r *Route) RemoveInputs(ctx context.Context, key string) error { + panic("NOT IMPLEMENTED") +} + +// Contents of a route serialized to a file +type routeState struct { // Globally unique route ID ID string // Human-friendly route name. // A route may have more than one name. + // FIXME: store multiple names? Name string + + // Cue module containing the route layout + // The input's top-level artifact is used as a module directory. + LayoutSource Input + + Inputs []inputKV +} + +type inputKV struct { + Key string + Value Input } func CreateRoute(ctx context.Context, name string, o *CreateOpts) (*Route, error) { - panic("NOT IMPLEMENTED") + return &Route{ + st: routeState{ + ID: "FIXME", + Name: name, + }, + }, nil } type CreateOpts struct{} @@ -57,66 +108,3 @@ func (r *Route) Query(ctx context.Context, expr interface{}, o *QueryOpts) (*com } type QueryOpts struct{} - -func (r *Route) SetLayout(ctx context.Context, a *Artifact) error { - panic("NOT IMPLEMENTED") -} - -func (r *Route) Layout() (*Artifact, error) { - panic("NOT IMPLEMENTED") -} - -func (r *Route) AddInputArtifact(ctx context.Context, target string, a *Artifact) error { - panic("NOT IMPLEMENTED") -} - -func (r *Route) AddInputValue(ctx context.Context, target string, v *compiler.Value) error { - panic("NOT IMPLEMENTED") -} - -// FIXME: how does remove work? Does it require a specific file layout? -func (r *Route) RemoveInputs(ctx context.Context, target string) error { - panic("NOT IMPLEMENTED") -} - -// FIXME: connect outputs to auto-export values and artifacts. - -// An artifact is a piece of data, like a source code checkout, -// binary bundle, container image, database backup etc. -// -// Artifacts can be passed as inputs, generated dynamically from -// other inputs, and received as outputs. -// -// Under the hood, an artifact is encoded as a LLB pipeline, and -// attached to the cue configuration as a -type Artifact struct { - llb interface{} -} - -func Dir(path string, include []string) *Artifact { - var llb struct { - Do string - Include []string - } - llb.Do = "local" - llb.Include = include - return &Artifact{ - llb: llb, - } -} - -func Git(remote, ref, dir string) *Artifact { - panic("NOT IMPLEMENTED") -} - -func Container(ref string) *Artifact { - panic("NOT IMPLEMENTED") -} - -func LLB(code interface{}) *Artifact { - panic("NOT IMPLEMENTED") -} - -// FIXME: manage base -// FIXME: manage inputs -// FIXME: manage outputs From 43b3af6fff9a5f2bc5f144e3d9db6bd61a3cb039 Mon Sep 17 00:00:00 2001 From: Sam Alba Date: Wed, 24 Mar 2021 15:03:05 -0700 Subject: [PATCH 07/23] init local route dir Signed-off-by: Sam Alba --- dagger/route.go | 22 ++++++++++++++++++---- go.mod | 1 + go.sum | 2 ++ 3 files changed, 21 insertions(+), 4 deletions(-) diff --git a/dagger/route.go b/dagger/route.go index 5d02e0db..5a9764fb 100644 --- a/dagger/route.go +++ b/dagger/route.go @@ -2,24 +2,38 @@ package dagger import ( "context" + "os" "dagger.io/go/dagger/compiler" + + "github.com/google/uuid" ) +const ( + routeLocation = "$HOME/.config/dagger/routes" +) + +func init() { + f := os.ExpandEnv(routeLocation) + if err := os.MkdirAll(f, 0755); err != nil { + panic(err) + } +} + // A deployment route type Route struct { st routeState } -func (r Route) ID() string { +func (r *Route) ID() string { return r.st.ID } -func (r Route) Name() string { +func (r *Route) Name() string { return r.st.Name } -func (r Route) LayoutSource() Input { +func (r *Route) LayoutSource() Input { return r.st.LayoutSource } @@ -65,7 +79,7 @@ type inputKV struct { func CreateRoute(ctx context.Context, name string, o *CreateOpts) (*Route, error) { return &Route{ st: routeState{ - ID: "FIXME", + ID: uuid.New().String(), Name: name, }, }, nil diff --git a/go.mod b/go.mod index e23764ae..c5d39008 100644 --- a/go.mod +++ b/go.mod @@ -8,6 +8,7 @@ require ( github.com/containerd/console v1.0.1 github.com/docker/distribution v2.7.1+incompatible github.com/emicklei/proto v1.9.0 // indirect + github.com/google/uuid v1.2.0 // indirect github.com/jaguilar/vt100 v0.0.0-20150826170717-2703a27b14ea github.com/mitchellh/colorstring v0.0.0-20190213212951-d06e56a500db github.com/moby/buildkit v0.8.2 diff --git a/go.sum b/go.sum index 90a59599..4275008e 100644 --- a/go.sum +++ b/go.sum @@ -659,6 +659,8 @@ github.com/google/subcommands v1.0.1/go.mod h1:ZjhPrFU+Olkh9WazFPsl27BQ4UPiG37m3 github.com/google/uuid v1.0.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/uuid v1.1.1 h1:Gkbcsh/GbpXz7lPftLA3P6TYMwjCLYm83jiFQZF/3gY= github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/google/uuid v1.2.0 h1:qJYtXnJRWmpe7m/3XlyhrsLrEURqHRM2kxzoxXqyUDs= +github.com/google/uuid v1.2.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/wire v0.3.0/go.mod h1:i1DMg/Lu8Sz5yYl25iOdmc5CT5qusaa+zmRWs16741s= github.com/google/wire v0.4.0 h1:kXcsA/rIGzJImVqPdhfnr6q0xsS9gU0515q1EPpJ9fE= github.com/google/wire v0.4.0/go.mod h1:ngWDr9Qvq3yZA10YrxfyGELY/AFWGVpy9c1LTRi1EoU= From cba524eb0fa6fe78d73e969a9a9df0b94fde744a Mon Sep 17 00:00:00 2001 From: Andrea Luzzardi Date: Wed, 24 Mar 2021 17:55:21 -0700 Subject: [PATCH 08/23] merge Route into Env, rebase `dagger compute` to use the new API Signed-off-by: Andrea Luzzardi --- cmd/dagger/cmd/compute.go | 132 ++++++++--- cmd/dagger/cmd/new.go | 3 +- cmd/dagger/cmd/up.go | 3 +- dagger/client.go | 6 +- dagger/dagger_test.go | 4 +- dagger/deprecated_input.go | 298 ------------------------ dagger/env.go | 323 ------------------------- dagger/input.go | 38 +-- dagger/input_test.go | 2 +- dagger/route.go | 401 +++++++++++++++++++++++++------- dagger/state.go | 132 +++++++++++ examples/react-netlify/main.cue | 1 + tests/test.sh | 7 +- 13 files changed, 578 insertions(+), 772 deletions(-) delete mode 100644 dagger/deprecated_input.go delete mode 100644 dagger/env.go create mode 100644 dagger/state.go diff --git a/cmd/dagger/cmd/compute.go b/cmd/dagger/cmd/compute.go index dcee0ffb..1559cd92 100644 --- a/cmd/dagger/cmd/compute.go +++ b/cmd/dagger/cmd/compute.go @@ -1,20 +1,22 @@ package cmd import ( + "encoding/json" + "errors" "fmt" + "os" + "strings" "dagger.io/go/cmd/dagger/logger" "dagger.io/go/dagger" + "go.mozilla.org/sops" + "go.mozilla.org/sops/decrypt" + "github.com/google/uuid" "github.com/spf13/cobra" "github.com/spf13/viper" ) -var ( - input *dagger.InputValue - updater *dagger.InputValue -) - var computeCmd = &cobra.Command{ Use: "compute CONFIG", Short: "Compute a configuration", @@ -30,25 +32,100 @@ var computeCmd = &cobra.Command{ lg := logger.New() ctx := lg.WithContext(cmd.Context()) - env, err := dagger.NewEnv() - if err != nil { - lg.Fatal().Err(err).Msg("unable to initialize environment") - } - if err := updater.SourceFlag().Set(args[0]); err != nil { - lg.Fatal().Err(err).Msg("invalid local source") + name := getRouteName(lg, cmd) + st := &dagger.RouteState{ + ID: uuid.New().String(), + Name: name, + LayoutSource: dagger.DirInput(args[0], []string{"*.cue", "cue.mod"}), } - if err := env.SetUpdater(updater.Value()); err != nil { - lg.Fatal().Err(err).Msg("invalid updater script") + for _, input := range viper.GetStringSlice("input-string") { + parts := strings.SplitN(input, "=", 2) + k, v := parts[0], parts[1] + err := st.AddInput(ctx, k, dagger.TextInput(v)) + if err != nil { + lg.Fatal().Err(err).Str("input", k).Msg("failed to add input") + } } - if err := env.SetInput(input.Value()); err != nil { - lg.Fatal().Err(err).Msg("invalid input") + + for _, input := range viper.GetStringSlice("input-dir") { + parts := strings.SplitN(input, "=", 2) + k, v := parts[0], parts[1] + err := st.AddInput(ctx, k, dagger.DirInput(v, []string{})) + if err != nil { + lg.Fatal().Err(err).Str("input", k).Msg("failed to add input") + } } + + for _, input := range viper.GetStringSlice("input-git") { + parts := strings.SplitN(input, "=", 2) + k, v := parts[0], parts[1] + err := st.AddInput(ctx, k, dagger.GitInput(v, "", "")) + if err != nil { + lg.Fatal().Err(err).Str("input", k).Msg("failed to add input") + } + } + + if f := viper.GetString("input-json"); f != "" { + lg := lg.With().Str("path", f).Logger() + + content, err := os.ReadFile(f) + if err != nil { + lg.Fatal().Err(err).Msg("failed to read file") + } + + plaintext, err := decrypt.Data(content, "json") + if err != nil && !errors.Is(err, sops.MetadataNotFound) { + lg.Fatal().Err(err).Msg("unable to decrypt") + } + + if len(plaintext) > 0 { + content = plaintext + } + + if !json.Valid(content) { + lg.Fatal().Msg("invalid json") + } + + err = st.AddInput(ctx, "", dagger.JSONInput(string(content))) + if err != nil { + lg.Fatal().Err(err).Msg("failed to add input") + } + } + + if f := viper.GetString("input-yaml"); f != "" { + lg := lg.With().Str("path", f).Logger() + + content, err := os.ReadFile(f) + if err != nil { + lg.Fatal().Err(err).Msg("failed to read file") + } + + plaintext, err := decrypt.Data(content, "yaml") + if err != nil && !errors.Is(err, sops.MetadataNotFound) { + lg.Fatal().Err(err).Msg("unable to decrypt") + } + + if len(plaintext) > 0 { + content = plaintext + } + + err = st.AddInput(ctx, "", dagger.YAMLInput(string(content))) + if err != nil { + lg.Fatal().Err(err).Msg("failed to add input") + } + } + + route, err := dagger.NewRoute(st) + if err != nil { + lg.Fatal().Err(err).Msg("unable to initialize route") + } + c, err := dagger.NewClient(ctx, "") if err != nil { lg.Fatal().Err(err).Msg("unable to create client") } - output, err := c.Compute(ctx, env) + output, err := c.Up(ctx, route) if err != nil { lg.Fatal().Err(err).Msg("failed to compute") } @@ -57,24 +134,11 @@ var computeCmd = &cobra.Command{ } func init() { - var err error - // Setup --input-* flags - input, err = dagger.NewInputValue("{}") - if err != nil { - panic(err) - } - computeCmd.Flags().Var(input.StringFlag(), "input-string", "TARGET=STRING") - computeCmd.Flags().Var(input.DirFlag(), "input-dir", "TARGET=PATH") - computeCmd.Flags().Var(input.GitFlag(), "input-git", "TARGET=REMOTE#REF") - computeCmd.Flags().Var(input.CueFlag(), "input-cue", "CUE") - computeCmd.Flags().Var(input.JSONFlag(), "input-json", "JSON") - computeCmd.Flags().Var(input.YAMLFlag(), "input-yaml", "YAML") - - // Setup (future) --from-* flags - updater, err = dagger.NewInputValue("[...{do:string, ...}]") - if err != nil { - panic(err) - } + computeCmd.Flags().StringSlice("input-string", []string{}, "TARGET=STRING") + computeCmd.Flags().StringSlice("input-dir", []string{}, "TARGET=PATH") + computeCmd.Flags().StringSlice("input-git", []string{}, "TARGET=REMOTE#REF") + computeCmd.Flags().String("input-json", "", "JSON") + computeCmd.Flags().String("input-yaml", "", "YAML") if err := viper.BindPFlags(computeCmd.Flags()); err != nil { panic(err) diff --git a/cmd/dagger/cmd/new.go b/cmd/dagger/cmd/new.go index 6c8197a3..38cf0e24 100644 --- a/cmd/dagger/cmd/new.go +++ b/cmd/dagger/cmd/new.go @@ -41,7 +41,8 @@ var newCmd = &cobra.Command{ if upRoute { lg.Info().Str("route-id", route.ID()).Msg("bringing route online") - if err := route.Up(ctx, nil); err != nil { + // FIXME + if err := route.FIXME(ctx); err != nil { lg.Fatal().Err(err).Str("route-id", route.ID()).Msg("failed to create route") } } diff --git a/cmd/dagger/cmd/up.go b/cmd/dagger/cmd/up.go index d5c9cf3f..ded638a0 100644 --- a/cmd/dagger/cmd/up.go +++ b/cmd/dagger/cmd/up.go @@ -32,7 +32,8 @@ var upCmd = &cobra.Command{ } // TODO: Implement options: --no-cache - if err := route.Up(ctx, nil); err != nil { + // FIXME + if err := route.FIXME(ctx); err != nil { lg.Fatal().Err(err).Str("route-name", routeName).Str("route-id", route.ID()).Msg("failed to up the route") } }, diff --git a/dagger/client.go b/dagger/client.go index 6ae4ebbc..399166e9 100644 --- a/dagger/client.go +++ b/dagger/client.go @@ -61,7 +61,7 @@ func NewClient(ctx context.Context, host string) (*Client, error) { } // FIXME: return completed *Env, instead of *compiler.Value -func (c *Client) Compute(ctx context.Context, env *Env) (*compiler.Value, error) { +func (c *Client) Up(ctx context.Context, env *Route) (*compiler.Value, error) { lg := log.Ctx(ctx) eg, gctx := errgroup.WithContext(ctx) @@ -95,7 +95,7 @@ func (c *Client) Compute(ctx context.Context, env *Env) (*compiler.Value, error) return out, compiler.Err(eg.Wait()) } -func (c *Client) buildfn(ctx context.Context, env *Env, ch chan *bk.SolveStatus, w io.WriteCloser) error { +func (c *Client) buildfn(ctx context.Context, env *Route, ch chan *bk.SolveStatus, w io.WriteCloser) error { lg := log.Ctx(ctx) // Scan local dirs to grant access @@ -138,7 +138,7 @@ func (c *Client) buildfn(ctx context.Context, env *Env, ch chan *bk.SolveStatus, // Compute output overlay lg.Debug().Msg("computing env") - if err := env.Compute(ctx, s); err != nil { + if err := env.Up(ctx, s, nil); err != nil { return nil, err } diff --git a/dagger/dagger_test.go b/dagger/dagger_test.go index 2e78eecb..43dd99c6 100644 --- a/dagger/dagger_test.go +++ b/dagger/dagger_test.go @@ -33,8 +33,8 @@ func TestLocalDirs(t *testing.T) { } } -func mkEnv(t *testing.T, updater, input string) *Env { - env, err := NewEnv() +func mkEnv(t *testing.T, updater, input string) *Route { + env, err := NewRoute() if err != nil { t.Fatal(err) } diff --git a/dagger/deprecated_input.go b/dagger/deprecated_input.go deleted file mode 100644 index bff4442e..00000000 --- a/dagger/deprecated_input.go +++ /dev/null @@ -1,298 +0,0 @@ -package dagger - -import ( - "encoding/json" - "errors" - "fmt" - "net/url" - "os" - "strings" - - "cuelang.org/go/cue" - "github.com/spf13/pflag" - - "dagger.io/go/dagger/compiler" - - "go.mozilla.org/sops" - "go.mozilla.org/sops/decrypt" -) - -// A mutable cue value with an API suitable for user inputs, -// such as command-line flag parsing. -type InputValue struct { - root *compiler.Value -} - -func (iv *InputValue) Value() *compiler.Value { - return iv.root -} - -func (iv *InputValue) String() string { - s, _ := iv.root.SourceString() - return s -} - -func NewInputValue(base interface{}) (*InputValue, error) { - root, err := compiler.Compile("base", base) - if err != nil { - return nil, err - } - return &InputValue{ - root: root, - }, nil -} - -func (iv *InputValue) Set(s string, enc func(string) (interface{}, error)) error { - // Split from eg. 'foo.bar={bla:"bla"}` - k, vRaw := splitkv(s) - v, err := enc(vRaw) - if err != nil { - return err - } - root, err := iv.root.MergePath(v, k) - if err != nil { - return err - } - iv.root = root - return nil -} - -// Adapter to receive string values from pflag -func (iv *InputValue) StringFlag() pflag.Value { - return stringFlag{ - iv: iv, - } -} - -type stringFlag struct { - iv *InputValue -} - -func (sf stringFlag) Set(s string) error { - return sf.iv.Set(s, func(s string) (interface{}, error) { - return s, nil - }) -} - -func (sf stringFlag) String() string { - return sf.iv.String() -} - -func (sf stringFlag) Type() string { - return "STRING" -} - -// DIR FLAG -// Receive a local directory path and translate it into a component -func (iv *InputValue) DirFlag(include ...string) pflag.Value { - if include == nil { - include = []string{} - } - return dirFlag{ - iv: iv, - include: include, - } -} - -type dirFlag struct { - iv *InputValue - include []string -} - -func (f dirFlag) Set(s string) error { - return f.iv.Set(s, func(s string) (interface{}, error) { - // FIXME: this is a hack because cue API can't merge into a list - include, err := json.Marshal(f.include) - if err != nil { - return nil, err - } - return compiler.Compile("", fmt.Sprintf( - `#compute: [{do:"local",dir:"%s", include:%s}]`, - s, - include, - )) - }) -} - -func (f dirFlag) String() string { - return f.iv.String() -} - -func (f dirFlag) Type() string { - return "PATH" -} - -// GIT FLAG -// Receive a git repository reference and translate it into a component -func (iv *InputValue) GitFlag() pflag.Value { - return gitFlag{ - iv: iv, - } -} - -type gitFlag struct { - iv *InputValue -} - -func (f gitFlag) Set(s string) error { - return f.iv.Set(s, func(s string) (interface{}, error) { - u, err := url.Parse(s) - if err != nil { - return nil, fmt.Errorf("invalid git url") - } - ref := u.Fragment // eg. #main - u.Fragment = "" - remote := u.String() - - return compiler.Compile("", fmt.Sprintf( - `#compute: [{do:"fetch-git", remote:"%s", ref:"%s"}]`, - remote, - ref, - )) - }) -} - -func (f gitFlag) String() string { - return f.iv.String() -} - -func (f gitFlag) Type() string { - return "REMOTE,REF" -} - -// SOURCE FLAG -// Adapter to receive a simple source description and translate it to a loader script. -// For example 'git+https://github.com/cuelang/cue#master` -> [{do:"git",remote:"https://github.com/cuelang/cue",ref:"master"}] - -func (iv *InputValue) SourceFlag() pflag.Value { - return sourceFlag{ - iv: iv, - } -} - -type sourceFlag struct { - iv *InputValue -} - -func (f sourceFlag) Set(s string) error { - return f.iv.Set(s, func(s string) (interface{}, error) { - u, err := url.Parse(s) - if err != nil { - return nil, err - } - switch u.Scheme { - case "", "file": - return compiler.Compile( - "source", - // FIXME: include only cue files as a shortcut. Make this configurable somehow. - fmt.Sprintf(`[{do:"local",dir:"%s",include:["*.cue","cue.mod"]}]`, u.Host+u.Path), - ) - default: - return nil, fmt.Errorf("unsupported source scheme: %q", u.Scheme) - } - }) -} - -func (f sourceFlag) String() string { - return f.iv.String() -} - -func (f sourceFlag) Type() string { - return "PATH | file://PATH | git+ssh://HOST/PATH | git+https://HOST/PATH" -} - -// RAW CUE FLAG -// Adapter to receive raw cue values from pflag -func (iv *InputValue) CueFlag() pflag.Value { - return cueFlag{ - iv: iv, - } -} - -type cueFlag struct { - iv *InputValue -} - -func (f cueFlag) Set(s string) error { - return f.iv.Set(s, func(s string) (interface{}, error) { - return compiler.Compile("cue input", s) - }) -} - -func (f cueFlag) String() string { - return f.iv.String() -} - -func (f cueFlag) Type() string { - return "CUE" -} - -func (iv *InputValue) YAMLFlag() pflag.Value { - return fileFlag{ - iv: iv, - format: "yaml", - } -} - -func (iv *InputValue) JSONFlag() pflag.Value { - return fileFlag{ - iv: iv, - format: "json", - } -} - -type fileFlag struct { - format string - iv *InputValue -} - -func (f fileFlag) Set(s string) error { - return f.iv.Set(s, func(s string) (interface{}, error) { - content, err := os.ReadFile(s) - if err != nil { - return nil, err - } - - plaintext, err := decrypt.Data(content, f.format) - if err != nil && !errors.Is(err, sops.MetadataNotFound) { - return nil, fmt.Errorf("unable to decrypt %q: %w", s, err) - } - - if len(plaintext) > 0 { - content = plaintext - } - - switch f.format { - case "json": - return compiler.DecodeJSON(s, content) - case "yaml": - return compiler.DecodeYAML(s, content) - default: - panic("unsupported file format") - } - }) -} - -func (f fileFlag) String() string { - return f.iv.String() -} - -func (f fileFlag) Type() string { - return strings.ToUpper(f.format) -} - -// UTILITIES - -func splitkv(kv string) (cue.Path, string) { - parts := strings.SplitN(kv, "=", 2) - if len(parts) == 2 { - if parts[0] == "." || parts[0] == "" { - return cue.MakePath(), parts[1] - } - return cue.ParsePath(parts[0]), parts[1] - } - if len(parts) == 1 { - return cue.MakePath(), parts[0] - } - return cue.MakePath(), "" -} diff --git a/dagger/env.go b/dagger/env.go deleted file mode 100644 index c2e1896e..00000000 --- a/dagger/env.go +++ /dev/null @@ -1,323 +0,0 @@ -package dagger - -import ( - "context" - "fmt" - "io/fs" - "strings" - "time" - - "cuelang.org/go/cue" - cueflow "cuelang.org/go/tools/flow" - "dagger.io/go/dagger/compiler" - "dagger.io/go/stdlib" - - "github.com/opentracing/opentracing-go" - "github.com/opentracing/opentracing-go/ext" - otlog "github.com/opentracing/opentracing-go/log" - "github.com/rs/zerolog/log" -) - -type Env struct { - // Env boot script, eg. `[{do:"local",dir:"."}]` - // FIXME: rename to 'update' (script to update the env config) - // FIXME: embed update script in base as '#update' ? - // FIXME: simplify Env by making it single layer? Each layer is one env. - - // How to update the base configuration - updater *compiler.Value - - // Layer 1: base configuration - base *compiler.Value - - // Layer 2: user inputs - input *compiler.Value - - // Layer 3: computed values - output *compiler.Value - - // All layers merged together: base + input + output - state *compiler.Value -} - -func (env *Env) Updater() *compiler.Value { - return env.updater -} - -// Set the updater script for this environment. -func (env *Env) SetUpdater(v *compiler.Value) error { - if v == nil { - var err error - v, err = compiler.Compile("", "[]") - if err != nil { - return err - } - } - env.updater = v - return nil -} - -func NewEnv() (*Env, error) { - empty := compiler.EmptyStruct() - env := &Env{ - base: empty, - input: empty, - output: empty, - } - if err := env.mergeState(); err != nil { - return nil, err - } - if err := env.SetUpdater(nil); err != nil { - return nil, err - } - return env, nil -} - -func (env *Env) State() *compiler.Value { - return env.state -} - -func (env *Env) Input() *compiler.Value { - return env.input -} - -func (env *Env) SetInput(i *compiler.Value) error { - if i == nil { - i = compiler.EmptyStruct() - } - env.input = i - return env.mergeState() -} - -// Update the base configuration -func (env *Env) Update(ctx context.Context, s Solver) error { - span, ctx := opentracing.StartSpanFromContext(ctx, "Env.Update") - defer span.Finish() - - p := NewPipeline("[internal] source", s, nil) - // execute updater script - if err := p.Do(ctx, env.updater); err != nil { - return err - } - - // Build a Cue config by overlaying the source with the stdlib - sources := map[string]fs.FS{ - stdlib.Path: stdlib.FS, - "/": p.FS(), - } - base, err := compiler.Build(sources) - if err != nil { - return fmt.Errorf("base config: %w", err) - } - env.base = base - // Commit - return env.mergeState() -} - -func (env *Env) Base() *compiler.Value { - return env.base -} - -func (env *Env) Output() *compiler.Value { - return env.output -} - -// Scan all scripts in the environment for references to local directories (do:"local"), -// and return all referenced directory names. -// This is used by clients to grant access to local directories when they are referenced -// by user-specified scripts. -func (env *Env) LocalDirs() map[string]string { - dirs := map[string]string{} - localdirs := func(code ...*compiler.Value) { - Analyze( - func(op *compiler.Value) error { - do, err := op.Get("do").String() - if err != nil { - return err - } - // nolint:goconst - // FIXME: merge Env into Route, or fix the linter error - if do != "local" { - return nil - } - dir, err := op.Get("dir").String() - if err != nil { - return err - } - dirs[dir] = dir - return nil - }, - code..., - ) - } - // 1. Scan the environment state - // FIXME: use a common `flow` instance to avoid rescanning the tree. - inst := env.state.CueInst() - flow := cueflow.New(&cueflow.Config{}, inst, newTaskFunc(inst, noOpRunner)) - for _, t := range flow.Tasks() { - v := compiler.Wrap(t.Value(), inst) - localdirs(v.Get("#compute")) - } - // 2. Scan the environment updater - localdirs(env.Updater()) - return dirs -} - -// FIXME: this is just a 3-way merge. Add var args to compiler.Value.Merge. -func (env *Env) mergeState() error { - // FIXME: make this cleaner in *compiler.Value by keeping intermediary instances - // FIXME: state.CueInst() must return an instance with the same - // contents as state.v, for the purposes of cueflow. - // That is not currently how *compiler.Value works, so we prepare the cue - // instance manually. - // --> refactor the compiler.Value API to do this for us. - var ( - state = compiler.EmptyStruct() - stateInst = state.CueInst() - err error - ) - - stateInst, err = stateInst.Fill(env.base.Cue()) - if err != nil { - return fmt.Errorf("merge base & input: %w", err) - } - stateInst, err = stateInst.Fill(env.input.Cue()) - if err != nil { - return fmt.Errorf("merge base & input: %w", err) - } - stateInst, err = stateInst.Fill(env.output.Cue()) - if err != nil { - return fmt.Errorf("merge output with base & input: %w", err) - } - - state = compiler.Wrap(stateInst.Value(), stateInst) - - // commit - env.state = state - return nil -} - -// Compute missing values in env configuration, and write them to state. -func (env *Env) Compute(ctx context.Context, s Solver) error { - span, ctx := opentracing.StartSpanFromContext(ctx, "Env.Compute") - defer span.Finish() - - lg := log.Ctx(ctx) - - // Cueflow cue instance - inst := env.state.CueInst() - - // Reset the output - env.output = compiler.EmptyStruct() - - // Cueflow config - flowCfg := &cueflow.Config{ - UpdateFunc: func(c *cueflow.Controller, t *cueflow.Task) error { - if t == nil { - return nil - } - - lg := lg. - With(). - Str("component", t.Path().String()). - Str("state", t.State().String()). - Logger() - - if t.State() != cueflow.Terminated { - return nil - } - // Merge task value into output - var err error - env.output, err = env.output.MergePath(t.Value(), t.Path()) - if err != nil { - lg. - Error(). - Err(err). - Msg("failed to fill task result") - return err - } - return nil - }, - } - // Orchestrate execution with cueflow - flow := cueflow.New(flowCfg, inst, newTaskFunc(inst, newPipelineRunner(inst, s))) - if err := flow.Run(ctx); err != nil { - return err - } - - { - span, _ := opentracing.StartSpanFromContext(ctx, "Env.Compute: merge state") - defer span.Finish() - - return env.mergeState() - } -} - -func newTaskFunc(inst *cue.Instance, runner cueflow.RunnerFunc) cueflow.TaskFunc { - return func(flowVal cue.Value) (cueflow.Runner, error) { - v := compiler.Wrap(flowVal, inst) - if !isComponent(v) { - // No compute script - return nil, nil - } - return runner, nil - } -} - -func noOpRunner(t *cueflow.Task) error { - return nil -} - -func newPipelineRunner(inst *cue.Instance, s Solver) cueflow.RunnerFunc { - return cueflow.RunnerFunc(func(t *cueflow.Task) error { - ctx := t.Context() - lg := log. - Ctx(ctx). - With(). - Str("component", t.Path().String()). - Logger() - ctx = lg.WithContext(ctx) - span, ctx := opentracing.StartSpanFromContext(ctx, - fmt.Sprintf("compute: %s", t.Path().String()), - ) - defer span.Finish() - - start := time.Now() - lg. - Info(). - Msg("computing") - for _, dep := range t.Dependencies() { - lg. - Debug(). - Str("dependency", dep.Path().String()). - Msg("dependency detected") - } - v := compiler.Wrap(t.Value(), inst) - p := NewPipeline(t.Path().String(), s, NewFillable(t)) - err := p.Do(ctx, v) - if err != nil { - span.LogFields(otlog.String("error", err.Error())) - ext.Error.Set(span, true) - - // FIXME: this should use errdefs.IsCanceled(err) - if strings.Contains(err.Error(), "context canceled") { - lg. - Error(). - Dur("duration", time.Since(start)). - Msg("canceled") - return err - } - lg. - Error(). - Dur("duration", time.Since(start)). - Err(err). - Msg("failed") - return err - } - lg. - Info(). - Dur("duration", time.Since(start)). - Msg("completed") - return nil - }) -} diff --git a/dagger/input.go b/dagger/input.go index b58aacb9..9e6b8125 100644 --- a/dagger/input.go +++ b/dagger/input.go @@ -34,9 +34,9 @@ func DirInput(path string, include []string) Input { } type dirInput struct { - Type string - Path string - Include []string + Type string `json:"type,omitempty"` + Path string `json:"path,omitempty"` + Include []string `json:"include,omitempty"` } func (dir dirInput) Compile() (*compiler.Value, error) { @@ -46,7 +46,7 @@ func (dir dirInput) Compile() (*compiler.Value, error) { return nil, err } llb := fmt.Sprintf( - `[{do:"local",dir:"%s",include:%s}]`, + `#compute: [{do:"local",dir:"%s", include:%s}]`, dir.Path, includeLLB, ) @@ -55,10 +55,10 @@ func (dir dirInput) Compile() (*compiler.Value, error) { // An input artifact loaded from a git repository type gitInput struct { - Type string - Remote string - Ref string - Dir string + Type string `json:"type,omitempty"` + Remote string `json:"remote,omitempty"` + Ref string `json:"ref,omitempty"` + Dir string `json:"dir,omitempty"` } func GitInput(remote, ref, dir string) Input { @@ -83,8 +83,8 @@ func DockerInput(ref string) Input { } type dockerInput struct { - Type string - Ref string + Type string `json:"type,omitempty"` + Ref string `json:"ref,omitempty"` } func (i dockerInput) Compile() (*compiler.Value, error) { @@ -100,12 +100,12 @@ func TextInput(data string) Input { } type textInput struct { - Type string - Data string + Type string `json:"type,omitempty"` + Data string `json:"data,omitempty"` } func (i textInput) Compile() (*compiler.Value, error) { - panic("NOT IMPLEMENTED") + return compiler.Compile("", fmt.Sprintf("%q", i.Data)) } // An input value encoded as JSON @@ -117,13 +117,13 @@ func JSONInput(data string) Input { } type jsonInput struct { - Type string + Type string `json:"type,omitempty"` // Marshalled JSON data - Data string + Data string `json:"data,omitempty"` } func (i jsonInput) Compile() (*compiler.Value, error) { - panic("NOT IMPLEMENTED") + return compiler.DecodeJSON("", []byte(i.Data)) } // An input value encoded as YAML @@ -135,11 +135,11 @@ func YAMLInput(data string) Input { } type yamlInput struct { - Type string + Type string `json:"type,omitempty"` // Marshalled YAML data - Data string + Data string `json:"data,omitempty"` } func (i yamlInput) Compile() (*compiler.Value, error) { - panic("NOT IMPLEMENTED") + return compiler.DecodeYAML("", []byte(i.Data)) } diff --git a/dagger/input_test.go b/dagger/input_test.go index 836be8e2..bd71070d 100644 --- a/dagger/input_test.go +++ b/dagger/input_test.go @@ -5,7 +5,7 @@ import ( ) func TestEnvInputFlag(t *testing.T) { - env, err := NewEnv() + env, err := NewRoute() if err != nil { t.Fatal(err) } diff --git a/dagger/route.go b/dagger/route.go index 5a9764fb..a97e55b4 100644 --- a/dagger/route.go +++ b/dagger/route.go @@ -2,27 +2,45 @@ package dagger import ( "context" - "os" + "errors" + "fmt" + "io/fs" + "strings" + "time" + "cuelang.org/go/cue" + cueflow "cuelang.org/go/tools/flow" "dagger.io/go/dagger/compiler" + "dagger.io/go/stdlib" - "github.com/google/uuid" + "github.com/opentracing/opentracing-go" + "github.com/opentracing/opentracing-go/ext" + otlog "github.com/opentracing/opentracing-go/log" + "github.com/rs/zerolog/log" ) -const ( - routeLocation = "$HOME/.config/dagger/routes" -) - -func init() { - f := os.ExpandEnv(routeLocation) - if err := os.MkdirAll(f, 0755); err != nil { - panic(err) - } -} - -// A deployment route type Route struct { - st routeState + st *RouteState + + // Env boot script, eg. `[{do:"local",dir:"."}]` + // FIXME: rename to 'update' (script to update the env config) + // FIXME: embed update script in base as '#update' ? + // FIXME: simplify Env by making it single layer? Each layer is one r. + + // How to update the base configuration + updater *compiler.Value + + // Layer 1: layout configuration + layout *compiler.Value + + // Layer 2: user inputs + input *compiler.Value + + // Layer 3: computed values + output *compiler.Value + + // All layers merged together: layout + input + output + state *compiler.Value } func (r *Route) ID() string { @@ -37,88 +55,299 @@ func (r *Route) LayoutSource() Input { return r.st.LayoutSource } -func (r *Route) SetLayoutSource(ctx context.Context, src Input) error { - r.st.LayoutSource = src +func NewRoute(st *RouteState) (*Route, error) { + empty := compiler.EmptyStruct() + r := &Route{ + st: st, + layout: empty, + input: empty, + output: empty, + } + + // Prepare inputs + for _, input := range st.Inputs { + v, err := input.Value.Compile() + if err != nil { + return nil, err + } + if input.Key == "" { + r.input, err = r.input.Merge(v) + } else { + r.input, err = r.input.MergeTarget(v, input.Key) + } + if err != nil { + return nil, err + } + } + if err := r.mergeState(); err != nil { + return nil, err + } + + return r, nil +} + +func (r *Route) State() *compiler.Value { + return r.state +} + +// Update the base configuration +func (r *Route) Update(ctx context.Context, s Solver) error { + span, ctx := opentracing.StartSpanFromContext(ctx, "r.Update") + defer span.Finish() + + layout, err := r.st.LayoutSource.Compile() + if err != nil { + return err + } + + p := NewPipeline("[internal] source", s, nil) + // execute updater script + if err := p.Do(ctx, layout); err != nil { + return err + } + + // Build a Cue config by overlaying the source with the stdlib + sources := map[string]fs.FS{ + stdlib.Path: stdlib.FS, + "/": p.FS(), + } + base, err := compiler.Build(sources) + if err != nil { + return fmt.Errorf("base config: %w", err) + } + r.layout = base + + // Commit + return r.mergeState() +} + +func (r *Route) Base() *compiler.Value { + return r.layout +} + +func (r *Route) Output() *compiler.Value { + return r.output +} + +// Scan all scripts in the environment for references to local directories (do:"local"), +// and return all referenced directory names. +// This is used by clients to grant access to local directories when they are referenced +// by user-specified scripts. +func (r *Route) LocalDirs() map[string]string { + dirs := map[string]string{} + localdirs := func(code ...*compiler.Value) { + Analyze( + func(op *compiler.Value) error { + do, err := op.Get("do").String() + if err != nil { + return err + } + // nolint:goconst + // FIXME: merge Env into Route, or fix the linter error + if do != "local" { + return nil + } + dir, err := op.Get("dir").String() + if err != nil { + return err + } + dirs[dir] = dir + return nil + }, + code..., + ) + } + // 1. Scan the environment state + // FIXME: use a common `flow` instance to avoid rescanning the tree. + inst := r.state.CueInst() + flow := cueflow.New(&cueflow.Config{}, inst, newTaskFunc(inst, noOpRunner)) + for _, t := range flow.Tasks() { + v := compiler.Wrap(t.Value(), inst) + localdirs(v.Get("#compute")) + } + + // 2. Scan the layout + layout, err := r.st.LayoutSource.Compile() + if err != nil { + panic(err) + } + localdirs(layout) + return dirs +} + +// FIXME: this is just a 3-way merge. Add var args to compiler.Value.Merge. +func (r *Route) mergeState() error { + // FIXME: make this cleaner in *compiler.Value by keeping intermediary instances + // FIXME: state.CueInst() must return an instance with the same + // contents as state.v, for the purposes of cueflow. + // That is not currently how *compiler.Value works, so we prepare the cue + // instance manually. + // --> refactor the compiler.Value API to do this for us. + var ( + state = compiler.EmptyStruct() + stateInst = state.CueInst() + err error + ) + + stateInst, err = stateInst.Fill(r.layout.Cue()) + if err != nil { + return fmt.Errorf("merge base & input: %w", err) + } + stateInst, err = stateInst.Fill(r.input.Cue()) + if err != nil { + return fmt.Errorf("merge base & input: %w", err) + } + stateInst, err = stateInst.Fill(r.output.Cue()) + if err != nil { + return fmt.Errorf("merge output with base & input: %w", err) + } + + state = compiler.Wrap(stateInst.Value(), stateInst) + + // commit + r.state = state return nil } -func (r *Route) AddInput(ctx context.Context, key string, value Input) error { - r.st.Inputs = append(r.st.Inputs, inputKV{Key: key, Value: value}) - return nil -} - -// Remove all inputs at the given key, including sub-keys. -// For example RemoveInputs("foo.bar") will remove all inputs -// at foo.bar, foo.bar.baz, etc. -func (r *Route) RemoveInputs(ctx context.Context, key string) error { - panic("NOT IMPLEMENTED") -} - -// Contents of a route serialized to a file -type routeState struct { - // Globally unique route ID - ID string - - // Human-friendly route name. - // A route may have more than one name. - // FIXME: store multiple names? - Name string - - // Cue module containing the route layout - // The input's top-level artifact is used as a module directory. - LayoutSource Input - - Inputs []inputKV -} - -type inputKV struct { - Key string - Value Input -} - -func CreateRoute(ctx context.Context, name string, o *CreateOpts) (*Route, error) { - return &Route{ - st: routeState{ - ID: uuid.New().String(), - Name: name, - }, - }, nil -} - -type CreateOpts struct{} - -func DeleteRoute(ctx context.Context, o *DeleteOpts) (*Route, error) { - panic("NOT IMPLEMENTED") -} - -type DeleteOpts struct{} - -func LookupRoute(name string, o *LookupOpts) (*Route, error) { - panic("NOT IMPLEMENTED") -} - -type LookupOpts struct{} - -func LoadRoute(ctx context.Context, id string, o *LoadOpts) (*Route, error) { - panic("NOT IMPLEMENTED") -} - -type LoadOpts struct{} - -func (r *Route) Up(ctx context.Context, o *UpOpts) error { - panic("NOT IMPLEMENTED") -} - type UpOpts struct{} -func (r *Route) Down(ctx context.Context, o *DownOpts) error { - panic("NOT IMPLEMENTED") +// Up missing values in env configuration, and write them to state. +func (r *Route) Up(ctx context.Context, s Solver, _ *UpOpts) error { + span, ctx := opentracing.StartSpanFromContext(ctx, "r.Compute") + defer span.Finish() + + lg := log.Ctx(ctx) + + // Cueflow cue instance + inst := r.state.CueInst() + + // Reset the output + r.output = compiler.EmptyStruct() + + // Cueflow config + flowCfg := &cueflow.Config{ + UpdateFunc: func(c *cueflow.Controller, t *cueflow.Task) error { + if t == nil { + return nil + } + + lg := lg. + With(). + Str("component", t.Path().String()). + Str("state", t.State().String()). + Logger() + + if t.State() != cueflow.Terminated { + return nil + } + // Merge task value into output + var err error + r.output, err = r.output.MergePath(t.Value(), t.Path()) + if err != nil { + lg. + Error(). + Err(err). + Msg("failed to fill task result") + return err + } + return nil + }, + } + // Orchestrate execution with cueflow + flow := cueflow.New(flowCfg, inst, newTaskFunc(inst, newPipelineRunner(inst, s))) + if err := flow.Run(ctx); err != nil { + return err + } + + { + span, _ := opentracing.StartSpanFromContext(ctx, "r.Compute: merge state") + defer span.Finish() + + return r.mergeState() + } } type DownOpts struct{} +func (r *Route) Down(ctx context.Context, _ *DownOpts) error { + panic("NOT IMPLEMENTED") +} + func (r *Route) Query(ctx context.Context, expr interface{}, o *QueryOpts) (*compiler.Value, error) { panic("NOT IMPLEMENTED") } +func (r *Route) FIXME(ctx context.Context) error { + return errors.New("FIXME") +} + type QueryOpts struct{} + +func newTaskFunc(inst *cue.Instance, runner cueflow.RunnerFunc) cueflow.TaskFunc { + return func(flowVal cue.Value) (cueflow.Runner, error) { + v := compiler.Wrap(flowVal, inst) + if !isComponent(v) { + // No compute script + return nil, nil + } + return runner, nil + } +} + +func noOpRunner(t *cueflow.Task) error { + return nil +} + +func newPipelineRunner(inst *cue.Instance, s Solver) cueflow.RunnerFunc { + return cueflow.RunnerFunc(func(t *cueflow.Task) error { + ctx := t.Context() + lg := log. + Ctx(ctx). + With(). + Str("component", t.Path().String()). + Logger() + ctx = lg.WithContext(ctx) + span, ctx := opentracing.StartSpanFromContext(ctx, + fmt.Sprintf("compute: %s", t.Path().String()), + ) + defer span.Finish() + + start := time.Now() + lg. + Info(). + Msg("computing") + for _, dep := range t.Dependencies() { + lg. + Debug(). + Str("dependency", dep.Path().String()). + Msg("dependency detected") + } + v := compiler.Wrap(t.Value(), inst) + p := NewPipeline(t.Path().String(), s, NewFillable(t)) + err := p.Do(ctx, v) + if err != nil { + span.LogFields(otlog.String("error", err.Error())) + ext.Error.Set(span, true) + + // FIXME: this should use errdefs.IsCanceled(err) + if strings.Contains(err.Error(), "context canceled") { + lg. + Error(). + Dur("duration", time.Since(start)). + Msg("canceled") + return err + } + lg. + Error(). + Dur("duration", time.Since(start)). + Err(err). + Msg("failed") + return err + } + lg. + Info(). + Dur("duration", time.Since(start)). + Msg("completed") + return nil + }) +} diff --git a/dagger/state.go b/dagger/state.go new file mode 100644 index 00000000..74dfa87c --- /dev/null +++ b/dagger/state.go @@ -0,0 +1,132 @@ +package dagger + +import ( + "context" + "encoding/json" + "errors" + "os" + "path" + + "github.com/google/uuid" +) + +const ( + routeLocation = "$HOME/.config/dagger/routes" +) + +// Contents of a route serialized to a file +type RouteState struct { + // Globally unique route ID + ID string `json:"id,omitempty"` + + // Human-friendly route name. + // A route may have more than one name. + // FIXME: store multiple names? + Name string `json:"name,omitempty"` + + // Cue module containing the route layout + // The input's top-level artifact is used as a module directory. + LayoutSource Input `json:"layout,omitempty"` + + Inputs []inputKV `json:"inputs,omitempty"` +} + +type inputKV struct { + Key string `json:"key,omitempty"` + Value Input `json:"value,omitempty"` +} + +func (r *RouteState) SetLayoutSource(ctx context.Context, src Input) error { + r.LayoutSource = src + return nil +} + +func (r *RouteState) AddInput(ctx context.Context, key string, value Input) error { + r.Inputs = append(r.Inputs, inputKV{Key: key, Value: value}) + return nil +} + +// Remove all inputs at the given key, including sub-keys. +// For example RemoveInputs("foo.bar") will remove all inputs +// at foo.bar, foo.bar.baz, etc. +func (r *RouteState) RemoveInputs(ctx context.Context, key string) error { + panic("NOT IMPLEMENTED") +} + +func routePath(name string) string { + return path.Join(os.ExpandEnv(routeLocation), name+".json") +} + +func syncRoute(r *Route) error { + p := routePath(r.st.Name) + + if err := os.MkdirAll(path.Dir(p), 0755); err != nil { + return err + } + + data, err := json.MarshalIndent(r.st, "", " ") + if err != nil { + return err + } + + return os.WriteFile(p, data, 0644) +} + +func loadRoute(name string) (*RouteState, error) { + data, err := os.ReadFile(routePath(name)) + if err != nil { + return nil, err + } + var st *RouteState + if err := json.Unmarshal(data, st); err != nil { + return nil, err + } + return st, nil +} + +func CreateRoute(ctx context.Context, name string, o *CreateOpts) (*Route, error) { + r, err := LookupRoute(name, &LookupOpts{}) + if err != nil && !errors.Is(err, os.ErrNotExist) { + return nil, err + } + if r != nil { + return nil, os.ErrExist + } + r, err = NewRoute( + &RouteState{ + ID: uuid.New().String(), + Name: name, + }, + ) + if err != nil { + return nil, err + } + + return r, syncRoute(r) +} + +type CreateOpts struct{} + +func DeleteRoute(ctx context.Context, o *DeleteOpts) (*Route, error) { + panic("NOT IMPLEMENTED") +} + +type DeleteOpts struct{} + +func LookupRoute(name string, o *LookupOpts) (*Route, error) { + st, err := loadRoute(name) + if err != nil { + return nil, err + } + return &Route{ + st: st, + }, nil +} + +type LookupOpts struct{} + +func LoadRoute(ctx context.Context, id string, o *LoadOpts) (*Route, error) { + panic("NOT IMPLEMENTED") +} + +type LoadOpts struct{} diff --git a/examples/react-netlify/main.cue b/examples/react-netlify/main.cue index f925acd6..9ccb8d05 100644 --- a/examples/react-netlify/main.cue +++ b/examples/react-netlify/main.cue @@ -25,5 +25,6 @@ todoApp: netlify.#Site & { contents: yarn.#Script & { source: repository run: "build" + env: "xx" :"bar" } } diff --git a/tests/test.sh b/tests/test.sh index 7a8296b7..01169718 100755 --- a/tests/test.sh +++ b/tests/test.sh @@ -129,7 +129,7 @@ test::exec(){ test::one "Exec: env valid" --exit=0 --stdout={} \ "$dagger" "${DAGGER_BINARY_ARGS[@]}" compute "$d"/exec/env/valid test::one "Exec: env with overlay" --exit=0 \ - "$dagger" "${DAGGER_BINARY_ARGS[@]}" compute --input-cue 'bar: "overlay environment"' "$d"/exec/env/overlay + "$dagger" "${DAGGER_BINARY_ARGS[@]}" compute --input-string 'bar=overlay environment' "$d"/exec/env/overlay test::one "Exec: non existent dir" --exit=0 --stdout={} \ "$dagger" "${DAGGER_BINARY_ARGS[@]}" compute "$d"/exec/dir/doesnotexist @@ -230,16 +230,15 @@ test::input() { "$dagger" "${DAGGER_BINARY_ARGS[@]}" compute "$d"/input/simple test::one "Input: simple input" --exit=0 --stdout='{"in":"foobar","test":"received: foobar"}' \ - "$dagger" "${DAGGER_BINARY_ARGS[@]}" compute --input-cue 'in: "foobar"' "$d"/input/simple + "$dagger" "${DAGGER_BINARY_ARGS[@]}" compute --input-string 'in=foobar' "$d"/input/simple test::one "Input: default values" --exit=0 --stdout='{"in":"default input","test":"received: default input"}' \ "$dagger" "${DAGGER_BINARY_ARGS[@]}" compute "$d"/input/default test::one "Input: override default value" --exit=0 --stdout='{"in":"foobar","test":"received: foobar"}' \ - "$dagger" "${DAGGER_BINARY_ARGS[@]}" compute --input-cue 'in: "foobar"' "$d"/input/default + "$dagger" "${DAGGER_BINARY_ARGS[@]}" compute --input-string 'in=foobar' "$d"/input/default } - test::subdir() { test::one "Subdir: simple usage" --exit=0 --stdout='{"hello":"world"}' \ "$dagger" "${DAGGER_BINARY_ARGS[@]}" compute "$d"/subdir/simple From 43956e38cc6e701bb2a384f514a14d1fe75c6695 Mon Sep 17 00:00:00 2001 From: Andrea Luzzardi Date: Wed, 24 Mar 2021 18:07:52 -0700 Subject: [PATCH 09/23] separate Store from State Signed-off-by: Andrea Luzzardi --- cmd/dagger/cmd/compute.go | 10 +-- cmd/dagger/cmd/down.go | 2 +- cmd/dagger/cmd/query.go | 2 +- cmd/dagger/cmd/up.go | 2 +- dagger/route.go | 34 ++++++++++ dagger/state.go | 132 -------------------------------------- dagger/store.go | 103 +++++++++++++++++++++++++++++ 7 files changed, 145 insertions(+), 140 deletions(-) delete mode 100644 dagger/state.go create mode 100644 dagger/store.go diff --git a/cmd/dagger/cmd/compute.go b/cmd/dagger/cmd/compute.go index 1559cd92..d5149ce3 100644 --- a/cmd/dagger/cmd/compute.go +++ b/cmd/dagger/cmd/compute.go @@ -42,7 +42,7 @@ var computeCmd = &cobra.Command{ for _, input := range viper.GetStringSlice("input-string") { parts := strings.SplitN(input, "=", 2) k, v := parts[0], parts[1] - err := st.AddInput(ctx, k, dagger.TextInput(v)) + err := st.AddInput(k, dagger.TextInput(v)) if err != nil { lg.Fatal().Err(err).Str("input", k).Msg("failed to add input") } @@ -51,7 +51,7 @@ var computeCmd = &cobra.Command{ for _, input := range viper.GetStringSlice("input-dir") { parts := strings.SplitN(input, "=", 2) k, v := parts[0], parts[1] - err := st.AddInput(ctx, k, dagger.DirInput(v, []string{})) + err := st.AddInput(k, dagger.DirInput(v, []string{})) if err != nil { lg.Fatal().Err(err).Str("input", k).Msg("failed to add input") } @@ -60,7 +60,7 @@ var computeCmd = &cobra.Command{ for _, input := range viper.GetStringSlice("input-git") { parts := strings.SplitN(input, "=", 2) k, v := parts[0], parts[1] - err := st.AddInput(ctx, k, dagger.GitInput(v, "", "")) + err := st.AddInput(k, dagger.GitInput(v, "", "")) if err != nil { lg.Fatal().Err(err).Str("input", k).Msg("failed to add input") } @@ -87,7 +87,7 @@ var computeCmd = &cobra.Command{ lg.Fatal().Msg("invalid json") } - err = st.AddInput(ctx, "", dagger.JSONInput(string(content))) + err = st.AddInput("", dagger.JSONInput(string(content))) if err != nil { lg.Fatal().Err(err).Msg("failed to add input") } @@ -110,7 +110,7 @@ var computeCmd = &cobra.Command{ content = plaintext } - err = st.AddInput(ctx, "", dagger.YAMLInput(string(content))) + err = st.AddInput("", dagger.YAMLInput(string(content))) if err != nil { lg.Fatal().Err(err).Msg("failed to add input") } diff --git a/cmd/dagger/cmd/down.go b/cmd/dagger/cmd/down.go index 64e7f692..097a194f 100644 --- a/cmd/dagger/cmd/down.go +++ b/cmd/dagger/cmd/down.go @@ -26,7 +26,7 @@ var downCmd = &cobra.Command{ ctx := lg.WithContext(cmd.Context()) routeName := getRouteName(lg, cmd) - route, err := dagger.LookupRoute(routeName, nil) + route, err := dagger.LookupRoute(ctx, routeName, nil) if err != nil { lg.Fatal().Err(err).Str("route-name", routeName).Msg("failed to lookup route") } diff --git a/cmd/dagger/cmd/query.go b/cmd/dagger/cmd/query.go index 91b0775a..ffe52f70 100644 --- a/cmd/dagger/cmd/query.go +++ b/cmd/dagger/cmd/query.go @@ -27,7 +27,7 @@ var queryCmd = &cobra.Command{ ctx := lg.WithContext(cmd.Context()) routeName := getRouteName(lg, cmd) - route, err := dagger.LookupRoute(routeName, nil) + route, err := dagger.LookupRoute(ctx, routeName, nil) if err != nil { lg.Fatal().Err(err).Str("route-name", routeName).Msg("failed to lookup route") } diff --git a/cmd/dagger/cmd/up.go b/cmd/dagger/cmd/up.go index ded638a0..f41ec94d 100644 --- a/cmd/dagger/cmd/up.go +++ b/cmd/dagger/cmd/up.go @@ -26,7 +26,7 @@ var upCmd = &cobra.Command{ ctx := lg.WithContext(cmd.Context()) routeName := getRouteName(lg, cmd) - route, err := dagger.LookupRoute(routeName, nil) + route, err := dagger.LookupRoute(ctx, routeName, nil) if err != nil { lg.Fatal().Err(err).Str("route-name", routeName).Msg("failed to lookup route") } diff --git a/dagger/route.go b/dagger/route.go index a97e55b4..666d0daf 100644 --- a/dagger/route.go +++ b/dagger/route.go @@ -19,6 +19,40 @@ import ( "github.com/rs/zerolog/log" ) +// Contents of a route serialized to a file +type RouteState struct { + // Globally unique route ID + ID string `json:"id,omitempty"` + + // Human-friendly route name. + // A route may have more than one name. + // FIXME: store multiple names? + Name string `json:"name,omitempty"` + + // Cue module containing the route layout + // The input's top-level artifact is used as a module directory. + LayoutSource Input `json:"layout,omitempty"` + + Inputs []inputKV `json:"inputs,omitempty"` +} + +type inputKV struct { + Key string `json:"key,omitempty"` + Value Input `json:"value,omitempty"` +} + +func (r *RouteState) AddInput(key string, value Input) error { + r.Inputs = append(r.Inputs, inputKV{Key: key, Value: value}) + return nil +} + +// Remove all inputs at the given key, including sub-keys. +// For example RemoveInputs("foo.bar") will remove all inputs +// at foo.bar, foo.bar.baz, etc. +func (r *RouteState) RemoveInputs(key string) error { + panic("NOT IMPLEMENTED") +} + type Route struct { st *RouteState diff --git a/dagger/state.go b/dagger/state.go deleted file mode 100644 index 74dfa87c..00000000 --- a/dagger/state.go +++ /dev/null @@ -1,132 +0,0 @@ -package dagger - -import ( - "context" - "encoding/json" - "errors" - "os" - "path" - - "github.com/google/uuid" -) - -const ( - routeLocation = "$HOME/.config/dagger/routes" -) - -// Contents of a route serialized to a file -type RouteState struct { - // Globally unique route ID - ID string `json:"id,omitempty"` - - // Human-friendly route name. - // A route may have more than one name. - // FIXME: store multiple names? - Name string `json:"name,omitempty"` - - // Cue module containing the route layout - // The input's top-level artifact is used as a module directory. - LayoutSource Input `json:"layout,omitempty"` - - Inputs []inputKV `json:"inputs,omitempty"` -} - -type inputKV struct { - Key string `json:"key,omitempty"` - Value Input `json:"value,omitempty"` -} - -func (r *RouteState) SetLayoutSource(ctx context.Context, src Input) error { - r.LayoutSource = src - return nil -} - -func (r *RouteState) AddInput(ctx context.Context, key string, value Input) error { - r.Inputs = append(r.Inputs, inputKV{Key: key, Value: value}) - return nil -} - -// Remove all inputs at the given key, including sub-keys. -// For example RemoveInputs("foo.bar") will remove all inputs -// at foo.bar, foo.bar.baz, etc. -func (r *RouteState) RemoveInputs(ctx context.Context, key string) error { - panic("NOT IMPLEMENTED") -} - -func routePath(name string) string { - return path.Join(os.ExpandEnv(routeLocation), name+".json") -} - -func syncRoute(r *Route) error { - p := routePath(r.st.Name) - - if err := os.MkdirAll(path.Dir(p), 0755); err != nil { - return err - } - - data, err := json.MarshalIndent(r.st, "", " ") - if err != nil { - return err - } - - return os.WriteFile(p, data, 0644) -} - -func loadRoute(name string) (*RouteState, error) { - data, err := os.ReadFile(routePath(name)) - if err != nil { - return nil, err - } - var st *RouteState - if err := json.Unmarshal(data, st); err != nil { - return nil, err - } - return st, nil -} - -func CreateRoute(ctx context.Context, name string, o *CreateOpts) (*Route, error) { - r, err := LookupRoute(name, &LookupOpts{}) - if err != nil && !errors.Is(err, os.ErrNotExist) { - return nil, err - } - if r != nil { - return nil, os.ErrExist - } - r, err = NewRoute( - &RouteState{ - ID: uuid.New().String(), - Name: name, - }, - ) - if err != nil { - return nil, err - } - - return r, syncRoute(r) -} - -type CreateOpts struct{} - -func DeleteRoute(ctx context.Context, o *DeleteOpts) (*Route, error) { - panic("NOT IMPLEMENTED") -} - -type DeleteOpts struct{} - -func LookupRoute(name string, o *LookupOpts) (*Route, error) { - st, err := loadRoute(name) - if err != nil { - return nil, err - } - return &Route{ - st: st, - }, nil -} - -type LookupOpts struct{} - -func LoadRoute(ctx context.Context, id string, o *LoadOpts) (*Route, error) { - panic("NOT IMPLEMENTED") -} - -type LoadOpts struct{} diff --git a/dagger/store.go b/dagger/store.go new file mode 100644 index 00000000..9bcfffbf --- /dev/null +++ b/dagger/store.go @@ -0,0 +1,103 @@ +package dagger + +import ( + "context" + "encoding/json" + "errors" + "os" + "path" + + "github.com/google/uuid" +) + +const ( + storeLocation = "$HOME/.config/dagger/routes" +) + +type CreateOpts struct{} + +func CreateRoute(ctx context.Context, name string, o *CreateOpts) (*Route, error) { + r, err := LookupRoute(ctx, name, &LookupOpts{}) + if err != nil && !errors.Is(err, os.ErrNotExist) { + return nil, err + } + if r != nil { + return nil, os.ErrExist + } + r, err = NewRoute( + &RouteState{ + ID: uuid.New().String(), + Name: name, + }, + ) + if err != nil { + return nil, err + } + + return r, syncRoute(r) +} + +type UpdateOpts struct{} + +func UpdateRoute(ctx context.Context, r *Route, o *UpdateOpts) error { + return syncRoute(r) +} + +type DeleteOpts struct{} + +func DeleteRoute(ctx context.Context, r *Route, o *DeleteOpts) error { + return deleteRoute(r) +} + +type LookupOpts struct{} + +func LookupRoute(ctx context.Context, name string, o *LookupOpts) (*Route, error) { + st, err := loadRoute(name) + if err != nil { + return nil, err + } + return &Route{ + st: st, + }, nil +} + +type LoadOpts struct{} + +func LoadRoute(ctx context.Context, id string, o *LoadOpts) (*Route, error) { + panic("NOT IMPLEMENTED") +} + +func routePath(name string) string { + return path.Join(os.ExpandEnv(storeLocation), name+".json") +} + +func syncRoute(r *Route) error { + p := routePath(r.st.Name) + + if err := os.MkdirAll(path.Dir(p), 0755); err != nil { + return err + } + + data, err := json.MarshalIndent(r.st, "", " ") + if err != nil { + return err + } + + return os.WriteFile(p, data, 0644) +} + +func deleteRoute(r *Route) error { + return os.Remove(routePath(r.st.Name)) +} + +func loadRoute(name string) (*RouteState, error) { + data, err := os.ReadFile(routePath(name)) + if err != nil { + return nil, err + } + var st *RouteState + if err := json.Unmarshal(data, st); err != nil { + return nil, err + } + return st, nil +} From 7ad541feb18cccd89a716eb41356196516982835 Mon Sep 17 00:00:00 2001 From: Sam Alba Date: Thu, 25 Mar 2021 14:24:05 -0700 Subject: [PATCH 10/23] implemented new, up, list Signed-off-by: Sam Alba --- cmd/dagger/cmd/common.go | 15 +++++++++++++++ cmd/dagger/cmd/compute.go | 11 +---------- cmd/dagger/cmd/list.go | 17 ++++++++++++++--- cmd/dagger/cmd/new.go | 10 +++------- cmd/dagger/cmd/up.go | 5 +---- dagger/route.go | 5 ----- dagger/store.go | 22 ++++++++++++++++++++++ 7 files changed, 56 insertions(+), 29 deletions(-) diff --git a/cmd/dagger/cmd/common.go b/cmd/dagger/cmd/common.go index a3de45b2..1b404e93 100644 --- a/cmd/dagger/cmd/common.go +++ b/cmd/dagger/cmd/common.go @@ -1,9 +1,12 @@ package cmd import ( + "context" + "fmt" "os" "path/filepath" + "dagger.io/go/dagger" "github.com/rs/zerolog" "github.com/spf13/cobra" ) @@ -31,3 +34,15 @@ func getRouteName(lg zerolog.Logger, cmd *cobra.Command) string { return currentDir } + +func routeUp(ctx context.Context, lg zerolog.Logger, route *dagger.Route) { + c, err := dagger.NewClient(ctx, "") + if err != nil { + lg.Fatal().Err(err).Msg("unable to create client") + } + output, err := c.Up(ctx, route) + if err != nil { + lg.Fatal().Err(err).Msg("failed to compute") + } + fmt.Println(output.JSON()) +} diff --git a/cmd/dagger/cmd/compute.go b/cmd/dagger/cmd/compute.go index d5149ce3..5e533660 100644 --- a/cmd/dagger/cmd/compute.go +++ b/cmd/dagger/cmd/compute.go @@ -3,7 +3,6 @@ package cmd import ( "encoding/json" "errors" - "fmt" "os" "strings" @@ -121,15 +120,7 @@ var computeCmd = &cobra.Command{ lg.Fatal().Err(err).Msg("unable to initialize route") } - c, err := dagger.NewClient(ctx, "") - if err != nil { - lg.Fatal().Err(err).Msg("unable to create client") - } - output, err := c.Up(ctx, route) - if err != nil { - lg.Fatal().Err(err).Msg("failed to compute") - } - fmt.Println(output.JSON()) + routeUp(ctx, lg, route) }, } diff --git a/cmd/dagger/cmd/list.go b/cmd/dagger/cmd/list.go index 108f9928..4df40908 100644 --- a/cmd/dagger/cmd/list.go +++ b/cmd/dagger/cmd/list.go @@ -1,6 +1,10 @@ package cmd import ( + "fmt" + + "dagger.io/go/cmd/dagger/logger" + "dagger.io/go/dagger" "github.com/spf13/cobra" "github.com/spf13/viper" ) @@ -17,10 +21,17 @@ var listCmd = &cobra.Command{ } }, Run: func(cmd *cobra.Command, args []string) { - // lg := logger.New() - // ctx := lg.WithContext(cmd.Context()) + lg := logger.New() + ctx := lg.WithContext(cmd.Context()) - panic("not implemented") + routes, err := dagger.ListRoutes(ctx) + if err != nil { + lg.Fatal().Err(err).Msg("cannot list routes") + } + + for _, name := range routes { + fmt.Println(name) + } }, } diff --git a/cmd/dagger/cmd/new.go b/cmd/dagger/cmd/new.go index 38cf0e24..6368f1d6 100644 --- a/cmd/dagger/cmd/new.go +++ b/cmd/dagger/cmd/new.go @@ -24,7 +24,7 @@ var newCmd = &cobra.Command{ ctx := lg.WithContext(cmd.Context()) // nolint:staticcheck - upRoute, err := cmd.Flags().GetBool("up") + upRouteFlag, err := cmd.Flags().GetBool("up") if err != nil { lg.Fatal().Err(err).Str("flag", "up").Msg("unable to resolve flag") } @@ -39,12 +39,8 @@ var newCmd = &cobra.Command{ } lg.Info().Str("route-id", route.ID()).Str("route-name", routeName).Msg("created route") - if upRoute { - lg.Info().Str("route-id", route.ID()).Msg("bringing route online") - // FIXME - if err := route.FIXME(ctx); err != nil { - lg.Fatal().Err(err).Str("route-id", route.ID()).Msg("failed to create route") - } + if upRouteFlag { + routeUp(ctx, lg, route) } }, } diff --git a/cmd/dagger/cmd/up.go b/cmd/dagger/cmd/up.go index f41ec94d..9464eb86 100644 --- a/cmd/dagger/cmd/up.go +++ b/cmd/dagger/cmd/up.go @@ -32,10 +32,7 @@ var upCmd = &cobra.Command{ } // TODO: Implement options: --no-cache - // FIXME - if err := route.FIXME(ctx); err != nil { - lg.Fatal().Err(err).Str("route-name", routeName).Str("route-id", route.ID()).Msg("failed to up the route") - } + routeUp(ctx, lg, route) }, } diff --git a/dagger/route.go b/dagger/route.go index 666d0daf..1634ad5b 100644 --- a/dagger/route.go +++ b/dagger/route.go @@ -2,7 +2,6 @@ package dagger import ( "context" - "errors" "fmt" "io/fs" "strings" @@ -311,10 +310,6 @@ func (r *Route) Query(ctx context.Context, expr interface{}, o *QueryOpts) (*com panic("NOT IMPLEMENTED") } -func (r *Route) FIXME(ctx context.Context) error { - return errors.New("FIXME") -} - type QueryOpts struct{} func newTaskFunc(inst *cue.Instance, runner cueflow.RunnerFunc) cueflow.TaskFunc { diff --git a/dagger/store.go b/dagger/store.go index 9bcfffbf..b52467de 100644 --- a/dagger/store.go +++ b/dagger/store.go @@ -4,8 +4,10 @@ import ( "context" "encoding/json" "errors" + "io/ioutil" "os" "path" + "strings" "github.com/google/uuid" ) @@ -67,6 +69,26 @@ func LoadRoute(ctx context.Context, id string, o *LoadOpts) (*Route, error) { panic("NOT IMPLEMENTED") } +func ListRoutes(ctx context.Context) ([]string, error) { + routes := []string{} + + rootDir := os.ExpandEnv(storeLocation) + files, err := ioutil.ReadDir(rootDir) + if err != nil { + return nil, err + } + + for _, f := range files { + if f.IsDir() || !strings.HasSuffix(f.Name(), ".json") { + // There is extra data in the directory, ignore + continue + } + routes = append(routes, f.Name()[:len(f.Name())-5]) + } + + return routes, nil +} + func routePath(name string) string { return path.Join(os.ExpandEnv(storeLocation), name+".json") } From 524f77df65504efd1e6384f92446af481c7ff252 Mon Sep 17 00:00:00 2001 From: Andrea Luzzardi Date: Thu, 25 Mar 2021 14:35:55 -0700 Subject: [PATCH 11/23] tests: fix unit tests Signed-off-by: Andrea Luzzardi --- dagger/compiler/compiler_test.go | 45 +++++++------------------ dagger/dagger_test.go | 56 -------------------------------- dagger/input_test.go | 16 +++------ dagger/route.go | 13 ++++---- go.mod | 1 + 5 files changed, 24 insertions(+), 107 deletions(-) delete mode 100644 dagger/dagger_test.go diff --git a/dagger/compiler/compiler_test.go b/dagger/compiler/compiler_test.go index 37c298ee..c4ff093f 100644 --- a/dagger/compiler/compiler_test.go +++ b/dagger/compiler/compiler_test.go @@ -2,55 +2,34 @@ package compiler import ( "testing" + + "github.com/stretchr/testify/require" ) // Test that a non-existing field is detected correctly func TestFieldNotExist(t *testing.T) { c := &Compiler{} root, err := c.Compile("test.cue", `foo: "bar"`) - if err != nil { - t.Fatal(err) - } - if v := root.Get("foo"); !v.Exists() { - // value should exist - t.Fatal(v) - } - if v := root.Get("bar"); v.Exists() { - // value should NOT exist - t.Fatal(v) - } + require.NoError(t, err) + require.True(t, root.Get("foo").Exists()) + require.False(t, root.Get("bar").Exists()) } // Test that a non-existing definition is detected correctly func TestDefNotExist(t *testing.T) { c := &Compiler{} root, err := c.Compile("test.cue", `foo: #bla: "bar"`) - if err != nil { - t.Fatal(err) - } - if v := root.Get("foo.#bla"); !v.Exists() { - // value should exist - t.Fatal(v) - } - if v := root.Get("foo.#nope"); v.Exists() { - // value should NOT exist - t.Fatal(v) - } + require.NoError(t, err) + require.True(t, root.Get("foo.#bla").Exists()) + require.False(t, root.Get("foo.#nope").Exists()) } func TestJSON(t *testing.T) { c := &Compiler{} v, err := c.Compile("", `foo: hello: "world"`) - if err != nil { - t.Fatal(err) - } - b1 := v.JSON() - if string(b1) != `{"foo":{"hello":"world"}}` { - t.Fatal(b1) - } + require.NoError(t, err) + require.Equal(t, `{"foo":{"hello":"world"}}`, string(v.JSON())) + // Reproduce a bug where Value.Get().JSON() ignores Get() - b2 := v.Get("foo").JSON() - if string(b2) != `{"hello":"world"}` { - t.Fatal(b2) - } + require.Equal(t, `{"hello":"world"}`, string(v.Get("foo").JSON())) } diff --git a/dagger/dagger_test.go b/dagger/dagger_test.go deleted file mode 100644 index 43dd99c6..00000000 --- a/dagger/dagger_test.go +++ /dev/null @@ -1,56 +0,0 @@ -package dagger - -import ( - "testing" - - "dagger.io/go/dagger/compiler" -) - -func TestLocalDirs(t *testing.T) { - env := mkEnv(t, - `#compute: [ - { - do: "local" - dir: "bar" - } - ]`, - `dir: #compute: [ - { - do: "local" - dir: "foo" - } - ]`, - ) - dirs := env.LocalDirs() - if len(dirs) != 2 { - t.Fatal(dirs) - } - if _, ok := dirs["foo"]; !ok { - t.Fatal(dirs) - } - if _, ok := dirs["bar"]; !ok { - t.Fatal(dirs) - } -} - -func mkEnv(t *testing.T, updater, input string) *Route { - env, err := NewRoute() - if err != nil { - t.Fatal(err) - } - u, err := compiler.Compile("updater.cue", updater) - if err != nil { - t.Fatal(err) - } - if err := env.SetUpdater(u); err != nil { - t.Fatal(err) - } - i, err := compiler.Compile("input.cue", input) - if err != nil { - t.Fatal(err) - } - if err := env.SetInput(i); err != nil { - t.Fatal(err) - } - return env -} diff --git a/dagger/input_test.go b/dagger/input_test.go index bd71070d..8702f8c8 100644 --- a/dagger/input_test.go +++ b/dagger/input_test.go @@ -2,24 +2,18 @@ package dagger import ( "testing" + + "github.com/stretchr/testify/require" ) func TestEnvInputFlag(t *testing.T) { - env, err := NewRoute() - if err != nil { - t.Fatal(err) - } + st := &RouteState{} + require.NoError(t, st.AddInput("www.source", DirInput(".", []string{}))) - input, err := NewInputValue(`{}`) + env, err := NewRoute(st) if err != nil { t.Fatal(err) } - if err := input.DirFlag().Set("www.source=."); err != nil { - t.Fatal(err) - } - if err := env.SetInput(input.Value()); err != nil { - t.Fatal(err) - } localdirs := env.LocalDirs() if len(localdirs) != 1 { diff --git a/dagger/route.go b/dagger/route.go index 1634ad5b..52678c1a 100644 --- a/dagger/route.go +++ b/dagger/route.go @@ -60,9 +60,6 @@ type Route struct { // FIXME: embed update script in base as '#update' ? // FIXME: simplify Env by making it single layer? Each layer is one r. - // How to update the base configuration - updater *compiler.Value - // Layer 1: layout configuration layout *compiler.Value @@ -200,11 +197,13 @@ func (r *Route) LocalDirs() map[string]string { } // 2. Scan the layout - layout, err := r.st.LayoutSource.Compile() - if err != nil { - panic(err) + if r.st.LayoutSource != nil { + layout, err := r.st.LayoutSource.Compile() + if err != nil { + panic(err) + } + localdirs(layout) } - localdirs(layout) return dirs } diff --git a/go.mod b/go.mod index c5d39008..219e6ed8 100644 --- a/go.mod +++ b/go.mod @@ -19,6 +19,7 @@ require ( github.com/spf13/cobra v1.1.3 github.com/spf13/pflag v1.0.5 github.com/spf13/viper v1.7.1 + github.com/stretchr/testify v1.5.1 // indirect github.com/tonistiigi/fsutil v0.0.0-20201103201449-0834f99b7b85 github.com/tonistiigi/units v0.0.0-20180711220420-6950e57a87ea github.com/uber/jaeger-client-go v2.25.0+incompatible From 9fec69f3a0a19a692bab156fa5f7474039fdb9b7 Mon Sep 17 00:00:00 2001 From: Andrea Luzzardi Date: Thu, 25 Mar 2021 15:17:35 -0700 Subject: [PATCH 12/23] make store a struct and add tests Signed-off-by: Andrea Luzzardi --- cmd/dagger/cmd/down.go | 4 +- cmd/dagger/cmd/list.go | 3 +- cmd/dagger/cmd/new.go | 5 +-- cmd/dagger/cmd/query.go | 4 +- cmd/dagger/cmd/up.go | 4 +- dagger/store.go | 76 ++++++++++++++++----------------- dagger/store_test.go | 37 ++++++++++++++++ examples/react-netlify/main.cue | 1 - 8 files changed, 85 insertions(+), 49 deletions(-) create mode 100644 dagger/store_test.go diff --git a/cmd/dagger/cmd/down.go b/cmd/dagger/cmd/down.go index 097a194f..c88df0e3 100644 --- a/cmd/dagger/cmd/down.go +++ b/cmd/dagger/cmd/down.go @@ -22,11 +22,11 @@ var downCmd = &cobra.Command{ }, Run: func(cmd *cobra.Command, args []string) { lg := logger.New() - // nolint:staticcheck ctx := lg.WithContext(cmd.Context()) + store := dagger.DefaultStore() routeName := getRouteName(lg, cmd) - route, err := dagger.LookupRoute(ctx, routeName, nil) + route, err := store.LookupRoute(ctx, routeName, nil) if err != nil { lg.Fatal().Err(err).Str("route-name", routeName).Msg("failed to lookup route") } diff --git a/cmd/dagger/cmd/list.go b/cmd/dagger/cmd/list.go index 4df40908..bb3b02bb 100644 --- a/cmd/dagger/cmd/list.go +++ b/cmd/dagger/cmd/list.go @@ -23,8 +23,9 @@ var listCmd = &cobra.Command{ Run: func(cmd *cobra.Command, args []string) { lg := logger.New() ctx := lg.WithContext(cmd.Context()) + store := dagger.DefaultStore() - routes, err := dagger.ListRoutes(ctx) + routes, err := store.ListRoutes(ctx) if err != nil { lg.Fatal().Err(err).Msg("cannot list routes") } diff --git a/cmd/dagger/cmd/new.go b/cmd/dagger/cmd/new.go index 6368f1d6..21409d68 100644 --- a/cmd/dagger/cmd/new.go +++ b/cmd/dagger/cmd/new.go @@ -22,18 +22,17 @@ var newCmd = &cobra.Command{ Run: func(cmd *cobra.Command, args []string) { lg := logger.New() ctx := lg.WithContext(cmd.Context()) + store := dagger.DefaultStore() - // nolint:staticcheck upRouteFlag, err := cmd.Flags().GetBool("up") if err != nil { lg.Fatal().Err(err).Str("flag", "up").Msg("unable to resolve flag") } - // nolint:staticcheck routeName := getRouteName(lg, cmd) // TODO: Implement options: --layout-*, --setup - route, err := dagger.CreateRoute(ctx, routeName, nil) + route, err := store.CreateRoute(ctx, routeName, nil) if err != nil { lg.Fatal().Err(err).Msg("failed to create route") } diff --git a/cmd/dagger/cmd/query.go b/cmd/dagger/cmd/query.go index ffe52f70..58f485ce 100644 --- a/cmd/dagger/cmd/query.go +++ b/cmd/dagger/cmd/query.go @@ -23,11 +23,11 @@ var queryCmd = &cobra.Command{ }, Run: func(cmd *cobra.Command, args []string) { lg := logger.New() - // nolint:staticcheck ctx := lg.WithContext(cmd.Context()) + store := dagger.DefaultStore() routeName := getRouteName(lg, cmd) - route, err := dagger.LookupRoute(ctx, routeName, nil) + route, err := store.LookupRoute(ctx, routeName, nil) if err != nil { lg.Fatal().Err(err).Str("route-name", routeName).Msg("failed to lookup route") } diff --git a/cmd/dagger/cmd/up.go b/cmd/dagger/cmd/up.go index 9464eb86..2d4c972a 100644 --- a/cmd/dagger/cmd/up.go +++ b/cmd/dagger/cmd/up.go @@ -22,11 +22,11 @@ var upCmd = &cobra.Command{ }, Run: func(cmd *cobra.Command, args []string) { lg := logger.New() - // nolint:staticcheck ctx := lg.WithContext(cmd.Context()) + store := dagger.DefaultStore() routeName := getRouteName(lg, cmd) - route, err := dagger.LookupRoute(ctx, routeName, nil) + route, err := store.LookupRoute(ctx, routeName, nil) if err != nil { lg.Fatal().Err(err).Str("route-name", routeName).Msg("failed to lookup route") } diff --git a/dagger/store.go b/dagger/store.go index b52467de..2eab5963 100644 --- a/dagger/store.go +++ b/dagger/store.go @@ -4,22 +4,35 @@ import ( "context" "encoding/json" "errors" - "io/ioutil" "os" "path" - "strings" + "path/filepath" "github.com/google/uuid" ) const ( - storeLocation = "$HOME/.config/dagger/routes" + defaultStoreRoot = "$HOME/.config/dagger/routes" ) +type Store struct { + root string +} + +func NewStore(root string) *Store { + return &Store{ + root: root, + } +} + +func DefaultStore() *Store { + return NewStore(os.ExpandEnv(defaultStoreRoot)) +} + type CreateOpts struct{} -func CreateRoute(ctx context.Context, name string, o *CreateOpts) (*Route, error) { - r, err := LookupRoute(ctx, name, &LookupOpts{}) +func (s *Store) CreateRoute(ctx context.Context, name string, o *CreateOpts) (*Route, error) { + r, err := s.LookupRoute(ctx, name, &LookupOpts{}) if err != nil && !errors.Is(err, os.ErrNotExist) { return nil, err } @@ -36,50 +49,53 @@ func CreateRoute(ctx context.Context, name string, o *CreateOpts) (*Route, error return nil, err } - return r, syncRoute(r) + return r, s.syncRoute(r) } type UpdateOpts struct{} -func UpdateRoute(ctx context.Context, r *Route, o *UpdateOpts) error { - return syncRoute(r) +func (s *Store) UpdateRoute(ctx context.Context, r *Route, o *UpdateOpts) error { + return s.syncRoute(r) } type DeleteOpts struct{} -func DeleteRoute(ctx context.Context, r *Route, o *DeleteOpts) error { - return deleteRoute(r) +func (s *Store) DeleteRoute(ctx context.Context, r *Route, o *DeleteOpts) error { + return os.Remove(s.routePath(r.st.Name)) } type LookupOpts struct{} -func LookupRoute(ctx context.Context, name string, o *LookupOpts) (*Route, error) { - st, err := loadRoute(name) +func (s *Store) LookupRoute(ctx context.Context, name string, o *LookupOpts) (*Route, error) { + data, err := os.ReadFile(s.routePath(name)) if err != nil { return nil, err } + var st RouteState + if err := json.Unmarshal(data, &st); err != nil { + return nil, err + } return &Route{ - st: st, + st: &st, }, nil } type LoadOpts struct{} -func LoadRoute(ctx context.Context, id string, o *LoadOpts) (*Route, error) { +func (s *Store) LoadRoute(ctx context.Context, id string, o *LoadOpts) (*Route, error) { panic("NOT IMPLEMENTED") } -func ListRoutes(ctx context.Context) ([]string, error) { +func (s *Store) ListRoutes(ctx context.Context) ([]string, error) { routes := []string{} - rootDir := os.ExpandEnv(storeLocation) - files, err := ioutil.ReadDir(rootDir) + files, err := os.ReadDir(s.root) if err != nil { return nil, err } for _, f := range files { - if f.IsDir() || !strings.HasSuffix(f.Name(), ".json") { + if f.IsDir() || filepath.Ext(f.Name()) != ".json" { // There is extra data in the directory, ignore continue } @@ -89,12 +105,12 @@ func ListRoutes(ctx context.Context) ([]string, error) { return routes, nil } -func routePath(name string) string { - return path.Join(os.ExpandEnv(storeLocation), name+".json") +func (s *Store) routePath(name string) string { + return path.Join(s.root, name+".json") } -func syncRoute(r *Route) error { - p := routePath(r.st.Name) +func (s *Store) syncRoute(r *Route) error { + p := s.routePath(r.st.Name) if err := os.MkdirAll(path.Dir(p), 0755); err != nil { return err @@ -107,19 +123,3 @@ func syncRoute(r *Route) error { return os.WriteFile(p, data, 0644) } - -func deleteRoute(r *Route) error { - return os.Remove(routePath(r.st.Name)) -} - -func loadRoute(name string) (*RouteState, error) { - data, err := os.ReadFile(routePath(name)) - if err != nil { - return nil, err - } - var st *RouteState - if err := json.Unmarshal(data, st); err != nil { - return nil, err - } - return st, nil -} diff --git a/dagger/store_test.go b/dagger/store_test.go new file mode 100644 index 00000000..c6d3d3b6 --- /dev/null +++ b/dagger/store_test.go @@ -0,0 +1,37 @@ +package dagger + +import ( + "context" + "errors" + "os" + "testing" + + "github.com/stretchr/testify/require" +) + +func TestStore(t *testing.T) { + ctx := context.TODO() + + root, err := os.MkdirTemp(os.TempDir(), "dagger-*") + require.NoError(t, err) + store := NewStore(root) + + _, err = store.LookupRoute(ctx, "notexist", nil) + require.Error(t, err) + require.True(t, errors.Is(err, os.ErrNotExist)) + + r, err := store.CreateRoute(ctx, "test", nil) + require.NoError(t, err) + require.NotNil(t, r) + require.Equal(t, "test", r.Name()) + + r, err = store.LookupRoute(ctx, "test", nil) + require.NoError(t, err) + require.NotNil(t, r) + require.Equal(t, "test", r.Name()) + + routes, err := store.ListRoutes(ctx) + require.NoError(t, err) + require.Len(t, routes, 1) + require.Equal(t, "test", routes[0]) +} diff --git a/examples/react-netlify/main.cue b/examples/react-netlify/main.cue index 9ccb8d05..f925acd6 100644 --- a/examples/react-netlify/main.cue +++ b/examples/react-netlify/main.cue @@ -25,6 +25,5 @@ todoApp: netlify.#Site & { contents: yarn.#Script & { source: repository run: "build" - env: "xx" :"bar" } } From 0e21144529f401008ddca01aea97116271b9f3b0 Mon Sep 17 00:00:00 2001 From: Andrea Luzzardi Date: Thu, 25 Mar 2021 15:46:26 -0700 Subject: [PATCH 13/23] tests: fix input tests Signed-off-by: Andrea Luzzardi --- dagger/input_test.go | 18 ++++++------------ 1 file changed, 6 insertions(+), 12 deletions(-) diff --git a/dagger/input_test.go b/dagger/input_test.go index 8702f8c8..c639576e 100644 --- a/dagger/input_test.go +++ b/dagger/input_test.go @@ -6,20 +6,14 @@ import ( "github.com/stretchr/testify/require" ) -func TestEnvInputFlag(t *testing.T) { +func TestInputDir(t *testing.T) { st := &RouteState{} require.NoError(t, st.AddInput("www.source", DirInput(".", []string{}))) - env, err := NewRoute(st) - if err != nil { - t.Fatal(err) - } + route, err := NewRoute(st) + require.NoError(t, err) - localdirs := env.LocalDirs() - if len(localdirs) != 1 { - t.Fatal(localdirs) - } - if dir, ok := localdirs["."]; !ok || dir != "." { - t.Fatal(localdirs) - } + localdirs := route.LocalDirs() + require.Len(t, localdirs, 1) + require.Equal(t, ".", localdirs["."]) } From a3b84386bc99f62d3cb6deb2b292b2fa634a0307 Mon Sep 17 00:00:00 2001 From: Andrea Luzzardi Date: Thu, 25 Mar 2021 15:50:19 -0700 Subject: [PATCH 14/23] store routes as directories Signed-off-by: Andrea Luzzardi --- dagger/store.go | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/dagger/store.go b/dagger/store.go index 2eab5963..9c4a88bf 100644 --- a/dagger/store.go +++ b/dagger/store.go @@ -6,7 +6,6 @@ import ( "errors" "os" "path" - "path/filepath" "github.com/google/uuid" ) @@ -95,18 +94,16 @@ func (s *Store) ListRoutes(ctx context.Context) ([]string, error) { } for _, f := range files { - if f.IsDir() || filepath.Ext(f.Name()) != ".json" { - // There is extra data in the directory, ignore - continue + if f.IsDir() { + routes = append(routes, f.Name()) } - routes = append(routes, f.Name()[:len(f.Name())-5]) } return routes, nil } func (s *Store) routePath(name string) string { - return path.Join(s.root, name+".json") + return path.Join(s.root, name, "route.json") } func (s *Store) syncRoute(r *Route) error { From f59b30a27cdd336dbaa5cb4f94675aec959c1bf8 Mon Sep 17 00:00:00 2001 From: Andrea Luzzardi Date: Thu, 25 Mar 2021 16:07:39 -0700 Subject: [PATCH 15/23] cli: cleanup logger usage Signed-off-by: Andrea Luzzardi --- cmd/dagger/cmd/common.go | 21 ++++++++++++++++----- cmd/dagger/cmd/compute.go | 22 +++++++++++++++++----- cmd/dagger/cmd/down.go | 15 ++++++++++++--- cmd/dagger/cmd/list.go | 5 ++++- cmd/dagger/cmd/new.go | 16 ++++++++++++---- cmd/dagger/cmd/query.go | 15 ++++++++++++--- cmd/dagger/cmd/up.go | 10 +++++++--- 7 files changed, 80 insertions(+), 24 deletions(-) diff --git a/cmd/dagger/cmd/common.go b/cmd/dagger/cmd/common.go index 1b404e93..79a06f6a 100644 --- a/cmd/dagger/cmd/common.go +++ b/cmd/dagger/cmd/common.go @@ -7,15 +7,21 @@ import ( "path/filepath" "dagger.io/go/dagger" - "github.com/rs/zerolog" + "github.com/rs/zerolog/log" "github.com/spf13/cobra" ) // getRouteName returns the selected route name (based on explicit CLI selection or current work dir) -func getRouteName(lg zerolog.Logger, cmd *cobra.Command) string { +func getRouteName(ctx context.Context, cmd *cobra.Command) string { + lg := log.Ctx(ctx) + routeName, err := cmd.Flags().GetString("route") if err != nil { - lg.Fatal().Err(err).Str("flag", "route").Msg("unable to resolve flag") + lg. + Fatal(). + Err(err). + Str("flag", "route"). + Msg("unable to resolve flag") } if routeName != "" { @@ -24,7 +30,10 @@ func getRouteName(lg zerolog.Logger, cmd *cobra.Command) string { workDir, err := os.Getwd() if err != nil { - lg.Fatal().Err(err).Msg("failed to get current working dir") + lg. + Fatal(). + Err(err). + Msg("failed to get current working dir") } currentDir := filepath.Base(workDir) @@ -35,7 +44,9 @@ func getRouteName(lg zerolog.Logger, cmd *cobra.Command) string { return currentDir } -func routeUp(ctx context.Context, lg zerolog.Logger, route *dagger.Route) { +func routeUp(ctx context.Context, route *dagger.Route) { + lg := log.Ctx(ctx) + c, err := dagger.NewClient(ctx, "") if err != nil { lg.Fatal().Err(err).Msg("unable to create client") diff --git a/cmd/dagger/cmd/compute.go b/cmd/dagger/cmd/compute.go index 5e533660..af298b4b 100644 --- a/cmd/dagger/cmd/compute.go +++ b/cmd/dagger/cmd/compute.go @@ -31,7 +31,7 @@ var computeCmd = &cobra.Command{ lg := logger.New() ctx := lg.WithContext(cmd.Context()) - name := getRouteName(lg, cmd) + name := getRouteName(ctx, cmd) st := &dagger.RouteState{ ID: uuid.New().String(), Name: name, @@ -43,7 +43,11 @@ var computeCmd = &cobra.Command{ k, v := parts[0], parts[1] err := st.AddInput(k, dagger.TextInput(v)) if err != nil { - lg.Fatal().Err(err).Str("input", k).Msg("failed to add input") + lg. + Fatal(). + Err(err). + Str("input", k). + Msg("failed to add input") } } @@ -52,7 +56,11 @@ var computeCmd = &cobra.Command{ k, v := parts[0], parts[1] err := st.AddInput(k, dagger.DirInput(v, []string{})) if err != nil { - lg.Fatal().Err(err).Str("input", k).Msg("failed to add input") + lg. + Fatal(). + Err(err). + Str("input", k). + Msg("failed to add input") } } @@ -61,7 +69,11 @@ var computeCmd = &cobra.Command{ k, v := parts[0], parts[1] err := st.AddInput(k, dagger.GitInput(v, "", "")) if err != nil { - lg.Fatal().Err(err).Str("input", k).Msg("failed to add input") + lg. + Fatal(). + Err(err). + Str("input", k). + Msg("failed to add input") } } @@ -120,7 +132,7 @@ var computeCmd = &cobra.Command{ lg.Fatal().Err(err).Msg("unable to initialize route") } - routeUp(ctx, lg, route) + routeUp(ctx, route) }, } diff --git a/cmd/dagger/cmd/down.go b/cmd/dagger/cmd/down.go index c88df0e3..495ec6cd 100644 --- a/cmd/dagger/cmd/down.go +++ b/cmd/dagger/cmd/down.go @@ -25,15 +25,24 @@ var downCmd = &cobra.Command{ ctx := lg.WithContext(cmd.Context()) store := dagger.DefaultStore() - routeName := getRouteName(lg, cmd) + routeName := getRouteName(ctx, cmd) route, err := store.LookupRoute(ctx, routeName, nil) if err != nil { - lg.Fatal().Err(err).Str("route-name", routeName).Msg("failed to lookup route") + lg. + Fatal(). + Err(err). + Str("routeName", routeName). + Msg("failed to lookup route") } // TODO: Implement options: --no-cache if err := route.Down(ctx, nil); err != nil { - lg.Fatal().Err(err).Str("route-name", routeName).Str("route-id", route.ID()).Msg("failed to up the route") + lg. + Fatal(). + Err(err). + Str("routeName", routeName). + Str("routeId", route.ID()). + Msg("failed to up the route") } }, } diff --git a/cmd/dagger/cmd/list.go b/cmd/dagger/cmd/list.go index bb3b02bb..79dbd5ff 100644 --- a/cmd/dagger/cmd/list.go +++ b/cmd/dagger/cmd/list.go @@ -27,7 +27,10 @@ var listCmd = &cobra.Command{ routes, err := store.ListRoutes(ctx) if err != nil { - lg.Fatal().Err(err).Msg("cannot list routes") + lg. + Fatal(). + Err(err). + Msg("cannot list routes") } for _, name := range routes { diff --git a/cmd/dagger/cmd/new.go b/cmd/dagger/cmd/new.go index 21409d68..fadcb896 100644 --- a/cmd/dagger/cmd/new.go +++ b/cmd/dagger/cmd/new.go @@ -26,20 +26,28 @@ var newCmd = &cobra.Command{ upRouteFlag, err := cmd.Flags().GetBool("up") if err != nil { - lg.Fatal().Err(err).Str("flag", "up").Msg("unable to resolve flag") + lg. + Fatal(). + Err(err). + Str("flag", "up"). + Msg("unable to resolve flag") } - routeName := getRouteName(lg, cmd) + routeName := getRouteName(ctx, cmd) // TODO: Implement options: --layout-*, --setup route, err := store.CreateRoute(ctx, routeName, nil) if err != nil { lg.Fatal().Err(err).Msg("failed to create route") } - lg.Info().Str("route-id", route.ID()).Str("route-name", routeName).Msg("created route") + lg. + Info(). + Str("routeId", route.ID()). + Str("routeName", routeName). + Msg("route created") if upRouteFlag { - routeUp(ctx, lg, route) + routeUp(ctx, route) } }, } diff --git a/cmd/dagger/cmd/query.go b/cmd/dagger/cmd/query.go index 58f485ce..7fedf29a 100644 --- a/cmd/dagger/cmd/query.go +++ b/cmd/dagger/cmd/query.go @@ -26,17 +26,26 @@ var queryCmd = &cobra.Command{ ctx := lg.WithContext(cmd.Context()) store := dagger.DefaultStore() - routeName := getRouteName(lg, cmd) + routeName := getRouteName(ctx, cmd) route, err := store.LookupRoute(ctx, routeName, nil) if err != nil { - lg.Fatal().Err(err).Str("route-name", routeName).Msg("failed to lookup route") + lg. + Fatal(). + Err(err). + Str("routeName", routeName). + Msg("failed to lookup route") } expr := args[0] out, err := route.Query(ctx, expr, nil) if err != nil { - lg.Fatal().Err(err).Str("route-name", routeName).Str("route-id", route.ID()).Msg("failed to query route") + lg. + Fatal(). + Err(err). + Str("routeName", routeName). + Str("routeId", route.ID()). + Msg("failed to query route") } fmt.Println(out) diff --git a/cmd/dagger/cmd/up.go b/cmd/dagger/cmd/up.go index 2d4c972a..8186d725 100644 --- a/cmd/dagger/cmd/up.go +++ b/cmd/dagger/cmd/up.go @@ -25,14 +25,18 @@ var upCmd = &cobra.Command{ ctx := lg.WithContext(cmd.Context()) store := dagger.DefaultStore() - routeName := getRouteName(lg, cmd) + routeName := getRouteName(ctx, cmd) route, err := store.LookupRoute(ctx, routeName, nil) if err != nil { - lg.Fatal().Err(err).Str("route-name", routeName).Msg("failed to lookup route") + lg. + Fatal(). + Err(err). + Str("routeName", routeName). + Msg("failed to lookup route") } // TODO: Implement options: --no-cache - routeUp(ctx, lg, route) + routeUp(ctx, route) }, } From 1e8cef9ad0befdf92d0e266ff5de253627cb2359 Mon Sep 17 00:00:00 2001 From: Andrea Luzzardi Date: Thu, 25 Mar 2021 16:11:52 -0700 Subject: [PATCH 16/23] cli: access flags using viper rather than cobra Signed-off-by: Andrea Luzzardi --- cmd/dagger/cmd/common.go | 14 +++----------- cmd/dagger/cmd/compute.go | 2 +- cmd/dagger/cmd/down.go | 2 +- cmd/dagger/cmd/new.go | 11 ++--------- cmd/dagger/cmd/query.go | 2 +- cmd/dagger/cmd/up.go | 2 +- 6 files changed, 9 insertions(+), 24 deletions(-) diff --git a/cmd/dagger/cmd/common.go b/cmd/dagger/cmd/common.go index 79a06f6a..21de7181 100644 --- a/cmd/dagger/cmd/common.go +++ b/cmd/dagger/cmd/common.go @@ -8,22 +8,14 @@ import ( "dagger.io/go/dagger" "github.com/rs/zerolog/log" - "github.com/spf13/cobra" + "github.com/spf13/viper" ) // getRouteName returns the selected route name (based on explicit CLI selection or current work dir) -func getRouteName(ctx context.Context, cmd *cobra.Command) string { +func getRouteName(ctx context.Context) string { lg := log.Ctx(ctx) - routeName, err := cmd.Flags().GetString("route") - if err != nil { - lg. - Fatal(). - Err(err). - Str("flag", "route"). - Msg("unable to resolve flag") - } - + routeName := viper.GetString("route") if routeName != "" { return routeName } diff --git a/cmd/dagger/cmd/compute.go b/cmd/dagger/cmd/compute.go index af298b4b..6867cfb0 100644 --- a/cmd/dagger/cmd/compute.go +++ b/cmd/dagger/cmd/compute.go @@ -31,7 +31,7 @@ var computeCmd = &cobra.Command{ lg := logger.New() ctx := lg.WithContext(cmd.Context()) - name := getRouteName(ctx, cmd) + name := getRouteName(ctx) st := &dagger.RouteState{ ID: uuid.New().String(), Name: name, diff --git a/cmd/dagger/cmd/down.go b/cmd/dagger/cmd/down.go index 495ec6cd..a81c92a2 100644 --- a/cmd/dagger/cmd/down.go +++ b/cmd/dagger/cmd/down.go @@ -25,7 +25,7 @@ var downCmd = &cobra.Command{ ctx := lg.WithContext(cmd.Context()) store := dagger.DefaultStore() - routeName := getRouteName(ctx, cmd) + routeName := getRouteName(ctx) route, err := store.LookupRoute(ctx, routeName, nil) if err != nil { lg. diff --git a/cmd/dagger/cmd/new.go b/cmd/dagger/cmd/new.go index fadcb896..20481b6d 100644 --- a/cmd/dagger/cmd/new.go +++ b/cmd/dagger/cmd/new.go @@ -24,16 +24,9 @@ var newCmd = &cobra.Command{ ctx := lg.WithContext(cmd.Context()) store := dagger.DefaultStore() - upRouteFlag, err := cmd.Flags().GetBool("up") - if err != nil { - lg. - Fatal(). - Err(err). - Str("flag", "up"). - Msg("unable to resolve flag") - } + upRouteFlag := viper.GetBool("up") - routeName := getRouteName(ctx, cmd) + routeName := getRouteName(ctx) // TODO: Implement options: --layout-*, --setup route, err := store.CreateRoute(ctx, routeName, nil) diff --git a/cmd/dagger/cmd/query.go b/cmd/dagger/cmd/query.go index 7fedf29a..ede5b140 100644 --- a/cmd/dagger/cmd/query.go +++ b/cmd/dagger/cmd/query.go @@ -26,7 +26,7 @@ var queryCmd = &cobra.Command{ ctx := lg.WithContext(cmd.Context()) store := dagger.DefaultStore() - routeName := getRouteName(ctx, cmd) + routeName := getRouteName(ctx) route, err := store.LookupRoute(ctx, routeName, nil) if err != nil { lg. diff --git a/cmd/dagger/cmd/up.go b/cmd/dagger/cmd/up.go index 8186d725..b710658d 100644 --- a/cmd/dagger/cmd/up.go +++ b/cmd/dagger/cmd/up.go @@ -25,7 +25,7 @@ var upCmd = &cobra.Command{ ctx := lg.WithContext(cmd.Context()) store := dagger.DefaultStore() - routeName := getRouteName(ctx, cmd) + routeName := getRouteName(ctx) route, err := store.LookupRoute(ctx, routeName, nil) if err != nil { lg. From e08e64b3116ea458ac25f8c70cb6691adda1c20e Mon Sep 17 00:00:00 2001 From: Andrea Luzzardi Date: Thu, 25 Mar 2021 19:08:52 -0700 Subject: [PATCH 17/23] store: keep an in-memory index of routes, support lookup by path Signed-off-by: Andrea Luzzardi --- cmd/dagger/cmd/down.go | 14 ++- cmd/dagger/cmd/list.go | 9 +- cmd/dagger/cmd/new.go | 23 +++- cmd/dagger/cmd/query.go | 15 ++- cmd/dagger/cmd/up.go | 15 ++- dagger/route.go | 11 +- dagger/store.go | 263 +++++++++++++++++++++++++++------------- dagger/store_test.go | 90 +++++++++++--- 8 files changed, 329 insertions(+), 111 deletions(-) diff --git a/cmd/dagger/cmd/down.go b/cmd/dagger/cmd/down.go index a81c92a2..1b246d00 100644 --- a/cmd/dagger/cmd/down.go +++ b/cmd/dagger/cmd/down.go @@ -23,10 +23,13 @@ var downCmd = &cobra.Command{ Run: func(cmd *cobra.Command, args []string) { lg := logger.New() ctx := lg.WithContext(cmd.Context()) - store := dagger.DefaultStore() + store, err := dagger.DefaultStore() + if err != nil { + lg.Fatal().Err(err).Msg("failed to load store") + } routeName := getRouteName(ctx) - route, err := store.LookupRoute(ctx, routeName, nil) + st, err := store.LookupRouteByName(ctx, routeName) if err != nil { lg. Fatal(). @@ -34,6 +37,13 @@ var downCmd = &cobra.Command{ Str("routeName", routeName). Msg("failed to lookup route") } + route, err := dagger.NewRoute(st) + if err != nil { + lg. + Fatal(). + Err(err). + Msg("failed to initialize route") + } // TODO: Implement options: --no-cache if err := route.Down(ctx, nil); err != nil { diff --git a/cmd/dagger/cmd/list.go b/cmd/dagger/cmd/list.go index 79dbd5ff..8bf507ce 100644 --- a/cmd/dagger/cmd/list.go +++ b/cmd/dagger/cmd/list.go @@ -23,7 +23,10 @@ var listCmd = &cobra.Command{ Run: func(cmd *cobra.Command, args []string) { lg := logger.New() ctx := lg.WithContext(cmd.Context()) - store := dagger.DefaultStore() + store, err := dagger.DefaultStore() + if err != nil { + lg.Fatal().Err(err).Msg("failed to load store") + } routes, err := store.ListRoutes(ctx) if err != nil { @@ -33,8 +36,8 @@ var listCmd = &cobra.Command{ Msg("cannot list routes") } - for _, name := range routes { - fmt.Println(name) + for _, r := range routes { + fmt.Println(r.Name) } }, } diff --git a/cmd/dagger/cmd/new.go b/cmd/dagger/cmd/new.go index 20481b6d..9ee4fd8c 100644 --- a/cmd/dagger/cmd/new.go +++ b/cmd/dagger/cmd/new.go @@ -22,23 +22,36 @@ var newCmd = &cobra.Command{ Run: func(cmd *cobra.Command, args []string) { lg := logger.New() ctx := lg.WithContext(cmd.Context()) - store := dagger.DefaultStore() + store, err := dagger.DefaultStore() + if err != nil { + lg.Fatal().Err(err).Msg("failed to load store") + } upRouteFlag := viper.GetBool("up") - routeName := getRouteName(ctx) + st := &dagger.RouteState{ + Name: getRouteName(ctx), + } // TODO: Implement options: --layout-*, --setup - route, err := store.CreateRoute(ctx, routeName, nil) + err = store.CreateRoute(ctx, st) if err != nil { lg.Fatal().Err(err).Msg("failed to create route") } lg. Info(). - Str("routeId", route.ID()). - Str("routeName", routeName). + Str("routeId", st.ID). + Str("routeName", st.Name). Msg("route created") + route, err := dagger.NewRoute(st) + if err != nil { + lg. + Fatal(). + Err(err). + Msg("failed to initialize route") + } + if upRouteFlag { routeUp(ctx, route) } diff --git a/cmd/dagger/cmd/query.go b/cmd/dagger/cmd/query.go index ede5b140..8bcfd86a 100644 --- a/cmd/dagger/cmd/query.go +++ b/cmd/dagger/cmd/query.go @@ -24,10 +24,13 @@ var queryCmd = &cobra.Command{ Run: func(cmd *cobra.Command, args []string) { lg := logger.New() ctx := lg.WithContext(cmd.Context()) - store := dagger.DefaultStore() + store, err := dagger.DefaultStore() + if err != nil { + lg.Fatal().Err(err).Msg("failed to load store") + } routeName := getRouteName(ctx) - route, err := store.LookupRoute(ctx, routeName, nil) + st, err := store.LookupRouteByName(ctx, routeName) if err != nil { lg. Fatal(). @@ -36,6 +39,14 @@ var queryCmd = &cobra.Command{ Msg("failed to lookup route") } + route, err := dagger.NewRoute(st) + if err != nil { + lg. + Fatal(). + Err(err). + Msg("failed to initialize route") + } + expr := args[0] out, err := route.Query(ctx, expr, nil) diff --git a/cmd/dagger/cmd/up.go b/cmd/dagger/cmd/up.go index b710658d..94b07f2d 100644 --- a/cmd/dagger/cmd/up.go +++ b/cmd/dagger/cmd/up.go @@ -23,10 +23,13 @@ var upCmd = &cobra.Command{ Run: func(cmd *cobra.Command, args []string) { lg := logger.New() ctx := lg.WithContext(cmd.Context()) - store := dagger.DefaultStore() + store, err := dagger.DefaultStore() + if err != nil { + lg.Fatal().Err(err).Msg("failed to load store") + } routeName := getRouteName(ctx) - route, err := store.LookupRoute(ctx, routeName, nil) + st, err := store.LookupRouteByName(ctx, routeName) if err != nil { lg. Fatal(). @@ -35,6 +38,14 @@ var upCmd = &cobra.Command{ Msg("failed to lookup route") } + route, err := dagger.NewRoute(st) + if err != nil { + lg. + Fatal(). + Err(err). + Msg("failed to initialize route") + } + // TODO: Implement options: --no-cache routeUp(ctx, route) }, diff --git a/dagger/route.go b/dagger/route.go index 52678c1a..5a1f377f 100644 --- a/dagger/route.go +++ b/dagger/route.go @@ -49,7 +49,16 @@ func (r *RouteState) AddInput(key string, value Input) error { // For example RemoveInputs("foo.bar") will remove all inputs // at foo.bar, foo.bar.baz, etc. func (r *RouteState) RemoveInputs(key string) error { - panic("NOT IMPLEMENTED") + newInputs := make([]inputKV, 0, len(r.Inputs)) + for _, i := range r.Inputs { + if i.Key == key { + continue + } + newInputs = append(newInputs, i) + } + r.Inputs = newInputs + + return nil } type Route struct { diff --git a/dagger/store.go b/dagger/store.go index 9c4a88bf..996ce0ee 100644 --- a/dagger/store.go +++ b/dagger/store.go @@ -6,6 +6,7 @@ import ( "errors" "os" "path" + "sync" "github.com/google/uuid" ) @@ -16,107 +17,205 @@ const ( type Store struct { root string + + l sync.RWMutex + + routes map[string]*RouteState + + // Various indices for fast lookups + routesByName map[string]*RouteState + routesByPath map[string]*RouteState + pathsByRoute map[string][]string } -func NewStore(root string) *Store { - return &Store{ - root: root, +func NewStore(root string) (*Store, error) { + store := &Store{ + root: root, + routes: make(map[string]*RouteState), + routesByName: make(map[string]*RouteState), + routesByPath: make(map[string]*RouteState), + pathsByRoute: make(map[string][]string), } + return store, store.loadAll() } -func DefaultStore() *Store { +func DefaultStore() (*Store, error) { return NewStore(os.ExpandEnv(defaultStoreRoot)) } -type CreateOpts struct{} - -func (s *Store) CreateRoute(ctx context.Context, name string, o *CreateOpts) (*Route, error) { - r, err := s.LookupRoute(ctx, name, &LookupOpts{}) - if err != nil && !errors.Is(err, os.ErrNotExist) { - return nil, err - } - if r != nil { - return nil, os.ErrExist - } - r, err = NewRoute( - &RouteState{ - ID: uuid.New().String(), - Name: name, - }, - ) - if err != nil { - return nil, err - } - - return r, s.syncRoute(r) -} - -type UpdateOpts struct{} - -func (s *Store) UpdateRoute(ctx context.Context, r *Route, o *UpdateOpts) error { - return s.syncRoute(r) -} - -type DeleteOpts struct{} - -func (s *Store) DeleteRoute(ctx context.Context, r *Route, o *DeleteOpts) error { - return os.Remove(s.routePath(r.st.Name)) -} - -type LookupOpts struct{} - -func (s *Store) LookupRoute(ctx context.Context, name string, o *LookupOpts) (*Route, error) { - data, err := os.ReadFile(s.routePath(name)) - if err != nil { - return nil, err - } - var st RouteState - if err := json.Unmarshal(data, &st); err != nil { - return nil, err - } - return &Route{ - st: &st, - }, nil -} - -type LoadOpts struct{} - -func (s *Store) LoadRoute(ctx context.Context, id string, o *LoadOpts) (*Route, error) { - panic("NOT IMPLEMENTED") -} - -func (s *Store) ListRoutes(ctx context.Context) ([]string, error) { - routes := []string{} - - files, err := os.ReadDir(s.root) - if err != nil { - return nil, err - } - - for _, f := range files { - if f.IsDir() { - routes = append(routes, f.Name()) - } - } - - return routes, nil -} - func (s *Store) routePath(name string) string { return path.Join(s.root, name, "route.json") } -func (s *Store) syncRoute(r *Route) error { - p := s.routePath(r.st.Name) +func (s *Store) loadAll() error { + files, err := os.ReadDir(s.root) + if err != nil { + if errors.Is(err, os.ErrNotExist) { + return nil + } + return err + } + + for _, f := range files { + if !f.IsDir() { + continue + } + if err := s.loadRoute(f.Name()); err != nil { + return err + } + } + + return nil +} + +func (s *Store) loadRoute(name string) error { + data, err := os.ReadFile(s.routePath(name)) + if err != nil { + return err + } + var st RouteState + if err := json.Unmarshal(data, &st); err != nil { + return err + } + s.indexRoute(&st) + return nil +} + +func (s *Store) syncRoute(r *RouteState) error { + p := s.routePath(r.Name) if err := os.MkdirAll(path.Dir(p), 0755); err != nil { return err } - data, err := json.MarshalIndent(r.st, "", " ") + data, err := json.MarshalIndent(r, "", " ") if err != nil { return err } - return os.WriteFile(p, data, 0644) + if err := os.WriteFile(p, data, 0644); err != nil { + return err + } + + s.reindexRoute(r) + + return nil +} + +func (s *Store) indexRoute(r *RouteState) { + s.routes[r.ID] = r + s.routesByName[r.Name] = r + + mapPath := func(i Input) { + d, ok := i.(*dirInput) + if !ok { + return + } + s.routesByPath[d.Path] = r + s.pathsByRoute[r.ID] = append(s.pathsByRoute[r.ID], d.Path) + } + + mapPath(r.LayoutSource) + for _, i := range r.Inputs { + mapPath(i.Value) + } +} + +func (s *Store) deindexRoute(id string) { + r, ok := s.routes[id] + if !ok { + return + } + delete(s.routes, r.ID) + delete(s.routesByName, r.Name) + + for _, p := range s.pathsByRoute[r.ID] { + delete(s.routesByPath, p) + } + delete(s.pathsByRoute, r.ID) +} + +func (s *Store) reindexRoute(r *RouteState) { + s.deindexRoute(r.ID) + s.indexRoute(r) +} + +func (s *Store) CreateRoute(ctx context.Context, st *RouteState) error { + s.l.Lock() + defer s.l.Unlock() + + if _, ok := s.routesByName[st.Name]; ok { + return os.ErrExist + } + + st.ID = uuid.New().String() + return s.syncRoute(st) +} + +type UpdateOpts struct{} + +func (s *Store) UpdateRoute(ctx context.Context, r *RouteState, o *UpdateOpts) error { + s.l.Lock() + defer s.l.Unlock() + + return s.syncRoute(r) +} + +type DeleteOpts struct{} + +func (s *Store) DeleteRoute(ctx context.Context, r *RouteState, o *DeleteOpts) error { + s.l.Lock() + defer s.l.Unlock() + + if err := os.Remove(s.routePath(r.Name)); err != nil { + return err + } + s.deindexRoute(r.ID) + return nil +} + +func (s *Store) LookupRouteByID(ctx context.Context, id string) (*RouteState, error) { + s.l.RLock() + defer s.l.RUnlock() + + st, ok := s.routes[id] + if !ok { + return nil, os.ErrNotExist + } + return st, nil +} + +func (s *Store) LookupRouteByName(ctx context.Context, name string) (*RouteState, error) { + s.l.RLock() + defer s.l.RUnlock() + + st, ok := s.routesByName[name] + if !ok { + return nil, os.ErrNotExist + } + return st, nil +} + +func (s *Store) LookupRouteByPath(ctx context.Context, path string) (*RouteState, error) { + s.l.RLock() + defer s.l.RUnlock() + + st, ok := s.routesByPath[path] + if !ok { + return nil, os.ErrNotExist + } + return st, nil +} + +func (s *Store) ListRoutes(ctx context.Context) ([]*RouteState, error) { + s.l.RLock() + defer s.l.RUnlock() + + routes := make([]*RouteState, 0, len(s.routes)) + + for _, st := range s.routes { + routes = append(routes, st) + } + + return routes, nil } diff --git a/dagger/store_test.go b/dagger/store_test.go index c6d3d3b6..cc279773 100644 --- a/dagger/store_test.go +++ b/dagger/store_test.go @@ -9,29 +9,91 @@ import ( "github.com/stretchr/testify/require" ) -func TestStore(t *testing.T) { +func TestStoreLoad(t *testing.T) { ctx := context.TODO() root, err := os.MkdirTemp(os.TempDir(), "dagger-*") require.NoError(t, err) - store := NewStore(root) + store, err := NewStore(root) + require.NoError(t, err) - _, err = store.LookupRoute(ctx, "notexist", nil) + _, err = store.LookupRouteByName(ctx, "notexist") require.Error(t, err) require.True(t, errors.Is(err, os.ErrNotExist)) - r, err := store.CreateRoute(ctx, "test", nil) - require.NoError(t, err) - require.NotNil(t, r) - require.Equal(t, "test", r.Name()) + st := &RouteState{ + Name: "test", + } + require.NoError(t, store.CreateRoute(ctx, st)) - r, err = store.LookupRoute(ctx, "test", nil) - require.NoError(t, err) - require.NotNil(t, r) - require.Equal(t, "test", r.Name()) + checkRoutes := func(store *Store) { + r, err := store.LookupRouteByID(ctx, st.ID) + require.NoError(t, err) + require.NotNil(t, r) + require.Equal(t, "test", r.Name) - routes, err := store.ListRoutes(ctx) + r, err = store.LookupRouteByName(ctx, "test") + require.NoError(t, err) + require.NotNil(t, r) + require.Equal(t, "test", r.Name) + + routes, err := store.ListRoutes(ctx) + require.NoError(t, err) + require.Len(t, routes, 1) + require.Equal(t, "test", routes[0].Name) + } + + checkRoutes(store) + + // Reload the routes from disk and check again + newStore, err := NewStore(root) + require.NoError(t, err) + checkRoutes(newStore) +} + +func TestStoreLookupByPath(t *testing.T) { + ctx := context.TODO() + + root, err := os.MkdirTemp(os.TempDir(), "dagger-*") + require.NoError(t, err) + store, err := NewStore(root) + require.NoError(t, err) + + st := &RouteState{ + Name: "test", + } + require.NoError(t, st.AddInput("foo", DirInput("/test/path", []string{}))) + require.NoError(t, store.CreateRoute(ctx, st)) + + // Lookup by path + r, err := store.LookupRouteByPath(ctx, "/test/path") + require.NoError(t, err) + require.NotNil(t, r) + require.Equal(t, st.ID, r.ID) + + // Add a new path + require.NoError(t, st.AddInput("bar", DirInput("/test/anotherpath", []string{}))) + require.NoError(t, store.UpdateRoute(ctx, st, nil)) + + // Lookup by the previous path + r, err = store.LookupRouteByPath(ctx, "/test/path") + require.NoError(t, err) + require.Equal(t, st.ID, r.ID) + + // Lookup by the new path + r, err = store.LookupRouteByPath(ctx, "/test/anotherpath") + require.NoError(t, err) + require.Equal(t, st.ID, r.ID) + + // Remove a path + require.NoError(t, st.RemoveInputs("foo")) + require.NoError(t, store.UpdateRoute(ctx, st, nil)) + + // Lookup by the removed path should fail + _, err = store.LookupRouteByPath(ctx, "/test/path") + require.Error(t, err) + + // Lookup by the other path should still work + _, err = store.LookupRouteByPath(ctx, "/test/anotherpath") require.NoError(t, err) - require.Len(t, routes, 1) - require.Equal(t, "test", routes[0]) } From a1c90413633df09cd7582b894efd277acc6df72e Mon Sep 17 00:00:00 2001 From: Sam Alba Date: Fri, 26 Mar 2021 11:04:31 -0700 Subject: [PATCH 18/23] implement proper route lookup Signed-off-by: Sam Alba --- cmd/dagger/cmd/common.go | 39 +++++++++++++++++++------------ cmd/dagger/cmd/compute.go | 3 +-- cmd/dagger/cmd/down.go | 20 +++------------- cmd/dagger/cmd/new.go | 49 +++++++++++++++++++++++++++++++++++---- cmd/dagger/cmd/query.go | 21 +++-------------- cmd/dagger/cmd/up.go | 18 +------------- 6 files changed, 76 insertions(+), 74 deletions(-) diff --git a/cmd/dagger/cmd/common.go b/cmd/dagger/cmd/common.go index 21de7181..0f479395 100644 --- a/cmd/dagger/cmd/common.go +++ b/cmd/dagger/cmd/common.go @@ -4,36 +4,45 @@ import ( "context" "fmt" "os" - "path/filepath" "dagger.io/go/dagger" "github.com/rs/zerolog/log" "github.com/spf13/viper" ) -// getRouteName returns the selected route name (based on explicit CLI selection or current work dir) -func getRouteName(ctx context.Context) string { +// getCurrentRoute returns the current selected route based on its abs path +func getCurrentRoute(ctx context.Context, store *dagger.Store) *dagger.Route { lg := log.Ctx(ctx) + var ( + st *dagger.RouteState + err error + ) + routeName := viper.GetString("route") if routeName != "" { - return routeName + st, err = store.LookupRouteByName(ctx, routeName) + if err != nil { + lg.Fatal().Err(err).Str("routeName", routeName).Msg("failed to lookup route by name") + } + } else { + wd, err := os.Getwd() + if err != nil { + lg.Fatal().Err(err).Msg("cannot get current working directory") + } + + st, err = store.LookupRouteByPath(ctx, wd) + if err != nil { + lg.Fatal().Err(err).Str("routePath", wd).Msg("failed to lookup route by path") + } } - workDir, err := os.Getwd() + route, err := dagger.NewRoute(st) if err != nil { - lg. - Fatal(). - Err(err). - Msg("failed to get current working dir") + lg.Fatal().Err(err).Interface("routeState", st).Msg("failed to init route") } - currentDir := filepath.Base(workDir) - if currentDir == "/" { - return "root" - } - - return currentDir + return route } func routeUp(ctx context.Context, route *dagger.Route) { diff --git a/cmd/dagger/cmd/compute.go b/cmd/dagger/cmd/compute.go index 6867cfb0..dc220c4b 100644 --- a/cmd/dagger/cmd/compute.go +++ b/cmd/dagger/cmd/compute.go @@ -31,10 +31,9 @@ var computeCmd = &cobra.Command{ lg := logger.New() ctx := lg.WithContext(cmd.Context()) - name := getRouteName(ctx) st := &dagger.RouteState{ ID: uuid.New().String(), - Name: name, + Name: "FIXME", LayoutSource: dagger.DirInput(args[0], []string{"*.cue", "cue.mod"}), } diff --git a/cmd/dagger/cmd/down.go b/cmd/dagger/cmd/down.go index 1b246d00..447b4c2a 100644 --- a/cmd/dagger/cmd/down.go +++ b/cmd/dagger/cmd/down.go @@ -23,34 +23,20 @@ var downCmd = &cobra.Command{ Run: func(cmd *cobra.Command, args []string) { lg := logger.New() ctx := lg.WithContext(cmd.Context()) + store, err := dagger.DefaultStore() if err != nil { lg.Fatal().Err(err).Msg("failed to load store") } - routeName := getRouteName(ctx) - st, err := store.LookupRouteByName(ctx, routeName) - if err != nil { - lg. - Fatal(). - Err(err). - Str("routeName", routeName). - Msg("failed to lookup route") - } - route, err := dagger.NewRoute(st) - if err != nil { - lg. - Fatal(). - Err(err). - Msg("failed to initialize route") - } + route := getCurrentRoute(ctx, store) // TODO: Implement options: --no-cache if err := route.Down(ctx, nil); err != nil { lg. Fatal(). Err(err). - Str("routeName", routeName). + Str("routeName", route.Name()). Str("routeId", route.ID()). Msg("failed to up the route") } diff --git a/cmd/dagger/cmd/new.go b/cmd/dagger/cmd/new.go index 9ee4fd8c..7fd9e9b7 100644 --- a/cmd/dagger/cmd/new.go +++ b/cmd/dagger/cmd/new.go @@ -1,9 +1,14 @@ package cmd import ( + "context" + "os" + "path/filepath" + "dagger.io/go/cmd/dagger/logger" "dagger.io/go/dagger" + "github.com/rs/zerolog/log" "github.com/spf13/cobra" "github.com/spf13/viper" ) @@ -27,13 +32,11 @@ var newCmd = &cobra.Command{ lg.Fatal().Err(err).Msg("failed to load store") } - upRouteFlag := viper.GetBool("up") - st := &dagger.RouteState{ - Name: getRouteName(ctx), + Name: getNewRouteName(ctx), + LayoutSource: getLayoutSource(ctx), } - // TODO: Implement options: --layout-*, --setup err = store.CreateRoute(ctx, st) if err != nil { lg.Fatal().Err(err).Msg("failed to create route") @@ -52,12 +55,48 @@ var newCmd = &cobra.Command{ Msg("failed to initialize route") } - if upRouteFlag { + if viper.GetBool("up") { routeUp(ctx, route) } }, } +func getNewRouteName(ctx context.Context) string { + lg := log.Ctx(ctx) + + routeName := viper.GetString("route") + if routeName != "" { + return routeName + } + + workDir, err := os.Getwd() + if err != nil { + lg. + Fatal(). + Err(err). + Msg("failed to get current working dir") + } + + currentDir := filepath.Base(workDir) + if currentDir == "/" { + return "root" + } + + return currentDir +} + +// FIXME: Implement options: --layout-* +func getLayoutSource(ctx context.Context) dagger.Input { + lg := log.Ctx(ctx) + + wd, err := os.Getwd() + if err != nil { + lg.Fatal().Err(err).Msg("cannot get current working directory") + } + + return dagger.DirInput(wd, []string{"*.cue", "cue.mod"}) +} + func init() { newCmd.Flags().StringP("name", "n", "", "Specify a route name") newCmd.Flags().BoolP("up", "u", false, "Bring the route online") diff --git a/cmd/dagger/cmd/query.go b/cmd/dagger/cmd/query.go index 8bcfd86a..0eae30de 100644 --- a/cmd/dagger/cmd/query.go +++ b/cmd/dagger/cmd/query.go @@ -24,28 +24,13 @@ var queryCmd = &cobra.Command{ Run: func(cmd *cobra.Command, args []string) { lg := logger.New() ctx := lg.WithContext(cmd.Context()) + store, err := dagger.DefaultStore() if err != nil { lg.Fatal().Err(err).Msg("failed to load store") } - routeName := getRouteName(ctx) - st, err := store.LookupRouteByName(ctx, routeName) - if err != nil { - lg. - Fatal(). - Err(err). - Str("routeName", routeName). - Msg("failed to lookup route") - } - - route, err := dagger.NewRoute(st) - if err != nil { - lg. - Fatal(). - Err(err). - Msg("failed to initialize route") - } + route := getCurrentRoute(ctx, store) expr := args[0] @@ -54,7 +39,7 @@ var queryCmd = &cobra.Command{ lg. Fatal(). Err(err). - Str("routeName", routeName). + Str("routeName", route.Name()). Str("routeId", route.ID()). Msg("failed to query route") } diff --git a/cmd/dagger/cmd/up.go b/cmd/dagger/cmd/up.go index 94b07f2d..4ea07a3e 100644 --- a/cmd/dagger/cmd/up.go +++ b/cmd/dagger/cmd/up.go @@ -28,23 +28,7 @@ var upCmd = &cobra.Command{ lg.Fatal().Err(err).Msg("failed to load store") } - routeName := getRouteName(ctx) - st, err := store.LookupRouteByName(ctx, routeName) - if err != nil { - lg. - Fatal(). - Err(err). - Str("routeName", routeName). - Msg("failed to lookup route") - } - - route, err := dagger.NewRoute(st) - if err != nil { - lg. - Fatal(). - Err(err). - Msg("failed to initialize route") - } + route := getCurrentRoute(ctx, store) // TODO: Implement options: --no-cache routeUp(ctx, route) From ef84d2d43166d499a44a9c2e037874fcc282642c Mon Sep 17 00:00:00 2001 From: Andrea Luzzardi Date: Fri, 26 Mar 2021 14:11:54 -0700 Subject: [PATCH 19/23] inputs: use a struct rather than an interface Signed-off-by: Andrea Luzzardi --- dagger/input.go | 106 ++++++++++++++++++++++++++++++++++-------------- dagger/route.go | 10 ++--- dagger/store.go | 7 ++-- 3 files changed, 82 insertions(+), 41 deletions(-) diff --git a/dagger/input.go b/dagger/input.go index 9e6b8125..d37ee82b 100644 --- a/dagger/input.go +++ b/dagger/input.go @@ -19,22 +19,61 @@ import ( // Under the hood, an artifact is encoded as a LLB pipeline, and // attached to the cue configuration as a // -type Input interface { - // Compile to a cue value which can be merged into a route config - Compile() (*compiler.Value, error) +type InputType string + +const ( + InputTypeDir InputType = "dir" + InputTypeGit InputType = "git" + InputTypeDocker InputType = "docker" + InputTypeText InputType = "text" + InputTypeJSON InputType = "json" + InputTypeYAML InputType = "yaml" +) + +type Input struct { + Type InputType `json:"type,omitempty"` + + Dir *dirInput `json:"dir,omitempty"` + Git *gitInput `json:"git,omitempty"` + Docker *dockerInput `json:"docker,omitempty"` + Text *textInput `json:"text,omitempty"` + JSON *jsonInput `json:"json,omitempty"` + YAML *yamlInput `json:"yaml,omitempty"` +} + +func (i Input) Compile() (*compiler.Value, error) { + switch i.Type { + case InputTypeDir: + return i.Dir.Compile() + case InputTypeGit: + return i.Git.Compile() + case InputTypeDocker: + return i.Docker.Compile() + case InputTypeText: + return i.Text.Compile() + case InputTypeJSON: + return i.JSON.Compile() + case InputTypeYAML: + return i.YAML.Compile() + case "": + return nil, fmt.Errorf("input has not been set") + default: + return nil, fmt.Errorf("unsupported input type: %s", i.Type) + } } // An input artifact loaded from a local directory func DirInput(path string, include []string) Input { - return &dirInput{ - Type: "dir", - Path: path, - Include: include, + return Input{ + Type: InputTypeDir, + Dir: &dirInput{ + Path: path, + Include: include, + }, } } type dirInput struct { - Type string `json:"type,omitempty"` Path string `json:"path,omitempty"` Include []string `json:"include,omitempty"` } @@ -55,18 +94,19 @@ func (dir dirInput) Compile() (*compiler.Value, error) { // An input artifact loaded from a git repository type gitInput struct { - Type string `json:"type,omitempty"` Remote string `json:"remote,omitempty"` Ref string `json:"ref,omitempty"` Dir string `json:"dir,omitempty"` } func GitInput(remote, ref, dir string) Input { - return &gitInput{ - Type: "git", - Remote: remote, - Ref: ref, - Dir: dir, + return Input{ + Type: InputTypeGit, + Git: &gitInput{ + Remote: remote, + Ref: ref, + Dir: dir, + }, } } @@ -76,15 +116,16 @@ func (git gitInput) Compile() (*compiler.Value, error) { // An input artifact loaded from a docker container func DockerInput(ref string) Input { - return &dockerInput{ - Type: "docker", - Ref: ref, + return Input{ + Type: InputTypeDocker, + Docker: &dockerInput{ + Ref: ref, + }, } } type dockerInput struct { - Type string `json:"type,omitempty"` - Ref string `json:"ref,omitempty"` + Ref string `json:"ref,omitempty"` } func (i dockerInput) Compile() (*compiler.Value, error) { @@ -93,14 +134,15 @@ func (i dockerInput) Compile() (*compiler.Value, error) { // An input value encoded as text func TextInput(data string) Input { - return &textInput{ - Type: "text", - Data: data, + return Input{ + Type: InputTypeText, + Text: &textInput{ + Data: data, + }, } } type textInput struct { - Type string `json:"type,omitempty"` Data string `json:"data,omitempty"` } @@ -110,14 +152,15 @@ func (i textInput) Compile() (*compiler.Value, error) { // An input value encoded as JSON func JSONInput(data string) Input { - return &jsonInput{ - Type: "json", - Data: data, + return Input{ + Type: InputTypeJSON, + JSON: &jsonInput{ + Data: data, + }, } } type jsonInput struct { - Type string `json:"type,omitempty"` // Marshalled JSON data Data string `json:"data,omitempty"` } @@ -128,14 +171,15 @@ func (i jsonInput) Compile() (*compiler.Value, error) { // An input value encoded as YAML func YAMLInput(data string) Input { - return &yamlInput{ - Type: "yaml", - Data: data, + return Input{ + Type: InputTypeYAML, + YAML: &yamlInput{ + Data: data, + }, } } type yamlInput struct { - Type string `json:"type,omitempty"` // Marshalled YAML data Data string `json:"data,omitempty"` } diff --git a/dagger/route.go b/dagger/route.go index 5a1f377f..ac378dd3 100644 --- a/dagger/route.go +++ b/dagger/route.go @@ -206,13 +206,11 @@ func (r *Route) LocalDirs() map[string]string { } // 2. Scan the layout - if r.st.LayoutSource != nil { - layout, err := r.st.LayoutSource.Compile() - if err != nil { - panic(err) - } - localdirs(layout) + layout, err := r.st.LayoutSource.Compile() + if err != nil { + panic(err) } + localdirs(layout) return dirs } diff --git a/dagger/store.go b/dagger/store.go index 996ce0ee..50e604d7 100644 --- a/dagger/store.go +++ b/dagger/store.go @@ -107,12 +107,11 @@ func (s *Store) indexRoute(r *RouteState) { s.routesByName[r.Name] = r mapPath := func(i Input) { - d, ok := i.(*dirInput) - if !ok { + if i.Type != InputTypeDir { return } - s.routesByPath[d.Path] = r - s.pathsByRoute[r.ID] = append(s.pathsByRoute[r.ID], d.Path) + s.routesByPath[i.Dir.Path] = r + s.pathsByRoute[r.ID] = append(s.pathsByRoute[r.ID], i.Dir.Path) } mapPath(r.LayoutSource) From 576613e46ad54e398a7316542c68bc673146111c Mon Sep 17 00:00:00 2001 From: Sam Alba Date: Fri, 26 Mar 2021 15:50:18 -0700 Subject: [PATCH 20/23] implemented inputs Signed-off-by: Sam Alba --- cmd/dagger/cmd/{ => common}/common.go | 25 ++++++++++++------- cmd/dagger/cmd/compute.go | 3 ++- cmd/dagger/cmd/down.go | 3 ++- cmd/dagger/cmd/input/container.go | 12 ++++++---- cmd/dagger/cmd/input/dir.go | 12 ++++++---- cmd/dagger/cmd/input/git.go | 17 +++++++++---- cmd/dagger/cmd/input/root.go | 28 ++++++++++++++++++++-- cmd/dagger/cmd/input/secret.go | 4 ++-- cmd/dagger/cmd/input/{value.go => text.go} | 18 +++++++------- cmd/dagger/cmd/new.go | 3 ++- cmd/dagger/cmd/query.go | 3 ++- cmd/dagger/cmd/up.go | 5 ++-- 12 files changed, 91 insertions(+), 42 deletions(-) rename cmd/dagger/cmd/{ => common}/common.go (79%) rename cmd/dagger/cmd/input/{value.go => text.go} (52%) diff --git a/cmd/dagger/cmd/common.go b/cmd/dagger/cmd/common/common.go similarity index 79% rename from cmd/dagger/cmd/common.go rename to cmd/dagger/cmd/common/common.go index 0f479395..155d9133 100644 --- a/cmd/dagger/cmd/common.go +++ b/cmd/dagger/cmd/common/common.go @@ -1,4 +1,4 @@ -package cmd +package common import ( "context" @@ -11,7 +11,19 @@ import ( ) // getCurrentRoute returns the current selected route based on its abs path -func getCurrentRoute(ctx context.Context, store *dagger.Store) *dagger.Route { +func GetCurrentRoute(ctx context.Context, store *dagger.Store) *dagger.Route { + lg := log.Ctx(ctx) + st := GetCurrentRouteState(ctx, store) + + route, err := dagger.NewRoute(st) + if err != nil { + lg.Fatal().Err(err).Interface("routeState", st).Msg("failed to init route") + } + + return route +} + +func GetCurrentRouteState(ctx context.Context, store *dagger.Store) *dagger.RouteState { lg := log.Ctx(ctx) var ( @@ -37,15 +49,10 @@ func getCurrentRoute(ctx context.Context, store *dagger.Store) *dagger.Route { } } - route, err := dagger.NewRoute(st) - if err != nil { - lg.Fatal().Err(err).Interface("routeState", st).Msg("failed to init route") - } - - return route + return st } -func routeUp(ctx context.Context, route *dagger.Route) { +func RouteUp(ctx context.Context, route *dagger.Route) { lg := log.Ctx(ctx) c, err := dagger.NewClient(ctx, "") diff --git a/cmd/dagger/cmd/compute.go b/cmd/dagger/cmd/compute.go index dc220c4b..86054007 100644 --- a/cmd/dagger/cmd/compute.go +++ b/cmd/dagger/cmd/compute.go @@ -6,6 +6,7 @@ import ( "os" "strings" + "dagger.io/go/cmd/dagger/cmd/common" "dagger.io/go/cmd/dagger/logger" "dagger.io/go/dagger" "go.mozilla.org/sops" @@ -131,7 +132,7 @@ var computeCmd = &cobra.Command{ lg.Fatal().Err(err).Msg("unable to initialize route") } - routeUp(ctx, route) + common.RouteUp(ctx, route) }, } diff --git a/cmd/dagger/cmd/down.go b/cmd/dagger/cmd/down.go index 447b4c2a..cce8255e 100644 --- a/cmd/dagger/cmd/down.go +++ b/cmd/dagger/cmd/down.go @@ -2,6 +2,7 @@ package cmd import ( + "dagger.io/go/cmd/dagger/cmd/common" "dagger.io/go/cmd/dagger/logger" "dagger.io/go/dagger" @@ -29,7 +30,7 @@ var downCmd = &cobra.Command{ lg.Fatal().Err(err).Msg("failed to load store") } - route := getCurrentRoute(ctx, store) + route := common.GetCurrentRoute(ctx, store) // TODO: Implement options: --no-cache if err := route.Down(ctx, nil); err != nil { diff --git a/cmd/dagger/cmd/input/container.go b/cmd/dagger/cmd/input/container.go index 5187ca9b..1ec31366 100644 --- a/cmd/dagger/cmd/input/container.go +++ b/cmd/dagger/cmd/input/container.go @@ -1,14 +1,16 @@ package input import ( + "dagger.io/go/cmd/dagger/logger" + "dagger.io/go/dagger" "github.com/spf13/cobra" "github.com/spf13/viper" ) var containerCmd = &cobra.Command{ - Use: "container ID", + Use: "container TARGET CONTAINER-IMAGE", Short: "Add a container image as input artifact", - Args: cobra.ExactArgs(1), + Args: cobra.ExactArgs(2), PreRun: func(cmd *cobra.Command, args []string) { // Fix Viper bug for duplicate flags: // https://github.com/spf13/viper/issues/233 @@ -17,10 +19,10 @@ var containerCmd = &cobra.Command{ } }, Run: func(cmd *cobra.Command, args []string) { - // lg := logger.New() - // ctx := lg.WithContext(cmd.Context()) + lg := logger.New() + ctx := lg.WithContext(cmd.Context()) - panic("not implemented") + updateRouteInput(ctx, args[0], dagger.DockerInput(args[1])) }, } diff --git a/cmd/dagger/cmd/input/dir.go b/cmd/dagger/cmd/input/dir.go index 170aa955..809a1401 100644 --- a/cmd/dagger/cmd/input/dir.go +++ b/cmd/dagger/cmd/input/dir.go @@ -1,14 +1,16 @@ package input import ( + "dagger.io/go/cmd/dagger/logger" + "dagger.io/go/dagger" "github.com/spf13/cobra" "github.com/spf13/viper" ) var dirCmd = &cobra.Command{ - Use: "dir PATH", + Use: "dir TARGET PATH", Short: "Add a local directory as input artifact", - Args: cobra.ExactArgs(1), + Args: cobra.ExactArgs(2), PreRun: func(cmd *cobra.Command, args []string) { // Fix Viper bug for duplicate flags: // https://github.com/spf13/viper/issues/233 @@ -17,10 +19,10 @@ var dirCmd = &cobra.Command{ } }, Run: func(cmd *cobra.Command, args []string) { - // lg := logger.New() - // ctx := lg.WithContext(cmd.Context()) + lg := logger.New() + ctx := lg.WithContext(cmd.Context()) - panic("not implemented") + updateRouteInput(ctx, args[0], dagger.DirInput(args[1], []string{})) }, } diff --git a/cmd/dagger/cmd/input/git.go b/cmd/dagger/cmd/input/git.go index 3452f1a7..db0c75cd 100644 --- a/cmd/dagger/cmd/input/git.go +++ b/cmd/dagger/cmd/input/git.go @@ -1,14 +1,16 @@ package input import ( + "dagger.io/go/cmd/dagger/logger" + "dagger.io/go/dagger" "github.com/spf13/cobra" "github.com/spf13/viper" ) var gitCmd = &cobra.Command{ - Use: "git REMOTE REF [SUBDIR]", + Use: "git TARGET REMOTE REF [SUBDIR]", Short: "Add a git repository as input artifact", - Args: cobra.MinimumNArgs(2), + Args: cobra.RangeArgs(3, 4), PreRun: func(cmd *cobra.Command, args []string) { // Fix Viper bug for duplicate flags: // https://github.com/spf13/viper/issues/233 @@ -17,10 +19,15 @@ var gitCmd = &cobra.Command{ } }, Run: func(cmd *cobra.Command, args []string) { - // lg := logger.New() - // ctx := lg.WithContext(cmd.Context()) + lg := logger.New() + ctx := lg.WithContext(cmd.Context()) - panic("not implemented") + subDir := "" + if len(args) > 3 { + subDir = args[3] + } + + updateRouteInput(ctx, args[0], dagger.GitInput(args[1], args[2], subDir)) }, } diff --git a/cmd/dagger/cmd/input/root.go b/cmd/dagger/cmd/input/root.go index a8cb9f06..62150429 100644 --- a/cmd/dagger/cmd/input/root.go +++ b/cmd/dagger/cmd/input/root.go @@ -1,6 +1,13 @@ package input -import "github.com/spf13/cobra" +import ( + "context" + + "dagger.io/go/cmd/dagger/cmd/common" + "dagger.io/go/dagger" + "github.com/rs/zerolog/log" + "github.com/spf13/cobra" +) // Cmd exposes the top-level command var Cmd = &cobra.Command{ @@ -14,6 +21,23 @@ func init() { gitCmd, containerCmd, secretCmd, - valueCmd, + textCmd, ) } + +func updateRouteInput(ctx context.Context, target string, input dagger.Input) { + lg := log.Ctx(ctx) + + store, err := dagger.DefaultStore() + if err != nil { + lg.Fatal().Err(err).Msg("failed to load store") + } + + st := common.GetCurrentRouteState(ctx, store) + st.AddInput(target, input) + + if err := store.UpdateRoute(ctx, st, nil); err != nil { + lg.Fatal().Err(err).Str("routeId", st.ID).Str("routeName", st.Name).Msg("cannot update route") + } + lg.Info().Str("routeId", st.ID).Str("routeName", st.Name).Msg("updated route") +} diff --git a/cmd/dagger/cmd/input/secret.go b/cmd/dagger/cmd/input/secret.go index b1abd078..7701d046 100644 --- a/cmd/dagger/cmd/input/secret.go +++ b/cmd/dagger/cmd/input/secret.go @@ -6,9 +6,9 @@ import ( ) var secretCmd = &cobra.Command{ - Use: "secret VALUE", + Use: "secret TARGET VALUE", Short: "Add an encrypted input secret", - Args: cobra.ExactArgs(1), + Args: cobra.ExactArgs(2), PreRun: func(cmd *cobra.Command, args []string) { // Fix Viper bug for duplicate flags: // https://github.com/spf13/viper/issues/233 diff --git a/cmd/dagger/cmd/input/value.go b/cmd/dagger/cmd/input/text.go similarity index 52% rename from cmd/dagger/cmd/input/value.go rename to cmd/dagger/cmd/input/text.go index f8573493..3f500680 100644 --- a/cmd/dagger/cmd/input/value.go +++ b/cmd/dagger/cmd/input/text.go @@ -1,14 +1,16 @@ package input import ( + "dagger.io/go/cmd/dagger/logger" + "dagger.io/go/dagger" "github.com/spf13/cobra" "github.com/spf13/viper" ) -var valueCmd = &cobra.Command{ - Use: "value VALUE", - Short: "Add an input value", - Args: cobra.ExactArgs(1), +var textCmd = &cobra.Command{ + Use: "text TARGET VALUE", + Short: "Add an input text", + Args: cobra.ExactArgs(2), PreRun: func(cmd *cobra.Command, args []string) { // Fix Viper bug for duplicate flags: // https://github.com/spf13/viper/issues/233 @@ -17,15 +19,15 @@ var valueCmd = &cobra.Command{ } }, Run: func(cmd *cobra.Command, args []string) { - // lg := logger.New() - // ctx := lg.WithContext(cmd.Context()) + lg := logger.New() + ctx := lg.WithContext(cmd.Context()) - panic("not implemented") + updateRouteInput(ctx, args[0], dagger.TextInput(args[1])) }, } func init() { - if err := viper.BindPFlags(valueCmd.Flags()); err != nil { + if err := viper.BindPFlags(textCmd.Flags()); err != nil { panic(err) } } diff --git a/cmd/dagger/cmd/new.go b/cmd/dagger/cmd/new.go index 7fd9e9b7..b5a83ae1 100644 --- a/cmd/dagger/cmd/new.go +++ b/cmd/dagger/cmd/new.go @@ -5,6 +5,7 @@ import ( "os" "path/filepath" + "dagger.io/go/cmd/dagger/cmd/common" "dagger.io/go/cmd/dagger/logger" "dagger.io/go/dagger" @@ -56,7 +57,7 @@ var newCmd = &cobra.Command{ } if viper.GetBool("up") { - routeUp(ctx, route) + common.RouteUp(ctx, route) } }, } diff --git a/cmd/dagger/cmd/query.go b/cmd/dagger/cmd/query.go index 0eae30de..8a37bc76 100644 --- a/cmd/dagger/cmd/query.go +++ b/cmd/dagger/cmd/query.go @@ -3,6 +3,7 @@ package cmd import ( "fmt" + "dagger.io/go/cmd/dagger/cmd/common" "dagger.io/go/cmd/dagger/logger" "dagger.io/go/dagger" @@ -30,7 +31,7 @@ var queryCmd = &cobra.Command{ lg.Fatal().Err(err).Msg("failed to load store") } - route := getCurrentRoute(ctx, store) + route := common.GetCurrentRoute(ctx, store) expr := args[0] diff --git a/cmd/dagger/cmd/up.go b/cmd/dagger/cmd/up.go index 4ea07a3e..d47bbb6a 100644 --- a/cmd/dagger/cmd/up.go +++ b/cmd/dagger/cmd/up.go @@ -2,6 +2,7 @@ package cmd import ( + "dagger.io/go/cmd/dagger/cmd/common" "dagger.io/go/cmd/dagger/logger" "dagger.io/go/dagger" @@ -28,10 +29,10 @@ var upCmd = &cobra.Command{ lg.Fatal().Err(err).Msg("failed to load store") } - route := getCurrentRoute(ctx, store) + route := common.GetCurrentRoute(ctx, store) // TODO: Implement options: --no-cache - routeUp(ctx, route) + common.RouteUp(ctx, route) }, } From 3bfa056b9be624c4ac797665413dab0785876cea Mon Sep 17 00:00:00 2001 From: Andrea Luzzardi Date: Fri, 26 Mar 2021 16:24:18 -0700 Subject: [PATCH 21/23] tests: fix input test Signed-off-by: Andrea Luzzardi --- dagger/input_test.go | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/dagger/input_test.go b/dagger/input_test.go index c639576e..a8482946 100644 --- a/dagger/input_test.go +++ b/dagger/input_test.go @@ -7,13 +7,16 @@ import ( ) func TestInputDir(t *testing.T) { - st := &RouteState{} + st := &RouteState{ + LayoutSource: DirInput("/tmp/source", []string{}), + } require.NoError(t, st.AddInput("www.source", DirInput(".", []string{}))) route, err := NewRoute(st) require.NoError(t, err) localdirs := route.LocalDirs() - require.Len(t, localdirs, 1) - require.Equal(t, ".", localdirs["."]) + require.Len(t, localdirs, 2) + require.Contains(t, localdirs, ".") + require.Contains(t, localdirs, "/tmp/source") } From a45f3447b738960d2a03cea608017ad22904aab1 Mon Sep 17 00:00:00 2001 From: Andrea Luzzardi Date: Fri, 26 Mar 2021 16:44:13 -0700 Subject: [PATCH 22/23] env -> route cleanup Signed-off-by: Andrea Luzzardi --- dagger/client.go | 26 ++++++++--------- dagger/compiler/value.go | 11 -------- dagger/pipeline.go | 6 +++- dagger/route.go | 60 +++++++++++++++++++++------------------- 4 files changed, 50 insertions(+), 53 deletions(-) diff --git a/dagger/client.go b/dagger/client.go index 399166e9..fdf422d0 100644 --- a/dagger/client.go +++ b/dagger/client.go @@ -60,8 +60,8 @@ func NewClient(ctx context.Context, host string) (*Client, error) { }, nil } -// FIXME: return completed *Env, instead of *compiler.Value -func (c *Client) Up(ctx context.Context, env *Route) (*compiler.Value, error) { +// FIXME: return completed *Route, instead of *compiler.Value +func (c *Client) Up(ctx context.Context, route *Route) (*compiler.Value, error) { lg := log.Ctx(ctx) eg, gctx := errgroup.WithContext(ctx) @@ -78,7 +78,7 @@ func (c *Client) Up(ctx context.Context, env *Route) (*compiler.Value, error) { outr, outw := io.Pipe() eg.Go(func() error { defer outw.Close() - return c.buildfn(gctx, env, events, outw) + return c.buildfn(gctx, route, events, outw) }) // Spawn output retriever @@ -95,11 +95,11 @@ func (c *Client) Up(ctx context.Context, env *Route) (*compiler.Value, error) { return out, compiler.Err(eg.Wait()) } -func (c *Client) buildfn(ctx context.Context, env *Route, ch chan *bk.SolveStatus, w io.WriteCloser) error { +func (c *Client) buildfn(ctx context.Context, route *Route, ch chan *bk.SolveStatus, w io.WriteCloser) error { lg := log.Ctx(ctx) // Scan local dirs to grant access - localdirs := env.LocalDirs() + localdirs := route.LocalDirs() for label, dir := range localdirs { abs, err := filepath.Abs(dir) if err != nil { @@ -132,24 +132,24 @@ func (c *Client) buildfn(ctx context.Context, env *Route, ch chan *bk.SolveStatu s := NewSolver(c.c, gw, ch) lg.Debug().Msg("loading configuration") - if err := env.Update(ctx, s); err != nil { + if err := route.LoadLayout(ctx, s); err != nil { return nil, err } // Compute output overlay - lg.Debug().Msg("computing env") - if err := env.Up(ctx, s, nil); err != nil { + lg.Debug().Msg("computing route") + if err := route.Up(ctx, s, nil); err != nil { return nil, err } - // Export env to a cue directory + // Export route to a cue directory // FIXME: this should be elsewhere - lg.Debug().Msg("exporting env") - span, _ := opentracing.StartSpanFromContext(ctx, "Env.Export") + lg.Debug().Msg("exporting route") + span, _ := opentracing.StartSpanFromContext(ctx, "Route.Export") defer span.Finish() st := llb.Scratch().File( - llb.Mkfile("state.cue", 0600, env.State().JSON()), + llb.Mkfile("state.cue", 0600, route.State().JSON()), llb.WithCustomName("[internal] serializing state to JSON"), ) ref, err := s.Solve(ctx, st) @@ -178,7 +178,7 @@ func (c *Client) buildfn(ctx context.Context, env *Route, ch chan *bk.SolveStatu func (c *Client) outputfn(ctx context.Context, r io.Reader) (*compiler.Value, error) { lg := log.Ctx(ctx) - // FIXME: merge this into env output. + // FIXME: merge this into route output. out := compiler.EmptyStruct() tr := tar.NewReader(r) diff --git a/dagger/compiler/value.go b/dagger/compiler/value.go index 0314cfdc..d0ee9477 100644 --- a/dagger/compiler/value.go +++ b/dagger/compiler/value.go @@ -123,11 +123,6 @@ func (v *Value) Int64() (int64, error) { return v.val.Int64() } -func (v *Value) SourceUnsafe() string { - s, _ := v.SourceString() - return s -} - // Proxy function to the underlying cue.Value func (v *Value) Path() cue.Path { return v.val.Path() @@ -236,12 +231,6 @@ func (v *Value) Source() ([]byte, error) { return cueformat.Node(v.val.Eval().Syntax()) } -// Return cue source for this value, as a Go string -func (v *Value) SourceString() (string, error) { - b, err := v.Source() - return string(b), err -} - func (v *Value) IsEmptyStruct() bool { if st, err := v.Struct(); err == nil { if st.Len() == 0 { diff --git a/dagger/pipeline.go b/dagger/pipeline.go index 6ea5034e..c7ec55d3 100644 --- a/dagger/pipeline.go +++ b/dagger/pipeline.go @@ -84,7 +84,11 @@ func ops(code ...*compiler.Value) ([]*compiler.Value, error) { ops = append(ops, xops...) } else { // 4. error - return nil, fmt.Errorf("not executable: %s", x.SourceUnsafe()) + source, err := x.Source() + if err != nil { + panic(err) + } + return nil, fmt.Errorf("not executable: %s", source) } } return ops, nil diff --git a/dagger/route.go b/dagger/route.go index ac378dd3..b2013b1b 100644 --- a/dagger/route.go +++ b/dagger/route.go @@ -82,18 +82,6 @@ type Route struct { state *compiler.Value } -func (r *Route) ID() string { - return r.st.ID -} - -func (r *Route) Name() string { - return r.st.Name -} - -func (r *Route) LayoutSource() Input { - return r.st.LayoutSource -} - func NewRoute(st *RouteState) (*Route, error) { empty := compiler.EmptyStruct() r := &Route{ @@ -125,23 +113,47 @@ func NewRoute(st *RouteState) (*Route, error) { return r, nil } +func (r *Route) ID() string { + return r.st.ID +} + +func (r *Route) Name() string { + return r.st.Name +} + +func (r *Route) LayoutSource() Input { + return r.st.LayoutSource +} + +func (r *Route) Layout() *compiler.Value { + return r.layout +} + +func (r *Route) Input() *compiler.Value { + return r.input +} + +func (r *Route) Output() *compiler.Value { + return r.output +} + func (r *Route) State() *compiler.Value { return r.state } -// Update the base configuration -func (r *Route) Update(ctx context.Context, s Solver) error { +// LoadLayout loads the layout +func (r *Route) LoadLayout(ctx context.Context, s Solver) error { span, ctx := opentracing.StartSpanFromContext(ctx, "r.Update") defer span.Finish() - layout, err := r.st.LayoutSource.Compile() + layoutSource, err := r.st.LayoutSource.Compile() if err != nil { return err } p := NewPipeline("[internal] source", s, nil) // execute updater script - if err := p.Do(ctx, layout); err != nil { + if err := p.Do(ctx, layoutSource); err != nil { return err } @@ -150,24 +162,16 @@ func (r *Route) Update(ctx context.Context, s Solver) error { stdlib.Path: stdlib.FS, "/": p.FS(), } - base, err := compiler.Build(sources) + layout, err := compiler.Build(sources) if err != nil { - return fmt.Errorf("base config: %w", err) + return fmt.Errorf("layout config: %w", err) } - r.layout = base + r.layout = layout // Commit return r.mergeState() } -func (r *Route) Base() *compiler.Value { - return r.layout -} - -func (r *Route) Output() *compiler.Value { - return r.output -} - // Scan all scripts in the environment for references to local directories (do:"local"), // and return all referenced directory names. // This is used by clients to grant access to local directories when they are referenced @@ -299,7 +303,7 @@ func (r *Route) Up(ctx context.Context, s Solver, _ *UpOpts) error { } { - span, _ := opentracing.StartSpanFromContext(ctx, "r.Compute: merge state") + span, _ := opentracing.StartSpanFromContext(ctx, "merge state") defer span.Finish() return r.mergeState() From 0f09ba5e875d99caf272500e35db0f23f5b072f6 Mon Sep 17 00:00:00 2001 From: Andrea Luzzardi Date: Tue, 30 Mar 2021 18:53:09 -0700 Subject: [PATCH 23/23] minor error messages and linter cleanups Signed-off-by: Andrea Luzzardi --- cmd/dagger/cmd/common/common.go | 42 +++++++++++++++++++-------------- cmd/dagger/cmd/down.go | 1 - cmd/dagger/cmd/up.go | 1 - dagger/route.go | 3 +-- dagger/store.go | 14 +++++++---- dagger/store_test.go | 2 +- 6 files changed, 36 insertions(+), 27 deletions(-) diff --git a/cmd/dagger/cmd/common/common.go b/cmd/dagger/cmd/common/common.go index 155d9133..c7f3f527 100644 --- a/cmd/dagger/cmd/common/common.go +++ b/cmd/dagger/cmd/common/common.go @@ -17,7 +17,11 @@ func GetCurrentRoute(ctx context.Context, store *dagger.Store) *dagger.Route { route, err := dagger.NewRoute(st) if err != nil { - lg.Fatal().Err(err).Interface("routeState", st).Msg("failed to init route") + lg. + Fatal(). + Err(err). + Interface("routeState", st). + Msg("failed to init route") } return route @@ -26,29 +30,31 @@ func GetCurrentRoute(ctx context.Context, store *dagger.Store) *dagger.Route { func GetCurrentRouteState(ctx context.Context, store *dagger.Store) *dagger.RouteState { lg := log.Ctx(ctx) - var ( - st *dagger.RouteState - err error - ) - routeName := viper.GetString("route") if routeName != "" { - st, err = store.LookupRouteByName(ctx, routeName) + st, err := store.LookupRouteByName(ctx, routeName) if err != nil { - lg.Fatal().Err(err).Str("routeName", routeName).Msg("failed to lookup route by name") - } - } else { - wd, err := os.Getwd() - if err != nil { - lg.Fatal().Err(err).Msg("cannot get current working directory") - } - - st, err = store.LookupRouteByPath(ctx, wd) - if err != nil { - lg.Fatal().Err(err).Str("routePath", wd).Msg("failed to lookup route by path") + lg. + Fatal(). + Err(err). + Str("routeName", routeName). + Msg("failed to lookup route by name") } + return st } + wd, err := os.Getwd() + if err != nil { + lg.Fatal().Err(err).Msg("cannot get current working directory") + } + st, err := store.LookupRouteByPath(ctx, wd) + if err != nil { + lg. + Fatal(). + Err(err). + Str("routePath", wd). + Msg("failed to lookup route by path") + } return st } diff --git a/cmd/dagger/cmd/down.go b/cmd/dagger/cmd/down.go index cce8255e..35aa7d89 100644 --- a/cmd/dagger/cmd/down.go +++ b/cmd/dagger/cmd/down.go @@ -1,4 +1,3 @@ -// nolint:dupl package cmd import ( diff --git a/cmd/dagger/cmd/up.go b/cmd/dagger/cmd/up.go index d47bbb6a..bffb7722 100644 --- a/cmd/dagger/cmd/up.go +++ b/cmd/dagger/cmd/up.go @@ -1,4 +1,3 @@ -// nolint:dupl package cmd import ( diff --git a/dagger/route.go b/dagger/route.go index b2013b1b..788cfc3c 100644 --- a/dagger/route.go +++ b/dagger/route.go @@ -143,7 +143,7 @@ func (r *Route) State() *compiler.Value { // LoadLayout loads the layout func (r *Route) LoadLayout(ctx context.Context, s Solver) error { - span, ctx := opentracing.StartSpanFromContext(ctx, "r.Update") + span, ctx := opentracing.StartSpanFromContext(ctx, "route.Update") defer span.Finish() layoutSource, err := r.st.LayoutSource.Compile() @@ -185,7 +185,6 @@ func (r *Route) LocalDirs() map[string]string { if err != nil { return err } - // nolint:goconst // FIXME: merge Env into Route, or fix the linter error if do != "local" { return nil diff --git a/dagger/store.go b/dagger/store.go index 50e604d7..4566126b 100644 --- a/dagger/store.go +++ b/dagger/store.go @@ -4,6 +4,7 @@ import ( "context" "encoding/json" "errors" + "fmt" "os" "path" "sync" @@ -11,6 +12,11 @@ import ( "github.com/google/uuid" ) +var ( + ErrRouteExist = errors.New("route already exists") + ErrRouteNotExist = errors.New("route doesn't exist") +) + const ( defaultStoreRoot = "$HOME/.config/dagger/routes" ) @@ -144,7 +150,7 @@ func (s *Store) CreateRoute(ctx context.Context, st *RouteState) error { defer s.l.Unlock() if _, ok := s.routesByName[st.Name]; ok { - return os.ErrExist + return fmt.Errorf("%s: %w", st.Name, ErrRouteExist) } st.ID = uuid.New().String() @@ -179,7 +185,7 @@ func (s *Store) LookupRouteByID(ctx context.Context, id string) (*RouteState, er st, ok := s.routes[id] if !ok { - return nil, os.ErrNotExist + return nil, fmt.Errorf("%s: %w", id, ErrRouteNotExist) } return st, nil } @@ -190,7 +196,7 @@ func (s *Store) LookupRouteByName(ctx context.Context, name string) (*RouteState st, ok := s.routesByName[name] if !ok { - return nil, os.ErrNotExist + return nil, fmt.Errorf("%s: %w", name, ErrRouteNotExist) } return st, nil } @@ -201,7 +207,7 @@ func (s *Store) LookupRouteByPath(ctx context.Context, path string) (*RouteState st, ok := s.routesByPath[path] if !ok { - return nil, os.ErrNotExist + return nil, fmt.Errorf("%s: %w", path, ErrRouteNotExist) } return st, nil } diff --git a/dagger/store_test.go b/dagger/store_test.go index cc279773..9602a336 100644 --- a/dagger/store_test.go +++ b/dagger/store_test.go @@ -19,7 +19,7 @@ func TestStoreLoad(t *testing.T) { _, err = store.LookupRouteByName(ctx, "notexist") require.Error(t, err) - require.True(t, errors.Is(err, os.ErrNotExist)) + require.True(t, errors.Is(err, ErrRouteNotExist)) st := &RouteState{ Name: "test",