Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 4 additions & 2 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,9 @@ cmd/ cobra 커맨드 정의 — 얇게 유지. 인자 파싱과
internal/ 모든 실제 로직. cmd/에서 로직을 구현하지 않는다.
config.go viper 설정 로드 (Flags, Config 전역)
const.go ZooKeeper znode 경로 상수
util.go ZK 커넥션/znode 헬퍼, 표준입력 읽기
zookeeper.go ZooKeeper 커넥션/znode 헬퍼
prompt.go 표준입력 및 확인 프롬프트
password.go ACL 비밀번호 정책 검증
topology/ YAML 토폴로지 타입 + Validate() + Load*()
store/ ~/.arcusctl 하위 배포 메타데이터 영속화
ssh/ ssh/scp 서브프로세스 실행 래퍼
Expand Down Expand Up @@ -86,7 +88,7 @@ internal/ 모든 실제 로직. cmd/에서 로직을 구현하지 않
- 사용자에게 보이는 에러 메시지는 **무엇이 / 왜 실패했고 / 어떻게 복구하는지**를 담는다. 원격 호스트가 관련되면 어느 호스트인지 반드시 포함한다.
- **cobra 커맨드는 `RunE`로 error를 반환한다.** `cmd.Execute()`가 stderr 출력과 exit code 1을 담당한다. 커맨드 안에서
`panic()`이나 `os.Exit()`을 부르지 않는다.
> 현재 `cmd/` 일부와 `internal/config.go`, `internal/util.go`에 `panic(err)`이
> 현재 `cmd/` 일부와 `internal/config.go`에 `panic(err)`이
> 남아 있다. 이것은 **정리 대상인 과거 패턴**이며 새 코드가 따라갈 기준이 아니다.
> 건드리는 김에 점진적으로 `RunE` + error 반환으로 옮긴다.

