From 5b733024e965e59d4bd1854d9dedd6e070c71edd Mon Sep 17 00:00:00 2001 From: Fred Reinink Date: Sat, 23 Aug 2025 15:39:35 -0600 Subject: [PATCH 1/4] Add empty download-all command --- .../securefile/download-all/download-all.go | 46 +++++++++++++++++++ internal/commands/securefile/securefile.go | 2 + 2 files changed, 48 insertions(+) create mode 100644 internal/commands/securefile/download-all/download-all.go diff --git a/internal/commands/securefile/download-all/download-all.go b/internal/commands/securefile/download-all/download-all.go new file mode 100644 index 000000000..3ad287ce2 --- /dev/null +++ b/internal/commands/securefile/download-all/download-all.go @@ -0,0 +1,46 @@ +package downloadall + +import ( + "github.com/MakeNowJust/heredoc/v2" + "github.com/spf13/cobra" + + "gitlab.com/gitlab-org/cli/internal/cmdutils" +) + +type options struct { + dir string +} + +func NewCmdDownloadAll(f cmdutils.Factory) *cobra.Command { + opts := &options{} + + securefileDownloadAllCmd := &cobra.Command{ + Use: "download-all [flags]", + Short: `Download all secure files for a project.`, + Example: heredoc.Doc(` + Download all (liimit 100) secure files for a project to the current directory and verify checksums. + - glab securefile download-all + + Download all (limit 100) secure files for a project to the current directory and verify checksums to a given path. + - glab securefile download-all --path="securefiles/" + `), + Long: heredoc.Doc(` + Download all secure files for a project, and verify their checksums. + A maximum of 100 secure files can be downloaded. + Files are saved to the specified directory with their original names. + If no directory is specified, files are downloaded to the current directory. + `), + Args: cobra.NoArgs, + RunE: func(cmd *cobra.Command, args []string) error { + // client, err := f.GitLabClient() + // if err != nil { + // return err + // } + // return nil + }, + } + + securefileDownloadAllCmd.Flags().StringVarP(&opts.dir, "path", "p", ".", "Path to download the secure files to") + + return securefileDownloadAllCmd +} diff --git a/internal/commands/securefile/securefile.go b/internal/commands/securefile/securefile.go index bb540bd93..e9fa391fa 100644 --- a/internal/commands/securefile/securefile.go +++ b/internal/commands/securefile/securefile.go @@ -7,6 +7,7 @@ import ( securefileCreateCmd "gitlab.com/gitlab-org/cli/internal/commands/securefile/create" securefileDownloadCmd "gitlab.com/gitlab-org/cli/internal/commands/securefile/download" + securefileDownloadAllCmd "gitlab.com/gitlab-org/cli/internal/commands/securefile/download-all" securefileGetCmd "gitlab.com/gitlab-org/cli/internal/commands/securefile/get" securefileListCmd "gitlab.com/gitlab-org/cli/internal/commands/securefile/list" securefileRemoveCmd "gitlab.com/gitlab-org/cli/internal/commands/securefile/remove" @@ -28,6 +29,7 @@ func NewCmdSecurefile(f cmdutils.Factory) *cobra.Command { securefileCmd.AddCommand(securefileCreateCmd.NewCmdCreate(f)) securefileCmd.AddCommand(securefileDownloadCmd.NewCmdDownload(f)) + securefileCmd.AddCommand(securefileDownloadAllCmd.NewCmdDownloadAll(f)) securefileCmd.AddCommand(securefileGetCmd.NewCmdGet(f)) securefileCmd.AddCommand(securefileListCmd.NewCmdList(f)) securefileCmd.AddCommand(securefileRemoveCmd.NewCmdRemove(f)) -- GitLab From 1ba1143888abb9d4af397e848f2c53327258de54 Mon Sep 17 00:00:00 2001 From: Fred Reinink Date: Wed, 27 Aug 2025 14:40:25 -0600 Subject: [PATCH 2/4] Add download-all functionality --- .../securefile/download-all/download-all.go | 74 +++++++++++++++++-- .../commands/securefile/download/download.go | 4 +- 2 files changed, 71 insertions(+), 7 deletions(-) diff --git a/internal/commands/securefile/download-all/download-all.go b/internal/commands/securefile/download-all/download-all.go index 3ad287ce2..b1efb3509 100644 --- a/internal/commands/securefile/download-all/download-all.go +++ b/internal/commands/securefile/download-all/download-all.go @@ -1,10 +1,19 @@ package downloadall import ( + "crypto/sha256" + "encoding/hex" + "fmt" + "os" + "github.com/MakeNowJust/heredoc/v2" + securejoin "github.com/cyphar/filepath-securejoin" "github.com/spf13/cobra" + gitlab "gitlab.com/gitlab-org/api/client-go" + "gitlab.com/gitlab-org/cli/internal/api" "gitlab.com/gitlab-org/cli/internal/cmdutils" + "gitlab.com/gitlab-org/cli/internal/commands/securefile/download" ) type options struct { @@ -32,11 +41,51 @@ func NewCmdDownloadAll(f cmdutils.Factory) *cobra.Command { `), Args: cobra.NoArgs, RunE: func(cmd *cobra.Command, args []string) error { - // client, err := f.GitLabClient() - // if err != nil { - // return err - // } - // return nil + client, err := f.GitLabClient() + if err != nil { + return err + } + + repo, err := f.BaseRepo() + if err != nil { + return err + } + + l := &gitlab.ListProjectSecureFilesOptions{ + Page: 1, + PerPage: api.MaxPerPage, + } + + files, _, err := client.SecureFiles.ListProjectSecureFiles(repo.FullName(), l) + if err != nil { + return fmt.Errorf("Error fetching list of secure files: %v", err) + } + + if len(files) == 0 { + return nil + } + + path, err := cmd.Flags().GetString("path") + if err != nil { + return fmt.Errorf("Unable to get path flag: %v", err) + } + + for _, file := range files { + filePath, err := securejoin.SecureJoin(path, file.Name) + if err != nil { + return err + } + + if err := download.SaveFile(client, repo, file.ID, filePath); err != nil { + return err + } + + if err := verifyChecksum(*file, filePath); err != nil { + return err + } + } + + return nil }, } @@ -44,3 +93,18 @@ func NewCmdDownloadAll(f cmdutils.Factory) *cobra.Command { return securefileDownloadAllCmd } + +func verifyChecksum(file gitlab.SecureFile, filePath string) error { + body, err := os.ReadFile(filePath) + if err != nil { + return err + } + + sum := sha256.Sum256(body) + + if hex.EncodeToString(sum[:]) == file.Checksum { + return nil + } + + return fmt.Errorf("Failure validating checksum for %s", file.Name) +} diff --git a/internal/commands/securefile/download/download.go b/internal/commands/securefile/download/download.go index 7692d6b4c..66f6fe8fb 100644 --- a/internal/commands/securefile/download/download.go +++ b/internal/commands/securefile/download/download.go @@ -49,7 +49,7 @@ func NewCmdDownload(f cmdutils.Factory) *cobra.Command { return fmt.Errorf("Unable to get path flag: %v", err) } - err = saveFile(client, repo, fileID, path) + err = SaveFile(client, repo, fileID, path) if err != nil { return err } @@ -62,7 +62,7 @@ func NewCmdDownload(f cmdutils.Factory) *cobra.Command { return securefileDownloadCmd } -func saveFile(apiClient *gitlab.Client, repo glrepo.Interface, fileID int, path string) error { +func SaveFile(apiClient *gitlab.Client, repo glrepo.Interface, fileID int, path string) error { contents, _, err := apiClient.SecureFiles.DownloadSecureFile(repo.FullName(), fileID) if err != nil { return fmt.Errorf("Error downloading secure file: %v", err) -- GitLab From fd6d1b9782ae05650aff1168afa59c5603f3acd3 Mon Sep 17 00:00:00 2001 From: Fred Reinink Date: Wed, 27 Aug 2025 14:42:37 -0600 Subject: [PATCH 3/4] Move directory creation into own function --- .../securefile/download-all/download-all.go | 5 ++++ .../commands/securefile/download/download.go | 23 +++++++++++++------ 2 files changed, 21 insertions(+), 7 deletions(-) diff --git a/internal/commands/securefile/download-all/download-all.go b/internal/commands/securefile/download-all/download-all.go index b1efb3509..dd4c69ce6 100644 --- a/internal/commands/securefile/download-all/download-all.go +++ b/internal/commands/securefile/download-all/download-all.go @@ -70,6 +70,11 @@ func NewCmdDownloadAll(f cmdutils.Factory) *cobra.Command { return fmt.Errorf("Unable to get path flag: %v", err) } + err = download.CreateDirectory(path) + if err != nil { + return err + } + for _, file := range files { filePath, err := securejoin.SecureJoin(path, file.Name) if err != nil { diff --git a/internal/commands/securefile/download/download.go b/internal/commands/securefile/download/download.go index 66f6fe8fb..5569d819b 100644 --- a/internal/commands/securefile/download/download.go +++ b/internal/commands/securefile/download/download.go @@ -49,6 +49,11 @@ func NewCmdDownload(f cmdutils.Factory) *cobra.Command { return fmt.Errorf("Unable to get path flag: %v", err) } + err = CreateDirectory(path) + if err != nil { + return err + } + err = SaveFile(client, repo, fileID, path) if err != nil { return err @@ -62,19 +67,23 @@ func NewCmdDownload(f cmdutils.Factory) *cobra.Command { return securefileDownloadCmd } -func SaveFile(apiClient *gitlab.Client, repo glrepo.Interface, fileID int, path string) error { - contents, _, err := apiClient.SecureFiles.DownloadSecureFile(repo.FullName(), fileID) - if err != nil { - return fmt.Errorf("Error downloading secure file: %v", err) - } - - // Ensure directory exists +func CreateDirectory(path string) error { dir := filepath.Dir(path) if dir != "." { if err := os.MkdirAll(dir, 0o755); err != nil { return fmt.Errorf("Error creating directory: %v", err) } } + + return nil +} + +func SaveFile(apiClient *gitlab.Client, repo glrepo.Interface, fileID int, path string) error { + contents, _, err := apiClient.SecureFiles.DownloadSecureFile(repo.FullName(), fileID) + if err != nil { + return fmt.Errorf("Error downloading secure file: %v", err) + } + file, err := os.Create(path) if err != nil { return fmt.Errorf("Error creating file: %v", err) -- GitLab From ee3108676aec373534c1af0bb02261aa80801e88 Mon Sep 17 00:00:00 2001 From: Fred Reinink Date: Wed, 27 Aug 2025 14:47:56 -0600 Subject: [PATCH 4/4] Generate docs --- docs/source/securefile/download-all.md | 49 ++++++++++++++++++++++++++ docs/source/securefile/index.md | 1 + 2 files changed, 50 insertions(+) create mode 100644 docs/source/securefile/download-all.md diff --git a/docs/source/securefile/download-all.md b/docs/source/securefile/download-all.md new file mode 100644 index 000000000..0db179fb5 --- /dev/null +++ b/docs/source/securefile/download-all.md @@ -0,0 +1,49 @@ +--- +stage: Create +group: Code Review +info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/product/ux/technical-writing/#assignments +--- + + + +# `glab securefile download-all` + +Download all secure files for a project. + +## Synopsis + + Download all secure files for a project, and verify their checksums. +A maximum of 100 secure files can be downloaded. +Files are saved to the specified directory with their original names. +If no directory is specified, files are downloaded to the current directory. + +```plaintext +glab securefile download-all [flags] +``` + +## Examples + +```console +Download all (liimit 100) secure files for a project to the current directory and verify checksums. +- glab securefile download-all + +Download all (limit 100) secure files for a project to the current directory and verify checksums to a given path. +- glab securefile download-all --path="securefiles/" + +``` + +## Options + +```plaintext + -p, --path string Path to download the secure files to (default ".") +``` + +## Options inherited from parent commands + +```plaintext + -h, --help Show help for this command. + -R, --repo OWNER/REPO Select another repository. Can use either OWNER/REPO or `GROUP/NAMESPACE/REPO` format. Also accepts full URL or Git URL. +``` diff --git a/docs/source/securefile/index.md b/docs/source/securefile/index.md index 1ef41a549..338194752 100644 --- a/docs/source/securefile/index.md +++ b/docs/source/securefile/index.md @@ -36,6 +36,7 @@ and binary files are supported, but they must be smaller than 5 MB. - [`create`](create.md) - [`download`](download.md) +- [`download-all`](download-all.md) - [`get`](get.md) - [`list`](list.md) - [`remove`](remove.md) -- GitLab