/
service_hostname.go
247 lines (208 loc) · 6.2 KB
/
service_hostname.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
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
package server
import (
"context"
"fmt"
"strings"
empty "github.com/golang/protobuf/ptypes/empty"
"github.com/hashicorp/go-hclog"
"github.com/hashicorp/horizon/pkg/dbx"
hznpb "github.com/hashicorp/horizon/pkg/pb"
petname "github.com/hashicorp/waypoint-hzn/internal/pkg/golang-petname"
"github.com/jinzhu/gorm"
"github.com/mitchellh/mapstructure"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/status"
"github.com/hashicorp/waypoint-hzn/pkg/models"
"github.com/hashicorp/waypoint-hzn/pkg/pb"
)
var (
// testLabelLinkFailed if set will cause the AddLabelLink to always fail.
// This is used for tests.
testLabelLinkFailed bool
)
func (s *service) RegisterHostname(
ctx context.Context,
req *pb.RegisterHostnameRequest,
) (*pb.RegisterHostnameResponse, error) {
L := hclog.FromContext(ctx)
// Auth required.
token, err := s.checkAuth(ctx)
if err != nil {
return nil, err
}
// Validate
if err := req.Validate(); err != nil {
return nil, err
}
// Parse our labels
var labels hznpb.LabelSet
if err := mapstructure.Decode(req.Labels, &labels); err != nil {
return nil, err
}
// Get our account registration
var reg models.Registration
if err = dbx.Check(
s.DB.Where("account_id = ?", token.Account().AccountId.Bytes()).First(®),
); err != nil {
return nil, status.Errorf(codes.PermissionDenied,
"unregistered account")
}
// Determine the full hostname
var hostname, fqdn string
trying:
for {
switch v := req.Hostname.(type) {
case *pb.RegisterHostnameRequest_Generate:
hostname = petname.Generate(3, "-")
if strings.Contains(hostname, "--") {
// extremely odd, but go ahead and just retry
continue trying
}
case *pb.RegisterHostnameRequest_Exact:
hostname = v.Exact
if strings.Contains(hostname, "--") {
return nil, fmt.Errorf("hostname must not contain a double hyphen")
}
}
var host models.Hostname
host.RegistrationId = reg.Id
host.Hostname = hostname
host.Labels = labels.AsStringArray()
if err := dbx.Check(s.DB.Create(&host)); err != nil {
// For now, assume the failure is because of failing the unique
// constraint. If we autogenerated the name, retry, otherwise return
// an error.
if _, ok := req.Hostname.(*pb.RegisterHostnameRequest_Generate); ok {
continue
}
L.Error("error creating hostname", "error", err)
return nil, fmt.Errorf("requested hostname is not available")
}
// Add the domain
fqdn = hostname + "." + s.Domain
break
}
L.Debug("adding label link", "hostname", fqdn, "target", req.Labels)
_, err = s.HznControl.AddLabelLink(ctx, &hznpb.AddLabelLinkRequest{
Labels: hznpb.MakeLabels(":hostname", fqdn),
Account: token.Account(),
Target: &labels,
})
if err == nil && testLabelLinkFailed {
err = fmt.Errorf("forced err by setting testLabelLinkFailed")
}
if err != nil {
// We need to delete our record we created earlier since the
// creation failed.
if derr := dbx.Check(s.DB.Delete(
models.Hostname{},
"registration_id = ? and hostname = ?", reg.Id, hostname),
); derr != nil {
// If we fail, the best we can do is make a loud log message
// since we're already returning an error to the user anyways.
L.Error("error deleting hostname after label link failed, dangling hosname",
"hostname", fqdn,
"err", derr)
}
return nil, err
}
L.Info("added label link", "hostname", fqdn, "target", req.Labels)
return &pb.RegisterHostnameResponse{
Hostname: hostname,
Fqdn: fqdn,
}, nil
}
func (s *service) ListHostnames(
ctx context.Context,
req *pb.ListHostnamesRequest,
) (*pb.ListHostnamesResponse, error) {
L := hclog.FromContext(ctx)
// Auth required.
token, err := s.checkAuth(ctx)
if err != nil {
return nil, err
}
// Validate
if err := req.Validate(); err != nil {
return nil, err
}
// Get our account registration
var reg models.Registration
if err = dbx.Check(
s.DB.Where("account_id = ?", token.Account().AccountId.Bytes()).First(®),
); err != nil {
return nil, status.Errorf(codes.PermissionDenied,
"unregistered account")
}
var hostnames []*models.Hostname
err = dbx.Check(s.DB.Find(&hostnames, "registration_id = ?", reg.Id))
if err != nil {
if err == gorm.ErrRecordNotFound {
return nil, status.Errorf(codes.NotFound, "unregistered account")
}
L.Error("error looking up hostnames for account", "registration-id", reg.Id)
return nil, status.Errorf(codes.Internal, "error querying hostnames")
}
var resp pb.ListHostnamesResponse
for _, h := range hostnames {
var hznlabels hznpb.LabelSet
if err := hznlabels.Scan(h.Labels); err != nil {
return nil, status.Errorf(codes.Internal, "error querying hostnames")
}
// Parse our labels
var labels pb.LabelSet
if err := mapstructure.Decode(hznlabels, &labels); err != nil {
return nil, err
}
resp.Hostnames = append(resp.Hostnames, &pb.ListHostnamesResponse_Hostname{
Hostname: h.Hostname,
Fqdn: h.Hostname + "." + s.Domain,
Labels: &labels,
})
}
return &resp, nil
}
func (s *service) DeleteHostname(
ctx context.Context,
req *pb.DeleteHostnameRequest,
) (*empty.Empty, error) {
L := hclog.FromContext(ctx)
// Auth required.
token, err := s.checkAuth(ctx)
if err != nil {
return nil, err
}
// Validate
if err := req.Validate(); err != nil {
return nil, err
}
// Get our account registration
var reg models.Registration
if err = dbx.Check(
s.DB.Where("account_id = ?", token.Account().AccountId.Bytes()).First(®),
); err != nil {
return nil, status.Errorf(codes.PermissionDenied,
"unregistered account")
}
// Delete from our DB
err = dbx.Check(s.DB.Delete(
models.Hostname{},
"registration_id = ? and hostname = ?", reg.Id, req.Hostname),
)
if err != nil {
if err == gorm.ErrRecordNotFound {
return &empty.Empty{}, nil
}
L.Error("error looking up hostnames for account", "registration-id", reg.Id)
return nil, status.Errorf(codes.Internal, "error deleting hostname")
}
_, err = s.HznControl.RemoveLabelLink(ctx, &hznpb.RemoveLabelLinkRequest{
Labels: hznpb.MakeLabels(":hostname", req.Hostname),
Account: token.Account(),
})
if err != nil {
L.Error("error removing label link", "error", err)
return nil, status.Errorf(codes.Internal, "error deleting hostname")
}
return &empty.Empty{}, nil
}