github.com/felipejfc/helm@v2.1.2+incompatible/pkg/repo/repo.go (about)

     1  /*
     2  Copyright 2016 The Kubernetes Authors All rights reserved.
     3  
     4  Licensed under the Apache License, Version 2.0 (the "License");
     5  you may not use this file except in compliance with the License.
     6  You may obtain a copy of the License at
     7  
     8      http://www.apache.org/licenses/LICENSE-2.0
     9  
    10  Unless required by applicable law or agreed to in writing, software
    11  distributed under the License is distributed on an "AS IS" BASIS,
    12  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    13  See the License for the specific language governing permissions and
    14  limitations under the License.
    15  */
    16  
    17  package repo // import "k8s.io/helm/pkg/repo"
    18  
    19  import (
    20  	"errors"
    21  	"fmt"
    22  	"io/ioutil"
    23  	"os"
    24  	"path/filepath"
    25  	"strings"
    26  	"time"
    27  
    28  	"github.com/ghodss/yaml"
    29  
    30  	"k8s.io/helm/pkg/chartutil"
    31  	"k8s.io/helm/pkg/provenance"
    32  )
    33  
    34  // ErrRepoOutOfDate indicates that the repository file is out of date, but
    35  // is fixable.
    36  var ErrRepoOutOfDate = errors.New("repository file is out of date")
    37  
    38  // ChartRepository represents a chart repository
    39  type ChartRepository struct {
    40  	RootPath   string
    41  	URL        string // URL of repository
    42  	ChartPaths []string
    43  	IndexFile  *IndexFile
    44  }
    45  
    46  // Entry represents one repo entry in a repositories listing.
    47  type Entry struct {
    48  	Name  string `json:"name"`
    49  	Cache string `json:"cache"`
    50  	URL   string `json:"url"`
    51  }
    52  
    53  // RepoFile represents the repositories.yaml file in $HELM_HOME
    54  type RepoFile struct {
    55  	APIVersion   string    `json:"apiVersion"`
    56  	Generated    time.Time `json:"generated"`
    57  	Repositories []*Entry  `json:"repositories"`
    58  }
    59  
    60  // NewRepoFile generates an empty repositories file.
    61  //
    62  // Generated and APIVersion are automatically set.
    63  func NewRepoFile() *RepoFile {
    64  	return &RepoFile{
    65  		APIVersion:   APIVersionV1,
    66  		Generated:    time.Now(),
    67  		Repositories: []*Entry{},
    68  	}
    69  }
    70  
    71  // LoadRepositoriesFile takes a file at the given path and returns a RepoFile object
    72  //
    73  // If this returns ErrRepoOutOfDate, it also returns a recovered RepoFile that
    74  // can be saved as a replacement to the out of date file.
    75  func LoadRepositoriesFile(path string) (*RepoFile, error) {
    76  	b, err := ioutil.ReadFile(path)
    77  	if err != nil {
    78  		return nil, err
    79  	}
    80  
    81  	r := &RepoFile{}
    82  	err = yaml.Unmarshal(b, r)
    83  	if err != nil {
    84  		return nil, err
    85  	}
    86  
    87  	// File is either corrupt, or is from before v2.0.0-Alpha.5
    88  	if r.APIVersion == "" {
    89  		m := map[string]string{}
    90  		if err = yaml.Unmarshal(b, &m); err != nil {
    91  			return nil, err
    92  		}
    93  		r := NewRepoFile()
    94  		for k, v := range m {
    95  			r.Add(&Entry{
    96  				Name:  k,
    97  				URL:   v,
    98  				Cache: fmt.Sprintf("%s-index.yaml", k),
    99  			})
   100  		}
   101  		return r, ErrRepoOutOfDate
   102  	}
   103  
   104  	return r, nil
   105  }
   106  
   107  // Add adds one or more repo entries to a repo file.
   108  func (r *RepoFile) Add(re ...*Entry) {
   109  	r.Repositories = append(r.Repositories, re...)
   110  }
   111  
   112  // Update attempts to replace one or more repo entries in a repo file. If an
   113  // entry with the same name doesn't exist in the repo file it will add it.
   114  func (r *RepoFile) Update(re ...*Entry) {
   115  	for _, target := range re {
   116  		found := false
   117  		for j, repo := range r.Repositories {
   118  			if repo.Name == target.Name {
   119  				r.Repositories[j] = target
   120  				found = true
   121  				break
   122  			}
   123  		}
   124  		if !found {
   125  			r.Add(target)
   126  		}
   127  	}
   128  }
   129  
   130  // Has returns true if the given name is already a repository name.
   131  func (r *RepoFile) Has(name string) bool {
   132  	for _, rf := range r.Repositories {
   133  		if rf.Name == name {
   134  			return true
   135  		}
   136  	}
   137  	return false
   138  }
   139  
   140  // Remove removes the entry from the list of repositories.
   141  func (r *RepoFile) Remove(name string) bool {
   142  	cp := []*Entry{}
   143  	found := false
   144  	for _, rf := range r.Repositories {
   145  		if rf.Name == name {
   146  			found = true
   147  			continue
   148  		}
   149  		cp = append(cp, rf)
   150  	}
   151  	r.Repositories = cp
   152  	return found
   153  }
   154  
   155  // WriteFile writes a repositories file to the given path.
   156  func (r *RepoFile) WriteFile(path string, perm os.FileMode) error {
   157  	data, err := yaml.Marshal(r)
   158  	if err != nil {
   159  		return err
   160  	}
   161  	return ioutil.WriteFile(path, data, perm)
   162  }
   163  
   164  // LoadChartRepository loads a directory of charts as if it were a repository.
   165  //
   166  // It requires the presence of an index.yaml file in the directory.
   167  //
   168  // This function evaluates the contents of the directory and
   169  // returns a ChartRepository
   170  func LoadChartRepository(dir, url string) (*ChartRepository, error) {
   171  	dirInfo, err := os.Stat(dir)
   172  	if err != nil {
   173  		return nil, err
   174  	}
   175  
   176  	if !dirInfo.IsDir() {
   177  		return nil, fmt.Errorf("%q is not a directory", dir)
   178  	}
   179  
   180  	r := &ChartRepository{RootPath: dir, URL: url}
   181  
   182  	// FIXME: Why are we recursively walking directories?
   183  	// FIXME: Why are we not reading the repositories.yaml to figure out
   184  	// what repos to use?
   185  	filepath.Walk(dir, func(path string, f os.FileInfo, err error) error {
   186  		if !f.IsDir() {
   187  			if strings.Contains(f.Name(), "-index.yaml") {
   188  				i, err := LoadIndexFile(path)
   189  				if err != nil {
   190  					return nil
   191  				}
   192  				r.IndexFile = i
   193  			} else if strings.HasSuffix(f.Name(), ".tgz") {
   194  				r.ChartPaths = append(r.ChartPaths, path)
   195  			}
   196  		}
   197  		return nil
   198  	})
   199  	return r, nil
   200  }
   201  
   202  func (r *ChartRepository) saveIndexFile() error {
   203  	index, err := yaml.Marshal(r.IndexFile)
   204  	if err != nil {
   205  		return err
   206  	}
   207  	return ioutil.WriteFile(filepath.Join(r.RootPath, indexPath), index, 0644)
   208  }
   209  
   210  // Index generates an index for the chart repository and writes an index.yaml file.
   211  func (r *ChartRepository) Index() error {
   212  	err := r.generateIndex()
   213  	if err != nil {
   214  		return err
   215  	}
   216  	return r.saveIndexFile()
   217  }
   218  
   219  func (r *ChartRepository) generateIndex() error {
   220  	if r.IndexFile == nil {
   221  		r.IndexFile = NewIndexFile()
   222  	}
   223  
   224  	for _, path := range r.ChartPaths {
   225  		ch, err := chartutil.Load(path)
   226  		if err != nil {
   227  			return err
   228  		}
   229  
   230  		digest, err := provenance.DigestFile(path)
   231  		if err != nil {
   232  			return err
   233  		}
   234  
   235  		if !r.IndexFile.Has(ch.Metadata.Name, ch.Metadata.Version) {
   236  			r.IndexFile.Add(ch.Metadata, path, r.URL, digest)
   237  		}
   238  		// TODO: If a chart exists, but has a different Digest, should we error?
   239  	}
   240  	r.IndexFile.SortEntries()
   241  	return nil
   242  }