github.com/kyma-incubator/compass/components/director@v0.0.0-20230623144113-d764f56ff805/pkg/operation/api.go (about)

     1  /*
     2   * Copyright 2020 The Compass Authors
     3   *
     4   * Licensed under the Apache License, Version 2.0 (the "License");
     5   * you may not use this file except in compliance with the License.
     6   * You may obtain a copy of the License at
     7   *
     8   *     http://www.apache.org/licenses/LICENSE-2.0
     9   *
    10   * Unless required by applicable law or agreed to in writing, software
    11   * distributed under the License is distributed on an "AS IS" BASIS,
    12   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    13   * See the License for the specific language governing permissions and
    14   * limitations under the License.
    15   */
    16  
    17  package operation
    18  
    19  import (
    20  	"context"
    21  	"encoding/json"
    22  	"fmt"
    23  	"net/http"
    24  
    25  	"github.com/gorilla/mux"
    26  
    27  	"github.com/kyma-incubator/compass/components/director/internal/model"
    28  	"github.com/kyma-incubator/compass/components/director/pkg/apperrors"
    29  
    30  	"github.com/kyma-incubator/compass/components/director/pkg/persistence"
    31  
    32  	"github.com/kyma-incubator/compass/components/director/pkg/resource"
    33  
    34  	"github.com/kyma-incubator/compass/components/director/pkg/log"
    35  )
    36  
    37  // ResourceIDParam missing godoc
    38  const ResourceIDParam = "resource_id"
    39  
    40  // ResourceTypeParam missing godoc
    41  const ResourceTypeParam = "resource_type"
    42  
    43  // ResourceFetcherFunc defines a function which fetches a particular resource by tenant and resource ID
    44  type ResourceFetcherFunc func(ctx context.Context, tenantID, resourceID string) (model.Entity, error)
    45  
    46  // TenantLoaderFunc defines a function which fetches the tenant for a particular request
    47  type TenantLoaderFunc func(ctx context.Context) (string, error)
    48  
    49  type handler struct {
    50  	transact            persistence.Transactioner
    51  	resourceFetcherFunc ResourceFetcherFunc
    52  	tenantLoaderFunc    TenantLoaderFunc
    53  }
    54  
    55  // NewHandler creates a new handler struct associated with the Operations API
    56  func NewHandler(transact persistence.Transactioner, resourceFetcherFunc ResourceFetcherFunc, tenantLoaderFunc TenantLoaderFunc) *handler {
    57  	return &handler{
    58  		transact:            transact,
    59  		resourceFetcherFunc: resourceFetcherFunc,
    60  		tenantLoaderFunc:    tenantLoaderFunc,
    61  	}
    62  }
    63  
    64  // ServeHTTP handles the Operations API requests
    65  func (h *handler) ServeHTTP(writer http.ResponseWriter, request *http.Request) {
    66  	ctx := request.Context()
    67  
    68  	tenantID, err := h.tenantLoaderFunc(ctx)
    69  	if err != nil {
    70  		log.C(ctx).WithError(err).Errorf("An error occurred while retrieving tenant from context: %s", err.Error())
    71  		apperrors.WriteAppError(ctx, writer, apperrors.NewInternalError("Unable to determine tenant for request"), http.StatusInternalServerError)
    72  		return
    73  	}
    74  
    75  	routeVariables := mux.Vars(request)
    76  	resourceID := routeVariables[ResourceIDParam]
    77  	resourceType := routeVariables[ResourceTypeParam]
    78  
    79  	op := &Operation{
    80  		ResourceID:   resourceID,
    81  		ResourceType: resource.Type(resourceType),
    82  	}
    83  
    84  	log.C(ctx).Infof("Executing Operation API with resourceType: %s and resourceID: %s", op.ResourceType, op.ResourceID)
    85  
    86  	if err := op.Validate(); err != nil {
    87  		apperrors.WriteAppError(ctx, writer, apperrors.NewInvalidDataError("Unexpected resource type and/or GUID"), http.StatusBadRequest)
    88  		return
    89  	}
    90  
    91  	tx, err := h.transact.Begin()
    92  	if err != nil {
    93  		log.C(ctx).WithError(err).Errorf("An error occurred while opening db transaction: %s", err.Error())
    94  		apperrors.WriteAppError(ctx, writer, apperrors.NewInternalError("Unable to establish connection with database"), http.StatusInternalServerError)
    95  		return
    96  	}
    97  	defer h.transact.RollbackUnlessCommitted(ctx, tx)
    98  
    99  	ctx = persistence.SaveToContext(ctx, tx)
   100  
   101  	res, err := h.resourceFetcherFunc(ctx, tenantID, op.ResourceID)
   102  	if err != nil {
   103  		log.C(ctx).WithError(err).Errorf("An error occurred while fetching resource from database: %s", err.Error())
   104  
   105  		if apperrors.IsNotFoundError(err) {
   106  			apperrors.WriteAppError(ctx, writer, apperrors.NewNotFoundErrorWithMessage(resource.Application, op.ResourceID,
   107  				fmt.Sprintf("Operation for application with id %s not found", op.ResourceID)), http.StatusNotFound)
   108  			return
   109  		}
   110  
   111  		apperrors.WriteAppError(ctx, writer, apperrors.NewInternalError("Unable to execute database operation"), http.StatusInternalServerError)
   112  		return
   113  	}
   114  
   115  	if err := tx.Commit(); err != nil {
   116  		log.C(ctx).WithError(err).Errorf("An error occurred while closing database transaction: %s", err.Error())
   117  		apperrors.WriteAppError(ctx, writer, apperrors.NewInternalError("Unable to finalize database operation"), http.StatusInternalServerError)
   118  		return
   119  	}
   120  
   121  	opResponse := buildLastOperation(res)
   122  
   123  	err = json.NewEncoder(writer).Encode(opResponse)
   124  	if err != nil {
   125  		log.C(ctx).WithError(err).Errorf("An error occurred while encoding operation data: %v", err)
   126  	}
   127  }
   128  
   129  func buildLastOperation(resource model.Entity) *OperationResponse {
   130  	opResponse := &OperationResponse{
   131  		Operation: &Operation{
   132  			ResourceID:   resource.GetID(),
   133  			ResourceType: resource.GetType(),
   134  		},
   135  		Error: resource.GetError(),
   136  	}
   137  
   138  	opResponse.initializeOperationType(resource)
   139  	opResponse.initializeOperationStatus(resource)
   140  	opResponse.initializeCreationTime(resource)
   141  
   142  	return opResponse
   143  }