Przeglądaj źródła

temp create ecr repository fix

Alexander Belanger 5 lat temu
rodzic
commit
7bf830ef2e

+ 79 - 0
cli/cmd/api/k8s.go

@@ -46,3 +46,82 @@ func (c *Client) GetK8sNamespaces(
 
 	return bodyResp, nil
 }
+
+// GetKubeconfigResponse is the list of namespaces returned when a
+// user has successfully authenticated
+type GetKubeconfigResponse struct {
+	Kubeconfig []byte `json:"kubeconfig"`
+}
+
+// GetK8sNamespaces gets a namespaces list in a k8s cluster
+func (c *Client) GetKubeconfig(
+	ctx context.Context,
+	projectID uint,
+	clusterID uint,
+) (*GetKubeconfigResponse, error) {
+	cl := fmt.Sprintf("%d", clusterID)
+
+	req, err := http.NewRequest(
+		"GET",
+		fmt.Sprintf("%s/projects/%d/k8s/kubeconfig?"+url.Values{
+			"cluster_id": []string{cl},
+		}.Encode(), c.BaseURL, projectID),
+		nil,
+	)
+
+	if err != nil {
+		return nil, err
+	}
+
+	req = req.WithContext(ctx)
+	bodyResp := &GetKubeconfigResponse{}
+
+	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
+}
+
+// GetReleaseAllPodsResponse is the list of all pods for a given Helm release
+type GetReleaseAllPodsResponse []v1.Pod
+
+// GetK8sAllPods gets all pods for a given release
+func (c *Client) GetK8sAllPods(
+	ctx context.Context,
+	projectID, clusterID uint,
+	namespace, name string,
+) (GetReleaseAllPodsResponse, error) {
+	cl := fmt.Sprintf("%d", clusterID)
+
+	req, err := http.NewRequest(
+		"GET",
+		fmt.Sprintf("%s/projects/%d/releases/%s/0/pods/all?"+url.Values{
+			"cluster_id": []string{cl},
+			"namespace":  []string{namespace},
+			"storage":    []string{"secret"},
+		}.Encode(), c.BaseURL, projectID, name),
+		nil,
+	)
+
+	if err != nil {
+		return nil, err
+	}
+
+	req = req.WithContext(ctx)
+	bodyResp := &GetReleaseAllPodsResponse{}
+
+	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
+}

+ 157 - 0
cli/cmd/run.go

@@ -0,0 +1,157 @@
+package cmd
+
+import (
+	"context"
+	"fmt"
+	"os"
+
+	"github.com/porter-dev/porter/cli/cmd/api"
+	"github.com/spf13/cobra"
+	"k8s.io/apimachinery/pkg/runtime"
+	"k8s.io/apimachinery/pkg/runtime/schema"
+	"k8s.io/client-go/rest"
+	"k8s.io/client-go/tools/clientcmd"
+	"k8s.io/client-go/tools/remotecommand"
+	"k8s.io/kubectl/pkg/util/term"
+)
+
+// runCmd represents the "porter run" base command when called
+// without any subcommands
+var runCmd = &cobra.Command{
+	Use:   "run [cmd]",
+	Args:  cobra.ExactArgs(1),
+	Short: "Runs a command inside a connected cluster container.",
+	Run: func(cmd *cobra.Command, args []string) {
+		err := checkLoginAndRun(args, run)
+
+		if err != nil {
+			os.Exit(1)
+		}
+	},
+}
+
+func init() {
+	rootCmd.AddCommand(runCmd)
+
+	runCmd.PersistentFlags().StringVar(
+		&host,
+		"host",
+		getHost(),
+		"host url of Porter instance",
+	)
+}
+
+func run(_ *api.AuthCheckResponse, client *api.Client, args []string) error {
+	podNames, err := getPods(client)
+
+	if err != nil {
+		return fmt.Errorf("Could not retrieve list of pods: %s", err.Error())
+	}
+
+	restConf, err := getRESTConfig(client)
+
+	if err != nil {
+		return fmt.Errorf("Could not retrieve kube credentials: %s", err.Error())
+	}
+
+	return executeRun(restConf, "default", podNames[0])
+}
+
+func getRESTConfig(client *api.Client) (*rest.Config, error) {
+	pID := getProjectID()
+	cID := getClusterID()
+
+	kubeResp, err := client.GetKubeconfig(context.TODO(), pID, cID)
+
+	if err != nil {
+		return nil, err
+	}
+
+	kubeBytes := kubeResp.Kubeconfig
+
+	cmdConf, err := clientcmd.NewClientConfigFromBytes(kubeBytes)
+
+	if err != nil {
+		return nil, err
+	}
+
+	restConf, err := cmdConf.ClientConfig()
+
+	if err != nil {
+		return nil, err
+	}
+
+	restConf.GroupVersion = &schema.GroupVersion{
+		Group:   "api",
+		Version: "v1",
+	}
+
+	restConf.NegotiatedSerializer = runtime.NewSimpleNegotiatedSerializer(runtime.SerializerInfo{})
+
+	return restConf, nil
+}
+
+func getPods(client *api.Client) ([]string, error) {
+	pID := getProjectID()
+	cID := getClusterID()
+
+	resp, err := client.GetK8sAllPods(context.TODO(), pID, cID, "default", "same-name")
+
+	if err != nil {
+		return nil, err
+	}
+
+	res := make([]string, 0)
+
+	for _, pod := range resp {
+		res = append(res, pod.ObjectMeta.Name)
+	}
+
+	return res, nil
+}
+
+func executeRun(config *rest.Config, namespace, name string) error {
+	restClient, err := rest.RESTClientFor(config)
+
+	if err != nil {
+		return err
+	}
+
+	req := restClient.Post().
+		Resource("pods").
+		Name(name).
+		Namespace(namespace).
+		SubResource("exec")
+
+	// req.Param("container", "web")
+	req.Param("command", "sh")
+	req.Param("stdin", "true")
+	req.Param("stdout", "true")
+	req.Param("tty", "true")
+
+	t := term.TTY{
+		In:  os.Stdin,
+		Out: os.Stdout,
+	}
+
+	fn := func() error {
+		exec, err := remotecommand.NewSPDYExecutor(config, "POST", req.URL())
+
+		if err != nil {
+			return err
+		}
+
+		return exec.Stream(remotecommand.StreamOptions{
+			Stdin:  os.Stdin,
+			Stdout: os.Stdout,
+			Stderr: os.Stderr,
+			Tty:    true,
+		})
+	}
+
+	if err := t.Safe(fn); err != nil {
+		return err
+	}
+
+	return err
+}

