|
| 1 | +# Copyright 2016 W. Trevor King <wking@tremily.us> |
| 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 | + |
| 15 | +import os.path |
| 16 | +import re |
| 17 | +import unittest |
| 18 | + |
| 19 | +from . import util |
| 20 | + |
| 21 | + |
| 22 | +class TestProcess(unittest.TestCase): |
| 23 | + ENVIRONMENT_VARIABLE_KEY_INVALID_REGEX = re.compile('[^a-zA-Z0-9_]') |
| 24 | + |
| 25 | + @util.skip_if_unrecognized_version |
| 26 | + def test_process(self): |
| 27 | + """process (object, required). |
| 28 | +
|
| 29 | + This is currently underspecified [1,2], but I expect it to be |
| 30 | + required [3]. |
| 31 | +
|
| 32 | + [1]: https://github.com/opencontainers/runtime-spec/blob/v1.0.0-rc1/config.md#process-configuration |
| 33 | + [2]: https://github.com/opencontainers/runtime-spec/blob/v0.5.0/config.md#process-configuration |
| 34 | + [3]: https://github.com/opencontainers/runtime-spec/pull/489 |
| 35 | + """ |
| 36 | + self.assertIn( |
| 37 | + 'process', sorted(util.CONFIG_JSON.keys()), |
| 38 | + 'process is not set') |
| 39 | + process = util.CONFIG_JSON['process'] |
| 40 | + self.assertTrue(isinstance(process, dict), 'process is not an object') |
| 41 | + |
| 42 | + @util.skip_if_unrecognized_version |
| 43 | + @unittest.skipUnless( |
| 44 | + isinstance(util.CONFIG_JSON.get('process'), dict), |
| 45 | + 'cannot validate process.terminal without a process object') |
| 46 | + def test_terminal(self): |
| 47 | + """terminal (bool, optional). |
| 48 | +
|
| 49 | + [1]: https://github.com/opencontainers/runtime-spec/blob/v1.0.0-rc1/config.md#process-configuration |
| 50 | + [2]: https://github.com/opencontainers/runtime-spec/blob/v0.5.0/config.md#process-configuration |
| 51 | + """ |
| 52 | + process = util.CONFIG_JSON['process'] |
| 53 | + if 'terminal' in process: |
| 54 | + terminal = process['terminal'] |
| 55 | + self.assertIn( |
| 56 | + terminal, [True, False], 'process.terminal is not a boolean') |
| 57 | + |
| 58 | + @util.skip_if_unrecognized_version |
| 59 | + @unittest.skipUnless( |
| 60 | + isinstance(util.CONFIG_JSON.get('process'), dict), |
| 61 | + 'cannot validate process.cwd without a process object') |
| 62 | + @util.skip_unless_path_separator_matches |
| 63 | + def test_cwd(self): |
| 64 | + """cwd (string, required). |
| 65 | +
|
| 66 | + From the spec [1,2]: |
| 67 | +
|
| 68 | + This value MUST be an absolute path. |
| 69 | +
|
| 70 | + [1]: https://github.com/opencontainers/runtime-spec/blob/v1.0.0-rc1/config.md#process-configuration |
| 71 | + [2]: https://github.com/opencontainers/runtime-spec/blob/v0.5.0/config.md#process-configuration |
| 72 | + """ |
| 73 | + process = util.CONFIG_JSON['process'] |
| 74 | + self.assertIn('cwd', sorted(process.keys()), 'process.cwd is not set') |
| 75 | + cwd = process['cwd'] |
| 76 | + self.assertTrue( |
| 77 | + os.path.isabs(cwd), 'process.cwd MUST be an absolute path') |
| 78 | + |
| 79 | + @util.skip_if_unrecognized_version |
| 80 | + @unittest.skipUnless( |
| 81 | + isinstance(util.CONFIG_JSON.get('process'), dict), |
| 82 | + 'cannot validate process.env without a process object') |
| 83 | + def test_env(self): |
| 84 | + """env (array of strings, optional). |
| 85 | +
|
| 86 | + From the spec [1,2]: |
| 87 | +
|
| 88 | + Elements in the array are specified as Strings in the form |
| 89 | + "KEY=value". The left hand side must consist solely of |
| 90 | + letters, digits, and underscores _ as outlined in IEEE Std |
| 91 | + 1003.1-2001. |
| 92 | +
|
| 93 | + I'd rather punt to POSIX [3] (which is less strict), but the |
| 94 | + pull request for that is still in flight. |
| 95 | +
|
| 96 | + [1]: https://github.com/opencontainers/runtime-spec/blob/v1.0.0-rc1/config.md#process-configuration |
| 97 | + [2]: https://github.com/opencontainers/runtime-spec/blob/v0.5.0/config.md#process-configuration |
| 98 | + [3]: https://github.com/opencontainers/runtime-spec/pull/427#issuecomment-220530504 |
| 99 | + """ |
| 100 | + process = util.CONFIG_JSON['process'] |
| 101 | + if 'env' in process: |
| 102 | + env = process['env'] |
| 103 | + self.assertTrue( |
| 104 | + isinstance(env, list), 'process.env is not an array') |
| 105 | + for i, env_var in enumerate(env): |
| 106 | + with self.subTest(i=i, environment_variable=env_var): |
| 107 | + self.assertTrue( |
| 108 | + isinstance(env_var, str), |
| 109 | + 'process.env[{}] ({}) is not a string' |
| 110 | + .format(i, env_var)) |
| 111 | + # the only POSIX requirement is an equals sign |
| 112 | + self.assertTrue( |
| 113 | + '=' in env_var, |
| 114 | + 'process.env[{}] ({}) does not contain an equals sign' |
| 115 | + .format(i, env_var)) |
| 116 | + # additional restrictions from the OCI spec |
| 117 | + key = env_var.split('=', 1)[0] |
| 118 | + match = self.ENVIRONMENT_VARIABLE_KEY_INVALID_REGEX.search( |
| 119 | + key) |
| 120 | + if match: |
| 121 | + invalid_character = match.group(0) |
| 122 | + raise self.failureException( |
| 123 | + "process.env[{}]'s key ({}) contains an invalid " |
| 124 | + 'character: {!r}' |
| 125 | + .format(i, key, invalid_character)) |
| 126 | + |
| 127 | + @util.skip_if_unrecognized_version |
| 128 | + @unittest.skipUnless( |
| 129 | + isinstance(util.CONFIG_JSON.get('process'), dict), |
| 130 | + 'cannot validate process.args without a process object') |
| 131 | + def test_args(self): |
| 132 | + """args (array of strings, required). |
| 133 | +
|
| 134 | + From the spec [1,2]: |
| 135 | +
|
| 136 | + The executable is the first element and MUST be available at |
| 137 | + the given path inside of the rootfs. If the executable path |
| 138 | + is not an absolute path then the search $PATH is interpreted |
| 139 | + to find the executable. |
| 140 | +
|
| 141 | + v0.5.0 used 'must' instead of 'MUST', but that was accidental |
| 142 | + [3]. |
| 143 | +
|
| 144 | + I don't see how "MUST be available at the given path" squares |
| 145 | + with "If the executable path is not an absolute path then |
| 146 | + search $PATH". And that's not how execvp works anyway (it |
| 147 | + walks PATH only if there *no* separators in the the file [4]). |
| 148 | + I expect we want to punt all of this to POSIX [5], so for now |
| 149 | + I only check that args is an array of strings with at least |
| 150 | + one element. |
| 151 | +
|
| 152 | + [1]: https://github.com/opencontainers/runtime-spec/blob/v1.0.0-rc1/config.md#process-configuration |
| 153 | + [2]: https://github.com/opencontainers/runtime-spec/blob/v0.5.0/config.md#process-configuration |
| 154 | + [3]: https://github.com/opencontainers/runtime-spec/pull/438 |
| 155 | + [4]: http://pubs.opengroup.org/onlinepubs/9699919799/functions/execvp.html |
| 156 | + [5]: https://github.com/opencontainers/runtime-spec/pull/427#issuecomment-220530504 |
| 157 | + """ |
| 158 | + process = util.CONFIG_JSON['process'] |
| 159 | + self.assertIn( |
| 160 | + 'args', sorted(process.keys()), 'process.args is not set') |
| 161 | + args = process['args'] |
| 162 | + self.assertTrue(isinstance(args, list), 'process.args is not an array') |
| 163 | + self.assertTrue( |
| 164 | + len(args) > 0, 'process.args must have at least one element') |
| 165 | + for i, arg in enumerate(args): |
| 166 | + with self.subTest(i=i, argument=arg): |
| 167 | + self.assertTrue( |
| 168 | + isinstance(arg, str), |
| 169 | + 'process.args[{}] ({}) is not a string'.format(i, arg)) |
0 commit comments