コード例 #1
0
func sendCloudFormationResponse(customResourceRequest *AbstractCustomResourceRequest,
	results map[string]interface{},
	responseErr error,
	logger *logrus.Logger) error {

	parsedURL, parsedURLErr := url.ParseRequestURI(customResourceRequest.ResponseURL)
	if nil != parsedURLErr {
		return parsedURLErr
	}

	status := "FAILED"
	if nil == responseErr {
		status = "SUCCESS"
	}
	reasonText := ""
	if nil != responseErr {
		reasonText = fmt.Sprintf("%s. Details in CloudWatch Logs: %s : %s",
			responseErr.Error(),
			customResourceRequest.LogGroupName,
			customResourceRequest.LogStreamName)
	} else {
		reasonText = fmt.Sprintf("Details in CloudWatch Logs: %s : %s",
			customResourceRequest.LogGroupName,
			customResourceRequest.LogStreamName)
	}

	responseData := map[string]interface{}{
		"Status":             status,
		"Reason":             reasonText,
		"PhysicalResourceId": customResourceRequest.PhysicalResourceID,
		"StackId":            customResourceRequest.StackID,
		"RequestId":          customResourceRequest.RequestID,
		"LogicalResourceId":  customResourceRequest.LogicalResourceID,
	}
	if nil != responseErr {
		responseData["Data"] = map[string]interface{}{
			"Error": responseErr,
		}
	} else if nil != results {
		responseData["Data"] = results
	} else {
		responseData["Data"] = map[string]interface{}{}
	}

	logger.WithFields(logrus.Fields{
		"ResponsePayload": responseData,
	}).Debug("Response Info")

	jsonData, jsonError := json.Marshal(responseData)
	if nil != jsonError {
		return jsonError
	}

	responseBuffer := strings.NewReader(string(jsonData))
	req, httpErr := http.NewRequest("PUT", customResourceRequest.ResponseURL, responseBuffer)

	if nil != httpErr {
		return httpErr
	}
	// Need to use the Opaque field b/c Go will parse inline encoded values
	// which are supposed to be roundtripped to AWS.
	// Ref: https://tools.ietf.org/html/rfc3986#section-2.2
	// Ref: https://golang.org/pkg/net/url/#URL
	req.URL = &url.URL{
		Scheme:   parsedURL.Scheme,
		Host:     parsedURL.Host,
		Opaque:   parsedURL.RawPath,
		RawQuery: parsedURL.RawQuery,
	}
	logger.WithFields(logrus.Fields{
		"URL": req.URL,
	}).Debug("Created URL response")

	// Although it seems reasonable to set the Content-Type to "application/json" - don't.
	// The Content-Type must be an empty string in order for the
	// AWS Signature checker to pass.
	// Ref: http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-lambda-function-code.html
	req.Header.Set("Content-Type", "")

	client := &http.Client{}
	resp, httpErr := client.Do(req)
	if httpErr != nil {
		return httpErr
	}
	logger.WithFields(logrus.Fields{
		"LogicalResourceId":  customResourceRequest.LogicalResourceID,
		"Result":             responseData["Status"],
		"ResponseStatusCode": resp.StatusCode,
	}).Info("Sent CloudFormation response")

	if resp.StatusCode < 200 || resp.StatusCode > 299 {
		body, bodyErr := ioutil.ReadAll(resp.Body)
		if bodyErr != nil {
			logger.Warn("Unable to read body: " + bodyErr.Error())
			body = []byte{}
		}
		return fmt.Errorf("Error sending response: %d. Data: %s", resp.StatusCode, string(body))
	}
	defer resp.Body.Close()
	return nil
}