resource/docker/volume/volume.go
// Copyright © 2016 Asteris, LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// +build !solaris
package volume
import (
"fmt"
"sort"
"strings"
"github.com/asteris-llc/converge/resource"
"github.com/asteris-llc/converge/resource/docker"
dc "github.com/fsouza/go-dockerclient"
"golang.org/x/net/context"
)
// State type for Volume
type State string
const (
// StatePresent indicates the volume should be present
StatePresent State = "present"
// StateAbsent indicates the volume should be absent
StateAbsent State = "absent"
)
// Volume is responsible for managing docker volumes
type Volume struct {
client docker.VolumeClient
// volume name
Name string `export:"name"`
// volume labels
Labels map[string]string `export:"labels"`
// driver the volume is configured to use
Driver string `export:"driver"`
// driver-specific options
Options map[string]string `export:"options"`
// volume state
State State `export:"state"`
// reflects whether or not the force option was configured
Force bool `export:"force"`
}
// Check system for docker volume
func (v *Volume) Check(context.Context, resource.Renderer) (resource.TaskStatus, error) {
status := resource.NewStatus()
vol, err := v.client.FindVolume(v.Name)
if err != nil {
status.Level = resource.StatusFatal
return status, err
}
status.AddDifference(v.Name, string(volumeState(vol)), string(v.State), "")
if v.State == StatePresent && vol != nil && v.Force {
expectedLabels := mapToString(v.Labels)
actualLabels := mapToString(vol.Labels)
status.AddDifference("labels", actualLabels, expectedLabels, "")
status.AddDifference("driver", vol.Driver, v.Driver, "local")
// we cannot detect difference in Options because the Docker API does not
// return that information:
// https://docs.docker.com/engine/reference/api/docker_remote_api_v1.24/#/inspect-a-volume
}
status.RaiseLevelForDiffs()
return status, nil
}
// Apply ensures the volume matches the desired state
func (v *Volume) Apply(context.Context) (resource.TaskStatus, error) {
status := resource.NewStatus()
var (
vol *dc.Volume
err error
)
vol, err = v.client.FindVolume(v.Name)
if err != nil {
status.Level = resource.StatusFatal
return status, err
}
if v.State == StatePresent {
if vol != nil {
if !v.Force {
return status, nil
}
err = v.client.RemoveVolume(v.Name)
if err != nil {
status.Level = resource.StatusFatal
return status, err
}
status.AddMessage(fmt.Sprintf("removed volume %s", v.Name))
}
opts := dc.CreateVolumeOptions{
Name: v.Name,
Driver: v.Driver,
DriverOpts: v.Options,
Labels: v.Labels,
}
vol, err = v.client.CreateVolume(opts)
if err != nil {
status.Level = resource.StatusFatal
return status, err
}
status.AddMessage(fmt.Sprintf("created volume %s", v.Name))
status.RaiseLevel(resource.StatusWillChange)
} else {
if vol != nil {
err = v.client.RemoveVolume(v.Name)
if err != nil {
status.Level = resource.StatusFatal
return status, err
}
status.AddMessage(fmt.Sprintf("removed volume %s", v.Name))
status.RaiseLevel(resource.StatusWillChange)
}
}
status.AddDifference(v.Name, string(volumeState(vol)), string(v.State), "")
return status, nil
}
// SetClient injects a docker api client
func (v *Volume) SetClient(client docker.VolumeClient) {
v.client = client
}
func volumeState(vol *dc.Volume) State {
if vol != nil {
return StatePresent
}
return StateAbsent
}
func mapToString(m map[string]string) string {
if len(m) == 0 {
return ""
}
strs := make([]string, len(m))
i := 0
for k, v := range m {
strs[i] = fmt.Sprintf("%s=%s", k, v)
i++
}
sort.Strings(strs)
return strings.Join(strs, ", ")
}