226 lines
5.6 KiB
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
|
|
}
|