refac(hub): pack functions behind a struct

This commit is contained in:
Markus Pesch 2020-01-26 15:29:11 +01:00
parent b6e52111e5
commit 05a008e720
Signed by: volker.raschek
GPG Key ID: 852BCC170D81A982
7 changed files with 158 additions and 152 deletions

View File

@ -14,14 +14,15 @@ jobs:
# - name: "Test golang binaries inside a container image" # - name: "Test golang binaries inside a container image"
# stage: test # stage: test
# script: make container-run/test # script: make container-run/test
# after_script: bash <(curl -s https://codecov.io/bash)
- name: "Deploy container-image tagged as latest" - name: "Deploy container-image tagged as latest"
stage: deploy stage: deploy
script: make container-image/push script: make container-image/push
# - name: "Deploy container-image tagged with a git tag" - name: "Deploy container-image tagged with a git tag"
# stage: deploy stage: deploy
# script: make container-image/push VERSION=${TRAVIS_TAG} CONTAINER_IMAGE_VERSION=${TRAVIS_TAG} script: make container-image/push VERSION=${TRAVIS_TAG} CONTAINER_IMAGE_VERSION=${TRAVIS_TAG}
# on: on:
# tags: true tags: true
notifications: notifications:
email: email:

View File

@ -2,7 +2,7 @@
# If no version is specified as a parameter of make, the last git hash # If no version is specified as a parameter of make, the last git hash
# value is taken. # value is taken.
# VERSION?=$(shell git describe --abbrev=0)+hash.$(shell git rev-parse --short HEAD) # VERSION?=$(shell git describe --abbrev=0)+hash.$(shell git rev-parse --short HEAD)
VERSION?=$(shell git describe --abbrev=0)+hash.$(shell git rev-parse --short HEAD) VERSION?=0.0.0+hash.$(shell git rev-parse --short HEAD)
# GO SETTINGS # GO SETTINGS
# Defines a proxy server to download dependent libraries. If no proxy is # Defines a proxy server to download dependent libraries. If no proxy is
@ -88,14 +88,14 @@ bin/tmp/${EXECUTABLE}: bindata
# BINDATA # BINDATA
# ============================================================================== # ==============================================================================
BINDATA_TARGETS:=\ BINDATA_TARGETS := \
pkg/hub/bindata_test.go pkg/hub/bindata.go
PHONY+=bindata PHONY+=bindata
bindata: ${BINDATA_TARGETS} bindata: ${BINDATA_TARGETS}
pkg/hub/bindata_test.go: pkg/hub/bindata.go:
go-bindata -pkg hub_test -o ${@} README.md go-bindata -pkg hub -o ${@} README.md
# TEST # TEST
# ============================================================================== # ==============================================================================

View File

@ -10,6 +10,8 @@ can update the short and long description of a docker repository.
## Usage ## Usage
Test
The examples below describe two ways to update the full description of the The examples below describe two ways to update the full description of the
docker hub repository. First by the binary and second by a container based way. docker hub repository. First by the binary and second by a container based way.

View File

@ -1,6 +1,7 @@
package cmd package cmd
import ( import (
"fmt"
"io/ioutil" "io/ioutil"
"log" "log"
"os" "os"
@ -15,74 +16,72 @@ var (
dockerHubUser string dockerHubUser string
dockerHubPassword string dockerHubPassword string
dockerHubRepository string dockerHubRepository string
file string
) )
var rootCmd = &cobra.Command{
Use: "dhdu",
Short: "docker hub description updater (dhdu)",
Run: func(cmd *cobra.Command, args []string) {
if len(dockerHubUser) <= 0 {
log.Fatalf("No user defined over flags")
}
if len(dockerHubPassword) <= 0 {
log.Fatalf("No password defined over flags")
}
if len(dockerHubNamespace) <= 0 {
log.Printf("No namespace defined over flags: Use docker username %v instead", dockerHubUser)
dockerHubNamespace = dockerHubUser
}
if len(dockerHubRepository) <= 0 {
log.Fatalf("No repository defined over flags")
}
if _, err := os.Stat(file); os.IsNotExist(err) && len(file) <= 0 {
log.Fatalf("Can not find file: %v", file)
}
f, err := ioutil.ReadFile(file)
if err != nil {
log.Fatalf("Can not read file %v: %v", file, err)
}
fullDescription := string(f)
loginCredentials := &types.LoginCredentials{
User: dockerHubUser,
Password: dockerHubPassword,
}
token, err := hub.GetToken(loginCredentials)
if err != nil {
log.Fatalf("%v", err)
}
repository := &types.Repository{
Name: dockerHubRepository,
Namespcace: dockerHubNamespace,
FullDescription: fullDescription,
}
_, err = hub.PatchRepository(repository, token)
if err != nil {
log.Fatalf("%v", err)
}
},
}
// Execute a // Execute a
func Execute(version string) { func Execute(version string) {
rootCmd.Version = version rootCmd := &cobra.Command{
Use: "dhdu",
Short: "docker hub description updater (dhdu)",
RunE: runE,
Args: cobra.ExactArgs(1),
Version: version,
}
rootCmd.Flags().StringVarP(&dockerHubNamespace, "namespace", "n", "", "Docker Hub Namespace (default \"username\")") rootCmd.Flags().StringVarP(&dockerHubNamespace, "namespace", "n", "", "Docker Hub Namespace (default \"username\")")
rootCmd.Flags().StringVarP(&dockerHubPassword, "password", "p", "", "Docker Hub Password") rootCmd.Flags().StringVarP(&dockerHubPassword, "password", "p", "", "Docker Hub Password")
rootCmd.Flags().StringVarP(&dockerHubRepository, "repository", "r", "", "Docker Hub Repository") rootCmd.Flags().StringVarP(&dockerHubRepository, "repository", "r", "", "Docker Hub Repository")
rootCmd.Flags().StringVarP(&dockerHubUser, "username", "u", "", "Docker Hub Username") rootCmd.Flags().StringVarP(&dockerHubUser, "username", "u", "", "Docker Hub Username")
rootCmd.Flags().StringVarP(&file, "file", "f", "./README.md", "File which should be uploaded as docker hub description")
rootCmd.Execute() rootCmd.Execute()
} }
func runE(cmd *cobra.Command, args []string) error {
file := args[0]
if len(dockerHubUser) <= 0 {
return fmt.Errorf("No user defined over flags")
}
if len(dockerHubPassword) <= 0 {
return fmt.Errorf("No password defined over flags")
}
if len(dockerHubNamespace) <= 0 {
log.Printf("No namespace defined over flags: Use docker username %v instead", dockerHubUser)
dockerHubNamespace = dockerHubUser
}
if len(dockerHubRepository) <= 0 {
return fmt.Errorf("No repository defined over flags")
}
if _, err := os.Stat(file); os.IsNotExist(err) && len(file) <= 0 {
return fmt.Errorf("Can not find file: %v", file)
}
f, err := ioutil.ReadFile(file)
if err != nil {
return fmt.Errorf("Can not read file %v: %v", file, err)
}
fullDescription := string(f)
loginCredentials := &types.LoginCredentials{
User: dockerHubUser,
Password: dockerHubPassword,
}
h := hub.New(loginCredentials)
repository := &types.Repository{
Name: dockerHubRepository,
Namespcace: dockerHubNamespace,
FullDescription: fullDescription,
}
_, err = h.PatchRepository(repository)
if err != nil {
return err
}
return nil
}

View File

@ -3,8 +3,13 @@ package hub
import "errors" import "errors"
var ( var (
errorNoUserDefined = errors.New("No User defined") errorFailedToCreateRequest = errors.New("Failed to create http request")
errorNoPasswordDefined = errors.New("No Password defined") errorFailedToParseJSON = errors.New("Failed to parse json")
errorNoNamespaceDefined = errors.New("No Namespace defined") errorFailedToParseURL = errors.New("Failed to parse url")
errorNoRepositoryDefined = errors.New("No Repository defined") errorFailedToSendRequest = errors.New("Failed to send http request")
errorNoUserDefined = errors.New("No User defined")
errorNoPasswordDefined = errors.New("No Password defined")
errorNoNamespaceDefined = errors.New("No Namespace defined")
errorNoRepositoryDefined = errors.New("No Repository defined")
errorUnexpectedHTTPStatuscode = errors.New("Unexpected HTTP-Statuscode")
) )

View File

@ -4,11 +4,9 @@ import (
"bytes" "bytes"
"encoding/json" "encoding/json"
"fmt" "fmt"
"io/ioutil"
"net/http" "net/http"
"net/url" "net/url"
"strconv" "time"
"strings"
"github.com/volker-raschek/docker-hub-description-updater/pkg/types" "github.com/volker-raschek/docker-hub-description-updater/pkg/types"
) )
@ -17,7 +15,14 @@ var (
dockerHubAPI = "https://hub.docker.com/v2" dockerHubAPI = "https://hub.docker.com/v2"
) )
func GetRepository(namespace string, name string, token *types.Token) (*types.Repository, error) { type Hub struct {
client *http.Client
credentials *types.LoginCredentials
token *types.Token
}
// GetRepository returns a repository struct
func (h *Hub) GetRepository(namespace string, name string) (*types.Repository, error) {
if len(namespace) <= 0 { if len(namespace) <= 0 {
return nil, errorNoNamespaceDefined return nil, errorNoNamespaceDefined
@ -27,85 +32,80 @@ func GetRepository(namespace string, name string, token *types.Token) (*types.Re
return nil, errorNoRepositoryDefined return nil, errorNoRepositoryDefined
} }
client := new(http.Client) rawURL := fmt.Sprintf("%v/repositories/%v/%v", dockerHubAPI, namespace, name)
url, err := url.Parse(rawURL)
url, err := url.Parse(fmt.Sprintf("%v/repositories/%v/%v", dockerHubAPI, namespace, name))
if err != nil { if err != nil {
return nil, fmt.Errorf("Can not prase URL: %v", err) return nil, fmt.Errorf("%v: %v", errorFailedToParseURL, err)
} }
req, err := http.NewRequest(http.MethodGet, url.String(), nil) req, err := http.NewRequest(http.MethodGet, url.String(), nil)
if err != nil { if err != nil {
return nil, fmt.Errorf("Can not create request to get repository: %v", err) return nil, fmt.Errorf("%v: %v", errorFailedToCreateRequest, err)
} }
if token != nil { if h.token == nil {
req.Header.Add("Authorization", fmt.Sprintf("JWT %v", token.Token)) token, err := h.getToken()
if err != nil {
return nil, err
}
h.token = token
} }
req.Header.Add("Authorization", fmt.Sprintf("JWT %v", h.token.Token))
resp, err := client.Do(req) resp, err := h.client.Do(req)
if err != nil { if err != nil {
return nil, fmt.Errorf("An error has occured: %v", err) return nil, fmt.Errorf("%v: %v", errorFailedToSendRequest, err)
} }
defer resp.Body.Close() defer resp.Body.Close()
if resp.StatusCode != 200 { if resp.StatusCode != http.StatusOK {
return nil, fmt.Errorf("Invalid HTTP-Statuscode: Get %v but expect 200", resp.StatusCode) return nil, fmt.Errorf("%v: expect %v, received %v", errorUnexpectedHTTPStatuscode, http.StatusOK, resp.StatusCode)
} }
repository := new(types.Repository) repository := new(types.Repository)
jsonDecoder := json.NewDecoder(resp.Body) jsonDecoder := json.NewDecoder(resp.Body)
if err := jsonDecoder.Decode(repository); err != nil { if err := jsonDecoder.Decode(repository); err != nil {
return nil, fmt.Errorf("Can not encode JSON from Repository struct: %v", err) return nil, fmt.Errorf("%v: %v", errorFailedToParseJSON, err)
} }
return repository, nil return repository, nil
} }
func GetToken(loginCredentials *types.LoginCredentials) (*types.Token, error) { func (h *Hub) getToken() (*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) loginBuffer := new(bytes.Buffer)
jsonEncoder := json.NewEncoder(loginBuffer) jsonEncoder := json.NewEncoder(loginBuffer)
if err := jsonEncoder.Encode(loginCredentials); err != nil { if err := jsonEncoder.Encode(h.credentials); err != nil {
return nil, fmt.Errorf("Can not encode JSON from LoginCredential struct: %v", err) return nil, fmt.Errorf("%v: %v", errorFailedToParseJSON, err)
} }
req, err := http.NewRequest(http.MethodPost, fmt.Sprintf("%v/users/login/", dockerHubAPI), loginBuffer) rawURL := fmt.Sprintf("%v/users/login/", dockerHubAPI)
req, err := http.NewRequest(http.MethodPost, rawURL, loginBuffer)
if err != nil { if err != nil {
return nil, fmt.Errorf("Can not create request to get token from %v: %v", dockerHubAPI, err) return nil, fmt.Errorf("%v: %v", errorFailedToCreateRequest, err)
} }
req.Header.Add("Content-Type", "application/json") req.Header.Add("Content-Type", "application/json")
resp, err := client.Do(req) resp, err := h.client.Do(req)
if err != nil { 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) return nil, fmt.Errorf("%v: %v", errorFailedToCreateRequest, err)
} }
defer resp.Body.Close() defer resp.Body.Close()
if resp.StatusCode != 200 { if resp.StatusCode != http.StatusOK {
return nil, fmt.Errorf("Invalid HTTP-Statuscode while getting the JWT Token: Get %v but expect 200", resp.StatusCode) return nil, fmt.Errorf("%v: expect %v, received %v", errorUnexpectedHTTPStatuscode, http.StatusOK, resp.StatusCode)
} }
token := new(types.Token) token := new(types.Token)
jsonDecoder := json.NewDecoder(resp.Body) jsonDecoder := json.NewDecoder(resp.Body)
if err := jsonDecoder.Decode(token); err != nil { if err := jsonDecoder.Decode(token); err != nil {
return nil, fmt.Errorf("Can not decode token: %v", err) return nil, fmt.Errorf("%v: %v", errorFailedToParseJSON, err)
} }
return token, nil return token, nil
} }
func PatchRepository(repository *types.Repository, token *types.Token) (*types.Repository, error) { // PatchRepository updates the docker hub repository
func (h *Hub) PatchRepository(repository *types.Repository) (*types.Repository, error) {
if len(repository.Namespcace) <= 0 { if len(repository.Namespcace) <= 0 {
return nil, errorNoNamespaceDefined return nil, errorNoNamespaceDefined
@ -115,53 +115,55 @@ func PatchRepository(repository *types.Repository, token *types.Token) (*types.R
return nil, errorNoRepositoryDefined return nil, errorNoRepositoryDefined
} }
// repositoryBuffer := new(bytes.Buffer) if h.token == nil {
// jsonEncoder := json.NewEncoder(repositoryBuffer) token, err := h.getToken()
// if err := jsonEncoder.Encode(repository); err != nil { if err != nil {
// return nil, fmt.Errorf("Can not encode JSON from Repository struct: %v", err) return nil, err
// } }
h.token = token
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)
} }
data := url.Values{} rawURL := fmt.Sprintf("%v/repositories/%v/%v", dockerHubAPI, repository.Namespcace, repository.Name)
patchURL, err := url.Parse(rawURL)
if err != nil {
return nil, fmt.Errorf("%v: %v", errorFailedToParseURL, err)
}
data := &url.Values{}
data.Set("full_description", repository.FullDescription) data.Set("full_description", repository.FullDescription)
patchURL.RawQuery = data.Encode()
req, err := http.NewRequest(http.MethodPatch, patchURL.String(), strings.NewReader(data.Encode())) req, err := http.NewRequest(http.MethodPatch, patchURL.String(), nil)
if err != nil { if err != nil {
return nil, fmt.Errorf("Can not create http request to update file: %v", err) return nil, fmt.Errorf("%v: %v", errorFailedToCreateRequest, err)
} }
req.Header.Set("Authorization", fmt.Sprintf("JWT %v", h.token.Token))
// Disable gzip compression: resp, err := h.client.Do(req)
// - https://stackoverflow.com/questions/33469723/go-how-to-control-gzip-compression-when-sending-http-request
req.Header.Set("Accept-Encoding", "identity")
req.Header.Set("Authorization", fmt.Sprintf("JWT %v", token.Token))
req.Header.Set("Content-Length", strconv.Itoa(len(data.Encode())))
//req.Header.Add("Content-Type", "application/json")
req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
resp, err := client.Do(req)
if err != nil { if err != nil {
return nil, fmt.Errorf("An error has occured: %v", err) return nil, fmt.Errorf("%v: %v", errorFailedToCreateRequest, err)
} }
defer resp.Body.Close() defer resp.Body.Close()
if resp.StatusCode != 200 { if resp.StatusCode != http.StatusOK {
bodyBytes, _ := ioutil.ReadAll(resp.Body) return nil, fmt.Errorf("%v: expect %v, received %v", errorUnexpectedHTTPStatuscode, http.StatusOK, resp.StatusCode)
return nil, fmt.Errorf("Invalid HTTP-Statuscode: Get %v but expect 200: %v", resp.StatusCode, string(bodyBytes))
} }
patchedRepository := new(types.Repository) patchedRepository := new(types.Repository)
jsonDecoder := json.NewDecoder(resp.Body) jsonDecoder := json.NewDecoder(resp.Body)
if err := jsonDecoder.Decode(patchedRepository); err != nil { if err := jsonDecoder.Decode(h.token); err != nil {
return nil, fmt.Errorf("Can not encode JSON from Repository struct: %v", err) return nil, fmt.Errorf("%v: %v", errorFailedToParseJSON, err)
} }
return patchedRepository, nil return patchedRepository, nil
} }
func New(credentials *types.LoginCredentials) *Hub {
return &Hub{
client: &http.Client{
Timeout: time.Second * 15,
},
credentials: credentials,
}
}

