Explorar o código

chart list + cli commands

Alexander Belanger %!s(int64=5) %!d(string=hai) anos
pai
achega
88223eeb6b

+ 123 - 0
cli/cmd/api/helm_repo.go

@@ -0,0 +1,123 @@
+package api
+
+import (
+	"context"
+	"encoding/json"
+	"fmt"
+	"net/http"
+	"strings"
+
+	"github.com/porter-dev/porter/internal/models"
+)
+
+// CreateHelmRepoRequest represents the accepted fields for creating
+// a Helm repository with basic authentication
+type CreateHelmRepoRequest struct {
+	Name               string `json:"name"`
+	RepoURL            string `json:"repo_url"`
+	BasicIntegrationID uint   `json:"basic_integration_id"`
+}
+
+// CreateHelmRepoResponse is the resulting helm repo after creation
+type CreateHelmRepoResponse models.HelmRepoExternal
+
+// CreateHelmRepo creates an Helm repository integration with basic authentication
+func (c *Client) CreateHelmRepo(
+	ctx context.Context,
+	projectID uint,
+	createHR *CreateHelmRepoRequest,
+) (*CreateHelmRepoResponse, error) {
+	data, err := json.Marshal(createHR)
+
+	if err != nil {
+		return nil, err
+	}
+
+	req, err := http.NewRequest(
+		"POST",
+		fmt.Sprintf("%s/projects/%d/helmrepos", c.BaseURL, projectID),
+		strings.NewReader(string(data)),
+	)
+
+	if err != nil {
+		return nil, err
+	}
+
+	req = req.WithContext(ctx)
+	bodyResp := &CreateHelmRepoResponse{}
+
+	if httpErr, err := c.sendRequest(req, bodyResp, true); httpErr != nil || err != nil {
+		if httpErr != nil {
+			return nil, fmt.Errorf("code %d, errors %v", httpErr.Code, httpErr.Errors)
+		}
+
+		return nil, err
+	}
+
+	return bodyResp, nil
+}
+
+// ListHelmRepoResponse is the list of Helm repos for a project
+type ListHelmRepoResponse []models.HelmRepoExternal
+
+// ListHelmRepos returns a list of Helm repos for a project
+func (c *Client) ListHelmRepos(
+	ctx context.Context,
+	projectID uint,
+) (ListHelmRepoResponse, error) {
+	req, err := http.NewRequest(
+		"GET",
+		fmt.Sprintf("%s/projects/%d/helmrepos", c.BaseURL, projectID),
+		nil,
+	)
+
+	if err != nil {
+		return nil, err
+	}
+
+	req = req.WithContext(ctx)
+	bodyResp := &ListHelmRepoResponse{}
+
+	if httpErr, err := c.sendRequest(req, bodyResp, true); httpErr != nil || err != nil {
+		if httpErr != nil {
+			return nil, fmt.Errorf("code %d, errors %v", httpErr.Code, httpErr.Errors)
+		}
+
+		return nil, err
+	}
+
+	return *bodyResp, nil
+}
+
+// ListChartsResponse is the list of charts in a Helm repository
+type ListChartsResponse []models.PorterChartList
+
+// ListCharts lists the charts in a Helm repository
+func (c *Client) ListCharts(
+	ctx context.Context,
+	projectID uint,
+	helmID uint,
+) (ListChartsResponse, error) {
+	req, err := http.NewRequest(
+		"GET",
+		fmt.Sprintf("%s/projects/%d/helmrepos/%d/charts", c.BaseURL, projectID, helmID),
+		nil,
+	)
+
+	if err != nil {
+		return nil, err
+	}
+
+	req = req.WithContext(ctx)
+	bodyResp := &ListChartsResponse{}
+
+	if httpErr, err := c.sendRequest(req, bodyResp, true); httpErr != nil || err != nil {
+		if httpErr != nil {
+			return nil, fmt.Errorf("code %d, errors %v", httpErr.Code, httpErr.Errors)
+		}
+
+		return nil, err
+	}
+
+	return *bodyResp, nil
+}

+ 46 - 0
cli/cmd/api/integration.go

@@ -101,3 +101,49 @@ func (c *Client) CreateGCPIntegration(
 
 	return bodyResp, nil
 }
