-
Notifications
You must be signed in to change notification settings - Fork 4.6k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
The current implementation does not make it ease to fully customize nodes before kube install. This PR adds the ability to include file assets in the cluster and instaneGroup spec which can be consumed by nodeup. Allowing those whom need (i.e. me :-)) greater flexibilty around their nodes. @note, nothing is enforced, so unless you've specified anything everything is as the same - updated the cluster_spec.md to reflect the changes - permit users to place inline files into the cluster and instance group specs - added the ability to template the files, the Cluster and InstanceGroup specs are passed into context - cleaned up and missed comment, unordered imports etc along the journey
- Loading branch information
Showing
18 changed files
with
616 additions
and
57 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,154 @@ | ||
/* | ||
Copyright 2016 The Kubernetes Authors. | ||
Licensed under the Apache License, Version 2.0 (the "License"); | ||
you may not use this file except in compliance with the License. | ||
You may obtain a copy of the License at | ||
http://www.apache.org/licenses/LICENSE-2.0 | ||
Unless required by applicable law or agreed to in writing, software | ||
distributed under the License is distributed on an "AS IS" BASIS, | ||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
See the License for the specific language governing permissions and | ||
limitations under the License. | ||
*/ | ||
|
||
package model | ||
|
||
import ( | ||
"encoding/base64" | ||
"errors" | ||
"fmt" | ||
"os" | ||
"path/filepath" | ||
"strings" | ||
"text/template" | ||
|
||
"k8s.io/kops/pkg/apis/kops" | ||
"k8s.io/kops/pkg/model" | ||
"k8s.io/kops/upup/pkg/fi" | ||
"k8s.io/kops/upup/pkg/fi/nodeup/nodetasks" | ||
) | ||
|
||
// FileAssetsBuilder configures the hooks | ||
type FileAssetsBuilder struct { | ||
*NodeupModelContext | ||
} | ||
|
||
var _ fi.ModelBuilder = &FileAssetsBuilder{} | ||
|
||
// Build is responsible for writing out the file assets from cluster and instanceGroup | ||
func (f *FileAssetsBuilder) Build(c *fi.ModelBuilderContext) error { | ||
// used to keep track of previous file, so a instanceGroup can override a cluster wide one | ||
tracker := make(map[string]bool, 0) | ||
// do we have any instanceGroup file assets | ||
if f.InstanceGroup.Spec.FileAssets != nil { | ||
if err := f.buildFileAssets(c, f.InstanceGroup.Spec.FileAssets, tracker); err != nil { | ||
return err | ||
} | ||
} | ||
if f.Cluster.Spec.FileAssets != nil { | ||
if err := f.buildFileAssets(c, f.Cluster.Spec.FileAssets, tracker); err != nil { | ||
return err | ||
} | ||
} | ||
|
||
return nil | ||
} | ||
|
||
// buildFileAssets is responsible for rendering the file assets to disk | ||
func (f *FileAssetsBuilder) buildFileAssets(c *fi.ModelBuilderContext, assets []*kops.FileAssetSpec, tracker map[string]bool) error { | ||
for _, asset := range assets { | ||
if err := validateFileAsset(asset); err != nil { | ||
return fmt.Errorf("The file asset is invalid, name: %s, error: %q", asset.Name, err) | ||
} | ||
// @check if the file has already been done | ||
if _, found := tracker[asset.Path]; found { | ||
continue | ||
} | ||
tracker[asset.Path] = true // update the tracker | ||
|
||
// fill in the defaults for the file perms | ||
if asset.Mode == "" { | ||
asset.Mode = "0400" | ||
} | ||
// @check is the contents requires decoding | ||
content := asset.Content | ||
if asset.IsBase64 { | ||
decoded, err := base64.RawStdEncoding.DecodeString(content) | ||
if err != nil { | ||
return fmt.Errorf("Failed on file asset: %s is invalid, unable to decode base64, error: %q", asset.Name, err) | ||
} | ||
content = string(decoded) | ||
} | ||
|
||
// @check if the directory structure exist or create it | ||
if err := os.MkdirAll(filepath.Dir(asset.Path), 0755); err != nil { | ||
return fmt.Errorf("Failed on file asset: %s, unable to ensure directory: %s, error: %q", asset.Name, | ||
filepath.Dir(asset.Path), err) | ||
} | ||
|
||
// @check the file permissions | ||
perms := asset.Mode | ||
if !strings.HasPrefix(perms, "0") { | ||
perms = fmt.Sprintf("%d%s", 0, perms) | ||
} | ||
|
||
var resource fi.Resource | ||
var err error | ||
switch asset.Templated { | ||
case true: | ||
resource, err = f.getRenderedResource(content) | ||
if err != nil { | ||
return fmt.Errorf("Failed on file assets: %s, build rendered resource, error: %q", asset.Name, err) | ||
} | ||
default: | ||
resource = fi.NewStringResource(content) | ||
} | ||
|
||
c.AddTask(&nodetasks.File{ | ||
Contents: resource, | ||
Mode: s(perms), | ||
Path: asset.Path, | ||
Type: nodetasks.FileType_File, | ||
}) | ||
} | ||
|
||
return nil | ||
} | ||
|
||
// @perhaps a path finder? | ||
var templateFuncs = template.FuncMap{ | ||
"split": strings.Split, | ||
"join": strings.Join, | ||
} | ||
|
||
// getRenderedResource is responsible for rendering the content if templated | ||
func (f *FileAssetsBuilder) getRenderedResource(content string) (fi.Resource, error) { | ||
context := map[string]interface{}{ | ||
"Cluster": f.Cluster.Spec, | ||
"InstanceGroup": f.InstanceGroup.Spec, | ||
"Master": fmt.Sprintf("%t", f.IsMaster), | ||
"Name": f.InstanceGroup.Name, | ||
} | ||
|
||
resource, err := model.NewTemplateResource("FileAsset", content, templateFuncs, context) | ||
if err != nil { | ||
return nil, err | ||
} | ||
|
||
return resource, nil | ||
} | ||
|
||
// validateFileAsset performs some basic validation on the asset | ||
func validateFileAsset(asset *kops.FileAssetSpec) error { | ||
if asset.Path == "" { | ||
return errors.New("does not have a path") | ||
} | ||
if asset.Content == "" { | ||
return errors.New("does not have any contents") | ||
} | ||
|
||
return nil | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,123 @@ | ||
/* | ||
Copyright 2016 The Kubernetes Authors. | ||
Licensed under the Apache License, Version 2.0 (the "License"); | ||
you may not use this file except in compliance with the License. | ||
You may obtain a copy of the License at | ||
http://www.apache.org/licenses/LICENSE-2.0 | ||
Unless required by applicable law or agreed to in writing, software | ||
distributed under the License is distributed on an "AS IS" BASIS, | ||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
See the License for the specific language governing permissions and | ||
limitations under the License. | ||
*/ | ||
|
||
package model | ||
|
||
import ( | ||
"io/ioutil" | ||
"testing" | ||
|
||
"k8s.io/kops/pkg/apis/kops" | ||
) | ||
|
||
func TestFileAssetRenderOK(t *testing.T) { | ||
cs := []struct { | ||
Content string | ||
Expected string | ||
}{ | ||
{}, | ||
{ | ||
Content: "{{ .Cluster.NetworkCIDR }}", | ||
Expected: "10.79.0.0/24", | ||
}, | ||
{ | ||
Content: ` | ||
*filter | ||
:INPUT ACCEPT [0:0] | ||
-A INPUT -i docker0 -p tcp -m tcp --dport 22 -m state --state NEW -j REJECT | ||
-A INPUT -i docker0 -p tcp -m tcp --dport 10250 -m state --state NEW -j REJECT | ||
-A INPUT -p tcp -m state -s {{ .Cluster.NonMasqueradeCIDR }} --dport 22 --state NEW -j REJECT | ||
:FORWARD ACCEPT [0:0] | ||
-A FORWARD -i docker0 -p tcp -m tcp -d 169.254.169.254/32 --dport 80 -m state --state NEW -j REJECT | ||
-A FORWARD -i docker0 -p tcp -m tcp --dport 2379 -m state --state NEW -j REJECT | ||
-A FORWARD -i docker0 -p tcp -m tcp -d {{ .Cluster.NetworkCIDR }} --dport 22 -m state --state NEW -j REJECT | ||
-A FORWARD -i docker0 -p tcp -m tcp -d {{ .Cluster.NetworkCIDR }} --dport 10250 -m state --state NEW -j REJECT | ||
:OUTPUT ACCEPT [0:0] | ||
COMMIT`, | ||
Expected: ` | ||
*filter | ||
:INPUT ACCEPT [0:0] | ||
-A INPUT -i docker0 -p tcp -m tcp --dport 22 -m state --state NEW -j REJECT | ||
-A INPUT -i docker0 -p tcp -m tcp --dport 10250 -m state --state NEW -j REJECT | ||
-A INPUT -p tcp -m state -s 10.100.0.0/16 --dport 22 --state NEW -j REJECT | ||
:FORWARD ACCEPT [0:0] | ||
-A FORWARD -i docker0 -p tcp -m tcp -d 169.254.169.254/32 --dport 80 -m state --state NEW -j REJECT | ||
-A FORWARD -i docker0 -p tcp -m tcp --dport 2379 -m state --state NEW -j REJECT | ||
-A FORWARD -i docker0 -p tcp -m tcp -d 10.79.0.0/24 --dport 22 -m state --state NEW -j REJECT | ||
-A FORWARD -i docker0 -p tcp -m tcp -d 10.79.0.0/24 --dport 10250 -m state --state NEW -j REJECT | ||
:OUTPUT ACCEPT [0:0] | ||
COMMIT`, | ||
}, | ||
} | ||
cluster := makeTestCluster() | ||
group := makeTestInstanceGroup() | ||
|
||
for i, x := range cs { | ||
fb := &FileAssetsBuilder{ | ||
NodeupModelContext: &NodeupModelContext{Cluster: cluster, InstanceGroup: group}, | ||
} | ||
resource, err := fb.getRenderedResource(x.Content) | ||
if err != nil { | ||
t.Errorf("case %d failed to create resource. error: %s", i, err) | ||
continue | ||
} | ||
reader, err := resource.Open() | ||
if err != nil { | ||
t.Errorf("case %d failed to render resource, error: %s", i, err) | ||
continue | ||
} | ||
rendered, err := ioutil.ReadAll(reader) | ||
if err != nil { | ||
t.Errorf("case %d failed to read reander, error: %s", i, err) | ||
continue | ||
} | ||
|
||
if string(rendered) != x.Expected { | ||
t.Errorf("case %d, expected: %s. got: %s", i, x.Expected, rendered) | ||
} | ||
} | ||
} | ||
|
||
func makeTestCluster() *kops.Cluster { | ||
return &kops.Cluster{ | ||
Spec: kops.ClusterSpec{ | ||
CloudProvider: "aws", | ||
KubernetesVersion: "1.7.0", | ||
Subnets: []kops.ClusterSubnetSpec{ | ||
{Name: "test", Zone: "eu-west-1a"}, | ||
}, | ||
NonMasqueradeCIDR: "10.100.0.0/16", | ||
EtcdClusters: []*kops.EtcdClusterSpec{ | ||
{ | ||
Name: "main", | ||
Members: []*kops.EtcdMemberSpec{ | ||
{ | ||
Name: "test", | ||
InstanceGroup: s("master-1"), | ||
}, | ||
}, | ||
}, | ||
}, | ||
NetworkCIDR: "10.79.0.0/24", | ||
}, | ||
} | ||
} | ||
|
||
func makeTestInstanceGroup() *kops.InstanceGroup { | ||
return &kops.InstanceGroup{ | ||
Spec: kops.InstanceGroupSpec{}, | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.