Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 6 additions & 8 deletions .github/workflows/terraform-test.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -23,29 +23,27 @@ jobs:
PR_NUMBER: >-
${{ github.event_name == 'workflow_dispatch' &&
github.event.inputs.pr_number || github.event.pull_request.number }}


steps:
- name: Checkout PR code
uses: actions/checkout@v4
with:
ref: refs/pull/${{ env.PR_NUMBER }}/head

- name: Configure AWS credentials via OIDC
uses: aws-actions/configure-aws-credentials@v4
uses: aws-actions/configure-aws-credentials@v2
with:
role-to-assume: ${{ secrets.AWS_ROLE_ARN }}
role-to-assume: ${{ secrets.ARC_IAC_TERRATEST_ROLE }}
aws-region: us-east-1

- name: Set up Go
uses: actions/setup-go@v5
uses: actions/setup-go@v4
with:
go-version: '1.24'

- name: Set up Terraform
uses: hashicorp/setup-terraform@v3
uses: hashicorp/setup-terraform@v2
with:
terraform_version: 1.5.7
terraform_version: 1.7.5
terraform_wrapper: false

- name: Create test directory and download go from S3
Expand All @@ -60,7 +58,7 @@ jobs:
go get github.com/gruntwork-io/terratest/modules/terraform
go get github.com/stretchr/testify/assert
go mod tidy
go test -v -timeout 60m
go test -v -timeout 50m
- name: Report check status manually
uses: actions/github-script@v7
with:
Expand Down
2 changes: 0 additions & 2 deletions .snyk

This file was deleted.

108 changes: 105 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,11 +9,37 @@

## Overview

The ARC AWS SageMaker Terraform module provides a robust and fully extensible solution for managing SageMaker Studio domains, user profiles, models, endpoint, endpoint configurations, and pipelines. It supports custom app settings, and VPC integration to meet enterprise-grade machine learning needs. With dynamic configurations and secure defaults, the module enables rapid, consistent, and scalable SageMaker infrastructure provisioning using best practices.
The ARC Terraform module provides a robust and fully extensible solution for managing SageMaker Studio domains, user profiles, models, endpoint, endpoint configurations, and pipelines. It supports custom app settings, and VPC integration to meet enterprise-grade machine learning needs. With dynamic configurations and secure defaults, the module enables rapid, consistent, and scalable SageMaker infrastructure provisioning using best practices.

## Usage
### Prerequisites
Before using this module, ensure you have the following:

- AWS credentials configured.
- Terraform installed.
- A working knowledge of Terraform.

## Getting Started

1. **Define the Module**

Initially, it's essential to define a Terraform module, which is organized as a distinct directory encompassing Terraform configuration files. Within this module directory, input variables and output values must be defined in the variables.tf and outputs.tf files, respectively. The following illustrates an example directory structure:



```plaintext
sagemaker/
|-- main.tf
|-- variables.tf
|-- outputs.tf
```


2. **Define Input Variables**

To see a full examples, check out the [main.tf](./examples/endpoint/main.tf) file in the example folder.
Inside the `variables.tf` or in `*.tfvars` file, you should define values for the variables that the module requires.

3. **Use the Module in Your Main Configuration**
In your main Terraform configuration file (e.g., main.tf), you can use the module. Specify the source of the module, and version, For Example

```hcl
module "sagemaker_model" {
Expand Down Expand Up @@ -43,6 +69,81 @@ module "sagemaker_model" {
}
```

4. **Output Values**

Inside the `outputs.tf` file of the module, you can define output values that can be referenced in the main configuration. For example:

```hcl
output "model_name" {
description = "Name of the SageMaker model"
value = module.sagemaker_model.model_name
}

output "model_arn" {
description = "ARN of the SageMaker model"
value = module.sagemaker_model.model_arn
}

output "endpoint_config_arn" {
description = "ARN of the SageMaker endpoint configuration"
value = module.sagemaker_model.endpoint_config_arn
}

```

5. **.tfvars**

Inside the `.tfvars` file of the module, you can provide desired values that can be referenced in the main configuration.


