Skip to content

Commit f255868

Browse files
Merge pull request #8292 from mjturek/add-service-endpoints
MULTIARCH-4616: Power VS: Add ServiceEndpoints for endpoint overrides
2 parents 5d5fe31 + 6008338 commit f255868

File tree

5 files changed

+227
-5
lines changed

5 files changed

+227
-5
lines changed

data/data/install.openshift.io_installconfigs.yaml

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4247,6 +4247,34 @@ spec:
42474247
description: Region specifies the IBM Cloud colo region where
42484248
the cluster will be created.
42494249
type: string
4250+
serviceEndpoints:
4251+
description: ServiceEndpoints is a list which contains custom
4252+
endpoints to override default service endpoints of IBM Cloud
4253+
Services. There must only be one ServiceEndpoint for a service
4254+
(no duplicates).
4255+
items:
4256+
description: PowervsServiceEndpoint stores the configuration
4257+
of a custom url to override existing defaults of PowerVS Services.
4258+
properties:
4259+
name:
4260+
description: name is the name of the Power VS service. Few
4261+
of the services are IAM - https://cloud.ibm.com/apidocs/iam-identity-token-api
4262+
ResourceController - https://cloud.ibm.com/apidocs/resource-controller/resource-controller
4263+
Power Cloud - https://cloud.ibm.com/apidocs/power-cloud
4264+
pattern: ^[a-z0-9-]+$
4265+
type: string
4266+
url:
4267+
description: url is fully qualified URI with scheme https,
4268+
that overrides the default generated endpoint for a client.
4269+
This must be provided and cannot be empty.
4270+
format: uri
4271+
pattern: ^https://
4272+
type: string
4273+
required:
4274+
- name
4275+
- url
4276+
type: object
4277+
type: array
42504278
serviceInstanceGUID:
42514279
description: ServiceInstanceGUID is the GUID of the Power IAAS
42524280
instance created from the IBM Cloud Catalog before the cluster

pkg/asset/manifests/infrastructure.go

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -295,11 +295,12 @@ func (i *Infrastructure) Generate(dependencies asset.Parents) error {
295295
return errors.New("unknown publishing strategy")
296296
}
297297
config.Status.PlatformStatus.PowerVS = &configv1.PowerVSPlatformStatus{
298-
Region: installConfig.Config.Platform.PowerVS.Region,
299-
Zone: installConfig.Config.Platform.PowerVS.Zone,
300-
ResourceGroup: installConfig.Config.Platform.PowerVS.PowerVSResourceGroup,
301-
CISInstanceCRN: cisInstanceCRN,
302-
DNSInstanceCRN: dnsInstanceCRN,
298+
Region: installConfig.Config.Platform.PowerVS.Region,
299+
Zone: installConfig.Config.Platform.PowerVS.Zone,
300+
ResourceGroup: installConfig.Config.Platform.PowerVS.PowerVSResourceGroup,
301+
CISInstanceCRN: cisInstanceCRN,
302+
DNSInstanceCRN: dnsInstanceCRN,
303+
ServiceEndpoints: installConfig.Config.Platform.PowerVS.ServiceEndpoints,
303304
}
304305
case nutanix.Name:
305306
config.Spec.PlatformSpec.Type = configv1.NutanixPlatformType

pkg/types/powervs/platform.go

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,9 @@
11
package powervs
22

