-
Notifications
You must be signed in to change notification settings - Fork 0
/
ref.go
157 lines (142 loc) · 5.26 KB
/
ref.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
// 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"
"go.chromium.org/gae/service/datastore"
"github.com/TriggerMail/luci-go/auth/identity"
"github.com/TriggerMail/luci-go/common/clock"
"github.com/TriggerMail/luci-go/common/errors"
"github.com/TriggerMail/luci-go/common/proto/google"
"github.com/TriggerMail/luci-go/common/retry/transient"
"github.com/TriggerMail/luci-go/grpc/grpcutil"
api "github.com/TriggerMail/luci-go/cipd/api/cipd/v1"
"github.com/TriggerMail/luci-go/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, who identity.Identity) error {
return Txn(c, "SetRef", func(c context.Context) error {
if err := CheckInstanceReady(c, inst); err != nil {
return err
}
// 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
}
return transient.Tag.Apply(datastore.Put(c, &Ref{
Name: ref,
Package: inst.Package,
InstanceID: inst.InstanceID,
ModifiedBy: string(who),
ModifiedTs: clock.Now(c).UTC(),
}))
})
}
// 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 transient.Tag.Apply(datastore.Delete(c, &Ref{
Name: ref,
Package: PackageKey(c, pkg),
}))
}
// 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
}