singularity-cli 0.2.1 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/README.md +1 -0
- data/bin/singularity +14 -2
- data/lib/singularity.rb +154 -9
- metadata +2 -2
    
        checksums.yaml
    CHANGED
    
    | @@ -1,7 +1,7 @@ | |
| 1 1 | 
             
            ---
         | 
| 2 2 | 
             
            SHA1:
         | 
| 3 | 
            -
              metadata.gz:  | 
| 4 | 
            -
              data.tar.gz:  | 
| 3 | 
            +
              metadata.gz: 4f917e15b139df05230f76923b7c1296caa65d62
         | 
| 4 | 
            +
              data.tar.gz: 230d2d431af444026ae30b1ece3e4fcdd65edca8
         | 
| 5 5 | 
             
            SHA512:
         | 
| 6 | 
            -
              metadata.gz:  | 
| 7 | 
            -
              data.tar.gz:  | 
| 6 | 
            +
              metadata.gz: f294ca9b8811d3a3092475baa0f302ffdb10c14a40866d556bb2d96e57e8421ed111b7c41ba2b5320693121e09e24783a3afd734294a805dba2d31ddcec86b59
         | 
| 7 | 
            +
              data.tar.gz: 79759aa9d74a1692304e4c5e738d69f0d13e26904dbc01b9665a74729d5b584786594182b1ac615e8657b5d455232c18742adb84954865ab12b8cf4f5afb1cea
         | 
    
        data/README.md
    CHANGED
    
    
    
        data/bin/singularity
    CHANGED
    
    | @@ -5,7 +5,7 @@ require 'singularity' | |
| 5 5 | 
             
            ARGV << '--help' if ARGV.empty?
         | 
| 6 6 |  | 
| 7 7 | 
             
            def print_usage
         | 
| 8 | 
            -
              puts "Usage:\n\tsingularity delete <uri> <file>\n\tsingularity deploy <uri> <file> <release>"
         | 
| 8 | 
            +
              puts "Usage:\n\tsingularity delete <uri> <file.json>\n\tsingularity deploy <uri> <file.json> <release>\n\tsingularity run <uri> <file.json> <release> <script>"
         | 
| 9 9 | 
             
              exit
         | 
| 10 10 | 
             
            end
         | 
| 11 11 |  | 
| @@ -14,7 +14,6 @@ uri = ARGV[1] | |
| 14 14 | 
             
            file = ARGV[2]
         | 
| 15 15 |  | 
| 16 16 | 
             
            case action
         | 
| 17 | 
            -
             | 
| 18 17 | 
             
              when "delete"
         | 
| 19 18 | 
             
                print_usage unless ARGV.size == 3
         | 
| 20 19 | 
             
                Singularity::Deleter.new(uri,file).delete
         | 
| @@ -24,6 +23,19 @@ case action | |
| 24 23 | 
             
                release = ARGV[3]
         | 
| 25 24 | 
             
                Singularity::Deployer.new(uri,file,release).deploy
         | 
| 26 25 |  | 
| 26 | 
            +
              when "run"
         | 
| 27 | 
            +
                # remove the word "run" from ARGV, pass the remainder to Runner
         | 
| 28 | 
            +
                ARGV.shift
         | 
| 29 | 
            +
                Singularity::Runner.new(ARGV).runner
         | 
| 30 | 
            +
             | 
| 31 | 
            +
              when "runx"
         | 
| 32 | 
            +
                # this option is to skip the use of /sbin/my_init
         | 
| 33 | 
            +
                # (some commands won't run correctly when both are used)
         | 
| 34 | 
            +
                Singularity::Runner.new(ARGV).runner
         | 
| 35 | 
            +
             | 
| 36 | 
            +
              when "ssh"
         | 
| 37 | 
            +
                Singularity::Runner.new("ssh").runner
         | 
| 38 | 
            +
             | 
| 27 39 | 
             
              else
         | 
| 28 40 | 
             
                  print_usage
         | 
| 29 41 | 
             
            end
         | 
    
        data/lib/singularity.rb
    CHANGED
    
    | @@ -2,18 +2,17 @@ require 'erb' | |
