Help us shape the future of Go! Take the 2026 Go Developer Survey

actionrequests

package
v3.3.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Oct 20, 2026 License: MIT Imports: 16 Imported by: 0

Documentation

Overview

Package actionrequests answers, for a catalog action, which functions run when it is called and what those functions put on the wire.

It is the handler resolver cmd/audit_readonly_graphql was built on, taken out of that command because a second reader needs the same answer: the derivation of which requests each action makes, which is what decides the fine-grained permissions an action needs (issue 952). One reader of one fact is the rule cmd/internal/actionids and cmd/internal/graphqldocs were created for, and two copies of a resolver would drift apart exactly where a gate needs them to agree.

From an action to its handlers

Program.Sites resolves every ActionSpec construction in the loaded source to the action name it declares, the individual tool name its options declare, and the functions its route runs. The resolution is a small interprocedural constant propagation, because the specs are written in many shapes: most domains call a package-local helper that forwards the name, the route and the options to a toolutil constructor as parameters, and the resolver follows the parameters rather than matching one spelling.

Match joins a site to a catalog action (Catalog) on the canonical ID, through the tool name where the name and the package do not decide. They do not for the project and group badge actions, which one package declares under one set of names, for the two security settings updates and for the license template read the admin specs declare again; joined on name and owner alone, each of those fifteen actions was handed both sites and both handlers.

From handlers to what they reach

Program.Roots turns the matched handlers into the functions a walk starts from, and Program.Reachable walks what they call, with no depth bound and a cycle guard. Three shapes needed more than a call graph of declared functions:

  • A handler written as a function literal is indexed as a function of its own, so what it names directly is part of what the action does.
  • A literal that calls a parameter of the route helper it was written in (awardEmojiDeleteRoute wraps the delete it was handed) calls whatever the helper's caller bound to that parameter, which the frame the literal was resolved in records.
  • A package-level variable's initializer is indexed as a function of its own, so a test seam holding a literal (the group boards' newRawRequest), a dispatch table of literals (the integrations' getters) and a method expression (commits' newRequest) are reached by the bodies that name them.

