File: modify.py

package info (click to toggle)
charliecloud 0.43-1
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid
  • size: 3,084 kB
  • sloc: python: 6,021; sh: 4,284; ansic: 3,863; makefile: 598
file content (237 lines) | stat: -rw-r--r-- 9,209 bytes parent folder | download
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
# implementation of ch-image modify

import enum
import os
import subprocess
import sys
import tempfile
import uuid

import charliecloud as ch
import build
import build_cache as bu
import force
import image as im
import irtree

lark = im.lark

class Modify_Mode(enum.Enum):
   COMMAND_SEQ = "commands"
   INTERACTIVE = "interactive"
   SCRIPT = "script"

def main(cli_):
   # In this file, “cli” is used as a global variable
   global cli
   cli = cli_

   # CLI opts that “build.py” expects, but that don’t make sense in the context
   # of “modify”. We set “parse_only” to “False” because we don’t do any
   # parsing, and “context” to the root of the filesystem to ensure that
   # necessary files (e.g. the modify script) will always be somewhere in the
   # context dir.
   cli.parse_only = False
   cli.context = os.path.abspath(os.sep)

   build.cli_process_common(cli)

   commands = []
   # “Flatten” commands array
   for c in cli.c:
      commands += c
   src_image = im.Image(im.Reference(cli.image_ref))
   out_image = im.Image(im.Reference(cli.out_image))
   if (not src_image.unpack_exist_p):
      ch.FATAL("not in storage: %s" % src_image.ref)
   if (cli.out_image == cli.image_ref):
      ch.FATAL("output must be different from source image (%s)" % cli.image_ref)
   if (cli.script is not None):
      if (not ch.Path(cli.script).exists):
         ch.FATAL("%s: no such file" % cli.script)

   # This kludge is necessary because cli is a global variable, with cli.tag
   # assumed present elsewhere in the file. Here, cli.tag represents the
   # destination image.
   cli.tag = str(out_image)

   # Determine modify mode based on what is present in command line
   if (commands != []):
      if (cli.interactive):
         ch.FATAL("incompatible opts: “-c”, “-i”")
      if (cli.script is not None):
         ch.FATAL("script mode incompatible with command mode")
      mode = Modify_Mode.COMMAND_SEQ
   elif (cli.script is not None):
      if (cli.interactive):
         ch.FATAL("script mode incompatible with interactive mode")
      mode = Modify_Mode.SCRIPT
   elif (sys.stdin.isatty() or (cli.interactive)):
      mode = Modify_Mode.INTERACTIVE
   else:
      # Write stdin to tempfile, copy tempfile into container as a script, run
      # script.
      stdin = sys.stdin.read()
      if (stdin == ''):
         ch.FATAL("modify mode unclear")

      tmp = tempfile.NamedTemporaryFile()
      with open(tmp.name, 'w') as fd:
         fd.write(stdin)

      cli.script = tmp.name

      mode = Modify_Mode.SCRIPT

   ch.VERBOSE("modify shell: %s" % cli.shell)
   ch.VERBOSE("modify mode: %s" % mode.value)

   if (mode == Modify_Mode.INTERACTIVE):
      # Interactive case

      # Generate “fake” SID for build cache. We do this because we can’t
      # compute an SID, but we still want to make sure that it’s unique enough
      # that we’re unlikely to run into a collision.
      fake_sid = uuid.uuid4()
      out_image.unpack_clear()
      out_image.copy_unpacked(src_image)
      bu.cache.worktree_adopt(out_image, src_image.ref.for_path)
      bu.cache.ready(out_image)
      bu.cache.branch_nocheckout(src_image.ref, out_image.ref)
      cp = ch.cmd_simple(  [ch.CH_BIN + "/ch-run", "--unsafe", "-w"]
                         + sum([["-b", i] for i in cli.bind], [])
                         + [str(out_image.ref), "--", cli.shell])
      try:
         xt = ch.Run_Exit(cp.returncode)
         if (xt == ch.Run_Exit.OK):
            pass
         elif (xt == ch.Run_Exit.ERR_CMD):
            ch.FATAL("can’t run shell: %s" % cli.shell)
         else:
            raise ValueError()  # YOLO
      except ValueError:
         ch.WARNING("interactive session exited with %d; committing anyway"
                    % cp.returncode)
      ch.VERBOSE("using SID %s" % fake_sid)
      # FIXME: metadata history stuff? See misc.import_.
      if (out_image.metadata["history"] == []):
         out_image.metadata["history"].append({ "empty_layer": False,
                                                "command":     "ch-image import"})
      out_image.metadata_save()
      bu.cache.commit(out_image.unpack_path, fake_sid, "MODIFY interactive", [])
   else:
      # non-interactive case
      if (mode == Modify_Mode.SCRIPT):
         # script specified
         tree = modify_tree_make_script(src_image.ref, cli.script)
      elif (mode == Modify_Mode.COMMAND_SEQ):
         # “-c” specified
         tree = modify_tree_make(src_image.ref, commands)
      else:
         assert False, "unreachable code reached"

      ch.VERBOSE(tree.pretty()[:-1])  # rm trailing newline

      image_ct = sum(1 for i in tree.children_("from_"))

      irtree.parse_tree_traverse(tree, image_ct, cli)