## First Time Usage
***uncomment the backend block in [main.tf](./examples/endpoint//main.tf)***
```shell
terraform init -backend-config=config.dev.hcl
```
***If testing locally, `terraform init` should be fine***

Create a `dev` workspace
```shell
terraform workspace new dev
```

Plan Terraform
```shell
terraform plan -var-file dev.tfvars
```

Apply Terraform
```shell
terraform apply -var-file dev.tfvars
```

## Production Setup
```shell
terraform init -backend-config=config.prod.hcl
```

Create a `prod` workspace
```shell
terraform workspace new prod
```

Plan Terraform
```shell
terraform plan -var-file prod.tfvars
```

Apply Terraform
```shell
terraform apply -var-file prod.tfvars
```

## Cleanup
Destroy Terraform
```shell
terraform destroy -var-file dev.tfvars
```

<!-- BEGINNING OF PRE-COMMIT-TERRAFORM DOCS HOOK -->
## Requirements

Expand Down Expand Up @@ -100,6 +201,7 @@ module "sagemaker_model" {
| <a name="input_create_model"></a> [create\_model](#input\_create\_model) | Whether to create the SageMaker model | `bool` | `false` | no |
| <a name="input_create_pipeline"></a> [create\_pipeline](#input\_create\_pipeline) | Whether to create the SageMaker pipeline | `bool` | `false` | no |
| <a name="input_create_pipeline_role"></a> [create\_pipeline\_role](#input\_create\_pipeline\_role) | Whether to create a separate role for pipelines | `bool` | `false` | no |
| <a name="input_create_sagemaker_execution_role"></a> [create\_sagemaker\_execution\_role](#input\_create\_sagemaker\_execution\_role) | Whether to create an SageMaker execution role | `bool` | `false` | no |
| <a name="input_create_security_groups"></a> [create\_security\_groups](#input\_create\_security\_groups) | Whether to create security groups for SageMaker Studio | `bool` | `false` | no |
| <a name="input_create_user_profile"></a> [create\_user\_profile](#input\_create\_user\_profile) | Whether to create the SageMaker user profile | `bool` | `false` | no |
| <a name="input_data_capture_config"></a> [data\_capture\_config](#input\_data\_capture\_config) | (Optional) Configuration for capturing input/output data. | <pre>object({<br/> initial_sampling_percentage = number<br/> destination_s3_uri = string<br/> kms_key_id = optional(string)<br/> enable_capture = optional(bool)<br/> capture_options = list(object({<br/> capture_mode = string<br/> }))<br/> capture_content_type_header = optional(object({<br/> csv_content_types = optional(list(string))<br/> json_content_types = optional(list(string))<br/> }))<br/> })</pre> | `null` | no |
Expand Down
7 changes: 4 additions & 3 deletions examples/endpoint/main.tf
Original file line number Diff line number Diff line change
Expand Up @@ -31,9 +31,10 @@ module "tags" {
module "sagemaker_model" {
source = "../.."

name = "terraform-arc"
create_endpoint_config = true
create_model = true
name = "terraform-arc"
create_endpoint_config = true
create_model = true
create_sagemaker_execution_role = true


primary_container = {
Expand Down
3 changes: 0 additions & 3 deletions examples/endpoint/terraform.tfvars
Original file line number Diff line number Diff line change
@@ -1,4 +1 @@
# Complete SageMaker Example Configuration
# This file demonstrates how to enable all SageMaker components: Model/Endpoint, Pipeline, and Studio

region = "us-east-1"
56 changes: 56 additions & 0 deletions examples/multi-user/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
<!-- BEGIN_TF_DOCS -->
## Requirements

| Name | Version |
|------|---------|
| <a name="requirement_terraform"></a> [terraform](#requirement\_terraform) | >= 1.5.0 |
| <a name="requirement_aws"></a> [aws](#requirement\_aws) | ~> 5.0 |

## Providers

| Name | Version |
|------|---------|
| <a name="provider_aws"></a> [aws](#provider\_aws) | 5.100.0 |

## Modules

| Name | Source | Version |
|------|--------|---------|
| <a name="module_sagemaker_studio_multi_user"></a> [sagemaker\_studio\_multi\_user](#module\_sagemaker\_studio\_multi\_user) | ../../ | n/a |
| <a name="module_tags"></a> [tags](#module\_tags) | sourcefuse/arc-tags/aws | 1.2.6 |

## Resources

| Name | Type |
|------|------|
| [aws_iam_role.sagemaker_execution_role](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_role) | resource |
| [aws_iam_role_policy_attachment.attach_policy](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_role_policy_attachment) | resource |
| [aws_subnets.private](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/subnets) | data source |
| [aws_vpc.default](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/vpc) | data source |

## Inputs

| Name | Description | Type | Default | Required |
|------|-------------|------|---------|:--------:|
| <a name="input_create_execution_role"></a> [create\_execution\_role](#input\_create\_execution\_role) | Whether to create a default execution role | `bool` | `false` | no |
| <a name="input_environment"></a> [environment](#input\_environment) | Environment name (dev, staging, prod) | `string` | `"dev"` | no |
| <a name="input_input_data_s3_uri"></a> [input\_data\_s3\_uri](#input\_input\_data\_s3\_uri) | S3 URI for input data in shared pipeline | `string` | n/a | yes |
| <a name="input_kms_key_id"></a> [kms\_key\_id](#input\_kms\_key\_id) | KMS key ID for encryption | `string` | `null` | no |
| <a name="input_namespace"></a> [namespace](#input\_namespace) | Namespace of the project, i.e. arc | `string` | n/a | yes |
| <a name="input_output_data_s3_path"></a> [output\_data\_s3\_path](#input\_output\_data\_s3\_path) | S3 path for pipeline output data | `string` | n/a | yes |
| <a name="input_region"></a> [region](#input\_region) | AWS region | `string` | `"us-east-1"` | no |
| <a name="input_security_group_data"></a> [security\_group\_data](#input\_security\_group\_data) | (optional) Security Group data | <pre>object({<br/> security_group_ids_to_attach = optional(list(string), [])<br/> create = optional(bool, true)<br/> description = optional(string, null)<br/> ingress_rules = optional(list(object({<br/> description = optional(string, null)<br/> cidr_block = optional(string, null)<br/> source_security_group_id = optional(string, null)<br/> from_port = number<br/> ip_protocol = string<br/> to_port = string<br/> self = optional(bool, false)<br/> })), [])<br/> egress_rules = optional(list(object({<br/> description = optional(string, null)<br/> cidr_block = optional(string, null)<br/> destination_security_group_id = optional(string, null)<br/> from_port = number<br/> ip_protocol = string<br/> to_port = string<br/> prefix_list_id = optional(string, null)<br/> })), [])<br/> })</pre> | <pre>{<br/> "create": false<br/>}</pre> | no |
| <a name="input_security_group_name"></a> [security\_group\_name](#input\_security\_group\_name) | sagemaker security group name | `string` | n/a | yes |
| <a name="input_shared_s3_path"></a> [shared\_s3\_path](#input\_shared\_s3\_path) | S3 path for shared notebook outputs | `string` | n/a | yes |
| <a name="input_subnet_names"></a> [subnet\_names](#input\_subnet\_names) | List of subnet names to lookup | `list(string)` | <pre>[<br/> "arc-poc-private-subnet-private-us-east-1a",<br/> "arc-poc-private-subnet-private-us-east-1b"<br/>]</pre> | no |
| <a name="input_vpc_name"></a> [vpc\_name](#input\_vpc\_name) | Name of the VPC to add the resources | `string` | `"arc-poc-vpc"` | no |

## Outputs

| Name | Description |
|------|-------------|
| <a name="output_domain_id"></a> [domain\_id](#output\_domain\_id) | SageMaker Domain ID |
| <a name="output_domain_url"></a> [domain\_url](#output\_domain\_url) | URL to access SageMaker Studio |
| <a name="output_pipeline_arns"></a> [pipeline\_arns](#output\_pipeline\_arns) | Map of pipeline names to their ARNs |
| <a name="output_user_profiles"></a> [user\_profiles](#output\_user\_profiles) | Map of user profile names to their ARNs |
<!-- END_TF_DOCS -->
6 changes: 4 additions & 2 deletions iam.tf
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
resource "aws_iam_role" "sagemaker_execution_role" {
name = "${var.name}-sagemaker-execution-role"
count = var.create_sagemaker_execution_role ? 1 : 0
name = "${var.name}-sagemaker-execution-role"

assume_role_policy = jsonencode({
Version = "2012-10-17"
Expand All @@ -14,7 +15,8 @@ resource "aws_iam_role" "sagemaker_execution_role" {
}

resource "aws_iam_role_policy_attachment" "attach_policy" {
role = aws_iam_role.sagemaker_execution_role.name
count = var.create_sagemaker_execution_role ? 1 : 0
role = aws_iam_role.sagemaker_execution_role[0].name
policy_arn = "arn:aws:iam::aws:policy/AmazonSageMakerFullAccess"
}
# IAM Role for SageMaker Execution
Expand Down
4 changes: 2 additions & 2 deletions main.tf
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
resource "aws_sagemaker_model" "this" {
count = var.create_model ? 1 : 0
name = var.name
execution_role_arn = aws_iam_role.sagemaker_execution_role.arn
execution_role_arn = aws_iam_role.sagemaker_execution_role[0].arn
tags = var.tags

dynamic "primary_container" {
Expand Down Expand Up @@ -362,7 +362,7 @@ resource "aws_sagemaker_domain" "this" {
for_each = jupyter_lab_app_settings.value.emr_settings != null ? [jupyter_lab_app_settings.value.emr_settings] : []
content {
assumable_role_arns = emr_settings.value.assumable_role_arns
execution_role_arns = aws_iam_role.execution_role.arn
execution_role_arns = emr_settings.value.execution_role
}
}
}
Expand Down
6 changes: 6 additions & 0 deletions variables.tf
Original file line number Diff line number Diff line change
Expand Up @@ -850,6 +850,12 @@ variable "pipelines" {
default = []
}

variable "create_sagemaker_execution_role" {
description = "Whether to create an SageMaker execution role"
type = bool
default = false
}

# IAM Configuration
variable "create_execution_role" {
description = "Whether to create an execution role for SageMaker"
Expand Down
Loading