diff --git a/.gitignore b/.gitignore index 3109500d3..9cb135444 100644 --- a/.gitignore +++ b/.gitignore @@ -25,3 +25,6 @@ temp/ /all-releases.txt /latest-release-tag.txt /release-tag.txt + +credentials.json +token.json \ No newline at end of file diff --git a/cmd/certsuite/main.go b/cmd/certsuite/main.go index 36543536e..9f9729d9b 100644 --- a/cmd/certsuite/main.go +++ b/cmd/certsuite/main.go @@ -11,6 +11,7 @@ import ( "github.com/redhat-best-practices-for-k8s/certsuite/cmd/certsuite/generate" "github.com/redhat-best-practices-for-k8s/certsuite/cmd/certsuite/info" "github.com/redhat-best-practices-for-k8s/certsuite/cmd/certsuite/run" + "github.com/redhat-best-practices-for-k8s/certsuite/cmd/certsuite/upload" "github.com/redhat-best-practices-for-k8s/certsuite/cmd/certsuite/version" ) @@ -26,6 +27,7 @@ func newRootCmd() *cobra.Command { rootCmd.AddCommand(run.NewCommand()) rootCmd.AddCommand(info.NewCommand()) rootCmd.AddCommand(version.NewCommand()) + rootCmd.AddCommand(upload.NewCommand()) return &rootCmd } diff --git a/cmd/certsuite/upload/results_spreadsheet/client.go b/cmd/certsuite/upload/results_spreadsheet/client.go new file mode 100644 index 000000000..5f5d697ce --- /dev/null +++ b/cmd/certsuite/upload/results_spreadsheet/client.go @@ -0,0 +1,77 @@ +package resultsspreadsheet + +import ( + "context" + "encoding/json" + "fmt" + "net/http" + "os" + + "golang.org/x/oauth2" +) + +const tokenPermissions = 0o600 + +// Retrieve a token, saves the token, then returns the generated client. +func getClient(config *oauth2.Config) (*http.Client, error) { + // The file token.json stores the user's access and refresh tokens, and is + // created automatically when the authorization flow completes for the first + // time. + tokFile := "token.json" + tok, err := tokenFromFile(tokFile) + if err != nil { + tok, err = getTokenFromWeb(config) + if err != nil { + return nil, err + } + if err := saveToken(tokFile, tok); err != nil { + return nil, err + } + } + return config.Client(context.Background(), tok), nil +} + +// Request a token from the web, then returns the retrieved token. +func getTokenFromWeb(config *oauth2.Config) (*oauth2.Token, error) { + authURL := config.AuthCodeURL("state-token", oauth2.AccessTypeOffline) + fmt.Printf("Go to the following link in your browser then type the "+ + "authorization code: \n%v\n", authURL) + + var authCode string + if _, err := fmt.Scan(&authCode); err != nil { + return nil, fmt.Errorf("unable to read authorization code: %v", err) + } + + tok, err := config.Exchange(context.TODO(), authCode) + if err != nil { + return nil, fmt.Errorf("unable to retrieve token from web: %v", err) + } + return tok, nil +} + +// Retrieves a token from a local file. +func tokenFromFile(file string) (*oauth2.Token, error) { + f, err := os.Open(file) + if err != nil { + return nil, err + } + defer f.Close() + tok := &oauth2.Token{} + err = json.NewDecoder(f).Decode(tok) + return tok, err +} + +// Saves a token to a file path. +func saveToken(path string, token *oauth2.Token) error { + fmt.Printf("Saving credential file to: %s\n", path) + f, err := os.OpenFile(path, os.O_RDWR|os.O_CREATE|os.O_TRUNC, tokenPermissions) + if err != nil { + return fmt.Errorf("unable to cache oauth token: %v", err) + } + defer f.Close() + err = json.NewEncoder(f).Encode(token) + if err != nil { + return fmt.Errorf("unable to encode token: %v", err) + } + return nil +} diff --git a/cmd/certsuite/upload/results_spreadsheet/const.go b/cmd/certsuite/upload/results_spreadsheet/const.go new file mode 100644 index 000000000..fe06046aa --- /dev/null +++ b/cmd/certsuite/upload/results_spreadsheet/const.go @@ -0,0 +1,13 @@ +package resultsspreadsheet + +const ( + categoryConclusionsCol = "Category" + workloadVersionConclusionsCol = "Workload Version" + ocpVersionConclusionsCol = "OCP Version" + workloadNameConclusionsCol = "Workload Name" + resultsConclusionsCol = "Results" + workloadNameRawResultsCol = "CNFName" + workloadTypeRawResultsCol = "CNFType" + operatorVersionRawResultsCol = "OperatorVersion" + cellContentLimit = 50000 +) diff --git a/cmd/certsuite/upload/results_spreadsheet/drive_utils.go b/cmd/certsuite/upload/results_spreadsheet/drive_utils.go new file mode 100644 index 000000000..c410f06bc --- /dev/null +++ b/cmd/certsuite/upload/results_spreadsheet/drive_utils.go @@ -0,0 +1,78 @@ +package resultsspreadsheet + +import ( + "fmt" + "log" + "net/url" + "strings" + + "google.golang.org/api/drive/v3" + "google.golang.org/api/sheets/v4" +) + +func createDriveFolder(srv *drive.Service, folderName, parentFolderID string) (*drive.File, error) { + driveFolder := &drive.File{ + Name: folderName, + Parents: []string{parentFolderID}, + MimeType: "application/vnd.google-apps.folder", + } + + // Search for an existing folder with the same name + q := fmt.Sprintf("name = '%s' and mimeType = 'application/vnd.google-apps.folder' and '%s' in parents and trashed = false", folderName, parentFolderID) + call := srv.Files.List().Q(q).Fields("files(id, name)") + + files, err := call.Do() + if err != nil { + return nil, fmt.Errorf("unable to list files: %v", err) + } + + if len(files.Files) > 0 { + return nil, fmt.Errorf("folder %s already exists in %s folder ID", folderName, parentFolderID) + } + + createdFolder, err := srv.Files.Create(driveFolder).Do() + if err != nil { + return nil, fmt.Errorf("unable to create folder: %v", err) + } + + return createdFolder, nil +} + +func MoveSpreadSheetToFolder(srv *drive.Service, folder *drive.File, spreadsheet *sheets.Spreadsheet) error { + file, err := srv.Files.Get(spreadsheet.SpreadsheetId).Fields("parents").Do() + if err != nil { + log.Fatalf("Unable to get file: %v", err) + } + + // Collect the current parent IDs to remove (if needed) + oldParents := append([]string{}, file.Parents...) + + updateCall := srv.Files.Update(spreadsheet.SpreadsheetId, nil) + updateCall.AddParents(folder.Id) + + // Remove the file from its old parents + if len(oldParents) > 0 { + for _, parent := range oldParents { + updateCall.RemoveParents(parent) + } + } + + _, err = updateCall.Do() + if err != nil { + log.Fatalf("Unable change file location: %v", err) + } + + return nil +} + +func extractFolderIDFromURL(u string) (string, error) { + parsedURL, err := url.Parse(u) + if err != nil { + return "", err + } + + pathSegments := strings.Split(parsedURL.Path, "/") + + // The folder ID is the last segment in the path + return pathSegments[len(pathSegments)-1], nil +} diff --git a/cmd/certsuite/upload/results_spreadsheet/results_spreadsheet.go b/cmd/certsuite/upload/results_spreadsheet/results_spreadsheet.go new file mode 100644 index 000000000..e7344ae09 --- /dev/null +++ b/cmd/certsuite/upload/results_spreadsheet/results_spreadsheet.go @@ -0,0 +1,374 @@ +package resultsspreadsheet + +import ( + "context" + "encoding/csv" + "fmt" + "log" + "os" + "strings" + "time" + + "github.com/spf13/cobra" + + "golang.org/x/oauth2/google" + "google.golang.org/api/drive/v3" + "google.golang.org/api/option" + "google.golang.org/api/sheets/v4" +) + +var stringToPointer = func(s string) *string { return &s } +var conclusionSheetHeaders = []string{categoryConclusionsCol, workloadVersionConclusionsCol, ocpVersionConclusionsCol, workloadNameConclusionsCol, resultsConclusionsCol} + +var ( + resultsFilePath string + rootFolderURL string + ocpVersion string + credentials string +) + +var ( + uploadResultSpreadSheetCmd = &cobra.Command{ + Use: "results-spreadsheet", + Short: "Generates a google spread sheets with test suite results.", + Run: func(cmd *cobra.Command, args []string) { + generateResultsSpreadSheet() + }, + } +) + +func NewCommand() *cobra.Command { + uploadResultSpreadSheetCmd.Flags().StringVarP(&resultsFilePath, "results-file", "f", "", "Required: path to results file") + uploadResultSpreadSheetCmd.Flags().StringVarP(&rootFolderURL, "dest-url", "d", "", "Required: Destination drive folder's URL") + uploadResultSpreadSheetCmd.Flags().StringVarP(&ocpVersion, "version", "v", "", "Optional: OCP Version") + uploadResultSpreadSheetCmd.Flags().StringVarP(&credentials, "credentials", "c", "credentials.json", "Optional: Google credentials file path, default path: credentials.json") + + err := uploadResultSpreadSheetCmd.MarkFlagRequired("results-file") + if err != nil { + log.Fatalf("Failed to mark results file path as required parameter: %v", err) + return nil + } + + err = uploadResultSpreadSheetCmd.MarkFlagRequired("dest-url") + if err != nil { + log.Fatalf("Failed to mark dest url path as required parameter: %v", err) + return nil + } + + return uploadResultSpreadSheetCmd +} + +func readCSV(fp string) ([][]string, error) { + file, err := os.Open(fp) + if err != nil { + return nil, err + } + defer file.Close() + + reader := csv.NewReader(file) + records, err := reader.ReadAll() + if err != nil { + return nil, err + } + return records, nil +} + +func createSheetsAndDriveServices() (sheetService *sheets.Service, driveService *drive.Service, err error) { + ctx := context.Background() + b, err := os.ReadFile(credentials) + if err != nil { + return nil, nil, fmt.Errorf("unable to read client secret file: %v", err) + } + + // If modifying these scopes, delete your previously saved token.json. + config, err := google.ConfigFromJSON(b, sheets.SpreadsheetsScope, drive.DriveScope) + if err != nil { + return nil, nil, fmt.Errorf("unable to parse client secret file to config: %v", err) + } + client, err := getClient(config) + if err != nil { + return nil, nil, fmt.Errorf("unable to get client: %v", err) + } + + sheetSrv, err := sheets.NewService(ctx, option.WithHTTPClient(client)) + if err != nil { + return nil, nil, fmt.Errorf("unable to retrieve Sheets client: %v", err) + } + + driveSrv, err := drive.NewService(ctx, option.WithHTTPClient(client)) + if err != nil { + return nil, nil, fmt.Errorf("unable to retrieve Drive client: %v", err) + } + + return sheetSrv, driveSrv, nil +} + +func prepareRecordsForSpreadSheet(records [][]string) []*sheets.RowData { + var rows []*sheets.RowData + for _, row := range records { + var rowData []*sheets.CellData + for _, col := range row { + var val string + // cell content cannot exceed 50,000 letters. + if len(col) > cellContentLimit { + col = col[:cellContentLimit] + } + // use space for empty values to avoid cells overlapping + if col == "" { + val = " " + } + // avoid line breaks in cell + val = strings.ReplaceAll(strings.ReplaceAll(col, "\r\n", " "), "\n", " ") + + rowData = append(rowData, &sheets.CellData{ + UserEnteredValue: &sheets.ExtendedValue{StringValue: &val}, + }) + } + rows = append(rows, &sheets.RowData{Values: rowData}) + } + return rows +} + +// createSingleWorkloadRawResultsSheet creates a new sheet with test case results of a single workload, +// extracted from rawResultsSheets (which may contain the results of several workloads). +// The sheet will use the same header columns as the rawResultsSheet, but will also add two extra columns: +// - "Owner/TechLead Conclusion": the partner/user is expected to add the name of the workload owner that should lead the fix +// of this test case result. +// - "Next Step Actions": the partner/user may use this column to add the follow-up actions to fix this test case result. +// +// Note: the caller of the function is responsible to check that the given rawResultsSheet data is not empty +func createSingleWorkloadRawResultsSheet(rawResultsSheet *sheets.Sheet, workloadName string) (*sheets.Sheet, error) { + // Initialize sheet with the two new column headers only. + filteredRows := []*sheets.RowData{{Values: []*sheets.CellData{ + {UserEnteredValue: &sheets.ExtendedValue{StringValue: stringToPointer("Owner/TechLead Conclusion")}}, + {UserEnteredValue: &sheets.ExtendedValue{StringValue: stringToPointer("Next Step Actions")}}, + }}} + + // Add existing column headers from the rawResultsSheet + filteredRows[0].Values = append(filteredRows[0].Values, rawResultsSheet.Data[0].RowData[0].Values...) + + headers := getHeadersFromSheet(rawResultsSheet) + indices, err := getHeaderIndicesByColumnNames(headers, []string{"CNFName"}) + if err != nil { + return nil, err + } + workloadNameIndex := indices[0] + + // add to sheet only rows of given workload name + for _, row := range rawResultsSheet.Data[0].RowData[1:] { + if len(row.Values) <= workloadNameIndex { + return nil, fmt.Errorf("workload %s not found in raw spreadsheet", workloadName) + } + curWorkloadName := *row.Values[workloadNameIndex].UserEnteredValue.StringValue + if curWorkloadName == workloadName { + // add empty values in 2 added columns + newRow := &sheets.RowData{ + Values: append([]*sheets.CellData{{}, {}}, row.Values...), + } + filteredRows = append(filteredRows, newRow) + } + } + + workloadResultsSheet := &sheets.Sheet{ + Properties: &sheets.SheetProperties{ + Title: "results", + }, + Data: []*sheets.GridData{{RowData: filteredRows}}, + } + + return workloadResultsSheet, nil +} + +func createSingleWorkloadRawResultsSpreadSheet(sheetService *sheets.Service, driveService *drive.Service, folder *drive.File, rawResultsSheet *sheets.Sheet, workloadName string) (*sheets.Spreadsheet, error) { + workloadResultsSheet, err := createSingleWorkloadRawResultsSheet(rawResultsSheet, workloadName) + if err != nil { + return nil, err + } + + workloadResultsSpreadsheet := &sheets.Spreadsheet{ + Properties: &sheets.SpreadsheetProperties{ + Title: fmt.Sprintf("%s Best Practices Test Results", workloadName), + }, + Sheets: []*sheets.Sheet{workloadResultsSheet}, + } + + workloadResultsSpreadsheet, err = sheetService.Spreadsheets.Create(workloadResultsSpreadsheet).Do() + if err != nil { + return nil, err + } + + if err := addFilterByFailedAndMandatoryToSheet(sheetService, workloadResultsSpreadsheet, "results"); err != nil { + return nil, err + } + + if err := MoveSpreadSheetToFolder(driveService, folder, workloadResultsSpreadsheet); err != nil { + return nil, err + } + + return workloadResultsSpreadsheet, nil +} + +// createConclusionsSheet creates a new sheet with unique workloads data extracted from rawResultsSheets. +// The sheet's columns include: +// "Category" (Telco\Non-Telco workload), "Workload Version", "OCP Version", "Workload Name" and +// "Results" containing a hyper link leading to the workload's raw results spreadsheet. +// +//nolint:funlen +func createConclusionsSheet(sheetsService *sheets.Service, driveService *drive.Service, rawResultsSheet *sheets.Sheet, mainResultsFolderID string) (*sheets.Sheet, error) { + workloadsFolderName := "Results Per Workload" + workloadsResultsFolder, err := createDriveFolder(driveService, workloadsFolderName, mainResultsFolderID) + if err != nil { + return nil, fmt.Errorf("unable to create workloads results folder: %v", err) + } + + rawSheetHeaders := getHeadersFromSheet(rawResultsSheet) + colsIndices, err := getHeaderIndicesByColumnNames(rawSheetHeaders, []string{workloadNameRawResultsCol, workloadTypeRawResultsCol, operatorVersionRawResultsCol}) + if err != nil { + return nil, err + } + + workloadNameColIndex := colsIndices[0] + workloadTypeColIndex := colsIndices[1] + operatorVersionColIndex := colsIndices[2] + + // Initialize sheet with headers + conclusionsSheetRowsValues := []*sheets.CellData{} + for _, colHeader := range conclusionSheetHeaders { + headerCellData := &sheets.CellData{UserEnteredValue: &sheets.ExtendedValue{StringValue: &colHeader}} + conclusionsSheetRowsValues = append(conclusionsSheetRowsValues, headerCellData) + } + conclusionsSheetRows := []*sheets.RowData{{Values: conclusionsSheetRowsValues}} + + // If rawResultsSheet has now workloads data, return an error + if len(rawResultsSheet.Data[0].RowData) <= 1 { + return nil, fmt.Errorf("raw results has no workloads data") + } + + // Extract unique values from the CNFName column and fill sheet + uniqueWorkloadNames := make(map[string]bool) + for _, rawResultsSheetrow := range rawResultsSheet.Data[0].RowData[1:] { + workloadName := *rawResultsSheetrow.Values[workloadNameColIndex].UserEnteredValue.StringValue + // if workload has already been added to sheet, skip it + if uniqueWorkloadNames[workloadName] { + continue + } + uniqueWorkloadNames[workloadName] = true + + curConsclusionRowValues := []*sheets.CellData{} + for _, colHeader := range conclusionSheetHeaders { + curCellData := &sheets.CellData{UserEnteredValue: &sheets.ExtendedValue{}} + + switch colHeader { + case categoryConclusionsCol: + curCellData.UserEnteredValue.StringValue = rawResultsSheetrow.Values[workloadTypeColIndex].UserEnteredValue.StringValue + + case workloadVersionConclusionsCol: + curCellData.UserEnteredValue.StringValue = rawResultsSheetrow.Values[operatorVersionColIndex].UserEnteredValue.StringValue + + case ocpVersionConclusionsCol: + curCellData.UserEnteredValue.StringValue = stringToPointer(ocpVersion + " ") + + case workloadNameConclusionsCol: + curCellData.UserEnteredValue.StringValue = &workloadName + + case resultsConclusionsCol: + workloadResultsSpreadsheet, err := createSingleWorkloadRawResultsSpreadSheet(sheetsService, driveService, workloadsResultsFolder, rawResultsSheet, workloadName) + if err != nil { + return nil, fmt.Errorf("error has occurred while creating %s results file: %v", workloadName, err) + } + + hyperlinkFormula := fmt.Sprintf("=HYPERLINK(%q, %q)", workloadResultsSpreadsheet.SpreadsheetUrl, "Results") + curCellData.UserEnteredValue.FormulaValue = &hyperlinkFormula + + default: + // use space for empty values to avoid cells overlapping + curCellData.UserEnteredValue.StringValue = stringToPointer(" ") + } + + curConsclusionRowValues = append(curConsclusionRowValues, curCellData) + } + conclusionsSheetRows = append(conclusionsSheetRows, &sheets.RowData{Values: curConsclusionRowValues}) + } + + conclusionSheet := &sheets.Sheet{ + Properties: &sheets.SheetProperties{ + Title: "conclusions", + GridProperties: &sheets.GridProperties{FrozenRowCount: 1}, + }, + Data: []*sheets.GridData{{RowData: conclusionsSheetRows}}, + } + + return conclusionSheet, nil +} + +func createRawResultsSheet(fp string) (*sheets.Sheet, error) { + records, err := readCSV(fp) + if err != nil { + return nil, fmt.Errorf("failed to read csv file: %v", err) + } + + rows := prepareRecordsForSpreadSheet(records) + + rawResultsSheet := &sheets.Sheet{ + Properties: &sheets.SheetProperties{ + Title: "raw results", + GridProperties: &sheets.GridProperties{FrozenRowCount: 1}, + }, + Data: []*sheets.GridData{{RowData: rows}}, + } + + return rawResultsSheet, nil +} + +func generateResultsSpreadSheet() { + sheetService, driveService, err := createSheetsAndDriveServices() + if err != nil { + log.Fatalf("Unable to create services: %v", err) + } + + rootFolderID, err := extractFolderIDFromURL(rootFolderURL) + if err != nil { + log.Fatalf("error getting folder ID from URL") + } + mainFolderName := strings.TrimLeft(fmt.Sprintf("%s Redhat Best Practices for K8 Test Results %s", ocpVersion, time.Now().Format("2006-01-02T15:04:05Z07:00")), " ") + mainResultsFolder, err := createDriveFolder(driveService, mainFolderName, rootFolderID) + if err != nil { + log.Fatalf("Unable to create main results folder: %v", err) + } + + rawResultsSheet, err := createRawResultsSheet(resultsFilePath) + if err != nil { + log.Fatalf("Unable to create raw results sheet: %v", err) + } + + conclusionSheet, err := createConclusionsSheet(sheetService, driveService, rawResultsSheet, mainResultsFolder.Id) + if err != nil { + log.Fatalf("Unable to create conclusions sheet: %v", err) + } + + spreadsheet := &sheets.Spreadsheet{ + Properties: &sheets.SpreadsheetProperties{ + Title: strings.TrimLeft(fmt.Sprintf("%s Redhat Best Practices for K8 Test Results", ocpVersion), " "), + }, + Sheets: []*sheets.Sheet{rawResultsSheet, conclusionSheet}, + } + + spreadsheet, err = sheetService.Spreadsheets.Create(spreadsheet).Do() + if err != nil { + log.Fatalf("Unable to create spreadsheet: %v", err) + } + + if err := MoveSpreadSheetToFolder(driveService, mainResultsFolder, spreadsheet); err != nil { + log.Fatal(err) + } + + if err = addBasicFilterToSpreadSheet(sheetService, spreadsheet); err != nil { + log.Fatalf("Unable to apply filter to the spread sheet: %v", err) + } + + if err = addDescendingSortFilterToSheet(sheetService, spreadsheet, conclusionSheet.Properties.Title, "Category"); err != nil { + log.Fatalf("Unable to apply filter to the spread sheet: %v", err) + } + + fmt.Printf("Results spreadsheet was created successfully: %s\n", spreadsheet.SpreadsheetUrl) +} diff --git a/cmd/certsuite/upload/results_spreadsheet/sheet_utils.go b/cmd/certsuite/upload/results_spreadsheet/sheet_utils.go new file mode 100644 index 000000000..206883d07 --- /dev/null +++ b/cmd/certsuite/upload/results_spreadsheet/sheet_utils.go @@ -0,0 +1,166 @@ +package resultsspreadsheet + +import ( + "fmt" + + "google.golang.org/api/sheets/v4" +) + +func getHeadersFromSheet(sheet *sheets.Sheet) []string { + headers := []string{} + for _, val := range sheet.Data[0].RowData[0].Values { + headers = append(headers, *val.UserEnteredValue.StringValue) + } + return headers +} + +func getHeadersFromValueRange(sheetsValues *sheets.ValueRange) []string { + headers := []string{} + for _, val := range sheetsValues.Values[0] { + headers = append(headers, fmt.Sprint(val)) + } + return headers +} + +func getHeaderIndicesByColumnNames(headers, names []string) ([]int, error) { + indices := []int{} + for _, name := range names { + found := false + for i, val := range headers { + if name == val { + found = true + indices = append(indices, i) + break + } + } + if !found { + return nil, fmt.Errorf("column %s doesn't exist in given headers list", name) + } + } + return indices, nil +} + +func getSheetIDByName(spreadsheet *sheets.Spreadsheet, name string) (int64, error) { + for _, sheet := range spreadsheet.Sheets { + if sheet.Properties.Title == name { + return sheet.Properties.SheetId, nil + } + } + return -1, fmt.Errorf("there is no sheet named %s in spreadsheet %s", name, spreadsheet.SpreadsheetUrl) +} + +func addBasicFilterToSpreadSheet(srv *sheets.Service, spreadsheet *sheets.Spreadsheet) error { + requests := []*sheets.Request{} + for _, sheet := range spreadsheet.Sheets { + requests = append(requests, &sheets.Request{ + SetBasicFilter: &sheets.SetBasicFilterRequest{ + Filter: &sheets.BasicFilter{ + Range: &sheets.GridRange{SheetId: sheet.Properties.SheetId}, + }, + }, + }) + } + + _, err := srv.Spreadsheets.BatchUpdate(spreadsheet.SpreadsheetId, &sheets.BatchUpdateSpreadsheetRequest{ + Requests: requests, + }).Do() + if err != nil { + return err + } + return nil +} + +func addDescendingSortFilterToSheet(srv *sheets.Service, spreadsheet *sheets.Spreadsheet, sheetName, colName string) error { + sheetsValues, err := srv.Spreadsheets.Values.Get(spreadsheet.SpreadsheetId, sheetName).Do() + if err != nil { + return fmt.Errorf("unable to retrieve sheet %s values: %v", sheetName, err) + } + headers := getHeadersFromValueRange(sheetsValues) + indices, err := getHeaderIndicesByColumnNames(headers, []string{colName}) + if err != nil { + return nil + } + + sheetID, err := getSheetIDByName(spreadsheet, sheetName) + if err != nil { + return fmt.Errorf("unable to retrieve sheet %s id: %v", sheetName, err) + } + + requests := []*sheets.Request{ + { + SortRange: &sheets.SortRangeRequest{ + Range: &sheets.GridRange{ + SheetId: sheetID, + StartRowIndex: 1, + }, + SortSpecs: []*sheets.SortSpec{ + { + DimensionIndex: int64(indices[0]), + SortOrder: "DESCENDING", + }, + }, + }, + }, + } + + _, err = srv.Spreadsheets.BatchUpdate(spreadsheet.SpreadsheetId, &sheets.BatchUpdateSpreadsheetRequest{ + Requests: requests, + }).Do() + if err != nil { + return err + } + return nil +} + +func addFilterByFailedAndMandatoryToSheet(srv *sheets.Service, spreadsheet *sheets.Spreadsheet, sheetName string) error { + sheetsValues, err := srv.Spreadsheets.Values.Get(spreadsheet.SpreadsheetId, sheetName).Do() + if err != nil { + return fmt.Errorf("unable to retrieve sheet %s values: %v", sheetName, err) + } + headers := getHeadersFromValueRange(sheetsValues) + indices, err := getHeaderIndicesByColumnNames(headers, []string{"State", "Mandatory/Optional"}) + if err != nil { + return nil + } + + stateColIndex := indices[0] + isMandatoryColIndex := indices[1] + + sheetID, err := getSheetIDByName(spreadsheet, sheetName) + if err != nil { + return fmt.Errorf("unable to retrieve sheet %s id: %v", sheetName, err) + } + + requests := []*sheets.Request{ + { + SetBasicFilter: &sheets.SetBasicFilterRequest{ + Filter: &sheets.BasicFilter{ + Range: &sheets.GridRange{SheetId: sheetID}, + Criteria: map[string]sheets.FilterCriteria{ + fmt.Sprint(stateColIndex): { + Condition: &sheets.BooleanCondition{ + Type: "TEXT_EQ", + Values: []*sheets.ConditionValue{ + {UserEnteredValue: "failed"}, + }, + }, + }, + fmt.Sprint(isMandatoryColIndex): { + Condition: &sheets.BooleanCondition{ + Type: "TEXT_EQ", + Values: []*sheets.ConditionValue{ + {UserEnteredValue: "Mandatory"}, + }, + }, + }, + }, + }, + }, + }, + } + + _, err = srv.Spreadsheets.BatchUpdate(spreadsheet.SpreadsheetId, &sheets.BatchUpdateSpreadsheetRequest{ + Requests: requests, + }).Do() + return err +} diff --git a/cmd/certsuite/upload/upload.go b/cmd/certsuite/upload/upload.go new file mode 100644 index 000000000..99cdecabc --- /dev/null +++ b/cmd/certsuite/upload/upload.go @@ -0,0 +1,19 @@ +package upload + +import ( + resultsspreadsheet "github.com/redhat-best-practices-for-k8s/certsuite/cmd/certsuite/upload/results_spreadsheet" + "github.com/spf13/cobra" +) + +var ( + upload = &cobra.Command{ + Use: "upload", + Short: "upload tool for various test suite assets", + } +) + +func NewCommand() *cobra.Command { + upload.AddCommand(resultsspreadsheet.NewCommand()) + + return upload +} diff --git a/go.mod b/go.mod index 917a90812..956a6c584 100644 --- a/go.mod +++ b/go.mod @@ -28,9 +28,12 @@ require ( ) require ( + cloud.google.com/go/auth v0.9.0 // indirect + cloud.google.com/go/auth/oauth2adapt v0.2.4 // indirect + cloud.google.com/go/compute/metadata v0.5.0 // indirect github.com/AdaLogics/go-fuzz-headers v0.0.0-20230811130428-ced1acdcaa24 // indirect github.com/Azure/go-ansiterm v0.0.0-20230124172434-306776ec8161 // indirect - github.com/BurntSushi/toml v1.3.2 // indirect + github.com/BurntSushi/toml v1.4.1-0.20240526193622-a339e1f7089c // indirect github.com/MakeNowJust/heredoc v1.0.0 // indirect github.com/Masterminds/goutils v1.1.1 // indirect github.com/Masterminds/sprig/v3 v3.2.3 // indirect @@ -82,8 +85,11 @@ require ( github.com/google/go-cmp v0.6.0 // indirect github.com/google/go-containerregistry v0.20.0 // indirect github.com/google/gofuzz v1.2.0 // indirect + github.com/google/s2a-go v0.1.8 // indirect github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 // indirect github.com/google/uuid v1.6.0 // indirect + github.com/googleapis/enterprise-certificate-proxy v0.3.2 // indirect + github.com/googleapis/gax-go/v2 v2.13.0 // indirect github.com/gorilla/mux v1.8.1 // indirect github.com/gosuri/uitable v0.0.4 // indirect github.com/gregjones/httpcache v0.0.0-20190611155906-901d90724c79 // indirect @@ -107,7 +113,7 @@ require ( github.com/mailru/easyjson v0.7.7 // indirect github.com/mattn/go-colorable v0.1.13 // indirect github.com/mattn/go-isatty v0.0.20 // indirect - github.com/mattn/go-runewidth v0.0.15 // indirect + github.com/mattn/go-runewidth v0.0.16 // indirect github.com/mitchellh/copystructure v1.2.0 // indirect github.com/mitchellh/go-homedir v1.1.0 // indirect github.com/mitchellh/go-wordwrap v1.0.1 // indirect @@ -136,13 +142,13 @@ require ( github.com/rivo/uniseg v0.4.7 // indirect github.com/rubenv/sql-migrate v1.6.0 // indirect github.com/russross/blackfriday/v2 v2.1.0 // indirect - github.com/sagikazarmark/locafero v0.4.0 // indirect + github.com/sagikazarmark/locafero v0.6.0 // indirect github.com/sagikazarmark/slog-shim v0.1.0 // indirect github.com/shopspring/decimal v1.3.1 // indirect github.com/shurcooL/graphql v0.0.0-20230722043721-ed46e5a46466 // indirect github.com/sourcegraph/conc v0.3.0 // indirect github.com/spf13/afero v1.11.0 // indirect - github.com/spf13/cast v1.6.0 // indirect + github.com/spf13/cast v1.7.0 // indirect github.com/spf13/pflag v1.0.6-0.20210604193023-d5e0c0615ace // indirect github.com/spf13/viper v1.19.0 // indirect github.com/stoewer/go-strcase v1.3.0 // indirect @@ -154,6 +160,7 @@ require ( github.com/xlab/treeprint v1.2.0 // indirect go.etcd.io/etcd/client/pkg/v3 v3.5.15 // indirect go.etcd.io/etcd/client/v3 v3.5.15 // indirect + go.opencensus.io v0.24.0 // indirect go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.53.0 // indirect go.opentelemetry.io/otel v1.28.0 // indirect go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.28.0 // indirect @@ -168,7 +175,6 @@ require ( golang.org/x/crypto v0.26.0 // indirect golang.org/x/exp v0.0.0-20240808152545-0cdaa3abc0fa // indirect golang.org/x/net v0.28.0 // indirect - golang.org/x/oauth2 v0.22.0 // indirect golang.org/x/sync v0.8.0 // indirect golang.org/x/sys v0.25.0 // indirect golang.org/x/text v0.17.0 // indirect @@ -217,9 +223,11 @@ require ( github.com/manifoldco/promptui v0.9.0 github.com/redhat-best-practices-for-k8s/oct v0.0.22 github.com/redhat-best-practices-for-k8s/privileged-daemonset v1.0.34 - github.com/redhat-openshift-ecosystem/openshift-preflight v0.0.0-20240715111135-c9048da99aae + github.com/redhat-openshift-ecosystem/openshift-preflight v0.0.0-20240812211034-2f51d732179a github.com/robert-nix/ansihtml v1.0.1 + golang.org/x/oauth2 v0.22.0 golang.org/x/term v0.24.0 + google.golang.org/api v0.193.0 gopkg.in/yaml.v3 v3.0.1 k8s.io/kubectl v0.30.3 ) diff --git a/go.sum b/go.sum index f89154221..54f115c58 100644 --- a/go.sum +++ b/go.sum @@ -1,9 +1,17 @@ +cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= +cloud.google.com/go/auth v0.9.0 h1:cYhKl1JUhynmxjXfrk4qdPc6Amw7i+GC9VLflgT0p5M= +cloud.google.com/go/auth v0.9.0/go.mod h1:2HsApZBr9zGZhC9QAXsYVYaWk8kNUt37uny+XVKi7wM= +cloud.google.com/go/auth/oauth2adapt v0.2.4 h1:0GWE/FUsXhf6C+jAkWgYm7X9tK8cuEIfy19DBn6B6bY= +cloud.google.com/go/auth/oauth2adapt v0.2.4/go.mod h1:jC/jOpwFP6JBxhB3P5Rr0a9HLMC/Pe3eaL4NmdvqPtc= +cloud.google.com/go/compute/metadata v0.5.0 h1:Zr0eK8JbFv6+Wi4ilXAR8FJ3wyNdpxHKJNPos6LTZOY= +cloud.google.com/go/compute/metadata v0.5.0/go.mod h1:aHnloV2TPI38yx4s9+wAZhHykWvVCfu7hQbF+9CWoiY= github.com/AdaLogics/go-fuzz-headers v0.0.0-20230811130428-ced1acdcaa24 h1:bvDV9vkmnHYOMsOr4WLk+Vo07yKIzd94sVoIqshQ4bU= github.com/AdaLogics/go-fuzz-headers v0.0.0-20230811130428-ced1acdcaa24/go.mod h1:8o94RPi1/7XTJvwPpRSzSUedZrtlirdB3r9Z20bi2f8= github.com/Azure/go-ansiterm v0.0.0-20230124172434-306776ec8161 h1:L/gRVlceqvL25UVaW/CKtUDjefjrs0SPonmDGUVOYP0= github.com/Azure/go-ansiterm v0.0.0-20230124172434-306776ec8161/go.mod h1:xomTg63KZ2rFqZQzSB4Vz2SUXa1BpHTVz9L5PTmPC4E= -github.com/BurntSushi/toml v1.3.2 h1:o7IhLm0Msx3BaB+n3Ag7L8EVlByGnpq14C4YWiu/gL8= -github.com/BurntSushi/toml v1.3.2/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ= +github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= +github.com/BurntSushi/toml v1.4.1-0.20240526193622-a339e1f7089c h1:pxW6RcqyfI9/kWtOwnv/G+AzdKuy2ZrqINhenH4HyNs= +github.com/BurntSushi/toml v1.4.1-0.20240526193622-a339e1f7089c/go.mod h1:ukJfTF/6rtPPRCnwkur4qwRxa8vTRFBF0uk2lLoLwho= github.com/DATA-DOG/go-sqlmock v1.5.2 h1:OcvFkGmslmlZibjAjaHm3L//6LiuBgolP7OputlJIzU= github.com/DATA-DOG/go-sqlmock v1.5.2/go.mod h1:88MAG/4G7SMwSE3CeA0ZKzrT5CiOU3OJ+JlNzwDqpNU= github.com/MakeNowJust/heredoc v1.0.0 h1:cXCdzVdstXyiTqTvfqk9SDHpKNjxuom+DOlyEeQ4pzQ= @@ -53,6 +61,7 @@ github.com/bugsnag/panicwrap v0.0.0-20151223152923-e2c28503fcd0 h1:nvj0OLI3YqYXe github.com/bugsnag/panicwrap v0.0.0-20151223152923-e2c28503fcd0/go.mod h1:D/8v3kj0zr8ZAKg1AQ6crr+5VwKN5eIywRkfhyM/+dE= github.com/cenkalti/backoff/v4 v4.3.0 h1:MyRJ/UdXutAwSAT+s3wNd7MfTIcy71VQueUuFK343L8= github.com/cenkalti/backoff/v4 v4.3.0/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE= +github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs= github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/chai2010/gettext-go v1.0.2 h1:1Lwwip6Q2QGsAdl/ZKPCwTe9fe0CjlUbqj5bFNSjIRk= @@ -66,6 +75,8 @@ github.com/chzyer/readline v1.5.1/go.mod h1:Eh+b79XXUwfKfcPLepksvw2tcLE/Ct21YObk github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= github.com/chzyer/test v1.0.0 h1:p3BQDXSxOhOG0P9z6/hGnII4LGiEPOYBhs8asl/fC04= github.com/chzyer/test v1.0.0/go.mod h1:2JlltgoNkt4TW/z9V/IzDdFaMTM2JPIi26O1pF38GC8= +github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= +github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= github.com/containerd/cgroups v1.1.0 h1:v8rEWFl6EoqHB+swVNjVoCJE8o3jX7e8nqBGPLaDFBM= github.com/containerd/cgroups v1.1.0/go.mod h1:6ppBcbh/NOOUU+dMKrykgaBnK9lCIBxHqJDGwsa1mIw= github.com/containerd/containerd v1.7.12 h1:+KQsnv4VnzyxWcfO9mlxxELaoztsDEjOuCMPAuPqgU0= @@ -114,6 +125,10 @@ github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkp github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto= github.com/emicklei/go-restful/v3 v3.12.1 h1:PJMDIM/ak7btuL8Ex0iYET9hxM3CI2sjZtzpL63nKAU= github.com/emicklei/go-restful/v3 v3.12.1/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc= +github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= +github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= +github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= +github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= github.com/evanphx/json-patch v5.7.0+incompatible h1:vgGkfT/9f8zE6tvSCe74nfpAVDQ2tG6yudJd8LBksgI= github.com/evanphx/json-patch v5.7.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk= github.com/evanphx/json-patch/v5 v5.9.0 h1:kcBlZQbplgElYIlo/n1hJbls2z/1awpXxpRi0/FOJfg= @@ -166,11 +181,21 @@ github.com/gobwas/glob v0.2.3/go.mod h1:d3Ez4x06l9bZtSvzIay5+Yzi0fmZzPgnTbPcKjJA github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= +github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= +github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da h1:oI5xCqsCo564l8iNU+DwB5epxmsaqB+rhGL0m5jtYqE= github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= +github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= +github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs= +github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w= +github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= +github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8= +github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek= github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps= github.com/gomodule/redigo v1.8.2 h1:H5XSIre1MB5NbPYFp+i1NBbb5qN1W8Y8YAQoAYbkm8k= @@ -181,7 +206,12 @@ github.com/google/cel-go v0.21.0 h1:cl6uW/gxN+Hy50tNYvI691+sXxioCnstFzLp2WO4GCI= github.com/google/cel-go v0.21.0/go.mod h1:rHUlWCcBKgyEk+eV03RPdZUekPp6YcJwV0FxuUksYxc= github.com/google/gnostic-models v0.6.8 h1:yo/ABAfM5IMRsS1VnXjTBvUb61tFIHozhlYvRgGre9I= github.com/google/gnostic-models v0.6.8/go.mod h1:5n7qKqH0f5wFt+aWF8CW6pZLLNOfYuF5OpfBSENuI8U= +github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= +github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= +github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.3/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= @@ -192,11 +222,18 @@ github.com/google/gofuzz v1.2.0 h1:xRy4A+RhZaiKjJ1bPfwQ8sedCA+YS2YcCHW6ec7JMi0= github.com/google/gofuzz v1.2.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/pprof v0.0.0-20240727154555-813a5fbdbec8 h1:FKHo8hFI3A+7w0aUQuYXQ+6EN5stWmeY/AZqtM8xk9k= github.com/google/pprof v0.0.0-20240727154555-813a5fbdbec8/go.mod h1:K1liHPHnj73Fdn/EKuT8nrFqBihUSKXoLYU0BuatOYo= +github.com/google/s2a-go v0.1.8 h1:zZDs9gcbt9ZPLV0ndSyQk6Kacx2g/X+SKYovpnz3SMM= +github.com/google/s2a-go v0.1.8/go.mod h1:6iNWHTpQ+nfNRN5E00MSdfDwVesa8hhS32PhPO8deJA= github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 h1:El6M4kTTCOh6aBiKaUGG7oYTSPP8MxqL4YI3kZKwcP4= github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510/go.mod h1:pupxD2MaaD3pAXIBCelhxNneeOaAeabZDe5s4K6zSpQ= github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/googleapis/enterprise-certificate-proxy v0.3.2 h1:Vie5ybvEvT75RniqhfFxPRy3Bf7vr3h0cechB90XaQs= +github.com/googleapis/enterprise-certificate-proxy v0.3.2/go.mod h1:VLSiSSBs/ksPL8kq3OBOQ6WRI2QnaFynd1DCjZ62+V0= +github.com/googleapis/gax-go/v2 v2.13.0 h1:yitjD5f7jQHhyDsnhKEBU52NdvvdSeGzlAnDPT0hH1s= +github.com/googleapis/gax-go/v2 v2.13.0/go.mod h1:Z/fvTZXF8/uw7Xu5GuslPw+bplx6SS338j1Is2S+B7A= github.com/gorilla/handlers v1.5.1 h1:9lRY6j8DEeeBT10CvO9hGW0gmky0BprnvDI5vfhUHH4= github.com/gorilla/handlers v1.5.1/go.mod h1:t8XrUpc4KVXb7HGyJ4/cEnwQiaxrX/hz1Zv/4g96P1Q= github.com/gorilla/mux v1.8.1 h1:TuBL49tXwgrFYWhqrNgrUNEY92u81SPhu7sTdzQEiWY= @@ -275,8 +312,8 @@ github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovk github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= -github.com/mattn/go-runewidth v0.0.15 h1:UNAjwbU9l54TA3KzvqLGxwWjHmMgBUVhBiTjelZgg3U= -github.com/mattn/go-runewidth v0.0.15/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= +github.com/mattn/go-runewidth v0.0.16 h1:E5ScNMtiwvlvB5paMFdw9p4kSQzbXFikJ5SQO6TULQc= +github.com/mattn/go-runewidth v0.0.16/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= github.com/mattn/go-sqlite3 v1.14.6/go.mod h1:NyWgC/yNuGj7Q9rpYnZvas74GogHl5/Z4A/KQRfk6bU= github.com/mattn/go-sqlite3 v1.14.16 h1:yOQRA0RpS5PFz/oikGwBEqvAWhWg5ufRz4ETLjwpU1Y= github.com/mattn/go-sqlite3 v1.14.16/go.mod h1:2eHXhiwb8IkHr+BDWZGa96P6+rkvnG63S2DGjv9HUNg= @@ -362,6 +399,7 @@ github.com/prometheus/client_golang v1.20.0 h1:jBzTZ7B099Rg24tny+qngoynol8LtVYlA github.com/prometheus/client_golang v1.20.0/go.mod h1:PIEt8X02hGcP8JWbeHyeZ53Y/jReSnHgO035n//V5WE= github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= +github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= github.com/prometheus/client_model v0.6.1 h1:ZKSh/rekM+n3CeS952MLRAdFwIKqeY8b62p8ais2e9E= github.com/prometheus/client_model v0.6.1/go.mod h1:OrxVMOVHjw3lKMa8+x6HeMGkHMQyHDk9E3jmP2AmGiY= github.com/prometheus/common v0.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= @@ -394,8 +432,8 @@ github.com/rubenv/sql-migrate v1.6.0 h1:IZpcTlAx/VKXphWEpwWJ7BaMq05tYtE80zYz+8a5 github.com/rubenv/sql-migrate v1.6.0/go.mod h1:m3ilnKP7sNb4eYkLsp6cGdPOl4OBcXM6rcbzU+Oqc5k= github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk= github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= -github.com/sagikazarmark/locafero v0.4.0 h1:HApY1R9zGo4DBgr7dqsTH/JJxLTTsOt7u6keLGt6kNQ= -github.com/sagikazarmark/locafero v0.4.0/go.mod h1:Pe1W6UlPYUk/+wc/6KFhbORCfqzgYEpgQ3O5fPuL3H4= +github.com/sagikazarmark/locafero v0.6.0 h1:ON7AQg37yzcRPU69mt7gwhFEBwxI6P9T4Qu3N51bwOk= +github.com/sagikazarmark/locafero v0.6.0/go.mod h1:77OmuIc6VTraTXKXIs/uvUxKGUXjE1GbemJYHqdNjX0= github.com/sagikazarmark/slog-shim v0.1.0 h1:diDBnUNK9N/354PgrxMywXnAwEr1QZcOr6gto+ugjYE= github.com/sagikazarmark/slog-shim v0.1.0/go.mod h1:SrcSrq8aKtyuqEI1uvTDTK1arOWRIczQRv+GVI1AkeQ= github.com/sergi/go-diff v1.1.0 h1:we8PVUC3FE2uYfodKH/nBHMSetSfHDR6scGdBi+erh0= @@ -413,8 +451,8 @@ github.com/sourcegraph/conc v0.3.0/go.mod h1:Sdozi7LEKbFPqYX2/J+iBAM6HpqSLTASQIK github.com/spf13/afero v1.11.0 h1:WJQKhtpdm3v2IzqG8VMqrr6Rf3UYpEF239Jy9wNepM8= github.com/spf13/afero v1.11.0/go.mod h1:GH9Y3pIexgf1MTIWtNGyogA5MwRIDXGUr+hbWNoBjkY= github.com/spf13/cast v1.3.1/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= -github.com/spf13/cast v1.6.0 h1:GEiTHELF+vaR5dhz3VqZfFSzZjYbgeKDpBxQVS4GYJ0= -github.com/spf13/cast v1.6.0/go.mod h1:ancEpBxwJDODSW/UG4rDrAqiKolqNNh2DX3mk86cAdo= +github.com/spf13/cast v1.7.0 h1:ntdiHjuueXFgm5nzDRdOS4yfT43P5Fnud6DH50rz/7w= +github.com/spf13/cast v1.7.0/go.mod h1:ancEpBxwJDODSW/UG4rDrAqiKolqNNh2DX3mk86cAdo= github.com/spf13/cobra v1.8.1 h1:e5/vxKd/rZsfSJMUX1agtjeTDf+qv1/JdBF8gg5k9ZM= github.com/spf13/cobra v1.8.1/go.mod h1:wHxEcudfqmLYa8iTfL+OuZPbBZkmvliBWKIezN3kD9Y= github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= @@ -508,26 +546,37 @@ golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5y golang.org/x/crypto v0.3.0/go.mod h1:hebNnKkNXi2UzZN1eVRvBB7co0a+JxK6XbPiWVs/3J4= golang.org/x/crypto v0.26.0 h1:RrRspgV4mU+YwB4FYnuBoKsUapNIL5cohGAmSH3azsw= golang.org/x/crypto v0.26.0/go.mod h1:GY7jblb9wI+FOo5y8/S2oY4zWP07AkOJ4+jxCqdqn54= +golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20240808152545-0cdaa3abc0fa h1:ELnwvuAXPNtPk1TJRuGkI9fDTwym6AYBu0qzT8AcHdI= golang.org/x/exp v0.0.0-20240808152545-0cdaa3abc0fa/go.mod h1:akd2r19cwCdwSwWeIdzYQGa/EZZyqcOdwWiwj5L5eKQ= +golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= +golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= +golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= golang.org/x/mod v0.20.0 h1:utOm6MM3R3dnawAiJgn0y+xvuYRsm1RKM/4giyfDgV0= golang.org/x/mod v0.20.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= +golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= +golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= golang.org/x/net v0.2.0/go.mod h1:KqCZLdyyvdV855qA2rE3GC2aiw5xGR5TEjj8smXukLY= golang.org/x/net v0.28.0 h1:a9JDOJc5GMUJ0+UDqmLT86WiEy7iWyIhz8gz8E4e5hE= golang.org/x/net v0.28.0/go.mod h1:yqtgsTWOOnlGLG9GFRrK3++bGOUEkNBoHZc8MEDWPNg= +golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.22.0 h1:BzDx2FehcG7jJwgWLELCdmLuxk2i+x9UDpSiss2u0ZA= golang.org/x/oauth2 v0.22.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbhtI= +golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= @@ -536,6 +585,7 @@ golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJ golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.8.0 h1:3NFvSEYkUoMifnESzZl15y791HH1qU2xm6eCJU5ZPXQ= golang.org/x/sync v0.8.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= +golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20181122145206-62eef0e2fa9b/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= @@ -569,6 +619,10 @@ golang.org/x/text v0.17.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY= golang.org/x/time v0.6.0 h1:eTDhh4ZXt5Qf0augr54TN6suAUudPcawVZeIAPU7D4U= golang.org/x/time v0.6.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= +golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= @@ -583,12 +637,33 @@ golang.org/x/xerrors v0.0.0-20231012003039-104605ab7028 h1:+cNy6SZtPcJQH3LJVLOSm golang.org/x/xerrors v0.0.0-20231012003039-104605ab7028/go.mod h1:NDW/Ps6MPRej6fsCIbMTohpP40sJ/P/vI1MoTEGwX90= gomodules.xyz/jsonpatch/v2 v2.4.0 h1:Ci3iUJyx9UeRx7CeFN8ARgGbkESwJK+KB9lLcWxY/Zw= gomodules.xyz/jsonpatch/v2 v2.4.0/go.mod h1:AH3dM2RI6uoBZxn3LVrfvJ3E0/9dG4cSrbuBJT4moAY= +google.golang.org/api v0.193.0 h1:eOGDoJFsLU+HpCBaDJex2fWiYujAw9KbXgpOAMePoUs= +google.golang.org/api v0.193.0/go.mod h1:Po3YMV1XZx+mTku3cfJrlIYR03wiGrCOsdpC67hjZvw= +google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= +google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= +google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= +google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= +google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo= google.golang.org/genproto/googleapis/api v0.0.0-20240814211410-ddb44dafa142 h1:wKguEg1hsxI2/L3hUYrpo1RVi48K+uTyzKqprwLXsb8= google.golang.org/genproto/googleapis/api v0.0.0-20240814211410-ddb44dafa142/go.mod h1:d6be+8HhtEtucleCbxpPW9PA9XwISACu8nvpPqF0BVo= google.golang.org/genproto/googleapis/rpc v0.0.0-20240814211410-ddb44dafa142 h1:e7S5W7MGGLaSu8j3YjdezkZ+m1/Nm0uRVRMEMGk26Xs= google.golang.org/genproto/googleapis/rpc v0.0.0-20240814211410-ddb44dafa142/go.mod h1:UqMtugtsSgubUsoxbuAoiCXvqvErP7Gf0so0mK9tHxU= +google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= +google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= +google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY= +google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= +google.golang.org/grpc v1.33.2/go.mod h1:JMHMWHQWaTccqQQlmk3MJZS+GWXOdAesneDmEnv2fbc= google.golang.org/grpc v1.65.0 h1:bs/cUb4lp1G5iImFFd3u5ixQzweKizoZJAwBNLR42lc= google.golang.org/grpc v1.65.0/go.mod h1:WgYC2ypjlB0EiQi6wdKixMqukr6lBc0Vo+oOgjrM5ZQ= +google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= +google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= +google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= +google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE= +google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo= +google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= +google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= +google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= +google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c= google.golang.org/protobuf v1.34.2 h1:6xV6lTsCfpGD21XK49h7MhtcApnLqkfYgPcdHftf6hg= google.golang.org/protobuf v1.34.2/go.mod h1:qYOHts0dSfpeUzUFpOMr/WGzszTmLH+DiWniOlNbLDw= gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= @@ -618,6 +693,8 @@ gotest.tools/v3 v3.5.1 h1:EENdUnS3pdur5nybKYIh2Vfgc8IUNBjxDPSjtiJcOzU= gotest.tools/v3 v3.5.1/go.mod h1:isy3WKz7GK6uNw/sbHzfKBLvlvXwUyV06n6brMxxopU= helm.sh/helm/v3 v3.15.4 h1:UFHd6oZ1IN3FsUZ7XNhOQDyQ2QYknBNWRHH57e9cbHY= helm.sh/helm/v3 v3.15.4/go.mod h1:phOwlxqGSgppCY/ysWBNRhG3MtnpsttOzxaTK+Mt40E= +honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= k8s.io/api v0.31.0 h1:b9LiSjR2ym/SzTOlfMHm1tr7/21aD7fSkqgD/CVJBCo= k8s.io/api v0.31.0/go.mod h1:0YiFF+JfFxMM6+1hQei8FY8M7s1Mth+z/q7eF1aJkTE= k8s.io/apiextensions-apiserver v0.31.0 h1:fZgCVhGwsclj3qCw1buVXCV6khjRzKC5eCFt24kyLSk=