This commit is contained in:
Dmitry Ivanov 2016-01-20 16:36:39 +01:00
Родитель 07f63df5ee
Коммит e20a4249ae
32 изменённых файлов: 1221 добавлений и 2 удалений

1
.foodcritic Normal file
Просмотреть файл

@ -0,0 +1 @@
~FC052

11
.gitignore поставляемый Normal file
Просмотреть файл

@ -0,0 +1,11 @@
.vagrant
.vagrant.d
Gemfile.lock
Berksfile.lock
.bundle
.cache
.kitchen
bin
.kitchen.local.yml
.coverage
.kitchen/

87
.kitchen.yml Normal file
Просмотреть файл

@ -0,0 +1,87 @@
---
driver:
name: vagrant
gui: true
customize:
cpus: 2
memory: 1024
provisioner:
name: chef_zero
client_rb:
add_formatter: doc
platforms:
- name: debian7
driver_config:
box: debian7
provisioner:
require_chef_omnibus: 11.18.6
- name: ubuntu1404
driver_config:
box: ubuntu/trusty64
provisioner:
require_chef_omnibus: 12.4.3
- name: centos6
driver_config:
box: bento/centos-6.7
provisioner:
require_chef_omnibus: 12.4.3
# private boxes
- name: osx109-desktop
driver_config:
box: osx109-desktop #private
provisioner:
require_chef_omnibus: 12.4.3
- name: windows10
driver_config:
guest: windows
communicator: 'winrm'
box: win10x64-enterprise #private
provisioner:
require_chef_omnibus: 12.4.3
- name: windows81
driver_config:
guest: windows
communicator: 'winrm'
box: win81x64-enterprise #private
provisioner:
require_chef_omnibus: 11.18.6
suites:
- name: xplat-basic
run_list:
- recipe[xplat-basic::default]
includes:
- debian7
- ubuntu1404
- centos6
- osx109-desktop
attributes:
vsts_build_agent_test:
vsts_url: <%= ENV['VSTS_URL'] %>
vsts_pool: <%= ENV['VSTS_POOL'] %>
vsts_user: <%= ENV['VSTS_USER'] %>
vsts_token: <%= ENV['VSTS_TOKEN'] %>
- name: windows-basic
run_list:
- recipe[windows-basic::default]
includes:
- windows10
- windows81
attributes:
vsts_build_agent_test:
vsts_url: <%= ENV['VSTS_URL'] %>
vsts_pool: <%= ENV['VSTS_POOL'] %>
vsts_user: <%= ENV['VSTS_USER'] %>
vsts_token: <%= ENV['VSTS_TOKEN'] %>

2
.rspec Normal file
Просмотреть файл

@ -0,0 +1,2 @@
--color
--format documentation

13
.rubocop.yml Normal file
Просмотреть файл

@ -0,0 +1,13 @@
LineLength:
Max: 300
Style/AlignParameters:
Exclude:
- '**/metadata.rb'
Style/SingleSpaceBeforeFirstArg:
Exclude:
- '**/metadata.rb'
HashSyntax:
EnforcedStyle: hash_rockets

8
Berksfile Normal file
Просмотреть файл

@ -0,0 +1,8 @@
source 'https://supermarket.chef.io'
metadata
group :integration do
cookbook 'windows-basic', :path => './test/cookbooks/windows-basic'
cookbook 'xplat-basic', :path => './test/cookbooks/xplat-basic'
end

10
CONTRIBUTING.md Normal file
Просмотреть файл

