Skip to content

Commit

Permalink
feat(main.go,actions): create a docker image tagging command
Browse files Browse the repository at this point in the history
Fixes #57
Fixes #6
Fixes #58
  • Loading branch information
Aaron Schlesinger committed Jun 28, 2016
1 parent 2c4ec68 commit 09dba8a
Show file tree
Hide file tree
Showing 28 changed files with 938 additions and 76 deletions.
47 changes: 7 additions & 40 deletions actions/common.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ import (
"os"
"path/filepath"
"text/template"

"github.com/deis/deisrel/git"
)

const (
Expand Down Expand Up @@ -46,35 +48,18 @@ type releaseName struct {
}

var (
// TODO: https://github.com/deis/deisrel/issues/12
repoToComponentNames = map[string][]string{
"builder": {"Builder"},
"controller": {"Controller"},
"dockerbuilder": {"DockerBuilder"},
"fluentd": {"FluentD"},
"monitor": {"InfluxDB", "Grafana", "Telegraf"},
"logger": {"Logger"},
"minio": {"Minio"},
"postgres": {"Database"},
"registry": {"Registry"},
"router": {"Router"},
"slugbuilder": {"SlugBuilder"},
"slugrunner": {"SlugRunner"},
"workflow-e2e": {"WorkflowE2E"},
"workflow-manager": {"WorkflowManager"},
}

repoNames = getRepoNames(repoToComponentNames)

// additionalGitRepoNames represents the repo names lacking representation
// in any helm chart, yet still requiring updates during each Workflow
// release, including changelog generation and creation of git tags.
additionalGitRepoNames = []string{"workflow", "charts"}

// allGitRepoNames represent all GitHub repo names needing git-based updates for a release
allGitRepoNames = append(repoNames, additionalGitRepoNames...)
allGitRepoNames = append(git.RepoNames(), additionalGitRepoNames...)

componentNames = getComponentNames(repoToComponentNames)
repoNames = git.RepoNames()
componentNames = git.ComponentNames()
// TODO: https://github.com/deis/deisrel/issues/12
repoToComponentNames = git.RepoToComponentNames()

deisRelease = releaseName{
Full: os.Getenv("WORKFLOW_RELEASE"),
Expand Down Expand Up @@ -117,24 +102,6 @@ var (
}
)

func getRepoNames(repoToComponentNames map[string][]string) []string {
repoNames := make([]string, 0, len(repoToComponentNames))
for repoName := range repoToComponentNames {
repoNames = append(repoNames, repoName)
}
return repoNames
}

func getComponentNames(repoToComponentNames map[string][]string) []string {
var ret []string
for _, componentNames := range repoToComponentNames {
for _, componentName := range componentNames {
ret = append(ret, componentName)
}
}
return ret
}

func getFullPath(dirName string) string {
currentWorkingDir, err := os.Getwd()
if err != nil {
Expand Down
43 changes: 43 additions & 0 deletions actions/docker/cmd.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
package docker

import (
"github.com/codegangsta/cli"
"github.com/deis/deisrel/actions"
"github.com/deis/deisrel/docker"
"github.com/google/go-github/github"
)

// Command returns the entire set of subcommands for the 'deisrel docker ...' command
func Command(ghClient *github.Client, dockerCl docker.Client) cli.Command {
return cli.Command{
Name: "docker",
Subcommands: []cli.Command{
cli.Command{
Name: "retag",
Description: "This command pulls specific Docker images for each corresponding repository's Git SHA, then retags each one to a uniform release tag",
Usage: "Retag each specific Docker image from a repo-specific Git SHA to a uniform release tag",
Flags: []cli.Flag{
cli.BoolFlag{
Name: actions.YesFlag,
Usage: "If true, skip the prompt to confirm that newly-tagged images will be pushed",
},
cli.StringFlag{
Name: actions.ShaFilepathFlag,
Value: "",
Usage: "the file path which to read in the shas to release",
},
cli.StringFlag{
Name: newOrgFlag,
Usage: "The Docker registry organization for the new tagged images (default: deis)",
},
cli.StringFlag{
Name: actions.RefFlag,
Value: "master",
Usage: "Optional ref to add to GitHub repo request (can be SHA, branch or tag)",
},
},
Action: retagCmd(ghClient, dockerCl),
},
},
}
}
122 changes: 122 additions & 0 deletions actions/docker/retag.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,122 @@
package docker

import (
"fmt"
"log"

"github.com/codegangsta/cli"
"github.com/deis/deisrel/actions"
"github.com/deis/deisrel/docker"
"github.com/deis/deisrel/git"
"github.com/google/go-github/github"
)

const (
newOrgFlag = "new-org"
defaultNewOrg = "deis"
)

func getAllReposAndShas(ghClient *github.Client, shaFilePath, ref string) ([]git.RepoAndSha, error) {
var allReposAndShas []git.RepoAndSha
if shaFilePath != "" {
reposFromFile, err := git.GetShasFromFilepath(shaFilePath)
if err != nil {
return nil, fmt.Errorf("getting git SHAs from %s (%s)", shaFilePath, err)
}
allReposAndShas = reposFromFile
}

reposAndShas, err := git.GetSHAs(ghClient, git.RepoNames(), git.NoTransform, ref)
if err != nil {
return nil, fmt.Errorf("getting all SHAs from HEAD on each repository (%s)", err)
}
allReposAndShas = reposAndShas
return allReposAndShas, nil
}

func ensureImages(dockerCl docker.Client, images []*docker.Image) {
imgsCh, errCh, doneCh := docker.PullImages(dockerCl, images)
for {
select {
case img := <-imgsCh:
fmt.Printf("pulled %s\n", img.String())
case err := <-errCh:
fmt.Printf("error pulling %s (%s)\n", err.Img.String(), err.Err)
case <-doneCh:
return
}
}
}

func retagAll(dockerCl docker.Client, imageTagPairs []docker.ImageTagPair) {
pairsCh, errCh, doneCh := docker.RetagImages(dockerCl, imageTagPairs)
for {
select {
case pair := <-pairsCh:
fmt.Printf("re-tagged %s to %s\n", pair.Source.String(), pair.Target.String())
case err := <-errCh:
fmt.Printf("error re-tagging %s to %s (%s)\n", err.SourceImage.String(), err.TargetImage.String(), err.Err)
case <-doneCh:
return
}
}
}

func pushTargets(dockerCl docker.Client, imageTagPairs []docker.ImageTagPair) {
images := make([]*docker.Image, len(imageTagPairs))
for i, imageTagPair := range imageTagPairs {
images[i] = imageTagPair.Target
}
if err := docker.PushImages(dockerCl, images); err != nil {
log.Printf("Error pushing (%s)", err)
return
}
}

func retagCmd(ghClient *github.Client, dockerCl docker.Client) func(c *cli.Context) error {
return func(c *cli.Context) error {
newTag := c.Args().Get(0)
if newTag == "" {
log.Fatal("This command should have 1 argument to specify the new tag to use")
}
newOrg := c.String(newOrgFlag)
if newOrg == "" {
newOrg = defaultNewOrg
}
// only prompt to push new images if the yes flag was false
shaFilepath := c.String(actions.ShaFilepathFlag)
ref := c.String(actions.RefFlag)
promptPush := !c.Bool(actions.YesFlag)

allReposAndShas, err := getAllReposAndShas(ghClient, shaFilepath, ref)
if err != nil {
log.Fatalf("Error getting all git SHAs (%s)", err)
}

repoAndShaList := git.NewRepoAndShaListFromSlice(allReposAndShas)
repoAndShaList.Sort()
images, err := docker.ParseImagesFromRepoAndShaList(docker.DeisCIDockerOrg, repoAndShaList)
if err != nil {
log.Fatalf("Error parsing docker images (%s)", err)
}

fmt.Printf("Pulling %d images\n", len(images))
ensureImages(dockerCl, images)
fmt.Println("Re-tagging images...")
imageTagPairs := docker.CreateImageTagPairsFromTransform(images, func(img docker.Image) *docker.Image {
img.SetRepo(newOrg)
img.SetTag(newTag)
return &img
})
retagAll(dockerCl, imageTagPairs)
fmt.Println("done")

if promptPush {
fmt.Println("Pushing new tags")
pushTargets(dockerCl, imageTagPairs)
} else {
fmt.Println("Not pushing newly tagged images")
}
return nil
}
}
27 changes: 1 addition & 26 deletions actions/git.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ func GitTag(client *github.Client) func(c *cli.Context) error {
}
if shaFilepath != "" {
// update the latest shas with the shas in shaFilepath
reposFromFile, err := getShasFromFilepath(shaFilepath)
reposFromFile, err := git.GetShasFromFilepath(shaFilepath)
if err != nil {
log.Fatal(err)
}
Expand Down Expand Up @@ -99,28 +99,3 @@ func prompt() (bool, error) {
}
return false, nil
}

func getShasFromFilepath(path string) ([]git.RepoAndSha, error) {
ret := []git.RepoAndSha{}
file, err := os.Open(path)
if err != nil {
return nil, fmt.Errorf("could not open %s: %s", path, err)
}
defer file.Close()

scanner := bufio.NewScanner(file)
for scanner.Scan() {
line := scanner.Text()
if strings.ContainsRune(line, '=') {
repoParts := strings.SplitN(line, "=", 2)
ret = append(ret, git.RepoAndSha{
Name: repoParts[0],
SHA: repoParts[1],
})
}
}
if err := scanner.Err(); err != nil {
return nil, fmt.Errorf("failed reading %s: %s", path, err)
}
return ret, nil
}
8 changes: 8 additions & 0 deletions docker/client.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
package docker

// Client is the interface to interact with the docker daemon.
type Client interface {
Push(*Image) error
Pull(*Image) error
Retag(*Image, *Image) error
}
27 changes: 27 additions & 0 deletions docker/cmd_client.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
package docker

import (
"os/exec"
)

type cmdClient struct{}

// NewCmdClient creates a new Client that does all of its operations by shelling out to the docker CLI
func NewCmdClient() Client {
return &cmdClient{}
}

func (c *cmdClient) Push(img *Image) error {
cmd := exec.Command("docker", "push", img.String())
return cmd.Run()
}

func (c *cmdClient) Pull(img *Image) error {
cmd := exec.Command("docker", "pull", img.String())
return cmd.Run()
}

func (c *cmdClient) Retag(src *Image, tar *Image) error {
cmd := exec.Command("docker", "tag", src.String(), tar.String())
return cmd.Run()
}
Loading

0 comments on commit 09dba8a

Please sign in to comment.