hyperkit 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/.gitignore +13 -0
- data/.rspec +2 -0
- data/.travis.yml +4 -0
- data/.yardopts +1 -0
- data/CODE_OF_CONDUCT.md +49 -0
- data/Gemfile +23 -0
- data/Guardfile +43 -0
- data/LICENSE.txt +47 -0
- data/README.md +341 -0
- data/Rakefile +6 -0
- data/Vagrantfile +123 -0
- data/bin/console +14 -0
- data/bin/setup +8 -0
- data/hyperkit.gemspec +33 -0
- data/lib/hyperkit.rb +58 -0
- data/lib/hyperkit/client.rb +82 -0
- data/lib/hyperkit/client/certificates.rb +102 -0
- data/lib/hyperkit/client/containers.rb +1100 -0
- data/lib/hyperkit/client/images.rb +672 -0
- data/lib/hyperkit/client/networks.rb +47 -0
- data/lib/hyperkit/client/operations.rb +123 -0
- data/lib/hyperkit/client/profiles.rb +59 -0
- data/lib/hyperkit/configurable.rb +110 -0
- data/lib/hyperkit/connection.rb +196 -0
- data/lib/hyperkit/default.rb +140 -0
- data/lib/hyperkit/error.rb +267 -0
- data/lib/hyperkit/middleware/follow_redirects.rb +131 -0
- data/lib/hyperkit/response/raise_error.rb +47 -0
- data/lib/hyperkit/version.rb +3 -0
- metadata +116 -0
data/Rakefile
ADDED
data/Vagrantfile
ADDED
@@ -0,0 +1,123 @@
|
|
1
|
+
# -*- mode: ruby -*-
|
2
|
+
# vi: set ft=ruby :
|
3
|
+
|
4
|
+
# All Vagrant configuration is done below. The "2" in Vagrant.configure
|
5
|
+
# configures the configuration version (we support older styles for
|
6
|
+
# backwards compatibility). Please don't change it unless you know what
|
7
|
+
# you're doing.
|
8
|
+
Vagrant.configure(2) do |config|
|
9
|
+
# The most common configuration options are documented and commented below.
|
10
|
+
# For a complete reference, please see the online documentation at
|
11
|
+
# https://docs.vagrantup.com.
|
12
|
+
|
13
|
+
# Every Vagrant development environment requires a box. You can search for
|
14
|
+
# boxes at https://atlas.hashicorp.com/search.
|
15
|
+
config.vm.box = "ubuntu/trusty64"
|
16
|
+
|
17
|
+
config.vm.define "lxd1" do |lxd1|
|
18
|
+
lxd1.vm.box = "ubuntu/trusty64"
|
19
|
+
lxd1.vm.network "forwarded_port", guest: 8443, host: 8443
|
20
|
+
lxd1.vm.network "private_network", ip: "192.168.103.101"
|
21
|
+
|
22
|
+
lxd1.vm.provider "virtualbox" do |vb|
|
23
|
+
vb.memory = "2048"
|
24
|
+
disk_file = File.expand_path("../.vagrant/disks/lxd1-disk1.vdi", __FILE__)
|
25
|
+
|
26
|
+
if ! File.exist?(disk_file)
|
27
|
+
vb.customize ['createhd', '--filename', disk_file, '--size', 1024 * 1024]
|
28
|
+
vb.customize ['storageattach', :id, '--storagectl', 'SATAController', '--port', 1, '--device', 0, '--type', 'hdd', '--medium', disk_file]
|
29
|
+
end
|
30
|
+
|
31
|
+
end
|
32
|
+
|
33
|
+
lxd1.vm.provision "ansible" do |ansible|
|
34
|
+
ansible.playbook = File.expand_path("../spec/vagrant/lxd-playbook.yml", __FILE__)
|
35
|
+
ansible.extra_vars = {
|
36
|
+
lxd_port: 8443,
|
37
|
+
lxd_trust_password: "lxd1"
|
38
|
+
}
|
39
|
+
end
|
40
|
+
|
41
|
+
end
|
42
|
+
|
43
|
+
config.vm.define "lxd2" do |lxd2|
|
44
|
+
lxd2.vm.box = "ubuntu/trusty64"
|
45
|
+
lxd2.vm.network "forwarded_port", guest: 8444, host: 8444
|
46
|
+
lxd2.vm.network "private_network", ip: "192.168.103.102"
|
47
|
+
|
48
|
+
lxd2.vm.provider "virtualbox" do |vb|
|
49
|
+
vb.memory = "256"
|
50
|
+
|
51
|
+
disk_file = File.expand_path("../.vagrant/disks/lxd2-disk1.vdi", __FILE__)
|
52
|
+
|
53
|
+
if ! File.exist?(disk_file)
|
54
|
+
vb.customize ['createhd', '--filename', disk_file, '--size', 1024 * 1024]
|
55
|
+
vb.customize ['storageattach', :id, '--storagectl', 'SATAController', '--port', 1, '--device', 0, '--type', 'hdd', '--medium', disk_file]
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
lxd2.vm.provision "ansible" do |ansible|
|
60
|
+
ansible.playbook = File.expand_path("../spec/vagrant/lxd-playbook.yml", __FILE__)
|
61
|
+
ansible.extra_vars = {
|
62
|
+
lxd_port: 8443,
|
63
|
+
lxd_trust_password: "lxd2"
|
64
|
+
}
|
65
|
+
end
|
66
|
+
|
67
|
+
end
|
68
|
+
|
69
|
+
# Disable automatic box update checking. If you disable this, then
|
70
|
+
# boxes will only be checked for updates when the user runs
|
71
|
+
# `vagrant box outdated`. This is not recommended.
|
72
|
+
# config.vm.box_check_update = false
|
73
|
+
|
74
|
+
# Create a forwarded port mapping which allows access to a specific port
|
75
|
+
# within the machine from a port on the host machine. In the example below,
|
76
|
+
# accessing "localhost:8080" will access port 80 on the guest machine.
|
77
|
+
# config.vm.network "forwarded_port", guest: 80, host: 8080
|
78
|
+
|
79
|
+
# Create a private network, which allows host-only access to the machine
|
80
|
+
# using a specific IP.
|
81
|
+
# config.vm.network "private_network", ip: "192.168.33.10"
|
82
|
+
|
83
|
+
# Create a public network, which generally matched to bridged network.
|
84
|
+
# Bridged networks make the machine appear as another physical device on
|
85
|
+
# your network.
|
86
|
+
# config.vm.network "public_network"
|
87
|
+
|
88
|
+
# Share an additional folder to the guest VM. The first argument is
|
89
|
+
# the path on the host to the actual folder. The second argument is
|
90
|
+
# the path on the guest to mount the folder. And the optional third
|
91
|
+
# argument is a set of non-required options.
|
92
|
+
# config.vm.synced_folder "../data", "/vagrant_data"
|
93
|
+
|
94
|
+
# Provider-specific configuration so you can fine-tune various
|
95
|
+
# backing providers for Vagrant. These expose provider-specific options.
|
96
|
+
# Example for VirtualBox:
|
97
|
+
#
|
98
|
+
# config.vm.provider "virtualbox" do |vb|
|
99
|
+
# # Display the VirtualBox GUI when booting the machine
|
100
|
+
# vb.gui = true
|
101
|
+
#
|
102
|
+
# # Customize the amount of memory on the VM:
|
103
|
+
# vb.memory = "1024"
|
104
|
+
# end
|
105
|
+
#
|
106
|
+
# View the documentation for the provider you are using for more
|
107
|
+
# information on available options.
|
108
|
+
|
109
|
+
# Define a Vagrant Push strategy for pushing to Atlas. Other push strategies
|
110
|
+
# such as FTP and Heroku are also available. See the documentation at
|
111
|
+
# https://docs.vagrantup.com/v2/push/atlas.html for more information.
|
112
|
+
# config.push.define "atlas" do |push|
|
113
|
+
# push.app = "YOUR_ATLAS_USERNAME/YOUR_APPLICATION_NAME"
|
114
|
+
# end
|
115
|
+
|
116
|
+
# Enable provisioning with a shell script. Additional provisioners such as
|
117
|
+
# Puppet, Chef, Ansible, Salt, and Docker are also available. Please see the
|
118
|
+
# documentation for more information about their specific syntax and use.
|
119
|
+
# config.vm.provision "shell", inline: <<-SHELL
|
120
|
+
# sudo apt-get update
|
121
|
+
# sudo apt-get install -y apache2
|
122
|
+
# SHELL
|
123
|
+
end
|
data/bin/console
ADDED
@@ -0,0 +1,14 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require "bundler/setup"
|
4
|
+
require "hyperkit"
|
5
|
+
|
6
|
+
# You can add fixtures and/or initialization code here to make experimenting
|
7
|
+
# with your gem easier. You can also use a different console, if you like.
|
8
|
+
|
9
|
+
# (If you use this, don't forget to add pry to your Gemfile!)
|
10
|
+
# require "pry"
|
11
|
+
# Pry.start
|
12
|
+
|
13
|
+
require "irb"
|
14
|
+
IRB.start
|
data/bin/setup
ADDED
data/hyperkit.gemspec
ADDED
@@ -0,0 +1,33 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
lib = File.expand_path('../lib', __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
require 'hyperkit/version'
|
5
|
+
|
6
|
+
Gem::Specification.new do |spec|
|
7
|
+
spec.name = "hyperkit"
|
8
|
+
spec.version = Hyperkit::VERSION
|
9
|
+
spec.authors = ["Jeff Shantz"]
|
10
|
+
spec.email = ["hyperkit@jeffshantz.com"]
|
11
|
+
|
12
|
+
spec.summary = %q{Hyperkit is a flat API wrapper for LXD, the next-generation hypervisor}
|
13
|
+
spec.homepage = "https://github.com/jeffshantz/hyperkit"
|
14
|
+
spec.license = "MIT"
|
15
|
+
|
16
|
+
# Prevent pushing this gem to RubyGems.org by setting 'allowed_push_host', or
|
17
|
+
# delete this section to allow pushing this gem to any host.
|
18
|
+
#if spec.respond_to?(:metadata)
|
19
|
+
# spec.metadata['allowed_push_host'] = "TODO: Set to 'http://mygemserver.com'"
|
20
|
+
#else
|
21
|
+
# raise "RubyGems 2.0 or newer is required to protect against public gem pushes."
|
22
|
+
#end
|
23
|
+
|
24
|
+
spec.files = `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
|
25
|
+
spec.bindir = "exe"
|
26
|
+
spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
|
27
|
+
spec.require_paths = ["lib"]
|
28
|
+
|
29
|
+
spec.add_dependency "activesupport", "~> 4.2.6"
|
30
|
+
spec.add_dependency "sawyer"
|
31
|
+
spec.add_development_dependency "bundler", "~> 1.11"
|
32
|
+
|
33
|
+
end
|
data/lib/hyperkit.rb
ADDED
@@ -0,0 +1,58 @@
|
|
1
|
+
################################################################################
|
2
|
+
# #
|
3
|
+
# Based on Octokit #
|
4
|
+
# #
|
5
|
+
# Original Octokit license #
|
6
|
+
# ---------------------------------------------------------------------------- #
|
7
|
+
# Copyright (c) 2009-2016 Wynn Netherland, Adam Stacoviak, Erik Michaels-Ober #
|
8
|
+
# #
|
9
|
+
# Permission is hereby granted, free of charge, to any person obtaining a #
|
10
|
+
# copy of this software and associated documentation files (the "Software"), #
|
11
|
+
# to deal in the Software without restriction, including without limitation #
|
12
|
+
# the rights to use, copy, modify, merge, publish, distribute, sublicense, #
|
13
|
+
# and/or sell copies of the Software, and to permit persons to whom the #
|
14
|
+
# Software is furnished to do so, subject to the following conditions: #
|
15
|
+
# #
|
16
|
+
# The above copyright notice and this permission notice shall be included #
|
17
|
+
# in all copies or substantial portions of the Software. #
|
18
|
+
# ---------------------------------------------------------------------------- #
|
19
|
+
# #
|
20
|
+
################################################################################
|
21
|
+
|
22
|
+
require 'hyperkit/version'
|
23
|
+
require 'hyperkit/client'
|
24
|
+
require 'hyperkit/default'
|
25
|
+
|
26
|
+
# Ruby toolkit for the LXD API.
|
27
|
+
# LXD - the next-generation container hypervisor for Linux
|
28
|
+
module Hyperkit
|
29
|
+
|
30
|
+
class << self
|
31
|
+
include Hyperkit::Configurable
|
32
|
+
|
33
|
+
# API client based on configured options {Configurable}
|
34
|
+
#
|
35
|
+
# @return [Hyperkit::Client] API wrapper
|
36
|
+
def client
|
37
|
+
return @client if defined?(@client) && @client.same_options?(options)
|
38
|
+
@client = Hyperkit::Client.new(options)
|
39
|
+
end
|
40
|
+
|
41
|
+
private
|
42
|
+
|
43
|
+
def respond_to_missing?(method_name, include_private=false)
|
44
|
+
client.respond_to?(method_name, include_private)
|
45
|
+
end
|
46
|
+
|
47
|
+
def method_missing(method_name, *args, &block)
|
48
|
+
if client.respond_to?(method_name)
|
49
|
+
return client.send(method_name, *args, &block)
|
50
|
+
end
|
51
|
+
|
52
|
+
super
|
53
|
+
end
|
54
|
+
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
Hyperkit.setup
|
@@ -0,0 +1,82 @@
|
|
1
|
+
################################################################################
|
2
|
+
# #
|
3
|
+
# Modeled on Octokit::Client #
|
4
|
+
# #
|
5
|
+
# Original Octokit license #
|
6
|
+
# ---------------------------------------------------------------------------- #
|
7
|
+
# Copyright (c) 2009-2016 Wynn Netherland, Adam Stacoviak, Erik Michaels-Ober #
|
8
|
+
# #
|
9
|
+
# Permission is hereby granted, free of charge, to any person obtaining a #
|
10
|
+
# copy of this software and associated documentation files (the "Software"), #
|
11
|
+
# to deal in the Software without restriction, including without limitation #
|
12
|
+
# the rights to use, copy, modify, merge, publish, distribute, sublicense, #
|
13
|
+
# and/or sell copies of the Software, and to permit persons to whom the #
|
14
|
+
# Software is furnished to do so, subject to the following conditions: #
|
15
|
+
# #
|
16
|
+
# The above copyright notice and this permission notice shall be included #
|
17
|
+
# in all copies or substantial portions of the Software. #
|
18
|
+
# ---------------------------------------------------------------------------- #
|
19
|
+
# #
|
20
|
+
################################################################################
|
21
|
+
|
22
|
+
|
23
|
+
require 'hyperkit/configurable'
|
24
|
+
require 'hyperkit/connection'
|
25
|
+
require 'hyperkit/client/certificates'
|
26
|
+
require 'hyperkit/client/containers'
|
27
|
+
require 'hyperkit/client/images'
|
28
|
+
require 'hyperkit/client/networks'
|
29
|
+
require 'hyperkit/client/operations'
|
30
|
+
require 'hyperkit/client/profiles'
|
31
|
+
|
32
|
+
module Hyperkit
|
33
|
+
|
34
|
+
# LXD client
|
35
|
+
# @see Hyperkit::Client::Certificates
|
36
|
+
# @see Hyperkit::Client::Containers
|
37
|
+
# @see Hyperkit::Client::Images
|
38
|
+
# @see Hyperkit::Client::Networks
|
39
|
+
# @see Hyperkit::Client::Operations
|
40
|
+
# @see Hyperkit::Client::Profiles
|
41
|
+
class Client
|
42
|
+
|
43
|
+
include Hyperkit::Configurable
|
44
|
+
include Hyperkit::Connection
|
45
|
+
include Hyperkit::Client::Certificates
|
46
|
+
include Hyperkit::Client::Containers
|
47
|
+
include Hyperkit::Client::Images
|
48
|
+
include Hyperkit::Client::Networks
|
49
|
+
include Hyperkit::Client::Operations
|
50
|
+
include Hyperkit::Client::Profiles
|
51
|
+
|
52
|
+
# Initialize a new Hyperkit client
|
53
|
+
#
|
54
|
+
# @param options [Hash] Any of the attributes listed in {Hyperkit::Configurable}
|
55
|
+
#
|
56
|
+
# @example Use a client with default options
|
57
|
+
# client = Hyperkit.client
|
58
|
+
#
|
59
|
+
# @example Create a new client and override the <code>api_endpoint</code>
|
60
|
+
# client = Hyperkit::Client.new(api_endpoint: "https://images.linuxcontainers.org:8443")
|
61
|
+
def initialize(options = {})
|
62
|
+
|
63
|
+
# Use options passed in, but fall back to module defaults
|
64
|
+
Hyperkit::Configurable.keys.each do |key|
|
65
|
+
|
66
|
+
# Allow user to explicitly override default values by passing 'key: nil'
|
67
|
+
next if options.has_key?(key) && options[key].nil?
|
68
|
+
|
69
|
+
if options.has_key?(key)
|
70
|
+
value = options[key]
|
71
|
+
else
|
72
|
+
value = Hyperkit.instance_variable_get(:"@#{key}")
|
73
|
+
end
|
74
|
+
|
75
|
+
instance_variable_set(:"@#{key}", value)
|
76
|
+
end
|
77
|
+
|
78
|
+
end
|
79
|
+
|
80
|
+
end
|
81
|
+
|
82
|
+
end
|
@@ -0,0 +1,102 @@
|
|
1
|
+
require 'active_support/core_ext/hash/slice'
|
2
|
+
require 'base64'
|
3
|
+
|
4
|
+
module Hyperkit
|
5
|
+
|
6
|
+
class Client
|
7
|
+
|
8
|
+
# Methods for the certificates API
|
9
|
+
#
|
10
|
+
# @see Hyperkit::Client
|
11
|
+
# @see https://github.com/lxc/lxd/blob/master/specs/rest-api.md
|
12
|
+
module Certificates
|
13
|
+
|
14
|
+
# List of trusted certificates on the server
|
15
|
+
#
|
16
|
+
# @return [Array<String>] An array of certificate fingerprints
|
17
|
+
#
|
18
|
+
# @example Get list of containers
|
19
|
+
# Hyperkit.certificates #=> [
|
20
|
+
# "c782c0f3530a04a5b2b78fc5292b7500aef1299370288b5eeb0450a6613a2c82",
|
21
|
+
# "b7720e1eb839056158cf65d182865491a0403f766983b95f5098d05911bbff89"
|
22
|
+
# ]
|
23
|
+
def certificates
|
24
|
+
response = get(certificates_path)
|
25
|
+
response.metadata.map { |path| path.split('/').last }
|
26
|
+
end
|
27
|
+
|
28
|
+
# Add a new trusted certificate to the server
|
29
|
+
#
|
30
|
+
# @param cert [String] Certificate contents in PEM format
|
31
|
+
# @param options [Hash] Additional data to be passed
|
32
|
+
# @option options [String] :name Optional name for the certificate. If not specified, the host in the TLS header for the request is used.
|
33
|
+
# @option options [String] :password The trust password for that server. Only required if untrusted.
|
34
|
+
# @return [Sawyer::Resource]
|
35
|
+
#
|
36
|
+
# @example Add trusted certificate
|
37
|
+
# Hyperkit.create_certificate(File.read("/tmp/cert.pem"))
|
38
|
+
#
|
39
|
+
# @example Add trusted certificate via untrusted client connection
|
40
|
+
# Hyperkit.create_certificate(File.read("/tmp/cert.pem"), password: "server-trust-password")
|
41
|
+
def create_certificate(cert, options={})
|
42
|
+
options = options.slice(:name, :password)
|
43
|
+
options = options.merge(type: "client", certificate: Base64.strict_encode64(OpenSSL::X509::Certificate.new(cert).to_der))
|
44
|
+
post(certificates_path, options).metadata
|
45
|
+
end
|
46
|
+
|
47
|
+
# Retrieve a trusted certificate from the server
|
48
|
+
#
|
49
|
+
# @param fingerprint [String] Fingerprint of the certificate to retrieve. Can be a prefix, as long as it is unambigous
|
50
|
+
# @return [Sawyer::Resource] Certificate information
|
51
|
+
#
|
52
|
+
# @example Retrieve a certificate
|
53
|
+
# Hyperkit.certificate("c782c0f3530a04a5b2b78fc5292b7500aef1299370288b5eeb0450a6613a2c82") #=> {
|
54
|
+
# :certificate => "-----BEGIN CERTIFICATE-----\nMIIEW...ceyg04=\n-----END CERTIFICATE-----\n",
|
55
|
+
# :fingerprint => "c782c0f3530a04a5b2b78fc5292b7500aef1299370288b5eeb0450a6613a2c82",
|
56
|
+
# :type => "client"
|
57
|
+
# }
|
58
|
+
#
|
59
|
+
# @example Retrieve a certificate by specifying a prefix of its fingerprint
|
60
|
+
# Hyperkit.certificate("c7") #=> {
|
61
|
+
# :certificate => "-----BEGIN CERTIFICATE-----\nMIIEW...ceyg04=\n-----END CERTIFICATE-----\n",
|
62
|
+
# :fingerprint => "c782c0f3530a04a5b2b78fc5292b7500aef1299370288b5eeb0450a6613a2c82",
|
63
|
+
# :type => "client"
|
64
|
+
# }
|
65
|
+
#
|
66
|
+
# @todo Write tests for the prefix
|
67
|
+
def certificate(fingerprint)
|
68
|
+
get(certificate_path(fingerprint)).metadata
|
69
|
+
end
|
70
|
+
|
71
|
+
# Delete a trusted certificate from the server
|
72
|
+
#
|
73
|
+
# @param fingerprint [String] Fingerprint of the certificate to retrieve. Can be a prefix, as long as it is unambigous
|
74
|
+
# @return [Sawyer::Resource]
|
75
|
+
#
|
76
|
+
# @example Delete a certificate
|
77
|
+
# Hyperkit.delete_certificate("c782c0f3530a04a5b2b78fc5292b7500aef1299370288b5eeb0450a6613a2c82")
|
78
|
+
#
|
79
|
+
# @example Delete a certificate by specifying a prefix of its fingerprint
|
80
|
+
# Hyperkit.delete_certificate("c7")
|
81
|
+
#
|
82
|
+
# @todo Write tests for the prefix
|
83
|
+
def delete_certificate(fingerprint)
|
84
|
+
delete(certificate_path(fingerprint)).metadata
|
85
|
+
end
|
86
|
+
|
87
|
+
private
|
88
|
+
|
89
|
+
def certificate_path(fingerprint)
|
90
|
+
File.join(certificates_path, fingerprint)
|
91
|
+
end
|
92
|
+
|
93
|
+
def certificates_path
|
94
|
+
"/1.0/certificates"
|
95
|
+
end
|
96
|
+
|
97
|
+
end
|
98
|
+
|
99
|
+
end
|
100
|
+
|
101
|
+
end
|
102
|
+
|
@@ -0,0 +1,1100 @@
|
|
1
|
+
require 'active_support/core_ext/array/extract_options'
|
2
|
+
require 'shellwords'
|
3
|
+
|
4
|
+
module Hyperkit
|
5
|
+
|
6
|
+
class Client
|
7
|
+
|
8
|
+
# Methods for the containers API
|
9
|
+
#
|
10
|
+
# @see Hyperkit::Client
|
11
|
+
# @see https://github.com/lxc/lxd/blob/master/specs/rest-api.md
|
12
|
+
module Containers
|
13
|
+
|
14
|
+
# @!group Retrieval
|
15
|
+
|
16
|
+
# List of containers on the server (public or private)
|
17
|
+
#
|
18
|
+
# @return [Array<String>] An array of container names
|
19
|
+
#
|
20
|
+
# @example Get list of containers
|
21
|
+
# Hyperkit.containers #=> ["container1", "container2", "container3"]
|
22
|
+
def containers
|
23
|
+
response = get(containers_path)
|
24
|
+
response.metadata.map { |path| path.split('/').last }
|
25
|
+
end
|
26
|
+
|
27
|
+
# Get information on a container
|
28
|
+
#
|
29
|
+
# @param name [String] Container name
|
30
|
+
# @return [Sawyer::Resource] Container information
|
31
|
+
#
|
32
|
+
# @example Get information about a container
|
33
|
+
# Hyperkit.container("test-container") #=> {
|
34
|
+
# :architecture => "x86_64",
|
35
|
+
# :config => {
|
36
|
+
# :"volatile.base_image" => "097e75d6f7419d3a5e204d8125582f2d7bdd4ee4c35bd324513321c645f0c415",
|
37
|
+
# :"volatile.eth0.hwaddr" => "00:16:3e:24:5d:7a",
|
38
|
+
# :"volatile.eth0.name" => "eth0",
|
39
|
+
# :"volatile.last_state.idmap" =>
|
40
|
+
# "[{\"Isuid\":true,\"Isgid\":false,\"Hostid\":165536,\"Nsid\":0,\"Maprange\":65536},{\"Isuid\":false,\"Isgid\":true,\"Hostid\":165536,\"Nsid\":0,\"Maprange\":65536}]"
|
41
|
+
# },
|
42
|
+
# :created_at => 2016-03-18 20:55:26 UTC,
|
43
|
+
# :devices => {
|
44
|
+
# :root => {:path => "/", :type => "disk"}
|
45
|
+
# },
|
46
|
+
# :ephemeral => false,
|
47
|
+
# :expanded_config => {
|
48
|
+
# :"volatile.base_image" => "097e75d6f7419d3a5e204d8125582f2d7bdd4ee4c35bd324513321c645f0c415",
|
49
|
+
# :"volatile.eth0.hwaddr" => "00:16:3e:24:5d:7a",
|
50
|
+
# :"volatile.eth0.name" => "eth0",
|
51
|
+
# :"volatile.last_state.idmap" =>
|
52
|
+
# "[{\"Isuid\":true,\"Isgid\":false,\"Hostid\":165536,\"Nsid\":0,\"Maprange\":65536},{\"Isuid\":false,\"Isgid\":true,\"Hostid\":165536,\"Nsid\":0,\"Maprange\":65536}]"
|
53
|
+
# },
|
54
|
+
# :expanded_devices => {
|
55
|
+
# :eth0 => { :nictype => "bridged", :parent => "lxcbr0", :type => "nic"},
|
56
|
+
# :root => { :path => "/", :type => "disk"}
|
57
|
+
# },
|
58
|
+
# :name => "test-container",
|
59
|
+
# :profiles => ["default"],
|
60
|
+
# :stateful => false,
|
61
|
+
# :status => "Stopped",
|
62
|
+
# :status_code => 102
|
63
|
+
# }
|
64
|
+
def container(name)
|
65
|
+
get(container_path(name)).metadata
|
66
|
+
end
|
67
|
+
|
68
|
+
# @!endgroup
|
69
|
+
|
70
|
+
# @!group Creation
|
71
|
+
|
72
|
+
# Create a container from an image (local or remote). The container will
|
73
|
+
# be created in the <code>Stopped</code> state.
|
74
|
+
#
|
75
|
+
# @async This method is asynchronous. See {Hyperkit::Configurable#auto_sync} for more information.
|
76
|
+
#
|
77
|
+
# @param name [String] Container name
|
78
|
+
# @param options [Hash] Additional data to be passed
|
79
|
+
# @option options [String] :alias Alias of the source image. <b>Either <code>:alias</code>, <code>:fingerprint</code>, <code>:properties</code>, or <code>empty: true</code> must be specified</b>.
|
80
|
+
# @option options [String] :architecture Architecture of the container (e.g. <code>x86_64</code>). By default, this will be obtained from the image metadata
|
81
|
+
# @option options [String] :certificate PEM certificate to use to authenticate with the remote server. If not specified, and the source image is private, the target LXD server's certificate is used for authentication. <b>This option is valid only when transferring an image from a remote server using the <code>:server</code> option.</b>
|
82
|
+
# @option options [Hash] :config Container configuration
|
83
|
+
# @option options [Boolean] :ephemeral Whether to make the container ephemeral (i.e. delete it when it is stopped; default: <code>false</code>)
|
84
|
+
# @option options [Boolean] :empty Whether to make an empty container (i.e. not from an image). Specifying <code>true</code> will cause LXD to create a container with no rootfs. That is, /var/lib/lxd/<container-name> will simply be an empty directly. One can then create a rootfs directory within this directory and populate it manually. This is useful when migrating LXC containers to LXD.
|
85
|
+
# @option options [String] :fingerprint SHA-256 fingerprint of the source image. This can be a prefix of a fingerprint, as long as it is unambiguous. <b>Either <code>:alias</code>, <code>:fingerprint</code>, <code>:properties</code>, or <code>empty: true</code> must be specified</b>.
|
86
|
+
# @option options [Array] :profiles List of profiles to be applied to the container (default: <code>[]</code>)
|
87
|
+
# @option options [String] :properties Properties of the source image. <b>Either <code>:alias</code>, <code>:fingerprint</code>, <code>:properties</code>, or <code>empty: true</code> must be specified</b>.
|
88
|
+
# @option options [String] :protocol Protocol to use in transferring the image (<code>lxd</code> or <code>simplestreams</code>; defaults to <code>lxd</code>). <b>This option is valid only when transferring an image from a remote server using the <code>:server</code> option.</b>
|
89
|
+
# @option options [String] :secret Secret to use to retrieve the image. <b>This option is valid only when transferring an image from a remote server using the <code>:server</code> option.</b>
|
90
|
+
# @option options [String] :server URL of remote server from which to obtain image. By default, the image will be obtained from the client's <code>api_endpoint</code>.
|
91
|
+
# @option options [Boolean] :sync If <code>false</code>, returns an asynchronous operation that must be passed to {Hyperkit::Client::Operations#wait_for_operation}. If <code>true</code>, automatically waits and returns the result of the operation. Defaults to value of {Hyperkit::Configurable#auto_sync}.
|
92
|
+
# @return [Sawyer::Resource] Operation or result, depending value of <code>:sync</code> parameter and/or {Hyperkit::Client::auto_sync}
|
93
|
+
#
|
94
|
+
# @example Create container from image specified by alias
|
95
|
+
# Hyperkit.create_container("test-container", alias: "ubuntu/xenial/amd64")
|
96
|
+
#
|
97
|
+
# @example Create container from image specified by fingerprint
|
98
|
+
# Hyperkit.create_container("test-container",
|
99
|
+
# fingerprint: "097e75d6f7419d3a5e204d8125582f2d7bdd4ee4c35bd324513321c645f0c415")
|
100
|
+
#
|
101
|
+
# @example Create container from image specified by fingerprint prefix
|
102
|
+
# Hyperkit.create_container("test-container", fingerprint: "097")
|
103
|
+
#
|
104
|
+
# @example Create container based on most recent match of image properties
|
105
|
+
# Hyperkit.create_container("test-container",
|
106
|
+
# properties: { os: "ubuntu", release: "14.04", architecture: "x86_64" }
|
107
|
+
#
|
108
|
+
# @example Create an empty container
|
109
|
+
# Hyperkit.create_container("test-container", empty: true)
|
110
|
+
#
|
111
|
+
# @example Create container with custom configuration.
|
112
|
+
#
|
113
|
+
# # Set the MAC address of the container's eth0 device
|
114
|
+
# Hyperkit.create_container("test-container",
|
115
|
+
# alias: "ubuntu/xenial/amd64",
|
116
|
+
# config: {
|
117
|
+
# "volatile.eth0.hwaddr" => "aa:bb:cc:dd:ee:ff"
|
118
|
+
# }
|
119
|
+
# )
|
120
|
+
#
|
121
|
+
# @example Create container and apply profiles to it
|
122
|
+
# Hyperkit.create_container("test-container",
|
123
|
+
# alias: "ubuntu/xenial/amd64",
|
124
|
+
# profiles: ["migratable", "unconfined"]
|
125
|
+
# )
|
126
|
+
#
|
127
|
+
# @example Create container from a publicly-accessible remote image
|
128
|
+
# Hyperkit.create_container("test-container",
|
129
|
+
# server: "https://images.linuxcontainers.org:8443",
|
130
|
+
# alias: "ubuntu/xenial/amd64")
|
131
|
+
#
|
132
|
+
# @example Create container from a private remote image (authenticated by a secret)
|
133
|
+
# Hyperkit.create_container("test-container",
|
134
|
+
# server: "https://private.example.com:8443",
|
135
|
+
# alias: "ubuntu/xenial/amd64",
|
136
|
+
# secret: "shhhhh")
|
137
|
+
def create_container(name, options={})
|
138
|
+
|
139
|
+
source = container_source_attribute(options)
|
140
|
+
opts = options.except(:sync)
|
141
|
+
|
142
|
+
if ! opts[:empty] && source.empty?
|
143
|
+
raise Hyperkit::ImageIdentifierRequired.new("Specify source image by alias, fingerprint, or properties, or create an empty container with 'empty: true'")
|
144
|
+
end
|
145
|
+
|
146
|
+
if opts[:empty]
|
147
|
+
opts = empty_container_options(name, opts)
|
148
|
+
elsif options[:server]
|
149
|
+
opts = remote_image_container_options(name, source, opts)
|
150
|
+
else
|
151
|
+
opts = local_image_container_options(name, source, opts)
|
152
|
+
end
|
153
|
+
|
154
|
+
response = post(containers_path, opts).metadata
|
155
|
+
handle_async(response, options[:sync])
|
156
|
+
end
|
157
|
+
|
158
|
+
# Create a copy of an existing local container.
|
159
|
+
#
|
160
|
+
# @async This method is asynchronous. See {Hyperkit::Configurable#auto_sync} for more information.
|
161
|
+
#
|
162
|
+
# @param source_name [String] Source container name
|
163
|
+
# @param target_name [String] Target container name
|
164
|
+
# @param options [Hash] Additional data to be passed
|
165
|
+
# @option options [String] :architecture Architecture of the container (e.g. <code>x86_64</code>). By default, this will be obtained from the image metadata
|
166
|
+
# @option options [Hash] :config Container configuration
|
167
|
+
# @option options [Boolean] :ephemeral Whether to make the container ephemeral (i.e. delete it when it is stopped; default: <code>false</code>)
|
168
|
+
# @option options [Array] :profiles List of profiles to be applied to the container (default: <code>[]</code>)
|
169
|
+
# @option options [Boolean] :sync If <code>false</code>, returns an asynchronous operation that must be passed to {Hyperkit::Client::Operations#wait_for_operation}. If <code>true</code>, automatically waits and returns the result of the operation. Defaults to value of {Hyperkit::Configurable#auto_sync}.
|
170
|
+
# @return [Sawyer::Resource] Operation or result, depending value of <code>:sync</code> parameter and/or {Hyperkit::Client::auto_sync}
|
171
|
+
#
|
172
|
+
# @example Copy container
|
173
|
+
# Hyperkit.copy_container("existing", "new")
|
174
|
+
#
|
175
|
+
# @example Copy container and override its configuration.
|
176
|
+
#
|
177
|
+
# # Set the MAC address of the container's eth0 device
|
178
|
+
# Hyperkit.copy_container("existing", "new", config: {
|
179
|
+
# "volatile.eth0.hwaddr" => "aa:bb:cc:dd:ee:ff"
|
180
|
+
# }
|
181
|
+
# )
|
182
|
+
#
|
183
|
+
# @example Copy container and apply profiles to it
|
184
|
+
# Hyperkit.copy_container("existing", "new", profiles: ["migratable", "unconfined"])
|
185
|
+
#
|
186
|
+
# @example Create container from a publicly-accessible remote image
|
187
|
+
# Hyperkit.create_container("test-container",
|
188
|
+
# server: "https://images.linuxcontainers.org:8443",
|
189
|
+
# alias: "ubuntu/xenial/amd64")
|
190
|
+
def copy_container(source_name, target_name, options={})
|
191
|
+
|
192
|
+
opts = {
|
193
|
+
source: {
|
194
|
+
type: "copy",
|
195
|
+
source: source_name
|
196
|
+
}
|
197
|
+
}.merge(extract_container_options(target_name, options))
|
198
|
+
|
199
|
+
response = post(containers_path, opts).metadata
|
200
|
+
handle_async(response, options[:sync])
|
201
|
+
|
202
|
+
end
|
203
|
+
|
204
|
+
# @!endgroup
|
205
|
+
|
206
|
+
# @!group Editing
|
207
|
+
|
208
|
+
# Update the configuration of a container.
|
209
|
+
#
|
210
|
+
# Configuration is overwritten, not merged. Accordingly, clients should
|
211
|
+
# first call container to obtain the current configuration of a
|
212
|
+
# container. The resulting object should be modified and then passed to
|
213
|
+
# update_container.
|
214
|
+
#
|
215
|
+
# Note that LXD does not allow certain attributes to be changed (e.g.
|
216
|
+
# <code>status</code>, <code>status_code</code>, <code>stateful</code>,
|
217
|
+
# <code>name</code>, etc.) through this call.
|
218
|
+
#
|
219
|
+
# @async This method is asynchronous. See {Hyperkit::Configurable#auto_sync} for more information.
|
220
|
+
#
|
221
|
+
# @param name [String] Container name
|
222
|
+
# @param config [Sawyer::Resource|Hash] Container configuration obtained from #container
|
223
|
+
# @param options [Hash] Additional data to be passed
|
224
|
+
# @option options [Boolean] :sync If <code>false</code>, returns an asynchronous operation that must be passed to {Hyperkit::Client::Operations#wait_for_operation}. If <code>true</code>, automatically waits and returns the result of the operation. Defaults to value of {Hyperkit::Configurable#auto_sync}.
|
225
|
+
# @return [Sawyer::Resource] Operation or result, depending value of <code>:sync</code> parameter and/or {Hyperkit::Client::auto_sync}
|
226
|
+
#
|
227
|
+
# @example Add 'eth1' device to a container
|
228
|
+
# container = Hyperkit.container("test-container")
|
229
|
+
# container.devices.eth1 = {nictype: "bridged", parent: "lxcbr0", type: "nic"}
|
230
|
+
# Hyperkit.update_container("test-container", container)
|
231
|
+
#
|
232
|
+
# @example Change container to be ephemeral (i.e. it will be deleted when stopped)
|
233
|
+
# container = Hyperkit.container("test-container")
|
234
|
+
# container.ephemeral = true
|
235
|
+
# Hyperkit.update_container("test-container", container)
|
236
|
+
#
|
237
|
+
# @example Change container's AppArmor profile to 'unconfined'.
|
238
|
+
# container = Hyperkit.container("test-container")
|
239
|
+
#
|
240
|
+
# # Note: due to a bug in Sawyer::Resource, the following will fail
|
241
|
+
# container.config[:"raw.lxc"] = "lxc.aa_profile=unconfined"
|
242
|
+
#
|
243
|
+
# # Instead, convert 'config' to a Hash, and update the Hash
|
244
|
+
# container.config = container.config.to_hash
|
245
|
+
# container.config["raw.lxc"] = "lxc.aa_profile=unconfined"
|
246
|
+
#
|
247
|
+
# Hyperkit.update_container("test-container", container)
|
248
|
+
def update_container(name, config, options={})
|
249
|
+
|
250
|
+
config = config.to_hash
|
251
|
+
|
252
|
+
# Stringify values in the config hash, since LXD chokes on non-String values
|
253
|
+
if config[:config]
|
254
|
+
config[:config] = config[:config].inject({}){|h,(k,v)| h[k.to_s] = v.to_s; h}
|
255
|
+
end
|
256
|
+
|
257
|
+
response = put(container_path(name), config).metadata
|
258
|
+
handle_async(response, options[:sync])
|
259
|
+
end
|
260
|
+
|
261
|
+
# Rename a container
|
262
|
+
#
|
263
|
+
# @async This method is asynchronous. See {Hyperkit::Configurable#auto_sync} for more information.
|
264
|
+
#
|
265
|
+
# @param old_name [String] Existing container name
|
266
|
+
# @param new_name [String] New container name
|
267
|
+
# @param options [Hash] Additional data to be passed
|
268
|
+
# @option options [Boolean] :sync If <code>false</code>, returns an asynchronous operation that must be passed to {Hyperkit::Client::Operations#wait_for_operation}. If <code>true</code>, automatically waits and returns the result of the operation. Defaults to value of {Hyperkit::Configurable#auto_sync}.
|
269
|
+
# @return [Sawyer::Resource] Operation or result, depending value of <code>:sync</code> parameter and/or {Hyperkit::Client::auto_sync}
|
270
|
+
#
|
271
|
+
# @example Rename container "test" to "test2"
|
272
|
+
# Hyperkit.rename_container("test", "test2")
|
273
|
+
def rename_container(old_name, new_name, options={})
|
274
|
+
response = post(container_path(old_name), { "name": new_name }).metadata
|
275
|
+
handle_async(response, options[:sync])
|
276
|
+
end
|
277
|
+
|
278
|
+
# @!endgroup
|
279
|
+
|
280
|
+
# @!group Commands
|
281
|
+
|
282
|
+
# Execute a command in a container
|
283
|
+
#
|
284
|
+
# @async This method is asynchronous. See {Hyperkit::Configurable#auto_sync} for more information.
|
285
|
+
#
|
286
|
+
# @param container [String] Container name
|
287
|
+
# @param command [Array|String] Command to execute
|
288
|
+
# @param options [Hash] Additional data to be passed
|
289
|
+
# @option options [Hash] :environment Environment variables to set prior to command execution
|
290
|
+
# @option options [Boolean] :sync If <code>false</code>, returns an asynchronous operation that must be passed to {Hyperkit::Client::Operations#wait_for_operation}. If <code>true</code>, automatically waits and returns the result of the operation. Defaults to value of {Hyperkit::Configurable#auto_sync}.
|
291
|
+
# @return [Sawyer::Resource] Operation or result, depending value of <code>:sync</code> parameter and/or {Hyperkit::Client::auto_sync}
|
292
|
+
#
|
293
|
+
# @example Run a command (passed as a string) in container "test-container"
|
294
|
+
# Hyperkit.execute_command("test-container", "echo 'hello world'")
|
295
|
+
#
|
296
|
+
# @example Run a command (passed as an array) in container "test-container"
|
297
|
+
# Hyperkit.execute_command("test-container",
|
298
|
+
# ["bash", "-c", "echo 'hello world' > /tmp/test.txt"]
|
299
|
+
# )
|
300
|
+
#
|
301
|
+
# @example Run a command and pass environment variables
|
302
|
+
# Hyperkit.execute_command("test-container",
|
303
|
+
# "/bin/sh -c 'echo \"$MYVAR\" $MYVAR2 > /tmp/test.txt'",
|
304
|
+
# environment: {
|
305
|
+
# MYVAR: "hello world",
|
306
|
+
# MYVAR2: 42
|
307
|
+
# }
|
308
|
+
# )
|
309
|
+
def execute_command(container, command, options={})
|
310
|
+
|
311
|
+
opts = options.slice(:environment)
|
312
|
+
command = Shellwords.shellsplit(command) if command.is_a?(String)
|
313
|
+
|
314
|
+
# Stringify any environment values since LXD croaks on non-String values
|
315
|
+
if opts[:environment]
|
316
|
+
opts[:environment] = opts[:environment].inject({}){|h,(k,v)| h[k.to_s] = v.to_s; h}
|
317
|
+
end
|
318
|
+
|
319
|
+
response = post(File.join(container_path(container), "exec"), {
|
320
|
+
command: command,
|
321
|
+
environment: opts[:environment] || {},
|
322
|
+
"wait-for-websocket" => false,
|
323
|
+
interactive: false
|
324
|
+
}).metadata
|
325
|
+
|
326
|
+
handle_async(response, options[:sync])
|
327
|
+
|
328
|
+
end
|
329
|
+
|
330
|
+
alias_method :run_command, :execute_command
|
331
|
+
|
332
|
+
# @!endgroup
|
333
|
+
|
334
|
+
# @!group Deletion
|
335
|
+
|
336
|
+
# Delete a container. Throws an error if the container is running.
|
337
|
+
#
|
338
|
+
# @async This method is asynchronous. See {Hyperkit::Configurable#auto_sync} for more information.
|
339
|
+
#
|
340
|
+
# @param name [String] Container name
|
341
|
+
# @param options [Hash] Additional data to be passed
|
342
|
+
# @option options [Boolean] :sync If <code>false</code>, returns an asynchronous operation that must be passed to {Hyperkit::Client::Operations#wait_for_operation}. If <code>true</code>, automatically waits and returns the result of the operation. Defaults to value of {Hyperkit::Configurable#auto_sync}.
|
343
|
+
# @return [Sawyer::Resource] Operation or result, depending value of <code>:sync</code> parameter and/or {Hyperkit::Client::auto_sync}
|
344
|
+
#
|
345
|
+
# @example Delete container "test"
|
346
|
+
# Hyperkit.delete_container("test")
|
347
|
+
#
|
348
|
+
def delete_container(name, options={})
|
349
|
+
response = delete(container_path(name)).metadata
|
350
|
+
handle_async(response, options[:sync])
|
351
|
+
end
|
352
|
+
|
353
|
+
# @!endgroup
|
354
|
+
|
355
|
+
# @!group State
|
356
|
+
|
357
|
+
# Retrieve the current state of a container
|
358
|
+
#
|
359
|
+
# @param name [String] Container name
|
360
|
+
# @return [Sawyer::Resource] Container state
|
361
|
+
#
|
362
|
+
# @example Get container state
|
363
|
+
# Hyperkit.container_state("test-container") #=> {
|
364
|
+
# }
|
365
|
+
def container_state(name)
|
366
|
+
get(container_state_path(name)).metadata
|
367
|
+
end
|
368
|
+
|
369
|
+
# Start a container
|
370
|
+
#
|
371
|
+
# @async This method is asynchronous. See {Hyperkit::Configurable#auto_sync} for more information.
|
372
|
+
#
|
373
|
+
# @param name [String] Container name
|
374
|
+
# @param options [Hash] Additional data to be passed
|
375
|
+
# @option options [Boolean] :stateful Whether to restore previously saved runtime state (default: <code>false</false>)
|
376
|
+
# @option options [Boolean] :sync If <code>false</code>, returns an asynchronous operation that must be passed to {Hyperkit::Client::Operations#wait_for_operation}. If <code>true</code>, automatically waits and returns the result of the operation. Defaults to value of {Hyperkit::Configurable#auto_sync}.
|
377
|
+
# @option options [Fixnum] :timeout Time after which the operation is considered to have failed (default: no timeout)
|
378
|
+
# @return [Sawyer::Resource] Operation or result, depending value of <code>:sync</code> parameter and/or {Hyperkit::Client::auto_sync}
|
379
|
+
#
|
380
|
+
# @example Start container
|
381
|
+
# Hyperkit.start_container("test")
|
382
|
+
#
|
383
|
+
# @example Start container and restore previously saved runtime state
|
384
|
+
# # Stop the container and save its runtime state
|
385
|
+
# Hyperkit.stop_container("test", stateful: true)
|
386
|
+
#
|
387
|
+
# # Start the container and restore its runtime state
|
388
|
+
# Hyperkit.start_container("test", stateful: true)
|
389
|
+
#
|
390
|
+
# @example Start container with a timeout
|
391
|
+
# Hyperkit.start_container("test", timeout: 30)
|
392
|
+
def start_container(name, options={})
|
393
|
+
opts = options.slice(:stateful, :timeout)
|
394
|
+
response = put(container_state_path(name), opts.merge(action: "start")).metadata
|
395
|
+
handle_async(response, options[:sync])
|
396
|
+
end
|
397
|
+
|
398
|
+
# Stop a container
|
399
|
+
#
|
400
|
+
# @async This method is asynchronous. See {Hyperkit::Configurable#auto_sync} for more information.
|
401
|
+
#
|
402
|
+
# @param name [String] Container name
|
403
|
+
# @param options [Hash] Additional data to be passed
|
404
|
+
# @option options [Boolean] :force Whether to force the operation by killing the container
|
405
|
+
# @option options [Boolean] :stateful Whether to restore previously saved runtime state (default: <code>false</false>)
|
406
|
+
# @option options [Boolean] :sync If <code>false</code>, returns an asynchronous operation that must be passed to {Hyperkit::Client::Operations#wait_for_operation}. If <code>true</code>, automatically waits and returns the result of the operation. Defaults to value of {Hyperkit::Configurable#auto_sync}.
|
407
|
+
# @option options [Fixnum] :timeout Time after which the operation is considered to have failed (default: no timeout)
|
408
|
+
# @return [Sawyer::Resource] Operation or result, depending value of <code>:sync</code> parameter and/or {Hyperkit::Client::auto_sync}
|
409
|
+
#
|
410
|
+
# @example Stop container
|
411
|
+
# Hyperkit.stop_container("test")
|
412
|
+
#
|
413
|
+
# @example Stop container and save its runtime state
|
414
|
+
# # Stop the container and save its runtime state
|
415
|
+
# Hyperkit.stop_container("test", stateful: true)
|
416
|
+
#
|
417
|
+
# # Start the container and restore its runtime state
|
418
|
+
# Hyperkit.start_container("test", stateful: true)
|
419
|
+
#
|
420
|
+
# @example Stop the container forcefully (i.e. kill it)
|
421
|
+
# Hyperkit.stop_container("test", force: true)
|
422
|
+
def stop_container(name, options={})
|
423
|
+
opts = options.slice(:force, :stateful, :timeout)
|
424
|
+
response = put(container_state_path(name), opts.merge(action: "stop")).metadata
|
425
|
+
handle_async(response, options[:sync])
|
426
|
+
end
|
427
|
+
|
428
|
+
# Restart a running container
|
429
|
+
#
|
430
|
+
# @async This method is asynchronous. See {Hyperkit::Configurable#auto_sync} for more information.
|
431
|
+
#
|
432
|
+
# @param name [String] Container name
|
433
|
+
# @param options [Hash] Additional data to be passed
|
434
|
+
# @option options [Boolean] :force Whether to force the operation by killing the container
|
435
|
+
# @option options [Boolean] :sync If <code>false</code>, returns an asynchronous operation that must be passed to {Hyperkit::Client::Operations#wait_for_operation}. If <code>true</code>, automatically waits and returns the result of the operation. Defaults to value of {Hyperkit::Configurable#auto_sync}.
|
436
|
+
# @option options [Fixnum] :timeout Time after which the operation is considered to have failed (default: no timeout)
|
437
|
+
# @return [Sawyer::Resource] Operation or result, depending value of <code>:sync</code> parameter and/or {Hyperkit::Client::auto_sync}
|
438
|
+
#
|
439
|
+
# @example Restart container
|
440
|
+
# Hyperkit.restart_container("test")
|
441
|
+
#
|
442
|
+
# @example Restart container forcefully
|
443
|
+
# Hyperkit.restart_container("test", force: true)
|
444
|
+
#
|
445
|
+
# @example Restart container with timeout
|
446
|
+
# Hyperkit.restart_container("test", timeout: 30)
|
447
|
+
def restart_container(name, options={})
|
448
|
+
opts = options.slice(:force, :timeout)
|
449
|
+
response = put(container_state_path(name), opts.merge(action: "restart")).metadata
|
450
|
+
handle_async(response, options[:sync])
|
451
|
+
end
|
452
|
+
|
453
|
+
# Freeze (suspend) a running container
|
454
|
+
#
|
455
|
+
# @async This method is asynchronous. See {Hyperkit::Configurable#auto_sync} for more information.
|
456
|
+
#
|
457
|
+
# @param name [String] Container name
|
458
|
+
# @param options [Hash] Additional data to be passed
|
459
|
+
# @option options [Boolean] :sync If <code>false</code>, returns an asynchronous operation that must be passed to {Hyperkit::Client::Operations#wait_for_operation}. If <code>true</code>, automatically waits and returns the result of the operation. Defaults to value of {Hyperkit::Configurable#auto_sync}.
|
460
|
+
# @option options [Fixnum] :timeout Time after which the operation is considered to have failed (default: no timeout)
|
461
|
+
# @return [Sawyer::Resource] Operation or result, depending value of <code>:sync</code> parameter and/or {Hyperkit::Client::auto_sync}
|
462
|
+
#
|
463
|
+
# @example Suspend container
|
464
|
+
# Hyperkit.freeze_container("test")
|
465
|
+
#
|
466
|
+
# @example Suspend container with timeout
|
467
|
+
# Hyperkit.freeze_container("test", timeout: 30)
|
468
|
+
def freeze_container(name, options={})
|
469
|
+
opts = options.slice(:timeout)
|
470
|
+
response = put(container_state_path(name), opts.merge(action: "freeze")).metadata
|
471
|
+
handle_async(response, options[:sync])
|
472
|
+
end
|
473
|
+
|
474
|
+
alias_method :pause_container, :freeze_container
|
475
|
+
alias_method :suspend_container, :freeze_container
|
476
|
+
|
477
|
+
# Unfreeze (resume) a frozen container
|
478
|
+
#
|
479
|
+
# @async This method is asynchronous. See {Hyperkit::Configurable#auto_sync} for more information.
|
480
|
+
#
|
481
|
+
# @param name [String] Container name
|
482
|
+
# @param options [Hash] Additional data to be passed
|
483
|
+
# @option options [Boolean] :sync If <code>false</code>, returns an asynchronous operation that must be passed to {Hyperkit::Client::Operations#wait_for_operation}. If <code>true</code>, automatically waits and returns the result of the operation. Defaults to value of {Hyperkit::Configurable#auto_sync}.
|
484
|
+
# @option options [Fixnum] :timeout Time after which the operation is considered to have failed (default: no timeout)
|
485
|
+
# @return [Sawyer::Resource] Operation or result, depending value of <code>:sync</code> parameter and/or {Hyperkit::Client::auto_sync}
|
486
|
+
#
|
487
|
+
# @example Resume container
|
488
|
+
# Hyperkit.unfreeze_container("test")
|
489
|
+
#
|
490
|
+
# @example Resume container with timeout
|
491
|
+
# Hyperkit.unfreeze_container("test", timeout: 30)
|
492
|
+
def unfreeze_container(name, options={})
|
493
|
+
opts = options.slice(:timeout)
|
494
|
+
response = put(container_state_path(name), opts.merge(action: "unfreeze")).metadata
|
495
|
+
handle_async(response, options[:sync])
|
496
|
+
end
|
497
|
+
|
498
|
+
alias_method :resume_container, :unfreeze_container
|
499
|
+
|
500
|
+
# @!endgroup
|
501
|
+
|
502
|
+
# @!group Migration
|
503
|
+
|
504
|
+
# Prepare to migrate a container or snapshot. Generates source data to be passed to {#migrate}.
|
505
|
+
#
|
506
|
+
# Note that CRIU must be installed on the server to migrate a running container, or LXD will
|
507
|
+
# return a 500 error. On Ubuntu, you can install it with
|
508
|
+
# <code>sudo apt-get install criu</code>.
|
509
|
+
#
|
510
|
+
# @param name [String] Container name
|
511
|
+
# @return [Sawyer::Resource] Source data to be passed to {#migrate}
|
512
|
+
#
|
513
|
+
# @example Retrieve migration source data for container "test"
|
514
|
+
# Hyperkit.init_migration("test") #=> {
|
515
|
+
# :architecture => "x86_64",
|
516
|
+
# :config => {
|
517
|
+
# :"volatile.base_image" => "b41f6b96f103335eafbf38ba65488eda66b05b08b590130e473803631d66ff38",
|
518
|
+
# :"volatile.eth0.hwaddr" => "00:16:3e:e9:d5:5c",
|
519
|
+
# :"volatile.last_state.idmap" =>
|
520
|
+
# "[{\"Isuid\":true,\"Isgid\":false,\"Hostid\":231072,\"Nsid\":0,\"Maprange\":65536},{\"Isuid\":false,\"Isgid\":true,\"Hostid\":231072,\"Nsid\":0,\"Maprange\":65536}]"
|
521
|
+
# },
|
522
|
+
# :profiles => ["default"],
|
523
|
+
# :websocket => {
|
524
|
+
# :url => "https://192.168.103.101:8443/1.0/operations/a30aca8e-8ff3-4437-b1da-bb28b43ee876",
|
525
|
+
# :secrets => {
|
526
|
+
# :control => "a6f8d21ebfe9ec76bf56585c98fd6d700fd43edee513ce61e48e1abeef479106",
|
527
|
+
# :criu => "c8601ec0d07f97f206835dde5783640c08640e9b27e45624d8555546b0cca327",
|
528
|
+
# :fs => "ddf9d064331b9f3728d098873a8a89a7742b8e656f2cd0815f0aee4777ff2b54"
|
529
|
+
# }
|
530
|
+
# },
|
531
|
+
# :certificate => "source server SSL certificate"
|
532
|
+
# }
|
533
|
+
#
|
534
|
+
# @example Retrieve migration source data for snapshot "snap" of container "test"
|
535
|
+
# Hyperkit.init_migration("test", "snap") #=> {
|
536
|
+
# :architecture => "x86_64",
|
537
|
+
# :config => {
|
538
|
+
# :"volatile.apply_template" => "create",
|
539
|
+
# :"volatile.base_image" => "b41f6b96f103335eafbf38ba65488eda66b05b08b590130e473803631d66ff38",
|
540
|
+
# :"volatile.eth0.hwaddr" => "00:16:3e:e9:d5:5c",
|
541
|
+
# :"volatile.last_state.idmap" =>
|
542
|
+
# "[{\"Isuid\":true,\"Isgid\":false,\"Hostid\":231072,\"Nsid\":0,\"Maprange\":65536},{\"Isuid\":false,\"Isgid\":true,\"Hostid\":231072,\"Nsid\":0,\"Maprange\":65536}]"
|
543
|
+
# },
|
544
|
+
# :profiles => ["default"],
|
545
|
+
# :websocket => {
|
546
|
+
# :url => "https://192.168.103.101:8443/1.0/operations/a30aca8e-8ff3-4437-b1da-bb28b43ee876",
|
547
|
+
# :secrets => {
|
548
|
+
# :control => "a6f8d21ebfe9ec76bf56585c98fd6d700fd43edee513ce61e48e1abeef479106",
|
549
|
+
# :criu => "c8601ec0d07f97f206835dde5783640c08640e9b27e45624d8555546b0cca327",
|
550
|
+
# :fs => "ddf9d064331b9f3728d098873a8a89a7742b8e656f2cd0815f0aee4777ff2b54"
|
551
|
+
# }
|
552
|
+
# },
|
553
|
+
# :certificate => "source server SSL certificate"
|
554
|
+
# }
|
555
|
+
def init_migration(container, snapshot=nil)
|
556
|
+
|
557
|
+
if snapshot
|
558
|
+
url = snapshot_path(container, snapshot)
|
559
|
+
source = snapshot(container, snapshot)
|
560
|
+
else
|
561
|
+
url = container_path(container)
|
562
|
+
source = container(container)
|
563
|
+
end
|
564
|
+
|
565
|
+
response = post(url, { "migration": true })
|
566
|
+
agent = response.agent
|
567
|
+
|
568
|
+
source_data = {
|
569
|
+
architecture: source.architecture,
|
570
|
+
config: source.config.to_hash,
|
571
|
+
profiles: source.profiles,
|
572
|
+
websocket: {
|
573
|
+
url: File.join(api_endpoint, response.operation),
|
574
|
+
secrets: response.metadata.metadata.to_hash,
|
575
|
+
},
|
576
|
+
certificate: get("/1.0").metadata.environment.certificate,
|
577
|
+
snapshot: ! snapshot.nil?
|
578
|
+
}
|
579
|
+
|
580
|
+
Sawyer::Resource.new(response.agent, source_data)
|
581
|
+
end
|
582
|
+
|
583
|
+
# Migrate a remote container or snapshot to the server
|
584
|
+
#
|
585
|
+
# Note that CRIU must be installed on the server to migrate a running container, or LXD will
|
586
|
+
# return a 500 error. On Ubuntu, you can install it with
|
587
|
+
# <code>sudo apt-get install criu</code>.
|
588
|
+
#
|
589
|
+
# Also note that, unless overridden with the <code>profiles</code> parameter, if the source
|
590
|
+
# container has profiles applied to it that do not exist on the target LXD instance, this
|
591
|
+
# method will throw an exception.
|
592
|
+
#
|
593
|
+
# @async This method is asynchronous. See {Hyperkit::Configurable#auto_sync} for more information.
|
594
|
+
#
|
595
|
+
# @param source [Sawyer::Resource] Source data retrieve from the remote server with {#init_migration}
|
596
|
+
# @param dest_name [String] Name of the new container
|
597
|
+
# @param options [Hash] Additional data to be passed
|
598
|
+
# @option options [String] :architecture Architecture of the container (e.g. <code>x86_64</code>). By default, this will be obtained from the image metadata
|
599
|
+
# @option options [String] :certificate PEM certificate of the source server. If not specified, defaults to the certificate returned by the source server in the <code>source</code> parameter.
|
600
|
+
# @option options [Hash] :config Container configuration
|
601
|
+
# @option options [Boolean] :ephemeral Whether to make the container ephemeral (i.e. delete it when it is stopped; default: <code>false</code>)
|
602
|
+
# @option options [Boolean] :move Whether the container is being moved (<code>true</code>) or copied (<code>false</code>). Note that this does not actually delete the container from the remote LXD instance. Specifying <code>move: true</code> prevents regenerating volatile data (such as a container's MAC addresses), while <code>move: false</code> will regenerate all of this data. Defaults to <code>false</code> (a copy)
|
603
|
+
# @option options [Array] :profiles List of profiles to be applied to the container (default: <code>[]</code>)
|
604
|
+
# @option options [Boolean] :sync If <code>false</code>, returns an asynchronous operation that must be passed to {Hyperkit::Client::Operations#wait_for_operation}. If <code>true</code>, automatically waits and returns the result of the operation. Defaults to value of {Hyperkit::Configurable#auto_sync}.
|
605
|
+
# @return [Sawyer::Resource] Operation or result, depending value of <code>:sync</code> parameter and/or {Hyperkit::Client::auto_sync}
|
606
|
+
#
|
607
|
+
# @example Migrate container from remote instance
|
608
|
+
# remote_lxd = Hyperkit::Client.new(api_endpoint: "remote.example.com")
|
609
|
+
# source_data = remote_lxd.init_migration("remote-container")
|
610
|
+
# Hyperkit.migrate(source_data, "new-container")
|
611
|
+
#
|
612
|
+
# @example Migrate container and do not regenerate volatile data (e.g. MAC addresses)
|
613
|
+
# remote_lxd = Hyperkit::Client.new(api_endpoint: "remote.example.com")
|
614
|
+
# source_data = remote_lxd.init_migration("remote-container")
|
615
|
+
# Hyperkit.migrate(source_data, "new-container", move: true)
|
616
|
+
#
|
617
|
+
# @example Migrate container and override its profiles
|
618
|
+
# remote_lxd = Hyperkit::Client.new(api_endpoint: "remote.example.com")
|
619
|
+
# source_data = remote_lxd.init_migration("remote-container")
|
620
|
+
# Hyperkit.migrate(source_data, "new-container", profiles: %w[test-profile1 test-profile2])
|
621
|
+
#
|
622
|
+
# @example Migrate a snapshot
|
623
|
+
# remote_lxd = Hyperkit::Client.new(api_endpoint: "remote.example.com")
|
624
|
+
# source_data = remote_lxd.init_migration("remote-container", "remote-snapshot")
|
625
|
+
# Hyperkit.migrate(source_data, "new-container", profiles: %w[test-profile1 test-profile2])
|
626
|
+
def migrate(source, dest_name, options={})
|
627
|
+
|
628
|
+
opts = {
|
629
|
+
name: dest_name,
|
630
|
+
architecture: options[:architecture] || source.architecture,
|
631
|
+
source: {
|
632
|
+
type: "migration",
|
633
|
+
mode: "pull",
|
634
|
+
operation: source.websocket.url,
|
635
|
+
certificate: options[:certificate] || source.certificate,
|
636
|
+
secrets: source.websocket.secrets.to_hash
|
637
|
+
}
|
638
|
+
}
|
639
|
+
|
640
|
+
if ! source.snapshot
|
641
|
+
opts["base-image"] = source.config["volatile.base_image"]
|
642
|
+
opts[:config] = options[:config] || source.config.to_hash
|
643
|
+
|
644
|
+
# If we're only copying the container, and configuration was explicitly
|
645
|
+
# overridden, then remove the volatile entries
|
646
|
+
if ! options[:move] && ! options.has_key?(:config)
|
647
|
+
opts[:config].delete_if { |k,v| k.to_s.start_with?("volatile") }
|
648
|
+
end
|
649
|
+
|
650
|
+
else
|
651
|
+
opts[:config] = options[:config] || {}
|
652
|
+
end
|
653
|
+
|
654
|
+
if options.has_key?(:profiles)
|
655
|
+
opts[:profiles] = options[:profiles]
|
656
|
+
else
|
657
|
+
|
658
|
+
dest_profiles = profiles()
|
659
|
+
|
660
|
+
if (source.profiles - dest_profiles).empty?
|
661
|
+
opts[:profiles] = source.profiles
|
662
|
+
else
|
663
|
+
raise Hyperkit::MissingProfiles.new("Not all profiles applied to source container exist on the target LXD instance")
|
664
|
+
end
|
665
|
+
|
666
|
+
end
|
667
|
+
|
668
|
+
if options.has_key?(:ephemeral)
|
669
|
+
opts[:ephemeral] = options[:ephemeral]
|
670
|
+
else
|
671
|
+
opts[:ephemeral] = !! source.ephemeral
|
672
|
+
end
|
673
|
+
|
674
|
+
response = post(containers_path, opts).metadata
|
675
|
+
handle_async(response, options[:sync])
|
676
|
+
end
|
677
|
+
|
678
|
+
# @!endgroup
|
679
|
+
|
680
|
+
# @!group Snapshots
|
681
|
+
|
682
|
+
# List of snapshots for a container
|
683
|
+
#
|
684
|
+
# @param container [String] Container name
|
685
|
+
# @return [Array<String>] An array of snapshot names
|
686
|
+
#
|
687
|
+
# @example Get list of snapshots for container "test"
|
688
|
+
# Hyperkit.snapshots("test") #=> ["snap1", "snap2", "snap3"]
|
689
|
+
def snapshots(container)
|
690
|
+
response = get snapshots_path(container)
|
691
|
+
response.metadata.map { |path| path.split('/').last }
|
692
|
+
end
|
693
|
+
|
694
|
+
# Get information on a snapshot
|
695
|
+
#
|
696
|
+
# @param container [String] Container name
|
697
|
+
# @param Snapshot [String] Snapshot name
|
698
|
+
# @return [Sawyer::Resource] Snapshot information
|
699
|
+
#
|
700
|
+
# @example Get information about a snapshot
|
701
|
+
# Hyperkit.snapshot("test-container", "test-snapshot") #=> {
|
702
|
+
# :architecture => "x86_64",
|
703
|
+
# :config => {
|
704
|
+
# :"volatile.apply_template" => "create",
|
705
|
+
# :"volatile.base_image" => "097e75d6f7419d3a5e204d8125582f2d7bdd4ee4c35bd324513321c645f0c415",
|
706
|
+
# :"volatile.eth0.hwaddr" => "00:16:3e:24:5d:7a",
|
707
|
+
# :"volatile.eth0.name" => "eth0",
|
708
|
+
# :"volatile.last_state.idmap" =>
|
709
|
+
# "[{\"Isuid\":true,\"Isgid\":false,\"Hostid\":165536,\"Nsid\":0,\"Maprange\":65536},{\"Isuid\":false,\"Isgid\":true,\"Hostid\":165536,\"Nsid\":0,\"Maprange\":65536}]"
|
710
|
+
# },
|
711
|
+
# :created_at => 2016-03-18 20:55:26 UTC,
|
712
|
+
# :devices => {
|
713
|
+
# :root => {:path => "/", :type => "disk"}
|
714
|
+
# },
|
715
|
+
# :ephemeral => false,
|
716
|
+
# :expanded_config => {
|
717
|
+
# :"volatile.apply_template" => "create",
|
718
|
+
# :"volatile.base_image" => "097e75d6f7419d3a5e204d8125582f2d7bdd4ee4c35bd324513321c645f0c415",
|
719
|
+
# :"volatile.eth0.hwaddr" => "00:16:3e:24:5d:7a",
|
720
|
+
# :"volatile.eth0.name" => "eth0",
|
721
|
+
# :"volatile.last_state.idmap" =>
|
722
|
+
# "[{\"Isuid\":true,\"Isgid\":false,\"Hostid\":165536,\"Nsid\":0,\"Maprange\":65536},{\"Isuid\":false,\"Isgid\":true,\"Hostid\":165536,\"Nsid\":0,\"Maprange\":65536}]"
|
723
|
+
# },
|
724
|
+
# :expanded_devices => {
|
725
|
+
# :eth0 => { :name => "eth0", :nictype => "bridged", :parent => "lxcbr0", :type => "nic"},
|
726
|
+
# :root => { :path => "/", :type => "disk"}
|
727
|
+
# },
|
728
|
+
# :name => "test-container/test-snapshot",
|
729
|
+
# :profiles => ["default"],
|
730
|
+
# :stateful => false
|
731
|
+
# }
|
732
|
+
def snapshot(container, snapshot)
|
733
|
+
get(snapshot_path(container, snapshot)).metadata
|
734
|
+
end
|
735
|
+
|
736
|
+
# Create a snapshot of a container
|
737
|
+
#
|
738
|
+
# If <code>stateful: true</code> is passed when creating a snapshot of a
|
739
|
+
# running container, the container's runtime state will be stored in the
|
740
|
+
# snapshot. Note that CRIU must be installed on the server to create a
|
741
|
+
# stateful snapshot, or LXD will return a 500 error. On Ubuntu, you can
|
742
|
+
# install it with
|
743
|
+
# <code>sudo apt-get install criu</code>.
|
744
|
+
#
|
745
|
+
# @async This method is asynchronous. See {Hyperkit::Configurable#auto_sync} for more information.
|
746
|
+
#
|
747
|
+
# @param container [String] Container name
|
748
|
+
# @param snapshot [String] Snapshot name
|
749
|
+
# @param options [Hash] Additional data to be passed
|
750
|
+
# @option options [Boolean] :stateful Whether to save runtime state for a running container (requires CRIU on the server; default: <code>false</false>)
|
751
|
+
# @option options [Boolean] :sync If <code>false</code>, returns an asynchronous operation that must be passed to {Hyperkit::Client::Operations#wait_for_operation}. If <code>true</code>, automatically waits and returns the result of the operation. Defaults to value of {Hyperkit::Configurable#auto_sync}.
|
752
|
+
# @return [Sawyer::Resource] Operation or result, depending value of <code>:sync</code> parameter and/or {Hyperkit::Client::auto_sync}
|
753
|
+
#
|
754
|
+
# @example Create stateless snapshot for container 'test'
|
755
|
+
# Hyperkit.create_snapshot("test", "snap1")
|
756
|
+
#
|
757
|
+
# @example Create snapshot and save runtime state for running container 'test'
|
758
|
+
# Hyperkit.create_snapshot("test", "snap1", stateful: true)
|
759
|
+
def create_snapshot(container, snapshot, options={})
|
760
|
+
opts = options.slice(:stateful)
|
761
|
+
opts[:name] = snapshot
|
762
|
+
response = post(snapshots_path(container), opts).metadata
|
763
|
+
handle_async(response, options[:sync])
|
764
|
+
end
|
765
|
+
|
766
|
+
# Delete a snapshot
|
767
|
+
#
|
768
|
+
# @async This method is asynchronous. See {Hyperkit::Configurable#auto_sync} for more information.
|
769
|
+
#
|
770
|
+
# @param container [String] Container name
|
771
|
+
# @param snapshot [String] Snapshot name
|
772
|
+
# @param options [Hash] Additional data to be passed
|
773
|
+
# @option options [Boolean] :sync If <code>false</code>, returns an asynchronous operation that must be passed to {Hyperkit::Client::Operations#wait_for_operation}. If <code>true</code>, automatically waits and returns the result of the operation. Defaults to value of {Hyperkit::Configurable#auto_sync}.
|
774
|
+
# @return [Sawyer::Resource] Operation or result, depending value of <code>:sync</code> parameter and/or {Hyperkit::Client::auto_sync}
|
775
|
+
#
|
776
|
+
# @example Delete snapshot "snap" from container "test"
|
777
|
+
# Hyperkit.delete_snapshot("test", "snap")
|
778
|
+
#
|
779
|
+
def delete_snapshot(container, snapshot, options={})
|
780
|
+
response = delete(snapshot_path(container, snapshot)).metadata
|
781
|
+
handle_async(response, options[:sync])
|
782
|
+
end
|
783
|
+
|
784
|
+
# Rename a snapshot
|
785
|
+
#
|
786
|
+
# @async This method is asynchronous. See {Hyperkit::Configurable#auto_sync} for more information.
|
787
|
+
#
|
788
|
+
# @param container [String] Container name
|
789
|
+
# @param old_name [String] Existing snapshot name
|
790
|
+
# @param new_name [String] New snapshot name
|
791
|
+
# @param options [Hash] Additional data to be passed
|
792
|
+
# @option options [Boolean] :sync If <code>false</code>, returns an asynchronous operation that must be passed to {Hyperkit::Client::Operations#wait_for_operation}. If <code>true</code>, automatically waits and returns the result of the operation. Defaults to value of {Hyperkit::Configurable#auto_sync}.
|
793
|
+
# @return [Sawyer::Resource] Operation or result, depending value of <code>:sync</code> parameter and/or {Hyperkit::Client::auto_sync}
|
794
|
+
#
|
795
|
+
# @example Rename snapshot "test/snap1" to "snap2"
|
796
|
+
# Hyperkit.rename_snapshot("test", "snap1", "snap2")
|
797
|
+
def rename_snapshot(container, old_name, new_name, options={})
|
798
|
+
response = post(snapshot_path(container, old_name), { "name": new_name }).metadata
|
799
|
+
handle_async(response, options[:sync])
|
800
|
+
end
|
801
|
+
|
802
|
+
# Restore a snapshot
|
803
|
+
#
|
804
|
+
# @async This method is asynchronous. See {Hyperkit::Configurable#auto_sync} for more information.
|
805
|
+
#
|
806
|
+
# @param container [String] Container name
|
807
|
+
# @param snapshot [String] Name of snapshot to restore
|
808
|
+
# @param options [Hash] Additional data to be passed
|
809
|
+
# @option options [Boolean] :sync If <code>false</code>, returns an asynchronous operation that must be passed to {Hyperkit::Client::Operations#wait_for_operation}. If <code>true</code>, automatically waits and returns the result of the operation. Defaults to value of {Hyperkit::Configurable#auto_sync}.
|
810
|
+
# @return [Sawyer::Resource] Operation or result, depending value of <code>:sync</code> parameter and/or {Hyperkit::Client::auto_sync}
|
811
|
+
#
|
812
|
+
# @example Restore container "test" back to snapshot "snap1"
|
813
|
+
# Hyperkit.restore_snapshot("test", "snap1")
|
814
|
+
def restore_snapshot(container, snapshot, options={})
|
815
|
+
response = put(container_path(container), { "restore": snapshot }).metadata
|
816
|
+
handle_async(response, options[:sync])
|
817
|
+
end
|
818
|
+
|
819
|
+
alias_method :revert_to_snapshot, :restore_snapshot
|
820
|
+
|
821
|
+
# @!endgroup
|
822
|
+
|
823
|
+
# @!group Files
|
824
|
+
|
825
|
+
# Read the contents of a file in a container
|
826
|
+
#
|
827
|
+
# @param container [String] Container name
|
828
|
+
# @param file [String] Full path to a file within the container
|
829
|
+
# @return [String] The contents of the file
|
830
|
+
#
|
831
|
+
# @example Read the file /etc/hostname in container "test"
|
832
|
+
# Hyperkit.read_file("test", "/etc/hostname") #=> "test-container.example.com"
|
833
|
+
def read_file(container, file)
|
834
|
+
get(file_path(container, file), url_encode: false)
|
835
|
+
end
|
836
|
+
|
837
|
+
# Copy a file from a container to the local system. The file will be
|
838
|
+
# written with the same permissions assigned to it in the container.
|
839
|
+
#
|
840
|
+
# @param container [String] Container name
|
841
|
+
# @param source_file [String] Full path to a file within the container
|
842
|
+
# @param dest_file [String] Full path of desired output file (will be created/overwritten)
|
843
|
+
# @return [String] Full path to the local output file
|
844
|
+
#
|
845
|
+
# @example Copy /etc/passwd in container "test" to the local file /tmp/passwd
|
846
|
+
# Hyperkit.pull_file("test", "/etc/passwd", "/tmp/passwd") #=> "/tmp/passwd"
|
847
|
+
def pull_file(container, source_file, dest_file)
|
848
|
+
contents = get(file_path(container, source_file), url_encode: false)
|
849
|
+
headers = last_response.headers
|
850
|
+
|
851
|
+
File.open(dest_file, "wb") do |f|
|
852
|
+
f.write(contents)
|
853
|
+
end
|
854
|
+
|
855
|
+
if headers["x-lxd-mode"]
|
856
|
+
File.chmod(headers["x-lxd-mode"].to_i(8), dest_file)
|
857
|
+
end
|
858
|
+
|
859
|
+
dest_file
|
860
|
+
|
861
|
+
end
|
862
|
+
|
863
|
+
# Write to a file in a container
|
864
|
+
#
|
865
|
+
# @param container [String] Container name
|
866
|
+
# @param dest_file [String] Path to the output file in the container
|
867
|
+
# @param options [Hash] Additional data to be passed
|
868
|
+
# @option options [Fixnum] :uid Owner to assign to the file
|
869
|
+
# @option options [Fixnum] :gid Group to assign to the file
|
870
|
+
# @option options [Fixnum] :mode File permissions (in octal) to assign to the file
|
871
|
+
# @option options [Fixnum] :content Content to write to the file (if no block given)
|
872
|
+
# @yieldparam io [StringIO] IO to be used to write to the file from a block
|
873
|
+
# @return [Sawyer::Resource]
|
874
|
+
#
|
875
|
+
# @example Write string "hello" to /tmp/test.txt in container test-container
|
876
|
+
# Hyperkit.write_file("test-container", "/tmp/test.txt", content: "hello")
|
877
|
+
#
|
878
|
+
# @example Write to file using a block
|
879
|
+
# Hyperkit.write_file("test-container", "/tmp/test.txt") do |io|
|
880
|
+
# io.print "Hello "
|
881
|
+
# io.puts "world"
|
882
|
+
# end
|
883
|
+
#
|
884
|
+
# @example Assign uid, gid, and mode to a file:
|
885
|
+
# Hyperkit.write_file("test-container",
|
886
|
+
# "/tmp/test.txt",
|
887
|
+
# content: "hello",
|
888
|
+
# uid: 1000,
|
889
|
+
# gid: 1000,
|
890
|
+
# mode: 0644
|
891
|
+
# )
|
892
|
+
def write_file(container, dest_file, options={}, &block)
|
893
|
+
|
894
|
+
headers = { "Content-Type" => "application/octet-stream" }
|
895
|
+
headers["X-LXD-uid"] = options[:uid].to_s if options[:uid]
|
896
|
+
headers["X-LXD-gid"] = options[:gid].to_s if options[:gid]
|
897
|
+
headers["X-LXD-mode"] = options[:mode].to_s(8).rjust(4, "0") if options[:mode]
|
898
|
+
|
899
|
+
if ! block_given?
|
900
|
+
content = options[:content].to_s
|
901
|
+
else
|
902
|
+
io = StringIO.new
|
903
|
+
yield io
|
904
|
+
io.rewind
|
905
|
+
content = io.read
|
906
|
+
end
|
907
|
+
|
908
|
+
post(file_path(container, dest_file), {
|
909
|
+
raw_body: content,
|
910
|
+
headers: headers
|
911
|
+
})
|
912
|
+
|
913
|
+
end
|
914
|
+
|
915
|
+
# Copy a file from the local system to container
|
916
|
+
#
|
917
|
+
# @param container [String] Container name
|
918
|
+
# @param source_file [String] Full path to a file within the container
|
919
|
+
# @param dest_file [String] Full path of desired output file (will be created/overwritten)
|
920
|
+
# @param options [Hash] Additional data to be passed
|
921
|
+
# @option options [Fixnum] :uid Owner to assign to the file
|
922
|
+
# @option options [Fixnum] :gid Group to assign to the file
|
923
|
+
# @option options [Fixnum] :mode File permissions (in octal) to assign to the file
|
924
|
+
# @return [Sawyer::Resource]
|
925
|
+
#
|
926
|
+
# @example Copy /tmp/test.txt from the local system to /etc/passwd in the container
|
927
|
+
# Hyperkit.push_file("/tmp/test.txt", "test-container", "/etc/passwd")
|
928
|
+
#
|
929
|
+
# @example Assign uid, gid, and mode to a file:
|
930
|
+
# Hyperkit.push_file("/tmp/test.txt",
|
931
|
+
# "test-container",
|
932
|
+
# "/etc/passwd",
|
933
|
+
# uid: 1000,
|
934
|
+
# gid: 1000,
|
935
|
+
# mode: 0644
|
936
|
+
# )
|
937
|
+
def push_file(source_file, container, dest_file, options={})
|
938
|
+
|
939
|
+
write_file(container, dest_file, options) do |f|
|
940
|
+
f.write File.read(source_file)
|
941
|
+
end
|
942
|
+
|
943
|
+
end
|
944
|
+
|
945
|
+
# @!endgroup
|
946
|
+
|
947
|
+
# @!group Logs
|
948
|
+
|
949
|
+
# Retrieve a list of logs for a container
|
950
|
+
#
|
951
|
+
# @param container [String] Container name
|
952
|
+
# @return [Array<String>] An array of log filenames
|
953
|
+
#
|
954
|
+
# @example Get list of logs for container "test-container"
|
955
|
+
# Hyperkit.logs("test-container")
|
956
|
+
def logs(container)
|
957
|
+
response = get(logs_path(container))
|
958
|
+
response.metadata.map { |path| path.sub(logs_path(container) + '/', '') }
|
959
|
+
end
|
960
|
+
|
961
|
+
# Retrieve the contents of a log for a container
|
962
|
+
#
|
963
|
+
# @param container [String] Container name
|
964
|
+
# @param log [String] Log filename
|
965
|
+
# @return [String] The contents of the log
|
966
|
+
#
|
967
|
+
# @example Get log "lxc.log" for container "test-container"
|
968
|
+
# Hyperkit.log("test-container", "lxc.log")
|
969
|
+
def log(container, log)
|
970
|
+
get(log_path(container, log))
|
971
|
+
end
|
972
|
+
|
973
|
+
# Delete a container's log
|
974
|
+
#
|
975
|
+
# @param container [String] Container name
|
976
|
+
# @param log [String] Filename of log to delete
|
977
|
+
#
|
978
|
+
# @example Delete log "lxc.log" for container "test-container"
|
979
|
+
# Hyperkit.delete_log("test-container", "lxc.log")
|
980
|
+
def delete_log(container, log)
|
981
|
+
delete(log_path(container, log))
|
982
|
+
end
|
983
|
+
|
984
|
+
# @!endgroup
|
985
|
+
|
986
|
+
private
|
987
|
+
|
988
|
+
REMOTE_IMAGE_ARGS = [:server, :protocol, :certificate, :secret]
|
989
|
+
|
990
|
+
def log_path(container, log)
|
991
|
+
File.join(logs_path(container), log)
|
992
|
+
end
|
993
|
+
|
994
|
+
def logs_path(container)
|
995
|
+
File.join(container_path(container), "logs")
|
996
|
+
end
|
997
|
+
|
998
|
+
def file_path(container, file)
|
999
|
+
File.join(container_path(container), "files") + "?path=#{file}"
|
1000
|
+
end
|
1001
|
+
|
1002
|
+
def snapshot_path(container, snapshot)
|
1003
|
+
File.join(snapshots_path(container), snapshot)
|
1004
|
+
end
|
1005
|
+
|
1006
|
+
def snapshots_path(name)
|
1007
|
+
File.join(container_path(name), "snapshots")
|
1008
|
+
end
|
1009
|
+
|
1010
|
+
def container_state_path(name)
|
1011
|
+
File.join(container_path(name), "state")
|
1012
|
+
end
|
1013
|
+
|
1014
|
+
def container_path(name)
|
1015
|
+
File.join(containers_path, name)
|
1016
|
+
end
|
1017
|
+
|
1018
|
+
def containers_path
|
1019
|
+
"/1.0/containers"
|
1020
|
+
end
|
1021
|
+
|
1022
|
+
def extract_container_options(name, options)
|
1023
|
+
opts = options.slice(:architecture, :profiles, :ephemeral, :config).
|
1024
|
+
merge({ name: name })
|
1025
|
+
|
1026
|
+
# Stringify any config values since LXD croaks on non-String values
|
1027
|
+
if opts[:config]
|
1028
|
+
opts[:config] = opts[:config].inject({}){|h,(k,v)| h[k.to_s] = v.to_s; h}
|
1029
|
+
end
|
1030
|
+
|
1031
|
+
opts
|
1032
|
+
end
|
1033
|
+
|
1034
|
+
def container_source_attribute(options)
|
1035
|
+
|
1036
|
+
[:fingerprint, :alias, :properties].each do |attr|
|
1037
|
+
return options.slice(attr) if options[attr]
|
1038
|
+
end
|
1039
|
+
|
1040
|
+
{}
|
1041
|
+
|
1042
|
+
end
|
1043
|
+
|
1044
|
+
def empty_container_options(name, options)
|
1045
|
+
opts = {
|
1046
|
+
source: {
|
1047
|
+
type: "none"
|
1048
|
+
}
|
1049
|
+
}.merge(extract_container_options(name, options))
|
1050
|
+
|
1051
|
+
[:alias, :certificate, :fingerprint, :properties, :protocol, :secret, :server].each do |prop|
|
1052
|
+
if ! (options.keys & [prop]).empty?
|
1053
|
+
raise Hyperkit::InvalidImageAttributes.new("empty: true is not compatible with the #{prop} option")
|
1054
|
+
end
|
1055
|
+
end
|
1056
|
+
|
1057
|
+
opts
|
1058
|
+
|
1059
|
+
end
|
1060
|
+
|
1061
|
+
|
1062
|
+
def remote_image_container_options(name, source, options)
|
1063
|
+
|
1064
|
+
opts = {
|
1065
|
+
source: {
|
1066
|
+
type: "image",
|
1067
|
+
mode: "pull"
|
1068
|
+
}.merge(options.slice(*REMOTE_IMAGE_ARGS)).merge(source)
|
1069
|
+
|
1070
|
+
}.merge(extract_container_options(name, options))
|
1071
|
+
|
1072
|
+
if options[:protocol] && ! %w[lxd simplestreams].include?(options[:protocol])
|
1073
|
+
raise Hyperkit::InvalidProtocol.new("Invalid protocol. Valid choices: lxd, simplestreams")
|
1074
|
+
end
|
1075
|
+
|
1076
|
+
opts
|
1077
|
+
|
1078
|
+
end
|
1079
|
+
|
1080
|
+
def local_image_container_options(name, source, options)
|
1081
|
+
|
1082
|
+
opts = {
|
1083
|
+
source: {
|
1084
|
+
type: "image"
|
1085
|
+
}.merge(source)
|
1086
|
+
}.merge(extract_container_options(name, options))
|
1087
|
+
|
1088
|
+
if ! (options.keys & REMOTE_IMAGE_ARGS).empty?
|
1089
|
+
raise Hyperkit::InvalidImageAttributes.new(":protocol, :certificate, and :secret only apply when :server is also passed")
|
1090
|
+
end
|
1091
|
+
|
1092
|
+
opts
|
1093
|
+
|
1094
|
+
end
|
1095
|
+
|
1096
|
+
end
|
1097
|
+
|
1098
|
+
end
|
1099
|
+
|
1100
|
+
end
|