3+
import (
4+
configv1 "github.com/openshift/api/config/v1"
5+
)
6+
37
// Platform stores all the global configuration that all machinesets
48
// use.
59
type Platform struct {
@@ -52,4 +56,10 @@ type Platform struct {
5256
// instance during cluster creation.
5357
// +optional
5458
ServiceInstanceGUID string `json:"serviceInstanceGUID,omitempty"`
59+
60+
// ServiceEndpoints is a list which contains custom endpoints to override default
61+
// service endpoints of IBM Cloud Services.
62+
// There must only be one ServiceEndpoint for a service (no duplicates).
63+
// +optional
64+
ServiceEndpoints []configv1.PowerVSServiceEndpoint `json:"serviceEndpoints,omitempty"`
5565
}

pkg/types/powervs/validation/platform.go

Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,15 @@
11
package validation
22

33
import (
4+
"fmt"
5+
"net/url"
6+
"regexp"
7+
48
"github.com/google/uuid"
9+
"k8s.io/apimachinery/pkg/util/sets"
510
"k8s.io/apimachinery/pkg/util/validation/field"
611

12+
configv1 "github.com/openshift/api/config/v1"
713
"github.com/openshift/installer/pkg/types/powervs"
814
)
915

@@ -44,6 +50,68 @@ func ValidatePlatform(p *powervs.Platform, fldPath *field.Path) field.ErrorList
4450
allErrs = append(allErrs, field.Invalid(fldPath.Child("ServiceInstanceGUID"), p.ServiceInstanceGUID, "ServiceInstanceGUID must be a valid UUID"))
4551
}
4652
}
53+
if p.ServiceEndpoints != nil {
54+
allErrs = append(allErrs, validateServiceEndpoints(p.ServiceEndpoints, fldPath.Child("serviceEndpoints"))...)
55+
}
56+
57+
return allErrs
58+
}
59+
60+
// validateServiceEndpoints checks that the specified ServiceEndpoints are valid.
61+
func validateServiceEndpoints(endpoints []configv1.PowerVSServiceEndpoint, fldPath *field.Path) field.ErrorList {
62+
allErrs := field.ErrorList{}
63+
64+
knownEndpoints := sets.New[string]()
65+
for index, endpoint := range endpoints {
66+
fldp := fldPath.Index(index)
67+
if knownEndpoints.Has(endpoint.Name) {
68+
allErrs = append(allErrs, field.Duplicate(fldp.Child("name"), endpoint.Name))
69+
}
70+
knownEndpoints.Insert(endpoint.Name)
4771

72+
if err := validateServiceURL(endpoint.URL); err != nil {
73+
allErrs = append(allErrs, field.Invalid(fldp.Child("url"), endpoint.URL, err.Error()))
74+
}
75+
}
4876
return allErrs
4977
}
78+
79+
// schemeRE is used to check whether a string starts with a scheme (URI format).
80+
var schemeRE = regexp.MustCompile("^([^:]+)://")
81+
82+
// versionPath is the regexp for a trailing API version in URL path ('/v1', '/v22/', etc.)
83+
var versionPath = regexp.MustCompile(`(/v\d+[/]{0,1})$`)
84+
85+
// validateServiceURL checks that a string meets certain URI expectations.
86+
func validateServiceURL(uri string) error {
87+
endpoint := uri
88+
httpsScheme := "https"
89+
90+
// determine if the endpoint (uri) starts with an URI scheme
91+
// add 'https' scheme if not
92+
if !schemeRE.MatchString(endpoint) {
93+
endpoint = fmt.Sprintf("%s://%s", httpsScheme, endpoint)
94+
}
95+
96+
// verify the endpoint meets the following criteria
97+
// 1. contains a hostname
98+
// 2. uses 'https' scheme
99+
// 3. contains no path or request parameters, except API version paths ('/v1')
100+
u, err := url.Parse(endpoint)
101+
if err != nil {
102+
return err
103+
}
104+
if u.Hostname() == "" {
105+
return fmt.Errorf("empty hostname provided, it cannot be empty")
106+
}
107+
// check the scheme in case one was provided and is not 'https' (we didn't set it above)
108+
if s := u.Scheme; s != httpsScheme {
109+
return fmt.Errorf("invalid scheme %s, only https is allowed", s)
110+
}
111+
// check that the path is empty ('/'), or only contains an API version ('/v1'), by using regexp to replace the API version and should result in empty string
112+
if r := u.RequestURI(); r != "/" && versionPath.ReplaceAllString(r, "") != "" {
113+
return fmt.Errorf("no path or request parameters can be provided, %q was provided", r)
114+
}
115+
116+
return nil
117+
}

pkg/types/powervs/validation/platform_test.go

Lines changed: 115 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import (
99
"github.com/stretchr/testify/assert"
1010
"k8s.io/apimachinery/pkg/util/validation/field"
1111

12+
configv1 "github.com/openshift/api/config/v1"
1213
"github.com/openshift/installer/pkg/types/powervs"
1314
)
1415

