Skip to content

Commit 42a560c

Browse files
authored
Merge pull request #2475 from keboola/petr-hosek-PAT-1091
Add restart disabled handling and wakeup overrides
2 parents 0205ac4 + 6a7f42e commit 42a560c

13 files changed

Lines changed: 191 additions & 23 deletions

File tree

.github/workflows/release-service-apps-proxy.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ on:
66
tags:
77
- 'production-apps-proxy-v**'
88
- 'dev-apps-proxy-v**'
9+
- 'canary-*-apps-proxy-v**'
910

1011
env:
1112
# DockerHub login

.github/workflows/release-service-stream.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ on:
66
tags:
77
- 'production-stream-v**'
88
- 'dev-stream-v**'
9+
- 'canary-*-stream-v**'
910

1011
env:
1112
# DockerHub login

.github/workflows/release-service-templates.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ on:
66
tags:
77
- 'production-templates-api-v**'
88
- 'dev-templates-api-v**'
9+
- 'canary-*-templates-api-v**'
910

1011
env:
1112
# DockerHub login

internal/pkg/service/appsproxy/dataapps/api/error.go

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,10 +5,15 @@ import (
55
"net/http"
66
)
77

8+
const (
9+
ContextCodeRestartDisabled = "apps.restartDisabled"
10+
)
11+
812
// Error represents the structure of Sandboxes API error.
913
type Error struct {
10-
Message string `json:"error"`
11-
ExceptionID string `json:"exceptionId"`
14+
Message string `json:"error"`
15+
ExceptionID string `json:"exceptionId"`
16+
Context map[string]any `json:"context,omitempty"`
1217
request *http.Request
1318
response *http.Response
1419
}
@@ -32,6 +37,14 @@ func (e *Error) ErrorExceptionID() string {
3237
return e.ExceptionID
3338
}
3439

40+
func (e *Error) HasRestartDisabled() bool {
41+
if e.Context == nil {
42+
return false
43+
}
44+
contextCode, ok := e.Context["code"].(string)
45+
return ok && contextCode == ContextCodeRestartDisabled
46+
}
47+
3548
// StatusCode returns HTTP status code.
3649
func (e *Error) StatusCode() int {
3750
return e.response.StatusCode

internal/pkg/service/appsproxy/dataapps/api/wakeup.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ func (a *API) WakeupApp(appID AppID) request.APIRequest[request.NoResult] {
2121
attribute.Int(attrSandboxesServiceStatusCode, response.StatusCode()),
2222
}
2323
span.SetAttributes(attrs...)
24-
return nil
24+
return err
2525
}).
2626
WithPatch("apps/{appId}").
2727
AndPathParam("appId", appID.String()).

internal/pkg/service/appsproxy/dataapps/wakeup/wakeup.go

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import (
1212
"github.com/keboola/keboola-as-code/internal/pkg/log"
1313
"github.com/keboola/keboola-as-code/internal/pkg/service/appsproxy/dataapps/api"
1414
"github.com/keboola/keboola-as-code/internal/pkg/service/appsproxy/syncmap"
15+
"github.com/keboola/keboola-as-code/internal/pkg/utils/errors"
1516
)
1617

