Repository: puppetlabs/wash
Pull request #217 (2019-04-18 18:30:04)

Extract client to interface; re-define meta + simplify plugins; fix bug in apifs
Contributions to this project require sign-off consistent with the [Developers Certificate of Origin](https://developercertificate.org). This can be as simple as using `git commit -s` on each commit.


Patch:
diff --git a/api/client/client.go b/api/client/client.go
index eb3560b..edace42 100644
--- a/api/client/client.go
+++ b/api/client/client.go
@@ -25,18 +25,30 @@ import (
 	apitypes "github.com/puppetlabs/wash/api/types"
 )
 
-// A DomainSocketClient is a wash API client.
-type DomainSocketClient struct {
-	http.Client
+// Client represents a Wash API client.
+type Client interface {
+	Info(path string) (apitypes.Entry, error)
+	List(path string) ([]apitypes.Entry, error)
+	Metadata(path string) (map[string]interface{}, error)
+	Stream(path string) (io.ReadCloser, error)
+	Exec(path string, command string, args []string, opts apitypes.ExecOptions) (<-chan apitypes.ExecPacket, error)
+	History() ([]apitypes.Activity, error)
+	ActivityJournal(index int) (io.ReadCloser, error)
+	Clear(path string) ([]string, error)
+}
+
+// A domainSocketClient is a wash API client.
+type domainSocketClient struct {
+	*http.Client
 }
 
 var domainSocketBaseURL = "http://localhost"
 
 // ForUNIXSocket returns a client suitable for making wash API calls over a UNIX
 // domain socket.
-func ForUNIXSocket(pathToSocket string) DomainSocketClient {
-	return DomainSocketClient{
-		http.Client{
+func ForUNIXSocket(pathToSocket string) Client {
+	return &domainSocketClient{
+		&http.Client{
 			Transport: &http.Transport{
 				DialContext: func(_ context.Context, _, _ string) (net.Conn, error) {
 					return net.Dial("unix", pathToSocket)
@@ -58,7 +70,7 @@ func unmarshalErrorResp(resp *http.Response) error {
 	return &errorObj
 }
 
-func (c *DomainSocketClient) doRequest(method, endpoint, path string, body io.Reader) (io.ReadCloser, error) {
+func (c *domainSocketClient) doRequest(method, endpoint, path string, body io.Reader) (io.ReadCloser, error) {
 	path, err := filepath.Abs(path)
 	if err != nil {
 		return nil, fmt.Errorf("could not calculate the absolute path of %v: %v", path, err)
@@ -87,7 +99,7 @@ func (c *DomainSocketClient) doRequest(method, endpoint, path string, body io.Re
 	return nil, unmarshalErrorResp(resp)
 }
 
-func (c *DomainSocketClient) getRequest(endpoint, path string, result interface{}) error {
+func (c *domainSocketClient) getRequest(endpoint, path string, result interface{}) error {
 	respBody, err := c.doRequest(http.MethodGet, endpoint, path, nil)
 	if err != nil {
 		return err
@@ -107,7 +119,7 @@ func (c *DomainSocketClient) getRequest(endpoint, path string, result interface{
 }
 
 // Info retrieves the information of the resource located at "path"
-func (c *DomainSocketClient) Info(path string) (apitypes.Entry, error) {
+func (c *domainSocketClient) Info(path string) (apitypes.Entry, error) {
 	var e apitypes.Entry
 	if err := c.getRequest("/fs/info", path, &e); err != nil {
 		return e, err
@@ -117,7 +129,7 @@ func (c *DomainSocketClient) Info(path string) (apitypes.Entry, error) {
 }
 
 // List lists the resources located at "path".
-func (c *DomainSocketClient) List(path string) ([]apitypes.Entry, error) {
+func (c *domainSocketClient) List(path string) ([]apitypes.Entry, error) {
 	var ls []apitypes.Entry
 	if err := c.getRequest("/fs/list", path, &ls); err != nil {
 		return nil, err
@@ -127,7 +139,7 @@ func (c *DomainSocketClient) List(path string) ([]apitypes.Entry, error) {
 }
 
 // Metadata gets the metadata of the resource located at "path".
-func (c *DomainSocketClient) Metadata(path string) (map[string]interface{}, error) {
+func (c *domainSocketClient) Metadata(path string) (map[string]interface{}, error) {
 	var metadata map[string]interface{}
 	if err := c.getRequest("/fs/metadata", path, &metadata); err != nil {
 		return nil, err
@@ -137,7 +149,7 @@ func (c *DomainSocketClient) Metadata(path string) (map[string]interface{}, erro
 }
 
 // Stream updates for the resource located at "path".
-func (c *DomainSocketClient) Stream(path string) (io.ReadCloser, error) {
+func (c *domainSocketClient) Stream(path string) (io.ReadCloser, error) {
 	respBody, err := c.doRequest(http.MethodGet, "/fs/stream", path, nil)
 	if err != nil {
 		return nil, err
@@ -150,7 +162,7 @@ func (c *DomainSocketClient) Stream(path string) (io.ReadCloser, error) {
 //
 // The resulting channel contains events, ordered as we receive them from the
 // server. The channel will be closed when there are no more events.
-func (c *DomainSocketClient) Exec(path string, command string, args []string, opts apitypes.ExecOptions) (<-chan apitypes.ExecPacket, error) {
+func (c *domainSocketClient) Exec(path string, command string, args []string, opts apitypes.ExecOptions) (<-chan apitypes.ExecPacket, error) {
 	payload := apitypes.ExecBody{Cmd: command, Args: args, Opts: opts}
 	jsonBody, err := json.Marshal(payload)
 	if err != nil {
@@ -186,7 +198,7 @@ func (c *DomainSocketClient) Exec(path string, command string, args []string, op
 }
 
 // History returns the command history for the current wash server session.
-func (c *DomainSocketClient) History() ([]apitypes.Activity, error) {
+func (c *domainSocketClient) History() ([]apitypes.Activity, error) {
 	// Intentionally skip journaling activity associated with history because that would modify it.
 	resp, err := c.Get(domainSocketBaseURL + "/history")
 	if err != nil {
@@ -212,7 +224,7 @@ func (c *DomainSocketClient) History() ([]apitypes.Activity, error) {
 }
 
 // ActivityJournal returns a reader for the journal associated with a particular command in history.
-func (c *DomainSocketClient) ActivityJournal(index int) (io.ReadCloser, error) {
+func (c *domainSocketClient) ActivityJournal(index int) (io.ReadCloser, error) {
 	// Intentionally skip journaling activity associated with history because that would modify it.
 	resp, err := c.Get(domainSocketBaseURL + "/history/" + strconv.Itoa(index))
 	if err != nil {
@@ -227,7 +239,7 @@ func (c *DomainSocketClient) ActivityJournal(index int) (io.ReadCloser, error) {
 }
 
 // Clear the cache at "path".
-func (c *DomainSocketClient) Clear(path string) ([]string, error) {
+func (c *domainSocketClient) Clear(path string) ([]string, error) {
 	respBody, err := c.doRequest(http.MethodDelete, "/cache", path, nil)
 	if err != nil {
 		return nil, err
diff --git a/api/fs/core.go b/api/fs/core.go
index a162c04..6df85ce 100644
--- a/api/fs/core.go
+++ b/api/fs/core.go
@@ -3,7 +3,9 @@
 package apifs
 
 import (
+	"context"
 	"os"
+	"time"
 
 	"github.com/puppetlabs/wash/plugin"
 )
@@ -14,7 +16,7 @@ type fsnode struct {
 	path string
 }
 
-func newFSNode(finfo os.FileInfo, path string) *fsnode {
+func newFSNode(ctx context.Context, finfo os.FileInfo, path string) *fsnode {
 	// TODO: finfo.Sys() contains more detailed file attributes,
 	// but it's platform-specific. We should eventually use it for
 	// a more complete implementation of apifs.
@@ -23,7 +25,7 @@ func newFSNode(finfo os.FileInfo, path string) *fsnode {
 		SetMtime(finfo.ModTime()).
 		SetMode(finfo.Mode()).
 		SetSize(uint64(finfo.Size())).
-		SetMeta(plugin.ToMeta(finfo))
+		SetMeta(plugin.ToMeta(newFileInfo(finfo)))
 
 	n := &fsnode{
 		EntryBase: plugin.NewEntry(finfo.Name()),
@@ -36,13 +38,37 @@ func newFSNode(finfo os.FileInfo, path string) *fsnode {
 
 // NewEntry constructs a new Wash entry from the given FS
 // path
-func NewEntry(path string) (plugin.Entry, error) {
+func NewEntry(ctx context.Context, path string) (plugin.Entry, error) {
 	finfo, err := os.Stat(path)
 	if err != nil {
 		return nil, err
 	}
 	if finfo.IsDir() {
-		return newDir(finfo, path), nil
+		return newDir(ctx, finfo, path), nil
+	}
+	return newFile(ctx, finfo, path), nil
+}
+
+// os.FileInfo is an interface. Its implementations do not export
+// any fields, so we cannot directly marshal an os.FileInfo object
+// into JSON. Thus, we create a wrapper "fileInfo" object whose fields
+// map to os.FileInfo's methods.
+type fileInfo struct {
+	Name    string      `json:"name"`
+	Size    int64       `json:"size"`
+	Mode    os.FileMode `json:"mode"`
+	ModTime time.Time   `json:"modTime"`
+	IsDir   bool        `json:"isDir"`
+	Sys     interface{} `json:"sys"`
+}
+
+func newFileInfo(finfo os.FileInfo) fileInfo {
+	return fileInfo{
+		Name:    finfo.Name(),
+		Size:    finfo.Size(),
+		Mode:    finfo.Mode(),
+		ModTime: finfo.ModTime(),
+		IsDir:   finfo.IsDir(),
+		Sys:     finfo.Sys(),
 	}
-	return newFile(finfo, path), nil
 }
diff --git a/api/fs/dir.go b/api/fs/dir.go
index 98713af..fa328a0 100644
--- a/api/fs/dir.go
+++ b/api/fs/dir.go
@@ -12,9 +12,9 @@ type dir struct {
 	*fsnode
 }
 
-func newDir(finfo os.FileInfo, path string) *dir {
+func newDir(ctx context.Context, finfo os.FileInfo, path string) *dir {
 	return &dir{
-		newFSNode(finfo, path),
+		newFSNode(ctx, finfo, path),
 	}
 }
 
@@ -26,7 +26,7 @@ func (d *dir) List(ctx context.Context) ([]plugin.Entry, error) {
 
 	entries := make([]plugin.Entry, len(matches))
 	for i, match := range matches {
-		entry, err := NewEntry(match)
+		entry, err := NewEntry(ctx, match)
 		if err != nil {
 			return nil, err
 		}
diff --git a/api/fs/file.go b/api/fs/file.go
index e0c777e..0e55538 100644
--- a/api/fs/file.go
+++ b/api/fs/file.go
@@ -13,9 +13,9 @@ type file struct {
 	*fsnode
 }
 
-func newFile(finfo os.FileInfo, path string) *file {
+func newFile(ctx context.Context, finfo os.FileInfo, path string) *file {
 	return &file{
-		newFSNode(finfo, path),
+		newFSNode(ctx, finfo, path),
 	}
 }
 
diff --git a/api/helpers.go b/api/helpers.go
index 8b7a11f..56e745a 100644
--- a/api/helpers.go
+++ b/api/helpers.go
@@ -93,7 +93,7 @@ func getEntryFromRequest(ctx context.Context, r *http.Request) (plugin.Entry, st
 		// but it does mean that we'll need to re-evaluate this code once
 		// we get to the point where supporting remote Wash servers is
 		// desirable.
-		e, err := apifs.NewEntry(path)
+		e, err := apifs.NewEntry(ctx, path)
 		if err != nil {
 			if os.IsNotExist(err) {
 				return nil, "", entryNotFoundResponse(path, err.Error())
diff --git a/cmd/internal/find/clientWrappers.go b/cmd/internal/find/clientWrappers.go
index 70750e3..a69692e 100644
--- a/cmd/internal/find/clientWrappers.go
+++ b/cmd/internal/find/clientWrappers.go
@@ -6,7 +6,7 @@ import (
 )
 
 // info is a wrapper to c.Info
-func info(c *client.DomainSocketClient, path string) (types.Entry, error) {
+func info(c client.Client, path string) (types.Entry, error) {
 	e, err := c.Info(path)
 	if err != nil {
 		return types.Entry{}, err
@@ -19,7 +19,7 @@ func info(c *client.DomainSocketClient, path string) (types.Entry, error) {
 
 // list is a wrapper to c.List that handles normalizing the children's
 // path relative to e's normalized path
-func list(c *client.DomainSocketClient, e types.Entry) ([]types.Entry, error) {
+func list(c client.Client, e types.Entry) ([]types.Entry, error) {
 	rawChildren, err := c.List(e.Path)
 	if err != nil {
 		return nil, err
diff --git a/cmd/internal/find/main.go b/cmd/internal/find/main.go
index 55968c5..f88e5ba 100644
--- a/cmd/internal/find/main.go
+++ b/cmd/internal/find/main.go
@@ -25,11 +25,11 @@ func Main(cmd *cobra.Command, args []string) int {
 
 	conn := client.ForUNIXSocket(config.Socket)
 
-	e, err := info(&conn, result.Path)
+	e, err := info(conn, result.Path)
 	if err != nil {
 		cmdutil.ErrPrintf("%v\n", err)
 		return 1
 	}
-	newWalker(result, &conn).Walk(e)
+	newWalker(result, conn).Walk(e)
 	return 0
 }
diff --git a/cmd/internal/find/walker.go b/cmd/internal/find/walker.go
index 098e422..d79ddf4 100644
--- a/cmd/internal/find/walker.go
+++ b/cmd/internal/find/walker.go
@@ -13,10 +13,10 @@ import (
 type walker struct {
 	p    types.Predicate
 	opts types.Options
-	conn *client.DomainSocketClient
+	conn client.Client
 }
 
-func newWalker(r parser.Result, conn *client.DomainSocketClient) *walker {
+func newWalker(r parser.Result, conn client.Client) *walker {
 	return &walker{
 		p:    r.Predicate,
 		opts: r.Options,
diff --git a/cmd/tail.go b/cmd/tail.go
index ff367e9..5790e82 100644
--- a/cmd/tail.go
+++ b/cmd/tail.go
@@ -52,7 +52,7 @@ func (w lineWriter) Write(b []byte) (int, error) {
 
 // Streams output via API to aggregator channel.
 // Returns nil if streaming's not supported on this path.
-func tailStream(conn client.DomainSocketClient, agg chan line, path string) io.Closer {
+func tailStream(conn client.Client, agg chan line, path string) io.Closer {
 	stream, err := conn.Stream(path)
 	if err != nil {
 		if errObj, ok := err.(*apitypes.ErrorObj); ok {
diff --git a/plugin/aws/ec2InstanceConsoleOutput.go b/plugin/aws/ec2InstanceConsoleOutput.go
index ee557dd..275308e 100644
--- a/plugin/aws/ec2InstanceConsoleOutput.go
+++ b/plugin/aws/ec2InstanceConsoleOutput.go
@@ -42,8 +42,7 @@ func newEC2InstanceConsoleOutput(ctx context.Context, inst *ec2Instance, latest
 		SetCtime(output.mtime).
 		SetMtime(output.mtime).
 		SetAtime(output.mtime).
-		SetSize(uint64(len(output.content))).
-		SetMeta(output.toMeta())
+		SetSize(uint64(len(output.content)))
 	cl.SetAttributes(attr)
 
 	return cl, nil
@@ -54,13 +53,6 @@ type consoleOutput struct {
 	content []byte
 }
 
-func (o consoleOutput) toMeta() plugin.EntryMetadata {
-	return plugin.EntryMetadata{
-		"LastModified": o.mtime,
-		"Size":         len(o.content),
-	}
-}
-
 func (cl *ec2InstanceConsoleOutput) cachedConsoleOutput(ctx context.Context) (consoleOutput, error) {
 	output, err := plugin.CachedOp(ctx, "ConsoleOutput", cl, 30*time.Second, func() (interface{}, error) {
 		request := &ec2Client.GetConsoleOutputInput{
@@ -93,15 +85,6 @@ func (cl *ec2InstanceConsoleOutput) cachedConsoleOutput(ctx context.Context) (co
 	return output.(consoleOutput), nil
 }
 
-func (cl *ec2InstanceConsoleOutput) Metadata(ctx context.Context) (plugin.EntryMetadata, error) {
-	output, err := cl.cachedConsoleOutput(ctx)
-	if err != nil {
-		return nil, err
-	}
-
-	return output.toMeta(), nil
-}
-
 func (cl *ec2InstanceConsoleOutput) Open(ctx context.Context) (plugin.SizedReader, error) {
 	output, err := cl.cachedConsoleOutput(ctx)
 	if err != nil {
diff --git a/plugin/aws/ec2InstanceMetadataJSON.go b/plugin/aws/ec2InstanceMetadataJSON.go
index 5f69d0c..01f9460 100644
--- a/plugin/aws/ec2InstanceMetadataJSON.go
+++ b/plugin/aws/ec2InstanceMetadataJSON.go
@@ -22,30 +22,18 @@ func newEC2InstanceMetadataJSON(ctx context.Context, inst *ec2Instance) (*ec2Ins
 	}
 	im.DisableDefaultCaching()
 
-	meta, err := im.Metadata(ctx)
+	content, err := im.Open(ctx)
 	if err != nil {
 		return nil, err
 	}
 
 	attr := plugin.EntryAttributes{}
-	attr.SetSize(uint64(meta["Size"].(int64)))
-	attr.SetMeta(meta)
+	attr.SetSize(uint64(content.Size()))
 	im.SetAttributes(attr)
 
 	return im, nil
 }
 
-func (im *ec2InstanceMetadataJSON) Metadata(ctx context.Context) (plugin.EntryMetadata, error) {
-	content, err := im.Open(ctx)
-	if err != nil {
-		return nil, err
-	}
-
-	return plugin.EntryMetadata{
-		"Size": content.Size(),
-	}, nil
-}
-
 func (im *ec2InstanceMetadataJSON) Open(ctx context.Context) (plugin.SizedReader, error) {
 	meta, err := plugin.CachedMetadata(ctx, im.inst)
 	if err != nil {
diff --git a/plugin/docker/container-log.go b/plugin/docker/container-log.go
index 40fb409..6bc9ea4 100644
--- a/plugin/docker/container-log.go
+++ b/plugin/docker/container-log.go
@@ -28,17 +28,6 @@ func (clf *containerLogFile) isTty(ctx context.Context) bool {
 	return true
 }
 
-func (clf *containerLogFile) Metadata(ctx context.Context) (plugin.EntryMetadata, error) {
-	content, err := plugin.CachedOpen(ctx, clf)
-	if err != nil {
-		return nil, err
-	}
-
-	return plugin.EntryMetadata{
-		"Size": content.Size(),
-	}, nil
-}
-
 func (clf *containerLogFile) Open(ctx context.Context) (plugin.SizedReader, error) {
 	opts := types.ContainerLogsOptions{ShowStdout: true, ShowStderr: true}
 	rdr, err := clf.client.ContainerLogs(ctx, clf.containerName, opts)
diff --git a/plugin/docker/container-metadata.go b/plugin/docker/container-metadata.go
index 85bcfd5..8b17df9 100644
--- a/plugin/docker/container-metadata.go
+++ b/plugin/docker/container-metadata.go
@@ -13,17 +13,6 @@ type containerMetadata struct {
 	container *container
 }
 
-func (cm *containerMetadata) Metadata(ctx context.Context) (plugin.EntryMetadata, error) {
-	content, err := cm.Open(ctx)
-	if err != nil {
-		return nil, err
-	}
-
-	return plugin.EntryMetadata{
-		"Size": content.Size(),
-	}, nil
-}
-
 func (cm *containerMetadata) Open(ctx context.Context) (plugin.SizedReader, error) {
 	metadata, err := plugin.CachedMetadata(ctx, cm.container)
 	if err != nil {
diff --git a/plugin/docker/container.go b/plugin/docker/container.go
index 489b685..3bba64c 100644
--- a/plugin/docker/container.go
+++ b/plugin/docker/container.go
@@ -33,22 +33,22 @@ func (c *container) List(ctx context.Context) ([]plugin.Entry, error) {
 	// read-only files. Lots of shared code between these two.
 	cm := &containerMetadata{plugin.NewEntry("metadata.json"), c}
 	cm.DisableDefaultCaching()
-	meta, err := cm.Metadata(ctx)
+	content, err := cm.Open(ctx)
 	if err != nil {
 		return nil, err
 	}
 	cmAttr := plugin.EntryAttributes{}
-	cmAttr.SetSize(uint64(meta["Size"].(int64)))
+	cmAttr.SetSize(uint64(content.Size()))
 	cm.SetAttributes(cmAttr)
 
 	clf := &containerLogFile{plugin.NewEntry("log"), c.id, c.client}
 	clf.DisableCachingFor(plugin.MetadataOp)
-	meta, err = clf.Metadata(ctx)
+	content, err = clf.Open(ctx)
 	if err != nil {
 		return nil, err
 	}
 	clfAttr := plugin.EntryAttributes{}
-	clfAttr.SetSize(uint64(meta["Size"].(int64)))
+	clfAttr.SetSize(uint64(content.Size()))
 	clf.SetAttributes(clfAttr)
 
 	return []plugin.Entry{cm, clf}, nil
diff --git a/plugin/entryAttributes.go b/plugin/entryAttributes.go
index afeb487..61b03de 100644
--- a/plugin/entryAttributes.go
+++ b/plugin/entryAttributes.go
@@ -160,11 +160,15 @@ func (a *EntryAttributes) SetSize(size uint64) *EntryAttributes {
 	return a
 }
 
-// Meta returns the entry's metadata. All entries have a
-// meta attribute, which by default is an empty map.
+// Meta returns the portion of the entry's metadata that's returned by the
+// plugin API's List endpoint. This may or may not match what's returned by
+// e.Metadata (i.e. the entry's full metadata). That detail is plugin-specific.
+//
+// If a.SetMeta(m) was called, then this returns m. Otherwise, it returns
+// a.ToMap(false).
 func (a *EntryAttributes) Meta() EntryMetadata {
 	if a.meta == nil {
-		return EntryMetadata{}
+		return a.ToMap(false)
 	}
 
 	return a.meta
@@ -176,10 +180,9 @@ func (a *EntryAttributes) SetMeta(meta EntryMetadata) *EntryAttributes {
 	return a
 }
 
-// ToMap converts the entry's attributes to a map,
-// which makes it easier to write generic code on
-// them.
-func (a *EntryAttributes) ToMap() map[string]interface{} {
+// ToMap converts the entry's attributes to a map, which makes it easier to write
+// generic code on them.
+func (a *EntryAttributes) ToMap(includeMeta bool) map[string]interface{} {
 	mp := make(map[string]interface{})
 	if a.HasAtime() {
 		mp["atime"] = a.Atime()
@@ -196,13 +199,15 @@ func (a *EntryAttributes) ToMap() map[string]interface{} {
 	if a.HasSize() {
 		mp["size"] = a.Size()
 	}
-	mp["meta"] = a.Meta()
+	if includeMeta {
+		mp["meta"] = a.Meta()
+	}
 	return mp
 }
 
 // MarshalJSON marshals the entry's attributes to JSON.
 func (a *EntryAttributes) MarshalJSON() ([]byte, error) {
-	return json.Marshal(a.ToMap())
+	return json.Marshal(a.ToMap(true))
 }
 
 // UnmarshalJSON unmarshals the entry's attributes from JSON.
diff --git a/plugin/entryBase.go b/plugin/entryBase.go
index 2e3861c..998e347 100644
--- a/plugin/entryBase.go
+++ b/plugin/entryBase.go
@@ -49,7 +49,9 @@ func NewEntry(name string) EntryBase {
 // ENTRY INTERFACE
 
 // Metadata returns the entry's meta attribute (see plugin.EntryAttributes).
-// Do not override this if the entry's metadata will never change.
+// This should be overridden if e's full metadata does not match the meta
+// attribute, which is true if the plugin API's List endpoint returns only a
+// subset of the full metadata.
 func (e *EntryBase) Metadata(ctx context.Context) (EntryMetadata, error) {
 	// Disable Metadata's caching in case the plugin author forgot to do this
 	e.DisableCachingFor(MetadataOp)


Test patch:
diff --git a/plugin/entryAttributes_test.go b/plugin/entryAttributes_test.go
index 9f1a00f..06ebae8 100644
--- a/plugin/entryAttributes_test.go
+++ b/plugin/entryAttributes_test.go
@@ -63,57 +63,57 @@ func (suite *EntryAttributesTestSuite) TestEntryAttributes() {
 
 	// Tests for Atime
 	suite.Equal(false, attr.HasAtime())
-	suite.Equal(expectedMp, attr.ToMap())
+	suite.Equal(expectedMp, attr.ToMap(true))
 	t := timeNow()
 	attr.SetAtime(t)
 	expectedMp["atime"] = t
 	suite.Equal(t, attr.Atime())
 	suite.Equal(true, attr.HasAtime())
-	suite.Equal(expectedMp, attr.ToMap())
+	suite.Equal(expectedMp, attr.ToMap(true))
 	doUnmarshalJSONTests()
 
 	// Tests for Mtime
 	suite.Equal(false, attr.HasMtime())
-	suite.Equal(expectedMp, attr.ToMap())
+	suite.Equal(expectedMp, attr.ToMap(true))
 	t = timeNow()
 	attr.SetMtime(t)
 	expectedMp["mtime"] = t
 	suite.Equal(t, attr.Mtime())
 	suite.Equal(true, attr.HasMtime())
-	suite.Equal(expectedMp, attr.ToMap())
+	suite.Equal(expectedMp, attr.ToMap(true))
 	doUnmarshalJSONTests()
 
 	// Tests for Ctime
 	suite.Equal(false, attr.HasCtime())
-	suite.Equal(expectedMp, attr.ToMap())
+	suite.Equal(expectedMp, attr.ToMap(true))
 	t = timeNow()
 	attr.SetCtime(t)
 	expectedMp["ctime"] = t
 	suite.Equal(t, attr.Ctime())
 	suite.Equal(true, attr.HasCtime())
-	suite.Equal(expectedMp, attr.ToMap())
+	suite.Equal(expectedMp, attr.ToMap(true))
 	doUnmarshalJSONTests()
 
 	// Tests for Mode
 	suite.Equal(false, attr.HasMode())
-	suite.Equal(expectedMp, attr.ToMap())
+	suite.Equal(expectedMp, attr.ToMap(true))
 	m := os.FileMode(0777)
 	attr.SetMode(m)
 	expectedMp["mode"] = m
 	suite.Equal(m, attr.Mode())
 	suite.Equal(true, attr.HasMode())
-	suite.Equal(expectedMp, attr.ToMap())
+	suite.Equal(expectedMp, attr.ToMap(true))
 	doUnmarshalJSONTests()
 
 	// Tests for Size
 	suite.Equal(false, attr.HasSize())
-	suite.Equal(expectedMp, attr.ToMap())
+	suite.Equal(expectedMp, attr.ToMap(true))
 	sz := uint64(10)
 	attr.SetSize(sz)
 	expectedMp["size"] = sz
 	suite.Equal(sz, attr.Size())
 	suite.Equal(true, attr.HasSize())
-	suite.Equal(expectedMp, attr.ToMap())
+	suite.Equal(expectedMp, attr.ToMap(true))
 	doUnmarshalJSONTests()
 
 	// Tests for Meta
@@ -122,7 +122,7 @@ func (suite *EntryAttributesTestSuite) TestEntryAttributes() {
 	attr.SetMeta(meta)
 	expectedMp["meta"] = meta
 	suite.Equal(meta, attr.Meta())
-	suite.Equal(expectedMp, attr.ToMap())
+	suite.Equal(expectedMp, attr.ToMap(true))
 	doUnmarshalJSONTests()
 }