github.com/david-imola/snapd@v0.0.0-20210611180407-2de8ddeece6d/seed/internal/options20.go (about)

     1  // -*- Mode: Go; indent-tabs-mode: t -*-
     2  
     3  /*
     4   * Copyright (C) 2019 Canonical Ltd
     5   *
     6   * This program is free software: you can redistribute it and/or modify
     7   * it under the terms of the GNU General Public License version 3 as
     8   * published by the Free Software Foundation.
     9   *
    10   * This program is distributed in the hope that it will be useful,
    11   * but WITHOUT ANY WARRANTY; without even the implied warranty of
    12   * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    13   * GNU General Public License for more details.
    14   *
    15   * You should have received a copy of the GNU General Public License
    16   * along with this program.  If not, see <http://www.gnu.org/licenses/>.
    17   *
    18   */
    19  
    20  package internal
    21  
    22  import (
    23  	"fmt"
    24  	"io/ioutil"
    25  	"strings"
    26  
    27  	"gopkg.in/yaml.v2"
    28  
    29  	"github.com/snapcore/snapd/osutil"
    30  	"github.com/snapcore/snapd/snap/channel"
    31  	"github.com/snapcore/snapd/snap/naming"
    32  )
    33  
    34  // Snap20 carries options for a model snap or an extra snap
    35  // in grade: dangerous.
    36  type Snap20 struct {
    37  	Name string `yaml:"name"`
    38  	// id and unasserted can be both set, in which case it only
    39  	// cross-references the model
    40  	SnapID string `yaml:"id,omitempty"`
    41  
    42  	// Unasserted has the filename for an unasserted local snap
    43  	Unasserted string `yaml:"unasserted,omitempty"`
    44  
    45  	Channel string `yaml:"channel,omitempty"`
    46  	// TODO: DevMode bool   `yaml:"devmode,omitempty"`
    47  }
    48  
    49  // SnapName implements naming.SnapRef.
    50  func (sn *Snap20) SnapName() string {
    51  	return sn.Name
    52  }
    53  
    54  // ID implements naming.SnapRef.
    55  func (sn *Snap20) ID() string {
    56  	return sn.SnapID
    57  }
    58  
    59  type Options20 struct {
    60  	Snaps []*Snap20 `yaml:"snaps"`
    61  }
    62  
    63  func ReadOptions20(optionsFn string) (*Options20, error) {
    64  	errPrefix := "cannot read grade dangerous options yaml"
    65  
    66  	yamlData, err := ioutil.ReadFile(optionsFn)
    67  	if err != nil {
    68  		return nil, fmt.Errorf("%s: %v", errPrefix, err)
    69  	}
    70  
    71  	var options Options20
    72  	if err := yaml.Unmarshal(yamlData, &options); err != nil {
    73  		return nil, fmt.Errorf("%s: cannot unmarshal %q: %s", errPrefix, yamlData, err)
    74  	}
    75  
    76  	seenNames := make(map[string]bool, len(options.Snaps))
    77  	// validate
    78  	for _, sn := range options.Snaps {
    79  		if sn == nil {
    80  			return nil, fmt.Errorf("%s: empty snaps element", errPrefix)
    81  		}
    82  		// TODO: check if it's a parallel install explicitly,
    83  		// need to move *Instance* helpers from snap to naming
    84  		if err := naming.ValidateSnap(sn.Name); err != nil {
    85  			return nil, fmt.Errorf("%s: %v", errPrefix, err)
    86  		}
    87  		if sn.SnapID == "" && sn.Channel == "" && sn.Unasserted == "" {
    88  			return nil, fmt.Errorf("%s: at least one of id, channel or unasserted must be set for snap %q", errPrefix, sn.Name)
    89  		}
    90  		if sn.SnapID != "" {
    91  			if err := naming.ValidateSnapID(sn.SnapID); err != nil {
    92  				return nil, fmt.Errorf("%s: %v", errPrefix, err)
    93  			}
    94  		}
    95  		if sn.Channel != "" {
    96  			if _, err := channel.Parse(sn.Channel, ""); err != nil {
    97  				return nil, fmt.Errorf("%s: %v", errPrefix, err)
    98  			}
    99  		}
   100  		if sn.Unasserted != "" && strings.Contains(sn.Unasserted, "/") {
   101  			return nil, fmt.Errorf("%s: %q must be a filename, not a path", errPrefix, sn.Unasserted)
   102  		}
   103  
   104  		// make sure names and file names are unique
   105  		if seenNames[sn.Name] {
   106  			return nil, fmt.Errorf("%s: snap name %q must be unique", errPrefix, sn.Name)
   107  		}
   108  		seenNames[sn.Name] = true
   109  	}
   110  
   111  	return &options, nil
   112  }
   113  
   114  func (options *Options20) Write(optionsFn string) error {
   115  	data, err := yaml.Marshal(options)
   116  	if err != nil {
   117  		return err
   118  	}
   119  	if err := osutil.AtomicWriteFile(optionsFn, data, 0644, 0); err != nil {
   120  		return err
   121  	}
   122  	return nil
   123  }