+
+// CreateBasicAuthIntegrationRequest represents the accepted fields for creating
+// a "basic auth" integration
+type CreateBasicAuthIntegrationRequest struct {
+	Username string `json:"username"`
+	Password string `json:"password"`
+}
+
+// CreateBasicAuthIntegrationResponse is the resulting integration after creation
+type CreateBasicAuthIntegrationResponse ints.BasicIntegrationExternal
+
+// CreateBasicAuthIntegration creates a "basic auth" integration
+func (c *Client) CreateBasicAuthIntegration(
+	ctx context.Context,
+	projectID uint,
+	createBasic *CreateBasicAuthIntegrationRequest,
+) (*CreateBasicAuthIntegrationResponse, error) {
+	data, err := json.Marshal(createBasic)
+
+	if err != nil {
+		return nil, err
+	}
+
+	req, err := http.NewRequest(
+		"POST",
+		fmt.Sprintf("%s/projects/%d/integrations/basic", c.BaseURL, projectID),
+		strings.NewReader(string(data)),
+	)
+
+	if err != nil {
+		return nil, err
+	}
+
+	req = req.WithContext(ctx)
+	bodyResp := &CreateBasicAuthIntegrationResponse{}
+
+	if httpErr, err := c.sendRequest(req, bodyResp, true); httpErr != nil || err != nil {
+		if httpErr != nil {
+			return nil, fmt.Errorf("code %d, errors %v", httpErr.Code, httpErr.Errors)
+		}
+
+		return nil, err
+	}
+
+	return bodyResp, nil
+}

+ 37 - 0
cli/cmd/config.go

@@ -19,6 +19,7 @@ var (
 	projectID  uint
 	registryID uint
 	clusterID  uint
+	helmRepoID uint
 )
 
 var configCmd = &cobra.Command{
@@ -95,6 +96,27 @@ var setRegistryCmd = &cobra.Command{
 	},
 }
 
