scaleway-delete-image-plugin/main.go

226 lines
5.6 KiB
Go

package main
import (
"context"
"encoding/json"
"errors"
"fmt"
"io"
"net/http"
"codeberg.org/woodpecker-plugins/go-plugin"
"github.com/rs/zerolog/log"
"github.com/urfave/cli/v2"
)
type Settings struct {
Token string
ImageName string
Tag string
Region string
}
type Plugin struct {
*plugin.Plugin
Settings *Settings
}
func (p *Plugin) Flags() []cli.Flag {
return []cli.Flag{
&cli.StringFlag{
Name: "token",
Usage: "Token for the Scaleway API",
EnvVars: []string{"PLUGIN_TOKEN"},
Destination: &p.Settings.Token,
},
&cli.StringFlag{
Name: "image-name",
Usage: "The image from which the tag should be deleted",
EnvVars: []string{"PLUGIN_IMAGE_NAME"},
Destination: &p.Settings.ImageName,
},
&cli.StringFlag{
Name: "tag",
Usage: "The tag that should be deleted",
EnvVars: []string{"PLUGIN_TAG"},
Destination: &p.Settings.Tag,
},
&cli.StringFlag{
Name: "region",
Usage: "The Scaleway region the registry exists",
EnvVars: []string{"PLUGIN_REGION"},
Destination: &p.Settings.Region,
},
}
}
func (p *Plugin) Execute(ctx context.Context) error {
missingFlags := requiredFlags(p.Settings)
if len(missingFlags) != 0 {
log.Error().Msg("following flags are missing:")
for _, errMsg := range missingFlags {
log.Error().Msg("-" + errMsg)
}
return errors.New("missing required flags")
}
log.Info().Msg("start")
token := p.Settings.Token
region := p.Settings.Region
imageName := p.Settings.ImageName
log.Debug().Msg("get image id")
imageId, err := getImageId(region, imageName, token)
if err != nil {
return fmt.Errorf("get image id: %w", err)
}
tagName := p.Settings.Tag
log.Debug().Msg("get tag id")
tagId, err := getImageTagId(region, tagName, imageId, token)
if err != nil {
return fmt.Errorf("get tag id: %w", err)
}
log.Debug().Msg("delete image id")
err = deleteImageTagById(region, tagId, token)
if err != nil {
return fmt.Errorf("delete tag id: %w", err)
}
log.Info().Msg("finish")
return nil
}
func requiredFlags(settings *Settings) []string {
var errorList []string
if settings.ImageName == "" {
errorList = append(errorList, "image-name")
}
if settings.Tag == "" {
errorList = append(errorList, "tag")
}
if settings.Region == "" {
errorList = append(errorList, "region")
}
if settings.Token == "" {
errorList = append(errorList, "token")
}
return errorList
}
func main() {
p := &Plugin{
Settings: &Settings{},
}
p.Plugin = plugin.New(plugin.Options{
Name: "Scaleway Delete Image Plugin",
Description: "Automatically delete image tags from Scaleway registry",
Flags: p.Flags(),
Execute: p.Execute,
})
p.Run()
}
type ImagesResponse struct {
Images []Image `json:"images"`
}
type Image struct {
Id string `json:"id"`
}
func getImageId(region, imageName, token string) (string, error) {
url := fmt.Sprintf("https://api.scaleway.com/registry/v1/regions/%s/images?name=%s", region, imageName)
res, err := doRequest(http.MethodGet, url, token)
if err != nil {
return "", fmt.Errorf("get request for image id: %w", err)
}
defer closeLogger(res.Body)
if res.StatusCode != 200 {
return "", fmt.Errorf("expected status code 200 but got: %d", res.StatusCode)
}
body, err := io.ReadAll(res.Body)
if err != nil {
return "", fmt.Errorf("reading response body for image id: %w", err)
}
var imagesResponse ImagesResponse
err = json.Unmarshal(body, &imagesResponse)
if err != nil {
return "", fmt.Errorf("unmarshal body for image id: %w", err)
}
if len(imagesResponse.Images) != 1 {
return "", fmt.Errorf("expected 1 image but found '%d' with the name: %s", len(imagesResponse.Images), imageName)
}
return imagesResponse.Images[0].Id, nil
}
type TagsResponse struct {
Tags []Tag `json:"tags"`
}
type Tag struct {
Id string `json:"id"`
}
func getImageTagId(region, tagName, imageId, token string) (string, error) {
url := fmt.Sprintf("https://api.scaleway.com/registry/v1/regions/%s/images/%s/tags?name=%s", region, imageId, tagName)
res, err := doRequest(http.MethodGet, url, token)
if err != nil {
return "", fmt.Errorf("get request tag id: %w", err)
}
defer closeLogger(res.Body)
if res.StatusCode != 200 {
return "", fmt.Errorf("expected status code 200 but got: %d", res.StatusCode)
}
body, err := io.ReadAll(res.Body)
if err != nil {
return "", fmt.Errorf("reading response body for tag id: %w", err)
}
var tagsResponse TagsResponse
err = json.Unmarshal(body, &tagsResponse)
if err != nil {
return "", fmt.Errorf("unmarshal body for tag id: %w", err)
}
if len(tagsResponse.Tags) != 1 {
return "", fmt.Errorf("expected 1 but found '%d' with name: %s", len(tagsResponse.Tags), tagName)
}
return tagsResponse.Tags[0].Id, nil
}
func deleteImageTagById(region, tagId, token string) error {
url := fmt.Sprintf("https://api.scaleway.com/registry/v1/regions/%s/tags/%s", region, tagId)
res, err := doRequest(http.MethodDelete, url, token)
if err != nil {
return fmt.Errorf("deleting tag id: %w", err)
}
defer closeLogger(res.Body)
if res.StatusCode != 200 {
return fmt.Errorf("expected status code 200 but got: %d", res.StatusCode)
}
return nil
}
func closeLogger(closer io.Closer) {
err := closer.Close()
if err != nil {
log.Warn().Msgf("closing io operation: %s", err.Error())
}
}
func doRequest(method, url, token string) (*http.Response, error) {
req, err := http.NewRequest(method, url, nil)
if err != nil {
return nil, fmt.Errorf("building request: %w", err)
}
req.Header.Set("X-Auth-Token", token)
res, err := http.DefaultClient.Do(req)
if err != nil {
return nil, fmt.Errorf("making request: %w", err)
}
return res, nil
}