| 2 2 | 
             
            require 'json'
         | 
| 3 3 | 
             
            require 'rest-client'
         | 
| 4 4 | 
             
            require 'colorize'
         | 
| 5 | 
            +
            require 'yaml'
         | 
| 5 6 |  | 
| 6 7 | 
             
            module Singularity
         | 
| 7 8 | 
             
              class Request
         | 
| 8 9 | 
             
                attr_accessor :release, :cpus, :mem, :envs, :schedule, :cmd, :arguments, :request_id, :repo, :release_string, :release_id_string
         | 
| 9 | 
            -
             | 
| 10 10 | 
             
                def get_binding
         | 
| 11 11 | 
             
                  binding()
         | 
| 12 12 | 
             
                end
         | 
| 13 13 | 
             
              end
         | 
| 14 14 |  | 
| 15 15 | 
             
              class Deployer
         | 
| 16 | 
            -
             | 
| 17 16 | 
             
                def initialize(uri, file, release)
         | 
| 18 17 | 
             
                  @uri = uri
         | 
| 19 18 | 
             
                  @file = file
         | 
| @@ -25,7 +24,6 @@ module Singularity | |
| 25 24 | 
             
                  print @data['id']
         | 
| 26 25 | 
             
                end
         | 
| 27 26 |  | 
| 28 | 
            -
             | 
| 29 27 | 
             
                def is_paused
         | 
| 30 28 | 
             
                  begin
         | 
| 31 29 | 
             
                    resp = RestClient.get "#{@uri}/api/requests/request/#{@data['id']}"
         | 
| @@ -45,7 +43,6 @@ module Singularity | |
| 45 43 | 
             
                      # create or update the request
         | 
| 46 44 | 
             
                      resp = RestClient.post "#{@uri}/api/requests", @data.to_json, :content_type => :json
         | 
| 47 45 | 
             
                    end
         | 
| 48 | 
            -
                    
         | 
| 49 46 | 
             
                    # deploy the request
         | 
| 50 47 | 
             
                    @data['requestId'] = @data['id']
         | 
| 51 48 | 
             
                    @data['id'] = "#{@release}.#{Time.now.to_i}"
         | 
| @@ -54,9 +51,7 @@ module Singularity | |
| 54 51 | 
             
                     'user' => `whoami`.chomp,
         | 
| 55 52 | 
             
                     'unpauseOnSuccessfulDeploy' => false
         | 
| 56 53 | 
             
                    }
         | 
| 57 | 
            -
             | 
| 58 54 | 
             
                    resp = RestClient.post "#{@uri}/api/deploys", deploy.to_json, :content_type => :json
         | 
| 59 | 
            -
             | 
| 60 55 | 
             
                    puts " DEPLOYED".green
         | 
| 61 56 | 
             
                  rescue Exception => e
         | 
| 62 57 | 
             
                    puts " #{e.response}".red
         | 
| @@ -65,12 +60,10 @@ module Singularity | |
| 65 60 | 
             
              end
         | 
| 66 61 |  | 
| 67 62 | 
             
              class Deleter
         | 
| 68 | 
            -
             | 
| 69 63 | 
             
                def initialize(uri, file)
         | 
| 70 64 | 
             
                  @uri = uri
         | 
| 71 65 | 
             
                  @file = file
         | 
| 72 66 | 
             
                end
         | 
| 73 | 
            -
             | 
| 74 67 | 
             
                # Deleter.delete -- arguments are <uri>, <file>
         | 
| 75 68 | 
             
                def delete
         | 
| 76 69 | 
             
                  begin
         | 
| @@ -82,6 +75,158 @@ module Singularity | |
| 82 75 | 
             
                    puts "#{task_id} #{$!.response}"
         | 
| 83 76 | 
             
                  end
         | 
| 84 77 | 
             
                end
         | 
| 85 | 
            -
              end | 
| 78 | 
            +
              end
         | 
| 79 | 
            +
             | 
| 80 | 
            +
              class Runner
         | 
| 81 | 
            +
                def initialize(script)
         | 
| 82 | 
            +
                  #########################################################
         | 
| 83 | 
            +
                  # TODO
         | 
