From a86462206bd7c488add3cf41fc23027963a440ab Mon Sep 17 00:00:00 2001 From: Markus Pesch Date: Mon, 16 Sep 2019 21:36:27 +0200 Subject: [PATCH] Initial Commit --- .editorconfig | 12 +++ .gitattributes | 1 + .gitignore | 4 + .travis.yml | 20 +++++ Dockerfile | 9 ++ Makefile | 154 ++++++++++++++++++++++++++++++++++ README.md | 84 +++++++++++++++++++ go.mod | 11 +++ go.sum | 36 ++++++++ main.go | 122 +++++++++++++++++++++++++++ pkg/hub/errors.go | 10 +++ pkg/hub/hub.go | 179 ++++++++++++++++++++++++++++++++++++++++ pkg/hub/hub_test.go | 60 ++++++++++++++ pkg/types/login.go | 6 ++ pkg/types/repository.go | 29 +++++++ pkg/types/token.go | 6 ++ test.sh | 28 +++++++ 17 files changed, 771 insertions(+) create mode 100644 .editorconfig create mode 100644 .gitattributes create mode 100644 .gitignore create mode 100644 .travis.yml create mode 100644 Dockerfile create mode 100644 Makefile create mode 100644 README.md create mode 100644 go.mod create mode 100644 go.sum create mode 100644 main.go create mode 100644 pkg/hub/errors.go create mode 100644 pkg/hub/hub.go create mode 100644 pkg/hub/hub_test.go create mode 100644 pkg/types/login.go create mode 100644 pkg/types/repository.go create mode 100644 pkg/types/token.go create mode 100755 test.sh diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..b53e68c --- /dev/null +++ b/.editorconfig @@ -0,0 +1,12 @@ +root = true + +[*] +indent_style = space +indent_size = 2 +end_of_line = lf +charset = utf-8 +trim_trailing_whitespace = true +insert_final_newline = false + +[Makefile] +indent_style = tab \ No newline at end of file diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..dcd9d00 --- /dev/null +++ b/.gitattributes @@ -0,0 +1 @@ +Makefile eol=lf \ No newline at end of file diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..221095c --- /dev/null +++ b/.gitignore @@ -0,0 +1,4 @@ +/bin +/dhd + +**/bindata*.go \ No newline at end of file diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 0000000..5e4dab1 --- /dev/null +++ b/.travis.yml @@ -0,0 +1,20 @@ +language: go + +services: +- docker + +jobs: + include: + - stage: build + script: make container-run/all + +deploy: + - provider: script + script: make container-image/push + on: + tags: true + +notifications: + email: + on_success: change + on_failure: change \ No newline at end of file diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..061a0b3 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,9 @@ +FROM volkerraschek/build-image:1.4.0 AS build-env + +ADD ./ /workspace + +RUN make bin/linux/amd64/dhd + +FROM busybox:latest +COPY --from=build-env /workspace/bin/linux/amd64/dhd /usr/bin/dhd +ENTRYPOINT [ "/usr/bin/dhd" ] \ No newline at end of file diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..57bd53a --- /dev/null +++ b/Makefile @@ -0,0 +1,154 @@ +# VERSION/RELEASE +# If no version is specified as a parameter of make, the last git hash +# value is taken. +# VERSION?=$(shell git describe --abbrev=0)+hash.$(shell git rev-parse --short HEAD) +VERSION?=$(shell git rev-parse --short HEAD) +RELEASE?=1 + +# EXECUTABLE +# Executable binary which should be compiled for different architecures +EXECUTABLE:=dhdu + +# LINUX_EXECUTABLES AND TARGETS +LINUX_EXECUTABLES:=\ + linux/amd64/$(EXECUTABLE) \ + linux/arm/5/$(EXECUTABLE) \ + linux/arm/7/$(EXECUTABLE) + +LINUX_EXECUTABLE_TARGETS:=${LINUX_EXECUTABLES:%=bin/%} + +# UNIX_EXECUTABLES AND TARGETS +# Define all executables for different architectures and operation systems +UNIX_EXECUTABLES:=\ + ${LINUX_EXECUTABLES} + +UNIX_EXECUTABLE_TARGETS:=\ + ${LINUX_EXECUTABLE_TARGETS} + +# EXECUTABLE_TARGETS +# Include all UNIX and Windows targets. +EXECUTABLES:=\ + ${UNIX_EXECUTABLES} + +EXECUTABLE_TARGETS:=\ + ${UNIX_EXECUTABLE_TARGETS} + +# CONTAINER_RUNTIME / BUILD_IMAGE +# The CONTAINER_RUNTIME variable will be used to specified the path to a +# container runtime. This is needed to start and run a container image defined +# by the BUILD_IMAGE variable. The BUILD_IMAGE container serve as build +# environment to execute the different make steps inside. Therefore, the bulid +# environment requires all necessary dependancies to build this project. +CONTAINER_RUNTIME?=$(shell which docker) +BUILD_IMAGE:=volkerraschek/build-image:latest + +# REGISTRY_MIRROR / REGISTRY_NAMESPACE +# The REGISTRY_MIRROR variable contains the name of the registry server to push +# on or pull from container images. The REGISTRY_NAMESPACE defines the Namespace +# where the CONTAINER_RUNTIME will be search for container images or push them +# onto. The most time it's the same as REGISTRY_USER. +REGISTRY_MIRROR=docker.io +REGISTRY_NAMESPACE:=${REGISTRY_USER} +REGISTRY_USER:=volkerraschek + +# CONTAINER_IMAGE_VERSION / CONTAINER_IMAGE_NAME / CONTAINER_IMAGE +# Defines the name of the new container to be built using several variables. +CONTAINER_IMAGE_NAME=${EXECUTABLE} +CONTAINER_IMAGE_VERSION?=latest +CONTAINER_IMAGE=${REGISTRY_NAMESPACE}/${CONTAINER_IMAGE_NAME}:${CONTAINER_IMAGE_VERSION} + +README_FILE:=README.md + +# BINARIES +# ============================================================================== +PHONY:=all +all: ${EXECUTABLE_TARGETS} + +bin/linux/amd64/$(EXECUTABLE): bindata + GOARCH=amd64 GOOS=linux go build -ldflags "-X main.version=${VERSION}" -o "$@" + +bin/linux/arm/5/$(EXECUTABLE): bindata + GOARCH=amd64 GOOS=linux go build -ldflags "-X main.version=${VERSION}" -o "$@" + +bin/linux/arm/7/$(EXECUTABLE): bindata + GOARCH=amd64 GOOS=linux go build -ldflags "-X main.version=${VERSION}" -o "$@" + +bin/tmp/${EXECUTABLE}: bindata + go build -ldflags "-X main.version=${VERSION}" -o "$@" + +# BINDATA +# ============================================================================== +BINDATA_TARGETS:=\ + pkg/hub/bindata_test.go + +PHONY+=bindata +bindata: ${BINDATA_TARGETS} + +pkg/hub/bindata_test.go: + go-bindata -pkg hub_test -o ${@} README.md + +# TEST +# ============================================================================== +PHONY+=test +test: clean bin/tmp/${EXECUTABLE} + REGISTRY_USER=${REGISTRY_USER} \ + REGISTRY_PASSWORD=${REGISTRY_PASSWORD} \ + REGISTRY_NAMESPACE=${REGISTRY_NAMESPACE} \ + CONTAINER_IMAGE_NAME=${CONTAINER_IMAGE_NAME} \ + README_FILE=${README_FILE} \ + go test -v ./pkg/... + +# CLEAN +# ============================================================================== +PHONY+=clean +clean: + rm --force ${EXECUTABLE} || true + rm --force --recursive bin || true + rm --force --recursive ${BINDATA_TARGETS} || true + +# CONTAINER IMAGE STEPS +# ============================================================================== +container-image/build: + ${CONTAINER_RUNTIME} build \ + --tag ${REGISTRY_NAMESPACE}/${CONTAINER_IMAGE_NAME}:${CONTAINER_IMAGE_VERSION} \ + --tag ${REGISTRY_MIRROR}/${REGISTRY_NAMESPACE}/${CONTAINER_IMAGE_NAME}:${CONTAINER_IMAGE_VERSION} \ + . + + if [ -f $(shell which docker) ] && [ "${CONTAINER_RUNTIME}" == "$(shell which podman)" ]; then \ + podman push ${REGISTRY_MIRROR}/${CONTAINER_IMAGE} docker-daemon:${CONTAINER_IMAGE}; \ + fi + +container-image/push: container-image/build + ${CONTAINER_RUNTIME} push ${REGISTRY_MIRROR}/${REGISTRY_NAMESPACE}/${CONTAINER_IMAGE_NAME}:${CONTAINER_IMAGE_VERSION} + +# CONTAINER STEPS - BINARY +# ============================================================================== +PHONY+=container-run/all +container-run/all: + $(MAKE) container-run COMMAND=${@:container-run/%=%} + +PHONY+=${UNIX_EXECUTABLE_TARGETS:%=container-run/%} +${UNIX_EXECUTABLE_TARGETS:%=container-run/%}: + $(MAKE) container-run COMMAND=${@:container-run/%=%} + +# CONTAINER STEPS - CLEAN +# ============================================================================== +PHONY+=container-run/clean +container-run/clean: + $(MAKE) container-run COMMAND=${@:container-run/%=%} + +# GENERAL CONTAINER COMMAND +# ============================================================================== +PHONY+=container-run +container-run: + ${CONTAINER_RUNTIME} run \ + --rm \ + --volume ${PWD}:/workspace \ + ${BUILD_IMAGE} \ + make ${COMMAND} \ + VERSION=${VERSION} \ + RELEASE=${RELEASE} + +# PHONY +# ============================================================================== +.PHONY: ${PHONY} \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..a0714bb --- /dev/null +++ b/README.md @@ -0,0 +1,84 @@ +# docker hub description updater + +[![Build Status](https://travis-ci.com/volker-raschek/docker-hub-description-updater.svg?branch=master)](https://travis-ci.com/volker-raschek/docker-hub-description-updater) +[![Go Report Card](https://goreportcard.com/badge/github.com/volker-raschek/docker-hub-description-updater)](https://goreportcard.com/report/github.com/volker-raschek/docker-hub-description-updater) +[![GoDoc Reference](https://godoc.org/github.com/volker-raschek/docker-hub-description-updater?status.svg)](http://godoc.org/github.com/volker-raschek/docker-hub-description-updater) +[![Docker Pulls](https://img.shields.io/docker/pulls/volkerraschek/docker-hub-description-updater)](https://hub.docker.com/r/volkerraschek/docker-hub-description-updater) + +By specifying the login data for hub.docker.com you can update the short and +long description of a docker repository. + +## Usage + +Several options are available to update the descriptions. Either based on +Markdown files or as a normal string, which is passed as argument when calling. +The examples below describe two ways, the binary and container based way. + +### Example 1: Update full description of the repository with a Markdown file + +```bash +dhdu \ + -user= \ + -password= \ + -namespace= \ + -repository= \ + -full-description-file=./README.md +``` + +```bash +docker run \ + --rm \ + --volume $(pwd):/workspace \ + volkerraschek/dhdu \ + -user= \ + -password= \ + -namespace= \ + -repository= \ + -full-description-file=./README.md +``` + +### Example 2: Update full description of the repository over an argument + +```bash +dhdu -user= \ + -password= \ + -namespace= \ + -repository= \ + -full-description="My awesome description" +``` + +```bash +docker run \ + --rm \ + --volume $(pwd):/workspace \ + volkerraschek/dhdu \ + -user= \ + -password= \ + -namespace= \ + -repository= \ + -full-description="My awesome description" +``` + +## Compiling the source code + +There are two different ways to compile dhdu from scratch. The easier ways is +to use the pre-defined container image in the Makefile, which has included all +dependancies to compile dhdu. Alternatively, if all dependencies are met, +dhdu can also be compiled without the container image. Both variants are +briefly described. + +### Compiling the source code via container image + +To compile dhdu via container image it's necessary, that a container runtime +is installed. In the Makefile is predefined docker, but it's can be also used +podman. Execute `make container-run/dhdu` to start the compiling process. + +```bash +make container-run/dhdu +``` + +#### Compiling the source code without container image + +Make sure you have installed go >= v1.12. Execute `make dhdu` to compile +dhdu without a container-image. There should be a similar output as when +compiling dhdu via the container image. diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..29f9be1 --- /dev/null +++ b/go.mod @@ -0,0 +1,11 @@ +module github.com/volker-raschek/dhd + +go 1.13 + +require ( + github.com/Masterminds/semver v1.5.0 + github.com/d2r2/go-logger v0.0.0-20181221090742-9998a510495e + github.com/davecgh/go-spew v1.1.1 // indirect + github.com/go-flucky/flucky v0.0.0-20190714170626-0dd156f480be + github.com/stretchr/testify v1.3.0 +) diff --git a/go.sum b/go.sum new file mode 100644 index 0000000..991e099 --- /dev/null +++ b/go.sum @@ -0,0 +1,36 @@ +github.com/Masterminds/semver v1.5.0 h1:H65muMkzWKEuNDnfl9d70GUjFniHKHRbFPGBuZ3QEww= +github.com/Masterminds/semver v1.5.0/go.mod h1:MB6lktGJrhw8PrUyiEoblNEGEQ+RzHPF078ddwwvV3Y= +github.com/d2r2/go-bsbmp v0.0.0-20190515110334-3b4b3aea8375 h1:vdUOwcZdV+bBfGUUh5oPPWSzw9p+lBnNSuGgQwGpCH4= +github.com/d2r2/go-bsbmp v0.0.0-20190515110334-3b4b3aea8375/go.mod h1:3iz1WHlYJU9b4NJei+Q8G7DN3K05arcCMlOQ+qNCDjo= +github.com/d2r2/go-i2c v0.0.0-20181113114621-14f8dd4e89ce h1:Dog7PLNz1fPaXqHPOHonpERqsF57Oh4X76pM80T1GDY= +github.com/d2r2/go-i2c v0.0.0-20181113114621-14f8dd4e89ce/go.mod h1:AwxDPnsgIpy47jbGXZHA9Rv7pDkOJvQbezPuK1Y+nNk= +github.com/d2r2/go-logger v0.0.0-20181221090742-9998a510495e h1:ZG3JBA6rPRl0xxQ+nNSfO7tor8w+CNCTs05DNJQYbLM= +github.com/d2r2/go-logger v0.0.0-20181221090742-9998a510495e/go.mod h1:oA+9PUt8F1aKJ6o4YU1T120i7sgo1T6/1LWEEBy0BSs= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/go-flucky/flucky v0.0.0-20190714170626-0dd156f480be h1:jCs/SAXdXUA0aqvEVH0wyeGpJzYOZ1XC9Kpm5anSY5E= +github.com/go-flucky/flucky v0.0.0-20190714170626-0dd156f480be/go.mod h1:WNT729lCsTFhT6Vyvg6cG8aHV1t6p+DsA8l4omOuaos= +github.com/go-flucky/go-dht v0.1.1 h1:dPz9F5D7oUaTd0GUGTvT4lBH2R043h1bkmhTlpQax1Y= +github.com/go-flucky/go-dht v0.1.1/go.mod h1:Yk/cct+/u+eCS7pB/kc0tc7MrVXdFI4W15MJCj5FRUc= +github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= +github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= +github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= +github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/satori/go.uuid v1.2.0 h1:0uYX9dsZ2yD7q2RtLRtPSdGDWzjeM3TbMJP9utgA0ww= +github.com/satori/go.uuid v1.2.0/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdhQKdks0= +github.com/spf13/cobra v0.0.3 h1:ZlrZ4XsMRm04Fr5pSFxBgfND2EBVa1nLpiy1stUsX/8= +github.com/spf13/cobra v0.0.3/go.mod h1:1l0Ry5zgKvJasoi3XT1TypsSe7PqH0Sj9dhYf7v3XqQ= +github.com/spf13/pflag v1.0.3 h1:zPAT6CGy6wXeQ7NtTnaTerfKOsV6V6F8agHXFiazDkg= +github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= +github.com/stianeikeland/go-rpio v4.2.0+incompatible h1:CUOlIxdJdT+H1obJPsmg8byu7jMSECLfAN9zynm5QGo= +github.com/stianeikeland/go-rpio v4.2.0+incompatible/go.mod h1:Sh81rdJwD96E2wja2Gd7rrKM+XZ9LrwvN2w4IXrqLR8= +github.com/stretchr/objx v0.1.0 h1:4G4v2dO3VZwixGIRoQ5Lfboy6nUhCyYzaqnIAPPhYs4= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/testify v1.3.0 h1:TivCn/peBQ7UY8ooIcPgZFpTNSz0Q2U6UrFlUfqbe0Q= +github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= +gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +periph.io/x/periph v3.4.0+incompatible h1:5gzxE4ryPq52cdqSw0mErR6pyJK8cBF2qdUAcOWh0bo= +periph.io/x/periph v3.4.0+incompatible/go.mod h1:EWr+FCIU2dBWz5/wSWeiIUJTriYv9v2j2ENBmgYyy7Y= diff --git a/main.go b/main.go new file mode 100644 index 0000000..34b97c5 --- /dev/null +++ b/main.go @@ -0,0 +1,122 @@ +package main + +import ( + "flag" + "io/ioutil" + "log" + + "github.com/Masterminds/semver" + "github.com/go-flucky/flucky/pkg/logger" + "github.com/volker-raschek/dhd/pkg/hub" + "github.com/volker-raschek/dhd/pkg/types" +) + +var ( + dockerHubAPI string = "https://hub.docker.com/v2" + dockerHubUser string + dockerHubPassword string + dockerHubNamespace string + dockerHubRepository string + + shortDescription string + shortDescriptionFile string + fullDescription string + fullDescriptionFile string + + semVersion *semver.Version + version string + + flogger logger.Logger +) + +func init() { + // sVersion, err := semver.NewVersion(version) + // if err != nil { + // log.Fatalf("Can not create new semantic version from %v: %v", version, err) + // } + // semVersion = sVersion + + flogger = logger.NewDefaultLogger(logger.LogLevelDebug) +} + +func main() { + + flogger.Debug("Parse flags") + flag.StringVar(&dockerHubUser, "user", "", "Docker Hub Username") + flag.StringVar(&dockerHubPassword, "password", "", "Docker Hub Password") + flag.StringVar(&dockerHubNamespace, "namespace", "", "Docker Hub Namespace") + flag.StringVar(&dockerHubRepository, "repository", "", "Docker Hub Repository") + flag.StringVar(&shortDescription, "short-description", "", "Short description of the repository ") + flag.StringVar(&shortDescriptionFile, "short-description-file", "", "Short description of the repository. Override short-description if defined.") + flag.StringVar(&fullDescription, "full-description", "", "Full description of the repository") + flag.StringVar(&fullDescriptionFile, "full-description-file", "./README.md", "Full description of the repository. Override full-description if defined.") + flag.Parse() + + if len(dockerHubUser) <= 0 { + flogger.Fatal("No user defined over flags") + } + + if len(dockerHubPassword) <= 0 { + flogger.Fatal("No password defined over flags") + } + + if len(dockerHubNamespace) <= 0 { + flogger.Fatal("No namespace defined over flags") + } + + if len(dockerHubRepository) <= 0 { + flogger.Fatal("No repository defined over flags") + } + + hub.SetLogger(flogger) + + loginCredentials := &types.LoginCredentials{ + User: dockerHubUser, + Password: dockerHubPassword, + } + + actualShortDescription := "" + if len(shortDescription) > 0 { + actualShortDescription = shortDescription + flogger.Debug("Select short description from flag") + } else if len(shortDescriptionFile) > 0 { + f, err := ioutil.ReadFile(shortDescriptionFile) + if err != nil { + log.Fatalf("Can not read file %v", shortDescriptionFile) + } + actualShortDescription = string(f) + flogger.Debug("Select short description from file") + } + + actualFullDescription := "" + if len(fullDescription) > 0 { + actualFullDescription = fullDescription + flogger.Debug("Select full description from flag") + } else if len(fullDescriptionFile) > 0 { + f, err := ioutil.ReadFile(fullDescriptionFile) + if err != nil { + log.Fatalf("Can not read file %v", fullDescriptionFile) + } + actualFullDescription = string(f) + flogger.Debug("Select full description from file") + } + + flogger.Debug("Get Token") + token, err := hub.GetToken(loginCredentials) + if err != nil { + log.Fatalf("%v", err) + } + + repository := &types.Repository{ + Name: dockerHubRepository, + Namespcace: dockerHubNamespace, + Description: actualShortDescription, + FullDescription: actualFullDescription, + } + + flogger.Debug("Send Repository Patch") + _, err = hub.PatchRepository(repository, token) + if err != nil { + log.Fatalf("%v", err) + } +} diff --git a/pkg/hub/errors.go b/pkg/hub/errors.go new file mode 100644 index 0000000..0f00291 --- /dev/null +++ b/pkg/hub/errors.go @@ -0,0 +1,10 @@ +package hub + +import "errors" + +var ( + errorNoUserDefined = errors.New("No User defined") + errorNoPasswordDefined = errors.New("No Password defined") + errorNoNamespaceDefined = errors.New("No Namespace defined") + errorNoRepositoryDefined = errors.New("No Repository defined") +) diff --git a/pkg/hub/hub.go b/pkg/hub/hub.go new file mode 100644 index 0000000..e962a1c --- /dev/null +++ b/pkg/hub/hub.go @@ -0,0 +1,179 @@ +package hub + +import ( + "bytes" + "encoding/json" + "fmt" + "io/ioutil" + "net/http" + "net/url" + "strconv" + "strings" + + "github.com/go-flucky/flucky/pkg/logger" + "github.com/volker-raschek/dhd/pkg/types" +) + +var ( + dockerHubAPI = "https://hub.docker.com/v2" + flogger logger.Logger +) + +func init() { + flogger = logger.NewSilentLogger() +} + +func GetRepository(namespace string, name string, token *types.Token) (*types.Repository, error) { + + if len(namespace) <= 0 { + return nil, errorNoNamespaceDefined + } + + if len(name) <= 0 { + return nil, errorNoRepositoryDefined + } + + client := new(http.Client) + + url, err := url.Parse(fmt.Sprintf("%v/repositories/%v/%v", dockerHubAPI, namespace, name)) + if err != nil { + return nil, fmt.Errorf("Can not prase URL: %v", err) + } + + req, err := http.NewRequest(http.MethodGet, url.String(), nil) + if err != nil { + return nil, fmt.Errorf("Can not create request to get repository: %v", err) + } + + if token != nil { + req.Header.Add("Authorization", fmt.Sprintf("JWT %v", token.Token)) + } + + resp, err := client.Do(req) + if err != nil { + return nil, fmt.Errorf("An error has occured: %v", err) + } + defer resp.Body.Close() + + if resp.StatusCode != 200 { + return nil, fmt.Errorf("Invalid HTTP-Statuscode: Get %v but expect 200", resp.StatusCode) + } + + repository := new(types.Repository) + jsonDecoder := json.NewDecoder(resp.Body) + if err := jsonDecoder.Decode(repository); err != nil { + return nil, fmt.Errorf("Can not encode JSON from Repository struct: %v", err) + } + + return repository, nil +} + +func GetToken(loginCredentials *types.LoginCredentials) (*types.Token, error) { + + if len(loginCredentials.User) <= 0 { + return nil, errorNoUserDefined + } + + if len(loginCredentials.Password) <= 0 { + return nil, errorNoPasswordDefined + } + + client := new(http.Client) + + loginBuffer := new(bytes.Buffer) + jsonEncoder := json.NewEncoder(loginBuffer) + if err := jsonEncoder.Encode(loginCredentials); err != nil { + return nil, fmt.Errorf("Can not encode JSON from LoginCredential struct: %v", err) + } + + req, err := http.NewRequest(http.MethodPost, fmt.Sprintf("%v/users/login/", dockerHubAPI), loginBuffer) + if err != nil { + return nil, fmt.Errorf("Can not create request to get token from %v: %v", dockerHubAPI, err) + } + req.Header.Add("Content-Type", "application/json") + + resp, err := client.Do(req) + if err != nil { + return nil, fmt.Errorf("An error has occured after sending the http request to get a JWT token from %v: %v", dockerHubAPI, err) + } + defer resp.Body.Close() + + if resp.StatusCode != 200 { + return nil, fmt.Errorf("Invalid HTTP-Statuscode while getting the JWT Token: Get %v but expect 200", resp.StatusCode) + } + + token := new(types.Token) + jsonDecoder := json.NewDecoder(resp.Body) + if err := jsonDecoder.Decode(token); err != nil { + return nil, fmt.Errorf("Can not decode token: %v", err) + } + + return token, nil +} + +func PatchRepository(repository *types.Repository, token *types.Token) (*types.Repository, error) { + + if len(repository.Namespcace) <= 0 { + return nil, errorNoNamespaceDefined + } + + if len(repository.Name) <= 0 { + return nil, errorNoRepositoryDefined + } + + repositoryBuffer := new(bytes.Buffer) + jsonEncoder := json.NewEncoder(repositoryBuffer) + if err := jsonEncoder.Encode(repository); err != nil { + return nil, fmt.Errorf("Can not encode JSON from Repository struct: %v", err) + } + + client := new(http.Client) + + // patchURL, err := url.Parse(fmt.Sprintf("%v/repositories/%v/%v", dockerHubAPI, repository.Namespcace, repository.Name)) + // if err != nil { + // return nil, fmt.Errorf("Can not prase URL: %v", err) + // } + + patchURL := "https://httpbin.org/patch" + + data := url.Values{} + data.Set("full_description", repository.FullDescription) + + req, err := http.NewRequest(http.MethodPatch, patchURL, strings.NewReader(data.Encode())) + if err != nil { + return nil, fmt.Errorf("Can not create request to update readme: %v", err) + } + req.Header.Add("Accept", "*/*") + req.Header.Add("Authorization", fmt.Sprintf("JWT %v", token.Token)) + req.Header.Add("Content-Type", "application/x-www-form-urlencoded") + req.Header.Add("Content-Length", strconv.Itoa(len(data.Encode()))) + req.Header.Del("Accept-Encoding") + + flogger.Debug("Content-Length", strconv.Itoa(len(data.Encode()))) + + resp, err := client.Do(req) + if err != nil { + return nil, fmt.Errorf("An error has occured: %v", err) + } + defer resp.Body.Close() + + flogger.Debug("Get Statuscode: %v", resp.StatusCode) + + if resp.StatusCode == 200 { + bodyBytes, _ := ioutil.ReadAll(resp.Body) + //return nil, fmt.Errorf("Invalid HTTP-Statuscode: Get %v but expect 200: %v", resp.StatusCode, string(bodyBytes)) + flogger.Debug("RESP_BODY: %v", string(bodyBytes)) + } + + patchedRepository := new(types.Repository) + + if err := json.NewDecoder(resp.Body).Decode(patchedRepository); err != nil { + return nil, fmt.Errorf("Can not encode JSON from Repository struct: %v", err) + } + + return patchedRepository, nil +} + +func SetLogger(l logger.Logger) { + flogger = l +} diff --git a/pkg/hub/hub_test.go b/pkg/hub/hub_test.go new file mode 100644 index 0000000..f96cc18 --- /dev/null +++ b/pkg/hub/hub_test.go @@ -0,0 +1,60 @@ +package hub_test + +import ( + "os" + "testing" + + flogger "github.com/go-flucky/flucky/pkg/logger" + "github.com/stretchr/testify/require" + "github.com/volker-raschek/dhd/pkg/hub" + "github.com/volker-raschek/dhd/pkg/types" +) + +func TestPatchRepository(t *testing.T) { + + hub.SetLogger(flogger.NewDefaultLogger(flogger.LogLevelDebug)) + + dockerHubUser := os.Getenv("REGISTRY_USER") + if len(dockerHubUser) <= 0 { + t.Fatalf("Environment variable REGISTRY_USER is empty") + } + + dockerHubPassword := os.Getenv("REGISTRY_PASSWORD") + if len(dockerHubPassword) <= 0 { + t.Fatalf("Environment variable REGISTRY_PASSWORD is empty") + } + + dockerHubNamespace := os.Getenv("REGISTRY_NAMESPACE") + if len(dockerHubNamespace) <= 0 { + t.Fatalf("Environment variable REGISTRY_NAMESPACE is empty") + } + + dockerHubRepository := os.Getenv("CONTAINER_IMAGE_NAME") + if len(dockerHubRepository) <= 0 { + t.Fatalf("Environment variable CONTAINER_IMAGE_NAME is empty") + } + + loginCredentials := &types.LoginCredentials{ + User: dockerHubUser, + Password: dockerHubPassword, + } + + require := require.New(t) + token, err := hub.GetToken(loginCredentials) + require.NoError(err) + + readme, err := Asset("README.md") + require.NoError(err) + + currentRepository, err := hub.GetRepository(dockerHubNamespace, dockerHubRepository, token) + require.NoError(err) + + expectedRepository := *currentRepository + expectedRepository.FullDescription = string(readme) + + actualRepository, err := hub.PatchRepository(&expectedRepository, token) + require.NoError(err) + + require.NotEqual(currentRepository, actualRepository, "The repository properties have remained the same even though an update was performed") + require.Equal(&expectedRepository, actualRepository, "The update was successfully") +} diff --git a/pkg/types/login.go b/pkg/types/login.go new file mode 100644 index 0000000..96bf31b --- /dev/null +++ b/pkg/types/login.go @@ -0,0 +1,6 @@ +package types + +type LoginCredentials struct { + User string `json:"username"` + Password string `json:"password"` +} diff --git a/pkg/types/repository.go b/pkg/types/repository.go new file mode 100644 index 0000000..ef13a43 --- /dev/null +++ b/pkg/types/repository.go @@ -0,0 +1,29 @@ +package types + +import "time" + +type Repository struct { + User string `json:"user"` + Name string `json:"name"` + Namespcace string `json:"namespace"` + Type string `json:"repository_type"` + Status int `json:"status"` + Description string `json:"description"` + Private bool `json:"is_private"` + Automated bool `json:"is_automated"` + Edit bool `json:"can_edit"` + StarCount int `json:"start_count"` + PullCount int `json:"pull_count"` + LastUpdated time.Time `json:"last_updated"` + IsMigrated bool `json:"is_migrated"` + HasStarred bool `json:"has_starred"` + FullDescription string `json:"full_description"` + Affiliation string `json:"affilition"` + Permissions *Permissions `json:"permissions"` +} + +type Permissions struct { + Read bool `json:"read"` + Write bool `json:"write"` + Admin bool `json:"admin"` +} diff --git a/pkg/types/token.go b/pkg/types/token.go new file mode 100644 index 0000000..f1a4415 --- /dev/null +++ b/pkg/types/token.go @@ -0,0 +1,6 @@ +package types + +type Token struct { + Token string `json:"token"` +} + diff --git a/test.sh b/test.sh new file mode 100755 index 0000000..7d7031c --- /dev/null +++ b/test.sh @@ -0,0 +1,28 @@ +#!/bin/sh -l +set -euo pipefail +IFS=$'\n\t' + +# Set the default path to README.md +README_FILEPATH=${README_FILEPATH:="./README.md"} + +DOCKERHUB_PASSWORD=${DOCKER_PASSWORD} +DOCKERHUB_USERNAME=volkerraschek +DOCKERHUB_REPOSITORY=volkerraschek/dhd + +# Acquire a token for the Docker Hub API +echo "Acquiring token" +LOGIN_PAYLOAD="{\"username\": \"${DOCKERHUB_USERNAME}\", \"password\": \"${DOCKERHUB_PASSWORD}\"}" +TOKEN=$(curl -H "Content-Type: application/json" -X POST -d ${LOGIN_PAYLOAD} https://hub.docker.com/v2/users/login/ | jq -r .token) + +# Send a PATCH request to update the description of the repository +echo "Sending PATCH request" +# REPO_URL="https://hub.docker.com/v2/repositories/${DOCKERHUB_REPOSITORY}/" +REPO_URL="https://httpbin.org/patch" +RESPONSE_CODE=$(curl --write-out %{response_code} -H "Authorization: JWT ${TOKEN}" -X PATCH --data-urlencode full_description@${README_FILEPATH} ${REPO_URL}) +echo "Received response code: $RESPONSE_CODE" + +if [ $RESPONSE_CODE -eq 200 ]; then + exit 0 +else + exit 1 +fi \ No newline at end of file