@@ -10,10 +10,12 @@ import (
1010 "os"
1111 "os/signal"
1212 "path/filepath"
13+ "strings"
1314 "time"
1415
1516 "github.com/fatih/color"
1617 "github.com/hashicorp/go-slug"
18+ "gopkg.in/yaml.v3"
1719
1820 "github.com/urfave/cli/v2"
1921)
@@ -28,7 +30,10 @@ const (
2830 folderPathWarningMsg = "The workspace is configured with the folder path %s,\n Harness will upload the following directory and its contents: \n %s"
2931 noFolderPathWarningMsg = "The workspace has no configured folder path,\n Harness will upload the following directory and its contents \n %s"
3032 folderPathNotFoundErr = "The folder path configured in the workspace %s does not exist in the current directory"
31- folderPathErr = "An error occurred when trying to find the folder path in the current directory: %v"
33+ folderPathErr = "An error occurred when trying to find the repo root from the current current directory: %v"
34+
35+ workspaceInfoCliArgErr = "When supplying workspace info via CLI arguments the org-id, project-id and workspace-id must all be present"
36+ workspaceInfoFileErr = "No workspace.yaml file present in the .harness folder in the current directory, consider creating one or supplying workspace info via cli argument"
3237
3338 startingStepMsg = "========================== Starting step %s ==========================\n "
3439 startingStageMsg = "========================== Starting stage %s ==========================\n "
@@ -48,6 +53,12 @@ type LogClient interface {
4853 Blob (ctx context.Context , key string ) error
4954}
5055
56+ type WorkspaceInfo struct {
57+ Org string `yaml:"org"`
58+ Project string `yaml:"project"`
59+ Workspace string `yaml:"workspace"`
60+ }
61+
5162type IacmCommand struct {
5263 client IacmClient
5364 logClient LogClient
@@ -69,8 +80,20 @@ func NewIacmCommand(account string, client IacmClient, logClient LogClient) Iacm
6980}
7081
7182func (c IacmCommand ) executePlan (ctx context.Context ) error {
72- fmt .Printf ("Fetching workspace %s... \n " , c .workspace )
73- ws , err := c .client .GetWorkspace (ctx , c .org , c .project , c .workspace )
83+ wd , err := os .Getwd ()
84+ if err != nil {
85+ fmt .Println (utils .GetColoredText (err .Error (), color .FgRed ))
86+ return err
87+ }
88+
89+ workspaceInfo , err := getWorkspaceInfo (c .org , c .project , c .workspace , wd )
90+ if err != nil {
91+ fmt .Println (utils .GetColoredText (err .Error (), color .FgRed ))
92+ return err
93+ }
94+
95+ fmt .Printf ("Fetching workspace %s... \n " , workspaceInfo .Workspace )
96+ ws , err := c .client .GetWorkspace (ctx , workspaceInfo .Org , workspaceInfo .Project , workspaceInfo .Workspace )
7497 if err != nil {
7598 fmt .Printf ("An error occurred when fetching the workspace: %v \n " , err )
7699 return err
@@ -86,12 +109,7 @@ func (c IacmCommand) executePlan(ctx context.Context) error {
86109 utils .GetColoredText (defaultPipeline , color .FgCyan ),
87110 )
88111
89- wd , err := os .Getwd ()
90- if err != nil {
91- return err
92- }
93-
94- warning , err := validateWorkingDirectory (wd , ws )
112+ repoRoot , warning , err := getRepoRootFromWorkingDirectory (wd , ws )
95113 if err != nil {
96114 fmt .Println (utils .GetColoredText (err .Error (), color .FgRed ))
97115 return err
@@ -110,7 +128,7 @@ func (c IacmCommand) executePlan(ctx context.Context) error {
110128 }
111129
112130 archive := bytes .NewBuffer ([]byte {})
113- _ , err = packer .Pack (wd , archive )
131+ _ , err = packer .Pack (repoRoot , archive )
114132 if err != nil {
115133 return err
116134 }
@@ -122,26 +140,26 @@ func (c IacmCommand) executePlan(ctx context.Context) error {
122140 if len (c .targets ) > 0 {
123141 customArguments ["target" ] = c .targets
124142 }
125- plan , err := c .client .CreateRemoteExecution (ctx , c . org , c . project , c . workspace , customArguments )
143+ plan , err := c .client .CreateRemoteExecution (ctx , ws . Org , ws . Project , ws . Identifier , customArguments )
126144 if err != nil {
127145 fmt .Println (utils .GetColoredText (fmt .Sprintf ("An error occurred creating the remote execution: %v" , err .Error ()), color .FgRed ))
128146 return err
129147 }
130148
131- plan , err = c .client .UploadRemoteExecution (ctx , c . org , c . project , c . workspace , plan .ID , archive .Bytes ())
149+ plan , err = c .client .UploadRemoteExecution (ctx , ws . Org , ws . Project , ws . Identifier , plan .ID , archive .Bytes ())
132150 if err != nil {
133151 fmt .Println (utils .GetColoredText (fmt .Sprintf ("An error occurred uploading the source code: %v" , err .Error ()), color .FgRed ))
134152 return err
135153 }
136154
137- plan , err = c .client .ExecuteRemoteExecution (ctx , c . org , c . project , c . workspace , plan .ID )
155+ plan , err = c .client .ExecuteRemoteExecution (ctx , ws . Org , ws . Project , ws . Identifier , plan .ID )
138156 if err != nil {
139157 fmt .Println (utils .GetColoredText (fmt .Sprintf ("An error occurred executing the pipeline: %v" , err .Error ()), color .FgRed ))
140158 return err
141159 }
142160 fmt .Printf ("Pipeline execution: %s\n " , utils .GetColoredText (plan .PipelineExecutionURL , color .FgCyan ))
143161
144- startingNodeID , err := c .getStartingNodeID (ctx , c . org , c . project , plan .PipelineExecutionID )
162+ startingNodeID , err := c .getStartingNodeID (ctx , ws . Org , ws . Project , plan .PipelineExecutionID )
145163 if err != nil {
146164 fmt .Println (utils .GetColoredText (fmt .Sprintf ("An error occurred fetching starting node id: %v" , err .Error ()), color .FgRed ))
147165 return err
@@ -202,22 +220,79 @@ func getDefaultPipeline(defaultPipelines map[string]*client.DefaultPipelineOverr
202220 return "" , err
203221}
204222
205- func validateWorkingDirectory (workingDirectory string , workspace * client.Workspace ) (string , error ) {
206- if workspace .RepositoryPath != "" {
207- _ , err := os .Stat (filepath .Join (workingDirectory , workspace .RepositoryPath ))
223+ func getRepoRootFromWorkingDirectory (workingDirectory string , workspace * client.Workspace ) (string , string , error ) {
224+ if workspace .RepositoryPath == "" {
225+ return workingDirectory , fmt .Sprintf (noFolderPathWarningMsg , utils .GetColoredText (workingDirectory , color .FgCyan )), nil
226+ }
227+
228+ workingDirectory = filepath .Clean (workingDirectory )
229+ repositoryPath := filepath .Clean (workspace .RepositoryPath )
230+ // if the working directory is the same as the configured workspace repository
231+ // path we trim the repository path from the working directory to find the repo
232+ // root
233+ if strings .HasSuffix (workingDirectory , repositoryPath ) {
234+ repoRoot := strings .TrimSuffix (workingDirectory , workspace .RepositoryPath )
235+ repoRoot = filepath .Clean (repoRoot )
236+ _ , err := os .Stat (repoRoot )
208237 if err != nil {
209- if os .IsNotExist (err ) {
210- return "" , fmt .Errorf (folderPathNotFoundErr , workspace .RepositoryPath )
211- }
212- return "" , fmt .Errorf (folderPathErr , err )
238+ return "" , "" , fmt .Errorf (folderPathErr , err )
213239 }
214- return fmt .Sprintf (
240+ return repoRoot ,
241+ fmt .Sprintf (
242+ folderPathWarningMsg ,
243+ utils .GetColoredText (repositoryPath , color .FgCyan ),
244+ utils .GetColoredText (repoRoot , color .FgCyan ),
245+ ), nil
246+ }
247+
248+ // the working directory is not the repository path so we try and find
249+ // the repository path within the working directory and if found use the
250+ // working directory as the repo root
251+ _ , err := os .Stat (filepath .Join (workingDirectory , repositoryPath ))
252+ if os .IsNotExist (err ) {
253+ return "" , "" , fmt .Errorf (folderPathNotFoundErr , repositoryPath )
254+ }
255+
256+ return workingDirectory ,
257+ fmt .Sprintf (
215258 folderPathWarningMsg ,
216- utils .GetColoredText (workspace . RepositoryPath , color .FgCyan ),
259+ utils .GetColoredText (repositoryPath , color .FgCyan ),
217260 utils .GetColoredText (workingDirectory , color .FgCyan ),
218261 ), nil
262+ }
263+
264+ // getWorkspaceInfo returns the values supplied directly to the cli if they are present
265+ // and falls back to a config file in .harness/workspace.yaml if not
266+ func getWorkspaceInfo (org , project , workspace , workingDirectory string ) (* WorkspaceInfo , error ) {
267+ if org != "" && project != "" && workspace != "" {
268+ return & WorkspaceInfo {
269+ Org : org ,
270+ Project : project ,
271+ Workspace : workspace ,
272+ }, nil
273+ }
274+ if org == "" && (project != "" || workspace != "" ) {
275+ return nil , errors .New (workspaceInfoCliArgErr )
276+ }
277+ if project == "" && (org != "" || workspace != "" ) {
278+ return nil , errors .New (workspaceInfoCliArgErr )
279+ }
280+ if workspace == "" && (org != "" || project != "" ) {
281+ return nil , errors .New (workspaceInfoCliArgErr )
282+ }
283+
284+ file , err := os .ReadFile (filepath .Join (workingDirectory , ".harness/workspace.yaml" ))
285+ if err != nil {
286+ if os .IsNotExist (err ) {
287+ return nil , errors .New (workspaceInfoFileErr )
288+ }
289+ }
290+ workspaceInfo := & WorkspaceInfo {}
291+ err = yaml .Unmarshal (file , workspaceInfo )
292+ if err != nil {
293+ return nil , err
219294 }
220- return fmt . Sprintf ( noFolderPathWarningMsg , utils . GetColoredText ( workingDirectory , color . FgCyan )) , nil
295+ return workspaceInfo , nil
221296}
222297
223298func (c * IacmCommand ) getStartingNodeID (ctx context.Context , org , project , executionID string ) (string , error ) {
0 commit comments