Skip to content

Commit

Permalink
Add VolumeListModificationsHandler and VolumeListSnapshotsHandler (#17)
Browse files Browse the repository at this point in the history
* add VolumeListModificationsHandler and VolumeListSnapshotsHandler

* update go modules

* check for nil

Co-authored-by: Tenyo Grozev <[email protected]>
  • Loading branch information
tenyo and Tenyo Grozev authored Mar 22, 2022
1 parent 2da0c99 commit c943798
Show file tree
Hide file tree
Showing 9 changed files with 383 additions and 6 deletions.
79 changes: 79 additions & 0 deletions api/handlers_volumes.go
Original file line number Diff line number Diff line change
Expand Up @@ -112,3 +112,82 @@ func (s *server) VolumeGetHandler(w http.ResponseWriter, r *http.Request) {

handleResponseOk(w, toEc2VolumeResponse(out[0]))
}

func (s *server) VolumeListModificationsHandler(w http.ResponseWriter, r *http.Request) {
w = LogWriter{w}
vars := mux.Vars(r)
account := s.mapAccountNumber(vars["account"])
id := vars["id"]

role := fmt.Sprintf("arn:aws:iam::%s:role/%s", account, s.session.RoleName)

session, err := s.assumeRole(
r.Context(),
s.session.ExternalID,
role,
"",
"arn:aws:iam::aws:policy/AmazonEC2ReadOnlyAccess",
)
if err != nil {
msg := fmt.Sprintf("failed to assume role in account: %s", account)
handleError(w, apierror.New(apierror.ErrForbidden, msg, err))
return
}

service := ec2.New(
ec2.WithSession(session.Session),
ec2.WithOrg(s.org),
)

out, err := service.ListVolumeModifications(r.Context(), id)
if err != nil {
handleError(w, err)
return
}

w.Header().Set("X-Items", strconv.Itoa(len(out)))

handleResponseOk(w, toEc2VolumeModificationsResponse(out))
}

func (s *server) VolumeListSnapshotsHandler(w http.ResponseWriter, r *http.Request) {
w = LogWriter{w}
vars := mux.Vars(r)
account := s.mapAccountNumber(vars["account"])
id := vars["id"]

role := fmt.Sprintf("arn:aws:iam::%s:role/%s", account, s.session.RoleName)

session, err := s.assumeRole(
r.Context(),
s.session.ExternalID,
role,
"",
"arn:aws:iam::aws:policy/AmazonEC2ReadOnlyAccess",
)
if err != nil {
msg := fmt.Sprintf("failed to assume role in account: %s", account)
handleError(w, apierror.New(apierror.ErrForbidden, msg, err))
return
}

service := ec2.New(
ec2.WithSession(session.Session),
ec2.WithOrg(s.org),
)

out, err := service.ListVolumeSnapshots(r.Context(), id)
if err != nil {
handleError(w, err)
return
}

list := make([]map[string]string, 0, len(out))
for _, s := range out {
list = append(list, map[string]string{"id": s})
}

w.Header().Set("X-Items", strconv.Itoa(len(out)))

handleResponseOk(w, list)
}
4 changes: 2 additions & 2 deletions api/routes.go
Original file line number Diff line number Diff line change
Expand Up @@ -45,8 +45,8 @@ func (s *server) routes() {
api.HandleFunc("/{account}/sgs/{id}", s.SecurityGroupGetHandler).Methods(http.MethodGet)
api.HandleFunc("/{account}/volumes", s.VolumeListHandler).Methods(http.MethodGet)
api.HandleFunc("/{account}/volumes/{id}", s.VolumeGetHandler).Methods(http.MethodGet)
api.HandleFunc("/{account}/volumes/{id}/modifications", s.ProxyRequestHandler).Methods(http.MethodGet)
api.HandleFunc("/{account}/volumes/{id}/snapshots", s.ProxyRequestHandler).Methods(http.MethodGet)
api.HandleFunc("/{account}/volumes/{id}/modifications", s.VolumeListModificationsHandler).Methods(http.MethodGet)
api.HandleFunc("/{account}/volumes/{id}/snapshots", s.VolumeListSnapshotsHandler).Methods(http.MethodGet)
api.HandleFunc("/{account}/snapshots", s.SnapshotListHandler).Methods(http.MethodGet)
api.HandleFunc("/{account}/snapshots/{id}", s.SnapshotGetHandler).Methods(http.MethodGet)
api.HandleFunc("/{account}/subnets", s.SubnetsListHandler).Methods(http.MethodGet).Queries("vpc", "{vpc}")
Expand Down
56 changes: 56 additions & 0 deletions api/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,10 @@ func timeFormat(t *time.Time) string {
// tzTimeFormat returns the time format with a TZ used in a few places
// TODO get rid of these places
func tzTimeFormat(t *time.Time) string {
if t == nil {
return ""
}

return t.UTC().Format("2006-01-02 15:04:05 MST")
}

Expand Down Expand Up @@ -199,6 +203,58 @@ func toEc2VolumeResponse(volume *ec2.Volume) *Ec2VolumeResponse {
return &response
}

type Ec2VolumeModification struct {
EndTime string `json:"end_time"`
ModificationState string `json:"modification_state"`
OriginalIops int64 `json:"original_iops"`
OriginalMultiAttachEnabled bool `json:"original_multi_attach_enabled"`
OriginalSize int64 `json:"original_size"`
OriginalThroughput int64 `json:"original_throughput"`
OriginalVolumeType string `json:"original_volume_type"`
Progress int64 `json:"progress"`
StartTime string `json:"start_time"`
StatusMessage string `json:"status_message"`
TargetIops int64 `json:"target_iops"`
TargetMultiAttachEnabled bool `json:"target_multi_attach_enabled"`
TargetSize int64 `json:"target_size"`
TargetThroughput int64 `json:"target_throughput"`
TargetVolumeType string `json:"target_volume_type"`
VolumeId string `json:"volume_id"`
}

func toEc2VolumeModificationsResponse(modifications []*ec2.VolumeModification) []Ec2VolumeModification {
if modifications == nil {
return nil
}

log.Debugf("mapping ec2 volume modifications %s", awsutil.Prettify(modifications))

response := []Ec2VolumeModification{}

for _, m := range modifications {
response = append(response, Ec2VolumeModification{
EndTime: tzTimeFormat(m.EndTime),
ModificationState: aws.StringValue(m.ModificationState),
OriginalIops: aws.Int64Value(m.OriginalIops),
OriginalMultiAttachEnabled: aws.BoolValue(m.OriginalMultiAttachEnabled),
OriginalSize: aws.Int64Value(m.OriginalSize),
OriginalThroughput: aws.Int64Value(m.OriginalThroughput),
OriginalVolumeType: aws.StringValue(m.OriginalVolumeType),
Progress: aws.Int64Value(m.Progress),
StartTime: tzTimeFormat(m.StartTime),
StatusMessage: aws.StringValue(m.StatusMessage),
TargetIops: aws.Int64Value(m.TargetIops),
TargetMultiAttachEnabled: aws.BoolValue(m.TargetMultiAttachEnabled),
TargetSize: aws.Int64Value(m.TargetSize),
TargetThroughput: aws.Int64Value(m.TargetThroughput),
TargetVolumeType: aws.StringValue(m.TargetVolumeType),
VolumeId: aws.StringValue(m.VolumeId),
})
}

return response
}

type Ec2SnapshotResponse struct {
CreatedAt string `json:"created_at"`
Description string `json:"description"`
Expand Down
111 changes: 111 additions & 0 deletions api/types_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -290,6 +290,117 @@ func Test_toEc2InstanceResponse(t *testing.T) {
}
}

func Test_toEc2VolumeModificationsResponse(t *testing.T) {
testTime, err := time.Parse("2006-01-02 15:04:05 -0700 MST", "2022-02-22 22:22:22 +0000 UTC")
if err != nil {
t.Errorf("failed to parse time: %s", err)
}

type args struct {
modifications []*ec2.VolumeModification
}
tests := []struct {
name string
args args
want []Ec2VolumeModification
}{
{
name: "nil input",
args: args{modifications: nil},
want: nil,
},
{
name: "modification completed",
args: args{modifications: []*ec2.VolumeModification{
{
EndTime: aws.Time(testTime),
ModificationState: aws.String("completed"),
OriginalIops: aws.Int64(3000),
OriginalMultiAttachEnabled: aws.Bool(false),
OriginalSize: aws.Int64(8),
OriginalThroughput: aws.Int64(125),
OriginalVolumeType: aws.String("gp3"),
Progress: aws.Int64(100),
StartTime: aws.Time(testTime),
TargetIops: aws.Int64(3000),
TargetMultiAttachEnabled: aws.Bool(false),
TargetSize: aws.Int64(100),
TargetThroughput: aws.Int64(125),
TargetVolumeType: aws.String("gp3"),
VolumeId: aws.String("vol-0123456789abcdef0"),
},
}},
want: []Ec2VolumeModification{
{
EndTime: "2022-02-22 22:22:22 UTC",
ModificationState: "completed",
OriginalIops: 3000,
OriginalMultiAttachEnabled: false,
OriginalSize: 8,
OriginalThroughput: 125,
OriginalVolumeType: "gp3",
Progress: 100,
StartTime: "2022-02-22 22:22:22 UTC",
TargetIops: 3000,
TargetMultiAttachEnabled: false,
TargetSize: 100,
TargetThroughput: 125,
TargetVolumeType: "gp3",
VolumeId: "vol-0123456789abcdef0",
},
},
},
{
name: "modification in progress",
args: args{modifications: []*ec2.VolumeModification{
{
ModificationState: aws.String("optimizing"),
OriginalIops: aws.Int64(3000),
OriginalMultiAttachEnabled: aws.Bool(false),
OriginalSize: aws.Int64(8),
OriginalThroughput: aws.Int64(125),
OriginalVolumeType: aws.String("gp3"),
Progress: aws.Int64(0),
StartTime: aws.Time(testTime),
TargetIops: aws.Int64(3000),
TargetMultiAttachEnabled: aws.Bool(false),
TargetSize: aws.Int64(100),
TargetThroughput: aws.Int64(125),
TargetVolumeType: aws.String("gp3"),
VolumeId: aws.String("vol-0123456789abcdef0"),
},
}},
want: []Ec2VolumeModification{
{
EndTime: "",
ModificationState: "optimizing",
OriginalIops: 3000,
OriginalMultiAttachEnabled: false,
OriginalSize: 8,
OriginalThroughput: 125,
OriginalVolumeType: "gp3",
Progress: 0,
StartTime: "2022-02-22 22:22:22 UTC",
TargetIops: 3000,
TargetMultiAttachEnabled: false,
TargetSize: 100,
TargetThroughput: 125,
TargetVolumeType: "gp3",
VolumeId: "vol-0123456789abcdef0",
},
},
},
}

for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if got := toEc2VolumeModificationsResponse(tt.args.modifications); !reflect.DeepEqual(got, tt.want) {
t.Errorf("toEc2VolumeModificationsResponse() = %+v, want %+v", got, tt.want)
}
})
}
}

