Feature: AppConfig (#6391)
This commit is contained in:
parent
6d9f4646c1
commit
6b86113534
@ -280,6 +280,55 @@
|
||||
- [X] update_vpc_link
|
||||
</details>
|
||||
|
||||
## appconfig
|
||||
<details>
|
||||
<summary>34% implemented</summary>
|
||||
|
||||
- [X] create_application
|
||||
- [X] create_configuration_profile
|
||||
- [ ] create_deployment_strategy
|
||||
- [ ] create_environment
|
||||
- [ ] create_extension
|
||||
- [ ] create_extension_association
|
||||
- [X] create_hosted_configuration_version
|
||||
- [X] delete_application
|
||||
- [X] delete_configuration_profile
|
||||
- [ ] delete_deployment_strategy
|
||||
- [ ] delete_environment
|
||||
- [ ] delete_extension
|
||||
- [ ] delete_extension_association
|
||||
- [X] delete_hosted_configuration_version
|
||||
- [X] get_application
|
||||
- [ ] get_configuration
|
||||
- [X] get_configuration_profile
|
||||
- [ ] get_deployment
|
||||
- [ ] get_deployment_strategy
|
||||
- [ ] get_environment
|
||||
- [ ] get_extension
|
||||
- [ ] get_extension_association
|
||||
- [X] get_hosted_configuration_version
|
||||
- [ ] list_applications
|
||||
- [X] list_configuration_profiles
|
||||
- [ ] list_deployment_strategies
|
||||
- [ ] list_deployments
|
||||
- [ ] list_environments
|
||||
- [ ] list_extension_associations
|
||||
- [ ] list_extensions
|
||||
- [ ] list_hosted_configuration_versions
|
||||
- [X] list_tags_for_resource
|
||||
- [ ] start_deployment
|
||||
- [ ] stop_deployment
|
||||
- [X] tag_resource
|
||||
- [X] untag_resource
|
||||
- [X] update_application
|
||||
- [X] update_configuration_profile
|
||||
- [ ] update_deployment_strategy
|
||||
- [ ] update_environment
|
||||
- [ ] update_extension
|
||||
- [ ] update_extension_association
|
||||
- [ ] validate_configuration
|
||||
</details>
|
||||
|
||||
## application-autoscaling
|
||||
<details>
|
||||
<summary>69% implemented</summary>
|
||||
@ -7121,7 +7170,6 @@
|
||||
- amplifybackend
|
||||
- amplifyuibuilder
|
||||
- apigatewaymanagementapi
|
||||
- appconfig
|
||||
- appconfigdata
|
||||
- appflow
|
||||
- appintegrations
|
||||
|
77
docs/docs/services/appconfig.rst
Normal file
77
docs/docs/services/appconfig.rst
Normal file
@ -0,0 +1,77 @@
|
||||
.. _implementedservice_appconfig:
|
||||
|
||||
.. |start-h3| raw:: html
|
||||
|
||||
<h3>
|
||||
|
||||
.. |end-h3| raw:: html
|
||||
|
||||
</h3>
|
||||
|
||||
=========
|
||||
appconfig
|
||||
=========
|
||||
|
||||
.. autoclass:: moto.appconfig.models.AppConfigBackend
|
||||
|
||||
|start-h3| Example usage |end-h3|
|
||||
|
||||
.. sourcecode:: python
|
||||
|
||||
@mock_appconfig
|
||||
def test_appconfig_behaviour:
|
||||
boto3.client("appconfig")
|
||||
...
|
||||
|
||||
|
||||
|
||||
|start-h3| Implemented features for this service |end-h3|
|
||||
|
||||
- [X] create_application
|
||||
- [X] create_configuration_profile
|
||||
- [ ] create_deployment_strategy
|
||||
- [ ] create_environment
|
||||
- [ ] create_extension
|
||||
- [ ] create_extension_association
|
||||
- [X] create_hosted_configuration_version
|
||||
|
||||
The LatestVersionNumber-parameter is not yet implemented
|
||||
|
||||
|
||||
- [X] delete_application
|
||||
- [X] delete_configuration_profile
|
||||
- [ ] delete_deployment_strategy
|
||||
- [ ] delete_environment
|
||||
- [ ] delete_extension
|
||||
- [ ] delete_extension_association
|
||||
- [X] delete_hosted_configuration_version
|
||||
- [X] get_application
|
||||
- [ ] get_configuration
|
||||
- [X] get_configuration_profile
|
||||
- [ ] get_deployment
|
||||
- [ ] get_deployment_strategy
|
||||
- [ ] get_environment
|
||||
- [ ] get_extension
|
||||
- [ ] get_extension_association
|
||||
- [X] get_hosted_configuration_version
|
||||
- [ ] list_applications
|
||||
- [X] list_configuration_profiles
|
||||
- [ ] list_deployment_strategies
|
||||
- [ ] list_deployments
|
||||
- [ ] list_environments
|
||||
- [ ] list_extension_associations
|
||||
- [ ] list_extensions
|
||||
- [ ] list_hosted_configuration_versions
|
||||
- [X] list_tags_for_resource
|
||||
- [ ] start_deployment
|
||||
- [ ] stop_deployment
|
||||
- [X] tag_resource
|
||||
- [X] untag_resource
|
||||
- [X] update_application
|
||||
- [X] update_configuration_profile
|
||||
- [ ] update_deployment_strategy
|
||||
- [ ] update_environment
|
||||
- [ ] update_extension
|
||||
- [ ] update_extension_association
|
||||
- [ ] validate_configuration
|
||||
|
@ -20,6 +20,7 @@ mock_acmpca = lazy_load(".acmpca", "mock_acmpca", boto3_name="acm-pca")
|
||||
mock_amp = lazy_load(".amp", "mock_amp")
|
||||
mock_apigateway = lazy_load(".apigateway", "mock_apigateway")
|
||||
mock_apigatewayv2 = lazy_load(".apigatewayv2", "mock_apigatewayv2")
|
||||
mock_appconfig = lazy_load(".appconfig", "mock_appconfig")
|
||||
mock_appsync = lazy_load(".appsync", "mock_appsync")
|
||||
mock_athena = lazy_load(".athena", "mock_athena")
|
||||
mock_applicationautoscaling = lazy_load(
|
||||
|
5
moto/appconfig/__init__.py
Normal file
5
moto/appconfig/__init__.py
Normal file
@ -0,0 +1,5 @@
|
||||
"""appconfig module initialization; sets value for base decorator."""
|
||||
from .models import appconfig_backends
|
||||
from ..core.models import base_decorator
|
||||
|
||||
mock_appconfig = base_decorator(appconfig_backends)
|
19
moto/appconfig/exceptions.py
Normal file
19
moto/appconfig/exceptions.py
Normal file
@ -0,0 +1,19 @@
|
||||
"""Exceptions raised by the appconfig service."""
|
||||
from moto.core.exceptions import JsonRESTError
|
||||
|
||||
|
||||
class AppNotFoundException(JsonRESTError):
|
||||
def __init__(self) -> None:
|
||||
super().__init__("ResourceNotFoundException", "Application not found")
|
||||
|
||||
|
||||
class ConfigurationProfileNotFound(JsonRESTError):
|
||||
def __init__(self) -> None:
|
||||
super().__init__("ResourceNotFoundException", "ConfigurationProfile not found")
|
||||
|
||||
|
||||
class ConfigurationVersionNotFound(JsonRESTError):
|
||||
def __init__(self) -> None:
|
||||
super().__init__(
|
||||
"ResourceNotFoundException", "HostedConfigurationVersion not found"
|
||||
)
|
281
moto/appconfig/models.py
Normal file
281
moto/appconfig/models.py
Normal file
@ -0,0 +1,281 @@
|
||||
from moto.core import BaseBackend, BackendDict, BaseModel
|
||||
from moto.moto_api._internal import mock_random
|
||||
from moto.utilities.tagging_service import TaggingService
|
||||
from typing import Any, Dict, List, Iterable, Optional
|
||||
from .exceptions import (
|
||||
AppNotFoundException,
|
||||
ConfigurationProfileNotFound,
|
||||
ConfigurationVersionNotFound,
|
||||
)
|
||||
|
||||
|
||||
class HostedConfigurationVersion(BaseModel):
|
||||
def __init__(
|
||||
self,
|
||||
app_id: str,
|
||||
config_id: str,
|
||||
version: int,
|
||||
description: str,
|
||||
content: str,
|
||||
content_type: str,
|
||||
version_label: str,
|
||||
):
|
||||
self.app_id = app_id
|
||||
self.config_id = config_id
|
||||
self.version = version
|
||||
self.description = description
|
||||
self.content = content
|
||||
self.content_type = content_type
|
||||
self.version_label = version_label
|
||||
|
||||
def get_headers(self) -> Dict[str, Any]:
|
||||
return {
|
||||
"application-id": self.app_id,
|
||||
"configuration-profile-id": self.config_id,
|
||||
"version-number": self.version,
|
||||
"description": self.description,
|
||||
"content-type": self.content_type,
|
||||
"VersionLabel": self.version_label,
|
||||
}
|
||||
|
||||
|
||||
class ConfigurationProfile(BaseModel):
|
||||
def __init__(
|
||||
self,
|
||||
application_id: str,
|
||||
name: str,
|
||||
region: str,
|
||||
account_id: str,
|
||||
description: str,
|
||||
location_uri: str,
|
||||
retrieval_role_arn: str,
|
||||
validators: List[Dict[str, str]],
|
||||
_type: str,
|
||||
):
|
||||
self.id = mock_random.get_random_hex(7)
|
||||
self.arn = f"arn:aws:appconfig:{region}:{account_id}:application/{application_id}/configurationprofile/{self.id}"
|
||||
self.application_id = application_id
|
||||
self.name = name
|
||||
self.description = description
|
||||
self.location_uri = location_uri
|
||||
self.retrieval_role_arn = retrieval_role_arn
|
||||
self.validators = validators
|
||||
self._type = _type
|
||||
self.config_versions: Dict[int, HostedConfigurationVersion] = dict()
|
||||
|
||||
def create_version(
|
||||
self,
|
||||
app_id: str,
|
||||
config_id: str,
|
||||
description: str,
|
||||
content: str,
|
||||
content_type: str,
|
||||
version_label: str,
|
||||
) -> HostedConfigurationVersion:
|
||||
if self.config_versions:
|
||||
version = sorted(self.config_versions.keys())[-1] + 1
|
||||
else:
|
||||
version = 1
|
||||
self.config_versions[version] = HostedConfigurationVersion(
|
||||
app_id=app_id,
|
||||
config_id=config_id,
|
||||
version=version,
|
||||
description=description,
|
||||
content=content,
|
||||
content_type=content_type,
|
||||
version_label=version_label,
|
||||
)
|
||||
return self.config_versions[version]
|
||||
|
||||
def get_version(self, version: int) -> HostedConfigurationVersion:
|
||||
if version not in self.config_versions:
|
||||
raise ConfigurationVersionNotFound
|
||||
return self.config_versions[version]
|
||||
|
||||
def delete_version(self, version: int) -> None:
|
||||
self.config_versions.pop(version)
|
||||
|
||||
def to_json(self) -> Dict[str, Any]:
|
||||
return {
|
||||
"Id": self.id,
|
||||
"Name": self.name,
|
||||
"ApplicationId": self.application_id,
|
||||
"Description": self.description,
|
||||
"LocationUri": self.location_uri,
|
||||
"RetrievalRoleArn": self.retrieval_role_arn,
|
||||
"Validators": self.validators,
|
||||
"Type": self._type,
|
||||
}
|
||||
|
||||
|
||||
class Application(BaseModel):
|
||||
def __init__(
|
||||
self, name: str, description: Optional[str], region: str, account_id: str
|
||||
):
|
||||
self.id = mock_random.get_random_hex(7)
|
||||
self.arn = f"arn:aws:appconfig:{region}:{account_id}:application/{self.id}"
|
||||
self.name = name
|
||||
self.description = description
|
||||
|
||||
self.config_profiles: Dict[str, ConfigurationProfile] = dict()
|
||||
|
||||
def to_json(self) -> Dict[str, Any]:
|
||||
return {
|
||||
"Id": self.id,
|
||||
"Name": self.name,
|
||||
"Description": self.description,
|
||||
}
|
||||
|
||||
|
||||
class AppConfigBackend(BaseBackend):
|
||||
"""Implementation of AppConfig APIs."""
|
||||
|
||||
def __init__(self, region_name: str, account_id: str):
|
||||
super().__init__(region_name, account_id)
|
||||
self.applications: Dict[str, Application] = dict()
|
||||
self.tagger = TaggingService()
|
||||
|
||||
def create_application(
|
||||
self, name: str, description: Optional[str], tags: Dict[str, str]
|
||||
) -> Application:
|
||||
app = Application(
|
||||
name, description, region=self.region_name, account_id=self.account_id
|
||||
)
|
||||
self.applications[app.id] = app
|
||||
self.tag_resource(app.arn, tags)
|
||||
return app
|
||||
|
||||
def delete_application(self, app_id: str) -> None:
|
||||
self.applications.pop(app_id, None)
|
||||
|
||||
def get_application(self, app_id: str) -> Application:
|
||||
if app_id not in self.applications:
|
||||
raise AppNotFoundException
|
||||
return self.applications[app_id]
|
||||
|
||||
def update_application(
|
||||
self, application_id: str, name: str, description: str
|
||||
) -> Application:
|
||||
app = self.get_application(application_id)
|
||||
if name is not None:
|
||||
app.name = name
|
||||
if description is not None:
|
||||
app.description = description
|
||||
return app
|
||||
|
||||
def create_configuration_profile(
|
||||
self,
|
||||
application_id: str,
|
||||
name: str,
|
||||
description: str,
|
||||
location_uri: str,
|
||||
retrieval_role_arn: str,
|
||||
validators: List[Dict[str, str]],
|
||||
_type: str,
|
||||
tags: Dict[str, str],
|
||||
) -> ConfigurationProfile:
|
||||
config_profile = ConfigurationProfile(
|
||||
application_id=application_id,
|
||||
name=name,
|
||||
region=self.region_name,
|
||||
account_id=self.account_id,
|
||||
description=description,
|
||||
location_uri=location_uri,
|
||||
retrieval_role_arn=retrieval_role_arn,
|
||||
validators=validators,
|
||||
_type=_type,
|
||||
)
|
||||
self.tag_resource(config_profile.arn, tags)
|
||||
self.get_application(application_id).config_profiles[
|
||||
config_profile.id
|
||||
] = config_profile
|
||||
return config_profile
|
||||
|
||||
def delete_configuration_profile(self, app_id: str, config_profile_id: str) -> None:
|
||||
self.get_application(app_id).config_profiles.pop(config_profile_id)
|
||||
|
||||
def get_configuration_profile(
|
||||
self, app_id: str, config_profile_id: str
|
||||
) -> ConfigurationProfile:
|
||||
app = self.get_application(app_id)
|
||||
if config_profile_id not in app.config_profiles:
|
||||
raise ConfigurationProfileNotFound
|
||||
return app.config_profiles[config_profile_id]
|
||||
|
||||
def update_configuration_profile(
|
||||
self,
|
||||
application_id: str,
|
||||
config_profile_id: str,
|
||||
name: str,
|
||||
description: str,
|
||||
retrieval_role_arn: str,
|
||||
validators: List[Dict[str, str]],
|
||||
) -> ConfigurationProfile:
|
||||
config_profile = self.get_configuration_profile(
|
||||
application_id, config_profile_id
|
||||
)
|
||||
if name is not None:
|
||||
config_profile.name = name
|
||||
if description is not None:
|
||||
config_profile.description = description
|
||||
if retrieval_role_arn is not None:
|
||||
config_profile.retrieval_role_arn = retrieval_role_arn
|
||||
if validators is not None:
|
||||
config_profile.validators = validators
|
||||
return config_profile
|
||||
|
||||
def list_configuration_profiles(
|
||||
self, app_id: str
|
||||
) -> Iterable[ConfigurationProfile]:
|
||||
app = self.get_application(app_id)
|
||||
return app.config_profiles.values()
|
||||
|
||||
def create_hosted_configuration_version(
|
||||
self,
|
||||
app_id: str,
|
||||
config_profile_id: str,
|
||||
description: str,
|
||||
content: str,
|
||||
content_type: str,
|
||||
version_label: str,
|
||||
) -> HostedConfigurationVersion:
|
||||
"""
|
||||
The LatestVersionNumber-parameter is not yet implemented
|
||||
"""
|
||||
profile = self.get_configuration_profile(app_id, config_profile_id)
|
||||
return profile.create_version(
|
||||
app_id=app_id,
|
||||
config_id=config_profile_id,
|
||||
description=description,
|
||||
content=content,
|
||||
content_type=content_type,
|
||||
version_label=version_label,
|
||||
)
|
||||
|
||||
def get_hosted_configuration_version(
|
||||
self, app_id: str, config_profile_id: str, version: int
|
||||
) -> HostedConfigurationVersion:
|
||||
profile = self.get_configuration_profile(
|
||||
app_id=app_id, config_profile_id=config_profile_id
|
||||
)
|
||||
return profile.get_version(version)
|
||||
|
||||
def delete_hosted_configuration_version(
|
||||
self, app_id: str, config_profile_id: str, version: int
|
||||
) -> None:
|
||||
profile = self.get_configuration_profile(
|
||||
app_id=app_id, config_profile_id=config_profile_id
|
||||
)
|
||||
profile.delete_version(version=version)
|
||||
|
||||
def list_tags_for_resource(self, arn: str) -> Dict[str, str]:
|
||||
return self.tagger.get_tag_dict_for_resource(arn)
|
||||
|
||||
def tag_resource(self, arn: str, tags: Dict[str, str]) -> None:
|
||||
self.tagger.tag_resource(arn, TaggingService.convert_dict_to_tags_input(tags))
|
||||
|
||||
def untag_resource(self, arn: str, tag_keys: List[str]) -> None:
|
||||
self.tagger.untag_resource_using_names(arn, tag_keys)
|
||||
|
||||
|
||||
appconfig_backends = BackendDict(AppConfigBackend, "appconfig")
|
169
moto/appconfig/responses.py
Normal file
169
moto/appconfig/responses.py
Normal file
@ -0,0 +1,169 @@
|
||||
import json
|
||||
|
||||
from moto.core.responses import BaseResponse
|
||||
from .models import appconfig_backends, AppConfigBackend
|
||||
from typing import Any, Dict, Tuple
|
||||
from urllib.parse import unquote
|
||||
|
||||
|
||||
class AppConfigResponse(BaseResponse):
|
||||
def tags(self, request: Any, full_url: str, headers: Any) -> str: # type: ignore[return]
|
||||
self.setup_class(request, full_url, headers)
|
||||
if request.method == "GET":
|
||||
return self.list_tags_for_resource()
|
||||
if request.method == "POST":
|
||||
return self.tag_resource()
|
||||
if request.method == "DELETE":
|
||||
return self.untag_resource()
|
||||
|
||||
def __init__(self) -> None:
|
||||
super().__init__(service_name="appconfig")
|
||||
|
||||
@property
|
||||
def appconfig_backend(self) -> AppConfigBackend:
|
||||
return appconfig_backends[self.current_account][self.region]
|
||||
|
||||
def create_application(self) -> str:
|
||||
name = self._get_param("Name")
|
||||
description = self._get_param("Description")
|
||||
tags = self._get_param("Tags")
|
||||
app = self.appconfig_backend.create_application(
|
||||
name=name,
|
||||
description=description,
|
||||
tags=tags,
|
||||
)
|
||||
return json.dumps(app.to_json())
|
||||
|
||||
def delete_application(self) -> str:
|
||||
app_id = self._get_param("ApplicationId")
|
||||
self.appconfig_backend.delete_application(app_id)
|
||||
return "{}"
|
||||
|
||||
def get_application(self) -> str:
|
||||
app_id = self._get_param("ApplicationId")
|
||||
app = self.appconfig_backend.get_application(app_id)
|
||||
return json.dumps(app.to_json())
|
||||
|
||||
def update_application(self) -> str:
|
||||
app_id = self._get_param("ApplicationId")
|
||||
name = self._get_param("Name")
|
||||
description = self._get_param("Description")
|
||||
app = self.appconfig_backend.update_application(
|
||||
application_id=app_id,
|
||||
name=name,
|
||||
description=description,
|
||||
)
|
||||
return json.dumps(app.to_json())
|
||||
|
||||
def create_configuration_profile(self) -> str:
|
||||
app_id = self._get_param("ApplicationId")
|
||||
name = self._get_param("Name")
|
||||
description = self._get_param("Description")
|
||||
location_uri = self._get_param("LocationUri")
|
||||
retrieval_role_arn = self._get_param("RetrievalRoleArn")
|
||||
validators = self._get_param("Validators")
|
||||
_type = self._get_param("Type")
|
||||
tags = self._get_param("Tags")
|
||||
config_profile = self.appconfig_backend.create_configuration_profile(
|
||||
application_id=app_id,
|
||||
name=name,
|
||||
description=description,
|
||||
location_uri=location_uri,
|
||||
retrieval_role_arn=retrieval_role_arn,
|
||||
validators=validators,
|
||||
_type=_type,
|
||||
tags=tags,
|
||||
)
|
||||
return json.dumps(config_profile.to_json())
|
||||
|
||||
def delete_configuration_profile(self) -> str:
|
||||
app_id = self._get_param("ApplicationId")
|
||||
config_profile_id = self._get_param("ConfigurationProfileId")
|
||||
self.appconfig_backend.delete_configuration_profile(app_id, config_profile_id)
|
||||
return "{}"
|
||||
|
||||
def get_configuration_profile(self) -> str:
|
||||
app_id = self._get_param("ApplicationId")
|
||||
config_profile_id = self._get_param("ConfigurationProfileId")
|
||||
config_profile = self.appconfig_backend.get_configuration_profile(
|
||||
app_id, config_profile_id
|
||||
)
|
||||
return json.dumps(config_profile.to_json())
|
||||
|
||||
def update_configuration_profile(self) -> str:
|
||||
app_id = self._get_param("ApplicationId")
|
||||
config_profile_id = self._get_param("ConfigurationProfileId")
|
||||
name = self._get_param("Name")
|
||||
description = self._get_param("Description")
|
||||
retrieval_role_arn = self._get_param("RetrievalRoleArn")
|
||||
validators = self._get_param("Validators")
|
||||
config_profile = self.appconfig_backend.update_configuration_profile(
|
||||
application_id=app_id,
|
||||
config_profile_id=config_profile_id,
|
||||
name=name,
|
||||
description=description,
|
||||
retrieval_role_arn=retrieval_role_arn,
|
||||
validators=validators,
|
||||
)
|
||||
return json.dumps(config_profile.to_json())
|
||||
|
||||
def list_configuration_profiles(self) -> str:
|
||||
app_id = self._get_param("ApplicationId")
|
||||
profiles = self.appconfig_backend.list_configuration_profiles(app_id)
|
||||
return json.dumps({"Items": [p.to_json() for p in profiles]})
|
||||
|
||||
def list_tags_for_resource(self) -> str:
|
||||
arn = unquote(self.path.split("/tags/")[-1])
|
||||
tags = self.appconfig_backend.list_tags_for_resource(arn)
|
||||
return json.dumps({"Tags": tags})
|
||||
|
||||
def tag_resource(self) -> str:
|
||||
arn = unquote(self.path.split("/tags/")[-1])
|
||||
tags = self._get_param("Tags")
|
||||
self.appconfig_backend.tag_resource(arn, tags)
|
||||
return "{}"
|
||||
|
||||
def untag_resource(self) -> str:
|
||||
arn = unquote(self.path.split("/tags/")[-1])
|
||||
tag_keys = self.querystring.get("tagKeys")
|
||||
self.appconfig_backend.untag_resource(arn, tag_keys) # type: ignore[arg-type]
|
||||
return "{}"
|
||||
|
||||
def create_hosted_configuration_version(self) -> Tuple[str, Dict[str, Any]]:
|
||||
app_id = self._get_param("ApplicationId")
|
||||
config_profile_id = self._get_param("ConfigurationProfileId")
|
||||
description = self.headers.get("Description")
|
||||
content = self.body
|
||||
content_type = self.headers.get("Content-Type")
|
||||
version_label = self.headers.get("VersionLabel")
|
||||
version = self.appconfig_backend.create_hosted_configuration_version(
|
||||
app_id=app_id,
|
||||
config_profile_id=config_profile_id,
|
||||
description=description,
|
||||
content=content,
|
||||
content_type=content_type,
|
||||
version_label=version_label,
|
||||
)
|
||||
return version.content, version.get_headers()
|
||||
|
||||
def get_hosted_configuration_version(self) -> Tuple[str, Dict[str, Any]]:
|
||||
app_id = self._get_param("ApplicationId")
|
||||
config_profile_id = self._get_param("ConfigurationProfileId")
|
||||
version_number = self._get_int_param("VersionNumber")
|
||||
version = self.appconfig_backend.get_hosted_configuration_version(
|
||||
app_id=app_id,
|
||||
config_profile_id=config_profile_id,
|
||||
version=version_number,
|
||||
)
|
||||
return version.content, version.get_headers()
|
||||
|
||||
def delete_hosted_configuration_version(self) -> str:
|
||||
app_id = self._get_param("ApplicationId")
|
||||
config_profile_id = self._get_param("ConfigurationProfileId")
|
||||
version_number = self._get_int_param("VersionNumber")
|
||||
self.appconfig_backend.delete_hosted_configuration_version(
|
||||
app_id=app_id,
|
||||
config_profile_id=config_profile_id,
|
||||
version=version_number,
|
||||
)
|
||||
return "{}"
|
22
moto/appconfig/urls.py
Normal file
22
moto/appconfig/urls.py
Normal file
@ -0,0 +1,22 @@
|
||||
"""appconfig base URL and path."""
|
||||
from .responses import AppConfigResponse
|
||||
|
||||
url_bases = [
|
||||
r"https?://appconfig\.(.+)\.amazonaws\.com",
|
||||
]
|
||||
|
||||
|
||||
response = AppConfigResponse()
|
||||
|
||||
|
||||
url_paths = {
|
||||
"{0}/applications$": response.dispatch,
|
||||
"{0}/applications/(?P<app_id>[^/]+)$": response.dispatch,
|
||||
"{0}/applications/(?P<app_id>[^/]+)/configurationprofiles$": response.dispatch,
|
||||
"{0}/applications/(?P<app_id>[^/]+)/configurationprofiles/(?P<config_profile_id>[^/]+)$": response.dispatch,
|
||||
"{0}/applications/(?P<app_id>[^/]+)/configurationprofiles/(?P<config_profile_id>[^/]+)/hostedconfigurationversions$": response.dispatch,
|
||||
"{0}/applications/(?P<app_id>[^/]+)/configurationprofiles/(?P<config_profile_id>[^/]+)/hostedconfigurationversions/(?P<version>[^/]+)$": response.dispatch,
|
||||
"{0}/tags/(?P<app_id>.+)$": response.dispatch,
|
||||
"{0}/tags/(?P<arn_part_1>[^/]+)/(?P<app_id>[^/]+)$": response.tags,
|
||||
"{0}/tags/(?P<arn_part_1>[^/]+)/(?P<app_id>[^/]+)/configurationprofile/(?P<cp_id>[^/]+)$": response.tags,
|
||||
}
|
@ -6,6 +6,7 @@ backend_url_patterns = [
|
||||
("acm-pca", re.compile("https?://acm-pca\\.(.+)\\.amazonaws\\.com")),
|
||||
("amp", re.compile("https?://aps\\.(.+)\\.amazonaws\\.com")),
|
||||
("apigateway", re.compile("https?://apigateway\\.(.+)\\.amazonaws.com")),
|
||||
("appconfig", re.compile("https?://appconfig\\.(.+)\\.amazonaws\\.com")),
|
||||
(
|
||||
"applicationautoscaling",
|
||||
re.compile("https?://application-autoscaling\\.(.+)\\.amazonaws.com"),
|
||||
|
@ -31,6 +31,12 @@ apigatewayv2:
|
||||
- TestAccAPIGatewayV2Model
|
||||
- TestAccAPIGatewayV2Route
|
||||
- TestAccAPIGatewayV2VPCLink
|
||||
appconfig:
|
||||
- TestAccAppConfigConfigurationProfileDataSource_basic
|
||||
- TestAccAppConfigConfigurationProfile_
|
||||
- TestAccAppConfigConfigurationProfilesDataSource_basic
|
||||
- TestAccAppConfigApplication_
|
||||
- TestAccAppConfigHostedConfigurationVersion_
|
||||
autoscaling:
|
||||
- TestAccAutoScalingAttachment
|
||||
- TestAccAutoScalingGroupDataSource
|
||||
|
0
tests/test_appconfig/__init__.py
Normal file
0
tests/test_appconfig/__init__.py
Normal file
83
tests/test_appconfig/test_appconfig_applications.py
Normal file
83
tests/test_appconfig/test_appconfig_applications.py
Normal file
@ -0,0 +1,83 @@
|
||||
"""Unit tests for appconfig-supported APIs."""
|
||||
import boto3
|
||||
import pytest
|
||||
|
||||
from botocore.exceptions import ClientError
|
||||
from moto import mock_appconfig
|
||||
|
||||
# See our Development Tips on writing tests for hints on how to write good tests:
|
||||
# http://docs.getmoto.org/en/latest/docs/contributing/development_tips/tests.html
|
||||
|
||||
|
||||
@mock_appconfig
|
||||
def test_create_application():
|
||||
client = boto3.client("appconfig", region_name="ap-southeast-1")
|
||||
post = client.create_application(Name="testapp", Description="blah")
|
||||
|
||||
assert "Id" in post
|
||||
assert post["Name"] == "testapp"
|
||||
assert post["Description"] == "blah"
|
||||
|
||||
get = client.get_application(ApplicationId=post["Id"])
|
||||
assert post["Id"] == get["Id"]
|
||||
assert post["Name"] == get["Name"]
|
||||
assert post["Description"] == get["Description"]
|
||||
|
||||
update = client.update_application(
|
||||
ApplicationId=post["Id"],
|
||||
Name="name2",
|
||||
Description="desc2",
|
||||
)
|
||||
assert update["Name"] == "name2"
|
||||
assert update["Description"] == "desc2"
|
||||
|
||||
client.delete_application(ApplicationId=post["Id"])
|
||||
|
||||
with pytest.raises(ClientError) as exc:
|
||||
client.get_application(ApplicationId=post["Id"])
|
||||
err = exc.value.response["Error"]
|
||||
assert err["Code"] == "ResourceNotFoundException"
|
||||
|
||||
|
||||
@mock_appconfig
|
||||
def test_tag_application():
|
||||
client = boto3.client("appconfig", region_name="us-east-2")
|
||||
app_id = client.create_application(Name="testapp")["Id"]
|
||||
app_arn = f"arn:aws:appconfig:us-east-2:123456789012:application/{app_id}"
|
||||
|
||||
tags = client.list_tags_for_resource(ResourceArn=app_arn)["Tags"]
|
||||
assert tags == {}
|
||||
|
||||
client.tag_resource(
|
||||
ResourceArn=app_arn,
|
||||
Tags={"k1": "v1"},
|
||||
)
|
||||
|
||||
tags = client.list_tags_for_resource(ResourceArn=app_arn)["Tags"]
|
||||
assert tags == {"k1": "v1"}
|
||||
|
||||
####
|
||||
# Check this flow works when creating an app with tags
|
||||
app_id = client.create_application(Name="testapp", Tags={"k1": "v1"})["Id"]
|
||||
app_arn = f"arn:aws:appconfig:us-east-2:123456789012:application/{app_id}"
|
||||
|
||||
tags = client.list_tags_for_resource(ResourceArn=app_arn)["Tags"]
|
||||
assert tags == {"k1": "v1"}
|
||||
|
||||
client.tag_resource(
|
||||
ResourceArn=app_arn,
|
||||
Tags={"k2": "v2", "k3": "v3"},
|
||||
)
|
||||
|
||||
tags = client.list_tags_for_resource(ResourceArn=app_arn)["Tags"]
|
||||
assert tags == {"k1": "v1", "k2": "v2", "k3": "v3"}
|
||||
|
||||
client.untag_resource(ResourceArn=app_arn, TagKeys=["k2"])
|
||||
|
||||
tags = client.list_tags_for_resource(ResourceArn=app_arn)["Tags"]
|
||||
assert tags == {"k1": "v1", "k3": "v3"}
|
||||
|
||||
client.untag_resource(ResourceArn=app_arn, TagKeys=["k1", "k3"])
|
||||
|
||||
tags = client.list_tags_for_resource(ResourceArn=app_arn)["Tags"]
|
||||
assert tags == {}
|
134
tests/test_appconfig/test_appconfig_config_profiles.py
Normal file
134
tests/test_appconfig/test_appconfig_config_profiles.py
Normal file
@ -0,0 +1,134 @@
|
||||
import boto3
|
||||
import pytest
|
||||
|
||||
from botocore.exceptions import ClientError
|
||||
from moto import mock_appconfig
|
||||
|
||||
|
||||
@mock_appconfig
|
||||
def test_create_configuration_profile():
|
||||
client = boto3.client("appconfig", region_name="eu-north-1")
|
||||
app_id = client.create_application(Name="testapp")["Id"]
|
||||
resp = client.create_configuration_profile(
|
||||
ApplicationId=app_id,
|
||||
Name="config_name",
|
||||
Description="desc",
|
||||
LocationUri="luri",
|
||||
RetrievalRoleArn="rrarn:rrarn:rrarn:rrarn",
|
||||
Validators=[{"Type": "JSON", "Content": "c"}],
|
||||
Type="freeform",
|
||||
)
|
||||
del resp["ResponseMetadata"]
|
||||
assert "Id" in resp
|
||||
config_profile_id = resp.pop("Id")
|
||||
|
||||
expected_response = {
|
||||
"ApplicationId": app_id,
|
||||
"Name": "config_name",
|
||||
"Description": "desc",
|
||||
"LocationUri": "luri",
|
||||
"RetrievalRoleArn": "rrarn:rrarn:rrarn:rrarn",
|
||||
"Validators": [{"Type": "JSON", "Content": "c"}],
|
||||
"Type": "freeform",
|
||||
}
|
||||
|
||||
assert resp == expected_response
|
||||
|
||||
resp = client.get_configuration_profile(
|
||||
ApplicationId=app_id,
|
||||
ConfigurationProfileId=config_profile_id,
|
||||
)
|
||||
del resp["ResponseMetadata"]
|
||||
assert "Id" in resp
|
||||
resp.pop("Id")
|
||||
assert resp == expected_response
|
||||
|
||||
profiles = client.list_configuration_profiles(ApplicationId=app_id)["Items"]
|
||||
assert profiles == [
|
||||
{
|
||||
"ApplicationId": app_id,
|
||||
"Id": config_profile_id,
|
||||
"Name": "config_name",
|
||||
"LocationUri": "luri",
|
||||
"Type": "freeform",
|
||||
}
|
||||
]
|
||||
|
||||
update = client.update_configuration_profile(
|
||||
ApplicationId=app_id,
|
||||
ConfigurationProfileId=config_profile_id,
|
||||
Name="name2",
|
||||
Description="desc2",
|
||||
RetrievalRoleArn="rrarn:rrarn:rrarn:222",
|
||||
Validators=[],
|
||||
)
|
||||
assert update["Name"] == "name2"
|
||||
assert update["RetrievalRoleArn"] == "rrarn:rrarn:rrarn:222"
|
||||
|
||||
client.delete_configuration_profile(
|
||||
ApplicationId=app_id,
|
||||
ConfigurationProfileId=config_profile_id,
|
||||
)
|
||||
|
||||
with pytest.raises(ClientError) as exc:
|
||||
client.get_configuration_profile(
|
||||
ApplicationId=app_id,
|
||||
ConfigurationProfileId=config_profile_id,
|
||||
)
|
||||
err = exc.value.response["Error"]
|
||||
assert err["Code"] == "ResourceNotFoundException"
|
||||
|
||||
|
||||
@mock_appconfig
|
||||
def test_tag_config_profile():
|
||||
client = boto3.client("appconfig", region_name="us-east-2")
|
||||
app_id = client.create_application(Name="testapp")["Id"]
|
||||
cp_id = client.create_configuration_profile(
|
||||
ApplicationId=app_id,
|
||||
Name="config_name",
|
||||
LocationUri="luri",
|
||||
)["Id"]
|
||||
cp_arn = f"arn:aws:appconfig:us-east-2:123456789012:application/{app_id}/configurationprofile/{cp_id}"
|
||||
|
||||
tags = client.list_tags_for_resource(ResourceArn=cp_arn)["Tags"]
|
||||
assert tags == {}
|
||||
|
||||
client.tag_resource(
|
||||
ResourceArn=cp_arn,
|
||||
Tags={"k1": "v1"},
|
||||
)
|
||||
|
||||
tags = client.list_tags_for_resource(ResourceArn=cp_arn)["Tags"]
|
||||
assert tags == {"k1": "v1"}
|
||||
|
||||
####
|
||||
# Check this flow works when creating an app with tags
|
||||
app_id = client.create_application(Name="testapp")["Id"]
|
||||
cp_id = client.create_configuration_profile(
|
||||
ApplicationId=app_id,
|
||||
Name="config_name",
|
||||
LocationUri="luri",
|
||||
Tags={"k1": "v1"},
|
||||
)["Id"]
|
||||
cp_arn = f"arn:aws:appconfig:us-east-2:123456789012:application/{app_id}/configurationprofile/{cp_id}"
|
||||
|
||||
tags = client.list_tags_for_resource(ResourceArn=cp_arn)["Tags"]
|
||||
assert tags == {"k1": "v1"}
|
||||
|
||||
client.tag_resource(
|
||||
ResourceArn=cp_arn,
|
||||
Tags={"k2": "v2", "k3": "v3"},
|
||||
)
|
||||
|
||||
tags = client.list_tags_for_resource(ResourceArn=cp_arn)["Tags"]
|
||||
assert tags == {"k1": "v1", "k2": "v2", "k3": "v3"}
|
||||
|
||||
client.untag_resource(ResourceArn=cp_arn, TagKeys=["k2"])
|
||||
|
||||
tags = client.list_tags_for_resource(ResourceArn=cp_arn)["Tags"]
|
||||
assert tags == {"k1": "v1", "k3": "v3"}
|
||||
|
||||
client.untag_resource(ResourceArn=cp_arn, TagKeys=["k1", "k3"])
|
||||
|
||||
tags = client.list_tags_for_resource(ResourceArn=cp_arn)["Tags"]
|
||||
assert tags == {}
|
@ -0,0 +1,88 @@
|
||||
import boto3
|
||||
import pytest
|
||||
|
||||
from botocore.exceptions import ClientError
|
||||
from moto import mock_appconfig
|
||||
|
||||
|
||||
@mock_appconfig
|
||||
class TestHostedConfigurationVersions:
|
||||
def setup_method(self, *args): # pylint: disable=unused-argument
|
||||
self.client = boto3.client("appconfig", region_name="us-west-1")
|
||||
self.app_id = self.client.create_application(Name="testapp")["Id"]
|
||||
self.config_profile_id = self.client.create_configuration_profile(
|
||||
ApplicationId=self.app_id,
|
||||
Name="config_name",
|
||||
Description="desc",
|
||||
LocationUri="luri",
|
||||
RetrievalRoleArn="rrarn:rrarn:rrarn:rrarn",
|
||||
Validators=[{"Type": "JSON", "Content": "c"}],
|
||||
Type="freeform",
|
||||
)["Id"]
|
||||
|
||||
def test_create_hosted_configuration_version(self):
|
||||
resp = self.client.create_hosted_configuration_version(
|
||||
ApplicationId=self.app_id,
|
||||
ConfigurationProfileId=self.config_profile_id,
|
||||
Description="desc",
|
||||
Content=b"asdf",
|
||||
ContentType="text/xml",
|
||||
VersionLabel="vl",
|
||||
)
|
||||
assert resp["ApplicationId"] == self.app_id
|
||||
assert resp["ConfigurationProfileId"] == self.config_profile_id
|
||||
assert resp["VersionNumber"] == 1
|
||||
assert resp["Description"] == "desc"
|
||||
assert resp["VersionLabel"] == "vl"
|
||||
assert resp["ContentType"] == "text/xml"
|
||||
assert resp["Content"].read() == b"asdf"
|
||||
|
||||
resp = self.client.create_hosted_configuration_version(
|
||||
ApplicationId=self.app_id,
|
||||
ConfigurationProfileId=self.config_profile_id,
|
||||
Content=b"asdf",
|
||||
ContentType="text/xml",
|
||||
)
|
||||
assert resp["VersionNumber"] == 2
|
||||
|
||||
def test_get_hosted_configuration_version(self):
|
||||
self.client.create_hosted_configuration_version(
|
||||
ApplicationId=self.app_id,
|
||||
ConfigurationProfileId=self.config_profile_id,
|
||||
Description="desc",
|
||||
Content=b"asdf",
|
||||
ContentType="text/xml",
|
||||
VersionLabel="vl",
|
||||
)
|
||||
get = self.client.get_hosted_configuration_version(
|
||||
ApplicationId=self.app_id,
|
||||
ConfigurationProfileId=self.config_profile_id,
|
||||
VersionNumber=1,
|
||||
)
|
||||
assert get["ApplicationId"] == self.app_id
|
||||
assert get["ConfigurationProfileId"] == self.config_profile_id
|
||||
assert get["Description"] == "desc"
|
||||
assert get["VersionLabel"] == "vl"
|
||||
assert get["ContentType"] == "text/xml"
|
||||
assert get["Content"].read() == b"asdf"
|
||||
|
||||
def test_delete_hosted_configuration_version(self):
|
||||
self.client.create_hosted_configuration_version(
|
||||
ApplicationId=self.app_id,
|
||||
ConfigurationProfileId=self.config_profile_id,
|
||||
Content=b"asdf",
|
||||
ContentType="text/xml",
|
||||
)
|
||||
self.client.delete_hosted_configuration_version(
|
||||
ApplicationId=self.app_id,
|
||||
ConfigurationProfileId=self.config_profile_id,
|
||||
VersionNumber=1,
|
||||
)
|
||||
with pytest.raises(ClientError) as exc:
|
||||
self.client.get_hosted_configuration_version(
|
||||
ApplicationId=self.app_id,
|
||||
ConfigurationProfileId=self.config_profile_id,
|
||||
VersionNumber=1,
|
||||
)
|
||||
err = exc.value.response["Error"]
|
||||
assert err["Code"] == "ResourceNotFoundException"
|
Loading…
Reference in New Issue
Block a user