1718
// Interval sets how often the proxy sends wakeup request to sandboxes service.
@@ -72,6 +73,15 @@ func (l *Manager) Wakeup(ctx context.Context, appID api.AppID) error {
7273

7374
// Send the notification
7475
_, err := l.api.WakeupApp(appID).Send(ctx)
76+
77+
// Check if it's a restart disabled error via context code
78+
var apiErr *api.Error
79+
if errors.As(err, &apiErr) && apiErr.HasRestartDisabled() {
80+
// This is expected for apps with restart disabled, don't log as error
81+
l.logger.Infof(ctx, `app "%s" has restart disabled`, appID)
82+
return err // Still return the error so it can be handled by the proxy
83+
}
84+
7585
// If it does not succeed but app is currently stopping do not log it as error, log only other errors
7686
// Instead of implementing state machine as in sandboxes service, we want to skip valid state that the
7787
// pod is deallocating, and we want to wait till pod is `stopped` and we can `start` the pod again.

internal/pkg/service/appsproxy/proxy/apphandler/authproxy/oauthproxy/pagewriter.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -99,7 +99,7 @@ func (pw *pageWriter) WriteErrorPage(w http.ResponseWriter, req *http.Request, o
9999
}
100100

101101
func (pw *pageWriter) ProxyErrorHandler(w http.ResponseWriter, req *http.Request, err error) {
102-
pw.pageWriter.ProxyErrorHandler(w, req, pw.app, err)
102+
pw.pageWriter.ProxyErrorHandler(w, req, pw.app, nil, err)
103103
}
104104

105105
func (pw *pageWriter) WriteRobotsTxt(w http.ResponseWriter, req *http.Request) {

internal/pkg/service/appsproxy/proxy/apphandler/upstream/upstream.go

Lines changed: 17 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -49,13 +49,14 @@ type Manager struct {
4949
}
5050

5151
type AppUpstream struct {
52-
manager *Manager
53-
app api.AppConfig
54-
target *url.URL
55-
handler *chain.Chain
56-
wsHandler *chain.Chain
57-
cancelWs context.CancelCauseFunc
58-
activeWsCount atomic.Int64
52+
manager *Manager
53+
app api.AppConfig
54+
target *url.URL
55+
handler *chain.Chain
56+
wsHandler *chain.Chain
57+
cancelWs context.CancelCauseFunc
58+
activeWsCount atomic.Int64
59+
restartDisabled atomic.Bool
5960
}
6061

6162
type dependencies interface {
@@ -141,7 +142,7 @@ func (u *AppUpstream) ServeHTTPOrError(rw http.ResponseWriter, req *http.Request
141142
func (u *AppUpstream) newProxy(timeout time.Duration) *chain.Chain {
142143
proxy := httputil.NewSingleHostReverseProxy(u.target)
143144
proxy.Transport = u.manager.transport
144-
proxy.ErrorHandler = u.manager.pageWriter.ProxyErrorHandlerFor(u.app)
145+
proxy.ErrorHandler = u.manager.pageWriter.ProxyErrorHandlerFor(u.app, &u.restartDisabled)
145146

146147
return chain.
147148
New(chain.HandlerFunc(func(w http.ResponseWriter, req *http.Request) error {
@@ -160,7 +161,7 @@ func (u *AppUpstream) newProxy(timeout time.Duration) *chain.Chain {
160161
func (u *AppUpstream) newWebsocketProxy(timeout time.Duration) *chain.Chain {
161162
proxy := httputil.NewSingleHostReverseProxy(u.target)
162163
proxy.Transport = u.manager.transport
163-
proxy.ErrorHandler = u.manager.pageWriter.ProxyErrorHandlerFor(u.app)
164+
proxy.ErrorHandler = u.manager.pageWriter.ProxyErrorHandlerFor(u.app, &u.restartDisabled)
164165

165166
return chain.
166167
New(chain.HandlerFunc(func(w http.ResponseWriter, req *http.Request) error {
@@ -232,6 +233,13 @@ func (u *AppUpstream) wakeup(ctx context.Context, err error) {
232233

233234
// Error is already logged by the Wakeup method itself.
234235
err := u.manager.wakeup.Wakeup(wakeupCtx, u.app.ID) //nolint:contextcheck
236+
237+
// Check for restart disabled error
238+
var apiErr *api.Error
239+
if errors.As(err, &apiErr) && apiErr.HasRestartDisabled() {
240+
u.restartDisabled.Store(true)
241+
}
242+
235243
span.End(&err)
236244
})
237245
}

internal/pkg/service/appsproxy/proxy/pagewriter/error.go

Lines changed: 14 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import (
44
"net"
55
"net/http"
66
"strings"
7+
"sync/atomic"
78

89
"go.opentelemetry.io/otel/attribute"
910
semconv "go.opentelemetry.io/otel/semconv/v1.17.0"
@@ -14,7 +15,9 @@ import (
1415
"github.com/keboola/keboola-as-code/internal/pkg/utils/errors"
1516
)
1617

17-
const ExceptionIDPrefix = "keboola-appsproxy-"
18+
const (
19+
ExceptionIDPrefix = "keboola-appsproxy-"
20+
)
1821

1922
type errorPageData struct {
2023
App *AppData
@@ -24,13 +27,20 @@ type errorPageData struct {
2427
ExceptionID string
2528
}
2629

27-
func (pw *Writer) ProxyErrorHandlerFor(app api.AppConfig) func(w http.ResponseWriter, req *http.Request, err error) {
30+
func (pw *Writer) ProxyErrorHandlerFor(app api.AppConfig, restartDisabled *atomic.Bool) func(w http.ResponseWriter, req *http.Request, err error) {
2831
return func(w http.ResponseWriter, req *http.Request, err error) {
29-
pw.ProxyErrorHandler(w, req, app, err)
32+
pw.ProxyErrorHandler(w, req, app, restartDisabled, err)
3033
}
3134
}
3235

33-
func (pw *Writer) ProxyErrorHandler(w http.ResponseWriter, req *http.Request, app api.AppConfig, err error) {
36+
func (pw *Writer) ProxyErrorHandler(w http.ResponseWriter, req *http.Request, app api.AppConfig, restartDisabled *atomic.Bool, err error) {
37+
// Check for restart disabled error
38+
if restartDisabled.Load() {
39+
pw.logger.Info(req.Context(), "app has restart disabled, rendering restart disabled page")
40+
pw.WriteRestartDisabledPage(w, req, app)
41+
return
42+
}
43+
3444
var dnsError *net.DNSError
3545
if errors.As(err, &dnsError) {
3646
pw.logger.Info(req.Context(), "app is not running, rendering spinner page")
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
package pagewriter
2+
3+
import (
4+
"net/http"
5+
6+
"github.com/keboola/keboola-as-code/internal/pkg/service/appsproxy/dataapps/api"
7+
)
8+
9+
type RestartDisabledPageData struct {
10+
App AppData
11+
}
12+
13+
func (pw *Writer) WriteRestartDisabledPage(w http.ResponseWriter, req *http.Request, app api.AppConfig) {
14+
w.Header().Set("Cache-Control", "no-cache, no-store, must-revalidate;")
15+
w.Header().Set("pragma", "no-cache")
16+
pw.writePage(w, req, "restart_disabled.gohtml", http.StatusNotFound, RestartDisabledPageData{
17+
App: NewAppData(&app),
18+
})
19+
}

0 commit comments

Comments
 (0)