func Test_toEc2VolumeResponse(t *testing.T) {
type args struct {
volume *ec2.Volume
Expand Down
9 changes: 9 additions & 0 deletions ec2/filters.go
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,15 @@ func withInstanceId(id string) *ec2.Filter {
}
}

func withVolumeId(id string) *ec2.Filter {
return &ec2.Filter{
Name: aws.String("volume-id"),
Values: aws.StringSlice(
[]string{id},
),
}
}

func isAvailable() *ec2.Filter {
return &ec2.Filter{
Name: aws.String("state"),
Expand Down
29 changes: 29 additions & 0 deletions ec2/filters_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,35 @@ func Test_withInstanceId(t *testing.T) {
}
}

func Test_withVolumeId(t *testing.T) {
type args struct {
id string
}
tests := []struct {
name string
args args
want *ec2.Filter
}{
{
name: "with volume id",
args: args{id: "vol-0123456789abcdef0"},
want: &ec2.Filter{
Name: aws.String("volume-id"),
Values: aws.StringSlice(
[]string{"vol-0123456789abcdef0"},
),
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if got := withVolumeId(tt.args.id); !reflect.DeepEqual(got, tt.want) {
t.Errorf("withVolumeId() = %v, want %v", got, tt.want)
}
})
}
}

func Test_inVpc(t *testing.T) {
type args struct {
vpc string
Expand Down
Loading

0 comments on commit c943798

Please sign in to comment.