+ 1 - 0
go.mod

@@ -88,6 +88,7 @@ require (
 	k8s.io/client-go v0.18.8
 	k8s.io/helm v2.16.12+incompatible
 	k8s.io/klog/v2 v2.2.0 // indirect
+	k8s.io/kubectl v0.18.8
 	k8s.io/utils v0.0.0-20200912215256-4140de9c8800 // indirect
 	rsc.io/letsencrypt v0.0.3 // indirect
 	sigs.k8s.io/aws-iam-authenticator v0.5.2

+ 2 - 2
internal/kubernetes/config.go

@@ -181,7 +181,7 @@ func (conf *OutOfClusterConfig) GetClientConfigFromCluster() (clientcmd.ClientCo
 		return clientcmd.NewClientConfigFromBytes(kubeAuth.Kubeconfig)
 	}
 
-	apiConfig, err := conf.createRawConfigFromCluster()
+	apiConfig, err := conf.CreateRawConfigFromCluster()
 
 	if err != nil {
 		return nil, err
@@ -200,7 +200,7 @@ func (conf *OutOfClusterConfig) GetClientConfigFromCluster() (clientcmd.ClientCo
 	return config, nil
 }
 
-func (conf *OutOfClusterConfig) createRawConfigFromCluster() (*api.Config, error) {
+func (conf *OutOfClusterConfig) CreateRawConfigFromCluster() (*api.Config, error) {
 	cluster := conf.Cluster
 
 	apiConfig := &api.Config{}

+ 16 - 3
internal/registry/registry.go

@@ -10,6 +10,7 @@ import (
 	"strings"
 	"time"
 
+	"github.com/aws/aws-sdk-go/aws/awserr"
 	"github.com/aws/aws-sdk-go/service/ecr"
 	"github.com/porter-dev/porter/internal/models"
 	"github.com/porter-dev/porter/internal/oauth"
@@ -408,11 +409,23 @@ func (r *Registry) createECRRepository(
 
 	svc := ecr.New(sess)
 
-	_, err = svc.CreateRepository(&ecr.CreateRepositoryInput{
-		RepositoryName: &name,
+	// determine if repository already exists
+	_, err = svc.DescribeRepositories(&ecr.DescribeRepositoriesInput{
+		RepositoryNames: []*string{&name},
 	})
 
-	return err
+	// if the repository was not found, create it
+	if aerr, ok := err.(awserr.Error); ok && aerr.Code() == ecr.ErrCodeRepositoryNotFoundException {
+		_, err = svc.CreateRepository(&ecr.CreateRepositoryInput{
+			RepositoryName: &name,
+		})
+
+		return err
+	} else if err != nil {
+		return err
+	}
+
+	return nil
 }
 
 // ListImages lists the images for an image repository

+ 6 - 1
internal/templater/utils/values.go

@@ -24,8 +24,13 @@ func MergeYAML(base, override []byte) (map[string]interface{}, error) {
 
 // CoalesceValues replaces arrays and scalar values, merges maps
 func CoalesceValues(base, override map[string]interface{}) map[string]interface{} {
-	for key, val := range base {
+	if base == nil && override != nil {
+		return override
+	} else if override == nil {
+		return base
+	}
 
+	for key, val := range base {
 		if oVal, ok := override[key]; ok {
 			if oVal == nil {
 				delete(override, key)

+ 42 - 0
server/api/k8s_handler.go

@@ -13,6 +13,7 @@ import (
 	"github.com/porter-dev/porter/internal/kubernetes"
 	"github.com/porter-dev/porter/internal/kubernetes/prometheus"
 	v1 "k8s.io/api/core/v1"
+	"k8s.io/client-go/tools/clientcmd"
 )
 
 // Enumeration of k8s API error codes, represented as int64
@@ -644,3 +645,44 @@ func (app *App) HandleGetPodMetrics(w http.ResponseWriter, r *http.Request) {
 
 	fmt.Fprint(w, string(rawQuery))
 }
+
+type KubeconfigResponse struct {
+	Kubeconfig []byte `json:"kubeconfig"`
+}
+
+func (app *App) HandleGetTemporaryKubeconfig(w http.ResponseWriter, r *http.Request) {
+	vals, err := url.ParseQuery(r.URL.RawQuery)
+
+	if err != nil {
+		app.handleErrorFormDecoding(err, ErrReleaseDecode, w)
+		return
+	}
+
+	// get the filter options
+	form := &forms.K8sForm{
+		OutOfClusterConfig: &kubernetes.OutOfClusterConfig{
+			Repo:              app.Repo,
+			DigitalOceanOAuth: app.DOConf,
+		},
+	}
+
+	form.PopulateK8sOptionsFromQueryParams(vals, app.Repo.Cluster)
+
+	// get the API config
+	apiConf, err := form.OutOfClusterConfig.CreateRawConfigFromCluster()
+
+	if err != nil {
+		app.handleErrorFormDecoding(err, ErrProjectDecode, w)
+		return
+	}
+
+	bytes, err := clientcmd.Write(*apiConf)
+	res := &KubeconfigResponse{
+		Kubeconfig: bytes,
+	}
+
+	if err := json.NewEncoder(w).Encode(res); err != nil {
+		app.handleErrorFormDecoding(err, ErrK8sDecode, w)
+		return
+	}
+}

+ 152 - 0
server/api/release_handler.go

@@ -12,6 +12,8 @@ import (
 	"github.com/porter-dev/porter/internal/models"
 	"github.com/porter-dev/porter/internal/templater/parser"
 	"helm.sh/helm/v3/pkg/release"
+	v1 "k8s.io/api/core/v1"
+	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
 
 	"github.com/go-chi/chi"
 	"github.com/porter-dev/porter/internal/forms"
@@ -395,6 +397,156 @@ func (app *App) HandleGetReleaseControllers(w http.ResponseWriter, r *http.Reque
 	}
 }
 
+// HandleGetReleaseAllPods retrieves all pods that are associated with a given release.
+func (app *App) HandleGetReleaseAllPods(w http.ResponseWriter, r *http.Request) {
+	name := chi.URLParam(r, "name")
+	revision, err := strconv.ParseUint(chi.URLParam(r, "revision"), 0, 64)
+
+	form := &forms.GetReleaseForm{
+		ReleaseForm: &forms.ReleaseForm{
+			Form: &helm.Form{
+				Repo:              app.Repo,
+				DigitalOceanOAuth: app.DOConf,
+			},
+		},
+		Name:     name,
+		Revision: int(revision),
+	}
+
+	agent, err := app.getAgentFromQueryParams(
+		w,
+		r,
+		form.ReleaseForm,
+		form.ReleaseForm.PopulateHelmOptionsFromQueryParams,
+	)
+
+	// errors are handled in app.getAgentFromQueryParams
+	if err != nil {
+		return
+	}
+
+	release, err := agent.GetRelease(form.Name, form.Revision)
+
+	if err != nil {
+		app.sendExternalError(err, http.StatusNotFound, HTTPError{
+			Code:   ErrReleaseReadData,
+			Errors: []string{"release not found"},
+		}, w)
+
+		return
+	}
+
+	vals, err := url.ParseQuery(r.URL.RawQuery)
+
+	if err != nil {
+		app.handleErrorFormDecoding(err, ErrReleaseDecode, w)
+		return
+	}
+
+	// get the filter options
+	k8sForm := &forms.K8sForm{
+		OutOfClusterConfig: &kubernetes.OutOfClusterConfig{
+			Repo:              app.Repo,
+			DigitalOceanOAuth: app.DOConf,
+		},
+	}
+
+	k8sForm.PopulateK8sOptionsFromQueryParams(vals, app.Repo.Cluster)
+	k8sForm.DefaultNamespace = form.ReleaseForm.Namespace
+
+	// validate the form
+	if err := app.validator.Struct(k8sForm); err != nil {
+		app.handleErrorFormValidation(err, ErrK8sValidate, w)
+		return
+	}
+
+	// create a new kubernetes agent
+	var k8sAgent *kubernetes.Agent
+
+	if app.ServerConf.IsTesting {
+		k8sAgent = app.TestAgents.K8sAgent
+	} else {
+		k8sAgent, err = kubernetes.GetAgentOutOfClusterConfig(k8sForm.OutOfClusterConfig)
+	}
+
+	yamlArr := grapher.ImportMultiDocYAML([]byte(release.Manifest))
+	controllers := grapher.ParseControllers(yamlArr)
+	pods := make([]v1.Pod, 0)
+
+	// get current status of each controller
+	for _, c := range controllers {
+		var selector *metav1.LabelSelector
+
+		switch c.Kind {
+		case "Deployment":
+			rc, err := k8sAgent.GetDeployment(c)
+
+			if err != nil {
+				app.handleErrorDataRead(err, w)
+				return
+			}
+
+			selector = rc.Spec.Selector
+		case "StatefulSet":
+			rc, err := k8sAgent.GetStatefulSet(c)
+
+			if err != nil {
+				app.handleErrorDataRead(err, w)
+				return
+			}
+
+			selector = rc.Spec.Selector
+		case "DaemonSet":
+			rc, err := k8sAgent.GetDaemonSet(c)
+
+			if err != nil {
+				app.handleErrorDataRead(err, w)
+				return
+			}
+
+			selector = rc.Spec.Selector
+		case "ReplicaSet":
+			rc, err := k8sAgent.GetReplicaSet(c)
+
+			if err != nil {
+				app.handleErrorDataRead(err, w)
+				return
+			}
+
+			selector = rc.Spec.Selector
+		case "CronJob":
+			rc, err := k8sAgent.GetCronJob(c)
+
+			if err != nil {
+				app.handleErrorDataRead(err, w)
+				return
+			}
+
+			selector = rc.Spec.JobTemplate.Spec.Selector
+		}
+
+		selectors := make([]string, 0)
+
+		for key, val := range selector.MatchLabels {
+			selectors = append(selectors, key+"="+val)
+		}
+
+		podList, err := k8sAgent.GetPodsByLabel(strings.Join(selectors, ","))
+
+		if err != nil {
+			app.handleErrorDataRead(err, w)
+			return
+		}
+
+		pods = append(pods, podList.Items...)
+	}
+
+	if err := json.NewEncoder(w).Encode(pods); err != nil {
+		app.handleErrorFormDecoding(err, ErrReleaseDecode, w)
+		return
+	}
+}
+
 // HandleListReleaseHistory retrieves a history of releases based on a release name
 func (app *App) HandleListReleaseHistory(w http.ResponseWriter, r *http.Request) {
 	name := chi.URLParam(r, "name")

+ 28 - 0
server/router/router.go

@@ -896,6 +896,20 @@ func New(a *api.App) *chi.Mux {
 			),
 		)
 
+		r.Method(
+			"GET",
+			"/projects/{project_id}/releases/{name}/{revision}/pods/all",
+			auth.DoesUserHaveProjectAccess(
+				auth.DoesUserHaveClusterAccess(
+					requestlog.NewHandler(a.HandleGetReleaseAllPods, l),
+					mw.URLParam,
+					mw.QueryParam,
+				),
+				mw.URLParam,
+				mw.ReadAccess,
+			),
+		)
+
 		r.Method(
 			"GET",
 			"/projects/{project_id}/releases/{name}/history",
@@ -1084,6 +1098,20 @@ func New(a *api.App) *chi.Mux {
 			),
 		)
 
+		r.Method(
+			"GET",
+			"/projects/{project_id}/k8s/kubeconfig",
+			auth.DoesUserHaveProjectAccess(
+				auth.DoesUserHaveClusterAccess(
+					requestlog.NewHandler(a.HandleGetTemporaryKubeconfig, l),
+					mw.URLParam,
+					mw.QueryParam,
+				),
+				mw.URLParam,
+				mw.WriteAccess,
+			),
+		)
+
 		r.Method(
 			"GET",
 			"/projects/{project_id}/k8s/prometheus/detect",