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
- func RenderRecord(actions []RecordAction) []byte
- func SendsGraphQL(callee *types.Func) bool
- func ServiceMethod(callee *types.Func) (string, bool)
- type Action
- type DocumentUse
- type Function
- type Handler
- type Program
- func (p *Program) Document(obj types.Object) (string, bool)
- func (p *Program) Function(fn *types.Func) (*Function, bool)
- func (p *Program) Initializer(variable *types.Var) (*types.Func, bool)
- func (p *Program) Packages() []*packages.Package
- func (p *Program) Position(pos token.Pos) token.Position
- func (p *Program) Reachable(roots []*types.Func) map[*types.Func]bool
- func (p *Program) Roots(matched []Site) []*types.Func
- func (p *Program) Sites() map[string][]Site
- func (p *Program) Unattributed() []graphqldocs.Document
- type Record
- type RecordAction
- type RecordRequest
- type Site
Constants ¶
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.
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.
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.
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 ¶
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 ¶
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 ¶
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 ¶
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 ¶
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 ¶
Package returns the package the body was written in, whose type information a reader of Function.Root resolves names through.
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 ¶
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 ¶
Document returns the text of the GraphQL document an object declares, or false for an object that declares none.
func (*Program) Function ¶
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 ¶
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 ¶
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) Reachable ¶
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 ¶
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 ¶
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 ¶
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 ¶
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. |