diff --git a/docs/configuration.md b/docs/configuration.md index 7c416e9..e1e4ab7 100644 --- a/docs/configuration.md +++ b/docs/configuration.md @@ -1099,7 +1099,7 @@ Whether to ignore invalid/self-signed certificates. Whether to open the link in the same or a new tab. ### Releases -Display a list of releases for specific repositories on Github. Draft releases and prereleases will not be shown. +Display a list of latest releases for specific repositories on Github, GitLab or Docker Hub. Example: @@ -1138,6 +1138,24 @@ repositories: - dockerhub:glanceapp/glance ``` +Official images on Docker Hub can be specified by ommiting the owner: + +```yaml +repositories: + - dockerhub:nginx + - dockerhub:node + - dockerhub:alpine +``` + +You can also specify specific tags for Docker Hub images: + +```yaml +repositories: + - dockerhub:nginx:latest + - dockerhub:nginx:stable-alpine +``` + + ##### `show-source-icon` Shows an icon of the source (GitHub/GitLab/Docker Hub) next to the repository name when set to `true`. diff --git a/internal/feed/dockerhub.go b/internal/feed/dockerhub.go index 45e67b7..e979d37 100644 --- a/internal/feed/dockerhub.go +++ b/internal/feed/dockerhub.go @@ -7,26 +7,40 @@ import ( ) type dockerHubRepositoryTagsResponse struct { - Results []struct { - Name string `json:"name"` - LastPushed string `json:"tag_last_pushed"` - } `json:"results"` + Results []dockerHubRepositoryTagResponse `json:"results"` } -const dockerHubReleaseNotesURLFormat = "https://hub.docker.com/r/%s/tags?name=%s" +type dockerHubRepositoryTagResponse struct { + Name string `json:"name"` + LastPushed string `json:"tag_last_pushed"` +} + +const dockerHubOfficialRepoTagURLFormat = "https://hub.docker.com/_/%s/tags?name=%s" +const dockerHubRepoTagURLFormat = "https://hub.docker.com/r/%s/tags?name=%s" +const dockerHubTagsURLFormat = "https://hub.docker.com/v2/namespaces/%s/repositories/%s/tags" +const dockerHubSpecificTagURLFormat = "https://hub.docker.com/v2/namespaces/%s/repositories/%s/tags/%s" func fetchLatestDockerHubRelease(request *ReleaseRequest) (*AppRelease, error) { - parts := strings.Split(request.Repository, "/") - if len(parts) != 2 { + nameParts := strings.Split(request.Repository, "/") + + if len(nameParts) > 2 { return nil, fmt.Errorf("invalid repository name: %s", request.Repository) + } else if len(nameParts) == 1 { + nameParts = []string{"library", nameParts[0]} } - httpRequest, err := http.NewRequest( - "GET", - fmt.Sprintf("https://hub.docker.com/v2/namespaces/%s/repositories/%s/tags", parts[0], parts[1]), - nil, - ) + tagParts := strings.SplitN(nameParts[1], ":", 2) + + var requestURL string + + if len(tagParts) == 2 { + requestURL = fmt.Sprintf(dockerHubSpecificTagURLFormat, nameParts[0], tagParts[0], tagParts[1]) + } else { + requestURL = fmt.Sprintf(dockerHubTagsURLFormat, nameParts[0], nameParts[1]) + } + + httpRequest, err := http.NewRequest("GET", requestURL, nil) if err != nil { return nil, err @@ -36,22 +50,52 @@ func fetchLatestDockerHubRelease(request *ReleaseRequest) (*AppRelease, error) { httpRequest.Header.Add("Authorization", "Bearer "+(*request.Token)) } - response, err := decodeJsonFromRequest[dockerHubRepositoryTagsResponse](defaultClient, httpRequest) + var tag *dockerHubRepositoryTagResponse - if err != nil { - return nil, err + if len(tagParts) == 1 { + response, err := decodeJsonFromRequest[dockerHubRepositoryTagsResponse](defaultClient, httpRequest) + + if err != nil { + return nil, err + } + + if len(response.Results) == 0 { + return nil, fmt.Errorf("no tags found for repository: %s", request.Repository) + } + + tag = &response.Results[0] + } else { + response, err := decodeJsonFromRequest[dockerHubRepositoryTagResponse](defaultClient, httpRequest) + + if err != nil { + return nil, err + } + + tag = &response } - if len(response.Results) == 0 { - return nil, fmt.Errorf("no tags found for repository: %s", request.Repository) + var repo string + var displayName string + var notesURL string + + if len(tagParts) == 1 { + repo = nameParts[1] + } else { + repo = tagParts[0] } - tag := response.Results[0] + if nameParts[0] == "library" { + displayName = repo + notesURL = fmt.Sprintf(dockerHubOfficialRepoTagURLFormat, repo, tag.Name) + } else { + displayName = nameParts[0] + "/" + repo + notesURL = fmt.Sprintf(dockerHubRepoTagURLFormat, displayName, tag.Name) + } return &AppRelease{ Source: ReleaseSourceDockerHub, - NotesUrl: fmt.Sprintf(dockerHubReleaseNotesURLFormat, request.Repository, tag.Name), - Name: request.Repository, + NotesUrl: notesURL, + Name: displayName, Version: tag.Name, TimeReleased: parseRFC3339Time(tag.LastPushed), }, nil diff --git a/internal/widget/releases.go b/internal/widget/releases.go index c824ed6..f32e581 100644 --- a/internal/widget/releases.go +++ b/internal/widget/releases.go @@ -38,7 +38,7 @@ func (widget *Releases) Initialize() error { var gitLabTokenAsString = widget.GitLabToken.String() for _, repository := range widget.Repositories { - parts := strings.Split(repository, ":") + parts := strings.SplitN(repository, ":", 2) var request *feed.ReleaseRequest if len(parts) == 1 {