apilive

package
v3.0.0 Latest Latest
Warning

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

Go to latest
Published: Sep 10, 2026 License: MIT Imports: 8 Imported by: 0

Documentation

Overview

Package apilive is the committed record of what a booted GitLab says its own REST API is, and the one reader of it.

Every other oracle this repository holds about GitLab's REST API is a reading of text: the OpenAPI document GitLab commits to its own repository, or a scan of the Grape source that document is generated from. Both are downstream of the object that decides what a request returns, which is the Rails application with its classes loaded, and both lose the same thing when a name is not written down. GeoSiteStatus exposes its fields by iterating a constant assembled from two method calls: the source says "expose the loop variable", a scanner reads 26 fields, and GitLab sends 606. That is not a hole a better parser closes.

So this record is produced by asking the application. cmd/gen_api_live boots a released GitLab image, runs one script inside it, and writes what comes back here; every audit then reads this file with no Docker and no network, the way cmd/gen_graphql_schema's pin is read. The boot is a generator, never an audit: an audit that needed a container could not be a gate.

One thing evaluation does not give and the record therefore carries from source: a Grape condition is a Proc, and a Proc knows where it was written but not what it says. The generator reads those lines back from inside the same image, so a condition arrives here both located and quoted.

Index

Constants

View Source
const (
	// TierPremium unlocks with any paid plan from Premium up.
	TierPremium = "premium"
	// TierUltimate unlocks with Ultimate only.
	TierUltimate = "ultimate"
	// TierGlobal is the table's GLOBAL_FEATURES: licensed, but not a tier,
	// since every paid plan carries it.
	TierGlobal = "global"
)

Tier values, matching the licensed feature table's own three lists.

View Source
const DefaultDir = "docs/development"

DefaultDir is where the record lives, beside the other pinned records.

View Source
const EndpointPrefix = "/api/:version"

EndpointPrefix is what Grape mounts every route under. A caller writes /projects/:id and Grape holds /api/:version/projects/:id, so one of the two spellings has to give, and it is this one: the inventory records what the SDK sent, which is what a reader of a finding will go looking for.

View Source
const FileName = "gitlab-api-live.json"

FileName is the record's name on disk.

View Source
const SchemaVersion = 2

SchemaVersion is the shape of this record. A reader refuses a version it was not written for rather than guessing at a field that moved.

Version 2 records Field.Merge. The bump is not cosmetic: version 1 spelled a merged exposure exactly like a nested one, so a reader of an old record would resolve it into the wrong keys and never know, which is the one failure a version guard exists to stop.

Variables

This section is empty.

Functions

func LicensedFeatures

func LicensedFeatures(condition string) []string

LicensedFeatures names the features a condition asks a license about, in order of appearance and without repeats.

A symbol found here is not necessarily licensed: feature_available?(:issues) asks a project setting, not a plan. Document.Tier is what tells them apart, because only the licensed feature table can.

func NormalizePath

func NormalizePath(path string) string

NormalizePath trims the mount prefix off a route, leaving the path a caller writes.

Grape's own placeholders are already the spelling the request inventory records (:id, :user_id), so nothing else has to be rewritten. That is a property of asking the router rather than reading a generated document: the OpenAPI record spells the same placeholder {id} and had to be translated.

func OpenAPIName

func OpenAPIName(rubyName string) string

OpenAPIName is the entity's Ruby name in the spelling GitLab's OpenAPI document gives its schema, so a reader holding one can look up the other.

API::Entities::Ci::Variable becomes APIEntitiesCiVariable: the separators go and the segments keep their own casing.

func Path

func Path(dir string) string

Path is where the record lives under a repository root.

func Write

func Write(dir string, doc Document) error

Write commits the record, formatted so a re-pin is a readable diff rather than one very long line.

Types

type Condition

type Condition struct {
	// Kind is Grape's own class name for it: BlockCondition for a lambda,
	// HashCondition for `if: {…}`.
	Kind string `json:"kind"`
	// Inverse is true for `unless:`.
	Inverse bool `json:"inverse,omitempty"`
	// File and Line locate a block condition's lambda, repository relative.
	File string `json:"file,omitempty"`
	Line int    `json:"line,omitempty"`
	// Text is those lines read back from inside the image and squeezed onto
	// one. A Proc knows where it was written and not what it says, so this is
	// the only way the condition arrives readable, and it is what a rule
	// classifies on.
	Text string `json:"text,omitempty"`
	// Hash is a hash condition's own data.
	Hash string `json:"hash,omitempty"`
}