@ -0,0 +1,10 @@
# How to contribute
One of the easiest ways to contribute is to participate in discussions and discuss issues. You can also contribute by submitting pull requests with code changes.
## General feedback and discussions?
Please start a discussion on the [issue tracker](https://github.com/Microsoft/vsts-build-agent-cookbook/issues).
## Contributing code and content
Get familiar with github pull requests https://help.github.com/articles/using-pull-requests/
You will need to sign a [Contributor License Agreement](https://cla2.dotnetfoundation.org/) before submitting your pull request.

23
LICENSE.txt Normal file
Просмотреть файл

@ -0,0 +1,23 @@
vsts-build-agent-cookbook
Copyright (c) Microsoft Corporation
All rights reserved.
MIT License
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

143
README.md
Просмотреть файл

@ -1,2 +1,141 @@
# vsts-build-agent-cookbook
Visual Studio Team Services Build Agent Cookbook. It installs and configures Visual Studio Team Services Build Agents
Visual Studio Team Services Build Agent Cookbook
================
Installs and configures Visual Studio Team Services [Build Agents](https://www.visualstudio.com/en-us/get-started/build/build-your-app-vs) (a.k.a VSO Build Agents)
Requirements
------------
- Chef 11 or higher
### Platforms
The following platforms are tested and supported:
- Debian 7 (Wheezy)
- Ubuntu 14.04
- CentOS 6
- Windows 8.1
- Windows 10
- Mac OS X 10.9.5
The following platforms are known to work:
- Microsoft Windows (8, 8.1, 10)
### Dependent Cookbooks
This cookbook doesn't install nodejs executables for an XPlat(CrossPlatform) build agent.
Please use [nodejs](https://supermarket.chef.io/cookbooks/nodejs) cookbook or any other ways which suits your case.
Attributes
----------
* `node['vsts_build_agent']['xplat']['package_name']` - Set an xplat build agent [npm](https://www.npmjs.com/package/vsoagent-installer) package name
* `node['vsts_build_agent']['xplat']['package_version']` - Set an npm package version. Possible values 'x.y.z' or 'latest'
* `node['vsts_build_agent']['xplat']['skip_vsoagent_installer']` - Set to 'true' if you need another way to install npm package.
Resource/Provider
-----------------
### windows
This resource installs and configures a build agent on windows host
#### Actions
- :install: Install and configure a build agent
- :remove: Remove a build agent and unregister it from VSTS
- :restart: Restart a build agent service
#### Parameters
- agent_name: Name attribute. The name of a build agent
- install_dir: A target directory to install a build agent
- sv_name: Set a windows service name. Default vsoagent.host.agent_name
- sv_user: Set a user name to run windows service. Possible values are "NT AUTHORITY\\NetworkService", "NT AUTHORITY\\LocalService" or any system valid username
- sv_password: Set password with sv_user unless it is equal to NetworkService or LocalService
- vsts_url: A target VSTS url
- vsts_user: A user to connect with VSTS
- vsts_token: A personal access token from VSTS. [See](http://roadtoalm.com/2015/07/22/using-personal-access-tokens-to-access-visual-studio-online/)
- vsts_pool: A pool name on VSTS
- work_folder: Set different workspace location. Default is "install_dir/\_work"
#### Examples
Install, configure, restart and remove a build agent.
Check [tests](test/cookbooks/basic/recipes/xplat.rb) for more examples.
```ruby
include_recipe 'vsts_build_agent::default'
vsts_build_agent_windows 'agent' do
install_dir 'c:\\agents\\agent1'
sv_user 'vagrant'
sv_password 'vagrant'
vsts_url 'https://<account>.visualstudio.com'
vsts_pool 'default'
vsts_user 'builder'
vsts_token 'my_secret_token_from_vsts'
action :install
end
vsts_build_agent_windows 'agent' do
action :restart
end
vsts_build_agent_windows 'agent' do
vsts_token 'my_secret_token_from_vsts'
action :remove
end
```
### xplat
This resource installs and configures a build agent on linux or macosx host
#### Actions
- :install: Install and configure a build agent
- :remove: Remove a build agent and unregister it from VSTS
- :restart: Restart a build agent service
#### Parameters
- agent_name: Name attribute. The name of build agent
- install_dir: A target directory to install build agent
- user: Set a user to run build agent.
- group: Set a group to run build agent.
- sv_name: Set a service name. Default vsoagent.host.agent_name
- sv_envs: Set hash of environment variables to pass into an agent process
- sv_session: For MacOsX only. Set a LaunchAgent session.
- vsts_url: A target VSTS url
- vsts_user: A user to connect with VSTS
- vsts_token: A personal access token from VSTS. [See](http://roadtoalm.com/2015/07/22/using-personal-access-tokens-to-access-visual-studio-online/)
- vsts_pool: A pool name on VSTS
#### Examples
Install, configure, restart and remove build agent.
Check [tests](test/cookbooks/basic/recipes/windows.rb) for more examples.
```ruby
include_recipe 'vsts_build_agent::default'
if platform_family?('mac_os_x')
include_recipe 'homebrew'
end
include_recipe 'nodejs::default'
include_recipe 'nodejs::npm'
vsts_build_agent_xplat 'xplat_agent' do
install_dir "/home/vagrant/agents/xplat_agent"
user 'vagrant'
group 'vagrant'
sv_envs(
'PATH' => '/usr/local/bin/:/opt/local/bin:/sbin:/usr/sbin:/bin:/usr/bin',
'TEST' => 'agent1'
)
vsts_url 'https://account.visualstudio.com'
vsts_pool 'default'
vsts_user 'builder'
vsts_token 'my_secret_token_from_vsts'
action :install
end
vsts_build_agent_xplat 'xplat_agent' do
action :restart
end
vsts_build_agent_xplat 'xplat_agent' do
vsts_token 'my_secret_token_from_vsts'
action :remove
end
```
# How to contribute
Check [Contribution Guide](CONTRIBUTING.md) and [Testing Guide](TESTING.md)

28
Rakefile Normal file
Просмотреть файл

@ -0,0 +1,28 @@
require 'rspec/core/rake_task'
require 'rubocop/rake_task'
require 'foodcritic'
require 'kitchen'
namespace :style do
desc 'Run Ruby style checks'
RuboCop::RakeTask.new(:ruby)
desc 'Run Chef style checks'
FoodCritic::Rake::LintTask.new(:chef)
end
desc 'Run all style checks'
task :style => ['style:chef', 'style:ruby']
desc 'Run ChefSpec'
RSpec::Core::RakeTask.new(:spec)
namespace :kitchen do
desc 'Run Test Kitchen with Vagrant'
task :linux do
Kitchen.logger = Kitchen.default_file_logger
Kitchen::Config.new.instances.get('xplat-basic-ubuntu1404').test(:always)
end
end
task :default => ['style', 'kitchen:linux']

33
TESTING.md Normal file
Просмотреть файл

@ -0,0 +1,33 @@
# Testing Documentation
## Prerequisites
VSTS Build Agent cookbook requires ChefDK installation. ChefDK can be downloaded at https://downloads.chef.io/chef-dk/
Integration testing uses Hashicorp's [Vagrant](https://www.vagrantup.com/downloads.html) and Oracle's [Virtualbox](https://www.virtualbox.org/wiki/Downloads), which must be installed first.
## Style Testing
Ruby and Chef([Foodcritic](http://www.foodcritic.io/)) style checks can be performed by running:
```
chef exec rake style
```
or
```
rake style
```
## Integration Testing
Integration tests are orchestrated by [test-kitchen](https://github.com/test-kitchen/test-kitchen). Currently kitchen.yml contains mix of public(linux) and private boxes(Windows and MacOSX). Windows and MacOSX boxes can be built locally with help of Hashicorp's [packer](https://www.packer.io/) tool.
!NOTE: To use MacOSX boxes you need an Apple-branded computer.
To run test against specific platform run:
```
kitchen verify PLATFORM
```
Available platforms:
* debian7 (public)
* ubuntu1404 (public)
* centos6 (public)
* osx109-desktop (private)
* windows10 (private)
* windows81 (private)

3
attributes/default.rb Normal file
Просмотреть файл

@ -0,0 +1,3 @@
default['vsts_build_agent']['xplat']['package_name'] = 'vsoagent-installer'
default['vsts_build_agent']['xplat']['package_version'] = 'latest'
default['vsts_build_agent']['xplat']['skip_vsoagent_installer'] = false

Просмотреть файл

@ -0,0 +1,57 @@
var cfgm = require("./configuration");
var cm = require('./common');
var webapi = require('vso-node-api/WebApi');;
var cfgr = new cfgm.Configurator();
var _creds;
cm.readBasicCreds().then(function (credentials) {
_creds = credentials;
action = process.argv.slice(2)[0];
settings = cfgm.read();
var agentPoolId;
if (action == 'install') {
console.info("Trying to create agent")
return cfgr.create(credentials).fail(function (err) {
// we couldn't create agent, then we try to update
console.info("Trying to update agent")
cfgr.update(credentials, settings)
})
} else if (action == 'remove') {
console.info("Trying to remove agent")
var agentApi = new webapi.WebApi(settings.serverUrl, cm.basicHandlerFromCreds(credentials)).getQTaskAgentApi();
agentApi.connect()
.then(function (connected) {
console.log('successful connect as ' + connected.authenticatedUser.customDisplayName);
return agentApi.getAgentPools(settings.poolName, null);
}).then(function (agentPools) {
if (agentPools.length == 0) {
throw new Error(settings.poolName + ' pool does not exist.');
}
// we queried by name so should only get 1
agentPoolId = agentPools[0].id;
console.log('Retrieved agent pool: ' + agentPools[0].name + ' (' + agentPoolId + ')');
return agentApi.getAgents(agentPoolId, settings.agentName);
}).then(function (agents) {
if (agents.length == 1) {
console.log('Found agent in pool ' + agents[0].name + ' (' + agentPoolId + ')');
var agentId = agents[0].id;
agentApi.deleteAgent(agentPoolId, agentId);
} else {
console.log('Not found agents in pool '+ agentPoolId);
}
});
} else {
console.error("Wrong action. Must be install or remove")
process.exit(1);
}
}).fail(function (err) {
console.error('Error starting the agent');
console.error(err.message);
process.exit(1);
});

92
libraries/helpers.rb Normal file
Просмотреть файл

@ -0,0 +1,92 @@
module VSTS
module Build
module Agent
# Helper methods for VSTS Build Agent installation
module Helpers
VARS_TO_SAVE = %w(vsts_url vsts_pool vsts_user install_dir sv_name sv_session user group user_home)
def agent_installed?(resource, node)
agent_attribute?(resource.agent_name, node) &&
(::File.exist?("#{resource.install_dir}/.agent") ||
::File.file?("#{resource.install_dir}\\Agent\\VsoAgent.exe"))
end
def service_name(resource)
return resource.sv_name if resource.sv_name
return nil unless resource.vsts_url
hostname = URI.parse(resource.vsts_url).host
hostname = hostname[0, hostname.index('.')] if hostname.include?('.')
"vsoagent.#{hostname}.#{resource.agent_name}"
end
def get_npm_install_cmd(node)
npm_cmd = "npm install -global #{node['vsts_build_agent']['xplat']['package_name']}"
unless node['vsts_build_agent']['xplat']['package_version'] == 'latest'
npm_cmd += "@#{node['vsts_build_agent']['xplat']['package_version']}"
end
npm_cmd
end
def save_current_state(resource, node)
VARS_TO_SAVE.each do |var|
node.set['vsts_build_agent']['agents'][resource.agent_name][var] = resource.send(var) if resource.respond_to?(var.to_sym)
end
node.save
end
def load_current_state(resource, node)
return unless agent_attribute?(resource.agent_name, node)
VARS_TO_SAVE.each do |var|
resource.send(var, node['vsts_build_agent']['agents'][resource.agent_name][var]) if resource.respond_to?(var.to_sym)
end
end
def agent_attribute?(agent_name, node)
node['vsts_build_agent']['agents'] && node['vsts_build_agent']['agents'][agent_name]
end
def remove_current_state(resource, node)
node.set['vsts_build_agent']['agents'][resource.agent_name] = {}
node.save
end
def plist_path(resource)
if resource.sv_session
path = "/Library/LaunchAgents/#{resource.sv_name}.plist"
else
path = "/Library/LaunchDaemons/#{resource.sv_name}.plist"
end
path = "#{resource.user_home}#{path}" if resource.user_home
path
end
def launchctl_load(resource)
plist = plist_path resource
command = 'launchctl load -w '
command += "-S #{resource.sv_session} " if resource.sv_session
command += plist
command
end
def launchctl_unload(resource)
plist = plist_path resource
command = "launchctl unload #{plist}"
command
end
def vsagentexec(args = {})
command = 'Agent\\VsoAgent.exe '
args.each do |key, value|
command += "/#{key}"
command += ":\"#{value}\"" unless value.nil?
command += ' '
end
command
end
end
end
end
end

15
metadata.rb Normal file
Просмотреть файл

@ -0,0 +1,15 @@
name 'vsts_build_agent'
maintainer 'Microsoft'
license 'MIT'
description 'Installs/Configures visualstudio team services build agents'
long_description IO.read(File.join(File.dirname(__FILE__), 'README.md'))
version '0.1.0'
%w( ubuntu debian mac_os_x mac_os_x_server windows ).each do |os|
supports os
end
suggests 'nodejs'
depends 'runit'
depends 'windows'

138
providers/windows.rb Normal file
Просмотреть файл

@ -0,0 +1,138 @@
require 'chef/mixin/shell_out'
include Chef::Mixin::ShellOut
include ::VSTS::Build::Agent::Helpers
use_inline_resources
def whyrun_supported?
true
end
def load_current_resource
@current_resource = Chef::Resource::VstsBuildAgentWindows.new(@new_resource.name)
@current_resource.agent_name(@new_resource.agent_name)
@current_resource.exists = false
load_current_state(@current_resource, node)
@new_resource.sv_name(service_name(@new_resource))
if agent_installed?(@current_resource, node)
@current_resource.vsts_token(@new_resource.vsts_token)
@current_resource.exists = true
end
@current_resource
end
action :install do
if @current_resource.exists
Chef::Log.info "#{new_resource.agent_name} agent already exists - nothing to do"
else
converge_by("Installing agent \"#{new_resource.agent_name}\"") do
powershell_script 'Downloading vsoagent' do
code <<-EOH
$username = "#{new_resource.vsts_user}"
$patToken = "#{new_resource.vsts_token}"
$OutFile = "#{Chef::Config[:file_cache_path]}/vso_agent.zip"
$auth = ('{0}:{1}' -f $username,$patToken)
$auth = [System.Text.Encoding]::UTF8.GetBytes($auth)
$auth = [System.Convert]::ToBase64String($auth)
$h = @{Authorization=('Basic {0}' -f $auth)}
$wc = New-Object Net.WebClient
$wc.Headers.add('Authorization', 'Basic {0}' -f $auth)
$wc.DownloadFile( "#{new_resource.vsts_url}/_apis/distributedtask/packages/agent", $OutFile )
EOH
not_if { ::File.exist?("#{Chef::Config[:file_cache_path]}/vso_agent.zip") }
end
directory new_resource.install_dir do
rights :full_control, new_resource.sv_user, :applies_to_children => true
recursive true
action :create
end
windows_zipfile new_resource.install_dir do
source "#{Chef::Config[:file_cache_path]}/vso_agent.zip"
action :unzip
end
powershell_script 'Removing the ZoneIdentifier from files downloaded from the internet' do
cwd new_resource.install_dir
code <<-EOH
Get-ChildItem -Path #{new_resource.install_dir} | Unblock-File | out-null
Get-ChildItem -Recurse -Path #{new_resource.install_dir}\\Agent | Unblock-File | out-null
EOH
end
args = {
'configure' => nil,
'RunningAsService' => nil,
'serverUrl' => new_resource.vsts_url,
'WindowsServiceName' => new_resource.sv_name,
'WindowsServiceLogonAccount' => new_resource.sv_user,
'WindowsServiceLogonPassword' => new_resource.sv_password,
'name' => new_resource.agent_name,
'PoolName' => new_resource.vsts_pool,
'WorkFolder' => new_resource.work_folder,
'Login' => "#{new_resource.vsts_user},#{new_resource.vsts_token}",
'force' => nil,
'NoPrompt' => nil
}
execute "Configuring agent \"#{new_resource.agent_name}\"" do
cwd new_resource.install_dir
command vsagentexec(args)
action :run
end
end
save_current_state(new_resource, node)
new_resource.updated_by_last_action(true)
Chef::Log.info "\"#{new_resource.agent_name}\" agent was installed"
end
end
action :remove do
if @current_resource.exists
converge_by("Removing agent \"#{@current_resource.agent_name}\"") do
args = {
'unconfigure' => nil,
'Login' => "#{@current_resource.vsts_user},#{@current_resource.vsts_token}",
'force' => nil,
'NoPrompt' => nil
}
execute "Unconfiguring agent \"#{@current_resource.agent_name}\"" do
cwd current_resource.install_dir
command vsagentexec(args)
action :run
retries 3
retry_delay 15
end
directory current_resource.install_dir do
recursive true
action :delete
end
end
remove_current_state(@current_resource, node)
new_resource.updated_by_last_action(true)
Chef::Log.info "\"#{new_resource.agent_name}\" agent was removed"
end
end
action :restart do
if @current_resource.exists
converge_by("Restarting agent \"#{@current_resource.agent_name}\"") do
service @current_resource.sv_name do
action :restart
end
end
new_resource.updated_by_last_action(true)
Chef::Log.info "\"#{@current_resource.agent_name}\" agent was restarted"
end
end

213
providers/xplat.rb Normal file
Просмотреть файл

@ -0,0 +1,213 @@
require 'chef/mixin/shell_out'
require 'json'
include ::VSTS::Build::Agent::Helpers
use_inline_resources
def whyrun_supported?
true
end
def load_current_resource
@current_resource = Chef::Resource::VstsBuildAgentXplat.new(@new_resource.name)
@current_resource.agent_name(@new_resource.agent_name)
@current_resource.exists = false
load_current_state(@current_resource, node)
@new_resource.sv_name(service_name(@new_resource))
if agent_installed?(@current_resource, node)
@current_resource.vsts_token(@new_resource.vsts_token)
@current_resource.exists = true
end
@current_resource
end
action :install do
if @current_resource.exists
Chef::Log.info "#{new_resource.agent_name} agent already exists - nothing to do"
else
converge_by("Installing agent \"#{new_resource.agent_name}\"") do
npm_cmd = get_npm_install_cmd(node)
execute 'Install vsoagent-installer npm package' do
command npm_cmd
not_if { node['vsts_build_agent']['xplat']['skip_vsoagent_installer'] }
end
directory new_resource.install_dir do
user new_resource.user
group new_resource.group
mode '0755'
recursive true
action [:delete, :create]
end
execute "Initializing agent \"#{new_resource.agent_name}\"" do
cwd new_resource.install_dir
command 'vsoagent-installer'
user new_resource.user
group new_resource.group
end
template "#{new_resource.install_dir}/.agent" do
source 'agent_conf.erb'
variables(:agent => new_resource)
owner new_resource.user
group new_resource.group
cookbook 'vsts_build_agent'
end
cookbook_file "#{new_resource.install_dir}/agent/vsoagent_configurator.js" do
source 'vsoagent_configurator.js'
owner new_resource.user
group new_resource.group
cookbook 'vsts_build_agent'
end
execute "Configuring agent \"#{new_resource.agent_name}\"" do
cwd new_resource.install_dir
command "node agent/vsoagent_configurator install -u #{new_resource.vsts_user} -p #{new_resource.vsts_token} -s #{new_resource.vsts_url} -a #{new_resource.agent_name} -l #{new_resource.vsts_pool} -b false"
user new_resource.user
group new_resource.group
end
if new_resource.user_home && !new_resource.sv_envs.key?('HOME')
new_resource.sv_envs['HOME'] = new_resource.user_home
end
if mac_os_x?
plist = plist_path new_resource
directory "#{new_resource.user_home}/Library/LaunchDaemons" do
user new_resource.user
group new_resource.group
mode '0755'
action :create
only_if { new_resource.user_home }
end
template plist do
source "#{new_resource.sv_template}.plist.erb"
cookbook new_resource.sv_cookbook
user new_resource.user if new_resource.user_home
group new_resource.group if new_resource.user_home
variables(
:agent => new_resource
)
end
cmd = launchctl_load @new_resource
execute cmd do
user new_resource.user if new_resource.user_home
group new_resource.group if new_resource.user_home
action :run
end
else
runit_service new_resource.sv_name do
options(
:agent => new_resource
)
owner new_resource.user
group new_resource.group
cookbook new_resource.sv_cookbook
run_template_name new_resource.sv_template
log_template_name new_resource.sv_template
sv_timeout new_resource.sv_timeout
sv_verbose true
action :enable
end
ruby_block 'Wait for service setup' do
block do
# TODO: remove when runit cookbook will wait for service setup
sleep new_resource.sv_wait_timeout
end
end
runit_service new_resource.sv_name do
action :start
end
end
save_current_state(new_resource, node)
Chef::Log.info "\"#{new_resource.agent_name}\" agent was installed"
new_resource.updated_by_last_action(true)
end
end
end
action :remove do
if @current_resource.exists
converge_by("Removing agent \"#{@current_resource.agent_name}\"") do
if mac_os_x?
plist = plist_path @current_resource
cmd = launchctl_unload @current_resource
execute "Unload service for #{@current_resource.agent_name}" do
user current_resource.user if current_resource.user_home
group current_resource.group if current_resource.user_home
command cmd
only_if { ::File.exist?(plist) }
action :run
end
file plist do
action :delete
end
else
runit_service @current_resource.sv_name do
action [:stop, :disable]
end
end
execute "Delete agent \"#{@current_resource.agent_name}\" from server" do
cwd current_resource.install_dir
command "node agent/vsoagent_configurator remove -u #{current_resource.vsts_user} -p #{current_resource.vsts_token} -s #{current_resource.vsts_url} -a #{current_resource.agent_name} -l #{current_resource.vsts_pool} -b false"
user current_resource.user
group current_resource.group
end
directory @current_resource.install_dir do
recursive true
action :delete
end
remove_current_state(@current_resource, node)
Chef::Log.info "\"#{@current_resource.agent_name}\" agent was removed"
new_resource.updated_by_last_action(true)
end
end
end
action :restart do
if @current_resource.exists
converge_by("Restarting agent \"#{@current_resource.agent_name}\"") do
if mac_os_x?
cmd = launchctl_unload @current_resource
execute cmd do
user current_resource.user if current_resource.user_home
group current_resource.group if current_resource.user_home
action :run
end
cmd = launchctl_load @current_resource
execute cmd do
user current_resource.user if current_resource.user_home
group current_resource.group if current_resource.user_home
action :run
end
else
runit_service @current_resource.sv_name do
action :restart
end
end
Chef::Log.info "\"#{@current_resource.agent_name}\" agent was restarted"
new_resource.updated_by_last_action(true)
end
end
end
private
def mac_os_x?
platform_family?('mac_os_x') || platform_family?('mac_os_x_server')
end

3
recipes/default.rb Normal file
Просмотреть файл

@ -0,0 +1,3 @@
if %w(debian rhel fedora gentoo).include?(node['platform_family'])
include_recipe 'runit::default'
end

18
resources/windows.rb Normal file
Просмотреть файл

@ -0,0 +1,18 @@
actions :install, :remove, :restart
default_action :install
attribute :agent_name, :name_attribute => true
attribute :install_dir, :kind_of => String
attribute :sv_name, :kind_of => String
attribute :sv_user, :kind_of => String
attribute :sv_password, :kind_of => String
attribute :vsts_url, :kind_of => String
attribute :vsts_pool, :kind_of => String
attribute :vsts_user, :kind_of => String
attribute :vsts_token, :kind_of => String
attribute :work_folder, :kind_of => String, :default => '_work' # not supported on client side
attr_accessor :exists

24
resources/xplat.rb Normal file
Просмотреть файл

@ -0,0 +1,24 @@
actions :install, :remove, :restart
default_action :install
attribute :agent_name, :name_attribute => true
attribute :install_dir, :kind_of => String
attribute :user, :kind_of => String
attribute :group, :kind_of => String
attribute :user_home, :kind_of => String
attribute :sv_name, :kind_of => String # Default vsoagent.host.agent_name
attribute :sv_cookbook, :kind_of => String, :default => 'vsts_build_agent'
attribute :sv_template, :kind_of => String, :default => 'vsts_build_agent'
attribute :sv_timeout, :kind_of => Integer, :default => 120
attribute :sv_envs, :kind_of => Hash, :default => {}
attribute :sv_session, :kind_of => String, :default => nil # used by MacOsX. Can be Aqua for interact with GUI
attribute :sv_wait_timeout, :kind_of => Integer, :default => 5
attribute :vsts_url, :kind_of => String
attribute :vsts_pool, :kind_of => String
attribute :vsts_user, :kind_of => String
attribute :vsts_token, :kind_of => String
attr_accessor :exists

Просмотреть файл

@ -0,0 +1,5 @@
{
"poolName": "<%= @agent.vsts_pool %>",
"serverUrl": "<%= @agent.vsts_url %>",
"agentName": "<%= @agent.agent_name %>"
}

Просмотреть файл

@ -0,0 +1,2 @@
#!/bin/sh
exec svlogd -tt ./main

Просмотреть файл

@ -0,0 +1,11 @@
#!/bin/sh
ulimit -Hn 65550
ulimit -Sn 65550
exec 2>&1
cd <%= @options[:agent].install_dir %>
exec env <% unless @options[:agent].sv_envs.empty? -%><%= @options[:agent].sv_envs.map{|k,v| "#{k}=#{v}"}.join(' ') %><% end -%> \
chpst -u <%= @options[:agent].user %>:<%= @options[:agent].group %> \
-U <%= @options[:agent].user %>:<%= @options[:agent].group %> \
node ./agent/vsoagent.js -u <%= @options[:agent].vsts_user %> -p <%= @options[:agent].vsts_token %>

Просмотреть файл

@ -0,0 +1,44 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>Label</key>
<string><%= @agent.sv_name %></string>
<key>ProgramArguments</key>
<array>
<string>node</string>
<string><%= @agent.install_dir %>/agent/vsoagent.js</string>
<string>-u</string>
<string><%= @agent.vsts_user %> </string>
<string>-p</string>
<string><%= @agent.vsts_token %></string>
</array>
<key>UserName</key>
<string><%= @agent.user %></string>
<% if @session -%>
<key>LimitLoadToSessionType</key>
<string><%= @agent.sv_session %></string>
<% end -%>
<key>WorkingDirectory</key>
<string><%= @agent.install_dir %></string>
<key>RunAtLoad</key>
<true/>
<key>StandardOutPath</key>
<string><%= @agent.install_dir %>/service.log</string>
<key>StandardErrorPath</key>
<string><%= @agent.install_dir %>/service.err.log</string>
<% unless @agent.sv_envs.empty? -%>
<key>EnvironmentVariables</key>
<dict>
<% @agent.sv_envs.each do |key, val| -%>
<key><%= key %></key>
<string><%= val %></string>
<% end -%>
</dict>
<% end -%>
</dict>
</plist>

Просмотреть файл

@ -0,0 +1,5 @@
# set attributes through test kitchen
default['vsts_build_agent_test']['vsts_url'] = nil
default['vsts_build_agent_test']['vsts_pool'] = nil
default['vsts_build_agent_test']['vsts_user'] = nil
default['vsts_build_agent_test']['vsts_token'] = nil

Просмотреть файл

@ -0,0 +1,5 @@
name 'windows-basic'
version '0.0.1'
depends 'vsts_build_agent'
depends 'windows'

Просмотреть файл

@ -0,0 +1,73 @@
include_recipe 'vsts_build_agent::default'
agent_prefix = node['hostname']
sys_user = 'vagrant'
sys_passwd = 'vagrant'
# clean previous run
vsts_build_agent_windows "#{agent_prefix}_01" do
vsts_token node['vsts_build_agent_test']['vsts_token']
action :remove
end
vsts_build_agent_windows "#{agent_prefix}_02" do
vsts_token node['vsts_build_agent_test']['vsts_token']
action :remove
end
vsts_build_agent_windows "#{agent_prefix}_03" do
install_dir "c:\\agents\\#{agent_prefix}_03"
vsts_user node['vsts_build_agent_test']['vsts_user']
vsts_token node['vsts_build_agent_test']['vsts_token']
action :remove
end
# install agents
# agent with default service name
vsts_build_agent_windows "#{agent_prefix}_01" do
install_dir 'c:\\agents\\agent_01'
sv_user sys_user
sv_password sys_passwd
vsts_url node['vsts_build_agent_test']['vsts_url']
vsts_pool node['vsts_build_agent_test']['vsts_pool']
vsts_user node['vsts_build_agent_test']['vsts_user']
vsts_token node['vsts_build_agent_test']['vsts_token']
action :install
end
vsts_build_agent_windows "#{agent_prefix}_01" do
action :restart
end
# agent with overriden service name
# run as Local Service user
vsts_build_agent_windows "#{agent_prefix}_02" do
install_dir 'c:\\agents\\agent_02'
sv_user 'NT AUTHORITY\\LocalService'
sv_name 'agent_02.service'
vsts_url node['vsts_build_agent_test']['vsts_url']
vsts_pool node['vsts_build_agent_test']['vsts_pool']
vsts_user node['vsts_build_agent_test']['vsts_user']
vsts_token node['vsts_build_agent_test']['vsts_token']
action :install
end
vsts_build_agent_windows "#{agent_prefix}_02" do
action :restart
end
# agent to remove
vsts_build_agent_windows "#{agent_prefix}_03" do
install_dir 'c:\\agents\\agent_03'
sv_user 'NT AUTHORITY\\NetworkService'
vsts_url node['vsts_build_agent_test']['vsts_url']
vsts_pool node['vsts_build_agent_test']['vsts_pool']
vsts_user node['vsts_build_agent_test']['vsts_user']
vsts_token node['vsts_build_agent_test']['vsts_token']
action :install
end
vsts_build_agent_windows "#{agent_prefix}_03" do
vsts_token node['vsts_build_agent_test']['vsts_token']
action :remove
end

Просмотреть файл

@ -0,0 +1,11 @@
default['nodejs']['version'] = '4.2.3'
default['nodejs']['source']['checksum'] = nil
default['nodejs']['binary']['checksum']['linux_x64'] = nil
default['nodejs']['binary']['checksum']['linux_x86'] = nil
default['nodejs']['install_method'] = 'binary'
# set attributes through test kitchen
default['vsts_build_agent_test']['vsts_url'] = nil
default['vsts_build_agent_test']['vsts_pool'] = nil
default['vsts_build_agent_test']['vsts_user'] = nil
default['vsts_build_agent_test']['vsts_token'] = nil

Просмотреть файл

@ -0,0 +1,10 @@
name 'xplat-basic'
version '0.0.1'
depends 'vsts_build_agent'
depends 'apt'
depends 'build-essential'
depends 'nodejs'
depends 'runit'
depends 'homebrew'

Просмотреть файл

@ -0,0 +1,94 @@
include_recipe 'apt::default' if platform_family?('debian')
include_recipe 'vsts_build_agent::default'
include_recipe 'build-essential::default'
include_recipe 'vsts_build_agent::default'
agent1_name = "#{node['hostname']}_01"
agent2_name = "#{node['hostname']}_02"
if platform_family?('mac_os_x')
home_dir = '/Users/vagrant'
bash 'Prepare dirs for the homebrew' do
code <<-EOH
mkdir -p /usr/local
chown -R vagrant:staff /usr/local
EOH
end
node.set['homebrew']['owner'] = 'vagrant'
include_recipe 'homebrew'
execute 'Install nodejs from brew' do
command 'brew install node'
user 'vagrant'
group 'staff'
action :run
end
else
home_dir = '/home/vagrant'
include_recipe 'nodejs::default'
include_recipe 'nodejs::npm'
execute 'Set npm global prefix' do
command 'npm config set prefix /usr/local'
end
end
# # cleanup
vsts_build_agent_xplat agent1_name do
vsts_token node['vsts_build_agent_test']['vsts_token']
action :remove
end
vsts_build_agent_xplat agent2_name do
vsts_token node['vsts_build_agent_test']['vsts_token']
action :remove
end
vsts_build_agent_xplat agent1_name do
install_dir "#{home_dir}/agents/agent_01"
user 'vagrant'
group 'vagrant'
sv_envs(
'PATH' => '/usr/local/bin/:/opt/local/bin:/sbin:/usr/sbin:/bin:/usr/bin',
'TEST' => 'agent1'
)
vsts_url node['vsts_build_agent_test']['vsts_url']
vsts_pool node['vsts_build_agent_test']['vsts_pool']
vsts_user node['vsts_build_agent_test']['vsts_user']
vsts_token node['vsts_build_agent_test']['vsts_token']
action :install
end
vsts_build_agent_xplat agent2_name do
install_dir "#{home_dir}/agents/agent_02"
user 'vagrant'
group 'vagrant'
user_home home_dir
sv_name 'agent2'
sv_envs(
'PATH' => '/usr/local/bin/:/opt/local/bin:/sbin:/usr/sbin:/bin:/usr/bin',
'TEST' => 'agent2'
)
vsts_url node['vsts_build_agent_test']['vsts_url']
vsts_pool node['vsts_build_agent_test']['vsts_pool']
vsts_user node['vsts_build_agent_test']['vsts_user']
vsts_token node['vsts_build_agent_test']['vsts_token']
action :install
notifies :restart, "vsts_build_agent_xplat[#{agent2_name}]", :delayed
end
vsts_build_agent_xplat "Restart '#{agent1_name}'" do
agent_name agent1_name
action :restart
end
vsts_build_agent_xplat "Remove '#{agent1_name}'" do
agent_name agent1_name
vsts_token node['vsts_build_agent_test']['vsts_token']
action :remove
end

Просмотреть файл

@ -0,0 +1,20 @@
require 'serverspec'
set :backend, :cmd
set :os, :family => 'windows'
describe file('c:\\agents\\agent_01\\Agent\\VsoAgent.exe') do
it { should be_file }
end
describe file('c:\\agents\\agent_02\\Agent\\VsoAgent.exe') do
it { should be_file }
end
describe file('c:\\agents\\agent_03\\Agent\\VsoAgent.exe') do
it { should_not be_file }
end
describe service('agent_02.service') do
it { should be_running }
end

Просмотреть файл

@ -0,0 +1,21 @@
require 'serverspec'
set :backend, :exec
if os[:family] == 'darwin'
home_dir = '/Users/vagrant'
else
home_dir = '/home/vagrant'
end
describe file("#{home_dir}/agents/agent_01/.agent") do
it { should_not exist }
end
describe file("#{home_dir}/agents/agent_02/.agent") do
it { should be_file }
end
describe service('agent2'), :unless => os[:family] == 'darwin' do
it { should be_running }
end