job.py (6532B)
1 # This Source Code Form is subject to the terms of the Mozilla Public 2 # License, v. 2.0. If a copy of the MPL was not distributed with this 3 # file, You can obtain one at http://mozilla.org/MPL/2.0/. 4 5 6 from shlex import quote as shell_quote 7 8 from gecko_taskgraph.transforms.job import configure_taskdesc_for_run, run_job_using 9 from taskgraph.util import path 10 from taskgraph.util.schema import Schema, taskref_or_string 11 from voluptuous import Any, Optional, Required 12 13 secret_schema = { 14 Required("name"): str, 15 Required("path"): str, 16 Required("key"): str, 17 Optional("json"): bool, 18 Optional("decode"): bool, 19 } 20 21 dummy_secret_schema = { 22 Required("content"): str, 23 Required("path"): str, 24 Optional("json"): bool, 25 } 26 27 gradlew_schema = Schema({ 28 Required("using"): "gradlew", 29 Optional("pre-gradlew"): [[str]], 30 Required("gradlew"): [str], 31 Optional("post-gradlew"): [[str]], 32 # Base work directory used to set up the task. 33 Required("workdir"): str, 34 Optional("use-caches"): Any(bool, [str]), 35 Optional("secrets"): [secret_schema], 36 Optional("dummy-secrets"): [dummy_secret_schema], 37 }) 38 39 run_commands_schema = Schema({ 40 Required("using"): "run-commands", 41 Optional("pre-commands"): [[str]], 42 Required("commands"): [[taskref_or_string]], 43 Required("workdir"): str, 44 Optional("use-caches"): Any(bool, [str]), 45 Optional("secrets"): [secret_schema], 46 Optional("dummy-secrets"): [dummy_secret_schema], 47 }) 48 49 50 @run_job_using("docker-worker", "run-commands", schema=run_commands_schema) 51 def configure_run_commands_schema(config, job, taskdesc): 52 run = job["run"] 53 pre_commands = run.pop("pre-commands", []) 54 pre_commands += [ 55 _generate_dummy_secret_command(secret) 56 for secret in run.pop("dummy-secrets", []) 57 ] 58 pre_commands += [ 59 _generate_secret_command(secret) for secret in run.get("secrets", []) 60 ] 61 62 all_commands = pre_commands + run.pop("commands", []) 63 64 run["command"] = _convert_commands_to_string(all_commands) 65 _inject_secrets_scopes(run, taskdesc) 66 _set_run_task_attributes(job) 67 configure_taskdesc_for_run(config, job, taskdesc, job["worker"]["implementation"]) 68 69 70 @run_job_using("docker-worker", "gradlew", schema=gradlew_schema) 71 def configure_gradlew(config, job, taskdesc): 72 run = job["run"] 73 worker = taskdesc["worker"] = job["worker"] 74 75 fetches_dir = "/builds/worker/fetches" 76 topsrc_dir = "/builds/worker/checkouts/gecko" 77 worker.setdefault("env", {}).update({ 78 "ANDROID_SDK_ROOT": path.join(fetches_dir, "android-sdk-linux"), 79 "GRADLE_USER_HOME": path.join( 80 topsrc_dir, "mobile/android/gradle/dotgradle-offline" 81 ), 82 "MOZ_BUILD_DATE": config.params["moz_build_date"], 83 }) 84 worker["env"].setdefault( 85 "MOZCONFIG", 86 path.join( 87 topsrc_dir, 88 "mobile/android/config/mozconfigs/android-arm/nightly-android-lints", 89 ), 90 ) 91 worker["env"].setdefault( 92 "MOZ_ANDROID_FAT_AAR_ARCHITECTURES", "armeabi-v7a,arm64-v8a,x86_64" 93 ) 94 95 dummy_secrets = [ 96 _generate_dummy_secret_command(secret) 97 for secret in run.pop("dummy-secrets", []) 98 ] 99 secrets = [_generate_secret_command(secret) for secret in run.get("secrets", [])] 100 worker["env"].update({ 101 "PRE_GRADLEW": _convert_commands_to_string(run.pop("pre-gradlew", [])), 102 "GET_SECRETS": _convert_commands_to_string(dummy_secrets + secrets), 103 "GRADLEW_ARGS": " ".join(run.pop("gradlew")), 104 "POST_GRADLEW": _convert_commands_to_string(run.pop("post-gradlew", [])), 105 }) 106 run["command"] = ( 107 "/builds/worker/checkouts/gecko/taskcluster/scripts/builder/build-android.sh" 108 ) 109 _inject_secrets_scopes(run, taskdesc) 110 _set_run_task_attributes(job) 111 configure_taskdesc_for_run(config, job, taskdesc, job["worker"]["implementation"]) 112 113 114 def _generate_secret_command(secret): 115 secret_command = [ 116 "/builds/worker/checkouts/gecko/taskcluster/scripts/get-secret.py", 117 "-s", 118 secret["name"], 119 "-k", 120 secret["key"], 121 "-f", 122 secret["path"], 123 ] 124 if secret.get("json"): 125 secret_command.append("--json") 126 127 if secret.get("decode"): 128 secret_command.append("--decode") 129 130 return secret_command 131 132 133 def _generate_dummy_secret_command(secret): 134 secret_command = [ 135 "/builds/worker/checkouts/gecko/taskcluster/scripts/write-dummy-secret.py", 136 "-f", 137 secret["path"], 138 "-c", 139 secret["content"], 140 ] 141 if secret.get("json"): 142 secret_command.append("--json") 143 144 return secret_command 145 146 147 def _convert_commands_to_string(commands): 148 should_artifact_reference = False 149 should_task_reference = False 150 151 sanitized_commands = [] 152 for command in commands: 153 sanitized_parts = [] 154 for part in command: 155 if isinstance(part, dict): 156 if "artifact-reference" in part: 157 part_string = part["artifact-reference"] 158 should_artifact_reference = True 159 elif "task-reference" in part: 160 part_string = part["task-reference"] 161 should_task_reference = True 162 else: 163 raise ValueError(f"Unsupported dict: {part}") 164 else: 165 part_string = part 166 167 sanitized_parts.append(part_string) 168 sanitized_commands.append(sanitized_parts) 169 170 shell_quoted_commands = [ 171 " ".join(map(shell_quote, command)) for command in sanitized_commands 172 ] 173 full_string_command = " && ".join(shell_quoted_commands) 174 175 if should_artifact_reference and should_task_reference: 176 raise NotImplementedError( 177 '"arifact-reference" and "task-reference" cannot be both used' 178 ) 179 elif should_artifact_reference: 180 return {"artifact-reference": full_string_command} 181 elif should_task_reference: 182 return {"task-reference": full_string_command} 183 else: 184 return full_string_command 185 186 187 def _inject_secrets_scopes(run, taskdesc): 188 secrets = run.pop("secrets", []) 189 scopes = taskdesc.setdefault("scopes", []) 190 new_secret_scopes = ["secrets:get:{}".format(secret["name"]) for secret in secrets] 191 new_secret_scopes = list( 192 set(new_secret_scopes) 193 ) # Scopes must not have any duplicates 194 scopes.extend(new_secret_scopes) 195 196 197 def _set_run_task_attributes(job): 198 run = job["run"] 199 run["cwd"] = "{checkout}" 200 run["using"] = "run-task"