Sample to check if an ECS instance is stopped¶
This example demonstrates how to verify whether an Elastic Cloud Server (ECS) instance is in a stopped state using a custom rule.
Prerequisites¶
Before running this example, ensure you have the following:
An existing ECS instance that you want to check.
FunctionGraph function¶
# -*- coding:utf-8 -*-
import http.client
import json
import time
import requests
requests.packages.urllib3.disable_warnings()
"""
Get the policy resource representation for RMS.
"""
def get_policy_resource(domain_id, resource):
return {
"domain_id": domain_id,
"region_id": resource.get("region_id"),
"resource_id": resource.get("id"),
"resource_name": resource.get("name"),
"resource_provider": resource.get("provider"),
"resource_type": resource.get("type"),
}
"""
The evaluation result of a rule will be either Compliant or NonCompliant.
In this example, if the properties.status of a resource matches the specified ECSstatus,
NonCompliant is returned. Otherwise, Compliant is returned.
"""
def evaluate_compliance(logger, resource, parameter):
if resource.get("properties").get("status") == parameter.get("ECSstatus").get(
"value"
):
logger.info("NonCompliant")
return "NonCompliant"
else:
logger.info("Compliant")
return "Compliant"
"""
Update the policy state in RMS with the evaluation result.
"""
def update_policy_state(context, domain_id, evaluation):
logger = context.getLogger()
endpoint_url = context.getUserData("RMS_ENDPOINT_URL")
url = f"{endpoint_url}/v1/resource-manager/domains/{domain_id}/policy-states"
logger.info(
"Updating policy state with URL: %s, payload: %s", url, json.dumps(evaluation)
)
return requests.put(
url=url,
headers={"X-Auth-Token": context.getToken()},
json=evaluation,
verify=False,
)
"""
FunctionGraph handler to handle the incoming event and evaluate compliance for the resource.
"""
def handler(event, context):
logger = context.getLogger()
logger.debug("Invoking event: %s", json.dumps(event))
resource = event.get("invoking_event", {})
parameters = event.get("rule_parameter", {})
compliance_state = evaluate_compliance(logger, resource, parameters)
requests = {
"policy_resource": get_policy_resource(event.get("domain_id"), resource),
"trigger_type": event.get("trigger_type"),
"compliance_state": compliance_state,
"policy_assignment_id": event.get("policy_assignment_id"),
"policy_assignment_name": event.get("policy_assignment_name"),
"function_urn": event.get("function_urn"),
"evaluation_time": event.get("evaluation_time"),
"evaluation_hash": event.get("evaluation_hash"),
}
for retry in range(3):
response = update_policy_state(context, event.get("domain_id"), requests)
if response.status_code == http.client.TOO_MANY_REQUESTS:
logger.error("TOO_MANY_REQUESTS: retry again, attempt %d", retry + 1)
time.sleep(1)
else:
if response.status_code == http.client.OK:
logger.info("Update policyState successfully.")
else:
logger.error("Failed to update policyState with response: %s", response.json())
break
Note
The parameter ECSstatus is defined in the config deployment file in opentelekomcloud_rms_policy_assignment_v1.my_policy_assignment
parameters = {
ECSstatus = "\"SHUTOFF\""
}
Terraform deployment files¶
FunctionGraph deployment files¶
##########################################################
# Python event function to handle ECS stopped compliance
##########################################################
resource "opentelekomcloud_fgs_function_v2" "MyFunction" {
name = format("%s_%s", var.prefix, var.function_name)
app = "default"
handler = var.handler_name
runtime = "Python3.10"
agency = opentelekomcloud_identity_agency_v3.agency.name
code_type = "inline"
func_code = filebase64(format("${path.module}/../%s", "src/index.py"))
code_filename = "index.py"
description = var.description
memory_size = 128
timeout = 30
max_instance_num = 10
log_group_id = opentelekomcloud_lts_group_v2.MyLogGroup.id
log_group_name = opentelekomcloud_lts_group_v2.MyLogGroup.group_name
log_topic_id = opentelekomcloud_lts_stream_v2.MyLogStream.id
log_topic_name = opentelekomcloud_lts_stream_v2.MyLogStream.stream_name
# set some environment variables
user_data = jsonencode({
"RMS_ENDPOINT_URL" : "https://rms.eu-de.otc.t-systems.com"
})
tags = {
"app_group" = var.tag_app_group
}
}
output "MY_FUNCTION_URN" {
value = opentelekomcloud_fgs_function_v2.MyFunction.urn
}
output "MY_FUNCTION_VERSION" {
value = opentelekomcloud_fgs_function_v2.MyFunction.version
}
##################################################################
# Custom role to allow FunctionGraph to update RMS policy states
##################################################################
resource "opentelekomcloud_identity_role_v3" "role" {
display_name = format("%s-%s-role", var.prefix, var.function_name)
description = "Role for FunctionGraph to update RMS policy states"
display_layer = "project"
statement {
effect = "Allow"
action = [
"rms:policyStates:update"
]
}
}
##########################################################
# Agency for FunctionGraph
# Attention: Creating agency will take some time.
# Calls to function after creating agency will fail until
# agency is set up.
##########################################################
resource "opentelekomcloud_identity_agency_v3" "agency" {
depends_on = [opentelekomcloud_identity_role_v3.role]
delegated_domain_name = "op_svc_cff"
name = format("%s-%s-agency", var.prefix, var.function_name)
description = "Agency for FunctionGraph to access RMS"
project_role {
all_projects = true
# project = var.OTC_SDK_PROJECTNAME
roles = [
opentelekomcloud_identity_role_v3.role.display_name
]
}
}
##########################################################
# Create Log Group
##########################################################
resource "opentelekomcloud_lts_group_v2" "MyLogGroup" {
group_name = format("%s_%s_%s", var.prefix, var.function_name, "log_group")
ttl_in_days = 1
tags = {
"app_group" = var.tag_app_group
}
}
##########################################################
# Create Log Stream
##########################################################
resource "opentelekomcloud_lts_stream_v2" "MyLogStream" {
group_id = opentelekomcloud_lts_group_v2.MyLogGroup.id
stream_name = format("%s_%s_%s", var.prefix, var.function_name, "log_stream")
tags = {
"app_group" = var.tag_app_group
}
}
Config deployment files¶
#######################################################################
# Policy assignment for FunctionGraph to manage ECS stopped compliance
#######################################################################
resource "opentelekomcloud_rms_policy_assignment_v1" "my_policy_assignment" {
name = format("%s-%s", var.prefix, "Stopped_ECS_is_nonCompliant")
description = "Policy assignment for FunctionGraph"
custom_policy {
auth_type = "agency"
auth_value = {
agency_name = format("\"%s\"", opentelekomcloud_identity_agency_v3.config_agency.name)
}
function_urn = format("%s:%s", opentelekomcloud_fgs_function_v2.MyFunction.urn, opentelekomcloud_fgs_function_v2.MyFunction.version)
}
parameters = {
"ECSstatus": "\"SHUTOFF\""
}
period = ""
policy_filter {
# Specify the region where your ECS instance is located
region = "eu-de"
# ID of your ECS instance that you want to check for stopped compliance
resource_id = "cdb29bdd-1235-4e98-90d3-34bb77450393"
resource_provider = "ecs"
resource_type = "cloudservers"
tag_key = ""
tag_value = ""
}
status = "Enabled"
}
Note
In policy_filter you need to adapt the region and resource_id to match your ECS instance.
For getting values for resource_provider and resource_type see FAQ: Listing Cloud Services.
The Parameter ECSstatus configured in parameters = {} is used in the FunctionGraph function for comparison.
def evaluate_compliance() in index.py¶# ... if resource.get("properties").get("status") == parameter.get("ECSstatus").get("value"): # ...
For possible values for ECSstatus, see the FAQ: How to get Resource Details. Execute the script for every change in the ECS instance status and check value of resources.properties.status.
##################################################################
# Custom role to allow RMS to execute FunctionGraph functions
##################################################################
resource "opentelekomcloud_identity_role_v3" "fg-role" {
display_name = format("%s-%s-config-role", var.prefix, var.function_name)
description = "Role for RMS to execute FunctionGraph functions"
display_layer = "project"
statement {
effect = "Allow"
action = [
"functiongraph:function:invokeAsync",
"functiongraph:function:getConfig"
]
resource = [
format("FunctionGraph:*:*:function:%s/%s",
opentelekomcloud_fgs_function_v2.MyFunction.app,
opentelekomcloud_fgs_function_v2.MyFunction.name)
]
}
}
#################################################################
# Config agency for RMS to access FunctionGraph
#################################################################
resource "opentelekomcloud_identity_agency_v3" "config_agency" {
name = format("%s-%s-config-agency", var.prefix, var.function_name)
description = "Config agency for FunctionGraph access"
delegated_domain_name = "op_svc_rms"
project_role {
all_projects = true
# project = var.OTC_SDK_PROJECTNAME
roles = [
#"FunctionGraph FullAccess"
opentelekomcloud_identity_role_v3.fg-role.display_name
]
}
}
Variables¶
Sample variables file:
# Terraform variables for scratch-event sample
# prefix of all resources
prefix = "python"
# description of the function
description = "Custom rule for ECS stopped state"
# name of the function (will be prefixed)
function_name = "custom-rule-ecs-stopped"
# handler function name defined in your code, e.g. "index.handler"
handler_name = "index.handler"
# resources will be tagged with this app_group tag
tag_app_group = "custom-rule-ecs-stopped"
You might want to adjust the variables to your need.
Makefile¶
Sample Makefile:
# Makefile for Terraform deployment
# Terraform backend configuration
BACKEND_CONFIG_BUCKET := "doc-samples-tf-backend"
BACKEND_CONFIG_KEY := "terraform_state/python/custom-rule-ecs-stopped.tf"
BACKEND_CONFIG_REGION := "eu-de"
BACKEND_CONFIG_ENDPOINTS := "endpoints={s3=\"https://obs.eu-de.otc.t-systems.com\"}"
CURRENT_MAKEFILE := $(firstword $(MAKEFILE_LIST))
tf_init:
terraform -chdir=terraform \
init \
-backend-config=$(BACKEND_CONFIG_ENDPOINTS) \
-backend-config="bucket=$(BACKEND_CONFIG_BUCKET)" \
-backend-config="key=$(BACKEND_CONFIG_KEY)" \
-backend-config="region=$(BACKEND_CONFIG_REGION)"
tf_plan:
if [ ! -f "terraform/.terraform.lock.hcl" ]; then \
$(MAKE) -f $(CURRENT_MAKEFILE) tf_init; \
fi
terraform -chdir=terraform \
plan \
-var-file="variables.tfvars"
tf_apply:
if [ ! -f "terraform/.terraform.lock.hcl" ]; then \
$(MAKE) -f $(CURRENT_MAKEFILE) tf_init; \
fi
terraform -chdir=terraform \
apply -auto-approve \
-var-file="variables.tfvars"
tf_destroy:
terraform -chdir=terraform \
destroy -auto-approve \
-var-file="variables.tfvars"
.PHONY: tf_init tf_plan tf_apply tf_destroy
You might following variables to your need:
# Terraform backend configuration
BACKEND_CONFIG_BUCKET := "doc-samples-tf-backend"
BACKEND_CONFIG_KEY := "terraform_state/python/custom-rule-ecs-stopped.tf"
BACKEND_CONFIG_REGION := "eu-de"
BACKEND_CONFIG_ENDPOINTS := "endpoints={s3=\"https://obs.eu-de.otc.t-systems.com\"}"
Deploy¶
To deploy using Terraform run:
make tf_deploy
To destroy the deployed resources using Terraform run:
make tf_destroy