Skip to content

Commit b2b2dbc

Browse files
authored
feat(api): hatchery config property IgnoreJobWithNoRegion on consumer (#6162)
1 parent 7fe993a commit b2b2dbc

File tree

22 files changed

+219
-83
lines changed

22 files changed

+219
-83
lines changed

cli/cdsctl/consumer.go

+8
Original file line numberDiff line numberDiff line change
@@ -194,6 +194,11 @@ func authConsumerNewRun(v cli.Values) error {
194194
svcRegion = cli.AskValue("Service region")
195195
}
196196

197+
var svcIgnoreJobWithNoRegion = v.GetBool("service-ignore-job-with-no-region")
198+
if !svcIgnoreJobWithNoRegion && !v.GetBool("no-interactive") {
199+
svcIgnoreJobWithNoRegion = cli.AskConfirm("Service ignore job with no region")
200+
}
201+
197202
var consumer = sdk.AuthConsumer{
198203
Name: name,
199204
Description: description,
@@ -211,6 +216,9 @@ func authConsumerNewRun(v cli.Values) error {
211216
if svcRegion != "" {
212217
consumer.ServiceRegion = &svcRegion
213218
}
219+
if svcIgnoreJobWithNoRegion {
220+
consumer.ServiceIgnoreJobWithNoRegion = &svcIgnoreJobWithNoRegion
221+
}
214222

215223
res, err := client.AuthConsumerCreateForUser(username, consumer)
216224
if err != nil {

engine/api/auth_builtin.go

+16-3
Original file line numberDiff line numberDiff line change
@@ -77,11 +77,24 @@ func (api *API) postAuthBuiltinSigninHandler() service.Handler {
7777
if consumer.ServiceType != nil && *consumer.ServiceType != srv.Type {
7878
return sdk.NewErrorFrom(sdk.ErrForbidden, "service type %q doesn't match with consumer %q", srv.Type, *consumer.ServiceType)
7979
}
80-
if consumer.ServiceRegion != nil && *consumer.ServiceRegion != *srv.Region {
81-
return sdk.NewErrorFrom(sdk.ErrForbidden, "service region %q doesn't match with consumer %q", srv.Type, *consumer.ServiceRegion)
80+
if consumer.ServiceRegion != nil {
81+
if srv.Region == nil {
82+
return sdk.NewErrorFrom(sdk.ErrForbidden, "unknown service region doesn't match with consumer %q", *consumer.ServiceRegion)
83+
}
84+
if *consumer.ServiceRegion != *srv.Region {
85+
return sdk.NewErrorFrom(sdk.ErrForbidden, "service region %q doesn't match with consumer %q", *srv.Region, *consumer.ServiceRegion)
86+
}
87+
}
88+
if consumer.ServiceIgnoreJobWithNoRegion != nil {
89+
if srv.IgnoreJobWithNoRegion == nil {
90+
return sdk.NewErrorFrom(sdk.ErrForbidden, "unknown service ignore job with no region value doesn't match with consumer '%t'", *consumer.ServiceIgnoreJobWithNoRegion)
91+
}
92+
if *consumer.ServiceIgnoreJobWithNoRegion != *srv.IgnoreJobWithNoRegion {
93+
return sdk.NewErrorFrom(sdk.ErrForbidden, "service ignore job with no region flag value '%t' doesn't match with consumer '%t'", *srv.IgnoreJobWithNoRegion, *consumer.ServiceIgnoreJobWithNoRegion)
94+
}
8295
}
8396
} else {
84-
if consumer.ServiceName != nil || consumer.ServiceType != nil || consumer.ServiceRegion != nil {
97+
if consumer.ServiceName != nil || consumer.ServiceType != nil || consumer.ServiceRegion != nil || consumer.ServiceIgnoreJobWithNoRegion != nil {
8598
return sdk.NewErrorFrom(sdk.ErrForbidden, "signing request doesn't match with consumer %q service definition. missing service payload", consumer.Name)
8699
}
87100
}

engine/api/auth_consumer.go

+9-8
Original file line numberDiff line numberDiff line change
@@ -85,14 +85,15 @@ func (api *API) postConsumerByUserHandler() service.Handler {
8585

8686
// Create the new built in consumer from request data
8787
consumerOpts := builtin.NewConsumerOptions{
88-
Name: reqData.Name,
89-
Description: reqData.Description,
90-
Duration: reqData.ValidityPeriods.Latest().Duration,
91-
GroupIDs: reqData.GroupIDs,
92-
Scopes: reqData.ScopeDetails,
93-
ServiceName: reqData.ServiceName,
94-
ServiceType: reqData.ServiceType,
95-
ServiceRegion: reqData.ServiceRegion,
88+
Name: reqData.Name,
89+
Description: reqData.Description,
90+
Duration: reqData.ValidityPeriods.Latest().Duration,
91+
GroupIDs: reqData.GroupIDs,
92+
Scopes: reqData.ScopeDetails,
93+
ServiceName: reqData.ServiceName,
94+
ServiceType: reqData.ServiceType,
95+
ServiceRegion: reqData.ServiceRegion,
96+
ServiceIgnoreJobWithNoRegion: reqData.ServiceIgnoreJobWithNoRegion,
9697
}
9798
newConsumer, token, err := builtin.NewConsumer(ctx, tx, consumerOpts, consumer)
9899
if err != nil {

engine/api/authentication/builtin/builtin.go

+22-20
Original file line numberDiff line numberDiff line change
@@ -66,14 +66,15 @@ func (d AuthDriver) CheckSigninRequest(req sdk.AuthConsumerSigninRequest) error
6666
// NewConsumer returns a new builtin consumer for given data.
6767
// The parent consumer should be given with all data loaded including the authentified user.
6868
type NewConsumerOptions struct {
69-
Name string
70-
Description string
71-
Duration time.Duration
72-
GroupIDs []int64
73-
Scopes sdk.AuthConsumerScopeDetails
74-
ServiceName *string
75-
ServiceType *string
76-
ServiceRegion *string
69+
Name string
70+
Description string
71+
Duration time.Duration
72+
GroupIDs []int64
73+
Scopes sdk.AuthConsumerScopeDetails
74+
ServiceName *string
75+
ServiceType *string
76+
ServiceRegion *string
77+
ServiceIgnoreJobWithNoRegion *bool
7778
}
7879

7980
func NewConsumer(ctx context.Context, db gorpmapper.SqlExecutorWithTx, opts NewConsumerOptions, parentConsumer *sdk.AuthConsumer) (*sdk.AuthConsumer, string, error) {
@@ -102,18 +103,19 @@ func NewConsumer(ctx context.Context, db gorpmapper.SqlExecutorWithTx, opts NewC
102103
}
103104

104105
c := sdk.AuthConsumer{
105-
Name: opts.Name,
106-
Description: opts.Description,
107-
ParentID: &parentConsumer.ID,
108-
AuthentifiedUserID: parentConsumer.AuthentifiedUserID,
109-
Type: sdk.ConsumerBuiltin,
110-
Data: map[string]string{},
111-
GroupIDs: opts.GroupIDs,
112-
ScopeDetails: opts.Scopes,
113-
ValidityPeriods: sdk.NewAuthConsumerValidityPeriod(time.Now(), opts.Duration),
114-
ServiceName: opts.ServiceName,
115-
ServiceType: opts.ServiceType,
116-
ServiceRegion: opts.ServiceRegion,
106+
Name: opts.Name,
107+
Description: opts.Description,
108+
ParentID: &parentConsumer.ID,
109+
AuthentifiedUserID: parentConsumer.AuthentifiedUserID,
110+
Type: sdk.ConsumerBuiltin,
111+
Data: map[string]string{},
112+
GroupIDs: opts.GroupIDs,
113+
ScopeDetails: opts.Scopes,
114+
ValidityPeriods: sdk.NewAuthConsumerValidityPeriod(time.Now(), opts.Duration),
115+
ServiceName: opts.ServiceName,
116+
ServiceType: opts.ServiceType,
117+
ServiceRegion: opts.ServiceRegion,
118+
ServiceIgnoreJobWithNoRegion: opts.ServiceIgnoreJobWithNoRegion,
117119
}
118120

119121
if err := authentication.InsertConsumer(ctx, db, &c); err != nil {

engine/api/authentication/gorp_model.go

+2-2
Original file line numberDiff line numberDiff line change
@@ -12,9 +12,9 @@ type authConsumer struct {
1212
}
1313

1414
func (c authConsumer) Canonical() gorpmapper.CanonicalForms {
15-
_ = []interface{}{c.ID, c.AuthentifiedUserID, c.Type, c.Data, c.Created, c.GroupIDs, c.ScopeDetails, c.Disabled, c.ServiceName, c.ServiceType, c.ServiceRegion} // Checks that fields exists at compilation
15+
_ = []interface{}{c.ID, c.AuthentifiedUserID, c.Type, c.Data, c.Created, c.GroupIDs, c.ScopeDetails, c.Disabled, c.ServiceName, c.ServiceType, c.ServiceRegion, c.ServiceIgnoreJobWithNoRegion} // Checks that fields exists at compilation
1616
return []gorpmapper.CanonicalForm{
17-
//"{{.ID}}{{.AuthentifiedUserID}}{{print .Type}}{{print .Data}}{{printDate .Created}}{{print .GroupIDs}}{{print .ScopeDetails}}{{print .Disabled}}{{.ServiceName}}{{.ServiceType}}{{.ServiceRegion}}",
17+
//"{{.ID}}{{.AuthentifiedUserID}}{{print .Type}}{{print .Data}}{{printDate .Created}}{{print .GroupIDs}}{{print .ScopeDetails}}{{print .Disabled}}{{.ServiceName}}{{.ServiceType}}{{.ServiceRegion}}{{.ServiceIgnoreJobWithNoRegion}}",
1818
"{{.ID}}{{.AuthentifiedUserID}}{{print .Type}}{{print .Data}}{{printDate .Created}}{{print .GroupIDs}}{{print .ScopeDetails}}{{print .Disabled}}",
1919
}
2020
}

engine/api/services/gorp_model.go

+2
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,9 @@ type service struct {
1212
}
1313

1414
func (s service) Canonical() gorpmapper.CanonicalForms {
15+
_ = []interface{}{s.ID, s.Name, s.Type, s.Region, s.IgnoreJobWithNoRegion} // Checks that fields exists at compilation
1516
return []gorpmapper.CanonicalForm{
17+
//"{{.ID}}{{.Name}}{{.Type}}{{.Region}}{{.IgnoreJobWithNoRegion}}",
1618
"{{.ID}}{{.Name}}{{.Type}}",
1719
}
1820
}

engine/api/worker/registration.go

+8-5
Original file line numberDiff line numberDiff line change
@@ -111,12 +111,15 @@ func RegisterWorker(ctx context.Context, db gorpmapper.SqlExecutorWithTx, store
111111
}
112112
}
113113

114-
// Check additional information based on the consumer if a region is set, allows to register only job with same region.
115-
// TODO add another information on the consumer to allow job with a specific region requirement or no region (runNodeJob.Region == nil).
114+
// Check additional information based on the consumer if a region is set.
115+
// Allows to register only job with same region or job without region if ServiceIgnoreJobWithNoRegion is not true.
116116
if hatcheryConsumer.ServiceRegion != nil && *hatcheryConsumer.ServiceRegion != "" {
117-
canTakeJobWithRegion := runNodeJob.Region != nil && *runNodeJob.Region == *hatcheryConsumer.ServiceRegion
118-
if !canTakeJobWithRegion {
119-
return nil, sdk.WrapError(sdk.ErrForbidden, "hatchery can't register job with id %q for region %s", spawnArgs.JobID, *runNodeJob.Region)
117+
if runNodeJob.Region == nil {
118+
if hatcheryConsumer.ServiceIgnoreJobWithNoRegion != nil && *hatcheryConsumer.ServiceIgnoreJobWithNoRegion {
119+
return nil, sdk.WrapError(sdk.ErrForbidden, "hatchery can't register job with id %d without region requirement", spawnArgs.JobID)
120+
}
121+
} else if *runNodeJob.Region != *hatcheryConsumer.ServiceRegion {
122+
return nil, sdk.WrapError(sdk.ErrForbidden, "hatchery can't register job with id %d for region %s", spawnArgs.JobID, *runNodeJob.Region)
120123
}
121124
}
122125
}

engine/gorpmapper/signature_test.go

+78
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
package gorpmapper
2+
3+
import (
4+
"bytes"
5+
"testing"
6+
7+
"github.com/ovh/cds/sdk"
8+
"github.com/stretchr/testify/require"
9+
)
10+
11+
type testService struct {
12+
sdk.Service
13+
SignedEntity
14+
}
15+
16+
func (s testService) Canonical() CanonicalForms {
17+
return []CanonicalForm{
18+
"{{.ID}}{{.Name}}{{.Type}}{{.Region}}{{.IgnoreJobWithNoRegion}}",
19+
}
20+
}
21+
22+
func Test_CanonicalFormWithPointer(t *testing.T) {
23+
m := New()
24+
m.Register(m.NewTableMapping(testService{}, "service", false, "id"))
25+
26+
region := "test"
27+
28+
cases := []struct {
29+
name string
30+
s testService
31+
res string
32+
}{
33+
{
34+
name: "Service with empty values",
35+
s: testService{
36+
Service: sdk.Service{
37+
CanonicalService: sdk.CanonicalService{
38+
ID: 123,
39+
Name: "my-service",
40+
Type: sdk.TypeHatchery,
41+
Region: nil,
42+
IgnoreJobWithNoRegion: nil,
43+
},
44+
},
45+
},
46+
res: "123my-servicehatchery<nil><nil>",
47+
},
48+
{
49+
name: "Service without empty values",
50+
s: testService{
51+
Service: sdk.Service{
52+
CanonicalService: sdk.CanonicalService{
53+
ID: 123,
54+
Name: "my-service",
55+
Type: sdk.TypeHatchery,
56+
Region: &region,
57+
IgnoreJobWithNoRegion: &sdk.True,
58+
},
59+
},
60+
},
61+
res: "123my-servicehatcherytesttrue",
62+
},
63+
}
64+
65+
for _, c := range cases {
66+
t.Run(c.name, func(t *testing.T) {
67+
f, _ := c.s.Canonical().Latest()
68+
tmpl, err := m.getCanonicalTemplate(f)
69+
require.NoError(t, err)
70+
require.NotNil(t, tmpl)
71+
72+
var clearContent = new(bytes.Buffer)
73+
require.NoError(t, tmpl.Execute(clearContent, c.s))
74+
75+
require.Equal(t, c.res, clearContent.String())
76+
})
77+
}
78+
}

engine/hatchery/kubernetes/kubernetes.go

+1
Original file line numberDiff line numberDiff line change
@@ -94,6 +94,7 @@ func (h *HatcheryKubernetes) ApplyConfiguration(cfg interface{}) error {
9494
return fmt.Errorf("unable to parse RSA private Key: %v", err)
9595
}
9696
h.Common.Common.Region = h.Config.Provision.Region
97+
h.Common.Common.IgnoreJobWithNoRegion = h.Config.Provision.IgnoreJobWithNoRegion
9798

9899
return nil
99100
}

engine/hatchery/local/local.go

+1
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,7 @@ func (h *HatcheryLocal) ApplyConfiguration(cfg interface{}) error {
7777
return fmt.Errorf("unable to parse RSA private Key: %v", err)
7878
}
7979
h.Common.Common.Region = h.Config.Provision.Region
80+
h.Common.Common.IgnoreJobWithNoRegion = h.Config.Provision.IgnoreJobWithNoRegion
8081

8182
return nil
8283
}

engine/hatchery/marathon/marathon.go

+1
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,7 @@ func (h *HatcheryMarathon) ApplyConfiguration(cfg interface{}) error {
8181
return fmt.Errorf("unable to parse RSA private Key: %v", err)
8282
}
8383
h.Common.Common.Region = h.Config.Provision.Region
84+
h.Common.Common.IgnoreJobWithNoRegion = h.Config.Provision.IgnoreJobWithNoRegion
8485

8586
return nil
8687
}

engine/hatchery/openstack/openstack.go

+1
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,7 @@ func (h *HatcheryOpenstack) ApplyConfiguration(cfg interface{}) error {
8585
return fmt.Errorf("unable to parse RSA private Key: %v", err)
8686
}
8787
h.Common.Common.Region = h.Config.Provision.Region
88+
h.Common.Common.IgnoreJobWithNoRegion = h.Config.Provision.IgnoreJobWithNoRegion
8889

8990
return nil
9091
}

engine/hatchery/swarm/swarm_conf.go

+1
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,7 @@ func (h *HatcherySwarm) ApplyConfiguration(cfg interface{}) error {
5555
return fmt.Errorf("unable to parse RSA private Key: %v", err)
5656
}
5757
h.Common.Common.Region = h.Config.Provision.Region
58+
h.Common.Common.IgnoreJobWithNoRegion = h.Config.Provision.IgnoreJobWithNoRegion
5859

5960
return nil
6061
}

engine/hatchery/vsphere/hatchery.go

+1
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,7 @@ func (h *HatcheryVSphere) ApplyConfiguration(cfg interface{}) error {
7474
return sdk.WithStack(fmt.Errorf("unable to parse RSA private Key: %v", err))
7575
}
7676
h.Common.Common.Region = h.Config.Provision.Region
77+
h.Common.Common.IgnoreJobWithNoRegion = h.Config.Provision.IgnoreJobWithNoRegion
7778

7879
if h.Config.WorkerTTL == 0 {
7980
h.Config.WorkerTTL = 120

engine/service/service.go

+3
Original file line numberDiff line numberDiff line change
@@ -111,6 +111,9 @@ func (c *Common) Signin(ctx context.Context, cdsclientConfig cdsclient.ServiceCo
111111
if c.Region != "" {
112112
registerPayload.CanonicalService.Region = &c.Region
113113
}
114+
if c.IgnoreJobWithNoRegion {
115+
registerPayload.CanonicalService.IgnoreJobWithNoRegion = &c.IgnoreJobWithNoRegion
116+
}
114117

115118
initClient := func(ctx context.Context) error {
116119
var err error

engine/service/types.go

+16-15
Original file line numberDiff line numberDiff line change
@@ -106,21 +106,22 @@ func (hcc HatcheryCommonConfiguration) Check() error {
106106

107107
// Common is the struct representing a CDS µService
108108
type Common struct {
109-
Client cdsclient.Interface
110-
APIPublicKey []byte
111-
ParsedAPIPublicKey *rsa.PublicKey
112-
StartupTime time.Time
113-
HTTPURL string
114-
MaxHeartbeatFailures int
115-
ServiceName string
116-
ServiceType string
117-
ServiceInstance *sdk.Service
118-
PrivateKey *rsa.PrivateKey
119-
Signer jose.Signer
120-
CDNLogsURL string
121-
ServiceLogger *logrus.Logger
122-
GoRoutines *sdk.GoRoutines
123-
Region string
109+
Client cdsclient.Interface
110+
APIPublicKey []byte
111+
ParsedAPIPublicKey *rsa.PublicKey
112+
StartupTime time.Time
113+
HTTPURL string
114+
MaxHeartbeatFailures int
115+
ServiceName string
116+
ServiceType string
117+
ServiceInstance *sdk.Service
118+
PrivateKey *rsa.PrivateKey
119+
Signer jose.Signer
120+
CDNLogsURL string
121+
ServiceLogger *logrus.Logger
122+
GoRoutines *sdk.GoRoutines
123+
Region string
124+
IgnoreJobWithNoRegion bool
124125
}
125126

126127
// Service is the interface for a engine service
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
-- +migrate Up
2+
ALTER TABLE "auth_consumer" ADD COLUMN IF NOT EXISTS "service_ignore_job_with_no_region" BOOLEAN;
3+
4+
ALTER TABLE "service" ADD COLUMN IF NOT EXISTS "ignore_job_with_no_region" BOOLEAN;
5+
6+
-- +migrate Down
7+
ALTER TABLE "auth_consumer" DROP COLUMN IF EXISTS "service_ignore_job_with_no_region";
8+
9+
ALTER TABLE "service" DROP COLUMN IF EXISTS "ignore_job_with_no_region";

sdk/services.go

+9-8
Original file line numberDiff line numberDiff line change
@@ -21,14 +21,15 @@ const (
2121
)
2222

2323
type CanonicalService struct {
24-
ID int64 `json:"id" db:"id" mapstructure:"id"`
25-
Name string `json:"name" db:"name" cli:"name,key" mapstructure:"name"`
26-
ConsumerID *string `json:"-" db:"auth_consumer_id" mapstructure:"-"`
27-
Type string `json:"type" db:"type" cli:"type" mapstructure:"type"`
28-
HTTPURL string `json:"http_url" db:"http_url" cli:"url" mapstructure:"http_url"`
29-
Config ServiceConfig `json:"config" db:"config" cli:"-" mapstructure:"config"`
30-
PublicKey []byte `json:"public_key" db:"public_key" mapstructure:"public_key"`
31-
Region *string `json:"region" db:"region" mapstructure:"region"`
24+
ID int64 `json:"id" db:"id" mapstructure:"id"`
25+
Name string `json:"name" db:"name" cli:"name,key" mapstructure:"name"`
26+
ConsumerID *string `json:"-" db:"auth_consumer_id" mapstructure:"-"`
27+
Type string `json:"type" db:"type" cli:"type" mapstructure:"type"`
28+
HTTPURL string `json:"http_url" db:"http_url" cli:"url" mapstructure:"http_url"`
29+
Config ServiceConfig `json:"config" db:"config" cli:"-" mapstructure:"config"`
30+
PublicKey []byte `json:"public_key" db:"public_key" mapstructure:"public_key"`
31+
Region *string `json:"region" db:"region" mapstructure:"region"`
32+
IgnoreJobWithNoRegion *bool `json:"ignore_job_with_no_region" db:"ignore_job_with_no_region" mapstructure:"ignore_job_with_no_region"`
3233
}
3334

3435
// Service is a µService registered on CDS API.

0 commit comments

Comments
 (0)