diff --git a/cmd/generate.go b/cmd/generate.go new file mode 100644 index 0000000..87f1ed2 --- /dev/null +++ b/cmd/generate.go @@ -0,0 +1,161 @@ +package cmd + +import ( + "bytes" + "encoding/json" + "fmt" + "io" + "net/http" + "os" + "pb/pkg/common" + internalHTTP "pb/pkg/http" + + "github.com/spf13/cobra" +) + +const ( + generateStaticSchemaPath = "/logstream/schema/detect" +) + +var GenerateSchemaCmd = &cobra.Command{ + Use: "generate", + Short: "Generate Schema for JSON", + Example: "pb schema generate --file=test.json", + RunE: func(cmd *cobra.Command, _ []string) error { + // Get the file path from the `--file` flag + filePath, err := cmd.Flags().GetString("file") + if err != nil { + return fmt.Errorf(common.Red+"failed to read file flag: %w"+common.Reset, err) + } + + if filePath == "" { + return fmt.Errorf(common.Red + "file flag is required" + common.Reset) + } + + // Read the file content + fileContent, err := os.ReadFile(filePath) + if err != nil { + return fmt.Errorf(common.Red+"failed to read file %s: %w"+common.Reset, filePath, err) + } + + // Initialize HTTP client + client := internalHTTP.DefaultClient(&DefaultProfile) + + // Create the HTTP request + req, err := client.NewRequest(http.MethodPost, generateStaticSchemaPath, bytes.NewBuffer(fileContent)) + if err != nil { + return fmt.Errorf(common.Red+"failed to create new request: %w"+common.Reset, err) + } + + // Set Content-Type header + req.Header.Set("Content-Type", "application/json") + + // Execute the request + resp, err := client.Client.Do(req) + if err != nil { + return fmt.Errorf(common.Red+"request execution failed: %w"+common.Reset, err) + } + defer resp.Body.Close() + + // Check for non-200 status codes + if resp.StatusCode != http.StatusOK { + body, _ := io.ReadAll(resp.Body) + fmt.Printf(common.Red+"Error response: %s\n"+common.Reset, string(body)) + return fmt.Errorf(common.Red+"non-200 status code received: %s"+common.Reset, resp.Status) + } + + // Parse and print the response + respBody, err := io.ReadAll(resp.Body) + if err != nil { + return fmt.Errorf(common.Red+"failed to read response body: %w"+common.Reset, err) + } + + var prettyJSON bytes.Buffer + if err := json.Indent(&prettyJSON, respBody, "", " "); err != nil { + return fmt.Errorf(common.Red+"failed to format response as JSON: %w"+common.Reset, err) + } + + fmt.Println(common.Green + prettyJSON.String() + common.Reset) + return nil + }, +} + +var CreateSchemaCmd = &cobra.Command{ + Use: "create", + Short: "Create Schema for a Parseable stream", + Example: "pb schema create --stream=my_stream --file=schema.json", + RunE: func(cmd *cobra.Command, _ []string) error { + // Get the stream name from the `--stream` flag + streamName, err := cmd.Flags().GetString("stream") + if err != nil { + return fmt.Errorf(common.Red+"failed to read stream flag: %w"+common.Reset, err) + } + + if streamName == "" { + return fmt.Errorf(common.Red + "stream flag is required" + common.Reset) + } + + // Get the file path from the `--file` flag + filePath, err := cmd.Flags().GetString("file") + if err != nil { + return fmt.Errorf(common.Red+"failed to read config flag: %w"+common.Reset, err) + } + + if filePath == "" { + return fmt.Errorf(common.Red + "file path flag is required" + common.Reset) + } + + // Read the JSON schema file + schemaContent, err := os.ReadFile(filePath) + if err != nil { + return fmt.Errorf(common.Red+"failed to read schema file %s: %w"+common.Reset, filePath, err) + } + + // Initialize HTTP client + client := internalHTTP.DefaultClient(&DefaultProfile) + + // Construct the API path + apiPath := fmt.Sprintf("/logstream/%s", streamName) + + // Create the HTTP PUT request + req, err := client.NewRequest(http.MethodPut, apiPath, bytes.NewBuffer(schemaContent)) + if err != nil { + return fmt.Errorf(common.Red+"failed to create new request: %w"+common.Reset, err) + } + + // Set custom headers + req.Header.Set("Content-Type", "application/json") + req.Header.Set("X-P-Static-Schema-Flag", "true") + + // Execute the request + resp, err := client.Client.Do(req) + if err != nil { + return fmt.Errorf(common.Red+"request execution failed: %w"+common.Reset, err) + } + defer resp.Body.Close() + + // Check for non-200 status codes + if resp.StatusCode != http.StatusOK { + body, _ := io.ReadAll(resp.Body) + fmt.Printf(common.Red+"Error response: %s\n"+common.Reset, string(body)) + return fmt.Errorf(common.Red+"non-200 status code received: %s"+common.Reset, resp.Status) + } + + // Parse and print the response + respBody, err := io.ReadAll(resp.Body) + if err != nil { + return fmt.Errorf(common.Red+"failed to read response body: %w"+common.Reset, err) + } + + fmt.Println(common.Green + string(respBody) + common.Reset) + return nil + }, +} + +func init() { + // Add the `--file` flag to the command + GenerateSchemaCmd.Flags().StringP("file", "f", "", "Path to the JSON file to generate schema") + CreateSchemaCmd.Flags().StringP("stream", "s", "", "Name of the stream to associate with the schema") + CreateSchemaCmd.Flags().StringP("file", "f", "", "Path to the JSON file to create schema") + +} diff --git a/main.go b/main.go index c6c528d..6ef86c4 100644 --- a/main.go +++ b/main.go @@ -92,6 +92,32 @@ var profile = &cobra.Command{ }, } +var schema = &cobra.Command{ + Use: "schema", + Short: "Generate or create schemas for JSON data or Parseable streams", + Long: `The "schema" command allows you to either: + - Generate a schema automatically from a JSON file for analysis or integration. + - Create a custom schema for Parseable streams (PB streams) to structure and process your data. + +Examples: + - To generate a schema from a JSON file: + pb schema generate --file=data.json + - To create a schema for a PB stream: + pb schema create --stream-name=my_stream --config=data.json +`, + PersistentPreRunE: combinedPreRun, + PersistentPostRun: func(cmd *cobra.Command, args []string) { + if os.Getenv("PB_ANALYTICS") == "disable" { + return + } + wg.Add(1) + go func() { + defer wg.Done() + analytics.PostRunAnalytics(cmd, "generate", args) + }() + }, +} + var user = &cobra.Command{ Use: "user", Short: "Manage users", @@ -200,6 +226,9 @@ func main() { query.AddCommand(pb.QueryCmd) query.AddCommand(pb.SavedQueryList) + schema.AddCommand(pb.GenerateSchemaCmd) + schema.AddCommand(pb.CreateSchemaCmd) + install.AddCommand(pb.InstallOssCmd) cli.AddCommand(profile) @@ -211,6 +240,7 @@ func main() { cli.AddCommand(pb.AutocompleteCmd) cli.AddCommand(install) + cli.AddCommand(schema) // Set as command pb.VersionCmd.Run = func(_ *cobra.Command, _ []string) {