feat: pr approve/reject, repo migrate/template, secret stdin fix, docs
- fgj pr approve / pr reject: thin shortcuts over 'pr review --approve'
and '--request-changes'. Reject requires a body.
- fgj repo migrate: wrap SDK MigrateRepo. Supports git, github, gitlab,
gitea, gogs services; mirror mode with --mirror-interval; selective
import (wiki/labels/milestones/issues/PRs/releases/LFS); auth via
--auth-token or --auth-username/--auth-password. Defaults owner to
the authenticated user.
- fgj repo create-from-template: wrap SDK CreateRepoFromTemplate with
fine-grained --with-{content,topics,labels,webhooks,git-hooks,avatar}
flags. Template is owner/name; new repo defaults to the current user.
- Rework 'fgj actions secret create' input. New cmd/secret_input.go
resolves values from --body, --body-file (path or '-'), hidden TTY
prompt via term.ReadPassword, or piped stdin. Trims trailing
whitespace, rejects empty values. Replaces fmt.Scanln which broke on
spaces/newlines and echoed input.
- CHANGELOG: v0.4.0 Unreleased section documenting all additions,
changes, and development items.
- README: updated feature list with new commands.
This commit is contained in:
parent
424fb63a8b
commit
4eeef2ceca
7 changed files with 551 additions and 23 deletions
|
|
@ -251,12 +251,26 @@ var actionsSecretListCmd = &cobra.Command{
|
|||
var actionsSecretCreateCmd = &cobra.Command{
|
||||
Use: "create <name>",
|
||||
Short: "Create or update a repository secret",
|
||||
Long: "Create or update a secret for Forgejo Actions. The secret value will be read from stdin.",
|
||||
Example: ` # Create a secret (will prompt for value)
|
||||
Long: `Create or update a secret for Forgejo Actions.
|
||||
|
||||
The secret value is read from the first available source:
|
||||
1. --body <value>
|
||||
2. --body-file <path> (use "-" for stdin)
|
||||
3. interactive prompt (hidden input) if stdin is a TTY
|
||||
4. stdin (when piped)
|
||||
|
||||
Trailing newlines are trimmed. Empty values are rejected.`,
|
||||
Example: ` # Interactive (hidden prompt)
|
||||
fgj actions secret create DEPLOY_TOKEN
|
||||
|
||||
# Create a secret for a specific repo
|
||||
fgj actions secret create API_KEY -R owner/repo`,
|
||||
# Pipe a value
|
||||
op read op://vault/github/token | fgj actions secret create GH_TOKEN --body-file -
|
||||
|
||||
# Read from a file
|
||||
fgj actions secret create TLS_KEY --body-file ./server.key
|
||||
|
||||
# Inline (visible in shell history — use sparingly)
|
||||
fgj actions secret create DEBUG_FLAG --body "on"`,
|
||||
Args: cobra.ExactArgs(1),
|
||||
RunE: runActionsSecretCreate,
|
||||
}
|
||||
|
|
@ -397,6 +411,8 @@ func init() {
|
|||
// Add flags for secret commands
|
||||
addRepoFlags(actionsSecretListCmd)
|
||||
addRepoFlags(actionsSecretCreateCmd)
|
||||
actionsSecretCreateCmd.Flags().String("body", "", "Secret value (visible in shell history)")
|
||||
actionsSecretCreateCmd.Flags().String("body-file", "", "Read secret value from file, or '-' for stdin")
|
||||
addRepoFlags(actionsSecretDeleteCmd)
|
||||
|
||||
// Add flags for variable commands
|
||||
|
|
@ -1254,12 +1270,9 @@ func runActionsSecretCreate(cmd *cobra.Command, args []string) error {
|
|||
|
||||
secretName := args[0]
|
||||
|
||||
// Read secret value from stdin
|
||||
fmt.Fprint(ios.ErrOut, "Enter secret value: ")
|
||||
var secretValue string
|
||||
_, err = fmt.Scanln(&secretValue)
|
||||
secretValue, err := readSecretValue(cmd, secretName)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to read secret value: %w", err)
|
||||
return err
|
||||
}
|
||||
|
||||
opt := gitea.CreateSecretOption{
|
||||
|
|
@ -1267,12 +1280,12 @@ func runActionsSecretCreate(cmd *cobra.Command, args []string) error {
|
|||
Data: secretValue,
|
||||
}
|
||||
|
||||
_, err = client.CreateRepoActionSecret(owner, name, opt)
|
||||
if err != nil {
|
||||
if _, err := client.CreateRepoActionSecret(owner, name, opt); err != nil {
|
||||
return fmt.Errorf("failed to create secret: %w", err)
|
||||
}
|
||||
|
||||
fmt.Fprintf(ios.Out, "Secret '%s' created successfully\n", secretName)
|
||||
cs := ios.ColorScheme()
|
||||
fmt.Fprintf(ios.Out, "%s Secret %q created\n", cs.SuccessIcon(), secretName)
|
||||
return nil
|
||||
}
|
||||
|
||||
|
|
|
|||
102
cmd/pr_approve_reject.go
Normal file
102
cmd/pr_approve_reject.go
Normal file
|
|
@ -0,0 +1,102 @@
|
|||
package cmd
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"code.gitea.io/sdk/gitea"
|
||||
"forgejo.zerova.net/public/fgj-sid/internal/api"
|
||||
"forgejo.zerova.net/public/fgj-sid/internal/config"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
var prApproveCmd = &cobra.Command{
|
||||
Use: "approve <number>",
|
||||
Aliases: []string{"lgtm"},
|
||||
Short: "Approve a pull request",
|
||||
Long: "Shortcut for 'fgj pr review <n> --approve'. Body is optional.",
|
||||
Example: ` # Approve with no body
|
||||
fgj pr approve 42
|
||||
|
||||
# Approve with a message
|
||||
fgj pr approve 42 -b "Thanks, shipping."`,
|
||||
Args: cobra.ExactArgs(1),
|
||||
RunE: runPRApproveReject(gitea.ReviewStateApproved, "approved", false),
|
||||
}
|
||||
|
||||
var prRejectCmd = &cobra.Command{
|
||||
Use: "reject <number>",
|
||||
Short: "Request changes on a pull request",
|
||||
Long: "Shortcut for 'fgj pr review <n> --request-changes'. Body is required.",
|
||||
Example: ` # Reject with explanation
|
||||
fgj pr reject 42 -b "See the inline comments on auth.go"
|
||||
|
||||
# Reject with a longer message from a file
|
||||
fgj pr reject 42 --body-file feedback.md`,
|
||||
Args: cobra.ExactArgs(1),
|
||||
RunE: runPRApproveReject(gitea.ReviewStateRequestChanges, "reviewed with requested changes", true),
|
||||
}
|
||||
|
||||
func init() {
|
||||
prCmd.AddCommand(prApproveCmd)
|
||||
prCmd.AddCommand(prRejectCmd)
|
||||
|
||||
for _, c := range []*cobra.Command{prApproveCmd, prRejectCmd} {
|
||||
c.Flags().StringP("repo", "R", "", "Repository in owner/name format")
|
||||
c.Flags().StringP("body", "b", "", "Review body/message")
|
||||
c.Flags().String("body-file", "", "Read body from file (use \"-\" for stdin)")
|
||||
addJSONFlags(c, "Output created review as JSON")
|
||||
}
|
||||
}
|
||||
|
||||
func runPRApproveReject(state gitea.ReviewStateType, verb string, requireBody bool) func(cmd *cobra.Command, args []string) error {
|
||||
return func(cmd *cobra.Command, args []string) error {
|
||||
prNumber, err := parseIssueArg(args[0])
|
||||
if err != nil {
|
||||
return fmt.Errorf("invalid pull request number: %w", err)
|
||||
}
|
||||
|
||||
body, err := readBody(cmd)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if requireBody && body == "" {
|
||||
return fmt.Errorf("a body is required (use --body or --body-file)")
|
||||
}
|
||||
|
||||
repo, _ := cmd.Flags().GetString("repo")
|
||||
owner, name, err := parseRepo(repo)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
cfg, err := config.Load()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
client, err := api.NewClientFromConfig(cfg, "", getDetectedHost(), getCwd())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
ios.StartSpinner("Submitting review...")
|
||||
review, _, err := client.CreatePullReview(owner, name, prNumber, gitea.CreatePullReviewOptions{
|
||||
State: state,
|
||||
Body: body,
|
||||
})
|
||||
ios.StopSpinner()
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to submit review: %w", err)
|
||||
}
|
||||
|
||||
if wantJSON(cmd) {
|
||||
return outputJSON(cmd, review)
|
||||
}
|
||||
|
||||
cs := ios.ColorScheme()
|
||||
fmt.Fprintf(ios.Out, "%s PR #%d %s\n", cs.SuccessIcon(), prNumber, verb)
|
||||
if review.HTMLURL != "" {
|
||||
fmt.Fprintf(ios.Out, "View at: %s\n", review.HTMLURL)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
}
|
||||
170
cmd/repo_migrate.go
Normal file
170
cmd/repo_migrate.go
Normal file
|
|
@ -0,0 +1,170 @@
|
|||
package cmd
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"code.gitea.io/sdk/gitea"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
var repoMigrateCmd = &cobra.Command{
|
||||
Use: "migrate <clone-url>",
|
||||
Aliases: []string{"m"},
|
||||
Short: "Migrate a repository from an external service",
|
||||
Long: `Import a repository from GitHub, GitLab, Gogs, Gitea, or a plain Git
|
||||
remote. By default the migration is a one-shot import; pass --mirror
|
||||
to keep syncing on an interval.
|
||||
|
||||
Authentication for the source repo is passed via --auth-token or
|
||||
--auth-username + --auth-password. Neither is stored after the
|
||||
migration completes on the server side.`,
|
||||
Example: ` # Migrate a GitHub repo to this user's account
|
||||
fgj repo migrate https://github.com/cli/cli \
|
||||
--name gh-mirror --service github --auth-token "$GH_TOKEN"
|
||||
|
||||
# Mirror a plain Git remote into an org
|
||||
fgj repo migrate https://example.com/project.git \
|
||||
--name project --owner infrastructure --mirror --mirror-interval 8h
|
||||
|
||||
# Migrate with all content kinds
|
||||
fgj repo migrate https://gitea.com/user/repo \
|
||||
--name repo --service gitea --auth-token "$TOKEN" \
|
||||
--wiki --labels --milestones --issues --pulls --releases --lfs`,
|
||||
Args: cobra.ExactArgs(1),
|
||||
RunE: runRepoMigrate,
|
||||
}
|
||||
|
||||
func init() {
|
||||
repoCmd.AddCommand(repoMigrateCmd)
|
||||
|
||||
repoMigrateCmd.Flags().String("name", "", "Name for the new repository (required)")
|
||||
repoMigrateCmd.Flags().String("owner", "", "Owner (user or org) for the new repository (defaults to you)")
|
||||
repoMigrateCmd.Flags().String("service", "git", "Source service: git, github, gitlab, gitea, gogs")
|
||||
repoMigrateCmd.Flags().StringP("description", "d", "", "Description of the new repository")
|
||||
repoMigrateCmd.Flags().String("auth-token", "", "Auth token for the source repo (preferred over username/password)")
|
||||
repoMigrateCmd.Flags().String("auth-username", "", "Auth username for the source repo")
|
||||
repoMigrateCmd.Flags().String("auth-password", "", "Auth password for the source repo")
|
||||
repoMigrateCmd.Flags().Bool("private", false, "Make the new repository private")
|
||||
repoMigrateCmd.Flags().Bool("mirror", false, "Mirror the source (keep syncing) instead of one-shot import")
|
||||
repoMigrateCmd.Flags().String("mirror-interval", "", "Mirror sync interval (e.g. 8h, 24h); only with --mirror")
|
||||
repoMigrateCmd.Flags().Bool("wiki", false, "Include wiki in the migration")
|
||||
repoMigrateCmd.Flags().Bool("labels", false, "Include labels")
|
||||
repoMigrateCmd.Flags().Bool("milestones", false, "Include milestones")
|
||||
repoMigrateCmd.Flags().Bool("issues", false, "Include issues")
|
||||
repoMigrateCmd.Flags().Bool("pulls", false, "Include pull requests")
|
||||
repoMigrateCmd.Flags().Bool("releases", false, "Include releases")
|
||||
repoMigrateCmd.Flags().Bool("lfs", false, "Include Git LFS content")
|
||||
repoMigrateCmd.Flags().String("lfs-endpoint", "", "Explicit Git LFS server URL")
|
||||
|
||||
_ = repoMigrateCmd.MarkFlagRequired("name")
|
||||
addJSONFlags(repoMigrateCmd, "Output created repository as JSON")
|
||||
}
|
||||
|
||||
func runRepoMigrate(cmd *cobra.Command, args []string) error {
|
||||
cloneURL := args[0]
|
||||
|
||||
repoName, _ := cmd.Flags().GetString("name")
|
||||
if strings.TrimSpace(repoName) == "" {
|
||||
return fmt.Errorf("--name is required")
|
||||
}
|
||||
|
||||
owner, _ := cmd.Flags().GetString("owner")
|
||||
serviceStr, _ := cmd.Flags().GetString("service")
|
||||
|
||||
service, err := parseGitService(serviceStr)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
client, err := loadClient()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Default owner = authenticated user.
|
||||
if owner == "" {
|
||||
user, _, err := client.GetMyUserInfo()
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to resolve current user (pass --owner to override): %w", err)
|
||||
}
|
||||
owner = user.UserName
|
||||
}
|
||||
|
||||
description, _ := cmd.Flags().GetString("description")
|
||||
authToken, _ := cmd.Flags().GetString("auth-token")
|
||||
authUser, _ := cmd.Flags().GetString("auth-username")
|
||||
authPass, _ := cmd.Flags().GetString("auth-password")
|
||||
private, _ := cmd.Flags().GetBool("private")
|
||||
mirror, _ := cmd.Flags().GetBool("mirror")
|
||||
mirrorInterval, _ := cmd.Flags().GetString("mirror-interval")
|
||||
if mirrorInterval != "" && !mirror {
|
||||
return fmt.Errorf("--mirror-interval requires --mirror")
|
||||
}
|
||||
|
||||
wiki, _ := cmd.Flags().GetBool("wiki")
|
||||
labels, _ := cmd.Flags().GetBool("labels")
|
||||
milestones, _ := cmd.Flags().GetBool("milestones")
|
||||
issues, _ := cmd.Flags().GetBool("issues")
|
||||
pulls, _ := cmd.Flags().GetBool("pulls")
|
||||
releases, _ := cmd.Flags().GetBool("releases")
|
||||
lfs, _ := cmd.Flags().GetBool("lfs")
|
||||
lfsEndpoint, _ := cmd.Flags().GetString("lfs-endpoint")
|
||||
|
||||
opt := gitea.MigrateRepoOption{
|
||||
RepoName: repoName,
|
||||
RepoOwner: owner,
|
||||
CloneAddr: cloneURL,
|
||||
Service: service,
|
||||
AuthUsername: authUser,
|
||||
AuthPassword: authPass,
|
||||
AuthToken: authToken,
|
||||
Private: private,
|
||||
Description: description,
|
||||
Mirror: mirror,
|
||||
MirrorInterval: mirrorInterval,
|
||||
Wiki: wiki,
|
||||
Labels: labels,
|
||||
Milestones: milestones,
|
||||
Issues: issues,
|
||||
PullRequests: pulls,
|
||||
Releases: releases,
|
||||
LFS: lfs,
|
||||
LFSEndpoint: lfsEndpoint,
|
||||
}
|
||||
|
||||
ios.StartSpinner("Starting migration...")
|
||||
repo, _, err := client.MigrateRepo(opt)
|
||||
ios.StopSpinner()
|
||||
if err != nil {
|
||||
return fmt.Errorf("migration failed: %w", err)
|
||||
}
|
||||
|
||||
if wantJSON(cmd) {
|
||||
return outputJSON(cmd, repo)
|
||||
}
|
||||
|
||||
cs := ios.ColorScheme()
|
||||
fmt.Fprintf(ios.Out, "%s Migrated to %s\n", cs.SuccessIcon(), repo.FullName)
|
||||
if repo.HTMLURL != "" {
|
||||
fmt.Fprintf(ios.Out, " %s\n", repo.HTMLURL)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func parseGitService(s string) (gitea.GitServiceType, error) {
|
||||
switch strings.ToLower(s) {
|
||||
case "", "git", "plain":
|
||||
return gitea.GitServicePlain, nil
|
||||
case "github":
|
||||
return gitea.GitServiceGithub, nil
|
||||
case "gitlab":
|
||||
return gitea.GitServiceGitlab, nil
|
||||
case "gitea", "forgejo":
|
||||
return gitea.GitServiceGitea, nil
|
||||
case "gogs":
|
||||
return gitea.GitServiceGogs, nil
|
||||
default:
|
||||
return "", fmt.Errorf("unknown --service %q (expected: git, github, gitlab, gitea, gogs)", s)
|
||||
}
|
||||
}
|
||||
113
cmd/repo_template.go
Normal file
113
cmd/repo_template.go
Normal file
|
|
@ -0,0 +1,113 @@
|
|||
package cmd
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"code.gitea.io/sdk/gitea"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
var repoCreateFromTemplateCmd = &cobra.Command{
|
||||
Use: "create-from-template <template-owner/template-name> <new-name>",
|
||||
Aliases: []string{"ct"},
|
||||
Short: "Create a repository from a template",
|
||||
Long: `Scaffold a new repository based on an existing template repository.
|
||||
|
||||
By default this copies only the default branch content. Pass --with-<kind>
|
||||
flags to include topics, labels, webhooks, git hooks, and other template
|
||||
metadata.`,
|
||||
Example: ` # Create a new repo under your account from a template
|
||||
fgj repo create-from-template org/template-name my-new-repo
|
||||
|
||||
# Target a specific owner and make it private
|
||||
fgj repo create-from-template org/template-name new-repo --owner myorg --private
|
||||
|
||||
# Copy everything: content, topics, labels, webhooks, hooks, avatar
|
||||
fgj repo create-from-template org/template-name new-repo \
|
||||
--with-content --with-topics --with-labels \
|
||||
--with-webhooks --with-git-hooks --with-avatar`,
|
||||
Args: cobra.ExactArgs(2),
|
||||
RunE: runRepoCreateFromTemplate,
|
||||
}
|
||||
|
||||
func init() {
|
||||
repoCmd.AddCommand(repoCreateFromTemplateCmd)
|
||||
|
||||
repoCreateFromTemplateCmd.Flags().String("owner", "", "Owner (user or org) for the new repository (defaults to you)")
|
||||
repoCreateFromTemplateCmd.Flags().StringP("description", "d", "", "Description for the new repository")
|
||||
repoCreateFromTemplateCmd.Flags().Bool("private", false, "Make the new repository private")
|
||||
repoCreateFromTemplateCmd.Flags().Bool("with-content", true, "Include default branch content from the template")
|
||||
repoCreateFromTemplateCmd.Flags().Bool("with-topics", false, "Include topics from the template")
|
||||
repoCreateFromTemplateCmd.Flags().Bool("with-labels", false, "Include labels from the template")
|
||||
repoCreateFromTemplateCmd.Flags().Bool("with-webhooks", false, "Include webhooks from the template")
|
||||
repoCreateFromTemplateCmd.Flags().Bool("with-git-hooks", false, "Include git hooks from the template")
|
||||
repoCreateFromTemplateCmd.Flags().Bool("with-avatar", false, "Include the template repo's avatar")
|
||||
|
||||
addJSONFlags(repoCreateFromTemplateCmd, "Output created repository as JSON")
|
||||
}
|
||||
|
||||
func runRepoCreateFromTemplate(cmd *cobra.Command, args []string) error {
|
||||
templateSlug := args[0]
|
||||
newName := args[1]
|
||||
|
||||
parts := strings.SplitN(templateSlug, "/", 2)
|
||||
if len(parts) != 2 || parts[0] == "" || parts[1] == "" {
|
||||
return fmt.Errorf("template must be in owner/name format (got %q)", templateSlug)
|
||||
}
|
||||
templateOwner, templateName := parts[0], parts[1]
|
||||
|
||||
client, err := loadClient()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
owner, _ := cmd.Flags().GetString("owner")
|
||||
if owner == "" {
|
||||
user, _, err := client.GetMyUserInfo()
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to resolve current user (pass --owner to override): %w", err)
|
||||
}
|
||||
owner = user.UserName
|
||||
}
|
||||
|
||||
description, _ := cmd.Flags().GetString("description")
|
||||
private, _ := cmd.Flags().GetBool("private")
|
||||
withContent, _ := cmd.Flags().GetBool("with-content")
|
||||
withTopics, _ := cmd.Flags().GetBool("with-topics")
|
||||
withLabels, _ := cmd.Flags().GetBool("with-labels")
|
||||
withWebhooks, _ := cmd.Flags().GetBool("with-webhooks")
|
||||
withGitHooks, _ := cmd.Flags().GetBool("with-git-hooks")
|
||||
withAvatar, _ := cmd.Flags().GetBool("with-avatar")
|
||||
|
||||
opt := gitea.CreateRepoFromTemplateOption{
|
||||
Owner: owner,
|
||||
Name: newName,
|
||||
Description: description,
|
||||
Private: private,
|
||||
GitContent: withContent,
|
||||
Topics: withTopics,
|
||||
Labels: withLabels,
|
||||
Webhooks: withWebhooks,
|
||||
GitHooks: withGitHooks,
|
||||
Avatar: withAvatar,
|
||||
}
|
||||
|
||||
ios.StartSpinner("Creating from template...")
|
||||
repo, _, err := client.CreateRepoFromTemplate(templateOwner, templateName, opt)
|
||||
ios.StopSpinner()
|
||||
if err != nil {
|
||||
return fmt.Errorf("template instantiation failed: %w", err)
|
||||
}
|
||||
|
||||
if wantJSON(cmd) {
|
||||
return outputJSON(cmd, repo)
|
||||
}
|
||||
|
||||
cs := ios.ColorScheme()
|
||||
fmt.Fprintf(ios.Out, "%s Created %s from template %s\n", cs.SuccessIcon(), repo.FullName, templateSlug)
|
||||
if repo.HTMLURL != "" {
|
||||
fmt.Fprintf(ios.Out, " %s\n", repo.HTMLURL)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
68
cmd/secret_input.go
Normal file
68
cmd/secret_input.go
Normal file
|
|
@ -0,0 +1,68 @@
|
|||
package cmd
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
"golang.org/x/term"
|
||||
)
|
||||
|
||||
// readSecretValue resolves the value for a secret/token flag from, in order:
|
||||
// 1. --body (inline; visible in shell history)
|
||||
// 2. --body-file (file path, or "-" for stdin)
|
||||
// 3. interactive TTY prompt (hidden)
|
||||
// 4. piped stdin
|
||||
//
|
||||
// Trailing whitespace (including the final newline common in heredocs and
|
||||
// `echo ... | fgj ...`) is trimmed. An empty resolved value is rejected so we
|
||||
// never silently write an empty secret.
|
||||
func readSecretValue(cmd *cobra.Command, label string) (string, error) {
|
||||
if v, _ := cmd.Flags().GetString("body"); v != "" {
|
||||
return strings.TrimRight(v, "\r\n"), nil
|
||||
}
|
||||
|
||||
if path, _ := cmd.Flags().GetString("body-file"); path != "" {
|
||||
var raw []byte
|
||||
var err error
|
||||
if path == "-" {
|
||||
raw, err = io.ReadAll(ios.In)
|
||||
} else {
|
||||
raw, err = os.ReadFile(path)
|
||||
}
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("failed to read secret from %q: %w", path, err)
|
||||
}
|
||||
value := strings.TrimRight(string(raw), "\r\n")
|
||||
if value == "" {
|
||||
return "", fmt.Errorf("secret value from %q is empty", path)
|
||||
}
|
||||
return value, nil
|
||||
}
|
||||
|
||||
if ios.IsStdinTTY() {
|
||||
fmt.Fprintf(ios.ErrOut, "Value for %s: ", label)
|
||||
pw, err := term.ReadPassword(int(os.Stdin.Fd()))
|
||||
fmt.Fprintln(ios.ErrOut)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("failed to read secret: %w", err)
|
||||
}
|
||||
value := strings.TrimRight(string(pw), "\r\n")
|
||||
if value == "" {
|
||||
return "", fmt.Errorf("secret value is empty")
|
||||
}
|
||||
return value, nil
|
||||
}
|
||||
|
||||
raw, err := io.ReadAll(ios.In)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("failed to read secret from stdin: %w", err)
|
||||
}
|
||||
value := strings.TrimRight(string(raw), "\r\n")
|
||||
if value == "" {
|
||||
return "", fmt.Errorf("secret value from stdin is empty")
|
||||
}
|
||||
return value, nil
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue