commit a86462206bd7c488add3cf41fc23027963a440ab Author: Markus Pesch Date: Mon Sep 16 21:36:27 2019 +0200 Initial Commit 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