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
package gateway
import (
"context"
"encoding/json"
"errors"
"github.com/johnandersen777/socialweb-computer-kcp/internal/revocation"
)
type ApplyRequest struct {
Cluster string `json:"cluster"`
Namespace string `json:"namespace,omitempty"`
Name string `json:"name,omitempty"`
FieldManager string `json:"fieldManager,omitempty"`
Force bool `json:"force,omitempty"`
DryRun bool `json:"dryRun,omitempty"`
Manifest json.RawMessage `json:"manifest"`
}
type ApplyResponse struct {
APIVersion string `json:"apiVersion"`
Kind string `json:"kind"`
Namespace string `json:"namespace,omitempty"`
Name string `json:"name"`
UID string `json:"uid,omitempty"`
ResourceVersion string `json:"resourceVersion,omitempty"`
Generation int64 `json:"generation,omitempty"`
DryRun bool `json:"dryRun,omitempty"`
Object json.RawMessage `json:"object"`
}
type JobRequest struct {
Cluster string `json:"cluster"`
Namespace string `json:"namespace"`
Name string `json:"name"`
TailLines *int64 `json:"tailLines,omitempty"`
}
type JobCondition struct {
Type string `json:"type"`
Status string `json:"status"`
Reason string `json:"reason,omitempty"`
Message string `json:"message,omitempty"`
}
type JobContainer struct {
Name string `json:"name"`
State string `json:"state"`
Reason string `json:"reason,omitempty"`
ExitCode *int64 `json:"exitCode,omitempty"`
Log string `json:"log,omitempty"`
LogError string `json:"logError,omitempty"`
}
type JobPod struct {
Name string `json:"name"`
Phase string `json:"phase"`
Node string `json:"node,omitempty"`
Containers []JobContainer `json:"containers"`
}
type JobStatus struct {
Namespace string `json:"namespace"`
Name string `json:"name"`
UID string `json:"uid"`
Phase string `json:"phase"`
Active int64 `json:"active"`
Succeeded int64 `json:"succeeded"`
Failed int64 `json:"failed"`
StartTime string `json:"startTime,omitempty"`
CompletionTime string `json:"completionTime,omitempty"`
Conditions []JobCondition `json:"conditions"`
Pods []JobPod `json:"pods"`
}
const (
JobPending = "Pending"
JobRunning = "Running"
JobSucceeded = "Succeeded"
JobFailed = "Failed"
DefaultJobTailLines = 100
MaxJobTailLines = 5000
)
type TenantAPI interface {
Apply(ctx context.Context, did, cluster string, req ApplyRequest) (*ApplyResponse, error)
Job(ctx context.Context, did, cluster string, req JobRequest) (*JobStatus, error)
}
var (
ErrClusterNotRunning = &XRPCError{
"ClusterNotRunning", "the control plane is stopped; call controlPlaneUp first", 409,
}
ErrNoTenantAPI = &XRPCError{
"ClusterUnavailable", "this gateway was built without a tenant API client", 503,
}
)
func (s *Server) KubectlApplyServerSide(ctx context.Context, caller Caller, req ApplyRequest) (*ApplyResponse, error) {
if err := s.reachable(ctx, caller, req.Cluster); err != nil {
return nil, err
}
if len(req.Manifest) == 0 {
return nil, ErrInvalidRequest
}
return s.Tenant.Apply(ctx, string(caller), req.Cluster, req)
}
func (s *Server) GetJob(ctx context.Context, caller Caller, req JobRequest) (*JobStatus, error) {
if req.Namespace == "" || req.Name == "" {
return nil, ErrInvalidRequest
}
if req.TailLines != nil && (*req.TailLines < 0 || *req.TailLines > MaxJobTailLines) {
return nil, ErrInvalidRequest
}
if err := s.reachable(ctx, caller, req.Cluster); err != nil {
return nil, err
}
return s.Tenant.Job(ctx, string(caller), req.Cluster, req)
}
func (s *Server) reachable(ctx context.Context, caller Caller, cluster string) error {
if s.Tenant == nil {
return ErrNoTenantAPI
}
if caller == "" {
return ErrUnauthorized
}
permit, err := revocation.Admit(ctx, s.Deny, string(caller))
if err != nil {
if x := errForDeny(err); x != nil {
return x
}
return err
}
if permit.Admitted() != string(caller) {
return errors.New("gateway: the admit permit is for another identity")
}
view, err := s.lookup(ctx, caller, NameRequest{Name: cluster})
if err != nil {
return err
}
switch {
case view.Phase == "Draining":
return ErrClusterDraining
case !view.Running:
return ErrClusterNotRunning
case view.Phase != "Ready":
return ErrClusterInitializing
}
return nil
}