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  }