diff --git a/api.go b/api.go index f6959b2f1e23c7610d4e5b1cb96d28845c736da2..1a43d779227b8c694af4447d4aa5e82f438dc4fa 100644 --- a/api.go +++ b/api.go @@ -3,7 +3,6 @@ package radioai import ( "bytes" "encoding/json" - "errors" "github.com/coreos/go-etcd/etcd" "strings" ) @@ -17,15 +16,19 @@ type RadioAPI struct { client *etcd.Client } -// GetMount returns data on a specific mountpoint (returns an error if -// not found). +func NewRadioAPI(client *etcd.Client) *RadioAPI { + return &RadioAPI{client} +} + +// GetMount returns data on a specific mountpoint (returns nil if not +// found). func (r *RadioAPI) GetMount(mountName string) (*Mount, error) { response, err := r.client.Get(mountPath(mountName)) if err != nil { return nil, err } if len(response) != 1 { - return nil, errors.New("not found") + return nil, nil } var m Mount diff --git a/cmd/radioctl/radioctl.go b/cmd/radioctl/radioctl.go new file mode 100644 index 0000000000000000000000000000000000000000..a7eb83a093bcb68ccd754bef85cf38a81f1e5910 --- /dev/null +++ b/cmd/radioctl/radioctl.go @@ -0,0 +1,155 @@ +package main + +import ( + "crypto/rand" + "encoding/base64" + "flag" + "fmt" + "hash/crc32" + "log" + "os" + "path/filepath" + "strings" + + "git.autistici.org/ale/radioai" +) + +// Describes the syntax of a command. +type Command struct { + Name string + UsageStr string + Help string + Fn func([]string) + MinArgs int + MaxArgs int +} + +func (c Command) PrintHelp() { + fmt.Fprintf(os.Stderr, "%s %s\n %s\n\n", c.Name, c.UsageStr, c.Help) +} + +func (c Command) Run(args []string) { + c.Fn(args) +} + +// List of all known commands. +type CommandList []Command + +func (cl CommandList) PrintHelp() { + fmt.Fprintf(os.Stderr, "Usage: %s <COMMAND> [<ARGS>...]\n", filepath.Base(os.Args[0])) + fmt.Fprintf(os.Stderr, "Known commands:\n\n") + for _, cmd := range cl { + cmd.PrintHelp() + } +} + +func Run(cl CommandList) { + flag.Parse() + + args := flag.Args() + if len(args) < 1 { + cl.PrintHelp() + return + } + + cmdArg := args[0] + args = args[1:] + for _, cmd := range cl { + if cmd.Name == cmdArg { + if (cmd.MinArgs > 0 && len(args) < cmd.MinArgs) || (cmd.MaxArgs >= 0 && cmd.MaxArgs >= cmd.MinArgs && len(args) > cmd.MaxArgs) { + fmt.Fprintf(os.Stderr, "Wrong number of arguments!\n") + cmd.PrintHelp() + } else { + cmd.Run(args) + } + return + } + } + log.Fatalf("Unknown command '%s'", cmdArg) +} + +func getClient() *radioai.RadioAPI { + etc := radioai.NewEtcdClient() + return radioai.NewRadioAPI(etc) +} + +func deleteMount(args []string) { + if err := getClient().DelMount(args[0]); err != nil { + log.Fatal(err) + } +} + +func listMounts(args []string) { + mounts, err := getClient().ListMounts() + if err != nil { + log.Fatal(err) + } + for _, m := range mounts { + fmt.Printf("%s\n", m.Name) + } +} + +func showMount(args []string) { + mount, err := getClient().GetMount(args[0]) + if err != nil { + log.Fatal(err) + } + if mount == nil { + log.Fatal("Mount not found") + } + fmt.Printf("%+v\n", mount) +} + +func generateUsername(path string) string { + return fmt.Sprintf("source%d", crc32.ChecksumIEEE([]byte(path))) +} + +func generatePassword() string { + b := make([]byte, 6) + rand.Read(b) + return base64.StdEncoding.EncodeToString(b) +} + +func createMount(args []string) { + path := args[0] + if !strings.HasPrefix(path, "/") { + log.Fatal("Mount points should specify a full path") + } + + // Check if the mount already exists. + client := getClient() + oldm, err := client.GetMount(path) + if err != nil { + log.Fatal(err) + } + if oldm != nil { + log.Fatal("A mount with that name already exists!") + } + + // Create the new mount, randomly generate source authentication. + username := generateUsername(path) + password := generatePassword() + m := &radioai.Mount{ + Name: path, + Username: username, + Password: password, + } + + if err := client.SetMount(m); err != nil { + log.Fatal(err) + } + + fmt.Printf("%+v\n", m) +} + +var commands = CommandList{ + {"create-mount", "<path>", "Create a new mountpoint", createMount, 1, 1}, + {"delete-mount", "<path>", "Delete a mountpoint", deleteMount, 1, 1}, + {"list-mounts", "", "List all known mountpoints", listMounts, 0, 0}, + {"show-mount", "<path>", "Show configuration of a mount", showMount, 1, 1}, +} + +func main() { + log.SetFlags(0) + Run(commands) +}