diff --git a/nix/ebs-volume.nix b/nix/ebs-volume.nix index f03a0df7c..d2b12ef2d 100644 --- a/nix/ebs-volume.nix +++ b/nix/ebs-volume.nix @@ -36,11 +36,32 @@ with pkgs.lib; size = mkOption { example = 100; type = types.int; - description = "Volume size (in gigabytes)."; + description = '' + Volume size (in gigabytes). This may be left unset if you are + creating the volume from a snapshot, in which case the size of + the volume will be equal to the size of the snapshot. + However, you can set a size larger than the snapshot, allowing + the volume to be larger than the snapshot from which it is + created. + ''; + }; + + snapshot = mkOption { + default = ""; + example = "snap-1cbda474"; + type = types.str; + description = '' + The snapshot ID from which this volume will be created. If + not specified, an empty volume is created. Changing the + snapshot ID has no effect if the volume already exists. + ''; }; }; - config._type = "ebs-volume"; + config = { + _type = "ebs-volume"; + size = mkIf (config.snapshot != "") (mkDefault 0); + }; } diff --git a/nixops/resources/ebs_volume.py b/nixops/resources/ebs_volume.py index 0299533e0..9ad907a13 100644 --- a/nixops/resources/ebs_volume.py +++ b/nixops/resources/ebs_volume.py @@ -22,7 +22,8 @@ def __init__(self, xml): self.region = xml.find("attrs/attr[@name='region']/string").get("value") self.zone = xml.find("attrs/attr[@name='zone']/string").get("value") self.access_key_id = xml.find("attrs/attr[@name='accessKeyId']/string").get("value") - self.size = xml.find("attrs/attr[@name='size']/int").get("value") + self.size = int(xml.find("attrs/attr[@name='size']/int").get("value")) + self.snapshot = xml.find("attrs/attr[@name='snapshot']/string").get("value") def show_type(self): return "{0} [{1}]".format(self.get_type(), self.region) @@ -36,7 +37,7 @@ class EBSVolumeState(nixops.resources.ResourceState): region = nixops.util.attr_property("ec2.region", None) zone = nixops.util.attr_property("ec2.zone", None) volume_id = nixops.util.attr_property("ec2.volumeId", None) - size = nixops.util.attr_property("ec2.size", None) + size = nixops.util.attr_property("ec2.size", None, int) @classmethod @@ -74,15 +75,24 @@ def create(self, defn, check, allow_reboot, allow_recreate): if self.state == self.UP and (self.region != defn.region or self.zone != defn.zone): raise Exception("changing the region or availability zone of an EBS volume is not supported") - if self.state == self.UP and (self.size != defn.size): + if self.state == self.UP and (defn.size != 0 and self.size != defn.size): raise Exception("changing the size an EBS volume is currently not supported") if self.state != self.UP: self.connect(defn.region) - self.log("creating EBS volume of {0} GiB...".format(defn.size)) - volume = self._conn.create_volume(size=defn.size, zone=defn.zone) + if defn.size == 0 and defn.snapshot != "": + snapshots = self._conn.get_all_snapshots(snapshot_ids=[defn.snapshot]) + assert len(snapshots) == 1 + defn.size = snapshots[0].volume_size + + if defn.snapshot: + self.log("creating EBS volume of {0} GiB from snapshot ‘{1}’...".format(defn.size, defn.snapshot)) + else: + self.log("creating EBS volume of {0} GiB...".format(defn.size)) + + volume = self._conn.create_volume(zone=defn.zone, size=defn.size, snapshot=defn.snapshot) # FIXME: if we crash before the next step, we forget the # volume we just created. Doesn't seem to be anything we