• Stars
    star
    222
  • Rank 179,123 (Top 4 %)
  • Language
    Ruby
  • License
    MIT License
  • Created over 12 years ago
  • Updated over 6 years ago

Reviews

There are no reviews yet. Be the first to send feedback to the community and the maintainers!

Repository Details

A deployment system, with design goals 1: Magic and 2: More Magic

Salticid

Salticidae is the family of jumping spiders: small, nimble, and quite intelligent within a limited domain.

Salticidae Maratus sp. Salticid installing riak

Salticid runs commands on other computers via SSH, with just enough programmability and structure to run small-scale (~10-20 nodes in parallel) deployments. It relies on horrifyingly evil ruby metaprogramming to provide a small DSL which looks a bit like a shell.

gem install salticid
# example.rb
host 'my_machine.com' do
  user 'my_username'

  task :hello do
    exec! 'ls -la /', echo: true
  end
end
salticid -l example.rb -h my_machine hello

Salticid will ssh to my_machine (you'll want your SSH agent to have your credentials cached) and run the hello task, which lists the root directory and echoes it to the console. If you don't have a remote node available, you could always use host 'localhost'. Hit q to quit the ncurses interface when you're satisfied.

We don't have to use fully qualified paths. Salticid hosts have a state machine which tracks the current directory:

host 'my_machine.com' do
  user 'my_username'

  task :hello do
    cd '/'
    cd 'tmp'
    log "Now in directory", cwd
    exec! 'ls -la /', echo: true
  end
end

You can scroll using the arrow keys, or pgup/pgdn. If you don't like q, you can always leave via Control+C, too. Sometimes salticid doesn't shut down actively running processes properly: ^C will make sure they quit.

Some commands are built into lib/salticid/host.rb. Any ruby code you write inside a task is instance_eval'd inside a Host object, so you have access to any methods there. Any unrecognized methods will be intrepreted as shell commands (or possibly as roles or tasks; we'll get to that later). So we can replace exec! with:

host 'my_machine.com' do
  user 'my_username'

  task :hello do
    ls '-la', '/', echo: true
  end
end

Salticid will escape any strings you pass this way. cat 'foo bar' in ruby will turn into the shell command cat "foo bar". exec! doesn't escape its string: exec! 'cat foo bar' turns into the shell command cat foo bar. You can always use escape("some string") if you need to do your own escaping.

Salticid functions return whatever the process printed to stdout, so it's easy to get information from the remote system, massage it in Ruby, and use it in your program:

host 'my_machine.com' do
  user 'my_username'

  task :hello do
    my_username = whoami
    log "I am #{my_username.inspect}"
  end
end

The bar at the top of the interface turns green when the task completes successfully, or red if it fails. Salticid checks the exit status of every command, and throws an exception if it's non-zero:

host 'my_machine.com' do
  user 'my_username'

  task :hello do
    cat '/frobnitz'
  end
end

Salticid logs the command which failed, the exit status, and the processes' stderr and stdout. Now you can use Ruby's exception handling techniques to write more complex tasks:

host 'my_machine.com' do
  user 'my_username'

  task :hello do
    x = begin
          cat '/frobnitz' 
        rescue
          :purple
        end
    log "Got #{x}"
  end
end

Salticid is asynchronous, and you can register handlers to process stderr and stdout interactively, or echo it to the interface as lines arrive:

task :tail do
  tail '-F', '/var/log/syslog', echo: true
end

This one you have to kill with ^C.

You can redirect to a file with the :to argument, and send stdin to a process using :stdin:

echo :stdin 'hallo', to: '/tmp/salutations'

Become a different user for the scope of a block:

sudo do
  shutdown '-h', :now
end

sudo :postgres do
  createdb :omghai2u
end

Upload and download files (you probably want to check the source: these do some helpful but non-obvious things)

# Remote path, local path:
download '/etc/passwd', 'omghax.txt'

__DIR__ is the directory the current file is in, so it's easy to keep scripts and their related data files together. / combines path fragments. Pretty much anywhere in Salticid, you can treat symbols and strings interchangeably.

echo File.read(__DIR__/:riak/'app.config'),
     to: '/opt/riak/rel/riak/etc/app.config'

Often, you want to replace a file while preserving its ownership and mode:

sudo_upload __DIR__/'apache.conf', '/etc/apache2/apache.conf'

Higher-level structure

You can group related tasks together into a role, and invoke tasks from each other to bundle together dependencies:

