зеркало из https://github.com/Azure/ARO-RP.git
Unique Client ID base implementation (#122)
Unique Client ID base implementation
This commit is contained in:
Родитель
b2ae5b2eff
Коммит
3ed813c823
|
@ -89,6 +89,11 @@
|
|||
"paths": [
|
||||
"/clusterResourceGroupIdKey"
|
||||
]
|
||||
},
|
||||
{
|
||||
"paths": [
|
||||
"/clientIdKey"
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
|
@ -15,7 +15,8 @@
|
|||
"type": "string"
|
||||
},
|
||||
"keyvaultPrefix": {
|
||||
"type": "string"
|
||||
"type": "string",
|
||||
"maxLength": 20
|
||||
},
|
||||
"rpServicePrincipalId": {
|
||||
"type": "string"
|
||||
|
@ -38,7 +39,7 @@
|
|||
},
|
||||
"accessPolicies": []
|
||||
},
|
||||
"name": "[concat(parameters('keyvaultPrefix'), '-clusters')]",
|
||||
"name": "[concat(parameters('keyvaultPrefix'), '-cls')]",
|
||||
"type": "Microsoft.KeyVault/vaults",
|
||||
"location": "[resourceGroup().location]",
|
||||
"tags": {
|
||||
|
@ -81,7 +82,7 @@
|
|||
}
|
||||
]
|
||||
},
|
||||
"name": "[concat(parameters('keyvaultPrefix'), '-service')]",
|
||||
"name": "[concat(parameters('keyvaultPrefix'), '-svc')]",
|
||||
"type": "Microsoft.KeyVault/vaults",
|
||||
"location": "[resourceGroup().location]",
|
||||
"tags": {
|
||||
|
|
|
@ -34,7 +34,8 @@
|
|||
"type": "string"
|
||||
},
|
||||
"keyvaultPrefix": {
|
||||
"type": "string"
|
||||
"type": "string",
|
||||
"maxLength": 20
|
||||
},
|
||||
"mdmCertificate": {
|
||||
"type": "string"
|
||||
|
@ -276,7 +277,7 @@
|
|||
},
|
||||
"accessPolicies": "[concat(variables('clustersKeyvaultAccessPolicies'), parameters('extraKeyvaultAccessPolicies'))]"
|
||||
},
|
||||
"name": "[concat(parameters('keyvaultPrefix'), '-clusters')]",
|
||||
"name": "[concat(parameters('keyvaultPrefix'), '-cls')]",
|
||||
"type": "Microsoft.KeyVault/vaults",
|
||||
"location": "[resourceGroup().location]",
|
||||
"tags": {
|
||||
|
@ -293,7 +294,7 @@
|
|||
},
|
||||
"accessPolicies": "[concat(variables('serviceKeyvaultAccessPolicies'), parameters('extraKeyvaultAccessPolicies'))]"
|
||||
},
|
||||
"name": "[concat(parameters('keyvaultPrefix'), '-service')]",
|
||||
"name": "[concat(parameters('keyvaultPrefix'), '-svc')]",
|
||||
"type": "Microsoft.KeyVault/vaults",
|
||||
"location": "[resourceGroup().location]",
|
||||
"tags": {
|
||||
|
@ -520,6 +521,11 @@
|
|||
"paths": [
|
||||
"/clusterResourceGroupIdKey"
|
||||
]
|
||||
},
|
||||
{
|
||||
"paths": [
|
||||
"/clientIdKey"
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
|
@ -43,7 +43,7 @@ var (
|
|||
CloudErrorCodeInvalidParameter = "InvalidParameter"
|
||||
CloudErrorCodeInvalidRequestContent = "InvalidRequestContent"
|
||||
CloudErrorCodeInvalidResource = "InvalidResource"
|
||||
CloudErrorCodeInvalidResourceGroup = "InvalidResourceGroup"
|
||||
CloudErrorCodeDuplicateResourceGroup = "DuplicateResourceGroup"
|
||||
CloudErrorCodeInvalidResourceNamespace = "InvalidResourceNamespace"
|
||||
CloudErrorCodeInvalidResourceType = "InvalidResourceType"
|
||||
CloudErrorCodeInvalidSubscriptionID = "InvalidSubscriptionID"
|
||||
|
@ -64,6 +64,7 @@ var (
|
|||
CloudErrorCodeInvalidServicePrincipalPermissions = "InvalidServicePrincipalPermissions"
|
||||
CloudErrorCodeInvalidLocation = "InvalidLocation"
|
||||
CloudErrorCodeInvalidOperationID = "InvalidOperationID"
|
||||
CloudErrorCodeDuplicateClientID = "DuplicateClientID"
|
||||
)
|
||||
|
||||
// NewCloudError returns a new CloudError
|
||||
|
|
|
@ -28,6 +28,7 @@ type OpenShiftClusterDocument struct {
|
|||
Key string `json:"key,omitempty"`
|
||||
PartitionKey string `json:"partitionKey,omitempty"`
|
||||
ClusterResourceGroupIDKey string `json:"clusterResourceGroupIdKey,omitempty"`
|
||||
ClientIDKey string `json:"clientIdKey,omitempty"`
|
||||
|
||||
Bucket int `json:"bucket,omitempty"`
|
||||
|
||||
|
|
|
@ -36,6 +36,8 @@ type OpenShiftClusters interface {
|
|||
Dequeue(context.Context) (*api.OpenShiftClusterDocument, error)
|
||||
Lease(context.Context, string) (*api.OpenShiftClusterDocument, error)
|
||||
EndLease(context.Context, string, api.ProvisioningState, api.ProvisioningState) (*api.OpenShiftClusterDocument, error)
|
||||
GetByClientID(ctx context.Context, partitionKey, clientID string) (*api.OpenShiftClusterDocuments, error)
|
||||
GetByClusterResourceGroupID(ctx context.Context, partitionKey, resourceGroupID string) (*api.OpenShiftClusterDocuments, error)
|
||||
}
|
||||
|
||||
// NewOpenShiftClusters returns a new OpenShiftClusters
|
||||
|
@ -293,3 +295,35 @@ func (c *openShiftClusters) partitionKey(key string) (string, error) {
|
|||
r, err := azure.ParseResourceID(key)
|
||||
return r.SubscriptionID, err
|
||||
}
|
||||
|
||||
func (c *openShiftClusters) GetByClientID(ctx context.Context, partitionKey, clientID string) (*api.OpenShiftClusterDocuments, error) {
|
||||
docs, err := c.c.QueryAll(ctx, partitionKey, &cosmosdb.Query{
|
||||
Query: "SELECT * FROM OpenShiftClusters doc WHERE doc.clientIdKey = @clientID",
|
||||
Parameters: []cosmosdb.Parameter{
|
||||
{
|
||||
Name: "@clientID",
|
||||
Value: clientID,
|
||||
},
|
||||
},
|
||||
}, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return docs, nil
|
||||
}
|
||||
|
||||
func (c *openShiftClusters) GetByClusterResourceGroupID(ctx context.Context, partitionKey, resourceGroupID string) (*api.OpenShiftClusterDocuments, error) {
|
||||
docs, err := c.c.QueryAll(ctx, partitionKey, &cosmosdb.Query{
|
||||
Query: "SELECT * FROM OpenShiftClusters doc WHERE doc.clusterResourceGroupIdKey = @resourceGroupID",
|
||||
Parameters: []cosmosdb.Parameter{
|
||||
{
|
||||
Name: "@resourceGroupID",
|
||||
Value: resourceGroupID,
|
||||
},
|
||||
},
|
||||
}, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return docs, nil
|
||||
}
|
||||
|
|
|
@ -8,4 +8,6 @@ const (
|
|||
KeyVaultTagName = "vault"
|
||||
ClustersKeyVaultTagValue = "clusters"
|
||||
ServiceKeyVaultTagValue = "service"
|
||||
kvClusterSuffix = "-cls"
|
||||
kvServiceSuffix = "-svc"
|
||||
)
|
||||
|
|
|
@ -543,7 +543,7 @@ func (g *generator) clustersKeyvault() *arm.Resource {
|
|||
},
|
||||
AccessPolicies: &[]mgmtkeyvault.AccessPolicyEntry{},
|
||||
},
|
||||
Name: to.StringPtr("[concat(parameters('keyvaultPrefix'), '-clusters')]"),
|
||||
Name: to.StringPtr("[concat(parameters('keyvaultPrefix'), '" + kvClusterSuffix + "')]"),
|
||||
Type: to.StringPtr("Microsoft.KeyVault/vaults"),
|
||||
Location: to.StringPtr("[resourceGroup().location]"),
|
||||
Tags: map[string]*string{
|
||||
|
@ -579,7 +579,7 @@ func (g *generator) serviceKeyvault() *arm.Resource {
|
|||
},
|
||||
AccessPolicies: &[]mgmtkeyvault.AccessPolicyEntry{},
|
||||
},
|
||||
Name: to.StringPtr("[concat(parameters('keyvaultPrefix'), '-service')]"),
|
||||
Name: to.StringPtr("[concat(parameters('keyvaultPrefix'), '" + kvServiceSuffix + "')]"),
|
||||
Type: to.StringPtr("Microsoft.KeyVault/vaults"),
|
||||
Location: to.StringPtr("[resourceGroup().location]"),
|
||||
Tags: map[string]*string{
|
||||
|
@ -755,6 +755,11 @@ func (g *generator) database(databaseName string, addDependsOn bool) []*arm.Reso
|
|||
"/clusterResourceGroupIdKey",
|
||||
},
|
||||
},
|
||||
{
|
||||
Paths: &[]string{
|
||||
"/clientIdKey",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
|
@ -915,6 +920,17 @@ func (g *generator) template() *arm.Template {
|
|||
typ = "securestring"
|
||||
}
|
||||
t.Parameters[param] = &arm.TemplateParameter{Type: typ}
|
||||
if param == "keyvaultPrefix" {
|
||||
t.Parameters[param] = &arm.TemplateParameter{
|
||||
Type: typ,
|
||||
MaxLength: 24 - func(a, b int) int {
|
||||
if a > b {
|
||||
return a
|
||||
}
|
||||
return b
|
||||
}(len(kvClusterSuffix), len(kvServiceSuffix)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if g.production {
|
||||
|
|
|
@ -130,6 +130,7 @@ func (f *frontend) _putOrPatchOpenShiftCluster(ctx context.Context, r *http.Requ
|
|||
|
||||
if isCreate {
|
||||
doc.ClusterResourceGroupIDKey = strings.ToLower(doc.OpenShiftCluster.Properties.ClusterProfile.ResourceGroupID)
|
||||
doc.ClientIDKey = strings.ToLower(doc.OpenShiftCluster.Properties.ServicePrincipalProfile.ClientID)
|
||||
doc.OpenShiftCluster.Properties.ProvisioningState = api.ProvisioningStateCreating
|
||||
doc.OpenShiftCluster.Properties.ClusterProfile.Version = "4.3.0"
|
||||
|
||||
|
@ -166,7 +167,7 @@ func (f *frontend) _putOrPatchOpenShiftCluster(ctx context.Context, r *http.Requ
|
|||
if isCreate {
|
||||
newdoc, err := f.db.OpenShiftClusters.Create(ctx, doc)
|
||||
if cosmosdb.IsErrorStatusCode(err, http.StatusPreconditionFailed) {
|
||||
return nil, api.NewCloudError(http.StatusBadRequest, api.CloudErrorCodeInvalidResourceGroup, "", "The provided resource group '%s' already contains a cluster.", doc.OpenShiftCluster.Properties.ClusterProfile.ResourceGroupID)
|
||||
return nil, f.validateOpenShiftUniqueKey(ctx, doc)
|
||||
}
|
||||
doc = newdoc
|
||||
} else {
|
||||
|
|
|
@ -524,6 +524,114 @@ func TestPutOrPatchOpenShiftCluster(t *testing.T) {
|
|||
wantStatusCode: http.StatusBadRequest,
|
||||
wantError: "400: RequestNotAllowed: : Request is not allowed on cluster whose deletion failed. Delete the cluster.",
|
||||
},
|
||||
{
|
||||
name: "creating cluster failing when provided cluster resource group already contains a cluster",
|
||||
resourceID: fmt.Sprintf("/subscriptions/%s/resourcegroups/resourceGroup/providers/Microsoft.RedHatOpenShift/openshiftClusters/resourceName", mockSubID),
|
||||
request: func(oc *v20191231preview.OpenShiftCluster) {
|
||||
oc.Properties.ServicePrincipalProfile.ClientID = mockSubID
|
||||
oc.Properties.ClusterProfile.ResourceGroupID = fmt.Sprintf("/subscriptions/%s/resourcegroups/aro-vjb21wca", mockSubID)
|
||||
},
|
||||
mocks: func(tt *test, asyncOperations *mock_database.MockAsyncOperations, openShiftClusters *mock_database.MockOpenShiftClusters) {
|
||||
openShiftClusters.EXPECT().
|
||||
Get(gomock.Any(), strings.ToLower(tt.resourceID)).
|
||||
Return(nil, &cosmosdb.Error{StatusCode: http.StatusNotFound})
|
||||
|
||||
expectAsyncOperationDocumentCreate(asyncOperations, strings.ToLower(tt.resourceID), api.ProvisioningStateCreating)
|
||||
|
||||
clusterdoc := &api.OpenShiftClusterDocument{
|
||||
Key: strings.ToLower(tt.resourceID),
|
||||
Bucket: 1,
|
||||
OpenShiftCluster: &api.OpenShiftCluster{
|
||||
ID: tt.resourceID,
|
||||
Name: "resourceName",
|
||||
Type: "Microsoft.RedHatOpenShift/openshiftClusters",
|
||||
Properties: api.Properties{
|
||||
ProvisioningState: api.ProvisioningStateCreating,
|
||||
ClusterProfile: api.ClusterProfile{
|
||||
Version: "4.3.0",
|
||||
ResourceGroupID: fmt.Sprintf("/subscriptions/%s/resourcegroups/aro-vjb21wca", mockSubID),
|
||||
},
|
||||
ServicePrincipalProfile: api.ServicePrincipalProfile{
|
||||
TenantID: "11111111-1111-1111-1111-111111111111",
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
openShiftClusters.EXPECT().
|
||||
Create(gomock.Any(), gomock.Any()).
|
||||
Return(clusterdoc, &cosmosdb.Error{StatusCode: http.StatusPreconditionFailed})
|
||||
openShiftClusters.EXPECT().
|
||||
GetByClientID(gomock.Any(), clusterdoc.PartitionKey, mockSubID).
|
||||
Return(&api.OpenShiftClusterDocuments{
|
||||
Count: 0,
|
||||
OpenShiftClusterDocuments: []*api.OpenShiftClusterDocument{},
|
||||
}, nil)
|
||||
openShiftClusters.EXPECT().
|
||||
GetByClusterResourceGroupID(gomock.Any(), clusterdoc.PartitionKey, fmt.Sprintf("/subscriptions/%s/resourcegroups/aro-vjb21wca", mockSubID)).
|
||||
Return(&api.OpenShiftClusterDocuments{
|
||||
Count: 1,
|
||||
OpenShiftClusterDocuments: []*api.OpenShiftClusterDocument{
|
||||
&api.OpenShiftClusterDocument{
|
||||
ClusterResourceGroupIDKey: fmt.Sprintf("/subscriptions/%s/resourcegroups/aro-vjb21wca", mockSubID),
|
||||
},
|
||||
},
|
||||
}, nil)
|
||||
},
|
||||
wantAsync: true,
|
||||
wantStatusCode: http.StatusBadRequest,
|
||||
wantError: fmt.Sprintf("400: DuplicateResourceGroup: : The provided resource group '/subscriptions/%s/resourcegroups/aro-vjb21wca' already contains a cluster.", mockSubID),
|
||||
},
|
||||
{
|
||||
name: "creating cluster failing when provided client ID is not unique",
|
||||
resourceID: fmt.Sprintf("/subscriptions/%s/resourcegroups/resourceGroup/providers/Microsoft.RedHatOpenShift/openshiftClusters/resourceName", mockSubID),
|
||||
request: func(oc *v20191231preview.OpenShiftCluster) {
|
||||
oc.Properties.ServicePrincipalProfile.ClientID = mockSubID
|
||||
},
|
||||
mocks: func(tt *test, asyncOperations *mock_database.MockAsyncOperations, openShiftClusters *mock_database.MockOpenShiftClusters) {
|
||||
openShiftClusters.EXPECT().
|
||||
Get(gomock.Any(), strings.ToLower(tt.resourceID)).
|
||||
Return(nil, &cosmosdb.Error{StatusCode: http.StatusNotFound})
|
||||
|
||||
expectAsyncOperationDocumentCreate(asyncOperations, strings.ToLower(tt.resourceID), api.ProvisioningStateCreating)
|
||||
|
||||
clusterdoc := &api.OpenShiftClusterDocument{
|
||||
Key: strings.ToLower(tt.resourceID),
|
||||
Bucket: 1,
|
||||
OpenShiftCluster: &api.OpenShiftCluster{
|
||||
ID: tt.resourceID,
|
||||
Name: "resourceName",
|
||||
Type: "Microsoft.RedHatOpenShift/openshiftClusters",
|
||||
Properties: api.Properties{
|
||||
ProvisioningState: api.ProvisioningStateCreating,
|
||||
ClusterProfile: api.ClusterProfile{
|
||||
Version: "4.3.0",
|
||||
},
|
||||
ServicePrincipalProfile: api.ServicePrincipalProfile{
|
||||
TenantID: "11111111-1111-1111-1111-111111111111",
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
openShiftClusters.EXPECT().
|
||||
Create(gomock.Any(), gomock.Any()).
|
||||
Return(clusterdoc, &cosmosdb.Error{StatusCode: http.StatusPreconditionFailed})
|
||||
openShiftClusters.EXPECT().
|
||||
GetByClientID(gomock.Any(), clusterdoc.PartitionKey, mockSubID).
|
||||
Return(&api.OpenShiftClusterDocuments{
|
||||
Count: 1,
|
||||
OpenShiftClusterDocuments: []*api.OpenShiftClusterDocument{
|
||||
&api.OpenShiftClusterDocument{
|
||||
ClientIDKey: mockSubID,
|
||||
},
|
||||
},
|
||||
}, nil)
|
||||
},
|
||||
wantAsync: true,
|
||||
wantStatusCode: http.StatusBadRequest,
|
||||
wantError: fmt.Sprintf("400: DuplicateClientID: : The provided client ID '%s' is already in use by a cluster.", mockSubID),
|
||||
},
|
||||
} {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
defer cli.CloseIdleConnections()
|
||||
|
|
|
@ -44,3 +44,22 @@ func (f *frontend) validateSubscriptionState(ctx context.Context, key string, al
|
|||
|
||||
return nil, api.NewCloudError(http.StatusBadRequest, api.CloudErrorCodeInvalidSubscriptionState, "", "Request is not allowed in subscription in state '%s'.", doc.Subscription.State)
|
||||
}
|
||||
|
||||
// validateOpenShiftUniqueKey returns which unique key if causing a 412 error
|
||||
func (f *frontend) validateOpenShiftUniqueKey(ctx context.Context, doc *api.OpenShiftClusterDocument) error {
|
||||
docs, err := f.db.OpenShiftClusters.GetByClientID(ctx, doc.PartitionKey, doc.ClientIDKey)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if docs.Count != 0 {
|
||||
return api.NewCloudError(http.StatusBadRequest, api.CloudErrorCodeDuplicateClientID, "", "The provided client ID '%s' is already in use by a cluster.", doc.OpenShiftCluster.Properties.ServicePrincipalProfile.ClientID)
|
||||
}
|
||||
docs, err = f.db.OpenShiftClusters.GetByClusterResourceGroupID(ctx, doc.PartitionKey, doc.ClusterResourceGroupIDKey)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if docs.Count != 0 {
|
||||
return api.NewCloudError(http.StatusBadRequest, api.CloudErrorCodeDuplicateResourceGroup, "", "The provided resource group '%s' already contains a cluster.", doc.OpenShiftCluster.Properties.ClusterProfile.ResourceGroupID)
|
||||
}
|
||||
return api.NewCloudError(http.StatusInternalServerError, api.CloudErrorCodeInternalServerError, "", "Internal server error.")
|
||||
}
|
||||
|
|
|
@ -298,6 +298,21 @@ func (mr *MockOpenShiftClustersMockRecorder) Update(arg0, arg1 interface{}) *gom
|
|||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Update", reflect.TypeOf((*MockOpenShiftClusters)(nil).Update), arg0, arg1)
|
||||
}
|
||||
|
||||
// ValidateUniqueKey mocks base method
|
||||
func (m *MockOpenShiftClusters) ValidateUniqueKey(arg0 context.Context, arg1, arg2, arg3 string) (*api.OpenShiftClusterDocuments, error) {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "ValidateUniqueKey", arg0, arg1, arg2, arg3)
|
||||
ret0, _ := ret[0].(*api.OpenShiftClusterDocuments)
|
||||
ret1, _ := ret[1].(error)
|
||||
return ret0, ret1
|
||||
}
|
||||
|
||||
// ValidateUniqueKey indicates an expected call of ValidateUniqueKey
|
||||
func (mr *MockOpenShiftClustersMockRecorder) ValidateUniqueKey(arg0, arg1, arg2, arg3 interface{}) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ValidateUniqueKey", reflect.TypeOf((*MockOpenShiftClusters)(nil).ValidateUniqueKey), arg0, arg1, arg2, arg3)
|
||||
}
|
||||
|
||||
// MockSubscriptions is a mock of Subscriptions interface
|
||||
type MockSubscriptions struct {
|
||||
ctrl *gomock.Controller
|
||||
|
|
Загрузка…
Ссылка в новой задаче