2019-10-16 06:29:17 +03:00
package frontend
import (
2019-10-17 02:43:39 +03:00
"crypto/rand"
"crypto/rsa"
2019-10-16 06:29:17 +03:00
"encoding/json"
2019-11-28 19:31:37 +03:00
"fmt"
2019-10-16 06:29:17 +03:00
"io/ioutil"
2019-10-17 02:43:39 +03:00
"math/big"
2019-10-16 06:29:17 +03:00
"net/http"
2019-11-28 00:18:49 +03:00
"github.com/Azure/go-autorest/autorest/azure"
2019-10-16 06:29:17 +03:00
"github.com/gorilla/mux"
uuid "github.com/satori/go.uuid"
"github.com/sirupsen/logrus"
"github.com/jim-minter/rp/pkg/api"
"github.com/jim-minter/rp/pkg/database/cosmosdb"
)
func ( f * frontend ) putOrPatchOpenShiftCluster ( w http . ResponseWriter , r * http . Request ) {
log := r . Context ( ) . Value ( contextKeyLog ) . ( * logrus . Entry )
vars := mux . Vars ( r )
toExternal , found := api . APIs [ api . APIVersionType { APIVersion : r . URL . Query ( ) . Get ( "api-version" ) , Type : "OpenShiftCluster" } ]
if ! found {
2019-11-26 16:22:00 +03:00
api . WriteError ( w , http . StatusNotFound , api . CloudErrorCodeInvalidResourceType , "" , "The resource type '%s' could not be found in the namespace '%s' for api version '%s'." , vars [ "resourceType" ] , vars [ "resourceProviderNamespace" ] , r . URL . Query ( ) . Get ( "api-version" ) )
2019-10-16 06:29:17 +03:00
return
}
if r . Header . Get ( "Content-Type" ) != "application/json" {
2019-11-26 16:22:00 +03:00
api . WriteError ( w , http . StatusUnsupportedMediaType , api . CloudErrorCodeUnsupportedMediaType , "" , "The content media type '%s' is not supported. Only 'application/json' is supported." , r . Header . Get ( "Content-Type" ) )
2019-10-16 06:29:17 +03:00
return
}
body , err := ioutil . ReadAll ( http . MaxBytesReader ( w , r . Body , 1048576 ) )
if err != nil {
2019-11-26 16:22:00 +03:00
api . WriteError ( w , http . StatusUnsupportedMediaType , api . CloudErrorCodeInvalidResource , "" , "The resource definition is invalid." )
2019-10-16 06:29:17 +03:00
return
}
var b [ ] byte
2019-11-12 19:34:18 +03:00
var created bool
2019-10-16 06:29:17 +03:00
err = cosmosdb . RetryOnPreconditionFailed ( func ( ) error {
2019-11-12 19:34:18 +03:00
b , created , err = f . _putOrPatchOpenShiftCluster ( & request {
2019-11-21 05:32:34 +03:00
context : r . Context ( ) ,
2019-10-16 06:29:17 +03:00
method : r . Method ,
resourceID : r . URL . Path ,
resourceName : vars [ "resourceName" ] ,
resourceType : vars [ "resourceProviderNamespace" ] + "/" + vars [ "resourceType" ] ,
body : body ,
toExternal : toExternal ,
} )
return err
} )
if err != nil {
switch err := err . ( type ) {
case * api . CloudError :
2019-11-26 16:22:00 +03:00
api . WriteCloudError ( w , err )
2019-10-16 06:29:17 +03:00
default :
log . Error ( err )
2019-11-26 16:22:00 +03:00
api . WriteError ( w , http . StatusInternalServerError , api . CloudErrorCodeInternalServerError , "" , "Internal server error." )
2019-10-16 06:29:17 +03:00
}
return
}
2019-11-12 19:34:18 +03:00
if created {
w . WriteHeader ( http . StatusCreated )
}
2019-10-16 06:29:17 +03:00
w . Write ( b )
w . Write ( [ ] byte { '\n' } )
}
2019-11-12 19:34:18 +03:00
func ( f * frontend ) _putOrPatchOpenShiftCluster ( r * request ) ( [ ] byte , bool , error ) {
2019-11-28 00:18:49 +03:00
originalPath := r . context . Value ( contextKeyOriginalPath ) . ( string )
originalR , err := azure . ParseResourceID ( originalPath )
if err != nil {
return nil , false , err
}
2019-11-28 00:39:22 +03:00
doc , err := f . db . OpenShiftClusters . Get ( api . Key ( r . resourceID ) )
2019-10-18 22:01:46 +03:00
if err != nil && ! cosmosdb . IsErrorStatusCode ( err , http . StatusNotFound ) {
2019-11-12 19:34:18 +03:00
return nil , false , err
2019-10-16 06:29:17 +03:00
}
isCreate := doc == nil
var external api . External
if isCreate {
doc = & api . OpenShiftClusterDocument {
ID : uuid . NewV4 ( ) . String ( ) ,
}
external = r . toExternal ( & api . OpenShiftCluster {
2019-11-28 00:18:49 +03:00
ID : originalPath ,
Name : originalR . ResourceName ,
Type : originalR . ResourceType ,
2019-10-16 06:29:17 +03:00
Properties : api . Properties {
2019-11-28 19:31:37 +03:00
ProvisioningState : api . ProvisioningStateCreating ,
2019-10-16 06:29:17 +03:00
} ,
} )
} else {
2019-11-28 20:32:24 +03:00
err = f . validateSubscriptionState ( doc , api . SubscriptionStateRegistered )
if err != nil {
return nil , false , err
}
2019-11-28 17:11:57 +03:00
err = validateTerminalProvisioningState ( doc . OpenShiftCluster . Properties . ProvisioningState )
2019-10-16 06:29:17 +03:00
if err != nil {
2019-11-12 19:34:18 +03:00
return nil , false , err
2019-10-16 06:29:17 +03:00
}
2019-11-28 19:31:37 +03:00
if doc . OpenShiftCluster . Properties . ProvisioningState == api . ProvisioningStateFailed {
switch doc . OpenShiftCluster . Properties . FailedProvisioningState {
case api . ProvisioningStateCreating :
2019-11-28 19:41:23 +03:00
return nil , false , api . NewCloudError ( http . StatusBadRequest , api . CloudErrorCodeRequestNotAllowed , "" , "Request is not allowed on cluster whose creation failed. Delete the cluster." )
2019-11-28 19:31:37 +03:00
case api . ProvisioningStateUpdating :
// allow
case api . ProvisioningStateDeleting :
return nil , false , api . NewCloudError ( http . StatusBadRequest , api . CloudErrorCodeRequestNotAllowed , "" , "Request is not allowed on cluster whose deletion failed. Delete the cluster." )
default :
return nil , false , fmt . Errorf ( "unexpected failedProvisioningState %q" , doc . OpenShiftCluster . Properties . FailedProvisioningState )
}
2019-11-28 17:11:57 +03:00
}
2019-11-28 19:31:37 +03:00
doc . OpenShiftCluster . Properties . ProvisioningState = api . ProvisioningStateUpdating
2019-10-16 06:29:17 +03:00
switch r . method {
case http . MethodPut :
external = r . toExternal ( & api . OpenShiftCluster {
2019-11-28 00:18:49 +03:00
ID : doc . OpenShiftCluster . ID ,
Name : doc . OpenShiftCluster . Name ,
Type : doc . OpenShiftCluster . Type ,
2019-10-16 06:29:17 +03:00
Properties : api . Properties {
ProvisioningState : doc . OpenShiftCluster . Properties . ProvisioningState ,
} ,
} )
case http . MethodPatch :
external = r . toExternal ( doc . OpenShiftCluster )
}
}
err = json . Unmarshal ( r . body , & external )
if err != nil {
2019-11-12 19:34:18 +03:00
return nil , false , api . NewCloudError ( http . StatusBadRequest , api . CloudErrorCodeInvalidRequestContent , "" , "The request content was invalid and could not be deserialized: %q." , err )
2019-10-16 06:29:17 +03:00
}
2019-11-21 05:32:34 +03:00
err = external . Validate ( r . context , r . resourceID , doc . OpenShiftCluster )
2019-10-16 06:29:17 +03:00
if err != nil {
2019-11-12 19:34:18 +03:00
return nil , false , err
2019-10-16 06:29:17 +03:00
}
if doc . OpenShiftCluster == nil {
2019-11-28 00:18:49 +03:00
doc . OpenShiftCluster = & api . OpenShiftCluster { }
2019-10-16 06:29:17 +03:00
}
2019-11-28 00:18:49 +03:00
oldID , oldName , oldType := doc . OpenShiftCluster . ID , doc . OpenShiftCluster . Name , doc . OpenShiftCluster . Type
2019-10-16 06:29:17 +03:00
external . ToInternal ( doc . OpenShiftCluster )
2019-11-28 00:18:49 +03:00
doc . OpenShiftCluster . ID , doc . OpenShiftCluster . Name , doc . OpenShiftCluster . Type = oldID , oldName , oldType
2019-10-16 06:29:17 +03:00
if isCreate {
2019-11-28 00:18:49 +03:00
doc . OpenShiftCluster . Key = api . Key ( r . resourceID )
2019-11-28 19:41:23 +03:00
doc . OpenShiftCluster . Properties . Install = & api . Install { }
2019-11-28 00:18:49 +03:00
// TODO: ResourceGroup should be exposed in external API
2019-10-17 02:43:39 +03:00
doc . OpenShiftCluster . Properties . ResourceGroup = doc . OpenShiftCluster . Name
2019-11-26 21:07:46 +03:00
doc . OpenShiftCluster . Properties . DomainName = uuid . NewV4 ( ) . String ( )
2019-11-29 03:24:01 +03:00
doc . OpenShiftCluster . Properties . InfraID = "aro"
2019-10-17 02:43:39 +03:00
doc . OpenShiftCluster . Properties . SSHKey , err = rsa . GenerateKey ( rand . Reader , 2048 )
if err != nil {
2019-11-12 19:34:18 +03:00
return nil , false , err
2019-10-17 02:43:39 +03:00
}
doc . OpenShiftCluster . Properties . StorageSuffix , err = randomLowerCaseAlphanumericString ( 5 )
if err != nil {
2019-11-12 19:34:18 +03:00
return nil , false , err
2019-10-17 02:43:39 +03:00
}
2019-11-28 00:39:22 +03:00
doc , err = f . db . OpenShiftClusters . Create ( doc )
2019-10-16 06:29:17 +03:00
} else {
2019-11-28 00:39:22 +03:00
doc , err = f . db . OpenShiftClusters . Update ( doc )
2019-10-16 06:29:17 +03:00
}
if err != nil {
2019-11-12 19:34:18 +03:00
return nil , false , err
2019-10-16 06:29:17 +03:00
}
2019-11-18 06:07:44 +03:00
doc . OpenShiftCluster . Properties . ServicePrincipalProfile . ClientSecret = ""
2019-10-16 06:29:17 +03:00
2019-11-12 19:34:18 +03:00
b , err := json . MarshalIndent ( r . toExternal ( doc . OpenShiftCluster ) , "" , " " )
if err != nil {
return nil , false , err
}
return b , isCreate , nil
2019-10-16 06:29:17 +03:00
}
2019-10-17 02:43:39 +03:00
func randomLowerCaseAlphanumericString ( n int ) ( string , error ) {
return randomString ( "abcdefghijklmnopqrstuvwxyz0123456789" , n )
}
func randomString ( letterBytes string , n int ) ( string , error ) {
b := make ( [ ] byte , n )
for i := range b {
o , err := rand . Int ( rand . Reader , big . NewInt ( int64 ( len ( letterBytes ) ) ) )
if err != nil {
return "" , err
}
b [ i ] = letterBytes [ o . Int64 ( ) ]
}
return string ( b ) , nil
}