changelist

package
v0.0.0-...-51f9457 Latest Latest
Warning

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

Go to latest
Published: Jul 9, 2021 License: Apache-2.0 Imports: 20 Imported by: 0

Documentation

Overview

Package changelist implements operations on a single CL.

It is agnostic about the kind of a CL even though currently all CLs represent Gerrit changes.

Index

Constants

This section is empty.

Variables

View Source
var (
	DepKind_name = map[int32]string{
		0: "DEP_KIND_UNSPECIFIED",
		1: "HARD",
		2: "SOFT",
	}
	DepKind_value = map[string]int32{
		"DEP_KIND_UNSPECIFIED": 0,
		"HARD":                 1,
		"SOFT":                 2,
	}
)

Enum value maps for DepKind.

View Source
var ErrStopMutation = errors.New("stop CL mutation")

ErrStopMutation is a special error used by MutateCallback to signal that no mutation is necessary.

This is very useful because the datastore.RunInTransaction(ctx, f, ...) does retries by default which combined with submarine writes (transaction actually succeeded, but the client didn't get to know, e.g. due to network flake) means an idempotent MutateCallback can avoid noop updates yet still keep the code clean and readable. For example,

```

cl, err := mu.Update(ctx, project, clid, func (cl *changelist.CL) error {
  if cl.Snapshot == nil {
    return ErrStopMutation // noop
  }
  cl.Snapshot = nil
  return nil
})
if err != nil {
  return errors.Annotate(err, "failed to reset Snapshot").Err()
}

doSomething(ctx, cl) ```

View Source
var File_go_chromium_org_luci_cv_internal_changelist_storage_proto protoreflect.FileDescriptor
View Source
var File_go_chromium_org_luci_cv_internal_changelist_task_proto protoreflect.FileDescriptor

Functions

func Delete

func Delete(ctx context.Context, id common.CLID) error

Delete deletes CL and its CLMap entities transactionally.

Thus, Delete and insertion (part of ExternalID.getOrInsert) are atomic with respect to one another.

However, ExternalID.get and fast path of ExternalID.getOrInsert if called concurrently with Delete may return a temporary error, but on retry they would return ErrNoSuchEntity.

func Lookup

func Lookup(ctx context.Context, eids []ExternalID) ([]common.CLID, error)

Lookup loads CLID for each given ExternalID.

CLID is 0 if ExternalID is not yet known. Returns a single error (not MultiError) if there were multiple errors.

func RemoveUnusedGerritInfo

func RemoveUnusedGerritInfo(ci *gerritpb.ChangeInfo)

RemoveUnusedGerritInfo mutates given ChangeInfo to remove what CV definitely doesn't need to reduce bytes shuffled to/from Datastore.

Doesn't complain if anything is missing.

NOTE: keep this function actions in sync with storage.proto doc for Gerrit.info field.

func Sort

func Sort(cls []*CL)

Sort sorts slice of CLs by their CLID.

Types

type Access

type Access struct {
	ByProject map[string]*Access_Project `` // TODO(tandrii): per-project ApplicableConfig here.
	/* 176-byte string literal not displayed */
	// contains filtered or unexported fields
}

Access records which LUCI project can or can't see a CL.

If a LUCI project has Access, it means both:

(1) the project can read details of the CL (via Git/Gerrit ACLs);
(2) the project is the only LUCI project watching this CL in CV
    (via the CV config).
    Note: there can still be several applicable ConfigGroups of the same
    project (see ApplicableConfig).

In practice, .Access is set in 4 cases:

(a) `CQ-Depend: host:number` Gerrit CL footers allow users to specify

arbitrary dependencies, which typically happens due to typos,
but malicious actors can try to get CL details of restricted projects.
Either way, CV must not be a confused deputy here and must keep track
which project can see what.

(b) due to recent re-configuration of one or more LUCI projects, either

in CV config and/or in Gerrit ACLs, the previously watched & readable CL
becomes unwatched and/or unreadable.

(c) a previously existing CL was deleted (e.g. by its owner or Gerrit

administrators).

(d) eventual consistency of Gerrit masquerading as HTTP 404 on stale replica,

while quorum of replicas think CL actually exists and specific LUCI
project having access to it.

Unfortunately, (d) isn't easy to distinguish from (b) and (c), so CV resorts to tracking time since CL became invisible -- the longer, the more likely it is (b) or (c).

Furthermore, in case of (a), iff CV knows nothing about specific Gerrit CL identified as `CQ-Depend: host:change`, CV in general can't determine which LUCI project is allowed to watch this CL *before* fetching Gerrit project (repo) and target ref.

NOTE on CV as confused deputy.

CV works with multiple LUCI projects. As of this writing (June 2021), unfortunately, CV doesn't verify that Gerrit repos watched by a LUCI project are in fact owned by that LUCI project. Thus, nothing prevents one LUCI project from starting to watch repos de-facto owned by another LUCI project. This in turn brings 2 problems:

(1) Denial of service: unsolved.

Mitigation: CV will refuse to work with CLs which are watched by more
than 1 project. Since CV will communicate by posting message to affected
CL, this should be noticed and fixed quickly.

(2) Information leaks: solved.

Each LUCI project MUST use project-scoped service account (PSSA)
(migration is under way, see https://crbug.com/824492).
CV uses this account for all interaction with Gerrit on behalf a specific
LUCI project. Corresponding Gerrit repos:
  * SHOULD limit read access to its own PSSA + developers,
  * MUST limit Submit rights to its own PSSA and possibly developers.

For example,

  • `infra` project has all its Gerrit CLs public and doesn't care about information leaks. All other LUCI projects can read its CLs, as well as the whole Internet.
  • `infra-internal` project protects its Gerrit CLs, making them visible to `infra-internal-scoped@...` account only. When CV queries Gerrit on `infra-internal` behalf, CV uses `infra-internal-scoped` account and can fetch the data.
  • Suppose malicious actor compromised `infra` repo, and placed a new CV config there to start watching CLs of the `infra-internal` project as well as super/secret/repo, which wasn't watched by any CV before.
  • Unfortunately, CV can't currently object to the new config.
  • However, when querying Gerrit on `infra` behalf, CV uses `infra-scoped@...` account, which presumably won't be configured with read access to neither infra-internal nor super/secret/repo.
  • So, corresponding CLs will have .Access entry recording that `infra` has no access to them.
  • NOTE: CLs of infra-internal will also have .ApplicableConfig with two projects there, which will prevent normal operation of `infra-internal` CV but will not cause any leaks.

func (*Access) Descriptor deprecated

func (*Access) Descriptor() ([]byte, []int)

Deprecated: Use Access.ProtoReflect.Descriptor instead.

func (*Access) GetByProject

func (x *Access) GetByProject() map[string]*Access_Project

func (*Access) ProtoMessage

func (*Access) ProtoMessage()

func (*Access) ProtoReflect

func (x *Access) ProtoReflect() protoreflect.Message

func (*Access) Reset

func (x *Access) Reset()

func (*Access) String

func (x *Access) String() string

type AccessKind

type AccessKind int

AccessKind is the level of access a LUCI project has to a CL.

const (
	// AccessUnknown means a CL needs refreshing in the context of this project
	// in order to ascertain the AccessKind.
	AccessUnknown AccessKind = iota
	// AccessGranted means this LUCI project has exclusive access to the CL.
	//
	//  * this LUCI project is configured to watch this config,
	//    * and no other project is;
	//  * this LUCI project has access to the CL in code review (e.g., Gerrit);
	AccessGranted
	// AccessDeniedProbably means there is early evidence that LUCI project lacks
	// access to the project.
	//
	// This is a mitigation to Gerrit eventual consistency, which may result in
	// HTTP 404 returned for a CL that has just been created.
	AccessDeniedProbably
	// AccessDenied means the LUCI project has no access to this CL.
	//
	// Can be either due to project config not being the only watcher of the CL,
	// or due to the inability to fetch CL from code review (e.g. Gerrit).
	AccessDenied
)

type Access_Project

type Access_Project struct {

	// Deprecated. Use no_access_time instead.
	NoAccess bool `protobuf:"varint,1,opt,name=no_access,json=noAccess,proto3" json:"no_access,omitempty"`
	// The time when this was last re-confirmed.
	UpdateTime *timestamppb.Timestamp `protobuf:"bytes,2,opt,name=update_time,json=updateTime,proto3" json:"update_time,omitempty"`
	// The time after which CV should consider lack of access stable.
	//
	// TODO(crbug/1216630): may be unset until backfil is done,
	// in which case use `no_access` field.
	NoAccessTime *timestamppb.Timestamp `protobuf:"bytes,3,opt,name=no_access_time,json=noAccessTime,proto3" json:"no_access_time,omitempty"`
	// contains filtered or unexported fields
}

func (*Access_Project) Descriptor deprecated

func (*Access_Project) Descriptor() ([]byte, []int)

Deprecated: Use Access_Project.ProtoReflect.Descriptor instead.

func (*Access_Project) GetNoAccess

func (x *Access_Project) GetNoAccess() bool

func (*Access_Project) GetNoAccessTime

func (x *Access_Project) GetNoAccessTime() *timestamppb.Timestamp

func (*Access_Project) GetUpdateTime

func (x *Access_Project) GetUpdateTime() *timestamppb.Timestamp

func (*Access_Project) ProtoMessage

func (*Access_Project) ProtoMessage()

func (*Access_Project) ProtoReflect

func (x *Access_Project) ProtoReflect() protoreflect.Message

func (*Access_Project) Reset

func (x *Access_Project) Reset()

func (*Access_Project) String

func (x *Access_Project) String() string

type ApplicableConfig

type ApplicableConfig struct {
	Projects []*ApplicableConfig_Project `protobuf:"bytes,2,rep,name=projects,proto3" json:"projects,omitempty"`
	// contains filtered or unexported fields
}

ApplicableConfig keeps track of configs applicable to a CL.

This is computed based on known set of LUCI project configs, versions of which are updated by CV independently, so the ApplicableConfig are also eventually consistent.

Normally, there is 1 applicable configs = exactly 1 project with 1 config group. If CL is no longer watched by CV, there will be 0 applicable configs.

Sometimes, there can be 2+ applicable configs. This happens if either:

  • eventual consistency: responsibility for CL is moved from one LUCI project to another. Three is no way to make this atomically, so CL may temporarily end up with 0 or 2 projects watching it, before settling on just 1.
  • misconfiguration: two projects or 2 different ConfigGroups within the same project watch the same CL.

In either case, CV refuses to guess and will abstain from processing such CLs, but storing the list is very useful for CV debugging and potentially for better diagnostic messages to CV users and LUCI project owners.

func (*ApplicableConfig) Descriptor deprecated

func (*ApplicableConfig) Descriptor() ([]byte, []int)

Deprecated: Use ApplicableConfig.ProtoReflect.Descriptor instead.

func (*ApplicableConfig) GetProjects

func (x *ApplicableConfig) GetProjects() []*ApplicableConfig_Project

func (*ApplicableConfig) HasOnlyProject

func (a *ApplicableConfig) HasOnlyProject(luciProject string) bool

HasOnlyProject returns true iff ApplicableConfig contains only the given project, regardless of the number of applicable config groups it may contain.

func (*ApplicableConfig) HasProject

func (a *ApplicableConfig) HasProject(luciProject string) bool

HasProject returns true whether ApplicableConfig contains the given project, possibly among other projects.

func (*ApplicableConfig) ProtoMessage

func (*ApplicableConfig) ProtoMessage()

func (*ApplicableConfig) ProtoReflect

func (x *ApplicableConfig) ProtoReflect() protoreflect.Message

func (*ApplicableConfig) Reset

func (x *ApplicableConfig) Reset()

func (*ApplicableConfig) SemanticallyEqual

func (a *ApplicableConfig) SemanticallyEqual(b *ApplicableConfig) bool

SemanticallyEqual checks if ApplicableConfig configs are the same.

func (*ApplicableConfig) String

func (x *ApplicableConfig) String() string

type ApplicableConfig_Project

type ApplicableConfig_Project struct {
	Name string `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"`
	// ID of the specific ConfigGroup. See cv/internal/config.ConfigGroupID.
	//
	// The referenced version may no longer be available to datastore,
	// commonly happening if CL wasn't active for a long time.
	ConfigGroupIds []string `protobuf:"bytes,2,rep,name=config_group_ids,json=configGroupIds,proto3" json:"config_group_ids,omitempty"`
	// contains filtered or unexported fields
}

func (*ApplicableConfig_Project) Descriptor deprecated

func (*ApplicableConfig_Project) Descriptor() ([]byte, []int)

Deprecated: Use ApplicableConfig_Project.ProtoReflect.Descriptor instead.

func (*ApplicableConfig_Project) GetConfigGroupIds

func (x *ApplicableConfig_Project) GetConfigGroupIds() []string

func (*ApplicableConfig_Project) GetName

func (x *ApplicableConfig_Project) GetName() string

func (*ApplicableConfig_Project) ProtoMessage

func (*ApplicableConfig_Project) ProtoMessage()

func (*ApplicableConfig_Project) ProtoReflect

func (x *ApplicableConfig_Project) ProtoReflect() protoreflect.Message

func (*ApplicableConfig_Project) Reset

func (x *ApplicableConfig_Project) Reset()

func (*ApplicableConfig_Project) String

func (x *ApplicableConfig_Project) String() string

type BatchOnCLUpdatedTask

type BatchOnCLUpdatedTask struct {
	Projects map[string]*CLUpdatedEvents `` /* 157-byte string literal not displayed */
	Runs     map[string]*CLUpdatedEvents `` /* 149-byte string literal not displayed */
	// contains filtered or unexported fields
}

BatchOnCLUpdatedTask notifies many Projects and Runs about updated CLs.

Queue: "notify-on-cl-updated".

func (*BatchOnCLUpdatedTask) Descriptor deprecated

func (*BatchOnCLUpdatedTask) Descriptor() ([]byte, []int)

Deprecated: Use BatchOnCLUpdatedTask.ProtoReflect.Descriptor instead.

func (*BatchOnCLUpdatedTask) GetProjects

func (x *BatchOnCLUpdatedTask) GetProjects() map[string]*CLUpdatedEvents

func (*BatchOnCLUpdatedTask) GetRuns

func (x *BatchOnCLUpdatedTask) GetRuns() map[string]*CLUpdatedEvents

func (*BatchOnCLUpdatedTask) ProtoMessage

func (*BatchOnCLUpdatedTask) ProtoMessage()

func (*BatchOnCLUpdatedTask) ProtoReflect

func (x *BatchOnCLUpdatedTask) ProtoReflect() protoreflect.Message

func (*BatchOnCLUpdatedTask) Reset

func (x *BatchOnCLUpdatedTask) Reset()

func (*BatchOnCLUpdatedTask) String

func (x *BatchOnCLUpdatedTask) String() string

type CL

type CL struct {

	// ID is auto-generated by Datastore.
	ID common.CLID `gae:"$id"` // int64
	// ExternalID must not be modified once entity is created.
	ExternalID ExternalID `gae:",noindex"` // string. Indexed in CLMap entities.

	// EVersion is entity version. Every update should increment it by 1.
	// See Update() function.
	EVersion int `gae:",noindex"`

	// UpdateTime is exact time of when this entity was last updated.
	//
	// It's not indexed to avoid hot areas in the index.
	UpdateTime time.Time `gae:",noindex"`

	// Snapshot is the latest known state of a CL. It may be and often is
	// behind the source of truth, which is the code review site (e.g. Gerrit).
	Snapshot *Snapshot

	// ApplicableConfig keeps track of configs applicable to the CL.
	//
	// TODO(tandrii): merge into .Access.
	ApplicableConfig *ApplicableConfig

	// Access records per-LUCI project visibility of a CL.
	//
	// See description in protobuf type with the same name.
	//
	// TODO(tandrii): rename GAE field to `Access`.
	Access *Access `gae:"DependentMeta"`

	// IncompleteRuns tracks not yet finalized Runs working on this CL. Sorted.
	//
	// It's updated transactionally with the Run being modified.
	IncompleteRuns common.RunIDs `gae:",noindex"`
	// contains filtered or unexported fields
}

CL is a CL entity in Datastore.

func LoadCLs

func LoadCLs(ctx context.Context, clids common.CLIDs) ([]*CL, error)

LoadCLs loads `CL` entities of the provided list of clids.

func LoadCLsMap

func LoadCLsMap(ctx context.Context, m map[common.CLID]*CL) ([]*CL, error)

LoadCLsMap loads `CL` entities which are values in the provided map.

Updates `CL` entities *in place*, but also returns them as a slice.

func (*CL) AccessKind

func (cl *CL) AccessKind(ctx context.Context, luciProject string) AccessKind

AccessKind returns AccessKind of a CL.

func (*CL) AccessKindFromCodeReviewSite

func (cl *CL) AccessKindFromCodeReviewSite(ctx context.Context, luciProject string) AccessKind

AccessKind returns AccessKind of a CL from code review site.

func (*CL) AccessKindWithReason

func (cl *CL) AccessKindWithReason(ctx context.Context, luciProject string) (AccessKind, string)

AccessKindWithReason returns AccessKind of a CL and a reason for it.

func (*CL) Mutate

func (cl *CL) Mutate(ctx context.Context, mut func(*CL) (updated bool)) (updated bool)

Mutate mutates the CL by executing `mut`.

It does basic sanity checks and ensures EVersion and UpdateTime are correctly updated if `mut` has changed the CL.

func (*CL) URL

func (cl *CL) URL() (string, error)

URL returns URL of the CL.

type CLError

type CLError struct {

	// Types that are assignable to Kind:
	//	*CLError_OwnerLacksEmail
	//	*CLError_WatchedByManyConfigGroups_
	//	*CLError_InvalidDeps_
	//	*CLError_UnsupportedMode
	//	*CLError_SelfCqDepend
	//	*CLError_CorruptGerritMetadata
	//	*CLError_ReusedTrigger_
	Kind isCLError_Kind `protobuf_oneof:"kind"`
	// contains filtered or unexported fields
}

CLError encapsulates all kinds of CL errors, which ultimately result in purging of the CL while communicating the reason to the relevant users.

The primary goal of the CLError is to transport via CV guts sufficient information to generate a clear user-friendly error message.

func (*CLError) Descriptor deprecated

func (*CLError) Descriptor() ([]byte, []int)

Deprecated: Use CLError.ProtoReflect.Descriptor instead.

func (*CLError) GetCorruptGerritMetadata

func (x *CLError) GetCorruptGerritMetadata() string

func (*CLError) GetInvalidDeps

func (x *CLError) GetInvalidDeps() *CLError_InvalidDeps

func (*CLError) GetKind

func (m *CLError) GetKind() isCLError_Kind

func (*CLError) GetOwnerLacksEmail

func (x *CLError) GetOwnerLacksEmail() bool

func (*CLError) GetReusedTrigger

func (x *CLError) GetReusedTrigger() *CLError_ReusedTrigger

func (*CLError) GetSelfCqDepend

func (x *CLError) GetSelfCqDepend() bool

func (*CLError) GetUnsupportedMode

func (x *CLError) GetUnsupportedMode() string

func (*CLError) GetWatchedByManyConfigGroups

func (x *CLError) GetWatchedByManyConfigGroups() *CLError_WatchedByManyConfigGroups

func (*CLError) ProtoMessage

func (*CLError) ProtoMessage()

func (*CLError) ProtoReflect

func (x *CLError) ProtoReflect() protoreflect.Message

func (*CLError) Reset

func (x *CLError) Reset()

func (*CLError) String

func (x *CLError) String() string

type CLError_CorruptGerritMetadata

type CLError_CorruptGerritMetadata struct {
	CorruptGerritMetadata string `protobuf:"bytes,6,opt,name=corrupt_gerrit_metadata,json=corruptGerritMetadata,proto3,oneof"`
}

type CLError_InvalidDeps

type CLError_InvalidDeps struct {

	// Deps not watched by the same LUCI project as the dependent.
	Unwatched []*Dep `protobuf:"bytes,1,rep,name=unwatched,proto3" json:"unwatched,omitempty"`
	// Deps watched by the same LUCI project but different config group.
	WrongConfigGroup []*Dep `protobuf:"bytes,2,rep,name=wrong_config_group,json=wrongConfigGroup,proto3" json:"wrong_config_group,omitempty"`
	// Not yet submitted deps of a full run in non-combinable mode.
	SingleFullDeps []*Dep `protobuf:"bytes,3,rep,name=single_full_deps,json=singleFullDeps,proto3" json:"single_full_deps,omitempty"`
	// Not yet CQ-ed deps of a Run in combinable mode.
	CombinableUntriggered []*Dep `protobuf:"bytes,4,rep,name=combinable_untriggered,json=combinableUntriggered,proto3" json:"combinable_untriggered,omitempty"`
	// CQ-ed deps of a different mode.
	CombinableMismatchedMode []*Dep `` /* 135-byte string literal not displayed */
	// There are more non-submitted deps than is supported by CV.
	TooMany *CLError_InvalidDeps_TooMany `protobuf:"bytes,6,opt,name=too_many,json=tooMany,proto3" json:"too_many,omitempty"`
	// contains filtered or unexported fields
}

func (*CLError_InvalidDeps) Descriptor deprecated

func (*CLError_InvalidDeps) Descriptor() ([]byte, []int)

Deprecated: Use CLError_InvalidDeps.ProtoReflect.Descriptor instead.

func (*CLError_InvalidDeps) GetCombinableMismatchedMode

func (x *CLError_InvalidDeps) GetCombinableMismatchedMode() []*Dep

func (*CLError_InvalidDeps) GetCombinableUntriggered

func (x *CLError_InvalidDeps) GetCombinableUntriggered() []*Dep

func (*CLError_InvalidDeps) GetSingleFullDeps

func (x *CLError_InvalidDeps) GetSingleFullDeps() []*Dep

func (*CLError_InvalidDeps) GetTooMany

func (*CLError_InvalidDeps) GetUnwatched

func (x *CLError_InvalidDeps) GetUnwatched() []*Dep

func (*CLError_InvalidDeps) GetWrongConfigGroup

func (x *CLError_InvalidDeps) GetWrongConfigGroup() []*Dep

func (*CLError_InvalidDeps) ProtoMessage

func (*CLError_InvalidDeps) ProtoMessage()

func (*CLError_InvalidDeps) ProtoReflect

func (x *CLError_InvalidDeps) ProtoReflect() protoreflect.Message

func (*CLError_InvalidDeps) Reset

func (x *CLError_InvalidDeps) Reset()

func (*CLError_InvalidDeps) String

func (x *CLError_InvalidDeps) String() string

type CLError_InvalidDeps_

type CLError_InvalidDeps_ struct {
	InvalidDeps *CLError_InvalidDeps `protobuf:"bytes,3,opt,name=invalid_deps,json=invalidDeps,proto3,oneof"`
}

type CLError_InvalidDeps_TooMany

type CLError_InvalidDeps_TooMany struct {
	Actual     int32 `protobuf:"varint,1,opt,name=actual,proto3" json:"actual,omitempty"`
	MaxAllowed int32 `protobuf:"varint,2,opt,name=max_allowed,json=maxAllowed,proto3" json:"max_allowed,omitempty"`
	// contains filtered or unexported fields
}

func (*CLError_InvalidDeps_TooMany) Descriptor deprecated

func (*CLError_InvalidDeps_TooMany) Descriptor() ([]byte, []int)

Deprecated: Use CLError_InvalidDeps_TooMany.ProtoReflect.Descriptor instead.

func (*CLError_InvalidDeps_TooMany) GetActual

func (x *CLError_InvalidDeps_TooMany) GetActual() int32

func (*CLError_InvalidDeps_TooMany) GetMaxAllowed

func (x *CLError_InvalidDeps_TooMany) GetMaxAllowed() int32

func (*CLError_InvalidDeps_TooMany) ProtoMessage

func (*CLError_InvalidDeps_TooMany) ProtoMessage()

func (*CLError_InvalidDeps_TooMany) ProtoReflect

func (*CLError_InvalidDeps_TooMany) Reset

func (x *CLError_InvalidDeps_TooMany) Reset()

func (*CLError_InvalidDeps_TooMany) String

func (x *CLError_InvalidDeps_TooMany) String() string

type CLError_OwnerLacksEmail

type CLError_OwnerLacksEmail struct {
	OwnerLacksEmail bool `protobuf:"varint,1,opt,name=owner_lacks_email,json=ownerLacksEmail,proto3,oneof"`
}

type CLError_ReusedTrigger

type CLError_ReusedTrigger struct {

	// ID of the finalized Run.
	Run string `protobuf:"bytes,1,opt,name=run,proto3" json:"run,omitempty"`
	// contains filtered or unexported fields
}

ReusedTrigger means a CL trigger (e.g. CQ+1 vote) has already resulted in a CQ Run which was finalized.

Two known cases when this happens with a Gerrit CL:

  1. A user uploads a CL on ref A, then votes CQ+1. Before the Dry Run completes, the CL is moved to ref B, while preserving the CQ+1 vote. The old Run is finalized, but the new Run has the exact same trigger, which in CQDaemon-compatible mode means the new Run's ID is exactly the same as the old one, so CV can't create a new Run. TODO(crbug/1223349): after CQDaemon is deleted, the Run ID generation scheme can take into account the ref of a CL, and this use case can be allowed.
  2. The same as above but instead of moving CL between refs, abandon and restore the CL.

func (*CLError_ReusedTrigger) Descriptor deprecated

func (*CLError_ReusedTrigger) Descriptor() ([]byte, []int)

Deprecated: Use CLError_ReusedTrigger.ProtoReflect.Descriptor instead.

func (*CLError_ReusedTrigger) GetRun

func (x *CLError_ReusedTrigger) GetRun() string

func (*CLError_ReusedTrigger) ProtoMessage

func (*CLError_ReusedTrigger) ProtoMessage()

func (*CLError_ReusedTrigger) ProtoReflect

func (x *CLError_ReusedTrigger) ProtoReflect() protoreflect.Message

func (*CLError_ReusedTrigger) Reset

func (x *CLError_ReusedTrigger) Reset()

func (*CLError_ReusedTrigger) String

func (x *CLError_ReusedTrigger) String() string

type CLError_ReusedTrigger_

type CLError_ReusedTrigger_ struct {
	ReusedTrigger *CLError_ReusedTrigger `protobuf:"bytes,7,opt,name=reused_trigger,json=reusedTrigger,proto3,oneof"`
}

type CLError_SelfCqDepend

type CLError_SelfCqDepend struct {
	SelfCqDepend bool `protobuf:"varint,5,opt,name=self_cq_depend,json=selfCqDepend,proto3,oneof"`
}

type CLError_UnsupportedMode

type CLError_UnsupportedMode struct {
	UnsupportedMode string `protobuf:"bytes,4,opt,name=unsupported_mode,json=unsupportedMode,proto3,oneof"`
}

type CLError_WatchedByManyConfigGroups

type CLError_WatchedByManyConfigGroups struct {

	// Config group names without LUCI project prefix.
	ConfigGroups []string `protobuf:"bytes,1,rep,name=config_groups,json=configGroups,proto3" json:"config_groups,omitempty"`
	// contains filtered or unexported fields
}

func (*CLError_WatchedByManyConfigGroups) Descriptor deprecated

func (*CLError_WatchedByManyConfigGroups) Descriptor() ([]byte, []int)

Deprecated: Use CLError_WatchedByManyConfigGroups.ProtoReflect.Descriptor instead.

func (*CLError_WatchedByManyConfigGroups) GetConfigGroups

func (x *CLError_WatchedByManyConfigGroups) GetConfigGroups() []string

func (*CLError_WatchedByManyConfigGroups) ProtoMessage

func (*CLError_WatchedByManyConfigGroups) ProtoMessage()

func (*CLError_WatchedByManyConfigGroups) ProtoReflect

func (*CLError_WatchedByManyConfigGroups) Reset

func (*CLError_WatchedByManyConfigGroups) String

type CLError_WatchedByManyConfigGroups_

type CLError_WatchedByManyConfigGroups_ struct {
	WatchedByManyConfigGroups *CLError_WatchedByManyConfigGroups `protobuf:"bytes,2,opt,name=watched_by_many_config_groups,json=watchedByManyConfigGroups,proto3,oneof"`
}

type CLMutation

type CLMutation struct {
	// CL can be modified except the following fields:
	//  * ID
	//  * ExternalID
	//  * EVersion
	//  * UpdateTime
	CL *CL
	// contains filtered or unexported fields
}

CLMutation encapsulates one CL mutation.

func (*CLMutation) Finalize

func (clm *CLMutation) Finalize(ctx context.Context) (*CL, error)

Finalize finalizes CL mutation.

Must be called at most once. Must be called in the same Datastore transaction as Begin() which began the CL mutation.

type CLUpdatedEvent

type CLUpdatedEvent struct {
	Clid     int64 `protobuf:"varint,1,opt,name=clid,proto3" json:"clid,omitempty"`
	Eversion int64 `protobuf:"varint,2,opt,name=eversion,proto3" json:"eversion,omitempty"`
	// contains filtered or unexported fields
}

CLUpdatedEvent is just a CL ID pinned to its latest known EVersion.

func (*CLUpdatedEvent) Descriptor deprecated

func (*CLUpdatedEvent) Descriptor() ([]byte, []int)

Deprecated: Use CLUpdatedEvent.ProtoReflect.Descriptor instead.

func (*CLUpdatedEvent) GetClid

func (x *CLUpdatedEvent) GetClid() int64

func (*CLUpdatedEvent) GetEversion

func (x *CLUpdatedEvent) GetEversion() int64

func (*CLUpdatedEvent) ProtoMessage

func (*CLUpdatedEvent) ProtoMessage()

func (*CLUpdatedEvent) ProtoReflect

func (x *CLUpdatedEvent) ProtoReflect() protoreflect.Message

func (*CLUpdatedEvent) Reset

func (x *CLUpdatedEvent) Reset()

func (*CLUpdatedEvent) String

func (x *CLUpdatedEvent) String() string

type CLUpdatedEvents

type CLUpdatedEvents struct {
	Events []*CLUpdatedEvent `protobuf:"bytes,1,rep,name=events,proto3" json:"events,omitempty"`
	// contains filtered or unexported fields
}

CLUpdatedEvents is a batch of CLUpdatedEvents.

func (*CLUpdatedEvents) Descriptor deprecated

func (*CLUpdatedEvents) Descriptor() ([]byte, []int)

Deprecated: Use CLUpdatedEvents.ProtoReflect.Descriptor instead.

func (*CLUpdatedEvents) GetEvents

func (x *CLUpdatedEvents) GetEvents() []*CLUpdatedEvent

func (*CLUpdatedEvents) ProtoMessage

func (*CLUpdatedEvents) ProtoMessage()

func (*CLUpdatedEvents) ProtoReflect

func (x *CLUpdatedEvents) ProtoReflect() protoreflect.Message

func (*CLUpdatedEvents) Reset

func (x *CLUpdatedEvents) Reset()

func (*CLUpdatedEvents) String

func (x *CLUpdatedEvents) String() string

type Dep

type Dep struct {

	// CLID is internal CV ID of a CL which is the dependency.
	Clid int64   `protobuf:"varint,1,opt,name=clid,proto3" json:"clid,omitempty"`
	Kind DepKind `protobuf:"varint,2,opt,name=kind,proto3,enum=cv.internal.changelist.DepKind" json:"kind,omitempty"`
	// contains filtered or unexported fields
}

func (*Dep) Descriptor deprecated

func (*Dep) Descriptor() ([]byte, []int)

Deprecated: Use Dep.ProtoReflect.Descriptor instead.

func (*Dep) GetClid

func (x *Dep) GetClid() int64

func (*Dep) GetKind

func (x *Dep) GetKind() DepKind

func (*Dep) ProtoMessage

func (*Dep) ProtoMessage()

func (*Dep) ProtoReflect

func (x *Dep) ProtoReflect() protoreflect.Message

func (*Dep) Reset

func (x *Dep) Reset()

func (*Dep) String

func (x *Dep) String() string

type DepKind

type DepKind int32
const (
	DepKind_DEP_KIND_UNSPECIFIED DepKind = 0
	// Dep MUST be patched in / submitted before the dependent CL.
	DepKind_HARD DepKind = 1
	// Dep SHOULD be patched in / submitted before the dependent CL,
	// but doesn't have to be.
	DepKind_SOFT DepKind = 2
)

func (DepKind) Descriptor

func (DepKind) Descriptor() protoreflect.EnumDescriptor

func (DepKind) Enum

func (x DepKind) Enum() *DepKind

func (DepKind) EnumDescriptor deprecated

func (DepKind) EnumDescriptor() ([]byte, []int)

Deprecated: Use DepKind.Descriptor instead.

func (DepKind) Number

func (x DepKind) Number() protoreflect.EnumNumber

func (DepKind) String

func (x DepKind) String() string

func (DepKind) Type

func (DepKind) Type() protoreflect.EnumType

type ExternalID

type ExternalID string

ExternalID is a unique CL ID deterministically constructed based on CL data.

Currently, only Gerrit is supported.

func GobID

func GobID(host string, change int64) (ExternalID, error)

GobID makes an ExternalID for a Gerrit CL.

Host is typically "something-review.googlesource.com". Change is a number, e.g. 2515619 for https://chromium-review.googlesource.com/c/infra/luci/luci-go/+/2515619

func MustGobID

func MustGobID(host string, change int64) ExternalID

MustGobID is like GobID but panics on error.

func (ExternalID) Get

func (eid ExternalID) Get(ctx context.Context) (*CL, error)

Get reads a CL from Datastore.

Returns datastore.ErrNoSuchEntity if it doesn't exist.

func (ExternalID) GetOrInsert

func (eid ExternalID) GetOrInsert(ctx context.Context, populate func(cl *CL)) (*CL, error)

GetOrInsert reads a CL from Datastore, creating a new one if it doesn't exist yet.

populate is called within a transaction to populate fields of a new entity. It should be a fast function.

Warning:

  • populate may be called several times since transaction can be retried.
  • cl.ExternalID and cl.ID must not be changed by populate.

func (ExternalID) MustURL

func (e ExternalID) MustURL() string

MustURL is like `URL()` but panic on err.

func (ExternalID) ParseGobID

func (e ExternalID) ParseGobID() (host string, change int64, err error)

ParseGobID returns Gerrit host and change if this is a GobID.

func (ExternalID) URL

func (e ExternalID) URL() (string, error)

URL returns URL of the CL.

type Gerrit

type Gerrit struct {

	// Gerrit host.
	Host string `protobuf:"bytes,5,opt,name=host,proto3" json:"host,omitempty"`
	// Info contains subset of ChangeInfo listed below.
	//
	// NOTE: keep this list in sync with RemoveUnusedGerritInfo() function.
	//  * number
	//  * owner
	//      * id
	//      * email (may be not set)
	//  * project
	//  * ref
	//  * status
	//  * current_revision
	//  * revisions
	//      * kind
	//      * number
	//      * ref
	//      * created
	//      * commit (for current_revision only)
	//          * message (current CL description)
	//  * labels
	//      * optional
	//      * all (only if vote != 0)
	//          * user
	//              * id
	//              * email (may be not set)
	//      * value
	//  * messages
	//      * id
	//      * date
	//      * message
	//      * author
	//        * id
	//      * realauthor
	//        * id
	//  * updated
	//  * created
	Info *gerrit.ChangeInfo `protobuf:"bytes,1,opt,name=info,proto3" json:"info,omitempty"`
	// Files are filenames touched in the current revision.
	//
	// It's derived from gerrit.ListFilesResponse, see
	// https://gerrit-review.googlesource.com/Documentation/rest-api-changes.html#list-files.
	Files []string `protobuf:"bytes,2,rep,name=files,proto3" json:"files,omitempty"`
	// Git dependencies of the current revision.
	GitDeps []*GerritGitDep `protobuf:"bytes,3,rep,name=git_deps,json=gitDeps,proto3" json:"git_deps,omitempty"`
	// Free-form dependencies. Currently, sourced from CQ-Depend footers.
	// In the future, this may be derived from Gerrit hashtags, topics, or other
	// mechanisms.
	SoftDeps []*GerritSoftDep `protobuf:"bytes,4,rep,name=soft_deps,json=softDeps,proto3" json:"soft_deps,omitempty"`
	// contains filtered or unexported fields
}

func (*Gerrit) Descriptor deprecated

func (*Gerrit) Descriptor() ([]byte, []int)

Deprecated: Use Gerrit.ProtoReflect.Descriptor instead.

func (*Gerrit) GetFiles

func (x *Gerrit) GetFiles() []string

func (*Gerrit) GetGitDeps

func (x *Gerrit) GetGitDeps() []*GerritGitDep

func (*Gerrit) GetHost

func (x *Gerrit) GetHost() string

func (*Gerrit) GetInfo

func (x *Gerrit) GetInfo() *gerrit.ChangeInfo

func (*Gerrit) GetSoftDeps

func (x *Gerrit) GetSoftDeps() []*GerritSoftDep

func (*Gerrit) ProtoMessage

func (*Gerrit) ProtoMessage()

func (*Gerrit) ProtoReflect

func (x *Gerrit) ProtoReflect() protoreflect.Message

func (*Gerrit) Reset

func (x *Gerrit) Reset()

func (*Gerrit) String

func (x *Gerrit) String() string

type GerritGitDep

type GerritGitDep struct {

	// Gerrit Change number.
	Change int64 `protobuf:"varint,1,opt,name=change,proto3" json:"change,omitempty"`
	// Immediate is set iff this dep is an immediate parent of the Gerrit CL.
	//
	// Immediate dep must be submitted before its child.
	// Non-immediate CLs don't necessarily have to be submitted before:
	//   for example, for a chain <base> <- A1 <- B1 <- C1 <- D1
	//   D1's deps are [A,B,C] but only C is immediate, and 1 stands for patchset.
	//   Developer may then swap B,C without re-uploading D (say, to avoid
	//   patchset churn), resulting in a new logical chain:
	//      <base> <- A1 <- C2 <- B2
	//                   \
	//                    <- B1 <- C1 <- D1
	//
	//   In this case, Gerrit's related changes for D1 will still return A1,B1,C1,
	//   which CV interprets as C must be landed before D, while B and A should
	//   be landed before D.
	//
	// TODO(tandrii): this is replicating existing CQDaemon logic. I think
	// it'd be reasonable to treat all (A,B,C) as MUST BE submitted before D.
	Immediate bool `protobuf:"varint,2,opt,name=immediate,proto3" json:"immediate,omitempty"`
	// contains filtered or unexported fields
}

GerritGitDep is a dependency discovered via Git child->parent chain for one Gerrit CL.

func (*GerritGitDep) Descriptor deprecated

func (*GerritGitDep) Descriptor() ([]byte, []int)

Deprecated: Use GerritGitDep.ProtoReflect.Descriptor instead.

func (*GerritGitDep) GetChange

func (x *GerritGitDep) GetChange() int64

func (*GerritGitDep) GetImmediate

func (x *GerritGitDep) GetImmediate() bool

func (*GerritGitDep) ProtoMessage

func (*GerritGitDep) ProtoMessage()

func (*GerritGitDep) ProtoReflect

func (x *GerritGitDep) ProtoReflect() protoreflect.Message

func (*GerritGitDep) Reset

func (x *GerritGitDep) Reset()

func (*GerritGitDep) String

func (x *GerritGitDep) String() string

type GerritSoftDep

type GerritSoftDep struct {

	// Gerrit host.
	Host string `protobuf:"bytes,1,opt,name=host,proto3" json:"host,omitempty"`
	// Gerrit change number.
	Change int64 `protobuf:"varint,2,opt,name=change,proto3" json:"change,omitempty"`
	// contains filtered or unexported fields
}

func (*GerritSoftDep) Descriptor deprecated

func (*GerritSoftDep) Descriptor() ([]byte, []int)

Deprecated: Use GerritSoftDep.ProtoReflect.Descriptor instead.

func (*GerritSoftDep) GetChange

func (x *GerritSoftDep) GetChange() int64

func (*GerritSoftDep) GetHost

func (x *GerritSoftDep) GetHost() string

func (*GerritSoftDep) ProtoMessage

func (*GerritSoftDep) ProtoMessage()

func (*GerritSoftDep) ProtoReflect

func (x *GerritSoftDep) ProtoReflect() protoreflect.Message

func (*GerritSoftDep) Reset

func (x *GerritSoftDep) Reset()

func (*GerritSoftDep) String

func (x *GerritSoftDep) String() string

type MutateCallback

type MutateCallback func(cl *CL) error

MutateCallback is called by Mutator to mutate the CL inside a transaction.

The function should be idempotent.

If no error is returned, Mutator proceeds saving the CL.

If special ErrStopMutation is returned, Mutator aborts the tranasction and returns existing CL read from Datastore and no error. In the special case of Upsert(), the returned CL may actually be nil if CL didn't exist.

If any error is returned other than ErrStopMutation, Mutator aborts the transaction and returns nil CL and the exact same error.

type Mutator

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

Mutator modifies CLs and guarantees at least once notification of relevant CV components.

All CL entities in production code must be modified via the Mutator.

Mutator notifies 2 CV components: Run and Project managers. In the future, it'll also notify Tryjob Manager.

Run Manager is notified for each IncompleteRuns in the **new** CL version.

Project manager is notified in following cases:

  1. On the project in the context of which the CL is being modified.
  2. On the project which owns the Snapshot of the *prior* CL version (if it had any Snapshot).

When the number of notifications is large, Mutator may chose to transactionally enqueue a TQ task, which will send notifications in turn.

func NewMutator

func NewMutator(pm pmNotifier, rm rmNotifier) *Mutator

func (*Mutator) Begin

func (m *Mutator) Begin(ctx context.Context, project string, id common.CLID) (*CLMutation, error)

Begin starts mutation of one CL inside an existing transaction in the context of the given LUCI project.

func (*Mutator) BeginBatch

func (m *Mutator) BeginBatch(ctx context.Context, project string, ids common.CLIDs) ([]*CLMutation, error)

func (*Mutator) FinalizeBatch

func (m *Mutator) FinalizeBatch(ctx context.Context, muts []*CLMutation) ([]*CL, error)

func (*Mutator) Update

func (m *Mutator) Update(ctx context.Context, project string, id common.CLID, clbk MutateCallback) (*CL, error)

Update mutates one CL via a dedicated transaction in the context of the given LUCI project.

If the callback returns ErrStopMutation, then Update returns the read CL entity and nil error.

func (*Mutator) Upsert

func (m *Mutator) Upsert(ctx context.Context, project string, eid ExternalID, clbk MutateCallback) (*CL, error)

Upsert creates new or updates existing CL via a dedicated transaction in the context of the given LUCI project.

Prefer to use Update if CL ID is known.

If CL didn't exist before, the callback is provided a CL with temporarily reserved ID. Until Upsert returns with success, this ID is not final, but it's fine to use it in other entities saved within the same transaction.

If CL didn't exist before and the callback returns ErrStopMutation, then Upsert returns (nil, nil).

type Notify

type Notify func(ctx context.Context, cl *CL) error

Notify is called with the updated CL in a transaction context after CL is successfully created/updated.

type Snapshot

type Snapshot struct {

	// The timestamp from external system.
	// Used to determine if re-querying external system is needed.
	ExternalUpdateTime *timestamppb.Timestamp `protobuf:"bytes,1,opt,name=external_update_time,json=externalUpdateTime,proto3" json:"external_update_time,omitempty"`
	// LUCI project in the context of which this snapshot was saved.
	//
	// Since a CL isn't a resource of CV, CV can't infer whether specific LUCI
	// project has access to a CL w/o re-querying Gerrit and effectively
	// recomputing the snapshot.
	LuciProject string `protobuf:"bytes,2,opt,name=luci_project,json=luciProject,proto3" json:"luci_project,omitempty"`
	// Resolved dependencies of a CL.
	Deps []*Dep `protobuf:"bytes,3,rep,name=deps,proto3" json:"deps,omitempty"`
	// Patchset is incremental number of the latest patchset (aka revision).
	Patchset int32 `protobuf:"varint,4,opt,name=patchset,proto3" json:"patchset,omitempty"`
	// MinEquivalentPatchset is the smallest and hence the earliest patchset
	// which is code-wise equivalent to the latest one.
	//
	// See gerrit.EquivalentPatchsetRange function for details.
	//
	// CV tracks this to determine which prior tryjobs can be re-used and which
	// can be canceled.
	MinEquivalentPatchset int32 `` /* 127-byte string literal not displayed */
	// If set, indicates problems while ingesting CL into CV, which ought to be
	// communicated back to user.
	Errors []*CLError `protobuf:"bytes,6,rep,name=errors,proto3" json:"errors,omitempty"`
	// Outdated if set means Snapshot must be considered likely outdated due to
	// recent CV mutations.
	//
	// In particular, Project Manager does not act on the CLs with .Outdated set.
	Outdated *Snapshot_Outdated `protobuf:"bytes,7,opt,name=outdated,proto3" json:"outdated,omitempty"`
	// CL-kind specific data.
	//
	// Types that are assignable to Kind:
	//	*Snapshot_Gerrit
	Kind isSnapshot_Kind `protobuf_oneof:"kind"`
	// contains filtered or unexported fields
}

Snapshot stores a snapshot of CL info as seen by CV at a certain time.

When stored in CL entity, represents latest known Gerrit data. When stored in RunCL entity, represents data pertaining to a fixed patchset.

func (*Snapshot) Descriptor deprecated

func (*Snapshot) Descriptor() ([]byte, []int)

Deprecated: Use Snapshot.ProtoReflect.Descriptor instead.

func (*Snapshot) GetDeps

func (x *Snapshot) GetDeps() []*Dep

func (*Snapshot) GetErrors

func (x *Snapshot) GetErrors() []*CLError

func (*Snapshot) GetExternalUpdateTime

func (x *Snapshot) GetExternalUpdateTime() *timestamppb.Timestamp

func (*Snapshot) GetGerrit

func (x *Snapshot) GetGerrit() *Gerrit

func (*Snapshot) GetKind

func (m *Snapshot) GetKind() isSnapshot_Kind

func (*Snapshot) GetLuciProject

func (x *Snapshot) GetLuciProject() string

func (*Snapshot) GetMinEquivalentPatchset

func (x *Snapshot) GetMinEquivalentPatchset() int32

func (*Snapshot) GetOutdated

func (x *Snapshot) GetOutdated() *Snapshot_Outdated

func (*Snapshot) GetPatchset

func (x *Snapshot) GetPatchset() int32

func (*Snapshot) OwnerIdentity

func (s *Snapshot) OwnerIdentity() (identity.Identity, error)

OwnerIdentity is the identity of a user owning this CL.

Snapshot must not be nil.

func (*Snapshot) PanicIfNotValid

func (s *Snapshot) PanicIfNotValid()

PanicIfNotValid checks that Snapshot stored has required fields set.

func (*Snapshot) ProtoMessage

func (*Snapshot) ProtoMessage()

func (*Snapshot) ProtoReflect

func (x *Snapshot) ProtoReflect() protoreflect.Message

func (*Snapshot) Reset

func (x *Snapshot) Reset()

func (*Snapshot) String

func (x *Snapshot) String() string

type Snapshot_Gerrit

type Snapshot_Gerrit struct {
	Gerrit *Gerrit `protobuf:"bytes,11,opt,name=gerrit,proto3,oneof"`
}

type Snapshot_Outdated

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

Outdated establishes conditions for refreshing Snapshot after CV mutations.

func (*Snapshot_Outdated) Descriptor deprecated

func (*Snapshot_Outdated) Descriptor() ([]byte, []int)

Deprecated: Use Snapshot_Outdated.ProtoReflect.Descriptor instead.

func (*Snapshot_Outdated) ProtoMessage

func (*Snapshot_Outdated) ProtoMessage()

func (*Snapshot_Outdated) ProtoReflect

func (x *Snapshot_Outdated) ProtoReflect() protoreflect.Message

func (*Snapshot_Outdated) Reset

func (x *Snapshot_Outdated) Reset()

func (*Snapshot_Outdated) String

func (x *Snapshot_Outdated) String() string

Jump to

Keyboard shortcuts

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