├── .travis.yml ├── Changelog.md ├── LICENSE ├── Readme.md ├── bin └── bork ├── docs ├── assertion_status_codes.markdown └── howto_write_an_assertion_type.markdown ├── lib ├── declarations │ ├── changes.sh │ ├── destination.sh │ ├── include.sh │ ├── ok.sh │ └── register.sh ├── helpers │ ├── arguments.sh │ ├── bag.sh │ ├── bake.sh │ ├── http.sh │ ├── md5cmd.sh │ ├── operations.sh │ ├── permission_cmd.sh │ ├── status_codes.sh │ ├── system.sh │ └── text.sh └── support │ └── compile.sh ├── test ├── declare-change.bats ├── declare-include.bats ├── declare-ok.bats ├── declare-register.bats ├── fixtures │ ├── apt-dpkg-dependencies.txt │ ├── apt-upgrade-dry.txt │ ├── brew-list.txt │ ├── brew-outdated.txt │ ├── brew-tap-list.txt │ ├── brew-tap-pinned.txt │ ├── cask-list.txt │ ├── cask-outdated-info.txt │ ├── custom.sh │ ├── defaults-dictionary-value.txt │ ├── gem-list.txt │ ├── http-head-curl.txt │ ├── include-one.sh │ ├── include-two.sh │ ├── mas-list.txt │ ├── mas-outdated.txt │ ├── npm-list.txt │ ├── npm-outdated.txt │ ├── pip-list.txt │ ├── pipsi-list.txt │ ├── rpm-qa.txt │ ├── user-list.txt │ ├── yum-list-updates.txt │ └── zypper-list-updates.txt ├── fn-compiler.sh ├── help-arguments.bats ├── help-bag.bats ├── help-http.bats ├── help-md5.bats ├── help-system.bats ├── help-text.bats ├── helpers.sh ├── type-apt.bats ├── type-brew-tap.bats ├── type-brew.bats ├── type-cask.bats ├── type-defaults.bats ├── type-directories.bats ├── type-directory-perms.bats ├── type-download.bats ├── type-file.bats ├── type-gem.bats ├── type-git.bats ├── type-github.bats ├── type-go-get.bats ├── type-group.bats ├── type-iptables.bats ├── type-mas.bats ├── type-npm.bats ├── type-pip.bats ├── type-pipsi.bats ├── type-symlink.bats ├── type-user.bats ├── type-yum.bats └── type-zypper.bats └── types ├── apm.sh ├── apt.sh ├── brew-tap.sh ├── brew.sh ├── cask.sh ├── check.sh ├── defaults.sh ├── directory.sh ├── download.sh ├── file.sh ├── gem.sh ├── git.sh ├── github.sh ├── go-get.sh ├── group.sh ├── iptables.sh ├── mas.sh ├── npm.sh ├── pip.sh ├── pipsi.sh ├── scutil.sh ├── symlink.sh ├── user.sh ├── yum.sh └── zypper.sh /.travis.yml: -------------------------------------------------------------------------------- 1 | # ugly hack to get bash working 2 | language: c 3 | before_install: 4 | - wget https://github.com/sstephenson/bats/archive/v0.3.1.tar.gz -O /tmp/bats.tar.gz 5 | - tar -xvf /tmp/bats.tar.gz 6 | - export PATH=$PATH:$PWD/bats-0.3.1/bin/ 7 | - bash --version 8 | script: bats --tap test 9 | notifications: 10 | email: 11 | on_success: change 12 | on_failure: change 13 | -------------------------------------------------------------------------------- /Changelog.md: -------------------------------------------------------------------------------- 1 | # Change Log 2 | All notable changes to this project will be documented in this file, from 2016-03-24 going forward. This project adheres to [Semantic Versioning](http://semver.org/). 3 | 4 | ## [0.11.1] - 2018-01-28 5 | 6 | ### Deprecated 7 | - The `--size` option in the `download` type is going away. It will be replaced with some sort of hash checking option. 8 | 9 | ### Fixed 10 | - the `ok` statement now single-quotes arguments to the type handler. This is the first step in a more consistent and correct behavior for quoting things in Bork. [@martinwalsh] 11 | - The github type once again works correctly in compiled scripts. [@mattly][] 12 | - There is no longer a `bin/bork_compile` script for someone to try to run when they shouldn't. [@mattly][] 13 | - Homebrew doesn't treat tap names as case-sensitive, and now neither does bork. [@mattly][] 14 | - Clarified some documentation around the update behavior for the brew-tap type [@mattly][] 15 | 16 | ## [0.11.0] - 2018-01-27 17 | 18 | Hey folks, sorry it's been a while! I started a new job not long after 0.10.0 was relased and then had my first child not long after that. I'm finally feeling a bit like I have some spare time. -- [@mattly][] 19 | 20 | ### Added 21 | - new `--owner`, `--group`, and `--mode` flags for the `directory` type that do what you think they do. Thanks [@jitakirin][] 22 | - `zypper` type for working with the SUSE package manager. Thanks [@jitakirin][] 23 | - `pipsi` type for installing python packages to virtualenvs. Thanks [@jitakirin][] 24 | - Reference to `#bork` freenode IRC channel in Readme. 25 | - `go-get` type for asserting the presence of a go package. Thanks [@edrex][] 26 | - Use `apm-beta` over `apm` if it is available. Thanks [@frdmn][] 27 | 28 | ### Improved 29 | - Let homebrew itself tell us whether it is outdated. Thanks [@frdmn][] 30 | 31 | ### Fixed 32 | - Use `npm install` to update npm packages, because `npm upgrade` could install things "newer" than the latest, causing an "outdated" status from bork. By [@mattly][] 33 | - Don't check a user's shell if not requested. Thanks [@jitakirin][] 34 | - Fix for removing an item from a bag value. Thanks [@ngkz][] 35 | - Add version flag to `brew cask` check to bypass warning. Thanks [@rmhsilva][] 36 | - Readme typo fix. Thanks [@indigo423][] 37 | - force legacy listing format for PIP for conformative parsing. Thanks [@frdmn][] 38 | - fix apt-status for outdated packages. Thanks [@dylanvaughn][] 39 | - bypass homebrew not to auto-update itself when performing checks. Thanks [@frdmn][] 40 | - the `desired_type` variable on the `defaults` type is now escaped when checking. Thanks [@bcomnes][] 41 | - the `--size` flag check on the `download` type. Thanks [@bcomnes][] 42 | - Some typos in the readme. Thanks [@rgieseke][] 43 | 44 | ## [0.10.0] - 2016-03-29 45 | 46 | ### Added 47 | 48 | - `bag` helper: added `print` command to echo each item as a line 49 | - `git` type: added an option to explicitly set the destination. These are equivalent: 50 | 51 | ```bash 52 | cd ~/code; ok git git@somewhere.com:/me/someproject.git 53 | ok git ~/code/someproject git@somewhere.com:/me/someproject.git 54 | ``` 55 | 56 | I am inclined to deprecate the original implicit version, and welcome feedback about this. 57 | 58 | - `github` type: made to work with explicit destination option for `git` above. 59 | - `github` type: added `-ssh` option to specify `git@github.com:` style urls. 60 | - new `apm` type for managing packages for the [Atom](https://atom.io) text editor. Thanks [@frdmn][] 61 | - `npm` type: Tests! 62 | - `npm` type: Added outdated/upgrade support. 63 | - `Readme.md`: Added installation instructions, moved some sections around. Thanks [@frdmn][] 64 | - `Changelog.md`: moved from `History.md`, improved organization. 65 | 66 | ### Deprecated 67 | 68 | - `destination` declaration is now a proxy for unix `cd`, and will emit to STDERR a message stating it will be removed in the future. 69 | 70 | ### Removed 71 | 72 | - `ok` declaration no longer runs commands from the set `destination`; it will run them from the current directory. 73 | 74 | ### Fixed 75 | 76 | - `dict` type: fix handling for `dict` entries. 77 | - `dict` type: alias `int` type to `integer`. 78 | - `symlink` type: properly quote target in status checks. 79 | - `npm` type: Some versions of the `npm` executable have a bug preventing `--depth 0` and `--parseable` from working together. We work around this by using only `--depth 0` and parsing that output manually. 80 | - `file` type: during `compile` operation, if file is missing, will halt the compile and emit an error to STDERR. 81 | 82 | ## [0.9.1] — 2016-03-25 83 | 84 | ### Fixed 85 | 86 | - Fix a regression introduced in fd49cab that assumed the bork script path (passed on the command line) was relative. Thanks @frdmn 87 | 88 | ## [0.9] – 2016-03-24 89 | 90 | Initial tagged release, prompted by getting bork into homebrew. Conversely, about three years after I started working on this project. 91 | 92 | [@bcomnes]: https://github.com/bcomnes 93 | [@dylanvaughn]: https://github.com/dylanvaughn 94 | [@edrex]: https://github.com/edrex 95 | [@frdmn]: https://github.com/frdmn 96 | [@indigo423]: https://github.com/indigo423 97 | [@jitakirin]: https://github.com/jitakirin 98 | [@martinwalsh]: https://github.com/martinwalsh 99 | [@mattly]: https://github.com/mattly 100 | [@ngkz]: https://github.com/ngkz 101 | [@rgieseke]: https://github.com/rgieseke 102 | [@rmhsilva]: https://github.com/rmhsilva 103 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright 2014 Matthew Lyon 2 | 3 | Licensed under the Apache License, Version 2.0 (the "License"); 4 | you may not use this file except in compliance with the License. 5 | You may obtain a copy of the License at 6 | 7 | http://www.apache.org/licenses/LICENSE-2.0 8 | 9 | Unless required by applicable law or agreed to in writing, software 10 | distributed under the License is distributed on an "AS IS" BASIS, 11 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | See the License for the specific language governing permissions and 13 | limitations under the License. 14 | -------------------------------------------------------------------------------- /Readme.md: -------------------------------------------------------------------------------- 1 | # Bork - (no longer under development) 2 | 3 | Bork puts the 'sh' back into IT. [Bork Bork Bork](https://www.youtube.com/results?search_query=swedish+chef). 4 | 5 | Bork is no longer under active development. If you fork it and your fork gets some steam going, please let me know, I'm happy to add you to this list. 6 | 7 | ## Known Active Forks: 8 | 9 | - [Skylar MacDonald](https://github.com/skylarmacdonald/bork) 10 | 11 | ## the Swedish Chef Puppet of Config Management 12 | 13 | Bork is a bash DSL for making declarative assertions about the state of a system. 14 | 15 | Bork is written against Bash 3.2 and common unix utilities such as sed, awk and 16 | grep. It is designed to work on any UNIX-based system and maintain awareness of 17 | platform differences between BSD and GPL versions of unix utilities. 18 | 19 | # Installation 20 | 21 | ## From source 22 | 23 | 1. Clone this repository: 24 | `git clone https://github.com/mattly/bork /usr/local/src/bork` 25 | 26 | 1. Symlink the bork binaries into your `$PATH`: 27 | ```bash 28 | ln -sf /usr/local/src/bork/bin/bork /usr/local/bin/bork 29 | ``` 30 | 31 | ## via Homebrew (Mac OS X) 32 | 33 | 1. Install via Homebrew: 34 | `brew install bork` 35 | 36 | # Usage and Operations 37 | 38 | Running bork without arguments will output some help: 39 | 40 | ``` 41 | bork usage: 42 | 43 | bork operation [config-file] [options] 44 | 45 | where "operation" is one of: 46 | 47 | - check: perform 'status' for a single command 48 | example: bork check ok github mattly/dotfiles 49 | - compile: compile the config file to a self-contained script output to STDOUT 50 | --conflicts=(y|yes|n|no) If given, sets an automatic answer for conflict resolution. 51 | example: bork compile dotfiles.sh --conflicts=y > install.sh 52 | - do: perform 'satisfy' for a single command 53 | example: bork do ok github mattly/dotfiles 54 | - satisfy: satisfy the config file's conditions if possible 55 | - status: determine if the config file's conditions are met 56 | - types: list types and their usage information 57 | ``` 58 | 59 | Let's explore these in more depth: 60 | 61 | ## Assertions and Config Files 62 | 63 | At the heart of bork is making **assertions** in a **declarative** manner via 64 | the `ok` function. That is, you tell it *what* you want the system to look like 65 | instead of *how* to make it look like that. An assertion takes a **type** and a 66 | number of arguments. It invokes the type's handler function with an *action* 67 | such as `status`, `install`, or `upgrade`, which determines the imperative 68 | commands needed to test the assertion or bring it up to date. There are a number 69 | of included types in the `types` directory, and bork makes it easy to create 70 | your own. 71 | 72 | Here's a basic example: 73 | 74 | ```bash 75 | ok brew # presence and updatedness of Homebrew 76 | ok brew git # presence and updatedness of Homebrew git package 77 | ok directory $HOME/code # presence of the ~/code directory 78 | ok github $HOME/code/dotfiles mattly/dotfiles # presence, drift of git repository in ~/code/dotfiles 79 | cd $HOME 80 | for file in $HOME/code/dotfiles/configs/.[!.]* 81 | do # for each file in ~/code/dotfiles/configs, 82 | ok symlink "$(basename $file)" $file # presense of a symlink to file in ~ with a leading dot 83 | done 84 | ``` 85 | 86 | When run, bork will test each `ok` assertion and determine if it's met or not. 87 | If not, bork can go ahead and *satisfy* the assertion by installing, upgrading, or 88 | altering the configuration of the item to match the assertion. It will then test 89 | the assertion again. Declarations are idempotent -- if the assertion is already 90 | met, bork will not do anything. 91 | 92 | When you're happy with your config script, you can compile it to a standalone 93 | script which does not require bork to run. The compiled script can be passed 94 | around via curl, scp or the like and run on completely new systems. 95 | 96 | ## Assertion Types 97 | 98 | You can run `bork types` from the command line to get a list of the assertion types 99 | and some basic information about their usage and options. 100 | 101 | ### Generic assertions 102 | ``` 103 | check: runs a given command. OK if returns 0, FAILED otherwise. 104 | ``` 105 | 106 | ### File System 107 | ``` 108 | directory: asserts presence of a directory 109 | file: asserts the presence, checksum, owner and permissions of a file 110 | download: asserts the presence of a file compared to an http(s) url 111 | symlink: assert presence and target of a symlink 112 | ``` 113 | 114 | ### Source Control 115 | ``` 116 | git: asserts presence and state of a git repository 117 | github: front-end for git type, uses github urls 118 | ``` 119 | 120 | ### Language Package Managers 121 | ``` 122 | gem: asserts the presence of a gem in the environment's ruby 123 | npm: asserts the presence of a nodejs module in npm's global installation 124 | pip: asserts presence of packages installed via pip 125 | pipsi: asserts presence of pipsi or packages installed via pipsi 126 | apm: asserts the presence of an atom package 127 | go-get: asserts the presence of a go package 128 | ``` 129 | 130 | ### Mac OS X specific 131 | ``` 132 | brew: asserts presence of packages installed via Homebrew on Mac OS X 133 | brew-tap: asserts a Homebrew formula repository has been tapped; does NOT assert updatedness of a tap's formula. Use `ok brew` for that. 134 | cask: asserts presence of apps installed via caskroom.io on Mac OS X 135 | defaults: asserts settings for OS X's 'defaults' system 136 | mas: asserts a Mac app is installed and up-to-date from the App Store 137 | via the 'mas' utility https://github.com/argon/mas 138 | scutil: verifies OS X machine name with scutil 139 | ``` 140 | 141 | ### Linux specific: 142 | ``` 143 | apt: asserts packages installed via apt-get on Debian or Ubuntu Linux 144 | yum: asserts packages installed via yum on CentOS or RedHat Linux 145 | zypper: asserts packages installed via zypper (SUSE) 146 | ``` 147 | 148 | ### User management (currently Linux-only) 149 | ``` 150 | group: asserts presence of a unix group (Linux only, for now) 151 | user: assert presence of a user on the system 152 | ``` 153 | 154 | ### UNIX utilities 155 | ``` 156 | iptables: asserts presence of iptables rule 157 | ``` 158 | 159 | ## Runtime Operations 160 | 161 | Per the usage guide, bork has a few main modes of operation: 162 | 163 | - `status`: Reports on the status of the assertions in a config file. 164 | - `satisfy`: Checks the status of assertions in a config file, satisfying them 165 | where needed. 166 | - `compile`: Compiles a config file to a standalone script. 167 | - `check`: Performs a status report on a single assertion. 168 | - `do`: Performs a satisfy operation on a single assertion. 169 | 170 | ### bork status myconfig.sh 171 | 172 | The `status` command will confirm that assertions are met or not, and output 173 | their status. It will not take any action to satisfy those assertions. There are 174 | a handful of statuses an assertion can return, and this since this mode is the 175 | closest bork can do to a true `dry run`(*) you can use it to test a script 176 | against a pre-existing machine. 177 | 178 | * Some types, such as `git`, need to modify local state by talking to the network 179 | (such as performing `git fetch`), without modifying the things the assertion aims 180 | to check. 181 | 182 | The status command will give you output such as: 183 | 184 | ``` 185 | outdated: brew 186 | ok: brew git 187 | missing: brew fish 188 | ok: directory /Users/mattly/code/mattly 189 | conflict (upgradable): github mattly/dotfiles 190 | local git repository has uncommitted changes 191 | ok: symlink /Users/mattly/.gitignore /Users/mattly/code/mattly/dotfiles/configs/gitignore 192 | conflict (clobber required): symlink /Users/mattly/.lein /Users/mattly/code/mattly/dotfiles/configs/lein 193 | not a symlink: /Users/mattly/.lein 194 | mismatch (upgradable): defaults com.apple.dock tilesize integer 36 195 | expected type: integer 196 | received type: float 197 | expected value: 36 198 | received value: 55 199 | ``` 200 | 201 | Each item reports its status like so: 202 | 203 | - `ok`: The assertion is met as best we can determine. 204 | - `missing`: The assertion is not met, and no trace of it ever being met was found. 205 | - `outdated`: The assertion is met, but can be upgraded to a newer version. 206 | - `mismatch (upgradable)`: The assertion is not met as specified, something is 207 | different. It can be satisfied easily. An explanation will be given. 208 | - `conflict (upgradable)`: The assertion is not met as specified. It can be 209 | satisfied easily, but doing so may result in data loss. 210 | - `conflict (clobber required)`: The assertion is not met as specified. Bork 211 | cannot currently satisfy this assertion. In the future, it will be able to, 212 | but doing so may result in data loss. 213 | 214 | ### bork check ok github mattly/dotfiles 215 | 216 | The `check` command will take a single assertion on the command line and perform 217 | a `status` check as above for it. 218 | 219 | ### bork satisfy myconfig.sh 220 | 221 | The `satisfy` command is where the real magic happens. For every assertion in 222 | the config file, bork will check its status as described in the `status` command 223 | above, and if it is not `ok` it will attempt to make it `ok`, typically via 224 | *installing* or *upgrading* something -- but sometimes a *conflict* is detected 225 | which could lose data, such as a local git repository having uncommitted 226 | changes. In that case, bork will warn you about the problem and ask if you want 227 | to proceed. Sometimes conflicts are detected which bork does not know how to 228 | resolve — it will warn you about the problem so you can fix it yourself. 229 | 230 | ### bork do ok github mattly/dotfiles 231 | 232 | The `do` command will take a single assertion on the command line and perform a 233 | `satisfy` operation on it as above. 234 | 235 | ### bork compile myconfig.sh 236 | 237 | The `compile` command will output to STDOUT a standalone shell script that does 238 | not require bork to run. You may pass this around as with any file via curl or 239 | scp or whatever you like and run it. Any sub-configs via `include` will be 240 | included in the output, and any type that needs to include resources to do what 241 | it does, such as the `file` type, will include their resources in the script as 242 | base64 encoded data. 243 | 244 | ### Custom Types 245 | 246 | Writing new types is pretty straightforward, and there is a guide to writing 247 | them in the `docs/` directory. If you wish to use a type that is not in bork's 248 | `types` directory, you can let bork know about it with the `register` 249 | declaration: 250 | 251 | ```bash 252 | register etc/pgdb.sh 253 | ok pgdb my_app_db 254 | ``` 255 | 256 | ### Composing Config Files 257 | 258 | You may compose config files into greater operations with the `include` 259 | directive with a path to a script relative to the current script's directory. 260 | 261 | ```bash 262 | # this is main.sh 263 | include databases.sh 264 | include etc/projects.sh 265 | ``` 266 | 267 | ```bash 268 | # this is etc/projects.sh 269 | include project-one.sh 270 | include project-two.sh 271 | # these will be read from the etc/ directory 272 | ``` 273 | 274 | ### Taking Further Action on Changes 275 | 276 | Bork doesn't have callbacks per-se, but after each assertion there are a handful 277 | of functions you can call to take further action: 278 | 279 | ```bash 280 | ok brew fish 281 | if did_install; then 282 | sudo echo "/usr/local/bin/fish" >> /etc/shells 283 | chsh -s /usr/local/bin/fish 284 | fi 285 | ``` 286 | There are four functions to help you take further actions on change: 287 | 288 | - `did_install`: did the previous assertion result in the item being installed 289 | from scratch? 290 | - `did_upgrade`: did the previous assertion result in the existing item being 291 | upgraded? 292 | - `did_update`: did the previous assertion result in either the item being 293 | installed or upgraded? 294 | - `did_error`: did attempting to install or upgrade the previous assertion 295 | result in an error? 296 | 297 | ## Contributing 298 | 299 | 1. Fork it 300 | 2. Create your feature branch: `git checkout -b feature/my-new-feature` 301 | 3. Commit your changes: `git commit -am 'Add some feature'` 302 | 4. Push to the branch: `git push origin feature/my-new-feature` 303 | 5. Submit a pull request 304 | 305 | ### Contribution Guidelines 306 | 307 | 1. Prefer clarity of intent over brevity. Bash can be an obtuse language, but it 308 | doesn't *have* to be. Many people have said bork has some of the clearest 309 | bash code they've ever seen, and that's a standard to strive for. 310 | 311 | 2. Favor helper abstractions over arbitrary platform-specific checks. See 312 | [`md5cmd`](lib/helpers/md5cmd.sh), [`http`](lib/helpers/http.sh), and 313 | [`permission_cmd`](lib/helpers/permission_cmd.sh), and look at how they're 314 | used. 315 | 316 | 3. Types are independent, stateless, and atomic. Do not attempt to maintain a 317 | cache in a type file unless you're talking to the network. An assertion is 318 | the *whole* of the assertion — don't attempt to create a multi-stage 319 | assertion type that depends on maintaining state. Find a way to express the 320 | whole of the assertion in one go. 321 | 322 | 4. Leave Dependency Management to the user. Is a needed binary not installed for 323 | a type? Return `$STATUS_FAILED_PRECONDITION` in your status check. Let the 324 | user decide the best way to satisfy any dependencies. 325 | 326 | ## Community 327 | 328 | Feel free to join us in IRC: 329 | 330 | - Hostname: `irc.freenode.org` 331 | - Channel: `#bork.sh` 332 | - [Web IRC client in case you don't have a native one](https://kiwiirc.com/client/irc.freenode.net/bork.sh) 333 | 334 | ## Requirements / Dependencies 335 | 336 | * Bash 3.2 337 | 338 | ## Version 339 | 340 | 0.10.0 341 | 342 | ## License 343 | 344 | [Apache License 2.0](LICENSE) 345 | -------------------------------------------------------------------------------- /bin/bork: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | # this is done as an eval'd string instead of a straight up 4 | # function and statement because we want to re-use it in 5 | # "compile" mode without repeating ourselves. It's a hack, 6 | # but a purposeful one. 7 | BORK_SETUP_FUNCTION=$(cat < install.sh 87 | - do: perform 'satisfy' for a single command 88 | example: bork do ok github mattly/dotfiles 89 | - satisfy: satisfy the config file's conditions if possible 90 | - status: determine if the config file's conditions are met 91 | - types: list types and their usage information 92 | END 93 | exit 1 94 | ;; 95 | esac 96 | -------------------------------------------------------------------------------- /docs/assertion_status_codes.markdown: -------------------------------------------------------------------------------- 1 | When writing a status clause, it is best practice to traverse the codes described above in order from highest to lowest, testing conditions as necessary. If multiple conditions exist that would return the same status code, they should all be tested and echo any appropriate messages before returning the code. 2 | 3 | #### Normal Status Codes 4 | 5 | `0`: Satisfied. No further action needed. 6 | 7 | `10`: Missing. Satisfy with 'install'. 8 | 9 | `11`: Outdated. Satisfy with 'upgrade'. Indicates the existing thing is behind. 10 | 11 | `12`: Partial. Satisfy with 'upgrade'. Indicates the existing thing is incomplete. 12 | 13 | `13`: Mismatched, Upgrade. Satisfy with 'upgrade'. No conflict (defined below) occurs, and the assertion is not 'outdated', but perhaps some options are different. 14 | 15 | `14`: Mismatched, Clobber. Satisfy with 'delete' followed by 'install'. Not supported yet. 16 | 17 | #### Conflict Status Codes 18 | 19 | **Conflicts** are a class of responses which indicate the system is in a state where the process of satisfying the assertion will cause the existing state of the assertion to go away. Examples include a file assertion having a different md5 sum, or a git repository having diverged from its upstream. The script **should** echo one or more lines describing what is conflicted and what might be lost if the conflict resolves. 20 | 21 | Conflicts are not currently resolvable, but a future version of bork will prompt the user or allow a --force option to satisfy the assertion. 22 | 23 | `20`: Conflict, Upgrade. Satisfy with 'upgrade'. Some data might be lost, such as a file with a different md5 sum, uncommitted SCM changes, etc. 24 | 25 | `21`: Conflict, Clobber. Satisfy with 'delete' followed by 'install'. 26 | 27 | `25`: Conflict, Halt. The script does not know how to resolve this conflict. 28 | 29 | #### Error Status Codes 30 | 31 | **Errors** are a class of responses which indicate the script cannot proceed with satisfying this assertion. The script **should** echo one or more lines describing the problem and hint at a solution. 32 | 33 | `30`: Bad arguments. The script was provided with arguments it cannot understand. 34 | 35 | `31`: Failed arguments. The script was provided with arguments that do not resolve into a resource the script can use to satisfy the result, or even further determine its status. 36 | 37 | `32`: Failed argument precondition. The script was provided with arguments that indicate it should do something the script knows it cannot do. For example, use a git branch that doesn't exist. 38 | 39 | `33`: Failed precondition. The script cannot run, a requisite condition (f.e., git) is missing. 40 | 41 | `34`: Unsupported platform. The script cannot run on this host, the operating system is not supported. 42 | 43 | -------------------------------------------------------------------------------- /docs/howto_write_an_assertion_type.markdown: -------------------------------------------------------------------------------- 1 | # How To Write a Bork Assertion Type 2 | 3 | So, you have something you'd like to be able to track with bork's `ok` 4 | declaration. Perhaps it's the presence of packages from yet another 5 | programming language's packaging system, perhaps something you interact 6 | with through the shell. If you can programatically determine if it's 7 | present, and programatically make it present, you can probably make a bork 8 | assertion type out of it. 9 | 10 | ## Action Calls 11 | 12 | Bork assertions are scripts that are called by the runner. Ideally they could be run independently of the runner, provided the bork helpers are loaded via `bork load`, if they even call on the helpers. The runner calls with an `action` and the arguments provided to `ok`. For example, this call to `ok`: 13 | 14 | ok brew bats 15 | 16 | is transformed into one or more of calls to the `brew` assertion: 17 | 18 | types/brew.sh status bats 19 | types/brew.sh install bats 20 | types/brew.sh upgrade bats 21 | 22 | Most of the bork "core" assertions use a case statement to switch on the provided "action". 23 | 24 | The runner decides what calls to perform based on its current operation and the state of the system. Here are the actions a script can expect from the runner: 25 | 26 | ### desc 27 | 28 | types/file.sh desc 29 | 30 | Outputs basic usage information. This is included in `bork types`. Only really useful right now for scripts that are in the `types/` directory included with bork. 31 | 32 | ### status 33 | 34 | types/file.sh status path/to/targetfile path/from/sourcefile 35 | 36 | When called with `status`, the assertion script should determine if the assertion is met, and return a code to indicate the current status of the assertion. It _may_ echo messages to STDOUT indicating guidance to the user indicating any problems or warnings. 37 | 38 | Example: checks that targetfile exists, has the same md5 sum as sourcefile. 39 | 40 | See the [Status Codes Reference](./assertion_status_codes.markdown) for the complete list. 41 | 42 | ### install 43 | 44 | types/file.sh install path/to/targetfile path/from/sourcefile 45 | 46 | When called with `install`, the assertion script should assume that `status` was called with the same arguments and returned `10`; that is, nothing about the assertion exists on the host system. 47 | 48 | Example: copies sourcefile to targetfile. 49 | 50 | The script should output any relevant messages, and return 0 on success. 51 | 52 | ### upgrade 53 | 54 | types/file.sh upgrade to/targetfile from/sourcefile --permissions=700 55 | 56 | When called with `upgrade`, the assertion script should assume that `status` was called with the same arguments and returned 11, 12, or 20. Enough of the assertion exists that a different, hopefully quicker path can be taken to satisfying the assertion. 57 | 58 | Example: Updates the permissions on targetfile to 700. 59 | 60 | The script should output any relevant messages and updates, and return 0 on success. 61 | 62 | ### delete 63 | 64 | types/file.sh delete to/targetfile from/sourcefile 65 | 66 | Not implemented in the runner yet. The script should remove the artifacts of the assertion from the system. 67 | 68 | Example: Deletes targetfile 69 | 70 | The script should output any relevant messages, and return 0 on success. 71 | 72 | ### compile 73 | 74 | types/file.sh compile to/targetfile from/sourcefile 75 | 76 | Echo any relevant information about the current system for the given arguments that will be copied to the compiled script. The compiled script itself will be included by the compiler, as will the assertion that is calling 'compile' to begin with. 77 | 78 | When called from the compiled script, you can test `is_compiled` in status, install, etc., to determine if you need to do anything differently. 79 | 80 | Example: base64-encodes `sourcefile` and assigns it to a variable that maps to its path. The `status` and `install` actions know to use this variable instead of looking for the sourcefile and base64-decode its contents. 81 | 82 | ## Helpers 83 | 84 | Bork makes a number of helpers available to ease common bash scripting pain points. They are in the `lib/helpers` directory, but there is one in particular you should be familiar with when writing assertion type scripts: 85 | 86 | ### bake 87 | 88 | Bork has the notion of the "source system" and the "target system". They are currently realized only in the scope of the "compile" operation, but in the future it might be possible to run bork locally and have it execute commands on another host system via ssh. The key to doing this is `bake`. 89 | 90 | Any command that queries the state of or modifies the target system should be run through bake. In normal operation, it will simply eval the command as passed. Querying the state of the "source system" or logic do not need to be passed through bake. 91 | 92 | This is a little bit of overhead, but I believe it will yield promising results. Controlling remote hosts is one possibility, providing "compile" with an option to just do a super-lightweight install script is another. It's used for mocking behavior in the tests. 93 | 94 | 95 | 96 | -------------------------------------------------------------------------------- /lib/declarations/changes.sh: -------------------------------------------------------------------------------- 1 | bork_performed_install=0 2 | bork_performed_upgrade=0 3 | bork_performed_error=0 4 | 5 | bork_any_updated=0 6 | 7 | did_install () { [ "$bork_performed_install" -eq 1 ] && return 0 || return 1; } 8 | did_upgrade () { [ "$bork_performed_upgrade" -eq 1 ] && return 0 || return 1; } 9 | did_update () { 10 | if did_install; then return 0 11 | elif did_upgrade; then return 0 12 | else return 1 13 | fi 14 | } 15 | did_error () { [ "$bork_performed_error" -gt 0 ] && return 0 || return 1; } 16 | 17 | any_updated () { [ "$bork_any_updated" -gt 0 ] && return 0 || return 1; } 18 | 19 | _changes_reset () { 20 | bork_performed_install=0 21 | bork_performed_upgrade=0 22 | bork_performed_error=0 23 | last_change_type= 24 | } 25 | 26 | _changes_complete () { 27 | status=$1 28 | action=$2 29 | if [ "$status" -gt 0 ]; then bork_performed_error=1 30 | elif [ "$action" = "install" ]; then bork_performed_install=1 31 | elif [ "$action" = "upgrade" ]; then bork_performed_upgrade=1 32 | fi 33 | if did_update; then bork_any_updated=1 ;fi 34 | [ "$status" -gt 0 ] && echo "* failure" 35 | } 36 | 37 | -------------------------------------------------------------------------------- /lib/declarations/destination.sh: -------------------------------------------------------------------------------- 1 | destination () { 2 | echo "deprecation warning: 'destination' utility will be removed in a future version - use 'cd' instead" 1>&2 3 | cd $1 4 | } 5 | -------------------------------------------------------------------------------- /lib/declarations/include.sh: -------------------------------------------------------------------------------- 1 | # keeps track of where we've come from 2 | bag init include_directories 3 | bag push include_directories "$BORK_SCRIPT_DIR" 4 | 5 | include () { 6 | incl_script="$(bag read include_directories)/$1" 7 | if [ -e $incl_script ]; then 8 | target_dir=$(dirname $incl_script) 9 | bag push include_directories "$target_dir" 10 | case $operation in 11 | compile) compile_file "$incl_script" ;; 12 | *) . $incl_script ;; 13 | esac 14 | bag pop include_directories 15 | else 16 | echo "include: $incl_script: No such file" 1>&2 17 | exit 1 18 | fi 19 | return 0 20 | } 21 | 22 | -------------------------------------------------------------------------------- /lib/declarations/ok.sh: -------------------------------------------------------------------------------- 1 | _source_runner () { 2 | if is_compiled; then echo "$1" 3 | else echo ". $1" 4 | fi 5 | } 6 | 7 | _bork_check_failed=0 8 | check_failed () { [ "$_bork_check_failed" -gt 0 ] && return 0 || return 1; } 9 | 10 | _checked_len=0 11 | _checking () { 12 | type=$1 13 | shift 14 | check_str="$type: $*" 15 | _checked_len=${#check_str} 16 | echo -n "$check_str"$'\r' 17 | } 18 | _checked () { 19 | report="$*" 20 | (( pad=$_checked_len - ${#report} )) 21 | i=1 22 | while [ "$i" -le $pad ]; do 23 | report+=" " 24 | (( i++ )) 25 | done 26 | echo "$report" 27 | } 28 | 29 | _conflict_approve () { 30 | if [ -n "$BORK_CONFLICT_RESOLVE" ]; then 31 | return $BORK_CONFLICT_RESOLVE 32 | fi 33 | echo 34 | echo "== Warning! Assertion: $*" 35 | echo "Attempting to satisfy has resulted in a conflict. Satisfying this may overwrite data." 36 | _yesno "Do you want to continue?" 37 | return $? 38 | } 39 | 40 | _yesno () { 41 | answered=0 42 | answer= 43 | while [ "$answered" -eq 0 ]; do 44 | read -p "$* (yes/no) " answer 45 | if [[ "$answer" == 'y' || "$answer" == "yes" || "$answer" == "n" || "$answer" == "no" ]]; then 46 | answered=1 47 | else 48 | echo "Valid answers are: yes y no n" >&2 49 | fi 50 | done 51 | [[ "$answer" == 'y' || "$answer" == 'yes' ]] 52 | } 53 | 54 | ok () { 55 | assertion=$1 56 | shift 57 | _bork_check_failed=0 58 | _changes_reset 59 | fn=$(_lookup_type $assertion) 60 | if [ -z "$fn" ]; then 61 | echo "not found: $assertion" 1>&2 62 | return 1 63 | fi 64 | argstr=$* 65 | quoted_argstr= 66 | while [ -n "$1" ]; do 67 | quoted_argstr=$(echo "$quoted_argstr '$1'") 68 | shift 69 | done 70 | case $operation in 71 | echo) echo "$fn $argstr" ;; 72 | status) 73 | _checking "checking" $assertion $argstr 74 | output=$(eval "$(_source_runner $fn) status $quoted_argstr") 75 | status=$? 76 | _checked "$(_status_for $status): $assertion $argstr" 77 | [ "$status" -eq 1 ] && _bork_check_failed=1 78 | [ "$status" -ne 0 ] && [ -n "$output" ] && echo "$output" 79 | return $status ;; 80 | satisfy) 81 | _checking "checking" $assertion $argstr 82 | status_output=$(eval "$(_source_runner $fn) status $quoted_argstr") 83 | status=$? 84 | _checked "$(_status_for $status): $assertion $argstr" 85 | case $status in 86 | 0) : ;; 87 | 1) 88 | _bork_check_failed=1 89 | echo "$status_output" 90 | ;; 91 | 10) 92 | eval "$(_source_runner $fn) install $quoted_argstr" 93 | _changes_complete $? 'install' 94 | ;; 95 | 11|12|13) 96 | echo "$status_output" 97 | eval "$(_source_runner $fn) upgrade $quoted_argstr" 98 | _changes_complete $? 'upgrade' 99 | ;; 100 | 20) 101 | echo "$status_output" 102 | _conflict_approve $assertion $argstr 103 | if [ "$?" -eq 0 ]; then 104 | echo "Resolving conflict..." 105 | eval "$(_source_runner $fn) upgrade $quoted_argstr" 106 | _changes_complete $? 'upgrade' 107 | else 108 | echo "Conflict unresolved." 109 | fi 110 | ;; 111 | *) 112 | echo "-- sorry, bork doesn't handle this response yet" 113 | echo "$status_output" 114 | ;; 115 | esac 116 | if did_update; then 117 | echo "verifying $last_change_type: $assertion $argstr" 118 | output=$(eval "$(_source_runner $fn) status $quoted_argstr") 119 | status=$? 120 | if [ "$status" -gt 0 ]; then 121 | echo "* $last_change_type failed" 122 | _checked "$(_status_for $status)" 123 | echo "$output" 124 | else 125 | echo "* success" 126 | fi 127 | return 1 128 | fi 129 | ;; 130 | esac 131 | } 132 | 133 | 134 | -------------------------------------------------------------------------------- /lib/declarations/register.sh: -------------------------------------------------------------------------------- 1 | # manages assertion types 2 | 3 | # is a bag that keeps track of assertion types their locations 4 | bag init bork_assertion_types 5 | 6 | # register a local assertion type 7 | # register $filename 8 | # - $filename: path to a local file to register 9 | # basename of file is the asertion type. 10 | # 11 | # register helpers/pip.sh 12 | # ok pip pygments 13 | # 14 | # exits with status 1 if the file doesn't exist 15 | register () { 16 | file=$1 17 | type=$(basename $file '.sh') 18 | if [ -e "$BORK_SCRIPT_DIR/$file" ]; then 19 | file="$BORK_SCRIPT_DIR/$file" 20 | else 21 | exit 1 22 | fi 23 | bag set bork_assertion_types $type $file 24 | } 25 | 26 | # lookup assertion function 27 | 28 | # yes, this could have been done in fewer lines with a gnarly nested IF/ELSE 29 | # type statement. I have no interest in saving lines at the cost of clarity. 30 | _lookup_type () { 31 | assertion=$1 32 | if is_compiled; then 33 | echo "type_$assertion" 34 | return 35 | fi 36 | fn=$(bag get bork_assertion_types $assertion) 37 | if [ -n "$fn" ]; then 38 | echo "$fn" 39 | return 40 | fi 41 | bork_official="$BORK_SOURCE_DIR/types/$(echo $assertion).sh" 42 | if [ -e "$bork_official" ]; then 43 | echo "$bork_official" 44 | return 45 | fi 46 | local_script="$BORK_SCRIPT_DIR/$assertion" 47 | if [ -e "$local_script" ]; then 48 | echo "$local_script" 49 | return 50 | fi 51 | return 1 52 | } 53 | 54 | -------------------------------------------------------------------------------- /lib/helpers/arguments.sh: -------------------------------------------------------------------------------- 1 | arguments () { 2 | op=$1 3 | shift 4 | case $op in 5 | get) 6 | key=$1 7 | shift 8 | value= 9 | while [ -n "$1" ] && [ -z "$value" ]; do 10 | this=$1 11 | shift 12 | if [ ${this:0:2} = '--' ]; then 13 | tmp=${this:2} # strip off leading -- 14 | echo "$tmp" | grep -E '=' > /dev/null 15 | if [ "$?" -eq 0 ]; then 16 | param=${tmp%%=*} # everything before = 17 | val=${tmp##*=} # everything after = 18 | else 19 | param=$tmp 20 | val="true" 21 | fi 22 | if [ "$param" = $key ]; then value=$val; fi 23 | fi 24 | done 25 | [ -n $value ] && echo "$value" 26 | ;; 27 | *) return 1 ;; 28 | esac 29 | } 30 | -------------------------------------------------------------------------------- /lib/helpers/bag.sh: -------------------------------------------------------------------------------- 1 | # a bag is a combination of a stack, array and dict 2 | # ## like a stack, you can 3 | # - **push** a value onto the top 4 | # - **pop** a value off of the top 5 | # - **read** the topmost value 6 | # - get the **size** of the stack 7 | # ## like an array, you can 8 | # - **filter** for values matching a pattern 9 | # - **find** the first value matching a pattern 10 | # - get the **index** of the first value matching a pattern 11 | # ## like a dict, you can 12 | # - **set** a key to a value. This is stored as "$key=$value". It will 13 | # overwrite any previous value for $key. 14 | # - **get** the value for a key. 15 | # - **print** outputs the contents of the bag, one line at a time. 16 | bag () { 17 | action=$1 18 | varname=$2 19 | shift 2 20 | if [ "$action" != "init" ]; then 21 | length=$(eval "echo \${#$varname[*]}") 22 | last=$(( length - 1 )) 23 | fi 24 | case "$action" in 25 | init) eval "$varname=( )" ;; 26 | push) eval "$varname[$length]=\"$1\"" ;; 27 | pop) eval "unset $varname[$last]" ;; 28 | read) 29 | [ "$length" -gt 0 ] && echo $(eval "echo \${$varname[$last]}") ;; 30 | size) echo $length ;; 31 | filter) 32 | index=0 33 | (( limit=$2 )) 34 | [ "$limit" -eq 0 ] && limit=-1 35 | while [ "$index" -lt $length ]; do 36 | line=$(eval "echo \${$varname[$index]}") 37 | if str_matches "$line" "$1"; then 38 | [ -n "$3" ] && echo $index || echo $line 39 | [ "$limit" -ge $index ] && return 40 | fi 41 | (( index++ )) 42 | done ;; 43 | find) echo $(bag filter $varname $1 1) ;; 44 | index) echo $(bag filter $varname $1 1 1) ;; 45 | set) 46 | idx=$(bag index $varname "^$1=") 47 | [ -z "$idx" ] && idx=$length 48 | eval "$varname[$idx]=\"$1=$2\"" 49 | ;; 50 | get) 51 | line=$(bag filter $varname "^$1=" 1) 52 | echo "${line##*=}" ;; 53 | print) 54 | index=0 55 | while [ "$index" -lt $length ]; do 56 | eval "echo \"\${$varname[$index]}\"" 57 | (( index++ )) 58 | done 59 | ;; 60 | *) return 1 ;; 61 | esac 62 | } 63 | -------------------------------------------------------------------------------- /lib/helpers/bake.sh: -------------------------------------------------------------------------------- 1 | bake () { eval "$*"; } 2 | -------------------------------------------------------------------------------- /lib/helpers/http.sh: -------------------------------------------------------------------------------- 1 | has_curl () { 2 | needs_exec "curl" 3 | } 4 | 5 | http_head_cmd () { 6 | url=$1 7 | shift 1 8 | has_curl 9 | if [ "$?" -eq 0 ]; then 10 | echo "curl -sI \"$url\"" 11 | else 12 | echo "curl not found; wget support not implemented yet" 13 | return 1 14 | fi 15 | } 16 | 17 | http_header () { 18 | header=$1 19 | headers=$2 20 | echo "$headers" | grep "$header" | tr -s ' ' | cut -d' ' -f2 21 | } 22 | 23 | http_get_cmd () { 24 | url=$1 25 | target=$2 26 | has_curl 27 | if [ "$?" -eq 0 ]; then 28 | echo "curl -so \"$target\" \"$url\" &> /dev/null" 29 | else 30 | echo "curl not found; wget support not implemented yet" 31 | return 1 32 | fi 33 | } 34 | -------------------------------------------------------------------------------- /lib/helpers/md5cmd.sh: -------------------------------------------------------------------------------- 1 | md5cmd () { 2 | case $1 in 3 | Darwin) 4 | [ -z "$2" ] && echo "md5" || echo "md5 -q $2" 5 | ;; 6 | Linux) 7 | [ -z "$2" ] && arg="" || arg="$2 " 8 | echo "md5sum $arg| awk '{print \$1}'" 9 | ;; 10 | *) return 1 ;; 11 | esac 12 | } 13 | 14 | -------------------------------------------------------------------------------- /lib/helpers/operations.sh: -------------------------------------------------------------------------------- 1 | satisfying () { [ "$operation" == "satisfy" ]; } 2 | -------------------------------------------------------------------------------- /lib/helpers/permission_cmd.sh: -------------------------------------------------------------------------------- 1 | permission_cmd () { 2 | case $1 in 3 | Linux) echo "stat --printf '%a'" ;; 4 | Darwin) echo "stat -f '%Lp'" ;; 5 | *) return 1 ;; 6 | esac 7 | } 8 | -------------------------------------------------------------------------------- /lib/helpers/status_codes.sh: -------------------------------------------------------------------------------- 1 | STATUS_OK=0 2 | STATUS_FAILED=1 3 | STATUS_MISSING=10 4 | STATUS_OUTDATED=11 5 | STATUS_PARTIAL=12 6 | STATUS_MISMATCH_UPGRADE=13 7 | STATUS_MISMATCH_CLOBBER=14 8 | STATUS_CONFLICT_UPGRADE=20 9 | STATUS_CONFLICT_CLOBBER=21 10 | STATUS_CONFLICT_HALT=25 11 | STATUS_BAD_ARGUMENTS=30 12 | STATUS_FAILED_ARGUMENTS=31 13 | STATUS_FAILED_ARGUMENT_PRECONDITION=32 14 | STATUS_FAILED_PRECONDITION=33 15 | STATUS_UNSUPPORTED_PLATFORM=34 16 | 17 | _status_for () { 18 | case "$1" in 19 | $STATUS_OK) echo "ok" ;; 20 | $STATUS_FAILED) echo "failed" ;; 21 | $STATUS_MISSING) echo "missing" ;; 22 | $STATUS_OUTDATED) echo "outdated" ;; 23 | $STATUS_PARTIAL) echo "partial" ;; 24 | $STATUS_MISMATCH_UPGRADE) echo "mismatch (upgradable)" ;; 25 | $STATUS_MISMATCH_CLOBBER) echo "mismatch (clobber required)" ;; 26 | $STATUS_CONFLICT_UPGRADE) echo "conflict (upgradable)" ;; 27 | $STATUS_CONFLICT_CLOBBER) echo "conflict (clobber required)" ;; 28 | $STATUS_CONFLICT_HALT) echo "conflict (unresolvable)" ;; 29 | $STATUS_BAD_ARGUMENT) echo "error (bad arguments)" ;; 30 | $STATUS_FAILED_ARGUMENTS) echo "error (failed arguments)" ;; 31 | $STATUS_FAILED_ARGUMENT_PRECONDITION) echo "error (failed argument precondition)" ;; 32 | $STATUS_FAILED_PRECONDITION) echo "error (failed precondition)" ;; 33 | $STATUS_UNSUPPORTED_PLATFORM) echo "error (unsupported platform)" ;; 34 | *) echo "unknown status: $1" ;; 35 | esac 36 | } 37 | 38 | -------------------------------------------------------------------------------- /lib/helpers/system.sh: -------------------------------------------------------------------------------- 1 | # to be called from an assertions's "status" action, to determine is the target 2 | # system has a necessary exec. Returns 0 if found, $2 + 1 if not. 3 | # 4 | # arguments 5 | # $1: exec to test against. Will be provided to `which`. 6 | # required 7 | # $2: running status. Allows you to "chain" needs exec calls, to easily test 8 | # multiple `needs_exec` calls and know if any failed. 9 | # optional, default: 0 10 | needs_exec () { 11 | [ -z "$1" ] && return 1 12 | [ -z "$2" ] && running_status=0 || running_status=$2 13 | 14 | # was seeing some weirdness on this where $1 would have carraige returns sometimes, so it's quoted. 15 | path=$(bake "which $1") 16 | 17 | if [ "$?" -gt 0 ]; then 18 | echo "missing required exec: $1" 19 | retval=$((running_status+1)) 20 | return $retval 21 | else return $running_status 22 | fi 23 | } 24 | 25 | 26 | platform=$(uname -s) 27 | 28 | # TODO: deprecated in favor of platform_is 29 | is_platform () { 30 | [ "$platform" = $1 ] 31 | return $? 32 | } 33 | 34 | platform_is () { 35 | [ "$platform" = $1 ] 36 | return $? 37 | } 38 | 39 | baking_platform= 40 | baking_platform_is () { 41 | # this is done lazily, to allow time for bake to be reconfigured. 42 | [ -z "$baking_platform" ] && baking_platform=$(bake uname -s) 43 | 44 | [ "$baking_platform" = $1 ] 45 | return $? 46 | } 47 | 48 | -------------------------------------------------------------------------------- /lib/helpers/text.sh: -------------------------------------------------------------------------------- 1 | # Checks a list for a complete match 2 | # pass: "foo bar bee" "foo" 3 | # fail: "foo bar bee" "oo" 4 | str_contains () { 5 | str_matches "$1" "^$2\$" 6 | } 7 | 8 | # retrieves the space-seperated field from a string 9 | # str_get_field "foo bar bee" 2 -> "bar" 10 | str_get_field () { 11 | echo $(echo "$1" | awk '{print $'"$2"'}') 12 | } 13 | 14 | # Counts the number of iteratable items in a string. 15 | # Note that if the string is the output of a shell command, f.e: 16 | # dir_listing=$(ls) 17 | # That you *must* quote the variable when passing it to the function: 18 | # str_item_count "$dir_listing" 19 | # If you do not it will simply return '1' 20 | str_item_count () { 21 | accum=0 22 | for item in $1; do 23 | ((accum++)) 24 | done 25 | echo $accum 26 | } 27 | 28 | # Checks a string for any match. Accepts a regexp 29 | # pass: "foo bar bee" "o{2,}\s+" 30 | # fail: "foo bar bee" "ee\s+" 31 | str_matches () { 32 | $(echo "$1" | grep -E "$2" > /dev/null) 33 | return $? 34 | } 35 | 36 | # Takes a string, replaces matches with a replacement 37 | # "foo bar" "b\w+" "oo" -> "foo boo" 38 | str_replace () { 39 | echo $(echo "$1" | sed -E 's|'"$2"'|'"$3"'|g') 40 | } 41 | 42 | # Removes blank or comment-only lines from stdin 43 | strip_blanks () { 44 | awk '!/^($|[[:space:]]*#)/{print $0}' <&0 45 | } 46 | -------------------------------------------------------------------------------- /lib/support/compile.sh: -------------------------------------------------------------------------------- 1 | # helpers related to the "compile" operation 2 | 3 | is_compiling () { 4 | [ $operation = "compile" ] && return 0 || return 1 5 | } 6 | 7 | # multiline, keeps list of compiled types 8 | bag init compiled_types 9 | 10 | # TODO: test 11 | # interface for the compiled_type multiline 12 | compiled_type_push () { 13 | bag push compiled_types "$1" 14 | } 15 | # TODO: test 16 | # interface for the compiled_type multiline 17 | compiled_type_exists () { 18 | exists=$(bag find compiled_types "^$1\$") 19 | [ -n "$exists" ] 20 | return $? 21 | } 22 | 23 | # if compiling, echoes a function that contains the given assertion 24 | # include_assertion_for_compiling $assertion_type $file_path 25 | # - $assertion_type: key for the assertion 26 | # - $file_path: absolute/relative path to the file 27 | # 28 | # returns immediately with 0 if not compiling 29 | include_assertion () { 30 | if ! is_compiling; then return 0; fi 31 | if compiled_type_exists $1; then return 0; fi 32 | compiled_type_push $1 33 | echo "# $2" 34 | echo "type_$1 () {" 35 | cat $2 | strip_blanks | awk '{print " " $0}' 36 | echo "}" 37 | } 38 | 39 | compile_file () { 40 | cat $1 | while read line; do 41 | first_token=$(str_get_field "$line" 1) 42 | case $first_token in 43 | ok) 44 | type=$(str_get_field "$line" 2) 45 | fn=$(_lookup_type $type) 46 | if [ -z "$fn" ]; then 47 | echo "type $type not found, can't proceed" 1>&2 48 | exit 1 49 | fi 50 | include_assertion $type $fn 51 | compile_cmd=$(echo "$line" | sed -E "s|ok $type ||") 52 | . $fn compile $compile_cmd 53 | echo "$line" 54 | ;; 55 | register|include) eval "$line" ;; 56 | *) echo "$line" ;; 57 | esac 58 | done 59 | } 60 | 61 | 62 | bork_compile () { 63 | cat <2 96 | exit 1 ;; 97 | esac 98 | fi 99 | 100 | compile_file $config 101 | } 102 | -------------------------------------------------------------------------------- /test/declare-change.bats: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bats 2 | 3 | . test/helpers.sh 4 | 5 | @test "did_install reflects \$performed_install" { 6 | bork_performed_install=1 7 | run did_install 8 | [ "$status" -eq 0 ] 9 | bork_performed_install=0 10 | run did_install 11 | [ "$status" -eq 1 ] 12 | } 13 | 14 | @test "did_upgrade reflects \$performed_upgrade" { 15 | bork_performed_upgrade=1 16 | run did_upgrade 17 | [ "$status" -eq 0 ] 18 | bork_performed_upgrade=0 19 | run did_upgrade 20 | [ "$status" -eq 1 ] 21 | } 22 | 23 | @test "did_update reflects both install/upgrade" { 24 | bork_performed_install=1 25 | run did_update 26 | [ "$status" -eq 0 ] 27 | bork_performed_install=0 28 | bork_performed_upgrade=1 29 | run did_update 30 | [ "$status" -eq 0 ] 31 | bork_performed_upgrade=0 32 | run did_update 33 | [ "$status" -eq 1 ] 34 | } 35 | -------------------------------------------------------------------------------- /test/declare-include.bats: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bats 2 | 3 | . test/helpers.sh 4 | 5 | @test "include: maintains relative directories" { 6 | run include "test/fixtures/include-one.sh" 7 | [ "$status" -eq 0 ] 8 | [ "${lines[0]}" = 'one' ] 9 | [ "${lines[1]}" = 'two' ] 10 | } 11 | -------------------------------------------------------------------------------- /test/declare-ok.bats: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bats 2 | 3 | . test/helpers.sh 4 | 5 | operation='echo' 6 | BORK_SCRIPT_DIR="$BORK_SOURCE_DIR/test" 7 | 8 | @test "ok: checks against core types" { 9 | run ok directory foo 10 | [ "$status" -eq 0 ] 11 | [[ "$BORK_SOURCE_DIR/types/directory.sh foo" == $output ]] 12 | } 13 | 14 | @test "ok: checks against stdlib_types" { 15 | run ok brew foo 16 | [ "$status" -eq 0 ] 17 | [[ "$BORK_SOURCE_DIR/types/brew.sh foo" == $output ]] 18 | } 19 | 20 | @test "ok: checks against local scripts" { 21 | run ok fixtures/custom.sh foo 22 | [ "$status" -eq 0 ] 23 | [[ "$BORK_SCRIPT_DIR/fixtures/custom.sh foo" == $output ]] 24 | } 25 | 26 | @test "ok: checks against registered types" { 27 | register fixtures/custom.sh 28 | run ok custom foo 29 | [ "$status" -eq 0 ] 30 | [[ "$BORK_SCRIPT_DIR/fixtures/custom.sh foo" == $output ]] 31 | } 32 | -------------------------------------------------------------------------------- /test/declare-register.bats: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bats 2 | 3 | . test/helpers.sh 4 | 5 | operation='echo' 6 | 7 | is_compiled () { [ -n "$is_compiled" ]; } 8 | 9 | @test "register: recognizes paths" { 10 | BORK_SCRIPT_DIR="$BORK_WORKING_DIR/test" 11 | register "./fixtures/custom.sh" 12 | ok custom foo 13 | } 14 | 15 | @test "register: exits 1 for non-valid values, does not add lib" { 16 | run register foo 17 | [ "$status" -eq 1 ] 18 | run ok foo 19 | [ "$status" -eq 1 ] 20 | } 21 | 22 | # --- lookup_assertion ------------------------------------------- 23 | @test "lookup_type: when is_compiled, echoes type_\$assertion" { 24 | is_compiled=true 25 | run _lookup_type 'foo' 26 | [ "$status" -eq 0 ] 27 | [ "$output" = "type_foo" ] 28 | } 29 | 30 | @test "lookup_type: when assertion_types include type, echoes script" { 31 | p "set one" 32 | bag set bork_assertion_types foo bar 33 | p "set two" 34 | bag set bork_assertion_types bar bee 35 | p "done setting: " 36 | p "${bork_assertion_types[@]}" 37 | run _lookup_type 'foo' 38 | p $status 39 | p $output 40 | [ "$status" -eq 0 ] 41 | [ "$output" = 'bar' ] 42 | } 43 | 44 | @test "lookup_type: when references official assertion, echoes that" { 45 | run _lookup_type "git" 46 | [ "$status" -eq 0 ] 47 | path="$BORK_SOURCE_DIR/types/git.sh" 48 | [ "$output" = $path ] 49 | } 50 | 51 | @test "when missing and references local script, echoes that" { 52 | script_path="test/fixtures/custom.sh" 53 | run _lookup_type "$script_path" 54 | [ "$status" -eq 0 ] 55 | abs_path="$BORK_SCRIPT_DIR/$script_path" 56 | [ "$output" = $abs_path ] 57 | } 58 | -------------------------------------------------------------------------------- /test/fixtures/apt-dpkg-dependencies.txt: -------------------------------------------------------------------------------- 1 | outdated_package install 2 | current_package install 3 | -------------------------------------------------------------------------------- /test/fixtures/apt-upgrade-dry.txt: -------------------------------------------------------------------------------- 1 | Conf current_package 2 | Inst outdated_package [1:9.8.1.dfsg.P1-4] (1:9.8.1.dfsg.P1-4ubuntu0.3 Ubuntu:12.04/precise-updates [i386]) [] 3 | Conf outdated_package 4 | -------------------------------------------------------------------------------- /test/fixtures/brew-list.txt: -------------------------------------------------------------------------------- 1 | current_package 2 | outdated_package 3 | -------------------------------------------------------------------------------- /test/fixtures/brew-outdated.txt: -------------------------------------------------------------------------------- 1 | outdated_package (0.5 < 0.6) 2 | another_outdated_package (0.4 < 0.4.1) 3 | -------------------------------------------------------------------------------- /test/fixtures/brew-tap-list.txt: -------------------------------------------------------------------------------- 1 | homebrew/games 2 | railwaycat/emacsmacport 3 | caskroom/cask 4 | -------------------------------------------------------------------------------- /test/fixtures/brew-tap-pinned.txt: -------------------------------------------------------------------------------- 1 | homebrew/games 2 | -------------------------------------------------------------------------------- /test/fixtures/cask-list.txt: -------------------------------------------------------------------------------- 1 | installed_package 2 | 3 | -------------------------------------------------------------------------------- /test/fixtures/cask-outdated-info.txt: -------------------------------------------------------------------------------- 1 | iterm2: 2.1.4 2 | iTerm2 3 | https://www.iterm2.com/ 4 | Not installed 5 | https://github.com/caskroom/homebrew-cask/blob/master/Casks/iterm2.rb 6 | ==> Contents 7 | iTerm.app (app) 8 | -------------------------------------------------------------------------------- /test/fixtures/custom.sh: -------------------------------------------------------------------------------- 1 | # placeholder 2 | -------------------------------------------------------------------------------- /test/fixtures/defaults-dictionary-value.txt: -------------------------------------------------------------------------------- 1 | { 2 | key = 49; 3 | mod = 1048576; 4 | string = Space; 5 | } 6 | -------------------------------------------------------------------------------- /test/fixtures/gem-list.txt: -------------------------------------------------------------------------------- 1 | bar (1.4.15) 2 | foo (3.1.3, 3.1.4) 3 | -------------------------------------------------------------------------------- /test/fixtures/http-head-curl.txt: -------------------------------------------------------------------------------- 1 | HTTP/1.1 200 OK 2 | Location: http://foo.com/bar.txt 3 | Content-Type: text/plain; charset=UTF-8 4 | Date: Tue, 22 Mar 2016 04:04:05 GMT 5 | Content-Length: 312 6 | -------------------------------------------------------------------------------- /test/fixtures/include-one.sh: -------------------------------------------------------------------------------- 1 | echo "one" 2 | include include-two.sh 3 | -------------------------------------------------------------------------------- /test/fixtures/include-two.sh: -------------------------------------------------------------------------------- 1 | echo "two" 2 | -------------------------------------------------------------------------------- /test/fixtures/mas-list.txt: -------------------------------------------------------------------------------- 1 | 497799835 Xcode 2 | 458034879 Dash 3 | -------------------------------------------------------------------------------- /test/fixtures/mas-outdated.txt: -------------------------------------------------------------------------------- 1 | 458034879 Dash 2 | -------------------------------------------------------------------------------- /test/fixtures/npm-list.txt: -------------------------------------------------------------------------------- 1 | /usr/local/lib 2 | ├── cardinal@0.6.0 3 | ├── coffee-script@0.9.3 4 | ├── js-beautify@1.5.10 5 | ├── nodemon@1.8.1 6 | ├── npm@3.8.5 7 | ├── sibilant@0.4.0 8 | └── tern@0.16.0 9 | -------------------------------------------------------------------------------- /test/fixtures/npm-outdated.txt: -------------------------------------------------------------------------------- 1 | Package Current Wanted Latest Location 2 | coffee-script 1.9.3 1.9.3 1.10.0 3 | npm 3.8.5 3.8.5 3.8.3 4 | sibilant 0.4.0 0.4.0 0.5.0 5 | -------------------------------------------------------------------------------- /test/fixtures/pip-list.txt: -------------------------------------------------------------------------------- 1 | bar (0.11.0) 2 | foo (2.1.2) 3 | -------------------------------------------------------------------------------- /test/fixtures/pipsi-list.txt: -------------------------------------------------------------------------------- 1 | Packages and scripts installed through pipsi: 2 | Package "pipsi": 3 | /home/user/.local/bin/pipsi 4 | Package "current_package": 5 | /home/user/.local/bin/current_package 6 | Package "outdated_package": 7 | /home/user/.local/bin/outdated_package 8 | -------------------------------------------------------------------------------- /test/fixtures/rpm-qa.txt: -------------------------------------------------------------------------------- 1 | outdated_package 2 | current_package 3 | -------------------------------------------------------------------------------- /test/fixtures/user-list.txt: -------------------------------------------------------------------------------- 1 | nobody:*:-2:-2:Unprivileged User:/var/empty:/usr/bin/false 2 | root:*:0:0:System Administrator:/var/root:/bin/sh 3 | daemon:*:1:1:System Services:/var/root:/usr/bin/false 4 | existant:*:100:100::/home/existant:/bin/bash 5 | 6 | -------------------------------------------------------------------------------- /test/fixtures/yum-list-updates.txt: -------------------------------------------------------------------------------- 1 | outdated_package 2 | -------------------------------------------------------------------------------- /test/fixtures/zypper-list-updates.txt: -------------------------------------------------------------------------------- 1 | Loading repository data... 2 | Reading installed packages... 3 | S | Repository | Name | Current Version | Available Version | Arch 4 | --+---------------------------+-------------------------------------+-------------------------------------+-------------------------------------+------- 5 | v | openSUSE-Tumbleweed-Oss | outdated_package | 1.1.0-1.0 | 1.2.0-1.0 | noarch 6 | -------------------------------------------------------------------------------- /test/fn-compiler.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bats 2 | 3 | . test/helpers.sh 4 | 5 | compiled_type_push () { 6 | echo $1 >> $compiled_type_test 7 | } 8 | compiled_type_exists () { 9 | [ "$1" = "aloha" ] && return 0 || return 1 10 | } 11 | 12 | setup () { 13 | example_include="$BORK_SCRIPT_DIR/types/git.sh" 14 | compiled_type_test=$(mktemp -t cttXXXXXX) 15 | } 16 | 17 | @test "is_compiling: returns 1 if not compiling" { 18 | run is_compiling 19 | [ "$status" -eq 1 ] 20 | } 21 | @test "is_compiling: returns 0 if compiling" { 22 | operation="compile" 23 | run is_compiling 24 | [ "$status" -eq 0 ] 25 | } 26 | 27 | @test "include_assertion: does nothing if not compiling" { 28 | run include_assertion 'hello' "$example_include" 29 | [ "$status" -eq 0 ] 30 | } 31 | 32 | @test "include_assertion: outputs function if compiling" { 33 | operation="compile" 34 | run include_assertion 'hello' "$example_include" 35 | [ "$status" -eq 0 ] 36 | git_length=$(cat $example_include | strip_blanks | wc -l | awk '{print $1}') 37 | (( git_length = $git_length + 3 )) 38 | [ "${#lines[*]}" -eq $git_length ] 39 | comment_leader="# $example_include" 40 | [ "${lines[0]}" = $comment_leader ] 41 | [ "${lines[1]}" = "type_hello () {" ] 42 | expected_first_line=" $(cat $example_include | strip_blanks | head -n 1)" 43 | [ "${lines[2]}" = "$expected_first_line" ] 44 | run cat $compiled_type_test 45 | [ "${#lines[*]}" -eq 1 ] 46 | [ "${lines[0]}" = "hello" ] 47 | } 48 | 49 | @test "include_assertion: does nothing if fn compiled already" { 50 | operation="compile" 51 | run include_assertion 'aloha' "$example_include" 52 | [ "$status" -eq 0 ] 53 | [ -z "$output" ] 54 | run cat $compiled_type_test 55 | [ -z "$output" ] 56 | } 57 | -------------------------------------------------------------------------------- /test/help-arguments.bats: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bats 2 | 3 | . test/helpers.sh 4 | 5 | @test "get echoes a value when present" { 6 | result=$(arguments get foo thing --foo=bar) 7 | [ "$result" = "bar" ] 8 | } 9 | 10 | @test "get echoes nothing when not present" { 11 | result=$(arguments get bar thing --foo=bar) 12 | [ -z "$result" ] 13 | } 14 | 15 | @test "get echoes 'true' when no equal sign" { 16 | result=$(arguments get foo --foo) 17 | [ "$result" = "true" ] 18 | } 19 | 20 | @test "unknown command returns 1" { 21 | run arguments 22 | [ "$status" -eq 1 ] 23 | } 24 | 25 | -------------------------------------------------------------------------------- /test/help-bag.bats: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bats 2 | 3 | . test/helpers.sh 4 | 5 | @test "bag: init creates variable" { 6 | bag init foo 7 | foo[0]="hello" 8 | [ "${#foo[*]}" -eq 1 ] 9 | } 10 | 11 | @test "bag: init clears existing variable" { 12 | foo=( one two three ) 13 | [ "${foo[0]}" = "one" ] 14 | bag init foo 15 | [ "${#foo[*]}" -eq 0 ] 16 | } 17 | 18 | @test "bag: push appends to stack" { 19 | bag init foo 20 | bag push foo "something else" 21 | bag push foo "algo mas" 22 | [ "${#foo[*]}" -eq 2 ] 23 | [ "${foo[0]}" = "something else" ] 24 | [ "${foo[1]}" = "algo mas" ] 25 | } 26 | 27 | @test "bag: read echoes the read item" { 28 | bag init foo 29 | bag push foo "something" 30 | [ "$(bag read foo)" = "something" ] 31 | bag push foo "algo mas" 32 | [ "$(bag read foo)" = "algo mas" ] 33 | } 34 | 35 | @test "bag: pop removes top item from stack" { 36 | bag init foo 37 | bag push foo "something" 38 | bag push foo "algo mas" 39 | [ "${#foo[*]}" -eq 2 ] 40 | bag pop foo 41 | [ "${#foo[*]}" -eq 1 ] 42 | } 43 | 44 | @test "bag: set appends key/value to stack" { 45 | bag init foo 46 | bag push foo "something" 47 | bag set foo something else 48 | bag set foo algo mas 49 | [ "${#foo[*]}" -eq 3 ] 50 | [ "${foo[1]}" = "something=else" ] 51 | [ "${foo[2]}" = "algo=mas" ] 52 | } 53 | 54 | @test "bag: set overwrites an existing key" { 55 | bag init foo 56 | bag set foo something else 57 | bag set foo algo mas 58 | bag set foo something more 59 | [ "${#foo[*]}" -eq 2 ] 60 | val=$(bag get foo something) 61 | [ "$val" = "more" ] 62 | } 63 | 64 | @test "bag: get returns a specified key" { 65 | bag init foo 66 | bag set foo something else 67 | bag set foo algo mas 68 | val=$(bag get foo something) 69 | [ "$val" = "else" ] 70 | } 71 | 72 | @test "bag: filter echoes all lines matching a pattern" { 73 | bag init foo 74 | bag push foo something 75 | bag set foo something else 76 | bag set foo algo mas 77 | bag push foo "something in the way she moves me" 78 | bag push foo "it's about the something" 79 | results=$(bag filter foo '^something') 80 | run echo "$results" 81 | [ "${#lines[*]}" -eq 3 ] 82 | [ "${lines[0]}" = "something" ] 83 | [ "${lines[1]}" = "something=else" ] 84 | [ "${lines[2]}" = "something in the way she moves me" ] 85 | } 86 | 87 | @test "bag: index echoes index of first matching item" { 88 | bag init foo 89 | bag push foo something 90 | bag push foo "algo mas" 91 | bag push foo "something=more" 92 | [ "$(bag index foo '^something')" -eq 0 ] 93 | [ "$(bag index foo 'more$')" -eq 2 ] 94 | } 95 | 96 | @test "bag: find echoes first line matching a pattern" { 97 | bag init foo 98 | bag push foo something 99 | bag push foo "algo mas" 100 | bag push foo "something in the way she moes me" 101 | result=$(bag find foo '^something') 102 | run echo "$result" 103 | [ "${#lines[*]}" -eq 1 ] 104 | [ "${lines[0]}" = "something" ] 105 | } 106 | 107 | @test "bag: print echoes each item line-by-line" { 108 | bag init foo 109 | bag push foo "{" 110 | bag push foo " key = value" 111 | bag push foo "}" 112 | run bag print foo 113 | [ "${#lines[*]}" -eq 3 ] 114 | [ "${lines[0]}" = "{" ] 115 | [ "${lines[1]}" = " key = value" ] 116 | [ "${lines[2]}" = "}" ] 117 | } 118 | -------------------------------------------------------------------------------- /test/help-http.bats: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bats 2 | 3 | . test/helpers.sh 4 | 5 | @test "http_head_cmd: with curl performs a head request" { 6 | respond_to "which curl" "echo /usr/bin/curl" 7 | url="https://foo.com" 8 | respond_to "curl -sI \"https://foo.com\"" "cat $fixtures/http-head-curl.txt" 9 | run http_head_cmd "$url" 10 | [ "$status" -eq 0 ] 11 | [[ 'curl -sI "https://foo.com"' == $output ]] 12 | } 13 | 14 | @test "http_header: extracting a header value" { 15 | input=$(cat "$fixtures/http-head-curl.txt") 16 | run http_header "Content-Length" "$input" 17 | [ "312" -eq $output ] 18 | } 19 | 20 | @test "htpp_get: getting a file" { 21 | url="https://foo.com/bar" 22 | target="/boo/baz" 23 | run http_get_cmd "$url" "$target" 24 | [ "$status" -eq 0 ] 25 | [[ "curl -so \"$target\" \"$url\" &> /dev/null" == $output ]] 26 | } 27 | -------------------------------------------------------------------------------- /test/help-md5.bats: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bats 2 | 3 | . test/helpers.sh 4 | 5 | @test "md5cmd Darwin echoes 'md5'" { 6 | run md5cmd Darwin 7 | [ "$status" -eq 0 ] 8 | p "$output" 9 | [ "$output" = "md5" ] 10 | } 11 | 12 | @test "md5cmd Darwin :file echoes 'md5 :file'" { 13 | run md5cmd Darwin Readme.md 14 | [ "$status" -eq 0 ] 15 | [ "$output" = "md5 -q Readme.md" ] 16 | } 17 | 18 | @test "md5cmd Linux echoes md5sum with awk" { 19 | run md5cmd Linux 20 | [ "$status" -eq 0 ] 21 | [ "$output" = "md5sum | awk '{print \$1}'" ] 22 | } 23 | 24 | @test "md5cmd Linux :file echoes md5sum :file with awk" { 25 | run md5cmd Linux Readme.md 26 | [ "$status" -eq 0 ] 27 | [ "$output" = "md5sum Readme.md | awk '{print \$1}'" ] 28 | } 29 | 30 | @test "md5cmd BSD returns 1" { 31 | run md5cmd BSD 32 | [ "$status" -eq 1 ] 33 | } 34 | -------------------------------------------------------------------------------- /test/help-system.bats: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bats 2 | 3 | . test/helpers.sh 4 | 5 | # == needs_exec 6 | @test "needs_exec: returns \$2 if exec found" { 7 | run needs_exec "cat" 0 8 | [ "$status" -eq 0 ] 9 | [ -z "$output" ] 10 | 11 | run needs_exec "cat" 11 12 | [ "$status" -eq 11 ] 13 | [ -z "$output" ] 14 | 15 | run needs_exec "cat" 16 | [ "$status" -eq 0 ] 17 | [ -z "$output" ] 18 | } 19 | 20 | @test "needs_exec: returns \$2 + 1 if exec not found, echoes message" { 21 | respond_to "which foo" "return 1" 22 | 23 | run needs_exec foo 0 24 | [ "$status" -eq 1 ] 25 | str_matches "$output" "^missing.*foo$" 26 | 27 | run needs_exec foo 11 28 | [ "$status" -eq 12 ] 29 | str_matches "$output" "^missing.*foo$" 30 | 31 | run needs_exec foo 32 | [ "$status" -eq 1 ] 33 | str_matches "$output" "^missing.*foo$" 34 | } 35 | 36 | # == is_platform 37 | @test "is_platform: returns 0 if argument is for platform" { 38 | expects=$(uname -s) 39 | run is_platform "$expects" 40 | [ "$status" -eq 0 ] 41 | } 42 | 43 | @test "is_platform: returns 1 if argument is not for platform" { 44 | run is_platform "HAL9000" 45 | [ "$status" -eq 1 ] 46 | } 47 | 48 | -------------------------------------------------------------------------------- /test/help-text.bats: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bats 2 | 3 | . test/helpers.sh 4 | 5 | # == Contains 6 | @test "contains: returns 0 for full matches of items in a list" { 7 | run str_contains "$(ls)" "Readme.md" 8 | [ "$status" -eq 0 ] 9 | } 10 | @test "contains: returns 1 when no full matches" { 11 | run str_contains "$(ls)" "Make" 12 | [ "$status" -eq 1 ] 13 | } 14 | 15 | # == Get Field 16 | @test "get_field: returns the field on a match" { 17 | result=$(str_get_field "foo bar bee" 2) 18 | [ "$result" = "bar" ] 19 | } 20 | 21 | # == Item Count 22 | @test "item_count: returns 0 for empty strings" { 23 | result=$(str_item_count "") 24 | [ $result -eq 0 ] 25 | } 26 | 27 | @test "item_count: counts items in the same line" { 28 | result=$(str_item_count "foo bar bee") 29 | [ $result -eq 3 ] 30 | } 31 | 32 | @test "item_count: counts items across lines" { 33 | str=$( 34 | echo "one two three" 35 | echo "four five six" 36 | ) 37 | result=$(str_item_count "$str") 38 | [ $result -eq 6 ] 39 | } 40 | 41 | # == matches 42 | @test "matches: returns 0 for regex matches of items in a list" { 43 | run str_matches "$(ls)" "^Read" 44 | [ "$status" -eq 0 ] 45 | } 46 | @test "matches: returns 1 on pattern misses" { 47 | run str_matches "$(ls)" "^Make$" 48 | [ "$status" -eq 1 ] 49 | } 50 | @test "matches: recognizes extended patterns" { 51 | run str_matches "not a symlink: FOO" "not a symlink.+FOO$" 52 | [ "$status" -eq 0 ] 53 | } 54 | @test "matches: recognizes backslash patterns" { 55 | run str_matches "D foo" '^\s?\w' 56 | [ "$status" -eq 0 ] 57 | run str_matches " D foo" "^\\s?\\w" 58 | [ "$status" -eq 0 ] 59 | } 60 | 61 | # == replace 62 | @test "replace: replaces the matched pattern with the result" { 63 | result=$(str_replace "foobobaz" "o*b" "at") 64 | [ "$result" = "fatataz" ] 65 | } 66 | 67 | @test "replace: returns the input when the pattern does not match" { 68 | result=$(str_replace "foobar" "aa" "bb") 69 | [ "$result" = "foobar" ] 70 | } 71 | -------------------------------------------------------------------------------- /test/helpers.sh: -------------------------------------------------------------------------------- 1 | . bin/bork load 2 | 3 | here=$PWD 4 | debug_mode="$DEBUG" 5 | p () { 6 | [ -n "$debug_mode" ] && echo "$*" >> "$here/debug" 7 | return 0 8 | } 9 | 10 | md5c=$(md5cmd $platform) 11 | baking_responder= 12 | baking_file=$(mktemp -t bork_test.XXXXXX) 13 | bake () { 14 | echo "$*" >> $baking_file; 15 | key=$(echo "$*" | eval $md5c) 16 | handler=$(bag get responders $key) 17 | p "looking up $* at $key, found $handler" 18 | if [ -n "$handler" ]; then 19 | eval $handler 20 | else 21 | baking_responder $* 22 | fi 23 | return 24 | } 25 | # overwrite this in your tests 26 | baking_responder () { :; } 27 | 28 | baked_output () { cat $baking_file; } 29 | 30 | fixtures="$BORK_SOURCE_DIR/test/fixtures" 31 | 32 | bag init responders 33 | respond_to () { 34 | key=$(echo "$1" | eval $md5c) 35 | p "setting $1 at $key" 36 | bag set responders "$key" "$2" 37 | } 38 | return_with () { return $1; } 39 | 40 | -------------------------------------------------------------------------------- /test/type-apt.bats: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bats 2 | 3 | . test/helpers.sh 4 | 5 | apt () { . $BORK_SOURCE_DIR/types/apt.sh $*; } 6 | 7 | setup () { 8 | respond_to "uname -s" "echo Linux" 9 | respond_to "dpkg --get-selections" "cat $fixtures/apt-dpkg-dependencies.txt" 10 | respond_to "sudo apt-get upgrade --dry-run" "cat $fixtures/apt-upgrade-dry.txt" 11 | } 12 | 13 | @test "apt status reports incorrect platform" { 14 | respond_to "uname -s" "echo Darwin" 15 | run apt status some_package 16 | [ "$status" -eq $STATUS_UNSUPPORTED_PLATFORM ] 17 | } 18 | 19 | @test "apt status reports missing apt-get" { 20 | respond_to "which apt-get" "return 1" 21 | run apt status some_package 22 | [ "$status" -eq $STATUS_FAILED_PRECONDITION ] 23 | } 24 | @test "apt status reports missing dpkg" { 25 | respond_to "which dpkg" "return 1" 26 | run apt status some_package 27 | [ "$status" -eq $STATUS_FAILED_PRECONDITION ] 28 | } 29 | 30 | @test "apt status reports a package is missing" { 31 | run apt status missing_package 32 | [ "$status" -eq $STATUS_MISSING ] 33 | } 34 | 35 | @test "apt status reports a package is outdated" { 36 | run apt status outdated_package 37 | [ "$status" -eq $STATUS_OUTDATED ] 38 | } 39 | 40 | @test "apt status reports a package is current" { 41 | run apt status current_package 42 | [ "$status" -eq $STATUS_OK ] 43 | } 44 | 45 | @test "apt install runs 'apt-get install'" { 46 | run apt install missing_package 47 | [ "$status" -eq $STATUS_OK ] 48 | run baked_output 49 | [ "$output" = 'sudo apt-get --yes install missing_package' ] 50 | } 51 | 52 | @test "apt upgrade runs 'apt-get upgrade'" { 53 | run apt upgrade outdated_package 54 | [ "$status" -eq $STATUS_OK ] 55 | run baked_output 56 | [ "$output" = 'sudo apt-get --yes install outdated_package' ] 57 | } 58 | 59 | -------------------------------------------------------------------------------- /test/type-brew-tap.bats: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bats 2 | 3 | . test/helpers.sh 4 | brew-tap () { . $BORK_SOURCE_DIR/types/brew-tap.sh $*; } 5 | 6 | setup () { 7 | respond_to "uname -s" "echo Darwin" 8 | respond_to "brew tap" "cat $fixtures/brew-tap-list.txt" 9 | respond_to "brew tap --list-pinned" "cat $fixtures/brew-tap-pinned.txt" 10 | } 11 | 12 | @test "brew-tap status reports missing when untapped" { 13 | run brew-tap status some/tap 14 | [ "$status" -eq $STATUS_MISSING ] 15 | } 16 | 17 | @test "brew-tap status reports partial when installed but missing pin status" { 18 | run brew-tap status railwaycat/emacsmacport --pin 19 | [ "$status" -eq $STATUS_PARTIAL ] 20 | } 21 | 22 | @test "brew-tap status reports partial when installed but has pin status when it shouldn't" { 23 | run brew-tap status homebrew/games 24 | [ "$status" -eq $STATUS_PARTIAL ] 25 | } 26 | 27 | @test "brew-tap status reports ok when installed has correct no-pin status" { 28 | run brew-tap status railwaycat/emacsmacport 29 | [ "$status" -eq $STATUS_OK ] 30 | } 31 | 32 | @test "brew-tap status reports ok when provided tap name has capitals" { 33 | run brew-tap status Caskroom/cask 34 | [ "$status" -eq $STATUS_OK ] 35 | } 36 | 37 | @test "brew-tap status reports ok when installed has correct yes-pin status" { 38 | run brew-tap status homebrew/games --pin 39 | [ "$status" -eq $STATUS_OK ] 40 | } 41 | 42 | 43 | @test "brew-tap install installs tap" { 44 | run brew-tap install homebrew/science 45 | [ "$status" -eq 0 ] 46 | run baked_output 47 | [ "$output" = 'brew tap homebrew/science' ] 48 | } 49 | 50 | @test "brew-tap install installs tap with pin" { 51 | run brew-tap install homebrew/science --pin 52 | [ "$status" -eq 0 ] 53 | run baked_output 54 | [[ "brew tap homebrew/science" == ${lines[0]} ]] 55 | [[ "brew tap-pin homebrew/science" == ${lines[1]} ]] 56 | } 57 | 58 | @test "brew-tap upgrade with pin adds pin" { 59 | run brew-tap upgrade homebrew/science --pin 60 | [ "$status" -eq 0 ] 61 | run baked_output 62 | [ "$output" = "brew tap-pin homebrew/science" ] 63 | } 64 | 65 | @test "brew-tap upgrade without pin remvoes pin" { 66 | run brew-tap upgrade homebrew/science 67 | [ "$status" -eq 0 ] 68 | run baked_output 69 | [ "$output" = "brew tap-unpin homebrew/science" ] 70 | } 71 | -------------------------------------------------------------------------------- /test/type-brew.bats: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bats 2 | 3 | . test/helpers.sh 4 | brew () { . $BORK_SOURCE_DIR/types/brew.sh $*; } 5 | 6 | setup () { 7 | respond_to "uname -s" "echo Darwin" 8 | respond_to "brew list" "cat $fixtures/brew-list.txt" 9 | respond_to "brew outdated" "cat $fixtures/brew-outdated.txt" 10 | } 11 | 12 | @test "brew status reports unsupported platform" { 13 | respond_to "uname -s" "echo Linux" 14 | run brew status something 15 | [ "$status" -eq $STATUS_UNSUPPORTED_PLATFORM ] 16 | } 17 | 18 | @test "brew status reports missing brew exec" { 19 | respond_to "which brew" "return 1" 20 | run brew status something 21 | [ "$status" -eq $STATUS_FAILED_PRECONDITION ] 22 | } 23 | 24 | @test "brew status reports a package is missing" { 25 | run brew status missing_package_is_missing 26 | [ "$status" -eq $STATUS_MISSING ] 27 | } 28 | 29 | @test "brew status reports a package is outdated" { 30 | run brew status outdated_package 31 | [ "$status" -eq $STATUS_OUTDATED ] 32 | } 33 | 34 | @test "brew status reports a packge is current" { 35 | run brew status current_package 36 | [ "$status" -eq $STATUS_OK ] 37 | [ "${#lines[*]}" -eq 0 ] 38 | } 39 | 40 | @test "brew install runs 'install'" { 41 | run brew install missing_package_is_missing 42 | [ "$status" -eq 0 ] 43 | run baked_output 44 | [ "$output" = 'brew install missing_package_is_missing' ] 45 | } 46 | 47 | @test "brew upgrade runs 'upgrade'" { 48 | run brew upgrade outdated_package 49 | [ "$status" -eq 0 ] 50 | run baked_output 51 | [ "$output" = 'brew upgrade outdated_package' ] 52 | } 53 | 54 | -------------------------------------------------------------------------------- /test/type-cask.bats: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bats 2 | 3 | . test/helpers.sh 4 | cask () { . $BORK_SOURCE_DIR/types/cask.sh $*; } 5 | 6 | setup () { 7 | respond_to "uname -s" "echo Darwin" 8 | respond_to "which brew" "echo /usr/local/bin/brew" 9 | respond_to "brew cask list" "cat $fixtures/cask-list.txt" 10 | } 11 | 12 | @test "cask statups reports unsupported platforms" { 13 | respond_to "uname -s" "echo Linux" 14 | run cask status something 15 | [ "$status" -eq $STATUS_UNSUPPORTED_PLATFORM ] 16 | } 17 | 18 | @test "cask status reports missing brew exec" { 19 | respond_to "which brew" "return 1" 20 | run cask status something 21 | [ "$status" -eq $STATUS_FAILED_PRECONDITION ] 22 | } 23 | 24 | @test "cask status reports missing cask package" { 25 | respond_to "brew cask --version" "return 1" 26 | run cask status something 27 | [ "$status" -eq $STATUS_FAILED_PRECONDITION ] 28 | } 29 | 30 | @test "cask status reports an app is missing" { 31 | run cask status missing_app 32 | [ "$status" -eq $STATUS_MISSING ] 33 | } 34 | 35 | @test "cask status reports an app is current" { 36 | run cask status installed_app 37 | [ "$status" -eq $STATUS_MISSING ] 38 | } 39 | 40 | @test "cask status reports an app is outdated" { 41 | respond_to "brew cask info installed_package" "cat $fixtures/cask-outdated-info.txt" 42 | run cask status installed_package 43 | [ "$status" -eq $STATUS_OUTDATED ] 44 | } 45 | 46 | @test "cask install runs 'install'" { 47 | run cask install missing_package 48 | [ "$status" -eq 0 ] 49 | run baked_output 50 | [ "$output" = 'brew cask install missing_package' ] 51 | } 52 | 53 | @test "cask upgrade performs a force install and cleans up old versions" { 54 | run cask upgrade installed_package 55 | [ "$status" -eq 0 ] 56 | run baked_output 57 | [ "${lines[0]}" = "rm -rf /opt/homebrew-cask/Caskroom/installed_package" ] 58 | [ "${lines[1]}" = "brew cask install installed_package --force" ] 59 | } 60 | -------------------------------------------------------------------------------- /test/type-defaults.bats: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bats 2 | 3 | . test/helpers.sh 4 | defaults () { . $BORK_SOURCE_DIR/types/defaults.sh $*; } 5 | 6 | @test "defaults status: returns FAILED_PRECODITION without defaults exec" { 7 | respond_to "which defaults" "return 1" 8 | run defaults status foodomain fookey string bar 9 | [ "$status" -eq $STATUS_FAILED_PRECONDITION ] 10 | } 11 | 12 | @test "defaults status: returns MISSING if no value for domain/key" { 13 | respond_to "defaults read MissingDomain MissingKey" \ 14 | "echo '2014-06-29 13:47:17.155 defaults[31031:507]'; echo 'The domain/default pair of \(MissingDomain, MissingKey\) does not exist'; return 1" 15 | run defaults status MissingDomain MissingKey string something 16 | [ "$status" -eq $STATUS_MISSING ] 17 | } 18 | 19 | @test "defaults status: returns MISMATCH_UPGRADE when existing type doesn't match" { 20 | respond_to "defaults read-type NSGlobalDomain AppleEnableMenuBarTransparency" "echo 'Type is boolean'" 21 | run defaults status NSGlobalDomain AppleEnableMenuBarTransparency integer 0 22 | [ "$status" -eq $STATUS_MISMATCH_UPGRADE ] 23 | } 24 | 25 | @test "defaults status: returns MISMATCH_UPGRADE when existing value doesn't match" { 26 | respond_to "defaults read-type NSGlobalDomain AppleEnableMenuBarTransparency" "echo 'Type is boolean'" 27 | respond_to "defaults read NSGlobalDomain AppleEnableMenuBarTransparency" "echo 0" 28 | run defaults status NSGlobalDomain AppleEnableMenuBarTransparency bool true 29 | [ "$status" -eq $STATUS_MISMATCH_UPGRADE ] 30 | } 31 | 32 | @test "defaults status: returns OK when existing type and value matches" { 33 | respond_to "defaults read-type NSGlobalDomain AppleEnableMenuBarTransparency" "echo 'Type is boolean'" 34 | respond_to "defaults read NSGlobalDomain AppleEnableMenuBarTransparency" "echo 0" 35 | run defaults status NSGlobalDomain AppleEnableMenuBarTransparency bool false 36 | p "bool" 37 | p $output 38 | [ "$status" -eq $STATUS_OK ] 39 | } 40 | 41 | @test "defaults status: returns OK when type is int and value matches" { 42 | respond_to "defaults read-type NSGlobalDomain NSTableViewDefaultSizeMode" "echo 'Type is integer'" 43 | respond_to "defaults read NSGlobalDomain NSTableViewDefaultSizeMode" "echo 2" 44 | run defaults status NSGlobalDomain NSTableViewDefaultSizeMode int 2 45 | p "int" 46 | p $output 47 | [ "$status" -eq $STATUS_OK ] 48 | } 49 | 50 | @test "defaults status: returns OK when type is dict and value matches" { 51 | respond_to "defaults read-type com.runningwithcrayons.Alfred-Preferences hotkey.default" "echo 'Type is dictionary'" 52 | respond_to "defaults read com.runningwithcrayons.Alfred-Preferences hotkey.default" "cat $fixtures/defaults-dictionary-value.txt" 53 | run defaults status com.runningwithcrayons.Alfred-Preferences hotkey.default dict key -int 49 mod -int 1048576 string space 54 | [ "$status" -eq $STATUS_OK ] 55 | } 56 | 57 | @test "defaults upgrade: runs defaults write with: \$domain \$key -\$type \$value" { 58 | run defaults upgrade NSGlobalDomain AppleEnableMenuBarTransparency bool false 59 | [ "$status" -eq 0 ] 60 | run baked_output 61 | [ "$output" = "defaults write NSGlobalDomain AppleEnableMenuBarTransparency -bool false" ] 62 | } 63 | 64 | @test "defaults install|upgrade: handles dict with proper args" { 65 | run defaults install com.runningwithcrayons.Alfred-Preferences hotkey.default dict key -int 49 mod -int 1048576 string Space 66 | [ "$status" -eq 0 ] 67 | run baked_output 68 | [ "$output" = "defaults write com.runningwithcrayons.Alfred-Preferences hotkey.default -dict key -int 49 mod -int 1048576 string Space" ] 69 | } 70 | -------------------------------------------------------------------------------- /test/type-directories.bats: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bats 2 | 3 | . test/helpers.sh 4 | directory () { . $BORK_SOURCE_DIR/types/directory.sh $*; } 5 | 6 | # these tests use live directories in a tempdir 7 | baking_responder () { eval "$*"; } 8 | 9 | setup () { 10 | tmpdir=$(mktemp -d -t bork-dirXXXXXX) 11 | cd $tmpdir 12 | } 13 | teardown () { 14 | rm -rf $tmpdir 15 | } 16 | 17 | mkdirs () { 18 | for d in $*; do mkdir -p $d; done 19 | } 20 | 21 | @test "directory: status returns OK if directory is present" { 22 | mkdirs foo 23 | run directory status foo 24 | [ "$status" -eq $STATUS_OK ] 25 | } 26 | 27 | @test "directory: status returns MISSING if directory isn't present" { 28 | run directory status foo 29 | [ "$status" -eq $STATUS_MISSING ] 30 | } 31 | 32 | @test "directory: status returns CONFLICT_CLOBBER if target is non-directory" { 33 | echo "FOO" > foo 34 | run directory status foo 35 | [ "$status" -eq $STATUS_CONFLICT_CLOBBER ] 36 | str_matches "${lines[0]}" "exists" 37 | } 38 | 39 | @test "directory: install creates target directory" { 40 | run directory install foo 41 | [ "$status" -eq 0 ] 42 | run baked_output 43 | [ "${lines[0]}" = "install -C -d foo" ] 44 | } 45 | -------------------------------------------------------------------------------- /test/type-directory-perms.bats: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bats 2 | 3 | . test/helpers.sh 4 | directory () { . $BORK_SOURCE_DIR/types/directory.sh $*; } 5 | 6 | setup() { 7 | respond_to "stat --printf %U\\\n%G\\\n%a foo" "printf '%s\n%s\n%s' user users 755" 8 | } 9 | 10 | @test "directory: status returns MISMATCH_UPGRADE when target directory has incorrect owner" { 11 | run directory status foo --owner=bork 12 | (( status == STATUS_MISMATCH_UPGRADE )) 13 | [[ ${lines[0]} == 'expected owner: bork' ]] 14 | [[ ${lines[1]} == 'received owner: user' ]] 15 | } 16 | 17 | @test "directory: status returns MISMATCH_UPGRADE when target directory has incorrect group" { 18 | run directory status foo --group=bork 19 | (( status == STATUS_MISMATCH_UPGRADE )) 20 | [[ ${lines[0]} == 'expected group: bork' ]] 21 | [[ ${lines[1]} == 'received group: users' ]] 22 | } 23 | 24 | @test "directory: status returns MISMATCH_UPGRADE when target directory has incorrect mode" { 25 | run directory status foo --mode=700 26 | (( status == STATUS_MISMATCH_UPGRADE )) 27 | [[ ${lines[0]} == 'expected mode: 700' ]] 28 | [[ ${lines[1]} == 'received mode: 755' ]] 29 | } 30 | 31 | @test "directory: status returns OK when everything matches" { 32 | run directory status foo --owner=user --group=users --mode=755 33 | (( status == STATUS_OK )) 34 | } 35 | 36 | @test "directory: install sets owner for directory" { 37 | run directory install foo --owner=bork 38 | (( status == 0 )) 39 | run baked_output 40 | [[ ${lines[-1]} =~ ^sudo\ install ]] 41 | [[ ${lines[-1]} =~ '-o bork' ]] 42 | } 43 | 44 | @test "directory: install sets group for directory" { 45 | run directory install foo --group=bork 46 | (( status == 0 )) 47 | run baked_output 48 | [[ ${lines[-1]} =~ ^sudo\ install ]] 49 | [[ ${lines[-1]} =~ '-g bork' ]] 50 | } 51 | 52 | @test "directory: install sets mode for directory" { 53 | run directory install foo --mode=700 54 | (( status == 0 )) 55 | run baked_output 56 | [[ ${lines[-1]} =~ ^install ]] 57 | [[ ${lines[-1]} =~ '-m 700' ]] 58 | } 59 | -------------------------------------------------------------------------------- /test/type-download.bats: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bats 2 | 3 | . test/helpers.sh 4 | 5 | download () { . $BORK_SOURCE_DIR/types/download.sh $*; } 6 | 7 | @test "download status: when file is MISSING" { 8 | respond_to "[ -f \"missing\" ]" "return 1" 9 | run download status missing "http://foo.com" 10 | [ "$status" -eq $STATUS_MISSING ] 11 | } 12 | 13 | @test "download status: without comparisons returns OK when file exists" { 14 | target="foo/bar.txt" 15 | respond_to "curl -sI \"http://foo.com/bar.txt\"" "return 1" 16 | run download status "$target" "http://foo.com/bar.txt" 17 | [ "$status" -eq $STATUS_OK ] 18 | } 19 | 20 | @test "download status: returns CONFLICT_UPGRADE when comparing size and it doesn't match" { 21 | target="foo/bar.txt" 22 | respond_to "ls -al \"foo/bar.txt\"" \ 23 | "echo '-rw-r--r-- 1 mattly staff 11091 Mar 21 12:55 bar.txt'" 24 | respond_to "curl -sI \"http://foo.com/bar.txt\"" "cat $fixtures/http-head-curl.txt" 25 | run download status "$target" "http://foo.com/bar.txt" --size 26 | [ "$status" -eq $STATUS_CONFLICT_UPGRADE ] 27 | [ "${#lines[*]}" -eq 2 ] 28 | } 29 | 30 | @test "download status: returns OK when conditions match" { 31 | target="foo/bar.txt" 32 | respond_to "ls -al \"foo/bar.txt\"" \ 33 | "echo '-rw-r--r-- 1 mattly staff 312 Mar 21 12:55 bar.txt'" 34 | respond_to "curl -sI \"http://foo.com/bar.txt\"" "cat $fixtures/http-head-curl.txt" 35 | run download status "$target" "http://foo.com/bar.txt" --size 36 | [ "$status" -eq $STATUS_OK ] 37 | } 38 | 39 | @test "download install: gets from remote" { 40 | target="foo/bar.txt" 41 | run download install "$target" "http://foo.com/bar.txt" 42 | [ "$status" -eq $STATUS_OK ] 43 | run baked_output 44 | expected="curl -so \"$target\" \"http://foo.com/bar.txt\" &> /dev/null" 45 | [[ "${lines[1]}" = $expected ]] 46 | } 47 | -------------------------------------------------------------------------------- /test/type-file.bats: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bats 2 | 3 | . test/helpers.sh 4 | 5 | file () { . $BORK_SOURCE_DIR/types/file.sh $*; } 6 | 7 | setup () { 8 | readsum=$(eval $(md5cmd $platform Readme.md)) 9 | } 10 | 11 | # -- without arguments ------------------------------- 12 | @test "file status: returns MISSING when file is missing" { 13 | respond_to "[ -f missing ]" "return 1" 14 | run file status missing Readme.md 15 | [ "$status" -eq $STATUS_MISSING ] 16 | } 17 | 18 | @test "file status: returns FAILED_ARGUMENTS when source file is missing" { 19 | run file status somefile missingfile 20 | [ "$status" -eq $STATUS_FAILED_ARGUMENTS ] 21 | } 22 | 23 | @test "file status: returns CONFLICT_UPGRADE when sum doesn't match" { 24 | respond_to "$(md5cmd $platform wrongfile)" "echo 123456" 25 | run file status wrongfile Readme.md 26 | [ "$status" -eq $STATUS_CONFLICT_UPGRADE ] 27 | expected="expected sum: $readsum" 28 | [[ ${lines[0]} == $expected ]] 29 | [[ ${lines[1]} == "received sum: 123456" ]] 30 | } 31 | 32 | @test "file status: returns OK when all is well" { 33 | respond_to "$(md5cmd $platform goodfile)" "echo $readsum" 34 | run file status goodfile Readme.md 35 | [ "$status" -eq $STATUS_OK ] 36 | } 37 | 38 | @test "file install: creates directory, copies file" { 39 | run file install path/to/target path/from/source 40 | [ "$status" -eq 0 ] 41 | run baked_output 42 | [ "${lines[0]}" = "mkdir -p path/to" ] 43 | [ "${lines[1]}" = "cp path/from/source path/to/target" ] 44 | } 45 | 46 | @test "file install: ignores directory if not present" { 47 | run file install target source 48 | [ "$status" -eq 0 ] 49 | run baked_output 50 | [ "${lines[0]}" = "cp source target" ] 51 | } 52 | 53 | # -- with permission argument ------------------------ 54 | @test "file status: returns MISMATCH_UPGRADE when target file has incorrect permissions" { 55 | respond_to "$(md5cmd $platform tfile)" "echo $readsum" 56 | respond_to "$(permission_cmd $platform) tfile" "echo 755" 57 | run file status tfile Readme.md --permissions=700 58 | [ "$status" -eq $STATUS_MISMATCH_UPGRADE ] 59 | [ "${lines[0]}" = "expected permissions: 700" ] 60 | [ "${lines[1]}" = "received permissions: 755" ] 61 | } 62 | 63 | @test "file install: sets permissions for file after copying" { 64 | run file install target path/from/source --permissions=700 65 | [ "$status" -eq 0 ] 66 | run baked_output 67 | [ "${lines[1]}" = "chmod 700 target" ] 68 | } 69 | 70 | # -- with owner argument ----------------------------- 71 | @test "file status: returns FAILED_ARGUMENT_PRECONDITION when target user doesn't exist" { 72 | respond_to "id -u kermit" "echo 'id: kermit: no such user'; return 1" 73 | run file status target Readme.md --owner=kermit 74 | [ "$status" -eq $STATUS_FAILED_ARGUMENT_PRECONDITION ] 75 | [ "${lines[0]}" = "unknown owner: kermit" ] 76 | } 77 | 78 | @test "file status: returns MISMATCH_UPGRADE when target file has incorrect owner" { 79 | respond_to "sudo $(md5cmd $platform target)" "echo $readsum" 80 | respond_to "sudo ls -l target" "echo -rw-r--r-- 1 kermit staff 4604" 81 | run file status target Readme.md --owner=bork 82 | [ "$status" -eq $STATUS_MISMATCH_UPGRADE ] 83 | [ "${lines[0]}" = "expected owner: bork" ] 84 | [ "${lines[1]}" = "received owner: kermit" ] 85 | } 86 | 87 | @test "file status: returns OK with owner and all is well" { 88 | respond_to "sudo $(md5cmd $platform target)" "echo $readsum" 89 | respond_to "sudo ls -l target" "echo -rw-r--r-- 1 kermit staff 4604" 90 | run file status target Readme.md --owner=kermit 91 | [ "$status" -eq $STATUS_OK ] 92 | } 93 | 94 | @test "file install: copies file as correct user" { 95 | run file install path/to/target path/from/source --owner=kermit 96 | [ "$status" -eq 0 ] 97 | run baked_output 98 | [ "${lines[0]}" = "sudo mkdir -p path/to" ] 99 | [ "${lines[1]}" = "sudo chown kermit path/to" ] 100 | [ "${lines[2]}" = "sudo cp path/from/source path/to/target" ] 101 | [ "${lines[3]}" = "sudo chown kermit path/to/target" ] 102 | } 103 | 104 | # --- compile ---------------------------------------- 105 | @test "file compile: echoes base64 representation to screen" { 106 | run file compile path/to/target Readme.md 107 | [ "$status" -eq 0 ] 108 | expected="borkfiles__UmVhZG1lLm1kCg=\"$(base64 Readme.md)\"" 109 | accum="${lines[2]}" 110 | line=2 111 | while [ "$line" -lt ${#lines[*]} ]; do 112 | (( line++ )) 113 | accum=$(echo "$accum"; echo "${lines[line]}") 114 | done 115 | [[ "$accum" = $expected ]] 116 | } 117 | 118 | @test "file compile: outputs warning to stderr on missing file" { 119 | run file compile target foo-missing 120 | [ "$status" -eq 1 ] 121 | [ "$output" = "fatal: file 'foo-missing' does not exist!" ] 122 | } 123 | 124 | is_compiled () { [ -n "$is_compiled" ]; } 125 | 126 | @test "file status: if compiled, uses stored variable" { 127 | is_compiled=1 128 | borkfiles__cGF0aC9mcm9tL3NvdXJjZQo="$(base64 Readme.md)" 129 | respond_to "$(md5cmd $platform path/to/target)" "echo $readsum" 130 | run file status path/to/target path/from/source 131 | [ "$status" -eq $STATUS_OK ] 132 | } 133 | 134 | @test "file install: if compiled, uses stored variable" { 135 | is_compiled=1 136 | borkfiles__cGF0aC9mcm9tL3NvdXJjZQo="$(base64 Readme.md)" 137 | run file install path/to/target path/from/source 138 | [ "$status" -eq $STATUS_OK ] 139 | run baked_output 140 | expected="echo \"$borkfiles__cGF0aC9mcm9tL3NvdXJjZQo\" | base64 --decode > path/to/target" 141 | expected=$(echo $expected) 142 | [[ "${lines[1]}" = $expected ]] 143 | } 144 | -------------------------------------------------------------------------------- /test/type-gem.bats: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bats 2 | 3 | . test/helpers.sh 4 | gem () { . $BORK_SOURCE_DIR/types/gem.sh $*; } 5 | 6 | setup () { 7 | respond_to "gem list" "cat $fixtures/gem-list.txt" 8 | } 9 | 10 | @test "gem status: returns FAILED_PRECONDITION without gem exec" { 11 | respond_to "which gem" "return 1" 12 | run gem status foo 13 | [ "$status" -eq $STATUS_FAILED_PRECONDITION ] 14 | } 15 | 16 | @test "gem status: returns MISSING if gem isn't installed" { 17 | run gem status baz 18 | [ "$status" -eq $STATUS_MISSING ] 19 | } 20 | 21 | @test "gem status: returns OK if gem is installed" { 22 | run gem status foo 23 | [ "$status" -eq $STATUS_OK ] 24 | } 25 | 26 | @test "gem install: performs the installation" { 27 | run gem install foo 28 | [ "$status" -eq 0 ] 29 | run baked_output 30 | [ "${lines[0]}" = "sudo gem install foo" ] 31 | } 32 | -------------------------------------------------------------------------------- /test/type-git.bats: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bats 2 | 3 | . test/helpers.sh 4 | git () { . $BORK_SOURCE_DIR/types/git.sh $*; } 5 | 6 | repo="git@github.com:mattly/bork" 7 | dir_exists=1 8 | dir_listing=$(echo 'foo'; echo 'bar') 9 | git_status= 10 | setup () { 11 | respond_to "[ ! -d bork ]" "dir_exists_handler" 12 | respond_to "ls -A bork" "dir_listing_handler" 13 | respond_to "git status -uno -b --porcelain" "git_status_handler" 14 | } 15 | dir_exists_handler () { [ "$dir_exists" -eq 0 ]; } 16 | dir_listing_handler () { echo "$dir_listing"; } 17 | git_status_handler () { echo "$git_status"; } 18 | 19 | @test "git status: returns FAILED_PRECONDITION when git exec is missing" { 20 | respond_to "which git" "return 1" 21 | run git status $repo 22 | [ "$status" -eq $STATUS_FAILED_PRECONDITION ] 23 | } 24 | 25 | @test "git status: returns MISSING when the directory doesn't exist" { 26 | dir_exists=0 27 | run git status $repo 28 | [ "$status" -eq $STATUS_MISSING ] 29 | } 30 | 31 | @test "git status: returns MISSING when the directory is empty" { 32 | dir_listing='' 33 | run git status $repo 34 | [ "$status" -eq $STATUS_MISSING ] 35 | } 36 | 37 | @test "git status: returns MISSING with directory argument for directory that doesn't exist" { 38 | respond_to "ls -A ~/code/bork" "return 1" 39 | run git status ~/code/bork $repo 40 | [ "$status" -eq $STATUS_MISSING ] 41 | } 42 | 43 | @test "git status: returns CONFLICT_CLOBBER when the directory is not empty and not a git repository" { 44 | respond_to "git fetch" "return_with 1" 45 | run git status $repo 46 | [ "$status" -eq $STATUS_CONFLICT_CLOBBER ] 47 | echo "$output" | grep -E "bork exists" 48 | } 49 | 50 | @test "git status: returns MISMATCH_UPGRADE when not on the desired branch" { 51 | git_status="## foobar" 52 | run git status $repo 53 | [ "$status" -eq $STATUS_MISMATCH_UPGRADE ] 54 | echo "$output" | grep -E 'incorrect branch' 55 | } 56 | 57 | @test "git status: returns MISMATCH_UPGRADE when the local git repository uses another origin" { 58 | skip 59 | } 60 | 61 | @test "git status: returns CONFLICT_UPGRADE when the local git repository is ahead" { 62 | git_status="## master..origin/master [ahead 3]" 63 | run git status $repo 64 | [ "$status" -eq $STATUS_CONFLICT_UPGRADE ] 65 | echo "$output" | grep -E 'is ahead' 66 | } 67 | 68 | @test "git status: returns CONFLICT_UPGRADE when the local git repository has unstaged changes" { 69 | git_status=$(echo "## master"; echo " D foo") 70 | run git status git@github.com:mattly/bork 71 | [ "$status" -eq $STATUS_CONFLICT_UPGRADE ] 72 | echo "$output" | grep -E 'uncommitted' 73 | } 74 | 75 | @test "git status: returns CONFLICT_UPGRADE when local git repository has uncommitted staged changes" { 76 | git_status=$(echo "## master"; echo "D foo") 77 | run git status git@github.com:mattly/bork 78 | [ "$status" -eq $STATUS_CONFLICT_UPGRADE ] 79 | echo "$output" | grep -E 'uncommitted' 80 | } 81 | 82 | @test "git status: returns OUTDATED when the local git repository is known to be behind" { 83 | git_status="## master..origin/master [behind 3]" 84 | run git status git@github.com:mattly/bork 85 | [ "$status" -eq $STATUS_OUTDATED ] 86 | } 87 | 88 | @test "git status: returns OK when the git repository is up-to-date" { 89 | git_status="## master" 90 | run git status git@github.com:mattly/bork 91 | [ "$status" -eq $STATUS_OK ] 92 | } 93 | 94 | @test "git status: returns OK with directory argument and repo is current" { 95 | respond_to "[ ! -d /Users/mattly/code/bork ]" "return 1" 96 | respond_to "ls -A /Users/mattly/code/bork" "dir_listing_handler" 97 | git_status="## master" 98 | run git status /Users/mattly/code/bork git@github.com:mattly/bork 99 | [ "$status" -eq $STATUS_OK ] 100 | } 101 | 102 | @test "git install: bakes target dir, git clone" { 103 | run git install $repo 104 | [ "$status" -eq 0 ] 105 | run baked_output 106 | [[ "mkdir -p bork" == ${lines[0]} ]] 107 | [[ "git clone -b master $repo bork" == ${lines[1]} ]] 108 | } 109 | 110 | @test "git install: with target argument, performs clone" { 111 | run git install /Users/mattly/code/bork $repo 112 | [ "$status" -eq 0 ] 113 | run baked_output 114 | [[ "mkdir -p /Users/mattly/code/bork" == ${lines[0]} ]] 115 | [[ "git clone -b master $repo /Users/mattly/code/bork" == ${lines[1]} ]] 116 | } 117 | 118 | @test "git install: uses specified branch" { 119 | run git install $repo --branch=experimental 120 | [ "$status" -eq 0 ] 121 | run baked_output 122 | [[ "mkdir -p bork" == ${lines[0]} ]] 123 | [[ "git clone -b experimental $repo bork" == ${lines[1]} ]] 124 | } 125 | 126 | @test "git upgrade: merges to new ref, echoes changelog" { 127 | run git upgrade $repo 128 | [ "$status" -eq 0 ] 129 | run baked_output 130 | [[ "cd bork" == ${lines[0]} ]] 131 | [[ "git reset --hard" == ${lines[1]} ]] 132 | [[ "git pull" == ${lines[2]} ]] 133 | [[ "git checkout master" == ${lines[3]} ]] 134 | [[ "git log HEAD@{2}.." == ${lines[4]} ]] 135 | } 136 | -------------------------------------------------------------------------------- /test/type-github.bats: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bats 2 | 3 | . test/helpers.sh 4 | github () { . $BORK_SOURCE_DIR/types/github.sh $*; } 5 | 6 | intercept_git () { echo "$*"; } 7 | git_call="intercept_git" 8 | 9 | @test "github status: handles implicit target" { 10 | run github status mattly/bork 11 | [ "$output" = "status https://github.com/mattly/bork.git" ] 12 | } 13 | 14 | @test "github status: handles explicit target" { 15 | run github status /Users/mattly/code/bork mattly/bork 16 | [ "$output" = "status /Users/mattly/code/bork https://github.com/mattly/bork.git" ] 17 | } 18 | 19 | @test "github status: handles --ssh argument" { 20 | run github status mattly/bork --ssh 21 | [ "$output" = "status git@github.com:mattly/bork.git" ] 22 | } 23 | 24 | @test "github compile: outputs git type via include_assertion" { 25 | operation="compile" 26 | gitfn=$(include_assertion git $BORK_SOURCE_DIR/types/git.sh) 27 | bag init compiled_types 28 | run github compile foo/bar 29 | [ "$status" -eq 0 ] 30 | [[ ${output} == "${gitfn}" ]] 31 | } 32 | -------------------------------------------------------------------------------- /test/type-go-get.bats: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bats 2 | 3 | . test/helpers.sh 4 | go-get () { . $BORK_SOURCE_DIR/types/go-get.sh $*; } 5 | 6 | @test "go-get status: returns FAILED_PRECONDITION without go exec" { 7 | respond_to "which go" "return 1" 8 | run go-get status foo 9 | [ "$status" -eq $STATUS_FAILED_PRECONDITION ] 10 | } 11 | 12 | @test "go-get status: returns MISSING if package isn't installed" { 13 | respond_to "go list baz" "return 1" 14 | run go-get status baz 15 | [ "$status" -eq $STATUS_MISSING ] 16 | } 17 | 18 | @test "go-get status: returns OK if package is installed" { 19 | respond_to "go-get list foo" "return 0" 20 | run go-get status foo 21 | [ "$status" -eq $STATUS_OK ] 22 | } 23 | 24 | @test "go-get install: performs the installation" { 25 | run go-get install foo 26 | [ "$status" -eq 0 ] 27 | run baked_output 28 | [ "${lines[0]}" = "go get -u foo" ] 29 | } 30 | -------------------------------------------------------------------------------- /test/type-group.bats: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bats 2 | 3 | . test/helpers.sh 4 | group () { . $BORK_SOURCE_DIR/types/group.sh $*; } 5 | 6 | setup () { 7 | respond_to "cat /etc/group" "echo 'root:x:0'; echo 'admin:x:50'" 8 | } 9 | 10 | @test "group status: returns FAILED_PRECONDITION when missing groupadd exec" { 11 | respond_to "which groupadd" "return 1" 12 | run group status foo 13 | [ "$status" -eq $STATUS_FAILED_PRECONDITION ] 14 | } 15 | 16 | @test "group status: returns MISSING when group doesn't exist" { 17 | run group status custom 18 | [ "$status" -eq $STATUS_MISSING ] 19 | } 20 | 21 | @test "group status: returns OK when group exists" { 22 | run group status admin 23 | [ "$status" -eq $STATUS_OK ] 24 | } 25 | 26 | @test "group install: bakes 'groupadd'" { 27 | run group install custom 28 | [ "$status" -eq 0 ] 29 | run baked_output 30 | [ "${#lines[*]}" -eq 1 ] 31 | [ "${lines[0]}" = "groupadd custom" ] 32 | } 33 | -------------------------------------------------------------------------------- /test/type-iptables.bats: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bats 2 | 3 | . test/helpers.sh 4 | 5 | iptables () { . $BORK_SOURCE_DIR/types/iptables.sh $*; } 6 | 7 | @test "iptables status: returns MISSING when rule is missing" { 8 | respond_to "sudo iptables -C INPUT -i lo -j ACCEPT" \ 9 | "echo 'iptables: Bad rule (does a matching rule exist in that chain?).' >&2; return 1" 10 | run iptables status "INPUT -i lo -j ACCEPT" 11 | [ "$status" -eq $STATUS_MISSING ] 12 | } 13 | 14 | @test "iptables status: returns OK when rule is present" { 15 | run iptables status "INPUT -i lo -j ACCEPT" 16 | [ "$status" -eq $STATUS_OK ] 17 | } 18 | 19 | @test "iptables install: bakes the -A command" { 20 | run iptables install "INPUT -i lo -j ACCEPT" 21 | [ "$status" -eq 0 ] 22 | run baked_output 23 | [ "${lines[0]}" = "sudo iptables -A INPUT -i lo -j ACCEPT" ] 24 | } 25 | -------------------------------------------------------------------------------- /test/type-mas.bats: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bats 2 | 3 | . test/helpers.sh 4 | mas () { . $BORK_SOURCE_DIR/types/mas.sh $*; } 5 | 6 | setup () { 7 | respond_to "uname -s" "echo Darwin" 8 | respond_to "mas list" "cat $fixtures/mas-list.txt" 9 | respond_to "mas outdated" "cat $fixtures/mas-outdated.txt" 10 | } 11 | 12 | @test "mas status: returns FAILED_PRECONDITION without mas exec" { 13 | respond_to "which mas" "return 1" 14 | run mas status 497799835 Xcode 15 | [ "$status" -eq $STATUS_FAILED_PRECONDITION ] 16 | } 17 | 18 | @test "mas status: returns MISSING when app not installed" { 19 | run mas status 477670270 2Do 20 | [ "$status" -eq $STATUS_MISSING ] 21 | } 22 | 23 | @test "mas status: returns OUTDATED when app upgrade pending" { 24 | run mas status 458034879 Dash 25 | [ "$status" -eq $STATUS_OUTDATED ] 26 | } 27 | 28 | @test "mas status: returns OK when app installed and up-to-date" { 29 | run mas status 497799835 Xcode 30 | [ "$status" -eq $STATUS_OK ] 31 | } 32 | 33 | @test "mas install: performs install" { 34 | run mas install 477670270 2Do 35 | [ "$status" -eq 0 ] 36 | run baked_output 37 | [ "$output" = 'mas install 477670270' ] 38 | } 39 | 40 | @test "mas upgrade: performs upgrade" { 41 | run mas upgrade 458034879 Dash 42 | [ "$status" -eq 0 ] 43 | run baked_output 44 | [ "$output" = 'mas upgrade' ] 45 | } 46 | -------------------------------------------------------------------------------- /test/type-npm.bats: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bats 2 | 3 | . test/helpers.sh 4 | npm () { . $BORK_SOURCE_DIR/types/npm.sh $*; } 5 | 6 | setup () { 7 | respond_to "npm ls -g --depth 0" "cat $fixtures/npm-list.txt" 8 | respond_to "npm outdated -g" "cat $fixtures/npm-outdated.txt" 9 | } 10 | 11 | @test "npm status: returns FAILED_PRECONDITION without npm exec" { 12 | respond_to "which npm" "return 1" 13 | run npm status nodemon 14 | } 15 | 16 | @test "npm status: returns MISSING if package isn't installed" { 17 | run npm status missing-package 18 | [ "$status" -eq $STATUS_MISSING ] 19 | } 20 | 21 | @test "npm status: returns OUTDATED if package npm reports new version" { 22 | run npm status coffee-script 23 | [ "$status" -eq $STATUS_OUTDATED ] 24 | } 25 | 26 | @test "npm status: returns OK if package is installed" { 27 | run npm status nodemon 28 | [ "$status" -eq $STATUS_OK ] 29 | } 30 | 31 | @test "npm install: performs an installation" { 32 | run npm install foo 33 | [ "$status" -eq 0 ] 34 | run baked_output 35 | [ "${lines[0]}" = "npm -g install foo" ] 36 | } 37 | 38 | @test "npm upgrade: performs an upgrade" { 39 | run npm upgrade foo 40 | [ "$status" -eq 0 ] 41 | run baked_output 42 | [ "${lines[0]}" = "npm -g install foo" ] 43 | } 44 | -------------------------------------------------------------------------------- /test/type-pip.bats: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bats 2 | 3 | . test/helpers.sh 4 | pip () { . $BORK_SOURCE_DIR/types/pip.sh $*; } 5 | 6 | setup () { 7 | respond_to "pip list" "cat $fixtures/pip-list.txt" 8 | } 9 | 10 | @test "pip status: returns FAILED_PRECONDITION without pip exec" { 11 | respond_to "which pip" "return 1" 12 | run pip status foo 13 | [ "$status" -eq $STATUS_FAILED_PRECONDITION ] 14 | } 15 | 16 | @test "pip status: returns MISSING if pkg isn't installed" { 17 | run pip status baz 18 | [ "$status" -eq $STATUS_MISSING ] 19 | } 20 | 21 | @test "pip status: returns OK if pkg is installed" { 22 | run pip status foo 23 | [ "$status" -eq $STATUS_OK ] 24 | } 25 | 26 | @test "pip install: performs installation" { 27 | run pip install foo 28 | [ "$status" -eq 0 ] 29 | run baked_output 30 | [ "${lines[0]}" = "pip install foo" ] 31 | } 32 | -------------------------------------------------------------------------------- /test/type-pipsi.bats: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bats 2 | 3 | . test/helpers.sh 4 | 5 | pipsi_global_bin_dir="/usr/local/bin" 6 | pipsi_global_home="/usr/local/lib/pipsi" 7 | 8 | pipsi() { . $BORK_SOURCE_DIR/types/pipsi.sh $*; } 9 | 10 | setup() { 11 | respond_to 'pipsi list' "cat $fixtures/pipsi-list.txt" 12 | } 13 | 14 | # tests for pipsi bootstraping itself 15 | 16 | @test "pipsi status (no-pkg) returns OK when pipsi is available" { 17 | respond_to 'which pipsi' "echo ${HOME}/.local/bin/pipsi; return 0" 18 | respond_to 'type -ap pipsi' "echo ${HOME}/.local/bin/pipsi; return 0" 19 | run pipsi status 20 | (( status == STATUS_OK )) 21 | [[ -z ${output} ]] 22 | } 23 | 24 | @test "pipsi status global (no-pkg) returns OK when global pipsi is available" { 25 | mock_which="echo ${pipsi_global_bin_dir}/pipsi" 26 | respond_to 'which pipsi' "${mock_which}" 27 | respond_to 'type -ap pipsi' "${mock_which}" 28 | 29 | run pipsi status --global 30 | (( status == STATUS_OK )) 31 | [[ -z ${output} ]] 32 | } 33 | 34 | @test "pipsi status global (no-pkg) returns OK when pipsi is installed as both local and global" { 35 | local_pipsi="${HOME}/.local/bin/pipsi" 36 | global_pipsi="${pipsi_global_bin_dir}/pipsi" 37 | respond_to 'which pipsi' "echo ${local_pipsi}; return 0" 38 | respond_to 'type -ap pipsi' "echo ${local_pipsi}; echo ${global_pipsi}" 39 | 40 | run pipsi status --global 41 | (( status == STATUS_OK )) 42 | [[ -z ${output} ]] 43 | } 44 | 45 | @test "pipsi status (no-pkg) returns MISSING when pipsi is missing" { 46 | respond_to 'which pipsi' 'return 1' 47 | run pipsi status 48 | (( status == STATUS_MISSING )) 49 | } 50 | 51 | @test "pipsi status (no-pkg) returns MISSING when local pipsi is missing" { 52 | respond_to 'which pipsi' "echo ${pipsi_global_bin_dir}/pipsi; return 0" 53 | run pipsi status 54 | (( status == STATUS_MISSING )) 55 | [[ -z ${output} ]] 56 | } 57 | 58 | @test "pipsi status global (no-pkg) returns MISSING when global pipsi is missing" { 59 | respond_to 'which pipsi' "echo ${HOME}/.local/bin/pipsi; return 0" 60 | run pipsi status --global 61 | (( status == STATUS_MISSING )) 62 | [[ -z ${output} ]] 63 | } 64 | 65 | @test "pipsi status (no-pkg) returns FAILED_PRECONDITION when python 3 is missing" { 66 | respond_to 'which python3' 'return 1' 67 | run pipsi status 68 | (( status == STATUS_FAILED_PRECONDITION )) 69 | } 70 | 71 | @test "pipsi status (no-pkg) returns FAILED_PRECONDITION when pipsi and curl are missing" { 72 | respond_to 'which pipsi' 'return 1' 73 | respond_to 'which curl' 'return 1' 74 | 75 | run pipsi status 76 | (( status == STATUS_FAILED_PRECONDITION )) 77 | } 78 | 79 | @test "pipsi status (no-pkg) returns FAILED_PRECONDITION when pipsi and git are missing" { 80 | respond_to 'which pipsi' 'return 1' 81 | respond_to 'which git' 'return 1' 82 | 83 | run pipsi status 84 | (( status == STATUS_FAILED_PRECONDITION )) 85 | } 86 | 87 | @test "pipsi install (no-pkg) bootstraps itself" { 88 | run pipsi install 89 | (( status == 0 )) 90 | run baked_output 91 | [[ ${output} =~ curl\ .*get-pipsi\.py ]] 92 | # should not check if already installed (bork type already does that) 93 | [[ ${output} =~ --ignore-existing ]] 94 | # should be installed from git master 95 | [[ ${output} =~ 'git+https://github.com/mitsuhiko/pipsi.git#egg=pipsi' ]] 96 | } 97 | 98 | @test "pipsi install (no-pkg) upgrades pip and setuptools after install" { 99 | run pipsi install 100 | run baked_output 101 | [[ ${output} =~ 'pip install --upgrade pip setuptools' ]] 102 | } 103 | 104 | @test "pipsi install global (no-pkg) bootstraps itself globally" { 105 | respond_to "test -w ${pipsi_global_bin_dir}/" 'return 0' 106 | respond_to "test -w ${pipsi_global_home}/" 'return 0' 107 | 108 | run pipsi install --global 109 | (( status == 0 )) 110 | run baked_output 111 | [[ ${output} =~ curl\ .*get-pipsi\.py ]] 112 | [[ ${output} =~ '| python3' ]] 113 | [[ ${output} =~ --bin-dir=${pipsi_global_bin_dir} ]] 114 | [[ ${output} =~ --home=${pipsi_global_home} ]] 115 | # should not check if already installed (bork type already does that) 116 | [[ ${output} =~ --ignore-existing ]] 117 | } 118 | 119 | @test "pipsi install global (no-pkg) uses sudo if necessary to write to target dirs" { 120 | respond_to "test -w ${pipsi_global_bin_dir}/" 'return 1' 121 | respond_to "test -w ${pipsi_global_home}/" 'return 0' 122 | 123 | run pipsi install --global 124 | (( status == 0 )) 125 | run baked_output 126 | [[ ${output} =~ curl\ .*get-pipsi\.py ]] 127 | [[ ${output} =~ '| sudo python3' ]] 128 | } 129 | 130 | @test "pipsi upgrade (no-pkg) upgrades itself" { 131 | run pipsi upgrade 132 | (( status == 0 )) 133 | run baked_output 134 | [[ ${output} =~ 'pipsi upgrade pipsi' ]] 135 | } 136 | 137 | @test "pipsi delete (no-pkg) removes itself" { 138 | run pipsi delete 139 | (( status == 0 )) 140 | run baked_output 141 | [[ ${output} =~ 'pipsi uninstall pipsi' ]] 142 | } 143 | 144 | # tests for pipsi installing packages 145 | 146 | @test "pipsi status returns FAILED_PRECONDITION when pipsi is missing" { 147 | respond_to 'which pipsi' 'return 1' 148 | run pipsi status something 149 | (( status == STATUS_FAILED_PRECONDITION )) 150 | } 151 | 152 | @test "pipsi status returns MISSING when package is not installed" { 153 | run pipsi status missing_package_is_missing 154 | (( status == STATUS_MISSING )) 155 | } 156 | 157 | @test "pipsi status returns OK when packge is installed and current" { 158 | run pipsi status current_package 159 | (( status == STATUS_OK )) 160 | } 161 | 162 | @test "pipsi status returns OUTDATED when package is installed but outdated" { 163 | # check for outdated needs to run `pip` directly so hardcodes default 164 | # path to pipsi virtualenvs 165 | pip="${HOME}/.local/venvs/outdated_package/bin/pip" 166 | respond_to "${pip} list --outdated --format=legacy" \ 167 | 'echo \"outdated_package (1.1.0) - Latest: 1.2.0 [wheel]\"' 168 | 169 | run pipsi status outdated_package 170 | (( status == STATUS_OUTDATED )) 171 | } 172 | 173 | @test "pipsi status global runs 'list' with appropriate options" { 174 | run pipsi status something --global 175 | run baked_output 176 | [[ ${lines[-1]} =~ ^pipsi ]] 177 | [[ ${lines[-1]} =~ --bin-dir=${pipsi_global_bin_dir} ]] 178 | [[ ${lines[-1]} =~ --home=${pipsi_global_home} ]] 179 | [[ ${lines[-1]} =~ list ]] 180 | } 181 | 182 | @test "pipsi install runs 'install pkg'" { 183 | run pipsi install missing_package_is_missing 184 | (( status == 0 )) 185 | run baked_output 186 | [[ ${output} =~ 'pipsi install missing_package_is_missing' ]] 187 | } 188 | 189 | @test "pipsi install upgrades pip and setuptools after install" { 190 | run pipsi install missing_package_is_missing 191 | run baked_output 192 | [[ ${output} =~ 'pip install --upgrade pip setuptools' ]] 193 | } 194 | 195 | @test "pipsi install global runs 'install pkg' with appropriate options" { 196 | run pipsi install missing_package_is_missing --global 197 | (( status == 0 )) 198 | run baked_output 199 | [[ ${lines[-2]} =~ ^\ *pipsi ]] 200 | [[ ${lines[-2]} =~ --bin-dir=${pipsi_global_bin_dir} ]] 201 | [[ ${lines[-2]} =~ --home=${pipsi_global_home} ]] 202 | [[ ${lines[-2]} =~ install\ missing_package_is_missing ]] 203 | } 204 | 205 | @test "pipsi install global uses sudo if necessary to write to target dirs" { 206 | respond_to "test -w ${pipsi_global_bin_dir}/" 'return 1' 207 | respond_to "test -w ${pipsi_global_home}/" 'return 1' 208 | 209 | run pipsi install missing_package_is_missing --global 210 | (( status == 0 )) 211 | run baked_output 212 | [[ ${lines[-2]} =~ ^sudo\ pipsi ]] 213 | } 214 | 215 | @test "pipsi upgrade runs 'upgrade pkg'" { 216 | run pipsi upgrade outdated_package 217 | (( status == 0 )) 218 | run baked_output 219 | [[ ${output} =~ 'pipsi upgrade outdated_package' ]] 220 | } 221 | 222 | @test "pipsi upgrade global runs 'upgrade pkg' with appropriate options" { 223 | run pipsi upgrade outdated_package --global 224 | (( status == 0 )) 225 | run baked_output 226 | [[ ${lines[-1]} =~ ^\ *pipsi ]] 227 | [[ ${lines[-1]} =~ --bin-dir=${pipsi_global_bin_dir} ]] 228 | [[ ${lines[-1]} =~ --home=${pipsi_global_home} ]] 229 | [[ ${lines[-1]} =~ upgrade\ outdated_package ]] 230 | } 231 | 232 | @test "pipsi upgrade global uses sudo if necessary to write to target dirs" { 233 | respond_to "test -w ${pipsi_global_bin_dir}/" 'return 0' 234 | respond_to "test -w ${pipsi_global_home}/" 'return 1' 235 | 236 | run pipsi upgrade outdated_package --global 237 | (( status == 0 )) 238 | run baked_output 239 | [[ ${lines[-1]} =~ ^sudo\ pipsi ]] 240 | } 241 | 242 | @test "pipsi delete runs 'uninstall pkg'" { 243 | run pipsi delete current_package 244 | (( status == 0 )) 245 | run baked_output 246 | [[ ${output} =~ 'pipsi uninstall current_package' ]] 247 | } 248 | 249 | @test "pipsi delete global runs 'uninstall pkg' with appropriate options" { 250 | run pipsi delete current_package --global 251 | (( status == 0 )) 252 | run baked_output 253 | [[ ${lines[-1]} =~ ^\ *pipsi ]] 254 | [[ ${lines[-1]} =~ --bin-dir=${pipsi_global_bin_dir} ]] 255 | [[ ${lines[-1]} =~ --home=${pipsi_global_home} ]] 256 | [[ ${lines[-1]} =~ uninstall\ current_package ]] 257 | } 258 | -------------------------------------------------------------------------------- /test/type-symlink.bats: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bats 2 | 3 | . test/helpers.sh 4 | symlink () { . $BORK_SOURCE_DIR/types/symlink.sh $*; } 5 | 6 | # passes through to actual tests on the file system 7 | baking_responder () { eval "$*"; } 8 | 9 | tmpdir= 10 | setup () { 11 | tmpdir=$(mktemp -d -t bork-symlinkXXXX) 12 | cd $tmpdir 13 | source=$(mktemp -d -t bork-symlink-srcXXXX) 14 | files=( 'LICENSE' 'README' ) 15 | for f in ${files[@]}; do echo "$f" > $source/$f; done 16 | } 17 | teardown () { 18 | rm -rf $tmpdir 19 | } 20 | 21 | make_links () { 22 | for f in $source/*; do 23 | ln -sf $f $tmpdir/$(basename $f) 24 | done 25 | } 26 | 27 | 28 | @test "symlink: status returns OK if the source is symlinked in dest" { 29 | ln -s "$source/README" .README 30 | run symlink status .README "$source/README" 31 | [ "$status" -eq $STATUS_OK ] 32 | } 33 | 34 | @test "symlink: status returns MISSING if the source is not symlinked in dest" { 35 | run symlink status README "$source/README" 36 | [ "$status" -eq $STATUS_MISSING ] 37 | } 38 | 39 | @test "symlink: status returns MISMATCH_UPGRADE if dest is symlinked to a non-source" { 40 | ln -sf $source/README LICENSE 41 | run symlink status LICENSE $source/LICENSE 42 | [ "$status" -eq $STATUS_MISMATCH_UPGRADE ] 43 | str_matches "${lines[0]}" "received source.+README$" 44 | } 45 | 46 | @test "symlink: status returns CONFLICT_CLOBBER if dest is a non-symlink" { 47 | echo "foo" > $tmpdir/LICENSE 48 | run symlink status LICENSE "$source/LICENSE" 49 | [ "$status" -eq $STATUS_CONFLICT_CLOBBER ] 50 | str_matches "${lines[0]}" "not a symlink.+LICENSE$" 51 | } 52 | 53 | @test "symlink: install creates the target symlink" { 54 | sourcefile=$source/README 55 | run symlink install .README $sourcefile 56 | [ "$status" -eq 0 ] 57 | run baked_output 58 | [[ "ln -sf $sourcefile .README" == ${lines[0]} ]] 59 | [ -h .README ] 60 | [[ ${sourcefile} == $(readlink .README) ]] 61 | } 62 | -------------------------------------------------------------------------------- /test/type-user.bats: -------------------------------------------------------------------------------- 1 | #!/user/bin/env bats 2 | 3 | . test/helpers.sh 4 | user () { . $BORK_SOURCE_DIR/types/user.sh $*; } 5 | 6 | users_query="cat /etc/passwd" 7 | groups_query="groups existant" 8 | setup () { 9 | respond_to "$users_query" "cat $fixtures/user-list.txt" 10 | respond_to "$groups_query" "echo 'bee existant '" 11 | } 12 | 13 | # --- without arguments ---------------------------------------- 14 | @test "user status: returns FAILED_PRECONDITION when useradd isn't found" { 15 | respond_to "which useradd" "return 1" 16 | run user status foo 17 | [ "$status" -eq $STATUS_FAILED_PRECONDITION ] 18 | } 19 | 20 | @test "user status: returns MISSING when user doesn't exist" { 21 | run user status nonexistant 22 | [ "$status" -eq $STATUS_MISSING ] 23 | } 24 | 25 | @test "user status: returns OK when user exists" { 26 | run user status existant 27 | [ "$status" -eq $STATUS_OK ] 28 | } 29 | 30 | @test "user install: bakes 'useradd' with -m" { 31 | run user install nonexistant 32 | [ "$status" -eq 0 ] 33 | run baked_output 34 | [ "${#lines[*]}" -eq 1 ] 35 | [ "${lines[0]}" = "useradd -m nonexistant" ] 36 | } 37 | 38 | # --- with shell argument ------------------------------------- 39 | @test "user status: with shell, returns MISSING when user doesn't exist" { 40 | run user status nonexistant --shell=/bin/zsh 41 | [ "$status" -eq $STATUS_MISSING ] 42 | } 43 | 44 | @test "user status: with shell, returns MISMATCHED_UPGRADE when user exists, wrong shell" { 45 | run user status existant --shell=/bin/zsh 46 | [ "$status" -eq $STATUS_MISMATCH_UPGRADE ] 47 | [ "${#lines[*]}" -eq 1 ] 48 | echo "${lines[0]}" | grep -E "^--shell:" >/dev/null 49 | echo "${lines[0]}" | grep -E "/bin/bash$" >/dev/null 50 | } 51 | 52 | @test "user status: with shell, returns OK when user exists, right shell" { 53 | run user status existant --shell=/bin/bash 54 | [ "$status" -eq $STATUS_OK ] 55 | } 56 | 57 | @test "user install: with shell, bakes 'useradd' with --shell" { 58 | run user install nonexistant --shell=/bin/zsh 59 | [ "$status" -eq 0 ] 60 | run baked_output 61 | [ "${#lines[*]}" -eq 1 ] 62 | [ "${lines[0]}" = "useradd -m --shell /bin/zsh nonexistant" ] 63 | } 64 | 65 | @test "user upgrade: with shell, bakes 'chsh -s'" { 66 | run user upgrade existant --shell=/bin/zsh 67 | [ "$status" -eq 0 ] 68 | run baked_output 69 | [ "${#lines[*]}" -eq 3 ] 70 | [[ ${lines[0]} == $users_query ]] 71 | [[ ${lines[1]} == "chsh -s /bin/zsh existant" ]] 72 | [[ ${lines[2]} == $groups_query ]] 73 | } 74 | 75 | # --- with group argument ------------------------------------ 76 | @test "user status: with group, returns MISSING when user doesn't exist" { 77 | run user status nonexistant --groups=foo,bar 78 | [ "$status" -eq $STATUS_MISSING ] 79 | } 80 | 81 | @test "user status: with group, returns PARTIAL when user belongs to none" { 82 | run user status existant --groups=foo,bar 83 | [ "$status" -eq $STATUS_PARTIAL ] 84 | [ "${#lines[*]}" -eq 1 ] 85 | echo "${lines[0]}" | grep -E "^--groups:" >/dev/null 86 | echo "${lines[0]}" | grep -E "foo bar$" >/dev/null 87 | } 88 | 89 | @test "user status: with group, returns PARTIAL when user belongs to some" { 90 | run user status existant --groups=foo,bar,bee 91 | [ "$status" -eq $STATUS_PARTIAL ] 92 | [ "${#lines[*]}" -eq 1 ] 93 | echo "${lines[0]}" | grep -E "^--groups:" >/dev/null 94 | echo "${lines[0]}" | grep -E "foo bar$" > /dev/null 95 | } 96 | 97 | @test "user status: with group, returns OK when user belongs to all" { 98 | run user status existant --groups=existant,bee 99 | [ "$status" -eq $STATUS_OK ] 100 | } 101 | 102 | @test "user install: with group, bakes 'useradd' with --groups" { 103 | run user install nonexistant --groups=foo,bar 104 | [ "$status" -eq 0 ] 105 | run baked_output 106 | [ "${#lines[*]}" -eq 1 ] 107 | [ "${lines[0]}" = "useradd -m --groups foo,bar nonexistant" ] 108 | } 109 | 110 | @test "user install: with group matching user handle, bakes 'useradd' with --groups and -g" { 111 | run user install nonexistant --groups=nonexistant,foo,bar 112 | [ "$status" -eq 0 ] 113 | run baked_output 114 | [ "${#lines[*]}" -eq 1 ] 115 | [ "${lines[0]}" = "useradd -m --groups nonexistant,foo,bar -g nonexistant nonexistant" ] 116 | } 117 | 118 | @test "user upgrade: with group, bakes 'adduser' with user and group for each group" { 119 | run user upgrade existant --groups=foo,bar 120 | [ "$status" -eq 0 ] 121 | # expect no output or errors 122 | [[ -z ${output} ]] 123 | run baked_output 124 | [ "${#lines[*]}" -eq 3 ] 125 | [ "${lines[0]}" = "$groups_query" ] 126 | [ "${lines[1]}" = "adduser existant foo" ] 127 | [ "${lines[2]}" = "adduser existant bar" ] 128 | } 129 | -------------------------------------------------------------------------------- /test/type-yum.bats: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bats 2 | # TODO: how have CI run yum tests? 3 | 4 | . test/helpers.sh 5 | 6 | yum () { . $BORK_SOURCE_DIR/types/yum.sh $*; } 7 | 8 | setup () { 9 | respond_to "uname -s" "echo Linux" 10 | respond_to "rpm -qa" "cat $fixtures/rpm-qa.txt" 11 | respond_to "sudo yum list updates" "cat $fixtures/yum-list-updates.txt" 12 | } 13 | 14 | @test "yum status reports incorrect platform" { 15 | respond_to "uname -s" "echo Darwin" 16 | run yum status some_package 17 | [ "$status" -eq $STATUS_UNSUPPORTED_PLATFORM ] 18 | } 19 | 20 | @test "yum status reports missing yum" { 21 | respond_to "which yum" "return 1" 22 | run yum status some_package 23 | [ "$status" -eq $STATUS_FAILED_PRECONDITION ] 24 | } 25 | 26 | @test "yum status reports a package is missing" { 27 | run yum status missing_package 28 | [ "$status" -eq $STATUS_MISSING ] 29 | } 30 | 31 | @test "yum status reports a package is outdated" { 32 | run yum status outdated_package 33 | [ "$status" -eq $STATUS_OUTDATED ] 34 | } 35 | 36 | @test "yum status reports a package is current" { 37 | run yum status current_package 38 | [ "$status" -eq $STATUS_OK ] 39 | } 40 | 41 | @test "yum install runs 'yum install'" { 42 | run yum install missing_package 43 | [ "$status" -eq $STATUS_OK ] 44 | run baked_output 45 | [ "$output" = 'sudo yum -y install missing_package' ] 46 | } 47 | 48 | @test "yum upgrade runs 'yum install'" { 49 | run yum upgrade outdated_package 50 | [ "$status" -eq $STATUS_OK ] 51 | run baked_output 52 | [ "$output" = 'sudo yum -y install outdated_package' ] 53 | } 54 | -------------------------------------------------------------------------------- /test/type-zypper.bats: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bats 2 | 3 | . test/helpers.sh 4 | 5 | zypper() { . $BORK_SOURCE_DIR/types/zypper.sh "$@"; } 6 | 7 | @test "zypper status returns FAILED_PRECONDITION when rpm is missing" { 8 | respond_to 'which rpm' 'return 1' 9 | run zypper status something 10 | (( status == STATUS_FAILED_PRECONDITION )) 11 | } 12 | 13 | @test "zypper status returns FAILED_PRECONDITION when zypper is missing" { 14 | respond_to 'which zypper' 'return 1' 15 | run zypper status something 16 | (( status == STATUS_FAILED_PRECONDITION )) 17 | } 18 | 19 | @test "zypper status returns MISSING when package is not installed" { 20 | respond_to 'rpm -q missing_package_is_missing' \ 21 | 'echo package missing_package_is_missing is not installed; return 1' 22 | 23 | run zypper status missing_package_is_missing 24 | (( status == STATUS_MISSING )) 25 | } 26 | 27 | @test "zypper status returns OK when packge is installed and current" { 28 | respond_to 'rpm -q current_package' 'echo current_package-1.1.0-1.0.noarch' 29 | 30 | run zypper status current_package 31 | (( status == STATUS_OK )) 32 | } 33 | 34 | @test "zypper status returns OUTDATED when package is installed but outdated" { 35 | respond_to 'zypper --terse list-updates' \ 36 | "cat ${fixtures}/zypper-list-updates.txt" 37 | 38 | run zypper status outdated_package 39 | (( status == STATUS_OUTDATED )) 40 | } 41 | 42 | @test "zypper install runs 'install pkg'" { 43 | run zypper install missing_package_is_missing 44 | (( status == 0 )) 45 | run baked_output 46 | [[ ${output} == 'sudo zypper -nt install missing_package_is_missing' ]] 47 | } 48 | 49 | @test "zypper upgrade runs 'update pkg'" { 50 | run zypper upgrade outdated_package 51 | (( status == 0 )) 52 | run baked_output 53 | [[ ${output} == 'sudo zypper -nt update outdated_package' ]] 54 | } 55 | 56 | @test "zypper delete runs 'remove pkg'" { 57 | run zypper delete current_package 58 | (( status == 0 )) 59 | run baked_output 60 | [[ ${output} == 'sudo zypper -nt remove current_package' ]] 61 | } 62 | -------------------------------------------------------------------------------- /types/apm.sh: -------------------------------------------------------------------------------- 1 | # TODO tests - because it ain't code without tests 2 | # TODO --version - support for status, install, update 3 | 4 | action=$1 5 | pkgname=$2 6 | shift 2 7 | 8 | if hash "apm-beta" 2>/dev/null; then 9 | bin="apm-beta" 10 | elif hash "apm" 2>/dev/null; then 11 | bin="apm" 12 | fi 13 | 14 | case $action in 15 | desc) 16 | echo "asserts the presence of an atom package" 17 | echo "> apm docblockr" 18 | ;; 19 | status) 20 | needs_exec "${bin}" || return $STATUS_FAILED_PRECONDITION 21 | pkgs=$(bake ${bin} ls --installed -b| # list all installed packages 22 | sed 's/@[0-9\.]*//g') # remove version pattern (@0.1.6) 23 | if ! str_matches "$pkgs" "^$pkgname$"; then 24 | return $STATUS_MISSING 25 | fi 26 | return 0 27 | ;; 28 | install) 29 | bake ${bin} install "$pkgname" 30 | ;; 31 | esac 32 | -------------------------------------------------------------------------------- /types/apt.sh: -------------------------------------------------------------------------------- 1 | # TODO 2 | # - cache output of apt-get upgrade, only needs to be done once per run 3 | # - perhaps move the apt-get upgrade command out to a separate call without 4 | # a package name, similar to how the "brew" type does it. 5 | # - specify versions to install with --version flag (ie, ruby=2.0.0) 6 | # - specify distribution to install from with --dist flag (ie ruby/unstable) 7 | 8 | action=$1 9 | name=$2 10 | shift 2 11 | case $action in 12 | desc) 13 | echo "asserts packages installed via apt-get on debian or ubuntu linux" 14 | echo "* apt package-name" 15 | ;; 16 | status) 17 | baking_platform_is "Linux" || return $STATUS_UNSUPPORTED_PLATFORM 18 | needs_exec "apt-get" 0 19 | needs_exec "dpkg" $? 20 | [ "$?" -gt 0 ] && return $STATUS_FAILED_PRECONDITION 21 | 22 | echo "$(bake dpkg --get-selections)" | grep -E "^$name\\s+install$" 23 | [ "$?" -gt 0 ] && return $STATUS_MISSING 24 | 25 | outdated=$(bake sudo apt-get upgrade --dry-run \ 26 | | grep "^Inst" | awk '{print $2}') 27 | $(str_contains "$outdated" "$name") 28 | [ "$?" -eq 0 ] && return $STATUS_OUTDATED 29 | return $STATUS_OK 30 | ;; 31 | 32 | install|upgrade) 33 | bake sudo apt-get --yes install $name 34 | ;; 35 | 36 | *) return 1 ;; 37 | esac 38 | 39 | -------------------------------------------------------------------------------- /types/brew-tap.sh: -------------------------------------------------------------------------------- 1 | action=$1 2 | name="$(echo $2 | awk '{print tolower($0)}')" 3 | shift 2 4 | pin=$(arguments get pin $*) 5 | 6 | case $action in 7 | desc) 8 | echo "asserts a homebrew forumla repository has been tapped" 9 | echo "does NOT assert the updated-ness of a tap's formula - use `ok brew`" 10 | echo "> brew-tap homebrew/games (taps homebrew/games)" 11 | echo "--pin (pins the formula repository)" 12 | ;; 13 | 14 | status) 15 | baking_platform_is "Darwin" || return $STATUS_UNSUPPORTED_PLATFORM 16 | needs_exec "brew" || return $STATUS_FAILED_PRECONDITION 17 | list=$(bake brew tap) 18 | echo "$list" | grep -E "$name$" > /dev/null 19 | [ "$?" -gt 0 ] && return $STATUS_MISSING 20 | pinlist=$(bake brew tap --list-pinned) 21 | echo "$pinlist" | grep -E "$name$" > /dev/null 22 | pinstatus=$? 23 | if [ -n "$pin" ]; then 24 | [ "$pinstatus" -gt 0 ] && return $STATUS_PARTIAL 25 | else 26 | [ "$pinstatus" -eq 0 ] && return $STATUS_PARTIAL 27 | fi 28 | return $STATUS_OK ;; 29 | 30 | install) 31 | bake brew tap $name 32 | if [ -n "$pin" ]; then 33 | bake brew tap-pin $name 34 | fi 35 | ;; 36 | 37 | upgrade) 38 | if [ -n "$pin" ]; then 39 | bake brew tap-pin $name 40 | else 41 | bake brew tap-unpin $name 42 | fi 43 | ;; 44 | 45 | *) return 1 ;; 46 | esac 47 | -------------------------------------------------------------------------------- /types/brew.sh: -------------------------------------------------------------------------------- 1 | # TODO write tests for the packageless 'brew' assertion 2 | # TODO specify install/upgrade options, such as --env, --cc, etc 3 | # TODO would handling --cc etc also handle package options such as --with-spacemacs-icon on emacs-mac ? 4 | 5 | action=$1 6 | name=$2 7 | shift 2 8 | from=$(arguments get from $*) 9 | 10 | if [ -z "$name" ]; then 11 | case $action in 12 | desc) 13 | echo "asserts presence of packages installed via homebrew on mac os x" 14 | echo "* brew (installs/updates homebrew)" 15 | echo "* brew package-name (installs package)" 16 | echo "--from=caskroom/cask (source repository)" 17 | ;; 18 | status) 19 | baking_platform_is "Darwin" || return $STATUS_UNSUPPORTED_PLATFORM 20 | needs_exec "ruby" || return $STATUS_FAILED_PRECONDITION 21 | path=$(bake which brew) 22 | [ "$?" -gt 0 ] && return $STATUS_MISSING 23 | bake brew update && return $STATUS_OK || return $STATUS_FAILED 24 | ;; 25 | 26 | install) 27 | bake 'ruby -e "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/master/install)"' 28 | ;; 29 | 30 | *) return 1 ;; 31 | esac 32 | 33 | else 34 | case $action in 35 | status) 36 | baking_platform_is "Darwin" || return $STATUS_UNSUPPORTED_PLATFORM 37 | needs_exec "brew" || return $STATUS_FAILED_PRECONDITION 38 | 39 | bake brew list | grep -E "^$name$" > /dev/null 40 | [ "$?" -gt 0 ] && return $STATUS_MISSING 41 | bake brew outdated | awk '{print $1}' | grep -E "^$name$" > /dev/null 42 | [ "$?" -eq 0 ] && return $STATUS_OUTDATED 43 | return 0 ;; 44 | 45 | install) 46 | if [ -z "$from" ]; then 47 | HOMEBREW_NO_AUTO_UPDATE=true bake brew install $name 48 | else 49 | HOMEBREW_NO_AUTO_UPDATE=true bake brew install $from/$name 50 | fi 51 | ;; 52 | 53 | upgrade) HOMEBREW_NO_AUTO_UPDATE=true bake brew upgrade $name ;; 54 | 55 | *) return 1 ;; 56 | esac 57 | fi 58 | -------------------------------------------------------------------------------- /types/cask.sh: -------------------------------------------------------------------------------- 1 | action=$1 2 | name=$2 3 | shift 2 4 | appdir=$(arguments get appdir $*) 5 | 6 | case $action in 7 | desc) 8 | echo "asserts presenece of apps installed via caskroom.io on Mac OS X" 9 | echo "* cask app-name (installs cask)" 10 | echo "--appdir=/Applications (changes symlink path)" 11 | ;; 12 | 13 | status) 14 | baking_platform_is "Darwin" || return $STATUS_UNSUPPORTED_PLATFORM 15 | needs_exec "brew" || return $STATUS_FAILED_PRECONDITION 16 | bake brew cask --version > /dev/null 17 | [ "$?" -gt 0 ] && return $STATUS_FAILED_PRECONDITION 18 | 19 | list=$(bake brew cask list) 20 | echo "$list" | grep -E "^$name$" > /dev/null 21 | [ "$?" -gt 0 ] && return $STATUS_MISSING 22 | 23 | info=$(bake brew cask info $name) 24 | echo "$info" | grep 'Not installed' > /dev/null 25 | # TODO replace with perhaps "OUTDATED_CLOBBER" ? 26 | [ "$?" -eq 0 ] && return $STATUS_OUTDATED 27 | 28 | return 0 ;; 29 | 30 | install) 31 | if [ -n "$appdir" ]; then 32 | bake brew cask install $name --appdir=$appdir 33 | else 34 | bake brew cask install $name 35 | fi 36 | ;; 37 | 38 | upgrade) 39 | # TODO move rm statement to remove action with clobber 40 | bake rm -rf "/opt/homebrew-cask/Caskroom/$name" 41 | if [ -n "$appdir" ]; then 42 | bake brew cask install $name --appdir=$appdir --force 43 | else 44 | bake brew cask install $name --force 45 | fi 46 | ;; 47 | 48 | *) return 1 ;; 49 | esac 50 | -------------------------------------------------------------------------------- /types/check.sh: -------------------------------------------------------------------------------- 1 | # TODO tests on check type 2 | 3 | action=$1 4 | shift 1 5 | case $action in 6 | desc) 7 | echo "runs a given command. OK if returns 0, FAILED otherwise." 8 | echo '* check evalstr' 9 | echo '> check "[ -d $HOME/.ssh/id_rsa ]"' 10 | echo '> if check_failed; then ...' 11 | ;; 12 | status) 13 | eval "$*" 14 | [ "$?" -gt 0 ] && return $STATUS_FAILED || return $STATUS_OK 15 | ;; 16 | esac 17 | -------------------------------------------------------------------------------- /types/defaults.sh: -------------------------------------------------------------------------------- 1 | # dict types should be specified as such: 2 | # ok defaults com.runningwithcrayons.Alfred-Preferences hotkey.default dict key -int 49 mod -int 1048576 string Space 3 | # TODO maybe have multi-stage dict key/value declarations using dict-add as such: 4 | # ok defaults com.runningwithcrayons.Alfred-Preferences hotkey.default dict 5 | # ok defaults com.runningwithcrayons.Alfred-Preferences hotkey.default dict-entry key -int 49 6 | # ok defaults com.runningwithcrayons.Alfred-Preferences hotkey.default dict-entry mod -int 1048576 7 | # ok defaults com.runningwithcrayons.Alfred-Preferences hotkey.default dict-entry string Space 8 | 9 | # TODO handle array values 10 | # TODO recognize when domain is path to /Library/Preferences and prefix bake cmd with sudo 11 | 12 | action=$1 13 | domain=$2 14 | key=$3 15 | desired_type=$4 16 | 17 | [ "$desired_type" = "int" ] && desired_type="integer" 18 | 19 | shift 4 20 | 21 | if [ "${desired_type:0:4}" = "dict" ]; then 22 | desired_val=$* 23 | else 24 | desired_val=$1 25 | fi 26 | 27 | case $action in 28 | desc) 29 | echo "asserts settings for OS X's 'defaults' system" 30 | echo "* defaults domain key type value" 31 | echo "> defaults com.apple.dock autohide bool true" 32 | ;; 33 | status) 34 | needs_exec "defaults" || return $STATUS_FAILED_PRECONDITION 35 | current_val=$(bake defaults read $domain $key) 36 | [ "$?" -eq 1 ] && return $STATUS_MISSING 37 | 38 | current_type=$(str_get_field "$(bake defaults read-type $domain $key)" 3) 39 | conflict= 40 | 41 | if [ "$current_type" = "boolean" ]; then 42 | current_type="bool" 43 | case "$current_val" in 44 | 0) current_val="false" ;; 45 | 1|YES) current_val="true" ;; 46 | esac 47 | fi 48 | if [ "$current_type" = "dictionary" ]; then 49 | current_type="dict" 50 | bag init temp_defaults_value 51 | bag push temp_defaults_value "{" 52 | while [ -n "$1" ]; do 53 | key=$1 54 | shift 55 | next="$1" 56 | [ ${next:0:1} = '-' ] && shift 57 | value=$1 58 | shift 59 | bash push temp_defaults_value " $key = $value" 60 | done 61 | bag push temp_defaults_value "}" 62 | desired_val=$(bag print temp_defaults_value) 63 | fi 64 | if [ "$desired_type" != $current_type ]; then 65 | conflict=1 66 | echo "expected type: $desired_type" 67 | echo "received type: $current_type" 68 | fi 69 | if [ "$current_val" != $desired_val ]; then 70 | conflict=1 71 | echo "expected value: $desired_val" 72 | echo "received value: $current_val" 73 | fi 74 | [ -n "$conflict" ] && return $STATUS_MISMATCH_UPGRADE 75 | return $STATUS_OK 76 | ;; 77 | 78 | install|upgrade) 79 | bake defaults write $domain $key "-$desired_type" $desired_val 80 | ;; 81 | 82 | *) return 1 ;; 83 | esac 84 | -------------------------------------------------------------------------------- /types/directory.sh: -------------------------------------------------------------------------------- 1 | # TODO add --permissions flag, perhaps copy/extract from file? 2 | 3 | action=$1 4 | dir=$2 5 | shift 2 6 | 7 | owner=$(arguments get owner $*) 8 | group=$(arguments get group $*) 9 | mode=$(arguments get mode $*) 10 | 11 | case "$action" in 12 | desc) 13 | printf '%s\n' \ 14 | 'asserts presence of a directory' \ 15 | '* directory path [options]' \ 16 | '--owner=user-name' \ 17 | '--group=group-name' \ 18 | '--mode=mode' \ 19 | '> directory ~/.ssh --mode=700' 20 | ;; 21 | 22 | status) 23 | bake [ -e "${dir}" ] || return $STATUS_MISSING 24 | bake [ -d "${dir}" ] || { 25 | echo "target exists as non-directory" 26 | return $STATUS_CONFLICT_CLOBBER 27 | } 28 | 29 | mismatch=false 30 | if [[ -n ${owner} || -n ${group} || -n ${mode} ]]; then 31 | readarray -t dir_stat < <(bake stat --printf '%U\\n%G\\n%a' "${dir}") 32 | 33 | if [[ -n ${owner} && ${dir_stat[0]} != ${owner} ]]; then 34 | printf '%s owner: %s\n' \ 35 | 'expected' "${owner}" \ 36 | 'received' "${dir_stat[0]}" 37 | mismatch=true 38 | fi 39 | 40 | if [[ -n ${group} && ${dir_stat[1]} != ${group} ]]; then 41 | printf '%s group: %s\n' \ 42 | 'expected' "${group}" \ 43 | 'received' "${dir_stat[1]}" 44 | mismatch=true 45 | fi 46 | 47 | if [[ -n ${mode} && ${dir_stat[2]} != ${mode} ]]; then 48 | printf '%s mode: %s\n' \ 49 | 'expected' "${mode}" \ 50 | 'received' "${dir_stat[2]}" 51 | mismatch=true 52 | fi 53 | fi 54 | 55 | if ${mismatch}; then 56 | return "${STATUS_MISMATCH_UPGRADE}" 57 | fi 58 | 59 | return "${STATUS_OK}" 60 | ;; 61 | 62 | install|upgrade) 63 | inst_cmd=( install -C -d ) 64 | [[ -z ${owner} && -z ${group} ]] || inst_cmd=( sudo "${inst_cmd[@]}" ) 65 | [[ -z ${owner} ]] || inst_cmd+=( -o "${owner}" ) 66 | [[ -z ${group} ]] || inst_cmd+=( -g "${group}" ) 67 | [[ -z ${mode} ]] || inst_cmd+=( -m "${mode}" ) 68 | bake "${inst_cmd[@]}" "${dir}" 69 | ;; 70 | 71 | *) return 1 ;; 72 | esac 73 | -------------------------------------------------------------------------------- /types/download.sh: -------------------------------------------------------------------------------- 1 | action=$1 2 | targetfile=$2 3 | sourceurl=$3 4 | shift 3 5 | size=$(arguments get size $*) 6 | 7 | case "$action" in 8 | desc) 9 | echo "assert the presence & comparisons of a file to a URL" 10 | echo "> download ~/file.zip \"http://example.com/file.zip\"" 11 | echo "--size (compare size to Content-Length at URL)" 12 | ;; 13 | 14 | status) 15 | bake [ -f "\"$targetfile\"" ] || return $STATUS_MISSING 16 | 17 | if [ -n "$size" ]; then 18 | fileinfo=$(bake ls -al "\"$targetfile\"") 19 | sourcesize=$(echo "$fileinfo" | tr -s ' ' | cut -d' ' -f5) 20 | remoteinfo=$(bake $(http_head_cmd "$sourceurl")) 21 | remotesize=$(http_header "Content-Length" "$remoteinfo") 22 | remotesize=${remotesize%%[^0-9]*} 23 | if [ "$sourcesize" != "$remotesize" ]; then 24 | echo "expected size: $remotesize bytes" 25 | echo "received size: $localsize bytes" 26 | return $STATUS_CONFLICT_UPGRADE 27 | fi 28 | fi 29 | return $STATUS_OK 30 | ;; 31 | 32 | install|upgrade) 33 | bake $(http_get_cmd "$sourceurl" "$targetfile") 34 | ;; 35 | 36 | *) return 1 ;; 37 | esac 38 | -------------------------------------------------------------------------------- /types/file.sh: -------------------------------------------------------------------------------- 1 | # TODO change compiled filename transformation to md5 representation instead of base64 2 | # TODO any way to test for sudo??? 3 | # TODO distinguish target from local system for file sums 4 | 5 | action=$1 6 | targetfile=$2 7 | sourcefile=$3 8 | shift 3 9 | 10 | perms=$(arguments get permissions $*) 11 | owner=$(arguments get owner $*) 12 | _bake () { 13 | if [ -n "$owner" ]; then 14 | bake sudo $* 15 | else bake $* 16 | fi 17 | } 18 | file_varname="borkfiles__$(echo "$sourcefile" | base64 | sed -E 's|\+|_|' | sed -E 's|\?|__|' | sed -E 's|=+||')" 19 | 20 | case $action in 21 | desc) 22 | echo "asserts the presence, checksum, owner and permissions of a file" 23 | echo "* file target-path source-path [arguments]" 24 | echo "--permissions=755 permissions for the file" 25 | echo "--owner=owner-name owner name of the file" 26 | ;; 27 | status) 28 | if ! is_compiled && [ ! -f $sourcefile ]; then 29 | echo "source file doesn't exist: $sourcefile" 30 | return $STATUS_FAILED_ARGUMENTS 31 | fi 32 | if [ -n "$owner" ]; then 33 | owner_id=$(bake id -u $owner) 34 | if [ "$?" -gt 0 ]; then 35 | echo "unknown owner: $owner" 36 | return $STATUS_FAILED_ARGUMENT_PRECONDITION 37 | fi 38 | fi 39 | 40 | bake [ -f $targetfile ] || return $STATUS_MISSING 41 | 42 | # TODO: need to distinguish local platfrom from target platform 43 | if is_compiled; then 44 | md5c=$(md5cmd $platform) 45 | sourcesum=$(echo "${!file_varname}" | base64 --decode | eval $md5c) 46 | else 47 | sourcesum=$(eval $(md5cmd $platform $sourcefile)) 48 | fi 49 | targetsum=$(_bake $(md5cmd $platform $targetfile)) 50 | if [ "$targetsum" != $sourcesum ]; then 51 | echo "expected sum: $sourcesum" 52 | echo "received sum: $targetsum" 53 | return $STATUS_CONFLICT_UPGRADE 54 | fi 55 | 56 | mismatch= 57 | if [ -n "$perms" ]; then 58 | existing_perms=$(_bake $(permission_cmd $platform) $targetfile) 59 | if [ "$existing_perms" != $perms ]; then 60 | echo "expected permissions: $perms" 61 | echo "received permissions: $existing_perms" 62 | mismatch=1 63 | fi 64 | fi 65 | if [ -n "$owner" ]; then 66 | existing_user=$(_bake ls -l $targetfile | awk '{print $3}') 67 | if [ "$existing_user" != $owner ]; then 68 | echo "expected owner: $owner" 69 | echo "received owner: $existing_user" 70 | mismatch=1 71 | fi 72 | fi 73 | [ -n "$mismatch" ] && return $STATUS_MISMATCH_UPGRADE 74 | return 0 75 | ;; 76 | 77 | install|upgrade) 78 | dirn=$(dirname $targetfile) 79 | [ "$dirn" != . ] && _bake mkdir -p $dirn 80 | [ -n "$owner" ] && _bake chown $owner $dirn 81 | if is_compiled; then 82 | _bake "echo \"${!file_varname}\" | base64 --decode > $targetfile" 83 | else 84 | _bake cp $sourcefile $targetfile 85 | fi 86 | [ -n "$owner" ] && _bake chown $owner $targetfile 87 | [ -n "$perms" ] && _bake chmod $perms $targetfile 88 | return 0 89 | ;; 90 | 91 | compile) 92 | if [ ! -f "$sourcefile" ]; then 93 | echo "fatal: file '$sourcefile' does not exist!" 1>&2 94 | exit 1 95 | fi 96 | if [ ! -r "$sourcefile" ]; then 97 | echo "fatal: you do not have read permission for file '$sourcefile'" 98 | exit 1 99 | fi 100 | echo "# source: $sourcefile" 101 | echo "# md5 sum: $(eval $(md5cmd $platform $sourcefile))" 102 | echo "$file_varname=\"$(cat $sourcefile | base64)\"" 103 | ;; 104 | 105 | *) return 1 ;; 106 | esac 107 | -------------------------------------------------------------------------------- /types/gem.sh: -------------------------------------------------------------------------------- 1 | # TODO install - test for necessity of 'sudo' prefix 2 | # TODO status - check against "gem outdated" list 3 | # TODO update - update outdated gems mentioned 4 | # TODO --version - support for status, install, update 5 | # TODO gem flags - figure out convention to pass through, similar to brew? 6 | 7 | action=$1 8 | gemname=$2 9 | shift 2 10 | 11 | case $action in 12 | desc) 13 | echo "asserts the presence of a gem in the environment's ruby" 14 | echo "> gem bundler" 15 | ;; 16 | status) 17 | needs_exec "gem" || return $STATUS_FAILED_PRECONDITION 18 | gems=$(bake gem list) 19 | if ! str_matches "$gems" "^$gemname"; then 20 | return $STATUS_MISSING 21 | fi 22 | return 0 ;; 23 | install) 24 | bake sudo gem install "$gemname" 25 | ;; 26 | esac 27 | -------------------------------------------------------------------------------- /types/git.sh: -------------------------------------------------------------------------------- 1 | # TODO compare origins to ensure correct, provide fix for 2 | # TODO provide flag for refspec name, ensure status/install/upgrade use it properly 3 | # TODO perhaps do --depth=0 by default (quicker) & provide flag for --full ? 4 | # TODO submodules? 5 | # TODO anything here we can extract and re-use for an hg or darcs type? 6 | # TODO use merge instead of pull 7 | # TODO specify alternate refs instead of "master"; maybe change branch? 8 | 9 | action=$1 10 | git_url=$2 11 | shift 2 12 | next=$1 13 | if [ -n "$next" ] && [ ${next:0:1} != '-' ]; then 14 | target_dir=$git_url 15 | git_url=$1 16 | shift 17 | else 18 | git_name=$(basename $git_url .git) 19 | target_dir="$git_name" 20 | fi 21 | 22 | branch=$(arguments get branch $*) 23 | 24 | 25 | if [[ ! -z $branch ]]; then 26 | git_branch=$branch 27 | else 28 | git_branch="master" 29 | fi 30 | 31 | case $action in 32 | desc) 33 | echo "asserts presence and state of a git repository" 34 | echo "> git git@github.com:mattly/bork" 35 | echo "> git ~/code/bork git@github.com:mattly/bork" 36 | echo "--branch=gh-pages (specify branch, tag, or ref)" 37 | ;; 38 | status) 39 | needs_exec "git" || return $STATUS_FAILED_PRECONDITION 40 | 41 | # if the directory is missing, it's missing 42 | bake [ ! -d $target_dir ] && return $STATUS_MISSING 43 | 44 | # if the directory is present but empty, it's missing 45 | target_dir_contents=$(str_item_count "$(bake ls -A $target_dir)") 46 | [ "$target_dir_contents" -eq 0 ] && return $STATUS_MISSING 47 | 48 | bake cd $target_dir 49 | # fetch from the remote without fast-forwarding 50 | # this *does* change the local repository's pointers and takes longer 51 | # up front, but I believe in the grand scheme is the right thing to do. 52 | git_fetch="$(bake git fetch 2>&1)" 53 | git_fetch_status=$? 54 | 55 | # If the directory isn't a git repo, conflict 56 | if [ $git_fetch_status -gt 0 ]; then 57 | echo "destination directory $target_dir exists, not a git repository (exit status $git_fetch_status)" 58 | return $STATUS_CONFLICT_CLOBBER 59 | elif str_matches "$git_fetch" '"^fatal"'; then 60 | echo "destination directory exists, not a git repository" 61 | echo "$git_fetch" 62 | return $STATUS_CONFLICT_CLOBBER 63 | fi 64 | 65 | git_stat=$(bake git status -uno -b --porcelain) 66 | git_first_line=$(echo "$git_stat" | head -n 1) 67 | 68 | git_divergence=$(str_get_field "$git_first_line" 3) 69 | if str_matches "$git_divergence" 'ahead'; then 70 | echo "local git repository is ahead of remote" 71 | return $STATUS_CONFLICT_UPGRADE 72 | fi 73 | 74 | # are there changes? 75 | if str_matches "$git_stat" "^\\s?\\w"; then 76 | echo "local git repository has uncommitted changes" 77 | return $STATUS_CONFLICT_UPGRADE 78 | fi 79 | 80 | str_matches "$(str_get_field "$git_first_line" 2)" "$git_branch" 81 | if [ "$?" -ne 0 ]; then 82 | echo "local git repository is on incorrect branch" 83 | return $STATUS_MISMATCH_UPGRADE 84 | fi 85 | 86 | # If it's known to be behind, outdated 87 | if str_matches "$git_divergence" 'behind'; then return $STATUS_OUTDATED; fi 88 | 89 | # guess we're clean, so things are OK 90 | return $STATUS_OK ;; 91 | 92 | install) 93 | bake mkdir -p $target_dir 94 | bake git clone -b $git_branch $git_url $target_dir 95 | ;; 96 | 97 | upgrade) 98 | bake cd $target_dir 99 | bake git reset --hard 100 | bake git pull 101 | bake git checkout $git_branch 102 | bake git log HEAD@{2}.. 103 | printf "\n" 104 | ;; 105 | 106 | *) return 1 ;; 107 | esac 108 | 109 | -------------------------------------------------------------------------------- /types/github.sh: -------------------------------------------------------------------------------- 1 | # TODO some flag for git:// urls 2 | 3 | if [ -z "$git_call" ]; then 4 | git_call=". $BORK_SOURCE_DIR/types/git.sh" 5 | is_compiled && git_call="type_git" 6 | fi 7 | 8 | action=$1 9 | repo=$2 10 | shift 2 11 | 12 | case $action in 13 | desc) 14 | echo "front-end for git type, uses github urls" 15 | echo "passes arguments to git type" 16 | echo "> ok github mattly/bork" 17 | echo "> ok github ~/code/bork mattly/bork" 18 | echo "--ssh (clones via ssh instead of https)" 19 | ;; 20 | 21 | compile) 22 | include_assertion git $BORK_SOURCE_DIR/types/git.sh 23 | ;; 24 | 25 | status|install|upgrade) 26 | next=$1 27 | target_dir= 28 | if [ -n "$next" ] && [ ${next:0:1} != '-' ]; then 29 | target_dir="$repo" 30 | repo=$1 31 | shift 32 | fi 33 | args="$*" 34 | if [ -n "$(arguments get ssh $*)" ]; then 35 | url="git@github.com:$(echo $repo).git" 36 | args=$(echo "$args" | sed -E 's|--ssh||') 37 | else 38 | url="https://github.com/$(echo $repo).git" 39 | fi 40 | eval "$git_call $action $target_dir $url $args" 41 | ;; 42 | 43 | *) return 1 ;; 44 | esac 45 | -------------------------------------------------------------------------------- /types/go-get.sh: -------------------------------------------------------------------------------- 1 | action=$1 2 | pkg=$2 3 | shift 2 4 | 5 | case $action in 6 | desc) 7 | echo "asserts a go pkg is installed in $GOPATH" 8 | echo "> go-get guru" 9 | ;; 10 | status) 11 | needs_exec "go" || return $STATUS_FAILED_PRECONDITION 12 | if ! bake go list $pkg> /dev/null 2>&1 ; then 13 | return $STATUS_MISSING 14 | fi 15 | return $STATUS_OK ;; 16 | install) 17 | bake go get -u "$pkg" 18 | ;; 19 | esac 20 | -------------------------------------------------------------------------------- /types/group.sh: -------------------------------------------------------------------------------- 1 | # TODO doesn't work on Darwin, is groupadd a GNU thing? 2 | 3 | action=$1 4 | groupname=$2 5 | shift 2 6 | 7 | case $action in 8 | desc) 9 | echo "asserts presence of a unix group (linux only, for now)" 10 | echo "> group admin" 11 | ;; 12 | status) 13 | needs_exec groupadd || return $STATUS_FAILED_PRECONDITION 14 | 15 | bake cat /etc/group | grep -E "^$groupname:" 16 | [ "$?" -gt 0 ] && return $STATUS_MISSING 17 | return $STATUS_OK ;; 18 | 19 | install) 20 | bake groupadd $groupname ;; 21 | 22 | *) return 1 ;; 23 | esac 24 | -------------------------------------------------------------------------------- /types/iptables.sh: -------------------------------------------------------------------------------- 1 | # TODO test for precondition of iptables exec 2 | # TODO need a way to test for ordering of rules, discussion in the original PR: https://github.com/mattly/bork/pull/10 3 | # TODO maybe take the chain as the first argument, the rule as the rest? 4 | 5 | action=$1 6 | shift 7 | 8 | case $action in 9 | desc) 10 | echo "asserts presence of iptables rule" 11 | echo "NOTE: does not assert ordering of rules" 12 | echo "> iptables INPUT -m conntrack --ctstate RELATED,ESTABLISHED -j ACCEPT" 13 | ;; 14 | status) 15 | out=$(bake sudo iptables -C $* 2>&1) 16 | status=$? 17 | [ "$status" -gt 0 ] && return $STATUS_MISSING 18 | return $STATUS_OK ;; 19 | 20 | install) 21 | bake sudo iptables -A $* 22 | ;; 23 | 24 | *) return 1 ;; 25 | esac 26 | -------------------------------------------------------------------------------- /types/mas.sh: -------------------------------------------------------------------------------- 1 | action=$1 2 | appid=$2 3 | 4 | shift 2 5 | 6 | case $action in 7 | desc) 8 | echo "asserts a Mac app is installed and up-to-date from the App Store" 9 | echo " via the 'mas' utility https://github.com/argon/mas" 10 | echo "app id is required, can be obtained from 'mas' utility, name is optional" 11 | echo "!WARNING! 'mas' will currently perform *all* pending upgrades when upgrading any app" 12 | echo "> mas 497799835 Xcode (installs/upgrades Xcode)" 13 | ;; 14 | 15 | status) 16 | baking_platform_is "Darwin" || return $STATUS_UNSUPPORTED_PLATFORM 17 | needs_exec "mas" || return $STATUS_FAILED_PRECONDITION 18 | bake mas list | grep -E "^$appid" > /dev/null 19 | [ "$?" -gt 0 ] && return $STATUS_MISSING 20 | bake mas outdated | grep -E "^$appid" > /dev/null 21 | [ "$?" -eq 0 ] && return $STATUS_OUTDATED 22 | return $STATUS_OK 23 | ;; 24 | 25 | install) bake mas install $appid ;; 26 | 27 | upgrade) bake mas upgrade ;; 28 | 29 | *) return 1 ;; 30 | esac 31 | -------------------------------------------------------------------------------- /types/npm.sh: -------------------------------------------------------------------------------- 1 | # TODO tests - because it ain't code without tests 2 | # TODO install - test for necessity of 'sudo' prefix 3 | # TODO status - check version outdated status 4 | # TODO --version - support for status, install, update 5 | 6 | action=$1 7 | pkgname=$2 8 | shift 2 9 | 10 | case $action in 11 | desc) 12 | echo "asserts the presence of a nodejs module in npm's global installation" 13 | echo "> npm grunt-cli" 14 | ;; 15 | 16 | status) 17 | needs_exec "npm" || return $STATUS_FAILED_PRECONDITION 18 | 19 | list=$(bake npm ls -g --depth 0) 20 | str_matches "$list" " $pkgname@" || return $STATUS_MISSING 21 | 22 | outdated=$(bake npm outdated -g) 23 | # TODO further tests against version for pinned versions, git urls, etc 24 | str_matches "$outdated" "^$pkgname " && return $STATUS_OUTDATED 25 | 26 | return $STATUS_OK 27 | ;; 28 | 29 | install) 30 | bake npm -g install "$pkgname" 31 | ;; 32 | 33 | upgrade) 34 | bake npm -g install "$pkgname" 35 | ;; 36 | 37 | *) return 1 ;; 38 | esac 39 | -------------------------------------------------------------------------------- /types/pip.sh: -------------------------------------------------------------------------------- 1 | # TODO --sudo flag 2 | # TODO versions 3 | # TODO update 4 | 5 | action=$1 6 | name=$2 7 | shift 2 8 | 9 | case $action in 10 | desc) 11 | echo "asserts presence of packages installed via pip" 12 | echo "> pip pygments" 13 | ;; 14 | status) 15 | needs_exec "pip" || return $STATUS_FAILED_PRECONDITION 16 | pkgs=$(PIP_FORMAT=legacy bake pip list) 17 | if ! str_matches "$pkgs" "^$name"; then 18 | return $STATUS_MISSING 19 | fi 20 | return 0 ;; 21 | install) 22 | bake pip install "$name" 23 | ;; 24 | esac 25 | 26 | -------------------------------------------------------------------------------- /types/pipsi.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | action=$1 4 | name=$2 5 | shift 2 6 | 7 | if [[ -n ${name} && ${name} == --global ]]; then 8 | global=true 9 | name='' 10 | else 11 | global=$(arguments get global $*) 12 | fi 13 | 14 | bootstrap=false 15 | if [[ -z ${name} ]]; then 16 | bootstrap=true 17 | name='pipsi' 18 | fi 19 | 20 | # set paths, try variables pipsi itself uses first and fall-back to 21 | # defaults; take the 'global' option into account 22 | declare -a pipsi_opts=( ) 23 | pipsi_bin_dir="${PIPSI_BIN_DIR:-"${HOME}/.local/bin"}" 24 | pipsi_home="${PIPSI_HOME:-"${HOME}/.local/venvs"}" 25 | if [[ ${global} == true ]]; then 26 | pipsi_bin_dir="${PIPSI_GLOBAL_BIN_DIR:-"/usr/local/bin"}" 27 | pipsi_home="${PIPSI_GLOBAL_HOME:-"/usr/local/lib/pipsi"}" 28 | pipsi_opts+=( --bin-dir=${pipsi_bin_dir} --home=${pipsi_home} ) 29 | fi 30 | pip="${pipsi_home}/${name}/bin/pip" 31 | 32 | # if bin_dir or home are not writable, automatically attempt to elevate 33 | # permissions using sudo 34 | get_su() { 35 | su='' 36 | bake test -w "${pipsi_bin_dir}/" || su='sudo' 37 | bake test -w "${pipsi_home}/" || su='sudo' 38 | printf '%s' "${su}" 39 | } 40 | 41 | # return STATUS_MISSING if pipsi (local or global, according to option) 42 | # is missing, return 0 otherwise 43 | # note this is only useful in case of operating on pipsi itself, if any 44 | # pipsi is available it is capable of installing packages either 45 | # locally or globally 46 | status_have_pipsi() { 47 | bake which pipsi &>/dev/null || return "${STATUS_MISSING}" 48 | if [[ ${global} == true ]]; then 49 | bake type -ap pipsi |grep -q '^/usr' || return "${STATUS_MISSING}" 50 | else 51 | bake type -ap pipsi |grep -q "^${HOME}" || return "${STATUS_MISSING}" 52 | fi 53 | } 54 | 55 | case "${action}" in 56 | desc) 57 | printf '%s\n' \ 58 | 'asserts presence of packages installed via pipsi' \ 59 | '* pipsi (install/upgrade pipsi itself)' \ 60 | '* pipsi packge-name (works on given package from pypi)' \ 61 | '--global (work on global packages instead of per-user)' 62 | ;; 63 | status) 64 | needs_exec "python3" || return "${STATUS_FAILED_PRECONDITION}" 65 | 66 | if ${bootstrap}; then # operate on pipsi itself 67 | status_have_pipsi || { 68 | status="$?" 69 | needs_exec "curl" || return "${STATUS_FAILED_PRECONDITION}" 70 | needs_exec "git" || return "${STATUS_FAILED_PRECONDITION}" 71 | return "${status}" 72 | } 73 | # pipsi is available, fall back to common check for up-to-date 74 | else # operate on provided packge 75 | bake which pipsi || return "${STATUS_FAILED_PRECONDITION}" 76 | 77 | # check output of `pipsi list` to see if package is installed 78 | [[ $(bake pipsi "${pipsi_opts[@]}" list) =~ "Package \"${name}\"" ]] \ 79 | || return "${STATUS_MISSING}" 80 | fi 81 | 82 | # pipsi doesn't provide a way to check if packge is up-to-date, 83 | # so for now have to use `pip` directly 84 | ! bake "${pip}" list --outdated --format=legacy | egrep "^${name} " \ 85 | || return "${STATUS_OUTDATED}" 86 | return "${STATUS_OK}" 87 | ;; 88 | install) 89 | su="$(get_su)" 90 | if ${bootstrap}; then # operate on pipsi itself 91 | # escape the pipe as we want `bake` to evaluate it lazily 92 | # install pipsi from git master for now as release on pypi is 93 | # ancient, master contains many fixes and some new features 94 | bake curl -fsSL \ 95 | https://raw.githubusercontent.com/mitsuhiko/pipsi/master/get-pipsi.py \ 96 | \| "${su}" python3 - "${pipsi_opts[@]}" \ 97 | --ignore-existing \ 98 | --src 'git+https://github.com/mitsuhiko/pipsi.git#egg=pipsi' 99 | else # operate on provided packge 100 | bake "${su}" pipsi "${pipsi_opts[@]}" install "${name}" 101 | fi 102 | bake "${pip}" install --upgrade pip setuptools 103 | ;; 104 | upgrade|delete) 105 | if [[ ${action} == delete ]]; then 106 | action="uninstall" 107 | fi 108 | bake "$(get_su)" pipsi "${pipsi_opts[@]}" "${action}" "${name}" 109 | ;; 110 | *) return 1 ;; 111 | esac 112 | -------------------------------------------------------------------------------- /types/scutil.sh: -------------------------------------------------------------------------------- 1 | # TODO tests 2 | 3 | action=$1 4 | type=$2 5 | name=$3 6 | shift 3 7 | 8 | case $action in 9 | desc) 10 | echo "Verifies OS X machine name with scutil" 11 | echo "> scutil ComputerName bork" 12 | ;; 13 | status) 14 | needs_exec "scutil" || return $STATUS_FAILED_PRECONDITION 15 | current_val=$(bake scutil --get $type) 16 | if [ "$current_val" != $name ]; then 17 | echo "expected: $name" 18 | echo "received: $current_val" 19 | return $STATUS_MISMATCH_UPGRADE 20 | fi 21 | return $STATUS_OK 22 | ;; 23 | upgrade) 24 | bake scutil --set $type $name 25 | ;; 26 | esac 27 | -------------------------------------------------------------------------------- /types/symlink.sh: -------------------------------------------------------------------------------- 1 | action=$1 2 | target=$2 3 | source=$3 4 | 5 | case "$action" in 6 | desc) 7 | echo "assert presence and target of a symlink" 8 | echo "> symlink .vimrc ~/code/dotfiles/configs/vimrc" 9 | ;; 10 | 11 | status) 12 | bake [ ! -e "$target" ] && return $STATUS_MISSING 13 | if bake [ ! -h "$target" ]; then 14 | echo "not a symlink: $target" 15 | return $STATUS_CONFLICT_CLOBBER 16 | else 17 | existing_source=$(bake readlink \"$target\") 18 | if [ "$existing_source" != "$source" ]; then 19 | echo "received source for existing symlink: $existing_source" 20 | echo "expected source for symlink: $source" 21 | return $STATUS_MISMATCH_UPGRADE 22 | fi 23 | fi 24 | return $STATUS_OK 25 | ;; 26 | 27 | install|upgrade) 28 | bake ln -sf "$source" "$target" ;; 29 | 30 | *) return 1;; 31 | esac 32 | -------------------------------------------------------------------------------- /types/user.sh: -------------------------------------------------------------------------------- 1 | # TODO check --shell argument for existence of shell, presence in /etc/shells 2 | # TODO need to check --groups to make sure they exist 3 | # TODO ability to check group memberships on Darwin? 4 | # TODO no useradd binary on Darwin 5 | 6 | action=$1 7 | handle=$2 8 | shift 2 9 | 10 | shell=$(arguments get shell $*) 11 | groups=$(arguments get groups $*) 12 | 13 | user_get () { 14 | row=$(bake cat /etc/passwd | grep -E "^$1:") 15 | stat=$? 16 | echo $row 17 | return $stat 18 | } 19 | 20 | user_shell () { 21 | current_shell=$(echo "$1" | cut -d: -f 7) 22 | if [ "$current_shell" != $2 ]; then 23 | echo $current_shell 24 | return 1 25 | fi 26 | return 0 27 | } 28 | 29 | user_groups () { 30 | current_groups=$(bake groups $1) 31 | case $platform in 32 | Linux) current_groups=$(echo "$current_groups" | cut -d: -f 2) ;; 33 | esac 34 | missing_groups= 35 | expected_groups=$(IFS=','; echo $2) 36 | 37 | for group in $expected_groups; do 38 | echo "$current_groups" | grep -E "\b$group\b" > /dev/null 39 | if [ "$?" -gt 0 ]; then 40 | missing_groups=1 41 | echo $group 42 | fi 43 | done 44 | 45 | [ -n "$missing_groups" ] && return 1 46 | return 0 47 | } 48 | 49 | case $action in 50 | desc) 51 | echo "assert presence of a user on the system" 52 | echo "> user admin" 53 | echo "--shell=/bin/fish" 54 | echo "--groups=admin,deploy" 55 | ;; 56 | status) 57 | needs_exec "useradd" || return $STATUS_FAILED_PRECONDITION 58 | 59 | row=$(user_get $handle) 60 | [ "$?" -gt 0 ] && return $STATUS_MISSING 61 | 62 | if [ -n "$shell" ]; then 63 | msg=$(user_shell "$row" $shell) 64 | if [ "$?" -gt 0 ]; then 65 | echo "--shell: expected $shell; is $msg" 66 | mismatched=1 67 | fi 68 | fi 69 | 70 | if [ -n "$groups" ]; then 71 | msg=$(user_groups $handle $groups) 72 | if [ "$?" -gt 0 ]; then 73 | echo "--groups: expected $groups; missing $(echo $msg)" 74 | partial=1 75 | fi 76 | fi 77 | [ -n "$mismatched" ] && return $STATUS_MISMATCH_UPGRADE 78 | [ -n "$partial" ] && return $STATUS_PARTIAL 79 | return 0 ;; 80 | 81 | install) 82 | args="-m" 83 | [ -n "$shell" ] && args="$args --shell $shell" 84 | [ -n "$groups" ] && groups_list=(${groups//,/ }) && args="$args --groups $groups" 85 | [[ -n "$groups_list" && "${groups_list[0]}" == "$handle" ]] && args="$args -g $handle" 86 | bake useradd $args $handle 87 | ;; 88 | 89 | upgrade) 90 | if [[ -n ${shell} ]] \ 91 | && ! user_shell "$(user_get "${handle}")" "${shell}"; then 92 | bake chsh -s $shell $handle 93 | fi 94 | missing=$(user_groups $handle $groups) 95 | if [ "$?" -gt 0 ]; then 96 | groups_to_create=$(IFS=','; echo $missing) 97 | for group in $groups_to_create; do 98 | bake adduser $handle $group 99 | done 100 | fi 101 | ;; 102 | 103 | *) return 1 ;; 104 | esac 105 | -------------------------------------------------------------------------------- /types/yum.sh: -------------------------------------------------------------------------------- 1 | 2 | action=$1 3 | name=$2 4 | shift 2 5 | case $action in 6 | desc) 7 | echo "asserts packages installed via yum on CentOS or RedHat" 8 | echo "* yum package-name" 9 | ;; 10 | status) 11 | baking_platform_is "Linux" || return $STATUS_UNSUPPORTED_PLATFORM 12 | needs_exec "yum" 0 13 | [ "$?" -gt 0 ] && return $STATUS_FAILED_PRECONDITION 14 | 15 | echo "$(bake rpm -qa)" | grep "^$name" 16 | [ "$?" -gt 0 ] && return $STATUS_MISSING 17 | 18 | echo "$(bake sudo yum list updates)" | grep "^$name" 19 | [ "$?" -eq 0 ] && return $STATUS_OUTDATED 20 | return $STATUS_OK 21 | ;; 22 | 23 | install|upgrade) 24 | bake sudo yum -y install $name 25 | ;; 26 | 27 | *) return 1 ;; 28 | esac 29 | -------------------------------------------------------------------------------- /types/zypper.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | action=$1 4 | name=$2 5 | shift 2 6 | 7 | case "${action}" in 8 | desc) 9 | printf '%s\n' \ 10 | 'asserts presence of packages installed via zypper (SUSE)' \ 11 | '* zypper packge-name (install/upgrade given package)' 12 | ;; 13 | status) 14 | needs_exec "rpm" || return "${STATUS_FAILED_PRECONDITION}" 15 | needs_exec "zypper" || return "${STATUS_FAILED_PRECONDITION}" 16 | 17 | bake rpm -q "${name}" &>/dev/null || return "${STATUS_MISSING}" 18 | ! bake zypper --terse list-updates | egrep -q " ${name} " \ 19 | || return "${STATUS_OUTDATED}" 20 | return "${STATUS_OK}" 21 | ;; 22 | install|upgrade|delete) 23 | case "${action}" in 24 | upgrade) action="update" ;; 25 | delete) action="remove" ;; 26 | esac 27 | bake sudo zypper -nt "${action}" "${name}" 28 | ;; 29 | *) return 1 ;; 30 | esac 31 | --------------------------------------------------------------------------------