Преглед изворни кода

Merge pull request #2163 from porter-dev/master

Cluster Info + Dependency Improvements -> staging
abelanger5 пре 3 година
родитељ
комит
3e06fb62cf

+ 115 - 0
api/server/handlers/cluster_integration/aws/get_cluster_info.go

@@ -0,0 +1,115 @@
+package aws
+
+import (
+	"errors"
+	"fmt"
+	"net/http"
+	"strings"
+
+	"github.com/aws/aws-sdk-go/aws"
+	"github.com/aws/aws-sdk-go/service/ec2"
+	"github.com/aws/aws-sdk-go/service/eks"
+	"github.com/porter-dev/porter/api/server/handlers"
+	"github.com/porter-dev/porter/api/server/shared"
+	"github.com/porter-dev/porter/api/server/shared/apierrors"
+	"github.com/porter-dev/porter/api/server/shared/config"
+	"github.com/porter-dev/porter/api/types"
+	"github.com/porter-dev/porter/internal/models"
+	"gorm.io/gorm"
+)
+
+type GetClusterInfoHandler struct {
+	handlers.PorterHandlerReadWriter
+}
+
+func NewGetClusterInfoHandler(
+	config *config.Config,
+	decoderValidator shared.RequestDecoderValidator,
+	writer shared.ResultWriter,
+) *GetClusterInfoHandler {
+	return &GetClusterInfoHandler{
+		PorterHandlerReadWriter: handlers.NewDefaultPorterHandler(config, decoderValidator, writer),
+	}
+}
+
+func (c *GetClusterInfoHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
+	proj, _ := r.Context().Value(types.ProjectScope).(*models.Project)
+	cluster, _ := r.Context().Value(types.ClusterScope).(*models.Cluster)
+
+	if cluster.AWSIntegrationID == 0 {
+		c.HandleAPIError(w, r, apierrors.NewErrForbidden(fmt.Errorf("no AWS cluster found with cluster ID: %d", cluster.ID)))
+		return
+	}
+
+	awsInt, err := c.Repo().AWSIntegration().ReadAWSIntegration(proj.ID, cluster.AWSIntegrationID)
+
+	if err != nil {
+		if errors.Is(err, gorm.ErrRecordNotFound) {
+			c.HandleAPIError(w, r, apierrors.NewErrNotFound(fmt.Errorf("no AWS integration found with project ID: %d and "+
+				"integration ID: %d", proj.ID, cluster.AWSIntegrationID)))
+			return
+		}
+
+		c.HandleAPIError(w, r, apierrors.NewErrInternal(fmt.Errorf("error fetching AWS integration with project ID: %d and "+
+			"integration ID: %d. Error: %w", proj.ID, cluster.AWSIntegrationID, err)))
+		return
+	}
+
+	awsSession, err := awsInt.GetSession()
+
+	if err != nil {
+		c.HandleAPIError(w, r, apierrors.NewErrPassThroughToClient(fmt.Errorf("error fetching new session for AWS with "+
+			"project ID: %d and integration ID: %d. Error: %w", proj.ID, cluster.AWSIntegrationID, err), http.StatusConflict))
+		return
+	}
+
+	clusterName := cluster.Name
+
+	if strings.HasPrefix(clusterName, "arn:aws:eks:") {
+		parts := strings.Split(clusterName, "/")
+		clusterName = parts[len(parts)-1]
+	}
+
+	awsConf := aws.NewConfig()
+
+	if awsInt.AWSRegion != "" {
+		awsConf = awsConf.WithRegion(awsInt.AWSRegion)
+	}
+
+	eksSvc := eks.New(awsSession, awsConf)
+
+	clusterInfo, err := eksSvc.DescribeCluster(&eks.DescribeClusterInput{
+		Name: &clusterName,
+	})
+
+	if err != nil || clusterInfo.Cluster == nil {
+		c.HandleAPIError(w, r, apierrors.NewErrInternal(err))
+		return
+	}
+
+	ec2Svc := ec2.New(awsSession, awsConf)
+
+	subnetsInfo, err := ec2Svc.DescribeSubnets(&ec2.DescribeSubnetsInput{
+		SubnetIds: clusterInfo.Cluster.ResourcesVpcConfig.SubnetIds,
+	})
+
+	if err != nil || len(subnetsInfo.Subnets) == 0 {
+		c.HandleAPIError(w, r, apierrors.NewErrInternal(err))
+		return
+	}
+
+	res := &types.GetAWSClusterInfoResponse{
+		Name:       clusterName,
+		K8sVersion: *clusterInfo.Cluster.Version,
+		EKSVersion: *clusterInfo.Cluster.PlatformVersion,
+	}
+
+	for _, subnet := range subnetsInfo.Subnets {
+		res.Subnets = append(res.Subnets, &types.AWSSubnet{
+			AvailabilityZone:        *subnet.AvailabilityZone,
+			AvailableIPAddressCount: *subnet.AvailableIpAddressCount,
+		})
+	}
+
+	c.WriteResult(w, r, res)
+}

