github.com/mweagle/Sparta@v1.15.0/docker/docker.go (about) 1 package docker 2 3 import ( 4 "bytes" 5 "encoding/base64" 6 "fmt" 7 "os" 8 "os/exec" 9 "strings" 10 "time" 11 12 "github.com/aws/aws-sdk-go/aws/session" 13 "github.com/aws/aws-sdk-go/service/ecr" 14 "github.com/aws/aws-sdk-go/service/sts" 15 "github.com/mweagle/Sparta/system" 16 "github.com/pkg/errors" 17 "github.com/sirupsen/logrus" 18 ) 19 20 //////////////////////////////////////////////////////////////////////////////// 21 // CONSTANTS 22 //////////////////////////////////////////////////////////////////////////////// 23 24 const ( 25 // BinaryNameArgument is the argument provided to docker build that 26 // supplies the local statically built Go binary 27 BinaryNameArgument = "SPARTA_DOCKER_BINARY" 28 ) 29 30 // BuildDockerImageWithFlags is an extended version of BuildDockerImage that includes 31 // support for build time tags 32 func BuildDockerImageWithFlags(serviceName string, 33 dockerFilepath string, 34 dockerTags map[string]string, 35 buildTags string, 36 linkFlags string, 37 logger *logrus.Logger) error { 38 39 // BEGIN DOCKER PRECONDITIONS 40 // Ensure that serviceName and tags are lowercase to make Docker happy 41 var dockerErrors []string 42 for eachKey, eachValue := range dockerTags { 43 if eachKey != strings.ToLower(eachKey) || 44 eachValue != strings.ToLower(eachValue) { 45 dockerErrors = append(dockerErrors, fmt.Sprintf("--tag %s:%s MUST be lower case", eachKey, eachValue)) 46 } 47 } 48 49 if len(dockerErrors) > 0 { 50 return errors.Errorf("Docker build errors: %s", strings.Join(dockerErrors[:], ", ")) 51 } 52 // BEGIN Informational - output the docker version... 53 dockerVersionCmd := exec.Command("docker", "-v") 54 dockerVersionCmdErr := system.RunOSCommand(dockerVersionCmd, logger) 55 if dockerVersionCmdErr != nil { 56 return errors.Wrapf(dockerVersionCmdErr, "Attempting to get docker version") 57 } 58 // END Informational - output the docker version... 59 60 // END DOCKER PRECONDITIONS 61 62 // Compile this binary for minimal Docker size 63 // https://blog.codeship.com/building-minimal-docker-containers-for-go-applications/ 64 currentTime := time.Now().UnixNano() 65 executableOutput := fmt.Sprintf("%s-%d-docker.lambda.amd64", serviceName, currentTime) 66 buildErr := system.BuildGoBinary(serviceName, 67 executableOutput, 68 false, 69 fmt.Sprintf("%d", currentTime), 70 buildTags, 71 linkFlags, 72 false, 73 logger) 74 if buildErr != nil { 75 return errors.Wrapf(buildErr, "Attempting to build Docker binary") 76 } 77 defer func() { 78 removeErr := os.Remove(executableOutput) 79 if removeErr != nil { 80 logger.WithFields(logrus.Fields{ 81 "Path": executableOutput, 82 "Error": removeErr, 83 }).Warn("Failed to delete temporary Docker binary") 84 } 85 }() 86 87 // ARG SPARTA_DOCKER_BINARY reference s.t. we can supply the binary 88 // name to the build.. 89 // We need to build the static binary s.t. we can add it to the Docker container... 90 // Build the image... 91 dockerArgs := []string{ 92 "build", 93 "--build-arg", 94 fmt.Sprintf("%s=%s", BinaryNameArgument, executableOutput), 95 } 96 97 if dockerFilepath != "" { 98 dockerArgs = append(dockerArgs, "--file", dockerFilepath) 99 } 100 // Add the latest tag 101 // dockerArgs = append(dockerArgs, "--tag", fmt.Sprintf("sparta/%s:latest", serviceName)) 102 logger.WithFields(logrus.Fields{ 103 "Tags": dockerTags, 104 }).Info("Creating Docker image") 105 106 for eachKey, eachValue := range dockerTags { 107 dockerArgs = append(dockerArgs, "--tag", fmt.Sprintf("%s:%s", 108 strings.ToLower(eachKey), 109 strings.ToLower(eachValue))) 110 } 111 112 dockerArgs = append(dockerArgs, ".") 113 dockerCmd := exec.Command("docker", dockerArgs...) 114 return system.RunOSCommand(dockerCmd, logger) 115 } 116 117 // BuildDockerImage creates the smallest docker image for this Golang binary 118 // using the serviceName as the image name and including the supplied tags 119 func BuildDockerImage(serviceName string, 120 dockerFilepath string, 121 tags map[string]string, 122 logger *logrus.Logger) error { 123 124 return BuildDockerImageWithFlags(serviceName, 125 dockerFilepath, 126 tags, 127 "", 128 "", 129 logger) 130 } 131 132 // PushDockerImageToECR pushes a local Docker image to an ECR repository 133 func PushDockerImageToECR(localImageTag string, 134 ecrRepoName string, 135 awsSession *session.Session, 136 logger *logrus.Logger) (string, error) { 137 138 stsSvc := sts.New(awsSession) 139 ecrSvc := ecr.New(awsSession) 140 141 // 1. Get the caller identity s.t. we can get the ECR URL which includes the 142 // account name 143 stsIdentityOutput, stsIdentityErr := stsSvc.GetCallerIdentity(&sts.GetCallerIdentityInput{}) 144 if stsIdentityErr != nil { 145 return "", errors.Wrapf(stsIdentityErr, "Attempting to get AWS caller identity") 146 } 147 148 // 2. Create the URL to which we're going to do the push 149 localImageTagParts := strings.Split(localImageTag, ":") 150 ecrTagValue := fmt.Sprintf("%s.dkr.ecr.%s.amazonaws.com/%s:%s", 151 *stsIdentityOutput.Account, 152 *awsSession.Config.Region, 153 ecrRepoName, 154 localImageTagParts[len(localImageTagParts)-1]) 155 156 // 3. Tag the local image with the ECR tag 157 dockerTagCmd := exec.Command("docker", "tag", localImageTag, ecrTagValue) 158 dockerTagCmdErr := system.RunOSCommand(dockerTagCmd, logger) 159 if dockerTagCmdErr != nil { 160 return "", errors.Wrapf(dockerTagCmdErr, "Attempting to tag Docker image") 161 } 162 163 // 4. Push the image - if that fails attempt to reauthorize with the docker 164 // client and try again 165 var pushError error 166 dockerPushCmd := exec.Command("docker", "push", ecrTagValue) 167 pushError = system.RunOSCommand(dockerPushCmd, logger) 168 if pushError != nil { 169 logger.WithFields(logrus.Fields{ 170 "Error": pushError, 171 }).Info("ECR push failed - reauthorizing") 172 ecrAuthTokenResult, ecrAuthTokenResultErr := ecrSvc.GetAuthorizationToken(&ecr.GetAuthorizationTokenInput{}) 173 if ecrAuthTokenResultErr != nil { 174 pushError = ecrAuthTokenResultErr 175 } else { 176 authData := ecrAuthTokenResult.AuthorizationData[0] 177 authToken, authTokenErr := base64.StdEncoding.DecodeString(*authData.AuthorizationToken) 178 if authTokenErr != nil { 179 pushError = authTokenErr 180 } else { 181 authTokenString := string(authToken) 182 authTokenParts := strings.Split(authTokenString, ":") 183 dockerURL := fmt.Sprintf("https://%s.dkr.ecr.%s.amazonaws.com", 184 *stsIdentityOutput.Account, 185 *awsSession.Config.Region) 186 dockerLoginCmd := exec.Command("docker", 187 "login", 188 "-u", 189 authTokenParts[0], 190 "--password-stdin", 191 dockerURL) 192 dockerLoginCmd.Stdout = os.Stdout 193 dockerLoginCmd.Stdin = bytes.NewReader([]byte(fmt.Sprintf("%s\n", authTokenParts[1]))) 194 dockerLoginCmd.Stderr = os.Stderr 195 dockerLoginCmdErr := system.RunOSCommand(dockerLoginCmd, logger) 196 if dockerLoginCmdErr != nil { 197 pushError = dockerLoginCmdErr 198 } else { 199 // Try it again... 200 dockerRetryPushCmd := exec.Command("docker", "push", ecrTagValue) 201 dockerRetryPushCmdErr := system.RunOSCommand(dockerRetryPushCmd, logger) 202 pushError = dockerRetryPushCmdErr 203 } 204 } 205 } 206 } 207 if pushError != nil { 208 pushError = errors.Wrapf(pushError, "Attempting to push Docker image") 209 } 210 return ecrTagValue, pushError 211 }