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  }