role :riak do
  task :start do
    sudo do
      cd '/opt/riak/rel/riak'
      exec! 'bash -c "ulimit -n 10000 && bin/riak start"', echo: true
    end
  end

  task :restart do
    sudo do
      cd '/opt/riak/rel/riak'
      exec! 'bin/riak start', echo: true
    end
  end

  task :stop do
    sudo do
      cd '/opt/riak/rel/riak'
      exec! 'bin/riak stop', echo: true
    end
  end

  task :ping do
    sudo do
      exec! '/opt/riak/rel/riak/bin/riak ping', echo: true
    end
  end

  task :deploy do
    sudo do
      riak.stop
      echo File.read(__DIR__/:riak/'app.config'), to: '/opt/riak/rel/riak/etc/app.config'
      echo File.read(__DIR__/:riak/'vm.args').gsub('%%NODE%%', name), to: '/opt/riak/rel/riak/etc/vm.args'
    end
    riak.start
  end
end

You can assign hosts to roles individually:

host 'my-host' do
  role :riak
  role :postgres
end

And hosts can be bound together into groups:

group :texas do
  host :n1
  host :n2
  host :n3
  host :n4

  each_host do
    user :deploy

    # You can assign instance variables to hosts to keep track of state.
    # @password is used by sudo and friends: 
    @password = "sup"

    # We can add roles and tasks here too:
    role :riak
    role :postgres
  end
end

Use salticid -s salticid to show all the groups, roles, hosts, and top-level tasks defined. You can run a task only on hosts belonging to a role, group, or a specific host with -r, -g, and -h respectively; or on all appropriate hosts by default:

# Runs the deploy task in the riak role, on every host which has the riak role.
salticid riak.deploy

Salticid parallelizes across hosts. Use the left and right arrow keys, or tab, to switch between hosts in the ncurses interface.

Typically you'll use salticid with a common set of files over and over again, instead of loading files explicitly with -l. You can put any commands to source in ~/.salticidrc (or .salticidrc in the current working directory); they'll be evaluated on the top-level salticid context:

load ENV['HOME'] + '/my-deploy/salticid/*.rb'

load can be used to load files in any salticid context. It obeys shell glob expansion.

An example

Take a look at Jepsen's, old salticid config: https://github.com/aphyr/jepsen/blob/old/salticid/main.rb

Caveats

Salticid starts to get unresponsive or flaky above 10 or 20 nodes in parallel. There's no reason the interface and scheduler can't execute tasks in small chunks instead of all at once; I just never needed the feature so I didn't write it.

Salticid's ncurses interface is a little flaky/slow. It doesn't scroll line-wise very well.

Salticid is not a cloud deployment system, though you could dynamically create hosts to make it into one. As presented here, it's designed for fixed sets of nodes. We used it at Showyou and Vodpod to manage ~30 physical nodes in two datacenters.

Salticid has no central control of deployment. There is no server or locking. There is nothing to install. All you need is SSH. Anyone with credentials can use it, which makes it ideal for scenarios when you don't have control over the entire infrastructure but still need to automate some tasks. We had a small team and kept our config in a git repo, and added a ruby check to verify the repo was up to date before running any commands. All is anarchy.

This is not a config management tool. It has no notion of convergence or scheduled checks, and can't tell you when things are out of sync with a target. On the other hand, it runs at interactive latencies and tells you what went wrong immediately, so you may find keeping systems up to date is easier with Salticid. I strongly recommend writing idempotent tasks so you can just re-run them whenever you make a change or want to confirm everything is in order.

apt-get -y is your friend. :)

Advanced

Salticid can tunnel its connections through an intermediate SSH gateway node. I like to give each user a distinct user account on the gateway and a special key for the gateway, and share a single deploy account/keypair for everything behind the gateway; makes it fast and easy to revoke a pubkey for a specific user if their laptop is stolen, without having to touch every machine.

gw 'gw.mycorp.com' do
  user ENV['USER']
end

[1,2,3,4,5].each do |i|
  host "db#{i}" do
    gw 'gw.mycorp.com'
    user :deploy
    role :ubuntu
    role :riak
  end
end

You can break out of Salticid at any point, which is useful if you don't want the full interface:

task :console do
  Salticid::Interface.shutdown false

  if tunnel
    tunnel.open(name, 22) do |port|
      system "ssh #{user}@localhost -p #{port}"
    end
  else
    system "ssh #{user}@#{name}"
  end
end

Now salticid -h my.host.com console will drop you into an SSH console on that node.

History

Salticid is a deployment tool I wrote for Vodpod and Showyou in a couple of weekends. Its only design goals were:

  1. Magic
  2. More Magic

It violates every principle of good software development possible. It is clunky, slow, buggy, and dangerous.