Condition is one gate on an exposure.

type Document

type Document struct {
	SchemaVersion int    `json:"schema_version"`
	Note          string `json:"note"`
	Source        Source `json:"source"`
	// Entities is every API::Entities class the instance had loaded, keyed by
	// its Ruby name (API::Entities::Project). OpenAPIName translates that to
	// the spelling the OpenAPI record uses, for a reader joining the two.
	Entities map[string]Entity `json:"entities"`
	// Routes is every endpoint Grape had mounted, in the order the router
	// holds them.
	Routes []Route `json:"routes"`
	// Features maps a licensed feature symbol to the tier that unlocks it.
	Features map[string]string `json:"features"`
}

Document is the committed record.

func Read

func Read(dir string) (Document, error)

Read loads the committed record.

A schema version this build was not written for is refused rather than decoded: the fields would parse and mean something else, which is the one failure a reader cannot detect later.

func (Document) FieldCount

func (d Document) FieldCount() int

FieldCount totals the exposures across every entity, which is the figure a floor is set against: a record that lost half its fields is one an audit would read as GitLab having stopped sending them.

func (Document) FieldNames

func (d Document) FieldNames(entity string) []string

FieldNames lists the keys an entity sends, sorted, and nil for one this record does not hold. It is what an audit holds a response against.

func (Document) Fields

func (d Document) Fields(entity string) (map[string]Field, bool)

Fields is the entity's exposures by the key GitLab sends, the last declaration of a name winning as Grape's own render does, and false for an entity this record does not hold.

Inheritance needs no resolving here: the introspection reads root_exposures, which is the class as it will render, with everything it inherits and everything an Enterprise module prepended already flattened in. The scanned record this replaced had to walk a parent chain to reach the same list, and could not see a prepend it had no file for. Merging is the one edge that still has to be followed, and Document.Resolve follows it.

func (Document) GateOf

func (d Document) GateOf(field Field) Gate

GateOf reads what a field's conditions demand, resolving the tier against this record's own licensed feature table.

func (Document) Names

func (d Document) Names() []string

Names lists the entities in a stable order.

func (Document) NestedNames

func (d Document) NestedNames(entity string) map[string][]string

NestedNames is, for each field of the entity that renders with an entity of its own, the keys that child sends.

This is the tree the record carries and a generated OpenAPI document flattens: `expose :author, using: Entities::UserBasic` says exactly which object sits under `author`, so a nested comparison joins on the edge rather than on a property name that happened to describe an object.

func (Document) Resolve

func (d Document) Resolve(entity string) ([]Field, bool)

Resolve is the entity's exposures in declaration order with every merged one replaced by the fields it contributes, and false for an entity this record does not hold.

A merged exposure sends its child's keys on the parent object and no key of its own, so it is the one place where the exposure list and the response differ. Reading it literally inverts both halves of a comparison at once: API::Entities::Member merges UserBasic, and an audit reading the raw list reported `user` as a key GitLab sends and we drop, while reporting the id, username, name, state and avatar_url we do publish as keys GitLab never sends.

A merged exposure's own conditions gate everything it contributes, since Grape skips the whole exposure when they fail, so they are carried onto each promoted field ahead of that field's own.

func (Document) RoutesByEntity

func (d Document) RoutesByEntity() map[string][]Route

RoutesByEntity indexes the endpoints each entity is annotated on, which is the join an audit walks: from a type, to the entity it models, to the endpoints that render it.

func (Document) Tier

func (d Document) Tier(features ...string) string

Tier resolves the highest tier the named features unlock at.

Ultimate wins over premium, and premium over global, because a field gated by two features needs the dearer plan. A feature the table does not list resolves to nothing: a project setting such as :issues is not a license.

type Entity

