From 25e5e8f21e94c1e5c208e570dbd57ff369bfa098 Mon Sep 17 00:00:00 2001 From: Mark Spicer Date: Mon, 21 Aug 2023 15:31:47 -0400 Subject: [PATCH] feat: Support private app creation. (#870) This commit updates the create command to be able to support the creation of private apps. This is only available for Tidbyt For Teams customers at the moment and for the forseeable future. --- cmd/create.go | 93 ++++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 92 insertions(+), 1 deletion(-) diff --git a/cmd/create.go b/cmd/create.go index f66c9f9c61..2b900ef222 100644 --- a/cmd/create.go +++ b/cmd/create.go @@ -3,9 +3,14 @@ package cmd import ( + "bytes" + "encoding/json" "fmt" + "io" + "net/http" "os" "path/filepath" + "time" "github.com/spf13/cobra" "tidbyt.dev/pixlet/cmd/community" @@ -13,11 +18,29 @@ import ( "tidbyt.dev/pixlet/tools/repo" ) +var createPrivate bool +var createOrg string +var createURL string + +type TidbytCreateAppRequest struct { + OrganizationID string `json:"organizationID"` +} + +type TidbytCreateAppReply struct { + AppID string `json:"appID"` +} + +func init() { + CreateCmd.Flags().StringVarP(&createOrg, "org", "o", "", "organization to create the app in") + CreateCmd.Flags().BoolVarP(&createPrivate, "private", "p", false, "create a private app") + CreateCmd.Flags().StringVarP(&createURL, "url", "u", "https://api.tidbyt.com", "base URL of the remote bundle store") +} + // CreateCmd prompts the user for info and generates a new app. var CreateCmd = &cobra.Command{ Use: "create", Short: "Creates a new app", - Long: `This command will prompt for all of the information we need to generate a new Tidbyt app.`, + Long: `This command will prompt for all of the information we need to generate a new Tidbyt app. No flags are necessary unless you are creating a private app, which is only available with our Tidbyt For Teams offering.`, RunE: func(cmd *cobra.Command, args []string) error { // Get the current working directory. cwd, err := os.Getwd() @@ -51,6 +74,23 @@ var CreateCmd = &cobra.Command{ return fmt.Errorf("app creation, couldn't get user input: %w", err) } + if createPrivate { + // create a private app + apiToken := oauthTokenFromConfig(cmd.Context()) + if apiToken == "" { + return fmt.Errorf("login with `pixlet login` or use `pixlet set-auth` to configure auth") + } + + if createOrg == "" { + return fmt.Errorf("organization must not be blank") + } + + app.ID, err = createPrivateApp(apiToken, createOrg) + if err != nil { + return fmt.Errorf("remote app creation failed: %w", err) + } + } + // Generate app. g, err := generator.NewGenerator(appType, root) if err != nil { @@ -78,6 +118,57 @@ var CreateCmd = &cobra.Command{ fmt.Println("") fmt.Println("For docs, head to:") fmt.Printf("\thttps://tidbyt.dev\n") + + if createPrivate { + fmt.Println("") + fmt.Println("To deploy your app:") + fmt.Printf("\tpixlet bundle ./\n") + fmt.Printf("\tpixlet upload bundle.tar.gz --app %s --version v0.0.1\n", app.ID) + fmt.Printf("\tpixlet deploy --app %s --version v0.0.1\n", app.ID) + } return nil }, } + +func createPrivateApp(apiToken string, org string) (string, error) { + createAppRequest := &TidbytCreateAppRequest{ + OrganizationID: org, + } + + b, err := json.Marshal(createAppRequest) + if err != nil { + return "", fmt.Errorf("could not create http request: %w", err) + } + + requestURL := fmt.Sprintf("%s/v0/apps", createURL) + req, err := http.NewRequest(http.MethodPost, requestURL, bytes.NewBuffer(b)) + if err != nil { + return "", fmt.Errorf("could not create http request: %w", err) + } + + req.Header.Set("Content-Type", "application/json") + req.Header.Add("Authorization", fmt.Sprintf("Bearer %s", apiToken)) + + client := http.Client{ + Timeout: 30 * time.Second, + } + + resp, err := client.Do(req) + if err != nil { + return "", fmt.Errorf("could not make HTTP request to %s: %w", requestURL, err) + } + defer resp.Body.Close() + + if resp.StatusCode != 200 { + body, _ := io.ReadAll(resp.Body) + return "", fmt.Errorf("request returned status %d with message: %s", resp.StatusCode, body) + } + + createAppReply := &TidbytCreateAppReply{} + err = json.NewDecoder(resp.Body).Decode(&createAppReply) + if err != nil { + return "", fmt.Errorf("could not decode response: %w", err) + } + + return createAppReply.AppID, nil +}