| 84 | 
            +
                  # check to see that .mescal.json and mesos-deploy.yml exist
         | 
| 85 | 
            +
                  #########################################################
         | 
| 86 | 
            +
                  @script = script
         | 
| 87 | 
            +
                  # read .mescal.json for ssh command, image, release number, cpus, mem
         | 
| 88 | 
            +
                  @configData = JSON.parse(ERB.new(open(File.join(Dir.pwd, ".mescal.json")).read).result(Request.new.get_binding))
         | 
| 89 | 
            +
                  @sshCmd = @configData['sshCmd']
         | 
| 90 | 
            +
                  @image = @configData['image'].split(':')[0]
         | 
| 91 | 
            +
                  @release = @configData['image'].split(':')[1]
         | 
| 92 | 
            +
             | 
| 93 | 
            +
                  # read mesos-deploy.yml for singularity url
         | 
| 94 | 
            +
                  @mesosDeployConfig = YAML.load_file(File.join(Dir.pwd, "mesos-deploy.yml"))
         | 
| 95 | 
            +
                  @uri = @mesosDeployConfig['singularity_url']
         | 
| 96 | 
            +
             | 
| 97 | 
            +
                  # create request/deploy json data
         | 
| 98 | 
            +
                  @data = {
         | 
| 99 | 
            +
                    'command' => "/sbin/my_init",
         | 
| 100 | 
            +
                    'resources' => {
         | 
| 101 | 
            +
                      'memoryMb' => @configData['mem'],
         | 
| 102 | 
            +
                      'cpus' => @configData['cpus'],
         | 
| 103 | 
            +
                      'numPorts' => 1
         | 
| 104 | 
            +
                    },
         | 
| 105 | 
            +
                    'env' => {
         | 
| 106 | 
            +
                      'APPLICATION_ENV' => "production"
         | 
| 107 | 
            +
                    },
         | 
| 108 | 
            +
                    'requestType' => "RUN_ONCE",
         | 
| 109 | 
            +
                    'containerInfo' => {
         | 
| 110 | 
            +
                      'type' => "DOCKER",
         | 
| 111 | 
            +
                      'docker' => {
         | 
| 112 | 
            +
                        'image' => @configData['image'],
         | 
| 113 | 
            +
                        'network' => "BRIDGE",
         | 
| 114 | 
            +
                        'portMappings' => [{
         | 
| 115 | 
            +
                          'containerPortType': "LITERAL",
         | 
| 116 | 
            +
                          'containerPort': 22,
         | 
| 117 | 
            +
                          'hostPortType': "FROM_OFFER",
         | 
| 118 | 
            +
                          'hostPort': 0
         | 
| 119 | 
            +
                        }]
         | 
| 120 | 
            +
                      }
         | 
| 121 | 
            +
                    }
         | 
| 122 | 
            +
                  }
         | 
| 123 | 
            +
                  # either we typed 'singularity ssh'
         | 
| 124 | 
            +
                  if @script == "ssh"
         | 
| 125 | 
            +
                    @data['id'] = Dir.pwd.split('/').last + "_SSH"
         | 
| 126 | 
            +
                    @data['command'] = "#{@sshCmd}"
         | 
| 127 | 
            +
                  # or we passed a script/commands to 'singularity run'
         | 
| 128 | 
            +
                  else
         | 
| 129 | 
            +
                    # if we passed "runx", then skip use of /sbin/my_init
         | 
| 130 | 
            +
                    if @script[0] == "runx"
         | 
| 131 | 
            +
                      @data['arguments'] = [] # don't use "--" as first argument
         | 
| 132 | 
            +
                      @data['command'] = @script[1] #remove "runx" from commands
         | 
| 133 | 
            +
                      @script.shift
         | 
| 134 | 
            +
                      @data['id'] = @script.join("-").tr('@/\*?% []#$', '_')
         | 
| 135 | 
            +
                      @data['id'][0] = ''
         | 
| 136 | 
            +
                      @script.shift
         | 
| 137 | 
            +
                    # else join /sbin/my_init with your commands
         | 
| 138 | 
            +
                    else
         | 
| 139 | 
            +
                      @data['arguments'] = ["--"]
         | 
