diff --git a/cft-templates/ec2-dcv-claudeOnBedrock.yml b/cft-templates/ec2-dcv-claudeOnBedrock.yml index b8180ae..deafd14 100644 --- a/cft-templates/ec2-dcv-claudeOnBedrock.yml +++ b/cft-templates/ec2-dcv-claudeOnBedrock.yml @@ -3,6 +3,14 @@ Metadata: AWSTemplateFormatVersion: '2010-09-09' Description: 'AWS CloudFormation Template to create an GPU based EC2 instance with NICE DCV pre installed.' Parameters: + InstanceType: + Description: Choose the instance type from the dropdown. + Type: String + ConstraintDescription: must be a valid EC2 instance type. + EBSVolumeSize: + Description: The initial size of the volume (in GBs) EBS will use for storage. + Type: Number + Default: 70 Namespace: Type: String Description: An environment name that will be prefixed to resource names @@ -17,17 +25,30 @@ Parameters: Description: >- An S3 URI (starting with "s3://") that specifies the location of files to be copied to the environment instance, including any bootstrap scripts - InstanceType: - Description: Choose the instance type e.g t3.medium (2vCPU , 2GiB RAM), t3.large (2vCPU, 8GiB RAM), t3.xlarge(4vCPU, 16GiB RAM) - Type: String - ConstraintDescription: must be a valid EC2 instance type. - EBSVolumeSize: - Description: The initial size of the volume (in GBs) EBS will use for storage. - Type: Number - Default: 70 + # AvailabilityZone: # Description: Select the availability zone in which to create the instance. If you plan to attach a secondary volume to the instance, create this instance in the same AvailabilityZone as the volume you created. - # Type: AWS::EC2::AvailabilityZone::Name + # Type: AWS::EC2::AvailabilityZone::Name + ProjectId: + Type: String + Default: '' + Description: >- + RG project id. Research Gateway auto-fills this hidden parameter on every launch + (same convention used by other products). Applied as project_name tag on the + Bedrock Mantle project created by this stack. + ResearcherName: + Type: String + Default: '' + Description: >- + Researcher email. Research Gateway auto-fills this hidden parameter on every launch + (same convention used by other products). Applied as researcher_name tag on the + Bedrock Mantle project created by this stack. + CostResource: + Type: String + Default: '' + Description: >- + Cost allocation tag for this workspace. When empty, defaults to this stack's name + (same pattern as the EC2 instance cost_resource tag below). # Password: # Type: String # NoEcho: True @@ -37,8 +58,24 @@ Parameters: Conditions: IamPolicyEmpty: !Equals [!Ref IamPolicyDocument, '{}'] + HasCostResource: !Not [!Equals [!Ref CostResource, '']] + +Resources: + ClaudeMantleProject: + Type: AWS::BedrockMantle::Project + Properties: + Name: !Ref Namespace + Tags: + - Key: project_name + Value: !Ref ProjectId + - Key: researcher_name + Value: !Ref ResearcherName + - Key: cost_resource + Value: !If + - HasCostResource + - !Ref CostResource + - !Sub '${AWS::StackName}' -Resources: SSMPolicy: Type: AWS::IAM::ManagedPolicy Properties: @@ -92,22 +129,20 @@ Resources: Resource: '*' - Effect: Allow Action: - - 'ssmmessages:*' + - 'bedrock:*' + - 'bedrock-mantle:*' Resource: '*' - Effect: Allow Action: - - 'ec2messages:*' + - 'ssmmessages:*' Resource: '*' - Effect: Allow Action: - - 'sns:Publish' + - 'ec2messages:*' Resource: '*' - Effect: Allow Action: - - bedrock:ListInferenceProfiles - - bedrock:GetInferenceProfile - - bedrock:InvokeModel - - bedrock:InvokeModelWithResponseStream + - 'sns:Publish' Resource: '*' BedrockPolicy: Type: AWS::IAM::ManagedPolicy @@ -116,6 +151,14 @@ Resources: PolicyDocument: Version: "2012-10-17" Statement: + - Sid: AllowBedrockMantleInference + Effect: Allow + Action: + - bedrock-mantle:CreateInference + - bedrock-mantle:GetProject + - bedrock-mantle:ListProjects + - bedrock-mantle:ListTagsForResources + Resource: "*" - Sid: AllowBedrockInvoke Effect: Allow Action: @@ -148,6 +191,7 @@ Resources: - Ref: SSMPolicy - Ref: BedrockPolicy - arn:aws:iam::aws:policy/AmazonSSMManagedInstanceCore + - arn:aws:iam::aws:policy/AmazonS3FilesClientFullAccess PermissionsBoundary: !Ref InstanceRolePermissionBoundary @@ -162,6 +206,7 @@ Resources: EC2Instance: Type: AWS::EC2::Instance + DependsOn: ClaudeMantleProject CreationPolicy: ResourceSignal: Timeout: PT15M @@ -319,6 +364,7 @@ Resources: #!/usr/bin/env bash exec > >(tee /var/log/user-data.log|logger -t user-data -s 2>/dev/console) 2>&1 # trap '/opt/aws/bin/cfn-signal --exit-code 1 --resource EC2Instance --region ${AWS::Region} --stack ${AWS::StackName}' ERR + sudo dnf install -y amazon-efs-utils # Download and execute bootstrap script aws s3 cp --region "${AWS::Region}" "${EnvironmentInstanceFiles}/get_bootstrap.sh" "/tmp" chmod 500 "/tmp/get_bootstrap.sh" @@ -328,17 +374,36 @@ Resources: sudo -u ec2-user bash -lc 'curl -fsSL https://claude.ai/install.sh | bash' [ -x /home/ec2-user/.local/bin/claude ] && ln -sf /home/ec2-user/.local/bin/claude /usr/local/bin/claude && (grep -q '.local/bin' /home/ec2-user/.bashrc || echo 'export PATH="$HOME/.local/bin:$PATH"' >> /home/ec2-user/.bashrc) - # Configure Claude Code to use Bedrock + # Configure Claude Code for Bedrock Mantle only (not Invoke API). The Mantle project + # created above (ClaudeMantleProject) is proj_... — pass both workspace headers + # (project id, not ARN) so inference is attributed to this stack's Mantle project. + MANTLE_PROJECT_ID="${ClaudeMantleProject}" sudo -u ec2-user mkdir -p /home/ec2-user/.claude cat >/home/ec2-user/.claude/settings.json <- An S3 URI (starting with "s3://") that specifies the location of files to be copied to the environment instance, including any bootstrap scripts - InstanceType: - Description: Choose the instance type e.g t3.medium (2vCPU , 2GiB RAM), t3.large (2vCPU, 8GiB RAM), t3.xlarge(4vCPU, 16GiB RAM) - Type: String - ConstraintDescription: must be a valid EC2 instance type. - EBSVolumeSize: - Description: The initial size of the volume (in GBs) EBS will use for storage. - Type: Number - Default: 70 + # AvailabilityZone: # Description: Select the availability zone in which to create the instance. If you plan to attach a secondary volume to the instance, create this instance in the same AvailabilityZone as the volume you created. # Type: AWS::EC2::AvailabilityZone::Name @@ -126,6 +127,7 @@ Resources: ManagedPolicyArns: - Ref: SSMPolicy - arn:aws:iam::aws:policy/AmazonSSMManagedInstanceCore + - arn:aws:iam::aws:policy/AmazonS3FilesClientFullAccess PermissionsBoundary: !Ref InstanceRolePermissionBoundary @@ -297,6 +299,7 @@ Resources: #!/usr/bin/env bash exec > >(tee /var/log/user-data.log|logger -t user-data -s 2>/dev/console) 2>&1 # trap '/opt/aws/bin/cfn-signal --exit-code 1 --resource EC2Instance --region ${AWS::Region} --stack ${AWS::StackName}' ERR + sudo dnf install -y amazon-efs-utils # Download and execute bootstrap script aws s3 cp --region "${AWS::Region}" "${EnvironmentInstanceFiles}/get_bootstrap.sh" "/tmp" chmod 500 "/tmp/get_bootstrap.sh" diff --git a/cft-templates/ec2-dcv-codexOnBedrock.yml b/cft-templates/ec2-dcv-codexOnBedrock.yml index cc56bbf..41bdb7a 100644 --- a/cft-templates/ec2-dcv-codexOnBedrock.yml +++ b/cft-templates/ec2-dcv-codexOnBedrock.yml @@ -3,6 +3,14 @@ Metadata: AWSTemplateFormatVersion: '2010-09-09' Description: 'AWS CloudFormation Template to create an GPU based EC2 instance with NICE DCV pre installed.' Parameters: + InstanceType: + Description: Choose the instance type from the dropdown. + Type: String + ConstraintDescription: must be a valid EC2 instance type. + EBSVolumeSize: + Description: The initial size of the volume (in GBs) EBS will use for storage. + Type: Number + Default: 70 Namespace: Type: String Description: An environment name that will be prefixed to resource names @@ -16,18 +24,30 @@ Parameters: Type: String Description: >- An S3 URI (starting with "s3://") that specifies the location of files to be copied to - the environment instance, including any bootstrap scripts - InstanceType: - Description: Choose the instance type e.g t3.medium (2vCPU , 2GiB RAM), t3.large (2vCPU, 8GiB RAM), t3.xlarge(4vCPU, 16GiB RAM) - Type: String - ConstraintDescription: must be a valid EC2 instance type. - EBSVolumeSize: - Description: The initial size of the volume (in GBs) EBS will use for storage. - Type: Number - Default: 70 + the environment instance, including any bootstrap scripts # AvailabilityZone: # Description: Select the availability zone in which to create the instance. If you plan to attach a secondary volume to the instance, create this instance in the same AvailabilityZone as the volume you created. - # Type: AWS::EC2::AvailabilityZone::Name + # Type: AWS::EC2::AvailabilityZone::Name + ProjectId: + Type: String + Default: '' + Description: >- + RG project id. Research Gateway auto-fills this hidden parameter on every launch + (same convention used by other products). Applied as project_name tag on the + Bedrock Mantle project created by this stack. + ResearcherName: + Type: String + Default: '' + Description: >- + Researcher email. Research Gateway auto-fills this hidden parameter on every launch + (same convention used by other products). Applied as researcher_name tag on the + Bedrock Mantle project created by this stack. + CostResource: + Type: String + Default: '' + Description: >- + Cost allocation tag for this workspace. When empty, defaults to this stack's name + (same pattern as the EC2 instance cost_resource tag below). # Password: # Type: String # NoEcho: True @@ -37,8 +57,27 @@ Parameters: Conditions: IamPolicyEmpty: !Equals [!Ref IamPolicyDocument, '{}'] + HasCostResource: !Not [!Equals [!Ref CostResource, '']] + +Resources: + # One Bedrock Mantle project per Codex workspace — created by CloudFormation (not RG API). + # Tags match RG's standard cost allocation keys so Bedrock spend flows into the existing + # daily project cost sync. Ref returns proj_... for Codex OpenAI-Project header wiring. + CodexMantleProject: + Type: AWS::BedrockMantle::Project + Properties: + Name: !Ref Namespace + Tags: + - Key: project_name + Value: !Ref ProjectId + - Key: researcher_name + Value: !Ref ResearcherName + - Key: cost_resource + Value: !If + - HasCostResource + - !Ref CostResource + - !Sub '${AWS::StackName}' -Resources: SSMPolicy: Type: AWS::IAM::ManagedPolicy Properties: @@ -157,6 +196,7 @@ Resources: - Ref: SSMPolicy - Ref: BedrockPolicy - arn:aws:iam::aws:policy/AmazonSSMManagedInstanceCore + - arn:aws:iam::aws:policy/AmazonS3FilesClientFullAccess PermissionsBoundary: !Ref InstanceRolePermissionBoundary @@ -171,6 +211,7 @@ Resources: EC2Instance: Type: AWS::EC2::Instance + DependsOn: CodexMantleProject CreationPolicy: ResourceSignal: Timeout: PT15M @@ -328,6 +369,7 @@ Resources: #!/usr/bin/env bash exec > >(tee /var/log/user-data.log|logger -t user-data -s 2>/dev/console) 2>&1 # trap '/opt/aws/bin/cfn-signal --exit-code 1 --resource EC2Instance --region ${AWS::Region} --stack ${AWS::StackName}' ERR + sudo dnf install -y amazon-efs-utils # Download and execute bootstrap script aws s3 cp --region "${AWS::Region}" "${EnvironmentInstanceFiles}/get_bootstrap.sh" "/tmp" chmod 500 "/tmp/get_bootstrap.sh" @@ -348,6 +390,21 @@ Resources: region = "${AWS::Region}" wire_api = "responses" EOF + + # Attribute Codex/Bedrock Mantle usage to the Mantle project created above in this + # stack (CodexMantleProject). Ref returns proj_... used as OpenAI-Project header. + MANTLE_PROJECT_ID="${CodexMantleProject}" + if [ -n "$MANTLE_PROJECT_ID" ]; then + cat >> /home/ec2-user/.codex/config.toml << EOF + + [model_providers.amazon-bedrock] + http_headers = { "OpenAI-Project" = "$MANTLE_PROJECT_ID" } + EOF + echo "Codex configured to use Bedrock Mantle project $MANTLE_PROJECT_ID" + else + echo "No Mantle project id from stack; Codex will use the account default Mantle project" + fi + chown ec2-user:ec2-user /home/ec2-user/.codex/config.toml chmod 600 /home/ec2-user/.codex/config.toml @@ -510,6 +567,21 @@ Resources: CidrIp: 0.0.0.0/0 Outputs: + MantleProjectId: + Description: Bedrock Mantle project id (proj_...) for Codex OpenAI-Project header + Value: !Ref CodexMantleProject + MantleProjectNameTag: + Description: 'project_name cost allocation tag (RG project id)' + Value: !Ref ProjectId + MantleResearcherNameTag: + Description: 'researcher_name cost allocation tag (researcher email)' + Value: !Ref ResearcherName + MantleCostResourceTag: + Description: 'cost_resource cost allocation tag (defaults to stack name when CostResource param is empty)' + Value: !If + - HasCostResource + - !Ref CostResource + - !Sub '${AWS::StackName}' InstanceId: Description: InstanceId of the newly created EC2 instance Value: !Ref 'EC2Instance' diff --git a/dump/standardcatalogitems.json b/dump/standardcatalogitems.json index b284009..320734a 100644 --- a/dump/standardcatalogitems.json +++ b/dump/standardcatalogitems.json @@ -1773,6 +1773,18 @@ { "name": "Namespace", "type": "RL::SC::PARAM::HD" + }, + { + "name": "ProjectId", + "type": "RL::SC::PARAM::HD" + }, + { + "name": "ResearcherName", + "type": "RL::SC::PARAM::HD" + }, + { + "name": "CostResource", + "type": "RL::SC::PARAM::HD" } ] } @@ -1967,6 +1979,18 @@ { "name": "Namespace", "type": "RL::SC::PARAM::HD" + }, + { + "name": "ProjectId", + "type": "RL::SC::PARAM::HD" + }, + { + "name": "ResearcherName", + "type": "RL::SC::PARAM::HD" + }, + { + "name": "CostResource", + "type": "RL::SC::PARAM::HD" } ] }