Compare commits
No commits in common. "7d8679840a450f5f9fe8b3eba3f544e64dcf2f51" and "ecd2fece594f92e6f9a538473e178aaa42eebc4b" have entirely different histories.
7d8679840a
...
ecd2fece59
|
|
@ -18,8 +18,6 @@ var bootstrapYes bool
|
|||
var bootstrapPromptDemocraticCSI bool
|
||||
var bootstrapPromptOperationalSecrets bool
|
||||
var bootstrapInitializeOpenBaoRecovery bool
|
||||
var bootstrapInitializeOpenBao bool
|
||||
var bootstrapCreateForgejoRegistryToken bool
|
||||
var bootstrapPublishAppFrom string
|
||||
var bootstrapMergeBootstrapPR bool
|
||||
var bootstrapManageNetworkBridges bool
|
||||
|
|
@ -39,8 +37,6 @@ func init() {
|
|||
bootstrapCmd.Flags().BoolVar(&bootstrapPromptDemocraticCSI, "prompt-democratic-csi", false, "Prompt for and save Democratic CSI settings in --config")
|
||||
bootstrapCmd.Flags().BoolVar(&bootstrapPromptOperationalSecrets, "prompt-operational-secrets", false, "Prompt for and encrypt operational secrets for --config")
|
||||
bootstrapCmd.Flags().BoolVar(&bootstrapInitializeOpenBaoRecovery, "initialize-openbao-recovery", false, "Create and save a separate OpenBao recovery age identity for --config")
|
||||
bootstrapCmd.Flags().BoolVar(&bootstrapInitializeOpenBao, "initialize-openbao", false, "Initialize OpenBao and seed encrypted operational secrets for --config")
|
||||
bootstrapCmd.Flags().BoolVar(&bootstrapCreateForgejoRegistryToken, "create-forgejo-registry-token", false, "Create a least-privilege Forgejo package registry token and seed it through OpenBao")
|
||||
bootstrapCmd.Flags().StringVar(&bootstrapPublishAppFrom, "publish-app-from", "", "Push this app checkout's current branch and create a Forgejo delivery PR")
|
||||
bootstrapCmd.Flags().BoolVar(&bootstrapMergeBootstrapPR, "merge-bootstrap-pr", false, "Merge the generated Flux repository migration PR before bootstrapping")
|
||||
bootstrapCmd.Flags().BoolVar(&bootstrapManageNetworkBridges, "manage-network-bridges", false, "Persist Terraform management for existing Talos network bridges")
|
||||
|
|
@ -49,26 +45,6 @@ func init() {
|
|||
func runBootstrap(cmd *cobra.Command, args []string) error {
|
||||
var cfg config.Config
|
||||
var err error
|
||||
if bootstrapCreateForgejoRegistryToken {
|
||||
if bootstrapConfigPath == "" {
|
||||
return fmt.Errorf("--create-forgejo-registry-token requires --config")
|
||||
}
|
||||
cfg, err = config.Load(bootstrapConfigPath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return createForgejoRegistryToken(cfg)
|
||||
}
|
||||
if bootstrapInitializeOpenBao {
|
||||
if bootstrapConfigPath == "" {
|
||||
return fmt.Errorf("--initialize-openbao requires --config")
|
||||
}
|
||||
cfg, err = config.Load(bootstrapConfigPath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return bootstrap.InitializeOpenBao(cfg)
|
||||
}
|
||||
if bootstrapMergeBootstrapPR {
|
||||
if bootstrapConfigPath == "" {
|
||||
return fmt.Errorf("--merge-bootstrap-pr requires --config")
|
||||
|
|
@ -174,28 +150,3 @@ func runBootstrap(cmd *cobra.Command, args []string) error {
|
|||
runner := bootstrap.Runner{Config: cfg, Mode: bootstrap.Mode(bootstrapMode), ConfirmRebuild: bootstrapYes}
|
||||
return runner.Run()
|
||||
}
|
||||
|
||||
func createForgejoRegistryToken(cfg config.Config) error {
|
||||
if _, err := bootstrap.ReadOperationalSecrets(cfg.SOPS.OperationalSecretsPath, cfg.SOPS.AgeKeyPath); err != nil {
|
||||
return err
|
||||
}
|
||||
password, otp, name, err := ui.PromptForgejoRegistryToken()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
token, err := forgejo.CreateRegistryToken(cfg.Git.BaseURL, cfg.Git.Username, password, otp, name)
|
||||
if err != nil {
|
||||
return fmt.Errorf("create Forgejo registry token: %w", err)
|
||||
}
|
||||
dockerConfig, err := bootstrap.ForgejoRegistryDockerConfig(cfg.Delivery.ImageRepository, cfg.Git.Username, token)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err := bootstrap.UpsertOperationalSecret(cfg.SOPS.OperationalSecretsPath, cfg.SOPS.AgeKeyPath, "cicd/forgejo-registry", "dockerconfigjson", dockerConfig); err != nil {
|
||||
return fmt.Errorf("Forgejo registry token was created but could not be saved; revoke the new token in Forgejo and retry: %w", err)
|
||||
}
|
||||
if err := bootstrap.InitializeOpenBao(cfg); err != nil {
|
||||
return fmt.Errorf("Forgejo registry token was saved to encrypted operational secrets but OpenBao seeding failed; rerun bootstrap --config %q --initialize-openbao: %w", bootstrapConfigPath, err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,16 +0,0 @@
|
|||
package cmd
|
||||
|
||||
import "testing"
|
||||
|
||||
func TestCreateForgejoRegistryTokenRequiresConfig(t *testing.T) {
|
||||
originalConfigPath, originalCreate := bootstrapConfigPath, bootstrapCreateForgejoRegistryToken
|
||||
defer func() {
|
||||
bootstrapConfigPath = originalConfigPath
|
||||
bootstrapCreateForgejoRegistryToken = originalCreate
|
||||
}()
|
||||
bootstrapConfigPath = ""
|
||||
bootstrapCreateForgejoRegistryToken = true
|
||||
if err := runBootstrap(nil, nil); err == nil {
|
||||
t.Fatal("--create-forgejo-registry-token accepted a missing --config")
|
||||
}
|
||||
}
|
||||
|
|
@ -4,7 +4,6 @@ import (
|
|||
"bytes"
|
||||
"crypto/rand"
|
||||
"encoding/base64"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
|
|
@ -37,12 +36,6 @@ type Runner struct {
|
|||
ConfirmRebuild bool
|
||||
}
|
||||
|
||||
type operationalSecrets struct {
|
||||
Secrets map[string]map[string]string `yaml:"secrets"`
|
||||
}
|
||||
|
||||
var initializeOpenBao = openbao.Initialize
|
||||
|
||||
func (r Runner) Run() error {
|
||||
resolved, err := config.Resolve(r.Config)
|
||||
if err != nil {
|
||||
|
|
@ -266,74 +259,13 @@ func writeDemocraticCSISecret(path string, csi config.DemocraticCSIConfig, ageKe
|
|||
}
|
||||
|
||||
func WriteOperationalSecrets(path, ageKeyPath string, secrets map[string]map[string]string) error {
|
||||
plaintext, err := yaml.Marshal(operationalSecrets{Secrets: secrets})
|
||||
plaintext, err := yaml.Marshal(struct {
|
||||
Secrets map[string]map[string]string `yaml:"secrets"`
|
||||
}{Secrets: secrets})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return writeSOPSEncrypted(path, ageKeyPath, plaintext, "^(secrets)$")
|
||||
}
|
||||
|
||||
func UpsertOperationalSecret(path, ageKeyPath, secretPath, key, value string) error {
|
||||
secrets, err := ReadOperationalSecrets(path, ageKeyPath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
values := secrets[secretPath]
|
||||
if values == nil {
|
||||
values = map[string]string{}
|
||||
secrets[secretPath] = values
|
||||
}
|
||||
values[key] = value
|
||||
return WriteOperationalSecrets(path, ageKeyPath, secrets)
|
||||
}
|
||||
|
||||
func ReadOperationalSecrets(path, ageKeyPath string) (map[string]map[string]string, error) {
|
||||
plaintext, err := decryptSOPSFile(path, ageKeyPath)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("decrypt operational SOPS secrets: %w", err)
|
||||
}
|
||||
decoder := yaml.NewDecoder(bytes.NewReader(plaintext))
|
||||
decoder.KnownFields(true)
|
||||
var document operationalSecrets
|
||||
if err := decoder.Decode(&document); err != nil {
|
||||
return nil, fmt.Errorf("parse operational SOPS secrets: %w", err)
|
||||
}
|
||||
if err := decoder.Decode(&operationalSecrets{}); !errors.Is(err, io.EOF) {
|
||||
return nil, errors.New("operational SOPS secrets must contain one YAML document")
|
||||
}
|
||||
if document.Secrets == nil {
|
||||
return nil, errors.New("operational SOPS secrets requires a secrets mapping")
|
||||
}
|
||||
for secretPath, values := range document.Secrets {
|
||||
if values == nil {
|
||||
return nil, fmt.Errorf("operational SOPS secret %q must be a mapping", secretPath)
|
||||
}
|
||||
}
|
||||
return document.Secrets, nil
|
||||
}
|
||||
|
||||
func ForgejoRegistryDockerConfig(imageRepository, username, token string) (string, error) {
|
||||
registryHost := strings.Split(strings.TrimSpace(imageRepository), "/")[0]
|
||||
if registryHost == "" || username == "" || token == "" {
|
||||
return "", errors.New("delivery registry host, Forgejo username, and token are required")
|
||||
}
|
||||
config, err := json.Marshal(map[string]map[string]map[string]string{
|
||||
"auths": {
|
||||
registryHost: {"auth": base64.StdEncoding.EncodeToString([]byte(username + ":" + token))},
|
||||
},
|
||||
})
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return string(config), nil
|
||||
}
|
||||
|
||||
func InitializeOpenBao(cfg config.Config) error {
|
||||
kubeconfig := filepath.Join(cfg.Git.CloneParent, cfg.Talos.RepoDirName, cfg.Talos.GeneratedDir, "kubeconfig")
|
||||
if _, err := initializeOpenBao(kubeconfig, cfg.SOPS.RecoveryRecipient, cfg.SOPS.RecoveryIdentityPath, cfg.SOPS.RecoveryBundlePath, cfg.SOPS.AgeKeyPath, cfg.SOPS.OperationalSecretsPath); err != nil {
|
||||
return fmt.Errorf("initialize OpenBao: %w", err)
|
||||
}
|
||||
return nil
|
||||
return writeSOPSEncryptedFile(path, ageKeyPath, plaintext)
|
||||
}
|
||||
|
||||
func NewWebhookAuthorization() (string, error) {
|
||||
|
|
@ -345,10 +277,6 @@ func NewWebhookAuthorization() (string, error) {
|
|||
}
|
||||
|
||||
func writeSOPSEncryptedFile(path, ageKeyPath string, plaintext []byte) error {
|
||||
return writeSOPSEncrypted(path, ageKeyPath, plaintext, "^(data|stringData)$")
|
||||
}
|
||||
|
||||
func writeSOPSEncrypted(path, ageKeyPath string, plaintext []byte, encryptedRegex string) error {
|
||||
if err := os.MkdirAll(filepath.Dir(path), 0700); err != nil {
|
||||
return err
|
||||
}
|
||||
|
|
@ -356,11 +284,11 @@ func writeSOPSEncrypted(path, ageKeyPath string, plaintext []byte, encryptedRege
|
|||
if err != nil {
|
||||
return fmt.Errorf("derive SOPS age recipient: %w", err)
|
||||
}
|
||||
command := exec.Command("sops", "encrypt", "--age", strings.TrimSpace(string(recipient)), "--encrypted-regex", encryptedRegex, "--input-type", "yaml", "--output-type", "yaml", "--filename-override", filepath.Base(path))
|
||||
command := exec.Command("sops", "encrypt", "--age", strings.TrimSpace(string(recipient)), "--encrypted-regex", "^(data|stringData)$", "--input-type", "yaml", "--output-type", "yaml", "--filename-override", filepath.Base(path))
|
||||
command.Stdin = bytes.NewReader(plaintext)
|
||||
encrypted, err := command.Output()
|
||||
if err != nil {
|
||||
return fmt.Errorf("encrypt SOPS file: %w", err)
|
||||
return fmt.Errorf("encrypt Democratic CSI secret: %w", err)
|
||||
}
|
||||
return os.WriteFile(path, encrypted, 0600)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,9 +1,6 @@
|
|||
package bootstrap
|
||||
|
||||
import (
|
||||
"encoding/base64"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
|
|
@ -107,93 +104,6 @@ func TestNewWebhookAuthorization(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
func TestForgejoRegistryDockerConfig(t *testing.T) {
|
||||
dockerConfig, err := ForgejoRegistryDockerConfig("registry.example.test/team/app", "registry-user", "registry-token")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
var document struct {
|
||||
Auths map[string]struct {
|
||||
Auth string `json:"auth"`
|
||||
} `json:"auths"`
|
||||
}
|
||||
if err := json.Unmarshal([]byte(dockerConfig), &document); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if document.Auths["registry.example.test"].Auth != base64.StdEncoding.EncodeToString([]byte("registry-user:registry-token")) {
|
||||
t.Fatal("Forgejo registry Docker config has the wrong credentials")
|
||||
}
|
||||
}
|
||||
|
||||
func TestUpsertOperationalSecretPreservesExistingSecrets(t *testing.T) {
|
||||
if _, err := exec.LookPath("age-keygen"); err != nil {
|
||||
t.Skip("age-keygen is required for bootstrap encryption")
|
||||
}
|
||||
if _, err := exec.LookPath("sops"); err != nil {
|
||||
t.Skip("sops is required for bootstrap encryption")
|
||||
}
|
||||
dir := t.TempDir()
|
||||
identity := filepath.Join(dir, "age-key.txt")
|
||||
if err := exec.Command("age-keygen", "-o", identity).Run(); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
path := filepath.Join(dir, "operational-secrets.sops.yaml")
|
||||
if err := WriteOperationalSecrets(path, identity, map[string]map[string]string{
|
||||
"cicd/forgejo": {"token": "existing-token"},
|
||||
"platform/cloudflare": {"api-token": "existing-api-token"},
|
||||
"cicd/forgejo-registry": {"dockerconfigjson": "old-config"},
|
||||
}); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if err := UpsertOperationalSecret(path, identity, "cicd/forgejo-registry", "dockerconfigjson", "new-config"); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
encrypted, err := os.ReadFile(path)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if strings.Contains(string(encrypted), "existing-token") || strings.Contains(string(encrypted), "new-config") || !strings.Contains(string(encrypted), "sops:") {
|
||||
t.Fatal("operational secret upsert was not SOPS encrypted")
|
||||
}
|
||||
secrets, err := ReadOperationalSecrets(path, identity)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if secrets["cicd/forgejo"]["token"] != "existing-token" || secrets["platform/cloudflare"]["api-token"] != "existing-api-token" || secrets["cicd/forgejo-registry"]["dockerconfigjson"] != "new-config" {
|
||||
t.Fatal("operational secret upsert did not preserve unrelated secrets")
|
||||
}
|
||||
}
|
||||
|
||||
func TestInitializeOpenBaoUsesGeneratedKubeconfig(t *testing.T) {
|
||||
original := initializeOpenBao
|
||||
defer func() { initializeOpenBao = original }()
|
||||
var kubeconfig string
|
||||
initializeOpenBao = func(path, recipient, identityPath, bundlePath, ageKeyPath, operationalSecretsPath string) (map[string]map[string]string, error) {
|
||||
kubeconfig = path
|
||||
if recipient != "recipient" || identityPath != "identity" || bundlePath != "bundle" || ageKeyPath != "age" || operationalSecretsPath != "secrets" {
|
||||
t.Fatal("InitializeOpenBao passed incorrect configured paths")
|
||||
}
|
||||
return nil, nil
|
||||
}
|
||||
cfg := config.Config{
|
||||
Git: config.GitConfig{CloneParent: "checkout"},
|
||||
Talos: config.TalosConfig{RepoDirName: "talos", GeneratedDir: "generated"},
|
||||
SOPS: config.SOPSConfig{RecoveryRecipient: "recipient", RecoveryIdentityPath: "identity", RecoveryBundlePath: "bundle", AgeKeyPath: "age", OperationalSecretsPath: "secrets"},
|
||||
}
|
||||
if err := InitializeOpenBao(cfg); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if kubeconfig != filepath.Join("checkout", "talos", "generated", "kubeconfig") {
|
||||
t.Fatal("InitializeOpenBao did not use the configured generated kubeconfig")
|
||||
}
|
||||
initializeOpenBao = func(string, string, string, string, string, string) (map[string]map[string]string, error) {
|
||||
return nil, errors.New("unavailable")
|
||||
}
|
||||
if err := InitializeOpenBao(cfg); err == nil {
|
||||
t.Fatal("InitializeOpenBao accepted a seeding error")
|
||||
}
|
||||
}
|
||||
|
||||
func TestCopyDirSkipsGitDirectory(t *testing.T) {
|
||||
source := t.TempDir()
|
||||
destination := t.TempDir()
|
||||
|
|
|
|||
|
|
@ -67,15 +67,6 @@ type APIError struct {
|
|||
Status string
|
||||
}
|
||||
|
||||
type createTokenRequest struct {
|
||||
Name string `json:"name"`
|
||||
Scopes []string `json:"scopes"`
|
||||
}
|
||||
|
||||
type accessToken struct {
|
||||
SHA1 string `json:"sha1"`
|
||||
}
|
||||
|
||||
func (e *APIError) Error() string {
|
||||
return fmt.Sprintf("forgejo returned %s", e.Status)
|
||||
}
|
||||
|
|
@ -94,47 +85,6 @@ func NewRepoManager(baseURL, token, owner, username, manifestsRepo, fluxRepo, br
|
|||
}
|
||||
}
|
||||
|
||||
// CreateRegistryToken creates the package-registry token that Forgejo returns once.
|
||||
func CreateRegistryToken(baseURL, username, password, otp, name string) (string, error) {
|
||||
return createRegistryToken(&http.Client{Timeout: 15 * time.Second}, baseURL, username, password, otp, name)
|
||||
}
|
||||
|
||||
func createRegistryToken(client *http.Client, baseURL, username, password, otp, name string) (string, error) {
|
||||
if strings.TrimSpace(baseURL) == "" || username == "" || password == "" || strings.TrimSpace(name) == "" {
|
||||
return "", fmt.Errorf("Forgejo base URL, username, password, and token name are required")
|
||||
}
|
||||
body, err := json.Marshal(createTokenRequest{Name: name, Scopes: []string{"read:package", "write:package"}})
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
endpoint := fmt.Sprintf("%s/api/v1/users/%s/tokens", strings.TrimRight(baseURL, "/"), url.PathEscape(username))
|
||||
request, err := http.NewRequest(http.MethodPost, endpoint, bytes.NewReader(body))
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
request.SetBasicAuth(username, password)
|
||||
request.Header.Set("Content-Type", "application/json")
|
||||
if otp != "" {
|
||||
request.Header.Set("X-Forgejo-OTP", otp)
|
||||
}
|
||||
response, err := client.Do(request)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
defer response.Body.Close()
|
||||
if response.StatusCode != http.StatusCreated {
|
||||
return "", &APIError{StatusCode: response.StatusCode, Status: response.Status}
|
||||
}
|
||||
var token accessToken
|
||||
if err := json.NewDecoder(response.Body).Decode(&token); err != nil {
|
||||
return "", fmt.Errorf("parse Forgejo registry token response: %w", err)
|
||||
}
|
||||
if token.SHA1 == "" {
|
||||
return "", fmt.Errorf("Forgejo did not return a registry token")
|
||||
}
|
||||
return token.SHA1, nil
|
||||
}
|
||||
|
||||
func (rm *RepoManager) InitializeAll(createRepo func(string) error, createFlux func(string) error) error {
|
||||
if err := rm.ensureRepo(rm.ManifestsRepoName, "Centralized deployment manifests for Flux CD", createRepo); err != nil {
|
||||
return err
|
||||
|
|
|
|||
|
|
@ -4,41 +4,9 @@ import (
|
|||
"encoding/json"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"reflect"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestCreateRegistryTokenUsesBasicAuthAndPackageScopes(t *testing.T) {
|
||||
server := httptest.NewServer(http.HandlerFunc(func(writer http.ResponseWriter, request *http.Request) {
|
||||
if request.Method != http.MethodPost || request.URL.Path != "/api/v1/users/registry-user/tokens" {
|
||||
t.Fatal("unexpected Forgejo token request")
|
||||
}
|
||||
username, password, ok := request.BasicAuth()
|
||||
if !ok || username != "registry-user" || password != "password" {
|
||||
t.Fatal("Forgejo token request did not use the supplied basic authentication")
|
||||
}
|
||||
if request.Header.Get("X-Forgejo-OTP") != "123456" || request.Header.Get("Content-Type") != "application/json" {
|
||||
t.Fatal("Forgejo token request headers are incorrect")
|
||||
}
|
||||
var body createTokenRequest
|
||||
if err := json.NewDecoder(request.Body).Decode(&body); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if body.Name != "maidn-registry" || !reflect.DeepEqual(body.Scopes, []string{"read:package", "write:package"}) {
|
||||
t.Fatal("Forgejo token request did not use the least-privilege package scopes")
|
||||
}
|
||||
writer.Header().Set("Content-Type", "application/json")
|
||||
writer.WriteHeader(http.StatusCreated)
|
||||
_, _ = writer.Write([]byte(`{"sha1":"registry-token"}`))
|
||||
}))
|
||||
defer server.Close()
|
||||
|
||||
token, err := createRegistryToken(server.Client(), server.URL, "registry-user", "password", "123456", "maidn-registry")
|
||||
if err != nil || token != "registry-token" {
|
||||
t.Fatal("CreateRegistryToken() did not return the Forgejo token")
|
||||
}
|
||||
}
|
||||
|
||||
func TestRepoExistsOnlyCreatesOnNotFound(t *testing.T) {
|
||||
server := httptest.NewServer(http.HandlerFunc(func(writer http.ResponseWriter, request *http.Request) {
|
||||
writer.WriteHeader(http.StatusUnauthorized)
|
||||
|
|
|
|||
|
|
@ -85,19 +85,6 @@ func GetUserInputForGithubAuth() (username, token string, err error) {
|
|||
return
|
||||
}
|
||||
|
||||
// PromptForgejoRegistryToken collects credentials used only to create a registry token.
|
||||
func PromptForgejoRegistryToken() (password, otp, name string, err error) {
|
||||
password, err = promptHiddenRequired("Forgejo account password")
|
||||
if err != nil {
|
||||
return "", "", "", err
|
||||
}
|
||||
if otp, err = promptHiddenOptional("Forgejo OTP (optional)"); err != nil {
|
||||
return "", "", "", err
|
||||
}
|
||||
name = prompt(bufio.NewReader(os.Stdin), "Forgejo registry token name", "maidn-registry")
|
||||
return password, otp, name, nil
|
||||
}
|
||||
|
||||
func promptForToken(reader *bufio.Reader, prompt string) (string, error) {
|
||||
token := promptSecret(reader, prompt, "")
|
||||
if token == "" {
|
||||
|
|
@ -142,24 +129,3 @@ func promptSecret(reader *bufio.Reader, prompt, fallback string) string {
|
|||
}
|
||||
return secret
|
||||
}
|
||||
|
||||
func promptHiddenRequired(prompt string) (string, error) {
|
||||
value, err := promptHiddenOptional(prompt)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
if value == "" {
|
||||
return "", fmt.Errorf("%s cannot be empty", strings.ToLower(prompt))
|
||||
}
|
||||
return value, nil
|
||||
}
|
||||
|
||||
func promptHiddenOptional(prompt string) (string, error) {
|
||||
fmt.Printf("%s: ", prompt)
|
||||
value, err := term.ReadPassword(int(syscall.Stdin))
|
||||
fmt.Println()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return strings.TrimSpace(string(value)), nil
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue