sparse-intern-71089
11/12/2021, 12:08 PMprehistoric-activity-61023
11/12/2021, 12:08 PMprehistoric-activity-61023
11/12/2021, 12:09 PMget_credentials
? 🙂steep-beach-52652
11/12/2021, 12:09 PMprehistoric-activity-61023
11/12/2021, 12:10 PMcredentails
is definitely a typo, so let’s rule this out firststeep-beach-52652
11/12/2021, 12:10 PMsteep-beach-52652
11/12/2021, 12:10 PMsteep-beach-52652
11/12/2021, 12:11 PMprehistoric-activity-61023
11/12/2021, 12:12 PMsteep-beach-52652
11/12/2021, 12:13 PMsteep-beach-52652
11/12/2021, 12:15 PMcontainer_definitions=json.dumps([{
File "/usr/local/Cellar/python@3.9/3.9.0_2/Frameworks/Python.framework/Versions/3.9/lib/python3.9/json/__init__.py", line 231, in dumps
return _default_encoder.encode(obj)
File "/usr/local/Cellar/python@3.9/3.9.0_2/Frameworks/Python.framework/Versions/3.9/lib/python3.9/json/encoder.py", line 199, in encode
chunks = self.iterencode(o, _one_shot=True)
File "/usr/local/Cellar/python@3.9/3.9.0_2/Frameworks/Python.framework/Versions/3.9/lib/python3.9/json/encoder.py", line 257, in iterencode
return _iterencode(o, 0)
File "/usr/local/Cellar/python@3.9/3.9.0_2/Frameworks/Python.framework/Versions/3.9/lib/python3.9/json/encoder.py", line 179, in default
raise TypeError(f'Object of type {o.__class__.__name__} '
TypeError: Object of type Output is not JSON serializable
steep-beach-52652
11/12/2021, 12:21 PMsteep-beach-52652
11/12/2021, 12:21 PM# Build and publish our app's container image:
# ...1) Create a private ECR repository.
repo = aws.ecr.Repository('my-repo')
image_name = repo.repository_url
# ...2) Get registry info (creds and endpoint).
def get_registry_info(rid):
"""Get registry info (creds and endpoint)."""
creds = aws.ecr.get_credentails(registry_id=rid)
decoded = base64.b64decode(creds.authorization_token).decode()
parts = decoded.split(':')
if len(parts) != 2:
raise Exception('Invalid credentials')
return docker.ImageRegistry(creds.proxy_endpoint, parts[0], parts[1])
registry_info = repo.registry_id.apply(get_registry_info)
# ...3) Build and publish the container image.
image = docker.Image('my-app-image',
build='app',
image_name=image_name,
registry=registry_info
)
steep-beach-52652
11/12/2021, 12:22 PMsteep-beach-52652
11/12/2021, 12:23 PMsteep-beach-52652
11/12/2021, 12:23 PMprehistoric-activity-61023
11/12/2021, 12:33 PMprehistoric-activity-61023
11/12/2021, 12:33 PMimage.image_name
and app_secret
?prehistoric-activity-61023
11/12/2021, 12:34 PMObject of type Output is not JSON serializable
), you will have to use apply
to construct a JSON.prehistoric-activity-61023
11/12/2021, 12:35 PMprehistoric-activity-61023
11/12/2021, 12:38 PMimage_name
and app_secret
are pulumi outputs):
...
container_definitions=pulumi.Output.all(
image.image_name,
app_secret,
).apply(lambda args: _create_json(image_name=args[0], app_secret=args[1])),
...
prehistoric-activity-61023
11/12/2021, 12:39 PMdef _create_json(image_name: str, app_secret: str) -> str:
return json.dumps({
...
"image": image_name,
...
})
steep-beach-52652
11/12/2021, 12:57 PMsteep-beach-52652
11/12/2021, 12:58 PMsteep-beach-52652
11/12/2021, 12:58 PMsteep-beach-52652
11/12/2021, 12:59 PMsteep-beach-52652
11/12/2021, 12:59 PMprehistoric-activity-61023
11/12/2021, 1:07 PMimage.image_name
is an Output
class, right?prehistoric-activity-61023
11/12/2021, 1:08 PMstr
function is no-go eitherprehistoric-activity-61023
11/12/2021, 1:10 PMOutput
with apply
method or using pulumi.Output.all
(if you have more than one output)steep-beach-52652
11/12/2021, 1:25 PMsteep-beach-52652
11/12/2021, 1:27 PMprehistoric-activity-61023
11/12/2021, 1:31 PMcontainer_definitions
?prehistoric-activity-61023
11/12/2021, 1:32 PMprehistoric-activity-61023
11/12/2021, 1:36 PMsteep-beach-52652
11/12/2021, 1:40 PMprehistoric-activity-61023
11/12/2021, 1:40 PMcontainer_definitions
is marked as Optional[pulumi.Input[str]]
so it should deal properly with Output
typeprehistoric-activity-61023
11/12/2021, 1:41 PMInput = Union[T, Awaitable[T], 'Output[T]']
)steep-beach-52652
11/12/2021, 1:41 PMfrom pulumi import export, ResourceOptions
import pulumi_aws as aws
import pulumi_docker as docker
import json
import base64
from pulumi_aws.iam import policy
from pulumi import Output
# Build and publish our app's container image:
repo = aws.ecr.Repository('app-repo')
image_name = repo.repository_url
# Get registry info (creds and endpoint).
def get_registry_info(rid):
creds = aws.ecr.get_credentials(registry_id=rid)
decoded = base64.b64decode(creds.authorization_token).decode()
parts = decoded.split(':')
if len(parts) != 2:
raise Exception('Invalid credentials')
return docker.ImageRegistry(creds.proxy_endpoint, parts[0], parts[1])
registry_info = repo.registry_id.apply(get_registry_info)
# Build and publish the container image.
image = docker.Image('app-image',
build='app',
image_name=image_name,
registry=registry_info
)
## Get Secret from Secret manager
app_secret = json.loads((aws.secretsmanager.get_secret_version("appsecret")).secret_string)["appsecret"]
# Create an ECS cluster to run a container-based service.
cluster = aws.ecs.Cluster('cluster')
# Read back the default VPC and public subnets, which we will use.
default_vpc = aws.ec2.get_vpc(default=True)
default_vpc_subnets = aws.ec2.get_subnet_ids(vpc_id=default_vpc.id)
# Create a SecurityGroup that permits HTTP ingress and unrestricted egress.
group = aws.ec2.SecurityGroup('web-secgrp',
vpc_id=default_vpc.id,
description='Enable HTTP access',
ingress=[aws.ec2.SecurityGroupIngressArgs(
protocol='tcp',
from_port=80,
to_port=80,
cidr_blocks=['0.0.0.0/0'],
)],
egress=[aws.ec2.SecurityGroupEgressArgs(
protocol='-1',
from_port=0,
to_port=0,
cidr_blocks=['0.0.0.0/0'],
)],
)
# Create a load balancer to listen for HTTP traffic on port 80.
alb = aws.lb.LoadBalancer('app-lb',
security_groups=[group.id],
subnets=default_vpc_subnets.ids,
)
atg = aws.lb.TargetGroup('app-tg',
port=80,
protocol='HTTP',
target_type='ip',
vpc_id=default_vpc.id,
)
wl = aws.lb.Listener('web',
load_balancer_arn=alb.arn,
port=80,
default_actions=[aws.lb.ListenerDefaultActionArgs(
type='forward',
target_group_arn=atg.arn,
)],
)
# Create an IAM role that can be used by our service's task.
role = aws.iam.Role('task-exec-role',
assume_role_policy=json.dumps({
'Version': '2008-10-17',
'Statement': [{
'Sid': '',
'Effect': 'Allow',
'Principal': {
'Service': '<http://ecs-tasks.amazonaws.com|ecs-tasks.amazonaws.com>'
},
'Action': 'sts:AssumeRole',
}]
}),
)
rpa = aws.iam.RolePolicyAttachment('task-exec-policy',
role=role.name,
policy_arn='arn:aws:iam::aws:policy/service-role/AmazonECSTaskExecutionRolePolicy',
)
# Spin up a load balanced service running our container image.
task_definition = aws.ecs.TaskDefinition('app-task',
family='fargate-task-definition',
cpu='256',
memory='512',
network_mode='awsvpc',
requires_compatibilities=['FARGATE'],
execution_role_arn=role.arn,
task_role_arn=role.arn,
container_definitions=json.dumps([{
'name': 'my-app',
'image': str(image.image_name),
'readonlyRootFilesystem': False,
'environment': [
{
'name': 'appsecret',
'value': app_secret
}],
'portMappings': [{
'containerPort': 80,
'hostPort': 80,
'protocol': 'tcp'
}]
}])
)
service = aws.ecs.Service('app-svc',
cluster=cluster.arn,
desired_count=1,
launch_type='FARGATE',
task_definition=task_definition.arn,
enable_execute_command=True,
network_configuration=aws.ecs.ServiceNetworkConfigurationArgs(
assign_public_ip=True,
subnets=default_vpc_subnets.ids,
security_groups=[group.id],
),
load_balancers=[aws.ecs.ServiceLoadBalancerArgs(
target_group_arn=atg.arn,
container_name='my-app',
container_port=80,
)],
opts=ResourceOptions(depends_on=[wl]),
)
export('url', alb.dns_name)
export('image_base', image.base_image_name)
export('image_name', image.image_name)
prehistoric-activity-61023
11/12/2021, 1:42 PMstr
function.steep-beach-52652
11/12/2021, 1:42 PMprehistoric-activity-61023
11/12/2021, 1:42 PMapp_secret = json.loads((aws.secretsmanager.get_secret_version("appsecret")).secret_string)["appsecret"]
works as you expect?steep-beach-52652
11/12/2021, 1:43 PMsteep-beach-52652
11/12/2021, 1:43 PMsteep-beach-52652
11/12/2021, 1:43 PMsteep-beach-52652
11/12/2021, 1:43 PMprehistoric-activity-61023
11/12/2021, 1:43 PMpulumi.Outputs.all
so it didn’t work out for you?steep-beach-52652
11/12/2021, 1:43 PMprehistoric-activity-61023
11/12/2021, 1:44 PMapp_secret
actually works (not that I don’t trust you but considering problems you describe, it might be worth double checking)prehistoric-activity-61023
11/12/2021, 1:45 PMexport('app_secret', app_secret)
steep-beach-52652
11/12/2021, 1:45 PMsteep-beach-52652
11/12/2021, 1:46 PMsteep-beach-52652
11/12/2021, 1:46 PMprehistoric-activity-61023
11/12/2021, 1:46 PMprehistoric-activity-61023
11/12/2021, 1:46 PMapp_secret
of type Output[str]
as well?steep-beach-52652
11/12/2021, 1:47 PMsteep-beach-52652
11/12/2021, 1:47 PMprehistoric-activity-61023
11/12/2021, 1:48 PMtask_definition = aws.ecs.TaskDefinition('app-task',
family='fargate-task-definition',
cpu='256',
memory='512',
network_mode='awsvpc',
requires_compatibilities=['FARGATE'],
execution_role_arn=role.arn,
task_role_arn=role.arn,
container_definitions=image.image_name.apply(lambda image_name: [{
'name': 'my-app',
'image': image_name,
'readonlyRootFilesystem': False,
'environment': [
{
'name': 'appsecret',
'value': app_secret
}],
'portMappings': [{
'containerPort': 80,
'hostPort': 80,
'protocol': 'tcp'
}]
}])
)
steep-beach-52652
11/12/2021, 1:49 PMprehistoric-activity-61023
11/12/2021, 1:49 PMprehistoric-activity-61023
11/12/2021, 1:49 PMsteep-beach-52652
11/12/2021, 1:50 PMprehistoric-activity-61023
11/12/2021, 1:50 PMstr
• Input[str]
• Output[str]
prehistoric-activity-61023
11/12/2021, 1:50 PMOutput[str]
with some additional hardcoded data, you need to construct it using apply
(or pulumi.Output.all
/ pulumi.Output.concat
)steep-beach-52652
11/12/2021, 1:52 PMFile "/Users/iahmad/sandbox/my-repo/venv/lib/python3.9/site-packages/pulumi/runtime/rpc.py", line 107, in _get_list_element_type
raise AssertionError(f"Unexpected type. Expected 'list' got '{typ}'")
AssertionError: Unexpected type. Expected 'list' got '<class 'str'>'
error: an unhandled error occurred: Program exited with non-zero exit code: 1
Outputs:
+ image_base: "<http://379821691363.dkr.ecr.us-east-2.amazonaws.com/app-repo-96aa7ae|379821691363.dkr.ecr.us-east-2.amazonaws.com/app-repo-96aa7ae>"
+ image_name: "<http://379821691363.dkr.ecr.us-east-2.amazonaws.com/app-repo-96aa7ae:ee17cc979917a0f3f7a209b0193f12efea536d7b6eedea602bb37bf57768aa23|379821691363.dkr.ecr.us-east-2.amazonaws.com/app-repo-96aa7ae:ee17cc979917a0f3f7a209b0193f12efea536d7b6eedea602bb37bf57768aa23>"
prehistoric-activity-61023
11/12/2021, 1:53 PMprehistoric-activity-61023
11/12/2021, 1:53 PMprehistoric-activity-61023
11/12/2021, 1:54 PMprehistoric-activity-61023
11/12/2021, 1:54 PMjson.dumps
in the lambda functionsteep-beach-52652
11/12/2021, 1:54 PMprehistoric-activity-61023
11/12/2021, 1:54 PMprehistoric-activity-61023
11/12/2021, 1:55 PMtask_definition = aws.ecs.TaskDefinition('app-task',
family='fargate-task-definition',
cpu='256',
memory='512',
network_mode='awsvpc',
requires_compatibilities=['FARGATE'],
execution_role_arn=role.arn,
task_role_arn=role.arn,
container_definitions=image.image_name.apply(lambda image_name: json.dumps([{
'name': 'my-app',
'image': image_name,
'readonlyRootFilesystem': False,
'environment': [
{
'name': 'appsecret',
'value': app_secret
}],
'portMappings': [{
'containerPort': 80,
'hostPort': 80,
'protocol': 'tcp'
}]
}]))
)
steep-beach-52652
11/12/2021, 1:55 PMsteep-beach-52652
11/12/2021, 2:03 PMsteep-beach-52652
11/12/2021, 2:04 PMsteep-beach-52652
11/12/2021, 2:04 PMResources:
~ 1 to update
+-1 to replace
2 changes. 10 unchanged
Do you want to perform this update? yes
Updating (dev)
View Live: <https://app.pulumi.com/iahmad-khan/aws-py-fargate/dev/updates/6>
Type Name Status
steep-beach-52652
11/12/2021, 2:05 PMsteep-beach-52652
11/12/2021, 2:10 PMsteep-beach-52652
11/12/2021, 2:52 PM