type Entity struct {
	// Error is set when the class refused to describe itself, which is a fact
	// about that class rather than a reason to drop it: a silently missing
	// entity reads as one GitLab does not have.
	Error string `json:"error,omitempty"`
	// Fields are the exposures in declaration order, with everything the
	// class inherits already flattened in, because root_exposures answers for
	// the class as it will render.
	Fields []Field `json:"fields,omitempty"`
}

Entity is one Grape entity as the loaded class describes itself.

type Field

type Field struct {
	// Name is the key GitLab sends.
	Name string `json:"name"`
	// Attribute is the method the value comes from, recorded only when `as:`
	// made it differ from Name: the key is what a client sees and the
	// attribute is what a reader greps the source for.
	Attribute string `json:"attribute,omitempty"`
	// Using names the entity the value renders with, in the same keying as
	// Document.Entities. It is the edge that makes the record a tree rather
	// than a list.
	Using string `json:"using,omitempty"`
	// Merge is set for an exposure whose value is merged into the object
	// around it instead of being placed under Name: `expose :user, merge:
	// true, using: UserBasic` on a member sends the user's own keys on the
	// member, and no `user` key at all.
	//
	// Recording it is what keeps the record from reading as the opposite of
	// what GitLab sends. Without it a member said it carries a `user` object
	// and said nothing about the id, username and name it really carries, so
	// an audit reported the one key GitLab never sends as missing and the
	// nine it does send as invented.
	Merge bool `json:"merge,omitempty"`
	// Conditions gate the field. Empty means GitLab sends it with every
	// response of every endpoint that renders this entity.
	Conditions []Condition `json:"conditions,omitempty"`
}

Field is one exposure.

type Gate

type Gate struct {
	// If and Unless are the condition texts, joined with " && " when a field
	// carries several, since Grape requires all of them to pass.
	If     string
	Unless string
	// Tier is the highest plan the conditions demand, empty when they demand
	// none.
	Tier string
	// Edition is "ee" when a condition was written under ee/, which is where
	// Enterprise prepends live.
	//
	// It is read from the condition's own file, so a field with no condition
	// has no location to answer from and reports nothing: an exposure records
	// where its lambda was written and not where the exposure itself was.
	Edition string
}

Gate is what a field's conditions amount to, in the terms a finding reports.

func (Gate) Gated

func (g Gate) Gated() bool

Gated reports whether the field is sent under any condition at all.

type Param

type Param struct {
	Required bool   `json:"required,omitempty"`
	Type     string `json:"type,omitempty"`
	Default  string `json:"default,omitempty"`
	Desc     string `json:"desc,omitempty"`
}

Param is one declared parameter.

type Route

type Route struct {
	Method string `json:"method"`
	// Path is Grape's origin, without the (.:format) suffix the router adds,
	// since no caller sends that.
	Path string `json:"path"`
	// Entity is what the endpoint's `desc … success/entity` annotation names,
	// which is the same annotation GitLab's OpenAPI generator reads. It can be
	// wrong about what the endpoint really presents, and being wrong in the
	// record is better than being absent: GET /keys is annotated
	// APIEntitiesUserWithAdmin and serves an SSH key with a user under it.
	Entity  string `json:"entity,omitempty"`
	Summary string `json:"summary,omitempty"`
	// Params is what the endpoint declares it accepts, keyed by name.
	Params map[string]Param `json:"params,omitempty"`
}

Route is one mounted endpoint.

type Source

type Source struct {
	Image       string `json:"image"`
	Digest      string `json:"digest,omitempty"`
	Version     string `json:"version"`
	Revision    string `json:"revision,omitempty"`
	RetrievedAt string `json:"retrieved_at"`
	// SHA256 is of the introspection output as it left the container, before
	// this record wrapped it, so two runs of one image can be compared without
	// the wrapper's own fields entering the digest.
	SHA256   string `json:"sha256"`
	Entities int    `json:"entities"`
	Fields   int    `json:"fields"`
	Routes   int    `json:"routes"`
	Features int    `json:"features"`
}

Source is the instance the record was taken from.

The image reference and the version are both recorded because they answer different questions: the reference is what to run to reproduce this, and the version is what to compare against a GitLab release. A record whose version is behind the current release is stale in the sense that matters, and no amount of re-running the same image fixes it.

func (Source) String

func (s Source) String() string

String renders the provenance as one reportable line.

Jump to

Keyboard shortcuts

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