View File

@ -1,11 +1,10 @@
package hub_test package hub
import ( import (
"os" "os"
"testing" "testing"
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
"github.com/volker-raschek/docker-hub-description-updater/pkg/hub"
"github.com/volker-raschek/docker-hub-description-updater/pkg/types" "github.com/volker-raschek/docker-hub-description-updater/pkg/types"
) )
@ -36,22 +35,20 @@ func TestPatchRepository(t *testing.T) {
Password: dockerHubPassword, Password: dockerHubPassword,
} }
h := New(loginCredentials)
require := require.New(t) require := require.New(t)
token, err := hub.GetToken(loginCredentials)
require.NoError(err)
readme, err := Asset("README.md") readme, err := Asset("README.md")
require.NoError(err) require.NoError(err)
currentRepository, err := hub.GetRepository(dockerHubNamespace, dockerHubRepository, token) currentRepository, err := h.GetRepository(dockerHubNamespace, dockerHubRepository)
require.NoError(err) require.NoError(err)
expectedRepository := *currentRepository expectedRepository := *currentRepository
expectedRepository.FullDescription = string(readme) expectedRepository.FullDescription = string(readme)
actualRepository, err := hub.PatchRepository(&expectedRepository, token) actualRepository, err := h.PatchRepository(&expectedRepository)
require.NoError(err) require.NoError(err)
require.EqualValues(&expectedRepository.FullDescription, actualRepository.FullDescription, "Full description not equal")
require.NotEqual(currentRepository, actualRepository, "The repository properties have remained the same even though an update was performed")
require.EqualValues(&expectedRepository, actualRepository, "The update was successfully")
} }