vagrant-nixos 0.0.1 → 0.0.2

Sign up to get free protection for your applications and to get access to all the features.
data/README.md CHANGED
@@ -1,22 +1,66 @@
1
1
  # NixOS Vagrant Plugin
2
2
 
3
- This plugin makes working with [NixOS](http://nixos.org) guests much easier to work with in [Vagrant](http://www.vagrantup.com) by:
3
+ This plugin makes working with [NixOS](http://nixos.org) guests in [Vagrant](http://www.vagrantup.com) a bit nicer:
4
4
 
5
- * Allowing network configurations from Vagrantfile (Vagrant does not have a default NixOS guest capability)
6
- * Provide a convention for nixos
5
+ * Allow network configurations
6
+ * Allow hostname setting
7
+ * Provide nix provisioning
7
8
 
8
- ## Usage:
9
+ ## Install:
9
10
 
10
11
  ```bash
11
12
  $ vagrant plugin install vagrant-nixos
12
- ...
13
13
  ```
14
14
 
15
+ ## Example Vagrantfile
16
+
17
+ ```ruby
18
+ Vagrant.configure("2") do |config|
19
+
20
+ # use a suitable NixOS box
21
+ config.vm.box = "nixos-14.02-x86_64"
22
+
23
+ # set hostname
24
+ config.vm.hostname = "nixy"
25
+
26
+ # Setup networking
27
+ config.vm.network "public_network", :bridge => 'en1: Wi-Fi (AirPort)'
28
+ config.vm.network "private_network", :ip => "172.16.16.16"
29
+
30
+ # Add the htop package
31
+ config.vm.provision :nixos, :expression => {
32
+ environment: {
33
+ systemPackages: [ :htop ]
34
+ }
35
+ }, :NIX_PATH => "/custom/path/to/nixpkgs"
36
+
37
+ end
38
+ ```
39
+
40
+ In the above `Vagrantfile` example we provision the box using the `:expression` method, which will perform a simple ruby -> nix conversion. `:expression` provisioning creates a nix module that executes with `pkgs` in scope. It is roughly equivilent to the below version that uses the `:inline` method.
41
+
42
+ ```ruby
43
+ config.vm.provision :nixos, :inline => %{
44
+ {config, pkgs, ...}: with pkgs; {
45
+ environment.systemPackages = [ htop ];
46
+ }
47
+ }, :NIX_PATH => "/custom/path/to/nixpkgs"
48
+ ```
49
+
50
+ Both examples show the optional configuring of a custom `NIX_PATH` path.
51
+
52
+
15
53
  ## How it works
16
54
 
17
- In nixos we don't mess around with the files in `/etc` instead we write delarative expressions for the system configuration starting in `/etc/nixos/configuration.nix`.
55
+ In nixos we don't mess around with the files in `/etc` instead we write expressions for the system configuration starting in `/etc/nixos/configuration.nix`.
56
+
57
+ This plugin sets some ground rules for nixos boxes to keep this configuration clean and provisioning possible.
58
+
59
+ Box creators should ensure that their `configuration.nix` file imports an nix module `/etc/nixos/vagrant.nix` which will be overwritten by `vagrant-nixos` during `vagrant up` or `vagrant provision`.
60
+
61
+ See the configuration in our [NixOS packer template](http://github.com/oxdi/nixos) for an example.
18
62
 
19
- This plugin sets some ground rules for nixos boxes to keep this configuration clean.
63
+ ## Issues
20
64
 
21
- Box creators should ensure that their `configuration.nix` file imports an empty file `/etc/nixos/vagrant.nix` which will be overwritten by `vagrant-nixos` during `vagrant up` or `vagrant provision` and a `nixos-rebuild switch` will be triggerd.
65
+ It's a bit slow on the initial boot/provision at the moment as it must run nixos-rebuild several times. This is far from ideal I'm sure I'll find a better place to hook in the rebuild step soon.
22
66
 
@@ -0,0 +1,30 @@
1
+ require 'set'
2
+ require 'tempfile'
3
+
4
+
5
+ module VagrantPlugins
6
+ module Nixos
7
+ module Cap
8
+ class ChangeHostName
9
+ include Vagrant::Util
10
+
11
+ def self.nix_module(name)
12
+ <<-NIX
13
+ { config, pkgs, ... }:
14
+ {
15
+ networking.hostName = "#{name}";
16
+ }
17
+ NIX
18
+ end
19
+
20
+ def self.change_host_name(machine, name)
21
+ if Nixos.write_config(machine, "vagrant-hostname.nix", nix_module(name))
22
+ Nixos.rebuild(machine)
23
+ end
24
+ end
25
+
26
+ end
27
+ end
28
+ end
29
+ end
30
+
@@ -11,60 +11,41 @@ module VagrantPlugins
11
11
 
12
12
  def self.nix_interface_expr(options)
13
13
  <<-NIX
14
- {
15
- name = "eth#{options[:interface]}";
16
- ipAddress = "#{options[:ip]}";
17
- subnetMask = "#{options[:netmask]}";
18
- }
14
+ {
15
+ name = "eth#{options[:interface]}";
16
+ ipAddress = "#{options[:ip]}";
17
+ subnetMask = "#{options[:netmask]}";
18
+ }
19
19
  NIX
20
20
  end
21
21
 
22
- def self.nix_interface_module(networks)
22
+ def self.nix_module(networks)
23
23
  exprs = networks.inject([]) do |exprs, network|
24
24
  # Interfaces without an ip set will fallback to
25
25
  # DHCP if useDHCP is set. So skip them.
26
- if network[:ip].empty?
26
+ if network[:ip].nil? or network[:ip].empty?
27
27
  exprs
28
28
  else
29
29
  exprs << nix_interface_expr(network)
30
30
  end
31
31
  end
32
32
  <<-NIX
33
- { config, pkgs, ... }:
34
- {
35
- networking.useDHCP = true;
36
- networking.interfaces = [
37
- #{exprs.join("\n")}
38
- ];
39
- }
33
+ { config, pkgs, ... }:
34
+ {
35
+ networking.usePredictableInterfaceNames = false;
36
+ networking.useDHCP = true;
37
+ networking.interfaces = [
38
+ #{exprs.join("\n")}
39
+ ];
40
+ }
40
41
  NIX
41
42
  end
42
43
 
43
44
  def self.configure_networks(machine, networks)
44
- machine.communicate.tap do |comm|
45
- # build the network config
46
- conf = nix_interface_module(networks)
47
-
48
- # Perform the careful dance necessary to reconfigure
49
- # the network interfaces
50
- temp = Tempfile.new("vagrant")
51
- temp.binmode
52
- temp.write(conf)
53
- temp.close
54
-
55
- puts conf
56
-
57
- # add the network config
58
- filename = "vagrant-interfaces.nix"
59
- comm.upload(temp.path, "/tmp/#{filename}")
60
- comm.sudo("mv /tmp/#{filename} /etc/nixos/#{filename}")
61
-
62
- # TODO: check that the network config is referenced in vagrant.nix
63
-
64
- # cleanup
65
- temp.unlink
66
- end
67
- end
45
+ if Nixos.write_config(machine, "vagrant-interfaces.nix", nix_module(networks))
46
+ Nixos.rebuild(machine)
47
+ end
48
+ end
68
49
 
69
50
  end
70
51
  end
@@ -0,0 +1,46 @@
1
+
2
+ module VagrantPlugins
3
+ module Nixos
4
+ class Config < Vagrant.plugin("2", :config)
5
+ attr_accessor :inline
6
+ attr_accessor :path
7
+ attr_accessor :expression
8
+ attr_accessor :NIX_PATH
9
+
10
+ def initialize
11
+ @inline = UNSET_VALUE
12
+ @path = UNSET_VALUE
13
+ @expression = UNSET_VALUE
14
+ @NIX_PATH = UNSET_VALUE
15
+ end
16
+
17
+ def finalize!
18
+ @inline = nil if @inline == UNSET_VALUE
19
+ @path = nil if @path == UNSET_VALUE
20
+ @expression = nil if @expression == UNSET_VALUE
21
+ @NIX_PATH = nil if @NIX_PATH == UNSET_VALUE
22
+ end
23
+
24
+ def expression=(v)
25
+ @expression = v.to_nix
26
+ end
27
+
28
+ def validate(machine)
29
+ errors = _detected_errors
30
+
31
+ if (path && inline) or (path && expression) or (inline && expression)
32
+ errors << "You can have one and only one of :path, :expression or :inline for nixos provisioner"
33
+ elsif !path && !inline && !expression
34
+ errors << "Missing :inline, :expression or :path for nixos provisioner"
35
+ end
36
+
37
+ if path && !File.exist?(path)
38
+ errors << "Invalid path #{path}"
39
+ end
40
+
41
+ { "nixos provisioner" => errors }
42
+ end
43
+
44
+ end
45
+ end
46
+ end
@@ -1,9 +1,13 @@
1
1
  module VagrantPlugins
2
2
  module Nixos
3
3
  class Guest < Vagrant.plugin("2", :guest)
4
+
5
+ attr_accessor :nix_imports
6
+
4
7
  def detect?(machine)
5
8
  machine.communicate.test("test -f /etc/nixos/configuration.nix")
6
9
  end
10
+
7
11
  end
8
12
  end
9
13
  end
@@ -12,6 +12,9 @@ end
12
12
 
13
13
  module VagrantPlugins
14
14
  module Nixos
15
+
16
+ @@nix_imports = {}
17
+
15
18
  class Plugin < Vagrant.plugin("2")
16
19
  name "nixos"
17
20
  description <<-DESC
@@ -28,6 +31,22 @@ module VagrantPlugins
28
31
  Cap::ConfigureNetworks
29
32
  end
30
33
 
34
+ guest_capability("nixos", "change_host_name") do
35
+ require_relative "cap/change_host_name"
36
+ Cap::ChangeHostName
37
+ end
38
+
39
+ config :nixos, :provisioner do
40
+ require_relative "config"
41
+ Config
42
+ end
43
+
44
+ provisioner :nixos do
45
+ require_relative "provisioner"
46
+ Provisioner
47
+ end
48
+
31
49
  end
50
+
32
51
  end
33
52
  end
@@ -0,0 +1,28 @@
1
+ module VagrantPlugins
2
+ module Nixos
3
+ class NixosConfigError < Vagrant::Errors::VagrantError
4
+ end
5
+
6
+ class Provisioner < Vagrant.plugin("2", :provisioner)
7
+ # This is the method called when the actual provisioning should be
8
+ # done. The communicator is guaranteed to be ready at this point,
9
+ # and any shared folders or networks are already setup.
10
+ #
11
+ # No return value is expected.
12
+ def provision
13
+ conf = if @config.inline
14
+ @config.inline
15
+ elsif @config.path
16
+ File.read(@config.path)
17
+ elsif @config.expression
18
+ "{config, pkgs, ...}: with pkgs; #{@config.expression}"
19
+ else
20
+ raise NixosConfigError, "Mising :path, :inline or :expression"
21
+ end
22
+ Nixos.write_config(machine, "vagrant-provision.nix", conf)
23
+ Nixos.rebuild!(machine, @config.NIX_PATH)
24
+ end
25
+ end
26
+ end
27
+ end
28
+
@@ -0,0 +1,156 @@
1
+ module VagrantPlugins
2
+ module Nixos
3
+ ##############################################
4
+ # Not sure if this is legit. But We'll use this module
5
+ # to store the state of the nix configuration and handle
6
+ # sending it to the machine.
7
+ #############################################
8
+
9
+ @@imports = {}
10
+
11
+ # Send file to machine and report if it changed
12
+ # See _write_config
13
+ def self.write_config(machine, filename, conf)
14
+ include_config(machine, filename)
15
+ _write_config(machine, filename, conf)
16
+ end
17
+
18
+ # mark a file that should be imported to the main config
19
+ def self.include_config(machine, filename)
20
+ if @@imports[machine.id].nil?
21
+ @@imports[machine.id] = {}
22
+ end
23
+ @@imports[machine.id][filename] = true
24
+ end
25
+
26
+ def imports
27
+ end
28
+
29
+ # rebuild the base vagrant.nix configuration
30
+ def self.rebuild(machine, nix_env=nil)
31
+ # build
32
+ conf = "{ config, pkgs, ... }:\n{"
33
+ # Add a mock provision file if it is missing as during boot the
34
+ # provisioning file may not be deployed yet.
35
+ if !machine.communicate.test("test -f /etc/nixos/vagrant-provision.nix")
36
+ _write_config(machine, "vagrant-provision.nix", "{ config, pkgs, ... }: {}")
37
+ end
38
+ # imports
39
+ conf_paths = if @@imports[machine.id].nil?
40
+ []
41
+ else
42
+ @@imports[machine.id].keys.inject([]) do |paths, filename|
43
+ paths << "./#{filename}"
44
+ end
45
+ end
46
+ conf_paths << "./vagrant-provision.nix"
47
+ # construct the nix module
48
+ conf << %{
49
+ imports = [
50
+ #{conf_paths.join("\n\t\t")}
51
+ ];
52
+ }
53
+ # default NIX_PATH
54
+ if nix_env
55
+ conf << %{
56
+ config.environment.shellInit = ''
57
+ export NIX_PATH=#{nix_env}:$NIX_PATH
58
+ '';
59
+ }
60
+ end
61
+ conf << "}"
62
+ # output / build the config
63
+ _write_config(machine, "vagrant.nix", conf)
64
+ rebuild!(machine, nix_env)
65
+ end
66
+
67
+ # just do nixos-rebuild
68
+ def self.rebuild!(machine, nix_env=nil)
69
+ rebuild_cmd = "nixos-rebuild switch"
70
+ rebuild_cmd = "NIX_PATH=#{nix_env}:$NIX_PATH #{rebuild_cmd}" if nix_env
71
+ machine.communicate.tap do |comm|
72
+ comm.sudo(rebuild_cmd)
73
+ end
74
+ end
75
+
76
+ def self.same?(machine, f1, f2)
77
+ machine.communicate.test("cmp --silent '#{f1}' '#{f2}'")
78
+ end
79
+
80
+ protected
81
+
82
+ # Send file to machine.
83
+ # Returns true if the uploaded file if different from any
84
+ # preexisting file, false if the file is indentical
85
+ def self._write_config(machine, filename, conf)
86
+ temp = Tempfile.new("vagrant")
87
+ temp.binmode
88
+ temp.write(conf)
89
+ temp.close
90
+ changed = true
91
+ machine.communicate.tap do |comm|
92
+ source = "/tmp/#{filename}"
93
+ target = "/etc/nixos/#{filename}"
94
+ comm.upload(temp.path, source)
95
+ if same?(machine, source, target)
96
+ changed = false
97
+ else
98
+ comm.sudo("mv '#{source}' '#{target}'")
99
+ end
100
+ end
101
+ return changed
102
+ end
103
+ end
104
+ end
105
+
106
+ #################################################
107
+ # Naughty extending of builtins.
108
+ # Add to_nix functions to convert ruby2nix (ish).
109
+ #################################################
110
+
111
+ module Nix
112
+ INDENT_STRING=" "
113
+ end
114
+
115
+ class Symbol
116
+ def to_nix(indent = 0)
117
+ to_s
118
+ end
119
+ end
120
+
121
+ class NilClass
122
+ def to_nix(indent = 0)
123
+ "null"
124
+ end
125
+ end
126
+
127
+ class Hash
128
+ def to_nix(indent = 0)
129
+ "{\n" +
130
+ sort {|a, b| a[0].to_s <=> b[0].to_s}.map do |key, value|
131
+ raise "Key must be a Symbol, not #{key.class}" unless key.is_a?(Symbol)
132
+ Nix::INDENT_STRING * (indent + 1)+ key.to_nix +
133
+ " = " + value.to_nix(indent + 1) + ";"
134
+ end.join("\n") + "\n" +
135
+ Nix::INDENT_STRING * indent + "}"
136
+ end
137
+ end
138
+
139
+ class Array
140
+ def to_nix(indent = 0)
141
+ "[ " + map(&:to_nix).join(" ") + " ]"
142
+ end
143
+ end
144
+
145
+ class String
146
+ def to_nix(indent = 0)
147
+ # TODO: escape ${var} in string
148
+ "''#{self}''"
149
+ end
150
+ end
151
+
152
+ class Fixnum
153
+ def to_nix(indent = 0)
154
+ to_s
155
+ end
156
+ end
@@ -1,5 +1,5 @@
1
1
  module VagrantPlugins
2
2
  module Nixos
3
- VERSION = "0.0.1"
3
+ VERSION = "0.0.2"
4
4
  end
5
5
  end
data/lib/vagrant-nixos.rb CHANGED
@@ -1,5 +1,6 @@
1
1
  require "vagrant-nixos/version"
2
2
  require "vagrant-nixos/plugin"
3
+ require "vagrant-nixos/util"
3
4
 
4
5
  module VagrantPlugins
5
6
  module Nixos
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: vagrant-nixos
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.1
4
+ version: 0.0.2
5
5
  prerelease:
6
6
  platform: ruby
7
7
  authors:
@@ -57,9 +57,13 @@ files:
57
57
  - README.md
58
58
  - Rakefile
59
59
  - lib/vagrant-nixos.rb
60
+ - lib/vagrant-nixos/cap/change_host_name.rb
60
61
  - lib/vagrant-nixos/cap/configure_networks.rb
62
+ - lib/vagrant-nixos/config.rb
61
63
  - lib/vagrant-nixos/guest.rb
62
64
  - lib/vagrant-nixos/plugin.rb
65
+ - lib/vagrant-nixos/provisioner.rb
66
+ - lib/vagrant-nixos/util.rb
63
67
  - lib/vagrant-nixos/version.rb
64
68
  - vagrant-nixos.gemspec
65
69
  homepage: ''
@@ -77,7 +81,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
77
81
  version: '0'
78
82
  segments:
79
83
  - 0
80
- hash: 867435977267935838
84
+ hash: -2672908896929741571
81
85
  required_rubygems_version: !ruby/object:Gem::Requirement
82
86
  none: false
83
87
  requirements:
@@ -86,7 +90,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
86
90
  version: '0'
87
91
  segments:
88
92
  - 0
89
- hash: 867435977267935838
93
+ hash: -2672908896929741571
90
94
  requirements: []
91
95
  rubyforge_project:
92
96
  rubygems_version: 1.8.23