chroma / bin /generate_cloudformation.py
badalsahani's picture
feat: chroma initial deploy
287a0bc
import boto3
import json
import subprocess
import os
import re
def b64text(txt):
"""Generate Base 64 encoded CF json for a multiline string, subbing in values where appropriate"""
lines = []
for line in txt.splitlines(True):
if "${" in line:
lines.append({"Fn::Sub": line})
else:
lines.append(line)
return {"Fn::Base64": {"Fn::Join": ["", lines]}}
path = os.path.dirname(os.path.realpath(__file__))
version = subprocess.check_output(f"{path}/version").decode("ascii").strip()
with open(f"{path}/templates/docker-compose.yml") as f:
docker_compose_file = str(f.read())
cloud_config_script = """
#cloud-config
cloud_final_modules:
- [scripts-user, always]
"""
cloud_init_script = f"""
#!/bin/bash
amazon-linux-extras install docker
usermod -a -G docker ec2-user
curl -L https://github.com/docker/compose/releases/latest/download/docker-compose-$(uname -s)-$(uname -m) -o /usr/local/bin/docker-compose
chmod +x /usr/local/bin/docker-compose
ln -s /usr/local/bin/docker-compose /usr/bin/docker-compose
systemctl enable docker
systemctl start docker
cat << EOF > /home/ec2-user/docker-compose.yml
{docker_compose_file}
EOF
mkdir /home/ec2-user/config
docker-compose -f /home/ec2-user/docker-compose.yml up -d
"""
userdata = f"""Content-Type: multipart/mixed; boundary="//"
MIME-Version: 1.0
--//
Content-Type: text/cloud-config; charset="us-ascii"
MIME-Version: 1.0
Content-Transfer-Encoding: 7bit
Content-Disposition: attachment; filename="cloud-config.txt"
{cloud_config_script}
--//
Content-Type: text/x-shellscript; charset="us-ascii"
MIME-Version: 1.0
Content-Transfer-Encoding: 7bit
Content-Disposition: attachment; filename="userdata.txt"
{cloud_init_script}
--//--
"""
cf = {
"AWSTemplateFormatVersion": "2010-09-09",
"Description": "Create a stack that runs Chroma hosted on a single instance",
"Parameters": {
"KeyName": {
"Description": "Name of an existing EC2 KeyPair to enable SSH access to the instance",
"Type": "String",
"ConstraintDescription": "If present, must be the name of an existing EC2 KeyPair.",
"Default": "",
},
"InstanceType": {
"Description": "EC2 instance type",
"Type": "String",
"Default": "t3.small",
},
"ChromaVersion": {
"Description": "Chroma version to install",
"Type": "String",
"Default": version,
},
},
"Conditions": {
"HasKeyName": {"Fn::Not": [{"Fn::Equals": [{"Ref": "KeyName"}, ""]}]},
},
"Resources": {
"ChromaInstance": {
"Type": "AWS::EC2::Instance",
"Properties": {
"ImageId": {
"Fn::FindInMap": ["Region2AMI", {"Ref": "AWS::Region"}, "AMI"]
},
"InstanceType": {"Ref": "InstanceType"},
"UserData": b64text(userdata),
"SecurityGroupIds": [{"Ref": "ChromaInstanceSecurityGroup"}],
"KeyName": {
"Fn::If": [
"HasKeyName",
{"Ref": "KeyName"},
{"Ref": "AWS::NoValue"},
]
},
"BlockDeviceMappings": [
{
"DeviceName": {
"Fn::FindInMap": [
"Region2AMI",
{"Ref": "AWS::Region"},
"RootDeviceName",
]
},
"Ebs": {"VolumeSize": 24},
}
],
},
},
"ChromaInstanceSecurityGroup": {
"Type": "AWS::EC2::SecurityGroup",
"Properties": {
"GroupDescription": "Chroma Instance Security Group",
"SecurityGroupIngress": [
{
"IpProtocol": "tcp",
"FromPort": "22",
"ToPort": "22",
"CidrIp": "0.0.0.0/0",
},
{
"IpProtocol": "tcp",
"FromPort": "8000",
"ToPort": "8000",
"CidrIp": "0.0.0.0/0",
},
],
},
},
},
"Outputs": {
"ServerIp": {
"Description": "IP address of the Chroma server",
"Value": {"Fn::GetAtt": ["ChromaInstance", "PublicIp"]},
}
},
"Mappings": {"Region2AMI": {}},
}
# Populate the Region2AMI mappings
regions = boto3.client("ec2", region_name="us-east-1").describe_regions()["Regions"]
for region in regions:
region_name = region["RegionName"]
ami_result = boto3.client("ec2", region_name=region_name).describe_images(
Owners=["137112412989"],
Filters=[
{"Name": "name", "Values": ["amzn2-ami-kernel-5.10-hvm-*-x86_64-gp2"]},
{"Name": "root-device-type", "Values": ["ebs"]},
{"Name": "virtualization-type", "Values": ["hvm"]},
],
)
img = ami_result["Images"][0]
ami_id = img["ImageId"]
root_device_name = img["BlockDeviceMappings"][0]["DeviceName"]
cf["Mappings"]["Region2AMI"][region_name] = {
"AMI": ami_id,
"RootDeviceName": root_device_name,
}
# Write the CF json to a file
json.dump(cf, open("/tmp/chroma.cf.json", "w"), indent=4)
# upload to S3
s3 = boto3.client("s3", region_name="us-east-1")
s3.upload_file(
"/tmp/chroma.cf.json",
"public.trychroma.com",
f"cloudformation/{version}/chroma.cf.json",
)
# Upload to s3 under /latest version only if this is a release
pattern = re.compile(r"^\d+\.\d+\.\d+$")
if pattern.match(version):
s3.upload_file(
"/tmp/chroma.cf.json",
"public.trychroma.com",
"cloudformation/latest/chroma.cf.json",
)
else:
print(f"Version {version} is not a 3-part semver, not uploading to /latest")