diff --git a/Makefile b/Makefile index d1d7d902..5c4d437d 100644 --- a/Makefile +++ b/Makefile @@ -43,3 +43,7 @@ oapi-gen: .PHONY: start-observability start-observability: @docker compose up valkyrie-otel-collector jaeger prometheus -d + +.PHONY: odinc +odinc: + @go build -o odinc pkg/odin/client/main.go diff --git a/build/package/nix/odin.nix b/build/package/nix/odin.nix index 4e9cbe1b..22413c79 100644 --- a/build/package/nix/odin.nix +++ b/build/package/nix/odin.nix @@ -12,7 +12,7 @@ buildGoModule rec { pname = "odin"; version = "0.0.1"; - vendorHash = "sha256-g+YA2d4tuAtGazjtNiIyyaWbJfnZXMeHk7e8EDr+uUw="; + vendorHash = "sha256-dofEXl3JiXq0Xqk6bGqW5gSpPfBo2Y5SH7g47Rt9ZDo="; src = ../../..; @@ -33,8 +33,8 @@ buildGoModule rec { meta = with lib; { description = "Odin Server"; - license = licenses.asl20; - maintainers = with maintainers; [ ]; + license = licenses.mit; + maintainers = with maintainers; [ deepak ]; mainProgram = "odin"; }; } \ No newline at end of file diff --git a/build/package/nix/odin_client.nix b/build/package/nix/odin_client.nix new file mode 100644 index 00000000..24411027 --- /dev/null +++ b/build/package/nix/odin_client.nix @@ -0,0 +1,26 @@ +{ lib +, pkgs +, stdenv +, buildGoModule +}: + +buildGoModule rec { + pname = "odinc"; + version = "0.0.1"; + vendorHash = "sha256-dofEXl3JiXq0Xqk6bGqW5gSpPfBo2Y5SH7g47Rt9ZDo="; + + src = ../../..; + + doCheck = false; + + subPackages = [ "pkg/odin/odinc" ]; + + ldflags = [ "-s" "-w" "-X info.version=${version}" ]; + + meta = with lib; { + description = "Odin Client"; + license = licenses.mit; + maintainers = with maintainers; [ deepak ]; + mainProgram = "odinc"; + }; +} \ No newline at end of file diff --git a/flake.nix b/flake.nix index 6437b08b..86268512 100644 --- a/flake.nix +++ b/flake.nix @@ -35,6 +35,7 @@ packages = { odin = pkgs.callPackage ./build/package/nix/odin.nix { inherit pkgs; }; nardump = pkgs.callPackage ./build/package/nix/nardump.nix { inherit pkgs; }; + odinc = pkgs.callPackage ./build/package/nix/odin_client.nix { inherit pkgs; }; }; defaultPackage = packages.odin; diff --git a/internal/odin/config/environment.go b/internal/odin/config/environment.go index 11540c62..2ac3df97 100644 --- a/internal/odin/config/environment.go +++ b/internal/odin/config/environment.go @@ -17,8 +17,9 @@ type EnvConfig struct { POSTGRES_DB string `mapstructure:"POSTGRES_DB"` // represents the name of the PostgreSQL database. POSTGRES_SSL_MODE string `mapstructure:"POSTGRES_SSL_MODE"` // represents the SSL mode for connecting to PostgreSQL. - ODIN_SERVER_HOST string `mapstructure:"ODIN_SERVER_HOST"` // represents the host on which the Odin server will listen. - ODIN_SERVER_PORT string `mapstructure:"ODIN_SERVER_PORT"` // represents the port on which the Odin server will listen. + ODIN_SERVER_HOST string `mapstructure:"ODIN_SERVER_HOST"` // represents the host on which the Odin server will listen. + ODIN_SERVER_PORT string `mapstructure:"ODIN_SERVER_PORT"` // represents the port on which the Odin server will listen. + ODIN_SERVER_BASE_URL string ODIN_WORKER_PROVIDER string `mapstructure:"ODIN_WORKER_PROVIDER"` // represents the worker provider. ODIN_WORKER_CONCURRENCY int32 `mapstructure:"ODIN_WORKER_CONCURRENCY"` // represents the concurrency level for the worker. @@ -103,5 +104,6 @@ func GetEnvConfig() (*EnvConfig, error) { EnvConfig.ODIN_INFO_DIR = fmt.Sprintf("%s/%s", EnvConfig.USER_HOME_DIR, ".odin") EnvConfig.ODIN_WORKER_DIR = fmt.Sprintf("%s/%s", EnvConfig.ODIN_INFO_DIR, "worker") EnvConfig.ODIN_WORKER_INFO_FILE = fmt.Sprintf("%s/%s", EnvConfig.ODIN_WORKER_DIR, "worker-info.json") + EnvConfig.ODIN_SERVER_BASE_URL = fmt.Sprintf("http://%s:%s", EnvConfig.ODIN_SERVER_HOST, EnvConfig.ODIN_SERVER_PORT) return &EnvConfig, nil } diff --git a/internal/odin/server/execution.go b/internal/odin/server/execution.go index 542ba160..e3040a61 100644 --- a/internal/odin/server/execution.go +++ b/internal/odin/server/execution.go @@ -15,15 +15,15 @@ func (s *OdinServer) Execute(ctx context.Context, req *api.ExecutionRequest) (ap switch err.(type) { case *execution.ExecutionServiceError: return &api.ExecuteInternalServerError{ - Message: fmt.Sprintf("Execution Service: %v", err), + Message: fmt.Sprintf("Execution Service -> %v", err), }, nil case *execution.TemplateError: return &api.ExecuteBadRequest{ - Message: fmt.Sprintf("Failed to execute: %v", err), + Message: fmt.Sprintf("Failed to execute -> %v", err), }, nil default: return &api.ExecuteInternalServerError{ - Message: fmt.Sprintf("Failed to execute: %v", err), + Message: fmt.Sprintf("Failed to execute -> %v", err), }, nil } } diff --git a/internal/odin/services/execution/execute.go b/internal/odin/services/execution/execute.go index 941e598c..8b887b9a 100644 --- a/internal/odin/services/execution/execute.go +++ b/internal/odin/services/execution/execute.go @@ -111,6 +111,12 @@ func (s *ExecutionService) convertExecSpecToFlake(execSpec *api.ExecutionRequest } func (s *ExecutionService) AddJob(ctx context.Context, req *api.ExecutionRequest) (int64, error) { + if req.Environment.Value.Type == "" { + return 0, &ExecutionServiceError{ + Type: "environment", + Message: "Specify flake or language", + } + } execReq, err := s.prepareExecutionRequest(req) if err != nil { return 0, err diff --git a/pkg/odin/odinc/cmd/execute.go b/pkg/odin/odinc/cmd/execute.go new file mode 100644 index 00000000..7d09a334 --- /dev/null +++ b/pkg/odin/odinc/cmd/execute.go @@ -0,0 +1,82 @@ +package cmd + +import ( + "fmt" + "os" + + "github.com/spf13/cobra" + + "github.com/deepakdinesh1123/valkyrie/pkg/odin/api" +) + +var executeCmd = &cobra.Command{ + Use: "execute", + Short: "Execute a job", + Long: `Execute a job`, + RunE: executeExec, +} + +func executeExec(cmd *cobra.Command, args []string) error { + baseURL := cmd.Flag("base-url").Value.String() + + client, err := api.NewClient(baseURL) + if err != nil { + return err + } + + var req api.ExecutionRequest + + language := cmd.Flag("language").Value.String() + file := cmd.Flag("file_path").Value.String() + // arguments := cmd.Flag("args").Value.String() + code := cmd.Flag("code").Value.String() + flake := cmd.Flag("flake").Value.String() + // dir := cmd.Flag("dir").Value.String() + + if language != "" { + req.Language = language + req.Environment.Set = true + req.Environment.Value.Type = "ExecutionEnvironmentSpec" + + if code != "" && file != "" { + return fmt.Errorf("language must be specified with either code or file") + } else if code != "" { + req.Code = code + } else if file != "" { + file_content, err := os.ReadFile(file) + if err != nil { + return err + } + req.Code = string(file_content) + } + } else if flake != "" { + req.Environment.Set = true + req.Environment.Value.Type = "Flake" + req.Environment.Value.Flake = api.Flake(flake) + } else { + return fmt.Errorf("environment must be specified with either language or flake not both") + } + res, err := client.Execute(cmd.Context(), &req) + if err != nil { + return err + } + + switch res := res.(type) { + case *api.ExecuteOK: + fmt.Println("Execution ID: ", res.ExecutionId) + case *api.ExecuteBadRequest: + fmt.Println(res.Message) + case *api.ExecuteInternalServerError: + fmt.Println(res.Message) + } + return nil +} + +func init() { + executeCmd.Flags().String("language", "", "Language to execute in") + executeCmd.Flags().String("file_path", "", "File to execute") + executeCmd.Flags().String("args", "", "Arguments to pass to the script") + executeCmd.Flags().String("code", "", "Code to execute") + executeCmd.Flags().String("flake", "", "Flake for the environment") + executeCmd.Flags().String("dir", ".", "Path to the directory that contains the flake and script") +} diff --git a/pkg/odin/odinc/cmd/executions.go b/pkg/odin/odinc/cmd/executions.go new file mode 100644 index 00000000..cf28debb --- /dev/null +++ b/pkg/odin/odinc/cmd/executions.go @@ -0,0 +1,188 @@ +package cmd + +import ( + "fmt" + "strconv" + + "github.com/deepakdinesh1123/valkyrie/pkg/odin/api" + "github.com/spf13/cobra" +) + +var ( + page int32 + pageSz int32 +) + +var executionsCmd = &cobra.Command{ + Use: "executions", + Short: "Manage executions", + Long: `Manage executions`, + RunE: executionsExec, +} + +func executionsExec(cmd *cobra.Command, args []string) error { + _ = cmd.Usage() + return nil +} + +func init() { + executionsCmd.AddCommand(executionsListCmd) + executionsCmd.AddCommand(executionsResultsCmd) + executionsCmd.AddCommand(execitionResultByIdCmd) + executionsCmd.AddCommand(executionConfig) + executionsCmd.AddCommand(deleteExecutionCmd) + + executionsCmd.PersistentFlags().Int32VarP(&page, "page", "p", 0, "Page number") + executionsCmd.PersistentFlags().Int32VarP(&pageSz, "page-size", "s", 10, "Page size") +} + +var executionsListCmd = &cobra.Command{ + Use: "list", + Short: "List executions", + Long: `List executions`, + RunE: executionsListExec, +} + +func executionsListExec(cmd *cobra.Command, args []string) error { + baseURL := cmd.Flag("base-url").Value.String() + client, err := api.NewClient(baseURL) + if err != nil { + return err + } + res, err := client.GetAllExecutions(cmd.Context(), api.GetAllExecutionsParams{ + Page: api.NewOptInt32(page), + PageSize: api.NewOptInt32(pageSz), + }) + if err != nil { + return err + } + switch res := res.(type) { + case *api.GetAllExecutionsOK: + fmt.Println(res.Executions) + case *api.GetAllExecutionsBadRequest: + fmt.Println(res.Message) + } + return nil +} + +var executionsResultsCmd = &cobra.Command{ + Use: "results", + Short: "List execution results", + Long: `List execution results`, + RunE: executionsResultsExec, +} + +func executionsResultsExec(cmd *cobra.Command, args []string) error { + baseURL := cmd.Flag("base-url").Value.String() + client, err := api.NewClient(baseURL) + if err != nil { + return err + } + res, err := client.GetAllExecutionResults(cmd.Context(), api.GetAllExecutionResultsParams{ + Page: api.NewOptInt32(page), + PageSize: api.NewOptInt32(pageSz), + }) + if err != nil { + return err + } + switch res := res.(type) { + case *api.GetAllExecutionResultsOK: + fmt.Println(res.Executions) + case *api.GetAllExecutionResultsBadRequest: + fmt.Println(res.Message) + } + return nil +} + +var execitionResultByIdCmd = &cobra.Command{ + Use: "get", + Short: "Get execution result by id", + Long: `Get execution result by id`, + RunE: execitionResultByIdExec, +} + +func execitionResultByIdExec(cmd *cobra.Command, args []string) error { + baseURL := cmd.Flag("base-url").Value.String() + if len(args) == 0 { + return fmt.Errorf("id is required") + } + jobId, err := strconv.ParseInt(args[0], 10, 32) + if err != nil { + return err + } + client, err := api.NewClient(baseURL) + if err != nil { + return err + } + res, err := client.GetExecutionResultsById(cmd.Context(), api.GetExecutionResultsByIdParams{ + JobId: jobId, + Page: api.NewOptInt32(page), + PageSize: api.NewOptInt32(pageSz), + }) + if err != nil { + return err + } + switch res := res.(type) { + case *api.GetExecutionResultsByIdOK: + fmt.Println(res.Executions) + case *api.GetExecutionResultsByIdBadRequest: + fmt.Println(res.Message) + } + return nil +} + +var executionConfig = &cobra.Command{ + Use: "config", + Short: "Get execution config", + Long: `Get execution config`, + RunE: executionConfigExec, +} + +func executionConfigExec(cmd *cobra.Command, args []string) error { + baseURL := cmd.Flag("base-url").Value.String() + client, err := api.NewClient(baseURL) + if err != nil { + return err + } + res, err := client.GetExecutionConfig(cmd.Context()) + if err != nil { + return err + } + fmt.Println(res) + return nil +} + +var deleteExecutionCmd = &cobra.Command{ + Use: "delete", + Short: "Delete execution", + Long: `Delete execution`, + RunE: deleteExecutionExec, +} + +func deleteExecutionExec(cmd *cobra.Command, args []string) error { + baseURL := cmd.Flag("base-url").Value.String() + if len(args) == 0 { + return fmt.Errorf("id is required") + } + jobId, err := strconv.ParseInt(args[0], 10, 32) + if err != nil { + return err + } + client, err := api.NewClient(baseURL) + if err != nil { + return err + } + res, err := client.DeleteJob(cmd.Context(), api.DeleteJobParams{ + JobId: jobId, + }) + if err != nil { + return err + } + switch res := res.(type) { + case *api.DeleteJobOK: + fmt.Println(res) + case *api.DeleteJobBadRequest: + fmt.Println(res.Message) + } + return nil +} diff --git a/pkg/odin/odinc/cmd/root.go b/pkg/odin/odinc/cmd/root.go new file mode 100644 index 00000000..f6bee369 --- /dev/null +++ b/pkg/odin/odinc/cmd/root.go @@ -0,0 +1,28 @@ +package cmd + +import ( + "github.com/spf13/cobra" +) + +var RootCmd = &cobra.Command{ + Use: "odinc", + Short: "ODIN client", + Long: `ODIN client`, + RunE: func(cmd *cobra.Command, args []string) error { + _ = cmd.Usage() + return nil + }, +} + +func Execute() { + RootCmd.Execute() +} + +func init() { + RootCmd.AddCommand(executeCmd) + RootCmd.AddCommand(executionsCmd) + RootCmd.AddCommand(versionCmd) + RootCmd.AddCommand(executionWorkersCmd) + + RootCmd.PersistentFlags().String("base-url", "http://localhost:8080", "Base URL") +} diff --git a/pkg/odin/odinc/cmd/version.go b/pkg/odin/odinc/cmd/version.go new file mode 100644 index 00000000..d3f32c52 --- /dev/null +++ b/pkg/odin/odinc/cmd/version.go @@ -0,0 +1,32 @@ +package cmd + +import ( + "fmt" + + "github.com/deepakdinesh1123/valkyrie/pkg/odin/api" + "github.com/spf13/cobra" +) + +var versionCmd = &cobra.Command{ + Use: "version", + Short: "Get version", + Long: `Get version`, + RunE: versionExec, +} + +func versionExec(cmd *cobra.Command, args []string) error { + baseURL := cmd.Flag("base-url").Value.String() + client, err := api.NewClient(baseURL) + if err != nil { + return err + } + res, err := client.GetVersion(cmd.Context()) + if err != nil { + return err + } + switch res := res.(type) { + case *api.GetVersionOK: + fmt.Println(res.Version) + } + return nil +} diff --git a/pkg/odin/odinc/cmd/worker.go b/pkg/odin/odinc/cmd/worker.go new file mode 100644 index 00000000..adb6011a --- /dev/null +++ b/pkg/odin/odinc/cmd/worker.go @@ -0,0 +1,40 @@ +package cmd + +import ( + "fmt" + + "github.com/deepakdinesh1123/valkyrie/pkg/odin/api" + "github.com/spf13/cobra" +) + +var executionWorkersCmd = &cobra.Command{ + Use: "workers", + Short: "Manage execution workers", + Long: `Manage execution workers`, + RunE: executionWorkersExec, +} + +func executionWorkersExec(cmd *cobra.Command, args []string) error { + baseURL := cmd.Flag("base-url").Value.String() + client, err := api.NewClient(baseURL) + if err != nil { + return err + } + res, err := client.GetExecutionWorkers(cmd.Context(), api.GetExecutionWorkersParams{ + Page: api.NewOptInt32(page), + PageSize: api.NewOptInt32(pageSz), + }) + if err != nil { + return err + } + switch res := res.(type) { + case *api.GetExecutionWorkersOK: + fmt.Println(res.Workers) + } + return nil +} + +func init() { + executionWorkersCmd.PersistentFlags().Int32VarP(&page, "page", "p", 0, "Page number") + executionWorkersCmd.PersistentFlags().Int32VarP(&pageSz, "page-size", "s", 10, "Page size") +} diff --git a/pkg/odin/odinc/main.go b/pkg/odin/odinc/main.go new file mode 100644 index 00000000..bebf7f22 --- /dev/null +++ b/pkg/odin/odinc/main.go @@ -0,0 +1,9 @@ +package main + +import ( + "github.com/deepakdinesh1123/valkyrie/pkg/odin/odinc/cmd" +) + +func main() { + cmd.Execute() +}