Merge remote-tracking branch 'upstream/master'
This commit is contained in:
commit
3524eb9ef4
12
CHANGELOG.md
12
CHANGELOG.md
|
@ -2,10 +2,22 @@
|
|||
|
||||
FEATURES:
|
||||
|
||||
- providers/virtualbox: Virtualbox 5.2 support [GH-8955]
|
||||
|
||||
IMPROVEMENTS:
|
||||
|
||||
- core: Clear POSIXLY_CORRECT when using optparse [GH-8685]
|
||||
- docs: Add auto_start_action and auto_stop_action to docs. [GH-9029]
|
||||
- provisioners/chef: Handle chef provisioner reboot request [GH-8874]
|
||||
- provisioners/shell: Use ui.detail for displaying output [GH-8983]
|
||||
|
||||
BUG FIXES:
|
||||
|
||||
- core: Rescue more exceptions when checking if port is open [GH-8517]
|
||||
- guests/windows: Split out cygwin path helper for msys2/cygwin paths and ensure cygpath exists [GH-8972]
|
||||
- providers/hyper-v: Properly invoke Auto stop action [GH-9000]
|
||||
- virtualbox/synced_folders: Allow synced folders to contain spaces in the guest path [GH-8995]
|
||||
|
||||
## 2.0.0 (September 7, 2017)
|
||||
|
||||
IMPROVEMENTS:
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
# Vagrant
|
||||
|
||||
* Website: [https://www.vagrantup.com/](https://www.vagrantup.com/)
|
||||
* Source: [https://github.com/mitchellh/vagrant](https://github.com/mitchellh/vagrant)
|
||||
* Source: [https://github.com/hashicorp/vagrant](https://github.com/hashicorp/vagrant)
|
||||
* [![Gitter chat](https://badges.gitter.im/mitchellh/vagrant.png)](https://gitter.im/mitchellh/vagrant)
|
||||
* Mailing list: [Google Groups](https://groups.google.com/group/vagrant-up)
|
||||
|
||||
|
@ -49,7 +49,7 @@ and you're welcome to give it a shot. Please review the installation page [here]
|
|||
|
||||
## Contributing to Vagrant
|
||||
|
||||
To install Vagrant from source, please [follow the guide in the Wiki](https://github.com/mitchellh/vagrant/wiki/Installing-Vagrant-from-Source).
|
||||
To install Vagrant from source, please [follow the guide in the Wiki](https://github.com/hashicorp/vagrant/wiki/Installing-Vagrant-from-Source).
|
||||
|
||||
You can run the test suite with:
|
||||
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
Cmnd_Alias VAGRANT_EXPORTS_ADD = /usr/bin/tee -a /etc/exports
|
||||
Cmnd_Alias VAGRANT_NFSD_CHECK = /sbin/service nfsserver status
|
||||
Cmnd_Alias VAGRANT_NFSD_START = /sbin/service nfsserver start
|
||||
Cmnd_Alias VAGRANT_NFSD_APPLY = /usr/sbin/exportfs -ar
|
||||
Cmnd_Alias VAGRANT_EXPORTS_REMOVE = /usr/bin/sed -r -e * d -ibak /*/exports
|
||||
Cmnd_Alias VAGRANT_EXPORTS_REMOVE_2 = /usr/bin/cp /*/exports /etc/exports
|
||||
%vagrant ALL=(root) NOPASSWD: VAGRANT_EXPORTS_ADD, VAGRANT_NFSD_CHECK, VAGRANT_NFSD_START, VAGRANT_NFSD_APPLY, VAGRANT_EXPORTS_REMOVE, VAGRANT_EXPORTS_REMOVE_2
|
||||
Cmnd_Alias VAGRANT_CHOWN = /usr/bin/chown 0\:0 /tmp/vagrant[a-z0-9-]*
|
||||
Cmnd_Alias VAGRANT_MV = /usr/bin/mv -f /tmp/vagrant[a-z0-9-]* /etc/exports
|
||||
Cmnd_Alias VAGRANT_START = /sbin/service nfsserver start
|
||||
Cmnd_Alias VAGRANT_STATUS = /sbin/service nfsserver status
|
||||
Cmnd_Alias VAGRANT_APPLY = /usr/sbin/exportfs -ar
|
||||
%vagrant ALL=(root) NOPASSWD: VAGRANT_CHOWN, VAGRANT_MV, VAGRANT_START, VAGRANT_STATUS, VAGRANT_APPLY
|
||||
|
|
|
@ -43,6 +43,9 @@ module Vagrant
|
|||
# If this method returns `nil`, then you should assume that help
|
||||
# was printed and parsing failed.
|
||||
def parse_options(opts=nil)
|
||||
# make sure optparse doesn't use POSIXLY_CORRECT parsing
|
||||
ENV["POSIXLY_CORRECT"] = nil
|
||||
|
||||
# Creating a shallow copy of the arguments so the OptionParser
|
||||
# doesn't destroy the originals.
|
||||
argv = @argv.dup
|
||||
|
|
|
@ -30,7 +30,8 @@ module Vagrant
|
|||
return true
|
||||
end
|
||||
rescue Timeout::Error, Errno::ECONNREFUSED, Errno::EHOSTUNREACH, \
|
||||
Errno::ENETUNREACH, Errno::EACCES, Errno::ENOTCONN
|
||||
Errno::ENETUNREACH, Errno::EACCES, Errno::ENOTCONN, \
|
||||
Errno::EADDRNOTAVAIL
|
||||
# Any of the above exceptions signal that the port is closed.
|
||||
return false
|
||||
end
|
||||
|
|
|
@ -98,21 +98,20 @@ module Vagrant
|
|||
end
|
||||
|
||||
# This takes any path and converts it from a Windows path to a
|
||||
# Cygwin or msys style path.
|
||||
# Cygwin style path.
|
||||
#
|
||||
# @param [String] path
|
||||
# @return [String]
|
||||
def cygwin_path(path)
|
||||
begin
|
||||
# We have to revert to the old env
|
||||
# path here, otherwise it looks like
|
||||
# msys2 ends up using the wrong cygpath
|
||||
# binary and ends up with a `/cygdrive`
|
||||
# when it doesn't exist in msys2
|
||||
original_path_env = ENV['PATH']
|
||||
ENV['PATH'] = ENV['VAGRANT_OLD_ENV_PATH']
|
||||
cygpath = Vagrant::Util::Which.which("cygpath")
|
||||
cygpath.gsub!("/", '\\')
|
||||
if cygpath.nil?
|
||||
# If Which can't find it, just attempt to invoke it directly
|
||||
cygpath = "cygpath"
|
||||
else
|
||||
cygpath.gsub!("/", '\\')
|
||||
end
|
||||
|
||||
process = Subprocess.execute(
|
||||
cygpath, "-u", "-a", path.to_s)
|
||||
return process.stdout.chomp
|
||||
|
@ -125,14 +124,29 @@ module Vagrant
|
|||
"--norc",
|
||||
"-c", "cd #{Shellwords.escape(path)} && pwd")
|
||||
return process.stdout.chomp
|
||||
end
|
||||
end
|
||||
|
||||
# This takes any path and converts it from a Windows path to a
|
||||
# msys style path.
|
||||
#
|
||||
# @param [String] path
|
||||
# @return [String]
|
||||
def msys_path(path)
|
||||
begin
|
||||
# We have to revert to the old env
|
||||
# path here, otherwise it looks like
|
||||
# msys2 ends up using the wrong cygpath
|
||||
# binary and ends up with a `/cygdrive`
|
||||
# when it doesn't exist in msys2
|
||||
original_path_env = ENV['PATH']
|
||||
ENV['PATH'] = ENV['VAGRANT_OLD_ENV_PATH']
|
||||
cygwin_path(path)
|
||||
ensure
|
||||
ENV['PATH'] = original_path_env
|
||||
end
|
||||
end
|
||||
|
||||
# Identical to cygwin_path for now
|
||||
alias_method :msys_path, :cygwin_path
|
||||
|
||||
# This takes any path and converts it to a full-length Windows
|
||||
# path on Windows machines in Cygwin.
|
||||
#
|
||||
|
|
|
@ -32,6 +32,11 @@ if (ShuttingDown) {
|
|||
exit 2
|
||||
}
|
||||
|
||||
if ($LASTEXITCODE -eq 1115) {
|
||||
# A system shutdown is in progress
|
||||
exit 2
|
||||
}
|
||||
|
||||
# Remove the pending reboot we just created above
|
||||
if ($LASTEXITCODE -eq 0) {
|
||||
. shutdown.exe -a
|
||||
|
|
|
@ -115,7 +115,7 @@ if ($auto_start_action) {
|
|||
}
|
||||
|
||||
if ($auto_stop_action) {
|
||||
Set-VM -VM $vmConfig.VM -AutomaticStartAction $auto_stop_action
|
||||
Set-VM -VM $vmConfig.VM -AutomaticStopAction $auto_stop_action
|
||||
}
|
||||
|
||||
# Only set EFI secure boot for Gen 2 machines, not gen 1
|
||||
|
|
|
@ -62,6 +62,7 @@ module VagrantPlugins
|
|||
"4.3" => Version_4_3,
|
||||
"5.0" => Version_5_0,
|
||||
"5.1" => Version_5_1,
|
||||
"5.2" => Version_5_2,
|
||||
}
|
||||
|
||||
if @@version.start_with?("4.2.14")
|
||||
|
|
|
@ -0,0 +1,16 @@
|
|||
require File.expand_path("../version_5_1", __FILE__)
|
||||
|
||||
module VagrantPlugins
|
||||
module ProviderVirtualBox
|
||||
module Driver
|
||||
# Driver for VirtualBox 5.2.x
|
||||
class Version_5_2 < Version_5_1
|
||||
def initialize(uuid)
|
||||
super
|
||||
|
||||
@logger = Log4r::Logger.new("vagrant::provider::virtualbox_5_2")
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -57,6 +57,7 @@ module VagrantPlugins
|
|||
autoload :Version_4_3, File.expand_path("../driver/version_4_3", __FILE__)
|
||||
autoload :Version_5_0, File.expand_path("../driver/version_5_0", __FILE__)
|
||||
autoload :Version_5_1, File.expand_path("../driver/version_5_1", __FILE__)
|
||||
autoload :Version_5_2, File.expand_path("../driver/version_5_2", __FILE__)
|
||||
end
|
||||
|
||||
module Model
|
||||
|
|
|
@ -85,7 +85,7 @@ module VagrantPlugins
|
|||
end
|
||||
|
||||
def os_friendly_id(id)
|
||||
id.gsub(/[\/\\]/,'_').sub(/^_/, '')
|
||||
id.gsub(/[\s\/\\]/,'_').sub(/^_/, '')
|
||||
end
|
||||
|
||||
# share_folders sets up the shared folder definitions on the
|
||||
|
|
|
@ -73,35 +73,42 @@ module VagrantPlugins
|
|||
@machine.ui.warn(I18n.t("vagrant.chef_run_list_empty"))
|
||||
end
|
||||
|
||||
if @machine.guest.capability?(:wait_for_reboot)
|
||||
@machine.guest.capability(:wait_for_reboot)
|
||||
end
|
||||
|
||||
command = CommandBuilder.command(:client, @config,
|
||||
windows: windows?,
|
||||
colored: @machine.env.ui.color?,
|
||||
)
|
||||
|
||||
|
||||
still_active = 259 #provisioner has asked chef to reboot
|
||||
|
||||
@config.attempts.times do |attempt|
|
||||
if attempt == 0
|
||||
@machine.ui.info I18n.t("vagrant.provisioners.chef.running_client")
|
||||
else
|
||||
@machine.ui.info I18n.t("vagrant.provisioners.chef.running_client_again")
|
||||
exit_status = 0
|
||||
while exit_status == 0 || exit_status == still_active
|
||||
if @machine.guest.capability?(:wait_for_reboot)
|
||||
@machine.guest.capability(:wait_for_reboot)
|
||||
elsif attempt > 0
|
||||
sleep 10
|
||||
@machine.communicate.wait_for_ready(@machine.config.vm.boot_timeout)
|
||||
end
|
||||
if attempt == 0
|
||||
@machine.ui.info I18n.t("vagrant.provisioners.chef.running_client")
|
||||
else
|
||||
@machine.ui.info I18n.t("vagrant.provisioners.chef.running_client_again")
|
||||
end
|
||||
|
||||
opts = { error_check: false, elevated: true }
|
||||
exit_status = @machine.communicate.sudo(command, opts) do |type, data|
|
||||
# Output the data with the proper color based on the stream.
|
||||
color = type == :stdout ? :green : :red
|
||||
|
||||
data = data.chomp
|
||||
next if data.empty?
|
||||
|
||||
@machine.ui.info(data, color: color)
|
||||
end
|
||||
|
||||
# There is no need to run Chef again if it converges
|
||||
return if exit_status == 0
|
||||
end
|
||||
|
||||
opts = { error_check: false, elevated: true }
|
||||
exit_status = @machine.communicate.sudo(command, opts) do |type, data|
|
||||
# Output the data with the proper color based on the stream.
|
||||
color = type == :stdout ? :green : :red
|
||||
|
||||
data = data.chomp
|
||||
next if data.empty?
|
||||
|
||||
@machine.ui.info(data, color: color)
|
||||
end
|
||||
|
||||
# There is no need to run Chef again if it converges
|
||||
return if exit_status == 0
|
||||
end
|
||||
|
||||
# If we reached this point then Chef never converged! Error.
|
||||
|
|
|
@ -176,36 +176,43 @@ module VagrantPlugins
|
|||
@machine.ui.warn(I18n.t("vagrant.chef_run_list_empty"))
|
||||
end
|
||||
|
||||
if @machine.guest.capability?(:wait_for_reboot)
|
||||
@machine.guest.capability(:wait_for_reboot)
|
||||
end
|
||||
|
||||
command = CommandBuilder.command(:solo, @config,
|
||||
windows: windows?,
|
||||
colored: @machine.env.ui.color?,
|
||||
legacy_mode: @config.legacy_mode,
|
||||
)
|
||||
|
||||
still_active = 259 #provisioner has asked chef to reboot
|
||||
|
||||
@config.attempts.times do |attempt|
|
||||
if attempt == 0
|
||||
@machine.ui.info I18n.t("vagrant.provisioners.chef.running_solo")
|
||||
else
|
||||
@machine.ui.info I18n.t("vagrant.provisioners.chef.running_solo_again")
|
||||
exit_status = 0
|
||||
while exit_status == 0 || exit_status == still_active
|
||||
if @machine.guest.capability?(:wait_for_reboot)
|
||||
@machine.guest.capability(:wait_for_reboot)
|
||||
elsif attempt > 0
|
||||
sleep 10
|
||||
@machine.communicate.wait_for_ready(@machine.config.vm.boot_timeout)
|
||||
end
|
||||
if attempt == 0
|
||||
@machine.ui.info I18n.t("vagrant.provisioners.chef.running_solo")
|
||||
else
|
||||
@machine.ui.info I18n.t("vagrant.provisioners.chef.running_solo_again")
|
||||
end
|
||||
|
||||
opts = { error_check: false, elevated: true }
|
||||
exit_status = @machine.communicate.sudo(command, opts) do |type, data|
|
||||
# Output the data with the proper color based on the stream.
|
||||
color = type == :stdout ? :green : :red
|
||||
|
||||
data = data.chomp
|
||||
next if data.empty?
|
||||
|
||||
@machine.ui.info(data, color: color)
|
||||
end
|
||||
|
||||
# There is no need to run Chef again if it converges
|
||||
return if exit_status == 0
|
||||
end
|
||||
|
||||
opts = { error_check: false, elevated: true }
|
||||
exit_status = @machine.communicate.sudo(command, opts) do |type, data|
|
||||
# Output the data with the proper color based on the stream.
|
||||
color = type == :stdout ? :green : :red
|
||||
|
||||
data = data.chomp
|
||||
next if data.empty?
|
||||
|
||||
@machine.ui.info(data, color: color)
|
||||
end
|
||||
|
||||
# There is no need to run Chef again if it converges
|
||||
return if exit_status == 0
|
||||
end
|
||||
|
||||
# If we reached this point then Chef never converged! Error.
|
||||
|
|
|
@ -56,36 +56,43 @@ module VagrantPlugins
|
|||
@machine.ui.warn(I18n.t("vagrant.chef_run_list_empty"))
|
||||
end
|
||||
|
||||
if @machine.guest.capability?(:wait_for_reboot)
|
||||
@machine.guest.capability(:wait_for_reboot)
|
||||
end
|
||||
|
||||
command = CommandBuilder.command(:client, @config,
|
||||
windows: windows?,
|
||||
colored: @machine.env.ui.color?,
|
||||
local_mode: true,
|
||||
)
|
||||
|
||||
still_active = 259 #provisioner has asked chef to reboot
|
||||
|
||||
@config.attempts.times do |attempt|
|
||||
if attempt == 0
|
||||
@machine.ui.info I18n.t("vagrant.provisioners.chef.running_zero")
|
||||
else
|
||||
@machine.ui.info I18n.t("vagrant.provisioners.chef.running_zero_again")
|
||||
exit_status = 0
|
||||
while exit_status == 0 || exit_status == still_active
|
||||
if @machine.guest.capability?(:wait_for_reboot)
|
||||
@machine.guest.capability(:wait_for_reboot)
|
||||
elsif attempt > 0
|
||||
sleep 10
|
||||
@machine.communicate.wait_for_ready(@machine.config.vm.boot_timeout)
|
||||
end
|
||||
if attempt == 0
|
||||
@machine.ui.info I18n.t("vagrant.provisioners.chef.running_zero")
|
||||
else
|
||||
@machine.ui.info I18n.t("vagrant.provisioners.chef.running_zero_again")
|
||||
end
|
||||
|
||||
opts = { error_check: false, elevated: true }
|
||||
exit_status = @machine.communicate.sudo(command, opts) do |type, data|
|
||||
# Output the data with the proper color based on the stream.
|
||||
color = type == :stdout ? :green : :red
|
||||
|
||||
data = data.chomp
|
||||
next if data.empty?
|
||||
|
||||
@machine.ui.info(data, color: color)
|
||||
end
|
||||
|
||||
# There is no need to run Chef again if it converges
|
||||
return if exit_status == 0
|
||||
end
|
||||
|
||||
opts = { error_check: false, elevated: true }
|
||||
exit_status = @machine.communicate.sudo(command, opts) do |type, data|
|
||||
# Output the data with the proper color based on the stream.
|
||||
color = type == :stdout ? :green : :red
|
||||
|
||||
data = data.chomp
|
||||
next if data.empty?
|
||||
|
||||
@machine.ui.info(data, color: color)
|
||||
end
|
||||
|
||||
# There is no need to run Chef again if it converges
|
||||
return if exit_status == 0
|
||||
end
|
||||
|
||||
# If we reached this point then Chef never converged! Error.
|
||||
|
|
|
@ -43,7 +43,7 @@ module VagrantPlugins
|
|||
options = {}
|
||||
options[:color] = color if !config.keep_color
|
||||
|
||||
@machine.ui.info(data.chomp, options)
|
||||
@machine.ui.detail(data.chomp, options)
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
@ -44,4 +44,30 @@ describe VagrantPlugins::ProviderVirtualBox::SyncedFolder do
|
|||
|
||||
it "should share the folders"
|
||||
end
|
||||
|
||||
describe "os_friendly_id" do
|
||||
it "should not replace normal chars" do
|
||||
expect(subject.send(:os_friendly_id, 'perfectly_valid0_name')).to eq('perfectly_valid0_name')
|
||||
end
|
||||
|
||||
it "should replace spaces" do
|
||||
expect(subject.send(:os_friendly_id, 'Program Files')).to eq('Program_Files')
|
||||
end
|
||||
|
||||
it "should replace leading underscore" do
|
||||
expect(subject.send(:os_friendly_id, '_vagrant')).to eq('vagrant')
|
||||
end
|
||||
|
||||
it "should replace slash" do
|
||||
expect(subject.send(:os_friendly_id, 'va/grant')).to eq('va_grant')
|
||||
end
|
||||
|
||||
it "should replace leading underscore and slash" do
|
||||
expect(subject.send(:os_friendly_id, '/vagrant')).to eq('vagrant')
|
||||
end
|
||||
|
||||
it "should replace backslash" do
|
||||
expect(subject.send(:os_friendly_id, 'foo\\bar')).to eq('foo_bar')
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -22,10 +22,36 @@ describe Vagrant::Util::Platform do
|
|||
allow(Vagrant::Util::Which).to receive(:which).and_return("C:/msys2/cygpath")
|
||||
allow(Vagrant::Util::Subprocess).to receive(:execute).and_return(subprocess_result)
|
||||
|
||||
expect(Vagrant::Util::Subprocess).to receive(:execute).with("C:\\msys2\\cygpath", "-u", "-a", "C:\\msys2\\home\\vagrant")
|
||||
|
||||
expect(subject.cygwin_path(path)).to eq("/home/vagrant")
|
||||
end
|
||||
end
|
||||
|
||||
describe "#msys_path" do
|
||||
let(:updated_path) { "/home/vagrant" }
|
||||
let(:subprocess_result) do
|
||||
double("subprocess_result").tap do |result|
|
||||
allow(result).to receive(:exit_code).and_return(0)
|
||||
allow(result).to receive(:stdout).and_return(updated_path)
|
||||
end
|
||||
end
|
||||
let(:old_path) { "/old/path/bin:/usr/local/bin:/usr/bin" }
|
||||
|
||||
it "takes a windows path and returns a formatted path" do
|
||||
path = ENV["PATH"]
|
||||
allow(Vagrant::Util::Which).to receive(:which).and_return("C:/msys2/cygpath")
|
||||
allow(Vagrant::Util::Subprocess).to receive(:execute).and_return(subprocess_result)
|
||||
allow(ENV).to receive(:[]).with("PATH").and_return(path)
|
||||
allow(ENV).to receive(:[]).with("VAGRANT_OLD_ENV_PATH").and_return(old_path)
|
||||
|
||||
expect(Vagrant::Util::Subprocess).to receive(:execute).with("C:\\msys2\\cygpath", "-u", "-a", path)
|
||||
|
||||
expect(subject.msys_path(path)).to eq("/home/vagrant")
|
||||
expect(ENV["PATH"]).to eq(path)
|
||||
end
|
||||
end
|
||||
|
||||
describe "#cygwin?" do
|
||||
before do
|
||||
allow(subject).to receive(:platform).and_return("test")
|
||||
|
|
|
@ -71,7 +71,7 @@ Provider-specific guides for creating base boxes are linked below:
|
|||
We strongly recommend using [Packer](https://www.packer.io) to create reproducible
|
||||
builds for your base boxes, as well as automating the builds with
|
||||
[Atlas](https://atlas.hashicorp.com). Read more about
|
||||
[Creating Vagrant Boxes with Packer](https://atlas.hashicorp.com/help/packer/artifacts/creating-vagrant-boxes)
|
||||
[Creating Vagrant Boxes with Packer](https://www.terraform.io/docs/enterprise/packer/artifacts/creating-vagrant-boxes.html)
|
||||
in the Atlas documentation.
|
||||
|
||||
### Disk Space
|
||||
|
|
|
@ -32,6 +32,8 @@ you may set. A complete reference is shown below:
|
|||
* `enable_virtualization_extensions` (boolean) - Enable virtualization extensions for the virtual CPUs.
|
||||
This allows Hyper-V to be nested and run inside another Hyper-VM VM. It requires Windows 10 - 1511 (build 10586) or newer.
|
||||
Default is not defined. This will be disabled if not set.
|
||||
* `auto_start_action` (Nothing, StartIfRunning, Start) - Action on automatic start of VM when booting OS
|
||||
* `auto_stop_action` (ShutDown, TurnOff, Save) - Action on automatic stop of VM when shutting down OS.
|
||||
* `vm_integration_services` (Hash) - Hash to set the state of integration services.
|
||||
|
||||
Example:
|
||||
|
@ -47,6 +49,4 @@ you may set. A complete reference is shown below:
|
|||
vss: true
|
||||
}
|
||||
end
|
||||
```
|
||||
|
||||
|
||||
```
|
|
@ -63,7 +63,7 @@ the machine, but replace it with perhaps a more secure key later.
|
|||
<hr>
|
||||
|
||||
`config.ssh.keys_only` - Only use Vagrant-provided SSH private keys (do not use
|
||||
any keys stored in ssh-agent). The default value is `true`.`
|
||||
any keys stored in ssh-agent). The default value is `true`.
|
||||
|
||||
<hr>
|
||||
|
||||
|
|
Loading…
Reference in New Issue