github.com/daniellockard/packer@v0.7.6-0.20141210173435-5a9390934716/post-processor/atlas/post-processor.go (about) 1 package atlas 2 3 import ( 4 "fmt" 5 "os" 6 "strconv" 7 "strings" 8 9 "github.com/hashicorp/atlas-go/archive" 10 "github.com/hashicorp/atlas-go/v1" 11 "github.com/mitchellh/mapstructure" 12 "github.com/mitchellh/packer/common" 13 "github.com/mitchellh/packer/packer" 14 ) 15 16 const BuildEnvKey = "ATLAS_BUILD_ID" 17 18 // Artifacts can return a string for this state key and the post-processor 19 // will use automatically use this as the type. The user's value overrides 20 // this if `artifact_type_override` is set to true. 21 const ArtifactStateType = "atlas.artifact.type" 22 23 // Artifacts can return a map[string]string for this state key and this 24 // post-processor will automatically merge it into the metadata for any 25 // uploaded artifact versions. 26 const ArtifactStateMetadata = "atlas.artifact.metadata" 27 28 type Config struct { 29 common.PackerConfig `mapstructure:",squash"` 30 31 Artifact string 32 Type string `mapstructure:"artifact_type"` 33 TypeOverride bool `mapstructure:"artifact_type_override"` 34 Metadata map[string]string 35 36 ServerAddr string `mapstructure:"server_address"` 37 Token string 38 39 // This shouldn't ever be set outside of unit tests. 40 Test bool `mapstructure:"test"` 41 42 tpl *packer.ConfigTemplate 43 user, name string 44 buildId int 45 } 46 47 type PostProcessor struct { 48 config Config 49 client *atlas.Client 50 } 51 52 func (p *PostProcessor) Configure(raws ...interface{}) error { 53 _, err := common.DecodeConfig(&p.config, raws...) 54 if err != nil { 55 return err 56 } 57 58 p.config.tpl, err = packer.NewConfigTemplate() 59 if err != nil { 60 return err 61 } 62 p.config.tpl.UserVars = p.config.PackerUserVars 63 64 templates := map[string]*string{ 65 "artifact": &p.config.Artifact, 66 "type": &p.config.Type, 67 "server_address": &p.config.ServerAddr, 68 "token": &p.config.Token, 69 } 70 71 errs := new(packer.MultiError) 72 for key, ptr := range templates { 73 *ptr, err = p.config.tpl.Process(*ptr, nil) 74 if err != nil { 75 errs = packer.MultiErrorAppend( 76 errs, fmt.Errorf("Error processing %s: %s", key, err)) 77 } 78 } 79 80 required := map[string]*string{ 81 "artifact": &p.config.Artifact, 82 "artifact_type": &p.config.Type, 83 } 84 85 for key, ptr := range required { 86 if *ptr == "" { 87 errs = packer.MultiErrorAppend( 88 errs, fmt.Errorf("%s must be set", key)) 89 } 90 } 91 92 if len(errs.Errors) > 0 { 93 return errs 94 } 95 96 p.config.user, p.config.name, err = atlas.ParseSlug(p.config.Artifact) 97 if err != nil { 98 return err 99 } 100 101 // If we have a build ID, save it 102 if v := os.Getenv(BuildEnvKey); v != "" { 103 raw, err := strconv.ParseInt(v, 0, 0) 104 if err != nil { 105 return fmt.Errorf( 106 "Error parsing build ID: %s", err) 107 } 108 109 p.config.buildId = int(raw) 110 } 111 112 // Build the client 113 p.client = atlas.DefaultClient() 114 if p.config.ServerAddr != "" { 115 p.client, err = atlas.NewClient(p.config.ServerAddr) 116 if err != nil { 117 errs = packer.MultiErrorAppend( 118 errs, fmt.Errorf("Error initializing client: %s", err)) 119 return errs 120 } 121 } 122 if p.config.Token != "" { 123 p.client.Token = p.config.Token 124 } 125 126 if !p.config.Test { 127 // Verify the client 128 if err := p.client.Verify(); err != nil { 129 errs = packer.MultiErrorAppend( 130 errs, fmt.Errorf("Error initializing client: %s", err)) 131 return errs 132 } 133 } 134 135 return nil 136 } 137 138 func (p *PostProcessor) PostProcess(ui packer.Ui, artifact packer.Artifact) (packer.Artifact, bool, error) { 139 if _, err := p.client.Artifact(p.config.user, p.config.name); err != nil { 140 if err != atlas.ErrNotFound { 141 return nil, false, fmt.Errorf( 142 "Error finding artifact: %s", err) 143 } 144 145 // Artifact doesn't exist, create it 146 ui.Message(fmt.Sprintf("Creating artifact: %s", p.config.Artifact)) 147 _, err = p.client.CreateArtifact(p.config.user, p.config.name) 148 if err != nil { 149 return nil, false, fmt.Errorf( 150 "Error creating artifact: %s", err) 151 } 152 } 153 154 opts := &atlas.UploadArtifactOpts{ 155 User: p.config.user, 156 Name: p.config.name, 157 Type: p.config.Type, 158 ID: artifact.Id(), 159 Metadata: p.metadata(artifact), 160 BuildId: p.config.buildId, 161 } 162 163 if fs := artifact.Files(); len(fs) > 0 { 164 var archiveOpts archive.ArchiveOpts 165 166 // We have files. We want to compress/upload them. If we have just 167 // one file, then we use it as-is. Otherwise, we compress all of 168 // them into a single file. 169 var path string 170 if len(fs) == 1 { 171 path = fs[0] 172 } else { 173 path = longestCommonPrefix(fs) 174 if path == "" { 175 return nil, false, fmt.Errorf( 176 "No common prefix for achiving files: %v", fs) 177 } 178 179 // Modify the archive options to only include the files 180 // that are in our file list. 181 include := make([]string, 0, len(fs)) 182 for i, f := range fs { 183 include[i] = strings.Replace(f, path, "", 1) 184 } 185 archiveOpts.Include = include 186 } 187 188 r, err := archive.CreateArchive(path, &archiveOpts) 189 if err != nil { 190 return nil, false, fmt.Errorf( 191 "Error archiving artifact: %s", err) 192 } 193 defer r.Close() 194 195 opts.File = r 196 opts.FileSize = r.Size 197 } 198 199 ui.Message("Uploading artifact version...") 200 var av *atlas.ArtifactVersion 201 doneCh := make(chan struct{}) 202 errCh := make(chan error, 1) 203 go func() { 204 var err error 205 av, err = p.client.UploadArtifact(opts) 206 if err != nil { 207 errCh <- err 208 return 209 } 210 close(doneCh) 211 }() 212 213 select { 214 case err := <-errCh: 215 return nil, false, fmt.Errorf("Error uploading: %s", err) 216 case <-doneCh: 217 } 218 219 return &Artifact{ 220 Name: p.config.Artifact, 221 Type: p.config.Type, 222 Version: av.Version, 223 }, true, nil 224 } 225 226 func (p *PostProcessor) metadata(artifact packer.Artifact) map[string]string { 227 var metadata map[string]string 228 metadataRaw := artifact.State(ArtifactStateMetadata) 229 if metadataRaw != nil { 230 if err := mapstructure.Decode(metadataRaw, &metadata); err != nil { 231 panic(err) 232 } 233 } 234 235 if p.config.Metadata != nil { 236 // If we have no extra metadata, just return as-is 237 if metadata == nil { 238 return p.config.Metadata 239 } 240 241 // Merge the metadata 242 for k, v := range p.config.Metadata { 243 metadata[k] = v 244 } 245 } 246 247 return metadata 248 } 249 250 func (p *PostProcessor) artifactType(artifact packer.Artifact) string { 251 if !p.config.TypeOverride { 252 if v := artifact.State(ArtifactStateType); v != nil { 253 return v.(string) 254 } 255 } 256 257 return p.config.Type 258 }