Skip to content
Open
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
878 changes: 349 additions & 529 deletions README.md

Large diffs are not rendered by default.

5 changes: 4 additions & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,10 @@ build-backend = "setuptools.build_meta"

[project]
name = "amzn-nova-forge"
description = "A Python SDK for customizing Amazon Nova models."
description = "[DEPRECATED] Use sagemaker>=3.19.0 instead. A Python SDK for customizing Amazon Nova models."
classifiers = [
"Development Status :: 7 - Inactive",
]
dynamic = ["version"]
requires-python = ">=3.12"
authors = [
Expand Down
9 changes: 9 additions & 0 deletions src/amzn_nova_forge/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,15 @@
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
import warnings

warnings.warn(
"amzn-nova-forge is deprecated. Please migrate to the SageMaker Python SDK V3 "
"(pip install 'sagemaker>=3.19.0').",
DeprecationWarning,
stacklevel=2,
)

from .core.data_mixing_config import DataMixingConfig
from .core.enums import (
DeploymentMode,
Expand Down
2 changes: 1 addition & 1 deletion src/amzn_nova_forge/__version__.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,4 +11,4 @@
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
VERSION = "1.4.9" # pragma: no cover
VERSION = "1.4.10" # pragma: no cover
46 changes: 39 additions & 7 deletions src/amzn_nova_forge/manager/glue_runtime_manager.py
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@
JobConfig,
RuntimeManager,
)
from amzn_nova_forge.util.aws_utils import get_caller_account_id
from amzn_nova_forge.util.logging import logger
from amzn_nova_forge.util.s3_utils import (
GLUE_ARTIFACT_PREFIX,
Expand Down Expand Up @@ -515,13 +516,44 @@ def required_calling_role_permissions(cls, data_s3_path=None, output_s3_path=Non

permissions.extend(
[
("glue:CreateJob", "*"),
("glue:UpdateJob", "*"),
("glue:StartJobRun", "*"),
("glue:GetJobRun", "*"),
("glue:BatchStopJobRun", "*"),
("iam:GetRole", "*"),
("iam:PassRole", "*"),
(
"glue:CreateJob",
lambda infra: (
f"arn:aws:glue:{infra.region}:{get_caller_account_id(infra.region)}:job/*"
),
),
(
"glue:UpdateJob",
lambda infra: (
f"arn:aws:glue:{infra.region}:{get_caller_account_id(infra.region)}:job/*"
),
),
(
"glue:StartJobRun",
lambda infra: (
f"arn:aws:glue:{infra.region}:{get_caller_account_id(infra.region)}:job/*"
),
),
(
"glue:GetJobRun",
lambda infra: (
f"arn:aws:glue:{infra.region}:{get_caller_account_id(infra.region)}:job/*"
),
),
(
"glue:BatchStopJobRun",
lambda infra: (
f"arn:aws:glue:{infra.region}:{get_caller_account_id(infra.region)}:job/*"
),
),
(
"iam:GetRole",
lambda infra: f"arn:aws:iam::{get_caller_account_id(infra.region)}:role/*",
),
(
"iam:PassRole",
lambda infra: f"arn:aws:iam::{get_caller_account_id(infra.region)}:role/*",
),
]
)

Expand Down
120 changes: 78 additions & 42 deletions src/amzn_nova_forge/manager/runtime_manager.py
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@
from amzn_nova_forge.core.validation_patterns import MODEL_PACKAGE_ARN_REGEX
from amzn_nova_forge.manager.mtrl_manager import MTRLOperations
from amzn_nova_forge.telemetry import Feature, _telemetry_emitter
from amzn_nova_forge.util.aws_utils import get_caller_account_id
from amzn_nova_forge.util.bedrock import (
get_customization_type,
parse_bedrock_recipe_config,
Expand Down Expand Up @@ -159,32 +160,6 @@ class DataPrepJobConfig(JobConfig):
extra_pip_packages: List[str] = field(default_factory=list)


_account_id_cache: Optional[str] = None


def _get_caller_account_id(region: str = "us-east-1") -> str:
"""Return the AWS account ID of the caller, cached to avoid redundant STS calls.

Only caches successful results — transient STS failures return "*" without poisoning
the cache, so subsequent calls will retry.
"""
global _account_id_cache
if _account_id_cache is None:
try:
_account_id_cache = boto3.client("sts", region_name=region).get_caller_identity()[
"Account"
]
except Exception:
logger.warning(
"Failed to retrieve caller account ID via STS in region %s; "
"falling back to wildcard '*'",
region,
exc_info=True,
)
return "*"
return _account_id_cache


def _poll_for_training_job(sagemaker_client, job_name: str, timeout: int) -> str:
"""Poll ``list_training_jobs`` until the submitted job appears.

Expand Down Expand Up @@ -628,9 +603,24 @@ def required_calling_role_permissions(cls, data_s3_path=None, output_s3_path=Non
# Add SMTJ-specific permissions
permissions.extend(
[
("sagemaker:CreateTrainingJob", "*"),
("sagemaker:DescribeTrainingJob", "*"),
("sagemaker:StopTrainingJob", "*"),
(
"sagemaker:CreateTrainingJob",
lambda infra: (
f"arn:aws:sagemaker:{infra.region}:{get_caller_account_id(infra.region)}:training-job/*"
),
),
(
"sagemaker:DescribeTrainingJob",
lambda infra: (
f"arn:aws:sagemaker:{infra.region}:{get_caller_account_id(infra.region)}:training-job/*"
),
),
(
"sagemaker:StopTrainingJob",
lambda infra: (
f"arn:aws:sagemaker:{infra.region}:{get_caller_account_id(infra.region)}:training-job/*"
),
),
"iam:GetRole",
"iam:PassRole",
"iam:GetPolicy",
Expand Down Expand Up @@ -1518,11 +1508,32 @@ def required_calling_role_permissions(cls, data_s3_path=None, output_s3_path=Non

permissions.extend(
[
("sagemaker:CreateTrainingJob", "*"),
("sagemaker:DescribeTrainingJob", "*"),
("sagemaker:StopTrainingJob", "*"),
("iam:GetRole", "*"),
("iam:PassRole", "*"),
(
"sagemaker:CreateTrainingJob",
lambda infra: (
f"arn:aws:sagemaker:{infra.region}:{get_caller_account_id(infra.region)}:training-job/*"
),
),
(
"sagemaker:DescribeTrainingJob",
lambda infra: (
f"arn:aws:sagemaker:{infra.region}:{get_caller_account_id(infra.region)}:training-job/*"
),
),
(
"sagemaker:StopTrainingJob",
lambda infra: (
f"arn:aws:sagemaker:{infra.region}:{get_caller_account_id(infra.region)}:training-job/*"
),
),
(
"iam:GetRole",
lambda infra: f"arn:aws:iam::{get_caller_account_id(infra.region)}:role/*",
),
(
"iam:PassRole",
lambda infra: f"arn:aws:iam::{get_caller_account_id(infra.region)}:role/*",
),
# Artifact bucket: auto-create, check existence, upload script + .whl
("s3:CreateBucket", "*"),
("s3:HeadBucket", "*"),
Expand Down Expand Up @@ -1567,19 +1578,19 @@ def required_calling_role_permissions(cls, data_s3_path=None, output_s3_path=Non
(
"sagemaker:DescribeCluster",
lambda infra: (
f"arn:aws:sagemaker:{infra.region}:{_get_caller_account_id(infra.region)}:cluster/{infra.cluster_name}"
f"arn:aws:sagemaker:{infra.region}:{get_caller_account_id(infra.region)}:cluster/{infra.cluster_name}"
),
),
(
"eks:DescribeCluster",
lambda infra: (
f"arn:aws:eks:{infra.region}:{_get_caller_account_id(infra.region)}:cluster/*"
f"arn:aws:eks:{infra.region}:{get_caller_account_id(infra.region)}:cluster/*"
),
),
(
"eks:ListAddons",
lambda infra: (
f"arn:aws:eks:{infra.region}:{_get_caller_account_id(infra.region)}:cluster/{infra.cluster_name}"
f"arn:aws:eks:{infra.region}:{get_caller_account_id(infra.region)}:cluster/{infra.cluster_name}"
),
),
("sagemaker:ListClusters", "*"),
Expand Down Expand Up @@ -2155,9 +2166,24 @@ def required_calling_role_permissions(cls, data_s3_path=None, output_s3_path=Non
# Add Bedrock-specific permissions
permissions.extend(
[
("bedrock:CreateModelCustomizationJob", "*"),
("bedrock:StopModelCustomizationJob", "*"),
("bedrock:GetModelCustomizationJob", "*"),
(
"bedrock:CreateModelCustomizationJob",
lambda infra: (
f"arn:aws:bedrock:{infra.region}:{get_caller_account_id(infra.region)}:model-customization-job/*"
),
),
(
"bedrock:StopModelCustomizationJob",
lambda infra: (
f"arn:aws:bedrock:{infra.region}:{get_caller_account_id(infra.region)}:model-customization-job/*"
),
),
(
"bedrock:GetModelCustomizationJob",
lambda infra: (
f"arn:aws:bedrock:{infra.region}:{get_caller_account_id(infra.region)}:model-customization-job/*"
),
),
"iam:PassRole",
]
)
Expand Down Expand Up @@ -2205,8 +2231,18 @@ def required_calling_role_permissions(cls, data_s3_path=None, output_s3_path=Non
# Add SMTJ-specific permissions
permissions.extend(
[
("sagemaker:CreateTrainingJob", "*"),
("sagemaker:DescribeTrainingJob", "*"),
(
"sagemaker:CreateTrainingJob",
lambda infra: (
f"arn:aws:sagemaker:{infra.region}:{get_caller_account_id(infra.region)}:training-job/*"
),
),
(
"sagemaker:DescribeTrainingJob",
lambda infra: (
f"arn:aws:sagemaker:{infra.region}:{get_caller_account_id(infra.region)}:training-job/*"
),
),
"iam:GetRole",
"iam:PassRole",
"iam:GetPolicy",
Expand Down
46 changes: 46 additions & 0 deletions src/amzn_nova_forge/util/aws_utils.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
# Copyright Amazon.com, Inc. or its affiliates

# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
"""AWS utility helpers shared across the SDK."""

import logging
from typing import Optional

import boto3

logger = logging.getLogger(__name__)

_account_id_cache: Optional[str] = None


def get_caller_account_id(region: str = "us-east-1") -> str:
"""Return the AWS account ID of the caller, cached to avoid redundant STS calls.

Only caches successful results — transient STS failures return "*" without poisoning
the cache, so subsequent calls will retry.
"""
global _account_id_cache
if _account_id_cache is None:
try:
_account_id_cache = boto3.client("sts", region_name=region).get_caller_identity()[
"Account"
]
except Exception:
logger.warning(
"Failed to retrieve caller account ID via STS in region %s; "
"falling back to wildcard '*'",
region,
exc_info=True,
)
return "*"
return _account_id_cache
7 changes: 4 additions & 3 deletions src/amzn_nova_forge/validation/validator.py
Original file line number Diff line number Diff line change
Expand Up @@ -342,10 +342,11 @@ def _validate_calling_role_permissions(
elif callable(resource_spec):
# (api_string, resource_lambda) - call lambda with infra
if infra is None:
errors.append(
f"Cannot evaluate resource lambda for {api_string}: infra is None"
raise ValueError(
f"Cannot evaluate resource ARN for {api_string}: "
"runtime manager is None. "
"Set validation_config={'iam': False} to skip IAM validation."
)
continue

try:
resource_arn = resource_spec(infra)
Expand Down
Loading
Loading