diff --git a/cmd/actions.go b/cmd/actions.go index 11491e7..b9bd9cd 100644 --- a/cmd/actions.go +++ b/cmd/actions.go @@ -27,6 +27,7 @@ import ( "time" "github.com/MakeNowJust/heredoc" + "github.com/cli/go-gh" "github.com/pterm/pterm" "github.com/shurcooL/graphql" "github.com/spf13/cobra" @@ -59,6 +60,12 @@ var ( } `graphql:"repositoryOwner(login: $owner)"` } + ActionRepositoryUsesQuery struct { + Repository struct { + ActionUsesRepository + } `graphql:"repository(owner: $owner, name: $repo)"` + } + aur []ActionUsesRepository ce = map[string]bool{ @@ -150,11 +157,28 @@ func init() { // GetActionsReport returns a report on GitHub Actions func GetActionsReport(cmd *cobra.Command, args []string) (err error) { + sp.Start() + if repo != "" { - return fmt.Errorf("Repository not (yet) supported for this report") - } + cr, _ := gh.CurrentRepository() - sp.Start() + o := cr.Owner() + r := cr.Name() + + variables := map[string]interface{}{ + "owner": graphql.String(o), + "repo": graphql.String(r), + "ref": graphql.String("HEAD:.github/workflows"), + } + + sp.Suffix = fmt.Sprintf( + " fetching actions report for %s", + utils.HiBlack(o+"/"+r), + ) + + graphqlClient.Query("ActionUses", &ActionRepositoryUsesQuery, variables) + aur = append(aur, ActionRepositoryUsesQuery.Repository.ActionUsesRepository) + } if enterprise != "" { variables := map[string]interface{}{ @@ -174,173 +198,175 @@ func GetActionsReport(cmd *cobra.Command, args []string) (err error) { } } - if owner != "" { + if owner != "" && repo == "" { organizations = append(organizations, Organization{Login: owner}) } var res = []ActionUsesReport{} - for _, o := range organizations { - owner = o.Login - variables := map[string]interface{}{ - "owner": graphql.String(owner), - "page": (*graphql.String)(nil), - "ref": graphql.String("HEAD:.github/workflows"), - } + if len(organizations) > 0 { + for _, o := range organizations { + owner = o.Login + variables := map[string]interface{}{ + "owner": graphql.String(owner), + "page": (*graphql.String)(nil), + "ref": graphql.String("HEAD:.github/workflows"), + } - var i = 1 - for { - sp.Suffix = fmt.Sprintf( - " fetching actions report %s %s", - utils.Cyan(owner), - utils.HiBlack(fmt.Sprintf("(page %d)", i)), - ) + var i = 1 + for { + sp.Suffix = fmt.Sprintf( + " fetching actions report %s %s", + utils.Cyan(owner), + utils.HiBlack(fmt.Sprintf("(page %d)", i)), + ) - graphqlClient.Query("ActionUses", &ActionUsesQuery, variables) - aur = append(aur, ActionUsesQuery.RepositoryOwner.Repositories.Nodes...) + graphqlClient.Query("ActionUses", &ActionUsesQuery, variables) + aur = append(aur, ActionUsesQuery.RepositoryOwner.Repositories.Nodes...) - if !ActionUsesQuery.RepositoryOwner.Repositories.PageInfo.HasNextPage { - break + if !ActionUsesQuery.RepositoryOwner.Repositories.PageInfo.HasNextPage { + break + } + + // sleep for 1 second to avoid rate limiting + time.Sleep(1 * time.Second) + + variables["page"] = &ActionUsesQuery.RepositoryOwner.Repositories.PageInfo.EndCursor + i++ } + } + } - // sleep for 1 second to avoid rate limiting - time.Sleep(1 * time.Second) + for _, r := range aur { + // skip if repo is archived or fork + if r.IsArchived || r.IsFork { + continue + } - variables["page"] = &ActionUsesQuery.RepositoryOwner.Repositories.PageInfo.EndCursor - i++ + // skip if repo has no workflows + if len(r.Object.Tree.Entries) == 0 { + continue } - for _, r := range aur { - // skip if repo is archived or fork - if r.IsArchived || r.IsFork { + var wfs = []ActionWorkflow{} + for _, e := range r.Object.Tree.Entries { + // skip if not a yml|yaml file + if _, ok := ce[e.Extension]; !ok { continue } - // skip if repo has no workflows - if len(r.Object.Tree.Entries) == 0 { - continue + text := e.Object.Blob.Text + + // get Action uses + var wu WorkflowUses + if err := yaml.Unmarshal([]byte(text), &wu); err != nil && !silent { + fmt.Println( + utils.Red( + fmt.Sprintf( + "\nerror: parsing https://%s/%s/blob/HEAD/%s", + hostname, + r.NameWithOwner, e.Path, + ), + ), + ) } - var wfs = []ActionWorkflow{} - for _, e := range r.Object.Tree.Entries { - // skip if not a yml|yaml file - if _, ok := ce[e.Extension]; !ok { - continue - } - - text := e.Object.Blob.Text - - // get Action uses - var wu WorkflowUses - if err := yaml.Unmarshal([]byte(text), &wu); err != nil && !silent { - fmt.Println( - utils.Red( - fmt.Sprintf( - "\nerror: parsing https://%s/%s/blob/HEAD/%s", + var uses []ActionUses + for _, job := range wu.Jobs { + for _, step := range job.Steps { + if step.Uses != "" && excludeGitHubAuthored(step.Uses) { + a := strings.Split(step.Uses, "@") + + var an string + var av string + var url string + + an = a[0] + if len(a) == 2 { + av = a[1] + url = fmt.Sprintf( + "https://%s/%s/tree/%s", hostname, - r.NameWithOwner, e.Path, - ), - ), - ) - } + an, + av, + ) + } else { + url = fmt.Sprintf( + "https://%s/%s/tree/HEAD", + hostname, + an, + ) + } - var uses []ActionUses - for _, job := range wu.Jobs { - for _, step := range job.Steps { - if step.Uses != "" && excludeGitHubAuthored(step.Uses) { - a := strings.Split(step.Uses, "@") - - var an string - var av string - var url string - - an = a[0] - if len(a) == 2 { - av = a[1] - url = fmt.Sprintf( - "https://%s/%s/tree/%s", - hostname, - an, - av, - ) - } else { - url = fmt.Sprintf( - "https://%s/%s/tree/HEAD", - hostname, - an, - ) - } - - if strings.Contains(url, "./") { - url = fmt.Sprintf( - "https://%s/%s/%s/tree/HEAD/%s", - hostname, - r.Owner.Login, - r.Name, - strings.ReplaceAll(an, "./", ""), - ) - } - - uses = append(uses, ActionUses{ - Action: an, - Version: av, - URL: url, - }) + if strings.Contains(url, "./") { + url = fmt.Sprintf( + "https://%s/%s/%s/tree/HEAD/%s", + hostname, + r.Owner.Login, + r.Name, + strings.ReplaceAll(an, "./", ""), + ) } + + uses = append(uses, ActionUses{ + Action: an, + Version: av, + URL: url, + }) } } + } - // get Action permissions - var wp ActionPermissions - if err := yaml.Unmarshal([]byte(text), &wp); err != nil && !silent { - fmt.Println( - utils.Red( - fmt.Sprintf( - "\nerror: parsing https://%s/%s/blob/HEAD/%s", - hostname, - r.NameWithOwner, e.Path, - ), + // get Action permissions + var wp ActionPermissions + if err := yaml.Unmarshal([]byte(text), &wp); err != nil && !silent { + fmt.Println( + utils.Red( + fmt.Sprintf( + "\nerror: parsing https://%s/%s/blob/HEAD/%s", + hostname, + r.NameWithOwner, e.Path, ), - ) - } - - var permissions []string - // if permissions are defined at the workflow level - if wp.Permissions != nil { - permissions = append(permissions, getPermissions(wp.Permissions)...) - } + ), + ) + } - // if permissions are defined at the job level - for _, job := range wp.Jobs { - permissions = append(permissions, getPermissions(job.Permissions)...) - } + var permissions []string + // if permissions are defined at the workflow level + if wp.Permissions != nil { + permissions = append(permissions, getPermissions(wp.Permissions)...) + } - // put it all together - wfs = append(wfs, ActionWorkflow{ - Path: e.Path, - URL: fmt.Sprintf( - "https://%s/%s/%s/blob/HEAD/%s", - hostname, - r.Owner.Login, - r.Name, - e.Path, - ), - Uses: uniqueUses(uses), - Permissions: uniquePermissions(permissions), - }) + // if permissions are defined at the job level + for _, job := range wp.Jobs { + permissions = append(permissions, getPermissions(job.Permissions)...) } - res = append(res, ActionUsesReport{ - Owner: r.Owner.Login, - Repo: r.Name, - Workflows: wfs, + // put it all together + wfs = append(wfs, ActionWorkflow{ + Path: e.Path, + URL: fmt.Sprintf( + "https://%s/%s/%s/blob/HEAD/%s", + hostname, + r.Owner.Login, + r.Name, + e.Path, + ), + Uses: uniqueUses(uses), + Permissions: uniquePermissions(permissions), }) } - // sleep for 1 second to avoid rate limiting - time.Sleep(1 * time.Second) + res = append(res, ActionUsesReport{ + Owner: r.Owner.Login, + Repo: r.Name, + Workflows: wfs, + }) } + // sleep for 1 second to avoid rate limiting + time.Sleep(1 * time.Second) + sp.Stop() var td = pterm.TableData{