From e01e23e6a7ab382f6888ef412616eca91162a6a5 Mon Sep 17 00:00:00 2001 From: samueljmello <6161359+samueljmello@users.noreply.github.com> Date: Mon, 24 Jul 2023 21:01:40 -0400 Subject: [PATCH 1/8] get a bunch of stuff working --- main.go | 174 ++++++++++++++++++++++++++++++++++++++------------------ 1 file changed, 120 insertions(+), 54 deletions(-) diff --git a/main.go b/main.go index d864d2e..d223ca8 100644 --- a/main.go +++ b/main.go @@ -6,6 +6,7 @@ import ( "encoding/json" "errors" "fmt" + "net/url" "os" "regexp" "strconv" @@ -27,6 +28,7 @@ import ( var ( // flag vars AutoConfirm = false + CreateIssues = false GithubSourceOrg string GithubTargetOrg string ApiUrl string @@ -39,18 +41,19 @@ var ( ) // tool vars - DefaultApiUrl string = "github.com" - SourceRestClient api.RESTClient - TargetRestClient api.RESTClient - SourceGraphqlClient api.GQLClient - TargetGraphqlClient api.GQLClient - SourceRepositories []repository = []repository{} - TargetRepositories []repository = []repository{} - ToProcessRepositories []repository = []repository{} - LogFile *os.File - Threads int - ResultsTable pterm.TableData - WaitGroup sync.WaitGroup + DefaultApiUrl string = "github.com" + DefaultIssueTitleTemplate string = "Post Migration Assessment" + SourceRestClient api.RESTClient + TargetRestClient api.RESTClient + SourceGraphqlClient api.GQLClient + TargetGraphqlClient api.GQLClient + SourceRepositories []repository = []repository{} + TargetRepositories []repository = []repository{} + ToProcessRepositories []repository = []repository{} + LogFile *os.File + Threads int + ResultsTable pterm.TableData + WaitGroup sync.WaitGroup // Create some colors and a spinner Red = color.New(color.FgRed).SprintFunc() @@ -92,6 +95,7 @@ type apiResponse struct { } type environments struct { Environments []environment + Total_Count int } type environment struct { Name string @@ -117,24 +121,27 @@ type repository struct { Visibility string TargetVisibility string ExistsInTarget bool - Secrets int - Variables int - Environments int + Secrets secrets + Variables variables + Environments environments } type organization struct { Login string } type secrets struct { - Secrets []secret + Secrets []secret + Total_Count int } type secret struct { Name string } type variables struct { - Variables []secret + Variables []secret + Total_Count int } type variable struct { - Name string + Name string + Value string } type user struct { Login string @@ -192,7 +199,14 @@ func init() { &AutoConfirm, "confirm", false, - "Auto respond to confirmation prompt", + "Auto respond to visibility alignment confirmation prompt", + ) + rootCmd.PersistentFlags().BoolVarP( + &CreateIssues, + "create-issues", + "c", + false, + "Whether to create issues in target org repositories or not.", ) // make certain flags required @@ -440,7 +454,7 @@ func Process(cmd *cobra.Command, args []string) (err error) { } OutputFlags("Read Threads", fmt.Sprintf("%d", Threads)) LF() - Debug("---- LISTING REPOSITORIES ----") + Debug("---- SETTING UP API CLIENTS ----") // set up clients opts := GetOpts(ApiUrl, GithubSourcePat) @@ -469,28 +483,21 @@ func Process(cmd *cobra.Command, args []string) (err error) { OutputError("Failed set set up target GraphQL client.", true) } - Spinner.Start() + Debug("---- LOOKING UP REPOSITORIES IN ORGS ----") - // determine how many concurrent lookups can take place - lookGroups := 1 - if IsTargetProvided() { - lookGroups = 2 - } - WaitGroup.Add(lookGroups) + Spinner.Start() - // get source (and possible target) repositories + // thread getting repos + WaitGroup.Add(2) go GetSourceRepositories() if err != nil { Debug(fmt.Sprint("Error object: ", err)) OutputError("Failed to get source repositories.", true) } - - if IsTargetProvided() { - go GetTargetRepositories() - if err != nil { - Debug(fmt.Sprint("Error object: ", err)) - OutputError("Failed to get target repositories.", true) - } + go GetTargetRepositories() + if err != nil { + Debug(fmt.Sprint("Error object: ", err)) + OutputError("Failed to get target repositories.", true) } WaitGroup.Wait() @@ -607,11 +614,27 @@ func Process(cmd *cobra.Command, args []string) (err error) { } } + // create issues if we need to + if CreateIssues { + DebugAndStatus("Attempting to create issues for repositories...") + err = ProcessIssues(SourceRestClient, GithubTargetOrg, SourceRepositories) + if err != nil { + return err + } + } + // prompt for fixing if len(ToProcessRepositories) > 0 { + + // find out if we need to process visibility + repoWord := "repository" + if len(ToProcessRepositories) > 1 { + repoWord = "repositories" + } proceedMessage := Debug(fmt.Sprintf( - "Do you want to align repository visibilities for %d repositories?", + "Do you want to align visibility for %d %s?", len(ToProcessRepositories), + repoWord, )) // auto confirm @@ -619,13 +642,13 @@ func Process(cmd *cobra.Command, args []string) (err error) { if !AutoConfirm { c, err = AskForConfirmation(Yellow(proceedMessage)) } - - // fail if something goes wrong if err != nil { OutputError(err.Error(), true) } else if !c { // warn when manually abandoned + LF() OutputWarning("Alignment process abandoned.") + LF() return err } @@ -734,7 +757,6 @@ func GetRepositoryStatistics(client api.RESTClient, repoToProcess repository) { start := time.Now() // get number of secrets - secretCount := 0 var secretsResponse secrets secretsErr := client.Get( fmt.Sprintf( @@ -751,8 +773,7 @@ func GetRepositoryStatistics(client api.RESTClient, repoToProcess repository) { if secretsErr != nil { ExitManual(secretsErr) } else { - secretCount = len(secretsResponse.Secrets) - repoToProcess.Secrets = secretCount + repoToProcess.Secrets = secretsResponse } // validate we have API attempts left @@ -763,7 +784,6 @@ func GetRepositoryStatistics(client api.RESTClient, repoToProcess repository) { } // get number of variables - variableCount := 0 var variablesResponse variables variablesErr := client.Get( fmt.Sprintf( @@ -780,8 +800,7 @@ func GetRepositoryStatistics(client api.RESTClient, repoToProcess repository) { if variablesErr != nil { ExitManual(variablesErr) } else { - variableCount = len(variablesResponse.Variables) - repoToProcess.Variables = variableCount + repoToProcess.Variables = variablesResponse } // validate we have API attempts left @@ -791,7 +810,6 @@ func GetRepositoryStatistics(client api.RESTClient, repoToProcess repository) { } // get number of variables - envCount := 0 var envResponse environments envsErr := client.Get( fmt.Sprintf( @@ -808,8 +826,7 @@ func GetRepositoryStatistics(client api.RESTClient, repoToProcess repository) { if envsErr != nil { ExitManual(envsErr) } else { - envCount = len(envResponse.Environments) - repoToProcess.Environments = envCount + repoToProcess.Environments = envResponse } // find if repo exists in target @@ -847,23 +864,26 @@ func GetRepositoryStatistics(client api.RESTClient, repoToProcess repository) { // write to table for output visiblity := fmt.Sprintf( - "%s|%s", + "%s", repoToProcess.Visibility, - repoToProcess.TargetVisibility, ) existsInTarget := strconv.FormatBool(repoToProcess.ExistsInTarget) if !repoToProcess.ExistsInTarget { existsInTarget = Red(existsInTarget) } else if repoToProcess.Visibility != TargetRepositories[targetIdx].Visibility { - visiblity = Yellow(visiblity) + visiblity = fmt.Sprintf( + "%s|%s", + visiblity, + Yellow(repoToProcess.TargetVisibility), + ) } ResultsTable = append(ResultsTable, []string{ repoToProcess.NameWithOwner, existsInTarget, - visiblity, - fmt.Sprintf("%d", secretCount), - fmt.Sprintf("%d", variableCount), - fmt.Sprintf("%d", envCount), + strings.ToLower(visiblity), + fmt.Sprintf("%d", secretsResponse.Total_Count), + fmt.Sprintf("%d", variablesResponse.Total_Count), + fmt.Sprintf("%d", envResponse.Total_Count), }) // delay if write was fast @@ -968,6 +988,52 @@ func GetRepositories(restClient api.RESTClient, graphqlClient api.GQLClient, own return repoLookup, err } +func ProcessIssues(client api.RESTClient, targetOrg string, reposToProcess []repository) (err error) { + + var response interface{} + for _, repository := range reposToProcess { + + if !repository.ExistsInTarget { + Debug( + fmt.Sprintf( + "Skipped %s because it did not exist in the target org.", + repository.Name, + ), + ) + continue + } + + // validate rate + _, err := ValidateApiRate(client, "core") + if err != nil { + return err + } + + query := url.QueryEscape( + fmt.Sprintf( + "%s repo:%s/%s in:title", + DefaultIssueTitleTemplate, + targetOrg, + repository.Name, + ), + ) + Debug(fmt.Sprintf("Using search string: %s", query)) + err = client.Get( + fmt.Sprintf( + "search/issues?q=%s", + query, + ), + &response, + ) + Debug(fmt.Sprintf("Response from GET: %v", response)) + if err != nil { + return err + } + } + + return err +} + func ProcessRepositoryVisibilities(client api.RESTClient, targetOrg string, reposToProcess []repository) (err error) { var response interface{} From a180f655dbcfa2469e24a23a44425a16c3653b9e Mon Sep 17 00:00:00 2001 From: samueljmello <6161359+samueljmello@users.noreply.github.com> Date: Mon, 24 Jul 2023 21:57:44 -0400 Subject: [PATCH 2/8] Get create/update working --- main.go | 162 ++++++++++++++++++++++++++++++++++++++++++++------------ 1 file changed, 128 insertions(+), 34 deletions(-) diff --git a/main.go b/main.go index d223ca8..0902d9f 100644 --- a/main.go +++ b/main.go @@ -100,6 +100,16 @@ type environments struct { type environment struct { Name string } +type issues struct { + Items []issue + Total_Count int +} +type issue struct { + Title string + ID int + Number int + State string +} type repositoriesPage struct { PageInfo struct { HasNextPage bool @@ -380,6 +390,22 @@ func StripAnsi(str string) string { return regex.ReplaceAllString(str, "") } +func SleepIfLongerThan(thisTime time.Time) { + // delay if write was fast + elapsed := time.Since(thisTime) + if elapsed.Seconds() < 1 { + wait := 1000 - elapsed.Milliseconds() + DebugAndStatus( + fmt.Sprintf( + "Execution time was %v. Waiting for %vms to avoid rate limiting.", + elapsed.Seconds(), + wait, + ), + ) + time.Sleep(time.Duration(wait) * time.Millisecond) + } +} + func Process(cmd *cobra.Command, args []string) (err error) { // Create log file @@ -587,7 +613,9 @@ func Process(cmd *cobra.Command, args []string) (err error) { defer outputFile.Close() // write header - _, err = outputFile.WriteString("repository,exists_in_target,visibility,secrets,variables,environments\n") + _, err = outputFile.WriteString( + "repository,exists_in_target,visibility,secrets,variables,environments\n", + ) if err != nil { OutputError("Error writing to output file.", true) } @@ -604,9 +632,9 @@ func Process(cmd *cobra.Command, args []string) (err error) { line, repository.Visibility, repository.TargetVisibility, - repository.Secrets, - repository.Variables, - repository.Environments, + repository.Secrets.Total_Count, + repository.Variables.Total_Count, + repository.Environments.Total_Count, ) _, err = outputFile.WriteString(line) if err != nil { @@ -616,8 +644,10 @@ func Process(cmd *cobra.Command, args []string) (err error) { // create issues if we need to if CreateIssues { + Spinner.Start() DebugAndStatus("Attempting to create issues for repositories...") err = ProcessIssues(SourceRestClient, GithubTargetOrg, SourceRepositories) + Spinner.Stop() if err != nil { return err } @@ -886,19 +916,7 @@ func GetRepositoryStatistics(client api.RESTClient, repoToProcess repository) { fmt.Sprintf("%d", envResponse.Total_Count), }) - // delay if write was fast - elapsed := time.Since(start) - if elapsed.Seconds() < 1 { - wait := 1000 - elapsed.Milliseconds() - Debug( - fmt.Sprintf( - "Execution time was %v. Waiting for %vms to avoid rate limiting.", - elapsed.Seconds(), - wait, - ), - ) - time.Sleep(time.Duration(wait) * time.Millisecond) - } + SleepIfLongerThan(start) // close out this thread WaitGroup.Done() @@ -990,9 +1008,10 @@ func GetRepositories(restClient api.RESTClient, graphqlClient api.GQLClient, own func ProcessIssues(client api.RESTClient, targetOrg string, reposToProcess []repository) (err error) { - var response interface{} for _, repository := range reposToProcess { + var issuesResponse issues + if !repository.ExistsInTarget { Debug( fmt.Sprintf( @@ -1011,24 +1030,111 @@ func ProcessIssues(client api.RESTClient, targetOrg string, reposToProcess []rep query := url.QueryEscape( fmt.Sprintf( - "%s repo:%s/%s in:title", + "%s repo:%s/%s in:title state:open", DefaultIssueTitleTemplate, targetOrg, repository.Name, ), ) Debug(fmt.Sprintf("Using search string: %s", query)) + DebugAndStatus( + fmt.Sprintf( + "Searching issues in %s/%s", + targetOrg, + repository.Name, + ), + ) err = client.Get( fmt.Sprintf( "search/issues?q=%s", query, ), - &response, + &issuesResponse, ) - Debug(fmt.Sprintf("Response from GET: %v", response)) + Debug(fmt.Sprintf("Response from GET: %v", issuesResponse)) + + if err != nil { + return err + } + + // validate rate + _, err = ValidateApiRate(client, "core") if err != nil { return err } + + // start timer + start := time.Now() + + if issuesResponse.Total_Count == 1 { + + var updateResponse interface{} + // find issue number + foundIssue := issuesResponse.Items[0] + // update an issue + updateBody, err := json.Marshal(map[string]string{ + "body": fmt.Sprintf("test: %s", time.Now()), + }) + if err != nil { + return err + } + updateUrl := fmt.Sprintf( + "repos/%s/%s/issues/%d", + targetOrg, + repository.Name, + foundIssue.Number, + ) + DebugAndStatus(fmt.Sprintf("Updating issue on %s", updateUrl)) + err = client.Patch( + updateUrl, + bytes.NewBuffer(updateBody), + updateResponse, + ) + if err != nil { + return err + } + Debug(fmt.Sprintf("Response from PATCH: %v", updateResponse)) + + } else if issuesResponse.Total_Count == 0 { + + var createResponse interface{} + // create an issue + createBody, err := json.Marshal(map[string]string{ + "title": DefaultIssueTitleTemplate, + "body": "first test", + }) + if err != nil { + return err + } + createUrl := fmt.Sprintf( + "repos/%s/%s/issues", + targetOrg, + repository.Name, + ) + DebugAndStatus(fmt.Sprintf("Creating issue on %s", createUrl)) + err = client.Post( + createUrl, + bytes.NewBuffer(createBody), + createResponse, + ) + if err != nil { + return err + } + Debug(fmt.Sprintf("Response from POST: %v", createResponse)) + + } else { + + // when more than 1 issue is found + Debug(fmt.Sprint( + "Could not accurately determine issue to update because multiple ", + fmt.Sprintf( + "issues with the title '%s' were found.", + DefaultIssueTitleTemplate, + ), + )) + } + + SleepIfLongerThan(start) } return err @@ -1079,19 +1185,7 @@ func ProcessRepositoryVisibilities(client api.RESTClient, targetOrg string, repo return err } - // delay if write was fast - elapsed := time.Since(start) - if elapsed.Seconds() < 1 { - wait := 1000 - elapsed.Milliseconds() - Debug( - fmt.Sprintf( - "Execution time was %v. Waiting for %vms to avoid rate limiting.", - elapsed.Seconds(), - wait, - ), - ) - time.Sleep(time.Duration(wait) * time.Millisecond) - } + SleepIfLongerThan(start) } // always return From f0d1073b9cd21498432f2e38601ebfa77915ed91 Mon Sep 17 00:00:00 2001 From: samueljmello <6161359+samueljmello@users.noreply.github.com> Date: Mon, 24 Jul 2023 21:58:20 -0400 Subject: [PATCH 3/8] Update version --- main.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/main.go b/main.go index 0902d9f..c4907de 100644 --- a/main.go +++ b/main.go @@ -67,7 +67,7 @@ var ( Use: "gh pma", Short: Description, Long: Description, - Version: "0.0.6", + Version: "0.0.7", SilenceUsage: true, SilenceErrors: true, RunE: Process, From f0af76e53cfbb5e4c76ef34ee756463241ab3cf7 Mon Sep 17 00:00:00 2001 From: samueljmello <6161359+samueljmello@users.noreply.github.com> Date: Mon, 24 Jul 2023 22:01:04 -0400 Subject: [PATCH 4/8] switch to only creating csv on request --- main.go | 73 ++++++++++++++++++++++++++++++++------------------------- 1 file changed, 41 insertions(+), 32 deletions(-) diff --git a/main.go b/main.go index c4907de..eb052d3 100644 --- a/main.go +++ b/main.go @@ -29,6 +29,7 @@ var ( // flag vars AutoConfirm = false CreateIssues = false + CreateCSV = false GithubSourceOrg string GithubTargetOrg string ApiUrl string @@ -195,10 +196,9 @@ func init() { "", "", ) - rootCmd.PersistentFlags().IntVarP( + rootCmd.PersistentFlags().IntVar( &Threads, "threads", - "t", 3, fmt.Sprint( "Number of threads to process concurrently. Maximum of 10 allowed. ", @@ -214,10 +214,17 @@ func init() { rootCmd.PersistentFlags().BoolVarP( &CreateIssues, "create-issues", - "c", + "i", false, "Whether to create issues in target org repositories or not.", ) + rootCmd.PersistentFlags().BoolVarP( + &CreateCSV, + "create-csv", + "c", + false, + "Whether to create a CSV file with the results.", + ) // make certain flags required rootCmd.MarkPersistentFlagRequired("github-source-org") @@ -606,40 +613,42 @@ func Process(cmd *cobra.Command, args []string) (err error) { } // Create output file - outputFile, err := os.Create(fmt.Sprint(time.Now().Format("20060102150401"), ".", GithubSourceOrg, ".csv")) - if err != nil { - return err - } - defer outputFile.Close() - - // write header - _, err = outputFile.WriteString( - "repository,exists_in_target,visibility,secrets,variables,environments\n", - ) - if err != nil { - OutputError("Error writing to output file.", true) - } - // write body - for _, repository := range SourceRepositories { - line := fmt.Sprintf("%s", repository.NameWithOwner) - if !IsTargetProvided() { - line = fmt.Sprintf("%s%s", line, "Unknown") - } else { - line = fmt.Sprintf("%s%t", line, repository.ExistsInTarget) + if CreateCSV { + outputFile, err := os.Create(fmt.Sprint(time.Now().Format("20060102150401"), ".", GithubSourceOrg, ".csv")) + if err != nil { + return err } - line = fmt.Sprintf( - "%s,%s|%s,%d,%d,%d\n", - line, - repository.Visibility, - repository.TargetVisibility, - repository.Secrets.Total_Count, - repository.Variables.Total_Count, - repository.Environments.Total_Count, + defer outputFile.Close() + + // write header + _, err = outputFile.WriteString( + "repository,exists_in_target,visibility,secrets,variables,environments\n", ) - _, err = outputFile.WriteString(line) if err != nil { OutputError("Error writing to output file.", true) } + // write body + for _, repository := range SourceRepositories { + line := fmt.Sprintf("%s", repository.NameWithOwner) + if !IsTargetProvided() { + line = fmt.Sprintf("%s%s", line, "Unknown") + } else { + line = fmt.Sprintf("%s%t", line, repository.ExistsInTarget) + } + line = fmt.Sprintf( + "%s,%s|%s,%d,%d,%d\n", + line, + repository.Visibility, + repository.TargetVisibility, + repository.Secrets.Total_Count, + repository.Variables.Total_Count, + repository.Environments.Total_Count, + ) + _, err = outputFile.WriteString(line) + if err != nil { + OutputError("Error writing to output file.", true) + } + } } // create issues if we need to From d11fc9bfcd0ac8da7df1b6d536e326615b037b36 Mon Sep 17 00:00:00 2001 From: samueljmello <6161359+samueljmello@users.noreply.github.com> Date: Mon, 24 Jul 2023 22:07:26 -0400 Subject: [PATCH 5/8] clean up & reduce code --- README.md | 6 ++++-- main.go | 17 ++++++++++++----- 2 files changed, 16 insertions(+), 7 deletions(-) diff --git a/README.md b/README.md index 2cf9c66..05cf3c9 100644 --- a/README.md +++ b/README.md @@ -53,13 +53,15 @@ Usage: gh pma [flags] Flags: - --confirm Auto respond to confirmation prompt + --confirm Auto respond to visibility alignment confirmation prompt + -c, --create-csv Whether to create a CSV file with the results. + -i, --create-issues Whether to create issues in target org repositories or not. --ghes-api-url string Required if migrating from GHES. The domain name for your GHES instance. For example: ghes.contoso.com (default "github.com") --github-source-org string Uses GH_SOURCE_PAT env variable or --github-source-pat option. Will fall back to GH_PAT or --github-target-pat if not set. --github-source-pat string --github-target-org string Uses GH_PAT env variable or --github-target-pat option. --github-target-pat string -h, --help help for gh - -t, --threads int Number of threads to process concurrently. Maximum of 10 allowed. Increasing this number could get your PAT blocked due to API limiting. (default 3) + --threads int Number of threads to process concurrently. Maximum of 10 allowed. Increasing this number could get your PAT blocked due to API limiting. (default 3) -v, --version version for gh ``` \ No newline at end of file diff --git a/main.go b/main.go index eb052d3..cd1789c 100644 --- a/main.go +++ b/main.go @@ -330,6 +330,14 @@ func Debug(message string) string { return message } +func DisplayRateLeft() { + // validate we have API attempts left + rateLeft, _ := ValidateApiRate(SourceRestClient, "core") + rateMessage := Cyan("API Rate Limit Left:") + OutputNotice(fmt.Sprintf("%s %d", rateMessage, rateLeft)) + LF() +} + func IsTargetProvided() bool { if GithubTargetOrg != "" { return true @@ -688,6 +696,7 @@ func Process(cmd *cobra.Command, args []string) (err error) { LF() OutputWarning("Alignment process abandoned.") LF() + DisplayRateLeft() return err } @@ -702,20 +711,18 @@ func Process(cmd *cobra.Command, args []string) (err error) { // on successful processing if err == nil { + LF() OutputNotice( fmt.Sprintf( "Successfully processed %d repositories.", len(ToProcessRepositories), ), ) + LF() } } - // validate we have API attempts left - rateLeft, _ := ValidateApiRate(SourceRestClient, "core") - rateMessage := Cyan("API Rate Limit Left:") - OutputNotice(fmt.Sprintf("%s %d", rateMessage, rateLeft)) - LF() + DisplayRateLeft() // always return return err From 18c264e1cd3d891a89526dfa3ac3e0de73579a24 Mon Sep 17 00:00:00 2001 From: samueljmello <6161359+samueljmello@users.noreply.github.com> Date: Mon, 24 Jul 2023 22:32:03 -0400 Subject: [PATCH 6/8] Get template established --- main.go | 33 +++++++++++++++++++++++++++++---- 1 file changed, 29 insertions(+), 4 deletions(-) diff --git a/main.go b/main.go index cd1789c..0cad5d2 100644 --- a/main.go +++ b/main.go @@ -43,7 +43,7 @@ var ( // tool vars DefaultApiUrl string = "github.com" - DefaultIssueTitleTemplate string = "Post Migration Assessment" + DefaultIssueTitleTemplate string = "Post Migration Audit" SourceRestClient api.RESTClient TargetRestClient api.RESTClient SourceGraphqlClient api.GQLClient @@ -147,7 +147,7 @@ type secret struct { Name string } type variables struct { - Variables []secret + Variables []variable Total_Count int } type variable struct { @@ -1082,6 +1082,31 @@ func ProcessIssues(client api.RESTClient, targetOrg string, reposToProcess []rep // start timer start := time.Now() + // create a template for the issue + issueTemplate := "# Audit Results\n" + issueTemplate += fmt.Sprintf( + "Audit last performed on %s at %s.\n\n", + time.Now().Format("2006-01-02"), + time.Now().Format("15:04:05"), + ) + issueTemplate += "See below for migration details and whether " + issueTemplate += "you need to mitigate any items.\n\n" + issueTemplate += "## Details\n" + issueTemplate += "- **Migrated From:** %s\n" + issueTemplate += "- **Source Visibility:** %s\n\n" + issueTemplate += "## Items From Source\n" + issueTemplate += "- Variables: `%+v`\n" + issueTemplate += "- Secrets: `%+v`\n" + issueTemplate += "- Environments: `%+v`\n" + issueBody := fmt.Sprintf( + issueTemplate, + targetOrg, + repository.Visibility, + repository.Variables, + repository.Secrets, + repository.Environments, + ) + if issuesResponse.Total_Count == 1 { var updateResponse interface{} @@ -1089,7 +1114,7 @@ func ProcessIssues(client api.RESTClient, targetOrg string, reposToProcess []rep foundIssue := issuesResponse.Items[0] // update an issue updateBody, err := json.Marshal(map[string]string{ - "body": fmt.Sprintf("test: %s", time.Now()), + "body": issueBody, }) if err != nil { return err @@ -1117,7 +1142,7 @@ func ProcessIssues(client api.RESTClient, targetOrg string, reposToProcess []rep // create an issue createBody, err := json.Marshal(map[string]string{ "title": DefaultIssueTitleTemplate, - "body": "first test", + "body": issueBody, }) if err != nil { return err From 3a4e00a75c6f28e40b30f77ca0d8ff5214cd45d1 Mon Sep 17 00:00:00 2001 From: samueljmello <6161359+samueljmello@users.noreply.github.com> Date: Mon, 24 Jul 2023 22:42:44 -0400 Subject: [PATCH 7/8] Fix a bug with source owner --- main.go | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/main.go b/main.go index 0cad5d2..cff62f0 100644 --- a/main.go +++ b/main.go @@ -128,6 +128,7 @@ type repositoryNode struct { } type repository struct { Name string + Owner string NameWithOwner string Visibility string TargetVisibility string @@ -1002,6 +1003,7 @@ func GetRepositories(restClient api.RESTClient, graphqlClient api.GQLClient, own for _, repoNode := range query.Organization.Repositories.Nodes { var repoClone repository repoClone.Name = repoNode.Name + repoClone.Owner = repoNode.Owner.Login repoClone.NameWithOwner = repoNode.NameWithOwner repoClone.Visibility = repoNode.Visibility repoLookup = append(repoLookup, repoClone) @@ -1084,15 +1086,18 @@ func ProcessIssues(client api.RESTClient, targetOrg string, reposToProcess []rep // create a template for the issue issueTemplate := "# Audit Results\n" + now := time.Now() + tz, _ := now.Zone() issueTemplate += fmt.Sprintf( - "Audit last performed on %s at %s.\n\n", - time.Now().Format("2006-01-02"), - time.Now().Format("15:04:05"), + "Audit last performed on %s at %s %s.\n\n", + now.Format("2006-01-02"), + now.Format("15:04:05"), + tz, ) issueTemplate += "See below for migration details and whether " issueTemplate += "you need to mitigate any items.\n\n" issueTemplate += "## Details\n" - issueTemplate += "- **Migrated From:** %s\n" + issueTemplate += "- **Migrated From:** [%s](https://github.com/%s)\n" issueTemplate += "- **Source Visibility:** %s\n\n" issueTemplate += "## Items From Source\n" issueTemplate += "- Variables: `%+v`\n" @@ -1100,7 +1105,8 @@ func ProcessIssues(client api.RESTClient, targetOrg string, reposToProcess []rep issueTemplate += "- Environments: `%+v`\n" issueBody := fmt.Sprintf( issueTemplate, - targetOrg, + repository.Owner, + repository.Owner, repository.Visibility, repository.Variables, repository.Secrets, From fe838b5090349f5cfd042d1cc74f415f7e14e760 Mon Sep 17 00:00:00 2001 From: samueljmello <6161359+samueljmello@users.noreply.github.com> Date: Mon, 24 Jul 2023 22:49:59 -0400 Subject: [PATCH 8/8] update readme --- README.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 05cf3c9..aaae5c3 100644 --- a/README.md +++ b/README.md @@ -9,6 +9,8 @@ Will report on: - Variables - Environments +Optionally you can choose to create a CSV in your file system (`-c` flag) and/or an issue (`-i` flag) in the target repo with the results. + The tool could be expanded to include other non-migratable settings (see [what is & isn't migrated](https://docs.github.com/en/migrations/using-github-enterprise-importer/understanding-github-enterprise-importer/migration-support-for-github-enterprise-importer#githubcom-migration-support) during a migration with [GEI](https://github.com/github/gh-gei)), and most likely will in the near future. [![build](https://github.com/mona-actions/gh-pma/actions/workflows/build.yaml/badge.svg)](https://github.com/mona-actions/gh-pma/actions/workflows/build.yaml) @@ -19,7 +21,6 @@ The tool could be expanded to include other non-migratable settings (see [what i - Codespaces Secrets - Dependabot Secrets - Environmen Secrets & Vars (currently just Environments are detected) -- Create an issue on the target org repository with the list of items to mitigate. ## Prerequisites - [GitHub CLI](https://cli.github.com/manual/installation) installed.