Skip to content

Commit 76e13a5

Browse files
committed
adding shell
Signed-off-by: Vanessa Sochat <vsochat@stanford.edu>
1 parent 4aa9c1c commit 76e13a5

File tree

6 files changed

+137
-28
lines changed

6 files changed

+137
-28
lines changed

scompose/client/__init__.py

Lines changed: 19 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -82,11 +82,19 @@ def get_parser():
8282
config = subparsers.add_parser("config",
8383
help="Validate and view the compose file")
8484

85-
# Create (assumes built already)
85+
# Create (assumes built already), Up (will also build, if able)
8686

8787
create = subparsers.add_parser("create",
8888
help="create instances")
8989

90+
up = subparsers.add_parser("up",
91+
help="build and start containers")
92+
93+
for sub in [create, up]:
94+
sub.add_argument('--writable-tmpfs', dest="writable_tmpfs",
95+
help="allow the instance to write to tmp",
96+
default=False, action='store_true')
97+
9098
# Down
9199

92100
down = subparsers.add_parser("down",
@@ -95,8 +103,9 @@ def get_parser():
95103
execute = subparsers.add_parser("exec",
96104
help="execute a command to an instance")
97105

98-
images = subparsers.add_parser("images",
99-
help="list running instances")
106+
shell = subparsers.add_parser("shell",
107+
help="shell into an instance")
108+
100109

101110
kill = subparsers.add_parser("kill",
102111
help="kill instances")
@@ -110,17 +119,16 @@ def get_parser():
110119
restart = subparsers.add_parser("restart",
111120
help="stop and start containers.")
112121

113-
rm = subparsers.add_parser("rm",
114-
help="remove non-running container images")
115-
116-
up = subparsers.add_parser("up",
117-
help="build and start containers")
118-
119122
# Add list of names
120123
for sub in [create, down, up]:
121-
sub.add_argument('names', nargs="?",
124+
sub.add_argument('names', nargs="*",
122125
help='the names of the instances to target')
123126

127+
# Only one name allowed
128+
for sub in [shell, execute, kill]:
129+
sub.add_argument('name', nargs=1,
130+
help='the name of the instance to target')
131+
124132
return parser
125133

126134

@@ -129,7 +137,6 @@ def main():
129137
parser based on the command, and then import and call the appropriate
130138
main.
131139
'''
132-
133140
parser = get_parser()
134141

135142
def help(return_code=0):
@@ -165,13 +172,11 @@ def help(return_code=0):
165172
elif args.command == "config": from .config import main
166173
elif args.command == "down": from .down import main
167174
elif args.command == "exec": from .exec import main
168-
elif args.command == "images": from .images import main
169175
elif args.command == "kill": from .kill import main
170176
elif args.command == "logs": from .logs import main
171177
elif args.command == "ps": from .ps import main
172178
elif args.command == "restart": from .restart import main
173-
elif args.command == "rm": from .rm import main
174-
elif args.command == "stop": from .stop import main
179+
elif args.command == "shell": from .shell import main
175180
elif args.command == "up": from .up import main
176181

177182
# Pass on to the correct parser

scompose/client/create.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,4 +38,4 @@ def main(args, parser, extra):
3838
env_file=args.env_file)
3939

4040
# Create instances, and if none specified, create all
41-
project.create(args.names)
41+
project.create(args.names, writable_tmpfs=writable_tmpfs)

scompose/client/shell.py

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
'''
2+
3+
Copyright (C) 2019 Vanessa Sochat.
4+
5+
This program is free software: you can redistribute it and/or modify it
6+
under the terms of the GNU Affero General Public License as published by
7+
the Free Software Foundation, either version 3 of the License, or (at your
8+
option) any later version.
9+
10+
This program is distributed in the hope that it will be useful, but WITHOUT
11+
ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
12+
FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public
13+
License for more details.
14+
15+
You should have received a copy of the GNU Affero General Public License
16+
along with this program. If not, see <https://www.gnu.org/licenses/>.
17+
18+
'''
19+
20+
from scompose.project import Project
21+
import logging
22+
import json
23+
import sys
24+
import os
25+
26+
27+
log = logging.getLogger(__name__)
28+
29+
def main(args, parser, extra):
30+
'''bring one or more instances down
31+
'''
32+
# Initialize the project
33+
project = Project(filename=args.file,
34+
name=args.project_name,
35+
env_file=args.env_file)
36+
37+
# Create instances, and if none specified, create all
38+
project.shell(args.name[0])

scompose/client/up.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,4 +38,4 @@ def main(args, parser, extra):
3838
env_file=args.env_file)
3939

4040
# Create instances, and if none specified, create all
41-
project.up(args.names)
41+
project.up(args.names, writable_tmpfs=writable_tmpfs)

scompose/project/instance.py

Lines changed: 33 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -36,14 +36,15 @@ class Instance(object):
3636
named according to "name" is created for the image binary.
3737
params: all of the parameters defined in the configuration.
3838
'''
39-
def __init__(self, name, working_dir, params=None):
39+
def __init__(self, name, working_dir, sudo=False, params=None):
4040

4141
if not params:
4242
params = {}
4343

4444
self.image = None
4545
self.recipe = None
4646
self.instance = None
47+
self.sudo = sudo
4748
self.set_name(name, params)
4849
self.set_context(params)
4950
self.set_volumes(params)
@@ -143,6 +144,19 @@ def set_ports(self, params):
143144
'''
144145
self.ports = params.get('ports', [])
145146

147+
# Commands
148+
149+
def _get_network_commands(self):
150+
'''take a list of ports, return the list of --network-args to
151+
ensure they are bound correctly.
152+
'''
153+
ports = []
154+
if self.ports:
155+
ports.append('--net')
156+
for pair in self.ports:
157+
ports += ['--network-args', '"portmap=%s/tcp"' % pair]
158+
return ports
159+
146160
def _get_bind_commands(self):
147161
'''take a list of volumes, and return the bind commands for Singularity
148162
'''
@@ -266,7 +280,7 @@ def stop(self):
266280

267281
# Create and Delete
268282

269-
def up(self, working_dir):
283+
def up(self, working_dir, sudo=False, writable_tmpfs=False):
270284
'''up is the same as create, but like Docker, we build / pull instances
271285
first.
272286
'''
@@ -275,10 +289,10 @@ def up(self, working_dir):
275289
# Do a build if necessary
276290
if not os.path.exists(image):
277291
self.build(working_dir)
278-
self.create()
292+
self.create(writable_tmpfs=writable_tmpfs)
279293

280294

281-
def create(self):
295+
def create(self, sudo=False, writable_tmpfs=False):
282296
'''create an instance, if it doesn't exist.
283297
'''
284298
image = self.get_image()
@@ -295,7 +309,21 @@ def create(self):
295309
if not self.exists():
296310

297311
bot.info("Creating %s" % self.name)
312+
313+
# Volumes
298314
binds = self._get_bind_commands()
315+
316+
# Ports
317+
ports = self._get_network_commands()
318+
319+
# Hostname
320+
hostname = ["--hostname", self.name]
321+
322+
# Writable Temporary Directory
323+
if writable_tmpfs:
324+
hostname += ['--writable_tmpfs']
325+
299326
self.instance = self.client.instance(name=self.name,
300-
options=binds,
327+
sudo=self.sudo,
328+
options=binds + ports + hostname,
301329
image=image)

scompose/project/project.py

Lines changed: 45 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@ def __init__(self, filename=None, name=None, env_file=None):
4040
self.load()
4141
self.parse()
4242
self.env_file = env_file
43+
self.client = get_client()
4344

4445
# Names
4546

@@ -82,6 +83,21 @@ def set_name(self, name):
8283

8384
# Listing
8485

86+
def ps(self):
87+
'''ps will print a table of instances, including pids and names.
88+
'''
89+
instance_names = self.get_instance_names()
90+
table = []
91+
for instance in self.client.instances(quiet=True, sudo=self.sudo):
92+
if instance.name in instance_names:
93+
image = os.path.basename(instance._image)
94+
table.append([instance.name.rjust(12),
95+
instance.pid,
96+
image])
97+
98+
bot.custom(prefix="INSTANCES ", message="NAME PID IMAGE",color="CYAN")
99+
bot.table(table)
100+
85101
def iter_instances(self, names):
86102
'''yield instances one at a time. If an invalid name is given,
87103
exit with error.
@@ -116,21 +132,42 @@ def load(self):
116132

117133
def parse(self):
118134
'''parse a loaded config'''
135+
136+
# If a port is defined, we need root.
137+
self.sudo = False
138+
119139
if self.config is not None:
120140

141+
# If any of config has ports, must use sudo for networking
142+
for name in self.config.get('instances', []):
143+
params = self.config['instances'][name]
144+
if "ports" in params:
145+
self.sudo = True
146+
121147
# Create each instance object
122148
for name in self.config.get('instances', []):
123149
params = self.config['instances'][name]
124150

125151
# Validates params
126152
self.instances[name] = Instance(name=name,
127153
params=params,
154+
sudo=self.sudo,
128155
working_dir=self.working_dir)
129156

130157
# Update volumes with volumes from
131158
for _, instance in self.instances.items():
132159
instance.set_volumes_from(self.instances)
133-
160+
161+
# Commands
162+
163+
def shell(self, name):
164+
'''if an instance exists, shell into it.
165+
'''
166+
if self.instances:
167+
if name in self.instances:
168+
instance = self.instances[name]
169+
if instance.exists():
170+
self.client.shell(instance.instance.get_uri())
134171

135172
# Config
136173

@@ -156,18 +193,18 @@ def down(self, names):
156193

157194
# Create
158195

159-
def create(self, names):
196+
def create(self, names, writable_tmpfs=False):
160197
'''call the create function, which defaults to the command instance.create()
161198
'''
162-
return self._create(names)
199+
return self._create(names, writable_tmpfs=writable_tmpfs)
163200

164-
def up(self, names):
201+
def up(self, names, writable_tmpfs=False):
165202
'''call the up function, instance.up(), which will build before if
166203
a container binary does not exist.
167204
'''
168-
return self._create(names, command="up")
205+
return self._create(names, command="up", writable_tmpfs=writable_tmpfs)
169206

170-
def _create(self, names, command="create"):
207+
def _create(self, names, command="create", writable_tmpfs=False):
171208
'''create one or more instances. "Command" determines the sub function
172209
to call for the instance, which should be "create" or "up".
173210
If the user provide a list of names, use them, otherwise default
@@ -177,6 +214,7 @@ def _create(self, names, command="create"):
177214
==========
178215
names: the instance names to create
179216
command: one of "create" or "up"
217+
writable_tmpfs: if the instances should be given writable to tmp
180218
'''
181219
# If no names provided, we create all
182220
if not names:
@@ -198,7 +236,7 @@ def _create(self, names, command="create"):
198236
continue
199237

200238
# If we get here, execute command and add to list
201-
getattr(instance, command)(self.working_dir)
239+
getattr(instance, command)(self.working_dir, writable_tmpfs)
202240
created.append(instance.name)
203241
names.remove(instance.name)
204242

0 commit comments

Comments
 (0)