From 69a39e1c40d2344c8799f4114f7bc6e11d1bd02e Mon Sep 17 00:00:00 2001 From: yimsk Date: Fri, 2 Jan 2026 12:27:35 +0900 Subject: [PATCH 1/5] Align naming conventions with AWS CLI (#71) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Align registry names with AWS CLI conventions Rename 7 services to match AWS CLI naming: - computeoptimizer → compute-optimizer - cognito → cognito-idp - config → configservice - costexplorer → ce - eventbridge → events - macie → macie2 - sfn → stepfunctions Old names preserved as aliases for backward compatibility. Closes #65 * Fix pseudo-ARN prefix to match registry service name (ce) * Add comment explaining pseudo-ARN format for Cost Explorer * Rename directories to align with AWS CLI conventions - Service dirs: costexplorer→ce, eventbridge→events, sfn→stepfunctions, cognito→cognito-idp, config→configservice, macie→macie2, computeoptimizer→compute-optimizer, servicequotas→service-quotas, licensemanager→license-manager, networkfirewall→network-firewall - Resource dirs: hyphenate compound names (e.g., loggroups→log-groups) - Update imports and ARN mappings Closes #65 * Rename bedrock dirs to kebab-case for AWS CLI consistency * Fix DAO error strings and deduplicate client helpers * Fix error strings and deduplicate client helpers * Fix package names to match directory names (events, stepfunctions) * Add develop branch to CI triggers --- .github/workflows/ci.yml | 4 +- cmd/claws/imports_custom.go | 142 +++++++++--------- .../apigateway/{httpapis => http-apis}/dao.go | 2 +- .../{httpapis => http-apis}/register.go | 0 .../{httpapis => http-apis}/render.go | 0 .../apigateway/{restapis => rest-apis}/dao.go | 2 +- .../{restapis => rest-apis}/register.go | 0 .../{restapis => rest-apis}/render.go | 0 .../apigateway/{stagesv2 => stages-v2}/dao.go | 2 +- .../{stagesv2 => stages-v2}/register.go | 0 .../{stagesv2 => stages-v2}/render.go | 0 .../{datasources => data-sources}/dao.go | 2 +- .../{datasources => data-sources}/register.go | 0 .../{datasources => data-sources}/render.go | 0 .../{graphqlapis => graphql-apis}/dao.go | 2 +- .../{graphqlapis => graphql-apis}/register.go | 0 .../{graphqlapis => graphql-apis}/render.go | 0 .../dao.go | 2 +- .../register.go | 0 .../render.go | 0 .../dao.go | 2 +- .../register.go | 0 .../render.go | 0 .../dao.go | 2 +- .../register.go | 0 .../render.go | 0 custom/batch/{jobqueues => job-queues}/dao.go | 2 +- .../{jobqueues => job-queues}/register.go | 0 .../batch/{jobqueues => job-queues}/render.go | 0 .../agents/dao.go | 2 +- .../agents/register.go | 0 .../agents/render.go | 0 .../data-sources}/dao.go | 2 +- .../data-sources}/register.go | 0 .../data-sources}/render.go | 0 .../flows/dao.go | 2 +- .../flows/register.go | 0 .../flows/render.go | 0 .../knowledge-bases}/dao.go | 2 +- .../knowledge-bases}/register.go | 0 .../knowledge-bases}/render.go | 0 .../prompts/dao.go | 2 +- .../prompts/register.go | 0 .../prompts/render.go | 0 .../endpoints/dao.go | 2 +- .../endpoints/register.go | 0 .../endpoints/render.go | 0 .../runtimes/actions.go | 0 .../runtimes/dao.go | 2 +- .../runtimes/register.go | 0 .../runtimes/render.go | 0 .../versions/dao.go | 2 +- .../versions/register.go | 0 .../versions/render.go | 0 .../dao.go | 0 .../register.go | 0 .../render.go | 0 .../dao.go | 0 .../register.go | 0 .../render.go | 0 custom/{costexplorer => ce}/anomalies/dao.go | 4 +- .../anomalies/register.go | 2 +- .../{costexplorer => ce}/anomalies/render.go | 2 +- custom/{costexplorer => ce}/costs/dao.go | 10 +- custom/{costexplorer => ce}/costs/register.go | 2 +- custom/{costexplorer => ce}/costs/render.go | 2 +- custom/{costexplorer => ce}/monitors/dao.go | 4 +- .../{costexplorer => ce}/monitors/register.go | 2 +- .../{costexplorer => ce}/monitors/render.go | 2 +- custom/cfn/stacks/actions.go | 15 +- .../{loggroups => log-groups}/actions.go | 0 .../{loggroups => log-groups}/dao.go | 0 .../{loggroups => log-groups}/register.go | 0 .../{loggroups => log-groups}/render.go | 0 .../{logstreams => log-streams}/actions.go | 8 +- .../{logstreams => log-streams}/dao.go | 0 .../{logstreams => log-streams}/register.go | 0 .../{logstreams => log-streams}/render.go | 0 .../user-pools}/dao.go | 4 +- .../user-pools}/register.go | 2 +- .../user-pools}/render.go | 4 +- custom/{cognito => cognito-idp}/users/dao.go | 4 +- .../users/register.go | 2 +- .../{cognito => cognito-idp}/users/render.go | 2 +- .../recommendations/dao.go | 4 +- .../recommendations/register.go | 2 +- .../recommendations/render.go | 2 +- .../summary/dao.go | 4 +- .../summary/register.go | 2 +- .../summary/render.go | 2 +- custom/{config => configservice}/rules/dao.go | 4 +- .../rules/register.go | 2 +- .../{config => configservice}/rules/render.go | 2 +- .../dao.go | 2 +- .../register.go | 0 .../render.go | 0 .../dao.go | 0 .../register.go | 0 .../render.go | 0 custom/dynamodb/tables/actions.go | 8 +- .../dao.go | 0 .../register.go | 0 .../render.go | 0 .../{elasticips => elastic-ips}/actions.go | 0 custom/ec2/{elasticips => elastic-ips}/dao.go | 0 .../{elasticips => elastic-ips}/register.go | 0 .../ec2/{elasticips => elastic-ips}/render.go | 0 custom/ec2/{keypairs => key-pairs}/actions.go | 0 custom/ec2/{keypairs => key-pairs}/dao.go | 0 .../ec2/{keypairs => key-pairs}/register.go | 0 custom/ec2/{keypairs => key-pairs}/render.go | 0 .../dao.go | 0 .../register.go | 0 .../render.go | 0 .../actions.go | 0 .../dao.go | 0 .../register.go | 0 .../render.go | 0 custom/ecr/client.go | 17 +++ custom/ecr/repositories/actions.go | 8 +- .../{loadbalancers => load-balancers}/dao.go | 0 .../register.go | 0 .../render.go | 0 .../resource_test.go | 0 .../{targetgroups => target-groups}/dao.go | 0 .../register.go | 0 .../{targetgroups => target-groups}/render.go | 0 .../{eventbridge => events}/buses/actions.go | 16 +- custom/{eventbridge => events}/buses/dao.go | 4 +- .../{eventbridge => events}/buses/register.go | 2 +- .../{eventbridge => events}/buses/render.go | 4 +- custom/{eventbridge => events}/client.go | 2 +- .../{eventbridge => events}/rules/actions.go | 6 +- custom/{eventbridge => events}/rules/dao.go | 4 +- custom/{config => events}/rules/register.go | 2 +- .../{eventbridge => events}/rules/render.go | 4 +- custom/glue/{jobruns => job-runs}/dao.go | 0 custom/glue/{jobruns => job-runs}/register.go | 0 custom/glue/{jobruns => job-runs}/render.go | 0 .../dao.go | 0 .../register.go | 0 .../render.go | 0 custom/lambda/functions/actions.go | 8 +- .../configurations/dao.go | 0 .../configurations/register.go | 0 .../configurations/render.go | 0 .../grants/dao.go | 0 .../grants/register.go | 0 .../grants/render.go | 0 .../licenses/dao.go | 0 .../licenses/register.go | 0 .../licenses/render.go | 0 custom/{macie => macie2}/buckets/dao.go | 2 +- custom/{macie => macie2}/buckets/register.go | 2 +- custom/{macie => macie2}/buckets/render.go | 4 +- .../classification-jobs}/dao.go | 2 +- .../classification-jobs}/register.go | 2 +- .../classification-jobs}/render.go | 2 +- custom/{macie => macie2}/findings/dao.go | 2 +- custom/{macie => macie2}/findings/register.go | 2 +- custom/{macie => macie2}/findings/render.go | 2 +- .../firewall-policies}/dao.go | 0 .../firewall-policies}/register.go | 0 .../firewall-policies}/render.go | 0 .../firewalls/dao.go | 0 .../firewalls/register.go | 0 .../firewalls/render.go | 0 .../rule-groups}/dao.go | 0 .../rule-groups}/register.go | 0 .../rule-groups}/render.go | 0 custom/rds/snapshots/actions.go | 8 +- .../dao.go | 2 +- .../register.go | 0 .../render.go | 0 .../{savingsplans => savings-plans}/dao.go | 2 +- .../register.go | 0 .../{savingsplans => savings-plans}/render.go | 0 .../{hostedzones => hosted-zones}/dao.go | 0 .../{hostedzones => hosted-zones}/register.go | 0 .../{hostedzones => hosted-zones}/render.go | 0 .../{recordsets => record-sets}/dao.go | 0 .../{recordsets => record-sets}/register.go | 0 .../{recordsets => record-sets}/render.go | 0 .../{trainingjobs => training-jobs}/dao.go | 0 .../register.go | 0 .../{trainingjobs => training-jobs}/render.go | 0 custom/secretsmanager/client.go | 17 +++ custom/secretsmanager/secrets/actions.go | 7 +- .../quotas/dao.go | 0 .../quotas/register.go | 0 .../quotas/render.go | 0 .../services/dao.go | 0 .../services/register.go | 0 .../services/render.go | 0 custom/sns/subscriptions/actions.go | 8 +- custom/sns/topics/actions.go | 8 +- custom/sqs/queues/actions.go | 7 +- custom/{sfn => stepfunctions}/client.go | 2 +- .../executions/actions.go | 6 +- .../{sfn => stepfunctions}/executions/dao.go | 4 +- .../executions/register.go | 2 +- .../executions/render.go | 4 +- .../state-machines}/actions.go | 16 +- .../state-machines}/dao.go | 4 +- .../state-machines}/register.go | 2 +- .../state-machines}/render.go | 4 +- custom/vpc/{vpcendpoints => endpoints}/dao.go | 4 +- .../{vpcendpoints => endpoints}/register.go | 0 .../vpc/{vpcendpoints => endpoints}/render.go | 0 .../actions.go | 0 .../dao.go | 0 .../register.go | 0 .../render.go | 0 .../{natgateways => nat-gateways}/actions.go | 0 .../vpc/{natgateways => nat-gateways}/dao.go | 0 .../{natgateways => nat-gateways}/register.go | 0 .../{natgateways => nat-gateways}/render.go | 0 .../{routetables => route-tables}/actions.go | 0 .../vpc/{routetables => route-tables}/dao.go | 0 .../{routetables => route-tables}/register.go | 0 .../{routetables => route-tables}/render.go | 0 .../dao.go | 4 +- .../register.go | 0 .../render.go | 0 .../dao.go | 0 .../register.go | 0 .../render.go | 0 custom/wafv2/{webacls => web-acls}/dao.go | 0 .../wafv2/{webacls => web-acls}/register.go | 0 custom/wafv2/{webacls => web-acls}/render.go | 0 internal/aws/arn.go | 6 +- internal/aws/arn_test.go | 4 +- internal/genimports/genimports.go | 136 ++++++++--------- internal/registry/registry.go | 131 ++++++++-------- internal/view/dashboard_view.go | 2 +- internal/view/dashboard_view_fetch.go | 4 +- internal/view/dashboard_view_panels.go | 2 +- internal/view/dashboard_view_test.go | 2 +- 238 files changed, 381 insertions(+), 399 deletions(-) rename custom/apigateway/{httpapis => http-apis}/dao.go (98%) rename custom/apigateway/{httpapis => http-apis}/register.go (100%) rename custom/apigateway/{httpapis => http-apis}/render.go (100%) rename custom/apigateway/{restapis => rest-apis}/dao.go (98%) rename custom/apigateway/{restapis => rest-apis}/register.go (100%) rename custom/apigateway/{restapis => rest-apis}/render.go (100%) rename custom/apigateway/{stagesv2 => stages-v2}/dao.go (99%) rename custom/apigateway/{stagesv2 => stages-v2}/register.go (100%) rename custom/apigateway/{stagesv2 => stages-v2}/render.go (100%) rename custom/appsync/{datasources => data-sources}/dao.go (98%) rename custom/appsync/{datasources => data-sources}/register.go (100%) rename custom/appsync/{datasources => data-sources}/render.go (100%) rename custom/appsync/{graphqlapis => graphql-apis}/dao.go (98%) rename custom/appsync/{graphqlapis => graphql-apis}/register.go (100%) rename custom/appsync/{graphqlapis => graphql-apis}/render.go (100%) rename custom/athena/{queryexecutions => query-executions}/dao.go (98%) rename custom/athena/{queryexecutions => query-executions}/register.go (100%) rename custom/athena/{queryexecutions => query-executions}/render.go (100%) rename custom/batch/{computeenvironments => compute-environments}/dao.go (98%) rename custom/batch/{computeenvironments => compute-environments}/register.go (100%) rename custom/batch/{computeenvironments => compute-environments}/render.go (100%) rename custom/batch/{jobdefinitions => job-definitions}/dao.go (98%) rename custom/batch/{jobdefinitions => job-definitions}/register.go (100%) rename custom/batch/{jobdefinitions => job-definitions}/render.go (100%) rename custom/batch/{jobqueues => job-queues}/dao.go (98%) rename custom/batch/{jobqueues => job-queues}/register.go (100%) rename custom/batch/{jobqueues => job-queues}/render.go (100%) rename custom/{bedrockagent => bedrock-agent}/agents/dao.go (98%) rename custom/{bedrockagent => bedrock-agent}/agents/register.go (100%) rename custom/{bedrockagent => bedrock-agent}/agents/render.go (100%) rename custom/{bedrockagent/datasources => bedrock-agent/data-sources}/dao.go (98%) rename custom/{bedrockagent/datasources => bedrock-agent/data-sources}/register.go (100%) rename custom/{bedrockagent/datasources => bedrock-agent/data-sources}/render.go (100%) rename custom/{bedrockagent => bedrock-agent}/flows/dao.go (98%) rename custom/{bedrockagent => bedrock-agent}/flows/register.go (100%) rename custom/{bedrockagent => bedrock-agent}/flows/render.go (100%) rename custom/{bedrockagent/knowledgebases => bedrock-agent/knowledge-bases}/dao.go (98%) rename custom/{bedrockagent/knowledgebases => bedrock-agent/knowledge-bases}/register.go (100%) rename custom/{bedrockagent/knowledgebases => bedrock-agent/knowledge-bases}/render.go (100%) rename custom/{bedrockagent => bedrock-agent}/prompts/dao.go (98%) rename custom/{bedrockagent => bedrock-agent}/prompts/register.go (100%) rename custom/{bedrockagent => bedrock-agent}/prompts/render.go (100%) rename custom/{bedrockagentcore => bedrock-agentcore}/endpoints/dao.go (98%) rename custom/{bedrockagentcore => bedrock-agentcore}/endpoints/register.go (100%) rename custom/{bedrockagentcore => bedrock-agentcore}/endpoints/render.go (100%) rename custom/{bedrockagentcore => bedrock-agentcore}/runtimes/actions.go (100%) rename custom/{bedrockagentcore => bedrock-agentcore}/runtimes/dao.go (98%) rename custom/{bedrockagentcore => bedrock-agentcore}/runtimes/register.go (100%) rename custom/{bedrockagentcore => bedrock-agentcore}/runtimes/render.go (100%) rename custom/{bedrockagentcore => bedrock-agentcore}/versions/dao.go (98%) rename custom/{bedrockagentcore => bedrock-agentcore}/versions/register.go (100%) rename custom/{bedrockagentcore => bedrock-agentcore}/versions/render.go (100%) rename custom/bedrock/{foundationmodels => foundation-models}/dao.go (100%) rename custom/bedrock/{foundationmodels => foundation-models}/register.go (100%) rename custom/bedrock/{foundationmodels => foundation-models}/render.go (100%) rename custom/bedrock/{inferenceprofiles => inference-profiles}/dao.go (100%) rename custom/bedrock/{inferenceprofiles => inference-profiles}/register.go (100%) rename custom/bedrock/{inferenceprofiles => inference-profiles}/render.go (100%) rename custom/{costexplorer => ce}/anomalies/dao.go (97%) rename custom/{costexplorer => ce}/anomalies/register.go (82%) rename custom/{costexplorer => ce}/anomalies/render.go (99%) rename custom/{costexplorer => ce}/costs/dao.go (93%) rename custom/{costexplorer => ce}/costs/register.go (83%) rename custom/{costexplorer => ce}/costs/render.go (99%) rename custom/{costexplorer => ce}/monitors/dao.go (97%) rename custom/{costexplorer => ce}/monitors/register.go (82%) rename custom/{costexplorer => ce}/monitors/render.go (98%) rename custom/cloudwatch/{loggroups => log-groups}/actions.go (100%) rename custom/cloudwatch/{loggroups => log-groups}/dao.go (100%) rename custom/cloudwatch/{loggroups => log-groups}/register.go (100%) rename custom/cloudwatch/{loggroups => log-groups}/render.go (100%) rename custom/cloudwatch/{logstreams => log-streams}/actions.go (93%) rename custom/cloudwatch/{logstreams => log-streams}/dao.go (100%) rename custom/cloudwatch/{logstreams => log-streams}/register.go (100%) rename custom/cloudwatch/{logstreams => log-streams}/render.go (100%) rename custom/{cognito/userpools => cognito-idp/user-pools}/dao.go (98%) rename custom/{cognito/userpools => cognito-idp/user-pools}/register.go (82%) rename custom/{cognito/userpools => cognito-idp/user-pools}/render.go (97%) rename custom/{cognito => cognito-idp}/users/dao.go (98%) rename custom/{cognito => cognito-idp}/users/register.go (83%) rename custom/{cognito => cognito-idp}/users/render.go (99%) rename custom/{computeoptimizer => compute-optimizer}/recommendations/dao.go (98%) rename custom/{computeoptimizer => compute-optimizer}/recommendations/register.go (81%) rename custom/{computeoptimizer => compute-optimizer}/recommendations/render.go (99%) rename custom/{computeoptimizer => compute-optimizer}/summary/dao.go (97%) rename custom/{computeoptimizer => compute-optimizer}/summary/register.go (82%) rename custom/{computeoptimizer => compute-optimizer}/summary/render.go (99%) rename custom/{config => configservice}/rules/dao.go (97%) rename custom/{eventbridge => configservice}/rules/register.go (82%) rename custom/{config => configservice}/rules/render.go (98%) rename custom/datasync/{taskexecutions => task-executions}/dao.go (98%) rename custom/datasync/{taskexecutions => task-executions}/register.go (100%) rename custom/datasync/{taskexecutions => task-executions}/render.go (100%) rename custom/directconnect/{virtualinterfaces => virtual-interfaces}/dao.go (100%) rename custom/directconnect/{virtualinterfaces => virtual-interfaces}/register.go (100%) rename custom/directconnect/{virtualinterfaces => virtual-interfaces}/render.go (100%) rename custom/ec2/{capacityreservations => capacity-reservations}/dao.go (100%) rename custom/ec2/{capacityreservations => capacity-reservations}/register.go (100%) rename custom/ec2/{capacityreservations => capacity-reservations}/render.go (100%) rename custom/ec2/{elasticips => elastic-ips}/actions.go (100%) rename custom/ec2/{elasticips => elastic-ips}/dao.go (100%) rename custom/ec2/{elasticips => elastic-ips}/register.go (100%) rename custom/ec2/{elasticips => elastic-ips}/render.go (100%) rename custom/ec2/{keypairs => key-pairs}/actions.go (100%) rename custom/ec2/{keypairs => key-pairs}/dao.go (100%) rename custom/ec2/{keypairs => key-pairs}/register.go (100%) rename custom/ec2/{keypairs => key-pairs}/render.go (100%) rename custom/ec2/{launchtemplates => launch-templates}/dao.go (100%) rename custom/ec2/{launchtemplates => launch-templates}/register.go (100%) rename custom/ec2/{launchtemplates => launch-templates}/render.go (100%) rename custom/ec2/{securitygroups => security-groups}/actions.go (100%) rename custom/ec2/{securitygroups => security-groups}/dao.go (100%) rename custom/ec2/{securitygroups => security-groups}/register.go (100%) rename custom/ec2/{securitygroups => security-groups}/render.go (100%) create mode 100644 custom/ecr/client.go rename custom/elbv2/{loadbalancers => load-balancers}/dao.go (100%) rename custom/elbv2/{loadbalancers => load-balancers}/register.go (100%) rename custom/elbv2/{loadbalancers => load-balancers}/render.go (100%) rename custom/elbv2/{loadbalancers => load-balancers}/resource_test.go (100%) rename custom/elbv2/{targetgroups => target-groups}/dao.go (100%) rename custom/elbv2/{targetgroups => target-groups}/register.go (100%) rename custom/elbv2/{targetgroups => target-groups}/render.go (100%) rename custom/{eventbridge => events}/buses/actions.go (73%) rename custom/{eventbridge => events}/buses/dao.go (95%) rename custom/{eventbridge => events}/buses/register.go (83%) rename custom/{eventbridge => events}/buses/render.go (96%) rename custom/{eventbridge => events}/client.go (95%) rename custom/{eventbridge => events}/rules/actions.go (95%) rename custom/{eventbridge => events}/rules/dao.go (97%) rename custom/{config => events}/rules/register.go (85%) rename custom/{eventbridge => events}/rules/render.go (98%) rename custom/glue/{jobruns => job-runs}/dao.go (100%) rename custom/glue/{jobruns => job-runs}/register.go (100%) rename custom/glue/{jobruns => job-runs}/render.go (100%) rename custom/iam/{instanceprofiles => instance-profiles}/dao.go (100%) rename custom/iam/{instanceprofiles => instance-profiles}/register.go (100%) rename custom/iam/{instanceprofiles => instance-profiles}/render.go (100%) rename custom/{licensemanager => license-manager}/configurations/dao.go (100%) rename custom/{licensemanager => license-manager}/configurations/register.go (100%) rename custom/{licensemanager => license-manager}/configurations/render.go (100%) rename custom/{licensemanager => license-manager}/grants/dao.go (100%) rename custom/{licensemanager => license-manager}/grants/register.go (100%) rename custom/{licensemanager => license-manager}/grants/render.go (100%) rename custom/{licensemanager => license-manager}/licenses/dao.go (100%) rename custom/{licensemanager => license-manager}/licenses/register.go (100%) rename custom/{licensemanager => license-manager}/licenses/render.go (100%) rename custom/{macie => macie2}/buckets/dao.go (98%) rename custom/{macie => macie2}/buckets/register.go (84%) rename custom/{macie => macie2}/buckets/render.go (98%) rename custom/{macie/classificationjobs => macie2/classification-jobs}/dao.go (98%) rename custom/{macie/classificationjobs => macie2/classification-jobs}/register.go (83%) rename custom/{macie/classificationjobs => macie2/classification-jobs}/render.go (99%) rename custom/{macie => macie2}/findings/dao.go (99%) rename custom/{macie => macie2}/findings/register.go (84%) rename custom/{macie => macie2}/findings/render.go (99%) rename custom/{networkfirewall/firewallpolicies => network-firewall/firewall-policies}/dao.go (100%) rename custom/{networkfirewall/firewallpolicies => network-firewall/firewall-policies}/register.go (100%) rename custom/{networkfirewall/firewallpolicies => network-firewall/firewall-policies}/render.go (100%) rename custom/{networkfirewall => network-firewall}/firewalls/dao.go (100%) rename custom/{networkfirewall => network-firewall}/firewalls/register.go (100%) rename custom/{networkfirewall => network-firewall}/firewalls/render.go (100%) rename custom/{networkfirewall/rulegroups => network-firewall/rule-groups}/dao.go (100%) rename custom/{networkfirewall/rulegroups => network-firewall/rule-groups}/register.go (100%) rename custom/{networkfirewall/rulegroups => network-firewall/rule-groups}/render.go (100%) rename custom/risp/{reservedinstances => reserved-instances}/dao.go (98%) rename custom/risp/{reservedinstances => reserved-instances}/register.go (100%) rename custom/risp/{reservedinstances => reserved-instances}/render.go (100%) rename custom/risp/{savingsplans => savings-plans}/dao.go (98%) rename custom/risp/{savingsplans => savings-plans}/register.go (100%) rename custom/risp/{savingsplans => savings-plans}/render.go (100%) rename custom/route53/{hostedzones => hosted-zones}/dao.go (100%) rename custom/route53/{hostedzones => hosted-zones}/register.go (100%) rename custom/route53/{hostedzones => hosted-zones}/render.go (100%) rename custom/route53/{recordsets => record-sets}/dao.go (100%) rename custom/route53/{recordsets => record-sets}/register.go (100%) rename custom/route53/{recordsets => record-sets}/render.go (100%) rename custom/sagemaker/{trainingjobs => training-jobs}/dao.go (100%) rename custom/sagemaker/{trainingjobs => training-jobs}/register.go (100%) rename custom/sagemaker/{trainingjobs => training-jobs}/render.go (100%) create mode 100644 custom/secretsmanager/client.go rename custom/{servicequotas => service-quotas}/quotas/dao.go (100%) rename custom/{servicequotas => service-quotas}/quotas/register.go (100%) rename custom/{servicequotas => service-quotas}/quotas/render.go (100%) rename custom/{servicequotas => service-quotas}/services/dao.go (100%) rename custom/{servicequotas => service-quotas}/services/register.go (100%) rename custom/{servicequotas => service-quotas}/services/render.go (100%) rename custom/{sfn => stepfunctions}/client.go (94%) rename custom/{sfn => stepfunctions}/executions/actions.go (87%) rename custom/{sfn => stepfunctions}/executions/dao.go (97%) rename custom/{sfn => stepfunctions}/executions/register.go (82%) rename custom/{sfn => stepfunctions}/executions/render.go (97%) rename custom/{sfn/statemachines => stepfunctions/state-machines}/actions.go (76%) rename custom/{sfn/statemachines => stepfunctions/state-machines}/dao.go (96%) rename custom/{sfn/statemachines => stepfunctions/state-machines}/register.go (82%) rename custom/{sfn/statemachines => stepfunctions/state-machines}/render.go (97%) rename custom/vpc/{vpcendpoints => endpoints}/dao.go (97%) rename custom/vpc/{vpcendpoints => endpoints}/register.go (100%) rename custom/vpc/{vpcendpoints => endpoints}/render.go (100%) rename custom/vpc/{internetgateways => internet-gateways}/actions.go (100%) rename custom/vpc/{internetgateways => internet-gateways}/dao.go (100%) rename custom/vpc/{internetgateways => internet-gateways}/register.go (100%) rename custom/vpc/{internetgateways => internet-gateways}/render.go (100%) rename custom/vpc/{natgateways => nat-gateways}/actions.go (100%) rename custom/vpc/{natgateways => nat-gateways}/dao.go (100%) rename custom/vpc/{natgateways => nat-gateways}/register.go (100%) rename custom/vpc/{natgateways => nat-gateways}/render.go (100%) rename custom/vpc/{routetables => route-tables}/actions.go (100%) rename custom/vpc/{routetables => route-tables}/dao.go (100%) rename custom/vpc/{routetables => route-tables}/register.go (100%) rename custom/vpc/{routetables => route-tables}/render.go (100%) rename custom/vpc/{tgwattachments => tgw-attachments}/dao.go (97%) rename custom/vpc/{tgwattachments => tgw-attachments}/register.go (100%) rename custom/vpc/{tgwattachments => tgw-attachments}/render.go (100%) rename custom/vpc/{transitgateways => transit-gateways}/dao.go (100%) rename custom/vpc/{transitgateways => transit-gateways}/register.go (100%) rename custom/vpc/{transitgateways => transit-gateways}/render.go (100%) rename custom/wafv2/{webacls => web-acls}/dao.go (100%) rename custom/wafv2/{webacls => web-acls}/register.go (100%) rename custom/wafv2/{webacls => web-acls}/render.go (100%) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 037500c8..4465ed43 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -2,9 +2,9 @@ name: CI on: push: - branches: [main] + branches: [main, develop] pull_request: - branches: [main] + branches: [main, develop] permissions: contents: read diff --git a/cmd/claws/imports_custom.go b/cmd/claws/imports_custom.go index 25f73791..cd0c3421 100644 --- a/cmd/claws/imports_custom.go +++ b/cmd/claws/imports_custom.go @@ -16,21 +16,21 @@ import ( _ "github.com/clawscli/claws/custom/acm/certificates" // API Gateway - _ "github.com/clawscli/claws/custom/apigateway/httpapis" - _ "github.com/clawscli/claws/custom/apigateway/restapis" + _ "github.com/clawscli/claws/custom/apigateway/http-apis" + _ "github.com/clawscli/claws/custom/apigateway/rest-apis" _ "github.com/clawscli/claws/custom/apigateway/stages" - _ "github.com/clawscli/claws/custom/apigateway/stagesv2" + _ "github.com/clawscli/claws/custom/apigateway/stages-v2" // App Runner _ "github.com/clawscli/claws/custom/apprunner/operations" _ "github.com/clawscli/claws/custom/apprunner/services" // AppSync - _ "github.com/clawscli/claws/custom/appsync/datasources" - _ "github.com/clawscli/claws/custom/appsync/graphqlapis" + _ "github.com/clawscli/claws/custom/appsync/data-sources" + _ "github.com/clawscli/claws/custom/appsync/graphql-apis" // Athena - _ "github.com/clawscli/claws/custom/athena/queryexecutions" + _ "github.com/clawscli/claws/custom/athena/query-executions" _ "github.com/clawscli/claws/custom/athena/workgroups" // Auto Scaling @@ -48,32 +48,37 @@ import ( _ "github.com/clawscli/claws/custom/backup/vaults" // Batch - _ "github.com/clawscli/claws/custom/batch/computeenvironments" - _ "github.com/clawscli/claws/custom/batch/jobdefinitions" - _ "github.com/clawscli/claws/custom/batch/jobqueues" + _ "github.com/clawscli/claws/custom/batch/compute-environments" + _ "github.com/clawscli/claws/custom/batch/job-definitions" + _ "github.com/clawscli/claws/custom/batch/job-queues" _ "github.com/clawscli/claws/custom/batch/jobs" // Bedrock - _ "github.com/clawscli/claws/custom/bedrock/foundationmodels" + _ "github.com/clawscli/claws/custom/bedrock/foundation-models" _ "github.com/clawscli/claws/custom/bedrock/guardrails" - _ "github.com/clawscli/claws/custom/bedrock/inferenceprofiles" + _ "github.com/clawscli/claws/custom/bedrock/inference-profiles" // Bedrock Agent - _ "github.com/clawscli/claws/custom/bedrockagent/agents" - _ "github.com/clawscli/claws/custom/bedrockagent/datasources" - _ "github.com/clawscli/claws/custom/bedrockagent/flows" - _ "github.com/clawscli/claws/custom/bedrockagent/knowledgebases" - _ "github.com/clawscli/claws/custom/bedrockagent/prompts" + _ "github.com/clawscli/claws/custom/bedrock-agent/agents" + _ "github.com/clawscli/claws/custom/bedrock-agent/data-sources" + _ "github.com/clawscli/claws/custom/bedrock-agent/flows" + _ "github.com/clawscli/claws/custom/bedrock-agent/knowledge-bases" + _ "github.com/clawscli/claws/custom/bedrock-agent/prompts" // Bedrock AgentCore - _ "github.com/clawscli/claws/custom/bedrockagentcore/endpoints" - _ "github.com/clawscli/claws/custom/bedrockagentcore/runtimes" - _ "github.com/clawscli/claws/custom/bedrockagentcore/versions" + _ "github.com/clawscli/claws/custom/bedrock-agentcore/endpoints" + _ "github.com/clawscli/claws/custom/bedrock-agentcore/runtimes" + _ "github.com/clawscli/claws/custom/bedrock-agentcore/versions" // Budgets _ "github.com/clawscli/claws/custom/budgets/budgets" _ "github.com/clawscli/claws/custom/budgets/notifications" + // Cost Explorer + _ "github.com/clawscli/claws/custom/ce/anomalies" + _ "github.com/clawscli/claws/custom/ce/costs" + _ "github.com/clawscli/claws/custom/ce/monitors" + // CloudFormation _ "github.com/clawscli/claws/custom/cfn/events" _ "github.com/clawscli/claws/custom/cfn/outputs" @@ -89,8 +94,8 @@ import ( // CloudWatch _ "github.com/clawscli/claws/custom/cloudwatch/alarms" - _ "github.com/clawscli/claws/custom/cloudwatch/loggroups" - _ "github.com/clawscli/claws/custom/cloudwatch/logstreams" + _ "github.com/clawscli/claws/custom/cloudwatch/log-groups" + _ "github.com/clawscli/claws/custom/cloudwatch/log-streams" // CodeBuild _ "github.com/clawscli/claws/custom/codebuild/builds" @@ -101,24 +106,19 @@ import ( _ "github.com/clawscli/claws/custom/codepipeline/pipelines" // Cognito - _ "github.com/clawscli/claws/custom/cognito/userpools" - _ "github.com/clawscli/claws/custom/cognito/users" + _ "github.com/clawscli/claws/custom/cognito-idp/user-pools" + _ "github.com/clawscli/claws/custom/cognito-idp/users" // Compute Optimizer - _ "github.com/clawscli/claws/custom/computeoptimizer/recommendations" - _ "github.com/clawscli/claws/custom/computeoptimizer/summary" + _ "github.com/clawscli/claws/custom/compute-optimizer/recommendations" + _ "github.com/clawscli/claws/custom/compute-optimizer/summary" // Config - _ "github.com/clawscli/claws/custom/config/rules" - - // Cost Explorer - _ "github.com/clawscli/claws/custom/costexplorer/anomalies" - _ "github.com/clawscli/claws/custom/costexplorer/costs" - _ "github.com/clawscli/claws/custom/costexplorer/monitors" + _ "github.com/clawscli/claws/custom/configservice/rules" // DataSync _ "github.com/clawscli/claws/custom/datasync/locations" - _ "github.com/clawscli/claws/custom/datasync/taskexecutions" + _ "github.com/clawscli/claws/custom/datasync/task-executions" _ "github.com/clawscli/claws/custom/datasync/tasks" // Detective @@ -127,19 +127,19 @@ import ( // Direct Connect _ "github.com/clawscli/claws/custom/directconnect/connections" - _ "github.com/clawscli/claws/custom/directconnect/virtualinterfaces" + _ "github.com/clawscli/claws/custom/directconnect/virtual-interfaces" // DynamoDB _ "github.com/clawscli/claws/custom/dynamodb/tables" // EC2 - _ "github.com/clawscli/claws/custom/ec2/capacityreservations" - _ "github.com/clawscli/claws/custom/ec2/elasticips" + _ "github.com/clawscli/claws/custom/ec2/capacity-reservations" + _ "github.com/clawscli/claws/custom/ec2/elastic-ips" _ "github.com/clawscli/claws/custom/ec2/images" _ "github.com/clawscli/claws/custom/ec2/instances" - _ "github.com/clawscli/claws/custom/ec2/keypairs" - _ "github.com/clawscli/claws/custom/ec2/launchtemplates" - _ "github.com/clawscli/claws/custom/ec2/securitygroups" + _ "github.com/clawscli/claws/custom/ec2/key-pairs" + _ "github.com/clawscli/claws/custom/ec2/launch-templates" + _ "github.com/clawscli/claws/custom/ec2/security-groups" _ "github.com/clawscli/claws/custom/ec2/snapshots" _ "github.com/clawscli/claws/custom/ec2/volumes" @@ -156,8 +156,8 @@ import ( _ "github.com/clawscli/claws/custom/elasticache/clusters" // ELBv2 (ALB/NLB/GLB) - _ "github.com/clawscli/claws/custom/elbv2/loadbalancers" - _ "github.com/clawscli/claws/custom/elbv2/targetgroups" + _ "github.com/clawscli/claws/custom/elbv2/load-balancers" + _ "github.com/clawscli/claws/custom/elbv2/target-groups" _ "github.com/clawscli/claws/custom/elbv2/targets" // EMR @@ -165,8 +165,8 @@ import ( _ "github.com/clawscli/claws/custom/emr/steps" // EventBridge - _ "github.com/clawscli/claws/custom/eventbridge/buses" - _ "github.com/clawscli/claws/custom/eventbridge/rules" + _ "github.com/clawscli/claws/custom/events/buses" + _ "github.com/clawscli/claws/custom/events/rules" // Firewall Manager _ "github.com/clawscli/claws/custom/fms/policies" @@ -174,7 +174,7 @@ import ( // Glue _ "github.com/clawscli/claws/custom/glue/crawlers" _ "github.com/clawscli/claws/custom/glue/databases" - _ "github.com/clawscli/claws/custom/glue/jobruns" + _ "github.com/clawscli/claws/custom/glue/job-runs" _ "github.com/clawscli/claws/custom/glue/jobs" _ "github.com/clawscli/claws/custom/glue/tables" @@ -187,7 +187,7 @@ import ( // IAM _ "github.com/clawscli/claws/custom/iam/groups" - _ "github.com/clawscli/claws/custom/iam/instanceprofiles" + _ "github.com/clawscli/claws/custom/iam/instance-profiles" _ "github.com/clawscli/claws/custom/iam/policies" _ "github.com/clawscli/claws/custom/iam/roles" _ "github.com/clawscli/claws/custom/iam/users" @@ -205,19 +205,19 @@ import ( _ "github.com/clawscli/claws/custom/lambda/functions" // License Manager - _ "github.com/clawscli/claws/custom/licensemanager/configurations" - _ "github.com/clawscli/claws/custom/licensemanager/grants" - _ "github.com/clawscli/claws/custom/licensemanager/licenses" + _ "github.com/clawscli/claws/custom/license-manager/configurations" + _ "github.com/clawscli/claws/custom/license-manager/grants" + _ "github.com/clawscli/claws/custom/license-manager/licenses" // Macie - _ "github.com/clawscli/claws/custom/macie/buckets" - _ "github.com/clawscli/claws/custom/macie/classificationjobs" - _ "github.com/clawscli/claws/custom/macie/findings" + _ "github.com/clawscli/claws/custom/macie2/buckets" + _ "github.com/clawscli/claws/custom/macie2/classification-jobs" + _ "github.com/clawscli/claws/custom/macie2/findings" // Network Firewall - _ "github.com/clawscli/claws/custom/networkfirewall/firewallpolicies" - _ "github.com/clawscli/claws/custom/networkfirewall/firewalls" - _ "github.com/clawscli/claws/custom/networkfirewall/rulegroups" + _ "github.com/clawscli/claws/custom/network-firewall/firewall-policies" + _ "github.com/clawscli/claws/custom/network-firewall/firewalls" + _ "github.com/clawscli/claws/custom/network-firewall/rule-groups" // OpenSearch _ "github.com/clawscli/claws/custom/opensearch/domains" @@ -237,12 +237,12 @@ import ( _ "github.com/clawscli/claws/custom/redshift/snapshots" // RI/SP (Reserved Instances, Savings Plans) - _ "github.com/clawscli/claws/custom/risp/reservedinstances" - _ "github.com/clawscli/claws/custom/risp/savingsplans" + _ "github.com/clawscli/claws/custom/risp/reserved-instances" + _ "github.com/clawscli/claws/custom/risp/savings-plans" // Route53 - _ "github.com/clawscli/claws/custom/route53/hostedzones" - _ "github.com/clawscli/claws/custom/route53/recordsets" + _ "github.com/clawscli/claws/custom/route53/hosted-zones" + _ "github.com/clawscli/claws/custom/route53/record-sets" // S3 _ "github.com/clawscli/claws/custom/s3/buckets" @@ -255,7 +255,7 @@ import ( _ "github.com/clawscli/claws/custom/sagemaker/endpoints" _ "github.com/clawscli/claws/custom/sagemaker/models" _ "github.com/clawscli/claws/custom/sagemaker/notebooks" - _ "github.com/clawscli/claws/custom/sagemaker/trainingjobs" + _ "github.com/clawscli/claws/custom/sagemaker/training-jobs" // Secrets Manager _ "github.com/clawscli/claws/custom/secretsmanager/secrets" @@ -264,12 +264,8 @@ import ( _ "github.com/clawscli/claws/custom/securityhub/findings" // Service Quotas - _ "github.com/clawscli/claws/custom/servicequotas/quotas" - _ "github.com/clawscli/claws/custom/servicequotas/services" - - // Step Functions - _ "github.com/clawscli/claws/custom/sfn/executions" - _ "github.com/clawscli/claws/custom/sfn/statemachines" + _ "github.com/clawscli/claws/custom/service-quotas/quotas" + _ "github.com/clawscli/claws/custom/service-quotas/services" // SNS _ "github.com/clawscli/claws/custom/sns/subscriptions" @@ -281,6 +277,10 @@ import ( // SSM _ "github.com/clawscli/claws/custom/ssm/parameters" + // Step Functions + _ "github.com/clawscli/claws/custom/stepfunctions/executions" + _ "github.com/clawscli/claws/custom/stepfunctions/state-machines" + // Transcribe _ "github.com/clawscli/claws/custom/transcribe/jobs" @@ -292,17 +292,17 @@ import ( _ "github.com/clawscli/claws/custom/trustedadvisor/recommendations" // VPC - _ "github.com/clawscli/claws/custom/vpc/internetgateways" - _ "github.com/clawscli/claws/custom/vpc/natgateways" - _ "github.com/clawscli/claws/custom/vpc/routetables" + _ "github.com/clawscli/claws/custom/vpc/endpoints" + _ "github.com/clawscli/claws/custom/vpc/internet-gateways" + _ "github.com/clawscli/claws/custom/vpc/nat-gateways" + _ "github.com/clawscli/claws/custom/vpc/route-tables" _ "github.com/clawscli/claws/custom/vpc/subnets" - _ "github.com/clawscli/claws/custom/vpc/tgwattachments" - _ "github.com/clawscli/claws/custom/vpc/transitgateways" - _ "github.com/clawscli/claws/custom/vpc/vpcendpoints" + _ "github.com/clawscli/claws/custom/vpc/tgw-attachments" + _ "github.com/clawscli/claws/custom/vpc/transit-gateways" _ "github.com/clawscli/claws/custom/vpc/vpcs" // WAF - _ "github.com/clawscli/claws/custom/wafv2/webacls" + _ "github.com/clawscli/claws/custom/wafv2/web-acls" // X-Ray _ "github.com/clawscli/claws/custom/xray/groups" diff --git a/custom/apigateway/httpapis/dao.go b/custom/apigateway/http-apis/dao.go similarity index 98% rename from custom/apigateway/httpapis/dao.go rename to custom/apigateway/http-apis/dao.go index fb326151..beb275ea 100644 --- a/custom/apigateway/httpapis/dao.go +++ b/custom/apigateway/http-apis/dao.go @@ -22,7 +22,7 @@ type HttpAPIDAO struct { func NewHttpAPIDAO(ctx context.Context) (dao.DAO, error) { cfg, err := appaws.NewConfig(ctx) if err != nil { - return nil, apperrors.Wrap(err, "new apigateway/httpapis dao") + return nil, apperrors.Wrap(err, "new apigateway/http-apis dao") } return &HttpAPIDAO{ BaseDAO: dao.NewBaseDAO("apigateway", "http-apis"), diff --git a/custom/apigateway/httpapis/register.go b/custom/apigateway/http-apis/register.go similarity index 100% rename from custom/apigateway/httpapis/register.go rename to custom/apigateway/http-apis/register.go diff --git a/custom/apigateway/httpapis/render.go b/custom/apigateway/http-apis/render.go similarity index 100% rename from custom/apigateway/httpapis/render.go rename to custom/apigateway/http-apis/render.go diff --git a/custom/apigateway/restapis/dao.go b/custom/apigateway/rest-apis/dao.go similarity index 98% rename from custom/apigateway/restapis/dao.go rename to custom/apigateway/rest-apis/dao.go index 1db41f72..1aef9aa9 100644 --- a/custom/apigateway/restapis/dao.go +++ b/custom/apigateway/rest-apis/dao.go @@ -22,7 +22,7 @@ type RestAPIDAO struct { func NewRestAPIDAO(ctx context.Context) (dao.DAO, error) { cfg, err := appaws.NewConfig(ctx) if err != nil { - return nil, apperrors.Wrap(err, "new apigateway/restapis dao") + return nil, apperrors.Wrap(err, "new apigateway/rest-apis dao") } return &RestAPIDAO{ BaseDAO: dao.NewBaseDAO("apigateway", "rest-apis"), diff --git a/custom/apigateway/restapis/register.go b/custom/apigateway/rest-apis/register.go similarity index 100% rename from custom/apigateway/restapis/register.go rename to custom/apigateway/rest-apis/register.go diff --git a/custom/apigateway/restapis/render.go b/custom/apigateway/rest-apis/render.go similarity index 100% rename from custom/apigateway/restapis/render.go rename to custom/apigateway/rest-apis/render.go diff --git a/custom/apigateway/stagesv2/dao.go b/custom/apigateway/stages-v2/dao.go similarity index 99% rename from custom/apigateway/stagesv2/dao.go rename to custom/apigateway/stages-v2/dao.go index 7f96ebeb..70d05b3c 100644 --- a/custom/apigateway/stagesv2/dao.go +++ b/custom/apigateway/stages-v2/dao.go @@ -23,7 +23,7 @@ type StageV2DAO struct { func NewStageV2DAO(ctx context.Context) (dao.DAO, error) { cfg, err := appaws.NewConfig(ctx) if err != nil { - return nil, apperrors.Wrap(err, "new apigateway/stagesv2 dao") + return nil, apperrors.Wrap(err, "new apigateway/stages-v2 dao") } return &StageV2DAO{ BaseDAO: dao.NewBaseDAO("apigateway", "stages-v2"), diff --git a/custom/apigateway/stagesv2/register.go b/custom/apigateway/stages-v2/register.go similarity index 100% rename from custom/apigateway/stagesv2/register.go rename to custom/apigateway/stages-v2/register.go diff --git a/custom/apigateway/stagesv2/render.go b/custom/apigateway/stages-v2/render.go similarity index 100% rename from custom/apigateway/stagesv2/render.go rename to custom/apigateway/stages-v2/render.go diff --git a/custom/appsync/datasources/dao.go b/custom/appsync/data-sources/dao.go similarity index 98% rename from custom/appsync/datasources/dao.go rename to custom/appsync/data-sources/dao.go index f9f64220..cdc5e0dc 100644 --- a/custom/appsync/datasources/dao.go +++ b/custom/appsync/data-sources/dao.go @@ -22,7 +22,7 @@ type DataSourceDAO struct { func NewDataSourceDAO(ctx context.Context) (dao.DAO, error) { cfg, err := appaws.NewConfig(ctx) if err != nil { - return nil, apperrors.Wrap(err, "new appsync/datasources dao") + return nil, apperrors.Wrap(err, "new appsync/data-sources dao") } return &DataSourceDAO{ BaseDAO: dao.NewBaseDAO("appsync", "data-sources"), diff --git a/custom/appsync/datasources/register.go b/custom/appsync/data-sources/register.go similarity index 100% rename from custom/appsync/datasources/register.go rename to custom/appsync/data-sources/register.go diff --git a/custom/appsync/datasources/render.go b/custom/appsync/data-sources/render.go similarity index 100% rename from custom/appsync/datasources/render.go rename to custom/appsync/data-sources/render.go diff --git a/custom/appsync/graphqlapis/dao.go b/custom/appsync/graphql-apis/dao.go similarity index 98% rename from custom/appsync/graphqlapis/dao.go rename to custom/appsync/graphql-apis/dao.go index c1b6a589..90554049 100644 --- a/custom/appsync/graphqlapis/dao.go +++ b/custom/appsync/graphql-apis/dao.go @@ -21,7 +21,7 @@ type GraphQLApiDAO struct { func NewGraphQLApiDAO(ctx context.Context) (dao.DAO, error) { cfg, err := appaws.NewConfig(ctx) if err != nil { - return nil, apperrors.Wrap(err, "new appsync/graphqlapis dao") + return nil, apperrors.Wrap(err, "new appsync/graphql-apis dao") } return &GraphQLApiDAO{ BaseDAO: dao.NewBaseDAO("appsync", "graphql-apis"), diff --git a/custom/appsync/graphqlapis/register.go b/custom/appsync/graphql-apis/register.go similarity index 100% rename from custom/appsync/graphqlapis/register.go rename to custom/appsync/graphql-apis/register.go diff --git a/custom/appsync/graphqlapis/render.go b/custom/appsync/graphql-apis/render.go similarity index 100% rename from custom/appsync/graphqlapis/render.go rename to custom/appsync/graphql-apis/render.go diff --git a/custom/athena/queryexecutions/dao.go b/custom/athena/query-executions/dao.go similarity index 98% rename from custom/athena/queryexecutions/dao.go rename to custom/athena/query-executions/dao.go index 56708bd6..6befaafd 100644 --- a/custom/athena/queryexecutions/dao.go +++ b/custom/athena/query-executions/dao.go @@ -23,7 +23,7 @@ type QueryExecutionDAO struct { func NewQueryExecutionDAO(ctx context.Context) (dao.DAO, error) { cfg, err := appaws.NewConfig(ctx) if err != nil { - return nil, apperrors.Wrap(err, "new athena/queryexecutions dao") + return nil, apperrors.Wrap(err, "new athena/query-executions dao") } return &QueryExecutionDAO{ BaseDAO: dao.NewBaseDAO("athena", "query-executions"), diff --git a/custom/athena/queryexecutions/register.go b/custom/athena/query-executions/register.go similarity index 100% rename from custom/athena/queryexecutions/register.go rename to custom/athena/query-executions/register.go diff --git a/custom/athena/queryexecutions/render.go b/custom/athena/query-executions/render.go similarity index 100% rename from custom/athena/queryexecutions/render.go rename to custom/athena/query-executions/render.go diff --git a/custom/batch/computeenvironments/dao.go b/custom/batch/compute-environments/dao.go similarity index 98% rename from custom/batch/computeenvironments/dao.go rename to custom/batch/compute-environments/dao.go index e11607b8..5c72b40b 100644 --- a/custom/batch/computeenvironments/dao.go +++ b/custom/batch/compute-environments/dao.go @@ -22,7 +22,7 @@ type ComputeEnvironmentDAO struct { func NewComputeEnvironmentDAO(ctx context.Context) (dao.DAO, error) { cfg, err := appaws.NewConfig(ctx) if err != nil { - return nil, apperrors.Wrap(err, "new batch/computeenvironments dao") + return nil, apperrors.Wrap(err, "new batch/compute-environments dao") } return &ComputeEnvironmentDAO{ BaseDAO: dao.NewBaseDAO("batch", "compute-environments"), diff --git a/custom/batch/computeenvironments/register.go b/custom/batch/compute-environments/register.go similarity index 100% rename from custom/batch/computeenvironments/register.go rename to custom/batch/compute-environments/register.go diff --git a/custom/batch/computeenvironments/render.go b/custom/batch/compute-environments/render.go similarity index 100% rename from custom/batch/computeenvironments/render.go rename to custom/batch/compute-environments/render.go diff --git a/custom/batch/jobdefinitions/dao.go b/custom/batch/job-definitions/dao.go similarity index 98% rename from custom/batch/jobdefinitions/dao.go rename to custom/batch/job-definitions/dao.go index 427423e9..a09f9f6d 100644 --- a/custom/batch/jobdefinitions/dao.go +++ b/custom/batch/job-definitions/dao.go @@ -23,7 +23,7 @@ type JobDefinitionDAO struct { func NewJobDefinitionDAO(ctx context.Context) (dao.DAO, error) { cfg, err := appaws.NewConfig(ctx) if err != nil { - return nil, apperrors.Wrap(err, "new batch/jobdefinitions dao") + return nil, apperrors.Wrap(err, "new batch/job-definitions dao") } return &JobDefinitionDAO{ BaseDAO: dao.NewBaseDAO("batch", "job-definitions"), diff --git a/custom/batch/jobdefinitions/register.go b/custom/batch/job-definitions/register.go similarity index 100% rename from custom/batch/jobdefinitions/register.go rename to custom/batch/job-definitions/register.go diff --git a/custom/batch/jobdefinitions/render.go b/custom/batch/job-definitions/render.go similarity index 100% rename from custom/batch/jobdefinitions/render.go rename to custom/batch/job-definitions/render.go diff --git a/custom/batch/jobqueues/dao.go b/custom/batch/job-queues/dao.go similarity index 98% rename from custom/batch/jobqueues/dao.go rename to custom/batch/job-queues/dao.go index 97fd3cfc..17cf0585 100644 --- a/custom/batch/jobqueues/dao.go +++ b/custom/batch/job-queues/dao.go @@ -22,7 +22,7 @@ type JobQueueDAO struct { func NewJobQueueDAO(ctx context.Context) (dao.DAO, error) { cfg, err := appaws.NewConfig(ctx) if err != nil { - return nil, apperrors.Wrap(err, "new batch/jobqueues dao") + return nil, apperrors.Wrap(err, "new batch/job-queues dao") } return &JobQueueDAO{ BaseDAO: dao.NewBaseDAO("batch", "job-queues"), diff --git a/custom/batch/jobqueues/register.go b/custom/batch/job-queues/register.go similarity index 100% rename from custom/batch/jobqueues/register.go rename to custom/batch/job-queues/register.go diff --git a/custom/batch/jobqueues/render.go b/custom/batch/job-queues/render.go similarity index 100% rename from custom/batch/jobqueues/render.go rename to custom/batch/job-queues/render.go diff --git a/custom/bedrockagent/agents/dao.go b/custom/bedrock-agent/agents/dao.go similarity index 98% rename from custom/bedrockagent/agents/dao.go rename to custom/bedrock-agent/agents/dao.go index 1bb85377..b2826b58 100644 --- a/custom/bedrockagent/agents/dao.go +++ b/custom/bedrock-agent/agents/dao.go @@ -22,7 +22,7 @@ type AgentDAO struct { func NewAgentDAO(ctx context.Context) (dao.DAO, error) { cfg, err := appaws.NewConfig(ctx) if err != nil { - return nil, apperrors.Wrap(err, "new bedrockagent/agents dao") + return nil, apperrors.Wrap(err, "new bedrock-agent/agents dao") } return &AgentDAO{ BaseDAO: dao.NewBaseDAO("bedrock-agent", "agents"), diff --git a/custom/bedrockagent/agents/register.go b/custom/bedrock-agent/agents/register.go similarity index 100% rename from custom/bedrockagent/agents/register.go rename to custom/bedrock-agent/agents/register.go diff --git a/custom/bedrockagent/agents/render.go b/custom/bedrock-agent/agents/render.go similarity index 100% rename from custom/bedrockagent/agents/render.go rename to custom/bedrock-agent/agents/render.go diff --git a/custom/bedrockagent/datasources/dao.go b/custom/bedrock-agent/data-sources/dao.go similarity index 98% rename from custom/bedrockagent/datasources/dao.go rename to custom/bedrock-agent/data-sources/dao.go index 7b84b859..2b58edc3 100644 --- a/custom/bedrockagent/datasources/dao.go +++ b/custom/bedrock-agent/data-sources/dao.go @@ -24,7 +24,7 @@ type DataSourceDAO struct { func NewDataSourceDAO(ctx context.Context) (dao.DAO, error) { cfg, err := appaws.NewConfig(ctx) if err != nil { - return nil, apperrors.Wrap(err, "new bedrockagent/datasources dao") + return nil, apperrors.Wrap(err, "new bedrock-agent/data-sources dao") } return &DataSourceDAO{ BaseDAO: dao.NewBaseDAO("bedrock-agent", "data-sources"), diff --git a/custom/bedrockagent/datasources/register.go b/custom/bedrock-agent/data-sources/register.go similarity index 100% rename from custom/bedrockagent/datasources/register.go rename to custom/bedrock-agent/data-sources/register.go diff --git a/custom/bedrockagent/datasources/render.go b/custom/bedrock-agent/data-sources/render.go similarity index 100% rename from custom/bedrockagent/datasources/render.go rename to custom/bedrock-agent/data-sources/render.go diff --git a/custom/bedrockagent/flows/dao.go b/custom/bedrock-agent/flows/dao.go similarity index 98% rename from custom/bedrockagent/flows/dao.go rename to custom/bedrock-agent/flows/dao.go index d106aa90..28917da6 100644 --- a/custom/bedrockagent/flows/dao.go +++ b/custom/bedrock-agent/flows/dao.go @@ -22,7 +22,7 @@ type FlowDAO struct { func NewFlowDAO(ctx context.Context) (dao.DAO, error) { cfg, err := appaws.NewConfig(ctx) if err != nil { - return nil, apperrors.Wrap(err, "new bedrockagent/flows dao") + return nil, apperrors.Wrap(err, "new bedrock-agent/flows dao") } return &FlowDAO{ BaseDAO: dao.NewBaseDAO("bedrock-agent", "flows"), diff --git a/custom/bedrockagent/flows/register.go b/custom/bedrock-agent/flows/register.go similarity index 100% rename from custom/bedrockagent/flows/register.go rename to custom/bedrock-agent/flows/register.go diff --git a/custom/bedrockagent/flows/render.go b/custom/bedrock-agent/flows/render.go similarity index 100% rename from custom/bedrockagent/flows/render.go rename to custom/bedrock-agent/flows/render.go diff --git a/custom/bedrockagent/knowledgebases/dao.go b/custom/bedrock-agent/knowledge-bases/dao.go similarity index 98% rename from custom/bedrockagent/knowledgebases/dao.go rename to custom/bedrock-agent/knowledge-bases/dao.go index 11f48874..045d054f 100644 --- a/custom/bedrockagent/knowledgebases/dao.go +++ b/custom/bedrock-agent/knowledge-bases/dao.go @@ -22,7 +22,7 @@ type KnowledgeBaseDAO struct { func NewKnowledgeBaseDAO(ctx context.Context) (dao.DAO, error) { cfg, err := appaws.NewConfig(ctx) if err != nil { - return nil, apperrors.Wrap(err, "new bedrockagent/knowledgebases dao") + return nil, apperrors.Wrap(err, "new bedrock-agent/knowledge-bases dao") } return &KnowledgeBaseDAO{ BaseDAO: dao.NewBaseDAO("bedrock-agent", "knowledge-bases"), diff --git a/custom/bedrockagent/knowledgebases/register.go b/custom/bedrock-agent/knowledge-bases/register.go similarity index 100% rename from custom/bedrockagent/knowledgebases/register.go rename to custom/bedrock-agent/knowledge-bases/register.go diff --git a/custom/bedrockagent/knowledgebases/render.go b/custom/bedrock-agent/knowledge-bases/render.go similarity index 100% rename from custom/bedrockagent/knowledgebases/render.go rename to custom/bedrock-agent/knowledge-bases/render.go diff --git a/custom/bedrockagent/prompts/dao.go b/custom/bedrock-agent/prompts/dao.go similarity index 98% rename from custom/bedrockagent/prompts/dao.go rename to custom/bedrock-agent/prompts/dao.go index e05c81e7..f56bb034 100644 --- a/custom/bedrockagent/prompts/dao.go +++ b/custom/bedrock-agent/prompts/dao.go @@ -22,7 +22,7 @@ type PromptDAO struct { func NewPromptDAO(ctx context.Context) (dao.DAO, error) { cfg, err := appaws.NewConfig(ctx) if err != nil { - return nil, apperrors.Wrap(err, "new bedrockagent/prompts dao") + return nil, apperrors.Wrap(err, "new bedrock-agent/prompts dao") } return &PromptDAO{ BaseDAO: dao.NewBaseDAO("bedrock-agent", "prompts"), diff --git a/custom/bedrockagent/prompts/register.go b/custom/bedrock-agent/prompts/register.go similarity index 100% rename from custom/bedrockagent/prompts/register.go rename to custom/bedrock-agent/prompts/register.go diff --git a/custom/bedrockagent/prompts/render.go b/custom/bedrock-agent/prompts/render.go similarity index 100% rename from custom/bedrockagent/prompts/render.go rename to custom/bedrock-agent/prompts/render.go diff --git a/custom/bedrockagentcore/endpoints/dao.go b/custom/bedrock-agentcore/endpoints/dao.go similarity index 98% rename from custom/bedrockagentcore/endpoints/dao.go rename to custom/bedrock-agentcore/endpoints/dao.go index 054bd628..87f86967 100644 --- a/custom/bedrockagentcore/endpoints/dao.go +++ b/custom/bedrock-agentcore/endpoints/dao.go @@ -23,7 +23,7 @@ type EndpointDAO struct { func NewEndpointDAO(ctx context.Context) (dao.DAO, error) { cfg, err := appaws.NewConfig(ctx) if err != nil { - return nil, apperrors.Wrap(err, "new bedrockagentcore/endpoints dao") + return nil, apperrors.Wrap(err, "new bedrock-agentcore/endpoints dao") } return &EndpointDAO{ BaseDAO: dao.NewBaseDAO("bedrock-agentcore", "endpoints"), diff --git a/custom/bedrockagentcore/endpoints/register.go b/custom/bedrock-agentcore/endpoints/register.go similarity index 100% rename from custom/bedrockagentcore/endpoints/register.go rename to custom/bedrock-agentcore/endpoints/register.go diff --git a/custom/bedrockagentcore/endpoints/render.go b/custom/bedrock-agentcore/endpoints/render.go similarity index 100% rename from custom/bedrockagentcore/endpoints/render.go rename to custom/bedrock-agentcore/endpoints/render.go diff --git a/custom/bedrockagentcore/runtimes/actions.go b/custom/bedrock-agentcore/runtimes/actions.go similarity index 100% rename from custom/bedrockagentcore/runtimes/actions.go rename to custom/bedrock-agentcore/runtimes/actions.go diff --git a/custom/bedrockagentcore/runtimes/dao.go b/custom/bedrock-agentcore/runtimes/dao.go similarity index 98% rename from custom/bedrockagentcore/runtimes/dao.go rename to custom/bedrock-agentcore/runtimes/dao.go index f7a7d68c..befef066 100644 --- a/custom/bedrockagentcore/runtimes/dao.go +++ b/custom/bedrock-agentcore/runtimes/dao.go @@ -22,7 +22,7 @@ type RuntimeDAO struct { func NewRuntimeDAO(ctx context.Context) (dao.DAO, error) { cfg, err := appaws.NewConfig(ctx) if err != nil { - return nil, apperrors.Wrap(err, "new bedrockagentcore/runtimes dao") + return nil, apperrors.Wrap(err, "new bedrock-agentcore/runtimes dao") } return &RuntimeDAO{ BaseDAO: dao.NewBaseDAO("bedrock-agentcore", "runtimes"), diff --git a/custom/bedrockagentcore/runtimes/register.go b/custom/bedrock-agentcore/runtimes/register.go similarity index 100% rename from custom/bedrockagentcore/runtimes/register.go rename to custom/bedrock-agentcore/runtimes/register.go diff --git a/custom/bedrockagentcore/runtimes/render.go b/custom/bedrock-agentcore/runtimes/render.go similarity index 100% rename from custom/bedrockagentcore/runtimes/render.go rename to custom/bedrock-agentcore/runtimes/render.go diff --git a/custom/bedrockagentcore/versions/dao.go b/custom/bedrock-agentcore/versions/dao.go similarity index 98% rename from custom/bedrockagentcore/versions/dao.go rename to custom/bedrock-agentcore/versions/dao.go index cc8536c7..86ad596a 100644 --- a/custom/bedrockagentcore/versions/dao.go +++ b/custom/bedrock-agentcore/versions/dao.go @@ -23,7 +23,7 @@ type VersionDAO struct { func NewVersionDAO(ctx context.Context) (dao.DAO, error) { cfg, err := appaws.NewConfig(ctx) if err != nil { - return nil, apperrors.Wrap(err, "new bedrockagentcore/versions dao") + return nil, apperrors.Wrap(err, "new bedrock-agentcore/versions dao") } return &VersionDAO{ BaseDAO: dao.NewBaseDAO("bedrock-agentcore", "versions"), diff --git a/custom/bedrockagentcore/versions/register.go b/custom/bedrock-agentcore/versions/register.go similarity index 100% rename from custom/bedrockagentcore/versions/register.go rename to custom/bedrock-agentcore/versions/register.go diff --git a/custom/bedrockagentcore/versions/render.go b/custom/bedrock-agentcore/versions/render.go similarity index 100% rename from custom/bedrockagentcore/versions/render.go rename to custom/bedrock-agentcore/versions/render.go diff --git a/custom/bedrock/foundationmodels/dao.go b/custom/bedrock/foundation-models/dao.go similarity index 100% rename from custom/bedrock/foundationmodels/dao.go rename to custom/bedrock/foundation-models/dao.go diff --git a/custom/bedrock/foundationmodels/register.go b/custom/bedrock/foundation-models/register.go similarity index 100% rename from custom/bedrock/foundationmodels/register.go rename to custom/bedrock/foundation-models/register.go diff --git a/custom/bedrock/foundationmodels/render.go b/custom/bedrock/foundation-models/render.go similarity index 100% rename from custom/bedrock/foundationmodels/render.go rename to custom/bedrock/foundation-models/render.go diff --git a/custom/bedrock/inferenceprofiles/dao.go b/custom/bedrock/inference-profiles/dao.go similarity index 100% rename from custom/bedrock/inferenceprofiles/dao.go rename to custom/bedrock/inference-profiles/dao.go diff --git a/custom/bedrock/inferenceprofiles/register.go b/custom/bedrock/inference-profiles/register.go similarity index 100% rename from custom/bedrock/inferenceprofiles/register.go rename to custom/bedrock/inference-profiles/register.go diff --git a/custom/bedrock/inferenceprofiles/render.go b/custom/bedrock/inference-profiles/render.go similarity index 100% rename from custom/bedrock/inferenceprofiles/render.go rename to custom/bedrock/inference-profiles/render.go diff --git a/custom/costexplorer/anomalies/dao.go b/custom/ce/anomalies/dao.go similarity index 97% rename from custom/costexplorer/anomalies/dao.go rename to custom/ce/anomalies/dao.go index 4da6cdd1..b34bf63b 100644 --- a/custom/costexplorer/anomalies/dao.go +++ b/custom/ce/anomalies/dao.go @@ -27,10 +27,10 @@ func NewAnomalyDAO(ctx context.Context) (dao.DAO, error) { // Cost Explorer API is only available in us-east-1 cfg, err := appaws.NewConfigWithRegion(ctx, appaws.CostExplorerRegion) if err != nil { - return nil, apperrors.Wrap(err, "new costexplorer/anomalies dao") + return nil, apperrors.Wrap(err, "new ce/anomalies dao") } return &AnomalyDAO{ - BaseDAO: dao.NewBaseDAO("costexplorer", "anomalies"), + BaseDAO: dao.NewBaseDAO("ce", "anomalies"), client: costexplorer.NewFromConfig(cfg), }, nil } diff --git a/custom/costexplorer/anomalies/register.go b/custom/ce/anomalies/register.go similarity index 82% rename from custom/costexplorer/anomalies/register.go rename to custom/ce/anomalies/register.go index ecb753f3..d82e4171 100644 --- a/custom/costexplorer/anomalies/register.go +++ b/custom/ce/anomalies/register.go @@ -9,7 +9,7 @@ import ( ) func init() { - registry.Global.RegisterCustom("costexplorer", "anomalies", registry.Entry{ + registry.Global.RegisterCustom("ce", "anomalies", registry.Entry{ DAOFactory: func(ctx context.Context) (dao.DAO, error) { return NewAnomalyDAO(ctx) }, diff --git a/custom/costexplorer/anomalies/render.go b/custom/ce/anomalies/render.go similarity index 99% rename from custom/costexplorer/anomalies/render.go rename to custom/ce/anomalies/render.go index ddf06ebb..9b8e205b 100644 --- a/custom/costexplorer/anomalies/render.go +++ b/custom/ce/anomalies/render.go @@ -17,7 +17,7 @@ type AnomalyRenderer struct { func NewAnomalyRenderer() render.Renderer { return &AnomalyRenderer{ BaseRenderer: render.BaseRenderer{ - Service: "costexplorer", + Service: "ce", Resource: "anomalies", Cols: []render.Column{ {Name: "SERVICE", Width: 30, Getter: getDimensionValue}, diff --git a/custom/costexplorer/costs/dao.go b/custom/ce/costs/dao.go similarity index 93% rename from custom/costexplorer/costs/dao.go rename to custom/ce/costs/dao.go index b3ed6fcd..b24065f1 100644 --- a/custom/costexplorer/costs/dao.go +++ b/custom/ce/costs/dao.go @@ -24,10 +24,10 @@ func NewCostDAO(ctx context.Context) (dao.DAO, error) { // Cost Explorer API is only available in us-east-1 cfg, err := appaws.NewConfigWithRegion(ctx, appaws.CostExplorerRegion) if err != nil { - return nil, apperrors.Wrap(err, "new costexplorer/costs dao") + return nil, apperrors.Wrap(err, "new ce/costs dao") } return &CostDAO{ - BaseDAO: dao.NewBaseDAO("costexplorer", "costs"), + BaseDAO: dao.NewBaseDAO("ce", "costs"), client: costexplorer.NewFromConfig(cfg), }, nil } @@ -160,8 +160,10 @@ func NewCostResource(group types.Group, start, end string) *CostResource { return &CostResource{ BaseResource: dao.BaseResource{ - ID: serviceName, - ARN: fmt.Sprintf("costexplorer::%s", serviceName), + ID: serviceName, + // Pseudo-ARN: Cost Explorer aggregates don't have real ARNs. + // Format "ce::" enables internal resource identification. + ARN: fmt.Sprintf("ce::%s", serviceName), }, ServiceName: serviceName, Cost: cost, diff --git a/custom/costexplorer/costs/register.go b/custom/ce/costs/register.go similarity index 83% rename from custom/costexplorer/costs/register.go rename to custom/ce/costs/register.go index 38a5f35c..cd53f23f 100644 --- a/custom/costexplorer/costs/register.go +++ b/custom/ce/costs/register.go @@ -9,7 +9,7 @@ import ( ) func init() { - registry.Global.RegisterCustom("costexplorer", "costs", registry.Entry{ + registry.Global.RegisterCustom("ce", "costs", registry.Entry{ DAOFactory: func(ctx context.Context) (dao.DAO, error) { return NewCostDAO(ctx) }, diff --git a/custom/costexplorer/costs/render.go b/custom/ce/costs/render.go similarity index 99% rename from custom/costexplorer/costs/render.go rename to custom/ce/costs/render.go index ca7fddb0..689c2ada 100644 --- a/custom/costexplorer/costs/render.go +++ b/custom/ce/costs/render.go @@ -17,7 +17,7 @@ type CostRenderer struct { func NewCostRenderer() render.Renderer { return &CostRenderer{ BaseRenderer: render.BaseRenderer{ - Service: "costexplorer", + Service: "ce", Resource: "costs", Cols: []render.Column{ {Name: "SERVICE", Width: 45, Getter: func(r dao.Resource) string { return r.GetID() }}, diff --git a/custom/costexplorer/monitors/dao.go b/custom/ce/monitors/dao.go similarity index 97% rename from custom/costexplorer/monitors/dao.go rename to custom/ce/monitors/dao.go index 623f3696..6883099f 100644 --- a/custom/costexplorer/monitors/dao.go +++ b/custom/ce/monitors/dao.go @@ -23,10 +23,10 @@ func NewMonitorDAO(ctx context.Context) (dao.DAO, error) { // Cost Explorer API is only available in us-east-1 cfg, err := appaws.NewConfigWithRegion(ctx, appaws.CostExplorerRegion) if err != nil { - return nil, apperrors.Wrap(err, "new costexplorer/monitors dao") + return nil, apperrors.Wrap(err, "new ce/monitors dao") } return &MonitorDAO{ - BaseDAO: dao.NewBaseDAO("costexplorer", "monitors"), + BaseDAO: dao.NewBaseDAO("ce", "monitors"), client: costexplorer.NewFromConfig(cfg), }, nil } diff --git a/custom/costexplorer/monitors/register.go b/custom/ce/monitors/register.go similarity index 82% rename from custom/costexplorer/monitors/register.go rename to custom/ce/monitors/register.go index 219feb7c..8dac0779 100644 --- a/custom/costexplorer/monitors/register.go +++ b/custom/ce/monitors/register.go @@ -9,7 +9,7 @@ import ( ) func init() { - registry.Global.RegisterCustom("costexplorer", "monitors", registry.Entry{ + registry.Global.RegisterCustom("ce", "monitors", registry.Entry{ DAOFactory: func(ctx context.Context) (dao.DAO, error) { return NewMonitorDAO(ctx) }, diff --git a/custom/costexplorer/monitors/render.go b/custom/ce/monitors/render.go similarity index 98% rename from custom/costexplorer/monitors/render.go rename to custom/ce/monitors/render.go index 99c28a80..eb29f5c9 100644 --- a/custom/costexplorer/monitors/render.go +++ b/custom/ce/monitors/render.go @@ -16,7 +16,7 @@ type MonitorRenderer struct { func NewMonitorRenderer() render.Renderer { return &MonitorRenderer{ BaseRenderer: render.BaseRenderer{ - Service: "costexplorer", + Service: "ce", Resource: "monitors", Cols: []render.Column{ {Name: "NAME", Width: 35, Getter: getName}, diff --git a/custom/cfn/stacks/actions.go b/custom/cfn/stacks/actions.go index 6a36ff01..2d29691f 100644 --- a/custom/cfn/stacks/actions.go +++ b/custom/cfn/stacks/actions.go @@ -6,6 +6,7 @@ import ( "github.com/aws/aws-sdk-go-v2/service/cloudformation" + cfnClient "github.com/clawscli/claws/custom/cfn" "github.com/clawscli/claws/internal/action" appaws "github.com/clawscli/claws/internal/aws" "github.com/clawscli/claws/internal/dao" @@ -54,16 +55,8 @@ func executeStackAction(ctx context.Context, act action.Action, resource dao.Res } } -func getClient(ctx context.Context) (*cloudformation.Client, error) { - cfg, err := appaws.NewConfig(ctx) - if err != nil { - return nil, err - } - return cloudformation.NewFromConfig(cfg), nil -} - func executeDeleteStack(ctx context.Context, resource dao.Resource) action.ActionResult { - client, err := getClient(ctx) + client, err := cfnClient.GetClient(ctx) if err != nil { return action.ActionResult{Success: false, Error: err} } @@ -87,7 +80,7 @@ func executeDeleteStack(ctx context.Context, resource dao.Resource) action.Actio } func executeDetectStackDrift(ctx context.Context, resource dao.Resource) action.ActionResult { - client, err := getClient(ctx) + client, err := cfnClient.GetClient(ctx) if err != nil { return action.ActionResult{Success: false, Error: err} } @@ -110,7 +103,7 @@ func executeDetectStackDrift(ctx context.Context, resource dao.Resource) action. } func executeCancelUpdateStack(ctx context.Context, resource dao.Resource) action.ActionResult { - client, err := getClient(ctx) + client, err := cfnClient.GetClient(ctx) if err != nil { return action.ActionResult{Success: false, Error: err} } diff --git a/custom/cloudwatch/loggroups/actions.go b/custom/cloudwatch/log-groups/actions.go similarity index 100% rename from custom/cloudwatch/loggroups/actions.go rename to custom/cloudwatch/log-groups/actions.go diff --git a/custom/cloudwatch/loggroups/dao.go b/custom/cloudwatch/log-groups/dao.go similarity index 100% rename from custom/cloudwatch/loggroups/dao.go rename to custom/cloudwatch/log-groups/dao.go diff --git a/custom/cloudwatch/loggroups/register.go b/custom/cloudwatch/log-groups/register.go similarity index 100% rename from custom/cloudwatch/loggroups/register.go rename to custom/cloudwatch/log-groups/register.go diff --git a/custom/cloudwatch/loggroups/render.go b/custom/cloudwatch/log-groups/render.go similarity index 100% rename from custom/cloudwatch/loggroups/render.go rename to custom/cloudwatch/log-groups/render.go diff --git a/custom/cloudwatch/logstreams/actions.go b/custom/cloudwatch/log-streams/actions.go similarity index 93% rename from custom/cloudwatch/logstreams/actions.go rename to custom/cloudwatch/log-streams/actions.go index dbcfb976..24a3f81b 100644 --- a/custom/cloudwatch/logstreams/actions.go +++ b/custom/cloudwatch/log-streams/actions.go @@ -6,8 +6,8 @@ import ( "github.com/aws/aws-sdk-go-v2/service/cloudwatchlogs" + cwClient "github.com/clawscli/claws/custom/cloudwatch" "github.com/clawscli/claws/internal/action" - appaws "github.com/clawscli/claws/internal/aws" "github.com/clawscli/claws/internal/dao" ) @@ -56,11 +56,7 @@ func executeLogStreamAction(ctx context.Context, act action.Action, resource dao } func getCloudWatchLogsClient(ctx context.Context) (*cloudwatchlogs.Client, error) { - cfg, err := appaws.NewConfig(ctx) - if err != nil { - return nil, err - } - return cloudwatchlogs.NewFromConfig(cfg), nil + return cwClient.GetLogsClient(ctx) } func executeDeleteLogStream(ctx context.Context, resource dao.Resource) action.ActionResult { diff --git a/custom/cloudwatch/logstreams/dao.go b/custom/cloudwatch/log-streams/dao.go similarity index 100% rename from custom/cloudwatch/logstreams/dao.go rename to custom/cloudwatch/log-streams/dao.go diff --git a/custom/cloudwatch/logstreams/register.go b/custom/cloudwatch/log-streams/register.go similarity index 100% rename from custom/cloudwatch/logstreams/register.go rename to custom/cloudwatch/log-streams/register.go diff --git a/custom/cloudwatch/logstreams/render.go b/custom/cloudwatch/log-streams/render.go similarity index 100% rename from custom/cloudwatch/logstreams/render.go rename to custom/cloudwatch/log-streams/render.go diff --git a/custom/cognito/userpools/dao.go b/custom/cognito-idp/user-pools/dao.go similarity index 98% rename from custom/cognito/userpools/dao.go rename to custom/cognito-idp/user-pools/dao.go index 0305e5a4..9bdba074 100644 --- a/custom/cognito/userpools/dao.go +++ b/custom/cognito-idp/user-pools/dao.go @@ -21,10 +21,10 @@ type UserPoolDAO struct { func NewUserPoolDAO(ctx context.Context) (dao.DAO, error) { cfg, err := appaws.NewConfig(ctx) if err != nil { - return nil, apperrors.Wrap(err, "new cognito/userpools dao") + return nil, apperrors.Wrap(err, "new cognito-idp/user-pools dao") } return &UserPoolDAO{ - BaseDAO: dao.NewBaseDAO("cognito", "user-pools"), + BaseDAO: dao.NewBaseDAO("cognito-idp", "user-pools"), client: cognitoidentityprovider.NewFromConfig(cfg), }, nil } diff --git a/custom/cognito/userpools/register.go b/custom/cognito-idp/user-pools/register.go similarity index 82% rename from custom/cognito/userpools/register.go rename to custom/cognito-idp/user-pools/register.go index bd96d616..1d66f2f0 100644 --- a/custom/cognito/userpools/register.go +++ b/custom/cognito-idp/user-pools/register.go @@ -9,7 +9,7 @@ import ( ) func init() { - registry.Global.RegisterCustom("cognito", "user-pools", registry.Entry{ + registry.Global.RegisterCustom("cognito-idp", "user-pools", registry.Entry{ DAOFactory: func(ctx context.Context) (dao.DAO, error) { return NewUserPoolDAO(ctx) }, diff --git a/custom/cognito/userpools/render.go b/custom/cognito-idp/user-pools/render.go similarity index 97% rename from custom/cognito/userpools/render.go rename to custom/cognito-idp/user-pools/render.go index 715c1f0d..44c5220c 100644 --- a/custom/cognito/userpools/render.go +++ b/custom/cognito-idp/user-pools/render.go @@ -20,7 +20,7 @@ type UserPoolRenderer struct { func NewUserPoolRenderer() *UserPoolRenderer { return &UserPoolRenderer{ BaseRenderer: render.BaseRenderer{ - Service: "cognito", + Service: "cognito-idp", Resource: "user-pools", Cols: []render.Column{ {Name: "ID", Width: 25, Getter: getPoolId}, @@ -182,7 +182,7 @@ func (r *UserPoolRenderer) Navigations(resource dao.Resource) []render.Navigatio return []render.Navigation{ { - Key: "u", Label: "Users", Service: "cognito", Resource: "users", + Key: "u", Label: "Users", Service: "cognito-idp", Resource: "users", FilterField: "UserPoolId", FilterValue: pool.PoolId(), }, } diff --git a/custom/cognito/users/dao.go b/custom/cognito-idp/users/dao.go similarity index 98% rename from custom/cognito/users/dao.go rename to custom/cognito-idp/users/dao.go index 6cac8f44..654354ca 100644 --- a/custom/cognito/users/dao.go +++ b/custom/cognito-idp/users/dao.go @@ -23,10 +23,10 @@ type UserDAO struct { func NewUserDAO(ctx context.Context) (dao.DAO, error) { cfg, err := appaws.NewConfig(ctx) if err != nil { - return nil, apperrors.Wrap(err, "new cognito/users dao") + return nil, apperrors.Wrap(err, "new cognito-idp/users dao") } return &UserDAO{ - BaseDAO: dao.NewBaseDAO("cognito", "users"), + BaseDAO: dao.NewBaseDAO("cognito-idp", "users"), client: cognitoidentityprovider.NewFromConfig(cfg), }, nil } diff --git a/custom/cognito/users/register.go b/custom/cognito-idp/users/register.go similarity index 83% rename from custom/cognito/users/register.go rename to custom/cognito-idp/users/register.go index 445ea85d..b2bf465f 100644 --- a/custom/cognito/users/register.go +++ b/custom/cognito-idp/users/register.go @@ -9,7 +9,7 @@ import ( ) func init() { - registry.Global.RegisterCustom("cognito", "users", registry.Entry{ + registry.Global.RegisterCustom("cognito-idp", "users", registry.Entry{ DAOFactory: func(ctx context.Context) (dao.DAO, error) { return NewUserDAO(ctx) }, diff --git a/custom/cognito/users/render.go b/custom/cognito-idp/users/render.go similarity index 99% rename from custom/cognito/users/render.go rename to custom/cognito-idp/users/render.go index d8c5805d..e6643cd5 100644 --- a/custom/cognito/users/render.go +++ b/custom/cognito-idp/users/render.go @@ -20,7 +20,7 @@ type UserRenderer struct { func NewUserRenderer() *UserRenderer { return &UserRenderer{ BaseRenderer: render.BaseRenderer{ - Service: "cognito", + Service: "cognito-idp", Resource: "users", Cols: []render.Column{ {Name: "USERNAME", Width: 30, Getter: getUsername}, diff --git a/custom/computeoptimizer/recommendations/dao.go b/custom/compute-optimizer/recommendations/dao.go similarity index 98% rename from custom/computeoptimizer/recommendations/dao.go rename to custom/compute-optimizer/recommendations/dao.go index b6055e52..2e610e00 100644 --- a/custom/computeoptimizer/recommendations/dao.go +++ b/custom/compute-optimizer/recommendations/dao.go @@ -27,10 +27,10 @@ type RecommendationDAO struct { func NewRecommendationDAO(ctx context.Context) (dao.DAO, error) { cfg, err := appaws.NewConfig(ctx) if err != nil { - return nil, apperrors.Wrap(err, "new computeoptimizer/recommendations dao") + return nil, apperrors.Wrap(err, "new compute-optimizer/recommendations dao") } return &RecommendationDAO{ - BaseDAO: dao.NewBaseDAO("computeoptimizer", "recommendations"), + BaseDAO: dao.NewBaseDAO("compute-optimizer", "recommendations"), client: computeoptimizer.NewFromConfig(cfg), }, nil } diff --git a/custom/computeoptimizer/recommendations/register.go b/custom/compute-optimizer/recommendations/register.go similarity index 81% rename from custom/computeoptimizer/recommendations/register.go rename to custom/compute-optimizer/recommendations/register.go index 2d4e0c15..dda8f27a 100644 --- a/custom/computeoptimizer/recommendations/register.go +++ b/custom/compute-optimizer/recommendations/register.go @@ -9,7 +9,7 @@ import ( ) func init() { - registry.Global.RegisterCustom("computeoptimizer", "recommendations", registry.Entry{ + registry.Global.RegisterCustom("compute-optimizer", "recommendations", registry.Entry{ DAOFactory: func(ctx context.Context) (dao.DAO, error) { return NewRecommendationDAO(ctx) }, diff --git a/custom/computeoptimizer/recommendations/render.go b/custom/compute-optimizer/recommendations/render.go similarity index 99% rename from custom/computeoptimizer/recommendations/render.go rename to custom/compute-optimizer/recommendations/render.go index cabc4b19..fde43587 100644 --- a/custom/computeoptimizer/recommendations/render.go +++ b/custom/compute-optimizer/recommendations/render.go @@ -20,7 +20,7 @@ type RecommendationRenderer struct { func NewRecommendationRenderer() render.Renderer { return &RecommendationRenderer{ BaseRenderer: render.BaseRenderer{ - Service: "computeoptimizer", + Service: "compute-optimizer", Resource: "recommendations", Cols: []render.Column{ {Name: "TYPE", Width: 8, Getter: getType}, diff --git a/custom/computeoptimizer/summary/dao.go b/custom/compute-optimizer/summary/dao.go similarity index 97% rename from custom/computeoptimizer/summary/dao.go rename to custom/compute-optimizer/summary/dao.go index a0052b89..d730473c 100644 --- a/custom/computeoptimizer/summary/dao.go +++ b/custom/compute-optimizer/summary/dao.go @@ -22,10 +22,10 @@ type SummaryDAO struct { func NewSummaryDAO(ctx context.Context) (dao.DAO, error) { cfg, err := appaws.NewConfig(ctx) if err != nil { - return nil, apperrors.Wrap(err, "new computeoptimizer/summary dao") + return nil, apperrors.Wrap(err, "new compute-optimizer/summary dao") } return &SummaryDAO{ - BaseDAO: dao.NewBaseDAO("computeoptimizer", "summary"), + BaseDAO: dao.NewBaseDAO("compute-optimizer", "summary"), client: computeoptimizer.NewFromConfig(cfg), }, nil } diff --git a/custom/computeoptimizer/summary/register.go b/custom/compute-optimizer/summary/register.go similarity index 82% rename from custom/computeoptimizer/summary/register.go rename to custom/compute-optimizer/summary/register.go index 5790672c..1294d491 100644 --- a/custom/computeoptimizer/summary/register.go +++ b/custom/compute-optimizer/summary/register.go @@ -9,7 +9,7 @@ import ( ) func init() { - registry.Global.RegisterCustom("computeoptimizer", "summary", registry.Entry{ + registry.Global.RegisterCustom("compute-optimizer", "summary", registry.Entry{ DAOFactory: func(ctx context.Context) (dao.DAO, error) { return NewSummaryDAO(ctx) }, diff --git a/custom/computeoptimizer/summary/render.go b/custom/compute-optimizer/summary/render.go similarity index 99% rename from custom/computeoptimizer/summary/render.go rename to custom/compute-optimizer/summary/render.go index b8c47aa3..6f93b293 100644 --- a/custom/computeoptimizer/summary/render.go +++ b/custom/compute-optimizer/summary/render.go @@ -17,7 +17,7 @@ type SummaryRenderer struct { func NewSummaryRenderer() render.Renderer { return &SummaryRenderer{ BaseRenderer: render.BaseRenderer{ - Service: "computeoptimizer", + Service: "compute-optimizer", Resource: "summary", Cols: []render.Column{ {Name: "RESOURCE TYPE", Width: 20, Getter: getResourceType}, diff --git a/custom/config/rules/dao.go b/custom/configservice/rules/dao.go similarity index 97% rename from custom/config/rules/dao.go rename to custom/configservice/rules/dao.go index 0af82fcf..646fd585 100644 --- a/custom/config/rules/dao.go +++ b/custom/configservice/rules/dao.go @@ -22,10 +22,10 @@ type RuleDAO struct { func NewRuleDAO(ctx context.Context) (dao.DAO, error) { cfg, err := appaws.NewConfig(ctx) if err != nil { - return nil, apperrors.Wrap(err, "new config/rules dao") + return nil, apperrors.Wrap(err, "new configservice/rules dao") } return &RuleDAO{ - BaseDAO: dao.NewBaseDAO("config", "rules"), + BaseDAO: dao.NewBaseDAO("configservice", "rules"), client: configservice.NewFromConfig(cfg), }, nil } diff --git a/custom/eventbridge/rules/register.go b/custom/configservice/rules/register.go similarity index 82% rename from custom/eventbridge/rules/register.go rename to custom/configservice/rules/register.go index 977b2152..6c8051dd 100644 --- a/custom/eventbridge/rules/register.go +++ b/custom/configservice/rules/register.go @@ -9,7 +9,7 @@ import ( ) func init() { - registry.Global.RegisterCustom("eventbridge", "rules", registry.Entry{ + registry.Global.RegisterCustom("configservice", "rules", registry.Entry{ DAOFactory: func(ctx context.Context) (dao.DAO, error) { return NewRuleDAO(ctx) }, diff --git a/custom/config/rules/render.go b/custom/configservice/rules/render.go similarity index 98% rename from custom/config/rules/render.go rename to custom/configservice/rules/render.go index ad272b2b..2550aed7 100644 --- a/custom/config/rules/render.go +++ b/custom/configservice/rules/render.go @@ -14,7 +14,7 @@ type RuleRenderer struct { func NewRuleRenderer() render.Renderer { return &RuleRenderer{ BaseRenderer: render.BaseRenderer{ - Service: "config", + Service: "configservice", Resource: "rules", Cols: []render.Column{ {Name: "RULE NAME", Width: 45, Getter: func(r dao.Resource) string { return r.GetID() }}, diff --git a/custom/datasync/taskexecutions/dao.go b/custom/datasync/task-executions/dao.go similarity index 98% rename from custom/datasync/taskexecutions/dao.go rename to custom/datasync/task-executions/dao.go index 1b2bc5cc..32f74e26 100644 --- a/custom/datasync/taskexecutions/dao.go +++ b/custom/datasync/task-executions/dao.go @@ -24,7 +24,7 @@ type TaskExecutionDAO struct { func NewTaskExecutionDAO(ctx context.Context) (dao.DAO, error) { cfg, err := appaws.NewConfig(ctx) if err != nil { - return nil, apperrors.Wrap(err, "new datasync/taskexecutions dao") + return nil, apperrors.Wrap(err, "new datasync/task-executions dao") } return &TaskExecutionDAO{ BaseDAO: dao.NewBaseDAO("datasync", "task-executions"), diff --git a/custom/datasync/taskexecutions/register.go b/custom/datasync/task-executions/register.go similarity index 100% rename from custom/datasync/taskexecutions/register.go rename to custom/datasync/task-executions/register.go diff --git a/custom/datasync/taskexecutions/render.go b/custom/datasync/task-executions/render.go similarity index 100% rename from custom/datasync/taskexecutions/render.go rename to custom/datasync/task-executions/render.go diff --git a/custom/directconnect/virtualinterfaces/dao.go b/custom/directconnect/virtual-interfaces/dao.go similarity index 100% rename from custom/directconnect/virtualinterfaces/dao.go rename to custom/directconnect/virtual-interfaces/dao.go diff --git a/custom/directconnect/virtualinterfaces/register.go b/custom/directconnect/virtual-interfaces/register.go similarity index 100% rename from custom/directconnect/virtualinterfaces/register.go rename to custom/directconnect/virtual-interfaces/register.go diff --git a/custom/directconnect/virtualinterfaces/render.go b/custom/directconnect/virtual-interfaces/render.go similarity index 100% rename from custom/directconnect/virtualinterfaces/render.go rename to custom/directconnect/virtual-interfaces/render.go diff --git a/custom/dynamodb/tables/actions.go b/custom/dynamodb/tables/actions.go index 66cfe947..d9521eda 100644 --- a/custom/dynamodb/tables/actions.go +++ b/custom/dynamodb/tables/actions.go @@ -7,8 +7,8 @@ import ( "github.com/aws/aws-sdk-go-v2/service/dynamodb" "github.com/aws/aws-sdk-go-v2/service/dynamodb/types" + ddbClient "github.com/clawscli/claws/custom/dynamodb" "github.com/clawscli/claws/internal/action" - appaws "github.com/clawscli/claws/internal/aws" "github.com/clawscli/claws/internal/dao" ) @@ -75,11 +75,7 @@ func executeTableAction(ctx context.Context, act action.Action, resource dao.Res } func getDynamoDBClient(ctx context.Context) (*dynamodb.Client, error) { - cfg, err := appaws.NewConfig(ctx) - if err != nil { - return nil, err - } - return dynamodb.NewFromConfig(cfg), nil + return ddbClient.GetClient(ctx) } func executeScaleCapacity(ctx context.Context, resource dao.Resource, scaleRCU, scaleWCU bool) action.ActionResult { diff --git a/custom/ec2/capacityreservations/dao.go b/custom/ec2/capacity-reservations/dao.go similarity index 100% rename from custom/ec2/capacityreservations/dao.go rename to custom/ec2/capacity-reservations/dao.go diff --git a/custom/ec2/capacityreservations/register.go b/custom/ec2/capacity-reservations/register.go similarity index 100% rename from custom/ec2/capacityreservations/register.go rename to custom/ec2/capacity-reservations/register.go diff --git a/custom/ec2/capacityreservations/render.go b/custom/ec2/capacity-reservations/render.go similarity index 100% rename from custom/ec2/capacityreservations/render.go rename to custom/ec2/capacity-reservations/render.go diff --git a/custom/ec2/elasticips/actions.go b/custom/ec2/elastic-ips/actions.go similarity index 100% rename from custom/ec2/elasticips/actions.go rename to custom/ec2/elastic-ips/actions.go diff --git a/custom/ec2/elasticips/dao.go b/custom/ec2/elastic-ips/dao.go similarity index 100% rename from custom/ec2/elasticips/dao.go rename to custom/ec2/elastic-ips/dao.go diff --git a/custom/ec2/elasticips/register.go b/custom/ec2/elastic-ips/register.go similarity index 100% rename from custom/ec2/elasticips/register.go rename to custom/ec2/elastic-ips/register.go diff --git a/custom/ec2/elasticips/render.go b/custom/ec2/elastic-ips/render.go similarity index 100% rename from custom/ec2/elasticips/render.go rename to custom/ec2/elastic-ips/render.go diff --git a/custom/ec2/keypairs/actions.go b/custom/ec2/key-pairs/actions.go similarity index 100% rename from custom/ec2/keypairs/actions.go rename to custom/ec2/key-pairs/actions.go diff --git a/custom/ec2/keypairs/dao.go b/custom/ec2/key-pairs/dao.go similarity index 100% rename from custom/ec2/keypairs/dao.go rename to custom/ec2/key-pairs/dao.go diff --git a/custom/ec2/keypairs/register.go b/custom/ec2/key-pairs/register.go similarity index 100% rename from custom/ec2/keypairs/register.go rename to custom/ec2/key-pairs/register.go diff --git a/custom/ec2/keypairs/render.go b/custom/ec2/key-pairs/render.go similarity index 100% rename from custom/ec2/keypairs/render.go rename to custom/ec2/key-pairs/render.go diff --git a/custom/ec2/launchtemplates/dao.go b/custom/ec2/launch-templates/dao.go similarity index 100% rename from custom/ec2/launchtemplates/dao.go rename to custom/ec2/launch-templates/dao.go diff --git a/custom/ec2/launchtemplates/register.go b/custom/ec2/launch-templates/register.go similarity index 100% rename from custom/ec2/launchtemplates/register.go rename to custom/ec2/launch-templates/register.go diff --git a/custom/ec2/launchtemplates/render.go b/custom/ec2/launch-templates/render.go similarity index 100% rename from custom/ec2/launchtemplates/render.go rename to custom/ec2/launch-templates/render.go diff --git a/custom/ec2/securitygroups/actions.go b/custom/ec2/security-groups/actions.go similarity index 100% rename from custom/ec2/securitygroups/actions.go rename to custom/ec2/security-groups/actions.go diff --git a/custom/ec2/securitygroups/dao.go b/custom/ec2/security-groups/dao.go similarity index 100% rename from custom/ec2/securitygroups/dao.go rename to custom/ec2/security-groups/dao.go diff --git a/custom/ec2/securitygroups/register.go b/custom/ec2/security-groups/register.go similarity index 100% rename from custom/ec2/securitygroups/register.go rename to custom/ec2/security-groups/register.go diff --git a/custom/ec2/securitygroups/render.go b/custom/ec2/security-groups/render.go similarity index 100% rename from custom/ec2/securitygroups/render.go rename to custom/ec2/security-groups/render.go diff --git a/custom/ecr/client.go b/custom/ecr/client.go new file mode 100644 index 00000000..5ae6f71c --- /dev/null +++ b/custom/ecr/client.go @@ -0,0 +1,17 @@ +package ecr + +import ( + "context" + + "github.com/aws/aws-sdk-go-v2/service/ecr" + + appaws "github.com/clawscli/claws/internal/aws" +) + +func GetClient(ctx context.Context) (*ecr.Client, error) { + cfg, err := appaws.NewConfig(ctx) + if err != nil { + return nil, err + } + return ecr.NewFromConfig(cfg), nil +} diff --git a/custom/ecr/repositories/actions.go b/custom/ecr/repositories/actions.go index 070a240a..f1c407c8 100644 --- a/custom/ecr/repositories/actions.go +++ b/custom/ecr/repositories/actions.go @@ -6,8 +6,8 @@ import ( "github.com/aws/aws-sdk-go-v2/service/ecr" + ecrClient "github.com/clawscli/claws/custom/ecr" "github.com/clawscli/claws/internal/action" - appaws "github.com/clawscli/claws/internal/aws" "github.com/clawscli/claws/internal/dao" ) @@ -35,11 +35,7 @@ func executeRepositoryAction(ctx context.Context, act action.Action, resource da } func getECRClient(ctx context.Context) (*ecr.Client, error) { - cfg, err := appaws.NewConfig(ctx) - if err != nil { - return nil, err - } - return ecr.NewFromConfig(cfg), nil + return ecrClient.GetClient(ctx) } func executeDeleteRepository(ctx context.Context, resource dao.Resource) action.ActionResult { diff --git a/custom/elbv2/loadbalancers/dao.go b/custom/elbv2/load-balancers/dao.go similarity index 100% rename from custom/elbv2/loadbalancers/dao.go rename to custom/elbv2/load-balancers/dao.go diff --git a/custom/elbv2/loadbalancers/register.go b/custom/elbv2/load-balancers/register.go similarity index 100% rename from custom/elbv2/loadbalancers/register.go rename to custom/elbv2/load-balancers/register.go diff --git a/custom/elbv2/loadbalancers/render.go b/custom/elbv2/load-balancers/render.go similarity index 100% rename from custom/elbv2/loadbalancers/render.go rename to custom/elbv2/load-balancers/render.go diff --git a/custom/elbv2/loadbalancers/resource_test.go b/custom/elbv2/load-balancers/resource_test.go similarity index 100% rename from custom/elbv2/loadbalancers/resource_test.go rename to custom/elbv2/load-balancers/resource_test.go diff --git a/custom/elbv2/targetgroups/dao.go b/custom/elbv2/target-groups/dao.go similarity index 100% rename from custom/elbv2/targetgroups/dao.go rename to custom/elbv2/target-groups/dao.go diff --git a/custom/elbv2/targetgroups/register.go b/custom/elbv2/target-groups/register.go similarity index 100% rename from custom/elbv2/targetgroups/register.go rename to custom/elbv2/target-groups/register.go diff --git a/custom/elbv2/targetgroups/render.go b/custom/elbv2/target-groups/render.go similarity index 100% rename from custom/elbv2/targetgroups/render.go rename to custom/elbv2/target-groups/render.go diff --git a/custom/eventbridge/buses/actions.go b/custom/events/buses/actions.go similarity index 73% rename from custom/eventbridge/buses/actions.go rename to custom/events/buses/actions.go index a0daa6f6..4821b403 100644 --- a/custom/eventbridge/buses/actions.go +++ b/custom/events/buses/actions.go @@ -6,13 +6,13 @@ import ( "github.com/aws/aws-sdk-go-v2/service/eventbridge" + ebClient "github.com/clawscli/claws/custom/events" "github.com/clawscli/claws/internal/action" - appaws "github.com/clawscli/claws/internal/aws" "github.com/clawscli/claws/internal/dao" ) func init() { - action.Global.Register("eventbridge", "buses", []action.Action{ + action.Global.Register("events", "buses", []action.Action{ { Name: "Delete", Shortcut: "D", @@ -22,7 +22,7 @@ func init() { }, }) - action.RegisterExecutor("eventbridge", "buses", executeBusAction) + action.RegisterExecutor("events", "buses", executeBusAction) } func executeBusAction(ctx context.Context, act action.Action, resource dao.Resource) action.ActionResult { @@ -34,16 +34,8 @@ func executeBusAction(ctx context.Context, act action.Action, resource dao.Resou } } -func getEventBridgeClient(ctx context.Context) (*eventbridge.Client, error) { - cfg, err := appaws.NewConfig(ctx) - if err != nil { - return nil, err - } - return eventbridge.NewFromConfig(cfg), nil -} - func executeDeleteEventBus(ctx context.Context, resource dao.Resource) action.ActionResult { - client, err := getEventBridgeClient(ctx) + client, err := ebClient.GetClient(ctx) if err != nil { return action.ActionResult{Success: false, Error: err} } diff --git a/custom/eventbridge/buses/dao.go b/custom/events/buses/dao.go similarity index 95% rename from custom/eventbridge/buses/dao.go rename to custom/events/buses/dao.go index e4832525..62b553aa 100644 --- a/custom/eventbridge/buses/dao.go +++ b/custom/events/buses/dao.go @@ -21,10 +21,10 @@ type BusDAO struct { func NewBusDAO(ctx context.Context) (dao.DAO, error) { cfg, err := appaws.NewConfig(ctx) if err != nil { - return nil, apperrors.Wrap(err, "new eventbridge/buses dao") + return nil, apperrors.Wrap(err, "new events/buses dao") } return &BusDAO{ - BaseDAO: dao.NewBaseDAO("eventbridge", "buses"), + BaseDAO: dao.NewBaseDAO("events", "buses"), client: eventbridge.NewFromConfig(cfg), }, nil } diff --git a/custom/eventbridge/buses/register.go b/custom/events/buses/register.go similarity index 83% rename from custom/eventbridge/buses/register.go rename to custom/events/buses/register.go index aaace9c6..4ca3dbf1 100644 --- a/custom/eventbridge/buses/register.go +++ b/custom/events/buses/register.go @@ -9,7 +9,7 @@ import ( ) func init() { - registry.Global.RegisterCustom("eventbridge", "buses", registry.Entry{ + registry.Global.RegisterCustom("events", "buses", registry.Entry{ DAOFactory: func(ctx context.Context) (dao.DAO, error) { return NewBusDAO(ctx) }, diff --git a/custom/eventbridge/buses/render.go b/custom/events/buses/render.go similarity index 96% rename from custom/eventbridge/buses/render.go rename to custom/events/buses/render.go index 3b66c30e..6b7d8ff7 100644 --- a/custom/eventbridge/buses/render.go +++ b/custom/events/buses/render.go @@ -17,7 +17,7 @@ type BusRenderer struct { func NewBusRenderer() render.Renderer { return &BusRenderer{ BaseRenderer: render.BaseRenderer{ - Service: "eventbridge", + Service: "events", Resource: "buses", Cols: []render.Column{ { @@ -114,7 +114,7 @@ func (r *BusRenderer) Navigations(resource dao.Resource) []render.Navigation { // Rules navigation navs = append(navs, render.Navigation{ - Key: "r", Label: "Rules", Service: "eventbridge", Resource: "rules", + Key: "r", Label: "Rules", Service: "events", Resource: "rules", FilterField: "EventBusName", FilterValue: br.GetName(), }) diff --git a/custom/eventbridge/client.go b/custom/events/client.go similarity index 95% rename from custom/eventbridge/client.go rename to custom/events/client.go index 5cf03e69..90e219cb 100644 --- a/custom/eventbridge/client.go +++ b/custom/events/client.go @@ -1,4 +1,4 @@ -package eventbridge +package events import ( "context" diff --git a/custom/eventbridge/rules/actions.go b/custom/events/rules/actions.go similarity index 95% rename from custom/eventbridge/rules/actions.go rename to custom/events/rules/actions.go index 9328f5aa..e2b44ea0 100644 --- a/custom/eventbridge/rules/actions.go +++ b/custom/events/rules/actions.go @@ -6,14 +6,14 @@ import ( "github.com/aws/aws-sdk-go-v2/service/eventbridge" - ebClient "github.com/clawscli/claws/custom/eventbridge" + ebClient "github.com/clawscli/claws/custom/events" "github.com/clawscli/claws/internal/action" "github.com/clawscli/claws/internal/dao" ) func init() { // Register actions for EventBridge rules - action.Global.Register("eventbridge", "rules", []action.Action{ + action.Global.Register("events", "rules", []action.Action{ { Name: "Enable", Shortcut: "E", @@ -38,7 +38,7 @@ func init() { }) // Register executor - action.RegisterExecutor("eventbridge", "rules", executeRuleAction) + action.RegisterExecutor("events", "rules", executeRuleAction) } // executeRuleAction executes an action on an EventBridge rule diff --git a/custom/eventbridge/rules/dao.go b/custom/events/rules/dao.go similarity index 97% rename from custom/eventbridge/rules/dao.go rename to custom/events/rules/dao.go index 5d3a2c9d..88bb4e35 100644 --- a/custom/eventbridge/rules/dao.go +++ b/custom/events/rules/dao.go @@ -22,10 +22,10 @@ type RuleDAO struct { func NewRuleDAO(ctx context.Context) (dao.DAO, error) { cfg, err := appaws.NewConfig(ctx) if err != nil { - return nil, apperrors.Wrap(err, "new eventbridge/rules dao") + return nil, apperrors.Wrap(err, "new events/rules dao") } return &RuleDAO{ - BaseDAO: dao.NewBaseDAO("eventbridge", "rules"), + BaseDAO: dao.NewBaseDAO("events", "rules"), client: eventbridge.NewFromConfig(cfg), }, nil } diff --git a/custom/config/rules/register.go b/custom/events/rules/register.go similarity index 85% rename from custom/config/rules/register.go rename to custom/events/rules/register.go index ba996630..c313e2ec 100644 --- a/custom/config/rules/register.go +++ b/custom/events/rules/register.go @@ -9,7 +9,7 @@ import ( ) func init() { - registry.Global.RegisterCustom("config", "rules", registry.Entry{ + registry.Global.RegisterCustom("events", "rules", registry.Entry{ DAOFactory: func(ctx context.Context) (dao.DAO, error) { return NewRuleDAO(ctx) }, diff --git a/custom/eventbridge/rules/render.go b/custom/events/rules/render.go similarity index 98% rename from custom/eventbridge/rules/render.go rename to custom/events/rules/render.go index a99e2d07..531c44f5 100644 --- a/custom/eventbridge/rules/render.go +++ b/custom/events/rules/render.go @@ -22,7 +22,7 @@ type RuleRenderer struct { func NewRuleRenderer() render.Renderer { return &RuleRenderer{ BaseRenderer: render.BaseRenderer{ - Service: "eventbridge", + Service: "events", Resource: "rules", Cols: []render.Column{ { @@ -221,7 +221,7 @@ func (r *RuleRenderer) Navigations(resource dao.Resource) []render.Navigation { // Event Bus navigation navs = append(navs, render.Navigation{ - Key: "b", Label: "Event Bus", Service: "eventbridge", Resource: "buses", + Key: "b", Label: "Event Bus", Service: "events", Resource: "buses", FilterField: "Name", FilterValue: rr.EventBusName(), }) diff --git a/custom/glue/jobruns/dao.go b/custom/glue/job-runs/dao.go similarity index 100% rename from custom/glue/jobruns/dao.go rename to custom/glue/job-runs/dao.go diff --git a/custom/glue/jobruns/register.go b/custom/glue/job-runs/register.go similarity index 100% rename from custom/glue/jobruns/register.go rename to custom/glue/job-runs/register.go diff --git a/custom/glue/jobruns/render.go b/custom/glue/job-runs/render.go similarity index 100% rename from custom/glue/jobruns/render.go rename to custom/glue/job-runs/render.go diff --git a/custom/iam/instanceprofiles/dao.go b/custom/iam/instance-profiles/dao.go similarity index 100% rename from custom/iam/instanceprofiles/dao.go rename to custom/iam/instance-profiles/dao.go diff --git a/custom/iam/instanceprofiles/register.go b/custom/iam/instance-profiles/register.go similarity index 100% rename from custom/iam/instanceprofiles/register.go rename to custom/iam/instance-profiles/register.go diff --git a/custom/iam/instanceprofiles/render.go b/custom/iam/instance-profiles/render.go similarity index 100% rename from custom/iam/instanceprofiles/render.go rename to custom/iam/instance-profiles/render.go diff --git a/custom/lambda/functions/actions.go b/custom/lambda/functions/actions.go index cdf9c6fc..112c6a88 100644 --- a/custom/lambda/functions/actions.go +++ b/custom/lambda/functions/actions.go @@ -8,8 +8,8 @@ import ( "github.com/aws/aws-sdk-go-v2/service/lambda" lambdatypes "github.com/aws/aws-sdk-go-v2/service/lambda/types" + lambdaClient "github.com/clawscli/claws/custom/lambda" "github.com/clawscli/claws/internal/action" - appaws "github.com/clawscli/claws/internal/aws" "github.com/clawscli/claws/internal/dao" ) @@ -57,11 +57,7 @@ func executeFunctionAction(ctx context.Context, act action.Action, resource dao. } func getLambdaClient(ctx context.Context) (*lambda.Client, error) { - cfg, err := appaws.NewConfig(ctx) - if err != nil { - return nil, err - } - return lambda.NewFromConfig(cfg), nil + return lambdaClient.GetClient(ctx) } func executeInvoke(ctx context.Context, resource dao.Resource, dryRun bool) action.ActionResult { diff --git a/custom/licensemanager/configurations/dao.go b/custom/license-manager/configurations/dao.go similarity index 100% rename from custom/licensemanager/configurations/dao.go rename to custom/license-manager/configurations/dao.go diff --git a/custom/licensemanager/configurations/register.go b/custom/license-manager/configurations/register.go similarity index 100% rename from custom/licensemanager/configurations/register.go rename to custom/license-manager/configurations/register.go diff --git a/custom/licensemanager/configurations/render.go b/custom/license-manager/configurations/render.go similarity index 100% rename from custom/licensemanager/configurations/render.go rename to custom/license-manager/configurations/render.go diff --git a/custom/licensemanager/grants/dao.go b/custom/license-manager/grants/dao.go similarity index 100% rename from custom/licensemanager/grants/dao.go rename to custom/license-manager/grants/dao.go diff --git a/custom/licensemanager/grants/register.go b/custom/license-manager/grants/register.go similarity index 100% rename from custom/licensemanager/grants/register.go rename to custom/license-manager/grants/register.go diff --git a/custom/licensemanager/grants/render.go b/custom/license-manager/grants/render.go similarity index 100% rename from custom/licensemanager/grants/render.go rename to custom/license-manager/grants/render.go diff --git a/custom/licensemanager/licenses/dao.go b/custom/license-manager/licenses/dao.go similarity index 100% rename from custom/licensemanager/licenses/dao.go rename to custom/license-manager/licenses/dao.go diff --git a/custom/licensemanager/licenses/register.go b/custom/license-manager/licenses/register.go similarity index 100% rename from custom/licensemanager/licenses/register.go rename to custom/license-manager/licenses/register.go diff --git a/custom/licensemanager/licenses/render.go b/custom/license-manager/licenses/render.go similarity index 100% rename from custom/licensemanager/licenses/render.go rename to custom/license-manager/licenses/render.go diff --git a/custom/macie/buckets/dao.go b/custom/macie2/buckets/dao.go similarity index 98% rename from custom/macie/buckets/dao.go rename to custom/macie2/buckets/dao.go index 0caf7490..1dfa34b1 100644 --- a/custom/macie/buckets/dao.go +++ b/custom/macie2/buckets/dao.go @@ -25,7 +25,7 @@ func NewBucketDAO(ctx context.Context) (dao.DAO, error) { return nil, apperrors.Wrap(err, "new macie/buckets dao") } return &BucketDAO{ - BaseDAO: dao.NewBaseDAO("macie", "buckets"), + BaseDAO: dao.NewBaseDAO("macie2", "buckets"), client: macie2.NewFromConfig(cfg), }, nil } diff --git a/custom/macie/buckets/register.go b/custom/macie2/buckets/register.go similarity index 84% rename from custom/macie/buckets/register.go rename to custom/macie2/buckets/register.go index 6cb10631..75f3ffa7 100644 --- a/custom/macie/buckets/register.go +++ b/custom/macie2/buckets/register.go @@ -9,7 +9,7 @@ import ( ) func init() { - registry.Global.RegisterCustom("macie", "buckets", registry.Entry{ + registry.Global.RegisterCustom("macie2", "buckets", registry.Entry{ DAOFactory: func(ctx context.Context) (dao.DAO, error) { return NewBucketDAO(ctx) }, diff --git a/custom/macie/buckets/render.go b/custom/macie2/buckets/render.go similarity index 98% rename from custom/macie/buckets/render.go rename to custom/macie2/buckets/render.go index 1871d791..9c89729b 100644 --- a/custom/macie/buckets/render.go +++ b/custom/macie2/buckets/render.go @@ -19,7 +19,7 @@ type BucketRenderer struct { func NewBucketRenderer() render.Renderer { return &BucketRenderer{ BaseRenderer: render.BaseRenderer{ - Service: "macie", + Service: "macie2", Resource: "buckets", Cols: []render.Column{ {Name: "BUCKET NAME", Width: 40, Getter: func(r dao.Resource) string { return r.GetID() }}, @@ -105,7 +105,7 @@ func (r *BucketRenderer) Navigations(resource dao.Resource) []render.Navigation { Key: "f", Label: "Findings", - Service: "macie", + Service: "macie2", Resource: "findings", FilterField: "BucketName", FilterValue: bucket.Name(), diff --git a/custom/macie/classificationjobs/dao.go b/custom/macie2/classification-jobs/dao.go similarity index 98% rename from custom/macie/classificationjobs/dao.go rename to custom/macie2/classification-jobs/dao.go index 0c0445ce..159893d7 100644 --- a/custom/macie/classificationjobs/dao.go +++ b/custom/macie2/classification-jobs/dao.go @@ -25,7 +25,7 @@ func NewClassificationJobDAO(ctx context.Context) (dao.DAO, error) { return nil, apperrors.Wrap(err, "new macie/classificationjobs dao") } return &ClassificationJobDAO{ - BaseDAO: dao.NewBaseDAO("macie", "classification-jobs"), + BaseDAO: dao.NewBaseDAO("macie2", "classification-jobs"), client: macie2.NewFromConfig(cfg), }, nil } diff --git a/custom/macie/classificationjobs/register.go b/custom/macie2/classification-jobs/register.go similarity index 83% rename from custom/macie/classificationjobs/register.go rename to custom/macie2/classification-jobs/register.go index bb70d5b2..163981a2 100644 --- a/custom/macie/classificationjobs/register.go +++ b/custom/macie2/classification-jobs/register.go @@ -9,7 +9,7 @@ import ( ) func init() { - registry.Global.RegisterCustom("macie", "classification-jobs", registry.Entry{ + registry.Global.RegisterCustom("macie2", "classification-jobs", registry.Entry{ DAOFactory: func(ctx context.Context) (dao.DAO, error) { return NewClassificationJobDAO(ctx) }, diff --git a/custom/macie/classificationjobs/render.go b/custom/macie2/classification-jobs/render.go similarity index 99% rename from custom/macie/classificationjobs/render.go rename to custom/macie2/classification-jobs/render.go index c73a6cbb..e28ea7b5 100644 --- a/custom/macie/classificationjobs/render.go +++ b/custom/macie2/classification-jobs/render.go @@ -14,7 +14,7 @@ type ClassificationJobRenderer struct { func NewClassificationJobRenderer() render.Renderer { return &ClassificationJobRenderer{ BaseRenderer: render.BaseRenderer{ - Service: "macie", + Service: "macie2", Resource: "classification-jobs", Cols: []render.Column{ {Name: "JOB ID", Width: 35, Getter: func(r dao.Resource) string { return r.GetID() }}, diff --git a/custom/macie/findings/dao.go b/custom/macie2/findings/dao.go similarity index 99% rename from custom/macie/findings/dao.go rename to custom/macie2/findings/dao.go index 96808321..f751908e 100644 --- a/custom/macie/findings/dao.go +++ b/custom/macie2/findings/dao.go @@ -26,7 +26,7 @@ func NewFindingDAO(ctx context.Context) (dao.DAO, error) { return nil, apperrors.Wrap(err, "new macie/findings dao") } return &FindingDAO{ - BaseDAO: dao.NewBaseDAO("macie", "findings"), + BaseDAO: dao.NewBaseDAO("macie2", "findings"), client: macie2.NewFromConfig(cfg), }, nil } diff --git a/custom/macie/findings/register.go b/custom/macie2/findings/register.go similarity index 84% rename from custom/macie/findings/register.go rename to custom/macie2/findings/register.go index 630e0593..97e8acad 100644 --- a/custom/macie/findings/register.go +++ b/custom/macie2/findings/register.go @@ -9,7 +9,7 @@ import ( ) func init() { - registry.Global.RegisterCustom("macie", "findings", registry.Entry{ + registry.Global.RegisterCustom("macie2", "findings", registry.Entry{ DAOFactory: func(ctx context.Context) (dao.DAO, error) { return NewFindingDAO(ctx) }, diff --git a/custom/macie/findings/render.go b/custom/macie2/findings/render.go similarity index 99% rename from custom/macie/findings/render.go rename to custom/macie2/findings/render.go index 496c56a0..9aeb47c4 100644 --- a/custom/macie/findings/render.go +++ b/custom/macie2/findings/render.go @@ -16,7 +16,7 @@ type FindingRenderer struct { func NewFindingRenderer() render.Renderer { return &FindingRenderer{ BaseRenderer: render.BaseRenderer{ - Service: "macie", + Service: "macie2", Resource: "findings", Cols: []render.Column{ {Name: "FINDING ID", Width: 35, Getter: func(r dao.Resource) string { return r.GetID() }}, diff --git a/custom/networkfirewall/firewallpolicies/dao.go b/custom/network-firewall/firewall-policies/dao.go similarity index 100% rename from custom/networkfirewall/firewallpolicies/dao.go rename to custom/network-firewall/firewall-policies/dao.go diff --git a/custom/networkfirewall/firewallpolicies/register.go b/custom/network-firewall/firewall-policies/register.go similarity index 100% rename from custom/networkfirewall/firewallpolicies/register.go rename to custom/network-firewall/firewall-policies/register.go diff --git a/custom/networkfirewall/firewallpolicies/render.go b/custom/network-firewall/firewall-policies/render.go similarity index 100% rename from custom/networkfirewall/firewallpolicies/render.go rename to custom/network-firewall/firewall-policies/render.go diff --git a/custom/networkfirewall/firewalls/dao.go b/custom/network-firewall/firewalls/dao.go similarity index 100% rename from custom/networkfirewall/firewalls/dao.go rename to custom/network-firewall/firewalls/dao.go diff --git a/custom/networkfirewall/firewalls/register.go b/custom/network-firewall/firewalls/register.go similarity index 100% rename from custom/networkfirewall/firewalls/register.go rename to custom/network-firewall/firewalls/register.go diff --git a/custom/networkfirewall/firewalls/render.go b/custom/network-firewall/firewalls/render.go similarity index 100% rename from custom/networkfirewall/firewalls/render.go rename to custom/network-firewall/firewalls/render.go diff --git a/custom/networkfirewall/rulegroups/dao.go b/custom/network-firewall/rule-groups/dao.go similarity index 100% rename from custom/networkfirewall/rulegroups/dao.go rename to custom/network-firewall/rule-groups/dao.go diff --git a/custom/networkfirewall/rulegroups/register.go b/custom/network-firewall/rule-groups/register.go similarity index 100% rename from custom/networkfirewall/rulegroups/register.go rename to custom/network-firewall/rule-groups/register.go diff --git a/custom/networkfirewall/rulegroups/render.go b/custom/network-firewall/rule-groups/render.go similarity index 100% rename from custom/networkfirewall/rulegroups/render.go rename to custom/network-firewall/rule-groups/render.go diff --git a/custom/rds/snapshots/actions.go b/custom/rds/snapshots/actions.go index 7240dc1e..ca49300e 100644 --- a/custom/rds/snapshots/actions.go +++ b/custom/rds/snapshots/actions.go @@ -6,8 +6,8 @@ import ( "github.com/aws/aws-sdk-go-v2/service/rds" + rdsClient "github.com/clawscli/claws/custom/rds" "github.com/clawscli/claws/internal/action" - appaws "github.com/clawscli/claws/internal/aws" "github.com/clawscli/claws/internal/dao" ) @@ -35,11 +35,7 @@ func executeSnapshotAction(ctx context.Context, act action.Action, resource dao. } func getRDSClient(ctx context.Context) (*rds.Client, error) { - cfg, err := appaws.NewConfig(ctx) - if err != nil { - return nil, err - } - return rds.NewFromConfig(cfg), nil + return rdsClient.GetClient(ctx) } func executeDeleteDBSnapshot(ctx context.Context, resource dao.Resource) action.ActionResult { diff --git a/custom/risp/reservedinstances/dao.go b/custom/risp/reserved-instances/dao.go similarity index 98% rename from custom/risp/reservedinstances/dao.go rename to custom/risp/reserved-instances/dao.go index dc08fe3d..a08be924 100644 --- a/custom/risp/reservedinstances/dao.go +++ b/custom/risp/reserved-instances/dao.go @@ -23,7 +23,7 @@ type ReservedInstanceDAO struct { func NewReservedInstanceDAO(ctx context.Context) (dao.DAO, error) { cfg, err := appaws.NewConfig(ctx) if err != nil { - return nil, apperrors.Wrap(err, "new risp/reservedinstances dao") + return nil, apperrors.Wrap(err, "new risp/reserved-instances dao") } return &ReservedInstanceDAO{ BaseDAO: dao.NewBaseDAO("risp", "reserved-instances"), diff --git a/custom/risp/reservedinstances/register.go b/custom/risp/reserved-instances/register.go similarity index 100% rename from custom/risp/reservedinstances/register.go rename to custom/risp/reserved-instances/register.go diff --git a/custom/risp/reservedinstances/render.go b/custom/risp/reserved-instances/render.go similarity index 100% rename from custom/risp/reservedinstances/render.go rename to custom/risp/reserved-instances/render.go diff --git a/custom/risp/savingsplans/dao.go b/custom/risp/savings-plans/dao.go similarity index 98% rename from custom/risp/savingsplans/dao.go rename to custom/risp/savings-plans/dao.go index a524526b..88364a2a 100644 --- a/custom/risp/savingsplans/dao.go +++ b/custom/risp/savings-plans/dao.go @@ -24,7 +24,7 @@ type SavingsPlanDAO struct { func NewSavingsPlanDAO(ctx context.Context) (dao.DAO, error) { cfg, err := appaws.NewConfig(ctx) if err != nil { - return nil, apperrors.Wrap(err, "new risp/savingsplans dao") + return nil, apperrors.Wrap(err, "new risp/savings-plans dao") } return &SavingsPlanDAO{ BaseDAO: dao.NewBaseDAO("risp", "savings-plans"), diff --git a/custom/risp/savingsplans/register.go b/custom/risp/savings-plans/register.go similarity index 100% rename from custom/risp/savingsplans/register.go rename to custom/risp/savings-plans/register.go diff --git a/custom/risp/savingsplans/render.go b/custom/risp/savings-plans/render.go similarity index 100% rename from custom/risp/savingsplans/render.go rename to custom/risp/savings-plans/render.go diff --git a/custom/route53/hostedzones/dao.go b/custom/route53/hosted-zones/dao.go similarity index 100% rename from custom/route53/hostedzones/dao.go rename to custom/route53/hosted-zones/dao.go diff --git a/custom/route53/hostedzones/register.go b/custom/route53/hosted-zones/register.go similarity index 100% rename from custom/route53/hostedzones/register.go rename to custom/route53/hosted-zones/register.go diff --git a/custom/route53/hostedzones/render.go b/custom/route53/hosted-zones/render.go similarity index 100% rename from custom/route53/hostedzones/render.go rename to custom/route53/hosted-zones/render.go diff --git a/custom/route53/recordsets/dao.go b/custom/route53/record-sets/dao.go similarity index 100% rename from custom/route53/recordsets/dao.go rename to custom/route53/record-sets/dao.go diff --git a/custom/route53/recordsets/register.go b/custom/route53/record-sets/register.go similarity index 100% rename from custom/route53/recordsets/register.go rename to custom/route53/record-sets/register.go diff --git a/custom/route53/recordsets/render.go b/custom/route53/record-sets/render.go similarity index 100% rename from custom/route53/recordsets/render.go rename to custom/route53/record-sets/render.go diff --git a/custom/sagemaker/trainingjobs/dao.go b/custom/sagemaker/training-jobs/dao.go similarity index 100% rename from custom/sagemaker/trainingjobs/dao.go rename to custom/sagemaker/training-jobs/dao.go diff --git a/custom/sagemaker/trainingjobs/register.go b/custom/sagemaker/training-jobs/register.go similarity index 100% rename from custom/sagemaker/trainingjobs/register.go rename to custom/sagemaker/training-jobs/register.go diff --git a/custom/sagemaker/trainingjobs/render.go b/custom/sagemaker/training-jobs/render.go similarity index 100% rename from custom/sagemaker/trainingjobs/render.go rename to custom/sagemaker/training-jobs/render.go diff --git a/custom/secretsmanager/client.go b/custom/secretsmanager/client.go new file mode 100644 index 00000000..2b7d162c --- /dev/null +++ b/custom/secretsmanager/client.go @@ -0,0 +1,17 @@ +package secretsmanager + +import ( + "context" + + "github.com/aws/aws-sdk-go-v2/service/secretsmanager" + + appaws "github.com/clawscli/claws/internal/aws" +) + +func GetClient(ctx context.Context) (*secretsmanager.Client, error) { + cfg, err := appaws.NewConfig(ctx) + if err != nil { + return nil, err + } + return secretsmanager.NewFromConfig(cfg), nil +} diff --git a/custom/secretsmanager/secrets/actions.go b/custom/secretsmanager/secrets/actions.go index 27027bf4..d49122d7 100644 --- a/custom/secretsmanager/secrets/actions.go +++ b/custom/secretsmanager/secrets/actions.go @@ -6,6 +6,7 @@ import ( "github.com/aws/aws-sdk-go-v2/service/secretsmanager" + smClient "github.com/clawscli/claws/custom/secretsmanager" "github.com/clawscli/claws/internal/action" appaws "github.com/clawscli/claws/internal/aws" "github.com/clawscli/claws/internal/dao" @@ -50,11 +51,7 @@ func executeSecretAction(ctx context.Context, act action.Action, resource dao.Re } func getSecretsManagerClient(ctx context.Context) (*secretsmanager.Client, error) { - cfg, err := appaws.NewConfig(ctx) - if err != nil { - return nil, err - } - return secretsmanager.NewFromConfig(cfg), nil + return smClient.GetClient(ctx) } func executeDeleteSecret(ctx context.Context, resource dao.Resource) action.ActionResult { diff --git a/custom/servicequotas/quotas/dao.go b/custom/service-quotas/quotas/dao.go similarity index 100% rename from custom/servicequotas/quotas/dao.go rename to custom/service-quotas/quotas/dao.go diff --git a/custom/servicequotas/quotas/register.go b/custom/service-quotas/quotas/register.go similarity index 100% rename from custom/servicequotas/quotas/register.go rename to custom/service-quotas/quotas/register.go diff --git a/custom/servicequotas/quotas/render.go b/custom/service-quotas/quotas/render.go similarity index 100% rename from custom/servicequotas/quotas/render.go rename to custom/service-quotas/quotas/render.go diff --git a/custom/servicequotas/services/dao.go b/custom/service-quotas/services/dao.go similarity index 100% rename from custom/servicequotas/services/dao.go rename to custom/service-quotas/services/dao.go diff --git a/custom/servicequotas/services/register.go b/custom/service-quotas/services/register.go similarity index 100% rename from custom/servicequotas/services/register.go rename to custom/service-quotas/services/register.go diff --git a/custom/servicequotas/services/render.go b/custom/service-quotas/services/render.go similarity index 100% rename from custom/servicequotas/services/render.go rename to custom/service-quotas/services/render.go diff --git a/custom/sns/subscriptions/actions.go b/custom/sns/subscriptions/actions.go index b1a5f068..a628a992 100644 --- a/custom/sns/subscriptions/actions.go +++ b/custom/sns/subscriptions/actions.go @@ -6,8 +6,8 @@ import ( "github.com/aws/aws-sdk-go-v2/service/sns" + snsClient "github.com/clawscli/claws/custom/sns" "github.com/clawscli/claws/internal/action" - appaws "github.com/clawscli/claws/internal/aws" "github.com/clawscli/claws/internal/dao" ) @@ -36,11 +36,7 @@ func executeSubscriptionAction(ctx context.Context, act action.Action, resource } func getSNSClient(ctx context.Context) (*sns.Client, error) { - cfg, err := appaws.NewConfig(ctx) - if err != nil { - return nil, err - } - return sns.NewFromConfig(cfg), nil + return snsClient.GetClient(ctx) } func executeUnsubscribe(ctx context.Context, resource dao.Resource) action.ActionResult { diff --git a/custom/sns/topics/actions.go b/custom/sns/topics/actions.go index 59665fee..869a8077 100644 --- a/custom/sns/topics/actions.go +++ b/custom/sns/topics/actions.go @@ -6,8 +6,8 @@ import ( "github.com/aws/aws-sdk-go-v2/service/sns" + snsClient "github.com/clawscli/claws/custom/sns" "github.com/clawscli/claws/internal/action" - appaws "github.com/clawscli/claws/internal/aws" "github.com/clawscli/claws/internal/dao" ) @@ -36,11 +36,7 @@ func executeTopicAction(ctx context.Context, act action.Action, resource dao.Res } func getSNSClient(ctx context.Context) (*sns.Client, error) { - cfg, err := appaws.NewConfig(ctx) - if err != nil { - return nil, err - } - return sns.NewFromConfig(cfg), nil + return snsClient.GetClient(ctx) } func executeDeleteTopic(ctx context.Context, resource dao.Resource) action.ActionResult { diff --git a/custom/sqs/queues/actions.go b/custom/sqs/queues/actions.go index b6da9826..f831ba43 100644 --- a/custom/sqs/queues/actions.go +++ b/custom/sqs/queues/actions.go @@ -7,6 +7,7 @@ import ( "github.com/aws/aws-sdk-go-v2/service/sqs" + sqsClient "github.com/clawscli/claws/custom/sqs" "github.com/clawscli/claws/internal/action" appaws "github.com/clawscli/claws/internal/aws" "github.com/clawscli/claws/internal/dao" @@ -57,11 +58,7 @@ func executeQueueAction(ctx context.Context, act action.Action, resource dao.Res } func getSQSClient(ctx context.Context) (*sqs.Client, error) { - cfg, err := appaws.NewConfig(ctx) - if err != nil { - return nil, err - } - return sqs.NewFromConfig(cfg), nil + return sqsClient.GetClient(ctx) } func executePurgeQueue(ctx context.Context, resource dao.Resource) action.ActionResult { diff --git a/custom/sfn/client.go b/custom/stepfunctions/client.go similarity index 94% rename from custom/sfn/client.go rename to custom/stepfunctions/client.go index e76fbc70..b2390d59 100644 --- a/custom/sfn/client.go +++ b/custom/stepfunctions/client.go @@ -1,4 +1,4 @@ -package sfn +package stepfunctions import ( "context" diff --git a/custom/sfn/executions/actions.go b/custom/stepfunctions/executions/actions.go similarity index 87% rename from custom/sfn/executions/actions.go rename to custom/stepfunctions/executions/actions.go index 4a54e2ef..0b7ebd98 100644 --- a/custom/sfn/executions/actions.go +++ b/custom/stepfunctions/executions/actions.go @@ -6,14 +6,14 @@ import ( "github.com/aws/aws-sdk-go-v2/service/sfn" - sfnClient "github.com/clawscli/claws/custom/sfn" + sfnClient "github.com/clawscli/claws/custom/stepfunctions" "github.com/clawscli/claws/internal/action" "github.com/clawscli/claws/internal/dao" ) func init() { // Register actions for Step Functions executions - action.Global.Register("sfn", "executions", []action.Action{ + action.Global.Register("stepfunctions", "executions", []action.Action{ { Name: "Stop", Shortcut: "S", @@ -24,7 +24,7 @@ func init() { }) // Register executor - action.RegisterExecutor("sfn", "executions", executeExecutionAction) + action.RegisterExecutor("stepfunctions", "executions", executeExecutionAction) } // executeExecutionAction executes an action on a Step Functions execution diff --git a/custom/sfn/executions/dao.go b/custom/stepfunctions/executions/dao.go similarity index 97% rename from custom/sfn/executions/dao.go rename to custom/stepfunctions/executions/dao.go index 9c996ccb..1bda215b 100644 --- a/custom/sfn/executions/dao.go +++ b/custom/stepfunctions/executions/dao.go @@ -23,10 +23,10 @@ type ExecutionDAO struct { func NewExecutionDAO(ctx context.Context) (dao.DAO, error) { cfg, err := appaws.NewConfig(ctx) if err != nil { - return nil, apperrors.Wrap(err, "new sfn/executions dao") + return nil, apperrors.Wrap(err, "new stepfunctions/executions dao") } return &ExecutionDAO{ - BaseDAO: dao.NewBaseDAO("sfn", "executions"), + BaseDAO: dao.NewBaseDAO("stepfunctions", "executions"), client: sfn.NewFromConfig(cfg), }, nil } diff --git a/custom/sfn/executions/register.go b/custom/stepfunctions/executions/register.go similarity index 82% rename from custom/sfn/executions/register.go rename to custom/stepfunctions/executions/register.go index 48431afd..70c23059 100644 --- a/custom/sfn/executions/register.go +++ b/custom/stepfunctions/executions/register.go @@ -9,7 +9,7 @@ import ( ) func init() { - registry.Global.RegisterCustom("sfn", "executions", registry.Entry{ + registry.Global.RegisterCustom("stepfunctions", "executions", registry.Entry{ DAOFactory: func(ctx context.Context) (dao.DAO, error) { return NewExecutionDAO(ctx) }, diff --git a/custom/sfn/executions/render.go b/custom/stepfunctions/executions/render.go similarity index 97% rename from custom/sfn/executions/render.go rename to custom/stepfunctions/executions/render.go index af22db3d..81a0f792 100644 --- a/custom/sfn/executions/render.go +++ b/custom/stepfunctions/executions/render.go @@ -19,7 +19,7 @@ type ExecutionRenderer struct { func NewExecutionRenderer() render.Renderer { return &ExecutionRenderer{ BaseRenderer: render.BaseRenderer{ - Service: "sfn", + Service: "stepfunctions", Resource: "executions", Cols: []render.Column{ { @@ -200,7 +200,7 @@ func (r *ExecutionRenderer) Navigations(resource dao.Resource) []render.Navigati // State Machine navigation navs = append(navs, render.Navigation{ - Key: "m", Label: "State Machine", Service: "sfn", Resource: "state-machines", + Key: "m", Label: "State Machine", Service: "stepfunctions", Resource: "state-machines", FilterField: "StateMachineArn", FilterValue: er.StateMachineARN(), }) diff --git a/custom/sfn/statemachines/actions.go b/custom/stepfunctions/state-machines/actions.go similarity index 76% rename from custom/sfn/statemachines/actions.go rename to custom/stepfunctions/state-machines/actions.go index 3d0ae272..a57499f3 100644 --- a/custom/sfn/statemachines/actions.go +++ b/custom/stepfunctions/state-machines/actions.go @@ -6,13 +6,13 @@ import ( "github.com/aws/aws-sdk-go-v2/service/sfn" + sfnClient "github.com/clawscli/claws/custom/stepfunctions" "github.com/clawscli/claws/internal/action" - appaws "github.com/clawscli/claws/internal/aws" "github.com/clawscli/claws/internal/dao" ) func init() { - action.Global.Register("sfn", "state-machines", []action.Action{ + action.Global.Register("stepfunctions", "state-machines", []action.Action{ { Name: "Delete", Shortcut: "D", @@ -23,7 +23,7 @@ func init() { }, }) - action.RegisterExecutor("sfn", "state-machines", executeStateMachineAction) + action.RegisterExecutor("stepfunctions", "state-machines", executeStateMachineAction) } func executeStateMachineAction(ctx context.Context, act action.Action, resource dao.Resource) action.ActionResult { @@ -35,16 +35,8 @@ func executeStateMachineAction(ctx context.Context, act action.Action, resource } } -func getSFNClient(ctx context.Context) (*sfn.Client, error) { - cfg, err := appaws.NewConfig(ctx) - if err != nil { - return nil, err - } - return sfn.NewFromConfig(cfg), nil -} - func executeDeleteStateMachine(ctx context.Context, resource dao.Resource) action.ActionResult { - client, err := getSFNClient(ctx) + client, err := sfnClient.GetClient(ctx) if err != nil { return action.ActionResult{Success: false, Error: err} } diff --git a/custom/sfn/statemachines/dao.go b/custom/stepfunctions/state-machines/dao.go similarity index 96% rename from custom/sfn/statemachines/dao.go rename to custom/stepfunctions/state-machines/dao.go index 5fd56a64..54035cec 100644 --- a/custom/sfn/statemachines/dao.go +++ b/custom/stepfunctions/state-machines/dao.go @@ -22,10 +22,10 @@ type StateMachineDAO struct { func NewStateMachineDAO(ctx context.Context) (dao.DAO, error) { cfg, err := appaws.NewConfig(ctx) if err != nil { - return nil, apperrors.Wrap(err, "new sfn/statemachines dao") + return nil, apperrors.Wrap(err, "new stepfunctions/state-machines dao") } return &StateMachineDAO{ - BaseDAO: dao.NewBaseDAO("sfn", "state-machines"), + BaseDAO: dao.NewBaseDAO("stepfunctions", "state-machines"), client: sfn.NewFromConfig(cfg), }, nil } diff --git a/custom/sfn/statemachines/register.go b/custom/stepfunctions/state-machines/register.go similarity index 82% rename from custom/sfn/statemachines/register.go rename to custom/stepfunctions/state-machines/register.go index aecf05dc..4a8a6049 100644 --- a/custom/sfn/statemachines/register.go +++ b/custom/stepfunctions/state-machines/register.go @@ -9,7 +9,7 @@ import ( ) func init() { - registry.Global.RegisterCustom("sfn", "state-machines", registry.Entry{ + registry.Global.RegisterCustom("stepfunctions", "state-machines", registry.Entry{ DAOFactory: func(ctx context.Context) (dao.DAO, error) { return NewStateMachineDAO(ctx) }, diff --git a/custom/sfn/statemachines/render.go b/custom/stepfunctions/state-machines/render.go similarity index 97% rename from custom/sfn/statemachines/render.go rename to custom/stepfunctions/state-machines/render.go index a785deb8..05893ab4 100644 --- a/custom/sfn/statemachines/render.go +++ b/custom/stepfunctions/state-machines/render.go @@ -21,7 +21,7 @@ type StateMachineRenderer struct { func NewStateMachineRenderer() render.Renderer { return &StateMachineRenderer{ BaseRenderer: render.BaseRenderer{ - Service: "sfn", + Service: "stepfunctions", Resource: "state-machines", Cols: []render.Column{ { @@ -187,7 +187,7 @@ func (r *StateMachineRenderer) Navigations(resource dao.Resource) []render.Navig // Executions navigation navs = append(navs, render.Navigation{ - Key: "e", Label: "Executions", Service: "sfn", Resource: "executions", + Key: "e", Label: "Executions", Service: "stepfunctions", Resource: "executions", FilterField: "StateMachineName", FilterValue: sr.GetName(), }) diff --git a/custom/vpc/vpcendpoints/dao.go b/custom/vpc/endpoints/dao.go similarity index 97% rename from custom/vpc/vpcendpoints/dao.go rename to custom/vpc/endpoints/dao.go index d469713a..7cb54f14 100644 --- a/custom/vpc/vpcendpoints/dao.go +++ b/custom/vpc/endpoints/dao.go @@ -23,10 +23,10 @@ type VpcEndpointDAO struct { func NewVpcEndpointDAO(ctx context.Context) (dao.DAO, error) { cfg, err := appaws.NewConfig(ctx) if err != nil { - return nil, apperrors.Wrap(err, "new vpc/vpcendpoints dao") + return nil, apperrors.Wrap(err, "new vpc/endpoints dao") } return &VpcEndpointDAO{ - BaseDAO: dao.NewBaseDAO("ec2", "vpc-endpoints"), + BaseDAO: dao.NewBaseDAO("vpc", "endpoints"), client: ec2.NewFromConfig(cfg), }, nil } diff --git a/custom/vpc/vpcendpoints/register.go b/custom/vpc/endpoints/register.go similarity index 100% rename from custom/vpc/vpcendpoints/register.go rename to custom/vpc/endpoints/register.go diff --git a/custom/vpc/vpcendpoints/render.go b/custom/vpc/endpoints/render.go similarity index 100% rename from custom/vpc/vpcendpoints/render.go rename to custom/vpc/endpoints/render.go diff --git a/custom/vpc/internetgateways/actions.go b/custom/vpc/internet-gateways/actions.go similarity index 100% rename from custom/vpc/internetgateways/actions.go rename to custom/vpc/internet-gateways/actions.go diff --git a/custom/vpc/internetgateways/dao.go b/custom/vpc/internet-gateways/dao.go similarity index 100% rename from custom/vpc/internetgateways/dao.go rename to custom/vpc/internet-gateways/dao.go diff --git a/custom/vpc/internetgateways/register.go b/custom/vpc/internet-gateways/register.go similarity index 100% rename from custom/vpc/internetgateways/register.go rename to custom/vpc/internet-gateways/register.go diff --git a/custom/vpc/internetgateways/render.go b/custom/vpc/internet-gateways/render.go similarity index 100% rename from custom/vpc/internetgateways/render.go rename to custom/vpc/internet-gateways/render.go diff --git a/custom/vpc/natgateways/actions.go b/custom/vpc/nat-gateways/actions.go similarity index 100% rename from custom/vpc/natgateways/actions.go rename to custom/vpc/nat-gateways/actions.go diff --git a/custom/vpc/natgateways/dao.go b/custom/vpc/nat-gateways/dao.go similarity index 100% rename from custom/vpc/natgateways/dao.go rename to custom/vpc/nat-gateways/dao.go diff --git a/custom/vpc/natgateways/register.go b/custom/vpc/nat-gateways/register.go similarity index 100% rename from custom/vpc/natgateways/register.go rename to custom/vpc/nat-gateways/register.go diff --git a/custom/vpc/natgateways/render.go b/custom/vpc/nat-gateways/render.go similarity index 100% rename from custom/vpc/natgateways/render.go rename to custom/vpc/nat-gateways/render.go diff --git a/custom/vpc/routetables/actions.go b/custom/vpc/route-tables/actions.go similarity index 100% rename from custom/vpc/routetables/actions.go rename to custom/vpc/route-tables/actions.go diff --git a/custom/vpc/routetables/dao.go b/custom/vpc/route-tables/dao.go similarity index 100% rename from custom/vpc/routetables/dao.go rename to custom/vpc/route-tables/dao.go diff --git a/custom/vpc/routetables/register.go b/custom/vpc/route-tables/register.go similarity index 100% rename from custom/vpc/routetables/register.go rename to custom/vpc/route-tables/register.go diff --git a/custom/vpc/routetables/render.go b/custom/vpc/route-tables/render.go similarity index 100% rename from custom/vpc/routetables/render.go rename to custom/vpc/route-tables/render.go diff --git a/custom/vpc/tgwattachments/dao.go b/custom/vpc/tgw-attachments/dao.go similarity index 97% rename from custom/vpc/tgwattachments/dao.go rename to custom/vpc/tgw-attachments/dao.go index 83a73adf..1c34086c 100644 --- a/custom/vpc/tgwattachments/dao.go +++ b/custom/vpc/tgw-attachments/dao.go @@ -23,10 +23,10 @@ type TGWAttachmentDAO struct { func NewTGWAttachmentDAO(ctx context.Context) (dao.DAO, error) { cfg, err := appaws.NewConfig(ctx) if err != nil { - return nil, apperrors.Wrap(err, "new vpc/tgwattachments dao") + return nil, apperrors.Wrap(err, "new vpc/tgw-attachments dao") } return &TGWAttachmentDAO{ - BaseDAO: dao.NewBaseDAO("ec2", "tgw-attachments"), + BaseDAO: dao.NewBaseDAO("vpc", "tgw-attachments"), client: ec2.NewFromConfig(cfg), }, nil } diff --git a/custom/vpc/tgwattachments/register.go b/custom/vpc/tgw-attachments/register.go similarity index 100% rename from custom/vpc/tgwattachments/register.go rename to custom/vpc/tgw-attachments/register.go diff --git a/custom/vpc/tgwattachments/render.go b/custom/vpc/tgw-attachments/render.go similarity index 100% rename from custom/vpc/tgwattachments/render.go rename to custom/vpc/tgw-attachments/render.go diff --git a/custom/vpc/transitgateways/dao.go b/custom/vpc/transit-gateways/dao.go similarity index 100% rename from custom/vpc/transitgateways/dao.go rename to custom/vpc/transit-gateways/dao.go diff --git a/custom/vpc/transitgateways/register.go b/custom/vpc/transit-gateways/register.go similarity index 100% rename from custom/vpc/transitgateways/register.go rename to custom/vpc/transit-gateways/register.go diff --git a/custom/vpc/transitgateways/render.go b/custom/vpc/transit-gateways/render.go similarity index 100% rename from custom/vpc/transitgateways/render.go rename to custom/vpc/transit-gateways/render.go diff --git a/custom/wafv2/webacls/dao.go b/custom/wafv2/web-acls/dao.go similarity index 100% rename from custom/wafv2/webacls/dao.go rename to custom/wafv2/web-acls/dao.go diff --git a/custom/wafv2/webacls/register.go b/custom/wafv2/web-acls/register.go similarity index 100% rename from custom/wafv2/webacls/register.go rename to custom/wafv2/web-acls/register.go diff --git a/custom/wafv2/webacls/render.go b/custom/wafv2/web-acls/render.go similarity index 100% rename from custom/wafv2/webacls/render.go rename to custom/wafv2/web-acls/render.go diff --git a/internal/aws/arn.go b/internal/aws/arn.go index 49ff8b9e..619e673a 100644 --- a/internal/aws/arn.go +++ b/internal/aws/arn.go @@ -156,11 +156,11 @@ func isVPCResource(resourceType string) bool { var arnToRegistryService = map[string]string{ "logs": "cloudwatch", - "states": "sfn", + "states": "stepfunctions", "elasticloadbalancing": "elbv2", "execute-api": "apigateway", - "cognito-idp": "cognito", - "events": "eventbridge", + "config": "configservice", + "access-analyzer": "accessanalyzer", } // normalizeResourceType maps ARN resource types to claws registry resource types. diff --git a/internal/aws/arn_test.go b/internal/aws/arn_test.go index 1ad8b179..9485df5a 100644 --- a/internal/aws/arn_test.go +++ b/internal/aws/arn_test.go @@ -274,7 +274,7 @@ func TestARN_ServiceResourceType(t *testing.T) { { name: "Step Functions state machine", arn: "arn:aws:states:us-east-1:123456789012:stateMachine:my-sm", - wantService: "sfn", + wantService: "stepfunctions", wantResourceType: "state-machines", }, { @@ -298,7 +298,7 @@ func TestARN_ServiceResourceType(t *testing.T) { { name: "EventBridge event bus", arn: "arn:aws:events:us-east-1:123456789012:event-bus/my-bus", - wantService: "eventbridge", + wantService: "events", wantResourceType: "buses", }, { diff --git a/internal/genimports/genimports.go b/internal/genimports/genimports.go index 7f65e1ad..216ce326 100644 --- a/internal/genimports/genimports.go +++ b/internal/genimports/genimports.go @@ -53,74 +53,74 @@ func GetProjectRoot() (string, error) { } var ServiceDisplayNames = map[string]string{ - "accessanalyzer": "Access Analyzer", - "acm": "ACM", - "apigateway": "API Gateway", - "apprunner": "App Runner", - "appsync": "AppSync", - "athena": "Athena", - "autoscaling": "Auto Scaling", - "backup": "AWS Backup", - "batch": "Batch", - "bedrock": "Bedrock", - "bedrockagent": "Bedrock Agent", - "bedrockagentcore": "Bedrock AgentCore", - "budgets": "Budgets", - "cfn": "CloudFormation", - "cloudfront": "CloudFront", - "cloudtrail": "CloudTrail", - "cloudwatch": "CloudWatch", - "codebuild": "CodeBuild", - "codepipeline": "CodePipeline", - "cognito": "Cognito", - "computeoptimizer": "Compute Optimizer", - "config": "Config", - "costexplorer": "Cost Explorer", - "datasync": "DataSync", - "detective": "Detective", - "directconnect": "Direct Connect", - "dynamodb": "DynamoDB", - "ec2": "EC2", - "ecr": "ECR", - "ecs": "ECS", - "elasticache": "ElastiCache", - "elbv2": "ELBv2 (ALB/NLB/GLB)", - "emr": "EMR", - "eventbridge": "EventBridge", - "fms": "Firewall Manager", - "glue": "Glue", - "guardduty": "GuardDuty", - "health": "Health", - "iam": "IAM", - "inspector2": "Inspector", - "kinesis": "Kinesis", - "kms": "KMS", - "lambda": "Lambda", - "licensemanager": "License Manager", - "macie": "Macie", - "networkfirewall": "Network Firewall", - "opensearch": "OpenSearch", - "organizations": "Organizations", - "rds": "RDS", - "redshift": "Redshift", - "risp": "RI/SP (Reserved Instances, Savings Plans)", - "route53": "Route53", - "s3": "S3", - "s3vectors": "S3 Vectors", - "sagemaker": "SageMaker", - "secretsmanager": "Secrets Manager", - "securityhub": "Security Hub", - "servicequotas": "Service Quotas", - "sfn": "Step Functions", - "sns": "SNS", - "sqs": "SQS", - "ssm": "SSM", - "transcribe": "Transcribe", - "transfer": "Transfer Family", - "trustedadvisor": "Trusted Advisor", - "vpc": "VPC", - "wafv2": "WAF", - "xray": "X-Ray", + "accessanalyzer": "Access Analyzer", + "acm": "ACM", + "apigateway": "API Gateway", + "apprunner": "App Runner", + "appsync": "AppSync", + "athena": "Athena", + "autoscaling": "Auto Scaling", + "backup": "AWS Backup", + "batch": "Batch", + "bedrock": "Bedrock", + "bedrock-agent": "Bedrock Agent", + "bedrock-agentcore": "Bedrock AgentCore", + "budgets": "Budgets", + "ce": "Cost Explorer", + "cfn": "CloudFormation", + "cloudfront": "CloudFront", + "cloudtrail": "CloudTrail", + "cloudwatch": "CloudWatch", + "codebuild": "CodeBuild", + "codepipeline": "CodePipeline", + "cognito-idp": "Cognito", + "compute-optimizer": "Compute Optimizer", + "configservice": "Config", + "datasync": "DataSync", + "detective": "Detective", + "directconnect": "Direct Connect", + "dynamodb": "DynamoDB", + "ec2": "EC2", + "ecr": "ECR", + "ecs": "ECS", + "elasticache": "ElastiCache", + "elbv2": "ELBv2 (ALB/NLB/GLB)", + "emr": "EMR", + "events": "EventBridge", + "fms": "Firewall Manager", + "glue": "Glue", + "guardduty": "GuardDuty", + "health": "Health", + "iam": "IAM", + "inspector2": "Inspector", + "kinesis": "Kinesis", + "kms": "KMS", + "lambda": "Lambda", + "license-manager": "License Manager", + "macie2": "Macie", + "network-firewall": "Network Firewall", + "opensearch": "OpenSearch", + "organizations": "Organizations", + "rds": "RDS", + "redshift": "Redshift", + "risp": "RI/SP (Reserved Instances, Savings Plans)", + "route53": "Route53", + "s3": "S3", + "s3vectors": "S3 Vectors", + "sagemaker": "SageMaker", + "secretsmanager": "Secrets Manager", + "securityhub": "Security Hub", + "service-quotas": "Service Quotas", + "sns": "SNS", + "sqs": "SQS", + "ssm": "SSM", + "stepfunctions": "Step Functions", + "transcribe": "Transcribe", + "transfer": "Transfer Family", + "trustedadvisor": "Trusted Advisor", + "vpc": "VPC", + "wafv2": "WAF", + "xray": "X-Ray", } func GetServiceDisplayName(service string) string { diff --git a/internal/registry/registry.go b/internal/registry/registry.go index 81e06f18..50c18bf7 100644 --- a/internal/registry/registry.go +++ b/internal/registry/registry.go @@ -70,57 +70,60 @@ func New() *Registry { // defaultAliases returns the default service aliases func defaultAliases() map[string]string { return map[string]string{ - "cfn": "cloudformation", - "cf": "cloudformation", - "sg": "ec2/security-groups", - "asg": "autoscaling", - "cw": "cloudwatch", - "logs": "cloudwatch/log-groups", - "r53": "route53", - "ssm": "ssm", - "sm": "secretsmanager", - "ddb": "dynamodb", - "sqs": "sqs", - "sns": "sns", - "agentcore": "bedrock-agentcore", - "kb": "bedrock-agent/knowledge-bases", - "agent": "bedrock-agent/agents", - "models": "bedrock/foundation-models", - "guardrail": "bedrock/guardrails", - "eb": "eventbridge", - "events": "eventbridge", - "stepfunctions": "sfn", - "sfn": "sfn", - "sq": "service-quotas", - "quotas": "service-quotas", - "apigw": "apigateway", - "api": "apigateway", - "elb": "elbv2", - "alb": "elbv2", - "nlb": "elbv2", - "redis": "elasticache", - "cache": "elasticache", - "es": "opensearch", - "elasticsearch": "opensearch", - "cdn": "cloudfront", - "dist": "cloudfront", - "gd": "guardduty", - "build": "codebuild", - "cb": "codebuild", - "pipeline": "codepipeline", - "cp": "codepipeline", - "waf": "wafv2", - "ce": "costexplorer", - "cost-explorer": "costexplorer", - "ta": "trustedadvisor", - "co": "computeoptimizer", - "sftp": "transfer", - "aa": "accessanalyzer", - "analyzer": "accessanalyzer", - "ri": "risp/reserved-instances", - "sp": "risp/savings-plans", - "odcr": "ec2/capacity-reservations", - "tgw": "vpc/transit-gateways", + "cfn": "cloudformation", + "cf": "cloudformation", + "sg": "ec2/security-groups", + "asg": "autoscaling", + "cw": "cloudwatch", + "logs": "cloudwatch/log-groups", + "r53": "route53", + "ssm": "ssm", + "sm": "secretsmanager", + "ddb": "dynamodb", + "sqs": "sqs", + "sns": "sns", + "agentcore": "bedrock-agentcore", + "kb": "bedrock-agent/knowledge-bases", + "agent": "bedrock-agent/agents", + "models": "bedrock/foundation-models", + "guardrail": "bedrock/guardrails", + "eb": "events", + "eventbridge": "events", + "sfn": "stepfunctions", + "sq": "service-quotas", + "quotas": "service-quotas", + "apigw": "apigateway", + "api": "apigateway", + "elb": "elbv2", + "alb": "elbv2", + "nlb": "elbv2", + "redis": "elasticache", + "cache": "elasticache", + "es": "opensearch", + "elasticsearch": "opensearch", + "cdn": "cloudfront", + "dist": "cloudfront", + "gd": "guardduty", + "build": "codebuild", + "cb": "codebuild", + "pipeline": "codepipeline", + "cp": "codepipeline", + "waf": "wafv2", + "costexplorer": "ce", + "cost-explorer": "ce", + "ta": "trustedadvisor", + "computeoptimizer": "compute-optimizer", + "co": "compute-optimizer", + "sftp": "transfer", + "aa": "accessanalyzer", + "analyzer": "accessanalyzer", + "ri": "risp/reserved-instances", + "sp": "risp/savings-plans", + "odcr": "ec2/capacity-reservations", + "tgw": "vpc/transit-gateways", + "cognito": "cognito-idp", + "config": "configservice", + "macie": "macie2", } } @@ -146,9 +149,9 @@ func defaultDisplayNames() map[string]string { "cloudwatch": "CloudWatch", "codebuild": "CodeBuild", "codepipeline": "CodePipeline", - "cognito": "Cognito", - "config": "Config", - "costexplorer": "Cost Explorer", + "cognito-idp": "Cognito", + "configservice": "Config", + "ce": "Cost Explorer", "datasync": "DataSync", "detective": "Detective", "directconnect": "Direct Connect", @@ -164,13 +167,13 @@ func defaultDisplayNames() map[string]string { "ecs": "ECS", "elbv2": "Elastic Load Balancing", "emr": "EMR", - "eventbridge": "EventBridge", + "events": "EventBridge", "iam": "IAM", "kinesis": "Kinesis", "kms": "KMS", "lambda": "Lambda", "license-manager": "License Manager", - "macie": "Macie", + "macie2": "Macie", "network-firewall": "Network Firewall", "opensearch": "OpenSearch", "organizations": "Organizations", @@ -184,7 +187,7 @@ func defaultDisplayNames() map[string]string { "secretsmanager": "Secrets Manager", "securityhub": "Security Hub", "service-quotas": "Service Quotas", - "sfn": "Step Functions", + "stepfunctions": "Step Functions", "sns": "SNS", "sqs": "SQS", "ssm": "Systems Manager", @@ -194,7 +197,7 @@ func defaultDisplayNames() map[string]string { "wafv2": "WAF", "xray": "X-Ray", "trustedadvisor": "Trusted Advisor", - "computeoptimizer": "Compute Optimizer", + "compute-optimizer": "Compute Optimizer", } } @@ -223,11 +226,11 @@ func defaultCategories() []ServiceCategory { }, { Name: "Security & Identity", - Services: []string{"iam", "kms", "acm", "secretsmanager", "ssm", "cognito", "guardduty", "wafv2", "inspector2", "securityhub", "fms", "accessanalyzer", "detective", "macie"}, + Services: []string{"iam", "kms", "acm", "secretsmanager", "ssm", "cognito-idp", "guardduty", "wafv2", "inspector2", "securityhub", "fms", "accessanalyzer", "detective", "macie2"}, }, { Name: "Integration", - Services: []string{"sqs", "sns", "eventbridge", "sfn", "kinesis", "transfer", "datasync"}, + Services: []string{"sqs", "sns", "events", "stepfunctions", "kinesis", "transfer", "datasync"}, }, { Name: "DevOps", @@ -239,11 +242,11 @@ func defaultCategories() []ServiceCategory { }, { Name: "Governance", - Services: []string{"config", "organizations", "service-quotas", "license-manager", "backup", "trustedadvisor", "computeoptimizer"}, + Services: []string{"configservice", "organizations", "service-quotas", "license-manager", "backup", "trustedadvisor", "compute-optimizer"}, }, { Name: "Cost Management", - Services: []string{"risp", "costexplorer", "budgets"}, + Services: []string{"risp", "ce", "budgets"}, }, } } @@ -460,9 +463,9 @@ var subResourceSet = map[string]struct{}{ "elbv2/targets": {}, "s3vectors/indexes": {}, "guardduty/findings": {}, - "cognito/users": {}, + "cognito-idp/users": {}, "codepipeline/executions": {}, - "sfn/executions": {}, + "stepfunctions/executions": {}, "codebuild/builds": {}, "backup/recovery-points": {}, "backup/selections": {}, diff --git a/internal/view/dashboard_view.go b/internal/view/dashboard_view.go index 87d6d726..8f6a999e 100644 --- a/internal/view/dashboard_view.go +++ b/internal/view/dashboard_view.go @@ -423,7 +423,7 @@ func (d *DashboardView) activateCurrentRow() (tea.Model, tea.Cmd) { case panelCost: if d.focusedRow < len(d.costTop) { item := d.costTop[d.focusedRow] - return d.navigateToFiltered("costexplorer", "costs", "ServiceName", item.service) + return d.navigateToFiltered("ce", "costs", "ServiceName", item.service) } case panelOperations: diff --git a/internal/view/dashboard_view_fetch.go b/internal/view/dashboard_view_fetch.go index 1e1faedd..b7da7fd9 100644 --- a/internal/view/dashboard_view_fetch.go +++ b/internal/view/dashboard_view_fetch.go @@ -6,9 +6,9 @@ import ( tea "charm.land/bubbletea/v2" + "github.com/clawscli/claws/custom/ce/anomalies" + "github.com/clawscli/claws/custom/ce/costs" "github.com/clawscli/claws/custom/cloudwatch/alarms" - "github.com/clawscli/claws/custom/costexplorer/anomalies" - "github.com/clawscli/claws/custom/costexplorer/costs" "github.com/clawscli/claws/custom/health/events" "github.com/clawscli/claws/custom/securityhub/findings" "github.com/clawscli/claws/custom/trustedadvisor/recommendations" diff --git a/internal/view/dashboard_view_panels.go b/internal/view/dashboard_view_panels.go index 00754e1d..1d1842bb 100644 --- a/internal/view/dashboard_view_panels.go +++ b/internal/view/dashboard_view_panels.go @@ -24,7 +24,7 @@ const ( dashboardMaxRecords = 100 - targetCost = "costexplorer/costs" + targetCost = "ce/costs" targetOperations = "health/events" targetSecurity = "securityhub/findings" targetOptimization = "trustedadvisor/recommendations" diff --git a/internal/view/dashboard_view_test.go b/internal/view/dashboard_view_test.go index 8d409aa5..78ae877a 100644 --- a/internal/view/dashboard_view_test.go +++ b/internal/view/dashboard_view_test.go @@ -203,7 +203,7 @@ func TestDashboardView_NavigateTo(t *testing.T) { target string wantCmd bool }{ - {"valid target", "costexplorer/costs", true}, + {"valid target", "ce/costs", true}, {"valid security target", "securityhub/findings", true}, {"invalid target no slash", "invalid", false}, {"empty target", "", false}, From 329c28589fc475c07fe327a8c31285dde1a33b40 Mon Sep 17 00:00:00 2001 From: yimsk Date: Fri, 2 Jan 2026 19:18:13 +0900 Subject: [PATCH 2/5] feat: propagate ALL_PROXY to HTTP_PROXY/HTTPS_PROXY (#74) * feat: support ALL_PROXY environment variable (#69) * feat: propagate ALL_PROXY to HTTP_PROXY and configure NO_PROXY Extend proxy support based on issue #69 feedback: - Propagate ALL_PROXY to both HTTP_PROXY and HTTPS_PROXY - Auto-configure NO_PROXY for AWS credential endpoints: - 169.254.169.254 (EC2 IMDS) - 169.254.170.2 (ECS Task Role) - 169.254.170.23 (EKS Pod Identity) - Preserve existing NO_PROXY entries, only add missing endpoints Refs #69, #67 * fix: default NO_PROXY to IMDS only and remove proxy value from logs - NO_PROXY now only includes EC2 IMDS (169.254.169.254) by default - ECS/EKS endpoints can be added via config (TODO #67) - Remove proxy URL from log output to avoid credential exposure * refactor: simplify splitNoProxy and use tagged switch in parseFlags * refactor: separate NO_PROXY config from ALL_PROXY propagation configureNoProxy() now runs independently when any proxy is set, not tied to ALL_PROXY propagation. Cleaner separation of concerns. * refactor: remove lowercase env var support for simplicity * refactor: remove NO_PROXY auto-configuration * refactor: skip proxy propagation for --help/--version and cleanup test * test: add test case for lowercase all_proxy not supported --- cmd/claws/main.go | 52 ++++++++++++++++++----- cmd/claws/proxy_test.go | 94 +++++++++++++++++++++++++++++++++++++++++ 2 files changed, 136 insertions(+), 10 deletions(-) create mode 100644 cmd/claws/proxy_test.go diff --git a/cmd/claws/main.go b/cmd/claws/main.go index 4724a22c..4b1a468b 100644 --- a/cmd/claws/main.go +++ b/cmd/claws/main.go @@ -19,9 +19,10 @@ import ( var version = "dev" func main() { - // Parse command line flags opts := parseFlags() + propagateAllProxy() + // Apply CLI options to global config cfg := config.Global() @@ -102,30 +103,29 @@ func parseFlags() cliOptions { args := os.Args[1:] for i := 0; i < len(args); i++ { - arg := args[i] - switch { - case arg == "-p" || arg == "--profile": + switch args[i] { + case "-p", "--profile": if i+1 < len(args) { i++ opts.profile = args[i] } - case arg == "-r" || arg == "--region": + case "-r", "--region": if i+1 < len(args) { i++ opts.region = args[i] } - case arg == "-ro" || arg == "--read-only": + case "-ro", "--read-only": opts.readOnly = true - case arg == "-e" || arg == "--env": + case "-e", "--env": opts.envCreds = true - case arg == "-l" || arg == "--log-file": + case "-l", "--log-file": if i+1 < len(args) { i++ opts.logFile = args[i] } - case arg == "-h" || arg == "--help": + case "-h", "--help": showHelp = true - case arg == "-v" || arg == "--version": + case "-v", "--version": showVersion = true } } @@ -167,4 +167,36 @@ func printUsage() { fmt.Println() fmt.Println("Environment Variables:") fmt.Println(" CLAWS_READ_ONLY=1|true Enable read-only mode") + fmt.Println(" ALL_PROXY Propagated to HTTP_PROXY/HTTPS_PROXY if not set") +} + +// propagateAllProxy copies ALL_PROXY to HTTP_PROXY/HTTPS_PROXY if not set. +// Go's net/http ignores ALL_PROXY, so we propagate it to the standard vars. +func propagateAllProxy() { + allProxy := os.Getenv("ALL_PROXY") + if allProxy == "" { + return + } + + var propagated []string + + if os.Getenv("HTTPS_PROXY") == "" { + if err := os.Setenv("HTTPS_PROXY", allProxy); err != nil { + log.Warn("failed to set HTTPS_PROXY", "error", err) + } else { + propagated = append(propagated, "HTTPS_PROXY") + } + } + + if os.Getenv("HTTP_PROXY") == "" { + if err := os.Setenv("HTTP_PROXY", allProxy); err != nil { + log.Warn("failed to set HTTP_PROXY", "error", err) + } else { + propagated = append(propagated, "HTTP_PROXY") + } + } + + if len(propagated) > 0 { + log.Debug("propagated ALL_PROXY", "to", propagated) + } } diff --git a/cmd/claws/proxy_test.go b/cmd/claws/proxy_test.go new file mode 100644 index 00000000..06cfb8e4 --- /dev/null +++ b/cmd/claws/proxy_test.go @@ -0,0 +1,94 @@ +package main + +import ( + "os" + "testing" +) + +func TestPropagateAllProxy(t *testing.T) { + // Note: Only uppercase ALL_PROXY is supported. + // Lowercase all_proxy is intentionally not supported to match + // the AWS SDK behavior and keep the implementation simple. + // Go's net/http.ProxyFromEnvironment also only checks uppercase + // for HTTP_PROXY and HTTPS_PROXY on non-CGI environments. + + proxyVars := []string{ + "ALL_PROXY", + "HTTP_PROXY", + "HTTPS_PROXY", + } + + tests := []struct { + name string + envVars map[string]string + wantHTTPS string + wantHTTP string + }{ + { + name: "ALL_PROXY propagates to both HTTP and HTTPS", + envVars: map[string]string{"ALL_PROXY": "socks5h://proxy:1080"}, + wantHTTPS: "socks5h://proxy:1080", + wantHTTP: "socks5h://proxy:1080", + }, + { + name: "HTTPS_PROXY already set - only HTTP propagated", + envVars: map[string]string{"ALL_PROXY": "http://all", "HTTPS_PROXY": "http://existing"}, + wantHTTPS: "http://existing", + wantHTTP: "http://all", + }, + { + name: "HTTP_PROXY already set - only HTTPS propagated", + envVars: map[string]string{"ALL_PROXY": "http://all", "HTTP_PROXY": "http://existing"}, + wantHTTPS: "http://all", + wantHTTP: "http://existing", + }, + { + name: "both already set - no propagation", + envVars: map[string]string{"ALL_PROXY": "http://all", "HTTP_PROXY": "http://h", "HTTPS_PROXY": "http://s"}, + wantHTTPS: "http://s", + wantHTTP: "http://h", + }, + { + name: "no ALL_PROXY - no action", + envVars: map[string]string{}, + wantHTTPS: "", + wantHTTP: "", + }, + { + name: "lowercase all_proxy not supported", + envVars: map[string]string{"all_proxy": "http://proxy:8080"}, + wantHTTPS: "", + wantHTTP: "", + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + clearEnvVars(t, proxyVars) + setEnvVars(t, tt.envVars) + + propagateAllProxy() + + if got := os.Getenv("HTTPS_PROXY"); got != tt.wantHTTPS { + t.Errorf("HTTPS_PROXY = %q, want %q", got, tt.wantHTTPS) + } + if got := os.Getenv("HTTP_PROXY"); got != tt.wantHTTP { + t.Errorf("HTTP_PROXY = %q, want %q", got, tt.wantHTTP) + } + }) + } +} + +func clearEnvVars(t *testing.T, keys []string) { + t.Helper() + for _, key := range keys { + os.Unsetenv(key) + } +} + +func setEnvVars(t *testing.T, vars map[string]string) { + t.Helper() + for key, value := range vars { + t.Setenv(key, value) + } +} From 017ebd47c64b52ebf744d023600d4b87f2374681 Mon Sep 17 00:00:00 2001 From: yimsk Date: Fri, 2 Jan 2026 19:52:59 +0900 Subject: [PATCH 3/5] feat: add alias completion in command mode (#76) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * feat: add alias completion in command mode (#70) Include aliases in GetSuggestions() so :cost+Tab suggests cost-explorer. Self-referential aliases (sfn→sfn) excluded from suggestions. * refactor: use prefix+fuzzy for diff completion * refactor: use CutPrefix and cache GetAliases * refactor: extract match functions to shared module - Move fuzzyMatch and matchNamesWithFallback to match.go - Make :tag/:tags clear behavior explicit - Sort suggestions alphabetically (services + aliases) * refactor: cache GetAliasesForService and sort match results * refactor: unify command parser pattern for sort and login * test: add cache concurrency tests and improve comments * refactor: use CutPrefix in ecs tasks render * refactor: use CutSuffix in parseNumericValue * fix: quote profile name in error message for clarity * fix: lowercase pattern in fuzzyMatch for case insensitivity * chore: remove .claude symlink * chore: add .claude to gitignore * fix: lowercase pattern in matchNamesWithFallback prefix check * fix: return defensive copy from alias cache methods --- .gitignore | 1 + custom/ecs/tasks/render.go | 3 +- internal/registry/registry.go | 52 +++++-- internal/registry/registry_test.go | 69 +++++++++ internal/view/command_input.go | 174 ++++++++++++----------- internal/view/command_input_test.go | 88 ++++++++++-- internal/view/match.go | 51 +++++++ internal/view/match_test.go | 113 +++++++++++++++ internal/view/resource_browser_filter.go | 12 -- internal/view/resource_browser_sort.go | 4 +- internal/view/service_browser_test.go | 29 ---- 11 files changed, 448 insertions(+), 148 deletions(-) create mode 100644 internal/view/match.go create mode 100644 internal/view/match_test.go diff --git a/.gitignore b/.gitignore index f40fea8d..83f638d1 100644 --- a/.gitignore +++ b/.gitignore @@ -51,3 +51,4 @@ claws.log # Node.js node_modules/ +.claude diff --git a/custom/ecs/tasks/render.go b/custom/ecs/tasks/render.go index 1c7fa2b1..1bc11a4a 100644 --- a/custom/ecs/tasks/render.go +++ b/custom/ecs/tasks/render.go @@ -318,8 +318,7 @@ func (r *TaskRenderer) Navigations(resource dao.Resource) []render.Navigation { } // If task is part of a service, add service navigation - if group := task.Group(); strings.HasPrefix(group, "service:") { - serviceName := strings.TrimPrefix(group, "service:") + if serviceName, ok := strings.CutPrefix(task.Group(), "service:"); ok { navs = append(navs, render.Navigation{ Key: "s", Label: "Service", diff --git a/internal/registry/registry.go b/internal/registry/registry.go index 50c18bf7..289fe3ad 100644 --- a/internal/registry/registry.go +++ b/internal/registry/registry.go @@ -5,6 +5,7 @@ import ( "fmt" "maps" "slices" + "strings" "sync" "github.com/clawscli/claws/internal/dao" @@ -53,6 +54,12 @@ type Registry struct { aliases map[string]string // alias -> service name or service/resource displayNames map[string]string // service -> display name for UI categories []ServiceCategory // ordered list of service categories + + // Cached computed values (aliases are immutable after init, safe to cache) + aliasListOnce sync.Once // guards aliasListCache initialization + aliasListCache []string // cached result of GetAliases() + serviceAliasesOnce sync.Once // guards serviceAliasesCache initialization + serviceAliasesCache map[string][]string // cached result of GetAliasesForService() by service } // New creates a new Registry @@ -281,20 +288,43 @@ func (r *Registry) ResolveAlias(input string) (string, string, bool) { return input, "", false } -// GetAliasesForService returns all aliases for a given service +// GetAliasesForService returns all aliases for a given service. func (r *Registry) GetAliasesForService(service string) []string { - r.mu.RLock() - defer r.mu.RUnlock() + r.serviceAliasesOnce.Do(func() { + r.mu.RLock() + defer r.mu.RUnlock() + + r.serviceAliasesCache = make(map[string][]string) + for alias, target := range r.aliases { + svc := target + if idx := strings.Index(target, "/"); idx != -1 { + svc = target[:idx] + } + r.serviceAliasesCache[svc] = append(r.serviceAliasesCache[svc], alias) + } + for svc := range r.serviceAliasesCache { + slices.Sort(r.serviceAliasesCache[svc]) + } + }) + return slices.Clone(r.serviceAliasesCache[service]) +} + +// GetAliases returns all aliases (excluding self-referential ones like "sfn" -> "sfn"). +func (r *Registry) GetAliases() []string { + r.aliasListOnce.Do(func() { + r.mu.RLock() + defer r.mu.RUnlock() - var aliases []string - for alias, target := range r.aliases { - // Check if target matches service (handle "service" or "service/resource") - if target == service || (len(target) > len(service) && target[:len(service)] == service && target[len(service)] == '/') { - aliases = append(aliases, alias) + var aliases []string + for alias, target := range r.aliases { + if alias != target { + aliases = append(aliases, alias) + } } - } - slices.Sort(aliases) - return aliases + slices.Sort(aliases) + r.aliasListCache = aliases + }) + return slices.Clone(r.aliasListCache) } // RegisterCustom registers a custom (hand-written) implementation diff --git a/internal/registry/registry_test.go b/internal/registry/registry_test.go index c5c135da..a28aca31 100644 --- a/internal/registry/registry_test.go +++ b/internal/registry/registry_test.go @@ -2,6 +2,7 @@ package registry import ( "context" + "sync" "testing" "github.com/clawscli/claws/internal/dao" @@ -259,6 +260,74 @@ func TestRegistry_GetAliasesForService_WithResourceAlias(t *testing.T) { } } +func TestRegistry_GetAliases(t *testing.T) { + reg := New() + aliases := reg.GetAliases() + + if len(aliases) == 0 { + t.Fatal("GetAliases() should return aliases") + } + + aliasMap := make(map[string]bool) + for _, a := range aliases { + aliasMap[a] = true + } + + for _, expected := range []string{"cfn", "cf", "sg", "cost-explorer"} { + if !aliasMap[expected] { + t.Errorf("GetAliases() should include %q", expected) + } + } +} + +func TestRegistry_GetAliases_ExcludesSelfReferential(t *testing.T) { + reg := New() + aliases := reg.GetAliases() + + for _, alias := range aliases { + resolved, _, found := reg.ResolveAlias(alias) + if found && alias == resolved { + t.Errorf("GetAliases() should exclude self-referential alias %q", alias) + } + } +} + +func TestRegistry_GetAliases_ConcurrentAccess(t *testing.T) { + reg := New() + var wg sync.WaitGroup + const goroutines = 100 + + wg.Add(goroutines) + for range goroutines { + go func() { + defer wg.Done() + aliases := reg.GetAliases() + if len(aliases) == 0 { + t.Error("GetAliases() should return aliases") + } + }() + } + wg.Wait() +} + +func TestRegistry_GetAliasesForService_ConcurrentAccess(t *testing.T) { + reg := New() + var wg sync.WaitGroup + const goroutines = 100 + + wg.Add(goroutines) + for range goroutines { + go func() { + defer wg.Done() + aliases := reg.GetAliasesForService("cloudformation") + if len(aliases) != 2 { + t.Errorf("GetAliasesForService() returned %d aliases, want 2", len(aliases)) + } + }() + } + wg.Wait() +} + func TestRegistry_GetDAO_NotRegistered(t *testing.T) { reg := New() diff --git a/internal/view/command_input.go b/internal/view/command_input.go index 06db304f..1c29a76b 100644 --- a/internal/view/command_input.go +++ b/internal/view/command_input.go @@ -3,6 +3,7 @@ package view import ( "context" "fmt" + "slices" "strings" "charm.land/bubbles/v2/textinput" @@ -228,63 +229,58 @@ func (c *CommandInput) executeCommand() (tea.Cmd, *NavigateMsg) { return nil, &NavigateMsg{View: browser, ClearStack: true} } - // Handle sort command: :sort, :sort , :sort desc - if input == "sort" || strings.HasPrefix(input, "sort ") { - return c.parseSortCommand(input), nil + // Handle sort command: :sort (clear) or :sort (sort by column) + if input == "sort" { + return func() tea.Msg { + return SortMsg{Column: "", Ascending: true} + }, nil + } + if suffix, ok := strings.CutPrefix(input, "sort "); ok { + return c.parseSortArgs(suffix), nil } - if input == "login" || strings.HasPrefix(input, "login ") { - profileName := "claws-login" - if strings.HasPrefix(input, "login ") { - if name := strings.TrimSpace(strings.TrimPrefix(input, "login ")); name != "" { - if !config.IsValidProfileName(name) { - return func() tea.Msg { - return ErrorMsg{Err: fmt.Errorf("invalid profile name: %s", name)} - }, nil - } - profileName = name - } + // Handle login command: :login (default) or :login + if input == "login" { + return c.executeLogin("claws-login"), nil + } + if suffix, ok := strings.CutPrefix(input, "login "); ok { + profileName := strings.TrimSpace(suffix) + if profileName == "" { + return c.executeLogin("claws-login"), nil } - exec := &action.SimpleExec{ - Command: fmt.Sprintf("aws login --remote --profile %s", profileName), - ActionName: action.ActionNameLogin, - SkipAWSEnv: true, + if !config.IsValidProfileName(profileName) { + return func() tea.Msg { + return ErrorMsg{Err: fmt.Errorf("invalid profile name: %q", profileName)} + }, nil } - return tea.Exec(exec, func(err error) tea.Msg { - if err != nil { - return ErrorMsg{Err: err} - } - sel := config.NamedProfile(profileName) - config.Global().SetSelections([]config.ProfileSelection{sel}) - return navmsg.ProfilesChangedMsg{Selections: []config.ProfileSelection{sel}} - }), nil + return c.executeLogin(profileName), nil } - // Handle tag command: :tag - filter current view by tag - if input == "tag" || strings.HasPrefix(input, "tag ") { - tagFilter := "" - if strings.HasPrefix(input, "tag ") { - tagFilter = strings.TrimPrefix(input, "tag ") - } + // Handle tag command: :tag (clear) or :tag (filter by tag) + if input == "tag" { + return func() tea.Msg { + return TagFilterMsg{Filter: ""} + }, nil + } + if tagFilter, ok := strings.CutPrefix(input, "tag "); ok { return func() tea.Msg { return TagFilterMsg{Filter: tagFilter} }, nil } - // Handle tags command: :tags, :tags - cross-service tag search via Tagging API - if input == "tags" || strings.HasPrefix(input, "tags ") { - tagFilter := "" - if strings.HasPrefix(input, "tags ") { - tagFilter = strings.TrimPrefix(input, "tags ") - } + // Handle tags command: :tags (all) or :tags (cross-service tag search) + if input == "tags" { + browser := NewTagSearchView(c.ctx, c.registry, "") + return nil, &NavigateMsg{View: browser} + } + if tagFilter, ok := strings.CutPrefix(input, "tags "); ok { browser := NewTagSearchView(c.ctx, c.registry, tagFilter) return nil, &NavigateMsg{View: browser} } // Handle diff command: :diff or :diff - if strings.HasPrefix(input, "diff ") { - args := strings.TrimSpace(strings.TrimPrefix(input, "diff ")) - parts := strings.Fields(args) + if suffix, ok := strings.CutPrefix(input, "diff "); ok { + parts := strings.Fields(suffix) if len(parts) == 1 { // :diff - compare current row with named resource return func() tea.Msg { @@ -356,55 +352,56 @@ func (c *CommandInput) executeCommand() (tea.Cmd, *NavigateMsg) { return nil, nil } -// parseSortCommand parses the sort command and returns a SortMsg command -// Syntax: :sort, :sort , :sort desc -func (c *CommandInput) parseSortCommand(input string) tea.Cmd { - // :sort - clear sorting - if input == "sort" { - return func() tea.Msg { - return SortMsg{Column: "", Ascending: true} - } - } - - // Parse arguments - args := strings.TrimPrefix(input, "sort ") +func (c *CommandInput) parseSortArgs(args string) tea.Cmd { ascending := true column := args - // Check for "desc" prefix - if strings.HasPrefix(args, "desc ") { + if col, ok := strings.CutPrefix(args, "desc "); ok { ascending = false - column = strings.TrimPrefix(args, "desc ") - } else if strings.HasPrefix(args, "asc ") { - ascending = true - column = strings.TrimPrefix(args, "asc ") + column = col + } else if col, ok := strings.CutPrefix(args, "asc "); ok { + column = col } - column = strings.TrimSpace(column) - return func() tea.Msg { - return SortMsg{Column: column, Ascending: ascending} + return SortMsg{Column: strings.TrimSpace(column), Ascending: ascending} } } +func (c *CommandInput) executeLogin(profileName string) tea.Cmd { + exec := &action.SimpleExec{ + Command: fmt.Sprintf("aws login --remote --profile %s", profileName), + ActionName: action.ActionNameLogin, + SkipAWSEnv: true, + } + return tea.Exec(exec, func(err error) tea.Msg { + if err != nil { + return ErrorMsg{Err: err} + } + sel := config.NamedProfile(profileName) + config.Global().SetSelections([]config.ProfileSelection{sel}) + return navmsg.ProfilesChangedMsg{Selections: []config.ProfileSelection{sel}} + }) +} + // GetSuggestions returns command suggestions based on current input func (c *CommandInput) GetSuggestions() []string { input := c.textInput.Value() var suggestions []string // Handle :tag command completion - if strings.HasPrefix(input, "tag ") { - return c.getTagSuggestions("tag ", strings.TrimPrefix(input, "tag ")) + if suffix, ok := strings.CutPrefix(input, "tag "); ok { + return c.getTagSuggestions("tag ", suffix) } // Handle :tags command completion (same as :tag) - if strings.HasPrefix(input, "tags ") { - return c.getTagSuggestions("tags ", strings.TrimPrefix(input, "tags ")) + if suffix, ok := strings.CutPrefix(input, "tags "); ok { + return c.getTagSuggestions("tags ", suffix) } // Handle :diff command completion - if strings.HasPrefix(input, "diff ") { - return c.getDiffSuggestions(strings.TrimPrefix(input, "diff ")) + if suffix, ok := strings.CutPrefix(input, "diff "); ok { + return c.getDiffSuggestions(suffix) } if strings.Contains(input, "/") { @@ -462,40 +459,51 @@ func (c *CommandInput) GetSuggestions() []string { suggestions = append(suggestions, svc) } } + + for _, alias := range c.registry.GetAliases() { + if strings.HasPrefix(alias, input) { + suggestions = append(suggestions, alias) + } + } + + slices.Sort(suggestions) } return suggestions } -// getDiffSuggestions returns resource name suggestions for diff command -// Supports: :diff and :diff func (c *CommandInput) getDiffSuggestions(args string) []string { if c.diffProvider == nil { return nil } - var suggestions []string names := c.diffProvider.GetResourceNames() - - // Check if we're completing the second name (has space after first name) parts := strings.SplitN(args, " ", 2) + if len(parts) == 2 { - // Completing second name: "diff name1 " firstName := parts[0] secondPrefix := strings.ToLower(parts[1]) + + var filtered []string for _, name := range names { - if name != firstName && (secondPrefix == "" || strings.Contains(strings.ToLower(name), secondPrefix)) { - suggestions = append(suggestions, "diff "+firstName+" "+name) + if name != firstName { + filtered = append(filtered, name) } } - } else { - // Completing first name: "diff " - prefix := strings.ToLower(args) - for _, name := range names { - if prefix == "" || strings.Contains(strings.ToLower(name), prefix) { - suggestions = append(suggestions, "diff "+name) - } + + matched := matchNamesWithFallback(filtered, secondPrefix) + var suggestions []string + for _, name := range matched { + suggestions = append(suggestions, "diff "+firstName+" "+name) } + return suggestions + } + + prefix := strings.ToLower(args) + matched := matchNamesWithFallback(names, prefix) + var suggestions []string + for _, name := range matched { + suggestions = append(suggestions, "diff "+name) } return suggestions } diff --git a/internal/view/command_input_test.go b/internal/view/command_input_test.go index 197f952b..a5643b17 100644 --- a/internal/view/command_input_test.go +++ b/internal/view/command_input_test.go @@ -94,6 +94,46 @@ func TestCommandInput_GetSuggestions(t *testing.T) { } } +func TestCommandInput_GetSuggestions_Aliases(t *testing.T) { + ctx := context.Background() + reg := registry.New() + + reg.RegisterCustom("costexplorer", "costs", registry.Entry{}) + reg.RegisterCustom("cloudformation", "stacks", registry.Entry{}) + + ci := NewCommandInput(ctx, reg) + ci.Activate() + + tests := []struct { + input string + expected []string + }{ + {"cost", []string{"costexplorer", "cost-explorer"}}, + {"cf", []string{"cf", "cfn"}}, + {"cfn", []string{"cfn"}}, + } + + for _, tt := range tests { + t.Run(tt.input, func(t *testing.T) { + ci.textInput.SetValue(tt.input) + suggestions := ci.GetSuggestions() + + for _, exp := range tt.expected { + found := false + for _, s := range suggestions { + if s == exp { + found = true + break + } + } + if !found { + t.Errorf("Expected %q in suggestions for %q, got %v", exp, tt.input, suggestions) + } + } + }) + } +} + func TestCommandInput_SetWidth(t *testing.T) { ctx := context.Background() reg := registry.New() @@ -221,22 +261,46 @@ func TestCommandInput_getDiffSuggestions(t *testing.T) { want: nil, }, { - name: "empty args returns all", + name: "empty args returns all sorted", provider: &mockDiffProvider{names: []string{"web-server", "db-server", "cache"}}, args: "", - want: []string{"diff web-server", "diff db-server", "diff cache"}, + want: []string{"diff cache", "diff db-server", "diff web-server"}, + }, + { + name: "prefix match", + provider: &mockDiffProvider{names: []string{"web-server", "db-server", "cache"}}, + args: "web", + want: []string{"diff web-server"}, + }, + { + name: "prefix match multiple sorted", + provider: &mockDiffProvider{names: []string{"web-server", "web-api", "db-server"}}, + args: "web", + want: []string{"diff web-api", "diff web-server"}, }, { - name: "first name prefix filter", + name: "fuzzy fallback when no prefix sorted", provider: &mockDiffProvider{names: []string{"web-server", "db-server", "cache"}}, args: "server", - want: []string{"diff web-server", "diff db-server"}, + want: []string{"diff db-server", "diff web-server"}, }, { - name: "case insensitive match", + name: "fuzzy match pattern", + provider: &mockDiffProvider{names: []string{"web-server", "db-server", "cache"}}, + args: "wsr", + want: []string{"diff web-server"}, + }, + { + name: "case insensitive prefix", + provider: &mockDiffProvider{names: []string{"Web-Server", "DB-Server", "Cache"}}, + args: "WEB", + want: []string{"diff Web-Server"}, + }, + { + name: "case insensitive fuzzy sorted", provider: &mockDiffProvider{names: []string{"Web-Server", "DB-Server", "Cache"}}, args: "SERVER", - want: []string{"diff Web-Server", "diff DB-Server"}, + want: []string{"diff DB-Server", "diff Web-Server"}, }, { name: "no match returns empty", @@ -245,10 +309,10 @@ func TestCommandInput_getDiffSuggestions(t *testing.T) { want: nil, }, { - name: "second name completion excludes first", + name: "second name completion excludes first sorted", provider: &mockDiffProvider{names: []string{"web-server", "db-server", "cache"}}, args: "web-server ", - want: []string{"diff web-server db-server", "diff web-server cache"}, + want: []string{"diff web-server cache", "diff web-server db-server"}, }, { name: "second name with prefix", @@ -256,6 +320,12 @@ func TestCommandInput_getDiffSuggestions(t *testing.T) { args: "web-server db", want: []string{"diff web-server db-server"}, }, + { + name: "second name fuzzy fallback", + provider: &mockDiffProvider{names: []string{"web-server", "db-server", "cache"}}, + args: "web-server sr", + want: []string{"diff web-server db-server"}, + }, { name: "second name no match", provider: &mockDiffProvider{names: []string{"web-server", "db-server"}}, @@ -278,7 +348,7 @@ func TestCommandInput_getDiffSuggestions(t *testing.T) { name: "single resource for second - no suggestions", provider: &mockDiffProvider{names: []string{"only-one"}}, args: "only-one ", - want: nil, // can't diff with self + want: nil, }, } diff --git a/internal/view/match.go b/internal/view/match.go new file mode 100644 index 00000000..b1769fc7 --- /dev/null +++ b/internal/view/match.go @@ -0,0 +1,51 @@ +package view + +import ( + "slices" + "strings" +) + +// fuzzyMatch checks if pattern characters appear in order in str (case insensitive) +func fuzzyMatch(str, pattern string) bool { + str = strings.ToLower(str) + pattern = strings.ToLower(pattern) + pi := 0 + for i := 0; i < len(str) && pi < len(pattern); i++ { + if str[i] == pattern[pi] { + pi++ + } + } + return pi == len(pattern) +} + +// matchNamesWithFallback returns names matching the pattern. +// It first tries prefix matching, then falls back to fuzzy matching if no prefix matches. +func matchNamesWithFallback(names []string, pattern string) []string { + if pattern == "" { + result := slices.Clone(names) + slices.Sort(result) + return result + } + + pattern = strings.ToLower(pattern) + + var prefixMatches []string + for _, name := range names { + if strings.HasPrefix(strings.ToLower(name), pattern) { + prefixMatches = append(prefixMatches, name) + } + } + if len(prefixMatches) > 0 { + slices.Sort(prefixMatches) + return prefixMatches + } + + var fuzzyMatches []string + for _, name := range names { + if fuzzyMatch(name, pattern) { + fuzzyMatches = append(fuzzyMatches, name) + } + } + slices.Sort(fuzzyMatches) + return fuzzyMatches +} diff --git a/internal/view/match_test.go b/internal/view/match_test.go new file mode 100644 index 00000000..abab4beb --- /dev/null +++ b/internal/view/match_test.go @@ -0,0 +1,113 @@ +package view + +import ( + "slices" + "testing" +) + +func TestFuzzyMatch(t *testing.T) { + tests := []struct { + str string + pattern string + want bool + }{ + {"AgentCoreStackdev", "agecrstdev", true}, + {"AgentCoreStackdev", "agent", true}, + {"AgentCoreStackdev", "acd", true}, + {"AgentCoreStackdev", "xyz", false}, + {"AgentCoreStackdev", "deva", false}, + {"i-1234567890abcdef0", "i1234", true}, + {"i-1234567890abcdef0", "abcdef", true}, + {"production", "prod", true}, + {"production", "pdn", true}, + {"", "a", false}, + {"abc", "", true}, + // uppercase pattern - case insensitive + {"production", "PROD", true}, + {"AgentCoreStackdev", "ACD", true}, + {"web-server", "WEB", true}, + } + + for _, tt := range tests { + t.Run(tt.str+"_"+tt.pattern, func(t *testing.T) { + got := fuzzyMatch(tt.str, tt.pattern) + if got != tt.want { + t.Errorf("fuzzyMatch(%q, %q) = %v, want %v", tt.str, tt.pattern, got, tt.want) + } + }) + } +} + +func TestMatchNamesWithFallback(t *testing.T) { + tests := []struct { + name string + names []string + pattern string + want []string + }{ + { + name: "empty pattern returns all sorted", + names: []string{"web-server", "db-server", "cache"}, + pattern: "", + want: []string{"cache", "db-server", "web-server"}, + }, + { + name: "prefix match single", + names: []string{"web-server", "db-server", "cache"}, + pattern: "web", + want: []string{"web-server"}, + }, + { + name: "prefix match multiple", + names: []string{"web-server", "web-api", "db-server"}, + pattern: "web", + want: []string{"web-api", "web-server"}, + }, + { + name: "fuzzy fallback when no prefix", + names: []string{"web-server", "db-server", "cache"}, + pattern: "server", + want: []string{"db-server", "web-server"}, + }, + { + name: "fuzzy match pattern", + names: []string{"web-server", "db-server", "cache"}, + pattern: "wsr", + want: []string{"web-server"}, + }, + { + name: "case insensitive prefix lowercase pattern", + names: []string{"Web-Server", "DB-Server", "Cache"}, + pattern: "web", + want: []string{"Web-Server"}, + }, + { + name: "case insensitive prefix uppercase pattern", + names: []string{"web-server", "web-api", "db-server"}, + pattern: "WEB", + want: []string{"web-api", "web-server"}, + }, + { + name: "no match returns empty", + names: []string{"web-server", "db-server"}, + pattern: "xyz", + want: nil, + }, + { + name: "empty names", + names: []string{}, + pattern: "web", + want: nil, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got := matchNamesWithFallback(tt.names, tt.pattern) + if !slices.Equal(got, tt.want) { + t.Errorf("matchNamesWithFallback(%v, %q) = %v, want %v", + tt.names, tt.pattern, got, tt.want) + } + }) + } +} diff --git a/internal/view/resource_browser_filter.go b/internal/view/resource_browser_filter.go index 4c143e40..23ad866d 100644 --- a/internal/view/resource_browser_filter.go +++ b/internal/view/resource_browser_filter.go @@ -193,15 +193,3 @@ func getFieldValue(data any, fieldName string) string { return fmt.Sprintf("%v", field.Interface()) } } - -// fuzzyMatch checks if pattern characters appear in order in str (case insensitive) -func fuzzyMatch(str, pattern string) bool { - str = strings.ToLower(str) - pi := 0 - for i := 0; i < len(str) && pi < len(pattern); i++ { - if str[i] == pattern[pi] { - pi++ - } - } - return pi == len(pattern) -} diff --git a/internal/view/resource_browser_sort.go b/internal/view/resource_browser_sort.go index ce02cb15..242aa524 100644 --- a/internal/view/resource_browser_sort.go +++ b/internal/view/resource_browser_sort.go @@ -90,8 +90,8 @@ func parseNumeric(s string) (float64, error) { } for suffix, mult := range suffixes { - if strings.HasSuffix(s, suffix) { - s = strings.TrimSuffix(s, suffix) + if before, ok := strings.CutSuffix(s, suffix); ok { + s = before multiplier = mult break } diff --git a/internal/view/service_browser_test.go b/internal/view/service_browser_test.go index 35b06d10..15d989fe 100644 --- a/internal/view/service_browser_test.go +++ b/internal/view/service_browser_test.go @@ -140,35 +140,6 @@ func TestServiceBrowserCategoryNavigation(t *testing.T) { } } -func TestFuzzyMatch(t *testing.T) { - tests := []struct { - str string - pattern string - want bool - }{ - {"AgentCoreStackdev", "agecrstdev", true}, - {"AgentCoreStackdev", "agent", true}, - {"AgentCoreStackdev", "acd", true}, - {"AgentCoreStackdev", "xyz", false}, - {"AgentCoreStackdev", "deva", false}, // order matters - {"i-1234567890abcdef0", "i1234", true}, - {"i-1234567890abcdef0", "abcdef", true}, - {"production", "prod", true}, - {"production", "pdn", true}, - {"", "a", false}, - {"abc", "", true}, // empty pattern matches everything - } - - for _, tt := range tests { - t.Run(tt.str+"_"+tt.pattern, func(t *testing.T) { - got := fuzzyMatch(tt.str, tt.pattern) - if got != tt.want { - t.Errorf("fuzzyMatch(%q, %q) = %v, want %v", tt.str, tt.pattern, got, tt.want) - } - }) - } -} - func TestServiceBrowserMouseHover(t *testing.T) { ctx := context.Background() reg := registry.New() From 8fa253e7665b9cddbc440b63761e8b9670ec8f73 Mon Sep 17 00:00:00 2001 From: yimsk Date: Fri, 2 Jan 2026 23:04:54 +0900 Subject: [PATCH 4/5] feat: add config.yaml support for timeouts, concurrency, persistence (#79) * feat: add config.yaml support for timeouts, concurrency, persistence (#67, #50) - Add ~/.config/claws/config.yaml for configurable timeouts/concurrency - Add --persist/--no-persist flags for region/profile persistence - Persist disabled by default, startup values always loaded - Replace hardcoded timeout constants with config values * docs: add config file docs, use config for max concurrent fetches * refactor: remove unused ReloadFile, add negative value validation * refactor: unify mutex pattern in config package * refactor: add thread safety to Save, unify GetStartup lock pattern * refactor: add shared lock helpers, atomic config save * refactor: rename NoPersist to PersistEnabled for clarity * refactor: add MetricsWindow config, consolidate lock helpers - Add MetricsWindow (15m default) to FileConfig for CloudWatch metrics - Move lock helpers from internal/sync to internal/config - Update cloudwatch.go to use config.File().MetricsWindow() * refactor: move MetricsWindow to cloudwatch section, unify PersistenceEnabled naming * refactor: unify PersistenceEnabled to FileConfig, copy slice in GetStartup * fix: deep copy Regions in Save(), refactor startup config logic - Fix data race in Save() by deep copying Startup.Regions slice - Extract applyStartupConfig() helper for cleaner precedence handling - Reduce GetStartup() calls from 2 to 1 Addresses PR #79 review feedback. * fix: apply startup regions regardless of profile source --- README.md | 31 ++- cmd/claws/main.go | 59 ++-- go.mod | 1 + go.sum | 2 + internal/app/app.go | 28 +- internal/aws/init.go | 8 +- internal/config/config.go | 13 - internal/config/file.go | 318 ++++++++++++++++++++++ internal/config/file_test.go | 284 +++++++++++++++++++ internal/config/lock.go | 15 + internal/metrics/cloudwatch.go | 4 +- internal/view/resource_browser.go | 5 +- internal/view/resource_browser_fetch.go | 9 +- internal/view/resource_browser_metrics.go | 3 +- internal/view/tag_search_view.go | 8 +- 15 files changed, 726 insertions(+), 62 deletions(-) create mode 100644 internal/config/file.go create mode 100644 internal/config/file_test.go create mode 100644 internal/config/lock.go diff --git a/README.md b/README.md index cddd2490..31214eb8 100644 --- a/README.md +++ b/README.md @@ -385,7 +385,36 @@ claws uses your standard AWS configuration: - `~/.aws/config` - AWS configuration (region, profile) - Environment variables: `AWS_PROFILE`, `AWS_REGION`, `AWS_ACCESS_KEY_ID`, etc. -Configuration is stored in `~/.config/claws/config.yaml` for profile preferences. +### Configuration File + +Optional settings can be stored in `~/.config/claws/config.yaml`: + +```yaml +timeouts: + aws_init: 10s # AWS initialization timeout (default: 5s) + multi_region_fetch: 60s # Multi-region parallel fetch timeout (default: 30s) + tag_search: 45s # Tag search timeout (default: 30s) + metrics_load: 30s # CloudWatch metrics load timeout (default: 30s) + +concurrency: + max_fetches: 100 # Max concurrent API fetches (default: 50) + +cloudwatch: + window: 15m # Metrics data window period (default: 15m) + +persistence: + enabled: true # Save region/profile on change (default: false) + +startup: # Applied on launch if present + profile: production + regions: + - us-east-1 + - us-west-2 +``` + +The config file is **not created automatically**. Create it manually if needed. + +CLI flags (`-p`, `-r`, `--persist`, `--no-persist`) override config file settings. For required IAM permissions, see [docs/iam-permissions.md](docs/iam-permissions.md). diff --git a/cmd/claws/main.go b/cmd/claws/main.go index 4b1a468b..d9c18b49 100644 --- a/cmd/claws/main.go +++ b/cmd/claws/main.go @@ -23,9 +23,14 @@ func main() { propagateAllProxy() - // Apply CLI options to global config + fileCfg := config.File() cfg := config.Global() + // CLI persistence flags override config file + if opts.persist != nil { + fileCfg.SetPersistenceEnabled(*opts.persist) + } + // Check environment variables (CLI flags take precedence) if !opts.readOnly { if v := os.Getenv("CLAWS_READ_ONLY"); v == "1" || v == "true" { @@ -45,21 +50,7 @@ func main() { os.Exit(1) } - if opts.envCreds { - // Use environment credentials, ignore ~/.aws config - cfg.UseEnvOnly() - } else if opts.profile != "" { - cfg.UseProfile(opts.profile) - // Don't set AWS_PROFILE globally - it interferes with EnvOnly mode - // when switching profiles. SelectionLoadOptions uses WithSharedConfigProfile - // for SDK calls, and BuildSubprocessEnv handles subprocess environment. - } - // else: SDKDefault is the zero value, no action needed - if opts.region != "" { - cfg.SetRegion(opts.region) - // Don't set AWS_REGION globally - SelectionLoadOptions handles SDK calls, - // and BuildSubprocessEnv handles subprocess environment. - } + applyStartupConfig(opts, fileCfg, cfg) // Enable logging if log file specified if opts.logFile != "" { @@ -86,12 +77,12 @@ func main() { } } -// cliOptions holds command line options type cliOptions struct { profile string region string readOnly bool envCreds bool + persist *bool // nil = use config, true = enable, false = disable logFile string } @@ -118,6 +109,12 @@ func parseFlags() cliOptions { opts.readOnly = true case "-e", "--env": opts.envCreds = true + case "--persist": + t := true + opts.persist = &t + case "--no-persist": + f := false + opts.persist = &f case "-l", "--log-file": if i+1 < len(args) { i++ @@ -158,6 +155,10 @@ func printUsage() { fmt.Println(" Useful for instance profiles, ECS task roles, Lambda, etc.") fmt.Println(" -ro, --read-only") fmt.Println(" Run in read-only mode (disable dangerous actions)") + fmt.Println(" --persist") + fmt.Println(" Enable saving region/profile selection to config file") + fmt.Println(" --no-persist") + fmt.Println(" Disable saving region/profile selection to config file") fmt.Println(" -l, --log-file ") fmt.Println(" Enable debug logging to specified file") fmt.Println(" -v, --version") @@ -170,6 +171,30 @@ func printUsage() { fmt.Println(" ALL_PROXY Propagated to HTTP_PROXY/HTTPS_PROXY if not set") } +// applyStartupConfig applies profile/region config with precedence: +// 1. CLI flags (-p, -r, -e) - highest priority +// 2. Config file startup section +// 3. AWS SDK defaults +func applyStartupConfig(opts cliOptions, fileCfg *config.FileConfig, cfg *config.Config) { + startupRegions, startupProfile := fileCfg.GetStartup() + + // Apply profile: CLI > startup config + if opts.envCreds { + cfg.UseEnvOnly() + } else if opts.profile != "" { + cfg.UseProfile(opts.profile) + } else if startupProfile != "" { + cfg.UseProfile(startupProfile) + } + + // Apply region: CLI > startup config + if opts.region != "" { + cfg.SetRegion(opts.region) + } else if len(startupRegions) > 0 { + cfg.SetRegions(startupRegions) + } +} + // propagateAllProxy copies ALL_PROXY to HTTP_PROXY/HTTPS_PROXY if not set. // Go's net/http ignores ALL_PROXY, so we propagate it to the standard vars. func propagateAllProxy() { diff --git a/go.mod b/go.mod index 6be88d7e..302ee619 100644 --- a/go.mod +++ b/go.mod @@ -86,6 +86,7 @@ require ( golang.org/x/sync v0.19.0 golang.org/x/term v0.38.0 gopkg.in/ini.v1 v1.67.0 + gopkg.in/yaml.v3 v3.0.1 ) require ( diff --git a/go.sum b/go.sum index 6cd4337d..8b157de5 100644 --- a/go.sum +++ b/go.sum @@ -232,6 +232,8 @@ golang.org/x/sys v0.39.0 h1:CvCKL8MeisomCi6qNZ+wbb0DN9E5AATixKsvNtMoMFk= golang.org/x/sys v0.39.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= golang.org/x/term v0.38.0 h1:PQ5pkm/rLO6HnxFR7N2lJHOZX6Kez5Y1gDSJla6jo7Q= golang.org/x/term v0.38.0/go.mod h1:bSEAKrOT1W+VSu9TSCMtoGEOUcKxOKgl3LE5QEF/xVg= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/ini.v1 v1.67.0 h1:Dgnx+6+nfE+IfzjUEISNeydPJh9AXNNsWbGP9KzCsOA= gopkg.in/ini.v1 v1.67.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= diff --git a/internal/app/app.go b/internal/app/app.go index 844e02c9..4b3a7932 100644 --- a/internal/app/app.go +++ b/internal/app/app.go @@ -19,9 +19,6 @@ import ( "github.com/clawscli/claws/internal/view" ) -// awsInitTimeout is the maximum time to wait for AWS context initialization -const awsInitTimeout = 5 * time.Second - // clearErrorMsg is sent to clear transient errors after a timeout type clearErrorMsg struct{} @@ -113,7 +110,7 @@ func (a *App) Init() tea.Cmd { // Initialize AWS context in background (region detection, account ID fetch) // Use timeout to avoid indefinite hang on network issues initAWSCmd := func() tea.Msg { - ctx, cancel := context.WithTimeout(a.ctx, awsInitTimeout) + ctx, cancel := context.WithTimeout(a.ctx, config.File().AWSInitTimeout()) defer cancel() err := aws.InitContext(ctx) return awsContextReadyMsg{err: err} @@ -336,6 +333,16 @@ func (a *App) Update(msg tea.Msg) (tea.Model, tea.Cmd) { case navmsg.RegionChangedMsg: log.Info("regions changed", "regions", msg.Regions) + if config.File().PersistenceEnabled() { + profile := "" + if sel := config.Global().Selection(); sel.IsNamedProfile() { + profile = sel.ProfileName + } + config.File().SetStartup(msg.Regions, profile) + if err := config.File().Save(); err != nil { + log.Warn("failed to persist config", "error", err) + } + } // Pop views until we find a refreshable one (ResourceBrowser or ServiceBrowser) for len(a.viewStack) > 0 { a.currentView = a.viewStack[len(a.viewStack)-1] @@ -356,12 +363,23 @@ func (a *App) Update(msg tea.Msg) (tea.Model, tea.Cmd) { case navmsg.ProfilesChangedMsg: log.Info("profiles changed", "count", len(msg.Selections)) + if config.File().PersistenceEnabled() { + profile := "" + if len(msg.Selections) > 0 && msg.Selections[0].IsNamedProfile() { + profile = msg.Selections[0].ProfileName + } + regions := config.Global().Regions() + config.File().SetStartup(regions, profile) + if err := config.File().Save(); err != nil { + log.Warn("failed to persist config", "error", err) + } + } a.profileRefreshID++ a.profileRefreshing = true a.profileRefreshError = nil refreshID := a.profileRefreshID refreshCmd := func() tea.Msg { - ctx, cancel := context.WithTimeout(a.ctx, awsInitTimeout) + ctx, cancel := context.WithTimeout(a.ctx, config.File().AWSInitTimeout()) defer cancel() region, accountIDs, err := aws.RefreshContextData(ctx) return profileRefreshDoneMsg{ diff --git a/internal/aws/init.go b/internal/aws/init.go index 565bf2e4..4b2259f8 100644 --- a/internal/aws/init.go +++ b/internal/aws/init.go @@ -9,10 +9,6 @@ import ( appconfig "github.com/clawscli/claws/internal/config" ) -// maxConcurrentProfileFetches limits parallel AWS config loads to prevent -// file descriptor exhaustion and excessive memory usage with many profiles. -const maxConcurrentProfileFetches = 50 - // InitContext initializes AWS context by loading config and fetching account ID. // Updates the global config with region (if not already set) and account ID. func InitContext(ctx context.Context) error { @@ -37,7 +33,7 @@ func InitContext(ctx context.Context) error { // RefreshContextData re-fetches region and account ID for the current profile selection(s). // Returns the data without modifying global state, allowing the caller to apply changes. -// Fetches up to 50 profiles concurrently. Returns partial results and first error on failure. +// Concurrency is limited by config.File().MaxConcurrentFetches(). Returns partial results and first error on failure. func RefreshContextData(ctx context.Context) (region string, accountIDs map[string]string, err error) { selections := appconfig.Global().Selections() if len(selections) == 0 { @@ -56,7 +52,7 @@ func RefreshContextData(ctx context.Context) (region string, accountIDs map[stri accountIDs = make(map[string]string) var mu sync.Mutex errChan := make(chan error, len(selections)) - sem := make(chan struct{}, maxConcurrentProfileFetches) + sem := make(chan struct{}, appconfig.File().MaxConcurrentFetches()) for _, sel := range selections { wg.Add(1) diff --git a/internal/config/config.go b/internal/config/config.go index b4c20fa1..d158e08a 100644 --- a/internal/config/config.go +++ b/internal/config/config.go @@ -174,7 +174,6 @@ func (s ProfileSelection) ID() string { } } -// Config holds global application configuration type Config struct { mu sync.RWMutex regions []string @@ -189,18 +188,6 @@ var ( initOnce sync.Once ) -func withRLock[T any](mu *sync.RWMutex, fn func() T) T { - mu.RLock() - defer mu.RUnlock() - return fn() -} - -func doWithLock(mu *sync.RWMutex, fn func()) { - mu.Lock() - defer mu.Unlock() - fn() -} - // Global returns the global config instance func Global() *Config { initOnce.Do(func() { diff --git a/internal/config/file.go b/internal/config/file.go new file mode 100644 index 00000000..00e84f6a --- /dev/null +++ b/internal/config/file.go @@ -0,0 +1,318 @@ +package config + +import ( + "errors" + "fmt" + "os" + "path/filepath" + "sync" + "time" + + "gopkg.in/yaml.v3" +) + +const ( + DefaultAWSInitTimeout = 5 * time.Second + DefaultMultiRegionFetchTimeout = 30 * time.Second + DefaultTagSearchTimeout = 30 * time.Second + DefaultMetricsLoadTimeout = 30 * time.Second + DefaultMetricsWindow = 15 * time.Minute + DefaultMaxConcurrentFetches = 50 +) + +func ConfigDir() (string, error) { + home, err := os.UserHomeDir() + if err != nil { + return "", fmt.Errorf("get home dir: %w", err) + } + return filepath.Join(home, ".config", "claws"), nil +} + +func ConfigPath() (string, error) { + dir, err := ConfigDir() + if err != nil { + return "", err + } + return filepath.Join(dir, "config.yaml"), nil +} + +type TimeoutConfig struct { + AWSInit Duration `yaml:"aws_init,omitempty"` + MultiRegionFetch Duration `yaml:"multi_region_fetch,omitempty"` + TagSearch Duration `yaml:"tag_search,omitempty"` + MetricsLoad Duration `yaml:"metrics_load,omitempty"` +} + +type CloudWatchConfig struct { + Window Duration `yaml:"window,omitempty"` +} + +type ConcurrencyConfig struct { + MaxFetches int `yaml:"max_fetches,omitempty"` +} + +type PersistenceConfig struct { + Enabled bool `yaml:"enabled"` +} + +type StartupConfig struct { + Regions []string `yaml:"regions,omitempty"` + Profile string `yaml:"profile,omitempty"` +} + +type FileConfig struct { + mu sync.RWMutex `yaml:"-"` + persistenceOverride *bool `yaml:"-"` // CLI flag override (not persisted) + Timeouts TimeoutConfig `yaml:"timeouts,omitempty"` + Concurrency ConcurrencyConfig `yaml:"concurrency,omitempty"` + CloudWatch CloudWatchConfig `yaml:"cloudwatch,omitempty"` + Persistence PersistenceConfig `yaml:"persistence"` + Startup StartupConfig `yaml:"startup,omitempty"` +} + +// Duration wraps time.Duration for YAML marshal/unmarshal as string (e.g., "5s", "30s") +type Duration time.Duration + +func (d Duration) Duration() time.Duration { + return time.Duration(d) +} + +func (d Duration) MarshalYAML() (interface{}, error) { + return time.Duration(d).String(), nil +} + +func (d *Duration) UnmarshalYAML(node *yaml.Node) error { + var s string + if err := node.Decode(&s); err != nil { + return err + } + if s == "" { + *d = 0 + return nil + } + dur, err := time.ParseDuration(s) + if err != nil { + return fmt.Errorf("invalid duration %q: %w", s, err) + } + *d = Duration(dur) + return nil +} + +func DefaultFileConfig() *FileConfig { + return &FileConfig{ + Timeouts: TimeoutConfig{ + AWSInit: Duration(DefaultAWSInitTimeout), + MultiRegionFetch: Duration(DefaultMultiRegionFetchTimeout), + TagSearch: Duration(DefaultTagSearchTimeout), + MetricsLoad: Duration(DefaultMetricsLoadTimeout), + }, + Concurrency: ConcurrencyConfig{ + MaxFetches: DefaultMaxConcurrentFetches, + }, + CloudWatch: CloudWatchConfig{ + Window: Duration(DefaultMetricsWindow), + }, + Persistence: PersistenceConfig{ + Enabled: false, + }, + } +} + +var ( + fileConfig *FileConfig + fileConfigOnce sync.Once +) + +func File() *FileConfig { + fileConfigOnce.Do(func() { + cfg, err := Load() + if err != nil { + cfg = DefaultFileConfig() + } + fileConfig = cfg + }) + return fileConfig +} + +func Load() (*FileConfig, error) { + path, err := ConfigPath() + if err != nil { + return DefaultFileConfig(), nil + } + + data, err := os.ReadFile(path) + if err != nil { + if errors.Is(err, os.ErrNotExist) { + return DefaultFileConfig(), nil + } + return nil, fmt.Errorf("read config: %w", err) + } + + cfg := DefaultFileConfig() + if err := yaml.Unmarshal(data, cfg); err != nil { + return nil, fmt.Errorf("parse config: %w", err) + } + + cfg.applyDefaults() + return cfg, nil +} + +func (c *FileConfig) Save() error { + path, err := ConfigPath() + if err != nil { + return err + } + + dir := filepath.Dir(path) + if err := os.MkdirAll(dir, 0755); err != nil { + return fmt.Errorf("create config dir: %w", err) + } + + snapshot := withRLock(&c.mu, func() FileConfig { + return FileConfig{ + Timeouts: c.Timeouts, + Concurrency: c.Concurrency, + CloudWatch: c.CloudWatch, + Persistence: c.Persistence, + Startup: StartupConfig{ + Regions: append([]string(nil), c.Startup.Regions...), + Profile: c.Startup.Profile, + }, + } + }) + + data, err := yaml.Marshal(&snapshot) + if err != nil { + return fmt.Errorf("marshal config: %w", err) + } + + // Atomic write: write to temp file, then rename + tmpFile, err := os.CreateTemp(dir, ".config.yaml.tmp.*") + if err != nil { + return fmt.Errorf("create temp file: %w", err) + } + tmpPath := tmpFile.Name() + + if _, err := tmpFile.Write(data); err != nil { + _ = tmpFile.Close() + _ = os.Remove(tmpPath) + return fmt.Errorf("write temp file: %w", err) + } + if err := tmpFile.Close(); err != nil { + _ = os.Remove(tmpPath) + return fmt.Errorf("close temp file: %w", err) + } + + if err := os.Rename(tmpPath, path); err != nil { + _ = os.Remove(tmpPath) + return fmt.Errorf("rename config file: %w", err) + } + + return nil +} + +func (c *FileConfig) applyDefaults() { + if c.Timeouts.AWSInit <= 0 { + c.Timeouts.AWSInit = Duration(DefaultAWSInitTimeout) + } + if c.Timeouts.MultiRegionFetch <= 0 { + c.Timeouts.MultiRegionFetch = Duration(DefaultMultiRegionFetchTimeout) + } + if c.Timeouts.TagSearch <= 0 { + c.Timeouts.TagSearch = Duration(DefaultTagSearchTimeout) + } + if c.Timeouts.MetricsLoad <= 0 { + c.Timeouts.MetricsLoad = Duration(DefaultMetricsLoadTimeout) + } + if c.CloudWatch.Window <= 0 { + c.CloudWatch.Window = Duration(DefaultMetricsWindow) + } + if c.Concurrency.MaxFetches <= 0 { + c.Concurrency.MaxFetches = DefaultMaxConcurrentFetches + } +} + +func (c *FileConfig) AWSInitTimeout() time.Duration { + return withRLock(&c.mu, func() time.Duration { + if c.Timeouts.AWSInit == 0 { + return DefaultAWSInitTimeout + } + return c.Timeouts.AWSInit.Duration() + }) +} + +func (c *FileConfig) MultiRegionFetchTimeout() time.Duration { + return withRLock(&c.mu, func() time.Duration { + if c.Timeouts.MultiRegionFetch == 0 { + return DefaultMultiRegionFetchTimeout + } + return c.Timeouts.MultiRegionFetch.Duration() + }) +} + +func (c *FileConfig) TagSearchTimeout() time.Duration { + return withRLock(&c.mu, func() time.Duration { + if c.Timeouts.TagSearch == 0 { + return DefaultTagSearchTimeout + } + return c.Timeouts.TagSearch.Duration() + }) +} + +func (c *FileConfig) MetricsLoadTimeout() time.Duration { + return withRLock(&c.mu, func() time.Duration { + if c.Timeouts.MetricsLoad == 0 { + return DefaultMetricsLoadTimeout + } + return c.Timeouts.MetricsLoad.Duration() + }) +} + +func (c *FileConfig) MaxConcurrentFetches() int { + return withRLock(&c.mu, func() int { + if c.Concurrency.MaxFetches == 0 { + return DefaultMaxConcurrentFetches + } + return c.Concurrency.MaxFetches + }) +} + +func (c *FileConfig) MetricsWindow() time.Duration { + return withRLock(&c.mu, func() time.Duration { + if c.CloudWatch.Window == 0 { + return DefaultMetricsWindow + } + return c.CloudWatch.Window.Duration() + }) +} + +func (c *FileConfig) PersistenceEnabled() bool { + return withRLock(&c.mu, func() bool { + if c.persistenceOverride != nil { + return *c.persistenceOverride + } + return c.Persistence.Enabled + }) +} + +func (c *FileConfig) SetPersistenceEnabled(enabled bool) { + doWithLock(&c.mu, func() { c.persistenceOverride = &enabled }) +} + +func (c *FileConfig) SetStartup(regions []string, profile string) { + doWithLock(&c.mu, func() { + c.Startup.Regions = regions + c.Startup.Profile = profile + }) +} + +func (c *FileConfig) GetStartup() ([]string, string) { + type result struct { + regions []string + profile string + } + r := withRLock(&c.mu, func() result { + return result{append([]string(nil), c.Startup.Regions...), c.Startup.Profile} + }) + return r.regions, r.profile +} diff --git a/internal/config/file_test.go b/internal/config/file_test.go new file mode 100644 index 00000000..8a0043d8 --- /dev/null +++ b/internal/config/file_test.go @@ -0,0 +1,284 @@ +package config + +import ( + "os" + "path/filepath" + "testing" + "time" + + "gopkg.in/yaml.v3" +) + +func TestDuration_MarshalUnmarshal(t *testing.T) { + tests := []struct { + name string + duration Duration + want string + }{ + {"5s", Duration(5 * time.Second), "5s"}, + {"30s", Duration(30 * time.Second), "30s"}, + {"1m", Duration(1 * time.Minute), "1m0s"}, + {"zero", Duration(0), "0s"}, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + // Marshal + data, err := yaml.Marshal(tt.duration) + if err != nil { + t.Fatalf("Marshal failed: %v", err) + } + got := string(data) + // yaml.Marshal adds newline + if got != tt.want+"\n" { + t.Errorf("Marshal = %q, want %q", got, tt.want+"\n") + } + + // Unmarshal + var d Duration + if err := yaml.Unmarshal([]byte(tt.want), &d); err != nil { + t.Fatalf("Unmarshal failed: %v", err) + } + if d != tt.duration { + t.Errorf("Unmarshal = %v, want %v", d, tt.duration) + } + }) + } +} + +func TestDuration_UnmarshalEmpty(t *testing.T) { + var d Duration + if err := yaml.Unmarshal([]byte(`""`), &d); err != nil { + t.Fatalf("Unmarshal empty failed: %v", err) + } + if d != 0 { + t.Errorf("Unmarshal empty = %v, want 0", d) + } +} + +func TestDuration_UnmarshalInvalid(t *testing.T) { + var d Duration + err := yaml.Unmarshal([]byte(`"invalid"`), &d) + if err == nil { + t.Error("Unmarshal invalid should fail") + } +} + +func TestDefaultFileConfig(t *testing.T) { + cfg := DefaultFileConfig() + + if cfg.Timeouts.AWSInit.Duration() != DefaultAWSInitTimeout { + t.Errorf("AWSInit = %v, want %v", cfg.Timeouts.AWSInit.Duration(), DefaultAWSInitTimeout) + } + if cfg.Timeouts.MultiRegionFetch.Duration() != DefaultMultiRegionFetchTimeout { + t.Errorf("MultiRegionFetch = %v, want %v", cfg.Timeouts.MultiRegionFetch.Duration(), DefaultMultiRegionFetchTimeout) + } + if cfg.Timeouts.TagSearch.Duration() != DefaultTagSearchTimeout { + t.Errorf("TagSearch = %v, want %v", cfg.Timeouts.TagSearch.Duration(), DefaultTagSearchTimeout) + } + if cfg.Timeouts.MetricsLoad.Duration() != DefaultMetricsLoadTimeout { + t.Errorf("MetricsLoad = %v, want %v", cfg.Timeouts.MetricsLoad.Duration(), DefaultMetricsLoadTimeout) + } + if cfg.Concurrency.MaxFetches != DefaultMaxConcurrentFetches { + t.Errorf("MaxFetches = %d, want %d", cfg.Concurrency.MaxFetches, DefaultMaxConcurrentFetches) + } + if cfg.Persistence.Enabled { + t.Error("Persistence.Enabled should be false by default") + } +} + +func TestLoad_MissingFile(t *testing.T) { + // Use a temp dir that doesn't have config.yaml + tmpDir := t.TempDir() + origHome := os.Getenv("HOME") + defer os.Setenv("HOME", origHome) + os.Setenv("HOME", tmpDir) + + cfg, err := Load() + if err != nil { + t.Fatalf("Load failed: %v", err) + } + + // Should return defaults + if cfg.AWSInitTimeout() != DefaultAWSInitTimeout { + t.Errorf("AWSInitTimeout() = %v, want %v", cfg.AWSInitTimeout(), DefaultAWSInitTimeout) + } +} + +func TestLoad_Save_Roundtrip(t *testing.T) { + tmpDir := t.TempDir() + origHome := os.Getenv("HOME") + defer os.Setenv("HOME", origHome) + os.Setenv("HOME", tmpDir) + + // Create config with custom values + cfg := &FileConfig{ + Timeouts: TimeoutConfig{ + AWSInit: Duration(10 * time.Second), + MultiRegionFetch: Duration(60 * time.Second), + TagSearch: Duration(45 * time.Second), + MetricsLoad: Duration(20 * time.Second), + }, + Concurrency: ConcurrencyConfig{ + MaxFetches: 100, + }, + Persistence: PersistenceConfig{ + Enabled: true, + }, + Startup: StartupConfig{ + Regions: []string{"us-east-1", "us-west-2"}, + Profile: "production", + }, + } + + // Save + if err := cfg.Save(); err != nil { + t.Fatalf("Save failed: %v", err) + } + + // Verify file exists + configPath := filepath.Join(tmpDir, ".config", "claws", "config.yaml") + if _, err := os.Stat(configPath); os.IsNotExist(err) { + t.Fatal("config file was not created") + } + + // Load and verify + loaded, err := Load() + if err != nil { + t.Fatalf("Load failed: %v", err) + } + + if loaded.AWSInitTimeout() != 10*time.Second { + t.Errorf("AWSInitTimeout() = %v, want %v", loaded.AWSInitTimeout(), 10*time.Second) + } + if loaded.MultiRegionFetchTimeout() != 60*time.Second { + t.Errorf("MultiRegionFetchTimeout() = %v, want %v", loaded.MultiRegionFetchTimeout(), 60*time.Second) + } + if loaded.TagSearchTimeout() != 45*time.Second { + t.Errorf("TagSearchTimeout() = %v, want %v", loaded.TagSearchTimeout(), 45*time.Second) + } + if loaded.MetricsLoadTimeout() != 20*time.Second { + t.Errorf("MetricsLoadTimeout() = %v, want %v", loaded.MetricsLoadTimeout(), 20*time.Second) + } + if loaded.MaxConcurrentFetches() != 100 { + t.Errorf("MaxConcurrentFetches() = %d, want %d", loaded.MaxConcurrentFetches(), 100) + } + if !loaded.PersistenceEnabled() { + t.Error("PersistenceEnabled() should be true") + } + + regions, profile := loaded.GetStartup() + if len(regions) != 2 || regions[0] != "us-east-1" || regions[1] != "us-west-2" { + t.Errorf("GetStartup() regions = %v, want [us-east-1, us-west-2]", regions) + } + if profile != "production" { + t.Errorf("GetStartup() profile = %q, want %q", profile, "production") + } +} + +func TestFileConfig_ApplyDefaults(t *testing.T) { + cfg := &FileConfig{} + cfg.applyDefaults() + + if cfg.Timeouts.AWSInit.Duration() != DefaultAWSInitTimeout { + t.Errorf("AWSInit = %v, want %v", cfg.Timeouts.AWSInit.Duration(), DefaultAWSInitTimeout) + } + if cfg.Concurrency.MaxFetches != DefaultMaxConcurrentFetches { + t.Errorf("MaxFetches = %d, want %d", cfg.Concurrency.MaxFetches, DefaultMaxConcurrentFetches) + } +} + +func TestFileConfig_ApplyDefaults_NegativeValues(t *testing.T) { + cfg := &FileConfig{ + Timeouts: TimeoutConfig{ + AWSInit: Duration(-5 * time.Second), + MultiRegionFetch: Duration(-1 * time.Minute), + }, + Concurrency: ConcurrencyConfig{ + MaxFetches: -10, + }, + } + cfg.applyDefaults() + + if cfg.Timeouts.AWSInit.Duration() != DefaultAWSInitTimeout { + t.Errorf("negative AWSInit should default, got %v", cfg.Timeouts.AWSInit.Duration()) + } + if cfg.Timeouts.MultiRegionFetch.Duration() != DefaultMultiRegionFetchTimeout { + t.Errorf("negative MultiRegionFetch should default, got %v", cfg.Timeouts.MultiRegionFetch.Duration()) + } + if cfg.Concurrency.MaxFetches != DefaultMaxConcurrentFetches { + t.Errorf("negative MaxFetches should default, got %d", cfg.Concurrency.MaxFetches) + } +} + +func TestFileConfig_SetStartup(t *testing.T) { + cfg := &FileConfig{} + + cfg.SetStartup([]string{"eu-west-1"}, "dev") + + regions, profile := cfg.GetStartup() + if len(regions) != 1 || regions[0] != "eu-west-1" { + t.Errorf("GetStartup() regions = %v, want [eu-west-1]", regions) + } + if profile != "dev" { + t.Errorf("GetStartup() profile = %q, want %q", profile, "dev") + } +} + +func TestFileConfig_Getters_ZeroValues(t *testing.T) { + cfg := &FileConfig{} + + // Getters should return defaults when values are zero + if cfg.AWSInitTimeout() != DefaultAWSInitTimeout { + t.Errorf("AWSInitTimeout() = %v, want %v", cfg.AWSInitTimeout(), DefaultAWSInitTimeout) + } + if cfg.MultiRegionFetchTimeout() != DefaultMultiRegionFetchTimeout { + t.Errorf("MultiRegionFetchTimeout() = %v, want %v", cfg.MultiRegionFetchTimeout(), DefaultMultiRegionFetchTimeout) + } + if cfg.TagSearchTimeout() != DefaultTagSearchTimeout { + t.Errorf("TagSearchTimeout() = %v, want %v", cfg.TagSearchTimeout(), DefaultTagSearchTimeout) + } + if cfg.MetricsLoadTimeout() != DefaultMetricsLoadTimeout { + t.Errorf("MetricsLoadTimeout() = %v, want %v", cfg.MetricsLoadTimeout(), DefaultMetricsLoadTimeout) + } + if cfg.MaxConcurrentFetches() != DefaultMaxConcurrentFetches { + t.Errorf("MaxConcurrentFetches() = %d, want %d", cfg.MaxConcurrentFetches(), DefaultMaxConcurrentFetches) + } +} + +func TestLoad_PartialConfig(t *testing.T) { + tmpDir := t.TempDir() + origHome := os.Getenv("HOME") + defer os.Setenv("HOME", origHome) + os.Setenv("HOME", tmpDir) + + // Create config dir + configDir := filepath.Join(tmpDir, ".config", "claws") + if err := os.MkdirAll(configDir, 0755); err != nil { + t.Fatalf("MkdirAll failed: %v", err) + } + + // Write partial config (only timeouts.aws_init) + configPath := filepath.Join(configDir, "config.yaml") + data := []byte("timeouts:\n aws_init: 15s\n") + if err := os.WriteFile(configPath, data, 0644); err != nil { + t.Fatalf("WriteFile failed: %v", err) + } + + // Load should fill in defaults for missing values + cfg, err := Load() + if err != nil { + t.Fatalf("Load failed: %v", err) + } + + if cfg.AWSInitTimeout() != 15*time.Second { + t.Errorf("AWSInitTimeout() = %v, want %v", cfg.AWSInitTimeout(), 15*time.Second) + } + // Other values should be defaults + if cfg.MultiRegionFetchTimeout() != DefaultMultiRegionFetchTimeout { + t.Errorf("MultiRegionFetchTimeout() = %v, want %v", cfg.MultiRegionFetchTimeout(), DefaultMultiRegionFetchTimeout) + } + if cfg.MaxConcurrentFetches() != DefaultMaxConcurrentFetches { + t.Errorf("MaxConcurrentFetches() = %d, want %d", cfg.MaxConcurrentFetches(), DefaultMaxConcurrentFetches) + } +} diff --git a/internal/config/lock.go b/internal/config/lock.go new file mode 100644 index 00000000..182132ca --- /dev/null +++ b/internal/config/lock.go @@ -0,0 +1,15 @@ +package config + +import "sync" + +func withRLock[T any](mu *sync.RWMutex, fn func() T) T { + mu.RLock() + defer mu.RUnlock() + return fn() +} + +func doWithLock(mu *sync.RWMutex, fn func()) { + mu.Lock() + defer mu.Unlock() + fn() +} diff --git a/internal/metrics/cloudwatch.go b/internal/metrics/cloudwatch.go index f519f787..63f4ca5b 100644 --- a/internal/metrics/cloudwatch.go +++ b/internal/metrics/cloudwatch.go @@ -11,12 +11,12 @@ import ( "github.com/aws/aws-sdk-go-v2/service/cloudwatch/types" appaws "github.com/clawscli/claws/internal/aws" + "github.com/clawscli/claws/internal/config" "github.com/clawscli/claws/internal/render" ) const ( metricPeriod = 60 - metricWindow = 15 * time.Minute maxQueriesPerRequest = 500 ) @@ -39,7 +39,7 @@ func (f *Fetcher) Fetch(ctx context.Context, resourceIDs []string, spec *render. queries := f.buildQueries(resourceIDs, spec) endTime := time.Now().Truncate(time.Minute) - startTime := endTime.Add(-metricWindow) + startTime := endTime.Add(-config.File().MetricsWindow()) data := NewMetricData(spec) diff --git a/internal/view/resource_browser.go b/internal/view/resource_browser.go index 4bc16449..609fbad3 100644 --- a/internal/view/resource_browser.go +++ b/internal/view/resource_browser.go @@ -24,10 +24,7 @@ import ( // ResourceBrowser displays resources of a specific type -const ( - logTokenMaxLen = 20 - metricsLoadTimeout = 30 * time.Second -) +const logTokenMaxLen = 20 // resourceBrowserStyles holds cached lipgloss styles for performance type resourceBrowserStyles struct { diff --git a/internal/view/resource_browser_fetch.go b/internal/view/resource_browser_fetch.go index 2afe9307..f3375d11 100644 --- a/internal/view/resource_browser_fetch.go +++ b/internal/view/resource_browser_fetch.go @@ -16,11 +16,6 @@ import ( "github.com/clawscli/claws/internal/render" ) -const ( - multiRegionFetchTimeout = 30 * time.Second - maxConcurrentFetches = 50 // TODO: make configurable via config file -) - type listResourcesResult struct { resources []dao.Resource nextToken string @@ -72,11 +67,11 @@ func fetchParallel[K comparable]( fetch func(context.Context, K) ([]dao.Resource, string, error), formatError func(K, error) string, ) parallelFetchResult[K] { - ctx, cancel := context.WithTimeout(ctx, multiRegionFetchTimeout) + ctx, cancel := context.WithTimeout(ctx, config.File().MultiRegionFetchTimeout()) defer cancel() results := make(chan parallelFetchItem[K], len(keys)) - sem := make(chan struct{}, maxConcurrentFetches) + sem := make(chan struct{}, config.File().MaxConcurrentFetches()) var wg sync.WaitGroup for _, key := range keys { diff --git a/internal/view/resource_browser_metrics.go b/internal/view/resource_browser_metrics.go index 45e07391..eb797389 100644 --- a/internal/view/resource_browser_metrics.go +++ b/internal/view/resource_browser_metrics.go @@ -6,6 +6,7 @@ import ( tea "charm.land/bubbletea/v2" "github.com/clawscli/claws/internal/aws" + "github.com/clawscli/claws/internal/config" "github.com/clawscli/claws/internal/dao" "github.com/clawscli/claws/internal/metrics" "github.com/clawscli/claws/internal/render" @@ -44,7 +45,7 @@ func (r *ResourceBrowser) loadMetricsCmd() tea.Cmd { return nil } - ctx, cancel := context.WithTimeout(baseCtx, metricsLoadTimeout) + ctx, cancel := context.WithTimeout(baseCtx, config.File().MetricsLoadTimeout()) defer cancel() byRegion := make(map[string][]resourceInfo) diff --git a/internal/view/tag_search_view.go b/internal/view/tag_search_view.go index bd14a9db..fba34a59 100644 --- a/internal/view/tag_search_view.go +++ b/internal/view/tag_search_view.go @@ -6,7 +6,6 @@ import ( "sort" "strings" "sync" - "time" "charm.land/bubbles/v2/spinner" "charm.land/bubbles/v2/table" @@ -24,10 +23,7 @@ import ( "github.com/clawscli/claws/internal/ui" ) -const ( - tagSearchTimeout = 30 * time.Second - tagSearchLimit = 100 // AWS Resource Groups Tagging API max per request -) +const tagSearchLimit = 100 type taggedARN struct { ARN *aws.ARN @@ -131,7 +127,7 @@ func (v *TagSearchView) fetchTaggedResources(regions []string, existingTokens ma err error } - ctx, cancel := context.WithTimeout(v.ctx, tagSearchTimeout) + ctx, cancel := context.WithTimeout(v.ctx, config.File().TagSearchTimeout()) defer cancel() results := make(chan regionResult, len(regions)) From 5149ce57ed24bf67e554d6451f2a4084a9a75b8e Mon Sep 17 00:00:00 2001 From: yimsk Date: Sat, 3 Jan 2026 01:01:29 +0900 Subject: [PATCH 5/5] Release v0.6.0 (#77) (#81) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Align naming conventions with AWS CLI (#71) * Align registry names with AWS CLI conventions Rename 7 services to match AWS CLI naming: - computeoptimizer → compute-optimizer - cognito → cognito-idp - config → configservice - costexplorer → ce - eventbridge → events - macie → macie2 - sfn → stepfunctions Old names preserved as aliases for backward compatibility. Closes #65 * Fix pseudo-ARN prefix to match registry service name (ce) * Add comment explaining pseudo-ARN format for Cost Explorer * Rename directories to align with AWS CLI conventions - Service dirs: costexplorer→ce, eventbridge→events, sfn→stepfunctions, cognito→cognito-idp, config→configservice, macie→macie2, computeoptimizer→compute-optimizer, servicequotas→service-quotas, licensemanager→license-manager, networkfirewall→network-firewall - Resource dirs: hyphenate compound names (e.g., loggroups→log-groups) - Update imports and ARN mappings Closes #65 * Rename bedrock dirs to kebab-case for AWS CLI consistency * Fix DAO error strings and deduplicate client helpers * Fix error strings and deduplicate client helpers * Fix package names to match directory names (events, stepfunctions) * Add develop branch to CI triggers * feat: propagate ALL_PROXY to HTTP_PROXY/HTTPS_PROXY (#74) * feat: support ALL_PROXY environment variable (#69) * feat: propagate ALL_PROXY to HTTP_PROXY and configure NO_PROXY Extend proxy support based on issue #69 feedback: - Propagate ALL_PROXY to both HTTP_PROXY and HTTPS_PROXY - Auto-configure NO_PROXY for AWS credential endpoints: - 169.254.169.254 (EC2 IMDS) - 169.254.170.2 (ECS Task Role) - 169.254.170.23 (EKS Pod Identity) - Preserve existing NO_PROXY entries, only add missing endpoints Refs #69, #67 * fix: default NO_PROXY to IMDS only and remove proxy value from logs - NO_PROXY now only includes EC2 IMDS (169.254.169.254) by default - ECS/EKS endpoints can be added via config (TODO #67) - Remove proxy URL from log output to avoid credential exposure * refactor: simplify splitNoProxy and use tagged switch in parseFlags * refactor: separate NO_PROXY config from ALL_PROXY propagation configureNoProxy() now runs independently when any proxy is set, not tied to ALL_PROXY propagation. Cleaner separation of concerns. * refactor: remove lowercase env var support for simplicity * refactor: remove NO_PROXY auto-configuration * refactor: skip proxy propagation for --help/--version and cleanup test * test: add test case for lowercase all_proxy not supported * feat: add alias completion in command mode (#76) * feat: add alias completion in command mode (#70) Include aliases in GetSuggestions() so :cost+Tab suggests cost-explorer. Self-referential aliases (sfn→sfn) excluded from suggestions. * refactor: use prefix+fuzzy for diff completion * refactor: use CutPrefix and cache GetAliases * refactor: extract match functions to shared module - Move fuzzyMatch and matchNamesWithFallback to match.go - Make :tag/:tags clear behavior explicit - Sort suggestions alphabetically (services + aliases) * refactor: cache GetAliasesForService and sort match results * refactor: unify command parser pattern for sort and login * test: add cache concurrency tests and improve comments * refactor: use CutPrefix in ecs tasks render * refactor: use CutSuffix in parseNumericValue * fix: quote profile name in error message for clarity * fix: lowercase pattern in fuzzyMatch for case insensitivity * chore: remove .claude symlink * chore: add .claude to gitignore * fix: lowercase pattern in matchNamesWithFallback prefix check * fix: return defensive copy from alias cache methods