diff --git a/.circleci/config.yml b/.circleci/config.yml
index 781eae369..b4a62107a 100644
--- a/.circleci/config.yml
+++ b/.circleci/config.yml
@@ -60,13 +60,13 @@ jobs:
chmod +x komiser_windows_amd64.exe komiser_darwin_amd64 komiser_linux_amd64
- run:
name: Push Linux binary
- command: aws s3 cp komiser_linux_amd64 s3://komiser/2.1.0/linux/komiser --grants read=uri=http://acs.amazonaws.com/groups/global/AllUsers
+ command: aws s3 cp komiser_linux_amd64 s3://komiser/2.2.0/linux/komiser --grants read=uri=http://acs.amazonaws.com/groups/global/AllUsers
- run:
name: Push Windows binary
- command: aws s3 cp komiser_windows_amd64.exe s3://komiser/2.1.0/windows/komiser --grants read=uri=http://acs.amazonaws.com/groups/global/AllUsers
+ command: aws s3 cp komiser_windows_amd64.exe s3://komiser/2.2.0/windows/komiser --grants read=uri=http://acs.amazonaws.com/groups/global/AllUsers
- run:
name: Push Mac OS X binary
- command: aws s3 cp komiser_darwin_amd64 s3://komiser/2.1.0/osx/komiser --grants read=uri=http://acs.amazonaws.com/groups/global/AllUsers
+ command: aws s3 cp komiser_darwin_amd64 s3://komiser/2.2.0/osx/komiser --grants read=uri=http://acs.amazonaws.com/groups/global/AllUsers
- run:
name: Upload IAM policy
command: aws s3 cp policy.json s3://komiser/policy.json --grants read=uri=http://acs.amazonaws.com/groups/global/AllUsers
diff --git a/Dockerfile b/Dockerfile
index 836b81aa5..a7cc2bb0f 100644
--- a/Dockerfile
+++ b/Dockerfile
@@ -1,7 +1,7 @@
FROM alpine:3.9.4
MAINTAINER mlabouardy
-ENV VERSION 2.1.0
+ENV VERSION 2.2.0
ENV PORT 3000
ENV DURATION 30
@@ -11,4 +11,4 @@ RUN curl -L https://s3.us-east-1.amazonaws.com/komiser/$VERSION/linux/komiser -o
mkdir /lib64 && ln -s /lib/libc.musl-x86_64.so.1 /lib64/ld-linux-x86-64.so.2
EXPOSE $PORT
-ENTRYPOINT komiser start --port $PORT
+ENTRYPOINT ["komiser", "start"]
diff --git a/README.md b/README.md
index a582fc91e..0f82893fa 100644
--- a/README.md
+++ b/README.md
@@ -28,24 +28,24 @@ Stay under budget by uncovering hidden costs, monitoring increases in spend, and
## Download
-Below are the available downloads for the latest version of Komiser (2.1.0). Please download the proper package for your operating system and architecture.
+Below are the available downloads for the latest version of Komiser (2.2.0). Please download the proper package for your operating system and architecture.
### Linux:
```
-wget https://cli.komiser.io/2.1.0/linux/komiser
+wget https://cli.komiser.io/2.2.0/linux/komiser
```
### Windows:
```
-wget https://cli.komiser.io/2.1.0/windows/komiser
+wget https://cli.komiser.io/2.2.0/windows/komiser
```
### Mac OS X:
```
-wget https://cli.komiser.io/2.1.0/osx/komiser
+wget https://cli.komiser.io/2.2.0/osx/komiser
```
Docker for Mac is best installed with Homebrew:
@@ -60,7 +60,7 @@ _Note_: make sure to add the execution permission to Komiser `chmod +x komiser`
### Docker:
```
-docker run -d -p 3000:3000 -e AWS_ACCESS_KEY_ID="" -e AWS_SECRET_ACCESS_KEY="" -e AWS_DEFAULT_REGION="" --name komiser mlabouardy/komiser:2.1.0
+docker run -d -p 3000:3000 -e AWS_ACCESS_KEY_ID="" -e AWS_SECRET_ACCESS_KEY="" -e AWS_DEFAULT_REGION="" --name komiser mlabouardy/komiser:2.2.0
```
## How to use
@@ -100,6 +100,38 @@ komiser start --port 3000 --redis localhost:6379 --duration 30
+#### Multiple AWS Accounts Support
+
+Komiser support multiple AWS accounts through named profiles that are stored in the `config` and `credentials files`. You can configure additional profiles by using `aws configure` with the `--profile` option, or by adding entries to the `config` and `credentials` files.
+
+The following example shows a credentials file with 3 profiles (production, staging & sandbox accounts):
+
+```
+[Production]
+aws_access_key_id=
+aws_secret_access_key=
+
+[Staging]
+aws_access_key_id=
+aws_secret_access_key=
+
+[Sandbox]
+aws_access_key_id=
+aws_secret_access_key=
+```
+
+To enable multiple AWS accounts feature, add the --multiple option to Komiser:
+
+```
+komiser start --port 3000 --redis localhost:6379 --duration 30 --multiple
+```
+
+* If you point your browser to http://localhost:3000, you should be able to see your accounts:
+
+
+
+
+
### GCP
* Create a service account with *Viewer* permission, see [Creating and managing service accounts](https://cloud.google.com/iam/docs/creating-managing-service-accounts) docs.
@@ -153,6 +185,7 @@ komiser start [OPTIONS]
--duration value, -d value Cache expiration time (default: 30 minutes)
--redis value, -r value Redis server (localhost:6379)
--dataset value, -ds value BigQuery dataset name (project-id.dataset-name.table-name)
+ --multiple, -m Enable multiple AWS accounts feature
```
## Configuring Credentials
diff --git a/dashboard/src/app/app.component.html b/dashboard/src/app/app.component.html
index 7671c8b94..ae627b7ee 100644
--- a/dashboard/src/app/app.component.html
+++ b/dashboard/src/app/app.component.html
@@ -58,7 +58,7 @@
-
+
See all notifications
@@ -138,11 +138,19 @@
bottom: 0vh;
display: block;
width: 100%;">
-
- Select Cloud Provider
+
+ Cloud Provider:
{{provider.label}}
+ 1" style="margin-top:10px;">
+
+ AWS Account:
+
+ {{profile}}
+
+
+
diff --git a/dashboard/src/app/app.component.ts b/dashboard/src/app/app.component.ts
index a463f696d..0379fe349 100644
--- a/dashboard/src/app/app.component.ts
+++ b/dashboard/src/app/app.component.ts
@@ -4,6 +4,7 @@ import { GcpService } from './gcp.service';
import { StoreService } from './store.service';
import { not } from '@angular/compiler/src/output/output_ast';
import { Subscription } from 'rxjs';
+import { Subject } from "rxjs/Subject";
import * as moment from 'moment';
declare var ga: Function;
@@ -17,10 +18,12 @@ export class AppComponent implements OnDestroy {
public accountName: string = 'Username';
public redAlarms: number;
+ public profiles: Array = [];
+ public currentProfile: string;
public notifications: Array = [];
public _subscription: Subscription;
public currentProvider: any;
- public availableProviders : Array = [
+ public availableProviders: Array = [
{
label: 'Amazon Web Services',
value: 'aws'
@@ -29,13 +32,13 @@ export class AppComponent implements OnDestroy {
label: 'Google Cloud Platform',
value: 'gcp'
}
- ]
+ ];
private _storeService: StoreService;
private providers: Map = new Map();
- constructor(private awsService: AwsService, private gcpService: GcpService, private storeService: StoreService){
+ constructor(private awsService: AwsService, private gcpService: GcpService, private storeService: StoreService) {
this.providers['aws'] = {
label: 'Amazon Web Services',
@@ -49,6 +52,26 @@ export class AppComponent implements OnDestroy {
logo: 'https://cdn.komiser.io/images/gcp.png'
};
+ //if (this.storeService.getProvider() == 'aws') {
+ if (localStorage.getItem('profile')) {
+ this.currentProfile = localStorage.getItem('profile');
+ } else {
+ this.currentProfile = 'default';
+ localStorage.setItem('profile', this.currentProfile);
+ }
+
+ this.awsService.getProfiles().subscribe(profiles => {
+ this.profiles = profiles;
+ if (this.profiles.length > 0 && this.profiles.indexOf(this.currentProfile) == -1) {
+ this.currentProfile = this.profiles[0];
+ localStorage.setItem('profile', this.currentProfile);
+ }
+ }, err => {
+ this.profiles = [];
+ })
+ // }
+
+
this.currentProvider = this.providers[this.storeService.getProvider()];
this.storeService.onProviderChanged(this.storeService.getProvider());
@@ -64,22 +87,21 @@ export class AppComponent implements OnDestroy {
})
}
- private getAccountName(){
- console.log(this.currentProvider);
- if (this.currentProvider.value == 'aws'){
+ private getAccountName() {
+ if (this.currentProvider.value == 'aws') {
this.awsService.getAccountName().subscribe(data => {
this.accountName = data.username;
}, err => {
this.accountName = 'Username';
});
-
+
this.awsService.getCloudwatchAlarms().subscribe(data => {
this.redAlarms = data.ALARM;
}, err => {
this.redAlarms = 0;
});
} else {
- this.redAlarms = 0;
+ this.redAlarms = 0;
this.gcpService.getProjects().subscribe(data => {
this.accountName = data[0].name;
@@ -90,17 +112,24 @@ export class AppComponent implements OnDestroy {
}
ngOnDestroy() {
- this._subscription.unsubscribe();
- }
-
- public calcMoment(timestamp){
- return moment(timestamp).fromNow();
- }
-
- public onCloudProviderSelected(provider){
- this.currentProvider = this.providers[provider];
- this._storeService.onProviderChanged(provider);
- this.getAccountName();
- }
+ this._subscription.unsubscribe();
+ }
+
+ public calcMoment(timestamp) {
+ return moment(timestamp).fromNow();
+ }
+
+ public onCloudProviderSelected(provider) {
+ this.currentProvider = this.providers[provider];
+ this._storeService.onProviderChanged(provider);
+ this.getAccountName();
+ }
+
+ public onProfileSelected(profile) {
+ this.currentProfile = profile;
+ localStorage.setItem('profile', this.currentProfile);
+ this._storeService.onProfileChanged(profile);
+ this.getAccountName();
+ }
}
diff --git a/dashboard/src/app/app.module.ts b/dashboard/src/app/app.module.ts
index 1980175e6..6cafa2965 100644
--- a/dashboard/src/app/app.module.ts
+++ b/dashboard/src/app/app.module.ts
@@ -37,6 +37,7 @@ import { AwsLimitsComponent } from './limits/aws/aws.component';
import { GcpLimitsComponent } from './limits/gcp/gcp.component';
import { AwsProfileComponent } from './profile/aws/aws.component';
import { GcpProfileComponent } from './profile/gcp/gcp.component';
+import { NotificationsComponent } from './notifications/notifications.component';
@@ -76,6 +77,11 @@ const appRoutes: Routes = [
component: LimitsComponent,
data: { title: 'Service Limits Checks - Komiser' }
},
+ {
+ path: 'notifications',
+ component: NotificationsComponent,
+ data: { title: 'Notifications - Komiser' }
+ },
{ path: '',
component: DashboardComponent,
data: { title: 'Dashboard - Komiser' }
@@ -108,7 +114,8 @@ const appRoutes: Routes = [
AwsLimitsComponent,
GcpLimitsComponent,
AwsProfileComponent,
- GcpProfileComponent
+ GcpProfileComponent,
+ NotificationsComponent
],
imports: [
RouterModule.forRoot(
diff --git a/dashboard/src/app/aws.service.ts b/dashboard/src/app/aws.service.ts
index 096b8be5a..e67ad5254 100644
--- a/dashboard/src/app/aws.service.ts
+++ b/dashboard/src/app/aws.service.ts
@@ -1,5 +1,5 @@
import { Injectable } from '@angular/core';
-import { Http } from '@angular/http';
+import { Http, Headers } from '@angular/http';
import 'rxjs/add/operator/map';
import { Observable } from "rxjs/Rx";
import { StoreService } from './store.service';
@@ -11,9 +11,23 @@ export class AwsService {
constructor(private http: Http, private storeService: StoreService) { }
+ public getProfiles(){
+ return this.http
+ .get(`${this.BASE_URL}/profiles`)
+ .map(res => {
+ return res.json()
+ })
+ .catch(err => {
+ let payload = JSON.parse(err._body)
+ if (payload && payload.error)
+ this.storeService.add(payload.error);
+ return Observable.throw(err.json().error)
+ })
+ }
+
public getCurrentCost(){
return this.http
- .get(`${this.BASE_URL}/cost/current`)
+ .get(`${this.BASE_URL}/cost/current`, {headers: this.getHeaders()})
.map(res => {
return res.json()
})
@@ -27,7 +41,7 @@ export class AwsService {
public getCostAndUsage(){
return this.http
- .get(`${this.BASE_URL}/cost/history`)
+ .get(`${this.BASE_URL}/cost/history`, {headers: this.getHeaders()})
.map(res => {
return res.json()
})
@@ -41,7 +55,7 @@ export class AwsService {
public getIAMUsers(){
return this.http
- .get(`${this.BASE_URL}/iam/users`)
+ .get(`${this.BASE_URL}/iam/users`, {headers: this.getHeaders()})
.map(res => {
return res.json()
})
@@ -55,7 +69,7 @@ export class AwsService {
public getInstancesPerRegion(){
return this.http
- .get(`${this.BASE_URL}/ec2/regions`)
+ .get(`${this.BASE_URL}/ec2/regions`, {headers: this.getHeaders()})
.map(res => {
return res.json()
})
@@ -69,7 +83,7 @@ export class AwsService {
public getUsedRegions(){
return this.http
- .get(`${this.BASE_URL}/resources/regions`)
+ .get(`${this.BASE_URL}/resources/regions`, {headers: this.getHeaders()})
.map(res => {
return res.json()
})
@@ -83,7 +97,7 @@ export class AwsService {
public getCloudwatchAlarms(){
return this.http
- .get(`${this.BASE_URL}/cloudwatch/alarms`)
+ .get(`${this.BASE_URL}/cloudwatch/alarms`, {headers: this.getHeaders()})
.map(res => {
return res.json()
})
@@ -97,7 +111,7 @@ export class AwsService {
public getLambdaFunctions(){
return this.http
- .get(`${this.BASE_URL}/lambda/functions`)
+ .get(`${this.BASE_URL}/lambda/functions`, {headers: this.getHeaders()})
.map(res => {
return res.json()
})
@@ -111,7 +125,7 @@ export class AwsService {
public getLambdaInvocationMetrics(){
return this.http
- .get(`${this.BASE_URL}/lambda/invocations`)
+ .get(`${this.BASE_URL}/lambda/invocations`, {headers: this.getHeaders()})
.map(res => {
return res.json()
})
@@ -125,7 +139,7 @@ export class AwsService {
public getAccountName(){
return this.http
- .get(`${this.BASE_URL}/iam/account`)
+ .get(`${this.BASE_URL}/iam/account`, {headers: this.getHeaders()})
.map(res => {
return res.json()
})
@@ -139,7 +153,7 @@ export class AwsService {
public getNumberOfS3Buckets(){
return this.http
- .get(`${this.BASE_URL}/s3/buckets`)
+ .get(`${this.BASE_URL}/s3/buckets`, {headers: this.getHeaders()})
.map(res => {
return res.json()
})
@@ -153,7 +167,7 @@ export class AwsService {
public getBucketObjects(){
return this.http
- .get(`${this.BASE_URL}/s3/objects`)
+ .get(`${this.BASE_URL}/s3/objects`, {headers: this.getHeaders()})
.map(res => {
return res.json()
})
@@ -167,7 +181,7 @@ export class AwsService {
public getBucketSize(){
return this.http
- .get(`${this.BASE_URL}/s3/size`)
+ .get(`${this.BASE_URL}/s3/size`, {headers: this.getHeaders()})
.map(res => {
return res.json()
})
@@ -181,7 +195,7 @@ export class AwsService {
public getEBS(){
return this.http
- .get(`${this.BASE_URL}/ebs`)
+ .get(`${this.BASE_URL}/ebs`, {headers: this.getHeaders()})
.map(res => {
return res.json()
})
@@ -195,7 +209,7 @@ export class AwsService {
public getRDSInstances(){
return this.http
- .get(`${this.BASE_URL}/rds/instances`)
+ .get(`${this.BASE_URL}/rds/instances`, {headers: this.getHeaders()})
.map(res => {
return res.json()
})
@@ -209,7 +223,7 @@ export class AwsService {
public getDynamoDBTables(){
return this.http
- .get(`${this.BASE_URL}/dynamodb/tables`)
+ .get(`${this.BASE_URL}/dynamodb/tables`, {headers: this.getHeaders()})
.map(res => {
return res.json()
})
@@ -223,7 +237,7 @@ export class AwsService {
public getElasticacheClusters(){
return this.http
- .get(`${this.BASE_URL}/elasticache/clusters`)
+ .get(`${this.BASE_URL}/elasticache/clusters`, {headers: this.getHeaders()})
.map(res => {
return res.json()
})
@@ -237,7 +251,7 @@ export class AwsService {
public getVirtualPrivateClouds(){
return this.http
- .get(`${this.BASE_URL}/vpc`)
+ .get(`${this.BASE_URL}/vpc`, {headers: this.getHeaders()})
.map(res => {
return res.json()
})
@@ -251,7 +265,7 @@ export class AwsService {
public getAccessControlLists(){
return this.http
- .get(`${this.BASE_URL}/acl`)
+ .get(`${this.BASE_URL}/acl`, {headers: this.getHeaders()})
.map(res => {
return res.json()
})
@@ -265,7 +279,7 @@ export class AwsService {
public getRouteTables(){
return this.http
- .get(`${this.BASE_URL}/route_tables`)
+ .get(`${this.BASE_URL}/route_tables`, {headers: this.getHeaders()})
.map(res => {
return res.json()
})
@@ -279,7 +293,7 @@ export class AwsService {
public getCloudFrontRequests(){
return this.http
- .get(`${this.BASE_URL}/cloudfront/requests`)
+ .get(`${this.BASE_URL}/cloudfront/requests`, {headers: this.getHeaders()})
.map(res => {
return res.json()
})
@@ -293,7 +307,7 @@ export class AwsService {
public getCloudFrontDistributions(){
return this.http
- .get(`${this.BASE_URL}/cloudfront/distributions`)
+ .get(`${this.BASE_URL}/cloudfront/distributions`, {headers: this.getHeaders()})
.map(res => {
return res.json()
})
@@ -307,7 +321,7 @@ export class AwsService {
public getApiGatewayRequests(){
return this.http
- .get(`${this.BASE_URL}/apigateway/requests`)
+ .get(`${this.BASE_URL}/apigateway/requests`, {headers: this.getHeaders()})
.map(res => {
return res.json()
})
@@ -321,7 +335,7 @@ export class AwsService {
public getApiGatewayRestAPIs(){
return this.http
- .get(`${this.BASE_URL}/apigateway/apis`)
+ .get(`${this.BASE_URL}/apigateway/apis`, {headers: this.getHeaders()})
.map(res => {
return res.json()
})
@@ -335,7 +349,7 @@ export class AwsService {
public getELBRequests(){
return this.http
- .get(`${this.BASE_URL}/elb/requests`)
+ .get(`${this.BASE_URL}/elb/requests`, {headers: this.getHeaders()})
.map(res => {
return res.json()
})
@@ -349,7 +363,7 @@ export class AwsService {
public getELBFamily(){
return this.http
- .get(`${this.BASE_URL}/elb/family`)
+ .get(`${this.BASE_URL}/elb/family`, {headers: this.getHeaders()})
.map(res => {
return res.json()
})
@@ -363,7 +377,7 @@ export class AwsService {
public getKMSKeys(){
return this.http
- .get(`${this.BASE_URL}/kms`)
+ .get(`${this.BASE_URL}/kms`, {headers: this.getHeaders()})
.map(res => {
return res.json()
})
@@ -377,7 +391,7 @@ export class AwsService {
public getSecurityGroups(){
return this.http
- .get(`${this.BASE_URL}/security_groups`)
+ .get(`${this.BASE_URL}/security_groups`, {headers: this.getHeaders()})
.map(res => {
return res.json()
})
@@ -391,7 +405,7 @@ export class AwsService {
public getKeyPairs(){
return this.http
- .get(`${this.BASE_URL}/key_pairs`)
+ .get(`${this.BASE_URL}/key_pairs`, {headers: this.getHeaders()})
.map(res => {
return res.json()
})
@@ -405,7 +419,7 @@ export class AwsService {
public getACMListCertificates(){
return this.http
- .get(`${this.BASE_URL}/acm/certificates`)
+ .get(`${this.BASE_URL}/acm/certificates`, {headers: this.getHeaders()})
.map(res => {
return res.json()
})
@@ -419,7 +433,7 @@ export class AwsService {
public getACMExpiredCertificates(){
return this.http
- .get(`${this.BASE_URL}/acm/expired`)
+ .get(`${this.BASE_URL}/acm/expired`, {headers: this.getHeaders()})
.map(res => {
return res.json()
})
@@ -433,7 +447,7 @@ export class AwsService {
public getUnrestrictedSecurityGroups(){
return this.http
- .get(`${this.BASE_URL}/security_groups/unrestricted`)
+ .get(`${this.BASE_URL}/security_groups/unrestricted`, {headers: this.getHeaders()})
.map(res => {
return res.json()
})
@@ -447,7 +461,7 @@ export class AwsService {
public getSQSPublishedMessagesMetrics(){
return this.http
- .get(`${this.BASE_URL}/sqs/messages`)
+ .get(`${this.BASE_URL}/sqs/messages`, {headers: this.getHeaders()})
.map(res => {
return res.json()
})
@@ -461,7 +475,7 @@ export class AwsService {
public getSQSQueues(){
return this.http
- .get(`${this.BASE_URL}/sqs/queues`)
+ .get(`${this.BASE_URL}/sqs/queues`, {headers: this.getHeaders()})
.map(res => {
return res.json()
})
@@ -475,7 +489,7 @@ export class AwsService {
public getSNSTopics(){
return this.http
- .get(`${this.BASE_URL}/sns/topics`)
+ .get(`${this.BASE_URL}/sns/topics`, {headers: this.getHeaders()})
.map(res => {
return res.json()
})
@@ -489,7 +503,7 @@ export class AwsService {
public getActiveMQBrokers(){
return this.http
- .get(`${this.BASE_URL}/mq/brokers`)
+ .get(`${this.BASE_URL}/mq/brokers`, {headers: this.getHeaders()})
.map(res => {
return res.json()
})
@@ -503,7 +517,7 @@ export class AwsService {
public getKinesisStreams(){
return this.http
- .get(`${this.BASE_URL}/kinesis/streams`)
+ .get(`${this.BASE_URL}/kinesis/streams`, {headers: this.getHeaders()})
.map(res => {
return res.json()
})
@@ -517,7 +531,7 @@ export class AwsService {
public getKinesisShards(){
return this.http
- .get(`${this.BASE_URL}/kinesis/shards`)
+ .get(`${this.BASE_URL}/kinesis/shards`, {headers: this.getHeaders()})
.map(res => {
return res.json()
})
@@ -531,7 +545,7 @@ export class AwsService {
public getGlueCrawlers(){
return this.http
- .get(`${this.BASE_URL}/glue/crawlers`)
+ .get(`${this.BASE_URL}/glue/crawlers`, {headers: this.getHeaders()})
.map(res => {
return res.json()
})
@@ -545,7 +559,7 @@ export class AwsService {
public getGlueJobs(){
return this.http
- .get(`${this.BASE_URL}/glue/jobs`)
+ .get(`${this.BASE_URL}/glue/jobs`, {headers: this.getHeaders()})
.map(res => {
return res.json()
})
@@ -559,7 +573,7 @@ export class AwsService {
public getDataPipelines(){
return this.http
- .get(`${this.BASE_URL}/datapipeline/pipelines`)
+ .get(`${this.BASE_URL}/datapipeline/pipelines`, {headers: this.getHeaders()})
.map(res => {
return res.json()
})
@@ -573,7 +587,7 @@ export class AwsService {
public getESDomains(){
return this.http
- .get(`${this.BASE_URL}/es/domains`)
+ .get(`${this.BASE_URL}/es/domains`, {headers: this.getHeaders()})
.map(res => {
return res.json()
})
@@ -587,7 +601,7 @@ export class AwsService {
public getSWFDomains(){
return this.http
- .get(`${this.BASE_URL}/swf/domains`)
+ .get(`${this.BASE_URL}/swf/domains`, {headers: this.getHeaders()})
.map(res => {
return res.json()
})
@@ -601,7 +615,7 @@ export class AwsService {
public getOpenSupportTickets(){
return this.http
- .get(`${this.BASE_URL}/support/open`)
+ .get(`${this.BASE_URL}/support/open`, {headers: this.getHeaders()})
.map(res => {
return res.json()
})
@@ -615,7 +629,7 @@ export class AwsService {
public getSupportTicketsHistory(){
return this.http
- .get(`${this.BASE_URL}/support/history`)
+ .get(`${this.BASE_URL}/support/history`, {headers: this.getHeaders()})
.map(res => {
return res.json()
})
@@ -629,7 +643,7 @@ export class AwsService {
public getECS(){
return this.http
- .get(`${this.BASE_URL}/ecs`)
+ .get(`${this.BASE_URL}/ecs`, {headers: this.getHeaders()})
.map(res => {
return res.json()
})
@@ -643,7 +657,7 @@ export class AwsService {
public getRoute53Records(){
return this.http
- .get(`${this.BASE_URL}/route53/records`)
+ .get(`${this.BASE_URL}/route53/records`, {headers: this.getHeaders()})
.map(res => {
return res.json()
})
@@ -657,7 +671,7 @@ export class AwsService {
public getRoute53Zones(){
return this.http
- .get(`${this.BASE_URL}/route53/zones`)
+ .get(`${this.BASE_URL}/route53/zones`, {headers: this.getHeaders()})
.map(res => {
return res.json()
})
@@ -671,7 +685,7 @@ export class AwsService {
public getLogsVolume(){
return this.http
- .get(`${this.BASE_URL}/logs/volume`)
+ .get(`${this.BASE_URL}/logs/volume`, {headers: this.getHeaders()})
.map(res => {
return res.json()
})
@@ -685,7 +699,7 @@ export class AwsService {
public getConsoleLoginEvents(){
return this.http
- .get(`${this.BASE_URL}/cloudtrail/sign_in_event`)
+ .get(`${this.BASE_URL}/cloudtrail/sign_in_event`, {headers: this.getHeaders()})
.map(res => {
return res.json()
})
@@ -699,7 +713,7 @@ export class AwsService {
public getLambdaErrors(){
return this.http
- .get(`${this.BASE_URL}/lambda/errors`)
+ .get(`${this.BASE_URL}/lambda/errors`, {headers: this.getHeaders()})
.map(res => {
return res.json()
})
@@ -713,7 +727,7 @@ export class AwsService {
public getReservedInstances(){
return this.http
- .get(`${this.BASE_URL}/ec2/reserved`)
+ .get(`${this.BASE_URL}/ec2/reserved`, {headers: this.getHeaders()})
.map(res => {
return res.json()
})
@@ -727,7 +741,7 @@ export class AwsService {
public getScheduledInstances(){
return this.http
- .get(`${this.BASE_URL}/ec2/scheduled`)
+ .get(`${this.BASE_URL}/ec2/scheduled`, {headers: this.getHeaders()})
.map(res => {
return res.json()
})
@@ -741,7 +755,7 @@ export class AwsService {
public getSpotInstances(){
return this.http
- .get(`${this.BASE_URL}/ec2/spot`)
+ .get(`${this.BASE_URL}/ec2/spot`, {headers: this.getHeaders()})
.map(res => {
return res.json()
})
@@ -755,7 +769,7 @@ export class AwsService {
public getCostPerInstanceType(){
return this.http
- .get(`${this.BASE_URL}/cost/instance_type`)
+ .get(`${this.BASE_URL}/cost/instance_type`, {headers: this.getHeaders()})
.map(res => {
return res.json()
})
@@ -769,7 +783,7 @@ export class AwsService {
public getEKSClusters(){
return this.http
- .get(`${this.BASE_URL}/eks/clusters`)
+ .get(`${this.BASE_URL}/eks/clusters`, {headers: this.getHeaders()})
.map(res => {
return res.json()
})
@@ -783,7 +797,7 @@ export class AwsService {
public getConsoleLoginSourceIps(){
return this.http
- .get(`${this.BASE_URL}/cloudtrail/source_ip`)
+ .get(`${this.BASE_URL}/cloudtrail/source_ip`, {headers: this.getHeaders()})
.map(res => {
return res.json()
})
@@ -797,7 +811,7 @@ export class AwsService {
public getLogsRetentionPeriod(){
return this.http
- .get(`${this.BASE_URL}/logs/retention`)
+ .get(`${this.BASE_URL}/logs/retention`, {headers: this.getHeaders()})
.map(res => {
return res.json()
})
@@ -811,7 +825,7 @@ export class AwsService {
public getNatGatewayTraffic(){
return this.http
- .get(`${this.BASE_URL}/nat/traffic`)
+ .get(`${this.BASE_URL}/nat/traffic`, {headers: this.getHeaders()})
.map(res => {
return res.json()
})
@@ -825,7 +839,7 @@ export class AwsService {
public getOrganization(){
return this.http
- .get(`${this.BASE_URL}/iam/organization`)
+ .get(`${this.BASE_URL}/iam/organization`, {headers: this.getHeaders()})
.map(res => {
return res.json()
})
@@ -839,7 +853,7 @@ export class AwsService {
public getServiceLimits(){
return this.http
- .get(`${this.BASE_URL}/service/limits`)
+ .get(`${this.BASE_URL}/service/limits`, {headers: this.getHeaders()})
.map(res => {
return res.json()
})
@@ -853,7 +867,7 @@ export class AwsService {
public getEmptyBuckets(){
return this.http
- .get(`${this.BASE_URL}/s3/empty`)
+ .get(`${this.BASE_URL}/s3/empty`, {headers: this.getHeaders()})
.map(res => {
return res.json()
})
@@ -867,7 +881,7 @@ export class AwsService {
public getDetachedElasticIps(){
return this.http
- .get(`${this.BASE_URL}/eip/detached`)
+ .get(`${this.BASE_URL}/eip/detached`, {headers: this.getHeaders()})
.map(res => {
return res.json()
})
@@ -881,7 +895,7 @@ export class AwsService {
public getRedshiftClusters(){
return this.http
- .get(`${this.BASE_URL}/redshift/clusters`)
+ .get(`${this.BASE_URL}/redshift/clusters`, {headers: this.getHeaders()})
.map(res => {
return res.json()
})
@@ -895,7 +909,7 @@ export class AwsService {
public getVPCSubnets(){
return this.http
- .get(`${this.BASE_URL}/vpc/subnets`)
+ .get(`${this.BASE_URL}/vpc/subnets`, {headers: this.getHeaders()})
.map(res => {
return res.json()
})
@@ -909,7 +923,7 @@ export class AwsService {
public getForecastPrice(){
return this.http
- .get(`${this.BASE_URL}/cost/forecast`)
+ .get(`${this.BASE_URL}/cost/forecast`, {headers: this.getHeaders()})
.map(res => {
return res.json()
})
@@ -920,4 +934,10 @@ export class AwsService {
return Observable.throw(err.json().error)
})
}
+
+ private getHeaders(){
+ let headers = new Headers();
+ headers.append('profile', localStorage.getItem('profile'));
+ return headers;
+ }
}
diff --git a/dashboard/src/app/compute/aws/aws.component.ts b/dashboard/src/app/compute/aws/aws.component.ts
index a5b2a4915..afc337fbb 100644
--- a/dashboard/src/app/compute/aws/aws.component.ts
+++ b/dashboard/src/app/compute/aws/aws.component.ts
@@ -1,10 +1,12 @@
-import { Component, OnInit, AfterViewInit } from '@angular/core';
+import { Component, OnInit, AfterViewInit, OnDestroy } from '@angular/core';
import { AwsService } from '../../aws.service';
+import { StoreService } from '../../store.service';
import * as Chartist from 'chartist';
import 'chartist-plugin-tooltips';
declare var Chart: any;
declare var Circles: any;
import * as $ from "jquery";
+import { Subject, Subscription } from 'rxjs';
declare var moment: any;
@Component({
@@ -12,7 +14,13 @@ declare var moment: any;
templateUrl: './aws.component.html',
styleUrls: ['./aws.component.css']
})
-export class AwsComputeComponent implements OnInit, AfterViewInit {
+export class AwsComputeComponent implements OnInit, AfterViewInit, OnDestroy {
+ private costPerInstanceTypeChart: any;
+ private lambdaInvocationsChart: any;
+ private lambdaErrorsChart:any;
+ private instancesFamilyChart:any;
+ private instancesPrivacyChart:any;
+
public runningEC2Instances: number = 0;
public stoppedEC2Instances: number = 0;
public terminatedEC2Instances: number = 0;
@@ -44,9 +52,70 @@ export class AwsComputeComponent implements OnInit, AfterViewInit {
public loadingLambdaInvocationsChart: boolean = true;
public loadingLambdaErrorsChart: boolean = true;
+ private _subscription: Subscription;
+
+
+ constructor(private awsService: AwsService, private storeService: StoreService) {
+ this.initState();
+
+ this._subscription = this.storeService.profileChanged.subscribe(profile => {
+ this.costPerInstanceTypeChart.detach();
+ this.lambdaInvocationsChart.detach();
+ this.lambdaErrorsChart.detach();
+ this.instancesFamilyChart.destroy();
+ this.instancesPrivacyChart.destroy();
+
+ let tooltips = document.getElementsByClassName('chartist-tooltip')
+ for (let i = 0; i < tooltips.length; i++) {
+ tooltips[i].outerHTML = ""
+ }
+ for (let j = 0; j < 3; j++) {
+ let charts = document.getElementsByTagName('svg');
+ for (let i = 0; i < charts.length; i++) {
+ charts[i].outerHTML = ""
+ }
+ }
+
+ this.runningEC2Instances = 0;
+ this.stoppedEC2Instances = 0;
+ this.terminatedEC2Instances = 0;
+ this.lambdaFunctions = {};
+ this.ecsServices = 0;
+ this.ecsTasks = 0;
+ this.ecsClusters = 0;
+ this.reservedInstances = 0;
+ this.spotInstances = 0;
+ this.scheduledInstances = 0;
+ this.eksClusters = 0;
+ this.detchedElasticIps = 0;
+
+ this.loadingRunningInstances = true;
+ this.loadingStoppedInstances = true;
+ this.loadingTerminatedInstances = true;
+ this.loadingReservedInstances = true;
+ this.loadingSpotInstances = true;
+ this.loadingScheduledInstances = true;
+ this.loadingDetachedIps = true;
+ this.loadingLambdaFunctions= true;
+ this.loadingEcsClusters= true;
+ this.loadingEcsTasks = true;
+ this.loadingEcsServices = true;
+ this.loadingEksClusters= true;
+ this.loadingInstancesPrivacyChart = true;
+ this.loadingInstancesFamilyChart= true;
+ this.loadingCostPerInstanceTypeChart= true;
+ this.loadingLambdaInvocationsChart= true;
+ this.loadingLambdaErrorsChart= true;
+
+ this.initState();
+ });
+ }
- constructor(private awsService: AwsService) {
+ ngOnDestroy() {
+ this._subscription.unsubscribe();
+ }
+ private initState(){
this.lambdaFunctions = {}
this.awsService.getDetachedElasticIps().subscribe(data => {
@@ -141,7 +210,6 @@ export class AwsComputeComponent implements OnInit, AfterViewInit {
this.showLambdaInvocations(labels, series);
}, err => {
this.loadingLambdaInvocationsChart = false;
- console.log(err)
});
this.awsService.getECS().subscribe(data => {
@@ -188,7 +256,6 @@ export class AwsComputeComponent implements OnInit, AfterViewInit {
this.showLambdaErrors(labels, series);
}, err => {
this.loadingLambdaErrorsChart = false;
- console.log(err);
});
this.awsService.getReservedInstances().subscribe(data => {
@@ -252,7 +319,6 @@ export class AwsComputeComponent implements OnInit, AfterViewInit {
this.showCostPerInstanceType(periods, series);
}, err => {
this.loadingCostPerInstanceTypeChart = false;
- console.log(err);
});
}
@@ -303,14 +369,14 @@ export class AwsComputeComponent implements OnInit, AfterViewInit {
height: "245px",
}
- new Chartist.Bar('.costPerInstanceTypeChart', costHistory, optionChartCostHistory);
+ this.costPerInstanceTypeChart = new Chartist.Bar('.costPerInstanceTypeChart', costHistory, optionChartCostHistory);
}
private showInstancesPrivacy(series){
var canvas : any = document.getElementById('instancesPrivacyChart');
var ctx = canvas.getContext('2d');
- new Chart(ctx, {
+ this.instancesPrivacyChart = new Chart(ctx, {
type: 'pie',
data: {
datasets: [{
@@ -325,7 +391,7 @@ export class AwsComputeComponent implements OnInit, AfterViewInit {
private showLambdaErrors(labels, series){
let scope = this;
- new Chartist.Line('.lambdaErrorsChart', {
+ this.lambdaErrorsChart = new Chartist.Line('.lambdaErrorsChart', {
labels: labels,
series: series
}, {
@@ -349,7 +415,7 @@ export class AwsComputeComponent implements OnInit, AfterViewInit {
private showLambdaInvocations(labels, series){
let scope = this;
- new Chartist.Bar('.lambdaInvocationsChart', {
+ this.lambdaInvocationsChart = new Chartist.Bar('.lambdaInvocationsChart', {
labels: labels,
series: series
}, {
@@ -415,7 +481,7 @@ export class AwsComputeComponent implements OnInit, AfterViewInit {
};
var ctx = document.getElementById('instancesFamilyChart');
- var chart = new Chart.PolarArea(ctx, config);
+ this.instancesFamilyChart = new Chart.PolarArea(ctx, config);
}
}
diff --git a/dashboard/src/app/dashboard/aws/aws.component.ts b/dashboard/src/app/dashboard/aws/aws.component.ts
index facf2b1db..51c1c3e1f 100644
--- a/dashboard/src/app/dashboard/aws/aws.component.ts
+++ b/dashboard/src/app/dashboard/aws/aws.component.ts
@@ -1,5 +1,7 @@
-import { Component, OnInit, AfterViewInit } from '@angular/core';
+import { Component, OnInit, AfterViewInit, OnDestroy } from '@angular/core';
import { AwsService } from '../../aws.service';
+import { StoreService } from '../../store.service';
+import { Subject, Subscription } from 'rxjs';
import * as Chartist from 'chartist';
import 'chartist-plugin-tooltips';
import 'jquery-mapael';
@@ -12,7 +14,7 @@ declare var Chart: any;
templateUrl: './aws.component.html',
styleUrls: ['./aws.component.css']
})
-export class AwsDashboardComponent implements OnInit, AfterViewInit {
+export class AwsDashboardComponent implements OnInit, AfterViewInit, OnDestroy {
public iamUsers: number = 0;
public currentBill: number = 0;
public usedRegions: number = 0;
@@ -103,9 +105,41 @@ export class AwsDashboardComponent implements OnInit, AfterViewInit {
latitude: -23.5505199,
longitude: -46.6333094
}
+ };
+
+ private _subscription: Subscription;
+
+ constructor(private AwsService: AwsService, private storeService: StoreService) {
+ this.initState();
+
+ this._subscription = this.storeService.profileChanged.subscribe(profile => {
+ this.iamUsers = 0;
+ this.currentBill = 0;
+ this.usedRegions = 0;
+ this.redAlarms = 0;
+ this.mostUsedServices = [];
+ this.openTickets = 0;
+ this.resolvedTickets = 0;
+ this.forecastBill = '0';
+
+ this.loadingCurrentBill = true;
+ this.loadingIamUsers = true;
+ this.loadingUsedRegions = true;
+ this.loadingRedAlarms = true;
+ this.loadingOpenTickets = true;
+ this.loadingResolvedTickets = true;
+ this.loadingCostHistoryChart = true;
+ this.loadingForecastBill = true;
+
+ this.initState();
+ })
}
- constructor(private AwsService: AwsService) {
+ ngOnDestroy() {
+ this._subscription.unsubscribe();
+ }
+
+ private initState() {
this.mostUsedServices = []
this.AwsService.getIAMUsers().subscribe(data => {
@@ -217,12 +251,12 @@ export class AwsDashboardComponent implements OnInit, AfterViewInit {
});
}
- ngOnInit() {}
+ ngOnInit() { }
ngAfterViewInit(): void {
this.showEC2InstancesPerRegion({});
}
-
+
public formatNumber(labelValue) {
// Nine Zeroes for Billions
@@ -243,7 +277,7 @@ export class AwsDashboardComponent implements OnInit, AfterViewInit {
}
public showEC2InstancesPerRegion(plots) {
- var canvas : any = $(".mapregions");
+ var canvas: any = $(".mapregions");
canvas.mapael({
map: {
name: "world_countries",
@@ -332,7 +366,7 @@ export class AwsDashboardComponent implements OnInit, AfterViewInit {
},
axisY: {
offset: 80,
- labelInterpolationFnc: function(value) {
+ labelInterpolationFnc: function (value) {
return scope.formatNumber(value)
},
},
diff --git a/dashboard/src/app/data-and-ai/aws/aws.component.ts b/dashboard/src/app/data-and-ai/aws/aws.component.ts
index 592ab9995..5f97488be 100644
--- a/dashboard/src/app/data-and-ai/aws/aws.component.ts
+++ b/dashboard/src/app/data-and-ai/aws/aws.component.ts
@@ -1,6 +1,7 @@
-import { Component, OnInit } from '@angular/core';
+import { Component, OnInit, OnDestroy } from '@angular/core';
import { AwsService } from '../../aws.service';
-
+import { StoreService } from '../../store.service';
+import { Subject, Subscription } from 'rxjs';
declare var Chart: any;
declare var $: any;
declare var window: any;
@@ -14,7 +15,8 @@ import 'chartist-plugin-tooltips';
templateUrl: './aws.component.html',
styleUrls: ['./aws.component.css']
})
-export class AwsDataAndAIComponent implements OnInit {
+export class AwsDataAndAIComponent implements OnInit, OnDestroy {
+ private sqsMessagesChart:any;
public sqsQueues: number = 0;
public numberOfMessagesSentToday: number = 0;
@@ -42,7 +44,60 @@ export class AwsDataAndAIComponent implements OnInit {
public loadingESDomains: boolean = true;
public loadingSQSMessagesChart: boolean = true;
- constructor(private awsService: AwsService) {
+ private _subscription: Subscription;
+
+ constructor(private awsService: AwsService, private storeService: StoreService) {
+ this.initState();
+
+ this._subscription = this.storeService.profileChanged.subscribe(profile => {
+ this.sqsMessagesChart.detach();
+
+ let tooltips = document.getElementsByClassName('chartist-tooltip')
+ for (let i = 0; i < tooltips.length; i++) {
+ tooltips[i].outerHTML = ""
+ }
+ for (let j = 0; j < 3; j++) {
+ let charts = document.getElementsByTagName('svg');
+ for (let i = 0; i < charts.length; i++) {
+ charts[i].outerHTML = ""
+ }
+ }
+
+ this.sqsQueues = 0;
+ this.numberOfMessagesSentToday = 0;
+ this.numberOfMessagesDeletedToday = 0;
+ this.snsTopics = 0;
+ this.activemqBrokers = 0;
+ this.kinesisStreams = 0;
+ this.kinesisShards = 0;
+ this.glueJobs = 0;
+ this.glueCrawlers = 0;
+ this.dataPipelines = 0;
+ this.esDomains = 0;
+ this.swfDomains = 0;
+
+ this.loadingSQS = true;
+ this.loadingSQSMessages = true;
+ this.loadingSNS = true;
+ this.loadingGlueCrawlers = true;
+ this.loadingActiveMQBrokers = true;
+ this.loadingGlueJobs = true;
+ this.loadingSwfDomains = true;
+ this.loadingDataPipelines = true;
+ this.loadingKinesisStreams = true;
+ this.loadingKinesisShards = true;
+ this.loadingESDomains = true;
+ this.loadingSQSMessagesChart = true;
+
+ this.initState();
+ })
+ }
+
+ ngOnDestroy() {
+ this._subscription.unsubscribe();
+ }
+
+ private initState(){
this.awsService.getSQSPublishedMessagesMetrics().subscribe(data => {
this.numberOfMessagesSentToday = data[0].Datapoints[Object.keys(data[0].Datapoints)[Object.keys(data[0].Datapoints).length - 1]]
this.numberOfMessagesDeletedToday = data[1].Datapoints[Object.keys(data[1].Datapoints)[Object.keys(data[1].Datapoints).length - 1]]
@@ -74,7 +129,6 @@ export class AwsDataAndAIComponent implements OnInit {
}, err => {
this.loadingSQSMessagesChart = false;
this.loadingSQSMessages = false;
- console.log(err);
})
this.awsService.getSQSQueues().subscribe(data => {
@@ -181,7 +235,7 @@ export class AwsDataAndAIComponent implements OnInit {
private showSQSMessages(labels, series) {
let scope = this;
- new Chartist.Bar('#sqsMessagesChart', {
+ this.sqsMessagesChart = new Chartist.Bar('#sqsMessagesChart', {
labels: labels,
series: series
}, {
diff --git a/dashboard/src/app/limits/aws/aws.component.ts b/dashboard/src/app/limits/aws/aws.component.ts
index bf665086c..2232d752b 100644
--- a/dashboard/src/app/limits/aws/aws.component.ts
+++ b/dashboard/src/app/limits/aws/aws.component.ts
@@ -1,18 +1,34 @@
-import { Component, OnInit } from '@angular/core';
+import { Component, OnInit,OnDestroy } from '@angular/core';
import { AwsService } from '../../aws.service';
+import { StoreService } from '../../store.service';
+import { Subject, Subscription } from 'rxjs';
@Component({
selector: 'aws-limits',
templateUrl: './aws.component.html',
styleUrls: ['./aws.component.css']
})
-export class AwsLimitsComponent implements OnInit {
+export class AwsLimitsComponent implements OnInit,OnDestroy {
public serviceLimits: Array = [];
public loadingServiceLimits: boolean = true;
- constructor(private awsService: AwsService) {
+ private _subscription: Subscription;
+
+ constructor(private awsService: AwsService, private storeService: StoreService) {
+ this.initState();
+
+ this._subscription = this.storeService.profileChanged.subscribe(profile => {
+ this.serviceLimits = [];
+
+ this.loadingServiceLimits = true;
+
+ this.initState();
+ });
+ }
+
+ private initState(){
this.awsService.getServiceLimits().subscribe(data => {
this.serviceLimits = data;
this.loadingServiceLimits = false;
@@ -22,6 +38,10 @@ export class AwsLimitsComponent implements OnInit {
});
}
+ ngOnDestroy() {
+ this._subscription.unsubscribe();
+ }
+
public getColor(status: string) {
switch (status) {
case 'ok':
diff --git a/dashboard/src/app/network/aws/aws.component.ts b/dashboard/src/app/network/aws/aws.component.ts
index 53dfa3068..592d3a601 100644
--- a/dashboard/src/app/network/aws/aws.component.ts
+++ b/dashboard/src/app/network/aws/aws.component.ts
@@ -1,5 +1,7 @@
-import { Component, OnInit } from '@angular/core';
+import { Component, OnInit, OnDestroy } from '@angular/core';
import { AwsService } from '../../aws.service';
+import { StoreService } from '../../store.service';
+import { Subject, Subscription } from 'rxjs';
declare var Chart: any;
declare var $: any;
declare var window: any;
@@ -13,7 +15,13 @@ import 'chartist-plugin-tooltips';
templateUrl: './aws.component.html',
styleUrls: ['./aws.component.css']
})
-export class AwsNetworkComponent implements OnInit {
+export class AwsNetworkComponent implements OnInit, OnDestroy {
+ private cloudfrontRequests:any;
+ private apigatewayRequests:any;
+ private elbRequests: any;
+ private elbFamilyType:any;
+ private natGatewayChartTraffic:any;
+
public vpcNumber: number;
public aclNumber: number;
public subnetNumbers: number;
@@ -50,7 +58,74 @@ export class AwsNetworkComponent implements OnInit {
public loadingNatGatewayTrafficChart: boolean = true;
public loadingElbFamilyType: boolean = true;
- constructor(private awsService: AwsService) {
+ private _subscription: Subscription;
+
+ constructor(private awsService: AwsService, private storeService: StoreService) {
+ this.initState();
+
+ this._subscription = this.storeService.profileChanged.subscribe(profile => {
+ this.cloudfrontRequests.destroy();
+ this.apigatewayRequests.destroy();
+ this.elbRequests.destroy();
+ this.elbFamilyType.destroy();
+ //this.natGatewayChartTraffic.detach();
+
+ let tooltips = document.getElementsByClassName('chartist-tooltip')
+ for (let i = 0; i < tooltips.length; i++) {
+ tooltips[i].outerHTML = ""
+ }
+ for (let j = 0; j < 3; j++) {
+ let charts = document.getElementsByTagName('svg');
+ for (let i = 0; i < charts.length; i++) {
+ charts[i].outerHTML = ""
+ }
+ }
+
+ this.vpcNumber = 0;
+ this.aclNumber = 0;
+ this.subnetNumbers = 0;
+ this.routeTablesNumber = 0;
+ this.cloudfrontDistributions = 0;
+ this.cdnYesterdayRequests = '0';
+ this.cdnTodayRequests = '0';
+ this.apigatewayYesterdayRequests = '0';
+ this.apigatewayTodayRequests = '0';
+ this.apigatewayApis = 0;
+ this.elbYesterdayRequests = '0';
+ this.elbTodayRequests = '0';
+ this.loadBalancers = 0;
+ this.route53Records = 0;
+ this.route53Zones = 0;
+ this.natGatewayAvailableRegions = [];
+ this.natGatewayTraffic = [];
+
+ this.loadingVPCNumbers= true;
+ this.loadingACLNumbers = true;
+ this.loadingSubnetNumbers = true;
+ this.loadingRouteTablesNumber = true;
+ this.loadingCDNNumbers = true;
+ this.loadingCDNRequests = true;
+ this.loadingAPIGateways = true;
+ this.loadingAPIRequests = true;
+ this.loadingELBNumber = true;
+ this.loadingElbRequests = true;
+ this.loadingRoute53Zones = true;
+ this.loadingRoute53ARecords = true;
+ this.loadingCloudfrontRequestsChart = true;
+ this.loadingApigatewayRequestsChart = true;
+ this.loadingElbRequestsChart = true;
+ this.loadingNatGatewayTrafficChart = true;
+ this.loadingElbFamilyType = true;
+
+ this.initState();
+ });
+ }
+
+ ngOnDestroy() {
+ this._subscription.unsubscribe();
+ }
+
+ private initState() {
this.awsService.getVirtualPrivateClouds().subscribe(data => {
this.vpcNumber = data;
this.loadingVPCNumbers = false;
@@ -348,7 +423,7 @@ export class AwsNetworkComponent implements OnInit {
private showNatGatewayTraffic(labels, series) {
let scope = this;
- new Chartist.Bar('#natGatewayChartTraffic', {
+ this.natGatewayChartTraffic = new Chartist.Bar('#natGatewayChartTraffic', {
labels: labels,
series: series
}, {
@@ -416,9 +491,9 @@ export class AwsNetworkComponent implements OnInit {
};
- var canvas : any = document.getElementById('elbFamilyType');
+ var canvas: any = document.getElementById('elbFamilyType');
var ctx = canvas.getContext('2d');
- window.myBar = new Chart(ctx, {
+ this.elbFamilyType = new Chart(ctx, {
type: 'pie',
data: barChartData,
options: {
@@ -499,9 +574,9 @@ export class AwsNetworkComponent implements OnInit {
}
};
- var canvas : any = document.getElementById('apigatewayRequests');
+ var canvas: any = document.getElementById('apigatewayRequests');
var ctx = canvas.getContext('2d');
- new Chart(ctx, config);
+ this.apigatewayRequests = new Chart(ctx, config);
}
private showCloudFrontRequests(datasets) {
@@ -554,9 +629,9 @@ export class AwsNetworkComponent implements OnInit {
}
};
- var canvas : any = document.getElementById('cloudfrontRequests');
+ var canvas: any = document.getElementById('cloudfrontRequests');
var ctx = canvas.getContext('2d');
- new Chart(ctx, config);
+ this.cloudfrontRequests = new Chart(ctx, config);
}
private showELBRequests(datasets) {
@@ -609,9 +684,9 @@ export class AwsNetworkComponent implements OnInit {
}
};
- var canvas : any = document.getElementById('elbRequests');
+ var canvas: any = document.getElementById('elbRequests');
var ctx = canvas.getContext('2d');
- new Chart(ctx, config);
+ this.elbRequests = new Chart(ctx, config);
}
}
diff --git a/dashboard/src/app/notifications/notifications.component.css b/dashboard/src/app/notifications/notifications.component.css
new file mode 100644
index 000000000..4a6594722
--- /dev/null
+++ b/dashboard/src/app/notifications/notifications.component.css
@@ -0,0 +1,3 @@
+.badge-margin{
+ margin-right: 20px;
+}
\ No newline at end of file
diff --git a/dashboard/src/app/notifications/notifications.component.html b/dashboard/src/app/notifications/notifications.component.html
new file mode 100644
index 000000000..e231226af
--- /dev/null
+++ b/dashboard/src/app/notifications/notifications.component.html
@@ -0,0 +1,18 @@
+
+
+
Notifications
+
+
+
+
+ You have currently no notifications.
+
+
+
+
+ 1">{{notification.total}} {{notification.content}} {{calcMoment(notification.timestamp)}}
+
+
+
+
+
\ No newline at end of file
diff --git a/dashboard/src/app/notifications/notifications.component.spec.ts b/dashboard/src/app/notifications/notifications.component.spec.ts
new file mode 100644
index 000000000..0147b0d89
--- /dev/null
+++ b/dashboard/src/app/notifications/notifications.component.spec.ts
@@ -0,0 +1,25 @@
+import { async, ComponentFixture, TestBed } from '@angular/core/testing';
+
+import { NotificationsComponent } from './notifications.component';
+
+describe('NotificationsComponent', () => {
+ let component: NotificationsComponent;
+ let fixture: ComponentFixture;
+
+ beforeEach(async(() => {
+ TestBed.configureTestingModule({
+ declarations: [ NotificationsComponent ]
+ })
+ .compileComponents();
+ }));
+
+ beforeEach(() => {
+ fixture = TestBed.createComponent(NotificationsComponent);
+ component = fixture.componentInstance;
+ fixture.detectChanges();
+ });
+
+ it('should create', () => {
+ expect(component).toBeTruthy();
+ });
+});
diff --git a/dashboard/src/app/notifications/notifications.component.ts b/dashboard/src/app/notifications/notifications.component.ts
new file mode 100644
index 000000000..60a5fd4e8
--- /dev/null
+++ b/dashboard/src/app/notifications/notifications.component.ts
@@ -0,0 +1,41 @@
+import { Component, OnInit, OnDestroy } from '@angular/core';
+import { StoreService } from '../store.service';
+import { Subscription } from 'rxjs';
+import * as moment from 'moment';
+
+@Component({
+ selector: 'app-notifications',
+ templateUrl: './notifications.component.html',
+ styleUrls: ['./notifications.component.css']
+})
+export class NotificationsComponent implements OnInit, OnDestroy {
+ public notifications: Array = [];
+ public _subscription: Subscription;
+
+ constructor(private storeService: StoreService) {
+
+ let temp = this.storeService.list();
+ Object.keys(temp).forEach(key => {
+ this.notifications.push(temp[key]);
+ })
+
+ this._subscription = this.storeService.newNotification.subscribe(notifications => {
+ this.notifications = [];
+ Object.keys(notifications).forEach(key => {
+ this.notifications.push(notifications[key]);
+ })
+ })
+ }
+
+ public calcMoment(timestamp) {
+ return moment(timestamp).fromNow();
+ }
+
+ ngOnDestroy(){
+ this._subscription.unsubscribe();
+ }
+
+ ngOnInit() {
+ }
+
+}
diff --git a/dashboard/src/app/profile/aws/aws.component.ts b/dashboard/src/app/profile/aws/aws.component.ts
index 25023a997..a2435e491 100644
--- a/dashboard/src/app/profile/aws/aws.component.ts
+++ b/dashboard/src/app/profile/aws/aws.component.ts
@@ -1,16 +1,31 @@
-import { Component, OnInit } from '@angular/core';
+import { Component, OnInit, OnDestroy } from '@angular/core';
import { AwsService } from '../../aws.service';
+import { StoreService } from '../../store.service';
+import { Subject, Subscription } from 'rxjs';
@Component({
selector: 'aws-profile',
templateUrl: './aws.component.html',
styleUrls: ['./aws.component.css']
})
-export class AwsProfileComponent implements OnInit {
+export class AwsProfileComponent implements OnInit, OnDestroy {
public account : Object = {};
public organization: Object = {};
- constructor(private awsService: AwsService) {
+ private _subscription: Subscription;
+
+ constructor(private awsService: AwsService, private storeService: StoreService) {
+ this.initState();
+
+ this._subscription = this.storeService.profileChanged.subscribe(profile => {
+ this.account = {};
+ this.organization = {};
+
+ this.initState();
+ });
+ }
+
+ private initState(){
this.awsService.getAccountName().subscribe(data => {
this.account = data;
}, err => {
@@ -24,6 +39,10 @@ export class AwsProfileComponent implements OnInit {
});
}
+ ngOnDestroy() {
+ this._subscription.unsubscribe();
+ }
+
ngOnInit() {
}
diff --git a/dashboard/src/app/security/aws/aws.component.ts b/dashboard/src/app/security/aws/aws.component.ts
index 70624fcfc..0443dc4c2 100644
--- a/dashboard/src/app/security/aws/aws.component.ts
+++ b/dashboard/src/app/security/aws/aws.component.ts
@@ -1,7 +1,8 @@
-import { Component, OnInit, AfterViewInit } from '@angular/core';
+import { Component, OnInit, AfterViewInit, OnDestroy } from '@angular/core';
import { AwsService } from '../../aws.service';
import { PageChangedEvent } from 'ngx-bootstrap/pagination';
-
+import { StoreService } from '../../store.service';
+import { Subject, Subscription } from 'rxjs';
import 'jquery-mapael';
import 'jquery-mapael/js/maps/world_countries.js';
import * as $ from "jquery";
@@ -13,7 +14,8 @@ import 'chartist-plugin-tooltips';
templateUrl: './aws.component.html',
styleUrls: ['./aws.component.css']
})
-export class AwsSecurityComponent implements OnInit, AfterViewInit {
+export class AwsSecurityComponent implements OnInit, AfterViewInit, OnDestroy {
+ private signInEventsChart:any;
public kmsKeys: number;
public securityGroups: number;
@@ -33,7 +35,44 @@ export class AwsSecurityComponent implements OnInit, AfterViewInit {
public loadingACMExpiredCertificates: boolean = true;
public loadingSignInEventsChart: boolean = true;
- constructor(private awsService: AwsService) {
+ private _subscription: Subscription;
+
+ constructor(private awsService: AwsService, private storeService: StoreService) {
+ this.initState();
+
+ this._subscription = this.storeService.profileChanged.subscribe(profile => {
+ this.signInEventsChart.detach();
+
+ for (let j = 0; j < 3; j++) {
+ let charts = document.getElementsByTagName('svg');
+ for (let i = 0; i < charts.length; i++) {
+ charts[i].outerHTML = ""
+ }
+ }
+
+ this.kmsKeys = 0;
+ this.securityGroups = 0;
+ this.keyPairs = 0;
+ this.routeTables = 0;
+ this.acmCertificates = 0;
+ this.acmExpiredCertificates = 0;
+ this.unrestrictedSecurityGroups = [];
+ this.returnedUnrestrictedSecurityGroups = [];
+ this.consoleLoginSourceIps = [];
+
+ this.loadingKMSKeys = true;
+ this.loadingSecurityGroups = true;
+ this.loadingKeyPairs = true;
+ this.loadingRouteTables = true;
+ this.loadingACMCertificates = true;
+ this.loadingACMExpiredCertificates = true;
+ this.loadingSignInEventsChart = true;
+
+ this.initState();
+ });
+ }
+
+ private initState() {
this.awsService.getKMSKeys().subscribe(data => {
this.kmsKeys = data;
this.loadingKMSKeys = false;
@@ -144,6 +183,10 @@ export class AwsSecurityComponent implements OnInit, AfterViewInit {
});
}
+ ngOnDestroy() {
+ this._subscription.unsubscribe();
+ }
+
pageChanged(event: PageChangedEvent): void {
const startItem = (event.page - 1) * event.itemsPerPage;
const endItem = event.page * event.itemsPerPage;
@@ -158,7 +201,7 @@ export class AwsSecurityComponent implements OnInit, AfterViewInit {
ngOnInit() { }
private showSourceIpLogin(plots) {
- var canvas : any = $("#sourceIpsChart");
+ var canvas: any = $("#sourceIpsChart");
canvas.mapael({
map: {
name: "world_countries",
@@ -231,7 +274,7 @@ export class AwsSecurityComponent implements OnInit, AfterViewInit {
}
private showSignInEvents(labels, series) {
- new Chartist.Bar('#signInEventsChart', {
+ this.signInEventsChart = new Chartist.Bar('#signInEventsChart', {
labels: labels,
series: series
}, {
diff --git a/dashboard/src/app/storage/aws/aws.component.ts b/dashboard/src/app/storage/aws/aws.component.ts
index d58379d54..2ed39c9e5 100644
--- a/dashboard/src/app/storage/aws/aws.component.ts
+++ b/dashboard/src/app/storage/aws/aws.component.ts
@@ -1,6 +1,7 @@
-import { Component, OnInit } from '@angular/core';
+import { Component, OnInit, OnDestroy } from '@angular/core';
import { AwsService } from '../../aws.service';
-
+import { StoreService } from '../../store.service';
+import { Subject, Subscription } from 'rxjs';
declare var Chart: any;
declare var $: any;
declare var window: any;
@@ -13,7 +14,12 @@ import 'chartist-plugin-tooltips';
templateUrl: './aws.component.html',
styleUrls: ['./aws.component.css']
})
-export class AwsStorageComponent implements OnInit {
+export class AwsStorageComponent implements OnInit, OnDestroy {
+ private s3BucketsSizeChart: any;
+ private s3BucketsObjectsChart: any;
+ private ebsFamilyChart: any;
+ private logsVolumeChart: any;
+
public s3Buckets: number;
public emptyBuckets: number;
public s3BucketSize: string;
@@ -48,7 +54,72 @@ export class AwsStorageComponent implements OnInit {
public loadingEbsFamilyChart: boolean = true;
public loadingLogsVolumeChart: boolean = true;
- constructor(private awsService: AwsService) {
+ private _subscription: Subscription;
+
+ constructor(private awsService: AwsService, private storeService: StoreService) {
+ this.initState();
+
+ this._subscription = this.storeService.profileChanged.subscribe(profile => {
+ this.s3BucketsSizeChart.detach();
+ this.s3BucketsObjectsChart.detach();
+ this.logsVolumeChart.detach();
+ this.ebsFamilyChart.destroy();
+
+ let tooltips = document.getElementsByClassName('chartist-tooltip')
+ for (let i = 0; i < tooltips.length; i++) {
+ tooltips[i].outerHTML = ""
+ }
+ for (let j = 0; j < 3; j++) {
+ let charts = document.getElementsByTagName('svg');
+ for (let i = 0; i < charts.length; i++) {
+ charts[i].outerHTML = ""
+ }
+ }
+
+
+ this.s3Buckets = 0;
+ this.emptyBuckets = 0;
+ this.s3BucketSize = '0 KB';
+ this.s3BucketObjects = '0';
+ this.ebsTotal = 0;
+ this.ebsTotalSize = '0 KB';
+ this.ebsUsed = 0;
+ this.dynamodbTables = 0;
+ this.rdsInstances = 0;
+ this.docdbInstances = 0;
+ this.memcachedClusters = 0;
+ this.redisClusters = 0;
+ this.logsRetentionPeriod = 0;
+ this.redshiftClusters = 0;
+
+ this.loadingS3Buckets = true;
+ this.loadingS3BucketSize = true;
+ this.loadingS3BucketObjects = true;
+ this.loadingEmptyBuckets = true;
+ this.loadingEbsTotal = true;
+ this.loadingEbsTotalSize = true;
+ this.loadingEbsUsed = true;
+ this.loadingLogsRetentionPeriod = true;
+ this.loadingDynamoTables = true;
+ this.loadingRdsInstances = true;
+ this.loadingDocDbInstances = true;
+ this.loadingRedshiftClusters = true;
+ this.loadingMemCachedClusters = true;
+ this.loadingRedisClusters = true;
+ this.loadingS3BucketsSizeChart = true;
+ this.loadingS3BucketsObjectsChart = true;
+ this.loadingEbsFamilyChart = true;
+ this.loadingLogsVolumeChart = true;
+
+ this.initState();
+ });
+ }
+
+ ngOnDestroy() {
+ this._subscription.unsubscribe();
+ }
+
+ private initState() {
this.awsService.getNumberOfS3Buckets().subscribe(data => {
this.s3Buckets = data ? data : 0;
this.loadingS3Buckets = false;
@@ -68,7 +139,7 @@ export class AwsStorageComponent implements OnInit {
})
this.ebsTotal = sum;
this.loadingEbsTotal = false;
- this.ebsTotalSize = this.bytesToSizeWithUnit(data.total*1024*1024);
+ this.ebsTotalSize = this.bytesToSizeWithUnit(data.total * 1024 * 1024);
this.loadingEbsTotalSize = false;
this.ebsUsed = data.state['in-use'];
this.loadingEbsUsed = false;
@@ -99,7 +170,7 @@ export class AwsStorageComponent implements OnInit {
serie.push({
meta: region, value: data[region][timestamp]
})
- if (i==0){
+ if (i == 0) {
labels.push(timestamp)
}
})
@@ -130,7 +201,7 @@ export class AwsStorageComponent implements OnInit {
serie.push({
meta: region, value: data[region][timestamp]
})
- if (i==0){
+ if (i == 0) {
labels.push(timestamp)
}
})
@@ -143,11 +214,9 @@ export class AwsStorageComponent implements OnInit {
total += data[region][Object.keys(data[region])[Object.keys(data[region]).length - 1]]
});
- console.log(labels)
- console.log(series);
this.loadingS3BucketsSizeChart = false;
this.loadingS3BucketSize = false;
- this.s3BucketSize = this.bytesToSizeWithUnit(total);
+ this.s3BucketSize = this.bytesToSizeWithUnit(total);
this.showS3BucketsSize(labels, series);
}, err => {
this.s3BucketObjects = '0';
@@ -225,7 +294,6 @@ export class AwsStorageComponent implements OnInit {
]);
}, err => {
this.loadingLogsVolumeChart = false;
- console.log(err);
});
this.awsService.getLogsRetentionPeriod().subscribe(data => {
@@ -245,10 +313,10 @@ export class AwsStorageComponent implements OnInit {
})
}
-
+
private showS3BucketsObjects(labels, series) {
let scope = this;
- new Chartist.Bar('#s3BucketsObjectsChart', {
+ this.s3BucketsObjectsChart = new Chartist.Bar('#s3BucketsObjectsChart', {
labels: labels,
series: series
}, {
@@ -273,7 +341,7 @@ export class AwsStorageComponent implements OnInit {
private showS3BucketsSize(labels, series) {
let scope = this;
- new Chartist.Bar('#s3BucketsSizeChart', {
+ this.s3BucketsSizeChart = new Chartist.Bar('#s3BucketsSizeChart', {
labels: labels,
series: series
}, {
@@ -298,7 +366,7 @@ export class AwsStorageComponent implements OnInit {
private showLogsVolume(labels, series) {
let scope = this;
- new Chartist.Bar('#logsVolumeChart', {
+ this.logsVolumeChart = new Chartist.Bar('#logsVolumeChart', {
labels: labels,
series: series
}, {
@@ -321,7 +389,7 @@ export class AwsStorageComponent implements OnInit {
});
}
- ngOnInit() {}
+ ngOnInit() { }
private showEBSFamily(labels, series) {
var barChartData = {
@@ -339,9 +407,9 @@ export class AwsStorageComponent implements OnInit {
};
- var canvas : any = document.getElementById('ebsFamilyChart');
+ let canvas: any = document.getElementById('ebsFamilyChart');
var ctx = canvas.getContext('2d');
- new Chart(ctx, {
+ this.ebsFamilyChart = new Chart(ctx, {
type: 'pie',
data: barChartData,
options: {
diff --git a/dashboard/src/app/store.service.ts b/dashboard/src/app/store.service.ts
index c5170f61f..8c89c22de 100644
--- a/dashboard/src/app/store.service.ts
+++ b/dashboard/src/app/store.service.ts
@@ -1,6 +1,5 @@
import { Injectable } from '@angular/core';
import { Subject } from "rxjs/Subject";
-
@Injectable()
export class StoreService {
@@ -12,6 +11,8 @@ export class StoreService {
public providerChanged: Subject = new Subject();
+ public profileChanged: Subject = new Subject();
+
constructor() {
if(localStorage.getItem('provider')){
this.provider = localStorage.getItem('provider');
@@ -48,6 +49,11 @@ export class StoreService {
return this.notifications;
}
+ public cleanNotifications(){
+ this.notifications = new Map();
+ this.newNotification.next(this.notifications);
+ }
+
public onProviderChanged(provider: string){
this.provider = provider;
localStorage.setItem('provider', this.provider);
@@ -56,4 +62,10 @@ export class StoreService {
this.newNotification.next(this.notifications);
}
+ public onProfileChanged(profile: string){
+ this.profileChanged.next(profile);
+ this.notifications = new Map();
+ this.newNotification.next(this.notifications);
+ }
+
}
diff --git a/handlers/aws/acm_handler.go b/handlers/aws/acm_handler.go
index 542ed97a0..ae6d1f38b 100644
--- a/handlers/aws/acm_handler.go
+++ b/handlers/aws/acm_handler.go
@@ -1,34 +1,61 @@
package aws
import (
+ "fmt"
"net/http"
+
+ "github.com/aws/aws-sdk-go-v2/aws/external"
)
func (handler *AWSHandler) APIGatewayListCertificatesHandler(w http.ResponseWriter, r *http.Request) {
- response, found := handler.cache.Get("aws_acm_certificates")
+ profile := r.Header.Get("profile")
+ cfg, err := external.LoadDefaultAWSConfig()
+
+ if handler.multiple {
+ cfg, err = external.LoadDefaultAWSConfig(external.WithSharedConfigProfile(profile))
+ if err != nil {
+ respondWithError(w, http.StatusInternalServerError, "Couldn't read "+profile+" profile")
+ }
+ }
+
+ key := fmt.Sprintf("aws.%s.acm.certificates", profile)
+
+ response, found := handler.cache.Get(key)
if found {
respondWithJSON(w, 200, response)
} else {
- response, err := handler.aws.ListCertificates(handler.cfg)
+ response, err := handler.aws.ListCertificates(cfg)
if err != nil {
respondWithError(w, http.StatusInternalServerError, "acm:ListCertificates is missing")
} else {
- handler.cache.Set("aws_acm_certificates", response)
+ handler.cache.Set(key, response)
respondWithJSON(w, 200, response)
}
}
}
func (handler *AWSHandler) APIGatewayExpiredCertificatesHandler(w http.ResponseWriter, r *http.Request) {
- response, found := handler.cache.Get("aws_acm_expired")
+ profile := r.Header.Get("profile")
+ cfg, err := external.LoadDefaultAWSConfig()
+
+ if handler.multiple {
+ cfg, err = external.LoadDefaultAWSConfig(external.WithSharedConfigProfile(profile))
+ if err != nil {
+ respondWithError(w, http.StatusInternalServerError, "Couldn't read "+profile+" profile")
+ }
+ }
+
+ key := fmt.Sprintf("aws.%s.acm.expired", profile)
+
+ response, found := handler.cache.Get(key)
if found {
respondWithJSON(w, 200, response)
} else {
- response, err := handler.aws.ListExpiredCertificates(handler.cfg)
+ response, err := handler.aws.ListExpiredCertificates(cfg)
if err != nil {
respondWithError(w, http.StatusInternalServerError, "acm:ListCertificates is missing")
} else {
- handler.cache.Set("aws_acm_expired", response)
+ handler.cache.Set(key, response)
respondWithJSON(w, 200, response)
}
}
diff --git a/handlers/aws/activemq_handler.go b/handlers/aws/activemq_handler.go
index beabb6520..493fbe4a4 100644
--- a/handlers/aws/activemq_handler.go
+++ b/handlers/aws/activemq_handler.go
@@ -1,19 +1,34 @@
package aws
import (
+ "fmt"
"net/http"
+
+ "github.com/aws/aws-sdk-go-v2/aws/external"
)
func (handler *AWSHandler) ActiveMQBrokersHandler(w http.ResponseWriter, r *http.Request) {
- response, found := handler.cache.Get("aws_mq_brokers")
+ profile := r.Header.Get("profile")
+ cfg, err := external.LoadDefaultAWSConfig()
+
+ if handler.multiple {
+ cfg, err = external.LoadDefaultAWSConfig(external.WithSharedConfigProfile(profile))
+ if err != nil {
+ respondWithError(w, http.StatusInternalServerError, "Couldn't read "+profile+" profile")
+ }
+ }
+
+ key := fmt.Sprintf("aws.%s.mq.brokers", profile)
+
+ response, found := handler.cache.Get(key)
if found {
respondWithJSON(w, 200, response)
} else {
- response, err := handler.aws.ListBrokers(handler.cfg)
+ response, err := handler.aws.ListBrokers(cfg)
if err != nil {
respondWithError(w, http.StatusInternalServerError, "mq:ListBrokers is missing")
} else {
- handler.cache.Set("aws_mq_brokers", response)
+ handler.cache.Set(key, response)
respondWithJSON(w, 200, response)
}
}
diff --git a/handlers/aws/apigateway_handler.go b/handlers/aws/apigateway_handler.go
index df6515231..07449e549 100644
--- a/handlers/aws/apigateway_handler.go
+++ b/handlers/aws/apigateway_handler.go
@@ -1,34 +1,61 @@
package aws
import (
+ "fmt"
"net/http"
+
+ "github.com/aws/aws-sdk-go-v2/aws/external"
)
func (handler *AWSHandler) APIGatewayRequestsHandler(w http.ResponseWriter, r *http.Request) {
- response, found := handler.cache.Get("aws_apigateway_requests")
+ profile := r.Header.Get("profile")
+ cfg, err := external.LoadDefaultAWSConfig()
+
+ if handler.multiple {
+ cfg, err = external.LoadDefaultAWSConfig(external.WithSharedConfigProfile(profile))
+ if err != nil {
+ respondWithError(w, http.StatusInternalServerError, "Couldn't read "+profile+" profile")
+ }
+ }
+
+ key := fmt.Sprintf("aws.%s.apigateway.requests", profile)
+
+ response, found := handler.cache.Get(key)
if found {
respondWithJSON(w, 200, response)
} else {
- response, err := handler.aws.GetAPIGatewayRequests(handler.cfg)
+ response, err := handler.aws.GetAPIGatewayRequests(cfg)
if err != nil {
respondWithError(w, http.StatusInternalServerError, "cloudwatch:GetMetricStatistics is missing")
} else {
- handler.cache.Set("aws_apigateway_requests", response)
+ handler.cache.Set(key, response)
respondWithJSON(w, 200, response)
}
}
}
func (handler *AWSHandler) APIGatewayRestAPIsHandler(w http.ResponseWriter, r *http.Request) {
- response, found := handler.cache.Get("aws_apigateway_apis")
+ profile := r.Header.Get("profile")
+ cfg, err := external.LoadDefaultAWSConfig()
+
+ if handler.multiple {
+ cfg, err = external.LoadDefaultAWSConfig(external.WithSharedConfigProfile(profile))
+ if err != nil {
+ respondWithError(w, http.StatusInternalServerError, "Couldn't read "+profile+" profile")
+ }
+ }
+
+ key := fmt.Sprintf("aws.%s.apigateway.apis", profile)
+
+ response, found := handler.cache.Get(key)
if found {
respondWithJSON(w, 200, response)
} else {
- response, err := handler.aws.GetRestAPIs(handler.cfg)
+ response, err := handler.aws.GetRestAPIs(cfg)
if err != nil {
respondWithError(w, http.StatusInternalServerError, "apigateway:GET is missing")
} else {
- handler.cache.Set("aws_apigateway_apis", response)
+ handler.cache.Set(key, response)
respondWithJSON(w, 200, response)
}
}
diff --git a/handlers/aws/cloudfront_handler.go b/handlers/aws/cloudfront_handler.go
index dbb1e0784..20a37671c 100644
--- a/handlers/aws/cloudfront_handler.go
+++ b/handlers/aws/cloudfront_handler.go
@@ -1,34 +1,61 @@
package aws
import (
+ "fmt"
"net/http"
+
+ "github.com/aws/aws-sdk-go-v2/aws/external"
)
func (handler *AWSHandler) CloudFrontDistributionsHandler(w http.ResponseWriter, r *http.Request) {
- response, found := handler.cache.Get("aws_cloudfront")
+ profile := r.Header.Get("profile")
+ cfg, err := external.LoadDefaultAWSConfig()
+
+ if handler.multiple {
+ cfg, err = external.LoadDefaultAWSConfig(external.WithSharedConfigProfile(profile))
+ if err != nil {
+ respondWithError(w, http.StatusInternalServerError, "Couldn't read "+profile+" profile")
+ }
+ }
+
+ key := fmt.Sprintf("aws.%s.cloudfront.distributions", profile)
+
+ response, found := handler.cache.Get(key)
if found {
respondWithJSON(w, 200, response)
} else {
- response, err := handler.aws.DescribeCloudFrontDistributions(handler.cfg)
+ response, err := handler.aws.DescribeCloudFrontDistributions(cfg)
if err != nil {
respondWithError(w, http.StatusInternalServerError, "cloudfront:ListDistributions is missing")
} else {
- handler.cache.Set("aws_cloudfront", response)
+ handler.cache.Set(key, response)
respondWithJSON(w, 200, response)
}
}
}
func (handler *AWSHandler) CloudFrontRequestsHandler(w http.ResponseWriter, r *http.Request) {
- response, found := handler.cache.Get("aws_cloudfront_requests")
+ profile := r.Header.Get("profile")
+ cfg, err := external.LoadDefaultAWSConfig()
+
+ if handler.multiple {
+ cfg, err = external.LoadDefaultAWSConfig(external.WithSharedConfigProfile(profile))
+ if err != nil {
+ respondWithError(w, http.StatusInternalServerError, "Couldn't read "+profile+" profile")
+ }
+ }
+
+ key := fmt.Sprintf("aws.%s.cloudfront.requests", profile)
+
+ response, found := handler.cache.Get(key)
if found {
respondWithJSON(w, 200, response)
} else {
- response, err := handler.aws.GetCloudFrontRequests(handler.cfg)
+ response, err := handler.aws.GetCloudFrontRequests(cfg)
if err != nil {
respondWithError(w, http.StatusInternalServerError, "cloudwatch:GetMetricStatistics is missing")
} else {
- handler.cache.Set("aws_cloudfront_requests", response)
+ handler.cache.Set(key, response)
respondWithJSON(w, 200, response)
}
}
diff --git a/handlers/aws/cloudtrail_handler.go b/handlers/aws/cloudtrail_handler.go
index 6f5e4ef22..c8a85b931 100644
--- a/handlers/aws/cloudtrail_handler.go
+++ b/handlers/aws/cloudtrail_handler.go
@@ -1,34 +1,61 @@
package aws
import (
+ "fmt"
"net/http"
+
+ "github.com/aws/aws-sdk-go-v2/aws/external"
)
func (handler *AWSHandler) CloudTrailConsoleSignInEventsHandler(w http.ResponseWriter, r *http.Request) {
- response, found := handler.cache.Get("aws_events_sign_in")
+ profile := r.Header.Get("profile")
+ cfg, err := external.LoadDefaultAWSConfig()
+
+ if handler.multiple {
+ cfg, err = external.LoadDefaultAWSConfig(external.WithSharedConfigProfile(profile))
+ if err != nil {
+ respondWithError(w, http.StatusInternalServerError, "Couldn't read "+profile+" profile")
+ }
+ }
+
+ key := fmt.Sprintf("aws.%s.cloudtrail.signin", profile)
+
+ response, found := handler.cache.Get(key)
if found {
respondWithJSON(w, 200, response)
} else {
- response, err := handler.aws.CloudTrailConsoleSignInEvents(handler.cfg)
+ response, err := handler.aws.CloudTrailConsoleSignInEvents(cfg)
if err != nil {
respondWithError(w, http.StatusInternalServerError, "cloudtrail:LookupEvents is missing")
} else {
- handler.cache.Set("aws_events_sign_in", response)
+ handler.cache.Set(key, response)
respondWithJSON(w, 200, response)
}
}
}
func (handler *AWSHandler) CloudTrailConsoleSignInSourceIpEventsHandler(w http.ResponseWriter, r *http.Request) {
- response, found := handler.cache.Get("aws_events_source_ip")
+ profile := r.Header.Get("profile")
+ cfg, err := external.LoadDefaultAWSConfig()
+
+ if handler.multiple {
+ cfg, err = external.LoadDefaultAWSConfig(external.WithSharedConfigProfile(profile))
+ if err != nil {
+ respondWithError(w, http.StatusInternalServerError, "Couldn't read "+profile+" profile")
+ }
+ }
+
+ key := fmt.Sprintf("aws.%s.cloudtrail.sourceip", profile)
+
+ response, found := handler.cache.Get(key)
if found {
respondWithJSON(w, 200, response)
} else {
- response, err := handler.aws.CloudTrailConsoleSignInSourceIpEvents(handler.cfg)
+ response, err := handler.aws.CloudTrailConsoleSignInSourceIpEvents(cfg)
if err != nil {
respondWithError(w, http.StatusInternalServerError, "cloudtrail:LookupEvents is missing")
} else {
- handler.cache.Set("aws_events_source_ip", response)
+ handler.cache.Set(key, response)
respondWithJSON(w, 200, response)
}
}
diff --git a/handlers/aws/cloudwatch_handler.go b/handlers/aws/cloudwatch_handler.go
index 990eaf285..b6f1b6647 100644
--- a/handlers/aws/cloudwatch_handler.go
+++ b/handlers/aws/cloudwatch_handler.go
@@ -1,19 +1,34 @@
package aws
import (
+ "fmt"
"net/http"
+
+ "github.com/aws/aws-sdk-go-v2/aws/external"
)
func (handler *AWSHandler) CloudWatchAlarmsHandler(w http.ResponseWriter, r *http.Request) {
- response, found := handler.cache.Get("aws_cloudwatch_alarms")
+ profile := r.Header.Get("profile")
+ cfg, err := external.LoadDefaultAWSConfig()
+
+ if handler.multiple {
+ cfg, err = external.LoadDefaultAWSConfig(external.WithSharedConfigProfile(profile))
+ if err != nil {
+ respondWithError(w, http.StatusInternalServerError, "Couldn't read "+profile+" profile")
+ }
+ }
+
+ key := fmt.Sprintf("aws.%s.cloudwatch.alarms", profile)
+
+ response, found := handler.cache.Get(key)
if found {
respondWithJSON(w, 200, response)
} else {
- response, err := handler.aws.DescribeCloudWatchAlarms(handler.cfg)
+ response, err := handler.aws.DescribeCloudWatchAlarms(cfg)
if err != nil {
respondWithError(w, http.StatusInternalServerError, "cloudwatch:DescribeAlarms is missing")
} else {
- handler.cache.Set("aws_cloudwatch_alarms", response)
+ handler.cache.Set(key, response)
respondWithJSON(w, 200, response)
}
}
diff --git a/handlers/aws/config_handler.go b/handlers/aws/config_handler.go
new file mode 100644
index 000000000..2ea31d848
--- /dev/null
+++ b/handlers/aws/config_handler.go
@@ -0,0 +1,21 @@
+package aws
+
+import (
+ "net/http"
+
+ "github.com/aws/aws-sdk-go-v2/aws/external"
+ . "github.com/mlabouardy/komiser/services/ini"
+)
+
+func (handler *AWSHandler) ConfigProfilesHandler(w http.ResponseWriter, r *http.Request) {
+ if handler.multiple {
+ sections, err := OpenFile(external.DefaultSharedCredentialsFilename())
+ if err != nil {
+ respondWithError(w, http.StatusInternalServerError, "Couldn't parse credentials file")
+ }
+ respondWithJSON(w, 200, sections.List())
+ } else {
+ respondWithJSON(w, 200, []string{})
+ }
+
+}
diff --git a/handlers/aws/costexplorer_handler.go b/handlers/aws/costexplorer_handler.go
index dfe5c9bc1..6c8d3fcb6 100644
--- a/handlers/aws/costexplorer_handler.go
+++ b/handlers/aws/costexplorer_handler.go
@@ -1,64 +1,115 @@
package aws
import (
+ "fmt"
"net/http"
+
+ "github.com/aws/aws-sdk-go-v2/aws/external"
)
func (handler *AWSHandler) CostAndUsageHandler(w http.ResponseWriter, r *http.Request) {
- response, found := handler.cache.Get("cost_usage_history")
+ profile := r.Header.Get("profile")
+ cfg, err := external.LoadDefaultAWSConfig()
+
+ if handler.multiple {
+ cfg, err = external.LoadDefaultAWSConfig(external.WithSharedConfigProfile(profile))
+ if err != nil {
+ respondWithError(w, http.StatusInternalServerError, "Couldn't read "+profile+" profile")
+ }
+ }
+
+ key := fmt.Sprintf("aws.%s.ce.history", profile)
+
+ response, found := handler.cache.Get(key)
if found {
respondWithJSON(w, 200, response)
} else {
- response, err := handler.aws.DescribeCostAndUsage(handler.cfg)
+ response, err := handler.aws.DescribeCostAndUsage(cfg)
if err != nil {
respondWithError(w, http.StatusInternalServerError, "ce:GetCostAndUsage is missing")
} else {
- handler.cache.Set("cost_usage_history", response.History)
+ handler.cache.Set(key, response.History)
respondWithJSON(w, 200, response.History)
}
}
}
func (handler *AWSHandler) CurrentCostHandler(w http.ResponseWriter, r *http.Request) {
- response, found := handler.cache.Get("cost_usage_total")
+ profile := r.Header.Get("profile")
+ cfg, err := external.LoadDefaultAWSConfig()
+
+ if handler.multiple {
+ cfg, err = external.LoadDefaultAWSConfig(external.WithSharedConfigProfile(profile))
+ if err != nil {
+ respondWithError(w, http.StatusInternalServerError, "Couldn't read "+profile+" profile")
+ }
+ }
+
+ key := fmt.Sprintf("aws.%s.ce.total", profile)
+
+ response, found := handler.cache.Get(key)
if found {
respondWithJSON(w, 200, response)
} else {
- response, err := handler.aws.DescribeCostAndUsage(handler.cfg)
+ response, err := handler.aws.DescribeCostAndUsage(cfg)
if err != nil {
respondWithError(w, http.StatusInternalServerError, "ce:GetCostAndUsage is missing")
} else {
- handler.cache.Set("cost_usage_total", response.Total)
+ handler.cache.Set(key, response.Total)
respondWithJSON(w, 200, response.Total)
}
}
}
func (handler *AWSHandler) CostAndUsagePerInstanceTypeHandler(w http.ResponseWriter, r *http.Request) {
- response, found := handler.cache.Get("aws_cost_per_instance_type")
+ profile := r.Header.Get("profile")
+ cfg, err := external.LoadDefaultAWSConfig()
+
+ if handler.multiple {
+ cfg, err = external.LoadDefaultAWSConfig(external.WithSharedConfigProfile(profile))
+ if err != nil {
+ respondWithError(w, http.StatusInternalServerError, "Couldn't read "+profile+" profile")
+ }
+ }
+
+ key := fmt.Sprintf("aws.%s.ce.instance_type", profile)
+
+ response, found := handler.cache.Get(key)
if found {
respondWithJSON(w, 200, response)
} else {
- response, err := handler.aws.DescribeCostAndUsagePerInstanceType(handler.cfg)
+ response, err := handler.aws.DescribeCostAndUsagePerInstanceType(cfg)
if err != nil {
respondWithError(w, http.StatusInternalServerError, "ce:GetCostAndUsage is missing")
} else {
- handler.cache.Set("aws_cost_per_instance_type", response)
+ handler.cache.Set(key, response)
respondWithJSON(w, 200, response)
}
}
}
func (handler *AWSHandler) DescribeForecastPriceHandler(w http.ResponseWriter, r *http.Request) {
- response, found := handler.cache.Get("aws_cost_forecast")
+ profile := r.Header.Get("profile")
+ cfg, err := external.LoadDefaultAWSConfig()
+
+ if handler.multiple {
+ cfg, err = external.LoadDefaultAWSConfig(external.WithSharedConfigProfile(profile))
+ if err != nil {
+ respondWithError(w, http.StatusInternalServerError, "Couldn't read "+profile+" profile")
+ }
+ }
+
+ key := fmt.Sprintf("aws.%s.ce.forecast", profile)
+
+ response, found := handler.cache.Get(key)
if found {
respondWithJSON(w, 200, response)
} else {
- response, err := handler.aws.DescribeForecastPrice(handler.cfg)
+ response, err := handler.aws.DescribeForecastPrice(cfg)
if err != nil {
respondWithError(w, http.StatusInternalServerError, "ce:GetCostForecast is missing")
} else {
- handler.cache.Set("aws_cost_forecast", response)
+ handler.cache.Set(key, response)
respondWithJSON(w, 200, response)
}
}
diff --git a/handlers/aws/datapipeline_handler.go b/handlers/aws/datapipeline_handler.go
index 8683312ac..7f4ebecb2 100644
--- a/handlers/aws/datapipeline_handler.go
+++ b/handlers/aws/datapipeline_handler.go
@@ -1,19 +1,34 @@
package aws
import (
+ "fmt"
"net/http"
+
+ "github.com/aws/aws-sdk-go-v2/aws/external"
)
func (handler *AWSHandler) DataPipelineListPipelines(w http.ResponseWriter, r *http.Request) {
- response, found := handler.cache.Get("aws_datapipeline_pipelines")
+ profile := r.Header.Get("profile")
+ cfg, err := external.LoadDefaultAWSConfig()
+
+ if handler.multiple {
+ cfg, err = external.LoadDefaultAWSConfig(external.WithSharedConfigProfile(profile))
+ if err != nil {
+ respondWithError(w, http.StatusInternalServerError, "Couldn't read "+profile+" profile")
+ }
+ }
+
+ key := fmt.Sprintf("aws.%s.datapipeline.pipelines", profile)
+
+ response, found := handler.cache.Get(key)
if found {
respondWithJSON(w, 200, response)
} else {
- response, err := handler.aws.ListDataPipelines(handler.cfg)
+ response, err := handler.aws.ListDataPipelines(cfg)
if err != nil {
respondWithError(w, http.StatusInternalServerError, "datapipeline:ListPipelines is missing")
} else {
- handler.cache.Set("aws_datapipeline_pipelines", response)
+ handler.cache.Set(key, response)
respondWithJSON(w, 200, response)
}
}
diff --git a/handlers/aws/dynamodb_handler.go b/handlers/aws/dynamodb_handler.go
index b9091ffe0..cc4e9444f 100644
--- a/handlers/aws/dynamodb_handler.go
+++ b/handlers/aws/dynamodb_handler.go
@@ -1,19 +1,34 @@
package aws
import (
+ "fmt"
"net/http"
+
+ "github.com/aws/aws-sdk-go-v2/aws/external"
)
func (handler *AWSHandler) DynamoDBTableHandler(w http.ResponseWriter, r *http.Request) {
- response, found := handler.cache.Get("aws_dynamodb")
+ profile := r.Header.Get("profile")
+ cfg, err := external.LoadDefaultAWSConfig()
+
+ if handler.multiple {
+ cfg, err = external.LoadDefaultAWSConfig(external.WithSharedConfigProfile(profile))
+ if err != nil {
+ respondWithError(w, http.StatusInternalServerError, "Couldn't read "+profile+" profile")
+ }
+ }
+
+ key := fmt.Sprintf("aws.%s.dynamodb.tables", profile)
+
+ response, found := handler.cache.Get(key)
if found {
respondWithJSON(w, 200, response)
} else {
- response, err := handler.aws.DescribeDynamoDBTables(handler.cfg)
+ response, err := handler.aws.DescribeDynamoDBTables(cfg)
if err != nil {
respondWithError(w, http.StatusInternalServerError, "dynamodb:ListTables or dynamodb:DescribeTable is missing")
} else {
- handler.cache.Set("aws_dynamodb", response)
+ handler.cache.Set(key, response)
respondWithJSON(w, 200, response)
}
}
diff --git a/handlers/aws/ebs_handler.go b/handlers/aws/ebs_handler.go
index c04cc691e..9f4fe60b6 100644
--- a/handlers/aws/ebs_handler.go
+++ b/handlers/aws/ebs_handler.go
@@ -1,19 +1,34 @@
package aws
import (
+ "fmt"
"net/http"
+
+ "github.com/aws/aws-sdk-go-v2/aws/external"
)
func (handler *AWSHandler) EBSHandler(w http.ResponseWriter, r *http.Request) {
- response, found := handler.cache.Get("aws_ebs")
+ profile := r.Header.Get("profile")
+ cfg, err := external.LoadDefaultAWSConfig()
+
+ if handler.multiple {
+ cfg, err = external.LoadDefaultAWSConfig(external.WithSharedConfigProfile(profile))
+ if err != nil {
+ respondWithError(w, http.StatusInternalServerError, "Couldn't read "+profile+" profile")
+ }
+ }
+
+ key := fmt.Sprintf("aws.%s.ebs.disks", profile)
+
+ response, found := handler.cache.Get(key)
if found {
respondWithJSON(w, 200, response)
} else {
- response, err := handler.aws.DescribeVolumes(handler.cfg)
+ response, err := handler.aws.DescribeVolumes(cfg)
if err != nil {
respondWithError(w, http.StatusInternalServerError, "ec2:DescribeVolumes is missing")
} else {
- handler.cache.Set("aws_ebs", response)
+ handler.cache.Set(key, response)
respondWithJSON(w, 200, response)
}
}
diff --git a/handlers/aws/ec2_handler.go b/handlers/aws/ec2_handler.go
index ba1878f97..8c1d56091 100644
--- a/handlers/aws/ec2_handler.go
+++ b/handlers/aws/ec2_handler.go
@@ -1,94 +1,169 @@
package aws
import (
+ "fmt"
"net/http"
+
+ "github.com/aws/aws-sdk-go-v2/aws/external"
)
func (handler *AWSHandler) EC2InstancesHandler(w http.ResponseWriter, r *http.Request) {
- response, found := handler.cache.Get("aws_ec2")
+ profile := r.Header.Get("profile")
+ cfg, err := external.LoadDefaultAWSConfig()
+
+ if handler.multiple {
+ cfg, err = external.LoadDefaultAWSConfig(external.WithSharedConfigProfile(profile))
+ if err != nil {
+ respondWithError(w, http.StatusInternalServerError, "Couldn't read "+profile+" profile")
+ }
+ }
+
+ key := fmt.Sprintf("aws.%s.ec2.instances", profile)
+
+ response, found := handler.cache.Get(key)
if found {
respondWithJSON(w, 200, response)
} else {
- response, err := handler.aws.DescribeInstances(handler.cfg)
+ response, err := handler.aws.DescribeInstances(cfg)
if err != nil {
respondWithError(w, http.StatusInternalServerError, "ec2:DescribeInstances is missing")
} else {
- handler.cache.Set("aws_ec2", response)
+ handler.cache.Set(key, response)
respondWithJSON(w, 200, response)
}
}
}
func (handler *AWSHandler) AutoScalingGroupHandler(w http.ResponseWriter, r *http.Request) {
- response, found := handler.cache.Get("aws_asg")
+ profile := r.Header.Get("profile")
+ cfg, err := external.LoadDefaultAWSConfig()
+
+ if handler.multiple {
+ cfg, err = external.LoadDefaultAWSConfig(external.WithSharedConfigProfile(profile))
+ if err != nil {
+ respondWithError(w, http.StatusInternalServerError, "Couldn't read "+profile+" profile")
+ }
+ }
+
+ key := fmt.Sprintf("aws.%s.ec2.asg", profile)
+
+ response, found := handler.cache.Get(key)
if found {
respondWithJSON(w, 200, response)
} else {
- response, err := handler.aws.DescribeAutoScalingGroups(handler.cfg)
+ response, err := handler.aws.DescribeAutoScalingGroups(cfg)
if err != nil {
respondWithError(w, http.StatusInternalServerError, "autoscaling:DescribeAutoScalingGroups is missing")
} else {
- handler.cache.Set("aws_asg", response)
+ handler.cache.Set(key, response)
respondWithJSON(w, 200, response)
}
}
}
func (handler *AWSHandler) ListUnrestrictedSecurityGroups(w http.ResponseWriter, r *http.Request) {
- response, found := handler.cache.Get("aws_sg_unrestricted")
+ profile := r.Header.Get("profile")
+ cfg, err := external.LoadDefaultAWSConfig()
+
+ if handler.multiple {
+ cfg, err = external.LoadDefaultAWSConfig(external.WithSharedConfigProfile(profile))
+ if err != nil {
+ respondWithError(w, http.StatusInternalServerError, "Couldn't read "+profile+" profile")
+ }
+ }
+
+ key := fmt.Sprintf("aws.%s.ec2.sg_unrestricted", profile)
+
+ response, found := handler.cache.Get(key)
if found {
respondWithJSON(w, 200, response)
} else {
- response, err := handler.aws.ListUnrestrictedSecurityGroups(handler.cfg)
+ response, err := handler.aws.ListUnrestrictedSecurityGroups(cfg)
if err != nil {
respondWithError(w, http.StatusInternalServerError, "ec2:DescribeSecurityGroups is missing")
} else {
- handler.cache.Set("aws_sg_unrestricted", response)
+ handler.cache.Set(key, response)
respondWithJSON(w, 200, response)
}
}
}
func (handler *AWSHandler) ScheduledEC2Instances(w http.ResponseWriter, r *http.Request) {
- response, found := handler.cache.Get("aws_ec2_scheduled")
+ profile := r.Header.Get("profile")
+ cfg, err := external.LoadDefaultAWSConfig()
+
+ if handler.multiple {
+ cfg, err = external.LoadDefaultAWSConfig(external.WithSharedConfigProfile(profile))
+ if err != nil {
+ respondWithError(w, http.StatusInternalServerError, "Couldn't read "+profile+" profile")
+ }
+ }
+
+ key := fmt.Sprintf("aws.%s.ec2.scheduled", profile)
+
+ response, found := handler.cache.Get(key)
if found {
respondWithJSON(w, 200, response)
} else {
- response, err := handler.aws.DescribeScheduledInstances(handler.cfg)
+ response, err := handler.aws.DescribeScheduledInstances(cfg)
if err != nil {
respondWithError(w, http.StatusInternalServerError, "ec2:DescribeScheduledInstances is missing")
} else {
- handler.cache.Set("aws_ec2_scheduled", response)
+ handler.cache.Set(key, response)
respondWithJSON(w, 200, response)
}
}
}
func (handler *AWSHandler) SpotEC2Instances(w http.ResponseWriter, r *http.Request) {
- response, found := handler.cache.Get("aws_ec2_spot")
+ profile := r.Header.Get("profile")
+ cfg, err := external.LoadDefaultAWSConfig()
+
+ if handler.multiple {
+ cfg, err = external.LoadDefaultAWSConfig(external.WithSharedConfigProfile(profile))
+ if err != nil {
+ respondWithError(w, http.StatusInternalServerError, "Couldn't read "+profile+" profile")
+ }
+ }
+
+ key := fmt.Sprintf("aws.%s.ec2.spot", profile)
+
+ response, found := handler.cache.Get(key)
if found {
respondWithJSON(w, 200, response)
} else {
- response, err := handler.aws.DescribeSpotInstances(handler.cfg)
+ response, err := handler.aws.DescribeSpotInstances(cfg)
if err != nil {
respondWithError(w, http.StatusInternalServerError, "ec2:DescribeSpotFleetRequests is missing")
} else {
- handler.cache.Set("aws_ec2_spot", response)
+ handler.cache.Set(key, response)
respondWithJSON(w, 200, response)
}
}
}
func (handler *AWSHandler) ReservedEC2Instances(w http.ResponseWriter, r *http.Request) {
- response, found := handler.cache.Get("aws_ec2_reserved")
+ profile := r.Header.Get("profile")
+ cfg, err := external.LoadDefaultAWSConfig()
+
+ if handler.multiple {
+ cfg, err = external.LoadDefaultAWSConfig(external.WithSharedConfigProfile(profile))
+ if err != nil {
+ respondWithError(w, http.StatusInternalServerError, "Couldn't read "+profile+" profile")
+ }
+ }
+
+ key := fmt.Sprintf("aws.%s.ec2.reserved", profile)
+
+ response, found := handler.cache.Get(key)
if found {
respondWithJSON(w, 200, response)
} else {
- response, err := handler.aws.DescribeReservedInstances(handler.cfg)
+ response, err := handler.aws.DescribeReservedInstances(cfg)
if err != nil {
respondWithError(w, http.StatusInternalServerError, "ec2:DescribeReservedInstances is missing")
} else {
- handler.cache.Set("aws_ec2_reserved", response)
+ handler.cache.Set(key, response)
respondWithJSON(w, 200, response)
}
}
diff --git a/handlers/aws/ecs_handler.go b/handlers/aws/ecs_handler.go
index 74a2632c5..c088f9700 100644
--- a/handlers/aws/ecs_handler.go
+++ b/handlers/aws/ecs_handler.go
@@ -1,19 +1,30 @@
package aws
import (
+ "fmt"
"net/http"
+
+ "github.com/aws/aws-sdk-go-v2/aws/external"
)
func (handler *AWSHandler) ECSHandler(w http.ResponseWriter, r *http.Request) {
- response, found := handler.cache.Get("aws_ecs")
+ profile := r.Header.Get("profile")
+ cfg, err := external.LoadDefaultAWSConfig(external.WithSharedConfigProfile(profile))
+ if err != nil {
+ respondWithError(w, http.StatusInternalServerError, "Couldn't read "+profile+" profile")
+ }
+
+ key := fmt.Sprintf("aws.%s.ecs.details", profile)
+
+ response, found := handler.cache.Get(key)
if found {
respondWithJSON(w, 200, response)
} else {
- response, err := handler.aws.DescribeECS(handler.cfg)
+ response, err := handler.aws.DescribeECS(cfg)
if err != nil {
respondWithError(w, http.StatusInternalServerError, "ecs:DescribeClusters or ecs:DescribeTasks or ecs:DescribeServices is missing")
} else {
- handler.cache.Set("aws_ecs", response)
+ handler.cache.Set(key, response)
respondWithJSON(w, 200, response)
}
}
diff --git a/handlers/aws/eks_handler.go b/handlers/aws/eks_handler.go
index 7a283a625..cec38cfdd 100644
--- a/handlers/aws/eks_handler.go
+++ b/handlers/aws/eks_handler.go
@@ -1,19 +1,34 @@
package aws
import (
+ "fmt"
"net/http"
+
+ "github.com/aws/aws-sdk-go-v2/aws/external"
)
func (handler *AWSHandler) EKSClustersHandler(w http.ResponseWriter, r *http.Request) {
- response, found := handler.cache.Get("aws_eks_clusters")
+ profile := r.Header.Get("profile")
+ cfg, err := external.LoadDefaultAWSConfig()
+
+ if handler.multiple {
+ cfg, err = external.LoadDefaultAWSConfig(external.WithSharedConfigProfile(profile))
+ if err != nil {
+ respondWithError(w, http.StatusInternalServerError, "Couldn't read "+profile+" profile")
+ }
+ }
+
+ key := fmt.Sprintf("aws.%s.eks.clusters", profile)
+
+ response, found := handler.cache.Get(key)
if found {
respondWithJSON(w, 200, response)
} else {
- response, err := handler.aws.DescribeEKSClusters(handler.cfg)
+ response, err := handler.aws.DescribeEKSClusters(cfg)
if err != nil {
respondWithError(w, http.StatusInternalServerError, "eks:ListClusters is missing")
} else {
- handler.cache.Set("aws_eks_clusters", response)
+ handler.cache.Set(key, response)
respondWithJSON(w, 200, response)
}
}
diff --git a/handlers/aws/elasticache_handler.go b/handlers/aws/elasticache_handler.go
index b795a10a8..bec1185a3 100644
--- a/handlers/aws/elasticache_handler.go
+++ b/handlers/aws/elasticache_handler.go
@@ -1,19 +1,34 @@
package aws
import (
+ "fmt"
"net/http"
+
+ "github.com/aws/aws-sdk-go-v2/aws/external"
)
func (handler *AWSHandler) ElasticacheClustersHandler(w http.ResponseWriter, r *http.Request) {
- response, found := handler.cache.Get("aws_elasticache")
+ profile := r.Header.Get("profile")
+ cfg, err := external.LoadDefaultAWSConfig()
+
+ if handler.multiple {
+ cfg, err = external.LoadDefaultAWSConfig(external.WithSharedConfigProfile(profile))
+ if err != nil {
+ respondWithError(w, http.StatusInternalServerError, "Couldn't read "+profile+" profile")
+ }
+ }
+
+ key := fmt.Sprintf("aws.%s.elasticache.clusters", profile)
+
+ response, found := handler.cache.Get(key)
if found {
respondWithJSON(w, 200, response)
} else {
- response, err := handler.aws.DescribeCacheClusters(handler.cfg)
+ response, err := handler.aws.DescribeCacheClusters(cfg)
if err != nil {
respondWithError(w, http.StatusInternalServerError, "elasticache:DescribeCacheClusters is missing")
} else {
- handler.cache.Set("aws_elasticache", response)
+ handler.cache.Set(key, response)
respondWithJSON(w, 200, response)
}
}
diff --git a/handlers/aws/elb_handler.go b/handlers/aws/elb_handler.go
index 4dcf86b95..431758bac 100644
--- a/handlers/aws/elb_handler.go
+++ b/handlers/aws/elb_handler.go
@@ -1,34 +1,61 @@
package aws
import (
+ "fmt"
"net/http"
+
+ "github.com/aws/aws-sdk-go-v2/aws/external"
)
func (handler *AWSHandler) ElasticLoadBalancerHandler(w http.ResponseWriter, r *http.Request) {
- response, found := handler.cache.Get("aws_elb")
+ profile := r.Header.Get("profile")
+ cfg, err := external.LoadDefaultAWSConfig()
+
+ if handler.multiple {
+ cfg, err = external.LoadDefaultAWSConfig(external.WithSharedConfigProfile(profile))
+ if err != nil {
+ respondWithError(w, http.StatusInternalServerError, "Couldn't read "+profile+" profile")
+ }
+ }
+
+ key := fmt.Sprintf("aws.%s.elb.total", profile)
+
+ response, found := handler.cache.Get(key)
if found {
respondWithJSON(w, 200, response)
} else {
- response, err := handler.aws.DescribeElasticLoadBalancer(handler.cfg)
+ response, err := handler.aws.DescribeElasticLoadBalancer(cfg)
if err != nil {
respondWithError(w, http.StatusInternalServerError, "elasticloadbalancing:DescribeLoadBalancers is missing")
} else {
- handler.cache.Set("aws_elb", response)
+ handler.cache.Set(key, response)
respondWithJSON(w, 200, response)
}
}
}
func (handler *AWSHandler) ELBRequestsHandler(w http.ResponseWriter, r *http.Request) {
- response, found := handler.cache.Get("aws_elb_requests")
+ profile := r.Header.Get("profile")
+ cfg, err := external.LoadDefaultAWSConfig()
+
+ if handler.multiple {
+ cfg, err = external.LoadDefaultAWSConfig(external.WithSharedConfigProfile(profile))
+ if err != nil {
+ respondWithError(w, http.StatusInternalServerError, "Couldn't read "+profile+" profile")
+ }
+ }
+
+ key := fmt.Sprintf("aws.%s.elb.requests", profile)
+
+ response, found := handler.cache.Get(key)
if found {
respondWithJSON(w, 200, response)
} else {
- response, err := handler.aws.GetELBRequests(handler.cfg)
+ response, err := handler.aws.GetELBRequests(cfg)
if err != nil {
respondWithError(w, http.StatusInternalServerError, "elasticloadbalancing:DescribeLoadBalancers is missing")
} else {
- handler.cache.Set("aws_elb_requests", response)
+ handler.cache.Set(key, response)
respondWithJSON(w, 200, response)
}
}
diff --git a/handlers/aws/es_handler.go b/handlers/aws/es_handler.go
index d535ed495..056768d78 100644
--- a/handlers/aws/es_handler.go
+++ b/handlers/aws/es_handler.go
@@ -1,19 +1,34 @@
package aws
import (
+ "fmt"
"net/http"
+
+ "github.com/aws/aws-sdk-go-v2/aws/external"
)
func (handler *AWSHandler) ESListDomainsHandler(w http.ResponseWriter, r *http.Request) {
- response, found := handler.cache.Get("aws_es_domains")
+ profile := r.Header.Get("profile")
+ cfg, err := external.LoadDefaultAWSConfig()
+
+ if handler.multiple {
+ cfg, err = external.LoadDefaultAWSConfig(external.WithSharedConfigProfile(profile))
+ if err != nil {
+ respondWithError(w, http.StatusInternalServerError, "Couldn't read "+profile+" profile")
+ }
+ }
+
+ key := fmt.Sprintf("aws.%s.es.clusters", profile)
+
+ response, found := handler.cache.Get(key)
if found {
respondWithJSON(w, 200, response)
} else {
- response, err := handler.aws.ListESDomains(handler.cfg)
+ response, err := handler.aws.ListESDomains(cfg)
if err != nil {
respondWithError(w, http.StatusInternalServerError, "es:ListDomainNames is missing")
} else {
- handler.cache.Set("aws_es_domains", response)
+ handler.cache.Set(key, response)
respondWithJSON(w, 200, response)
}
}
diff --git a/handlers/aws/glue_handler.go b/handlers/aws/glue_handler.go
index 3ea06375e..06f3cba2a 100644
--- a/handlers/aws/glue_handler.go
+++ b/handlers/aws/glue_handler.go
@@ -1,34 +1,61 @@
package aws
import (
+ "fmt"
"net/http"
+
+ "github.com/aws/aws-sdk-go-v2/aws/external"
)
func (handler *AWSHandler) GlueGetCrawlersHandler(w http.ResponseWriter, r *http.Request) {
- response, found := handler.cache.Get("aws_glue_crawlers")
+ profile := r.Header.Get("profile")
+ cfg, err := external.LoadDefaultAWSConfig()
+
+ if handler.multiple {
+ cfg, err = external.LoadDefaultAWSConfig(external.WithSharedConfigProfile(profile))
+ if err != nil {
+ respondWithError(w, http.StatusInternalServerError, "Couldn't read "+profile+" profile")
+ }
+ }
+
+ key := fmt.Sprintf("aws.%s.glue.crawlers", profile)
+
+ response, found := handler.cache.Get(key)
if found {
respondWithJSON(w, 200, response)
} else {
- response, err := handler.aws.GetGlueCrawlers(handler.cfg)
+ response, err := handler.aws.GetGlueCrawlers(cfg)
if err != nil {
respondWithError(w, http.StatusInternalServerError, "glue:GetCrawlers is missing")
} else {
- handler.cache.Set("aws_glue_crawlers", response)
+ handler.cache.Set(key, response)
respondWithJSON(w, 200, response)
}
}
}
func (handler *AWSHandler) GlueGetJobsHandler(w http.ResponseWriter, r *http.Request) {
- response, found := handler.cache.Get("aws_glue_jobs")
+ profile := r.Header.Get("profile")
+ cfg, err := external.LoadDefaultAWSConfig()
+
+ if handler.multiple {
+ cfg, err = external.LoadDefaultAWSConfig(external.WithSharedConfigProfile(profile))
+ if err != nil {
+ respondWithError(w, http.StatusInternalServerError, "Couldn't read "+profile+" profile")
+ }
+ }
+
+ key := fmt.Sprintf("aws.%s.glue.jobs", profile)
+
+ response, found := handler.cache.Get(key)
if found {
respondWithJSON(w, 200, response)
} else {
- response, err := handler.aws.GetGlueJobs(handler.cfg)
+ response, err := handler.aws.GetGlueJobs(cfg)
if err != nil {
respondWithError(w, http.StatusInternalServerError, "glue:GetJobs is missing")
} else {
- handler.cache.Set("aws_glue_jobs", response)
+ handler.cache.Set(key, response)
respondWithJSON(w, 200, response)
}
}
diff --git a/handlers/aws/handler.go b/handlers/aws/handler.go
index 89114b7cc..3c43b1d50 100644
--- a/handlers/aws/handler.go
+++ b/handlers/aws/handler.go
@@ -4,22 +4,21 @@ import (
"encoding/json"
"net/http"
- "github.com/aws/aws-sdk-go-v2/aws"
. "github.com/mlabouardy/komiser/services/aws"
. "github.com/mlabouardy/komiser/services/cache"
)
type AWSHandler struct {
- cfg aws.Config
- cache Cache
- aws AWS
+ cache Cache
+ multiple bool
+ aws AWS
}
-func NewAWSHandler(cfg aws.Config, cache Cache) *AWSHandler {
+func NewAWSHandler(cache Cache, multiple bool) *AWSHandler {
awsHandler := AWSHandler{
- cfg: cfg,
- cache: cache,
- aws: AWS{},
+ cache: cache,
+ multiple: multiple,
+ aws: AWS{},
}
return &awsHandler
}
diff --git a/handlers/aws/iam_handler.go b/handlers/aws/iam_handler.go
index 28be8c954..341f49e48 100644
--- a/handlers/aws/iam_handler.go
+++ b/handlers/aws/iam_handler.go
@@ -1,94 +1,169 @@
package aws
import (
+ "fmt"
"net/http"
+
+ "github.com/aws/aws-sdk-go-v2/aws/external"
)
func (handler *AWSHandler) IAMRolesHandler(w http.ResponseWriter, r *http.Request) {
- response, found := handler.cache.Get("aws_role")
+ profile := r.Header.Get("profile")
+ cfg, err := external.LoadDefaultAWSConfig()
+
+ if handler.multiple {
+ cfg, err = external.LoadDefaultAWSConfig(external.WithSharedConfigProfile(profile))
+ if err != nil {
+ respondWithError(w, http.StatusInternalServerError, "Couldn't read "+profile+" profile")
+ }
+ }
+
+ key := fmt.Sprintf("aws.%s.iam.roles", profile)
+
+ response, found := handler.cache.Get(key)
if found {
respondWithJSON(w, 200, response)
} else {
- response, err := handler.aws.DescribeIAMRoles(handler.cfg)
+ response, err := handler.aws.DescribeIAMRoles(cfg)
if err != nil {
respondWithError(w, http.StatusInternalServerError, "iam:ListRoles is missing")
} else {
- handler.cache.Set("aws_role", response)
+ handler.cache.Set(key, response)
respondWithJSON(w, 200, response)
}
}
}
func (handler *AWSHandler) IAMGroupsHandler(w http.ResponseWriter, r *http.Request) {
- response, found := handler.cache.Get("aws_group")
+ profile := r.Header.Get("profile")
+ cfg, err := external.LoadDefaultAWSConfig()
+
+ if handler.multiple {
+ cfg, err = external.LoadDefaultAWSConfig(external.WithSharedConfigProfile(profile))
+ if err != nil {
+ respondWithError(w, http.StatusInternalServerError, "Couldn't read "+profile+" profile")
+ }
+ }
+
+ key := fmt.Sprintf("aws.%s.iam.groups", profile)
+
+ response, found := handler.cache.Get(key)
if found {
respondWithJSON(w, 200, response)
} else {
- response, err := handler.aws.DescribeIAMGroups(handler.cfg)
+ response, err := handler.aws.DescribeIAMGroups(cfg)
if err != nil {
respondWithError(w, http.StatusInternalServerError, "iam:ListGroups is missing")
} else {
- handler.cache.Set("aws_group", response)
+ handler.cache.Set(key, response)
respondWithJSON(w, 200, response)
}
}
}
func (handler *AWSHandler) IAMPoliciesHandler(w http.ResponseWriter, r *http.Request) {
- response, found := handler.cache.Get("aws_policy")
+ profile := r.Header.Get("profile")
+ cfg, err := external.LoadDefaultAWSConfig()
+
+ if handler.multiple {
+ cfg, err = external.LoadDefaultAWSConfig(external.WithSharedConfigProfile(profile))
+ if err != nil {
+ respondWithError(w, http.StatusInternalServerError, "Couldn't read "+profile+" profile")
+ }
+ }
+
+ key := fmt.Sprintf("aws.%s.iam.policies", profile)
+
+ response, found := handler.cache.Get(key)
if found {
respondWithJSON(w, 200, response)
} else {
- response, err := handler.aws.DescribeIAMPolicies(handler.cfg)
+ response, err := handler.aws.DescribeIAMPolicies(cfg)
if err != nil {
respondWithError(w, http.StatusInternalServerError, "iam:ListPolicies is missing")
} else {
- handler.cache.Set("aws_policy", response)
+ handler.cache.Set(key, response)
respondWithJSON(w, 200, response)
}
}
}
func (handler *AWSHandler) IAMUsersHandler(w http.ResponseWriter, r *http.Request) {
- response, found := handler.cache.Get("aws_users")
+ profile := r.Header.Get("profile")
+ cfg, err := external.LoadDefaultAWSConfig()
+
+ if handler.multiple {
+ cfg, err = external.LoadDefaultAWSConfig(external.WithSharedConfigProfile(profile))
+ if err != nil {
+ respondWithError(w, http.StatusInternalServerError, "Couldn't read "+profile+" profile")
+ }
+ }
+
+ key := fmt.Sprintf("aws.%s.iam.users", profile)
+
+ response, found := handler.cache.Get(key)
if found {
respondWithJSON(w, 200, response)
} else {
- response, err := handler.aws.DescribeIAMUsers(handler.cfg)
+ response, err := handler.aws.DescribeIAMUsers(cfg)
if err != nil {
respondWithError(w, http.StatusInternalServerError, "iam:ListUsers is missing")
} else {
- handler.cache.Set("aws_users", response)
+ handler.cache.Set(key, response)
respondWithJSON(w, 200, response)
}
}
}
func (handler *AWSHandler) IAMUserHandler(w http.ResponseWriter, r *http.Request) {
- response, found := handler.cache.Get("aws_user")
+ profile := r.Header.Get("profile")
+ cfg, err := external.LoadDefaultAWSConfig()
+
+ if handler.multiple {
+ cfg, err = external.LoadDefaultAWSConfig(external.WithSharedConfigProfile(profile))
+ if err != nil {
+ respondWithError(w, http.StatusInternalServerError, "Couldn't read "+profile+" profile")
+ }
+ }
+
+ key := fmt.Sprintf("aws.%s.iam.user", profile)
+
+ response, found := handler.cache.Get(key)
if found {
respondWithJSON(w, 200, response)
} else {
- response, err := handler.aws.DescribeIAMUser(handler.cfg)
+ response, err := handler.aws.DescribeIAMUser(cfg)
if err != nil {
respondWithError(w, http.StatusInternalServerError, "iam:GetUser is missing")
} else {
- handler.cache.Set("aws_user", response)
+ handler.cache.Set(key, response)
respondWithJSON(w, 200, response)
}
}
}
func (handler *AWSHandler) DescribeOrganizationHandler(w http.ResponseWriter, r *http.Request) {
- response, found := handler.cache.Get("aws_organization")
+ profile := r.Header.Get("profile")
+ cfg, err := external.LoadDefaultAWSConfig()
+
+ if handler.multiple {
+ cfg, err = external.LoadDefaultAWSConfig(external.WithSharedConfigProfile(profile))
+ if err != nil {
+ respondWithError(w, http.StatusInternalServerError, "Couldn't read "+profile+" profile")
+ }
+ }
+
+ key := fmt.Sprintf("aws.%s.iam.organization", profile)
+
+ response, found := handler.cache.Get(key)
if found {
respondWithJSON(w, 200, response)
} else {
- response, err := handler.aws.DescribeOrganization(handler.cfg)
+ response, err := handler.aws.DescribeOrganization(cfg)
if err != nil {
respondWithError(w, http.StatusInternalServerError, "organizations:DescribeOrganization is missing")
} else {
- handler.cache.Set("aws_organization", response)
+ handler.cache.Set(key, response)
respondWithJSON(w, 200, response)
}
}
diff --git a/handlers/aws/kinesis_handler.go b/handlers/aws/kinesis_handler.go
index 1f3f966ea..c38847b18 100644
--- a/handlers/aws/kinesis_handler.go
+++ b/handlers/aws/kinesis_handler.go
@@ -1,34 +1,61 @@
package aws
import (
+ "fmt"
"net/http"
+
+ "github.com/aws/aws-sdk-go-v2/aws/external"
)
func (handler *AWSHandler) KinesisListStreamsHandler(w http.ResponseWriter, r *http.Request) {
- response, found := handler.cache.Get("aws_kinesis_streams")
+ profile := r.Header.Get("profile")
+ cfg, err := external.LoadDefaultAWSConfig()
+
+ if handler.multiple {
+ cfg, err = external.LoadDefaultAWSConfig(external.WithSharedConfigProfile(profile))
+ if err != nil {
+ respondWithError(w, http.StatusInternalServerError, "Couldn't read "+profile+" profile")
+ }
+ }
+
+ key := fmt.Sprintf("aws.%s.kinesis.streams", profile)
+
+ response, found := handler.cache.Get(key)
if found {
respondWithJSON(w, 200, response)
} else {
- response, err := handler.aws.ListStreams(handler.cfg)
+ response, err := handler.aws.ListStreams(cfg)
if err != nil {
respondWithError(w, http.StatusInternalServerError, "kinesis:ListStreams is missing")
} else {
- handler.cache.Set("aws_kinesis_streams", response)
+ handler.cache.Set(key, response)
respondWithJSON(w, 200, response)
}
}
}
func (handler *AWSHandler) KinesisListShardsHandler(w http.ResponseWriter, r *http.Request) {
- response, found := handler.cache.Get("aws_kinesis_shards")
+ profile := r.Header.Get("profile")
+ cfg, err := external.LoadDefaultAWSConfig()
+
+ if handler.multiple {
+ cfg, err = external.LoadDefaultAWSConfig(external.WithSharedConfigProfile(profile))
+ if err != nil {
+ respondWithError(w, http.StatusInternalServerError, "Couldn't read "+profile+" profile")
+ }
+ }
+
+ key := fmt.Sprintf("aws.%s.kinesis.shards", profile)
+
+ response, found := handler.cache.Get(key)
if found {
respondWithJSON(w, 200, response)
} else {
- response, err := handler.aws.ListShards(handler.cfg)
+ response, err := handler.aws.ListShards(cfg)
if err != nil {
respondWithError(w, http.StatusInternalServerError, "kinesis:ListShards is missing")
} else {
- handler.cache.Set("aws_kinesis_shards", response)
+ handler.cache.Set(key, response)
respondWithJSON(w, 200, response)
}
}
diff --git a/handlers/aws/kms_handler.go b/handlers/aws/kms_handler.go
index bb58b1213..e8ed86f62 100644
--- a/handlers/aws/kms_handler.go
+++ b/handlers/aws/kms_handler.go
@@ -1,19 +1,34 @@
package aws
import (
+ "fmt"
"net/http"
+
+ "github.com/aws/aws-sdk-go-v2/aws/external"
)
func (handler *AWSHandler) KMSKeysHandler(w http.ResponseWriter, r *http.Request) {
- response, found := handler.cache.Get("aws_kms_keys")
+ profile := r.Header.Get("profile")
+ cfg, err := external.LoadDefaultAWSConfig()
+
+ if handler.multiple {
+ cfg, err = external.LoadDefaultAWSConfig(external.WithSharedConfigProfile(profile))
+ if err != nil {
+ respondWithError(w, http.StatusInternalServerError, "Couldn't read "+profile+" profile")
+ }
+ }
+
+ key := fmt.Sprintf("aws.%s.kms.keys", profile)
+
+ response, found := handler.cache.Get(key)
if found {
respondWithJSON(w, 200, response)
} else {
- response, err := handler.aws.ListKeys(handler.cfg)
+ response, err := handler.aws.ListKeys(cfg)
if err != nil {
respondWithError(w, http.StatusInternalServerError, "kms:ListKeys is missing")
} else {
- handler.cache.Set("aws_kms_keys", response)
+ handler.cache.Set(key, response)
respondWithJSON(w, 200, response)
}
}
diff --git a/handlers/aws/lambda_handler.go b/handlers/aws/lambda_handler.go
index d30c1bad9..d8847de3e 100644
--- a/handlers/aws/lambda_handler.go
+++ b/handlers/aws/lambda_handler.go
@@ -1,49 +1,88 @@
package aws
import (
+ "fmt"
"net/http"
+
+ "github.com/aws/aws-sdk-go-v2/aws/external"
)
func (handler *AWSHandler) LambdaFunctionHandler(w http.ResponseWriter, r *http.Request) {
- response, found := handler.cache.Get("aws_lambda_functions")
+ profile := r.Header.Get("profile")
+ cfg, err := external.LoadDefaultAWSConfig()
+
+ if handler.multiple {
+ cfg, err = external.LoadDefaultAWSConfig(external.WithSharedConfigProfile(profile))
+ if err != nil {
+ respondWithError(w, http.StatusInternalServerError, "Couldn't read "+profile+" profile")
+ }
+ }
+
+ key := fmt.Sprintf("aws.%s.lambda.functions", profile)
+
+ response, found := handler.cache.Get(key)
if found {
respondWithJSON(w, 200, response)
} else {
- response, err := handler.aws.DescribeLambdaFunctions(handler.cfg)
+ response, err := handler.aws.DescribeLambdaFunctions(cfg)
if err != nil {
respondWithError(w, http.StatusInternalServerError, "lambda:ListFunctions is missing")
} else {
- handler.cache.Set("aws_lambda_functions", response)
+ handler.cache.Set(key, response)
respondWithJSON(w, 200, response)
}
}
}
func (handler *AWSHandler) GetLambdaInvocationMetrics(w http.ResponseWriter, r *http.Request) {
- response, found := handler.cache.Get("aws_lambda_invocations")
+ profile := r.Header.Get("profile")
+ cfg, err := external.LoadDefaultAWSConfig()
+
+ if handler.multiple {
+ cfg, err = external.LoadDefaultAWSConfig(external.WithSharedConfigProfile(profile))
+ if err != nil {
+ respondWithError(w, http.StatusInternalServerError, "Couldn't read "+profile+" profile")
+ }
+ }
+
+ key := fmt.Sprintf("aws.%s.lambda.invocations", profile)
+
+ response, found := handler.cache.Get(key)
if found {
respondWithJSON(w, 200, response)
} else {
- response, err := handler.aws.GetLambdaInvocationMetrics(handler.cfg)
+ response, err := handler.aws.GetLambdaInvocationMetrics(cfg)
if err != nil {
respondWithError(w, http.StatusInternalServerError, "cloudwatch:GetMetricStatistics is missing")
} else {
- handler.cache.Set("aws_lambda_invocations", response)
+ handler.cache.Set(key, response)
respondWithJSON(w, 200, response)
}
}
}
func (handler *AWSHandler) GetLambdaErrorsMetrics(w http.ResponseWriter, r *http.Request) {
- response, found := handler.cache.Get("aws_lambda_errors")
+ profile := r.Header.Get("profile")
+ cfg, err := external.LoadDefaultAWSConfig()
+
+ if handler.multiple {
+ cfg, err = external.LoadDefaultAWSConfig(external.WithSharedConfigProfile(profile))
+ if err != nil {
+ respondWithError(w, http.StatusInternalServerError, "Couldn't read "+profile+" profile")
+ }
+ }
+
+ key := fmt.Sprintf("aws.%s.lambda.errors", profile)
+
+ response, found := handler.cache.Get(key)
if found {
respondWithJSON(w, 200, response)
} else {
- response, err := handler.aws.GetLambdaErrorsMetrics(handler.cfg)
+ response, err := handler.aws.GetLambdaErrorsMetrics(cfg)
if err != nil {
respondWithError(w, http.StatusInternalServerError, "cloudwatch:GetMetricStatistics is missing")
} else {
- handler.cache.Set("aws_lambda_errors", response)
+ handler.cache.Set(key, response)
respondWithJSON(w, 200, response)
}
}
diff --git a/handlers/aws/logs_handler.go b/handlers/aws/logs_handler.go
index 5d40b0996..52e1d9572 100644
--- a/handlers/aws/logs_handler.go
+++ b/handlers/aws/logs_handler.go
@@ -1,34 +1,61 @@
package aws
import (
+ "fmt"
"net/http"
+
+ "github.com/aws/aws-sdk-go-v2/aws/external"
)
func (handler *AWSHandler) LogsVolumeHandler(w http.ResponseWriter, r *http.Request) {
- response, found := handler.cache.Get("aws_logs_volume")
+ profile := r.Header.Get("profile")
+ cfg, err := external.LoadDefaultAWSConfig()
+
+ if handler.multiple {
+ cfg, err = external.LoadDefaultAWSConfig(external.WithSharedConfigProfile(profile))
+ if err != nil {
+ respondWithError(w, http.StatusInternalServerError, "Couldn't read "+profile+" profile")
+ }
+ }
+
+ key := fmt.Sprintf("aws.%s.logs.volume", profile)
+
+ response, found := handler.cache.Get(key)
if found {
respondWithJSON(w, 200, response)
} else {
- response, err := handler.aws.GetLogsVolume(handler.cfg)
+ response, err := handler.aws.GetLogsVolume(cfg)
if err != nil {
respondWithError(w, http.StatusInternalServerError, "cloudwatch:GetMetricStatistics is missing")
} else {
- handler.cache.Set("aws_logs_volume", response)
+ handler.cache.Set(key, response)
respondWithJSON(w, 200, response)
}
}
}
func (handler *AWSHandler) MaximumLogsRetentionPeriodHandler(w http.ResponseWriter, r *http.Request) {
- response, found := handler.cache.Get("aws_logs_retention")
+ profile := r.Header.Get("profile")
+ cfg, err := external.LoadDefaultAWSConfig()
+
+ if handler.multiple {
+ cfg, err = external.LoadDefaultAWSConfig(external.WithSharedConfigProfile(profile))
+ if err != nil {
+ respondWithError(w, http.StatusInternalServerError, "Couldn't read "+profile+" profile")
+ }
+ }
+
+ key := fmt.Sprintf("aws.%s.logs.retention", profile)
+
+ response, found := handler.cache.Get(key)
if found {
respondWithJSON(w, 200, response)
} else {
- response, err := handler.aws.MaximumLogsRetentionPeriod(handler.cfg)
+ response, err := handler.aws.MaximumLogsRetentionPeriod(cfg)
if err != nil {
respondWithError(w, http.StatusInternalServerError, "logs:DescribeLogGroups is missing")
} else {
- handler.cache.Set("aws_logs_retention", response)
+ handler.cache.Set(key, response)
respondWithJSON(w, 200, response)
}
}
diff --git a/handlers/aws/rds_handler.go b/handlers/aws/rds_handler.go
index b7acdbe50..8206d81cf 100644
--- a/handlers/aws/rds_handler.go
+++ b/handlers/aws/rds_handler.go
@@ -1,19 +1,34 @@
package aws
import (
+ "fmt"
"net/http"
+
+ "github.com/aws/aws-sdk-go-v2/aws/external"
)
func (handler *AWSHandler) RDSInstanceHandler(w http.ResponseWriter, r *http.Request) {
- response, found := handler.cache.Get("aws_rds")
+ profile := r.Header.Get("profile")
+ cfg, err := external.LoadDefaultAWSConfig()
+
+ if handler.multiple {
+ cfg, err = external.LoadDefaultAWSConfig(external.WithSharedConfigProfile(profile))
+ if err != nil {
+ respondWithError(w, http.StatusInternalServerError, "Couldn't read "+profile+" profile")
+ }
+ }
+
+ key := fmt.Sprintf("aws.%s.rds.instances", profile)
+
+ response, found := handler.cache.Get(key)
if found {
respondWithJSON(w, 200, response)
} else {
- response, err := handler.aws.DescribeRDSInstances(handler.cfg)
+ response, err := handler.aws.DescribeRDSInstances(cfg)
if err != nil {
respondWithError(w, http.StatusInternalServerError, "rds:DescribeDBInstances is missing")
} else {
- handler.cache.Set("aws_rds", response)
+ handler.cache.Set(key, response)
respondWithJSON(w, 200, response)
}
}
diff --git a/handlers/aws/redshift_handler.go b/handlers/aws/redshift_handler.go
index b050016e2..297f26766 100644
--- a/handlers/aws/redshift_handler.go
+++ b/handlers/aws/redshift_handler.go
@@ -1,19 +1,34 @@
package aws
import (
+ "fmt"
"net/http"
+
+ "github.com/aws/aws-sdk-go-v2/aws/external"
)
func (handler *AWSHandler) DescribeRedshiftClustersHandler(w http.ResponseWriter, r *http.Request) {
- response, found := handler.cache.Get("aws_redshift_clusters")
+ profile := r.Header.Get("profile")
+ cfg, err := external.LoadDefaultAWSConfig()
+
+ if handler.multiple {
+ cfg, err = external.LoadDefaultAWSConfig(external.WithSharedConfigProfile(profile))
+ if err != nil {
+ respondWithError(w, http.StatusInternalServerError, "Couldn't read "+profile+" profile")
+ }
+ }
+
+ key := fmt.Sprintf("aws.%s.redshift.clusters", profile)
+
+ response, found := handler.cache.Get(key)
if found {
respondWithJSON(w, 200, response)
} else {
- response, err := handler.aws.DescribeRedshiftClusters(handler.cfg)
+ response, err := handler.aws.DescribeRedshiftClusters(cfg)
if err != nil {
respondWithError(w, http.StatusInternalServerError, "redshift:DescribeClusters is missing")
} else {
- handler.cache.Set("aws_redshift_clusters", response)
+ handler.cache.Set(key, response)
respondWithJSON(w, 200, response)
}
}
diff --git a/handlers/aws/resources_handler.go b/handlers/aws/resources_handler.go
index 1989e54b9..5225adb59 100644
--- a/handlers/aws/resources_handler.go
+++ b/handlers/aws/resources_handler.go
@@ -1,19 +1,34 @@
package aws
import (
+ "fmt"
"net/http"
+
+ "github.com/aws/aws-sdk-go-v2/aws/external"
)
func (handler *AWSHandler) UsedRegionsHandler(w http.ResponseWriter, r *http.Request) {
- response, found := handler.cache.Get("aws_used_regions")
+ profile := r.Header.Get("profile")
+ cfg, err := external.LoadDefaultAWSConfig()
+
+ if handler.multiple {
+ cfg, err = external.LoadDefaultAWSConfig(external.WithSharedConfigProfile(profile))
+ if err != nil {
+ respondWithError(w, http.StatusInternalServerError, "Couldn't read "+profile+" profile")
+ }
+ }
+
+ key := fmt.Sprintf("aws.%s.resources.regions", profile)
+
+ response, found := handler.cache.Get(key)
if found {
respondWithJSON(w, 200, response)
} else {
- response, err := handler.aws.DescribeResources(handler.cfg)
+ response, err := handler.aws.DescribeResources(cfg)
if err != nil {
respondWithError(w, http.StatusInternalServerError, "tag:GetResources is missing")
} else {
- handler.cache.Set("aws_used_regions", response)
+ handler.cache.Set(key, response)
respondWithJSON(w, 200, response)
}
}
diff --git a/handlers/aws/route53_handler.go b/handlers/aws/route53_handler.go
index f68a477fd..7df0d318f 100644
--- a/handlers/aws/route53_handler.go
+++ b/handlers/aws/route53_handler.go
@@ -1,34 +1,61 @@
package aws
import (
+ "fmt"
"net/http"
+
+ "github.com/aws/aws-sdk-go-v2/aws/external"
)
func (handler *AWSHandler) Route53HostedZonesHandler(w http.ResponseWriter, r *http.Request) {
- response, found := handler.cache.Get("aws_route53_zones")
+ profile := r.Header.Get("profile")
+ cfg, err := external.LoadDefaultAWSConfig()
+
+ if handler.multiple {
+ cfg, err = external.LoadDefaultAWSConfig(external.WithSharedConfigProfile(profile))
+ if err != nil {
+ respondWithError(w, http.StatusInternalServerError, "Couldn't read "+profile+" profile")
+ }
+ }
+
+ key := fmt.Sprintf("aws.%s.route53.zones", profile)
+
+ response, found := handler.cache.Get(key)
if found {
respondWithJSON(w, 200, response)
} else {
- response, err := handler.aws.DescribeHostedZones(handler.cfg)
+ response, err := handler.aws.DescribeHostedZones(cfg)
if err != nil {
respondWithError(w, http.StatusInternalServerError, "route53:ListHostedZones is missing")
} else {
- handler.cache.Set("aws_route53_zones", response)
+ handler.cache.Set(key, response)
respondWithJSON(w, 200, response)
}
}
}
func (handler *AWSHandler) Route53ARecordsHandler(w http.ResponseWriter, r *http.Request) {
- response, found := handler.cache.Get("aws_route53_a_records")
+ profile := r.Header.Get("profile")
+ cfg, err := external.LoadDefaultAWSConfig()
+
+ if handler.multiple {
+ cfg, err = external.LoadDefaultAWSConfig(external.WithSharedConfigProfile(profile))
+ if err != nil {
+ respondWithError(w, http.StatusInternalServerError, "Couldn't read "+profile+" profile")
+ }
+ }
+
+ key := fmt.Sprintf("aws.%s.route53.records", profile)
+
+ response, found := handler.cache.Get(key)
if found {
respondWithJSON(w, 200, response)
} else {
- response, err := handler.aws.DescribeARecords(handler.cfg)
+ response, err := handler.aws.DescribeARecords(cfg)
if err != nil {
respondWithError(w, http.StatusInternalServerError, "route53:ListResourceRecordSets is missing")
} else {
- handler.cache.Set("aws_route53_a_records", response)
+ handler.cache.Set(key, response)
respondWithJSON(w, 200, response)
}
}
diff --git a/handlers/aws/s3_handler.go b/handlers/aws/s3_handler.go
index 8361cf7c7..f285596f2 100644
--- a/handlers/aws/s3_handler.go
+++ b/handlers/aws/s3_handler.go
@@ -1,64 +1,115 @@
package aws
import (
+ "fmt"
"net/http"
+
+ "github.com/aws/aws-sdk-go-v2/aws/external"
)
func (handler *AWSHandler) S3BucketsHandler(w http.ResponseWriter, r *http.Request) {
- response, found := handler.cache.Get("aws_s3")
+ profile := r.Header.Get("profile")
+ cfg, err := external.LoadDefaultAWSConfig()
+
+ if handler.multiple {
+ cfg, err = external.LoadDefaultAWSConfig(external.WithSharedConfigProfile(profile))
+ if err != nil {
+ respondWithError(w, http.StatusInternalServerError, "Couldn't read "+profile+" profile")
+ }
+ }
+
+ key := fmt.Sprintf("aws.%s.s3.buckets", profile)
+
+ response, found := handler.cache.Get(key)
if found {
respondWithJSON(w, 200, response)
} else {
- response, err := handler.aws.DescribeS3Buckets(handler.cfg)
+ response, err := handler.aws.DescribeS3Buckets(cfg)
if err != nil {
respondWithError(w, http.StatusInternalServerError, "s3:ListAllMyBuckets is missing")
} else {
- handler.cache.Set("aws_s3", response)
+ handler.cache.Set(key, response)
respondWithJSON(w, 200, response)
}
}
}
func (handler *AWSHandler) S3BucketsSizeHandler(w http.ResponseWriter, r *http.Request) {
- response, found := handler.cache.Get("aws_s3_size")
+ profile := r.Header.Get("profile")
+ cfg, err := external.LoadDefaultAWSConfig()
+
+ if handler.multiple {
+ cfg, err = external.LoadDefaultAWSConfig(external.WithSharedConfigProfile(profile))
+ if err != nil {
+ respondWithError(w, http.StatusInternalServerError, "Couldn't read "+profile+" profile")
+ }
+ }
+
+ key := fmt.Sprintf("aws.%s.s3.size", profile)
+
+ response, found := handler.cache.Get(key)
if found {
respondWithJSON(w, 200, response)
} else {
- response, err := handler.aws.GetBucketsSize(handler.cfg)
+ response, err := handler.aws.GetBucketsSize(cfg)
if err != nil {
respondWithError(w, http.StatusInternalServerError, "cloudwatch:GetMetricStatistics is missing")
} else {
- handler.cache.Set("aws_s3_size", response)
+ handler.cache.Set(key, response)
respondWithJSON(w, 200, response)
}
}
}
func (handler *AWSHandler) S3BucketsObjectsHandler(w http.ResponseWriter, r *http.Request) {
- response, found := handler.cache.Get("aws_s3_objects")
+ profile := r.Header.Get("profile")
+ cfg, err := external.LoadDefaultAWSConfig()
+
+ if handler.multiple {
+ cfg, err = external.LoadDefaultAWSConfig(external.WithSharedConfigProfile(profile))
+ if err != nil {
+ respondWithError(w, http.StatusInternalServerError, "Couldn't read "+profile+" profile")
+ }
+ }
+
+ key := fmt.Sprintf("aws.%s.s3.objects", profile)
+
+ response, found := handler.cache.Get(key)
if found {
respondWithJSON(w, 200, response)
} else {
- response, err := handler.aws.GetBucketsObjects(handler.cfg)
+ response, err := handler.aws.GetBucketsObjects(cfg)
if err != nil {
respondWithError(w, http.StatusInternalServerError, "cloudwatch:GetMetricStatistics is missing")
} else {
- handler.cache.Set("aws_s3_objects", response)
+ handler.cache.Set(key, response)
respondWithJSON(w, 200, response)
}
}
}
func (handler *AWSHandler) GetEmptyBucketsHandler(w http.ResponseWriter, r *http.Request) {
- response, found := handler.cache.Get("aws_s3_empty")
+ profile := r.Header.Get("profile")
+ cfg, err := external.LoadDefaultAWSConfig()
+
+ if handler.multiple {
+ cfg, err = external.LoadDefaultAWSConfig(external.WithSharedConfigProfile(profile))
+ if err != nil {
+ respondWithError(w, http.StatusInternalServerError, "Couldn't read "+profile+" profile")
+ }
+ }
+
+ key := fmt.Sprintf("aws.%s.s3.empty", profile)
+
+ response, found := handler.cache.Get(key)
if found {
respondWithJSON(w, 200, response)
} else {
- response, err := handler.aws.GetEmptyBuckets(handler.cfg)
+ response, err := handler.aws.GetEmptyBuckets(cfg)
if err != nil {
respondWithError(w, http.StatusInternalServerError, "cloudwatch:GetMetricStatistics is missing")
} else {
- handler.cache.Set("aws_s3_empty", response)
+ handler.cache.Set(key, response)
respondWithJSON(w, 200, response)
}
}
diff --git a/handlers/aws/snapshot_handler.go b/handlers/aws/snapshot_handler.go
index fecbbc34f..49d7f3635 100644
--- a/handlers/aws/snapshot_handler.go
+++ b/handlers/aws/snapshot_handler.go
@@ -1,19 +1,34 @@
package aws
import (
+ "fmt"
"net/http"
+
+ "github.com/aws/aws-sdk-go-v2/aws/external"
)
func (handler *AWSHandler) SnapshotHandler(w http.ResponseWriter, r *http.Request) {
- response, found := handler.cache.Get("aws_snapshot")
+ profile := r.Header.Get("profile")
+ cfg, err := external.LoadDefaultAWSConfig()
+
+ if handler.multiple {
+ cfg, err = external.LoadDefaultAWSConfig(external.WithSharedConfigProfile(profile))
+ if err != nil {
+ respondWithError(w, http.StatusInternalServerError, "Couldn't read "+profile+" profile")
+ }
+ }
+
+ key := fmt.Sprintf("aws.%s.snapshots.total", profile)
+
+ response, found := handler.cache.Get(key)
if found {
respondWithJSON(w, 200, response)
} else {
- response, err := handler.aws.DescribeSnapshots(handler.cfg)
+ response, err := handler.aws.DescribeSnapshots(cfg)
if err != nil {
respondWithError(w, http.StatusInternalServerError, "ec2:DescribeSnapshots is missing")
} else {
- handler.cache.Set("aws_snapshot", response)
+ handler.cache.Set(key, response)
respondWithJSON(w, 200, response)
}
}
diff --git a/handlers/aws/sns_handler.go b/handlers/aws/sns_handler.go
index 4076ef904..f718a0e87 100644
--- a/handlers/aws/sns_handler.go
+++ b/handlers/aws/sns_handler.go
@@ -1,19 +1,34 @@
package aws
import (
+ "fmt"
"net/http"
+
+ "github.com/aws/aws-sdk-go-v2/aws/external"
)
func (handler *AWSHandler) SNSTopicsHandler(w http.ResponseWriter, r *http.Request) {
- response, found := handler.cache.Get("aws_sns")
+ profile := r.Header.Get("profile")
+ cfg, err := external.LoadDefaultAWSConfig()
+
+ if handler.multiple {
+ cfg, err = external.LoadDefaultAWSConfig(external.WithSharedConfigProfile(profile))
+ if err != nil {
+ respondWithError(w, http.StatusInternalServerError, "Couldn't read "+profile+" profile")
+ }
+ }
+
+ key := fmt.Sprintf("aws.%s.sns.topics", profile)
+
+ response, found := handler.cache.Get(key)
if found {
respondWithJSON(w, 200, response)
} else {
- response, err := handler.aws.DescribeSNSTopics(handler.cfg)
+ response, err := handler.aws.DescribeSNSTopics(cfg)
if err != nil {
respondWithError(w, http.StatusInternalServerError, "sns:ListTopics is missing")
} else {
- handler.cache.Set("aws_sns", response)
+ handler.cache.Set(key, response)
respondWithJSON(w, 200, response)
}
}
diff --git a/handlers/aws/sqs_handler.go b/handlers/aws/sqs_handler.go
index ff88bfe83..0816826de 100644
--- a/handlers/aws/sqs_handler.go
+++ b/handlers/aws/sqs_handler.go
@@ -1,34 +1,61 @@
package aws
import (
+ "fmt"
"net/http"
+
+ "github.com/aws/aws-sdk-go-v2/aws/external"
)
func (handler *AWSHandler) SQSQueuesHandler(w http.ResponseWriter, r *http.Request) {
- response, found := handler.cache.Get("aws_sqs")
+ profile := r.Header.Get("profile")
+ cfg, err := external.LoadDefaultAWSConfig()
+
+ if handler.multiple {
+ cfg, err = external.LoadDefaultAWSConfig(external.WithSharedConfigProfile(profile))
+ if err != nil {
+ respondWithError(w, http.StatusInternalServerError, "Couldn't read "+profile+" profile")
+ }
+ }
+
+ key := fmt.Sprintf("aws.%s.sqs.queues", profile)
+
+ response, found := handler.cache.Get(key)
if found {
respondWithJSON(w, 200, response)
} else {
- response, err := handler.aws.DescribeQueues(handler.cfg)
+ response, err := handler.aws.DescribeQueues(cfg)
if err != nil {
respondWithError(w, http.StatusInternalServerError, "sqs:ListQueues is missing")
} else {
- handler.cache.Set("aws_sqs", response)
+ handler.cache.Set(key, response)
respondWithJSON(w, 200, response)
}
}
}
func (handler *AWSHandler) GetNumberOfMessagesSentAndDeletedSQSHandler(w http.ResponseWriter, r *http.Request) {
- response, found := handler.cache.Get("aws_sqs_messages")
+ profile := r.Header.Get("profile")
+ cfg, err := external.LoadDefaultAWSConfig()
+
+ if handler.multiple {
+ cfg, err = external.LoadDefaultAWSConfig(external.WithSharedConfigProfile(profile))
+ if err != nil {
+ respondWithError(w, http.StatusInternalServerError, "Couldn't read "+profile+" profile")
+ }
+ }
+
+ key := fmt.Sprintf("aws.%s.sqs.messages", profile)
+
+ response, found := handler.cache.Get(key)
if found {
respondWithJSON(w, 200, response)
} else {
- response, err := handler.aws.GetNumberOfMessagesSentAndDeletedSQS(handler.cfg)
+ response, err := handler.aws.GetNumberOfMessagesSentAndDeletedSQS(cfg)
if err != nil {
respondWithError(w, http.StatusInternalServerError, "cloudwatch:GetMetricStatistics is missing")
} else {
- handler.cache.Set("aws_sqs_messages", response)
+ handler.cache.Set(key, response)
respondWithJSON(w, 200, response)
}
}
diff --git a/handlers/aws/support_handler.go b/handlers/aws/support_handler.go
index 82e54d266..c26784520 100644
--- a/handlers/aws/support_handler.go
+++ b/handlers/aws/support_handler.go
@@ -1,49 +1,88 @@
package aws
import (
+ "fmt"
"net/http"
+
+ "github.com/aws/aws-sdk-go-v2/aws/external"
)
func (handler *AWSHandler) SupportOpenTicketsHandler(w http.ResponseWriter, r *http.Request) {
- response, found := handler.cache.Get("aws_support_open_tickets")
+ profile := r.Header.Get("profile")
+ cfg, err := external.LoadDefaultAWSConfig()
+
+ if handler.multiple {
+ cfg, err = external.LoadDefaultAWSConfig(external.WithSharedConfigProfile(profile))
+ if err != nil {
+ respondWithError(w, http.StatusInternalServerError, "Couldn't read "+profile+" profile")
+ }
+ }
+
+ key := fmt.Sprintf("aws.%s.support.open", profile)
+
+ response, found := handler.cache.Get(key)
if found {
respondWithJSON(w, 200, response)
} else {
- response, err := handler.aws.OpenSupportTickets(handler.cfg)
+ response, err := handler.aws.OpenSupportTickets(cfg)
if err != nil {
respondWithError(w, http.StatusInternalServerError, "support:DescribeCases is missing")
} else {
- handler.cache.Set("aws_support_open_tickets", response)
+ handler.cache.Set(key, response)
respondWithJSON(w, 200, response)
}
}
}
func (handler *AWSHandler) SupportTicketsInLastSixMonthsHandlers(w http.ResponseWriter, r *http.Request) {
- response, found := handler.cache.Get("aws_support_history")
+ profile := r.Header.Get("profile")
+ cfg, err := external.LoadDefaultAWSConfig()
+
+ if handler.multiple {
+ cfg, err = external.LoadDefaultAWSConfig(external.WithSharedConfigProfile(profile))
+ if err != nil {
+ respondWithError(w, http.StatusInternalServerError, "Couldn't read "+profile+" profile")
+ }
+ }
+
+ key := fmt.Sprintf("aws.%s.support.history", profile)
+
+ response, found := handler.cache.Get(key)
if found {
respondWithJSON(w, 200, response)
} else {
- response, err := handler.aws.TicketsInLastSixMonthsTickets(handler.cfg)
+ response, err := handler.aws.TicketsInLastSixMonthsTickets(cfg)
if err != nil {
respondWithError(w, http.StatusInternalServerError, "support:DescribeCases is missing")
} else {
- handler.cache.Set("aws_support_history", response)
+ handler.cache.Set(key, response)
respondWithJSON(w, 200, response)
}
}
}
func (handler *AWSHandler) DescribeServiceLimitsChecks(w http.ResponseWriter, r *http.Request) {
- response, found := handler.cache.Get("aws_service_limits")
+ profile := r.Header.Get("profile")
+ cfg, err := external.LoadDefaultAWSConfig()
+
+ if handler.multiple {
+ cfg, err = external.LoadDefaultAWSConfig(external.WithSharedConfigProfile(profile))
+ if err != nil {
+ respondWithError(w, http.StatusInternalServerError, "Couldn't read "+profile+" profile")
+ }
+ }
+
+ key := fmt.Sprintf("aws.%s.support.limits", profile)
+
+ response, found := handler.cache.Get(key)
if found {
respondWithJSON(w, 200, response)
} else {
- response, err := handler.aws.DescribeServiceLimitsChecks(handler.cfg)
+ response, err := handler.aws.DescribeServiceLimitsChecks(cfg)
if err != nil {
respondWithError(w, http.StatusInternalServerError, "support:DescribeTrustedAdvisorChecks is missing")
} else {
- handler.cache.Set("aws_service_limits", response)
+ handler.cache.Set(key, response)
respondWithJSON(w, 200, response)
}
}
diff --git a/handlers/aws/swf_handler.go b/handlers/aws/swf_handler.go
index 939f774bf..27053fe1b 100644
--- a/handlers/aws/swf_handler.go
+++ b/handlers/aws/swf_handler.go
@@ -1,19 +1,34 @@
package aws
import (
+ "fmt"
"net/http"
+
+ "github.com/aws/aws-sdk-go-v2/aws/external"
)
func (handler *AWSHandler) SWFListDomainsHandler(w http.ResponseWriter, r *http.Request) {
- response, found := handler.cache.Get("aws_swf_domains")
+ profile := r.Header.Get("profile")
+ cfg, err := external.LoadDefaultAWSConfig()
+
+ if handler.multiple {
+ cfg, err = external.LoadDefaultAWSConfig(external.WithSharedConfigProfile(profile))
+ if err != nil {
+ respondWithError(w, http.StatusInternalServerError, "Couldn't read "+profile+" profile")
+ }
+ }
+
+ key := fmt.Sprintf("aws.%s.swf.domains", profile)
+
+ response, found := handler.cache.Get(key)
if found {
respondWithJSON(w, 200, response)
} else {
- response, err := handler.aws.GetSWFDomains(handler.cfg)
+ response, err := handler.aws.GetSWFDomains(cfg)
if err != nil {
respondWithError(w, http.StatusInternalServerError, "swf:ListDomains is missing")
} else {
- handler.cache.Set("aws_swf_domains", response)
+ handler.cache.Set(key, response)
respondWithJSON(w, 200, response)
}
}
diff --git a/handlers/aws/vpc_handler.go b/handlers/aws/vpc_handler.go
index 554c33e5c..15c6466a4 100644
--- a/handlers/aws/vpc_handler.go
+++ b/handlers/aws/vpc_handler.go
@@ -1,154 +1,275 @@
package aws
import (
+ "fmt"
"net/http"
+
+ "github.com/aws/aws-sdk-go-v2/aws/external"
)
func (handler *AWSHandler) VPCHandler(w http.ResponseWriter, r *http.Request) {
- response, found := handler.cache.Get("aws_vpc")
+ profile := r.Header.Get("profile")
+ cfg, err := external.LoadDefaultAWSConfig()
+
+ if handler.multiple {
+ cfg, err = external.LoadDefaultAWSConfig(external.WithSharedConfigProfile(profile))
+ if err != nil {
+ respondWithError(w, http.StatusInternalServerError, "Couldn't read "+profile+" profile")
+ }
+ }
+ key := fmt.Sprintf("aws.%s.vpc.total", profile)
+
+ response, found := handler.cache.Get(key)
if found {
respondWithJSON(w, 200, response)
} else {
- response, err := handler.aws.DescribeVPCsTotal(handler.cfg)
+ response, err := handler.aws.DescribeVPCsTotal(cfg)
if err != nil {
respondWithError(w, http.StatusInternalServerError, "ec2:DescribeVpcs is missing")
} else {
- handler.cache.Set("aws_vpc", response)
+ handler.cache.Set(key, response)
respondWithJSON(w, 200, response)
}
}
}
func (handler *AWSHandler) ACLHandler(w http.ResponseWriter, r *http.Request) {
- response, found := handler.cache.Get("aws_acl")
+ profile := r.Header.Get("profile")
+ cfg, err := external.LoadDefaultAWSConfig()
+
+ if handler.multiple {
+ cfg, err = external.LoadDefaultAWSConfig(external.WithSharedConfigProfile(profile))
+ if err != nil {
+ respondWithError(w, http.StatusInternalServerError, "Couldn't read "+profile+" profile")
+ }
+ }
+
+ key := fmt.Sprintf("aws.%s.vpc.acl", profile)
+
+ response, found := handler.cache.Get(key)
if found {
respondWithJSON(w, 200, response)
} else {
- response, err := handler.aws.DescribeACLsTotal(handler.cfg)
+ response, err := handler.aws.DescribeACLsTotal(cfg)
if err != nil {
respondWithError(w, http.StatusInternalServerError, "ec2:DescribeNetworkAcls is missing")
} else {
- handler.cache.Set("aws_acl", response)
+ handler.cache.Set(key, response)
respondWithJSON(w, 200, response)
}
}
}
func (handler *AWSHandler) SecurityGroupHandler(w http.ResponseWriter, r *http.Request) {
- response, found := handler.cache.Get("aws_sg")
+ profile := r.Header.Get("profile")
+ cfg, err := external.LoadDefaultAWSConfig()
+
+ if handler.multiple {
+ cfg, err = external.LoadDefaultAWSConfig(external.WithSharedConfigProfile(profile))
+ if err != nil {
+ respondWithError(w, http.StatusInternalServerError, "Couldn't read "+profile+" profile")
+ }
+ }
+
+ key := fmt.Sprintf("aws.%s.vpc.sg", profile)
+
+ response, found := handler.cache.Get(key)
if found {
respondWithJSON(w, 200, response)
} else {
- response, err := handler.aws.DescribeSecurityGroupsTotal(handler.cfg)
+ response, err := handler.aws.DescribeSecurityGroupsTotal(cfg)
if err != nil {
respondWithError(w, http.StatusInternalServerError, "ec2:DescribeSecurityGroups is missing")
} else {
- handler.cache.Set("aws_sg", response)
+ handler.cache.Set(key, response)
respondWithJSON(w, 200, response)
}
}
}
func (handler *AWSHandler) NatGatewayHandler(w http.ResponseWriter, r *http.Request) {
- response, found := handler.cache.Get("aws_nat")
+ profile := r.Header.Get("profile")
+ cfg, err := external.LoadDefaultAWSConfig()
+
+ if handler.multiple {
+ cfg, err = external.LoadDefaultAWSConfig(external.WithSharedConfigProfile(profile))
+ if err != nil {
+ respondWithError(w, http.StatusInternalServerError, "Couldn't read "+profile+" profile")
+ }
+ }
+
+ key := fmt.Sprintf("aws.%s.vpc.nat", profile)
+
+ response, found := handler.cache.Get(key)
if found {
respondWithJSON(w, 200, response)
} else {
- response, err := handler.aws.DescribeNatGatewaysTotal(handler.cfg)
+ response, err := handler.aws.DescribeNatGatewaysTotal(cfg)
if err != nil {
respondWithError(w, http.StatusInternalServerError, "ec2:DescribeNatGateways is missing")
} else {
- handler.cache.Set("aws_nat", response)
+ handler.cache.Set(key, response)
respondWithJSON(w, 200, response)
}
}
}
func (handler *AWSHandler) ElasticIPHandler(w http.ResponseWriter, r *http.Request) {
- response, found := handler.cache.Get("aws_eip")
+ profile := r.Header.Get("profile")
+ cfg, err := external.LoadDefaultAWSConfig()
+
+ if handler.multiple {
+ cfg, err = external.LoadDefaultAWSConfig(external.WithSharedConfigProfile(profile))
+ if err != nil {
+ respondWithError(w, http.StatusInternalServerError, "Couldn't read "+profile+" profile")
+ }
+ }
+
+ key := fmt.Sprintf("aws.%s.vpc.eip", profile)
+
+ response, found := handler.cache.Get(key)
if found {
respondWithJSON(w, 200, response)
} else {
- response, err := handler.aws.DescribeElasticIPsTotal(handler.cfg)
+ response, err := handler.aws.DescribeElasticIPsTotal(cfg)
if err != nil {
respondWithError(w, http.StatusInternalServerError, "ec2:DescribeElasticIPs is missing")
} else {
- handler.cache.Set("aws_eip", response)
+ handler.cache.Set(key, response)
respondWithJSON(w, 200, response)
}
}
}
func (handler *AWSHandler) InternetGatewayHandler(w http.ResponseWriter, r *http.Request) {
- response, found := handler.cache.Get("aws_igw")
+ profile := r.Header.Get("profile")
+ cfg, err := external.LoadDefaultAWSConfig()
+
+ if handler.multiple {
+ cfg, err = external.LoadDefaultAWSConfig(external.WithSharedConfigProfile(profile))
+ if err != nil {
+ respondWithError(w, http.StatusInternalServerError, "Couldn't read "+profile+" profile")
+ }
+ }
+
+ key := fmt.Sprintf("aws.%s.vpc.igw", profile)
+
+ response, found := handler.cache.Get(key)
if found {
respondWithJSON(w, 200, response)
} else {
- response, err := handler.aws.DescribeInternetGatewaysTotal(handler.cfg)
+ response, err := handler.aws.DescribeInternetGatewaysTotal(cfg)
if err != nil {
respondWithError(w, http.StatusInternalServerError, "ec2:DescribeInternetGateways is missing")
} else {
- handler.cache.Set("aws_igw", response)
+ handler.cache.Set(key, response)
respondWithJSON(w, 200, response)
}
}
}
func (handler *AWSHandler) RouteTableHandler(w http.ResponseWriter, r *http.Request) {
- response, found := handler.cache.Get("aws_rt")
+ profile := r.Header.Get("profile")
+ cfg, err := external.LoadDefaultAWSConfig()
+
+ if handler.multiple {
+ cfg, err = external.LoadDefaultAWSConfig(external.WithSharedConfigProfile(profile))
+ if err != nil {
+ respondWithError(w, http.StatusInternalServerError, "Couldn't read "+profile+" profile")
+ }
+ }
+
+ key := fmt.Sprintf("aws.%s.vpc.rt", profile)
+
+ response, found := handler.cache.Get(key)
if found {
respondWithJSON(w, 200, response)
} else {
- response, err := handler.aws.DescribeRouteTablesTotal(handler.cfg)
+ response, err := handler.aws.DescribeRouteTablesTotal(cfg)
if err != nil {
respondWithError(w, http.StatusInternalServerError, "ec2:DescribeRouteTables is missing")
} else {
- handler.cache.Set("aws_rt", response)
+ handler.cache.Set(key, response)
respondWithJSON(w, 200, response)
}
}
}
func (handler *AWSHandler) KeyPairHandler(w http.ResponseWriter, r *http.Request) {
- response, found := handler.cache.Get("aws_kp")
+ profile := r.Header.Get("profile")
+ cfg, err := external.LoadDefaultAWSConfig()
+
+ if handler.multiple {
+ cfg, err = external.LoadDefaultAWSConfig(external.WithSharedConfigProfile(profile))
+ if err != nil {
+ respondWithError(w, http.StatusInternalServerError, "Couldn't read "+profile+" profile")
+ }
+ }
+
+ key := fmt.Sprintf("aws.%s.vpc.kp", profile)
+
+ response, found := handler.cache.Get(key)
if found {
respondWithJSON(w, 200, response)
} else {
- response, err := handler.aws.DescribeKeyPairsTotal(handler.cfg)
+ response, err := handler.aws.DescribeKeyPairsTotal(cfg)
if err != nil {
respondWithError(w, http.StatusInternalServerError, "ec2:DescribeKeyPairs is missing")
} else {
- handler.cache.Set("aws_kp", response)
+ handler.cache.Set(key, response)
respondWithJSON(w, 200, response)
}
}
}
func (handler *AWSHandler) GetNatGatewayTrafficHandler(w http.ResponseWriter, r *http.Request) {
- response, found := handler.cache.Get("aws_nat_traffic")
+ profile := r.Header.Get("profile")
+ cfg, err := external.LoadDefaultAWSConfig()
+
+ if handler.multiple {
+ cfg, err = external.LoadDefaultAWSConfig(external.WithSharedConfigProfile(profile))
+ if err != nil {
+ respondWithError(w, http.StatusInternalServerError, "Couldn't read "+profile+" profile")
+ }
+ }
+ key := fmt.Sprintf("aws.%s.vpc.traffic", profile)
+
+ response, found := handler.cache.Get(key)
if found {
respondWithJSON(w, 200, response)
} else {
- response, err := handler.aws.GetNatGatewayTraffic(handler.cfg)
+ response, err := handler.aws.GetNatGatewayTraffic(cfg)
if err != nil {
respondWithError(w, http.StatusInternalServerError, "cloudwatch:GetMetricStatistics is missing")
} else {
- handler.cache.Set("aws_nat_traffic", response)
+ handler.cache.Set(key, response)
respondWithJSON(w, 200, response)
}
}
}
func (handler *AWSHandler) DescribeSubnetsHandler(w http.ResponseWriter, r *http.Request) {
- response, found := handler.cache.Get("aws_subnets")
+ profile := r.Header.Get("profile")
+ cfg, err := external.LoadDefaultAWSConfig()
+
+ if handler.multiple {
+ cfg, err = external.LoadDefaultAWSConfig(external.WithSharedConfigProfile(profile))
+ if err != nil {
+ respondWithError(w, http.StatusInternalServerError, "Couldn't read "+profile+" profile")
+ }
+ }
+
+ key := fmt.Sprintf("aws.%s.vpc.subnets", profile)
+
+ response, found := handler.cache.Get(key)
if found {
respondWithJSON(w, 200, response)
} else {
- response, err := handler.aws.DescribeSubnets(handler.cfg)
+ response, err := handler.aws.DescribeSubnets(cfg)
if err != nil {
respondWithError(w, http.StatusInternalServerError, "ec2:DescribeSubnets is missing")
} else {
- handler.cache.Set("aws_subnets", response)
+ handler.cache.Set(key, response)
respondWithJSON(w, 200, response)
}
}
diff --git a/main.go b/main.go
index 30dfbdbd9..b0f61156e 100644
--- a/main.go
+++ b/main.go
@@ -7,12 +7,12 @@ import (
"os"
"time"
- "github.com/aws/aws-sdk-go-v2/aws/external"
"github.com/gorilla/handlers"
"github.com/gorilla/mux"
. "github.com/mlabouardy/komiser/handlers/aws"
. "github.com/mlabouardy/komiser/handlers/gcp"
. "github.com/mlabouardy/komiser/services/cache"
+ . "github.com/mlabouardy/komiser/services/ini"
"github.com/urfave/cli"
)
@@ -21,18 +21,14 @@ const (
DEFAULT_DURATION = 30
)
-func startServer(port int, cache Cache, dataset string) {
- cfg, err := external.LoadDefaultAWSConfig()
- if err != nil {
- log.Fatal(err)
- }
-
+func startServer(port int, cache Cache, dataset string, multiple bool) {
cache.Connect()
gcpHandler := NewGCPHandler(cache, dataset)
- awsHandler := NewAWSHandler(cfg, cache)
+ awsHandler := NewAWSHandler(cache, multiple)
r := mux.NewRouter()
+ r.HandleFunc("/aws/profiles", awsHandler.ConfigProfilesHandler)
r.HandleFunc("/aws/iam/users", awsHandler.IAMUsersHandler)
r.HandleFunc("/aws/iam/account", awsHandler.IAMUserHandler)
r.HandleFunc("/aws/cost/current", awsHandler.CurrentCostHandler)
@@ -145,10 +141,11 @@ func startServer(port int, cache Cache, dataset string) {
r.HandleFunc("/gcp/iam/service_accounts", gcpHandler.IAMServiceAccountsHandler)
r.HandleFunc("/gcp/dataflow/jobs", gcpHandler.DataflowJobsHandler)
r.HandleFunc("/gcp/nat/gateways", gcpHandler.NatGatewaysHandler)
-
r.PathPrefix("/").Handler(http.FileServer(assetFS()))
- loggedRouter := handlers.LoggingHandler(os.Stdout, handlers.CORS()(r))
- err = http.ListenAndServe(fmt.Sprintf(":%d", port), loggedRouter)
+
+ headersOk := handlers.AllowedHeaders([]string{"profile"})
+ loggedRouter := handlers.LoggingHandler(os.Stdout, handlers.CORS(headersOk)(r))
+ err := http.ListenAndServe(fmt.Sprintf(":%d", port), loggedRouter)
if err != nil {
log.Fatal(err)
} else {
@@ -159,7 +156,7 @@ func startServer(port int, cache Cache, dataset string) {
func main() {
app := cli.NewApp()
app.Name = "Komiser"
- app.Version = "2.1.0"
+ app.Version = "2.2.0"
app.Usage = "Cloud Environment Inspector"
app.Copyright = "Komiser - https://komiser.io"
app.Compiled = time.Now()
@@ -192,12 +189,17 @@ func main() {
Name: "dataset, ds",
Usage: "BigQuery Bill dataset",
},
+ cli.BoolFlag{
+ Name: "multiple, m",
+ Usage: "Enable multiple AWS accounts",
+ },
},
Action: func(c *cli.Context) error {
port := c.Int("port")
duration := c.Int("duration")
redis := c.String("redis")
dataset := c.String("dataset")
+ multiple := c.Bool("multiple")
var cache Cache
@@ -219,7 +221,7 @@ func main() {
}
}
- startServer(port, cache, dataset)
+ startServer(port, cache, dataset, multiple)
return nil
},
},
diff --git a/policy.json b/policy.json
index 1eb34ec25..2cdfe59e0 100644
--- a/policy.json
+++ b/policy.json
@@ -44,9 +44,9 @@
"rds:DescribeDBInstances",
"cloudwatch:DescribeAlarms",
"cloudfront:ListDistributions",
- "ecs:DescribeClusters",
- "ecs:DescribeServices",
- "ecs:DescribeTasks"
+ "ecs:ListServices",
+ "ecs:ListTasks",
+ "ecs:ListClusters"
],
"Resource": "*"
},
diff --git a/services/aws/ecs.go b/services/aws/ecs.go
index 7ba82146a..37c94d187 100644
--- a/services/aws/ecs.go
+++ b/services/aws/ecs.go
@@ -23,12 +23,12 @@ func (aws AWS) DescribeECS(cfg aws.Config) (map[string]int, error) {
}
countClusters += len(clusters)
for _, cluster := range clusters {
- tasks, err := aws.getTasks(cfg, cluster.Name, region.Name)
+ tasks, err := aws.getTasks(cfg, cluster, region.Name)
if err != nil {
return map[string]int{}, err
}
countTasks += len(tasks)
- services, err := aws.getServices(cfg, cluster.Name, region.Name)
+ services, err := aws.getServices(cfg, cluster, region.Name)
if err != nil {
return map[string]int{}, err
}
@@ -42,57 +42,30 @@ func (aws AWS) DescribeECS(cfg aws.Config) (map[string]int, error) {
}, nil
}
-func (aws AWS) getClusters(cfg aws.Config, region string) ([]Cluster, error) {
+func (aws AWS) getClusters(cfg aws.Config, region string) ([]string, error) {
cfg.Region = region
svc := ecs.New(cfg)
- req := svc.DescribeClustersRequest(&ecs.DescribeClustersInput{})
+ req := svc.ListClustersRequest(&ecs.ListClustersInput{})
result, err := req.Send(context.Background())
- if err != nil {
- return []Cluster{}, err
- }
- listOfClusters := make([]Cluster, 0, len(result.Clusters))
- for _, cluster := range result.Clusters {
- listOfClusters = append(listOfClusters, Cluster{
- Name: *cluster.ClusterName,
- })
- }
- return listOfClusters, nil
+ return result.ClusterArns, err
}
-func (aws AWS) getTasks(cfg aws.Config, cluster string, region string) ([]Task, error) {
+func (aws AWS) getTasks(cfg aws.Config, cluster string, region string) ([]string, error) {
cfg.Region = region
svc := ecs.New(cfg)
- req := svc.DescribeTasksRequest(&ecs.DescribeTasksInput{
+ req := svc.ListTasksRequest(&ecs.ListTasksInput{
Cluster: &cluster,
})
result, err := req.Send(context.Background())
- if err != nil {
- return []Task{}, err
- }
- listOfTasks := make([]Task, 0, len(result.Tasks))
- for _, task := range result.Tasks {
- listOfTasks = append(listOfTasks, Task{
- ARN: *task.TaskArn,
- })
- }
- return listOfTasks, nil
+ return result.TaskArns, err
}
-func (aws AWS) getServices(cfg aws.Config, cluster string, region string) ([]Service, error) {
+func (aws AWS) getServices(cfg aws.Config, cluster string, region string) ([]string, error) {
cfg.Region = region
svc := ecs.New(cfg)
- req := svc.DescribeServicesRequest(&ecs.DescribeServicesInput{
+ req := svc.ListServicesRequest(&ecs.ListServicesInput{
Cluster: &cluster,
})
result, err := req.Send(context.Background())
- if err != nil {
- return []Service{}, err
- }
- listOfServices := make([]Service, 0, len(result.Services))
- for _, service := range result.Services {
- listOfServices = append(listOfServices, Service{
- Name: *service.ServiceName,
- })
- }
- return listOfServices, nil
+ return result.ServiceArns, err
}
diff --git a/services/ini/ast.go b/services/ini/ast.go
new file mode 100644
index 000000000..e83a99886
--- /dev/null
+++ b/services/ini/ast.go
@@ -0,0 +1,120 @@
+package ini
+
+// ASTKind represents different states in the parse table
+// and the type of AST that is being constructed
+type ASTKind int
+
+// ASTKind* is used in the parse table to transition between
+// the different states
+const (
+ ASTKindNone = ASTKind(iota)
+ ASTKindStart
+ ASTKindExpr
+ ASTKindEqualExpr
+ ASTKindStatement
+ ASTKindSkipStatement
+ ASTKindExprStatement
+ ASTKindSectionStatement
+ ASTKindNestedSectionStatement
+ ASTKindCompletedNestedSectionStatement
+ ASTKindCommentStatement
+ ASTKindCompletedSectionStatement
+)
+
+func (k ASTKind) String() string {
+ switch k {
+ case ASTKindNone:
+ return "none"
+ case ASTKindStart:
+ return "start"
+ case ASTKindExpr:
+ return "expr"
+ case ASTKindStatement:
+ return "stmt"
+ case ASTKindSectionStatement:
+ return "section_stmt"
+ case ASTKindExprStatement:
+ return "expr_stmt"
+ case ASTKindCommentStatement:
+ return "comment"
+ case ASTKindNestedSectionStatement:
+ return "nested_section_stmt"
+ case ASTKindCompletedSectionStatement:
+ return "completed_stmt"
+ case ASTKindSkipStatement:
+ return "skip"
+ default:
+ return ""
+ }
+}
+
+// AST interface allows us to determine what kind of node we
+// are on and casting may not need to be necessary.
+//
+// The root is always the first node in Children
+type AST struct {
+ Kind ASTKind
+ Root Token
+ RootToken bool
+ Children []AST
+}
+
+func newAST(kind ASTKind, root AST, children ...AST) AST {
+ return AST{
+ Kind: kind,
+ Children: append([]AST{root}, children...),
+ }
+}
+
+func newASTWithRootToken(kind ASTKind, root Token, children ...AST) AST {
+ return AST{
+ Kind: kind,
+ Root: root,
+ RootToken: true,
+ Children: children,
+ }
+}
+
+// AppendChild will append to the list of children an AST has.
+func (a *AST) AppendChild(child AST) {
+ a.Children = append(a.Children, child)
+}
+
+// GetRoot will return the root AST which can be the first entry
+// in the children list or a token.
+func (a *AST) GetRoot() AST {
+ if a.RootToken {
+ return *a
+ }
+
+ if len(a.Children) == 0 {
+ return AST{}
+ }
+
+ return a.Children[0]
+}
+
+// GetChildren will return the current AST's list of children
+func (a *AST) GetChildren() []AST {
+ if len(a.Children) == 0 {
+ return []AST{}
+ }
+
+ if a.RootToken {
+ return a.Children
+ }
+
+ return a.Children[1:]
+}
+
+// SetChildren will set and override all children of the AST.
+func (a *AST) SetChildren(children []AST) {
+ if a.RootToken {
+ a.Children = children
+ } else {
+ a.Children = append(a.Children[:1], children...)
+ }
+}
+
+// Start is used to indicate the starting state of the parse table.
+var Start = newAST(ASTKindStart, AST{})
diff --git a/services/ini/comma_token.go b/services/ini/comma_token.go
new file mode 100644
index 000000000..0895d53cb
--- /dev/null
+++ b/services/ini/comma_token.go
@@ -0,0 +1,11 @@
+package ini
+
+var commaRunes = []rune(",")
+
+func isComma(b rune) bool {
+ return b == ','
+}
+
+func newCommaToken() Token {
+ return newToken(TokenComma, commaRunes, NoneType)
+}
diff --git a/services/ini/comment_token.go b/services/ini/comment_token.go
new file mode 100644
index 000000000..0b76999ba
--- /dev/null
+++ b/services/ini/comment_token.go
@@ -0,0 +1,35 @@
+package ini
+
+// isComment will return whether or not the next byte(s) is a
+// comment.
+func isComment(b []rune) bool {
+ if len(b) == 0 {
+ return false
+ }
+
+ switch b[0] {
+ case ';':
+ return true
+ case '#':
+ return true
+ }
+
+ return false
+}
+
+// newCommentToken will create a comment token and
+// return how many bytes were read.
+func newCommentToken(b []rune) (Token, int, error) {
+ i := 0
+ for ; i < len(b); i++ {
+ if b[i] == '\n' {
+ break
+ }
+
+ if len(b)-i > 2 && b[i] == '\r' && b[i+1] == '\n' {
+ break
+ }
+ }
+
+ return newToken(TokenComment, b[:i], NoneType), i, nil
+}
diff --git a/services/ini/doc.go b/services/ini/doc.go
new file mode 100644
index 000000000..25ce0fe13
--- /dev/null
+++ b/services/ini/doc.go
@@ -0,0 +1,29 @@
+// Package ini is an LL(1) parser for configuration files.
+//
+// Example:
+// sections, err := ini.OpenFile("/path/to/file")
+// if err != nil {
+// panic(err)
+// }
+//
+// profile := "foo"
+// section, ok := sections.GetSection(profile)
+// if !ok {
+// fmt.Printf("section %q could not be found", profile)
+// }
+//
+// Below is the BNF that describes this parser
+// Grammar:
+// stmt -> value stmt'
+// stmt' -> epsilon | op stmt
+// value -> number | string | boolean | quoted_string
+//
+// section -> [ section'
+// section' -> value section_close
+// section_close -> ]
+//
+// SkipState will skip (NL WS)+
+//
+// comment -> # comment' | ; comment'
+// comment' -> epsilon | value
+package ini
diff --git a/services/ini/empty_token.go b/services/ini/empty_token.go
new file mode 100644
index 000000000..04345a54c
--- /dev/null
+++ b/services/ini/empty_token.go
@@ -0,0 +1,4 @@
+package ini
+
+// emptyToken is used to satisfy the Token interface
+var emptyToken = newToken(TokenNone, []rune{}, NoneType)
diff --git a/services/ini/expression.go b/services/ini/expression.go
new file mode 100644
index 000000000..91ba2a59d
--- /dev/null
+++ b/services/ini/expression.go
@@ -0,0 +1,24 @@
+package ini
+
+// newExpression will return an expression AST.
+// Expr represents an expression
+//
+// grammar:
+// expr -> string | number
+func newExpression(tok Token) AST {
+ return newASTWithRootToken(ASTKindExpr, tok)
+}
+
+func newEqualExpr(left AST, tok Token) AST {
+ return newASTWithRootToken(ASTKindEqualExpr, tok, left)
+}
+
+// EqualExprKey will return a LHS value in the equal expr
+func EqualExprKey(ast AST) string {
+ children := ast.GetChildren()
+ if len(children) == 0 || ast.Kind != ASTKindEqualExpr {
+ return ""
+ }
+
+ return string(children[0].Root.Raw())
+}
diff --git a/services/ini/fuzz.go b/services/ini/fuzz.go
new file mode 100644
index 000000000..8d462f77e
--- /dev/null
+++ b/services/ini/fuzz.go
@@ -0,0 +1,17 @@
+// +build gofuzz
+
+package ini
+
+import (
+ "bytes"
+)
+
+func Fuzz(data []byte) int {
+ b := bytes.NewReader(data)
+
+ if _, err := Parse(b); err != nil {
+ return 0
+ }
+
+ return 1
+}
diff --git a/services/ini/ini.go b/services/ini/ini.go
new file mode 100644
index 000000000..af6f397d2
--- /dev/null
+++ b/services/ini/ini.go
@@ -0,0 +1,51 @@
+package ini
+
+import (
+ "io"
+ "os"
+
+ "github.com/aws/aws-sdk-go-v2/aws/awserr"
+)
+
+// OpenFile takes a path to a given file, and will open and parse
+// that file.
+func OpenFile(path string) (Sections, error) {
+ f, err := os.Open(path)
+ if err != nil {
+ return Sections{}, awserr.New(ErrCodeUnableToReadFile, "unable to open file", err)
+ }
+ defer f.Close()
+
+ return Parse(f)
+}
+
+// Parse will parse the given file using the shared config
+// visitor.
+func Parse(f io.Reader) (Sections, error) {
+ tree, err := ParseAST(f)
+ if err != nil {
+ return Sections{}, err
+ }
+
+ v := NewDefaultVisitor()
+ if err = Walk(tree, v); err != nil {
+ return Sections{}, err
+ }
+
+ return v.Sections, nil
+}
+
+// ParseBytes will parse the given bytes and return the parsed sections.
+func ParseBytes(b []byte) (Sections, error) {
+ tree, err := ParseASTBytes(b)
+ if err != nil {
+ return Sections{}, err
+ }
+
+ v := NewDefaultVisitor()
+ if err = Walk(tree, v); err != nil {
+ return Sections{}, err
+ }
+
+ return v.Sections, nil
+}
diff --git a/services/ini/ini_lexer.go b/services/ini/ini_lexer.go
new file mode 100644
index 000000000..898ebb05c
--- /dev/null
+++ b/services/ini/ini_lexer.go
@@ -0,0 +1,165 @@
+package ini
+
+import (
+ "bytes"
+ "io"
+ "io/ioutil"
+
+ "github.com/aws/aws-sdk-go-v2/aws/awserr"
+)
+
+const (
+ // ErrCodeUnableToReadFile is used when a file is failed to be
+ // opened or read from.
+ ErrCodeUnableToReadFile = "FailedRead"
+)
+
+// TokenType represents the various different tokens types
+type TokenType int
+
+func (t TokenType) String() string {
+ switch t {
+ case TokenNone:
+ return "none"
+ case TokenLit:
+ return "literal"
+ case TokenSep:
+ return "sep"
+ case TokenOp:
+ return "op"
+ case TokenWS:
+ return "ws"
+ case TokenNL:
+ return "newline"
+ case TokenComment:
+ return "comment"
+ case TokenComma:
+ return "comma"
+ default:
+ return ""
+ }
+}
+
+// TokenType enums
+const (
+ TokenNone = TokenType(iota)
+ TokenLit
+ TokenSep
+ TokenComma
+ TokenOp
+ TokenWS
+ TokenNL
+ TokenComment
+)
+
+type iniLexer struct{}
+
+// Tokenize will return a list of tokens during lexical analysis of the
+// io.Reader.
+func (l *iniLexer) Tokenize(r io.Reader) ([]Token, error) {
+ b, err := ioutil.ReadAll(r)
+ if err != nil {
+ return nil, awserr.New(ErrCodeUnableToReadFile, "unable to read file", err)
+ }
+
+ return l.tokenize(b)
+}
+
+func (l *iniLexer) tokenize(b []byte) ([]Token, error) {
+ runes := bytes.Runes(b)
+ var err error
+ n := 0
+ tokenAmount := countTokens(runes)
+ tokens := make([]Token, tokenAmount)
+ count := 0
+
+ for len(runes) > 0 && count < tokenAmount {
+ switch {
+ case isWhitespace(runes[0]):
+ tokens[count], n, err = newWSToken(runes)
+ case isComma(runes[0]):
+ tokens[count], n = newCommaToken(), 1
+ case isComment(runes):
+ tokens[count], n, err = newCommentToken(runes)
+ case isNewline(runes):
+ tokens[count], n, err = newNewlineToken(runes)
+ case isSep(runes):
+ tokens[count], n, err = newSepToken(runes)
+ case isOp(runes):
+ tokens[count], n, err = newOpToken(runes)
+ default:
+ tokens[count], n, err = newLitToken(runes)
+ }
+
+ if err != nil {
+ return nil, err
+ }
+
+ count++
+
+ runes = runes[n:]
+ }
+
+ return tokens[:count], nil
+}
+
+func countTokens(runes []rune) int {
+ count, n := 0, 0
+ var err error
+
+ for len(runes) > 0 {
+ switch {
+ case isWhitespace(runes[0]):
+ _, n, err = newWSToken(runes)
+ case isComma(runes[0]):
+ _, n = newCommaToken(), 1
+ case isComment(runes):
+ _, n, err = newCommentToken(runes)
+ case isNewline(runes):
+ _, n, err = newNewlineToken(runes)
+ case isSep(runes):
+ _, n, err = newSepToken(runes)
+ case isOp(runes):
+ _, n, err = newOpToken(runes)
+ default:
+ _, n, err = newLitToken(runes)
+ }
+
+ if err != nil {
+ return 0
+ }
+
+ count++
+ runes = runes[n:]
+ }
+
+ return count + 1
+}
+
+// Token indicates a metadata about a given value.
+type Token struct {
+ t TokenType
+ ValueType ValueType
+ base int
+ raw []rune
+}
+
+var emptyValue = Value{}
+
+func newToken(t TokenType, raw []rune, v ValueType) Token {
+ return Token{
+ t: t,
+ raw: raw,
+ ValueType: v,
+ }
+}
+
+// Raw return the raw runes that were consumed
+func (tok Token) Raw() []rune {
+ return tok.raw
+}
+
+// Type returns the token type
+func (tok Token) Type() TokenType {
+ return tok.t
+}
diff --git a/services/ini/ini_parser.go b/services/ini/ini_parser.go
new file mode 100644
index 000000000..8be520ae6
--- /dev/null
+++ b/services/ini/ini_parser.go
@@ -0,0 +1,347 @@
+package ini
+
+import (
+ "fmt"
+ "io"
+)
+
+// State enums for the parse table
+const (
+ InvalidState = iota
+ // stmt -> value stmt'
+ StatementState
+ // stmt' -> MarkComplete | op stmt
+ StatementPrimeState
+ // value -> number | string | boolean | quoted_string
+ ValueState
+ // section -> [ section'
+ OpenScopeState
+ // section' -> value section_close
+ SectionState
+ // section_close -> ]
+ CloseScopeState
+ // SkipState will skip (NL WS)+
+ SkipState
+ // SkipTokenState will skip any token and push the previous
+ // state onto the stack.
+ SkipTokenState
+ // comment -> # comment' | ; comment'
+ // comment' -> MarkComplete | value
+ CommentState
+ // MarkComplete state will complete statements and move that
+ // to the completed AST list
+ MarkCompleteState
+ // TerminalState signifies that the tokens have been fully parsed
+ TerminalState
+)
+
+// parseTable is a state machine to dictate the grammar above.
+var parseTable = map[ASTKind]map[TokenType]int{
+ ASTKindStart: map[TokenType]int{
+ TokenLit: StatementState,
+ TokenSep: OpenScopeState,
+ TokenWS: SkipTokenState,
+ TokenNL: SkipTokenState,
+ TokenComment: CommentState,
+ TokenNone: TerminalState,
+ },
+ ASTKindCommentStatement: map[TokenType]int{
+ TokenLit: StatementState,
+ TokenSep: OpenScopeState,
+ TokenWS: SkipTokenState,
+ TokenNL: SkipTokenState,
+ TokenComment: CommentState,
+ TokenNone: MarkCompleteState,
+ },
+ ASTKindExpr: map[TokenType]int{
+ TokenOp: StatementPrimeState,
+ TokenLit: ValueState,
+ TokenSep: OpenScopeState,
+ TokenWS: ValueState,
+ TokenNL: SkipState,
+ TokenComment: CommentState,
+ TokenNone: MarkCompleteState,
+ },
+ ASTKindEqualExpr: map[TokenType]int{
+ TokenLit: ValueState,
+ TokenWS: SkipTokenState,
+ TokenNL: SkipState,
+ },
+ ASTKindStatement: map[TokenType]int{
+ TokenLit: SectionState,
+ TokenSep: CloseScopeState,
+ TokenWS: SkipTokenState,
+ TokenNL: SkipTokenState,
+ TokenComment: CommentState,
+ TokenNone: MarkCompleteState,
+ },
+ ASTKindExprStatement: map[TokenType]int{
+ TokenLit: ValueState,
+ TokenSep: OpenScopeState,
+ TokenOp: ValueState,
+ TokenWS: ValueState,
+ TokenNL: MarkCompleteState,
+ TokenComment: CommentState,
+ TokenNone: TerminalState,
+ TokenComma: SkipState,
+ },
+ ASTKindSectionStatement: map[TokenType]int{
+ TokenLit: SectionState,
+ TokenOp: SectionState,
+ TokenSep: CloseScopeState,
+ TokenWS: SectionState,
+ TokenNL: SkipTokenState,
+ },
+ ASTKindCompletedSectionStatement: map[TokenType]int{
+ TokenWS: SkipTokenState,
+ TokenNL: SkipTokenState,
+ TokenLit: StatementState,
+ TokenSep: OpenScopeState,
+ TokenComment: CommentState,
+ TokenNone: MarkCompleteState,
+ },
+ ASTKindSkipStatement: map[TokenType]int{
+ TokenLit: StatementState,
+ TokenSep: OpenScopeState,
+ TokenWS: SkipTokenState,
+ TokenNL: SkipTokenState,
+ TokenComment: CommentState,
+ TokenNone: TerminalState,
+ },
+}
+
+// ParseAST will parse input from an io.Reader using
+// an LL(1) parser.
+func ParseAST(r io.Reader) ([]AST, error) {
+ lexer := iniLexer{}
+ tokens, err := lexer.Tokenize(r)
+ if err != nil {
+ return []AST{}, err
+ }
+
+ return parse(tokens)
+}
+
+// ParseASTBytes will parse input from a byte slice using
+// an LL(1) parser.
+func ParseASTBytes(b []byte) ([]AST, error) {
+ lexer := iniLexer{}
+ tokens, err := lexer.tokenize(b)
+ if err != nil {
+ return []AST{}, err
+ }
+
+ return parse(tokens)
+}
+
+func parse(tokens []Token) ([]AST, error) {
+ start := Start
+ stack := newParseStack(3, len(tokens))
+
+ stack.Push(start)
+ s := newSkipper()
+
+loop:
+ for stack.Len() > 0 {
+ k := stack.Pop()
+
+ var tok Token
+ if len(tokens) == 0 {
+ // this occurs when all the tokens have been processed
+ // but reduction of what's left on the stack needs to
+ // occur.
+ tok = emptyToken
+ } else {
+ tok = tokens[0]
+ }
+
+ step := parseTable[k.Kind][tok.Type()]
+ if s.ShouldSkip(tok) {
+ // being in a skip state with no tokens will break out of
+ // the parse loop since there is nothing left to process.
+ if len(tokens) == 0 {
+ break loop
+ }
+
+ step = SkipTokenState
+ }
+
+ switch step {
+ case TerminalState:
+ // Finished parsing. Push what should be the last
+ // statement to the stack. If there is anything left
+ // on the stack, an error in parsing has occurred.
+ if k.Kind != ASTKindStart {
+ stack.MarkComplete(k)
+ }
+ break loop
+ case SkipTokenState:
+ // When skipping a token, the previous state was popped off the stack.
+ // To maintain the correct state, the previous state will be pushed
+ // onto the stack.
+ stack.Push(k)
+ case StatementState:
+ if k.Kind != ASTKindStart {
+ stack.MarkComplete(k)
+ }
+ expr := newExpression(tok)
+ stack.Push(expr)
+ case StatementPrimeState:
+ if tok.Type() != TokenOp {
+ stack.MarkComplete(k)
+ continue
+ }
+
+ if k.Kind != ASTKindExpr {
+ return nil, NewParseError(
+ fmt.Sprintf("invalid expression: expected Expr type, but found %T type", k),
+ )
+ }
+
+ k = trimSpaces(k)
+ expr := newEqualExpr(k, tok)
+ stack.Push(expr)
+ case ValueState:
+ // ValueState requires the previous state to either be an equal expression
+ // or an expression statement.
+ //
+ // This grammar occurs when the RHS is a number, word, or quoted string.
+ // equal_expr -> lit op equal_expr'
+ // equal_expr' -> number | string | quoted_string
+ // quoted_string -> " quoted_string'
+ // quoted_string' -> string quoted_string_end
+ // quoted_string_end -> "
+ //
+ // otherwise
+ // expr_stmt -> equal_expr (expr_stmt')*
+ // expr_stmt' -> ws S | op S | MarkComplete
+ // S -> equal_expr' expr_stmt'
+ switch k.Kind {
+ case ASTKindEqualExpr:
+ // assiging a value to some key
+ k.AppendChild(newExpression(tok))
+ stack.Push(newExprStatement(k))
+ case ASTKindExpr:
+ k.Root.raw = append(k.Root.raw, tok.Raw()...)
+ stack.Push(k)
+ case ASTKindExprStatement:
+ root := k.GetRoot()
+ children := root.GetChildren()
+ if len(children) == 0 {
+ return nil, NewParseError(
+ fmt.Sprintf("invalid expression: AST contains no children %s", k.Kind),
+ )
+ }
+
+ rhs := children[len(children)-1]
+
+ if rhs.Root.ValueType != QuotedStringType {
+ rhs.Root.ValueType = StringType
+ rhs.Root.raw = append(rhs.Root.raw, tok.Raw()...)
+
+ }
+
+ children[len(children)-1] = rhs
+ k.SetChildren(children)
+
+ stack.Push(k)
+ }
+ case OpenScopeState:
+ if !runeCompare(tok.Raw(), openBrace) {
+ return nil, NewParseError("expected '['")
+ }
+
+ stmt := newStatement()
+ stack.Push(stmt)
+ case CloseScopeState:
+ if !runeCompare(tok.Raw(), closeBrace) {
+ return nil, NewParseError("expected ']'")
+ }
+
+ k = trimSpaces(k)
+ stack.Push(newCompletedSectionStatement(k))
+ case SectionState:
+ var stmt AST
+
+ switch k.Kind {
+ case ASTKindStatement:
+ // If there are multiple literals inside of a scope declaration,
+ // then the current token's raw value will be appended to the Name.
+ //
+ // This handles cases like [ profile default ]
+ //
+ // k will represent a SectionStatement with the children representing
+ // the label of the section
+ stmt = newSectionStatement(tok)
+ case ASTKindSectionStatement:
+ k.Root.raw = append(k.Root.raw, tok.Raw()...)
+ stmt = k
+ default:
+ return nil, NewParseError(
+ fmt.Sprintf("invalid statement: expected statement: %v", k.Kind),
+ )
+ }
+
+ stack.Push(stmt)
+ case MarkCompleteState:
+ if k.Kind != ASTKindStart {
+ stack.MarkComplete(k)
+ }
+
+ if stack.Len() == 0 {
+ stack.Push(start)
+ }
+ case SkipState:
+ stack.Push(newSkipStatement(k))
+ s.Skip()
+ case CommentState:
+ if k.Kind == ASTKindStart {
+ stack.Push(k)
+ } else {
+ stack.MarkComplete(k)
+ }
+
+ stmt := newCommentStatement(tok)
+ stack.Push(stmt)
+ default:
+ return nil, NewParseError(fmt.Sprintf("invalid state with ASTKind %v and TokenType %v", k, tok))
+ }
+
+ if len(tokens) > 0 {
+ tokens = tokens[1:]
+ }
+ }
+
+ // this occurs when a statement has not been completed
+ if stack.top > 1 {
+ return nil, NewParseError(fmt.Sprintf("incomplete expression: %v", stack.container))
+ }
+
+ // returns a sublist which exludes the start symbol
+ return stack.List(), nil
+}
+
+// trimSpaces will trim spaces on the left and right hand side of
+// the literal.
+func trimSpaces(k AST) AST {
+ // trim left hand side of spaces
+ for i := 0; i < len(k.Root.raw); i++ {
+ if !isWhitespace(k.Root.raw[i]) {
+ break
+ }
+
+ k.Root.raw = k.Root.raw[1:]
+ i--
+ }
+
+ // trim right hand side of spaces
+ for i := len(k.Root.raw) - 1; i >= 0; i-- {
+ if !isWhitespace(k.Root.raw[i]) {
+ break
+ }
+
+ k.Root.raw = k.Root.raw[:len(k.Root.raw)-1]
+ }
+
+ return k
+}
diff --git a/services/ini/literal_tokens.go b/services/ini/literal_tokens.go
new file mode 100644
index 000000000..24df543d3
--- /dev/null
+++ b/services/ini/literal_tokens.go
@@ -0,0 +1,324 @@
+package ini
+
+import (
+ "fmt"
+ "strconv"
+ "strings"
+)
+
+var (
+ runesTrue = []rune("true")
+ runesFalse = []rune("false")
+)
+
+var literalValues = [][]rune{
+ runesTrue,
+ runesFalse,
+}
+
+func isBoolValue(b []rune) bool {
+ for _, lv := range literalValues {
+ if isLitValue(lv, b) {
+ return true
+ }
+ }
+ return false
+}
+
+func isLitValue(want, have []rune) bool {
+ if len(have) < len(want) {
+ return false
+ }
+
+ for i := 0; i < len(want); i++ {
+ if want[i] != have[i] {
+ return false
+ }
+ }
+
+ return true
+}
+
+// isNumberValue will return whether not the leading characters in
+// a byte slice is a number. A number is delimited by whitespace or
+// the newline token.
+//
+// A number is defined to be in a binary, octal, decimal (int | float), hex format,
+// or in scientific notation.
+func isNumberValue(b []rune) bool {
+ negativeIndex := 0
+ helper := numberHelper{}
+ needDigit := false
+
+ for i := 0; i < len(b); i++ {
+ negativeIndex++
+
+ switch b[i] {
+ case '-':
+ if helper.IsNegative() || negativeIndex != 1 {
+ return false
+ }
+ helper.Determine(b[i])
+ needDigit = true
+ continue
+ case 'e', 'E':
+ if err := helper.Determine(b[i]); err != nil {
+ return false
+ }
+ negativeIndex = 0
+ needDigit = true
+ continue
+ case 'b':
+ if helper.numberFormat == hex {
+ break
+ }
+ fallthrough
+ case 'o', 'x':
+ needDigit = true
+ if i == 0 {
+ return false
+ }
+
+ fallthrough
+ case '.':
+ if err := helper.Determine(b[i]); err != nil {
+ return false
+ }
+ needDigit = true
+ continue
+ }
+
+ if i > 0 && (isNewline(b[i:]) || isWhitespace(b[i])) {
+ return !needDigit
+ }
+
+ if !helper.CorrectByte(b[i]) {
+ return false
+ }
+ needDigit = false
+ }
+
+ return !needDigit
+}
+
+func isValid(b []rune) (bool, int, error) {
+ if len(b) == 0 {
+ // TODO: should probably return an error
+ return false, 0, nil
+ }
+
+ return isValidRune(b[0]), 1, nil
+}
+
+func isValidRune(r rune) bool {
+ return r != ':' && r != '=' && r != '[' && r != ']' && r != ' ' && r != '\n'
+}
+
+// ValueType is an enum that will signify what type
+// the Value is
+type ValueType int
+
+func (v ValueType) String() string {
+ switch v {
+ case NoneType:
+ return "NONE"
+ case DecimalType:
+ return "FLOAT"
+ case IntegerType:
+ return "INT"
+ case StringType:
+ return "STRING"
+ case BoolType:
+ return "BOOL"
+ }
+
+ return ""
+}
+
+// ValueType enums
+const (
+ NoneType = ValueType(iota)
+ DecimalType
+ IntegerType
+ StringType
+ QuotedStringType
+ BoolType
+)
+
+// Value is a union container
+type Value struct {
+ Type ValueType
+ raw []rune
+
+ integer int64
+ decimal float64
+ boolean bool
+ str string
+}
+
+func newValue(t ValueType, base int, raw []rune) (Value, error) {
+ v := Value{
+ Type: t,
+ raw: raw,
+ }
+ var err error
+
+ switch t {
+ case DecimalType:
+ v.decimal, err = strconv.ParseFloat(string(raw), 64)
+ case IntegerType:
+ if base != 10 {
+ raw = raw[2:]
+ }
+
+ v.integer, err = strconv.ParseInt(string(raw), base, 64)
+ case StringType:
+ v.str = string(raw)
+ case QuotedStringType:
+ v.str = string(raw[1 : len(raw)-1])
+ case BoolType:
+ v.boolean = runeCompare(v.raw, runesTrue)
+ }
+
+ // issue 2253
+ //
+ // if the value trying to be parsed is too large, then we will use
+ // the 'StringType' and raw value instead.
+ if nerr, ok := err.(*strconv.NumError); ok && nerr.Err == strconv.ErrRange {
+ v.Type = StringType
+ v.str = string(raw)
+ err = nil
+ }
+
+ return v, err
+}
+
+// Append will append values and change the type to a string
+// type.
+func (v *Value) Append(tok Token) {
+ r := tok.Raw()
+ if v.Type != QuotedStringType {
+ v.Type = StringType
+ r = tok.raw[1 : len(tok.raw)-1]
+ }
+ if tok.Type() != TokenLit {
+ v.raw = append(v.raw, tok.Raw()...)
+ } else {
+ v.raw = append(v.raw, r...)
+ }
+}
+
+func (v Value) String() string {
+ switch v.Type {
+ case DecimalType:
+ return fmt.Sprintf("decimal: %f", v.decimal)
+ case IntegerType:
+ return fmt.Sprintf("integer: %d", v.integer)
+ case StringType:
+ return fmt.Sprintf("string: %s", string(v.raw))
+ case QuotedStringType:
+ return fmt.Sprintf("quoted string: %s", string(v.raw))
+ case BoolType:
+ return fmt.Sprintf("bool: %t", v.boolean)
+ default:
+ return "union not set"
+ }
+}
+
+func newLitToken(b []rune) (Token, int, error) {
+ n := 0
+ var err error
+
+ token := Token{}
+ if b[0] == '"' {
+ n, err = getStringValue(b)
+ if err != nil {
+ return token, n, err
+ }
+
+ token = newToken(TokenLit, b[:n], QuotedStringType)
+ } else if isNumberValue(b) {
+ var base int
+ base, n, err = getNumericalValue(b)
+ if err != nil {
+ return token, 0, err
+ }
+
+ value := b[:n]
+ vType := IntegerType
+ if contains(value, '.') || hasExponent(value) {
+ vType = DecimalType
+ }
+ token = newToken(TokenLit, value, vType)
+ token.base = base
+ } else if isBoolValue(b) {
+ n, err = getBoolValue(b)
+
+ token = newToken(TokenLit, b[:n], BoolType)
+ } else {
+ n, err = getValue(b)
+ token = newToken(TokenLit, b[:n], StringType)
+ }
+
+ return token, n, err
+}
+
+// IntValue returns an integer value
+func (v Value) IntValue() int64 {
+ return v.integer
+}
+
+// FloatValue returns a float value
+func (v Value) FloatValue() float64 {
+ return v.decimal
+}
+
+// BoolValue returns a bool value
+func (v Value) BoolValue() bool {
+ return v.boolean
+}
+
+func isTrimmable(r rune) bool {
+ switch r {
+ case '\n', ' ':
+ return true
+ }
+ return false
+}
+
+// StringValue returns the string value
+func (v Value) StringValue() string {
+ switch v.Type {
+ case StringType:
+ return strings.TrimFunc(string(v.raw), isTrimmable)
+ case QuotedStringType:
+ // preserve all characters in the quotes
+ return string(removeEscapedCharacters(v.raw[1 : len(v.raw)-1]))
+ default:
+ return strings.TrimFunc(string(v.raw), isTrimmable)
+ }
+}
+
+func contains(runes []rune, c rune) bool {
+ for i := 0; i < len(runes); i++ {
+ if runes[i] == c {
+ return true
+ }
+ }
+
+ return false
+}
+
+func runeCompare(v1 []rune, v2 []rune) bool {
+ if len(v1) != len(v2) {
+ return false
+ }
+
+ for i := 0; i < len(v1); i++ {
+ if v1[i] != v2[i] {
+ return false
+ }
+ }
+
+ return true
+}
diff --git a/services/ini/newline_token.go b/services/ini/newline_token.go
new file mode 100644
index 000000000..e52ac399f
--- /dev/null
+++ b/services/ini/newline_token.go
@@ -0,0 +1,30 @@
+package ini
+
+func isNewline(b []rune) bool {
+ if len(b) == 0 {
+ return false
+ }
+
+ if b[0] == '\n' {
+ return true
+ }
+
+ if len(b) < 2 {
+ return false
+ }
+
+ return b[0] == '\r' && b[1] == '\n'
+}
+
+func newNewlineToken(b []rune) (Token, int, error) {
+ i := 1
+ if b[0] == '\r' && isNewline(b[1:]) {
+ i++
+ }
+
+ if !isNewline([]rune(b[:i])) {
+ return emptyToken, 0, NewParseError("invalid new line token")
+ }
+
+ return newToken(TokenNL, b[:i], NoneType), i, nil
+}
diff --git a/services/ini/number_helper.go b/services/ini/number_helper.go
new file mode 100644
index 000000000..a45c0bc56
--- /dev/null
+++ b/services/ini/number_helper.go
@@ -0,0 +1,152 @@
+package ini
+
+import (
+ "bytes"
+ "fmt"
+ "strconv"
+)
+
+const (
+ none = numberFormat(iota)
+ binary
+ octal
+ decimal
+ hex
+ exponent
+)
+
+type numberFormat int
+
+// numberHelper is used to dictate what format a number is in
+// and what to do for negative values. Since -1e-4 is a valid
+// number, we cannot just simply check for duplicate negatives.
+type numberHelper struct {
+ numberFormat numberFormat
+
+ negative bool
+ negativeExponent bool
+}
+
+func (b numberHelper) Exists() bool {
+ return b.numberFormat != none
+}
+
+func (b numberHelper) IsNegative() bool {
+ return b.negative || b.negativeExponent
+}
+
+func (b *numberHelper) Determine(c rune) error {
+ if b.Exists() {
+ return NewParseError(fmt.Sprintf("multiple number formats: 0%v", string(c)))
+ }
+
+ switch c {
+ case 'b':
+ b.numberFormat = binary
+ case 'o':
+ b.numberFormat = octal
+ case 'x':
+ b.numberFormat = hex
+ case 'e', 'E':
+ b.numberFormat = exponent
+ case '-':
+ if b.numberFormat != exponent {
+ b.negative = true
+ } else {
+ b.negativeExponent = true
+ }
+ case '.':
+ b.numberFormat = decimal
+ default:
+ return NewParseError(fmt.Sprintf("invalid number character: %v", string(c)))
+ }
+
+ return nil
+}
+
+func (b numberHelper) CorrectByte(c rune) bool {
+ switch {
+ case b.numberFormat == binary:
+ if !isBinaryByte(c) {
+ return false
+ }
+ case b.numberFormat == octal:
+ if !isOctalByte(c) {
+ return false
+ }
+ case b.numberFormat == hex:
+ if !isHexByte(c) {
+ return false
+ }
+ case b.numberFormat == decimal:
+ if !isDigit(c) {
+ return false
+ }
+ case b.numberFormat == exponent:
+ if !isDigit(c) {
+ return false
+ }
+ case b.negativeExponent:
+ if !isDigit(c) {
+ return false
+ }
+ case b.negative:
+ if !isDigit(c) {
+ return false
+ }
+ default:
+ if !isDigit(c) {
+ return false
+ }
+ }
+
+ return true
+}
+
+func (b numberHelper) Base() int {
+ switch b.numberFormat {
+ case binary:
+ return 2
+ case octal:
+ return 8
+ case hex:
+ return 16
+ default:
+ return 10
+ }
+}
+
+func (b numberHelper) String() string {
+ buf := bytes.Buffer{}
+ i := 0
+
+ switch b.numberFormat {
+ case binary:
+ i++
+ buf.WriteString(strconv.Itoa(i) + ": binary format\n")
+ case octal:
+ i++
+ buf.WriteString(strconv.Itoa(i) + ": octal format\n")
+ case hex:
+ i++
+ buf.WriteString(strconv.Itoa(i) + ": hex format\n")
+ case exponent:
+ i++
+ buf.WriteString(strconv.Itoa(i) + ": exponent format\n")
+ default:
+ i++
+ buf.WriteString(strconv.Itoa(i) + ": integer format\n")
+ }
+
+ if b.negative {
+ i++
+ buf.WriteString(strconv.Itoa(i) + ": negative format\n")
+ }
+
+ if b.negativeExponent {
+ i++
+ buf.WriteString(strconv.Itoa(i) + ": negative exponent format\n")
+ }
+
+ return buf.String()
+}
diff --git a/services/ini/op_tokens.go b/services/ini/op_tokens.go
new file mode 100644
index 000000000..8a84c7cbe
--- /dev/null
+++ b/services/ini/op_tokens.go
@@ -0,0 +1,39 @@
+package ini
+
+import (
+ "fmt"
+)
+
+var (
+ equalOp = []rune("=")
+ equalColonOp = []rune(":")
+)
+
+func isOp(b []rune) bool {
+ if len(b) == 0 {
+ return false
+ }
+
+ switch b[0] {
+ case '=':
+ return true
+ case ':':
+ return true
+ default:
+ return false
+ }
+}
+
+func newOpToken(b []rune) (Token, int, error) {
+ tok := Token{}
+
+ switch b[0] {
+ case '=':
+ tok = newToken(TokenOp, equalOp, NoneType)
+ case ':':
+ tok = newToken(TokenOp, equalColonOp, NoneType)
+ default:
+ return tok, 0, NewParseError(fmt.Sprintf("unexpected op type, %v", b[0]))
+ }
+ return tok, 1, nil
+}
diff --git a/services/ini/parse_error.go b/services/ini/parse_error.go
new file mode 100644
index 000000000..457287019
--- /dev/null
+++ b/services/ini/parse_error.go
@@ -0,0 +1,43 @@
+package ini
+
+import "fmt"
+
+const (
+ // ErrCodeParseError is returned when a parsing error
+ // has occurred.
+ ErrCodeParseError = "INIParseError"
+)
+
+// ParseError is an error which is returned during any part of
+// the parsing process.
+type ParseError struct {
+ msg string
+}
+
+// NewParseError will return a new ParseError where message
+// is the description of the error.
+func NewParseError(message string) *ParseError {
+ return &ParseError{
+ msg: message,
+ }
+}
+
+// Code will return the ErrCodeParseError
+func (err *ParseError) Code() string {
+ return ErrCodeParseError
+}
+
+// Message returns the error's message
+func (err *ParseError) Message() string {
+ return err.msg
+}
+
+// OrigError return nothing since there will never be any
+// original error.
+func (err *ParseError) OrigError() error {
+ return nil
+}
+
+func (err *ParseError) Error() string {
+ return fmt.Sprintf("%s: %s", err.Code(), err.Message())
+}
diff --git a/services/ini/parse_stack.go b/services/ini/parse_stack.go
new file mode 100644
index 000000000..7f01cf7c7
--- /dev/null
+++ b/services/ini/parse_stack.go
@@ -0,0 +1,60 @@
+package ini
+
+import (
+ "bytes"
+ "fmt"
+)
+
+// ParseStack is a stack that contains a container, the stack portion,
+// and the list which is the list of ASTs that have been successfully
+// parsed.
+type ParseStack struct {
+ top int
+ container []AST
+ list []AST
+ index int
+}
+
+func newParseStack(sizeContainer, sizeList int) ParseStack {
+ return ParseStack{
+ container: make([]AST, sizeContainer),
+ list: make([]AST, sizeList),
+ }
+}
+
+// Pop will return and truncate the last container element.
+func (s *ParseStack) Pop() AST {
+ s.top--
+ return s.container[s.top]
+}
+
+// Push will add the new AST to the container
+func (s *ParseStack) Push(ast AST) {
+ s.container[s.top] = ast
+ s.top++
+}
+
+// MarkComplete will append the AST to the list of completed statements
+func (s *ParseStack) MarkComplete(ast AST) {
+ s.list[s.index] = ast
+ s.index++
+}
+
+// List will return the completed statements
+func (s ParseStack) List() []AST {
+ return s.list[:s.index]
+}
+
+// Len will return the length of the container
+func (s *ParseStack) Len() int {
+ return s.top
+}
+
+func (s ParseStack) String() string {
+ buf := bytes.Buffer{}
+ for i, node := range s.list {
+ buf.WriteString(fmt.Sprintf("%d: %v\n", i+1, node))
+ }
+
+ return buf.String()
+}
diff --git a/services/ini/sep_tokens.go b/services/ini/sep_tokens.go
new file mode 100644
index 000000000..f82095ba2
--- /dev/null
+++ b/services/ini/sep_tokens.go
@@ -0,0 +1,41 @@
+package ini
+
+import (
+ "fmt"
+)
+
+var (
+ emptyRunes = []rune{}
+)
+
+func isSep(b []rune) bool {
+ if len(b) == 0 {
+ return false
+ }
+
+ switch b[0] {
+ case '[', ']':
+ return true
+ default:
+ return false
+ }
+}
+
+var (
+ openBrace = []rune("[")
+ closeBrace = []rune("]")
+)
+
+func newSepToken(b []rune) (Token, int, error) {
+ tok := Token{}
+
+ switch b[0] {
+ case '[':
+ tok = newToken(TokenSep, openBrace, NoneType)
+ case ']':
+ tok = newToken(TokenSep, closeBrace, NoneType)
+ default:
+ return tok, 0, NewParseError(fmt.Sprintf("unexpected sep type, %v", b[0]))
+ }
+ return tok, 1, nil
+}
diff --git a/services/ini/skipper.go b/services/ini/skipper.go
new file mode 100644
index 000000000..6bb696447
--- /dev/null
+++ b/services/ini/skipper.go
@@ -0,0 +1,45 @@
+package ini
+
+// skipper is used to skip certain blocks of an ini file.
+// Currently skipper is used to skip nested blocks of ini
+// files. See example below
+//
+// [ foo ]
+// nested = ; this section will be skipped
+// a=b
+// c=d
+// bar=baz ; this will be included
+type skipper struct {
+ shouldSkip bool
+ TokenSet bool
+ prevTok Token
+}
+
+func newSkipper() skipper {
+ return skipper{
+ prevTok: emptyToken,
+ }
+}
+
+func (s *skipper) ShouldSkip(tok Token) bool {
+ if s.shouldSkip &&
+ s.prevTok.Type() == TokenNL &&
+ tok.Type() != TokenWS {
+
+ s.Continue()
+ return false
+ }
+ s.prevTok = tok
+
+ return s.shouldSkip
+}
+
+func (s *skipper) Skip() {
+ s.shouldSkip = true
+ s.prevTok = emptyToken
+}
+
+func (s *skipper) Continue() {
+ s.shouldSkip = false
+ s.prevTok = emptyToken
+}
diff --git a/services/ini/statement.go b/services/ini/statement.go
new file mode 100644
index 000000000..ba0af01b5
--- /dev/null
+++ b/services/ini/statement.go
@@ -0,0 +1,35 @@
+package ini
+
+// Statement is an empty AST mostly used for transitioning states.
+func newStatement() AST {
+ return newAST(ASTKindStatement, AST{})
+}
+
+// SectionStatement represents a section AST
+func newSectionStatement(tok Token) AST {
+ return newASTWithRootToken(ASTKindSectionStatement, tok)
+}
+
+// ExprStatement represents a completed expression AST
+func newExprStatement(ast AST) AST {
+ return newAST(ASTKindExprStatement, ast)
+}
+
+// CommentStatement represents a comment in the ini defintion.
+//
+// grammar:
+// comment -> #comment' | ;comment'
+// comment' -> epsilon | value
+func newCommentStatement(tok Token) AST {
+ return newAST(ASTKindCommentStatement, newExpression(tok))
+}
+
+// CompletedSectionStatement represents a completed section
+func newCompletedSectionStatement(ast AST) AST {
+ return newAST(ASTKindCompletedSectionStatement, ast)
+}
+
+// SkipStatement is used to skip whole statements
+func newSkipStatement(ast AST) AST {
+ return newAST(ASTKindSkipStatement, ast)
+}
diff --git a/services/ini/value_util.go b/services/ini/value_util.go
new file mode 100644
index 000000000..305999d29
--- /dev/null
+++ b/services/ini/value_util.go
@@ -0,0 +1,284 @@
+package ini
+
+import (
+ "fmt"
+)
+
+// getStringValue will return a quoted string and the amount
+// of bytes read
+//
+// an error will be returned if the string is not properly formatted
+func getStringValue(b []rune) (int, error) {
+ if b[0] != '"' {
+ return 0, NewParseError("strings must start with '\"'")
+ }
+
+ endQuote := false
+ i := 1
+
+ for ; i < len(b) && !endQuote; i++ {
+ if escaped := isEscaped(b[:i], b[i]); b[i] == '"' && !escaped {
+ endQuote = true
+ break
+ } else if escaped {
+ /*c, err := getEscapedByte(b[i])
+ if err != nil {
+ return 0, err
+ }
+
+ b[i-1] = c
+ b = append(b[:i], b[i+1:]...)
+ i--*/
+
+ continue
+ }
+ }
+
+ if !endQuote {
+ return 0, NewParseError("missing '\"' in string value")
+ }
+
+ return i + 1, nil
+}
+
+// getBoolValue will return a boolean and the amount
+// of bytes read
+//
+// an error will be returned if the boolean is not of a correct
+// value
+func getBoolValue(b []rune) (int, error) {
+ if len(b) < 4 {
+ return 0, NewParseError("invalid boolean value")
+ }
+
+ n := 0
+ for _, lv := range literalValues {
+ if len(lv) > len(b) {
+ continue
+ }
+
+ if isLitValue(lv, b) {
+ n = len(lv)
+ }
+ }
+
+ if n == 0 {
+ return 0, NewParseError("invalid boolean value")
+ }
+
+ return n, nil
+}
+
+// getNumericalValue will return a numerical string, the amount
+// of bytes read, and the base of the number
+//
+// an error will be returned if the number is not of a correct
+// value
+func getNumericalValue(b []rune) (int, int, error) {
+ if !isDigit(b[0]) {
+ return 0, 0, NewParseError("invalid digit value")
+ }
+
+ i := 0
+ helper := numberHelper{}
+
+loop:
+ for negativeIndex := 0; i < len(b); i++ {
+ negativeIndex++
+
+ if !isDigit(b[i]) {
+ switch b[i] {
+ case '-':
+ if helper.IsNegative() || negativeIndex != 1 {
+ return 0, 0, NewParseError("parse error '-'")
+ }
+
+ n := getNegativeNumber(b[i:])
+ i += (n - 1)
+ helper.Determine(b[i])
+ continue
+ case '.':
+ if err := helper.Determine(b[i]); err != nil {
+ return 0, 0, err
+ }
+ case 'e', 'E':
+ if err := helper.Determine(b[i]); err != nil {
+ return 0, 0, err
+ }
+
+ negativeIndex = 0
+ case 'b':
+ if helper.numberFormat == hex {
+ break
+ }
+ fallthrough
+ case 'o', 'x':
+ if i == 0 && b[i] != '0' {
+ return 0, 0, NewParseError("incorrect base format, expected leading '0'")
+ }
+
+ if i != 1 {
+ return 0, 0, NewParseError(fmt.Sprintf("incorrect base format found %s at %d index", string(b[i]), i))
+ }
+
+ if err := helper.Determine(b[i]); err != nil {
+ return 0, 0, err
+ }
+ default:
+ if isWhitespace(b[i]) {
+ break loop
+ }
+
+ if isNewline(b[i:]) {
+ break loop
+ }
+
+ if !(helper.numberFormat == hex && isHexByte(b[i])) {
+ if i+2 < len(b) && !isNewline(b[i:i+2]) {
+ return 0, 0, NewParseError("invalid numerical character")
+ } else if !isNewline([]rune{b[i]}) {
+ return 0, 0, NewParseError("invalid numerical character")
+ }
+
+ break loop
+ }
+ }
+ }
+ }
+
+ return helper.Base(), i, nil
+}
+
+// isDigit will return whether or not something is an integer
+func isDigit(b rune) bool {
+ return b >= '0' && b <= '9'
+}
+
+func hasExponent(v []rune) bool {
+ return contains(v, 'e') || contains(v, 'E')
+}
+
+func isBinaryByte(b rune) bool {
+ switch b {
+ case '0', '1':
+ return true
+ default:
+ return false
+ }
+}
+
+func isOctalByte(b rune) bool {
+ switch b {
+ case '0', '1', '2', '3', '4', '5', '6', '7':
+ return true
+ default:
+ return false
+ }
+}
+
+func isHexByte(b rune) bool {
+ if isDigit(b) {
+ return true
+ }
+ return (b >= 'A' && b <= 'F') ||
+ (b >= 'a' && b <= 'f')
+}
+
+func getValue(b []rune) (int, error) {
+ i := 0
+
+ for i < len(b) {
+ if isNewline(b[i:]) {
+ break
+ }
+
+ if isOp(b[i:]) {
+ break
+ }
+
+ valid, n, err := isValid(b[i:])
+ if err != nil {
+ return 0, err
+ }
+
+ if !valid {
+ break
+ }
+
+ i += n
+ }
+
+ return i, nil
+}
+
+// getNegativeNumber will return a negative number from a
+// byte slice. This will iterate through all characters until
+// a non-digit has been found.
+func getNegativeNumber(b []rune) int {
+ if b[0] != '-' {
+ return 0
+ }
+
+ i := 1
+ for ; i < len(b); i++ {
+ if !isDigit(b[i]) {
+ return i
+ }
+ }
+
+ return i
+}
+
+// isEscaped will return whether or not the character is an escaped
+// character.
+func isEscaped(value []rune, b rune) bool {
+ if len(value) == 0 {
+ return false
+ }
+
+ switch b {
+ case '\'': // single quote
+ case '"': // quote
+ case 'n': // newline
+ case 't': // tab
+ case '\\': // backslash
+ default:
+ return false
+ }
+
+ return value[len(value)-1] == '\\'
+}
+
+func getEscapedByte(b rune) (rune, error) {
+ switch b {
+ case '\'': // single quote
+ return '\'', nil
+ case '"': // quote
+ return '"', nil
+ case 'n': // newline
+ return '\n', nil
+ case 't': // table
+ return '\t', nil
+ case '\\': // backslash
+ return '\\', nil
+ default:
+ return b, NewParseError(fmt.Sprintf("invalid escaped character %c", b))
+ }
+}
+
+func removeEscapedCharacters(b []rune) []rune {
+ for i := 0; i < len(b); i++ {
+ if isEscaped(b[:i], b[i]) {
+ c, err := getEscapedByte(b[i])
+ if err != nil {
+ return b
+ }
+
+ b[i-1] = c
+ b = append(b[:i], b[i+1:]...)
+ i--
+ }
+ }
+
+ return b
+}
diff --git a/services/ini/visitor.go b/services/ini/visitor.go
new file mode 100644
index 000000000..94841c324
--- /dev/null
+++ b/services/ini/visitor.go
@@ -0,0 +1,166 @@
+package ini
+
+import (
+ "fmt"
+ "sort"
+)
+
+// Visitor is an interface used by walkers that will
+// traverse an array of ASTs.
+type Visitor interface {
+ VisitExpr(AST) error
+ VisitStatement(AST) error
+}
+
+// DefaultVisitor is used to visit statements and expressions
+// and ensure that they are both of the correct format.
+// In addition, upon visiting this will build sections and populate
+// the Sections field which can be used to retrieve profile
+// configuration.
+type DefaultVisitor struct {
+ scope string
+ Sections Sections
+}
+
+// NewDefaultVisitor return a DefaultVisitor
+func NewDefaultVisitor() *DefaultVisitor {
+ return &DefaultVisitor{
+ Sections: Sections{
+ container: map[string]Section{},
+ },
+ }
+}
+
+// VisitExpr visits expressions...
+func (v *DefaultVisitor) VisitExpr(expr AST) error {
+ t := v.Sections.container[v.scope]
+ if t.values == nil {
+ t.values = values{}
+ }
+
+ switch expr.Kind {
+ case ASTKindExprStatement:
+ opExpr := expr.GetRoot()
+ switch opExpr.Kind {
+ case ASTKindEqualExpr:
+ children := opExpr.GetChildren()
+ if len(children) <= 1 {
+ return NewParseError("unexpected token type")
+ }
+
+ rhs := children[1]
+
+ if rhs.Root.Type() != TokenLit {
+ return NewParseError("unexpected token type")
+ }
+
+ key := EqualExprKey(opExpr)
+ v, err := newValue(rhs.Root.ValueType, rhs.Root.base, rhs.Root.Raw())
+ if err != nil {
+ return err
+ }
+
+ t.values[key] = v
+ default:
+ return NewParseError(fmt.Sprintf("unsupported expression %v", expr))
+ }
+ default:
+ return NewParseError(fmt.Sprintf("unsupported expression %v", expr))
+ }
+
+ v.Sections.container[v.scope] = t
+ return nil
+}
+
+// VisitStatement visits statements...
+func (v *DefaultVisitor) VisitStatement(stmt AST) error {
+ switch stmt.Kind {
+ case ASTKindCompletedSectionStatement:
+ child := stmt.GetRoot()
+ if child.Kind != ASTKindSectionStatement {
+ return NewParseError(fmt.Sprintf("unsupported child statement: %T", child))
+ }
+
+ name := string(child.Root.Raw())
+ v.Sections.container[name] = Section{}
+ v.scope = name
+ default:
+ return NewParseError(fmt.Sprintf("unsupported statement: %s", stmt.Kind))
+ }
+
+ return nil
+}
+
+// Sections is a map of Section structures that represent
+// a configuration.
+type Sections struct {
+ container map[string]Section
+}
+
+// GetSection will return section p. If section p does not exist,
+// false will be returned in the second parameter.
+func (t Sections) GetSection(p string) (Section, bool) {
+ v, ok := t.container[p]
+ return v, ok
+}
+
+// values represents a map of union values.
+type values map[string]Value
+
+// List will return a list of all sections that were successfully
+// parsed.
+func (t Sections) List() []string {
+ keys := make([]string, len(t.container))
+ i := 0
+ for k := range t.container {
+ keys[i] = k
+ i++
+ }
+
+ sort.Strings(keys)
+ return keys
+}
+
+// Section contains a name and values. This represent
+// a sectioned entry in a configuration file.
+type Section struct {
+ Name string
+ values values
+}
+
+// Has will return whether or not an entry exists in a given section
+func (t Section) Has(k string) bool {
+ _, ok := t.values[k]
+ return ok
+}
+
+// ValueType will returned what type the union is set to. If
+// k was not found, the NoneType will be returned.
+func (t Section) ValueType(k string) (ValueType, bool) {
+ v, ok := t.values[k]
+ return v.Type, ok
+}
+
+// Bool returns a bool value at k
+func (t Section) Bool(k string) bool {
+ return t.values[k].BoolValue()
+}
+
+// Int returns an integer value at k
+func (t Section) Int(k string) int64 {
+ return t.values[k].IntValue()
+}
+
+// Float64 returns a float value at k
+func (t Section) Float64(k string) float64 {
+ return t.values[k].FloatValue()
+}
+
+// String returns the string value at k
+func (t Section) String(k string) string {
+ _, ok := t.values[k]
+ if !ok {
+ return ""
+ }
+ return t.values[k].StringValue()
+}
diff --git a/services/ini/walker.go b/services/ini/walker.go
new file mode 100644
index 000000000..99915f7f7
--- /dev/null
+++ b/services/ini/walker.go
@@ -0,0 +1,25 @@
+package ini
+
+// Walk will traverse the AST using the v, the Visitor.
+func Walk(tree []AST, v Visitor) error {
+ for _, node := range tree {
+ switch node.Kind {
+ case ASTKindExpr,
+ ASTKindExprStatement:
+
+ if err := v.VisitExpr(node); err != nil {
+ return err
+ }
+ case ASTKindStatement,
+ ASTKindCompletedSectionStatement,
+ ASTKindNestedSectionStatement,
+ ASTKindCompletedNestedSectionStatement:
+
+ if err := v.VisitStatement(node); err != nil {
+ return err
+ }
+ }
+ }
+
+ return nil
+}
diff --git a/services/ini/ws_token.go b/services/ini/ws_token.go
new file mode 100644
index 000000000..7ffb4ae06
--- /dev/null
+++ b/services/ini/ws_token.go
@@ -0,0 +1,24 @@
+package ini
+
+import (
+ "unicode"
+)
+
+// isWhitespace will return whether or not the character is
+// a whitespace character.
+//
+// Whitespace is defined as a space or tab.
+func isWhitespace(c rune) bool {
+ return unicode.IsSpace(c) && c != '\n' && c != '\r'
+}
+
+func newWSToken(b []rune) (Token, int, error) {
+ i := 0
+ for ; i < len(b); i++ {
+ if !isWhitespace(b[i]) {
+ break
+ }
+ }
+
+ return newToken(TokenWS, b[:i], NoneType), i, nil
+}