Skip to content

Commit

Permalink
MM-31206 Fetch the image digest and use that instead of the tag name (m…
Browse files Browse the repository at this point in the history
…attermost#64)

* Include tag in the Installation metadata
  • Loading branch information
Ian Whitlock authored Jan 4, 2021
1 parent 27255eb commit c2ae42d
Show file tree
Hide file tree
Showing 8 changed files with 72 additions and 4 deletions.
1 change: 1 addition & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ go 1.13

require (
github.com/blang/semver/v4 v4.0.0
github.com/docker/distribution v2.7.1+incompatible
github.com/google/gofuzz v1.1.0 // indirect
github.com/gorilla/websocket v1.4.2 // indirect
github.com/heroku/docker-registry-client v0.0.0-20190909225348-afc9e1acc3d5
Expand Down
13 changes: 11 additions & 2 deletions server/command_create.go
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,8 @@ func parseCreateArgs(args []string, install *Installation) error {
return err
}

install.Tag = install.Version

install.Affinity, err = createFlagSet.GetString("affinity")
if err != nil {
return err
Expand Down Expand Up @@ -153,6 +155,11 @@ func (p *Plugin) runCreateCommand(args []string, extra *model.CommandArgs) (*mod
}

if install.Version != "" {
err = validVersionOption(install.Version)
if err != nil {
return nil, true, err
}

var exists bool
repository := "mattermost/mattermost-enterprise-edition"
exists, err = p.dockerClient.ValidTag(install.Version, repository)
Expand All @@ -163,10 +170,12 @@ func (p *Plugin) runCreateCommand(args []string, extra *model.CommandArgs) (*mod
return nil, true, errors.Errorf("%s is not a valid docker tag for repository %s", install.Version, repository)
}

err = validVersionOption(install.Version)
var digest string
digest, err = p.dockerClient.GetDigestForTag(install.Version, repository)
if err != nil {
return nil, true, err
return nil, false, errors.Wrapf(err, "failed to find a manifest digest for version %s", install.Version)
}
install.Version = digest
}

req := &cloud.CreateInstallationRequest{
Expand Down
2 changes: 1 addition & 1 deletion server/command_list_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ func TestGetUpdatedInstallsForUser(t *testing.T) {
api.On("KVCompareAndSet", mock.AnythingOfType("string"), mock.Anything, mock.Anything).Return(true, nil)
api.On("GetDirectChannel", mock.AnythingOfType("string"), mock.AnythingOfType("string")).Return(&model.Channel{}, nil)
api.On("CreatePost", &model.Post{
Message: "Cloud installation installation-three has been manually deleted and is now removed from the cloud plugin.\n\n``` json\n{\n\t\"Name\": \"installation-three\",\n\t\"ID\": \"id3\",\n\t\"OwnerID\": \"\",\n\t\"GroupID\": null,\n\t\"Version\": \"\",\n\t\"Image\": \"\",\n\t\"DNS\": \"\",\n\t\"Database\": \"\",\n\t\"Filestore\": \"\",\n\t\"License\": \"hidden\",\n\t\"MattermostEnv\": null,\n\t\"Size\": \"\",\n\t\"Affinity\": \"\",\n\t\"State\": \"deleted\",\n\t\"CreateAt\": 0,\n\t\"DeleteAt\": 0,\n\t\"APISecurityLock\": false,\n\t\"LockAcquiredBy\": null,\n\t\"LockAcquiredAt\": 0,\n\t\"TestData\": false\n}\n```",
Message: "Cloud installation installation-three has been manually deleted and is now removed from the cloud plugin.\n\n``` json\n{\n\t\"Name\": \"installation-three\",\n\t\"ID\": \"id3\",\n\t\"OwnerID\": \"\",\n\t\"GroupID\": null,\n\t\"Version\": \"\",\n\t\"Image\": \"\",\n\t\"DNS\": \"\",\n\t\"Database\": \"\",\n\t\"Filestore\": \"\",\n\t\"License\": \"hidden\",\n\t\"MattermostEnv\": null,\n\t\"Size\": \"\",\n\t\"Affinity\": \"\",\n\t\"State\": \"deleted\",\n\t\"CreateAt\": 0,\n\t\"DeleteAt\": 0,\n\t\"APISecurityLock\": false,\n\t\"LockAcquiredBy\": null,\n\t\"LockAcquiredAt\": 0,\n\t\"TestData\": false,\n\t\"Tag\": \"\"\n}\n```",
}).Return(nil, nil)
api.On("LogWarn", mock.AnythingOfTypeArgument("string")).Return(nil)

Expand Down
16 changes: 15 additions & 1 deletion server/command_upgrade.go
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,9 @@ func buildPatchInstallationRequestFromArgs(args []string) (*cloud.PatchInstallat
return request, nil
}

// runUpgradeCommand requests an upgrade and returns the response, an error, and a boolean set to true if a non-nil error is returned due to user error, and false if the error was caused by something else.
// runUpgradeCommand requests an upgrade and returns the response, an
// error, and a boolean set to true if a non-nil error is returned due
// to user error, and false if the error was caused by something else.
func (p *Plugin) runUpgradeCommand(args []string, extra *model.CommandArgs) (*model.CommandResponse, bool, error) {
if len(args) == 0 || len(args[0]) == 0 {
return nil, true, errors.Errorf("must provide an installation name")
Expand Down Expand Up @@ -103,6 +105,13 @@ func (p *Plugin) runUpgradeCommand(args []string, extra *model.CommandArgs) (*mo
if !exists {
return nil, true, errors.Errorf("%s is not a valid docker tag for repository %s", *request.Version, dockerRepository)
}
var digest string
digest, err = p.dockerClient.GetDigestForTag(*request.Version, dockerRepository)
if err != nil {
return nil, false, errors.Wrapf(err, "failed to find a manifest digest for version %s", *request.Version)
}
installToUpgrade.Tag = *request.Version
request.Version = &digest
}

if request.License != nil {
Expand All @@ -127,5 +136,10 @@ func (p *Plugin) runUpgradeCommand(args []string, extra *model.CommandArgs) (*mo
return nil, false, err
}

err = p.updateInstallation(installToUpgrade)
if err != nil {
return nil, false, errors.Wrap(err, "failed to store new tag in plugin Installation object")
}

return getCommandResponse(model.COMMAND_RESPONSE_TYPE_EPHEMERAL, fmt.Sprintf("Upgrade of installation %s has begun. You will receive a notification when it is ready. Use /cloud list to check on the status of your installations.", name)), false, nil
}
38 changes: 38 additions & 0 deletions server/docker.go
Original file line number Diff line number Diff line change
@@ -1,7 +1,12 @@
package main

import (
"fmt"
"net/http"

"github.com/docker/distribution/manifest/schema2"
"github.com/heroku/docker-registry-client/registry"
"github.com/pkg/errors"
)

const dockerHubURL = "https://registry.hub.docker.com"
Expand Down Expand Up @@ -42,3 +47,36 @@ func (dc *DockerClient) ValidTag(desiredTag, repository string) (bool, error) {

return false, nil
}

// GetDigestForTag fetches the digest for the image. Sadly, this
// functionality is not present in the Heroku docker client, which
// will only get digests for v1 manifests, which contain the wrong
// digest sum
func (dc *DockerClient) GetDigestForTag(desiredTag, repository string) (string, error) {
resource := fmt.Sprintf("%s/v2/%s/manifests/%s", dc.registryURL, repository, desiredTag)
tt := &registry.TokenTransport{
Transport: http.DefaultTransport,
Username: dc.username,
Password: dc.password,
}

req, err := http.NewRequest("HEAD", resource, nil)
if err != nil {
return "", errors.Wrap(err, "failed to create request")
}

req.Header.Set("Accept", schema2.MediaTypeManifest)
resp, err := tt.RoundTrip(req)
if err != nil {
return "", errors.Wrap(err, "failed to HEAD manifest registry endpoint")
}

digestHeader, ok := resp.Header["Docker-Content-Digest"]
if !ok {
return "", errors.New("image digest header was missing")
} else if len(digestHeader) < 1 {
return "", errors.New("image digest header was empty")
}

return digestHeader[0], nil
}
4 changes: 4 additions & 0 deletions server/docker_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,3 +7,7 @@ type MockedDockerClient struct {
func (mc *MockedDockerClient) ValidTag(desiredTag, repository string) (bool, error) {
return mc.tagExists, nil
}

func (mc *MockedDockerClient) GetDigestForTag(desiredTag, repository string) (string, error) {
return desiredTag, nil
}
1 change: 1 addition & 0 deletions server/installation.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ type Installation struct {
Name string
cloud.Installation
TestData bool
Tag string
}

// ToPrettyJSON will return a JSON string installation with indentation and new lines
Expand Down
1 change: 1 addition & 0 deletions server/plugin.go
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ type CloudClient interface {
// DockerClientInterface is the interface for interacting with docker.
type DockerClientInterface interface {
ValidTag(desiredTag, repository string) (bool, error)
GetDigestForTag(desiredTag, repository string) (string, error)
}

// BuildHash is the full git hash of the build.
Expand Down

0 comments on commit c2ae42d

Please sign in to comment.