Merge pull request #858 from spulec/dashboard

Add a dashboard
This commit is contained in:
Steve Pulec 2017-03-12 20:26:42 -04:00 committed by GitHub
commit 5807a38092
42 changed files with 389 additions and 123 deletions

View File

@ -5,7 +5,7 @@ import datetime
import requests
from moto.packages.responses import responses
from moto.core import BaseBackend
from moto.core import BaseBackend, BaseModel
from moto.core.utils import iso_8601_datetime_with_milliseconds
from .utils import create_id
from .exceptions import StageNotFoundException
@ -13,7 +13,7 @@ from .exceptions import StageNotFoundException
STAGE_URL = "https://{api_id}.execute-api.{region_name}.amazonaws.com/{stage_name}"
class Deployment(dict):
class Deployment(BaseModel, dict):
def __init__(self, deployment_id, name, description=""):
super(Deployment, self).__init__()
@ -24,7 +24,7 @@ class Deployment(dict):
datetime.datetime.now())
class IntegrationResponse(dict):
class IntegrationResponse(BaseModel, dict):
def __init__(self, status_code, selection_pattern=None):
self['responseTemplates'] = {"application/json": None}
@ -33,7 +33,7 @@ class IntegrationResponse(dict):
self['selectionPattern'] = selection_pattern
class Integration(dict):
class Integration(BaseModel, dict):
def __init__(self, integration_type, uri, http_method, request_templates=None):
super(Integration, self).__init__()
@ -58,14 +58,14 @@ class Integration(dict):
return self["integrationResponses"].pop(status_code)
class MethodResponse(dict):
class MethodResponse(BaseModel, dict):
def __init__(self, status_code):
super(MethodResponse, self).__init__()
self['statusCode'] = status_code
class Method(dict):
class Method(BaseModel, dict):
def __init__(self, method_type, authorization_type):
super(Method, self).__init__()
@ -92,7 +92,7 @@ class Method(dict):
return self.method_responses.pop(response_code)
class Resource(object):
class Resource(BaseModel):
def __init__(self, id, region_name, api_id, path_part, parent_id):
self.id = id
@ -165,7 +165,7 @@ class Resource(object):
return self.resource_methods[method_type].pop('methodIntegration')
class Stage(dict):
class Stage(BaseModel, dict):
def __init__(self, name=None, deployment_id=None, variables=None,
description='', cacheClusterEnabled=False, cacheClusterSize=None):
@ -293,7 +293,7 @@ class Stage(dict):
raise Exception('Patch operation "%s" not implemented' % op['op'])
class RestAPI(object):
class RestAPI(BaseModel):
def __init__(self, id, region_name, name, description):
self.id = id

View File