It also worked surprisingly well. YMMV. d=('_~)=b

More Repositories

1

distsys-class

Class materials for a distributed systems lecture series
8,983
star
2

tesser

Clojure reducers, but for parallel execution: locally and on distributed systems.
Clojure
867
star
3

meangirls

Convergent Replicated Data Types
Ruby
650
star
4

tund

SSH reverse tunnel daemon
Ruby
418
star
5

tea-time

Lightweight Clojure task scheduler
Clojure
240
star
6

dom-top

Unorthodox control flow, for Clojurists with masochistic sensibilities.
Clojure
204
star
7

timelike

A library for simulating parallel systems, in Clojure
Clojure
184
star
8

partitions-post

A blog post on network partitions in practice
182
star
9

clj-antlr

Clojure bindings for the ANTLR 4 parser
Clojure
167
star
10

less-awful-ssl

Sssh no tears, only TLS now. For Clojure.
Clojure
154
star
11

interval-metrics

Clojure data structures for performance metrics over discrete time intervals.
Clojure
118
star
12

dist-sagas

A paper on sagas in distributed systems
TeX
91
star
13

gretchen

Offline serializability verification, in Clojure
Clojure
68
star
14

prism

Automatically re-run clojure tests
Clojure
59
star
15

verschlimmbesserung

An etcd client with modern Clojure sensibilities
Clojure
56
star
16

merkle

Clojure Merkle Trees
Clojure
51
star
17

gnuplot

Clojure gnuplot bindings
Clojure
47
star
18

risky

A lightweight Ruby ORM for Riak
Ruby
40
star
19

schadenfreude

Clojure benchmarking tools
Clojure
35
star
20

jepsen-talks

Slides and resources for talks on partition tolerance
Clojure
33
star
21

meitner

Explodes Clojure functions and macros into dependency graphs
Clojure
30
star
22

aesahaettr

Sharding, partitioning, and consistent hashing for Clojure. May release spectres.
Clojure
26
star
23

bitcask-ruby

An (incomplete) interface to the Bitcask storage system
Ruby
19
star
24

ustate

micro state daemon
Ruby
18
star
25

salesfear

A Clojure salesforce client.
Clojure
17
star
26

construct

Extensible, persistent, structured configuration for Ruby
Ruby
16
star
27

skewbinheap

A Skew Binomial Heap for Erlang.
Erlang
15
star
28

yamr

A Linux Yammer client.
Ruby
12
star
29

bifurcan-clj

Clojure wrapper for the Bifurcan family of data structures
Clojure
12
star
30

tumblr-archiver

Hacky Clojure program to download media from tumblr liked posts
Clojure
12
star
31

cyclic.js

Cyclic time series data structures for javascript
JavaScript
10
star
32

riemann-bench

An example for using the Reimann Clojure client
Clojure
10
star
33

london-gen

Silly London Landmarks
Clojure
8
star
34

gifdex

A gif tagging server for local use
Clojure
8
star
35

mtrc

Ruby metrics
Ruby
8
star
36

adaptive-executor

Adaptive threadpool executor experiment
Clojure
6
star
37

lights

Change your Hue lights to randomly generated colors, continuously
Clojure
5
star
38

thought-leaders

The definitive list of thought leaders
5
star
39

mastodon-utils

Utilities for working with Mastodon's API
Clojure
5
star
40

prometheus-mastodon-exporter

Exports Mastodon statistics for polling by Prometheus
Clojure
5
star
41

cortex-reaver

A dangerous Ruby blog engine, with a photographic memory.
Ruby
5
star
42

hangman

A ridiculously overpowered hangman AI in Clojure
Clojure
4
star
43

qsd-phase-space-reconstruction

Some ruby scripts for exploring datasets in an attempt to reconstruct phase space dynamics (and to identify lyapunov exponents) of a QSD-simulated Duffing oscillator
Ruby
4
star
44

exocora

A lightweight CGI script framework
Ruby
3
star
45

joedahato

Predict's Joe Damato's hat choices
Clojure
3
star
46

tattoo

Overkill
Clojure
2
star
47

producer_consumer

Ruby queue-backed producer consumer gem
Ruby
2
star
48

ruby-vodpod

Ruby bindings for the Vodpod API.
Ruby
2
star
49

frisk-management

NLP + erotic fiction -> PowerPoint slides
Clojure
2
star
50

heliotrope

A client for the distributed processing system Fabric
Ruby
2
star
51

qsd-tangent

Fork of QSD library with tangent space evolution
C++
2
star
52

caremad

Consistent Commutative Replicated DataTypes
2
star
53

euler-clj

Project Euler solutions in Clojure
Clojure
1
star
54

req-replay

silly experiment
Clojure
1
star
55

autotags

Jquery javascript tag editor with autocomplete
JavaScript
1
star
56

riakeys

Riak key cache (apparently impossible)
Clojure
1
star
57

clojure-perf

Mucking around with clojure performance testing
Clojure
1
star
58

mecha-query

Clojure
1
star