Skip to content
Draft
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
2 changes: 2 additions & 0 deletions commands.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import (
"github.com/mackerelio/mkr/plugin"
"github.com/mackerelio/mkr/services"
"github.com/mackerelio/mkr/status"
"github.com/mackerelio/mkr/users"
"github.com/mackerelio/mkr/wrap"
"github.com/urfave/cli"
)
Expand All @@ -41,4 +42,5 @@ var Commands = []cli.Command{
wrap.Command,
aws_integrations.Command,
metric_names.Command,
users.CommandUsers,
}
1 change: 1 addition & 0 deletions mackerelclient/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ type Client interface {
FindHost(id string) (*mackerel.Host, error)
FindServices() ([]*mackerel.Service, error)
FindChannels() ([]*mackerel.Channel, error)
FindUsers() ([]*mackerel.User, error)
GetOrg() (*mackerel.Org, error)
CreateHost(param *mackerel.CreateHostParam) (string, error)
UpdateHostStatus(hostID string, status string) error
Expand Down
16 changes: 16 additions & 0 deletions mackerelclient/mock_client.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ type MockClient struct {
findHostCallback func(id string) (*mackerel.Host, error)
findServicesCallback func() ([]*mackerel.Service, error)
findChannelsCallback func() ([]*mackerel.Channel, error)
findUsersCallback func() ([]*mackerel.User, error)
getOrgCallback func() (*mackerel.Org, error)
createHostCallback func(param *mackerel.CreateHostParam) (string, error)
updateHostStatusCallback func(hostID string, status string) error
Expand Down Expand Up @@ -172,3 +173,18 @@ func MockListHostMetricNames(callback func(string) ([]string, error)) MockClient
c.listHostMetricNamesCallback = callback
}
}

// FindUsers ...
func (c *MockClient) FindUsers() ([]*mackerel.User, error) {
if c.findUsersCallback != nil {
return c.findUsersCallback()
}
return nil, errCallbackNotFound("FindUsers")
}

// MockFindUsers returns an option to set the callback of FindUsers
func MockFindUsers(callback func() ([]*mackerel.User, error)) MockClientOption {
return func(c *MockClient) {
c.findUsersCallback = callback
}
}
75 changes: 75 additions & 0 deletions users/app.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
package users

import (
"fmt"
"io"
"text/template"
"time"

"github.com/mackerelio/mkr/format"
"github.com/mackerelio/mkr/mackerelclient"
)

type appLogger interface {
Log(string, string)
Error(error)
}

type userApp struct {
client mackerelclient.Client
logger appLogger
outStream io.Writer
jqFilter string
}

type findUsersParam struct {
verbose bool
format string
}

// User defines output json structure.
type User struct {
ID string `json:"id,omitempty"`
ScreenName string `json:"screenName,omitempty"`
Email string `json:"email,omitempty"`
Authority string `json:"authority,omitempty"`
IsInRegistrationProcess bool `json:"isInRegistrationProcess"`
IsMFAEnabled bool `json:"isMFAEnabled"`
AuthenticationMethods []string `json:"authenticationMethods,omitempty"`
JoinedAt string `json:"joinedAt,omitempty"`
}

func (ua *userApp) findUsers(param findUsersParam) error {
users, err := ua.client.FindUsers()
if err != nil {
return err
}

switch {
case param.format != "" && ua.jqFilter != "":
return fmt.Errorf("--format and --jq options are incompatible.")
case param.format != "":
t, err := template.New("format").Parse(param.format)
if err != nil {
return err
}
return t.Execute(ua.outStream, users)
case param.verbose:
return format.PrettyPrintJSON(ua.outStream, users, ua.jqFilter)
default:
usersFormat := make([]*User, 0)
for _, user := range users {
usersFormat = append(usersFormat, &User{
ID: user.ID,
ScreenName: user.ScreenName,
Email: user.Email,
Authority: user.Authority,
IsInRegistrationProcess: user.IsInRegistrationProcess,
IsMFAEnabled: user.IsMFAEnabled,
AuthenticationMethods: user.AuthenticationMethods,
JoinedAt: format.ISO8601Extended(time.Unix(user.JoinedAt, 0)),
})
}
return format.PrettyPrintJSON(ua.outStream, usersFormat, ua.jqFilter)
}
}
179 changes: 179 additions & 0 deletions users/app_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,179 @@
package users

import (
"bytes"
"fmt"
"testing"
"time"

"github.com/mackerelio/mackerel-client-go"
"github.com/stretchr/testify/assert"

"github.com/mackerelio/mkr/mackerelclient"
)

var (
sampleUser1 = &mackerel.User{
ID: "user1",
ScreenName: "Alice",
Email: "alice@example.com",
Authority: "owner",
IsInRegistrationProcess: false,
IsMFAEnabled: true,
AuthenticationMethods: []string{"password", "google"},
JoinedAt: 1553000000,
}
sampleUser2 = &mackerel.User{
ID: "user2",
ScreenName: "Bob",
Email: "bob@example.com",
Authority: "collaborator",
IsInRegistrationProcess: true,
IsMFAEnabled: false,
AuthenticationMethods: []string{"password"},
JoinedAt: 1552000000,
}
)

