├── .gitignore ├── .rspec ├── .rubocop.yml ├── .travis.yml ├── CHANGELOG.md ├── Gemfile ├── Guardfile ├── LICENSE.txt ├── README.md ├── Rakefile ├── lib ├── vagrant-timezone.rb └── vagrant-timezone │ ├── action │ └── set_timezone.rb │ ├── cap │ ├── debian.rb │ ├── gentoo.rb │ ├── linux.rb │ ├── redhat.rb │ ├── unix.rb │ └── windows.rb │ ├── config.rb │ ├── logger.rb │ ├── plugin.rb │ └── version.rb ├── locales └── en.yml ├── tasks └── acceptance.rake ├── test ├── acceptance │ ├── config │ │ ├── boxes.yaml │ │ └── vagrant-spec.config.rb │ ├── skeletons │ │ └── timezone │ │ │ └── Vagrantfile │ ├── support │ │ ├── box_helper.rb │ │ └── spec_helper.rb │ └── vagrant-timezone │ │ └── timezone_spec.rb ├── spec_helper.rb └── unit │ └── vagrant-timezone │ ├── cap │ └── windows_spec.rb │ └── config_spec.rb └── vagrant-timezone.gemspec /.gitignore: -------------------------------------------------------------------------------- 1 | /.bundle 2 | /Gemfile.lock 3 | *.gem 4 | /test/acceptance/boxes/ 5 | -------------------------------------------------------------------------------- /.rspec: -------------------------------------------------------------------------------- 1 | --color 2 | --default-path test 3 | --format documentation 4 | --order random 5 | --require spec_helper 6 | -------------------------------------------------------------------------------- /.rubocop.yml: -------------------------------------------------------------------------------- 1 | AllCops: 2 | DisplayStyleGuide: true 3 | ExtraDetails: true 4 | TargetRubyVersion: 2.2 5 | 6 | Layout/AlignHash: 7 | EnforcedHashRocketStyle: table 8 | EnforcedColonStyle: table 9 | 10 | Metrics/BlockLength: 11 | Exclude: 12 | - '*.gemspec' 13 | - '**/*.rake' 14 | - '**/*_spec.rb' 15 | 16 | Metrics/ClassLength: 17 | Severity: warning 18 | 19 | Metrics/LineLength: 20 | Max: 100 21 | Severity: warning 22 | 23 | Metrics/MethodLength: 24 | Max: 13 25 | Severity: warning 26 | 27 | Naming/FileName: 28 | Exclude: 29 | - 'lib/vagrant-timezone.rb' 30 | - '**/vagrant-spec.config.rb' 31 | 32 | Style/GuardClause: 33 | MinBodyLength: 3 34 | 35 | Style/NegatedIf: 36 | Enabled: false 37 | 38 | Style/TrailingCommaInArrayLiteral: 39 | Enabled: false 40 | 41 | Style/TrailingCommaInHashLiteral: 42 | Enabled: false 43 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: ruby 2 | cache: bundler 3 | sudo: false 4 | 5 | bundler_args: --without=development 6 | 7 | script: 8 | - bundle exec rake test:unit 9 | - bundle exec rake style 10 | 11 | env: 12 | global: 13 | - NOKOGIRI_USE_SYSTEM_LIBRARIES=true 14 | 15 | rvm: 2.4.4 16 | matrix: 17 | include: 18 | - env: VAGRANT_VERSION=v2.2.3 19 | - env: VAGRANT_VERSION=v2.0.4 20 | - env: 21 | - VAGRANT_VERSION=v1.9.8 22 | rvm: 2.2.5 23 | - env: VAGRANT_VERSION=master 24 | allow_failures: 25 | - env: VAGRANT_VERSION=master 26 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # 1.3.1 / _Not released yet_ 2 | 3 | 4 | # 1.3.0 / 2019-01-10 5 | 6 | - Use `timedatectl` on all Linux distributions if found ([GH-10][], [GH-7][], [GH-5][]) 7 | * `systemd` is now used on many Linux distros (e.g. Debian based ones) 8 | 9 | - Improve testing by adding acceptance and style tests ([GH-11][]) 10 | 11 | [GH-5]: https://github.com/tmatilai/vagrant-timezone/issues/5 "Issue 5" 12 | [GH-7]: https://github.com/tmatilai/vagrant-timezone/issues/7 "Issue 7" 13 | [GH-10]: https://github.com/tmatilai/vagrant-timezone/issues/10 "Issue 10" 14 | [GH-11]: https://github.com/tmatilai/vagrant-timezone/issues/11 "Issue 11" 15 | 16 | # 1.2.0 / 2017-02-25 17 | 18 | Features: 19 | 20 | - Support Windows guests ([GH-4][]) 21 | 22 | [GH-4]: https://github.com/tmatilai/vagrant-timezone/issues/4 "Issue 4" 23 | 24 | # 1.1.0 / 2015-10-01 25 | 26 | Features: 27 | 28 | - Add option (`config.timezone.value = :host`) to synchronize the guest timezone with the host ([GH-2][]) 29 | 30 | [GH-2]: https://github.com/tmatilai/vagrant-timezone/issues/2 "Issue 2" 31 | 32 | # 1.0.0 / 2014-10-27 33 | 34 | Features: 35 | 36 | - Support Arch, CoreOS, and Gentoo Linux 37 | - Support FreeBSD, NetBSD, and OpenBSD 38 | - Support OS X 39 | 40 | # 0.1.0 / 2014-10-26 41 | 42 | - Initial release 43 | - Support Debian and RedHat based distros 44 | -------------------------------------------------------------------------------- /Gemfile: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | source 'https://rubygems.org' 4 | 5 | gem 'vagrant', 6 | git: 'https://github.com/hashicorp/vagrant.git', 7 | tag: ENV.fetch('VAGRANT_VERSION', 'v2.2.3') 8 | 9 | gem 'rake' 10 | gem 'rspec', '~> 3.1' 11 | gem 'rubocop', '~> 0.62' 12 | 13 | gem 'vagrant-spec', 14 | git: 'https://github.com/hashicorp/vagrant-spec.git' 15 | 16 | group :development do 17 | gem 'guard-rspec', '~> 4.7' 18 | gem 'guard-rubocop', '~> 1.3' 19 | end 20 | 21 | group :plugins do 22 | gemspec 23 | end 24 | -------------------------------------------------------------------------------- /Guardfile: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | spec_dir = 'test/unit' 4 | 5 | group :ruby, halt_on_fail: true do 6 | guard :rspec, cmd: 'bundle exec rspec --format progress', spec_paths: [spec_dir] do 7 | # RSpec files 8 | watch('test/spec_helper.rb') { spec_dir } 9 | watch(%r{^#{spec_dir}/.+_spec\.rb$}) 10 | 11 | # Ruby files 12 | watch(%r{^lib/(.+)\.rb$}) { |m| "#{spec_dir}/#{m[1]}_spec.rb" } 13 | end 14 | 15 | guard :rubocop do 16 | watch(/\.rb$/) 17 | watch(/\.gemspec$/) 18 | watch('Gemfile') 19 | watch('Rakefile') 20 | watch(%r{/Vagrantfile$}) 21 | watch('.rubocop.yml') { '.' } 22 | end 23 | end 24 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | The MIT License 2 | 3 | Copyright (c) 2014-2019 Teemu Matilainen 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Time Zone Configuration Plugin for Vagrant 2 | 3 | [![Gem Version](https://badge.fury.io/rb/vagrant-timezone.png)][gem] 4 | [![Build Status](https://travis-ci.org/tmatilai/vagrant-timezone.png?branch=master)][travis] 5 | 6 | [gem]: https://rubygems.org/gems/vagrant-timezone 7 | [travis]: https://travis-ci.org/tmatilai/vagrant-timezone 8 | 9 | A [Vagrant](http://www.vagrantup.com/) plugin that configures the time zone of a virtual machines. 10 | 11 | If you want to use a specific time zone in a Vagrant VM, or if a third party base box comes with a non-standard time zone, this plugin is to the rescue. The configuration is done on `vagrant up` and `vagrant reload` actions. Note that no services are restarted automatically so they may keep using the old time zone information. 12 | 13 | ## Usage 14 | 15 | Install the plugin: 16 | 17 | ```sh 18 | vagrant plugin install vagrant-timezone 19 | ``` 20 | 21 | To configure time zone for all Vagrant VMs, add the following to _$HOME/.vagrant.d/Vagrantfile_ (or to a project specific _Vagrantfile_): 22 | 23 | ```ruby 24 | Vagrant.configure("2") do |config| 25 | if Vagrant.has_plugin?("vagrant-timezone") 26 | config.timezone.value = "UTC" 27 | end 28 | # ... other stuff 29 | end 30 | ``` 31 | 32 | The value can be anything that the [tz database supports](http://en.wikipedia.org/wiki/List_of_tz_database_time_zones) (the "TZ" column). For example "UTC" or "Europe/Helsinki". 33 | 34 | For Windows guests the value can be a name in [this table](https://msdn.microsoft.com/en-us/library/ms912391(v=winembedded.11).aspx), or "Etc/GMT``" (like with the `:host` value, see the next chapter). 35 | 36 | ### Matching the Host Timezone 37 | 38 | If the special symbol `:host` is passed at the parameter (`config.timezone.value = :host`), the plugin will attempt to set the guest timezone offset to match the current offset of the host. Because of limitations in Ruby's ability to get the named timezone from the host, it will instead convert the host's timezone offset to a calculated offset from UTC. So for example, on the west coast of the USA the calculated timezone might be `Etc/GMT+8`. After a change in the host's timezone (including a change due to Daylight Savings Time taking effect), the next time the Vagrantfile is run the guest clock will be updated to match. Note that this functionality has only been tested with an OS X host and Linux guest. 39 | 40 | ## Compatibility 41 | 42 | This plugin requires Vagrant 1.2 or newer ([downloads](https://www.vagrantup.com/downloads)). 43 | 44 | The plugin is supposed to be compatible with all Vagrant providers and other plugins. Please file an [issue](https://github.com/tmatilai/vagrant-timezone/issues) if this is not the case. 45 | 46 | At the moment the supported platforms include: 47 | 48 | - All Linux guests. Confirmed at least: 49 | * Arch 50 | * CoreOS 51 | * Debian (and derivatives) 52 | * Gentoo 53 | * RedHat (and derivatives) 54 | 55 | - All BSD guests: 56 | * FreeBSD 57 | * NetBSD 58 | * OpenBSD 59 | * OS X 60 | 61 | - Windows 62 | 63 | ## Development 64 | 65 | As Vagrant bundles Ruby, the same version should be used when developing this plugin. For example Vagrant 1.9 comes with Ruby 2.2. 66 | -------------------------------------------------------------------------------- /Rakefile: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'bundler/gem_tasks' 4 | require 'rspec/core/rake_task' 5 | require 'rubocop/rake_task' 6 | 7 | task default: ['test:unit', 'style'] 8 | 9 | desc 'Run all tests' 10 | task test: ['test:unit', 'test:acceptance'] 11 | 12 | namespace :test do 13 | desc 'Run unit tests' 14 | RSpec::Core::RakeTask.new(:unit) do |task| 15 | task.pattern = 'test/unit/**/*_spec.rb' 16 | end 17 | 18 | load 'tasks/acceptance.rake' 19 | end 20 | 21 | desc 'Run all style checks' 22 | task style: ['style:ruby'] 23 | 24 | namespace :style do 25 | desc 'Run style checks for Ruby' 26 | RuboCop::RakeTask.new(:ruby) 27 | end 28 | 29 | # Remove 'install' task as the gem is installed to Vagrant, not to system 30 | Rake::Task[:install].clear 31 | -------------------------------------------------------------------------------- /lib/vagrant-timezone.rb: -------------------------------------------------------------------------------- 1 | require_relative 'vagrant-timezone/version' 2 | require_relative 'vagrant-timezone/plugin' 3 | -------------------------------------------------------------------------------- /lib/vagrant-timezone/action/set_timezone.rb: -------------------------------------------------------------------------------- 1 | require_relative '../logger' 2 | 3 | module VagrantPlugins 4 | module TimeZone 5 | module Action 6 | # Vagrant middleware action that sets the specified time zone 7 | class SetTimeZone 8 | def initialize(app, _env) 9 | @app = app 10 | end 11 | 12 | def call(env) # rubocop:disable Metrics/AbcSize 13 | @app.call(env) 14 | 15 | machine = env[:machine] 16 | timezone = machine.config.timezone.value 17 | 18 | if timezone.nil? 19 | logger.info I18n.t('vagrant_timezone.not_enabled') 20 | elsif machine.guest.capability?(:change_timezone) 21 | env[:ui].info I18n.t('vagrant_timezone.configuring', zone: timezone) 22 | machine.guest.capability(:change_timezone, timezone) 23 | else 24 | logger.info I18n.t('vagrant_timezone.not_supported') 25 | end 26 | end 27 | 28 | # @return [Log4r::Logger] 29 | def logger 30 | TimeZone.logger 31 | end 32 | end 33 | end 34 | end 35 | end 36 | -------------------------------------------------------------------------------- /lib/vagrant-timezone/cap/debian.rb: -------------------------------------------------------------------------------- 1 | require_relative 'linux' 2 | 3 | module VagrantPlugins 4 | module TimeZone 5 | module Cap 6 | # Debian capabilities for changing time zone 7 | class Debian < Linux 8 | # Set the time zone if `timedatectl` is not found 9 | def self.change_timezone_generic(machine, timezone) 10 | machine.communicate.tap do |comm| 11 | comm.sudo("echo '#{timezone}' > /etc/timezone") 12 | comm.sudo('dpkg-reconfigure --frontend noninteractive tzdata') 13 | end 14 | end 15 | end 16 | end 17 | end 18 | end 19 | -------------------------------------------------------------------------------- /lib/vagrant-timezone/cap/gentoo.rb: -------------------------------------------------------------------------------- 1 | require_relative 'linux' 2 | 3 | module VagrantPlugins 4 | module TimeZone 5 | module Cap 6 | # Gentoo capabilities for changing time zone 7 | class Gentoo < Linux 8 | # Set the time zone if `timedatectl` is not found 9 | def self.change_timezone_generic(machine, timezone) 10 | machine.communicate.tap do |comm| 11 | super 12 | 13 | comm.sudo("echo '#{timezone}' > /etc/timezone") 14 | comm.sudo('emerge --config timezone-data') 15 | end 16 | end 17 | end 18 | end 19 | end 20 | end 21 | -------------------------------------------------------------------------------- /lib/vagrant-timezone/cap/linux.rb: -------------------------------------------------------------------------------- 1 | require_relative 'unix' 2 | 3 | module VagrantPlugins 4 | module TimeZone 5 | module Cap 6 | # Generic Linix capability for changing time zone 7 | class Linux 8 | # Set the time zone. 9 | # Uses `timedatectl` if found. 10 | def self.change_timezone(machine, timezone) 11 | if timedatectl?(machine) 12 | change_timezone_timedatectl(machine, timezone) 13 | else 14 | change_timezone_generic(machine, timezone) 15 | end 16 | end 17 | 18 | def self.timedatectl?(machine) 19 | machine.communicate.test('which timedatectl', sudo: true) 20 | end 21 | 22 | def self.change_timezone_timedatectl(machine, timezone) 23 | machine.communicate.sudo("timedatectl set-timezone '#{timezone}'") 24 | end 25 | 26 | # Set the time zone if `timedatectl` is not found. 27 | # Defaults to `Unix.change_timezone`, but is overridden by distro 28 | # specific classes. 29 | def self.change_timezone_generic(machine, timezone) 30 | Unix.change_timezone(machine, timezone) 31 | end 32 | end 33 | end 34 | end 35 | end 36 | -------------------------------------------------------------------------------- /lib/vagrant-timezone/cap/redhat.rb: -------------------------------------------------------------------------------- 1 | require_relative 'linux' 2 | 3 | module VagrantPlugins 4 | module TimeZone 5 | module Cap 6 | # RedHat capabilities for changing time zone 7 | class RedHat < Linux 8 | # Set the time zone if `timedatectl` is not found 9 | def self.change_timezone_generic(machine, timezone) 10 | super 11 | 12 | machine.communicate.sudo( 13 | "echo 'ZONE=\"#{timezone}\"' > /etc/sysconfig/clock" 14 | ) 15 | end 16 | end 17 | end 18 | end 19 | end 20 | -------------------------------------------------------------------------------- /lib/vagrant-timezone/cap/unix.rb: -------------------------------------------------------------------------------- 1 | module VagrantPlugins 2 | module TimeZone 3 | module Cap 4 | # Generic *nix capabilities for changing time zone 5 | class Unix 6 | # Set the time zone 7 | def self.change_timezone(machine, timezone) 8 | machine.communicate.sudo( 9 | "ln -sf /usr/share/zoneinfo/#{timezone} /etc/localtime", 10 | shell: 'sh' 11 | ) 12 | end 13 | end 14 | end 15 | end 16 | end 17 | -------------------------------------------------------------------------------- /lib/vagrant-timezone/cap/windows.rb: -------------------------------------------------------------------------------- 1 | module VagrantPlugins 2 | module TimeZone 3 | module Cap 4 | # Generic Windows capabilities for changing time zone 5 | class Windows 6 | # Set the time zone 7 | def self.change_timezone(machine, timezone) 8 | machine.communicate.sudo( 9 | "tzutil /s \"#{timezone_name(timezone)}\"", 10 | shell: 'powershell' 11 | ) 12 | end 13 | 14 | # If the specified timezone is in format like "Etc/GMT+8", returns the 15 | # matching timezone name. Otherwise just returns the passed timezone. 16 | def self.timezone_name(timezone) 17 | if %r{^Etc/GMT(?[+-]\d+)$} =~ timezone 18 | TIMEZONE_NAMES.fetch(offset) 19 | else 20 | timezone 21 | end 22 | end 23 | 24 | TIMEZONE_NAMES = { 25 | '-12' => 'Dateline Standard Time', 26 | '-11' => 'UTC-11', 27 | '-10' => 'Hawaiian Standard Time', 28 | '-9' => 'Alaskan Standard Time', 29 | '-8' => 'Pacific Standard Time', 30 | '-7' => 'Mountain Standard Time', 31 | '-6' => 'Central Standard Time', 32 | '-5' => 'Eastern Standard Time', 33 | '-4' => 'Atlantic Standard Time', 34 | '-3' => 'Greenland Standard Time', 35 | '-2' => 'UTC-02', 36 | '-1' => 'Azores Standard Time', 37 | '+0' => 'UTC', 38 | '+1' => 'Central Europe Standard Time', 39 | '+2' => 'E. Europe Standard Time', 40 | '+3' => 'Russian Standard Time', 41 | '+4' => 'Arabian Standard Time', 42 | '+5' => 'Pakistan Standard Time', 43 | '+6' => 'Central Asia Standard Time', 44 | '+7' => 'SE Asia Standard Time', 45 | '+8' => 'China Standard Time', 46 | '+9' => 'Tokyo Standard Time', 47 | '+10' => 'E. Australia Standard Time', 48 | '+11' => 'Central Pacific Standard Time' 49 | }.freeze 50 | end 51 | end 52 | end 53 | end 54 | -------------------------------------------------------------------------------- /lib/vagrant-timezone/config.rb: -------------------------------------------------------------------------------- 1 | require 'vagrant' 2 | 3 | module VagrantPlugins 4 | module TimeZone 5 | # Configuration for the Time Zone plugin 6 | # 7 | # @!parse class Config < Vagrant::Plugin::V2::Config; end 8 | class Config < Vagrant.plugin('2', :config) 9 | attr_accessor :value 10 | 11 | def initialize 12 | super 13 | 14 | @value = UNSET_VALUE 15 | end 16 | 17 | def finalize! 18 | super 19 | 20 | if @value == UNSET_VALUE 21 | @value = nil 22 | elsif @value == :host 23 | # Get the offset of the current timezone of the host. Ruby doesn't reliably 24 | # detect the named timezone, so we have to use the hour offset. Note that when 25 | # DST changes, etc, this offset will change. 26 | 27 | # We set timezone offset negative to match POSIX standards 28 | # https://github.com/eggert/tz/blob/master/etcetera 29 | @value = format('Etc/GMT%+d', -(Time.now.utc_offset / 3600)) 30 | end 31 | end 32 | end 33 | end 34 | end 35 | -------------------------------------------------------------------------------- /lib/vagrant-timezone/logger.rb: -------------------------------------------------------------------------------- 1 | require 'log4r' 2 | 3 | module VagrantPlugins 4 | # Base module for Vagrant TimeZone plugin 5 | module TimeZone 6 | # @return [Log4r::Logger] the logger instance for this plugin 7 | def self.logger 8 | @logger ||= Log4r::Logger.new('vagrant::timezone') 9 | end 10 | end 11 | end 12 | -------------------------------------------------------------------------------- /lib/vagrant-timezone/plugin.rb: -------------------------------------------------------------------------------- 1 | require 'vagrant' 2 | require_relative 'logger' 3 | 4 | module VagrantPlugins 5 | module TimeZone 6 | # Vagrant Plugin class that registers configs, hooks, etc. 7 | # 8 | # @!parse class Plugin < Vagrant::Plugin::V2::Plugin; end 9 | class Plugin < Vagrant.plugin('2') 10 | # Compatible Vagrant versions 11 | VAGRANT_VERSION_REQUIREMENT = '>= 1.2.0'.freeze 12 | 13 | # Returns true if the Vagrant version fulfills the requirements 14 | # 15 | # @param requirements [String, Array] the version requirement 16 | # @return [Boolean] 17 | def self.check_vagrant_version(*requirements) 18 | Gem::Requirement.new(*requirements).satisfied_by?( 19 | Gem::Version.new(Vagrant::VERSION) 20 | ) 21 | end 22 | 23 | # Verifies that the Vagrant version fulfills the requirements 24 | # 25 | # @raise [VagrantPlugins::TimeZone::VagrantVersionError] if this plugin 26 | # is incompatible with the Vagrant version 27 | def self.check_vagrant_version! 28 | return if check_vagrant_version(VAGRANT_VERSION_REQUIREMENT) 29 | 30 | msg = I18n.t( 31 | 'vagrant_timezone.errors.vagrant_version', 32 | requirement: VAGRANT_VERSION_REQUIREMENT.inspect 33 | ) 34 | warn msg 35 | raise msg 36 | end 37 | 38 | # Initializes the internationalization strings 39 | def self.setup_i18n 40 | I18n.load_path << File.expand_path('../../locales/en.yml', __dir__) 41 | I18n.reload! 42 | end 43 | 44 | setup_i18n 45 | check_vagrant_version! 46 | 47 | name 'vagrant-timezone' 48 | 49 | config 'timezone' do 50 | require_relative 'config' 51 | Config 52 | end 53 | 54 | action_hook 'timezone_configure', :machine_action_up do |hook| 55 | require_relative 'action/set_timezone' 56 | hook.after Vagrant::Action::Builtin::Provision, Action::SetTimeZone 57 | end 58 | 59 | action_hook 'timezone_configure', :machine_action_reload do |hook| 60 | require_relative 'action/set_timezone' 61 | hook.after Vagrant::Action::Builtin::Provision, Action::SetTimeZone 62 | end 63 | 64 | guest_capability 'bsd', 'change_timezone' do 65 | require_relative 'cap/unix' 66 | Cap::Unix 67 | end 68 | 69 | guest_capability 'debian', 'change_timezone' do 70 | require_relative 'cap/debian' 71 | Cap::Debian 72 | end 73 | 74 | guest_capability 'gentoo', 'change_timezone' do 75 | require_relative 'cap/gentoo' 76 | Cap::Gentoo 77 | end 78 | 79 | guest_capability 'linux', 'change_timezone' do 80 | require_relative 'cap/linux' 81 | Cap::Linux 82 | end 83 | 84 | guest_capability 'redhat', 'change_timezone' do 85 | require_relative 'cap/redhat' 86 | Cap::RedHat 87 | end 88 | 89 | guest_capability 'windows', 'change_timezone' do 90 | require_relative 'cap/windows' 91 | Cap::Windows 92 | end 93 | end 94 | end 95 | end 96 | -------------------------------------------------------------------------------- /lib/vagrant-timezone/version.rb: -------------------------------------------------------------------------------- 1 | module VagrantPlugins 2 | module TimeZone 3 | VERSION = '1.3.1.dev'.freeze 4 | end 5 | end 6 | -------------------------------------------------------------------------------- /locales/en.yml: -------------------------------------------------------------------------------- 1 | en: 2 | vagrant_timezone: 3 | not_enabled: "Time zone not configured" 4 | not_supported: "Setting time zone not supported" 5 | configuring: "Setting time zone to '%{zone}'..." 6 | 7 | errors: 8 | vagrant_version: |- 9 | vagrant-timezone plugin requires Vagrant version %{requirement} 10 | -------------------------------------------------------------------------------- /tasks/acceptance.rake: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require_relative '../test/acceptance/support/box_helper' 4 | 5 | desc 'Run all acceptance tests' 6 | task acceptance: 'acceptance:all' 7 | 8 | namespace :acceptance do 9 | include VagrantTimezone::BoxHelper 10 | 11 | task all: providers 12 | 13 | providers.each do |provider| 14 | desc "Run all acceptance tests for #{provider}" 15 | task provider => provider.boxes.map { |box| "#{provider}:#{box}" } 16 | 17 | namespace provider.name do 18 | provider.boxes.each do |box| 19 | desc "Run acceptance tests for #{box} on #{provider}" 20 | task box.name => box.path do 21 | env = { 22 | 'VAGRANT_SPEC_PROVIDER' => provider.name, 23 | 'VAGRANT_SPEC_BOX_PATH' => box.path.to_s 24 | } 25 | cmd = ['bundle', 'exec', 'vagrant-spec', 'test'] 26 | cmd << "--config=#{config_path('vagrant-spec.config.rb')}" 27 | 28 | rake_output_message "Running acceptance tests for #{box} on #{provider}" 29 | rake_output_message cmd.join(' ') 30 | system(env, *cmd) 31 | end 32 | 33 | file box.path => provider.box_dir do 34 | rake_output_message "Downloading #{box} for #{provider}" 35 | download(box) 36 | end 37 | 38 | directory provider.box_dir 39 | end 40 | end 41 | end 42 | end 43 | -------------------------------------------------------------------------------- /test/acceptance/config/boxes.yaml: -------------------------------------------------------------------------------- 1 | virtualbox: 2 | - ubuntu/bionic64 3 | - debian/wheezy64 4 | - centos/7 5 | - archlinux/archlinux 6 | - generic/gentoo 7 | - generic/freebsd12 8 | - generic/openbsd6 9 | -------------------------------------------------------------------------------- /test/acceptance/config/vagrant-spec.config.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require_relative '../support/spec_helper.rb' 4 | 5 | Vagrant::Spec::Acceptance.configure do |c| 6 | provider = ENV.fetch('VAGRANT_SPEC_PROVIDER') 7 | box_path = ENV.fetch('VAGRANT_SPEC_BOX_PATH') 8 | 9 | c.component_paths = [File.expand_path('../vagrant-timezone', __dir__)] 10 | c.skeleton_paths = [File.expand_path('../skeletons', __dir__)] 11 | 12 | c.provider(provider, box_path: box_path) 13 | end 14 | -------------------------------------------------------------------------------- /test/acceptance/skeletons/timezone/Vagrantfile: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | timezone = ENV['VAGRANT_SPEC_TIMEZONE'] 4 | timezone = :host if timezone == ':host' 5 | 6 | Vagrant.configure('2') do |config| 7 | config.vm.box = 'box' 8 | 9 | config.timezone.value = timezone if timezone 10 | end 11 | -------------------------------------------------------------------------------- /test/acceptance/support/box_helper.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'cgi' 4 | require 'fileutils' 5 | require 'json' 6 | require 'open-uri' 7 | require 'pathname' 8 | require 'tempfile' 9 | require 'yaml' 10 | 11 | module VagrantTimezone 12 | module BoxHelper 13 | ROOT_DIR = Pathname.new(__dir__).join('..') 14 | BOX_DIR = ROOT_DIR.join('boxes') 15 | CONFIG_DIR = ROOT_DIR.join('config') 16 | 17 | def config_path(file_name) 18 | CONFIG_DIR.join(file_name) 19 | end 20 | 21 | def providers 22 | @providers ||= begin 23 | data = IO.read(config_path('boxes.yaml')) 24 | YAML.safe_load(data).map do |provider, boxes| 25 | Provider.new(provider, boxes) 26 | end 27 | end 28 | end 29 | 30 | def download(box) 31 | Downloader.download(box) 32 | end 33 | 34 | class Provider 35 | attr_reader :name, :boxes 36 | 37 | def initialize(name, box_names) 38 | @name = name 39 | @boxes = box_names.map { |box| Box.new(box, self) } 40 | end 41 | 42 | alias to_s name 43 | 44 | def box_dir 45 | @box_dir ||= BOX_DIR.join(name) 46 | end 47 | end 48 | 49 | class Box 50 | attr_reader :name, :provider 51 | 52 | def initialize(name, provider) 53 | %r{^[^/]+/[^/]+$}.match(name) || 54 | raise(ArgumentError, 'Box names must be in form of /') 55 | 56 | @name = name 57 | @provider = provider 58 | end 59 | 60 | alias to_s name 61 | 62 | def path 63 | @path ||= provider.box_dir.join("#{sanitized_name}.box") 64 | end 65 | 66 | def sanitized_name 67 | name.tr('/', '_') 68 | end 69 | 70 | def metadata_url 71 | user, box = name.split('/') 72 | URI("https://app.vagrantup.com/#{CGI.escape(user)}/boxes/#{box}.json") 73 | end 74 | end 75 | 76 | class Downloader 77 | def self.download(box) 78 | metadata = download_metadata(box) 79 | 80 | url = find_url(metadata, box.provider) 81 | raise StandardError, "No active versions found for #{box} for #{box.provider}" if !url 82 | 83 | download_box(box, url) 84 | end 85 | 86 | def self.download_metadata(box) 87 | data = box.metadata_url.open.read 88 | JSON.parse(data) 89 | end 90 | 91 | def self.download_box(box, url) 92 | case io = url.open 93 | when Tempfile 94 | io.close 95 | FileUtils.mv(io.path, box.path) 96 | else 97 | IO.copy_stream(io, box.path) 98 | end 99 | end 100 | 101 | def self.find_url(metadata, provider) 102 | metadata['versions'].each do |v| 103 | next if v['status'] != 'active' 104 | 105 | v['providers'].each do |p| 106 | return URI(p['url']) if p['name'] == provider.name 107 | end 108 | end 109 | end 110 | end 111 | end 112 | end 113 | -------------------------------------------------------------------------------- /test/acceptance/support/spec_helper.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'vagrant-spec/acceptance' 4 | 5 | # Dirty monkey patch to work around 6 | # https://github.com/hashicorp/vagrant-spec/pull/17 7 | module Vagrant 8 | module Spec 9 | class AcceptanceIsolatedEnvironment 10 | attr_reader :env if !method_defined?(:env) 11 | end 12 | end 13 | end 14 | 15 | # Disable Vagrant's version checks 16 | ENV['VAGRANT_CHECKPOINT_DISABLE'] = '1' 17 | -------------------------------------------------------------------------------- /test/acceptance/vagrant-timezone/timezone_spec.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | shared_examples 'provider/timezone' do |provider, options| 4 | if !options[:box_path] 5 | raise ArgumentError, 6 | "'box_path' option must be specified for provider: #{provider}" 7 | end 8 | 9 | include_context 'acceptance' 10 | 11 | if provider.to_s == 'virtualbox' 12 | let(:extra_env) do 13 | # Special magic for VirtualBox 14 | { 'VBOX_USER_HOME' => '{{homedir}}' } 15 | end 16 | end 17 | 18 | before do 19 | environment.skeleton('timezone') 20 | environment.env['VAGRANT_SPEC_TIMEZONE'] = timezone 21 | 22 | assert_execute('vagrant', 'box', 'add', 'box', options[:box_path]) 23 | end 24 | 25 | after do 26 | execute('vagrant', 'destroy', '--force') 27 | end 28 | 29 | context 'when starting' do 30 | context 'without configuration' do 31 | let(:timezone) { nil } 32 | 33 | it 'does not configure the time zone' do 34 | result = assert_execute('vagrant', 'up', "--provider=#{provider}") 35 | expect(result.stdout).not_to match(/Setting time zone/) 36 | end 37 | end 38 | 39 | context 'with a time zone name' do 40 | # Something without DST so that the offset is always known 41 | let(:timezone) { 'America/Argentina/Buenos_Aires' } 42 | 43 | it 'configures the specified time zone' do 44 | result = assert_execute('vagrant', 'up', "--provider=#{provider}") 45 | expect(result.stdout).to match(/Setting time zone to '#{timezone}'/) 46 | 47 | result = assert_execute('vagrant', 'ssh', '-c', 'date "+TZ: %z"') 48 | expect(result.stdout).to match(/^TZ: -0300$/) 49 | end 50 | end 51 | end 52 | 53 | context 'when reloading' do 54 | context 'with :host' do 55 | # First configure to something else than the host 56 | let(:timezone) do 57 | offset = host_offset[0, 3].to_i 58 | offset = 10 if offset.zero? 59 | # Negate the host offset! 60 | # https://github.com/eggert/tz/blob/master/etcetera 61 | Kernel.format('Etc/GMT%+d', offset) 62 | end 63 | 64 | let(:host_offset) { `date "+%z"`.chomp } 65 | 66 | it 'configures the same time zone than on the host' do 67 | status('First start the machine with other timezone') 68 | assert_execute('vagrant', 'up', "--provider=#{provider}") 69 | 70 | # Change the configuration in Vagrantfile 71 | environment.env['VAGRANT_SPEC_TIMEZONE'] = ':host' 72 | 73 | status('Reloading with :host') 74 | result = assert_execute('vagrant', 'reload') 75 | expect(result.stdout).to match(%r{Setting time zone to 'Etc/GMT}) 76 | 77 | result = assert_execute('vagrant', 'ssh', '-c', 'date "+TZ: %z"') 78 | expect(result.stdout).to match(/^TZ: #{Regexp.escape(host_offset)}$/) 79 | end 80 | end 81 | end 82 | end 83 | -------------------------------------------------------------------------------- /test/spec_helper.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | $LOAD_PATH.unshift File.expand_path('../lib', __dir__) 4 | -------------------------------------------------------------------------------- /test/unit/vagrant-timezone/cap/windows_spec.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'vagrant-timezone/cap/windows' 4 | 5 | describe VagrantPlugins::TimeZone::Cap::Windows do 6 | subject { described_class } 7 | 8 | describe '.timezone_name' do 9 | context 'with Etc/GMT format' do 10 | it 'returns the timezone name for negative offset' do 11 | expect(subject.timezone_name('Etc/GMT-10')).to eq 'Hawaiian Standard Time' 12 | end 13 | 14 | it 'returns the timezone name for positive offset' do 15 | expect(subject.timezone_name('Etc/GMT+10')).to eq 'E. Australia Standard Time' 16 | end 17 | end 18 | 19 | context 'with unknown format' do 20 | it 'returns the specied timezone unchanged' do 21 | expect(subject.timezone_name('UTC')).to eq 'UTC' 22 | end 23 | end 24 | end 25 | end 26 | -------------------------------------------------------------------------------- /test/unit/vagrant-timezone/config_spec.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'vagrant-timezone/config' 4 | 5 | describe VagrantPlugins::TimeZone::Config do 6 | subject { described_class.new } 7 | 8 | describe '#value' do 9 | it 'defaults to nil' do 10 | subject.finalize! 11 | expect(subject.value).to be(nil) 12 | end 13 | 14 | it 'can be set' do 15 | subject.value = 'FOO' 16 | subject.finalize! 17 | expect(subject.value).to eq('FOO') 18 | end 19 | 20 | it 'can be set' do 21 | subject.value = 'FOO' 22 | subject.finalize! 23 | expect(subject.value).to eq('FOO') 24 | end 25 | 26 | it 'converts `:host` to the zone of the host' do 27 | allow(Time).to receive(:now).and_return(Time.new(2016, 7, 10, 2, 12, 0, '+03:00')) 28 | subject.value = :host 29 | subject.finalize! 30 | expect(subject.value).to eq('Etc/GMT-3') 31 | end 32 | end 33 | end 34 | -------------------------------------------------------------------------------- /vagrant-timezone.gemspec: -------------------------------------------------------------------------------- 1 | lib = File.expand_path('lib', __dir__) 2 | $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib) 3 | require 'vagrant-timezone/version' 4 | 5 | Gem::Specification.new do |spec| 6 | spec.name = 'vagrant-timezone' 7 | spec.version = VagrantPlugins::TimeZone::VERSION 8 | spec.description = 'A Vagrant plugin that configures the time zone of a virtual machine' 9 | spec.summary = spec.description 10 | spec.homepage = 'http://github.com/tmatilai/vagrant-timezone' 11 | spec.license = 'MIT' 12 | 13 | spec.authors = [ 14 | 'Teemu Matilainen', 15 | 'Kyle Corbitt', 16 | 'Robert R. Meyer' 17 | ] 18 | spec.email = [ 19 | 'teemu.matilainen@iki.fi', 20 | 'kyle@corbt.com', 21 | 'Blue.Dog.Archolite@gmail.com' 22 | ] 23 | 24 | spec.files = `git ls-files`.split($INPUT_RECORD_SEPARATOR) 25 | spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) } 26 | spec.test_files = spec.files.grep(%r{^(test|spec|features)/}) 27 | spec.require_paths = ['lib'] 28 | end 29 | --------------------------------------------------------------------------------