+ 86 - 0
api/server/router/cluster_integration.go

@@ -0,0 +1,86 @@
+package router
+
+import (
+	"github.com/go-chi/chi"
+	awsClusterInt "github.com/porter-dev/porter/api/server/handlers/cluster_integration/aws"
+	"github.com/porter-dev/porter/api/server/shared"
+	"github.com/porter-dev/porter/api/server/shared/config"
+	"github.com/porter-dev/porter/api/server/shared/router"
+	"github.com/porter-dev/porter/api/types"
+)
+
+func NewClusterIntegrationScopedRegisterer(children ...*router.Registerer) *router.Registerer {
+	return &router.Registerer{
+		GetRoutes: GetClusterIntegrationScopedRoutes,
+		Children:  children,
+	}
+}
+
+func GetClusterIntegrationScopedRoutes(
+	r chi.Router,
+	config *config.Config,
+	basePath *types.Path,
+	factory shared.APIEndpointFactory,
+	children ...*router.Registerer,
+) []*router.Route {
+	routes, projPath := getClusterIntegrationRoutes(r, config, basePath, factory)
+
+	if len(children) > 0 {
+		r.Route(projPath.RelativePath, func(r chi.Router) {
+			for _, child := range children {
+				childRoutes := child.GetRoutes(r, config, basePath, factory, child.Children...)
+
+				routes = append(routes, childRoutes...)
+			}
+		})
+	}
+
+	return routes
+}
+
+func getClusterIntegrationRoutes(
+	r chi.Router,
+	config *config.Config,
+	basePath *types.Path,
+	factory shared.APIEndpointFactory,
+) ([]*router.Route, *types.Path) {
+	relPath := "/integrations"
+
+	newPath := &types.Path{
+		Parent:       basePath,
+		RelativePath: relPath,
+	}
+
+	routes := make([]*router.Route, 0)
+
+	// GET /api/projects/{project_id}/clusters/{cluster_id}/integrations/aws/info -> awsClusterInt.NewGetClusterInfoHandler
+	getAWSClusterInfoEndpoint := factory.NewAPIEndpoint(
+		&types.APIRequestMetadata{
+			Verb:   types.APIVerbGet,
+			Method: types.HTTPVerbGet,
+			Path: &types.Path{
+				Parent:       basePath,
+				RelativePath: relPath + "/aws/info",
+			},
+			Scopes: []types.PermissionScope{
+				types.UserScope,
+				types.ProjectScope,
+				types.ClusterScope,
+			},
+		},
+	)
+
+	getAWSClusterInfoHandler := awsClusterInt.NewGetClusterInfoHandler(
+		config,
+		factory.GetDecoderValidator(),
+		factory.GetResultWriter(),
+	)
+
+	routes = append(routes, &router.Route{
+		Endpoint: getAWSClusterInfoEndpoint,
+		Handler:  getAWSClusterInfoHandler,
+		Router:   r,
+	})
+
+	return routes, newPath
+}

+ 2 - 1
api/server/router/router.go