+var setHelmRepoCmd = &cobra.Command{
+	Use:   "set-helmrepo [id]",
+	Args:  cobra.ExactArgs(1),
+	Short: "Saves the helm repo id in the default configuration",
+	Run: func(cmd *cobra.Command, args []string) {
+		hrID, err := strconv.ParseUint(args[0], 10, 64)
+
+		if err != nil {
+			color.New(color.FgRed).Printf("An error occurred: %v\n", err)
+			os.Exit(1)
+		}
+
+		err = setHelmRepo(uint(hrID))
+
+		if err != nil {
+			color.New(color.FgRed).Printf("An error occurred: %v\n", err)
+			os.Exit(1)
+		}
+	},
+}
+
 var setHostCmd = &cobra.Command{
 	Use:   "set-host [host]",
 	Args:  cobra.ExactArgs(1),
@@ -116,6 +138,7 @@ func init() {
 	configCmd.AddCommand(setClusterCmd)
 	configCmd.AddCommand(setHostCmd)
 	configCmd.AddCommand(setRegistryCmd)
+	configCmd.AddCommand(setHelmRepoCmd)
 }
 
 func setDriver(driver string) error {
@@ -167,6 +190,12 @@ func setRegistry(id uint) error {
 	return viper.WriteConfig()
 }
 
+func setHelmRepo(id uint) error {
+	viper.Set("helm_repo", id)
+	color.New(color.FgGreen).Printf("Set the current helm repo id as %d\n", id)
+	return viper.WriteConfig()
+}
+
 func setHost(host string) error {
 	viper.Set("host", host)
 	err := viper.WriteConfig()
@@ -198,6 +227,14 @@ func getRegistryID() uint {
 	return viper.GetUint("registry")
 }
 
+func getHelmRepoID() uint {
+	if helmRepoID != 0 {
+		return helmRepoID
+	}
+
+	return viper.GetUint("helm_repo")
+}
+
 func getProjectID() uint {
 	if projectID != 0 {
 		return projectID

+ 27 - 0
cli/cmd/connect.go

@@ -55,6 +55,19 @@ var connectGCRCmd = &cobra.Command{
 	},
 }
 
+var connectHRCmd = &cobra.Command{
+	Use:     "helmrepo",
+	Aliases: []string{"helm", "helmrepos"},
+	Short:   "Adds a Helm repository to a project",
+	Run: func(cmd *cobra.Command, args []string) {
+		err := checkLoginAndRun(args, runConnectHelmRepoBasic)
+
+		if err != nil {
+			os.Exit(1)
+		}
+	},
+}
+
 func init() {
 	rootCmd.AddCommand(connectCmd)
 
@@ -90,6 +103,7 @@ func init() {
 
 	connectCmd.AddCommand(connectECRCmd)
 	connectCmd.AddCommand(connectGCRCmd)
+	connectCmd.AddCommand(connectHRCmd)
 }
 
 func runConnectKubeconfig(_ *api.AuthCheckResponse, client *api.Client, _ []string) error {
@@ -139,3 +153,16 @@ func runConnectGCR(_ *api.AuthCheckResponse, client *api.Client, _ []string) err
 
 	return setRegistry(regID)
 }
+
+func runConnectHelmRepoBasic(_ *api.AuthCheckResponse, client *api.Client, _ []string) error {
+	hrID, err := connect.Helm(
+		client,
+		getProjectID(),
+	)
+
+	if err != nil {
+		return err
+	}
+
+	return setHelmRepo(hrID)
+}

+ 97 - 0
cli/cmd/connect/helm.go

@@ -0,0 +1,97 @@
+package connect
+
+import (
+	"context"
+	"fmt"
+	"strings"
+
+	"github.com/fatih/color"
+	"github.com/porter-dev/porter/cli/cmd/api"
+	"github.com/porter-dev/porter/cli/cmd/utils"
+)
+
+// Helm connects a Helm repository using HTTP basic authentication
+func Helm(
+	client *api.Client,
+	projectID uint,
+) (uint, error) {
+	// if project ID is 0, ask the user to set the project ID or create a project
+	if projectID == 0 {
+		return 0, fmt.Errorf("no project set, please run porter project set [id]")
+	}
+
+	// query for helm repo name
+	helmName, err := utils.PromptPlaintext(fmt.Sprintf(`Give this Helm repository a name: `))
+
+	if err != nil {
+		return 0, err
+	}
+
+	repoURL, err := utils.PromptPlaintext(fmt.Sprintf(`Provide the Helm repository URL: `))
+
+	if err != nil {
+		return 0, err
+	}
+
+	userResp, err := utils.PromptPlaintext(
+		fmt.Sprintf(`Does this endpoint require a username/password to authenticate? %s `,
+			color.New(color.FgCyan).Sprintf("[y/n]"),
+		),
+	)
+
+	if err != nil {
+		return 0, err
+	}
+
+	username := ""
+	password := ""
+
+	if userResp := strings.ToLower(userResp); userResp == "y" || userResp == "yes" {
+		username, err = utils.PromptPlaintext(fmt.Sprintf(`Username: `))
+
+		if err != nil {
+			return 0, err
+		}
+
+		password, err = utils.PromptPasswordWithConfirmation()
+
+		if err != nil {
+			return 0, err
+		}
+	}
+
+	// create the basic auth integration
+	integration, err := client.CreateBasicAuthIntegration(
+		context.Background(),
+		projectID,
+		&api.CreateBasicAuthIntegrationRequest{
+			Username: username,
+			Password: password,
+		},
+	)
+
+	if err != nil {
+		return 0, err
+	}
+
+	color.New(color.FgGreen).Printf("created basic auth integration with id %d\n", integration.ID)
+
+	// create the helm repo
+	hr, err := client.CreateHelmRepo(
+		context.Background(),
+		projectID,
+		&api.CreateHelmRepoRequest{
+			Name:               helmName,
+			RepoURL:            repoURL,
+			BasicIntegrationID: integration.ID,
+		},
+	)
+
+	if err != nil {
+		return 0, err
+	}
+
+	color.New(color.FgGreen).Printf("created helm repo with id %d and name %s\n", hr.ID, hr.Name)
+
+	return hr.ID, nil
+}

+ 127 - 0
cli/cmd/helm_repo.go

@@ -0,0 +1,127 @@
+package cmd
+
+import (
+	"context"
+	"fmt"
+	"os"
+	"strings"
+	"text/tabwriter"
+
+	"github.com/fatih/color"
+	"github.com/porter-dev/porter/cli/cmd/api"
+	"github.com/spf13/cobra"
+)
+
+// helmRepoCmd represents the "porter helmrepo" base command when called
+// without any subcommands
+var helmRepoCmd = &cobra.Command{
+	Use:     "helmrepo",
+	Aliases: []string{"helm", "helmrepos"},
+	Short:   "Commands that read from a connected Helm repository",
+}
+
+var helmRepoListCmd = &cobra.Command{
+	Use:   "list",
+	Short: "Lists the Helm repositories linked to a project",
+	Run: func(cmd *cobra.Command, args []string) {
+		err := checkLoginAndRun(args, listHelmRepos)
+
+		if err != nil {
+			os.Exit(1)
+		}
+	},
+}
+
+var helmRepoChartCmd = &cobra.Command{
+	Use:     "chart",
+	Aliases: []string{"charts"},
+	Short:   "Commands for interacting with Helm repository charts",
+}
+
+var helmRepoChartListCmd = &cobra.Command{
+	Use:   "list",
+	Short: "Lists charts in the default Helm repository",
+	Run: func(cmd *cobra.Command, args []string) {
+		err := checkLoginAndRun(args, listHelmRepoCharts)
+
+		if err != nil {
+			os.Exit(1)
+		}
+	},
+}
+
+func init() {
+	rootCmd.AddCommand(helmRepoCmd)
+
+	helmRepoCmd.PersistentFlags().UintVar(
+		&helmRepoID,
+		"helmrepo-id",
+		0,
+		"id of the helm repo",
+	)
+
+	helmRepoCmd.AddCommand(helmRepoListCmd)
+	helmRepoCmd.AddCommand(helmRepoChartCmd)
+
+	helmRepoChartCmd.AddCommand(helmRepoChartListCmd)
+}
+
+func listHelmRepos(user *api.AuthCheckResponse, client *api.Client, args []string) error {
+	pID := getProjectID()
+
+	hrs, err := client.ListHelmRepos(
+		context.Background(),
+		pID,
+	)
+
+	if err != nil {
+		return err
+	}
+
+	w := new(tabwriter.Writer)
+	w.Init(os.Stdout, 3, 8, 0, '\t', tabwriter.AlignRight)
+
+	fmt.Fprintf(w, "%s\t%s\t%s\t%s\n", "ID", "NAME", "URL", "SERVICE")
+
+	currHelmID := getHelmRepoID()
+
+	for _, hr := range hrs {
+		if currHelmID == hr.ID {
+			color.New(color.FgGreen).Fprintf(w, "%d\t%s\t%s\t%s (current helm repo)\n", hr.ID, hr.Name, hr.RepoURL, hr.Service)
+		} else {
+			fmt.Fprintf(w, "%d\t%s\t%s\t%s\n", hr.ID, hr.Name, hr.RepoURL, hr.Service)
+		}
+	}
+
+	w.Flush()
+
+	return nil
+}
+
+func listHelmRepoCharts(user *api.AuthCheckResponse, client *api.Client, args []string) error {
+	pID := getProjectID()
+	hrID := getHelmRepoID()
+
+	charts, err := client.ListCharts(
+		context.Background(),
+		pID,
+		hrID,
+	)
+
+	if err != nil {
+		return err
+	}
+
+	w := new(tabwriter.Writer)
+	w.Init(os.Stdout, 3, 8, 0, '\t', tabwriter.AlignRight)
+
+	fmt.Fprintf(w, "%s\t%s\n", "NAME", "VERSION")
+
+	for _, chart := range charts {
+		fmt.Fprintf(w, "%s\t%s\n", strings.ToLower(chart.Name), chart.Version)
+	}
+
+	w.Flush()
+
+	return nil
+}

+ 2 - 0
cmd/app/main.go

@@ -51,7 +51,9 @@ func main() {
 		&ints.GCPIntegration{},
 		&ints.AWSIntegration{},
 		&ints.TokenCache{},
+		&ints.ClusterTokenCache{},
 		&ints.RegTokenCache{},
+		&ints.HelmRepoTokenCache{},
 	)
 
 	if err != nil {

+ 2 - 0
cmd/migrate/main.go

@@ -42,7 +42,9 @@ func main() {
 		&ints.GCPIntegration{},
 		&ints.AWSIntegration{},
 		&ints.TokenCache{},
+		&ints.ClusterTokenCache{},
 		&ints.RegTokenCache{},
+		&ints.HelmRepoTokenCache{},
 	)
 
 	if err != nil {

+ 2 - 2
internal/forms/integration.go

@@ -26,8 +26,8 @@ func (cgf *CreateGCPIntegrationForm) ToGCPIntegration() (*ints.GCPIntegration, e
 type CreateBasicAuthIntegrationForm struct {
 	UserID    uint   `json:"user_id" form:"required"`
 	ProjectID uint   `json:"project_id" form:"required"`
-	Username  string `json:"username" form:"required"`
-	Password  string `json:"password" form:"required"`
+	Username  string `json:"username"`
+	Password  string `json:"password"`
 }
 
 // ToBasicIntegration converts the project to a gorm project model

+ 1 - 0
internal/helm/loader/loader.go

@@ -24,6 +24,7 @@ func RepoIndexToPorterChartList(index *repo.IndexFile) []*models.PorterChartList
 
 		porterChart := &models.PorterChartList{
 			Name:        indexChart.Name,
+			Version:     indexChart.Version,
 			Description: indexChart.Description,
 			Icon:        indexChart.Icon,
 		}