diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index f0d04a9..188a994 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -12,13 +12,6 @@ env: REGISTRY_IMAGE: docker.io/appuio/image-cleanup jobs: - lint: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v1 - #- name: Lint YAML files - # run: docker run --rm -v $(pwd):/data docker.io/cytopia/yamllint . - test: runs-on: ubuntu-latest steps: @@ -30,31 +23,9 @@ jobs: - name: Run unit tests run: go test ./... - docker: - runs-on: ubuntu-latest - needs: - - lint - - test - steps: - - uses: actions/checkout@v1 - - name: Register qemu for multiarch build - run: docker run --rm --privileged docker.io/multiarch/qemu-user-static:register --reset - - name: Build Docker image - run: > - docker build - --tag ${REGISTRY_IMAGE}:${GITHUB_REF##*/} - --build-arg VERSION=${GITHUB_REF##*/} - --build-arg GIT_COMMIT=${GITHUB_SHA:-unknown} - . - - name: Login to Docker hub - run: docker login -u ${{ secrets.DOCKER_HUB_USER }} -p ${{ secrets.DOCKER_HUB_PASSWORD }} - - name: Push Docker image - run: docker push ${REGISTRY_IMAGE}:${GITHUB_REF##*/} - release: runs-on: ubuntu-latest needs: - - lint - test if: github.ref != 'refs/heads/master' steps: @@ -63,6 +34,8 @@ jobs: with: path: /home/runner/go/pkg/mod key: go-mod + - name: Login to Docker hub + run: docker login -u ${{ secrets.DOCKER_HUB_USER }} -p ${{ secrets.DOCKER_HUB_PASSWORD }} - uses: goreleaser/goreleaser-action@v1 with: args: release diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index c487235..b0b3801 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -11,13 +11,6 @@ on: types: [opened, reopened] jobs: - lint: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v1 - #- name: Lint YAML files - # run: docker run --rm -v $(pwd):/data docker.io/cytopia/yamllint . - test: runs-on: ubuntu-latest steps: diff --git a/.goreleaser.yml b/.goreleaser.yml index c2d12fa..4a55498 100644 --- a/.goreleaser.yml +++ b/.goreleaser.yml @@ -1,7 +1,9 @@ # Make sure to check the documentation at http://goreleaser.com builds: -- goarch: +- env: + - CGO_ENABLED=0 # this is needed otherwise the Docker image build is faulty + goarch: - amd64 - arm - arm64 @@ -11,7 +13,7 @@ builds: - darwin goarm: - 7 - - + archives: - format: binary @@ -21,6 +23,11 @@ checksum: snapshot: name_template: "{{ .Tag }}-snapshot" +dockers: +- image_templates: + - "docker.io/appuio/image-cleanup:{{ .Tag }}" + - "docker.io/appuio/image-cleanup:{{ .Major }}" + changelog: sort: asc filters: diff --git a/Dockerfile b/Dockerfile index 6dfab47..2febde4 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,37 +1,9 @@ -#______________________________________________________________________________ -#### Base Image, to save build time on local dev machine -ARG GOARCH=amd64 -ARG ARCH=amd64-edge -FROM docker.io/library/golang:1.13-alpine as builder +FROM docker.io/library/alpine:3.10 as runtime -WORKDIR /go/src/app - -COPY ["go.mod", "go.sum", "./"] - -RUN \ - go mod download - -ARG VERSION=unspecified -ARG GIT_COMMIT=unspecified - -COPY / . -RUN \ - go build -ldflags "-X main.version=${VERSION} -X main.commit=${GIT_COMMIT} -X main.date=$(date -u '+%Y-%m-%dT%TZ')" - -#______________________________________________________________________________ -#### Runtime Image -ARG ARCH=amd64-edge -FROM docker.io/multiarch/alpine:${ARCH} as runtime - -ENV ALPINE_MIRROR=http://dl-cdn.alpinelinux.org/alpine ENTRYPOINT ["image-cleanup"] RUN \ - echo "${ALPINE_MIRROR}/${ALPINE_REL}/main" > /etc/apk/repositories && \ - echo "${ALPINE_MIRROR}/${ALPINE_REL}/community" >> /etc/apk/repositories && \ - apk update && \ - apk upgrade && \ apk add --no-cache curl bash -COPY --from=builder /go/src/app/image-cleanup /usr/bin/image-cleanup +COPY image-cleanup /usr/bin/image-cleanup USER 1000:0 diff --git a/README.md b/README.md index 0fc7bf3..c566695 100644 --- a/README.md +++ b/README.md @@ -1,13 +1,19 @@ # Image Cleanup Client +[![dockeri.co](https://dockeri.co/image/appuio/image-cleanup)](https://hub.docker.com/r/appuio/image-cleanup) + +![](https://img.shields.io/github/workflow/status/appuio/image-cleanup/Release) +![](https://img.shields.io/github/v/release/appuio/image-cleanup?include_prereleases) +![](https://img.shields.io/github/issues-raw/appuio/image-cleanup) +![](https://img.shields.io/github/issues-pr-raw/appuio/image-cleanup) +![](https://img.shields.io/github/license/appuio/image-cleanup) + ## General The image cleanup client is used to clean up Docker images in a Docker Registry when they are tagged using git SHA. This helps to save space because obsolete images are being removed from the registry. -The respective licenses for attribution are placed in `/attribution`. - ## Development ### Requirements @@ -30,6 +36,8 @@ go test ./... ### Run ``` ./dist/image-cleanup_linux_amd64/image-cleanup --help +# or +docker run --rm -it appuio/image-cleanup: ``` ## License diff --git a/commands/git.go b/commands/git.go index ed20259..61933aa 100644 --- a/commands/git.go +++ b/commands/git.go @@ -3,6 +3,7 @@ package commands import ( "fmt" + log "github.com/sirupsen/logrus" "github.com/spf13/cobra" "gopkg.in/src-d/go-billy.v4/osfs" "gopkg.in/src-d/go-git.v4" @@ -18,19 +19,21 @@ var gitCmd = &cobra.Command{ Short: "Print the git HEAD", Long: `tbd`, Args: cobra.ExactValidArgs(1), - Run: func(cmd *cobra.Command, args []string) { - worktree := osfs.New(args[0]) - gitdir, err := worktree.Chroot(".git") - if err != nil { - panic(err) - } + Run: PrintGitHEAD, +} + +func PrintGitHEAD(cmd *cobra.Command, args []string) { + worktree := osfs.New(args[0]) + gitdir, err := worktree.Chroot(".git") + if err != nil { + log.WithError(err).WithField("path", ".git").Fatal("Could not change root") + } - storer := filesystem.NewStorage(gitdir, nil) - repository, err := git.Open(storer, worktree) - if err != nil { - panic(err) - } + storer := filesystem.NewStorage(gitdir, nil) + repository, err := git.Open(storer, worktree) + if err != nil { + log.WithError(err).WithField("path", ".git").Fatal("Could not open git repository") + } - fmt.Println(repository.Head()) - }, + fmt.Println(repository.Head()) } diff --git a/commands/git_test.go b/commands/git_test.go new file mode 100644 index 0000000..8995512 --- /dev/null +++ b/commands/git_test.go @@ -0,0 +1,23 @@ +package commands + +import ( + "github.com/spf13/cobra" + "testing" +) + +func Test_printGitHEAD(t *testing.T) { + type args struct { + cmd *cobra.Command + args []string + } + tests := []struct { + name string + args args + }{ + // TODO: Add test cases. + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + }) + } +} diff --git a/commands/imagestream.go b/commands/imagestream.go index 7e72f22..25845ad 100644 --- a/commands/imagestream.go +++ b/commands/imagestream.go @@ -17,38 +17,39 @@ var imagestreamCmd = &cobra.Command{ Use: "imagestream", Short: "Print imagestreams from namespace", Long: `tbd`, - Run: func(cmd *cobra.Command, args []string) { - // Instantiate loader for kubeconfig file. - kubeconfig := clientcmd.NewNonInteractiveDeferredLoadingClientConfig( - clientcmd.NewDefaultClientConfigLoadingRules(), - &clientcmd.ConfigOverrides{}, - ) - - namespace := resolveNamespace(kubeconfig) - - // Get a rest.Config from the kubeconfig file. This will be passed into all - // the client objects we create. - restconfig, err := kubeconfig.ClientConfig() - if err != nil { - panic(err) - } - - // Create an OpenShift image/v1 client. - imageclient, err := imagev1client.NewForConfig(restconfig) - if err != nil { - panic(err) - } - - imagestreamlist, err := imageclient.ImageStreams(namespace).List(metav1.ListOptions{}) - if err != nil { - panic(err) - } - - for _, imagestream := range imagestreamlist.Items { - fmt.Println(imagestream.ObjectMeta.Name) - } - - }, + Run: printImageStreamsFromNamespace, +} + +func printImageStreamsFromNamespace(cmd *cobra.Command, args []string) { + // Instantiate loader for kubeconfig file. + kubeconfig := clientcmd.NewNonInteractiveDeferredLoadingClientConfig( + clientcmd.NewDefaultClientConfigLoadingRules(), + &clientcmd.ConfigOverrides{}, + ) + + namespace := resolveNamespace(kubeconfig) + + // Get a rest.Config from the kubeconfig file. This will be passed into all + // the client objects we create. + restconfig, err := kubeconfig.ClientConfig() + if err != nil { + panic(err) + } + + // Create an OpenShift image/v1 client. + imageclient, err := imagev1client.NewForConfig(restconfig) + if err != nil { + panic(err) + } + + imagestreamlist, err := imageclient.ImageStreams(namespace).List(metav1.ListOptions{}) + if err != nil { + panic(err) + } + + for _, imagestream := range imagestreamlist.Items { + fmt.Println(imagestream.ObjectMeta.Name) + } } // Get the namespace defined in the kubeconfig diff --git a/commands/imagestream_test.go b/commands/imagestream_test.go new file mode 100644 index 0000000..e63070e --- /dev/null +++ b/commands/imagestream_test.go @@ -0,0 +1,23 @@ +package commands + +import ( + "github.com/spf13/cobra" + "testing" +) + +func Test_printImageStreamsFromNamespace(t *testing.T) { + type args struct { + cmd *cobra.Command + args []string + } + tests := []struct { + name string + args args + }{ + // TODO: Add test cases. + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + }) + } +} diff --git a/commands/tag.go b/commands/tag.go index a9081fa..e577767 100644 --- a/commands/tag.go +++ b/commands/tag.go @@ -4,6 +4,7 @@ import ( "fmt" "github.com/heroku/docker-registry-client/registry" + log "github.com/sirupsen/logrus" "github.com/spf13/cobra" ) @@ -18,21 +19,25 @@ var tagCmd = &cobra.Command{ Short: "Print the available tags", Long: `tbd`, Args: cobra.ExactValidArgs(1), - Run: func(cmd *cobra.Command, args []string) { - image := args[0] - url := "https://registry-1.docker.io/" - username := "" // anonymous - password := "" // anonymous - hub, err := registry.New(url, username, password) - - tags, err := hub.Tags(image) - if err != nil { - panic(err) - } - - for _, tag := range tags { - fmt.Println(tag) - } - - }, + Run: printImageStreamTags, +} + +func printImageStreamTags(cmd *cobra.Command, args []string) { + image := args[0] + url := "https://registry-1.docker.io/" + username := "" // anonymous + password := "" // anonymous + hub, err := registry.New(url, username, password) + if err != nil { + log.WithError(err).WithField("url", url).Fatal("Registry is currently unavailable.") + } + tags, err := hub.Tags(image) + + if err != nil { + log.WithError(err).WithField("url", url).Fatal("Could not list image tags.") + } + + for _, tag := range tags { + fmt.Println(tag) + } } diff --git a/commands/tag_test.go b/commands/tag_test.go new file mode 100644 index 0000000..9310f20 --- /dev/null +++ b/commands/tag_test.go @@ -0,0 +1,23 @@ +package commands + +import ( + "github.com/spf13/cobra" + "testing" +) + +func Test_printImageStreamTags(t *testing.T) { + type args struct { + cmd *cobra.Command + args []string + } + tests := []struct { + name string + args args + }{ + // TODO: Add test cases. + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + }) + } +} diff --git a/main.go b/main.go index 21ed737..ea0faf7 100644 --- a/main.go +++ b/main.go @@ -28,7 +28,7 @@ func ConfigureLogging() { FullTimestamp: true, }) - log.SetOutput(os.Stdout) + log.SetOutput(os.Stderr) //TODO: To make this configurable via flag level, err := log.ParseLevel("debug")