| 140 | 
            +
                      @data['id'] = @script.join("-").tr('@/\*?% []#$', '_')
         | 
| 141 | 
            +
                      @data['id'][0] = ''
         | 
| 142 | 
            +
                    end
         | 
| 143 | 
            +
                    @script.each { |i| @data['arguments'].push i }
         | 
| 144 | 
            +
                  end
         | 
| 145 | 
            +
                end
         | 
| 146 | 
            +
             | 
| 147 | 
            +
                def is_paused
         | 
| 148 | 
            +
                  begin
         | 
| 149 | 
            +
                    resp = RestClient.get "#{@uri}/api/requests/request/#{@data['id']}"
         | 
| 150 | 
            +
                    JSON.parse(resp)['state'] == 'PAUSED'
         | 
| 151 | 
            +
                  rescue
         | 
| 152 | 
            +
                    print " Deploying request...".light_green
         | 
| 153 | 
            +
                    false
         | 
| 154 | 
            +
                  end
         | 
| 155 | 
            +
                end
         | 
| 156 | 
            +
             | 
| 157 | 
            +
                def runner
         | 
| 158 | 
            +
                  begin
         | 
| 159 | 
            +
                    if is_paused()
         | 
| 160 | 
            +
                      puts " PAUSED, SKIPPING.".yellow
         | 
| 161 | 
            +
                      return
         | 
| 162 | 
            +
                    else
         | 
| 163 | 
            +
                      # create or update the request
         | 
| 164 | 
            +
                      RestClient.post "#{@uri}/api/requests", @data.to_json, :content_type => :json
         | 
| 165 | 
            +
                    end
         | 
| 166 | 
            +
             | 
| 167 | 
            +
                    # deploy the request
         | 
| 168 | 
            +
                    @data['requestId'] = @data['id']
         | 
| 169 | 
            +
                    @data['id'] = "#{@release}.#{Time.now.to_i}"
         | 
| 170 | 
            +
                    @deploy = {
         | 
| 171 | 
            +
                     'deploy' => @data,
         | 
| 172 | 
            +
                     'user' => `whoami`.chomp,
         | 
| 173 | 
            +
                     'unpauseOnSuccessfulDeploy' => false
         | 
| 174 | 
            +
                    }
         | 
| 175 | 
            +
                    RestClient.post "#{@uri}/api/deploys", @deploy.to_json, :content_type => :json
         | 
| 176 | 
            +
                    puts " Deploy succeeded.".green
         | 
| 177 | 
            +
             | 
| 178 | 
            +
                    # get active tasks until ours shows up so we can get IP/PORT
         | 
| 179 | 
            +
                    begin
         | 
| 180 | 
            +
                      @thisTask = ''
         | 
| 181 | 
            +
                      @tasks = RestClient.get "#{@uri}/api/tasks/active", :content_type => :json
         | 
| 182 | 
            +
                      @tasks = JSON.parse(@tasks)
         | 
| 183 | 
            +
                      @tasks.each do |entry|
         | 
| 184 | 
            +
                        if entry['taskRequest']['request']['id'] == @data['requestId']
         | 
| 185 | 
            +
                          @thisTask = entry
         | 
| 186 | 
            +
                        end
         | 
| 187 | 
            +
                      end
         | 
| 188 | 
            +
                    end until @thisTask != ''
         | 
| 189 | 
            +
                    @ip = @thisTask['offer']['url']['address']['ip']
         | 
| 190 | 
            +
                    @port = @thisTask['mesosTask']['container']['docker']['portMappings'][0]['hostPort']
         | 
| 191 | 
            +
             | 
| 192 | 
            +
                    # SSH into the machine
         | 
| 193 | 
            +
                    if @script == "ssh"
         | 
| 194 | 
            +
                      # uses "begin end until" because "system" will keep returning "false" unless the command exits with success
         | 
| 195 | 
            +
                      # this makes sure that the docker image has completely started and the SSH command succeeds
         | 
| 196 | 
            +
                      where = Dir.pwd.split('/').last
         | 
| 197 | 
            +
                      puts " Opening a shell to #{where}, please wait a moment...".light_blue
         | 
