forked from openshift/origin
-
Notifications
You must be signed in to change notification settings - Fork 0
/
rest.go
160 lines (144 loc) · 5.16 KB
/
rest.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
package buildlog
import (
"fmt"
"time"
"github.com/golang/glog"
kapi "k8s.io/kubernetes/pkg/api"
"k8s.io/kubernetes/pkg/api/errors"
"k8s.io/kubernetes/pkg/api/rest"
kclient "k8s.io/kubernetes/pkg/client"
"k8s.io/kubernetes/pkg/fields"
"k8s.io/kubernetes/pkg/labels"
genericrest "k8s.io/kubernetes/pkg/registry/generic/rest"
"k8s.io/kubernetes/pkg/registry/pod"
"k8s.io/kubernetes/pkg/runtime"
"k8s.io/kubernetes/pkg/watch"
"github.com/openshift/origin/pkg/build/api"
"github.com/openshift/origin/pkg/build/registry/build"
buildutil "github.com/openshift/origin/pkg/build/util"
)
// REST is an implementation of RESTStorage for the api server.
type REST struct {
BuildRegistry build.Registry
PodGetter pod.ResourceGetter
ConnectionInfo kclient.ConnectionInfoGetter
Timeout time.Duration
}
type podGetter struct {
podsNamespacer kclient.PodsNamespacer
}
func (g *podGetter) Get(ctx kapi.Context, name string) (runtime.Object, error) {
ns, ok := kapi.NamespaceFrom(ctx)
if !ok {
return nil, errors.NewBadRequest("namespace parameter required.")
}
return g.podsNamespacer.Pods(ns).Get(name)
}
const defaultTimeout time.Duration = 10 * time.Second
// NewREST creates a new REST for BuildLog
// Takes build registry and pod client to get necessary attributes to assemble
// URL to which the request shall be redirected in order to get build logs.
func NewREST(b build.Registry, pn kclient.PodsNamespacer, connectionInfo kclient.ConnectionInfoGetter) *REST {
return &REST{
BuildRegistry: b,
PodGetter: &podGetter{pn},
ConnectionInfo: connectionInfo,
Timeout: defaultTimeout,
}
}
var _ = rest.GetterWithOptions(&REST{})
// Get returns a streamer resource with the contents of the build log
func (r *REST) Get(ctx kapi.Context, name string, opts runtime.Object) (runtime.Object, error) {
buildLogOpts, ok := opts.(*api.BuildLogOptions)
if !ok {
return nil, errors.NewBadRequest("did not get an expected options.")
}
build, err := r.BuildRegistry.GetBuild(ctx, name)
if err != nil {
return nil, errors.NewNotFound("build", name)
}
switch build.Status.Phase {
// Build has not launched, wait til it runs
case api.BuildPhaseNew, api.BuildPhasePending:
if buildLogOpts.NoWait {
glog.V(4).Infof("Build %s/%s is in %s state. No logs to retrieve yet.", build.Namespace, name, build.Status.Phase)
// return empty content if not waiting for build
return &genericrest.LocationStreamer{}, nil
}
glog.V(4).Infof("Build %s/%s is in %s state, waiting for Build to start", build.Namespace, name, build.Status.Phase)
err := r.waitForBuild(ctx, build)
if err != nil {
return nil, err
}
// The build was cancelled
case api.BuildPhaseCancelled:
return nil, errors.NewBadRequest(fmt.Sprintf("build %s/%s was cancelled. %s", build.Namespace, build.Name, buildutil.NoBuildLogsMessage))
// An error occurred launching the build, return an error
case api.BuildPhaseError:
return nil, errors.NewBadRequest(fmt.Sprintf("build %s/%s is in an error state. %s", build.Namespace, build.Name, buildutil.NoBuildLogsMessage))
}
// The container should be the default build container, so setting it to blank
buildPodName := buildutil.GetBuildPodName(build)
logOpts := &kapi.PodLogOptions{
Follow: buildLogOpts.Follow,
}
location, transport, err := pod.LogLocation(r.PodGetter, r.ConnectionInfo, ctx, buildPodName, logOpts)
if err != nil {
if errors.IsNotFound(err) {
return nil, errors.NewNotFound("pod", buildPodName)
}
return nil, errors.NewBadRequest(err.Error())
}
return &genericrest.LocationStreamer{
Location: location,
Transport: transport,
ContentType: "text/plain",
Flush: buildLogOpts.Follow,
}, nil
}
func (r *REST) waitForBuild(ctx kapi.Context, build *api.Build) error {
fieldSelector := fields.Set{"metadata.name": build.Name}.AsSelector()
w, err := r.BuildRegistry.WatchBuilds(ctx, labels.Everything(), fieldSelector, build.ResourceVersion)
if err != nil {
return err
}
defer w.Stop()
done := make(chan struct{})
errchan := make(chan error)
go func(ch <-chan watch.Event) {
for event := range ch {
obj, ok := event.Object.(*api.Build)
if !ok {
errchan <- fmt.Errorf("event object is not a Build: %#v", event.Object)
break
}
switch obj.Status.Phase {
case api.BuildPhaseCancelled:
errchan <- fmt.Errorf("build %s/%s was cancelled. %s", build.Namespace, build.Name, buildutil.NoBuildLogsMessage)
break
case api.BuildPhaseError:
errchan <- fmt.Errorf("build %s/%s is in an error state. %s", build.Namespace, build.Name, buildutil.NoBuildLogsMessage)
break
case api.BuildPhaseRunning, api.BuildPhaseComplete, api.BuildPhaseFailed:
done <- struct{}{}
break
}
}
}(w.ResultChan())
select {
case err := <-errchan:
return err
case <-done:
return nil
case <-time.After(r.Timeout):
return errors.NewTimeoutError(fmt.Sprintf("timed out waiting for Build %s/%s", build.Namespace, build.Name), 1)
}
}
// NewGetOptions returns a new options object for build logs
func (r *REST) NewGetOptions() (runtime.Object, bool, string) {
return &api.BuildLogOptions{}, false, ""
}
// New creates an empty BuildLog resource
func (r *REST) New() runtime.Object {
return &api.BuildLog{}
}