Compare commits

..

No commits in common. "main" and "v0.8.2" have entirely different histories.
main ... v0.8.2

49 changed files with 940 additions and 3467 deletions

View file

@ -1,9 +0,0 @@
**/*.yml
**/*.yaml
**/*.md
**/*example
**/Dockerfile*
coverage
examples
scripts
vendor

View file

@ -15,3 +15,7 @@ FIXME
- [ ] Tests
- [ ] Documentation
## Reviewers
@demophoon
@briancain

View file

@ -1,19 +0,0 @@
version: 2
updates:
- package-ecosystem: bundler
directory: "/"
schedule:
interval: weekly
open-pull-requests-limit: 10
- package-ecosystem: docker
directory: "/"
schedule:
interval: weekly
open-pull-requests-limit: 10
- package-ecosystem: github-actions
directory: "/"
schedule:
interval: weekly
open-pull-requests-limit: 10

View file

@ -1,12 +0,0 @@
name: Automated release prep
on:
workflow_dispatch:
jobs:
auto_release_prep:
uses: puppetlabs/release-engineering-repo-standards/.github/workflows/auto_release_prep.yml@v1
secrets: inherit
with:
project-type: ruby
version-file-path: lib/vmfloaty/version.rb

View file

@ -1,8 +0,0 @@
name: Dependabot auto-merge
on: pull_request
jobs:
dependabot_merge:
uses: puppetlabs/release-engineering-repo-standards/.github/workflows/dependabot_merge.yml@v1
secrets: inherit

View file

@ -1,8 +0,0 @@
name: Ensure label
on: pull_request
jobs:
ensure_label:
uses: puppetlabs/release-engineering-repo-standards/.github/workflows/ensure_label.yml@v1
secrets: inherit

View file

@ -1,118 +0,0 @@
name: Tag Release & Push Gem & Docker
on: workflow_dispatch
permissions:
contents: write
issues: read
pull-requests: read
packages: write
jobs:
release:
name: Validate Docs, Tag, and Docker Push
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v4
with:
ref: ${{ github.ref }}
clean: true
fetch-depth: 0
- name: Get New Version
id: nv
run: |
version=$(grep VERSION lib/vmfloaty/version.rb |rev |cut -d "'" -f2 |rev)
echo "version=$version" >> $GITHUB_OUTPUT
echo "Found version $version from lib/vmfloaty/version.rb"
- name: Get Current Version
uses: actions/github-script@v7
id: cv
with:
script: |
const { data: response } = await github.rest.repos.getLatestRelease({
owner: context.repo.owner,
repo: context.repo.repo,
})
console.log(`The latest release is ${response.tag_name}`)
return response.tag_name
result-encoding: string
- name: Generate Changelog
uses: docker://githubchangeloggenerator/github-changelog-generator:1.16.2
with:
args: >-
--future-release ${{ steps.nv.outputs.version }}
env:
CHANGELOG_GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- name: Validate Changelog
run : |
set -e
if output=$(git status --porcelain) && [ ! -z "$output" ]; then
echo "Here is the current git status:"
git status
echo
echo "The following changes were detected:"
git --no-pager diff
echo "Uncommitted PRs found in the changelog. Please submit a release prep PR of changes after running './release-prep ${{ steps.nv.outputs.version }}'"
exit 1
fi
- name: Generate Release Notes
uses: docker://githubchangeloggenerator/github-changelog-generator:1.16.2
with:
args: >-
--since-tag ${{ steps.cv.outputs.result }}
--future-release ${{ steps.nv.outputs.version }}
--output release-notes.md
env:
CHANGELOG_GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- name: Tag Release
uses: ncipollo/release-action@v1
with:
tag: ${{ steps.nv.outputs.version }}
token: ${{ secrets.GITHUB_TOKEN }}
bodyfile: release-notes.md
draft: false
prerelease: false
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
- name: Login to GitHub Container Registry
uses: docker/login-action@v3
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Build and push
uses: docker/build-push-action@v5
with:
push: true
tags: |
ghcr.io/${{ github.repository }}:${{ steps.nv.outputs.version }}
ghcr.io/${{ github.repository }}:latest
- name: Set up Ruby 3.2
uses: actions/setup-ruby@v1
with:
version: 3.2.x
- name: Build gem
run: gem build *.gemspec
- name: Publish gem
run: |
mkdir -p $HOME/.gem
touch $HOME/.gem/credentials
chmod 0600 $HOME/.gem/credentials
printf -- "---\n:rubygems_api_key: ${GEM_HOST_API_KEY}\n" > $HOME/.gem/credentials
gem push *.gem
env:
GEM_HOST_API_KEY: ${{secrets.RUBYGEMS_AUTH_TOKEN}}

View file

@ -1,39 +0,0 @@
name: Security
on:
workflow_dispatch:
push:
branches:
- main
jobs:
scan:
name: Mend Scanning
runs-on: ubuntu-latest
steps:
- name: checkout repo content
uses: actions/checkout@v4
with:
fetch-depth: 1
- name: setup ruby
uses: ruby/setup-ruby@v1
with:
ruby-version: 2.7
# setup a package lock if one doesn't exist, otherwise do nothing
- name: check lock
run: '[ -f "Gemfile.lock" ] && echo "package lock file exists, skipping" || bundle lock'
# install java
- uses: actions/setup-java@v4
with:
distribution: 'temurin' # See 'Supported distributions' for available options
java-version: '17'
# download mend
- name: download_mend
run: curl -o wss-unified-agent.jar https://unified-agent.s3.amazonaws.com/wss-unified-agent.jar
- name: run mend
run: java -jar wss-unified-agent.jar
env:
WS_APIKEY: ${{ secrets.MEND_API_KEY }}
WS_WSS_URL: https://saas-eu.whitesourcesoftware.com/agent
WS_USERKEY: ${{ secrets.MEND_TOKEN }}
WS_PRODUCTNAME: RE
WS_PROJECTNAME: ${{ github.event.repository.name }}

View file

@ -1,54 +0,0 @@
# This workflow uses actions that are not certified by GitHub.
# They are provided by a third-party and are governed by
# separate terms of service, privacy policy, and support
# documentation.
# This workflow will download a prebuilt Ruby version, install dependencies and run tests with Rake
# For more information see: https://github.com/marketplace/actions/setup-ruby-jruby-and-truffleruby
name: Test
on:
pull_request:
branches:
- main
jobs:
spec:
runs-on: ubuntu-latest
strategy:
matrix:
ruby-version:
- '2.7'
- '3.0'
- '3.1'
- '3.2'
steps:
- uses: actions/checkout@v4
- name: Set up Ruby
# To automatically get bug fixes and new Ruby versions for ruby/setup-ruby,
# change this to (see https://github.com/ruby/setup-ruby#versioning):
# uses: ruby/setup-ruby@v1
uses: ruby/setup-ruby@v1
with:
ruby-version: ${{ matrix.ruby-version }}
bundler-cache: true # runs 'bundle install' and caches installed gems automatically
- name: Run tests
run: bundle exec rake spec
- name: Coveralls
uses: coverallsapp/github-action@v2
with:
github-token: ${{ secrets.GITHUB_TOKEN }}
flag-name: run-${{ matrix.ruby-version }}
parallel: true
finish:
needs: spec
if: ${{ always() }}
runs-on: ubuntu-latest
steps:
- name: Coveralls Finished
uses: coverallsapp/github-action@v2
with:
github-token: ${{ secrets.github_token }}
parallel-finished: true

View file

@ -1,3 +0,0 @@
project=vmfloaty
user=puppetlabs
exclude_labels=maintenance

4
.gitignore vendored
View file

@ -22,13 +22,11 @@ build/
## Environment normalisation:
/.bundle/
/vendor/
/lib/bundler/man/
.dccache
# for a library or gem, you might want to ignore these files since the code is
# intended to run in multiple environments; otherwise, check them in:
Gemfile.lock
.ruby-version
.ruby-gemset

5
.travis.yml Normal file
View file

@ -0,0 +1,5 @@
sudo: false
language: ruby
rvm:
- 2.4
script: rspec spec

View file

