blob: 07f3a15d38ee79634c687bc02eae673ad042c5f9 [file] [log] [blame]
// Copyright 2018 The LUCI Authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package model
import (
"context"
"time"
"google.golang.org/grpc/codes"
"go.chromium.org/gae/service/datastore"
"go.chromium.org/luci/common/clock"
"go.chromium.org/luci/common/errors"
"go.chromium.org/luci/common/proto/google"
"go.chromium.org/luci/common/retry/transient"
"go.chromium.org/luci/grpc/grpcutil"
"go.chromium.org/luci/server/auth"
api "go.chromium.org/luci/cipd/api/cipd/v1"
"go.chromium.org/luci/cipd/common"
)
// Ref represents a named pointer to some package instance.
//
// ID is a ref name, the parent entity is the corresponding Package.
//
// Compatible with the python version of the backend.
type Ref struct {
_kind string `gae:"$kind,PackageRef"`
_extra datastore.PropertyMap `gae:"-,extra"`
Name string `gae:"$id"` // e.g. "latest"
Package *datastore.Key `gae:"$parent"` // see PackageKey()
InstanceID string `gae:"instance_id"` // see common.ObjectRefToInstanceID()
ModifiedBy string `gae:"modified_by"` // who moved it the last time
ModifiedTs time.Time `gae:"modified_ts"` // when it was moved the last time
}
// Proto returns cipd.Ref proto with information from this entity.
func (e *Ref) Proto() *api.Ref {
return &api.Ref{
Name: e.Name,
Package: e.Package.StringID(),
Instance: common.InstanceIDToObjectRef(e.InstanceID),
ModifiedBy: e.ModifiedBy,
ModifiedTs: google.NewTimestamp(e.ModifiedTs),
}
}
// SetRef moves or creates a ref.
//
// Assumes inputs are already validated. Launches a transaction inside (and thus
// can't be a part of a transaction itself). Updates 'inst' in-place with the
// most recent instance state.
//
// Returns gRPC-tagged errors:
// NotFound if there's no such instance or package.
// FailedPrecondition if some processors are still running.
// Aborted if some processors have failed.
func SetRef(c context.Context, ref string, inst *Instance) error {
return Txn(c, "SetRef", func(c context.Context) error {
if err := CheckInstanceReady(c, inst); err != nil {
return err
}
events := Events{}
// Do not touch the ref's ModifiedBy/ModifiedTs if it already points to the
// requested instance. Need to fetch the ref to check this.
r := Ref{Name: ref, Package: inst.Package}
switch err := datastore.Get(c, &r); {
case err == datastore.ErrNoSuchEntity:
break // need to create the new ref
case err != nil:
return errors.Annotate(err, "failed to fetch the ref").Tag(transient.Tag).Err()
case r.InstanceID == inst.InstanceID:
return nil // already set to the requested instance
default:
events.Emit(&api.Event{
Kind: api.EventKind_INSTANCE_REF_UNSET,
Package: inst.Package.StringID(),
Instance: r.InstanceID,
Ref: ref,
})
}
events.Emit(&api.Event{
Kind: api.EventKind_INSTANCE_REF_SET,
Package: inst.Package.StringID(),
Instance: inst.InstanceID,
Ref: ref,
})
err := datastore.Put(c, &Ref{
Name: ref,
Package: inst.Package,
InstanceID: inst.InstanceID,
ModifiedBy: string(auth.CurrentIdentity(c)),
ModifiedTs: clock.Now(c).UTC(),
})
if err != nil {
return transient.Tag.Apply(err)
}
return events.Flush(c)
})
}
// GetRef fetches the given ref.
//
// Returns gRPC-tagged NotFound error if there's no such ref.
func GetRef(c context.Context, pkg, ref string) (*Ref, error) {
r := &Ref{Name: ref, Package: PackageKey(c, pkg)}
switch err := datastore.Get(c, r); {
case err == datastore.ErrNoSuchEntity:
return nil, errors.Reason("no such ref").Tag(grpcutil.NotFoundTag).Err()
case err != nil:
return nil, errors.Annotate(err, "failed to fetch the ref").Tag(transient.Tag).Err()
}
return r, nil
}
// DeleteRef removes the ref if it exists.
//
// Does nothing if there's no such ref or package.
func DeleteRef(c context.Context, pkg, ref string) error {
return Txn(c, "DeleteRef", func(c context.Context) error {
r, err := GetRef(c, pkg, ref)
switch {
case grpcutil.Code(err) == codes.NotFound:
return nil
case err != nil:
return err // transient
}
err = datastore.Delete(c, &Ref{
Name: ref,
Package: PackageKey(c, pkg),
})
if err != nil {
return transient.Tag.Apply(err)
}
return EmitEvent(c, &api.Event{
Kind: api.EventKind_INSTANCE_REF_UNSET,
Package: pkg,
Instance: r.InstanceID,
Ref: ref,
})
})
}
// ListPackageRefs returns all refs in a package, most recently modified first.
//
// Returns an empty list if there's no such package at all.
func ListPackageRefs(c context.Context, pkg string) (out []*Ref, err error) {
q := datastore.NewQuery("PackageRef").
Ancestor(PackageKey(c, pkg)).
Order("-modified_ts")
if err := datastore.GetAll(c, q, &out); err != nil {
return nil, errors.Annotate(err, "datastore query failed").Tag(transient.Tag).Err()
}
return
}
// ListInstanceRefs returns all refs that point to a particular instance, most
// recently modified first.
//
// This is a subset of the output of ListPackageRefs for the corresponding
// package.
//
// Assumes 'inst' is a valid Instance, panics otherwise.
//
// Returns an empty list if there's no such instance at all.
func ListInstanceRefs(c context.Context, inst *Instance) (out []*Ref, err error) {
if inst.Package == nil {
panic("bad Instance")
}
q := datastore.NewQuery("PackageRef").
Ancestor(inst.Package).
Eq("instance_id", inst.InstanceID).
Order("-modified_ts")
if err := datastore.GetAll(c, q, &out); err != nil {
return nil, errors.Annotate(err, "datastore query failed").Tag(transient.Tag).Err()
}
return
}