Skip to content

Commit 21b845b

Browse files
authored
fix(api): Allow to get every service through getServiceHandler (#5173)
1 parent 1a6173f commit 21b845b

File tree

14 files changed

+140
-71
lines changed

14 files changed

+140
-71
lines changed

contrib/grpcplugins/action/clair/main.go

+2-1
Original file line numberDiff line numberDiff line change
@@ -38,10 +38,11 @@ func (actPlugin *clairActionPlugin) Manifest(ctx context.Context, _ *empty.Empty
3838
func (actPlugin *clairActionPlugin) Run(ctx context.Context, q *actionplugin.ActionQuery) (*actionplugin.ActionResult, error) {
3939
// get clair configuration
4040
fmt.Printf("Retrieve clair configuration...")
41-
serv, err := grpcplugins.GetExternalServices(actPlugin.HTTPPort, "clair")
41+
servs, err := grpcplugins.GetServices(actPlugin.HTTPPort, "clair")
4242
if err != nil {
4343
return actionplugin.Fail("Unable to get clair configuration: %s", err)
4444
}
45+
serv := servs[0]
4546
viper.Set("clair.uri", serv.URL)
4647
viper.Set("clair.port", serv.Port)
4748
viper.Set("clair.healthPort", serv.HealthPort)

contrib/grpcplugins/grpcplugins.go

+9-9
Original file line numberDiff line numberDiff line change
@@ -38,34 +38,34 @@ func SendVulnerabilityReport(workerHTTPPort int32, report sdk.VulnerabilityWorke
3838
return nil
3939
}
4040

41-
// GetExternalServices call worker to get external service configuration
42-
func GetExternalServices(workerHTTPPort int32, serviceType string) (sdk.ExternalService, error) {
41+
// GetServices call worker to get external service configuration
42+
func GetServices(workerHTTPPort int32, serviceType string) ([]sdk.ServiceConfiguration, error) {
4343
if workerHTTPPort == 0 {
44-
return sdk.ExternalService{}, nil
44+
return nil, nil
4545
}
4646

4747
req, err := http.NewRequest("GET", fmt.Sprintf("http://127.0.0.1:%d/services/%s", workerHTTPPort, serviceType), nil)
4848
if err != nil {
49-
return sdk.ExternalService{}, fmt.Errorf("get service from worker /services: %v", err)
49+
return nil, fmt.Errorf("get service from worker /services: %v", err)
5050
}
5151

5252
resp, err := http.DefaultClient.Do(req)
5353
if err != nil {
54-
return sdk.ExternalService{}, fmt.Errorf("cannot get service from worker /services: %v", err)
54+
return nil, fmt.Errorf("cannot get service from worker /services: %v", err)
5555
}
5656

5757
if resp.StatusCode >= 300 {
58-
return sdk.ExternalService{}, fmt.Errorf("cannot get services from worker /services: HTTP %d", resp.StatusCode)
58+
return nil, fmt.Errorf("cannot get services from worker /services: HTTP %d", resp.StatusCode)
5959
}
6060

6161
b, err := ioutil.ReadAll(resp.Body)
6262
if err != nil {
63-
return sdk.ExternalService{}, fmt.Errorf("cannot read body /services: %v", err)
63+
return nil, fmt.Errorf("cannot read body /services: %v", err)
6464
}
6565

66-
var serv sdk.ExternalService
66+
var serv []sdk.ServiceConfiguration
6767
if err := json.Unmarshal(b, &serv); err != nil {
68-
return serv, fmt.Errorf("cannot unmarshal body /services: %v", err)
68+
return nil, fmt.Errorf("cannot unmarshal body /services: %v", err)
6969
}
7070
return serv, nil
7171
}

engine/api/api.go

+5-17
Original file line numberDiff line numberDiff line change
@@ -181,9 +181,9 @@ type Configuration struct {
181181
Token string `toml:"token" comment:"Token shared between Izanami and CDS to be able to send webhooks from izanami" json:"-"`
182182
} `toml:"izanami" comment:"Feature flipping provider: https://maif.github.io/izanami" json:"izanami"`
183183
} `toml:"features" comment:"###########################\n CDS Features flipping Settings \n##########################" json:"features"`
184-
Services []ServiceConfiguration `toml:"services" comment:"###########################\n CDS Services Settings \n##########################" json:"services"`
185-
DefaultOS string `toml:"defaultOS" default:"linux" comment:"if no model and os/arch is specified in your job's requirements then spawn worker on this operating system (example: freebsd, linux, windows)" json:"defaultOS"`
186-
DefaultArch string `toml:"defaultArch" default:"amd64" comment:"if no model and no os/arch is specified in your job's requirements then spawn worker on this architecture (example: amd64, arm, 386)" json:"defaultArch"`
184+
Services []sdk.ServiceConfiguration `toml:"services" comment:"###########################\n CDS Services Settings \n##########################" json:"services"`
185+
DefaultOS string `toml:"defaultOS" default:"linux" comment:"if no model and os/arch is specified in your job's requirements then spawn worker on this operating system (example: freebsd, linux, windows)" json:"defaultOS"`
186+
DefaultArch string `toml:"defaultArch" default:"amd64" comment:"if no model and no os/arch is specified in your job's requirements then spawn worker on this architecture (example: amd64, arm, 386)" json:"defaultArch"`
187187
Graylog struct {
188188
AccessToken string `toml:"accessToken" json:"-"`
189189
Stream string `toml:"stream" json:"-"`
@@ -196,18 +196,6 @@ type Configuration struct {
196196
CDN cdn.Configuration `toml:"cdn" json:"cdn" comment:"###########################\n CDN settings.\n##########################"`
197197
}
198198

199-
// ServiceConfiguration is the configuration of external service
200-
type ServiceConfiguration struct {
201-
Name string `toml:"name" json:"name"`
202-
URL string `toml:"url" json:"url"`
203-
Port string `toml:"port" json:"port"`
204-
Path string `toml:"path" json:"path"`
205-
HealthURL string `toml:"healthUrl" json:"healthUrl"`
206-
HealthPort string `toml:"healthPort" json:"healthPort"`
207-
HealthPath string `toml:"healthPath" json:"healthPath"`
208-
Type string `toml:"type" json:"type"`
209-
}
210-
211199
// DefaultValues is the struc for API Default configuration default values
212200
type DefaultValues struct {
213201
ServerSecretsKey string
@@ -782,10 +770,10 @@ func (a *API) Serve(ctx context.Context) error {
782770
// Init Services
783771
services.Initialize(ctx, a.DBConnectionFactory, a.PanicDump())
784772

785-
externalServices := make([]sdk.ExternalService, 0, len(a.Config.Services))
773+
externalServices := make([]services.ExternalService, 0, len(a.Config.Services))
786774
for _, s := range a.Config.Services {
787775
log.Info(ctx, "Managing external service %s %s", s.Name, s.Type)
788-
serv := sdk.ExternalService{
776+
serv := services.ExternalService{
789777
Service: sdk.Service{
790778
CanonicalService: sdk.CanonicalService{
791779
Name: s.Name,

engine/api/api_routes.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -423,7 +423,7 @@ func (api *API) InitRouter() {
423423
// Engine µServices
424424
r.Handle("/services/register", Scope(sdk.AuthConsumerScopeService), r.POST(api.postServiceRegisterHandler, MaintenanceAware()))
425425
r.Handle("/services/heartbeat", Scope(sdk.AuthConsumerScopeService), r.POST(api.postServiceHearbeatHandler))
426-
r.Handle("/services/{type}", Scope(sdk.AuthConsumerScopeService), r.GET(api.getExternalServiceHandler))
426+
r.Handle("/services/{type}", Scope(sdk.AuthConsumerScopeService), r.GET(api.getServiceHandler))
427427

428428
// Templates
429429
r.Handle("/template", Scope(sdk.AuthConsumerScopeTemplate), r.GET(api.getTemplatesHandler), r.POST(api.postTemplateHandler))

engine/api/services.go

+33-11
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ package api
22

33
import (
44
"context"
5+
"encoding/base64"
56
"encoding/json"
67
"net/http"
78
"time"
@@ -16,25 +17,46 @@ import (
1617
"github.com/ovh/cds/sdk/log"
1718
)
1819

19-
func (api *API) getExternalServiceHandler() service.Handler {
20+
func (api *API) getServiceHandler() service.Handler {
2021
return func(ctx context.Context, w http.ResponseWriter, r *http.Request) error {
2122
vars := mux.Vars(r)
2223
typeService := vars["type"]
2324

25+
var servicesConf []sdk.ServiceConfiguration
2426
for _, s := range api.Config.Services {
2527
if s.Type == typeService {
26-
extService := sdk.ExternalService{
27-
HealthPath: s.HealthPath,
28-
HealthPort: s.HealthPort,
29-
Path: s.Path,
30-
HealthURL: s.HealthURL,
31-
Port: s.Port,
32-
URL: s.URL,
33-
}
34-
return service.WriteJSON(w, extService, http.StatusOK)
28+
servicesConf = append(servicesConf, s)
3529
}
3630
}
37-
return sdk.WrapError(sdk.ErrNotFound, "service %s not found", typeService)
31+
if len(servicesConf) != 0 {
32+
return service.WriteJSON(w, servicesConf, http.StatusOK)
33+
}
34+
35+
// Try to load from DB
36+
var srvs []sdk.Service
37+
var err error
38+
if isAdmin(ctx) || isMaintainer(ctx) {
39+
srvs, err = services.LoadAllByType(ctx, api.mustDB(), typeService)
40+
} else {
41+
c := getAPIConsumer(ctx)
42+
srvs, err = services.LoadAllByTypeAndUserID(ctx, api.mustDB(), typeService, c.AuthentifiedUserID)
43+
}
44+
if err != nil {
45+
return err
46+
}
47+
for _, s := range srvs {
48+
servicesConf = append(servicesConf, sdk.ServiceConfiguration{
49+
URL: s.HTTPURL,
50+
Name: s.Name,
51+
ID: s.ID,
52+
PublicKey: base64.StdEncoding.EncodeToString(s.PublicKey),
53+
Type: s.Type,
54+
})
55+
}
56+
if len(servicesConf) == 0 {
57+
return sdk.WrapError(sdk.ErrNotFound, "service %s not found", typeService)
58+
}
59+
return service.WriteJSON(w, servicesConf, http.StatusOK)
3860
}
3961
}
4062

engine/api/services/dao.go

+13-3
Original file line numberDiff line numberDiff line change
@@ -69,11 +69,21 @@ func LoadAll(ctx context.Context, db gorp.SqlExecutor) ([]sdk.Service, error) {
6969
}
7070

7171
// LoadAllByType returns all services with given type.
72-
func LoadAllByType(ctx context.Context, db gorp.SqlExecutor, stype string) ([]sdk.Service, error) {
73-
if ss, ok := internalCache.getFromCache(stype); ok {
72+
func LoadAllByType(ctx context.Context, db gorp.SqlExecutor, typeService string) ([]sdk.Service, error) {
73+
if ss, ok := internalCache.getFromCache(typeService); ok {
7474
return ss, nil
7575
}
76-
query := gorpmapping.NewQuery(`SELECT * FROM service WHERE type = $1`).Args(stype)
76+
query := gorpmapping.NewQuery(`SELECT * FROM service WHERE type = $1`).Args(typeService)
77+
return getAll(ctx, db, query)
78+
}
79+
80+
// LoadAllByType returns all services that users can see with given type.
81+
func LoadAllByTypeAndUserID(ctx context.Context, db gorp.SqlExecutor, typeService string, userID string) ([]sdk.Service, error) {
82+
query := gorpmapping.NewQuery(`
83+
SELECT service.*
84+
FROM service
85+
JOIN auth_consumer on auth_consumer.id = service.auth_consumer_id
86+
WHERE service.type = $1 AND auth_consumer.user_id = $2`).Args(typeService, userID)
7787
return getAll(ctx, db, query)
7888
}
7989

engine/api/services/external.go

+22-3
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ package services
22

33
import (
44
"context"
5+
"encoding/json"
56
"fmt"
67
"net/url"
78
"time"
@@ -11,8 +12,26 @@ import (
1112
"github.com/ovh/cds/sdk/log"
1213
)
1314

15+
// ExternalService represents an external service
16+
type ExternalService struct {
17+
sdk.Service `json:"-"`
18+
HealthURL string `json:"health_url"`
19+
HealthPort string `json:"health_port"`
20+
HealthPath string `json:"health_path"`
21+
Port string `json:"port"`
22+
URL string `json:"url"`
23+
Path string `json:"path"`
24+
}
25+
26+
func (e ExternalService) ServiceConfig() sdk.ServiceConfig {
27+
b, _ := json.Marshal(e)
28+
var cfg sdk.ServiceConfig
29+
json.Unmarshal(b, &cfg) // nolint
30+
return cfg
31+
}
32+
1433
// Pings browses all external services and ping them
15-
func Pings(ctx context.Context, dbFunc func() *gorp.DbMap, ss []sdk.ExternalService) {
34+
func Pings(ctx context.Context, dbFunc func() *gorp.DbMap, ss []ExternalService) {
1635
tickPing := time.NewTicker(1 * time.Minute)
1736
db := dbFunc()
1837
for {
@@ -42,7 +61,7 @@ func Pings(ctx context.Context, dbFunc func() *gorp.DbMap, ss []sdk.ExternalServ
4261
}
4362
}
4463

45-
func ping(ctx context.Context, db gorp.SqlExecutor, s sdk.ExternalService) error {
64+
func ping(ctx context.Context, db gorp.SqlExecutor, s ExternalService) error {
4665
// Select for update
4766
serv, err := LoadByNameForUpdateAndSkipLocked(context.Background(), db, s.Name)
4867
if err != nil && !sdk.ErrorIs(err, sdk.ErrNotFound) {
@@ -93,7 +112,7 @@ func ping(ctx context.Context, db gorp.SqlExecutor, s sdk.ExternalService) error
93112
}
94113

95114
// InitExternal initializes external services
96-
func InitExternal(ctx context.Context, db *gorp.DbMap, ss []sdk.ExternalService) error {
115+
func InitExternal(ctx context.Context, db *gorp.DbMap, ss []ExternalService) error {
97116
for _, s := range ss {
98117
tx, err := db.Begin()
99118
if err != nil {

engine/api/services_test.go

+33
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ func TestServicesHandlers(t *testing.T) {
2020
defer end()
2121

2222
admin, jwtRaw := assets.InsertAdminUser(t, api.mustDB())
23+
_, jwtLambda := assets.InsertLambdaUser(t, api.mustDB())
2324

2425
data := sdk.AuthConsumer{
2526
Name: sdk.RandomString(10),
@@ -82,5 +83,37 @@ func TestServicesHandlers(t *testing.T) {
8283
api.Router.Mux.ServeHTTP(rec, req)
8384
require.Equal(t, 204, rec.Code)
8485

86+
// Get service with lambda user => 404
87+
uri = api.Router.GetRoute(http.MethodGet, api.getServiceHandler, map[string]string{
88+
"type": services.TypeHatchery,
89+
})
90+
require.NotEmpty(t, uri)
91+
req = assets.NewJWTAuthentifiedRequest(t, jwtLambda, http.MethodGet, uri, data)
92+
rec = httptest.NewRecorder()
93+
api.Router.Mux.ServeHTTP(rec, req)
94+
require.Equal(t, 404, rec.Code)
95+
96+
// lambda user Insert a service
97+
uri = api.Router.GetRoute(http.MethodPost, api.postServiceRegisterHandler, nil)
98+
require.NotEmpty(t, uri)
99+
req = assets.NewJWTAuthentifiedRequest(t, jwtLambda, http.MethodPost, uri, srv)
100+
rec = httptest.NewRecorder()
101+
api.Router.Mux.ServeHTTP(rec, req)
102+
require.Equal(t, 200, rec.Code)
103+
104+
// Get service with lambda user => 404
105+
uri = api.Router.GetRoute(http.MethodGet, api.getServiceHandler, map[string]string{
106+
"type": services.TypeHatchery,
107+
})
108+
require.NotEmpty(t, uri)
109+
req = assets.NewJWTAuthentifiedRequest(t, jwtLambda, http.MethodGet, uri, data)
110+
rec = httptest.NewRecorder()
111+
api.Router.Mux.ServeHTTP(rec, req)
112+
require.Equal(t, 200, rec.Code)
113+
114+
var servs []sdk.ServiceConfiguration
115+
require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &servs))
116+
require.Equal(t, 1, len(servs))
117+
85118
require.NoError(t, services.Delete(api.mustDB(), &srv))
86119
}

engine/config.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -68,7 +68,7 @@ func configBootstrap(args []string) Configuration {
6868
conf.API = &api.Configuration{}
6969
conf.API.Name = "cds-api-" + namesgenerator.GetRandomNameCDS(0)
7070
defaults.SetDefaults(conf.API)
71-
conf.API.Services = append(conf.API.Services, api.ServiceConfiguration{
71+
conf.API.Services = append(conf.API.Services, sdk.ServiceConfiguration{
7272
Name: "sample-service",
7373
URL: "https://ovh.github.io",
7474
Port: "443",

engine/worker/internal/handler_service.go

+2-2
Original file line numberDiff line numberDiff line change
@@ -15,12 +15,12 @@ func serviceHandler(ctx context.Context, wk *CurrentWorker) http.HandlerFunc {
1515
serviceType := vars["type"]
1616

1717
log.Debug("Getting service configuration...")
18-
serviceConfig, err := wk.Client().ServiceConfigurationGet(ctx, serviceType)
18+
servicesConfig, err := wk.Client().ServiceConfigurationGet(ctx, serviceType)
1919
if err != nil {
2020
log.Warning(ctx, "unable to get data: %v", err)
2121
writeError(w, r, fmt.Errorf("unable to get service configuration"))
2222
}
23-
writeJSON(w, serviceConfig, http.StatusOK)
23+
writeJSON(w, servicesConfig, http.StatusOK)
2424
return
2525
}
2626
}

sdk/cdsclient/client_services.go

+4-4
Original file line numberDiff line numberDiff line change
@@ -33,11 +33,11 @@ func (c *client) ServiceRegister(ctx context.Context, s sdk.Service) (*sdk.Servi
3333
return &s, nil
3434
}
3535

36-
func (c *client) ServiceConfigurationGet(ctx context.Context, t string) (*sdk.ExternalService, error) {
37-
var serviceConf sdk.ExternalService
38-
_, err := c.GetJSON(ctx, fmt.Sprintf("/services/%s", t), &serviceConf)
36+
func (c *client) ServiceConfigurationGet(ctx context.Context, t string) ([]sdk.ServiceConfiguration, error) {
37+
var servicesConf []sdk.ServiceConfiguration
38+
_, err := c.GetJSON(ctx, fmt.Sprintf("/services/%s", t), &servicesConf)
3939
if err != nil {
4040
return nil, err
4141
}
42-
return &serviceConf, nil
42+
return servicesConf, nil
4343
}

sdk/cdsclient/interface.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -383,7 +383,7 @@ type WorkerInterface interface {
383383
ProjectIntegrationGet(projectKey string, integrationName string, clearPassword bool) (sdk.ProjectIntegration, error)
384384
QueueClient
385385
Requirements() ([]sdk.Requirement, error)
386-
ServiceConfigurationGet(context.Context, string) (*sdk.ExternalService, error)
386+
ServiceConfigurationGet(context.Context, string) ([]sdk.ServiceConfiguration, error)
387387
WorkerClient
388388
WorkflowRunArtifacts(projectKey string, name string, number int64) ([]sdk.WorkflowNodeRunArtifact, error)
389389
WorkflowCachePush(projectKey, integrationName, ref string, tarContent io.Reader, size int) error

sdk/cdsclient/mock_cdsclient/interface_mock.go

+2-2
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

sdk/services.go

+12-16
Original file line numberDiff line numberDiff line change
@@ -57,20 +57,16 @@ func (c *ServiceConfig) Scan(src interface{}) error {
5757
return WrapError(json.Unmarshal(source, c), "cannot unmarshal ServiceConfig")
5858
}
5959

60-
// ExternalService represents an external service
61-
type ExternalService struct {
62-
Service `json:"-"`
63-
HealthURL string `json:"health_url"`
64-
HealthPort string `json:"health_port"`
65-
HealthPath string `json:"health_path"`
66-
Port string `json:"port"`
67-
URL string `json:"url"`
68-
Path string `json:"path"`
69-
}
70-
71-
func (e ExternalService) ServiceConfig() ServiceConfig {
72-
b, _ := json.Marshal(e)
73-
var cfg ServiceConfig
74-
json.Unmarshal(b, &cfg) // nolint
75-
return cfg
60+
// ServiceConfiguration is the configuration of service
61+
type ServiceConfiguration struct {
62+
Name string `toml:"name" json:"name"`
63+
URL string `toml:"url" json:"url"`
64+
Port string `toml:"port" json:"port"`
65+
Path string `toml:"path" json:"path"`
66+
HealthURL string `toml:"healthUrl" json:"health_url"`
67+
HealthPort string `toml:"healthPort" json:"health_port"`
68+
HealthPath string `toml:"healthPath" json:"health_path"`
69+
Type string `toml:"type" json:"type"`
70+
PublicKey string `json:"publicKey"`
71+
ID int64 `json:"id"`
7672
}

0 commit comments

Comments
 (0)