@ -1,340 +0,0 @@
# Changelog
## [1.8.1](https://github.com/puppetlabs/vmfloaty/tree/1.8.1) (2023-08-07)
[Full Changelog](https://github.com/puppetlabs/vmfloaty/compare/1.8.0...1.8.1)
**Fixed bugs:**
- status and summary broken for pooler service after v3 [\#185](https://github.com/puppetlabs/vmfloaty/issues/185)
- \(RE-15687\) Use relative path for pooler status and summary [\#186](https://github.com/puppetlabs/vmfloaty/pull/186) ([yachub](https://github.com/yachub))
**Merged pull requests:**
- Bump rubocop from 1.54.2 to 1.55.1 [\#183](https://github.com/puppetlabs/vmfloaty/pull/183) ([dependabot[bot]](https://github.com/apps/dependabot))
- Bump rubocop from 1.52.0 to 1.54.2 [\#182](https://github.com/puppetlabs/vmfloaty/pull/182) ([dependabot[bot]](https://github.com/apps/dependabot))
- Bump rubocop from 1.51.0 to 1.52.0 [\#177](https://github.com/puppetlabs/vmfloaty/pull/177) ([dependabot[bot]](https://github.com/apps/dependabot))
- Bump rubocop from 1.50.2 to 1.51.0 [\#176](https://github.com/puppetlabs/vmfloaty/pull/176) ([dependabot[bot]](https://github.com/apps/dependabot))
- Bump rubocop from 1.49.0 to 1.50.2 [\#174](https://github.com/puppetlabs/vmfloaty/pull/174) ([dependabot[bot]](https://github.com/apps/dependabot))
- Bump rubocop from 1.48.1 to 1.49.0 [\#173](https://github.com/puppetlabs/vmfloaty/pull/173) ([dependabot[bot]](https://github.com/apps/dependabot))
- Update simplecov requirement from ~\> 0.21.2 to ~\> 0.22.0 [\#167](https://github.com/puppetlabs/vmfloaty/pull/167) ([dependabot[bot]](https://github.com/apps/dependabot))
- Update rspec requirement from ~\> 3.11.0 to ~\> 3.12.0 [\#166](https://github.com/puppetlabs/vmfloaty/pull/166) ([dependabot[bot]](https://github.com/apps/dependabot))
## [1.8.0](https://github.com/puppetlabs/vmfloaty/tree/1.8.0) (2023-03-21)
[Full Changelog](https://github.com/puppetlabs/vmfloaty/compare/1.7.0...1.8.0)
**Implemented enhancements:**
- Docker, Actions, and Docs Updates [\#170](https://github.com/puppetlabs/vmfloaty/pull/170) ([yachub](https://github.com/yachub))
**Fixed bugs:**
- Fix `floaty list --active` for vmpooler api v2 [\#169](https://github.com/puppetlabs/vmfloaty/pull/169) ([yachub](https://github.com/yachub))
**Merged pull requests:**
- \(RE-15111\) Migrate Synk to Mend [\#168](https://github.com/puppetlabs/vmfloaty/pull/168) ([yachub](https://github.com/yachub))
- \(RE-14811\) Move codeowners from DIO to RE [\#165](https://github.com/puppetlabs/vmfloaty/pull/165) ([yachub](https://github.com/yachub))
- Add Snyk action and Move to RE org [\#164](https://github.com/puppetlabs/vmfloaty/pull/164) ([yachub](https://github.com/yachub))
- Add release-engineering to codeowners [\#163](https://github.com/puppetlabs/vmfloaty/pull/163) ([yachub](https://github.com/yachub))
## [1.7.0](https://github.com/puppetlabs/vmfloaty/tree/1.7.0) (2022-04-05)
[Full Changelog](https://github.com/puppetlabs/vmfloaty/compare/v1.6.0...1.7.0)
**Implemented enhancements:**
- \(maint\) Add Ruby 3.1 to test matrix + dockerfile and drop EOL Ruby 2.6 [\#159](https://github.com/puppetlabs/vmfloaty/pull/159) ([yachub](https://github.com/yachub))
- \(DIO-3101\) Add VMPooler API v2 Support [\#158](https://github.com/puppetlabs/vmfloaty/pull/158) ([yachub](https://github.com/yachub))
**Fixed bugs:**
- \(maint\) Remove colorize usage from `floaty status` [\#160](https://github.com/puppetlabs/vmfloaty/pull/160) ([yachub](https://github.com/yachub))
**Merged pull requests:**
- v1.7.0 release prep [\#162](https://github.com/puppetlabs/vmfloaty/pull/162) ([yachub](https://github.com/yachub))
- \(maint\) removing previous maintainers [\#157](https://github.com/puppetlabs/vmfloaty/pull/157) ([binford2k](https://github.com/binford2k))
- Update rspec requirement from ~\> 3.10.0 to ~\> 3.11.0 [\#155](https://github.com/puppetlabs/vmfloaty/pull/155) ([dependabot[bot]](https://github.com/apps/dependabot))
- Docs on contributing and releasing [\#152](https://github.com/puppetlabs/vmfloaty/pull/152) ([genebean](https://github.com/genebean))
## [v1.6.0](https://github.com/puppetlabs/vmfloaty/tree/v1.6.0) (2022-02-16)
[Full Changelog](https://github.com/puppetlabs/vmfloaty/compare/v1.5.0...v1.6.0)
**Merged pull requests:**
- \(DIO-2700\) Vmfloaty should not use the Colorize gem [\#156](https://github.com/puppetlabs/vmfloaty/pull/156) ([sbeaulie](https://github.com/sbeaulie))
- \(maint\) Fix up nspooler list active bug [\#154](https://github.com/puppetlabs/vmfloaty/pull/154) ([cthorn42](https://github.com/cthorn42))
- Minor cleanup to the readme [\#151](https://github.com/puppetlabs/vmfloaty/pull/151) ([genebean](https://github.com/genebean))
- Release prep for 1.5.0 [\#150](https://github.com/puppetlabs/vmfloaty/pull/150) ([genebean](https://github.com/genebean))
## [v1.5.0](https://github.com/puppetlabs/vmfloaty/tree/v1.5.0) (2021-10-12)
[Full Changelog](https://github.com/puppetlabs/vmfloaty/compare/v1.4.0...v1.5.0)
**Merged pull requests:**
- DIO 2412- Ondemand and Priority flag added to SSH command [\#149](https://github.com/puppetlabs/vmfloaty/pull/149) ([tanisha-payne](https://github.com/tanisha-payne))
- \(DIO-2135\) Update docker FROM image to ruby 3.0.2 [\#148](https://github.com/puppetlabs/vmfloaty/pull/148) ([sbeaulie](https://github.com/sbeaulie))
- Migrate CI to GitHub Actions [\#147](https://github.com/puppetlabs/vmfloaty/pull/147) ([genebean](https://github.com/genebean))
- v1.4.0 release prep [\#146](https://github.com/puppetlabs/vmfloaty/pull/146) ([genebean](https://github.com/genebean))
## [v1.4.0](https://github.com/puppetlabs/vmfloaty/tree/v1.4.0) (2021-07-16)
[Full Changelog](https://github.com/puppetlabs/vmfloaty/compare/v1.3.0...v1.4.0)
**Merged pull requests:**
- \(maint\) Use latest Faraday/webmock, update specs [\#145](https://github.com/puppetlabs/vmfloaty/pull/145) ([nmburgan](https://github.com/nmburgan))
- Update commander requirement from \>= 4.4.3, \< 4.6.0 to \>= 4.4.3, \< 4.7.0 [\#140](https://github.com/puppetlabs/vmfloaty/pull/140) ([dependabot[bot]](https://github.com/apps/dependabot))
- Release prep for v1.3.0 [\#137](https://github.com/puppetlabs/vmfloaty/pull/137) ([sbeaulie](https://github.com/sbeaulie))
- Run the rubocop auto\_correct [\#135](https://github.com/puppetlabs/vmfloaty/pull/135) ([sbeaulie](https://github.com/sbeaulie))
## [v1.3.0](https://github.com/puppetlabs/vmfloaty/tree/v1.3.0) (2021-03-03)
[Full Changelog](https://github.com/puppetlabs/vmfloaty/compare/v1.2.0...v1.3.0)
**Merged pull requests:**
- \(DIO-1522\) Show the VM state \(running, destroyed\) and colorize in red… [\#134](https://github.com/puppetlabs/vmfloaty/pull/134) ([sbeaulie](https://github.com/sbeaulie))
- Release prep for 1.2.0 [\#132](https://github.com/puppetlabs/vmfloaty/pull/132) ([genebean](https://github.com/genebean))
- Update rubocop requirement from ~\> 0.52 to ~\> 1.6 [\#124](https://github.com/puppetlabs/vmfloaty/pull/124) ([dependabot[bot]](https://github.com/apps/dependabot))
## [v1.2.0](https://github.com/puppetlabs/vmfloaty/tree/v1.2.0) (2021-02-11)
[Full Changelog](https://github.com/puppetlabs/vmfloaty/compare/v1.1.1...v1.2.0)
**Merged pull requests:**
- \(DIO-908\) Floaty can now report the status of ABS requests [\#131](https://github.com/puppetlabs/vmfloaty/pull/131) ([sbeaulie](https://github.com/sbeaulie))
- Update rspec requirement from ~\> 3.9.0 to ~\> 3.10.0 [\#116](https://github.com/puppetlabs/vmfloaty/pull/116) ([dependabot[bot]](https://github.com/apps/dependabot))
## [v1.1.1](https://github.com/puppetlabs/vmfloaty/tree/v1.1.1) (2020-10-16)
[Full Changelog](https://github.com/puppetlabs/vmfloaty/compare/v1.1.0...v1.1.1)
**Merged pull requests:**
- V1.1.1 [\#112](https://github.com/puppetlabs/vmfloaty/pull/112) ([sbeaulie](https://github.com/sbeaulie))
## [v1.1.0](https://github.com/puppetlabs/vmfloaty/tree/v1.1.0) (2020-10-09)
[Full Changelog](https://github.com/puppetlabs/vmfloaty/compare/v1.0.0...v1.1.0)
**Merged pull requests:**
- \(maint\) Add more uniqueness to jobid and useful termination message [\#107](https://github.com/puppetlabs/vmfloaty/pull/107) ([sbeaulie](https://github.com/sbeaulie))
- \(maint\) Fix bug with detecting ABS service [\#106](https://github.com/puppetlabs/vmfloaty/pull/106) ([sbeaulie](https://github.com/sbeaulie))
- \(maint\) Don't require config file for list --active [\#105](https://github.com/puppetlabs/vmfloaty/pull/105) ([sbeaulie](https://github.com/sbeaulie))
- \(maint\) Don't require configuration file for get [\#104](https://github.com/puppetlabs/vmfloaty/pull/104) ([nwolfe](https://github.com/nwolfe))
- \(maint\) Add vmpooler\_fallback to the get service check [\#103](https://github.com/puppetlabs/vmfloaty/pull/103) ([cthorn42](https://github.com/cthorn42))
- Update completion scripts for `service` subcommands [\#102](https://github.com/puppetlabs/vmfloaty/pull/102) ([scotje](https://github.com/scotje))
- Bump to version 1.0.0 [\#100](https://github.com/puppetlabs/vmfloaty/pull/100) ([genebean](https://github.com/genebean))
## [v1.0.0](https://github.com/puppetlabs/vmfloaty/tree/v1.0.0) (2020-09-22)
[Full Changelog](https://github.com/puppetlabs/vmfloaty/compare/0.11.1...v1.0.0)
**Merged pull requests:**
- \(maint\) Fix for ABS PR\#306 that includes json responses [\#99](https://github.com/puppetlabs/vmfloaty/pull/99) ([sbeaulie](https://github.com/sbeaulie))
- \(maint\) Support any vmpooler for ABS via vmpooler\_fallback [\#98](https://github.com/puppetlabs/vmfloaty/pull/98) ([sbeaulie](https://github.com/sbeaulie))
- \(DIO-991\) Add service command [\#97](https://github.com/puppetlabs/vmfloaty/pull/97) ([genebean](https://github.com/genebean))
- \( DIO-911\) Include job\_id in ABS --json output [\#96](https://github.com/puppetlabs/vmfloaty/pull/96) ([sbeaulie](https://github.com/sbeaulie))
- ABS enables fallback to vmpooler for some scenarios [\#94](https://github.com/puppetlabs/vmfloaty/pull/94) ([sbeaulie](https://github.com/sbeaulie))
- WIP \(DIO-911\) Include job\_id in ABS --json output [\#92](https://github.com/puppetlabs/vmfloaty/pull/92) ([nwolfe](https://github.com/nwolfe))
- \(maint\) Remove warning about missing configuration file [\#91](https://github.com/puppetlabs/vmfloaty/pull/91) ([nwolfe](https://github.com/nwolfe))
- Add tab completion script for zsh and fix bash completion for ABS services [\#90](https://github.com/puppetlabs/vmfloaty/pull/90) ([scotje](https://github.com/scotje))
## [0.11.1](https://github.com/puppetlabs/vmfloaty/tree/0.11.1) (2020-08-20)
[Full Changelog](https://github.com/puppetlabs/vmfloaty/compare/0.10.0...0.11.1)
**Merged pull requests:**
- Update version for 0.11.1 release [\#89](https://github.com/puppetlabs/vmfloaty/pull/89) ([mattkirby](https://github.com/mattkirby))
- \(maint\) Fix using ABS service without a .vmfloaty.yml file [\#88](https://github.com/puppetlabs/vmfloaty/pull/88) ([cthorn42](https://github.com/cthorn42))
- Fix the argument list for nonstandardpooler [\#87](https://github.com/puppetlabs/vmfloaty/pull/87) ([jarretlavallee](https://github.com/jarretlavallee))
- Json output for delete/list + better ABS error handling [\#86](https://github.com/puppetlabs/vmfloaty/pull/86) ([mcdonaldseanp](https://github.com/mcdonaldseanp))
- Bump version for 0.11.0 release [\#85](https://github.com/puppetlabs/vmfloaty/pull/85) ([mattkirby](https://github.com/mattkirby))
- Print all non-success output to STDERR [\#84](https://github.com/puppetlabs/vmfloaty/pull/84) ([austb](https://github.com/austb))
- Update travis.yml [\#83](https://github.com/puppetlabs/vmfloaty/pull/83) ([rooneyshuman](https://github.com/rooneyshuman))
- Bump version to 0.10.0 for release [\#82](https://github.com/puppetlabs/vmfloaty/pull/82) ([mattkirby](https://github.com/mattkirby))
## [0.10.0](https://github.com/puppetlabs/vmfloaty/tree/0.10.0) (2020-08-04)
[Full Changelog](https://github.com/puppetlabs/vmfloaty/compare/v0.9.2-retag-for-gh-actions-for-real...0.10.0)
**Merged pull requests:**
- Update Dependabot config file [\#78](https://github.com/puppetlabs/vmfloaty/pull/78) ([dependabot-preview[bot]](https://github.com/apps/dependabot-preview))
- Update rspec requirement from ~\> 3.5.0 to ~\> 3.9.0 [\#75](https://github.com/puppetlabs/vmfloaty/pull/75) ([dependabot-preview[bot]](https://github.com/apps/dependabot-preview))
- Update commander requirement from ~\> 4.4.3 to \>= 4.4.3, \< 4.6.0 [\#73](https://github.com/puppetlabs/vmfloaty/pull/73) ([dependabot-preview[bot]](https://github.com/apps/dependabot-preview))
- Fix formatting of CODEOWNERS [\#71](https://github.com/puppetlabs/vmfloaty/pull/71) ([genebean](https://github.com/genebean))
- Add Dependabot and Coveralls [\#70](https://github.com/puppetlabs/vmfloaty/pull/70) ([genebean](https://github.com/genebean))
- Update docs [\#69](https://github.com/puppetlabs/vmfloaty/pull/69) ([genebean](https://github.com/genebean))
- Remove old maintainer note [\#68](https://github.com/puppetlabs/vmfloaty/pull/68) ([briancain](https://github.com/briancain))
- Add support for vmpooler on demand provisioning [\#67](https://github.com/puppetlabs/vmfloaty/pull/67) ([mattkirby](https://github.com/mattkirby))
## [v0.9.2-retag-for-gh-actions-for-real](https://github.com/puppetlabs/vmfloaty/tree/v0.9.2-retag-for-gh-actions-for-real) (2020-02-05)
[Full Changelog](https://github.com/puppetlabs/vmfloaty/compare/v0.9.2...v0.9.2-retag-for-gh-actions-for-real)
**Merged pull requests:**
- Update gempush action to remove GPR publish [\#66](https://github.com/puppetlabs/vmfloaty/pull/66) ([highb](https://github.com/highb))
## [v0.9.2](https://github.com/puppetlabs/vmfloaty/tree/v0.9.2) (2020-02-05)
[Full Changelog](https://github.com/puppetlabs/vmfloaty/compare/v0.9.2-retag-for-gh-actions...v0.9.2)
## [v0.9.2-retag-for-gh-actions](https://github.com/puppetlabs/vmfloaty/tree/v0.9.2-retag-for-gh-actions) (2020-02-05)
[Full Changelog](https://github.com/puppetlabs/vmfloaty/compare/v0.9.0...v0.9.2-retag-for-gh-actions)
**Merged pull requests:**
- Bump version.rb to 0.9.2 for release [\#65](https://github.com/puppetlabs/vmfloaty/pull/65) ([highb](https://github.com/highb))
- Create gempush.yml Github Action [\#64](https://github.com/puppetlabs/vmfloaty/pull/64) ([highb](https://github.com/highb))
- Bump faraday dependency for Ruby 2.7 compatibility [\#62](https://github.com/puppetlabs/vmfloaty/pull/62) ([nicklewis](https://github.com/nicklewis))
- SSH Command respects ABS now and tests should fail if the API changes… [\#61](https://github.com/puppetlabs/vmfloaty/pull/61) ([mikkergimenez](https://github.com/mikkergimenez))
- \(QENG-7604\) Add support for Job IDs to ABS delete [\#60](https://github.com/puppetlabs/vmfloaty/pull/60) ([highb](https://github.com/highb))
- Bump version.rb to 0.9.1 [\#59](https://github.com/puppetlabs/vmfloaty/pull/59) ([highb](https://github.com/highb))
- Fix error with delete command for vmpooler and nspooler [\#58](https://github.com/puppetlabs/vmfloaty/pull/58) ([mikkergimenez](https://github.com/mikkergimenez))
## [v0.9.0](https://github.com/puppetlabs/vmfloaty/tree/v0.9.0) (2019-12-17)
[Full Changelog](https://github.com/puppetlabs/vmfloaty/compare/v0.8.2...v0.9.0)
**Implemented enhancements:**
- Add abs vm get [\#53](https://github.com/puppetlabs/vmfloaty/pull/53) ([mikkergimenez](https://github.com/mikkergimenez))
**Fixed bugs:**
- vmfloaty reports an error on latest API version output [\#48](https://github.com/puppetlabs/vmfloaty/issues/48)
**Merged pull requests:**
- ABS will sometimes return null values in the /status/queue endpoint [\#57](https://github.com/puppetlabs/vmfloaty/pull/57) ([mikkergimenez](https://github.com/mikkergimenez))
- Minor version bump to 0.9.0 [\#56](https://github.com/puppetlabs/vmfloaty/pull/56) ([highb](https://github.com/highb))
- Update pooler provider to throw an exception if the API returns non-OK [\#55](https://github.com/puppetlabs/vmfloaty/pull/55) ([highb](https://github.com/highb))
- Update Faraday to 0.15, remove unnecessary headers [\#54](https://github.com/puppetlabs/vmfloaty/pull/54) ([highb](https://github.com/highb))
- change urls in docs to use example.net/.com [\#50](https://github.com/puppetlabs/vmfloaty/pull/50) ([steveax](https://github.com/steveax))
- Rubocop cleanup [\#49](https://github.com/puppetlabs/vmfloaty/pull/49) ([rodjek](https://github.com/rodjek))
## [v0.8.2](https://github.com/puppetlabs/vmfloaty/tree/v0.8.2) (2018-01-05)
[Full Changelog](https://github.com/puppetlabs/vmfloaty/compare/v0.8.1...v0.8.2)
**Merged pull requests:**
- 🎂🎂🎂 Add --json option for `floaty get` [\#47](https://github.com/puppetlabs/vmfloaty/pull/47) ([nicklewis](https://github.com/nicklewis))
## [v0.8.1](https://github.com/puppetlabs/vmfloaty/tree/v0.8.1) (2017-10-24)
[Full Changelog](https://github.com/puppetlabs/vmfloaty/compare/v0.8.0...v0.8.1)
**Merged pull requests:**
- Bump commander version to clear up deprecation warnings [\#46](https://github.com/puppetlabs/vmfloaty/pull/46) ([highb](https://github.com/highb))
## [v0.8.0](https://github.com/puppetlabs/vmfloaty/tree/v0.8.0) (2017-10-13)
[Full Changelog](https://github.com/puppetlabs/vmfloaty/compare/v0.7.9...v0.8.0)
**Closed issues:**
- don't automatically call system pager for help screens [\#20](https://github.com/puppetlabs/vmfloaty/issues/20)
**Merged pull requests:**
- Add configuration for multiple pooler services and integration with nspooler [\#45](https://github.com/puppetlabs/vmfloaty/pull/45) ([caseywilliams](https://github.com/caseywilliams))
## [v0.7.9](https://github.com/puppetlabs/vmfloaty/tree/v0.7.9) (2017-07-31)
[Full Changelog](https://github.com/puppetlabs/vmfloaty/compare/v0.7.8...v0.7.9)
**Closed issues:**
- Handle when vmfloaty cannot reach vmpooler [\#39](https://github.com/puppetlabs/vmfloaty/issues/39)
**Merged pull requests:**
- Add basic bash completion script and framework for others [\#44](https://github.com/puppetlabs/vmfloaty/pull/44) ([scotje](https://github.com/scotje))
- Developersdevelopersdevelopers [\#43](https://github.com/puppetlabs/vmfloaty/pull/43) ([mckern](https://github.com/mckern))
## [v0.7.8](https://github.com/puppetlabs/vmfloaty/tree/v0.7.8) (2016-12-20)
[Full Changelog](https://github.com/puppetlabs/vmfloaty/compare/v0.7.7...v0.7.8)
## [v0.7.7](https://github.com/puppetlabs/vmfloaty/tree/v0.7.7) (2016-12-14)
[Full Changelog](https://github.com/puppetlabs/vmfloaty/compare/v0.7.6...v0.7.7)
## [v0.7.6](https://github.com/puppetlabs/vmfloaty/tree/v0.7.6) (2016-12-09)
[Full Changelog](https://github.com/puppetlabs/vmfloaty/compare/v0.7.5...v0.7.6)
**Closed issues:**
- Improve the help text for floaty commands [\#41](https://github.com/puppetlabs/vmfloaty/issues/41)
- Require force flag for pool requests larger than 5? [\#40](https://github.com/puppetlabs/vmfloaty/issues/40)
## [v0.7.5](https://github.com/puppetlabs/vmfloaty/tree/v0.7.5) (2016-12-06)
[Full Changelog](https://github.com/puppetlabs/vmfloaty/compare/1f86113243eb2e898b21c29892c05477e3487d2d...v0.7.5)
**Implemented enhancements:**
- Improve how to specify number of vms in get request [\#8](https://github.com/puppetlabs/vmfloaty/issues/8)
- Improve output from commands [\#3](https://github.com/puppetlabs/vmfloaty/issues/3)
**Fixed bugs:**
- floaty snapshot fails to authenticate [\#4](https://github.com/puppetlabs/vmfloaty/issues/4)
**Closed issues:**
- floaty snapshot should warn users about how long it takes to get snapshot [\#37](https://github.com/puppetlabs/vmfloaty/issues/37)
- floaty modify should allow to make changes on more than one machine [\#36](https://github.com/puppetlabs/vmfloaty/issues/36)
- Pooler.modify raises exception if Token is nil [\#34](https://github.com/puppetlabs/vmfloaty/issues/34)
- Improve error handling in Pooler and Auth classes [\#33](https://github.com/puppetlabs/vmfloaty/issues/33)
- Handle vmpooler responses when token is invalid in Pooler class [\#32](https://github.com/puppetlabs/vmfloaty/issues/32)
- Misuse of 'floaty revert ...' seems to create a new snapshot [\#31](https://github.com/puppetlabs/vmfloaty/issues/31)
- Test [\#30](https://github.com/puppetlabs/vmfloaty/issues/30)
- Don't system exit in Auth class [\#29](https://github.com/puppetlabs/vmfloaty/issues/29)
- Add flag to auto-ssh into a newly created \(single\) VM [\#28](https://github.com/puppetlabs/vmfloaty/issues/28)
- Handle vmpooler URL key that doesn't have 'https' [\#27](https://github.com/puppetlabs/vmfloaty/issues/27)
- Abstract vmfloaty cli related errors to command class rather than pooler class [\#26](https://github.com/puppetlabs/vmfloaty/issues/26)
- Don't puts results in `delete` method in Pooler library [\#25](https://github.com/puppetlabs/vmfloaty/issues/25)
- Improve `get` output [\#24](https://github.com/puppetlabs/vmfloaty/issues/24)
- specs don't work [\#22](https://github.com/puppetlabs/vmfloaty/issues/22)
- Add ability to get additional disk space for a running vm [\#19](https://github.com/puppetlabs/vmfloaty/issues/19)
- Have a force option for `delete --all` [\#17](https://github.com/puppetlabs/vmfloaty/issues/17)
- Stop printing json response in library methods [\#14](https://github.com/puppetlabs/vmfloaty/issues/14)
- Stop system exiting in library methods [\#13](https://github.com/puppetlabs/vmfloaty/issues/13)
- List active vms for a given token [\#12](https://github.com/puppetlabs/vmfloaty/issues/12)
- Provide a way to clean up vms obtained by a token [\#11](https://github.com/puppetlabs/vmfloaty/issues/11)
- Allow spaces when passing in vms for commands [\#10](https://github.com/puppetlabs/vmfloaty/issues/10)
- Write Tests for Pooler class [\#9](https://github.com/puppetlabs/vmfloaty/issues/9)
- Document all valid config file keys [\#7](https://github.com/puppetlabs/vmfloaty/issues/7)
- Write up simple "introduction" to using the tool [\#6](https://github.com/puppetlabs/vmfloaty/issues/6)
- Document how to use Pooler class for ruby scripts [\#5](https://github.com/puppetlabs/vmfloaty/issues/5)
- Convert vmfloaty to use latest pooler API [\#1](https://github.com/puppetlabs/vmfloaty/issues/1)
**Merged pull requests:**
- Show the status of pools with `floaty status` [\#38](https://github.com/puppetlabs/vmfloaty/pull/38) ([nicklewis](https://github.com/nicklewis))
- Show tag values in `list --active` [\#23](https://github.com/puppetlabs/vmfloaty/pull/23) ([justinstoller](https://github.com/justinstoller))
- \(\#19\) Update vmfloaty to expect /api/v1 in URL for disk endpoint [\#21](https://github.com/puppetlabs/vmfloaty/pull/21) ([briancain](https://github.com/briancain))
- \(\#17\) Add a force option for delete --all [\#18](https://github.com/puppetlabs/vmfloaty/pull/18) ([briancain](https://github.com/briancain))
- \(\#12\) List active vms for a given token [\#16](https://github.com/puppetlabs/vmfloaty/pull/16) ([briancain](https://github.com/briancain))
- Cleanup vmfloaty library and command processor [\#15](https://github.com/puppetlabs/vmfloaty/pull/15) ([briancain](https://github.com/briancain))
- \(\#1\) Update with commander [\#2](https://github.com/puppetlabs/vmfloaty/pull/2) ([briancain](https://github.com/briancain))
\* *This Changelog was automatically generated by [github_changelog_generator](https://github.com/github-changelog-generator/github-changelog-generator)*

View file

@ -1,3 +0,0 @@
# Set the default code owners
* @puppetlabs/release-engineering

View file

@ -1,23 +0,0 @@
FROM ruby:3.3.5-slim-bullseye
LABEL org.opencontainers.image.authors="@puppetlabs/release-engineering"
LABEL org.opencontainers.image.title="vmfloaty"
LABEL org.opencontainers.image.source=https://github.com/puppetlabs/vmfloaty
LABEL org.opencontainers.image.description="A CLI helper tool for VMPooler"
RUN apt-get update -qq && apt-get install -y build-essential less make openssh-client
RUN groupadd --gid 1000 floatygroup \
&& useradd --uid 1000 --gid 1000 -m floatyuser
USER floatyuser
WORKDIR /home/floatyuser/app
COPY --chown=floatyuser:floatygroup . .
RUN gem install bundler \
&& bundle install \
&& gem build vmfloaty.gemspec \
&& gem install vmfloaty*.gem
ENTRYPOINT [ "floaty" ]

13
Gemfile
View file

@ -1,5 +1,3 @@
# frozen_string_literal: true
source 'https://rubygems.org'
gemspec
@ -7,12 +5,7 @@ gemspec
gem 'rake', require: false
group :test do
gem 'simplecov', '~> 0.22.0'
gem 'simplecov-html', '~> 0.13.1'
gem 'simplecov-lcov', '~> 0.8.0'
gem 'pry'
gem 'rb-readline'
gem 'rspec', '~> 3.13.0'
gem 'rubocop', '~> 1.66'
gem 'webmock', '~> 3.23'
gem 'rspec', '~> 3.5.0'
gem 'rubocop', '~> 0.52'
gem 'webmock', '1.21.0'
end

View file

@ -1,125 +0,0 @@
PATH
remote: .
specs:
vmfloaty (1.8.1)
commander (>= 4.4.3, < 4.7.0)
faraday (~> 1.5, >= 1.5.1)
GEM
remote: https://rubygems.org/
specs:
addressable (2.8.6)
public_suffix (>= 2.0.2, < 6.0)
ast (2.4.2)
bigdecimal (3.1.8)
coderay (1.1.3)
commander (4.6.0)
highline (~> 2.0.0)
crack (1.0.0)
bigdecimal
rexml
diff-lcs (1.5.1)
docile (1.4.0)
faraday (1.10.3)
faraday-em_http (~> 1.0)
faraday-em_synchrony (~> 1.0)
faraday-excon (~> 1.1)
faraday-httpclient (~> 1.0)
faraday-multipart (~> 1.0)
faraday-net_http (~> 1.0)
faraday-net_http_persistent (~> 1.0)
faraday-patron (~> 1.0)
faraday-rack (~> 1.0)
faraday-retry (~> 1.0)
ruby2_keywords (>= 0.0.4)
faraday-em_http (1.0.0)
faraday-em_synchrony (1.0.0)
faraday-excon (1.1.0)
faraday-httpclient (1.0.1)
faraday-multipart (1.0.4)
multipart-post (~> 2)
faraday-net_http (1.0.1)
faraday-net_http_persistent (1.2.0)
faraday-patron (1.0.0)
faraday-rack (1.0.0)
faraday-retry (1.0.3)
hashdiff (1.1.0)
highline (2.0.3)
json (2.7.2)
language_server-protocol (3.17.0.3)
method_source (1.0.0)
multipart-post (2.3.0)
parallel (1.26.3)
parser (3.3.5.0)
ast (~> 2.4.1)
racc
pry (0.14.2)
coderay (~> 1.1)
method_source (~> 1.0)
public_suffix (5.0.5)
racc (1.8.1)
rainbow (3.1.1)
rake (13.2.1)
rb-readline (0.5.5)
regexp_parser (2.9.2)
rexml (3.3.6)
strscan
rspec (3.13.0)
rspec-core (~> 3.13.0)
rspec-expectations (~> 3.13.0)
rspec-mocks (~> 3.13.0)
rspec-core (3.13.0)
rspec-support (~> 3.13.0)
rspec-expectations (3.13.0)
diff-lcs (>= 1.2.0, < 2.0)
rspec-support (~> 3.13.0)
rspec-mocks (3.13.0)
diff-lcs (>= 1.2.0, < 2.0)
rspec-support (~> 3.13.0)
rspec-support (3.13.0)
rubocop (1.66.1)
json (~> 2.3)
language_server-protocol (>= 3.17.0)
parallel (~> 1.10)
parser (>= 3.3.0.2)
rainbow (>= 2.2.2, < 4.0)
regexp_parser (>= 2.4, < 3.0)
rubocop-ast (>= 1.32.2, < 2.0)
ruby-progressbar (~> 1.7)
unicode-display_width (>= 2.4.0, < 3.0)
rubocop-ast (1.32.3)
parser (>= 3.3.1.0)
ruby-progressbar (1.13.0)
ruby2_keywords (0.0.5)
simplecov (0.22.0)
docile (~> 1.1)
simplecov-html (~> 0.11)
simplecov_json_formatter (~> 0.1)
simplecov-html (0.13.1)
simplecov-lcov (0.8.0)
simplecov_json_formatter (0.1.4)
strscan (3.1.0)
unicode-display_width (2.5.0)
webmock (3.23.1)
addressable (>= 2.8.0)
crack (>= 0.3.2)
hashdiff (>= 0.4.0, < 2.0.0)
PLATFORMS
aarch64-linux
x86_64-linux
DEPENDENCIES
pry
rake
rb-readline
rspec (~> 3.13.0)
rubocop (~> 1.66)
simplecov (~> 0.22.0)
simplecov-html (~> 0.13.1)
simplecov-lcov (~> 0.8.0)
vmfloaty!
webmock (~> 3.23)
BUNDLED WITH
2.4.8

216
README.md
View file

@ -1,76 +1,40 @@
# vmfloaty
vmfloaty
========
[![Gem Version](https://badge.fury.io/rb/vmfloaty.svg)](https://badge.fury.io/rb/vmfloaty)
[![Test](https://github.com/puppetlabs/vmfloaty/actions/workflows/test.yml/badge.svg)](https://github.com/puppetlabs/vmfloaty/actions/workflows/test.yml)
[![Gem Version](https://badge.fury.io/rb/vmfloaty.svg)](https://badge.fury.io/rb/vmfloaty) [![Build Status](https://travis-ci.org/briancain/vmfloaty.svg?branch=master)](https://travis-ci.org/briancain/vmfloaty)
A CLI helper tool for [Puppet's VMPooler](https://github.com/puppetlabs/vmpooler) to help you stay afloat.
A CLI helper tool for [Puppet Labs vmpooler](https://github.com/puppetlabs/vmpooler) to help you stay afloat.
![float image](float.jpg)
<img src="http://i.imgur.com/xGcGwuH.jpg" width=200 height=200>
- [vmfloaty](#vmfloaty)
- [Install](#install)
- [Ruby](#ruby)
- [Docker](#docker)
- [Usage](#usage)
- [Example workflow](#example-workflow)
- [vmfloaty dotfile](#vmfloaty-dotfile)
- [Basic configuration](#basic-configuration)
- [Using multiple services](#using-multiple-services)
- [Using backends besides VMPooler](#using-backends-besides-vmpooler)
- [Valid config keys](#valid-config-keys)
- [Tab Completion](#tab-completion)
- [VMPooler API](#vmpooler-api)
- [Using the Pooler class](#using-the-pooler-class)
- [Example Projects](#example-projects)
- [Contributing](#contributing)
- [Code Reviews](#code-reviews)
- [Releasing](#releasing)
- [Special thanks](#special-thanks)
This project is still supported by @briancain and @demophoon. Ping either of us if you'd like something merged and released.
## Install
### Ruby
Grab the latest from ruby gems...
```bash
gem install vmfloaty
```
### Docker
Run the docker image:
`docker run -it --rm -v ~/.vmfloaty.yml:/home/floatyuser/.vmfloaty.yml ghcr.io/puppetlabs/vmfloaty --help`
$ gem install vmfloaty
...
...
$ floaty --help
```
## Usage
```plain
$ floaty --help
NAME:
floaty
DESCRIPTION:
A CLI helper tool for Puppet's VMPooler to help you stay afloat
COMMANDS:
completion Outputs path to completion script
delete Schedules the deletion of a host or hosts
get Gets a vm or vms based on the os argument
help Display global or [command] help documentation
list Shows a list of available vms from the pooler or vms obtained with a token
modify Modify a VM's tags, time to live, disk space, or reservation reason
query Get information about a given vm
revert Reverts a vm to a specified snapshot
service Display information about floaty services and their configuration
snapshot Takes a snapshot of a given vm
ssh Grabs a single vm and sshs into it
status Prints the status of pools in the pooler service
summary Prints a summary of a pooler service
token Retrieves or deletes a token or checks token status
```
delete Schedules the deletion of a host or hosts
get Gets a vm or vms based on the os argument
help Display global or [command] help documentation
list Shows a list of available vms from the pooler or vms obtained with a token
modify Modify a vms tags, time to live, and disk space
query Get information about a given vm
revert Reverts a vm to a specified snapshot
snapshot Takes a snapshot of a given vm
ssh Grabs a single vm and sshs into it
status Prints the status of pools in vmpooler
summary Prints a summary of vmpooler
token Retrieves or deletes a token or checks token status
GLOBAL OPTIONS:
@ -88,62 +52,111 @@ $ floaty --help
Grabbing a token for authenticated pooler requests:
```bash
floaty token get --user username --url https://vmpooler.example.net/api/v1
```
floaty token get --user username --url https://vmpooler.mycompany.net/api/v1
```
This command will then ask you to log in. If successful, it will return a token that you can save either in a dotfile or use with other cli commands.
Grabbing vms:
```bash
floaty get centos-7-x86_64=2 debian-7-x86_64 windows-10=3 --token mytokenstring --url https://vmpooler.example.net/api/v1
```
floaty get centos-7-x86_64=2 debian-7-x86_64 windows-10=3 --token mytokenstring --url https://vmpooler.mycompany.net/api/v1
```
### vmfloaty dotfile
If you do not wish to continually specify various config options with the cli, you can `~/.vmfloaty.yml` for some defaults. You can get a list of valid service types and example configuration files via `floaty service types` and `floaty service examples`, respectively.
If you do not wish to continuely specify various config options with the cli, you can have a dotfile in your home directory for some defaults. For example:
#### Basic configuration
This is the simplest type of configuration where you only need a single service:
```yaml
# file at ~/.vmfloaty.yml
url: 'https://vmpooler.example.net/api/v1'
# file at /Users/me/.vmfloaty.yml
url: 'https://vmpooler.mycompany.net/api/v1'
user: 'brian'
token: 'tokenstring'
```
Run `floaty service examples` to see additional configuration options
Now vmfloaty will use those config files if no flag was specified.
#### Using multiple services
#### Configuring multiple services
Most commands allow you to specify a `--service <servicename>` option to allow the use of multiple pooler instances. This can be useful when you'd rather not specify a `--url` or `--token` by hand for alternate services.
Most commands allow you to specify a `--service <servicename>` option to allow the use of multiple vmpooler instances. This can be useful when you'd rather not specify a `--url` or `--token` by hand for alternate services.
To configure multiple services, you can set up your `~/.vmfloaty.yml` config file like this:
```yaml
# file at /Users/me/.vmfloaty.yml
user: 'brian'
services:
main:
url: 'https://vmpooler.mycompany.net/api/v1'
token: 'tokenstring'
alternate:
url: 'https://vmpooler.alternate.net/api/v1'
token: 'alternate-tokenstring'
```
- If you run `floaty` without a `--service <name>` option, vmfloaty will use the first configured service by default.
With the config file above, the default would be to use the 'main' vmpooler instance.
- If keys are missing for a configured service, vmfloaty will attempt to fall back to the top-level values.
This makes it so you can specify things like `user` once at the top of your `~/.vmfloaty.yml`.
With the config file above, 'brian' will be used as the username for both configured services, since neither specifies a username.
#### Using backends besides VMPooler
Examples using the above configuration:
vmfloaty supports additional backends besides VMPooler. To see a complete list, run `floaty service types`. The output of `floaty service examples` will show you how to configure each of the supported backends.
List available vm types from our main vmpooler instance:
```sh
floaty list --service main
# or, since the first configured service is used by default:
floaty list
```
List available vm types from our alternate vmpooler instance:
```sh
floaty list --service alternate
```
#### Using a Nonstandard Pooler service
vmfloaty is capable of working with Puppet's [nonstandard pooler](https://github.com/puppetlabs/nspooler) in addition to the default vmpooler API. To add a nonstandard pooler service, specify an API `type` value in your service configuration, like this:
```yaml
# file at /Users/me/.vmfloaty.yml
user: 'brian'
services:
vm:
url: 'https://vmpooler.mycompany.net/api/v1'
token: 'tokenstring'
ns:
url: 'https://nspooler.mycompany.net/api/v1'
token: 'nspooler-tokenstring'
type: 'nonstandard' # <-- 'type' is necessary for any non-vmpooler service
```
With this configuration, you could list available OS types from nspooler like this:
```sh
floaty list --service ns
```
#### Valid config keys
Here are the keys that vmfloaty currently supports:
- verbose (Boolean)
- token (String)
- user (String)
- url (String)
- services (String)
- type (String)
- vmpooler_fallback (String)
- verbose
+ Boolean
- token
+ String
- user
+ String
- url
+ String
- services
+ Map
### Tab Completion
There is a basic completion script for Bash (and possibly other shells) included with the gem in the [extras/completions](https://github.com/puppetlabs/vmfloaty/blob/master/extras/completions) folder. To activate, that file simply needs to be sourced somehow in your shell profile.
There is a basic completion script for Bash (and possibly other shells) included with the gem in the [extras/completions](https://github.com/briancain/vmfloaty/blob/master/extras/completions) folder. To activate, that file simply needs to be sourced somehow in your shell profile.
For convenience, the path to the completion script for the currently active version of the gem can be found with the `floaty completion` subcommand. This makes it easy to add the completion script to your profile like so:
@ -157,44 +170,17 @@ If you are running on macOS and use Homebrew's `bash-completion` formula, you ca
ln -s $(floaty completion --shell bash) /usr/local/etc/bash_completion.d/floaty
```
There is also tab completion for zsh:
## vmpooler API
```zsh
source $(floaty completion --shell zsh)
```
## VMPooler API
This cli tool uses the [VMPooler API](https://github.com/puppetlabs/vmpooler/blob/master/API.md).
This cli tool uses the [vmpooler API](https://github.com/puppetlabs/vmpooler/blob/master/API.md).
## Using the Pooler class
vmfloaty providers a `Pooler` class that gives users the ability to make requests to VMPooler without having to write their own requests. It also provides an `Auth` class for managing VMPooler tokens within your application.
vmfloaty providers a `Pooler` class that gives users the ability to make requests to vmpooler without having to write their own requests. It also provides an `Auth` class for managing vmpooler tokens within your application.
### Example Projects
- [John McCabe: vmpooler-bitbar](https://github.com/johnmccabe/vmpooler-bitbar/)
- vmpooler status and management in your menubar with bitbar
+ vmpooler status and management in your menubar with bitbar
- [Brian Cain: vagrant-vmpooler](https://github.com/briancain/vagrant-vmpooler)
- Use Vagrant to manage your vmpooler instances
## Contributing
PR's are welcome! We always love to see how others think this tool can be made better.
### Code Reviews
Please wait for multiple code owners to sign off on any notable change.
## Releasing
Follow these steps to publish a new GitHub release, build and push the gem to <https://rubygems.org>, and build and push a Docker Image to GitHub Container Registry:
1. Bump the "VERSION" in `lib/vmfloaty/version.rb` appropriately based on changes in `CHANGELOG.md` since the last release.
2. Run `./release-prep` to update `Gemfile.lock` and `CHANGELOG.md`.
3. Commit and push changes to a new branch, then open a pull request against `main` and be sure to add the "maintenance" label.
4. After the pull request is approved and merged, then navigate to <https://github.com/puppetlabs/vmfloaty/actions/workflows/release.yml> --> Run workflow --> select "main" branch --> Run workflow. This will publish a GitHub release, build and push the gem to RubyGems, and build and push a Docker Image to GitHub Container Registry.
## Special thanks
Special thanks to [Brian Cain](https://github.com/briancain) as he is the original author of vmfloaty! Vast amounts of this code exist thanks to his efforts.
+ Use Vagrant to manage your vmpooler instances

View file

@ -1,5 +1,3 @@
# frozen_string_literal: true
require 'rubygems'
require 'bundler/setup'
require 'rspec/core/rake_task'
@ -11,7 +9,7 @@ $stdout.sync = true
$stderr.sync = true
# Change to the directory of this file.
Dir.chdir(File.expand_path(__dir__))
Dir.chdir(File.expand_path('../', __FILE__))
# This installs the tasks that help with gem creation and
# publishing.

View file

@ -1,7 +1,6 @@
#!/usr/bin/env ruby
# frozen_string_literal: true
$LOAD_PATH.unshift(File.expand_path('../lib', __dir__))
$LOAD_PATH.unshift(File.expand_path('../../lib', __FILE__))
require 'vmfloaty'

View file

@ -2,37 +2,29 @@
_vmfloaty()
{
local cur prev commands template_arg_commands hostname_arg_commands service_subcommands
local cur prev subcommands template_subcommands hostname_subcommands
COMPREPLY=()
cur="${COMP_WORDS[COMP_CWORD]}"
prev="${COMP_WORDS[COMP_CWORD-1]}"
commands="delete get help list modify query revert service snapshot ssh status summary token"
template_arg_commands="get ssh"
hostname_arg_commands="delete modify query revert snapshot"
service_subcommands="types examples"
subcommands="delete get help list modify query revert snapshot ssh status summary token"
template_subcommands="get ssh"
hostname_subcommands="delete modify query revert snapshot"
if [[ $cur == -* ]] ; then
# TODO: option completion
COMPREPLY=()
elif [[ $template_arg_commands =~ (^| )$prev($| ) ]] ; then
elif [[ $template_subcommands =~ (^| )$prev($| ) ]] ; then
if [[ -z "$_vmfloaty_avail_templates" ]] ; then
# TODO: need a --hostnameonly equivalent here because the section headers of
# `floaty list` are adding some spurious entries (including files in current
# directory because part of the headers is `**` which is getting expanded)
_vmfloaty_avail_templates=$(floaty list 2>/dev/null)
fi
COMPREPLY=( $(compgen -W "${_vmfloaty_avail_templates}" -- "${cur}") )
elif [[ $hostname_arg_commands =~ (^| )$prev($| ) ]] ; then
_vmfloaty_active_hostnames=$(floaty list --active --hostnameonly 2>/dev/null)
elif [[ $hostname_subcommands =~ (^| )$prev($| ) ]] ; then
_vmfloaty_active_hostnames=$(floaty list --active 2>/dev/null | grep '^-' | cut -d' ' -f2)
COMPREPLY=( $(compgen -W "${_vmfloaty_active_hostnames}" -- "${cur}") )
elif [[ "service" == $prev ]] ; then
COMPREPLY=( $(compgen -W "${service_subcommands}" -- "${cur}") )
elif [[ $1 == $prev ]] ; then
# only show top level commands we are at root
COMPREPLY=( $(compgen -W "${commands}" -- "${cur}") )
else
COMPREPLY=( $(compgen -W "${subcommands}" -- "${cur}") )
fi
}
complete -F _vmfloaty floaty

View file

@ -1,43 +0,0 @@
_floaty()
{
local line commands template_arg_commands hostname_arg_commands service_subcommands
commands="delete get help list modify query revert service snapshot ssh status summary token"
template_arg_commands=("get" "ssh")
hostname_arg_commands=("delete" "modify" "query" "revert" "snapshot")
service_subcommands=("types" "examples")
_arguments -C \
"1: :(${commands})" \
"*::arg:->args"
if ((template_arg_commands[(Ie)$line[1]])); then
_floaty_template_sub
elif ((hostname_arg_commands[(Ie)$line[1]])); then
_floaty_hostname_sub
elif [[ "service" == $line[1] ]]; then
_arguments "1: :(${service_subcommands})"
fi
}
_floaty_template_sub()
{
if [[ -z "$_vmfloaty_avail_templates" ]] ; then
# TODO: need a --hostnameonly equivalent here because the section headers of
# `floaty list` are adding some spurious entries (including files in current
# directory because part of the headers is `**` which is getting expanded)
_vmfloaty_avail_templates=$(floaty list 2>/dev/null)
fi
_arguments "1: :(${_vmfloaty_avail_templates})"
}
_floaty_hostname_sub()
{
_vmfloaty_active_hostnames=$(floaty list --active --hostnameonly 2>/dev/null)
_arguments "1: :(${_vmfloaty_active_hostnames})"
}
compdef _floaty floaty

BIN
float.jpg

Binary file not shown.

Before

Width:  |  Height:  |  Size: 11 KiB

View file

@ -1,7 +1,8 @@
# frozen_string_literal: true
#!/usr/bin/env ruby
require 'rubygems'
require 'commander'
require 'colorize'
require 'json'
require 'pp'
require 'uri'
@ -12,15 +13,13 @@ require 'vmfloaty/conf'
require 'vmfloaty/utils'
require 'vmfloaty/service'
require 'vmfloaty/ssh'
require 'vmfloaty/logger'
class Vmfloaty
include Commander::Methods
def run # rubocop:disable Metrics/AbcSize
def run
program :version, Vmfloaty::VERSION
program :description,
"A CLI helper tool for Puppet's vmpooler to help you stay afloat.\n\nConfiguration may be placed in a ~/.vmfloaty.yml file."
program :description, 'A CLI helper tool for Puppet Labs VM poolers to help you stay afloat'
config = Conf.read_config
@ -34,47 +33,38 @@ class Vmfloaty
c.option '--user STRING', String, 'User to authenticate with'
c.option '--url STRING', String, 'URL of pooler service'
c.option '--token STRING', String, 'Token for pooler service'
c.option '--priority STRING', 'Priority for supported backends(ABS) (High(1), Medium(2), Low(3))'
c.option '--notoken', 'Makes a request without a token'
c.option '--force', 'Forces vmfloaty to get requested vms'
c.option '--json', 'Prints retrieved vms in JSON format'
c.option '--ondemand', 'Requested vms are provisioned upon receival of the request, tracked by a request ID'
c.option '--continue STRING', String, 'resume polling ABS for job_id, for use when the cli was interrupted'
c.option '--loglevel STRING', String, 'the log level to use (debug, info, error)'
c.action do |args, options|
verbose = options.verbose || config['verbose']
FloatyLogger.setlevel = options.loglevel if options.loglevel
service = Service.new(options, config)
use_token = !options.notoken
force = options.force
if args.empty?
FloatyLogger.error 'No operating systems provided to obtain. See `floaty get --help` for more information on how to get VMs.'
STDERR.puts "No operating systems provided to obtain. See `floaty get --help` for more information on how to get VMs."
exit 1
end
os_types = Utils.generate_os_hash(args)
if os_types.empty?
FloatyLogger.error 'No operating systems provided to obtain. See `floaty get --help` for more information on how to get VMs.'
exit 1
end
max_pool_request = 5
large_pool_requests = os_types.select { |_, v| v > max_pool_request }
if !large_pool_requests.empty? && !force
FloatyLogger.error "Requesting vms over #{max_pool_request} requires a --force flag."
FloatyLogger.error 'Try again with `floaty get --force`'
large_pool_requests = os_types.select{|_,v| v > max_pool_request}
if ! large_pool_requests.empty? and ! force
STDERR.puts "Requesting vms over #{max_pool_request} requires a --force flag."
STDERR.puts "Try again with `floaty get --force`"
exit 1
end
response = service.retrieve(verbose, os_types, use_token, options.ondemand, options.continue)
request_id = response['request_id'] if options.ondemand
response = service.wait_for_request(verbose, request_id) if options.ondemand
if os_types.empty?
STDERR.puts "No operating systems provided to obtain. See `floaty get --help` for more information on how to get VMs."
exit 1
end
response = service.retrieve(verbose, os_types, use_token)
hosts = Utils.standardize_hostnames(response)
if options.json || options.ondemand
if options.json
puts JSON.pretty_generate(hosts)
else
puts Utils.format_host_output(hosts)
@ -90,40 +80,19 @@ class Vmfloaty
c.option '--verbose', 'Enables verbose output'
c.option '--service STRING', String, 'Configured pooler service name'
c.option '--active', 'Prints information about active vms for a given token'
c.option '--json', 'Prints information as JSON'
c.option '--hostnameonly', 'When listing active vms, prints only hostnames, one per line'
c.option '--token STRING', String, 'Token for pooler service'
c.option '--url STRING', String, 'URL of pooler service'
c.option '--user STRING', String, 'User to authenticate with'
c.option '--loglevel STRING', String, 'the log level to use (debug, info, error)'
c.action do |args, options|
verbose = options.verbose || config['verbose']
FloatyLogger.setlevel = options.loglevel if options.loglevel
service = Service.new(options, config)
filter = args[0]
if options.active
# list active vms
running_vms = if service.type == 'ABS'
# this is actually job_ids
service.list_active_job_ids(verbose, service.url, service.user)
else
service.list_active(verbose)
end
running_vms = service.list_active(verbose)
host = URI.parse(service.url).host
if running_vms.empty?
if options.json
puts {}.to_json
else
FloatyLogger.info "You have no running VMs on #{host}"
end
elsif options.json
puts Utils.get_host_data(verbose, service, running_vms).to_json
elsif options.hostnameonly
Utils.get_host_data(verbose, service, running_vms).each do |hostname, host_data|
Utils.print_fqdn_for_host(service, hostname, host_data)
end
puts "You have no running VMs on #{host}"
else
puts "Your VMs on #{host}:"
Utils.pretty_print_hosts(verbose, service, running_vms)
@ -139,7 +108,7 @@ class Vmfloaty
command :query do |c|
c.syntax = 'floaty query hostname [options]'
c.summary = 'Get information about a given vm'
c.description = 'Given a hostname from the pooler service, vmfloaty with query the service to get various details about the vm. If using ABS, you can query a job_id'
c.description = 'Given a hostname from the pooler service, vmfloaty with query the service to get various details about the vm.'
c.example 'Get information about a sample host', 'floaty query hostname --url http://vmpooler.example.com'
c.option '--verbose', 'Enables verbose output'
c.option '--service STRING', String, 'Configured pooler service name'
@ -158,8 +127,7 @@ class Vmfloaty
c.syntax = 'floaty modify hostname [options]'
c.summary = 'Modify a VM\'s tags, time to live, disk space, or reservation reason'
c.description = 'This command makes modifications to the virtual machines state in the pooler service. You can either append tags to the vm, increase how long it stays active for, or increase the amount of disk space.'
c.example 'Modifies myhost1 to have a TTL of 12 hours and adds a custom tag',
'floaty modify myhost1 --lifetime 12 --url https://myurl --token mytokenstring --tags \'{"tag":"myvalue"}\''
c.example 'Modifies myhost1 to have a TTL of 12 hours and adds a custom tag', 'floaty modify myhost1 --lifetime 12 --url https://myurl --token mytokenstring --tags \'{"tag":"myvalue"}\''
c.option '--verbose', 'Enables verbose output'
c.option '--service STRING', String, 'Configured pooler service name'
c.option '--url STRING', String, 'URL of pooler service'
@ -175,23 +143,18 @@ class Vmfloaty
hostname = args[0]
modify_all = options.all
if hostname.nil? && !modify_all
FloatyLogger.error 'ERROR: Provide a hostname or specify --all.'
if hostname.nil? and !modify_all
STDERR.puts "ERROR: Provide a hostname or specify --all."
exit 1
end
running_vms =
if modify_all
service.list_active(verbose)
else
hostname.split(',')
end
running_vms = modify_all ? service.list_active(verbose) : hostname.split(",")
tags = options.tags ? JSON.parse(options.tags) : nil
modify_hash = {
lifetime: options.lifetime,
disk: options.disk,
tags: tags,
reason: options.reason
lifetime: options.lifetime,
disk: options.disk,
tags: tags,
reason: options.reason
}
modify_hash.delete_if { |_, value| value.nil? }
@ -199,18 +162,20 @@ class Vmfloaty
ok = true
modified_hash = {}
running_vms.each do |vm|
modified_hash[vm] = service.modify(verbose, vm, modify_hash)
rescue ModifyError => e
FloatyLogger.error e
ok = false
begin
modified_hash[vm] = service.modify(verbose, vm, modify_hash)
rescue ModifyError => e
STDERR.puts e
ok = false
end
end
if ok
if modify_all
puts "Successfully modified all #{running_vms.count} VMs."
puts "Successfully modified all VMs."
else
puts "Successfully modified VM #{hostname}."
end
puts 'Use `floaty list --active` to see the results.'
puts "Use `floaty list --active` to see the results."
end
end
end
@ -218,24 +183,17 @@ class Vmfloaty
command :delete do |c|
c.syntax = 'floaty delete hostname,hostname2 [options]'
c.syntax += "\n floaty delete job1,job2 [options] (only supported with ABS)"
c.summary = 'Schedules the deletion of a host or hosts'
c.description = 'Given a comma separated list of hostnames, or --all for all vms, vmfloaty makes a request to the pooler service to schedule the deletion of those vms. If you are using the ABS service, you can also pass in JobIDs here. Note that passing in a Job ID will delete *all* of the hosts in the job.' # rubocop:disable Layout/LineLength
c.description = 'Given a comma separated list of hostnames, or --all for all vms, vmfloaty makes a request to the pooler service to schedule the deletion of those vms.'
c.example 'Schedules the deletion of a host or hosts', 'floaty delete myhost1,myhost2 --url http://vmpooler.example.com'
c.example 'Schedules the deletion of a JobID or JobIDs', 'floaty delete 1579300120799,1579300120800 --url http://abs.example.com'
c.option '--verbose', 'Enables verbose output'
c.option '--service STRING', String, 'Configured pooler service name'
c.option '--all', 'Deletes all vms acquired by a token'
c.option '-f', 'Does not prompt user when deleting all vms'
c.option '--json', 'Outputs hosts scheduled for deletion as JSON'
c.option '--token STRING', String, 'Token for pooler service'
c.option '--url STRING', String, 'URL of pooler service'
c.option '--user STRING', String, 'User to authenticate with'
c.option '--loglevel STRING', String, 'the log level to use (debug, info, error)'
c.action do |args, options|
verbose = options.verbose || config['verbose']
FloatyLogger.setlevel = options.loglevel if options.loglevel
service = Service.new(options, config)
hostnames = args[0]
delete_all = options.all
@ -245,23 +203,15 @@ class Vmfloaty
successes = []
if delete_all
running_vms = if service.type == 'ABS'
# this is actually job_ids
service.list_active_job_ids(verbose, service.url, service.user)
else
service.list_active(verbose)
end
running_vms = service.list_active(verbose)
if running_vms.empty?
if options.json
puts {}.to_json
else
FloatyLogger.info 'You have no running VMs.'
end
STDERR.puts "You have no running VMs."
else
Utils.pretty_print_hosts(verbose, service, running_vms)
# Confirm deletion
puts
confirmed = true
unless force
Utils.pretty_print_hosts(verbose, service, running_vms, true)
# Confirm deletion
confirmed = agree('Delete all these VMs? [y/N]')
end
if confirmed
@ -286,28 +236,23 @@ class Vmfloaty
end
end
else
FloatyLogger.info 'You did not provide any hosts to delete'
STDERR.puts "You did not provide any hosts to delete"
exit 1
end
unless failures.empty?
FloatyLogger.info 'Unable to delete the following VMs:'
STDERR.puts 'Unable to delete the following VMs:'
failures.each do |hostname|
FloatyLogger.info "- #{hostname}"
STDERR.puts "- #{hostname}"
end
FloatyLogger.info 'Check `floaty list --active`; Do you need to specify a different service?'
STDERR.puts 'Check `floaty list --active`; Do you need to specify a different service?'
end
unless successes.empty?
if options.json
puts successes.to_json
else
puts 'Scheduled the following VMs for deletion:'
output = ''
successes.each do |hostname|
output += "- #{hostname}\n"
end
puts output
puts unless failures.empty?
puts 'Scheduled the following VMs for deletion:'
successes.each do |hostname|
puts "- #{hostname}"
end
end
@ -319,8 +264,7 @@ class Vmfloaty
c.syntax = 'floaty snapshot hostname [options]'
c.summary = 'Takes a snapshot of a given vm'
c.description = 'Will request a snapshot be taken of the given hostname in the pooler service. This command is known to take a while depending on how much load is on the pooler service.'
c.example 'Takes a snapshot for a given host',
'floaty snapshot myvm.example.com --url http://vmpooler.example.com --token a9znth9dn01t416hrguu56ze37t790bl'
c.example 'Takes a snapshot for a given host', 'floaty snapshot myvm.example.com --url http://vmpooler.example.com --token a9znth9dn01t416hrguu56ze37t790bl'
c.option '--verbose', 'Enables verbose output'
c.option '--service STRING', String, 'Configured pooler service name'
c.option '--url STRING', String, 'URL of pooler service'
@ -333,7 +277,7 @@ class Vmfloaty
begin
snapshot_req = service.snapshot(verbose, hostname)
rescue TokenError, ModifyError => e
FloatyLogger.error e
STDERR.puts e
exit 1
end
@ -346,8 +290,7 @@ class Vmfloaty
c.syntax = 'floaty revert hostname snapshot [options]'
c.summary = 'Reverts a vm to a specified snapshot'
c.description = 'Given a snapshot SHA, vmfloaty will request a revert to the pooler service to go back to a previous snapshot.'
c.example 'Reverts to a snapshot for a given host',
'floaty revert myvm.example.com n4eb4kdtp7rwv4x158366vd9jhac8btq --url http://vmpooler.example.com --token a9znth9dn01t416hrguu56ze37t790bl'
c.example 'Reverts to a snapshot for a given host', 'floaty revert myvm.example.com n4eb4kdtp7rwv4x158366vd9jhac8btq --url http://vmpooler.example.com --token a9znth9dn01t416hrguu56ze37t790bl'
c.option '--verbose', 'Enables verbose output'
c.option '--service STRING', String, 'Configured pooler service name'
c.option '--url STRING', String, 'URL of pooler service'
@ -360,13 +303,13 @@ class Vmfloaty
snapshot_sha = args[1] || options.snapshot
if args[1] && options.snapshot
FloatyLogger.info "Two snapshot arguments were given....using snapshot #{snapshot_sha}"
STDERR.puts "Two snapshot arguments were given....using snapshot #{snapshot_sha}"
end
begin
revert_req = service.revert(verbose, hostname, snapshot_sha)
rescue TokenError, ModifyError => e
FloatyLogger.error e
STDERR.puts e
exit 1
end
@ -383,10 +326,8 @@ class Vmfloaty
c.option '--service STRING', String, 'Configured pooler service name'
c.option '--url STRING', String, 'URL of pooler service'
c.option '--json', 'Prints status in JSON format'
c.option '--loglevel STRING', String, 'the log level to use (debug, info, error)'
c.action do |_, options|
verbose = options.verbose || config['verbose']
FloatyLogger.setlevel = options.loglevel if options.loglevel
service = Service.new(options, config)
if options.json
pp service.status(verbose)
@ -431,26 +372,28 @@ class Vmfloaty
begin
case action
when 'get'
token = service.get_new_token(verbose)
puts token
when 'delete'
result = service.delete_token(verbose, options.token)
puts result
when 'status'
token_value = options.token
token_value = args[1] if token_value.nil?
status = service.token_status(verbose, token_value)
puts status
when nil
FloatyLogger.error 'No action provided'
exit 1
else
FloatyLogger.error "Unknown action: #{action}"
exit 1
when 'get'
token = service.get_new_token(verbose)
puts token
when 'delete'
result = service.delete_token(verbose, options.token)
puts result
when 'status'
token_value = options.token
if token_value.nil?
token_value = args[1]
end
status = service.token_status(verbose, token_value)
puts status
when nil
STDERR.puts 'No action provided'
exit 1
else
STDERR.puts "Unknown action: #{action}"
exit 1
end
rescue TokenError => e
FloatyLogger.error e
STDERR.puts e
exit 1
end
exit 0
@ -468,23 +411,23 @@ class Vmfloaty
c.option '--user STRING', String, 'User to authenticate with'
c.option '--token STRING', String, 'Token for pooler service'
c.option '--notoken', 'Makes a request without a token'
c.option '--priority STRING', 'Priority for supported backends(ABS) (High(1), Medium(2), Low(3))'
c.option '--ondemand', 'Requested vms are provisioned upon receival of the request, tracked by a request ID'
c.action do |args, options|
verbose = options.verbose || config['verbose']
service = Service.new(options, config)
use_token = !options.notoken
if args.empty?
FloatyLogger.error 'No operating systems provided to obtain. See `floaty ssh --help` for more information on how to get VMs.'
STDERR.puts "No operating systems provided to obtain. See `floaty ssh --help` for more information on how to get VMs."
exit 1
end
host_os = args.first
FloatyLogger.info "Can't ssh to multiple hosts; Using #{host_os} only..." if args.length > 1
if args.length > 1
STDERR.puts "Can't ssh to multiple hosts; Using #{host_os} only..."
end
service.ssh(verbose, host_os, use_token, options.ondemand)
service.ssh(verbose, host_os, use_token)
exit 0
end
end
@ -492,13 +435,13 @@ class Vmfloaty
command :completion do |c|
c.syntax = 'floaty completion [options]'
c.summary = 'Outputs path to completion script'
c.description = Utils.strip_heredoc(<<-DESCRIPTION)
c.description = Utils.strip_heredoc(<<-EOF)
Outputs path to a completion script for the specified shell (or 'bash' if not specified). This makes it easy to add the completion script to your profile:
source $(floaty completion --shell bash)
This subcommand will exit non-zero with an error message if no completion script is available for the requested shell.
DESCRIPTION
EOF
c.example 'Gets path to bash tab completion script', 'floaty completion --shell bash'
c.option '--shell STRING', String, 'Shell to request completion script for'
c.action do |_, options|
@ -509,71 +452,7 @@ class Vmfloaty
puts completion_file
exit 0
else
FloatyLogger.error "Could not find completion file for '#{shell}': No such file #{completion_file}"
exit 1
end
end
end
command :service do |c|
c.syntax = 'floaty service <types examples>'
c.summary = 'Display information about floaty services and their configuration'
c.description = 'Display information about floaty services to aid in setting up a configuration file.'
c.example 'Print a list of the valid service types', 'floaty service types'
c.example 'Print a sample config file with multiple services', 'floaty service examples'
c.example 'list vms from the service named "nspooler-prod"', 'floaty list --service nspooler-prod'
c.action do |args, _options|
action = args.first
example_config = Utils.strip_heredoc(<<-CONFIG)
# Sample ~/.vmfloaty.yml with just vmpooler
user: 'jdoe'
url: 'https://vmpooler.example.net'
token: '456def789'
# Sample ~/.vmfloaty.yml with multiple services
# Note: when the --service is not specified on the command line,
# the first service listed here is selected automatically
user: 'jdoe'
services:
abs-prod:
type: 'abs'
url: 'https://abs.example.net/api/v2'
token: '123abc456'
vmpooler_fallback: 'vmpooler-prod'
nspooler-prod:
type: 'nspooler'
url: 'https://nspooler.example.net'
token: '789ghi012'
vmpooler-dev:
type: 'vmpooler'
url: 'https://vmpooler-dev.example.net'
token: '987dsa654'
vmpooler-prod:
type: 'vmpooler'
url: 'https://vmpooler.example.net'
token: '456def789'
CONFIG
types_output = Utils.strip_heredoc(<<-TYPES)
The values on the left below can be used in ~/.vmfloaty.yml as the value of type:
abs: Puppet's Always Be Scheduling
nspooler: Puppet's Non-standard Pooler, aka NSPooler
vmpooler: Puppet's VMPooler
TYPES
case action
when 'examples'
FloatyLogger.info example_config
when 'types'
FloatyLogger.info types_output
when nil
FloatyLogger.error 'No action provided'
exit 1
else
FloatyLogger.error "Unknown action: #{action}"
STDERR.puts "Could not find completion file for '#{shell}': No such file #{completion_file}"
exit 1
end
end

View file

@ -1,422 +0,0 @@
# frozen_string_literal: true
require 'vmfloaty/errors'
require 'vmfloaty/http'
require 'vmfloaty/utils'
require 'faraday'
require 'json'
class ABS
# List active VMs in ABS
# This is what a job request looks like:
# {
# "state":"filled",
# "last_processed":"2019-10-31 20:59:33 +0000",
# "allocated_resources": [
# {
# "hostname":"h3oyntawjm7xdch.delivery.puppetlabs.net",
# "type":"centos-7.2-tmpfs-x86_64",
# "engine":"vmpooler"}
# ],
# "audit_log":{
# "2019-10-30 20:33:12 +0000":"Allocated h3oyntawjm7xdch.delivery.puppetlabs.net for job 1572467589"
# },
# "request":{
# "resources":{
# "centos-7.2-tmpfs-x86_64":1
# },
# "job": {
# "id":1572467589,
# "tags": {
# "user":"mikker",
# "url_string":"floaty://mikker/1572467589"
# },
# "user":"mikker",
# "time-received":1572467589
# }
# }
# }
#
@active_hostnames = {}
def self.list_active_job_ids(verbose, url, user)
all_job_ids = []
@active_hostnames = {}
get_active_requests(verbose, url, user).each do |req_hash|
@active_hostnames[req_hash['request']['job']['id']] = req_hash # full hash saved for later retrieval
all_job_ids.push(req_hash['request']['job']['id'])
end
all_job_ids
end
def self.list_active(verbose, url, _token, user)
hosts = []
get_active_requests(verbose, url, user).each do |req_hash|
next unless req_hash.key?('allocated_resources')
req_hash['allocated_resources'].each do |onehost|
hosts.push(onehost['hostname'])
end
end
hosts
end
def self.get_active_requests(verbose, url, user)
conn = Http.get_conn(verbose, supported_abs_url(url))
res = conn.get 'status/queue'
if valid_json?(res.body)
requests = JSON.parse(res.body)
else
FloatyLogger.warn "Warning: couldn't parse body returned from abs/status/queue"
end
ret_val = []
requests.each do |req|
next if req == 'null'
if valid_json?(req) # legacy ABS had another JSON string always-be-scheduling/pull/306
req_hash = JSON.parse(req)
elsif req.is_a?(Hash)
req_hash = req
else
FloatyLogger.warn "Warning: couldn't parse request returned from abs/status/queue"
next
end
begin
next unless user == req_hash['request']['job']['user']
ret_val.push(req_hash)
rescue NoMethodError
FloatyLogger.warn "Warning: couldn't parse user returned from abs/status/queue: "
end
end
ret_val
end
def self.all_job_resources_accounted_for(allocated_resources, hosts)
allocated_host_list = allocated_resources.map { |ar| ar['hostname'] }
(allocated_host_list - hosts).empty?
end
def self.delete(verbose, url, hosts, token, user)
# In ABS terms, this is a "returned" host.
conn = Http.get_conn(verbose, supported_abs_url(url))
conn.headers['X-AUTH-TOKEN'] = token if token
FloatyLogger.info "Trying to delete hosts #{hosts}" if verbose
requests = get_active_requests(verbose, url, user)
jobs_to_delete = []
ret_status = {}
hosts.each do |host|
ret_status[host] = {
'ok' => false
}
end
requests.each do |req_hash|
next unless req_hash['state'] == 'allocated' || req_hash['state'] == 'filled'
if hosts.include? req_hash['request']['job']['id']
jobs_to_delete.push(req_hash)
next
end
req_hash['allocated_resources'].each do |vm_name, _i|
if hosts.include? vm_name['hostname']
if all_job_resources_accounted_for(req_hash['allocated_resources'], hosts)
ret_status[vm_name['hostname']] = {
'ok' => true
}
jobs_to_delete.push(req_hash)
else
FloatyLogger.info "When using ABS you must delete all vms that you requested at the same time: Can't delete #{req_hash['request']['job']['id']}: #{hosts} does not include all of #{req_hash['allocated_resources']}"
end
end
end
end
response_body = {}
jobs_to_delete.each do |job|
req_obj = {
'job_id' => job['request']['job']['id'],
'hosts' => job['allocated_resources']
}
FloatyLogger.info "Deleting #{req_obj}" if verbose
return_result = conn.post 'return', req_obj.to_json
req_obj['hosts'].each do |host|
response_body[host['hostname']] = { 'ok' => true } if return_result.body == 'OK'
end
end
response_body
end
# List available VMs in ABS
def self.list(verbose, url, os_filter = nil)
conn = Http.get_conn(verbose, supported_abs_url(url))
os_list = []
res = conn.get 'status/platforms/vmpooler'
if valid_json?(res.body)
res_body = JSON.parse(res.body)
if res_body.key?('vmpooler_platforms')
os_list << '*** VMPOOLER Pools ***'
os_list += if res_body['vmpooler_platforms'].is_a?(String)
JSON.parse(res_body['vmpooler_platforms']) # legacy ABS had another JSON string always-be-scheduling/pull/306
else
res_body['vmpooler_platforms']
end
end
end
res = conn.get 'status/platforms/ondemand_vmpooler'
if valid_json?(res.body)
res_body = JSON.parse(res.body)
if res_body.key?('ondemand_vmpooler_platforms') && res_body['ondemand_vmpooler_platforms'] != '[]'
os_list << ''
os_list << '*** VMPOOLER ONDEMAND Pools ***'
if res_body['ondemand_vmpooler_platforms'].is_a?(String)
os_list += JSON.parse(res_body['ondemand_vmpooler_platforms']) # legacy ABS had another JSON string always-be-scheduling/pull/306
else
os_list += res_body['ondemand_vmpooler_platforms']
end
end
end
res = conn.get 'status/platforms/nspooler'
if valid_json?(res.body)
res_body = JSON.parse(res.body)
if res_body.key?('nspooler_platforms')
os_list << ''
os_list << '*** NSPOOLER Pools ***'
os_list += if res_body['nspooler_platforms'].is_a?(String)
JSON.parse(res_body['nspooler_platforms']) # legacy ABS had another JSON string always-be-scheduling/pull/306
else
res_body['nspooler_platforms']
end
end
end
res = conn.get 'status/platforms/aws'
if valid_json?(res.body)
res_body = JSON.parse(res.body)
if res_body.key?('aws_platforms')
os_list << ''
os_list << '*** AWS Pools ***'
os_list += if res_body['aws_platforms'].is_a?(String)
JSON.parse(res_body['aws_platforms']) # legacy ABS had another JSON string always-be-scheduling/pull/306
else
res_body['aws_platforms']
end
end
end
os_list.delete 'ok'
os_filter ? os_list.select { |i| i[/#{os_filter}/] } : os_list
end
# Retrieve an OS from ABS.
def self.retrieve(verbose, os_types, token, url, user, config, _ondemand = nil, continue = nil)
#
# Contents of post must be like:
#
# {
# "resources": {
# "centos-7-i386": 1,
# "ubuntu-1404-x86_64": 2
# },
# "job": {
# "id": "12345",
# "tags": {
# "user": "username",
# }
# }
# }
conn = Http.get_conn(verbose, supported_abs_url(url))
conn.headers['X-AUTH-TOKEN'] = token if token
saved_job_id = if continue.nil?
"#{user}-#{DateTime.now.strftime('%Q')}"
else
continue
end
req_obj = {
resources: os_types,
job: {
id: saved_job_id,
tags: {
user: user
}
}
}
if config['vmpooler_fallback'] # optional and not available as cli flag
vmpooler_config = Utils.get_vmpooler_service_config(config['vmpooler_fallback'])
# request with this token, on behalf of this user
req_obj[:vm_token] = vmpooler_config['token']
end
if config['priority']
req_obj[:priority] = case config['priority']
when 'high'
1
when 'medium'
2
when 'low'
3
else
config['priority'].to_i
end
end
FloatyLogger.info "Posting to ABS #{req_obj.to_json}" if verbose
# os_string = os_type.map { |os, num| Array(os) * num }.flatten.join('+')
# raise MissingParamError, 'No operating systems provided to obtain.' if os_string.empty?
FloatyLogger.info "Requesting VMs with job_id: #{saved_job_id} Will retry for up to an hour."
res = conn.post 'request', req_obj.to_json
retries = 360
status = validate_queue_status_response(res.status, res.body, 'Initial request', verbose)
begin
(1..retries).each do |i|
res_body = check_queue(conn, saved_job_id, req_obj, verbose)
return translated(res_body, saved_job_id) if res_body.is_a?(Array) # when we get a response with hostnames
sleep_seconds = 10 if i >= 10
sleep_seconds = i if i < 10
FloatyLogger.info "Waiting #{sleep_seconds}s (x#{i}) #{res_body.strip}"
sleep(sleep_seconds)
end
rescue SystemExit, Interrupt
FloatyLogger.info "\n\nFloaty interrupted, you can resume polling with\n1) `floaty get [same arguments] and adding the flag --continue #{saved_job_id}` or query the state of the queue via\n2) `floaty query #{saved_job_id}` or delete it via\n3) `floaty delete #{saved_job_id}`"
exit 1
end
nil
end
#
# We should fix the ABS API to be more like the vmpooler or nspooler api, but for now
#
def self.translated(res_body, job_id)
vmpooler_formatted_body = { 'job_id' => job_id }
res_body.each do |host|
if vmpooler_formatted_body[host['type']] && vmpooler_formatted_body[host['type']]['hostname'].instance_of?(Array)
vmpooler_formatted_body[host['type']]['hostname'] << host['hostname']
else
vmpooler_formatted_body[host['type']] = { 'hostname' => [host['hostname']] }
end
end
vmpooler_formatted_body['ok'] = true
vmpooler_formatted_body
end
def self.check_queue(conn, _job_id, req_obj, verbose)
res = conn.post 'request', req_obj.to_json
status = validate_queue_status_response(res.status, res.body, 'Check queue request', verbose)
unless res.body.empty? || !valid_json?(res.body)
res_body = JSON.parse(res.body)
return res_body
end
res.body
end
def self.snapshot(_verbose, _url, _hostname, _token)
raise NoMethodError, "Can't snapshot with ABS, use '--service vmpooler' (even for vms checked out with ABS)"
end
def self.status(verbose, url)
conn = Http.get_conn(verbose, supported_abs_url(url))
res = conn.get 'status'
res.body == 'OK'
end
def self.summary(_verbose, _url)
raise NoMethodError, 'summary is not defined for ABS'
end
def self.query(verbose, url, job_id)
# return saved hostnames from the last time list_active was run
# preventing having to query the API again.
# This works as long as query is called after list_active
return @active_hostnames if @active_hostnames && !@active_hostnames.empty?
# If using the cli query job_id
conn = Http.get_conn(verbose, supported_abs_url(url))
queue_info_res = conn.get "status/queue/info/#{job_id}"
if valid_json?(queue_info_res.body)
queue_info = JSON.parse(queue_info_res.body)
else
FloatyLogger.warn "Could not parse the status/queue/info/#{job_id}"
end
queue_info
end
def self.modify(_verbose, _url, _hostname, _token, _modify_hash)
raise NoMethodError, 'modify is not defined for ABS'
end
def self.disk(_verbose, _url, _hostname, _token, _disk)
raise NoMethodError, 'disk is not defined for ABS'
end
def self.revert(_verbose, _url, _hostname, _token, _snapshot_sha)
raise NoMethodError, 'revert is not defined for ABS'
end
# Validate the http code returned during a queue status request.
#
# Return a success message that can be displayed if the status code is
# success, otherwise raise an error.
def self.validate_queue_status_response(status_code, body, request_name, verbose)
case status_code
when 200
"#{request_name} returned success (Code 200)" if verbose
when 202
"#{request_name} returned accepted, processing (Code 202)" if verbose
when 401
raise AuthError, "HTTP #{status_code}: The token provided could not authenticate.\n#{body}"
else
raise "HTTP #{status_code}: #{request_name} request to ABS failed!\n#{body}"
end
end
def self.valid_json?(json)
JSON.parse(json)
true
rescue TypeError, JSON::ParserError => e
false
end
# when missing, adds the required api/v2 in the url
def self.supported_abs_url(url)
expected_ending = 'api/v2'
unless url.include?(expected_ending)
# add a slash if missing
expected_ending = "/#{expected_ending}" if url[-1] != '/'
url = "#{url}#{expected_ending}"
end
url
end
end

View file

@ -1,5 +1,3 @@
# frozen_string_literal: true
require 'faraday'
require 'json'
require 'vmfloaty/http'
@ -9,36 +7,46 @@ class Auth
def self.get_token(verbose, url, user, password)
conn = Http.get_conn_with_auth(verbose, url, user, password)
resp = conn.post 'token'
resp = conn.post "token"
res_body = JSON.parse(resp.body)
return res_body['token'] if res_body['ok']
raise TokenError, "HTTP #{resp.status}: There was a problem requesting a token:\n#{res_body}"
if res_body["ok"]
return res_body["token"]
else
raise TokenError, "HTTP #{resp.status}: There was a problem requesting a token:\n#{res_body}"
end
end
def self.delete_token(verbose, url, user, password, token)
raise TokenError, 'You did not provide a token' if token.nil?
if token.nil?
raise TokenError, 'You did not provide a token'
end
conn = Http.get_conn_with_auth(verbose, url, user, password)
response = conn.delete "token/#{token}"
res_body = JSON.parse(response.body)
return res_body if res_body['ok']
raise TokenError, "HTTP #{response.status}: There was a problem deleting a token:\n#{res_body}"
if res_body["ok"]
return res_body
else
raise TokenError, "HTTP #{response.status}: There was a problem deleting a token:\n#{res_body}"
end
end
def self.token_status(verbose, url, token)
raise TokenError, 'You did not provide a token' if token.nil?
if token.nil?
raise TokenError, 'You did not provide a token'
end
conn = Http.get_conn(verbose, url)
response = conn.get "token/#{token}"
res_body = JSON.parse(response.body)
return res_body if res_body['ok']
raise TokenError, "HTTP #{response.status}: There was a problem getting the status of a token:\n#{res_body}"
if res_body["ok"]
return res_body
else
raise TokenError, "HTTP #{response.status}: There was a problem getting the status of a token:\n#{res_body}"
end
end
end

View file

@ -1,14 +1,13 @@
# frozen_string_literal: true
require 'yaml'
class Conf
def self.read_config
conf = {}
begin
conf = YAML.load_file("#{Dir.home}/.vmfloaty.yml")
rescue StandardError
# ignore
rescue
STDERR.puts "WARNING: There was no config file at #{Dir.home}/.vmfloaty.yml"
end
conf
end

View file

@ -1,25 +1,23 @@
# frozen_string_literal: true
class AuthError < StandardError
def initialize(msg = 'Could not authenticate to pooler')
def initialize(msg="Could not authenticate to pooler")
super
end
end
class TokenError < StandardError
def initialize(msg = 'Could not do operation with token provided')
def initialize(msg="Could not do operation with token provided")
super
end
end
class MissingParamError < StandardError
def initialize(msg = 'Argument provided to function is missing')
def initialize(msg="Argument provided to function is missing")
super
end
end
class ModifyError < StandardError
def initialize(msg = 'Could not modify VM')
def initialize(msg="Could not modify VM")
super
end
end

View file

@ -1,45 +1,60 @@
# frozen_string_literal: true
require 'faraday'
require 'uri'
class Http
def self.url?(url)
def self.is_url(url)
# This method exists because it seems like Farady
# has no handling around if a user gives us a URI
# with no protocol on the beginning of the URL
uri = URI.parse(url)
return true if uri.is_a?(URI::HTTP) || uri.is_a?(URI::HTTPS)
if uri.kind_of?(URI::HTTP) or uri.kind_of?(URI::HTTPS)
return true
end
false
return false
end
def self.get_conn(verbose, url)
raise 'Did not provide a url to connect to' if url.nil?
if url.nil?
raise "Did not provide a url to connect to"
end
url = "https://#{url}" unless url?(url)
unless is_url(url)
url = "https://#{url}"
end
Faraday.new(url: url, ssl: { verify: false }) do |faraday|
conn = Faraday.new(:url => url, :ssl => {:verify => false}) do |faraday|
faraday.request :url_encoded
faraday.response :logger if verbose
faraday.adapter Faraday.default_adapter
end
return conn
end
def self.get_conn_with_auth(verbose, url, user, password)
raise 'Did not provide a url to connect to' if url.nil?
if url.nil?
raise "Did not provide a url to connect to"
end
raise 'You did not provide a user to authenticate with' if user.nil?
if user.nil?
raise "You did not provide a user to authenticate with"
end
url = "https://#{url}" unless url?(url)
unless is_url(url)
url = "https://#{url}"
end
Faraday.new(url: url, ssl: { verify: false }) do |faraday|
conn = Faraday.new(:url => url, :ssl => {:verify => false}) do |faraday|
faraday.request :url_encoded
faraday.request :basic_auth, user, password
faraday.response :logger if verbose
faraday.adapter Faraday.default_adapter
end
return conn
end
end

View file

@ -1,43 +0,0 @@
# frozen_string_literal: true
require 'logger'
class FloatyLogger < ::Logger
def self.logger
@@logger ||= FloatyLogger.new
end
def self.info(msg)
FloatyLogger.logger.info msg
end
def self.warn(msg)
FloatyLogger.logger.warn msg
end
def self.error(msg)
FloatyLogger.logger.error msg
end
def self.setlevel=(level)
level = level.downcase
case level
when 'debug'
logger.level = ::Logger::DEBUG
when 'info'
logger.level = ::Logger::INFO
when 'error'
logger.level = ::Logger::ERROR
else
error('set loglevel to debug, info or error')
end
end
def initialize
super($stderr)
self.level = ::Logger::INFO
self.formatter = proc do |_severity, _datetime, _progname, msg|
"#{msg}\n"
end
end
end

View file

@ -1,5 +1,3 @@
# frozen_string_literal: true
require 'vmfloaty/errors'
require 'vmfloaty/http'
require 'faraday'
@ -17,17 +15,27 @@ class NonstandardPooler
os_filter ? os_list.select { |i| i[/#{os_filter}/] } : os_list
end
def self.list_active(verbose, url, token, _user)
def self.list_active(verbose, url, token)
status = Auth.token_status(verbose, url, token)
status['reserved_hosts'] || []
end
def self.retrieve(verbose, os_type, token, url, _user, _options, _ondemand = nil, _continue = nil)
def self.retrieve(verbose, os_type, token, url)
conn = Http.get_conn(verbose, url)
conn.headers['X-AUTH-TOKEN'] = token if token
os_string = os_type.map { |os, num| Array(os) * num }.flatten.join('+')
raise MissingParamError, 'No operating systems provided to obtain.' if os_string.empty?
os_string = ''
os_type.each do |os, num|
num.times do |_i|
os_string << os + '+'
end
end
os_string = os_string.chomp('+')
if os_string.empty?
raise MissingParamError, 'No operating systems provided to obtain.'
end
response = conn.post "host/#{os_string}"
@ -43,11 +51,14 @@ class NonstandardPooler
end
def self.modify(verbose, url, hostname, token, modify_hash)
raise TokenError, 'Token provided was nil; Request cannot be made to modify VM' if token.nil?
if token.nil?
raise TokenError, 'Token provided was nil; Request cannot be made to modify VM'
end
modify_hash.each do |key, _value|
raise ModifyError, "Configured service type does not support modification of #{key}" unless %i[reason
reserved_for_reason].include? key
modify_hash.each do |key, value|
unless [:reason, :reserved_for_reason].include? key
raise ModifyError, "Configured service type does not support modification of #{key}"
end
end
if modify_hash[:reason]
@ -66,20 +77,22 @@ class NonstandardPooler
response.body.empty? ? {} : JSON.parse(response.body)
end
def self.disk(_verbose, _url, _hostname, _token, _disk)
def self.disk(verbose, url, hostname, token, disk)
raise ModifyError, 'Configured service type does not support modification of disk space'
end
def self.snapshot(_verbose, _url, _hostname, _token)
def self.snapshot(verbose, url, hostname, token)
raise ModifyError, 'Configured service type does not support snapshots'
end
def self.revert(_verbose, _url, _hostname, _token, _snapshot_sha)
def self.revert(verbose, url, hostname, token, snapshot_sha)
raise ModifyError, 'Configured service type does not support snapshots'
end
def self.delete(verbose, url, hosts, token, _user)
raise TokenError, 'Token provided was nil; Request cannot be made to delete VM' if token.nil?
def self.delete(verbose, url, hosts, token)
if token.nil?
raise TokenError, 'Token provided was nil; Request cannot be made to delete VM'
end
conn = Http.get_conn(verbose, url)
@ -87,7 +100,9 @@ class NonstandardPooler
response_body = {}
hosts = hosts.split(',') unless hosts.is_a? Array
unless hosts.is_a? Array
hosts = hosts.split(',')
end
hosts.each do |host|
response = conn.delete "host/#{host}"
res_body = JSON.parse(response.body)

View file

@ -1,93 +1,77 @@
# frozen_string_literal: true
require 'faraday'
require 'vmfloaty/http'
require 'json'
require 'vmfloaty/errors'
class Pooler
def self.list(verbose, url, os_filter = nil)
def self.list(verbose, url, os_filter=nil)
conn = Http.get_conn(verbose, url)
response = conn.get 'vm'
response_body = JSON.parse(response.body)
if os_filter
response_body.select { |i| i[/#{os_filter}/] }
hosts = response_body.select { |i| i[/#{os_filter}/] }
else
response_body
hosts = response_body
end
hosts
end
def self.list_active(verbose, url, token, _user)
def self.list_active(verbose, url, token)
status = Auth.token_status(verbose, url, token)
vms = []
vms = status[token]['vms']['running'] if status[token] && status[token]['vms']
if status[token] && status[token]['vms']
vms = status[token]['vms']['running']
end
vms
end
def self.retrieve(verbose, os_type, token, url, _user, _options, ondemand = nil, _continue = nil)
def self.retrieve(verbose, os_type, token, url)
# NOTE:
# Developers can use `Utils.generate_os_hash` to
# generate the os_type param.
conn = Http.get_conn(verbose, url)
conn.headers['X-AUTH-TOKEN'] = token if token
if token
conn.headers['X-AUTH-TOKEN'] = token
end
os_string = os_type.map { |os, num| Array(os) * num }.flatten.join('+')
raise MissingParamError, 'No operating systems provided to obtain.' if os_string.empty?
os_string = ""
os_type.each do |os,num|
num.times do |i|
os_string << os+"+"
end
end
response = conn.post "vm/#{os_string}" unless ondemand
response ||= conn.post "ondemandvm/#{os_string}"
os_string = os_string.chomp("+")
if os_string.size == 0
raise MissingParamError, "No operating systems provided to obtain."
end
response = conn.post "vm/#{os_string}"
res_body = JSON.parse(response.body)
if res_body['ok']
if res_body["ok"]
res_body
elsif response.status == 401
raise AuthError, "HTTP #{response.status}: The token provided could not authenticate to the pooler.\n#{res_body}"
elsif response.status == 403
raise "HTTP #{response.status}: Failed to obtain VMs from the pooler at #{url}/vm/#{os_string}. Request exceeds the configured per pool maximum. #{res_body}"
else
unless ondemand
raise "HTTP #{response.status}: Failed to obtain VMs from the pooler at #{url}/vm/#{os_string}. #{res_body}"
end
raise "HTTP #{response.status}: Failed to obtain VMs from the pooler at #{url}/ondemandvm/#{os_string}. #{res_body}"
raise "HTTP #{response.status}: Failed to obtain VMs from the pooler at #{url}/vm/#{os_string}. #{res_body}"
end
end
def self.wait_for_request(verbose, request_id, url, timeout = 300)
start_time = Time.now
while check_ondemandvm(verbose, request_id, url) == false
return false if (Time.now - start_time).to_i > timeout
FloatyLogger.info "waiting for request #{request_id} to be fulfilled"
sleep 5
end
FloatyLogger.info 'The request has been fulfilled'
check_ondemandvm(verbose, request_id, url)
end
def self.check_ondemandvm(verbose, request_id, url)
conn = Http.get_conn(verbose, url)
response = conn.get "ondemandvm/#{request_id}"
res_body = JSON.parse(response.body)
return res_body if response.status == 200
return false if response.status == 202
raise "HTTP #{response.status}: The request cannot be found, or an unknown error occurred" if response.status == 404
false
end
def self.modify(verbose, url, hostname, token, modify_hash)
raise TokenError, 'Token provided was nil. Request cannot be made to modify vm' if token.nil?
if token.nil?
raise TokenError, "Token provided was nil. Request cannot be made to modify vm"
end
modify_hash.each_key do |key|
raise ModifyError, "Configured service type does not support modification of #{key}." unless %i[tags lifetime
disk].include? key
modify_hash.keys.each do |key|
unless [:tags, :lifetime, :disk].include? key
raise ModifyError, "Configured service type does not support modification of #{key}."
end
end
conn = Http.get_conn(verbose, url)
@ -104,33 +88,33 @@ class Pooler
end
res_body = JSON.parse(response.body)
if res_body['ok']
res_body
elsif response.status == 401
raise AuthError, "HTTP #{response.status}: The token provided could not authenticate to the pooler.\n#{res_body}"
else
raise ModifyError, "HTTP #{response.status}: Failed to modify VMs from the pooler vm/#{hostname}. #{res_body}"
end
res_body
end
def self.disk(verbose, url, hostname, token, disk)
raise TokenError, 'Token provided was nil. Request cannot be made to modify vm' if token.nil?
if token.nil?
raise TokenError, "Token provided was nil. Request cannot be made to modify vm"
end
conn = Http.get_conn(verbose, url)
conn.headers['X-AUTH-TOKEN'] = token
response = conn.post "vm/#{hostname}/disk/#{disk}"
JSON.parse(response.body)
res_body = JSON.parse(response.body)
res_body
end
def self.delete(verbose, url, hosts, token, _user)
raise TokenError, 'Token provided was nil. Request cannot be made to delete vm' if token.nil?
def self.delete(verbose, url, hosts, token)
if token.nil?
raise TokenError, "Token provided was nil. Request cannot be made to delete vm"
end
conn = Http.get_conn(verbose, url)
conn.headers['X-AUTH-TOKEN'] = token if token
if token
conn.headers['X-AUTH-TOKEN'] = token
end
response_body = {}
@ -146,43 +130,55 @@ class Pooler
def self.status(verbose, url)
conn = Http.get_conn(verbose, url)
response = conn.get 'status'
JSON.parse(response.body)
response = conn.get '/status'
res_body = JSON.parse(response.body)
res_body
end
def self.summary(verbose, url)
conn = Http.get_conn(verbose, url)
response = conn.get 'summary'
JSON.parse(response.body)
response = conn.get '/summary'
res_body = JSON.parse(response.body)
res_body
end
def self.query(verbose, url, hostname)
conn = Http.get_conn(verbose, url)
response = conn.get "vm/#{hostname}"
JSON.parse(response.body)
res_body = JSON.parse(response.body)
res_body
end
def self.snapshot(verbose, url, hostname, token)
raise TokenError, 'Token provided was nil. Request cannot be made to snapshot vm' if token.nil?
if token.nil?
raise TokenError, "Token provided was nil. Request cannot be made to snapshot vm"
end
conn = Http.get_conn(verbose, url)
conn.headers['X-AUTH-TOKEN'] = token
response = conn.post "vm/#{hostname}/snapshot"
JSON.parse(response.body)
res_body = JSON.parse(response.body)
res_body
end
def self.revert(verbose, url, hostname, token, snapshot_sha)
raise TokenError, 'Token provided was nil. Request cannot be made to revert vm' if token.nil?
if token.nil?
raise TokenError, "Token provided was nil. Request cannot be made to revert vm"
end
conn = Http.get_conn(verbose, url)
conn.headers['X-AUTH-TOKEN'] = token
raise "Snapshot SHA provided was nil, could not revert #{hostname}" if snapshot_sha.nil?
if snapshot_sha.nil?
raise "Snapshot SHA provided was nil, could not revert #{hostname}"
end
response = conn.post "vm/#{hostname}/snapshot/#{snapshot_sha}"
JSON.parse(response.body)
res_body = JSON.parse(response.body)
res_body
end
end

View file

@ -1,33 +1,26 @@
# frozen_string_literal: true
require 'commander/user_interaction'
require 'commander/command'
require 'vmfloaty/utils'
require 'vmfloaty/ssh'
class Service
attr_reader :config
attr_accessor :silent
def initialize(options, config_hash = {})
options ||= Commander::Command::Options.new
@config = Utils.get_service_config config_hash, options
@service_object = Utils.get_service_object @config['type']
@silent = false
end
def method_missing(method_name, *args, &block)
if @service_object.respond_to?(method_name)
@service_object.send(method_name, *args, &block)
def method_missing(m, *args, &block)
if @service_object.respond_to? m
@service_object.send(m, *args, &block)
else
super
end
end
def respond_to_missing?(method_name, *)
@service_object.respond_to?(method_name) || super
end
def url
@config['url']
end
@ -38,15 +31,15 @@ class Service
def user
unless @config['user']
FloatyLogger.info "Enter your #{@config['url']} service username:"
@config['user'] = $stdin.gets.chomp
puts "Enter your pooler service username:"
@config['user'] = STDIN.gets.chomp
end
@config['user']
end
def token
unless @config['token']
FloatyLogger.info 'No token found. Retrieving a token...'
puts "No token found. Retrieving a token..."
@config['token'] = get_new_token(nil)
end
@config['token']
@ -54,13 +47,13 @@ class Service
def get_new_token(verbose)
username = user
pass = Commander::UI.password "Enter your #{@config['url']} service password:", '*'
pass = Commander::UI::password "Enter your pooler service password:", '*'
Auth.get_token(verbose, url, username, pass)
end
def delete_token(verbose, token_value = @config['token'])
username = user
pass = Commander::UI.password "Enter your #{@config['url']} service password:", '*'
pass = Commander::UI::password "Enter your pooler service password:", '*'
Auth.delete_token(verbose, url, username, pass, token_value)
end
@ -74,30 +67,35 @@ class Service
end
def list_active(verbose)
@service_object.list_active verbose, url, token, user
@service_object.list_active verbose, url, token
end
def retrieve(verbose, os_types, use_token = true, ondemand = nil, continue = nil)
FloatyLogger.info 'Requesting a vm without a token...' unless use_token
def retrieve(verbose, os_types, use_token = true)
puts 'Requesting a vm without a token...' unless use_token
token_value = use_token ? token : nil
@service_object.retrieve verbose, os_types, token_value, url, user, @config, ondemand, continue
@service_object.retrieve verbose, os_types, token_value, url
end
def wait_for_request(verbose, requestid)
@service_object.wait_for_request verbose, requestid, url
end
def ssh(verbose, host_os, use_token = true, ondemand = nil)
def ssh(verbose, host_os, use_token = true)
token_value = nil
if use_token
begin
token_value = token || get_new_token(verbose)
rescue TokenError => e
FloatyLogger.error e
FloatyLogger.info 'Could not get token... requesting vm without a token anyway...'
STDERR.puts e
STDERR.puts 'Could not get token... requesting vm without a token anyway...'
end
end
Ssh.ssh(verbose, self, host_os, token_value, ondemand)
Ssh.ssh(verbose, host_os, token_value, url)
end
def pretty_print_running(verbose, hostnames = [])
if hostnames.empty?
puts "You have no running VMs."
else
puts "Running VMs:"
@service_object.pretty_print_hosts(verbose, hostnames, url)
end
end
def query(verbose, hostname)
@ -105,12 +103,11 @@ class Service
end
def modify(verbose, hostname, modify_hash)
maybe_use_vmpooler
@service_object.modify verbose, url, hostname, token, modify_hash
end
def delete(verbose, hosts)
@service_object.delete verbose, url, hosts, token, user
@service_object.delete verbose, url, hosts, token
end
def status(verbose)
@ -118,35 +115,19 @@ class Service
end
def summary(verbose)
maybe_use_vmpooler
@service_object.summary verbose, url
end
def snapshot(verbose, hostname)
maybe_use_vmpooler
@service_object.snapshot verbose, url, hostname, token
end
def revert(verbose, hostname, snapshot_sha)
maybe_use_vmpooler
@service_object.revert verbose, url, hostname, token, snapshot_sha
end
def disk(verbose, hostname, disk)
maybe_use_vmpooler
@service_object.disk(verbose, url, hostname, token, disk)
end
# some methods do not exist for ABS, and if possible should target the Pooler service
def maybe_use_vmpooler
if @service_object == ABS # this is not an instance
unless silent
FloatyLogger.info 'The service in use is ABS, but the requested method should run against vmpooler directly, using fallback_vmpooler config from ~/.vmfloaty.yml'
self.silent = true
end
@config = Utils.get_vmpooler_service_config(@config['vmpooler_fallback'])
@service_object = Pooler
end
end
end
end

View file

@ -1,61 +1,43 @@
# frozen_string_literal: true
class Ssh
def self.which(cmd)
# Gets path of executable for given command
exts = ENV['PATHEXT'] ? ENV['PATHEXT'].split(';') : ['']
ENV['PATH'].split(File::PATH_SEPARATOR).each do |path|
exts.each do |ext|
exts.each { |ext|
exe = File.join(path, "#{cmd}#{ext}")
return exe if File.executable?(exe) && !File.directory?(exe)
end
}
end
nil
return nil
end
def self.command_string(verbose, service, host_os, use_token, ondemand = nil)
ssh_path = which('ssh')
raise 'Could not determine path to ssh' unless ssh_path
os_types = Utils.generate_os_hash([host_os])
def self.ssh(verbose, host_os, token, url)
ssh_path = which("ssh")
if !ssh_path
raise "Could not determine path to ssh"
end
os_types = {}
os_types[host_os] = 1
response = service.retrieve(verbose, os_types, use_token, ondemand)
raise "Could not get vm from #{service.type}:\n #{response}" unless response['ok']
user = /win/.match?(host_os) ? 'Administrator' : 'root'
if ondemand
requestid = response['request_id']
service.wait_for_request(verbose, requestid)
hosts = service.check_ondemandvm(verbose, requestid, service.url)
if hosts['domain'].nil?
hostname = hosts[host_os]['hostname']
hostname = hosts[host_os]['hostname'][0] if hosts[host_os]['hostname'].is_a?(Array)
response = Pooler.retrieve(verbose, os_types, token, url)
if response["ok"] == true
if host_os =~ /win/
user = "Administrator"
else
# Provides backwards compatibility with VMPooler API v1
hostname = "#{hosts[host_os]['hostname']}.#{hosts['domain']}"
hostname = "#{hosts[host_os]['hostname'][0]}.#{hosts['domain']}" if hosts[host_os]['hostname'].is_a?(Array)
user = "root"
end
hostname = "#{response[host_os]["hostname"]}.#{response["domain"]}"
cmd = "#{ssh_path} #{user}@#{hostname}"
# TODO: Should this respect more ssh settings? Can it be configured
# by users ssh config and does this respect those settings?
Kernel.exec(cmd)
else
if response['domain'].nil?
hostname = response[host_os]['hostname']
hostname = response[host_os]['hostname'][0] if response[host_os]['hostname'].is_a?(Array)
else
# Provides backwards compatibility with VMPooler API v1
hostname = "#{response[host_os]['hostname']}.#{response['domain']}"
hostname = "#{response[host_os]['hostname'][0]}.#{response['domain']}" if response[host_os]['hostname'].is_a?(Array)
end
raise "Could not get vm from vmpooler:\n #{response}"
end
"#{ssh_path} #{user}@#{hostname}"
end
def self.ssh(verbose, service, host_os, use_token, ondemand)
cmd = command_string(verbose, service, host_os, use_token, ondemand)
# TODO: Should this respect more ssh settings? Can it be configured
# by users ssh config and does this respect those settings?
Kernel.exec(cmd)
nil
return
end
end

View file

@ -1,15 +1,11 @@
# frozen_string_literal: true
require 'vmfloaty/abs'
require 'vmfloaty/nonstandard_pooler'
require 'vmfloaty/pooler'
require 'vmfloaty/conf'
require 'vmfloaty/nonstandard_pooler'
class Utils
# TODO: Takes the json response body from an HTTP GET
# request and "pretty prints" it
def self.standardize_hostnames(response_body)
# vmpooler api v1 response body example when `floaty get` arguments are `ubuntu-1610-x86_64=2 centos-7-x86_64`:
# vmpooler response body example when `floaty get` arguments are `ubuntu-1610-x86_64=2 centos-7-x86_64`:
# {
# "ok": true,
# "domain": "delivery.mycompany.net",
@ -21,17 +17,6 @@ class Utils
# }
# }
# vmpooler api v2 response body example when `floaty get` arguments are `ubuntu-1610-x86_64=2 centos-7-x86_64`:
# {
# "ok": true,
# "ubuntu-1610-x86_64": {
# "hostname": ["gdoy8q3nckuob0i.pooler.example.com", "ctnktsd0u11p9tm.pooler.example.com"]
# },
# "centos-7-x86_64": {
# "hostname": "dlgietfmgeegry2.pooler.example.com"
# }
# }
# nonstandard pooler response body example when `floaty get` arguments are `solaris-11-sparc=2 ubuntu-16.04-power8`:
# {
# "ok": true,
@ -43,16 +28,8 @@ class Utils
# }
# }
# abs pooler response body example when `floaty get` arguments are :
# {
# "hostname"=>"thin-soutane.delivery.puppetlabs.net",
# "type"=>"centos-7.2-tmpfs-x86_64",
# "engine"=>"vmpooler"
# }
unless response_body.delete('ok')
raise ArgumentError,
"Bad GET response passed to format_hosts: #{response_body.to_json}"
raise ArgumentError, "Bad GET response passed to format_hosts: #{response_body.to_json}"
end
# vmpooler reports the domain separately from the hostname
@ -60,14 +37,11 @@ class Utils
result = {}
# ABS has a job_id associated with hosts so pass that along
abs_job_id = response_body.delete('job_id')
result['job_id'] = abs_job_id unless abs_job_id.nil?
filtered_response_body = response_body.reject { |key, _| %w[request_id ready].include?(key) }
filtered_response_body.each do |os, value|
response_body.each do |os, value|
hostnames = Array(value['hostname'])
hostnames.map! { |host| "#{host}.#{domain}" } if domain
if domain
hostnames.map! {|host| "#{host}.#{domain}"}
end
result[os] = hostnames
end
@ -76,8 +50,7 @@ class Utils
def self.format_host_output(hosts)
hosts.flat_map do |os, names|
# Assume hosts are stored in Arrays and ignore everything else
names.map { |name| "- #{name} (#{os})" } if names.is_a? Array
names.map { |name| "- #{name} (#{os})" }
end.join("\n")
end
@ -92,159 +65,94 @@ class Utils
# ...]
os_types = {}
os_args.each do |arg|
os_arr = arg.split('=')
os_types[os_arr[0]] = os_arr.size == 1 ? 1 : os_arr[1].to_i
os_arr = arg.split("=")
if os_arr.size == 1
# assume they didn't specify an = sign if split returns 1 size
os_types[os_arr[0]] = 1
else
os_types[os_arr[0]] = os_arr[1].to_i
end
end
os_types
end
def self.print_fqdn_for_host(service, hostname, host_data)
case service.type
when 'ABS'
abs_hostnames = []
host_data['allocated_resources'].each do |vm_name, _i|
abs_hostnames << vm_name['hostname']
end
puts abs_hostnames.join("\n")
when 'Pooler'
if host_data['domain'].nil?
puts hostname
else
puts "#{hostname}.#{host_data['domain']}"
end
when 'NonstandardPooler'
puts host_data['fqdn']
else
raise "Invalid service type #{service.type}"
end
end
def self.pretty_print_hosts(verbose, service, hostnames = [], print_to_stderr = false, indent = 0)
output_target = print_to_stderr ? $stderr : $stdout
fetched_data = get_host_data(verbose, service, hostnames)
fetched_data.each do |hostname, host_data|
case service.type
when 'ABS'
# For ABS, 'hostname' variable is the jobID
#
# Create a vmpooler service to query each hostname there so as to get the metadata too
output_target.puts "- [JobID:#{host_data['request']['job']['id']}] <#{host_data['state']}>"
host_data['allocated_resources'].each do |allocated_resources, _i|
if (allocated_resources['engine'] == 'vmpooler' || allocated_resources['engine'] == 'ondemand') && service.config['vmpooler_fallback']
vmpooler_service = service.clone
vmpooler_service.silent = true
vmpooler_service.maybe_use_vmpooler
pretty_print_hosts(verbose, vmpooler_service, allocated_resources['hostname'].split('.')[0],
print_to_stderr, indent + 2)
else
# TODO: we could add more specific metadata for the other services, nspooler and aws
output_target.puts " - #{allocated_resources['hostname']} (#{allocated_resources['type']})"
end
end
when 'Pooler'
tag_pairs = []
tag_pairs = host_data['tags'].map { |key, value| "#{key}: #{value}" } unless host_data['tags'].nil?
duration = "#{host_data['running']}/#{host_data['lifetime']} hours"
metadata = [host_data['state'], host_data['template'], duration, *tag_pairs]
# For backwards compatibility with vmpooler api v1
message =
if host_data['domain']
"- #{hostname}.#{host_data['domain']} (#{metadata.join(', ')})".gsub(/^/, ' ' * indent)
else
"- #{host_data['fqdn']} (#{metadata.join(', ')})".gsub(/^/, ' ' * indent)
end
if host_data['state'] && host_data['state'] == 'destroyed'
output_target.puts "- DESTROYED #{hostname}.#{host_data['domain']}".gsub(/^/, ' ' * indent)
else
output_target.puts message
end
when 'NonstandardPooler'
line = "- #{host_data['fqdn']} (#{host_data['os_triple']}"
line += ", #{host_data['hours_left_on_reservation']}h remaining"
line += ", reason: #{host_data['reserved_for_reason']}" unless host_data['reserved_for_reason'].nil? || host_data['reserved_for_reason'].empty?
line += ')'
output_target.puts line
else
raise "Invalid service type #{service.type}"
end
end
end
def self.get_host_data(verbose, service, hostnames = [])
result = {}
def self.pretty_print_hosts(verbose, service, hostnames = [])
hostnames = [hostnames] unless hostnames.is_a? Array
hostnames.each do |hostname|
response = service.query(verbose, hostname)
host_data = response[hostname]
if block_given?
yield host_data result
else
begin
response = service.query(verbose, hostname)
host_data = response[hostname]
case service.type
when 'ABS'
# For ABS, 'hostname' variable is the jobID
result[hostname] = host_data if host_data['state'] == 'allocated' || host_data['state'] == 'filled'
when 'Pooler'
result[hostname] = host_data
when 'NonstandardPooler'
result[hostname] = host_data
else
raise "Invalid service type #{service.type}"
when 'Pooler'
tag_pairs = []
unless host_data['tags'].nil?
tag_pairs = host_data['tags'].map {|key, value| "#{key}: #{value}"}
end
duration = "#{host_data['running']}/#{host_data['lifetime']} hours"
metadata = [host_data['template'], duration, *tag_pairs]
puts "- #{hostname}.#{host_data['domain']} (#{metadata.join(", ")})"
when 'NonstandardPooler'
line = "- #{host_data['fqdn']} (#{host_data['os_triple']}"
line += ", #{host_data['hours_left_on_reservation']}h remaining"
unless host_data['reserved_for_reason'].empty?
line += ", reason: #{host_data['reserved_for_reason']}"
end
line += ')'
puts line
else
raise "Invalid service type #{service.type}"
end
rescue => e
STDERR.puts("Something went wrong while trying to gather information on #{hostname}:")
STDERR.puts(e)
end
rescue StandardError => e
FloatyLogger.error("Something went wrong while trying to gather information on #{hostname}:")
FloatyLogger.error(e)
end
result
end
def self.pretty_print_status(verbose, service)
status_response = service.status(verbose)
case service.type
when 'Pooler'
message = status_response['status']['message']
pools = status_response['pools']
pools.select! { |_, pool| pool['ready'] < pool['max'] } unless verbose
when 'Pooler'
message = status_response['status']['message']
pools = status_response['pools']
pools.select! {|_, pool| pool['ready'] < pool['max']} unless verbose
width = pools.keys.map(&:length).max
pools.each do |name, pool|
max = pool['max']
ready = pool['ready']
pending = pool['pending']
missing = max - ready - pending
char = 'o'
puts "#{name.ljust(width)} #{(char * ready)}#{(char * pending)}#{(char * missing)}"
rescue StandardError => e
FloatyLogger.error "#{name.ljust(width)} #{e}"
end
puts message
when 'NonstandardPooler'
pools = status_response
pools.delete 'ok'
pools.select! { |_, pool| pool['available_hosts'] < pool['total_hosts'] } unless verbose
width = pools.keys.map(&:length).max
pools.each do |name, pool|
begin
max = pool['max']
ready = pool['ready']
pending = pool['pending']
missing = max - ready - pending
char = 'o'
puts "#{name.ljust(width)} #{(char*ready).green}#{(char*pending).yellow}#{(char*missing).red}"
rescue => e
puts "#{name.ljust(width)} #{e.red}"
end
end
puts message.colorize(status_response['status']['ok'] ? :default : :red)
when 'NonstandardPooler'
pools = status_response
pools.delete 'ok'
pools.select! {|_, pool| pool['available_hosts'] < pool['total_hosts']} unless verbose
width = pools.keys.map(&:length).max
pools.each do |name, pool|
max = pool['total_hosts']
ready = pool['available_hosts']
pending = pool['pending'] || 0 # not available for nspooler
missing = max - ready - pending
char = 'o'
puts "#{name.ljust(width)} #{(char * ready)}#{(char * pending)}#{(char * missing)}"
rescue StandardError => e
FloatyLogger.error "#{name.ljust(width)} #{e}"
end
when 'ABS'
FloatyLogger.error 'ABS Not OK' unless status_response
puts 'ABS is OK' if status_response
else
raise "Invalid service type #{service.type}"
width = pools.keys.map(&:length).max
pools.each do |name, pool|
begin
max = pool['total_hosts']
ready = pool['available_hosts']
pending = pool['pending'] || 0 # not available for nspooler
missing = max - ready - pending
char = 'o'
puts "#{name.ljust(width)} #{(char*ready).green}#{(char*pending).yellow}#{(char*missing).red}"
rescue => e
puts "#{name.ljust(width)} #{e.red}"
end
end
else
raise "Invalid service type #{service.type}"
end
end
@ -257,15 +165,9 @@ class Utils
end
def self.get_service_object(type = '')
abs_strings = %w[abs alwaysbescheduling always_be_scheduling]
nspooler_strings = %w[ns nspooler nonstandard nonstandard_pooler]
vmpooler_strings = %w[vmpooler]
if abs_strings.include? type.downcase
ABS
elsif nspooler_strings.include? type.downcase
nspooler_strings = ['ns', 'nspooler', 'nonstandard', 'nonstandard_pooler']
if nspooler_strings.include? type.downcase
NonstandardPooler
elsif vmpooler_strings.include? type.downcase
Pooler
else
Pooler
end
@ -274,11 +176,10 @@ class Utils
def self.get_service_config(config, options)
# The top-level url, user, and token values in the config file are treated as defaults
service_config = {
'url' => config['url'],
'user' => config['user'],
'token' => config['token'],
'vmpooler_fallback' => config['vmpooler_fallback'],
'type' => config['type'] || 'vmpooler'
'url' => config['url'],
'user' => config['user'],
'token' => config['token'],
'type' => config['type'] || 'vmpooler'
}
if config['services']
@ -289,51 +190,20 @@ class Utils
service_config.merge! values
else
# If the user provided a service name at the command line, use that service if posible, or fail
unless config['services'][options.service]
raise ArgumentError,
"Could not find a configured service named '#{options.service}' in ~/.vmfloaty.yml"
if config['services'][options.service]
# If the service is configured but some values are missing, use the top-level defaults to fill them in
service_config.merge! config['services'][options.service]
else
raise ArgumentError, "Could not find a configured service named '#{options.service}' in ~/.vmfloaty.yml"
end
# If the service is configured but some values are missing, use the top-level defaults to fill them in
service_config.merge! config['services'][options.service]
end
# No config file but service is declared on command line
elsif !config['services'] && options.service
service_config['type'] = options.service
end
# Prioritize an explicitly specified url, user, or token if the user provided one
service_config['priority'] = options.priority unless options.priority.nil?
service_config['url'] = options.url unless options.url.nil?
service_config['token'] = options.token unless options.token.nil?
service_config['user'] = options.user unless options.user.nil?
service_config
end
# This method gets the vmpooler service configured in ~/.vmfloaty
def self.get_vmpooler_service_config(vmpooler_fallback)
config = Conf.read_config
# The top-level url, user, and token values in the config file are treated as defaults
service_config = {
'url' => config['url'],
'user' => config['user'],
'token' => config['token'],
'type' => 'vmpooler'
}
# at a minimum, the url needs to be configured
if config['services'] && config['services'][vmpooler_fallback] && config['services'][vmpooler_fallback]['url']
# If the service is configured but some values are missing, use the top-level defaults to fill them in
service_config.merge! config['services'][vmpooler_fallback]
elsif vmpooler_fallback.nil?
raise ArgumentError,
"The abs service should have a key named 'vmpooler_fallback' in ~/.vmfloaty.yml with a value that points to a vmpooler service name use this format:\nservices:\n myabs:\n url: 'http://abs.com'\n user: 'superman'\n token: 'kryptonite'\n vmpooler_fallback: 'myvmpooler'\n myvmpooler:\n url: 'http://vmpooler.com'\n user: 'superman'\n token: 'kryptonite'"
else
raise ArgumentError,
"Could not find a configured service named '#{vmpooler_fallback}' in ~/.vmfloaty.yml use this format:\nservices:\n #{vmpooler_fallback}:\n url: 'http://vmpooler.com'\n user: 'superman'\n token: 'kryptonite'"
end
service_config
end
end

View file

@ -1,5 +1,3 @@
# frozen_string_literal: true
class Vmfloaty
VERSION = '1.8.1'
VERSION = '0.8.2'.freeze
end

View file

@ -1,12 +0,0 @@
#!/usr/bin/env bash
# bundle install
docker run -t --rm \
-v $(pwd):/app \
$(grep ^FROM ./Dockerfile |cut -d ' ' -f2) \
/bin/bash -c 'apt-get update -qq && apt-get install -y --no-install-recommends build-essential make openssh-client && cd /app && gem install bundler && bundle install --jobs 3; echo "LOCK_FILE_UPDATE_EXIT_CODE=$?"'
# Update Changelog
docker run -t --rm -e CHANGELOG_GITHUB_TOKEN -v $(pwd):/usr/local/src/your-app \
githubchangeloggenerator/github-changelog-generator:1.16.2 \
github_changelog_generator --future-release $(grep VERSION lib/vmfloaty/version.rb |rev |cut -d "'" -f2 |rev)

View file

@ -1,25 +1,3 @@
# frozen_string_literal: true
require 'simplecov'
require 'simplecov-lcov'
require 'base64'
SimpleCov::Formatter::LcovFormatter.config do |c|
c.report_with_single_file = true
c.single_report_path = 'coverage/lcov.info'
end
SimpleCov.formatter = SimpleCov::Formatter::MultiFormatter.new(
[
SimpleCov::Formatter::HTMLFormatter,
SimpleCov::Formatter::LcovFormatter
]
)
SimpleCov.start do
add_filter %r{^/spec/}
end
require 'vmfloaty'
require 'webmock/rspec'
@ -35,19 +13,3 @@ RSpec.configure do |config|
config.tty = true
config.formatter = :documentation
end
def get_headers(username: nil, password: nil, token: nil, content_type: nil, content_length: nil)
headers = {
'Accept' => '*/*',
'Accept-Encoding' => /gzip/,
'User-Agent' => /Faraday/,
}
if username && password
auth = Base64.encode64("#{username}:#{password}").chomp
headers['Authorization'] = "Basic #{auth}"
end
headers['X-Auth-Token'] = token if token
headers['Content-Type'] = content_type if content_type
headers['Content-Length'] = content_length.to_s if content_length
headers
end

View file

@ -1,93 +0,0 @@
# frozen_string_literal: true
require 'spec_helper'
require_relative '../../../lib/vmfloaty/auth'
user = 'first.last'
pass = 'password'
describe Pooler do
before :each do
@abs_url = 'https://abs.example.com/api/v2'
end
describe '#get_token' do
before :each do
@get_token_response = '{"ok": true,"token":"utpg2i2xswor6h8ttjhu3d47z53yy47y"}'
@token = 'utpg2i2xswor6h8ttjhu3d47z53yy47y'
end
it 'returns a token from abs' do
stub_request(:post, 'https://abs.example.com/api/v2/token')
.with(headers: get_headers(username: user, password: pass, content_length: 0))
.to_return(status: 200, body: @get_token_response, headers: {})
token = Auth.get_token(false, @abs_url, user, pass)
expect(token).to eq @token
end
it 'raises a token error if something goes wrong' do
stub_request(:post, 'https://abs.example.com/api/v2/token')
.with(headers: get_headers(username: user, password: pass, content_length: 0))
.to_return(status: 500, body: '{"ok":false}', headers: {})
expect { Auth.get_token(false, @abs_url, user, pass) }.to raise_error(TokenError)
end
end
describe '#delete_token' do
before :each do
@delete_token_response = '{"ok":true}'
@token = 'utpg2i2xswor6h8ttjhu3d47z53yy47y'
end
it 'deletes the specified token' do
stub_request(:delete, 'https://abs.example.com/api/v2/token/utpg2i2xswor6h8ttjhu3d47z53yy47y')
.with(headers: get_headers(username: user, password: pass))
.to_return(status: 200, body: @delete_token_response, headers: {})
expect(Auth.delete_token(false, @abs_url, user, pass,
@token)).to eq JSON.parse(@delete_token_response)
end
it 'raises a token error if something goes wrong' do
stub_request(:delete, 'https://abs.example.com/api/v2/token/utpg2i2xswor6h8ttjhu3d47z53yy47y')
.with(headers: get_headers(username: user, password: pass))
.to_return(status: 500, body: '{"ok":false}', headers: {})
expect { Auth.delete_token(false, @abs_url, user, pass, @token) }.to raise_error(TokenError)
end
it 'raises a token error if no token provided' do
expect { Auth.delete_token(false, @abs_url, user, pass, nil) }.to raise_error(TokenError)
end
end
describe '#token_status' do
before :each do
@token_status_response = '{"ok":true,"utpg2i2xswor6h8ttjhu3d47z53yy47y":{"created":"2015-04-28 19:17:47 -0700"}}'
@token = 'utpg2i2xswor6h8ttjhu3d47z53yy47y'
end
it 'checks the status of a token' do
stub_request(:get, "#{@abs_url}/token/utpg2i2xswor6h8ttjhu3d47z53yy47y")
.with(headers: get_headers)
.to_return(status: 200, body: @token_status_response, headers: {})
expect(Auth.token_status(false, @abs_url, @token)).to eq JSON.parse(@token_status_response)
end
it 'raises a token error if something goes wrong' do
stub_request(:get, "#{@abs_url}/token/utpg2i2xswor6h8ttjhu3d47z53yy47y")
.with(headers: get_headers)
.to_return(status: 500, body: '{"ok":false}', headers: {})
expect { Auth.token_status(false, @abs_url, @token) }.to raise_error(TokenError)
end
it 'raises a token error if no token provided' do
expect { Auth.token_status(false, @abs_url, nil) }.to raise_error(TokenError)
end
end
end

View file

@ -1,182 +0,0 @@
# frozen_string_literal: true
require 'spec_helper'
require 'vmfloaty/utils'
require 'vmfloaty/errors'
require 'vmfloaty/abs'
describe ABS do
before :each do
end
describe '#list' do
it 'skips empty platforms and lists aws' do
stub_request(:get, 'http://foo/api/v2/status/platforms/vmpooler')
.to_return(status: 200, body: '', headers: {})
stub_request(:get, 'http://foo/api/v2/status/platforms/ondemand_vmpooler')
.to_return(status: 200, body: '', headers: {})
stub_request(:get, 'http://foo/api/v2/status/platforms/nspooler')
.to_return(status: 200, body: '', headers: {})
body = '{
"aws_platforms": [
"amazon-6-x86_64",
"amazon-7-x86_64",
"amazon-7-arm64",
"centos-7-x86-64-west",
"redhat-8-arm64"
]
}'
stub_request(:get, 'http://foo/api/v2/status/platforms/aws')
.to_return(status: 200, body: body, headers: {})
results = ABS.list(false, 'http://foo')
expect(results).to include('amazon-6-x86_64', 'amazon-7-x86_64', 'amazon-7-arm64', 'centos-7-x86-64-west',
'redhat-8-arm64')
end
it 'legacy JSON string, prior to PR 306' do
stub_request(:get, 'http://foo/api/v2/status/platforms/vmpooler')
.to_return(status: 200, body: '', headers: {})
stub_request(:get, 'http://foo/api/v2/status/platforms/ondemand_vmpooler')
.to_return(status: 200, body: '', headers: {})
stub_request(:get, 'http://foo/api/v2/status/platforms/nspooler')
.to_return(status: 200, body: '', headers: {})
body = '{
"aws_platforms": "[\"amazon-6-x86_64\",\"amazon-7-x86_64\",\"amazon-7-arm64\",\"centos-7-x86-64-west\",\"redhat-8-arm64\"]"
}'
stub_request(:get, 'http://foo/api/v2/status/platforms/aws')
.to_return(status: 200, body: body, headers: {})
results = ABS.list(false, 'http://foo')
expect(results).to include('amazon-6-x86_64', 'amazon-7-x86_64', 'amazon-7-arm64', 'centos-7-x86-64-west',
'redhat-8-arm64')
end
end
describe '#format' do
it 'returns an hash formatted like a vmpooler return, plus the job_id' do
job_id = 'generated_by_floaty_12345'
abs_formatted_response = [
{ 'hostname' => 'aaaaaaaaaaaaaaa.delivery.puppetlabs.net', 'type' => 'centos-7.2-x86_64',
'engine' => 'vmpooler' },
{ 'hostname' => 'aaaaaaaaaaaaaab.delivery.puppetlabs.net', 'type' => 'centos-7.2-x86_64',
'engine' => 'vmpooler' },
{ 'hostname' => 'aaaaaaaaaaaaaac.delivery.puppetlabs.net', 'type' => 'ubuntu-7.2-x86_64',
'engine' => 'vmpooler' }
]
vmpooler_formatted_response = ABS.translated(abs_formatted_response, job_id)
vmpooler_formatted_compare = {
'centos-7.2-x86_64' => {},
'ubuntu-7.2-x86_64' => {}
}
vmpooler_formatted_compare['centos-7.2-x86_64']['hostname'] =
['aaaaaaaaaaaaaaa.delivery.puppetlabs.net', 'aaaaaaaaaaaaaab.delivery.puppetlabs.net']
vmpooler_formatted_compare['ubuntu-7.2-x86_64']['hostname'] = ['aaaaaaaaaaaaaac.delivery.puppetlabs.net']
vmpooler_formatted_compare['ok'] = true
vmpooler_formatted_compare['job_id'] = job_id
expect(vmpooler_formatted_response).to eq(vmpooler_formatted_compare)
vmpooler_formatted_response.delete('ok')
vmpooler_formatted_compare.delete('ok')
expect(vmpooler_formatted_response).to eq(vmpooler_formatted_compare)
end
it 'won\'t delete a job if not all vms are listed' do
hosts = ['host1']
allocated_resources = [
{
'hostname' => 'host1'
},
{
'hostname' => 'host2'
}
]
expect(ABS.all_job_resources_accounted_for(allocated_resources, hosts)).to eq(false)
hosts = %w[host1 host2]
allocated_resources = [
{
'hostname' => 'host1'
},
{
'hostname' => 'host2'
}
]
expect(ABS.all_job_resources_accounted_for(allocated_resources, hosts)).to eq(true)
end
before :each do
@abs_url = 'https://abs.example.com'
end
describe '#test_abs_status_queue_endpoint' do
before :each do
# rubocop:disable Layout/LineLength
@active_requests_response = '
[
{ "state":"allocated","last_processed":"2019-12-16 23:00:34 +0000","allocated_resources":[{"hostname":"take-this.delivery.puppetlabs.net","type":"win-2012r2-x86_64","engine":"vmpooler"}],"audit_log":{"2019-12-13 16:45:29 +0000":"Allocated take-this.delivery.puppetlabs.net for job 1576255517241"},"request":{"resources":{"win-2012r2-x86_64":1},"job":{"id":"1576255517241","tags":{"user":"test-user"},"user":"test-user","time-received":1576255519},"priority":1}},
"null",
{"state":"allocated","last_processed":"2019-12-16 23:00:34 +0000","allocated_resources":[{"hostname":"not-this.delivery.puppetlabs.net","type":"win-2012r2-x86_64","engine":"vmpooler"}],"audit_log":{"2019-12-13 16:46:14 +0000":"Allocated not-this.delivery.puppetlabs.net for job 1576255565159"},"request":{"resources":{"win-2012r2-x86_64":1},"job":{"id":"1576255565159","tags":{"user":"not-test-user"},"user":"not-test-user","time-received":1576255566},"priority":1}}
]'
# rubocop:enable Layout/LineLength
@token = 'utpg2i2xswor6h8ttjhu3d47z53yy47y'
@test_user = 'test-user'
end
it 'will skip a line with a null value returned from abs' do
stub_request(:get, 'https://abs.example.com/api/v2/status/queue')
.to_return(status: 200, body: @active_requests_response, headers: {})
ret = ABS.get_active_requests(false, @abs_url, @test_user)
expect(ret[0]).to include(
'allocated_resources' => [{
'hostname' => 'take-this.delivery.puppetlabs.net',
'type' => 'win-2012r2-x86_64',
'engine' => 'vmpooler'
}]
)
end
end
describe '#test_abs_delete_jobid' do
before :each do
# rubocop:disable Layout/LineLength
@active_requests_response = '
[
{ "state":"allocated", "last_processed":"2020-01-17 22:29:13 +0000", "allocated_resources":[{"hostname":"craggy-chord.delivery.puppetlabs.net", "type":"centos-7-x86_64", "engine":"vmpooler"}, {"hostname":"visible-revival.delivery.puppetlabs.net", "type":"centos-7-x86_64", "engine":"vmpooler"}], "audit_log":{"2020-01-17 22:28:45 +0000":"Allocated craggy-chord.delivery.puppetlabs.net, visible-revival.delivery.puppetlabs.net for job 1579300120799"}, "request":{"resources":{"centos-7-x86_64":2}, "job":{"id":"1579300120799", "tags":{"user":"test-user"}, "user":"test-user", "time-received":1579300120}, "priority":3}}
]'
@return_request = {
"job_id" => "1579300120799",
"hosts" => [{"hostname"=>"craggy-chord.delivery.puppetlabs.net","type"=>"centos-7-x86_64","engine"=>"vmpooler"},
{"hostname"=>"visible-revival.delivery.puppetlabs.net","type"=>"centos-7-x86_64","engine"=>"vmpooler"}]
}
# rubocop:enable Layout/LineLength
@token = 'utpg2i2xswor6h8ttjhu3d47z53yy47y'
@test_user = 'test-user'
# Job ID
@hosts = ['1579300120799']
end
it 'will delete the whole job' do
stub_request(:get, 'https://abs.example.com/api/v2/status/queue')
.to_return(status: 200, body: @active_requests_response, headers: {})
stub_request(:post, 'https://abs.example.com/api/v2/return')
.with(headers: get_headers(content_type: 'application/x-www-form-urlencoded', token: @token), body: @return_request.to_json)
.to_return(status: 200, body: 'OK', headers: {})
ret = ABS.delete(false, @abs_url, @hosts, @token, @test_user)
expect(ret).to include(
'craggy-chord.delivery.puppetlabs.net' => { 'ok' => true }, 'visible-revival.delivery.puppetlabs.net' => { 'ok' => true }
)
end
end
end
end

View file

@ -1,92 +1,86 @@
# frozen_string_literal: true
require 'spec_helper'
require_relative '../../lib/vmfloaty/auth'
user = 'first.last'
pass = 'password'
describe Pooler do
before :each do
@vmpooler_url = 'https://vmpooler.example.com'
@vmpooler_url = "https://vmpooler.example.com"
end
describe '#get_token' do
describe "#get_token" do
before :each do
@get_token_response = '{"ok": true,"token":"utpg2i2xswor6h8ttjhu3d47z53yy47y"}'
@token = 'utpg2i2xswor6h8ttjhu3d47z53yy47y'
@get_token_response = "{\"ok\": true,\"token\":\"utpg2i2xswor6h8ttjhu3d47z53yy47y\"}"
@token = "utpg2i2xswor6h8ttjhu3d47z53yy47y"
end
it 'returns a token from vmpooler' do
stub_request(:post, 'https://vmpooler.example.com/token')
.with(headers: get_headers(username: user, password: pass, content_length: 0))
.to_return(status: 200, body: @get_token_response, headers: {})
it "returns a token from vmpooler" do
stub_request(:post, "https://first.last:password@vmpooler.example.com/token").
with(:headers => {'Accept'=>'*/*', 'Accept-Encoding'=>'gzip;q=1.0,deflate;q=0.6,identity;q=0.3', 'Content-Length'=>'0', 'User-Agent'=>'Faraday v0.9.2'}).
to_return(:status => 200, :body => @get_token_response, :headers => {})
token = Auth.get_token(false, @vmpooler_url, user, pass)
token = Auth.get_token(false, @vmpooler_url, "first.last", "password")
expect(token).to eq @token
end
it 'raises a token error if something goes wrong' do
stub_request(:post, 'https://vmpooler.example.com/token')
.with(headers: get_headers(username: user, password: pass, content_length: 0))
.to_return(status: 500, body: '{"ok":false}', headers: {})
it "raises a token error if something goes wrong" do
stub_request(:post, "https://first.last:password@vmpooler.example.com/token").
with(:headers => {'Accept'=>'*/*', 'Accept-Encoding'=>'gzip;q=1.0,deflate;q=0.6,identity;q=0.3', 'Content-Length'=>'0', 'User-Agent'=>'Faraday v0.9.2'}).
to_return(:status => 500, :body => "{\"ok\":false}", :headers => {})
expect { Auth.get_token(false, @vmpooler_url, user, pass) }.to raise_error(TokenError)
expect{ Auth.get_token(false, @vmpooler_url, "first.last", "password") }.to raise_error(TokenError)
end
end
describe '#delete_token' do
describe "#delete_token" do
before :each do
@delete_token_response = '{"ok":true}'
@token = 'utpg2i2xswor6h8ttjhu3d47z53yy47y'
@delete_token_response = "{\"ok\":true}"
@token = "utpg2i2xswor6h8ttjhu3d47z53yy47y"
end
it 'deletes the specified token' do
stub_request(:delete, 'https://vmpooler.example.com/token/utpg2i2xswor6h8ttjhu3d47z53yy47y')
.with(headers: get_headers(username: user, password: pass))
.to_return(status: 200, body: @delete_token_response, headers: {})
it "deletes the specified token" do
stub_request(:delete, "https://first.last:password@vmpooler.example.com/token/utpg2i2xswor6h8ttjhu3d47z53yy47y").
with(:headers => {'Accept'=>'*/*', 'Accept-Encoding'=>'gzip;q=1.0,deflate;q=0.6,identity;q=0.3', 'User-Agent'=>'Faraday v0.9.2'}).
to_return(:status => 200, :body => @delete_token_response, :headers => {})
expect(Auth.delete_token(false, @vmpooler_url, user, pass,
@token)).to eq JSON.parse(@delete_token_response)
expect(Auth.delete_token(false, @vmpooler_url, "first.last", "password", @token)).to eq JSON.parse(@delete_token_response)
end
it 'raises a token error if something goes wrong' do
stub_request(:delete, 'https://vmpooler.example.com/token/utpg2i2xswor6h8ttjhu3d47z53yy47y')
.with(headers: get_headers(username: user, password: pass))
.to_return(status: 500, body: '{"ok":false}', headers: {})
it "raises a token error if something goes wrong" do
stub_request(:delete, "https://first.last:password@vmpooler.example.com/token/utpg2i2xswor6h8ttjhu3d47z53yy47y").
with(:headers => {'Accept'=>'*/*', 'Accept-Encoding'=>'gzip;q=1.0,deflate;q=0.6,identity;q=0.3', 'User-Agent'=>'Faraday v0.9.2'}).
to_return(:status => 500, :body => "{\"ok\":false}", :headers => {})
expect { Auth.delete_token(false, @vmpooler_url, 'first.last', 'password', @token) }.to raise_error(TokenError)
expect{ Auth.delete_token(false, @vmpooler_url, "first.last", "password", @token) }.to raise_error(TokenError)
end
it 'raises a token error if no token provided' do
expect { Auth.delete_token(false, @vmpooler_url, 'first.last', 'password', nil) }.to raise_error(TokenError)
it "raises a token error if no token provided" do
expect{ Auth.delete_token(false, @vmpooler_url, "first.last", "password", nil) }.to raise_error(TokenError)
end
end
describe '#token_status' do
describe "#token_status" do
before :each do
@token_status_response = '{"ok":true,"utpg2i2xswor6h8ttjhu3d47z53yy47y":{"created":"2015-04-28 19:17:47 -0700"}}'
@token = 'utpg2i2xswor6h8ttjhu3d47z53yy47y'
@token_status_response = "{\"ok\":true,\"utpg2i2xswor6h8ttjhu3d47z53yy47y\":{\"created\":\"2015-04-28 19:17:47 -0700\"}}"
@token = "utpg2i2xswor6h8ttjhu3d47z53yy47y"
end
it 'checks the status of a token' do
stub_request(:get, "#{@vmpooler_url}/token/utpg2i2xswor6h8ttjhu3d47z53yy47y")
.with(headers: get_headers)
.to_return(status: 200, body: @token_status_response, headers: {})
it "checks the status of a token" do
stub_request(:get, "#{@vmpooler_url}/token/utpg2i2xswor6h8ttjhu3d47z53yy47y").
with(:headers => {'Accept'=>'*/*', 'Accept-Encoding'=>'gzip;q=1.0,deflate;q=0.6,identity;q=0.3', 'User-Agent'=>'Faraday v0.9.2'}).
to_return(:status => 200, :body => @token_status_response, :headers => {})
expect(Auth.token_status(false, @vmpooler_url, @token)).to eq JSON.parse(@token_status_response)
end
it 'raises a token error if something goes wrong' do
stub_request(:get, "#{@vmpooler_url}/token/utpg2i2xswor6h8ttjhu3d47z53yy47y")
.with(headers: get_headers)
.to_return(status: 500, body: '{"ok":false}', headers: {})
it "raises a token error if something goes wrong" do
stub_request(:get, "#{@vmpooler_url}/token/utpg2i2xswor6h8ttjhu3d47z53yy47y").
with(:headers => {'Accept'=>'*/*', 'Accept-Encoding'=>'gzip;q=1.0,deflate;q=0.6,identity;q=0.3', 'User-Agent'=>'Faraday v0.9.2'}).
to_return(:status => 500, :body => "{\"ok\":false}", :headers => {})
expect { Auth.token_status(false, @vmpooler_url, @token) }.to raise_error(TokenError)
expect{ Auth.token_status(false, @vmpooler_url, @token) }.to raise_error(TokenError)
end
it 'raises a token error if no token provided' do
expect { Auth.token_status(false, @vmpooler_url, nil) }.to raise_error(TokenError)
it "raises a token error if no token provided" do
expect{ Auth.token_status(false, @vmpooler_url, nil) }.to raise_error(TokenError)
end
end
end

View file

@ -1,5 +1,3 @@
# frozen_string_literal: true
require 'spec_helper'
require 'vmfloaty/utils'
require 'vmfloaty/errors'
@ -8,27 +6,42 @@ require 'vmfloaty/nonstandard_pooler'
describe NonstandardPooler do
before :each do
@nspooler_url = 'https://nspooler.example.com'
@auth_token_headers = get_headers(token: 'token-value')
@post_request_headers = {
'Accept' => '*/*',
'Accept-Encoding' => 'gzip;q=1.0,deflate;q=0.6,identity;q=0.3',
'User-Agent' => 'Faraday v0.9.2',
'X-Auth-Token' => 'token-value'
}
@get_request_headers = {
'Accept' => '*/*',
'Accept-Encoding' => 'gzip;q=1.0,deflate;q=0.6,identity;q=0.3',
'User-Agent' => 'Faraday v0.9.2',
'X-Auth-Token' => 'token-value'
}
@get_request_headers_notoken = @get_request_headers.tap do |headers|
headers.delete('X-Auth-Token')
end
end
describe '#list' do
before :each do
@status_response_body = <<~BODY
{
"ok": true,
"solaris-10-sparc": {
"total_hosts": 11,
"available_hosts": 11
},
"ubuntu-16.04-power8": {
"total_hosts": 10,
"available_hosts": 10
},
"aix-7.2-power": {
"total_hosts": 5,
"available_hosts": 4
}
}
@status_response_body = <<-BODY
{
"ok": true,
"solaris-10-sparc": {
"total_hosts": 11,
"available_hosts": 11
},
"ubuntu-16.04-power8": {
"total_hosts": 10,
"available_hosts": 10
},
"aix-7.2-power": {
"total_hosts": 5,
"available_hosts": 4
}
}
BODY
end
@ -61,24 +74,24 @@ describe NonstandardPooler do
describe '#list_active' do
before :each do
@token_status_body_active = <<~BODY
{
"ok": true,
"user": "first.last",
"created": "2017-09-18 01:25:41 +0000",
"last_accessed": "2017-09-21 19:46:25 +0000",
"reserved_hosts": ["sol10-9", "sol10-11"]
}
BODY
@token_status_body_empty = <<~BODY
{
"ok": true,
"user": "first.last",
"created": "2017-09-18 01:25:41 +0000",
"last_accessed": "2017-09-21 19:46:25 +0000",
"reserved_hosts": []
}
BODY
@token_status_body_active = <<-BODY
{
"ok": true,
"user": "first.last",
"created": "2017-09-18 01:25:41 +0000",
"last_accessed": "2017-09-21 19:46:25 +0000",
"reserved_hosts": ["sol10-9", "sol10-11"]
}
BODY
@token_status_body_empty = <<-BODY
{
"ok": true,
"user": "first.last",
"created": "2017-09-18 01:25:41 +0000",
"last_accessed": "2017-09-21 19:46:25 +0000",
"reserved_hosts": []
}
BODY
end
it 'prints an output of fqdn, template, and duration' do
@ -86,72 +99,69 @@ describe NonstandardPooler do
.with(false, @nspooler_url, 'token-value')
.and_return(JSON.parse(@token_status_body_active))
list = NonstandardPooler.list_active(false, @nspooler_url, 'token-value', 'user')
expect(list).to eql %w[sol10-9 sol10-11]
list = NonstandardPooler.list_active(false, @nspooler_url, 'token-value')
expect(list).to eql ['sol10-9', 'sol10-11']
end
end
describe '#retrieve' do
before :each do
@retrieve_response_body_single = <<~BODY
{
"ok": true,
"solaris-11-sparc": {
"hostname": "sol11-4.delivery.puppetlabs.net"
}
}
BODY
@retrieve_response_body_many = <<~BODY
{
"ok": true,
"solaris-10-sparc": {
"hostname": [
"sol10-9.delivery.puppetlabs.net",
"sol10-10.delivery.puppetlabs.net"
]
},
"aix-7.1-power": {
"hostname": "pe-aix-71-ci-acceptance.delivery.puppetlabs.net"
}
}
BODY
@retrieve_response_body_single = <<-BODY
{
"ok": true,
"solaris-11-sparc": {
"hostname": "sol11-4.delivery.puppetlabs.net"
}
}
BODY
@retrieve_response_body_many = <<-BODY
{
"ok": true,
"solaris-10-sparc": {
"hostname": [
"sol10-9.delivery.puppetlabs.net",
"sol10-10.delivery.puppetlabs.net"
]
},
"aix-7.1-power": {
"hostname": "pe-aix-71-ci-acceptance.delivery.puppetlabs.net"
}
}
BODY
end
it 'raises an AuthError if the token is invalid' do
stub_request(:post, "#{@nspooler_url}/host/solaris-11-sparc")
.with(headers: get_headers(token: 'token-value'))
.with(headers: @post_request_headers)
.to_return(status: 401, body: '{"ok":false,"reason": "token: token-value does not exist"}', headers: {})
vm_hash = { 'solaris-11-sparc' => 1 }
expect do
NonstandardPooler.retrieve(false, vm_hash, 'token-value', @nspooler_url, 'first.last', {})
end.to raise_error(AuthError)
expect { NonstandardPooler.retrieve(false, vm_hash, 'token-value', @nspooler_url) }.to raise_error(AuthError)
end
it 'retrieves a single vm with a token' do
stub_request(:post, "#{@nspooler_url}/host/solaris-11-sparc")
.with(headers: @auth_token_headers)
.with(headers: @post_request_headers)
.to_return(status: 200, body: @retrieve_response_body_single, headers: {})
vm_hash = { 'solaris-11-sparc' => 1 }
vm_req = NonstandardPooler.retrieve(false, vm_hash, 'token-value', @nspooler_url, 'first.last', {})
vm_req = NonstandardPooler.retrieve(false, vm_hash, 'token-value', @nspooler_url)
expect(vm_req).to be_an_instance_of Hash
expect(vm_req['ok']).to equal true
expect(vm_req['solaris-11-sparc']['hostname']).to eq 'sol11-4.delivery.puppetlabs.net'
end
it 'retrieves a multiple vms with a token' do
stub_request(:post, "#{@nspooler_url}/host/aix-7.1-power+solaris-10-sparc+solaris-10-sparc")
.with(headers: @auth_token_headers)
stub_request(:post,"#{@nspooler_url}/host/aix-7.1-power+solaris-10-sparc+solaris-10-sparc")
.with(headers: @post_request_headers)
.to_return(status: 200, body: @retrieve_response_body_many, headers: {})
vm_hash = { 'aix-7.1-power' => 1, 'solaris-10-sparc' => 2 }
vm_req = NonstandardPooler.retrieve(false, vm_hash, 'token-value', @nspooler_url, 'first.last', {})
vm_req = NonstandardPooler.retrieve(false, vm_hash, 'token-value', @nspooler_url)
expect(vm_req).to be_an_instance_of Hash
expect(vm_req['ok']).to equal true
expect(vm_req['solaris-10-sparc']['hostname']).to be_an_instance_of Array
expect(vm_req['solaris-10-sparc']['hostname']).to eq ['sol10-9.delivery.puppetlabs.net',
'sol10-10.delivery.puppetlabs.net']
expect(vm_req['solaris-10-sparc']['hostname']).to eq ['sol10-9.delivery.puppetlabs.net', 'sol10-10.delivery.puppetlabs.net']
expect(vm_req['aix-7.1-power']['hostname']).to eq 'pe-aix-71-ci-acceptance.delivery.puppetlabs.net'
end
end
@ -162,23 +172,23 @@ describe NonstandardPooler do
end
it 'raises an error if the user tries to modify an unsupported attribute' do
stub_request(:put, 'https://nspooler.example.com/host/myfakehost')
.with(body: { '{}' => true },
headers: @auth_token_headers)
.to_return(status: 200, body: '', headers: {})
stub_request(:put, "https://nspooler.example.com/host/myfakehost").
with(body: {"{}"=>true},
headers: {'Accept'=>'*/*', 'Accept-Encoding'=>'gzip;q=1.0,deflate;q=0.6,identity;q=0.3', 'Content-Type'=>'application/x-www-form-urlencoded', 'User-Agent'=>'Faraday v0.9.2', 'X-Auth-Token'=>'token-value'}).
to_return(status: 200, body: "", headers: {})
details = { lifetime: 12 }
expect { NonstandardPooler.modify(false, @nspooler_url, 'myfakehost', 'token-value', details) }
.to raise_error(ModifyError)
.to raise_error(ModifyError)
end
it 'modifies the reason of a vm' do
modify_request_body = { '{"reserved_for_reason":"testing"}' => nil }
modify_request_body = { '{"reserved_for_reason":"testing"}' => true }
stub_request(:put, "#{@nspooler_url}/host/myfakehost")
.with(body: modify_request_body,
headers: @auth_token_headers)
headers: @post_request_headers)
.to_return(status: 200, body: '{"ok": true}', headers: {})
modify_hash = { reason: 'testing' }
modify_hash = { reason: "testing" }
modify_req = NonstandardPooler.modify(false, @nspooler_url, 'myfakehost', 'token-value', modify_hash)
expect(modify_req['ok']).to be true
end
@ -188,27 +198,28 @@ describe NonstandardPooler do
before :each do
@status_response_body = '{"capacity":{"current":716,"total":717,"percent": 99.9},"status":{"ok":true,"message":"Battle station fully armed and operational."}}'
# TODO: make this report stuff like 'broken'
@status_response_body = <<~BODY
{
"ok": true,
"solaris-10-sparc": {
"total_hosts": 11,
"available_hosts": 10
},
"ubuntu-16.04-power8": {
"total_hosts": 10,
"available_hosts": 10
},
"aix-7.2-power": {
"total_hosts": 5,
"available_hosts": 4
}
}
BODY
@status_response_body = <<-BODY
{
"ok": true,
"solaris-10-sparc": {
"total_hosts": 11,
"available_hosts": 10
},
"ubuntu-16.04-power8": {
"total_hosts": 10,
"available_hosts": 10
},
"aix-7.2-power": {
"total_hosts": 5,
"available_hosts": 4
}
}
BODY
end
it 'prints the status' do
stub_request(:get, "#{@nspooler_url}/status")
.with(headers: @get_request_headers)
.to_return(status: 200, body: @status_response_body, headers: {})
status = NonstandardPooler.status(false, @nspooler_url)
@ -218,20 +229,21 @@ describe NonstandardPooler do
describe '#summary' do
before :each do
@status_response_body = <<~BODY
{
"ok": true,
"total": 57,
"available": 39,
"in_use": 16,
"resetting": 2,
"broken": 0
}
BODY
@status_response_body = <<-BODY
{
"ok": true,
"total": 57,
"available": 39,
"in_use": 16,
"resetting": 2,
"broken": 0
}
BODY
end
it 'prints the summary' do
stub_request(:get, "#{@nspooler_url}/summary")
.with(headers: @get_request_headers)
.to_return(status: 200, body: @status_response_body, headers: {})
summary = NonstandardPooler.summary(false, @nspooler_url)
@ -241,22 +253,23 @@ describe NonstandardPooler do
describe '#query' do
before :each do
@query_response_body = <<~BODY
{
"ok": true,
"sol10-11": {
"fqdn": "sol10-11.delivery.puppetlabs.net",
"os_triple": "solaris-10-sparc",
"reserved_by_user": "first.last",
"reserved_for_reason": "testing",
"hours_left_on_reservation": 29.12
}
}
BODY
@query_response_body = <<-BODY
{
"ok": true,
"sol10-11": {
"fqdn": "sol10-11.delivery.puppetlabs.net",
"os_triple": "solaris-10-sparc",
"reserved_by_user": "first.last",
"reserved_for_reason": "testing",
"hours_left_on_reservation": 29.12
}
}
BODY
end
it 'makes a query about a vm' do
stub_request(:get, "#{@nspooler_url}/host/sol10-11")
.with(headers: @get_request_headers_notoken)
.to_return(status: 200, body: @query_response_body, headers: {})
query_req = NonstandardPooler.query(false, @nspooler_url, 'sol10-11')
@ -272,19 +285,19 @@ describe NonstandardPooler do
it 'deletes a single existing vm' do
stub_request(:delete, "#{@nspooler_url}/host/sol11-7")
.with(headers: @auth_token_headers)
.with(headers: @post_request_headers)
.to_return(status: 200, body: @delete_response_success, headers: {})
request = NonstandardPooler.delete(false, @nspooler_url, 'sol11-7', 'token-value', nil)
request = NonstandardPooler.delete(false, @nspooler_url, 'sol11-7', 'token-value')
expect(request['sol11-7']['ok']).to be true
end
it 'does not delete a nonexistant vm' do
stub_request(:delete, "#{@nspooler_url}/host/fakehost")
.with(headers: @auth_token_headers)
.with(headers: @post_request_headers)
.to_return(status: 401, body: @delete_response_failure, headers: {})
request = NonstandardPooler.delete(false, @nspooler_url, 'fakehost', 'token-value', nil)
request = NonstandardPooler.delete(false, @nspooler_url, 'fakehost', 'token-value')
expect(request['fakehost']['ok']).to be false
end
end

View file

@ -1,247 +1,224 @@
# frozen_string_literal: true
require 'spec_helper'
require_relative '../../lib/vmfloaty/pooler'
describe Pooler do
before :each do
@vmpooler_url = 'https://vmpooler.example.com'
@vmpooler_url = "https://vmpooler.example.com"
end
describe '#list' do
describe "#list" do
before :each do
@list_response_body = '["debian-7-i386","debian-7-x86_64","centos-7-x86_64"]'
@list_response_body = "[\"debian-7-i386\",\"debian-7-x86_64\",\"centos-7-x86_64\"]"
end
it 'returns a hash with operating systems from the pooler' do
stub_request(:get, "#{@vmpooler_url}/vm")
.to_return(status: 200, body: @list_response_body, headers: {})
it "returns a hash with operating systems from the pooler" do
stub_request(:get, "#{@vmpooler_url}/vm").
to_return(:status => 200, :body => @list_response_body, :headers => {})
list = Pooler.list(false, @vmpooler_url, nil)
expect(list).to be_an_instance_of Array
end
it 'filters operating systems based on the filter param' do
stub_request(:get, "#{@vmpooler_url}/vm")
.to_return(status: 200, body: @list_response_body, headers: {})
it "filters operating systems based on the filter param" do
stub_request(:get, "#{@vmpooler_url}/vm").
to_return(:status => 200, :body => @list_response_body, :headers => {})
list = Pooler.list(false, @vmpooler_url, 'deb')
list = Pooler.list(false, @vmpooler_url, "deb")
expect(list).to be_an_instance_of Array
expect(list.size).to equal 2
end
it 'returns nothing if the filter does not match' do
stub_request(:get, "#{@vmpooler_url}/vm")
.to_return(status: 200, body: @list_response_body, headers: {})
it "returns nothing if the filter does not match" do
stub_request(:get, "#{@vmpooler_url}/vm").
to_return(:status => 200, :body => @list_response_body, :headers => {})
list = Pooler.list(false, @vmpooler_url, 'windows')
list = Pooler.list(false, @vmpooler_url, "windows")
expect(list).to be_an_instance_of Array
expect(list.size).to equal 0
end
end
describe '#retrieve' do
describe "#retrieve" do
before :each do
@retrieve_response_body_single = '{"ok":true,"debian-7-i386":{"hostname":"fq6qlpjlsskycq6"}}'
@retrieve_response_body_double = '{"ok":true,"debian-7-i386":{"hostname":["sc0o4xqtodlul5w","4m4dkhqiufnjmxy"]},"centos-7-x86_64":{"hostname":"zb91y9qbrbf6d3q"}}'
@retrieve_response_body_single = "{\"ok\":true,\"debian-7-i386\":{\"hostname\":\"fq6qlpjlsskycq6\"}}"
@retrieve_response_body_double = "{\"ok\":true,\"debian-7-i386\":{\"hostname\":[\"sc0o4xqtodlul5w\",\"4m4dkhqiufnjmxy\"]},\"centos-7-x86_64\":{\"hostname\":\"zb91y9qbrbf6d3q\"}}"
end
it 'raises an AuthError if the token is invalid' do
stub_request(:post, "#{@vmpooler_url}/vm/debian-7-i386")
.with(headers: { 'X-Auth-Token' => 'mytokenfile' })
.to_return(status: 401, body: '{"ok":false}', headers: {})
it "raises an AuthError if the token is invalid" do
stub_request(:post, "#{@vmpooler_url}/vm/debian-7-i386").
with(:headers => {'Accept'=>'*/*', 'Accept-Encoding'=>'gzip;q=1.0,deflate;q=0.6,identity;q=0.3', 'Content-Length'=>'0', 'User-Agent'=>'Faraday v0.9.2', 'X-Auth-Token'=>'mytokenfile'}).
to_return(:status => 401, :body => "{\"ok\":false}", :headers => {})
vm_hash = {}
vm_hash['debian-7-i386'] = 1
expect { Pooler.retrieve(false, vm_hash, 'mytokenfile', @vmpooler_url, 'user', {}) }.to raise_error(AuthError)
expect{ Pooler.retrieve(false, vm_hash, 'mytokenfile', @vmpooler_url) }.to raise_error(AuthError)
end
it 'retrieves a single vm with a token' do
stub_request(:post, "#{@vmpooler_url}/vm/debian-7-i386")
.with(headers: { 'X-Auth-Token' => 'mytokenfile' })
.to_return(status: 200, body: @retrieve_response_body_single, headers: {})
it "retrieves a single vm with a token" do
stub_request(:post, "#{@vmpooler_url}/vm/debian-7-i386").
with(:headers => {'Accept'=>'*/*', 'Accept-Encoding'=>'gzip;q=1.0,deflate;q=0.6,identity;q=0.3', 'Content-Length'=>'0', 'User-Agent'=>'Faraday v0.9.2', 'X-Auth-Token'=>'mytokenfile'}).
to_return(:status => 200, :body => @retrieve_response_body_single, :headers => {})
vm_hash = {}
vm_hash['debian-7-i386'] = 1
vm_req = Pooler.retrieve(false, vm_hash, 'mytokenfile', @vmpooler_url, 'user', {})
vm_req = Pooler.retrieve(false, vm_hash, 'mytokenfile', @vmpooler_url)
expect(vm_req).to be_an_instance_of Hash
expect(vm_req['ok']).to equal true
expect(vm_req['debian-7-i386']['hostname']).to eq 'fq6qlpjlsskycq6'
expect(vm_req["ok"]).to equal true
expect(vm_req["debian-7-i386"]["hostname"]).to eq "fq6qlpjlsskycq6"
end
it 'retrieves a multiple vms with a token' do
stub_request(:post, "#{@vmpooler_url}/vm/debian-7-i386+debian-7-i386+centos-7-x86_64")
.with(headers: { 'X-Auth-Token' => 'mytokenfile' })
.to_return(status: 200, body: @retrieve_response_body_double, headers: {})
it "retrieves a multiple vms with a token" do
stub_request(:post, "#{@vmpooler_url}/vm/debian-7-i386+debian-7-i386+centos-7-x86_64").
with(:headers => {'Accept'=>'*/*', 'Accept-Encoding'=>'gzip;q=1.0,deflate;q=0.6,identity;q=0.3', 'Content-Length'=>'0', 'User-Agent'=>'Faraday v0.9.2', 'X-Auth-Token'=>'mytokenfile'}).
to_return(:status => 200, :body => @retrieve_response_body_double, :headers => {})
vm_hash = {}
vm_hash['debian-7-i386'] = 2
vm_hash['centos-7-x86_64'] = 1
vm_req = Pooler.retrieve(false, vm_hash, 'mytokenfile', @vmpooler_url, 'user', {})
vm_req = Pooler.retrieve(false, vm_hash, 'mytokenfile', @vmpooler_url)
expect(vm_req).to be_an_instance_of Hash
expect(vm_req['ok']).to equal true
expect(vm_req['debian-7-i386']['hostname']).to be_an_instance_of Array
expect(vm_req['debian-7-i386']['hostname']).to eq %w[sc0o4xqtodlul5w 4m4dkhqiufnjmxy]
expect(vm_req['centos-7-x86_64']['hostname']).to eq 'zb91y9qbrbf6d3q'
end
context 'with ondemand provisioning' do
let(:ondemand_response) { '{"ok":true,"request_id":"1234"}' }
it 'retreives the vm with a token' do
stub_request(:post, "#{@vmpooler_url}/ondemandvm/debian-7-i386")
.with(headers: { 'X-Auth-Token' => 'mytokenfile' })
.to_return(status: 200, body: ondemand_response, headers: {})
stub_request(:get, "#{@vmpooler_url}/ondemandvm/1234")
.to_return(status: 200, body: @retrieve_response_body_single, headers: {})
vm_hash = {}
vm_hash['debian-7-i386'] = 1
Pooler.retrieve(false, vm_hash, 'mytokenfile', @vmpooler_url, 'user', {}, true)
vm_req = Pooler.check_ondemandvm(false, '1234', @vmpooler_url)
expect(vm_req).to be_an_instance_of Hash
expect(vm_req['ok']).to equal true
expect(vm_req['debian-7-i386']['hostname']).to eq 'fq6qlpjlsskycq6'
end
expect(vm_req["ok"]).to equal true
expect(vm_req["debian-7-i386"]["hostname"]).to be_an_instance_of Array
expect(vm_req["debian-7-i386"]["hostname"]).to eq ["sc0o4xqtodlul5w", "4m4dkhqiufnjmxy"]
expect(vm_req["centos-7-x86_64"]["hostname"]).to eq "zb91y9qbrbf6d3q"
end
end
describe '#modify' do
describe "#modify" do
before :each do
@modify_response_body_success = '{"ok":true}'
@modify_response_body_fail = '{"ok":false}'
@modify_response_body_success = "{\"ok\":true}"
@modify_response_body_fail = "{\"ok\":false}"
end
it 'raises a TokenError if token provided is nil' do
expect { Pooler.modify(false, @vmpooler_url, 'myfakehost', nil, {}) }.to raise_error(TokenError)
it "raises a TokenError if token provided is nil" do
expect{ Pooler.modify(false, @vmpooler_url, 'myfakehost', nil, {}) }.to raise_error(TokenError)
end
it 'modifies the TTL of a vm' do
modify_hash = { lifetime: 12 }
stub_request(:put, "#{@vmpooler_url}/vm/fq6qlpjlsskycq6")
.with(body: { '{"lifetime":12}' => nil },
headers: get_headers(content_type: 'application/x-www-form-urlencoded', token: 'mytokenfile'))
.to_return(status: 200, body: @modify_response_body_success, headers: {})
it "modifies the TTL of a vm" do
modify_hash = { :lifetime => 12 }
stub_request(:put, "#{@vmpooler_url}/vm/fq6qlpjlsskycq6").
with(:body => {'{"lifetime":12}'=>true},
:headers => {'Accept'=>'*/*', 'Accept-Encoding'=>'gzip;q=1.0,deflate;q=0.6,identity;q=0.3', 'Content-Type'=>'application/x-www-form-urlencoded', 'User-Agent'=>'Faraday v0.9.2', 'X-Auth-Token'=>'mytokenfile'}).
to_return(:status => 200, :body => @modify_response_body_success, :headers => {})
modify_req = Pooler.modify(false, @vmpooler_url, 'fq6qlpjlsskycq6', 'mytokenfile', modify_hash)
expect(modify_req['ok']).to be true
expect(modify_req["ok"]).to be true
end
end
describe '#delete' do
describe "#delete" do
before :each do
@delete_response_body_success = '{"ok":true}'
@delete_response = { 'fq6qlpjlsskycq6' => { 'ok' => true } }
@delete_response_body_success = "{\"ok\":true}"
@delete_response = {"fq6qlpjlsskycq6"=>{"ok"=>true}}
end
it 'deletes a specified vm' do
stub_request(:delete, "#{@vmpooler_url}/vm/fq6qlpjlsskycq6")
.with(headers: { 'X-Auth-Token' => 'mytokenfile' })
.to_return(status: 200, body: @delete_response_body_success, headers: {})
it "deletes a specified vm" do
stub_request(:delete, "#{@vmpooler_url}/vm/fq6qlpjlsskycq6").
with(:headers => {'Accept'=>'*/*', 'Accept-Encoding'=>'gzip;q=1.0,deflate;q=0.6,identity;q=0.3', 'User-Agent'=>'Faraday v0.9.2', 'X-Auth-Token'=>'mytokenfile'}).
to_return(:status => 200, :body => @delete_response_body_success, :headers => {})
expect(Pooler.delete(false, @vmpooler_url, ['fq6qlpjlsskycq6'], 'mytokenfile', nil)).to eq @delete_response
expect(Pooler.delete(false, @vmpooler_url, ['fq6qlpjlsskycq6'], 'mytokenfile')).to eq @delete_response
end
it 'raises a token error if no token provided' do
expect { Pooler.delete(false, @vmpooler_url, ['myfakehost'], nil, nil) }.to raise_error(TokenError)
it "raises a token error if no token provided" do
expect{ Pooler.delete(false, @vmpooler_url, ['myfakehost'], nil) }.to raise_error(TokenError)
end
end
describe '#status' do
describe "#status" do
before :each do
# smaller version
@status_response_body = '{"capacity":{"current":716,"total":717,"percent": 99.9},"status":{"ok":true,"message":"Battle station fully armed and operational."}}'
#smaller version
@status_response_body = "{\"capacity\":{\"current\":716,\"total\":717,\"percent\": 99.9},\"status\":{\"ok\":true,\"message\":\"Battle station fully armed and operational.\"}}"
end
it 'prints the status' do
stub_request(:get, "#{@vmpooler_url}/status")
.to_return(status: 200, body: @status_response_body, headers: {})
it "prints the status" do
stub_request(:get, "#{@vmpooler_url}/status").
with(:headers => {'Accept'=>'*/*', 'Accept-Encoding'=>'gzip;q=1.0,deflate;q=0.6,identity;q=0.3', 'User-Agent'=>'Faraday v0.9.2'}).
to_return(:status => 200, :body => @status_response_body, :headers => {})
status = Pooler.status(false, @vmpooler_url)
expect(status).to be_an_instance_of Hash
end
end
describe '#summary' do
describe "#summary" do
before :each do
@status_response_body = ''
@status_response_body = ""
it 'prints the summary' do
it "prints the summary" do
end
end
end
describe '#query' do
describe "#query" do
before :each do
@query_response_body = '{"ok": true,"fq6qlpjlsskycq6":{"template":"debian-7-x86_64","lifetime": 2,"running": 0.08,"state":"running","snapshots":["n4eb4kdtp7rwv4x158366vd9jhac8btq" ],"domain": "delivery.puppetlabs.net"}}'
@query_response_body = "{\"ok\": true,\"fq6qlpjlsskycq6\":{\"template\":\"debian-7-x86_64\",\"lifetime\": 2,\"running\": 0.08,\"state\":\"running\",\"snapshots\":[\"n4eb4kdtp7rwv4x158366vd9jhac8btq\" ],\"domain\": \"delivery.puppetlabs.net\"}}"
end
it 'makes a query about a vm' do
stub_request(:get, "#{@vmpooler_url}/vm/fq6qlpjlsskycq6")
.to_return(status: 200, body: @query_response_body, headers: {})
it "makes a query about a vm" do
stub_request(:get, "#{@vmpooler_url}/vm/fq6qlpjlsskycq6").
with(:headers => {'Accept'=>'*/*', 'Accept-Encoding'=>'gzip;q=1.0,deflate;q=0.6,identity;q=0.3', 'User-Agent'=>'Faraday v0.9.2'}).
to_return(:status => 200, :body => @query_response_body, :headers => {})
query_req = Pooler.query(false, @vmpooler_url, 'fq6qlpjlsskycq6')
expect(query_req).to be_an_instance_of Hash
end
end
describe '#snapshot' do
describe "#snapshot" do
before :each do
@snapshot_response_body = '{"ok":true}'
@snapshot_response_body = "{\"ok\":true}"
end
it 'makes a snapshot for a single vm' do
stub_request(:post, "#{@vmpooler_url}/vm/fq6qlpjlsskycq6/snapshot")
.with(headers: { 'X-Auth-Token' => 'mytokenfile' })
.to_return(status: 200, body: @snapshot_response_body, headers: {})
it "makes a snapshot for a single vm" do
stub_request(:post, "#{@vmpooler_url}/vm/fq6qlpjlsskycq6/snapshot").
with(:headers => {'Accept'=>'*/*', 'Accept-Encoding'=>'gzip;q=1.0,deflate;q=0.6,identity;q=0.3', 'Content-Length'=>'0', 'User-Agent'=>'Faraday v0.9.2', 'X-Auth-Token'=>'mytokenfile'}).
to_return(:status => 200, :body => @snapshot_response_body, :headers => {})
snapshot_req = Pooler.snapshot(false, @vmpooler_url, 'fq6qlpjlsskycq6', 'mytokenfile')
expect(snapshot_req['ok']).to be true
expect(snapshot_req["ok"]).to be true
end
end
describe '#revert' do
describe "#revert" do
before :each do
@revert_response_body = '{"ok":true}'
@revert_response_body = "{\"ok\":true}"
end
it 'makes a request to revert a vm from a snapshot' do
stub_request(:post, "#{@vmpooler_url}/vm/fq6qlpjlsskycq6/snapshot/dAfewKNfaweLKNve")
.with(headers: { 'X-Auth-Token' => 'mytokenfile' })
.to_return(status: 200, body: @revert_response_body, headers: {})
it "makes a request to revert a vm from a snapshot" do
stub_request(:post, "#{@vmpooler_url}/vm/fq6qlpjlsskycq6/snapshot/dAfewKNfaweLKNve").
with(:headers => {'Accept'=>'*/*', 'Accept-Encoding'=>'gzip;q=1.0,deflate;q=0.6,identity;q=0.3', 'Content-Length'=>'0', 'User-Agent'=>'Faraday v0.9.2', 'X-Auth-Token'=>'mytokenfile'}).
to_return(:status => 200, :body => @revert_response_body, :headers => {})
revert_req = Pooler.revert(false, @vmpooler_url, 'fq6qlpjlsskycq6', 'mytokenfile', 'dAfewKNfaweLKNve')
expect(revert_req['ok']).to be true
expect(revert_req["ok"]).to be true
end
it "doesn't make a request to revert a vm if snapshot is not provided" do
expect do
Pooler.revert(false, @vmpooler_url, 'fq6qlpjlsskycq6', 'mytokenfile',
nil)
end.to raise_error(RuntimeError, 'Snapshot SHA provided was nil, could not revert fq6qlpjlsskycq6')
expect{ Pooler.revert(false, @vmpooler_url, 'fq6qlpjlsskycq6', 'mytokenfile', nil) }.to raise_error(RuntimeError, "Snapshot SHA provided was nil, could not revert fq6qlpjlsskycq6")
end
it 'raises a TokenError if no token was provided' do
expect { Pooler.revert(false, @vmpooler_url, 'myfakehost', nil, 'shaaaaaaa') }.to raise_error(TokenError)
it "raises a TokenError if no token was provided" do
expect{ Pooler.revert(false, @vmpooler_url, 'myfakehost', nil, 'shaaaaaaa') }.to raise_error(TokenError)
end
end
describe '#disk' do
describe "#disk" do
before :each do
@disk_response_body_success = '{"ok":true}'
@disk_response_body_fail = '{"ok":false}'
@disk_response_body_success = "{\"ok\":true}"
@disk_response_body_fail = "{\"ok\":false}"
end
it 'makes a request to extend disk space of a vm' do
stub_request(:post, "#{@vmpooler_url}/vm/fq6qlpjlsskycq6/disk/12")
.with(headers: { 'X-Auth-Token' => 'mytokenfile' }).to_return(status: 200, body: @disk_response_body_success, headers: {})
it "makes a request to extend disk space of a vm" do
stub_request(:post, "#{@vmpooler_url}/vm/fq6qlpjlsskycq6/disk/12").
with(:headers => {'Accept'=>'*/*', 'Accept-Encoding'=>'gzip;q=1.0,deflate;q=0.6,identity;q=0.3', 'Content-Length'=>'0', 'User-Agent'=>'Faraday v0.9.2', 'X-Auth-Token'=>'mytokenfile'}). to_return(:status => 200, :body => @disk_response_body_success, :headers => {})
disk_req = Pooler.disk(false, @vmpooler_url, 'fq6qlpjlsskycq6', 'mytokenfile', 12)
expect(disk_req['ok']).to be true
expect(disk_req["ok"]).to be true
end
it 'raises a TokenError if no token was provided' do
expect { Pooler.disk(false, @vmpooler_url, 'myfakehost', nil, 12) }.to raise_error(TokenError)
it "raises a TokenError if no token was provided" do
expect{ Pooler.disk(false, @vmpooler_url, 'myfakehost', nil, 12) }.to raise_error(TokenError)
end
end
end

View file

@ -1,12 +1,11 @@
# frozen_string_literal: true
require_relative '../../lib/vmfloaty/service'
describe Service do
describe '#initialize' do
it 'store configuration options' do
options = MockOptions.new({})
config = { 'url' => 'http://example.url' }
config = {'url' => 'http://example.url'}
service = Service.new(options, config)
expect(service.config).to include config
end
@ -16,9 +15,9 @@ describe Service do
it 'prompts the user for their password and retrieves a token' do
config = { 'user' => 'first.last', 'url' => 'http://default.url' }
service = Service.new(MockOptions.new, config)
allow($stdout).to receive(:puts).with('Enter your http://default.url service password:')
allow(STDOUT).to receive(:puts).with('Enter your pooler service password:')
allow(Commander::UI).to(receive(:password)
.with('Enter your http://default.url service password:', '*')
.with('Enter your pooler service password:', '*')
.and_return('hunter2'))
allow(Auth).to(receive(:get_token)
.with(nil, config['url'], config['user'], 'hunter2')
@ -29,11 +28,11 @@ describe Service do
it 'prompts the user for their username and password if the username is unknown' do
config = { 'url' => 'http://default.url' }
service = Service.new(MockOptions.new({}), config)
allow($stdout).to receive(:puts).with 'Enter your http://default.url service username:'
allow($stdout).to receive(:puts).with "\n"
allow($stdin).to receive(:gets).and_return('first.last')
allow(STDOUT).to receive(:puts).with 'Enter your pooler service username:'
allow(STDOUT).to receive(:puts).with "\n"
allow(STDIN).to receive(:gets).and_return('first.last')
allow(Commander::UI).to(receive(:password)
.with('Enter your http://default.url service password:', '*')
.with('Enter your pooler service password:', '*')
.and_return('hunter2'))
allow(Auth).to(receive(:get_token)
.with(nil, config['url'], 'first.last', 'hunter2')
@ -44,31 +43,31 @@ describe Service do
describe '#delete_token' do
it 'deletes a token' do
service = Service.new(MockOptions.new, 'user' => 'first.last', 'url' => 'http://default.url')
service = Service.new(MockOptions.new,{'user' => 'first.last', 'url' => 'http://default.url'})
allow(Commander::UI).to(receive(:password)
.with('Enter your http://default.url service password:', '*')
.with('Enter your pooler service password:', '*')
.and_return('hunter2'))
allow(Auth).to(receive(:delete_token)
.with(nil, 'http://default.url', 'first.last', 'hunter2', 'token-value')
.and_return('ok' => true))
expect(service.delete_token(nil, 'token-value')).to eql('ok' => true)
expect(service.delete_token(nil, 'token-value')).to eql({'ok' => true})
end
end
describe '#token_status' do
it 'reports the status of a token' do
config = {
'user' => 'first.last',
'url' => 'http://default.url'
'user' => 'first.last',
'url' => 'http://default.url'
}
options = MockOptions.new('token' => 'token-value')
service = Service.new(options, config)
status = {
'ok' => true,
'user' => config['user'],
'created' => '2017-09-22 02:04:18 +0000',
'last_accessed' => '2017-09-22 02:04:28 +0000',
'reserved_hosts' => []
'ok' => true,
'user' => config['user'],
'created' => '2017-09-22 02:04:18 +0000',
'last_accessed' => '2017-09-22 02:04:28 +0000',
'reserved_hosts' => []
}
allow(Auth).to(receive(:token_status)
.with(nil, config['url'], 'token-value')
@ -76,4 +75,5 @@ describe Service do
expect(service.token_status(nil, 'token-value')).to eql(status)
end
end
end

View file

@ -1,112 +0,0 @@
# frozen_string_literal: true
require 'spec_helper'
require 'vmfloaty/ssh'
class ServiceStub
def retrieve(_verbose, os_types, _use_token, ondemand)
if os_types.keys[0] == 'abs_host_string'
return {
os_types.keys[0] => { 'hostname' => ['abs-hostname.delivery.puppetlabs.net'] },
'ok' => true
}
elsif os_types.keys[0] == 'vmpooler_api_v2_host_string'
return {
os_types.keys[0] => { 'hostname' => ['vmpooler-v2-hostname.delivery.puppetlabs.net'] },
'ok' => true
}
else
return {
os_types.keys[0] => { 'hostname' => 'vmpooler-v1-hostname' },
'domain' => 'delivery.puppetlabs.net',
'ok' => true
}
end
end
def type
return 'abs' if os_types == 'abs_host_string'
return 'vmpooler' if os_types == 'vmpooler_api_v1_host_string' || os_types == 'vmpooler_api_v2_host_string'
end
def wait_for_request(verbose, requestid)
return true
end
end
describe Ssh do
before :each do
end
context "for pooled requests" do
it 'gets a hostname string for abs' do
verbose = false
service = ServiceStub.new
host_os = 'abs_host_string'
use_token = false
cmd = Ssh.command_string(verbose, service, host_os, use_token)
expect(cmd).to match(/ssh root@abs-hostname.delivery.puppetlabs.net/)
end
it 'gets a hostname string for vmpooler api v1' do
verbose = true
service = ServiceStub.new
host_os = 'vmpooler_api_v1_host_string'
use_token = false
cmd = Ssh.command_string(verbose, service, host_os, use_token)
expect(cmd).to match(/ssh root@vmpooler-v1-hostname.delivery.puppetlabs.net/)
end
it 'gets a hostname string for vmpooler api v2' do
verbose = false
service = ServiceStub.new
host_os = 'vmpooler_api_v2_host_string'
use_token = false
cmd = Ssh.command_string(verbose, service, host_os, use_token)
expect(cmd).to match(/ssh root@vmpooler-v2-hostname.delivery.puppetlabs.net/)
end
end
context "for ondemand requests" do
let(:service) { ServiceStub.new }
let(:url) { 'http://pooler.example.com' }
it 'gets a hostname string for abs' do
verbose = false
host_os = 'abs_host_string'
use_token = false
ondemand = true
response = {'abs_host_string' => { 'hostname' => ['abs-hostname.delivery.puppetlabs.net']}}
allow(service).to receive(:url)
allow(service).to receive(:check_ondemandvm).and_return(response)
cmd = Ssh.command_string(verbose, service, host_os, use_token, ondemand)
expect(cmd).to match(/ssh root@abs-hostname.delivery.puppetlabs.net/)
end
it 'gets a hostname string for abs' do
verbose = false
host_os = 'vmpooler_api_v1_host_string'
use_token = false
ondemand = true
response = {'vmpooler_api_v1_host_string' => { 'hostname' => ['vmpooler_api_v1_host_string.delivery.puppetlabs.net']}}
allow(service).to receive(:url)
allow(service).to receive(:check_ondemandvm).and_return(response)
cmd = Ssh.command_string(verbose, service, host_os, use_token, ondemand)
expect(cmd).to match(/ssh root@vmpooler_api_v1_host_string.delivery.puppetlabs.net/)
end
it 'gets a hostname string for abs' do
verbose = false
host_os = 'vmpooler_api_v2_host_string'
use_token = false
ondemand = true
response = {'vmpooler_api_v2_host_string' => { 'hostname' => ['vmpooler_api_v2_host_string.delivery.puppetlabs.net']}}
allow(service).to receive(:url)
allow(service).to receive(:check_ondemandvm).and_return(response)
cmd = Ssh.command_string(verbose, service, host_os, use_token, ondemand)
expect(cmd).to match(/ssh root@vmpooler_api_v2_host_string.delivery.puppetlabs.net/)
end
end
end

View file

@ -1,19 +1,13 @@
# frozen_string_literal: true
require 'spec_helper'
require 'json'
require 'commander/command'
require_relative '../../lib/vmfloaty/utils'
# allow changing config in service for tests
class Service
attr_writer :config
end
describe Utils do
describe '#standardize_hostnames' do
describe "#standardize_hostnames" do
before :each do
@vmpooler_api_v1_response_body = '{
@vmpooler_response_body ='{
"ok": true,
"domain": "delivery.mycompany.net",
"ubuntu-1610-x86_64": {
@ -23,15 +17,6 @@ describe Utils do
"hostname": "dlgietfmgeegry2"
}
}'
@vmpooler_api_v2_response_body = '{
"ok": true,
"ubuntu-1610-x86_64": {
"hostname": ["gdoy8q3nckuob0i.delivery.mycompany.net", "ctnktsd0u11p9tm.delivery.mycompany.net"]
},
"centos-7-x86_64": {
"hostname": "dlgietfmgeegry2.delivery.mycompany.net"
}
}'
@nonstandard_response_body = '{
"ok": true,
"solaris-10-sparc": {
@ -43,95 +28,79 @@ describe Utils do
}'
end
it 'formats a result from vmpooler v1 api into a hash of os to hostnames' do
result = Utils.standardize_hostnames(JSON.parse(@vmpooler_api_v1_response_body))
expect(result).to eq('centos-7-x86_64' => ['dlgietfmgeegry2.delivery.mycompany.net'],
'ubuntu-1610-x86_64' => ['gdoy8q3nckuob0i.delivery.mycompany.net',
'ctnktsd0u11p9tm.delivery.mycompany.net'])
it "formats a result from vmpooler into a hash of os to hostnames" do
result = Utils.standardize_hostnames(JSON.parse(@vmpooler_response_body))
expect(result).to eq('centos-7-x86_64' => ["dlgietfmgeegry2.delivery.mycompany.net"],
'ubuntu-1610-x86_64' => ["gdoy8q3nckuob0i.delivery.mycompany.net", "ctnktsd0u11p9tm.delivery.mycompany.net"])
end
it 'formats a result from vmpooler v2 api into a hash of os to hostnames' do
result = Utils.standardize_hostnames(JSON.parse(@vmpooler_api_v2_response_body))
expect(result).to eq('centos-7-x86_64' => ['dlgietfmgeegry2.delivery.mycompany.net'],
'ubuntu-1610-x86_64' => ['gdoy8q3nckuob0i.delivery.mycompany.net',
'ctnktsd0u11p9tm.delivery.mycompany.net'])
end
it 'formats a result from the nonstandard pooler into a hash of os to hostnames' do
it "formats a result from the nonstandard pooler into a hash of os to hostnames" do
result = Utils.standardize_hostnames(JSON.parse(@nonstandard_response_body))
expect(result).to eq('solaris-10-sparc' => ['sol10-10.delivery.mycompany.net', 'sol10-11.delivery.mycompany.net'],
'ubuntu-16.04-power8' => ['power8-ubuntu16.04-6.delivery.mycompany.net'])
end
end
describe '#format_host_output' do
describe "#format_host_output" do
before :each do
@vmpooler_results = {
'centos-7-x86_64' => ['dlgietfmgeegry2.delivery.mycompany.net'],
'ubuntu-1610-x86_64' => ['gdoy8q3nckuob0i.delivery.mycompany.net', 'ctnktsd0u11p9tm.delivery.mycompany.net']
'centos-7-x86_64' => ["dlgietfmgeegry2.delivery.mycompany.net"],
'ubuntu-1610-x86_64' => ["gdoy8q3nckuob0i.delivery.mycompany.net", "ctnktsd0u11p9tm.delivery.mycompany.net"]
}
@nonstandard_results = {
'solaris-10-sparc' => ['sol10-10.delivery.mycompany.net', 'sol10-11.delivery.mycompany.net'],
'ubuntu-16.04-power8' => ['power8-ubuntu16.04-6.delivery.mycompany.net']
}
@vmpooler_output = <<~OUT.chomp
- dlgietfmgeegry2.delivery.mycompany.net (centos-7-x86_64)
- gdoy8q3nckuob0i.delivery.mycompany.net (ubuntu-1610-x86_64)
- ctnktsd0u11p9tm.delivery.mycompany.net (ubuntu-1610-x86_64)
@vmpooler_output = <<-OUT.chomp
- dlgietfmgeegry2.delivery.mycompany.net (centos-7-x86_64)
- gdoy8q3nckuob0i.delivery.mycompany.net (ubuntu-1610-x86_64)
- ctnktsd0u11p9tm.delivery.mycompany.net (ubuntu-1610-x86_64)
OUT
@nonstandard_output = <<~OUT.chomp
- sol10-10.delivery.mycompany.net (solaris-10-sparc)
- sol10-11.delivery.mycompany.net (solaris-10-sparc)
- power8-ubuntu16.04-6.delivery.mycompany.net (ubuntu-16.04-power8)
@nonstandard_output = <<-OUT.chomp
- sol10-10.delivery.mycompany.net (solaris-10-sparc)
- sol10-11.delivery.mycompany.net (solaris-10-sparc)
- power8-ubuntu16.04-6.delivery.mycompany.net (ubuntu-16.04-power8)
OUT
end
it 'formats a hostname hash from vmpooler into a list that includes the os' do
it "formats a hostname hash from vmpooler into a list that includes the os" do
expect(Utils.format_host_output(@vmpooler_results)).to eq(@vmpooler_output)
end
it 'formats a hostname hash from the nonstandard pooler into a list that includes the os' do
it "formats a hostname hash from the nonstandard pooler into a list that includes the os" do
expect(Utils.format_host_output(@nonstandard_results)).to eq(@nonstandard_output)
end
end
describe '#get_service_object' do
it 'assumes vmpooler by default' do
describe "#get_service_object" do
it "assumes vmpooler by default" do
expect(Utils.get_service_object).to be Pooler
end
it 'uses abs when told explicitly' do
expect(Utils.get_service_object('abs')).to be ABS
end
it 'uses nspooler when told explicitly' do
expect(Utils.get_service_object('nspooler')).to be NonstandardPooler
end
it 'uses vmpooler when told explicitly' do
expect(Utils.get_service_object('vmpooler')).to be Pooler
it "uses nspooler when told explicitly" do
expect(Utils.get_service_object "nspooler").to be NonstandardPooler
end
end
describe '#get_service_config' do
describe "#get_service_config" do
before :each do
@default_config = {
'url' => 'http://default.url',
'user' => 'first.last.default',
'token' => 'default-token'
"url" => "http://default.url",
"user" => "first.last.default",
"token" => "default-token",
}
@services_config = {
'services' => {
'vm' => {
'url' => 'http://vmpooler.url',
'user' => 'first.last.vmpooler',
'token' => 'vmpooler-token'
},
'ns' => {
'url' => 'http://nspooler.url',
'user' => 'first.last.nspooler',
'token' => 'nspooler-token'
"services" => {
"vm" => {
"url" => "http://vmpooler.url",
"user" => "first.last.vmpooler",
"token" => "vmpooler-token"
},
"ns" => {
"url" => "http://nspooler.url",
"user" => "first.last.nspooler",
"token" => "nspooler-token"
}
}
}
}
end
@ -141,587 +110,140 @@ describe Utils do
expect(Utils.get_service_config(config, options)).to include @services_config['services']['vm']
end
it 'allows selection by configured service key' do
it "allows selection by configured service key" do
config = @default_config.merge @services_config
options = MockOptions.new(service: 'ns')
options = MockOptions.new({:service => "ns"})
expect(Utils.get_service_config(config, options)).to include @services_config['services']['ns']
end
it 'uses top-level service config values as defaults when configured service values are missing' do
it "uses top-level service config values as defaults when configured service values are missing" do
config = @default_config.merge @services_config
config['services']['vm'].delete 'url'
options = MockOptions.new(service: 'vm')
config["services"]['vm'].delete 'url'
options = MockOptions.new({:service => "vm"})
expect(Utils.get_service_config(config, options)['url']).to eq 'http://default.url'
end
it "raises an error if passed a service name that hasn't been configured" do
config = @default_config.merge @services_config
options = MockOptions.new(service: 'none')
options = MockOptions.new({:service => "none"})
expect { Utils.get_service_config(config, options) }.to raise_error ArgumentError
end
it 'prioritizes values passed as command line options over configuration options' do
it "prioritizes values passed as command line options over configuration options" do
config = @default_config
options = MockOptions.new(url: 'http://alternate.url', token: 'alternate-token')
expected = config.merge('url' => 'http://alternate.url', 'token' => 'alternate-token')
options = MockOptions.new({:url => "http://alternate.url", :token => "alternate-token"})
expected = config.merge({"url" => "http://alternate.url", "token" => "alternate-token"})
expect(Utils.get_service_config(config, options)).to include expected
end
end
describe '#generate_os_hash' do
describe "#generate_os_hash" do
before :each do
@host_hash = { 'centos' => 1, 'debian' => 5, 'windows' => 1 }
@host_hash = {"centos"=>1, "debian"=>5, "windows"=>1}
end
it 'takes an array of os arguments and returns a formatted hash' do
host_arg = ['centos', 'debian=5', 'windows=1']
it "takes an array of os arguments and returns a formatted hash" do
host_arg = ["centos", "debian=5", "windows=1"]
expect(Utils.generate_os_hash(host_arg)).to eq @host_hash
end
it 'returns an empty hash if there are no arguments provided' do
it "returns an empty hash if there are no arguments provided" do
host_arg = []
expect(Utils.generate_os_hash(host_arg)).to be_empty
end
end
describe '#print_fqdn_for_host' do
let(:url) { 'http://pooler.example.com' }
describe '#pretty_print_hosts' do
let(:url) { 'http://pooler.example.com' }
subject { Utils.print_fqdn_for_host(service, hostname, host_data) }
describe 'with vmpooler host' do
let(:service) { Service.new(MockOptions.new, 'url' => url) }
let(:hostname) { 'mcpy42eqjxli9g2' }
let(:domain) { 'delivery.mycompany.net' }
let(:fqdn) { [hostname, domain].join('.') }
let(:host_data) do
{
it 'prints a vmpooler output with host fqdn, template and duration info' do
hostname = 'mcpy42eqjxli9g2'
response_body = { hostname => {
'template' => 'ubuntu-1604-x86_64',
'lifetime' => 12,
'running' => 9.66,
'running' => 9.66,
'state' => 'running',
'ip' => '127.0.0.1',
'domain' => domain
}
end
'domain' => 'delivery.mycompany.net'
}}
output = "- mcpy42eqjxli9g2.delivery.mycompany.net (ubuntu-1604-x86_64, 9.66/12 hours)"
it 'outputs fqdn for host' do
expect($stdout).to receive(:puts).with(fqdn)
expect(Utils).to receive(:puts).with(output)
subject
end
service = Service.new(MockOptions.new, {'url' => url})
allow(service).to receive(:query)
.with(nil, hostname)
.and_return(response_body)
Utils.pretty_print_hosts(nil, service, hostname)
end
describe 'with nonstandard pooler host' do
let(:service) { Service.new(MockOptions.new, 'url' => url, 'type' => 'ns') }
let(:hostname) { 'sol11-9.delivery.mycompany.net' }
let(:host_data) do
{
it 'prints a vmpooler output with host fqdn, template, duration info, and tags when supplied' do
hostname = 'aiydvzpg23r415q'
response_body = { hostname => {
'template' => 'redhat-7-x86_64',
'lifetime' => 48,
'running' => 7.67,
'state' => 'running',
'tags' => {
'user' => 'bob',
'role' => 'agent'
},
'ip' => '127.0.0.1',
'domain' => 'delivery.mycompany.net'
}}
output = "- aiydvzpg23r415q.delivery.mycompany.net (redhat-7-x86_64, 7.67/48 hours, user: bob, role: agent)"
expect(Utils).to receive(:puts).with(output)
service = Service.new(MockOptions.new, {'url' => url})
allow(service).to receive(:query)
.with(nil, hostname)
.and_return(response_body)
Utils.pretty_print_hosts(nil, service, hostname)
end
it 'prints a nonstandard pooler output with host, template, and time remaining' do
hostname = "sol11-9.delivery.mycompany.net"
response_body = { hostname => {
'fqdn' => hostname,
'os_triple' => 'solaris-11-sparc',
'reserved_by_user' => 'first.last',
'reserved_for_reason' => '',
'hours_left_on_reservation' => 35.89
}
end
let(:fqdn) { hostname } # for nspooler these are the same
}}
output = "- sol11-9.delivery.mycompany.net (solaris-11-sparc, 35.89h remaining)"
it 'outputs fqdn for host' do
expect($stdout).to receive(:puts).with(fqdn)
expect(Utils).to receive(:puts).with(output)
subject
end
end
describe 'with ABS host' do
let(:service) { Service.new(MockOptions.new, 'url' => url, 'type' => 'abs') }
let(:hostname) { '1597952189390' }
let(:fqdn) { 'example-noun.delivery.puppetlabs.net' }
let(:template) { 'ubuntu-1604-x86_64' }
# This seems to be the miminal stub response from ABS for the current output
let(:host_data) do
{
'state' => 'allocated',
'allocated_resources' => [
{
'hostname' => fqdn,
'type' => template,
'enging' => 'vmpooler'
}
],
'request' => {
'job' => {
'id' => hostname
}
}
}
end
it 'outputs fqdn for host' do
expect($stdout).to receive(:puts).with(fqdn)
subject
end
end
end
describe '#pretty_print_hosts' do
let(:url) { 'http://pooler.example.com' }
let(:verbose) { nil }
let(:print_to_stderr) { false }
before(:each) do
service = Service.new(MockOptions.new, {'url' => url, 'type' => 'ns'})
allow(service).to receive(:query)
.with(anything, hostname)
.and_return(response_body)
.with(nil, hostname)
.and_return(response_body)
Utils.pretty_print_hosts(nil, service, hostname)
end
subject { Utils.pretty_print_hosts(verbose, service, hostname, print_to_stderr) }
describe 'with vmpooler api v2 service' do
let(:service) { Service.new(MockOptions.new, 'url' => url) }
let(:hostname) { 'mcpy42eqjxli9g2' }
let(:fqdn) { [hostname, 'delivery.puppetlabs.net'].join('.') }
let(:response_body) do
{
hostname => {
'template' => 'ubuntu-1604-x86_64',
'lifetime' => 12,
'running' => 9.66,
'state' => 'running',
'ip' => '127.0.0.1',
'fqdn' => fqdn
}
}
end
let(:default_output) { "- #{fqdn} (running, ubuntu-1604-x86_64, 9.66/12 hours)" }
it 'prints output with host fqdn, template and duration info' do
expect($stdout).to receive(:puts).with(default_output)
subject
end
context 'when tags are supplied' do
let(:hostname) { 'aiydvzpg23r415q' }
let(:response_body) do
{
hostname => {
'template' => 'redhat-7-x86_64',
'lifetime' => 48,
'running' => 7.67,
'state' => 'running',
'tags' => {
'user' => 'bob',
'role' => 'agent'
},
'ip' => '127.0.0.1',
'fqdn' => fqdn
}
}
end
it 'prints output with host fqdn, template, duration info, and tags' do
output = "- #{fqdn} (running, redhat-7-x86_64, 7.67/48 hours, user: bob, role: agent)"
expect($stdout).to receive(:puts).with(output)
subject
end
end
context 'when print_to_stderr option is true' do
let(:print_to_stderr) { true }
it 'outputs to stderr instead of stdout' do
expect($stderr).to receive(:puts).with(default_output)
subject
end
end
end
describe 'with vmpooler api v1 service' do
let(:service) { Service.new(MockOptions.new, 'url' => url) }
let(:hostname) { 'mcpy42eqjxli9g2' }
let(:domain) { 'delivery.mycompany.net' }
let(:fqdn) { [hostname, domain].join('.') }
let(:response_body) do
{
hostname => {
'template' => 'ubuntu-1604-x86_64',
'lifetime' => 12,
'running' => 9.66,
'state' => 'running',
'ip' => '127.0.0.1',
'domain' => domain
}
}
end
let(:default_output) { "- #{fqdn} (running, ubuntu-1604-x86_64, 9.66/12 hours)" }
it 'prints output with host fqdn, template and duration info' do
expect($stdout).to receive(:puts).with(default_output)
subject
end
context 'when tags are supplied' do
let(:hostname) { 'aiydvzpg23r415q' }
let(:response_body) do
{
hostname => {
'template' => 'redhat-7-x86_64',
'lifetime' => 48,
'running' => 7.67,
'state' => 'running',
'tags' => {
'user' => 'bob',
'role' => 'agent'
},
'ip' => '127.0.0.1',
'domain' => domain
}
}
end
it 'prints output with host fqdn, template, duration info, and tags' do
output = "- #{fqdn} (running, redhat-7-x86_64, 7.67/48 hours, user: bob, role: agent)"
expect($stdout).to receive(:puts).with(output)
subject
end
end
context 'when print_to_stderr option is true' do
let(:print_to_stderr) { true }
it 'outputs to stderr instead of stdout' do
expect($stderr).to receive(:puts).with(default_output)
subject
end
end
end
describe 'with nonstandard pooler service' do
let(:service) { Service.new(MockOptions.new, 'url' => url, 'type' => 'ns') }
let(:hostname) { 'sol11-9.delivery.mycompany.net' }
let(:response_body) do
{
hostname => {
'fqdn' => hostname,
'os_triple' => 'solaris-11-sparc',
'reserved_by_user' => 'first.last',
'reserved_for_reason' => '',
'hours_left_on_reservation' => 35.89
}
}
end
let(:default_output) { "- #{hostname} (solaris-11-sparc, 35.89h remaining)" }
it 'prints output with host, template, and time remaining' do
expect($stdout).to receive(:puts).with(default_output)
subject
end
context 'when reason is supplied' do
let(:response_body) do
{
hostname => {
'fqdn' => hostname,
'os_triple' => 'solaris-11-sparc',
'reserved_by_user' => 'first.last',
'reserved_for_reason' => 'testing',
'hours_left_on_reservation' => 35.89
}
}
end
it 'prints output with host, template, time remaining, and reason' do
output = '- sol11-9.delivery.mycompany.net (solaris-11-sparc, 35.89h remaining, reason: testing)'
expect($stdout).to receive(:puts).with(output)
subject
end
end
context 'when print_to_stderr option is true' do
let(:print_to_stderr) { true }
it 'outputs to stderr instead of stdout' do
expect($stderr).to receive(:puts).with(default_output)
subject
end
end
end
describe 'with ABS service' do
let(:service) { Service.new(MockOptions.new, 'url' => url, 'type' => 'abs') }
let(:hostname) { '1597952189390' }
let(:fqdn) { 'example-noun.delivery.mycompany.net' }
let(:fqdn_hostname) { 'example-noun' }
let(:template) { 'ubuntu-1604-x86_64' }
# This seems to be the miminal stub response from ABS for the current output
let(:response_body) do
{
hostname => {
'state' => 'allocated',
'allocated_resources' => [
{
'hostname' => fqdn,
'type' => template,
'engine' => 'vmpooler'
}
],
'request' => {
'job' => {
'id' => hostname
}
}
}
}
end
# The vmpooler response contains metadata that is printed
let(:domain) { 'delivery.mycompany.net' }
let(:response_body_vmpooler) do
{
fqdn_hostname => {
'template' => template,
'lifetime' => 48,
'running' => 7.67,
'state' => 'running',
'tags' => {
'user' => 'bob',
'role' => 'agent'
},
'ip' => '127.0.0.1',
'domain' => domain
}
}
end
before(:each) do
allow(Utils).to receive(:get_vmpooler_service_config).and_return({
'url' => 'http://vmpooler.example.com',
'token' => 'krypto-knight'
})
allow(service).to receive(:query)
.with(anything, fqdn_hostname)
.and_return(response_body_vmpooler)
end
let(:default_output_first_line) { "- [JobID:#{hostname}] <allocated>" }
let(:default_output_second_line) { " - #{fqdn} (#{template})" }
it 'prints output with job id, host, and template' do
expect($stdout).to receive(:puts).with(default_output_first_line)
expect($stdout).to receive(:puts).with(default_output_second_line)
subject
end
it 'prints more information when vmpooler_fallback is set output with job id, host, template, lifetime, user and role' do
fallback = { 'vmpooler_fallback' => 'vmpooler' }
service.config.merge! fallback
default_output_second_line = " - #{fqdn} (running, #{template}, 7.67/48 hours, user: bob, role: agent)"
expect($stdout).to receive(:puts).with(default_output_first_line)
expect($stdout).to receive(:puts).with(default_output_second_line)
subject
end
it 'prints DESTROYED and hostname when destroyed' do
fallback = { 'vmpooler_fallback' => 'vmpooler' }
service.config.merge! fallback
response_body_vmpooler[fqdn_hostname]['state'] = 'destroyed'
default_output_second_line = " - DESTROYED #{fqdn}"
expect($stdout).to receive(:puts).with(default_output_first_line)
expect($stdout).to receive(:puts).with(default_output_second_line)
subject
end
context 'when print_to_stderr option is true' do
let(:print_to_stderr) { true }
it 'outputs to stderr instead of stdout' do
expect($stderr).to receive(:puts).with(default_output_first_line)
expect($stderr).to receive(:puts).with(default_output_second_line)
subject
end
end
end
describe 'with ABS service returning vmpooler and nspooler resources' do
let(:service) { Service.new(MockOptions.new, 'url' => url, 'type' => 'abs') }
let(:hostname) { '1597952189390' }
let(:fqdn) { 'this-noun.delivery.mycompany.net' }
let(:fqdn_ns) { 'that-noun.delivery.mycompany.net' }
let(:fqdn_hostname) { 'this-noun' }
let(:fqdn_ns_hostname) { 'that-noun' }
let(:template) { 'ubuntu-1604-x86_64' }
let(:template_ns) { 'solaris-10-sparc' }
# This seems to be the miminal stub response from ABS for the current output
let(:response_body) do
{
hostname => {
'state' => 'allocated',
'allocated_resources' => [
{
'hostname' => fqdn,
'type' => template,
'engine' => 'vmpooler'
},
{
'hostname' => fqdn_ns,
'type' => template_ns,
'engine' => 'nspooler'
}
],
'request' => {
'job' => {
'id' => hostname
}
}
}
}
end
# The vmpooler response contains metadata that is printed
let(:domain) { 'delivery.mycompany.net' }
let(:response_body_vmpooler) do
{
fqdn_hostname => {
'template' => template,
'lifetime' => 48,
'running' => 7.67,
'state' => 'running',
'tags' => {
'user' => 'bob',
'role' => 'agent'
},
'ip' => '127.0.0.1',
'domain' => domain
}
}
end
before(:each) do
allow(Utils).to receive(:get_vmpooler_service_config).and_return({
'url' => 'http://vmpooler.example.com',
'token' => 'krypto-knight'
})
allow(service).to receive(:query)
.with(anything, fqdn_hostname)
.and_return(response_body_vmpooler)
end
let(:default_output_first_line) { "- [JobID:#{hostname}] <allocated>" }
let(:default_output_second_line) { " - #{fqdn} (#{template})" }
let(:default_output_third_line) { " - #{fqdn_ns} (#{template_ns})" }
it 'prints output with job id, host, and template' do
expect($stdout).to receive(:puts).with(default_output_first_line)
expect($stdout).to receive(:puts).with(default_output_second_line)
expect($stdout).to receive(:puts).with(default_output_third_line)
subject
end
context 'when print_to_stderr option is true' do
let(:print_to_stderr) { true }
it 'outputs to stderr instead of stdout' do
expect($stderr).to receive(:puts).with(default_output_first_line)
expect($stderr).to receive(:puts).with(default_output_second_line)
expect($stderr).to receive(:puts).with(default_output_third_line)
subject
end
end
end
end
describe '#get_vmpooler_service_config' do
let(:Conf) { double }
it 'returns an error if the vmpooler_fallback is not setup' do
config = {
'user' => 'foo',
'services' => {
'myabs' => {
'url' => 'http://abs.com',
'token' => 'krypto-night',
'type' => 'abs'
}
}
}
allow(Conf).to receive(:read_config).and_return(config)
expect do
Utils.get_vmpooler_service_config(config['services']['myabs']['vmpooler_fallback'])
end.to raise_error(ArgumentError)
end
it 'returns an error if the vmpooler_fallback is setup but cannot be found' do
config = {
'user' => 'foo',
'services' => {
'myabs' => {
'url' => 'http://abs.com',
'token' => 'krypto-night',
'type' => 'abs',
'vmpooler_fallback' => 'myvmpooler'
}
}
}
allow(Conf).to receive(:read_config).and_return(config)
expect do
Utils.get_vmpooler_service_config(config['services']['myabs']['vmpooler_fallback'])
end.to raise_error(ArgumentError,
/myvmpooler/)
end
it 'returns the vmpooler_fallback config' do
config = {
'user' => 'foo',
'services' => {
'myabs' => {
'url' => 'http://abs.com',
'token' => 'krypto-night',
'type' => 'abs',
'vmpooler_fallback' => 'myvmpooler'
},
'myvmpooler' => {
'url' => 'http://vmpooler.com',
'token' => 'krypto-knight'
}
}
}
allow(Conf).to receive(:read_config).and_return(config)
expect(Utils.get_vmpooler_service_config(config['services']['myabs']['vmpooler_fallback'])).to include({
'url' => 'http://vmpooler.com',
'token' => 'krypto-knight',
'user' => 'foo',
'type' => 'vmpooler'
})
it 'prints a nonstandard pooler output with host, template, time remaining, and reason' do
hostname = 'sol11-9.delivery.mycompany.net'
response_body = { hostname => {
'fqdn' => hostname,
'os_triple' => 'solaris-11-sparc',
'reserved_by_user' => 'first.last',
'reserved_for_reason' => 'testing',
'hours_left_on_reservation' => 35.89
}}
output = "- sol11-9.delivery.mycompany.net (solaris-11-sparc, 35.89h remaining, reason: testing)"
expect(Utils).to receive(:puts).with(output)
service = Service.new(MockOptions.new, {'url' => url, 'type' => 'ns'})
allow(service).to receive(:query)
.with(nil, hostname)
.and_return(response_body)
Utils.pretty_print_hosts(nil, service, hostname)
end
end
end

View file

@ -1,39 +0,0 @@
# frozen_string_literal: true
# All of the interfaces for the different services must be the
# same, otherwise there will be errors when you change the caller
# for the services from services.rb.
#
require_relative '../../lib/vmfloaty/pooler'
require_relative '../../lib/vmfloaty/abs'
require_relative '../../lib/vmfloaty/nonstandard_pooler'
shared_examples 'a vmfloaty service' do
it { is_expected.to respond_to(:delete).with(5).arguments }
it { is_expected.to respond_to(:disk).with(5).arguments }
it { is_expected.to respond_to(:list).with(3).arguments }
it { is_expected.to respond_to(:list_active).with(4).arguments }
it { is_expected.to respond_to(:modify).with(5).arguments }
it { is_expected.to respond_to(:retrieve).with(6).arguments }
it { is_expected.to respond_to(:revert).with(5).arguments }
it { is_expected.to respond_to(:query).with(3).arguments }
it { is_expected.to respond_to(:snapshot).with(4).arguments }
it { is_expected.to respond_to(:status).with(2).arguments }
it { is_expected.to respond_to(:summary).with(2).arguments }
end
describe Pooler do
subject { Pooler }
it_behaves_like 'a vmfloaty service'
end
describe ABS do
subject { ABS }
it_behaves_like 'a vmfloaty service'
end
describe NonstandardPooler do
subject { NonstandardPooler }
it_behaves_like 'a vmfloaty service'
end

View file

@ -1,18 +1,13 @@
# frozen_string_literal: true
$LOAD_PATH.push File.expand_path('lib', __dir__)
$LOAD_PATH.push File.expand_path('../lib', __FILE__)
require 'vmfloaty/version'
Gem::Specification.new do |s|
s.name = 'vmfloaty'
s.version = Vmfloaty::VERSION
s.authors = [
'Brian Cain',
'Puppet'
]
s.email = 'info@puppet.com'
s.authors = ['Brian Cain']
s.email = ['brianccain@gmail.com']
s.license = 'Apache-2.0'
s.homepage = 'https://github.com/puppetlabs/vmfloaty'
s.homepage = 'https://github.com/briancain/vmfloaty'
s.description = 'A helper tool for vmpooler to help you stay afloat'
s.summary = 'CLI application to interface with vmpooler'
@ -21,6 +16,7 @@ Gem::Specification.new do |s|
s.test_files = Dir['spec/**/*']
s.require_path = 'lib'
s.add_dependency 'commander', '>= 4.4.3', '< 4.7.0'
s.add_dependency 'faraday', '~> 1.5', '>= 1.5.1'
s.add_dependency 'commander', '~> 4.4.3'
s.add_dependency 'faraday', '~> 0.9.0'
s.add_dependency 'colorize', '~> 0.8.1'
end

View file

@ -1,8 +1,8 @@
user: 'brian'
services:
main:
url: 'https://vmpooler.example.net/api/v1'
url: 'https://vmpooler.mycompany.net/api/v1'
token: 'tokenstring'
alternate:
url: 'https://vmpooler.example.com/api/v1'
url: 'https://vmpooler.alternate.net/api/v1'
token: 'alternate-tokenstring'