Просмотр исходного кода

create porter app before running apply in cli (#3442)

Co-authored-by: David Townley <davidtownley@Davids-MacBook-Air.local>
d-g-town 2 лет назад
Родитель
Сommit
ed53eb6796
3 измененных файлов с 175 добавлено и 0 удалено
  1. 59 0
      api/client/porter_app.go
  2. 61 0
      api/server/handlers/porter_app/create_app.go
  3. 55 0
      cli/cmd/v2/apply.go

+ 59 - 0
api/client/porter_app.go

@@ -273,6 +273,65 @@ func (c *Client) CurrentAppRevision(
 	return resp, err
 }
 
+// CreatePorterAppDBEntryInput is the input struct to CreatePorterAppDBEntry
+type CreatePorterAppDBEntryInput struct {
+	AppName         string
+	GitRepoName     string
+	GitRepoID       uint
+	GitBranch       string
+	ImageRepository string
+	PorterYamlPath  string
+	ImageTag        string
+	Local           bool
+}
+
+// CreatePorterAppDBEntry creates an entry in the porter app
+func (c *Client) CreatePorterAppDBEntry(
+	ctx context.Context,
+	projectID uint, clusterID uint,
+	inp CreatePorterAppDBEntryInput,
+) error {
+	var sourceType porter_app.SourceType
+	var image *porter_app.Image
+	if inp.Local {
+		sourceType = porter_app.SourceType_Local
+	}
+	if inp.GitRepoName != "" {
+		sourceType = porter_app.SourceType_Github
+	}
+	if inp.ImageRepository != "" {
+		sourceType = porter_app.SourceType_DockerRegistry
+		image = &porter_app.Image{
+			Repository: inp.ImageRepository,
+			Tag:        inp.ImageTag,
+		}
+	}
+	if sourceType == "" {
+		return fmt.Errorf("cannot determine source type")
+	}
+
+	req := &porter_app.CreateAppRequest{
+		Name:           inp.AppName,
+		SourceType:     sourceType,
+		GitBranch:      inp.GitBranch,
+		GitRepoName:    inp.GitRepoName,
+		GitRepoID:      inp.GitRepoID,
+		PorterYamlPath: inp.PorterYamlPath,
+		Image:          image,
+	}
+
+	err := c.postRequest(
+		fmt.Sprintf(
+			"/projects/%d/clusters/%d/apps/create",
+			projectID, clusterID,
+		),
+		req,
+		&types.PorterApp{},
+	)
+
+	return err
+}
+
 // CreateSubdomain returns a subdomain for a given service that point to the ingress-nginx service in the cluster
 func (c *Client) CreateSubdomain(
 	ctx context.Context,

+ 61 - 0
api/server/handlers/porter_app/create_app.go

@@ -39,6 +39,8 @@ const (
 	SourceType_Github SourceType = "github"
 	// SourceType_DockerRegistry is the source kind for an app using an image from a docker registry
 	SourceType_DockerRegistry SourceType = "docker-registry"
+	// SourceType_Local is the source kind for an app being built locally
+	SourceType_Local SourceType = "other"
 )
 
 // Image is the image used by an app with a docker registry source
@@ -80,6 +82,14 @@ type CreateDockerRegistryAppInput struct {
 	PorterAppRepository repository.PorterAppRepository
 }
 
+// CreateLocalAppInput is the input for creating an app that is built locally via the cli
+type CreateLocalAppInput struct {
+	ProjectID           uint
+	ClusterID           uint
+	Name                string
+	PorterAppRepository repository.PorterAppRepository
+}
+
 func (c *CreateAppHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
 	ctx, span := telemetry.NewSpan(r.Context(), "serve-create-app")
 	defer span.End()
@@ -114,6 +124,24 @@ func (c *CreateAppHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
 	}
 	telemetry.WithAttributes(span, telemetry.AttributeKV{Key: "source-type", Value: request.SourceType})
 
+	porterAppDBEntries, err := c.Repo().PorterApp().ReadPorterAppsByProjectIDAndName(project.ID, request.Name)
+	if err != nil {
+		err := telemetry.Error(ctx, span, nil, "error reading porter apps by project id and name")
+		c.HandleAPIError(w, r, apierrors.NewErrPassThroughToClient(err, http.StatusInternalServerError))
+		return
+	}
+
+	if len(porterAppDBEntries) > 1 {
+		err := telemetry.Error(ctx, span, nil, "multiple apps with same name")
+		c.HandleAPIError(w, r, apierrors.NewErrPassThroughToClient(err, http.StatusInternalServerError))
+		return
+	}
+	if len(porterAppDBEntries) == 1 {
+		telemetry.WithAttributes(span, telemetry.AttributeKV{Key: "existing-app-id", Value: porterAppDBEntries[0].ID})
+		c.WriteResult(w, r, porterAppDBEntries[0].ToPorterAppType())
+		return
+	}
+
 	var porterApp *types.PorterApp
 	switch request.SourceType {
 	case SourceType_Github:
@@ -185,6 +213,21 @@ func (c *CreateAppHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
 			return
 		}
 		porterApp = app.ToPorterAppType()
+	case SourceType_Local:
+		input := CreateLocalAppInput{
+			ProjectID:           project.ID,
+			ClusterID:           cluster.ID,
+			Name:                request.Name,
+			PorterAppRepository: c.Repo().PorterApp(),
+		}
+
+		app, err := createLocalApp(ctx, input)
+		if err != nil {
+			err := telemetry.Error(ctx, span, err, "error creating other app")
+			c.HandleAPIError(w, r, apierrors.NewErrPassThroughToClient(err, http.StatusInternalServerError))
+			return
+		}
+		porterApp = app.ToPorterAppType()
 	default:
 		err := telemetry.Error(ctx, span, nil, "source type not supported")
 		c.HandleAPIError(w, r, apierrors.NewErrPassThroughToClient(err, http.StatusBadRequest))
@@ -236,3 +279,21 @@ func createDockerRegistryApp(ctx context.Context, input CreateDockerRegistryAppI
 
 	return porterApp, nil
 }
+
+func createLocalApp(ctx context.Context, input CreateLocalAppInput) (*models.PorterApp, error) {
+	ctx, span := telemetry.NewSpan(ctx, "create-local-app")
+	defer span.End()
+
+	porterApp := &models.PorterApp{
+		Name:      input.Name,
+		ProjectID: input.ProjectID,
+		ClusterID: input.ClusterID,
+	}
+
+	porterApp, err := input.PorterAppRepository.CreatePorterApp(porterApp)
+	if err != nil {
+		return porterApp, telemetry.Error(ctx, span, err, "error creating porter app")
+	}
+
+	return porterApp, nil
+}

+ 55 - 0
cli/cmd/v2/apply.go

@@ -7,6 +7,7 @@ import (
 	"fmt"
 	"os"
 	"path/filepath"
+	"strconv"
 
 	"github.com/fatih/color"
 	"github.com/porter-dev/api-contracts/generated/go/helpers"
@@ -56,6 +57,16 @@ func Apply(ctx context.Context, cliConf config.CLIConfig, client api.Client, por
 	}
 	base64AppProto := validateResp.ValidatedBase64AppProto
 
+	createPorterAppDBEntryInp, err := createPorterAppDbEntryInputFromProtoAndEnv(validateResp.ValidatedBase64AppProto)
+	if err != nil {
+		return fmt.Errorf("error creating porter app db entry input from proto: %w", err)
+	}
+
+	err = client.CreatePorterAppDBEntry(ctx, cliConf.Project, cliConf.Cluster, createPorterAppDBEntryInp)
+	if err != nil {
+		return fmt.Errorf("error creating porter app db entry: %w", err)
+	}
+
 	base64AppProtoWithSubdomains, err := addPorterSubdomainsIfNecessary(ctx, client, cliConf.Project, cliConf.Cluster, base64AppProto)
 	if err != nil {
 		return fmt.Errorf("error creating subdomains: %w", err)
@@ -117,6 +128,50 @@ func Apply(ctx context.Context, cliConf config.CLIConfig, client api.Client, por
 	return nil
 }
 
+func createPorterAppDbEntryInputFromProtoAndEnv(base64AppProto string) (api.CreatePorterAppDBEntryInput, error) {
+	var input api.CreatePorterAppDBEntryInput
+
+	decoded, err := base64.StdEncoding.DecodeString(base64AppProto)
+	if err != nil {
+		return input, fmt.Errorf("unable to decode base64 app for revision: %w", err)
+	}
+
+	app := &porterv1.PorterApp{}
+	err = helpers.UnmarshalContractObject(decoded, app)
+	if err != nil {
+		return input, fmt.Errorf("unable to unmarshal app for revision: %w", err)
+	}
+
+	if app.Name == "" {
+		return input, fmt.Errorf("app does not contain name")
+	}
+	input.AppName = app.Name
+
+	if app.Build != nil {
+		if os.Getenv("GITHUB_REPOSITORY_ID") == "" {
+			input.Local = true
+			return input, nil
+		}
+		gitRepoId, err := strconv.Atoi(os.Getenv("GITHUB_REPOSITORY_ID"))
+		if err != nil {
+			return input, fmt.Errorf("unable to parse GITHUB_REPOSITORY_ID to int: %w", err)
+		}
+		input.GitRepoID = uint(gitRepoId)
+		input.GitRepoName = os.Getenv("GITHUB_REPOSITORY")
+		input.GitBranch = os.Getenv("GITHUB_REF_NAME")
+		input.PorterYamlPath = "porter.yaml"
+		return input, nil
+	}
+
+	if app.Image != nil {
+		input.ImageRepository = app.Image.Repository
+		input.ImageTag = app.Image.Tag
+		return input, nil
+	}
+
+	return input, fmt.Errorf("app does not contain build or image settings")
+}
+
 func addPorterSubdomainsIfNecessary(ctx context.Context, client api.Client, project uint, cluster uint, base64AppProto string) (string, error) {
 	var editedB64AppProto string