Skip to content

Commit

Permalink
Merge pull request #77 from acidlemon/fix/purge-api
Browse files Browse the repository at this point in the history
Refactoring.
  • Loading branch information
fujiwara authored Nov 6, 2024
2 parents f4a9d94 + 092b549 commit 713242e
Show file tree
Hide file tree
Showing 4 changed files with 127 additions and 94 deletions.
13 changes: 6 additions & 7 deletions ecs.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@ import (
"encoding/base64"
"fmt"
"log/slog"
"regexp"
"sort"
"strings"
"sync"
Expand Down Expand Up @@ -43,28 +42,28 @@ type Information struct {
task *types.Task
}

func (info Information) ShouldBePurged(duration time.Duration, excludesMap map[string]struct{}, excludeTagsMap map[string]string, excludeRegexp *regexp.Regexp) bool {
func (info Information) ShouldBePurged(p *PurgeParams) bool {
if info.LastStatus != statusRunning {
slog.Info(f("skip not running task: %s subdomain: %s", info.LastStatus, info.SubDomain))
return false
}
if _, ok := excludesMap[info.SubDomain]; ok {
if _, ok := p.excludesMap[info.SubDomain]; ok {
slog.Info(f("skip exclude subdomain: %s", info.SubDomain))
return false
}
for _, t := range info.Tags {
k, v := aws.ToString(t.Key), aws.ToString(t.Value)
if ev, ok := excludeTagsMap[k]; ok && ev == v {
if ev, ok := p.excludeTagsMap[k]; ok && ev == v {
slog.Info(f("skip exclude tag: %s=%s subdomain: %s", k, v, info.SubDomain))
return false
}
}
if excludeRegexp != nil && excludeRegexp.MatchString(info.SubDomain) {
slog.Info(f("skip exclude regexp: %s subdomain: %s", excludeRegexp.String(), info.SubDomain))
if p.ExcludeRegexp != nil && p.ExcludeRegexp.MatchString(info.SubDomain) {
slog.Info(f("skip exclude regexp: %s subdomain: %s", p.ExcludeRegexp.String(), info.SubDomain))
return false
}

begin := time.Now().Add(-duration)
begin := time.Now().Add(-p.Duration)
if info.Created.After(begin) {
slog.Info(f("skip recent created: %s subdomain: %s", info.Created.Format(time.RFC3339), info.SubDomain))
return false
Expand Down
83 changes: 46 additions & 37 deletions ecs_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ package mirageecs_test

import (
"context"
"regexp"
"testing"
"time"

Expand Down Expand Up @@ -115,64 +114,69 @@ func TestToECSKeyValuePairsAndTags(t *testing.T) {
}

var purgeTests = []struct {
name string
duration time.Duration
excludesMap map[string]struct{}
excludeTagsMap map[string]string
excludeRegexp *regexp.Regexp
expected bool
name string
param *mirageecs.APIPurgeRequest
expected bool
}{
{
name: "young task",
duration: 10 * time.Minute,
name: "young task",
param: &mirageecs.APIPurgeRequest{
Duration: "600", // 10 minutes
},
expected: false,
},
{
name: "old task",
duration: 1 * time.Minute,
name: "old task",
param: &mirageecs.APIPurgeRequest{
Duration: "300", // 5 minutes
},
expected: true,
},
{
name: "excluded task",
duration: 1 * time.Minute,
excludesMap: map[string]struct{}{
"test": {},
name: "excluded task",
param: &mirageecs.APIPurgeRequest{
Duration: "300",
Excludes: []string{"test"},
},
expected: false,
},
{
name: "excluded task not match",
duration: 1 * time.Minute,
excludesMap: map[string]struct{}{
"test2": {},
name: "excluded task not match",
param: &mirageecs.APIPurgeRequest{
Duration: "300",
Excludes: []string{"test2"},
},
expected: true,
},
{
name: "excluded tag",
duration: 1 * time.Minute,
excludeTagsMap: map[string]string{
"DontPurge": "true",
name: "excluded tag",
param: &mirageecs.APIPurgeRequest{
Duration: "300",
ExcludeTags: []string{"DontPurge:true"},
},
expected: false,
},
{
name: "excluded regexp",
duration: 1 * time.Minute,
excludeRegexp: regexp.MustCompile("te.t"),
expected: false,
name: "excluded regexp",
param: &mirageecs.APIPurgeRequest{
Duration: "300",
ExcludeRegexp: "te.t",
},
expected: false,
},
{
name: "excluded regexp not match",
duration: 1 * time.Minute,
excludeRegexp: regexp.MustCompile("text"),
expected: true,
name: "excluded regexp not match",
param: &mirageecs.APIPurgeRequest{
Duration: "300",
ExcludeRegexp: "xxx",
},
expected: true,
},
{
name: "excluded tag not match",
duration: 1 * time.Minute,
excludeTagsMap: map[string]string{
"xxx": "true",
name: "excluded tag not match",
param: &mirageecs.APIPurgeRequest{
Duration: "300",
ExcludeTags: []string{"xxx:true"},
},
expected: true,
},
Expand All @@ -186,7 +190,7 @@ func TestShouldBePurged(t *testing.T) {
GitBranch: "develop",
TaskDef: "dummy",
IPAddress: "127.0.0.1",
Created: time.Now().Add(-5 * time.Minute),
Created: time.Now().Add(-7 * time.Minute),
LastStatus: "RUNNING",
PortMap: map[string]int{"http": 80},
Env: map[string]string{"ENV": "test"},
Expand All @@ -197,7 +201,12 @@ func TestShouldBePurged(t *testing.T) {
}
for _, s := range purgeTests {
t.Run(s.name, func(t *testing.T) {
if info.ShouldBePurged(s.duration, s.excludesMap, s.excludeTagsMap, s.excludeRegexp) != s.expected {
p, err := s.param.Validate()
if err != nil {
t.Errorf("Error in Validate: %v", err)
}
t.Logf("PurgeParams: %#v", p)
if info.ShouldBePurged(p) != s.expected {
t.Errorf("Mismatch in ShouldBePurged: %v", s)
}
})
Expand Down
60 changes: 60 additions & 0 deletions types.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,11 @@ package mirageecs

import (
"encoding/json"
"fmt"
"net/url"
"regexp"
"strings"
"time"
)

// APIListResponse is a response of /api/list
Expand Down Expand Up @@ -61,6 +65,62 @@ type APIPurgeRequest struct {
ExcludeRegexp string `json:"exclude_regexp" form:"exclude_regexp"`
}

type PurgeParams struct {
Duration time.Duration
Excludes []string
ExcludeTags []string
ExcludeRegexp *regexp.Regexp

excludesMap map[string]struct{}
excludeTagsMap map[string]string
}

func (r *APIPurgeRequest) Validate() (*PurgeParams, error) {
excludes := r.Excludes
excludeTags := r.ExcludeTags
di, err := r.Duration.Int64()
if err != nil {
return nil, fmt.Errorf("invalid duration %s", r.Duration)
}
minimum := int64(PurgeMinimumDuration.Seconds())
if di < minimum {
return nil, fmt.Errorf("invalid duration %d (at least %d)", di, minimum)
}

excludesMap := make(map[string]struct{}, len(excludes))
for _, exclude := range excludes {
excludesMap[exclude] = struct{}{}
}
excludeTagsMap := make(map[string]string, len(excludeTags))
for _, excludeTag := range excludeTags {
p := strings.SplitN(excludeTag, ":", 2)
if len(p) != 2 {
return nil, fmt.Errorf("invalid exclude_tags format %s", excludeTag)
}
k, v := p[0], p[1]
excludeTagsMap[k] = v
}
var excludeRegexp *regexp.Regexp
if r.ExcludeRegexp != "" {
var err error
excludeRegexp, err = regexp.Compile(r.ExcludeRegexp)
if err != nil {
return nil, fmt.Errorf("invalid exclude_regexp %s", r.ExcludeRegexp)
}
}
duration := time.Duration(di) * time.Second

return &PurgeParams{
Duration: duration,
Excludes: excludes,
ExcludeTags: excludeTags,
ExcludeRegexp: excludeRegexp,

excludesMap: excludesMap,
excludeTagsMap: excludeTagsMap,
}, nil
}

type APITerminateRequest struct {
ID string `json:"id" form:"id"`
Subdomain string `json:"subdomain" form:"subdomain"`
Expand Down
65 changes: 15 additions & 50 deletions webapi.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ package mirageecs

import (
"context"
"errors"
"fmt"
"html/template"
"io"
Expand Down Expand Up @@ -377,72 +376,38 @@ func (api *WebApi) purge(c echo.Context) (int, error) {
if err := c.Bind(&r); err != nil {
return http.StatusBadRequest, err
}
excludes := r.Excludes
excludeTags := r.ExcludeTags
di, err := r.Duration.Int64()

p, err := r.Validate()
if err != nil {
msg := fmt.Sprintf("invalid duration %s", r.Duration)
slog.Error(msg)
return http.StatusBadRequest, errors.New(msg)
}
minimum := int64(PurgeMinimumDuration.Seconds())
if di < minimum {
msg := fmt.Sprintf("invalid duration %d (at least %d)", di, minimum)
slog.Error(msg)
return http.StatusBadRequest, errors.New(msg)
}

excludesMap := make(map[string]struct{}, len(excludes))
for _, exclude := range excludes {
excludesMap[exclude] = struct{}{}
}
excludeTagsMap := make(map[string]string, len(excludeTags))
for _, excludeTag := range excludeTags {
p := strings.SplitN(excludeTag, ":", 2)
if len(p) != 2 {
msg := fmt.Sprintf("invalid exclude_tags format %s", excludeTag)
slog.Error(msg)
return http.StatusBadRequest, errors.New(msg)
}
k, v := p[0], p[1]
excludeTagsMap[k] = v
}
var excludeRegexp *regexp.Regexp
if r.ExcludeRegexp != "" {
var err error
excludeRegexp, err = regexp.Compile(r.ExcludeRegexp)
if err != nil {
msg := fmt.Sprintf("invalid exclude_regexp %s", r.ExcludeRegexp)
slog.Error(msg)
return http.StatusBadRequest, errors.New(msg)
}
slog.Error(f("purge failed: %s", err))
return http.StatusBadRequest, err
}

duration := time.Duration(di) * time.Second

infos, err := api.runner.List(c.Request().Context(), statusRunning)
if err != nil {
slog.Error(f("list ecs failed: %s", err))
return http.StatusInternalServerError, err
}
slog.Info("purge subdomains",
"duration", duration,
"excludes", excludes,
"exclude_tags", excludeTags,
"exclude_regexp", excludeRegexp,
"duration", p.Duration,
"excludes", p.Excludes,
"exclude_tags", p.ExcludeTags,
"exclude_regexp", p.ExcludeRegexp,
)
tm := make(map[string]struct{}, len(infos))
terminates := []string{}
for _, info := range infos {
if info.ShouldBePurged(duration, excludesMap, excludeTagsMap, excludeRegexp) {
tm[info.SubDomain] = struct{}{}
if info.ShouldBePurged(p) {
terminates = append(terminates, info.SubDomain)
}
}
terminates := lo.Keys(tm)
terminates = lo.Uniq(terminates)
if len(terminates) > 0 {
slog.Info(f("purge %d subdomains", len(terminates)))
// running in background. Don't cancel by client context.
go api.purgeSubdomains(context.Background(), terminates, duration)
go api.purgeSubdomains(context.Background(), terminates, p.Duration)
}

slog.Info("no subdomains to purge")
return http.StatusOK, nil
}

Expand Down

0 comments on commit 713242e

Please # to comment.