github.com/ActiveState/cli@v0.0.0-20240508170324-6801f60cd051/scripts/start-story/main.go (about)

     1  package main
     2  
     3  import (
     4  	"os"
     5  	"strings"
     6  
     7  	"github.com/ActiveState/cli/internal/environment"
     8  	"github.com/ActiveState/cli/internal/errs"
     9  	"github.com/ActiveState/cli/internal/osutils"
    10  	wc "github.com/ActiveState/cli/scripts/internal/workflow-controllers"
    11  	wh "github.com/ActiveState/cli/scripts/internal/workflow-helpers"
    12  	"github.com/andygrunwald/go-jira"
    13  	"github.com/blang/semver"
    14  	"github.com/google/go-github/v45/github"
    15  	"github.com/thoas/go-funk"
    16  )
    17  
    18  func main() {
    19  	if err := run(); err != nil {
    20  		wc.Print("Error: %s\n", errs.JoinMessage(err))
    21  	}
    22  }
    23  
    24  type Meta struct {
    25  	Version           semver.Version
    26  	JiraIssue         *jira.Issue
    27  	JiraVersion       string
    28  	VersionPRName     string
    29  	VersionBranchName string
    30  	VersionPR         *github.PullRequest
    31  }
    32  
    33  func (m Meta) GetVersion() semver.Version {
    34  	return m.Version
    35  }
    36  
    37  func (m Meta) GetJiraVersion() string {
    38  	return m.JiraVersion
    39  }
    40  
    41  func (m Meta) GetVersionBranchName() string {
    42  	return m.VersionBranchName
    43  }
    44  
    45  func (m Meta) GetVersionPRName() string {
    46  	return m.VersionPRName
    47  }
    48  
    49  func run() error {
    50  	finish := wc.PrintStart("Initializing clients")
    51  	// Initialize Clients
    52  	ghClient := wh.InitGHClient()
    53  	jiraClient, err := wh.InitJiraClient()
    54  	if err != nil {
    55  		return errs.Wrap(err, "failed to initialize Jira client")
    56  	}
    57  	finish()
    58  
    59  	finish = wc.PrintStart("Verifying input")
    60  	// Grab input
    61  	if len(os.Args) < 2 {
    62  		return errs.New("Usage: start-story <story-id> [branch-name]")
    63  	}
    64  	jiraIssueID := os.Args[1]
    65  
    66  	branchName := jiraIssueID
    67  	if len(os.Args) > 2 {
    68  		branchName = os.Args[1]
    69  		detectedIssueID, err := wh.ParseJiraKey(branchName)
    70  		if err != nil {
    71  			return errs.Wrap(err, "failed to parse Jira key from branch name")
    72  		}
    73  		if !strings.EqualFold(detectedIssueID, jiraIssueID) {
    74  			return errs.New("Branch name contains story ID %s, but story being targeted is %s", detectedIssueID, jiraIssueID)
    75  		}
    76  	}
    77  	finish()
    78  
    79  	finish = wc.PrintStart("Fetching meta for story %s", jiraIssueID)
    80  	// Collect meta information about the PR and all it's related resources
    81  	meta, err := fetchMeta(ghClient, jiraClient, jiraIssueID)
    82  	if err != nil {
    83  		return errs.Wrap(err, "failed to fetch meta")
    84  	}
    85  	finish()
    86  
    87  	ref := ""
    88  	if meta.VersionPR != nil {
    89  		ref = meta.VersionPR.Head.GetRef()
    90  	} else {
    91  		finish := wc.PrintStart("Detecting base ref to fork from")
    92  		ref, err = wc.DetectBaseRef(ghClient, jiraClient, meta)
    93  		if err != nil {
    94  			return errs.Wrap(err, "failed to detect base ref")
    95  		}
    96  		finish()
    97  	}
    98  
    99  	finish = wc.PrintStart("Creating branch: %s from ref: %s", branchName, ref)
   100  	if os.Getenv("DRYRUN") == "true" {
   101  		wc.Print("DRY RUN: Skipping")
   102  		finish()
   103  		return nil
   104  	}
   105  
   106  	stdout, stderr, err := osutils.ExecSimpleFromDir(environment.GetRootPathUnsafe(), "git", []string{"checkout", ref}, nil)
   107  	if err != nil {
   108  		return errs.Wrap(err, "failed to checkout base ref, stdout:\n%s\nstderr:\n%s", stdout, stderr)
   109  	}
   110  	stdout, stderr, err = osutils.ExecSimpleFromDir(environment.GetRootPathUnsafe(), "git", []string{"pull", "--rebase"}, nil)
   111  	if err != nil {
   112  		return errs.Wrap(err, "failed to pull latest changes, stdout:\n%s\nstderr:\n%s", stdout, stderr)
   113  	}
   114  	stdout, stderr, err = osutils.ExecSimpleFromDir(environment.GetRootPathUnsafe(), "git", []string{"branch", branchName}, nil)
   115  	if err != nil {
   116  		return errs.Wrap(err, "failed to create branch, stdout:\n%s\nstderr:\n%s", stdout, stderr)
   117  	}
   118  	stdout, stderr, err = osutils.ExecSimpleFromDir(environment.GetRootPathUnsafe(), "git", []string{"checkout", branchName}, nil)
   119  	if err != nil {
   120  		return errs.Wrap(err, "failed to checkout branch, stdout:\n%s\nstderr:\n%s", stdout, stderr)
   121  	}
   122  	finish()
   123  
   124  	if meta.JiraIssue.Fields.Status.Name == wh.JiraStatusTodo || meta.JiraIssue.Fields.Status.Name == wh.JiraStatusPending {
   125  		finish = wc.PrintStart("Updating jira issue to In Progress")
   126  		if err := wh.UpdateJiraStatus(jiraClient, meta.JiraIssue, wh.JiraStatusInProgress); err != nil {
   127  			return errs.Wrap(err, "failed to update Jira status")
   128  		}
   129  		finish()
   130  	}
   131  
   132  	wc.Print("All Done")
   133  
   134  	return nil
   135  }
   136  
   137  func fetchMeta(ghClient *github.Client, jiraClient *jira.Client, jiraIssueID string) (Meta, error) {
   138  	// Retrieve Relevant Jira Issue for PR being handled
   139  	finish := wc.PrintStart("Fetching Jira issue")
   140  	jiraIssue, err := wh.FetchJiraIssue(jiraClient, jiraIssueID)
   141  	if err != nil {
   142  		return Meta{}, errs.Wrap(err, "failed to get Jira issue")
   143  	}
   144  	finish()
   145  
   146  	if len(jiraIssue.Fields.FixVersions) == 0 || jiraIssue.Fields.FixVersions[0] == nil {
   147  		return Meta{}, errs.New("Jira issue does not have a fix version")
   148  	}
   149  	fixVersion := jiraIssue.Fields.FixVersions[0]
   150  
   151  	if fixVersion.Archived != nil && *fixVersion.Archived || fixVersion.Released != nil && *fixVersion.Released {
   152  		return Meta{}, errs.New("Target issue has fixVersion '%s', which has either been archived or released\n", fixVersion.Name)
   153  	}
   154  
   155  	if !funk.ContainsString([]string{wh.JiraStatusTodo, wh.JiraStatusInProgress, wh.JiraStatusPending}, jiraIssue.Fields.Status.Name) {
   156  		return Meta{}, errs.New("Story is in the %s state, but only '%s', '%s' and '%s' are valid states to start a story from.",
   157  			jiraIssue.Fields.Status.Name, wh.JiraStatusTodo, wh.JiraStatusInProgress, wh.JiraStatusPending)
   158  	}
   159  
   160  	finish = wc.PrintStart("Fetching Jira Versions")
   161  	availableVersions, err := wh.FetchAvailableVersions(jiraClient)
   162  	if err != nil {
   163  		return Meta{}, errs.Wrap(err, "Failed to fetch JIRA issue")
   164  	}
   165  	finish()
   166  
   167  	finish = wc.PrintStart("Parsing version")
   168  	version, jiraVersion, err := wh.ParseTargetFixVersion(jiraIssue, availableVersions)
   169  	if err != nil {
   170  		return Meta{}, errs.Wrap(err, "failed to parse version")
   171  	}
   172  	finish()
   173  
   174  	var versionPR *github.PullRequest
   175  	var versionPRName string
   176  	if version.NE(wh.VersionMaster) {
   177  		versionPRName = wh.VersionedPRTitle(version.Version)
   178  
   179  		// Retrieve Relevant Fixversion Pr
   180  		finish = wc.PrintStart("Checking if Version PR with title '%s' exists", versionPRName)
   181  		versionPR, err = wh.FetchPRByTitle(ghClient, versionPRName)
   182  		if err != nil {
   183  			return Meta{}, errs.Wrap(err, "failed to get target PR")
   184  		}
   185  		wc.Print("Exists: %v", versionPR != nil)
   186  		finish()
   187  	}
   188  
   189  	return Meta{
   190  		Version:           version.Version,
   191  		JiraIssue:         jiraIssue,
   192  		JiraVersion:       jiraVersion.Name,
   193  		VersionPR:         versionPR,
   194  		VersionPRName:     versionPRName,
   195  		VersionBranchName: wh.VersionedBranchName(version.Version),
   196  	}, nil
   197  }