github.com/coreos/rocket@v1.30.1-0.20200224141603-171c416fac02/pkg/aci/aci.go (about)

     1  // Copyright 2014 The rkt Authors
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use this file except in compliance with the License.
     5  // You may obtain a copy of the License at
     6  //
     7  //     http://www.apache.org/licenses/LICENSE-2.0
     8  //
     9  // Unless required by applicable law or agreed to in writing, software
    10  // distributed under the License is distributed on an "AS IS" BASIS,
    11  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  // See the License for the specific language governing permissions and
    13  // limitations under the License.
    14  
    15  // Package aci implements helper functions for working with ACIs
    16  package aci
    17  
    18  import (
    19  	"archive/tar"
    20  	"bytes"
    21  	"encoding/json"
    22  	"errors"
    23  	"io"
    24  	"io/ioutil"
    25  	"os"
    26  	"strings"
    27  	"time"
    28  
    29  	"github.com/appc/spec/aci"
    30  	"github.com/appc/spec/schema"
    31  	"github.com/appc/spec/schema/types"
    32  	"github.com/hashicorp/errwrap"
    33  	"golang.org/x/crypto/openpgp"
    34  )
    35  
    36  type ACIEntry struct {
    37  	Header   *tar.Header
    38  	Contents string
    39  }
    40  
    41  type imageArchiveWriter struct {
    42  	*tar.Writer
    43  	am *schema.ImageManifest
    44  }
    45  
    46  // NewImageWriter creates a new ArchiveWriter which will generate an App
    47  // Container Image based on the given manifest and write it to the given
    48  // tar.Writer
    49  // TODO(sgotti) this is a copy of appc/spec/aci.imageArchiveWriter with
    50  // addFileNow changed to create the file with the current user. needed for
    51  // testing as non root user.
    52  func NewImageWriter(am schema.ImageManifest, w *tar.Writer) aci.ArchiveWriter {
    53  	aw := &imageArchiveWriter{
    54  		w,
    55  		&am,
    56  	}
    57  	return aw
    58  }
    59  
    60  func (aw *imageArchiveWriter) AddFile(hdr *tar.Header, r io.Reader) error {
    61  	err := aw.Writer.WriteHeader(hdr)
    62  	if err != nil {
    63  		return err
    64  	}
    65  
    66  	if r != nil {
    67  		_, err := io.Copy(aw.Writer, r)
    68  		if err != nil {
    69  			return err
    70  		}
    71  	}
    72  
    73  	return nil
    74  }
    75  
    76  func (aw *imageArchiveWriter) addFileNow(path string, contents []byte) error {
    77  	buf := bytes.NewBuffer(contents)
    78  	now := time.Now()
    79  	hdr := tar.Header{
    80  		Name:       path,
    81  		Mode:       0644,
    82  		Uid:        os.Getuid(),
    83  		Gid:        os.Getgid(),
    84  		Size:       int64(buf.Len()),
    85  		ModTime:    now,
    86  		Typeflag:   tar.TypeReg,
    87  		ChangeTime: now,
    88  	}
    89  	return aw.AddFile(&hdr, buf)
    90  }
    91  
    92  func (aw *imageArchiveWriter) addManifest(name string, m json.Marshaler) error {
    93  	out, err := m.MarshalJSON()
    94  	if err != nil {
    95  		return err
    96  	}
    97  	return aw.addFileNow(name, out)
    98  }
    99  
   100  func (aw *imageArchiveWriter) Close() error {
   101  	if err := aw.addManifest(aci.ManifestFile, aw.am); err != nil {
   102  		return err
   103  	}
   104  	return aw.Writer.Close()
   105  }
   106  
   107  // NewBasicACI creates a new ACI in the given directory with the given name.
   108  // Used for testing.
   109  func NewBasicACI(dir string, name string) (*os.File, error) {
   110  	manifest := schema.ImageManifest{
   111  		ACKind:    schema.ImageManifestKind,
   112  		ACVersion: schema.AppContainerVersion,
   113  		Name:      types.ACIdentifier(name),
   114  	}
   115  
   116  	b, err := manifest.MarshalJSON()
   117  	if err != nil {
   118  		return nil, err
   119  	}
   120  
   121  	return NewACI(dir, string(b), nil)
   122  }
   123  
   124  // NewACI creates a new ACI in the given directory with the given image
   125  // manifest and entries.
   126  // Used for testing.
   127  func NewACI(dir string, manifest string, entries []*ACIEntry) (*os.File, error) {
   128  	var im schema.ImageManifest
   129  	if err := im.UnmarshalJSON([]byte(manifest)); err != nil {
   130  		return nil, errwrap.Wrap(errors.New("invalid image manifest"), err)
   131  	}
   132  
   133  	tf, err := ioutil.TempFile(dir, "")
   134  	if err != nil {
   135  		return nil, err
   136  	}
   137  	defer os.Remove(tf.Name())
   138  
   139  	tw := tar.NewWriter(tf)
   140  	aw := NewImageWriter(im, tw)
   141  
   142  	for _, entry := range entries {
   143  		// Add default mode
   144  		if entry.Header.Mode == 0 {
   145  			if entry.Header.Typeflag == tar.TypeDir {
   146  				entry.Header.Mode = 0755
   147  			} else {
   148  				entry.Header.Mode = 0644
   149  			}
   150  		}
   151  		// Add calling user uid and gid or tests will fail
   152  		entry.Header.Uid = os.Getuid()
   153  		entry.Header.Gid = os.Getgid()
   154  		sr := strings.NewReader(entry.Contents)
   155  		if err := aw.AddFile(entry.Header, sr); err != nil {
   156  			return nil, err
   157  		}
   158  	}
   159  
   160  	if err := aw.Close(); err != nil {
   161  		return nil, err
   162  	}
   163  	return tf, nil
   164  }
   165  
   166  // NewDetachedSignature creates a new openpgp armored detached signature for the given ACI
   167  // signed with armoredPrivateKey.
   168  func NewDetachedSignature(armoredPrivateKey string, aci io.Reader) (io.Reader, error) {
   169  	entityList, err := openpgp.ReadArmoredKeyRing(bytes.NewBufferString(armoredPrivateKey))
   170  	if err != nil {
   171  		return nil, err
   172  	}
   173  	if len(entityList) < 1 {
   174  		return nil, errors.New("empty entity list")
   175  	}
   176  	signature := &bytes.Buffer{}
   177  	if err := openpgp.ArmoredDetachSign(signature, entityList[0], aci, nil); err != nil {
   178  		return nil, err
   179  	}
   180  	return signature, nil
   181  }