github.com/rothwerx/packer@v0.9.0/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 (
    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  
   142  	return nil
   143  }
   144  
   145  func (p *PostProcessor) PostProcess(ui packer.Ui, artifact packer.Artifact) (packer.Artifact, bool, error) {
   146  	if _, err := p.client.Artifact(p.config.user, p.config.name); err != nil {
   147  		if err != atlas.ErrNotFound {
   148  			return nil, false, fmt.Errorf(
   149  				"Error finding artifact: %s", err)
   150  		}
   151  
   152  		// Artifact doesn't exist, create it
   153  		ui.Message(fmt.Sprintf("Creating artifact: %s", p.config.Artifact))
   154  		_, err = p.client.CreateArtifact(p.config.user, p.config.name)
   155  		if err != nil {
   156  			return nil, false, fmt.Errorf(
   157  				"Error creating artifact: %s", err)
   158  		}
   159  	}
   160  
   161  	opts := &atlas.UploadArtifactOpts{
   162  		User:      p.config.user,
   163  		Name:      p.config.name,
   164  		Type:      p.config.Type,
   165  		ID:        artifact.Id(),
   166  		Metadata:  p.metadata(artifact),
   167  		BuildID:   p.config.buildId,
   168  		CompileID: p.config.compileId,
   169  	}
   170  
   171  	if fs := artifact.Files(); len(fs) > 0 {
   172  		var archiveOpts archive.ArchiveOpts
   173  
   174  		// We have files. We want to compress/upload them. If we have just
   175  		// one file, then we use it as-is. Otherwise, we compress all of
   176  		// them into a single file.
   177  		var path string
   178  		if len(fs) == 1 {
   179  			path = fs[0]
   180  		} else {
   181  			path = longestCommonPrefix(fs)
   182  			if path == "" {
   183  				return nil, false, fmt.Errorf(
   184  					"No common prefix for achiving files: %v", fs)
   185  			}
   186  
   187  			// Modify the archive options to only include the files
   188  			// that are in our file list.
   189  			include := make([]string, len(fs))
   190  			for i, f := range fs {
   191  				include[i] = strings.Replace(f, path, "", 1)
   192  			}
   193  			archiveOpts.Include = include
   194  		}
   195  
   196  		r, err := archive.CreateArchive(path, &archiveOpts)
   197  		if err != nil {
   198  			return nil, false, fmt.Errorf(
   199  				"Error archiving artifact: %s", err)
   200  		}
   201  		defer r.Close()
   202  
   203  		opts.File = r
   204  		opts.FileSize = r.Size
   205  	}
   206  
   207  	ui.Message(fmt.Sprintf("Uploading artifact (%d bytes)", opts.FileSize))
   208  	var av *atlas.ArtifactVersion
   209  	doneCh := make(chan struct{})
   210  	errCh := make(chan error, 1)
   211  	go func() {
   212  		var err error
   213  		av, err = p.client.UploadArtifact(opts)
   214  		if err != nil {
   215  			errCh <- err
   216  			return
   217  		}
   218  		close(doneCh)
   219  	}()
   220  
   221  	select {
   222  	case err := <-errCh:
   223  		return nil, false, fmt.Errorf("Error uploading (%d bytes): %s", opts.FileSize, err)
   224  	case <-doneCh:
   225  	}
   226  
   227  	return &Artifact{
   228  		Name:    p.config.Artifact,
   229  		Type:    p.config.Type,
   230  		Version: av.Version,
   231  	}, true, nil
   232  }
   233  
   234  func (p *PostProcessor) metadata(artifact packer.Artifact) map[string]string {
   235  	var metadata map[string]string
   236  	metadataRaw := artifact.State(ArtifactStateMetadata)
   237  	if metadataRaw != nil {
   238  		if err := mapstructure.Decode(metadataRaw, &metadata); err != nil {
   239  			panic(err)
   240  		}
   241  	}
   242  
   243  	if p.config.Metadata != nil {
   244  		// If we have no extra metadata, just return as-is
   245  		if metadata == nil {
   246  			return p.config.Metadata
   247  		}
   248  
   249  		// Merge the metadata
   250  		for k, v := range p.config.Metadata {
   251  			metadata[k] = v
   252  		}
   253  	}
   254  
   255  	return metadata
   256  }
   257  
   258  func (p *PostProcessor) artifactType(artifact packer.Artifact) string {
   259  	if !p.config.TypeOverride {
   260  		if v := artifact.State(ArtifactStateType); v != nil {
   261  			return v.(string)
   262  		}
   263  	}
   264  
   265  	return p.config.Type
   266  }