@ -1,6 +1,6 @@
from __future__ import unicode_literals
from boto.ec2.blockdevicemapping import BlockDeviceType, BlockDeviceMapping
from moto.core import BaseBackend
from moto.core import BaseBackend, BaseModel
from moto.ec2 import ec2_backends
from moto.elb import elb_backends
from moto.elb.exceptions import LoadBalancerNotFoundError
@ -16,7 +16,7 @@ class InstanceState(object):
self.lifecycle_state = lifecycle_state
class FakeScalingPolicy(object):
class FakeScalingPolicy(BaseModel):
def __init__(self, name, policy_type, adjustment_type, as_name, scaling_adjustment,
cooldown, autoscaling_backend):
@ -43,7 +43,7 @@ class FakeScalingPolicy(object):
self.as_name, self.scaling_adjustment)
class FakeLaunchConfiguration(object):
class FakeLaunchConfiguration(BaseModel):
def __init__(self, name, image_id, key_name, ramdisk_id, kernel_id, security_groups, user_data,
instance_type, instance_monitoring, instance_profile_name,
@ -142,7 +142,7 @@ class FakeLaunchConfiguration(object):
return block_device_map
class FakeAutoScalingGroup(object):
class FakeAutoScalingGroup(BaseModel):
def __init__(self, name, availability_zones, desired_capacity, max_size,
min_size, launch_config_name, vpc_zone_identifier,

View File

@ -14,12 +14,12 @@ except:
from io import StringIO
import boto.awslambda
from moto.core import BaseBackend
from moto.core import BaseBackend, BaseModel
from moto.s3.models import s3_backend
from moto.s3.exceptions import MissingBucket
class LambdaFunction(object):
class LambdaFunction(BaseModel):
def __init__(self, spec):
# required

View File

@ -4,14 +4,14 @@ import json
import uuid
import boto.cloudformation
from moto.core import BaseBackend
from moto.core import BaseBackend, BaseModel
from .parsing import ResourceMap, OutputMap
from .utils import generate_stack_id
from .exceptions import ValidationError
class FakeStack(object):
class FakeStack(BaseModel):
def __init__(self, stack_id, name, template, parameters, region_name, notification_arns=None, tags=None, role_arn=None):
self.stack_id = stack_id
@ -99,7 +99,7 @@ class FakeStack(object):
self.status = "DELETE_COMPLETE"
class FakeEvent(object):
class FakeEvent(BaseModel):
def __init__(self, stack_id, stack_name, logical_resource_id, physical_resource_id, resource_type, resource_status, resource_status_reason=None, resource_properties=None):
self.stack_id = stack_id

View File

@ -1,4 +1,4 @@
from moto.core import BaseBackend
from moto.core import BaseBackend, BaseModel
import boto.ec2.cloudwatch
import datetime
@ -10,7 +10,7 @@ class Dimension(object):
self.value = value
class FakeAlarm(object):
class FakeAlarm(BaseModel):
def __init__(self, name, namespace, metric_name, comparison_operator, evaluation_periods,
period, threshold, statistic, description, dimensions, alarm_actions,
@ -34,7 +34,7 @@ class FakeAlarm(object):
self.configuration_updated_timestamp = datetime.datetime.utcnow()
class MetricDatum(object):
class MetricDatum(BaseModel):
def __init__(self, namespace, name, value, dimensions):
self.namespace = namespace

View File

@ -1,4 +1,4 @@
from __future__ import unicode_literals
from .models import BaseBackend, moto_api_backend # flake8: noqa
from .models import BaseModel, BaseBackend, moto_api_backend # flake8: noqa
moto_api_backends = {"global": moto_api_backend}

View File

@ -1,9 +1,11 @@
from __future__ import unicode_literals
from __future__ import absolute_import
from collections import defaultdict
import functools
import inspect
import re
import six
from moto import settings
from moto.packages.responses import responses
@ -208,9 +210,36 @@ class Model(type):
return dec
model_data = defaultdict(dict)
class InstanceTrackerMeta(type):
def __new__(meta, name, bases, dct):
cls = super(InstanceTrackerMeta, meta).__new__(meta, name, bases, dct)
if name == 'BaseModel':
return cls
service = cls.__module__.split(".")[1]
if name not in model_data[service]:
model_data[service][name] = cls
cls.instances = []
return cls
@six.add_metaclass(InstanceTrackerMeta)
class BaseModel(object):
def __new__(cls, *args, **kwargs):
if six.PY2:
instance = super(BaseModel, cls).__new__(cls, *args, **kwargs)
else:
instance = super(BaseModel, cls).__new__(cls)
cls.instances.append(instance)
return instance
class BaseBackend(object):
def reset(self):
for service, models in model_data.items():
for model_name, model in models.items():
model.instances = []
self.__dict__ = {}
self.__init__()

View File

@ -12,6 +12,7 @@ from jinja2 import Environment, DictLoader, TemplateNotFound
import six
from six.moves.urllib.parse import parse_qs, urlparse
from flask import render_template
import xmltodict
from pkg_resources import resource_filename
from werkzeug.exceptions import HTTPException
@ -350,6 +351,32 @@ class MotoAPIResponse(BaseResponse):
return 200, {}, json.dumps({"status": "ok"})
return 400, {}, json.dumps({"Error": "Need to POST to reset Moto"})
def model_data(self, request, full_url, headers):
from moto.core.models import model_data
results = {}
for service in sorted(model_data):
models = model_data[service]
results[service] = {}
for name in sorted(models):
model = models[name]
results[service][name] = []
for instance in model.instances:
inst_result = {}
for attr in dir(instance):
if not attr.startswith("_"):
try:
json.dumps(getattr(instance, attr))
except TypeError:
pass
else:
inst_result[attr] = getattr(instance, attr)
results[service][name].append(inst_result)
return 200, {"Content-Type": "application/javascript"}, json.dumps(results)
def dashboard(self, request, full_url, headers):
return render_template('dashboard.html')
class _RecursiveDictRef(object):
"""Store a recursive reference to dict."""

View File

@ -8,5 +8,7 @@ url_bases = [
response_instance = MotoAPIResponse()
url_paths = {
'{0}/moto-api/$': response_instance.dashboard,
'{0}/moto-api/data.json': response_instance.model_data,
'{0}/moto-api/reset': response_instance.reset_response,
}

View File

@ -122,7 +122,10 @@ class convert_flask_to_httpretty_response(object):
result = self.callback(request, request.url, {})
# result is a status, headers, response tuple
if len(result) == 3:
status, headers, content = result
else:
status, headers, content = 200, {}, result
response = Response(response=content, status=status, headers=headers)
if request.method == "HEAD" and 'content-length' in headers:

View File

@ -2,11 +2,11 @@ from __future__ import unicode_literals
import datetime
import boto.datapipeline
from moto.core import BaseBackend
from moto.core import BaseBackend, BaseModel
from .utils import get_random_pipeline_id, remove_capitalization_of_dict_keys
class PipelineObject(object):
class PipelineObject(BaseModel):
def __init__(self, object_id, name, fields):
self.object_id = object_id
@ -21,7 +21,7 @@ class PipelineObject(object):
}
class Pipeline(object):
class Pipeline(BaseModel):
def __init__(self, name, unique_id):
self.name = name

View File

@ -4,7 +4,7 @@ import datetime
import json
from moto.compat import OrderedDict
from moto.core import BaseBackend
from moto.core import BaseBackend, BaseModel
from moto.core.utils import unix_time
from .comparisons import get_comparison_func
@ -53,7 +53,7 @@ class DynamoType(object):
return comparison_func(self.value, *range_values)
class Item(object):
class Item(BaseModel):
def __init__(self, hash_key, hash_key_type, range_key, range_key_type, attrs):
self.hash_key = hash_key
@ -90,7 +90,7 @@ class Item(object):
}
class Table(object):
class Table(BaseModel):
def __init__(self, name, hash_key_attr, hash_key_type,
range_key_attr=None, range_key_type=None, read_capacity=None,

View File

@ -5,7 +5,7 @@ import decimal
import json
from moto.compat import OrderedDict
from moto.core import BaseBackend
from moto.core import BaseBackend, BaseModel
from moto.core.utils import unix_time
from .comparisons import get_comparison_func
@ -76,7 +76,7 @@ class DynamoType(object):
return comparison_func(self.cast_value, *range_values)
class Item(object):
class Item(BaseModel):
def __init__(self, hash_key, hash_key_type, range_key, range_key_type, attrs):
self.hash_key = hash_key
@ -173,7 +173,7 @@ class Item(object):
'ADD not supported for %s' % ', '.join(update_action['Value'].keys()))
class Table(object):
class Table(BaseModel):
def __init__(self, table_name, schema=None, attr=None, throughput=None, indexes=None, global_indexes=None):
self.name = table_name

View File

@ -13,7 +13,7 @@ from boto.ec2.spotinstancerequest import SpotInstanceRequest as BotoSpotRequest
from boto.ec2.launchspecification import LaunchSpecification
from moto.core import BaseBackend
from moto.core.models import Model
from moto.core.models import Model, BaseModel
from moto.core.utils import iso_8601_datetime_with_milliseconds, camelcase_to_underscores
from .exceptions import (
EC2ClientError,
@ -129,7 +129,7 @@ class StateReason(object):
self.code = code
class TaggedEC2Resource(object):
class TaggedEC2Resource(BaseModel):
def get_tags(self, *args, **kwargs):
tags = self.ec2_backend.describe_tags(
@ -2612,7 +2612,7 @@ class InternetGatewayBackend(object):
return self.describe_internet_gateways(internet_gateway_ids=igw_ids)[0]
class VPCGatewayAttachment(object):
class VPCGatewayAttachment(BaseModel):
def __init__(self, gateway_id, vpc_id):
self.gateway_id = gateway_id
@ -2633,7 +2633,7 @@ class VPCGatewayAttachment(object):
@property
def physical_resource_id(self):
return self.id
return self.vpc_id
class VPCGatewayAttachmentBackend(object):

View File

@ -2,12 +2,12 @@ from __future__ import unicode_literals
import uuid
from random import randint, random
from moto.core import BaseBackend
from moto.core import BaseBackend, BaseModel
from moto.ec2 import ec2_backends
from copy import copy
class BaseObject(object):
class BaseObject(BaseModel):
def camelCase(self, key):
words = []

View File

@ -11,7 +11,7 @@ from boto.ec2.elb.policies import (
Policies,
OtherPolicy,
)
from moto.core import BaseBackend
from moto.core import BaseBackend, BaseModel
from moto.ec2.models import ec2_backends
from .exceptions import (
LoadBalancerNotFoundError,
@ -21,7 +21,7 @@ from .exceptions import (
)
class FakeHealthCheck(object):
class FakeHealthCheck(BaseModel):
def __init__(self, timeout, healthy_threshold, unhealthy_threshold,
interval, target):
@ -34,7 +34,7 @@ class FakeHealthCheck(object):
raise BadHealthCheckDefinition
class FakeListener(object):
class FakeListener(BaseModel):
def __init__(self, load_balancer_port, instance_port, protocol, ssl_certificate_id):
self.load_balancer_port = load_balancer_port
@ -47,7 +47,7 @@ class FakeListener(object):
return "FakeListener(lbp: %s, inp: %s, pro: %s, cid: %s, policies: %s)" % (self.load_balancer_port, self.instance_port, self.protocol, self.ssl_certificate_id, self.policy_names)
class FakeBackend(object):
class FakeBackend(BaseModel):
def __init__(self, instance_port):
self.instance_port = instance_port
@ -57,7 +57,7 @@ class FakeBackend(object):
return "FakeBackend(inp: %s, policies: %s)" % (self.instance_port, self.policy_names)
class FakeLoadBalancer(object):
class FakeLoadBalancer(BaseModel):
def __init__(self, name, zones, ports, scheme='internet-facing', vpc_id=None, subnets=None):
self.name = name

View File

@ -5,12 +5,12 @@ from datetime import timedelta
import boto.emr
import pytz
from dateutil.parser import parse as dtparse
from moto.core import BaseBackend
from moto.core import BaseBackend, BaseModel
from .utils import random_instance_group_id, random_cluster_id, random_step_id
class FakeApplication(object):
class FakeApplication(BaseModel):
def __init__(self, name, version, args=None, additional_info=None):
self.additional_info = additional_info or {}
@ -19,7 +19,7 @@ class FakeApplication(object):
self.version = version
class FakeBootstrapAction(object):
class FakeBootstrapAction(BaseModel):
def __init__(self, args, name, script_path):
self.args = args or []
@ -27,7 +27,7 @@ class FakeBootstrapAction(object):
self.script_path = script_path
class FakeInstanceGroup(object):
class FakeInstanceGroup(BaseModel):
def __init__(self, instance_count, instance_role, instance_type,
market='ON_DEMAND', name=None, id=None, bid_price=None):
@ -57,7 +57,7 @@ class FakeInstanceGroup(object):
self.num_instances = instance_count
class FakeStep(object):
class FakeStep(BaseModel):
def __init__(self,
state,
@ -81,7 +81,7 @@ class FakeStep(object):
self.state = state
class FakeCluster(object):
class FakeCluster(BaseModel):
def __init__(self,
emr_backend,

View File

@ -1,10 +1,10 @@
import os
import re
from moto.core import BaseBackend
from moto.core import BaseBackend, BaseModel
class Rule(object):
class Rule(BaseModel):
def _generate_arn(self, name):
return 'arn:aws:events:us-west-2:111111111111:rule/' + name

View File

@ -3,12 +3,12 @@ from __future__ import unicode_literals
import hashlib
import boto.glacier
from moto.core import BaseBackend
from moto.core import BaseBackend, BaseModel
from .utils import get_job_id
class ArchiveJob(object):
class ArchiveJob(BaseModel):
def __init__(self, job_id, archive_id):
self.job_id = job_id
@ -35,7 +35,7 @@ class ArchiveJob(object):
}
class Vault(object):
class Vault(BaseModel):
def __init__(self, vault_name, region):
self.vault_name = vault_name

View File

@ -3,13 +3,13 @@ import base64
from datetime import datetime
import pytz
from moto.core import BaseBackend
from moto.core import BaseBackend, BaseModel
from .exceptions import IAMNotFoundException, IAMConflictException, IAMReportNotPresentException
from .utils import random_access_key, random_alphanumeric, random_resource_id, random_policy_id
class Policy(object):
class Policy(BaseModel):
is_attachable = False
@ -54,7 +54,7 @@ class InlinePolicy(Policy):
"""TODO: is this needed?"""
class Role(object):
class Role(BaseModel):
def __init__(self, role_id, name, assume_role_policy_document, path):
self.id = role_id
@ -96,7 +96,7 @@ class Role(object):
raise UnformattedGetAttTemplateException()
class InstanceProfile(object):
class InstanceProfile(BaseModel):
def __init__(self, instance_profile_id, name, path, roles):
self.id = instance_profile_id
@ -126,7 +126,7 @@ class InstanceProfile(object):
raise UnformattedGetAttTemplateException()
class Certificate(object):
class Certificate(BaseModel):
def __init__(self, cert_name, cert_body, private_key, cert_chain=None, path=None):
self.cert_name = cert_name
@ -140,7 +140,7 @@ class Certificate(object):
return self.name
class AccessKey(object):
class AccessKey(BaseModel):
def __init__(self, user_name):
self.user_name = user_name
@ -159,7 +159,7 @@ class AccessKey(object):
raise UnformattedGetAttTemplateException()
class Group(object):
class Group(BaseModel):
def __init__(self, name, path='/'):
self.name = name
@ -198,7 +198,7 @@ class Group(object):
return self.policies.keys()
class User(object):
class User(BaseModel):
def __init__(self, name, path=None):
self.name = name

View File

@ -11,13 +11,13 @@ from operator import attrgetter
from hashlib import md5
from moto.compat import OrderedDict
from moto.core import BaseBackend
from moto.core import BaseBackend, BaseModel
from .exceptions import StreamNotFoundError, ShardNotFoundError, ResourceInUseError, \
ResourceNotFoundError, InvalidArgumentError
from .utils import compose_shard_iterator, compose_new_shard_iterator, decompose_shard_iterator
class Record(object):
class Record(BaseModel):
def __init__(self, partition_key, data, sequence_number, explicit_hash_key):
self.partition_key = partition_key
@ -33,7 +33,7 @@ class Record(object):
}
class Shard(object):
class Shard(BaseModel):
def __init__(self, shard_id, starting_hash, ending_hash):
self._shard_id = shard_id
@ -94,7 +94,7 @@ class Shard(object):
}
class Stream(object):
class Stream(BaseModel):
def __init__(self, stream_name, shard_count, region):
self.stream_name = stream_name
@ -173,14 +173,14 @@ class Stream(object):
}
class FirehoseRecord(object):
class FirehoseRecord(BaseModel):
def __init__(self, record_data):
self.record_id = 12345678
self.record_data = record_data
class DeliveryStream(object):
class DeliveryStream(BaseModel):
def __init__(self, stream_name, **stream_kwargs):
self.name = stream_name

View File

@ -1,12 +1,12 @@
from __future__ import unicode_literals
import boto.kms
from moto.core import BaseBackend
from moto.core import BaseBackend, BaseModel
from .utils import generate_key_id
from collections import defaultdict
class Key(object):
class Key(BaseModel):
def __init__(self, policy, key_usage, description, region):
self.id = generate_key_id()

View File

@ -1,5 +1,5 @@
from __future__ import unicode_literals
from moto.core import BaseBackend
from moto.core import BaseBackend, BaseModel
from moto.ec2 import ec2_backends
import uuid
import datetime
@ -8,7 +8,7 @@ from random import choice
from .exceptions import ResourceNotFoundException, ValidationException
class OpsworkInstance(object):
class OpsworkInstance(BaseModel):
"""
opsworks maintains its own set of ec2 instance metadata.
This metadata exists before any instance reservations are made, and is
@ -166,7 +166,7 @@ class OpsworkInstance(object):
return d
class Layer(object):
class Layer(BaseModel):
def __init__(self, stack_id, type, name, shortname,
attributes=None,
@ -292,7 +292,7 @@ class Layer(object):
return d
class Stack(object):
class Stack(BaseModel):
def __init__(self, name, region, service_role_arn, default_instance_profile_arn,
vpcid="vpc-1f99bf7a",

View File

@ -6,13 +6,13 @@ import boto.rds
from jinja2 import Template
from moto.cloudformation.exceptions import UnformattedGetAttTemplateException
from moto.core import BaseBackend
from moto.core import BaseBackend, BaseModel
from moto.core.utils import get_random_hex
from moto.ec2.models import ec2_backends
from moto.rds2.models import rds2_backends
class Database(object):
class Database(BaseModel):
def __init__(self, **kwargs):
self.status = "available"
@ -239,7 +239,7 @@ class Database(object):
backend.delete_database(self.db_instance_identifier)
class SecurityGroup(object):
class SecurityGroup(BaseModel):
def __init__(self, group_name, description):
self.group_name = group_name
@ -317,7 +317,7 @@ class SecurityGroup(object):
backend.delete_security_group(self.group_name)
class SubnetGroup(object):
class SubnetGroup(BaseModel):
def __init__(self, subnet_name, description, subnets):
self.subnet_name = subnet_name

View File

@ -7,7 +7,7 @@ import boto.rds2
from jinja2 import Template
from re import compile as re_compile
from moto.cloudformation.exceptions import UnformattedGetAttTemplateException
from moto.core import BaseBackend
from moto.core import BaseBackend, BaseModel
from moto.core.utils import get_random_hex
from moto.ec2.models import ec2_backends
from .exceptions import (RDSClientError,
@ -17,7 +17,7 @@ from .exceptions import (RDSClientError,
DBParameterGroupNotFoundError)
class Database(object):
class Database(BaseModel):
def __init__(self, **kwargs):
self.status = "available"
@ -372,7 +372,7 @@ class Database(object):
backend.delete_database(self.db_instance_identifier)
class SecurityGroup(object):
class SecurityGroup(BaseModel):
def __init__(self, group_name, description, tags):
self.group_name = group_name
@ -481,7 +481,7 @@ class SecurityGroup(object):
backend.delete_security_group(self.group_name)
class SubnetGroup(object):
class SubnetGroup(BaseModel):
def __init__(self, subnet_name, description, subnets, tags):
self.subnet_name = subnet_name

View File

@ -1,7 +1,7 @@
from __future__ import unicode_literals
import boto.redshift
from moto.core import BaseBackend
from moto.core import BaseBackend, BaseModel
from moto.ec2 import ec2_backends
from .exceptions import (
ClusterNotFoundError,
@ -12,7 +12,7 @@ from .exceptions import (
)
class Cluster(object):
class Cluster(BaseModel):
def __init__(self, redshift_backend, cluster_identifier, node_type, master_username,
master_user_password, db_name, cluster_type, cluster_security_groups,
@ -174,7 +174,7 @@ class Cluster(object):
}
class SubnetGroup(object):
class SubnetGroup(BaseModel):
def __init__(self, ec2_backend, cluster_subnet_group_name, description, subnet_ids):
self.ec2_backend = ec2_backend
@ -220,7 +220,7 @@ class SubnetGroup(object):
}
class SecurityGroup(object):
class SecurityGroup(BaseModel):
def __init__(self, cluster_security_group_name, description):
self.cluster_security_group_name = cluster_security_group_name
@ -235,7 +235,7 @@ class SecurityGroup(object):
}
class ParameterGroup(object):
class ParameterGroup(BaseModel):
def __init__(self, cluster_parameter_group_name, group_family, description):
self.cluster_parameter_group_name = cluster_parameter_group_name

View File

@ -5,11 +5,11 @@ from collections import defaultdict
import uuid
from jinja2 import Template
from moto.core import BaseBackend
from moto.core import BaseBackend, BaseModel
from moto.core.utils import get_random_hex
class HealthCheck(object):
class HealthCheck(BaseModel):
def __init__(self, health_check_id, health_check_args):
self.id = health_check_id
@ -63,7 +63,7 @@ class HealthCheck(object):
return template.render(health_check=self)
class RecordSet(object):
class RecordSet(BaseModel):
def __init__(self, kwargs):
self.name = kwargs.get('Name')
@ -154,7 +154,7 @@ class RecordSet(object):
hosted_zone.delete_rrset_by_name(self.name)
class FakeZone(object):
class FakeZone(BaseModel):
def __init__(self, name, id_, private_zone, comment=None):
self.name = name
@ -212,7 +212,7 @@ class FakeZone(object):
return hosted_zone
class RecordSetGroup(object):
class RecordSetGroup(BaseModel):
def __init__(self, hosted_zone_id, record_sets):
self.hosted_zone_id = hosted_zone_id

View File

@ -9,7 +9,7 @@ import codecs
import six
from bisect import insort
from moto.core import BaseBackend
from moto.core import BaseBackend, BaseModel
from moto.core.utils import iso_8601_datetime_with_milliseconds, rfc_1123_datetime
from .exceptions import BucketAlreadyExists, MissingBucket, MissingKey, InvalidPart, EntityTooSmall
from .utils import clean_key_name, _VersionedKeyStore
@ -18,7 +18,7 @@ UPLOAD_ID_BYTES = 43
UPLOAD_PART_MIN_SIZE = 5242880
class FakeKey(object):
class FakeKey(BaseModel):
def __init__(self, name, value, storage="STANDARD", etag=None, is_versioned=False, version_id=0):
self.name = name
@ -119,7 +119,7 @@ class FakeKey(object):
return self._expiry.strftime("%a, %d %b %Y %H:%M:%S GMT")
class FakeMultipart(object):
class FakeMultipart(BaseModel):
def __init__(self, key_name, metadata):
self.key_name = key_name
@ -167,7 +167,7 @@ class FakeMultipart(object):
yield self.parts[part_id]
class FakeGrantee(object):
class FakeGrantee(BaseModel):
def __init__(self, id='', uri='', display_name=''):
self.id = id
@ -193,14 +193,14 @@ PERMISSION_WRITE_ACP = 'WRITE_ACP'
PERMISSION_READ_ACP = 'READ_ACP'
class FakeGrant(object):
class FakeGrant(BaseModel):
def __init__(self, grantees, permissions):
self.grantees = grantees
self.permissions = permissions
class FakeAcl(object):
class FakeAcl(BaseModel):
def __init__(self, grants=[]):
self.grants = grants
@ -234,7 +234,7 @@ def get_canned_acl(acl):
return FakeAcl(grants=grants)
class LifecycleRule(object):
class LifecycleRule(BaseModel):
def __init__(self, id=None, prefix=None, status=None, expiration_days=None,
expiration_date=None, transition_days=None,
@ -249,7 +249,7 @@ class LifecycleRule(object):
self.storage_class = storage_class
class FakeBucket(object):
class FakeBucket(BaseModel):
def __init__(self, name, region_name):
self.name = name

View File

@ -47,7 +47,7 @@ class DomainDispatcherApplication(object):
def get_application(self, environ):
path_info = environ.get('PATH_INFO', '')
if path_info.startswith("/moto-api"):
if path_info.startswith("/moto-api") or path_info == "/favicon.ico":
host = "moto_api"
elif path_info.startswith("/latest/meta-data/"):
host = "instance_metadata"

View File

@ -2,7 +2,7 @@ from __future__ import unicode_literals
import email
from moto.core import BaseBackend
from moto.core import BaseBackend, BaseModel
from .exceptions import MessageRejectedError
from .utils import get_random_message_id
@ -10,19 +10,19 @@ from .utils import get_random_message_id
RECIPIENT_LIMIT = 50
class Message(object):
class Message(BaseModel):
def __init__(self, message_id):
self.id = message_id
class RawMessage(object):
class RawMessage(BaseModel):
def __init__(self, message_id):
self.id = message_id
class SESQuota(object):
class SESQuota(BaseModel):
def __init__(self, sent):
self.sent = sent

View File

@ -9,7 +9,7 @@ import requests
import six
from moto.compat import OrderedDict
from moto.core import BaseBackend
from moto.core import BaseBackend, BaseModel
from moto.core.utils import iso_8601_datetime_with_milliseconds
from moto.sqs import sqs_backends
from .exceptions import SNSNotFoundError
@ -19,7 +19,7 @@ DEFAULT_ACCOUNT_ID = 123456789012
DEFAULT_PAGE_SIZE = 100
class Topic(object):
class Topic(BaseModel):
def __init__(self, name, sns_backend):
self.name = name
@ -67,7 +67,7 @@ class Topic(object):
return topic
class Subscription(object):
class Subscription(BaseModel):
def __init__(self, topic, endpoint, protocol):
self.topic = topic
@ -99,7 +99,7 @@ class Subscription(object):
}
class PlatformApplication(object):
class PlatformApplication(BaseModel):
def __init__(self, region, name, platform, attributes):
self.region = region
@ -116,7 +116,7 @@ class PlatformApplication(object):
)
class PlatformEndpoint(object):
class PlatformEndpoint(BaseModel):
def __init__(self, region, application, custom_user_data, token, attributes):
self.region = region

View File

@ -6,7 +6,7 @@ from xml.sax.saxutils import escape
import boto.sqs
from moto.core import BaseBackend
from moto.core import BaseBackend, BaseModel
from moto.core.utils import camelcase_to_underscores, get_random_message_id, unix_time, unix_time_millis
from .utils import generate_receipt_handle
from .exceptions import (
@ -18,7 +18,7 @@ DEFAULT_ACCOUNT_ID = 123456789012
DEFAULT_SENDER_ID = "AIDAIT2UOQQY3AUEKVGXU"
class Message(object):
class Message(BaseModel):
def __init__(self, message_id, body):
self.id = message_id
@ -93,7 +93,7 @@ class Message(object):
return False
class Queue(object):
class Queue(BaseModel):
camelcase_attributes = ['ApproximateNumberOfMessages',
'ApproximateNumberOfMessagesDelayed',
'ApproximateNumberOfMessagesNotVisible',

View File

@ -1,10 +1,10 @@
from __future__ import unicode_literals
import datetime
from moto.core import BaseBackend
from moto.core import BaseBackend, BaseModel
from moto.core.utils import iso_8601_datetime_with_milliseconds
class Token(object):
class Token(BaseModel):
def __init__(self, duration, name=None, policy=None):
now = datetime.datetime.utcnow()
@ -17,7 +17,7 @@ class Token(object):
return iso_8601_datetime_with_milliseconds(self.expiration)
class AssumedRole(object):
class AssumedRole(BaseModel):
def __init__(self, role_session_name, role_arn, policy, duration, external_id):
self.session_name = role_session_name

View File

@ -2,13 +2,14 @@ from __future__ import unicode_literals
from datetime import datetime
import uuid
from moto.core import BaseModel
from moto.core.utils import unix_time
from ..exceptions import SWFWorkflowExecutionClosedError
from .timeout import Timeout
class ActivityTask(object):
class ActivityTask(BaseModel):
def __init__(self, activity_id, activity_type, scheduled_event_id,
workflow_execution, timeouts, input=None):

View File

@ -2,13 +2,14 @@ from __future__ import unicode_literals
from datetime import datetime
import uuid
from moto.core import BaseModel
from moto.core.utils import unix_time
from ..exceptions import SWFWorkflowExecutionClosedError
from .timeout import Timeout
class DecisionTask(object):
class DecisionTask(BaseModel):
def __init__(self, workflow_execution, scheduled_event_id):
self.workflow_execution = workflow_execution

View File

@ -1,13 +1,14 @@
from __future__ import unicode_literals
from collections import defaultdict
from moto.core import BaseModel
from ..exceptions import (
SWFUnknownResourceFault,
SWFWorkflowExecutionAlreadyStartedFault,
)
class Domain(object):
class Domain(BaseModel):
def __init__(self, name, retention, description=None):
self.name = name

View File

@ -1,9 +1,10 @@
from __future__ import unicode_literals
from moto.core import BaseModel
from moto.core.utils import camelcase_to_underscores
class GenericType(object):
class GenericType(BaseModel):
def __init__(self, name, version, **kwargs):
self.name = name

View File

@ -1,5 +1,6 @@
from __future__ import unicode_literals
from moto.core import BaseModel
from moto.core.utils import underscores_to_camelcase, unix_time
from ..utils import decapitalize
@ -27,7 +28,7 @@ SUPPORTED_HISTORY_EVENT_TYPES = (
)
class HistoryEvent(object):
class HistoryEvent(BaseModel):
def __init__(self, event_id, event_type, event_timestamp=None, **kwargs):
if event_type not in SUPPORTED_HISTORY_EVENT_TYPES:

View File

@ -1,7 +1,8 @@
from moto.core import BaseModel
from moto.core.utils import unix_time
class Timeout(object):
class Timeout(BaseModel):
def __init__(self, obj, timestamp, kind):
self.obj = obj

View File

@ -1,6 +1,7 @@
from __future__ import unicode_literals
import uuid
from moto.core import BaseModel
from moto.core.utils import camelcase_to_underscores, unix_time
from ..constants import (
@ -20,7 +21,7 @@ from .timeout import Timeout
# TODO: extract decision related logic into a Decision class
class WorkflowExecution(object):
class WorkflowExecution(BaseModel):
# NB: the list is ordered exactly as in SWF validation exceptions so we can
# mimic error messages closely ; don't reorder it without checking SWF.

View File

@ -0,0 +1,186 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Moto</title>
<link href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css" rel="stylesheet">
<link href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap-theme.min.css" rel="stylesheet">
<style>
body {
padding-top: 70px;
padding-bottom: 30px;
}
.theme-dropdown .dropdown-menu {
position: static;
display: block;
margin-bottom: 20px;
}
.theme-showcase > p > .btn {
margin: 5px 0;
}
.theme-showcase .navbar .container {
width: auto;
}
</style>
</head>
<body>
<nav class="navbar navbar-inverse navbar-fixed-top">
<div class="container">
<div class="navbar-header">
<button type="button" class="navbar-toggle collapsed" data-toggle="collapse" data-target="#navbar" aria-expanded="false" aria-controls="navbar">
<span class="sr-only">Toggle navigation</span>
<span class="icon-bar"></span>
<span class="icon-bar"></span>
<span class="icon-bar"></span>
</button>
<a class="navbar-brand" href="#">Moto</a>
</div>
<div id="navbar" class="navbar-collapse collapse">
<ul class="nav navbar-nav">
<li class="active"><a href="#">Home</a></li>
<li><a href="#about" data-toggle="modal" data-target="#aboutModal">About</a></li>
</ul>
</div>
</div>
</nav>
<div class="container theme-showcase" role="main" id="main">
</div>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.1.1/jquery.min.js"></script>
<script src="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/js/bootstrap.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/handlebars.js/4.0.6/handlebars.js"></script>
{% raw %}
<script id="template" type="text/x-handlebars-template">
<ul id="myTab" class="nav nav-pills">
{{#each data}}
<li {{#if @first}}class="active"{{/if}}><a href="#{{this.name}}" data-toggle="tab">{{this.name}}</a></li>
{{/each}}
</ul>
<div id="myTabContent" class="tab-content">
{{#each data}}
<div class="tab-pane fade {{#if @first}}in active{{/if}}" id="{{this.name}}">
{{#each this}}
{{#unless @last}} <!-- Skip name key -->
<div class="page-header">
<h3>{{@key}}</h3>
</div>
<div class="row">
<div class="col-md-12">
<table class="table table-striped table-bordered table-condensed">
{{#each this}}
<tr>
{{#each this}}
<td>{{@key}}: {{this}}</td>
{{/each}}
</tr>
{{else}}
<tr><td>[]</td></tr>
{{/each}}
</table>
</div>
</div>
{{/unless}}
{{/each}}
</div>
{{/each}}
</div>
</script>
<script>
sortObject = function(obj) {
if ($.isArray(obj)) {
var result = [];
$.each(obj, function(index, array_item) {
result.push(sortObject(array_item));
})
return result;
}
if (!$.isPlainObject(obj)) {
return obj;
}
var keys = $.map(obj, function(element,index) {return index});
keys.sort();
var len = keys.length;
var result = {};
$.each(keys, function(index, key) {
var val = obj[key];
result[key] = sortObject(val);
})
return result;
}
flattenAndSortObject = function(obj) {
if (!$.isPlainObject(obj)) {
return obj;
}
var keys = $.map(obj, function(element,index) {return index});
keys.sort();
var len = keys.length;
var result = [];
$.each(keys, function(index, key) {
var val = obj[key];
val.name = key;
result.push(sortObject(val));
})
return result;
}
$(document).ready(function (){
$.getJSON("/moto-api/data.json", function(data) {
var source = $('#template').html();
var template = Handlebars.compile(source);
data = flattenAndSortObject(data);
$('#main').append(template({"data": data}));
});
})
</script>
{% endraw %}
<!-- Modal -->
<div class="modal fade" id="aboutModal" tabindex="-1" role="dialog" aria-labelledby="myModalLabel">
<div class="modal-dialog" role="document">
<div class="modal-content">
<div class="modal-header">
<button type="button" class="close" data-dismiss="modal" aria-label="Close"><span aria-hidden="true">&times;</span></button>
<h4 class="modal-title" id="myModalLabel">About Moto</h4>
</div>
<div class="modal-body">
<p>Moto was created by <a href="https://twitter.com/spulec">Steve Pulec</a> and <a href="https://github.com/spulec/moto/blob/master/AUTHORS.md">many other contributors</a>.</p>
<p>Please open any issues <a href="https://github.com/spulec/moto/issues">here</a>.</p>
</div>
</div>
</div>
</div>
</body>
</html>

View File

@ -19,3 +19,15 @@ def test_reset_api():
res.content.should.equal(b'{"status": "ok"}')
conn.list_queues().shouldnt.contain('QueueUrls') # No more queues
@mock_sqs
def test_data_api():
conn = boto3.client("sqs", region_name='us-west-1')
conn.create_queue(QueueName="queue1")
res = requests.post("{base_url}/moto-api/data.json".format(base_url=base_url))
queues = res.json()['sqs']['Queue']
len(queues).should.equal(1)
queue = queues[0]
queue['name'].should.equal("queue1")