Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
92 changes: 12 additions & 80 deletions cmd/cloud.go
Original file line number Diff line number Diff line change
@@ -1,12 +1,9 @@
package cmd

import (
"bytes"
"encoding/json"
"fmt"
"io"
"net/http"
"net/url"
"os"
"path/filepath"
"strconv"
Expand Down Expand Up @@ -82,58 +79,20 @@ var cloudDeployCmd = &cobra.Command{
appUrl := viper.GetString("overrides.app_url")
token := viper.GetString("auth.api_key")

u, err := url.Parse(apiUrl)
if err != nil {
logger.Fatal("error parsing api url: %s. %s", apiUrl, err)
}
u.Path = fmt.Sprintf("/cli/project/%s", project.ProjectId)
req, err := http.NewRequest("GET", u.String(), nil)
if err != nil {
logger.Fatal("error creating project request: %s", err)
}
req.Header.Set("Authorization", "Bearer "+token)
resp, err := http.DefaultClient.Do(req)
if err != nil {
logger.Fatal("error requesting project: %s (%s)", err, u.String())
}
defer resp.Body.Close()
if resp.StatusCode != http.StatusOK {
buf, _ := io.ReadAll(resp.Body)
logger.Fatal("unexpected error requesting project (%s) %s", resp.Status, string(buf))
}
enc := json.NewDecoder(resp.Body)
client := util.NewAPIClient(apiUrl, token)

// Get project details
var projectResponse projectResponse
if err := enc.Decode(&projectResponse); err != nil {
logger.Fatal("error decoding project response json: %s", err)
if err := client.Do("GET", fmt.Sprintf("/cli/project/%s", project.ProjectId), nil, &projectResponse); err != nil {
logger.Fatal("error requesting project: %s", err)
}
orgId := projectResponse.Data.OrgId

// start the deployment request to get a one-time upload url
u.Path = fmt.Sprintf("/cli/deploy/start/%s/%s", orgId, project.ProjectId)
req, err = http.NewRequest("PUT", u.String(), nil)
if err != nil {
logger.Fatal("error creating url route: %s", err)
}
req.Header.Set("Authorization", "Bearer "+token)
resp, err = http.DefaultClient.Do(req)
if err != nil {
logger.Fatal("error creating start request for upload: %s", err)
}
defer resp.Body.Close()
if resp.StatusCode != http.StatusAccepted {
logger.Fatal("unexpected error uploading (%s)", resp.Status)
}
enc = json.NewDecoder(resp.Body)
// Start deployment
var startResponse startResponse
if err := enc.Decode(&startResponse); err != nil {
logger.Fatal("error decoding start response json: %s", err)
}
resp.Body.Close()
if !startResponse.Success {
logger.Fatal("error generating start authentication: %s", startResponse.Message)
if err := client.Do("PUT", fmt.Sprintf("/cli/deploy/start/%s/%s", orgId, project.ProjectId), nil, &startResponse); err != nil {
logger.Fatal("error starting deployment: %s", err)
}
logger.Debug("upload api is %s", startResponse.Data.Url)
logger.Debug("deployment id is %s", startResponse.Data.DeploymentId)

// load up any gitignore files
gitignore := filepath.Join(dir, ignore.Ignore)
Expand Down Expand Up @@ -188,21 +147,20 @@ var cloudDeployCmd = &cobra.Command{
started = time.Now()

// send the zip file to the upload endpoint provided
req, err = http.NewRequest("PUT", startResponse.Data.Url, of)
req, err := http.NewRequest("PUT", startResponse.Data.Url, of)
if err != nil {
logger.Fatal("error creating PUT request", err)
}
req.ContentLength = fi.Size()
req.Header.Set("Content-Type", "application/zip")
req.Header.Set("Content-Length", strconv.FormatInt(fi.Size(), 10))

resp, err = http.DefaultClient.Do(req)
resp, err := http.DefaultClient.Do(req)
if err != nil {
if err := updateDeploymentStatus(apiUrl, token, startResponse.Data.DeploymentId, "failed"); err != nil {
logger.Fatal("%s", err)
}
logger.Fatal("error uploading deployment: %s", err)

}
if resp.StatusCode != http.StatusOK {
buf, _ := io.ReadAll(resp.Body)
Expand All @@ -224,35 +182,9 @@ var cloudDeployCmd = &cobra.Command{
}

func updateDeploymentStatus(apiUrl, token, deploymentId, status string) error {
u, err := url.Parse(apiUrl)
if err != nil {
return fmt.Errorf("error parsing api url: %s", err)
}
u.Path = fmt.Sprintf("/cli/deploy/upload/%s", deploymentId)

client := util.NewAPIClient(apiUrl, token)
payload := map[string]string{"state": status}
body, err := json.Marshal(payload)
if err != nil {
return fmt.Errorf("error marshalling payload: %s", err)
}

req, err := http.NewRequest("PUT", u.String(), bytes.NewBuffer(body))
if err != nil {
return fmt.Errorf("error creating status update request: %s", err)
}
req.Header.Set("Content-Type", "application/json")
req.Header.Set("Authorization", "Bearer "+token)

resp, err := http.DefaultClient.Do(req)
if err != nil {
return fmt.Errorf("error sending status update request: %s", err)
}
defer resp.Body.Close()

if resp.StatusCode != http.StatusAccepted {
return fmt.Errorf("error updating deployment status (%s)", resp.Status)
}
return nil
return client.Do("PUT", fmt.Sprintf("/cli/deploy/upload/%s", deploymentId), payload, nil)
}

func init() {
Expand Down
102 changes: 102 additions & 0 deletions cmd/env.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
package cmd

import (
"fmt"

"github.com/agentuity/cli/internal/project"
"github.com/agentuity/go-common/env"
"github.com/spf13/cobra"
"github.com/spf13/viper"
)

var envCmd = &cobra.Command{
Use: "env",
Short: "Environment related commands",
Run: func(cmd *cobra.Command, args []string) {
cmd.Help()
},
}

var envSetCmd = &cobra.Command{
Use: "set [key] [value]",
Short: "Set environment variables",
Args: cobra.MinimumNArgs(2),
Run: func(cmd *cobra.Command, args []string) {
logger := env.NewLogger(cmd)
dir := resolveProjectDir(logger, cmd)
apiUrl := viper.GetString("overrides.api_url")
apiKey := viper.GetString("auth.api_key")
if apiKey == "" {
logger.Fatal("you are not logged in")
}
project := project.NewProject()
if err := project.Load(dir); err != nil {
logger.Fatal("failed to load project: %s", err)
}
_, err := project.SetProjectEnv(logger, apiUrl, apiKey, map[string]interface{}{args[0]: args[1]})
if err != nil {
logger.Fatal("failed to set project env: %s", err)
}
printSuccess(fmt.Sprintf("Environment variable %s set successfully", args[0]))
},
}

var envGetCmd = &cobra.Command{
Use: "get [key]",
Short: "Get environment variables",
Args: cobra.MinimumNArgs(1),
Run: func(cmd *cobra.Command, args []string) {
logger := env.NewLogger(cmd)
dir := resolveProjectDir(logger, cmd)
apiUrl := viper.GetString("overrides.api_url")
apiKey := viper.GetString("auth.api_key")
if apiKey == "" {
logger.Fatal("you are not logged in")
}
project := project.NewProject()
if err := project.Load(dir); err != nil {
logger.Fatal("failed to load project: %s", err)
}
projectData, err := project.ListProjectEnv(logger, apiUrl, apiKey)
if err != nil {
logger.Fatal("failed to list project env: %s", err)
}
for key, value := range projectData.Env {
if key == args[0] {
fmt.Printf("%s=%s\n", key, value)
}
}
},
}

var envListCmd = &cobra.Command{
Use: "list",
Short: "List all environment variables",
Run: func(cmd *cobra.Command, args []string) {
logger := env.NewLogger(cmd)
dir := resolveProjectDir(logger, cmd)
apiUrl := viper.GetString("overrides.api_url")
apiKey := viper.GetString("auth.api_key")
if apiKey == "" {
logger.Fatal("you are not logged in")
}
project := project.NewProject()
if err := project.Load(dir); err != nil {
logger.Fatal("failed to load project: %s", err)
}
projectData, err := project.ListProjectEnv(logger, apiUrl, apiKey)
if err != nil {
logger.Fatal("failed to list project env: %s", err)
}
for key, value := range projectData.Env {
fmt.Printf("%s=%s\n", key, value)
}
},
}

func init() {
rootCmd.AddCommand(envCmd)
envCmd.AddCommand(envSetCmd)
envCmd.AddCommand(envListCmd)
envCmd.AddCommand(envGetCmd)
}
35 changes: 33 additions & 2 deletions internal/project/project.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,9 @@ import (
"os"
"path/filepath"

"github.com/agentuity/cli/internal/util"
"github.com/agentuity/go-common/logger"

"gopkg.in/yaml.v3"
"k8s.io/apimachinery/pkg/api/resource"
)
Expand All @@ -24,8 +26,10 @@ type initProjectResult struct {
}

type ProjectData struct {
APIKey string `json:"api_key"`
ProjectId string `json:"id"`
APIKey string `json:"api_key"`
ProjectId string `json:"id"`
Env map[string]interface{} `json:"env"`
Secrets map[string]interface{} `json:"secrets"`
}

// InitProject will create a new project in the organization.
Expand Down Expand Up @@ -152,6 +156,33 @@ func NewProject() *Project {
return &Project{}
}

type ProjectResponse struct {
Success bool `json:"success"`
Message string `json:"message"`
Data ProjectData `json:"data"`
}

func (p *Project) ListProjectEnv(logger logger.Logger, baseUrl string, token string) (*ProjectData, error) {
client := util.NewAPIClient(baseUrl, token)

var projectResponse ProjectResponse
if err := client.Do("GET", fmt.Sprintf("/cli/project/%s", p.ProjectId), nil, &projectResponse); err != nil {
logger.Fatal("error getting project env: %s", err)
}
return &projectResponse.Data, nil
}

func (p *Project) SetProjectEnv(logger logger.Logger, baseUrl string, token string, env map[string]interface{}) (*ProjectData, error) {
client := util.NewAPIClient(baseUrl, token)
var projectResponse ProjectResponse
if err := client.Do("PUT", fmt.Sprintf("/cli/project/%s/env", p.ProjectId), map[string]interface{}{
"env": env,
}, &projectResponse); err != nil {
logger.Fatal("error setting project env: %s", err)
}
return &projectResponse.Data, nil
}

type DeploymentConfig struct {
Provider string `yaml:"provider"`
Language string `yaml:"language"`
Expand Down
63 changes: 63 additions & 0 deletions internal/util/api.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
package util

import (
"bytes"
"encoding/json"
"fmt"
"net/http"
"net/url"
)

type APIClient struct {
baseURL string
token string
client *http.Client
}

func NewAPIClient(baseURL, token string) *APIClient {
return &APIClient{
baseURL: baseURL,
token: token,
client: http.DefaultClient,
}
}

func (c *APIClient) Do(method, path string, payload interface{}, response interface{}) error {
u, err := url.Parse(c.baseURL)
if err != nil {
return fmt.Errorf("error parsing base url: %w", err)
}
u.Path = path

var body []byte
if payload != nil {
body, err = json.Marshal(payload)
if err != nil {
return fmt.Errorf("error marshalling payload: %w", err)
}
}

req, err := http.NewRequest(method, u.String(), bytes.NewBuffer(body))
if err != nil {
return fmt.Errorf("error creating request: %w", err)
}
req.Header.Set("Content-Type", "application/json")
req.Header.Set("Authorization", "Bearer "+c.token)

resp, err := c.client.Do(req)
if err != nil {
return fmt.Errorf("error sending request: %w", err)
}
defer resp.Body.Close()

if resp.StatusCode != http.StatusOK && resp.StatusCode != http.StatusAccepted {
return fmt.Errorf("request failed with status (%s)", resp.Status)
}

if response != nil {
if err := json.NewDecoder(resp.Body).Decode(response); err != nil {
return fmt.Errorf("error decoding response: %w", err)
}
}
return nil
}