| 198 | 
            +
                      puts " STDOUT / STDERR will print to console when session is over.".light_blue
         | 
| 199 | 
            +
                      begin end until system "ssh -o LogLevel=quiet -o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no root@#{@ip} -p #{@port}"
         | 
| 200 | 
            +
                    else
         | 
| 201 | 
            +
                      puts " Deployed and running #{@data['command']} #{@data['arguments']}".light_green
         | 
| 202 | 
            +
                    end
         | 
| 203 | 
            +
             | 
| 204 | 
            +
                    # need to wait for "task_finished" or "task_running & ssh" before we can ask for STDOUT/STDERR
         | 
| 205 | 
            +
                    begin
         | 
| 206 | 
            +
                      @taskState = RestClient.get "#{@uri}/api/history/task/#{@thisTask['taskId']['id']}"
         | 
| 207 | 
            +
                      @taskState = JSON.parse(@taskState)
         | 
| 208 | 
            +
                      @taskState["taskUpdates"].each do |update|
         | 
| 209 | 
            +
                        @taskState = update['taskState']
         | 
| 210 | 
            +
                      end
         | 
| 211 | 
            +
                    end until @taskState == "TASK_FINISHED" || (@taskState == "TASK_RUNNING" and @script == "ssh")
         | 
| 212 | 
            +
             | 
| 213 | 
            +
                    # output STDOUT / STDERR to shell
         | 
| 214 | 
            +
                    puts ""
         | 
| 215 | 
            +
                    stdout = RestClient.get "#{@uri}/api/sandbox/#{@thisTask['taskId']['id']}/read", {params: {path: "stdout", length: 30000, offset: 0}}
         | 
| 216 | 
            +
                    stdout = JSON.parse(stdout)
         | 
| 217 | 
            +
                    puts "stdout: ".cyan
         | 
| 218 | 
            +
                    puts stdout['data'].light_cyan
         | 
| 219 | 
            +
                    stderr = RestClient.get "#{@uri}/api/sandbox/#{@thisTask['taskId']['id']}/read", {params: {path: "stderr", length: 30000, offset: 0}}
         | 
| 220 | 
            +
                    stderr = JSON.parse(stderr)
         | 
| 221 | 
            +
                    puts "stderr: ".red
         | 
| 222 | 
            +
                    puts stderr['data'].light_magenta
         | 
| 86 223 |  | 
| 224 | 
            +
                    # finally, delete the request (which also deletes the corresponding task)
         | 
| 225 | 
            +
                    RestClient.delete "#{@uri}/api/requests/request/#{@data['requestId']}"
         | 
| 226 | 
            +
             | 
| 227 | 
            +
                  rescue Exception => e
         | 
| 228 | 
            +
                    puts " #{e.response}".red
         | 
| 229 | 
            +
                  end
         | 
| 230 | 
            +
                end
         | 
| 231 | 
            +
              end
         | 
| 87 232 | 
             
            end
         | 
    
        metadata
    CHANGED
    
    | @@ -1,7 +1,7 @@ | |
| 1 1 | 
             
            --- !ruby/object:Gem::Specification
         | 
| 2 2 | 
             
            name: singularity-cli
         | 
| 3 3 | 
             
            version: !ruby/object:Gem::Version
         | 
| 4 | 
            -
              version: 0. | 
| 4 | 
            +
              version: 0.3.0
         | 
| 5 5 | 
             
            platform: ruby
         | 
| 6 6 | 
             
            authors:
         | 
| 7 7 | 
             
            - Travis Webb
         | 
| @@ -9,7 +9,7 @@ authors: | |
| 9 9 | 
             
            autorequire: 
         | 
| 10 10 | 
             
            bindir: bin
         | 
| 11 11 | 
             
            cert_chain: []
         | 
| 12 | 
            -
            date: 2016- | 
| 12 | 
            +
            date: 2016-10-03 00:00:00.000000000 Z
         | 
| 13 13 | 
             
            dependencies:
         | 
| 14 14 | 
             
            - !ruby/object:Gem::Dependency
         | 
| 15 15 | 
             
              name: rest-client
         |