A workflow step can open a g1t.page build with POST /repos/{owner}/{repo}/deployments/builds (the workflow tool's start_deployment_build over MCP), sending its job's own token and, optionally, the project's slug and the commit; the project may be left out when the repository has one project with Deployments on. It answers with the build's id and token for g1t-runner deploy, whether it is production or a preview, the app's address, whether the run was trusted, the project's root directory, build command and output directory, and the variables and secrets builds of that kind get. It needs deployments:write, which a job's permissions give with deployments: write or pages: write, and no token but a job's is let through. The API reference, its example, and the MCP reference say so.
- Claude-Session:
- https://claude.ai/code/session_01PnwTRSNaC7vEHTAuBzjhrn
10 files+72−30/10 viewed
| 24 | 24 | CreateDeploymentStatus, | |
| 25 | 25 | ListEnvironments, | |
| 26 | 26 | GetEnvironment, | |
| 27 | + | StartDeploymentBuild, | |
| 27 | 28 | } | |
| 28 | 29 | ||
| 29 | 30 | /// The states a deployment's status can have. | |
| ⋯ | |||
| 33 | 34 | /// Every one: `Op::ALL` lists each as `Op::Deployments(…)`, which a | |
| 34 | 35 | /// test checks against this. | |
| 35 | 36 | #[cfg(test)] | |
| 36 | − | pub const ALL: [DeploymentsOp; 7] = [ | |
| 37 | + | pub const ALL: [DeploymentsOp; 8] = [ | |
| 37 | 38 | DeploymentsOp::ListDeployments, | |
| 38 | 39 | DeploymentsOp::GetDeployment, | |
| 39 | 40 | DeploymentsOp::CreateDeployment, | |
| ⋯ | |||
| 41 | 42 | DeploymentsOp::CreateDeploymentStatus, | |
| 42 | 43 | DeploymentsOp::ListEnvironments, | |
| 43 | 44 | DeploymentsOp::GetEnvironment, | |
| 45 | + | DeploymentsOp::StartDeploymentBuild, | |
| 44 | 46 | ]; | |
| 45 | 47 | ||
| 46 | 48 | pub fn name(self) -> &'static str { | |
| ⋯ | |||
| 52 | 54 | DeploymentsOp::CreateDeploymentStatus => "create_deployment_status", | |
| 53 | 55 | DeploymentsOp::ListEnvironments => "list_environments", | |
| 54 | 56 | DeploymentsOp::GetEnvironment => "get_environment", | |
| 57 | + | DeploymentsOp::StartDeploymentBuild => "start_deployment_build", | |
| 55 | 58 | } | |
| 56 | 59 | } | |
| 57 | 60 | ||
| ⋯ | |||
| 65 | 68 | DeploymentsOp::CreateDeploymentStatus => "Create a deployment status", | |
| 66 | 69 | DeploymentsOp::ListEnvironments => "List environments", | |
| 67 | 70 | DeploymentsOp::GetEnvironment => "Get an environment", | |
| 71 | + | DeploymentsOp::StartDeploymentBuild => "Start a g1t.page build from a workflow", | |
| 68 | 72 | } | |
| 69 | 73 | } | |
| 70 | 74 | ||
| ⋯ | |||
| 77 | 81 | DeploymentsOp::CreateDeploymentStatus => "Add a status to a reported deployment: state (queued, in_progress, success, failure, error or inactive), description, environment_url (where it is served) and log_url (where its output can be read). The deployment takes its state, and any address it gives. A success with auto_inactive (true unless you say) makes the environment's older successful deployments inactive. The commit's `deploy / <environment>` check follows: pending while queued or in progress, then success, failure or error. A g1t.page build's statuses come from the build and cannot be added to. Needs the Write role.", | |
| 78 | 82 | DeploymentsOp::ListEnvironments => "List the environments a repository's deployments went to, those people use directly first (production by name before others), then the most recently deployed, and after them those with protection rules but no deployment yet. Each has its name, url (where its current deployment is served), production_environment, transient_environment, deployments_count, latest (its newest deployment, whatever its state), current (its newest successful deployment that is still active) and updated_at, and, when it has rules, protection_rules (required_reviewers, wait_timer, branch_policy), deployment_branch_policy, branch_policies and can_admins_bypass. total_count counts deployments across every environment. Needs the Read role.", | |
| 79 | 83 | DeploymentsOp::GetEnvironment => "Get one environment by name, matched without regard to case, with its current and latest deployments and its protection rules (see update_environment). An environment with rules but no deployment yet is found too. A name with slashes is URL-encoded in the path. Needs the Read role.", | |
| 84 | + | DeploymentsOp::StartDeploymentBuild => "From a running g1t Actions job, open a g1t.page build of one of the repository's projects, which the job then builds in its own checkout with `g1t-runner deploy` (the g1t/deploy action does both). Only the job's own token (G1T_TOKEN) may call it, with `deployments: write` (or `pages: write`) in its `permissions:`. project is the project's slug; it may be left out when the repository has one project with Deployments on. The run decides what is built: a run on the project's production branch (a push, a dispatch or a schedule there) builds production, a pull request's run builds that pull request's preview, and a run on another branch builds that branch's preview; a tag's run is refused. sha is the commit built, the run's unless you say. A pull request from a fork or from someone without the Write role, and a run that had to be approved, get no build secrets. The workspace's plan must include Deployments and its spend limit must not be reached; the build's time is the job's Actions time and is not charged again. Returns deploy_id and token (DEPLOY_ID and DEPLOY_TOKEN for `g1t-runner deploy`, the token good for this build alone until it finishes), kind (production or preview), project, branch, number, commit, url (the app's address), trusted, the project's root_dir, build_command and output_dir, and build_env and build_secrets (the project's variables and secrets for builds of that kind). The commit's `g1t / deploy` check is pending until the build reports.", | |
| 80 | 85 | } | |
| 81 | 86 | } | |
| 82 | 87 | ||
| 83 | 88 | /// Whether it changes anything. | |
| 84 | 89 | pub fn writes(self) -> bool { | |
| 85 | − | matches!(self, DeploymentsOp::CreateDeployment | DeploymentsOp::CreateDeploymentStatus) | |
| 90 | + | matches!( | |
| 91 | + | self, | |
| 92 | + | DeploymentsOp::CreateDeployment | DeploymentsOp::CreateDeploymentStatus | DeploymentsOp::StartDeploymentBuild | |
| 93 | + | ) | |
| 86 | 94 | } | |
| 87 | 95 | ||
| 88 | 96 | pub fn input(self) -> Value { | |
| ⋯ | |||
| 137 | 145 | &["repo", "id", "state"], | |
| 138 | 146 | ), | |
| 139 | 147 | DeploymentsOp::ListEnvironments => (json!({ "repo": repo }), &["repo"]), | |
| 148 | + | DeploymentsOp::StartDeploymentBuild => ( | |
| 149 | + | json!({ | |
| 150 | + | "repo": repo, | |
| 151 | + | "project": { "type": "string", "description": "The project's slug; may be left out when the repository has one project with Deployments on." }, | |
| 152 | + | "sha": { "type": "string", "description": "The commit built, in hex; the run's commit unless you say." }, | |
| 153 | + | }), | |
| 154 | + | &["repo"], | |
| 155 | + | ), | |
| 140 | 156 | DeploymentsOp::GetEnvironment => ( | |
| 141 | 157 | json!({ | |
| 142 | 158 | "repo": repo, | |
| ⋯ | |||
| 236 | 252 | "create_deployment_status" | |
| 237 | 253 | } | |
| 238 | 254 | DeploymentsOp::ListEnvironments => "list_environments", | |
| 255 | + | // The deployments service checks the job's token itself: the | |
| 256 | + | // caller (the job's workspace, with `token.job`) is the actor. | |
| 257 | + | DeploymentsOp::StartDeploymentBuild => { | |
| 258 | + | for key in ["project", "sha"] { | |
| 259 | + | if let Some(value) = input[key].as_str().map(str::trim).filter(|value| !value.is_empty()) { | |
| 260 | + | out.insert(key.into(), value.into()); | |
| 261 | + | } | |
| 262 | + | } | |
| 263 | + | "start_from_workflow" | |
| 264 | + | } | |
| 239 | 265 | DeploymentsOp::GetEnvironment => { | |
| 240 | 266 | let name = input["environment"].as_str().map(str::trim).filter(|name| !name.is_empty()).ok_or("Name the environment.")?; | |
| 241 | 267 | out.insert("name".into(), name.into()); | |
| ⋯ | |||
| 294 | 320 | assert!(super::args(DeploymentsOp::CreateDeploymentStatus, &json!({ "repo": "acme/web", "id": "dep_1" })).is_err()); | |
| 295 | 321 | let (method, args) = super::args(DeploymentsOp::GetEnvironment, &json!({ "repo": "acme/web", "environment": "review/x" })).unwrap(); | |
| 296 | 322 | assert_eq!((method, args["name"].clone()), ("get_environment", json!("review/x"))); | |
| 323 | + | let (method, args) = super::args(DeploymentsOp::StartDeploymentBuild, &json!({ "repo": "acme/web", "project": " web ", "sha": "" })).unwrap(); | |
| 324 | + | assert_eq!(method, "start_from_workflow"); | |
| 325 | + | assert_eq!(args["project"], json!("web")); | |
| 326 | + | assert!(!args.contains_key("sha"), "an empty sha is the run's"); | |
| 327 | + | assert!(DeploymentsOp::StartDeploymentBuild.writes()); | |
| 297 | 328 | } | |
| 298 | 329 | ||
| 299 | 330 | #[test] | |
| 460 | 460 | Op::Deployments(DeploymentsOp::CreateDeploymentStatus), | |
| 461 | 461 | Op::Deployments(DeploymentsOp::ListEnvironments), | |
| 462 | 462 | Op::Deployments(DeploymentsOp::GetEnvironment), | |
| 463 | + | Op::Deployments(DeploymentsOp::StartDeploymentBuild), | |
| 463 | 464 | ], | |
| 464 | 465 | ), | |
| 465 | 466 | ( |
| 707 | 707 | } | |
| 708 | 708 | ||
| 709 | 709 | impl Op { | |
| 710 | − | pub const ALL: [Op; 350] = [ | |
| 710 | + | pub const ALL: [Op; 351] = [ | |
| 711 | 711 | Op::Whoami, | |
| 712 | 712 | Op::GetWorkspace, | |
| 713 | 713 | Op::CreateWorkspace, | |
| ⋯ | |||
| 983 | 983 | Op::Deployments(DeploymentsOp::CreateDeploymentStatus), | |
| 984 | 984 | Op::Deployments(DeploymentsOp::ListEnvironments), | |
| 985 | 985 | Op::Deployments(DeploymentsOp::GetEnvironment), | |
| 986 | + | Op::Deployments(DeploymentsOp::StartDeploymentBuild), | |
| 986 | 987 | Op::Artifacts(ArtifactsOp::ListArtifacts), | |
| 987 | 988 | Op::Artifacts(ArtifactsOp::ListRunArtifacts), | |
| 988 | 989 | Op::Artifacts(ArtifactsOp::GetArtifact), | |
| 10567 | 10567 | }, | |
| 10568 | 10568 | "notes": "Production comes first, then the most recently deployed. `latest` is the newest deployment whatever its state; `current` is the newest that succeeded and is still active, and `url` is where it is served." | |
| 10569 | 10569 | }, | |
| 10570 | + | "start_deployment_build": { | |
| 10571 | + | "params": { | |
| 10572 | + | "owner": "flagon-io", | |
| 10573 | + | "name": "hello" | |
| 10574 | + | }, | |
| 10575 | + | "request": { | |
| 10576 | + | "project": "hello" | |
| 10577 | + | }, | |
| 10578 | + | "response": { | |
| 10579 | + | "deploy_id": "dpl_01kq9c4e6g8j0m2p4r6t8v0x2z", | |
| 10580 | + | "token": "…", | |
| 10581 | + | "kind": "preview", | |
| 10582 | + | "project": "hello", | |
| 10583 | + | "branch": "new-header", | |
| 10584 | + | "number": 42, | |
| 10585 | + | "commit": "9f2c4e6a8b0d1f3e5a7c9b1d3f5e7a9c0b2d4f6e", | |
| 10586 | + | "url": "https://hello-git-new-header-flagonio.g1t.page", | |
| 10587 | + | "trusted": true, | |
| 10588 | + | "root_dir": "", | |
| 10589 | + | "build_command": null, | |
| 10590 | + | "output_dir": null, | |
| 10591 | + | "build_env": { | |
| 10592 | + | "PUBLIC_API_URL": "https://api.example.com" | |
| 10593 | + | }, | |
| 10594 | + | "build_secrets": { | |
| 10595 | + | "SENTRY_AUTH_TOKEN": "…" | |
| 10596 | + | } | |
| 10597 | + | }, | |
| 10598 | + | "notes": "Called from a workflow step with the job's own token, `${{ github.token }}`, in a job with `permissions: deployments: write`; the [`g1t/deploy`](/guides/deployments/#deploying-from-a-workflow) action calls it, masks the token and the secrets, and runs `g1t-runner deploy` with them. Any other token is refused. This is a pull request's run, so it builds the pull request's preview; the same workflow on the production branch builds production. See [Deploying from a workflow](/guides/deployments/#deploying-from-a-workflow)." | |
| 10599 | + | }, | |
| 10570 | 10600 | "get_environment": { | |
| 10571 | 10601 | "params": { | |
| 10572 | 10602 | "owner": "flagon-io", |
| 359 | 359 | &[("environment", "environment"), ("ref", "ref"), ("sha", "sha"), ("task", "task"), ("state", "state"), ("source", "source"), ("creator", "creator"), ("page", "page"), ("per_page", "per_page")], | |
| 360 | 360 | ), | |
| 361 | 361 | route("POST", "/repos/:owner/:name/deployments", Op::Deployments(DeploymentsOp::CreateDeployment), &[]), | |
| 362 | + | // A g1t Actions job opening a g1t.page build it runs itself. | |
| 363 | + | route("POST", "/repos/:owner/:name/deployments/builds", Op::Deployments(DeploymentsOp::StartDeploymentBuild), &[]), | |
| 362 | 364 | route("GET", "/repos/:owner/:name/deployments/:id", Op::Deployments(DeploymentsOp::GetDeployment), &[]), | |
| 363 | 365 | route("GET", "/repos/:owner/:name/deployments/:id/statuses", Op::Deployments(DeploymentsOp::ListDeploymentStatuses), &[]), | |
| 364 | 366 | route("POST", "/repos/:owner/:name/deployments/:id/statuses", Op::Deployments(DeploymentsOp::CreateDeploymentStatus), &[]), |
| 263 | 263 | a("create_deployment_status", Op::Deployments(DeploymentsOp::CreateDeploymentStatus), "Report where a deployment is: in_progress, success, failure"), | |
| 264 | 264 | a("list_environments", Op::Deployments(DeploymentsOp::ListEnvironments), "Environments with their current and latest deployments"), | |
| 265 | 265 | a("get_environment", Op::Deployments(DeploymentsOp::GetEnvironment), "One environment by name, with its protection rules"), | |
| 266 | + | a("start_deployment_build", Op::Deployments(DeploymentsOp::StartDeploymentBuild), "From a workflow job: open a g1t.page build it runs itself"), | |
| 266 | 267 | a("update_environment", Op::Protection(ProtectionOp::UpdateEnvironment), "Set an environment's reviewers, wait timer and branches"), | |
| 267 | 268 | a("delete_environment", Op::Protection(ProtectionOp::DeleteEnvironment), "Remove an environment's protection rules"), | |
| 268 | 269 | a("pending_deployments", Op::Protection(ProtectionOp::GetPendingDeployments), "The environments holding a run's jobs"), |
| 464 | 464 | | [`create_deployment_status`](/reference/api/deployments/create-deployment-status/) | Report where a deployment is: `state` (`queued`, `in_progress`, `success`, `failure`, `error` or `inactive`), with optional `description`, `environment_url`, `log_url` and `auto_inactive`. Not for a g1t.page build. Write role. | `repo`, `id`, `state` | `deployments:write` | | |
| 465 | 465 | | [`list_environments`](/reference/api/deployments/list-environments/) | Environments with their address, current and latest deployments, production first. | `repo` | `deployments:read` | | |
| 466 | 466 | | [`get_environment`](/reference/api/deployments/get-environment/) | One environment by name, with its protection rules. | `repo`, `environment` | `deployments:read` | | |
| 467 | + | | [`start_deployment_build`](/reference/api/deployments/start-deployment-build/) | From a running workflow job, with the job's own token only: open a g1t.page build of a `project` (optional when the repository has one with Deployments on) that the job builds itself with `g1t-runner deploy`, optionally at `sha`. Production for a run on the production branch, a preview otherwise. Returns `deploy_id`, `token`, `kind`, `url`, `build_env` and `build_secrets`. See [deploying from a workflow](/guides/deployments/#deploying-from-a-workflow). | `repo` | `deployments:write` | | |
| 467 | 468 | | [`update_environment`](/reference/api/run-protection/update-environment/) | Set an environment's [protection rules](/guides/actions/#environments): `reviewers` (up to 6, `{"type": "User" or "Team", "name"}`), `prevent_self_review`, `wait_timer` (minutes), `deployment_branch_policy`, `branch_policies` and `can_admins_bypass`. Admin role. | `repo`, `environment` | `repo:admin` | | |
| 468 | 469 | | [`delete_environment`](/reference/api/run-protection/delete-environment/) | Remove an environment's protection rules; its secrets and deployments stay. Admin role. | `repo`, `environment` | `repo:admin` | | |
| 469 | 470 | | [`pending_deployments`](/reference/api/run-protection/get-pending-deployments/) | The environments holding a run's jobs: their state, reviewers, wait timer, jobs, and whether you may approve. | `repo`, `id` | `workflows:read` | |
Binary or large file; its contents are not shown.
| 1142 | 1142 | ("get_environment", Scope::DeploymentsRead), | |
| 1143 | 1143 | ("create_deployment", Scope::DeploymentsWrite), | |
| 1144 | 1144 | ("create_deployment_status", Scope::DeploymentsWrite), | |
| 1145 | + | ("start_deployment_build", Scope::DeploymentsWrite), | |
| 1145 | 1146 | // What keeps runs safe: the runs environments hold and reviewing them, | |
| 1146 | 1147 | // approving a pull request's run, and a repository's own rules for | |
| 1147 | 1148 | // its environments and tokens, which are an admin's. |
| 516 | 516 | ["get_environment", "deployments:read"], | |
| 517 | 517 | ["create_deployment", "deployments:write"], | |
| 518 | 518 | ["create_deployment_status", "deployments:write"], | |
| 519 | + | ["start_deployment_build", "deployments:write"], | |
| 519 | 520 | // What keeps runs safe: the runs environments hold and reviewing them, | |
| 520 | 521 | // approving a pull request's run, and a repository's own rules for its | |
| 521 | 522 | // environments and tokens, which are an admin's. |
Comments
Sign in to comment.