Expand Down
32 changes: 23 additions & 9 deletions cmd/acl/admin/passwd.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,34 +22,48 @@ For password requirements, see: https://github.com/jam2in/arcusctl/blob/main/doc
Example: ` # Change admin password for group 'cache01'
arcusctl acl admin passwd cache01`,
Args: cobra.ExactArgs(1),
Run: func(cmd *cobra.Command, args []string) {
RunE: func(cmd *cobra.Command, args []string) error {
groupName := args[0]

adminName := internal.ReadStdin("admin name", false)
adminPassword := internal.ReadStdin("admin password", true)
newPassword := internal.ReadStdin("new password", true)
if newPassword != internal.ReadStdin("repeat new password", true) {
panic("password does not match")
adminName, err := internal.ReadInput("admin name")
Comment thread
f1v3-dev marked this conversation as resolved.
if err != nil {
return fmt.Errorf("read admin name: %w", err)
}
adminPassword, err := internal.ReadPassword("admin password")
if err != nil {
return err
}
newPassword, err := internal.ReadPassword("new password")
if err != nil {
return err
}
repeatedPassword, err := internal.ReadPassword("repeat new password")
if err != nil {
return err
}
if newPassword != repeatedPassword {
return errors.New("password does not match")
}

conn, err := internal.ConnectZooKeeper(internal.Config.ZooKeeper)
if err != nil {
panic(err)
return err
}
defer conn.Close()

if err := conn.AddAuth("digest", []byte(adminName+":"+adminPassword)); err != nil {
panic(err)
return fmt.Errorf("authenticate ZooKeeper admin %q: %w", adminName, err)
}

acls := append(zk.DigestACL(zk.PermAll, adminName, newPassword),
zk.WorldACL(zk.PermRead)...)

if err := setAclRecursive(conn, internal.ZPATH_ACL_ROOT+"/"+groupName, acls); err != nil {
panic(err)
return err
}

fmt.Println("OK")
return nil
},
}

Expand Down
32 changes: 23 additions & 9 deletions cmd/acl/group/add.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package group

import (
"errors"
"fmt"

"github.com/go-zookeeper/zk"
Expand All @@ -21,32 +22,45 @@ For password requirements, see: https://github.com/jam2in/arcusctl/blob/main/doc
Example: ` # Create a new group named 'cache01'
arcusctl acl group add cache01`,
Args: cobra.ExactArgs(1),
Run: func(cmd *cobra.Command, args []string) {
RunE: func(cmd *cobra.Command, args []string) error {
groupName := args[0]

adminName := internal.ReadStdin("admin name", false)
adminPassword := internal.ReadStdin("password", true)
if adminPassword != internal.ReadStdin("repeat password", true) {
panic("password does not match")
adminName, err := internal.ReadInput("admin name")
if err != nil {
return fmt.Errorf("read admin name: %w", err)
}
adminPassword, err := internal.ReadPassword("password")
if err != nil {
return err
}
repeatedPassword, err := internal.ReadPassword("repeat password")
if err != nil {
return err
}
if adminPassword != repeatedPassword {
return errors.New("password does not match")
}

conn, err := internal.ConnectZooKeeper(internal.Config.ZooKeeper)
if err != nil {
panic(err)
return err
}
defer conn.Close()

if err := conn.AddAuth("digest", []byte(adminName+":"+adminPassword)); err != nil {
panic(err)
return fmt.Errorf("authenticate ZooKeeper admin %q: %w", adminName, err)
}

internal.EnsureZPath(conn, internal.ZPATH_ACL_ROOT)
if err := internal.EnsureZNode(conn, internal.ZPATH_ACL_ROOT); err != nil {
return err
}
if _, err := conn.Create(internal.ZPATH_ACL_ROOT+"/"+groupName, nil, 0,
append(zk.DigestACL(zk.PermAll, adminName, adminPassword),
zk.WorldACL(zk.PermRead)...)); err != nil {
panic(err)
return fmt.Errorf("create ACL group %q: %w", groupName, err)
}

fmt.Println("OK")
return nil
},
}
94 changes: 67 additions & 27 deletions cmd/acl/user/add.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package user

import (
"errors"
"fmt"
"strings"

Expand All @@ -25,35 +26,37 @@ For password requirements, see: https://github.com/jam2in/arcusctl/blob/main/doc
# Add a user 'john' for operator with logging enabled
arcusctl acl user add cache01 john attr,scan,flush,admin logAll`,
Args: cobra.RangeArgs(3, 4),
Run: func(cmd *cobra.Command, args []string) {
RunE: func(cmd *cobra.Command, args []string) error {
groupName := args[0]
userName := args[1]
permissions := args[2]
if len(args) == 4 {
if args[3] == "logAll" {
permissions += ",logall"
} else {
panic("invalid arguments")
return fmt.Errorf("invalid fourth argument %q: expected logAll", args[3])
}
}

adminName := internal.ReadStdin("admin name", false)
adminPassword := internal.ReadStdin("admin password", true)
userPassword := internal.ReadStdin("user password", true)
if userPassword != internal.ReadStdin("repeat user password", true) {
panic("password does not match")
adminName, adminPassword, err := readAdminCredentials()
if err != nil {
return err
}
userPassword, err := readConfirmedPassword("user password", "repeat user password")
if err != nil {
return err
}

secret := scram.GenerateScramSHA256Secret(userPassword, nil, 0)

conn, err := internal.ConnectZooKeeper(internal.Config.ZooKeeper)
if err != nil {
panic(err)
return err
}
defer conn.Close()

if err := conn.AddAuth("digest", []byte(adminName+":"+adminPassword)); err != nil {
panic(err)
return fmt.Errorf("authenticate ZooKeeper admin %q: %w", adminName, err)
}

acls := append(zk.DigestACL(zk.PermAll, adminName, adminPassword),
Expand All @@ -73,10 +76,11 @@ For password requirements, see: https://github.com/jam2in/arcusctl/blob/main/doc
Flags: 0,
},
); err != nil {
panic(err)
return fmt.Errorf("create ACL user %q in group %q: %w", userName, groupName, err)
}

fmt.Println("OK")
return nil
},
}

Expand All @@ -89,35 +93,38 @@ For password requirements, see: https://github.com/jam2in/arcusctl/blob/main/doc
Example: ` # Change password for user 'john' in group 'cache01'
arcusctl acl user passwd cache01 john`,
Args: cobra.ExactArgs(2),
Run: func(cmd *cobra.Command, args []string) {
RunE: func(cmd *cobra.Command, args []string) error {
groupName := args[0]
userName := args[1]

adminName := internal.ReadStdin("admin name", false)
adminPassword := internal.ReadStdin("admin password", true)
userPassword := internal.ReadStdin("user password", true)
if userPassword != internal.ReadStdin("repeat user password", true) {
panic("password does not match")
adminName, adminPassword, err := readAdminCredentials()
if err != nil {
return err
}
userPassword, err := readConfirmedPassword("user password", "repeat user password")
if err != nil {
return err
}

secret := scram.GenerateScramSHA256Secret(userPassword, nil, 0)

conn, err := internal.ConnectZooKeeper(internal.Config.ZooKeeper)
if err != nil {
panic(err)
return err
}
defer conn.Close()

if err := conn.AddAuth("digest", []byte(adminName+":"+adminPassword)); err != nil {
panic(err)
return fmt.Errorf("authenticate ZooKeeper admin %q: %w", adminName, err)
}

if _, err := conn.Set(internal.ZPATH_ACL_ROOT+"/"+groupName+"/"+userName+"/"+propName,
[]byte(secret.EncodeToBase64()), -1); err != nil {
panic(err)
return fmt.Errorf("change password for ACL user %q in group %q: %w", userName, groupName, err)
}

fmt.Println("OK")
return nil
},
}

Expand All @@ -131,28 +138,30 @@ The logAll flag will be preserved from the user's existing configuration.`,
Example: ` # Update user permissions to key-value only
arcusctl acl user permissions cache01 john kv`,
Args: cobra.ExactArgs(3),
Run: func(cmd *cobra.Command, args []string) {
RunE: func(cmd *cobra.Command, args []string) error {
groupName := args[0]
userName := args[1]
permissions := args[2]

adminName := internal.ReadStdin("admin name", false)
adminPassword := internal.ReadStdin("admin password", true)
adminName, adminPassword, err := readAdminCredentials()
if err != nil {
return err
}

conn, err := internal.ConnectZooKeeper(internal.Config.ZooKeeper)
if err != nil {
panic(err)
return err
}
defer conn.Close()

if err := conn.AddAuth("digest", []byte(adminName+":"+adminPassword)); err != nil {
panic(err)
return fmt.Errorf("authenticate ZooKeeper admin %q: %w", adminName, err)
}

// It's pretty stupid
// Preserve the existing logall flag while replacing the other permissions.
beforePerm, _, err := conn.Get(internal.ZPATH_ACL_ROOT + "/" + groupName + "/" + userName)
if err != nil {
panic(err)
return fmt.Errorf("read permissions for ACL user %q in group %q: %w", userName, groupName, err)
}
beforePermList := strings.Split(string(beforePerm), ",")
if beforePermList[len(beforePermList)-1] == "logall" {
Expand All @@ -161,9 +170,40 @@ The logAll flag will be preserved from the user's existing configuration.`,

if _, err := conn.Set(internal.ZPATH_ACL_ROOT+"/"+groupName+"/"+userName,
[]byte(permissions), -1); err != nil {
panic(err)
return fmt.Errorf("update permissions for ACL user %q in group %q: %w", userName, groupName, err)
}

fmt.Println("OK")
return nil
},
}

func readAdminCredentials() (string, string, error) {
name, err := internal.ReadInput("admin name")
if err != nil {
return "", "", fmt.Errorf("read admin name: %w", err)
}

password, err := internal.ReadPassword("admin password")
Comment thread
f1v3-dev marked this conversation as resolved.
if err != nil {
return "", "", err
}

return name, password, nil
}

func readConfirmedPassword(prompt string, repeatPrompt string) (string, error) {
password, err := internal.ReadPassword(prompt)
if err != nil {
return "", err
}
repeatedPassword, err := internal.ReadPassword(repeatPrompt)
if err != nil {
return "", err
}
if password != repeatedPassword {
return "", errors.New("password does not match")
}

return password, nil
}
17 changes: 10 additions & 7 deletions cmd/acl/user/remove.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,25 +20,27 @@ and will fail if active connections are found.`,
Example: ` # Remove user 'john' from group 'cache01'
arcusctl acl user remove cache01 john`,
Args: cobra.ExactArgs(2),
Run: func(cmd *cobra.Command, args []string) {
RunE: func(cmd *cobra.Command, args []string) error {
groupName := args[0]
userName := args[1]

adminName := internal.ReadStdin("admin name", false)
adminPassword := internal.ReadStdin("admin password", true)
adminName, adminPassword, err := readAdminCredentials()
if err != nil {
return err
}

conn, err := internal.ConnectZooKeeper(internal.Config.ZooKeeper)
if err != nil {
panic(err)
return err
}
defer conn.Close()

if err := conn.AddAuth("digest", []byte(adminName+":"+adminPassword)); err != nil {
panic(err)
return fmt.Errorf("authenticate ZooKeeper admin %q: %w", adminName, err)
}

if err := CheckClientList(conn, groupName, userName); err != nil {
panic(err)
return err
}

if _, err := conn.Multi(
Expand All @@ -51,10 +53,11 @@ and will fail if active connections are found.`,
Version: -1,
},
); err != nil {
panic(err)
return fmt.Errorf("remove ACL user %q from group %q: %w", userName, groupName, err)
}

fmt.Println("OK")
return nil
},
}

Expand Down
6 changes: 5 additions & 1 deletion internal/cluster/delete.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,11 @@ func Delete(serviceCode string, purge bool) error {
}

fmt.Printf("This will remove Arcus cluster %q from all servers.\n", serviceCode)
if !internal.Confirm("Are you sure you want to proceed? (y/N): ") {
confirmed, err := internal.Confirm("Are you sure you want to proceed? (y/N): ")
if err != nil {
return fmt.Errorf("confirm Arcus cluster deletion: %w", err)
}
if !confirmed {
fmt.Println("Aborted.")
return nil
}
Expand Down
Loading
Loading