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