diff --git a/cmd/wire_gen.go b/cmd/wire_gen.go index 404cb9d46..375af1bb8 100644 --- a/cmd/wire_gen.go +++ b/cmd/wire_gen.go @@ -282,11 +282,12 @@ func NewGateway(c Config) (component, error) { } controllerServiceClientConfig := gatewayConfig.Controller controllerServiceClient := grpc.NewControllerServiceClient(controllerServiceClientConfig) + imageConfig := c.Image publicKeys, err := provideRepositoryPublicKey(c) if err != nil { return nil, err } - service, err := apiserver.NewService(artifactRepository, applicationRepository, buildRepository, environmentRepository, gitRepositoryRepository, userRepository, storage, mariaDBManager, mongoDBManager, metricsService, containerLogger, controllerServiceClient, publicKeys) + service, err := apiserver.NewService(artifactRepository, applicationRepository, buildRepository, environmentRepository, gitRepositoryRepository, userRepository, storage, mariaDBManager, mongoDBManager, metricsService, containerLogger, controllerServiceClient, imageConfig, publicKeys) if err != nil { return nil, err } diff --git a/pkg/usecase/apiserver/app_service.go b/pkg/usecase/apiserver/app_service.go index f23405e27..5bdfba93c 100644 --- a/pkg/usecase/apiserver/app_service.go +++ b/pkg/usecase/apiserver/app_service.go @@ -3,6 +3,8 @@ package apiserver import ( "context" "fmt" + "github.com/regclient/regclient/types/ref" + "github.com/traPtitech/neoshowcase/pkg/util/regutil" "strconv" "github.com/friendsofgo/errors" @@ -274,7 +276,31 @@ func (s *Service) deleteApplicationDatabase(ctx context.Context, app *domain.App return nil } +func (s *Service) deleteApplicationImages(ctx context.Context, app *domain.Application) error { + if app.DeployType != domain.DeployTypeRuntime { + return nil + } + + imageName := s.image.ImageName(app.ID) + tags, err := regutil.TagList(ctx, s.registry, imageName) + if err != nil { + return err + } + for _, tag := range tags { + tagRef, err := ref.New(imageName + ":" + tag) + if err != nil { + return err + } + err = s.registry.TagDelete(ctx, tagRef) + if err != nil { + return err + } + } + return nil +} + func (s *Service) DeleteApplication(ctx context.Context, id string) error { + // Validate err := s.isApplicationOwner(ctx, id) if err != nil { return err @@ -288,6 +314,7 @@ func (s *Service) DeleteApplication(ctx context.Context, id string) error { return newError(ErrorTypeBadRequest, "stop the application first before deleting", nil) } + // Delete app database env, err := s.envRepo.GetEnv(ctx, domain.GetEnvCondition{ApplicationID: optional.From(id)}) if err != nil { return err @@ -296,6 +323,13 @@ func (s *Service) DeleteApplication(ctx context.Context, id string) error { if err != nil { return err } + // Delete runtime app image in background + go func() { + err := s.deleteApplicationImages(context.WithoutCancel(ctx), app) + if err != nil { + log.Errorf("Deleting application %v (id: %v) image: %+v", app.Name, app.ID, err) + } + }() // delete artifacts artifacts, err := s.artifactRepo.GetArtifacts(ctx, domain.GetArtifactCondition{ApplicationID: optional.From(app.ID)}) diff --git a/pkg/usecase/apiserver/service.go b/pkg/usecase/apiserver/service.go index d9b31ff26..e0bb04e13 100644 --- a/pkg/usecase/apiserver/service.go +++ b/pkg/usecase/apiserver/service.go @@ -2,6 +2,8 @@ package apiserver import ( "context" + "github.com/regclient/regclient" + "github.com/traPtitech/neoshowcase/pkg/domain/builder" "github.com/friendsofgo/errors" "github.com/go-git/go-git/v5/plumbing/transport/ssh" @@ -35,6 +37,8 @@ type Service struct { containerLogger domain.ContainerLogger controller domain.ControllerServiceClient fallbackKey *ssh.PublicKeys + image builder.ImageConfig + registry *regclient.RegClient systemInfo func(ctx context.Context) (*domain.SystemInfo, error) tmpKeys *tmpKeyPairService @@ -53,6 +57,7 @@ func NewService( metricsService domain.MetricsService, containerLogger domain.ContainerLogger, controller domain.ControllerServiceClient, + image builder.ImageConfig, fallbackKey *ssh.PublicKeys, ) (*Service, error) { return &Service{ @@ -69,6 +74,8 @@ func NewService( containerLogger: containerLogger, controller: controller, fallbackKey: fallbackKey, + image: image, + registry: image.NewRegistry(), systemInfo: scutil.Once(controller.GetSystemInfo), tmpKeys: newTmpKeyPairService(), diff --git a/pkg/usecase/cleaner/service.go b/pkg/usecase/cleaner/service.go index ca806a7d6..d417c6bb5 100644 --- a/pkg/usecase/cleaner/service.go +++ b/pkg/usecase/cleaner/service.go @@ -59,7 +59,7 @@ func NewService( c.start = func() { go loop.Loop(ctx, func(ctx context.Context) { start := time.Now() - err := c.pruneImages(ctx, r, image.Registry.Addr) + err := c.pruneImages(ctx, r) if err != nil { log.Errorf("failed to prune images: %+v", err) return @@ -91,14 +91,14 @@ func (c *cleanerService) Shutdown(_ context.Context) error { return nil } -func (c *cleanerService) pruneImages(ctx context.Context, r *regclient.RegClient, regHost string) error { +func (c *cleanerService) pruneImages(ctx context.Context, r *regclient.RegClient) error { applications, err := c.appRepo.GetApplications(ctx, domain.GetApplicationCondition{DeployType: optional.From(domain.DeployTypeRuntime)}) if err != nil { return err } for _, app := range applications { - err = c.pruneImage(ctx, r, regHost, c.image.NamePrefix, app) + err = c.pruneImage(ctx, r, app) if err != nil { log.Errorf("pruning image %v: %+v", c.image.NamePrefix+app.ID, err) // fail-safe for each image @@ -108,9 +108,9 @@ func (c *cleanerService) pruneImages(ctx context.Context, r *regclient.RegClient return nil } -func (c *cleanerService) pruneImage(ctx context.Context, r *regclient.RegClient, regHost string, imagePrefix string, app *domain.Application) error { - imageName := imagePrefix + app.ID - tags, err := regutil.TagList(ctx, r, regHost, imageName) +func (c *cleanerService) pruneImage(ctx context.Context, r *regclient.RegClient, app *domain.Application) error { + imageName := c.image.ImageName(app.ID) + tags, err := regutil.TagList(ctx, r, imageName) if err != nil { return errors.Wrap(err, "getting tags") } @@ -127,7 +127,7 @@ func (c *cleanerService) pruneImage(ctx context.Context, r *regclient.RegClient, // NOTE: needs manual execution of "registry garbage-collect --delete-untagged" in docker registry side // to actually delete the layers // https://docs.docker.com/registry/garbage-collection/ - tagRef, err := ref.New(regHost + "/" + imageName + ":" + tag) + tagRef, err := ref.New(imageName + ":" + tag) if err != nil { return err } diff --git a/pkg/util/regutil/list.go b/pkg/util/regutil/list.go index bcd3ed4cd..19fac5b1e 100644 --- a/pkg/util/regutil/list.go +++ b/pkg/util/regutil/list.go @@ -27,7 +27,7 @@ func RepoList(ctx context.Context, r *regclient.RegClient, regHost string) ([]st } } -func TagList(ctx context.Context, r *regclient.RegClient, regHost string, imageName string) ([]string, error) { +func TagList(ctx context.Context, r *regclient.RegClient, imageName string) ([]string, error) { const limit = 100 var tags []string for { @@ -35,7 +35,7 @@ func TagList(ctx context.Context, r *regclient.RegClient, regHost string, imageN if len(tags) > 0 { opts = append(opts, scheme.WithTagLast(tags[len(tags)-1])) } - repoRef, err := ref.New(regHost + "/" + imageName) + repoRef, err := ref.New(imageName) if err != nil { return nil, err }