Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Initial usage of buildplanner for state init. #2844

Merged
merged 5 commits into from
Oct 25, 2023
Merged
Show file tree
Hide file tree
Changes from 4 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
44 changes: 26 additions & 18 deletions internal/runners/initialize/init.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ import (
"path/filepath"
"strings"

"github.com/go-openapi/strfmt"

"github.com/ActiveState/cli/internal/analytics"
"github.com/ActiveState/cli/internal/constants"
"github.com/ActiveState/cli/internal/errs"
Expand Down Expand Up @@ -86,6 +88,7 @@ func inferLanguage(config projectfile.ConfigGetter) (string, string, bool) {
}

func (r *Initialize) Run(params *RunParams) (rerr error) {
defer rationalizeError(&rerr)
logging.Debug("Init: %s/%s %v", params.Namespace.Owner, params.Namespace.Project, params.Private)

if !r.auth.Authenticated() {
Expand Down Expand Up @@ -217,7 +220,29 @@ func (r *Initialize) Run(params *RunParams) (rerr error) {
return err
}

commitID, err := model.CommitInitial(model.HostPlatform, lang.Requirement(), version)
logging.Debug("Creating Platform project")

platformID, err := model.PlatformNameToPlatformID(model.HostPlatform)
if err != nil {
return errs.Wrap(err, "Unable to determine Platform ID from %s", model.HostPlatform)
}

timestamp, err := model.FetchLatestTimeStamp()
if err != nil {
return errs.Wrap(err, "Unable to fetch latest timestamp")
}

bp := model.NewBuildPlannerModel(r.auth)
commitID, err := bp.CreateProject(&model.CreateProjectParams{
Owner: namespace.Owner,
Project: namespace.Project,
PlatformID: strfmt.UUID(platformID),
Language: lang.Requirement(),
Version: version,
Private: params.Private,
Timestamp: *timestamp,
Description: locale.T("commit_message_add_initial"),
})
if err != nil {
return locale.WrapError(err, "err_init_commit", "Could not create initial commit")
}
Expand All @@ -233,23 +258,6 @@ func (r *Initialize) Run(params *RunParams) (rerr error) {
}
}

logging.Debug("Creating Platform project and pushing it")

platformProject, err := model.CreateEmptyProject(namespace.Owner, namespace.Project, params.Private)
if err != nil {
return locale.WrapInputError(err, "err_init_create_project", "Failed to create a Platform project at {{.V0}}.", namespace.String())
}

branch, err := model.DefaultBranchForProject(platformProject) // only one branch for newly created project
if err != nil {
return locale.NewInputError("err_no_default_branch")
}

err = model.UpdateProjectBranchCommitWithModel(platformProject, branch.Label, commitID)
if err != nil {
return locale.WrapError(err, "err_init_push", "Failed to push to the newly created Platform project at {{.V0}}", namespace.String())
}

err = runbits.RefreshRuntime(r.auth, r.out, r.analytics, proj, commitID, true, target.TriggerInit, r.svcModel)
if err != nil {
logging.Debug("Deleting remotely created project due to runtime setup error")
Expand Down
29 changes: 29 additions & 0 deletions internal/runners/initialize/rationalize.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
package initialize

import (
"errors"

"github.com/ActiveState/cli/internal/errs"
"github.com/ActiveState/cli/internal/locale"
bpModel "github.com/ActiveState/cli/pkg/platform/api/buildplanner/model"
)

func rationalizeError(err *error) {
if err == nil {
return
}

pcErr := &bpModel.ProjectCreatedError{}
if !errors.As(*err, &pcErr) {
return
}
switch pcErr.Type {
case bpModel.AlreadyExistsErrorType:
*err = errs.NewUserFacing(locale.Tl("err_create_project_exists", "That project already exists."), errs.SetInput())
case bpModel.ForbiddenErrorType:
*err = errs.NewUserFacing(
locale.Tl("err_create_project_forbidden", "You do not have permission to create that project"),
errs.SetInput(),
errs.SetTips(locale.T("err_init_authenticated")))
}
}
24 changes: 24 additions & 0 deletions pkg/platform/api/buildplanner/model/buildplan.go
Original file line number Diff line number Diff line change
Expand Up @@ -333,6 +333,20 @@ func ProcessProjectError(project *Project, fallbackMessage string) error {
return errs.New(fallbackMessage)
}

type ProjectCreatedError struct {
Type string
Message string
}

func (p *ProjectCreatedError) Error() string { return p.Message }

func ProcessProjectCreatedError(pcErr *projectCreated, fallbackMessage string) error {
if pcErr.Type != "" {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we add a comment here and reference the user-facing errors story? We should be handling the specific error types but I know that's out of scope currently.

return &ProjectCreatedError{pcErr.Type, pcErr.Message}
}
return errs.New(fallbackMessage)
}

type BuildExpression struct {
Type string `json:"__typename"`
Commit *Commit `json:"commit"`
Expand All @@ -355,6 +369,16 @@ type StageCommitResult struct {
Commit *Commit `json:"stageCommit"`
}

type projectCreated struct {
Type string `json:"__typename"`
Commit *Commit `json:"commit"`
*Error
}

type CreateProjectResult struct {
ProjectCreated *projectCreated `json:"createProject"`
}
mitchell-as marked this conversation as resolved.
Show resolved Hide resolved

// Error contains an error message.
type Error struct {
Message string `json:"message"`
Expand Down
57 changes: 57 additions & 0 deletions pkg/platform/api/buildplanner/request/createproject.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
package request

import "github.com/ActiveState/cli/pkg/platform/runtime/buildexpression"

func CreateProject(owner, project string, private bool, expr *buildexpression.BuildExpression, description string) *createProject {
return &createProject{map[string]interface{}{
"organization": owner,
"project": project,
"private": private,
"expr": expr,
"description": description,
}}
}

type createProject struct {
vars map[string]interface{}
}

func (c *createProject) Query() string {
return `
mutation ($organization: String!, $project: String!, $private: Boolean!, $expr: BuildExpr!, $description: String!) {
createProject(input:{organization:$organization, project:$project, private:$private, expr:$expr, description:$description}) {
... on ProjectCreated {
__typename
commit {
__typename
commitId
}
}
... on AlreadyExists {
__typename
message
}
... on NotFound {
__typename
message
}
... on ParseError {
__typename
message
path
}
... on ValidationError {
__typename
message
}
... on Forbidden {
__typename
message
}
}
}`
}

func (c *createProject) Vars() map[string]interface{} {
return c.vars
}
60 changes: 60 additions & 0 deletions pkg/platform/model/buildplanner.go
Original file line number Diff line number Diff line change
Expand Up @@ -349,6 +349,66 @@ func (bp *BuildPlanner) GetBuildExpression(owner, project, commitID string) (*bu
return expression, nil
}

type CreateProjectParams struct {
Owner string
Project string
PlatformID strfmt.UUID
Language string
Version string
Private bool
Timestamp strfmt.DateTime
Description string
}

func (bp *BuildPlanner) CreateProject(params *CreateProjectParams) (strfmt.UUID, error) {
logging.Debug("CreateProject, owner: %s, project: %s, language: %s, version: %s", params.Owner, params.Project, params.Language, params.Version)

// Construct an initial buildexpression for the new project.
expr, err := buildexpression.NewEmpty()
if err != nil {
return "", errs.Wrap(err, "Unable to create initial buildexpression")
}

// Add the platform.
expr.UpdatePlatform(model.OperationAdded, params.PlatformID)

// Create a requirement for the given language and version.
versionRequirements, err := VersionStringToRequirements(params.Version)
if err != nil {
return "", errs.Wrap(err, "Unable to read version")
}
expr.UpdateRequirement(model.OperationAdded, bpModel.Requirement{
Name: params.Language,
Namespace: "language", // TODO: make this a constant DX-1738
VersionRequirement: versionRequirements,
})

// Add the timestamp.
expr.UpdateTimestamp(params.Timestamp)

// Create the project.
request := request.CreateProject(params.Owner, params.Project, params.Private, expr, params.Description)
resp := &bpModel.CreateProjectResult{}
err = bp.client.Run(request, resp)
if err != nil {
return "", processBuildPlannerError(err, "Failed to create project")
}

if resp.ProjectCreated == nil {
return "", errs.New("ProjectCreated is nil")
mitchell-as marked this conversation as resolved.
Show resolved Hide resolved
}

if bpModel.IsErrorResponse(resp.ProjectCreated.Type) {
return "", bpModel.ProcessProjectCreatedError(resp.ProjectCreated, "Could not create project")
}

if resp.ProjectCreated.Commit == nil {
return "", errs.New("ProjectCreated.Commit is nil")
}

return resp.ProjectCreated.Commit.CommitID, nil
}

// processBuildPlannerError will check for special error types that should be
// handled differently. If no special error type is found, the fallback message
// will be used.
Expand Down
29 changes: 28 additions & 1 deletion pkg/platform/runtime/buildexpression/buildexpression.go
Original file line number Diff line number Diff line change
Expand Up @@ -84,7 +84,7 @@ type In struct {
Name *string
}

// NewBuildExpression creates a BuildExpression from a JSON byte array.
// New creates a BuildExpression from a JSON byte array.
// The JSON must be a valid BuildExpression in the following format:
//
// {
Expand Down Expand Up @@ -171,6 +171,33 @@ func New(data []byte) (*BuildExpression, error) {
return expr, nil
}

// NewEmpty creates a minimal, empty buildexpression.
func NewEmpty() (*BuildExpression, error) {
// At this time, there is no way to ask the Platform for an empty buildexpression, so build one
// manually.
expr, err := New([]byte(`
{
"let": {
"runtime": {
"solve_legacy": {
"at_time": "",
"build_flags": [],
"camel_flags": [],
"platforms": [],
"requirements": [],
"solver_version": null
}
},
"in": "$runtime"
}
}
`))
if err != nil {
return nil, errs.Wrap(err, "Unable to create initial buildexpression")
}
return expr, nil
}

func newLet(path []string, m map[string]interface{}) (*Let, error) {
path = append(path, ctxLet)
defer func() {
Expand Down
Loading