From 05a008e7203791548f14f938bd4bf596459c4a69 Mon Sep 17 00:00:00 2001 From: Markus Pesch Date: Sun, 26 Jan 2020 15:29:11 +0100 Subject: [PATCH] refac(hub): pack functions behind a struct --- .travis.yml | 11 ++-- Makefile | 10 ++-- README.md | 2 + cmd/root.go | 121 +++++++++++++++++++------------------- pkg/hub/errors.go | 13 +++-- pkg/hub/hub.go | 138 ++++++++++++++++++++++---------------------- pkg/hub/hub_test.go | 15 ++--- 7 files changed, 158 insertions(+), 152 deletions(-) diff --git a/.travis.yml b/.travis.yml index eb2bb7f..f5f9fbf 100644 --- a/.travis.yml +++ b/.travis.yml @@ -14,14 +14,15 @@ jobs: # - name: "Test golang binaries inside a container image" # stage: test # script: make container-run/test + # after_script: bash <(curl -s https://codecov.io/bash) - name: "Deploy container-image tagged as latest" stage: deploy script: make container-image/push - # - name: "Deploy container-image tagged with a git tag" - # stage: deploy - # script: make container-image/push VERSION=${TRAVIS_TAG} CONTAINER_IMAGE_VERSION=${TRAVIS_TAG} - # on: - # tags: true + - name: "Deploy container-image tagged with a git tag" + stage: deploy + script: make container-image/push VERSION=${TRAVIS_TAG} CONTAINER_IMAGE_VERSION=${TRAVIS_TAG} + on: + tags: true notifications: email: diff --git a/Makefile b/Makefile index 269e1ca..6df3cb0 100644 --- a/Makefile +++ b/Makefile @@ -2,7 +2,7 @@ # 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 describe --abbrev=0)+hash.$(shell git rev-parse --short HEAD) +VERSION?=0.0.0+hash.$(shell git rev-parse --short HEAD) # GO SETTINGS # Defines a proxy server to download dependent libraries. If no proxy is @@ -88,14 +88,14 @@ bin/tmp/${EXECUTABLE}: bindata # BINDATA # ============================================================================== -BINDATA_TARGETS:=\ - pkg/hub/bindata_test.go +BINDATA_TARGETS := \ + pkg/hub/bindata.go PHONY+=bindata bindata: ${BINDATA_TARGETS} -pkg/hub/bindata_test.go: - go-bindata -pkg hub_test -o ${@} README.md +pkg/hub/bindata.go: + go-bindata -pkg hub -o ${@} README.md # TEST # ============================================================================== diff --git a/README.md b/README.md index ba60060..eaed218 100644 --- a/README.md +++ b/README.md @@ -10,6 +10,8 @@ can update the short and long description of a docker repository. ## Usage +Test + 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. diff --git a/cmd/root.go b/cmd/root.go index 6fc4f83..68a1ff8 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -1,6 +1,7 @@ package cmd import ( + "fmt" "io/ioutil" "log" "os" @@ -15,74 +16,72 @@ var ( dockerHubUser string dockerHubPassword 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 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(&dockerHubPassword, "password", "p", "", "Docker Hub Password") rootCmd.Flags().StringVarP(&dockerHubRepository, "repository", "r", "", "Docker Hub Repository") 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() } + +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 +} diff --git a/pkg/hub/errors.go b/pkg/hub/errors.go index 0f00291..755d197 100644 --- a/pkg/hub/errors.go +++ b/pkg/hub/errors.go @@ -3,8 +3,13 @@ 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") + errorFailedToCreateRequest = errors.New("Failed to create http request") + errorFailedToParseJSON = errors.New("Failed to parse json") + errorFailedToParseURL = errors.New("Failed to parse url") + 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") ) diff --git a/pkg/hub/hub.go b/pkg/hub/hub.go index e062838..66bc7e1 100644 --- a/pkg/hub/hub.go +++ b/pkg/hub/hub.go @@ -4,11 +4,9 @@ import ( "bytes" "encoding/json" "fmt" - "io/ioutil" "net/http" "net/url" - "strconv" - "strings" + "time" "github.com/volker-raschek/docker-hub-description-updater/pkg/types" ) @@ -17,7 +15,14 @@ var ( 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 { return nil, errorNoNamespaceDefined @@ -27,85 +32,80 @@ func GetRepository(namespace string, name string, token *types.Token) (*types.Re return nil, errorNoRepositoryDefined } - client := new(http.Client) - - url, err := url.Parse(fmt.Sprintf("%v/repositories/%v/%v", dockerHubAPI, namespace, name)) + rawURL := fmt.Sprintf("%v/repositories/%v/%v", dockerHubAPI, namespace, name) + url, err := url.Parse(rawURL) 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) 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 { - req.Header.Add("Authorization", fmt.Sprintf("JWT %v", token.Token)) + if h.token == nil { + 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 { - return nil, fmt.Errorf("An error has occured: %v", err) + return nil, fmt.Errorf("%v: %v", errorFailedToSendRequest, err) } defer resp.Body.Close() - if resp.StatusCode != 200 { - return nil, fmt.Errorf("Invalid HTTP-Statuscode: Get %v but expect 200", resp.StatusCode) + if resp.StatusCode != http.StatusOK { + return nil, fmt.Errorf("%v: expect %v, received %v", errorUnexpectedHTTPStatuscode, http.StatusOK, 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 nil, fmt.Errorf("%v: %v", errorFailedToParseJSON, 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) - +func (h *Hub) getToken() (*types.Token, error) { 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) + if err := jsonEncoder.Encode(h.credentials); err != nil { + 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 { - 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") - resp, err := client.Do(req) + resp, err := h.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) + return nil, fmt.Errorf("%v: %v", errorFailedToCreateRequest, 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) + if resp.StatusCode != http.StatusOK { + return nil, fmt.Errorf("%v: expect %v, received %v", errorUnexpectedHTTPStatuscode, http.StatusOK, 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 nil, fmt.Errorf("%v: %v", errorFailedToParseJSON, err) } 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 { return nil, errorNoNamespaceDefined @@ -115,53 +115,55 @@ func PatchRepository(repository *types.Repository, token *types.Token) (*types.R 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) - // } + if h.token == nil { + token, err := h.getToken() + if err != nil { + 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) + 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 { - 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: - // - 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) + resp, err := h.client.Do(req) 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() - 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)) + if resp.StatusCode != http.StatusOK { + return nil, fmt.Errorf("%v: expect %v, received %v", errorUnexpectedHTTPStatuscode, http.StatusOK, resp.StatusCode) } patchedRepository := new(types.Repository) - jsonDecoder := json.NewDecoder(resp.Body) - if err := jsonDecoder.Decode(patchedRepository); err != nil { - return nil, fmt.Errorf("Can not encode JSON from Repository struct: %v", err) + if err := jsonDecoder.Decode(h.token); err != nil { + return nil, fmt.Errorf("%v: %v", errorFailedToParseJSON, err) } return patchedRepository, nil } + +func New(credentials *types.LoginCredentials) *Hub { + return &Hub{ + client: &http.Client{ + Timeout: time.Second * 15, + }, + credentials: credentials, + } +} diff --git a/pkg/hub/hub_test.go b/pkg/hub/hub_test.go index a34a2b4..4be7536 100644 --- a/pkg/hub/hub_test.go +++ b/pkg/hub/hub_test.go @@ -1,11 +1,10 @@ -package hub_test +package hub import ( "os" "testing" "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" ) @@ -36,22 +35,20 @@ func TestPatchRepository(t *testing.T) { Password: dockerHubPassword, } + h := New(loginCredentials) + 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) + currentRepository, err := h.GetRepository(dockerHubNamespace, dockerHubRepository) require.NoError(err) expectedRepository := *currentRepository expectedRepository.FullDescription = string(readme) - actualRepository, err := hub.PatchRepository(&expectedRepository, token) + actualRepository, err := h.PatchRepository(&expectedRepository) require.NoError(err) - - 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") + require.EqualValues(&expectedRepository.FullDescription, actualRepository.FullDescription, "Full description not equal") }