ARO-RP/pkg/frontend/openshiftcluster_putorpatch.go

213 строки
6.6 KiB
Go
Исходник Обычный вид История

2019-10-16 06:29:17 +03:00
package frontend
import (
"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"
"math/big"
2019-10-16 06:29:17 +03:00
"net/http"
"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 {
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" {
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 {
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:
api.WriteCloudError(w, err)
2019-10-16 06:29:17 +03:00
default:
log.Error(err)
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) {
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))
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{
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
}
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:
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 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{
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 {
doc.OpenShiftCluster = &api.OpenShiftCluster{}
2019-10-16 06:29:17 +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)
doc.OpenShiftCluster.ID, doc.OpenShiftCluster.Name, doc.OpenShiftCluster.Type = oldID, oldName, oldType
2019-10-16 06:29:17 +03:00
if isCreate {
doc.OpenShiftCluster.Key = api.Key(r.resourceID)
doc.OpenShiftCluster.Properties.Install = &api.Install{}
// TODO: ResourceGroup should be exposed in external API
doc.OpenShiftCluster.Properties.ResourceGroup = doc.OpenShiftCluster.Name
2019-11-26 21:07:46 +03:00
doc.OpenShiftCluster.Properties.DomainName = uuid.NewV4().String()
doc.OpenShiftCluster.Properties.InfraID = "aro"
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
}
doc.OpenShiftCluster.Properties.StorageSuffix, err = randomLowerCaseAlphanumericString(5)
if err != nil {
2019-11-12 19:34:18 +03:00
return nil, false, err
}
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
}
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
}