def modify_tree_make(src_img, cmds):
   """Construct a parse tree corresponding to a set of “ch-image modify”
      commands, as though the commands had been specified in a Dockerfile. Note
      that because “ch-image modify” simply executes one or more commands inside
      a container, the only Dockerfile instructions we need to consider are
      “FROM” and “RUN”. E.g. for the command line

         $ ch-image modify -c 'echo foo' -c 'echo bar' -- foo foo2

      this function produces the following parse tree

         start
            dockerfile
               from_
                  image_ref
                     IMAGE_REF foo
               run
                  run_shell
                     LINE_CHUNK echo foo
               run
                  run_shell
                     LINE_CHUNK echo bar
      """
   # Children of dockerfile tree
   df_children = []
   # Metadata attribute. We use this attribute in the “_pretty” method for our
   # “Tree” class. Constructing a tree without specifying a “Meta” instance that
   # has been given a “line” value will result in the attribute not being present,
   # which causes an error when we try to access that attribute. Here we give the
   # attribute a debug value of -1 to avoid said errors.
   meta = lark.tree.Meta()
   meta.line = -1
   meta.column = -1
   df_children.append(im.Tree(lark.Token('RULE', 'from_'),
                      [im.Tree(lark.Token('RULE', 'image_ref'),
                        [lark.Token('IMAGE_REF', str(src_img))],
                        meta)
                      ], meta))
   if (cli.shell is not None):
      df_children.append(im.Tree(lark.Token('RULE', 'shell'),
                         [lark.Token('STRING_QUOTED', '"%s"' % cli.shell),
                          lark.Token('STRING_QUOTED', '"-c"')
                         ],meta))
   for cmd in cmds:
      df_children.append(im.Tree(lark.Token('RULE', 'run'),
                         [im.Tree(lark.Token('RULE', 'run_shell'),
                           [lark.Token('LINE_CHUNK', cmd)],
                           meta)
                         ], meta))
   return im.Tree(lark.Token('RULE', 'start'), [im.Tree(lark.Token('RULE','dockerfile'), df_children, meta)], meta)

def modify_tree_make_script(src_img, path):
   """Temporary(?) analog of “modify_tree_make” for the non-interactive version
      of “modify” using a script. For the command line:

         $ ch-image modify foo foo2 /path/to/script

      this function produces the following parse tree

         start
            dockerfile
               from_
                  image_ref
                     IMAGE_REF foo
               copy
                  copy_shell
                     WORD /path/to/script WORD /ch/script.sh
               run
                  run_shell
                     LINE_CHUNK /bin/sh /ch/script.sh
      """
   # Children of dockerfile tree
   df_children = []
   # Metadata attribute. We use this attribute in the “_pretty” method for our
   # “Tree” class. Constructing a tree without specifying a “Meta” instance that
   # has been given a “line” value will result in the attribute not being present,
   # which causes an error when we try to access that attribute. Here we give the
   # attribute a debug value of -1 to avoid said errors.
   meta = lark.tree.Meta()
   meta.line = -1
   meta.column = -1
   df_children.append(im.Tree(lark.Token('RULE', 'from_'),
                      [im.Tree(lark.Token('RULE', 'image_ref'),
                        [lark.Token('IMAGE_REF', str(src_img))],
                        meta)
                      ], meta))
   df_children.append(im.Tree(lark.Token('RULE', 'copy'),
                      [im.Tree(lark.Token('RULE', 'copy_shell'),
                        [lark.Token('WORD', path),
                         lark.Token('WORD', '/ch/script.sh')
                        ], meta)
                      ],meta))
   # FIXME: Add error handling if “cli.shell” doesn’t exist (issue #1913).
   df_children.append(im.Tree(lark.Token('RULE', 'run'),
                      [im.Tree(lark.Token('RULE', 'run_shell'),
                        [lark.Token('LINE_CHUNK', '%s /ch/script.sh' % cli.shell)],
                        meta)
                      ], meta))
   return im.Tree(lark.Token('RULE', 'start'), [im.Tree(lark.Token('RULE','dockerfile'), df_children, meta)], meta)