diff --git a/MountAws.UnitTests/DynamoDbRoutingTests.cs b/MountAws.UnitTests/DynamoDbRoutingTests.cs
new file mode 100644
index 0000000..6183815
--- /dev/null
+++ b/MountAws.UnitTests/DynamoDbRoutingTests.cs
@@ -0,0 +1,31 @@
+using System;
+using AwesomeAssertions;
+using MountAnything;
+using MountAnything.Routing;
+using MountAws.Services.DynamoDb;
+using Xunit;
+
+namespace MountAws.UnitTests;
+
+public class DynamoDbRoutingTests
+{
+ private readonly Router _router;
+
+ public DynamoDbRoutingTests()
+ {
+ var provider = new MountAwsProvider();
+ _router = provider.CreateRouter();
+ }
+
+ [Theory]
+ [InlineData("myprofile/us-east-1/dynamodb", typeof(DynamoDbRootHandler))]
+ [InlineData("myprofile/us-east-1/dynamodb/tables", typeof(TablesHandler))]
+ [InlineData("myprofile/us-east-1/dynamodb/tables/my-table", typeof(TableHandler))]
+ [InlineData("myprofile/us-east-1/dynamodb/tables/my-table/autoscaling", typeof(TableAutoscalingHandler))]
+ [InlineData("myprofile/us-east-1/dynamodb/tables/my-table/items", typeof(TableItemsHandler))]
+ public void DynamoDbRoutesResolveToCorrectHandlers(string path, Type expectedHandlerType)
+ {
+ var resolver = _router.GetResolver(new ItemPath(path));
+ resolver.HandlerType.Should().Be(expectedHandlerType);
+ }
+}
diff --git a/MountAws/MountAws.csproj b/MountAws/MountAws.csproj
index 9680e2b..95a6d1b 100644
--- a/MountAws/MountAws.csproj
+++ b/MountAws/MountAws.csproj
@@ -26,6 +26,7 @@
+
diff --git a/MountAws/Services/AppAutoscaling/ApiExtensions.cs b/MountAws/Services/AppAutoscaling/ApiExtensions.cs
new file mode 100644
index 0000000..fa922cd
--- /dev/null
+++ b/MountAws/Services/AppAutoscaling/ApiExtensions.cs
@@ -0,0 +1,67 @@
+using Amazon.ApplicationAutoScaling;
+using Amazon.ApplicationAutoScaling.Model;
+using static MountAws.PagingHelper;
+
+namespace MountAws.Services.AppAutoscaling;
+
+public static class ApiExtensions
+{
+ public static IEnumerable DescribeScalableTargets(this IAmazonApplicationAutoScaling autoScaling, ServiceNamespace serviceNamespace)
+ {
+ return Paginate(nextToken =>
+ {
+ var response = autoScaling.DescribeScalableTargetsAsync(new DescribeScalableTargetsRequest
+ {
+ ServiceNamespace = serviceNamespace,
+ NextToken = nextToken
+ }).GetAwaiter().GetResult();
+
+ return (response.ScalableTargets, response.NextToken);
+ });
+ }
+
+ public static IEnumerable DescribeScalingPolicies(this IAmazonApplicationAutoScaling autoScaling, ServiceNamespace serviceNamespace, string resourceId)
+ {
+ return Paginate(nextToken =>
+ {
+ var response = autoScaling.DescribeScalingPoliciesAsync(new DescribeScalingPoliciesRequest
+ {
+ ServiceNamespace = serviceNamespace,
+ ResourceId = resourceId,
+ NextToken = nextToken
+ }).GetAwaiter().GetResult();
+
+ return (response.ScalingPolicies, response.NextToken);
+ });
+ }
+
+ public static IEnumerable DescribeScalingActivities(this IAmazonApplicationAutoScaling autoScaling, ServiceNamespace serviceNamespace, string resourceId)
+ {
+ return Paginate(nextToken =>
+ {
+ var response = autoScaling.DescribeScalingActivitiesAsync(new DescribeScalingActivitiesRequest
+ {
+ ServiceNamespace = serviceNamespace,
+ ResourceId = resourceId,
+ NextToken = nextToken
+ }).GetAwaiter().GetResult();
+
+ return (response.ScalingActivities, response.NextToken);
+ });
+ }
+
+ public static IEnumerable DescribeScheduledActions(this IAmazonApplicationAutoScaling autoScaling, ServiceNamespace serviceNamespace, string resourceId)
+ {
+ return Paginate(nextToken =>
+ {
+ var response = autoScaling.DescribeScheduledActionsAsync(new DescribeScheduledActionsRequest
+ {
+ ServiceNamespace = serviceNamespace,
+ ResourceId = resourceId,
+ NextToken = nextToken
+ }).GetAwaiter().GetResult();
+
+ return (response.ScheduledActions, response.NextToken);
+ });
+ }
+}
diff --git a/MountAws/Services/AppAutoscaling/AutoscalingHandler.cs b/MountAws/Services/AppAutoscaling/AutoscalingHandler.cs
new file mode 100644
index 0000000..a078bc9
--- /dev/null
+++ b/MountAws/Services/AppAutoscaling/AutoscalingHandler.cs
@@ -0,0 +1,25 @@
+using MountAnything;
+using MountAws.Services.Core;
+
+namespace MountAws.Services.AppAutoscaling;
+
+public class AutoscalingHandler(ItemPath path, IPathHandlerContext context) : PathHandler(path, context)
+{
+ public static Item CreateItem(ItemPath parentPath)
+ {
+ return new GenericContainerItem(parentPath, "autoscaling",
+ "Navigate application autoscaling dimensions");
+ }
+
+ protected override IItem? GetItemImpl()
+ {
+ return CreateItem(ParentPath);
+ }
+
+ protected override IEnumerable GetChildItemsImpl()
+ {
+ yield return ScalingPoliciesHandler.CreateItem(Path);
+ yield return ScalingActivitiesHandler.CreateItem(Path);
+ yield return ScheduledActionsHandler.CreateItem(Path);
+ }
+}
\ No newline at end of file
diff --git a/MountAws/Services/AppAutoscaling/CurrentServiceNamespace.cs b/MountAws/Services/AppAutoscaling/CurrentServiceNamespace.cs
new file mode 100644
index 0000000..fc13aec
--- /dev/null
+++ b/MountAws/Services/AppAutoscaling/CurrentServiceNamespace.cs
@@ -0,0 +1,8 @@
+using MountAnything;
+
+namespace MountAws.Services.AppAutoscaling;
+
+public class CurrentServiceNamespace : TypedString
+{
+ public CurrentServiceNamespace(string value) : base(value) { }
+}
diff --git a/MountAws/Services/AppAutoscaling/Formats.ps1xml b/MountAws/Services/AppAutoscaling/Formats.ps1xml
new file mode 100644
index 0000000..80633e1
--- /dev/null
+++ b/MountAws/Services/AppAutoscaling/Formats.ps1xml
@@ -0,0 +1,132 @@
+
+
+
+ ScalableTarget
+
+ MountAws.Services.AppAutoscaling.ScalableTargetItem
+
+
+
+
+
+
+
+
+
+
+
+
+ ItemName
+
+
+ ScalableDimension
+
+
+ MinCapacity
+
+
+ MaxCapacity
+
+
+
+
+
+
+
+ ScalingPolicy
+
+ MountAws.Services.AppAutoscaling.ScalingPolicyItem
+
+
+
+
+
+
+
+
+
+
+
+
+ ItemName
+
+
+ PolicyType
+
+
+ ScalableDimension
+
+
+ Summary
+
+
+
+
+
+
+
+ ScalingActivity
+
+ MountAws.Services.AppAutoscaling.ScalingActivityItem
+
+
+
+
+
+
+
+
+
+
+
+
+ ItemName
+
+
+ StatusCode
+
+
+ Description
+
+
+ StartTime
+
+
+
+
+
+
+
+ ScheduledAction
+
+ MountAws.Services.AppAutoscaling.ScheduledActionItem
+
+
+
+
+
+
+
+
+
+
+
+
+ ItemName
+
+
+ Schedule
+
+
+ ScalableDimension
+
+
+ CreationTime
+
+
+
+
+
+
+
+
diff --git a/MountAws/Services/AppAutoscaling/IResourceIdResolver.cs b/MountAws/Services/AppAutoscaling/IResourceIdResolver.cs
new file mode 100644
index 0000000..8bbde56
--- /dev/null
+++ b/MountAws/Services/AppAutoscaling/IResourceIdResolver.cs
@@ -0,0 +1,6 @@
+namespace MountAws.Services.AppAutoscaling;
+
+public interface IResourceIdResolver
+{
+ string ResourceId { get; }
+}
\ No newline at end of file
diff --git a/MountAws/Services/AppAutoscaling/ItemResourceIdResolver.cs b/MountAws/Services/AppAutoscaling/ItemResourceIdResolver.cs
new file mode 100644
index 0000000..c3de7af
--- /dev/null
+++ b/MountAws/Services/AppAutoscaling/ItemResourceIdResolver.cs
@@ -0,0 +1,9 @@
+using MountAnything;
+
+namespace MountAws.Services.AppAutoscaling;
+
+public class ItemResourceIdResolver(IItemAncestor itemAncestor, Func resourceIdAccessor)
+ : IResourceIdResolver where TItem : IItem
+{
+ public string ResourceId => resourceIdAccessor(itemAncestor.Item);
+}
\ No newline at end of file
diff --git a/MountAws/Services/AppAutoscaling/Registrar.cs b/MountAws/Services/AppAutoscaling/Registrar.cs
new file mode 100644
index 0000000..31492f5
--- /dev/null
+++ b/MountAws/Services/AppAutoscaling/Registrar.cs
@@ -0,0 +1,16 @@
+using Amazon;
+using Amazon.ApplicationAutoScaling;
+using Amazon.Runtime;
+using Autofac;
+
+namespace MountAws.Services.AppAutoscaling;
+
+public class Registrar : IServiceRegistrar
+{
+ public void Register(ContainerBuilder builder)
+ {
+ builder.RegisterType()
+ .As()
+ .UsingConstructor(typeof(AWSCredentials), typeof(RegionEndpoint));
+ }
+}
diff --git a/MountAws/Services/AppAutoscaling/RouteExtensions.cs b/MountAws/Services/AppAutoscaling/RouteExtensions.cs
new file mode 100644
index 0000000..e2e7eb2
--- /dev/null
+++ b/MountAws/Services/AppAutoscaling/RouteExtensions.cs
@@ -0,0 +1,37 @@
+using Autofac;
+using MountAnything;
+using MountAnything.Routing;
+
+namespace MountAws.Services.AppAutoscaling;
+
+public static class RouteExtensions
+{
+ public static void MapAppAutoscaling(this Route route, string serviceNamespace, Func resourceIdSelector) where TItem : IItem
+ {
+ route.ConfigureContainer(c =>
+ {
+ c.RegisterInstance(new CurrentServiceNamespace(serviceNamespace));
+ c.Register(s =>
+ {
+ var item = s.Resolve>();
+
+ return new ItemResourceIdResolver(item, resourceIdSelector);
+ }).As();
+ });
+ route.MapLiteral("autoscaling", autoscaling =>
+ {
+ autoscaling.MapLiteral("scaling-policies", scalablePolicies =>
+ {
+ scalablePolicies.Map();
+ });
+ autoscaling.MapLiteral("scaling-activities", scalingActivities =>
+ {
+ scalingActivities.Map();
+ });
+ autoscaling.MapLiteral("scheduled-actions", scheduledActions =>
+ {
+ scheduledActions.Map();
+ });
+ });
+ }
+}
\ No newline at end of file
diff --git a/MountAws/Services/AppAutoscaling/ScalableTargetItem.cs b/MountAws/Services/AppAutoscaling/ScalableTargetItem.cs
new file mode 100644
index 0000000..1af99bf
--- /dev/null
+++ b/MountAws/Services/AppAutoscaling/ScalableTargetItem.cs
@@ -0,0 +1,23 @@
+using Amazon.ApplicationAutoScaling.Model;
+using MountAnything;
+
+namespace MountAws.Services.AppAutoscaling;
+
+public class ScalableTargetItem(ItemPath parentPath, ScalableTarget underlyingObject)
+ : AwsItem(parentPath, underlyingObject)
+{
+ public override string ItemName => UnderlyingObject.ResourceId.Replace("/", ":");
+ public override bool IsContainer => true;
+
+ [ItemProperty]
+ public string ScalableDimension => UnderlyingObject.ScalableDimension.Value;
+
+ [ItemProperty]
+ public int MinCapacity => UnderlyingObject.MinCapacity;
+
+ [ItemProperty]
+ public int MaxCapacity => UnderlyingObject.MaxCapacity;
+
+ [ItemProperty]
+ public string ServiceNamespace => UnderlyingObject.ServiceNamespace.Value;
+}
diff --git a/MountAws/Services/AppAutoscaling/ScalableTargetsHandler.cs b/MountAws/Services/AppAutoscaling/ScalableTargetsHandler.cs
new file mode 100644
index 0000000..d0cfddf
--- /dev/null
+++ b/MountAws/Services/AppAutoscaling/ScalableTargetsHandler.cs
@@ -0,0 +1,26 @@
+using Amazon.ApplicationAutoScaling;
+using MountAnything;
+using MountAws.Services.Core;
+
+namespace MountAws.Services.AppAutoscaling;
+
+public class ScalableTargetsHandler(ItemPath path, IPathHandlerContext context, CurrentServiceNamespace currentServiceNamespace, IAmazonApplicationAutoScaling autoScaling)
+ : PathHandler(path, context)
+{
+ public static Item CreateItem(ItemPath parentPath)
+ {
+ return new GenericContainerItem(parentPath, "scalable-targets",
+ "Navigate the scalable targets of the current service namespace.");
+ }
+
+ protected override IItem? GetItemImpl()
+ {
+ return CreateItem(ParentPath);
+ }
+
+ protected override IEnumerable GetChildItemsImpl()
+ {
+ return autoScaling.DescribeScalableTargets(currentServiceNamespace.Value)
+ .Select(target => new ScalableTargetItem(Path, target));
+ }
+}
diff --git a/MountAws/Services/AppAutoscaling/ScalingActivitiesHandler.cs b/MountAws/Services/AppAutoscaling/ScalingActivitiesHandler.cs
new file mode 100644
index 0000000..6bf9391
--- /dev/null
+++ b/MountAws/Services/AppAutoscaling/ScalingActivitiesHandler.cs
@@ -0,0 +1,32 @@
+using Amazon.ApplicationAutoScaling;
+using MountAnything;
+using MountAws.Services.Core;
+
+namespace MountAws.Services.AppAutoscaling;
+
+public class ScalingActivitiesHandler(
+ ItemPath path,
+ IPathHandlerContext context,
+ CurrentServiceNamespace currentServiceNamespace,
+ IResourceIdResolver resourceIdResolver,
+ IAmazonApplicationAutoScaling autoScaling)
+ : PathHandler(path, context)
+{
+ public static Item CreateItem(ItemPath parentPath)
+ {
+ return new GenericContainerItem(parentPath, "scaling-activities",
+ "Navigate the scaling activities for this scalable target");
+ }
+
+ protected override IItem? GetItemImpl()
+ {
+ return CreateItem(ParentPath);
+ }
+
+ protected override IEnumerable GetChildItemsImpl()
+ {
+ var serviceNamespace = new ServiceNamespace(currentServiceNamespace.Value);
+ return autoScaling.DescribeScalingActivities(serviceNamespace, resourceIdResolver.ResourceId)
+ .Select(a => new ScalingActivityItem(Path, a));
+ }
+}
diff --git a/MountAws/Services/AppAutoscaling/ScalingActivityHandler.cs b/MountAws/Services/AppAutoscaling/ScalingActivityHandler.cs
new file mode 100644
index 0000000..bd1e9c5
--- /dev/null
+++ b/MountAws/Services/AppAutoscaling/ScalingActivityHandler.cs
@@ -0,0 +1,25 @@
+using Amazon.ApplicationAutoScaling;
+using MountAnything;
+
+namespace MountAws.Services.AppAutoscaling;
+
+public class ScalingActivityHandler(
+ ItemPath path,
+ IPathHandlerContext context,
+ CurrentServiceNamespace currentServiceNamespace,
+ IResourceIdResolver resourceIdResolver,
+ IAmazonApplicationAutoScaling autoScaling) : PathHandler(path, context)
+{
+ protected override IItem? GetItemImpl()
+ {
+ var scalingActivity = autoScaling.DescribeScalingActivities(currentServiceNamespace.Value, resourceIdResolver.ResourceId)
+ .FirstOrDefault(a => a.ActivityId == ItemName);
+
+ return scalingActivity == null ? null : new ScalingActivityItem(ParentPath, scalingActivity);
+ }
+
+ protected override IEnumerable GetChildItemsImpl()
+ {
+ yield break;
+ }
+}
\ No newline at end of file
diff --git a/MountAws/Services/AppAutoscaling/ScalingActivityItem.cs b/MountAws/Services/AppAutoscaling/ScalingActivityItem.cs
new file mode 100644
index 0000000..fea538a
--- /dev/null
+++ b/MountAws/Services/AppAutoscaling/ScalingActivityItem.cs
@@ -0,0 +1,32 @@
+using Amazon.ApplicationAutoScaling.Model;
+using MountAnything;
+
+namespace MountAws.Services.AppAutoscaling;
+
+public class ScalingActivityItem(ItemPath parentPath, ScalingActivity underlyingObject)
+ : AwsItem(parentPath, underlyingObject)
+{
+ public override string ItemName => UnderlyingObject.ActivityId;
+ public override bool IsContainer => false;
+
+ [ItemProperty]
+ public string StatusCode => UnderlyingObject.StatusCode.Value;
+
+ [ItemProperty]
+ public string Description => UnderlyingObject.Description;
+
+ [ItemProperty]
+ public string Cause => UnderlyingObject.Cause;
+
+ [ItemProperty]
+ public DateTime StartTime => UnderlyingObject.StartTime;
+
+ [ItemProperty]
+ public DateTime? EndTime => UnderlyingObject.EndTime;
+
+ [ItemProperty]
+ public string ResourceId => UnderlyingObject.ResourceId;
+
+ [ItemProperty]
+ public string ScalableDimension => UnderlyingObject.ScalableDimension.Value;
+}
diff --git a/MountAws/Services/AppAutoscaling/ScalingPoliciesHandler.cs b/MountAws/Services/AppAutoscaling/ScalingPoliciesHandler.cs
new file mode 100644
index 0000000..a85a876
--- /dev/null
+++ b/MountAws/Services/AppAutoscaling/ScalingPoliciesHandler.cs
@@ -0,0 +1,32 @@
+using Amazon.ApplicationAutoScaling;
+using MountAnything;
+using MountAws.Services.Core;
+
+namespace MountAws.Services.AppAutoscaling;
+
+public class ScalingPoliciesHandler(
+ ItemPath path,
+ IPathHandlerContext context,
+ CurrentServiceNamespace currentServiceNamespace,
+ IResourceIdResolver resourceIdResolver,
+ IAmazonApplicationAutoScaling autoScaling)
+ : PathHandler(path, context)
+{
+ public static Item CreateItem(ItemPath parentPath)
+ {
+ return new GenericContainerItem(parentPath, "scaling-policies",
+ "Navigate the scaling policies for this scalable target");
+ }
+
+ protected override IItem? GetItemImpl()
+ {
+ return CreateItem(ParentPath);
+ }
+
+ protected override IEnumerable GetChildItemsImpl()
+ {
+ var serviceNamespace = new ServiceNamespace(currentServiceNamespace.Value);
+ return autoScaling.DescribeScalingPolicies(serviceNamespace, resourceIdResolver.ResourceId)
+ .Select(p => new ScalingPolicyItem(Path, p));
+ }
+}
diff --git a/MountAws/Services/AppAutoscaling/ScalingPolicyHandler.cs b/MountAws/Services/AppAutoscaling/ScalingPolicyHandler.cs
new file mode 100644
index 0000000..19ac20d
--- /dev/null
+++ b/MountAws/Services/AppAutoscaling/ScalingPolicyHandler.cs
@@ -0,0 +1,27 @@
+using Amazon.ApplicationAutoScaling;
+using MountAnything;
+
+namespace MountAws.Services.AppAutoscaling;
+
+public class ScalingPolicyHandler(
+ ItemPath path,
+ IPathHandlerContext context,
+ CurrentServiceNamespace currentServiceNamespace,
+ IResourceIdResolver resourceIdResolver,
+ IAmazonApplicationAutoScaling autoScaling)
+ : PathHandler(path, context)
+{
+ protected override IItem? GetItemImpl()
+ {
+ var serviceNamespace = new ServiceNamespace(currentServiceNamespace.Value);
+ var policy = autoScaling.DescribeScalingPolicies(serviceNamespace, resourceIdResolver.ResourceId)
+ .FirstOrDefault(p => p.PolicyName == ItemName);
+
+ return policy != null ? new ScalingPolicyItem(ParentPath, policy) : null;
+ }
+
+ protected override IEnumerable GetChildItemsImpl()
+ {
+ return Enumerable.Empty();
+ }
+}
diff --git a/MountAws/Services/AppAutoscaling/ScalingPolicyItem.cs b/MountAws/Services/AppAutoscaling/ScalingPolicyItem.cs
new file mode 100644
index 0000000..599d704
--- /dev/null
+++ b/MountAws/Services/AppAutoscaling/ScalingPolicyItem.cs
@@ -0,0 +1,49 @@
+using System.Globalization;
+using Amazon.ApplicationAutoScaling.Model;
+using MountAnything;
+
+namespace MountAws.Services.AppAutoscaling;
+
+public class ScalingPolicyItem(ItemPath parentPath, ScalingPolicy underlyingObject)
+ : AwsItem(parentPath, underlyingObject)
+{
+ public override string ItemName => UnderlyingObject.PolicyName;
+ public override bool IsContainer => false;
+
+ [ItemProperty]
+ public string ScalableDimension => UnderlyingObject.ScalableDimension.Value;
+
+ [ItemProperty]
+ public string Summary => UnderlyingObject switch
+ {
+ { TargetTrackingScalingPolicyConfiguration: not null } => GetTargetTrackingSummary(UnderlyingObject
+ .TargetTrackingScalingPolicyConfiguration),
+ { PredictiveScalingPolicyConfiguration: not null } => GetPredictiveScalingSummary(UnderlyingObject
+ .PredictiveScalingPolicyConfiguration),
+ { StepScalingPolicyConfiguration: not null } => GetStepScalingSummary(UnderlyingObject
+ .StepScalingPolicyConfiguration),
+ _ => UnderlyingObject.PolicyType.Value
+ };
+
+ private string GetTargetTrackingSummary(TargetTrackingScalingPolicyConfiguration targetTracking)
+ {
+ return targetTracking switch
+ {
+ { PredefinedMetricSpecification: not null } =>
+ $"{targetTracking.PredefinedMetricSpecification.PredefinedMetricType} {targetTracking.TargetValue}",
+ { CustomizedMetricSpecification: not null } =>
+ $"{targetTracking.CustomizedMetricSpecification.MetricName} {targetTracking.TargetValue}",
+ _ => targetTracking.TargetValue.ToString(CultureInfo.CurrentCulture)
+ };
+ }
+
+ private string GetPredictiveScalingSummary(PredictiveScalingPolicyConfiguration predectiveScaling)
+ {
+ return predectiveScaling.Mode.Value;
+ }
+
+ private string GetStepScalingSummary(StepScalingPolicyConfiguration stepScaling)
+ {
+ return $"{stepScaling.MetricAggregationType} {stepScaling.AdjustmentType}";
+ }
+}
diff --git a/MountAws/Services/AppAutoscaling/ScheduledActionHandler.cs b/MountAws/Services/AppAutoscaling/ScheduledActionHandler.cs
new file mode 100644
index 0000000..b4ab3cd
--- /dev/null
+++ b/MountAws/Services/AppAutoscaling/ScheduledActionHandler.cs
@@ -0,0 +1,27 @@
+using Amazon.ApplicationAutoScaling;
+using MountAnything;
+
+namespace MountAws.Services.AppAutoscaling;
+
+public class ScheduledActionHandler(
+ ItemPath path,
+ IPathHandlerContext context,
+ CurrentServiceNamespace currentServiceNamespace,
+ IResourceIdResolver resourceIdResolver,
+ IAmazonApplicationAutoScaling autoScaling)
+ : PathHandler(path, context)
+{
+ protected override IItem? GetItemImpl()
+ {
+ var serviceNamespace = new ServiceNamespace(currentServiceNamespace.Value);
+ var action = autoScaling.DescribeScheduledActions(serviceNamespace, resourceIdResolver.ResourceId)
+ .FirstOrDefault(a => a.ScheduledActionName == ItemName);
+
+ return action != null ? new ScheduledActionItem(ParentPath, action) : null;
+ }
+
+ protected override IEnumerable GetChildItemsImpl()
+ {
+ return Enumerable.Empty();
+ }
+}
diff --git a/MountAws/Services/AppAutoscaling/ScheduledActionItem.cs b/MountAws/Services/AppAutoscaling/ScheduledActionItem.cs
new file mode 100644
index 0000000..5780300
--- /dev/null
+++ b/MountAws/Services/AppAutoscaling/ScheduledActionItem.cs
@@ -0,0 +1,29 @@
+using Amazon.ApplicationAutoScaling.Model;
+using MountAnything;
+
+namespace MountAws.Services.AppAutoscaling;
+
+public class ScheduledActionItem(ItemPath parentPath, ScheduledAction underlyingObject)
+ : AwsItem(parentPath, underlyingObject)
+{
+ public override string ItemName => UnderlyingObject.ScheduledActionName;
+ public override bool IsContainer => false;
+
+ [ItemProperty]
+ public string Schedule => UnderlyingObject.Schedule;
+
+ [ItemProperty]
+ public string ResourceId => UnderlyingObject.ResourceId;
+
+ [ItemProperty]
+ public string ScalableDimension => UnderlyingObject.ScalableDimension.Value;
+
+ [ItemProperty]
+ public DateTime? StartTime => UnderlyingObject.StartTime;
+
+ [ItemProperty]
+ public DateTime? EndTime => UnderlyingObject.EndTime;
+
+ [ItemProperty]
+ public DateTime CreationTime => UnderlyingObject.CreationTime;
+}
diff --git a/MountAws/Services/AppAutoscaling/ScheduledActionsHandler.cs b/MountAws/Services/AppAutoscaling/ScheduledActionsHandler.cs
new file mode 100644
index 0000000..203e568
--- /dev/null
+++ b/MountAws/Services/AppAutoscaling/ScheduledActionsHandler.cs
@@ -0,0 +1,32 @@
+using Amazon.ApplicationAutoScaling;
+using MountAnything;
+using MountAws.Services.Core;
+
+namespace MountAws.Services.AppAutoscaling;
+
+public class ScheduledActionsHandler(
+ ItemPath path,
+ IPathHandlerContext context,
+ CurrentServiceNamespace currentServiceNamespace,
+ IResourceIdResolver resourceIdResolver,
+ IAmazonApplicationAutoScaling autoScaling)
+ : PathHandler(path, context)
+{
+ public static Item CreateItem(ItemPath parentPath)
+ {
+ return new GenericContainerItem(parentPath, "scheduled-actions",
+ "Navigate the scheduled actions for this scalable target");
+ }
+
+ protected override IItem? GetItemImpl()
+ {
+ return CreateItem(ParentPath);
+ }
+
+ protected override IEnumerable GetChildItemsImpl()
+ {
+ var serviceNamespace = new ServiceNamespace(currentServiceNamespace.Value);
+ return autoScaling.DescribeScheduledActions(serviceNamespace, resourceIdResolver.ResourceId)
+ .Select(a => new ScheduledActionItem(Path, a));
+ }
+}
diff --git a/MountAws/Services/AppAutoscaling/ServiceHandler.cs b/MountAws/Services/AppAutoscaling/ServiceHandler.cs
new file mode 100644
index 0000000..904a4af
--- /dev/null
+++ b/MountAws/Services/AppAutoscaling/ServiceHandler.cs
@@ -0,0 +1,23 @@
+using Amazon.ApplicationAutoScaling;
+using MountAnything;
+
+namespace MountAws.Services.AppAutoscaling;
+
+public class ServiceHandler : PathHandler
+{
+ public ServiceHandler(ItemPath path, IPathHandlerContext context) : base(path, context)
+ {
+ }
+
+ protected override IItem? GetItemImpl()
+ {
+ var serviceNamespace = new ServiceNamespace(Path.Name);
+
+ return new ServiceItem(ParentPath, serviceNamespace);
+ }
+
+ protected override IEnumerable GetChildItemsImpl()
+ {
+ yield return ScalableTargetsHandler.CreateItem(Path);
+ }
+}
\ No newline at end of file
diff --git a/MountAws/Services/AppAutoscaling/ServiceItem.cs b/MountAws/Services/AppAutoscaling/ServiceItem.cs
new file mode 100644
index 0000000..2c23e6f
--- /dev/null
+++ b/MountAws/Services/AppAutoscaling/ServiceItem.cs
@@ -0,0 +1,15 @@
+using Amazon.ApplicationAutoScaling;
+using MountAnything;
+
+namespace MountAws.Services.AppAutoscaling;
+
+public class ServiceItem : AwsItem
+{
+ public ServiceItem(ItemPath parentPath, ServiceNamespace underlyingObject) : base(parentPath, underlyingObject)
+ {
+
+ }
+
+ public override string ItemName => UnderlyingObject.Value;
+ public override bool IsContainer => true;
+}
\ No newline at end of file
diff --git a/MountAws/Services/AppAutoscaling/ServicesHandler.cs b/MountAws/Services/AppAutoscaling/ServicesHandler.cs
new file mode 100644
index 0000000..c37c085
--- /dev/null
+++ b/MountAws/Services/AppAutoscaling/ServicesHandler.cs
@@ -0,0 +1,47 @@
+using Amazon.ApplicationAutoScaling;
+using MountAnything;
+using MountAws.Services.Core;
+
+namespace MountAws.Services.AppAutoscaling;
+
+public class ServicesHandler : PathHandler
+{
+ internal static readonly ServiceNamespace[] KnownServiceNamespaces =
+ [
+ ServiceNamespace.Appstream,
+ ServiceNamespace.Cassandra,
+ ServiceNamespace.Comprehend,
+ ServiceNamespace.CustomResource,
+ ServiceNamespace.Dynamodb,
+ ServiceNamespace.Ec2,
+ ServiceNamespace.Ecs,
+ ServiceNamespace.Elasticache,
+ ServiceNamespace.Elasticmapreduce,
+ ServiceNamespace.Kafka,
+ ServiceNamespace.Lambda,
+ ServiceNamespace.Neptune,
+ ServiceNamespace.Rds,
+ ServiceNamespace.Sagemaker,
+ ServiceNamespace.Workspaces
+ ];
+
+ public static Item CreateItem(ItemPath parentPath)
+ {
+ return new GenericContainerItem(parentPath, "services",
+ "Navigate application autoscaling service namespaces");
+ }
+
+ public ServicesHandler(ItemPath path, IPathHandlerContext context) : base(path, context)
+ {
+ }
+
+ protected override IItem? GetItemImpl()
+ {
+ return CreateItem(ParentPath);
+ }
+
+ protected override IEnumerable GetChildItemsImpl()
+ {
+ return KnownServiceNamespaces.Select(ns => new ServiceItem(Path, ns));
+ }
+}
diff --git a/MountAws/Services/DynamoDb/CurrentTable.cs b/MountAws/Services/DynamoDb/CurrentTable.cs
new file mode 100644
index 0000000..0dd4926
--- /dev/null
+++ b/MountAws/Services/DynamoDb/CurrentTable.cs
@@ -0,0 +1,8 @@
+using MountAnything;
+
+namespace MountAws.Services.DynamoDb;
+
+public class CurrentTable(string tableName) : TypedString(tableName)
+{
+ public string Name => Value;
+}
\ No newline at end of file
diff --git a/MountAws/Services/DynamoDb/Routes.cs b/MountAws/Services/DynamoDb/Routes.cs
index 20f959d..be396c0 100644
--- a/MountAws/Services/DynamoDb/Routes.cs
+++ b/MountAws/Services/DynamoDb/Routes.cs
@@ -1,4 +1,5 @@
using MountAnything.Routing;
+using MountAws.Services.AppAutoscaling;
namespace MountAws.Services.DynamoDb;
@@ -10,9 +11,13 @@ public void AddServiceRoutes(Route regionRoute)
{
dynamodb.MapLiteral("tables", tables =>
{
- tables.Map(table =>
+ tables.Map(table =>
{
- table.MapRegex(@"[a-z0-9-_\.\,]+");
+ table.MapAppAutoscaling("dynamodb", item => $"table/{item.ItemName}");
+ table.MapLiteral("items", items =>
+ {
+ items.MapRegex(@"[a-z0-9-_\.\,]+");
+ });
});
});
});
diff --git a/MountAws/Services/DynamoDb/TableAutoscalingHandler.cs b/MountAws/Services/DynamoDb/TableAutoscalingHandler.cs
new file mode 100644
index 0000000..4a16abf
--- /dev/null
+++ b/MountAws/Services/DynamoDb/TableAutoscalingHandler.cs
@@ -0,0 +1,30 @@
+using Amazon.ApplicationAutoScaling;
+using Amazon.ApplicationAutoScaling.Model;
+using MountAnything;
+using MountAws.Services.AppAutoscaling;
+using MountAws.Services.Core;
+
+namespace MountAws.Services.DynamoDb;
+
+public class TableAutoscalingHandler(ItemPath path, IPathHandlerContext context, IAmazonApplicationAutoScaling autoScaling) : PathHandler(path, context)
+{
+ public static IItem CreateItem(ItemPath parentPath)
+ {
+ return new GenericContainerItem(parentPath, "autoscaling",
+ "Navigate DynamoDB autoscaling dimensions, policies and activities for this table");
+ }
+
+ protected override IItem? GetItemImpl()
+ {
+ return CreateItem(ParentPath);
+ }
+
+ protected override IEnumerable GetChildItemsImpl()
+ {
+ var tableName = ParentPath.Name;
+ return autoScaling.DescribeScalableTargets(ServiceNamespace.Dynamodb)
+ .Where(t => t.ResourceId == $"table/{tableName}")
+ .Select(t => new ScalableTargetItem(Path, t))
+ .ToList();
+ }
+}
diff --git a/MountAws/Services/DynamoDb/TableHandler.cs b/MountAws/Services/DynamoDb/TableHandler.cs
index 21d4b55..2e7230c 100644
--- a/MountAws/Services/DynamoDb/TableHandler.cs
+++ b/MountAws/Services/DynamoDb/TableHandler.cs
@@ -5,7 +5,7 @@
namespace MountAws.Services.DynamoDb;
-public class TableHandler : PathHandler, IGetChildItemParameters
+public class TableHandler : PathHandler
{
private readonly IAmazonDynamoDB _dynamo;
@@ -29,19 +29,7 @@ public TableHandler(ItemPath path, IPathHandlerContext context, IAmazonDynamoDB
protected override IEnumerable GetChildItemsImpl()
{
- var table = _dynamo.DescribeTable(ItemName);
-
- return _dynamo.Scan(ItemName, GetChildItemParameters.Limit).Select(v => new DynamoItem(Path, table.KeySchema, v));
+ yield return TableAutoscalingHandler.CreateItem(Path);
+ yield return TableItemsHandler.CreateItem(Path);
}
-
- // since we don't return the full child item set, we don't want the list of children cached
- protected override bool CacheChildren => false;
-
- public TableChildItemParameters GetChildItemParameters { get; set; } = null!;
-}
-
-public class TableChildItemParameters
-{
- [Parameter()]
- public int? Limit { get; set; }
}
\ No newline at end of file
diff --git a/MountAws/Services/DynamoDb/ItemHandler.cs b/MountAws/Services/DynamoDb/TableItemHandler.cs
similarity index 50%
rename from MountAws/Services/DynamoDb/ItemHandler.cs
rename to MountAws/Services/DynamoDb/TableItemHandler.cs
index ccd4c4e..c747da8 100644
--- a/MountAws/Services/DynamoDb/ItemHandler.cs
+++ b/MountAws/Services/DynamoDb/TableItemHandler.cs
@@ -3,20 +3,18 @@
namespace MountAws.Services.DynamoDb;
-public class ItemHandler : PathHandler
+public class TableItemHandler(
+ ItemPath path,
+ IPathHandlerContext context,
+ CurrentTable currentTable,
+ IAmazonDynamoDB dynamo)
+ : PathHandler(path, context)
{
- private readonly IAmazonDynamoDB _dynamo;
-
- public ItemHandler(ItemPath path, IPathHandlerContext context, IAmazonDynamoDB dynamo) : base(path, context)
- {
- _dynamo = dynamo;
- }
-
protected override IItem? GetItemImpl()
{
- var table = _dynamo.DescribeTable(ParentPath.Name);
+ var table = dynamo.DescribeTable(currentTable.Name);
var keys = ItemName.Split(",");
- var item = _dynamo.GetItem(table, keys);
+ var item = dynamo.GetItem(table, keys);
return new DynamoItem(ParentPath, table.KeySchema, item);
}
diff --git a/MountAws/Services/DynamoDb/TableItemsHandler.cs b/MountAws/Services/DynamoDb/TableItemsHandler.cs
new file mode 100644
index 0000000..2e068b5
--- /dev/null
+++ b/MountAws/Services/DynamoDb/TableItemsHandler.cs
@@ -0,0 +1,41 @@
+using System.Management.Automation;
+using Amazon.DynamoDBv2;
+using MountAnything;
+using MountAws.Services.Core;
+
+namespace MountAws.Services.DynamoDb;
+
+public class TableItemsHandler(
+ ItemPath path,
+ IPathHandlerContext context,
+ CurrentTable currentTable,
+ IAmazonDynamoDB dynamo) : PathHandler(path, context), IGetChildItemParameters
+{
+ public static IItem CreateItem(ItemPath parentPath)
+ {
+ return new GenericContainerItem(parentPath, "items", "Navigate the items in this table");
+ }
+
+ protected override IItem? GetItemImpl()
+ {
+ return CreateItem(ParentPath);
+ }
+
+ protected override IEnumerable GetChildItemsImpl()
+ {
+ var table = dynamo.DescribeTable(currentTable.Name);
+
+ return dynamo.Scan(currentTable.Name, GetChildItemParameters.Limit).Select(v => new DynamoItem(Path, table.KeySchema, v));
+ }
+
+ // since we don't return the full child item set, we don't want the list of children cached
+ protected override bool CacheChildren => false;
+
+ public TableItemsChildItemParameters GetChildItemParameters { get; set; } = null!;
+}
+
+public class TableItemsChildItemParameters
+{
+ [Parameter]
+ public int? Limit { get; set; }
+}
\ No newline at end of file
diff --git a/MountAws/Services/Ecs/ClustersHandler.cs b/MountAws/Services/Ecs/ClustersHandler.cs
index 664aeba..1dfaa67 100644
--- a/MountAws/Services/Ecs/ClustersHandler.cs
+++ b/MountAws/Services/Ecs/ClustersHandler.cs
@@ -7,20 +7,13 @@
namespace MountAws.Services.Ecs;
-public class ClustersHandler : PathHandler
+public class ClustersHandler(ItemPath path, IPathHandlerContext context, IAmazonECS ecs) : PathHandler(path, context)
{
- private readonly IAmazonECS _ecs;
-
public static Item CreateItem(ItemPath parentPath)
{
return new GenericContainerItem(parentPath, "clusters",
"Navigate the ECS clusters in this account and region");
}
-
- public ClustersHandler(ItemPath path, IPathHandlerContext context, IAmazonECS ecs) : base(path, context)
- {
- _ecs = ecs;
- }
protected override IItem? GetItemImpl()
{
@@ -29,8 +22,8 @@ public ClustersHandler(ItemPath path, IPathHandlerContext context, IAmazonECS ec
protected override IEnumerable GetChildItemsImpl()
{
- var clusterArns = _ecs.ListClusters();
- var clusters = _ecs.DescribeClusters(clusterArns, new []{"TAGS"});
+ var clusterArns = ecs.ListClusters();
+ var clusters = ecs.DescribeClusters(clusterArns, new []{"TAGS"});
return clusters.Select(c => new ClusterItem(Path, c)).OrderBy(c => c.ItemName);
}
diff --git a/MountAws/Services/Ecs/CurrentService.cs b/MountAws/Services/Ecs/CurrentService.cs
new file mode 100644
index 0000000..e74604e
--- /dev/null
+++ b/MountAws/Services/Ecs/CurrentService.cs
@@ -0,0 +1,8 @@
+using MountAnything;
+
+namespace MountAws.Services.Ecs;
+
+public class CurrentService(string serviceName) : TypedString(serviceName)
+{
+ public string Name => Value;
+}
\ No newline at end of file
diff --git a/MountAws/Services/Ecs/EcsApiExtensions.cs b/MountAws/Services/Ecs/EcsApiExtensions.cs
index ccbe6a7..30ed70a 100644
--- a/MountAws/Services/Ecs/EcsApiExtensions.cs
+++ b/MountAws/Services/Ecs/EcsApiExtensions.cs
@@ -35,11 +35,11 @@ public static IEnumerable ListClusters(this IAmazonECS ecs)
public static IEnumerable DescribeClusters(this IAmazonECS ecs, IEnumerable clusters, IEnumerable? include = null)
{
- return ecs.DescribeClustersAsync(new DescribeClustersRequest
+ return clusters.Chunk(100).SelectMany(clustersPage => ecs.DescribeClustersAsync(new DescribeClustersRequest
{
- Clusters = clusters.ToList(),
+ Clusters = clustersPage.ToList(),
Include = include?.ToList()
- }).GetAwaiter().GetResult().Clusters;
+ }).GetAwaiter().GetResult().Clusters);
}
public static Cluster DescribeCluster(this IAmazonECS ecs, string cluster, IEnumerable? include = null)
diff --git a/MountAws/Services/Ecs/EcsRoutes.cs b/MountAws/Services/Ecs/EcsRoutes.cs
index b35881e..f180951 100644
--- a/MountAws/Services/Ecs/EcsRoutes.cs
+++ b/MountAws/Services/Ecs/EcsRoutes.cs
@@ -1,4 +1,5 @@
using MountAnything.Routing;
+using MountAws.Services.AppAutoscaling;
namespace MountAws.Services.Ecs;
@@ -21,9 +22,13 @@ public void AddServiceRoutes(Route route)
});
cluster.MapLiteral("services", services =>
{
- services.Map(service =>
+ services.Map(service =>
{
- service.Map();
+ service.MapAppAutoscaling("ecs", item => $"service/{item.UnderlyingObject.ClusterName()}/{item.ItemName}");
+ service.MapLiteral("tasks", tasks =>
+ {
+ tasks.Map();
+ });
});
});
});
diff --git a/MountAws/Services/Ecs/ServiceHandler.cs b/MountAws/Services/Ecs/ServiceHandler.cs
index b05717d..a049359 100644
--- a/MountAws/Services/Ecs/ServiceHandler.cs
+++ b/MountAws/Services/Ecs/ServiceHandler.cs
@@ -3,6 +3,7 @@
using Amazon.ECS;
using MountAnything;
using MountAws.Api.AwsSdk.Ecs;
+using MountAws.Services.AppAutoscaling;
using MountAws.Services.Ec2;
namespace MountAws.Services.Ecs;
@@ -36,37 +37,8 @@ public ServiceHandler(ItemPath path, IPathHandlerContext context, IAmazonECS ecs
protected override IEnumerable GetChildItemsImpl()
{
- var taskArns = _ecs.ListTasksByService(_currentCluster.Name, ItemName);
-
- var tasks = taskArns.Chunk(10).SelectMany(taskArnChunk =>
- {
- return _ecs.DescribeTasks(_currentCluster.Name,
- taskArnChunk,
- new[] { "TAGS" });
- });
-
- var containerInstanceArns = tasks
- .Where(t => !string.IsNullOrEmpty(t.ContainerInstanceArn))
- .Select(t => t.ContainerInstanceArn)
- .Distinct()
- .ToArray();
- Dictionary ec2InstancesByContainerInstanceArn = new Dictionary();
- if (containerInstanceArns.Any())
- {
- var containerInstances = _ecs.DescribeContainerInstances(_currentCluster.Name, containerInstanceArns)
- .Where(c => !string.IsNullOrEmpty(c.Ec2InstanceId))
- .ToDictionary(i => i.Ec2InstanceId);
-
- var ec2InstanceIds = containerInstances.Values.Select(c => c.Ec2InstanceId);
- ec2InstancesByContainerInstanceArn = _ec2.GetInstancesByIds(ec2InstanceIds)
- .Where(instance => containerInstances.ContainsKey(instance.InstanceId))
- .ToDictionary(i => containerInstances[i.InstanceId].ContainerInstanceArn);
- }
-
- return tasks.Select(t => new TaskItem(Path, t,
- t.ContainerInstanceArn != null
- ? ec2InstancesByContainerInstanceArn.GetValueOrDefault(t.ContainerInstanceArn)
- : null, LinkGenerator, useServiceView:true));
+ yield return TasksHandler.CreateItem(Path);
+ yield return AutoscalingHandler.CreateItem(Path);
}
public void RemoveItem()
diff --git a/MountAws/Services/Ecs/TasksHandler.cs b/MountAws/Services/Ecs/TasksHandler.cs
new file mode 100644
index 0000000..6e19dc7
--- /dev/null
+++ b/MountAws/Services/Ecs/TasksHandler.cs
@@ -0,0 +1,64 @@
+using Amazon.EC2;
+using Amazon.EC2.Model;
+using Amazon.ECS;
+using MountAnything;
+using MountAws.Api.AwsSdk.Ecs;
+using MountAws.Services.Core;
+using MountAws.Services.Ec2;
+
+namespace MountAws.Services.Ecs;
+
+public class TasksHandler(
+ ItemPath path,
+ IPathHandlerContext context,
+ CurrentCluster currentCluster,
+ CurrentService currentService,
+ IAmazonECS ecs,
+ IAmazonEC2 ec2) : PathHandler(path, context)
+{
+ public static Item CreateItem(ItemPath parentPath)
+ {
+ return new GenericContainerItem(parentPath, "tasks",
+ "Navigate the ECS tasks in this service");
+ }
+
+ protected override IItem? GetItemImpl()
+ {
+ return CreateItem(ParentPath);
+ }
+
+ protected override IEnumerable GetChildItemsImpl()
+ {
+ var taskArns = ecs.ListTasksByService(currentCluster.Name, currentService.Name);
+
+ var tasks = taskArns.Chunk(10).SelectMany(taskArnChunk =>
+ {
+ return ecs.DescribeTasks(currentCluster.Name,
+ taskArnChunk,
+ new[] { "TAGS" });
+ });
+
+ var containerInstanceArns = tasks
+ .Where(t => !string.IsNullOrEmpty(t.ContainerInstanceArn))
+ .Select(t => t.ContainerInstanceArn)
+ .Distinct()
+ .ToArray();
+ Dictionary ec2InstancesByContainerInstanceArn = new Dictionary();
+ if (containerInstanceArns.Any())
+ {
+ var containerInstances = ecs.DescribeContainerInstances(currentCluster.Name, containerInstanceArns)
+ .Where(c => !string.IsNullOrEmpty(c.Ec2InstanceId))
+ .ToDictionary(i => i.Ec2InstanceId);
+
+ var ec2InstanceIds = containerInstances.Values.Select(c => c.Ec2InstanceId);
+ ec2InstancesByContainerInstanceArn = ec2.GetInstancesByIds(ec2InstanceIds)
+ .Where(instance => containerInstances.ContainsKey(instance.InstanceId))
+ .ToDictionary(i => containerInstances[i.InstanceId].ContainerInstanceArn);
+ }
+
+ return tasks.Select(t => new TaskItem(Path, t,
+ t.ContainerInstanceArn != null
+ ? ec2InstancesByContainerInstanceArn.GetValueOrDefault(t.ContainerInstanceArn)
+ : null, LinkGenerator, useServiceView:true));
+ }
+}
\ No newline at end of file