Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(main.go,actions): create a docker image tagging command #89

Merged
merged 4 commits into from
Jun 29, 2016
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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{
Copy link
Member

@vdice vdice Jun 20, 2016

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@arschles can we also allow use of the --ref <ref> flag for this command so that the shas can be pulled from HEAD of a branch other than master? addressed in latest changeset.

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