@@ -29,7 +29,8 @@ func NewAPIRouter(config *config.Config) *chi.Mux {
 
 	releaseRegisterer := NewReleaseScopedRegisterer()
 	namespaceRegisterer := NewNamespaceScopedRegisterer(releaseRegisterer)
-	clusterRegisterer := NewClusterScopedRegisterer(namespaceRegisterer)
+	clusterIntegrationRegisterer := NewClusterIntegrationScopedRegisterer()
+	clusterRegisterer := NewClusterScopedRegisterer(namespaceRegisterer, clusterIntegrationRegisterer)
 	infraRegisterer := NewInfraScopedRegisterer()
 	gitInstallationRegisterer := NewGitInstallationScopedRegisterer()
 	registryRegisterer := NewRegistryScopedRegisterer()

+ 13 - 0
api/types/cluster_integration.go

@@ -0,0 +1,13 @@
+package types
+
+type AWSSubnet struct {
+	AvailabilityZone        string `json:"availability_zone"`
+	AvailableIPAddressCount int64  `json:"available_ip_address_count"`
+}
+
+type GetAWSClusterInfoResponse struct {
+	Name       string       `json:"name"`
+	K8sVersion string       `json:"kubernetes_server_version"`
+	EKSVersion string       `json:"eks_version"`
+	Subnets    []*AWSSubnet `json:"subnets"`
+}

+ 17 - 12
internal/helm/agent.go

@@ -112,21 +112,26 @@ func (a *Agent) GetRelease(
 
 	if getDeps && release.Chart != nil && release.Chart.Metadata != nil {
 		for _, dep := range release.Chart.Metadata.Dependencies {
-			depExists := false
-
-			for _, currDep := range release.Chart.Dependencies() {
-				// we just case on name for now -- there might be edge cases we're missing
-				// but this will cover 99% of cases
-				if dep != nil && currDep != nil && dep.Name == currDep.Name() {
-					depExists = true
-					break
+			// only search for dependency if it passes the condition specified in Chart.yaml
+			if dep.Enabled {
+				depExists := false
+
+				for _, currDep := range release.Chart.Dependencies() {
+					// we just case on name for now -- there might be edge cases we're missing
+					// but this will cover 99% of cases
+					if dep != nil && currDep != nil && dep.Name == currDep.Name() {
+						depExists = true
+						break
+					}
 				}
-			}
 
-			if !depExists {
-				depChart, err := loader.LoadChartPublic(dep.Repository, dep.Name, dep.Version)
+				if !depExists {
+					depChart, err := loader.LoadChartPublic(dep.Repository, dep.Name, dep.Version)
+
+					if err != nil {
+						return nil, fmt.Errorf("Error retrieving chart dependency %s/%s-%s: %s", dep.Repository, dep.Name, dep.Version, err.Error())
+					}
 
-				if err == nil {
 					release.Chart.AddDependency(depChart)
 				}
 			}

+ 1 - 1
internal/helm/config.go

@@ -81,7 +81,7 @@ func GetAgentFromK8sAgent(stg string, ns string, l *logger.Logger, k8sAgent *kub
 // the underlying kubernetes.GetAgentInClusterConfig method
 func GetAgentInClusterConfig(form *Form, l *logger.Logger) (*Agent, error) {
 	// create a kubernetes agent
-	k8sAgent, err := kubernetes.GetAgentInClusterConfig()
+	k8sAgent, err := kubernetes.GetAgentInClusterConfig(form.Namespace)
 
 	if err != nil {
 		return nil, err

+ 8 - 4
internal/kubernetes/config.go

@@ -59,7 +59,7 @@ func GetDynamicClientOutOfClusterConfig(conf *OutOfClusterConfig) (dynamic.Inter
 // GetAgentOutOfClusterConfig creates a new Agent using the OutOfClusterConfig
 func GetAgentOutOfClusterConfig(conf *OutOfClusterConfig) (*Agent, error) {
 	if conf.AllowInClusterConnections && conf.Cluster.AuthMechanism == models.InCluster {
-		return GetAgentInClusterConfig()
+		return GetAgentInClusterConfig(conf.DefaultNamespace)
 	}
 
 	restConf, err := conf.ToRESTConfig()
@@ -89,14 +89,14 @@ func IsInCluster() bool {
 
 // GetAgentInClusterConfig uses the service account that kubernetes
 // gives to pods to connect
-func GetAgentInClusterConfig() (*Agent, error) {
+func GetAgentInClusterConfig(namespace string) (*Agent, error) {
 	conf, err := rest.InClusterConfig()
 
 	if err != nil {
 		return nil, err
 	}
 
-	restClientGetter := NewRESTClientGetterFromInClusterConfig(conf)
+	restClientGetter := NewRESTClientGetterFromInClusterConfig(conf, namespace)
 	clientset, err := kubernetes.NewForConfig(conf)
 
 	return &Agent{restClientGetter, clientset}, nil
@@ -419,9 +419,13 @@ func (conf *OutOfClusterConfig) setTokenCache(token string, expiry time.Time) er
 
 // NewRESTClientGetterFromInClusterConfig returns a RESTClientGetter using
 // default values set from the *rest.Config
-func NewRESTClientGetterFromInClusterConfig(conf *rest.Config) genericclioptions.RESTClientGetter {
+func NewRESTClientGetterFromInClusterConfig(conf *rest.Config, namespace string) genericclioptions.RESTClientGetter {
 	cfs := genericclioptions.NewConfigFlags(false)
 
+	if namespace != "" {
+		cfs.Namespace = &namespace
+	}
+
 	cfs.ClusterName = &conf.ServerName
 	cfs.Insecure = &conf.Insecure
 	cfs.APIServer = &conf.Host

+ 8 - 1
internal/kubernetes/local/kubeconfig.go

@@ -80,7 +80,14 @@ func GetSelfAgentFromFileConfig(kubeconfigPath string) (*kubernetes.Agent, error
 		return nil, err
 	}
 
-	restClientGetter := kubernetes.NewRESTClientGetterFromInClusterConfig(restConf)
+	var namespace string
+	cmdConfNamespace, _, err := cmdConf.Namespace()
+
+	if err == nil && cmdConfNamespace != "" {
+		namespace = cmdConfNamespace
+	}
+
+	restClientGetter := kubernetes.NewRESTClientGetterFromInClusterConfig(restConf, namespace)
 	clientset, err := k8s.NewForConfig(restConf)
 
 	return &kubernetes.Agent{

+ 1 - 1
provisioner/server/config/config.go

@@ -248,7 +248,7 @@ func getProvisionerAgent(conf *ProvisionerConf) (*kubernetes.Agent, error) {
 		return nil, fmt.Errorf(`"kubeconfig" cluster option requires path to kubeconfig`)
 	}
 
-	agent, _ := kubernetes.GetAgentInClusterConfig()
+	agent, _ := kubernetes.GetAgentInClusterConfig(conf.ProvisionerJobNamespace)
 
 	return agent, nil
 }