@@ -118,6 +119,120 @@ func TestValidatePlatform(t *testing.T) {
118119
}(),
119120
valid: false,
120121
},
122+
{
123+
name: "invalid url (no hostname) for service endpoint",
124+
platform: func() *powervs.Platform {
125+
p := validMinimalPlatform()
126+
p.ServiceEndpoints = []configv1.PowerVSServiceEndpoint{{
127+
Name: string(configv1.IBMCloudServiceCOS),
128+
URL: "/some/path",
129+
}}
130+
return p
131+
}(),
132+
valid: false,
133+
},
134+
{
135+
name: "invalid url (has path) for service endpoint",
136+
platform: func() *powervs.Platform {
137+
p := validMinimalPlatform()
138+
p.ServiceEndpoints = []configv1.PowerVSServiceEndpoint{{
139+
Name: string(configv1.IBMCloudServiceCOS),
140+
URL: "https://test-cos.random.local/some/path",
141+
}}
142+
return p
143+
}(),
144+
valid: false,
145+
},
146+
{
147+
name: "valid url (has version path, no trailing '/') for service endpoint",
148+
platform: func() *powervs.Platform {
149+
p := validMinimalPlatform()
150+
p.ServiceEndpoints = []configv1.PowerVSServiceEndpoint{{
151+
Name: string(configv1.IBMCloudServiceCOS),
152+
URL: "https://test-cos.random.local/v2",
153+
}}
154+
return p
155+
}(),
156+
valid: true,
157+
},
158+
{
159+
name: "valid url (has version path and trailing '/') for service endpoint",
160+
platform: func() *powervs.Platform {
161+
p := validMinimalPlatform()
162+
p.ServiceEndpoints = []configv1.PowerVSServiceEndpoint{{
163+
Name: string(configv1.IBMCloudServiceCOS),
164+
URL: "https://test-cos.random.local/v35/",
165+
}}
166+
return p
167+
}(),
168+
valid: true,
169+
},
170+
{
171+
name: "invalid url (has request) for service endpoint",
172+
platform: func() *powervs.Platform {
173+
p := validMinimalPlatform()
174+
p.ServiceEndpoints = []configv1.PowerVSServiceEndpoint{{
175+
Name: string(configv1.IBMCloudServiceCOS),
176+
URL: "https://test-iam.random.local?foo=some",
177+
}}
178+
return p
179+
}(),
180+
valid: false,
181+
},
182+
{
183+
name: "valid url (no scheme) for service endpoint",
184+
platform: func() *powervs.Platform {
185+
p := validMinimalPlatform()
186+
p.ServiceEndpoints = []configv1.PowerVSServiceEndpoint{{
187+
Name: string(configv1.IBMCloudServiceCOS),
188+
URL: "test-cos.random.local",
189+
}}
190+
return p
191+
}(),
192+
valid: true,
193+
},
194+
{
195+
name: "valid url (with scheme) for service endpoint",
196+
platform: func() *powervs.Platform {
197+
p := validMinimalPlatform()
198+
p.ServiceEndpoints = []configv1.PowerVSServiceEndpoint{{
199+
Name: string(configv1.IBMCloudServiceCOS),
200+
URL: "https://test-cos.random.local",
201+
}}
202+
return p
203+
}(),
204+
valid: true,
205+
},
206+
{
207+
name: "duplicate service endpoints",
208+
platform: func() *powervs.Platform {
209+
p := validMinimalPlatform()
210+
p.ServiceEndpoints = []configv1.PowerVSServiceEndpoint{{
211+
Name: string(configv1.IBMCloudServiceCOS),
212+
URL: "https://test-cos.random.local",
213+
}, {
214+
Name: string(configv1.IBMCloudServiceCOS),
215+
URL: "test-cos.random.local",
216+
}}
217+
return p
218+
}(),
219+
valid: false,
220+
},
221+
{
222+
name: "multiple valid service endpoints",
223+
platform: func() *powervs.Platform {
224+
p := validMinimalPlatform()
225+
p.ServiceEndpoints = []configv1.PowerVSServiceEndpoint{{
226+
Name: string(configv1.IBMCloudServiceCOS),
227+
URL: "test-cos.random.local",
228+
}, {
229+
Name: string(configv1.IBMCloudServiceDNSServices),
230+
URL: "test-dns.random.local",
231+
}}
232+
return p
233+
}(),
234+
valid: true,
235+
},
121236
}
122237
for _, tc := range cases {
123238
t.Run(tc.name, func(t *testing.T) {

0 commit comments

Comments
 (0)