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