-
Notifications
You must be signed in to change notification settings - Fork 2
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Fix docker cp #20
base: master
Are you sure you want to change the base?
Fix docker cp #20
Changes from 1 commit
bb0d010
e55a292
b18043e
7043a09
c0d28c0
03f2054
0764601
1985b52
3a67652
c57da77
f9aadb4
38424e8
197c6fe
f6634d9
e441320
149fe1a
01de48d
ebe57ea
10b7f9c
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
@@ -0,0 +1,110 @@ | ||||||||||||||||||||||||||
package main | ||||||||||||||||||||||||||
|
||||||||||||||||||||||||||
import ( | ||||||||||||||||||||||||||
"encoding/json" | ||||||||||||||||||||||||||
"errors" | ||||||||||||||||||||||||||
"fmt" | ||||||||||||||||||||||||||
"io" | ||||||||||||||||||||||||||
"os" | ||||||||||||||||||||||||||
|
||||||||||||||||||||||||||
"github.com/docker/go-plugins-helpers/volume" | ||||||||||||||||||||||||||
) | ||||||||||||||||||||||||||
|
||||||||||||||||||||||||||
type ActiveMount struct { | ||||||||||||||||||||||||||
UsageCount int | ||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||
|
||||||||||||||||||||||||||
func (d *DockerOnTop) Activate(request *volume.MountRequest, activemountsdir lockedFile) (bool, error) { | ||||||||||||||||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. A comment on both
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I hope this looks better now with the changes I have done: There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Also commit 3a67652 since I didn't properly rename |
||||||||||||||||||||||||||
var activeMount ActiveMount | ||||||||||||||||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It could be declared closer to its usage, making it easier to read the code There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Fixed 7043a09 |
||||||||||||||||||||||||||
var result bool | ||||||||||||||||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Could we make this name more informative? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. |
||||||||||||||||||||||||||
|
||||||||||||||||||||||||||
_, readDirErr := activemountsdir.ReadDir(1) // Check if there are any files inside activemounts dir | ||||||||||||||||||||||||||
if readDirErr == nil { | ||||||||||||||||||||||||||
// There is something no need to mount the filesystem again | ||||||||||||||||||||||||||
result = false | ||||||||||||||||||||||||||
} else if errors.Is(readDirErr, io.EOF) { | ||||||||||||||||||||||||||
// The directory is empty, mount the filesystem | ||||||||||||||||||||||||||
result = true | ||||||||||||||||||||||||||
} else { | ||||||||||||||||||||||||||
log.Errorf("Failed to list the activemounts directory: %v", readDirErr) | ||||||||||||||||||||||||||
return false, fmt.Errorf("failed to list activemounts/ %v", readDirErr) | ||||||||||||||||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Done. |
||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||
|
||||||||||||||||||||||||||
activemountFilePath := d.activemountsdir(request.Name) + request.ID | ||||||||||||||||||||||||||
file, err := os.Open(activemountFilePath) | ||||||||||||||||||||||||||
|
||||||||||||||||||||||||||
if err == nil { | ||||||||||||||||||||||||||
// The file can exist from a previous mount when doing a docker cp on an already mounted container, no need to mount the filesystem again | ||||||||||||||||||||||||||
payload, _ := io.ReadAll(file) | ||||||||||||||||||||||||||
json.Unmarshal(payload, &activeMount) | ||||||||||||||||||||||||||
file.Close() | ||||||||||||||||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. All three of these functions may return an error. In go, you usually want to check the error value when one is returned and handle it somehow. In some cases it makes sense to behave in one way or another when there's an error; in other cases the best you can do is just log the error and/or return it from your function (so the caller has to deal with it). Finally, in some (pretty rare) cases you indeed want to ignore an error value returned by a function. In such cases it's better to do this explicitly, e.g., In your case:
Btw, you can see some examples of documented functions in volumeTreeManagement.go. Ironically, all of them are unexported There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Fixed: f6634d9 |
||||||||||||||||||||||||||
} else if os.IsNotExist(err) { | ||||||||||||||||||||||||||
// Default case, we need to create a new active mount, the filesystem needs to be mounted | ||||||||||||||||||||||||||
activeMount = ActiveMount{UsageCount: 0} | ||||||||||||||||||||||||||
} else { | ||||||||||||||||||||||||||
log.Errorf("Active mount file %s exists but cannot be read.", activemountFilePath) | ||||||||||||||||||||||||||
return false, fmt.Errorf("active mount file %s exists but cannot be read", activemountFilePath) | ||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I have applied the suggestions as they do make good sense. As if it was intended, the case and punctuation changes they were, initially they where the same with human readable format (casing and sentence ending dots) but the linter complained about that format on the error message. That's how it ended up like this. |
||||||||||||||||||||||||||
|
||||||||||||||||||||||||||
activeMount.UsageCount++ | ||||||||||||||||||||||||||
|
||||||||||||||||||||||||||
payload, _ := json.Marshal(activeMount) | ||||||||||||||||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The error is ignored explicitly but it's not clear to me why. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. it's not clear to me why. json.Marshal's behavior should be much more predictable than that of json.Unmarshal I have added a comment to indicate why it is safe to ignore the error, which basically boils down to the fact that there is no room for error when converting an structure containing a single integer to JSON. |
||||||||||||||||||||||||||
err = os.WriteFile(activemountFilePath, payload, 0o666) | ||||||||||||||||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Indeed, 0764601 |
||||||||||||||||||||||||||
if err != nil { | ||||||||||||||||||||||||||
log.Errorf("Active mount file %s cannot be written.", activemountFilePath) | ||||||||||||||||||||||||||
return false, fmt.Errorf("active mount file %s cannot be written", activemountFilePath) | ||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Same two points as for a similar error handling earlier There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. |
||||||||||||||||||||||||||
|
||||||||||||||||||||||||||
return result, nil | ||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||
|
||||||||||||||||||||||||||
func (d *DockerOnTop) Deactivate(request *volume.UnmountRequest, activemountsdir lockedFile) (bool, error) { | ||||||||||||||||||||||||||
|
||||||||||||||||||||||||||
dirEntries, readDirErr := activemountsdir.ReadDir(2) // Check if there is any _other_ container using the volume | ||||||||||||||||||||||||||
if errors.Is(readDirErr, io.EOF) { | ||||||||||||||||||||||||||
// If directory is empty, unmount overlay and clean up | ||||||||||||||||||||||||||
log.Errorf("There are no active mount files and one was expected. Unmounting.") | ||||||||||||||||||||||||||
return true, fmt.Errorf("there are no active mount files and one was expected. Unmounting") | ||||||||||||||||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Same two points as for a similar error handling earlier There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. |
||||||||||||||||||||||||||
} else if readDirErr != nil { | ||||||||||||||||||||||||||
log.Errorf("Failed to list the activemounts directory: %v", readDirErr) | ||||||||||||||||||||||||||
return false, fmt.Errorf("failed to list activemounts/ %v", readDirErr) | ||||||||||||||||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Done |
||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||
|
||||||||||||||||||||||||||
var activeMount ActiveMount | ||||||||||||||||||||||||||
activemountFilePath := d.activemountsdir(request.Name) + request.ID | ||||||||||||||||||||||||||
|
||||||||||||||||||||||||||
otherVolumesPresent := len(dirEntries) > 1 || dirEntries[0].Name() != request.ID | ||||||||||||||||||||||||||
|
||||||||||||||||||||||||||
file, err := os.Open(activemountFilePath) | ||||||||||||||||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Unless there is a reason for these lines in this order, I'd reorder them as follows:
Suggested change
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Fixed: c0d28c0 |
||||||||||||||||||||||||||
|
||||||||||||||||||||||||||
if err == nil { | ||||||||||||||||||||||||||
payload, _ := io.ReadAll(file) | ||||||||||||||||||||||||||
json.Unmarshal(payload, &activeMount) | ||||||||||||||||||||||||||
file.Close() | ||||||||||||||||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Same error handling note as in There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Fixed: f6634d9 |
||||||||||||||||||||||||||
} else if os.IsNotExist(err) { | ||||||||||||||||||||||||||
log.Errorf("The active mount file %s was expected but is not there", activemountFilePath) | ||||||||||||||||||||||||||
return !otherVolumesPresent, fmt.Errorf("the active mount file %s was expected but is not there", activemountFilePath) | ||||||||||||||||||||||||||
} else { | ||||||||||||||||||||||||||
log.Errorf("The active mount file %s could not be opened", activemountFilePath) | ||||||||||||||||||||||||||
return false, fmt.Errorf("the active mount file %s could not be opened", activemountFilePath) | ||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Same two points as for a similar error handling earlier There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. |
||||||||||||||||||||||||||
|
||||||||||||||||||||||||||
activeMount.UsageCount-- | ||||||||||||||||||||||||||
|
||||||||||||||||||||||||||
if activeMount.UsageCount == 0 { | ||||||||||||||||||||||||||
err := os.Remove(activemountFilePath) | ||||||||||||||||||||||||||
if err != nil { | ||||||||||||||||||||||||||
log.Errorf("The active mount file %s could not be deleted", activemountFilePath) | ||||||||||||||||||||||||||
return false, fmt.Errorf("the active mount file %s could not be deleted", activemountFilePath) | ||||||||||||||||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Same two points as for a similar error handling earlier. Plus! As I can see, the behavior here is significantly different from that of previous One thing you've changed is the order: you're first trying to remove the file, and then, on success, attempt to unmount the overlay (unlike the previous code which attempted it the other way around). And that's for the better! In the old version, if an error occurred during the file deletion, this would bring the volume in a very bad state (overlay is not actually mounted but it looks like it is, so other containers will try to use it straight away - expect horrible bugs); with this change, if an error occurs during the file deletion, the volume is in a just bad state (the volume looks like it's used, although it's not, but it's at least properly mounted). The other thing you've changed is the logging level: it used to be So, actually, no problem that I first thought of when starting the "Plus!" part. Good job! But I would still add something to this error message, something like "this volume will never get unmounted until a reboot" There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'm not really convinced with the new text either, if you have a suggestion please do not hesitate to propose. |
||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||
return !otherVolumesPresent, nil | ||||||||||||||||||||||||||
} else { | ||||||||||||||||||||||||||
payload, _ := json.Marshal(activeMount) | ||||||||||||||||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Same note as for the previous There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. |
||||||||||||||||||||||||||
err = os.WriteFile(activemountFilePath, payload, 0o666) | ||||||||||||||||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Permission: presumably, you intended There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. You give me too much credit 😉, I probably copy-pasted that from somewhere else and didn't give it a second thought. Thankfully revisions exist. |
||||||||||||||||||||||||||
if err != nil { | ||||||||||||||||||||||||||
log.Errorf("The active mount file %s could not be updated", activemountFilePath) | ||||||||||||||||||||||||||
return false, fmt.Errorf("the active mount file %s could not be updated", activemountFilePath) | ||||||||||||||||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Same two points as for a similar error handling earlier There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. |
||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||
return false, nil | ||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -3,7 +3,6 @@ package main | |
import ( | ||
"errors" | ||
"fmt" | ||
"io" | ||
"os" | ||
"regexp" | ||
"strings" | ||
|
@@ -184,10 +183,10 @@ func (d *DockerOnTop) Mount(request *volume.MountRequest) (*volume.MountResponse | |
} | ||
defer activemountsdir.Close() // There is nothing I could do about the error (logging is performed inside `Close()` anyway) | ||
|
||
_, readDirErr := activemountsdir.ReadDir(1) // Check if there are any files inside activemounts dir | ||
if errors.Is(readDirErr, io.EOF) { | ||
// No files => no other containers are using the volume. Need to mount the overlay | ||
|
||
mount, err := d.Activate(request, activemountsdir) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It's not clear to me what kind of value the boolean variable Is it an instruction for us returned by There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Changed to doMountFs also to differenciate between the two meanings of "Mount". |
||
if err != nil { | ||
return nil, internalError("failed to activate the active mount:", err) | ||
} else if mount { | ||
lowerdir := thisVol.BaseDirPath | ||
upperdir := d.upperdir(request.Name) | ||
workdir := d.workdir(request.Name) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Read this comment first. Here you, unfortunately, did kind of the opposite (similarly, you reversed the original order, but it has the inverse effect in this case). Combining the logic inside
Now, imagine the file creation step succeeds but the next one fails. For that container, of course, we'll report the error and it will refuse to start, but the volume ends up in a very bad state. If another container tries to use that same volume, it will perform the same steps above, but in the first one it will find an activemount file from the previous container and will assume that the volume is already mounted, while it in fact is not!! The reverse order is not very good either but slightly better: if the first container manages to mount the volume but doesn't manage to mark it as mounted, it, likewise, won't let the current container start (so that So,
In fact, I now see how we could improve the There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I have improved the way Mount function works, I keep the new order but in case the filesystem mount fails it will undo the |
||
|
@@ -210,48 +209,9 @@ func (d *DockerOnTop) Mount(request *volume.MountRequest) (*volume.MountResponse | |
log.Errorf("Failed to mount overlay for volume %s: %v", request.Name, err) | ||
return nil, internalError("failed to mount overlay", err) | ||
} | ||
|
||
log.Debugf("Mounted volume %s at %s", request.Name, mountpoint) | ||
} else if err == nil { | ||
log.Debugf("Volume %s is already mounted for some other container. Indicating success without remounting", | ||
request.Name) | ||
} else { | ||
log.Errorf("Failed to list the activemounts directory: %v", err) | ||
return nil, internalError("failed to list activemounts/", err) | ||
} | ||
|
||
activemountFilePath := d.activemountsdir(request.Name) + request.ID | ||
f, err := os.Create(activemountFilePath) | ||
if err == nil { | ||
// We don't care about the file's contents | ||
_ = f.Close() | ||
} else { | ||
if os.IsExist(err) { | ||
// Super weird. I can't imagine why this would happen. | ||
log.Warningf("Active mount %s already exists (but it shouldn't...)", activemountFilePath) | ||
} else { | ||
// A really bad situation! | ||
// We successfully mounted (`syscall.Mount`) the volume but failed to put information about the container | ||
// using the volume. In the worst case (if we just created the volume) the following happens: | ||
// Using the plugin, it is now impossible to unmount the volume (this container is not created, so there's | ||
// no one to trigger `.Unmount()`) and impossible to remove (the directory mountpoint/ is a mountpoint, so | ||
// attempting to remove it will fail with `syscall.EBUSY`). | ||
// It is possible to mount the volume again: a new overlay will be mounted, shadowing the previous one. | ||
// The new overlay will be possible to unmount but, as the old overlay remains, the Unmount method won't | ||
// succeed because the attempt to remove mountpoint/ will result in `syscall.EBUSY`. | ||
// | ||
// Thus, a human interaction is required. | ||
// | ||
// (if it's not us who actually mounted the overlay, then the situation isn't too bad: no new container is | ||
// started, the error is reported to the end user). | ||
log.Criticalf("Failed to create active mount file: %v. If no other container was currently "+ | ||
"using the volume, this volume's state is now invalid. A human interaction or a reboot is required", | ||
err) | ||
return nil, fmt.Errorf("docker-on-top internal error: failed to create an active mount file: %w. "+ | ||
"The volume is now locked. Make sure that no other container is using the volume, then run "+ | ||
"`unmount %s` to unlock it. Human interaction is required. Please, report this bug", | ||
err, mountpoint) | ||
} | ||
log.Debugf("Volume %s already mounted at %s", request.Name, mountpoint) | ||
} | ||
|
||
return &response, nil | ||
|
@@ -273,40 +233,20 @@ func (d *DockerOnTop) Unmount(request *volume.UnmountRequest) error { | |
} | ||
defer activemountsdir.Close() // There's nothing I could do about the error if it occurs | ||
|
||
dirEntries, readDirErr := activemountsdir.ReadDir(2) // Check if there is any _other_ container using the volume | ||
if len(dirEntries) == 1 || errors.Is(readDirErr, io.EOF) { | ||
// If just one entry or directory is empty, unmount overlay and clean up | ||
|
||
unmount, err := d.Deactivate(request, activemountsdir) | ||
if err != nil { | ||
return internalError("failed to deactivate the active mount:", err) | ||
} else if unmount { | ||
err = syscall.Unmount(d.mountpointdir(request.Name), 0) | ||
if err != nil { | ||
log.Errorf("Failed to unmount %s: %v", d.mountpointdir(request.Name), err) | ||
return err | ||
} | ||
|
||
err = d.volumeTreePostUnmount(request.Name) | ||
// Don't return yet. The above error will be returned later | ||
} else if readDirErr == nil { | ||
log.Debugf("Volume %s is still mounted in some other container. Indicating success without unmounting", | ||
request.Name) | ||
} else { | ||
log.Errorf("Failed to list the activemounts directory: %v", err) | ||
return internalError("failed to list activemounts/", err) | ||
} | ||
|
||
activemountFilePath := d.activemountsdir(request.Name) + request.ID | ||
err2 := os.Remove(activemountFilePath) | ||
if os.IsNotExist(err2) { | ||
log.Warningf("Failed to remove %s because it does not exist (but it should...)", activemountFilePath) | ||
} else if err2 != nil { | ||
// Another pretty bad situation. Even though we are no longer using the volume, it is seemingly in use by us | ||
// because we failed to remove the file corresponding to this container. | ||
log.Criticalf("Failed to remove the active mount file: %v. The volume is now considered used by a container "+ | ||
"that no longer exists", err) | ||
// The user most likely won't see this error message due to daemon not showing unmount errors to the | ||
// `docker run` clients :(( | ||
return fmt.Errorf("docker-on-top internal error: failed to remove the active mount file: %w. The volume is "+ | ||
"now considered used by a container that no longer exists. Human interaction is required: remove the file "+ | ||
"manually to fix the problem", err) | ||
log.Debugf("Unmounted volume %s", request.Name) | ||
} else { | ||
log.Debugf("Volume %s is still mounted. Indicating success without unmounting", request.Name) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I don't agree with the error message change here. Here, you're seemingly saying "still mounted so I'm not unmounting it", which doesn't make sense. That's because of the ambiguity between "mounted" as in "a container is running where this volume is used" and "mounted" as in "the underlying overlay filesystem is present". IMO, the previous error message was clearer: log.Debugf("Volume %s is still mounted in some other container. Indicating success without unmounting", request.Name) Maybe could be phrased even better, e.g., There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I like the one you proposed, it's more clear of what's happening: |
||
} | ||
|
||
// Report an error during cleanup, if any | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You probably meant to call it
activeMount
(see point 2 of the next note, on exporting entities in go).(note that unlike the structure, its field should be exported: otherwise
json.Marshal
/.Unmarshal
won't be able to access it, as they are not part of this package)There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It's my first real program in Go, I had no idea about that, I simply copied how
VolumeInfo
was defined. I have fixed it.e55a292