Every indexed body records the GraphQL documents it names (by text, since what a document asks for is each reader's question), whether it reaches the GraphQL transport, and the client-go service methods it names, keyed the way cmd/internal/sdkroutes keys the request each one sends.

What it does not do is derive a raw request's path, classify a request as unconditional or one of several alternatives, or join anything to GitLab's own declarations; those belong to the generator that reads this package for the fine-grained permission table.

The record of what each action sends

What that generator derives is committed as docs/development/action-requests.json, and the record's shape is declared here (Record, ReadRecord, RenderRecord) because it has two readers on either side of the file: cmd/gen_action_grants writes it, and R-GRANT (cmd/audit_1to1 -scope=grants) reads it back to say what shaped each action's requirement and to hold the derivation to what the unit suite and an end-to-end run were seen sending. The request kinds and classes are spelled once here for the same reason.

Index

Constants

View Source
const (
	// KindREST is a REST route.
	KindREST = "rest"
	// KindGraphQL is one GraphQL document posted to the GraphQL endpoint.
	KindGraphQL = "graphql"
	// KindUnresolved is a request the walk reached and could not read, which a
	// declaration answers.
	KindUnresolved = "unresolved"

	// ClassMandatory is a request every path of the action makes.
	ClassMandatory = "mandatory"
	// ClassAlternative is a request some paths make and others do not.
	ClassAlternative = "alternative"
	// ClassOptional is a request no path needs.
	ClassOptional = "optional"

	// ClassicAPIOnlyField heads the classic declaration of a GraphQL query
	// that needs api because it selects a field GitLab answers only to api,
	// followed by the fields it selects.
	ClassicAPIOnlyField = "api-only-field"
)

The kinds a recorded request comes in, and the classes it is needed by.

View Source
const ClientGoPath = "gitlab.com/gitlab-org/api/client-go/v3"

ClientGoPath is the import path of client-go's root package, whose service methods are the requests a handler hands to the SDK, and whose Client builds the raw requests a handler sends without one. Exported so a reader of this package's bodies tells client-go apart by the same spelling.

View Source
const RecordNote = "" /* 627-byte string literal not displayed */

RecordNote heads the record, so a reader arriving at the file knows what it is and where it comes from. It is one literal rather than a concatenation, which leaves the mutation tool no operator to report as unmeasured.

View Source
const RecordPath = "docs/development/action-requests.json"

RecordPath is where the record lives, relative to the repository root.

Variables

This section is empty.

Functions

func RenderRecord

func RenderRecord(actions []RecordAction) []byte

RenderRecord marshals actions as the committed record, under its note. An action with no path is written with an empty list rather than none, since a reader indexes it, and no action at all is written as an empty list too.

The marshal goes through cmdutil.Must because it cannot fail on a value built out of strings, ints and slices of them.

func SendsGraphQL

func SendsGraphQL(callee *types.Func) bool

SendsGraphQL reports whether naming callee puts a GraphQL document on the wire: client-go's GraphQL Do, or one of the shared toolutil executors that call it with a document their caller supplied. It is the test a body's Function.SendsGraphQL is recorded by, exported for a reader that walks a body itself and has to ask it of one name at a time.

func ServiceMethod

func ServiceMethod(callee *types.Func) (string, bool)

ServiceMethod names a client-go service method the way [sdkroutes] keys it and reports whether callee is one, which is how a body's Function.SDKMethods are recorded, exported for the same reader as SendsGraphQL.

Types

type Action

type Action struct {
	// ID is the canonical action ID, which a finding is filed under.
	ID string
	// Name is the action name its spec declares, which a construction site
	// is found by.
	Name string
	// Owner is the package the catalog names as the action's owner.
	Owner string
	// Tool is the individual tool name the action projects, which tells apart
	// two sites declaring one name in one package. It is "" for an action the
	// individual surface does not publish.
	Tool string
	// ReadOnly is the catalog's classification of the action.
	ReadOnly bool
	// Group is the tool name of the catalog group the action belongs to,
	// which the scopes a group demands are keyed by.
	Group string
}

Action is one catalog action, with what a construction site is joined to it on and the classification a reader judges it by.

func Catalog

func Catalog() ([]Action, error)

Catalog returns every action this repository publishes, sorted by ID: the catalog at Ultimate for a self-managed instance and for GitLab.com, with the standalone surface actions, each action once.

Both builds are read because each holds actions the other does not: Orbit's six only the GitLab.com build, and an action gated the other way only the self-managed one. An action both hold is taken from the first build, which is the self-managed one; the two agree on everything read here.

type DocumentUse

type DocumentUse struct {
	// Text is the document as GitLab receives it, fragments spliced in.
	Text string
	// Name is the constant or variable the document was declared as, or ""
	// when it is written inline at the point of use.
	Name string
	// Pos is where the body names it, which is the line inside the handler a
	// reader has to change, and so the line a finding points at.
	Pos token.Pos
}

DocumentUse is one GraphQL document named inside a body.

type Function

type Function struct {
	// Documents is every GraphQL document this body names.
	Documents []DocumentUse
	// SendsGraphQL reports whether this body reaches the GraphQL transport,
	// which is what makes a document it names a request rather than a string.
	SendsGraphQL bool
	// SDKMethods is every client-go service method this body names, as
	// "Service.Method", sorted. A method value handed to a helper counts, for
	// the reason a function reference counts as a call.
	SDKMethods []string
	// contains filtered or unexported fields
}

Function is one indexed body with everything a reader of the walk asks of it. The exported fields are what it found; the rest is how it is walked.

func (*Function) Bound

func (f *Function) Bound(variable *types.Var) []*types.Func

Bound returns, for a handler literal, the functions the route helper's caller bound to one of the helper's function-valued parameters the literal names, and nothing for any other variable or body.

func (*Function) Decl

func (f *Function) Decl() *ast.FuncDecl

Decl returns the declaration a declared function was indexed from, whose parameters a caller's arguments bind to, or nil for a stand-in: a handler literal's or an initializer's.

func (*Function) Package

func (f *Function) Package() *packages.Package

Package returns the package the body was written in, whose type information a reader of Function.Root resolves names through.

func (*Function) Root

func (f *Function) Root() ast.Node

Root returns the node the body was indexed from: a declared function's body, a handler literal's body, or a package-level variable's initializer. A reader that asks more of a body than the summary fields carry, such as which of its requests sit in a branch, walks this.

type Handler

type Handler struct {
	// Func is the declared function, or nil for a literal.
	Func *types.Func
	// Lit is the literal, or nil for a declared function.
	Lit *ast.FuncLit
	// contains filtered or unexported fields
}

Handler is one function an action routes to: a declared function, or a function literal written at the route site.

type Program

type Program struct {
	// contains filtered or unexported fields
}

Program is the loaded, indexed source the resolution and the walk reason over.

It carries what a reader of it consults and nothing else. Two fields used to sit beside these and be read by nothing: a map of the loaded packages by import path, filled at load, and on each Function the object it was declared as, which funcs is already keyed by. A field no reader consults is one no test can hold, so both are gone.

func Load

func Load(dir string, patterns []string, overlay map[string][]byte) (*Program, error)

Load loads and indexes the packages named by patterns, rooted at dir.

The load itself, including the refusal of a package that did not type-check, belongs to goprogram.Load; what is here is the indexing the walk needs. The overlay is passed straight through: it is how a test supplies source that is not on disk, so a fixture package written in a test file type-checks against the real toolutil and the resolver is exercised on the shapes it has to handle rather than on a mock of them. Production passes nil.

func (*Program) Document

func (p *Program) Document(obj types.Object) (string, bool)

Document returns the text of the GraphQL document an object declares, or false for an object that declares none.

func (*Program) Function

func (p *Program) Function(fn *types.Func) (*Function, bool)

Function returns the indexed body of a function, or false for one this program holds no body for: a function of a package outside the load, or a method of an interface.

func (*Program) Initializer

func (p *Program) Initializer(variable *types.Var) (*types.Func, bool)

Initializer returns the stand-in function a package-level variable's initializer was indexed as, or false for a variable this program indexed no initializer for.

func (*Program) Packages

func (p *Program) Packages() []*packages.Package

Packages returns the loaded packages in the loader's order, for a reader that reads something of its own out of the syntax, such as a directive.

func (*Program) Position

func (p *Program) Position(pos token.Pos) token.Position

Position renders a source position.

func (*Program) Reachable

func (p *Program) Reachable(roots []*types.Func) map[*types.Func]bool

Reachable returns the transitive closure of functions a handler can run, including the handlers themselves. It is unbounded, with a cycle guard: the requests the deeper calls make are the second and third requests of the actions that make several, so a depth bound loses them.

func (*Program) Roots

func (p *Program) Roots(matched []Site) []*types.Func

Roots turns the handlers of matched sites into call-graph roots, one per handler.

A handler written as a function literal has no declared function, so its body is indexed as a function of its own under a stand-in, and the stand-in is the root: what the literal names directly is part of what the action does, and so is what its callees do. A literal that calls a parameter of the route helper it was written in (awardEmojiDeleteRoute wraps the delete it was handed) calls whatever the helper's caller bound to that parameter, and that function is recorded as one of the stand-in's calls.

A literal is indexed afresh on every call, because what it calls through a parameter depends on the frame it was resolved in: the four guided creation flows are one literal in elicitationRoute, each bound to its own flow, and a stand-in kept across calls would hand every flow the first one's.

func (*Program) Sites

func (p *Program) Sites() map[string][]Site

Sites resolves every ActionSpec construction the loaded packages contain, keyed by the action name it declares.

func (*Program) Unattributed

func (p *Program) Unattributed() []graphqldocs.Document

Unattributed returns every GraphQL document the inventory holds that nothing here can tie to a body. A reader that answers for what its handlers send has to report these rather than pass them, since one could be a mutation a handler sends.

type Record

type Record struct {
	Note    string         `json:"note"`
	Actions []RecordAction `json:"actions"`
}

Record is the file.

func ReadRecord

func ReadRecord(root string) (Record, error)

ReadRecord loads the committed record from a repository root.

A record with no action in it is an error rather than an empty record, for the reason the request inventory gives: every reader answers a question of the form "which of these does the derivation say", and an empty file answers all of them with "nothing", which is the loudest possible wrong answer.

type RecordAction

type RecordAction struct {
	ID       string   `json:"id"`
	Handlers []string `json:"handlers"`
	// Declaration is the category of the action-level declaration that
	// answers what the walk could not, when one does.
	Declaration string `json:"declaration,omitempty"`
	// Classic is the classic scope the action needs, as
	// finegrained.ClassicScope spells it, and GroupScopes the scopes its
	// catalog group demands besides (admin_mode), which the server's scope
	// filter decides per group.
	Classic     string          `json:"classic"`
	GroupScopes []string        `json:"group_scopes,omitempty"`
	Requests    []RecordRequest `json:"requests,omitempty"`
	// Paths are the ways the action runs, as indices into Requests.
	Paths [][]int `json:"paths"`
}

RecordAction is one action.

type RecordRequest

type RecordRequest struct {
	// Kind is one of the Kind constants.
	Kind string `json:"kind"`
	// Route is a REST request's route as the live record spells it, and
	// Derived the derivation's spelling where a declaration placed it.
	Route   string `json:"route,omitempty"`
	Derived string `json:"derived,omitempty"`
	// Operation, RootFields and Positions describe a GraphQL request.
	Operation  string   `json:"operation,omitempty"`
	RootFields []string `json:"root_fields,omitempty"`
	Positions  []string `json:"positions,omitempty"`
	// Reason names an unresolved request's class and subject.
	Reason string `json:"reason,omitempty"`
	// Class is one of the Class constants.
	Class string `json:"class"`
	// Classic is the classic scope GitLab requires for the request, as
	// finegrained.ClassicScope spells it, and ClassicDeclaration the category
	// of what departed from GitLab's general rule to decide it, when
	// something did.
	Classic            string   `json:"classic"`
	ClassicDeclaration string   `json:"classic_declaration,omitempty"`
	Sites              []string `json:"sites,omitempty"`
	SDKMethods         []string `json:"sdk_methods,omitempty"`
	// Directives are the //gitlab:request directives that qualified it, as
	// "<kind>: <reason>".
	Directives []string `json:"directives,omitempty"`
	// Declaration is the category of the declaration that supplied it.
	Declaration string `json:"declaration,omitempty"`
}

RecordRequest is one request of one action.

type Site

type Site struct {
	// Package is the name of the package the construction is written in.
	Package string
	// Name is the action name it declares.
	Name string
	// Tool is the individual tool name its options declare, or "" when the
	// options could not be followed to one. It is what joins a site to one
	// canonical ID where two sites share a name: the project and group badge
	// actions are declared by one package under one set of names.
	Tool string
	// Handlers is every function its route runs.
	Handlers []Handler
	// Pos is where the construction is written.
	Pos token.Pos
}

Site is one resolved ActionSpec construction.

func Match

func Match(sites map[string][]Site, act Action) []Site

Match picks the construction sites that declare one catalog action.

The join is on the canonical ID, through what a site can be read for: its action name, the individual tool name its options declare, and the package it is written in. A site whose tool name was read and is not the action's declares another action whatever its name and package, so it is never taken; a site whose tool name is the action's is the action's, and when one is found nothing else is taken. Only where no site's tool name could be read does the package decide, and when none of those is the owner, every site with the name is taken, because a wrong guess must not be the quiet one.

Directories

Path Synopsis
Package actionfixture holds the fixture packages the suites of cmd/internal/actionrequests and cmd/audit_readonly_graphql both load: Go source written as strings, put into the loader's overlay under Dir, a directory that exists nowhere on disk, and type-checked against the real toolutil and client-go so a resolver is held to the shapes it meets in the tree rather than to a mock of them.
Package actionfixture holds the fixture packages the suites of cmd/internal/actionrequests and cmd/audit_readonly_graphql both load: Go source written as strings, put into the loader's overlay under Dir, a directory that exists nowhere on disk, and type-checked against the real toolutil and client-go so a resolver is held to the shapes it meets in the tree rather than to a mock of them.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL