github.com/coreos/mantle@v0.13.0/platform/conf/conf.go (about)

     1  // Copyright 2016-2018 CoreOS, Inc.
     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 conf
    16  
    17  import (
    18  	"encoding/json"
    19  	"fmt"
    20  	"io/ioutil"
    21  	"net/url"
    22  	"os"
    23  	"reflect"
    24  	"strings"
    25  
    26  	ct "github.com/coreos/container-linux-config-transpiler/config"
    27  	cci "github.com/coreos/coreos-cloudinit/config"
    28  	ignerr "github.com/coreos/ignition/config/shared/errors"
    29  	v1 "github.com/coreos/ignition/config/v1"
    30  	v1types "github.com/coreos/ignition/config/v1/types"
    31  	v2 "github.com/coreos/ignition/config/v2_0"
    32  	v2types "github.com/coreos/ignition/config/v2_0/types"
    33  	v21 "github.com/coreos/ignition/config/v2_1"
    34  	v21types "github.com/coreos/ignition/config/v2_1/types"
    35  	v22 "github.com/coreos/ignition/config/v2_2"
    36  	v22types "github.com/coreos/ignition/config/v2_2/types"
    37  	v23 "github.com/coreos/ignition/config/v2_3"
    38  	v23types "github.com/coreos/ignition/config/v2_3/types"
    39  	ignvalidate "github.com/coreos/ignition/config/validate"
    40  	ign3err "github.com/coreos/ignition/v2/config/shared/errors"
    41  	v3 "github.com/coreos/ignition/v2/config/v3_0"
    42  	v3types "github.com/coreos/ignition/v2/config/v3_0/types"
    43  	ign3validate "github.com/coreos/ignition/v2/config/validate"
    44  	"github.com/coreos/pkg/capnslog"
    45  	"github.com/vincent-petithory/dataurl"
    46  	"golang.org/x/crypto/ssh/agent"
    47  )
    48  
    49  type kind int
    50  
    51  const (
    52  	kindEmpty kind = iota
    53  	kindCloudConfig
    54  	kindIgnition
    55  	kindContainerLinuxConfig
    56  	kindScript
    57  )
    58  
    59  var plog = capnslog.NewPackageLogger("github.com/coreos/mantle", "platform/conf")
    60  
    61  // UserData is an immutable, unvalidated configuration for a Container Linux
    62  // machine.
    63  type UserData struct {
    64  	kind      kind
    65  	data      string
    66  	extraKeys []*agent.Key // SSH keys to be injected during rendering
    67  }
    68  
    69  // Conf is a configuration for a Container Linux machine. It may be either a
    70  // coreos-cloudconfig or an ignition configuration.
    71  type Conf struct {
    72  	ignitionV1  *v1types.Config
    73  	ignitionV2  *v2types.Config
    74  	ignitionV21 *v21types.Config
    75  	ignitionV22 *v22types.Config
    76  	ignitionV23 *v23types.Config
    77  	ignitionV3  *v3types.Config
    78  	cloudconfig *cci.CloudConfig
    79  	script      string
    80  }
    81  
    82  func Empty() *UserData {
    83  	return &UserData{
    84  		kind: kindEmpty,
    85  	}
    86  }
    87  
    88  func ContainerLinuxConfig(data string) *UserData {
    89  	return &UserData{
    90  		kind: kindContainerLinuxConfig,
    91  		data: data,
    92  	}
    93  }
    94  
    95  func Ignition(data string) *UserData {
    96  	return &UserData{
    97  		kind: kindIgnition,
    98  		data: data,
    99  	}
   100  }
   101  
   102  func CloudConfig(data string) *UserData {
   103  	return &UserData{
   104  		kind: kindCloudConfig,
   105  		data: data,
   106  	}
   107  }
   108  
   109  func Script(data string) *UserData {
   110  	return &UserData{
   111  		kind: kindScript,
   112  		data: data,
   113  	}
   114  }
   115  
   116  func Unknown(data string) *UserData {
   117  	u := &UserData{
   118  		data: data,
   119  	}
   120  
   121  	_, _, err := v22.Parse([]byte(data))
   122  	switch err {
   123  	case ignerr.ErrEmpty:
   124  		u.kind = kindEmpty
   125  	case ignerr.ErrCloudConfig:
   126  		u.kind = kindCloudConfig
   127  	case ignerr.ErrScript:
   128  		u.kind = kindScript
   129  	default:
   130  		// Guess whether this is an Ignition config or a CLC.
   131  		// This treats an invalid Ignition config as a CLC, and a
   132  		// CLC in the JSON subset of YAML as an Ignition config.
   133  		var decoded interface{}
   134  		if err := json.Unmarshal([]byte(data), &decoded); err != nil {
   135  			u.kind = kindContainerLinuxConfig
   136  		} else {
   137  			u.kind = kindIgnition
   138  		}
   139  	}
   140  
   141  	return u
   142  }
   143  
   144  // Contains returns true if the UserData contains the specified string.
   145  func (u *UserData) Contains(substr string) bool {
   146  	return strings.Contains(u.data, substr)
   147  }
   148  
   149  // Performs a string substitution and returns a new UserData.
   150  func (u *UserData) Subst(old, new string) *UserData {
   151  	ret := *u
   152  	ret.data = strings.Replace(u.data, old, new, -1)
   153  	return &ret
   154  }
   155  
   156  // Adds an SSH key and returns a new UserData.
   157  func (u *UserData) AddKey(key agent.Key) *UserData {
   158  	ret := *u
   159  	ret.extraKeys = append(ret.extraKeys, &key)
   160  	return &ret
   161  }
   162  
   163  func (u *UserData) IsIgnitionCompatible() bool {
   164  	return u.kind == kindIgnition || u.kind == kindContainerLinuxConfig
   165  }
   166  
   167  // Render parses userdata and returns a new Conf. It returns an error if the
   168  // userdata can't be parsed.
   169  func (u *UserData) Render(ctPlatform string) (*Conf, error) {
   170  	c := &Conf{}
   171  
   172  	renderIgnition := func() error {
   173  		// Try each known version in turn.  Newer parsers will
   174  		// fall back to older ones, so try older versions first.
   175  		ignc1, report, err := v1.Parse([]byte(u.data))
   176  		if err == nil {
   177  			c.ignitionV1 = &ignc1
   178  			return nil
   179  		} else if err != ignerr.ErrUnknownVersion {
   180  			plog.Errorf("invalid userdata: %v", report)
   181  			return err
   182  		}
   183  
   184  		ignc2, report, err := v2.Parse([]byte(u.data))
   185  		if err == nil {
   186  			c.ignitionV2 = &ignc2
   187  			return nil
   188  		} else if err != ignerr.ErrUnknownVersion {
   189  			plog.Errorf("invalid userdata: %v", report)
   190  			return err
   191  		}
   192  
   193  		ignc21, report, err := v21.Parse([]byte(u.data))
   194  		if err == nil {
   195  			c.ignitionV21 = &ignc21
   196  			return nil
   197  		} else if err != ignerr.ErrUnknownVersion {
   198  			plog.Errorf("invalid userdata: %v", report)
   199  			return err
   200  		}
   201  
   202  		ignc22, report, err := v22.Parse([]byte(u.data))
   203  		if err == nil {
   204  			c.ignitionV22 = &ignc22
   205  			return nil
   206  		} else if err != ignerr.ErrUnknownVersion {
   207  			plog.Errorf("invalid userdata: %v", report)
   208  			return err
   209  		}
   210  
   211  		ignc23, report, err := v23.Parse([]byte(u.data))
   212  		if err == nil {
   213  			c.ignitionV23 = &ignc23
   214  			return nil
   215  		} else if err != ignerr.ErrUnknownVersion {
   216  			plog.Errorf("invalid userdata: %v", report)
   217  			return err
   218  		}
   219  
   220  		ignc3, report3, err := v3.Parse([]byte(u.data))
   221  		if err == nil {
   222  			c.ignitionV3 = &ignc3
   223  			return nil
   224  		} else if err != ign3err.ErrUnknownVersion {
   225  			plog.Errorf("invalid userdata: %v", report3)
   226  			return err
   227  		}
   228  
   229  		// give up
   230  		return err
   231  	}
   232  
   233  	switch u.kind {
   234  	case kindEmpty:
   235  		// empty, noop
   236  	case kindCloudConfig:
   237  		var err error
   238  		c.cloudconfig, err = cci.NewCloudConfig(u.data)
   239  		if err != nil {
   240  			return nil, err
   241  		}
   242  	case kindScript:
   243  		// pass through scripts unmodified, you are on your own.
   244  		c.script = u.data
   245  	case kindIgnition:
   246  		err := renderIgnition()
   247  		if err != nil {
   248  			return nil, err
   249  		}
   250  	case kindContainerLinuxConfig:
   251  		clc, ast, report := ct.Parse([]byte(u.data))
   252  		if report.IsFatal() {
   253  			return nil, fmt.Errorf("parsing Container Linux config: %s", report)
   254  		} else if len(report.Entries) > 0 {
   255  			plog.Warningf("parsing Container Linux config: %s", report)
   256  		}
   257  
   258  		ignc, report := ct.Convert(clc, ctPlatform, ast)
   259  		if report.IsFatal() {
   260  			return nil, fmt.Errorf("rendering Container Linux config for platform %q: %s", ctPlatform, report)
   261  		} else if len(report.Entries) > 0 {
   262  			plog.Warningf("rendering Container Linux config: %s", report)
   263  		}
   264  
   265  		c.ignitionV22 = &ignc
   266  	default:
   267  		panic("invalid kind")
   268  	}
   269  
   270  	if len(u.extraKeys) > 0 {
   271  		// not a no-op in the zero-key case
   272  		c.CopyKeys(u.extraKeys)
   273  	}
   274  
   275  	return c, nil
   276  }
   277  
   278  // String returns the string representation of the userdata in Conf.
   279  func (c *Conf) String() string {
   280  	if c.ignitionV1 != nil {
   281  		buf, _ := json.Marshal(c.ignitionV1)
   282  		return string(buf)
   283  	} else if c.ignitionV2 != nil {
   284  		buf, _ := json.Marshal(c.ignitionV2)
   285  		return string(buf)
   286  	} else if c.ignitionV21 != nil {
   287  		buf, _ := json.Marshal(c.ignitionV21)
   288  		return string(buf)
   289  	} else if c.ignitionV22 != nil {
   290  		buf, _ := json.Marshal(c.ignitionV22)
   291  		return string(buf)
   292  	} else if c.ignitionV23 != nil {
   293  		buf, _ := json.Marshal(c.ignitionV23)
   294  		return string(buf)
   295  	} else if c.ignitionV3 != nil {
   296  		buf, _ := json.Marshal(c.ignitionV3)
   297  		return string(buf)
   298  	} else if c.cloudconfig != nil {
   299  		return c.cloudconfig.String()
   300  	} else if c.script != "" {
   301  		return c.script
   302  	}
   303  
   304  	return ""
   305  }
   306  
   307  // MergeV3 merges a config with the ignitionV3 config via Ignition's merging function.
   308  func (c *Conf) MergeV3(newConfig v3types.Config) {
   309  	mergeConfig := v3.Merge(*c.ignitionV3, newConfig)
   310  	c.ignitionV3 = &mergeConfig
   311  }
   312  
   313  func (c *Conf) ValidConfig() bool {
   314  	if !c.IsIgnition() {
   315  		return false
   316  	}
   317  	val := c.getIgnitionValidateValue()
   318  	if c.ignitionV3 != nil {
   319  		rpt := ign3validate.ValidateWithContext(c.ignitionV3, nil)
   320  		return !rpt.IsFatal()
   321  	} else {
   322  		rpt := ignvalidate.ValidateWithoutSource(val)
   323  		return !rpt.IsFatal()
   324  	}
   325  }
   326  
   327  func (c *Conf) getIgnitionValidateValue() reflect.Value {
   328  	if c.ignitionV1 != nil {
   329  		return reflect.ValueOf(c.ignitionV1)
   330  	} else if c.ignitionV2 != nil {
   331  		return reflect.ValueOf(c.ignitionV2)
   332  	} else if c.ignitionV21 != nil {
   333  		return reflect.ValueOf(c.ignitionV21)
   334  	} else if c.ignitionV22 != nil {
   335  		return reflect.ValueOf(c.ignitionV22)
   336  	} else if c.ignitionV23 != nil {
   337  		return reflect.ValueOf(c.ignitionV23)
   338  	} else if c.ignitionV3 != nil {
   339  		return reflect.ValueOf(c.ignitionV3)
   340  	}
   341  	return reflect.ValueOf(nil)
   342  }
   343  
   344  // WriteFile writes the userdata in Conf to a local file.
   345  func (c *Conf) WriteFile(name string) error {
   346  	return ioutil.WriteFile(name, []byte(c.String()), 0666)
   347  }
   348  
   349  // Bytes returns the serialized userdata in Conf.
   350  func (c *Conf) Bytes() []byte {
   351  	return []byte(c.String())
   352  }
   353  
   354  func (c *Conf) addFileV2(path, filesystem, contents string, mode int) {
   355  	u, err := url.Parse(dataurl.EncodeBytes([]byte(contents)))
   356  	if err != nil {
   357  		plog.Warningf("parsing dataurl contents: %v", err)
   358  		return
   359  	}
   360  	c.ignitionV2.Storage.Files = append(c.ignitionV2.Storage.Files, v2types.File{
   361  		Filesystem: filesystem,
   362  		Path:       v2types.Path(path),
   363  		Contents: v2types.FileContents{
   364  			Source: v2types.Url(*u),
   365  		},
   366  		Mode: v2types.FileMode(os.FileMode(mode)),
   367  	})
   368  }
   369  
   370  func (c *Conf) addFileV21(path, filesystem, contents string, mode int) {
   371  	c.ignitionV21.Storage.Files = append(c.ignitionV21.Storage.Files, v21types.File{
   372  		Node: v21types.Node{
   373  			Filesystem: filesystem,
   374  			Path:       path,
   375  		},
   376  		FileEmbedded1: v21types.FileEmbedded1{
   377  			Contents: v21types.FileContents{
   378  				Source: dataurl.EncodeBytes([]byte(contents)),
   379  			},
   380  			Mode: mode,
   381  		},
   382  	})
   383  }
   384  
   385  func (c *Conf) addFileV22(path, filesystem, contents string, mode int) {
   386  	c.ignitionV22.Storage.Files = append(c.ignitionV22.Storage.Files, v22types.File{
   387  		Node: v22types.Node{
   388  			Filesystem: filesystem,
   389  			Path:       path,
   390  		},
   391  		FileEmbedded1: v22types.FileEmbedded1{
   392  			Contents: v22types.FileContents{
   393  				Source: dataurl.EncodeBytes([]byte(contents)),
   394  			},
   395  			Mode: &mode,
   396  		},
   397  	})
   398  }
   399  
   400  func (c *Conf) addFileV23(path, filesystem, contents string, mode int) {
   401  	c.ignitionV23.Storage.Files = append(c.ignitionV23.Storage.Files, v23types.File{
   402  		Node: v23types.Node{
   403  			Filesystem: filesystem,
   404  			Path:       path,
   405  		},
   406  		FileEmbedded1: v23types.FileEmbedded1{
   407  			Contents: v23types.FileContents{
   408  				Source: dataurl.EncodeBytes([]byte(contents)),
   409  			},
   410  			Mode: &mode,
   411  		},
   412  	})
   413  }
   414  
   415  func (c *Conf) addFileV3(path, filesystem, contents string, mode int) {
   416  	source := dataurl.EncodeBytes([]byte(contents))
   417  	newConfig := v3types.Config{
   418  		Ignition: v3types.Ignition{
   419  			Version: "3.0.0",
   420  		},
   421  		Storage: v3types.Storage{
   422  			Files: []v3types.File{
   423  				{
   424  					Node: v3types.Node{
   425  						Path: path,
   426  					},
   427  					FileEmbedded1: v3types.FileEmbedded1{
   428  						Contents: v3types.FileContents{
   429  							Source: &source,
   430  						},
   431  						Mode: &mode,
   432  					},
   433  				},
   434  			},
   435  		},
   436  	}
   437  	c.MergeV3(newConfig)
   438  }
   439  
   440  func (c *Conf) AddFile(path, filesystem, contents string, mode int) {
   441  	if c.ignitionV3 != nil {
   442  		c.addFileV3(path, filesystem, contents, mode)
   443  	} else if c.ignitionV2 != nil {
   444  		c.addFileV2(path, filesystem, contents, mode)
   445  	} else if c.ignitionV21 != nil {
   446  		c.addFileV21(path, filesystem, contents, mode)
   447  	} else if c.ignitionV22 != nil {
   448  		c.addFileV22(path, filesystem, contents, mode)
   449  	} else if c.ignitionV23 != nil {
   450  		c.addFileV23(path, filesystem, contents, mode)
   451  	} else if c.ignitionV1 != nil || c.cloudconfig != nil {
   452  		panic("conf: AddFile does not support ignition v1 or cloudconfig")
   453  	}
   454  }
   455  
   456  func (c *Conf) addSystemdUnitV1(name, contents string, enable bool) {
   457  	c.ignitionV1.Systemd.Units = append(c.ignitionV1.Systemd.Units, v1types.SystemdUnit{
   458  		Name:     v1types.SystemdUnitName(name),
   459  		Contents: contents,
   460  		Enable:   enable,
   461  	})
   462  }
   463  
   464  func (c *Conf) addSystemdUnitV2(name, contents string, enable bool) {
   465  	c.ignitionV2.Systemd.Units = append(c.ignitionV2.Systemd.Units, v2types.SystemdUnit{
   466  		Name:     v2types.SystemdUnitName(name),
   467  		Contents: contents,
   468  		Enable:   enable,
   469  	})
   470  }
   471  
   472  func (c *Conf) addSystemdUnitV21(name, contents string, enable bool) {
   473  	c.ignitionV21.Systemd.Units = append(c.ignitionV21.Systemd.Units, v21types.Unit{
   474  		Name:     name,
   475  		Contents: contents,
   476  		Enabled:  &enable,
   477  	})
   478  }
   479  
   480  func (c *Conf) addSystemdUnitV22(name, contents string, enable bool) {
   481  	c.ignitionV22.Systemd.Units = append(c.ignitionV22.Systemd.Units, v22types.Unit{
   482  		Name:     name,
   483  		Contents: contents,
   484  		Enabled:  &enable,
   485  	})
   486  }
   487  
   488  func (c *Conf) addSystemdUnitV23(name, contents string, enable bool) {
   489  	c.ignitionV23.Systemd.Units = append(c.ignitionV23.Systemd.Units, v23types.Unit{
   490  		Name:     name,
   491  		Contents: contents,
   492  		Enabled:  &enable,
   493  	})
   494  }
   495  
   496  func (c *Conf) addSystemdUnitV3(name, contents string, enable bool) {
   497  	newConfig := v3types.Config{
   498  		Ignition: v3types.Ignition{
   499  			Version: "3.0.0",
   500  		},
   501  		Systemd: v3types.Systemd{
   502  			Units: []v3types.Unit{
   503  				{
   504  					Name:     name,
   505  					Contents: &contents,
   506  					Enabled:  &enable,
   507  				},
   508  			},
   509  		},
   510  	}
   511  	c.MergeV3(newConfig)
   512  }
   513  
   514  func (c *Conf) addSystemdUnitCloudConfig(name, contents string, enable bool) {
   515  	c.cloudconfig.CoreOS.Units = append(c.cloudconfig.CoreOS.Units, cci.Unit{
   516  		Name:    name,
   517  		Content: contents,
   518  		Enable:  enable,
   519  	})
   520  }
   521  
   522  func (c *Conf) AddSystemdUnit(name, contents string, enable bool) {
   523  	if c.ignitionV1 != nil {
   524  		c.addSystemdUnitV1(name, contents, enable)
   525  	} else if c.ignitionV2 != nil {
   526  		c.addSystemdUnitV2(name, contents, enable)
   527  	} else if c.ignitionV21 != nil {
   528  		c.addSystemdUnitV21(name, contents, enable)
   529  	} else if c.ignitionV22 != nil {
   530  		c.addSystemdUnitV22(name, contents, enable)
   531  	} else if c.ignitionV23 != nil {
   532  		c.addSystemdUnitV23(name, contents, enable)
   533  	} else if c.ignitionV3 != nil {
   534  		c.addSystemdUnitV3(name, contents, enable)
   535  	} else if c.cloudconfig != nil {
   536  		c.addSystemdUnitCloudConfig(name, contents, enable)
   537  	}
   538  }
   539  
   540  func (c *Conf) addSystemdDropinV1(service, name, contents string) {
   541  	for i, unit := range c.ignitionV1.Systemd.Units {
   542  		if unit.Name == v1types.SystemdUnitName(service) {
   543  			unit.DropIns = append(unit.DropIns, v1types.SystemdUnitDropIn{
   544  				Name:     v1types.SystemdUnitDropInName(name),
   545  				Contents: contents,
   546  			})
   547  			c.ignitionV1.Systemd.Units[i] = unit
   548  			return
   549  		}
   550  	}
   551  	c.ignitionV1.Systemd.Units = append(c.ignitionV1.Systemd.Units, v1types.SystemdUnit{
   552  		Name: v1types.SystemdUnitName(service),
   553  		DropIns: []v1types.SystemdUnitDropIn{
   554  			{
   555  				Name:     v1types.SystemdUnitDropInName(name),
   556  				Contents: contents,
   557  			},
   558  		},
   559  	})
   560  }
   561  
   562  func (c *Conf) addSystemdDropinV2(service, name, contents string) {
   563  	for i, unit := range c.ignitionV2.Systemd.Units {
   564  		if unit.Name == v2types.SystemdUnitName(service) {
   565  			unit.DropIns = append(unit.DropIns, v2types.SystemdUnitDropIn{
   566  				Name:     v2types.SystemdUnitDropInName(name),
   567  				Contents: contents,
   568  			})
   569  			c.ignitionV2.Systemd.Units[i] = unit
   570  			return
   571  		}
   572  	}
   573  	c.ignitionV2.Systemd.Units = append(c.ignitionV2.Systemd.Units, v2types.SystemdUnit{
   574  		Name: v2types.SystemdUnitName(service),
   575  		DropIns: []v2types.SystemdUnitDropIn{
   576  			{
   577  				Name:     v2types.SystemdUnitDropInName(name),
   578  				Contents: contents,
   579  			},
   580  		},
   581  	})
   582  }
   583  
   584  func (c *Conf) addSystemdDropinV21(service, name, contents string) {
   585  	for i, unit := range c.ignitionV21.Systemd.Units {
   586  		if unit.Name == service {
   587  			unit.Dropins = append(unit.Dropins, v21types.Dropin{
   588  				Name:     name,
   589  				Contents: contents,
   590  			})
   591  			c.ignitionV21.Systemd.Units[i] = unit
   592  			return
   593  		}
   594  	}
   595  	c.ignitionV21.Systemd.Units = append(c.ignitionV21.Systemd.Units, v21types.Unit{
   596  		Name: service,
   597  		Dropins: []v21types.Dropin{
   598  			{
   599  				Name:     name,
   600  				Contents: contents,
   601  			},
   602  		},
   603  	})
   604  }
   605  
   606  func (c *Conf) addSystemdDropinV22(service, name, contents string) {
   607  	for i, unit := range c.ignitionV22.Systemd.Units {
   608  		if unit.Name == service {
   609  			unit.Dropins = append(unit.Dropins, v22types.SystemdDropin{
   610  				Name:     name,
   611  				Contents: contents,
   612  			})
   613  			c.ignitionV22.Systemd.Units[i] = unit
   614  			return
   615  		}
   616  	}
   617  	c.ignitionV22.Systemd.Units = append(c.ignitionV22.Systemd.Units, v22types.Unit{
   618  		Name: service,
   619  		Dropins: []v22types.SystemdDropin{
   620  			{
   621  				Name:     name,
   622  				Contents: contents,
   623  			},
   624  		},
   625  	})
   626  }
   627  
   628  func (c *Conf) addSystemdDropinV23(service, name, contents string) {
   629  	for i, unit := range c.ignitionV23.Systemd.Units {
   630  		if unit.Name == service {
   631  			unit.Dropins = append(unit.Dropins, v23types.SystemdDropin{
   632  				Name:     name,
   633  				Contents: contents,
   634  			})
   635  			c.ignitionV23.Systemd.Units[i] = unit
   636  			return
   637  		}
   638  	}
   639  	c.ignitionV23.Systemd.Units = append(c.ignitionV23.Systemd.Units, v23types.Unit{
   640  		Name: service,
   641  		Dropins: []v23types.SystemdDropin{
   642  			{
   643  				Name:     name,
   644  				Contents: contents,
   645  			},
   646  		},
   647  	})
   648  }
   649  
   650  func (c *Conf) addSystemdDropinV3(service, name, contents string) {
   651  	newConfig := v3types.Config{
   652  		Ignition: v3types.Ignition{
   653  			Version: "3.0.0",
   654  		},
   655  		Systemd: v3types.Systemd{
   656  			Units: []v3types.Unit{
   657  				{
   658  					Name: service,
   659  					Dropins: []v3types.Dropin{
   660  						{
   661  							Name:     name,
   662  							Contents: &contents,
   663  						},
   664  					},
   665  				},
   666  			},
   667  		},
   668  	}
   669  	c.MergeV3(newConfig)
   670  }
   671  
   672  func (c *Conf) addSystemdDropinCloudConfig(service, name, contents string) {
   673  	for i, unit := range c.cloudconfig.CoreOS.Units {
   674  		if unit.Name == service {
   675  			unit.DropIns = append(unit.DropIns, cci.UnitDropIn{
   676  				Name:    name,
   677  				Content: contents,
   678  			})
   679  			c.cloudconfig.CoreOS.Units[i] = unit
   680  			return
   681  		}
   682  	}
   683  	c.cloudconfig.CoreOS.Units = append(c.cloudconfig.CoreOS.Units, cci.Unit{
   684  		Name: service,
   685  		DropIns: []cci.UnitDropIn{
   686  			{
   687  				Name:    name,
   688  				Content: contents,
   689  			},
   690  		},
   691  	})
   692  }
   693  
   694  func (c *Conf) AddSystemdUnitDropin(service, name, contents string) {
   695  	if c.ignitionV1 != nil {
   696  		c.addSystemdDropinV1(service, name, contents)
   697  	} else if c.ignitionV2 != nil {
   698  		c.addSystemdDropinV2(service, name, contents)
   699  	} else if c.ignitionV21 != nil {
   700  		c.addSystemdDropinV21(service, name, contents)
   701  	} else if c.ignitionV22 != nil {
   702  		c.addSystemdDropinV22(service, name, contents)
   703  	} else if c.ignitionV23 != nil {
   704  		c.addSystemdDropinV23(service, name, contents)
   705  	} else if c.ignitionV3 != nil {
   706  		c.addSystemdDropinV3(service, name, contents)
   707  	} else if c.cloudconfig != nil {
   708  		c.addSystemdDropinCloudConfig(service, name, contents)
   709  	}
   710  }
   711  
   712  func (c *Conf) copyKeysIgnitionV1(keys []*agent.Key) {
   713  	keyStrs := keysToStrings(keys)
   714  	for i := range c.ignitionV1.Passwd.Users {
   715  		user := &c.ignitionV1.Passwd.Users[i]
   716  		if user.Name == "core" {
   717  			user.SSHAuthorizedKeys = append(user.SSHAuthorizedKeys, keyStrs...)
   718  			return
   719  		}
   720  	}
   721  	c.ignitionV1.Passwd.Users = append(c.ignitionV1.Passwd.Users, v1types.User{
   722  		Name:              "core",
   723  		SSHAuthorizedKeys: keyStrs,
   724  	})
   725  }
   726  
   727  func (c *Conf) copyKeysIgnitionV2(keys []*agent.Key) {
   728  	keyStrs := keysToStrings(keys)
   729  	for i := range c.ignitionV2.Passwd.Users {
   730  		user := &c.ignitionV2.Passwd.Users[i]
   731  		if user.Name == "core" {
   732  			user.SSHAuthorizedKeys = append(user.SSHAuthorizedKeys, keyStrs...)
   733  			return
   734  		}
   735  	}
   736  	c.ignitionV2.Passwd.Users = append(c.ignitionV2.Passwd.Users, v2types.User{
   737  		Name:              "core",
   738  		SSHAuthorizedKeys: keyStrs,
   739  	})
   740  }
   741  
   742  func (c *Conf) copyKeysIgnitionV21(keys []*agent.Key) {
   743  	var keyObjs []v21types.SSHAuthorizedKey
   744  	for _, key := range keys {
   745  		keyObjs = append(keyObjs, v21types.SSHAuthorizedKey(key.String()))
   746  	}
   747  	for i := range c.ignitionV21.Passwd.Users {
   748  		user := &c.ignitionV21.Passwd.Users[i]
   749  		if user.Name == "core" {
   750  			user.SSHAuthorizedKeys = append(user.SSHAuthorizedKeys, keyObjs...)
   751  			return
   752  		}
   753  	}
   754  	c.ignitionV21.Passwd.Users = append(c.ignitionV21.Passwd.Users, v21types.PasswdUser{
   755  		Name:              "core",
   756  		SSHAuthorizedKeys: keyObjs,
   757  	})
   758  }
   759  
   760  func (c *Conf) copyKeysIgnitionV22(keys []*agent.Key) {
   761  	var keyObjs []v22types.SSHAuthorizedKey
   762  	for _, key := range keys {
   763  		keyObjs = append(keyObjs, v22types.SSHAuthorizedKey(key.String()))
   764  	}
   765  	for i := range c.ignitionV22.Passwd.Users {
   766  		user := &c.ignitionV22.Passwd.Users[i]
   767  		if user.Name == "core" {
   768  			user.SSHAuthorizedKeys = append(user.SSHAuthorizedKeys, keyObjs...)
   769  			return
   770  		}
   771  	}
   772  	c.ignitionV22.Passwd.Users = append(c.ignitionV22.Passwd.Users, v22types.PasswdUser{
   773  		Name:              "core",
   774  		SSHAuthorizedKeys: keyObjs,
   775  	})
   776  }
   777  
   778  func (c *Conf) copyKeysIgnitionV23(keys []*agent.Key) {
   779  	var keyObjs []v23types.SSHAuthorizedKey
   780  	for _, key := range keys {
   781  		keyObjs = append(keyObjs, v23types.SSHAuthorizedKey(key.String()))
   782  	}
   783  	for i := range c.ignitionV23.Passwd.Users {
   784  		user := &c.ignitionV23.Passwd.Users[i]
   785  		if user.Name == "core" {
   786  			user.SSHAuthorizedKeys = append(user.SSHAuthorizedKeys, keyObjs...)
   787  			return
   788  		}
   789  	}
   790  	c.ignitionV23.Passwd.Users = append(c.ignitionV23.Passwd.Users, v23types.PasswdUser{
   791  		Name:              "core",
   792  		SSHAuthorizedKeys: keyObjs,
   793  	})
   794  }
   795  
   796  func (c *Conf) copyKeysIgnitionV3(keys []*agent.Key) {
   797  	var keyObjs []v3types.SSHAuthorizedKey
   798  	for _, key := range keys {
   799  		keyObjs = append(keyObjs, v3types.SSHAuthorizedKey(key.String()))
   800  	}
   801  	newConfig := v3types.Config{
   802  		Ignition: v3types.Ignition{
   803  			Version: "3.0.0",
   804  		},
   805  		Passwd: v3types.Passwd{
   806  			Users: []v3types.PasswdUser{
   807  				{
   808  					Name:              "core",
   809  					SSHAuthorizedKeys: keyObjs,
   810  				},
   811  			},
   812  		},
   813  	}
   814  	c.MergeV3(newConfig)
   815  }
   816  
   817  func (c *Conf) copyKeysCloudConfig(keys []*agent.Key) {
   818  	c.cloudconfig.SSHAuthorizedKeys = append(c.cloudconfig.SSHAuthorizedKeys, keysToStrings(keys)...)
   819  }
   820  
   821  func (c *Conf) copyKeysScript(keys []*agent.Key) {
   822  	keyString := strings.Join(keysToStrings(keys), "\n")
   823  	c.script = strings.Replace(c.script, "@SSH_KEYS@", keyString, -1)
   824  }
   825  
   826  // CopyKeys copies public keys from agent ag into the configuration to the
   827  // appropriate configuration section for the core user.
   828  func (c *Conf) CopyKeys(keys []*agent.Key) {
   829  	if c.ignitionV1 != nil {
   830  		c.copyKeysIgnitionV1(keys)
   831  	} else if c.ignitionV2 != nil {
   832  		c.copyKeysIgnitionV2(keys)
   833  	} else if c.ignitionV21 != nil {
   834  		c.copyKeysIgnitionV21(keys)
   835  	} else if c.ignitionV22 != nil {
   836  		c.copyKeysIgnitionV22(keys)
   837  	} else if c.ignitionV23 != nil {
   838  		c.copyKeysIgnitionV23(keys)
   839  	} else if c.ignitionV3 != nil {
   840  		c.copyKeysIgnitionV3(keys)
   841  	} else if c.cloudconfig != nil {
   842  		c.copyKeysCloudConfig(keys)
   843  	} else if c.script != "" {
   844  		c.copyKeysScript(keys)
   845  	}
   846  }
   847  
   848  func keysToStrings(keys []*agent.Key) (keyStrs []string) {
   849  	for _, key := range keys {
   850  		keyStrs = append(keyStrs, key.String())
   851  	}
   852  	return
   853  }
   854  
   855  // IsIgnition returns true if the config is for Ignition.
   856  // Returns false in the case of empty configs as on most platforms,
   857  // this will default back to cloudconfig
   858  func (c *Conf) IsIgnition() bool {
   859  	return c.ignitionV1 != nil || c.ignitionV2 != nil || c.ignitionV21 != nil || c.ignitionV22 != nil || c.ignitionV23 != nil || c.ignitionV3 != nil
   860  }
   861  
   862  func (c *Conf) IsEmpty() bool {
   863  	return !c.IsIgnition() && c.cloudconfig == nil && c.script == ""
   864  }