func TestUserApp_FindUsers(t *testing.T) {
time.Local = time.FixedZone("Asia/Tokyo", 9*60*60)
defer func() { time.Local = nil }()
testCases := []struct {
id string
verbose bool
format string
users []*mackerel.User
expected string
}{
{
id: "default",
users: []*mackerel.User{sampleUser1, sampleUser2},
expected: `[
{
"id": "user1",
"screenName": "Alice",
"email": "alice@example.com",
"authority": "owner",
"isInRegistrationProcess": false,
"isMFAEnabled": true,
"authenticationMethods": [
"password",
"google"
],
"joinedAt": "2019-03-19T21:53:20+09:00"
},
{
"id": "user2",
"screenName": "Bob",
"email": "bob@example.com",
"authority": "collaborator",
"isInRegistrationProcess": true,
"isMFAEnabled": false,
"authenticationMethods": [
"password"
],
"joinedAt": "2019-03-08T08:06:40+09:00"
}
]
`,
},
{
id: "verbose",
users: []*mackerel.User{sampleUser1, sampleUser2},
verbose: true,
expected: `[
{
"id": "user1",
"screenName": "Alice",
"email": "alice@example.com",
"authority": "owner",
"isMFAEnabled": true,
"authenticationMethods": [
"password",
"google"
],
"joinedAt": 1553000000
},
{
"id": "user2",
"screenName": "Bob",
"email": "bob@example.com",
"authority": "collaborator",
"isInRegistrationProcess": true,
"authenticationMethods": [
"password"
],
"joinedAt": 1552000000
}
]
`,
},
{
id: "format",
users: []*mackerel.User{sampleUser1, sampleUser2},
format: `{{range .}}{{.ID}} {{.ScreenName}} {{.Authority}} {{.JoinedAt}}{{"\n"}}{{end}}`,
expected: `user1 Alice owner 1553000000
user2 Bob collaborator 1552000000
`,
},
{
id: "empty",
users: []*mackerel.User{},
expected: "[]\n",
},
}
for _, tc := range testCases {
client := mackerelclient.NewMockClient(
mackerelclient.MockFindUsers(func() ([]*mackerel.User, error) {
return tc.users, nil
}),
)
t.Run(tc.id, func(t *testing.T) {
out := new(bytes.Buffer)
app := &userApp{
client: client,
outStream: out,
}
assert.NoError(t, app.findUsers(findUsersParam{
verbose: tc.verbose,
format: tc.format,
}))
assert.Equal(t, tc.expected, out.String())
})
}
}

func TestUserApp_FindUsersError(t *testing.T) {
client := mackerelclient.NewMockClient(
mackerelclient.MockFindUsers(func() ([]*mackerel.User, error) {
return nil, fmt.Errorf("API error")
}),
)
out := new(bytes.Buffer)
app := &userApp{
client: client,
outStream: out,
}
err := app.findUsers(findUsersParam{})
assert.Error(t, err)
assert.Equal(t, "API error", err.Error())
}

func TestUserApp_FindUsersFormatAndJqIncompatible(t *testing.T) {
client := mackerelclient.NewMockClient(
mackerelclient.MockFindUsers(func() ([]*mackerel.User, error) {
return []*mackerel.User{sampleUser1}, nil
}),
)
out := new(bytes.Buffer)
app := &userApp{
client: client,
outStream: out,
jqFilter: ".[]",
}
err := app.findUsers(findUsersParam{
format: "{{.ID}}",
})
assert.Error(t, err)
assert.Equal(t, "--format and --jq options are incompatible.", err.Error())
}
43 changes: 43 additions & 0 deletions users/command.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
package users

import (
"os"

"github.com/mackerelio/mkr/jq"
"github.com/mackerelio/mkr/logger"
"github.com/mackerelio/mkr/mackerelclient"
"github.com/urfave/cli"
)

var CommandUsers = cli.Command{
Name: "users",
Usage: "List users",
ArgsUsage: "[--verbose | -v] [--format | -f <format>] [--jq <formula>]",
Description: `
List the information of the users.
Requests "GET /api/v0/users". See https://mackerel.io/api-docs/entry/users#list .
`,
Action: doUsers,
Flags: []cli.Flag{
cli.StringFlag{Name: "format, f", Value: "", Usage: "Output format template"},
cli.BoolFlag{Name: "verbose, v", Usage: "Verbose output mode"},
jq.CommandLineFlag,
},
}

func doUsers(c *cli.Context) error {
client, err := mackerelclient.New(c.GlobalString("conf"), c.GlobalString("apibase"))
if err != nil {
return err
}

return (&userApp{
client: client,
logger: logger.New(),
outStream: os.Stdout,
jqFilter: c.String("jq"),
}).findUsers(findUsersParam{
verbose: c.Bool("verbose"),
format: c.String("format"),
})
}
Loading