Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Allow passing in extra user-data to cloud-init #3633

Merged
merged 10 commits into from
Nov 10, 2017
Merged
40 changes: 30 additions & 10 deletions cmd/kops/integration_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,11 @@ func TestMinimalCloudformation(t *testing.T) {
runTestCloudformation(t, "minimal.example.com", "minimal-cloudformation", "v1alpha2", false)
}

// TestAdditionalUserData runs the test on passing additional user-data to an instance at bootstrap.
func TestAdditionalUserData(t *testing.T) {
runTestCloudformation(t, "additionaluserdata.example.com", "additional_user-data", "v1alpha2", false)
}

// TestMinimal_141 runs the test on a configuration from 1.4.1 release
func TestMinimal_141(t *testing.T) {
runTestAWS(t, "minimal-141.example.com", "minimal-141", "v1alpha0", false, 1)
Expand Down Expand Up @@ -508,21 +513,36 @@ func runTestCloudformation(t *testing.T, clusterName string, srcDir string, vers
t.Fatalf("cloudformation output differed from expected")
}

actualExtracted, err := yaml.Marshal(extracted)
if err != nil {
t.Fatalf("unexpected error serializing extracted values: %v", err)
}
expectedExtracted, err := ioutil.ReadFile(path.Join(srcDir, expectedCfPath+".extracted.yaml"))
if err != nil {
t.Fatalf("unexpected error reading expected extracted cloudformation output: %v", err)
}

actualExtractedTrimmed := strings.TrimSpace(string(actualExtracted))
expectedExtractedTrimmed := strings.TrimSpace(string(expectedExtracted))
if actualExtractedTrimmed != expectedExtractedTrimmed {
diffString := diff.FormatDiff(actualExtractedTrimmed, expectedExtractedTrimmed)
t.Logf("diff:\n%s\n", diffString)
t.Fatalf("cloudformation output differed from expected")
expected := make(map[string]string)
err = yaml.Unmarshal(expectedExtracted, &expected)
if err != nil {
t.Fatalf("unexpected error unmarshal expected extracted cloudformation output: %v", err)
}

if len(extracted) != len(expected) {
t.Fatalf("error differed number of cloudformation in expected and extracted: %v", err)
}

for key, expectedValue := range expected {
extractedValue, ok := extracted[key]
if !ok {
t.Fatalf("unexpected error expected cloudformation not found for k: %v", key)
}

// Strip cariage return as expectedValue is stored in a yaml string literal
// and golang will automaticaly strip CR from any string literal
extractedValueTrimmed := strings.Replace(extractedValue, "\r", "", -1)
if expectedValue != extractedValueTrimmed {

diffString := diff.FormatDiff(expectedValue, extractedValueTrimmed)
t.Logf("diff for key %s:\n%s\n\n\n\n\n\n", key, diffString)
t.Fatalf("cloudformation output differed from expected")
}
}
}
}
Expand Down
29 changes: 29 additions & 0 deletions docs/instance_groups.md
Original file line number Diff line number Diff line change
Expand Up @@ -213,3 +213,32 @@ EBS-Optimized instances can be created by setting the following field:
spec:
rootVolumeOptimization: true
```

## Additional user-data for cloud-init

Kops utilizes cloud-init to initialize and setup a host at boot time. However in certain cases you may already be leaveraging certain features of cloud-init in your infrastructure and would like to continue doing so. More information on cloud-init can be found [here](http://cloudinit.readthedocs.io/en/latest/)


Aditional user-user data can be passed to the host provisioning by setting the `ExtraUserData` field. A list of valid user-data content-types can be found [here](http://cloudinit.readthedocs.io/en/latest/topics/format.html#mime-multi-part-archive)

Example:
```
spec:
extraUserData:
- name: myscript.sh
type: text/x-shellscript
content: |
#!/bin/sh
echo "Hello World. The time is now $(date -R)!" | tee /root/output.txt
- name: local_repo.txt
type: text/cloud-config
content: |
#cloud-config
apt:
primary:
- arches: [default]
uri: http://local-mirror.mydomain
search:
- http://local-mirror.mydomain
- http://archive.ubuntu.com
```
12 changes: 12 additions & 0 deletions pkg/apis/kops/instancegroup.go
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,18 @@ type InstanceGroupSpec struct {
Kubelet *KubeletConfigSpec `json:"kubelet,omitempty"`
// Taints indicates the kubernetes taints for nodes in this group
Taints []string `json:"taints,omitempty"`
// AdditionalUserData is any aditional user-data to be passed to the host
AdditionalUserData []UserData `json:"additionalUserData,omitempty"`
}

// UserData defines a user-data section
type UserData struct {
// Name is the name of the user-data
Name string `json:"name,omitempty"`
// Type is the type of user-data
Type string `json:"type,omitempty"`
// Content is the user-data content
Content string `json:"content,omitempty"`
}

// PerformAssignmentsInstanceGroups populates InstanceGroups with default values
Expand Down
12 changes: 12 additions & 0 deletions pkg/apis/kops/v1alpha1/instancegroup.go
Original file line number Diff line number Diff line change
Expand Up @@ -89,7 +89,19 @@ type InstanceGroupSpec struct {
Kubelet *KubeletConfigSpec `json:"kubelet,omitempty"`
// Taints indicates the kubernetes taints for nodes in this group
Taints []string `json:"taints,omitempty"`
// AdditionalUserData is any aditional user-data to be passed to the host
AdditionalUserData []UserData `json:"additionalUserData,omitempty"`
// Zones is the names of the Zones where machines in this instance group should be placed
// This is needed for regional subnets (e.g. GCE), to restrict placement to particular zones
Zones []string `json:"zones,omitempty"`
}

// UserData defines a user-data section
type UserData struct {
// Name is the name of the user-data
Name string `json:"name,omitempty"`
// Type is the type of user-data
Type string `json:"type,omitempty"`
// Content is the user-data content
Content string `json:"content,omitempty"`
}
52 changes: 50 additions & 2 deletions pkg/apis/kops/v1alpha1/zz_generated.conversion.go
Original file line number Diff line number Diff line change
Expand Up @@ -139,6 +139,8 @@ func RegisterConversions(scheme *runtime.Scheme) error {
Convert_kops_SSHCredentialList_To_v1alpha1_SSHCredentialList,
Convert_v1alpha1_SSHCredentialSpec_To_kops_SSHCredentialSpec,
Convert_kops_SSHCredentialSpec_To_v1alpha1_SSHCredentialSpec,
Convert_v1alpha1_UserData_To_kops_UserData,
Convert_kops_UserData_To_v1alpha1_UserData,
Convert_v1alpha1_WeaveNetworkingSpec_To_kops_WeaveNetworkingSpec,
Convert_kops_WeaveNetworkingSpec_To_v1alpha1_WeaveNetworkingSpec,
)
Expand Down Expand Up @@ -587,7 +589,6 @@ func autoConvert_v1alpha1_ClusterSpec_To_kops_ClusterSpec(in *ClusterSpec, out *
out.Project = in.Project
out.MasterPublicName = in.MasterPublicName
out.MasterInternalName = in.MasterInternalName
out.AdditionalSANs = in.AdditionalSANs
out.NetworkCIDR = in.NetworkCIDR
out.NetworkID = in.NetworkID
if in.Topology != nil {
Expand All @@ -603,6 +604,7 @@ func autoConvert_v1alpha1_ClusterSpec_To_kops_ClusterSpec(in *ClusterSpec, out *
out.KeyStore = in.KeyStore
out.ConfigStore = in.ConfigStore
out.DNSZone = in.DNSZone
out.AdditionalSANs = in.AdditionalSANs
out.ClusterDNSDomain = in.ClusterDNSDomain
// WARNING: in.Multizone requires manual conversion: does not exist in peer-type
out.ServiceClusterIPRange = in.ServiceClusterIPRange
Expand Down Expand Up @@ -822,7 +824,6 @@ func autoConvert_kops_ClusterSpec_To_v1alpha1_ClusterSpec(in *kops.ClusterSpec,
out.Project = in.Project
out.MasterPublicName = in.MasterPublicName
out.MasterInternalName = in.MasterInternalName
out.AdditionalSANs = in.AdditionalSANs
out.NetworkCIDR = in.NetworkCIDR
out.NetworkID = in.NetworkID
if in.Topology != nil {
Expand All @@ -838,6 +839,7 @@ func autoConvert_kops_ClusterSpec_To_v1alpha1_ClusterSpec(in *kops.ClusterSpec,
out.KeyStore = in.KeyStore
out.ConfigStore = in.ConfigStore
out.DNSZone = in.DNSZone
out.AdditionalSANs = in.AdditionalSANs
out.ClusterDNSDomain = in.ClusterDNSDomain
out.ServiceClusterIPRange = in.ServiceClusterIPRange
out.NonMasqueradeCIDR = in.NonMasqueradeCIDR
Expand Down Expand Up @@ -1681,6 +1683,17 @@ func autoConvert_v1alpha1_InstanceGroupSpec_To_kops_InstanceGroupSpec(in *Instan
out.Kubelet = nil
}
out.Taints = in.Taints
if in.AdditionalUserData != nil {
in, out := &in.AdditionalUserData, &out.AdditionalUserData
*out = make([]kops.UserData, len(*in))
for i := range *in {
if err := Convert_v1alpha1_UserData_To_kops_UserData(&(*in)[i], &(*out)[i], s); err != nil {
return err
}
}
} else {
out.AdditionalUserData = nil
}
out.Zones = in.Zones
return nil
}
Expand Down Expand Up @@ -1735,6 +1748,17 @@ func autoConvert_kops_InstanceGroupSpec_To_v1alpha1_InstanceGroupSpec(in *kops.I
out.Kubelet = nil
}
out.Taints = in.Taints
if in.AdditionalUserData != nil {
in, out := &in.AdditionalUserData, &out.AdditionalUserData
*out = make([]UserData, len(*in))
for i := range *in {
if err := Convert_kops_UserData_To_v1alpha1_UserData(&(*in)[i], &(*out)[i], s); err != nil {
return err
}
}
} else {
out.AdditionalUserData = nil
}
return nil
}

Expand Down Expand Up @@ -2606,6 +2630,30 @@ func Convert_kops_SSHCredentialSpec_To_v1alpha1_SSHCredentialSpec(in *kops.SSHCr
return autoConvert_kops_SSHCredentialSpec_To_v1alpha1_SSHCredentialSpec(in, out, s)
}

func autoConvert_v1alpha1_UserData_To_kops_UserData(in *UserData, out *kops.UserData, s conversion.Scope) error {
out.Name = in.Name
out.Type = in.Type
out.Content = in.Content
return nil
}

// Convert_v1alpha1_UserData_To_kops_UserData is an autogenerated conversion function.
func Convert_v1alpha1_UserData_To_kops_UserData(in *UserData, out *kops.UserData, s conversion.Scope) error {
return autoConvert_v1alpha1_UserData_To_kops_UserData(in, out, s)
}

func autoConvert_kops_UserData_To_v1alpha1_UserData(in *kops.UserData, out *UserData, s conversion.Scope) error {
out.Name = in.Name
out.Type = in.Type
out.Content = in.Content
return nil
}

// Convert_kops_UserData_To_v1alpha1_UserData is an autogenerated conversion function.
func Convert_kops_UserData_To_v1alpha1_UserData(in *kops.UserData, out *UserData, s conversion.Scope) error {
return autoConvert_kops_UserData_To_v1alpha1_UserData(in, out, s)
}

func autoConvert_v1alpha1_WeaveNetworkingSpec_To_kops_WeaveNetworkingSpec(in *WeaveNetworkingSpec, out *kops.WeaveNetworkingSpec, s conversion.Scope) error {
out.MTU = in.MTU
return nil
Expand Down
30 changes: 30 additions & 0 deletions pkg/apis/kops/v1alpha1/zz_generated.deepcopy.go
Original file line number Diff line number Diff line change
Expand Up @@ -258,6 +258,10 @@ func RegisterDeepCopies(scheme *runtime.Scheme) error {
in.(*TopologySpec).DeepCopyInto(out.(*TopologySpec))
return nil
}, InType: reflect.TypeOf(&TopologySpec{})},
conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error {
in.(*UserData).DeepCopyInto(out.(*UserData))
return nil
}, InType: reflect.TypeOf(&UserData{})},
conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error {
in.(*WeaveNetworkingSpec).DeepCopyInto(out.(*WeaveNetworkingSpec))
return nil
Expand Down Expand Up @@ -759,6 +763,11 @@ func (in *ClusterSpec) DeepCopyInto(out *ClusterSpec) {
(*in).DeepCopyInto(*out)
}
}
if in.AdditionalSANs != nil {
in, out := &in.AdditionalSANs, &out.AdditionalSANs
*out = make([]string, len(*in))
copy(*out, *in)
}
if in.Multizone != nil {
in, out := &in.Multizone, &out.Multizone
if *in == nil {
Expand Down Expand Up @@ -1776,6 +1785,11 @@ func (in *InstanceGroupSpec) DeepCopyInto(out *InstanceGroupSpec) {
*out = make([]string, len(*in))
copy(*out, *in)
}
if in.AdditionalUserData != nil {
in, out := &in.AdditionalUserData, &out.AdditionalUserData
*out = make([]UserData, len(*in))
copy(*out, *in)
}
if in.Zones != nil {
in, out := &in.Zones, &out.Zones
*out = make([]string, len(*in))
Expand Down Expand Up @@ -2827,6 +2841,22 @@ func (in *TopologySpec) DeepCopy() *TopologySpec {
return out
}

// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *UserData) DeepCopyInto(out *UserData) {
*out = *in
return
}

// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new UserData.
func (in *UserData) DeepCopy() *UserData {
if in == nil {
return nil
}
out := new(UserData)
in.DeepCopyInto(out)
return out
}

// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *WeaveNetworkingSpec) DeepCopyInto(out *WeaveNetworkingSpec) {
*out = *in
Expand Down
12 changes: 12 additions & 0 deletions pkg/apis/kops/v1alpha2/instancegroup.go
Original file line number Diff line number Diff line change
Expand Up @@ -101,4 +101,16 @@ type InstanceGroupSpec struct {
Kubelet *KubeletConfigSpec `json:"kubelet,omitempty"`
// Taints indicates the kubernetes taints for nodes in this group
Taints []string `json:"taints,omitempty"`
// AdditionalUserData is any aditional user-data to be passed to the host
AdditionalUserData []UserData `json:"additionalUserData,omitempty"`
}

// UserData defines a user-data section
type UserData struct {
// Name is the name of the user-data
Name string `json:"name,omitempty"`
// Type is the type of user-data
Type string `json:"type,omitempty"`
// Content is the user-data content
Content string `json:"content,omitempty"`
}
Loading